From patchwork Wed Jan 10 08:46:09 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marth64 X-Patchwork-Id: 45551 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:bf2f:b0:199:de12:6fa6 with SMTP id gc47csp911237pzb; Wed, 10 Jan 2024 00:47:20 -0800 (PST) X-Google-Smtp-Source: AGHT+IGPXBI6czGrwbzpdTSczphZv2xHWBzeEU0X1isi1YZej0fUQF+duQ044W2qEAFb6y4T8ucd X-Received: by 2002:a17:907:7891:b0:a2b:f864:dd27 with SMTP id ku17-20020a170907789100b00a2bf864dd27mr239574ejc.7.1704876440057; Wed, 10 Jan 2024 00:47:20 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1704876440; cv=none; d=google.com; s=arc-20160816; b=sGuVnFqFcX7GhYY6k37Y5qIJ4XZ4hWiUsXd0VhCeqCAVoqtKP9a3/FPIDlJb6CEgz0 pEBlwuoTVV+2EweoLVVwr+PlwUx3hLaCnYnikI54MeTy2HPLxGskROVG6QAJ9v00oY+Y EGuy16iKoldCnyTzI4GIDZPpcvtBUVy4fVhmdDLH0Ns+ehI1M6EXuxrQ8xsSXSd4It9v N0DgNHtQi5cvdZVRrbdDJM1ig8vezCU8lRXGXvHtKifwV+rrjugIFdNb6sgEldDamfKL fD7EZI8DIKMqJI0dhOzeeDEXQF66132qQIMHt4GPghZGkhGPg2GDdifkvxtCivhltk6g DA6w== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:from:dkim-signature:delivered-to; bh=/JapT3nOQO3Ly2Z94AoCUnGTKsvAwwy3mcDx+OwVsoE=; fh=PlWMzmI9LD2qGS7ipLrQl8z0iaQTLQLHzoGuXcBzpCg=; b=Pj471g1ZkEyweJG3LhSHz5DXYnVPBXBkso/okN+7acBJ5E6dAaJvyl0rJEcAw+h22Q Y4Q6G5g/0H7ahox7LT02EGG2wIdrlZ5tLirDMOmuhsHbSCiidUvAdPwmCYwBKFgK1Izd FMEJtHfP7lu7ps39HeJgK4To18drQa+zvDmUjdub1VvcrCHa5LWPuUEEgXlo+GFEuiVt cfsdVAJisWpsROKQjqI59CxSsyasmU9ZVuhEbTVCSENHi4+1tz74ewfYM0x42BSy4nw2 CRAG8XjTaGG8pgblC8FQBY2O492oklbWFOlC6GHls1HwK/UF2UCnt1MlQVgGl4T2BGVS T2hw== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@proxyid.net header.s=google header.b=HWFWaRBW; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id ot3-20020a170906ccc300b00a2b039afe35si1603812ejb.618.2024.01.10.00.47.19; Wed, 10 Jan 2024 00:47:20 -0800 (PST) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@proxyid.net header.s=google header.b=HWFWaRBW; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 17EA668CEBC; Wed, 10 Jan 2024 10:47:15 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-pg1-f227.google.com (mail-pg1-f227.google.com [209.85.215.227]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 046DF68C867 for ; Wed, 10 Jan 2024 10:47:08 +0200 (EET) Received: by mail-pg1-f227.google.com with SMTP id 41be03b00d2f7-5cdf76cde78so1756365a12.1 for ; Wed, 10 Jan 2024 00:47:08 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=proxyid.net; s=google; t=1704876427; x=1705481227; darn=ffmpeg.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=E4rjssVuFZfdZwRCxkZLZzEWFpYnztZzB3msNlM5YJ8=; b=HWFWaRBWSNDItuFgRe1XZLLkHZxZ46/RJ94OECRJzM9aW/40Ti5xYZJNvZC8HXALpD 2j9JUHRmVGH0B9H6Vgg/18gBS0sJ6iu5U+pj8EoBtTUE5z2BRFjSKglAVBzkyTC8yL0I XpfJ5f9+ebTZLVU7N7u6PqWuSUcs13OFUCmfIyO2BSF1LYGrk2/1qrBToly6ISo2qz0j pBaOo/fcseNW1N0HZMwgV6w1sjkD/7HwErPPH2xkyvqUodC3VwQbS7JPgjGx8BMV9i4v iBKqZ90KgrOLdsKRHjAdaqGjSWJmyDyQohl9lBIR1wRNoknGcLyqDqVKa2XyXzR/WXO7 zPWA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1704876427; x=1705481227; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=E4rjssVuFZfdZwRCxkZLZzEWFpYnztZzB3msNlM5YJ8=; b=uPfCoW3W3adN3zHk6rQ00ZJlQc7u+i5sC6vc+TRMGE93JqhWYJq3tP0v73eR8tmyzz 1u7WUukWJ/ga+xp9WgsRLAXxnDOuaLVmi11KJDHNkydbi0h/2EGHf4Kl7/4xXh+cGS31 M5Z6yWRiyx772u7Go7an/1/kOpqa85b9gO+gyAaJn/oqzrHgCK0B2HK4IFYiJ/LVnYAt IeykDAQqvnGQ3GHcaF3jhRWKSaoQMzINFP6IOx6HNZX10cNHteZ/m6uav6jeNyNarFx7 vuVwwCTBrYAFzNUFDl1CvtzVvF/6x48XHu3ncPoi9bfJrxqkmDaB2v1VkqipQskIowAm aKaA== X-Gm-Message-State: AOJu0YxEC+/4emVGYAzNNbvmcK3RhZ4g53y5hfkL3hGbQsR4oBhfdSHC JD0s4k8yuzsDO06cnaCW5bcQX6NzN5OjTtt6yzBhHiRGOd2OSWxpsYW1QIGw0SAUPQ== X-Received: by 2002:a05:6a21:99a9:b0:199:9542:3e06 with SMTP id ve41-20020a056a2199a900b0019995423e06mr450687pzb.109.1704876426933; Wed, 10 Jan 2024 00:47:06 -0800 (PST) Received: from wsx-cc1-001.. (c-76-141-0-17.hsd1.il.comcast.net. [76.141.0.17]) by smtp-relay.gmail.com with ESMTPS id jv4-20020a170903058400b001d0ae3177b9sm118412plb.91.2024.01.10.00.47.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 10 Jan 2024 00:47:06 -0800 (PST) X-Relaying-Domain: proxyid.net From: Marth64 To: ffmpeg-devel@ffmpeg.org Date: Wed, 10 Jan 2024 02:46:09 -0600 Message-Id: <20240110084608.1889310-1-marth64@proxyid.net> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20231209100639.607234-1-marth64@proxyid.net> References: <20231209100639.607234-1-marth64@proxyid.net> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v2] dvdvideo: add DVD-Video demuxer, powered by libdvdnav and libdvdread X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 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 Cc: Marth64 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: w0Zsn8A9Cti8 Dust off those old DVD discs! This version is greatly simplified and improved from the patch submitted in December. Discontinuity issues are resolved, and many discs should work smoothly out of the box. Also, GPL sector validation code is removed. Basic Usage: ffmpeg -f dvdvideo -title 1 -i PATH_TO_DVD_STRUCTURE ... You will have to set your own title number, although the default value (1) gives good coverage. Note that this demuxer will process one PGC only. If you are working with a multi-PGC title or strange authoring, you may specify and target an exact PGC/PG (see options). Known issues: * Chapter points are not precise, this is highest priority to fix * Subtitle palette support will come in the next patchset (to consolidate shared code) * Some oddly authored discs with 1 cell and a command link back to itself will loop infinitely, I am debugging this scenario. Not implemented: * Shuffle PGC mode, menus, or rarely used DVD features like karaoke extensions * SDDS codec is not supported, as I don't have any material to test it with * Seeking by time is a low priority at this time * Probing will come but I have some concerns to think through first Signed-off-by: Marth64 --- Changelog | 1 + configure | 8 + libavformat/Makefile | 1 + libavformat/allformats.c | 1 + libavformat/avlanguage.c | 10 +- libavformat/dvdvideodec.c | 1022 +++++++++++++++++++++++++++++++++++++ 6 files changed, 1041 insertions(+), 2 deletions(-) create mode 100644 libavformat/dvdvideodec.c diff --git a/Changelog b/Changelog index 5b2899d05b..1b377fed2f 100644 --- a/Changelog +++ b/Changelog @@ -18,6 +18,7 @@ version : - lavu/eval: introduce randomi() function in expressions - VVC decoder - fsync filter +- DVD-Video demuxer, powered by libdvdnav and libdvdread version 6.1: - libaribcaption decoder diff --git a/configure b/configure index e87a09ce83..1f21f4f1c2 100755 --- a/configure +++ b/configure @@ -227,6 +227,8 @@ External library support: --enable-libdavs2 enable AVS2 decoding via libdavs2 [no] --enable-libdc1394 enable IIDC-1394 grabbing using libdc1394 and libraw1394 [no] + --enable-libdvdnav enable libdvdnav, needed for DVD demuxing [no] + --enable-libdvdread enable libdvdread, needed for DVD demuxing [no] --enable-libfdk-aac enable AAC de/encoding via libfdk-aac [no] --enable-libflite enable flite (voice synthesis) support via libflite [no] --enable-libfontconfig enable libfontconfig, useful for drawtext filter [no] @@ -1806,6 +1808,8 @@ EXTERNAL_LIBRARY_GPL_LIST=" frei0r libcdio libdavs2 + libdvdnav + libdvdread librubberband libvidstab libx264 @@ -3519,6 +3523,8 @@ dts_demuxer_select="dca_parser" dtshd_demuxer_select="dca_parser" dv_demuxer_select="dvprofile" dv_muxer_select="dvprofile" +dvdvideo_demuxer_select="mpegps_demuxer" +dvdvideo_demuxer_deps="libdvdnav libdvdread" dxa_demuxer_select="riffdec" eac3_demuxer_select="ac3_parser" evc_demuxer_select="evc_frame_merge_bsf evc_parser" @@ -6760,6 +6766,8 @@ enabled libdav1d && require_pkg_config libdav1d "dav1d >= 0.5.0" "dav1d enabled libdavs2 && require_pkg_config libdavs2 "davs2 >= 1.6.0" davs2.h davs2_decoder_open enabled libdc1394 && require_pkg_config libdc1394 libdc1394-2 dc1394/dc1394.h dc1394_new enabled libdrm && check_pkg_config libdrm libdrm xf86drm.h drmGetVersion +enabled libdvdnav && require_pkg_config libdvdnav "dvdnav >= 6.1.1" dvdnav/dvdnav.h dvdnav_open2 +enabled libdvdread && require_pkg_config libdvdread "dvdread >= 6.1.2" dvdread/dvd_reader.h DVDOpen2 -ldvdread enabled libfdk_aac && { check_pkg_config libfdk_aac fdk-aac "fdk-aac/aacenc_lib.h" aacEncOpen || { require libfdk_aac fdk-aac/aacenc_lib.h aacEncOpen -lfdk-aac && warn "using libfdk without pkg-config"; } } diff --git a/libavformat/Makefile b/libavformat/Makefile index 581e378d95..3c1cb21fe2 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -192,6 +192,7 @@ OBJS-$(CONFIG_DTS_MUXER) += rawenc.o OBJS-$(CONFIG_DV_MUXER) += dvenc.o OBJS-$(CONFIG_DVBSUB_DEMUXER) += dvbsub.o rawdec.o OBJS-$(CONFIG_DVBTXT_DEMUXER) += dvbtxt.o rawdec.o +OBJS-$(CONFIG_DVDVIDEO_DEMUXER) += dvdvideodec.o OBJS-$(CONFIG_DXA_DEMUXER) += dxa.o OBJS-$(CONFIG_EA_CDATA_DEMUXER) += eacdata.o OBJS-$(CONFIG_EA_DEMUXER) += electronicarts.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index ce6be5f04d..ea88d4c094 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -150,6 +150,7 @@ extern const AVInputFormat ff_dv_demuxer; extern const FFOutputFormat ff_dv_muxer; extern const AVInputFormat ff_dvbsub_demuxer; extern const AVInputFormat ff_dvbtxt_demuxer; +extern const AVInputFormat ff_dvdvideo_demuxer; extern const AVInputFormat ff_dxa_demuxer; extern const AVInputFormat ff_ea_demuxer; extern const AVInputFormat ff_ea_cdata_demuxer; diff --git a/libavformat/avlanguage.c b/libavformat/avlanguage.c index 782a58adb2..202d9aa835 100644 --- a/libavformat/avlanguage.c +++ b/libavformat/avlanguage.c @@ -29,7 +29,7 @@ typedef struct LangEntry { uint16_t next_equivalent; } LangEntry; -static const uint16_t lang_table_counts[] = { 484, 20, 184 }; +static const uint16_t lang_table_counts[] = { 484, 20, 190 }; static const uint16_t lang_table_offsets[] = { 0, 484, 504 }; static const LangEntry lang_table[] = { @@ -539,7 +539,7 @@ static const LangEntry lang_table[] = { /*0501*/ { "slk", 647 }, /*0502*/ { "sqi", 652 }, /*0503*/ { "zho", 686 }, - /*----- AV_LANG_ISO639_1 entries (184) -----*/ + /*----- AV_LANG_ISO639_1 entries (190) -----*/ /*0504*/ { "aa" , 0 }, /*0505*/ { "ab" , 1 }, /*0506*/ { "ae" , 33 }, @@ -724,6 +724,12 @@ static const LangEntry lang_table[] = { /*0685*/ { "za" , 478 }, /*0686*/ { "zh" , 78 }, /*0687*/ { "zu" , 480 }, + /*0688*/ { "in" , 195 }, /* deprecated */ + /*0689*/ { "iw" , 172 }, /* deprecated */ + /*0690*/ { "ji" , 472 }, /* deprecated */ + /*0691*/ { "jw" , 202 }, /* deprecated */ + /*0692*/ { "mo" , 358 }, /* deprecated */ + /*0693*/ { "sh" , 693 }, /* deprecated (no equivalent) */ { "", 0 } }; diff --git a/libavformat/dvdvideodec.c b/libavformat/dvdvideodec.c new file mode 100644 index 0000000000..c99adb0d70 --- /dev/null +++ b/libavformat/dvdvideodec.c @@ -0,0 +1,1022 @@ +/* + * DVD-Video demuxer, powered by libdvdnav and libdvdread + * Author: Marth64 + * + * 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 + */ + +/** + * DVD-Video is not a directly accessible, linear container format in the + * traditional sense. Instead, it allows for complex and programmatic + * playback of carefully muxed streams. A typical DVD player relies on + * user GUI interaction to drive the direction of the demuxing. + * Ultimately, the logical playback sequence is defined by a title's PGC + * and a user selected "angle". An additional layer of control is defined by + * NAV packets in the MPEG-PS, but as these are processed by libdvdnav, + * they are witheld from the output of this demuxer. + * + * Therefore, the high-level approach is as follows: + * 1) Open the volume with libdvdread + * 2) Gather information about the user-requested title and PGC coordinates + * 3) Request playback at the coordinates and chosen angle with libdvdnav + * 4) Seek playback to first cell at the coordinates (skipping stills, etc.) + * 5) Begin the playback (reading and demuxing) of MPEG-PS blocks + * 6) End playback if the PGC or angle change, or nav leads to a menu or backwards + * 7) Close resources + **/ + +#include +#include +#include +#include +#include + +#include "libavutil/avutil.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/mem.h" +#include "libavutil/opt.h" +#include "libavutil/samplefmt.h" +#include "libavutil/timestamp.h" + +#include "libavcodec/avcodec.h" +#include "libavformat/avio_internal.h" +#include "libavformat/avlanguage.h" +#include "libavformat/avformat.h" +#include "libavformat/demux.h" +#include "libavformat/internal.h" +#include "libavformat/url.h" + +#define DVDVIDEO_MAX_PS_SEARCH_BLOCKS 128 +#define DVDVIDEO_BLOCK_SIZE 2048 +#define DVDVIDEO_TIME_BASE_Q (AVRational) { 1, 90000 } +#define DVDVIDEO_PTS_WRAP_BITS 32 /* DVD uses 32 (PES allows 33) */ + +#define DVDVIDEO_SUBP_CLUT_LEN 16 +#define DVDVIDEO_SUBP_CLUT_SIZE DVDVIDEO_SUBP_CLUT_LEN * sizeof(uint32_t) + +typedef struct DVDVideoVTSVideoStreamEntry { + int startcode; + enum AVCodecID codec_id; + int width; + int height; + AVRational dar; + AVRational framerate; + int has_cc; +} DVDVideoVTSVideoStreamEntry; + +typedef struct DVDVideoPGCAudioStreamEntry { + int startcode; + enum AVCodecID codec_id; + int sample_fmt; + int sample_rate; + int bit_depth; + int nb_channels; + AVChannelLayout ch_layout; + char *lang_iso; +} DVDVideoPGCAudioStreamEntry; + +typedef struct DVDVideoPGCSubtitleStreamEntry { + int startcode; + uint32_t *clut; + char *lang_iso; +} DVDVideoPGCSubtitleStreamEntry; + +typedef struct DVDVideoDemuxContext { + const AVClass *class; + + /* options */ + int opt_title; /* the user-provided title number (1-indexed) */ + int opt_ptt; /* the user-provided PTT number (1-indexed) */ + int opt_pgc; /* the user-provided PGC number (1-indexed) */ + int opt_pg; /* the user-provided PG number (1-indexed) */ + int opt_angle; /* the user-provided angle number (1-indexed) */ + int opt_region; /* the user-provided region digit */ + + /* subdemux */ + const AVInputFormat *mpeg_fmt; /* inner MPEG-PS (VOB) demuxer */ + AVFormatContext *mpeg_ctx; /* context for inner demuxer */ + uint8_t *mpeg_buf; /* buffer for inner demuxer */ + FFIOContext mpeg_pb; /* buffer context for inner demuxer */ + + /* volume */ + dvd_reader_t *dvdread; /* handle to libdvdread */ + ifo_handle_t *vmg_ifo; /* handle to the VMG (VIDEO_TS.IFO) */ + ifo_handle_t *vts_ifo; /* handle to the active VTS (VTS_nn_n.IFO) */ + dvdnav_t *dvdnav; /* handle to libdvdnav */ + + /* playback control */ + pgc_t *play_pgc; /* handle to the active PGC */ + int64_t play_ts_offset; /* PTS discontinuity offset (e.g. VOB change) */ + int64_t play_vobu_e_ptm; /* end PTS of the current VOBU */ + int play_vtsn; /* number of the active VTS (video title set) */ + int play_celln; /* number of the active cell */ + int play_pgn; /* number of the active program */ + int play_ptt; /* number of the active PTT (chapter) */ + int play_in_vts; /* if our play state is in the VTS */ + int play_in_pgc; /* if our play state is in the PGC */ + int play_in_ps; /* if our play state is in the program stream */ + int play_skip_cell; /* if this cell is being skipped*/ + int play_skip_cell_last;/* if last cell was skipped */ + +} DVDVideoDemuxContext; + +static void dvdvideo_pgc_close(AVFormatContext *s) +{ + DVDVideoDemuxContext *c = s->priv_data; + + av_log(s, AV_LOG_TRACE, "closing DVD volume\n"); + + if (c->dvdnav) + dvdnav_close(c->dvdnav); + + if (c->vts_ifo) + ifoClose(c->vts_ifo); + + if (c->vmg_ifo) + ifoClose(c->vmg_ifo); + + if (c->dvdread) + DVDClose(c->dvdread); +} + +static int dvdvideo_pgc_open(AVFormatContext *s) +{ + DVDVideoDemuxContext *c = s->priv_data; + + dvdnav_status_t dvdnav_open_status; + + title_info_t title_info; + int cur_title, cur_pgcn, cur_pgn; + + int32_t disc_region_mask; + int32_t player_region_mask; + + c->dvdread = DVDOpen(s->url); + if (!c->dvdread) + goto end_fail_external; + + if (!(c->vmg_ifo = ifoOpen(c->dvdread, 0))) + goto end_fail_external; + + if (c->opt_title > c->vmg_ifo->tt_srpt->nr_of_srpts) { + av_log(s, AV_LOG_ERROR, "Title not found\n"); + + return AVERROR_STREAM_NOT_FOUND; + } + + title_info = c->vmg_ifo->tt_srpt->title[c->opt_title - 1]; + if (c->opt_angle > title_info.nr_of_angles) { + av_log(s, AV_LOG_ERROR, "Angle not found\n"); + + return AVERROR_STREAM_NOT_FOUND; + } + + if (title_info.nr_of_ptts < 1) { + av_log(s, AV_LOG_ERROR, "Title invalid\n"); + + return AVERROR_INVALIDDATA; + } + + if (!(c->vts_ifo = ifoOpen(c->dvdread, title_info.title_set_nr))) + goto end_fail_external; + + if (title_info.vts_ttn < 1 + || title_info.vts_ttn > 99 + || title_info.vts_ttn > c->vts_ifo->vts_ptt_srpt->nr_of_srpts + || c->vts_ifo->vtsi_mat->nr_of_vts_audio_streams > 8 + || c->vts_ifo->vtsi_mat->nr_of_vts_subp_streams > 32) { + av_log(s, AV_LOG_ERROR, "Title invalid in VTS\n"); + + return AVERROR_INVALIDDATA; + } + + dvdnav_open_status = dvdnav_open(&c->dvdnav, s->url); + if (!c->dvdnav) + goto end_fail_external; + + if (dvdnav_open_status != DVDNAV_STATUS_OK + || dvdnav_set_readahead_flag(c->dvdnav, 0) != DVDNAV_STATUS_OK + || dvdnav_set_PGC_positioning_flag(c->dvdnav, 1) != DVDNAV_STATUS_OK + || dvdnav_get_region_mask(c->dvdnav, &disc_region_mask) != DVDNAV_STATUS_OK) + goto end_fail_external; + + player_region_mask = c->opt_region > 0 ? (1 << (c->opt_region - 1)) : disc_region_mask; + if (dvdnav_set_region_mask(c->dvdnav, player_region_mask) != DVDNAV_STATUS_OK) + goto end_fail_external; + + if (c->opt_pgc > 0 && c->opt_pg > 0) { + if (dvdnav_program_play(c->dvdnav, c->opt_title, c->opt_pgc, c->opt_pg) != DVDNAV_STATUS_OK) + goto end_fail_external; + } else { + if (dvdnav_part_play(c->dvdnav, c->opt_title, c->opt_ptt) != DVDNAV_STATUS_OK + || dvdnav_current_title_program(c->dvdnav, &cur_title, &cur_pgcn, &cur_pgn) != DVDNAV_STATUS_OK) + goto end_fail_external; + + c->opt_pgc = cur_pgcn; + c->opt_pg = cur_pgn; + } + + if (dvdnav_angle_change(c->dvdnav, c->opt_angle) != DVDNAV_STATUS_OK) + goto end_fail_external; + + /* update the context */ + c->play_vtsn = title_info.title_set_nr; + c->play_pgc = c->vts_ifo->vts_pgcit->pgci_srp[c->opt_pgc - 1].pgc; + + if (c->play_pgc->pg_playback_mode != 0) { + av_log(s, AV_LOG_ERROR, "Sorry, non-sequential PGCs are not supported\n"); + + return AVERROR_PATCHWELCOME; + } + + return 0; + +end_fail_external: + av_log(s, AV_LOG_ERROR, "Unable to start DVD playback at coordinates\n"); + + return AVERROR_EXTERNAL; +} + +static int dvdvideo_pgc_chapters_setup(AVFormatContext *s) +{ + DVDVideoDemuxContext *c = s->priv_data; + + uint64_t duration; + uint64_t *times; + uint64_t time_prev = 0; + int nb_chapters = 0; + + /* as a side effect of dvdnav_describe_title_chapters(), beneficial safety checks are done */ + nb_chapters = dvdnav_describe_title_chapters(c->dvdnav, c->opt_title, ×, &duration); + if (!nb_chapters) + return AVERROR_EXTERNAL; + + for (int i = c->opt_ptt - 1; i < nb_chapters - 1; i++) { + uint64_t time_effective = times[i]; + + if (!avpriv_new_chapter(s, i, DVDVIDEO_TIME_BASE_Q, time_prev, time_effective, NULL)) { + av_log(s, AV_LOG_ERROR, "Unable to allocate chapter\n"); + return AVERROR(ENOMEM); + } + + time_prev = time_effective; + } + + free(times); + + s->duration = av_rescale_q(duration, DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q); + + return 0; +} + +static int dvdvideo_video_stream_analyze(video_attr_t video_attr, + DVDVideoVTSVideoStreamEntry *entry) +{ + AVRational framerate; + int height = 0; + int width = 0; + int is_pal = video_attr.video_format == 1; + + framerate = is_pal ? (AVRational) { 25, 1 } : (AVRational) { 30000, 1001 }; + height = is_pal ? 576 : 480; + + if (height > 0) { + switch (video_attr.picture_size) { + case 0: /* D1 */ + width = 720; + break; + case 1: /* 4CIF */ + width = 704; + break; + case 2: /* Half D1 */ + width = 352; + break; + case 3: /* CIF */ + width = 352; + height /= 2; + break; + } + } + + if (!width || !height) { + return AVERROR_INVALIDDATA; + } + + entry->startcode = 0x1E0; + entry->codec_id = !video_attr.mpeg_version ? AV_CODEC_ID_MPEG1VIDEO : AV_CODEC_ID_MPEG2VIDEO; + entry->width = width; + entry->height = height; + entry->dar = video_attr.display_aspect_ratio ? (AVRational) { 16, 9 } : (AVRational) { 4, 3 }; + entry->framerate = framerate; + entry->has_cc = video_attr.line21_cc_1 || video_attr.line21_cc_2; + + return 0; +} + +static int dvdvideo_video_stream_add(AVFormatContext *s, + DVDVideoVTSVideoStreamEntry *entry, enum AVStreamParseType need_parsing) +{ + AVStream *st; + FFStream *sti; + + st = avformat_new_stream(s, NULL); + if (!st) { + return AVERROR(ENOMEM); + } + + st->id = entry->startcode; + st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; + st->codecpar->codec_id = entry->codec_id; + st->codecpar->width = entry->width; + st->codecpar->height = entry->height; + st->codecpar->format = AV_PIX_FMT_YUV420P; + st->codecpar->color_range = AVCOL_RANGE_MPEG; + + st->codecpar->framerate = entry->framerate; +#if FF_API_R_FRAME_RATE + st->r_frame_rate = entry->framerate; +#endif + st->avg_frame_rate = entry->framerate; + + sti = ffstream(st); + sti->request_probe = 0; + sti->need_parsing = need_parsing; + sti->display_aspect_ratio = entry->dar; + sti->avctx->framerate = entry->framerate; + + if (entry->has_cc) + sti->avctx->properties |= FF_CODEC_PROPERTY_CLOSED_CAPTIONS; + + avpriv_set_pts_info(st, DVDVIDEO_PTS_WRAP_BITS, + DVDVIDEO_TIME_BASE_Q.num, DVDVIDEO_TIME_BASE_Q.den); + + return 0; +} + +static int dvdvideo_video_stream_setup(AVFormatContext *s) +{ + DVDVideoDemuxContext *c = s->priv_data; + + DVDVideoVTSVideoStreamEntry *entry; + int ret; + + entry = av_malloc(sizeof(DVDVideoVTSVideoStreamEntry)); + + if ((ret = dvdvideo_video_stream_analyze(c->vts_ifo->vtsi_mat->vts_video_attr, entry)) < 0 + || (ret = dvdvideo_video_stream_add(c->mpeg_ctx, entry, AVSTREAM_PARSE_FULL)) < 0 + || (ret = dvdvideo_video_stream_add(s, entry, AVSTREAM_PARSE_NONE)) < 0) { + av_free(entry); + av_log(s, AV_LOG_ERROR, "Unable to allocate video stream\n"); + + return ret; + } + + av_free(entry); + + return 0; +} + +static int dvdvideo_audio_stream_analyze(audio_attr_t audio_attr, uint16_t audio_control, + DVDVideoPGCAudioStreamEntry *entry) +{ + int startcode = 0; + enum AVCodecID codec_id = AV_CODEC_ID_NONE; + int sample_fmt = AV_SAMPLE_FMT_NONE; + int sample_rate = 0; + int bit_depth = 0; + int nb_channels = 0; + AVChannelLayout ch_layout; + char lang_dvd[3] = {0}; + + int position = (audio_control & 0x7F00) >> 8; + + switch (audio_attr.audio_format) { + case 0: + codec_id = AV_CODEC_ID_AC3; + sample_fmt = AV_SAMPLE_FMT_FLTP; + sample_rate = 48000; + startcode = 0x80 + position; + break; + case 2: + codec_id = AV_CODEC_ID_MP1; + sample_fmt = audio_attr.quantization ? AV_SAMPLE_FMT_S32 : AV_SAMPLE_FMT_S16; + sample_rate = 48000; + bit_depth = audio_attr.quantization ? 20 : 16; + startcode = 0x1C0 + position; + break; + case 3: + codec_id = AV_CODEC_ID_MP2; + sample_fmt = audio_attr.quantization ? AV_SAMPLE_FMT_S32 : AV_SAMPLE_FMT_S16; + sample_rate = 48000; + bit_depth = audio_attr.quantization ? 20 : 16; + startcode = 0x1C0 + position; + break; + case 4: + codec_id = audio_attr.quantization ? AV_CODEC_ID_PCM_S32LE : AV_CODEC_ID_PCM_S16LE; + sample_fmt = audio_attr.quantization ? AV_SAMPLE_FMT_S32 : AV_SAMPLE_FMT_S16; + sample_rate = audio_attr.sample_frequency ? 96000 : 48000; + bit_depth = audio_attr.quantization == 2 ? 24 : (audio_attr.quantization ? 20 : 16); + startcode = 0xA0 + position; + break; + case 6: + codec_id = AV_CODEC_ID_DTS; + sample_fmt = AV_SAMPLE_FMT_FLTP; + sample_rate = 48000; + bit_depth = audio_attr.quantization == 2 ? 24 : (audio_attr.quantization ? 20 : 16); + startcode = 0x88 + position; + break; + } + + nb_channels = audio_attr.channels + 1; + + if (codec_id == AV_CODEC_ID_NONE || startcode == 0 || sample_fmt == AV_SAMPLE_FMT_NONE + || sample_rate == 0 || nb_channels == 0) { + return AVERROR_INVALIDDATA; + } + + if (nb_channels == 2) + ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_STEREO; + else if (nb_channels == 6) + ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_5POINT1; + else if (nb_channels == 8) + ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_7POINT1; + else + ch_layout = (AVChannelLayout) { 0 }; + + AV_WB16(lang_dvd, audio_attr.lang_code); + + entry->startcode = startcode; + entry->codec_id = codec_id; + entry->sample_rate = sample_rate; + entry->bit_depth = bit_depth; + entry->nb_channels = nb_channels; + entry->ch_layout = ch_layout; + entry->lang_iso = (char *) ff_convert_lang_to(lang_dvd, AV_LANG_ISO639_2_BIBL); + + return 0; +} + +static int dvdvideo_audio_stream_add(AVFormatContext *s, DVDVideoPGCAudioStreamEntry *entry, + enum AVStreamParseType need_parsing) +{ + AVStream *st; + FFStream *sti; + + st = avformat_new_stream(s, NULL); + if (!st) { + return AVERROR(ENOMEM); + } + + st->id = entry->startcode; + st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; + st->codecpar->codec_id = entry->codec_id; + st->codecpar->format = entry->sample_fmt; + st->codecpar->sample_rate = entry->sample_rate; + st->codecpar->bits_per_coded_sample = entry->bit_depth; + st->codecpar->bits_per_raw_sample = entry->bit_depth; + st->codecpar->ch_layout = entry->ch_layout; + st->codecpar->ch_layout.nb_channels = entry->nb_channels; + + if (entry->lang_iso) { + av_dict_set(&st->metadata, "language", entry->lang_iso, 0); + } + + sti = ffstream(st); + sti->request_probe = 0; + sti->need_parsing = need_parsing; + + avpriv_set_pts_info(st, DVDVIDEO_PTS_WRAP_BITS, + DVDVIDEO_TIME_BASE_Q.num, DVDVIDEO_TIME_BASE_Q.den); + + return 0; +} + +static int dvdvideo_audio_stream_add_all(AVFormatContext *s) +{ + DVDVideoDemuxContext *c = s->priv_data; + + int ret; + + for (int i = 0; i < c->vts_ifo->vtsi_mat->nr_of_vts_audio_streams; i++) { + DVDVideoPGCAudioStreamEntry *entry; + + if (!(c->play_pgc->audio_control[i] & 0x8000)) + continue; + + entry = av_malloc(sizeof(DVDVideoPGCAudioStreamEntry)); + if (!entry) + return AVERROR(ENOMEM); + + if ((ret = dvdvideo_audio_stream_analyze(c->vts_ifo->vtsi_mat->vts_audio_attr[i], + c->play_pgc->audio_control[i], entry)) < 0) + goto break_free_and_error; + + if (c->vts_ifo->vtsi_mat->vts_audio_attr[i].application_mode == 1) + av_log(s, AV_LOG_WARNING, "Audio %d: karaoke extension enabled\n", entry->startcode); + + for (int j = 0; j < s->nb_streams; j++) + if (s->streams[j]->id == entry->startcode) + goto continue_free; + + if ((ret = dvdvideo_audio_stream_add(c->mpeg_ctx, entry, AVSTREAM_PARSE_FULL)) < 0) + goto break_free_and_error; + + if ((ret = dvdvideo_audio_stream_add(s, entry, AVSTREAM_PARSE_NONE)) < 0) + goto break_free_and_error; + +continue_free: + av_free(entry); + continue; + +break_free_and_error: + av_free(entry); + av_log(s, AV_LOG_ERROR, "Unable to allocate audio stream\n"); + return ret; + } + + return 0; +} + +static int dvdvideo_subp_stream_analyze(AVFormatContext *s, uint32_t offset, subp_attr_t subp_attr, + DVDVideoPGCSubtitleStreamEntry *entry) +{ + DVDVideoDemuxContext *c = s->priv_data; + + char lang_dvd[3] = {0}; + + entry->startcode = 0x20 + (offset & 0x1F); + + entry->clut = av_mallocz(DVDVIDEO_SUBP_CLUT_SIZE); + memcpy(entry->clut, c->play_pgc->palette, DVDVIDEO_SUBP_CLUT_SIZE); + + // XXX: YUV2RGB conversion will be handled in upcoming dedicated patch + // ff_dvdvideo_subp_clut_yuv_to_rgb(entry->clut, DVDVIDEO_SUBP_CLUT_SIZE); + + AV_WB16(lang_dvd, subp_attr.lang_code); + entry->lang_iso = (char *) ff_convert_lang_to(lang_dvd, AV_LANG_ISO639_2_BIBL); + + return 0; +} + +static int dvdvideo_subp_stream_add(AVFormatContext *s, + DVDVideoPGCSubtitleStreamEntry *entry, enum AVStreamParseType need_parsing) +{ + AVStream *st; + FFStream *sti; + + st = avformat_new_stream(s, NULL); + if (!st) { + return AVERROR(ENOMEM); + } + + st->id = entry->startcode; + st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codecpar->codec_id = AV_CODEC_ID_DVD_SUBTITLE; + + // XXX: Palettes will be fixed in dedicated patchset (with shared utility) + // if ((ret = ff_alloc_extradata(st->codecpar, DVDVIDEO_SUBP_PALETTE_EXTRADATA_SIZE)) < 0) + // return ret; + // ff_dvdvideo_subp_palette_extradata_cat(entry->clut, DVDVIDEO_SUBP_CLUT_SIZE, + // st->codecpar->extradata, st->codecpar->extradata_size); + + if (entry->lang_iso) + av_dict_set(&st->metadata, "language", entry->lang_iso, 0); + + sti = ffstream(st); + sti->request_probe = 0; + sti->need_parsing = need_parsing; + + avpriv_set_pts_info(st, DVDVIDEO_PTS_WRAP_BITS, + DVDVIDEO_TIME_BASE_Q.num, DVDVIDEO_TIME_BASE_Q.den); + + return 0; +} + +static int dvdvideo_subp_stream_add_internal(AVFormatContext *s, + uint32_t offset, subp_attr_t subp_attr) +{ + DVDVideoDemuxContext *c = s->priv_data; + + DVDVideoPGCSubtitleStreamEntry *entry; + int ret = 0; + + entry = av_malloc(sizeof(DVDVideoPGCSubtitleStreamEntry)); + + if ((ret = dvdvideo_subp_stream_analyze(s, offset, subp_attr, entry)) < 0) + goto end_free_error; + + for (int i = 0; i < s->nb_streams; i++) + if (s->streams[i]->id == entry->startcode) + goto end_free; + + if ((ret = dvdvideo_subp_stream_add(c->mpeg_ctx, entry, AVSTREAM_PARSE_FULL)) < 0 + || (ret = dvdvideo_subp_stream_add(s, entry, AVSTREAM_PARSE_NONE)) < 0) + goto end_free_error; + + goto end_free; + +end_free_error: + av_log(s, AV_LOG_ERROR, "Unable to allocate subtitle stream\n"); + +end_free: + av_free(entry->clut); + av_free(entry); + + return ret; +} + +static int dvdvideo_subp_stream_add_all(AVFormatContext *s) +{ + DVDVideoDemuxContext *c = s->priv_data; + + if (!c->vts_ifo->vtsi_mat->nr_of_vts_subp_streams) + return 0; + + for (int i = 0; i < c->vts_ifo->vtsi_mat->nr_of_vts_subp_streams; i++) { + int ret; + uint32_t subp_control; + subp_attr_t subp_attr; + video_attr_t video_attr; + + subp_control = c->play_pgc->subp_control[i]; + if (!(subp_control & 0x80000000)) + continue; + + /* there can be several presentations for one SPU */ + /* for now, be flexible with the DAR check due to weird authoring */ + video_attr = c->vts_ifo->vtsi_mat->vts_video_attr; + subp_attr = c->vts_ifo->vtsi_mat->vts_subp_attr[i]; + + /* 4:3 */ + if (!video_attr.display_aspect_ratio) { + if ((ret = dvdvideo_subp_stream_add_internal(s, subp_control >> 24, subp_attr)) < 0) + return ret; + + continue; + } + + /* 16:9 */ + if ((ret = dvdvideo_subp_stream_add_internal(s, subp_control >> 16, subp_attr)) < 0) + return ret; + + /* 16:9 letterbox */ + if (video_attr.permitted_df == 2 || video_attr.permitted_df == 0) + if ((ret = dvdvideo_subp_stream_add_internal(s, subp_control >> 8, subp_attr)) < 0) + return ret; + + /* 16:9 pan-and-scan */ + if (video_attr.permitted_df == 1 || video_attr.permitted_df == 0) + if ((ret = dvdvideo_subp_stream_add_internal(s, subp_control, subp_attr)) < 0) + return ret; + } + + return 0; +} + +static int dvdvideo_subdemux_read_data(void *opaque, uint8_t *buf, int buf_size) +{ + AVFormatContext *s = opaque; + DVDVideoDemuxContext *c = s->priv_data; + + uint8_t nav_buf[DVDVIDEO_BLOCK_SIZE] = {0}; + int nav_event; + int nav_len; + + dvdnav_vts_change_event_t *e_vts; + dvdnav_cell_change_event_t *e_cell; + int cur_title, cur_pgcn, cur_pgn, cur_angle, cur_title_unused, cur_ptt, cur_nb_angles; + pci_t *p_pci; + + if (buf_size != DVDVIDEO_BLOCK_SIZE) { + av_log(s, AV_LOG_ERROR, "Invalid buffer size\n"); + + return AVERROR(ENOMEM); + } + + for (int i = 0; i < DVDVIDEO_MAX_PS_SEARCH_BLOCKS; i++) { + if (ff_check_interrupt(&s->interrupt_callback)) + return AVERROR_EXIT; + + if (dvdnav_get_next_block(c->dvdnav, nav_buf, &nav_event, &nav_len) != DVDNAV_STATUS_OK) + return AVERROR_EXTERNAL; + + if (nav_len > DVDVIDEO_BLOCK_SIZE) + return AVERROR_INVALIDDATA; + + if (dvdnav_current_title_program(c->dvdnav, &cur_title, &cur_pgcn, &cur_pgn) != DVDNAV_STATUS_OK) + return AVERROR_EXTERNAL; + + if (dvdnav_current_title_info(c->dvdnav, &cur_title_unused, &cur_ptt) != DVDNAV_STATUS_OK) + return AVERROR_EXTERNAL; + + if (dvdnav_get_angle_info(c->dvdnav, &cur_angle, &cur_nb_angles) != DVDNAV_STATUS_OK) + return AVERROR_EXTERNAL; + + av_log(s, AV_LOG_TRACE, "new block: i=%d nav_event=%d nav_len=%d " + "cur_title=%d cur_ptt=%d cur_angle=%d cur_celln=%d cur_pgcn=%d cur_pgn=%d " + "play_in_vts=%d play_in_pgc=%d play_in_ps=%d\n", + i, nav_event, nav_len, + cur_title, cur_ptt, cur_angle, c->play_celln, cur_pgcn, cur_pgn, + c->play_in_vts, c->play_in_pgc, c->play_in_ps); + + if (c->play_in_pgc && (cur_pgcn != c->opt_pgc || !dvdnav_is_domain_vts(c->dvdnav))) + return AVERROR_EOF; + + switch (nav_event) { + case DVDNAV_VTS_CHANGE: + if (c->play_in_vts) + return AVERROR_EOF; + + e_vts = (dvdnav_vts_change_event_t *) nav_buf; + + if (e_vts->new_vtsN == c->play_vtsn && e_vts->new_domain == DVD_DOMAIN_VTSTitle) + c->play_in_vts = 1; + + continue; + case DVDNAV_CELL_CHANGE: + if (!c->play_in_vts) + continue; + + e_cell = (dvdnav_cell_change_event_t *) nav_buf; + + av_log(s, AV_LOG_TRACE, "new cell: prev=%d new=%d\n", c->play_celln, e_cell->cellN); + + if (!c->play_in_ps && !c->play_in_pgc) { + if (cur_title == c->opt_title && cur_ptt == c->opt_ptt + && cur_pgcn == c->opt_pgc && cur_pgn == c->opt_pg) { + c->play_in_pgc = 1; + } + } else if (c->play_celln > e_cell->cellN || c->play_pgn > cur_pgn) + return AVERROR_EOF; + + c->play_celln = e_cell->cellN; + c->play_pgn = cur_pgn; + c->play_skip_cell_last = c->play_skip_cell; + /* XXX: should we be skipping restricted cells mid-stream? */ + c->play_skip_cell = c->play_pgc->cell_playback[e_cell->cellN - 1].restricted != 0; + + if (c->play_skip_cell) + av_log(s, AV_LOG_TRACE, "cell skipped: cell=%d\n", e_cell->cellN); + + continue; + case DVDNAV_NAV_PACKET: + if (!c->play_in_pgc) + continue; + + c->play_ptt = cur_ptt; + + p_pci = dvdnav_get_current_nav_pci(c->dvdnav); + + if (!c->play_in_ps) { + /* XXX: this should shift start time to 0, but not working on all discs */ + c->play_ts_offset -= p_pci->pci_gi.vobu_s_ptm; + c->play_in_ps = 1; + + continue; + } + + if (cur_ptt < c->play_ptt) + return AVERROR_EOF; + + if (c->play_skip_cell) + c->play_ts_offset -= p_pci->pci_gi.vobu_e_ptm; + else if (p_pci->pci_gi.vobu_s_ptm < c->play_vobu_e_ptm) { + av_log(s, AV_LOG_TRACE, "discontinuity: adjusting timestamps\n"); + + c->play_ts_offset += c->play_vobu_e_ptm; + + if (!c->play_skip_cell_last) { + avio_flush(&c->mpeg_pb.pub); + ff_read_frame_flush(c->mpeg_ctx); + } + } + + c->play_vobu_e_ptm = p_pci->pci_gi.vobu_e_ptm; + + continue; + case DVDNAV_BLOCK_OK: + if (!c->play_in_ps || c->play_skip_cell) + continue; + + if (nav_len != DVDVIDEO_BLOCK_SIZE) + return AVERROR_INVALIDDATA; + + if (cur_angle != c->opt_angle) { + av_log(s, AV_LOG_ERROR, "Unexpected angle change\n"); + + return AVERROR_INPUT_CHANGED; + } + + memcpy(buf, &nav_buf, nav_len); + + return nav_len; + case DVDNAV_STILL_FRAME: + if (c->play_in_ps) + return AVERROR_EOF; + + dvdnav_still_skip(c->dvdnav); + + continue; + case DVDNAV_WAIT: + if (c->play_in_ps) + return AVERROR_EOF; + + dvdnav_wait_skip(c->dvdnav); + + continue; + case DVDNAV_HOP_CHANNEL: + case DVDNAV_HIGHLIGHT: + if (c->play_in_ps) + return AVERROR_EOF; + + continue; + case DVDNAV_STOP: + return AVERROR_EOF; + default: + continue; + } + } + + av_log(s, AV_LOG_ERROR, "Unable to find next program stream block\n"); + + return AVERROR_EOF; +} + +static void dvdvideo_subdemux_close(AVFormatContext *s) +{ + DVDVideoDemuxContext *c = s->priv_data; + + av_freep(&c->mpeg_pb.pub.buffer); + av_freep(&c->mpeg_pb); + avformat_close_input(&c->mpeg_ctx); +} + +static int dvdvideo_subdemux_open(AVFormatContext *s) +{ + DVDVideoDemuxContext *c = s->priv_data; + + int ret; + + const AVInputFormat *mpeg_fmt = NULL; + AVFormatContext *mpeg_ctx = NULL; + uint8_t *mpeg_buf = NULL; + + if (!(mpeg_fmt = av_find_input_format("mpeg"))) + return AVERROR_DEMUXER_NOT_FOUND; + + if (!(mpeg_ctx = avformat_alloc_context())) + return AVERROR(ENOMEM); + + if (!(mpeg_buf = av_malloc(DVDVIDEO_BLOCK_SIZE))) { + avformat_free_context(mpeg_ctx); + + return AVERROR(ENOMEM); + } + + ffio_init_context(&c->mpeg_pb, mpeg_buf, DVDVIDEO_BLOCK_SIZE, 0, s, + dvdvideo_subdemux_read_data, NULL, NULL); + c->mpeg_pb.pub.seekable = 0; + + if ((ret = ff_copy_whiteblacklists(mpeg_ctx, s)) < 0) { + avformat_free_context(mpeg_ctx); + + return ret; + } + + mpeg_ctx->flags = AVFMT_FLAG_CUSTOM_IO | AVFMT_FLAG_GENPTS; + mpeg_ctx->ctx_flags |= AVFMTCTX_UNSEEKABLE; + mpeg_ctx->probesize = 0; + mpeg_ctx->max_analyze_duration = 0; + mpeg_ctx->interrupt_callback = s->interrupt_callback; + mpeg_ctx->pb = &c->mpeg_pb.pub; + mpeg_ctx->io_open = NULL; + + if ((ret = avformat_open_input(&mpeg_ctx, "", mpeg_fmt, NULL)) < 0) { + avformat_free_context(mpeg_ctx); + + return ret; + } + + c->mpeg_fmt = mpeg_fmt; + c->mpeg_ctx = mpeg_ctx; + c->mpeg_buf = mpeg_buf; + + return 0; +} + +static int dvdvideo_read_header(AVFormatContext *s) +{ + int ret; + + if ((ret = dvdvideo_pgc_open(s)) < 0) + return ret; + + if ((ret = dvdvideo_pgc_chapters_setup(s)) < 0) + return ret; + + if ((ret = dvdvideo_subdemux_open(s)) < 0) + return ret; + + if ((ret = dvdvideo_video_stream_setup(s)) < 0) + return ret; + + if ((ret = dvdvideo_audio_stream_add_all(s)) < 0) + return ret; + + if ((ret = dvdvideo_subp_stream_add_all(s)) < 0) + return ret; + + return 0; +} + +static int dvdvideo_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + DVDVideoDemuxContext *c = s->priv_data; + + int ret; + + ret = av_read_frame(c->mpeg_ctx, pkt); + + if (ret >= 0) { + if (c->mpeg_ctx->nb_streams != s->nb_streams) { + av_log(s, AV_LOG_ERROR, "Unexpected new stream in playback\n"); + + return AVERROR_INPUT_CHANGED; + } + + if (pkt->pts != AV_NOPTS_VALUE) + pkt->pts += c->play_ts_offset; + if (pkt->dts != AV_NOPTS_VALUE) + pkt->dts += c->play_ts_offset; + + return 0; + } + + return ret; +} + +static int dvdvideo_read_seek(AVFormatContext *s, int stream_index, + int64_t timestamp, int flags) +{ + return AVERROR_PATCHWELCOME; +} + +static int dvdvideo_close(AVFormatContext *s) +{ + dvdvideo_subdemux_close(s); + dvdvideo_pgc_close(s); + + return 0; +} + +static int dvdvideo_probe(const AVProbeData *p) +{ + /* PATCHWELCOME: not implemented at this time */ + return 0; +} + +#define OFFSET(x) offsetof(DVDVideoDemuxContext, x) +static const AVOption dvdvideo_options[] = { + {"title", "Title Number", OFFSET(opt_title), AV_OPT_TYPE_INT, { .i64=1 }, 1, 99, AV_OPT_FLAG_DECODING_PARAM }, + {"chapter", "Entry Chapter (PTT) Number", OFFSET(opt_ptt), AV_OPT_TYPE_INT, { .i64=1 }, 1, 99, AV_OPT_FLAG_DECODING_PARAM }, + {"pgc", "Entry PGC Number (0=auto)", OFFSET(opt_pgc), AV_OPT_TYPE_INT, { .i64=0 }, 0, 32767, AV_OPT_FLAG_DECODING_PARAM }, + {"pg", "Entry PG Number (0=auto)", OFFSET(opt_pg), AV_OPT_TYPE_INT, { .i64=0 }, 0, 255, AV_OPT_FLAG_DECODING_PARAM }, + {"angle", "Video Angle Number", OFFSET(opt_angle), AV_OPT_TYPE_INT, { .i64=1 }, 1, 9, AV_OPT_FLAG_DECODING_PARAM }, + {"region", "Playback Region Number (0=free)", OFFSET(opt_region), AV_OPT_TYPE_INT, { .i64=0 }, 0, 8, AV_OPT_FLAG_DECODING_PARAM }, + {NULL} +}; + +static const AVClass dvdvideo_class = { + .class_name = "DVD-Video demuxer", + .item_name = av_default_item_name, + .option = dvdvideo_options, + .version = LIBAVUTIL_VERSION_INT +}; + +const AVInputFormat ff_dvdvideo_demuxer = { + .name = "dvdvideo", + .long_name = NULL_IF_CONFIG_SMALL("DVD-Video"), + .priv_class = &dvdvideo_class, + .priv_data_size = sizeof(DVDVideoDemuxContext), + .flags = AVFMT_NOFILE | AVFMT_SHOW_IDS | AVFMT_TS_DISCONT | AVFMT_NO_BYTE_SEEK | AVFMT_NOGENSEARCH | AVFMT_NOBINSEARCH, + .flags_internal = FF_FMT_INIT_CLEANUP, + .read_probe = dvdvideo_probe, + .read_close = dvdvideo_close, + .read_header = dvdvideo_read_header, + .read_packet = dvdvideo_read_packet, + .read_seek = dvdvideo_read_seek +};