changeset 36365:de83009f96bd

mpeg123: Support in-stream format changes. Also fixes bugzilla #2149. Patch by Thomas Orgis [thomas-forum orgis org].
author reimar
date Fri, 04 Oct 2013 17:22:22 +0000
parents 4f8cf378dba4
children 7ec0ea475900
files libmpcodecs/ad_mpg123.c
diffstat 1 files changed, 88 insertions(+), 61 deletions(-) [+]
line wrap: on
line diff
--- a/libmpcodecs/ad_mpg123.c	Fri Oct 04 01:10:58 2013 +0000
+++ b/libmpcodecs/ad_mpg123.c	Fri Oct 04 17:22:22 2013 +0000
@@ -1,7 +1,7 @@
 /*
  * MPEG 1.0/2.0/2.5 audio layer I, II, III decoding with libmpg123
  *
- * Copyright (C) 2010-2012 Thomas Orgis <thomas@orgis.org>
+ * Copyright (C) 2010-2013 Thomas Orgis <thomas@orgis.org>
  *
  * MPlayer is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -59,6 +59,7 @@
 
 struct ad_mpg123_context {
     mpg123_handle *handle;
+    char new_format;
 #ifdef AD_MPG123_MEAN_BITRATE
     /* Running mean for bit rate, stream length estimation. */
     float mean_rate;
