[FFmpeg-devel] [PATCH 2/2] avformat: add demuxer for argonaut games' ASF format

Zane van Iperen zane at zanevaniperen.com
Sun Jan 19 09:06:17 EET 2020


19/1/20 2:18 pm, Andreas Rheinhardt пишет:
> 
> On Sun, Jan 19, 2020 at 4:12 AM Zane van Iperen <zane at zanevaniperen.com>
> wrote:
> 
>> Adds support for the custom ASF container used by some Argonaut Games'
>> games, such as 'Croc! Legend of the Gobbos', and 'Croc 2'.
>>
>> Can also handle the sample files in:
>> https://samples.ffmpeg.org/game-formats/brender/part2.zip
>>
>> Signed-off-by: Zane van Iperen <zane at zanevaniperen.com>
>> ---
>>   Changelog                |   1 +
>>   libavformat/Makefile     |   1 +
>>   libavformat/allformats.c |   1 +
>>   libavformat/argo_asf.c   | 254 +++++++++++++++++++++++++++++++++++++++
>>   libavformat/version.h    |   4 +-
>>   5 files changed, 259 insertions(+), 2 deletions(-)
>>   create mode 100644 libavformat/argo_asf.c
>>
>> diff --git a/Changelog b/Changelog
>> index e26320c0ce..bc1593bfd1 100644
>> --- a/Changelog
>> +++ b/Changelog
>> @@ -31,6 +31,7 @@ version <next>:
>>   - thistogram filter
>>   - freezeframes filter
>>   - Argonaut Games ADPCM decoder
>> +- Argonaut Games ASF demuxer
>>
>>   version 4.2:
>>   - tpad filter
>> diff --git a/libavformat/Makefile b/libavformat/Makefile
>> index 52f29b1a6d..ba6ea8c4a6 100644
>> --- a/libavformat/Makefile
>> +++ b/libavformat/Makefile
>> @@ -99,6 +99,7 @@ OBJS-$(CONFIG_APTX_MUXER)                += rawenc.o
>>   OBJS-$(CONFIG_APTX_HD_DEMUXER)           += aptxdec.o rawdec.o
>>   OBJS-$(CONFIG_APTX_HD_MUXER)             += rawenc.o
>>   OBJS-$(CONFIG_AQTITLE_DEMUXER)           += aqtitledec.o subtitles.o
>> +OBJS-$(CONFIG_ARGO_ASF_DEMUXER)          += argo_asf.o
>>   OBJS-$(CONFIG_ASF_DEMUXER)               += asfdec_f.o asf.o asfcrypt.o \
>>                                               avlanguage.o
>>   OBJS-$(CONFIG_ASF_O_DEMUXER)             += asfdec_o.o asf.o asfcrypt.o \
>> diff --git a/libavformat/allformats.c b/libavformat/allformats.c
>> index ff9bdb906f..fe74a32e47 100644
>> --- a/libavformat/allformats.c
>> +++ b/libavformat/allformats.c
>> @@ -60,6 +60,7 @@ extern AVOutputFormat ff_aptx_muxer;
>>   extern AVInputFormat  ff_aptx_hd_demuxer;
>>   extern AVOutputFormat ff_aptx_hd_muxer;
>>   extern AVInputFormat  ff_aqtitle_demuxer;
>> +extern AVInputFormat  ff_argo_asf_demuxer;
>>   extern AVInputFormat  ff_asf_demuxer;
>>   extern AVOutputFormat ff_asf_muxer;
>>   extern AVInputFormat  ff_asf_o_demuxer;
>> diff --git a/libavformat/argo_asf.c b/libavformat/argo_asf.c
>> new file mode 100644
>> index 0000000000..ab8a0955ab
>> --- /dev/null
>> +++ b/libavformat/argo_asf.c
>> @@ -0,0 +1,254 @@
>> +/*
>> + * Argonaut Games ASF demuxer
>> + *
>> + * Copyright (C) 2020 Zane van Iperen (zane at zanevaniperen.com)
>> + *
>> + * 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 "avformat.h"
>> +#include "internal.h"
>> +#include "libavutil/internal.h"
>> +#include "libavutil/bswap.h"
>> +#include "libavutil/avassert.h"
>> +
>> +#pragma pack(push, 1)
>> +typedef struct ArgoASFFileHeader {
>> +    uint8_t     magic[4];       /*< Magic Number, {'A', 'S', 'F', '\0'} */
>> +    uint16_t    version_major;  /*< File Major Version. */
>> +    uint16_t    version_minor;  /*< File Minor Version. */
>> +    uint32_t    num_chunks;     /*< No. chunks in the file. */
>> +    uint32_t    chunk_offset;   /*< Offset to the first chunk from the
>> start of the file. */
>> +    int8_t      name[8];        /*< Name. */
>> +} ArgoASFFileHeader;
>> +
>> +typedef struct ArgoASFChunkHeader {
>> +    uint32_t    num_blocks;     /*< No. blocks in the chunk. */
>> +    uint32_t    num_samples;    /*< No. samples per channel in a block. */
>> +    uint32_t    unk1;           /*< Unknown */
>> +    uint16_t    sample_rate;    /*< Sample rate. */
>> +    uint16_t    unk2;           /*< Unknown. */
>> +    uint32_t    flags;          /*< Stream flags. */
>> +} ArgoASFChunkHeader;
>> +#pragma pack(pop)
>> +
>>
> 
> These pragmas should be removed.
> 
> 

