I use tvtime (an open source TV application) to watch TV on my Fedora 16
system.  As some of you may know forwarding audio from the sound card
built into the TV tuner card to the primary sound card is not done by
tvtime.  There are various ways of forwarding the audio including SoX,
module-loopback, etc. but I've had the best luck (least latency) using
pacat in a manner similar to what this webpage describes:

http://thelinuxexperiment.com/guinea-pigs/tyler-b/fix-pulseaudio-loopback-delay/

But with a change that I've made to pacat that I'd like to share in case
it's helpful.

The web page above describes using two instances of pacat with a pipe:
  pacat -r ... | pacat -p ...
to forward the audio.  I've found that this works quite well for a
while, but gradually the latency increases.  If for some reason the
pacat processes are temporarily suspended and then resumed (ctrl-Z
followed by bg, for example) the latency increases drastically.

I've found that the increased latency is due to audio queuing up on the
input to the play instance of pacat (the pacat on the right side of the
"|" shown above).  The extra queued up audio never gets consumed because
audio is being produced by the record instance of pacat (the pacat on
the left side of the "|" shown above) at the same rate it is consumed by
the play instance of pacat.  So I experimented with ways of discarding
the queued up audio that did not interfere with the sound quality too
much.

Each time stdin_callback() is called by the play instance of pacat to
get audio from the input pa_stream_writable_size() is called to see how
many bytes the sink can take.  This limits the amount of data that can
be read from stdin.

Each read of stdin is either full (precisely the number of bytes
requested is returned) or partial (less than the number of bytes
requested is returned).  I've found the if a large number of full reads
occur in a row that it's likely there latency due to extra audio queued
up in the input.  When this happens I've modified pacat to read as much
as 64 KiB more than what pa_stream_writable_size() requested, but then
discard the extra data read.

I suspect that there may be a more elegant solution at a different layer
in the code.  At the very least the change should be made optional so
that it does not break pacat when playing a file instead of reading from
a pipe:
  pacapt -p ... < audio.raw
But if there is interest I can add a command line option so that the
change is not always in effect.

-- 
------------------------------------------------------------------------
|  Steven Elliott  |  http://selliott.org  |  sellio...@austin.rr.com  |
------------------------------------------------------------------------
--- src/utils/pacat.c.orig	2012-03-24 10:13:10.382540053 -0500
+++ src/utils/pacat.c	2012-03-24 22:51:41.350164538 -0500
@@ -51,6 +51,10 @@
 
 #define CLEAR_LINE "\x1B[K"
 
+#define DRAIN_THRESHOLD 100
+
+#define DRAIN_BYTES 65536
+
 static enum { RECORD, PLAYBACK } mode = PLAYBACK;
 
 static pa_context *context = NULL;
@@ -501,8 +505,9 @@
 
 /* New data on STDIN **/
 static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) {
-    size_t l, w = 0;
+    size_t l, l_extra, w = 0;
     ssize_t r;
+    static int full_reads; // Consecutive full reads (all bytes read).
 
     pa_assert(a == mainloop_api);
     pa_assert(e);
@@ -516,9 +521,10 @@
     if (!stream || pa_stream_get_state(stream) != PA_STREAM_READY || !(l = w = pa_stream_writable_size(stream)))
         l = 4096;
 
-    buffer = pa_xmalloc(l);
+    l_extra = (full_reads > DRAIN_THRESHOLD) ? (l + DRAIN_BYTES) : l;
+    buffer = pa_xmalloc(l_extra);
 
-    if ((r = read(fd, buffer, l)) <= 0) {
+    if ((r = read(fd, buffer, l_extra)) <= 0) {
         if (r == 0) {
             if (verbose)
                 pa_log(_("Got EOF."));
@@ -535,8 +541,27 @@
         return;
     }
 
-    buffer_length = (uint32_t) r;
-    buffer_index = 0;
+    if ((size_t) r == l_extra) {
+        full_reads++;
+    }
+    else {
+        full_reads = 0;
+    }
+
+    if ((size_t) r > l) {
+        // The read exceeds the writeable size.  Find the end of the buffer
+        // that is aligned on a 4 byte boundary.
+        // TODO: Determine the alignment bytes rather than assuming 4.
+        buffer_index = (~3) & (r - l);
+        buffer_length = (size_t) r - buffer_index;
+        if (buffer_length > l)
+        {
+            buffer_length = l;
+        }
+    } else {
+        buffer_length = (uint32_t) r;
+        buffer_index = 0;
+    }
 
     if (w)
         do_stream_write(w);
_______________________________________________
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss

Reply via email to