mp3dec: Improve bad main_data_begin pointer error handling.
authorAndre Noll <maan@systemlinux.org>
Mon, 31 Aug 2009 19:17:21 +0000 (21:17 +0200)
committerAndre Noll <maan@systemlinux.org>
Mon, 31 Aug 2009 19:17:21 +0000 (21:17 +0200)
These errors from mad_frame_decode() are non-fatal and happen if the
stream is started at the middle of the file, e.g. when para_audiod
is started while para_server is already streaming.

If libmad encounters such an error it throws away the first (and
probably the second) frame which messes up the timing in udp/fec mode,
causing an audible buffer underrun after the remaining frames of the
first fec group have been decoded and fed to the writer.

This patch makes the mp3dec filter keep track of bad main_data_begin
pointer errors that happen at the start of the stream. In this case
decoding is deferred until more data has arrived or 60ms have passed.

mp3dec_filter.c

index 92da4a5..24123bc 100644 (file)
 #define MAD_TO_SHORT(f) (f) >= MAD_F_ONE? SHRT_MAX :\
        (f) <= -MAD_F_ONE? -SHRT_MAX : (signed short) ((f) >> (MAD_F_FRACBITS - 15))
 
+/** State of the decoding process. */
+enum mp3dec_flags {
+       /** Bad main_data_begin pointer encounterd. */
+       MP3DEC_FLAG_BAD_DATA = 1,
+       /** Some output has already been produced. */
+       MP3DEC_FLAG_DECODE_STARTED = 2,
+};
+
 /** Data specific to the mp3dec filter. */
 struct private_mp3dec_data {
        /** Information on the current mp3 stream. */
@@ -28,8 +36,55 @@ struct private_mp3dec_data {
        struct mad_frame frame;
        /** Contains the PCM output. */
        struct mad_synth synth;
+       /** See \ref mp3dec_flags. */
+       unsigned flags;
+       /** Defer decoding until this time. */
+       struct timeval stream_start_barrier;
+       /** Wait until this many input bytes are available. */
+       size_t input_len_barrier;
 };
 
+static int need_bad_data_delay(struct private_mp3dec_data *pmd,
+               size_t bytes_available)
+{
+       if (!(pmd->flags & MP3DEC_FLAG_BAD_DATA))
+               return 0;
+       if (pmd->flags & MP3DEC_FLAG_DECODE_STARTED)
+               return 0;
+       if (bytes_available >= pmd->input_len_barrier)
+               return 0;
+       if (tv_diff(now, &pmd->stream_start_barrier, NULL) > 0)
+               return 0;
+       return 1;
+}
+
+/*
+ * Returns negative on serious errors, zero if the error should be ignored and
+ * positive on bad data pointer errors at stream start.
+ */
+static int handle_decode_error(struct private_mp3dec_data *pmd, size_t len)
+{
+       if (!MAD_RECOVERABLE(pmd->stream.error)
+                       && pmd->stream.error != MAD_ERROR_BUFLEN) {
+               PARA_ERROR_LOG("%s\n", mad_stream_errorstr(&pmd->stream));
+               return -E_MAD_FRAME_DECODE;
+       }
+       PARA_DEBUG_LOG("%s\n", mad_stream_errorstr(&pmd->stream));
+       if (pmd->stream.error != MAD_ERROR_BADDATAPTR)
+               return 0;
+       if (pmd->flags & MP3DEC_FLAG_DECODE_STARTED)
+               return 0;
+       /*
+        * Bad data pointer at stream start. Defer decoding until the amount of
+        * data we are about to skip is available again, but wait at most 60ms.
+        */
+       pmd->flags |= MP3DEC_FLAG_BAD_DATA;
+       pmd->input_len_barrier = len;
+       tv_add(now, &(struct timeval){0, 60 * 1000},
+               &pmd->stream_start_barrier);
+       return 1;
+}
+
 static ssize_t mp3dec(char *inbuffer, size_t len, struct filter_node *fn)
 {
        int i, ret;
@@ -38,6 +93,8 @@ static ssize_t mp3dec(char *inbuffer, size_t len, struct filter_node *fn)
 
        if (fn->loaded + 16384 > fn->bufsize)
                return 0;
+       if (need_bad_data_delay(pmd, len))
+               return 0;
        mad_stream_buffer(&pmd->stream, (unsigned char *) inbuffer, copy);
        pmd->stream.error = 0;
 next_frame:
@@ -52,18 +109,18 @@ next_frame:
        fn->fc->samplerate = pmd->frame.header.samplerate;
        fn->fc->channels = MAD_NCHANNELS(&pmd->frame.header);
        ret = mad_frame_decode(&pmd->frame, &pmd->stream);
-       if (ret) {
-               if (MAD_RECOVERABLE(pmd->stream.error) ||
-                       pmd->stream.error == MAD_ERROR_BUFLEN) {
-                       PARA_DEBUG_LOG("frame decode: %s\n",
-                               mad_stream_errorstr(&pmd->stream));
+       if (ret != 0) {
+               ret = handle_decode_error(pmd, len);
+               if (ret < 0)
+                       return ret;
+               if (ret == 0)
                        goto out;
-               }
-               PARA_ERROR_LOG("frame decode: %s\n",
-                       mad_stream_errorstr(&pmd->stream));
-               return -E_MAD_FRAME_DECODE;
+               ret = copy - (pmd->stream.bufend - pmd->stream.next_frame);
+               PARA_NOTICE_LOG("skipping %d input bytes\n", ret);
+               return ret;
        }
        mad_synth_frame(&pmd->synth, &pmd->frame);
+       pmd->flags |= MP3DEC_FLAG_DECODE_STARTED;
 
        for (i = 0; i < pmd->synth.pcm.length; i++) {
                int s = MAD_TO_SHORT(pmd->synth.pcm.samples[0][i]);