[FFmpeg-devel] avcodec: add a WavPack DSD decoder

David Bryant david at wavpack.com
Mon Jul 22 02:03:14 EEST 2019


Hi,

As I promised late last year, here is a patch to add a WavPack DSD decoder.

Thanks!

-David Bryant

-------------- next part --------------
From d80f4fb59e4afec1e2c539391a7b4484f5ae8436 Mon Sep 17 00:00:00 2001
From: David Bryant <david at wavpack.com>
Date: Sun, 21 Jul 2019 15:43:25 -0700
Subject: [PATCH] avcodec: add a WavPack DSD decoder

Signed-off-by: David Bryant <david at wavpack.com>
---
 libavcodec/Makefile      |   1 +
 libavcodec/allcodecs.c   |   1 +
 libavcodec/avcodec.h     |   1 +
 libavcodec/codec_desc.c  |   7 +
 libavcodec/wavpack.h     |   2 +
 libavcodec/wavpack_dsd.c | 792 +++++++++++++++++++++++++++++++++++++++++++++++
 libavformat/wvdec.c      |  32 +-
 7 files changed, 824 insertions(+), 12 deletions(-)
 create mode 100644 libavcodec/wavpack_dsd.c

diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 3cd73fb..b94327e 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -686,6 +686,7 @@ OBJS-$(CONFIG_VPLAYER_DECODER)         += textdec.o ass.o
 OBJS-$(CONFIG_VP9_V4L2M2M_DECODER)     += v4l2_m2m_dec.o
 OBJS-$(CONFIG_VQA_DECODER)             += vqavideo.o
 OBJS-$(CONFIG_WAVPACK_DECODER)         += wavpack.o
+OBJS-$(CONFIG_WAVPACK_DSD_DECODER)     += wavpack_dsd.o dsd.o
 OBJS-$(CONFIG_WAVPACK_ENCODER)         += wavpackenc.o
 OBJS-$(CONFIG_WCMV_DECODER)            += wcmv.o
 OBJS-$(CONFIG_WEBP_DECODER)            += webp.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index d2f9a39..a2f414b 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -485,6 +485,7 @@ extern AVCodec ff_vorbis_encoder;
 extern AVCodec ff_vorbis_decoder;
 extern AVCodec ff_wavpack_encoder;
 extern AVCodec ff_wavpack_decoder;
+extern AVCodec ff_wavpack_dsd_decoder;
 extern AVCodec ff_wmalossless_decoder;
 extern AVCodec ff_wmapro_decoder;
 extern AVCodec ff_wmav1_encoder;
diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
index d234271..8d3a551 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -652,6 +652,7 @@ enum AVCodecID {
     AV_CODEC_ID_SBC,
     AV_CODEC_ID_ATRAC9,
     AV_CODEC_ID_HCOM,
+    AV_CODEC_ID_WAVPACK_DSD,
 
     /* subtitle codecs */
     AV_CODEC_ID_FIRST_SUBTITLE = 0x17000,          ///< A dummy ID pointing at the start of subtitle codecs.
diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index 4d033c2..bee88b8 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -2985,6 +2985,13 @@ static const AVCodecDescriptor codec_descriptors[] = {
         .long_name = NULL_IF_CONFIG_SMALL("HCOM Audio"),
         .props     = AV_CODEC_PROP_LOSSY,
     },
+    {
+        .id        = AV_CODEC_ID_WAVPACK_DSD,
+        .type      = AVMEDIA_TYPE_AUDIO,
+        .name      = "wavpack_dsd",
+        .long_name = NULL_IF_CONFIG_SMALL("WavPack DSD"),
+        .props     = AV_CODEC_PROP_LOSSLESS,
+    },
 
     /* subtitle codecs */
     {
diff --git a/libavcodec/wavpack.h b/libavcodec/wavpack.h
index 6caad03..43aaac8 100644
--- a/libavcodec/wavpack.h
+++ b/libavcodec/wavpack.h
@@ -35,6 +35,7 @@
 #define WV_FLOAT_DATA     0x00000080
 #define WV_INT32_DATA     0x00000100
 #define WV_FALSE_STEREO   0x40000000
+#define WV_DSD_DATA       0x80000000
 
 #define WV_HYBRID_MODE    0x00000008
 #define WV_HYBRID_SHAPE   0x00000008
@@ -77,6 +78,7 @@ enum WP_ID {
     WP_ID_CORR,
     WP_ID_EXTRABITS,
     WP_ID_CHANINFO,
+    WP_ID_DSD_DATA,
     WP_ID_SAMPLE_RATE = 0x27,
 };
 
diff --git a/libavcodec/wavpack_dsd.c b/libavcodec/wavpack_dsd.c
new file mode 100644
index 0000000..0754314
--- /dev/null
+++ b/libavcodec/wavpack_dsd.c
@@ -0,0 +1,792 @@
+/*
+ * WavPack lossless DSD audio decoder
+ * Copyright (c) 2006,2011 Konstantin Shishkov
+ * Copyright (c) 2019 David Bryant
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libavutil/channel_layout.h"
+
+#include "avcodec.h"
+#include "bytestream.h"
+#include "internal.h"
+#include "wavpack.h"
+#include "dsd.h"
+
+/**
+ * @file
+ * WavPack lossless DSD audio decoder
+ */
+
+#define DSD_BYTE_READY(low,high) (!(((low) ^ (high)) & 0xff000000))
+
+#define PTABLE_BITS 8
+#define PTABLE_BINS (1<<PTABLE_BITS)
+#define PTABLE_MASK (PTABLE_BINS-1)
+
+#define UP   0x010000fe
+#define DOWN 0x00010000
+#define DECAY 8
+
+#define PRECISION 20
+#define VALUE_ONE (1 << PRECISION)
+#define PRECISION_USE 12
+
+#define RATE_S 20
+
+#define MAX_HISTORY_BITS    5
+#define MAX_HISTORY_BINS    (1 << MAX_HISTORY_BITS)
+
+typedef struct WavpackFrameContext {
+    AVCodecContext *avctx;
+    int stereo, stereo_in;
+    uint32_t CRC;
+    int samples;
+    GetByteContext gb;
+    int ptable [PTABLE_BINS];
+    int16_t summed_probabilities [MAX_HISTORY_BINS] [256];
+    unsigned char probabilities [MAX_HISTORY_BINS] [256];
+    unsigned char *value_lookup [MAX_HISTORY_BINS];
+    DSDContext dsdctx[2];
+} WavpackFrameContext;
+
+#define WV_MAX_FRAME_DECODERS 14
+
+typedef struct WavpackContext {
+    AVCodecContext *avctx;
+
+    WavpackFrameContext *fdec[WV_MAX_FRAME_DECODERS];
+    int fdec_num;
+
+    int block;
+    int samples;
+    int ch_offset;
+} WavpackContext;
+
+static inline int wv_check_crc(WavpackFrameContext *s, uint32_t crc)
+{
+    if (crc != s->CRC) {
+        av_log(s->avctx, AV_LOG_ERROR, "CRC error\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    return 0;
+}
+
+static void init_ptable (int *table, int rate_i, int rate_s)
+{
+    int value = 0x808000, rate = rate_i << 8, c, i;
+
+    for (c = (rate + 128) >> 8; c--;)
+        value += (DOWN - value) >> DECAY;
+
+    for (i = 0; i < PTABLE_BINS/2; ++i) {
+        table [i] = value;
+        table [PTABLE_BINS-1-i] = 0x100ffff - value;
+
+        if (value > 0x010000) {
+            rate += (rate * rate_s + 128) >> 8;
+
+            for (c = (rate + 64) >> 7; c--;)
+                value += (DOWN - value) >> DECAY;
+        }
+    }
+}
+
+typedef struct {
+    int32_t value, fltr0, fltr1, fltr2, fltr3, fltr4, fltr5, fltr6, factor, byte;
+} DSDfilters;
+
+static int wv_unpack_dsd_high(WavpackFrameContext *s, void *dst_l, void *dst_r)
+{
+    uint8_t *dsd_l                  = dst_l;
+    uint8_t *dsd_r                  = dst_r;
+    uint32_t crc                    = 0xFFFFFFFF;
+    int total_samples = s->samples, stereo = dst_r ? 1 : 0;
+    DSDfilters filters [2], *sp = filters;
+    int channel, rate_i, rate_s, i;
+    uint32_t low, high, value;
+
+    if (bytestream2_get_bytes_left(&s->gb) < (stereo ? 20 : 13))
+        return AVERROR_INVALIDDATA;
+
+    rate_i = bytestream2_get_byte(&s->gb);
+    rate_s = bytestream2_get_byte(&s->gb);
+
+    if (rate_s != RATE_S)
+        return AVERROR_INVALIDDATA;
+
+    init_ptable (s->ptable, rate_i, rate_s);
+
+    for (channel = 0; channel < stereo + 1; ++channel) {
+        DSDfilters *sp = filters + channel;
+
+        sp->fltr1 = bytestream2_get_byte(&s->gb) << (PRECISION - 8);
+        sp->fltr2 = bytestream2_get_byte(&s->gb) << (PRECISION - 8);
+        sp->fltr3 = bytestream2_get_byte(&s->gb) << (PRECISION - 8);
+        sp->fltr4 = bytestream2_get_byte(&s->gb) << (PRECISION - 8);
+        sp->fltr5 = bytestream2_get_byte(&s->gb) << (PRECISION - 8);
+        sp->fltr6 = 0;
+        sp->factor = bytestream2_get_byte(&s->gb) & 0xff;
+        sp->factor |= (bytestream2_get_byte(&s->gb) << 8) & 0xff00;
+        sp->factor = (sp->factor << 16) >> 16;
+    }
+
+    high = 0xffffffff;
+    low = 0x0;
+
+    for (i = 4; i--;)
+        value = (value << 8) | bytestream2_get_byte(&s->gb);
+
+    memset (dst_l, 0x69, total_samples * 4);
+
+    if (stereo)
+        memset (dst_r, 0x69, total_samples * 4);
+
+    while (total_samples--) {
+        int bitcount = 8;
+
+        sp [0].value = sp [0].fltr1 - sp [0].fltr5 + ((sp [0].fltr6 * sp [0].factor) >> 2);
+
+        if (stereo)
+            sp [1].value = sp [1].fltr1 - sp [1].fltr5 + ((sp [1].fltr6 * sp [1].factor) >> 2);
+
+        while (bitcount--) {
+            int32_t *pp = s->ptable + ((sp [0].value >> (PRECISION - PRECISION_USE)) & PTABLE_MASK);
+            uint32_t split = low + ((high - low) >> 8) * (*pp >> 16);
+
+            if (value <= split) {
+                high = split;
+                *pp += (UP - *pp) >> DECAY;
+                sp [0].fltr0 = -1;
+            }
+            else {
+                low = split + 1;
+                *pp += (DOWN - *pp) >> DECAY;
+                sp [0].fltr0 = 0;
+            }
+
+            while (DSD_BYTE_READY (high, low) && bytestream2_get_bytes_left(&s->gb)) {
+                value = (value << 8) | bytestream2_get_byte(&s->gb);
+                high = (high << 8) | 0xff;
+                low <<= 8;
+            }
+
+            sp [0].value += sp [0].fltr6 << 3;
+            sp [0].byte = (sp [0].byte << 1) | (sp [0].fltr0 & 1);
+            sp [0].factor += (((sp [0].value ^ sp [0].fltr0) >> 31) | 1) &
+                ((sp [0].value ^ (sp [0].value - (sp [0].fltr6 << 4))) >> 31);
+            sp [0].fltr1 += ((sp [0].fltr0 & VALUE_ONE) - sp [0].fltr1) >> 6;
+            sp [0].fltr2 += ((sp [0].fltr0 & VALUE_ONE) - sp [0].fltr2) >> 4;
+            sp [0].fltr3 += (sp [0].fltr2 - sp [0].fltr3) >> 4;
+            sp [0].fltr4 += (sp [0].fltr3 - sp [0].fltr4) >> 4;
+            sp [0].value = (sp [0].fltr4 - sp [0].fltr5) >> 4;
+            sp [0].fltr5 += sp [0].value;
+            sp [0].fltr6 += (sp [0].value - sp [0].fltr6) >> 3;
+            sp [0].value = sp [0].fltr1 - sp [0].fltr5 + ((sp [0].fltr6 * sp [0].factor) >> 2);
+
+            if (!stereo)
+                continue;
+
+            pp = s->ptable + ((sp [1].value >> (PRECISION - PRECISION_USE)) & PTABLE_MASK);
+            split = low + ((high - low) >> 8) * (*pp >> 16);
+
+            if (value <= split) {
+                high = split;
+                *pp += (UP - *pp) >> DECAY;
+                sp [1].fltr0 = -1;
+            }
+            else {
+                low = split + 1;
+                *pp += (DOWN - *pp) >> DECAY;
+                sp [1].fltr0 = 0;
+            }
+
+            while (DSD_BYTE_READY (high, low) && bytestream2_get_bytes_left(&s->gb)) {
+                value = (value << 8) | bytestream2_get_byte(&s->gb);
+                high = (high << 8) | 0xff;
+                low <<= 8;
+            }
+
+            sp [1].value += sp [1].fltr6 << 3;
+            sp [1].byte = (sp [1].byte << 1) | (sp [1].fltr0 & 1);
+            sp [1].factor += (((sp [1].value ^ sp [1].fltr0) >> 31) | 1) &
+                ((sp [1].value ^ (sp [1].value - (sp [1].fltr6 << 4))) >> 31);
+            sp [1].fltr1 += ((sp [1].fltr0 & VALUE_ONE) - sp [1].fltr1) >> 6;
+            sp [1].fltr2 += ((sp [1].fltr0 & VALUE_ONE) - sp [1].fltr2) >> 4;
+            sp [1].fltr3 += (sp [1].fltr2 - sp [1].fltr3) >> 4;
+            sp [1].fltr4 += (sp [1].fltr3 - sp [1].fltr4) >> 4;
+            sp [1].value = (sp [1].fltr4 - sp [1].fltr5) >> 4;
+            sp [1].fltr5 += sp [1].value;
+            sp [1].fltr6 += (sp [1].value - sp [1].fltr6) >> 3;
+            sp [1].value = sp [1].fltr1 - sp [1].fltr5 + ((sp [1].fltr6 * sp [1].factor) >> 2);
+        }
+
+        crc += (crc << 1) + (*dsd_l = sp [0].byte & 0xff);
+        sp [0].factor -= (sp [0].factor + 512) >> 10;
+        dsd_l += 4;
+
+        if (stereo) {
+            crc += (crc << 1) + (*dsd_r = filters [1].byte & 0xff);
+            filters [1].factor -= (filters [1].factor + 512) >> 10;
+            dsd_r += 4;
+        }
+    }
+
+    if ((s->avctx->err_recognition & AV_EF_CRCCHECK) && wv_check_crc(s, crc))
+        return AVERROR_INVALIDDATA;
+
+    return 0;
+}
+
+static int wv_unpack_dsd_fast(WavpackFrameContext *s, void *dst_l, void *dst_r)
+{
+    uint8_t *dsd_l                  = dst_l;
+    uint8_t *dsd_r                  = dst_r;
+    unsigned char history_bits, max_probability;
+    int total_summed_probabilities  = 0;
+    int total_samples               = s->samples;
+    int history_bins, p0, p1, chan;
+    uint32_t crc                    = 0xFFFFFFFF;
+    uint32_t low, high, value;
+    int ret = 0, i;
+
+    if (!bytestream2_get_bytes_left(&s->gb))
+        return AVERROR_INVALIDDATA;
+
+    history_bits = bytestream2_get_byte(&s->gb);
+
+    if (!bytestream2_get_bytes_left(&s->gb) || history_bits > MAX_HISTORY_BITS)
+        return AVERROR_INVALIDDATA;
+
+    history_bins = 1 << history_bits;
+    max_probability = bytestream2_get_byte(&s->gb);
+
+    if (max_probability < 0xff) {
+        unsigned char *outptr = (unsigned char *) s->probabilities;
+        unsigned char *outend = outptr + sizeof (*s->probabilities) * history_bins;
+
+        while (outptr < outend && bytestream2_get_bytes_left(&s->gb)) {
+            int code = bytestream2_get_byte(&s->gb);
+
+            if (code > max_probability) {
+                int zcount = code - max_probability;
+
+                while (outptr < outend && zcount--)
+                    *outptr++ = 0;
+            }
+            else if (code)
+                *outptr++ = code;
+            else
+                break;
+        }
+
+        if (outptr < outend ||
+            (bytestream2_get_bytes_left(&s->gb) && bytestream2_get_byte(&s->gb)))
+                return AVERROR_INVALIDDATA;
+    }
+    else if (bytestream2_get_bytes_left(&s->gb) > (int) sizeof (*s->probabilities) * history_bins)
+        bytestream2_get_buffer(&s->gb, (uint8_t *) s->probabilities,
+            sizeof (*s->probabilities) * history_bins);
+    else
+        return AVERROR_INVALIDDATA;
+
+    for (p0 = 0; p0 < history_bins; ++p0) {
+        int32_t sum_values;
+        unsigned char *vp;
+
+        for (sum_values = i = 0; i < 256; ++i)
+            s->summed_probabilities [p0] [i] = sum_values += s->probabilities [p0] [i];
+
+        if (sum_values) {
+            total_summed_probabilities += sum_values;
+            vp = s->value_lookup [p0] = av_malloc (sum_values);
+
+            for (i = 0; i < 256; i++) {
+                int c = s->probabilities [p0] [i];
+
+                while (c--)
+                    *vp++ = i;
+            }
+        }
+    }
+
+    if (bytestream2_get_bytes_left(&s->gb) < 4 ||
+        total_summed_probabilities > history_bins * 1280) {
+            ret = AVERROR_INVALIDDATA;
+            goto done;
+    }
+
+    for (i = 4; i--;)
+        value = (value << 8) | bytestream2_get_byte(&s->gb);
+
+    chan = p0 = p1 = 0;
+    low = 0; high = 0xffffffff;
+
+    memset (dst_l, 0x69, total_samples * 4);
+
+    if (dst_r) {
+        memset (dst_r, 0x69, total_samples * 4);
+        total_samples *= 2;
+    }
+
+    while (total_samples--) {
+        int mult, index, code;
+
+        if (!s->summed_probabilities [p0] [255]) {
+            ret = AVERROR_INVALIDDATA;
+            goto done;
+        }
+
+        mult = (high - low) / s->summed_probabilities [p0] [255];
+
+        if (!mult) {
+            if (bytestream2_get_bytes_left(&s->gb) >= 4)
+                for (i = 4; i--;)
+                    value = (value << 8) | bytestream2_get_byte(&s->gb);
+
+            low = 0;
+            high = 0xffffffff;
+            mult = high / s->summed_probabilities [p0] [255];
+
+            if (!mult) {
+                ret = AVERROR_INVALIDDATA;
+                goto done;
+            }
+        }
+
+        index = (value - low) / mult;
+
+        if (index >= s->summed_probabilities [p0] [255]) {
+            ret = AVERROR_INVALIDDATA;
+            goto done;
+        }
+
+        if (!dst_r) {
+            if ((*dsd_l = code = s->value_lookup [p0] [index]))
+                low += s->summed_probabilities [p0] [code-1] * mult;
+
+            dsd_l += 4;
+        }
+        else {
+            if ((code = s->value_lookup [p0] [index]))
+                low += s->summed_probabilities [p0] [code-1] * mult;
+
+            if (chan) {
+                *dsd_r = code;
+                dsd_r += 4;
+            }
+            else {
+                *dsd_l = code;
+                dsd_l += 4;
+            }
+
+            chan ^= 1;
+        }
+
+        high = low + s->probabilities [p0] [code] * mult - 1;
+        crc += (crc << 1) + code;
+
+        if (!dst_r)
+            p0 = code & (history_bins-1);
+        else {
+            p0 = p1;
+            p1 = code & (history_bins-1);
+        }
+
+        while (DSD_BYTE_READY (high, low) && bytestream2_get_bytes_left(&s->gb)) {
+            value = (value << 8) | bytestream2_get_byte(&s->gb);
+            high = (high << 8) | 0xff;
+            low <<= 8;
+        }
+    }
+
+done:
+    for (p0 = 0; p0 < history_bins; ++p0)
+        if (s->value_lookup [p0]) {
+            av_free (s->value_lookup [p0]);
+            s->value_lookup [p0] = NULL;
+        }
+
+    if (ret < 0)
+        return ret;
+
+    if ((s->avctx->err_recognition & AV_EF_CRCCHECK) && wv_check_crc(s, crc))
+        ret = AVERROR_INVALIDDATA;
+
+    return ret;
+}
+
+static int wv_unpack_dsd_copy(WavpackFrameContext *s, void *dst_l, void *dst_r)
+{
+    uint8_t *dsd_l              = dst_l;
+    uint8_t *dsd_r              = dst_r;
+    int total_samples           = s->samples;
+    uint32_t crc                = 0xFFFFFFFF;
+
+    if (bytestream2_get_bytes_left (&s->gb) != total_samples * (dst_r ? 2 : 1))
+        return AVERROR_INVALIDDATA;
+
+    memset (dst_l, 0x69, total_samples * 4);
+
+    if (dst_r)
+        memset (dst_r, 0x69, total_samples * 4);
+
+    while (total_samples--) {
+        crc += (crc << 1) + (*dsd_l = bytestream2_get_byte(&s->gb));
+        dsd_l += 4;
+
+        if (dst_r) {
+            crc += (crc << 1) + (*dsd_r = bytestream2_get_byte(&s->gb));
+            dsd_r += 4;
+        }
+    }
+
+    if ((s->avctx->err_recognition & AV_EF_CRCCHECK) && wv_check_crc(s, crc))
+        return AVERROR_INVALIDDATA;
+
+    return 0;
+}
+
+static av_cold int wv_alloc_frame_context(WavpackContext *c)
+{
+    if (c->fdec_num == WV_MAX_FRAME_DECODERS)
+        return -1;
+
+    c->fdec[c->fdec_num] = av_mallocz(sizeof(**c->fdec));
+    if (!c->fdec[c->fdec_num])
+        return -1;
+    c->fdec_num++;
+    c->fdec[c->fdec_num - 1]->avctx = c->avctx;
+    memset(c->fdec[c->fdec_num - 1]->dsdctx[0].buf, 0x69,
+        sizeof(c->fdec[c->fdec_num - 1]->dsdctx[0].buf));
+    memset(c->fdec[c->fdec_num - 1]->dsdctx[1].buf, 0x69,
+        sizeof(c->fdec[c->fdec_num - 1]->dsdctx[1].buf));
+
+    return 0;
+}
+
+static av_cold int wavpack_decode_init(AVCodecContext *avctx)
+{
+    WavpackContext *s = avctx->priv_data;
+
+    s->avctx = avctx;
+
+    s->fdec_num = 0;
+
+    ff_init_dsd_data();
+
+    return 0;
+}
+
+static av_cold int wavpack_decode_end(AVCodecContext *avctx)
+{
+    WavpackContext *s = avctx->priv_data;
+
+    s->fdec_num = 0;
+
+    return 0;
+}
+
+static int wavpack_decode_block(AVCodecContext *avctx, int block_no,
+                                AVFrame *frame, const uint8_t *buf, int buf_size)
+{
+    WavpackContext *wc = avctx->priv_data;
+    WavpackFrameContext *s;
+    GetByteContext gb;
+    void *samples_l = NULL, *samples_r = NULL;
+    int ret;
+    int got_dsd = 0;
+    int id, size, ssize;
+    int chan = 0, chmask = 0, sample_rate = 0, rate_x = 1, dsd_mode = 0;
+    int frame_flags, multiblock;
+
+    if (block_no >= wc->fdec_num && wv_alloc_frame_context(wc) < 0) {
+        av_log(avctx, AV_LOG_ERROR, "Error creating frame decode context\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    s = wc->fdec[block_no];
+    if (!s) {
+        av_log(avctx, AV_LOG_ERROR, "Context for block %d is not present\n",
+               block_no);
+        return AVERROR_INVALIDDATA;
+    }
+
+    bytestream2_init(&gb, buf, buf_size);
+
+    s->samples = bytestream2_get_le32(&gb);
+    if (s->samples != wc->samples) {
+        av_log(avctx, AV_LOG_ERROR, "Mismatching number of samples in "
+               "a sequence: %d and %d\n", wc->samples, s->samples);
+        return AVERROR_INVALIDDATA;
+    }
+    frame_flags = bytestream2_get_le32(&gb);
+    multiblock     = (frame_flags & WV_SINGLE_BLOCK) != WV_SINGLE_BLOCK;
+
+    s->stereo         = !(frame_flags & WV_MONO);
+    s->stereo_in      =  (frame_flags & WV_FALSE_STEREO) ? 0 : s->stereo;
+    s->CRC            = bytestream2_get_le32(&gb);
+
+    // parse metadata blocks
+    while (bytestream2_get_bytes_left(&gb)) {
+        id   = bytestream2_get_byte(&gb);
+        size = bytestream2_get_byte(&gb);
+        if (id & WP_IDF_LONG) {
+            size |= (bytestream2_get_byte(&gb)) << 8;
+            size |= (bytestream2_get_byte(&gb)) << 16;
+        }
+        size <<= 1; // size is specified in words
+        ssize  = size;
+        if (id & WP_IDF_ODD)
+            size--;
+        if (size < 0) {
+            av_log(avctx, AV_LOG_ERROR,
+                   "Got incorrect block %02X with size %i\n", id, size);
+            break;
+        }
+        if (bytestream2_get_bytes_left(&gb) < ssize) {
+            av_log(avctx, AV_LOG_ERROR,
+                   "Block size %i is out of bounds\n", size);
+            break;
+        }
+        switch (id & WP_IDF_MASK) {
+        case WP_ID_DSD_DATA:
+            if (size < 2) {
+                av_log(avctx, AV_LOG_ERROR, "Invalid DSD_DATA, size = %i\n",
+                       size);
+                bytestream2_skip(&gb, ssize);
+                continue;
+            }
+            rate_x = 1 << bytestream2_get_byte(&gb);
+            dsd_mode = bytestream2_get_byte(&gb);
+            if (dsd_mode && dsd_mode != 1 && dsd_mode != 3) {
+                av_log(avctx, AV_LOG_ERROR, "Invalid DSD encoding mode: %d\n",
+                    dsd_mode);
+                return AVERROR_INVALIDDATA;
+            }
+            bytestream2_init(&s->gb, gb.buffer, size-2);
+            bytestream2_skip(&gb, size-2);
+            got_dsd      = 1;
+            break;
+        case WP_ID_CHANINFO:
+            if (size <= 1) {
+                av_log(avctx, AV_LOG_ERROR,
+                       "Insufficient channel information\n");
+                return AVERROR_INVALIDDATA;
+            }
+            chan = bytestream2_get_byte(&gb);
+            switch (size - 2) {
+            case 0:
+                chmask = bytestream2_get_byte(&gb);
+                break;
+            case 1:
+                chmask = bytestream2_get_le16(&gb);
+                break;
+            case 2:
+                chmask = bytestream2_get_le24(&gb);
+                break;
+            case 3:
+                chmask = bytestream2_get_le32(&gb);
+                break;
+            case 4:
+                size = bytestream2_get_byte(&gb);
+                chan  |= (bytestream2_get_byte(&gb) & 0xF) << 8;
+                chan  += 1;
+                if (avctx->channels != chan)
+                    av_log(avctx, AV_LOG_WARNING, "%i channels signalled"
+                           " instead of %i.\n", chan, avctx->channels);
+                chmask = bytestream2_get_le24(&gb);
+                break;
+            case 5:
+                size = bytestream2_get_byte(&gb);
+                chan  |= (bytestream2_get_byte(&gb) & 0xF) << 8;
+                chan  += 1;
+                if (avctx->channels != chan)
+                    av_log(avctx, AV_LOG_WARNING, "%i channels signalled"
+                           " instead of %i.\n", chan, avctx->channels);
+                chmask = bytestream2_get_le32(&gb);
+                break;
+            default:
+                av_log(avctx, AV_LOG_ERROR, "Invalid channel info size %d\n",
+                       size);
+                chan   = avctx->channels;
+                chmask = avctx->channel_layout;
+            }
+            break;
+        case WP_ID_SAMPLE_RATE:
+            if (size != 3) {
+                av_log(avctx, AV_LOG_ERROR, "Invalid custom sample rate.\n");
+                return AVERROR_INVALIDDATA;
+            }
+            sample_rate = bytestream2_get_le24(&gb);
+            break;
+        default:
+            bytestream2_skip(&gb, size);
+        }
+        if (id & WP_IDF_ODD)
+            bytestream2_skip(&gb, 1);
+    }
+
+    if (!got_dsd) {
+        av_log(avctx, AV_LOG_ERROR, "Packed samples not found\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    if (!wc->ch_offset) {
+        int sr = (frame_flags >> 23) & 0xf;
+        if (sr == 0xf) {
+            if (!sample_rate) {
+                av_log(avctx, AV_LOG_ERROR, "Custom sample rate missing.\n");
+                return AVERROR_INVALIDDATA;
+            }
+            avctx->sample_rate = sample_rate * rate_x;
+        } else
+            avctx->sample_rate = wv_rates[sr] * rate_x;
+
+        if (multiblock) {
+            if (chan)
+                avctx->channels = chan;
+            if (chmask)
+                avctx->channel_layout = chmask;
+        } else {
+            avctx->channels       = s->stereo ? 2 : 1;
+            avctx->channel_layout = s->stereo ? AV_CH_LAYOUT_STEREO :
+                                                AV_CH_LAYOUT_MONO;
+        }
+
+        /* get output buffer */
+        frame->nb_samples = s->samples + 1;
+        if ((ret = ff_get_buffer(avctx, frame, 0)) < 0)
+            return ret;
+        frame->nb_samples = s->samples;
+    }
+
+    if (wc->ch_offset + s->stereo >= avctx->channels) {
+        av_log(avctx, AV_LOG_WARNING, "Too many channels coded in a packet.\n");
+        return ((avctx->err_recognition & AV_EF_EXPLODE) || !wc->ch_offset) ?
+            AVERROR_INVALIDDATA : 0;
+    }
+
+    samples_l = frame->extended_data[wc->ch_offset];
+    if (s->stereo)
+        samples_r = frame->extended_data[wc->ch_offset + 1];
+
+    wc->ch_offset += 1 + s->stereo;
+
+    if (s->stereo_in) {
+        if (dsd_mode == 3)
+            ret = wv_unpack_dsd_high(s, samples_l, samples_r);
+        else if (dsd_mode == 1)
+            ret = wv_unpack_dsd_fast(s, samples_l, samples_r);
+        else
+            ret = wv_unpack_dsd_copy(s, samples_l, samples_r);
+    }
+    else {
+        if (dsd_mode == 3)
+            ret = wv_unpack_dsd_high(s, samples_l, NULL);
+        else if (dsd_mode == 1)
+            ret = wv_unpack_dsd_fast(s, samples_l, NULL);
+        else
+            ret = wv_unpack_dsd_copy(s, samples_l, NULL);
+
+        if (s->stereo)
+            memcpy(samples_r, samples_l, 4 * s->samples);
+    }
+
+    ff_dsd2pcm_translate (&s->dsdctx [0], s->samples, 0, samples_l, 4, samples_l, 1);
+
+    if (s->stereo)
+        ff_dsd2pcm_translate (&s->dsdctx [1], s->samples, 0, samples_r, 4, samples_r, 1);
+
+    return ret;
+}
+
+static int wavpack_decode_frame(AVCodecContext *avctx, void *data,
+                                int *got_frame_ptr, AVPacket *avpkt)
+{
+    WavpackContext *s  = avctx->priv_data;
+    const uint8_t *buf = avpkt->data;
+    int buf_size       = avpkt->size;
+    AVFrame *frame     = data;
+    int frame_size, ret, frame_flags;
+
+    if (avpkt->size <= WV_HEADER_SIZE)
+        return AVERROR_INVALIDDATA;
+
+    s->block     = 0;
+    s->ch_offset = 0;
+
+    /* determine number of samples */
+    s->samples  = AV_RL32(buf + 20);
+    frame_flags = AV_RL32(buf + 24);
+    if (s->samples <= 0 || s->samples > WV_MAX_SAMPLES) {
+        av_log(avctx, AV_LOG_ERROR, "Invalid number of samples: %d\n",
+               s->samples);
+        return AVERROR_INVALIDDATA;
+    }
+
+    if (!(frame_flags & WV_DSD_DATA)) {
+        av_log(avctx, AV_LOG_ERROR, "Encountered a non-DSD frame\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    avctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
+
+    while (buf_size > 0) {
+        if (buf_size <= WV_HEADER_SIZE)
+            break;
+        frame_size = AV_RL32(buf + 4) - 12;
+        buf       += 20;
+        buf_size  -= 20;
+        if (frame_size <= 0 || frame_size > buf_size) {
+            av_log(avctx, AV_LOG_ERROR,
+                   "Block %d has invalid size (size %d vs. %d bytes left)\n",
+                   s->block, frame_size, buf_size);
+            return AVERROR_INVALIDDATA;
+        }
+        if ((ret = wavpack_decode_block(avctx, s->block,
+                                        frame, buf, frame_size)) < 0) {
+            return ret;
+        }
+        s->block++;
+        buf      += frame_size;
+        buf_size -= frame_size;
+    }
+
+    if (s->ch_offset != avctx->channels) {
+        av_log(avctx, AV_LOG_ERROR, "Not enough channels coded in a packet.\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    *got_frame_ptr = 1;
+
+    return avpkt->size;
+}
+
+AVCodec ff_wavpack_dsd_decoder = {
+    .name           = "wavpack_dsd",
+    .long_name      = NULL_IF_CONFIG_SMALL("WavPack DSD"),
+    .type           = AVMEDIA_TYPE_AUDIO,
+    .id             = AV_CODEC_ID_WAVPACK_DSD,
+    .priv_data_size = sizeof(WavpackContext),
+    .init           = wavpack_decode_init,
+    .close          = wavpack_decode_end,
+    .decode         = wavpack_decode_frame,
+    .capabilities   = AV_CODEC_CAP_DR1,
+};
diff --git a/libavformat/wvdec.c b/libavformat/wvdec.c
index 649791d..50f4079 100644
--- a/libavformat/wvdec.c
+++ b/libavformat/wvdec.c
@@ -79,7 +79,7 @@ static int wv_read_block_header(AVFormatContext *ctx, AVIOContext *pb)
 {
     WVContext *wc = ctx->priv_data;
     int ret;
-    int rate, bpp, chan;
+    int rate, rate_x, bpp, chan;
     uint32_t chmask, flags;
 
     wc->pos = avio_tell(pb);
@@ -98,11 +98,6 @@ static int wv_read_block_header(AVFormatContext *ctx, AVIOContext *pb)
         return ret;
     }
 
-    if (wc->header.flags & WV_DSD) {
-        avpriv_report_missing_feature(ctx, "WV DSD");
-        return AVERROR_PATCHWELCOME;
-    }
-
     if (wc->header.version < 0x402 || wc->header.version > 0x410) {
         avpriv_report_missing_feature(ctx, "WV version 0x%03X",
                                       wc->header.version);
@@ -115,7 +110,8 @@ static int wv_read_block_header(AVFormatContext *ctx, AVIOContext *pb)
         return 0;
     // parse flags
     flags  = wc->header.flags;
-    bpp    = ((flags & 3) + 1) << 3;
+    rate_x = (flags & WV_DSD) ? 4 : 1;
+    bpp    = (flags & WV_DSD) ? 0 : ((flags & 3) + 1) << 3;
     chan   = 1 + !(flags & WV_MONO);
     chmask = flags & WV_MONO ? AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO;
     rate   = wv_rates[(flags >> 23) & 0xF];
@@ -124,7 +120,7 @@ static int wv_read_block_header(AVFormatContext *ctx, AVIOContext *pb)
         chan   = wc->chan;
         chmask = wc->chmask;
     }
-    if ((rate == -1 || !chan) && !wc->block_parsed) {
+    if ((rate == -1 || !chan || flags & WV_DSD) && !wc->block_parsed) {
         int64_t block_end = avio_tell(pb) + wc->header.blocksize;
         if (!(pb->seekable & AVIO_SEEKABLE_NORMAL)) {
             av_log(ctx, AV_LOG_ERROR,
@@ -177,6 +173,16 @@ static int wv_read_block_header(AVFormatContext *ctx, AVIOContext *pb)
                     return AVERROR_INVALIDDATA;
                 }
                 break;
+            case 0xE:
+                if (size <= 1) {
+                    av_log(ctx, AV_LOG_ERROR,
+                           "Invalid DSD block\n");
+                    return AVERROR_INVALIDDATA;
+                }
+                rate_x = 1 << avio_r8(pb);
+                if (size)
+                    avio_skip(pb, size-1);
+                break;
             case 0x27:
                 rate = avio_rl24(pb);
                 break;
@@ -200,7 +206,7 @@ static int wv_read_block_header(AVFormatContext *ctx, AVIOContext *pb)
     if (!wc->chmask)
         wc->chmask = chmask;
     if (!wc->rate)
-        wc->rate   = rate;
+        wc->rate   = rate * rate_x;
 
     if (flags && bpp != wc->bpp) {
         av_log(ctx, AV_LOG_ERROR,
@@ -214,10 +220,10 @@ static int wv_read_block_header(AVFormatContext *ctx, AVIOContext *pb)
                chan, wc->chan);
         return AVERROR_INVALIDDATA;
     }
-    if (flags && rate != -1 && rate != wc->rate) {
+    if (flags && rate != -1 && !(flags & WV_DSD) && rate * rate_x != wc->rate) {
         av_log(ctx, AV_LOG_ERROR,
                "Sampling rate differ, this block: %i, header block: %i\n",
-               rate, wc->rate);
+               rate * rate_x, wc->rate);
         return AVERROR_INVALIDDATA;
     }
     return 0;
@@ -245,7 +251,9 @@ static int wv_read_header(AVFormatContext *s)
     if (!st)
         return AVERROR(ENOMEM);
     st->codecpar->codec_type            = AVMEDIA_TYPE_AUDIO;
-    st->codecpar->codec_id              = AV_CODEC_ID_WAVPACK;
+    st->codecpar->codec_id              = wc->header.flags & WV_DSD ?
+                                          AV_CODEC_ID_WAVPACK_DSD :
+                                          AV_CODEC_ID_WAVPACK;
     st->codecpar->channels              = wc->chan;
     st->codecpar->channel_layout        = wc->chmask;
     st->codecpar->sample_rate           = wc->rate;
-- 
1.9.1



More information about the ffmpeg-devel mailing list