Done.

>> +enum {
>> +    ASF_CF_BITS_PER_SAMPLE  = (1 << 0), /*< 16-bit if set, 8 otherwise.
>>      */
>> +    ASF_CF_STEREO           = (1 << 1), /*< Stereo if set, mono
>> otherwise.   */
>> +    ASF_CF_ALWAYS1_1        = (1 << 2), /*< Unknown, always seems to be
>> set. */
>> +    ASF_CF_ALWAYS1_2        = (1 << 3), /*< Unknown, always seems to be
>> set. */
>> +
>> +    ASF_CF_ALWAYS1          = ASF_CF_ALWAYS1_1 | ASF_CF_ALWAYS1_2,
>> +    ASF_CF_ALWAYS0          = ~(ASF_CF_BITS_PER_SAMPLE | ASF_CF_STEREO |
>> ASF_CF_ALWAYS1)
>> +};
>> +
>> +typedef struct ArgoASFDemuxContext
>> +{
>> +    ArgoASFFileHeader   fhdr;
>> +    ArgoASFChunkHeader  ckhdr;
>> +    uint32_t            blocks_read;
>> +} ArgoASFDemuxContext;
>> +
>> +static void argo_asf_fix_header_endianness(ArgoASFFileHeader *hdr)
>> +{
>> +    hdr->version_major  = av_le2ne16(hdr->version_major);
>> +    hdr->version_minor  = av_le2ne16(hdr->version_minor);
>> +    hdr->num_chunks     = av_le2ne32(hdr->num_chunks);
>> +    hdr->chunk_offset   = av_le2ne32(hdr->chunk_offset);
>> +}
>> +
>> +static void argo_asf_fix_chunk_endianness(ArgoASFChunkHeader *ck)
>> +{
>> +    ck->num_blocks      = av_le2ne32(ck->num_blocks);
>> +    ck->num_samples     = av_le2ne32(ck->num_samples);
>> +    ck->unk1            = av_le2ne32(ck->unk1);
>> +    ck->sample_rate     = av_le2ne16(ck->sample_rate);
>> +    ck->unk2            = av_le2ne32(ck->unk2);
>> +    ck->flags           = av_le2ne32(ck->flags);
>> +}
>> +
>> +/*
>> + * Known versions:
>> + * 1.1: The sample files in /game-formats/brender/part2.zip
>> + * 1.2: Croc! Legend of the Gobbos
>> + * 2.1: Croc 2
>> + */
>> +static int argo_asf_is_known_version(const ArgoASFFileHeader *hdr)
>> +{
>> +    return (hdr->version_major == 1 && hdr->version_minor == 1) ||
>> +           (hdr->version_major == 1 && hdr->version_minor == 2) ||
>> +           (hdr->version_major == 2 && hdr->version_minor == 1);
>> +}
>> +
>> +static int argo_asf_probe(const AVProbeData *p)
>> +{
>> +    int score;
>> +    ArgoASFFileHeader hdr;
>> +
>> +    av_assert0(sizeof(ArgoASFFileHeader) == 24);
>> +    av_assert0(sizeof(ArgoASFChunkHeader) == 20);
>> +
>>
> 
> These asserts should be removed, too.

Done.

> 
>> +    if (p->buf_size < sizeof(ArgoASFFileHeader))
>>
> 
> Instead of relying on the compiler not adding padding you should rather use
> a #define for the size of the header as it exists in the file and compare
> with that.
> 

Done.

> 
>> +        return AVPROBE_SCORE_RETRY;
>> +
>> +    if (p->buf[0] != 'A' || p->buf[1] != 'S' ||
>> +        p->buf[2] != 'F' || p->buf[3] != '\0')
>> +        return 0;
>>
> 
> I am sure this can be simplified with one of our macros for creating tags.
> 

Changed to use MKTAG()

> 
>> +
>> +    score = 0;
>> +    if (av_match_ext(p->filename, "asf"))
>> +        score += AVPROBE_SCORE_EXTENSION;
>> +
>> +    hdr = *(ArgoASFFileHeader*)p->buf;
>> +    argo_asf_fix_header_endianness(&hdr);
>> +
>>
> 
> You are modifying the probe buffer. I don't think you are supposed to do
> that (although the AVProbeData,buf is not a pointer to const uint8_t).
> Instead read what you need with AV_RL from the buffer.
> 
hdr is a stack variable. I'm simply assigning it from the buffer, so nothing's being changed.
It's a moot point anyway, since I've changed it to use AV_RL.

> 
>> +    if (argo_asf_is_known_version(&hdr))
>> +        score += 25;
>> +
>> +    /* Have only ever seen these with 1 chunk. */
>> +    if (hdr.num_chunks == 1)
>> +        score += 25;
>> +
>> +    if (score > AVPROBE_SCORE_MAX)
>> +        score = AVPROBE_SCORE_MAX;
>> +
>> +    return score;
>> +}
>> +
>> +static int argo_asf_read_header(AVFormatContext *s)
>> +{
>> +    int ret;
>> +    AVIOContext *pb = s->pb;
>> +    AVStream *st;
>> +    ArgoASFDemuxContext *asf = s->priv_data;
>> +
>> +    st = avformat_new_stream(s, NULL);
>> +    if (!st)
>> +        return AVERROR(ENOMEM);
>> +
>> +    if (avio_read(pb, (unsigned char*)&asf->fhdr, sizeof(asf->fhdr)) !=
>> sizeof(asf->fhdr))
>> +        return AVERROR(EIO);
>> +
>> +    argo_asf_fix_header_endianness(&asf->fhdr);
>>
> 
> Instead of relying on the availability of the pack pragma and fixing up the
> endianness afterwards you should read into a scratch buffer on the stack
> and then populate the header struct by reading the buffer with AV_RLxx,
> 

Done.

> 
> 
>> +
>> +    if (!argo_asf_is_known_version(&asf->fhdr)) {
>> +        avpriv_request_sample(s, "Version %hu.%hu",
>> +            asf->fhdr.version_major, asf->fhdr.version_minor
>> +        );
>> +        return AVERROR_PATCHWELCOME;
>> +    }
>> +
>> +    if ((ret = avio_seek(pb, asf->fhdr.chunk_offset, SEEK_SET)) < 0)
>>
> 
> avio_seek() returns an int64_t. If the chunk_offset > INT_MAX (it is an
> uint32_t after all), the seek could succeed, yet it would appear as failure
> after conversion to an int like ret. (Btw: You appear to rely on the
> AVIOContext to be at offset zero initially. A relative seek would not
> depend on this.)
> 

Fixed, and done.

> (Furthermore, maybe it would be a good idea to decrease the score in the
> probing function if this offset here is gigantic?)
> 

Good idea, done.

> 
>> +        return ret;
>> +
>> +    argo_asf_fix_chunk_endianness(&asf->ckhdr);
>>
> 
> Putting this here is useless as nothing has been read into the chunk header
> at all at this moment.
> Of course, you should not move this below, but rather use AV_RL to parse
> what you have read.

Oops, that was meant to be below. I'll change it to use AV_RL.

> 
>> +
>> +    if (avio_read(pb, (unsigned char*)&asf->ckhdr, sizeof(asf->ckhdr)) !=
>> sizeof(asf->ckhdr))
>>
> 
> As above: #define instead of sizeof and don't read directly into asf->ckhdr.
> 

Done.

>>
>> +        return AVERROR(EIO);
>> +
>> +    if ((asf->ckhdr.flags & ASF_CF_ALWAYS1) != ASF_CF_ALWAYS1 ||
>> (asf->ckhdr.flags & ASF_CF_ALWAYS0) != 0) {
>> +        avpriv_request_sample(s, "Nonstandard flags (0x%08X)",
>> asf->ckhdr.flags);
>> +        return AVERROR_PATCHWELCOME;
>> +    }
>> +
>> +    st->codecpar->codec_type                = AVMEDIA_TYPE_AUDIO;
>> +    st->codecpar->codec_id                  = AV_CODEC_ID_ADPCM_ARGO;
>> +    st->codecpar->format                    = AV_SAMPLE_FMT_S16;
>> +
>> +    if (asf->ckhdr.flags & ASF_CF_STEREO) {
>> +        st->codecpar->channel_layout        = AV_CH_LAYOUT_STEREO;
>> +        st->codecpar->channels              = 2;
>> +    } else {
>> +        st->codecpar->channel_layout        = AV_CH_LAYOUT_MONO;
>> +        st->codecpar->channels              = 1;
>> +    }
>> +
>> +    st->codecpar->sample_rate               = asf->ckhdr.sample_rate;
>> +
>> +    st->codecpar->bits_per_coded_sample     = 4;
>> +
>> +    if (asf->ckhdr.flags & ASF_CF_BITS_PER_SAMPLE)
>> +        st->codecpar->bits_per_raw_sample   = 16;
>> +    else
>> +        st->codecpar->bits_per_raw_sample   = 8;
>> +
>> +    if (st->codecpar->bits_per_raw_sample != 16) {
>> +        /* The header allows for these, but I've never seen any files
>> with them. */
>> +        avpriv_request_sample(s, "Non 16-bit samples");
>> +        return AVERROR_PATCHWELCOME;
>> +    }
>> +
>> +    /*
>> +     * (nchannel control bytes) + ((bytes_per_channel) * nchannel)
>> +     * For mono, this is 17. For stereo, this is 34.
>> +     */
>> +    st->codecpar->frame_size            = st->codecpar->channels +
>> +                                          (asf->ckhdr.num_samples / 2) *
>> +                                          st->codecpar->channels;
>> +
>> +    st->codecpar->block_align           = st->codecpar->frame_size;
>> +
>> +    st->codecpar->bit_rate              = st->codecpar->channels *
>> +                                          st->codecpar->sample_rate *
>> +
>> st->codecpar->bits_per_coded_sample;
>> +
>> +    avpriv_set_pts_info(st, 64, 1, st->codecpar->sample_rate);
>> +    st->start_time      = 0;
>> +    st->duration        = asf->ckhdr.num_blocks * asf->ckhdr.num_samples;
>> +    st->nb_frames       = asf->ckhdr.num_blocks;
>> +
>> +    asf->blocks_read    = 0;
>>
> 
> Unnecessary as asf will have been zeroed before this function.
> 

Done.

> 
>> +    return 0;
>> +}
>> +
>> +static int argo_asf_read_packet(AVFormatContext *s, AVPacket *pkt)
>> +{
>> +    ArgoASFDemuxContext *asf = s->priv_data;
>> +
>> +    AVStream *st = s->streams[0];
>> +    AVIOContext *pb = s->pb;
>> +    int ret;
>> +
>> +    if (asf->blocks_read >= asf->ckhdr.num_blocks)
>> +        return AVERROR_EOF;
>> +
>> +    ret = av_get_packet(pb, pkt, st->codecpar->frame_size);
>> +    if (ret < 0)
>> +        return ret;
>> +
>> +    if (ret != st->codecpar->frame_size)
>> +        return AVERROR_INVALIDDATA;
>> +
>> +    pkt->stream_index   = st->index;
>> +    pkt->duration       = asf->ckhdr.num_samples;
>> +
>> +    ++asf->blocks_read;
>> +    return ret;
>>
> 
> You should just return 0.

Done

> 
>> +}
>> +
>> +/*
>> + * Not actually sure what ASF stands for.
>> + * - Argonaut Sound File?
>> + * - Audio Stream File?
>> + */
>> +AVInputFormat ff_argo_asf_demuxer = {
>> +    .name           = "argo_asf",
>> +    .long_name      = NULL_IF_CONFIG_SMALL("Argonaut Games ASF"),
>> +    .priv_data_size = sizeof(ArgoASFDemuxContext),
>> +    .read_probe     = argo_asf_probe,
>> +    .read_header    = argo_asf_read_header,
>> +    .read_packet    = argo_asf_read_packet
>> +};
>> \ No newline at end of file
>>
> 
> Never ever seen this message here. I presume it is from git diff (or git
> format-patch?) or so. IIRC C source files need to end with a newline, so
> just add it.
> 

Yep, that's my bad. Fixed.

> 
>> diff --git a/libavformat/version.h b/libavformat/version.h
>> index 0a79868663..f72fb9478a 100644
>> --- a/libavformat/version.h
>> +++ b/libavformat/version.h
>> @@ -32,8 +32,8 @@
>>   // Major bumping may affect Ticket5467, 5421, 5451(compatibility with
>> Chromium)
>>   // Also please add any ticket numbers that you believe might be affected
>> here
>>   #define LIBAVFORMAT_VERSION_MAJOR  58
>> -#define LIBAVFORMAT_VERSION_MINOR  35
>> -#define LIBAVFORMAT_VERSION_MICRO 103
>> +#define LIBAVFORMAT_VERSION_MINOR  36
>> +#define LIBAVFORMAT_VERSION_MICRO 100
>>
>>   #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR,
>> \
>>                                                  LIBAVFORMAT_VERSION_MINOR,
>> \
>>
>>
> - Andreas
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> 
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request at ffmpeg.org with subject "unsubscribe".
> 


Do I need to submit only the revised commit or do I resend the entire series as a "v2"?

Also, should I include any additional headers such as "Reviewed-by"?

Thanks,
Zane



More information about the ffmpeg-devel mailing list