[FFmpeg-devel] [PATCH] avcodec/wmadec: fix WMA gapless playback

bananaman255 at gmail.com bananaman255 at gmail.com
Fri Oct 5 20:39:26 EEST 2018


From: bnnm <bananaman255 at gmail.com>

Fixes trac issue #7473.

Removes encoder delay (skip samples) and writes remaining frame samples after EOF to get correct sample count.

Output is now accurate vs players that use Microsoft's codecs (Windows Media Format Runtime).

Tested vs encode>decode WMAv2 with MS's codecs and most sample rate/bit rate/channel/mode combinations in ASF/XWMA. WMAv1 appears to use the same delay, from FFmpeg samples.

Signed-off-by: bnnm <bananaman255 at gmail.com>
---
 libavcodec/wma.h    |  2 ++
 libavcodec/wmadec.c | 34 ++++++++++++++++++++++++++++++++--
 2 files changed, 34 insertions(+), 2 deletions(-)

diff --git a/libavcodec/wma.h b/libavcodec/wma.h
index 325f03c44b..c80068de80 100644
--- a/libavcodec/wma.h
+++ b/libavcodec/wma.h
@@ -133,6 +133,8 @@ typedef struct WMACodecContext {
     float lsp_pow_m_table2[(1 << LSP_POW_BITS)];
     AVFloatDSPContext *fdsp;
 
+    int eof_done; /* decode flag to output remaining samples after EOF */
+
 #ifdef TRACE
     int frame_count;
 #endif /* TRACE */
diff --git a/libavcodec/wmadec.c b/libavcodec/wmadec.c
index 78b51e5871..d59432d3f1 100644
--- a/libavcodec/wmadec.c
+++ b/libavcodec/wmadec.c
@@ -124,6 +124,11 @@ static av_cold int wma_decode_init(AVCodecContext *avctx)
 
     avctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
 
+    /* Skip WMA encoder delay (>=32000: 4096, >=22050: 2048, >=8000: 1024).
+     * The amount doesn't seem specified in the flags or container (ASF/XWMA),
+     * but can be verified compared to Microsoft codecs' output. */
+    avctx->internal->skip_samples = s->frame_len * 2;
+
     return 0;
 }
 
@@ -819,7 +824,29 @@ static int wma_decode_superframe(AVCodecContext *avctx, void *data,
     ff_tlog(avctx, "***decode_superframe:\n");
 
     if (buf_size == 0) {
+        /* must output one final frame with remaining samples */
+
+        if (s->eof_done)
+            return 0;
+
+        /* get output buffer */
+        frame->nb_samples = s->frame_len;
+        if ((ret = ff_get_buffer(avctx, frame, 0)) < 0)
+            return ret;
+        samples = (float **) frame->extended_data;
+
+        /* clean output buffer and copy last IMCDT samples */
+        for (i = 0; i < s->avctx->channels; i++) {
+            memset(frame->extended_data[i], 0,
+                 s->frame_len * sizeof(*s->frame_out[i]));
+
+            memcpy(frame->extended_data[i], &s->frame_out[i][0],
+                 s->frame_len * sizeof(*s->frame_out[i]) >> 1);
+        }
+
         s->last_superframe_len = 0;
+        s->eof_done = 1;
+        *got_frame_ptr = 1;
         return 0;
     }
     if (buf_size < avctx->block_align) {
@@ -965,6 +992,9 @@ static av_cold void flush(AVCodecContext *avctx)
 
     s->last_bitoffset      =
     s->last_superframe_len = 0;
+
+    s->eof_done = 0;
+    avctx->internal->skip_samples = s->frame_len * 2;
 }
 
 #if CONFIG_WMAV1_DECODER
@@ -978,7 +1008,7 @@ AVCodec ff_wmav1_decoder = {
     .close          = ff_wma_end,
     .decode         = wma_decode_superframe,
     .flush          = flush,
-    .capabilities   = AV_CODEC_CAP_DR1,
+    .capabilities   = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY,
     .sample_fmts    = (const enum AVSampleFormat[]) { AV_SAMPLE_FMT_FLTP,
                                                       AV_SAMPLE_FMT_NONE },
 };
@@ -994,7 +1024,7 @@ AVCodec ff_wmav2_decoder = {
     .close          = ff_wma_end,
     .decode         = wma_decode_superframe,
     .flush          = flush,
-    .capabilities   = AV_CODEC_CAP_DR1,
+    .capabilities   = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY,
     .sample_fmts    = (const enum AVSampleFormat[]) { AV_SAMPLE_FMT_FLTP,
                                                       AV_SAMPLE_FMT_NONE },
 };
-- 
2.11.0.windows.3



More information about the ffmpeg-devel mailing list