[pulseaudio-discuss] [PATCH] "pipe-sink" added option "use_system_clock_for_timing"
Samo Pogačnik
samo_pogacnik at t-2.net
Wed Jan 3 19:34:58 UTC 2018
Below is a new patch.
Dne 29.12.2017 (pet) ob 15:40 +0200 je Tanu Kaskinen napisal(a):
> On Sat, 2017-12-16 at 16:57 +0100, Samo Pogačnik wrote:
> >
> > +static ssize_t pipe_sink_write(struct userdata *u, pa_memchunk
> > *pchunk) {
> > + size_t index, length;
> > + ssize_t count = 0;
> > + void *p;
> > +
> > + pa_assert(u);
> > + pa_assert(pchunk);
> > +
> > + index = pchunk->index;
> > + length = pchunk->length;
> > + p = pa_memblock_acquire(pchunk->memblock);
> > +
> > + for (;;) {
> > + ssize_t l;
> > +
> > + l = pa_write(u->fd, (uint8_t*) p + index, length, &u-
> > >write_type);
> > +
> > + pa_assert(l != 0);
> > +
> > + if (l < 0) {
> > + if (errno == EAGAIN)
> > + break;
> > + else if (errno != EINTR) {
> > + if (!u->fifo_error) {
> > + pa_log("Failed to write data to FIFO: %s",
> > pa_cstrerror(errno));
> > + u->fifo_error = true;
> > + }
> > + count = l - count;
> process_render_use_timing() assumes that l is -1, so you should
> replace
> l with -1 here.
>
Done.
> >
> > + break;
> > + }
> > + } else {
> > + count += l;
> > + index += l;
> > + length -= l;
> > +
> > + if (length <= 0) {
> > + break;
> > + }
> > + }
> > + }
> > +
> > + pa_memblock_release(pchunk->memblock);
> > +
> > + if (u->fifo_error && count >= 0) {
> > + pa_log("Recovered from FIFO error");
> > + u->fifo_error = false;
> > + }
> This logic is not right. If in the first loop iteration pa_write()
> does
> a partial write and then an error occurs during the second iteration,
> the code incorrectly thinks that we have recovered from the error.
> The
> recovery happens when pa_write() returns a positive number for the
> first time.
>
Corrected.
> Also, use pa_log_debug() or pa_log_info(). pa_log() is an alias for
> pa_log_error(), and this is not an error message.
>
Done.
> >
> > + return count;
> > +}
> > +
> > +static int process_render_use_timing(struct userdata *u, pa_usec_t
> > now) {
> The function return type can be changed to void.
>
Done.
regards and happy new year,
Samo
[PATCH] New pipe sink option "use_system_clock_for_timing"
Using this option, even the simplest tools like "cat" can properly
dump raw audio from the pipe.
---
src/modules/module-pipe-sink.c | 233 ++++++++++++++++++++++++++++++++++++++---
1 file changed, 221 insertions(+), 12 deletions(-)
diff --git a/src/modules/module-pipe-sink.c b/src/modules/module-pipe-sink.c
index 64ef807..fe2af70 100644
--- a/src/modules/module-pipe-sink.c
+++ b/src/modules/module-pipe-sink.c
@@ -34,6 +34,9 @@
#endif
#include <pulse/xmalloc.h>
+#include <pulse/timeval.h>
+#include <pulse/util.h>
+#include <pulse/rtclock.h>
#include <pulsecore/core-error.h>
#include <pulsecore/sink.h>
@@ -57,7 +60,9 @@ PA_MODULE_USAGE(
"format=<sample format> "
"rate=<sample rate> "
"channels=<number of channels> "
- "channel_map=<channel map>");
+ "channel_map=<channel map> "
+ "use_system_clock_for_timing=<yes or no> "
+);
#define DEFAULT_FILE_NAME "fifo_output"
#define DEFAULT_SINK_NAME "fifo_output"
@@ -74,12 +79,18 @@ struct userdata {
char *filename;
int fd;
size_t buffer_size;
+ size_t bytes_dropped;
+ bool fifo_error;
pa_memchunk memchunk;
pa_rtpoll_item *rtpoll_item;
int write_type;
+ pa_usec_t block_usec;
+ pa_usec_t timestamp;
+
+ bool use_system_clock_for_timing;
};
static const char* const valid_modargs[] = {
@@ -90,6 +101,7 @@ static const char* const valid_modargs[] = {
"rate",
"channels",
"channel_map",
+ "use_system_clock_for_timing",
NULL
};
@@ -97,27 +109,156 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
struct userdata *u = PA_SINK(o)->userdata;
switch (code) {
+ case PA_SINK_MESSAGE_SET_STATE:
+ if (pa_sink_get_state(u->sink) == PA_SINK_SUSPENDED || pa_sink_get_state(u->sink) == PA_SINK_INIT) {
+ if (PA_PTR_TO_UINT(data) == PA_SINK_RUNNING || PA_PTR_TO_UINT(data) == PA_SINK_IDLE)
+ u->timestamp = pa_rtclock_now();
+ } else if (pa_sink_get_state(u->sink) == PA_SINK_RUNNING || pa_sink_get_state(u->sink) == PA_SINK_IDLE) {
+ if (PA_PTR_TO_UINT(data) == PA_SINK_SUSPENDED) {
+ /* Clear potential FIFO error flag */
+ u->fifo_error = false;
+
+ /* Continuously dropping data (clear counter on entering suspended state. */
+ if (u->bytes_dropped != 0) {
+ pa_log_debug("Pipe-sink continuously dropping data - clear statistics (%zu -> 0 bytes dropped)", u->bytes_dropped);
+ u->bytes_dropped = 0;
+ }
+ }
+ }
+ break;
- case PA_SINK_MESSAGE_GET_LATENCY: {
- size_t n = 0;
+ case PA_SINK_MESSAGE_GET_LATENCY:
+ if (u->use_system_clock_for_timing) {
+ pa_usec_t now;
+ now = pa_rtclock_now();
+ *((int64_t*) data) = (int64_t)u->timestamp - (int64_t)now;
+ } else {
+ size_t n = 0;
#ifdef FIONREAD
- int l;
+ int l;
- if (ioctl(u->fd, FIONREAD, &l) >= 0 && l > 0)
- n = (size_t) l;
+ if (ioctl(u->fd, FIONREAD, &l) >= 0 && l > 0)
+ n = (size_t) l;
#endif
- n += u->memchunk.length;
+ n += u->memchunk.length;
- *((int64_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec);
+ *((int64_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec);
+ }
return 0;
- }
}
return pa_sink_process_msg(o, code, data, offset, chunk);
}
+static void sink_update_requested_latency_cb(pa_sink *s) {
+ struct userdata *u;
+ size_t nbytes;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ u->block_usec = pa_sink_get_requested_latency_within_thread(s);
+
+ if (u->block_usec == (pa_usec_t) -1)
+ u->block_usec = s->thread_info.max_latency;
+
+ nbytes = pa_usec_to_bytes(u->block_usec, &s->sample_spec);
+ pa_sink_set_max_request_within_thread(s, nbytes);
+}
+
+static ssize_t pipe_sink_write(struct userdata *u, pa_memchunk *pchunk) {
+ size_t index, length;
+ ssize_t count = 0;
+ void *p;
+
+ pa_assert(u);
+ pa_assert(pchunk);
+
+ index = pchunk->index;
+ length = pchunk->length;
+ p = pa_memblock_acquire(pchunk->memblock);
+
+ for (;;) {
+ ssize_t l;
+
+ l = pa_write(u->fd, (uint8_t*) p + index, length, &u->write_type);
+
+ pa_assert(l != 0);
+
+ if (l < 0) {
+ if (errno == EAGAIN)
+ break;
+ else if (errno != EINTR) {
+ if (!u->fifo_error) {
+ pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));
+ u->fifo_error = true;
+ }
+ count = -1 - count;
+ break;
+ }
+ } else {
+ if (u->fifo_error) {
+ pa_log_debug("Recovered from FIFO error");
+ u->fifo_error = false;
+ }
+ count += l;
+ index += l;
+ length -= l;
+
+ if (length <= 0) {
+ break;
+ }
+ }
+ }
+
+ pa_memblock_release(pchunk->memblock);
+
+ return count;
+}
+
+static void process_render_use_timing(struct userdata *u, pa_usec_t now) {
+ size_t dropped = 0;
+ size_t consumed = 0;
+
+ pa_assert(u);
+
+ /* Fill the buffer up the latency size */
+ while (u->timestamp < now + u->block_usec) {
+ ssize_t written = 0;
+ pa_memchunk chunk;
+
+ pa_sink_render(u->sink, u->sink->thread_info.max_request, &chunk);
+
+ pa_assert(chunk.length > 0);
+
+ if ((written = pipe_sink_write(u, &chunk)) < 0)
+ written = -1 - written;
+
+ pa_memblock_unref(chunk.memblock);
+
+ u->timestamp += pa_bytes_to_usec(chunk.length, &u->sink->sample_spec);
+
+ dropped = chunk.length - written;
+
+ if (u->bytes_dropped != 0 && dropped != chunk.length) {
+ pa_log_debug("Pipe-sink continuously dropped %zu bytes", u->bytes_dropped);
+ u->bytes_dropped = 0;
+ }
+
+ if (u->bytes_dropped == 0 && dropped != 0)
+ pa_log_debug("Pipe-sink just dropped %zu bytes", dropped);
+
+ u->bytes_dropped += dropped;
+
+ consumed += chunk.length;
+
+ if (consumed >= u->sink->thread_info.max_request)
+ break;
+ }
+}
+
static int process_render(struct userdata *u) {
pa_assert(u);
@@ -162,6 +303,54 @@ static int process_render(struct userdata *u) {
}
}
+static void thread_func_use_timing(void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(u);
+
+ pa_log_debug("Thread (use timing) starting up");
+
+ pa_thread_mq_install(&u->thread_mq);
+
+ u->timestamp = pa_rtclock_now();
+
+ for (;;) {
+ pa_usec_t now = 0;
+ int ret;
+
+ if (PA_SINK_IS_OPENED(u->sink->thread_info.state))
+ now = pa_rtclock_now();
+
+ if (PA_UNLIKELY(u->sink->thread_info.rewind_requested))
+ pa_sink_process_rewind(u->sink, 0);
+
+ /* Render some data and write it to the fifo */
+ if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
+ if (u->timestamp <= now)
+ process_render_use_timing(u, now);
+
+ pa_rtpoll_set_timer_absolute(u->rtpoll, u->timestamp);
+ } else
+ pa_rtpoll_set_timer_disabled(u->rtpoll);
+
+ /* Hmm, nothing to do. Let's sleep */
+ if ((ret = pa_rtpoll_run(u->rtpoll)) < 0)
+ goto fail;
+
+ if (ret == 0)
+ goto finish;
+ }
+
+fail:
+ /* If this was no regular exit from the loop we have to continue
+ * processing messages until we received PA_MESSAGE_SHUTDOWN */
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+finish:
+ pa_log_debug("Thread (use timing) shutting down");
+}
+
static void thread_func(void *userdata) {
struct userdata *u = userdata;
@@ -225,6 +414,7 @@ int pa__init(pa_module *m) {
pa_modargs *ma;
struct pollfd *pollfd;
pa_sink_new_data data;
+ pa_thread_func_t thread_routine;
pa_assert(m);
@@ -247,6 +437,11 @@ int pa__init(pa_module *m) {
pa_memchunk_reset(&u->memchunk);
u->rtpoll = pa_rtpoll_new();
+ if (pa_modargs_get_value_boolean(ma, "use_system_clock_for_timing", &u->use_system_clock_for_timing) < 0) {
+ pa_log("Failed to parse use_system_clock_for_timing argument.");
+ goto fail;
+ }
+
if (pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll) < 0) {
pa_log("pa_thread_mq_init() failed.");
goto fail;
@@ -292,7 +487,10 @@ int pa__init(pa_module *m) {
goto fail;
}
- u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY);
+ if (u->use_system_clock_for_timing)
+ u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY);
+ else
+ u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY);
pa_sink_new_data_done(&data);
if (!u->sink) {
@@ -301,21 +499,32 @@ int pa__init(pa_module *m) {
}
u->sink->parent.process_msg = sink_process_msg;
+ if (u->use_system_clock_for_timing)
+ u->sink->update_requested_latency = sink_update_requested_latency_cb;
u->sink->userdata = u;
pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
pa_sink_set_rtpoll(u->sink, u->rtpoll);
+ u->bytes_dropped = 0;
+ u->fifo_error = false;
u->buffer_size = pa_frame_align(pa_pipe_buf(u->fd), &u->sink->sample_spec);
+ if (u->use_system_clock_for_timing) {
+ u->block_usec = pa_bytes_to_usec(u->buffer_size, &u->sink->sample_spec);
+ pa_sink_set_latency_range(u->sink, 0, u->block_usec);
+ thread_routine = thread_func_use_timing;
+ } else {
+ pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->buffer_size, &u->sink->sample_spec));
+ thread_routine = thread_func;
+ }
pa_sink_set_max_request(u->sink, u->buffer_size);
- pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->buffer_size, &u->sink->sample_spec));
u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
pollfd->fd = u->fd;
pollfd->events = pollfd->revents = 0;
- if (!(u->thread = pa_thread_new("pipe-sink", thread_func, u))) {
+ if (!(u->thread = pa_thread_new("pipe-sink", thread_routine, u))) {
pa_log("Failed to create thread.");
goto fail;
}
--
2.7.4
More information about the pulseaudio-discuss
mailing list