static pa_sample_spec *parse_sdp_sample_spec(pa_sample_spec *ss, char *c) { unsigned rate, channels; pa_assert(ss); pa_assert(c); if (pa_startswith(c, "L16/")) { ss->format = PA_SAMPLE_S16BE; c += 4; } else if (pa_startswith(c, "L8/")) { ss->format = PA_SAMPLE_U8; c += 3; } else if (pa_startswith(c, "PCMA/")) { ss->format = PA_SAMPLE_ALAW; c += 5; } else if (pa_startswith(c, "PCMU/")) { ss->format = PA_SAMPLE_ULAW; c += 5; } else return NULL; if (sscanf(c, "%u/%u", &rate, &channels) == 2) { ss->rate = (uint32_t) rate; ss->channels = (uint8_t) channels; } else if (sscanf(c, "%u", &rate) == 2) { ss->rate = (uint32_t) rate; ss->channels = 1; } else return NULL; if (!pa_sample_spec_valid(ss)) return NULL; return ss; }
char *pa_sample_spec_to_mime_type(const pa_sample_spec *ss, const pa_channel_map *cm) { pa_assert(pa_channel_map_compatible(cm, ss)); pa_assert(pa_sample_spec_valid(ss)); if (!pa_sample_spec_is_mime(ss, cm)) return NULL; switch (ss->format) { case PA_SAMPLE_S16BE: case PA_SAMPLE_S24BE: case PA_SAMPLE_U8: /* Stupid UPnP implementations (PS3...) choke on spaces in * the mime type, that's why we write only ';' here, * instead of '; '. */ return pa_sprintf_malloc("audio/%s;rate=%u;channels=%u", ss->format == PA_SAMPLE_S16BE ? "L16" : (ss->format == PA_SAMPLE_S24BE ? "L24" : "L8"), ss->rate, ss->channels); case PA_SAMPLE_ULAW: return pa_xstrdup("audio/basic"); default: pa_assert_not_reached(); } }
/** * Start recording * * We request the default format used by pulse here because the data will be * converted and possibly re-sampled by obs anyway. * * For now we request a buffer length of 25ms although pulse seems to ignore * this setting for monitor streams. For "real" input streams this should work * fine though. */ static int_fast32_t pulse_start_recording(struct pulse_data *data) { if (pulse_get_server_info(pulse_server_info, (void *) data) < 0) { blog(LOG_ERROR, "Unable to get server info !"); return -1; } if (pulse_get_source_info(pulse_source_info, data->device, (void *) data) < 0) { blog(LOG_ERROR, "Unable to get source info !"); return -1; } pa_sample_spec spec; spec.format = data->format; spec.rate = data->samples_per_sec; spec.channels = data->channels; if (!pa_sample_spec_valid(&spec)) { blog(LOG_ERROR, "Sample spec is not valid"); return -1; } data->speakers = pulse_channels_to_obs_speakers(spec.channels); data->bytes_per_frame = pa_frame_size(&spec); data->stream = pulse_stream_new(obs_source_get_name(data->source), &spec, NULL); if (!data->stream) { blog(LOG_ERROR, "Unable to create stream"); return -1; } pulse_lock(); pa_stream_set_read_callback(data->stream, pulse_stream_read, (void *) data); pulse_unlock(); pa_buffer_attr attr; attr.fragsize = pa_usec_to_bytes(25000, &spec); attr.maxlength = (uint32_t) -1; attr.minreq = (uint32_t) -1; attr.prebuf = (uint32_t) -1; attr.tlength = (uint32_t) -1; pa_stream_flags_t flags = PA_STREAM_ADJUST_LATENCY; pulse_lock(); int_fast32_t ret = pa_stream_connect_record(data->stream, data->device, &attr, flags); pulse_unlock(); if (ret < 0) { pulse_stop_recording(data); blog(LOG_ERROR, "Unable to connect to stream"); return -1; } blog(LOG_INFO, "Started recording from '%s'", data->device); return 0; }
int pa_scache_add_item( pa_core *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map, const pa_memchunk *chunk, pa_proplist *p, uint32_t *idx) { pa_scache_entry *e; char st[PA_SAMPLE_SPEC_SNPRINT_MAX]; pa_channel_map tmap; pa_assert(c); pa_assert(name); pa_assert(!ss || pa_sample_spec_valid(ss)); pa_assert(!map || (pa_channel_map_valid(map) && ss && pa_channel_map_compatible(map, ss))); if (ss && !map) { pa_channel_map_init_extend(&tmap, ss->channels, PA_CHANNEL_MAP_DEFAULT); map = &tmap; } if (chunk && chunk->length > PA_SCACHE_ENTRY_SIZE_MAX) return -1; if (!(e = scache_add_item(c, name))) return -1; pa_sample_spec_init(&e->sample_spec); pa_channel_map_init(&e->channel_map); pa_cvolume_init(&e->volume); e->volume_is_set = FALSE; if (ss) { e->sample_spec = *ss; pa_cvolume_reset(&e->volume, ss->channels); } if (map) e->channel_map = *map; if (chunk) { e->memchunk = *chunk; pa_memblock_ref(e->memchunk.memblock); } if (p) pa_proplist_update(e->proplist, PA_UPDATE_REPLACE, p); if (idx) *idx = e->index; pa_log_debug("Created sample \"%s\" (#%d), %lu bytes with sample spec %s", name, e->index, (unsigned long) e->memchunk.length, pa_sample_spec_snprint(st, sizeof(st), &e->sample_spec)); return 0; }
pa_memchunk* pa_silence_memchunk_get(pa_silence_cache *cache, pa_mempool *pool, pa_memchunk* ret, const pa_sample_spec *spec, size_t length) { pa_memblock *b; size_t l; pa_assert(cache); pa_assert(pa_sample_spec_valid(spec)); if (!(b = cache->blocks[spec->format])) switch (spec->format) { case PA_SAMPLE_U8: cache->blocks[PA_SAMPLE_U8] = b = silence_memblock_new(pool, 0x80); break; case PA_SAMPLE_S16LE: case PA_SAMPLE_S16BE: case PA_SAMPLE_S32LE: case PA_SAMPLE_S32BE: case PA_SAMPLE_S24LE: case PA_SAMPLE_S24BE: case PA_SAMPLE_S24_32LE: case PA_SAMPLE_S24_32BE: case PA_SAMPLE_FLOAT32LE: case PA_SAMPLE_FLOAT32BE: cache->blocks[PA_SAMPLE_S16LE] = b = silence_memblock_new(pool, 0); cache->blocks[PA_SAMPLE_S16BE] = pa_memblock_ref(b); cache->blocks[PA_SAMPLE_S32LE] = pa_memblock_ref(b); cache->blocks[PA_SAMPLE_S32BE] = pa_memblock_ref(b); cache->blocks[PA_SAMPLE_S24LE] = pa_memblock_ref(b); cache->blocks[PA_SAMPLE_S24BE] = pa_memblock_ref(b); cache->blocks[PA_SAMPLE_S24_32LE] = pa_memblock_ref(b); cache->blocks[PA_SAMPLE_S24_32BE] = pa_memblock_ref(b); cache->blocks[PA_SAMPLE_FLOAT32LE] = pa_memblock_ref(b); cache->blocks[PA_SAMPLE_FLOAT32BE] = pa_memblock_ref(b); break; case PA_SAMPLE_ALAW: cache->blocks[PA_SAMPLE_ALAW] = b = silence_memblock_new(pool, 0xd5); break; case PA_SAMPLE_ULAW: cache->blocks[PA_SAMPLE_ULAW] = b = silence_memblock_new(pool, 0xff); break; default: pa_assert_not_reached(); } pa_assert(b); ret->memblock = pa_memblock_ref(b); l = pa_memblock_get_length(b); if (length > l || length == 0) length = l; ret->length = pa_frame_align(length, spec); ret->index = 0; return ret; }
/* * start recording */ static int_fast32_t pulse_start_recording(struct pulse_data *data) { if (pulse_get_server_info(pulse_server_info, (void *) data) < 0) { blog(LOG_ERROR, "pulse-input: Unable to get server info !"); return -1; } pa_sample_spec spec; spec.format = data->format; spec.rate = data->samples_per_sec; spec.channels = data->channels; if (!pa_sample_spec_valid(&spec)) { blog(LOG_ERROR, "pulse-input: Sample spec is not valid"); return -1; } data->bytes_per_frame = pa_frame_size(&spec); blog(LOG_DEBUG, "pulse-input: %u bytes per frame", (unsigned int) data->bytes_per_frame); data->stream = pulse_stream_new(obs_source_getname(data->source), &spec, NULL); if (!data->stream) { blog(LOG_ERROR, "pulse-input: Unable to create stream"); return -1; } pulse_lock(); pa_stream_set_read_callback(data->stream, pulse_stream_read, (void *) data); pulse_unlock(); pa_buffer_attr attr; attr.fragsize = get_buffer_size(data, 250); attr.maxlength = (uint32_t) -1; attr.minreq = (uint32_t) -1; attr.prebuf = (uint32_t) -1; attr.tlength = (uint32_t) -1; pa_stream_flags_t flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY; pulse_lock(); int_fast32_t ret = pa_stream_connect_record(data->stream, data->device, &attr, flags); pulse_unlock(); if (ret < 0) { blog(LOG_ERROR, "pulse-input: Unable to connect to stream"); return -1; } blog(LOG_DEBUG, "pulse-input: Recording started"); return 0; }
int pa_channel_map_compatible(const pa_channel_map *map, const pa_sample_spec *ss) { pa_assert(map); pa_assert(ss); pa_return_val_if_fail(pa_channel_map_valid(map), 0); pa_return_val_if_fail(pa_sample_spec_valid(ss), 0); return map->channels == ss->channels; }
void eventd_sound_pulseaudio_play_data(EventdSoundPulseaudioContext *context, gpointer data, gsize length, gint format, guint32 rate, guint8 channels) { pa_sample_spec sample_spec; pa_stream *stream; EventdSoundPulseaudioEventData *event_data; if ( data == NULL ) return; if ( ( context == NULL ) || ( pa_context_get_state(context->context) != PA_CONTEXT_READY ) ) { g_free(data); return; } switch ( format ) { case SF_FORMAT_PCM_16: case SF_FORMAT_PCM_U8: case SF_FORMAT_PCM_S8: sample_spec.format = PA_SAMPLE_S16NE; break; case SF_FORMAT_PCM_24: sample_spec.format = PA_SAMPLE_S24NE; break; case SF_FORMAT_PCM_32: sample_spec.format = PA_SAMPLE_S32NE; break; case SF_FORMAT_FLOAT: case SF_FORMAT_DOUBLE: sample_spec.format = PA_SAMPLE_FLOAT32NE; break; default: g_warning("Unsupported format"); return; } sample_spec.rate = rate; sample_spec.channels = channels; if ( ! pa_sample_spec_valid(&sample_spec) ) { g_warning("Invalid spec"); return; } stream = pa_stream_new(context->context, "sndfile plugin playback", &sample_spec, NULL); event_data = g_new0(EventdSoundPulseaudioEventData, 1); event_data->data = data; event_data->length = length; pa_stream_set_state_callback(stream, _eventd_sound_pulseaudio_stream_state_callback, event_data); pa_stream_connect_playback(stream, NULL, NULL, 0, NULL, NULL); }
int pa_cvolume_compatible(const pa_cvolume *v, const pa_sample_spec *ss) { pa_assert(v); pa_assert(ss); pa_return_val_if_fail(pa_cvolume_valid(v), 0); pa_return_val_if_fail(pa_sample_spec_valid(ss), 0); return v->channels == ss->channels; }
/** Pretty print a sample type specification to a string */ char* pa_sample_spec_snprint(char *s, size_t l, const pa_sample_spec *spec) { if ( s == NULL || l == 0 || spec == NULL ) return NULL; if ( pa_sample_spec_valid(spec) ) { snprintf(s, l, "%s %uch %uHz", pa_sample_format_to_string(spec->format), spec->channels, spec->rate); } else { snprintf(s, l, "Invalid"); } return s; }
/* * Create a new pulse audio stream and connect to it * * Return a negative value on error */ static int pulse_connect_stream(struct pulse_data *data) { pa_sample_spec spec; spec.format = data->format; spec.rate = data->samples_per_sec; spec.channels = get_audio_channels(data->speakers); if (!pa_sample_spec_valid(&spec)) { blog(LOG_ERROR, "pulse-input: Sample spec is not valid"); return -1; } data->bytes_per_frame = pa_frame_size(&spec); blog(LOG_DEBUG, "pulse-input: %u bytes per frame", (unsigned int) data->bytes_per_frame); pa_buffer_attr attr; attr.fragsize = get_buffer_size(data, 250); attr.maxlength = (uint32_t) -1; attr.minreq = (uint32_t) -1; attr.prebuf = (uint32_t) -1; attr.tlength = (uint32_t) -1; data->stream = pa_stream_new_with_proplist(data->context, obs_source_getname(data->source), &spec, NULL, data->props); if (!data->stream) { blog(LOG_ERROR, "pulse-input: Unable to create stream"); return -1; } pa_stream_flags_t flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY; if (pa_stream_connect_record(data->stream, NULL, &attr, flags) < 0) { blog(LOG_ERROR, "pulse-input: Unable to connect to stream"); return -1; } for (;;) { pulse_iterate(data); pa_stream_state_t state = pa_stream_get_state(data->stream); if (state == PA_STREAM_READY) { blog(LOG_DEBUG, "pulse-input: Stream ready"); break; } if (!PA_STREAM_IS_GOOD(state)) { blog(LOG_ERROR, "pulse-input: Stream connect failed"); return -1; } } return 0; }
int pa_sndfile_read_sample_spec(SNDFILE *sf, pa_sample_spec *ss) { SF_INFO sfi; int sf_errno; pa_assert(sf); pa_assert(ss); pa_zero(sfi); if ((sf_errno = sf_command(sf, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)))) { pa_log_error("sndfile: %s", sf_error_number(sf_errno)); return -1; } switch (sfi.format & SF_FORMAT_SUBMASK) { case SF_FORMAT_PCM_16: case SF_FORMAT_PCM_U8: case SF_FORMAT_PCM_S8: ss->format = PA_SAMPLE_S16NE; break; case SF_FORMAT_PCM_24: ss->format = PA_SAMPLE_S24NE; break; case SF_FORMAT_PCM_32: ss->format = PA_SAMPLE_S32NE; break; case SF_FORMAT_ULAW: ss->format = PA_SAMPLE_ULAW; break; case SF_FORMAT_ALAW: ss->format = PA_SAMPLE_ALAW; break; case SF_FORMAT_FLOAT: case SF_FORMAT_DOUBLE: default: ss->format = PA_SAMPLE_FLOAT32NE; break; } ss->rate = (uint32_t) sfi.samplerate; ss->channels = (uint8_t) sfi.channels; if (!pa_sample_spec_valid(ss)) return -1; return 0; }
size_t pa_context_get_tile_size(pa_context *c, const pa_sample_spec *ss) { size_t fs, mbs; pa_assert(c); pa_assert(PA_REFCNT_VALUE(c) >= 1); PA_CHECK_VALIDITY_RETURN_ANY(c, !pa_detect_fork(), PA_ERR_FORKED, (size_t) -1); PA_CHECK_VALIDITY_RETURN_ANY(c, !ss || pa_sample_spec_valid(ss), PA_ERR_INVALID, (size_t) -1); fs = ss ? pa_frame_size(ss) : 1; mbs = PA_ROUND_DOWN(pa_mempool_block_size_max(c->mempool), fs); return PA_MAX(mbs, fs); }
APULSE_EXPORT char * pa_sample_spec_snprint(char *s, size_t l, const pa_sample_spec *spec) { trace_info_f("F %s s=%p, l=%u, spec=%p\n", __func__, s, (unsigned)l, spec); if (spec && pa_sample_spec_valid(spec)) { snprintf(s, l, "%s %uch %uHz", pa_sample_format_to_string(spec->format), spec->channels, spec->rate); } else { snprintf(s, l, "invalid"); } return s; }
/** * Pulsaudio init */ static void pa_init () { int r; int i; if (!pa_sample_spec_valid (&sample_spec)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Wrong Spec\n")); } /* set up main record loop */ if (!(m = pa_mainloop_new ())) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("pa_mainloop_new() failed.\n")); } mainloop_api = pa_mainloop_get_api (m); /* listen to signals */ r = pa_signal_init (mainloop_api); GNUNET_assert (r == 0); pa_signal_new (SIGINT, &exit_signal_callback, NULL); pa_signal_new (SIGTERM, &exit_signal_callback, NULL); /* connect to the main pulseaudio context */ if (!(context = pa_context_new (mainloop_api, "GNUNET VoIP"))) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("pa_context_new() failed.\n")); } pa_context_set_state_callback (context, &context_state_callback, NULL); if (pa_context_connect (context, NULL, 0, NULL) < 0) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("pa_context_connect() failed: %s\n"), pa_strerror (pa_context_errno (context))); } if (pa_mainloop_run (m, &i) < 0) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("pa_mainloop_run() failed.\n")); } }
AudioStream::AudioStream(pa_context *c, pa_threaded_mainloop *m, const char *desc, int type, int smplrate, std::string& deviceName) : audiostream_(0), mainloop_(m) { static const pa_channel_map channel_map = { 1, { PA_CHANNEL_POSITION_MONO }, }; pa_sample_spec sample_spec = { PA_SAMPLE_S16LE, // PA_SAMPLE_FLOAT32LE, smplrate, 1 }; assert(pa_sample_spec_valid(&sample_spec)); assert(pa_channel_map_valid(&channel_map)); audiostream_ = pa_stream_new(c, desc, &sample_spec, &channel_map); if (!audiostream_) { ERROR("%s: pa_stream_new() failed : %s" , desc, pa_strerror(pa_context_errno(c))); throw std::runtime_error("Could not create stream\n"); } pa_buffer_attr attributes; attributes.maxlength = pa_usec_to_bytes(160 * PA_USEC_PER_MSEC, &sample_spec); attributes.tlength = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec); attributes.prebuf = 0; attributes.fragsize = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec); attributes.minreq = (uint32_t) -1; pa_threaded_mainloop_lock(mainloop_); if (type == PLAYBACK_STREAM || type == RINGTONE_STREAM) pa_stream_connect_playback(audiostream_, deviceName == "" ? NULL : deviceName.c_str(), &attributes, (pa_stream_flags_t)(PA_STREAM_ADJUST_LATENCY|PA_STREAM_AUTO_TIMING_UPDATE), NULL, NULL); else if (type == CAPTURE_STREAM) pa_stream_connect_record(audiostream_, deviceName == "" ? NULL : deviceName.c_str(), &attributes, (pa_stream_flags_t)(PA_STREAM_ADJUST_LATENCY|PA_STREAM_AUTO_TIMING_UPDATE)); pa_threaded_mainloop_unlock(mainloop_); pa_stream_set_state_callback(audiostream_, stream_state_callback, NULL); }
/* Initialize a new _mbx_out. This is a helper function for _mbx_out_new() */ static void malloc_and_init(_mbx_out *out_p, const char *name, const char *dev_name, _mbx_out_cb cb, void *output_cb_userdata) { *out_p = _mbx_xmalloc(sizeof(struct _mbx_out)); bzero(*out_p, sizeof(struct _mbx_out)); (*out_p)->state = _MBX_OUT_INITIALIZING; (*out_p)->output_cb_userdata = output_cb_userdata; (*out_p)->name = _mbx_xstrdup(name); (*out_p)->dev_name = _mbx_xstrdup(dev_name); /* TODO: The sample spec is hard coded. It should be determined depending * on the capabilities of the sound card. */ (*out_p)->sample_spec.rate = MBX_SAMPLE_RATE; (*out_p)->sample_spec.channels = 2; /* stereo */ (*out_p)->sample_spec.format = PA_SAMPLE_S16LE; /* an invalid sample spec would be a programming error */ assert(pa_sample_spec_valid(&(*out_p)->sample_spec)); (*out_p)->cb = cb; }
gboolean gst_pulse_fill_sample_spec (GstRingBufferSpec * spec, pa_sample_spec * ss) { if (spec->format == GST_MU_LAW && spec->width == 8) ss->format = PA_SAMPLE_ULAW; else if (spec->format == GST_A_LAW && spec->width == 8) ss->format = PA_SAMPLE_ALAW; else if (spec->format == GST_U8 && spec->width == 8) ss->format = PA_SAMPLE_U8; else if (spec->format == GST_S16_LE && spec->width == 16) ss->format = PA_SAMPLE_S16LE; else if (spec->format == GST_S16_BE && spec->width == 16) ss->format = PA_SAMPLE_S16BE; else if (spec->format == GST_FLOAT32_LE && spec->width == 32) ss->format = PA_SAMPLE_FLOAT32LE; else if (spec->format == GST_FLOAT32_BE && spec->width == 32) ss->format = PA_SAMPLE_FLOAT32BE; else if (spec->format == GST_S32_LE && spec->width == 32) ss->format = PA_SAMPLE_S32LE; else if (spec->format == GST_S32_BE && spec->width == 32) ss->format = PA_SAMPLE_S32BE; #ifdef HAVE_PULSE_0_9_15 else if (spec->format == GST_S24_3LE && spec->width == 24) ss->format = PA_SAMPLE_S24LE; else if (spec->format == GST_S24_3BE && spec->width == 24) ss->format = PA_SAMPLE_S24BE; else if (spec->format == GST_S24_LE && spec->width == 32) ss->format = PA_SAMPLE_S24_32LE; else if (spec->format == GST_S24_BE && spec->width == 32) ss->format = PA_SAMPLE_S24_32BE; #endif else return FALSE; ss->channels = spec->channels; ss->rate = spec->rate; if (!pa_sample_spec_valid (ss)) return FALSE; return TRUE; }
pa_format_info* pa_format_info_from_sample_spec(pa_sample_spec *ss, pa_channel_map *map) { char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; pa_format_info *f; pa_assert(ss && pa_sample_spec_valid(ss)); pa_assert(!map || pa_channel_map_valid(map)); f = pa_format_info_new(); f->encoding = PA_ENCODING_PCM; pa_format_info_set_sample_format(f, ss->format); pa_format_info_set_rate(f, ss->rate); pa_format_info_set_channels(f, ss->channels); if (map) { pa_channel_map_snprint(cm, sizeof(cm), map); pa_format_info_set_prop_string(f, PA_PROP_FORMAT_CHANNEL_MAP, cm); } return f; }
gboolean gst_pulse_fill_sample_spec (GstAudioRingBufferSpec * spec, pa_sample_spec * ss) { if (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW) { if (!gstaudioformat_to_pasampleformat (GST_AUDIO_INFO_FORMAT (&spec->info), &ss->format)) return FALSE; } else if (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_MU_LAW) { ss->format = PA_SAMPLE_ULAW; } else if (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_A_LAW) { ss->format = PA_SAMPLE_ALAW; } else return FALSE; ss->channels = GST_AUDIO_INFO_CHANNELS (&spec->info); ss->rate = GST_AUDIO_INFO_RATE (&spec->info); if (!pa_sample_spec_valid (ss)) return FALSE; return TRUE; }
AudioStream::AudioStream(pa_context *c, pa_threaded_mainloop *m, const char *desc, int type, unsigned samplrate, const PaDeviceInfos* infos, bool ec) : audiostream_(0), mainloop_(m) { const pa_channel_map channel_map = infos->channel_map; pa_sample_spec sample_spec = { PA_SAMPLE_S16LE, // PA_SAMPLE_FLOAT32LE, samplrate, channel_map.channels }; RING_DBG("%s: trying to create stream with device %s (%dHz, %d channels)", desc, infos->name.c_str(), samplrate, channel_map.channels); assert(pa_sample_spec_valid(&sample_spec)); assert(pa_channel_map_valid(&channel_map)); std::unique_ptr<pa_proplist, decltype(pa_proplist_free)&> pl (pa_proplist_new(), pa_proplist_free); pa_proplist_sets(pl.get(), PA_PROP_FILTER_WANT, "echo-cancel"); audiostream_ = pa_stream_new_with_proplist(c, desc, &sample_spec, &channel_map, ec ? pl.get() : nullptr); if (!audiostream_) { RING_ERR("%s: pa_stream_new() failed : %s" , desc, pa_strerror(pa_context_errno(c))); throw std::runtime_error("Could not create stream\n"); } pa_buffer_attr attributes; attributes.maxlength = pa_usec_to_bytes(160 * PA_USEC_PER_MSEC, &sample_spec); attributes.tlength = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec); attributes.prebuf = 0; attributes.fragsize = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec); attributes.minreq = (uint32_t) -1; { PulseMainLoopLock lock(mainloop_); const pa_stream_flags_t flags = static_cast<pa_stream_flags_t>(PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE); if (type == PLAYBACK_STREAM || type == RINGTONE_STREAM) { pa_stream_connect_playback(audiostream_, infos->name.empty() ? NULL : infos->name.c_str(), &attributes, flags, NULL, NULL); } else if (type == CAPTURE_STREAM) { pa_stream_connect_record(audiostream_, infos->name.empty() ? NULL : infos->name.c_str(), &attributes, flags); } } pa_stream_set_state_callback(audiostream_, [](pa_stream* s, void* user_data){ static_cast<AudioStream*>(user_data)->stateChanged(s); }, this); pa_stream_set_moved_callback(audiostream_, [](pa_stream* s, void* user_data){ static_cast<AudioStream*>(user_data)->moved(s); }, this); }
int pa__init(pa_module*m) { struct userdata *u = NULL; pa_sample_spec ss; pa_channel_map map; pa_modargs *ma = NULL; jack_status_t status; const char *server_name, *client_name; uint32_t channels = 0; pa_bool_t do_connect = TRUE; unsigned i; const char **ports = NULL, **p; pa_sink_new_data data; pa_assert(m); jack_set_error_function(jack_error_func); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments."); goto fail; } if (pa_modargs_get_value_boolean(ma, "connect", &do_connect) < 0) { pa_log("Failed to parse connect= argument."); goto fail; } server_name = pa_modargs_get_value(ma, "server_name", NULL); client_name = pa_modargs_get_value(ma, "client_name", "PulseAudio JACK Sink"); m->userdata = u = pa_xnew0(struct userdata, 1); u->core = m->core; u->module = m; u->saved_frame_time_valid = FALSE; u->rtpoll = pa_rtpoll_new(); pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); /* The queue linking the JACK thread and our RT thread */ u->jack_msgq = pa_asyncmsgq_new(0); /* The msgq from the JACK RT thread should have an even higher * priority than the normal message queues, to match the guarantee * all other drivers make: supplying the audio device with data is * the top priority -- and as long as that is possible we don't do * anything else */ u->rtpoll_item = pa_rtpoll_item_new_asyncmsgq_read(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq); if (!(u->client = jack_client_open(client_name, server_name ? JackServerName : JackNullOption, &status, server_name))) { pa_log("jack_client_open() failed."); goto fail; } ports = jack_get_ports(u->client, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortIsPhysical|JackPortIsInput); channels = 0; for (p = ports; *p; p++) channels++; if (!channels) channels = m->core->default_sample_spec.channels; if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || channels <= 0 || channels > PA_CHANNELS_MAX) { pa_log("Failed to parse channels= argument."); goto fail; } if (channels == m->core->default_channel_map.channels) map = m->core->default_channel_map; else pa_channel_map_init_extend(&map, channels, PA_CHANNEL_MAP_ALSA); if (pa_modargs_get_channel_map(ma, NULL, &map) < 0 || map.channels != channels) { pa_log("Failed to parse channel_map= argument."); goto fail; } pa_log_info("Successfully connected as '%s'", jack_get_client_name(u->client)); u->channels = ss.channels = (uint8_t) channels; ss.rate = jack_get_sample_rate(u->client); ss.format = PA_SAMPLE_FLOAT32NE; pa_assert(pa_sample_spec_valid(&ss)); for (i = 0; i < ss.channels; i++) { if (!(u->port[i] = jack_port_register(u->client, pa_channel_position_to_string(map.map[i]), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput|JackPortIsTerminal, 0))) { pa_log("jack_port_register() failed."); goto fail; } } pa_sink_new_data_init(&data); data.driver = __FILE__; data.module = m; pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); pa_sink_new_data_set_sample_spec(&data, &ss); pa_sink_new_data_set_channel_map(&data, &map); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "jack"); if (server_name) pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server_name); pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Jack sink (%s)", jack_get_client_name(u->client)); pa_proplist_sets(data.proplist, "jack.client_name", jack_get_client_name(u->client)); if (pa_modargs_get_proplist(ma, "sink_properties", data.proplist, PA_UPDATE_REPLACE) < 0) { pa_log("Invalid properties"); pa_sink_new_data_done(&data); goto fail; } u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); pa_sink_new_data_done(&data); if (!u->sink) { pa_log("Failed to create sink."); goto fail; } u->sink->parent.process_msg = sink_process_msg; u->sink->userdata = u; pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); pa_sink_set_max_request(u->sink, jack_get_buffer_size(u->client) * pa_frame_size(&u->sink->sample_spec)); jack_set_process_callback(u->client, jack_process, u); jack_on_shutdown(u->client, jack_shutdown, u); jack_set_thread_init_callback(u->client, jack_init, u); jack_set_buffer_size_callback(u->client, jack_buffer_size, u); if (!(u->thread = pa_thread_new(thread_func, u))) { pa_log("Failed to create thread."); goto fail; } if (jack_activate(u->client)) { pa_log("jack_activate() failed"); goto fail; } if (do_connect) { for (i = 0, p = ports; i < ss.channels; i++, p++) { if (!*p) { pa_log("Not enough physical output ports, leaving unconnected."); break; } pa_log_info("Connecting %s to %s", jack_port_name(u->port[i]), *p); if (jack_connect(u->client, jack_port_name(u->port[i]), *p)) { pa_log("Failed to connect %s to %s, leaving unconnected.", jack_port_name(u->port[i]), *p); break; } } } pa_sink_put(u->sink); free(ports); pa_modargs_free(ma); return 0; fail: if (ma) pa_modargs_free(ma); free(ports); pa__done(m); return -1; }
int pa_sndfile_write_sample_spec(SF_INFO *sfi, pa_sample_spec *ss) { pa_assert(sfi); pa_assert(ss); sfi->samplerate = (int) ss->rate; sfi->channels = (int) ss->channels; if (pa_sample_format_is_le(ss->format) > 0) sfi->format = SF_ENDIAN_LITTLE; else if (pa_sample_format_is_be(ss->format) > 0) sfi->format = SF_ENDIAN_BIG; switch (ss->format) { case PA_SAMPLE_U8: ss->format = PA_SAMPLE_S16NE; sfi->format = SF_FORMAT_PCM_U8; break; case PA_SAMPLE_S16LE: case PA_SAMPLE_S16BE: ss->format = PA_SAMPLE_S16NE; sfi->format |= SF_FORMAT_PCM_16; break; case PA_SAMPLE_S24LE: case PA_SAMPLE_S24BE: ss->format = PA_SAMPLE_S24NE; sfi->format |= SF_FORMAT_PCM_24; break; case PA_SAMPLE_S24_32LE: case PA_SAMPLE_S24_32BE: ss->format = PA_SAMPLE_S24_32NE; sfi->format |= SF_FORMAT_PCM_32; break; case PA_SAMPLE_S32LE: case PA_SAMPLE_S32BE: ss->format = PA_SAMPLE_S32NE; sfi->format |= SF_FORMAT_PCM_32; break; case PA_SAMPLE_ULAW: sfi->format = SF_FORMAT_ULAW; break; case PA_SAMPLE_ALAW: sfi->format = SF_FORMAT_ALAW; break; case PA_SAMPLE_FLOAT32LE: case PA_SAMPLE_FLOAT32BE: default: ss->format = PA_SAMPLE_FLOAT32NE; sfi->format |= SF_FORMAT_FLOAT; break; } if (!pa_sample_spec_valid(ss)) return -1; return 0; }
int main(int argc, char *argv[]) { pa_mainloop* m = NULL; int ret = 1, c; char *bn, *server = NULL; pa_time_event *time_event = NULL; const char *filename = NULL; static const struct option long_options[] = { {"record", 0, NULL, 'r'}, {"playback", 0, NULL, 'p'}, {"device", 1, NULL, 'd'}, {"server", 1, NULL, 's'}, {"client-name", 1, NULL, 'n'}, {"stream-name", 1, NULL, ARG_STREAM_NAME}, {"version", 0, NULL, ARG_VERSION}, {"help", 0, NULL, 'h'}, {"verbose", 0, NULL, 'v'}, {"volume", 1, NULL, ARG_VOLUME}, {"rate", 1, NULL, ARG_SAMPLERATE}, {"format", 1, NULL, ARG_SAMPLEFORMAT}, {"channels", 1, NULL, ARG_CHANNELS}, {"channel-map", 1, NULL, ARG_CHANNELMAP}, {"fix-format", 0, NULL, ARG_FIX_FORMAT}, {"fix-rate", 0, NULL, ARG_FIX_RATE}, {"fix-channels", 0, NULL, ARG_FIX_CHANNELS}, {"no-remap", 0, NULL, ARG_NO_REMAP}, {"no-remix", 0, NULL, ARG_NO_REMIX}, {"latency", 1, NULL, ARG_LATENCY}, {"process-time", 1, NULL, ARG_PROCESS_TIME}, {"property", 1, NULL, ARG_PROPERTY}, {"raw", 0, NULL, ARG_RAW}, {"file-format", 2, NULL, ARG_FILE_FORMAT}, {"list-file-formats", 0, NULL, ARG_LIST_FILE_FORMATS}, {"latency-msec", 1, NULL, ARG_LATENCY_MSEC}, {"process-time-msec", 1, NULL, ARG_PROCESS_TIME_MSEC}, {NULL, 0, NULL, 0} }; setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, PULSE_LOCALEDIR); bn = pa_path_get_filename(argv[0]); if (strstr(bn, "play")) { mode = PLAYBACK; raw = FALSE; } else if (strstr(bn, "record")) { mode = RECORD; raw = FALSE; } else if (strstr(bn, "cat")) { mode = PLAYBACK; raw = TRUE; } if (strstr(bn, "rec") || strstr(bn, "mon")) { mode = RECORD; raw = TRUE; } proplist = pa_proplist_new(); while ((c = getopt_long(argc, argv, "rpd:s:n:hv", long_options, NULL)) != -1) { switch (c) { case 'h' : help(bn); ret = 0; goto quit; case ARG_VERSION: printf(_("pacat %s\n" "Compiled with libpulse %s\n" "Linked with libpulse %s\n"), PACKAGE_VERSION, pa_get_headers_version(), pa_get_library_version()); ret = 0; goto quit; case 'r': mode = RECORD; break; case 'p': mode = PLAYBACK; break; case 'd': pa_xfree(device); device = pa_xstrdup(optarg); break; case 's': pa_xfree(server); server = pa_xstrdup(optarg); break; case 'n': { char *t; if (!(t = pa_locale_to_utf8(optarg)) || pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, t) < 0) { pa_log(_("Invalid client name '%s'"), t ? t : optarg); pa_xfree(t); goto quit; } pa_xfree(t); break; } case ARG_STREAM_NAME: { char *t; if (!(t = pa_locale_to_utf8(optarg)) || pa_proplist_sets(proplist, PA_PROP_MEDIA_NAME, t) < 0) { pa_log(_("Invalid stream name '%s'"), t ? t : optarg); pa_xfree(t); goto quit; } pa_xfree(t); break; } case 'v': verbose = 1; break; case ARG_VOLUME: { int v = atoi(optarg); volume = v < 0 ? 0U : (pa_volume_t) v; volume_is_set = TRUE; break; } case ARG_CHANNELS: sample_spec.channels = (uint8_t) atoi(optarg); sample_spec_set = TRUE; break; case ARG_SAMPLEFORMAT: sample_spec.format = pa_parse_sample_format(optarg); sample_spec_set = TRUE; break; case ARG_SAMPLERATE: sample_spec.rate = (uint32_t) atoi(optarg); sample_spec_set = TRUE; break; case ARG_CHANNELMAP: if (!pa_channel_map_parse(&channel_map, optarg)) { pa_log(_("Invalid channel map '%s'"), optarg); goto quit; } channel_map_set = TRUE; break; case ARG_FIX_CHANNELS: flags |= PA_STREAM_FIX_CHANNELS; break; case ARG_FIX_RATE: flags |= PA_STREAM_FIX_RATE; break; case ARG_FIX_FORMAT: flags |= PA_STREAM_FIX_FORMAT; break; case ARG_NO_REMIX: flags |= PA_STREAM_NO_REMIX_CHANNELS; break; case ARG_NO_REMAP: flags |= PA_STREAM_NO_REMAP_CHANNELS; break; case ARG_LATENCY: if (((latency = (size_t) atoi(optarg))) <= 0) { pa_log(_("Invalid latency specification '%s'"), optarg); goto quit; } break; case ARG_PROCESS_TIME: if (((process_time = (size_t) atoi(optarg))) <= 0) { pa_log(_("Invalid process time specification '%s'"), optarg); goto quit; } break; case ARG_LATENCY_MSEC: if (((latency_msec = (int32_t) atoi(optarg))) <= 0) { pa_log(_("Invalid latency specification '%s'"), optarg); goto quit; } break; case ARG_PROCESS_TIME_MSEC: if (((process_time_msec = (int32_t) atoi(optarg))) <= 0) { pa_log(_("Invalid process time specification '%s'"), optarg); goto quit; } break; case ARG_PROPERTY: { char *t; if (!(t = pa_locale_to_utf8(optarg)) || pa_proplist_setp(proplist, t) < 0) { pa_xfree(t); pa_log(_("Invalid property '%s'"), optarg); goto quit; } pa_xfree(t); break; } case ARG_RAW: raw = TRUE; break; case ARG_FILE_FORMAT: raw = FALSE; if (optarg) { if ((file_format = pa_sndfile_format_from_string(optarg)) < 0) { pa_log(_("Unknown file format %s."), optarg); goto quit; } } raw = FALSE; break; case ARG_LIST_FILE_FORMATS: pa_sndfile_dump_formats(); ret = 0; goto quit; default: goto quit; } } if (!pa_sample_spec_valid(&sample_spec)) { pa_log(_("Invalid sample specification")); goto quit; } if (optind+1 == argc) { int fd; filename = argv[optind]; if ((fd = open(argv[optind], mode == PLAYBACK ? O_RDONLY : O_WRONLY|O_TRUNC|O_CREAT, 0666)) < 0) { pa_log(_("open(): %s"), strerror(errno)); goto quit; } if (dup2(fd, mode == PLAYBACK ? STDIN_FILENO : STDOUT_FILENO) < 0) { pa_log(_("dup2(): %s"), strerror(errno)); goto quit; } pa_close(fd); } else if (optind+1 <= argc) { pa_log(_("Too many arguments.")); goto quit; } if (!raw) { SF_INFO sfi; pa_zero(sfi); if (mode == RECORD) { /* This might patch up the sample spec */ if (pa_sndfile_write_sample_spec(&sfi, &sample_spec) < 0) { pa_log(_("Failed to generate sample specification for file.")); goto quit; } /* Transparently upgrade classic .wav to wavex for multichannel audio */ if (file_format <= 0) { if ((sample_spec.channels == 2 && (!channel_map_set || (channel_map.map[0] == PA_CHANNEL_POSITION_LEFT && channel_map.map[1] == PA_CHANNEL_POSITION_RIGHT))) || (sample_spec.channels == 1 && (!channel_map_set || (channel_map.map[0] == PA_CHANNEL_POSITION_MONO)))) file_format = SF_FORMAT_WAV; else file_format = SF_FORMAT_WAVEX; } sfi.format |= file_format; } if (!(sndfile = sf_open_fd(mode == RECORD ? STDOUT_FILENO : STDIN_FILENO, mode == RECORD ? SFM_WRITE : SFM_READ, &sfi, 0))) { pa_log(_("Failed to open audio file.")); goto quit; } if (mode == PLAYBACK) { if (sample_spec_set) pa_log(_("Warning: specified sample specification will be overwritten with specification from file.")); if (pa_sndfile_read_sample_spec(sndfile, &sample_spec) < 0) { pa_log(_("Failed to determine sample specification from file.")); goto quit; } sample_spec_set = TRUE; if (!channel_map_set) { /* Allow the user to overwrite the channel map on the command line */ if (pa_sndfile_read_channel_map(sndfile, &channel_map) < 0) { if (sample_spec.channels > 2) pa_log(_("Warning: Failed to determine channel map from file.")); } else channel_map_set = TRUE; } } } if (!channel_map_set) pa_channel_map_init_extend(&channel_map, sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); if (!pa_channel_map_compatible(&channel_map, &sample_spec)) { pa_log(_("Channel map doesn't match sample specification")); goto quit; } if (!raw) { pa_proplist *sfp; if (mode == PLAYBACK) readf_function = pa_sndfile_readf_function(&sample_spec); else { if (pa_sndfile_write_channel_map(sndfile, &channel_map) < 0) pa_log(_("Warning: failed to write channel map to file.")); writef_function = pa_sndfile_writef_function(&sample_spec); } /* Fill in libsndfile prop list data */ sfp = pa_proplist_new(); pa_sndfile_init_proplist(sndfile, sfp); pa_proplist_update(proplist, PA_UPDATE_MERGE, sfp); pa_proplist_free(sfp); } if (verbose) { char tss[PA_SAMPLE_SPEC_SNPRINT_MAX], tcm[PA_CHANNEL_MAP_SNPRINT_MAX]; pa_log(_("Opening a %s stream with sample specification '%s' and channel map '%s'."), mode == RECORD ? _("recording") : _("playback"), pa_sample_spec_snprint(tss, sizeof(tss), &sample_spec), pa_channel_map_snprint(tcm, sizeof(tcm), &channel_map)); } /* Fill in client name if none was set */ if (!pa_proplist_contains(proplist, PA_PROP_APPLICATION_NAME)) { char *t; if ((t = pa_locale_to_utf8(bn))) { pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, t); pa_xfree(t); } } /* Fill in media name if none was set */ if (!pa_proplist_contains(proplist, PA_PROP_MEDIA_NAME)) { const char *t; if ((t = filename) || (t = pa_proplist_gets(proplist, PA_PROP_APPLICATION_NAME))) pa_proplist_sets(proplist, PA_PROP_MEDIA_NAME, t); } /* Set up a new main loop */ if (!(m = pa_mainloop_new())) { pa_log(_("pa_mainloop_new() failed.")); goto quit; } mainloop_api = pa_mainloop_get_api(m); pa_assert_se(pa_signal_init(mainloop_api) == 0); pa_signal_new(SIGINT, exit_signal_callback, NULL); pa_signal_new(SIGTERM, exit_signal_callback, NULL); #ifdef SIGUSR1 pa_signal_new(SIGUSR1, sigusr1_signal_callback, NULL); #endif pa_disable_sigpipe(); if (raw) { if (!(stdio_event = mainloop_api->io_new(mainloop_api, mode == PLAYBACK ? STDIN_FILENO : STDOUT_FILENO, mode == PLAYBACK ? PA_IO_EVENT_INPUT : PA_IO_EVENT_OUTPUT, mode == PLAYBACK ? stdin_callback : stdout_callback, NULL))) { pa_log(_("io_new() failed.")); goto quit; } } /* Create a new connection context */ if (!(context = pa_context_new_with_proplist(mainloop_api, NULL, proplist))) { pa_log(_("pa_context_new() failed.")); goto quit; } pa_context_set_state_callback(context, context_state_callback, NULL); /* Connect the context */ if (pa_context_connect(context, server, 0, NULL) < 0) { pa_log(_("pa_context_connect() failed: %s"), pa_strerror(pa_context_errno(context))); goto quit; } if (verbose) { if (!(time_event = pa_context_rttime_new(context, pa_rtclock_now() + TIME_EVENT_USEC, time_event_callback, NULL))) { pa_log(_("pa_context_rttime_new() failed.")); goto quit; } } /* Run the main loop */ if (pa_mainloop_run(m, &ret) < 0) { pa_log(_("pa_mainloop_run() failed.")); goto quit; } quit: if (stream) pa_stream_unref(stream); if (context) pa_context_unref(context); if (stdio_event) { pa_assert(mainloop_api); mainloop_api->io_free(stdio_event); } if (time_event) { pa_assert(mainloop_api); mainloop_api->time_free(time_event); } if (m) { pa_signal_done(); pa_mainloop_free(m); } pa_xfree(buffer); pa_xfree(server); pa_xfree(device); if (sndfile) sf_close(sndfile); if (proplist) pa_proplist_free(proplist); return ret; }
static int init(struct ao *ao, char *params) { struct pa_sample_spec ss; struct pa_channel_map map; char *devarg = NULL; char *host = NULL; char *sink = NULL; const char *version = pa_get_library_version(); struct priv *priv = talloc_zero(ao, struct priv); ao->priv = priv; if (params) { devarg = strdup(params); sink = strchr(devarg, ':'); if (sink) *sink++ = 0; if (devarg[0]) host = devarg; } priv->broken_pause = false; /* not sure which versions are affected, assume 0.9.11* to 0.9.14* * known bad: 0.9.14, 0.9.13 * known good: 0.9.9, 0.9.10, 0.9.15 * To test: pause, wait ca. 5 seconds, framestep and see if MPlayer * hangs somewhen. */ if (strncmp(version, "0.9.1", 5) == 0 && version[5] >= '1' && version[5] <= '4') { mp_msg(MSGT_AO, MSGL_WARN, "[pulse] working around probably broken pause functionality,\n" " see http://www.pulseaudio.org/ticket/440\n"); priv->broken_pause = true; } ss.channels = ao->channels; ss.rate = ao->samplerate; const struct format_map *fmt_map = format_maps; while (fmt_map->mp_format != ao->format) { if (fmt_map->mp_format == AF_FORMAT_UNKNOWN) { mp_msg(MSGT_AO, MSGL_V, "AO: [pulse] Unsupported format, using default\n"); fmt_map = format_maps; break; } fmt_map++; } ao->format = fmt_map->mp_format; ss.format = fmt_map->pa_format; if (!pa_sample_spec_valid(&ss)) { mp_msg(MSGT_AO, MSGL_ERR, "AO: [pulse] Invalid sample spec\n"); goto fail; } pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_ALSA); ao->bps = pa_bytes_per_second(&ss); if (!(priv->mainloop = pa_threaded_mainloop_new())) { mp_msg(MSGT_AO, MSGL_ERR, "AO: [pulse] Failed to allocate main loop\n"); goto fail; } if (!(priv->context = pa_context_new(pa_threaded_mainloop_get_api( priv->mainloop), PULSE_CLIENT_NAME))) { mp_msg(MSGT_AO, MSGL_ERR, "AO: [pulse] Failed to allocate context\n"); goto fail; } pa_context_set_state_callback(priv->context, context_state_cb, ao); if (pa_context_connect(priv->context, host, 0, NULL) < 0) goto fail; pa_threaded_mainloop_lock(priv->mainloop); if (pa_threaded_mainloop_start(priv->mainloop) < 0) goto unlock_and_fail; /* Wait until the context is ready */ pa_threaded_mainloop_wait(priv->mainloop); if (pa_context_get_state(priv->context) != PA_CONTEXT_READY) goto unlock_and_fail; if (!(priv->stream = pa_stream_new(priv->context, "audio stream", &ss, &map))) goto unlock_and_fail; 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); pa_buffer_attr bufattr = { .maxlength = -1, .tlength = pa_usec_to_bytes(1000000, &ss), .prebuf = -1, .minreq = -1, .fragsize = -1, }; if (pa_stream_connect_playback(priv->stream, sink, &bufattr, PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL) < 0) goto unlock_and_fail; /* Wait until the stream is ready */ pa_threaded_mainloop_wait(priv->mainloop); if (pa_stream_get_state(priv->stream) != PA_STREAM_READY) goto unlock_and_fail; pa_threaded_mainloop_unlock(priv->mainloop); free(devarg); return 0; unlock_and_fail: if (priv->mainloop) pa_threaded_mainloop_unlock(priv->mainloop); fail: if (priv->context) GENERIC_ERR_MSG(priv->context, "Init failed"); free(devarg); uninit(ao, true); 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(priv->context, "pa_stream_cork() failed"); } // Play the specified data to the pulseaudio server static int play(struct ao *ao, void *data, int len, int flags) { struct priv *priv = ao->priv; /* For some reason Pulseaudio behaves worse if this is done after * the write - rapidly repeated seeks result in bogus increasing * reported latency. */ if (priv->did_reset) cork(ao, false); pa_threaded_mainloop_lock(priv->mainloop); if (pa_stream_write(priv->stream, data, len, NULL, 0, PA_SEEK_RELATIVE) < 0) { GENERIC_ERR_MSG(priv->context, "pa_stream_write() failed"); len = -1; } if (priv->did_reset) { priv->did_reset = false; if (!waitop(priv, pa_stream_update_timing_info(priv->stream, success_cb, ao)) || !priv->retval) GENERIC_ERR_MSG(priv->context, "pa_stream_UPP() failed"); } else pa_threaded_mainloop_unlock(priv->mainloop); return len; } // 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(priv->context, "pa_stream_flush() failed"); priv->did_reset = true; }
bool CPulseAESound::Initialize() { /* we dont re-init the wav loader in PA as PA handles the samplerate */ if (!m_wavLoader.IsValid()) return false; m_sampleSpec.format = PA_SAMPLE_FLOAT32NE; m_sampleSpec.rate = m_wavLoader.GetSampleRate(); m_sampleSpec.channels = m_wavLoader.GetChannelLayout().Count(); if (!pa_sample_spec_valid(&m_sampleSpec)) { CLog::Log(LOGERROR, "CPulseAESound::Initialize - Invalid sample spec"); return false; } struct pa_channel_map map; map.channels = m_sampleSpec.channels; switch (map.channels) { case 1: map.map[0] = PA_CHANNEL_POSITION_MONO; break; case 2: map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; break; default: CLog::Log(LOGERROR, "CPulseAESound::Initialize - We do not yet support multichannel sounds"); return false; } 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); pa_threaded_mainloop_lock(m_mainLoop); if ((m_stream = pa_stream_new(m_context, m_pulseName.c_str(), &m_sampleSpec, &map)) == NULL) { CLog::Log(LOGERROR, "CPulseAESound::Initialize - Could not create a stream"); pa_threaded_mainloop_unlock(m_mainLoop); return false; } pa_stream_set_state_callback(m_stream, CPulseAESound::StreamStateCallback, this); pa_stream_set_write_callback(m_stream, CPulseAESound::StreamWriteCallback, this); if (pa_stream_connect_upload(m_stream, m_wavLoader.GetFrameCount() * pa_frame_size(&m_sampleSpec)) != 0) { CLog::Log(LOGERROR, "CPulseAESound::Initialize - Could not initialize the stream"); pa_stream_disconnect(m_stream); m_stream = NULL; pa_threaded_mainloop_unlock(m_mainLoop); return false; } /* check if the stream failed */ if (pa_stream_get_state(m_stream) == PA_STREAM_FAILED) { CLog::Log(LOGERROR, "CPulseAESound::Initialize - Waited for the stream but it failed"); pa_stream_disconnect(m_stream); m_stream = NULL; pa_threaded_mainloop_unlock(m_mainLoop); return false; } pa_threaded_mainloop_unlock(m_mainLoop); return true; }
int PulseAudioDriver::setup(bool capture, bool playback, const QString& ) { PENTER; sample_spec.rate = frame_rate; sample_spec.channels = 2; sample_spec.format = PA_SAMPLE_FLOAT32NE; assert(pa_sample_spec_valid(&sample_spec)); if (channel_map_set && channel_map.channels != sample_spec.channels) { fprintf(stderr, "Channel map doesn't match file.\n"); return -1; } /* Set up a new main loop */ if (!(mainloop = pa_mainloop_new())) { fprintf(stderr, "pa_mainloop_new() failed.\n"); return -1; } mainloop_api = pa_mainloop_get_api(mainloop); int r = pa_signal_init(mainloop_api); assert(r == 0); /* Create a new connection context */ if (!(context = pa_context_new(mainloop_api, "Traverso"))) { fprintf(stderr, "pa_context_new() failed.\n"); return -1; } pa_context_set_state_callback(context, context_state_callback, this); /* Connect the context */ pa_context_connect(context, "", (pa_context_flags_t)0, NULL); int ret; /* Run the main loop */ // if (pa_mainloop_run(mainloop, &ret) < 0) { // fprintf(stderr, "pa_mainloop_run() failed.\n"); // return -1; // } AudioChannel* audiochannel; int port_flags; char buf[32]; // TODO use the found maxchannel count for the playback stream, instead of assuming 2 !! for (int chn = 0; chn < 2; chn++) { snprintf (buf, sizeof(buf) - 1, "playback_%d", chn+1); audiochannel = device->register_playback_channel(buf, "32 bit float audio", port_flags, frames_per_cycle, chn); audiochannel->set_latency( frames_per_cycle + capture_frame_latency ); playbackChannels.append(audiochannel); } // TODO use the found maxchannel count for the capture stream, instead of assuming 0 !! for (int chn = 0; chn < 2; chn++) { snprintf (buf, sizeof(buf) - 1, "capture_%d", chn+1); audiochannel = device->register_capture_channel(buf, "32 bit float audio", port_flags, frames_per_cycle, chn); audiochannel->set_latency( frames_per_cycle + capture_frame_latency ); captureChannels.append(audiochannel); } return 1; }
static int pulse_open() { ENTER(__FUNCTION__); pa_sample_spec ss; pa_operation *o = NULL; int success; int ret = PULSE_ERROR; assert(!mainloop); assert(!context); assert(!stream); assert(!connected); pthread_mutex_init( &pulse_mutex, (const pthread_mutexattr_t *)NULL); ss.format = ESPEAK_FORMAT; ss.rate = wave_samplerate; ss.channels = ESPEAK_CHANNEL; if (!pa_sample_spec_valid(&ss)) return false; SHOW_TIME("pa_threaded_mainloop_new (call)"); if (!(mainloop = pa_threaded_mainloop_new())) { SHOW("Failed to allocate main loop\n",""); goto fail; } pa_threaded_mainloop_lock(mainloop); SHOW_TIME("pa_context_new (call)"); if (!(context = pa_context_new(pa_threaded_mainloop_get_api(mainloop), "eSpeak"))) { SHOW("Failed to allocate context\n",""); goto unlock_and_fail; } pa_context_set_state_callback(context, context_state_cb, NULL); pa_context_set_subscribe_callback(context, subscribe_cb, NULL); SHOW_TIME("pa_context_connect (call)"); if (pa_context_connect(context, NULL, (pa_context_flags_t)0, NULL) < 0) { SHOW("Failed to connect to server: %s", pa_strerror(pa_context_errno(context))); ret = PULSE_NO_CONNECTION; goto unlock_and_fail; } SHOW_TIME("pa_threaded_mainloop_start (call)"); if (pa_threaded_mainloop_start(mainloop) < 0) { SHOW("Failed to start main loop",""); goto unlock_and_fail; } /* Wait until the context is ready */ SHOW_TIME("pa_threaded_mainloop_wait"); pa_threaded_mainloop_wait(mainloop); if (pa_context_get_state(context) != PA_CONTEXT_READY) { SHOW("Failed to connect to server: %s", pa_strerror(pa_context_errno(context))); ret = PULSE_NO_CONNECTION; if (mainloop) pa_threaded_mainloop_stop(mainloop); goto unlock_and_fail; } SHOW_TIME("pa_stream_new"); if (!(stream = pa_stream_new(context, "unknown", &ss, NULL))) { SHOW("Failed to create stream: %s", pa_strerror(pa_context_errno(context))); goto unlock_and_fail; } pa_stream_set_state_callback(stream, stream_state_cb, NULL); pa_stream_set_write_callback(stream, stream_request_cb, NULL); pa_stream_set_latency_update_callback(stream, stream_latency_update_cb, NULL); pa_buffer_attr a_attr; a_attr.maxlength = MAXLENGTH; a_attr.tlength = TLENGTH; a_attr.prebuf = PREBUF; a_attr.minreq = MINREQ; a_attr.fragsize = 0; SHOW_TIME("pa_connect_playback"); if (pa_stream_connect_playback(stream, NULL, &a_attr, (pa_stream_flags_t)(PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE), NULL, NULL) < 0) { SHOW("Failed to connect stream: %s", pa_strerror(pa_context_errno(context))); goto unlock_and_fail; } /* Wait until the stream is ready */ SHOW_TIME("pa_threaded_mainloop_wait"); pa_threaded_mainloop_wait(mainloop); if (pa_stream_get_state(stream) != PA_STREAM_READY) { SHOW("Failed to connect stream: %s", pa_strerror(pa_context_errno(context))); goto unlock_and_fail; } /* Now subscribe to events */ SHOW_TIME("pa_context_subscribe"); if (!(o = pa_context_subscribe(context, PA_SUBSCRIPTION_MASK_SINK_INPUT, context_success_cb, &success))) { SHOW("pa_context_subscribe() failed: %s", pa_strerror(pa_context_errno(context))); goto unlock_and_fail; } success = 0; SHOW_TIME("pa_threaded_mainloop_wait"); while (pa_operation_get_state(o) != PA_OPERATION_DONE) { CHECK_DEAD_GOTO(fail, 1); pa_threaded_mainloop_wait(mainloop); } pa_operation_unref(o); if (!success) { SHOW("pa_context_subscribe() failed: %s", pa_strerror(pa_context_errno(context))); goto unlock_and_fail; } do_trigger = 0; written = 0; time_offset_msec = 0; just_flushed = 0; connected = 1; pa_threaded_mainloop_unlock(mainloop); SHOW_TIME("pulse_open (ret true)"); return PULSE_OK; unlock_and_fail: if (o) pa_operation_unref(o); pa_threaded_mainloop_unlock(mainloop); fail: // pulse_close(); if (ret == PULSE_NO_CONNECTION) { if (context) { SHOW_TIME("pa_context_disconnect (call)"); pa_context_disconnect(context); pa_context_unref(context); context = NULL; } if (mainloop) { SHOW_TIME("pa_threaded_mainloop_free (call)"); pa_threaded_mainloop_free(mainloop); mainloop = NULL; } } else { pulse_close(); } SHOW_TIME("pulse_open (ret false)"); return ret; }
void RageSoundDriver_PulseAudio::m_InitStream(void) { int error; pa_sample_spec ss; pa_channel_map map; /* init sample spec */ ss.format = PA_SAMPLE_S16LE; ss.channels = 2; ss.rate = PREFSMAN->m_iSoundPreferredSampleRate; if(ss.rate == 0) { ss.rate = 44100; } /* init channel map */ pa_channel_map_init_stereo(&map); /* check sample spec */ if(!pa_sample_spec_valid(&ss)) { if(asprintf(&m_Error, "invalid sample spec!") == -1) { m_Error = nullptr; } m_Sem.Post(); return; } /* log the used sample spec */ char specstring[PA_SAMPLE_SPEC_SNPRINT_MAX]; pa_sample_spec_snprint(specstring, sizeof(specstring), &ss); LOG->Trace("Pulse: using sample spec: %s", specstring); /* create the stream */ LOG->Trace("Pulse: pa_stream_new()..."); m_PulseStream = pa_stream_new(m_PulseCtx, "Stepmania Audio", &ss, &map); if(m_PulseStream == nullptr) { if(asprintf(&m_Error, "pa_stream_new(): %s", pa_strerror(pa_context_errno(m_PulseCtx))) == -1) { m_Error = nullptr; } m_Sem.Post(); return; } /* set the write callback, it will be called when the sound server * needs data */ pa_stream_set_write_callback(m_PulseStream, StaticStreamWriteCb, this); /* set the state callback, it will be called the the stream state will * change */ pa_stream_set_state_callback(m_PulseStream, StaticStreamStateCb, this); /* configure attributes of the stream */ pa_buffer_attr attr; memset(&attr, 0x00, sizeof(attr)); /* tlength: Target length of the buffer. * * "The server tries to assure that at least tlength bytes are always * available in the per-stream server-side playback buffer. It is * recommended to set this to (uint32_t) -1, which will initialize * this to a value that is deemed sensible by the server. However, * this value will default to something like 2s, i.e. for applications * that have specific latency requirements this value should be set to * the maximum latency that the application can deal with." * * We don't want the default here, we want a small latency. * We use pa_usec_to_bytes() to convert a latency to a buffer size. */ attr.tlength = pa_usec_to_bytes(20*PA_USEC_PER_MSEC, &ss); /* maxlength: Maximum length of the buffer * * "Setting this to (uint32_t) -1 will initialize this to the maximum * value supported by server, which is recommended." * * (uint32_t)-1 is NOT working here, setting it to tlength*2, like * openal-soft-pulseaudio does. */ attr.maxlength = attr.tlength*2; /* minreq: Minimum request * * "The server does not request less than minreq bytes from the client, * instead waits until the buffer is free enough to request more bytes * at once. It is recommended to set this to (uint32_t) -1, which will * initialize this to a value that is deemed sensible by the server." * * (uint32_t)-1 is NOT working here, setting it to 0, like * openal-soft-pulseaudio does. */ attr.minreq = 0; /* prebuf: Pre-buffering * * "The server does not start with playback before at least prebuf * bytes are available in the buffer. It is recommended to set this * to (uint32_t) -1, which will initialize this to the same value as * tlength" */ attr.prebuf = (uint32_t)-1; /* log the used target buffer length */ LOG->Trace("Pulse: using target buffer length of %i bytes", attr.tlength); /* connect the stream for playback */ LOG->Trace("Pulse: pa_stream_connect_playback()..."); error = pa_stream_connect_playback(m_PulseStream, nullptr, &attr, PA_STREAM_AUTO_TIMING_UPDATE, nullptr, nullptr); if(error < 0) { if(asprintf(&m_Error, "pa_stream_connect_playback(): %s", pa_strerror(pa_context_errno(m_PulseCtx))) == -1) { m_Error = nullptr; } m_Sem.Post(); return; } m_SampleRate = ss.rate; }
static void resolver_cb( AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *a, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void *userdata) { struct userdata *u = userdata; struct tunnel *tnl; pa_assert(u); tnl = tunnel_new(interface, protocol, name, type, domain); if (event != AVAHI_RESOLVER_FOUND) pa_log("Resolving of '%s' failed: %s", name, avahi_strerror(avahi_client_errno(u->client))); else { char *device = NULL, *dname, *module_name, *args; const char *t; char at[AVAHI_ADDRESS_STR_MAX], cmt[PA_CHANNEL_MAP_SNPRINT_MAX]; pa_sample_spec ss; pa_channel_map cm; AvahiStringList *l; pa_bool_t channel_map_set = FALSE; pa_module *m; ss = u->core->default_sample_spec; cm = u->core->default_channel_map; for (l = txt; l; l = l->next) { char *key, *value; pa_assert_se(avahi_string_list_get_pair(l, &key, &value, NULL) == 0); if (pa_streq(key, "device")) { pa_xfree(device); device = value; value = NULL; } else if (pa_streq(key, "rate")) ss.rate = (uint32_t) atoi(value); else if (pa_streq(key, "channels")) ss.channels = (uint8_t) atoi(value); else if (pa_streq(key, "format")) ss.format = pa_parse_sample_format(value); else if (pa_streq(key, "channel_map")) { pa_channel_map_parse(&cm, value); channel_map_set = TRUE; } avahi_free(key); avahi_free(value); } if (!channel_map_set && cm.channels != ss.channels) pa_channel_map_init_extend(&cm, ss.channels, PA_CHANNEL_MAP_DEFAULT); if (!pa_sample_spec_valid(&ss)) { pa_log("Service '%s' contains an invalid sample specification.", name); avahi_free(device); goto finish; } if (!pa_channel_map_valid(&cm) || cm.channels != ss.channels) { pa_log("Service '%s' contains an invalid channel map.", name); avahi_free(device); goto finish; } if (device) dname = pa_sprintf_malloc("tunnel.%s.%s", host_name, device); else dname = pa_sprintf_malloc("tunnel.%s", host_name); if (!pa_namereg_is_valid_name(dname)) { pa_log("Cannot construct valid device name from credentials of service '%s'.", dname); avahi_free(device); pa_xfree(dname); goto finish; } t = strstr(type, "sink") ? "sink" : "source"; module_name = pa_sprintf_malloc("module-tunnel-%s", t); args = pa_sprintf_malloc("server=[%s]:%u " "%s=%s " "format=%s " "channels=%u " "rate=%u " "%s_name=%s " "channel_map=%s", avahi_address_snprint(at, sizeof(at), a), port, t, device, pa_sample_format_to_string(ss.format), ss.channels, ss.rate, t, dname, pa_channel_map_snprint(cmt, sizeof(cmt), &cm)); pa_log_debug("Loading %s with arguments '%s'", module_name, args); if ((m = pa_module_load(u->core, module_name, args))) { tnl->module_index = m->index; pa_hashmap_put(u->tunnels, tnl, tnl); tnl = NULL; } pa_xfree(module_name); pa_xfree(dname); pa_xfree(args); avahi_free(device); } finish: avahi_service_resolver_free(r); if (tnl) tunnel_free(tnl); }