diff --git a/README.md b/README.md index a316ea8..f8bedb6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **⚠️ API Instability Warning: The AudioTee API is incredibly unstable at present and subject to change without notice.** -AudioTee captures your Mac's system audio output and writes raw PCM audio data directly to `stdout`. All logging and metadata information is written to `stderr`, allowing for clean redirection of audio data to files or pipes. It uses the [Core Audio taps](https://developer.apple.com/documentation/coreaudio/capturing-system-audio-with-core-audio-taps) API introduced in macOS 14.2 (released in December 2023). You can do whatever you want with this audio - stream it somewhere else, save it to disk, visualise it, etc. +AudioTee captures your Mac's system audio output and writes it in PCM encoded chunks to `stdout` at regular intervals. All logging and metadata information is written to `stderr`, allowing for clean redirection of audio data to files or pipes. It uses the [Core Audio taps](https://developer.apple.com/documentation/coreaudio/capturing-system-audio-with-core-audio-taps) API introduced in macOS 14.2 (released in December 2023). You can do whatever you want with this audio - stream it somewhere else, save it to disk, visualise it, etc. By default, it taps the audio output from **all** running processes. Tap output is forced to `mono` (not yet configurable) and preserves your output device's sample rate (configurable via the `--sample-rate` flag). Only the default output device is currently supported. diff --git a/Sources/Core/AudioBuffer.swift b/Sources/Core/AudioBuffer.swift index 4d4bfc7..80f8f4e 100644 --- a/Sources/Core/AudioBuffer.swift +++ b/Sources/Core/AudioBuffer.swift @@ -7,12 +7,12 @@ public class AudioBuffer { private var readIndex: Int = 0 private var availableBytes: Int = 0 private let maxBufferSize: Int - + private let bytesPerChunk: Int private let chunkDuration: Double public init(format: AudioStreamBasicDescription, chunkDuration: Double = 0.2) { - + // Pre-calculate chunk parameters let bytesPerFrame = Int(format.mBytesPerFrame) let samplesPerChunk = Int(format.mSampleRate * chunkDuration) @@ -22,24 +22,26 @@ public class AudioBuffer { // Calculate max buffer size to hold ~10 seconds of audio, way more than the maximum we allow let bytesPerSecond = Int(format.mSampleRate) * bytesPerFrame self.maxBufferSize = bytesPerSecond * 10 - + // Pre-allocated ring buffer self.buffer = Array(repeating: 0, count: maxBufferSize) } public func append(_ data: Data) { guard availableBytes + data.count <= maxBufferSize else { - Logger.error("Audio buffer overflow", context: [ - "requested": String(data.count), - "available": String(maxBufferSize - availableBytes) - ]) + Logger.error( + "Audio buffer overflow", + context: [ + "requested": String(data.count), + "available": String(maxBufferSize - availableBytes), + ]) return } - + data.withUnsafeBytes { bytes in let sourceBytes = bytes.bindMemory(to: UInt8.self) let dataSize = sourceBytes.count - + // Check if we can copy in one block (no wrap-around) if writeIndex + dataSize <= maxBufferSize { // only one write needed @@ -49,14 +51,14 @@ public class AudioBuffer { // two writes needed due to wrap-around let firstChunkSize = maxBufferSize - writeIndex let secondChunkSize = dataSize - firstChunkSize - + buffer.replaceSubrange(writeIndex..= bytesPerChunk else { return nil } var chunkData = Data(capacity: bytesPerChunk) - + // Check if we can copy in one block (no wrap-around) if readIndex + bytesPerChunk <= maxBufferSize { // one copy needed @@ -85,19 +87,18 @@ public class AudioBuffer { // two copies needed due to wrap-around let firstChunkSize = maxBufferSize - readIndex let secondChunkSize = bytesPerChunk - firstChunkSize - + chunkData.append(contentsOf: buffer[readIndex..