123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- /*
- * Argonaut Games CVG demuxer
- *
- * Copyright (C) 2021 Zane van Iperen (zane@zanevaniperen.com)
- *
- * This file is part of FFmpeg.
- *
- * FFmpeg is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * FFmpeg is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with FFmpeg; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
- #include "avformat.h"
- #include "internal.h"
- #include "libavutil/intreadwrite.h"
- /*
- * .CVG files are essentially PSX ADPCM wrapped with a size and checksum.
- * Found in the PSX versions of the game.
- */
- #define ARGO_CVG_HEADER_SIZE 12
- #define ARGO_CVG_BLOCK_ALIGN 0x10
- #define ARGO_CVG_NB_BLOCKS 32
- #define ARGO_CVG_SAMPLES_PER_BLOCK 28
- typedef struct ArgoCVGHeader {
- uint32_t size; /*< File size -8 (this + trailing checksum) */
- uint32_t unk1; /*< Unknown. Always seems to be 0 or 1. */
- uint32_t unk2; /*< Unknown. Always seems to be 0 or 1. */
- } ArgoCVGHeader;
- typedef struct ArgoCVGOverride {
- const char *name;
- ArgoCVGHeader header;
- uint32_t checksum;
- int sample_rate;
- } ArgoCVGOverride;
- typedef struct ArgoCVGDemuxContext {
- ArgoCVGHeader header;
- uint32_t checksum;
- uint32_t num_blocks;
- uint32_t blocks_read;
- } ArgoCVGDemuxContext;
- /* "Special" files that are played at a different rate. */
- static ArgoCVGOverride overrides[] = {
- { "CRYS.CVG", { 23592, 0, 1 }, 2495499, 88200 }, /* Beta */
- { "REDCRY88.CVG", { 38280, 0, 1 }, 4134848, 88200 }, /* Beta */
- { "DANLOOP1.CVG", { 54744, 1, 0 }, 5684641, 37800 }, /* Beta */
- { "PICKUP88.CVG", { 12904, 0, 1 }, 1348091, 48000 }, /* Beta */
- { "SELECT1.CVG", { 5080, 0, 1 }, 549987, 44100 }, /* Beta */
- };
- static int argo_cvg_probe(const AVProbeData *p)
- {
- ArgoCVGHeader cvg;
- /*
- * It's almost impossible to detect these files based
- * on the header alone. File extension is (unfortunately)
- * the best way forward.
- */
- if (!av_match_ext(p->filename, "cvg"))
- return 0;
- if (p->buf_size < ARGO_CVG_HEADER_SIZE)
- return 0;
- cvg.size = AV_RL32(p->buf + 0);
- cvg.unk1 = AV_RL32(p->buf + 4);
- cvg.unk2 = AV_RL32(p->buf + 8);
- if (cvg.size < 8)
- return 0;
- if (cvg.unk1 != 0 && cvg.unk1 != 1)
- return 0;
- if (cvg.unk2 != 0 && cvg.unk2 != 1)
- return 0;
- return AVPROBE_SCORE_MAX / 4 + 1;
- }
- static int argo_cvg_read_checksum(AVIOContext *pb, const ArgoCVGHeader *cvg, uint32_t *checksum)
- {
- int ret;
- uint8_t buf[4];
- if (!(pb->seekable & AVIO_SEEKABLE_NORMAL)) {
- *checksum = 0;
- return 0;
- }
- if ((ret = avio_seek(pb, cvg->size + 4, SEEK_SET)) < 0)
- return ret;
- /* NB: Not using avio_rl32() because no error checking. */
- if ((ret = avio_read(pb, buf, sizeof(buf))) < 0)
- return ret;
- else if (ret != sizeof(buf))
- return AVERROR(EIO);
- if ((ret = avio_seek(pb, ARGO_CVG_HEADER_SIZE, SEEK_SET)) < 0)
- return ret;
- *checksum = AV_RL32(buf);
- return 0;
- }
- static int argo_cvg_read_header(AVFormatContext *s)
- {
- int ret;
- AVStream *st;
- AVCodecParameters *par;
- uint8_t buf[ARGO_CVG_HEADER_SIZE];
- const char *filename = av_basename(s->url);
- ArgoCVGDemuxContext *ctx = s->priv_data;
- if (!(st = avformat_new_stream(s, NULL)))
- return AVERROR(ENOMEM);
- if ((ret = avio_read(s->pb, buf, ARGO_CVG_HEADER_SIZE)) < 0)
- return ret;
- else if (ret != ARGO_CVG_HEADER_SIZE)
- return AVERROR(EIO);
- ctx->header.size = AV_RL32(buf + 0);
- ctx->header.unk1 = AV_RL32(buf + 4);
- ctx->header.unk2 = AV_RL32(buf + 8);
- if (ctx->header.size < 8)
- return AVERROR_INVALIDDATA;
- av_log(s, AV_LOG_TRACE, "size = %u\n", ctx->header.size);
- av_log(s, AV_LOG_TRACE, "unk = %u, %u\n", ctx->header.unk1, ctx->header.unk2);
- if ((ret = argo_cvg_read_checksum(s->pb, &ctx->header, &ctx->checksum)) < 0)
- return ret;
- av_log(s, AV_LOG_TRACE, "checksum = %u\n", ctx->checksum);
- par = st->codecpar;
- par->codec_type = AVMEDIA_TYPE_AUDIO;
- par->codec_id = AV_CODEC_ID_ADPCM_PSX;
- par->sample_rate = 22050;
- for (size_t i = 0; i < FF_ARRAY_ELEMS(overrides); i++) {
- const ArgoCVGOverride *ovr = overrides + i;
- if (ovr->header.size != ctx->header.size ||
- ovr->header.unk1 != ctx->header.unk1 ||
- ovr->header.unk2 != ctx->header.unk2 ||
- ovr->checksum != ctx->checksum ||
- av_strcasecmp(filename, ovr->name) != 0)
- continue;
- av_log(s, AV_LOG_TRACE, "found override, name = %s\n", ovr->name);
- par->sample_rate = ovr->sample_rate;
- break;
- }
- par->channels = 1;
- par->channel_layout = AV_CH_LAYOUT_MONO;
- par->bits_per_coded_sample = 4;
- par->bits_per_raw_sample = 16;
- par->block_align = ARGO_CVG_BLOCK_ALIGN;
- par->bit_rate = par->sample_rate * par->bits_per_coded_sample;
- ctx->num_blocks = (ctx->header.size - 8) / ARGO_CVG_BLOCK_ALIGN;
- av_log(s, AV_LOG_TRACE, "num blocks = %u\n", ctx->num_blocks);
- avpriv_set_pts_info(st, 64, 1, par->sample_rate);
- st->start_time = 0;
- st->duration = ctx->num_blocks * ARGO_CVG_SAMPLES_PER_BLOCK;
- st->nb_frames = ctx->num_blocks;
- return 0;
- }
- static int argo_cvg_read_packet(AVFormatContext *s, AVPacket *pkt)
- {
- int ret;
- AVStream *st = s->streams[0];
- ArgoCVGDemuxContext *ctx = s->priv_data;
- if (ctx->blocks_read >= ctx->num_blocks)
- return AVERROR_EOF;
- ret = av_get_packet(s->pb, pkt, st->codecpar->block_align *
- FFMIN(ARGO_CVG_NB_BLOCKS, ctx->num_blocks - ctx->blocks_read));
- if (ret < 0)
- return ret;
- if (ret % st->codecpar->block_align != 0)
- return AVERROR_INVALIDDATA;
- pkt->stream_index = 0;
- pkt->duration = ARGO_CVG_SAMPLES_PER_BLOCK * (ret / st->codecpar->block_align);
- pkt->pts = ctx->blocks_read * ARGO_CVG_SAMPLES_PER_BLOCK;
- pkt->flags &= ~AV_PKT_FLAG_CORRUPT;
- ctx->blocks_read += ret / st->codecpar->block_align;
- return 0;
- }
- static int argo_cvg_seek(AVFormatContext *s, int stream_index,
- int64_t pts, int flags)
- {
- int64_t ret;
- ArgoCVGDemuxContext *ctx = s->priv_data;
- if (pts != 0 || stream_index != 0)
- return AVERROR(EINVAL);
- if ((ret = avio_seek(s->pb, ARGO_CVG_HEADER_SIZE, SEEK_SET)) < 0)
- return ret;
- ctx->blocks_read = 0;
- return 0;
- }
- const AVInputFormat ff_argo_cvg_demuxer = {
- .name = "argo_cvg",
- .long_name = NULL_IF_CONFIG_SMALL("Argonaut Games CVG"),
- .priv_data_size = sizeof(ArgoCVGDemuxContext),
- .read_probe = argo_cvg_probe,
- .read_header = argo_cvg_read_header,
- .read_packet = argo_cvg_read_packet,
- .read_seek = argo_cvg_seek,
- };
|