From patchwork Fri Feb 26 11:29:19 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul B Mahol X-Patchwork-Id: 26006 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id 3E5F744A7D5 for ; Fri, 26 Feb 2021 13:29:38 +0200 (EET) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 0D57E68A7B8; Fri, 26 Feb 2021 13:29:38 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-ej1-f41.google.com (mail-ej1-f41.google.com [209.85.218.41]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 30FF768A4A3 for ; Fri, 26 Feb 2021 13:29:32 +0200 (EET) Received: by mail-ej1-f41.google.com with SMTP id lr13so14210969ejb.8 for ; Fri, 26 Feb 2021 03:29:32 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id:in-reply-to:references; bh=GLhuULIYQ1oj4WDaSdzb126AqzPu3h1EpJENfydDHaM=; b=YOliLMPR8p6VPu/ixqmjTnOQeg7a88D7hCN0CzI7cMSwYuzCUNPQ6e70uKLPMd1mtV hvAufhafg4UzfdZytO8yLb/SEeyqVw9zVxcJSeMezioofKjXkcDF0EobF1kQz5gdFWZK onR7mdyNNOB8ADfD9m2zgMbIO6gDXXyQbnuBQRgYMt1dPymjaT/doxER9MgjfNKOOz3C gd4L55/IVnWx5PkeY518N8jTbHFG/wfxKuCgXc8Fw43kcjbo9VSApgVa0pD5VPzalOy+ aprtGdk1qtHnbZnFlvZH+LgB9oYxNm/sfEdYVUCRFs2hYA/p1VVSq9KgS0c84GTpHnXb C84Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id:in-reply-to :references; bh=GLhuULIYQ1oj4WDaSdzb126AqzPu3h1EpJENfydDHaM=; b=haFmTo9Q6rXiGhlvc7XRWQ+tsCXUu+YDoLffUBabQ2obVNSUAHXYsOzTGmbFrjylA5 hKOZ1XDw1rF23pQ2R+E4bRS3Dv3d6ZPHyhE1NoVZOeQ3UkvzlA2ETsculU+uksHqnwpJ XxiuQBRLGevr8A4vp1NoaIV8zm/304IggQ6vYWRs05EfLdFkLeww4+ixlGX/5RDi7N63 cxFamYD2dNreLzuuRsFdc1kdGOND65SZgAXvH3bhfoYy0kwBuEKxtzn5MTOkNE7rcord M2Ug3QH2P0+weYXzFY2muGlt4xwfPaRoB2QseN+l9LvrZWS9DN5uV9/UjPYcybSbH028 bJMw== X-Gm-Message-State: AOAM531K2KALg8O9LGftt+5cZR0kikzyo3t8En2fWqXYavqbdySP+IiP Hw9foYL6FvPJ01Q2K8oEDXDJho1qvWY= X-Google-Smtp-Source: ABdhPJyuY1ubfDsJikarRu5MNGm4OuzFrcgnK50v2ZPkPUtlXdYBLVdSRk8OuEtVL59yh21woslyhQ== X-Received: by 2002:a17:906:6047:: with SMTP id p7mr2860389ejj.400.1614338971629; Fri, 26 Feb 2021 03:29:31 -0800 (PST) Received: from localhost.localdomain ([109.227.52.18]) by smtp.gmail.com with ESMTPSA id t8sm5348817edv.16.2021.02.26.03.29.30 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Feb 2021 03:29:31 -0800 (PST) From: Paul B Mahol To: ffmpeg-devel@ffmpeg.org Date: Fri, 26 Feb 2021 12:29:19 +0100 Message-Id: <20210226112919.20437-3-onemda@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20210226112919.20437-1-onemda@gmail.com> References: <20210226112919.20437-1-onemda@gmail.com> Subject: [FFmpeg-devel] [PATCH 3/3] avformat: add Digital Pictures SGA game demuxer X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Signed-off-by: Paul B Mahol --- libavformat/Makefile | 1 + libavformat/allformats.c | 1 + libavformat/sga.c | 473 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 475 insertions(+) create mode 100644 libavformat/sga.c diff --git a/libavformat/Makefile b/libavformat/Makefile index 95ed25e866..4b03e9fa92 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..3dfce4e11e --- /dev/null +++ b/libavformat/sga.c @@ -0,0 +1,473 @@ +/* + * 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 (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 <= 8) + 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; + 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; + + 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, +};