static void stream_read_cb(pa_stream *s, size_t length, void *userdata) { demux_t *demux = userdata; demux_sys_t *sys = demux->p_sys; const void *ptr; unsigned samples = length / sys->framesize; if (pa_stream_peek(s, &ptr, &length) < 0) { vlc_pa_error(demux, "cannot peek stream", sys->context); return; } mtime_t pts = mdate(); pa_usec_t latency; int negative; if (pa_stream_get_latency(s, &latency, &negative) < 0) { vlc_pa_error(demux, "cannot determine latency", sys->context); return; } if (negative) pts += latency; else pts -= latency; es_out_Control(demux->out, ES_OUT_SET_PCR, pts); if (unlikely(sys->es == NULL)) goto race; block_t *block = block_Alloc(length); if (likely(block != NULL)) { memcpy(block->p_buffer, ptr, length); block->i_nb_samples = samples; block->i_dts = block->i_pts = pts; if (sys->discontinuity) { block->i_flags |= BLOCK_FLAG_DISCONTINUITY; sys->discontinuity = false; } es_out_Send(demux->out, sys->es, block); } else sys->discontinuity = true; race: pa_stream_drop(s); }
/** * Gets latency of a PulseAudio stream. * \return the latency or VLC_TS_INVALID on error. */ mtime_t vlc_pa_get_latency(vlc_object_t *obj, pa_context *ctx, pa_stream *s) { pa_usec_t latency; int negative; if (pa_stream_get_latency(s, &latency, &negative)) { if (pa_context_errno (ctx) != PA_ERR_NODATA) vlc_pa_error(obj, "unknown latency", ctx); return VLC_TS_INVALID; } return negative ? -latency : +latency; }
/** * Initializes the PulseAudio main loop and connects to the PulseAudio server. * @return a PulseAudio context on success, or NULL on error */ pa_context *vlc_pa_connect (vlc_object_t *obj, pa_threaded_mainloop **mlp) { msg_Dbg (obj, "using library version %s", pa_get_library_version ()); msg_Dbg (obj, " (compiled with version %s, protocol %u)", pa_get_headers_version (), PA_PROTOCOL_VERSION); /* Initialize main loop */ pa_threaded_mainloop *mainloop = pa_threaded_mainloop_new (); if (unlikely(mainloop == NULL)) return NULL; if (pa_threaded_mainloop_start (mainloop) < 0) { pa_threaded_mainloop_free (mainloop); return NULL; } /* Fill in context (client) properties */ char *ua = var_InheritString (obj, "user-agent"); pa_proplist *props = pa_proplist_new (); if (likely(props != NULL)) { pa_proplist_sets (props, PA_PROP_APPLICATION_NAME, ua); pa_proplist_sets (props, PA_PROP_APPLICATION_ID, "org.VideoLAN.VLC"); pa_proplist_sets (props, PA_PROP_APPLICATION_VERSION, PACKAGE_VERSION); pa_proplist_sets (props, PA_PROP_APPLICATION_ICON_NAME, PACKAGE_NAME); //pa_proplist_sets (props, PA_PROP_APPLICATION_LANGUAGE, _("C")); pa_proplist_sets (props, PA_PROP_APPLICATION_LANGUAGE, setlocale (LC_MESSAGES, NULL)); pa_proplist_setf (props, PA_PROP_APPLICATION_PROCESS_ID, "%lu", (unsigned long) getpid ()); //pa_proplist_sets (props, PA_PROP_APPLICATION_PROCESS_BINARY, // PACKAGE_NAME); for (size_t max = sysconf (_SC_GETPW_R_SIZE_MAX), len = max % 1024 + 1024; len < max; len += 1024) { struct passwd pwbuf, *pw; char buf[len]; if (getpwuid_r (getuid (), &pwbuf, buf, sizeof (buf), &pw) == 0 && pw != NULL) pa_proplist_sets (props, PA_PROP_APPLICATION_PROCESS_USER, pw->pw_name); } for (size_t max = sysconf (_SC_HOST_NAME_MAX), len = max % 1024 + 1024; len < max; len += 1024) { char hostname[len]; if (gethostname (hostname, sizeof (hostname)) == 0) pa_proplist_sets (props, PA_PROP_APPLICATION_PROCESS_HOST, hostname); } const char *session = getenv ("XDG_SESSION_COOKIE"); if (session != NULL) { pa_proplist_setf (props, PA_PROP_APPLICATION_PROCESS_MACHINE_ID, "%.32s", session); /* XXX: is this valid? */ pa_proplist_sets (props, PA_PROP_APPLICATION_PROCESS_SESSION_ID, session); } } /* Connect to PulseAudio daemon */ pa_context *ctx; pa_mainloop_api *api; pa_threaded_mainloop_lock (mainloop); api = pa_threaded_mainloop_get_api (mainloop); ctx = pa_context_new_with_proplist (api, ua, props); free (ua); if (props != NULL) pa_proplist_free (props); if (unlikely(ctx == NULL)) goto fail; pa_context_set_state_callback (ctx, context_state_cb, mainloop); if (pa_context_connect (ctx, NULL, 0, NULL) < 0 || context_wait (ctx, mainloop)) { vlc_pa_error (obj, "PulseAudio server connection failure", ctx); pa_context_unref (ctx); goto fail; } msg_Dbg (obj, "connected %s to %s as client #%"PRIu32, pa_context_is_local (ctx) ? "locally" : "remotely", pa_context_get_server (ctx), pa_context_get_index (ctx)); msg_Dbg (obj, "using protocol %"PRIu32", server protocol %"PRIu32, pa_context_get_protocol_version (ctx), pa_context_get_server_protocol_version (ctx)); pa_threaded_mainloop_unlock (mainloop); *mlp = mainloop; return ctx; fail: pa_threaded_mainloop_unlock (mainloop); pa_threaded_mainloop_stop (mainloop); pa_threaded_mainloop_free (mainloop); return NULL; }
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); }