mirror of
https://github.com/m1k1o/neko.git
synced 2026-11-03 08:21:50 -06:00
fix: reduce audio jitter buffer latency (#673)
* fix: reduce audio jitter buffer latency - Release listenersMu before dispatching samples to listeners - Buffer Track.sample channel (size 2) with non-blocking WriteSample - Buffer pipeline.sample channel (size 4) to unblock GStreamer thread - Add leaky=downstream max-size-buffers=5 to audio queue to prevent burst accumulation - Set sync=false on appsink to deliver buffers immediately without clock wait These changes break the backpressure chain that caused the GStreamer streaming thread to stall. When the Go scheduler has a hiccup, buffered frames were delivered in a burst, causing the browser audio jitter buffer to grow to ~10 seconds over time. Fixes: #598 * fix: log message when dropping samples due to full track channel
This commit is contained in:
@@ -135,7 +135,7 @@ func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCt
|
|||||||
"pulsesrc device=%s "+
|
"pulsesrc device=%s "+
|
||||||
"! audio/x-raw,channels=2 "+
|
"! audio/x-raw,channels=2 "+
|
||||||
"! audioconvert "+
|
"! audioconvert "+
|
||||||
"! queue "+
|
"! queue max-size-buffers=5 leaky=downstream "+
|
||||||
"! %s "+
|
"! %s "+
|
||||||
"! appsink name=appsink", config.AudioDevice, config.AudioCodec.Pipeline,
|
"! appsink name=appsink", config.AudioDevice, config.AudioCodec.Pipeline,
|
||||||
), nil
|
), nil
|
||||||
|
|||||||
@@ -369,7 +369,6 @@ func (manager *StreamSinkManagerCtx) saveSampleBitrate(timestamp time.Time, delt
|
|||||||
|
|
||||||
func (manager *StreamSinkManagerCtx) onSample(sample types.Sample) {
|
func (manager *StreamSinkManagerCtx) onSample(sample types.Sample) {
|
||||||
manager.listenersMu.Lock()
|
manager.listenersMu.Lock()
|
||||||
defer manager.listenersMu.Unlock()
|
|
||||||
|
|
||||||
// save to metrics
|
// save to metrics
|
||||||
length := float64(sample.Length)
|
length := float64(sample.Length)
|
||||||
@@ -386,7 +385,14 @@ func (manager *StreamSinkManagerCtx) onSample(sample types.Sample) {
|
|||||||
manager.listenersKf = make(map[uintptr]types.SampleListener)
|
manager.listenersKf = make(map[uintptr]types.SampleListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copy listeners before releasing lock to avoid holding it during dispatch
|
||||||
|
listeners := make([]types.SampleListener, 0, len(manager.listeners))
|
||||||
for _, l := range manager.listeners {
|
for _, l := range manager.listeners {
|
||||||
|
listeners = append(listeners, l)
|
||||||
|
}
|
||||||
|
manager.listenersMu.Unlock()
|
||||||
|
|
||||||
|
for _, l := range listeners {
|
||||||
l.WriteSample(sample)
|
l.WriteSample(sample)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func NewTrack(logger zerolog.Logger, codec codec.RTPCodec, connection *webrtc.Pe
|
|||||||
logger: logger.With().Str("id", id).Logger(),
|
logger: logger.With().Str("id", id).Logger(),
|
||||||
track: track,
|
track: track,
|
||||||
rtcpCh: nil,
|
rtcpCh: nil,
|
||||||
sample: make(chan types.Sample),
|
sample: make(chan types.Sample, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
@@ -110,7 +110,11 @@ func (t *Track) sampleReader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Track) WriteSample(sample types.Sample) {
|
func (t *Track) WriteSample(sample types.Sample) {
|
||||||
t.sample <- sample
|
select {
|
||||||
|
case t.sample <- sample:
|
||||||
|
default:
|
||||||
|
t.logger.Trace().Msg("dropping sample: track channel full")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- stream ---
|
// --- stream ---
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ static GstFlowReturn gstreamer_send_new_sample_handler(GstElement *object, gpoin
|
|||||||
|
|
||||||
void gstreamer_pipeline_attach_appsink(GstPipelineCtx *ctx, char *sinkName) {
|
void gstreamer_pipeline_attach_appsink(GstPipelineCtx *ctx, char *sinkName) {
|
||||||
ctx->appsink = gst_bin_get_by_name(GST_BIN(ctx->pipeline), sinkName);
|
ctx->appsink = gst_bin_get_by_name(GST_BIN(ctx->pipeline), sinkName);
|
||||||
g_object_set(ctx->appsink, "emit-signals", TRUE, NULL);
|
g_object_set(ctx->appsink, "emit-signals", TRUE, "sync", FALSE, NULL);
|
||||||
g_signal_connect(ctx->appsink, "new-sample", G_CALLBACK(gstreamer_send_new_sample_handler), ctx);
|
g_signal_connect(ctx->appsink, "new-sample", G_CALLBACK(gstreamer_send_new_sample_handler), ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ func CreatePipeline(pipelineStr string) (Pipeline, error) {
|
|||||||
Int("pipeline_id", int(id)).Logger(),
|
Int("pipeline_id", int(id)).Logger(),
|
||||||
src: pipelineStr,
|
src: pipelineStr,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
sample: make(chan types.Sample),
|
sample: make(chan types.Sample, 4),
|
||||||
}
|
}
|
||||||
|
|
||||||
pipelines[p.id] = p
|
pipelines[p.id] = p
|
||||||
|
|||||||
Reference in New Issue
Block a user