On Tue, Apr 12, 2011 at 01:32:26PM +0200, Max Horn wrote:
> Hi there!
> 
> I originally sent this patch to ffmpeg-devel, but DonDiego on #libav-devel 
> asked me to re-send it also here, which I am doing hereby. Below is my 
> original email; since then, I revised the patch to include seeking support, 
> so that part of it is outdated.
> 
> The patch, however, is against latest ffmpeg GIT, not libav GIT. I hope that 
> poses no major obstacles.

> From a8124fb041a8e1287666cf2bbb0ff2c8ddb3bbf5 Mon Sep 17 00:00:00 2001
> From: Max Horn <[email protected]>
> Date: Mon, 11 Apr 2011 12:00:33 +0200
> Subject: [PATCH] add xWMA demuxer
> 
> ---
>  Changelog                |    1 +
>  MAINTAINERS              |    1 +
>  doc/general.texi         |    1 +
>  libavformat/Makefile     |    1 +
>  libavformat/allformats.c |    1 +
>  libavformat/version.h    |    2 +-
>  libavformat/xwma.c       |  278 
> ++++++++++++++++++++++++++++++++++++++++++++++
>  7 files changed, 284 insertions(+), 1 deletions(-)
>  create mode 100644 libavformat/xwma.c
> 
> diff --git a/Changelog b/Changelog
> index d56780b..a57c1e7 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -93,6 +93,7 @@ version <next>:
>  - fieldorder video filter added
>  - AAC encoding via libvo-aacenc
>  - AMR-WB encoding via libvo-amrwbenc
> +- xWMA demuxer
>  
>  
>  version 0.6:
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 8588ba7..c4ceb9d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -338,6 +338,7 @@ Muxers/Demuxers:
>    westwood.c                            Mike Melanson
>    wtv.c                                 Peter Ross
>    wv.c                                  Kostya Shishkov
> +  xwma.c                                Max Horn
>  
>  Protocols:
>    http.c                                Ronald S. Bultje
> diff --git a/doc/general.texi b/doc/general.texi
> index 5f75cef..bb22ff7 100644
> --- a/doc/general.texi
> +++ b/doc/general.texi
> @@ -259,6 +259,7 @@ library:
>      @tab Multimedia format used in Westwood Studios games.
>  @item Westwood Studios VQA      @tab   @tab X
>      @tab Multimedia format used in Westwood Studios games.
> +@item xWMA                      @tab   @tab X
>  @item YUV4MPEG pipe             @tab X @tab X
>  @item Psygnosis YOP             @tab   @tab X
>  @end multitable

please add an explanation since this name alone is not very descriptive

> diff --git a/libavformat/xwma.c b/libavformat/xwma.c
> new file mode 100644
> index 0000000..0f139d9
> --- /dev/null
> +++ b/libavformat/xwma.c
> @@ -0,0 +1,278 @@
> +/*
> + * xWMA demuxer
> + * Copyright (c) 2011 Max Horn
> + *
> + * 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/intreadwrite.h"
> +#include "avformat.h"
> +#include "riff.h"
> +
> +/*
> +The following is taken from
> +<http://msdn.microsoft.com/en-us/library/ee415832%28v=vs.85%29.aspx>
> +
> +
> +xWMA File Structure
> +
> +An xWMA file is a standard RIFF file with the following chunk types.
> +RIFF
> +    Standard RIFF chunk containing a file type with the value XWMA in the
> +    first four bytes of its data section, and the other chunks in the file in
> +    the remainder of its data section.
> +fmt
> +    Contains the format header for the xWMA file. The data in this chunk
> +    corresponds to a WAVEFORMATEX structure for xWMA data with one or two
> +    channels or a WAVEFORMATEXTENSIBLE structure for xWMA data with three or
> +    more channels. On the Xbox 360 data loaded from a fmt chunk will need to
> +    be byte swapped to account for the endianness difference between Windows
> +    and Xbox 360.
> +data
> +    Contains the encoded xWMA audio data. When using xWMA in XAudio2, the
> +    contents of the data chunk will be read into a buffer, and passed to a
> +    source voice as the pAudioData member of an XAUDIO2_BUFFER structure. The
> +    contents of the data chunk do not need to be byte swapped.
> +dpds
> +    Contains the decoded packet cumulative data size array, each element is
> +    the number of bytes accumulated after the corresponding xWMA packet is
> +    decoded in order. The size of this chunk in UINT32 values and the 
> contents
> +    of its data section are used in XAudio2 to fill out an XAUDIO2_BUFFER_WMA
> +    structure. On the Xbox 360 data loaded from a dpds chunk will need to be
> +    byte swapped to account for the endianness difference between Windows and
> +    Xbox 360.
> +
> +*/

We have wiki.multimedia.cx for that, there's no need to have it here (and IIRC
you started putting some information there)

