simplify recorder

This commit is contained in:
Nick Payne
2025-08-12 13:37:39 +01:00
parent 8b3de5918c
commit a311cc583f
4 changed files with 16 additions and 35 deletions
+2 -1
View File
@@ -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)
+13 -23
View File
@@ -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() {
-8
View File
@@ -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),
])
@@ -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)
}