Add --flush option to reduce stdout buffering latency

This commit is contained in:
phritz
2026-01-24 10:36:58 -06:00
parent a311cc583f
commit 0b1ba5c8cd
2 changed files with 14 additions and 2 deletions
+5 -1
View File
@@ -8,6 +8,7 @@ struct AudioTee {
var stereo: Bool = false var stereo: Bool = false
var sampleRate: Double? var sampleRate: Double?
var chunkDuration: Double = 0.2 var chunkDuration: Double = 0.2
var flush: Bool = false
init() {} init() {}
@@ -31,6 +32,7 @@ struct AudioTee {
audiotee --include-processes 1234 5678 9012 # Tap only these processes audiotee --include-processes 1234 5678 9012 # Tap only these processes
audiotee --exclude-processes 1234 5678 # Tap everything except these audiotee --exclude-processes 1234 5678 # Tap everything except these
audiotee --mute # Mute processes being tapped audiotee --mute # Mute processes being tapped
audiotee --flush # Flush stdout after each chunk
""" """
) )
@@ -42,6 +44,7 @@ struct AudioTee {
name: "exclude-processes", help: "Process IDs to exclude (space-separated)") name: "exclude-processes", help: "Process IDs to exclude (space-separated)")
parser.addFlag(name: "mute", help: "Mute processes being tapped") parser.addFlag(name: "mute", help: "Mute processes being tapped")
parser.addFlag(name: "stereo", help: "Records in stereo") parser.addFlag(name: "stereo", help: "Records in stereo")
parser.addFlag(name: "flush", help: "Flush stdout after each audio chunk (reduces latency when piping)")
parser.addOption( parser.addOption(
name: "sample-rate", name: "sample-rate",
help: "Target sample rate (8000, 16000, 22050, 24000, 32000, 44100, 48000)") help: "Target sample rate (8000, 16000, 22050, 24000, 32000, 44100, 48000)")
@@ -59,6 +62,7 @@ struct AudioTee {
audioTee.excludeProcesses = try parser.getArrayValue("exclude-processes", as: Int32.self) audioTee.excludeProcesses = try parser.getArrayValue("exclude-processes", as: Int32.self)
audioTee.mute = parser.getFlag("mute") audioTee.mute = parser.getFlag("mute")
audioTee.stereo = parser.getFlag("stereo") audioTee.stereo = parser.getFlag("stereo")
audioTee.flush = parser.getFlag("flush")
audioTee.sampleRate = try parser.getOptionalValue("sample-rate", as: Double.self) audioTee.sampleRate = try parser.getOptionalValue("sample-rate", as: Double.self)
audioTee.chunkDuration = try parser.getValue("chunk-duration", as: Double.self) audioTee.chunkDuration = try parser.getValue("chunk-duration", as: Double.self)
@@ -136,7 +140,7 @@ struct AudioTee {
throw ExitCode.failure throw ExitCode.failure
} }
let outputHandler = BinaryAudioOutputHandler() let outputHandler = BinaryAudioOutputHandler(flushAfterWrite: flush)
let recorder = AudioRecorder( let recorder = AudioRecorder(
deviceID: deviceID, outputHandler: outputHandler, convertToSampleRate: sampleRate, deviceID: deviceID, outputHandler: outputHandler, convertToSampleRate: sampleRate,
chunkDuration: chunkDuration) chunkDuration: chunkDuration)
@@ -1,10 +1,18 @@
import Foundation import Foundation
public class BinaryAudioOutputHandler: AudioOutputHandler { public class BinaryAudioOutputHandler: AudioOutputHandler {
private let flushAfterWrite: Bool
public init(flushAfterWrite: Bool = false) {
self.flushAfterWrite = flushAfterWrite
}
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)
if flushAfterWrite {
fflush(stdout)
}
} }
public func handleMetadata(_ metadata: AudioStreamMetadata) { public func handleMetadata(_ metadata: AudioStreamMetadata) {