On Mon, 2012-03-26 at 23:54 -0500, Steven Elliott wrote:
> 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.

Since I've posted the above I've refined my patch a bit, which I've
attached, but I've also explored an additional cause of latency in the
PulseAudio server.  I've found that, if for some reason, there
is an underrun the watermark or latency is increased via
increase_watermark() in alsa-source.c and alsa-sink.c.  That probably
makes sense in a lot of cases, but in the case of watching TV via tvtime
I'd rather suffer a fleeting glitch due to underrun than a longer
lasting A/V sync issue.

I don't think the attached
"pulseaudio-limited-watermark-experimental.diff" patch is ideal which
why I marked it "experimental".  Consequently I'm curious if there is
potential for it to evolve into a submitable patch.  Should the 50 msec
limit that I've imposed in fix_tsched_watermark() be configurable?
Would it be preferable to decrease the watermark more aggressively in
decrease_watermark()?

-- 
------------------------------------------------------------------------
|  Steven Elliott  |  http://selliott.org  |  [email protected]  |
------------------------------------------------------------------------
This experimental patch adds a "--drain" option to pacat that causes pacat
to drain extra data from stdin.  This is helpful when pacat is used to forward
audio from one audio card to another with minimal latency.

--- man/pacat.1.xml.in.orig	2011-03-20 08:28:04.299902459 -0500
+++ man/pacat.1.xml.in	2012-04-06 15:12:55.642080385 -0500
@@ -212,6 +212,14 @@
     </option>
 
     <option>
+      <p><opt>--drain</opt></p>
+      <optdesc><p>Continually drain stdin.  This is used for forwarding
+      audio from one device to another where latency is to be minimized
+      by discarding extra audio from stdin when this program is invoked
+      as <cmd>pacat</cmd>.</p></optdesc>
+    </option>
+
+    <option>
       <p><opt>--property</opt><arg>=PROPERTY=VALUE</arg></p>
       <optdesc><p>Attach a property to the client and stream. May be
       used multiple times</p></optdesc>
--- src/utils/pacat.c.orig	2011-06-23 15:18:54.497124797 -0500
+++ src/utils/pacat.c	2012-04-06 15:25:53.478846580 -0500
@@ -51,6 +51,12 @@
 
 #define CLEAR_LINE "\x1B[K"
 
+/* It's not obvious what to set this to.  Setting it has high as 100 causes
+   bit of a delay before draining, but makes the adjustments less frequent. */
+#define DRAIN_THRESHOLD 5
+
+#define DRAIN_BYTES 65536
+
 static enum { RECORD, PLAYBACK } mode = PLAYBACK;
 
 static pa_context *context = NULL;
@@ -70,6 +76,7 @@
 static pa_bool_t verbose = FALSE;
 static pa_volume_t volume = PA_VOLUME_NORM;
 static pa_bool_t volume_is_set = FALSE;
