From patchwork Tue Feb 23 17:25:09 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: 25922 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 AC8BE449F21 for ; Tue, 23 Feb 2021 19:31:50 +0200 (EET) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 881C068AAE6; Tue, 23 Feb 2021 19:31:50 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-lj1-f181.google.com (mail-lj1-f181.google.com [209.85.208.181]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 7C995689AB3 for ; Tue, 23 Feb 2021 19:31:44 +0200 (EET) Received: by mail-lj1-f181.google.com with SMTP id g1so58084085ljj.13 for ; Tue, 23 Feb 2021 09:31:44 -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=WiATOEp1SbVALUEoXasUXUlYmH0rHUWb7p2rgVm9cTE=; b=B2HhvPf4nFcKp1hdjyXnrq/W42JAQfUojpvV/QqWx5/dCqnAUR43a8qnoRiTbNnzoD U37S6I1Wkmj+Ttx9KVP33gRFlzAXOQx/m9CN3USrJptycWg6yaQ8lvXIY2Zxyq8WRMJF oW6mRKW3fgyK/NyeDazOF2N0rlxGw7oQv0tpKfJYGkJpBCFCx+znfqACww6d8kgFKTut pg0ZEG4YC5wSXI0/3tJbwrdvCifjbz6+kfxLQMM5wRcp101E92nNMppZx7YiR+ptzABu QUVSRxXvhuJWTfEqwdJ0t6xvgYImA+NBcyztJcW2VCaXVBj73/w3kUjLlJVnvk4s2k98 MVfw== 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=WiATOEp1SbVALUEoXasUXUlYmH0rHUWb7p2rgVm9cTE=; b=uMuFamGr4r51lN2YfphVo3uOV2AczdgJaQzaukEFZlGSVchSLh7PA64LEOK2scWvQh Zecr5rTQH2lc8VhmLIYz/bjbm/QESUWjNU61+5H5BoJ6ze2TCKExjtjDF81Z+klU+jAA mVMym8bDle/I7AdnpJtqh51zJfdvngCiazg7vRsFg14UMb9beO91otCoXIOcgsE7hnR4 VTpvAeGwxldhWkBKlRNnKx+FGUdTzYTL5WKOTLBTWf2NUpwPP3Gl5hRKxaTILx1RCKsI EPI7EJmSq2OAhDN89yFiMM6kY1ccaH/X9W0/Fscwqm5QdGIjvQm2a74a4Po8X3Y+/54I 06OA== X-Gm-Message-State: AOAM531MQTeiwrp6oiXDvAMSj47OTCqhGgy7xZZlUAnG05O7wsDc9t/z NR7qxiqpDrYvnxbP82q0odQKcAOSol++8Q== X-Google-Smtp-Source: ABdhPJzczSQwuUJ6q5HHapzfOnbAXhXTyeVCngXuz7v1Y1lEr5qAktPFLLx+qqFtWukdHm28fQ7CPA== X-Received: by 2002:a17:906:43d7:: with SMTP id j23mr26921402ejn.519.1614101126425; Tue, 23 Feb 2021 09:25:26 -0800 (PST) Received: from localhost.localdomain ([109.227.52.18]) by smtp.gmail.com with ESMTPSA id s18sm12964456ejc.79.2021.02.23.09.25.25 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 23 Feb 2021 09:25:26 -0800 (PST) From: Paul B Mahol To: ffmpeg-devel@ffmpeg.org Date: Tue, 23 Feb 2021 18:25:09 +0100 Message-Id: <20210223172509.29631-3-onemda@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20210223172509.29631-1-onemda@gmail.com> References: <20210223172509.29631-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 | 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, +};