FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/argo_cvg.c
Date: 2026-04-20 16:43:18
Exec Total Coverage
Lines: 3 146 2.1%
Functions: 1 9 11.1%
Branches: 1 78 1.3%

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