> +typedef struct {
> +    int64_t data_end;
> +} XWMAContext;
> +
> +static int xwma_probe(AVProbeData *p)
> +{
> +    if (!memcmp(p->buf + 8, "XWMA", 4)) {
> +        if (!memcmp(p->buf, "RIFF", 4))
> +            return AVPROBE_SCORE_MAX;
> +    }
> +    return 0;
> +}
> +
> +static int xwma_read_header(AVFormatContext *s, AVFormatParameters *ap)
> +{
> +    int64_t size, av_uninit(data_size);
> +    uint32_t dpds_table_size = 0;
> +    uint32_t *dpds_table = 0;
> +    unsigned int tag;
> +    AVIOContext *pb = s->pb;
> +    AVStream *st;
> +    XWMAContext *xwma = s->priv_data;
> +    int i;
> +
> +    // The following code is mostly copied from wav.c, with some
> +    // minor alterations.
> +
> +    /* check RIFF header */
> +    tag = avio_rl32(pb);
> +    if (tag != MKTAG('R', 'I', 'F', 'F'))
> +        return -1;
> +    avio_rl32(pb); /* file size */
> +    tag = avio_rl32(pb);
> +    if (tag != MKTAG('X', 'W', 'M', 'A'))
> +        return -1;
> +
> +    /* parse fmt header */
> +    tag = avio_rl32(pb);
> +    if (tag != MKTAG('f', 'm', 't', ' '))
> +        return -1;
> +    size = avio_rl32(pb);
> +    if (size < 0)
> +        return -1;
> +    st = av_new_stream(s, 0);
> +    if (!st)
> +        return AVERROR(ENOMEM);
> +
> +    ff_get_wav_header(pb, st->codec, size);
> +    st->need_parsing = AVSTREAM_PARSE_NONE;
> +
> +    // In all xWMA files I have seen, there is no extradata.
> +    // But the WMA codecs require extradata, so we provide
> +    // our own fake extradata.

that should be enough for comment

> +    // The correct extradata was determined experimentally by me:
> +    // I simply let it convert test input data with all possible
> +    // extradata values, and picked the only one for which
> +    // a correct output was produced.
> +
> +    // First, check that there really was no extradata in the header.
> +    // If there was, then we don't really know what to do. So we
> +    // ask the user to provide feedback on this unusual file.
> +    if (st->codec->extradata_size != 0) {
> +        av_log(s, AV_LOG_ERROR, "unexpected extradata (%d bytes)\n", 
> st->codec->extradata_size);
> +        return -1;
> +    }

av_log_ask_for_sample(s, "file contains extradata\n")

but isn't it better just to pass it if it's found (maybe print a warning too)?

> +    // All xWMA files I have seen contained WMAv2 data. If there are files
> +    // using WMA Pro or some other codec, then we need to figure out the 
> right
> +    // extra data for that. Thus, once more ask the user for feedback.
> +    if (st->codec->codec_id == CODEC_ID_WMAV2) {
> +        uint32_t decode_flags = 31; // experimentally obtained value
> +
> +        st->codec->extradata_size = 6;
> +        st->codec->extradata = av_mallocz(6 + FF_INPUT_BUFFER_PADDING_SIZE);
> +        if (!st->codec->extradata)
> +            return AVERROR(ENOMEM);
> +
> +        AV_WL16(st->codec->extradata + 4, decode_flags);

setting just one byte should be enough here

> +    } else {
> +        av_log(s, AV_LOG_ERROR, "unsupported codec (tag 0x04%x; id %d)\n", 
> st->codec->codec_tag, st->codec->codec_id);
> +        return -1;  // Unsupported codec
> +    }
> +
> +    av_set_pts_info(st, 64, 1, st->codec->sample_rate);
> +
> +    for (;;) {
> +        if (url_feof(pb))
> +            return -1;
> +        /* read next tag */
> +        tag = avio_rl32(pb);
> +        size = avio_rl32(pb);
> +        if (tag == MKTAG('d', 'a', 't', 'a')) {
> +            /* We assume that the data chunk comes last. */
> +            break;
> +        } else if (tag == MKTAG('d','p','d','s')) {
> +            // Quoting the MSDN xWMA docs on the dpds chunk:
> +            // "Contains the decoded packet cumulative data size array, each 
> element is
> +            // the number of bytes accumulated after the corresponding xWMA 
> packet is
> +            // decoded in order"
> +            //
> +            // Each block in the xWMA file has size equal to 
> st->codec->block_align,
> +            // which in all cases I saw so far was always 2300.
> +            //
> +            // Thus, from the value "packet" and the value *p we are just 
> about to
> +            // read, together with the known output sample rate and the 
> block size,
> +            // we can compute a seeking index.
> +
> +            /* Error out if there is more than one dpds chunk. */
> +            if (dpds_table) {
> +                av_log(s, AV_LOG_ERROR, "two dpds chunks present\n");
> +                return -1;
> +            }
> +
> +            /* Compute the number of entries in the dpds chunk. */
> +            if (size % 4 != 0) {  /* Size should be divisible by four */
> +                av_log(s, AV_LOG_WARNING, "dpds chunk size %lld not 
> divisible by four\n", size);

IIRC, we use PRId64 macro instead of %lld

> +            }
> +            dpds_table_size = size / 4;
> +
> +            /* Allocate some temporary storage to keep the dpds data around 
> for processing later on. */
> +            dpds_table = av_malloc(dpds_table_size * sizeof(uint32_t));
> +            if (!dpds_table) {
> +                return AVERROR(ENOMEM);
> +            }
> +
> +            /* Read the dpds data, byte-swapping it if needed. */

too obvious comment, it's better to drop it

> +            for (i = 0; i < dpds_table_size; ++i) {
> +                dpds_table[i] = avio_rl32(pb);
> +                size -= 4;
> +            }
> +        }
> +        avio_skip(pb, size);
> +    }
> +
> +    // Determine overall data length
> +    if (size < 0)
> +        return -1;
> +    if (!size) {
> +        xwma->data_end = INT64_MAX;
> +    } else
> +        xwma->data_end = avio_tell(pb) + size;
> +
> +
> +    if (dpds_table && dpds_table_size) {
> +        int64_t cur_pos;
> +        uint64_t total_decoded_bytes;
> +
> +        // Estimate the duration from the total number of decoded output 
> bytes.
> +        total_decoded_bytes = dpds_table[dpds_table_size - 1] << 3;
> +        st->duration = total_decoded_bytes / (st->codec->channels * 
> st->codec->bits_per_coded_sample);
> +
> +        // Use the dpds data to build a seek table.
> +        // We do this after we found the data chunk, so that we can
> +        // figure out all offsets correctly.
> +        cur_pos = avio_tell(pb);
> +        for (i = 0; i < dpds_table_size; ++i) {
> +            // From the number of output bytes that would accumulate
> +            // in the output buffer after decoding the first (i+1) packets,
> +            // we compute a timestamp
> +            int64_t timestamp = dpds_table[i] / (st->codec->channels * 
> (st->codec->bits_per_coded_sample>>3));
> +            av_log(s, AV_LOG_DEBUG, "[%d] offset %lld, timestamp %lld, size 
> %d\n", i, cur_pos + i * st->codec->block_align, timestamp, 
> st->codec->block_align);
> +

please break these lines to be <80 chars long

> +            av_add_index_entry(st, cur_pos + (i+1) * st->codec->block_align,
> +                timestamp, st->codec->block_align, 0, AVINDEX_KEYFRAME);
> +        }
> +    } else if (st->codec->bit_rate) {
> +        // No dpds chunk was present (or only an empty one), so estimate
> +        // the total duration using the average bits per sample and the
> +        // total data length.
> +        st->duration = (size<<3) * st->codec->sample_rate / 
> st->codec->bit_rate;
> +    }
> +
> +    av_free(dpds_table);
> +
> +    return 0;
> +}
> +
> +#define MAX_SIZE 4096
> +
> +static int xwma_read_packet(AVFormatContext *s, AVPacket *pkt)
> +{
> +    int ret, size;
> +    int64_t left;
> +    AVStream *st;
> +    XWMAContext *xwma = s->priv_data;
> +
> +
> +    st = s->streams[0];
> +
> +    left = xwma->data_end - avio_tell(s->pb);
> +    if (left <= 0) {
> +        return AVERROR_EOF;
> +    }
> +
> +    size = MAX_SIZE;
> +    if (st->codec->block_align > 1) {
> +        if (size < st->codec->block_align)
> +            size = st->codec->block_align;
> +        size = (size / st->codec->block_align) * st->codec->block_align;

why? Shouldn't it read exactly one block of information?

> +    }
> +    size = FFMIN(size, left);
> +    ret  = av_get_packet(s->pb, pkt, size);
> +    if (ret < 0)
> +        return ret;
> +
> +    /* Only one stream in all xWMA files I've seen so far. */
> +    pkt->stream_index = 0;
> +    return ret;
> +}
> +
> +AVInputFormat ff_xwma_demuxer = {
> +    "xwma",
> +    NULL_IF_CONFIG_SMALL("Microsoft xWMA"),
> +    sizeof(XWMAContext),
> +    xwma_probe,
> +    xwma_read_header,
> +    xwma_read_packet,
> +};
> -- 
_______________________________________________
libav-devel mailing list
[email protected]
https://lists.libav.org/mailman/listinfo/libav-devel

Reply via email to