@@ -116,7 +117,9 @@
      * Let's try to run with the default for now. */
 
     /* That would produce floating point output.
-     * You can get 32 and 24 bit ints, even 8 bit via format matrix. */
+     * You can get 32 and 24 bit ints, even 8 bit via format matrix.
+     * If wanting a specific encoding here, configure format matrix and
+     * make sure it is in set_format(). */
     /* mpg123_param(con->handle, MPG123_ADD_FLAGS, MPG123_FORCE_FLOAT, 0.); */
 
     /* Example for RVA choice (available since libmpg123 1.0.0):
@@ -205,6 +208,59 @@
            smodes[i->mode]);
 }
 
+/* libmpg123 has a new format ready; query and store, return return value
+   of mpg123_getformat() */
+static int set_format(sh_audio_t *sh, struct ad_mpg123_context *con)
+{
+    int ret;
+    long rate;
+    int channels;
+    int encoding;
+    ret = mpg123_getformat(con->handle, &rate, &channels, &encoding);
+    if(ret == MPG123_OK) {
+        sh->channels   = channels;
+        sh->samplerate = rate;
+        /* Without external force, mpg123 will always choose signed encoding,
+         * and non-16-bit only on builds that don't support it.
+         * Be reminded that it doesn't matter to the MPEG file what encoding
+         * is produced from it. */
+        switch (encoding) {
+        case MPG123_ENC_SIGNED_8:
+            sh->sample_format = AF_FORMAT_S8;
+            sh->samplesize    = 1;
+            break;
+        case MPG123_ENC_SIGNED_16:
+            sh->sample_format = AF_FORMAT_S16_NE;
+            sh->samplesize    = 2;
+            break;
+        /* To stay compatible with the oldest libmpg123 headers, do not rely
+         * on float and 32 bit encoding symbols being defined.
+         * Those formats came later */
+        case 0x1180: /* MPG123_ENC_SIGNED_32 */
+            sh->sample_format = AF_FORMAT_S32_NE;
+            sh->samplesize    = 4;
+            break;
+        case 0x200: /* MPG123_ENC_FLOAT_32 */
+            sh->sample_format = AF_FORMAT_FLOAT_NE;
+            sh->samplesize    = 4;
+            break;
+        default:
+            /* This means we got a funny custom build of libmpg123 that only supports an unknown format. */
+            mp_msg(MSGT_DECAUDIO, MSGL_ERR,
+                   "Bad encoding from mpg123: %i.\n", encoding);
+            return MPG123_ERR;
+        }
+#ifdef AD_MPG123_FRAMEWISE
+        /* Going to decode directly to MPlayer's memory. It is important
+         * to have MPG123_AUTO_RESAMPLE disabled for the buffer size
+         * being an all-time limit. */
+        sh->audio_out_minsize = 1152 * 2 * sh->samplesize;
+#endif
+        con->new_format = 0;
+    }
+    return ret;
+}
+
 /* This tries to extract a requested amount of decoded data.
  * Even when you request 0 bytes, it will feed enough input so that
  * the decoder _could_ have delivered something.
@@ -234,6 +290,9 @@
      * This will be handled in init(). */
     do {
         size_t got_now = 0;
+        /* Fetch new format now, after old data has been used. */
+        if(con->new_format)
+            ret = set_format(sh, con);
 
         /* Feed the decoder. This will only fire from the second round on. */
         if (ret == MPG123_NEED_MORE) {
@@ -260,6 +319,15 @@
 #endif
             if (ret == MPG123_ERR)
                 break;
+
+            /* Indication of format change is possible here (from mpg123_decode()). */
+            if(ret == MPG123_NEW_FORMAT) {
+                con->new_format = 1;
+                if(got)
+                    break; /* Do not switch format during a chunk. */
+
+                ret = set_format(sh, con);
+            }
         }
         /* Theoretically, mpg123 could return MPG123_DONE, so be prepared.
          * Should not happen in our usage, but it is a valid return code. */
@@ -270,7 +338,7 @@
          * for the loop condition. */
 #ifdef AD_MPG123_FRAMEWISE
         if (!buf) { /* fake call just for feeding to get format */
-            ret = mpg123_getformat(con->handle, NULL, NULL, NULL);
+            ret = set_format(sh, con);
         } else { /* This is the decoding. One frame at a time. */
             ret = mpg123_replace_buffer(con->handle, buf, count);
             if (ret == MPG123_OK)
@@ -284,6 +352,15 @@
         got += got_now;
         sh->pts_bytes += got_now;
 
+        /* Indication of format change should happen here. */
+        if(ret == MPG123_NEW_FORMAT) {
+            con->new_format = 1;
+            if(got)
+                break; /* Do not switch format during a chunk. */
+
+            ret = set_format(sh, con);
+        }
+
 #ifdef AD_MPG123_FRAMEWISE
     } while (ret == MPG123_NEED_MORE || (got == 0 && count != 0));
 #else
@@ -293,8 +370,6 @@
     if (ret == MPG123_ERR) {
         mp_msg(MSGT_DECAUDIO, MSGL_ERR, "mpg123 decoding failed: %s\n",
                mpg123_strerror(con->handle));
-        mpg123_close(con->handle);
-        return -1;
     }
 
     return got;
@@ -313,39 +388,29 @@
     /* Open and make sure we have fed enough data to get stream properties. */
     if (MPG123_OK == mpg123_open_feed(con->handle) &&
         /* Feed data until mpg123 is ready (has found stream beginning). */
-        !decode_a_bit(sh, NULL, 0)) {
+        !decode_a_bit(sh, NULL, 0) &&
+        set_format(sh, con) == MPG123_OK) { /* format setting again just for return value */
         return 1;
     } else {
         mp_msg(MSGT_DECAUDIO, MSGL_ERR,
                "mpg123 failed to reopen stream: %s\n",
                mpg123_strerror(con->handle));
-        mpg123_close(con->handle);
         return 0;
     }
 }
 
 /* Now we really start accessing some data and determining file format.
- * Paranoia note: The mpg123_close() on errors is not really necessary,
- * But it ensures that we don't accidentally continue decoding with a
- * bad state (possibly interpreting the format badly or whatnot). */
+ * Format now is allowed to change on-the-fly. Here is the only point
+ * that has MPlayer react to errors. We have to pray that exceptional
+ * erros in other places simply cannot occur. */
 static int init(sh_audio_t *sh)
 {
-    long rate    = 0;
-    int channels = 0;
-    int encoding = 0;
     mpg123_id3v2 *v2;
     struct mpg123_frameinfo finfo;
     struct ad_mpg123_context *con = sh->context;
 
-    /* We're open about any output format that libmpg123 will suggest.
-     * Note that a standard build will always default to 16 bit signed and
-     * the native sample rate of the file. */
-    if (MPG123_OK == mpg123_format_all(con->handle) &&
-        reopen_stream(sh) &&
-        MPG123_OK == mpg123_getformat(con->handle, &rate, &channels, &encoding) &&
-        /* Forbid the format to change later on. */
-        MPG123_OK == mpg123_format_none(con->handle) &&
-        MPG123_OK == mpg123_format(con->handle, rate, channels, encoding) &&
+    con->new_format = 0;
+    if (reopen_stream(sh) &&
         /* Get MPEG header info. */
         MPG123_OK == mpg123_info(con->handle, &finfo) &&
         /* Since we queried format, mpg123 should have read past ID3v2 tags.
@@ -367,50 +432,11 @@
         con->mean_count = 0;
 #endif
         con->vbr = (finfo.vbr != MPG123_CBR);
-        sh->channels   = channels;
-        sh->samplerate = rate;
-        /* Without external force, mpg123 will always choose signed encoding,
-         * and non-16-bit only on builds that don't support it.
-         * Be reminded that it doesn't matter to the MPEG file what encoding
-         * is produced from it. */
-        switch (encoding) {
-        case MPG123_ENC_SIGNED_8:
-            sh->sample_format = AF_FORMAT_S8;
-            sh->samplesize    = 1;
-            break;
-        case MPG123_ENC_SIGNED_16:
-            sh->sample_format = AF_FORMAT_S16_NE;
-            sh->samplesize    = 2;
-            break;
-        /* To stay compatible with the oldest libmpg123 headers, do not rely
-         * on float and 32 bit encoding symbols being defined.
-         * Those formats came later */
-        case 0x1180: /* MPG123_ENC_SIGNED_32 */
-            sh->sample_format = AF_FORMAT_S32_NE;
-            sh->samplesize    = 4;
-            break;
-        case 0x200: /* MPG123_ENC_FLOAT_32 */
-            sh->sample_format = AF_FORMAT_FLOAT_NE;
-            sh->samplesize    = 4;
-            break;
-        default:
-            mp_msg(MSGT_DECAUDIO, MSGL_ERR,
-                   "Bad encoding from mpg123: %i.\n", encoding);
-            mpg123_close(con->handle);
-            return 0;
-        }
-#ifdef AD_MPG123_FRAMEWISE
-        /* Going to decode directly to MPlayer's memory. It is important
-         * to have MPG123_AUTO_RESAMPLE disabled for the buffer size
-         * being an all-time limit. */
-        sh->audio_out_minsize = 1152 * 2 * sh->samplesize;
-#endif
 
         return 1;
     } else {
         mp_msg(MSGT_DECAUDIO, MSGL_ERR, "mpg123 init error: %s\n",
                mpg123_strerror(con->handle));
-        mpg123_close(con->handle);
         return 0;
     }
 }
@@ -455,6 +481,7 @@
     int bytes;
 
     bytes = decode_a_bit(sh, buf, maxlen);
+    /* This EOF is ignored, apparently, until input data is exhausted. */
     if (bytes == 0)
         return -1;              /* EOF */