mirror of https://github.com/FFmpeg/FFmpeg.git
Signed-off-by: Zane van Iperen <zane@zanevaniperen.com>pull/362/head
parent
d1c28c6c78
commit
9789ea59d0
6 changed files with 251 additions and 2 deletions
@ -0,0 +1,245 @@ |
||||
/*
|
||||
* 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, |
||||
}; |
Loading…
Reference in new issue