From a311cc583fc81c470028d13eecfff9b43ea9b361 Mon Sep 17 00:00:00 2001 From: Nick Payne Date: Tue, 12 Aug 2025 13:37:39 +0100 Subject: [PATCH] simplify recorder --- Sources/Core/AudioFormatConverter.swift | 3 +- Sources/Core/AudioRecorder.swift | 36 +++++++------------ Sources/Core/AudioTapManager.swift | 8 ----- .../Output/Handlers/BinaryOutputHandler.swift | 4 +-- 4 files changed, 16 insertions(+), 35 deletions(-) diff --git a/Sources/Core/AudioFormatConverter.swift b/Sources/Core/AudioFormatConverter.swift index 01692e7..d689629 100644 --- a/Sources/Core/AudioFormatConverter.swift +++ b/Sources/Core/AudioFormatConverter.swift @@ -133,7 +133,8 @@ public class AudioFormatConverter { targetFormat.mFramesPerPacket = 1 targetFormat.mBitsPerChannel = 16 targetFormat.mChannelsPerFrame = sourceFormat.mChannelsPerFrame - targetFormat.mBytesPerFrame = (targetFormat.mBitsPerChannel / 8) * sourceFormat.mChannelsPerFrame + targetFormat.mBytesPerFrame = + (targetFormat.mBitsPerChannel / 8) * sourceFormat.mChannelsPerFrame targetFormat.mBytesPerPacket = targetFormat.mFramesPerPacket * targetFormat.mBytesPerFrame return try AudioFormatConverter(sourceFormat: sourceFormat, targetFormat: targetFormat) diff --git a/Sources/Core/AudioRecorder.swift b/Sources/Core/AudioRecorder.swift index e23ecb3..c093178 100644 --- a/Sources/Core/AudioRecorder.swift +++ b/Sources/Core/AudioRecorder.swift @@ -5,12 +5,10 @@ import Foundation public class AudioRecorder { private var deviceID: AudioObjectID private var ioProcID: AudioDeviceIOProcID? - private var sourceFormat: AudioStreamBasicDescription? - private var finalFormat: AudioStreamBasicDescription? + private var finalFormat: AudioStreamBasicDescription! private var audioBuffer: AudioBuffer? private var outputHandler: AudioOutputHandler private var converter: AudioFormatConverter? - private var chunkDuration: Double init( deviceID: AudioObjectID, outputHandler: AudioOutputHandler, convertToSampleRate: Double? = nil, @@ -18,11 +16,12 @@ public class AudioRecorder { ) { self.deviceID = deviceID self.outputHandler = outputHandler - self.chunkDuration = chunkDuration // Get source format and set up conversion if requested let sourceFormat = AudioFormatManager.getDeviceFormat(deviceID: deviceID) - self.sourceFormat = sourceFormat + + // Set up the audio buffer using source format and configurable chunk duration + self.audioBuffer = AudioBuffer(format: sourceFormat, chunkDuration: chunkDuration) if let targetSampleRate = convertToSampleRate { // Validate sample rate @@ -55,20 +54,12 @@ public class AudioRecorder { func startRecording() { Logger.debug("Starting audio recording") - guard let sourceFormat = sourceFormat, let finalFormat = finalFormat else { - fatalError("Audio formats not initialized") - } - - // Set up the audio buffer using source format and configurable chunk duration - self.audioBuffer = AudioBuffer(format: sourceFormat, chunkDuration: chunkDuration) - - // Log format info and send metadata for FINAL format + // Log format info and send metadata for final format AudioFormatManager.logFormatInfo(finalFormat) let metadata = AudioFormatManager.createMetadata(for: finalFormat) outputHandler.handleMetadata(metadata) outputHandler.handleStreamStart() - // Set up and start the IO proc setupAndStartIOProc() Logger.info("Audio device started successfully") @@ -116,24 +107,23 @@ public class AudioRecorder { let audioData = Data(bytes: firstBuffer.mData!, count: Int(firstBuffer.mDataByteSize)) audioBuffer?.append(audioData) - // Process and send complete chunks, applying conversion if needed - audioBuffer?.processChunks().forEach { packet in - let processedPacket = converter?.transform(packet) ?? packet - outputHandler.handleAudioPacket(processedPacket) - } + processAudioBuffer() return noErr } func stopRecording() { - // Send any remaining buffered audio, applying conversion if needed + processAudioBuffer() + outputHandler.handleStreamStop() + cleanupIOProc() + } + + private func processAudioBuffer() { + // Process and send complete chunks, applying conversion if needed audioBuffer?.processChunks().forEach { packet in let processedPacket = converter?.transform(packet) ?? packet outputHandler.handleAudioPacket(processedPacket) } - - outputHandler.handleStreamStop() - cleanupIOProc() } private func cleanupIOProc() { diff --git a/Sources/Core/AudioTapManager.swift b/Sources/Core/AudioTapManager.swift index 1476005..893473f 100644 --- a/Sources/Core/AudioTapManager.swift +++ b/Sources/Core/AudioTapManager.swift @@ -7,10 +7,6 @@ class AudioTapManager { private var tapID: AudioObjectID? private var deviceID: AudioObjectID? - init() { - // Empty init - setup happens in setupAudioTap() - } - deinit { Logger.debug("Cleaning up audio tap manager") @@ -48,10 +44,8 @@ class AudioTapManager { private func createSystemAudioTap(with config: TapConfiguration) throws -> AudioObjectID { Logger.debug("Creating tap description") - // Create a tap description let description = CATapDescription() - // Configure the tap to capture all system audio description.name = "audiotee-tap" description.processes = try translatePIDsToProcessObjects(config.processes) // Properly translate PIDs description.isPrivate = true @@ -67,9 +61,7 @@ class AudioTapManager { context: [ "name": description.name, "processes": String(describing: config.processes), - "private": String(description.isPrivate), "mute": String(describing: description.muteBehavior), - "mixdown": String(description.isMixdown), "mono": String(description.isMono), "exclusive": String(description.isExclusive), ]) diff --git a/Sources/Output/Handlers/BinaryOutputHandler.swift b/Sources/Output/Handlers/BinaryOutputHandler.swift index cc182a0..a932353 100644 --- a/Sources/Output/Handlers/BinaryOutputHandler.swift +++ b/Sources/Output/Handlers/BinaryOutputHandler.swift @@ -1,10 +1,8 @@ import Foundation -/// Binary output with JSON headers (pipe-optimised) public class BinaryAudioOutputHandler: AudioOutputHandler { - public init() {} - public func handleAudioPacket(_ packet: AudioPacket) { + // TODO: should we use a DispatchQueue instead of writing directly? // Write raw binary audio data directly to stdout FileHandle.standardOutput.write(packet.data) }