From c6c82428af51ab63068234a038564acfd4960eec Mon Sep 17 00:00:00 2001 From: Paul B Mahol Date: Sun, 21 Feb 2021 13:33:07 +0100 Subject: [PATCH] avformat: add Digital Pictures SGA game demuxer --- Changelog | 1 + libavformat/Makefile | 1 + libavformat/allformats.c | 1 + libavformat/sga.c | 477 +++++++++++++++++++++++++++++++++++++++ libavformat/version.h | 2 +- 5 files changed, 481 insertions(+), 1 deletion(-) create mode 100644 libavformat/sga.c diff --git a/Changelog b/Changelog index ba5aebf79d..9e7f67cc19 100644 --- a/Changelog +++ b/Changelog @@ -77,6 +77,7 @@ version : - OpenEXR image encoder - Simbiosis IMX decoder - Simbiosis IMX demuxer +- Digital Pictures SGA demuxer and decoders version 4.3: diff --git a/libavformat/Makefile b/libavformat/Makefile index 56fa96ea7f..48b91ea4d0 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..957ca0f4cd --- /dev/null +++ b/libavformat/sga.c @@ -0,0 +1,477 @@ +/* + * 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 sample_rate; + int first_audio_size; + int payload_size; + int packet_type; + int flags; + 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; + int last_left = 0; + int sample_rate = -1; + + if (p->buf_size < 2048) + return 0; + + for (int i = 0; i + 2 < p->buf_size; i += 2048) { + int header = AV_RB16(src + i); + + if ((header > 0x07FE && header < 0x8100) || + (header > 0x8200 && header < 0xA100) || + (header > 0xA200 && header < 0xC100)) { + sectors = 0; + break; + } + } + + for (int i = 0; i + 4 < p->buf_size;) { + int header = AV_RB16(src + i); + int left = AV_RB16(src + i + 2); + int offset, type, size; + + if (last_left < 0) + return 0; + if (sectors && header && last_left == 0) { + if (left <= 8) + return 0; + last_left = left; + } else if (sectors && header) { + left = header; + last_left -= left; + if (header != 0x7FE && left < 7) + return 0; + } else if (sectors) { + if (left <= 8) + return 0; + i += sectors ? 2048 : left + 4; + last_left = 0; + continue; + } + + if (sectors && (i > 0 && left < 0x7fe) && + (i + left + 14 < p->buf_size)) { + offset = i + left + 2; + } else if (sectors && i > 0) { + i += 2048; + last_left -= FFMIN(last_left, 2046); + continue; + } else { + offset = 0; + last_left = left; + } + + header = AV_RB16(src + offset); + size = AV_RB16(src + offset + 2) + 4; + + while ((header & 0xFF00) == 0) { + offset++; + if (offset + 4 >= p->buf_size) + break; + header = AV_RB16(src + offset); + size = AV_RB16(src + offset + 2) + 4; + } + + if (offset + 12 >= p->buf_size) + break; + if ((header & 0xFF) > 1) + return 0; + type = header >> 8; + + if (type == 0xAA || + type == 0xA1 || + type == 0xA2 || + type == 0xA3) { + int new_rate; + + if (size <= 12) + return 0; + new_rate = AV_RB16(src + offset + 8); + if (sample_rate < 0) + sample_rate = new_rate; + if (sample_rate == 0 || new_rate != sample_rate) + return 0; + if (src[offset + 10] != 1) + return 0; + + score += 10; + } else if (type == 0xC1 || + type == 0xC6 || + type == 0xC7 || + type == 0xC8 || + type == 0xC9 || + type == 0xCB || + type == 0xCD || + type == 0xE7) { + int nb_pals = src[offset + 9]; + int tiles_w = src[offset + 10]; + int tiles_h = src[offset + 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; + + score += 10; + } else if (header == 0x7FE) { + ; + } else { + return 0; + } + + i += sectors ? 2048 : size + 4; + last_left -= FFMIN(last_left, 2046); + + 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->first_audio_size = 0; + sga->video_stream_index = -1; + sga->audio_stream_index = -1; + sga->left = 2048; + sga->idx = 0; + + s->ctx_flags |= AVFMTCTX_NOHEADER; + + if (pb->seekable & AVIO_SEEKABLE_NORMAL) { + while (!avio_feof(pb)) { + int header = avio_rb16(pb); + int type = header >> 8; + int skip = 2046; + int clock; + + if (!sga->first_audio_size && + (type == 0xAA || + type == 0xA1 || + type == 0xA2 || + type == 0xA3)) { + sga->first_audio_size = avio_rb16(pb); + avio_skip(pb, 4); + clock = avio_rb16(pb); + sga->sample_rate = av_rescale(clock, + SEGA_CD_PCM_NUM, + SEGA_CD_PCM_DEN); + skip -= 8; + } + if ((header > 0x07FE && header < 0x8100) || + (header > 0x8200 && header < 0xA100) || + (header > 0xA200 && header < 0xC100)) { + sga->sector_headers = 0; + break; + } + + avio_skip(pb, skip); + } + + avio_seek(pb, 0, SEEK_SET); + } + + return 0; +} + +static void print_stats(AVFormatContext *s, const char *where) +{ + SGADemuxContext *sga = s->priv_data; + + av_log(s, AV_LOG_DEBUG, "START %s\n", where); + av_log(s, AV_LOG_DEBUG, "pos: %lX\n", avio_tell(s->pb)); + av_log(s, AV_LOG_DEBUG, "idx: %X\n", sga->idx); + av_log(s, AV_LOG_DEBUG, "packet_type: %X\n", sga->packet_type); + av_log(s, AV_LOG_DEBUG, "payload_size: %X\n", sga->payload_size); + av_log(s, AV_LOG_DEBUG, "SECTOR: %016lX\n", AV_RB64(sga->sector)); + av_log(s, AV_LOG_DEBUG, "stream: %X\n", sga->sector[1]); + av_log(s, AV_LOG_DEBUG, "END %s\n", where); +} + +static void update_type_size(AVFormatContext *s) +{ + SGADemuxContext *sga = s->priv_data; + + if (sga->idx >= 4) { + sga->packet_type = sga->sector[0]; + sga->payload_size = AV_RB16(sga->sector + 2); + } else { + sga->packet_type = 0; + sga->payload_size = 0; + } +} + +static int sga_video_packet(AVFormatContext *s, AVPacket *pkt) +{ + SGADemuxContext *sga = s->priv_data; + int ret; + + if (sga->payload_size <= 8) + return AVERROR_INVALIDDATA; + + if (sga->video_stream_index == -1) { + AVRational frame_rate; + + 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; + + if (sga->first_audio_size > 0 && sga->sample_rate > 0) { + frame_rate.num = sga->sample_rate; + frame_rate.den = sga->first_audio_size; + } else { + frame_rate.num = 15; + frame_rate.den = 1; + } + avpriv_set_pts_info(st, 64, frame_rate.den, frame_rate.num); + } + + ret = av_new_packet(pkt, sga->payload_size + 4); + if (ret < 0) + return AVERROR(ENOMEM); + memcpy(pkt->data, sga->sector, sga->payload_size + 4); + av_assert0(sga->idx >= sga->payload_size + 4); + memmove(sga->sector, sga->sector + sga->payload_size + 4, sga->idx - sga->payload_size - 4); + + pkt->stream_index = sga->video_stream_index; + pkt->duration = 1; + pkt->pos = sga->pkt_pos; + pkt->flags |= sga->flags; + sga->idx -= sga->payload_size + 4; + sga->flags = 0; + update_type_size(s); + + av_log(s, AV_LOG_DEBUG, "VIDEO PACKET: %d:%016lX i:%X\n", pkt->size, AV_RB64(sga->sector), sga->idx); + + return 0; +} + +static int sga_audio_packet(AVFormatContext *s, AVPacket *pkt) +{ + SGADemuxContext *sga = s->priv_data; + int ret; + + if (sga->payload_size <= 8) + 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->payload_size - 8); + if (ret < 0) + return AVERROR(ENOMEM); + memcpy(pkt->data, sga->sector + 12, sga->payload_size - 8); + av_assert0(sga->idx >= sga->payload_size + 4); + memmove(sga->sector, sga->sector + sga->payload_size + 4, sga->idx - sga->payload_size - 4); + + pkt->stream_index = sga->audio_stream_index; + pkt->duration = pkt->size; + pkt->pos = sga->pkt_pos; + pkt->flags |= sga->flags; + sga->idx -= sga->payload_size + 4; + sga->flags = 0; + update_type_size(s); + + av_log(s, AV_LOG_DEBUG, "AUDIO PACKET: %d:%016lX i:%X\n", pkt->size, AV_RB64(sga->sector), sga->idx); + + return 0; +} + +static int sga_packet(AVFormatContext *s, AVPacket *pkt) +{ + SGADemuxContext *sga = s->priv_data; + int ret = 0; + + if (sga->packet_type == 0xCD || + sga->packet_type == 0xCB || + sga->packet_type == 0xC9 || + sga->packet_type == 0xC8 || + sga->packet_type == 0xC7 || + 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 { + if (sga->idx == 0) + return AVERROR_EOF; + if (sga->sector[0]) + return AVERROR_INVALIDDATA; + memmove(sga->sector, sga->sector + 1, sga->idx - 1); + sga->idx--; + return AVERROR(EAGAIN); + } + + return ret; +} + +static int try_packet(AVFormatContext *s, AVPacket *pkt) +{ + SGADemuxContext *sga = s->priv_data; + int ret = AVERROR(EAGAIN); + + update_type_size(s); + if (sga->idx >= sga->payload_size + 4) { + print_stats(s, "before sga_packet"); + ret = sga_packet(s, pkt); + print_stats(s, "after sga_packet"); + if (ret != AVERROR(EAGAIN)) + return ret; + } + + return sga->idx < sga->payload_size + 4 ? AVERROR(EAGAIN) : ret; +} + +static int sga_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + SGADemuxContext *sga = s->priv_data; + AVIOContext *pb = s->pb; + int header, ret = 0; + + sga->pkt_pos = avio_tell(pb); + +retry: + update_type_size(s); + + print_stats(s, "start"); + if (avio_feof(pb) && + (!sga->payload_size || sga->idx < sga->payload_size + 4)) + return AVERROR_EOF; + + if (sga->idx < sga->payload_size + 4) { + ret = ffio_ensure_seekback(pb, 2); + if (ret < 0) + return ret; + + print_stats(s, "before read header"); + header = avio_rb16(pb); + if (!header) { + avio_skip(pb, 2046); + sga->left = 0; + } else if (!avio_feof(pb) && + ((header >> 15) || + !sga->sector_headers)) { + avio_seek(pb, -2, SEEK_CUR); + sga->flags = AV_PKT_FLAG_KEY; + sga->left = 2048; + } else { + sga->left = 2046; + } + + av_assert0(sga->idx + sga->left < sizeof(sga->sector)); + ret = avio_read(pb, sga->sector + sga->idx, sga->left); + if (ret > 0) + sga->idx += ret; + else if (ret != AVERROR_EOF && ret) + return ret; + print_stats(s, "after read header"); + + update_type_size(s); + } + + 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->payload_size = sga->idx = 0; + memset(sga->sector, 0, sizeof(sga->sector)); + + 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, +}; diff --git a/libavformat/version.h b/libavformat/version.h index c11d885b83..6ce2135ee1 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 58 -#define LIBAVFORMAT_VERSION_MINOR 68 +#define LIBAVFORMAT_VERSION_MINOR 69 #define LIBAVFORMAT_VERSION_MICRO 100 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \