[FFmpeg-devel] avcodec/wmaprodec: improve WMAPRO/XMA gapless output

Submitted by bananaman255@gmail.com on Oct. 3, 2018, 9:55 p.m.

Details

Message ID 20181003215532.3672-1-bananaman255@gmail.com
State New
Headers show

Commit Message

bananaman255@gmail.com Oct. 3, 2018, 9:55 p.m.
From: bnnm <bananaman255@gmail.com>

Improves trac issue #6722. Fixes truncated XMA output (was missing 128 samples) and applies bitstream gapless info (partially for XMA, fully for WMAPRO).

Applying XMA end_skip would require some extra changes in the XMA multi-stream handling, so end samples are slightly bigger than what should be.

Compared to MS's decoders, WMAPRO in XWMA are correct, while in ASF (.wma) don't seem to read the last frame, so output is around 128~512 samples smaller (this happens even with gapless disabled and affects other ASF codecs).

Signed-off-by: bnnm <bananaman255@gmail.com>
---
 libavcodec/wmaprodec.c | 92 ++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 66 insertions(+), 26 deletions(-)

Patch hide | download patch | download mbox

diff --git a/libavcodec/wmaprodec.c b/libavcodec/wmaprodec.c
index 9439bfa771..e9cc5d4ed9 100644
--- a/libavcodec/wmaprodec.c
+++ b/libavcodec/wmaprodec.c
@@ -216,9 +216,9 @@  typedef struct WMAProDecodeCtx {
     GetBitContext    gb;                            ///< bitstream reader context
     int              buf_bit_size;                  ///< buffer size in bits
     uint8_t          drc_gain;                      ///< gain for the DRC tool
-    int8_t           skip_frame;                    ///< skip output step
     int8_t           parsed_all_subframes;          ///< all subframes decoded?
     uint8_t          skip_packets;                  ///< packets to skip to find next packet in a stream (XMA1/2)
+    int8_t           eof_done;                      ///< set when EOF reached and extra subframe is written (XMA1/2)
 
     /* subframe/block decode state */
     int16_t          subframe_len;                  ///< current subframe length
@@ -379,12 +379,6 @@  static av_cold int decode_init(WMAProDecodeCtx *s, AVCodecContext *avctx, int nu
         return AVERROR_PATCHWELCOME;
     }
 
-    /** frame info */
-    if (avctx->codec_id != AV_CODEC_ID_WMAPRO)
-        s->skip_frame = 0;
-    else
-        s->skip_frame = 1; /* skip first frame */
-
     s->packet_loss = 1;
     s->len_prefix  = (s->decode_flags & 0x40);
 
@@ -1450,21 +1444,34 @@  static int decode_frame(WMAProDecodeCtx *s, AVFrame *frame, int *got_frame_ptr)
         ff_dlog(s->avctx, "drc_gain %i\n", s->drc_gain);
     }
 
-    /** no idea what these are for, might be the number of samples
-        that need to be skipped at the beginning or end of a stream */
+    /** read encoder delay/padding (gapless) info */
     if (get_bits1(gb)) {
-        int av_unused skip;
+        int start_skip, end_skip;
+
 
-        /** usually true for the first frame */
+        /** usually true for the first frame and equal to 1 frame */
         if (get_bits1(gb)) {
-            skip = get_bits(gb, av_log2(s->samples_per_frame * 2));
-            ff_dlog(s->avctx, "start skip: %i\n", skip);
+            start_skip = get_bits(gb, av_log2(s->samples_per_frame * 2));
+            start_skip = FFMIN(start_skip, s->samples_per_frame);
+
+            /* must add for XMA to respect starting skip_samples */
+            s->avctx->internal->skip_samples += start_skip;
         }
 
-        /** sometimes true for the last frame */
+        /** usually true for the last frame and less than 1 frame */
         if (get_bits1(gb)) {
-            skip = get_bits(gb, av_log2(s->samples_per_frame * 2));
-            ff_dlog(s->avctx, "end skip: %i\n", skip);
+            end_skip = get_bits(gb, av_log2(s->samples_per_frame * 2));
+            end_skip = FFMIN(end_skip, s->samples_per_frame);
+
+            if (s->avctx->codec_id == AV_CODEC_ID_WMAPRO) {
+                frame->nb_samples = s->samples_per_frame - end_skip;
+            }
+
+            //TODO XMA end skip (needs changes in multistream's handling)
+            /* for XMA this skip seems to consider last frame + extra subframe,
+             * and (unlike WMAPRO?) files may have only 1 packet. Last packet
+             * nb_samples would be: 512 + 128 - start_skip - end_skip
+             * (theoretically skips happen anywhere, test more_frames too) */
         }
 
     }
@@ -1500,13 +1507,9 @@  static int decode_frame(WMAProDecodeCtx *s, AVFrame *frame, int *got_frame_ptr)
                s->samples_per_frame * sizeof(*s->channel[i].out) >> 1);
     }
 
-    if (s->skip_frame) {
-        s->skip_frame = 0;
-        *got_frame_ptr = 0;
-        av_frame_unref(frame);
-    } else {
-        *got_frame_ptr = 1;
-    }
+    /** frame may be partially discarded with gapless info in the bitstream */
+    *got_frame_ptr = 1;
+
 
     if (s->len_prefix) {
         if (len != (get_bits_count(gb) - s->frame_offset) + 2) {
@@ -1609,7 +1612,33 @@  static int decode_packet(AVCodecContext *avctx, WMAProDecodeCtx *s,
 
     *got_frame_ptr = 0;
 
-    if (s->packet_done || s->packet_loss) {
+    if (!buf_size) {
+        AVFrame *frame = data;
+        int i;
+
+        /** XMA must output one extra subframe after stream end with
+         * remaining samples (WMAPRO encoder adds padding instead). */
+        s->packet_done = 0;
+        if (s->eof_done || avctx->codec_id == AV_CODEC_ID_WMAPRO)
+            return 0;
+
+        /** clean output buffer and copy last IMCDT samples */
+        for (i = 0; i < s->nb_channels; i++) {
+            memset(frame->extended_data[i], 0,
+            s->samples_per_frame * sizeof(*s->channel[i].out));
+
+            memcpy(frame->extended_data[i], s->channel[i].out,
+                   s->samples_per_frame * sizeof(*s->channel[i].out) >> 1);
+        }
+        /* TODO: should output 128 samples only, but needs changes in
+         * multi-stream handling, so currently writes full 512 samples. */
+
+        s->eof_done = 1;
+        s->packet_done = 1;
+        *got_frame_ptr = 1;
+        return 0;
+    }
+    else if (s->packet_done || s->packet_loss) {
         s->packet_done = 0;
 
         /** sanity check for the buffer length */
@@ -1896,6 +1925,12 @@  static av_cold int xma_decode_init(AVCodecContext *avctx)
         start_channels += s->xma[i].nb_channels;
     }
 
+    /** XMA seems to require this to create gapless files (WMAPRO encoder
+     * pads files with silence, while XMA can encode any nb_samples).
+     * This value seems to counter the extra subframe output at EOF
+     * (or perhaps, XMA decoder should output 64 samples earlier?) */
+    avctx->internal->skip_samples = 128/2;
+
     return ret;
 }
 
@@ -1922,6 +1957,7 @@  static void flush(WMAProDecodeCtx *s)
                sizeof(*s->channel[i].out));
     s->packet_loss = 1;
     s->skip_packets = 0;
+    s->eof_done = 0;
 }
 
 
@@ -1934,6 +1970,8 @@  static void wmapro_flush(AVCodecContext *avctx)
     WMAProDecodeCtx *s = avctx->priv_data;
 
     flush(s);
+
+    avctx->internal->skip_samples = 0;
 }
 
 static void xma_flush(AVCodecContext *avctx)
@@ -1946,6 +1984,8 @@  static void xma_flush(AVCodecContext *avctx)
 
     memset(s->offset, 0, sizeof(s->offset));
     s->current_stream = 0;
+
+    avctx->internal->skip_samples = 128/2;
 }
 
 
@@ -1976,7 +2016,7 @@  AVCodec ff_xma1_decoder = {
     .init           = xma_decode_init,
     .close          = xma_decode_end,
     .decode         = xma_decode_packet,
-    .capabilities   = AV_CODEC_CAP_SUBFRAMES | AV_CODEC_CAP_DR1,
+    .capabilities   = AV_CODEC_CAP_SUBFRAMES | AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY,
     .sample_fmts    = (const enum AVSampleFormat[]) { AV_SAMPLE_FMT_FLTP,
                                                       AV_SAMPLE_FMT_NONE },
 };
@@ -1991,7 +2031,7 @@  AVCodec ff_xma2_decoder = {
     .close          = xma_decode_end,
     .decode         = xma_decode_packet,
     .flush          = xma_flush,
-    .capabilities   = AV_CODEC_CAP_SUBFRAMES | AV_CODEC_CAP_DR1,
+    .capabilities   = AV_CODEC_CAP_SUBFRAMES | AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY,
     .sample_fmts    = (const enum AVSampleFormat[]) { AV_SAMPLE_FMT_FLTP,
                                                       AV_SAMPLE_FMT_NONE },
 };