FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/argo_cvg.c
Date: 2022-12-09 07:38:14
Exec Total Coverage
Lines: 3 157 1.9%
Functions: 1 9 11.1%
Branches: 1 86 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 "internal.h"
29 #include "libavutil/opt.h"
30 #include "libavutil/intreadwrite.h"
31
32 /*
33 * .CVG files are essentially PSX ADPCM wrapped with a size and checksum.
34 * Found in the PSX versions of the game.
35 */
36
37 #define ARGO_CVG_HEADER_SIZE 12
38 #define ARGO_CVG_BLOCK_ALIGN 0x10
39 #define ARGO_CVG_NB_BLOCKS 32
40 #define ARGO_CVG_SAMPLES_PER_BLOCK 28
41
42 typedef struct ArgoCVGHeader {
43 uint32_t size; /*< File size -8 (this + trailing checksum) */
44 uint32_t loop; /*< Loop flag. */
45 uint32_t reverb; /*< Reverb flag. */
46 } ArgoCVGHeader;
47
48 typedef struct ArgoCVGOverride {
49 const char *name;
50 ArgoCVGHeader header;
51 uint32_t checksum;
52 int sample_rate;
53 } ArgoCVGOverride;
54
55 typedef struct ArgoCVGDemuxContext {
56 ArgoCVGHeader header;
57 uint32_t checksum;
58 uint32_t num_blocks;
59 uint32_t blocks_read;
60 } ArgoCVGDemuxContext;
61
62 typedef struct ArgoCVGMuxContext {
63 const AVClass *class;
64 int skip_rate_check;
65 int loop;
66 int reverb;
67 uint32_t checksum;
68 size_t size;
69 } ArgoCVGMuxContext;
70
71 #if CONFIG_ARGO_CVG_DEMUXER
72 /* "Special" files that are played at a different rate. */
73 static const ArgoCVGOverride overrides[] = {
74 { "CRYS.CVG", { 23592, 0, 1 }, 2495499, 88200 }, /* Beta */
75 { "REDCRY88.CVG", { 38280, 0, 1 }, 4134848, 88200 }, /* Beta */
76 { "DANLOOP1.CVG", { 54744, 1, 0 }, 5684641, 37800 }, /* Beta */
77 { "PICKUP88.CVG", { 12904, 0, 1 }, 1348091, 48000 }, /* Beta */
78 { "SELECT1.CVG", { 5080, 0, 1 }, 549987, 44100 }, /* Beta */
79 };
80
81 6782 static int argo_cvg_probe(const AVProbeData *p)
82 {
83 ArgoCVGHeader cvg;
84
85 /*
86 * It's almost impossible to detect these files based
87 * on the header alone. File extension is (unfortunately)
88 * the best way forward.
89 */
90
1/2
✓ Branch 1 taken 6782 times.
✗ Branch 2 not taken.
6782 if (!av_match_ext(p->filename, "cvg"))
91 6782 return 0;
92
93 if (p->buf_size < ARGO_CVG_HEADER_SIZE)
94 return 0;
95
96 cvg.size = AV_RL32(p->buf + 0);
97 cvg.loop = AV_RL32(p->buf + 4);
98 cvg.reverb = AV_RL32(p->buf + 8);
99
100 if (cvg.size < 8)
101 return 0;
102
103 if (cvg.loop != 0 && cvg.loop != 1)
104 return 0;
105
106 if (cvg.reverb != 0 && cvg.reverb != 1)
107 return 0;
108
109 return AVPROBE_SCORE_MAX / 4 + 1;
110 }
111
112 static int argo_cvg_read_checksum(AVIOContext *pb, const ArgoCVGHeader *cvg, uint32_t *checksum)
113 {
114 int ret;
115 uint8_t buf[4];
116
117 if (!(pb->seekable & AVIO_SEEKABLE_NORMAL)) {
118 *checksum = 0;
119 return 0;
120 }
121
122 if ((ret = avio_seek(pb, cvg->size + 4, SEEK_SET)) < 0)
123 return ret;
124
125 /* NB: Not using avio_rl32() because no error checking. */
126 if ((ret = avio_read(pb, buf, sizeof(buf))) < 0)
127 return ret;
128 else if (ret != sizeof(buf))
129 return AVERROR(EIO);
130
131 if ((ret = avio_seek(pb, ARGO_CVG_HEADER_SIZE, SEEK_SET)) < 0)
132 return ret;
133
134 *checksum = AV_RL32(buf);
135 return 0;
136 }
137
138 static int argo_cvg_read_header(AVFormatContext *s)
139 {
140 int ret;
141 AVStream *st;
142 AVCodecParameters *par;
143 uint8_t buf[ARGO_CVG_HEADER_SIZE];
144 const char *filename = av_basename(s->url);
145 ArgoCVGDemuxContext *ctx = s->priv_data;
146
147 if (!(st = avformat_new_stream(s, NULL)))
148 return AVERROR(ENOMEM);
149
150 if ((ret = avio_read(s->pb, buf, ARGO_CVG_HEADER_SIZE)) < 0)
151 return ret;
152 else if (ret != ARGO_CVG_HEADER_SIZE)
153 return AVERROR(EIO);
154
155 ctx->header.size = AV_RL32(buf + 0);
156 ctx->header.loop = AV_RL32(buf + 4);
157 ctx->header.reverb = AV_RL32(buf + 8);
158
159 if (ctx->header.size < 8)
160 return AVERROR_INVALIDDATA;
161
162 if ((ret = argo_cvg_read_checksum(s->pb, &ctx->header, &ctx->checksum)) < 0)
163 return ret;
164
165 if ((ret = av_dict_set_int(&st->metadata, "loop", ctx->header.loop, 0)) < 0)
166 return ret;
167
168 if ((ret = av_dict_set_int(&st->metadata, "reverb", ctx->header.reverb, 0)) < 0)
169 return ret;
170
171 if ((ret = av_dict_set_int(&st->metadata, "checksum", ctx->checksum, 0)) < 0)
172 return ret;
173
174 par = st->codecpar;
175 par->codec_type = AVMEDIA_TYPE_AUDIO;
176 par->codec_id = AV_CODEC_ID_ADPCM_PSX;
177 par->sample_rate = 22050;
178
179 for (size_t i = 0; i < FF_ARRAY_ELEMS(overrides); i++) {
180 const ArgoCVGOverride *ovr = overrides + i;
181 if (ovr->header.size != ctx->header.size ||
182 ovr->header.loop != ctx->header.loop ||
183 ovr->header.reverb != ctx->header.reverb ||
184 ovr->checksum != ctx->checksum ||
185 av_strcasecmp(filename, ovr->name) != 0)
186 continue;
187
188 av_log(s, AV_LOG_TRACE, "found override, name = %s\n", ovr->name);
189 par->sample_rate = ovr->sample_rate;
190 break;
191 }
192
193 par->ch_layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_MONO;
194
195 par->bits_per_coded_sample = 4;
196 par->block_align = ARGO_CVG_BLOCK_ALIGN;
197 par->bit_rate = par->sample_rate * par->bits_per_coded_sample;
198
199 ctx->num_blocks = (ctx->header.size - 8) / ARGO_CVG_BLOCK_ALIGN;
200
201 av_log(s, AV_LOG_TRACE, "num blocks = %u\n", ctx->num_blocks);
202
203 avpriv_set_pts_info(st, 64, 1, par->sample_rate);
204
205 st->start_time = 0;
206 st->duration = ctx->num_blocks * ARGO_CVG_SAMPLES_PER_BLOCK;
207 st->nb_frames = ctx->num_blocks;
208 return 0;
209 }
210
211 static int argo_cvg_read_packet(AVFormatContext *s, AVPacket *pkt)
212 {
213 int ret;
214 AVStream *st = s->streams[0];
215 ArgoCVGDemuxContext *ctx = s->priv_data;
216
217 if (ctx->blocks_read >= ctx->num_blocks)
218 return AVERROR_EOF;
219
220 ret = av_get_packet(s->pb, pkt, st->codecpar->block_align *
221 FFMIN(ARGO_CVG_NB_BLOCKS, ctx->num_blocks - ctx->blocks_read));
222
223 if (ret < 0)
224 return ret;
225
226 if (ret % st->codecpar->block_align != 0)
227 return AVERROR_INVALIDDATA;
228
229 pkt->stream_index = 0;
230 pkt->duration = ARGO_CVG_SAMPLES_PER_BLOCK * (ret / st->codecpar->block_align);
231 pkt->pts = ctx->blocks_read * ARGO_CVG_SAMPLES_PER_BLOCK;
232 pkt->flags &= ~AV_PKT_FLAG_CORRUPT;
233
234 ctx->blocks_read += ret / st->codecpar->block_align;
235
236 return 0;
237 }
238
239 static int argo_cvg_seek(AVFormatContext *s, int stream_index,
240 int64_t pts, int flags)
241 {
242 int64_t ret;
243 ArgoCVGDemuxContext *ctx = s->priv_data;
244
245 if (pts != 0 || stream_index != 0)
246 return AVERROR(EINVAL);
247
248 if ((ret = avio_seek(s->pb, ARGO_CVG_HEADER_SIZE, SEEK_SET)) < 0)
249 return ret;
250
251 ctx->blocks_read = 0;
252 return 0;
253 }
254
255 const AVInputFormat ff_argo_cvg_demuxer = {
256 .name = "argo_cvg",
257 .long_name = NULL_IF_CONFIG_SMALL("Argonaut Games CVG"),
258 .priv_data_size = sizeof(ArgoCVGDemuxContext),
259 .read_probe = argo_cvg_probe,
260 .read_header = argo_cvg_read_header,
261 .read_packet = argo_cvg_read_packet,
262 .read_seek = argo_cvg_seek,
263 };
264 #endif
265
266 #if CONFIG_ARGO_CVG_MUXER
267 static int argo_cvg_write_init(AVFormatContext *s)
268 {
269 ArgoCVGMuxContext *ctx = s->priv_data;
270 const AVCodecParameters *par;
271
272 if (s->nb_streams != 1) {
273 av_log(s, AV_LOG_ERROR, "CVG files have exactly one stream\n");
274 return AVERROR(EINVAL);
275 }
276
277 par = s->streams[0]->codecpar;
278
279 if (par->codec_id != AV_CODEC_ID_ADPCM_PSX) {
280 av_log(s, AV_LOG_ERROR, "%s codec not supported\n",
281 avcodec_get_name(par->codec_id));
282 return AVERROR(EINVAL);
283 }
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 AVOutputFormat ff_argo_cvg_muxer = {
404 .name = "argo_cvg",
405 .long_name = NULL_IF_CONFIG_SMALL("Argonaut Games CVG"),
406 .extensions = "cvg",
407 .audio_codec = AV_CODEC_ID_ADPCM_PSX,
408 .video_codec = AV_CODEC_ID_NONE,
409 .init = argo_cvg_write_init,
410 .write_header = argo_cvg_write_header,
411 .write_packet = argo_cvg_write_packet,
412 .write_trailer = argo_cvg_write_trailer,
413 .priv_class = &argo_cvg_muxer_class,
414 .priv_data_size = sizeof(ArgoCVGMuxContext),
415 };
416 #endif
417