On 06/08/13 21:40, Christian Schmidt wrote:
> This patch removes the header decoding for PCM audio from
> libavformat/mpeg.c and the 20/24bit parts from libavcodec/pcm.c,
> and merges them into a new decoder in libavcodec/pcm-mpeg.c.
> 
> The decoder has added support for samples that span multiple
> packets and modified 20/24bit group decoding. Both is needed to
> decode samples that have been generated with DVD-Lab Pro 2. The
> decoding of 16bit PCM and two channel 24bit is identical to
> before. No other samples are known to verify the correctness of
> the encoding this software does.
> The complete list of tested formats is
> 48kHz/16bit/2-8 channels
> 48kHz/24bit/2-5 channels
> 96kHz/16bit/2-4 channels
> 96kHz/24bit/2 channels
> ---
> More cosmetics.
> 
>  libavcodec/Makefile   |   2 +-
>  libavcodec/pcm-mpeg.c | 260 
> ++++++++++++++++++++++++++++++++++++++++++++++++++
>  libavcodec/pcm.c      |  43 +--------
>  libavformat/mpeg.c    |  28 +-----
>  4 files changed, 263 insertions(+), 70 deletions(-)
> 
> diff --git a/libavcodec/Makefile b/libavcodec/Makefile
> index 2ba1a32..f1475ef 100644
> --- a/libavcodec/Makefile
> +++ b/libavcodec/Makefile
> @@ -423,7 +423,7 @@ OBJS-$(CONFIG_ZMBV_ENCODER)            += zmbvenc.o
>  OBJS-$(CONFIG_PCM_ALAW_DECODER)           += pcm.o
>  OBJS-$(CONFIG_PCM_ALAW_ENCODER)           += pcm.o
>  OBJS-$(CONFIG_PCM_BLURAY_DECODER)         += pcm-mpeg.o
> -OBJS-$(CONFIG_PCM_DVD_DECODER)            += pcm.o
> +OBJS-$(CONFIG_PCM_DVD_DECODER)            += pcm-mpeg.o
>  OBJS-$(CONFIG_PCM_F32BE_DECODER)          += pcm.o
>  OBJS-$(CONFIG_PCM_F32BE_ENCODER)          += pcm.o
>  OBJS-$(CONFIG_PCM_F32LE_DECODER)          += pcm.o
> diff --git a/libavcodec/pcm-mpeg.c b/libavcodec/pcm-mpeg.c
> index f28746d..bdbcd50 100644
> --- a/libavcodec/pcm-mpeg.c
> +++ b/libavcodec/pcm-mpeg.c
> @@ -29,6 +29,15 @@
>  #include "bytestream.h"
>  #include "internal.h"
>  
> +typedef struct PCMDVDContext {
> +    uint32_t last_header;    // Cached header to see if parsing is needed
> +    int block_size;          // Size of a block of samples in bytes
> +    int samples_per_block;   // Number of samples per channel per block
> +    int groups_per_block;    // Number of 20/24bit sammple groups per block

typo.

> +    uint8_t *extra_samples;  // Pointer to leftover samples from a frame
> +    int extra_sample_count;  // Number of leftover samples in the buffer
> +} PCMDVDContext;
> +
>  /*
>   * Channel Mapping according to
>   * Blu-ray Disc Read-Only Format Version 1
> @@ -302,6 +311,242 @@ static int pcm_bluray_decode_frame(AVCodecContext 
> *avctx, void *data,
>      return retval + 4;
>  }
>  
> +static av_cold int pcm_dvd_decode_init(AVCodecContext *avctx)
> +{
> +    PCMDVDContext *s = avctx->priv_data;
> +
> +    /* Invalid header to force parsing of the first header */
> +    s->last_header = -1;
> +    /* reserve space for 8 channels, 3 bytes/sample, 4 samples/block */
> +    if (!(s->extra_samples = av_malloc(8 * 3 * 4)))
> +        return AVERROR(ENOMEM);
> +    s->extra_sample_count = 0;
> +
> +    return 0;
> +}
> +
> +static av_cold int pcm_dvd_decode_uninit(AVCodecContext *avctx)
> +{
> +    PCMDVDContext *s = avctx->priv_data;
> +
> +    if (s->extra_samples)
> +        av_free(s->extra_samples);
> +
> +    return 0;
> +}
> +
> +static int pcm_dvd_parse_header(AVCodecContext *avctx, const uint8_t *header)
> +{
> +    /* no traces of 44100 and 32000Hz in any commercial software or player */
> +    static const uint32_t frequencies[4] = { 48000, 96000, 44100, 32000 };
> +    PCMDVDContext *s = avctx->priv_data;
> +    int header_int = (header[0] & 0xe0) | (header[1] << 8) | (header[2] << 
> 16);
> +
> +    /* early exit if the header didn't change apart from the frame number */
> +    if (s->last_header == header_int)
> +        return 0;
> +
> +    if (avctx->debug & FF_DEBUG_PICT_INFO)
> +        av_dlog(avctx, "pcm_dvd_parse_header: header = %02x%02x%02x\n",
> +                header[0], header[1], header[2]);
> +    av_log(avctx, AV_LOG_INFO, "pcm_dvd_parse_header: header = 
> %02x%02x%02x\n",
> +           header[0], header[1], header[2]);
> +    /*
> +     * header[0] emphasis (1), muse(1), reserved(1), frame number(5)
> +     * header[1] quant (2), freq(2), reserved(1), channels(3)
> +     * header[2] dynamic range control (0x80 = off)
> +     */
> +
> +    /* get the sample depth and derive the sample format from it */
> +    avctx->bits_per_coded_sample = 16 + (header[1] >> 6 & 3) * 4;
> +    if (avctx->bits_per_coded_sample == 28) {
> +        av_log(avctx, AV_LOG_ERROR, "PCM DVD unsupported sample depth\n");
> +        return AVERROR_INVALIDDATA;
> +    }
> +    avctx->sample_fmt = avctx->bits_per_coded_sample == 16 ? 
> AV_SAMPLE_FMT_S16
> +                                                           : 
> AV_SAMPLE_FMT_S32;
> +    avctx->bits_per_raw_sample = avctx->bits_per_coded_sample;
> +
> +    /* get the sample rate */
> +    avctx->sample_rate = frequencies[header[1] >> 4 & 3];
> +
> +    /* get the number of channels */
> +    avctx->channels = 1 + (header[1] & 7);
> +    /* calculate the bitrate */
> +    avctx->bit_rate = avctx->channels *
> +                      avctx->sample_rate *
> +                      avctx->bits_per_coded_sample;
> +
> +    /* 4 samples form a group in 20/24bit PCM on DVD Video.
> +     * A block is formed by the number of groups that are
> +     * needed to complete a set of samples for each channel. */
> +    if (avctx->bits_per_coded_sample == 16) {
> +        s->samples_per_block = 1;
> +        s->block_size        = avctx->channels * 2;
> +    } else {
> +        switch (avctx->channels) {
> +        case 1:
> +        case 2:
> +        case 4:
> +            /* one group has all the samples needed */
> +            s->block_size        = 4 * avctx->bits_per_coded_sample / 8;
> +            s->samples_per_block = 4 / avctx->channels;
> +            s->groups_per_block  = 1;
> +            break;
> +        case 8:
> +            /* two groups have all the samples needed */
> +            s->block_size        = 8 * avctx->bits_per_coded_sample / 8;
> +            s->samples_per_block = 1;
> +            s->groups_per_block  = 2;
> +            break;
> +        default:
> +            /* need avctx->channels groups */
> +            s->block_size        = 4 * avctx->channels *
> +                                   avctx->bits_per_coded_sample / 8;
> +            s->samples_per_block = 4;
> +            s->groups_per_block  = avctx->channels;
> +            break;
> +        }
> +    }
> +
> +    if (avctx->debug & FF_DEBUG_PICT_INFO)
> +        av_dlog(avctx,
> +                "pcm_dvd_parse_header: %d channels, %d bits per sample, %d 
> Hz, %d bit/s\n",
> +                avctx->channels, avctx->bits_per_coded_sample,
> +                avctx->sample_rate, avctx->bit_rate);
> +
> +    s->last_header = header_int;
> +
> +    return 0;
> +}
> +
> +static void *pcm_dvd_decode_samples(AVCodecContext *avctx, const uint8_t 
> *src,
> +                                    void *dst, int blocks)
> +{
> +    PCMDVDContext *s = avctx->priv_data;
> +    int16_t *dst16   = dst;
> +    int32_t *dst32   = dst;
> +    GetByteContext gb;
> +    int i;
> +    uint8_t t;
> +    int samples;
> +
> +    bytestream2_init(&gb, src, blocks * s->block_size);
> +    switch (avctx->bits_per_coded_sample) {
> +    case 16:
> +#if HAVE_BIGENDIAN
> +        bytestream2_get_buffer(&gb, dst16, blocks * s->block_size);
> +        dst16 += blocks * block_size / 2;
> +#else
> +        samples = blocks * avctx->channels;
> +        do {
> +            *dst16++ = bytestream2_get_be16u(&gb);
> +        } while (--samples);
> +#endif
> +        return dst16;
> +    case 20:
> +        do {
> +            for (i = s->groups_per_block; i; i--) {
> +                dst32[0] = bytestream2_get_be16u(&gb) << 16;
> +                dst32[1] = bytestream2_get_be16u(&gb) << 16;
> +                dst32[2] = bytestream2_get_be16u(&gb) << 16;
> +                dst32[3] = bytestream2_get_be16u(&gb) << 16;
> +                t = bytestream2_get_byteu(&gb);
> +                *dst32 += (t & 0xf0) << 8;
> +                *dst32 += (t & 0x0f) << 12;
> +                t = bytestream2_get_byteu(&gb);
> +                *dst32 += (t & 0xf0) << 8;
> +                *dst32 += (t & 0x0f) << 12;
> +            }
> +        } while (--blocks);
> +        return dst32;
> +    case 24:
> +        do {
> +            for (i = s->groups_per_block; i; i--) {
> +                dst32[0] = bytestream2_get_be16u(&gb) << 16;
> +                dst32[1] = bytestream2_get_be16u(&gb) << 16;
> +                dst32[2] = bytestream2_get_be16u(&gb) << 16;
> +                dst32[3] = bytestream2_get_be16u(&gb) << 16;
> +                *dst32++ += bytestream2_get_byteu(&gb) << 8;
> +                *dst32++ += bytestream2_get_byteu(&gb) << 8;
> +                *dst32++ += bytestream2_get_byteu(&gb) << 8;
> +                *dst32++ += bytestream2_get_byteu(&gb) << 8;
> +            }
> +        } while (--blocks);
> +        return dst32;
> +    default:
> +        return NULL;
> +    }
> +}
> +
> +static int pcm_dvd_decode_frame(AVCodecContext *avctx, void *data,
> +                                int *got_frame_ptr, AVPacket *avpkt)
> +{
> +    AVFrame *frame     = data;
> +    const uint8_t *src = avpkt->data;
> +    int buf_size       = avpkt->size;
> +    PCMDVDContext *s   = avctx->priv_data;
> +    int retval;
> +    int blocks;
> +    void *dst;
> +
> +    if (buf_size < 3) {
> +        av_log(avctx, AV_LOG_ERROR, "PCM packet too small\n");
> +        return AVERROR_INVALIDDATA;
> +    }
> +
> +    if ((retval = pcm_dvd_parse_header(avctx, src)))
> +        return retval;
> +    src      += 3;
> +    buf_size -= 3;
> +
> +    blocks = (buf_size + s->extra_sample_count) / s->block_size;
> +
> +    /* get output buffer */
> +    frame->nb_samples = blocks * s->samples_per_block;
> +    if ((retval = ff_get_buffer(avctx, frame, 0)) < 0) {
> +        av_log(avctx, AV_LOG_ERROR, "get_buffer() failed\n");
> +        return retval;
> +    }
> +    dst = frame->data[0];
> +
> +    /* consume leftover samples from last packet */
> +    if (s->extra_sample_count) {
> +        int missing_samples = s->block_size - s->extra_sample_count;
> +        if (buf_size >= missing_samples) {
> +            memcpy(s->extra_samples + s->extra_sample_count, src,
> +                   missing_samples);
> +            dst = pcm_dvd_decode_samples(avctx, s->extra_samples, dst, 1);
> +            src += missing_samples;
> +            buf_size -= missing_samples;
> +            s->extra_sample_count = 0;
> +            blocks--;
> +        } else {
> +            /* new packet still doesn't have enough samples */
> +            memcpy(s->extra_samples + s->extra_sample_count, src, buf_size);
> +            s->extra_sample_count += buf_size;
> +            return avpkt->size;
> +        }
> +    }
> +
> +    /* decode remaining complete samples */
> +    if (blocks) {
> +        pcm_dvd_decode_samples(avctx, src, dst, blocks);
> +        buf_size -= blocks * s->block_size;
> +    }
> +
> +    /* store leftover samples */
> +    if (buf_size) {
> +        src += blocks * s->block_size;
> +        memcpy(s->extra_samples, src, buf_size);
> +        s->extra_sample_count = buf_size;
> +    }
> +
> +    *got_frame_ptr = 1;
> +
> +    return avpkt->size;
> +}
> +
>  AVCodec ff_pcm_bluray_decoder = {
>      .name           = "pcm_bluray",
>      .type           = AVMEDIA_TYPE_AUDIO,
> @@ -313,3 +558,18 @@ AVCodec ff_pcm_bluray_decoder = {
>      },
>      .long_name      = NULL_IF_CONFIG_SMALL("PCM signed 16|20|24-bit 
> big-endian for Blu-ray media"),
>  };
> +
> +AVCodec ff_pcm_dvd_decoder = {
> +    .name           = "pcm_dvd",
> +    .long_name      = NULL_IF_CONFIG_SMALL("PCM signed 16|20|24-bit 
> big-endian for DVD media"),
> +    .type           = AVMEDIA_TYPE_AUDIO,
> +    .id             = AV_CODEC_ID_PCM_DVD,
> +    .priv_data_size = sizeof(PCMDVDContext),
> +    .init           = pcm_dvd_decode_init,
> +    .decode         = pcm_dvd_decode_frame,
> +    .close          = pcm_dvd_decode_uninit,
> +    .capabilities   = CODEC_CAP_DR1,
> +    .sample_fmts    = (const enum AVSampleFormat[]) {
> +        AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_S32, AV_SAMPLE_FMT_NONE
> +    }
> +};
> diff --git a/libavcodec/pcm.c b/libavcodec/pcm.c
> index 31ee283..5b3bdf4 100644
> --- a/libavcodec/pcm.c
> +++ b/libavcodec/pcm.c
> @@ -265,16 +265,7 @@ static int pcm_decode_frame(AVCodecContext *avctx, void 
> *data,
>  
>      /* av_get_bits_per_sample returns 0 for AV_CODEC_ID_PCM_DVD */
>      samples_per_block = 1;
> -    if (avctx->codec->id == AV_CODEC_ID_PCM_DVD) {
> -        if (avctx->bits_per_coded_sample != 20 &&
> -            avctx->bits_per_coded_sample != 24) {
> -            av_log(avctx, AV_LOG_ERROR, "PCM DVD unsupported sample 
> depth\n");
> -            return AVERROR(EINVAL);
> -        }
> -        /* 2 samples are interleaved per block in PCM_DVD */
> -        samples_per_block = 2;
> -        sample_size       = avctx->bits_per_coded_sample * 2 / 8;
> -    } else if (avctx->codec_id == AV_CODEC_ID_PCM_LXF) {
> +    if (avctx->codec_id == AV_CODEC_ID_PCM_LXF) {
>          /* we process 40-bit blocks per channel for LXF */
>          samples_per_block = 2;
>          sample_size       = 5;
> @@ -408,37 +399,6 @@ static int pcm_decode_frame(AVCodecContext *avctx, void 
> *data,
>              samples += 2;
>          }
>          break;
> -    case AV_CODEC_ID_PCM_DVD:
> -    {
> -        const uint8_t *src8;
> -        dst_int32_t = (int32_t *)frame->data[0];
> -        n /= avctx->channels;
> -        switch (avctx->bits_per_coded_sample) {
> -        case 20:
> -            while (n--) {
> -                c    = avctx->channels;
> -                src8 = src + 4 * c;
> -                while (c--) {
> -                    *dst_int32_t++ = (bytestream_get_be16(&src) << 16) + 
> ((*src8   & 0xf0) <<  8);
> -                    *dst_int32_t++ = (bytestream_get_be16(&src) << 16) + 
> ((*src8++ & 0x0f) << 12);
> -                }
> -                src = src8;
> -            }
> -            break;
> -        case 24:
> -            while (n--) {
> -                c    = avctx->channels;
> -                src8 = src + 4 * c;
> -                while (c--) {
> -                    *dst_int32_t++ = (bytestream_get_be16(&src) << 16) + 
> ((*src8++) << 8);
> -                    *dst_int32_t++ = (bytestream_get_be16(&src) << 16) + 
> ((*src8++) << 8);
> -                }
> -                src = src8;
> -            }
> -            break;
> -        }
> -        break;
> -    }
>      case AV_CODEC_ID_PCM_LXF:
>      {
>          int i;
> @@ -522,7 +482,6 @@ AVCodec ff_ ## name_ ## _decoder = {                      
>                   \
>  
>  /* Note: Do not forget to add new entries to the Makefile as well. */
>  PCM_CODEC  (PCM_ALAW,         AV_SAMPLE_FMT_S16, pcm_alaw,         "PCM 
> A-law");
> -PCM_DECODER(PCM_DVD,          AV_SAMPLE_FMT_S32, pcm_dvd,          "PCM 
> signed 20|24-bit big-endian");
>  PCM_CODEC  (PCM_F32BE,        AV_SAMPLE_FMT_FLT, pcm_f32be,        "PCM 
> 32-bit floating point big-endian");
>  PCM_CODEC  (PCM_F32LE,        AV_SAMPLE_FMT_FLT, pcm_f32le,        "PCM 
> 32-bit floating point little-endian");
>  PCM_CODEC  (PCM_F64BE,        AV_SAMPLE_FMT_DBL, pcm_f64be,        "PCM 
> 64-bit floating point big-endian");
> diff --git a/libavformat/mpeg.c b/libavformat/mpeg.c
> index 8a4c6d7..7430bb0 100644
> --- a/libavformat/mpeg.c
> +++ b/libavformat/mpeg.c
> @@ -453,7 +453,6 @@ static int mpegps_read_packet(AVFormatContext *s,
>          codec_id = AV_CODEC_ID_DTS;
>      } else if (startcode >= 0xa0 && startcode <= 0xaf) {
>          type = AVMEDIA_TYPE_AUDIO;
> -        /* 16 bit form will be handled as AV_CODEC_ID_PCM_S16BE */
>          codec_id = AV_CODEC_ID_PCM_DVD;
>      } else if (startcode >= 0xb0 && startcode <= 0xbf) {
>          type = AVMEDIA_TYPE_AUDIO;
> @@ -494,35 +493,10 @@ static int mpegps_read_packet(AVFormatContext *s,
>      st->id = startcode;
>      st->codec->codec_type = type;
>      st->codec->codec_id = codec_id;
> -    if (codec_id != AV_CODEC_ID_PCM_S16BE)
> -        st->need_parsing = AVSTREAM_PARSE_FULL;
> +    st->need_parsing = AVSTREAM_PARSE_FULL;
>   found:
>      if(st->discard >= AVDISCARD_ALL)
>          goto skip;
> -    if ((startcode >= 0xa0 && startcode <= 0xaf) ||
> -        (startcode == 0x1bd && ((dvdaudio_substream_type & 0xe0) == 0xa0))) {
> -        int b1, freq;
> -
> -        /* for LPCM, we just skip the header and consider it is raw
> -           audio data */
> -        if (len <= 3)
> -            goto skip;
> -        avio_r8(s->pb); /* emphasis (1), muse(1), reserved(1), frame 
> number(5) */
> -        b1 = avio_r8(s->pb); /* quant (2), freq(2), reserved(1), channels(3) 
> */
> -        avio_r8(s->pb); /* dynamic range control (0x80 = off) */
> -        len -= 3;
> -        freq = (b1 >> 4) & 3;
> -        st->codec->sample_rate = lpcm_freq_tab[freq];
> -        st->codec->channels = 1 + (b1 & 7);
> -        st->codec->bits_per_coded_sample = 16 + ((b1 >> 6) & 3) * 4;
> -        st->codec->bit_rate = st->codec->channels *
> -                              st->codec->sample_rate *
> -                              st->codec->bits_per_coded_sample;
> -        if (st->codec->bits_per_coded_sample == 16)
> -            st->codec->codec_id = AV_CODEC_ID_PCM_S16BE;
> -        else if (st->codec->bits_per_coded_sample == 28)
> -            return AVERROR(EINVAL);
> -    }
>      ret = av_get_packet(s->pb, pkt, len);
>      pkt->pts = pts;
>      pkt->dts = dts;
> 

Looks good to me but would be nice having two files and not perpetuate
the pcm exceptional situation =)

lu
_______________________________________________
libav-devel mailing list
[email protected]
https://lists.libav.org/mailman/listinfo/libav-devel

Reply via email to