fix build error
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
**⚠️ API Instability Warning: The AudioTee API is incredibly unstable at present and subject to change without notice.**
|
**⚠️ 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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ public class AudioBuffer {
|
|||||||
private var readIndex: Int = 0
|
private var readIndex: Int = 0
|
||||||
private var availableBytes: Int = 0
|
private var availableBytes: Int = 0
|
||||||
private let maxBufferSize: Int
|
private let maxBufferSize: Int
|
||||||
|
|
||||||
private let bytesPerChunk: Int
|
private let bytesPerChunk: Int
|
||||||
private let chunkDuration: Double
|
private let chunkDuration: Double
|
||||||
|
|
||||||
public init(format: AudioStreamBasicDescription, chunkDuration: Double = 0.2) {
|
public init(format: AudioStreamBasicDescription, chunkDuration: Double = 0.2) {
|
||||||
|
|
||||||
// Pre-calculate chunk parameters
|
// Pre-calculate chunk parameters
|
||||||
let bytesPerFrame = Int(format.mBytesPerFrame)
|
let bytesPerFrame = Int(format.mBytesPerFrame)
|
||||||
let samplesPerChunk = Int(format.mSampleRate * chunkDuration)
|
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
|
// Calculate max buffer size to hold ~10 seconds of audio, way more than the maximum we allow
|
||||||
let bytesPerSecond = Int(format.mSampleRate) * bytesPerFrame
|
let bytesPerSecond = Int(format.mSampleRate) * bytesPerFrame
|
||||||
self.maxBufferSize = bytesPerSecond * 10
|
self.maxBufferSize = bytesPerSecond * 10
|
||||||
|
|
||||||
// Pre-allocated ring buffer
|
// Pre-allocated ring buffer
|
||||||
self.buffer = Array(repeating: 0, count: maxBufferSize)
|
self.buffer = Array(repeating: 0, count: maxBufferSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func append(_ data: Data) {
|
public func append(_ data: Data) {
|
||||||
guard availableBytes + data.count <= maxBufferSize else {
|
guard availableBytes + data.count <= maxBufferSize else {
|
||||||
Logger.error("Audio buffer overflow", context: [
|
Logger.error(
|
||||||
"requested": String(data.count),
|
"Audio buffer overflow",
|
||||||
"available": String(maxBufferSize - availableBytes)
|
context: [
|
||||||
])
|
"requested": String(data.count),
|
||||||
|
"available": String(maxBufferSize - availableBytes),
|
||||||
|
])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data.withUnsafeBytes { bytes in
|
data.withUnsafeBytes { bytes in
|
||||||
let sourceBytes = bytes.bindMemory(to: UInt8.self)
|
let sourceBytes = bytes.bindMemory(to: UInt8.self)
|
||||||
let dataSize = sourceBytes.count
|
let dataSize = sourceBytes.count
|
||||||
|
|
||||||
// Check if we can copy in one block (no wrap-around)
|
// Check if we can copy in one block (no wrap-around)
|
||||||
if writeIndex + dataSize <= maxBufferSize {
|
if writeIndex + dataSize <= maxBufferSize {
|
||||||
// only one write needed
|
// only one write needed
|
||||||
@@ -49,14 +51,14 @@ public class AudioBuffer {
|
|||||||
// two writes needed due to wrap-around
|
// two writes needed due to wrap-around
|
||||||
let firstChunkSize = maxBufferSize - writeIndex
|
let firstChunkSize = maxBufferSize - writeIndex
|
||||||
let secondChunkSize = dataSize - firstChunkSize
|
let secondChunkSize = dataSize - firstChunkSize
|
||||||
|
|
||||||
buffer.replaceSubrange(writeIndex..<maxBufferSize, with: sourceBytes.prefix(firstChunkSize))
|
buffer.replaceSubrange(writeIndex..<maxBufferSize, with: sourceBytes.prefix(firstChunkSize))
|
||||||
buffer.replaceSubrange(0..<secondChunkSize, with: sourceBytes.suffix(secondChunkSize))
|
buffer.replaceSubrange(0..<secondChunkSize, with: sourceBytes.suffix(secondChunkSize))
|
||||||
|
|
||||||
writeIndex = secondChunkSize
|
writeIndex = secondChunkSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
availableBytes += data.count
|
availableBytes += data.count
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +77,7 @@ public class AudioBuffer {
|
|||||||
guard availableBytes >= bytesPerChunk else { return nil }
|
guard availableBytes >= bytesPerChunk else { return nil }
|
||||||
|
|
||||||
var chunkData = Data(capacity: bytesPerChunk)
|
var chunkData = Data(capacity: bytesPerChunk)
|
||||||
|
|
||||||
// Check if we can copy in one block (no wrap-around)
|
// Check if we can copy in one block (no wrap-around)
|
||||||
if readIndex + bytesPerChunk <= maxBufferSize {
|
if readIndex + bytesPerChunk <= maxBufferSize {
|
||||||
// one copy needed
|
// one copy needed
|
||||||
@@ -85,19 +87,18 @@ public class AudioBuffer {
|
|||||||
// two copies needed due to wrap-around
|
// two copies needed due to wrap-around
|
||||||
let firstChunkSize = maxBufferSize - readIndex
|
let firstChunkSize = maxBufferSize - readIndex
|
||||||
let secondChunkSize = bytesPerChunk - firstChunkSize
|
let secondChunkSize = bytesPerChunk - firstChunkSize
|
||||||
|
|
||||||
chunkData.append(contentsOf: buffer[readIndex..<maxBufferSize])
|
chunkData.append(contentsOf: buffer[readIndex..<maxBufferSize])
|
||||||
chunkData.append(contentsOf: buffer[0..<secondChunkSize])
|
chunkData.append(contentsOf: buffer[0..<secondChunkSize])
|
||||||
|
|
||||||
readIndex = secondChunkSize
|
readIndex = secondChunkSize
|
||||||
}
|
}
|
||||||
|
|
||||||
availableBytes -= bytesPerChunk
|
availableBytes -= bytesPerChunk
|
||||||
|
|
||||||
let packet = AudioPacket(
|
let packet = AudioPacket(
|
||||||
timestamp: Date(),
|
timestamp: Date(),
|
||||||
duration: chunkDuration,
|
duration: chunkDuration,
|
||||||
peakAmplitude: 0.0,
|
|
||||||
rawAudioData: chunkData
|
rawAudioData: chunkData
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user