+static pa_bool_t drain = FALSE;
 
 static pa_sample_spec sample_spec = {
     .format = PA_SAMPLE_S16LE,
@@ -501,8 +508,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 +524,14 @@
     if (!stream || pa_stream_get_state(stream) != PA_STREAM_READY || !(l = w = pa_stream_writable_size(stream)))
         l = 4096;
 
-    buffer = pa_xmalloc(l);
+    if (drain) {
+        l_extra = (full_reads > DRAIN_THRESHOLD) ? (l + DRAIN_BYTES) : l;
+    } else {
+        l_extra = 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 +548,31 @@
         return;
     }
 
-    buffer_length = (uint32_t) r;
-    buffer_index = 0;
+    if (drain) {
+        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: This probably fails if frame_size is not a power of 2. */
+        const pa_sample_spec *spec = pa_stream_get_sample_spec(stream);
+        size_t frame_size = pa_frame_size(spec);
+        buffer_index = (~frame_size) & (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);
@@ -657,6 +693,7 @@
              "      --process-time=BYTES              Request the specified process time per request in bytes.\n"
              "      --latency-msec=MSEC               Request the specified latency in msec.\n"
              "      --process-time-msec=MSEC          Request the specified process time per request in msec.\n"
+             "      --drain                           Continually drain stdin.  Used for audio forwarding.\n"
              "      --property=PROPERTY=VALUE         Set the specified property to the specified value.\n"
              "      --raw                             Record/play raw PCM data.\n"
              "      --file-format[=FFORMAT]           Record/play formatted PCM data.\n"
@@ -684,7 +721,8 @@
     ARG_FILE_FORMAT,
     ARG_LIST_FILE_FORMATS,
     ARG_LATENCY_MSEC,
-    ARG_PROCESS_TIME_MSEC
+    ARG_PROCESS_TIME_MSEC,
+    ARG_DRAIN
 };
 
 int main(int argc, char *argv[]) {
@@ -722,6 +760,7 @@
         {"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},
+        {"drain",        0, NULL, ARG_DRAIN},
         {NULL,           0, NULL, 0}
     };
 
@@ -895,6 +934,10 @@
                 }
                 break;
 
+            case ARG_DRAIN:
+                drain = TRUE;
+                break;
+
             case ARG_PROPERTY: {
                 char *t;
 
This experimental patch limits the latency that can be introduced by 
increase_watermark().  It's helpful for watching TV via tvtime, but there is 
probably a better way of solving the problem.

--- src/modules/alsa/alsa-source.c.orig	2012-05-12 22:30:59.335942782 -0500
+++ src/modules/alsa/alsa-source.c	2012-05-18 23:43:49.140383432 -0500
@@ -281,6 +281,13 @@
 
     if (u->tsched_watermark < u->min_wakeup)
         u->tsched_watermark = u->min_wakeup;
+
+    /* Set the maximum watermark to 50 msec. */
+    size_t max_watermark = pa_usec_to_bytes(50*PA_USEC_PER_MSEC, &u->source->sample_spec);
+    if (u->tsched_watermark > max_watermark)
+    {
+        u->tsched_watermark = max_watermark;
+    }
 }
 
 static void increase_watermark(struct userdata *u) {
@@ -301,17 +308,7 @@
         return;
     }
 
-    /* Hmm, we cannot increase the watermark any further, hence let's raise the latency */
-    old_min_latency = u->source->thread_info.min_latency;
-    new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_INC_STEP_USEC);
-    new_min_latency = PA_MIN(new_min_latency, u->source->thread_info.max_latency);
-
-    if (old_min_latency != new_min_latency) {
-        pa_log_info("Increasing minimal latency to %0.2f ms",
-                    (double) new_min_latency / PA_USEC_PER_MSEC);
-
-        pa_source_set_latency_range_within_thread(u->source, new_min_latency, u->source->thread_info.max_latency);
-    }
+    /* Don't allow the per-thread latency to increase. */
 
     /* When we reach this we're officialy fucked! */
 }
--- src/modules/alsa/alsa-sink.c.orig	2012-05-12 22:37:12.641215837 -0500
+++ src/modules/alsa/alsa-sink.c	2012-05-19 09:01:32.743076922 -0500
@@ -293,6 +293,13 @@
 
     if (u->tsched_watermark < u->min_wakeup)
         u->tsched_watermark = u->min_wakeup;
+
+    /* Set the maximum watermark to 50 msec. */
+    size_t max_watermark = pa_usec_to_bytes(50*PA_USEC_PER_MSEC, &u->sink->sample_spec);
+    if (u->tsched_watermark > max_watermark)
+    {
+        u->tsched_watermark = max_watermark;
+    }
 }
 
 static void increase_watermark(struct userdata *u) {
@@ -313,17 +320,7 @@
         return;
     }
 
-    /* Hmm, we cannot increase the watermark any further, hence let's raise the latency */
-    old_min_latency = u->sink->thread_info.min_latency;
-    new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_INC_STEP_USEC);
-    new_min_latency = PA_MIN(new_min_latency, u->sink->thread_info.max_latency);
-
-    if (old_min_latency != new_min_latency) {
-        pa_log_info("Increasing minimal latency to %0.2f ms",
-                    (double) new_min_latency / PA_USEC_PER_MSEC);
-
-        pa_sink_set_latency_range_within_thread(u->sink, new_min_latency, u->sink->thread_info.max_latency);
-    }
+    /* Don't allow the per-thread latency to increase. */
 
     /* When we reach this we're officialy fucked! */
 }
_______________________________________________
pulseaudio-discuss mailing list
[email protected]
http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss

Reply via email to