FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/argo_cvg.c
Date: 2024-11-20 23:03:26
Exec Total Coverage
Lines: 3 150 2.0%
Functions: 1 9 11.1%
Branches: 1 82 1.2%

Line Branch Exec Source
1 /*
2 * Argonaut Games CVG (de)muxer
3 *
4 * Copyright (C) 2021 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 "libavutil/channel_layout.h"
27 #include "avformat.h"
28 #include "demux.h"
29 #include "internal.h"
30 #include "mux.h"
31 #include "libavutil/opt.h"
32 #include "libavutil/intreadwrite.h"
33
34 /*
35 * .CVG files are essentially PSX ADPCM wrapped with a size and checksum.
36 * Found in the PSX versions of the game.
37 */
38
39 #define ARGO_CVG_HEADER_SIZE 12
40 #define ARGO_CVG_BLOCK_ALIGN 0x10
41 #define ARGO_CVG_NB_BLOCKS 32
42 #define ARGO_CVG_SAMPLES_PER_BLOCK 28
43
44 typedef struct ArgoCVGHeader {
45 uint32_t size; /*< File size -8 (this + trailing checksum) */
46 uint32_t loop; /*< Loop flag. */
47 uint32_t reverb; /*< Reverb flag. */
48 } ArgoCVGHeader;
49
50 typedef struct ArgoCVGDemuxContext {
51 ArgoCVGHeader header;
52 uint32_t checksum;
53 uint32_t num_blocks;
54 uint32_t blocks_read;
55 } ArgoCVGDemuxContext;
56
57 typedef struct ArgoCVGMuxContext {
58 const AVClass *class;
59 int skip_rate_check;
60 int loop;
61 int reverb;
62 uint32_t checksum;
63 size_t size;
64 } ArgoCVGMuxContext;
65
66 #if CONFIG_ARGO_CVG_DEMUXER
67 /* "Special" files that are played at a different rate. */
68 // FILE(NAME, SIZE, LOOP, REVERB, CHECKSUM, SAMPLE_RATE)
69 #define OVERRIDE_FILES(FILE) \
70 FILE(CRYS, 23592, 0, 1, 2495499, 88200) /* Beta */ \
71 FILE(REDCRY88, 38280, 0, 1, 4134848, 88200) /* Beta */ \
72 FILE(DANLOOP1, 54744, 1, 0, 5684641, 37800) /* Beta */ \
73 FILE(PICKUP88, 12904, 0, 1, 1348091, 48000) /* Beta */ \
74 FILE(SELECT1, 5080, 0, 1, 549987, 44100) /* Beta */ \
75
76 #define MAX_FILENAME_SIZE(NAME, SIZE, LOOP, REVERB, CHECKSUM, SAMPLE_RATE) \
77 MAX_SIZE_BEFORE_ ## NAME, \
78 MAX_SIZE_UNTIL_ ## NAME ## _MINUS1 = FFMAX(sizeof(#NAME ".CVG"), MAX_SIZE_BEFORE_ ## NAME) - 1,
79 enum {
80 OVERRIDE_FILES(MAX_FILENAME_SIZE)
81 MAX_OVERRIDE_FILENAME_SIZE
82 };
83
84 typedef struct ArgoCVGOverride {
85 const char name[MAX_OVERRIDE_FILENAME_SIZE];
86 ArgoCVGHeader header;
87 uint32_t checksum;
88 int sample_rate;
89 } ArgoCVGOverride;
90
91 #define FILE(NAME, SIZE, LOOP, REVERB, CHECKSUM, SAMPLE_RATE) \
92 { #NAME ".CVG", { SIZE, LOOP, REVERB }, CHECKSUM, SAMPLE_RATE },
93 static const ArgoCVGOverride overrides[] = {
94 OVERRIDE_FILES(FILE)
95 };
96
97 7186 static int argo_cvg_probe(const AVProbeData *p)
98 {
99 ArgoCVGHeader cvg;
100
101 /*
102 * It's almost impossible to detect these files based
103 * on the header alone. File extension is (unfortunately)
104 * the best way forward.
105 */
106
1/2
✓ Branch 1 taken 7186 times.
✗ Branch 2 not taken.
7186 if (!av_match_ext(p->filename, "cvg"))
107 7186 return 0;
108
109 if (p->buf_size < ARGO_CVG_HEADER_SIZE)
110 return 0;
111
112 cvg.size = AV_RL32(p->buf + 0);
113 cvg.loop = AV_RL32(p->buf + 4);
114 cvg.reverb = AV_RL32(p->buf + 8);
115
116 if (cvg.size < 8)
117 return 0;
118
119 if (cvg.loop != 0 && cvg.loop != 1)
120 return 0;
121
122 if (cvg.reverb != 0 && cvg.reverb != 1)
123 return 0;
124
125 return AVPROBE_SCORE_MAX / 4 + 1;
126 }
127
128 static int argo_cvg_read_checksum(AVIOContext *pb, const ArgoCVGHeader *cvg, uint32_t *checksum)
129 {
130 int ret;
131 uint8_t buf[4];
132
133 if (!(pb->seekable & AVIO_SEEKABLE_NORMAL)) {
134 *checksum = 0;
135 return 0;
136 }
137
138 if ((ret = avio_seek(pb, cvg->size + 4, SEEK_SET)) < 0)
139 return ret;
140
141 /* NB: Not using avio_rl32() because no error checking. */
142 if ((ret = avio_read(pb, buf, sizeof(buf))) < 0)
143 return ret;
144 else if (ret != sizeof(buf))
145 return AVERROR(EIO);
146
147 if ((ret = avio_seek(pb, ARGO_CVG_HEADER_SIZE, SEEK_SET)) < 0)
148 return ret;
149
150 *checksum = AV_RL32(buf);
151 return 0;
152 }
153
154 static int argo_cvg_read_header(AVFormatContext *s)
155 {
156 int ret;
157 AVStream *st;
158 AVCodecParameters *par;
159 uint8_t buf[ARGO_CVG_HEADER_SIZE];
160 const char *filename = av_basename(s->url);
161 ArgoCVGDemuxContext *ctx = s->priv_data;
162
163 if (!(st = avformat_new_stream(s, NULL)))
164 return AVERROR(ENOMEM);
165
166 if ((ret = avio_read(s->pb, buf, ARGO_CVG_HEADER_SIZE)) < 0)
167 return ret;
168 else if (ret != ARGO_CVG_HEADER_SIZE)
169 return AVERROR(EIO);
170
171 ctx->header.size = AV_RL32(buf + 0);
172 ctx->header.loop = AV_RL32(buf + 4);
173 ctx->header.reverb = AV_RL32(buf + 8);
174
175 if (ctx->header.size < 8)
176 return AVERROR_INVALIDDATA;
177
178 if ((ret = argo_cvg_read_checksum(s->pb, &ctx->header, &ctx->checksum)) < 0)
179 return ret;
180
181 if ((ret = av_dict_set_int(&st->metadata, "loop", ctx->header.loop, 0)) < 0)
182 return ret;
183
184 if ((ret = av_dict_set_int(&st->metadata, "reverb", ctx->header.reverb, 0)) < 0)
185 return ret;
186
187 if ((ret = av_dict_set_int(&st->metadata, "checksum", ctx->checksum, 0)) < 0)
188 return ret;
189
190 par = st->codecpar;
191 par->codec_type = AVMEDIA_TYPE_AUDIO;
192 par->codec_id = AV_CODEC_ID_ADPCM_PSX;
193 par->sample_rate = 22050;
194
195 for (size_t i = 0; i < FF_ARRAY_ELEMS(overrides); i++) {
196 const ArgoCVGOverride *ovr = overrides + i;
197 if (ovr->header.size != ctx->header.size ||
198 ovr->header.loop != ctx->header.loop ||
199 ovr->header.reverb != ctx->header.reverb ||
200 ovr->checksum != ctx->checksum ||
201 av_strcasecmp(filename, ovr->name) != 0)
202 continue;
203
204 av_log(s, AV_LOG_TRACE, "found override, name = %s\n", ovr->name);
205 par->sample_rate = ovr->sample_rate;
206 break;
207 }
208
209 par->ch_layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_MONO;
210
211 par->bits_per_coded_sample = 4;
212 par->block_align = ARGO_CVG_BLOCK_ALIGN;
213 par->bit_rate = par->sample_rate * par->bits_per_coded_sample;
214
215 ctx->num_blocks = (ctx->header.size - 8) / ARGO_CVG_BLOCK_ALIGN;
216
217 av_log(s, AV_LOG_TRACE, "num blocks = %u\n", ctx->num_blocks);
218
219 avpriv_set_pts_info(st, 64, 1, par->sample_rate);
220
221 st->start_time = 0;
222 st->duration = ctx->num_blocks * ARGO_CVG_SAMPLES_PER_BLOCK;
223 st->nb_frames = ctx->num_blocks;
224 return 0;
225 }
226
227 static int argo_cvg_read_packet(AVFormatContext *s, AVPacket *pkt)
228 {
229 int ret;
230 AVStream *st = s->streams[0];
231 ArgoCVGDemuxContext *ctx = s->priv_data;
232
233 if (ctx->blocks_read >= ctx->num_blocks)
234 return AVERROR_EOF;
235
236 ret = av_get_packet(s->pb, pkt, st->codecpar->block_align *
237 FFMIN(ARGO_CVG_NB_BLOCKS, ctx->num_blocks - ctx->blocks_read));
238
239 if (ret < 0)
240 return ret;
241
242 if (ret % st->codecpar->block_align != 0)
243 return AVERROR_INVALIDDATA;
244
245 pkt->stream_index = 0;
246 pkt->duration = ARGO_CVG_SAMPLES_PER_BLOCK * (ret / st->codecpar->block_align);
247 pkt->pts = ctx->blocks_read * ARGO_CVG_SAMPLES_PER_BLOCK;
248 pkt->flags &= ~AV_PKT_FLAG_CORRUPT;
249
250 ctx->blocks_read += ret / st->codecpar->block_align;
251
252 return 0;
253 }
254
255 static int argo_cvg_seek(AVFormatContext *s, int stream_index,
256 int64_t pts, int flags)
257 {
258 int64_t ret;
259 ArgoCVGDemuxContext *ctx = s->priv_data;
260
261 if (pts != 0 || stream_index != 0)
262 return AVERROR(EINVAL);
263
264 if ((ret = avio_seek(s->pb, ARGO_CVG_HEADER_SIZE, SEEK_SET)) < 0)
265 return ret;
266
267 ctx->blocks_read = 0;
268 return 0;
269 }
270
271 const FFInputFormat ff_argo_cvg_demuxer = {
272 .p.name = "argo_cvg",
273 .p.long_name = NULL_IF_CONFIG_SMALL("Argonaut Games CVG"),
274 .priv_data_size = sizeof(ArgoCVGDemuxContext),
275 .read_probe = argo_cvg_probe,
276 .read_header = argo_cvg_read_header,
277 .read_packet = argo_cvg_read_packet,
278 .read_seek = argo_cvg_seek,
279 };
280 #endif
281
282 #if CONFIG_ARGO_CVG_MUXER
283 static int argo_cvg_write_init(AVFormatContext *s)
284 {
285 ArgoCVGMuxContext *ctx = s->priv_data;
286 const AVCodecParameters *par = s->streams[0]->codecpar;
287
288 if (par->ch_layout.nb_channels != 1) {
289 av_log(s, AV_LOG_ERROR, "CVG files only support 1 channel\n");
290 return AVERROR(EINVAL);
291 }
292
293 if (par->block_align != ARGO_CVG_BLOCK_ALIGN)
294 return AVERROR(EINVAL);
295
296 if (!ctx->skip_rate_check && par->sample_rate != 22050) {
297 av_log(s, AV_LOG_ERROR, "Sample rate must be 22050\n");
298 return AVERROR(EINVAL);
299 }
300
301 if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL)) {
302 av_log(s, AV_LOG_ERROR, "Stream not seekable, unable to write output file\n");
303 return AVERROR(EINVAL);
304 }
305
306 return 0;
307 }
308
309 static int argo_cvg_write_header(AVFormatContext *s)
310 {
311 ArgoCVGMuxContext *ctx = s->priv_data;
312
313 avio_wl32(s->pb, 0); /* Size, fixed later. */
314 avio_wl32(s->pb, !!ctx->loop);
315 avio_wl32(s->pb, !!ctx->reverb);
316
317 ctx->checksum = !!ctx->loop + !!ctx->reverb;
318 ctx->size = 8;
319 return 0;
320 }
321
322 static int argo_cvg_write_packet(AVFormatContext *s, AVPacket *pkt)
323 {
324 ArgoCVGMuxContext *ctx = s->priv_data;
325 AVCodecParameters *par = s->streams[0]->codecpar;
326
327 if (pkt->size % par->block_align != 0)
328 return AVERROR_INVALIDDATA;
329
330 avio_write(s->pb, pkt->data, pkt->size);
331
332 ctx->size += pkt->size;
333
334 if (ctx->size > UINT32_MAX)
335 return AVERROR_INVALIDDATA;
336
337 for (int i = 0; i < pkt->size; i++)
338 ctx->checksum += pkt->data[i];
339
340 return 0;
341 }
342
343 static int argo_cvg_write_trailer(AVFormatContext *s)
344 {
345 ArgoCVGMuxContext *ctx = s->priv_data;
346 int64_t ret;
347
348 ctx->checksum += (ctx->size & 255)
349 + ((ctx->size>> 8) & 255)
350 + ((ctx->size>>16) & 255)
351 + (ctx->size>>24);
352
353 av_log(s, AV_LOG_TRACE, "size = %zu\n", ctx->size);
354 av_log(s, AV_LOG_TRACE, "checksum = %u\n", ctx->checksum);
355
356 avio_wl32(s->pb, ctx->checksum);
357
358 if ((ret = avio_seek(s->pb, 0, SEEK_SET)) < 0)
359 return ret;
360
361 avio_wl32(s->pb, (uint32_t)ctx->size);
362 return 0;
363 }
364
365 static const AVOption argo_cvg_options[] = {
366 {
367 .name = "skip_rate_check",
368 .help = "skip sample rate check",
369 .offset = offsetof(ArgoCVGMuxContext, skip_rate_check),
370 .type = AV_OPT_TYPE_BOOL,
371 .default_val = {.i64 = 0},
372 .min = 0,
373 .max = 1,
374 .flags = AV_OPT_FLAG_ENCODING_PARAM
375 },
376 {
377 .name = "loop",
378 .help = "set loop flag",
379 .offset = offsetof(ArgoCVGMuxContext, loop),
380 .type = AV_OPT_TYPE_BOOL,
381 .default_val = {.i64 = 0},
382 .min = 0,
383 .max = 1,
384 .flags = AV_OPT_FLAG_ENCODING_PARAM
385 },
386 {
387 .name = "reverb",
388 .help = "set reverb flag",
389 .offset = offsetof(ArgoCVGMuxContext, reverb),
390 .type = AV_OPT_TYPE_BOOL,
391 .default_val = {.i64 = 1},
392 .min = 0,
393 .max = 1,
394 .flags = AV_OPT_FLAG_ENCODING_PARAM
395 },
396 { NULL }
397 };
398
399 static const AVClass argo_cvg_muxer_class = {
400 .class_name = "argo_cvg_muxer",
401 .item_name = av_default_item_name,
402 .option = argo_cvg_options,
403 .version = LIBAVUTIL_VERSION_INT
404 };
405
406 const FFOutputFormat ff_argo_cvg_muxer = {
407 .p.name = "argo_cvg",
408 .p.long_name = NULL_IF_CONFIG_SMALL("Argonaut Games CVG"),
409 .p.extensions = "cvg",
410 .p.audio_codec = AV_CODEC_ID_ADPCM_PSX,
411 .p.video_codec = AV_CODEC_ID_NONE,
412 .p.subtitle_codec = AV_CODEC_ID_NONE,
413 .p.priv_class = &argo_cvg_muxer_class,
414 .flags_internal = FF_OFMT_FLAG_MAX_ONE_OF_EACH |
415 FF_OFMT_FLAG_ONLY_DEFAULT_CODECS,
416 .init = argo_cvg_write_init,
417 .write_header = argo_cvg_write_header,
418 .write_packet = argo_cvg_write_packet,
419 .write_trailer = argo_cvg_write_trailer,
420 .priv_data_size = sizeof(ArgoCVGMuxContext),
421 };
422 #endif
423