FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/argo_asf.c
Date: 2024-04-20 14:10:07
Exec Total Coverage
Lines: 149 199 74.9%
Functions: 14 15 93.3%
Branches: 47 96 49.0%

Line Branch Exec Source
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
23 #include "config_components.h"
24
25 #include "libavutil/avstring.h"
26 #include "avformat.h"
27 #include "demux.h"
28 #include "internal.h"
29 #include "mux.h"
30 #include "libavutil/channel_layout.h"
31 #include "libavutil/intreadwrite.h"
32 #include "libavutil/avassert.h"
33 #include "libavutil/opt.h"
34 #include "argo_asf.h"
35
36 /* Maximum number of blocks to read at once. */
37 #define ASF_NB_BLOCKS 32
38
39 typedef struct ArgoASFDemuxContext {
40 ArgoASFFileHeader fhdr;
41 ArgoASFChunkHeader ckhdr;
42 uint32_t blocks_read;
43 } ArgoASFDemuxContext;
44
45 typedef struct ArgoASFMuxContext {
46 const AVClass *class;
47 int version_major;
48 int version_minor;
49 const char *name;
50 int64_t nb_blocks;
51 } ArgoASFMuxContext;
52
53 7128 void ff_argo_asf_parse_file_header(ArgoASFFileHeader *hdr, const uint8_t *buf)
54 {
55 7128 hdr->magic = AV_RL32(buf + 0);
56 7128 hdr->version_major = AV_RL16(buf + 4);
57 7128 hdr->version_minor = AV_RL16(buf + 6);
58 7128 hdr->num_chunks = AV_RL32(buf + 8);
59 7128 hdr->chunk_offset = AV_RL32(buf + 12);
60 7128 memcpy(hdr->name, buf + 16, ASF_NAME_SIZE);
61 7128 hdr->name[ASF_NAME_SIZE] = '\0';
62 7128 }
63
64 3 int ff_argo_asf_validate_file_header(AVFormatContext *s, const ArgoASFFileHeader *hdr)
65 {
66
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 if (hdr->magic != ASF_TAG || hdr->num_chunks == 0)
67 return AVERROR_INVALIDDATA;
68
69
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (hdr->chunk_offset < ASF_FILE_HEADER_SIZE)
70 return AVERROR_INVALIDDATA;
71
72 3 return 0;
73 }
74
75 3 void ff_argo_asf_parse_chunk_header(ArgoASFChunkHeader *hdr, const uint8_t *buf)
76 {
77 3 hdr->num_blocks = AV_RL32(buf + 0);
78 3 hdr->num_samples = AV_RL32(buf + 4);
79 3 hdr->unk1 = AV_RL32(buf + 8);
80 3 hdr->sample_rate = AV_RL16(buf + 12);
81 3 hdr->unk2 = AV_RL16(buf + 14);
82 3 hdr->flags = AV_RL32(buf + 16);
83 3 }
84
85 3 int ff_argo_asf_fill_stream(AVFormatContext *s, AVStream *st, const ArgoASFFileHeader *fhdr,
86 const ArgoASFChunkHeader *ckhdr)
87 {
88
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (ckhdr->num_samples != ASF_SAMPLE_COUNT) {
89 av_log(s, AV_LOG_ERROR, "Invalid sample count. Got %u, expected %d\n",
90 ckhdr->num_samples, ASF_SAMPLE_COUNT);
91 return AVERROR_INVALIDDATA;
92 }
93
94
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 if ((ckhdr->flags & ASF_CF_ALWAYS1) != ASF_CF_ALWAYS1 || (ckhdr->flags & ASF_CF_ALWAYS0) != 0) {
95 avpriv_request_sample(s, "Nonstandard flags (0x%08X)", ckhdr->flags);
96 return AVERROR_PATCHWELCOME;
97 }
98
99 3 st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
100 3 st->codecpar->codec_id = AV_CODEC_ID_ADPCM_ARGO;
101 3 st->codecpar->format = AV_SAMPLE_FMT_S16P;
102
103
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if (ckhdr->flags & ASF_CF_STEREO) {
104 2 st->codecpar->ch_layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO;
105 } else {
106 1 st->codecpar->ch_layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_MONO;
107 }
108
109 /* v1.1 files (FX Fighter) are all marked as 44100, but are actually 22050. */
110
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
3 if (fhdr->version_major == 1 && fhdr->version_minor == 1)
111 1 st->codecpar->sample_rate = 22050;
112 else
113 2 st->codecpar->sample_rate = ckhdr->sample_rate;
114
115 3 st->codecpar->bits_per_coded_sample = 4;
116
117
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!(ckhdr->flags & ASF_CF_BITS_PER_SAMPLE)) {
118 /* The header allows for these, but I've never seen any files with them. */
119 avpriv_request_sample(s, "Non 16-bit samples");
120 return AVERROR_PATCHWELCOME;
121 }
122
123 /*
124 * (nchannel control bytes) + ((bytes_per_channel) * nchannel)
125 * For mono, this is 17. For stereo, this is 34.
126 */
127 3 st->codecpar->block_align = st->codecpar->ch_layout.nb_channels +
128 3 (ckhdr->num_samples / 2) *
129 st->codecpar->ch_layout.nb_channels;
130
131 3 st->codecpar->bit_rate = st->codecpar->ch_layout.nb_channels *
132 3 st->codecpar->sample_rate *
133 3 st->codecpar->bits_per_coded_sample;
134
135 3 avpriv_set_pts_info(st, 64, 1, st->codecpar->sample_rate);
136 3 st->start_time = 0;
137
138
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (fhdr->num_chunks == 1) {
139 3 st->duration = ckhdr->num_blocks * ckhdr->num_samples;
140 3 st->nb_frames = ckhdr->num_blocks;
141 }
142
143 3 return 0;
144 }
145
146 #if CONFIG_ARGO_ASF_DEMUXER
147 /*
148 * Known versions:
149 * 1.1: https://samples.ffmpeg.org/game-formats/brender/part2.zip
150 * FX Fighter
151 * 1.2: Croc! Legend of the Gobbos
152 * 2.1: Croc 2
153 * The Emperor's New Groove
154 * Disney's Aladdin in Nasira's Revenge
155 */
156 3 static int argo_asf_is_known_version(const ArgoASFFileHeader *hdr)
157 {
158
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 return (hdr->version_major == 1 && hdr->version_minor == 1) ||
159
3/6
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
6 (hdr->version_major == 1 && hdr->version_minor == 2) ||
160
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 (hdr->version_major == 2 && hdr->version_minor == 1);
161 }
162
163 7125 static int argo_asf_probe(const AVProbeData *p)
164 {
165 ArgoASFFileHeader hdr;
166
167 av_assert0(AVPROBE_PADDING_SIZE >= ASF_FILE_HEADER_SIZE);
168
169 7125 ff_argo_asf_parse_file_header(&hdr, p->buf);
170
171
2/2
✓ Branch 0 taken 7122 times.
✓ Branch 1 taken 3 times.
7125 if (hdr.magic != ASF_TAG)
172 7122 return 0;
173
174
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (!argo_asf_is_known_version(&hdr))
175 return AVPROBE_SCORE_EXTENSION / 2;
176
177 3 return AVPROBE_SCORE_EXTENSION + 1;
178 }
179
180 3 static int argo_asf_read_header(AVFormatContext *s)
181 {
182 int64_t ret;
183 3 AVIOContext *pb = s->pb;
184 AVStream *st;
185 3 ArgoASFDemuxContext *asf = s->priv_data;
186 uint8_t buf[ASF_MIN_BUFFER_SIZE];
187
188
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (!(st = avformat_new_stream(s, NULL)))
189 return AVERROR(ENOMEM);
190
191
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if ((ret = avio_read(pb, buf, ASF_FILE_HEADER_SIZE)) < 0)
192 return ret;
193
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 else if (ret != ASF_FILE_HEADER_SIZE)
194 return AVERROR(EIO);
195
196 3 ff_argo_asf_parse_file_header(&asf->fhdr, buf);
197
198
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if ((ret = ff_argo_asf_validate_file_header(s, &asf->fhdr)) < 0)
199 return ret;
200
201 /* This should only be 1 in ASF files. >1 is fine if in BRP. */
202
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (asf->fhdr.num_chunks != 1)
203 return AVERROR_INVALIDDATA;
204
205
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if ((ret = avio_skip(pb, asf->fhdr.chunk_offset - ASF_FILE_HEADER_SIZE)) < 0)
206 return ret;
207
208
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if ((ret = avio_read(pb, buf, ASF_CHUNK_HEADER_SIZE)) < 0)
209 return ret;
210
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 else if (ret != ASF_CHUNK_HEADER_SIZE)
211 return AVERROR(EIO);
212
213 3 ff_argo_asf_parse_chunk_header(&asf->ckhdr, buf);
214
215 3 av_dict_set(&s->metadata, "title", asf->fhdr.name, 0);
216
217 3 return ff_argo_asf_fill_stream(s, st, &asf->fhdr, &asf->ckhdr);
218 }
219
220 949 static int argo_asf_read_packet(AVFormatContext *s, AVPacket *pkt)
221 {
222 949 ArgoASFDemuxContext *asf = s->priv_data;
223
224 949 AVStream *st = s->streams[0];
225 949 AVIOContext *pb = s->pb;
226 int ret;
227
228
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 946 times.
949 if (asf->blocks_read >= asf->ckhdr.num_blocks)
229 3 return AVERROR_EOF;
230
231 946 ret = av_get_packet(pb, pkt, st->codecpar->block_align *
232 946 FFMIN(ASF_NB_BLOCKS, asf->ckhdr.num_blocks - asf->blocks_read));
233
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 946 times.
946 if (ret < 0)
234 return ret;
235
236 /* Something real screwy is going on. */
237
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 946 times.
946 if (ret % st->codecpar->block_align != 0)
238 return AVERROR_INVALIDDATA;
239
240
241 946 pkt->stream_index = st->index;
242 946 pkt->duration = asf->ckhdr.num_samples * (ret / st->codecpar->block_align);
243 946 pkt->pts = asf->blocks_read * asf->ckhdr.num_samples;
244 946 asf->blocks_read += (ret / st->codecpar->block_align);
245
246 946 pkt->flags &= ~AV_PKT_FLAG_CORRUPT;
247 946 return 0;
248 }
249
250 static int argo_asf_seek(AVFormatContext *s, int stream_index,
251 int64_t pts, int flags)
252 {
253 ArgoASFDemuxContext *asf = s->priv_data;
254 AVStream *st = s->streams[stream_index];
255 int64_t offset;
256 uint32_t block = pts / asf->ckhdr.num_samples;
257
258 if (block >= asf->ckhdr.num_blocks)
259 return -1;
260
261 offset = asf->fhdr.chunk_offset + ASF_CHUNK_HEADER_SIZE +
262 (block * st->codecpar->block_align);
263
264 if ((offset = avio_seek(s->pb, offset, SEEK_SET)) < 0)
265 return offset;
266
267 asf->blocks_read = block;
268 return 0;
269 }
270
271 /*
272 * Not actually sure what ASF stands for.
273 * - Argonaut Sound File?
274 * - Audio Stream File?
275 */
276 const FFInputFormat ff_argo_asf_demuxer = {
277 .p.name = "argo_asf",
278 .p.long_name = NULL_IF_CONFIG_SMALL("Argonaut Games ASF"),
279 .priv_data_size = sizeof(ArgoASFDemuxContext),
280 .read_probe = argo_asf_probe,
281 .read_header = argo_asf_read_header,
282 .read_packet = argo_asf_read_packet,
283 .read_seek = argo_asf_seek,
284 };
285 #endif
286
287 #if CONFIG_ARGO_ASF_MUXER
288 1 static int argo_asf_write_init(AVFormatContext *s)
289 {
290 1 ArgoASFMuxContext *ctx = s->priv_data;
291 1 const AVCodecParameters *par = s->streams[0]->codecpar;
292
293
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 if (ctx->version_major == 1 && ctx->version_minor == 1 && par->sample_rate != 22050) {
294 av_log(s, AV_LOG_ERROR, "ASF v1.1 files only support a sample rate of 22050\n");
295 return AVERROR(EINVAL);
296 }
297
298
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (par->ch_layout.nb_channels > 2) {
299 av_log(s, AV_LOG_ERROR, "ASF files only support up to 2 channels\n");
300 return AVERROR(EINVAL);
301 }
302
303
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (par->block_align != 17 * par->ch_layout.nb_channels)
304 return AVERROR(EINVAL);
305
306
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (par->sample_rate > UINT16_MAX) {
307 av_log(s, AV_LOG_ERROR, "Sample rate too large\n");
308 return AVERROR(EINVAL);
309 }
310
311
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL)) {
312 av_log(s, AV_LOG_ERROR, "Stream not seekable, unable to write output file\n");
313 return AVERROR(EINVAL);
314 }
315
316 1 return 0;
317 }
318
319 1 static void argo_asf_write_file_header(const ArgoASFFileHeader *fhdr, AVIOContext *pb)
320 {
321 1 avio_wl32( pb, fhdr->magic);
322 1 avio_wl16( pb, fhdr->version_major);
323 1 avio_wl16( pb, fhdr->version_minor);
324 1 avio_wl32( pb, fhdr->num_chunks);
325 1 avio_wl32( pb, fhdr->chunk_offset);
326 1 avio_write(pb, fhdr->name, ASF_NAME_SIZE);
327 1 }
328
329 1 static void argo_asf_write_chunk_header(const ArgoASFChunkHeader *ckhdr, AVIOContext *pb)
330 {
331 1 avio_wl32(pb, ckhdr->num_blocks);
332 1 avio_wl32(pb, ckhdr->num_samples);
333 1 avio_wl32(pb, ckhdr->unk1);
334 1 avio_wl16(pb, ckhdr->sample_rate);
335 1 avio_wl16(pb, ckhdr->unk2);
336 1 avio_wl32(pb, ckhdr->flags);
337 1 }
338
339 1 static int argo_asf_write_header(AVFormatContext *s)
340 {
341 1 const AVCodecParameters *par = s->streams[0]->codecpar;
342 1 ArgoASFMuxContext *ctx = s->priv_data;
343 ArgoASFChunkHeader chdr;
344 1 ArgoASFFileHeader fhdr = {
345 .magic = ASF_TAG,
346 1 .version_major = (uint16_t)ctx->version_major,
347 1 .version_minor = (uint16_t)ctx->version_minor,
348 .num_chunks = 1,
349 .chunk_offset = ASF_FILE_HEADER_SIZE
350 };
351 AVDictionaryEntry *t;
352 const char *name, *end;
353 size_t len;
354
355 /*
356 * If the user specified a name, use it as is. Otherwise,
357 * try to use metadata (if present), then fall back to the
358 * filename (minus extension).
359 */
360
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ctx->name) {
361 name = ctx->name;
362 len = strlen(ctx->name);
363
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 } else if ((t = av_dict_get(s->metadata, "title", NULL, 0))) {
364 name = t->value;
365 len = strlen(t->value);
366
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 } else if (!(end = strrchr((name = av_basename(s->url)), '.'))) {
367 len = strlen(name);
368 } else {
369 1 len = end - name;
370 }
371 1 memcpy(fhdr.name, name, FFMIN(len, ASF_NAME_SIZE));
372
373 1 chdr.num_blocks = 0;
374 1 chdr.num_samples = ASF_SAMPLE_COUNT;
375 1 chdr.unk1 = 0;
376
377
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (ctx->version_major == 1 && ctx->version_minor == 1)
378 chdr.sample_rate = 44100;
379 else
380 1 chdr.sample_rate = par->sample_rate;
381
382 1 chdr.unk2 = ~0;
383 1 chdr.flags = ASF_CF_BITS_PER_SAMPLE | ASF_CF_ALWAYS1;
384
385
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (par->ch_layout.nb_channels == 2)
386 1 chdr.flags |= ASF_CF_STEREO;
387
388 1 argo_asf_write_file_header(&fhdr, s->pb);
389 1 argo_asf_write_chunk_header(&chdr, s->pb);
390 1 return 0;
391 }
392
393 8269 static int argo_asf_write_packet(AVFormatContext *s, AVPacket *pkt)
394 {
395 8269 ArgoASFMuxContext *ctx = s->priv_data;
396 8269 AVCodecParameters *par = s->streams[0]->codecpar;
397 8269 int nb_blocks = pkt->size / par->block_align;
398
399
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8269 times.
8269 if (pkt->size % par->block_align != 0)
400 return AVERROR_INVALIDDATA;
401
402
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8269 times.
8269 if (ctx->nb_blocks + nb_blocks > UINT32_MAX)
403 return AVERROR_INVALIDDATA;
404
405 8269 avio_write(s->pb, pkt->data, pkt->size);
406
407 8269 ctx->nb_blocks += nb_blocks;
408 8269 return 0;
409 }
410
411 1 static int argo_asf_write_trailer(AVFormatContext *s)
412 {
413 1 ArgoASFMuxContext *ctx = s->priv_data;
414 int64_t ret;
415
416
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if ((ret = avio_seek(s->pb, ASF_FILE_HEADER_SIZE, SEEK_SET)) < 0)
417 return ret;
418
419 1 avio_wl32(s->pb, (uint32_t)ctx->nb_blocks);
420 1 return 0;
421 }
422
423 static const AVOption argo_asf_options[] = {
424 {
425 .name = "version_major",
426 .help = "override file major version",
427 .offset = offsetof(ArgoASFMuxContext, version_major),
428 .type = AV_OPT_TYPE_INT,
429 .default_val = {.i64 = 2},
430 .min = 0,
431 .max = UINT16_MAX,
432 .flags = AV_OPT_FLAG_ENCODING_PARAM
433 },
434 {
435 .name = "version_minor",
436 .help = "override file minor version",
437 .offset = offsetof(ArgoASFMuxContext, version_minor),
438 .type = AV_OPT_TYPE_INT,
439 .default_val = {.i64 = 1},
440 .min = 0,
441 .max = UINT16_MAX,
442 .flags = AV_OPT_FLAG_ENCODING_PARAM
443 },
444 {
445 .name = "name",
446 .help = "embedded file name (max 8 characters)",
447 .offset = offsetof(ArgoASFMuxContext, name),
448 .type = AV_OPT_TYPE_STRING,
449 .default_val = {.str = NULL},
450 .flags = AV_OPT_FLAG_ENCODING_PARAM
451 },
452 { NULL }
453 };
454
455 static const AVClass argo_asf_muxer_class = {
456 .class_name = "argo_asf_muxer",
457 .item_name = av_default_item_name,
458 .option = argo_asf_options,
459 .version = LIBAVUTIL_VERSION_INT
460 };
461
462 const FFOutputFormat ff_argo_asf_muxer = {
463 .p.name = "argo_asf",
464 .p.long_name = NULL_IF_CONFIG_SMALL("Argonaut Games ASF"),
465 /*
466 * NB: Can't do this as it conflicts with the actual ASF format.
467 * .p.extensions = "asf",
468 */
469 .p.audio_codec = AV_CODEC_ID_ADPCM_ARGO,
470 .p.video_codec = AV_CODEC_ID_NONE,
471 .p.subtitle_codec = AV_CODEC_ID_NONE,
472 .p.priv_class = &argo_asf_muxer_class,
473 .flags_internal = FF_OFMT_FLAG_MAX_ONE_OF_EACH |
474 FF_OFMT_FLAG_ONLY_DEFAULT_CODECS,
475 .init = argo_asf_write_init,
476 .write_header = argo_asf_write_header,
477 .write_packet = argo_asf_write_packet,
478 .write_trailer = argo_asf_write_trailer,
479 .priv_data_size = sizeof(ArgoASFMuxContext)
480 };
481 #endif
482