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
