// on underflow, increase pulseaudio latency in ~10ms steps void PulseAudio::UnderflowCallback(pa_stream* s) { m_pa_ba.tlength += BUFFER_SIZE; pa_stream_set_buffer_attr(s, &m_pa_ba, nullptr, nullptr); WARN_LOG(AUDIO, "pulseaudio underflow, new latency: %d bytes", m_pa_ba.tlength); }
static void kradpulse_stream_underflow_cb(pa_stream *s, void *userdata) { krad_pulse_t *kradpulse = (krad_pulse_t *)userdata; // We increase the latency by 50% if we get 6 underflows and latency is under 2s // This is very useful for over the network playback that can't handle low latencies printf("underflow\n"); kradpulse->underflows++; if (kradpulse->underflows >= 6 && kradpulse->latency < 2000000) { kradpulse->latency = (kradpulse->latency*3)/2; kradpulse->bufattr.maxlength = pa_usec_to_bytes(kradpulse->latency, &kradpulse->ss); kradpulse->bufattr.tlength = pa_usec_to_bytes(kradpulse->latency, &kradpulse->ss); pa_stream_set_buffer_attr(s, &kradpulse->bufattr, NULL, NULL); kradpulse->underflows = 0; printf("latency increased to %d\n", kradpulse->latency); } }
static void sink_update_requested_latency_cb(pa_sink *s) { struct userdata *u; pa_operation *operation; size_t nbytes; pa_usec_t block_usec; pa_buffer_attr bufferattr; pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); block_usec = pa_sink_get_requested_latency_within_thread(s); if (block_usec == (pa_usec_t) -1) block_usec = s->thread_info.max_latency; nbytes = pa_usec_to_bytes(block_usec, &s->sample_spec); pa_sink_set_max_request_within_thread(s, nbytes); if (u->stream) { switch (pa_stream_get_state(u->stream)) { case PA_STREAM_READY: if (pa_stream_get_buffer_attr(u->stream)->tlength == nbytes) break; reset_bufferattr(&bufferattr); bufferattr.tlength = nbytes; if ((operation = pa_stream_set_buffer_attr(u->stream, &bufferattr, stream_set_buffer_attr_cb, u))) pa_operation_unref(operation); break; case PA_STREAM_CREATING: /* we have to delay our request until stream is ready */ u->update_stream_bufferattr_after_connect = true; break; default: break; } } }
static ALCboolean pulse_reset_playback(ALCdevice *device) //{{{ { pulse_data *data = device->ExtraData; pa_stream_flags_t flags = 0; pa_channel_map chanmap; pa_threaded_mainloop_lock(data->loop); if(!(device->Flags&DEVICE_CHANNELS_REQUEST)) { pa_operation *o; o = pa_context_get_sink_info_by_name(data->context, data->device_name, sink_info_callback, device); while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(data->loop); pa_operation_unref(o); } if(!(device->Flags&DEVICE_FREQUENCY_REQUEST)) flags |= PA_STREAM_FIX_RATE; data->frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType); data->attr.prebuf = -1; data->attr.fragsize = -1; data->attr.minreq = device->UpdateSize * data->frame_size; data->attr.tlength = data->attr.minreq * maxu(device->NumUpdates, 2); data->attr.maxlength = -1; flags |= PA_STREAM_EARLY_REQUESTS; flags |= PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE; switch(device->FmtType) { case DevFmtByte: device->FmtType = DevFmtUByte; /* fall-through */ case DevFmtUByte: data->spec.format = PA_SAMPLE_U8; break; case DevFmtUShort: device->FmtType = DevFmtShort; /* fall-through */ case DevFmtShort: data->spec.format = PA_SAMPLE_S16NE; break; case DevFmtFloat: data->spec.format = PA_SAMPLE_FLOAT32NE; break; } data->spec.rate = device->Frequency; data->spec.channels = ChannelsFromDevFmt(device->FmtChans); if(pa_sample_spec_valid(&data->spec) == 0) { ERR("Invalid sample format\n"); pa_threaded_mainloop_unlock(data->loop); return ALC_FALSE; } if(!pa_channel_map_init_auto(&chanmap, data->spec.channels, PA_CHANNEL_MAP_WAVEEX)) { ERR("Couldn't build map for channel count (%d)!\n", data->spec.channels); pa_threaded_mainloop_unlock(data->loop); return ALC_FALSE; } SetDefaultWFXChannelOrder(device); data->stream = connect_playback_stream(device, flags, &data->attr, &data->spec, &chanmap); if(!data->stream) { pa_threaded_mainloop_unlock(data->loop); return ALC_FALSE; } pa_stream_set_state_callback(data->stream, stream_state_callback2, device); data->spec = *(pa_stream_get_sample_spec(data->stream)); if(device->Frequency != data->spec.rate) { pa_operation *o; if((device->Flags&DEVICE_FREQUENCY_REQUEST)) ERR("Failed to set frequency %dhz, got %dhz instead\n", device->Frequency, data->spec.rate); device->Flags &= ~DEVICE_FREQUENCY_REQUEST; /* Server updated our playback rate, so modify the buffer attribs * accordingly. */ data->attr.minreq = (ALuint64)(data->attr.minreq/data->frame_size) * data->spec.rate / device->Frequency * data->frame_size; data->attr.tlength = data->attr.minreq * maxu(device->NumUpdates, 2); o = pa_stream_set_buffer_attr(data->stream, &data->attr, stream_success_callback, device); while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(data->loop); pa_operation_unref(o); device->Frequency = data->spec.rate; } #if PA_CHECK_VERSION(0,9,15) if(pa_stream_set_buffer_attr_callback) pa_stream_set_buffer_attr_callback(data->stream, stream_buffer_attr_callback, device); #endif pa_stream_set_moved_callback(data->stream, stream_device_callback, device); pa_stream_set_write_callback(data->stream, stream_write_callback, device); pa_stream_set_underflow_callback(data->stream, stream_signal_callback, device); data->attr = *(pa_stream_get_buffer_attr(data->stream)); ERR("PulseAudio returned minreq=%d, tlength=%d\n", data->attr.minreq, data->attr.tlength); device->UpdateSize = data->attr.minreq / data->frame_size; device->NumUpdates = (data->attr.tlength/data->frame_size) / device->UpdateSize; while(device->NumUpdates <= 2) { pa_operation *o; ERR("minreq too high - expect lag or break up\n"); /* Server gave a comparatively large minreq, so modify the tlength. */ device->NumUpdates = 2; data->attr.tlength = data->attr.minreq * device->NumUpdates; o = pa_stream_set_buffer_attr(data->stream, &data->attr, stream_success_callback, device); while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(data->loop); pa_operation_unref(o); data->attr = *(pa_stream_get_buffer_attr(data->stream)); ERR("PulseAudio returned minreq=%d, tlength=%d", data->attr.minreq, data->attr.tlength); device->UpdateSize = data->attr.minreq / data->frame_size; device->NumUpdates = (data->attr.tlength/data->frame_size) / device->UpdateSize; } data->thread = StartThread(PulseProc, device); if(!data->thread) { #if PA_CHECK_VERSION(0,9,15) if(pa_stream_set_buffer_attr_callback) pa_stream_set_buffer_attr_callback(data->stream, NULL, NULL); #endif pa_stream_set_moved_callback(data->stream, NULL, NULL); pa_stream_set_write_callback(data->stream, NULL, NULL); pa_stream_set_underflow_callback(data->stream, NULL, NULL); pa_stream_disconnect(data->stream); pa_stream_unref(data->stream); data->stream = NULL; pa_threaded_mainloop_unlock(data->loop); return ALC_FALSE; } pa_threaded_mainloop_unlock(data->loop); return ALC_TRUE; } //}}}
static ALCboolean pulse_reset_playback(ALCdevice *device) { pulse_data *data = device->ExtraData; pa_stream_flags_t flags = 0; pa_channel_map chanmap; pa_threaded_mainloop_lock(data->loop); if(data->stream) { #if PA_CHECK_VERSION(0,9,15) if(pa_stream_set_buffer_attr_callback) pa_stream_set_buffer_attr_callback(data->stream, NULL, NULL); #endif pa_stream_disconnect(data->stream); pa_stream_unref(data->stream); data->stream = NULL; } if(!(device->Flags&DEVICE_CHANNELS_REQUEST)) { pa_operation *o; o = pa_context_get_sink_info_by_name(data->context, data->device_name, sink_info_callback, device); WAIT_FOR_OPERATION(o, data->loop); } if(!(device->Flags&DEVICE_FREQUENCY_REQUEST)) flags |= PA_STREAM_FIX_RATE; flags |= PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE; flags |= PA_STREAM_ADJUST_LATENCY; flags |= PA_STREAM_START_CORKED; flags |= PA_STREAM_DONT_MOVE; switch(device->FmtType) { case DevFmtByte: device->FmtType = DevFmtUByte; /* fall-through */ case DevFmtUByte: data->spec.format = PA_SAMPLE_U8; break; case DevFmtUShort: device->FmtType = DevFmtShort; /* fall-through */ case DevFmtShort: data->spec.format = PA_SAMPLE_S16NE; break; case DevFmtUInt: device->FmtType = DevFmtInt; /* fall-through */ case DevFmtInt: data->spec.format = PA_SAMPLE_S32NE; break; case DevFmtFloat: data->spec.format = PA_SAMPLE_FLOAT32NE; break; } data->spec.rate = device->Frequency; data->spec.channels = ChannelsFromDevFmt(device->FmtChans); if(pa_sample_spec_valid(&data->spec) == 0) { ERR("Invalid sample format\n"); pa_threaded_mainloop_unlock(data->loop); return ALC_FALSE; } if(!pa_channel_map_init_auto(&chanmap, data->spec.channels, PA_CHANNEL_MAP_WAVEEX)) { ERR("Couldn't build map for channel count (%d)!\n", data->spec.channels); pa_threaded_mainloop_unlock(data->loop); return ALC_FALSE; } SetDefaultWFXChannelOrder(device); data->attr.fragsize = -1; data->attr.prebuf = 0; data->attr.minreq = device->UpdateSize * pa_frame_size(&data->spec); data->attr.tlength = data->attr.minreq * maxu(device->NumUpdates, 2); data->attr.maxlength = -1; data->stream = connect_playback_stream(data->device_name, data->loop, data->context, flags, &data->attr, &data->spec, &chanmap); if(!data->stream) { pa_threaded_mainloop_unlock(data->loop); return ALC_FALSE; } pa_stream_set_state_callback(data->stream, stream_state_callback2, device); data->spec = *(pa_stream_get_sample_spec(data->stream)); if(device->Frequency != data->spec.rate) { pa_operation *o; /* Server updated our playback rate, so modify the buffer attribs * accordingly. */ data->attr.minreq = (ALuint64)device->UpdateSize * data->spec.rate / device->Frequency * pa_frame_size(&data->spec); data->attr.tlength = data->attr.minreq * maxu(device->NumUpdates, 2); data->attr.prebuf = 0; o = pa_stream_set_buffer_attr(data->stream, &data->attr, stream_success_callback, device); WAIT_FOR_OPERATION(o, data->loop); device->Frequency = data->spec.rate; } #if PA_CHECK_VERSION(0,9,15) if(pa_stream_set_buffer_attr_callback) pa_stream_set_buffer_attr_callback(data->stream, stream_buffer_attr_callback, device); #endif stream_buffer_attr_callback(data->stream, device); device->NumUpdates = device->UpdateSize*device->NumUpdates / (data->attr.minreq/pa_frame_size(&data->spec)); device->NumUpdates = maxu(device->NumUpdates, 2); device->UpdateSize = data->attr.minreq / pa_frame_size(&data->spec); pa_threaded_mainloop_unlock(data->loop); return ALC_TRUE; }
static int Open(vlc_object_t *obj) { demux_t *demux = (demux_t *)obj; demux_sys_t *sys = malloc(sizeof (*sys)); if (unlikely(sys == NULL)) return VLC_ENOMEM; sys->context = vlc_pa_connect(obj, &sys->mainloop); if (sys->context == NULL) { free(sys); return VLC_EGENERIC; } sys->stream = NULL; sys->es = NULL; sys->discontinuity = false; sys->caching = INT64_C(1000) * var_InheritInteger(obj, "live-caching"); demux->p_sys = sys; /* Stream parameters */ struct pa_sample_spec ss; ss.format = PA_SAMPLE_S16NE; ss.rate = 48000; ss.channels = 2; assert(pa_sample_spec_valid(&ss)); struct pa_channel_map map; map.channels = 2; map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; assert(pa_channel_map_valid(&map)); const pa_stream_flags_t flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY | PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE /*| PA_STREAM_FIX_CHANNELS*/; const char *dev = NULL; if (demux->psz_location != NULL && demux->psz_location[0] != '\0') dev = demux->psz_location; struct pa_buffer_attr attr = { .maxlength = -1, .fragsize = pa_usec_to_bytes(sys->caching, &ss) / 2, }; es_format_t fmt; /* Create record stream */ pa_stream *s; pa_operation *op; pa_threaded_mainloop_lock(sys->mainloop); s = pa_stream_new(sys->context, "audio stream", &ss, &map); if (s == NULL) goto error; sys->stream = s; pa_stream_set_state_callback(s, stream_state_cb, sys->mainloop); pa_stream_set_read_callback(s, stream_read_cb, demux); pa_stream_set_buffer_attr_callback(s, stream_buffer_attr_cb, demux); pa_stream_set_moved_callback(s, stream_moved_cb, demux); pa_stream_set_overflow_callback(s, stream_overflow_cb, demux); pa_stream_set_started_callback(s, stream_started_cb, demux); pa_stream_set_suspended_callback(s, stream_suspended_cb, demux); pa_stream_set_underflow_callback(s, stream_underflow_cb, demux); if (pa_stream_connect_record(s, dev, &attr, flags) < 0 || stream_wait(s, sys->mainloop)) { vlc_pa_error(obj, "cannot connect record stream", sys->context); goto error; } /* The ES should be initialized before stream_read_cb(), but how? */ const struct pa_sample_spec *pss = pa_stream_get_sample_spec(s); if ((unsigned)pss->format >= sizeof (fourccs) / sizeof (fourccs[0])) { msg_Err(obj, "unknown PulseAudio sample format %u", (unsigned)pss->format); goto error; } vlc_fourcc_t format = fourccs[pss->format]; if (format == 0) { /* FIXME: should renegotiate something else */ msg_Err(obj, "unsupported PulseAudio sample format %u", (unsigned)pss->format); goto error; } es_format_Init(&fmt, AUDIO_ES, format); fmt.audio.i_physical_channels = fmt.audio.i_original_channels = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT; fmt.audio.i_channels = ss.channels; fmt.audio.i_rate = pss->rate; fmt.audio.i_bitspersample = aout_BitsPerSample(format); fmt.audio.i_blockalign = fmt.audio.i_bitspersample * ss.channels / 8; fmt.i_bitrate = fmt.audio.i_bitspersample * ss.channels * pss->rate; sys->framesize = fmt.audio.i_blockalign; sys->es = es_out_Add (demux->out, &fmt); /* Update the buffer attributes according to actual format */ attr.fragsize = pa_usec_to_bytes(sys->caching, pss) / 2; op = pa_stream_set_buffer_attr(s, &attr, stream_success_cb, sys->mainloop); if (likely(op != NULL)) { while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(sys->mainloop); pa_operation_unref(op); } stream_buffer_attr_cb(s, demux); pa_threaded_mainloop_unlock(sys->mainloop); demux->pf_demux = NULL; demux->pf_control = Control; return VLC_SUCCESS; error: pa_threaded_mainloop_unlock(sys->mainloop); Close(obj); return VLC_EGENERIC; } static void Close (vlc_object_t *obj) { demux_t *demux = (demux_t *)obj; demux_sys_t *sys = demux->p_sys; pa_stream *s = sys->stream; if (likely(s != NULL)) { pa_threaded_mainloop_lock(sys->mainloop); pa_stream_disconnect(s); pa_stream_set_state_callback(s, NULL, NULL); pa_stream_set_read_callback(s, NULL, NULL); pa_stream_set_buffer_attr_callback(s, NULL, NULL); pa_stream_set_moved_callback(s, NULL, NULL); pa_stream_set_overflow_callback(s, NULL, NULL); pa_stream_set_started_callback(s, NULL, NULL); pa_stream_set_suspended_callback(s, NULL, NULL); pa_stream_set_underflow_callback(s, NULL, NULL); pa_stream_unref(s); pa_threaded_mainloop_unlock(sys->mainloop); } vlc_pa_disconnect(obj, sys->context, sys->mainloop); free(sys); }