]> git.sesse.net Git - ffmpeg/blob - libavformat/argo_asf.c
avformat/argo_asf: split functionality into a header
[ffmpeg] / libavformat / argo_asf.c
1 /*
2  * Argonaut Games ASF (de)muxer
3  *
4  * Copyright (C) 2020 Zane van Iperen (zane@zanevaniperen.com)
5  *
6  * This file is part of FFmpeg.
7  *
8  * FFmpeg is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * FFmpeg is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with FFmpeg; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 #include "avformat.h"
23 #include "internal.h"
24 #include "libavutil/intreadwrite.h"
25 #include "libavutil/avassert.h"
26 #include "libavutil/opt.h"
27 #include "argo_asf.h"
28
29 typedef struct ArgoASFDemuxContext {
30     ArgoASFFileHeader   fhdr;
31     ArgoASFChunkHeader  ckhdr;
32     uint32_t            blocks_read;
33 } ArgoASFDemuxContext;
34
35 typedef struct ArgoASFMuxContext {
36     const AVClass *class;
37     int            version_major;
38     int            version_minor;
39     const char    *name;
40 } ArgoASFMuxContext;
41
42 void ff_argo_asf_parse_file_header(ArgoASFFileHeader *hdr, const uint8_t *buf)
43 {
44     hdr->magic          = AV_RL32(buf + 0);
45     hdr->version_major  = AV_RL16(buf + 4);
46     hdr->version_minor  = AV_RL16(buf + 6);
47     hdr->num_chunks     = AV_RL32(buf + 8);
48     hdr->chunk_offset   = AV_RL32(buf + 12);
49     for (int i = 0; i < FF_ARRAY_ELEMS(hdr->name); i++)
50         hdr->name[i]    = AV_RL8(buf + 16 + i);
51 }
52
53 int ff_argo_asf_validate_file_header(AVFormatContext *s, const ArgoASFFileHeader *hdr)
54 {
55     if (hdr->magic != ASF_TAG || hdr->num_chunks == 0)
56         return AVERROR_INVALIDDATA;
57
58     if (hdr->num_chunks > 1) {
59         avpriv_request_sample(s, ">1 chunk");
60         return AVERROR_PATCHWELCOME;
61     }
62
63     if (hdr->chunk_offset < ASF_FILE_HEADER_SIZE)
64         return AVERROR_INVALIDDATA;
65
66     return 0;
67 }
68
69 void ff_argo_asf_parse_chunk_header(ArgoASFChunkHeader *hdr, const uint8_t *buf)
70 {
71     hdr->num_blocks     = AV_RL32(buf + 0);
72     hdr->num_samples    = AV_RL32(buf + 4);
73     hdr->unk1           = AV_RL32(buf + 8);
74     hdr->sample_rate    = AV_RL16(buf + 12);
75     hdr->unk2           = AV_RL16(buf + 14);
76     hdr->flags          = AV_RL32(buf + 16);
77 }
78
79 int ff_argo_asf_fill_stream(AVStream *st, const ArgoASFFileHeader *fhdr,
80                             const ArgoASFChunkHeader *ckhdr)
81 {
82     if (ckhdr->num_samples != ASF_SAMPLE_COUNT) {
83         av_log(st, AV_LOG_ERROR, "Invalid sample count. Got %u, expected %d\n",
84                ckhdr->num_samples, ASF_SAMPLE_COUNT);
85         return AVERROR_INVALIDDATA;
86     }
87
88     if ((ckhdr->flags & ASF_CF_ALWAYS1) != ASF_CF_ALWAYS1 || (ckhdr->flags & ASF_CF_ALWAYS0) != 0) {
89         avpriv_request_sample(st, "Nonstandard flags (0x%08X)", ckhdr->flags);
90         return AVERROR_PATCHWELCOME;
91     }
92
93     st->codecpar->codec_type                = AVMEDIA_TYPE_AUDIO;
94     st->codecpar->codec_id                  = AV_CODEC_ID_ADPCM_ARGO;
95     st->codecpar->format                    = AV_SAMPLE_FMT_S16P;
96
97     if (ckhdr->flags & ASF_CF_STEREO) {
98         st->codecpar->channel_layout        = AV_CH_LAYOUT_STEREO;
99         st->codecpar->channels              = 2;
100     } else {
101         st->codecpar->channel_layout        = AV_CH_LAYOUT_MONO;
102         st->codecpar->channels              = 1;
103     }
104
105     /* v1.1 files (FX Fighter) are all marked as 44100, but are actually 22050. */
106     if (fhdr->version_major == 1 && fhdr->version_minor == 1)
107         st->codecpar->sample_rate           = 22050;
108     else
109         st->codecpar->sample_rate           = ckhdr->sample_rate;
110
111     st->codecpar->bits_per_coded_sample     = 4;
112
113     if (ckhdr->flags & ASF_CF_BITS_PER_SAMPLE)
114         st->codecpar->bits_per_raw_sample   = 16;
115     else
116         st->codecpar->bits_per_raw_sample   = 8;
117
118     if (st->codecpar->bits_per_raw_sample != 16) {
119         /* The header allows for these, but I've never seen any files with them. */
120         avpriv_request_sample(st, "Non 16-bit samples");
121         return AVERROR_PATCHWELCOME;
122     }
123
124     /*
125      * (nchannel control bytes) + ((bytes_per_channel) * nchannel)
126      * For mono, this is 17. For stereo, this is 34.
127      */
128     st->codecpar->frame_size            = st->codecpar->channels +
129                                           (ckhdr->num_samples / 2) *
130                                           st->codecpar->channels;
131
132     st->codecpar->block_align           = st->codecpar->frame_size;
133
134     st->codecpar->bit_rate              = st->codecpar->channels *
135                                           st->codecpar->sample_rate *
136                                           st->codecpar->bits_per_coded_sample;
137
138     avpriv_set_pts_info(st, 64, 1, st->codecpar->sample_rate);
139     st->start_time      = 0;
140     st->duration        = ckhdr->num_blocks * ckhdr->num_samples;
141     st->nb_frames       = ckhdr->num_blocks;
142     return 0;
143 }
144
145 #if CONFIG_ARGO_ASF_DEMUXER
146 /*
147  * Known versions:
148  * 1.1: https://samples.ffmpeg.org/game-formats/brender/part2.zip
149  *      FX Fighter
150  * 1.2: Croc! Legend of the Gobbos
151  * 2.1: Croc 2
152  *      The Emperor's New Groove
153  *      Disney's Aladdin in Nasira's Revenge
154  */
155 static int argo_asf_is_known_version(const ArgoASFFileHeader *hdr)
156 {
157     return (hdr->version_major == 1 && hdr->version_minor == 1) ||
158            (hdr->version_major == 1 && hdr->version_minor == 2) ||
159            (hdr->version_major == 2 && hdr->version_minor == 1);
160 }
161
162 static int argo_asf_probe(const AVProbeData *p)
163 {
164     ArgoASFFileHeader hdr;
165
166     av_assert0(AVPROBE_PADDING_SIZE >= ASF_FILE_HEADER_SIZE);
167
168     ff_argo_asf_parse_file_header(&hdr, p->buf);
169
170     if (hdr.magic != ASF_TAG)
171         return 0;
172
173     if (!argo_asf_is_known_version(&hdr))
174         return AVPROBE_SCORE_EXTENSION / 2;
175
176     return AVPROBE_SCORE_EXTENSION + 1;
177 }
178
179 static int argo_asf_read_header(AVFormatContext *s)
180 {
181     int64_t ret;
182     AVIOContext *pb = s->pb;
183     AVStream *st;
184     ArgoASFDemuxContext *asf = s->priv_data;
185     uint8_t buf[FFMAX(ASF_FILE_HEADER_SIZE, ASF_CHUNK_HEADER_SIZE)];
186
187     if (!(st = avformat_new_stream(s, NULL)))
188         return AVERROR(ENOMEM);
189
190     if ((ret = avio_read(pb, buf, ASF_FILE_HEADER_SIZE)) < 0)
191         return ret;
192     else if (ret != ASF_FILE_HEADER_SIZE)
193         return AVERROR(EIO);
194
195     ff_argo_asf_parse_file_header(&asf->fhdr, buf);
196
197     if ((ret = ff_argo_asf_validate_file_header(s, &asf->fhdr)) < 0)
198         return ret;
199
200     if ((ret = avio_skip(pb, asf->fhdr.chunk_offset - ASF_FILE_HEADER_SIZE)) < 0)
201         return ret;
202
203     if ((ret = avio_read(pb, buf, ASF_CHUNK_HEADER_SIZE)) < 0)
204         return ret;
205     else if (ret != ASF_CHUNK_HEADER_SIZE)
206         return AVERROR(EIO);
207
208     ff_argo_asf_parse_chunk_header(&asf->ckhdr, buf);
209
210     return ff_argo_asf_fill_stream(st, &asf->fhdr, &asf->ckhdr);
211 }
212
213 static int argo_asf_read_packet(AVFormatContext *s, AVPacket *pkt)
214 {
215     ArgoASFDemuxContext *asf = s->priv_data;
216
217     AVStream *st = s->streams[0];
218     AVIOContext *pb = s->pb;
219     int ret;
220
221     if (asf->blocks_read >= asf->ckhdr.num_blocks)
222         return AVERROR_EOF;
223
224     if ((ret = av_get_packet(pb, pkt, st->codecpar->frame_size)) < 0)
225         return ret;
226     else if (ret != st->codecpar->frame_size)
227         return AVERROR_INVALIDDATA;
228
229     pkt->stream_index   = st->index;
230     pkt->duration       = asf->ckhdr.num_samples;
231
232     ++asf->blocks_read;
233     return 0;
234 }
235
236 /*
237  * Not actually sure what ASF stands for.
238  * - Argonaut Sound File?
239  * - Audio Stream File?
240  */
241 AVInputFormat ff_argo_asf_demuxer = {
242     .name           = "argo_asf",
243     .long_name      = NULL_IF_CONFIG_SMALL("Argonaut Games ASF"),
244     .priv_data_size = sizeof(ArgoASFDemuxContext),
245     .read_probe     = argo_asf_probe,
246     .read_header    = argo_asf_read_header,
247     .read_packet    = argo_asf_read_packet
248 };
249 #endif
250
251 #if CONFIG_ARGO_ASF_MUXER
252 static int argo_asf_write_init(AVFormatContext *s)
253 {
254     ArgoASFMuxContext *ctx = s->priv_data;
255     const AVCodecParameters *par;
256
257     if (s->nb_streams != 1) {
258         av_log(s, AV_LOG_ERROR, "ASF files have exactly one stream\n");
259         return AVERROR(EINVAL);
260     }
261
262     par = s->streams[0]->codecpar;
263
264     if (par->codec_id != AV_CODEC_ID_ADPCM_ARGO) {
265         av_log(s, AV_LOG_ERROR, "%s codec not supported\n",
266                avcodec_get_name(par->codec_id));
267         return AVERROR(EINVAL);
268     }
269
270     if (ctx->version_major == 1 && ctx->version_minor == 1 && par->sample_rate != 22050) {
271         av_log(s, AV_LOG_ERROR, "ASF v1.1 files only support a sample rate of 22050\n");
272         return AVERROR(EINVAL);
273     }
274
275     if (par->channels > 2) {
276         av_log(s, AV_LOG_ERROR, "ASF files only support up to 2 channels\n");
277         return AVERROR(EINVAL);
278     }
279
280     if (par->sample_rate > UINT16_MAX) {
281         av_log(s, AV_LOG_ERROR, "Sample rate too large\n");
282         return AVERROR(EINVAL);
283     }
284
285     if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL)) {
286         av_log(s, AV_LOG_ERROR, "Stream not seekable, unable to write output file\n");
287         return AVERROR(EINVAL);
288     }
289
290     return 0;
291 }
292
293 static void argo_asf_write_file_header(const ArgoASFFileHeader *fhdr, AVIOContext *pb)
294 {
295     avio_wl32( pb, fhdr->magic);
296     avio_wl16( pb, fhdr->version_major);
297     avio_wl16( pb, fhdr->version_minor);
298     avio_wl32( pb, fhdr->num_chunks);
299     avio_wl32( pb, fhdr->chunk_offset);
300     avio_write(pb, fhdr->name, sizeof(fhdr->name));
301 }
302
303 static void argo_asf_write_chunk_header(const ArgoASFChunkHeader *ckhdr, AVIOContext *pb)
304 {
305     avio_wl32(pb, ckhdr->num_blocks);
306     avio_wl32(pb, ckhdr->num_samples);
307     avio_wl32(pb, ckhdr->unk1);
308     avio_wl16(pb, ckhdr->sample_rate);
309     avio_wl16(pb, ckhdr->unk2);
310     avio_wl32(pb, ckhdr->flags);
311 }
312
313 static int argo_asf_write_header(AVFormatContext *s)
314 {
315     const AVCodecParameters  *par = s->streams[0]->codecpar;
316     ArgoASFMuxContext        *ctx = s->priv_data;
317     ArgoASFFileHeader  fhdr;
318     ArgoASFChunkHeader chdr;
319
320     fhdr.magic         = ASF_TAG;
321     fhdr.version_major = (uint16_t)ctx->version_major;
322     fhdr.version_minor = (uint16_t)ctx->version_minor;
323     fhdr.num_chunks    = 1;
324     fhdr.chunk_offset  = ASF_FILE_HEADER_SIZE;
325     /*
326      * If the user specified a name, use it as is. Otherwise take the
327      * basename and lop off the extension (if any).
328      */
329     if (ctx->name) {
330         strncpy(fhdr.name, ctx->name, sizeof(fhdr.name));
331     } else {
332         const char *start = av_basename(s->url);
333         const char *end   = strrchr(start, '.');
334         size_t      len;
335
336         if(end)
337             len = end - start;
338         else
339             len = strlen(start);
340
341         memcpy(fhdr.name, start, FFMIN(len, sizeof(fhdr.name)));
342     }
343
344     chdr.num_blocks    = 0;
345     chdr.num_samples   = ASF_SAMPLE_COUNT;
346     chdr.unk1          = 0;
347
348     if (ctx->version_major == 1 && ctx->version_minor == 1)
349         chdr.sample_rate = 44100;
350     else
351         chdr.sample_rate = par->sample_rate;
352
353     chdr.unk2          = ~0;
354     chdr.flags         = ASF_CF_BITS_PER_SAMPLE | ASF_CF_ALWAYS1;
355
356     if (par->channels == 2)
357         chdr.flags |= ASF_CF_STEREO;
358
359     argo_asf_write_file_header(&fhdr, s->pb);
360     argo_asf_write_chunk_header(&chdr, s->pb);
361     return 0;
362 }
363
364 static int argo_asf_write_packet(AVFormatContext *s, AVPacket *pkt)
365 {
366     if (pkt->size != 17 * s->streams[0]->codecpar->channels)
367         return AVERROR_INVALIDDATA;
368
369     if (s->streams[0]->nb_frames >= UINT32_MAX)
370         return AVERROR_INVALIDDATA;
371
372     avio_write(s->pb, pkt->data, pkt->size);
373     return 0;
374 }
375
376 static int argo_asf_write_trailer(AVFormatContext *s)
377 {
378     int64_t ret;
379
380     if ((ret = avio_seek(s->pb, ASF_FILE_HEADER_SIZE, SEEK_SET) < 0))
381         return ret;
382
383     avio_wl32(s->pb, (uint32_t)s->streams[0]->nb_frames);
384     return 0;
385 }
386
387 static const AVOption argo_asf_options[] = {
388     {
389         .name        = "version_major",
390         .help        = "override file major version",
391         .offset      = offsetof(ArgoASFMuxContext, version_major),
392         .type        = AV_OPT_TYPE_INT,
393         .default_val = {.i64 = 2},
394         .min         = 0,
395         .max         = UINT16_MAX,
396         .flags       = AV_OPT_FLAG_ENCODING_PARAM
397     },
398     {
399         .name        = "version_minor",
400         .help        = "override file minor version",
401         .offset      = offsetof(ArgoASFMuxContext, version_minor),
402         .type        = AV_OPT_TYPE_INT,
403         .default_val = {.i64 = 1},
404         .min         = 0,
405         .max         = UINT16_MAX,
406         .flags       = AV_OPT_FLAG_ENCODING_PARAM
407     },
408     {
409         .name        = "name",
410         .help        = "embedded file name (max 8 characters)",
411         .offset      = offsetof(ArgoASFMuxContext, name),
412         .type        = AV_OPT_TYPE_STRING,
413         .default_val = {.str = NULL},
414         .flags       = AV_OPT_FLAG_ENCODING_PARAM
415     },
416     { NULL }
417 };
418
419 static const AVClass argo_asf_muxer_class = {
420     .class_name = "argo_asf_muxer",
421     .item_name  = av_default_item_name,
422     .option     = argo_asf_options,
423     .version    = LIBAVUTIL_VERSION_INT
424 };
425
426 AVOutputFormat ff_argo_asf_muxer = {
427     .name           = "argo_asf",
428     .long_name      = NULL_IF_CONFIG_SMALL("Argonaut Games ASF"),
429     /*
430      * NB: Can't do this as it conflicts with the actual ASF format.
431      * .extensions  = "asf",
432      */
433     .audio_codec    = AV_CODEC_ID_ADPCM_ARGO,
434     .video_codec    = AV_CODEC_ID_NONE,
435     .init           = argo_asf_write_init,
436     .write_header   = argo_asf_write_header,
437     .write_packet   = argo_asf_write_packet,
438     .write_trailer  = argo_asf_write_trailer,
439     .priv_class     = &argo_asf_muxer_class,
440     .priv_data_size = sizeof(ArgoASFMuxContext)
441 };
442 #endif