diff mbox series

[FFmpeg-devel] avformat: add Argonaut Games CVG demuxer

Message ID 20210430140303.10905-1-zane@zanevaniperen.com
State Superseded
Headers show
Series [FFmpeg-devel] avformat: add Argonaut Games CVG demuxer
Related show

Checks

Context Check Description
andriy/x86_make success Make finished
andriy/x86_make_fate success Make fate finished
andriy/PPC64_make success Make finished
andriy/PPC64_make_fate success Make fate finished

Commit Message

Zane van Iperen April 30, 2021, 2:03 p.m. UTC
Signed-off-by: Zane van Iperen <zane@zanevaniperen.com>
---
 Changelog                |   1 +
 MAINTAINERS              |   1 +
 libavformat/Makefile     |   1 +
 libavformat/allformats.c |   1 +
 libavformat/argo_cvg.c   | 251 +++++++++++++++++++++++++++++++++++++++
 libavformat/version.h    |   2 +-
 6 files changed, 256 insertions(+), 1 deletion(-)
 create mode 100644 libavformat/argo_cvg.c
diff mbox series

Patch

diff --git a/Changelog b/Changelog
index ad950354d0..9567009d63 100644
--- a/Changelog
+++ b/Changelog
@@ -4,6 +4,7 @@  releases are sorted from youngest to oldest.
 version <next>:
 - ADPCM IMA Westwood encoder
 - Westwood AUD muxer
+- Argonaut Games CVG demuxer
 
 
 version 4.4:
diff --git a/MAINTAINERS b/MAINTAINERS
index ed8eddb317..dcac46003e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -399,6 +399,7 @@  Muxers/Demuxers:
   apngdec.c                             Benoit Fouet
   argo_asf.c                            Zane van Iperen
   argo_brp.c                            Zane van Iperen
+  argo_cvg.c                            Zane van Iperen
   ass*                                  Aurelien Jacobs
   astdec.c                              Paul B Mahol
   astenc.c                              James Almer
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 85b5d8e7eb..0dca1ffd77 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -107,6 +107,7 @@  OBJS-$(CONFIG_AQTITLE_DEMUXER)           += aqtitledec.o subtitles.o
 OBJS-$(CONFIG_ARGO_ASF_DEMUXER)          += argo_asf.o
 OBJS-$(CONFIG_ARGO_ASF_MUXER)            += argo_asf.o
 OBJS-$(CONFIG_ARGO_BRP_DEMUXER)          += argo_brp.o argo_asf.o
+OBJS-$(CONFIG_ARGO_CVG_DEMUXER)          += argo_cvg.o
 OBJS-$(CONFIG_ASF_DEMUXER)               += asfdec_f.o asf.o asfcrypt.o \
                                             avlanguage.o
 OBJS-$(CONFIG_ASF_O_DEMUXER)             += asfdec_o.o asf.o asfcrypt.o \
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 0c672ad8c8..923af3f649 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -66,6 +66,7 @@  extern const AVInputFormat  ff_aqtitle_demuxer;
 extern const AVInputFormat  ff_argo_asf_demuxer;
 extern const AVOutputFormat ff_argo_asf_muxer;
 extern const AVInputFormat  ff_argo_brp_demuxer;
+extern const AVInputFormat  ff_argo_cvg_demuxer;
 extern const AVInputFormat  ff_asf_demuxer;
 extern const AVOutputFormat ff_asf_muxer;
 extern const AVInputFormat  ff_asf_o_demuxer;
diff --git a/libavformat/argo_cvg.c b/libavformat/argo_cvg.c
new file mode 100644
index 0000000000..d576d8dd6f
--- /dev/null
+++ b/libavformat/argo_cvg.c
@@ -0,0 +1,251 @@ 
+/*
+ * 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.
+ * They are mostly found in the leaked Croc PSX Tech Demo and Beta.
+ */
+
+#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. */
+} ArgoCVGHeader;
+
+typedef struct ArgoCVGOverride {
+    const char    *name;
+    ArgoCVGHeader header;
+    uint32_t      checksum;
+    int           sample_rate;
+} ArgoCVGOverride;
+
+typedef struct ArgoCVGContext {
+    ArgoCVGHeader header;
+    uint32_t      checksum;
+    uint32_t      num_blocks;
+    uint32_t      blocks_read;
+} ArgoCVGContext;
+
+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);
+
+    /* The largest file I know of. */
+    if (cvg.size < 8 || cvg.size > 110552)
+        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);
+    ArgoCVGContext *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->channels;
+    par->bit_rate               = par->channels *
+                                  par->sample_rate *
+                                  par->bits_per_coded_sample;
+
+    /*
+     * Trailing data is usually a checksum + garbage.
+     * The last block always seems to be invalid.
+     */
+    ctx->num_blocks = ((ctx->header.size - 8) / (ARGO_CVG_BLOCK_ALIGN * par->channels)) - 1;
+
+    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];
+    ArgoCVGContext *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;
+    ArgoCVGContext *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(ArgoCVGContext),
+    .read_probe     = argo_cvg_probe,
+    .read_header    = argo_cvg_read_header,
+    .read_packet    = argo_cvg_read_packet,
+    .read_seek      = argo_cvg_seek,
+};
diff --git a/libavformat/version.h b/libavformat/version.h
index 987a38dbb4..53273334e2 100644
--- a/libavformat/version.h
+++ b/libavformat/version.h
@@ -32,7 +32,7 @@ 
 // Major bumping may affect Ticket5467, 5421, 5451(compatibility with Chromium)
 // Also please add any ticket numbers that you believe might be affected here
 #define LIBAVFORMAT_VERSION_MAJOR  59
-#define LIBAVFORMAT_VERSION_MINOR   0
+#define LIBAVFORMAT_VERSION_MINOR   1
 #define LIBAVFORMAT_VERSION_MICRO 100
 
 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \