simplify recorder
This commit is contained in:
@@ -133,7 +133,8 @@ public class AudioFormatConverter {
|
|||||||
targetFormat.mFramesPerPacket = 1
|
targetFormat.mFramesPerPacket = 1
|
||||||
targetFormat.mBitsPerChannel = 16
|
targetFormat.mBitsPerChannel = 16
|
||||||
targetFormat.mChannelsPerFrame = sourceFormat.mChannelsPerFrame
|
targetFormat.mChannelsPerFrame = sourceFormat.mChannelsPerFrame
|
||||||
targetFormat.mBytesPerFrame = (targetFormat.mBitsPerChannel / 8) * sourceFormat.mChannelsPerFrame
|
targetFormat.mBytesPerFrame =
|
||||||
|
(targetFormat.mBitsPerChannel / 8) * sourceFormat.mChannelsPerFrame
|
||||||
targetFormat.mBytesPerPacket = targetFormat.mFramesPerPacket * targetFormat.mBytesPerFrame
|
targetFormat.mBytesPerPacket = targetFormat.mFramesPerPacket * targetFormat.mBytesPerFrame
|
||||||
|
|
||||||
return try AudioFormatConverter(sourceFormat: sourceFormat, targetFormat: targetFormat)
|
return try AudioFormatConverter(sourceFormat: sourceFormat, targetFormat: targetFormat)
|
||||||
|
|||||||
@@ -5,12 +5,10 @@ import Foundation
|
|||||||
public class AudioRecorder {
|
public class AudioRecorder {
|
||||||
private var deviceID: AudioObjectID
|
private var deviceID: AudioObjectID
|
||||||
private var ioProcID: AudioDeviceIOProcID?
|
private var ioProcID: AudioDeviceIOProcID?
|
||||||
private var sourceFormat: AudioStreamBasicDescription?
|
private var finalFormat: AudioStreamBasicDescription!
|
||||||
private var finalFormat: AudioStreamBasicDescription?
|
|
||||||
private var audioBuffer: AudioBuffer?
|
private var audioBuffer: AudioBuffer?
|
||||||
private var outputHandler: AudioOutputHandler
|
private var outputHandler: AudioOutputHandler
|
||||||
private var converter: AudioFormatConverter?
|
private var converter: AudioFormatConverter?
|
||||||
private var chunkDuration: Double
|
|
||||||
|
|
||||||
init(
|
init(
|
||||||
deviceID: AudioObjectID, outputHandler: AudioOutputHandler, convertToSampleRate: Double? = nil,
|
deviceID: AudioObjectID, outputHandler: AudioOutputHandler, convertToSampleRate: Double? = nil,
|
||||||
@@ -18,11 +16,12 @@ public class AudioRecorder {
|
|||||||
) {
|
) {
|
||||||
self.deviceID = deviceID
|
self.deviceID = deviceID
|
||||||
self.outputHandler = outputHandler
|
self.outputHandler = outputHandler
|
||||||
self.chunkDuration = chunkDuration
|
|
||||||
|
|
||||||
// Get source format and set up conversion if requested
|
// Get source format and set up conversion if requested
|
||||||
let sourceFormat = AudioFormatManager.getDeviceFormat(deviceID: deviceID)
|
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 {
|
if let targetSampleRate = convertToSampleRate {
|
||||||
// Validate sample rate
|
// Validate sample rate
|
||||||
@@ -55,20 +54,12 @@ public class AudioRecorder {
|
|||||||
func startRecording() {
|
func startRecording() {
|
||||||
Logger.debug("Starting audio recording")
|
Logger.debug("Starting audio recording")
|
||||||
|
|
||||||
guard let sourceFormat = sourceFormat, let finalFormat = finalFormat else {
|
// Log format info and send metadata for final format
|
||||||
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
|
|
||||||
AudioFormatManager.logFormatInfo(finalFormat)
|
AudioFormatManager.logFormatInfo(finalFormat)
|
||||||
let metadata = AudioFormatManager.createMetadata(for: finalFormat)
|
let metadata = AudioFormatManager.createMetadata(for: finalFormat)
|
||||||
outputHandler.handleMetadata(metadata)
|
outputHandler.handleMetadata(metadata)
|
||||||
outputHandler.handleStreamStart()
|
outputHandler.handleStreamStart()
|
||||||
|
|
||||||
// Set up and start the IO proc
|
|
||||||
setupAndStartIOProc()
|
setupAndStartIOProc()
|
||||||
|
|
||||||
Logger.info("Audio device started successfully")
|
Logger.info("Audio device started successfully")
|
||||||
@@ -116,24 +107,23 @@ public class AudioRecorder {
|
|||||||
let audioData = Data(bytes: firstBuffer.mData!, count: Int(firstBuffer.mDataByteSize))
|
let audioData = Data(bytes: firstBuffer.mData!, count: Int(firstBuffer.mDataByteSize))
|
||||||
audioBuffer?.append(audioData)
|
audioBuffer?.append(audioData)
|
||||||
|
|
||||||
// Process and send complete chunks, applying conversion if needed
|
processAudioBuffer()
|
||||||
audioBuffer?.processChunks().forEach { packet in
|
|
||||||
let processedPacket = converter?.transform(packet) ?? packet
|
|
||||||
outputHandler.handleAudioPacket(processedPacket)
|
|
||||||
}
|
|
||||||
|
|
||||||
return noErr
|
return noErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopRecording() {
|
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
|
audioBuffer?.processChunks().forEach { packet in
|
||||||
let processedPacket = converter?.transform(packet) ?? packet
|
let processedPacket = converter?.transform(packet) ?? packet
|
||||||
outputHandler.handleAudioPacket(processedPacket)
|
outputHandler.handleAudioPacket(processedPacket)
|
||||||
}
|
}
|
||||||
|
|
||||||
outputHandler.handleStreamStop()
|
|
||||||
cleanupIOProc()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func cleanupIOProc() {
|
private func cleanupIOProc() {
|
||||||
|
|||||||
@@ -7,10 +7,6 @@ class AudioTapManager {
|
|||||||
private var tapID: AudioObjectID?
|
private var tapID: AudioObjectID?
|
||||||
private var deviceID: AudioObjectID?
|
private var deviceID: AudioObjectID?
|
||||||
|
|
||||||
init() {
|
|
||||||
// Empty init - setup happens in setupAudioTap()
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
Logger.debug("Cleaning up audio tap manager")
|
Logger.debug("Cleaning up audio tap manager")
|
||||||
|
|
||||||
@@ -48,10 +44,8 @@ class AudioTapManager {
|
|||||||
|
|
||||||
private func createSystemAudioTap(with config: TapConfiguration) throws -> AudioObjectID {
|
private func createSystemAudioTap(with config: TapConfiguration) throws -> AudioObjectID {
|
||||||
Logger.debug("Creating tap description")
|
Logger.debug("Creating tap description")
|
||||||
// Create a tap description
|
|
||||||
let description = CATapDescription()
|
let description = CATapDescription()
|
||||||
|
|
||||||
// Configure the tap to capture all system audio
|
|
||||||
description.name = "audiotee-tap"
|
description.name = "audiotee-tap"
|
||||||
description.processes = try translatePIDsToProcessObjects(config.processes) // Properly translate PIDs
|
description.processes = try translatePIDsToProcessObjects(config.processes) // Properly translate PIDs
|
||||||
description.isPrivate = true
|
description.isPrivate = true
|
||||||
@@ -67,9 +61,7 @@ class AudioTapManager {
|
|||||||
context: [
|
context: [
|
||||||
"name": description.name,
|
"name": description.name,
|
||||||
"processes": String(describing: config.processes),
|
"processes": String(describing: config.processes),
|
||||||
"private": String(description.isPrivate),
|
|
||||||
"mute": String(describing: description.muteBehavior),
|
"mute": String(describing: description.muteBehavior),
|
||||||
"mixdown": String(description.isMixdown),
|
|
||||||
"mono": String(description.isMono),
|
"mono": String(description.isMono),
|
||||||
"exclusive": String(description.isExclusive),
|
"exclusive": String(description.isExclusive),
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// Binary output with JSON headers (pipe-optimised)
|
|
||||||
public class BinaryAudioOutputHandler: AudioOutputHandler {
|
public class BinaryAudioOutputHandler: AudioOutputHandler {
|
||||||
public init() {}
|
|
||||||
|
|
||||||
public func handleAudioPacket(_ packet: AudioPacket) {
|
public func handleAudioPacket(_ packet: AudioPacket) {
|
||||||
|
// TODO: should we use a DispatchQueue instead of writing directly?
|
||||||
// Write raw binary audio data directly to stdout
|
// Write raw binary audio data directly to stdout
|
||||||
FileHandle.standardOutput.write(packet.data)
|
FileHandle.standardOutput.write(packet.data)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user