[FFmpeg-devel] [PATCH 3/3] avformat: add Digital Pictures SGA game demuxer
Paul B Mahol
onemda at gmail.com
Tue Feb 23 19:25:09 EET 2021
Signed-off-by: Paul B Mahol <onemda at gmail.com>
---
libavformat/Makefile | 1 +
libavformat/allformats.c | 1 +
libavformat/sga.c | 388 +++++++++++++++++++++++++++++++++++++++
3 files changed, 390 insertions(+)
create mode 100644 libavformat/sga.c
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 1555b9c2ca..dc271d1d8b 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -502,6 +502,7 @@ OBJS-$(CONFIG_SEGAFILM_DEMUXER) += segafilm.o
OBJS-$(CONFIG_SEGAFILM_MUXER) += segafilmenc.o
OBJS-$(CONFIG_SEGMENT_MUXER) += segment.o
OBJS-$(CONFIG_SER_DEMUXER) += serdec.o
+OBJS-$(CONFIG_SGA_DEMUXER) += sga.o
OBJS-$(CONFIG_SHORTEN_DEMUXER) += shortendec.o rawdec.o
OBJS-$(CONFIG_SIFF_DEMUXER) += siff.o
OBJS-$(CONFIG_SIMBIOSIS_IMX_DEMUXER) += imx.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 3b69423508..ade247640c 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -401,6 +401,7 @@ extern AVOutputFormat ff_segafilm_muxer;
extern AVOutputFormat ff_segment_muxer;
extern AVOutputFormat ff_stream_segment_muxer;
extern AVInputFormat ff_ser_demuxer;
+extern AVInputFormat ff_sga_demuxer;
extern AVInputFormat ff_shorten_demuxer;
extern AVInputFormat ff_siff_demuxer;
extern AVInputFormat ff_simbiosis_imx_demuxer;
diff --git a/libavformat/sga.c b/libavformat/sga.c
new file mode 100644
index 0000000000..6f5b85926b
--- /dev/null
+++ b/libavformat/sga.c
@@ -0,0 +1,388 @@
+/*
+ * Digital Pictures SGA game demuxer
+ *
+ * Copyright (C) 2021 Paul B Mahol
+ *
+ * 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 "libavutil/intreadwrite.h"
+#include "libavutil/avassert.h"
+#include "libavutil/internal.h"
+#include "avformat.h"
+#include "internal.h"
+#include "avio_internal.h"
+
+#define SEGA_CD_PCM_NUM 12500000
+#define SEGA_CD_PCM_DEN 786432
+
+typedef struct SGADemuxContext {
+ int video_stream_index;
+ int audio_stream_index;
+
+ uint8_t sector[65536 * 2];
+ int sector_headers;
+ int packet_size;
+ int packet_type;
+ int idx;
+ int left;
+ int64_t pkt_pos;
+} SGADemuxContext;
+
+static int sga_probe(const AVProbeData *p)
+{
+ const uint8_t *src = p->buf;
+ int score = 0, sectors = 1;
+
+ for (int i = 0; i + 12 < p->buf_size; i++) {
+ int header = AV_RB16(src + i);
+ int size = AV_RB16(src + i + 2);
+ int cnt, skip, stream;
+ int type;
+
+ if ((header > 0x07FE && header < 0x8100) ||
+ (header > 0x8200 && header < 0xA100) ||
+ (header > 0xA200 && header < 0xC100)) {
+ if (sectors) {
+ sectors = 0;
+ score--;
+ } else {
+ score -= 10;
+ }
+ } else {
+ type = header >> 8;
+ stream = header & 0xFF;
+
+ if (type == 0xAA ||
+ type == 0xA1 ||
+ type == 0xA2 ||
+ type == 0xA3) {
+ if (size <= 12)
+ return 0;
+ if (AV_RB16(src + i + 8) == 0)
+ return 0;
+ } else if (type == 0xC1 ||
+ type == 0xC6 ||
+ type == 0xC8 ||
+ type == 0xC9 ||
+ type == 0xCB ||
+ type == 0xCD ||
+ type == 0xE7) {
+ int nb_pals = src[i + 9];
+ int tiles_w = src[i + 10];
+ int tiles_h = src[i + 11];
+
+ if (size <= 12)
+ return 0;
+ if (nb_pals == 0 || nb_pals > 4)
+ return 0;
+ if (tiles_w == 0 || tiles_w > 80)
+ return 0;
+ if (tiles_h == 0 || tiles_h > 60)
+ return 0;
+ } else if (!header) {
+ if (i <= 1)
+ return 0;
+ if ((i & 2047) != 2047 &&
+ (i & 2047) != 0)
+ i = FFALIGN(i, 2048) - 1;
+ continue;
+ } else {
+ score -= 5;
+ continue;
+ }
+ }
+
+ if (sectors) {
+ cnt = size + 4;
+ skip = 0;
+ while (cnt >= 2046) {
+ skip += 2;
+ cnt -= 2046;
+ }
+
+ size += skip + 3;
+ } else {
+ size += 4;
+ }
+
+ i += size;
+ score += 10;
+
+ if (score < 0)
+ break;
+ }
+
+ return av_clip(score, 0, AVPROBE_SCORE_MAX);
+}
+
+static int sga_read_header(AVFormatContext *s)
+{
+ SGADemuxContext *sga = s->priv_data;
+ AVIOContext *pb = s->pb;
+
+ sga->sector_headers = 1;
+ sga->video_stream_index = -1;
+ sga->audio_stream_index = -1;
+ sga->idx = 0;
+
+ s->ctx_flags |= AVFMTCTX_NOHEADER;
+
+ while (!avio_feof(pb)) {
+ int header = avio_rb16(pb);
+
+ if ((header > 0x07FE && header < 0x8100) ||
+ (header > 0x8200 && header < 0xA100) ||
+ (header > 0xA200 && header < 0xC100)) {
+ sga->sector_headers = 0;
+ break;
+ }
+
+ avio_skip(pb, 2046);
+ }
+
+ avio_seek(pb, 0, SEEK_SET);
+
+ return 0;
+}
+
+static int sga_video_packet(AVFormatContext *s, AVPacket *pkt)
+{
+ SGADemuxContext *sga = s->priv_data;
+ int ret;
+
+ if (sga->packet_size <= 12)
+ return AVERROR_INVALIDDATA;
+
+ if (sga->video_stream_index == -1) {
+ AVStream *st = avformat_new_stream(s, NULL);
+ if (!st)
+ return AVERROR(ENOMEM);
+
+ st->start_time = 0;
+ st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
+ st->codecpar->codec_tag = 0;
+ st->codecpar->codec_id = AV_CODEC_ID_SGA_VIDEO;
+ sga->video_stream_index = st->index;
+
+ avpriv_set_pts_info(st, 64, 1, 15);
+ }
+
+ ret = av_new_packet(pkt, sga->packet_size);
+ if (ret < 0)
+ return AVERROR(ENOMEM);
+ memcpy(pkt->data, sga->sector, sga->packet_size);
+ av_assert0(sga->idx >= sga->packet_size);
+ memmove(sga->sector, sga->sector + sga->packet_size, sga->idx - sga->packet_size);
+
+ pkt->stream_index = sga->video_stream_index;
+ pkt->duration = 1;
+ pkt->pos = sga->pkt_pos;
+ pkt->flags |= AV_PKT_FLAG_KEY;
+ sga->idx -= sga->packet_size;
+ sga->packet_size = 0;
+ sga->packet_type = 0;
+
+ return 0;
+}
+
+static int sga_audio_packet(AVFormatContext *s, AVPacket *pkt)
+{
+ SGADemuxContext *sga = s->priv_data;
+ int ret;
+
+ if (sga->packet_size <= 12)
+ return AVERROR_INVALIDDATA;
+
+ if (sga->audio_stream_index == -1) {
+ AVStream *st = avformat_new_stream(s, NULL);
+ if (!st)
+ return AVERROR(ENOMEM);
+
+ st->start_time = 0;
+ st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
+ st->codecpar->codec_tag = 0;
+ st->codecpar->codec_id = AV_CODEC_ID_PCM_SGA;
+ st->codecpar->channels = 1;
+ st->codecpar->channel_layout= AV_CH_LAYOUT_MONO;
+ st->codecpar->sample_rate = av_rescale(AV_RB16(sga->sector + 8),
+ SEGA_CD_PCM_NUM,
+ SEGA_CD_PCM_DEN);
+ sga->audio_stream_index = st->index;
+
+ avpriv_set_pts_info(st, 64, 1, st->codecpar->sample_rate);
+ }
+
+ ret = av_new_packet(pkt, sga->packet_size - 12);
+ if (ret < 0)
+ return AVERROR(ENOMEM);
+ memcpy(pkt->data, sga->sector + 12, sga->packet_size - 12);
+ av_assert0(sga->idx >= sga->packet_size);
+ memmove(sga->sector, sga->sector + sga->packet_size, sga->idx - sga->packet_size);
+
+ pkt->stream_index = sga->audio_stream_index;
+ pkt->duration = pkt->size;
+ pkt->pos = sga->pkt_pos;
+ pkt->flags |= AV_PKT_FLAG_KEY;
+ sga->idx -= sga->packet_size;
+ sga->packet_size = 0;
+ sga->packet_type = 0;
+
+ return 0;
+}
+
+static void update_type_size(AVFormatContext *s)
+{
+ SGADemuxContext *sga = s->priv_data;
+
+ if (sga->idx >= 4) {
+ sga->packet_type = sga->sector[0];
+ sga->packet_size = AV_RB16(sga->sector + 2) + 4;
+ }
+}
+
+static int sga_packet(AVFormatContext *s, AVPacket *pkt)
+{
+ SGADemuxContext *sga = s->priv_data;
+ AVIOContext *pb = s->pb;
+ int ret = 0;
+
+ if (avio_feof(pb))
+ return AVERROR_EOF;
+
+ if (sga->packet_type == 0xCD ||
+ sga->packet_type == 0xCB ||
+ sga->packet_type == 0xC9 ||
+ sga->packet_type == 0xC8 ||
+ sga->packet_type == 0xC6 ||
+ sga->packet_type == 0xC1 ||
+ sga->packet_type == 0xE7) {
+ ret = sga_video_packet(s, pkt);
+ } else if (sga->packet_type == 0xA1 ||
+ sga->packet_type == 0xA2 ||
+ sga->packet_type == 0xA3 ||
+ sga->packet_type == 0xAA) {
+ ret = sga_audio_packet(s, pkt);
+ } else {
+ av_log(s, AV_LOG_DEBUG, "Unknown chunk: %X\n", sga->packet_type);
+ return AVERROR(EAGAIN);
+ }
+
+ return ret;
+}
+
+static int try_packet(AVFormatContext *s, AVPacket *pkt)
+{
+ SGADemuxContext *sga = s->priv_data;
+ int ret = 0;
+
+ do {
+ update_type_size(s);
+
+ if (sga->idx < sga->packet_size || sga->idx < 4)
+ return AVERROR(EAGAIN);
+
+ ret = sga_packet(s, pkt);
+ if (ret != AVERROR(EAGAIN))
+ break;
+
+ av_assert0(sga->idx > 0);
+ memmove(sga->sector, sga->sector + 1, sga->idx - 1);
+ sga->idx--;
+ } while (1);
+
+ return ret;
+}
+
+static int sga_read_packet(AVFormatContext *s, AVPacket *pkt)
+{
+ SGADemuxContext *sga = s->priv_data;
+ AVIOContext *pb = s->pb;
+ int64_t pos;
+ int ret = 0;
+
+ sga->pkt_pos = avio_tell(pb);
+
+retry:
+ if (avio_feof(pb))
+ return AVERROR_EOF;
+
+ update_type_size(s);
+
+ pos = avio_tell(pb);
+ if (pos == 0) {
+ ret = avio_read(pb, sga->sector, 2048);
+ if (ret < 0)
+ return ret;
+ sga->idx += ret;
+ } else {
+ int packet = 0;
+
+ ret = ffio_ensure_seekback(pb, 2);
+ if (ret < 0)
+ return ret;
+ if (avio_rb16(pb) >> 15 || !sga->sector_headers) {
+ avio_seek(pb, -2, SEEK_CUR);
+
+ ret = try_packet(s, pkt);
+ packet = ret == 0;
+ if (sga->sector_headers)
+ sga->idx = sga->packet_type = sga->packet_size = 0;
+ sga->left = 2048;
+ } else {
+ sga->left = 2046;
+ }
+
+ av_assert0(sga->idx >= 0);
+ if (sga->idx + sga->left > sizeof(sga->sector))
+ return AVERROR_INVALIDDATA;
+ ret = avio_read(pb, sga->sector + sga->idx, sga->left);
+ if (ret < 0)
+ return ret;
+ sga->idx += ret;
+ if (packet)
+ return ret;
+ }
+
+ ret = try_packet(s, pkt);
+ if (ret == AVERROR(EAGAIN))
+ goto retry;
+
+ return ret;
+}
+
+static int sga_seek(AVFormatContext *s, int stream_index,
+ int64_t timestamp, int flags)
+{
+ SGADemuxContext *sga = s->priv_data;
+
+ sga->packet_type = sga->packet_size = sga->idx = 0;
+
+ return -1;
+}
+
+AVInputFormat ff_sga_demuxer = {
+ .name = "sga",
+ .long_name = NULL_IF_CONFIG_SMALL("Digital Pictures SGA"),
+ .priv_data_size = sizeof(SGADemuxContext),
+ .read_probe = sga_probe,
+ .read_header = sga_read_header,
+ .read_packet = sga_read_packet,
+ .read_seek = sga_seek,
+ .extensions = "sga",
+ .flags = AVFMT_GENERIC_INDEX,
+};
--
2.17.1
More information about the ffmpeg-devel
mailing list