diff mbox

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

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

Commit Message

bananaman255@gmail.com Oct. 5, 2018, 5:39 p.m. UTC
From: bnnm <bananaman255@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@gmail.com>
---
 libavcodec/wma.h    |  2 ++
 libavcodec/wmadec.c | 34 ++++++++++++++++++++++++++++++++--
 2 files changed, 34 insertions(+), 2 deletions(-)
diff mbox

Patch

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 },
 };