Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1379 lines (1142 sloc) 38.2 KB
/*
* Copyright © 2014-2018 Rinat Ibragimov
*
* This file is part of "apulse" project.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "apulse.h"
#include "trace.h"
#include "util.h"
#define MAKE_SND_LIB_VERSION(a, b, c) (((a) << 16) | ((b) << 8) | (c))
#define HAVE_SND_PCM_AVAIL SND_LIB_VERSION >= MAKE_SND_LIB_VERSION(1, 0, 18)
static void
deh_stream_state_changed(pa_mainloop_api *api, pa_defer_event *de,
void *userdata)
{
pa_stream *s = userdata;
if (s->state_cb)
s->state_cb(s, s->state_cb_userdata);
pa_stream_unref(s);
}
static void
deh_stream_first_readwrite_callback(pa_mainloop_api *api, pa_defer_event *de,
void *userdata)
{
pa_stream *s = userdata;
if (s->direction == PA_STREAM_PLAYBACK) {
size_t writable_size = pa_stream_writable_size(s);
if (s->write_cb && writable_size > 0)
s->write_cb(s, writable_size, s->write_cb_userdata);
} else if (s->direction == PA_STREAM_RECORD) {
size_t readable_size = pa_stream_readable_size(s);
if (s->read_cb && readable_size > 0)
s->read_cb(s, readable_size, s->read_cb_userdata);
}
pa_stream_unref(s);
}
static void
data_available_for_stream(pa_mainloop_api *a, pa_io_event *ioe, int fd,
pa_io_event_flags_t events, void *userdata)
{
pa_stream *s = userdata;
snd_pcm_sframes_t frame_count;
size_t frame_size = pa_frame_size(&s->ss);
char buf[16 * 1024];
const size_t buf_size = pa_find_multiple_of(sizeof(buf), frame_size, 0);
int paused = g_atomic_int_get(&s->paused);
if (events & (PA_IO_EVENT_INPUT | PA_IO_EVENT_OUTPUT)) {
#if HAVE_SND_PCM_AVAIL
frame_count = snd_pcm_avail(s->ph);
#else
snd_pcm_hwsync(s->ph);
frame_count = snd_pcm_avail_update(s->ph);
#endif
if (frame_count < 0) {
if (frame_count == -EBADFD) {
// stream was closed
return;
}
int cnt = 0, ret;
do {
cnt++;
ret = snd_pcm_recover(s->ph, frame_count, 1);
} while (ret == -1 && errno == EINTR && cnt < 5);
switch (snd_pcm_state(s->ph)) {
case SND_PCM_STATE_OPEN:
// Highly unlikely device will be here in this state. But if it
// is, there is nothing can be done.
trace_error(
"Stream '%s' of context '%s' have its associated PCM "
"device in SND_PCM_STATE_OPEN state. Reconfiguration is "
"required, but is not possible at the moment. Giving up.",
s->name ? s->name : "", s->c->name ? s->c->name : "");
break;
case SND_PCM_STATE_SETUP:
// There is configuration, but device is not prepared and not
// started.
snd_pcm_prepare(s->ph);
snd_pcm_start(s->ph);
break;
case SND_PCM_STATE_PREPARED:
// Device prepared, but not started.
snd_pcm_start(s->ph);
break;
case SND_PCM_STATE_RUNNING:
// That's the expected state.
break;
case SND_PCM_STATE_XRUN:
trace_error(
"Stream '%s' of context '%s' have its associated device in "
"SND_PCM_STATE_XRUN state even after xrun recovery.",
s->name ? s->name : "", s->c->name ? s->c->name : "");
break;
case SND_PCM_STATE_DRAINING:
trace_error(
"Stream '%s' of context '%s' have its associated device in "
"SND_PCM_STATE_DRAINING state, which is highly unusual.",
s->name ? s->name : "", s->c->name ? s->c->name : "");
break;
case SND_PCM_STATE_PAUSED:
// Resume from paused state.
snd_pcm_pause(s->ph, 0);
break;
case SND_PCM_STATE_SUSPENDED:
// Resume from suspended state.
snd_pcm_resume(s->ph);
break;
case SND_PCM_STATE_DISCONNECTED:
trace_error(
"Stream '%s' of context '%s' have its associated device in "
"SND_PCM_STATE_DISCONNECTED state. Giving up.",
s->name ? s->name : "", s->c->name ? s->c->name : "");
break;
default:
// avoid compiler warnings of unhandled (library-private) enum values
break;
}
#if HAVE_SND_PCM_AVAIL
frame_count = snd_pcm_avail(s->ph);
#else
snd_pcm_hwsync(s->ph);
frame_count = snd_pcm_avail_update(s->ph);
#endif
if (frame_count < 0) {
trace_error(
"%s, can't recover after failed snd_pcm_avail (%d)\n",
__func__, (int)frame_count);
return;
}
}
} else {
return;
}
if (events & PA_IO_EVENT_OUTPUT) {
if (paused) {
// client stream is corked. Pass silence to ALSA
size_t bytecnt = MIN(buf_size, frame_count * frame_size);
memset(buf, 0, bytecnt);
snd_pcm_writei(s->ph, buf, bytecnt / frame_size);
} else {
size_t writable_size = pa_stream_writable_size(s);
// Ask client for data, but only if we are ready for at least
// |minreq| bytes.
if (s->write_cb && writable_size >= s->buffer_attr.minreq)
s->write_cb(s, s->buffer_attr.minreq, s->write_cb_userdata);
size_t bytecnt = MIN(buf_size, frame_count * frame_size);
bytecnt = ringbuffer_read(s->rb, buf, bytecnt);
pa_apply_volume_multiplier(buf, bytecnt, s->volume, &s->ss);
if (bytecnt == 0) {
// application is not ready yet, play silence
bytecnt = MIN(buf_size, frame_count * frame_size);
memset(buf, 0, bytecnt);
}
snd_pcm_writei(s->ph, buf, bytecnt / frame_size);
}
}
if (events & PA_IO_EVENT_INPUT) {
if (paused) {
// client stream is corked. Read data from ALSA and discard them
size_t bytecnt = MIN(buf_size, frame_count * frame_size);
snd_pcm_readi(s->ph, buf, bytecnt / frame_size);
} else {
size_t bytecnt = ringbuffer_writable_size(s->rb);
if (bytecnt == 0) {
// ringbuffer is full because app doesn't read data fast enough.
// Make some room
ringbuffer_drop(s->rb, frame_count * frame_size);
bytecnt = ringbuffer_writable_size(s->rb);
}
bytecnt = MIN(bytecnt, frame_count * frame_size);
bytecnt = MIN(bytecnt, buf_size);
if (bytecnt > 0) {
snd_pcm_readi(s->ph, buf, bytecnt / frame_size);
pa_apply_volume_multiplier(buf, bytecnt, s->c->source_volume,
&s->ss);
ringbuffer_write(s->rb, buf, bytecnt);
}
size_t readable_size = pa_stream_readable_size(s);
if (s->read_cb && readable_size > 0)
s->read_cb(s, readable_size, s->read_cb_userdata);
}
}
}
static int
do_connect_pcm(pa_stream *s, snd_pcm_stream_t stream_direction)
{
snd_pcm_hw_params_t *hw_params;
snd_pcm_sw_params_t *sw_params;
int errcode = 0;
const char *device_name;
const char *direction_name;
switch (stream_direction) {
default:
case SND_PCM_STREAM_PLAYBACK:
device_name = getenv("APULSE_PLAYBACK_DEVICE");
direction_name = "playback";
break;
case SND_PCM_STREAM_CAPTURE:
device_name = getenv("APULSE_CAPTURE_DEVICE");
direction_name = "capture";
break;
}
if (device_name == NULL)
device_name = "default";
char *device_description =
g_strdup_printf("%s device \"%s\"", direction_name, device_name);
if (!device_description) {
trace_error("%s: can't allocate memory for device description string\n",
__func__);
goto fatal_error;
}
errcode = snd_pcm_open(&s->ph, device_name, stream_direction, 0);
if (errcode != 0) {
trace_error("%s: can't open %s. Error code %d (%s)\n", __func__,
device_description, errcode, snd_strerror(errcode));
goto fatal_error;
}
errcode = snd_pcm_hw_params_malloc(&hw_params);
if (errcode != 0) {
trace_error(
"%s: can't allocate memory for hw parameters for %s. Error code %d "
"(%s)\n",
__func__, device_description, errcode, snd_strerror(errcode));
goto fatal_error;
}
errcode = snd_pcm_hw_params_any(s->ph, hw_params);
if (errcode != 0) {
trace_error(
"%s: can't get initial hw parameters for %s. Error code %d (%s)\n",
__func__, device_description, errcode, snd_strerror(errcode));
goto fatal_error;
}
errcode = snd_pcm_hw_params_set_access(s->ph, hw_params,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (errcode != 0) {
trace_error(
"%s: can't select interleaved mode for %s. Error code %d (%s)\n",
__func__, device_description, errcode, snd_strerror(errcode));
// TODO: is it worth to support non-interleaved mode?
goto fatal_error;
}
errcode = snd_pcm_hw_params_set_format(s->ph, hw_params,
pa_format_to_alsa(s->ss.format));
if (errcode != 0) {
snd_pcm_format_t alsa_format = pa_format_to_alsa(s->ss.format);
trace_error(
"%s: can't set sample format %d (\"%s\") for %s. Error code %d "
"(%s)\n",
__func__, alsa_format, snd_pcm_format_name(alsa_format),
device_description, errcode, snd_strerror(errcode));
goto fatal_error;
}
errcode = snd_pcm_hw_params_set_rate_resample(s->ph, hw_params, 1);
if (errcode != 0) {
trace_error(
"%s: can't enable rate resample for %s. Error code %d (%s)\n",
__func__, device_description, errcode, snd_strerror(errcode));
// This is not a fatal error. Audio speed will be wrong, but there will
// be something.
}
unsigned int rate = s->ss.rate;
int dir = 0;
errcode = snd_pcm_hw_params_set_rate_near(s->ph, hw_params, &rate, &dir);
if (errcode != 0) {
trace_error("%s: can't set sample rate for %s. Error code %d (%s)\n",
__func__, device_description, errcode,
snd_strerror(errcode));
goto fatal_error;
}
trace_info_f("%s: demanded %d Hz sample rate, got %d Hz for %s, dir = %d\n",
__func__, (int)s->ss.rate, (int)rate, device_description, dir);
if (rate != s->ss.rate)
trace_error(
"%s: actual sample rate, %d Hz, differs from required %d Hz\n",
__func__, (int)rate, (int)s->ss.rate);
errcode = snd_pcm_hw_params_set_channels(s->ph, hw_params, s->ss.channels);
if (errcode != 0) {
trace_error(
"%s: can't set channel count to %d for %s. Error code %d (%s)\n",
__func__, (int)s->ss.channels, device_description, errcode,
snd_strerror(errcode));
// TODO: channel count handling?
goto fatal_error;
}
const size_t frame_size = pa_frame_size(&s->ss);
snd_pcm_uframes_t requested_period_size =
s->buffer_attr.minreq / frame_size;
snd_pcm_uframes_t period_size = requested_period_size;
dir = 1; // Prefer larger period sizes, if exact is not possible.
errcode = snd_pcm_hw_params_set_period_size_near(s->ph, hw_params,
&period_size, &dir);
if (errcode != 0) {
trace_error(
"%s: can't set period size to %d frames for %s. Error code %d "
"(%s)\n",
__func__, (int)requested_period_size, device_description, errcode,
snd_strerror(errcode));
goto fatal_error;
}
trace_info_f(
"%s: requested period size of %d frames, got %d frames for %s\n",
__func__, (int)requested_period_size, (int)period_size,
device_description);
// Set up buffer size. Ensure it's at least four times larger than a period
// size.
snd_pcm_uframes_t requested_buffer_size =
s->buffer_attr.tlength / frame_size;
snd_pcm_uframes_t buffer_size = MAX(requested_buffer_size, 4 * period_size);
errcode =
snd_pcm_hw_params_set_buffer_size_near(s->ph, hw_params, &buffer_size);
if (errcode != 0) {
trace_error(
"%s: can't set buffer size to %d frames for %s. Error code %d "
"(%s)\n",
__func__, (int)buffer_size, device_description, errcode,
snd_strerror(errcode));
goto fatal_error;
}
trace_info_f(
"%s: requested buffer size of %d frames, got %d frames for %s\n",
__func__, (int)requested_buffer_size, (int)buffer_size,
device_description);
errcode = snd_pcm_hw_params(s->ph, hw_params);
if (errcode != 0) {
trace_error("%s: can't apply configured hw parameter block for %s\n",
__func__, device_description);
goto fatal_error;
}
snd_pcm_hw_params_free(hw_params);
errcode = snd_pcm_sw_params_malloc(&sw_params);
if (errcode != 0) {
trace_error("%s: can't allocate memory for sw parameters for %s\n",
__func__, device_description);
goto fatal_error;
}
errcode = snd_pcm_sw_params_current(s->ph, sw_params);
if (errcode != 0) {
trace_error("%s: can't acquire current sw parameters for %s\n",
__func__, device_description);
goto fatal_error;
}
errcode = snd_pcm_sw_params_set_avail_min(s->ph, sw_params, period_size);
if (errcode != 0) {
trace_error("%s: can't set avail min for %s\n", __func__,
device_description);
goto fatal_error;
}
// no period event requested
errcode = snd_pcm_sw_params(s->ph, sw_params);
if (errcode != 0) {
trace_error("%s: can't apply sw parameters for %s\n", __func__,
device_description);
goto fatal_error;
}
snd_pcm_sw_params_free(sw_params);
errcode = snd_pcm_prepare(s->ph);
if (errcode != 0) {
trace_error("%s: can't prepare PCM device to use for %s\n", __func__,
device_description);
goto fatal_error;
}
int nfds = snd_pcm_poll_descriptors_count(s->ph);
struct pollfd *fds = calloc(nfds, sizeof(struct pollfd));
s->ioe = calloc(nfds, sizeof(pa_io_event *));
s->nioe = nfds;
snd_pcm_poll_descriptors(s->ph, fds, nfds);
for (int k = 0; k < nfds; k++) {
pa_mainloop_api *api = s->c->mainloop_api;
s->ioe[k] = api->io_new(api, fds[k].fd, 0x80000000 | fds[k].events,
data_available_for_stream, s);
s->ioe[k]->pcm = s->ph;
}
free(fds);
s->state = PA_STREAM_READY;
pa_stream_ref(s);
s->c->mainloop_api->defer_new(s->c->mainloop_api, deh_stream_state_changed,
s);
pa_stream_ref(s);
s->c->mainloop_api->defer_new(s->c->mainloop_api,
deh_stream_first_readwrite_callback, s);
g_free(device_description);
return 0;
fatal_error:
trace_error(
"%s: failed to open ALSA device. Apulse does no resampling or format "
"conversion, leaving that task to ALSA plugins. Ensure that selected "
"device is capable of playing a particular sample format at a "
"particular rate. They have to be supported by either hardware "
"directly, or by \"plug\" and \"dmix\" ALSA plugins which will perform "
"required conversions on CPU.\n",
__func__);
if (errcode == -EACCES) {
trace_error(
"%s: additionally, the error code is %d, which means access was "
"denied. That looks like access restriction in a sandbox. If the "
"app you are running uses sandboxing techniques, make sure "
"/dev/snd/ directory is added into the allowed list. Both reading "
"and writing access to the files in that directory are required.\n",
__func__, -EACCES);
}
g_free(device_description);
return -1;
}
APULSE_EXPORT
int
pa_stream_begin_write(pa_stream *p, void **data, size_t *nbytes)
{
trace_info_f("F %s p=%p nbytes=%p(%" PRIu64 ")\n", __func__, p, nbytes,
(uint64_t)(nbytes ? *nbytes : 0));
free(p->write_buffer);
if (*nbytes == (size_t)-1)
*nbytes = 8192;
*nbytes = pa_find_multiple_of(*nbytes, pa_frame_size(&p->ss), 0);
p->write_buffer = malloc(*nbytes);
if (!p->write_buffer)
return -1;
*data = p->write_buffer;
return 0;
}
APULSE_EXPORT
int
pa_stream_cancel_write(pa_stream *p)
{
trace_info_f("F %s p=%p\n", __func__, p);
free(p->write_buffer);
p->write_buffer = NULL;
return 0;
}
static void
stream_adjust_buffer_attrs(pa_stream *s, const pa_buffer_attr *attr)
{
pa_buffer_attr *ba = &s->buffer_attr;
const size_t frame_size = pa_frame_size(&s->ss);
if (attr) {
*ba = *attr;
} else {
// If client passed NULL, all parameters have default values.
ba->maxlength = (uint32_t)-1;
ba->tlength = (uint32_t)-1;
ba->prebuf = (uint32_t)-1;
ba->minreq = (uint32_t)-1;
ba->fragsize = (uint32_t)-1;
}
// Adjust default values.
// Overall buffer length.
if (ba->maxlength == (uint32_t)-1)
ba->maxlength = 4 * 1024 * 1024;
if (ba->maxlength == 0)
ba->maxlength = frame_size;
// Target length of a buffer.
if (ba->tlength == (uint32_t)-1)
ba->tlength = pa_usec_to_bytes(2 * 1000 * 1000, &s->ss);
if (ba->tlength == 0)
ba->tlength = frame_size;
ba->tlength = MIN(ba->tlength, ba->maxlength);
// Minimum request (playback).
if (ba->minreq == (uint32_t)-1) {
ba->minreq = pa_usec_to_bytes(20 * 1000, &s->ss);
ba->minreq = MIN(ba->minreq, ba->tlength / 4);
}
if (ba->minreq == 0)
ba->minreq = frame_size;
// Fragment size (recording).
if (ba->fragsize == (uint32_t)-1) {
ba->fragsize = pa_usec_to_bytes(20 * 1000, &s->ss);
}
if (ba->fragsize == 0)
ba->fragsize = frame_size;
// Pre-buffering.
if (ba->prebuf == (uint32_t)-1)
ba->prebuf = ba->tlength - ba->minreq;
if (ba->prebuf > ba->tlength - ba->minreq)
ba->prebuf = ba->tlength - ba->minreq;
// Ensure values are all multiple of |frame_size|.
ba->maxlength = pa_find_multiple_of(ba->maxlength, frame_size, 1);
ba->tlength = pa_find_multiple_of(ba->tlength, frame_size, 1);
ba->prebuf = pa_find_multiple_of(ba->prebuf, frame_size, 1);
ba->minreq = pa_find_multiple_of(ba->minreq, frame_size, 1);
ba->fragsize = pa_find_multiple_of(ba->fragsize, frame_size, 1);
}
APULSE_EXPORT
int
pa_stream_connect_playback(pa_stream *s, const char *dev,
const pa_buffer_attr *attr, pa_stream_flags_t flags,
const pa_cvolume *volume, pa_stream *sync_stream)
{
gchar *s_attr = trace_pa_buffer_attr_as_string(attr);
trace_info_f(
"P %s s=%p, dev=%s, attr=%s, flags=0x%x, volume=%p, sync_stream=%p\n",
__func__, s, dev, s_attr, flags, volume, sync_stream);
g_free(s_attr);
s->direction = PA_STREAM_PLAYBACK;
stream_adjust_buffer_attrs(s, attr);
if (do_connect_pcm(s, SND_PCM_STREAM_PLAYBACK) != 0)
goto err;
g_atomic_int_set(&s->paused, !!(flags & PA_STREAM_START_CORKED));
return 0;
err:
return -1;
}
static void
pa_stream_cork_impl(pa_operation *op)
{
g_atomic_int_set(&op->s->paused, !!(op->int_arg_1));
if (op->stream_success_cb)
op->stream_success_cb(op->s, 1, op->cb_userdata);
pa_operation_done(op);
}
APULSE_EXPORT
pa_operation *
pa_stream_cork(pa_stream *s, int b, pa_stream_success_cb_t cb, void *userdata)
{
trace_info_f("F %s s=%p, b=%d, cb=%p, userdata=%p\n", __func__, s, b, cb,
userdata);
pa_operation *op =
pa_operation_new(s->c->mainloop_api, pa_stream_cork_impl);
op->s = s;
op->int_arg_1 = b;
op->stream_success_cb = cb;
op->cb_userdata = userdata;
pa_operation_launch(op);
return op;
}
APULSE_EXPORT
int
pa_stream_disconnect(pa_stream *s)
{
trace_info_f("F %s s=%p\n", __func__, s);
if (s->state != PA_STREAM_READY)
return PA_ERR_BADSTATE;
for (int k = 0; k < s->nioe; k++) {
pa_mainloop_api *api = s->c->mainloop_api;
api->io_free(s->ioe[k]);
}
free(s->ioe);
snd_pcm_close(s->ph);
s->state = PA_STREAM_TERMINATED;
return PA_OK;
}
static void
pa_stream_drain_impl(pa_operation *op)
{
snd_pcm_drain(op->s->ph);
if (op->stream_success_cb)
op->stream_success_cb(op->s, 1, op->cb_userdata);
pa_operation_done(op);
}
APULSE_EXPORT
pa_operation *
pa_stream_drain(pa_stream *s, pa_stream_success_cb_t cb, void *userdata)
{
trace_info_f("F %s s=%p, cb=%p, userdata=%p\n", __func__, s, cb, userdata);
pa_operation *op =
pa_operation_new(s->c->mainloop_api, pa_stream_drain_impl);
op->s = s;
op->stream_success_cb = cb;
op->cb_userdata = userdata;
pa_operation_launch(op);
return op;
}
static void
pa_stream_flush_impl(pa_operation *op)
{
// TODO: is it ok to do nothing?
if (op->stream_success_cb)
op->stream_success_cb(op->s, 1, op->cb_userdata);
pa_operation_done(op);
}
APULSE_EXPORT
pa_operation *
pa_stream_flush(pa_stream *s, pa_stream_success_cb_t cb, void *userdata)
{
trace_info_f("F %s s=%p, cb=%p, userdata=%p\n", __func__, s, cb, userdata);
pa_operation *op =
pa_operation_new(s->c->mainloop_api, pa_stream_flush_impl);
op->s = s;
op->stream_success_cb = cb;
op->cb_userdata = userdata;
pa_operation_launch(op);
return op;
}
APULSE_EXPORT
uint32_t
pa_stream_get_index(pa_stream *s)
{
trace_info_f("F %s s=%p\n", __func__, s);
return s->idx;
}
APULSE_EXPORT
int
pa_stream_get_latency(pa_stream *s, pa_usec_t *r_usec, int *negative)
{
trace_info_f("F %s s=%p\n", __func__, s);
snd_pcm_sframes_t delay;
if (snd_pcm_delay(s->ph, &delay) != 0)
delay = 0;
if (r_usec)
*r_usec = 1000 * 1000 * delay / s->ss.rate;
if (negative)
*negative = 0;
return 0;
}
APULSE_EXPORT
const pa_sample_spec *
pa_stream_get_sample_spec(pa_stream *s)
{
trace_info_f("F %s s=%p\n", __func__, s);
return &s->ss;
}
APULSE_EXPORT
pa_stream_state_t
pa_stream_get_state(pa_stream *s)
{
trace_info_f("F %s s=%p\n", __func__, s);
return s->state;
}
APULSE_EXPORT
int
pa_stream_get_time(pa_stream *s, pa_usec_t *r_usec)
{
trace_info_f("F %s\n", __func__);
// TODO: handle playback/capture delays?
int64_t data_index = s->timing_info.write_index;
if (data_index < 0)
data_index = 0;
*r_usec = pa_bytes_to_usec(data_index, &s->ss);
return 0;
}
APULSE_EXPORT
const pa_timing_info *
pa_stream_get_timing_info(pa_stream *s)
{
trace_info_f("F %s s=%p\n", __func__, s);
snd_pcm_sframes_t delay;
if (snd_pcm_delay(s->ph, &delay) != 0)
delay = 0;
s->timing_info.read_index =
s->timing_info.write_index - delay * pa_frame_size(&s->ss);
return &s->timing_info;
}
APULSE_EXPORT
int
pa_stream_is_corked(pa_stream *s)
{
trace_info_f("F %s s=%p\n", __func__, s);
return g_atomic_int_get(&s->paused);
}
APULSE_EXPORT
int
pa_stream_is_suspended(pa_stream *s)
{
trace_info_f("F %s s=%p\n", __func__, s);
// ALSA sink is never suspended
return 0;
}
APULSE_EXPORT
pa_stream *
pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *ss,
const pa_channel_map *map)
{
gchar *s_map = trace_pa_channel_map_as_string(map);
gchar *s_ss = trace_pa_sample_spec_as_string(ss);
trace_info_f("F %s c=%p, name=%s, ss=%s, map=%s\n", __func__, c, name, s_ss,
s_map);
g_free(s_ss);
g_free(s_map);
pa_proplist *p = pa_proplist_new();
pa_stream *s = pa_stream_new_with_proplist(c, name, ss, map, p);
pa_proplist_free(p);
return s;
}
APULSE_EXPORT
pa_stream *
pa_stream_new_extended(pa_context *c, const char *name,
pa_format_info *const *formats, unsigned int n_formats,
pa_proplist *p)
{
trace_info_f("P %s c=%p, name=%s, formats=%p, n_formats=%u, p=%p\n",
__func__, c, name, formats, n_formats, p);
// TODO: multiple formats?
// take first format
if (n_formats < 1) {
trace_error("%s, no formats\n", __func__);
return NULL;
}
pa_sample_spec ss = {
.format = PA_SAMPLE_S16LE, .rate = 48000, .channels = 2,
};
const char *val;
val = pa_proplist_gets(formats[0]->plist, PA_PROP_FORMAT_SAMPLE_FORMAT);
if (val)
ss.format = pa_sample_format_from_string(val);
val = pa_proplist_gets(formats[0]->plist, PA_PROP_FORMAT_RATE);
if (val)
ss.rate = atoi(val);
val = pa_proplist_gets(formats[0]->plist, PA_PROP_FORMAT_CHANNELS);
if (val)
ss.channels = atoi(val);
return pa_stream_new_with_proplist(c, name, &ss, NULL, p);
}
APULSE_EXPORT
pa_stream *
pa_stream_new_with_proplist(pa_context *c, const char *name,
const pa_sample_spec *ss, const pa_channel_map *map,
pa_proplist *p)
{
gchar *s_map = trace_pa_channel_map_as_string(map);
gchar *s_ss = trace_pa_sample_spec_as_string(ss);
trace_info_f("F %s c=%p, name=%s, ss=%s, map=%s, p=%p\n", __func__, c, name,
s_ss, s_map, p);
g_free(s_ss);
g_free(s_map);
pa_stream *s = calloc(1, sizeof(pa_stream));
s->c = c;
s->ref_cnt = 1;
s->state = PA_STREAM_UNCONNECTED;
s->ss = *ss;
s->idx = c->next_stream_idx++;
g_hash_table_insert(c->streams_ht, GINT_TO_POINTER(s->idx), s);
// fill initial values of s->timing_info
gettimeofday(&s->timing_info.timestamp, NULL);
s->timing_info.synchronized_clocks = 1;
s->timing_info.sink_usec = 0;
s->timing_info.source_usec = 0;
s->timing_info.transport_usec = 0;
s->timing_info.playing = 1;
s->timing_info.write_index_corrupt = 0;
s->timing_info.write_index = 0;
s->timing_info.read_index_corrupt = 0;
s->timing_info.read_index = 0;
s->timing_info.configured_sink_usec = 0;
s->timing_info.configured_source_usec = 0;
s->timing_info.since_underrun = 0;
s->rb = ringbuffer_new(72 * 1024); // TODO: figure out size
s->peek_buffer = malloc(s->rb->end - s->rb->start);
for (uint32_t k = 0; k < PA_CHANNELS_MAX; k++)
s->volume[k] = PA_VOLUME_NORM;
return s;
}
APULSE_EXPORT
pa_stream *
pa_stream_ref(pa_stream *s)
{
trace_info_f("F %s s=%p\n", __func__, s);
s->ref_cnt++;
return s;
}
APULSE_EXPORT
void
pa_stream_set_latency_update_callback(pa_stream *s, pa_stream_notify_cb_t cb,
void *userdata)
{
trace_info_f("F %s s=%p, cb=%p, userdata=%p\n", __func__, s, cb, userdata);
s->latency_update_cb = cb;
s->latency_update_cb_userdata = userdata;
}
static void
pa_stream_set_name_impl(pa_operation *op)
{
free(op->s->name);
op->s->name = op->char_ptr_arg_1;
if (op->stream_success_cb)
op->stream_success_cb(op->s, 1, op->cb_userdata);
pa_operation_done(op);
}
APULSE_EXPORT
pa_operation *
pa_stream_set_name(pa_stream *s, const char *name, pa_stream_success_cb_t cb,
void *userdata)
{
trace_info_f("P %s s=%p, name=%s, cb=%p, userdata=%p\n", __func__, s, name,
cb, userdata);
pa_operation *op =
pa_operation_new(s->c->mainloop_api, pa_stream_set_name_impl);
op->s = s;
op->stream_success_cb = cb;
op->cb_userdata = userdata;
op->char_ptr_arg_1 = strdup(name ? name : "");
pa_operation_launch(op);
return op;
}
APULSE_EXPORT
void
pa_stream_set_state_callback(pa_stream *s, pa_stream_notify_cb_t cb,
void *userdata)
{
trace_info_f("F %s s=%p, cb=%p, userdata=%p\n", __func__, s, cb, userdata);
s->state_cb = cb;
s->state_cb_userdata = userdata;
}
APULSE_EXPORT
void
pa_stream_set_write_callback(pa_stream *s, pa_stream_request_cb_t cb,
void *userdata)
{
trace_info_f("F %s s=%p, cb=%p, userdata=%p\n", __func__, s, cb, userdata);
s->write_cb = cb;
s->write_cb_userdata = userdata;
}
static void
pa_stream_trigger_impl(pa_operation *op)
{
// TODO: does nothing?
if (op->stream_success_cb)
op->stream_success_cb(op->s, 1, op->cb_userdata);
pa_operation_done(op);
}
APULSE_EXPORT
pa_operation *
pa_stream_trigger(pa_stream *s, pa_stream_success_cb_t cb, void *userdata)
{
trace_info_f("F %s s=%p, cb=%p, userdata=%p\n", __func__, s, cb, userdata);
pa_operation *op =
pa_operation_new(s->c->mainloop_api, pa_stream_trigger_impl);
op->s = s;
op->stream_success_cb = cb;
op->cb_userdata = userdata;
pa_operation_launch(op);
return op;
}
APULSE_EXPORT
void
pa_stream_unref(pa_stream *s)
{
trace_info_f("F %s s=%p\n", __func__, s);
s->ref_cnt--;
if (s->ref_cnt == 0) {
g_hash_table_remove(s->c->streams_ht, GINT_TO_POINTER(s->idx));
ringbuffer_free(s->rb);
free(s->peek_buffer);
free(s->write_buffer);
free(s->name);
free(s);
}
}
static void
pa_stream_update_timing_info_impl(pa_operation *op)
{
gettimeofday(&op->s->timing_info.timestamp, NULL);
if (op->s->latency_update_cb)
op->s->latency_update_cb(op->s, op->s->latency_update_cb_userdata);
if (op->stream_success_cb)
op->stream_success_cb(op->s, 1, op->cb_userdata);
pa_operation_done(op);
}
APULSE_EXPORT
pa_operation *
pa_stream_update_timing_info(pa_stream *s, pa_stream_success_cb_t cb,
void *userdata)
{
trace_info_f("F %s s=%p, cb=%p, userdata=%p\n", __func__, s, cb, userdata);
pa_operation *op =
pa_operation_new(s->c->mainloop_api, pa_stream_update_timing_info_impl);
op->s = s;
op->stream_success_cb = cb;
op->cb_userdata = userdata;
pa_operation_launch(op);
return op;
}
APULSE_EXPORT
size_t
pa_stream_writable_size(pa_stream *s)
{
trace_info_f("F %s s=%p\n", __func__, s);
size_t writable_size = ringbuffer_writable_size(s->rb);
// Some applications try to push more data than reported to be available
// by pa_stream_writable_size(), which is fine for original PulseAudio
// but is a severe error in this implementation, since buffer size is
// limited.
//
// Workaround issue by reserving certain amount for that case.
const size_t limit = 16 * 1024; // TODO: adaptive values?
if (writable_size < limit)
writable_size = 0;
return pa_find_multiple_of(writable_size, pa_frame_size(&s->ss), 0);
}
APULSE_EXPORT
size_t
pa_stream_readable_size(pa_stream *s)
{
trace_info_f("F %s s=%p\n", __func__, s);
size_t readable_size = ringbuffer_readable_size(s->rb);
return pa_find_multiple_of(readable_size, pa_frame_size(&s->ss), 0);
}
APULSE_EXPORT
int
pa_stream_write(pa_stream *s, const void *data, size_t nbytes,
pa_free_cb_t free_cb, int64_t offset, pa_seek_mode_t seek)
{
trace_info_f("F %s s=%p, data=%p, nbytes=%zu, free_cb=%p, offset=%" PRId64
", seek=%u\n",
__func__, s, data, nbytes, free_cb, offset, seek);
if (offset != 0)
trace_error("%s, offset != 0\n", __func__);
if (seek != PA_SEEK_RELATIVE)
trace_error("%s, seek != PA_SEEK_RELATIVE\n", __func__);
size_t written = ringbuffer_write(s->rb, data, nbytes);
s->timing_info.since_underrun += written;
s->timing_info.write_index += written;
if (data == s->write_buffer) {
free(s->write_buffer);
s->write_buffer = NULL;
} else {
if (free_cb)
free_cb((void *)data);
}
return 0;
}
APULSE_EXPORT
int
pa_stream_connect_record(pa_stream *s, const char *dev,
const pa_buffer_attr *attr, pa_stream_flags_t flags)
{
gchar *s_attr = trace_pa_buffer_attr_as_string(attr);
trace_info_f("P %s s=%p, dev=%s, attr=%s, flags=0x%x\n", __func__, s, dev,
s_attr, flags);
g_free(s_attr);
s->direction = PA_STREAM_RECORD;
stream_adjust_buffer_attrs(s, attr);
if (do_connect_pcm(s, SND_PCM_STREAM_CAPTURE) != 0)
goto err;
snd_pcm_start(s->ph);
return 0;
err:
return -1;
}
APULSE_EXPORT
int
pa_stream_drop(pa_stream *s)
{
trace_info_f("F %s s=%p\n", __func__, s);
ringbuffer_drop(s->rb, s->peek_buffer_data_len);
return 0;
}
APULSE_EXPORT
const pa_buffer_attr *
pa_stream_get_buffer_attr(pa_stream *s)
{
trace_info_f("F %s\n", __func__);
return &s->buffer_attr;
}
APULSE_EXPORT
uint32_t
pa_stream_get_device_index(pa_stream *s)
{
trace_info_f("F %s s=%p\n", __func__, s);
// apulse uses only one sink -- ALSA device, so index is always 0
return 0;
}
APULSE_EXPORT
const char *
pa_stream_get_device_name(pa_stream *s)
{
trace_info_f("F %s s=%p\n", __func__, s);
return "apulse";
}
APULSE_EXPORT
int
pa_stream_peek(pa_stream *s, const void **data, size_t *nbytes)
{
trace_info_f("F %s s=%p\n", __func__, s);
if (!s)
return -1;
size_t len = ringbuffer_readable_size(s->rb);
s->peek_buffer_data_len = ringbuffer_peek(s->rb, s->peek_buffer, len);
if (nbytes)
*nbytes = s->peek_buffer_data_len;
if (data)
*data = s->peek_buffer;
return 0;
}
APULSE_EXPORT
void
pa_stream_set_read_callback(pa_stream *s, pa_stream_request_cb_t cb,
void *userdata)
{
trace_info_f("F %s s=%p, cb=%p, userdata=%p\n", __func__, s, cb, userdata);
if (s) {
s->read_cb = cb;
s->read_cb_userdata = userdata;
}
}
APULSE_EXPORT
void
pa_stream_set_underflow_callback(pa_stream *p, pa_stream_notify_cb_t cb,
void *userdata)
{
trace_info_z("Z %s\n", __func__);
}
APULSE_EXPORT
pa_context *
pa_stream_get_context(pa_stream *p)
{
trace_info_z("Z %s\n", __func__);
return NULL;
}
APULSE_EXPORT
void
pa_stream_set_overflow_callback(pa_stream *p, pa_stream_notify_cb_t cb,
void *userdata)
{
trace_info_z("Z %s\n", __func__);
}
APULSE_EXPORT
int64_t
pa_stream_get_underflow_index(pa_stream *p)
{
trace_info_z("Z %s\n", __func__);
return 0;
}
APULSE_EXPORT
void
pa_stream_set_started_callback(pa_stream *p, pa_stream_notify_cb_t cb,
void *userdata)
{
trace_info_z("Z %s\n", __func__);
}
APULSE_EXPORT
void
pa_stream_set_moved_callback(pa_stream *p, pa_stream_notify_cb_t cb,
void *userdata)
{
trace_info_z("Z %s\n", __func__);
}
APULSE_EXPORT
void
pa_stream_set_suspended_callback(pa_stream *p, pa_stream_notify_cb_t cb,
void *userdata)
{
trace_info_z("Z %s\n", __func__);
}
APULSE_EXPORT
void
pa_stream_set_event_callback(pa_stream *p, pa_stream_event_cb_t cb,
void *userdata)
{
trace_info_z("Z %s\n", __func__);
}
APULSE_EXPORT
void
pa_stream_set_buffer_attr_callback(pa_stream *p, pa_stream_notify_cb_t cb,
void *userdata)
{
trace_info_z("Z %s\n", __func__);
}
APULSE_EXPORT
pa_operation *
pa_stream_prebuf(pa_stream *s, pa_stream_success_cb_t cb, void *userdata)
{
trace_info_z("Z %s\n", __func__);
return NULL;
}
APULSE_EXPORT
const pa_channel_map *
pa_stream_get_channel_map(pa_stream *s)
{
trace_info_z("Z %s\n", __func__);
return NULL;
}
APULSE_EXPORT
const pa_format_info *
pa_stream_get_format_info(pa_stream *s)
{
trace_info_z("Z %s\n", __func__);
return NULL;
}
APULSE_EXPORT
pa_operation *
pa_stream_set_buffer_attr(pa_stream *s, const pa_buffer_attr *attr,
pa_stream_success_cb_t cb, void *userdata)
{
trace_info_z("Z %s\n", __func__);
return NULL;
}
APULSE_EXPORT
pa_operation *
pa_stream_update_sample_rate(pa_stream *s, uint32_t rate,
pa_stream_success_cb_t cb, void *userdata)
{
trace_info_z("Z %s\n", __func__);
return NULL;
}
APULSE_EXPORT
pa_operation *
pa_stream_proplist_update(pa_stream *s, pa_update_mode_t mode, pa_proplist *p,
pa_stream_success_cb_t cb, void *userdata)
{
trace_info_z("Z %s\n", __func__);
return NULL;
}
APULSE_EXPORT
pa_operation *
pa_stream_proplist_remove(pa_stream *s, const char *const keys[],
pa_stream_success_cb_t cb, void *userdata)
{
trace_info_z("Z %s\n", __func__);
return NULL;
}
APULSE_EXPORT
int
pa_stream_set_monitor_stream(pa_stream *s, uint32_t sink_input_idx)
{
trace_info_z("Z %s\n", __func__);
return 0;
}
APULSE_EXPORT
uint32_t
pa_stream_get_monitor_stream(pa_stream *s)
{
trace_info_z("Z %s\n", __func__);
return 0;
}
APULSE_EXPORT
int
pa_stream_connect_upload(pa_stream *s, size_t length)
{
trace_info_z("Z %s\n", __func__);
return 0;
}
APULSE_EXPORT
int
pa_stream_finish_upload(pa_stream *s)
{
trace_info_z("Z %s\n", __func__);
return 0;
}
You can’t perform that action at this time.