#include "internal.h"
#include "libavutil/intreadwrite.h"
#include "libavutil/avassert.h"
+#include "libavutil/opt.h"
+#include "argo_asf.h"
-#define ASF_TAG MKTAG('A', 'S', 'F', '\0')
-#define ASF_FILE_HEADER_SIZE 24
-#define ASF_CHUNK_HEADER_SIZE 20
-#define ASF_SAMPLE_COUNT 32
-
-typedef struct ArgoASFFileHeader {
- uint32_t magic; /*< 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. Always 32. */
- uint32_t unk1; /*< Unknown */
- uint16_t sample_rate; /*< Sample rate. */
- uint16_t unk2; /*< Unknown. */
- uint32_t flags; /*< Stream flags. */
-} ArgoASFChunkHeader;
-
-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)
-};
+/* Maximum number of blocks to read at once. */
+#define ASF_NB_BLOCKS 32
typedef struct ArgoASFDemuxContext {
ArgoASFFileHeader fhdr;
uint32_t blocks_read;
} ArgoASFDemuxContext;
-#if CONFIG_ARGO_ASF_DEMUXER
-static void argo_asf_parse_file_header(ArgoASFFileHeader *hdr, const uint8_t *buf)
+typedef struct ArgoASFMuxContext {
+ const AVClass *class;
+ int version_major;
+ int version_minor;
+ const char *name;
+ int64_t nb_blocks;
+} ArgoASFMuxContext;
+
+void ff_argo_asf_parse_file_header(ArgoASFFileHeader *hdr, const uint8_t *buf)
{
hdr->magic = AV_RL32(buf + 0);
hdr->version_major = AV_RL16(buf + 4);
hdr->name[i] = AV_RL8(buf + 16 + i);
}
-static void argo_asf_parse_chunk_header(ArgoASFChunkHeader *hdr, const uint8_t *buf)
+int ff_argo_asf_validate_file_header(AVFormatContext *s, const ArgoASFFileHeader *hdr)
+{
+ if (hdr->magic != ASF_TAG || hdr->num_chunks == 0)
+ return AVERROR_INVALIDDATA;
+
+ if (hdr->chunk_offset < ASF_FILE_HEADER_SIZE)
+ return AVERROR_INVALIDDATA;
+
+ return 0;
+}
+
+void ff_argo_asf_parse_chunk_header(ArgoASFChunkHeader *hdr, const uint8_t *buf)
{
hdr->num_blocks = AV_RL32(buf + 0);
hdr->num_samples = AV_RL32(buf + 4);
hdr->flags = AV_RL32(buf + 16);
}
+int ff_argo_asf_fill_stream(AVFormatContext *s, AVStream *st, const ArgoASFFileHeader *fhdr,
+ const ArgoASFChunkHeader *ckhdr)
+{
+ if (ckhdr->num_samples != ASF_SAMPLE_COUNT) {
+ av_log(s, AV_LOG_ERROR, "Invalid sample count. Got %u, expected %d\n",
+ ckhdr->num_samples, ASF_SAMPLE_COUNT);
+ return AVERROR_INVALIDDATA;
+ }
+
+ if ((ckhdr->flags & ASF_CF_ALWAYS1) != ASF_CF_ALWAYS1 || (ckhdr->flags & ASF_CF_ALWAYS0) != 0) {
+ avpriv_request_sample(s, "Nonstandard flags (0x%08X)", 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_S16P;
+
+ if (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;
+ }
+
+ /* v1.1 files (FX Fighter) are all marked as 44100, but are actually 22050. */
+ if (fhdr->version_major == 1 && fhdr->version_minor == 1)
+ st->codecpar->sample_rate = 22050;
+ else
+ st->codecpar->sample_rate = ckhdr->sample_rate;
+
+ st->codecpar->bits_per_coded_sample = 4;
+
+ if (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->block_align = st->codecpar->channels +
+ (ckhdr->num_samples / 2) *
+ st->codecpar->channels;
+
+ 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;
+
+ if (fhdr->num_chunks == 1) {
+ st->duration = ckhdr->num_blocks * ckhdr->num_samples;
+ st->nb_frames = ckhdr->num_blocks;
+ }
+
+ return 0;
+}
+
+#if CONFIG_ARGO_ASF_DEMUXER
/*
* Known versions:
- * 1.1: The sample files in /game-formats/brender/part2.zip
+ * 1.1: https://samples.ffmpeg.org/game-formats/brender/part2.zip
+ * FX Fighter
* 1.2: Croc! Legend of the Gobbos
* 2.1: Croc 2
+ * The Emperor's New Groove
+ * Disney's Aladdin in Nasira's Revenge
*/
static int argo_asf_is_known_version(const ArgoASFFileHeader *hdr)
{
av_assert0(AVPROBE_PADDING_SIZE >= ASF_FILE_HEADER_SIZE);
- argo_asf_parse_file_header(&hdr, p->buf);
+ ff_argo_asf_parse_file_header(&hdr, p->buf);
if (hdr.magic != ASF_TAG)
return 0;
AVIOContext *pb = s->pb;
AVStream *st;
ArgoASFDemuxContext *asf = s->priv_data;
- uint8_t buf[FFMAX(ASF_FILE_HEADER_SIZE, ASF_CHUNK_HEADER_SIZE)];
+ uint8_t buf[ASF_MIN_BUFFER_SIZE];
if (!(st = avformat_new_stream(s, NULL)))
return AVERROR(ENOMEM);
else if (ret != ASF_FILE_HEADER_SIZE)
return AVERROR(EIO);
- argo_asf_parse_file_header(&asf->fhdr, buf);
-
- 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;
- }
+ ff_argo_asf_parse_file_header(&asf->fhdr, buf);
- if (asf->fhdr.num_chunks == 0) {
- return AVERROR_INVALIDDATA;
- } else if (asf->fhdr.num_chunks > 1) {
- avpriv_request_sample(s, ">1 chunk");
- return AVERROR_PATCHWELCOME;
- }
+ if ((ret = ff_argo_asf_validate_file_header(s, &asf->fhdr)) < 0)
+ return ret;
- if (asf->fhdr.chunk_offset < ASF_FILE_HEADER_SIZE)
+ /* This should only be 1 in ASF files. >1 is fine if in BRP. */
+ if (asf->fhdr.num_chunks != 1)
return AVERROR_INVALIDDATA;
if ((ret = avio_skip(pb, asf->fhdr.chunk_offset - ASF_FILE_HEADER_SIZE)) < 0)
else if (ret != ASF_CHUNK_HEADER_SIZE)
return AVERROR(EIO);
- argo_asf_parse_chunk_header(&asf->ckhdr, buf);
-
- if (asf->ckhdr.num_samples != ASF_SAMPLE_COUNT) {
- av_log(s, AV_LOG_ERROR, "Invalid sample count. Got %u, expected %d\n",
- asf->ckhdr.num_samples, ASF_SAMPLE_COUNT);
- return AVERROR_INVALIDDATA;
- }
-
- 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_S16P;
+ ff_argo_asf_parse_chunk_header(&asf->ckhdr, buf);
- 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;
- return 0;
+ return ff_argo_asf_fill_stream(s, st, &asf->fhdr, &asf->ckhdr);
}
static int argo_asf_read_packet(AVFormatContext *s, AVPacket *pkt)
if (asf->blocks_read >= asf->ckhdr.num_blocks)
return AVERROR_EOF;
- if ((ret = av_get_packet(pb, pkt, st->codecpar->frame_size)) < 0)
+ ret = av_get_packet(pb, pkt, st->codecpar->block_align *
+ FFMIN(ASF_NB_BLOCKS, asf->ckhdr.num_blocks - asf->blocks_read));
+ if (ret < 0)
return ret;
- else if (ret != st->codecpar->frame_size)
+
+ /* Something real screwy is going on. */
+ if (ret % st->codecpar->block_align != 0)
return AVERROR_INVALIDDATA;
+
pkt->stream_index = st->index;
- pkt->duration = asf->ckhdr.num_samples;
+ pkt->duration = asf->ckhdr.num_samples * (ret / st->codecpar->block_align);
+ pkt->pts = asf->blocks_read * asf->ckhdr.num_samples;
+ asf->blocks_read += (ret / st->codecpar->block_align);
+
+ pkt->flags &= ~AV_PKT_FLAG_CORRUPT;
+ return 0;
+}
+
+static int argo_asf_seek(AVFormatContext *s, int stream_index,
+ int64_t pts, int flags)
+{
+ ArgoASFDemuxContext *asf = s->priv_data;
+ AVStream *st = s->streams[stream_index];
+ int64_t offset;
+ uint32_t block = pts / asf->ckhdr.num_samples;
+
+ if (block >= asf->ckhdr.num_blocks)
+ return -1;
- ++asf->blocks_read;
+ offset = asf->fhdr.chunk_offset + ASF_CHUNK_HEADER_SIZE +
+ (block * st->codecpar->block_align);
+
+ if ((offset = avio_seek(s->pb, offset, SEEK_SET)) < 0)
+ return offset;
+
+ asf->blocks_read = block;
return 0;
}
* - Argonaut Sound File?
* - Audio Stream File?
*/
-AVInputFormat ff_argo_asf_demuxer = {
+const 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
+ .read_packet = argo_asf_read_packet,
+ .read_seek = argo_asf_seek,
};
#endif
#if CONFIG_ARGO_ASF_MUXER
static int argo_asf_write_init(AVFormatContext *s)
{
+ ArgoASFMuxContext *ctx = s->priv_data;
const AVCodecParameters *par;
if (s->nb_streams != 1) {
return AVERROR(EINVAL);
}
+ if (ctx->version_major == 1 && ctx->version_minor == 1 && par->sample_rate != 22050) {
+ av_log(s, AV_LOG_ERROR, "ASF v1.1 files only support a sample rate of 22050\n");
+ return AVERROR(EINVAL);
+ }
+
if (par->channels > 2) {
av_log(s, AV_LOG_ERROR, "ASF files only support up to 2 channels\n");
return AVERROR(EINVAL);
}
+ if (par->block_align != 17 * par->channels)
+ return AVERROR(EINVAL);
+
if (par->sample_rate > UINT16_MAX) {
av_log(s, AV_LOG_ERROR, "Sample rate too large\n");
return AVERROR(EINVAL);
static int argo_asf_write_header(AVFormatContext *s)
{
const AVCodecParameters *par = s->streams[0]->codecpar;
- ArgoASFFileHeader fhdr;
+ ArgoASFMuxContext *ctx = s->priv_data;
ArgoASFChunkHeader chdr;
+ ArgoASFFileHeader fhdr = {
+ .magic = ASF_TAG,
+ .version_major = (uint16_t)ctx->version_major,
+ .version_minor = (uint16_t)ctx->version_minor,
+ .num_chunks = 1,
+ .chunk_offset = ASF_FILE_HEADER_SIZE
+ };
- fhdr.magic = ASF_TAG;
- fhdr.version_major = 2;
- fhdr.version_minor = 1;
- fhdr.num_chunks = 1;
- fhdr.chunk_offset = ASF_FILE_HEADER_SIZE;
- strncpy(fhdr.name, av_basename(s->url), FF_ARRAY_ELEMS(fhdr.name));
+ /*
+ * If the user specified a name, use it as is. Otherwise take the
+ * basename and lop off the extension (if any).
+ */
+ if (ctx->name) {
+ strncpy(fhdr.name, ctx->name, sizeof(fhdr.name));
+ } else {
+ const char *start = av_basename(s->url);
+ const char *end = strrchr(start, '.');
+ size_t len;
+
+ if (end)
+ len = end - start;
+ else
+ len = strlen(start);
+
+ memcpy(fhdr.name, start, FFMIN(len, sizeof(fhdr.name)));
+ }
chdr.num_blocks = 0;
chdr.num_samples = ASF_SAMPLE_COUNT;
chdr.unk1 = 0;
- chdr.sample_rate = par->sample_rate;
+
+ if (ctx->version_major == 1 && ctx->version_minor == 1)
+ chdr.sample_rate = 44100;
+ else
+ chdr.sample_rate = par->sample_rate;
+
chdr.unk2 = ~0;
chdr.flags = ASF_CF_BITS_PER_SAMPLE | ASF_CF_ALWAYS1;
static int argo_asf_write_packet(AVFormatContext *s, AVPacket *pkt)
{
- if (pkt->size != 17 * s->streams[0]->codecpar->channels)
+ ArgoASFMuxContext *ctx = s->priv_data;
+ AVCodecParameters *par = s->streams[0]->codecpar;
+ int nb_blocks = pkt->size / par->block_align;
+
+ if (pkt->size % par->block_align != 0)
return AVERROR_INVALIDDATA;
- if (s->streams[0]->nb_frames >= UINT32_MAX)
+ if (ctx->nb_blocks + nb_blocks > UINT32_MAX)
return AVERROR_INVALIDDATA;
avio_write(s->pb, pkt->data, pkt->size);
+
+ ctx->nb_blocks += nb_blocks;
return 0;
}
static int argo_asf_write_trailer(AVFormatContext *s)
{
+ ArgoASFMuxContext *ctx = s->priv_data;
int64_t ret;
if ((ret = avio_seek(s->pb, ASF_FILE_HEADER_SIZE, SEEK_SET) < 0))
return ret;
- avio_wl32(s->pb, (uint32_t)s->streams[0]->nb_frames);
+ avio_wl32(s->pb, (uint32_t)ctx->nb_blocks);
return 0;
}
-AVOutputFormat ff_argo_asf_muxer = {
+static const AVOption argo_asf_options[] = {
+ {
+ .name = "version_major",
+ .help = "override file major version",
+ .offset = offsetof(ArgoASFMuxContext, version_major),
+ .type = AV_OPT_TYPE_INT,
+ .default_val = {.i64 = 2},
+ .min = 0,
+ .max = UINT16_MAX,
+ .flags = AV_OPT_FLAG_ENCODING_PARAM
+ },
+ {
+ .name = "version_minor",
+ .help = "override file minor version",
+ .offset = offsetof(ArgoASFMuxContext, version_minor),
+ .type = AV_OPT_TYPE_INT,
+ .default_val = {.i64 = 1},
+ .min = 0,
+ .max = UINT16_MAX,
+ .flags = AV_OPT_FLAG_ENCODING_PARAM
+ },
+ {
+ .name = "name",
+ .help = "embedded file name (max 8 characters)",
+ .offset = offsetof(ArgoASFMuxContext, name),
+ .type = AV_OPT_TYPE_STRING,
+ .default_val = {.str = NULL},
+ .flags = AV_OPT_FLAG_ENCODING_PARAM
+ },
+ { NULL }
+};
+
+static const AVClass argo_asf_muxer_class = {
+ .class_name = "argo_asf_muxer",
+ .item_name = av_default_item_name,
+ .option = argo_asf_options,
+ .version = LIBAVUTIL_VERSION_INT
+};
+
+const AVOutputFormat ff_argo_asf_muxer = {
.name = "argo_asf",
.long_name = NULL_IF_CONFIG_SMALL("Argonaut Games ASF"),
/*
.init = argo_asf_write_init,
.write_header = argo_asf_write_header,
.write_packet = argo_asf_write_packet,
- .write_trailer = argo_asf_write_trailer
+ .write_trailer = argo_asf_write_trailer,
+ .priv_class = &argo_asf_muxer_class,
+ .priv_data_size = sizeof(ArgoASFMuxContext)
};
#endif