gboolean gst_pulse_fill_format_info (GstAudioRingBufferSpec * spec, pa_format_info ** f, guint * channels) { pa_format_info *format; pa_sample_format_t sf = PA_SAMPLE_INVALID; GstAudioInfo *ainfo = &spec->info; format = pa_format_info_new (); if (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_MU_LAW && GST_AUDIO_INFO_WIDTH (ainfo) == 8) { format->encoding = PA_ENCODING_PCM; sf = PA_SAMPLE_ULAW; } else if (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_A_LAW && GST_AUDIO_INFO_WIDTH (ainfo) == 8) { format->encoding = PA_ENCODING_PCM; sf = PA_SAMPLE_ALAW; } else if (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW) { format->encoding = PA_ENCODING_PCM; if (!gstaudioformat_to_pasampleformat (GST_AUDIO_INFO_FORMAT (ainfo), &sf)) goto fail; } else if (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_AC3) { format->encoding = PA_ENCODING_AC3_IEC61937; } else if (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_EAC3) { format->encoding = PA_ENCODING_EAC3_IEC61937; } else if (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_DTS) { format->encoding = PA_ENCODING_DTS_IEC61937; } else if (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_MPEG) { format->encoding = PA_ENCODING_MPEG_IEC61937; } else { goto fail; } if (format->encoding == PA_ENCODING_PCM) { pa_format_info_set_sample_format (format, sf); pa_format_info_set_channels (format, GST_AUDIO_INFO_CHANNELS (ainfo)); } pa_format_info_set_rate (format, GST_AUDIO_INFO_RATE (ainfo)); if (!pa_format_info_valid (format)) goto fail; *f = format; *channels = GST_AUDIO_INFO_CHANNELS (ainfo); return TRUE; fail: if (format) pa_format_info_free (format); return FALSE; }
/* This is called whenever the context status changes */ static void context_state_callback(pa_context *c, void *userdata) { fail_unless(c != NULL); switch (pa_context_get_state(c)) { case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; case PA_CONTEXT_READY: { int i; fprintf(stderr, "Connection established.\n"); for (i = 0; i < NSTREAMS; i++) { char name[64]; pa_format_info *formats[1]; formats[0] = pa_format_info_new(); formats[0]->encoding = PA_ENCODING_PCM; pa_format_info_set_sample_format(formats[0], PA_SAMPLE_FLOAT32); pa_format_info_set_rate(formats[0], SAMPLE_HZ); pa_format_info_set_channels(formats[0], 1); fprintf(stderr, "Creating stream %i\n", i); snprintf(name, sizeof(name), "stream #%i", i); streams[i] = pa_stream_new_extended(c, name, formats, 1, NULL); fail_unless(streams[i] != NULL); pa_stream_set_state_callback(streams[i], stream_state_callback, (void*) (long) i); pa_stream_connect_playback(streams[i], NULL, &buffer_attr, PA_STREAM_START_CORKED, NULL, i == 0 ? NULL : streams[0]); pa_format_info_free(formats[0]); } break; } case PA_CONTEXT_TERMINATED: mainloop_api->quit(mainloop_api, 0); break; case PA_CONTEXT_FAILED: default: fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c))); fail(); } }
pa_format_info* pa_format_info_from_string(const char *str) { pa_format_info *f = pa_format_info_new(); char *encoding = NULL, *properties = NULL; size_t pos; pos = strcspn(str, ","); encoding = pa_xstrndup(str, pos); f->encoding = pa_encoding_from_string(pa_strip(encoding)); if (f->encoding == PA_ENCODING_INVALID) goto error; if (pos != strlen(str)) { pa_proplist *plist; properties = pa_xstrdup(&str[pos+1]); plist = pa_proplist_from_string(properties); if (!plist) goto error; pa_proplist_free(f->plist); f->plist = plist; } out: if (encoding) pa_xfree(encoding); if (properties) pa_xfree(properties); return f; error: pa_format_info_free(f); f = NULL; goto out; }
static void ext_device_restore_read_device_formats_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { pa_operation *o = userdata; int eol = 1; pa_assert(pd); pa_assert(o); pa_assert(PA_REFCNT_VALUE(o) >= 1); if (!o->context) goto finish; if (command != PA_COMMAND_REPLY) { if (pa_context_handle_error(o->context, command, t, FALSE) < 0) goto finish; eol = -1; } else { uint8_t j; while (!pa_tagstruct_eof(t)) { pa_ext_device_restore_info i; pa_zero(i); if (pa_tagstruct_getu32(t, &i.type) < 0 || pa_tagstruct_getu32(t, &i.index) < 0 || pa_tagstruct_getu8(t, &i.n_formats) < 0) { pa_context_fail(o->context, PA_ERR_PROTOCOL); goto finish; } if (PA_DEVICE_TYPE_SINK != i.type && PA_DEVICE_TYPE_SOURCE != i.type) { pa_context_fail(o->context, PA_ERR_PROTOCOL); goto finish; } if (i.index == PA_INVALID_INDEX) { pa_context_fail(o->context, PA_ERR_PROTOCOL); goto finish; } if (i.n_formats > 0) { i.formats = pa_xnew0(pa_format_info*, i.n_formats); for (j = 0; j < i.n_formats; j++) { pa_format_info *f = i.formats[j] = pa_format_info_new(); if (pa_tagstruct_get_format_info(t, f) < 0) { uint8_t k; pa_context_fail(o->context, PA_ERR_PROTOCOL); for (k = 0; k < j+1; k++) pa_format_info_free(i.formats[k]); pa_xfree(i.formats); goto finish; } } } if (o->callback) { pa_ext_device_restore_read_device_formats_cb_t cb = (pa_ext_device_restore_read_device_formats_cb_t) o->callback; cb(o->context, &i, 0, o->userdata); } for (j = 0; j < i.n_formats; j++) pa_format_info_free(i.formats[j]); pa_xfree(i.formats); } }
CPulseAEStream::CPulseAEStream(pa_context *context, pa_threaded_mainloop *mainLoop, enum AEDataFormat format, unsigned int sampleRate, CAEChannelInfo channelLayout, unsigned int options) : m_fader(this) { ASSERT(channelLayout.Count()); m_Destroyed = false; m_Initialized = false; m_Paused = false; m_Stream = NULL; m_Context = context; m_MainLoop = mainLoop; m_format = format; m_sampleRate = sampleRate; m_channelLayout = channelLayout; m_options = options; m_DrainOperation = NULL; m_slave = NULL; pa_threaded_mainloop_lock(m_MainLoop); m_SampleSpec.channels = channelLayout.Count(); m_SampleSpec.rate = m_sampleRate; switch (m_format) { case AE_FMT_U8 : m_SampleSpec.format = PA_SAMPLE_U8; break; case AE_FMT_S16NE : m_SampleSpec.format = PA_SAMPLE_S16NE; break; case AE_FMT_S16LE : m_SampleSpec.format = PA_SAMPLE_S16LE; break; case AE_FMT_S16BE : m_SampleSpec.format = PA_SAMPLE_S16BE; break; case AE_FMT_S24NE3: m_SampleSpec.format = PA_SAMPLE_S24NE; break; case AE_FMT_S24NE4: m_SampleSpec.format = PA_SAMPLE_S24_32NE; break; case AE_FMT_S32NE : m_SampleSpec.format = PA_SAMPLE_S32NE; break; case AE_FMT_S32LE : m_SampleSpec.format = PA_SAMPLE_S32LE; break; case AE_FMT_S32BE : m_SampleSpec.format = PA_SAMPLE_S32BE; break; case AE_FMT_FLOAT : m_SampleSpec.format = PA_SAMPLE_FLOAT32NE; break; #if PA_CHECK_VERSION(1,0,0) case AE_FMT_DTS : case AE_FMT_EAC3 : case AE_FMT_AC3 : m_SampleSpec.format = PA_SAMPLE_S16NE; break; #endif default: CLog::Log(LOGERROR, "PulseAudio: Invalid format %i", format); pa_threaded_mainloop_unlock(m_MainLoop); m_format = AE_FMT_INVALID; return; } if (!pa_sample_spec_valid(&m_SampleSpec)) { CLog::Log(LOGERROR, "PulseAudio: Invalid sample spec"); pa_threaded_mainloop_unlock(m_MainLoop); Destroy(); return /*false*/; } m_frameSize = pa_frame_size(&m_SampleSpec); struct pa_channel_map map; map.channels = m_channelLayout.Count(); for (unsigned int ch = 0; ch < m_channelLayout.Count(); ++ch) switch(m_channelLayout[ch]) { case AE_CH_NULL: break; case AE_CH_MAX : break; case AE_CH_RAW : break; case AE_CH_FL : map.map[ch] = PA_CHANNEL_POSITION_FRONT_LEFT ; break; case AE_CH_FR : map.map[ch] = PA_CHANNEL_POSITION_FRONT_RIGHT ; break; case AE_CH_FC : map.map[ch] = PA_CHANNEL_POSITION_FRONT_CENTER ; break; case AE_CH_BC : map.map[ch] = PA_CHANNEL_POSITION_REAR_CENTER ; break; case AE_CH_BL : map.map[ch] = PA_CHANNEL_POSITION_REAR_LEFT ; break; case AE_CH_BR : map.map[ch] = PA_CHANNEL_POSITION_REAR_RIGHT ; break; case AE_CH_LFE : map.map[ch] = PA_CHANNEL_POSITION_LFE ; break; case AE_CH_FLOC: map.map[ch] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER ; break; case AE_CH_FROC: map.map[ch] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; break; case AE_CH_SL : map.map[ch] = PA_CHANNEL_POSITION_SIDE_LEFT ; break; case AE_CH_SR : map.map[ch] = PA_CHANNEL_POSITION_SIDE_RIGHT ; break; case AE_CH_TC : map.map[ch] = PA_CHANNEL_POSITION_TOP_CENTER ; break; case AE_CH_TFL : map.map[ch] = PA_CHANNEL_POSITION_TOP_FRONT_LEFT ; break; case AE_CH_TFR : map.map[ch] = PA_CHANNEL_POSITION_TOP_FRONT_RIGHT ; break; case AE_CH_TFC : map.map[ch] = PA_CHANNEL_POSITION_TOP_CENTER ; break; case AE_CH_TBL : map.map[ch] = PA_CHANNEL_POSITION_TOP_REAR_LEFT ; break; case AE_CH_TBR : map.map[ch] = PA_CHANNEL_POSITION_TOP_REAR_RIGHT ; break; case AE_CH_TBC : map.map[ch] = PA_CHANNEL_POSITION_TOP_REAR_CENTER ; break; default: break; } m_MaxVolume = CAEFactory::GetEngine()->GetVolume(); m_Volume = 1.0f; pa_volume_t paVolume = pa_sw_volume_from_linear((double)(m_Volume * m_MaxVolume)); pa_cvolume_set(&m_ChVolume, m_SampleSpec.channels, paVolume); #if PA_CHECK_VERSION(1,0,0) pa_format_info *info[1]; info[0] = pa_format_info_new(); switch(m_format) { case AE_FMT_DTS : info[0]->encoding = PA_ENCODING_DTS_IEC61937 ; break; case AE_FMT_EAC3: info[0]->encoding = PA_ENCODING_EAC3_IEC61937; break; case AE_FMT_AC3 : info[0]->encoding = PA_ENCODING_AC3_IEC61937 ; break; default: info[0]->encoding = PA_ENCODING_PCM ; break; } pa_format_info_set_rate (info[0], m_SampleSpec.rate); pa_format_info_set_channels (info[0], m_SampleSpec.channels); pa_format_info_set_channel_map (info[0], &map); pa_format_info_set_sample_format(info[0], m_SampleSpec.format); m_Stream = pa_stream_new_extended(m_Context, "audio stream", info, 1, NULL); pa_format_info_free(info[0]); #else m_Stream = pa_stream_new(m_Context, "audio stream", &m_SampleSpec, &map); #endif if (m_Stream == NULL) { CLog::Log(LOGERROR, "PulseAudio: Could not create a stream"); pa_threaded_mainloop_unlock(m_MainLoop); Destroy(); return /*false*/; } pa_stream_set_state_callback(m_Stream, CPulseAEStream::StreamStateCallback, this); pa_stream_set_write_callback(m_Stream, CPulseAEStream::StreamRequestCallback, this); pa_stream_set_latency_update_callback(m_Stream, CPulseAEStream::StreamLatencyUpdateCallback, this); pa_stream_set_underflow_callback(m_Stream, CPulseAEStream::StreamUnderflowCallback, this); int flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE; if (options && AESTREAM_FORCE_RESAMPLE) flags |= PA_STREAM_VARIABLE_RATE; if (pa_stream_connect_playback(m_Stream, NULL, NULL, (pa_stream_flags)flags, &m_ChVolume, NULL) < 0) { CLog::Log(LOGERROR, "PulseAudio: Failed to connect stream to output"); pa_threaded_mainloop_unlock(m_MainLoop); Destroy(); return /*false*/; } /* Wait until the stream is ready */ do { pa_threaded_mainloop_wait(m_MainLoop); CLog::Log(LOGDEBUG, "PulseAudio: Stream %s", StreamStateToString(pa_stream_get_state(m_Stream))); } while (pa_stream_get_state(m_Stream) != PA_STREAM_READY && pa_stream_get_state(m_Stream) != PA_STREAM_FAILED); if (pa_stream_get_state(m_Stream) == PA_STREAM_FAILED) { CLog::Log(LOGERROR, "PulseAudio: Waited for the stream but it failed"); pa_threaded_mainloop_unlock(m_MainLoop); Destroy(); return /*false*/; } m_cacheSize = pa_stream_writable_size(m_Stream); pa_threaded_mainloop_unlock(m_MainLoop); m_Initialized = true; CLog::Log(LOGINFO, "PulseAEStream::Initialized"); CLog::Log(LOGINFO, " Sample Rate : %d", m_sampleRate); CLog::Log(LOGINFO, " Sample Format : %s", CAEUtil::DataFormatToStr(m_format)); CLog::Log(LOGINFO, " Channel Count : %d", m_channelLayout.Count()); CLog::Log(LOGINFO, " Channel Layout: %s", ((std::string)m_channelLayout).c_str()); CLog::Log(LOGINFO, " Frame Size : %d", m_frameSize); CLog::Log(LOGINFO, " Cache Size : %d", m_cacheSize); Resume(); return /*true*/; }
bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device) { { CSingleLock lock(m_sec); m_IsAllocated = false; } m_passthrough = false; m_BytesPerSecond = 0; m_BufferSize = 0; m_filled_bytes = 0; m_lastPackageStamp = 0; m_Channels = 0; m_Stream = NULL; m_Context = NULL; m_periodSize = 0; if (!SetupContext(NULL, &m_Context, &m_MainLoop)) { CLog::Log(LOGNOTICE, "PulseAudio might not be running. Context was not created."); Deinitialize(); return false; } pa_threaded_mainloop_lock(m_MainLoop); struct pa_channel_map map; pa_channel_map_init(&map); // PULSE cannot cope with e.g. planar formats so we fallback to FLOAT // when we receive an invalid pulse format if (AEFormatToPulseFormat(format.m_dataFormat) == PA_SAMPLE_INVALID) { CLog::Log(LOGDEBUG, "PULSE does not support format: %s - will fallback to AE_FMT_FLOAT", CAEUtil::DataFormatToStr(format.m_dataFormat)); format.m_dataFormat = AE_FMT_FLOAT; } m_passthrough = AE_IS_RAW(format.m_dataFormat); if(m_passthrough) { map.channels = 2; format.m_channelLayout = AE_CH_LAYOUT_2_0; } else { map = AEChannelMapToPAChannel(format.m_channelLayout); // if count has changed we need to fit the AE Map if(map.channels != format.m_channelLayout.Count()) format.m_channelLayout = PAChannelToAEChannelMap(map); } m_Channels = format.m_channelLayout.Count(); // store information about current sink SinkInfoStruct sinkStruct; sinkStruct.mainloop = m_MainLoop; sinkStruct.device_found = false; // get real sample rate of the device we want to open - to avoid resampling bool isDefaultDevice = (device == "Default"); WaitForOperation(pa_context_get_sink_info_by_name(m_Context, isDefaultDevice ? NULL : device.c_str(), SinkInfoCallback, &sinkStruct), m_MainLoop, "Get Sink Info"); // only check if the device is existing - don't alter the sample rate if (!sinkStruct.device_found) { CLog::Log(LOGERROR, "PulseAudio: Sink %s not found", device.c_str()); pa_threaded_mainloop_unlock(m_MainLoop); Deinitialize(); return false; } // Pulse can resample everything between 1 hz and 192000 hz // Make sure we are in the range that we originally added format.m_sampleRate = std::max(5512U, std::min(format.m_sampleRate, 192000U)); pa_format_info *info[1]; info[0] = pa_format_info_new(); info[0]->encoding = AEFormatToPulseEncoding(format.m_dataFormat); if(!m_passthrough) { pa_format_info_set_sample_format(info[0], AEFormatToPulseFormat(format.m_dataFormat)); pa_format_info_set_channel_map(info[0], &map); } pa_format_info_set_channels(info[0], m_Channels); // PA requires m_encodedRate in order to do EAC3 unsigned int samplerate = format.m_sampleRate; if (m_passthrough && (AEFormatToPulseEncoding(format.m_dataFormat) == PA_ENCODING_EAC3_IEC61937)) { // this is only used internally for PA to use EAC3 samplerate = format.m_encodedRate; } pa_format_info_set_rate(info[0], samplerate); if (!pa_format_info_valid(info[0])) { CLog::Log(LOGERROR, "PulseAudio: Invalid format info"); pa_format_info_free(info[0]); pa_threaded_mainloop_unlock(m_MainLoop); Deinitialize(); return false; } pa_sample_spec spec; #if PA_CHECK_VERSION(2,0,0) pa_format_info_to_sample_spec(info[0], &spec, NULL); #else spec.rate = (AEFormatToPulseEncoding(format.m_dataFormat) == PA_ENCODING_EAC3_IEC61937) ? 4 * samplerate : samplerate; spec.format = AEFormatToPulseFormat(format.m_dataFormat); spec.channels = m_Channels; #endif if (!pa_sample_spec_valid(&spec)) { CLog::Log(LOGERROR, "PulseAudio: Invalid sample spec"); pa_format_info_free(info[0]); pa_threaded_mainloop_unlock(m_MainLoop); Deinitialize(); return false; } m_BytesPerSecond = pa_bytes_per_second(&spec); unsigned int frameSize = pa_frame_size(&spec); m_Stream = pa_stream_new_extended(m_Context, "kodi audio stream", info, 1, NULL); pa_format_info_free(info[0]); if (m_Stream == NULL) { CLog::Log(LOGERROR, "PulseAudio: Could not create a stream"); pa_threaded_mainloop_unlock(m_MainLoop); Deinitialize(); return false; } pa_stream_set_state_callback(m_Stream, StreamStateCallback, m_MainLoop); pa_stream_set_write_callback(m_Stream, StreamRequestCallback, m_MainLoop); pa_stream_set_latency_update_callback(m_Stream, StreamLatencyUpdateCallback, m_MainLoop); // default buffer construction // align with AE's max buffer unsigned int latency = m_BytesPerSecond / 2.5; // 400 ms unsigned int process_time = latency / 4; // 100 ms if(sinkStruct.isHWDevice) { // on hw devices buffers can be further reduced // 200ms max latency // 50ms min packet size latency = m_BytesPerSecond / 5; process_time = latency / 4; } pa_buffer_attr buffer_attr; buffer_attr.fragsize = latency; buffer_attr.maxlength = (uint32_t) -1; buffer_attr.minreq = process_time; buffer_attr.prebuf = (uint32_t) -1; buffer_attr.tlength = latency; if (pa_stream_connect_playback(m_Stream, isDefaultDevice ? NULL : device.c_str(), &buffer_attr, ((pa_stream_flags)(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY)), NULL, NULL) < 0) { CLog::Log(LOGERROR, "PulseAudio: Failed to connect stream to output"); pa_threaded_mainloop_unlock(m_MainLoop); Deinitialize(); return false; } /* Wait until the stream is ready */ do { pa_threaded_mainloop_wait(m_MainLoop); CLog::Log(LOGDEBUG, "PulseAudio: Stream %s", StreamStateToString(pa_stream_get_state(m_Stream))); } while (pa_stream_get_state(m_Stream) != PA_STREAM_READY && pa_stream_get_state(m_Stream) != PA_STREAM_FAILED); if (pa_stream_get_state(m_Stream) == PA_STREAM_FAILED) { CLog::Log(LOGERROR, "PulseAudio: Waited for the stream but it failed"); pa_threaded_mainloop_unlock(m_MainLoop); Deinitialize(); return false; } const pa_buffer_attr *a; if (!(a = pa_stream_get_buffer_attr(m_Stream))) { CLog::Log(LOGERROR, "PulseAudio: %s", pa_strerror(pa_context_errno(m_Context))); pa_threaded_mainloop_unlock(m_MainLoop); Deinitialize(); return false; } else { unsigned int packetSize = a->minreq; m_BufferSize = a->tlength; m_periodSize = a->minreq; format.m_frames = packetSize / frameSize; } { CSingleLock lock(m_sec); // Register Callback for Sink changes pa_context_set_subscribe_callback(m_Context, SinkChangedCallback, this); const pa_subscription_mask_t mask = PA_SUBSCRIPTION_MASK_SINK; pa_operation *op = pa_context_subscribe(m_Context, mask, NULL, this); if (op != NULL) pa_operation_unref(op); // Register Callback for Sink Info changes - this handles volume pa_context_set_subscribe_callback(m_Context, SinkInputInfoChangedCallback, this); const pa_subscription_mask_t mask_input = PA_SUBSCRIPTION_MASK_SINK_INPUT; pa_operation* op_sinfo = pa_context_subscribe(m_Context, mask_input, NULL, this); if (op_sinfo != NULL) pa_operation_unref(op_sinfo); } pa_threaded_mainloop_unlock(m_MainLoop); format.m_frameSize = frameSize; format.m_frameSamples = format.m_frames * format.m_channelLayout.Count(); m_format = format; format.m_dataFormat = m_passthrough ? AE_FMT_S16NE : format.m_dataFormat; CLog::Log(LOGNOTICE, "PulseAudio: Opened device %s in %s mode with Buffersize %u ms", device.c_str(), m_passthrough ? "passthrough" : "pcm", (unsigned int) ((m_BufferSize / (float) m_BytesPerSecond) * 1000)); // Cork stream will resume when adding first package Pause(true); { CSingleLock lock(m_sec); m_IsAllocated = true; } return true; }
static int init(struct ao *ao) { pa_proplist *proplist = NULL; pa_format_info *format = NULL; struct priv *priv = ao->priv; char *sink = priv->cfg_sink && priv->cfg_sink[0] ? priv->cfg_sink : ao->device; if (pa_init_boilerplate(ao) < 0) return -1; pa_threaded_mainloop_lock(priv->mainloop); if (!(proplist = pa_proplist_new())) { MP_ERR(ao, "Failed to allocate proplist\n"); goto unlock_and_fail; } (void)pa_proplist_sets(proplist, PA_PROP_MEDIA_ICON_NAME, ao->client_name); if (!(format = pa_format_info_new())) goto unlock_and_fail; if (!set_format(ao, format)) { ao->channels = (struct mp_chmap) MP_CHMAP_INIT_STEREO; ao->samplerate = 48000; ao->format = AF_FORMAT_FLOAT; if (!set_format(ao, format)) { MP_ERR(ao, "Invalid audio format\n"); goto unlock_and_fail; } } if (!(priv->stream = pa_stream_new_extended(priv->context, "audio stream", &format, 1, proplist))) goto unlock_and_fail; pa_format_info_free(format); format = NULL; pa_proplist_free(proplist); proplist = NULL; pa_stream_set_state_callback(priv->stream, stream_state_cb, ao); pa_stream_set_write_callback(priv->stream, stream_request_cb, ao); pa_stream_set_latency_update_callback(priv->stream, stream_latency_update_cb, ao); int buf_size = af_fmt_seconds_to_bytes(ao->format, priv->cfg_buffer / 1000.0, ao->channels.num, ao->samplerate); pa_buffer_attr bufattr = { .maxlength = -1, .tlength = buf_size > 0 ? buf_size : (uint32_t)-1, .prebuf = -1, .minreq = -1, .fragsize = -1, }; int flags = PA_STREAM_NOT_MONOTONIC; if (!priv->cfg_latency_hacks) flags |= PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE; if (pa_stream_connect_playback(priv->stream, sink, &bufattr, flags, NULL, NULL) < 0) goto unlock_and_fail; /* Wait until the stream is ready */ while (1) { int state = pa_stream_get_state(priv->stream); if (state == PA_STREAM_READY) break; if (!PA_STREAM_IS_GOOD(state)) goto unlock_and_fail; pa_threaded_mainloop_wait(priv->mainloop); } if (pa_stream_is_suspended(priv->stream)) { MP_ERR(ao, "The stream is suspended. Bailing out.\n"); goto unlock_and_fail; } pa_threaded_mainloop_unlock(priv->mainloop); return 0; unlock_and_fail: pa_threaded_mainloop_unlock(priv->mainloop); if (format) pa_format_info_free(format); if (proplist) pa_proplist_free(proplist); uninit(ao); return -1; } static void cork(struct ao *ao, bool pause) { struct priv *priv = ao->priv; pa_threaded_mainloop_lock(priv->mainloop); priv->retval = 0; if (!waitop(priv, pa_stream_cork(priv->stream, pause, success_cb, ao)) || !priv->retval) GENERIC_ERR_MSG("pa_stream_cork() failed"); } // Play the specified data to the pulseaudio server static int play(struct ao *ao, void **data, int samples, int flags) { struct priv *priv = ao->priv; pa_threaded_mainloop_lock(priv->mainloop); if (pa_stream_write(priv->stream, data[0], samples * ao->sstride, NULL, 0, PA_SEEK_RELATIVE) < 0) { GENERIC_ERR_MSG("pa_stream_write() failed"); samples = -1; } if (flags & AOPLAY_FINAL_CHUNK) { // Force start in case the stream was too short for prebuf pa_operation *op = pa_stream_trigger(priv->stream, NULL, NULL); pa_operation_unref(op); } pa_threaded_mainloop_unlock(priv->mainloop); return samples; } // Reset the audio stream, i.e. flush the playback buffer on the server side static void reset(struct ao *ao) { // pa_stream_flush() works badly if not corked cork(ao, true); struct priv *priv = ao->priv; pa_threaded_mainloop_lock(priv->mainloop); priv->retval = 0; if (!waitop(priv, pa_stream_flush(priv->stream, success_cb, ao)) || !priv->retval) GENERIC_ERR_MSG("pa_stream_flush() failed"); cork(ao, false); }
void pa_format_info_free2(pa_format_info *f, void *userdata) { pa_format_info_free(f); }