* [FFmpeg-devel] [PATCH] [WIP] [RFC] dvdvideo: initial contribution (DVD demuxer)
@ 2023-12-09 10:06 Marth64
2023-12-10 2:27 ` Marth64
` (3 more replies)
0 siblings, 4 replies; 27+ messages in thread
From: Marth64 @ 2023-12-09 10:06 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Marth64
Hello, I am happy to share a DVD demuxer for ffmpeg powered by libdvdread and libdvdnav.
I have been working on this on/off throughout the year and think it is in a good spot
to share at the ML now. This was a major learning experience for me in many ways and
am open to any feedback on how I could improve it to be better. In fact, there is still
one issue I can't seem to figure out how to solve (in discussion below). Regardless,
a good number of DVDs I have tried seem to work out of the box. This is a full-service
demuxer with chapters, subtitles (and their palettes), as well as language metadata.
At a high level, the demuxer uses libdvdread for metadata of the disc, libdvdnav for
the actual playback, and the MPEG-PS demuxer for the underlying VOB stream.
First, the basic usage, is quite straightforward:
ffmpeg -f dvdvideo -title NN -i DVD.iso|/dev/srX|/path/to/DVD ...
Where NN can be replaced by your known title number in the disc
As the demuxer effectively works at a PGC level, multi-PGC titles are not supported.
But to provide this flexibility as there are many weirdly authored DVDs out there,
one can specify an exact PGC via -pgc option and an associated program (PG, can start at 1).
I am hoping and willing to improve this to be a robust demuxer wherever possible, but to
that extent there is still an issue I can't figure out how to solve: Dealing with DTS discontinuities
and PTS generation.
Right now, as a band-aid, I am adding AVFMT_FLAG_GENPTS (line 1408)
to the MPEG-PS subdemuxer. This works with most discs and the output seems OK. On discs with discontinuities, however,
this causes the demuxing to hang which is obviously unacceptable also. Removing the flag causes the discs
to not hang, but then the output for all discs becomes choppy for obvious reasons (invalid PTS).
It could be because I have some misunderstandings on how things work within ffmpeg,
but I have tried to the point now where I thought it best to ask the experts for help if you can spot
what I am doing wrong. I am really motivated to make this work and good quality.
Thank you!
---
configure | 8 +
libavformat/Makefile | 1 +
libavformat/allformats.c | 1 +
libavformat/avlanguage.c | 10 +-
libavformat/dvdvideodec.c | 1599 +++++++++++++++++++++++++++++++++++++
5 files changed, 1617 insertions(+), 2 deletions(-)
create mode 100644 libavformat/dvdvideodec.c
diff --git a/configure b/configure
index d77c053226..65e5968194 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]
@@ -1802,6 +1804,8 @@ EXTERNAL_LIBRARY_GPL_LIST="
frei0r
libcdio
libdavs2
+ libdvdnav
+ libdvdread
librubberband
libvidstab
libx264
@@ -3494,6 +3498,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"
@@ -6723,6 +6729,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 && require_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 2db83aff81..45dba53044 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 c8bb4e3866..dc2acf575c 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..2b22a7de4f
--- /dev/null
+++ b/libavformat/dvdvideodec.c
@@ -0,0 +1,1599 @@
+/*
+ * DVD-Video demuxer (powered by libdvdnav/libdvdread)
+ *
+ * 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
+ * 7) Close resources
+ **/
+
+/**
+ * Issues/bug tracker (TODO):
+ * - SDDS is not supported yet
+ * - Are we handling backwards cell changes and PTT changes correctly?
+ * - Some codec parameters/metadata is not being explicitly set:
+ * -> ChannelLayout, color/chroma info, DAR, frame size for MP1/MP2 audio
+ * - Additional PGC validations?
+**/
+
+#include <dvdread/dvd_reader.h>
+#include <dvdread/ifo_read.h>
+#include <dvdread/ifo_types.h>
+#include <dvdread/nav_read.h>
+#include <dvdnav/dvdnav.h>
+
+#include "libavutil/avstring.h"
+#include "libavutil/avutil.h"
+#include "libavutil/colorspace.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+#include "libavutil/samplefmt.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 ZERO_Q (AVRational) { 0, 1 }
+
+#define DVDVIDEO_PS_MAX_SEARCH_BLOCKS 128
+#define DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE 1024
+
+#define DVDVIDEO_TIME_BASE_Q (AVRational) { 1, 90000 }
+#define DVDVIDEO_TIME_BASE_MILLIS_Q (AVRational) { 1, 1000 }
+#define DVDVIDEO_PTS_WRAP_BITS 32
+#define DVDVIDEO_BLOCK_SIZE 2048
+
+#define DVDVIDEO_NTSC_FRAMERATE_Q (AVRational) { 30000, 1001 }
+#define DVDVIDEO_NTSC_HEIGHT 480
+#define DVDVIDEO_PAL_FRAMERATE_Q (AVRational) { 25, 1 }
+#define DVDVIDEO_PAL_HEIGHT 576
+#define DVDVIDEO_D1_WIDTH 720
+#define DVDVIDEO_4CIF_WIDTH 704
+#define DVDVIDEO_D1_HALF_WIDTH 352
+#define DVDVIDEO_CIF_WIDTH 352
+#define DVDVIDEO_PIXEL_FORMAT AV_PIX_FMT_YUV420P
+
+#define DVDVIDEO_STARTCODE_VIDEO 0x1E0
+#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_AC3 0x80
+#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP1 0x1C0
+#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP2 0x1C0
+#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_PCM 0xA0
+#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_DTS 0x88
+#define DVDVIDEO_STARTCODE_OFFSET_SUBP 0x20
+
+#define DVDVIDEO_TITLE_NUMBER_MAX 99
+#define DVDVIDEO_PTT_NUMBER_MAX 99
+#define DVDVIDEO_PGC_NUMBER_MAX 32767
+#define DVDVIDEO_PG_NUMBER_MAX 255
+#define DVDVIDEO_ANGLE_NUMBER_MAX 9
+#define DVDVIDEO_VTS_NUMBER_MAX 99
+#define DVDVIDEO_TT_NUMBER_MAX 99
+#define DVDVIDEO_REGION_NUMBER_MAX 8
+#define DVDVIDEO_AUDIO_CONTROL_ENABLED_MASK 0x8000
+#define DVDVIDEO_SUBP_CONTROL_ENABLED_MASK 0x80000000
+#define DVDVIDEO_VTS_AUDIO_STREAMS_MAX 8
+#define DVDVIDEO_VTS_SUBP_STREAMS_MAX 32
+
+/* ("palette: ") + ("rrggbb, "*15) + ("rrggbb") + \n + \0 */
+#define DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE (9 + (8 * 15) + 6 + 1 + 1)
+#define DVDVIDEO_SUBP_CLUT_LEN 16
+#define DVDVIDEO_SUBP_CLUT_SIZE DVDVIDEO_SUBP_CLUT_LEN * sizeof(uint32_t)
+
+/* convert binary-coded decimal to decimal */
+#define BCD2D(__x__) (((__x__ & 0xF0) >> 4) * 10 + (__x__ & 0x0F))
+
+/* crop table for YUV to RGB subpicture palette conversion */
+#define DVDVIDEO_YUV_NEG_CROP_MAX 1024
+#define times4(x) x, x, x, x
+#define times256(x) times4(times4(times4(times4(times4(x)))))
+
+const uint8_t dvdvideo_yuv_crop_tab[256 + 2 * DVDVIDEO_YUV_NEG_CROP_MAX] = {
+times256(0x00),
+0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
+0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
+0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
+0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
+0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
+0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,
+0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
+0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,
+0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
+0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
+0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
+0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
+0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
+0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
+0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,
+0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF,
+times256(0xFF)
+};
+
+enum DVDVideoVTSMPEGVersion {
+ DVDVIDEO_VTS_MPEG_VERSION_MPEG1 = 0,
+ DVDVIDEO_VTS_MPEG_VERSION_MPEG2 = 1
+};
+
+enum DVDVideoVTSPictureFormat {
+ DVDVIDEO_VTS_PICTURE_FORMAT_NTSC = 0,
+ DVDVIDEO_VTS_PICTURE_FORMAT_PAL = 1
+};
+
+enum DVDVideoVTSPictureSize {
+ DVDVIDEO_VTS_PICTURE_SIZE_D1 = 0,
+ DVDVIDEO_VTS_PICTURE_SIZE_4CIF = 1,
+ DVDVIDEO_VTS_PICTURE_SIZE_D1_HALF = 2,
+ DVDVIDEO_VTS_PICTURE_SIZE_CIF = 3
+};
+
+enum DVDVideoVTSPictureDAR {
+ DVDVIDEO_VTS_PICTURE_DAR_4_3 = 0,
+ DVDVIDEO_VTS_PICTURE_DAR_16_9 = 3
+};
+
+enum DVDVideoVTSPermittedFullscreenDisplay {
+ DVDVIDEO_VTS_SPU_PFD_ANY = 0,
+ DVDVIDEO_VTS_SPU_PFD_PANSCAN_ONLY = 1,
+ DVDVIDEO_VTS_SPU_PFD_LETTERBOX_ONLY = 2
+};
+
+enum DVDVideoVTSAudioFormat {
+ DVDVIDEO_VTS_AUDIO_FORMAT_AC3 = 0,
+ DVDVIDEO_VTS_AUDIO_FORMAT_MP1 = 2,
+ DVDVIDEO_VTS_AUDIO_FORMAT_MP2 = 3,
+ DVDVIDEO_VTS_AUDIO_FORMAT_PCM = 4,
+ DVDVIDEO_VTS_AUDIO_FORMAT_SDDS = 5,
+ DVDVIDEO_VTS_AUDIO_FORMAT_DTS = 6
+};
+
+enum FFDVDVideoVTSAudioSampleRate {
+ DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_48K = 0,
+ DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_96K = 1
+};
+
+enum FFDVDVideoVTSAudioQuantization {
+ DVDVIDEO_VTS_AUDIO_QUANTIZATION_16 = 0,
+ DVDVIDEO_VTS_AUDIO_QUANTIZATION_20 = 1,
+ DVDVIDEO_VTS_AUDIO_QUANTIZATION_24 = 2
+};
+
+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 identification 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 *vol_dvdread; /* handle to libdvdread */
+ ifo_handle_t *vol_vmg_ifo; /* handle to the VMG (VIDEO_TS.IFO) */
+
+ /* playback */
+ ifo_handle_t *play_vts_ifo; /* handle to the active VTS (VTS_nn_n.IFO) */
+ pgc_t *play_pgc; /* handle to the active PGC */
+ int play_vtsn; /* number of the active VTS (video title set) */
+ int play_pgcn; /* number of the active PGC (program chain) */
+ dvdnav_t *play_dvdnav; /* handle to libdvdnav */
+} DVDVideoDemuxContext;
+
+static void dvdvideo_libdvdread_log(void *opaque, dvd_logger_level_t level,
+ const char *msg, va_list msg_va)
+{
+ AVFormatContext *s = opaque;
+ int lavu_level;
+
+ char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
+ vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
+
+ switch (level) {
+ case DVD_LOGGER_LEVEL_ERROR:
+ lavu_level = AV_LOG_ERROR;
+ break;
+ case DVD_LOGGER_LEVEL_WARN:
+ lavu_level = AV_LOG_WARNING;
+ break;
+ case DVD_LOGGER_LEVEL_INFO:
+ lavu_level = AV_LOG_INFO;
+ break;
+ case DVD_LOGGER_LEVEL_DEBUG:
+ default:
+ lavu_level = AV_LOG_DEBUG;
+ }
+
+ /* libdvdread messages don't have line terminators */
+ av_log(s, lavu_level, "libdvdread: %s\n", msg_buf);
+}
+
+static void dvdvideo_libdvdnav_log(void *opaque, dvdnav_logger_level_t level,
+ const char *msg, va_list msg_va)
+{
+ AVFormatContext *s = opaque;
+ int lavu_level;
+
+ char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
+ vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
+
+ switch (level) {
+ case DVDNAV_LOGGER_LEVEL_ERROR:
+ lavu_level = AV_LOG_ERROR;
+ break;
+ case DVDNAV_LOGGER_LEVEL_WARN:
+ lavu_level = AV_LOG_WARNING;
+ break;
+ case DVDNAV_LOGGER_LEVEL_INFO:
+ lavu_level = AV_LOG_INFO;
+ break;
+ case DVDNAV_LOGGER_LEVEL_DEBUG:
+ default:
+ lavu_level = AV_LOG_DEBUG;
+ }
+
+ /* libdvdnav messages don't have line terminators */
+ av_log(s, lavu_level, "libdvdnav: %s\n", msg_buf);
+}
+
+static char *dvdvideo_lang_code_to_iso639__2(const uint16_t lang_code)
+{
+ char lang_code_str[3] = {0};
+
+ if (lang_code && lang_code != 0xFFFF) {
+ lang_code_str[0] = (lang_code >> 8) & 0xFF;
+ lang_code_str[1] = lang_code & 0xFF;
+ }
+
+ return (char *) ff_convert_lang_to(lang_code_str, AV_LANG_ISO639_2_BIBL);
+}
+
+static int64_t dvdvideo_time_to_millis(dvd_time_t *time)
+{
+ double fps;
+ int64_t ms;
+
+ int64_t sec = (int64_t) (BCD2D(time->hour)) * 60 * 60;
+ sec += (int64_t) (BCD2D(time->minute)) * 60;
+ sec += (int64_t) (BCD2D(time->second));
+
+ /* the 2 high bits are the frame rate */
+ switch ((time->frame_u & 0xC0) >> 6)
+ {
+ case 1:
+ fps = 25.0;
+ break;
+ case 3:
+ fps = 29.97;
+ break;
+ default:
+ fps = 2500.0;
+ break;
+ }
+ ms = BCD2D(time->frame_u & 0x3F) * 1000.0 / fps;
+
+ return (sec * 1000) + ms;
+}
+
+static int dvdvideo_volume_is_title_valid_in_context(AVFormatContext *s,
+ int title)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ return title >= 1
+ && title <= DVDVIDEO_TITLE_NUMBER_MAX
+ && title <= c->vol_vmg_ifo->tt_srpt->nr_of_srpts
+ && c->vol_vmg_ifo->tt_srpt->title[title - 1].nr_of_ptts > 0;
+}
+
+static int dvdvideo_volume_is_title_valid_in_vts(title_info_t title_info,
+ ifo_handle_t *vts_ifo)
+{
+ return title_info.vts_ttn >= 1
+ && title_info.vts_ttn <= DVDVIDEO_TT_NUMBER_MAX
+ && title_info.vts_ttn <= vts_ifo->vts_ptt_srpt->nr_of_srpts
+ && vts_ifo->vtsi_mat->nr_of_vts_audio_streams
+ <= DVDVIDEO_VTS_AUDIO_STREAMS_MAX
+ && vts_ifo->vtsi_mat->nr_of_vts_subp_streams
+ <= DVDVIDEO_VTS_SUBP_STREAMS_MAX;
+}
+
+static int dvdvideo_volume_is_angle_valid_in_title(int angle,
+ title_info_t title_info)
+{
+ return angle >= 1
+ && angle <= DVDVIDEO_ANGLE_NUMBER_MAX
+ && angle <= title_info.nr_of_angles;
+}
+
+static int dvdvideo_volume_is_pgc_valid_and_sequential(pgc_t *pgc)
+{
+ return pgc
+ && pgc->program_map
+ && pgc->cell_playback != NULL
+ && pgc->pg_playback_mode == 0
+ && pgc->nr_of_programs > 0
+ && pgc->nr_of_cells > 0;
+}
+
+static int dvdvideo_volume_is_pgcn_in_vts(int pgcn, ifo_handle_t *vts_ifo)
+{
+ return pgcn >= 1
+ && pgcn <= DVDVIDEO_PGC_NUMBER_MAX
+ && pgcn <= vts_ifo->vts_pgcit->nr_of_pgci_srp;
+}
+
+static int dvdvideo_volume_is_pgn_in_pgc(int pgn, pgc_t *pgc)
+{
+ return pgn >= 1
+ && pgn <= DVDVIDEO_PG_NUMBER_MAX
+ && pgn <= pgc->nr_of_programs;
+}
+
+static int dvdvideo_volume_open(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ dvd_reader_t *dvdread;
+ ifo_handle_t *vmg_ifo;
+
+ dvd_logger_cb dvdread_log_cb = { .pf_log = dvdvideo_libdvdread_log };
+ dvdread = DVDOpen2(s, &dvdread_log_cb, s->url);
+
+ if (!dvdread) {
+ av_log(s, AV_LOG_ERROR, "Unable to initialize libdvdread\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ if (!(vmg_ifo = ifoOpen(dvdread, 0))) {
+ DVDClose(dvdread);
+
+ av_log(s, AV_LOG_ERROR,
+ "Unable to open VIDEO_TS.IFO. "
+ "Input does not have a valid DVD-Video structure "
+ "or is not a compliant UDF DVD-Video image.\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ c->vol_dvdread = dvdread;
+ c->vol_vmg_ifo = vmg_ifo;
+
+ return 0;
+}
+
+static void dvdvideo_volume_close(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ if (c->vol_vmg_ifo) {
+ ifoClose(c->vol_vmg_ifo);
+ c->vol_vmg_ifo = NULL;
+ }
+
+ if (c->vol_dvdread) {
+ DVDClose(c->vol_dvdread);
+ c->vol_dvdread = NULL;
+ }
+}
+
+static int dvdvideo_volume_open_vts_ifo(AVFormatContext *s, int vtsn,
+ ifo_handle_t **p_vts_ifo)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ ifo_handle_t *vts_ifo;
+
+ if (vtsn < 1
+ || vtsn > DVDVIDEO_VTS_NUMBER_MAX
+ || !(vts_ifo = ifoOpen(c->vol_dvdread, vtsn))) {
+ return AVERROR_INVALIDDATA;
+ }
+
+ for (int i = 0; i < vts_ifo->vts_c_adt->nr_of_vobs; i++) {
+ int start_sector = vts_ifo->vts_c_adt->cell_adr_table[i].start_sector;
+ int end_sector = vts_ifo->vts_c_adt->cell_adr_table[i].last_sector;
+
+ if ((start_sector & 0xFFFFFF) == 0xFFFFFF
+ || (end_sector & 0xFFFFFF) == 0xFFFFFF
+ || start_sector >= end_sector) {
+ ifoClose(vts_ifo);
+
+ av_log(s, AV_LOG_WARNING, "VTS has invalid cell address table\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+ }
+
+ (*p_vts_ifo) = vts_ifo;
+
+ return 0;
+}
+
+static int dvdvideo_playback_open(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret;
+
+ ifo_handle_t *vts_ifo;
+ dvdnav_logger_cb dvdnav_log_cb;
+ dvdnav_status_t dvdnav_status;
+ dvdnav_t *dvdnav;
+
+ title_info_t title_info;
+ int pgcn;
+ int pgn;
+ pgc_t *pgc;
+
+ int32_t disc_region_mask;
+ int32_t player_region_mask;
+ int cell_search_has_vts = 0;
+
+ /* if the title is valid, open its VTS IFO */
+ if (!dvdvideo_volume_is_title_valid_in_context(s, c->opt_title)) {
+ av_log(s, AV_LOG_ERROR, "Title not found or invalid\n");
+
+ return AVERROR_STREAM_NOT_FOUND;
+ }
+
+ title_info = c->vol_vmg_ifo->tt_srpt->title[c->opt_title - 1];
+
+ if ((ret = dvdvideo_volume_open_vts_ifo(s,
+ title_info.title_set_nr, &vts_ifo)) != 0) {
+ av_log(s, AV_LOG_ERROR, "Title VTS could not be opened\n");
+
+ return ret;
+ }
+
+ if (!dvdvideo_volume_is_title_valid_in_vts(title_info, vts_ifo)) {
+ av_log(s, AV_LOG_ERROR, "Title VTS is invalid\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (!dvdvideo_volume_is_angle_valid_in_title(c->opt_angle, title_info)) {
+ av_log(s, AV_LOG_ERROR, "Angle not found or invalid\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ /* determine our PGC and PG (playback coordinates) */
+ if (c->opt_pgc > 0 && c->opt_pg > 0) {
+ if (c->opt_ptt > 0) {
+ av_log(s, AV_LOG_WARNING,
+ "PTT option ignored as PGC and PG are provided\n");
+ }
+
+ pgcn = c->opt_pgc;
+ pgn = c->opt_pg;
+ } else {
+ if (c->opt_ptt > title_info.nr_of_ptts) {
+ av_log(s, AV_LOG_ERROR, "PTT not found\n");
+
+ return AVERROR_STREAM_NOT_FOUND;
+ }
+
+ pgcn = vts_ifo->vts_ptt_srpt->title[title_info.vts_ttn - 1].ptt[c->opt_ptt - 1].pgcn;
+ pgn = vts_ifo->vts_ptt_srpt->title[title_info.vts_ttn - 1].ptt[c->opt_ptt - 1].pgn;
+ }
+
+ if (!dvdvideo_volume_is_pgcn_in_vts(pgcn, vts_ifo)) {
+ av_log(s, AV_LOG_ERROR, "PGC not found\n");
+
+ return AVERROR_STREAM_NOT_FOUND;
+ }
+
+ pgc = vts_ifo->vts_pgcit->pgci_srp[pgcn - 1].pgc;
+
+ if (!dvdvideo_volume_is_pgc_valid_and_sequential(pgc)) {
+ av_log(s, AV_LOG_ERROR, "PGC not valid\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (!dvdvideo_volume_is_pgn_in_pgc(pgn, pgc)) {
+ av_log(s, AV_LOG_ERROR, "PG not found\n");
+
+ return AVERROR_STREAM_NOT_FOUND;
+ }
+
+ /* set up libdvdnav */
+ dvdnav_log_cb = (dvdnav_logger_cb) { .pf_log = dvdvideo_libdvdnav_log };
+ dvdnav_status = dvdnav_open2(&dvdnav, NULL, &dvdnav_log_cb, s->url);
+
+ if (!dvdnav) {
+ av_log(s, AV_LOG_ERROR, "Unable to initialize libdvdnav\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ if (dvdnav_status != DVDNAV_STATUS_OK) {
+ goto end_search_error_dvdnav;
+ }
+
+ if (dvdnav_set_readahead_flag(dvdnav, 0) != DVDNAV_STATUS_OK) {
+ goto end_search_error_dvdnav;
+ }
+
+ if (dvdnav_set_PGC_positioning_flag(dvdnav, 1) != DVDNAV_STATUS_OK) {
+ goto end_search_error_dvdnav;
+ }
+
+ if (dvdnav_get_region_mask(dvdnav, &disc_region_mask) != DVDNAV_STATUS_OK) {
+ goto end_search_error_dvdnav;
+ }
+
+ if (c->opt_region > 0) {
+ player_region_mask = (1 << (c->opt_region - 1));
+ } else {
+ player_region_mask = disc_region_mask;
+ }
+
+ if (dvdnav_set_region_mask(dvdnav, player_region_mask) != DVDNAV_STATUS_OK) {
+ goto end_search_error_dvdnav;
+ }
+
+ if (dvdnav_program_play(dvdnav, c->opt_title, pgcn, pgn) != DVDNAV_STATUS_OK) {
+ goto end_search_error_dvdnav;
+ }
+
+ if (dvdnav_angle_change(dvdnav, c->opt_angle) != DVDNAV_STATUS_OK) {
+ goto end_search_error_dvdnav;
+ }
+
+ /* lock on to title's VTS and chosen PGC/PGN coordinates */
+ for (int i = 0; i < DVDVIDEO_PS_MAX_SEARCH_BLOCKS; i++) {
+ int nav_event;
+ int nav_len;
+ dvdnav_vts_change_event_t *vts_event;
+ uint8_t nav_buf[DVDVIDEO_BLOCK_SIZE] = {0};
+
+ if (ff_check_interrupt(&s->interrupt_callback)) {
+ return AVERROR_EXIT;
+ }
+
+ if (dvdnav_get_next_block(dvdnav, nav_buf, &nav_event, &nav_len)
+ != DVDNAV_STATUS_OK) {
+ goto end_search_error_dvdnav;
+ }
+
+ if (nav_len > DVDVIDEO_BLOCK_SIZE) {
+ av_log(s, AV_LOG_ERROR, "Invalid block (too big)\n");
+
+ ret = AVERROR(ENOMEM);
+
+ goto end_search_error;
+ }
+
+ av_log(s, AV_LOG_TRACE, "nav event=%d len=%d\n", nav_event, nav_len);
+
+ switch (nav_event) {
+ case DVDNAV_VTS_CHANGE:
+ vts_event = (dvdnav_vts_change_event_t *) nav_buf;
+
+ if (cell_search_has_vts) {
+ if (vts_event->new_vtsN != title_info.title_set_nr
+ || vts_event->new_domain != DVD_DOMAIN_VTSTitle) {
+ av_log(s, AV_LOG_ERROR, "Unexpected VTS change\n");
+
+ ret = AVERROR_INPUT_CHANGED;
+
+ goto end_search_error;
+ }
+ continue;
+ }
+
+ if (vts_event->new_vtsN == title_info.title_set_nr
+ && vts_event->new_domain == DVD_DOMAIN_VTSTitle) {
+ cell_search_has_vts = 1;
+ }
+
+ continue;
+ case DVDNAV_CELL_CHANGE:
+ // if we need more info about the cell:
+ // dvdnav_cell_change_event_t *event_data =
+ // (dvdnav_cell_change_event_t *) nav_buf;
+ int check_title;
+ int check_pgcn;
+ int check_pgn;
+
+ if (!cell_search_has_vts) {
+ continue;
+ }
+
+ dvdnav_current_title_program(dvdnav, &check_title,
+ &check_pgcn, &check_pgn);
+
+ if (check_title == c->opt_title && check_pgcn == pgcn
+ && check_pgn == pgn && dvdnav_is_domain_vts(dvdnav)) {
+ goto end_ready;
+ }
+
+ continue;
+ case DVDNAV_STILL_FRAME:
+ dvdnav_still_skip(dvdnav);
+
+ continue;
+ case DVDNAV_WAIT:
+ dvdnav_wait_skip(dvdnav);
+
+ continue;
+ case DVDNAV_STOP:
+ ret = AVERROR_INPUT_CHANGED;
+
+ goto end_search_error;
+ default:
+ continue;
+ }
+ }
+
+end_search_error_dvdnav:
+ ret = AVERROR_EXTERNAL;
+ av_log(s, AV_LOG_ERROR, "libdvdnav: %s\n", dvdnav_err_to_string(dvdnav));
+ av_log(s, AV_LOG_ERROR, "Error starting playback\n");
+
+end_search_error:
+ dvdnav_close(dvdnav);
+ ifoClose(vts_ifo);
+
+ return ret;
+
+end_ready:
+ /* update the context */
+ c->play_vts_ifo = vts_ifo;
+ c->play_pgc = pgc;
+ c->play_vtsn = title_info.title_set_nr;
+ c->play_pgcn = pgcn;
+ c->play_dvdnav = dvdnav;
+
+ return 0;
+}
+
+static void dvdvideo_playback_close(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ if (c->play_dvdnav) {
+ dvdnav_close(c->play_dvdnav);
+ c->play_dvdnav = NULL;
+ }
+
+ if (c->play_vts_ifo) {
+ ifoClose(c->play_vts_ifo);
+ c->play_vts_ifo = NULL;
+ }
+}
+
+static int dvdvideo_video_stream_analyze(video_attr_t video_attr,
+ enum AVCodecID *p_codec_id, AVRational *p_framerate,
+ int *p_height, int *p_width)
+{
+ enum AVCodecID codec_id = AV_CODEC_ID_NONE;
+ AVRational framerate = ZERO_Q;
+ int height = 0;
+ int width = 0;
+
+ switch (video_attr.mpeg_version) {
+ case DVDVIDEO_VTS_MPEG_VERSION_MPEG1:
+ codec_id = AV_CODEC_ID_MPEG1VIDEO;
+ break;
+ case DVDVIDEO_VTS_MPEG_VERSION_MPEG2:
+ codec_id = AV_CODEC_ID_MPEG2VIDEO;
+ break;
+ }
+
+ switch (video_attr.video_format) {
+ case DVDVIDEO_VTS_PICTURE_FORMAT_NTSC:
+ framerate = DVDVIDEO_NTSC_FRAMERATE_Q;
+ height = DVDVIDEO_NTSC_HEIGHT;
+ break;
+ case DVDVIDEO_VTS_PICTURE_FORMAT_PAL:
+ framerate = DVDVIDEO_PAL_FRAMERATE_Q;
+ height = DVDVIDEO_PAL_HEIGHT;
+ break;
+ }
+
+ if (height > 0) {
+ switch (video_attr.picture_size) {
+ case DVDVIDEO_VTS_PICTURE_SIZE_D1:
+ width = DVDVIDEO_D1_WIDTH;
+ break;
+ case DVDVIDEO_VTS_PICTURE_SIZE_4CIF:
+ width = DVDVIDEO_4CIF_WIDTH;
+ break;
+ case DVDVIDEO_VTS_PICTURE_SIZE_D1_HALF:
+ width = DVDVIDEO_D1_HALF_WIDTH;
+ break;
+ case DVDVIDEO_VTS_PICTURE_SIZE_CIF:
+ width = DVDVIDEO_CIF_WIDTH;
+ height /= 2;
+ break;
+ }
+ }
+
+ if (codec_id == AV_CODEC_ID_NONE || av_cmp_q(framerate, ZERO_Q) == 0
+ || width < 1 || height < 1) {
+ return AVERROR_INVALIDDATA;
+ }
+
+ (*p_codec_id) = codec_id;
+ (*p_framerate) = framerate;
+ (*p_height) = height;
+ (*p_width) = width;
+
+ return 0;
+}
+
+static int dvdvideo_video_stream_to_avstream(AVFormatContext *s,
+ enum AVCodecID codec_id, AVRational framerate,
+ int height, int width,
+ enum AVStreamParseType need_parsing)
+{
+ AVStream *st;
+ FFStream *sti;
+
+ st = avformat_new_stream(s, NULL);
+ if (!st) {
+ return AVERROR(ENOMEM);
+ }
+
+ st->id = DVDVIDEO_STARTCODE_VIDEO;
+ st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
+ st->codecpar->codec_id = codec_id;
+ st->codecpar->width = width;
+ st->codecpar->height = height;
+ st->codecpar->format = DVDVIDEO_PIXEL_FORMAT;
+
+ st->codecpar->framerate = framerate;
+#if FF_API_R_FRAME_RATE
+ st->r_frame_rate = framerate;
+#endif
+ st->avg_frame_rate = framerate;
+
+ avpriv_set_pts_info(st, DVDVIDEO_PTS_WRAP_BITS,
+ DVDVIDEO_TIME_BASE_Q.num, DVDVIDEO_TIME_BASE_Q.den);
+
+ sti = ffstream(st);
+ sti->request_probe = 0;
+ sti->need_parsing = need_parsing;
+ sti->avctx->framerate = framerate;
+
+ return 0;
+}
+
+static int dvdvideo_video_stream_register(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ enum AVCodecID codec_id;
+ AVRational framerate;
+ int height;
+ int width;
+
+ int ret;
+
+ if ((ret = dvdvideo_video_stream_analyze(c->play_vts_ifo->vtsi_mat->vts_video_attr, &codec_id,
+ &framerate, &height, &width)) != 0) {
+ av_log(s, AV_LOG_ERROR, "Invalid video parameters in VTS IFO\n");
+
+ return ret;
+ }
+
+ if ((ret = dvdvideo_video_stream_to_avstream(c->mpeg_ctx, codec_id,
+ framerate, height, width, AVSTREAM_PARSE_FULL)) != 0) {
+ av_log(s, AV_LOG_ERROR, "Unable to allocate video stream (subdemux)\n");
+
+ return ret;
+ }
+
+ if ((ret = dvdvideo_video_stream_to_avstream(s, codec_id,
+ framerate, height, width, AVSTREAM_PARSE_NONE)) != 0) {
+ av_log(s, AV_LOG_ERROR, "Unable to allocate video stream\n");
+
+ return ret;
+ }
+
+ return 0;
+}
+
+static int dvdvideo_audio_stream_analyze(audio_attr_t audio_attr,
+ uint16_t audio_control, enum AVCodecID *p_codec_id, int *p_startcode,
+ int *p_sample_rate, int *p_sample_bits,
+ int *p_nb_channels, char **lang_iso639__2)
+{
+ enum AVCodecID codec_id = AV_CODEC_ID_NONE;
+ int startcode = 0;
+ int sample_rate = 0;
+ int sample_bits = 0;
+ int nb_channels = 0;
+
+ int position = (audio_control & 0x7F00) >> 8;
+
+ switch (audio_attr.audio_format) {
+ case DVDVIDEO_VTS_AUDIO_FORMAT_AC3:
+ codec_id = AV_CODEC_ID_AC3;
+ sample_rate = 48000;
+ startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_AC3 + position;
+ break;
+ case DVDVIDEO_VTS_AUDIO_FORMAT_MP1:
+ codec_id = AV_CODEC_ID_MP1;
+ sample_rate = 48000;
+ startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP1 + position;
+ break;
+ case DVDVIDEO_VTS_AUDIO_FORMAT_MP2:
+ codec_id = AV_CODEC_ID_MP2;
+ sample_rate = 48000;
+ startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP2 + position;
+ break;
+ case DVDVIDEO_VTS_AUDIO_FORMAT_PCM:
+ codec_id = AV_CODEC_ID_PCM_DVD;
+
+ switch (audio_attr.sample_frequency) {
+ case DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_48K:
+ sample_rate = 48000;
+ break;
+ case DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_96K:
+ sample_rate = 96000;
+ break;
+ }
+
+ switch (audio_attr.quantization) {
+ case DVDVIDEO_VTS_AUDIO_QUANTIZATION_16:
+ sample_bits = 16;
+ break;
+ case DVDVIDEO_VTS_AUDIO_QUANTIZATION_20:
+ sample_bits = 20;
+ break;
+ case DVDVIDEO_VTS_AUDIO_QUANTIZATION_24:
+ sample_bits = 24;
+ break;
+ }
+
+ startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_PCM + position;
+ break;
+ case DVDVIDEO_VTS_AUDIO_FORMAT_DTS:
+ codec_id = AV_CODEC_ID_DTS;
+ sample_rate = 48000;
+ startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_DTS + position;
+ break;
+ }
+
+ nb_channels = audio_attr.channels + 1;
+
+ if (codec_id == AV_CODEC_ID_NONE || startcode == 0
+ || sample_rate == 0 || nb_channels == 0) {
+ return AVERROR_INVALIDDATA;
+ }
+
+ (*p_codec_id) = codec_id;
+ (*p_startcode) = startcode;
+ (*p_sample_rate) = sample_rate;
+ (*p_sample_bits) = sample_bits;
+ (*p_nb_channels) = nb_channels;
+ (*lang_iso639__2) = dvdvideo_lang_code_to_iso639__2(audio_attr.lang_code);
+
+ return 0;
+}
+
+static int dvdvideo_audio_stream_to_avstream(AVFormatContext *s,
+ enum AVCodecID codec_id, int startcode, int sample_rate,
+ int sample_bits, int nb_channels, char *lang_iso639__2,
+ enum AVStreamParseType need_parsing)
+{
+ AVStream *st;
+ FFStream *sti;
+
+ st = avformat_new_stream(s, NULL);
+ if (!st) {
+ return AVERROR(ENOMEM);
+ }
+
+ st->id = startcode;
+ st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
+ st->codecpar->codec_id = codec_id;
+ st->codecpar->sample_rate = sample_rate;
+ st->codecpar->ch_layout.nb_channels = nb_channels;
+
+ if (sample_bits > 0) {
+ st->codecpar->format = sample_bits == 16 ?
+ AV_SAMPLE_FMT_S16
+ : AV_SAMPLE_FMT_S32;
+ st->codecpar->bits_per_coded_sample = sample_bits;
+ st->codecpar->bits_per_raw_sample = sample_bits;
+ }
+
+ if (lang_iso639__2) {
+ av_dict_set(&st->metadata, "language", lang_iso639__2, 0);
+ }
+
+ avpriv_set_pts_info(st, DVDVIDEO_PTS_WRAP_BITS,
+ DVDVIDEO_TIME_BASE_Q.num, DVDVIDEO_TIME_BASE_Q.den);
+
+ sti = ffstream(st);
+ sti->request_probe = 0;
+ sti->need_parsing = need_parsing;
+
+ return 0;
+}
+
+static int dvdvideo_audio_stream_register_all(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ for (int i = 0; i < c->play_vts_ifo->vtsi_mat->nr_of_vts_audio_streams; i++) {
+ enum AVCodecID codec_id;
+ int startcode;
+ int sample_rate;
+ int sample_bits;
+ int nb_channels;
+ char *lang_iso639__2;
+ int ret;
+
+ if (!(c->play_pgc->audio_control[i]
+ & DVDVIDEO_AUDIO_CONTROL_ENABLED_MASK)) {
+ continue;
+ }
+
+ if ((ret = dvdvideo_audio_stream_analyze(
+ c->play_vts_ifo->vtsi_mat->vts_audio_attr[i],
+ c->play_pgc->audio_control[i], &codec_id, &startcode,
+ &sample_rate, &sample_bits, &nb_channels,
+ &lang_iso639__2)) != 0) {
+ av_log(s, AV_LOG_ERROR, "Invalid audio parameters in VTS IFO\n");
+
+ return ret;
+ }
+
+ if (c->play_vts_ifo->vtsi_mat->
+ vts_audio_attr[i].application_mode == 1) {
+ av_log(s, AV_LOG_ERROR,
+ "Audio stream uses karaoke extension which is unsupported\n");
+
+ return AVERROR_PATCHWELCOME;
+ }
+
+ for (int j = 0; j < s->nb_streams; j++) {
+ if (s->streams[j]->id == startcode) {
+ av_log(s, AV_LOG_WARNING,
+ "Audio stream has duplicate entry in VTS IFO\n");
+
+ continue;
+ }
+ }
+
+ if ((ret = dvdvideo_audio_stream_to_avstream(c->mpeg_ctx, codec_id,
+ startcode, sample_rate, sample_bits, nb_channels,
+ lang_iso639__2, AVSTREAM_PARSE_FULL)) != 0) {
+ av_log(s, AV_LOG_ERROR, "Unable to allocate video stream (subdemux)\n");
+
+ return ret;
+ }
+
+ if ((ret = dvdvideo_audio_stream_to_avstream(s, codec_id,
+ startcode, sample_rate, sample_bits, nb_channels,
+ lang_iso639__2, AVSTREAM_PARSE_NONE)) != 0) {
+ av_log(s, AV_LOG_ERROR, "Unable to allocate audio stream\n");
+
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int dvdvideo_subtitle_clut_rgb_extradata_cat(
+ const uint32_t *clut_rgb, size_t clut_rgb_size,
+ char *dst, size_t dst_size)
+{
+ if (dst_size < DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE) {
+ return AVERROR(ENOMEM);
+ }
+
+ if (clut_rgb_size != DVDVIDEO_SUBP_CLUT_SIZE) {
+ return AVERROR(EINVAL);
+ }
+
+ snprintf(dst, dst_size, "palette: ");
+
+ for (int i = 0; i < DVDVIDEO_SUBP_CLUT_LEN; i++) {
+ av_strlcatf(dst, dst_size,
+ "%06"PRIx32"%s", clut_rgb[i], i != 15 ? ", " : "");
+ }
+
+ av_strlcat(dst, "\n", 1);
+
+ return 0;
+}
+
+static int dvdvideo_subtitle_clut_yuv_to_rgb(uint32_t *clut,
+ const size_t clut_size)
+{
+ const uint8_t *cm = dvdvideo_yuv_crop_tab + DVDVIDEO_YUV_NEG_CROP_MAX;
+
+ int i, y, cb, cr;
+ uint8_t r, g, b;
+ int r_add, g_add, b_add;
+
+ if (clut_size != DVDVIDEO_SUBP_CLUT_SIZE) {
+ return AVERROR(EINVAL);
+ }
+
+ for (i = 0; i < DVDVIDEO_SUBP_CLUT_LEN; i++) {
+ y = (clut[i] >> 16) & 0xFF;
+ cr = (clut[i] >> 8) & 0xFF;
+ cb = clut[i] & 0xFF;
+
+ YUV_TO_RGB1_CCIR(cb, cr);
+ YUV_TO_RGB2_CCIR(r, g, b, y);
+
+ clut[i] = (r << 16) | (g << 8) | b;
+ }
+
+ return 0;
+}
+
+static int dvdvideo_subtitle_stream_to_avstream(AVFormatContext *s,
+ int startcode,
+ char *clut_rgb_extradata_buf, int clut_rgb_extradata_buf_size,
+ char *lang_iso639__2, enum AVStreamParseType need_parsing)
+{
+ AVStream *st;
+ FFStream *sti;
+ int ret;
+
+ st = avformat_new_stream(s, NULL);
+ if (!st) {
+ return AVERROR(ENOMEM);
+ }
+
+ st->id = startcode;
+ st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
+ st->codecpar->codec_id = AV_CODEC_ID_DVD_SUBTITLE;
+
+ if ((ret = ff_alloc_extradata(st->codecpar,
+ clut_rgb_extradata_buf_size)) != 0) {
+ return ret;
+ }
+
+ memcpy(st->codecpar->extradata, clut_rgb_extradata_buf,
+ st->codecpar->extradata_size);
+
+ if (lang_iso639__2) {
+ av_dict_set(&st->metadata, "language", lang_iso639__2, 0);
+ }
+
+ avpriv_set_pts_info(st, DVDVIDEO_PTS_WRAP_BITS,
+ DVDVIDEO_TIME_BASE_Q.num, DVDVIDEO_TIME_BASE_Q.den);
+
+ sti = ffstream(st);
+ sti->request_probe = 0;
+ sti->need_parsing = need_parsing;
+
+ return 0;
+}
+
+static int dvdvideo_subtitle_stream_register(AVFormatContext *s,
+ int position,
+ char *clut_rgb_extradata_buf, int clut_rgb_extradata_buf_size,
+ char *lang_iso639__2)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret;
+
+ int startcode = DVDVIDEO_STARTCODE_OFFSET_SUBP + position;
+
+ for (int i = 0; i < s->nb_streams; i++) {
+ /* don't need to warn the user about this, since params won't change */
+ if (s->streams[i]->id == startcode) {
+ av_log(s, AV_LOG_TRACE,
+ "Subtitle stream has duplicate entry in VTS IFO\n");
+ return 0;
+ }
+ }
+
+ if ((ret = dvdvideo_subtitle_stream_to_avstream(c->mpeg_ctx, startcode,
+ clut_rgb_extradata_buf, clut_rgb_extradata_buf_size,
+ lang_iso639__2, AVSTREAM_PARSE_FULL)) != 0) {
+ av_log(s, AV_LOG_ERROR, "Unable to allocate subtitle stream (subdemux)\n");
+
+ return ret;
+ }
+
+ if ((ret = dvdvideo_subtitle_stream_to_avstream(s, startcode,
+ clut_rgb_extradata_buf, clut_rgb_extradata_buf_size,
+ lang_iso639__2, AVSTREAM_PARSE_NONE)) != 0) {
+ av_log(s, AV_LOG_ERROR, "Unable to allocate subtitle stream\n");
+
+ return ret;
+ }
+
+ return 0;
+}
+
+static int dvdvideo_subtitle_stream_register_all(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret;
+ uint32_t clut_rgb[DVDVIDEO_SUBP_CLUT_SIZE] = {0};
+ char clut_rgb_extradata_buf[DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE] = {0};
+
+ if (c->play_vts_ifo->vtsi_mat->nr_of_vts_subp_streams < 1) {
+ return 0;
+ }
+
+ /* initialize the palette (same for all streams in this PGC) */
+ memcpy(clut_rgb, c->play_pgc->palette, DVDVIDEO_SUBP_CLUT_SIZE);
+
+ if ((ret = dvdvideo_subtitle_clut_yuv_to_rgb(
+ clut_rgb, DVDVIDEO_SUBP_CLUT_SIZE)) != 0) {
+ av_log(s, AV_LOG_ERROR, "Unable to convert subtitle palette\n");
+
+ return ret;
+ }
+
+ if ((ret = dvdvideo_subtitle_clut_rgb_extradata_cat(
+ clut_rgb, DVDVIDEO_SUBP_CLUT_SIZE,
+ clut_rgb_extradata_buf, DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE)) != 0) {
+ av_log(s, AV_LOG_ERROR, "Unable to set up subtitle extradata\n");
+
+ return ret;
+ }
+
+ for (int i = 0; i < c->play_vts_ifo->
+ vtsi_mat->nr_of_vts_subp_streams; i++) {
+ uint32_t subp_control;
+ subp_attr_t subp_attr;
+ video_attr_t video_attr;
+ char *lang_iso639__2;
+
+ subp_control = c->play_pgc->subp_control[i];
+
+ if (!(subp_control & DVDVIDEO_SUBP_CONTROL_ENABLED_MASK)) {
+ continue;
+ }
+
+ subp_attr = c->play_vts_ifo->vtsi_mat->vts_subp_attr[i];
+ video_attr = c->play_vts_ifo->vtsi_mat->vts_video_attr;
+ lang_iso639__2 = dvdvideo_lang_code_to_iso639__2(subp_attr.lang_code);
+
+ /* there can be several presentations for one SPU */
+ /* for now, be flexible with the DAR check due to weird authoring */
+ if (video_attr.display_aspect_ratio > 0) {
+ /* 16:9 */
+ ret = dvdvideo_subtitle_stream_register(s,
+ ((subp_control >> 16) & 0x1F),
+ clut_rgb_extradata_buf,
+ DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
+ lang_iso639__2);
+ if (ret != 0) {
+ return ret;
+ }
+
+ if (video_attr.permitted_df == DVDVIDEO_VTS_SPU_PFD_LETTERBOX_ONLY
+ || video_attr.permitted_df == DVDVIDEO_VTS_SPU_PFD_ANY) {
+ ret = dvdvideo_subtitle_stream_register(s,
+ ((subp_control >> 8) & 0x1F),
+ clut_rgb_extradata_buf,
+ DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
+ lang_iso639__2);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ if (video_attr.permitted_df == DVDVIDEO_VTS_SPU_PFD_PANSCAN_ONLY
+ || video_attr.permitted_df == DVDVIDEO_VTS_SPU_PFD_ANY) {
+ ret = dvdvideo_subtitle_stream_register(s,
+ (subp_control & 0x1F),
+ clut_rgb_extradata_buf,
+ DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
+ lang_iso639__2);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+ } else {
+ /* 4:3 */
+ ret = dvdvideo_subtitle_stream_register(s,
+ ((subp_control >> 24) & 0x1F),
+ clut_rgb_extradata_buf,
+ DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
+ lang_iso639__2);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int dvdvideo_subdemux_nested_io_open(AVFormatContext *s,
+ AVIOContext **pb, const char *url, int flags, AVDictionary **opts)
+{
+ av_log(s, AV_LOG_ERROR, "Nested io_open not supported for this format\n");
+
+ return AVERROR(EPERM);
+}
+
+static int dvdvideo_subdemux_read_data(void *opaque, uint8_t *buf,
+ int buf_size)
+{
+ AVFormatContext *s = opaque;
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ 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_PS_MAX_SEARCH_BLOCKS; i++) {
+ int nav_event;
+ int nav_len;
+
+ int check_title;
+ int check_pgcn;
+ int check_pgn;
+ int check_angle;
+ int check_nb_angles;
+
+ uint8_t nav_buf[DVDVIDEO_BLOCK_SIZE] = {0};
+
+ if (ff_check_interrupt(&s->interrupt_callback)) {
+ return AVERROR_EXIT;
+ }
+
+ if (dvdnav_get_next_block(c->play_dvdnav,
+ nav_buf, &nav_event, &nav_len) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Error reading block\n");
+
+ goto end_dvdnav_error;
+ }
+
+ if (nav_len > DVDVIDEO_BLOCK_SIZE) {
+ av_log(s, AV_LOG_ERROR, "Invalid block (too big)\n");
+
+ return AVERROR(ENOMEM);
+ }
+
+ if (nav_event != DVDNAV_BLOCK_OK && nav_event != DVDNAV_NAV_PACKET) {
+ av_log(s, AV_LOG_TRACE, "nav event=%d len=%d\n", nav_event, nav_len);
+ }
+
+ if (dvdnav_current_title_program(c->play_dvdnav, &check_title,
+ &check_pgcn, &check_pgn) != DVDNAV_STATUS_OK) {
+ goto end_dvdnav_error;
+ }
+
+ if (check_title != c->opt_title || check_pgcn != c->play_pgcn
+ || !dvdnav_is_domain_vts(c->play_dvdnav)) {
+ return AVERROR_EOF;
+ }
+
+ if (dvdnav_get_angle_info(c->play_dvdnav, &check_angle,
+ &check_nb_angles) != DVDNAV_STATUS_OK) {
+ goto end_dvdnav_error;
+ }
+
+ if (check_angle != c->opt_angle) {
+ av_log(s, AV_LOG_ERROR, "Unexpected angle change\n");
+
+ return AVERROR_INPUT_CHANGED;
+ }
+
+ switch (nav_event) {
+ case DVDNAV_BLOCK_OK:
+ if (nav_len != DVDVIDEO_BLOCK_SIZE) {
+ av_log(s, AV_LOG_ERROR, "Invalid block\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ memcpy(buf, &nav_buf, nav_len);
+
+ return nav_len;
+ case DVDNAV_HIGHLIGHT:
+ case DVDNAV_VTS_CHANGE:
+ case DVDNAV_STILL_FRAME:
+ case DVDNAV_STOP:
+ case DVDNAV_WAIT:
+ return AVERROR_EOF;
+ default:
+ continue;
+ }
+ }
+
+ av_log(s, AV_LOG_ERROR, "Unable to find next MPEG block\n");
+
+ return AVERROR_UNKNOWN;
+
+end_dvdnav_error:
+ av_log(s, AV_LOG_ERROR, "libdvdnav: %s\n", dvdnav_err_to_string(c->play_dvdnav));
+
+ return AVERROR_EXTERNAL;
+}
+
+static int64_t dvdvideo_subdemux_seek_data(void *opaque, int64_t offset,
+ int whence)
+{
+ AVFormatContext *s = opaque;
+
+ av_log(s, AV_LOG_ERROR,
+ "dvdvideo_subdemux_seek_data(): not implemented\n");
+
+ return AVERROR_PATCHWELCOME;
+}
+
+static int dvdvideo_subdemux_open(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret;
+
+ const AVInputFormat *mpeg_fmt;
+ AVFormatContext *mpeg_ctx;
+ uint8_t *mpeg_buf;
+
+ 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, dvdvideo_subdemux_seek_data);
+ c->mpeg_pb.pub.seekable = 0;
+
+ if ((ret = ff_copy_whiteblacklists(mpeg_ctx, s)) != 0) {
+ avformat_free_context(mpeg_ctx);
+
+ return ret;
+ }
+
+ /* TODO: fix the PTS issues (ask about this in ML) */
+ /* AVFMT_FLAG_GENPTS works in some scenarios, but in others, the reading process hangs */
+ mpeg_ctx->flags = AVFMT_FLAG_CUSTOM_IO | AVFMT_FLAG_GENPTS;
+
+ 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 = dvdvideo_subdemux_nested_io_open;
+
+ 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 void dvdvideo_subdemux_close(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ av_freep(&c->mpeg_pb.pub.buffer);
+ memset(&c->mpeg_pb, 0x00, sizeof(c->mpeg_pb));
+ av_freep(&c->mpeg_pb);
+
+ avformat_close_input(&c->mpeg_ctx);
+}
+
+static int dvdvideo_chapters_register(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+ int start_time = 0;
+ int cell = 0;
+
+ for (int i = 0; i < c->play_pgc->nr_of_programs; i++) {
+ int64_t ms = 0;
+ int next = c->play_pgc->program_map[i + 1];
+
+ if (i == c->play_pgc->nr_of_programs - 1) {
+ next = c->play_pgc->nr_of_cells + 1;
+ }
+
+ while (cell < next - 1) {
+ /* only consider first cell of multi-angle cells */
+ if (c->play_pgc->cell_playback[cell].block_mode <= 1) {
+ ms = ms + dvdvideo_time_to_millis(
+ &c->play_pgc->cell_playback[cell].playback_time);
+ }
+ cell++;
+ }
+
+ if (!avpriv_new_chapter(s, i, DVDVIDEO_TIME_BASE_MILLIS_Q,
+ start_time, start_time + ms, NULL)) {
+ av_log(s, AV_LOG_ERROR, "Unable to allocate chapter");
+ return AVERROR(ENOMEM);
+ }
+
+ start_time += ms;
+ }
+
+ return 0;
+}
+
+static int dvdvideo_read_header(AVFormatContext *s)
+{
+ int ret;
+
+ if ((ret = dvdvideo_volume_open(s)) != 0) {
+ return ret;
+ }
+
+ if ((ret = dvdvideo_playback_open(s)) != 0) {
+ return ret;
+ }
+
+ if ((ret = dvdvideo_subdemux_open(s)) != 0) {
+ return ret;
+ }
+
+ if ((ret = dvdvideo_video_stream_register(s)) != 0) {
+ return ret;
+ }
+
+ if ((ret = dvdvideo_audio_stream_register_all(s)) != 0) {
+ return ret;
+ }
+
+ if ((ret = dvdvideo_subtitle_stream_register_all(s)) != 0) {
+ return ret;
+ }
+
+ if ((ret = dvdvideo_chapters_register(s)) != 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+static int dvdvideo_read_packet(AVFormatContext *s, AVPacket *pkt)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ while (!ff_check_interrupt(&s->interrupt_callback)) {
+ int subdemux_ret;
+ AVPacket *subdemux_pkt;
+
+ subdemux_pkt = av_packet_alloc();
+ if (!subdemux_pkt) {
+ return AVERROR(ENOMEM);
+ }
+
+ subdemux_ret = av_read_frame(c->mpeg_ctx, subdemux_pkt);
+ if (subdemux_ret >= 0) {
+ if (c->mpeg_ctx->nb_streams != s->nb_streams) {
+ av_log(s, AV_LOG_ERROR, "Unexpected stream during playback\n");
+
+ av_packet_unref(subdemux_pkt);
+
+ return AVERROR_INPUT_CHANGED;
+ }
+
+ av_packet_move_ref(pkt, subdemux_pkt);
+
+ return 0;
+ }
+
+ return subdemux_ret;
+ }
+
+ return AVERROR_EOF;
+}
+
+static int dvdvideo_read_seek(AVFormatContext *s, int stream_index,
+ int64_t timestamp, int flags)
+{
+ av_log(s, AV_LOG_ERROR, "dvdvideo_read_seek(): not implemented\n");
+
+ return AVERROR_PATCHWELCOME;
+}
+
+static int dvdvideo_close(AVFormatContext *s)
+{
+ dvdvideo_subdemux_close(s);
+ dvdvideo_playback_close(s);
+ dvdvideo_volume_close(s);
+
+ return 0;
+}
+
+static int dvdvideo_probe(const AVProbeData *p)
+{
+ av_log(NULL, AV_LOG_ERROR, "dvdvideo_probe(): not implemented\n");
+
+ return AVERROR_PATCHWELCOME;
+}
+
+#define OFFSET(x) offsetof(DVDVideoDemuxContext, x)
+static const AVOption dvdvideo_options[] = {
+ {"title", "Title Number", OFFSET(opt_title), AV_OPT_TYPE_INT, { .i64=1 }, 1, DVDVIDEO_TITLE_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
+ {"ptt", "Entry PTT Number", OFFSET(opt_ptt), AV_OPT_TYPE_INT, { .i64=1 }, 1, DVDVIDEO_PTT_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
+ {"pgc", "Entry PGC Number (0=auto)", OFFSET(opt_pgc), AV_OPT_TYPE_INT, { .i64=0 }, 0, DVDVIDEO_PGC_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
+ {"pg", "Entry PG Number (0=auto)", OFFSET(opt_pg), AV_OPT_TYPE_INT, { .i64=0 }, 0, DVDVIDEO_PG_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
+ {"angle", "Video Angle Number", OFFSET(opt_angle), AV_OPT_TYPE_INT, { .i64=1 }, 1, DVDVIDEO_ANGLE_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
+ {"region", "Playback Region Number (0=free)", OFFSET(opt_region), AV_OPT_TYPE_INT, { .i64=0 }, 0, DVDVIDEO_REGION_NUMBER_MAX, 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_NO_BYTE_SEEK | AVFMT_NOGENSEARCH | AVFMT_NOBINSEARCH | AVFMT_SHOW_IDS | AVFMT_TS_DISCONT,
+ .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
+};
--
2.34.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH] [WIP] [RFC] dvdvideo: initial contribution (DVD demuxer)
2023-12-09 10:06 [FFmpeg-devel] [PATCH] [WIP] [RFC] dvdvideo: initial contribution (DVD demuxer) Marth64
@ 2023-12-10 2:27 ` Marth64
2023-12-10 3:03 ` Leo Izen
` (2 subsequent siblings)
3 siblings, 0 replies; 27+ messages in thread
From: Marth64 @ 2023-12-10 2:27 UTC (permalink / raw)
To: Marth64; +Cc: ffmpeg-devel
Realizing this late, but in full transparency there are some portions
adapted from VLC player (which is LGPL), namely chapter and time
conversions, and small number of lines adopted from Handbrake (which is
GPL), specifically one if statement validation and a constant value. For
the latter I will send a request to relicense as LGPL and update here. I
will then give credit in the header.
My apologies for mixing this. In the mean time I will also fix some
aforementioned bugs.
On Sat, Dec 9, 2023 at 4:06 AM Marth64 <marth64@proxyid.net> wrote:
> Hello, I am happy to share a DVD demuxer for ffmpeg powered by libdvdread
> and libdvdnav.
> I have been working on this on/off throughout the year and think it is in
> a good spot
> to share at the ML now. This was a major learning experience for me in
> many ways and
> am open to any feedback on how I could improve it to be better. In fact,
> there is still
> one issue I can't seem to figure out how to solve (in discussion below).
> Regardless,
> a good number of DVDs I have tried seem to work out of the box. This is a
> full-service
> demuxer with chapters, subtitles (and their palettes), as well as language
> metadata.
>
> At a high level, the demuxer uses libdvdread for metadata of the disc,
> libdvdnav for
> the actual playback, and the MPEG-PS demuxer for the underlying VOB stream.
>
> First, the basic usage, is quite straightforward:
> ffmpeg -f dvdvideo -title NN -i DVD.iso|/dev/srX|/path/to/DVD ...
> Where NN can be replaced by your known title number in the disc
>
> As the demuxer effectively works at a PGC level, multi-PGC titles are not
> supported.
> But to provide this flexibility as there are many weirdly authored DVDs
> out there,
> one can specify an exact PGC via -pgc option and an associated program
> (PG, can start at 1).
>
> I am hoping and willing to improve this to be a robust demuxer wherever
> possible, but to
> that extent there is still an issue I can't figure out how to solve:
> Dealing with DTS discontinuities
> and PTS generation.
>
> Right now, as a band-aid, I am adding AVFMT_FLAG_GENPTS (line 1408)
> to the MPEG-PS subdemuxer. This works with most discs and the output seems
> OK. On discs with discontinuities, however,
> this causes the demuxing to hang which is obviously unacceptable also.
> Removing the flag causes the discs
> to not hang, but then the output for all discs becomes choppy for obvious
> reasons (invalid PTS).
>
> It could be because I have some misunderstandings on how things work
> within ffmpeg,
> but I have tried to the point now where I thought it best to ask the
> experts for help if you can spot
> what I am doing wrong. I am really motivated to make this work and good
> quality.
>
> Thank you!
>
> ---
> configure | 8 +
> libavformat/Makefile | 1 +
> libavformat/allformats.c | 1 +
> libavformat/avlanguage.c | 10 +-
> libavformat/dvdvideodec.c | 1599 +++++++++++++++++++++++++++++++++++++
> 5 files changed, 1617 insertions(+), 2 deletions(-)
> create mode 100644 libavformat/dvdvideodec.c
>
> diff --git a/configure b/configure
> index d77c053226..65e5968194 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]
> @@ -1802,6 +1804,8 @@ EXTERNAL_LIBRARY_GPL_LIST="
> frei0r
> libcdio
> libdavs2
> + libdvdnav
> + libdvdread
> librubberband
> libvidstab
> libx264
> @@ -3494,6 +3498,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"
> @@ -6723,6 +6729,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 && require_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 2db83aff81..45dba53044 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 c8bb4e3866..dc2acf575c 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..2b22a7de4f
> --- /dev/null
> +++ b/libavformat/dvdvideodec.c
> @@ -0,0 +1,1599 @@
> +/*
> + * DVD-Video demuxer (powered by libdvdnav/libdvdread)
> + *
> + * 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
> + * 7) Close resources
> + **/
> +
> +/**
> + * Issues/bug tracker (TODO):
> + * - SDDS is not supported yet
> + * - Are we handling backwards cell changes and PTT changes correctly?
> + * - Some codec parameters/metadata is not being explicitly set:
> + * -> ChannelLayout, color/chroma info, DAR, frame size for MP1/MP2
> audio
> + * - Additional PGC validations?
> +**/
> +
> +#include <dvdread/dvd_reader.h>
> +#include <dvdread/ifo_read.h>
> +#include <dvdread/ifo_types.h>
> +#include <dvdread/nav_read.h>
> +#include <dvdnav/dvdnav.h>
> +
> +#include "libavutil/avstring.h"
> +#include "libavutil/avutil.h"
> +#include "libavutil/colorspace.h"
> +#include "libavutil/mem.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/samplefmt.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 ZERO_Q (AVRational) { 0,
> 1 }
> +
> +#define DVDVIDEO_PS_MAX_SEARCH_BLOCKS 128
> +#define DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE 1024
> +
> +#define DVDVIDEO_TIME_BASE_Q (AVRational) { 1,
> 90000 }
> +#define DVDVIDEO_TIME_BASE_MILLIS_Q (AVRational) { 1,
> 1000 }
> +#define DVDVIDEO_PTS_WRAP_BITS 32
> +#define DVDVIDEO_BLOCK_SIZE 2048
> +
> +#define DVDVIDEO_NTSC_FRAMERATE_Q (AVRational) {
> 30000, 1001 }
> +#define DVDVIDEO_NTSC_HEIGHT 480
> +#define DVDVIDEO_PAL_FRAMERATE_Q (AVRational) {
> 25, 1 }
> +#define DVDVIDEO_PAL_HEIGHT 576
> +#define DVDVIDEO_D1_WIDTH 720
> +#define DVDVIDEO_4CIF_WIDTH 704
> +#define DVDVIDEO_D1_HALF_WIDTH 352
> +#define DVDVIDEO_CIF_WIDTH 352
> +#define DVDVIDEO_PIXEL_FORMAT AV_PIX_FMT_YUV420P
> +
> +#define DVDVIDEO_STARTCODE_VIDEO 0x1E0
> +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_AC3 0x80
> +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP1 0x1C0
> +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP2 0x1C0
> +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_PCM 0xA0
> +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_DTS 0x88
> +#define DVDVIDEO_STARTCODE_OFFSET_SUBP 0x20
> +
> +#define DVDVIDEO_TITLE_NUMBER_MAX 99
> +#define DVDVIDEO_PTT_NUMBER_MAX 99
> +#define DVDVIDEO_PGC_NUMBER_MAX 32767
> +#define DVDVIDEO_PG_NUMBER_MAX 255
> +#define DVDVIDEO_ANGLE_NUMBER_MAX 9
> +#define DVDVIDEO_VTS_NUMBER_MAX 99
> +#define DVDVIDEO_TT_NUMBER_MAX 99
> +#define DVDVIDEO_REGION_NUMBER_MAX 8
> +#define DVDVIDEO_AUDIO_CONTROL_ENABLED_MASK 0x8000
> +#define DVDVIDEO_SUBP_CONTROL_ENABLED_MASK 0x80000000
> +#define DVDVIDEO_VTS_AUDIO_STREAMS_MAX 8
> +#define DVDVIDEO_VTS_SUBP_STREAMS_MAX 32
> +
> +/* ("palette: ") + ("rrggbb, "*15) + ("rrggbb") + \n + \0 */
> +#define DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE (9 + (8 * 15) + 6
> + 1 + 1)
> +#define DVDVIDEO_SUBP_CLUT_LEN 16
> +#define DVDVIDEO_SUBP_CLUT_SIZE
> DVDVIDEO_SUBP_CLUT_LEN * sizeof(uint32_t)
> +
> +/* convert binary-coded decimal to decimal */
> +#define BCD2D(__x__) (((__x__ & 0xF0) >> 4) * 10 + (__x__ & 0x0F))
> +
> +/* crop table for YUV to RGB subpicture palette conversion */
> +#define DVDVIDEO_YUV_NEG_CROP_MAX 1024
> +#define times4(x) x, x, x, x
> +#define times256(x) times4(times4(times4(times4(times4(x)))))
> +
> +const uint8_t dvdvideo_yuv_crop_tab[256 + 2 * DVDVIDEO_YUV_NEG_CROP_MAX]
> = {
> +times256(0x00),
>
> +0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
>
> +0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
>
> +0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
>
> +0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
>
> +0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
>
> +0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,
>
> +0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
>
> +0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,
>
> +0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
>
> +0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
>
> +0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
>
> +0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
>
> +0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
>
> +0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
>
> +0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,
>
> +0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF,
> +times256(0xFF)
> +};
> +
> +enum DVDVideoVTSMPEGVersion {
> + DVDVIDEO_VTS_MPEG_VERSION_MPEG1 = 0,
> + DVDVIDEO_VTS_MPEG_VERSION_MPEG2 = 1
> +};
> +
> +enum DVDVideoVTSPictureFormat {
> + DVDVIDEO_VTS_PICTURE_FORMAT_NTSC = 0,
> + DVDVIDEO_VTS_PICTURE_FORMAT_PAL = 1
> +};
> +
> +enum DVDVideoVTSPictureSize {
> + DVDVIDEO_VTS_PICTURE_SIZE_D1 = 0,
> + DVDVIDEO_VTS_PICTURE_SIZE_4CIF = 1,
> + DVDVIDEO_VTS_PICTURE_SIZE_D1_HALF = 2,
> + DVDVIDEO_VTS_PICTURE_SIZE_CIF = 3
> +};
> +
> +enum DVDVideoVTSPictureDAR {
> + DVDVIDEO_VTS_PICTURE_DAR_4_3 = 0,
> + DVDVIDEO_VTS_PICTURE_DAR_16_9 = 3
> +};
> +
> +enum DVDVideoVTSPermittedFullscreenDisplay {
> + DVDVIDEO_VTS_SPU_PFD_ANY = 0,
> + DVDVIDEO_VTS_SPU_PFD_PANSCAN_ONLY = 1,
> + DVDVIDEO_VTS_SPU_PFD_LETTERBOX_ONLY = 2
> +};
> +
> +enum DVDVideoVTSAudioFormat {
> + DVDVIDEO_VTS_AUDIO_FORMAT_AC3 = 0,
> + DVDVIDEO_VTS_AUDIO_FORMAT_MP1 = 2,
> + DVDVIDEO_VTS_AUDIO_FORMAT_MP2 = 3,
> + DVDVIDEO_VTS_AUDIO_FORMAT_PCM = 4,
> + DVDVIDEO_VTS_AUDIO_FORMAT_SDDS = 5,
> + DVDVIDEO_VTS_AUDIO_FORMAT_DTS = 6
> +};
> +
> +enum FFDVDVideoVTSAudioSampleRate {
> + DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_48K = 0,
> + DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_96K = 1
> +};
> +
> +enum FFDVDVideoVTSAudioQuantization {
> + DVDVIDEO_VTS_AUDIO_QUANTIZATION_16 = 0,
> + DVDVIDEO_VTS_AUDIO_QUANTIZATION_20 = 1,
> + DVDVIDEO_VTS_AUDIO_QUANTIZATION_24 = 2
> +};
> +
> +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 identification 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 *vol_dvdread; /* handle to
> libdvdread */
> + ifo_handle_t *vol_vmg_ifo; /* handle to
> the VMG (VIDEO_TS.IFO) */
> +
> + /* playback */
> + ifo_handle_t *play_vts_ifo; /* handle to
> the active VTS (VTS_nn_n.IFO) */
> + pgc_t *play_pgc; /* handle to
> the active PGC */
> + int play_vtsn; /* number of
> the active VTS (video title set) */
> + int play_pgcn; /* number of
> the active PGC (program chain) */
> + dvdnav_t *play_dvdnav; /* handle to
> libdvdnav */
> +} DVDVideoDemuxContext;
> +
> +static void dvdvideo_libdvdread_log(void *opaque, dvd_logger_level_t
> level,
> + const char *msg, va_list msg_va)
> +{
> + AVFormatContext *s = opaque;
> + int lavu_level;
> +
> + char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
> + vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
> +
> + switch (level) {
> + case DVD_LOGGER_LEVEL_ERROR:
> + lavu_level = AV_LOG_ERROR;
> + break;
> + case DVD_LOGGER_LEVEL_WARN:
> + lavu_level = AV_LOG_WARNING;
> + break;
> + case DVD_LOGGER_LEVEL_INFO:
> + lavu_level = AV_LOG_INFO;
> + break;
> + case DVD_LOGGER_LEVEL_DEBUG:
> + default:
> + lavu_level = AV_LOG_DEBUG;
> + }
> +
> + /* libdvdread messages don't have line terminators */
> + av_log(s, lavu_level, "libdvdread: %s\n", msg_buf);
> +}
> +
> +static void dvdvideo_libdvdnav_log(void *opaque, dvdnav_logger_level_t
> level,
> + const char *msg, va_list msg_va)
> +{
> + AVFormatContext *s = opaque;
> + int lavu_level;
> +
> + char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
> + vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
> +
> + switch (level) {
> + case DVDNAV_LOGGER_LEVEL_ERROR:
> + lavu_level = AV_LOG_ERROR;
> + break;
> + case DVDNAV_LOGGER_LEVEL_WARN:
> + lavu_level = AV_LOG_WARNING;
> + break;
> + case DVDNAV_LOGGER_LEVEL_INFO:
> + lavu_level = AV_LOG_INFO;
> + break;
> + case DVDNAV_LOGGER_LEVEL_DEBUG:
> + default:
> + lavu_level = AV_LOG_DEBUG;
> + }
> +
> + /* libdvdnav messages don't have line terminators */
> + av_log(s, lavu_level, "libdvdnav: %s\n", msg_buf);
> +}
> +
> +static char *dvdvideo_lang_code_to_iso639__2(const uint16_t lang_code)
> +{
> + char lang_code_str[3] = {0};
> +
> + if (lang_code && lang_code != 0xFFFF) {
> + lang_code_str[0] = (lang_code >> 8) & 0xFF;
> + lang_code_str[1] = lang_code & 0xFF;
> + }
> +
> + return (char *) ff_convert_lang_to(lang_code_str,
> AV_LANG_ISO639_2_BIBL);
> +}
> +
> +static int64_t dvdvideo_time_to_millis(dvd_time_t *time)
> +{
> + double fps;
> + int64_t ms;
> +
> + int64_t sec = (int64_t) (BCD2D(time->hour)) * 60 * 60;
> + sec += (int64_t) (BCD2D(time->minute)) * 60;
> + sec += (int64_t) (BCD2D(time->second));
> +
> + /* the 2 high bits are the frame rate */
> + switch ((time->frame_u & 0xC0) >> 6)
> + {
> + case 1:
> + fps = 25.0;
> + break;
> + case 3:
> + fps = 29.97;
> + break;
> + default:
> + fps = 2500.0;
> + break;
> + }
> + ms = BCD2D(time->frame_u & 0x3F) * 1000.0 / fps;
> +
> + return (sec * 1000) + ms;
> +}
> +
> +static int dvdvideo_volume_is_title_valid_in_context(AVFormatContext *s,
> + int title)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + return title >= 1
> + && title <= DVDVIDEO_TITLE_NUMBER_MAX
> + && title <= c->vol_vmg_ifo->tt_srpt->nr_of_srpts
> + && c->vol_vmg_ifo->tt_srpt->title[title - 1].nr_of_ptts > 0;
> +}
> +
> +static int dvdvideo_volume_is_title_valid_in_vts(title_info_t title_info,
> + ifo_handle_t *vts_ifo)
> +{
> + return title_info.vts_ttn >= 1
> + && title_info.vts_ttn <= DVDVIDEO_TT_NUMBER_MAX
> + && title_info.vts_ttn <= vts_ifo->vts_ptt_srpt->nr_of_srpts
> + && vts_ifo->vtsi_mat->nr_of_vts_audio_streams
> + <= DVDVIDEO_VTS_AUDIO_STREAMS_MAX
> + && vts_ifo->vtsi_mat->nr_of_vts_subp_streams
> + <= DVDVIDEO_VTS_SUBP_STREAMS_MAX;
> +}
> +
> +static int dvdvideo_volume_is_angle_valid_in_title(int angle,
> + title_info_t title_info)
> +{
> + return angle >= 1
> + && angle <= DVDVIDEO_ANGLE_NUMBER_MAX
> + && angle <= title_info.nr_of_angles;
> +}
> +
> +static int dvdvideo_volume_is_pgc_valid_and_sequential(pgc_t *pgc)
> +{
> + return pgc
> + && pgc->program_map
> + && pgc->cell_playback != NULL
> + && pgc->pg_playback_mode == 0
> + && pgc->nr_of_programs > 0
> + && pgc->nr_of_cells > 0;
> +}
> +
> +static int dvdvideo_volume_is_pgcn_in_vts(int pgcn, ifo_handle_t *vts_ifo)
> +{
> + return pgcn >= 1
> + && pgcn <= DVDVIDEO_PGC_NUMBER_MAX
> + && pgcn <= vts_ifo->vts_pgcit->nr_of_pgci_srp;
> +}
> +
> +static int dvdvideo_volume_is_pgn_in_pgc(int pgn, pgc_t *pgc)
> +{
> + return pgn >= 1
> + && pgn <= DVDVIDEO_PG_NUMBER_MAX
> + && pgn <= pgc->nr_of_programs;
> +}
> +
> +static int dvdvideo_volume_open(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + dvd_reader_t *dvdread;
> + ifo_handle_t *vmg_ifo;
> +
> + dvd_logger_cb dvdread_log_cb = { .pf_log = dvdvideo_libdvdread_log };
> + dvdread = DVDOpen2(s, &dvdread_log_cb, s->url);
> +
> + if (!dvdread) {
> + av_log(s, AV_LOG_ERROR, "Unable to initialize libdvdread\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + if (!(vmg_ifo = ifoOpen(dvdread, 0))) {
> + DVDClose(dvdread);
> +
> + av_log(s, AV_LOG_ERROR,
> + "Unable to open VIDEO_TS.IFO. "
> + "Input does not have a valid DVD-Video structure "
> + "or is not a compliant UDF DVD-Video image.\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + c->vol_dvdread = dvdread;
> + c->vol_vmg_ifo = vmg_ifo;
> +
> + return 0;
> +}
> +
> +static void dvdvideo_volume_close(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + if (c->vol_vmg_ifo) {
> + ifoClose(c->vol_vmg_ifo);
> + c->vol_vmg_ifo = NULL;
> + }
> +
> + if (c->vol_dvdread) {
> + DVDClose(c->vol_dvdread);
> + c->vol_dvdread = NULL;
> + }
> +}
> +
> +static int dvdvideo_volume_open_vts_ifo(AVFormatContext *s, int vtsn,
> + ifo_handle_t **p_vts_ifo)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + ifo_handle_t *vts_ifo;
> +
> + if (vtsn < 1
> + || vtsn > DVDVIDEO_VTS_NUMBER_MAX
> + || !(vts_ifo = ifoOpen(c->vol_dvdread, vtsn))) {
> + return AVERROR_INVALIDDATA;
> + }
> +
> + for (int i = 0; i < vts_ifo->vts_c_adt->nr_of_vobs; i++) {
> + int start_sector =
> vts_ifo->vts_c_adt->cell_adr_table[i].start_sector;
> + int end_sector =
> vts_ifo->vts_c_adt->cell_adr_table[i].last_sector;
> +
> + if ((start_sector & 0xFFFFFF) == 0xFFFFFF
> + || (end_sector & 0xFFFFFF) == 0xFFFFFF
> + || start_sector >= end_sector) {
> + ifoClose(vts_ifo);
> +
> + av_log(s, AV_LOG_WARNING, "VTS has invalid cell address
> table\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> + }
> +
> + (*p_vts_ifo) = vts_ifo;
> +
> + return 0;
> +}
> +
> +static int dvdvideo_playback_open(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret;
> +
> + ifo_handle_t *vts_ifo;
> + dvdnav_logger_cb dvdnav_log_cb;
> + dvdnav_status_t dvdnav_status;
> + dvdnav_t *dvdnav;
> +
> + title_info_t title_info;
> + int pgcn;
> + int pgn;
> + pgc_t *pgc;
> +
> + int32_t disc_region_mask;
> + int32_t player_region_mask;
> + int cell_search_has_vts = 0;
> +
> + /* if the title is valid, open its VTS IFO */
> + if (!dvdvideo_volume_is_title_valid_in_context(s, c->opt_title)) {
> + av_log(s, AV_LOG_ERROR, "Title not found or invalid\n");
> +
> + return AVERROR_STREAM_NOT_FOUND;
> + }
> +
> + title_info = c->vol_vmg_ifo->tt_srpt->title[c->opt_title - 1];
> +
> + if ((ret = dvdvideo_volume_open_vts_ifo(s,
> + title_info.title_set_nr, &vts_ifo)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Title VTS could not be opened\n");
> +
> + return ret;
> + }
> +
> + if (!dvdvideo_volume_is_title_valid_in_vts(title_info, vts_ifo)) {
> + av_log(s, AV_LOG_ERROR, "Title VTS is invalid\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + if (!dvdvideo_volume_is_angle_valid_in_title(c->opt_angle,
> title_info)) {
> + av_log(s, AV_LOG_ERROR, "Angle not found or invalid\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + /* determine our PGC and PG (playback coordinates) */
> + if (c->opt_pgc > 0 && c->opt_pg > 0) {
> + if (c->opt_ptt > 0) {
> + av_log(s, AV_LOG_WARNING,
> + "PTT option ignored as PGC and PG are provided\n");
> + }
> +
> + pgcn = c->opt_pgc;
> + pgn = c->opt_pg;
> + } else {
> + if (c->opt_ptt > title_info.nr_of_ptts) {
> + av_log(s, AV_LOG_ERROR, "PTT not found\n");
> +
> + return AVERROR_STREAM_NOT_FOUND;
> + }
> +
> + pgcn = vts_ifo->vts_ptt_srpt->title[title_info.vts_ttn -
> 1].ptt[c->opt_ptt - 1].pgcn;
> + pgn = vts_ifo->vts_ptt_srpt->title[title_info.vts_ttn -
> 1].ptt[c->opt_ptt - 1].pgn;
> + }
> +
> + if (!dvdvideo_volume_is_pgcn_in_vts(pgcn, vts_ifo)) {
> + av_log(s, AV_LOG_ERROR, "PGC not found\n");
> +
> + return AVERROR_STREAM_NOT_FOUND;
> + }
> +
> + pgc = vts_ifo->vts_pgcit->pgci_srp[pgcn - 1].pgc;
> +
> + if (!dvdvideo_volume_is_pgc_valid_and_sequential(pgc)) {
> + av_log(s, AV_LOG_ERROR, "PGC not valid\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + if (!dvdvideo_volume_is_pgn_in_pgc(pgn, pgc)) {
> + av_log(s, AV_LOG_ERROR, "PG not found\n");
> +
> + return AVERROR_STREAM_NOT_FOUND;
> + }
> +
> + /* set up libdvdnav */
> + dvdnav_log_cb = (dvdnav_logger_cb) { .pf_log = dvdvideo_libdvdnav_log
> };
> + dvdnav_status = dvdnav_open2(&dvdnav, NULL, &dvdnav_log_cb, s->url);
> +
> + if (!dvdnav) {
> + av_log(s, AV_LOG_ERROR, "Unable to initialize libdvdnav\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + if (dvdnav_status != DVDNAV_STATUS_OK) {
> + goto end_search_error_dvdnav;
> + }
> +
> + if (dvdnav_set_readahead_flag(dvdnav, 0) != DVDNAV_STATUS_OK) {
> + goto end_search_error_dvdnav;
> + }
> +
> + if (dvdnav_set_PGC_positioning_flag(dvdnav, 1) != DVDNAV_STATUS_OK) {
> + goto end_search_error_dvdnav;
> + }
> +
> + if (dvdnav_get_region_mask(dvdnav, &disc_region_mask) !=
> DVDNAV_STATUS_OK) {
> + goto end_search_error_dvdnav;
> + }
> +
> + if (c->opt_region > 0) {
> + player_region_mask = (1 << (c->opt_region - 1));
> + } else {
> + player_region_mask = disc_region_mask;
> + }
> +
> + if (dvdnav_set_region_mask(dvdnav, player_region_mask) !=
> DVDNAV_STATUS_OK) {
> + goto end_search_error_dvdnav;
> + }
> +
> + if (dvdnav_program_play(dvdnav, c->opt_title, pgcn, pgn) !=
> DVDNAV_STATUS_OK) {
> + goto end_search_error_dvdnav;
> + }
> +
> + if (dvdnav_angle_change(dvdnav, c->opt_angle) != DVDNAV_STATUS_OK) {
> + goto end_search_error_dvdnav;
> + }
> +
> + /* lock on to title's VTS and chosen PGC/PGN coordinates */
> + for (int i = 0; i < DVDVIDEO_PS_MAX_SEARCH_BLOCKS; i++) {
> + int nav_event;
> + int nav_len;
> + dvdnav_vts_change_event_t *vts_event;
> + uint8_t nav_buf[DVDVIDEO_BLOCK_SIZE] = {0};
> +
> + if (ff_check_interrupt(&s->interrupt_callback)) {
> + return AVERROR_EXIT;
> + }
> +
> + if (dvdnav_get_next_block(dvdnav, nav_buf, &nav_event, &nav_len)
> + != DVDNAV_STATUS_OK) {
> + goto end_search_error_dvdnav;
> + }
> +
> + if (nav_len > DVDVIDEO_BLOCK_SIZE) {
> + av_log(s, AV_LOG_ERROR, "Invalid block (too big)\n");
> +
> + ret = AVERROR(ENOMEM);
> +
> + goto end_search_error;
> + }
> +
> + av_log(s, AV_LOG_TRACE, "nav event=%d len=%d\n", nav_event,
> nav_len);
> +
> + switch (nav_event) {
> + case DVDNAV_VTS_CHANGE:
> + vts_event = (dvdnav_vts_change_event_t *) nav_buf;
> +
> + if (cell_search_has_vts) {
> + if (vts_event->new_vtsN != title_info.title_set_nr
> + || vts_event->new_domain !=
> DVD_DOMAIN_VTSTitle) {
> + av_log(s, AV_LOG_ERROR, "Unexpected VTS
> change\n");
> +
> + ret = AVERROR_INPUT_CHANGED;
> +
> + goto end_search_error;
> + }
> + continue;
> + }
> +
> + if (vts_event->new_vtsN == title_info.title_set_nr
> + && vts_event->new_domain == DVD_DOMAIN_VTSTitle) {
> + cell_search_has_vts = 1;
> + }
> +
> + continue;
> + case DVDNAV_CELL_CHANGE:
> + // if we need more info about the cell:
> + // dvdnav_cell_change_event_t *event_data =
> + // (dvdnav_cell_change_event_t *) nav_buf;
> + int check_title;
> + int check_pgcn;
> + int check_pgn;
> +
> + if (!cell_search_has_vts) {
> + continue;
> + }
> +
> + dvdnav_current_title_program(dvdnav, &check_title,
> + &check_pgcn, &check_pgn);
> +
> + if (check_title == c->opt_title && check_pgcn == pgcn
> + && check_pgn == pgn &&
> dvdnav_is_domain_vts(dvdnav)) {
> + goto end_ready;
> + }
> +
> + continue;
> + case DVDNAV_STILL_FRAME:
> + dvdnav_still_skip(dvdnav);
> +
> + continue;
> + case DVDNAV_WAIT:
> + dvdnav_wait_skip(dvdnav);
> +
> + continue;
> + case DVDNAV_STOP:
> + ret = AVERROR_INPUT_CHANGED;
> +
> + goto end_search_error;
> + default:
> + continue;
> + }
> + }
> +
> +end_search_error_dvdnav:
> + ret = AVERROR_EXTERNAL;
> + av_log(s, AV_LOG_ERROR, "libdvdnav: %s\n",
> dvdnav_err_to_string(dvdnav));
> + av_log(s, AV_LOG_ERROR, "Error starting playback\n");
> +
> +end_search_error:
> + dvdnav_close(dvdnav);
> + ifoClose(vts_ifo);
> +
> + return ret;
> +
> +end_ready:
> + /* update the context */
> + c->play_vts_ifo = vts_ifo;
> + c->play_pgc = pgc;
> + c->play_vtsn = title_info.title_set_nr;
> + c->play_pgcn = pgcn;
> + c->play_dvdnav = dvdnav;
> +
> + return 0;
> +}
> +
> +static void dvdvideo_playback_close(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + if (c->play_dvdnav) {
> + dvdnav_close(c->play_dvdnav);
> + c->play_dvdnav = NULL;
> + }
> +
> + if (c->play_vts_ifo) {
> + ifoClose(c->play_vts_ifo);
> + c->play_vts_ifo = NULL;
> + }
> +}
> +
> +static int dvdvideo_video_stream_analyze(video_attr_t video_attr,
> + enum AVCodecID *p_codec_id, AVRational *p_framerate,
> + int *p_height, int *p_width)
> +{
> + enum AVCodecID codec_id = AV_CODEC_ID_NONE;
> + AVRational framerate = ZERO_Q;
> + int height = 0;
> + int width = 0;
> +
> + switch (video_attr.mpeg_version) {
> + case DVDVIDEO_VTS_MPEG_VERSION_MPEG1:
> + codec_id = AV_CODEC_ID_MPEG1VIDEO;
> + break;
> + case DVDVIDEO_VTS_MPEG_VERSION_MPEG2:
> + codec_id = AV_CODEC_ID_MPEG2VIDEO;
> + break;
> + }
> +
> + switch (video_attr.video_format) {
> + case DVDVIDEO_VTS_PICTURE_FORMAT_NTSC:
> + framerate = DVDVIDEO_NTSC_FRAMERATE_Q;
> + height = DVDVIDEO_NTSC_HEIGHT;
> + break;
> + case DVDVIDEO_VTS_PICTURE_FORMAT_PAL:
> + framerate = DVDVIDEO_PAL_FRAMERATE_Q;
> + height = DVDVIDEO_PAL_HEIGHT;
> + break;
> + }
> +
> + if (height > 0) {
> + switch (video_attr.picture_size) {
> + case DVDVIDEO_VTS_PICTURE_SIZE_D1:
> + width = DVDVIDEO_D1_WIDTH;
> + break;
> + case DVDVIDEO_VTS_PICTURE_SIZE_4CIF:
> + width = DVDVIDEO_4CIF_WIDTH;
> + break;
> + case DVDVIDEO_VTS_PICTURE_SIZE_D1_HALF:
> + width = DVDVIDEO_D1_HALF_WIDTH;
> + break;
> + case DVDVIDEO_VTS_PICTURE_SIZE_CIF:
> + width = DVDVIDEO_CIF_WIDTH;
> + height /= 2;
> + break;
> + }
> + }
> +
> + if (codec_id == AV_CODEC_ID_NONE || av_cmp_q(framerate, ZERO_Q) == 0
> + || width < 1 || height < 1) {
> + return AVERROR_INVALIDDATA;
> + }
> +
> + (*p_codec_id) = codec_id;
> + (*p_framerate) = framerate;
> + (*p_height) = height;
> + (*p_width) = width;
> +
> + return 0;
> +}
> +
> +static int dvdvideo_video_stream_to_avstream(AVFormatContext *s,
> + enum AVCodecID codec_id, AVRational framerate,
> + int height, int width,
> + enum AVStreamParseType need_parsing)
> +{
> + AVStream *st;
> + FFStream *sti;
> +
> + st = avformat_new_stream(s, NULL);
> + if (!st) {
> + return AVERROR(ENOMEM);
> + }
> +
> + st->id = DVDVIDEO_STARTCODE_VIDEO;
> + st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
> + st->codecpar->codec_id = codec_id;
> + st->codecpar->width = width;
> + st->codecpar->height = height;
> + st->codecpar->format = DVDVIDEO_PIXEL_FORMAT;
> +
> + st->codecpar->framerate = framerate;
> +#if FF_API_R_FRAME_RATE
> + st->r_frame_rate = framerate;
> +#endif
> + st->avg_frame_rate = framerate;
> +
> + avpriv_set_pts_info(st, DVDVIDEO_PTS_WRAP_BITS,
> + DVDVIDEO_TIME_BASE_Q.num, DVDVIDEO_TIME_BASE_Q.den);
> +
> + sti = ffstream(st);
> + sti->request_probe = 0;
> + sti->need_parsing = need_parsing;
> + sti->avctx->framerate = framerate;
> +
> + return 0;
> +}
> +
> +static int dvdvideo_video_stream_register(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + enum AVCodecID codec_id;
> + AVRational framerate;
> + int height;
> + int width;
> +
> + int ret;
> +
> + if ((ret =
> dvdvideo_video_stream_analyze(c->play_vts_ifo->vtsi_mat->vts_video_attr,
> &codec_id,
> + &framerate, &height, &width)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Invalid video parameters in VTS IFO\n");
> +
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_video_stream_to_avstream(c->mpeg_ctx, codec_id,
> + framerate, height, width, AVSTREAM_PARSE_FULL)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Unable to allocate video stream
> (subdemux)\n");
> +
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_video_stream_to_avstream(s, codec_id,
> + framerate, height, width, AVSTREAM_PARSE_NONE)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Unable to allocate video stream\n");
> +
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int dvdvideo_audio_stream_analyze(audio_attr_t audio_attr,
> + uint16_t audio_control, enum AVCodecID *p_codec_id, int
> *p_startcode,
> + int *p_sample_rate, int *p_sample_bits,
> + int *p_nb_channels, char **lang_iso639__2)
> +{
> + enum AVCodecID codec_id = AV_CODEC_ID_NONE;
> + int startcode = 0;
> + int sample_rate = 0;
> + int sample_bits = 0;
> + int nb_channels = 0;
> +
> + int position = (audio_control & 0x7F00) >> 8;
> +
> + switch (audio_attr.audio_format) {
> + case DVDVIDEO_VTS_AUDIO_FORMAT_AC3:
> + codec_id = AV_CODEC_ID_AC3;
> + sample_rate = 48000;
> + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_AC3 + position;
> + break;
> + case DVDVIDEO_VTS_AUDIO_FORMAT_MP1:
> + codec_id = AV_CODEC_ID_MP1;
> + sample_rate = 48000;
> + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP1 + position;
> + break;
> + case DVDVIDEO_VTS_AUDIO_FORMAT_MP2:
> + codec_id = AV_CODEC_ID_MP2;
> + sample_rate = 48000;
> + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP2 + position;
> + break;
> + case DVDVIDEO_VTS_AUDIO_FORMAT_PCM:
> + codec_id = AV_CODEC_ID_PCM_DVD;
> +
> + switch (audio_attr.sample_frequency) {
> + case DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_48K:
> + sample_rate = 48000;
> + break;
> + case DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_96K:
> + sample_rate = 96000;
> + break;
> + }
> +
> + switch (audio_attr.quantization) {
> + case DVDVIDEO_VTS_AUDIO_QUANTIZATION_16:
> + sample_bits = 16;
> + break;
> + case DVDVIDEO_VTS_AUDIO_QUANTIZATION_20:
> + sample_bits = 20;
> + break;
> + case DVDVIDEO_VTS_AUDIO_QUANTIZATION_24:
> + sample_bits = 24;
> + break;
> + }
> +
> + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_PCM + position;
> + break;
> + case DVDVIDEO_VTS_AUDIO_FORMAT_DTS:
> + codec_id = AV_CODEC_ID_DTS;
> + sample_rate = 48000;
> + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_DTS + position;
> + break;
> + }
> +
> + nb_channels = audio_attr.channels + 1;
> +
> + if (codec_id == AV_CODEC_ID_NONE || startcode == 0
> + || sample_rate == 0 || nb_channels == 0) {
> + return AVERROR_INVALIDDATA;
> + }
> +
> + (*p_codec_id) = codec_id;
> + (*p_startcode) = startcode;
> + (*p_sample_rate) = sample_rate;
> + (*p_sample_bits) = sample_bits;
> + (*p_nb_channels) = nb_channels;
> + (*lang_iso639__2) =
> dvdvideo_lang_code_to_iso639__2(audio_attr.lang_code);
> +
> + return 0;
> +}
> +
> +static int dvdvideo_audio_stream_to_avstream(AVFormatContext *s,
> + enum AVCodecID codec_id, int startcode, int sample_rate,
> + int sample_bits, int nb_channels, char *lang_iso639__2,
> + enum AVStreamParseType need_parsing)
> +{
> + AVStream *st;
> + FFStream *sti;
> +
> + st = avformat_new_stream(s, NULL);
> + if (!st) {
> + return AVERROR(ENOMEM);
> + }
> +
> + st->id = startcode;
> + st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
> + st->codecpar->codec_id = codec_id;
> + st->codecpar->sample_rate = sample_rate;
> + st->codecpar->ch_layout.nb_channels = nb_channels;
> +
> + if (sample_bits > 0) {
> + st->codecpar->format = sample_bits == 16 ?
> + AV_SAMPLE_FMT_S16
> + : AV_SAMPLE_FMT_S32;
> + st->codecpar->bits_per_coded_sample = sample_bits;
> + st->codecpar->bits_per_raw_sample = sample_bits;
> + }
> +
> + if (lang_iso639__2) {
> + av_dict_set(&st->metadata, "language", lang_iso639__2, 0);
> + }
> +
> + avpriv_set_pts_info(st, DVDVIDEO_PTS_WRAP_BITS,
> + DVDVIDEO_TIME_BASE_Q.num, DVDVIDEO_TIME_BASE_Q.den);
> +
> + sti = ffstream(st);
> + sti->request_probe = 0;
> + sti->need_parsing = need_parsing;
> +
> + return 0;
> +}
> +
> +static int dvdvideo_audio_stream_register_all(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + for (int i = 0; i <
> c->play_vts_ifo->vtsi_mat->nr_of_vts_audio_streams; i++) {
> + enum AVCodecID codec_id;
> + int startcode;
> + int sample_rate;
> + int sample_bits;
> + int nb_channels;
> + char *lang_iso639__2;
> + int ret;
> +
> + if (!(c->play_pgc->audio_control[i]
> + & DVDVIDEO_AUDIO_CONTROL_ENABLED_MASK)) {
> + continue;
> + }
> +
> + if ((ret = dvdvideo_audio_stream_analyze(
> + c->play_vts_ifo->vtsi_mat->vts_audio_attr[i],
> + c->play_pgc->audio_control[i], &codec_id, &startcode,
> + &sample_rate, &sample_bits, &nb_channels,
> + &lang_iso639__2)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Invalid audio parameters in VTS
> IFO\n");
> +
> + return ret;
> + }
> +
> + if (c->play_vts_ifo->vtsi_mat->
> + vts_audio_attr[i].application_mode == 1) {
> + av_log(s, AV_LOG_ERROR,
> + "Audio stream uses karaoke extension which is
> unsupported\n");
> +
> + return AVERROR_PATCHWELCOME;
> + }
> +
> + for (int j = 0; j < s->nb_streams; j++) {
> + if (s->streams[j]->id == startcode) {
> + av_log(s, AV_LOG_WARNING,
> + "Audio stream has duplicate entry in VTS IFO\n");
> +
> + continue;
> + }
> + }
> +
> + if ((ret = dvdvideo_audio_stream_to_avstream(c->mpeg_ctx,
> codec_id,
> + startcode, sample_rate, sample_bits, nb_channels,
> + lang_iso639__2, AVSTREAM_PARSE_FULL)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Unable to allocate video stream
> (subdemux)\n");
> +
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_audio_stream_to_avstream(s, codec_id,
> + startcode, sample_rate, sample_bits, nb_channels,
> + lang_iso639__2, AVSTREAM_PARSE_NONE)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Unable to allocate audio stream\n");
> +
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int dvdvideo_subtitle_clut_rgb_extradata_cat(
> + const uint32_t *clut_rgb, size_t clut_rgb_size,
> + char *dst, size_t dst_size)
> +{
> + if (dst_size < DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE) {
> + return AVERROR(ENOMEM);
> + }
> +
> + if (clut_rgb_size != DVDVIDEO_SUBP_CLUT_SIZE) {
> + return AVERROR(EINVAL);
> + }
> +
> + snprintf(dst, dst_size, "palette: ");
> +
> + for (int i = 0; i < DVDVIDEO_SUBP_CLUT_LEN; i++) {
> + av_strlcatf(dst, dst_size,
> + "%06"PRIx32"%s", clut_rgb[i], i != 15 ? ", " : "");
> + }
> +
> + av_strlcat(dst, "\n", 1);
> +
> + return 0;
> +}
> +
> +static int dvdvideo_subtitle_clut_yuv_to_rgb(uint32_t *clut,
> + const size_t clut_size)
> +{
> + const uint8_t *cm = dvdvideo_yuv_crop_tab + DVDVIDEO_YUV_NEG_CROP_MAX;
> +
> + int i, y, cb, cr;
> + uint8_t r, g, b;
> + int r_add, g_add, b_add;
> +
> + if (clut_size != DVDVIDEO_SUBP_CLUT_SIZE) {
> + return AVERROR(EINVAL);
> + }
> +
> + for (i = 0; i < DVDVIDEO_SUBP_CLUT_LEN; i++) {
> + y = (clut[i] >> 16) & 0xFF;
> + cr = (clut[i] >> 8) & 0xFF;
> + cb = clut[i] & 0xFF;
> +
> + YUV_TO_RGB1_CCIR(cb, cr);
> + YUV_TO_RGB2_CCIR(r, g, b, y);
> +
> + clut[i] = (r << 16) | (g << 8) | b;
> + }
> +
> + return 0;
> +}
> +
> +static int dvdvideo_subtitle_stream_to_avstream(AVFormatContext *s,
> + int startcode,
> + char *clut_rgb_extradata_buf, int clut_rgb_extradata_buf_size,
> + char *lang_iso639__2, enum AVStreamParseType need_parsing)
> +{
> + AVStream *st;
> + FFStream *sti;
> + int ret;
> +
> + st = avformat_new_stream(s, NULL);
> + if (!st) {
> + return AVERROR(ENOMEM);
> + }
> +
> + st->id = startcode;
> + st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
> + st->codecpar->codec_id = AV_CODEC_ID_DVD_SUBTITLE;
> +
> + if ((ret = ff_alloc_extradata(st->codecpar,
> + clut_rgb_extradata_buf_size)) != 0) {
> + return ret;
> + }
> +
> + memcpy(st->codecpar->extradata, clut_rgb_extradata_buf,
> + st->codecpar->extradata_size);
> +
> + if (lang_iso639__2) {
> + av_dict_set(&st->metadata, "language", lang_iso639__2, 0);
> + }
> +
> + avpriv_set_pts_info(st, DVDVIDEO_PTS_WRAP_BITS,
> + DVDVIDEO_TIME_BASE_Q.num, DVDVIDEO_TIME_BASE_Q.den);
> +
> + sti = ffstream(st);
> + sti->request_probe = 0;
> + sti->need_parsing = need_parsing;
> +
> + return 0;
> +}
> +
> +static int dvdvideo_subtitle_stream_register(AVFormatContext *s,
> + int position,
> + char *clut_rgb_extradata_buf, int clut_rgb_extradata_buf_size,
> + char *lang_iso639__2)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret;
> +
> + int startcode = DVDVIDEO_STARTCODE_OFFSET_SUBP + position;
> +
> + for (int i = 0; i < s->nb_streams; i++) {
> + /* don't need to warn the user about this, since params won't
> change */
> + if (s->streams[i]->id == startcode) {
> + av_log(s, AV_LOG_TRACE,
> + "Subtitle stream has duplicate entry in VTS IFO\n");
> + return 0;
> + }
> + }
> +
> + if ((ret = dvdvideo_subtitle_stream_to_avstream(c->mpeg_ctx,
> startcode,
> + clut_rgb_extradata_buf, clut_rgb_extradata_buf_size,
> + lang_iso639__2, AVSTREAM_PARSE_FULL)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Unable to allocate subtitle stream
> (subdemux)\n");
> +
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_subtitle_stream_to_avstream(s, startcode,
> + clut_rgb_extradata_buf, clut_rgb_extradata_buf_size,
> + lang_iso639__2, AVSTREAM_PARSE_NONE)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Unable to allocate subtitle stream\n");
> +
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int dvdvideo_subtitle_stream_register_all(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret;
> + uint32_t clut_rgb[DVDVIDEO_SUBP_CLUT_SIZE] = {0};
> + char clut_rgb_extradata_buf[DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE] =
> {0};
> +
> + if (c->play_vts_ifo->vtsi_mat->nr_of_vts_subp_streams < 1) {
> + return 0;
> + }
> +
> + /* initialize the palette (same for all streams in this PGC) */
> + memcpy(clut_rgb, c->play_pgc->palette, DVDVIDEO_SUBP_CLUT_SIZE);
> +
> + if ((ret = dvdvideo_subtitle_clut_yuv_to_rgb(
> + clut_rgb, DVDVIDEO_SUBP_CLUT_SIZE)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Unable to convert subtitle palette\n");
> +
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_subtitle_clut_rgb_extradata_cat(
> + clut_rgb, DVDVIDEO_SUBP_CLUT_SIZE,
> + clut_rgb_extradata_buf,
> DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Unable to set up subtitle extradata\n");
> +
> + return ret;
> + }
> +
> + for (int i = 0; i < c->play_vts_ifo->
> + vtsi_mat->nr_of_vts_subp_streams; i++) {
> + uint32_t subp_control;
> + subp_attr_t subp_attr;
> + video_attr_t video_attr;
> + char *lang_iso639__2;
> +
> + subp_control = c->play_pgc->subp_control[i];
> +
> + if (!(subp_control & DVDVIDEO_SUBP_CONTROL_ENABLED_MASK)) {
> + continue;
> + }
> +
> + subp_attr = c->play_vts_ifo->vtsi_mat->vts_subp_attr[i];
> + video_attr = c->play_vts_ifo->vtsi_mat->vts_video_attr;
> + lang_iso639__2 =
> dvdvideo_lang_code_to_iso639__2(subp_attr.lang_code);
> +
> + /* there can be several presentations for one SPU */
> + /* for now, be flexible with the DAR check due to weird authoring
> */
> + if (video_attr.display_aspect_ratio > 0) {
> + /* 16:9 */
> + ret = dvdvideo_subtitle_stream_register(s,
> + ((subp_control >> 16) & 0x1F),
> + clut_rgb_extradata_buf,
> + DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
> + lang_iso639__2);
> + if (ret != 0) {
> + return ret;
> + }
> +
> + if (video_attr.permitted_df ==
> DVDVIDEO_VTS_SPU_PFD_LETTERBOX_ONLY
> + || video_attr.permitted_df ==
> DVDVIDEO_VTS_SPU_PFD_ANY) {
> + ret = dvdvideo_subtitle_stream_register(s,
> + ((subp_control >> 8) & 0x1F),
> + clut_rgb_extradata_buf,
> + DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
> + lang_iso639__2);
> + if (ret != 0) {
> + return ret;
> + }
> + }
> +
> + if (video_attr.permitted_df ==
> DVDVIDEO_VTS_SPU_PFD_PANSCAN_ONLY
> + || video_attr.permitted_df ==
> DVDVIDEO_VTS_SPU_PFD_ANY) {
> + ret = dvdvideo_subtitle_stream_register(s,
> + (subp_control & 0x1F),
> + clut_rgb_extradata_buf,
> + DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
> + lang_iso639__2);
> + if (ret != 0) {
> + return ret;
> + }
> + }
> + } else {
> + /* 4:3 */
> + ret = dvdvideo_subtitle_stream_register(s,
> + ((subp_control >> 24) & 0x1F),
> + clut_rgb_extradata_buf,
> + DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
> + lang_iso639__2);
> + if (ret != 0) {
> + return ret;
> + }
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int dvdvideo_subdemux_nested_io_open(AVFormatContext *s,
> + AVIOContext **pb, const char *url, int flags, AVDictionary **opts)
> +{
> + av_log(s, AV_LOG_ERROR, "Nested io_open not supported for this
> format\n");
> +
> + return AVERROR(EPERM);
> +}
> +
> +static int dvdvideo_subdemux_read_data(void *opaque, uint8_t *buf,
> + int buf_size)
> +{
> + AVFormatContext *s = opaque;
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + 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_PS_MAX_SEARCH_BLOCKS; i++) {
> + int nav_event;
> + int nav_len;
> +
> + int check_title;
> + int check_pgcn;
> + int check_pgn;
> + int check_angle;
> + int check_nb_angles;
> +
> + uint8_t nav_buf[DVDVIDEO_BLOCK_SIZE] = {0};
> +
> + if (ff_check_interrupt(&s->interrupt_callback)) {
> + return AVERROR_EXIT;
> + }
> +
> + if (dvdnav_get_next_block(c->play_dvdnav,
> + nav_buf, &nav_event, &nav_len) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Error reading block\n");
> +
> + goto end_dvdnav_error;
> + }
> +
> + if (nav_len > DVDVIDEO_BLOCK_SIZE) {
> + av_log(s, AV_LOG_ERROR, "Invalid block (too big)\n");
> +
> + return AVERROR(ENOMEM);
> + }
> +
> + if (nav_event != DVDNAV_BLOCK_OK && nav_event !=
> DVDNAV_NAV_PACKET) {
> + av_log(s, AV_LOG_TRACE, "nav event=%d len=%d\n", nav_event,
> nav_len);
> + }
> +
> + if (dvdnav_current_title_program(c->play_dvdnav, &check_title,
> + &check_pgcn, &check_pgn) != DVDNAV_STATUS_OK) {
> + goto end_dvdnav_error;
> + }
> +
> + if (check_title != c->opt_title || check_pgcn != c->play_pgcn
> + || !dvdnav_is_domain_vts(c->play_dvdnav)) {
> + return AVERROR_EOF;
> + }
> +
> + if (dvdnav_get_angle_info(c->play_dvdnav, &check_angle,
> + &check_nb_angles) != DVDNAV_STATUS_OK) {
> + goto end_dvdnav_error;
> + }
> +
> + if (check_angle != c->opt_angle) {
> + av_log(s, AV_LOG_ERROR, "Unexpected angle change\n");
> +
> + return AVERROR_INPUT_CHANGED;
> + }
> +
> + switch (nav_event) {
> + case DVDNAV_BLOCK_OK:
> + if (nav_len != DVDVIDEO_BLOCK_SIZE) {
> + av_log(s, AV_LOG_ERROR, "Invalid block\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + memcpy(buf, &nav_buf, nav_len);
> +
> + return nav_len;
> + case DVDNAV_HIGHLIGHT:
> + case DVDNAV_VTS_CHANGE:
> + case DVDNAV_STILL_FRAME:
> + case DVDNAV_STOP:
> + case DVDNAV_WAIT:
> + return AVERROR_EOF;
> + default:
> + continue;
> + }
> + }
> +
> + av_log(s, AV_LOG_ERROR, "Unable to find next MPEG block\n");
> +
> + return AVERROR_UNKNOWN;
> +
> +end_dvdnav_error:
> + av_log(s, AV_LOG_ERROR, "libdvdnav: %s\n",
> dvdnav_err_to_string(c->play_dvdnav));
> +
> + return AVERROR_EXTERNAL;
> +}
> +
> +static int64_t dvdvideo_subdemux_seek_data(void *opaque, int64_t offset,
> + int whence)
> +{
> + AVFormatContext *s = opaque;
> +
> + av_log(s, AV_LOG_ERROR,
> + "dvdvideo_subdemux_seek_data(): not implemented\n");
> +
> + return AVERROR_PATCHWELCOME;
> +}
> +
> +static int dvdvideo_subdemux_open(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret;
> +
> + const AVInputFormat *mpeg_fmt;
> + AVFormatContext *mpeg_ctx;
> + uint8_t *mpeg_buf;
> +
> + 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,
> dvdvideo_subdemux_seek_data);
> + c->mpeg_pb.pub.seekable = 0;
> +
> + if ((ret = ff_copy_whiteblacklists(mpeg_ctx, s)) != 0) {
> + avformat_free_context(mpeg_ctx);
> +
> + return ret;
> + }
> +
> + /* TODO: fix the PTS issues (ask about this in ML) */
> + /* AVFMT_FLAG_GENPTS works in some scenarios, but in others, the
> reading process hangs */
> + mpeg_ctx->flags = AVFMT_FLAG_CUSTOM_IO | AVFMT_FLAG_GENPTS;
> +
> + 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 = dvdvideo_subdemux_nested_io_open;
> +
> + 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 void dvdvideo_subdemux_close(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + av_freep(&c->mpeg_pb.pub.buffer);
> + memset(&c->mpeg_pb, 0x00, sizeof(c->mpeg_pb));
> + av_freep(&c->mpeg_pb);
> +
> + avformat_close_input(&c->mpeg_ctx);
> +}
> +
> +static int dvdvideo_chapters_register(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> + int start_time = 0;
> + int cell = 0;
> +
> + for (int i = 0; i < c->play_pgc->nr_of_programs; i++) {
> + int64_t ms = 0;
> + int next = c->play_pgc->program_map[i + 1];
> +
> + if (i == c->play_pgc->nr_of_programs - 1) {
> + next = c->play_pgc->nr_of_cells + 1;
> + }
> +
> + while (cell < next - 1) {
> + /* only consider first cell of multi-angle cells */
> + if (c->play_pgc->cell_playback[cell].block_mode <= 1) {
> + ms = ms + dvdvideo_time_to_millis(
> + &c->play_pgc->cell_playback[cell].playback_time);
> + }
> + cell++;
> + }
> +
> + if (!avpriv_new_chapter(s, i, DVDVIDEO_TIME_BASE_MILLIS_Q,
> + start_time, start_time + ms, NULL)) {
> + av_log(s, AV_LOG_ERROR, "Unable to allocate chapter");
> + return AVERROR(ENOMEM);
> + }
> +
> + start_time += ms;
> + }
> +
> + return 0;
> +}
> +
> +static int dvdvideo_read_header(AVFormatContext *s)
> +{
> + int ret;
> +
> + if ((ret = dvdvideo_volume_open(s)) != 0) {
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_playback_open(s)) != 0) {
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_subdemux_open(s)) != 0) {
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_video_stream_register(s)) != 0) {
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_audio_stream_register_all(s)) != 0) {
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_subtitle_stream_register_all(s)) != 0) {
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_chapters_register(s)) != 0) {
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int dvdvideo_read_packet(AVFormatContext *s, AVPacket *pkt)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + while (!ff_check_interrupt(&s->interrupt_callback)) {
> + int subdemux_ret;
> + AVPacket *subdemux_pkt;
> +
> + subdemux_pkt = av_packet_alloc();
> + if (!subdemux_pkt) {
> + return AVERROR(ENOMEM);
> + }
> +
> + subdemux_ret = av_read_frame(c->mpeg_ctx, subdemux_pkt);
> + if (subdemux_ret >= 0) {
> + if (c->mpeg_ctx->nb_streams != s->nb_streams) {
> + av_log(s, AV_LOG_ERROR, "Unexpected stream during
> playback\n");
> +
> + av_packet_unref(subdemux_pkt);
> +
> + return AVERROR_INPUT_CHANGED;
> + }
> +
> + av_packet_move_ref(pkt, subdemux_pkt);
> +
> + return 0;
> + }
> +
> + return subdemux_ret;
> + }
> +
> + return AVERROR_EOF;
> +}
> +
> +static int dvdvideo_read_seek(AVFormatContext *s, int stream_index,
> + int64_t timestamp, int flags)
> +{
> + av_log(s, AV_LOG_ERROR, "dvdvideo_read_seek(): not implemented\n");
> +
> + return AVERROR_PATCHWELCOME;
> +}
> +
> +static int dvdvideo_close(AVFormatContext *s)
> +{
> + dvdvideo_subdemux_close(s);
> + dvdvideo_playback_close(s);
> + dvdvideo_volume_close(s);
> +
> + return 0;
> +}
> +
> +static int dvdvideo_probe(const AVProbeData *p)
> +{
> + av_log(NULL, AV_LOG_ERROR, "dvdvideo_probe(): not implemented\n");
> +
> + return AVERROR_PATCHWELCOME;
> +}
> +
> +#define OFFSET(x) offsetof(DVDVideoDemuxContext, x)
> +static const AVOption dvdvideo_options[] = {
> + {"title", "Title Number", OFFSET(opt_title),
> AV_OPT_TYPE_INT, { .i64=1 }, 1,
> DVDVIDEO_TITLE_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
> + {"ptt", "Entry PTT Number", OFFSET(opt_ptt),
> AV_OPT_TYPE_INT, { .i64=1 }, 1, DVDVIDEO_PTT_NUMBER_MAX,
> AV_OPT_FLAG_DECODING_PARAM },
> + {"pgc", "Entry PGC Number (0=auto)", OFFSET(opt_pgc),
> AV_OPT_TYPE_INT, { .i64=0 }, 0, DVDVIDEO_PGC_NUMBER_MAX,
> AV_OPT_FLAG_DECODING_PARAM },
> + {"pg", "Entry PG Number (0=auto)", OFFSET(opt_pg),
> AV_OPT_TYPE_INT, { .i64=0 }, 0, DVDVIDEO_PG_NUMBER_MAX,
> AV_OPT_FLAG_DECODING_PARAM },
> + {"angle", "Video Angle Number", OFFSET(opt_angle),
> AV_OPT_TYPE_INT, { .i64=1 }, 1,
> DVDVIDEO_ANGLE_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
> + {"region", "Playback Region Number (0=free)", OFFSET(opt_region),
> AV_OPT_TYPE_INT, { .i64=0 }, 0,
> DVDVIDEO_REGION_NUMBER_MAX, 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_NO_BYTE_SEEK |
> AVFMT_NOGENSEARCH | AVFMT_NOBINSEARCH | AVFMT_SHOW_IDS | AVFMT_TS_DISCONT,
> + .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
> +};
> --
> 2.34.1
>
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH] [WIP] [RFC] dvdvideo: initial contribution (DVD demuxer)
2023-12-09 10:06 [FFmpeg-devel] [PATCH] [WIP] [RFC] dvdvideo: initial contribution (DVD demuxer) Marth64
2023-12-10 2:27 ` Marth64
@ 2023-12-10 3:03 ` Leo Izen
2023-12-10 3:16 ` Marth64
2023-12-10 3:47 ` Marth64
2023-12-13 20:45 ` Nicolas George
2024-01-10 8:46 ` [FFmpeg-devel] [PATCH v2] dvdvideo: add DVD-Video demuxer, powered by libdvdnav and libdvdread Marth64
3 siblings, 2 replies; 27+ messages in thread
From: Leo Izen @ 2023-12-10 3:03 UTC (permalink / raw)
To: ffmpeg-devel
On 12/9/23 05:06, Marth64 wrote:
> Hello, I am happy to share a DVD demuxer for ffmpeg powered by libdvdread and libdvdnav.
> I have been working on this on/off throughout the year and think it is in a good spot
> to share at the ML now. This was a major learning experience for me in many ways and
> am open to any feedback on how I could improve it to be better. In fact, there is still
> one issue I can't seem to figure out how to solve (in discussion below). Regardless,
> a good number of DVDs I have tried seem to work out of the box. This is a full-service
> demuxer with chapters, subtitles (and their palettes), as well as language metadata.
>
> At a high level, the demuxer uses libdvdread for metadata of the disc, libdvdnav for
> the actual playback, and the MPEG-PS demuxer for the underlying VOB stream.
>
> First, the basic usage, is quite straightforward:
> ffmpeg -f dvdvideo -title NN -i DVD.iso|/dev/srX|/path/to/DVD ...
> Where NN can be replaced by your known title number in the disc
>
> As the demuxer effectively works at a PGC level, multi-PGC titles are not supported.
> But to provide this flexibility as there are many weirdly authored DVDs out there,
> one can specify an exact PGC via -pgc option and an associated program (PG, can start at 1).
>
> I am hoping and willing to improve this to be a robust demuxer wherever possible, but to
> that extent there is still an issue I can't figure out how to solve: Dealing with DTS discontinuities
> and PTS generation.
>
> Right now, as a band-aid, I am adding AVFMT_FLAG_GENPTS (line 1408)
> to the MPEG-PS subdemuxer. This works with most discs and the output seems OK. On discs with discontinuities, however,
> this causes the demuxing to hang which is obviously unacceptable also. Removing the flag causes the discs
> to not hang, but then the output for all discs becomes choppy for obvious reasons (invalid PTS).
>
> It could be because I have some misunderstandings on how things work within ffmpeg,
> but I have tried to the point now where I thought it best to ask the experts for help if you can spot
> what I am doing wrong. I am really motivated to make this work and good quality.
>
> Thank you!
>
> ---
> configure | 8 +
> libavformat/Makefile | 1 +
> libavformat/allformats.c | 1 +
> libavformat/avlanguage.c | 10 +-
> libavformat/dvdvideodec.c | 1599 +++++++++++++++++++++++++++++++++++++
> 5 files changed, 1617 insertions(+), 2 deletions(-)
> create mode 100644 libavformat/dvdvideodec.c
>
> diff --git a/configure b/configure
> index d77c053226..65e5968194 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]
Why do you need both of these? libdvdnav depends on libdvdread.
> --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]
> @@ -1802,6 +1804,8 @@ EXTERNAL_LIBRARY_GPL_LIST="
> frei0r
> libcdio
> libdavs2
> + libdvdnav
> + libdvdread
> librubberband
> libvidstab
> libx264
> @@ -3494,6 +3498,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"
> @@ -6723,6 +6729,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 && require_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 2db83aff81..45dba53044 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 c8bb4e3866..dc2acf575c 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..2b22a7de4f
> --- /dev/null
> +++ b/libavformat/dvdvideodec.c
> @@ -0,0 +1,1599 @@
> +/*
> + * DVD-Video demuxer (powered by libdvdnav/libdvdread)
> + *
> + * 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
> + * 7) Close resources
> + **/
> +
> +/**
> + * Issues/bug tracker (TODO):
> + * - SDDS is not supported yet
> + * - Are we handling backwards cell changes and PTT changes correctly?
> + * - Some codec parameters/metadata is not being explicitly set:
> + * -> ChannelLayout, color/chroma info, DAR, frame size for MP1/MP2 audio
> + * - Additional PGC validations?
> +**/
> +
> +#include <dvdread/dvd_reader.h>
> +#include <dvdread/ifo_read.h>
> +#include <dvdread/ifo_types.h>
> +#include <dvdread/nav_read.h>
> +#include <dvdnav/dvdnav.h>
> +
> +#include "libavutil/avstring.h"
> +#include "libavutil/avutil.h"
> +#include "libavutil/colorspace.h"
> +#include "libavutil/mem.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/samplefmt.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 ZERO_Q (AVRational) { 0, 1 }
Unnecessary #define.
> +
> +#define DVDVIDEO_PS_MAX_SEARCH_BLOCKS 128
> +#define DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE 1024
> +
> +#define DVDVIDEO_TIME_BASE_Q (AVRational) { 1, 90000 }
> +#define DVDVIDEO_TIME_BASE_MILLIS_Q (AVRational) { 1, 1000 }
> +#define DVDVIDEO_PTS_WRAP_BITS 32
> +#define DVDVIDEO_BLOCK_SIZE 2048
> +
> +#define DVDVIDEO_NTSC_FRAMERATE_Q (AVRational) { 30000, 1001 }
> +#define DVDVIDEO_NTSC_HEIGHT 480
> +#define DVDVIDEO_PAL_FRAMERATE_Q (AVRational) { 25, 1 }
> +#define DVDVIDEO_PAL_HEIGHT 576
> +#define DVDVIDEO_D1_WIDTH 720
> +#define DVDVIDEO_4CIF_WIDTH 704
> +#define DVDVIDEO_D1_HALF_WIDTH 352
> +#define DVDVIDEO_CIF_WIDTH 352
> +#define DVDVIDEO_PIXEL_FORMAT AV_PIX_FMT_YUV420P
> +
> +#define DVDVIDEO_STARTCODE_VIDEO 0x1E0
> +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_AC3 0x80
> +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP1 0x1C0
> +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP2 0x1C0
> +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_PCM 0xA0
> +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_DTS 0x88
> +#define DVDVIDEO_STARTCODE_OFFSET_SUBP 0x20
> +
> +#define DVDVIDEO_TITLE_NUMBER_MAX 99
> +#define DVDVIDEO_PTT_NUMBER_MAX 99
> +#define DVDVIDEO_PGC_NUMBER_MAX 32767
> +#define DVDVIDEO_PG_NUMBER_MAX 255
> +#define DVDVIDEO_ANGLE_NUMBER_MAX 9
> +#define DVDVIDEO_VTS_NUMBER_MAX 99
> +#define DVDVIDEO_TT_NUMBER_MAX 99
> +#define DVDVIDEO_REGION_NUMBER_MAX 8
> +#define DVDVIDEO_AUDIO_CONTROL_ENABLED_MASK 0x8000
> +#define DVDVIDEO_SUBP_CONTROL_ENABLED_MASK 0x80000000
> +#define DVDVIDEO_VTS_AUDIO_STREAMS_MAX 8
> +#define DVDVIDEO_VTS_SUBP_STREAMS_MAX 32
> +
> +/* ("palette: ") + ("rrggbb, "*15) + ("rrggbb") + \n + \0 */
> +#define DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE (9 + (8 * 15) + 6 + 1 + 1)
> +#define DVDVIDEO_SUBP_CLUT_LEN 16
> +#define DVDVIDEO_SUBP_CLUT_SIZE DVDVIDEO_SUBP_CLUT_LEN * sizeof(uint32_t)
> +
> +/* convert binary-coded decimal to decimal */
> +#define BCD2D(__x__) (((__x__ & 0xF0) >> 4) * 10 + (__x__ & 0x0F))
> +
> +/* crop table for YUV to RGB subpicture palette conversion */
> +#define DVDVIDEO_YUV_NEG_CROP_MAX 1024
> +#define times4(x) x, x, x, x
> +#define times256(x) times4(times4(times4(times4(times4(x)))))
> +
> +const uint8_t dvdvideo_yuv_crop_tab[256 + 2 * DVDVIDEO_YUV_NEG_CROP_MAX] = {
> +times256(0x00),
> +0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
> +0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
> +0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
> +0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
> +0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
> +0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,
> +0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
> +0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,
> +0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
> +0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
> +0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
> +0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
> +0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
> +0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
> +0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,
> +0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF,
> +times256(0xFF)
> +};
> +
> +enum DVDVideoVTSMPEGVersion {
> + DVDVIDEO_VTS_MPEG_VERSION_MPEG1 = 0,
> + DVDVIDEO_VTS_MPEG_VERSION_MPEG2 = 1
> +};
> +
> +enum DVDVideoVTSPictureFormat {
> + DVDVIDEO_VTS_PICTURE_FORMAT_NTSC = 0,
> + DVDVIDEO_VTS_PICTURE_FORMAT_PAL = 1
> +};
> +
> +enum DVDVideoVTSPictureSize {
> + DVDVIDEO_VTS_PICTURE_SIZE_D1 = 0,
> + DVDVIDEO_VTS_PICTURE_SIZE_4CIF = 1,
> + DVDVIDEO_VTS_PICTURE_SIZE_D1_HALF = 2,
> + DVDVIDEO_VTS_PICTURE_SIZE_CIF = 3
> +};
> +
> +enum DVDVideoVTSPictureDAR {
> + DVDVIDEO_VTS_PICTURE_DAR_4_3 = 0,
> + DVDVIDEO_VTS_PICTURE_DAR_16_9 = 3
> +};
> +
> +enum DVDVideoVTSPermittedFullscreenDisplay {
> + DVDVIDEO_VTS_SPU_PFD_ANY = 0,
> + DVDVIDEO_VTS_SPU_PFD_PANSCAN_ONLY = 1,
> + DVDVIDEO_VTS_SPU_PFD_LETTERBOX_ONLY = 2
> +};
> +
> +enum DVDVideoVTSAudioFormat {
> + DVDVIDEO_VTS_AUDIO_FORMAT_AC3 = 0,
> + DVDVIDEO_VTS_AUDIO_FORMAT_MP1 = 2,
> + DVDVIDEO_VTS_AUDIO_FORMAT_MP2 = 3,
> + DVDVIDEO_VTS_AUDIO_FORMAT_PCM = 4,
> + DVDVIDEO_VTS_AUDIO_FORMAT_SDDS = 5,
> + DVDVIDEO_VTS_AUDIO_FORMAT_DTS = 6
> +};
> +
> +enum FFDVDVideoVTSAudioSampleRate {
> + DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_48K = 0,
> + DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_96K = 1
> +};
> +
> +enum FFDVDVideoVTSAudioQuantization {
> + DVDVIDEO_VTS_AUDIO_QUANTIZATION_16 = 0,
> + DVDVIDEO_VTS_AUDIO_QUANTIZATION_20 = 1,
> + DVDVIDEO_VTS_AUDIO_QUANTIZATION_24 = 2
> +};
> +
> +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 identification 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 *vol_dvdread; /* handle to libdvdread */
> + ifo_handle_t *vol_vmg_ifo; /* handle to the VMG (VIDEO_TS.IFO) */
> +
> + /* playback */
> + ifo_handle_t *play_vts_ifo; /* handle to the active VTS (VTS_nn_n.IFO) */
> + pgc_t *play_pgc; /* handle to the active PGC */
> + int play_vtsn; /* number of the active VTS (video title set) */
> + int play_pgcn; /* number of the active PGC (program chain) */
> + dvdnav_t *play_dvdnav; /* handle to libdvdnav */
> +} DVDVideoDemuxContext;
> +
> +static void dvdvideo_libdvdread_log(void *opaque, dvd_logger_level_t level,
> + const char *msg, va_list msg_va)
> +{
This function is unnecessary. If you really need to intercept logged
messages from libdvdnav and feed them to av_log, you should just have a
lookup that maps the corresponding message level.
> + AVFormatContext *s = opaque;
> + int lavu_level;
> +
> + char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
> + vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
> +
> + switch (level) {
> + case DVD_LOGGER_LEVEL_ERROR:
> + lavu_level = AV_LOG_ERROR;
> + break;
> + case DVD_LOGGER_LEVEL_WARN:
> + lavu_level = AV_LOG_WARNING;
> + break;
> + case DVD_LOGGER_LEVEL_INFO:
> + lavu_level = AV_LOG_INFO;
> + break;
> + case DVD_LOGGER_LEVEL_DEBUG:
> + default:
> + lavu_level = AV_LOG_DEBUG;
> + }
> +
> + /* libdvdread messages don't have line terminators */
> + av_log(s, lavu_level, "libdvdread: %s\n", msg_buf);
> +}
> +
> +static void dvdvideo_libdvdnav_log(void *opaque, dvdnav_logger_level_t level,
> + const char *msg, va_list msg_va)
> +{
> + AVFormatContext *s = opaque;
Same complaint here. Also you have no to reassign opaque here.
> + int lavu_level;
> +
> + char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
> + vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
> +
> + switch (level) {
> + case DVDNAV_LOGGER_LEVEL_ERROR:
> + lavu_level = AV_LOG_ERROR;
> + break;
> + case DVDNAV_LOGGER_LEVEL_WARN:
> + lavu_level = AV_LOG_WARNING;
> + break;
> + case DVDNAV_LOGGER_LEVEL_INFO:
> + lavu_level = AV_LOG_INFO;
> + break;
> + case DVDNAV_LOGGER_LEVEL_DEBUG:
> + default:
> + lavu_level = AV_LOG_DEBUG;
> + }
> +
> + /* libdvdnav messages don't have line terminators */
> + av_log(s, lavu_level, "libdvdnav: %s\n", msg_buf);
> +}
> +
> +static char *dvdvideo_lang_code_to_iso639__2(const uint16_t lang_code)
> +{
> + char lang_code_str[3] = {0};
> +
> + if (lang_code && lang_code != 0xFFFF) {
> + lang_code_str[0] = (lang_code >> 8) & 0xFF;
> + lang_code_str[1] = lang_code & 0xFF;
> + }
AV_WB16(lang_code_str, lang_code);
> +
> + return (char *) ff_convert_lang_to(lang_code_str, AV_LANG_ISO639_2_BIBL);
> +}
> +
> +static int64_t dvdvideo_time_to_millis(dvd_time_t *time)
> +{
> + double fps;
> + int64_t ms;
> +
> + int64_t sec = (int64_t) (BCD2D(time->hour)) * 60 * 60;
> + sec += (int64_t) (BCD2D(time->minute)) * 60;
> + sec += (int64_t) (BCD2D(time->second));
> +
> + /* the 2 high bits are the frame rate */
> + switch ((time->frame_u & 0xC0) >> 6)
> + {
> + case 1:
> + fps = 25.0;
> + break;
> + case 3:
> + fps = 29.97;
This is wrong, it's 30000/1001. Which you knew, because you #defined it
above. Why not use those?
> + break;
> + default:
> + fps = 2500.0;
> + break;
> + }
> + ms = BCD2D(time->frame_u & 0x3F) * 1000.0 / fps;
> +
> + return (sec * 1000) + ms;
> +}
> +
> +static int dvdvideo_volume_is_title_valid_in_context(AVFormatContext *s,
> + int title)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + return title >= 1
> + && title <= DVDVIDEO_TITLE_NUMBER_MAX
> + && title <= c->vol_vmg_ifo->tt_srpt->nr_of_srpts
> + && c->vol_vmg_ifo->tt_srpt->title[title - 1].nr_of_ptts > 0;
> +}
> +
> +static int dvdvideo_volume_is_title_valid_in_vts(title_info_t title_info,
> + ifo_handle_t *vts_ifo)
> +{
> + return title_info.vts_ttn >= 1
> + && title_info.vts_ttn <= DVDVIDEO_TT_NUMBER_MAX
> + && title_info.vts_ttn <= vts_ifo->vts_ptt_srpt->nr_of_srpts
> + && vts_ifo->vtsi_mat->nr_of_vts_audio_streams
> + <= DVDVIDEO_VTS_AUDIO_STREAMS_MAX
> + && vts_ifo->vtsi_mat->nr_of_vts_subp_streams
> + <= DVDVIDEO_VTS_SUBP_STREAMS_MAX;
> +}
> +
> +static int dvdvideo_volume_is_angle_valid_in_title(int angle,
> + title_info_t title_info)
> +{
> + return angle >= 1
> + && angle <= DVDVIDEO_ANGLE_NUMBER_MAX
> + && angle <= title_info.nr_of_angles;
> +}
> +
> +static int dvdvideo_volume_is_pgc_valid_and_sequential(pgc_t *pgc)
> +{
> + return pgc
> + && pgc->program_map
> + && pgc->cell_playback != NULL
> + && pgc->pg_playback_mode == 0
> + && pgc->nr_of_programs > 0
> + && pgc->nr_of_cells > 0;
> +}
> +
> +static int dvdvideo_volume_is_pgcn_in_vts(int pgcn, ifo_handle_t *vts_ifo)
> +{
> + return pgcn >= 1
> + && pgcn <= DVDVIDEO_PGC_NUMBER_MAX
> + && pgcn <= vts_ifo->vts_pgcit->nr_of_pgci_srp;
> +}
> +
> +static int dvdvideo_volume_is_pgn_in_pgc(int pgn, pgc_t *pgc)
> +{
> + return pgn >= 1
> + && pgn <= DVDVIDEO_PG_NUMBER_MAX
> + && pgn <= pgc->nr_of_programs;
> +}
> +
> +static int dvdvideo_volume_open(AVFormatContext *s)
av_cold
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + dvd_reader_t *dvdread;
> + ifo_handle_t *vmg_ifo;
> +
> + dvd_logger_cb dvdread_log_cb = { .pf_log = dvdvideo_libdvdread_log };
> + dvdread = DVDOpen2(s, &dvdread_log_cb, s->url);
> +
> + if (!dvdread) {
> + av_log(s, AV_LOG_ERROR, "Unable to initialize libdvdread\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + if (!(vmg_ifo = ifoOpen(dvdread, 0))) {
> + DVDClose(dvdread);
> +
> + av_log(s, AV_LOG_ERROR,
> + "Unable to open VIDEO_TS.IFO. "
> + "Input does not have a valid DVD-Video structure "
> + "or is not a compliant UDF DVD-Video image.\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + c->vol_dvdread = dvdread;
> + c->vol_vmg_ifo = vmg_ifo;
> +
> + return 0;
> +}
> +
> +static void dvdvideo_volume_close(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + if (c->vol_vmg_ifo) {
> + ifoClose(c->vol_vmg_ifo);
Can this fail?
> + c->vol_vmg_ifo = NULL;
> + }
> +
> + if (c->vol_dvdread) {
> + DVDClose(c->vol_dvdread);
Can this fail?
> + c->vol_dvdread = NULL;
> + }
> +}
> +
> +static int dvdvideo_volume_open_vts_ifo(AVFormatContext *s, int vtsn,
> + ifo_handle_t **p_vts_ifo)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + ifo_handle_t *vts_ifo;
> +
> + if (vtsn < 1
> + || vtsn > DVDVIDEO_VTS_NUMBER_MAX
> + || !(vts_ifo = ifoOpen(c->vol_dvdread, vtsn))) {
> + return AVERROR_INVALIDDATA;
> + }
> +
> + for (int i = 0; i < vts_ifo->vts_c_adt->nr_of_vobs; i++) {
> + int start_sector = vts_ifo->vts_c_adt->cell_adr_table[i].start_sector;
> + int end_sector = vts_ifo->vts_c_adt->cell_adr_table[i].last_sector;
> +
> + if ((start_sector & 0xFFFFFF) == 0xFFFFFF
> + || (end_sector & 0xFFFFFF) == 0xFFFFFF
> + || start_sector >= end_sector) {
> + ifoClose(vts_ifo);
> +
> + av_log(s, AV_LOG_WARNING, "VTS has invalid cell address table\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> + }
> +
> + (*p_vts_ifo) = vts_ifo;
> +
> + return 0;
> +}
> +
> +static int dvdvideo_playback_open(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret;
> +
> + ifo_handle_t *vts_ifo;
> + dvdnav_logger_cb dvdnav_log_cb;
> + dvdnav_status_t dvdnav_status;
> + dvdnav_t *dvdnav;
> +
> + title_info_t title_info;
> + int pgcn;
> + int pgn;
> + pgc_t *pgc;
> +
> + int32_t disc_region_mask;
> + int32_t player_region_mask;
> + int cell_search_has_vts = 0;
> +
> + /* if the title is valid, open its VTS IFO */
> + if (!dvdvideo_volume_is_title_valid_in_context(s, c->opt_title)) {
> + av_log(s, AV_LOG_ERROR, "Title not found or invalid\n");
> +
> + return AVERROR_STREAM_NOT_FOUND;
> + }
> +
> + title_info = c->vol_vmg_ifo->tt_srpt->title[c->opt_title - 1];
> +
> + if ((ret = dvdvideo_volume_open_vts_ifo(s,
> + title_info.title_set_nr, &vts_ifo)) != 0) {
Our convention is to return negative values on error in FFmpeg. Not
nonzero values.
> + av_log(s, AV_LOG_ERROR, "Title VTS could not be opened\n");
> +
> + return ret;
> + }
> +
> + if (!dvdvideo_volume_is_title_valid_in_vts(title_info, vts_ifo)) {
> + av_log(s, AV_LOG_ERROR, "Title VTS is invalid\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + if (!dvdvideo_volume_is_angle_valid_in_title(c->opt_angle, title_info)) {
> + av_log(s, AV_LOG_ERROR, "Angle not found or invalid\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + /* determine our PGC and PG (playback coordinates) */
> + if (c->opt_pgc > 0 && c->opt_pg > 0) {
> + if (c->opt_ptt > 0) {
> + av_log(s, AV_LOG_WARNING,
> + "PTT option ignored as PGC and PG are provided\n");
> + }
> +
> + pgcn = c->opt_pgc;
> + pgn = c->opt_pg;
> + } else {
> + if (c->opt_ptt > title_info.nr_of_ptts) {
> + av_log(s, AV_LOG_ERROR, "PTT not found\n");
> +
> + return AVERROR_STREAM_NOT_FOUND;
> + }
> +
> + pgcn = vts_ifo->vts_ptt_srpt->title[title_info.vts_ttn - 1].ptt[c->opt_ptt - 1].pgcn;
> + pgn = vts_ifo->vts_ptt_srpt->title[title_info.vts_ttn - 1].ptt[c->opt_ptt - 1].pgn;
> + }
> +
> + if (!dvdvideo_volume_is_pgcn_in_vts(pgcn, vts_ifo)) {
> + av_log(s, AV_LOG_ERROR, "PGC not found\n");
> +
> + return AVERROR_STREAM_NOT_FOUND;
> + }
> +
> + pgc = vts_ifo->vts_pgcit->pgci_srp[pgcn - 1].pgc;
> +
> + if (!dvdvideo_volume_is_pgc_valid_and_sequential(pgc)) {
> + av_log(s, AV_LOG_ERROR, "PGC not valid\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + if (!dvdvideo_volume_is_pgn_in_pgc(pgn, pgc)) {
> + av_log(s, AV_LOG_ERROR, "PG not found\n");
> +
> + return AVERROR_STREAM_NOT_FOUND;
> + }
> +
> + /* set up libdvdnav */
> + dvdnav_log_cb = (dvdnav_logger_cb) { .pf_log = dvdvideo_libdvdnav_log };
> + dvdnav_status = dvdnav_open2(&dvdnav, NULL, &dvdnav_log_cb, s->url);
> +
> + if (!dvdnav) {
> + av_log(s, AV_LOG_ERROR, "Unable to initialize libdvdnav\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + if (dvdnav_status != DVDNAV_STATUS_OK) {
Code style. We don't use braces with single-line blocks.
> + goto end_search_error_dvdnav;
> + }
> +
> + if (dvdnav_set_readahead_flag(dvdnav, 0) != DVDNAV_STATUS_OK) {
> + goto end_search_error_dvdnav;
> + }
> +
> + if (dvdnav_set_PGC_positioning_flag(dvdnav, 1) != DVDNAV_STATUS_OK) {
> + goto end_search_error_dvdnav;
> + }
> +
> + if (dvdnav_get_region_mask(dvdnav, &disc_region_mask) != DVDNAV_STATUS_OK) {
> + goto end_search_error_dvdnav;
> + }
> +
> + if (c->opt_region > 0) {
> + player_region_mask = (1 << (c->opt_region - 1));
> + } else {
> + player_region_mask = disc_region_mask;
> + }
> +
> + if (dvdnav_set_region_mask(dvdnav, player_region_mask) != DVDNAV_STATUS_OK) {
> + goto end_search_error_dvdnav;
> + }
> +
> + if (dvdnav_program_play(dvdnav, c->opt_title, pgcn, pgn) != DVDNAV_STATUS_OK) {
> + goto end_search_error_dvdnav;
> + }
> +
> + if (dvdnav_angle_change(dvdnav, c->opt_angle) != DVDNAV_STATUS_OK) {
> + goto end_search_error_dvdnav;
> + }
> +
> + /* lock on to title's VTS and chosen PGC/PGN coordinates */
> + for (int i = 0; i < DVDVIDEO_PS_MAX_SEARCH_BLOCKS; i++) {
> + int nav_event;
> + int nav_len;
> + dvdnav_vts_change_event_t *vts_event;
> + uint8_t nav_buf[DVDVIDEO_BLOCK_SIZE] = {0};
> +
> + if (ff_check_interrupt(&s->interrupt_callback)) {
> + return AVERROR_EXIT;
> + }
> +
> + if (dvdnav_get_next_block(dvdnav, nav_buf, &nav_event, &nav_len)
> + != DVDNAV_STATUS_OK) {
> + goto end_search_error_dvdnav;
> + }
> +
> + if (nav_len > DVDVIDEO_BLOCK_SIZE) {
> + av_log(s, AV_LOG_ERROR, "Invalid block (too big)\n");
> +
> + ret = AVERROR(ENOMEM);
> +
> + goto end_search_error;
> + }
> +
> + av_log(s, AV_LOG_TRACE, "nav event=%d len=%d\n", nav_event, nav_len);
> +
> + switch (nav_event) {
> + case DVDNAV_VTS_CHANGE:
> + vts_event = (dvdnav_vts_change_event_t *) nav_buf;
> +
> + if (cell_search_has_vts) {
> + if (vts_event->new_vtsN != title_info.title_set_nr
> + || vts_event->new_domain != DVD_DOMAIN_VTSTitle) {
> + av_log(s, AV_LOG_ERROR, "Unexpected VTS change\n");
> +
> + ret = AVERROR_INPUT_CHANGED;
> +
> + goto end_search_error;
> + }
> + continue;
> + }
> +
> + if (vts_event->new_vtsN == title_info.title_set_nr
> + && vts_event->new_domain == DVD_DOMAIN_VTSTitle) {
> + cell_search_has_vts = 1;
> + }
> +
> + continue;
> + case DVDNAV_CELL_CHANGE:
> + // if we need more info about the cell:
> + // dvdnav_cell_change_event_t *event_data =
> + // (dvdnav_cell_change_event_t *) nav_buf;
> + int check_title;
> + int check_pgcn;
> + int check_pgn;
> +
> + if (!cell_search_has_vts) {
> + continue;
> + }
> +
> + dvdnav_current_title_program(dvdnav, &check_title,
> + &check_pgcn, &check_pgn);
> +
> + if (check_title == c->opt_title && check_pgcn == pgcn
> + && check_pgn == pgn && dvdnav_is_domain_vts(dvdnav)) {
> + goto end_ready;
> + }
> +
> + continue;
> + case DVDNAV_STILL_FRAME:
> + dvdnav_still_skip(dvdnav);
> +
> + continue;
> + case DVDNAV_WAIT:
> + dvdnav_wait_skip(dvdnav);
> +
> + continue;
> + case DVDNAV_STOP:
> + ret = AVERROR_INPUT_CHANGED;
> +
> + goto end_search_error;
> + default:
> + continue;
> + }
> + }
> +
> +end_search_error_dvdnav:
> + ret = AVERROR_EXTERNAL;
> + av_log(s, AV_LOG_ERROR, "libdvdnav: %s\n", dvdnav_err_to_string(dvdnav));
> + av_log(s, AV_LOG_ERROR, "Error starting playback\n");
> +
> +end_search_error:
> + dvdnav_close(dvdnav);
> + ifoClose(vts_ifo);
> +
> + return ret;
> +
> +end_ready:
> + /* update the context */
> + c->play_vts_ifo = vts_ifo;
> + c->play_pgc = pgc;
> + c->play_vtsn = title_info.title_set_nr;
> + c->play_pgcn = pgcn;
> + c->play_dvdnav = dvdnav;
> +
> + return 0;
> +}
> +
> +static void dvdvideo_playback_close(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + if (c->play_dvdnav) {
> + dvdnav_close(c->play_dvdnav);
> + c->play_dvdnav = NULL;
> + }
> +
> + if (c->play_vts_ifo) {
> + ifoClose(c->play_vts_ifo);
> + c->play_vts_ifo = NULL;
> + }
> +}
> +
> +static int dvdvideo_video_stream_analyze(video_attr_t video_attr,
> + enum AVCodecID *p_codec_id, AVRational *p_framerate,
> + int *p_height, int *p_width)
> +{
> + enum AVCodecID codec_id = AV_CODEC_ID_NONE;
> + AVRational framerate = ZERO_Q;
> + int height = 0;
> + int width = 0;
> +
> + switch (video_attr.mpeg_version) {
> + case DVDVIDEO_VTS_MPEG_VERSION_MPEG1:
> + codec_id = AV_CODEC_ID_MPEG1VIDEO;
> + break;
> + case DVDVIDEO_VTS_MPEG_VERSION_MPEG2:
> + codec_id = AV_CODEC_ID_MPEG2VIDEO;
> + break;
> + }
> +
> + switch (video_attr.video_format) {
> + case DVDVIDEO_VTS_PICTURE_FORMAT_NTSC:
> + framerate = DVDVIDEO_NTSC_FRAMERATE_Q;
> + height = DVDVIDEO_NTSC_HEIGHT;
> + break;
> + case DVDVIDEO_VTS_PICTURE_FORMAT_PAL:
> + framerate = DVDVIDEO_PAL_FRAMERATE_Q;
> + height = DVDVIDEO_PAL_HEIGHT;
> + break;
> + }
> +
> + if (height > 0) {
> + switch (video_attr.picture_size) {
> + case DVDVIDEO_VTS_PICTURE_SIZE_D1:
> + width = DVDVIDEO_D1_WIDTH;
> + break;
> + case DVDVIDEO_VTS_PICTURE_SIZE_4CIF:
> + width = DVDVIDEO_4CIF_WIDTH;
> + break;
> + case DVDVIDEO_VTS_PICTURE_SIZE_D1_HALF:
> + width = DVDVIDEO_D1_HALF_WIDTH;
> + break;
> + case DVDVIDEO_VTS_PICTURE_SIZE_CIF:
> + width = DVDVIDEO_CIF_WIDTH;
> + height /= 2;
> + break;
> + }
> + }
> +
> + if (codec_id == AV_CODEC_ID_NONE || av_cmp_q(framerate, ZERO_Q) == 0
av_cmp_q for comparing a rational to zero is unnecessary. Just check if
the numerator is nonzero, as a fraction is zero if and only if its
numerator is zero.
> + || width < 1 || height < 1) {
> + return AVERROR_INVALIDDATA;
> + }
> +
> + (*p_codec_id) = codec_id;
> + (*p_framerate) = framerate;
> + (*p_height) = height;
> + (*p_width) = width;
> +
> + return 0;
> +}
> +
> +static int dvdvideo_video_stream_to_avstream(AVFormatContext *s,
> + enum AVCodecID codec_id, AVRational framerate,
> + int height, int width,
> + enum AVStreamParseType need_parsing)
> +{
> + AVStream *st;
> + FFStream *sti;
> +
> + st = avformat_new_stream(s, NULL);
> + if (!st) {
> + return AVERROR(ENOMEM);
> + }
> +
> + st->id = DVDVIDEO_STARTCODE_VIDEO;
> + st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
> + st->codecpar->codec_id = codec_id;
> + st->codecpar->width = width;
> + st->codecpar->height = height;
> + st->codecpar->format = DVDVIDEO_PIXEL_FORMAT;
> +
> + st->codecpar->framerate = framerate;
> +#if FF_API_R_FRAME_RATE
> + st->r_frame_rate = framerate;
> +#endif
> + st->avg_frame_rate = framerate;
> +
> + avpriv_set_pts_info(st, DVDVIDEO_PTS_WRAP_BITS,
> + DVDVIDEO_TIME_BASE_Q.num, DVDVIDEO_TIME_BASE_Q.den);
> +
> + sti = ffstream(st);
> + sti->request_probe = 0;
> + sti->need_parsing = need_parsing;
> + sti->avctx->framerate = framerate;
> +
> + return 0;
> +}
> +
> +static int dvdvideo_video_stream_register(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + enum AVCodecID codec_id;
> + AVRational framerate;
> + int height;
> + int width;
> +
> + int ret;
> +
> + if ((ret = dvdvideo_video_stream_analyze(c->play_vts_ifo->vtsi_mat->vts_video_attr, &codec_id,
> + &framerate, &height, &width)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Invalid video parameters in VTS IFO\n");
> +
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_video_stream_to_avstream(c->mpeg_ctx, codec_id,
> + framerate, height, width, AVSTREAM_PARSE_FULL)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Unable to allocate video stream (subdemux)\n");
> +
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_video_stream_to_avstream(s, codec_id,
> + framerate, height, width, AVSTREAM_PARSE_NONE)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Unable to allocate video stream\n");
> +
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int dvdvideo_audio_stream_analyze(audio_attr_t audio_attr,
> + uint16_t audio_control, enum AVCodecID *p_codec_id, int *p_startcode,
> + int *p_sample_rate, int *p_sample_bits,
> + int *p_nb_channels, char **lang_iso639__2)
> +{
> + enum AVCodecID codec_id = AV_CODEC_ID_NONE;
> + int startcode = 0;
> + int sample_rate = 0;
> + int sample_bits = 0;
> + int nb_channels = 0;
> +
> + int position = (audio_control & 0x7F00) >> 8;
> +
> + switch (audio_attr.audio_format) {
> + case DVDVIDEO_VTS_AUDIO_FORMAT_AC3:
> + codec_id = AV_CODEC_ID_AC3;
> + sample_rate = 48000;
> + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_AC3 + position;
> + break;
> + case DVDVIDEO_VTS_AUDIO_FORMAT_MP1:
> + codec_id = AV_CODEC_ID_MP1;
> + sample_rate = 48000;
> + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP1 + position;
> + break;
> + case DVDVIDEO_VTS_AUDIO_FORMAT_MP2:
> + codec_id = AV_CODEC_ID_MP2;
> + sample_rate = 48000;
> + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP2 + position;
> + break;
> + case DVDVIDEO_VTS_AUDIO_FORMAT_PCM:
> + codec_id = AV_CODEC_ID_PCM_DVD;
> +
> + switch (audio_attr.sample_frequency) {
> + case DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_48K:
> + sample_rate = 48000;
> + break;
> + case DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_96K:
> + sample_rate = 96000;
> + break;
> + }
> +
> + switch (audio_attr.quantization) {
> + case DVDVIDEO_VTS_AUDIO_QUANTIZATION_16:
> + sample_bits = 16;
> + break;
> + case DVDVIDEO_VTS_AUDIO_QUANTIZATION_20:
> + sample_bits = 20;
> + break;
> + case DVDVIDEO_VTS_AUDIO_QUANTIZATION_24:
> + sample_bits = 24;
> + break;
> + }
> +
> + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_PCM + position;
> + break;
> + case DVDVIDEO_VTS_AUDIO_FORMAT_DTS:
> + codec_id = AV_CODEC_ID_DTS;
> + sample_rate = 48000;
> + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_DTS + position;
> + break;
> + }
> +
> + nb_channels = audio_attr.channels + 1;
> +
> + if (codec_id == AV_CODEC_ID_NONE || startcode == 0
> + || sample_rate == 0 || nb_channels == 0) {
> + return AVERROR_INVALIDDATA;
> + }
> +
> + (*p_codec_id) = codec_id;
> + (*p_startcode) = startcode;
> + (*p_sample_rate) = sample_rate;
> + (*p_sample_bits) = sample_bits;
> + (*p_nb_channels) = nb_channels;
> + (*lang_iso639__2) = dvdvideo_lang_code_to_iso639__2(audio_attr.lang_code);
> +
> + return 0;
> +}
> +
> +static int dvdvideo_audio_stream_to_avstream(AVFormatContext *s,
> + enum AVCodecID codec_id, int startcode, int sample_rate,
> + int sample_bits, int nb_channels, char *lang_iso639__2,
> + enum AVStreamParseType need_parsing)
> +{
> + AVStream *st;
> + FFStream *sti;
> +
> + st = avformat_new_stream(s, NULL);
> + if (!st) {
> + return AVERROR(ENOMEM);
> + }
> +
> + st->id = startcode;
> + st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
> + st->codecpar->codec_id = codec_id;
> + st->codecpar->sample_rate = sample_rate;
> + st->codecpar->ch_layout.nb_channels = nb_channels;
> +
> + if (sample_bits > 0) {
> + st->codecpar->format = sample_bits == 16 ?
> + AV_SAMPLE_FMT_S16
> + : AV_SAMPLE_FMT_S32;
> + st->codecpar->bits_per_coded_sample = sample_bits;
> + st->codecpar->bits_per_raw_sample = sample_bits;
> + }
> +
> + if (lang_iso639__2) {
> + av_dict_set(&st->metadata, "language", lang_iso639__2, 0);
> + }
> +
> + avpriv_set_pts_info(st, DVDVIDEO_PTS_WRAP_BITS,
> + DVDVIDEO_TIME_BASE_Q.num, DVDVIDEO_TIME_BASE_Q.den);
> +
> + sti = ffstream(st);
> + sti->request_probe = 0;
> + sti->need_parsing = need_parsing;
> +
> + return 0;
> +}
> +
> +static int dvdvideo_audio_stream_register_all(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + for (int i = 0; i < c->play_vts_ifo->vtsi_mat->nr_of_vts_audio_streams; i++) {
> + enum AVCodecID codec_id;
> + int startcode;
> + int sample_rate;
> + int sample_bits;
> + int nb_channels;
> + char *lang_iso639__2;
> + int ret;
> +
> + if (!(c->play_pgc->audio_control[i]
> + & DVDVIDEO_AUDIO_CONTROL_ENABLED_MASK)) {
> + continue;
> + }
> +
> + if ((ret = dvdvideo_audio_stream_analyze(
> + c->play_vts_ifo->vtsi_mat->vts_audio_attr[i],
> + c->play_pgc->audio_control[i], &codec_id, &startcode,
> + &sample_rate, &sample_bits, &nb_channels,
> + &lang_iso639__2)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Invalid audio parameters in VTS IFO\n");
> +
> + return ret;
> + }
> +
> + if (c->play_vts_ifo->vtsi_mat->
> + vts_audio_attr[i].application_mode == 1) {
> + av_log(s, AV_LOG_ERROR,
> + "Audio stream uses karaoke extension which is unsupported\n");
> +
> + return AVERROR_PATCHWELCOME;
> + }
> +
> + for (int j = 0; j < s->nb_streams; j++) {
> + if (s->streams[j]->id == startcode) {
> + av_log(s, AV_LOG_WARNING,
> + "Audio stream has duplicate entry in VTS IFO\n");
> +
> + continue;
> + }
> + }
> +
> + if ((ret = dvdvideo_audio_stream_to_avstream(c->mpeg_ctx, codec_id,
> + startcode, sample_rate, sample_bits, nb_channels,
> + lang_iso639__2, AVSTREAM_PARSE_FULL)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Unable to allocate video stream (subdemux)\n");
> +
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_audio_stream_to_avstream(s, codec_id,
> + startcode, sample_rate, sample_bits, nb_channels,
> + lang_iso639__2, AVSTREAM_PARSE_NONE)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Unable to allocate audio stream\n");
> +
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int dvdvideo_subtitle_clut_rgb_extradata_cat(
> + const uint32_t *clut_rgb, size_t clut_rgb_size,
> + char *dst, size_t dst_size)
> +{
> + if (dst_size < DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE) {
> + return AVERROR(ENOMEM);This is the error code for an allocation failure, which is not what is
happening here. It's an illegal argument passed to the function.
> + }
> +
> + if (clut_rgb_size != DVDVIDEO_SUBP_CLUT_SIZE) {
> + return AVERROR(EINVAL);
> + }
> +
> + snprintf(dst, dst_size, "palette: ");
> +
> + for (int i = 0; i < DVDVIDEO_SUBP_CLUT_LEN; i++) {
> + av_strlcatf(dst, dst_size,
> + "%06"PRIx32"%s", clut_rgb[i], i != 15 ? ", " : "");
> + }
> +
> + av_strlcat(dst, "\n", 1);
> +
You should be using the AVBPrint api here to construct a string, not
av_strlcat.
> + return 0;
> +}
> +
> +static int dvdvideo_subtitle_clut_yuv_to_rgb(uint32_t *clut,
> + const size_t clut_size)
What is a yuv-to-rgb function doing inside a dvd-video demuxer?
> +{
> + const uint8_t *cm = dvdvideo_yuv_crop_tab + DVDVIDEO_YUV_NEG_CROP_MAX;
> +
> + int i, y, cb, cr;
> + uint8_t r, g, b;
> + int r_add, g_add, b_add;
> +
> + if (clut_size != DVDVIDEO_SUBP_CLUT_SIZE) {Why even have an argument to the function if it must equal some exact
integer? At that point just remove clut_size from the function argument
and just use DVDVIDEO_SUBP_CLUT_SIZE in its place.
> + return AVERROR(EINVAL);
> + }
> +
> + for (i = 0; i < DVDVIDEO_SUBP_CLUT_LEN; i++) {
> + y = (clut[i] >> 16) & 0xFF;
> + cr = (clut[i] >> 8) & 0xFF;
> + cb = clut[i] & 0xFF;
> +
> + YUV_TO_RGB1_CCIR(cb, cr);
> + YUV_TO_RGB2_CCIR(r, g, b, y);
> +
> + clut[i] = (r << 16) | (g << 8) | b;
> + }
> +
> + return 0;
> +}
> +
> +static int dvdvideo_subtitle_stream_to_avstream(AVFormatContext *s,
> + int startcode,
> + char *clut_rgb_extradata_buf, int clut_rgb_extradata_buf_size,
> + char *lang_iso639__2, enum AVStreamParseType need_parsing)
> +{
> + AVStream *st;
> + FFStream *sti;
> + int ret;
> +
> + st = avformat_new_stream(s, NULL);
> + if (!st) {
> + return AVERROR(ENOMEM);
> + }
> +
> + st->id = startcode;
> + st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
> + st->codecpar->codec_id = AV_CODEC_ID_DVD_SUBTITLE;
> +
> + if ((ret = ff_alloc_extradata(st->codecpar,
> + clut_rgb_extradata_buf_size)) != 0) {
> + return ret;
> + }
> +
> + memcpy(st->codecpar->extradata, clut_rgb_extradata_buf,
> + st->codecpar->extradata_size);
> +
> + if (lang_iso639__2) {
> + av_dict_set(&st->metadata, "language", lang_iso639__2, 0);
> + }
> +
> + avpriv_set_pts_info(st, DVDVIDEO_PTS_WRAP_BITS,
> + DVDVIDEO_TIME_BASE_Q.num, DVDVIDEO_TIME_BASE_Q.den);
> +
> + sti = ffstream(st);
> + sti->request_probe = 0;
> + sti->need_parsing = need_parsing;
> +
> + return 0;
> +}
> +
> +static int dvdvideo_subtitle_stream_register(AVFormatContext *s,
> + int position,
> + char *clut_rgb_extradata_buf, int clut_rgb_extradata_buf_size,
> + char *lang_iso639__2)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret;
> +
> + int startcode = DVDVIDEO_STARTCODE_OFFSET_SUBP + position;
> +
> + for (int i = 0; i < s->nb_streams; i++) {
> + /* don't need to warn the user about this, since params won't change */
> + if (s->streams[i]->id == startcode) {
> + av_log(s, AV_LOG_TRACE,
> + "Subtitle stream has duplicate entry in VTS IFO\n");
> + return 0;
> + }
> + }
> +
> + if ((ret = dvdvideo_subtitle_stream_to_avstream(c->mpeg_ctx, startcode,
> + clut_rgb_extradata_buf, clut_rgb_extradata_buf_size,
> + lang_iso639__2, AVSTREAM_PARSE_FULL)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Unable to allocate subtitle stream (subdemux)\n");
> +
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_subtitle_stream_to_avstream(s, startcode,
> + clut_rgb_extradata_buf, clut_rgb_extradata_buf_size,
> + lang_iso639__2, AVSTREAM_PARSE_NONE)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Unable to allocate subtitle stream\n");
> +
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int dvdvideo_subtitle_stream_register_all(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret;
> + uint32_t clut_rgb[DVDVIDEO_SUBP_CLUT_SIZE] = {0};
> + char clut_rgb_extradata_buf[DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE] = {0};
> +
> + if (c->play_vts_ifo->vtsi_mat->nr_of_vts_subp_streams < 1) {
> + return 0;
> + }
> +
> + /* initialize the palette (same for all streams in this PGC) */
> + memcpy(clut_rgb, c->play_pgc->palette, DVDVIDEO_SUBP_CLUT_SIZE);
> +
> + if ((ret = dvdvideo_subtitle_clut_yuv_to_rgb(
> + clut_rgb, DVDVIDEO_SUBP_CLUT_SIZE)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Unable to convert subtitle palette\n");Why are you converting the subtitle palette?
> +
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_subtitle_clut_rgb_extradata_cat(
> + clut_rgb, DVDVIDEO_SUBP_CLUT_SIZE,
> + clut_rgb_extradata_buf, DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE)) != 0) {
> + av_log(s, AV_LOG_ERROR, "Unable to set up subtitle extradata\n");
> +
> + return ret;
> + }
> +
> + for (int i = 0; i < c->play_vts_ifo->
> + vtsi_mat->nr_of_vts_subp_streams; i++) {
> + uint32_t subp_control;
> + subp_attr_t subp_attr;
> + video_attr_t video_attr;
> + char *lang_iso639__2;
> +
> + subp_control = c->play_pgc->subp_control[i];
> +
> + if (!(subp_control & DVDVIDEO_SUBP_CONTROL_ENABLED_MASK)) {
> + continue;
> + }
> +
> + subp_attr = c->play_vts_ifo->vtsi_mat->vts_subp_attr[i];
> + video_attr = c->play_vts_ifo->vtsi_mat->vts_video_attr;
> + lang_iso639__2 = dvdvideo_lang_code_to_iso639__2(subp_attr.lang_code);
> +
> + /* there can be several presentations for one SPU */
> + /* for now, be flexible with the DAR check due to weird authoring */
> + if (video_attr.display_aspect_ratio > 0) {
> + /* 16:9 */
> + ret = dvdvideo_subtitle_stream_register(s,
> + ((subp_control >> 16) & 0x1F),
> + clut_rgb_extradata_buf,
> + DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
> + lang_iso639__2);
> + if (ret != 0) {
> + return ret;
> + }
> +
> + if (video_attr.permitted_df == DVDVIDEO_VTS_SPU_PFD_LETTERBOX_ONLY
> + || video_attr.permitted_df == DVDVIDEO_VTS_SPU_PFD_ANY) {
> + ret = dvdvideo_subtitle_stream_register(s,
> + ((subp_control >> 8) & 0x1F),
> + clut_rgb_extradata_buf,
> + DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
> + lang_iso639__2);
> + if (ret != 0) {
> + return ret;
> + }
> + }
> +
> + if (video_attr.permitted_df == DVDVIDEO_VTS_SPU_PFD_PANSCAN_ONLY
> + || video_attr.permitted_df == DVDVIDEO_VTS_SPU_PFD_ANY) {
> + ret = dvdvideo_subtitle_stream_register(s,
> + (subp_control & 0x1F),
> + clut_rgb_extradata_buf,
> + DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
> + lang_iso639__2);
> + if (ret != 0) {
> + return ret;
> + }
> + }
> + } else {
> + /* 4:3 */
> + ret = dvdvideo_subtitle_stream_register(s,
> + ((subp_control >> 24) & 0x1F),
> + clut_rgb_extradata_buf,
> + DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
> + lang_iso639__2);
> + if (ret != 0) {
> + return ret;
> + }
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int dvdvideo_subdemux_nested_io_open(AVFormatContext *s,
> + AVIOContext **pb, const char *url, int flags, AVDictionary **opts)
> +{
This function is pointless.
> + av_log(s, AV_LOG_ERROR, "Nested io_open not supported for this format\n");
> +
> + return AVERROR(EPERM);
> +}
> +
> +static int dvdvideo_subdemux_read_data(void *opaque, uint8_t *buf,
> + int buf_size)
> +{
> + AVFormatContext *s = opaque;
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + 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_PS_MAX_SEARCH_BLOCKS; i++) {
> + int nav_event;
> + int nav_len;
> +
> + int check_title;
> + int check_pgcn;
> + int check_pgn;
> + int check_angle;
> + int check_nb_angles;
> +
> + uint8_t nav_buf[DVDVIDEO_BLOCK_SIZE] = {0};
> +
> + if (ff_check_interrupt(&s->interrupt_callback)) {
> + return AVERROR_EXIT;
> + }
> +
> + if (dvdnav_get_next_block(c->play_dvdnav,
> + nav_buf, &nav_event, &nav_len) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Error reading block\n");
> +
> + goto end_dvdnav_error;
> + }
> +
> + if (nav_len > DVDVIDEO_BLOCK_SIZE) {
> + av_log(s, AV_LOG_ERROR, "Invalid block (too big)\n");
> +
This is not what ENOMEM means.
> + return AVERROR(ENOMEM);
> + }
> +
> + if (nav_event != DVDNAV_BLOCK_OK && nav_event != DVDNAV_NAV_PACKET) {
> + av_log(s, AV_LOG_TRACE, "nav event=%d len=%d\n", nav_event, nav_len);
> + }
> +
> + if (dvdnav_current_title_program(c->play_dvdnav, &check_title,
> + &check_pgcn, &check_pgn) != DVDNAV_STATUS_OK) {
> + goto end_dvdnav_error;
> + }
> +
> + if (check_title != c->opt_title || check_pgcn != c->play_pgcn
> + || !dvdnav_is_domain_vts(c->play_dvdnav)) {
> + return AVERROR_EOF;
> + }
> +
> + if (dvdnav_get_angle_info(c->play_dvdnav, &check_angle,
> + &check_nb_angles) != DVDNAV_STATUS_OK) {
> + goto end_dvdnav_error;
> + }
> +
> + if (check_angle != c->opt_angle) {
> + av_log(s, AV_LOG_ERROR, "Unexpected angle change\n");
> +
> + return AVERROR_INPUT_CHANGED;
> + }
> +
> + switch (nav_event) {
> + case DVDNAV_BLOCK_OK:
> + if (nav_len != DVDVIDEO_BLOCK_SIZE) {
> + av_log(s, AV_LOG_ERROR, "Invalid block\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + memcpy(buf, &nav_buf, nav_len);
> +
> + return nav_len;
> + case DVDNAV_HIGHLIGHT:
> + case DVDNAV_VTS_CHANGE:
> + case DVDNAV_STILL_FRAME:
> + case DVDNAV_STOP:
> + case DVDNAV_WAIT:
> + return AVERROR_EOF;
> + default:
> + continue;
> + }
> + }
> +
> + av_log(s, AV_LOG_ERROR, "Unable to find next MPEG block\n");
> +
> + return AVERROR_UNKNOWN;
> +
> +end_dvdnav_error:
> + av_log(s, AV_LOG_ERROR, "libdvdnav: %s\n", dvdnav_err_to_string(c->play_dvdnav));
> +
> + return AVERROR_EXTERNAL;
> +}
> +
> +static int64_t dvdvideo_subdemux_seek_data(void *opaque, int64_t offset,
> + int whence)
> +{
> + AVFormatContext *s = opaque;
> +
> + av_log(s, AV_LOG_ERROR,
> + "dvdvideo_subdemux_seek_data(): not implemented\n");
> +
> + return AVERROR_PATCHWELCOME;
> +}
> +
> +static int dvdvideo_subdemux_open(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret;
> +
> + const AVInputFormat *mpeg_fmt;
> + AVFormatContext *mpeg_ctx;
> + uint8_t *mpeg_buf;
> +
> + 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);
> + }
Why are you doing this instead of just declaring it to be a protocol
that produces a mpeg-ps stream? That way it stops being your responsibility.
> +
> + ffio_init_context(&c->mpeg_pb, mpeg_buf, DVDVIDEO_BLOCK_SIZE, 0,
> + s, dvdvideo_subdemux_read_data, NULL, dvdvideo_subdemux_seek_data);
> + c->mpeg_pb.pub.seekable = 0;
> +
> + if ((ret = ff_copy_whiteblacklists(mpeg_ctx, s)) != 0) {
> + avformat_free_context(mpeg_ctx);
> +
> + return ret;
> + }
> +
> + /* TODO: fix the PTS issues (ask about this in ML) */
> + /* AVFMT_FLAG_GENPTS works in some scenarios, but in others, the reading process hangs */
> + mpeg_ctx->flags = AVFMT_FLAG_CUSTOM_IO | AVFMT_FLAG_GENPTS;
MPEG-PS contains pts info, why not just use that? Not amazing PTS info,
but usable PTS info.
> +
> + 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 = dvdvideo_subdemux_nested_io_open;
> +
> + 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 void dvdvideo_subdemux_close(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + av_freep(&c->mpeg_pb.pub.buffer);
> + memset(&c->mpeg_pb, 0x00, sizeof(c->mpeg_pb));
> + av_freep(&c->mpeg_pb);Why are you zeroing it before you free it? If this is a security issue
you can't use memset anyway as the compiler may throw it away. That's
why explicit_bzero was created.
> +
> + avformat_close_input(&c->mpeg_ctx);
> +}
> +
> +static int dvdvideo_chapters_register(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> + int start_time = 0;
> + int cell = 0;
> +
> + for (int i = 0; i < c->play_pgc->nr_of_programs; i++) {
> + int64_t ms = 0;
> + int next = c->play_pgc->program_map[i + 1];
In the last iteration of the loop, will this possibly give you an
out-of-bounds read?
> +
> + if (i == c->play_pgc->nr_of_programs - 1) {
> + next = c->play_pgc->nr_of_cells + 1;
> + }
> +
> + while (cell < next - 1) {
> + /* only consider first cell of multi-angle cells */
> + if (c->play_pgc->cell_playback[cell].block_mode <= 1) {
> + ms = ms + dvdvideo_time_to_millis(
> + &c->play_pgc->cell_playback[cell].playback_time);
> + }
> + cell++;
> + }
> +
> + if (!avpriv_new_chapter(s, i, DVDVIDEO_TIME_BASE_MILLIS_Q,
> + start_time, start_time + ms, NULL)) {
> + av_log(s, AV_LOG_ERROR, "Unable to allocate chapter");
> + return AVERROR(ENOMEM);
> + }
> +
> + start_time += ms;
> + }
> +
> + return 0;
> +}
> +
> +static int dvdvideo_read_header(AVFormatContext *s)
> +{
> + int ret;
> +
> + if ((ret = dvdvideo_volume_open(s)) != 0) {
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_playback_open(s)) != 0) {
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_subdemux_open(s)) != 0) {
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_video_stream_register(s)) != 0) {
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_audio_stream_register_all(s)) != 0) {
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_subtitle_stream_register_all(s)) != 0) {
> + return ret;
> + }
> +
> + if ((ret = dvdvideo_chapters_register(s)) != 0) {
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int dvdvideo_read_packet(AVFormatContext *s, AVPacket *pkt)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + while (!ff_check_interrupt(&s->interrupt_callback)) {
> + int subdemux_ret;
> + AVPacket *subdemux_pkt;
> +
> + subdemux_pkt = av_packet_alloc();
> + if (!subdemux_pkt) {
> + return AVERROR(ENOMEM);
> + }
> +
> + subdemux_ret = av_read_frame(c->mpeg_ctx, subdemux_pkt);
> + if (subdemux_ret >= 0) {
> + if (c->mpeg_ctx->nb_streams != s->nb_streams) {
> + av_log(s, AV_LOG_ERROR, "Unexpected stream during playback\n");
> +
> + av_packet_unref(subdemux_pkt);
> +
> + return AVERROR_INPUT_CHANGED;
> + }
> +
> + av_packet_move_ref(pkt, subdemux_pkt);
This seems like an unnecessary memcpy.
> +
> + return 0;
> + }
> +
> + return subdemux_ret;
> + }
> +
> + return AVERROR_EOF;
> +}
> +
> +static int dvdvideo_read_seek(AVFormatContext *s, int stream_index,
> + int64_t timestamp, int flags)
> +{
> + av_log(s, AV_LOG_ERROR, "dvdvideo_read_seek(): not implemented\n");
> +
> + return AVERROR_PATCHWELCOME;
> +}
> +
> +static int dvdvideo_close(AVFormatContext *s)
> +{
> + dvdvideo_subdemux_close(s);
> + dvdvideo_playback_close(s);
> + dvdvideo_volume_close(s);
> +
> + return 0;
> +}
> +
> +static int dvdvideo_probe(const AVProbeData *p)
> +{
> + av_log(NULL, AV_LOG_ERROR, "dvdvideo_probe(): not implemented\n");
> +
> + return AVERROR_PATCHWELCOME;
> +}
> +
> +#define OFFSET(x) offsetof(DVDVideoDemuxContext, x)
> +static const AVOption dvdvideo_options[] = {
> + {"title", "Title Number", OFFSET(opt_title), AV_OPT_TYPE_INT, { .i64=1 }, 1, DVDVIDEO_TITLE_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
> + {"ptt", "Entry PTT Number", OFFSET(opt_ptt), AV_OPT_TYPE_INT, { .i64=1 }, 1, DVDVIDEO_PTT_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
> + {"pgc", "Entry PGC Number (0=auto)", OFFSET(opt_pgc), AV_OPT_TYPE_INT, { .i64=0 }, 0, DVDVIDEO_PGC_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
> + {"pg", "Entry PG Number (0=auto)", OFFSET(opt_pg), AV_OPT_TYPE_INT, { .i64=0 }, 0, DVDVIDEO_PG_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
> + {"angle", "Video Angle Number", OFFSET(opt_angle), AV_OPT_TYPE_INT, { .i64=1 }, 1, DVDVIDEO_ANGLE_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
> + {"region", "Playback Region Number (0=free)", OFFSET(opt_region), AV_OPT_TYPE_INT, { .i64=0 }, 0, DVDVIDEO_REGION_NUMBER_MAX, 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_NO_BYTE_SEEK | AVFMT_NOGENSEARCH | AVFMT_NOBINSEARCH | AVFMT_SHOW_IDS | AVFMT_TS_DISCONT,
> + .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
> +};
There's a number of other issues and I didn't write each one of each
type, but my biggest question is why this is not a protocol that
produces an mpeg-ps stream.
- Leo Izen (Traneptora)
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH] [WIP] [RFC] dvdvideo: initial contribution (DVD demuxer)
2023-12-10 3:03 ` Leo Izen
@ 2023-12-10 3:16 ` Marth64
2023-12-10 3:47 ` Marth64
1 sibling, 0 replies; 27+ messages in thread
From: Marth64 @ 2023-12-10 3:16 UTC (permalink / raw)
To: FFmpeg development discussions and patches
Hi Leo,
Thank you for the feedback and review. I will work through the code style
issues. Please do let me know what else you find - I will fix.
Here is some responses on the conceptual issues,
> my biggest question is why this is not a protocol that produces an
mpeg-ps stream.
Protocol handler in its current state does not allow revealing of language
codes or chapters AFAIK.
Also, there are streams in DVD that may pop up later in the lifecycle such
as forced subtitles used in the middle of a program.
> Why do you need both of these? libdvdnav depends on libdvdread.
This is because I need access to libdvdread headers in order to probe the
stream information in advance of the playback. Specifically, I need access
to what all could be in the stream vs. what the VM in libdvdnav thinks it
is currently playing back.
> Unnecessary #define.
I will get rid of it, np
> This function is unnecessary. If you really need to intercept logged
messages from libdvdnav and feed them to av_log, you should just have a
lookup that maps the corresponding message level.
> This is wrong, it's 30000/1001. Which you knew, because you #defined it
above. Why not use those?
Sure, I will simplify this. I will fix the chapter timing function anyways
as I am now noticing it is very slightly off.
> Can this fail? (x2)
> Our convention is to return negative values on error in FFmpeg. Not
nonzero values.
Both have a return type void. However your question caused me to also
check dvdnav_close() which can fail with a status. So I will fix that,
along with the potential non-zero response and being explicit to prevent it.
> This seems like an unnecessary memcpy.
I can do without it. I assumed it was a good idea to create some distance
between my demuxer's packet and the MPEG PS packet but I guess there is no
value to your point.
On Sat, Dec 9, 2023 at 9:03 PM Leo Izen <leo.izen@gmail.com> wrote:
> On 12/9/23 05:06, Marth64 wrote:
> > Hello, I am happy to share a DVD demuxer for ffmpeg powered by
> libdvdread and libdvdnav.
> > I have been working on this on/off throughout the year and think it is
> in a good spot
> > to share at the ML now. This was a major learning experience for me in
> many ways and
> > am open to any feedback on how I could improve it to be better. In fact,
> there is still
> > one issue I can't seem to figure out how to solve (in discussion below).
> Regardless,
> > a good number of DVDs I have tried seem to work out of the box. This is
> a full-service
> > demuxer with chapters, subtitles (and their palettes), as well as
> language metadata.
> >
> > At a high level, the demuxer uses libdvdread for metadata of the disc,
> libdvdnav for
> > the actual playback, and the MPEG-PS demuxer for the underlying VOB
> stream.
> >
> > First, the basic usage, is quite straightforward:
> > ffmpeg -f dvdvideo -title NN -i DVD.iso|/dev/srX|/path/to/DVD ...
> > Where NN can be replaced by your known title number in the disc
> >
> > As the demuxer effectively works at a PGC level, multi-PGC titles are
> not supported.
> > But to provide this flexibility as there are many weirdly authored DVDs
> out there,
> > one can specify an exact PGC via -pgc option and an associated program
> (PG, can start at 1).
> >
> > I am hoping and willing to improve this to be a robust demuxer wherever
> possible, but to
> > that extent there is still an issue I can't figure out how to solve:
> Dealing with DTS discontinuities
> > and PTS generation.
> >
> > Right now, as a band-aid, I am adding AVFMT_FLAG_GENPTS (line 1408)
> > to the MPEG-PS subdemuxer. This works with most discs and the output
> seems OK. On discs with discontinuities, however,
> > this causes the demuxing to hang which is obviously unacceptable also.
> Removing the flag causes the discs
> > to not hang, but then the output for all discs becomes choppy for
> obvious reasons (invalid PTS).
> >
> > It could be because I have some misunderstandings on how things work
> within ffmpeg,
> > but I have tried to the point now where I thought it best to ask the
> experts for help if you can spot
> > what I am doing wrong. I am really motivated to make this work and good
> quality.
> >
> > Thank you!
> >
> > ---
> > configure | 8 +
> > libavformat/Makefile | 1 +
> > libavformat/allformats.c | 1 +
> > libavformat/avlanguage.c | 10 +-
> > libavformat/dvdvideodec.c | 1599 +++++++++++++++++++++++++++++++++++++
> > 5 files changed, 1617 insertions(+), 2 deletions(-)
> > create mode 100644 libavformat/dvdvideodec.c
> >
> > diff --git a/configure b/configure
> > index d77c053226..65e5968194 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]
>
> Why do you need both of these? libdvdnav depends on libdvdread.
>
> > --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]
> > @@ -1802,6 +1804,8 @@ EXTERNAL_LIBRARY_GPL_LIST="
> > frei0r
> > libcdio
> > libdavs2
> > + libdvdnav
> > + libdvdread
> > librubberband
> > libvidstab
> > libx264
> > @@ -3494,6 +3498,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"
> > @@ -6723,6 +6729,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 && require_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 2db83aff81..45dba53044 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 c8bb4e3866..dc2acf575c 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..2b22a7de4f
> > --- /dev/null
> > +++ b/libavformat/dvdvideodec.c
> > @@ -0,0 +1,1599 @@
> > +/*
> > + * DVD-Video demuxer (powered by libdvdnav/libdvdread)
> > + *
> > + * 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
> > + * 7) Close resources
> > + **/
> > +
> > +/**
> > + * Issues/bug tracker (TODO):
> > + * - SDDS is not supported yet
> > + * - Are we handling backwards cell changes and PTT changes correctly?
> > + * - Some codec parameters/metadata is not being explicitly set:
> > + * -> ChannelLayout, color/chroma info, DAR, frame size for
> MP1/MP2 audio
> > + * - Additional PGC validations?
> > +**/
> > +
> > +#include <dvdread/dvd_reader.h>
> > +#include <dvdread/ifo_read.h>
> > +#include <dvdread/ifo_types.h>
> > +#include <dvdread/nav_read.h>
> > +#include <dvdnav/dvdnav.h>
> > +
> > +#include "libavutil/avstring.h"
> > +#include "libavutil/avutil.h"
> > +#include "libavutil/colorspace.h"
> > +#include "libavutil/mem.h"
> > +#include "libavutil/opt.h"
> > +#include "libavutil/samplefmt.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 ZERO_Q (AVRational) {
> 0, 1 }
> Unnecessary #define.
>
> > +
> > +#define DVDVIDEO_PS_MAX_SEARCH_BLOCKS 128
> > +#define DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE 1024
> > +
> > +#define DVDVIDEO_TIME_BASE_Q (AVRational) {
> 1, 90000 }
> > +#define DVDVIDEO_TIME_BASE_MILLIS_Q (AVRational) {
> 1, 1000 }
> > +#define DVDVIDEO_PTS_WRAP_BITS 32
> > +#define DVDVIDEO_BLOCK_SIZE 2048
> > +
> > +#define DVDVIDEO_NTSC_FRAMERATE_Q (AVRational) {
> 30000, 1001 }
> > +#define DVDVIDEO_NTSC_HEIGHT 480
> > +#define DVDVIDEO_PAL_FRAMERATE_Q (AVRational) {
> 25, 1 }
> > +#define DVDVIDEO_PAL_HEIGHT 576
> > +#define DVDVIDEO_D1_WIDTH 720
> > +#define DVDVIDEO_4CIF_WIDTH 704
> > +#define DVDVIDEO_D1_HALF_WIDTH 352
> > +#define DVDVIDEO_CIF_WIDTH 352
> > +#define DVDVIDEO_PIXEL_FORMAT
> AV_PIX_FMT_YUV420P
> > +
> > +#define DVDVIDEO_STARTCODE_VIDEO 0x1E0
> > +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_AC3 0x80
> > +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP1 0x1C0
> > +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP2 0x1C0
> > +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_PCM 0xA0
> > +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_DTS 0x88
> > +#define DVDVIDEO_STARTCODE_OFFSET_SUBP 0x20
> > +
> > +#define DVDVIDEO_TITLE_NUMBER_MAX 99
> > +#define DVDVIDEO_PTT_NUMBER_MAX 99
> > +#define DVDVIDEO_PGC_NUMBER_MAX 32767
> > +#define DVDVIDEO_PG_NUMBER_MAX 255
> > +#define DVDVIDEO_ANGLE_NUMBER_MAX 9
> > +#define DVDVIDEO_VTS_NUMBER_MAX 99
> > +#define DVDVIDEO_TT_NUMBER_MAX 99
> > +#define DVDVIDEO_REGION_NUMBER_MAX 8
> > +#define DVDVIDEO_AUDIO_CONTROL_ENABLED_MASK 0x8000
> > +#define DVDVIDEO_SUBP_CONTROL_ENABLED_MASK 0x80000000
> > +#define DVDVIDEO_VTS_AUDIO_STREAMS_MAX 8
> > +#define DVDVIDEO_VTS_SUBP_STREAMS_MAX 32
> > +
> > +/* ("palette: ") + ("rrggbb, "*15) + ("rrggbb") + \n + \0 */
> > +#define DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE (9 + (8 * 15) +
> 6 + 1 + 1)
> > +#define DVDVIDEO_SUBP_CLUT_LEN 16
> > +#define DVDVIDEO_SUBP_CLUT_SIZE
> DVDVIDEO_SUBP_CLUT_LEN * sizeof(uint32_t)
> > +
> > +/* convert binary-coded decimal to decimal */
> > +#define BCD2D(__x__) (((__x__ & 0xF0) >> 4) * 10 + (__x__ & 0x0F))
> > +
> > +/* crop table for YUV to RGB subpicture palette conversion */
> > +#define DVDVIDEO_YUV_NEG_CROP_MAX 1024
> > +#define times4(x) x, x, x, x
> > +#define times256(x) times4(times4(times4(times4(times4(x)))))
> > +
> > +const uint8_t dvdvideo_yuv_crop_tab[256 + 2 *
> DVDVIDEO_YUV_NEG_CROP_MAX] = {
> > +times256(0x00),
> >
> +0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
> >
> +0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
> >
> +0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
> >
> +0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
> >
> +0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
> >
> +0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,
> >
> +0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
> >
> +0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,
> >
> +0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
> >
> +0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
> >
> +0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
> >
> +0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
> >
> +0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
> >
> +0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
> >
> +0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,
> >
> +0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF,
> > +times256(0xFF)
> > +};
> > +
> > +enum DVDVideoVTSMPEGVersion {
> > + DVDVIDEO_VTS_MPEG_VERSION_MPEG1 = 0,
> > + DVDVIDEO_VTS_MPEG_VERSION_MPEG2 = 1
> > +};
> > +
> > +enum DVDVideoVTSPictureFormat {
> > + DVDVIDEO_VTS_PICTURE_FORMAT_NTSC = 0,
> > + DVDVIDEO_VTS_PICTURE_FORMAT_PAL = 1
> > +};
> > +
> > +enum DVDVideoVTSPictureSize {
> > + DVDVIDEO_VTS_PICTURE_SIZE_D1 = 0,
> > + DVDVIDEO_VTS_PICTURE_SIZE_4CIF = 1,
> > + DVDVIDEO_VTS_PICTURE_SIZE_D1_HALF = 2,
> > + DVDVIDEO_VTS_PICTURE_SIZE_CIF = 3
> > +};
> > +
> > +enum DVDVideoVTSPictureDAR {
> > + DVDVIDEO_VTS_PICTURE_DAR_4_3 = 0,
> > + DVDVIDEO_VTS_PICTURE_DAR_16_9 = 3
> > +};
> > +
> > +enum DVDVideoVTSPermittedFullscreenDisplay {
> > + DVDVIDEO_VTS_SPU_PFD_ANY = 0,
> > + DVDVIDEO_VTS_SPU_PFD_PANSCAN_ONLY = 1,
> > + DVDVIDEO_VTS_SPU_PFD_LETTERBOX_ONLY = 2
> > +};
> > +
> > +enum DVDVideoVTSAudioFormat {
> > + DVDVIDEO_VTS_AUDIO_FORMAT_AC3 = 0,
> > + DVDVIDEO_VTS_AUDIO_FORMAT_MP1 = 2,
> > + DVDVIDEO_VTS_AUDIO_FORMAT_MP2 = 3,
> > + DVDVIDEO_VTS_AUDIO_FORMAT_PCM = 4,
> > + DVDVIDEO_VTS_AUDIO_FORMAT_SDDS = 5,
> > + DVDVIDEO_VTS_AUDIO_FORMAT_DTS = 6
> > +};
> > +
> > +enum FFDVDVideoVTSAudioSampleRate {
> > + DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_48K = 0,
> > + DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_96K = 1
> > +};
> > +
> > +enum FFDVDVideoVTSAudioQuantization {
> > + DVDVIDEO_VTS_AUDIO_QUANTIZATION_16 = 0,
> > + DVDVIDEO_VTS_AUDIO_QUANTIZATION_20 = 1,
> > + DVDVIDEO_VTS_AUDIO_QUANTIZATION_24 = 2
> > +};
> > +
> > +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 identification 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 *vol_dvdread; /* handle
> to libdvdread */
> > + ifo_handle_t *vol_vmg_ifo; /* handle
> to the VMG (VIDEO_TS.IFO) */
> > +
> > + /* playback */
> > + ifo_handle_t *play_vts_ifo; /* handle
> to the active VTS (VTS_nn_n.IFO) */
> > + pgc_t *play_pgc; /* handle
> to the active PGC */
> > + int play_vtsn; /* number
> of the active VTS (video title set) */
> > + int play_pgcn; /* number
> of the active PGC (program chain) */
> > + dvdnav_t *play_dvdnav; /* handle
> to libdvdnav */
> > +} DVDVideoDemuxContext;
> > +
> > +static void dvdvideo_libdvdread_log(void *opaque, dvd_logger_level_t
> level,
> > + const char *msg, va_list msg_va)
> > +{
>
> This function is unnecessary. If you really need to intercept logged
> messages from libdvdnav and feed them to av_log, you should just have a
> lookup that maps the corresponding message level.
>
> > + AVFormatContext *s = opaque;
> > + int lavu_level;
> > +
> > + char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
> > + vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
> > +
> > + switch (level) {
> > + case DVD_LOGGER_LEVEL_ERROR:
> > + lavu_level = AV_LOG_ERROR;
> > + break;
> > + case DVD_LOGGER_LEVEL_WARN:
> > + lavu_level = AV_LOG_WARNING;
> > + break;
> > + case DVD_LOGGER_LEVEL_INFO:
> > + lavu_level = AV_LOG_INFO;
> > + break;
> > + case DVD_LOGGER_LEVEL_DEBUG:
> > + default:
> > + lavu_level = AV_LOG_DEBUG;
> > + }
> > +
> > + /* libdvdread messages don't have line terminators */
> > + av_log(s, lavu_level, "libdvdread: %s\n", msg_buf);
> > +}
> > +
> > +static void dvdvideo_libdvdnav_log(void *opaque, dvdnav_logger_level_t
> level,
> > + const char *msg, va_list msg_va)
> > +{
> > + AVFormatContext *s = opaque;
> Same complaint here. Also you have no to reassign opaque here.
>
> > + int lavu_level;
> > +
> > + char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
> > + vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
> > +
> > + switch (level) {
> > + case DVDNAV_LOGGER_LEVEL_ERROR:
> > + lavu_level = AV_LOG_ERROR;
> > + break;
> > + case DVDNAV_LOGGER_LEVEL_WARN:
> > + lavu_level = AV_LOG_WARNING;
> > + break;
> > + case DVDNAV_LOGGER_LEVEL_INFO:
> > + lavu_level = AV_LOG_INFO;
> > + break;
> > + case DVDNAV_LOGGER_LEVEL_DEBUG:
> > + default:
> > + lavu_level = AV_LOG_DEBUG;
> > + }
> > +
> > + /* libdvdnav messages don't have line terminators */
> > + av_log(s, lavu_level, "libdvdnav: %s\n", msg_buf);
> > +}
> > +
> > +static char *dvdvideo_lang_code_to_iso639__2(const uint16_t lang_code)
> > +{
> > + char lang_code_str[3] = {0};
> > +
> > + if (lang_code && lang_code != 0xFFFF) {
> > + lang_code_str[0] = (lang_code >> 8) & 0xFF;
> > + lang_code_str[1] = lang_code & 0xFF;
> > + }
> AV_WB16(lang_code_str, lang_code);
>
> > +
> > + return (char *) ff_convert_lang_to(lang_code_str,
> AV_LANG_ISO639_2_BIBL);
> > +}
> > +
> > +static int64_t dvdvideo_time_to_millis(dvd_time_t *time)
> > +{
> > + double fps;
> > + int64_t ms;
> > +
> > + int64_t sec = (int64_t) (BCD2D(time->hour)) * 60 * 60;
> > + sec += (int64_t) (BCD2D(time->minute)) * 60;
> > + sec += (int64_t) (BCD2D(time->second));
> > +
> > + /* the 2 high bits are the frame rate */
> > + switch ((time->frame_u & 0xC0) >> 6)
> > + {
> > + case 1:
> > + fps = 25.0;
> > + break;
> > + case 3:
> > + fps = 29.97;
> This is wrong, it's 30000/1001. Which you knew, because you #defined it
> above. Why not use those?
>
> > + break;
> > + default:
> > + fps = 2500.0;
> > + break;
> > + }
> > + ms = BCD2D(time->frame_u & 0x3F) * 1000.0 / fps;
> > +
> > + return (sec * 1000) + ms;
> > +}
> > +
> > +static int dvdvideo_volume_is_title_valid_in_context(AVFormatContext *s,
> > + int title)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + return title >= 1
> > + && title <= DVDVIDEO_TITLE_NUMBER_MAX
> > + && title <= c->vol_vmg_ifo->tt_srpt->nr_of_srpts
> > + && c->vol_vmg_ifo->tt_srpt->title[title - 1].nr_of_ptts > 0;
> > +}
> > +
> > +static int dvdvideo_volume_is_title_valid_in_vts(title_info_t
> title_info,
> > + ifo_handle_t *vts_ifo)
> > +{
> > + return title_info.vts_ttn >= 1
> > + && title_info.vts_ttn <= DVDVIDEO_TT_NUMBER_MAX
> > + && title_info.vts_ttn <= vts_ifo->vts_ptt_srpt->nr_of_srpts
> > + && vts_ifo->vtsi_mat->nr_of_vts_audio_streams
> > + <= DVDVIDEO_VTS_AUDIO_STREAMS_MAX
> > + && vts_ifo->vtsi_mat->nr_of_vts_subp_streams
> > + <= DVDVIDEO_VTS_SUBP_STREAMS_MAX;
> > +}
> > +
> > +static int dvdvideo_volume_is_angle_valid_in_title(int angle,
> > + title_info_t title_info)
> > +{
> > + return angle >= 1
> > + && angle <= DVDVIDEO_ANGLE_NUMBER_MAX
> > + && angle <= title_info.nr_of_angles;
> > +}
> > +
> > +static int dvdvideo_volume_is_pgc_valid_and_sequential(pgc_t *pgc)
> > +{
> > + return pgc
> > + && pgc->program_map
> > + && pgc->cell_playback != NULL
> > + && pgc->pg_playback_mode == 0
> > + && pgc->nr_of_programs > 0
> > + && pgc->nr_of_cells > 0;
> > +}
> > +
> > +static int dvdvideo_volume_is_pgcn_in_vts(int pgcn, ifo_handle_t
> *vts_ifo)
> > +{
> > + return pgcn >= 1
> > + && pgcn <= DVDVIDEO_PGC_NUMBER_MAX
> > + && pgcn <= vts_ifo->vts_pgcit->nr_of_pgci_srp;
> > +}
> > +
> > +static int dvdvideo_volume_is_pgn_in_pgc(int pgn, pgc_t *pgc)
> > +{
> > + return pgn >= 1
> > + && pgn <= DVDVIDEO_PG_NUMBER_MAX
> > + && pgn <= pgc->nr_of_programs;
> > +}
> > +
> > +static int dvdvideo_volume_open(AVFormatContext *s)
> av_cold
>
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + dvd_reader_t *dvdread;
> > + ifo_handle_t *vmg_ifo;
> > +
> > + dvd_logger_cb dvdread_log_cb = { .pf_log = dvdvideo_libdvdread_log
> };
> > + dvdread = DVDOpen2(s, &dvdread_log_cb, s->url);
> > +
> > + if (!dvdread) {
> > + av_log(s, AV_LOG_ERROR, "Unable to initialize libdvdread\n");
> > +
> > + return AVERROR_EXTERNAL;
> > + }
> > +
> > + if (!(vmg_ifo = ifoOpen(dvdread, 0))) {
> > + DVDClose(dvdread);
> > +
> > + av_log(s, AV_LOG_ERROR,
> > + "Unable to open VIDEO_TS.IFO. "
> > + "Input does not have a valid DVD-Video structure "
> > + "or is not a compliant UDF DVD-Video image.\n");
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + c->vol_dvdread = dvdread;
> > + c->vol_vmg_ifo = vmg_ifo;
> > +
> > + return 0;
> > +}
> > +
> > +static void dvdvideo_volume_close(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + if (c->vol_vmg_ifo) {
> > + ifoClose(c->vol_vmg_ifo);
> Can this fail?
>
> > + c->vol_vmg_ifo = NULL;
> > + }
> > +
> > + if (c->vol_dvdread) {
> > + DVDClose(c->vol_dvdread);
> Can this fail?
>
> > + c->vol_dvdread = NULL;
> > + }
> > +}
> > +
> > +static int dvdvideo_volume_open_vts_ifo(AVFormatContext *s, int vtsn,
> > + ifo_handle_t **p_vts_ifo)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + ifo_handle_t *vts_ifo;
> > +
> > + if (vtsn < 1
> > + || vtsn > DVDVIDEO_VTS_NUMBER_MAX
> > + || !(vts_ifo = ifoOpen(c->vol_dvdread, vtsn))) {
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + for (int i = 0; i < vts_ifo->vts_c_adt->nr_of_vobs; i++) {
> > + int start_sector =
> vts_ifo->vts_c_adt->cell_adr_table[i].start_sector;
> > + int end_sector =
> vts_ifo->vts_c_adt->cell_adr_table[i].last_sector;
> > +
> > + if ((start_sector & 0xFFFFFF) == 0xFFFFFF
> > + || (end_sector & 0xFFFFFF) == 0xFFFFFF
> > + || start_sector >= end_sector) {
> > + ifoClose(vts_ifo);
> > +
> > + av_log(s, AV_LOG_WARNING, "VTS has invalid cell address
> table\n");
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > + }
> > +
> > + (*p_vts_ifo) = vts_ifo;
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_playback_open(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + int ret;
> > +
> > + ifo_handle_t *vts_ifo;
> > + dvdnav_logger_cb dvdnav_log_cb;
> > + dvdnav_status_t dvdnav_status;
> > + dvdnav_t *dvdnav;
> > +
> > + title_info_t title_info;
> > + int pgcn;
> > + int pgn;
> > + pgc_t *pgc;
> > +
> > + int32_t disc_region_mask;
> > + int32_t player_region_mask;
> > + int cell_search_has_vts = 0;
> > +
> > + /* if the title is valid, open its VTS IFO */
> > + if (!dvdvideo_volume_is_title_valid_in_context(s, c->opt_title)) {
> > + av_log(s, AV_LOG_ERROR, "Title not found or invalid\n");
> > +
> > + return AVERROR_STREAM_NOT_FOUND;
> > + }
> > +
> > + title_info = c->vol_vmg_ifo->tt_srpt->title[c->opt_title - 1];
> > +
> > + if ((ret = dvdvideo_volume_open_vts_ifo(s,
> > + title_info.title_set_nr, &vts_ifo)) != 0) {
> Our convention is to return negative values on error in FFmpeg. Not
> nonzero values.
>
> > + av_log(s, AV_LOG_ERROR, "Title VTS could not be opened\n");
> > +
> > + return ret;
> > + }
> > +
> > + if (!dvdvideo_volume_is_title_valid_in_vts(title_info, vts_ifo)) {
> > + av_log(s, AV_LOG_ERROR, "Title VTS is invalid\n");
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + if (!dvdvideo_volume_is_angle_valid_in_title(c->opt_angle,
> title_info)) {
> > + av_log(s, AV_LOG_ERROR, "Angle not found or invalid\n");
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + /* determine our PGC and PG (playback coordinates) */
> > + if (c->opt_pgc > 0 && c->opt_pg > 0) {
> > + if (c->opt_ptt > 0) {
> > + av_log(s, AV_LOG_WARNING,
> > + "PTT option ignored as PGC and PG are provided\n");
> > + }
> > +
> > + pgcn = c->opt_pgc;
> > + pgn = c->opt_pg;
> > + } else {
> > + if (c->opt_ptt > title_info.nr_of_ptts) {
> > + av_log(s, AV_LOG_ERROR, "PTT not found\n");
> > +
> > + return AVERROR_STREAM_NOT_FOUND;
> > + }
> > +
> > + pgcn = vts_ifo->vts_ptt_srpt->title[title_info.vts_ttn -
> 1].ptt[c->opt_ptt - 1].pgcn;
> > + pgn = vts_ifo->vts_ptt_srpt->title[title_info.vts_ttn -
> 1].ptt[c->opt_ptt - 1].pgn;
> > + }
> > +
> > + if (!dvdvideo_volume_is_pgcn_in_vts(pgcn, vts_ifo)) {
> > + av_log(s, AV_LOG_ERROR, "PGC not found\n");
> > +
> > + return AVERROR_STREAM_NOT_FOUND;
> > + }
> > +
> > + pgc = vts_ifo->vts_pgcit->pgci_srp[pgcn - 1].pgc;
> > +
> > + if (!dvdvideo_volume_is_pgc_valid_and_sequential(pgc)) {
> > + av_log(s, AV_LOG_ERROR, "PGC not valid\n");
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + if (!dvdvideo_volume_is_pgn_in_pgc(pgn, pgc)) {
> > + av_log(s, AV_LOG_ERROR, "PG not found\n");
> > +
> > + return AVERROR_STREAM_NOT_FOUND;
> > + }
> > +
> > + /* set up libdvdnav */
> > + dvdnav_log_cb = (dvdnav_logger_cb) { .pf_log =
> dvdvideo_libdvdnav_log };
> > + dvdnav_status = dvdnav_open2(&dvdnav, NULL, &dvdnav_log_cb, s->url);
> > +
> > + if (!dvdnav) {
> > + av_log(s, AV_LOG_ERROR, "Unable to initialize libdvdnav\n");
> > +
> > + return AVERROR_EXTERNAL;
> > + }
> > +
> > + if (dvdnav_status != DVDNAV_STATUS_OK) {
> Code style. We don't use braces with single-line blocks.
>
> > + goto end_search_error_dvdnav;
> > + }
> > +
> > + if (dvdnav_set_readahead_flag(dvdnav, 0) != DVDNAV_STATUS_OK) {
> > + goto end_search_error_dvdnav;
> > + }
> > +
> > + if (dvdnav_set_PGC_positioning_flag(dvdnav, 1) != DVDNAV_STATUS_OK)
> {
> > + goto end_search_error_dvdnav;
> > + }
> > +
> > + if (dvdnav_get_region_mask(dvdnav, &disc_region_mask) !=
> DVDNAV_STATUS_OK) {
> > + goto end_search_error_dvdnav;
> > + }
> > +
> > + if (c->opt_region > 0) {
> > + player_region_mask = (1 << (c->opt_region - 1));
> > + } else {
> > + player_region_mask = disc_region_mask;
> > + }
> > +
> > + if (dvdnav_set_region_mask(dvdnav, player_region_mask) !=
> DVDNAV_STATUS_OK) {
> > + goto end_search_error_dvdnav;
> > + }
> > +
> > + if (dvdnav_program_play(dvdnav, c->opt_title, pgcn, pgn) !=
> DVDNAV_STATUS_OK) {
> > + goto end_search_error_dvdnav;
> > + }
> > +
> > + if (dvdnav_angle_change(dvdnav, c->opt_angle) != DVDNAV_STATUS_OK) {
> > + goto end_search_error_dvdnav;
> > + }
> > +
> > + /* lock on to title's VTS and chosen PGC/PGN coordinates */
> > + for (int i = 0; i < DVDVIDEO_PS_MAX_SEARCH_BLOCKS; i++) {
> > + int nav_event;
> > + int nav_len;
> > + dvdnav_vts_change_event_t *vts_event;
> > + uint8_t nav_buf[DVDVIDEO_BLOCK_SIZE] = {0};
> > +
> > + if (ff_check_interrupt(&s->interrupt_callback)) {
> > + return AVERROR_EXIT;
> > + }
> > +
> > + if (dvdnav_get_next_block(dvdnav, nav_buf, &nav_event, &nav_len)
> > + != DVDNAV_STATUS_OK) {
> > + goto end_search_error_dvdnav;
> > + }
> > +
> > + if (nav_len > DVDVIDEO_BLOCK_SIZE) {
> > + av_log(s, AV_LOG_ERROR, "Invalid block (too big)\n");
> > +
> > + ret = AVERROR(ENOMEM);
> > +
> > + goto end_search_error;
> > + }
> > +
> > + av_log(s, AV_LOG_TRACE, "nav event=%d len=%d\n", nav_event,
> nav_len);
> > +
> > + switch (nav_event) {
> > + case DVDNAV_VTS_CHANGE:
> > + vts_event = (dvdnav_vts_change_event_t *) nav_buf;
> > +
> > + if (cell_search_has_vts) {
> > + if (vts_event->new_vtsN != title_info.title_set_nr
> > + || vts_event->new_domain !=
> DVD_DOMAIN_VTSTitle) {
> > + av_log(s, AV_LOG_ERROR, "Unexpected VTS
> change\n");
> > +
> > + ret = AVERROR_INPUT_CHANGED;
> > +
> > + goto end_search_error;
> > + }
> > + continue;
> > + }
> > +
> > + if (vts_event->new_vtsN == title_info.title_set_nr
> > + && vts_event->new_domain ==
> DVD_DOMAIN_VTSTitle) {
> > + cell_search_has_vts = 1;
> > + }
> > +
> > + continue;
> > + case DVDNAV_CELL_CHANGE:
> > + // if we need more info about the cell:
> > + // dvdnav_cell_change_event_t *event_data =
> > + // (dvdnav_cell_change_event_t *) nav_buf;
> > + int check_title;
> > + int check_pgcn;
> > + int check_pgn;
> > +
> > + if (!cell_search_has_vts) {
> > + continue;
> > + }
> > +
> > + dvdnav_current_title_program(dvdnav, &check_title,
> > + &check_pgcn, &check_pgn);
> > +
> > + if (check_title == c->opt_title && check_pgcn == pgcn
> > + && check_pgn == pgn &&
> dvdnav_is_domain_vts(dvdnav)) {
> > + goto end_ready;
> > + }
> > +
> > + continue;
> > + case DVDNAV_STILL_FRAME:
> > + dvdnav_still_skip(dvdnav);
> > +
> > + continue;
> > + case DVDNAV_WAIT:
> > + dvdnav_wait_skip(dvdnav);
> > +
> > + continue;
> > + case DVDNAV_STOP:
> > + ret = AVERROR_INPUT_CHANGED;
> > +
> > + goto end_search_error;
> > + default:
> > + continue;
> > + }
> > + }
> > +
> > +end_search_error_dvdnav:
> > + ret = AVERROR_EXTERNAL;
> > + av_log(s, AV_LOG_ERROR, "libdvdnav: %s\n",
> dvdnav_err_to_string(dvdnav));
> > + av_log(s, AV_LOG_ERROR, "Error starting playback\n");
> > +
> > +end_search_error:
> > + dvdnav_close(dvdnav);
> > + ifoClose(vts_ifo);
> > +
> > + return ret;
> > +
> > +end_ready:
> > + /* update the context */
> > + c->play_vts_ifo = vts_ifo;
> > + c->play_pgc = pgc;
> > + c->play_vtsn = title_info.title_set_nr;
> > + c->play_pgcn = pgcn;
> > + c->play_dvdnav = dvdnav;
> > +
> > + return 0;
> > +}
> > +
> > +static void dvdvideo_playback_close(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + if (c->play_dvdnav) {
> > + dvdnav_close(c->play_dvdnav);
> > + c->play_dvdnav = NULL;
> > + }
> > +
> > + if (c->play_vts_ifo) {
> > + ifoClose(c->play_vts_ifo);
> > + c->play_vts_ifo = NULL;
> > + }
> > +}
> > +
> > +static int dvdvideo_video_stream_analyze(video_attr_t video_attr,
> > + enum AVCodecID *p_codec_id, AVRational *p_framerate,
> > + int *p_height, int *p_width)
> > +{
> > + enum AVCodecID codec_id = AV_CODEC_ID_NONE;
> > + AVRational framerate = ZERO_Q;
> > + int height = 0;
> > + int width = 0;
> > +
> > + switch (video_attr.mpeg_version) {
> > + case DVDVIDEO_VTS_MPEG_VERSION_MPEG1:
> > + codec_id = AV_CODEC_ID_MPEG1VIDEO;
> > + break;
> > + case DVDVIDEO_VTS_MPEG_VERSION_MPEG2:
> > + codec_id = AV_CODEC_ID_MPEG2VIDEO;
> > + break;
> > + }
> > +
> > + switch (video_attr.video_format) {
> > + case DVDVIDEO_VTS_PICTURE_FORMAT_NTSC:
> > + framerate = DVDVIDEO_NTSC_FRAMERATE_Q;
> > + height = DVDVIDEO_NTSC_HEIGHT;
> > + break;
> > + case DVDVIDEO_VTS_PICTURE_FORMAT_PAL:
> > + framerate = DVDVIDEO_PAL_FRAMERATE_Q;
> > + height = DVDVIDEO_PAL_HEIGHT;
> > + break;
> > + }
> > +
> > + if (height > 0) {
> > + switch (video_attr.picture_size) {
> > + case DVDVIDEO_VTS_PICTURE_SIZE_D1:
> > + width = DVDVIDEO_D1_WIDTH;
> > + break;
> > + case DVDVIDEO_VTS_PICTURE_SIZE_4CIF:
> > + width = DVDVIDEO_4CIF_WIDTH;
> > + break;
> > + case DVDVIDEO_VTS_PICTURE_SIZE_D1_HALF:
> > + width = DVDVIDEO_D1_HALF_WIDTH;
> > + break;
> > + case DVDVIDEO_VTS_PICTURE_SIZE_CIF:
> > + width = DVDVIDEO_CIF_WIDTH;
> > + height /= 2;
> > + break;
> > + }
> > + }
> > +
> > + if (codec_id == AV_CODEC_ID_NONE || av_cmp_q(framerate, ZERO_Q) == 0
> av_cmp_q for comparing a rational to zero is unnecessary. Just check if
> the numerator is nonzero, as a fraction is zero if and only if its
> numerator is zero.
>
> > + || width < 1 || height < 1) {
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + (*p_codec_id) = codec_id;
> > + (*p_framerate) = framerate;
> > + (*p_height) = height;
> > + (*p_width) = width;
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_video_stream_to_avstream(AVFormatContext *s,
> > + enum AVCodecID codec_id, AVRational framerate,
> > + int height, int width,
> > + enum AVStreamParseType need_parsing)
> > +{
> > + AVStream *st;
> > + FFStream *sti;
> > +
> > + st = avformat_new_stream(s, NULL);
> > + if (!st) {
> > + return AVERROR(ENOMEM);
> > + }
> > +
> > + st->id = DVDVIDEO_STARTCODE_VIDEO;
> > + st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
> > + st->codecpar->codec_id = codec_id;
> > + st->codecpar->width = width;
> > + st->codecpar->height = height;
> > + st->codecpar->format = DVDVIDEO_PIXEL_FORMAT;
> > +
> > + st->codecpar->framerate = framerate;
> > +#if FF_API_R_FRAME_RATE
> > + st->r_frame_rate = framerate;
> > +#endif
> > + st->avg_frame_rate = framerate;
> > +
> > + avpriv_set_pts_info(st, DVDVIDEO_PTS_WRAP_BITS,
> > + DVDVIDEO_TIME_BASE_Q.num, DVDVIDEO_TIME_BASE_Q.den);
> > +
> > + sti = ffstream(st);
> > + sti->request_probe = 0;
> > + sti->need_parsing = need_parsing;
> > + sti->avctx->framerate = framerate;
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_video_stream_register(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + enum AVCodecID codec_id;
> > + AVRational framerate;
> > + int height;
> > + int width;
> > +
> > + int ret;
> > +
> > + if ((ret =
> dvdvideo_video_stream_analyze(c->play_vts_ifo->vtsi_mat->vts_video_attr,
> &codec_id,
> > + &framerate, &height, &width)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Invalid video parameters in VTS
> IFO\n");
> > +
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_video_stream_to_avstream(c->mpeg_ctx, codec_id,
> > + framerate, height, width, AVSTREAM_PARSE_FULL)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Unable to allocate video stream
> (subdemux)\n");
> > +
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_video_stream_to_avstream(s, codec_id,
> > + framerate, height, width, AVSTREAM_PARSE_NONE)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Unable to allocate video stream\n");
> > +
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_audio_stream_analyze(audio_attr_t audio_attr,
> > + uint16_t audio_control, enum AVCodecID *p_codec_id, int
> *p_startcode,
> > + int *p_sample_rate, int *p_sample_bits,
> > + int *p_nb_channels, char **lang_iso639__2)
> > +{
> > + enum AVCodecID codec_id = AV_CODEC_ID_NONE;
> > + int startcode = 0;
> > + int sample_rate = 0;
> > + int sample_bits = 0;
> > + int nb_channels = 0;
> > +
> > + int position = (audio_control & 0x7F00) >> 8;
> > +
> > + switch (audio_attr.audio_format) {
> > + case DVDVIDEO_VTS_AUDIO_FORMAT_AC3:
> > + codec_id = AV_CODEC_ID_AC3;
> > + sample_rate = 48000;
> > + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_AC3 + position;
> > + break;
> > + case DVDVIDEO_VTS_AUDIO_FORMAT_MP1:
> > + codec_id = AV_CODEC_ID_MP1;
> > + sample_rate = 48000;
> > + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP1 + position;
> > + break;
> > + case DVDVIDEO_VTS_AUDIO_FORMAT_MP2:
> > + codec_id = AV_CODEC_ID_MP2;
> > + sample_rate = 48000;
> > + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP2 + position;
> > + break;
> > + case DVDVIDEO_VTS_AUDIO_FORMAT_PCM:
> > + codec_id = AV_CODEC_ID_PCM_DVD;
> > +
> > + switch (audio_attr.sample_frequency) {
> > + case DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_48K:
> > + sample_rate = 48000;
> > + break;
> > + case DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_96K:
> > + sample_rate = 96000;
> > + break;
> > + }
> > +
> > + switch (audio_attr.quantization) {
> > + case DVDVIDEO_VTS_AUDIO_QUANTIZATION_16:
> > + sample_bits = 16;
> > + break;
> > + case DVDVIDEO_VTS_AUDIO_QUANTIZATION_20:
> > + sample_bits = 20;
> > + break;
> > + case DVDVIDEO_VTS_AUDIO_QUANTIZATION_24:
> > + sample_bits = 24;
> > + break;
> > + }
> > +
> > + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_PCM + position;
> > + break;
> > + case DVDVIDEO_VTS_AUDIO_FORMAT_DTS:
> > + codec_id = AV_CODEC_ID_DTS;
> > + sample_rate = 48000;
> > + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_DTS + position;
> > + break;
> > + }
> > +
> > + nb_channels = audio_attr.channels + 1;
> > +
> > + if (codec_id == AV_CODEC_ID_NONE || startcode == 0
> > + || sample_rate == 0 || nb_channels == 0) {
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + (*p_codec_id) = codec_id;
> > + (*p_startcode) = startcode;
> > + (*p_sample_rate) = sample_rate;
> > + (*p_sample_bits) = sample_bits;
> > + (*p_nb_channels) = nb_channels;
> > + (*lang_iso639__2) =
> dvdvideo_lang_code_to_iso639__2(audio_attr.lang_code);
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_audio_stream_to_avstream(AVFormatContext *s,
> > + enum AVCodecID codec_id, int startcode, int sample_rate,
> > + int sample_bits, int nb_channels, char *lang_iso639__2,
> > + enum AVStreamParseType need_parsing)
> > +{
> > + AVStream *st;
> > + FFStream *sti;
> > +
> > + st = avformat_new_stream(s, NULL);
> > + if (!st) {
> > + return AVERROR(ENOMEM);
> > + }
> > +
> > + st->id = startcode;
> > + st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
> > + st->codecpar->codec_id = codec_id;
> > + st->codecpar->sample_rate = sample_rate;
> > + st->codecpar->ch_layout.nb_channels = nb_channels;
> > +
> > + if (sample_bits > 0) {
> > + st->codecpar->format = sample_bits == 16 ?
> > + AV_SAMPLE_FMT_S16
> > + : AV_SAMPLE_FMT_S32;
> > + st->codecpar->bits_per_coded_sample = sample_bits;
> > + st->codecpar->bits_per_raw_sample = sample_bits;
> > + }
> > +
> > + if (lang_iso639__2) {
> > + av_dict_set(&st->metadata, "language", lang_iso639__2, 0);
> > + }
> > +
> > + avpriv_set_pts_info(st, DVDVIDEO_PTS_WRAP_BITS,
> > + DVDVIDEO_TIME_BASE_Q.num, DVDVIDEO_TIME_BASE_Q.den);
> > +
> > + sti = ffstream(st);
> > + sti->request_probe = 0;
> > + sti->need_parsing = need_parsing;
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_audio_stream_register_all(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + for (int i = 0; i <
> c->play_vts_ifo->vtsi_mat->nr_of_vts_audio_streams; i++) {
> > + enum AVCodecID codec_id;
> > + int startcode;
> > + int sample_rate;
> > + int sample_bits;
> > + int nb_channels;
> > + char *lang_iso639__2;
> > + int ret;
> > +
> > + if (!(c->play_pgc->audio_control[i]
> > + & DVDVIDEO_AUDIO_CONTROL_ENABLED_MASK)) {
> > + continue;
> > + }
> > +
> > + if ((ret = dvdvideo_audio_stream_analyze(
> > + c->play_vts_ifo->vtsi_mat->vts_audio_attr[i],
> > + c->play_pgc->audio_control[i], &codec_id, &startcode,
> > + &sample_rate, &sample_bits, &nb_channels,
> > + &lang_iso639__2)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Invalid audio parameters in VTS
> IFO\n");
> > +
> > + return ret;
> > + }
> > +
> > + if (c->play_vts_ifo->vtsi_mat->
> > + vts_audio_attr[i].application_mode == 1) {
> > + av_log(s, AV_LOG_ERROR,
> > + "Audio stream uses karaoke extension which is
> unsupported\n");
> > +
> > + return AVERROR_PATCHWELCOME;
> > + }
> > +
> > + for (int j = 0; j < s->nb_streams; j++) {
> > + if (s->streams[j]->id == startcode) {
> > + av_log(s, AV_LOG_WARNING,
> > + "Audio stream has duplicate entry in VTS
> IFO\n");
> > +
> > + continue;
> > + }
> > + }
> > +
> > + if ((ret = dvdvideo_audio_stream_to_avstream(c->mpeg_ctx,
> codec_id,
> > + startcode, sample_rate, sample_bits, nb_channels,
> > + lang_iso639__2, AVSTREAM_PARSE_FULL)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Unable to allocate video stream
> (subdemux)\n");
> > +
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_audio_stream_to_avstream(s, codec_id,
> > + startcode, sample_rate, sample_bits, nb_channels,
> > + lang_iso639__2, AVSTREAM_PARSE_NONE)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Unable to allocate audio
> stream\n");
> > +
> > + return ret;
> > + }
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_subtitle_clut_rgb_extradata_cat(
> > + const uint32_t *clut_rgb, size_t clut_rgb_size,
> > + char *dst, size_t dst_size)
> > +{
> > + if (dst_size < DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE) {
> > + return AVERROR(ENOMEM);This is the error code for an allocation
> failure, which is not what is
> happening here. It's an illegal argument passed to the function.
>
> > + }
> > +
> > + if (clut_rgb_size != DVDVIDEO_SUBP_CLUT_SIZE) {
> > + return AVERROR(EINVAL);
> > + }
> > +
> > + snprintf(dst, dst_size, "palette: ");
> > +
> > + for (int i = 0; i < DVDVIDEO_SUBP_CLUT_LEN; i++) {
> > + av_strlcatf(dst, dst_size,
> > + "%06"PRIx32"%s", clut_rgb[i], i != 15 ? ", " : "");
> > + }
> > +
> > + av_strlcat(dst, "\n", 1);
> > +
> You should be using the AVBPrint api here to construct a string, not
> av_strlcat.
>
>
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_subtitle_clut_yuv_to_rgb(uint32_t *clut,
> > + const size_t clut_size)
> What is a yuv-to-rgb function doing inside a dvd-video demuxer?
>
> > +{
> > + const uint8_t *cm = dvdvideo_yuv_crop_tab +
> DVDVIDEO_YUV_NEG_CROP_MAX;
> > +
> > + int i, y, cb, cr;
> > + uint8_t r, g, b;
> > + int r_add, g_add, b_add;
> > +
> > + if (clut_size != DVDVIDEO_SUBP_CLUT_SIZE) {Why even have an
> argument to the function if it must equal some exact
> integer? At that point just remove clut_size from the function argument
> and just use DVDVIDEO_SUBP_CLUT_SIZE in its place.
>
>
> > + return AVERROR(EINVAL);
> > + }
> > +
> > + for (i = 0; i < DVDVIDEO_SUBP_CLUT_LEN; i++) {
> > + y = (clut[i] >> 16) & 0xFF;
> > + cr = (clut[i] >> 8) & 0xFF;
> > + cb = clut[i] & 0xFF;
> > +
> > + YUV_TO_RGB1_CCIR(cb, cr);
> > + YUV_TO_RGB2_CCIR(r, g, b, y);
> > +
> > + clut[i] = (r << 16) | (g << 8) | b;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_subtitle_stream_to_avstream(AVFormatContext *s,
> > + int startcode,
> > + char *clut_rgb_extradata_buf, int clut_rgb_extradata_buf_size,
> > + char *lang_iso639__2, enum AVStreamParseType need_parsing)
> > +{
> > + AVStream *st;
> > + FFStream *sti;
> > + int ret;
> > +
> > + st = avformat_new_stream(s, NULL);
> > + if (!st) {
> > + return AVERROR(ENOMEM);
> > + }
> > +
> > + st->id = startcode;
> > + st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
> > + st->codecpar->codec_id = AV_CODEC_ID_DVD_SUBTITLE;
> > +
> > + if ((ret = ff_alloc_extradata(st->codecpar,
> > + clut_rgb_extradata_buf_size)) != 0) {
> > + return ret;
> > + }
> > +
> > + memcpy(st->codecpar->extradata, clut_rgb_extradata_buf,
> > + st->codecpar->extradata_size);
> > +
> > + if (lang_iso639__2) {
> > + av_dict_set(&st->metadata, "language", lang_iso639__2, 0);
> > + }
> > +
> > + avpriv_set_pts_info(st, DVDVIDEO_PTS_WRAP_BITS,
> > + DVDVIDEO_TIME_BASE_Q.num, DVDVIDEO_TIME_BASE_Q.den);
> > +
> > + sti = ffstream(st);
> > + sti->request_probe = 0;
> > + sti->need_parsing = need_parsing;
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_subtitle_stream_register(AVFormatContext *s,
> > + int position,
> > + char *clut_rgb_extradata_buf, int clut_rgb_extradata_buf_size,
> > + char *lang_iso639__2)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + int ret;
> > +
> > + int startcode = DVDVIDEO_STARTCODE_OFFSET_SUBP + position;
> > +
> > + for (int i = 0; i < s->nb_streams; i++) {
> > + /* don't need to warn the user about this, since params won't
> change */
> > + if (s->streams[i]->id == startcode) {
> > + av_log(s, AV_LOG_TRACE,
> > + "Subtitle stream has duplicate entry in VTS IFO\n");
> > + return 0;
> > + }
> > + }
> > +
> > + if ((ret = dvdvideo_subtitle_stream_to_avstream(c->mpeg_ctx,
> startcode,
> > + clut_rgb_extradata_buf, clut_rgb_extradata_buf_size,
> > + lang_iso639__2, AVSTREAM_PARSE_FULL)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Unable to allocate subtitle stream
> (subdemux)\n");
> > +
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_subtitle_stream_to_avstream(s, startcode,
> > + clut_rgb_extradata_buf, clut_rgb_extradata_buf_size,
> > + lang_iso639__2, AVSTREAM_PARSE_NONE)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Unable to allocate subtitle stream\n");
> > +
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_subtitle_stream_register_all(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + int ret;
> > + uint32_t clut_rgb[DVDVIDEO_SUBP_CLUT_SIZE] = {0};
> > + char clut_rgb_extradata_buf[DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE]
> = {0};
> > +
> > + if (c->play_vts_ifo->vtsi_mat->nr_of_vts_subp_streams < 1) {
> > + return 0;
> > + }
> > +
> > + /* initialize the palette (same for all streams in this PGC) */
> > + memcpy(clut_rgb, c->play_pgc->palette, DVDVIDEO_SUBP_CLUT_SIZE);
> > +
> > + if ((ret = dvdvideo_subtitle_clut_yuv_to_rgb(
> > + clut_rgb, DVDVIDEO_SUBP_CLUT_SIZE)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Unable to convert subtitle
> palette\n");Why are you converting the subtitle palette?
>
> > +
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_subtitle_clut_rgb_extradata_cat(
> > + clut_rgb, DVDVIDEO_SUBP_CLUT_SIZE,
> > + clut_rgb_extradata_buf,
> DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Unable to set up subtitle
> extradata\n");
> > +
> > + return ret;
> > + }
> > +
> > + for (int i = 0; i < c->play_vts_ifo->
> > + vtsi_mat->nr_of_vts_subp_streams; i++) {
> > + uint32_t subp_control;
> > + subp_attr_t subp_attr;
> > + video_attr_t video_attr;
> > + char *lang_iso639__2;
> > +
> > + subp_control = c->play_pgc->subp_control[i];
> > +
> > + if (!(subp_control & DVDVIDEO_SUBP_CONTROL_ENABLED_MASK)) {
> > + continue;
> > + }
> > +
> > + subp_attr = c->play_vts_ifo->vtsi_mat->vts_subp_attr[i];
> > + video_attr = c->play_vts_ifo->vtsi_mat->vts_video_attr;
> > + lang_iso639__2 =
> dvdvideo_lang_code_to_iso639__2(subp_attr.lang_code);
> > +
> > + /* there can be several presentations for one SPU */
> > + /* for now, be flexible with the DAR check due to weird
> authoring */
> > + if (video_attr.display_aspect_ratio > 0) {
> > + /* 16:9 */
> > + ret = dvdvideo_subtitle_stream_register(s,
> > + ((subp_control >> 16) & 0x1F),
> > + clut_rgb_extradata_buf,
> > + DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
> > + lang_iso639__2);
> > + if (ret != 0) {
> > + return ret;
> > + }
> > +
> > + if (video_attr.permitted_df ==
> DVDVIDEO_VTS_SPU_PFD_LETTERBOX_ONLY
> > + || video_attr.permitted_df ==
> DVDVIDEO_VTS_SPU_PFD_ANY) {
> > + ret = dvdvideo_subtitle_stream_register(s,
> > + ((subp_control >> 8) & 0x1F),
> > + clut_rgb_extradata_buf,
> > + DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
> > + lang_iso639__2);
> > + if (ret != 0) {
> > + return ret;
> > + }
> > + }
> > +
> > + if (video_attr.permitted_df ==
> DVDVIDEO_VTS_SPU_PFD_PANSCAN_ONLY
> > + || video_attr.permitted_df ==
> DVDVIDEO_VTS_SPU_PFD_ANY) {
> > + ret = dvdvideo_subtitle_stream_register(s,
> > + (subp_control & 0x1F),
> > + clut_rgb_extradata_buf,
> > + DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
> > + lang_iso639__2);
> > + if (ret != 0) {
> > + return ret;
> > + }
> > + }
> > + } else {
> > + /* 4:3 */
> > + ret = dvdvideo_subtitle_stream_register(s,
> > + ((subp_control >> 24) & 0x1F),
> > + clut_rgb_extradata_buf,
> > + DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
> > + lang_iso639__2);
> > + if (ret != 0) {
> > + return ret;
> > + }
> > + }
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_subdemux_nested_io_open(AVFormatContext *s,
> > + AVIOContext **pb, const char *url, int flags, AVDictionary
> **opts)
> > +{
> This function is pointless.
>
> > + av_log(s, AV_LOG_ERROR, "Nested io_open not supported for this
> format\n");
> > +
> > + return AVERROR(EPERM);
> > +}
> > +
> > +static int dvdvideo_subdemux_read_data(void *opaque, uint8_t *buf,
> > + int buf_size)
> > +{
> > + AVFormatContext *s = opaque;
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + 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_PS_MAX_SEARCH_BLOCKS; i++) {
> > + int nav_event;
> > + int nav_len;
> > +
> > + int check_title;
> > + int check_pgcn;
> > + int check_pgn;
> > + int check_angle;
> > + int check_nb_angles;
> > +
> > + uint8_t nav_buf[DVDVIDEO_BLOCK_SIZE] = {0};
> > +
> > + if (ff_check_interrupt(&s->interrupt_callback)) {
> > + return AVERROR_EXIT;
> > + }
> > +
> > + if (dvdnav_get_next_block(c->play_dvdnav,
> > + nav_buf, &nav_event, &nav_len) != DVDNAV_STATUS_OK) {
> > + av_log(s, AV_LOG_ERROR, "Error reading block\n");
> > +
> > + goto end_dvdnav_error;
> > + }
> > +
> > + if (nav_len > DVDVIDEO_BLOCK_SIZE) {
> > + av_log(s, AV_LOG_ERROR, "Invalid block (too big)\n");
> > +
> This is not what ENOMEM means.
>
> > + return AVERROR(ENOMEM);
> > + }
> > +
> > + if (nav_event != DVDNAV_BLOCK_OK && nav_event !=
> DVDNAV_NAV_PACKET) {
> > + av_log(s, AV_LOG_TRACE, "nav event=%d len=%d\n", nav_event,
> nav_len);
> > + }
> > +
> > + if (dvdnav_current_title_program(c->play_dvdnav, &check_title,
> > + &check_pgcn, &check_pgn) != DVDNAV_STATUS_OK) {
> > + goto end_dvdnav_error;
> > + }
> > +
> > + if (check_title != c->opt_title || check_pgcn != c->play_pgcn
> > + || !dvdnav_is_domain_vts(c->play_dvdnav)) {
> > + return AVERROR_EOF;
> > + }
> > +
> > + if (dvdnav_get_angle_info(c->play_dvdnav, &check_angle,
> > + &check_nb_angles) != DVDNAV_STATUS_OK) {
> > + goto end_dvdnav_error;
> > + }
> > +
> > + if (check_angle != c->opt_angle) {
> > + av_log(s, AV_LOG_ERROR, "Unexpected angle change\n");
> > +
> > + return AVERROR_INPUT_CHANGED;
> > + }
> > +
> > + switch (nav_event) {
> > + case DVDNAV_BLOCK_OK:
> > + if (nav_len != DVDVIDEO_BLOCK_SIZE) {
> > + av_log(s, AV_LOG_ERROR, "Invalid block\n");
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + memcpy(buf, &nav_buf, nav_len);
> > +
> > + return nav_len;
> > + case DVDNAV_HIGHLIGHT:
> > + case DVDNAV_VTS_CHANGE:
> > + case DVDNAV_STILL_FRAME:
> > + case DVDNAV_STOP:
> > + case DVDNAV_WAIT:
> > + return AVERROR_EOF;
> > + default:
> > + continue;
> > + }
> > + }
> > +
> > + av_log(s, AV_LOG_ERROR, "Unable to find next MPEG block\n");
> > +
> > + return AVERROR_UNKNOWN;
> > +
> > +end_dvdnav_error:
> > + av_log(s, AV_LOG_ERROR, "libdvdnav: %s\n",
> dvdnav_err_to_string(c->play_dvdnav));
> > +
> > + return AVERROR_EXTERNAL;
> > +}
> > +
> > +static int64_t dvdvideo_subdemux_seek_data(void *opaque, int64_t offset,
> > + int whence)
> > +{
> > + AVFormatContext *s = opaque;
> > +
> > + av_log(s, AV_LOG_ERROR,
> > + "dvdvideo_subdemux_seek_data(): not implemented\n");
> > +
> > + return AVERROR_PATCHWELCOME;
> > +}
> > +
> > +static int dvdvideo_subdemux_open(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + int ret;
> > +
> > + const AVInputFormat *mpeg_fmt;
> > + AVFormatContext *mpeg_ctx;
> > + uint8_t *mpeg_buf;
> > +
> > + 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);
> > + }
> Why are you doing this instead of just declaring it to be a protocol
> that produces a mpeg-ps stream? That way it stops being your
> responsibility.
>
>
> > +
> > + ffio_init_context(&c->mpeg_pb, mpeg_buf, DVDVIDEO_BLOCK_SIZE, 0,
> > + s, dvdvideo_subdemux_read_data, NULL,
> dvdvideo_subdemux_seek_data);
> > + c->mpeg_pb.pub.seekable = 0;
> > +
> > + if ((ret = ff_copy_whiteblacklists(mpeg_ctx, s)) != 0) {
> > + avformat_free_context(mpeg_ctx);
> > +
> > + return ret;
> > + }
> > +
> > + /* TODO: fix the PTS issues (ask about this in ML) */
> > + /* AVFMT_FLAG_GENPTS works in some scenarios, but in others, the
> reading process hangs */
> > + mpeg_ctx->flags = AVFMT_FLAG_CUSTOM_IO | AVFMT_FLAG_GENPTS;
> MPEG-PS contains pts info, why not just use that? Not amazing PTS info,
> but usable PTS info.
>
> > +
> > + 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 = dvdvideo_subdemux_nested_io_open;
> > +
> > + 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 void dvdvideo_subdemux_close(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + av_freep(&c->mpeg_pb.pub.buffer);
> > + memset(&c->mpeg_pb, 0x00, sizeof(c->mpeg_pb));
> > + av_freep(&c->mpeg_pb);Why are you zeroing it before you free it? If
> this is a security issue
> you can't use memset anyway as the compiler may throw it away. That's
> why explicit_bzero was created.
>
> > +
> > + avformat_close_input(&c->mpeg_ctx);
> > +}
> > +
> > +static int dvdvideo_chapters_register(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > + int start_time = 0;
> > + int cell = 0;
> > +
> > + for (int i = 0; i < c->play_pgc->nr_of_programs; i++) {
> > + int64_t ms = 0;
> > + int next = c->play_pgc->program_map[i + 1];
> In the last iteration of the loop, will this possibly give you an
> out-of-bounds read?
>
> > +
> > + if (i == c->play_pgc->nr_of_programs - 1) {
> > + next = c->play_pgc->nr_of_cells + 1;
> > + }
> > +
> > + while (cell < next - 1) {
> > + /* only consider first cell of multi-angle cells */
> > + if (c->play_pgc->cell_playback[cell].block_mode <= 1) {
> > + ms = ms + dvdvideo_time_to_millis(
> > +
> &c->play_pgc->cell_playback[cell].playback_time);
> > + }
> > + cell++;
> > + }
> > +
> > + if (!avpriv_new_chapter(s, i, DVDVIDEO_TIME_BASE_MILLIS_Q,
> > + start_time, start_time + ms, NULL)) {
> > + av_log(s, AV_LOG_ERROR, "Unable to allocate chapter");
> > + return AVERROR(ENOMEM);
> > + }
> > +
> > + start_time += ms;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_read_header(AVFormatContext *s)
> > +{
> > + int ret;
> > +
> > + if ((ret = dvdvideo_volume_open(s)) != 0) {
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_playback_open(s)) != 0) {
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_subdemux_open(s)) != 0) {
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_video_stream_register(s)) != 0) {
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_audio_stream_register_all(s)) != 0) {
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_subtitle_stream_register_all(s)) != 0) {
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_chapters_register(s)) != 0) {
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_read_packet(AVFormatContext *s, AVPacket *pkt)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + while (!ff_check_interrupt(&s->interrupt_callback)) {
> > + int subdemux_ret;
> > + AVPacket *subdemux_pkt;
> > +
> > + subdemux_pkt = av_packet_alloc();
> > + if (!subdemux_pkt) {
> > + return AVERROR(ENOMEM);
> > + }
> > +
> > + subdemux_ret = av_read_frame(c->mpeg_ctx, subdemux_pkt);
> > + if (subdemux_ret >= 0) {
> > + if (c->mpeg_ctx->nb_streams != s->nb_streams) {
> > + av_log(s, AV_LOG_ERROR, "Unexpected stream during
> playback\n");
> > +
> > + av_packet_unref(subdemux_pkt);
> > +
> > + return AVERROR_INPUT_CHANGED;
> > + }
> > +
> > + av_packet_move_ref(pkt, subdemux_pkt);
> This seems like an unnecessary memcpy.
>
> > +
> > + return 0;
> > + }
> > +
> > + return subdemux_ret;
> > + }
> > +
> > + return AVERROR_EOF;
> > +}
> > +
> > +static int dvdvideo_read_seek(AVFormatContext *s, int stream_index,
> > + int64_t timestamp, int flags)
> > +{
> > + av_log(s, AV_LOG_ERROR, "dvdvideo_read_seek(): not implemented\n");
> > +
> > + return AVERROR_PATCHWELCOME;
> > +}
> > +
> > +static int dvdvideo_close(AVFormatContext *s)
> > +{
> > + dvdvideo_subdemux_close(s);
> > + dvdvideo_playback_close(s);
> > + dvdvideo_volume_close(s);
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_probe(const AVProbeData *p)
> > +{
> > + av_log(NULL, AV_LOG_ERROR, "dvdvideo_probe(): not implemented\n");
> > +
> > + return AVERROR_PATCHWELCOME;
> > +}
> > +
> > +#define OFFSET(x) offsetof(DVDVideoDemuxContext, x)
> > +static const AVOption dvdvideo_options[] = {
> > + {"title", "Title Number", OFFSET(opt_title),
> AV_OPT_TYPE_INT, { .i64=1 }, 1,
> DVDVIDEO_TITLE_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
> > + {"ptt", "Entry PTT Number", OFFSET(opt_ptt),
> AV_OPT_TYPE_INT, { .i64=1 }, 1,
> DVDVIDEO_PTT_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
> > + {"pgc", "Entry PGC Number (0=auto)", OFFSET(opt_pgc),
> AV_OPT_TYPE_INT, { .i64=0 }, 0,
> DVDVIDEO_PGC_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
> > + {"pg", "Entry PG Number (0=auto)", OFFSET(opt_pg),
> AV_OPT_TYPE_INT, { .i64=0 }, 0,
> DVDVIDEO_PG_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
> > + {"angle", "Video Angle Number", OFFSET(opt_angle),
> AV_OPT_TYPE_INT, { .i64=1 }, 1,
> DVDVIDEO_ANGLE_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
> > + {"region", "Playback Region Number (0=free)",
> OFFSET(opt_region), AV_OPT_TYPE_INT, { .i64=0 }, 0,
> DVDVIDEO_REGION_NUMBER_MAX, 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_NO_BYTE_SEEK |
> AVFMT_NOGENSEARCH | AVFMT_NOBINSEARCH | AVFMT_SHOW_IDS | AVFMT_TS_DISCONT,
> > + .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
> > +};
>
> There's a number of other issues and I didn't write each one of each
> type, but my biggest question is why this is not a protocol that
> produces an mpeg-ps stream.
>
> - Leo Izen (Traneptora)
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH] [WIP] [RFC] dvdvideo: initial contribution (DVD demuxer)
2023-12-10 3:03 ` Leo Izen
2023-12-10 3:16 ` Marth64
@ 2023-12-10 3:47 ` Marth64
1 sibling, 0 replies; 27+ messages in thread
From: Marth64 @ 2023-12-10 3:47 UTC (permalink / raw)
To: FFmpeg development discussions and patches
> What is a yuv-to-rgb function doing inside a dvd-video demuxer?
DVD subtitle palette is YUV. However, when stored in modern formats such as
matroska or MP4, the "convention" is to store in RGB.
In fact, mp4/mov muxer has a similar routine.
> This function is pointless.
I'm opening the MPEG-PS subdemuxer with custom IO flag. It seems to expect
this according to the documentation. I can try without.
On Sat, Dec 9, 2023 at 9:03 PM Leo Izen <leo.izen@gmail.com> wrote:
> On 12/9/23 05:06, Marth64 wrote:
> > Hello, I am happy to share a DVD demuxer for ffmpeg powered by
> libdvdread and libdvdnav.
> > I have been working on this on/off throughout the year and think it is
> in a good spot
> > to share at the ML now. This was a major learning experience for me in
> many ways and
> > am open to any feedback on how I could improve it to be better. In fact,
> there is still
> > one issue I can't seem to figure out how to solve (in discussion below).
> Regardless,
> > a good number of DVDs I have tried seem to work out of the box. This is
> a full-service
> > demuxer with chapters, subtitles (and their palettes), as well as
> language metadata.
> >
> > At a high level, the demuxer uses libdvdread for metadata of the disc,
> libdvdnav for
> > the actual playback, and the MPEG-PS demuxer for the underlying VOB
> stream.
> >
> > First, the basic usage, is quite straightforward:
> > ffmpeg -f dvdvideo -title NN -i DVD.iso|/dev/srX|/path/to/DVD ...
> > Where NN can be replaced by your known title number in the disc
> >
> > As the demuxer effectively works at a PGC level, multi-PGC titles are
> not supported.
> > But to provide this flexibility as there are many weirdly authored DVDs
> out there,
> > one can specify an exact PGC via -pgc option and an associated program
> (PG, can start at 1).
> >
> > I am hoping and willing to improve this to be a robust demuxer wherever
> possible, but to
> > that extent there is still an issue I can't figure out how to solve:
> Dealing with DTS discontinuities
> > and PTS generation.
> >
> > Right now, as a band-aid, I am adding AVFMT_FLAG_GENPTS (line 1408)
> > to the MPEG-PS subdemuxer. This works with most discs and the output
> seems OK. On discs with discontinuities, however,
> > this causes the demuxing to hang which is obviously unacceptable also.
> Removing the flag causes the discs
> > to not hang, but then the output for all discs becomes choppy for
> obvious reasons (invalid PTS).
> >
> > It could be because I have some misunderstandings on how things work
> within ffmpeg,
> > but I have tried to the point now where I thought it best to ask the
> experts for help if you can spot
> > what I am doing wrong. I am really motivated to make this work and good
> quality.
> >
> > Thank you!
> >
> > ---
> > configure | 8 +
> > libavformat/Makefile | 1 +
> > libavformat/allformats.c | 1 +
> > libavformat/avlanguage.c | 10 +-
> > libavformat/dvdvideodec.c | 1599 +++++++++++++++++++++++++++++++++++++
> > 5 files changed, 1617 insertions(+), 2 deletions(-)
> > create mode 100644 libavformat/dvdvideodec.c
> >
> > diff --git a/configure b/configure
> > index d77c053226..65e5968194 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]
>
> Why do you need both of these? libdvdnav depends on libdvdread.
>
> > --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]
> > @@ -1802,6 +1804,8 @@ EXTERNAL_LIBRARY_GPL_LIST="
> > frei0r
> > libcdio
> > libdavs2
> > + libdvdnav
> > + libdvdread
> > librubberband
> > libvidstab
> > libx264
> > @@ -3494,6 +3498,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"
> > @@ -6723,6 +6729,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 && require_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 2db83aff81..45dba53044 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 c8bb4e3866..dc2acf575c 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..2b22a7de4f
> > --- /dev/null
> > +++ b/libavformat/dvdvideodec.c
> > @@ -0,0 +1,1599 @@
> > +/*
> > + * DVD-Video demuxer (powered by libdvdnav/libdvdread)
> > + *
> > + * 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
> > + * 7) Close resources
> > + **/
> > +
> > +/**
> > + * Issues/bug tracker (TODO):
> > + * - SDDS is not supported yet
> > + * - Are we handling backwards cell changes and PTT changes correctly?
> > + * - Some codec parameters/metadata is not being explicitly set:
> > + * -> ChannelLayout, color/chroma info, DAR, frame size for
> MP1/MP2 audio
> > + * - Additional PGC validations?
> > +**/
> > +
> > +#include <dvdread/dvd_reader.h>
> > +#include <dvdread/ifo_read.h>
> > +#include <dvdread/ifo_types.h>
> > +#include <dvdread/nav_read.h>
> > +#include <dvdnav/dvdnav.h>
> > +
> > +#include "libavutil/avstring.h"
> > +#include "libavutil/avutil.h"
> > +#include "libavutil/colorspace.h"
> > +#include "libavutil/mem.h"
> > +#include "libavutil/opt.h"
> > +#include "libavutil/samplefmt.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 ZERO_Q (AVRational) {
> 0, 1 }
> Unnecessary #define.
>
> > +
> > +#define DVDVIDEO_PS_MAX_SEARCH_BLOCKS 128
> > +#define DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE 1024
> > +
> > +#define DVDVIDEO_TIME_BASE_Q (AVRational) {
> 1, 90000 }
> > +#define DVDVIDEO_TIME_BASE_MILLIS_Q (AVRational) {
> 1, 1000 }
> > +#define DVDVIDEO_PTS_WRAP_BITS 32
> > +#define DVDVIDEO_BLOCK_SIZE 2048
> > +
> > +#define DVDVIDEO_NTSC_FRAMERATE_Q (AVRational) {
> 30000, 1001 }
> > +#define DVDVIDEO_NTSC_HEIGHT 480
> > +#define DVDVIDEO_PAL_FRAMERATE_Q (AVRational) {
> 25, 1 }
> > +#define DVDVIDEO_PAL_HEIGHT 576
> > +#define DVDVIDEO_D1_WIDTH 720
> > +#define DVDVIDEO_4CIF_WIDTH 704
> > +#define DVDVIDEO_D1_HALF_WIDTH 352
> > +#define DVDVIDEO_CIF_WIDTH 352
> > +#define DVDVIDEO_PIXEL_FORMAT
> AV_PIX_FMT_YUV420P
> > +
> > +#define DVDVIDEO_STARTCODE_VIDEO 0x1E0
> > +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_AC3 0x80
> > +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP1 0x1C0
> > +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP2 0x1C0
> > +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_PCM 0xA0
> > +#define DVDVIDEO_STARTCODE_OFFSET_AUDIO_DTS 0x88
> > +#define DVDVIDEO_STARTCODE_OFFSET_SUBP 0x20
> > +
> > +#define DVDVIDEO_TITLE_NUMBER_MAX 99
> > +#define DVDVIDEO_PTT_NUMBER_MAX 99
> > +#define DVDVIDEO_PGC_NUMBER_MAX 32767
> > +#define DVDVIDEO_PG_NUMBER_MAX 255
> > +#define DVDVIDEO_ANGLE_NUMBER_MAX 9
> > +#define DVDVIDEO_VTS_NUMBER_MAX 99
> > +#define DVDVIDEO_TT_NUMBER_MAX 99
> > +#define DVDVIDEO_REGION_NUMBER_MAX 8
> > +#define DVDVIDEO_AUDIO_CONTROL_ENABLED_MASK 0x8000
> > +#define DVDVIDEO_SUBP_CONTROL_ENABLED_MASK 0x80000000
> > +#define DVDVIDEO_VTS_AUDIO_STREAMS_MAX 8
> > +#define DVDVIDEO_VTS_SUBP_STREAMS_MAX 32
> > +
> > +/* ("palette: ") + ("rrggbb, "*15) + ("rrggbb") + \n + \0 */
> > +#define DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE (9 + (8 * 15) +
> 6 + 1 + 1)
> > +#define DVDVIDEO_SUBP_CLUT_LEN 16
> > +#define DVDVIDEO_SUBP_CLUT_SIZE
> DVDVIDEO_SUBP_CLUT_LEN * sizeof(uint32_t)
> > +
> > +/* convert binary-coded decimal to decimal */
> > +#define BCD2D(__x__) (((__x__ & 0xF0) >> 4) * 10 + (__x__ & 0x0F))
> > +
> > +/* crop table for YUV to RGB subpicture palette conversion */
> > +#define DVDVIDEO_YUV_NEG_CROP_MAX 1024
> > +#define times4(x) x, x, x, x
> > +#define times256(x) times4(times4(times4(times4(times4(x)))))
> > +
> > +const uint8_t dvdvideo_yuv_crop_tab[256 + 2 *
> DVDVIDEO_YUV_NEG_CROP_MAX] = {
> > +times256(0x00),
> >
> +0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
> >
> +0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
> >
> +0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
> >
> +0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
> >
> +0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
> >
> +0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,
> >
> +0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
> >
> +0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,
> >
> +0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
> >
> +0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
> >
> +0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
> >
> +0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
> >
> +0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
> >
> +0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
> >
> +0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,
> >
> +0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF,
> > +times256(0xFF)
> > +};
> > +
> > +enum DVDVideoVTSMPEGVersion {
> > + DVDVIDEO_VTS_MPEG_VERSION_MPEG1 = 0,
> > + DVDVIDEO_VTS_MPEG_VERSION_MPEG2 = 1
> > +};
> > +
> > +enum DVDVideoVTSPictureFormat {
> > + DVDVIDEO_VTS_PICTURE_FORMAT_NTSC = 0,
> > + DVDVIDEO_VTS_PICTURE_FORMAT_PAL = 1
> > +};
> > +
> > +enum DVDVideoVTSPictureSize {
> > + DVDVIDEO_VTS_PICTURE_SIZE_D1 = 0,
> > + DVDVIDEO_VTS_PICTURE_SIZE_4CIF = 1,
> > + DVDVIDEO_VTS_PICTURE_SIZE_D1_HALF = 2,
> > + DVDVIDEO_VTS_PICTURE_SIZE_CIF = 3
> > +};
> > +
> > +enum DVDVideoVTSPictureDAR {
> > + DVDVIDEO_VTS_PICTURE_DAR_4_3 = 0,
> > + DVDVIDEO_VTS_PICTURE_DAR_16_9 = 3
> > +};
> > +
> > +enum DVDVideoVTSPermittedFullscreenDisplay {
> > + DVDVIDEO_VTS_SPU_PFD_ANY = 0,
> > + DVDVIDEO_VTS_SPU_PFD_PANSCAN_ONLY = 1,
> > + DVDVIDEO_VTS_SPU_PFD_LETTERBOX_ONLY = 2
> > +};
> > +
> > +enum DVDVideoVTSAudioFormat {
> > + DVDVIDEO_VTS_AUDIO_FORMAT_AC3 = 0,
> > + DVDVIDEO_VTS_AUDIO_FORMAT_MP1 = 2,
> > + DVDVIDEO_VTS_AUDIO_FORMAT_MP2 = 3,
> > + DVDVIDEO_VTS_AUDIO_FORMAT_PCM = 4,
> > + DVDVIDEO_VTS_AUDIO_FORMAT_SDDS = 5,
> > + DVDVIDEO_VTS_AUDIO_FORMAT_DTS = 6
> > +};
> > +
> > +enum FFDVDVideoVTSAudioSampleRate {
> > + DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_48K = 0,
> > + DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_96K = 1
> > +};
> > +
> > +enum FFDVDVideoVTSAudioQuantization {
> > + DVDVIDEO_VTS_AUDIO_QUANTIZATION_16 = 0,
> > + DVDVIDEO_VTS_AUDIO_QUANTIZATION_20 = 1,
> > + DVDVIDEO_VTS_AUDIO_QUANTIZATION_24 = 2
> > +};
> > +
> > +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 identification 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 *vol_dvdread; /* handle
> to libdvdread */
> > + ifo_handle_t *vol_vmg_ifo; /* handle
> to the VMG (VIDEO_TS.IFO) */
> > +
> > + /* playback */
> > + ifo_handle_t *play_vts_ifo; /* handle
> to the active VTS (VTS_nn_n.IFO) */
> > + pgc_t *play_pgc; /* handle
> to the active PGC */
> > + int play_vtsn; /* number
> of the active VTS (video title set) */
> > + int play_pgcn; /* number
> of the active PGC (program chain) */
> > + dvdnav_t *play_dvdnav; /* handle
> to libdvdnav */
> > +} DVDVideoDemuxContext;
> > +
> > +static void dvdvideo_libdvdread_log(void *opaque, dvd_logger_level_t
> level,
> > + const char *msg, va_list msg_va)
> > +{
>
> This function is unnecessary. If you really need to intercept logged
> messages from libdvdnav and feed them to av_log, you should just have a
> lookup that maps the corresponding message level.
>
> > + AVFormatContext *s = opaque;
> > + int lavu_level;
> > +
> > + char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
> > + vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
> > +
> > + switch (level) {
> > + case DVD_LOGGER_LEVEL_ERROR:
> > + lavu_level = AV_LOG_ERROR;
> > + break;
> > + case DVD_LOGGER_LEVEL_WARN:
> > + lavu_level = AV_LOG_WARNING;
> > + break;
> > + case DVD_LOGGER_LEVEL_INFO:
> > + lavu_level = AV_LOG_INFO;
> > + break;
> > + case DVD_LOGGER_LEVEL_DEBUG:
> > + default:
> > + lavu_level = AV_LOG_DEBUG;
> > + }
> > +
> > + /* libdvdread messages don't have line terminators */
> > + av_log(s, lavu_level, "libdvdread: %s\n", msg_buf);
> > +}
> > +
> > +static void dvdvideo_libdvdnav_log(void *opaque, dvdnav_logger_level_t
> level,
> > + const char *msg, va_list msg_va)
> > +{
> > + AVFormatContext *s = opaque;
> Same complaint here. Also you have no to reassign opaque here.
>
> > + int lavu_level;
> > +
> > + char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
> > + vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
> > +
> > + switch (level) {
> > + case DVDNAV_LOGGER_LEVEL_ERROR:
> > + lavu_level = AV_LOG_ERROR;
> > + break;
> > + case DVDNAV_LOGGER_LEVEL_WARN:
> > + lavu_level = AV_LOG_WARNING;
> > + break;
> > + case DVDNAV_LOGGER_LEVEL_INFO:
> > + lavu_level = AV_LOG_INFO;
> > + break;
> > + case DVDNAV_LOGGER_LEVEL_DEBUG:
> > + default:
> > + lavu_level = AV_LOG_DEBUG;
> > + }
> > +
> > + /* libdvdnav messages don't have line terminators */
> > + av_log(s, lavu_level, "libdvdnav: %s\n", msg_buf);
> > +}
> > +
> > +static char *dvdvideo_lang_code_to_iso639__2(const uint16_t lang_code)
> > +{
> > + char lang_code_str[3] = {0};
> > +
> > + if (lang_code && lang_code != 0xFFFF) {
> > + lang_code_str[0] = (lang_code >> 8) & 0xFF;
> > + lang_code_str[1] = lang_code & 0xFF;
> > + }
> AV_WB16(lang_code_str, lang_code);
>
> > +
> > + return (char *) ff_convert_lang_to(lang_code_str,
> AV_LANG_ISO639_2_BIBL);
> > +}
> > +
> > +static int64_t dvdvideo_time_to_millis(dvd_time_t *time)
> > +{
> > + double fps;
> > + int64_t ms;
> > +
> > + int64_t sec = (int64_t) (BCD2D(time->hour)) * 60 * 60;
> > + sec += (int64_t) (BCD2D(time->minute)) * 60;
> > + sec += (int64_t) (BCD2D(time->second));
> > +
> > + /* the 2 high bits are the frame rate */
> > + switch ((time->frame_u & 0xC0) >> 6)
> > + {
> > + case 1:
> > + fps = 25.0;
> > + break;
> > + case 3:
> > + fps = 29.97;
> This is wrong, it's 30000/1001. Which you knew, because you #defined it
> above. Why not use those?
>
> > + break;
> > + default:
> > + fps = 2500.0;
> > + break;
> > + }
> > + ms = BCD2D(time->frame_u & 0x3F) * 1000.0 / fps;
> > +
> > + return (sec * 1000) + ms;
> > +}
> > +
> > +static int dvdvideo_volume_is_title_valid_in_context(AVFormatContext *s,
> > + int title)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + return title >= 1
> > + && title <= DVDVIDEO_TITLE_NUMBER_MAX
> > + && title <= c->vol_vmg_ifo->tt_srpt->nr_of_srpts
> > + && c->vol_vmg_ifo->tt_srpt->title[title - 1].nr_of_ptts > 0;
> > +}
> > +
> > +static int dvdvideo_volume_is_title_valid_in_vts(title_info_t
> title_info,
> > + ifo_handle_t *vts_ifo)
> > +{
> > + return title_info.vts_ttn >= 1
> > + && title_info.vts_ttn <= DVDVIDEO_TT_NUMBER_MAX
> > + && title_info.vts_ttn <= vts_ifo->vts_ptt_srpt->nr_of_srpts
> > + && vts_ifo->vtsi_mat->nr_of_vts_audio_streams
> > + <= DVDVIDEO_VTS_AUDIO_STREAMS_MAX
> > + && vts_ifo->vtsi_mat->nr_of_vts_subp_streams
> > + <= DVDVIDEO_VTS_SUBP_STREAMS_MAX;
> > +}
> > +
> > +static int dvdvideo_volume_is_angle_valid_in_title(int angle,
> > + title_info_t title_info)
> > +{
> > + return angle >= 1
> > + && angle <= DVDVIDEO_ANGLE_NUMBER_MAX
> > + && angle <= title_info.nr_of_angles;
> > +}
> > +
> > +static int dvdvideo_volume_is_pgc_valid_and_sequential(pgc_t *pgc)
> > +{
> > + return pgc
> > + && pgc->program_map
> > + && pgc->cell_playback != NULL
> > + && pgc->pg_playback_mode == 0
> > + && pgc->nr_of_programs > 0
> > + && pgc->nr_of_cells > 0;
> > +}
> > +
> > +static int dvdvideo_volume_is_pgcn_in_vts(int pgcn, ifo_handle_t
> *vts_ifo)
> > +{
> > + return pgcn >= 1
> > + && pgcn <= DVDVIDEO_PGC_NUMBER_MAX
> > + && pgcn <= vts_ifo->vts_pgcit->nr_of_pgci_srp;
> > +}
> > +
> > +static int dvdvideo_volume_is_pgn_in_pgc(int pgn, pgc_t *pgc)
> > +{
> > + return pgn >= 1
> > + && pgn <= DVDVIDEO_PG_NUMBER_MAX
> > + && pgn <= pgc->nr_of_programs;
> > +}
> > +
> > +static int dvdvideo_volume_open(AVFormatContext *s)
> av_cold
>
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + dvd_reader_t *dvdread;
> > + ifo_handle_t *vmg_ifo;
> > +
> > + dvd_logger_cb dvdread_log_cb = { .pf_log = dvdvideo_libdvdread_log
> };
> > + dvdread = DVDOpen2(s, &dvdread_log_cb, s->url);
> > +
> > + if (!dvdread) {
> > + av_log(s, AV_LOG_ERROR, "Unable to initialize libdvdread\n");
> > +
> > + return AVERROR_EXTERNAL;
> > + }
> > +
> > + if (!(vmg_ifo = ifoOpen(dvdread, 0))) {
> > + DVDClose(dvdread);
> > +
> > + av_log(s, AV_LOG_ERROR,
> > + "Unable to open VIDEO_TS.IFO. "
> > + "Input does not have a valid DVD-Video structure "
> > + "or is not a compliant UDF DVD-Video image.\n");
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + c->vol_dvdread = dvdread;
> > + c->vol_vmg_ifo = vmg_ifo;
> > +
> > + return 0;
> > +}
> > +
> > +static void dvdvideo_volume_close(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + if (c->vol_vmg_ifo) {
> > + ifoClose(c->vol_vmg_ifo);
> Can this fail?
>
> > + c->vol_vmg_ifo = NULL;
> > + }
> > +
> > + if (c->vol_dvdread) {
> > + DVDClose(c->vol_dvdread);
> Can this fail?
>
> > + c->vol_dvdread = NULL;
> > + }
> > +}
> > +
> > +static int dvdvideo_volume_open_vts_ifo(AVFormatContext *s, int vtsn,
> > + ifo_handle_t **p_vts_ifo)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + ifo_handle_t *vts_ifo;
> > +
> > + if (vtsn < 1
> > + || vtsn > DVDVIDEO_VTS_NUMBER_MAX
> > + || !(vts_ifo = ifoOpen(c->vol_dvdread, vtsn))) {
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + for (int i = 0; i < vts_ifo->vts_c_adt->nr_of_vobs; i++) {
> > + int start_sector =
> vts_ifo->vts_c_adt->cell_adr_table[i].start_sector;
> > + int end_sector =
> vts_ifo->vts_c_adt->cell_adr_table[i].last_sector;
> > +
> > + if ((start_sector & 0xFFFFFF) == 0xFFFFFF
> > + || (end_sector & 0xFFFFFF) == 0xFFFFFF
> > + || start_sector >= end_sector) {
> > + ifoClose(vts_ifo);
> > +
> > + av_log(s, AV_LOG_WARNING, "VTS has invalid cell address
> table\n");
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > + }
> > +
> > + (*p_vts_ifo) = vts_ifo;
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_playback_open(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + int ret;
> > +
> > + ifo_handle_t *vts_ifo;
> > + dvdnav_logger_cb dvdnav_log_cb;
> > + dvdnav_status_t dvdnav_status;
> > + dvdnav_t *dvdnav;
> > +
> > + title_info_t title_info;
> > + int pgcn;
> > + int pgn;
> > + pgc_t *pgc;
> > +
> > + int32_t disc_region_mask;
> > + int32_t player_region_mask;
> > + int cell_search_has_vts = 0;
> > +
> > + /* if the title is valid, open its VTS IFO */
> > + if (!dvdvideo_volume_is_title_valid_in_context(s, c->opt_title)) {
> > + av_log(s, AV_LOG_ERROR, "Title not found or invalid\n");
> > +
> > + return AVERROR_STREAM_NOT_FOUND;
> > + }
> > +
> > + title_info = c->vol_vmg_ifo->tt_srpt->title[c->opt_title - 1];
> > +
> > + if ((ret = dvdvideo_volume_open_vts_ifo(s,
> > + title_info.title_set_nr, &vts_ifo)) != 0) {
> Our convention is to return negative values on error in FFmpeg. Not
> nonzero values.
>
> > + av_log(s, AV_LOG_ERROR, "Title VTS could not be opened\n");
> > +
> > + return ret;
> > + }
> > +
> > + if (!dvdvideo_volume_is_title_valid_in_vts(title_info, vts_ifo)) {
> > + av_log(s, AV_LOG_ERROR, "Title VTS is invalid\n");
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + if (!dvdvideo_volume_is_angle_valid_in_title(c->opt_angle,
> title_info)) {
> > + av_log(s, AV_LOG_ERROR, "Angle not found or invalid\n");
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + /* determine our PGC and PG (playback coordinates) */
> > + if (c->opt_pgc > 0 && c->opt_pg > 0) {
> > + if (c->opt_ptt > 0) {
> > + av_log(s, AV_LOG_WARNING,
> > + "PTT option ignored as PGC and PG are provided\n");
> > + }
> > +
> > + pgcn = c->opt_pgc;
> > + pgn = c->opt_pg;
> > + } else {
> > + if (c->opt_ptt > title_info.nr_of_ptts) {
> > + av_log(s, AV_LOG_ERROR, "PTT not found\n");
> > +
> > + return AVERROR_STREAM_NOT_FOUND;
> > + }
> > +
> > + pgcn = vts_ifo->vts_ptt_srpt->title[title_info.vts_ttn -
> 1].ptt[c->opt_ptt - 1].pgcn;
> > + pgn = vts_ifo->vts_ptt_srpt->title[title_info.vts_ttn -
> 1].ptt[c->opt_ptt - 1].pgn;
> > + }
> > +
> > + if (!dvdvideo_volume_is_pgcn_in_vts(pgcn, vts_ifo)) {
> > + av_log(s, AV_LOG_ERROR, "PGC not found\n");
> > +
> > + return AVERROR_STREAM_NOT_FOUND;
> > + }
> > +
> > + pgc = vts_ifo->vts_pgcit->pgci_srp[pgcn - 1].pgc;
> > +
> > + if (!dvdvideo_volume_is_pgc_valid_and_sequential(pgc)) {
> > + av_log(s, AV_LOG_ERROR, "PGC not valid\n");
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + if (!dvdvideo_volume_is_pgn_in_pgc(pgn, pgc)) {
> > + av_log(s, AV_LOG_ERROR, "PG not found\n");
> > +
> > + return AVERROR_STREAM_NOT_FOUND;
> > + }
> > +
> > + /* set up libdvdnav */
> > + dvdnav_log_cb = (dvdnav_logger_cb) { .pf_log =
> dvdvideo_libdvdnav_log };
> > + dvdnav_status = dvdnav_open2(&dvdnav, NULL, &dvdnav_log_cb, s->url);
> > +
> > + if (!dvdnav) {
> > + av_log(s, AV_LOG_ERROR, "Unable to initialize libdvdnav\n");
> > +
> > + return AVERROR_EXTERNAL;
> > + }
> > +
> > + if (dvdnav_status != DVDNAV_STATUS_OK) {
> Code style. We don't use braces with single-line blocks.
>
> > + goto end_search_error_dvdnav;
> > + }
> > +
> > + if (dvdnav_set_readahead_flag(dvdnav, 0) != DVDNAV_STATUS_OK) {
> > + goto end_search_error_dvdnav;
> > + }
> > +
> > + if (dvdnav_set_PGC_positioning_flag(dvdnav, 1) != DVDNAV_STATUS_OK)
> {
> > + goto end_search_error_dvdnav;
> > + }
> > +
> > + if (dvdnav_get_region_mask(dvdnav, &disc_region_mask) !=
> DVDNAV_STATUS_OK) {
> > + goto end_search_error_dvdnav;
> > + }
> > +
> > + if (c->opt_region > 0) {
> > + player_region_mask = (1 << (c->opt_region - 1));
> > + } else {
> > + player_region_mask = disc_region_mask;
> > + }
> > +
> > + if (dvdnav_set_region_mask(dvdnav, player_region_mask) !=
> DVDNAV_STATUS_OK) {
> > + goto end_search_error_dvdnav;
> > + }
> > +
> > + if (dvdnav_program_play(dvdnav, c->opt_title, pgcn, pgn) !=
> DVDNAV_STATUS_OK) {
> > + goto end_search_error_dvdnav;
> > + }
> > +
> > + if (dvdnav_angle_change(dvdnav, c->opt_angle) != DVDNAV_STATUS_OK) {
> > + goto end_search_error_dvdnav;
> > + }
> > +
> > + /* lock on to title's VTS and chosen PGC/PGN coordinates */
> > + for (int i = 0; i < DVDVIDEO_PS_MAX_SEARCH_BLOCKS; i++) {
> > + int nav_event;
> > + int nav_len;
> > + dvdnav_vts_change_event_t *vts_event;
> > + uint8_t nav_buf[DVDVIDEO_BLOCK_SIZE] = {0};
> > +
> > + if (ff_check_interrupt(&s->interrupt_callback)) {
> > + return AVERROR_EXIT;
> > + }
> > +
> > + if (dvdnav_get_next_block(dvdnav, nav_buf, &nav_event, &nav_len)
> > + != DVDNAV_STATUS_OK) {
> > + goto end_search_error_dvdnav;
> > + }
> > +
> > + if (nav_len > DVDVIDEO_BLOCK_SIZE) {
> > + av_log(s, AV_LOG_ERROR, "Invalid block (too big)\n");
> > +
> > + ret = AVERROR(ENOMEM);
> > +
> > + goto end_search_error;
> > + }
> > +
> > + av_log(s, AV_LOG_TRACE, "nav event=%d len=%d\n", nav_event,
> nav_len);
> > +
> > + switch (nav_event) {
> > + case DVDNAV_VTS_CHANGE:
> > + vts_event = (dvdnav_vts_change_event_t *) nav_buf;
> > +
> > + if (cell_search_has_vts) {
> > + if (vts_event->new_vtsN != title_info.title_set_nr
> > + || vts_event->new_domain !=
> DVD_DOMAIN_VTSTitle) {
> > + av_log(s, AV_LOG_ERROR, "Unexpected VTS
> change\n");
> > +
> > + ret = AVERROR_INPUT_CHANGED;
> > +
> > + goto end_search_error;
> > + }
> > + continue;
> > + }
> > +
> > + if (vts_event->new_vtsN == title_info.title_set_nr
> > + && vts_event->new_domain ==
> DVD_DOMAIN_VTSTitle) {
> > + cell_search_has_vts = 1;
> > + }
> > +
> > + continue;
> > + case DVDNAV_CELL_CHANGE:
> > + // if we need more info about the cell:
> > + // dvdnav_cell_change_event_t *event_data =
> > + // (dvdnav_cell_change_event_t *) nav_buf;
> > + int check_title;
> > + int check_pgcn;
> > + int check_pgn;
> > +
> > + if (!cell_search_has_vts) {
> > + continue;
> > + }
> > +
> > + dvdnav_current_title_program(dvdnav, &check_title,
> > + &check_pgcn, &check_pgn);
> > +
> > + if (check_title == c->opt_title && check_pgcn == pgcn
> > + && check_pgn == pgn &&
> dvdnav_is_domain_vts(dvdnav)) {
> > + goto end_ready;
> > + }
> > +
> > + continue;
> > + case DVDNAV_STILL_FRAME:
> > + dvdnav_still_skip(dvdnav);
> > +
> > + continue;
> > + case DVDNAV_WAIT:
> > + dvdnav_wait_skip(dvdnav);
> > +
> > + continue;
> > + case DVDNAV_STOP:
> > + ret = AVERROR_INPUT_CHANGED;
> > +
> > + goto end_search_error;
> > + default:
> > + continue;
> > + }
> > + }
> > +
> > +end_search_error_dvdnav:
> > + ret = AVERROR_EXTERNAL;
> > + av_log(s, AV_LOG_ERROR, "libdvdnav: %s\n",
> dvdnav_err_to_string(dvdnav));
> > + av_log(s, AV_LOG_ERROR, "Error starting playback\n");
> > +
> > +end_search_error:
> > + dvdnav_close(dvdnav);
> > + ifoClose(vts_ifo);
> > +
> > + return ret;
> > +
> > +end_ready:
> > + /* update the context */
> > + c->play_vts_ifo = vts_ifo;
> > + c->play_pgc = pgc;
> > + c->play_vtsn = title_info.title_set_nr;
> > + c->play_pgcn = pgcn;
> > + c->play_dvdnav = dvdnav;
> > +
> > + return 0;
> > +}
> > +
> > +static void dvdvideo_playback_close(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + if (c->play_dvdnav) {
> > + dvdnav_close(c->play_dvdnav);
> > + c->play_dvdnav = NULL;
> > + }
> > +
> > + if (c->play_vts_ifo) {
> > + ifoClose(c->play_vts_ifo);
> > + c->play_vts_ifo = NULL;
> > + }
> > +}
> > +
> > +static int dvdvideo_video_stream_analyze(video_attr_t video_attr,
> > + enum AVCodecID *p_codec_id, AVRational *p_framerate,
> > + int *p_height, int *p_width)
> > +{
> > + enum AVCodecID codec_id = AV_CODEC_ID_NONE;
> > + AVRational framerate = ZERO_Q;
> > + int height = 0;
> > + int width = 0;
> > +
> > + switch (video_attr.mpeg_version) {
> > + case DVDVIDEO_VTS_MPEG_VERSION_MPEG1:
> > + codec_id = AV_CODEC_ID_MPEG1VIDEO;
> > + break;
> > + case DVDVIDEO_VTS_MPEG_VERSION_MPEG2:
> > + codec_id = AV_CODEC_ID_MPEG2VIDEO;
> > + break;
> > + }
> > +
> > + switch (video_attr.video_format) {
> > + case DVDVIDEO_VTS_PICTURE_FORMAT_NTSC:
> > + framerate = DVDVIDEO_NTSC_FRAMERATE_Q;
> > + height = DVDVIDEO_NTSC_HEIGHT;
> > + break;
> > + case DVDVIDEO_VTS_PICTURE_FORMAT_PAL:
> > + framerate = DVDVIDEO_PAL_FRAMERATE_Q;
> > + height = DVDVIDEO_PAL_HEIGHT;
> > + break;
> > + }
> > +
> > + if (height > 0) {
> > + switch (video_attr.picture_size) {
> > + case DVDVIDEO_VTS_PICTURE_SIZE_D1:
> > + width = DVDVIDEO_D1_WIDTH;
> > + break;
> > + case DVDVIDEO_VTS_PICTURE_SIZE_4CIF:
> > + width = DVDVIDEO_4CIF_WIDTH;
> > + break;
> > + case DVDVIDEO_VTS_PICTURE_SIZE_D1_HALF:
> > + width = DVDVIDEO_D1_HALF_WIDTH;
> > + break;
> > + case DVDVIDEO_VTS_PICTURE_SIZE_CIF:
> > + width = DVDVIDEO_CIF_WIDTH;
> > + height /= 2;
> > + break;
> > + }
> > + }
> > +
> > + if (codec_id == AV_CODEC_ID_NONE || av_cmp_q(framerate, ZERO_Q) == 0
> av_cmp_q for comparing a rational to zero is unnecessary. Just check if
> the numerator is nonzero, as a fraction is zero if and only if its
> numerator is zero.
>
> > + || width < 1 || height < 1) {
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + (*p_codec_id) = codec_id;
> > + (*p_framerate) = framerate;
> > + (*p_height) = height;
> > + (*p_width) = width;
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_video_stream_to_avstream(AVFormatContext *s,
> > + enum AVCodecID codec_id, AVRational framerate,
> > + int height, int width,
> > + enum AVStreamParseType need_parsing)
> > +{
> > + AVStream *st;
> > + FFStream *sti;
> > +
> > + st = avformat_new_stream(s, NULL);
> > + if (!st) {
> > + return AVERROR(ENOMEM);
> > + }
> > +
> > + st->id = DVDVIDEO_STARTCODE_VIDEO;
> > + st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
> > + st->codecpar->codec_id = codec_id;
> > + st->codecpar->width = width;
> > + st->codecpar->height = height;
> > + st->codecpar->format = DVDVIDEO_PIXEL_FORMAT;
> > +
> > + st->codecpar->framerate = framerate;
> > +#if FF_API_R_FRAME_RATE
> > + st->r_frame_rate = framerate;
> > +#endif
> > + st->avg_frame_rate = framerate;
> > +
> > + avpriv_set_pts_info(st, DVDVIDEO_PTS_WRAP_BITS,
> > + DVDVIDEO_TIME_BASE_Q.num, DVDVIDEO_TIME_BASE_Q.den);
> > +
> > + sti = ffstream(st);
> > + sti->request_probe = 0;
> > + sti->need_parsing = need_parsing;
> > + sti->avctx->framerate = framerate;
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_video_stream_register(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + enum AVCodecID codec_id;
> > + AVRational framerate;
> > + int height;
> > + int width;
> > +
> > + int ret;
> > +
> > + if ((ret =
> dvdvideo_video_stream_analyze(c->play_vts_ifo->vtsi_mat->vts_video_attr,
> &codec_id,
> > + &framerate, &height, &width)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Invalid video parameters in VTS
> IFO\n");
> > +
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_video_stream_to_avstream(c->mpeg_ctx, codec_id,
> > + framerate, height, width, AVSTREAM_PARSE_FULL)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Unable to allocate video stream
> (subdemux)\n");
> > +
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_video_stream_to_avstream(s, codec_id,
> > + framerate, height, width, AVSTREAM_PARSE_NONE)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Unable to allocate video stream\n");
> > +
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_audio_stream_analyze(audio_attr_t audio_attr,
> > + uint16_t audio_control, enum AVCodecID *p_codec_id, int
> *p_startcode,
> > + int *p_sample_rate, int *p_sample_bits,
> > + int *p_nb_channels, char **lang_iso639__2)
> > +{
> > + enum AVCodecID codec_id = AV_CODEC_ID_NONE;
> > + int startcode = 0;
> > + int sample_rate = 0;
> > + int sample_bits = 0;
> > + int nb_channels = 0;
> > +
> > + int position = (audio_control & 0x7F00) >> 8;
> > +
> > + switch (audio_attr.audio_format) {
> > + case DVDVIDEO_VTS_AUDIO_FORMAT_AC3:
> > + codec_id = AV_CODEC_ID_AC3;
> > + sample_rate = 48000;
> > + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_AC3 + position;
> > + break;
> > + case DVDVIDEO_VTS_AUDIO_FORMAT_MP1:
> > + codec_id = AV_CODEC_ID_MP1;
> > + sample_rate = 48000;
> > + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP1 + position;
> > + break;
> > + case DVDVIDEO_VTS_AUDIO_FORMAT_MP2:
> > + codec_id = AV_CODEC_ID_MP2;
> > + sample_rate = 48000;
> > + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_MP2 + position;
> > + break;
> > + case DVDVIDEO_VTS_AUDIO_FORMAT_PCM:
> > + codec_id = AV_CODEC_ID_PCM_DVD;
> > +
> > + switch (audio_attr.sample_frequency) {
> > + case DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_48K:
> > + sample_rate = 48000;
> > + break;
> > + case DVDVIDEO_VTS_AUDIO_SAMPLE_RATE_96K:
> > + sample_rate = 96000;
> > + break;
> > + }
> > +
> > + switch (audio_attr.quantization) {
> > + case DVDVIDEO_VTS_AUDIO_QUANTIZATION_16:
> > + sample_bits = 16;
> > + break;
> > + case DVDVIDEO_VTS_AUDIO_QUANTIZATION_20:
> > + sample_bits = 20;
> > + break;
> > + case DVDVIDEO_VTS_AUDIO_QUANTIZATION_24:
> > + sample_bits = 24;
> > + break;
> > + }
> > +
> > + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_PCM + position;
> > + break;
> > + case DVDVIDEO_VTS_AUDIO_FORMAT_DTS:
> > + codec_id = AV_CODEC_ID_DTS;
> > + sample_rate = 48000;
> > + startcode = DVDVIDEO_STARTCODE_OFFSET_AUDIO_DTS + position;
> > + break;
> > + }
> > +
> > + nb_channels = audio_attr.channels + 1;
> > +
> > + if (codec_id == AV_CODEC_ID_NONE || startcode == 0
> > + || sample_rate == 0 || nb_channels == 0) {
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + (*p_codec_id) = codec_id;
> > + (*p_startcode) = startcode;
> > + (*p_sample_rate) = sample_rate;
> > + (*p_sample_bits) = sample_bits;
> > + (*p_nb_channels) = nb_channels;
> > + (*lang_iso639__2) =
> dvdvideo_lang_code_to_iso639__2(audio_attr.lang_code);
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_audio_stream_to_avstream(AVFormatContext *s,
> > + enum AVCodecID codec_id, int startcode, int sample_rate,
> > + int sample_bits, int nb_channels, char *lang_iso639__2,
> > + enum AVStreamParseType need_parsing)
> > +{
> > + AVStream *st;
> > + FFStream *sti;
> > +
> > + st = avformat_new_stream(s, NULL);
> > + if (!st) {
> > + return AVERROR(ENOMEM);
> > + }
> > +
> > + st->id = startcode;
> > + st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
> > + st->codecpar->codec_id = codec_id;
> > + st->codecpar->sample_rate = sample_rate;
> > + st->codecpar->ch_layout.nb_channels = nb_channels;
> > +
> > + if (sample_bits > 0) {
> > + st->codecpar->format = sample_bits == 16 ?
> > + AV_SAMPLE_FMT_S16
> > + : AV_SAMPLE_FMT_S32;
> > + st->codecpar->bits_per_coded_sample = sample_bits;
> > + st->codecpar->bits_per_raw_sample = sample_bits;
> > + }
> > +
> > + if (lang_iso639__2) {
> > + av_dict_set(&st->metadata, "language", lang_iso639__2, 0);
> > + }
> > +
> > + avpriv_set_pts_info(st, DVDVIDEO_PTS_WRAP_BITS,
> > + DVDVIDEO_TIME_BASE_Q.num, DVDVIDEO_TIME_BASE_Q.den);
> > +
> > + sti = ffstream(st);
> > + sti->request_probe = 0;
> > + sti->need_parsing = need_parsing;
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_audio_stream_register_all(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + for (int i = 0; i <
> c->play_vts_ifo->vtsi_mat->nr_of_vts_audio_streams; i++) {
> > + enum AVCodecID codec_id;
> > + int startcode;
> > + int sample_rate;
> > + int sample_bits;
> > + int nb_channels;
> > + char *lang_iso639__2;
> > + int ret;
> > +
> > + if (!(c->play_pgc->audio_control[i]
> > + & DVDVIDEO_AUDIO_CONTROL_ENABLED_MASK)) {
> > + continue;
> > + }
> > +
> > + if ((ret = dvdvideo_audio_stream_analyze(
> > + c->play_vts_ifo->vtsi_mat->vts_audio_attr[i],
> > + c->play_pgc->audio_control[i], &codec_id, &startcode,
> > + &sample_rate, &sample_bits, &nb_channels,
> > + &lang_iso639__2)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Invalid audio parameters in VTS
> IFO\n");
> > +
> > + return ret;
> > + }
> > +
> > + if (c->play_vts_ifo->vtsi_mat->
> > + vts_audio_attr[i].application_mode == 1) {
> > + av_log(s, AV_LOG_ERROR,
> > + "Audio stream uses karaoke extension which is
> unsupported\n");
> > +
> > + return AVERROR_PATCHWELCOME;
> > + }
> > +
> > + for (int j = 0; j < s->nb_streams; j++) {
> > + if (s->streams[j]->id == startcode) {
> > + av_log(s, AV_LOG_WARNING,
> > + "Audio stream has duplicate entry in VTS
> IFO\n");
> > +
> > + continue;
> > + }
> > + }
> > +
> > + if ((ret = dvdvideo_audio_stream_to_avstream(c->mpeg_ctx,
> codec_id,
> > + startcode, sample_rate, sample_bits, nb_channels,
> > + lang_iso639__2, AVSTREAM_PARSE_FULL)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Unable to allocate video stream
> (subdemux)\n");
> > +
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_audio_stream_to_avstream(s, codec_id,
> > + startcode, sample_rate, sample_bits, nb_channels,
> > + lang_iso639__2, AVSTREAM_PARSE_NONE)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Unable to allocate audio
> stream\n");
> > +
> > + return ret;
> > + }
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_subtitle_clut_rgb_extradata_cat(
> > + const uint32_t *clut_rgb, size_t clut_rgb_size,
> > + char *dst, size_t dst_size)
> > +{
> > + if (dst_size < DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE) {
> > + return AVERROR(ENOMEM);This is the error code for an allocation
> failure, which is not what is
> happening here. It's an illegal argument passed to the function.
>
> > + }
> > +
> > + if (clut_rgb_size != DVDVIDEO_SUBP_CLUT_SIZE) {
> > + return AVERROR(EINVAL);
> > + }
> > +
> > + snprintf(dst, dst_size, "palette: ");
> > +
> > + for (int i = 0; i < DVDVIDEO_SUBP_CLUT_LEN; i++) {
> > + av_strlcatf(dst, dst_size,
> > + "%06"PRIx32"%s", clut_rgb[i], i != 15 ? ", " : "");
> > + }
> > +
> > + av_strlcat(dst, "\n", 1);
> > +
> You should be using the AVBPrint api here to construct a string, not
> av_strlcat.
>
>
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_subtitle_clut_yuv_to_rgb(uint32_t *clut,
> > + const size_t clut_size)
> What is a yuv-to-rgb function doing inside a dvd-video demuxer?
>
> > +{
> > + const uint8_t *cm = dvdvideo_yuv_crop_tab +
> DVDVIDEO_YUV_NEG_CROP_MAX;
> > +
> > + int i, y, cb, cr;
> > + uint8_t r, g, b;
> > + int r_add, g_add, b_add;
> > +
> > + if (clut_size != DVDVIDEO_SUBP_CLUT_SIZE) {Why even have an
> argument to the function if it must equal some exact
> integer? At that point just remove clut_size from the function argument
> and just use DVDVIDEO_SUBP_CLUT_SIZE in its place.
>
>
> > + return AVERROR(EINVAL);
> > + }
> > +
> > + for (i = 0; i < DVDVIDEO_SUBP_CLUT_LEN; i++) {
> > + y = (clut[i] >> 16) & 0xFF;
> > + cr = (clut[i] >> 8) & 0xFF;
> > + cb = clut[i] & 0xFF;
> > +
> > + YUV_TO_RGB1_CCIR(cb, cr);
> > + YUV_TO_RGB2_CCIR(r, g, b, y);
> > +
> > + clut[i] = (r << 16) | (g << 8) | b;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_subtitle_stream_to_avstream(AVFormatContext *s,
> > + int startcode,
> > + char *clut_rgb_extradata_buf, int clut_rgb_extradata_buf_size,
> > + char *lang_iso639__2, enum AVStreamParseType need_parsing)
> > +{
> > + AVStream *st;
> > + FFStream *sti;
> > + int ret;
> > +
> > + st = avformat_new_stream(s, NULL);
> > + if (!st) {
> > + return AVERROR(ENOMEM);
> > + }
> > +
> > + st->id = startcode;
> > + st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
> > + st->codecpar->codec_id = AV_CODEC_ID_DVD_SUBTITLE;
> > +
> > + if ((ret = ff_alloc_extradata(st->codecpar,
> > + clut_rgb_extradata_buf_size)) != 0) {
> > + return ret;
> > + }
> > +
> > + memcpy(st->codecpar->extradata, clut_rgb_extradata_buf,
> > + st->codecpar->extradata_size);
> > +
> > + if (lang_iso639__2) {
> > + av_dict_set(&st->metadata, "language", lang_iso639__2, 0);
> > + }
> > +
> > + avpriv_set_pts_info(st, DVDVIDEO_PTS_WRAP_BITS,
> > + DVDVIDEO_TIME_BASE_Q.num, DVDVIDEO_TIME_BASE_Q.den);
> > +
> > + sti = ffstream(st);
> > + sti->request_probe = 0;
> > + sti->need_parsing = need_parsing;
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_subtitle_stream_register(AVFormatContext *s,
> > + int position,
> > + char *clut_rgb_extradata_buf, int clut_rgb_extradata_buf_size,
> > + char *lang_iso639__2)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + int ret;
> > +
> > + int startcode = DVDVIDEO_STARTCODE_OFFSET_SUBP + position;
> > +
> > + for (int i = 0; i < s->nb_streams; i++) {
> > + /* don't need to warn the user about this, since params won't
> change */
> > + if (s->streams[i]->id == startcode) {
> > + av_log(s, AV_LOG_TRACE,
> > + "Subtitle stream has duplicate entry in VTS IFO\n");
> > + return 0;
> > + }
> > + }
> > +
> > + if ((ret = dvdvideo_subtitle_stream_to_avstream(c->mpeg_ctx,
> startcode,
> > + clut_rgb_extradata_buf, clut_rgb_extradata_buf_size,
> > + lang_iso639__2, AVSTREAM_PARSE_FULL)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Unable to allocate subtitle stream
> (subdemux)\n");
> > +
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_subtitle_stream_to_avstream(s, startcode,
> > + clut_rgb_extradata_buf, clut_rgb_extradata_buf_size,
> > + lang_iso639__2, AVSTREAM_PARSE_NONE)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Unable to allocate subtitle stream\n");
> > +
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_subtitle_stream_register_all(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + int ret;
> > + uint32_t clut_rgb[DVDVIDEO_SUBP_CLUT_SIZE] = {0};
> > + char clut_rgb_extradata_buf[DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE]
> = {0};
> > +
> > + if (c->play_vts_ifo->vtsi_mat->nr_of_vts_subp_streams < 1) {
> > + return 0;
> > + }
> > +
> > + /* initialize the palette (same for all streams in this PGC) */
> > + memcpy(clut_rgb, c->play_pgc->palette, DVDVIDEO_SUBP_CLUT_SIZE);
> > +
> > + if ((ret = dvdvideo_subtitle_clut_yuv_to_rgb(
> > + clut_rgb, DVDVIDEO_SUBP_CLUT_SIZE)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Unable to convert subtitle
> palette\n");Why are you converting the subtitle palette?
>
> > +
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_subtitle_clut_rgb_extradata_cat(
> > + clut_rgb, DVDVIDEO_SUBP_CLUT_SIZE,
> > + clut_rgb_extradata_buf,
> DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE)) != 0) {
> > + av_log(s, AV_LOG_ERROR, "Unable to set up subtitle
> extradata\n");
> > +
> > + return ret;
> > + }
> > +
> > + for (int i = 0; i < c->play_vts_ifo->
> > + vtsi_mat->nr_of_vts_subp_streams; i++) {
> > + uint32_t subp_control;
> > + subp_attr_t subp_attr;
> > + video_attr_t video_attr;
> > + char *lang_iso639__2;
> > +
> > + subp_control = c->play_pgc->subp_control[i];
> > +
> > + if (!(subp_control & DVDVIDEO_SUBP_CONTROL_ENABLED_MASK)) {
> > + continue;
> > + }
> > +
> > + subp_attr = c->play_vts_ifo->vtsi_mat->vts_subp_attr[i];
> > + video_attr = c->play_vts_ifo->vtsi_mat->vts_video_attr;
> > + lang_iso639__2 =
> dvdvideo_lang_code_to_iso639__2(subp_attr.lang_code);
> > +
> > + /* there can be several presentations for one SPU */
> > + /* for now, be flexible with the DAR check due to weird
> authoring */
> > + if (video_attr.display_aspect_ratio > 0) {
> > + /* 16:9 */
> > + ret = dvdvideo_subtitle_stream_register(s,
> > + ((subp_control >> 16) & 0x1F),
> > + clut_rgb_extradata_buf,
> > + DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
> > + lang_iso639__2);
> > + if (ret != 0) {
> > + return ret;
> > + }
> > +
> > + if (video_attr.permitted_df ==
> DVDVIDEO_VTS_SPU_PFD_LETTERBOX_ONLY
> > + || video_attr.permitted_df ==
> DVDVIDEO_VTS_SPU_PFD_ANY) {
> > + ret = dvdvideo_subtitle_stream_register(s,
> > + ((subp_control >> 8) & 0x1F),
> > + clut_rgb_extradata_buf,
> > + DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
> > + lang_iso639__2);
> > + if (ret != 0) {
> > + return ret;
> > + }
> > + }
> > +
> > + if (video_attr.permitted_df ==
> DVDVIDEO_VTS_SPU_PFD_PANSCAN_ONLY
> > + || video_attr.permitted_df ==
> DVDVIDEO_VTS_SPU_PFD_ANY) {
> > + ret = dvdvideo_subtitle_stream_register(s,
> > + (subp_control & 0x1F),
> > + clut_rgb_extradata_buf,
> > + DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
> > + lang_iso639__2);
> > + if (ret != 0) {
> > + return ret;
> > + }
> > + }
> > + } else {
> > + /* 4:3 */
> > + ret = dvdvideo_subtitle_stream_register(s,
> > + ((subp_control >> 24) & 0x1F),
> > + clut_rgb_extradata_buf,
> > + DVDVIDEO_SUBP_CLUT_RGB_EXTRADATA_SIZE,
> > + lang_iso639__2);
> > + if (ret != 0) {
> > + return ret;
> > + }
> > + }
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_subdemux_nested_io_open(AVFormatContext *s,
> > + AVIOContext **pb, const char *url, int flags, AVDictionary
> **opts)
> > +{
> This function is pointless.
>
> > + av_log(s, AV_LOG_ERROR, "Nested io_open not supported for this
> format\n");
> > +
> > + return AVERROR(EPERM);
> > +}
> > +
> > +static int dvdvideo_subdemux_read_data(void *opaque, uint8_t *buf,
> > + int buf_size)
> > +{
> > + AVFormatContext *s = opaque;
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + 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_PS_MAX_SEARCH_BLOCKS; i++) {
> > + int nav_event;
> > + int nav_len;
> > +
> > + int check_title;
> > + int check_pgcn;
> > + int check_pgn;
> > + int check_angle;
> > + int check_nb_angles;
> > +
> > + uint8_t nav_buf[DVDVIDEO_BLOCK_SIZE] = {0};
> > +
> > + if (ff_check_interrupt(&s->interrupt_callback)) {
> > + return AVERROR_EXIT;
> > + }
> > +
> > + if (dvdnav_get_next_block(c->play_dvdnav,
> > + nav_buf, &nav_event, &nav_len) != DVDNAV_STATUS_OK) {
> > + av_log(s, AV_LOG_ERROR, "Error reading block\n");
> > +
> > + goto end_dvdnav_error;
> > + }
> > +
> > + if (nav_len > DVDVIDEO_BLOCK_SIZE) {
> > + av_log(s, AV_LOG_ERROR, "Invalid block (too big)\n");
> > +
> This is not what ENOMEM means.
>
> > + return AVERROR(ENOMEM);
> > + }
> > +
> > + if (nav_event != DVDNAV_BLOCK_OK && nav_event !=
> DVDNAV_NAV_PACKET) {
> > + av_log(s, AV_LOG_TRACE, "nav event=%d len=%d\n", nav_event,
> nav_len);
> > + }
> > +
> > + if (dvdnav_current_title_program(c->play_dvdnav, &check_title,
> > + &check_pgcn, &check_pgn) != DVDNAV_STATUS_OK) {
> > + goto end_dvdnav_error;
> > + }
> > +
> > + if (check_title != c->opt_title || check_pgcn != c->play_pgcn
> > + || !dvdnav_is_domain_vts(c->play_dvdnav)) {
> > + return AVERROR_EOF;
> > + }
> > +
> > + if (dvdnav_get_angle_info(c->play_dvdnav, &check_angle,
> > + &check_nb_angles) != DVDNAV_STATUS_OK) {
> > + goto end_dvdnav_error;
> > + }
> > +
> > + if (check_angle != c->opt_angle) {
> > + av_log(s, AV_LOG_ERROR, "Unexpected angle change\n");
> > +
> > + return AVERROR_INPUT_CHANGED;
> > + }
> > +
> > + switch (nav_event) {
> > + case DVDNAV_BLOCK_OK:
> > + if (nav_len != DVDVIDEO_BLOCK_SIZE) {
> > + av_log(s, AV_LOG_ERROR, "Invalid block\n");
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + memcpy(buf, &nav_buf, nav_len);
> > +
> > + return nav_len;
> > + case DVDNAV_HIGHLIGHT:
> > + case DVDNAV_VTS_CHANGE:
> > + case DVDNAV_STILL_FRAME:
> > + case DVDNAV_STOP:
> > + case DVDNAV_WAIT:
> > + return AVERROR_EOF;
> > + default:
> > + continue;
> > + }
> > + }
> > +
> > + av_log(s, AV_LOG_ERROR, "Unable to find next MPEG block\n");
> > +
> > + return AVERROR_UNKNOWN;
> > +
> > +end_dvdnav_error:
> > + av_log(s, AV_LOG_ERROR, "libdvdnav: %s\n",
> dvdnav_err_to_string(c->play_dvdnav));
> > +
> > + return AVERROR_EXTERNAL;
> > +}
> > +
> > +static int64_t dvdvideo_subdemux_seek_data(void *opaque, int64_t offset,
> > + int whence)
> > +{
> > + AVFormatContext *s = opaque;
> > +
> > + av_log(s, AV_LOG_ERROR,
> > + "dvdvideo_subdemux_seek_data(): not implemented\n");
> > +
> > + return AVERROR_PATCHWELCOME;
> > +}
> > +
> > +static int dvdvideo_subdemux_open(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + int ret;
> > +
> > + const AVInputFormat *mpeg_fmt;
> > + AVFormatContext *mpeg_ctx;
> > + uint8_t *mpeg_buf;
> > +
> > + 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);
> > + }
> Why are you doing this instead of just declaring it to be a protocol
> that produces a mpeg-ps stream? That way it stops being your
> responsibility.
>
>
> > +
> > + ffio_init_context(&c->mpeg_pb, mpeg_buf, DVDVIDEO_BLOCK_SIZE, 0,
> > + s, dvdvideo_subdemux_read_data, NULL,
> dvdvideo_subdemux_seek_data);
> > + c->mpeg_pb.pub.seekable = 0;
> > +
> > + if ((ret = ff_copy_whiteblacklists(mpeg_ctx, s)) != 0) {
> > + avformat_free_context(mpeg_ctx);
> > +
> > + return ret;
> > + }
> > +
> > + /* TODO: fix the PTS issues (ask about this in ML) */
> > + /* AVFMT_FLAG_GENPTS works in some scenarios, but in others, the
> reading process hangs */
> > + mpeg_ctx->flags = AVFMT_FLAG_CUSTOM_IO | AVFMT_FLAG_GENPTS;
> MPEG-PS contains pts info, why not just use that? Not amazing PTS info,
> but usable PTS info.
>
> > +
> > + 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 = dvdvideo_subdemux_nested_io_open;
> > +
> > + 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 void dvdvideo_subdemux_close(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + av_freep(&c->mpeg_pb.pub.buffer);
> > + memset(&c->mpeg_pb, 0x00, sizeof(c->mpeg_pb));
> > + av_freep(&c->mpeg_pb);Why are you zeroing it before you free it? If
> this is a security issue
> you can't use memset anyway as the compiler may throw it away. That's
> why explicit_bzero was created.
>
> > +
> > + avformat_close_input(&c->mpeg_ctx);
> > +}
> > +
> > +static int dvdvideo_chapters_register(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > + int start_time = 0;
> > + int cell = 0;
> > +
> > + for (int i = 0; i < c->play_pgc->nr_of_programs; i++) {
> > + int64_t ms = 0;
> > + int next = c->play_pgc->program_map[i + 1];
> In the last iteration of the loop, will this possibly give you an
> out-of-bounds read?
>
> > +
> > + if (i == c->play_pgc->nr_of_programs - 1) {
> > + next = c->play_pgc->nr_of_cells + 1;
> > + }
> > +
> > + while (cell < next - 1) {
> > + /* only consider first cell of multi-angle cells */
> > + if (c->play_pgc->cell_playback[cell].block_mode <= 1) {
> > + ms = ms + dvdvideo_time_to_millis(
> > +
> &c->play_pgc->cell_playback[cell].playback_time);
> > + }
> > + cell++;
> > + }
> > +
> > + if (!avpriv_new_chapter(s, i, DVDVIDEO_TIME_BASE_MILLIS_Q,
> > + start_time, start_time + ms, NULL)) {
> > + av_log(s, AV_LOG_ERROR, "Unable to allocate chapter");
> > + return AVERROR(ENOMEM);
> > + }
> > +
> > + start_time += ms;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_read_header(AVFormatContext *s)
> > +{
> > + int ret;
> > +
> > + if ((ret = dvdvideo_volume_open(s)) != 0) {
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_playback_open(s)) != 0) {
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_subdemux_open(s)) != 0) {
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_video_stream_register(s)) != 0) {
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_audio_stream_register_all(s)) != 0) {
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_subtitle_stream_register_all(s)) != 0) {
> > + return ret;
> > + }
> > +
> > + if ((ret = dvdvideo_chapters_register(s)) != 0) {
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_read_packet(AVFormatContext *s, AVPacket *pkt)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + while (!ff_check_interrupt(&s->interrupt_callback)) {
> > + int subdemux_ret;
> > + AVPacket *subdemux_pkt;
> > +
> > + subdemux_pkt = av_packet_alloc();
> > + if (!subdemux_pkt) {
> > + return AVERROR(ENOMEM);
> > + }
> > +
> > + subdemux_ret = av_read_frame(c->mpeg_ctx, subdemux_pkt);
> > + if (subdemux_ret >= 0) {
> > + if (c->mpeg_ctx->nb_streams != s->nb_streams) {
> > + av_log(s, AV_LOG_ERROR, "Unexpected stream during
> playback\n");
> > +
> > + av_packet_unref(subdemux_pkt);
> > +
> > + return AVERROR_INPUT_CHANGED;
> > + }
> > +
> > + av_packet_move_ref(pkt, subdemux_pkt);
> This seems like an unnecessary memcpy.
>
> > +
> > + return 0;
> > + }
> > +
> > + return subdemux_ret;
> > + }
> > +
> > + return AVERROR_EOF;
> > +}
> > +
> > +static int dvdvideo_read_seek(AVFormatContext *s, int stream_index,
> > + int64_t timestamp, int flags)
> > +{
> > + av_log(s, AV_LOG_ERROR, "dvdvideo_read_seek(): not implemented\n");
> > +
> > + return AVERROR_PATCHWELCOME;
> > +}
> > +
> > +static int dvdvideo_close(AVFormatContext *s)
> > +{
> > + dvdvideo_subdemux_close(s);
> > + dvdvideo_playback_close(s);
> > + dvdvideo_volume_close(s);
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_probe(const AVProbeData *p)
> > +{
> > + av_log(NULL, AV_LOG_ERROR, "dvdvideo_probe(): not implemented\n");
> > +
> > + return AVERROR_PATCHWELCOME;
> > +}
> > +
> > +#define OFFSET(x) offsetof(DVDVideoDemuxContext, x)
> > +static const AVOption dvdvideo_options[] = {
> > + {"title", "Title Number", OFFSET(opt_title),
> AV_OPT_TYPE_INT, { .i64=1 }, 1,
> DVDVIDEO_TITLE_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
> > + {"ptt", "Entry PTT Number", OFFSET(opt_ptt),
> AV_OPT_TYPE_INT, { .i64=1 }, 1,
> DVDVIDEO_PTT_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
> > + {"pgc", "Entry PGC Number (0=auto)", OFFSET(opt_pgc),
> AV_OPT_TYPE_INT, { .i64=0 }, 0,
> DVDVIDEO_PGC_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
> > + {"pg", "Entry PG Number (0=auto)", OFFSET(opt_pg),
> AV_OPT_TYPE_INT, { .i64=0 }, 0,
> DVDVIDEO_PG_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
> > + {"angle", "Video Angle Number", OFFSET(opt_angle),
> AV_OPT_TYPE_INT, { .i64=1 }, 1,
> DVDVIDEO_ANGLE_NUMBER_MAX, AV_OPT_FLAG_DECODING_PARAM },
> > + {"region", "Playback Region Number (0=free)",
> OFFSET(opt_region), AV_OPT_TYPE_INT, { .i64=0 }, 0,
> DVDVIDEO_REGION_NUMBER_MAX, 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_NO_BYTE_SEEK |
> AVFMT_NOGENSEARCH | AVFMT_NOBINSEARCH | AVFMT_SHOW_IDS | AVFMT_TS_DISCONT,
> > + .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
> > +};
>
> There's a number of other issues and I didn't write each one of each
> type, but my biggest question is why this is not a protocol that
> produces an mpeg-ps stream.
>
> - Leo Izen (Traneptora)
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH] [WIP] [RFC] dvdvideo: initial contribution (DVD demuxer)
2023-12-09 10:06 [FFmpeg-devel] [PATCH] [WIP] [RFC] dvdvideo: initial contribution (DVD demuxer) Marth64
2023-12-10 2:27 ` Marth64
2023-12-10 3:03 ` Leo Izen
@ 2023-12-13 20:45 ` Nicolas George
2024-01-06 22:32 ` Marth64
2024-01-10 8:46 ` [FFmpeg-devel] [PATCH v2] dvdvideo: add DVD-Video demuxer, powered by libdvdnav and libdvdread Marth64
3 siblings, 1 reply; 27+ messages in thread
From: Nicolas George @ 2023-12-13 20:45 UTC (permalink / raw)
To: FFmpeg development discussions and patches; +Cc: Marth64
[-- Attachment #1.1: Type: text/plain, Size: 440 bytes --]
Marth64 (12023-12-09):
> I am hoping and willing to improve this to be a robust demuxer
> wherever possible, but to that extent there is still an issue I can't
> figure out how to solve: Dealing with DTS discontinuities and PTS
> generation.
The dvd2concat script manages it by shifting the times of the segments
defined in the index. You can probably do the same.
Interesting work, thanks.
Regards,
--
Nicolas George
[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
[-- Attachment #2: Type: text/plain, Size: 251 bytes --]
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH] [WIP] [RFC] dvdvideo: initial contribution (DVD demuxer)
2023-12-13 20:45 ` Nicolas George
@ 2024-01-06 22:32 ` Marth64
0 siblings, 0 replies; 27+ messages in thread
From: Marth64 @ 2024-01-06 22:32 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Marth64
Thank you, for the feedback and encouragement. I have made progress fixing
the timestamp issues and will follow up once I am more confident about the
solution.
On Wed, Dec 13, 2023 at 2:45 PM Nicolas George <george@nsup.org> wrote:
> Marth64 (12023-12-09):
> > I am hoping and willing to improve this to be a robust demuxer
> > wherever possible, but to that extent there is still an issue I can't
> > figure out how to solve: Dealing with DTS discontinuities and PTS
> > generation.
>
> The dvd2concat script manages it by shifting the times of the segments
> defined in the index. You can probably do the same.
>
> Interesting work, thanks.
>
> Regards,
>
> --
> Nicolas George
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* [FFmpeg-devel] [PATCH v2] dvdvideo: add DVD-Video demuxer, powered by libdvdnav and libdvdread
2023-12-09 10:06 [FFmpeg-devel] [PATCH] [WIP] [RFC] dvdvideo: initial contribution (DVD demuxer) Marth64
` (2 preceding siblings ...)
2023-12-13 20:45 ` Nicolas George
@ 2024-01-10 8:46 ` Marth64
2024-01-10 8:53 ` Marth64
` (2 more replies)
3 siblings, 3 replies; 27+ messages in thread
From: Marth64 @ 2024-01-10 8:46 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Marth64
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 <marth64@proxyid.net>
---
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 <next>:
- 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 <marth64@proxyid.net>
+ *
+ * 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 <dvdread/dvd_reader.h>
+#include <dvdread/ifo_read.h>
+#include <dvdread/ifo_types.h>
+#include <dvdread/nav_read.h>
+#include <dvdnav/dvdnav.h>
+
+#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
+};
--
2.34.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH v2] dvdvideo: add DVD-Video demuxer, powered by libdvdnav and libdvdread
2024-01-10 8:46 ` [FFmpeg-devel] [PATCH v2] dvdvideo: add DVD-Video demuxer, powered by libdvdnav and libdvdread Marth64
@ 2024-01-10 8:53 ` Marth64
2024-01-10 10:16 ` Nicolas George
2024-01-11 3:46 ` [FFmpeg-devel] [PATCH v3] " Marth64
2024-01-28 22:59 ` [FFmpeg-devel] [PATCH v5] libavformat: " Marth64
2 siblings, 1 reply; 27+ messages in thread
From: Marth64 @ 2024-01-10 8:53 UTC (permalink / raw)
To: Marth64; +Cc: ffmpeg-devel
I will add that I still had to maintain a dependency on GENPTS for the
inner MPEG demuxer. This is because dvdnav_get_current_time() does not
provide exact stream-level PTS. However, the crashing/hanging is fixed (by
parsing NAV packets), and the output is produced smoothly. GENPTS fills in
the blanks.
If anyone can think of a better way, please let me know. Right now, it
works well.
On Wed, Jan 10, 2024 at 2:47 AM Marth64 <marth64@proxyid.net> wrote:
> 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 <marth64@proxyid.net>
> ---
> 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 <next>:
> - 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 <marth64@proxyid.net>
> + *
> + * 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 <dvdread/dvd_reader.h>
> +#include <dvdread/ifo_read.h>
> +#include <dvdread/ifo_types.h>
> +#include <dvdread/nav_read.h>
> +#include <dvdnav/dvdnav.h>
> +
> +#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
> +};
> --
> 2.34.1
>
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH v2] dvdvideo: add DVD-Video demuxer, powered by libdvdnav and libdvdread
2024-01-10 8:53 ` Marth64
@ 2024-01-10 10:16 ` Nicolas George
2024-01-10 16:25 ` Marth64
0 siblings, 1 reply; 27+ messages in thread
From: Nicolas George @ 2024-01-10 10:16 UTC (permalink / raw)
To: FFmpeg development discussions and patches; +Cc: Marth64
Marth64 (12024-01-10):
> I will add that I still had to maintain a dependency on GENPTS for the
> inner MPEG demuxer. This is because dvdnav_get_current_time() does not
> provide exact stream-level PTS. However, the crashing/hanging is fixed (by
> parsing NAV packets), and the output is produced smoothly. GENPTS fills in
> the blanks.
>
> If anyone can think of a better way, please let me know. Right now, it
> works well.
You could imitate what tools/dvd2concat does internally: use
libdvdread to get the structure of the DVD but let the concat demuxer
handle all the actual demuxing work.
Regards,
--
Nicolas George
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH v2] dvdvideo: add DVD-Video demuxer, powered by libdvdnav and libdvdread
2024-01-10 10:16 ` Nicolas George
@ 2024-01-10 16:25 ` Marth64
2024-01-10 16:48 ` Marth64
0 siblings, 1 reply; 27+ messages in thread
From: Marth64 @ 2024-01-10 16:25 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Marth64
Thank you, Nicolas. I had tinkered with concat this way in the past as PGCs
are indeed just a bunch of segments mushed together, but I had run into
issues specifically with (1) concerns about forced subtitles that pop up
mid stream and (2) unable to figure out custom IO with it. Since dvdnav is
driving the navigation and reading of the disc, I do not have the luxury of
knowing VOB addresses for a perfect read of the title (without fully
playing back). So I don’t have a list of files and sectors to give concat.
As DVD also has cell commands that drive direction of the stream, this
complicates it further. The structure revealed by dvdread alone is not
enough to properly play back; the dvdnav VM plays a key role. Finally, I
had also worry about oddities when having demuxer->submuxer->subsubdemuxer.
In my approach (using mpeg demux directly) there doesn’t seem to be any
issue with concatenation/handling the gaps, and I am flushing the
subdemuxer when they occur. Just the GENPTS sticks out as being a non-ideal
thing to do.
That said, I will take another look and see if I misunderstood at the time.
Best,
On Wed, Jan 10, 2024 at 04:17 Nicolas George <george@nsup.org> wrote:
> Marth64 (12024-01-10):
> > I will add that I still had to maintain a dependency on GENPTS for the
> > inner MPEG demuxer. This is because dvdnav_get_current_time() does not
> > provide exact stream-level PTS. However, the crashing/hanging is fixed
> (by
> > parsing NAV packets), and the output is produced smoothly. GENPTS fills
> in
> > the blanks.
> >
> > If anyone can think of a better way, please let me know. Right now, it
> > works well.
>
> You could imitate what tools/dvd2concat does internally: use
> libdvdread to get the structure of the DVD but let the concat demuxer
> handle all the actual demuxing work.
>
> Regards,
>
> --
> Nicolas George
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH v2] dvdvideo: add DVD-Video demuxer, powered by libdvdnav and libdvdread
2024-01-10 16:25 ` Marth64
@ 2024-01-10 16:48 ` Marth64
0 siblings, 0 replies; 27+ messages in thread
From: Marth64 @ 2024-01-10 16:48 UTC (permalink / raw)
To: Marth64; +Cc: ffmpeg-devel
To elaborate slightly: GENPTS is working within each segment, whose start
and end times I have from NAV packets
On Wed, Jan 10, 2024 at 10:25 Marth64 <marth64@proxyid.net> wrote:
> Thank you, Nicolas. I had tinkered with concat this way in the past as
> PGCs are indeed just a bunch of segments mushed together, but I had run
> into issues specifically with (1) concerns about forced subtitles that pop
> up mid stream and (2) unable to figure out custom IO with it. Since dvdnav
> is driving the navigation and reading of the disc, I do not have the luxury
> of knowing VOB addresses for a perfect read of the title (without fully
> playing back). So I don’t have a list of files and sectors to give concat.
> As DVD also has cell commands that drive direction of the stream, this
> complicates it further. The structure revealed by dvdread alone is not
> enough to properly play back; the dvdnav VM plays a key role. Finally, I
> had also worry about oddities when having demuxer->submuxer->subsubdemuxer.
>
> In my approach (using mpeg demux directly) there doesn’t seem to be any
> issue with concatenation/handling the gaps, and I am flushing the
> subdemuxer when they occur. Just the GENPTS sticks out as being a non-ideal
> thing to do.
>
> That said, I will take another look and see if I misunderstood at the time.
>
> Best,
>
> On Wed, Jan 10, 2024 at 04:17 Nicolas George <george@nsup.org> wrote:
>
>> Marth64 (12024-01-10):
>> > I will add that I still had to maintain a dependency on GENPTS for the
>> > inner MPEG demuxer. This is because dvdnav_get_current_time() does not
>> > provide exact stream-level PTS. However, the crashing/hanging is fixed
>> (by
>> > parsing NAV packets), and the output is produced smoothly. GENPTS fills
>> in
>> > the blanks.
>> >
>> > If anyone can think of a better way, please let me know. Right now, it
>> > works well.
>>
>> You could imitate what tools/dvd2concat does internally: use
>> libdvdread to get the structure of the DVD but let the concat demuxer
>> handle all the actual demuxing work.
>>
>> Regards,
>>
>> --
>> Nicolas George
>>
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* [FFmpeg-devel] [PATCH v3] dvdvideo: add DVD-Video demuxer, powered by libdvdnav and libdvdread
2024-01-10 8:46 ` [FFmpeg-devel] [PATCH v2] dvdvideo: add DVD-Video demuxer, powered by libdvdnav and libdvdread Marth64
2024-01-10 8:53 ` Marth64
@ 2024-01-11 3:46 ` Marth64
2024-01-11 3:48 ` Marth64
2024-01-24 0:17 ` Stefano Sabatini
2024-01-28 22:59 ` [FFmpeg-devel] [PATCH v5] libavformat: " Marth64
2 siblings, 2 replies; 27+ messages in thread
From: Marth64 @ 2024-01-11 3:46 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Marth64
Fixes for several authoring styles/discs.
ffmpeg CLI may report wrong output time and show progress strangely for some discs.
This is not reproducible prior to `fftools/ffmpeg: convert to a threaded architecture`
But, I am looking into it.
Signed-off-by: Marth64 <marth64@proxyid.net>
---
Changelog | 1 +
configure | 8 +
libavformat/Makefile | 1 +
libavformat/allformats.c | 1 +
libavformat/avlanguage.c | 10 +-
libavformat/dvdvideodec.c | 1001 +++++++++++++++++++++++++++++++++++++
6 files changed, 1020 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 <next>:
- 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..856951a8f0
--- /dev/null
+++ b/libavformat/dvdvideodec.c
@@ -0,0 +1,1001 @@
+/*
+ * DVD-Video demuxer, powered by libdvdnav and libdvdread
+ * Author: Marth64 <marth64@proxyid.net>
+ *
+ * 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 <dvdread/dvd_reader.h>
+#include <dvdread/ifo_read.h>
+#include <dvdread/ifo_types.h>
+#include <dvdread/nav_read.h>
+#include <dvdnav/dvdnav.h>
+
+#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 */
+ int play_end; /* signal to the parent demuxer that we are done */
+
+} 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;
+ c->play_ts_offset = 0;
+ c->play_skip_cell = 0;
+ c->play_skip_cell_last = 0;
+ c->play_end = 0;
+
+ 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 = 0;
+
+ 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 ret;
+}
+
+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 = (AVChannelLayout) { 0 };
+ 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 == 7)
+ ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_6POINT1;
+ else if (nb_channels == 8)
+ ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_7POINT1;
+
+ 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 = 0;
+
+ 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 uses karaoke extension\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 ret;
+}
+
+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 = 0;
+ 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)))
+ goto end_eof;
+
+ switch (nav_event) {
+ case DVDNAV_VTS_CHANGE:
+ if (c->play_in_vts)
+ goto end_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) {
+ goto end_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)
+ goto end_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)
+ goto end_eof;
+
+ dvdnav_still_skip(c->dvdnav);
+
+ continue;
+ case DVDNAV_WAIT:
+ if (c->play_in_ps)
+ goto end_eof;
+
+ dvdnav_wait_skip(c->dvdnav);
+
+ continue;
+ case DVDNAV_HOP_CHANNEL:
+ case DVDNAV_HIGHLIGHT:
+ if (c->play_in_ps)
+ goto end_eof;
+
+ continue;
+ case DVDNAV_STOP:
+ goto end_eof;
+ default:
+ continue;
+ }
+ }
+
+ av_log(s, AV_LOG_ERROR, "Unable to find next program stream block\n");
+
+end_eof:
+ c->play_end = 1;
+ 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 = 0;
+
+ if (!(c->mpeg_fmt = av_find_input_format("mpeg")))
+ return AVERROR_DEMUXER_NOT_FOUND;
+
+ if (!(c->mpeg_ctx = avformat_alloc_context()))
+ return AVERROR(ENOMEM);
+
+ if (!(c->mpeg_buf = av_malloc(DVDVIDEO_BLOCK_SIZE))) {
+ avformat_free_context(c->mpeg_ctx);
+
+ return AVERROR(ENOMEM);
+ }
+
+ ffio_init_context(&c->mpeg_pb, c->mpeg_buf, DVDVIDEO_BLOCK_SIZE, 0, s,
+ dvdvideo_subdemux_read_data, NULL, NULL);
+ c->mpeg_pb.pub.seekable = 0;
+
+ if ((ret = ff_copy_whiteblacklists(c->mpeg_ctx, s)) < 0) {
+ avformat_free_context(c->mpeg_ctx);
+
+ return ret;
+ }
+
+ c->mpeg_ctx->flags = AVFMT_FLAG_CUSTOM_IO | AVFMT_FLAG_GENPTS;
+ c->mpeg_ctx->ctx_flags |= AVFMTCTX_UNSEEKABLE;
+ c->mpeg_ctx->probesize = 0;
+ c->mpeg_ctx->max_analyze_duration = 0;
+ c->mpeg_ctx->interrupt_callback = s->interrupt_callback;
+ c->mpeg_ctx->pb = &c->mpeg_pb.pub;
+ c->mpeg_ctx->io_open = NULL;
+
+ if ((ret = avformat_open_input(&c->mpeg_ctx, "", c->mpeg_fmt, NULL)) < 0) {
+ avformat_free_context(c->mpeg_ctx);
+
+ return ret;
+ }
+
+ return ret;
+}
+
+static int dvdvideo_read_header(AVFormatContext *s)
+{
+ int ret = 0;
+
+ if ((ret = dvdvideo_pgc_open(s)) < 0
+ || (ret = dvdvideo_pgc_chapters_setup(s)) < 0
+ || (ret = dvdvideo_subdemux_open(s)) < 0
+ || (ret = dvdvideo_video_stream_setup(s)) < 0
+ || (ret = dvdvideo_audio_stream_add_all(s)) < 0
+ || (ret = dvdvideo_subp_stream_add_all(s)) < 0)
+ return ret;
+
+ return ret;
+}
+
+static int dvdvideo_read_packet(AVFormatContext *s, AVPacket *pkt)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret = 0;
+
+ if (c->play_end)
+ return AVERROR_EOF;
+
+ 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 0x%02x in playback\n",
+ c->mpeg_ctx->streams[c->mpeg_ctx->nb_streams - 1]->id);
+
+ 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_close(AVFormatContext *s)
+{
+ dvdvideo_subdemux_close(s);
+ dvdvideo_pgc_close(s);
+
+ 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_close = dvdvideo_close,
+ .read_header = dvdvideo_read_header,
+ .read_packet = dvdvideo_read_packet
+};
--
2.34.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH v3] dvdvideo: add DVD-Video demuxer, powered by libdvdnav and libdvdread
2024-01-11 3:46 ` [FFmpeg-devel] [PATCH v3] " Marth64
@ 2024-01-11 3:48 ` Marth64
2024-01-24 0:17 ` Stefano Sabatini
1 sibling, 0 replies; 27+ messages in thread
From: Marth64 @ 2024-01-11 3:48 UTC (permalink / raw)
To: Marth64; +Cc: ffmpeg-devel
Also deleted more redundant code in v3^
On Wed, Jan 10, 2024 at 9:48 PM Marth64 <marth64@proxyid.net> wrote:
> Fixes for several authoring styles/discs.
> ffmpeg CLI may report wrong output time and show progress strangely for
> some discs.
> This is not reproducible prior to `fftools/ffmpeg: convert to a threaded
> architecture`
> But, I am looking into it.
>
> Signed-off-by: Marth64 <marth64@proxyid.net>
> ---
> Changelog | 1 +
> configure | 8 +
> libavformat/Makefile | 1 +
> libavformat/allformats.c | 1 +
> libavformat/avlanguage.c | 10 +-
> libavformat/dvdvideodec.c | 1001 +++++++++++++++++++++++++++++++++++++
> 6 files changed, 1020 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 <next>:
> - 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..856951a8f0
> --- /dev/null
> +++ b/libavformat/dvdvideodec.c
> @@ -0,0 +1,1001 @@
> +/*
> + * DVD-Video demuxer, powered by libdvdnav and libdvdread
> + * Author: Marth64 <marth64@proxyid.net>
> + *
> + * 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 <dvdread/dvd_reader.h>
> +#include <dvdread/ifo_read.h>
> +#include <dvdread/ifo_types.h>
> +#include <dvdread/nav_read.h>
> +#include <dvdnav/dvdnav.h>
> +
> +#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 */
> + int play_end; /* signal to the
> parent demuxer that we are done */
> +
> +} 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;
> + c->play_ts_offset = 0;
> + c->play_skip_cell = 0;
> + c->play_skip_cell_last = 0;
> + c->play_end = 0;
> +
> + 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 = 0;
> +
> + 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 ret;
> +}
> +
> +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 = (AVChannelLayout) { 0 };
> + 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 == 7)
> + ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_6POINT1;
> + else if (nb_channels == 8)
> + ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_7POINT1;
> +
> + 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 = 0;
> +
> + 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 uses karaoke
> extension\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 ret;
> +}
> +
> +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 = 0;
> + 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)))
> + goto end_eof;
> +
> + switch (nav_event) {
> + case DVDNAV_VTS_CHANGE:
> + if (c->play_in_vts)
> + goto end_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) {
> + goto end_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)
> + goto end_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)
> + goto end_eof;
> +
> + dvdnav_still_skip(c->dvdnav);
> +
> + continue;
> + case DVDNAV_WAIT:
> + if (c->play_in_ps)
> + goto end_eof;
> +
> + dvdnav_wait_skip(c->dvdnav);
> +
> + continue;
> + case DVDNAV_HOP_CHANNEL:
> + case DVDNAV_HIGHLIGHT:
> + if (c->play_in_ps)
> + goto end_eof;
> +
> + continue;
> + case DVDNAV_STOP:
> + goto end_eof;
> + default:
> + continue;
> + }
> + }
> +
> + av_log(s, AV_LOG_ERROR, "Unable to find next program stream block\n");
> +
> +end_eof:
> + c->play_end = 1;
> + 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 = 0;
> +
> + if (!(c->mpeg_fmt = av_find_input_format("mpeg")))
> + return AVERROR_DEMUXER_NOT_FOUND;
> +
> + if (!(c->mpeg_ctx = avformat_alloc_context()))
> + return AVERROR(ENOMEM);
> +
> + if (!(c->mpeg_buf = av_malloc(DVDVIDEO_BLOCK_SIZE))) {
> + avformat_free_context(c->mpeg_ctx);
> +
> + return AVERROR(ENOMEM);
> + }
> +
> + ffio_init_context(&c->mpeg_pb, c->mpeg_buf, DVDVIDEO_BLOCK_SIZE, 0, s,
> + dvdvideo_subdemux_read_data, NULL, NULL);
> + c->mpeg_pb.pub.seekable = 0;
> +
> + if ((ret = ff_copy_whiteblacklists(c->mpeg_ctx, s)) < 0) {
> + avformat_free_context(c->mpeg_ctx);
> +
> + return ret;
> + }
> +
> + c->mpeg_ctx->flags = AVFMT_FLAG_CUSTOM_IO | AVFMT_FLAG_GENPTS;
> + c->mpeg_ctx->ctx_flags |= AVFMTCTX_UNSEEKABLE;
> + c->mpeg_ctx->probesize = 0;
> + c->mpeg_ctx->max_analyze_duration = 0;
> + c->mpeg_ctx->interrupt_callback = s->interrupt_callback;
> + c->mpeg_ctx->pb = &c->mpeg_pb.pub;
> + c->mpeg_ctx->io_open = NULL;
> +
> + if ((ret = avformat_open_input(&c->mpeg_ctx, "", c->mpeg_fmt, NULL))
> < 0) {
> + avformat_free_context(c->mpeg_ctx);
> +
> + return ret;
> + }
> +
> + return ret;
> +}
> +
> +static int dvdvideo_read_header(AVFormatContext *s)
> +{
> + int ret = 0;
> +
> + if ((ret = dvdvideo_pgc_open(s)) < 0
> + || (ret = dvdvideo_pgc_chapters_setup(s)) < 0
> + || (ret = dvdvideo_subdemux_open(s)) < 0
> + || (ret = dvdvideo_video_stream_setup(s)) < 0
> + || (ret = dvdvideo_audio_stream_add_all(s)) < 0
> + || (ret = dvdvideo_subp_stream_add_all(s)) < 0)
> + return ret;
> +
> + return ret;
> +}
> +
> +static int dvdvideo_read_packet(AVFormatContext *s, AVPacket *pkt)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret = 0;
> +
> + if (c->play_end)
> + return AVERROR_EOF;
> +
> + 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 0x%02x in
> playback\n",
> + c->mpeg_ctx->streams[c->mpeg_ctx->nb_streams -
> 1]->id);
> +
> + 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_close(AVFormatContext *s)
> +{
> + dvdvideo_subdemux_close(s);
> + dvdvideo_pgc_close(s);
> +
> + 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_close = dvdvideo_close,
> + .read_header = dvdvideo_read_header,
> + .read_packet = dvdvideo_read_packet
> +};
> --
> 2.34.1
>
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH v3] dvdvideo: add DVD-Video demuxer, powered by libdvdnav and libdvdread
2024-01-11 3:46 ` [FFmpeg-devel] [PATCH v3] " Marth64
2024-01-11 3:48 ` Marth64
@ 2024-01-24 0:17 ` Stefano Sabatini
2024-01-24 0:58 ` Marth64
1 sibling, 1 reply; 27+ messages in thread
From: Stefano Sabatini @ 2024-01-24 0:17 UTC (permalink / raw)
To: FFmpeg development discussions and patches; +Cc: Marth64
On date Wednesday 2024-01-10 21:46:38 -0600, Marth64 wrote:
> Fixes for several authoring styles/discs.
> ffmpeg CLI may report wrong output time and show progress strangely for some discs.
> This is not reproducible prior to `fftools/ffmpeg: convert to a threaded architecture`
> But, I am looking into it.
>
> Signed-off-by: Marth64 <marth64@proxyid.net>
> ---
> Changelog | 1 +
> configure | 8 +
> libavformat/Makefile | 1 +
> libavformat/allformats.c | 1 +
> libavformat/avlanguage.c | 10 +-
> libavformat/dvdvideodec.c | 1001 +++++++++++++++++++++++++++++++++++++
> 6 files changed, 1020 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 <next>:
> - 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 }
> };
unrelated? might be committed as a separate patch
>
> diff --git a/libavformat/dvdvideodec.c b/libavformat/dvdvideodec.c
> new file mode 100644
> index 0000000000..856951a8f0
> --- /dev/null
> +++ b/libavformat/dvdvideodec.c
> @@ -0,0 +1,1001 @@
> +/*
> + * DVD-Video demuxer, powered by libdvdnav and libdvdread
> + * Author: Marth64 <marth64@proxyid.net>
> + *
> + * 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
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
typo: angle changes
> + * 7) Close resources
> + **/
some of this content migth be used to provide documentation in
doc/demuxers.texi.
> +
> +#include <dvdread/dvd_reader.h>
> +#include <dvdread/ifo_read.h>
> +#include <dvdread/ifo_types.h>
> +#include <dvdread/nav_read.h>
> +#include <dvdnav/dvdnav.h>
> +
> +#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 */
> + int play_end; /* signal to the parent demuxer that we are done */
> +
> +} 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");
mention the index of the title
> +
> + 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");
ditto
> +
> + return AVERROR_STREAM_NOT_FOUND;
> + }
> +
> + if (title_info.nr_of_ptts < 1) {
> + av_log(s, AV_LOG_ERROR, "Title invalid\n");
provide more context here to aid debugging
> +
> + 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) {
nit+: style, align clauses to title_info, move || in the previous line
> + av_log(s, AV_LOG_ERROR, "Title invalid in VTS\n");
provide more context about what is wrong here
> +
> + 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)
ditto here and below
> + 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;
please provide more debugging info here
[...]
Thanks.
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH v3] dvdvideo: add DVD-Video demuxer, powered by libdvdnav and libdvdread
2024-01-24 0:17 ` Stefano Sabatini
@ 2024-01-24 0:58 ` Marth64
0 siblings, 0 replies; 27+ messages in thread
From: Marth64 @ 2024-01-24 0:58 UTC (permalink / raw)
To: FFmpeg development discussions and patches, Marth64
Thank you Stefano, I will send updated version soon with also several bug
fixes and improvements.
On Tue, Jan 23, 2024 at 18:17 Stefano Sabatini <stefasab@gmail.com> wrote:
> On date Wednesday 2024-01-10 21:46:38 -0600, Marth64 wrote:
> > Fixes for several authoring styles/discs.
> > ffmpeg CLI may report wrong output time and show progress strangely for
> some discs.
> > This is not reproducible prior to `fftools/ffmpeg: convert to a threaded
> architecture`
> > But, I am looking into it.
> >
> > Signed-off-by: Marth64 <marth64@proxyid.net>
> > ---
> > Changelog | 1 +
> > configure | 8 +
> > libavformat/Makefile | 1 +
> > libavformat/allformats.c | 1 +
> > libavformat/avlanguage.c | 10 +-
> > libavformat/dvdvideodec.c | 1001 +++++++++++++++++++++++++++++++++++++
> > 6 files changed, 1020 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 <next>:
> > - 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 }
> > };
>
> unrelated? might be committed as a separate patch
> >
> > diff --git a/libavformat/dvdvideodec.c b/libavformat/dvdvideodec.c
> > new file mode 100644
> > index 0000000000..856951a8f0
> > --- /dev/null
> > +++ b/libavformat/dvdvideodec.c
> > @@ -0,0 +1,1001 @@
> > +/*
> > + * DVD-Video demuxer, powered by libdvdnav and libdvdread
> > + * Author: Marth64 <marth64@proxyid.net>
> > + *
> > + * 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; e
> <https://www.google.com/maps/search/ware+Foundation;+e?entry=gmail&source=g>
> ither
> > + * 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
>
> 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
>
> typo: angle changes
> > + * 7) Close resources
> > + **/
>
> some of this content migth be used to provide documentation in
> doc/demuxers.texi.
>
> > +
> > +#include <dvdread/dvd_reader.h>
> > +#include <dvdread/ifo_read.h>
> > +#include <dvdread/ifo_types.h>
> > +#include <dvdread/nav_read.h>
> > +#include <dvdnav/dvdnav.h>
> > +
> > +#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 */
> > + int play_end; /* signal to the
> parent demuxer that we are done */
> > +
> > +} 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");
>
> mention the index of the title
>
> > +
> > + 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");
>
> ditto
>
> > +
> > + return AVERROR_STREAM_NOT_FOUND;
> > + }
> > +
> > + if (title_info.nr_of_ptts < 1) {
>
> > + av_log(s, AV_LOG_ERROR, "Title invalid\n");
>
> provide more context here to aid debugging
>
> > +
> > + 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) {
>
> nit+: style, align clauses to title_info, move || in the previous line
>
>
> > + av_log(s, AV_LOG_ERROR, "Title invalid in VTS\n");
>
> provide more context about what is wrong here
>
> > +
> > + 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)
>
> ditto here and below
>
> > + 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;
>
> please provide more debugging info here
>
> [...]
>
> Thanks.
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* [FFmpeg-devel] [PATCH v5] libavformat: add DVD-Video demuxer, powered by libdvdnav and libdvdread
2024-01-10 8:46 ` [FFmpeg-devel] [PATCH v2] dvdvideo: add DVD-Video demuxer, powered by libdvdnav and libdvdread Marth64
2024-01-10 8:53 ` Marth64
2024-01-11 3:46 ` [FFmpeg-devel] [PATCH v3] " Marth64
@ 2024-01-28 22:59 ` Marth64
2024-01-31 23:57 ` Stefano Sabatini
2024-02-05 0:02 ` [FFmpeg-devel] [PATCH v6] libavformat/dvdvideo: add DVD-Video demuxer " Marth64
2 siblings, 2 replies; 27+ messages in thread
From: Marth64 @ 2024-01-28 22:59 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Marth64
- Add option to play only certain chapters or chapter ranges
- Add option to do a second pass indexing for accurate chapter markers
- Add documentation
- Fixes issues with PCM audio
- Support for essential track dispositions (forced, visual impaired, commentary)
- Better support for structure variances and padding cells/frames
- More user friendly log messages and errors
- dvdread/dvdnav logging is relatively very verbose, map lower levels as DEBUG logs
Signed-off-by: Marth64 <marth64@proxyid.net>
---
Changelog | 1 +
configure | 8 +
doc/demuxers.texi | 129 ++++
libavformat/Makefile | 1 +
libavformat/allformats.c | 1 +
libavformat/dvdvideodec.c | 1409 +++++++++++++++++++++++++++++++++++++
6 files changed, 1549 insertions(+)
create mode 100644 libavformat/dvdvideodec.c
diff --git a/Changelog b/Changelog
index c1fd66b4bd..9de157abe4 100644
--- a/Changelog
+++ b/Changelog
@@ -23,6 +23,7 @@ version <next>:
- ffmpeg CLI -bsf option may now be used for input as well as output
- ffmpeg CLI options may now be used as -/opt <path>, which is equivalent
to -opt <contents of file <path>>
+- DVD-Video demuxer, powered by libdvdnav and libdvdread
version 6.1:
- libaribcaption decoder
diff --git a/configure b/configure
index 21663000f8..d81420992a 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
@@ -3520,6 +3524,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"
@@ -6761,6 +6767,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/doc/demuxers.texi b/doc/demuxers.texi
index e4c5b560a6..f7f9e6769a 100644
--- a/doc/demuxers.texi
+++ b/doc/demuxers.texi
@@ -285,6 +285,135 @@ This demuxer accepts the following option:
@end table
+@section dvdvideo
+
+DVD-Video demuxer, powered by libdvdnav and libdvdread.
+
+Can directly ingest DVD titles, specifically sequential PGCs (see below),
+into a conversion pipeline. Menus and seeking are not supported at this time.
+Block devices (DVD drives), ISO files, and directory structures are accepted.
+Activate with @code{-f dvdvideo} in front of one of these inputs.
+
+Underlying playback is fully handled by libdvdnav, and structure parsing by libdvdread.
+Therefore, the ffmpeg build must be configured with these GPL libraries enabled.
+
+You will need to provide either the desired "title" number or exact PGC/PG coordinates.
+Many open-source DVD tools and players can aid in providing this information.
+If not specified, the demuxer will default to title 1 which works for many discs.
+However, due to the flexibility of the DVD standard, it is recommended to check manually.
+There are many discs that are authored strangely or with invalid headers.
+
+If the input is a real DVD drive, please note that there are some drives which may
+silently fail on reading bad sectors from the disc, returning random bits instead
+which is effectively corrupt data. This is especially prominent on aging or rotting discs.
+A second pass and integrity checks would be needed to detect the corruption.
+This is not an ffmpeg issue.
+
+@subsection Background
+
+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 MPEG-PS streams that are stored in headerless VOB files.
+To the end-user, these streams are known simply as "titles", but the actual
+logical playback sequence is defined by one or more "PGCs", or Program Group Chains,
+within the title. The sequence is in turn comprised of multiple "PGs", or Programs",
+which are the actual video segments. The PGC structure, along with stream layout
+and metadata, are stored in IFO files that need to be parsed.
+
+A typical DVD player relies on user GUI interaction via menus and an internal VM
+to drive the direction of demuxing. Generally, the user would either navigate (via menus)
+or automatically be redirected to the PGC of their choice. During this process and
+the subsequent playback, the DVD player's internal VM also maintains a state and
+executes instructions that can create jumps to different sectors during playback.
+This is why libdvdnav is involved, as a linear read of the MPEG-PS blobs on the
+disc (VOBs) is not enough to produce the right sequence in many cases.
+
+There are many other DVD structures (a long subject) that will not be discussed here.
+NAV packets, in particular, are handled by this demuxer to build accurate timing
+but not emitted as a stream. For a good high-level understanding, refer to:
+@url{https://code.videolan.org/videolan/libdvdnav/-/blob/master/doc/dvd_structures}
+
+@subsection Options
+
+This demuxer accepts the following options:
+
+@table @option
+
+@item title
+The title number to play. Must be set if @code{-pgc/pg} are not set.
+Default is 1, which is used by many discs.
+
+@item chapter_start
+The chapter, or PTT (part-of-title), number to start at. Default is 1.
+
+@item chapter_end
+The chapter, or PTT (part-of-title), number to end at. Default is 0,
+which is a special value to signal end at the last possible chapter.
+
+@item angle
+The video angle number, referring to what is essentially an alternative
+video stream that is interleaved in the VOBs. Default is 1.
+
+@item region
+The region code to use for playback. Some discs may use this to default playback
+at a particular angle in different regions. This option will not affect the region code
+of a real DVD drive, if used as an input. Default is 0, "world".
+
+@end table
+
+@subsection Examples
+
+Open title 3 from a given DVD structure:
+@example
+ffmpeg -f dvdvideo -title 3 -i <path to DVD> ...
+@end example
+
+Open chapters 3-6 from title 1 from a given DVD structure:
+@example
+ffmpeg -f dvdvideo -chapter_start 3 -chapter_end 6 -title 1 -i <path to DVD> ...
+@end example
+
+Open only chapters 5 from title 1 from a given DVD structure:
+@example
+ffmpeg -f dvdvideo -chapter_start 5 -chapter_end 5 -title 1 -i <path to DVD> ...
+@end example
+
+@subsection Advanced Options
+
+@table @option
+
+@item pgc
+The entry PGC to start playback, in conjunction with @code{-pg}.
+Alternative to setting @code{-title}.
+Chapter markers not supported at this time.
+Default is 0, automatically resolve from value of @code{-title}.
+
+@item pg
+The entry PG to start playback, in conjunction with @code{-pgc}.
+Alternative to setting @code{-title}.
+Chapter markers not supported at this time.
+Default is 0, automatically resolve from value of @code{-title}.
+
+@item preindex
+Enable this to have accurate chapter (PTT) markers and duration measurement,
+which requires a slow second pass read in order to index the chapter
+timestamps from NAV packets. This is non-ideal extra work for physical DVD drives.
+Can give imperfect results if not starting playback from chapter 1.
+Not compatible with @code{-pgc/pg}.
+Default is 0, false.
+
+@item trim
+Trim padding cells (i.e. cells shorter than 1 second) from the beginning of the title.
+Default is 1, true.
+
+@item wait_for_audio
+Wait for the first audio keyframe before emitting frames. This is necessary
+for a uniform output from many discs which insert AC3 delay or filler frames
+at the start of the PGC.
+Default is 1, true.
+
+@end table
+
@section ea
Electronic Arts Multimedia format demuxer.
diff --git a/libavformat/Makefile b/libavformat/Makefile
index dcc99eeac4..f0b42188a2 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 b04b43cab3..3e905c23f8 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/dvdvideodec.c b/libavformat/dvdvideodec.c
new file mode 100644
index 0000000000..b9ec3acaaf
--- /dev/null
+++ b/libavformat/dvdvideodec.c
@@ -0,0 +1,1409 @@
+/*
+ * DVD-Video demuxer, powered by libdvdnav and libdvdread
+ * Author: Marth64 <marth64@proxyid.net>
+ *
+ * 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
+ */
+
+/*
+ * See doc/demuxers.texi for a high-level overview.
+ *
+ * The tactical approach is as follows:
+ * 1) Open the volume with dvdread
+ * 2) Analyze the user-requested title and PGC coordinates in the IFO structures
+ * 3) Request playback at the coordinates and chosen angle with dvdnav
+ * 5) Begin the playback (reading and demuxing) of MPEG-PS blocks
+ * 6) End playback if navigation goes backwards, to a menu, or a different PGC or angle
+ * 7) Close the dvdnav VM, and free dvdnav's IFO structures
+ */
+
+#include <dvdnav/dvdnav.h>
+#include <dvdread/dvd_reader.h>
+#include <dvdread/ifo_read.h>
+#include <dvdread/ifo_types.h>
+#include <dvdread/nav_read.h>
+
+#include "libavcodec/avcodec.h"
+#include "libavutil/avstring.h"
+#include "libavutil/avutil.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+#include "libavutil/samplefmt.h"
+#include "libavutil/time.h"
+#include "libavutil/timestamp.h"
+
+#include "avformat.h"
+#include "avio_internal.h"
+#include "avlanguage.h"
+#include "demux.h"
+#include "internal.h"
+#include "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 64 /* VOBUs use 32 (PES allows 33) */
+#define DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE 1024
+
+enum DVDVideoSubpictureViewport {
+ DVDVIDEO_SUBP_VIEWPORT_FULLSCREEN,
+ DVDVIDEO_SUBP_VIEWPORT_WIDESCREEN,
+ DVDVIDEO_SUBP_VIEWPORT_LETTERBOX,
+ DVDVIDEO_SUBP_VIEWPORT_PANSCAN
+};
+const char* VIEWPORT_LABELS[4] = { "Fullscreen", "Widescreen", "Letterbox", "Pan and Scan" };
+
+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;
+ int disposition;
+ char *lang_iso;
+} DVDVideoPGCAudioStreamEntry;
+
+typedef struct DVDVideoPGCSubtitleStreamEntry {
+ int startcode;
+ int disposition;
+ char *lang_iso;
+ enum DVDVideoSubpictureViewport viewport;
+} DVDVideoPGCSubtitleStreamEntry;
+
+typedef struct DVDVideoPlaybackState {
+ int celln; /* ID of the active cell */
+ int entry_pgn; /* ID of the PG we are starting in */
+ int in_pgc; /* if our navigator is in the PGC */
+ int in_ps; /* if our navigator is in the program stream */
+ int in_vts; /* if our navigator is in the VTS */
+ int64_t nav_pts; /* PTS according to IFO, not frame-accurate */
+ uint64_t pgc_duration_est; /* estimated duration as reported by IFO */
+ uint64_t pgc_elapsed; /* the elapsed time of the PGC, cell-relative */
+ int pgc_nb_pg_est; /* number of PGs as reported by IFOs */
+ int pgcn; /* ID of the PGC we are playing */
+ int pgn; /* ID of the PG we are in now */
+ int ptt; /* ID of the chapter we are in now */
+ int64_t ts_offset; /* PTS discontinuity offset (ex. VOB change) */
+ uint32_t vobu_duration; /* duration of the current VOBU */
+ uint32_t vobu_e_ptm; /* end PTS of the current VOBU */
+ int vtsn; /* ID of the active VTS (video title set) */
+ uint64_t *pgc_pg_times_est; /* PG start times as reported by IFO */
+ pgc_t *pgc; /* handle to the active PGC */
+ dvdnav_t *dvdnav; /* handle to libdvdnav */
+} DVDVideoPlaybackState;
+
+typedef struct DVDVideoDemuxContext {
+ const AVClass *class;
+
+ /* options */
+ int opt_title; /* the user-provided title number (1-indexed) */
+ int opt_chapter_start; /* the user-provided entry PTT (1-indexed) */
+ int opt_chapter_end; /* the user-provided exit PTT (0 for last) */
+ 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 */
+ int opt_preindex; /* pre-indexing mode (2-pass read) */
+ int opt_trim; /* trim padding cells at beginning and end */
+ int opt_wait_for_audio; /* wait for audio streams to start, if any */
+
+ /* 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) */
+
+ /* playback control */
+ int play_end; /* signal EOF to the parent demuxer */
+ DVDVideoPlaybackState play_state; /* the active playback state */
+ int play_started; /* signal that playback has started */
+ int play_seg_started; /* signal that segment has started */
+ int64_t play_pts_offset; /* the PTS offset of the PGC */
+ int play_has_audio; /* if we are expecting audio streams */
+ uint64_t duration_ptm; /* total duration in DVD MPEG timebase */
+} DVDVideoDemuxContext;
+
+static void dvdvideo_libdvdread_log(void *opaque, dvd_logger_level_t level,
+ const char *msg, va_list msg_va)
+{
+ AVFormatContext *s = opaque;
+ int lavu_level;
+
+ char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
+ vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
+
+ switch (level) {
+ case DVD_LOGGER_LEVEL_ERROR:
+ lavu_level = AV_LOG_ERROR;
+ break;
+ case DVD_LOGGER_LEVEL_WARN:
+ lavu_level = AV_LOG_WARNING;
+ break;
+ /* dvdread's info messages are relatively very verbose, muffle them as debug */
+ case DVD_LOGGER_LEVEL_INFO:
+ case DVD_LOGGER_LEVEL_DEBUG:
+ default:
+ lavu_level = AV_LOG_DEBUG;
+ }
+
+ av_log(s, lavu_level, "libdvdread: %s\n", msg_buf);
+}
+
+static void dvdvideo_libdvdnav_log(void *opaque, dvdnav_logger_level_t level,
+ const char *msg, va_list msg_va)
+{
+ AVFormatContext *s = opaque;
+ int lavu_level;
+
+ char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
+ vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
+
+ switch (level) {
+ case DVDNAV_LOGGER_LEVEL_ERROR:
+ lavu_level = AV_LOG_ERROR;
+ break;
+ case DVDNAV_LOGGER_LEVEL_WARN:
+ /* some discs have invalid language codes set for their menus, muffle the noise */
+ lavu_level = av_strstart(msg, "Language", NULL) ? AV_LOG_DEBUG : AV_LOG_WARNING;
+ break;
+ /* dvdnav's info messages are relatively very verbose, muffle them as debug */
+ case DVDNAV_LOGGER_LEVEL_INFO:
+ case DVDNAV_LOGGER_LEVEL_DEBUG:
+ default:
+ lavu_level = AV_LOG_DEBUG;
+ }
+
+ av_log(s, lavu_level, "libdvdnav: %s\n", msg_buf);
+}
+
+static void dvdvideo_ifo_close(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ 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_ifo_open(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ dvd_logger_cb dvdread_log_cb;
+ title_info_t title_info;
+
+ dvdread_log_cb = (dvd_logger_cb) { .pf_log = dvdvideo_libdvdread_log };
+ c->dvdread = DVDOpen2(s, &dvdread_log_cb, s->url);
+
+ if (!c->dvdread) {
+ av_log(s, AV_LOG_ERROR, "Unable to open the DVD-Video structure\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ if (!(c->vmg_ifo = ifoOpen(c->dvdread, 0))) {
+ av_log(s, AV_LOG_ERROR, "Unable to open the VMG (VIDEO_TS.IFO)\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ if (c->opt_title > c->vmg_ifo->tt_srpt->nr_of_srpts) {
+ av_log(s, AV_LOG_ERROR, "Title %d not found\n", c->opt_title);
+
+ 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 %d not found\n", c->opt_angle);
+
+ return AVERROR_STREAM_NOT_FOUND;
+ }
+
+ if (title_info.nr_of_ptts < 1) {
+ av_log(s, AV_LOG_ERROR, "Title %d has invalid headers in VMG\n", c->opt_title);
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (c->opt_chapter_start > title_info.nr_of_ptts
+ || (c->opt_chapter_end > 0 && c->opt_chapter_end > title_info.nr_of_ptts)) {
+ av_log(s, AV_LOG_ERROR, "Chapter (PTT) range provided is invalid\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (!(c->vts_ifo = ifoOpen(c->dvdread, title_info.title_set_nr))) {
+ av_log(s, AV_LOG_ERROR, "Unable to process the VTS IFO structure\n");
+
+ return AVERROR_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 %d has invalid headers in VTS\n", c->opt_title);
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ return 0;
+}
+
+static void dvdvideo_play_close(AVFormatContext *s, DVDVideoPlaybackState *state)
+{
+ if (!state->dvdnav)
+ return;
+
+ /* not allocated by av_malloc() */
+ if (state->pgc_pg_times_est)
+ free(state->pgc_pg_times_est);
+
+ if (dvdnav_close(state->dvdnav) < 0)
+ av_log(s, AV_LOG_ERROR, "Unable to cleanly close dvdnav\n");
+}
+
+static int dvdvideo_play_open(AVFormatContext *s, DVDVideoPlaybackState *state)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret = 0;
+ dvdnav_logger_cb dvdnav_log_cb;
+ dvdnav_status_t dvdnav_open_status;
+ int cur_title, cur_pgcn, cur_pgn;
+ pgc_t *pgc;
+
+ int32_t disc_region_mask;
+ int32_t player_region_mask;
+
+ dvdnav_log_cb = (dvdnav_logger_cb) { .pf_log = dvdvideo_libdvdnav_log };
+ dvdnav_open_status = dvdnav_open2(&state->dvdnav, s, &dvdnav_log_cb, s->url);
+
+ if (!state->dvdnav
+ || dvdnav_open_status != DVDNAV_STATUS_OK
+ || dvdnav_set_readahead_flag(state->dvdnav, 0) != DVDNAV_STATUS_OK
+ || dvdnav_set_PGC_positioning_flag(state->dvdnav, 1) != DVDNAV_STATUS_OK
+ || dvdnav_get_region_mask(state->dvdnav, &disc_region_mask) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to open the DVD for playback\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ player_region_mask = c->opt_region > 0 ? (1 << (c->opt_region - 1)) : disc_region_mask;
+ if (dvdnav_set_region_mask(state->dvdnav, player_region_mask) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to set the playback region\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ if (c->opt_pgc > 0 && c->opt_pg > 0) {
+ if (dvdnav_program_play(state->dvdnav, c->opt_title,
+ c->opt_pgc, c->opt_pg) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to start playback at the given title or part\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ state->pgcn = c->opt_pgc;
+ state->entry_pgn = c->opt_pg;
+ } else {
+ if (dvdnav_part_play(state->dvdnav, c->opt_title, c->opt_chapter_start) != DVDNAV_STATUS_OK
+ || dvdnav_current_title_program(state->dvdnav, &cur_title,
+ &cur_pgcn, &cur_pgn) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to start playback at the given PGC/PG coordinates\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ state->pgcn = cur_pgcn;
+ state->entry_pgn = cur_pgn;
+ }
+
+ pgc = c->vts_ifo->vts_pgcit->pgci_srp[state->pgcn - 1].pgc;
+
+ if (pgc->pg_playback_mode != 0) {
+ av_log(s, AV_LOG_ERROR, "Sorry, non-sequential PGCs are not supported\n");
+
+ return AVERROR_PATCHWELCOME;
+ }
+
+ if (dvdnav_angle_change(state->dvdnav, c->opt_angle) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to start playback at the given angle\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ /* dvdnav_describe_title_chapters() performs several safety checks on the title structure */
+ /* take advantage of this side effect to ensure a safe navigation path */
+ state->pgc_nb_pg_est = dvdnav_describe_title_chapters(state->dvdnav, c->opt_title,
+ &state->pgc_pg_times_est,
+ &state->pgc_duration_est);
+ if (!state->pgc_nb_pg_est) {
+ av_log(s, AV_LOG_ERROR, "Unable to validate title structure\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ state->nav_pts = dvdnav_get_current_time(state->dvdnav);
+ state->vtsn = c->vmg_ifo->tt_srpt->title[c->opt_title - 1].title_set_nr;
+
+ state->pgc = pgc;
+
+ return ret;
+}
+
+static int dvdvideo_play_is_cell_promising(AVFormatContext *s, DVDVideoPlaybackState *state,
+ int celln)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+ dvd_time_t cell_duration;
+
+ if (!c->opt_trim)
+ return 1;
+
+ cell_duration = state->pgc->cell_playback[celln - 1].playback_time;
+
+ return cell_duration.second >= 1 || cell_duration.minute >= 1 || cell_duration.hour >= 1;
+}
+
+static int dvdvideo_play_next_ps_block(AVFormatContext *s, DVDVideoPlaybackState *state,
+ uint8_t *buf, int buf_size,
+ int *p_nav_event,
+ void (*flush_cb)(AVFormatContext *s))
+{
+ 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;
+ int is_cell_promising = 0;
+ pci_t *e_pci;
+ dsi_t *e_dsi;
+
+ 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(state->dvdnav, nav_buf, &nav_event, &nav_len) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to read next block of PGC\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ /* some discs follow NOPs with a premature stop event */
+ if (nav_event == DVDNAV_STOP)
+ return AVERROR_EOF;
+
+ if (nav_len > DVDVIDEO_BLOCK_SIZE) {
+ av_log(s, AV_LOG_ERROR, "Invalid block size\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (dvdnav_current_title_info(state->dvdnav, &cur_title,
+ &cur_ptt) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to validate title coordinates\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ /* we somehow navigated to a menu */
+ if (cur_title == 0 || !dvdnav_is_domain_vts(state->dvdnav))
+ return AVERROR_EOF;
+
+ if (dvdnav_current_title_program(state->dvdnav, &cur_title_unused,
+ &cur_pgcn, &cur_pgn) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to validate PGC coordinates\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ /* we somehow left the PGC */
+ if (state->in_pgc && cur_pgcn != state->pgcn)
+ return AVERROR_EOF;
+
+ if (dvdnav_get_angle_info(state->dvdnav, &cur_angle, &cur_nb_angles) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to validate angle coordinates\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ av_log(s, nav_event == DVDNAV_BLOCK_OK ? AV_LOG_TRACE : AV_LOG_DEBUG,
+ "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, state->celln, cur_pgcn, cur_pgn,
+ state->in_vts, state->in_pgc, state->in_ps);
+
+ switch (nav_event) {
+ case DVDNAV_VTS_CHANGE:
+ if (state->in_vts)
+ return AVERROR_EOF;
+
+ e_vts = (dvdnav_vts_change_event_t *) nav_buf;
+
+ if (e_vts->new_vtsN == state->vtsn && e_vts->new_domain == DVD_DOMAIN_VTSTitle)
+ state->in_vts = 1;
+
+ continue;
+ case DVDNAV_CELL_CHANGE:
+ if (!state->in_vts)
+ continue;
+
+ e_cell = (dvdnav_cell_change_event_t *) nav_buf;
+ is_cell_promising = dvdvideo_play_is_cell_promising(s, state, e_cell->cellN);
+
+ av_log(s, AV_LOG_DEBUG, "new cell: prev=%d new=%d promising=%d\n",
+ state->celln, e_cell->cellN, is_cell_promising);
+
+ if (!state->in_ps && !state->in_pgc) {
+ if (cur_title == c->opt_title && cur_ptt == c->opt_chapter_start
+ && cur_pgcn == state->pgcn && cur_pgn == state->entry_pgn
+ && is_cell_promising) {
+ state->in_pgc = 1;
+ }
+
+ if (c->opt_trim && !is_cell_promising)
+ av_log(s, AV_LOG_WARNING, "Skipping padding cell #%d\n", e_cell->cellN);
+ } else if (state->celln >= e_cell->cellN || state->pgn > cur_pgn) {
+ return AVERROR_EOF;
+ }
+
+ state->celln = e_cell->cellN;
+ state->ptt = cur_ptt;
+ state->pgn = cur_pgn;
+
+ continue;
+ case DVDNAV_NAV_PACKET:
+ if (!state->in_pgc)
+ continue;
+
+ if ((state->ptt > 0 && state->ptt > cur_ptt)
+ || (c->opt_chapter_end > 0 && cur_ptt > c->opt_chapter_end)) {
+ return AVERROR_EOF;
+ }
+
+ e_pci = dvdnav_get_current_nav_pci(state->dvdnav);
+ e_dsi = dvdnav_get_current_nav_dsi(state->dvdnav);
+
+ if (e_pci == NULL || e_dsi == NULL
+ || e_pci->pci_gi.vobu_s_ptm > e_pci->pci_gi.vobu_e_ptm) {
+ av_log(s, AV_LOG_ERROR, "Invalid NAV packet\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ state->vobu_duration = e_pci->pci_gi.vobu_e_ptm - e_pci->pci_gi.vobu_s_ptm;
+ state->pgc_elapsed += state->vobu_duration;
+ state->nav_pts = dvdnav_get_current_time(state->dvdnav);
+ state->ptt = cur_ptt;
+ state->pgn = cur_pgn;
+
+ av_log(s, AV_LOG_DEBUG, "NAV pack: s_ptm=%d e_ptm=%d "
+ "scr=%d lbn=%d vobu_duration=%d nav_pts=%ld\n",
+ e_pci->pci_gi.vobu_s_ptm, e_pci->pci_gi.vobu_e_ptm,
+ e_dsi->dsi_gi.nv_pck_scr,
+ e_pci->pci_gi.nv_pck_lbn, state->vobu_duration, state->nav_pts);
+
+ if (!state->in_ps) {
+ av_log(s, AV_LOG_DEBUG, "navigation: locked to program stream\n");
+
+ state->in_ps = 1;
+
+ /* HACK: compensate chapter markers for junk frames if starting after ptt 1 */
+ if (c->opt_preindex && c->opt_chapter_start > 1)
+ state->vobu_duration += 45000;
+ } else {
+ if (state->vobu_e_ptm != e_pci->pci_gi.vobu_s_ptm) {
+ if (flush_cb)
+ flush_cb(s);
+
+ state->ts_offset += state->vobu_e_ptm - e_pci->pci_gi.vobu_s_ptm;
+ }
+ }
+
+ state->vobu_e_ptm = e_pci->pci_gi.vobu_e_ptm;
+
+ (*p_nav_event) = nav_event;
+
+ return nav_len;
+ case DVDNAV_BLOCK_OK:
+ if (!state->in_ps) {
+ if (state->in_pgc)
+ i = 0; /* necessary in case we are skpping junk cells at the beginning */
+ continue;
+ }
+
+ if (nav_len != DVDVIDEO_BLOCK_SIZE) {
+ av_log(s, AV_LOG_ERROR, "Invalid block size\n");
+
+ 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);
+
+ /* in case NAV packet is missed */
+ state->ptt = cur_ptt;
+ state->pgn = cur_pgn;
+
+ (*p_nav_event) = nav_event;
+
+ return nav_len;
+ case DVDNAV_STILL_FRAME:
+ case DVDNAV_WAIT:
+ case DVDNAV_HOP_CHANNEL:
+ case DVDNAV_HIGHLIGHT:
+ if (state->in_ps)
+ return AVERROR_EOF;
+
+ if (nav_event == DVDNAV_STILL_FRAME)
+ dvdnav_still_skip(state->dvdnav);
+ if (nav_event == DVDNAV_WAIT)
+ dvdnav_wait_skip(state->dvdnav);
+
+ 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_INVALIDDATA;
+}
+
+static int dvdvideo_pgc_preindex(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret = 0, interrupt = 0;
+ int nb_chapters = 0, last_ptt = c->opt_chapter_start;
+ uint64_t cur_chapter_offset = 0, cur_chapter_duration = 0;
+ DVDVideoPlaybackState *state;
+
+ uint8_t nav_buf[DVDVIDEO_BLOCK_SIZE];
+ int nav_event;
+
+ if (c->opt_chapter_start == c->opt_chapter_end)
+ return 0;
+
+ av_log(s, AV_LOG_WARNING,
+ "Indexing chapter markers, this will take a long time. Please wait...\n");
+
+ state = av_mallocz(sizeof(DVDVideoPlaybackState));
+ if ((ret = dvdvideo_play_open(s, state)) < 0) {
+ av_freep(&state);
+ return ret;
+ }
+
+ while (!(interrupt = ff_check_interrupt(&s->interrupt_callback))) {
+ ret = dvdvideo_play_next_ps_block(s, state, nav_buf, DVDVIDEO_BLOCK_SIZE,
+ &nav_event, NULL);
+ if (ret < 0 && ret != AVERROR_EOF)
+ goto end_free;
+
+ if (nav_event != DVDNAV_NAV_PACKET && ret != AVERROR_EOF)
+ continue;
+
+ if (state->ptt == last_ptt) {
+ cur_chapter_duration += state->vobu_duration;
+ /* ensure we add the last chapter */
+ if (ret != AVERROR_EOF)
+ continue;
+ }
+
+ if (!avpriv_new_chapter(s, nb_chapters, DVDVIDEO_TIME_BASE_Q, cur_chapter_offset,
+ cur_chapter_offset + cur_chapter_duration, NULL)) {
+ av_log(s, AV_LOG_ERROR, "Unable to allocate chapter\n");
+
+ goto end_free;
+ }
+
+ nb_chapters++;
+ cur_chapter_offset += cur_chapter_duration;
+ cur_chapter_duration = state->vobu_duration;
+ last_ptt = state->ptt;
+
+ if (ret == AVERROR_EOF)
+ break;
+ }
+
+ if (interrupt) {
+ ret = AVERROR_EXIT;
+ goto end_free;
+ }
+
+ if (ret < 0 && ret != AVERROR_EOF)
+ goto end_free;
+
+ s->duration = av_rescale_q(state->pgc_elapsed, DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
+ c->duration_ptm = state->pgc_elapsed;
+
+ av_log(s, AV_LOG_INFO, "Chapter marker indexing complete\n");
+ ret = 0;
+
+end_free:
+ dvdvideo_play_close(s, state);
+ av_freep(&state);
+
+ return ret;
+}
+
+static int dvdvideo_pgc_chapters_setup(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ uint64_t time_prev = 0;
+ int64_t total_duration = 0;
+
+ int chapter_start = c->opt_chapter_start - 1;
+ int chapter_end = c->opt_chapter_end > 0 ? c->opt_chapter_end : c->play_state.pgc_nb_pg_est - 1;
+
+ if (chapter_start == chapter_end || c->play_state.pgc_nb_pg_est == 1) {
+ s->duration = av_rescale_q(c->play_state.pgc_duration_est,
+ DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
+ return 0;
+ }
+
+ for (int i = chapter_start; i < chapter_end; i++) {
+ uint64_t time_effective = c->play_state.pgc_pg_times_est[i] - c->play_state.nav_pts;
+
+ 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;
+ total_duration = time_effective;
+ }
+
+ if (c->opt_chapter_start == 1 && c->opt_chapter_end == 0)
+ s->duration = av_rescale_q(c->play_state.pgc_duration_est,
+ DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
+ else
+ s->duration = av_rescale_q(total_duration,
+ DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
+
+ return 0;
+}
+
+static int dvdvideo_video_stream_analyze(AVFormatContext *s, 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) {
+ av_log(s, AV_LOG_ERROR, "Invalid video dimensions\n");
+
+ 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 = !is_pal && (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 = NULL;
+ int ret = 0;
+
+ entry = av_mallocz(sizeof(DVDVideoVTSVideoStreamEntry));
+
+ if ((ret = dvdvideo_video_stream_analyze(s, 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_HEADERS)) < 0) {
+ av_freep(&entry);
+ av_log(s, AV_LOG_ERROR, "Unable to allocate video stream\n");
+
+ return ret;
+ }
+
+ av_freep(&entry);
+
+ return ret;
+}
+
+static int dvdvideo_audio_stream_analyze(AVFormatContext *s, 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 = (AVChannelLayout) {0};
+ char lang_dvd[3] = {0};
+
+ int position = (audio_control & 0x7F00) >> 8;
+
+ /* XXX(PATCHWELCOME): SDDS is not supported due to lack of sample material */
+ switch (audio_attr.audio_format) {
+ case 0: /* AC3 */
+ codec_id = AV_CODEC_ID_AC3;
+ sample_fmt = AV_SAMPLE_FMT_FLTP;
+ sample_rate = 48000;
+ startcode = 0x80 + position;
+ break;
+ case 2: /* MP1 */
+ 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: /* MP2 */
+ 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: /* DVD PCM */
+ codec_id = AV_CODEC_ID_PCM_DVD;
+ 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: /* DCA */
+ 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) {
+ av_log(s, AV_LOG_ERROR, "Invalid audio parameters\n");
+
+ 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 == 7)
+ ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_6POINT1;
+ else if (nb_channels == 8)
+ ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_7POINT1;
+
+ if (audio_attr.code_extension == 2)
+ entry->disposition |= AV_DISPOSITION_VISUAL_IMPAIRED;
+ if (audio_attr.code_extension == 3 || audio_attr.code_extension == 4)
+ entry->disposition |= AV_DISPOSITION_COMMENT;
+
+ 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;
+ st->disposition = entry->disposition;
+
+ 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 = 0;
+ int nb_streams = 0;
+
+ for (int i = 0; i < c->vts_ifo->vtsi_mat->nr_of_vts_audio_streams; i++) {
+ DVDVideoPGCAudioStreamEntry *entry = NULL;
+
+ if (!(c->play_state.pgc->audio_control[i] & 0x8000))
+ continue;
+
+ entry = av_mallocz(sizeof(DVDVideoPGCAudioStreamEntry));
+ if (!entry)
+ return AVERROR(ENOMEM);
+
+ if ((ret = dvdvideo_audio_stream_analyze(s, c->vts_ifo->vtsi_mat->vts_audio_attr[i],
+ c->play_state.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, "Karaoke metadata in %d not supported\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
+ || (ret = dvdvideo_audio_stream_add(s, entry, AVSTREAM_PARSE_HEADERS)) < 0)
+ goto break_free_and_error;
+
+ nb_streams++;
+
+continue_free:
+ av_freep(&entry);
+ continue;
+
+break_free_and_error:
+ av_freep(&entry);
+ av_log(s, AV_LOG_ERROR, "Unable to allocate audio stream\n");
+ return ret;
+ }
+
+ if (nb_streams > 0)
+ c->play_has_audio = 1;
+
+ return ret;
+}
+
+static int dvdvideo_subp_stream_analyze(AVFormatContext *s, uint32_t offset, subp_attr_t subp_attr,
+ DVDVideoPGCSubtitleStreamEntry *entry)
+{
+ char lang_dvd[3] = {0};
+
+ entry->startcode = 0x20 + (offset & 0x1F);
+
+ if (subp_attr.lang_extension == 9)
+ entry->disposition |= AV_DISPOSITION_FORCED;
+
+ 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;
+ int ret = 0;
+
+ 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: palette support will be inserted here in upcoming patch */
+
+ if (entry->lang_iso)
+ av_dict_set(&st->metadata, "language", entry->lang_iso, 0);
+
+ av_dict_set(&st->metadata, "VIEWPORT", VIEWPORT_LABELS[entry->viewport], 0);
+
+ st->disposition = entry->disposition;
+
+ 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 ret;
+}
+
+static int dvdvideo_subp_stream_add_internal(AVFormatContext *s, uint32_t offset,
+ subp_attr_t subp_attr,
+ enum DVDVideoSubpictureViewport viewport)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ DVDVideoPGCSubtitleStreamEntry *entry = NULL;
+ int ret = 0;
+
+ entry = av_mallocz(sizeof(DVDVideoPGCSubtitleStreamEntry));
+ entry->viewport = viewport;
+
+ 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_HEADERS)) < 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_freep(&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 = 0;
+ uint32_t subp_control;
+ subp_attr_t subp_attr;
+ video_attr_t video_attr;
+
+ subp_control = c->play_state.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,
+ DVDVIDEO_SUBP_VIEWPORT_FULLSCREEN)) < 0)
+ return ret;
+
+ continue;
+ }
+
+ /* 16:9 */
+ if ((ret = dvdvideo_subp_stream_add_internal(s, subp_control >> 16, subp_attr,
+ DVDVIDEO_SUBP_VIEWPORT_WIDESCREEN)) < 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,
+ DVDVIDEO_SUBP_VIEWPORT_LETTERBOX)) < 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,
+ DVDVIDEO_SUBP_VIEWPORT_PANSCAN)) < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void dvdvideo_subdemux_flush(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ if (!c->play_seg_started)
+ return;
+
+ av_log(s, AV_LOG_DEBUG, "flushing sub-demuxer\n");
+ avio_flush(&c->mpeg_pb.pub);
+ ff_read_frame_flush(c->mpeg_ctx);
+ c->play_seg_started = 0;
+}
+
+static int dvdvideo_subdemux_read_data(void *opaque, uint8_t *buf, int buf_size)
+{
+ AVFormatContext *s = opaque;
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret = 0;
+ int nav_event;
+
+ if (c->play_end)
+ return AVERROR_EOF;
+
+ ret = dvdvideo_play_next_ps_block(opaque, &c->play_state, buf, buf_size,
+ &nav_event, dvdvideo_subdemux_flush);
+
+ if (ret == AVERROR_EOF) {
+ c->mpeg_pb.pub.eof_reached = 1;
+ c->play_end = 1;
+
+ return AVERROR_EOF;
+ }
+
+ if (nav_event == DVDNAV_NAV_PACKET)
+ return FFERROR_REDO;
+
+ return ret;
+}
+
+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 = 0;
+
+ if (!(c->mpeg_fmt = av_find_input_format("mpeg")))
+ return AVERROR_DEMUXER_NOT_FOUND;
+
+ if (!(c->mpeg_ctx = avformat_alloc_context()))
+ return AVERROR(ENOMEM);
+
+ if (!(c->mpeg_buf = av_mallocz(DVDVIDEO_BLOCK_SIZE))) {
+ avformat_free_context(c->mpeg_ctx);
+
+ return AVERROR(ENOMEM);
+ }
+
+ ffio_init_context(&c->mpeg_pb, c->mpeg_buf, DVDVIDEO_BLOCK_SIZE, 0, s,
+ dvdvideo_subdemux_read_data, NULL, NULL);
+ c->mpeg_pb.pub.seekable = 0;
+
+ if ((ret = ff_copy_whiteblacklists(c->mpeg_ctx, s)) < 0) {
+ avformat_free_context(c->mpeg_ctx);
+
+ return ret;
+ }
+
+ c->mpeg_ctx->flags = AVFMT_FLAG_CUSTOM_IO | AVFMT_FLAG_GENPTS;
+ c->mpeg_ctx->ctx_flags |= AVFMTCTX_UNSEEKABLE;
+ c->mpeg_ctx->probesize = 0;
+ c->mpeg_ctx->max_analyze_duration = 0;
+ c->mpeg_ctx->interrupt_callback = s->interrupt_callback;
+ c->mpeg_ctx->pb = &c->mpeg_pb.pub;
+ c->mpeg_ctx->io_open = NULL;
+
+ if ((ret = avformat_open_input(&c->mpeg_ctx, "", c->mpeg_fmt, NULL)) < 0) {
+ avformat_free_context(c->mpeg_ctx);
+
+ return ret;
+ }
+
+ return ret;
+}
+
+static int dvdvideo_read_header(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret = 0;
+
+ if (c->opt_title == 0) {
+ av_log(s, AV_LOG_WARNING, "Defaulting to title #1. "
+ "This is not always the main feature's title, please check.\n");
+
+ c->opt_title = 1;
+ }
+
+ if (c->opt_pgc) {
+ if (c->opt_pg == 0)
+ av_log(s, AV_LOG_ERROR, "Invalid coordinates. If -pgc is set, -pg must be set too.\n");
+ else if (c->opt_chapter_start > 1 || c->opt_chapter_end > 0 || c->opt_preindex)
+ av_log(s, AV_LOG_ERROR, "-pgc is not compatible with -preindex, -chapter_start/end\n");
+
+ return AVERROR(EINVAL);
+ }
+
+ if ((ret = dvdvideo_ifo_open(s)) < 0)
+ return ret;
+
+ if (c->opt_preindex && (ret = dvdvideo_pgc_preindex(s)) < 0)
+ return ret;
+
+ if ((ret = dvdvideo_play_open(s, &c->play_state)) < 0
+ || (ret = dvdvideo_subdemux_open(s)) < 0
+ || (ret = dvdvideo_video_stream_setup(s)) < 0
+ || (ret = dvdvideo_audio_stream_add_all(s)) < 0
+ || (ret = dvdvideo_subp_stream_add_all(s)) < 0)
+ return ret;
+
+ if (!c->opt_preindex)
+ return dvdvideo_pgc_chapters_setup(s);
+
+ return ret;
+}
+
+static int dvdvideo_read_packet(AVFormatContext *s, AVPacket *pkt)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret;
+ enum AVMediaType st_type;
+
+ if (c->play_end)
+ return AVERROR_EOF;
+
+ ret = av_read_frame(c->mpeg_ctx, pkt);
+
+ if (ret < 0)
+ return ret;
+
+ if (pkt->stream_index >= s->nb_streams) {
+ av_log(s, AV_LOG_DEBUG, "ignoring unexpected stream 0x%02x\n",
+ c->mpeg_ctx->streams[pkt->stream_index]->id);
+
+ av_packet_unref(pkt);
+ return FFERROR_REDO;
+ }
+
+ st_type = c->mpeg_ctx->streams[pkt->stream_index]->codecpar->codec_type;
+
+ if (!c->play_started) {
+ /* wait for first audio keyframe, solves for discs with AC3 delay and early junk frames */
+ if (c->opt_wait_for_audio && c->play_has_audio
+ && (st_type != AVMEDIA_TYPE_AUDIO || !(pkt->flags & AV_PKT_FLAG_KEY))) {
+ av_log(s, AV_LOG_DEBUG, "skipping non-audio frame at start (st=%d pts=%ld)\n",
+ pkt->stream_index, pkt->pts);
+
+ av_packet_unref(pkt);
+ return FFERROR_REDO;
+ }
+
+ if (pkt->pts != AV_NOPTS_VALUE)
+ c->play_pts_offset -= pkt->pts;
+
+ c->play_started = 1;
+ }
+
+ if (!c->play_seg_started)
+ c->play_seg_started = 1;
+
+ if (pkt->pts != AV_NOPTS_VALUE)
+ pkt->pts += c->play_state.ts_offset + c->play_pts_offset;
+ if (pkt->dts != AV_NOPTS_VALUE)
+ pkt->dts += c->play_state.ts_offset + c->play_pts_offset;
+
+ av_log(s, AV_LOG_TRACE, "st=%d pts=%ld ts_offset=%ld play_pts_offset=%ld\n",
+ pkt->stream_index, pkt->pts,
+ c->play_state.ts_offset, c->play_pts_offset);
+
+ if (pkt->pts < 0)
+ av_log(s, AV_LOG_WARNING, "Invalid frame PTS @ st=%d pts=%ld dts=%ld, "
+ "try -wait_for_audio to discard if this is happening "
+ "at the start of the PGC (likely OK)\n",
+ pkt->stream_index, pkt->pts, pkt->dts);
+
+ return c->play_end ? AVERROR_EOF : 0;
+}
+
+static int dvdvideo_close(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ dvdvideo_subdemux_close(s);
+ dvdvideo_play_close(s, &c->play_state);
+ dvdvideo_ifo_close(s);
+
+ return 0;
+}
+
+#define OFFSET(x) offsetof(DVDVideoDemuxContext, x)
+static const AVOption dvdvideo_options[] = {
+ {"title", "title number", OFFSET(opt_title), AV_OPT_TYPE_INT, { .i64=0 }, 0, 99, AV_OPT_FLAG_DECODING_PARAM },
+ {"chapter_start", "entry chapter (PTT) number", OFFSET(opt_chapter_start), AV_OPT_TYPE_INT, { .i64=1 }, 1, 99, AV_OPT_FLAG_DECODING_PARAM },
+ {"chapter_end", "exit chapter (PTT) number", OFFSET(opt_chapter_end), AV_OPT_TYPE_INT, { .i64=0 }, 0, 99, AV_OPT_FLAG_DECODING_PARAM },
+ {"angle", "playback 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 },
+ /* advanced options */
+ {"pgc", "entry PGC number (0=auto)", OFFSET(opt_pgc), AV_OPT_TYPE_INT, { .i64=0 }, 0, 999, 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 },
+ {"preindex", "enable for accurate chapter markers, slow (2-pass read)", OFFSET(opt_preindex), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, AV_OPT_FLAG_DECODING_PARAM },
+ {"trim", "trim padding cells from start", OFFSET(opt_trim), AV_OPT_TYPE_BOOL, { .i64=1 }, 0, 1, AV_OPT_FLAG_DECODING_PARAM },
+ {"wait_for_audio", "wait for audio keyframe at start, if any", OFFSET(opt_wait_for_audio), AV_OPT_TYPE_BOOL, { .i64=1 }, 0, 1, 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_close = dvdvideo_close,
+ .read_header = dvdvideo_read_header,
+ .read_packet = dvdvideo_read_packet
+};
--
2.34.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH v5] libavformat: add DVD-Video demuxer, powered by libdvdnav and libdvdread
2024-01-28 22:59 ` [FFmpeg-devel] [PATCH v5] libavformat: " Marth64
@ 2024-01-31 23:57 ` Stefano Sabatini
2024-02-05 0:02 ` [FFmpeg-devel] [PATCH v6] libavformat/dvdvideo: add DVD-Video demuxer " Marth64
1 sibling, 0 replies; 27+ messages in thread
From: Stefano Sabatini @ 2024-01-31 23:57 UTC (permalink / raw)
To: FFmpeg development discussions and patches; +Cc: Marth64
On date Sunday 2024-01-28 16:59:22 -0600, Marth64 wrote:
> - Add option to play only certain chapters or chapter ranges
> - Add option to do a second pass indexing for accurate chapter markers
> - Add documentation
> - Fixes issues with PCM audio
> - Support for essential track dispositions (forced, visual impaired, commentary)
> - Better support for structure variances and padding cells/frames
> - More user friendly log messages and errors
> - dvdread/dvdnav logging is relatively very verbose, map lower levels as DEBUG logs
>
> Signed-off-by: Marth64 <marth64@proxyid.net>
> ---
> Changelog | 1 +
> configure | 8 +
> doc/demuxers.texi | 129 ++++
> libavformat/Makefile | 1 +
> libavformat/allformats.c | 1 +
> libavformat/dvdvideodec.c | 1409 +++++++++++++++++++++++++++++++++++++
> 6 files changed, 1549 insertions(+)
> create mode 100644 libavformat/dvdvideodec.c
>
> diff --git a/Changelog b/Changelog
> index c1fd66b4bd..9de157abe4 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -23,6 +23,7 @@ version <next>:
> - ffmpeg CLI -bsf option may now be used for input as well as output
> - ffmpeg CLI options may now be used as -/opt <path>, which is equivalent
> to -opt <contents of file <path>>
> +- DVD-Video demuxer, powered by libdvdnav and libdvdread
>
> version 6.1:
> - libaribcaption decoder
> diff --git a/configure b/configure
> index 21663000f8..d81420992a 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
> @@ -3520,6 +3524,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"
> @@ -6761,6 +6767,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/doc/demuxers.texi b/doc/demuxers.texi
> index e4c5b560a6..f7f9e6769a 100644
> --- a/doc/demuxers.texi
> +++ b/doc/demuxers.texi
> @@ -285,6 +285,135 @@ This demuxer accepts the following option:
>
> @end table
>
> +@section dvdvideo
> +
> +DVD-Video demuxer, powered by libdvdnav and libdvdread.
> +
> +Can directly ingest DVD titles, specifically sequential PGCs (see below),
> +into a conversion pipeline. Menus and seeking are not supported at this time.
> +Block devices (DVD drives), ISO files, and directory structures are accepted.
> +Activate with @code{-f dvdvideo} in front of one of these inputs.
> +
> +Underlying playback is fully handled by libdvdnav, and structure parsing by libdvdread.
> +Therefore, the ffmpeg build must be configured with these GPL libraries enabled.
nit, specifically mention the @code{--enable-libdvdread} and
@code{--enable-libdvdnav} options as done in other places.
> +
> +You will need to provide either the desired "title" number or exact PGC/PG coordinates.
> +Many open-source DVD tools and players can aid in providing this information.
> +If not specified, the demuxer will default to title 1 which works for many discs.
> +However, due to the flexibility of the DVD standard, it is recommended to check manually.
> +There are many discs that are authored strangely or with invalid headers.
> +
> +If the input is a real DVD drive, please note that there are some drives which may
> +silently fail on reading bad sectors from the disc, returning random bits instead
> +which is effectively corrupt data. This is especially prominent on aging or rotting discs.
> +A second pass and integrity checks would be needed to detect the corruption.
> +This is not an ffmpeg issue.
> +
> +@subsection Background
> +
> +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 MPEG-PS streams that are stored in headerless VOB files.
> +To the end-user, these streams are known simply as "titles", but the actual
> +logical playback sequence is defined by one or more "PGCs", or Program Group Chains,
> +within the title. The sequence is in turn comprised of multiple "PGs", or Programs",
> +which are the actual video segments. The PGC structure, along with stream layout
> +and metadata, are stored in IFO files that need to be parsed.
> +
> +A typical DVD player relies on user GUI interaction via menus and an internal VM
> +to drive the direction of demuxing. Generally, the user would either navigate (via menus)
> +or automatically be redirected to the PGC of their choice. During this process and
> +the subsequent playback, the DVD player's internal VM also maintains a state and
> +executes instructions that can create jumps to different sectors during playback.
> +This is why libdvdnav is involved, as a linear read of the MPEG-PS blobs on the
> +disc (VOBs) is not enough to produce the right sequence in many cases.
> +
> +There are many other DVD structures (a long subject) that will not be discussed here.
> +NAV packets, in particular, are handled by this demuxer to build accurate timing
> +but not emitted as a stream. For a good high-level understanding, refer to:
> +@url{https://code.videolan.org/videolan/libdvdnav/-/blob/master/doc/dvd_structures}
> +
> +@subsection Options
> +
> +This demuxer accepts the following options:
> +
> +@table @option
> +
> +@item title
> +The title number to play. Must be set if @code{-pgc/pg} are not set.
the @code{-pgc/pg} is a bit cryptic, as it seems to refer to an option
(whic is missing).
> +Default is 1, which is used by many discs.
> +
> +@item chapter_start
> +The chapter, or PTT (part-of-title), number to start at. Default is 1.
> +
> +@item chapter_end
> +The chapter, or PTT (part-of-title), number to end at. Default is 0,
> +which is a special value to signal end at the last possible chapter.
> +
> +@item angle
> +The video angle number, referring to what is essentially an alternative
> +video stream that is interleaved in the VOBs. Default is 1.
> +
> +@item region
> +The region code to use for playback. Some discs may use this to default playback
> +at a particular angle in different regions. This option will not affect the region code
> +of a real DVD drive, if used as an input. Default is 0, "world".
> +
> +@end table
> +
> +@subsection Examples
> +
nit: use
@itemize
@item
to introduce each item and make it clear where each example stands
> +Open title 3 from a given DVD structure:
> +@example
> +ffmpeg -f dvdvideo -title 3 -i <path to DVD> ...
> +@end example
> +
> +Open chapters 3-6 from title 1 from a given DVD structure:
> +@example
> +ffmpeg -f dvdvideo -chapter_start 3 -chapter_end 6 -title 1 -i <path to DVD> ...
> +@end example
> +
> +Open only chapters 5 from title 1 from a given DVD structure:
> +@example
> +ffmpeg -f dvdvideo -chapter_start 5 -chapter_end 5 -title 1 -i <path to DVD> ...
> +@end example
> +
> +@subsection Advanced Options
> +
> +@table @option
> +
> +@item pgc
> +The entry PGC to start playback, in conjunction with @code{-pg}.
use style:
@option{pg}
here and below
Also I'd merge the two options sections (since there is usually a
single options section).
> +Alternative to setting @code{-title}.
> +Chapter markers not supported at this time.
> +Default is 0, automatically resolve from value of @code{-title}.
> +
> +@item pg
> +The entry PG to start playback, in conjunction with @code{-pgc}.
> +Alternative to setting @code{-title}.
> +Chapter markers not supported at this time.
> +Default is 0, automatically resolve from value of @code{-title}.
> +
> +@item preindex
> +Enable this to have accurate chapter (PTT) markers and duration measurement,
> +which requires a slow second pass read in order to index the chapter
> +timestamps from NAV packets. This is non-ideal extra work for physical DVD drives.
> +Can give imperfect results if not starting playback from chapter 1.
> +Not compatible with @code{-pgc/pg}.
> +Default is 0, false.
> +
> +@item trim
> +Trim padding cells (i.e. cells shorter than 1 second) from the beginning of the title.
Maybe add a note why/when this is needed
> +Default is 1, true.
> +
> +@item wait_for_audio
> +Wait for the first audio keyframe before emitting frames. This is necessary
> +for a uniform output from many discs which insert AC3 delay or filler frames
> +at the start of the PGC.
> +Default is 1, true.
> +
> +@end table
> +
> @section ea
>
> Electronic Arts Multimedia format demuxer.
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index dcc99eeac4..f0b42188a2 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 b04b43cab3..3e905c23f8 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/dvdvideodec.c b/libavformat/dvdvideodec.c
> new file mode 100644
> index 0000000000..b9ec3acaaf
> --- /dev/null
> +++ b/libavformat/dvdvideodec.c
> @@ -0,0 +1,1409 @@
> +/*
> + * DVD-Video demuxer, powered by libdvdnav and libdvdread
> + * Author: Marth64 <marth64@proxyid.net>
> + *
> + * 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
> + */
> +
> +/*
> + * See doc/demuxers.texi for a high-level overview.
> + *
> + * The tactical approach is as follows:
> + * 1) Open the volume with dvdread
> + * 2) Analyze the user-requested title and PGC coordinates in the IFO structures
> + * 3) Request playback at the coordinates and chosen angle with dvdnav
> + * 5) Begin the playback (reading and demuxing) of MPEG-PS blocks
> + * 6) End playback if navigation goes backwards, to a menu, or a different PGC or angle
> + * 7) Close the dvdnav VM, and free dvdnav's IFO structures
> + */
> +
> +#include <dvdnav/dvdnav.h>
> +#include <dvdread/dvd_reader.h>
> +#include <dvdread/ifo_read.h>
> +#include <dvdread/ifo_types.h>
> +#include <dvdread/nav_read.h>
> +
> +#include "libavcodec/avcodec.h"
> +#include "libavutil/avstring.h"
> +#include "libavutil/avutil.h"
> +#include "libavutil/intreadwrite.h"
> +#include "libavutil/mem.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/samplefmt.h"
> +#include "libavutil/time.h"
> +#include "libavutil/timestamp.h"
> +
> +#include "avformat.h"
> +#include "avio_internal.h"
> +#include "avlanguage.h"
> +#include "demux.h"
> +#include "internal.h"
> +#include "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 64 /* VOBUs use 32 (PES allows 33) */
> +#define DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE 1024
> +
> +enum DVDVideoSubpictureViewport {
> + DVDVIDEO_SUBP_VIEWPORT_FULLSCREEN,
> + DVDVIDEO_SUBP_VIEWPORT_WIDESCREEN,
> + DVDVIDEO_SUBP_VIEWPORT_LETTERBOX,
> + DVDVIDEO_SUBP_VIEWPORT_PANSCAN
> +};
> +const char* VIEWPORT_LABELS[4] = { "Fullscreen", "Widescreen", "Letterbox", "Pan and Scan" };
> +
> +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;
> + int disposition;
> + char *lang_iso;
> +} DVDVideoPGCAudioStreamEntry;
> +
> +typedef struct DVDVideoPGCSubtitleStreamEntry {
> + int startcode;
> + int disposition;
> + char *lang_iso;
> + enum DVDVideoSubpictureViewport viewport;
> +} DVDVideoPGCSubtitleStreamEntry;
> +
> +typedef struct DVDVideoPlaybackState {
> + int celln; /* ID of the active cell */
> + int entry_pgn; /* ID of the PG we are starting in */
> + int in_pgc; /* if our navigator is in the PGC */
> + int in_ps; /* if our navigator is in the program stream */
> + int in_vts; /* if our navigator is in the VTS */
> + int64_t nav_pts; /* PTS according to IFO, not frame-accurate */
> + uint64_t pgc_duration_est; /* estimated duration as reported by IFO */
> + uint64_t pgc_elapsed; /* the elapsed time of the PGC, cell-relative */
> + int pgc_nb_pg_est; /* number of PGs as reported by IFOs */
> + int pgcn; /* ID of the PGC we are playing */
> + int pgn; /* ID of the PG we are in now */
> + int ptt; /* ID of the chapter we are in now */
> + int64_t ts_offset; /* PTS discontinuity offset (ex. VOB change) */
> + uint32_t vobu_duration; /* duration of the current VOBU */
> + uint32_t vobu_e_ptm; /* end PTS of the current VOBU */
> + int vtsn; /* ID of the active VTS (video title set) */
> + uint64_t *pgc_pg_times_est; /* PG start times as reported by IFO */
> + pgc_t *pgc; /* handle to the active PGC */
> + dvdnav_t *dvdnav; /* handle to libdvdnav */
> +} DVDVideoPlaybackState;
> +
> +typedef struct DVDVideoDemuxContext {
> + const AVClass *class;
> +
> + /* options */
> + int opt_title; /* the user-provided title number (1-indexed) */
> + int opt_chapter_start; /* the user-provided entry PTT (1-indexed) */
> + int opt_chapter_end; /* the user-provided exit PTT (0 for last) */
> + 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 */
> + int opt_preindex; /* pre-indexing mode (2-pass read) */
> + int opt_trim; /* trim padding cells at beginning and end */
> + int opt_wait_for_audio; /* wait for audio streams to start, if any */
> +
> + /* 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) */
> +
> + /* playback control */
> + int play_end; /* signal EOF to the parent demuxer */
> + DVDVideoPlaybackState play_state; /* the active playback state */
> + int play_started; /* signal that playback has started */
> + int play_seg_started; /* signal that segment has started */
> + int64_t play_pts_offset; /* the PTS offset of the PGC */
> + int play_has_audio; /* if we are expecting audio streams */
> + uint64_t duration_ptm; /* total duration in DVD MPEG timebase */
> +} DVDVideoDemuxContext;
> +
> +static void dvdvideo_libdvdread_log(void *opaque, dvd_logger_level_t level,
> + const char *msg, va_list msg_va)
> +{
> + AVFormatContext *s = opaque;
> + int lavu_level;
> +
> + char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
> + vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
> +
> + switch (level) {
> + case DVD_LOGGER_LEVEL_ERROR:
> + lavu_level = AV_LOG_ERROR;
> + break;
> + case DVD_LOGGER_LEVEL_WARN:
> + lavu_level = AV_LOG_WARNING;
> + break;
> + /* dvdread's info messages are relatively very verbose, muffle them as debug */
> + case DVD_LOGGER_LEVEL_INFO:
> + case DVD_LOGGER_LEVEL_DEBUG:
> + default:
> + lavu_level = AV_LOG_DEBUG;
> + }
> +
> + av_log(s, lavu_level, "libdvdread: %s\n", msg_buf);
> +}
> +
> +static void dvdvideo_libdvdnav_log(void *opaque, dvdnav_logger_level_t level,
> + const char *msg, va_list msg_va)
> +{
> + AVFormatContext *s = opaque;
> + int lavu_level;
> +
> + char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
> + vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
> +
> + switch (level) {
> + case DVDNAV_LOGGER_LEVEL_ERROR:
> + lavu_level = AV_LOG_ERROR;
> + break;
> + case DVDNAV_LOGGER_LEVEL_WARN:
> + /* some discs have invalid language codes set for their menus, muffle the noise */
> + lavu_level = av_strstart(msg, "Language", NULL) ? AV_LOG_DEBUG : AV_LOG_WARNING;
> + break;
> + /* dvdnav's info messages are relatively very verbose, muffle them as debug */
> + case DVDNAV_LOGGER_LEVEL_INFO:
> + case DVDNAV_LOGGER_LEVEL_DEBUG:
> + default:
> + lavu_level = AV_LOG_DEBUG;
> + }
> +
> + av_log(s, lavu_level, "libdvdnav: %s\n", msg_buf);
> +}
> +
> +static void dvdvideo_ifo_close(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + 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_ifo_open(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + dvd_logger_cb dvdread_log_cb;
> + title_info_t title_info;
> +
> + dvdread_log_cb = (dvd_logger_cb) { .pf_log = dvdvideo_libdvdread_log };
> + c->dvdread = DVDOpen2(s, &dvdread_log_cb, s->url);
> +
> + if (!c->dvdread) {
> + av_log(s, AV_LOG_ERROR, "Unable to open the DVD-Video structure\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + if (!(c->vmg_ifo = ifoOpen(c->dvdread, 0))) {
> + av_log(s, AV_LOG_ERROR, "Unable to open the VMG (VIDEO_TS.IFO)\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + if (c->opt_title > c->vmg_ifo->tt_srpt->nr_of_srpts) {
> + av_log(s, AV_LOG_ERROR, "Title %d not found\n", c->opt_title);
> +
> + 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 %d not found\n", c->opt_angle);
> +
> + return AVERROR_STREAM_NOT_FOUND;
> + }
> +
> + if (title_info.nr_of_ptts < 1) {
> + av_log(s, AV_LOG_ERROR, "Title %d has invalid headers in VMG\n", c->opt_title);
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + if (c->opt_chapter_start > title_info.nr_of_ptts
> + || (c->opt_chapter_end > 0 && c->opt_chapter_end > title_info.nr_of_ptts)) {
> + av_log(s, AV_LOG_ERROR, "Chapter (PTT) range provided is invalid\n");
you might also print the provided range and the title_info to aid
debugging
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + if (!(c->vts_ifo = ifoOpen(c->dvdread, title_info.title_set_nr))) {
> + av_log(s, AV_LOG_ERROR, "Unable to process the VTS IFO structure\n");
> +
> + return AVERROR_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 %d has invalid headers in VTS\n", c->opt_title);
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + return 0;
> +}
> +
> +static void dvdvideo_play_close(AVFormatContext *s, DVDVideoPlaybackState *state)
> +{
> + if (!state->dvdnav)
> + return;
> +
> + /* not allocated by av_malloc() */
> + if (state->pgc_pg_times_est)
> + free(state->pgc_pg_times_est);
> +
> + if (dvdnav_close(state->dvdnav) < 0)
> + av_log(s, AV_LOG_ERROR, "Unable to cleanly close dvdnav\n");
> +}
> +
> +static int dvdvideo_play_open(AVFormatContext *s, DVDVideoPlaybackState *state)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret = 0;
> + dvdnav_logger_cb dvdnav_log_cb;
> + dvdnav_status_t dvdnav_open_status;
> + int cur_title, cur_pgcn, cur_pgn;
> + pgc_t *pgc;
> +
> + int32_t disc_region_mask;
> + int32_t player_region_mask;
> +
> + dvdnav_log_cb = (dvdnav_logger_cb) { .pf_log = dvdvideo_libdvdnav_log };
> + dvdnav_open_status = dvdnav_open2(&state->dvdnav, s, &dvdnav_log_cb, s->url);
> +
> + if (!state->dvdnav
> + || dvdnav_open_status != DVDNAV_STATUS_OK
> + || dvdnav_set_readahead_flag(state->dvdnav, 0) != DVDNAV_STATUS_OK
> + || dvdnav_set_PGC_positioning_flag(state->dvdnav, 1) != DVDNAV_STATUS_OK
> + || dvdnav_get_region_mask(state->dvdnav, &disc_region_mask) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to open the DVD for playback\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + player_region_mask = c->opt_region > 0 ? (1 << (c->opt_region - 1)) : disc_region_mask;
> + if (dvdnav_set_region_mask(state->dvdnav, player_region_mask) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to set the playback region\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + if (c->opt_pgc > 0 && c->opt_pg > 0) {
> + if (dvdnav_program_play(state->dvdnav, c->opt_title,
> + c->opt_pgc, c->opt_pg) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to start playback at the given title or part\n");
again, you can print the opt_title and other options to aid debugging
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + state->pgcn = c->opt_pgc;
> + state->entry_pgn = c->opt_pg;
> + } else {
> + if (dvdnav_part_play(state->dvdnav, c->opt_title, c->opt_chapter_start) != DVDNAV_STATUS_OK
> + || dvdnav_current_title_program(state->dvdnav, &cur_title,
> + &cur_pgcn, &cur_pgn) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to start playback at the given PGC/PG coordinates\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + state->pgcn = cur_pgcn;
> + state->entry_pgn = cur_pgn;
> + }
> +
> + pgc = c->vts_ifo->vts_pgcit->pgci_srp[state->pgcn - 1].pgc;
> +
> + if (pgc->pg_playback_mode != 0) {
> + av_log(s, AV_LOG_ERROR, "Sorry, non-sequential PGCs are not supported\n");
nit: Non-sequential PGCs found which are not supported
?
> + return AVERROR_PATCHWELCOME;
> + }
> +
> + if (dvdnav_angle_change(state->dvdnav, c->opt_angle) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to start playback at the given angle\n");
show the angle
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + /* dvdnav_describe_title_chapters() performs several safety checks on the title structure */
> + /* take advantage of this side effect to ensure a safe navigation path */
> + state->pgc_nb_pg_est = dvdnav_describe_title_chapters(state->dvdnav, c->opt_title,
> + &state->pgc_pg_times_est,
> + &state->pgc_duration_est);
> + if (!state->pgc_nb_pg_est) {
> + av_log(s, AV_LOG_ERROR, "Unable to validate title structure\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + state->nav_pts = dvdnav_get_current_time(state->dvdnav);
> + state->vtsn = c->vmg_ifo->tt_srpt->title[c->opt_title - 1].title_set_nr;
> +
> + state->pgc = pgc;
> +
> + return ret;
> +}
> +
> +static int dvdvideo_play_is_cell_promising(AVFormatContext *s, DVDVideoPlaybackState *state,
> + int celln)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> + dvd_time_t cell_duration;
> +
> + if (!c->opt_trim)
> + return 1;
> +
> + cell_duration = state->pgc->cell_playback[celln - 1].playback_time;
> +
> + return cell_duration.second >= 1 || cell_duration.minute >= 1 || cell_duration.hour >= 1;
> +}
> +
> +static int dvdvideo_play_next_ps_block(AVFormatContext *s, DVDVideoPlaybackState *state,
> + uint8_t *buf, int buf_size,
> + int *p_nav_event,
> + void (*flush_cb)(AVFormatContext *s))
> +{
> + 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;
> + int is_cell_promising = 0;
> + pci_t *e_pci;
> + dsi_t *e_dsi;
> +
> + 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(state->dvdnav, nav_buf, &nav_event, &nav_len) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to read next block of PGC\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + /* some discs follow NOPs with a premature stop event */
> + if (nav_event == DVDNAV_STOP)
> + return AVERROR_EOF;
> +
> + if (nav_len > DVDVIDEO_BLOCK_SIZE) {
> + av_log(s, AV_LOG_ERROR, "Invalid block size\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + if (dvdnav_current_title_info(state->dvdnav, &cur_title,
> + &cur_ptt) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to validate title coordinates\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + /* we somehow navigated to a menu */
> + if (cur_title == 0 || !dvdnav_is_domain_vts(state->dvdnav))
> + return AVERROR_EOF;
> +
> + if (dvdnav_current_title_program(state->dvdnav, &cur_title_unused,
> + &cur_pgcn, &cur_pgn) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to validate PGC coordinates\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + /* we somehow left the PGC */
> + if (state->in_pgc && cur_pgcn != state->pgcn)
> + return AVERROR_EOF;
> +
> + if (dvdnav_get_angle_info(state->dvdnav, &cur_angle, &cur_nb_angles) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to validate angle coordinates\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + av_log(s, nav_event == DVDNAV_BLOCK_OK ? AV_LOG_TRACE : AV_LOG_DEBUG,
> + "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, state->celln, cur_pgcn, cur_pgn,
> + state->in_vts, state->in_pgc, state->in_ps);
nit: weird indent
[will continue review from here]
> +static const AVOption dvdvideo_options[] = {
> + {"title", "title number", OFFSET(opt_title), AV_OPT_TYPE_INT, { .i64=0 }, 0, 99, AV_OPT_FLAG_DECODING_PARAM },
> + {"chapter_start", "entry chapter (PTT) number", OFFSET(opt_chapter_start), AV_OPT_TYPE_INT, { .i64=1 }, 1, 99, AV_OPT_FLAG_DECODING_PARAM },
> + {"chapter_end", "exit chapter (PTT) number", OFFSET(opt_chapter_end), AV_OPT_TYPE_INT, { .i64=0 }, 0, 99, AV_OPT_FLAG_DECODING_PARAM },
> + {"angle", "playback 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 },
> + /* advanced options */
> + {"pgc", "entry PGC number (0=auto)", OFFSET(opt_pgc), AV_OPT_TYPE_INT, { .i64=0 }, 0, 999, 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 },
> + {"preindex", "enable for accurate chapter markers, slow (2-pass read)", OFFSET(opt_preindex), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, AV_OPT_FLAG_DECODING_PARAM },
> + {"trim", "trim padding cells from start", OFFSET(opt_trim), AV_OPT_TYPE_BOOL, { .i64=1 }, 0, 1, AV_OPT_FLAG_DECODING_PARAM },
> + {"wait_for_audio", "wait for audio keyframe at start, if any", OFFSET(opt_wait_for_audio), AV_OPT_TYPE_BOOL, { .i64=1 }, 0, 1, AV_OPT_FLAG_DECODING_PARAM },
> + {NULL}
sort by name?
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* [FFmpeg-devel] [PATCH v6] libavformat/dvdvideo: add DVD-Video demuxer powered by libdvdnav and libdvdread
2024-01-28 22:59 ` [FFmpeg-devel] [PATCH v5] libavformat: " Marth64
2024-01-31 23:57 ` Stefano Sabatini
@ 2024-02-05 0:02 ` Marth64
2024-02-05 0:09 ` Marth64
2024-02-05 5:48 ` [FFmpeg-devel] [PATCH v7 1/2] " Marth64
1 sibling, 2 replies; 27+ messages in thread
From: Marth64 @ 2024-02-05 0:02 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Marth64
- Further improve documentation, logging, and indentation
- Improve stream starting logic to be far more robust and not break GOPs
- Resolve and remove timing workaround for chapter markers when starting after ch. 1
- Fix sketchy error handling logic for certain edge case when NAV pack is missing
Overall it's in pretty good working shape.
Subtitle palette support remains in a separate patch set.
Signed-off-by: Marth64 <marth64@proxyid.net>
---
Changelog | 2 +
configure | 8 +
doc/demuxers.texi | 135 ++++
libavformat/Makefile | 1 +
libavformat/allformats.c | 1 +
libavformat/dvdvideodec.c | 1409 +++++++++++++++++++++++++++++++++++++
6 files changed, 1556 insertions(+)
create mode 100644 libavformat/dvdvideodec.c
diff --git a/Changelog b/Changelog
index c5fb21d198..88653bc6d3 100644
--- a/Changelog
+++ b/Changelog
@@ -24,6 +24,8 @@ version <next>:
- ffmpeg CLI options may now be used as -/opt <path>, which is equivalent
to -opt <contents of file <path>>
- showinfo bitstream filter
+- DVD-Video demuxer, powered by libdvdnav and libdvdread
+
version 6.1:
- libaribcaption decoder
diff --git a/configure b/configure
index 68f675a4bc..70c33ec96d 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
@@ -3520,6 +3524,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"
@@ -6761,6 +6767,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/doc/demuxers.texi b/doc/demuxers.texi
index e4c5b560a6..d8bc806ad5 100644
--- a/doc/demuxers.texi
+++ b/doc/demuxers.texi
@@ -285,6 +285,141 @@ This demuxer accepts the following option:
@end table
+@section dvdvideo
+
+DVD-Video demuxer, powered by libdvdnav and libdvdread.
+
+Can directly ingest DVD titles, specifically sequential PGCs,
+into a conversion pipeline. Menus and seeking are not supported at this time.
+
+Block devices (DVD drives), ISO files, and directory structures are accepted.
+Activate with @code{-f dvdvideo} in front of one of these inputs.
+
+Underlying playback is fully handled by libdvdnav, and structure parsing by libdvdread.
+ffmpeg must be built with GPL library support available as well as the switches
+@code{--enable-libdvdnav} and @code{--enable-libdvdread}.
+
+You will need to provide either the desired "title number" or exact PGC/PG coordinates.
+Many open-source DVD players and tools can aid in providing this information.
+If not specified, the demuxer will default to title 1 which works for many discs.
+However, due to the flexibility of DVD-Video, it is recommended to check manually.
+There are many discs that are authored strangely or with invalid headers.
+
+If the input is a real DVD drive, please note that there are some drives which may
+silently fail on reading bad sectors from the disc, returning random bits instead
+which is effectively corrupt data. This is especially prominent on aging or rotting discs.
+A second pass and integrity checks would be needed to detect the corruption.
+This is not an ffmpeg issue.
+
+@subsection Background
+
+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 MPEG-PS streams that are stored in headerless VOB files.
+To the end-user, these streams are known simply as "titles", but the actual
+logical playback sequence is defined by one or more "PGCs", or Program Group Chains,
+within the title. The PGC is in turn comprised of multiple "PGs", or Programs",
+which are the actual video segments which, for a typical movie, are sequentially
+ordered. The PGC structure, along with stream layout and metadata, are stored in
+IFO files that need to be parsed. PGCs can be thought of as playlists in easier terms.
+
+An actual DVD player relies on user GUI interaction via menus and an internal VM
+to drive the direction of demuxing. Generally, the user would either navigate (via menus)
+or automatically be redirected to the PGC of their choice. During this process and
+the subsequent playback, the DVD player's internal VM also maintains a state and
+executes instructions that can create jumps to different sectors during playback.
+This is why libdvdnav is involved, as a linear read of the MPEG-PS blobs on the
+disc (VOBs) is not enough to produce the right sequence in many cases.
+
+There are many other DVD structures (a long subject) that will not be discussed here.
+NAV packets, in particular, are handled by this demuxer to build accurate timing
+but not emitted as a stream. For a good high-level understanding, refer to:
+@url{https://code.videolan.org/videolan/libdvdnav/-/blob/master/doc/dvd_structures}
+
+@subsection Options
+
+This demuxer accepts the following options:
+
+@table @option
+
+@item title
+The title number to play. Must be set if @option{pgc} and @option{pg} are not set.
+Default is 0 (auto), which currently only selects the first available title (title 1)
+and notifies the user about the implications.
+
+@item chapter_start
+The chapter, or PTT (part-of-title), number to start at. Default is 1.
+
+@item chapter_end
+The chapter, or PTT (part-of-title), number to end at. Default is 0,
+which is a special value to signal end at the last possible chapter.
+
+@item angle
+The video angle number, referring to what is essentially an additional
+video stream that is composed from alternate frames interleaved in the VOBs.
+Default is 1.
+
+@item region
+The region code to use for playback. Some discs may use this to default playback
+at a particular angle in different regions. This option will not affect the region code
+of a real DVD drive, if used as an input. Default is 0, "world".
+
+@item pgc
+The entry PGC to start playback, in conjunction with @option{pg}.
+Alternative to setting @option{title}.
+Chapter markers not supported at this time.
+Default is 0, automatically resolve from value of @option{title}.
+
+@item pg
+The entry PG to start playback, in conjunction with @option{pgc}.
+Alternative to setting @option{title}.
+Chapter markers not supported at this time.
+Default is 0, automatically resolve from value of @option{title}.
+
+@item preindex
+Enable this to have accurate chapter (PTT) markers and duration measurement,
+which requires a slow second pass read in order to index the chapter
+timestamps from NAV packets. This is non-ideal extra work for physical DVD drives.
+Not compatible with @option{pgc} and @option{pg}.
+Default is 0, false.
+
+@item trim
+Skip padding cells (i.e. cells shorter than 1 second) from the beginning.
+There exist many discs with filler segments at the beginning of the PGC,
+often with junk data intended for controlling a real DVD player's
+buffering speed and with no other material data value.
+Default is 1, true.
+
+@item clut_rgb
+Output subtitle palettes (CLUTs) as RGB, required for Matroska.
+Disable to output the palette in its original YUV colorspace.
+Default is 1, true.
+
+
+@end table
+
+@subsection Examples
+
+@itemize
+@item
+Open title 3 from a given DVD structure:
+@example
+ffmpeg -f dvdvideo -title 3 -i <path to DVD> ...
+@end example
+
+@item
+Open chapters 3-6 from title 1 from a given DVD structure:
+@example
+ffmpeg -f dvdvideo -chapter_start 3 -chapter_end 6 -title 1 -i <path to DVD> ...
+@end example
+
+@item
+Open only chapters 5 from title 1 from a given DVD structure:
+@example
+ffmpeg -f dvdvideo -chapter_start 5 -chapter_end 5 -title 1 -i <path to DVD> ...
+@end example
+@end itemize
+
@section ea
Electronic Arts Multimedia format demuxer.
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 05b9b8a115..df69734877 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 b04b43cab3..3e905c23f8 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/dvdvideodec.c b/libavformat/dvdvideodec.c
new file mode 100644
index 0000000000..03cf2d1f2c
--- /dev/null
+++ b/libavformat/dvdvideodec.c
@@ -0,0 +1,1409 @@
+/*
+ * DVD-Video demuxer, powered by libdvdnav and libdvdread
+ *
+ * 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
+ */
+
+/*
+ * See doc/demuxers.texi for a high-level overview.
+ *
+ * The tactical approach is as follows:
+ * 1) Open the volume with dvdread
+ * 2) Analyze the user-requested title and PGC coordinates in the IFO structures
+ * 3) Request playback at the coordinates and chosen angle with dvdnav
+ * 5) Begin the playback (reading and demuxing) of MPEG-PS blocks
+ * 6) End playback if navigation goes backwards, to a menu, or a different PGC or angle
+ * 7) Close the dvdnav VM, and free dvdnav's IFO structures
+ */
+
+#include <dvdnav/dvdnav.h>
+#include <dvdread/dvd_reader.h>
+#include <dvdread/ifo_read.h>
+#include <dvdread/ifo_types.h>
+#include <dvdread/nav_read.h>
+
+#include "libavcodec/avcodec.h"
+#include "libavutil/avstring.h"
+#include "libavutil/avutil.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+#include "libavutil/samplefmt.h"
+#include "libavutil/time.h"
+#include "libavutil/timestamp.h"
+
+#include "avformat.h"
+#include "avio_internal.h"
+#include "avlanguage.h"
+#include "demux.h"
+#include "internal.h"
+#include "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 64 /* VOBUs use 32 (PES allows 33) */
+#define DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE 1024
+
+enum DVDVideoSubpictureViewport {
+ DVDVIDEO_SUBP_VIEWPORT_FULLSCREEN,
+ DVDVIDEO_SUBP_VIEWPORT_WIDESCREEN,
+ DVDVIDEO_SUBP_VIEWPORT_LETTERBOX,
+ DVDVIDEO_SUBP_VIEWPORT_PANSCAN
+};
+const char* VIEWPORT_LABELS[4] = { "Fullscreen", "Widescreen", "Letterbox", "Pan and Scan" };
+
+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;
+ int disposition;
+ char *lang_iso;
+} DVDVideoPGCAudioStreamEntry;
+
+typedef struct DVDVideoPGCSubtitleStreamEntry {
+ int startcode;
+ int disposition;
+ char *lang_iso;
+ enum DVDVideoSubpictureViewport viewport;
+} DVDVideoPGCSubtitleStreamEntry;
+
+typedef struct DVDVideoPlaybackState {
+ int celln; /* ID of the active cell */
+ int entry_pgn; /* ID of the PG we are starting in */
+ int in_pgc; /* if our navigator is in the PGC */
+ int in_ps; /* if our navigator is in the program stream */
+ int in_vts; /* if our navigator is in the VTS */
+ int64_t nav_pts; /* PTS according to IFO, not frame-accurate */
+ int nb_vobus_played; /* number of VOBUs played back so far */
+ uint64_t pgc_duration_est; /* estimated duration as reported by IFO */
+ uint64_t pgc_elapsed; /* the elapsed time of the PGC, cell-relative */
+ int pgc_nb_pg_est; /* number of PGs as reported by IFOs */
+ int pgcn; /* ID of the PGC we are playing */
+ int pgn; /* ID of the PG we are in now */
+ int ptt; /* ID of the chapter we are in now */
+ int64_t ts_offset; /* PTS discontinuity offset (ex. VOB change) */
+ uint32_t vobu_duration; /* duration of the current VOBU */
+ uint32_t vobu_e_ptm; /* end PTS of the current VOBU */
+ int vtsn; /* ID of the active VTS (video title set) */
+ uint64_t *pgc_pg_times_est; /* PG start times as reported by IFO */
+ pgc_t *pgc; /* handle to the active PGC */
+ dvdnav_t *dvdnav; /* handle to libdvdnav */
+} DVDVideoPlaybackState;
+
+typedef struct DVDVideoDemuxContext {
+ const AVClass *class;
+
+ /* options */
+ int opt_title; /* the user-provided title number (1-indexed) */
+ int opt_chapter_start; /* the user-provided entry PTT (1-indexed) */
+ int opt_chapter_end; /* the user-provided exit PTT (0 for last) */
+ 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 */
+ int opt_preindex; /* pre-indexing mode (2-pass read) */
+ int opt_trim; /* trim padding cells at beginning and end */
+
+ /* 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) */
+
+ /* playback control */
+ int play_end; /* signal EOF to the parent demuxer */
+ DVDVideoPlaybackState play_state; /* the active playback state */
+ int play_started; /* signal that playback has started */
+ int play_seg_started; /* signal that segment has started */
+ int64_t first_pts; /* the starting PTS offset of the PGC */
+ uint64_t duration_ptm; /* total duration in DVD MPEG timebase */
+} DVDVideoDemuxContext;
+
+static void dvdvideo_libdvdread_log(void *opaque, dvd_logger_level_t level,
+ const char *msg, va_list msg_va)
+{
+ AVFormatContext *s = opaque;
+ int lavu_level;
+
+ char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
+ vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
+
+ switch (level) {
+ case DVD_LOGGER_LEVEL_ERROR:
+ lavu_level = AV_LOG_ERROR;
+ break;
+ case DVD_LOGGER_LEVEL_WARN:
+ lavu_level = AV_LOG_WARNING;
+ break;
+ /* dvdread's info messages are relatively very verbose, muffle them as debug */
+ case DVD_LOGGER_LEVEL_INFO:
+ case DVD_LOGGER_LEVEL_DEBUG:
+ default:
+ lavu_level = AV_LOG_DEBUG;
+ }
+
+ av_log(s, lavu_level, "libdvdread: %s\n", msg_buf);
+}
+
+static void dvdvideo_libdvdnav_log(void *opaque, dvdnav_logger_level_t level,
+ const char *msg, va_list msg_va)
+{
+ AVFormatContext *s = opaque;
+ int lavu_level;
+
+ char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
+ vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
+
+ switch (level) {
+ case DVDNAV_LOGGER_LEVEL_ERROR:
+ lavu_level = AV_LOG_ERROR;
+ break;
+ case DVDNAV_LOGGER_LEVEL_WARN:
+ /* some discs have invalid language codes set for their menus, muffle the noise */
+ lavu_level = av_strstart(msg, "Language", NULL) ? AV_LOG_DEBUG : AV_LOG_WARNING;
+ break;
+ /* dvdnav's info messages are relatively very verbose, muffle them as debug */
+ case DVDNAV_LOGGER_LEVEL_INFO:
+ case DVDNAV_LOGGER_LEVEL_DEBUG:
+ default:
+ lavu_level = AV_LOG_DEBUG;
+ }
+
+ av_log(s, lavu_level, "libdvdnav: %s\n", msg_buf);
+}
+
+static void dvdvideo_ifo_close(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ 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_ifo_open(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ dvd_logger_cb dvdread_log_cb;
+ title_info_t title_info;
+
+ dvdread_log_cb = (dvd_logger_cb) { .pf_log = dvdvideo_libdvdread_log };
+ c->dvdread = DVDOpen2(s, &dvdread_log_cb, s->url);
+
+ if (!c->dvdread) {
+ av_log(s, AV_LOG_ERROR, "Unable to open the DVD-Video structure\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ if (!(c->vmg_ifo = ifoOpen(c->dvdread, 0))) {
+ av_log(s, AV_LOG_ERROR, "Unable to open the VMG (VIDEO_TS.IFO)\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ if (c->opt_title > c->vmg_ifo->tt_srpt->nr_of_srpts) {
+ av_log(s, AV_LOG_ERROR, "Title %d not found\n", c->opt_title);
+
+ 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 %d not found\n", c->opt_angle);
+
+ return AVERROR_STREAM_NOT_FOUND;
+ }
+
+ if (title_info.nr_of_ptts < 1) {
+ av_log(s, AV_LOG_ERROR, "Title %d has invalid headers in VMG\n", c->opt_title);
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (c->opt_chapter_start > title_info.nr_of_ptts
+ || (c->opt_chapter_end > 0 && c->opt_chapter_end > title_info.nr_of_ptts)) {
+ av_log(s, AV_LOG_ERROR, "Chapter (PTT) range [%d, %d] is invalid\n",
+ c->opt_chapter_start, c->opt_chapter_end);
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (!(c->vts_ifo = ifoOpen(c->dvdread, title_info.title_set_nr))) {
+ av_log(s, AV_LOG_ERROR, "Unable to process the VTS IFO structure\n");
+
+ return AVERROR_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 %d has invalid headers in VTS\n", c->opt_title);
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ return 0;
+}
+
+static void dvdvideo_play_close(AVFormatContext *s, DVDVideoPlaybackState *state)
+{
+ if (!state->dvdnav)
+ return;
+
+ /* not allocated by av_malloc() */
+ if (state->pgc_pg_times_est)
+ free(state->pgc_pg_times_est);
+
+ if (dvdnav_close(state->dvdnav) < 0)
+ av_log(s, AV_LOG_ERROR, "Unable to cleanly close dvdnav\n");
+}
+
+static int dvdvideo_play_open(AVFormatContext *s, DVDVideoPlaybackState *state)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret = 0;
+ dvdnav_logger_cb dvdnav_log_cb;
+ dvdnav_status_t dvdnav_open_status;
+ int cur_title, cur_pgcn, cur_pgn;
+ pgc_t *pgc;
+
+ int32_t disc_region_mask;
+ int32_t player_region_mask;
+
+ dvdnav_log_cb = (dvdnav_logger_cb) { .pf_log = dvdvideo_libdvdnav_log };
+ dvdnav_open_status = dvdnav_open2(&state->dvdnav, s, &dvdnav_log_cb, s->url);
+
+ if (!state->dvdnav
+ || dvdnav_open_status != DVDNAV_STATUS_OK
+ || dvdnav_set_readahead_flag(state->dvdnav, 0) != DVDNAV_STATUS_OK
+ || dvdnav_set_PGC_positioning_flag(state->dvdnav, 1) != DVDNAV_STATUS_OK
+ || dvdnav_get_region_mask(state->dvdnav, &disc_region_mask) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to open the DVD for playback\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ player_region_mask = c->opt_region > 0 ? (1 << (c->opt_region - 1)) : disc_region_mask;
+ if (dvdnav_set_region_mask(state->dvdnav, player_region_mask) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to set the playback region code %d\n", c->opt_region);
+
+ return AVERROR_EXTERNAL;
+ }
+
+ if (c->opt_pgc > 0 && c->opt_pg > 0) {
+ if (dvdnav_program_play(state->dvdnav, c->opt_title,
+ c->opt_pgc, c->opt_pg) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to start playback at title %d, PGC %d, PG %d\n",
+ c->opt_title, c->opt_pgc, c->opt_pg);
+
+ return AVERROR_EXTERNAL;
+ }
+
+ state->pgcn = c->opt_pgc;
+ state->entry_pgn = c->opt_pg;
+ } else {
+ if (dvdnav_part_play(state->dvdnav, c->opt_title, c->opt_chapter_start) != DVDNAV_STATUS_OK
+ || dvdnav_current_title_program(state->dvdnav, &cur_title,
+ &cur_pgcn, &cur_pgn) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to start playback at title %d, chapter (PTT) %d\n",
+ c->opt_title, c->opt_chapter_start);
+
+ return AVERROR_EXTERNAL;
+ }
+
+ state->pgcn = cur_pgcn;
+ state->entry_pgn = cur_pgn;
+ }
+
+ pgc = c->vts_ifo->vts_pgcit->pgci_srp[state->pgcn - 1].pgc;
+
+ if (pgc->pg_playback_mode != 0) {
+ av_log(s, AV_LOG_ERROR, "Non-sequential PGCs, such as shuffles, are not supported\n");
+
+ return AVERROR_PATCHWELCOME;
+ }
+
+ if (dvdnav_angle_change(state->dvdnav, c->opt_angle) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to start playback at the given angle\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ /* dvdnav_describe_title_chapters() performs several safety checks on the title structure */
+ /* take advantage of this side effect to ensure a safe navigation path */
+ state->pgc_nb_pg_est = dvdnav_describe_title_chapters(state->dvdnav, c->opt_title,
+ &state->pgc_pg_times_est,
+ &state->pgc_duration_est);
+ if (!state->pgc_nb_pg_est) {
+ av_log(s, AV_LOG_ERROR, "Unable to validate title structure\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ state->nav_pts = dvdnav_get_current_time(state->dvdnav);
+ state->vtsn = c->vmg_ifo->tt_srpt->title[c->opt_title - 1].title_set_nr;
+
+ state->pgc = pgc;
+
+ return ret;
+}
+
+static int dvdvideo_play_is_cell_promising(AVFormatContext *s, DVDVideoPlaybackState *state,
+ int celln)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+ dvd_time_t cell_duration;
+
+ if (!c->opt_trim)
+ return 1;
+
+ cell_duration = state->pgc->cell_playback[celln - 1].playback_time;
+
+ return cell_duration.second >= 1 || cell_duration.minute >= 1 || cell_duration.hour >= 1;
+}
+
+static int dvdvideo_play_next_ps_block(AVFormatContext *s, DVDVideoPlaybackState *state,
+ uint8_t *buf, int buf_size,
+ int *p_nav_event,
+ void (*flush_cb)(AVFormatContext *s))
+{
+ 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;
+ int is_cell_promising = 0;
+ pci_t *e_pci;
+ dsi_t *e_dsi;
+
+ 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(state->dvdnav, nav_buf, &nav_event, &nav_len) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to read next block of PGC\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ /* some discs follow NOPs with a premature stop event */
+ if (nav_event == DVDNAV_STOP)
+ return AVERROR_EOF;
+
+ if (nav_len > DVDVIDEO_BLOCK_SIZE) {
+ av_log(s, AV_LOG_ERROR, "Invalid block size\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (dvdnav_current_title_info(state->dvdnav, &cur_title,
+ &cur_ptt) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to validate title coordinates\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ /* we somehow navigated to a menu */
+ if (cur_title == 0 || !dvdnav_is_domain_vts(state->dvdnav))
+ return AVERROR_EOF;
+
+ if (dvdnav_current_title_program(state->dvdnav, &cur_title_unused,
+ &cur_pgcn, &cur_pgn) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to validate PGC coordinates\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ /* we somehow left the PGC */
+ if (state->in_pgc && cur_pgcn != state->pgcn)
+ return AVERROR_EOF;
+
+ if (dvdnav_get_angle_info(state->dvdnav, &cur_angle, &cur_nb_angles) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to validate angle coordinates\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ av_log(s, nav_event == DVDNAV_BLOCK_OK ? AV_LOG_TRACE : AV_LOG_DEBUG,
+ "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, state->celln, cur_pgcn, cur_pgn,
+ state->in_vts, state->in_pgc, state->in_ps);
+
+ switch (nav_event) {
+ case DVDNAV_VTS_CHANGE:
+ if (state->in_vts)
+ return AVERROR_EOF;
+
+ e_vts = (dvdnav_vts_change_event_t *) nav_buf;
+
+ if (e_vts->new_vtsN == state->vtsn && e_vts->new_domain == DVD_DOMAIN_VTSTitle)
+ state->in_vts = 1;
+
+ continue;
+ case DVDNAV_CELL_CHANGE:
+ if (!state->in_vts)
+ continue;
+
+ e_cell = (dvdnav_cell_change_event_t *) nav_buf;
+ is_cell_promising = dvdvideo_play_is_cell_promising(s, state, e_cell->cellN);
+
+ av_log(s, AV_LOG_DEBUG, "new cell: prev=%d new=%d promising=%d\n",
+ state->celln, e_cell->cellN, is_cell_promising);
+
+ if (!state->in_ps && !state->in_pgc) {
+ if (cur_title == c->opt_title && cur_ptt == c->opt_chapter_start
+ && cur_pgcn == state->pgcn && cur_pgn == state->entry_pgn
+ && is_cell_promising) {
+ state->in_pgc = 1;
+ }
+
+ if (c->opt_trim && !is_cell_promising)
+ av_log(s, AV_LOG_INFO, "Skipping padding cell #%d\n", e_cell->cellN);
+ } else if (state->celln >= e_cell->cellN || state->pgn > cur_pgn) {
+ return AVERROR_EOF;
+ }
+
+ state->celln = e_cell->cellN;
+ state->ptt = cur_ptt;
+ state->pgn = cur_pgn;
+
+ continue;
+ case DVDNAV_NAV_PACKET:
+ if (!state->in_pgc)
+ continue;
+
+ if ((state->ptt > 0 && state->ptt > cur_ptt)
+ || (c->opt_chapter_end > 0 && cur_ptt > c->opt_chapter_end)) {
+ return AVERROR_EOF;
+ }
+
+ e_pci = dvdnav_get_current_nav_pci(state->dvdnav);
+ e_dsi = dvdnav_get_current_nav_dsi(state->dvdnav);
+
+ if (e_pci == NULL || e_dsi == NULL
+ || e_pci->pci_gi.vobu_s_ptm > e_pci->pci_gi.vobu_e_ptm) {
+ av_log(s, AV_LOG_ERROR, "Invalid NAV packet\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ state->vobu_duration = e_pci->pci_gi.vobu_e_ptm - e_pci->pci_gi.vobu_s_ptm;
+ state->pgc_elapsed += state->vobu_duration;
+ state->nav_pts = dvdnav_get_current_time(state->dvdnav);
+ state->ptt = cur_ptt;
+ state->pgn = cur_pgn;
+ state->nb_vobus_played++;
+
+ av_log(s, AV_LOG_DEBUG, "NAV pack: s_ptm=%d e_ptm=%d "
+ "scr=%d lbn=%d vobu_duration=%d nav_pts=%ld\n",
+ e_pci->pci_gi.vobu_s_ptm, e_pci->pci_gi.vobu_e_ptm,
+ e_dsi->dsi_gi.nv_pck_scr,
+ e_pci->pci_gi.nv_pck_lbn, state->vobu_duration, state->nav_pts);
+
+ if (!state->in_ps) {
+ av_log(s, AV_LOG_DEBUG, "navigation: locked to program stream\n");
+
+ state->in_ps = 1;
+ } else {
+ if (state->vobu_e_ptm != e_pci->pci_gi.vobu_s_ptm) {
+ if (flush_cb)
+ flush_cb(s);
+
+ state->ts_offset += state->vobu_e_ptm - e_pci->pci_gi.vobu_s_ptm;
+ }
+ }
+
+ state->vobu_e_ptm = e_pci->pci_gi.vobu_e_ptm;
+
+ (*p_nav_event) = nav_event;
+
+ return nav_len;
+ case DVDNAV_BLOCK_OK:
+ if (!state->in_ps) {
+ if (state->in_pgc)
+ i = 0; /* necessary in case we are skpping junk cells at the beginning */
+ continue;
+ }
+
+ if (nav_len != DVDVIDEO_BLOCK_SIZE) {
+ av_log(s, AV_LOG_ERROR, "Invalid block size\n");
+
+ 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);
+
+ /* in case NAV packet is missed */
+ state->ptt = cur_ptt;
+ state->pgn = cur_pgn;
+
+ (*p_nav_event) = nav_event;
+
+ return nav_len;
+ case DVDNAV_STILL_FRAME:
+ case DVDNAV_WAIT:
+ case DVDNAV_HOP_CHANNEL:
+ case DVDNAV_HIGHLIGHT:
+ if (state->in_ps)
+ return AVERROR_EOF;
+
+ if (nav_event == DVDNAV_STILL_FRAME)
+ dvdnav_still_skip(state->dvdnav);
+ if (nav_event == DVDNAV_WAIT)
+ dvdnav_wait_skip(state->dvdnav);
+
+ 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_INVALIDDATA;
+}
+
+static int dvdvideo_pgc_preindex(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret = 0, interrupt = 0;
+ int nb_chapters = 0, last_ptt = c->opt_chapter_start;
+ uint64_t cur_chapter_offset = 0, cur_chapter_duration = 0;
+ DVDVideoPlaybackState *state;
+
+ uint8_t nav_buf[DVDVIDEO_BLOCK_SIZE];
+ int nav_event;
+
+ if (c->opt_chapter_start == c->opt_chapter_end)
+ return 0;
+
+ av_log(s, AV_LOG_INFO,
+ "Indexing chapter markers, this will take a long time. Please wait...\n");
+
+ state = av_mallocz(sizeof(DVDVideoPlaybackState));
+ if ((ret = dvdvideo_play_open(s, state)) < 0) {
+ av_freep(&state);
+ return ret;
+ }
+
+ while (!(interrupt = ff_check_interrupt(&s->interrupt_callback))) {
+ ret = dvdvideo_play_next_ps_block(s, state, nav_buf, DVDVIDEO_BLOCK_SIZE,
+ &nav_event, NULL);
+ if (ret < 0 && ret != AVERROR_EOF)
+ goto end_free;
+
+ if (nav_event != DVDNAV_NAV_PACKET && ret != AVERROR_EOF)
+ continue;
+
+ if (state->ptt == last_ptt) {
+ cur_chapter_duration += state->vobu_duration;
+ /* ensure we add the last chapter */
+ if (ret != AVERROR_EOF)
+ continue;
+ }
+
+ if (!avpriv_new_chapter(s, nb_chapters, DVDVIDEO_TIME_BASE_Q, cur_chapter_offset,
+ cur_chapter_offset + cur_chapter_duration, NULL)) {
+ av_log(s, AV_LOG_ERROR, "Unable to allocate chapter\n");
+
+ goto end_free;
+ }
+
+ nb_chapters++;
+ cur_chapter_offset += cur_chapter_duration;
+ cur_chapter_duration = state->vobu_duration;
+ last_ptt = state->ptt;
+
+ if (ret == AVERROR_EOF)
+ break;
+ }
+
+ if (interrupt) {
+ ret = AVERROR_EXIT;
+ goto end_free;
+ }
+
+ if (ret < 0 && ret != AVERROR_EOF)
+ goto end_free;
+
+ s->duration = av_rescale_q(state->pgc_elapsed, DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
+ c->duration_ptm = state->pgc_elapsed;
+
+ av_log(s, AV_LOG_INFO, "Chapter marker indexing complete\n");
+ ret = 0;
+
+end_free:
+ dvdvideo_play_close(s, state);
+ av_freep(&state);
+
+ return ret;
+}
+
+static int dvdvideo_pgc_chapters_setup(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ uint64_t time_prev = 0;
+ int64_t total_duration = 0;
+
+ int chapter_start = c->opt_chapter_start - 1;
+ int chapter_end = c->opt_chapter_end > 0 ? c->opt_chapter_end : c->play_state.pgc_nb_pg_est - 1;
+
+ if (chapter_start == chapter_end || c->play_state.pgc_nb_pg_est == 1) {
+ s->duration = av_rescale_q(c->play_state.pgc_duration_est,
+ DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
+ return 0;
+ }
+
+ for (int i = chapter_start; i < chapter_end; i++) {
+ uint64_t time_effective = c->play_state.pgc_pg_times_est[i] - c->play_state.nav_pts;
+
+ 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;
+ total_duration = time_effective;
+ }
+
+ if (c->opt_chapter_start == 1 && c->opt_chapter_end == 0)
+ s->duration = av_rescale_q(c->play_state.pgc_duration_est,
+ DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
+ else
+ s->duration = av_rescale_q(total_duration,
+ DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
+
+ return 0;
+}
+
+static int dvdvideo_video_stream_analyze(AVFormatContext *s, 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) {
+ av_log(s, AV_LOG_ERROR, "Invalid video dimensions\n");
+
+ 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 = !is_pal && (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 = NULL;
+ int ret = 0;
+
+ entry = av_mallocz(sizeof(DVDVideoVTSVideoStreamEntry));
+
+ if ((ret = dvdvideo_video_stream_analyze(s, 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_HEADERS)) < 0) {
+ av_freep(&entry);
+ av_log(s, AV_LOG_ERROR, "Unable to allocate video stream\n");
+
+ return ret;
+ }
+
+ av_freep(&entry);
+
+ return ret;
+}
+
+static int dvdvideo_audio_stream_analyze(AVFormatContext *s, 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 = (AVChannelLayout) {0};
+ char lang_dvd[3] = {0};
+
+ int position = (audio_control & 0x7F00) >> 8;
+
+ /* XXX(PATCHWELCOME): SDDS is not supported due to lack of sample material */
+ switch (audio_attr.audio_format) {
+ case 0: /* AC3 */
+ codec_id = AV_CODEC_ID_AC3;
+ sample_fmt = AV_SAMPLE_FMT_FLTP;
+ sample_rate = 48000;
+ startcode = 0x80 + position;
+ break;
+ case 2: /* MP1 */
+ 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: /* MP2 */
+ 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: /* DVD PCM */
+ codec_id = AV_CODEC_ID_PCM_DVD;
+ 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: /* DCA */
+ 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) {
+ av_log(s, AV_LOG_ERROR, "Invalid audio parameters\n");
+
+ 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 == 7)
+ ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_6POINT1;
+ else if (nb_channels == 8)
+ ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_7POINT1;
+
+ if (audio_attr.code_extension == 2)
+ entry->disposition |= AV_DISPOSITION_VISUAL_IMPAIRED;
+ if (audio_attr.code_extension == 3 || audio_attr.code_extension == 4)
+ entry->disposition |= AV_DISPOSITION_COMMENT;
+
+ 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;
+ st->disposition = entry->disposition;
+
+ 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 = 0;
+
+ for (int i = 0; i < c->vts_ifo->vtsi_mat->nr_of_vts_audio_streams; i++) {
+ DVDVideoPGCAudioStreamEntry *entry = NULL;
+
+ if (!(c->play_state.pgc->audio_control[i] & 0x8000))
+ continue;
+
+ entry = av_mallocz(sizeof(DVDVideoPGCAudioStreamEntry));
+ if (!entry)
+ return AVERROR(ENOMEM);
+
+ if ((ret = dvdvideo_audio_stream_analyze(s, c->vts_ifo->vtsi_mat->vts_audio_attr[i],
+ c->play_state.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, "Karaoke metadata in stream %d will not be retained\n",
+ entry->startcode);
+
+ /* IFO structures can declare duplicate entries for the same 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
+ || (ret = dvdvideo_audio_stream_add(s, entry, AVSTREAM_PARSE_HEADERS)) < 0)
+ goto break_free_and_error;
+
+continue_free:
+ av_freep(&entry);
+ continue;
+
+break_free_and_error:
+ av_freep(&entry);
+ av_log(s, AV_LOG_ERROR, "Unable to allocate audio stream\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+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);
+
+ if (subp_attr.lang_extension == 9)
+ entry->disposition |= AV_DISPOSITION_FORCED;
+
+ 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;
+ int ret = 0;
+
+ 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;
+
+ if (entry->lang_iso)
+ av_dict_set(&st->metadata, "language", entry->lang_iso, 0);
+
+ av_dict_set(&st->metadata, "VIEWPORT", VIEWPORT_LABELS[entry->viewport], 0);
+
+ st->disposition = entry->disposition;
+
+ 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 ret;
+}
+
+static int dvdvideo_subp_stream_add_internal(AVFormatContext *s, uint32_t offset,
+ subp_attr_t subp_attr,
+ enum DVDVideoSubpictureViewport viewport)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ DVDVideoPGCSubtitleStreamEntry *entry = NULL;
+ int ret = 0;
+
+ entry = av_mallocz(sizeof(DVDVideoPGCSubtitleStreamEntry));
+ entry->viewport = viewport;
+
+ if ((ret = dvdvideo_subp_stream_analyze(s, offset, subp_attr, entry)) < 0)
+ goto end_free_error;
+
+ /* IFO structures can declare duplicate entries for the same startcode */
+ 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_HEADERS)) < 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_freep(&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 = 0;
+ uint32_t subp_control;
+ subp_attr_t subp_attr;
+ video_attr_t video_attr;
+
+ subp_control = c->play_state.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,
+ DVDVIDEO_SUBP_VIEWPORT_FULLSCREEN)) < 0)
+ return ret;
+
+ continue;
+ }
+
+ /* 16:9 */
+ if ((ret = dvdvideo_subp_stream_add_internal(s, subp_control >> 16, subp_attr,
+ DVDVIDEO_SUBP_VIEWPORT_WIDESCREEN)) < 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,
+ DVDVIDEO_SUBP_VIEWPORT_LETTERBOX)) < 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,
+ DVDVIDEO_SUBP_VIEWPORT_PANSCAN)) < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void dvdvideo_subdemux_flush(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ if (!c->play_seg_started)
+ return;
+
+ av_log(s, AV_LOG_DEBUG, "flushing sub-demuxer\n");
+ avio_flush(&c->mpeg_pb.pub);
+ ff_read_frame_flush(c->mpeg_ctx);
+ c->play_seg_started = 0;
+}
+
+static int dvdvideo_subdemux_read_data(void *opaque, uint8_t *buf, int buf_size)
+{
+ AVFormatContext *s = opaque;
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret = 0;
+ int nav_event;
+
+ if (c->play_end)
+ return AVERROR_EOF;
+
+ ret = dvdvideo_play_next_ps_block(opaque, &c->play_state, buf, buf_size,
+ &nav_event, dvdvideo_subdemux_flush);
+
+ if (ret == AVERROR_EOF) {
+ c->mpeg_pb.pub.eof_reached = 1;
+ c->play_end = 1;
+
+ return AVERROR_EOF;
+ }
+
+ if (ret >= 0 && nav_event == DVDNAV_NAV_PACKET)
+ return FFERROR_REDO;
+
+ return ret;
+}
+
+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 = 0;
+
+ if (!(c->mpeg_fmt = av_find_input_format("mpeg")))
+ return AVERROR_DEMUXER_NOT_FOUND;
+
+ if (!(c->mpeg_ctx = avformat_alloc_context()))
+ return AVERROR(ENOMEM);
+
+ if (!(c->mpeg_buf = av_mallocz(DVDVIDEO_BLOCK_SIZE))) {
+ avformat_free_context(c->mpeg_ctx);
+
+ return AVERROR(ENOMEM);
+ }
+
+ ffio_init_context(&c->mpeg_pb, c->mpeg_buf, DVDVIDEO_BLOCK_SIZE, 0, s,
+ dvdvideo_subdemux_read_data, NULL, NULL);
+ c->mpeg_pb.pub.seekable = 0;
+
+ if ((ret = ff_copy_whiteblacklists(c->mpeg_ctx, s)) < 0) {
+ avformat_free_context(c->mpeg_ctx);
+
+ return ret;
+ }
+
+ c->mpeg_ctx->flags = AVFMT_FLAG_CUSTOM_IO | AVFMT_FLAG_GENPTS;
+ c->mpeg_ctx->ctx_flags |= AVFMTCTX_UNSEEKABLE;
+ c->mpeg_ctx->probesize = 0;
+ c->mpeg_ctx->max_analyze_duration = 0;
+ c->mpeg_ctx->interrupt_callback = s->interrupt_callback;
+ c->mpeg_ctx->pb = &c->mpeg_pb.pub;
+ c->mpeg_ctx->io_open = NULL;
+
+ if ((ret = avformat_open_input(&c->mpeg_ctx, "", c->mpeg_fmt, NULL)) < 0) {
+ avformat_free_context(c->mpeg_ctx);
+
+ return ret;
+ }
+
+ return ret;
+}
+
+static int dvdvideo_read_header(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret = 0;
+
+ if (c->opt_title == 0) {
+ av_log(s, AV_LOG_INFO, "Defaulting to title #1. "
+ "This is not always the main feature, validation suggested.\n");
+
+ c->opt_title = 1;
+ }
+
+ if (c->opt_pgc) {
+ if (c->opt_pg == 0)
+ av_log(s, AV_LOG_ERROR, "Invalid coordinates. If -pgc is set, -pg must be set too.\n");
+ else if (c->opt_chapter_start > 1 || c->opt_chapter_end > 0 || c->opt_preindex)
+ av_log(s, AV_LOG_ERROR, "-pgc is not compatible with -preindex, -chapter_start/end\n");
+
+ return AVERROR(EINVAL);
+ }
+
+ if ((ret = dvdvideo_ifo_open(s)) < 0)
+ return ret;
+
+ if (c->opt_preindex && (ret = dvdvideo_pgc_preindex(s)) < 0)
+ return ret;
+
+ if ((ret = dvdvideo_play_open(s, &c->play_state)) < 0
+ || (ret = dvdvideo_subdemux_open(s)) < 0
+ || (ret = dvdvideo_video_stream_setup(s)) < 0
+ || (ret = dvdvideo_audio_stream_add_all(s)) < 0
+ || (ret = dvdvideo_subp_stream_add_all(s)) < 0)
+ return ret;
+
+ if (!c->opt_preindex)
+ return dvdvideo_pgc_chapters_setup(s);
+
+ return ret;
+}
+
+static int dvdvideo_read_packet(AVFormatContext *s, AVPacket *pkt)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret;
+ enum AVMediaType st_type;
+ int64_t cur_delta = 0;
+
+ if (c->play_end)
+ return AVERROR_EOF;
+
+ ret = av_read_frame(c->mpeg_ctx, pkt);
+
+ if (ret < 0)
+ return ret;
+
+ if (!c->play_seg_started)
+ c->play_seg_started = 1;
+
+ if (pkt->stream_index >= s->nb_streams) {
+ av_log(s, AV_LOG_DEBUG, "discarding frame with unknown stream\n");
+ goto skip_redo;
+ }
+
+ st_type = c->mpeg_ctx->streams[pkt->stream_index]->codecpar->codec_type;
+
+ if (!c->play_started) {
+ if (st_type != AVMEDIA_TYPE_VIDEO || pkt->pts == AV_NOPTS_VALUE
+ || pkt->dts == AV_NOPTS_VALUE
+ || !(pkt->flags & AV_PKT_FLAG_KEY)) {
+ av_log(s, AV_LOG_DEBUG, "discarding non-video-keyframe at start\n");
+ goto skip_redo;
+ }
+
+ c->first_pts = pkt->pts;
+ c->play_started = 1;
+
+ pkt->pts = 0;
+ pkt->dts = c->play_state.ts_offset - pkt->pts;
+ } else {
+ cur_delta = c->play_state.ts_offset - c->first_pts;
+
+ if (c->play_state.nb_vobus_played == 1 && (pkt->pts == AV_NOPTS_VALUE
+ || pkt->dts == AV_NOPTS_VALUE
+ || (pkt->pts + cur_delta) < 0)) {
+ av_log(s, AV_LOG_DEBUG, "discarding frame with negative or unset timestamp "
+ "(this is OK at the start of the first playback VOBU)\n");
+ goto skip_redo;
+ }
+
+ if (pkt->pts != AV_NOPTS_VALUE)
+ pkt->pts += cur_delta;
+ if (pkt->dts != AV_NOPTS_VALUE)
+ pkt->dts += cur_delta;
+ }
+
+ av_log(s, AV_LOG_TRACE, "st=%d pts=%ld dts=%ld ts_offset=%ld first_pts=%ld\n",
+ pkt->stream_index, pkt->pts, pkt->dts,
+ c->play_state.ts_offset, c->first_pts);
+ if (pkt->pts < 0)
+ av_log(s, AV_LOG_WARNING, "Invalid frame PTS @ st=%d pts=%ld dts=%ld\n",
+ pkt->stream_index, pkt->pts, pkt->dts);
+
+ return c->play_end ? AVERROR_EOF : 0;
+
+skip_redo:
+ av_packet_unref(pkt);
+ return FFERROR_REDO;
+}
+
+static int dvdvideo_close(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ dvdvideo_subdemux_close(s);
+ dvdvideo_play_close(s, &c->play_state);
+ dvdvideo_ifo_close(s);
+
+ return 0;
+}
+
+#define OFFSET(x) offsetof(DVDVideoDemuxContext, x)
+static const AVOption dvdvideo_options[] = {
+ {"angle", "playback angle number", OFFSET(opt_angle), AV_OPT_TYPE_INT, { .i64=1 }, 1, 9, AV_OPT_FLAG_DECODING_PARAM },
+ {"chapter_end", "exit chapter (PTT) number", OFFSET(opt_chapter_end), AV_OPT_TYPE_INT, { .i64=0 }, 0, 99, AV_OPT_FLAG_DECODING_PARAM },
+ {"chapter_start", "entry chapter (PTT) number", OFFSET(opt_chapter_start), AV_OPT_TYPE_INT, { .i64=1 }, 1, 99, 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 },
+ {"pgc", "entry PGC number (0=auto)", OFFSET(opt_pgc), AV_OPT_TYPE_INT, { .i64=0 }, 0, 999, AV_OPT_FLAG_DECODING_PARAM },
+ {"preindex", "enable for accurate chapter markers, slow (2-pass read)", OFFSET(opt_preindex), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, 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 },
+ {"title", "title number", OFFSET(opt_title), AV_OPT_TYPE_INT, { .i64=0 }, 0, 99, AV_OPT_FLAG_DECODING_PARAM },
+ {"trim", "trim padding cells from start", OFFSET(opt_trim), AV_OPT_TYPE_BOOL, { .i64=1 }, 0, 1, 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_close = dvdvideo_close,
+ .read_header = dvdvideo_read_header,
+ .read_packet = dvdvideo_read_packet
+};
--
2.34.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH v6] libavformat/dvdvideo: add DVD-Video demuxer powered by libdvdnav and libdvdread
2024-02-05 0:02 ` [FFmpeg-devel] [PATCH v6] libavformat/dvdvideo: add DVD-Video demuxer " Marth64
@ 2024-02-05 0:09 ` Marth64
2024-02-05 5:48 ` [FFmpeg-devel] [PATCH v7 1/2] " Marth64
1 sibling, 0 replies; 27+ messages in thread
From: Marth64 @ 2024-02-05 0:09 UTC (permalink / raw)
To: Marth64; +Cc: ffmpeg-devel
Gah, just realized a minor issue in the documentation only. The -clut_rgb
option documentation is meant for the accompanying subtitle patchset, and
can be ignored.
I'll remove it, but will buffer the update in case there is more feedback.
Thanks!
On Sun, Feb 4, 2024 at 6:06 PM Marth64 <marth64@proxyid.net> wrote:
> - Further improve documentation, logging, and indentation
> - Improve stream starting logic to be far more robust and not break GOPs
> - Resolve and remove timing workaround for chapter markers when starting
> after ch. 1
> - Fix sketchy error handling logic for certain edge case when NAV pack is
> missing
>
> Overall it's in pretty good working shape.
>
> Subtitle palette support remains in a separate patch set.
>
> Signed-off-by: Marth64 <marth64@proxyid.net>
> ---
> Changelog | 2 +
> configure | 8 +
> doc/demuxers.texi | 135 ++++
> libavformat/Makefile | 1 +
> libavformat/allformats.c | 1 +
> libavformat/dvdvideodec.c | 1409 +++++++++++++++++++++++++++++++++++++
> 6 files changed, 1556 insertions(+)
> create mode 100644 libavformat/dvdvideodec.c
>
> diff --git a/Changelog b/Changelog
> index c5fb21d198..88653bc6d3 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -24,6 +24,8 @@ version <next>:
> - ffmpeg CLI options may now be used as -/opt <path>, which is equivalent
> to -opt <contents of file <path>>
> - showinfo bitstream filter
> +- DVD-Video demuxer, powered by libdvdnav and libdvdread
> +
>
> version 6.1:
> - libaribcaption decoder
> diff --git a/configure b/configure
> index 68f675a4bc..70c33ec96d 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
> @@ -3520,6 +3524,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"
> @@ -6761,6 +6767,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/doc/demuxers.texi b/doc/demuxers.texi
> index e4c5b560a6..d8bc806ad5 100644
> --- a/doc/demuxers.texi
> +++ b/doc/demuxers.texi
> @@ -285,6 +285,141 @@ This demuxer accepts the following option:
>
> @end table
>
> +@section dvdvideo
> +
> +DVD-Video demuxer, powered by libdvdnav and libdvdread.
> +
> +Can directly ingest DVD titles, specifically sequential PGCs,
> +into a conversion pipeline. Menus and seeking are not supported at this
> time.
> +
> +Block devices (DVD drives), ISO files, and directory structures are
> accepted.
> +Activate with @code{-f dvdvideo} in front of one of these inputs.
> +
> +Underlying playback is fully handled by libdvdnav, and structure parsing
> by libdvdread.
> +ffmpeg must be built with GPL library support available as well as the
> switches
> +@code{--enable-libdvdnav} and @code{--enable-libdvdread}.
> +
> +You will need to provide either the desired "title number" or exact
> PGC/PG coordinates.
> +Many open-source DVD players and tools can aid in providing this
> information.
> +If not specified, the demuxer will default to title 1 which works for
> many discs.
> +However, due to the flexibility of DVD-Video, it is recommended to check
> manually.
> +There are many discs that are authored strangely or with invalid headers.
> +
> +If the input is a real DVD drive, please note that there are some drives
> which may
> +silently fail on reading bad sectors from the disc, returning random bits
> instead
> +which is effectively corrupt data. This is especially prominent on aging
> or rotting discs.
> +A second pass and integrity checks would be needed to detect the
> corruption.
> +This is not an ffmpeg issue.
> +
> +@subsection Background
> +
> +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 MPEG-PS streams that are stored in headerless VOB files.
> +To the end-user, these streams are known simply as "titles", but the
> actual
> +logical playback sequence is defined by one or more "PGCs", or Program
> Group Chains,
> +within the title. The PGC is in turn comprised of multiple "PGs", or
> Programs",
> +which are the actual video segments which, for a typical movie, are
> sequentially
> +ordered. The PGC structure, along with stream layout and metadata, are
> stored in
> +IFO files that need to be parsed. PGCs can be thought of as playlists in
> easier terms.
> +
> +An actual DVD player relies on user GUI interaction via menus and an
> internal VM
> +to drive the direction of demuxing. Generally, the user would either
> navigate (via menus)
> +or automatically be redirected to the PGC of their choice. During this
> process and
> +the subsequent playback, the DVD player's internal VM also maintains a
> state and
> +executes instructions that can create jumps to different sectors during
> playback.
> +This is why libdvdnav is involved, as a linear read of the MPEG-PS blobs
> on the
> +disc (VOBs) is not enough to produce the right sequence in many cases.
> +
> +There are many other DVD structures (a long subject) that will not be
> discussed here.
> +NAV packets, in particular, are handled by this demuxer to build accurate
> timing
> +but not emitted as a stream. For a good high-level understanding, refer
> to:
> +@url{
> https://code.videolan.org/videolan/libdvdnav/-/blob/master/doc/dvd_structures
> }
> +
> +@subsection Options
> +
> +This demuxer accepts the following options:
> +
> +@table @option
> +
> +@item title
> +The title number to play. Must be set if @option{pgc} and @option{pg} are
> not set.
> +Default is 0 (auto), which currently only selects the first available
> title (title 1)
> +and notifies the user about the implications.
> +
> +@item chapter_start
> +The chapter, or PTT (part-of-title), number to start at. Default is 1.
> +
> +@item chapter_end
> +The chapter, or PTT (part-of-title), number to end at. Default is 0,
> +which is a special value to signal end at the last possible chapter.
> +
> +@item angle
> +The video angle number, referring to what is essentially an additional
> +video stream that is composed from alternate frames interleaved in the
> VOBs.
> +Default is 1.
> +
> +@item region
> +The region code to use for playback. Some discs may use this to default
> playback
> +at a particular angle in different regions. This option will not affect
> the region code
> +of a real DVD drive, if used as an input. Default is 0, "world".
> +
> +@item pgc
> +The entry PGC to start playback, in conjunction with @option{pg}.
> +Alternative to setting @option{title}.
> +Chapter markers not supported at this time.
> +Default is 0, automatically resolve from value of @option{title}.
> +
> +@item pg
> +The entry PG to start playback, in conjunction with @option{pgc}.
> +Alternative to setting @option{title}.
> +Chapter markers not supported at this time.
> +Default is 0, automatically resolve from value of @option{title}.
> +
> +@item preindex
> +Enable this to have accurate chapter (PTT) markers and duration
> measurement,
> +which requires a slow second pass read in order to index the chapter
> +timestamps from NAV packets. This is non-ideal extra work for physical
> DVD drives.
> +Not compatible with @option{pgc} and @option{pg}.
> +Default is 0, false.
> +
> +@item trim
> +Skip padding cells (i.e. cells shorter than 1 second) from the beginning.
> +There exist many discs with filler segments at the beginning of the PGC,
> +often with junk data intended for controlling a real DVD player's
> +buffering speed and with no other material data value.
> +Default is 1, true.
> +
> +@item clut_rgb
> +Output subtitle palettes (CLUTs) as RGB, required for Matroska.
> +Disable to output the palette in its original YUV colorspace.
> +Default is 1, true.
> +
> +
> +@end table
> +
> +@subsection Examples
> +
> +@itemize
> +@item
> +Open title 3 from a given DVD structure:
> +@example
> +ffmpeg -f dvdvideo -title 3 -i <path to DVD> ...
> +@end example
> +
> +@item
> +Open chapters 3-6 from title 1 from a given DVD structure:
> +@example
> +ffmpeg -f dvdvideo -chapter_start 3 -chapter_end 6 -title 1 -i <path to
> DVD> ...
> +@end example
> +
> +@item
> +Open only chapters 5 from title 1 from a given DVD structure:
> +@example
> +ffmpeg -f dvdvideo -chapter_start 5 -chapter_end 5 -title 1 -i <path to
> DVD> ...
> +@end example
> +@end itemize
> +
> @section ea
>
> Electronic Arts Multimedia format demuxer.
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index 05b9b8a115..df69734877 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 b04b43cab3..3e905c23f8 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/dvdvideodec.c b/libavformat/dvdvideodec.c
> new file mode 100644
> index 0000000000..03cf2d1f2c
> --- /dev/null
> +++ b/libavformat/dvdvideodec.c
> @@ -0,0 +1,1409 @@
> +/*
> + * DVD-Video demuxer, powered by libdvdnav and libdvdread
> + *
> + * 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
> + */
> +
> +/*
> + * See doc/demuxers.texi for a high-level overview.
> + *
> + * The tactical approach is as follows:
> + * 1) Open the volume with dvdread
> + * 2) Analyze the user-requested title and PGC coordinates in the IFO
> structures
> + * 3) Request playback at the coordinates and chosen angle with dvdnav
> + * 5) Begin the playback (reading and demuxing) of MPEG-PS blocks
> + * 6) End playback if navigation goes backwards, to a menu, or a
> different PGC or angle
> + * 7) Close the dvdnav VM, and free dvdnav's IFO structures
> + */
> +
> +#include <dvdnav/dvdnav.h>
> +#include <dvdread/dvd_reader.h>
> +#include <dvdread/ifo_read.h>
> +#include <dvdread/ifo_types.h>
> +#include <dvdread/nav_read.h>
> +
> +#include "libavcodec/avcodec.h"
> +#include "libavutil/avstring.h"
> +#include "libavutil/avutil.h"
> +#include "libavutil/intreadwrite.h"
> +#include "libavutil/mem.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/samplefmt.h"
> +#include "libavutil/time.h"
> +#include "libavutil/timestamp.h"
> +
> +#include "avformat.h"
> +#include "avio_internal.h"
> +#include "avlanguage.h"
> +#include "demux.h"
> +#include "internal.h"
> +#include "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 64 /* VOBUs use
> 32 (PES allows 33) */
> +#define DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE 1024
> +
> +enum DVDVideoSubpictureViewport {
> + DVDVIDEO_SUBP_VIEWPORT_FULLSCREEN,
> + DVDVIDEO_SUBP_VIEWPORT_WIDESCREEN,
> + DVDVIDEO_SUBP_VIEWPORT_LETTERBOX,
> + DVDVIDEO_SUBP_VIEWPORT_PANSCAN
> +};
> +const char* VIEWPORT_LABELS[4] = { "Fullscreen", "Widescreen",
> "Letterbox", "Pan and Scan" };
> +
> +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;
> + int disposition;
> + char *lang_iso;
> +} DVDVideoPGCAudioStreamEntry;
> +
> +typedef struct DVDVideoPGCSubtitleStreamEntry {
> + int startcode;
> + int disposition;
> + char *lang_iso;
> + enum DVDVideoSubpictureViewport viewport;
> +} DVDVideoPGCSubtitleStreamEntry;
> +
> +typedef struct DVDVideoPlaybackState {
> + int celln; /* ID of the active
> cell */
> + int entry_pgn; /* ID of the PG we
> are starting in */
> + int in_pgc; /* if our navigator
> is in the PGC */
> + int in_ps; /* if our navigator
> is in the program stream */
> + int in_vts; /* if our navigator
> is in the VTS */
> + int64_t nav_pts; /* PTS according to
> IFO, not frame-accurate */
> + int nb_vobus_played; /* number of VOBUs
> played back so far */
> + uint64_t pgc_duration_est; /* estimated duration
> as reported by IFO */
> + uint64_t pgc_elapsed; /* the elapsed time
> of the PGC, cell-relative */
> + int pgc_nb_pg_est; /* number of PGs as
> reported by IFOs */
> + int pgcn; /* ID of the PGC we
> are playing */
> + int pgn; /* ID of the PG we
> are in now */
> + int ptt; /* ID of the chapter
> we are in now */
> + int64_t ts_offset; /* PTS discontinuity
> offset (ex. VOB change) */
> + uint32_t vobu_duration; /* duration of the
> current VOBU */
> + uint32_t vobu_e_ptm; /* end PTS of the
> current VOBU */
> + int vtsn; /* ID of the active
> VTS (video title set) */
> + uint64_t *pgc_pg_times_est; /* PG start times as
> reported by IFO */
> + pgc_t *pgc; /* handle to the
> active PGC */
> + dvdnav_t *dvdnav; /* handle to
> libdvdnav */
> +} DVDVideoPlaybackState;
> +
> +typedef struct DVDVideoDemuxContext {
> + const AVClass *class;
> +
> + /* options */
> + int opt_title; /* the user-provided
> title number (1-indexed) */
> + int opt_chapter_start; /* the user-provided
> entry PTT (1-indexed) */
> + int opt_chapter_end; /* the user-provided
> exit PTT (0 for last) */
> + 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 */
> + int opt_preindex; /* pre-indexing mode
> (2-pass read) */
> + int opt_trim; /* trim padding cells
> at beginning and end */
> +
> + /* 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) */
> +
> + /* playback control */
> + int play_end; /* signal EOF to the
> parent demuxer */
> + DVDVideoPlaybackState play_state; /* the active
> playback state */
> + int play_started; /* signal that
> playback has started */
> + int play_seg_started; /* signal that
> segment has started */
> + int64_t first_pts; /* the starting PTS
> offset of the PGC */
> + uint64_t duration_ptm; /* total duration in
> DVD MPEG timebase */
> +} DVDVideoDemuxContext;
> +
> +static void dvdvideo_libdvdread_log(void *opaque, dvd_logger_level_t
> level,
> + const char *msg, va_list msg_va)
> +{
> + AVFormatContext *s = opaque;
> + int lavu_level;
> +
> + char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
> + vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
> +
> + switch (level) {
> + case DVD_LOGGER_LEVEL_ERROR:
> + lavu_level = AV_LOG_ERROR;
> + break;
> + case DVD_LOGGER_LEVEL_WARN:
> + lavu_level = AV_LOG_WARNING;
> + break;
> + /* dvdread's info messages are relatively very verbose, muffle
> them as debug */
> + case DVD_LOGGER_LEVEL_INFO:
> + case DVD_LOGGER_LEVEL_DEBUG:
> + default:
> + lavu_level = AV_LOG_DEBUG;
> + }
> +
> + av_log(s, lavu_level, "libdvdread: %s\n", msg_buf);
> +}
> +
> +static void dvdvideo_libdvdnav_log(void *opaque, dvdnav_logger_level_t
> level,
> + const char *msg, va_list msg_va)
> +{
> + AVFormatContext *s = opaque;
> + int lavu_level;
> +
> + char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
> + vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
> +
> + switch (level) {
> + case DVDNAV_LOGGER_LEVEL_ERROR:
> + lavu_level = AV_LOG_ERROR;
> + break;
> + case DVDNAV_LOGGER_LEVEL_WARN:
> + /* some discs have invalid language codes set for their
> menus, muffle the noise */
> + lavu_level = av_strstart(msg, "Language", NULL) ?
> AV_LOG_DEBUG : AV_LOG_WARNING;
> + break;
> + /* dvdnav's info messages are relatively very verbose, muffle
> them as debug */
> + case DVDNAV_LOGGER_LEVEL_INFO:
> + case DVDNAV_LOGGER_LEVEL_DEBUG:
> + default:
> + lavu_level = AV_LOG_DEBUG;
> + }
> +
> + av_log(s, lavu_level, "libdvdnav: %s\n", msg_buf);
> +}
> +
> +static void dvdvideo_ifo_close(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + 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_ifo_open(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + dvd_logger_cb dvdread_log_cb;
> + title_info_t title_info;
> +
> + dvdread_log_cb = (dvd_logger_cb) { .pf_log = dvdvideo_libdvdread_log
> };
> + c->dvdread = DVDOpen2(s, &dvdread_log_cb, s->url);
> +
> + if (!c->dvdread) {
> + av_log(s, AV_LOG_ERROR, "Unable to open the DVD-Video
> structure\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + if (!(c->vmg_ifo = ifoOpen(c->dvdread, 0))) {
> + av_log(s, AV_LOG_ERROR, "Unable to open the VMG
> (VIDEO_TS.IFO)\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + if (c->opt_title > c->vmg_ifo->tt_srpt->nr_of_srpts) {
> + av_log(s, AV_LOG_ERROR, "Title %d not found\n", c->opt_title);
> +
> + 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 %d not found\n", c->opt_angle);
> +
> + return AVERROR_STREAM_NOT_FOUND;
> + }
> +
> + if (title_info.nr_of_ptts < 1) {
> + av_log(s, AV_LOG_ERROR, "Title %d has invalid headers in VMG\n",
> c->opt_title);
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + if (c->opt_chapter_start > title_info.nr_of_ptts
> + || (c->opt_chapter_end > 0 && c->opt_chapter_end >
> title_info.nr_of_ptts)) {
> + av_log(s, AV_LOG_ERROR, "Chapter (PTT) range [%d, %d] is
> invalid\n",
> + c->opt_chapter_start, c->opt_chapter_end);
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + if (!(c->vts_ifo = ifoOpen(c->dvdread, title_info.title_set_nr))) {
> + av_log(s, AV_LOG_ERROR, "Unable to process the VTS IFO
> structure\n");
> +
> + return AVERROR_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 %d has invalid headers in VTS\n",
> c->opt_title);
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + return 0;
> +}
> +
> +static void dvdvideo_play_close(AVFormatContext *s, DVDVideoPlaybackState
> *state)
> +{
> + if (!state->dvdnav)
> + return;
> +
> + /* not allocated by av_malloc() */
> + if (state->pgc_pg_times_est)
> + free(state->pgc_pg_times_est);
> +
> + if (dvdnav_close(state->dvdnav) < 0)
> + av_log(s, AV_LOG_ERROR, "Unable to cleanly close dvdnav\n");
> +}
> +
> +static int dvdvideo_play_open(AVFormatContext *s, DVDVideoPlaybackState
> *state)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret = 0;
> + dvdnav_logger_cb dvdnav_log_cb;
> + dvdnav_status_t dvdnav_open_status;
> + int cur_title, cur_pgcn, cur_pgn;
> + pgc_t *pgc;
> +
> + int32_t disc_region_mask;
> + int32_t player_region_mask;
> +
> + dvdnav_log_cb = (dvdnav_logger_cb) { .pf_log = dvdvideo_libdvdnav_log
> };
> + dvdnav_open_status = dvdnav_open2(&state->dvdnav, s, &dvdnav_log_cb,
> s->url);
> +
> + if (!state->dvdnav
> + || dvdnav_open_status != DVDNAV_STATUS_OK
> + || dvdnav_set_readahead_flag(state->dvdnav, 0) !=
> DVDNAV_STATUS_OK
> + || dvdnav_set_PGC_positioning_flag(state->dvdnav, 1) !=
> DVDNAV_STATUS_OK
> + || dvdnav_get_region_mask(state->dvdnav, &disc_region_mask)
> != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to open the DVD for playback\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + player_region_mask = c->opt_region > 0 ? (1 << (c->opt_region - 1)) :
> disc_region_mask;
> + if (dvdnav_set_region_mask(state->dvdnav, player_region_mask) !=
> DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to set the playback region code
> %d\n", c->opt_region);
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + if (c->opt_pgc > 0 && c->opt_pg > 0) {
> + if (dvdnav_program_play(state->dvdnav, c->opt_title,
> + c->opt_pgc, c->opt_pg) !=
> DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to start playback at title
> %d, PGC %d, PG %d\n",
> + c->opt_title, c->opt_pgc, c->opt_pg);
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + state->pgcn = c->opt_pgc;
> + state->entry_pgn = c->opt_pg;
> + } else {
> + if (dvdnav_part_play(state->dvdnav, c->opt_title,
> c->opt_chapter_start) != DVDNAV_STATUS_OK
> + || dvdnav_current_title_program(state->dvdnav, &cur_title,
> + &cur_pgcn, &cur_pgn) !=
> DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to start playback at title
> %d, chapter (PTT) %d\n",
> + c->opt_title, c->opt_chapter_start);
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + state->pgcn = cur_pgcn;
> + state->entry_pgn = cur_pgn;
> + }
> +
> + pgc = c->vts_ifo->vts_pgcit->pgci_srp[state->pgcn - 1].pgc;
> +
> + if (pgc->pg_playback_mode != 0) {
> + av_log(s, AV_LOG_ERROR, "Non-sequential PGCs, such as shuffles,
> are not supported\n");
> +
> + return AVERROR_PATCHWELCOME;
> + }
> +
> + if (dvdnav_angle_change(state->dvdnav, c->opt_angle) !=
> DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to start playback at the given
> angle\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + /* dvdnav_describe_title_chapters() performs several safety checks on
> the title structure */
> + /* take advantage of this side effect to ensure a safe navigation
> path */
> + state->pgc_nb_pg_est = dvdnav_describe_title_chapters(state->dvdnav,
> c->opt_title,
> +
> &state->pgc_pg_times_est,
> +
> &state->pgc_duration_est);
> + if (!state->pgc_nb_pg_est) {
> + av_log(s, AV_LOG_ERROR, "Unable to validate title structure\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + state->nav_pts = dvdnav_get_current_time(state->dvdnav);
> + state->vtsn = c->vmg_ifo->tt_srpt->title[c->opt_title -
> 1].title_set_nr;
> +
> + state->pgc = pgc;
> +
> + return ret;
> +}
> +
> +static int dvdvideo_play_is_cell_promising(AVFormatContext *s,
> DVDVideoPlaybackState *state,
> + int celln)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> + dvd_time_t cell_duration;
> +
> + if (!c->opt_trim)
> + return 1;
> +
> + cell_duration = state->pgc->cell_playback[celln - 1].playback_time;
> +
> + return cell_duration.second >= 1 || cell_duration.minute >= 1 ||
> cell_duration.hour >= 1;
> +}
> +
> +static int dvdvideo_play_next_ps_block(AVFormatContext *s,
> DVDVideoPlaybackState *state,
> + uint8_t *buf, int buf_size,
> + int *p_nav_event,
> + void (*flush_cb)(AVFormatContext
> *s))
> +{
> + 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;
> + int is_cell_promising = 0;
> + pci_t *e_pci;
> + dsi_t *e_dsi;
> +
> + 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(state->dvdnav, nav_buf, &nav_event,
> &nav_len) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to read next block of PGC\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + /* some discs follow NOPs with a premature stop event */
> + if (nav_event == DVDNAV_STOP)
> + return AVERROR_EOF;
> +
> + if (nav_len > DVDVIDEO_BLOCK_SIZE) {
> + av_log(s, AV_LOG_ERROR, "Invalid block size\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + if (dvdnav_current_title_info(state->dvdnav, &cur_title,
> + &cur_ptt) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to validate title
> coordinates\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + /* we somehow navigated to a menu */
> + if (cur_title == 0 || !dvdnav_is_domain_vts(state->dvdnav))
> + return AVERROR_EOF;
> +
> + if (dvdnav_current_title_program(state->dvdnav, &cur_title_unused,
> + &cur_pgcn, &cur_pgn) !=
> DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to validate PGC
> coordinates\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + /* we somehow left the PGC */
> + if (state->in_pgc && cur_pgcn != state->pgcn)
> + return AVERROR_EOF;
> +
> + if (dvdnav_get_angle_info(state->dvdnav, &cur_angle,
> &cur_nb_angles) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to validate angle
> coordinates\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + av_log(s, nav_event == DVDNAV_BLOCK_OK ? AV_LOG_TRACE :
> AV_LOG_DEBUG,
> + "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, state->celln,
> cur_pgcn, cur_pgn,
> + state->in_vts, state->in_pgc,
> state->in_ps);
> +
> + switch (nav_event) {
> + case DVDNAV_VTS_CHANGE:
> + if (state->in_vts)
> + return AVERROR_EOF;
> +
> + e_vts = (dvdnav_vts_change_event_t *) nav_buf;
> +
> + if (e_vts->new_vtsN == state->vtsn && e_vts->new_domain
> == DVD_DOMAIN_VTSTitle)
> + state->in_vts = 1;
> +
> + continue;
> + case DVDNAV_CELL_CHANGE:
> + if (!state->in_vts)
> + continue;
> +
> + e_cell = (dvdnav_cell_change_event_t *) nav_buf;
> + is_cell_promising = dvdvideo_play_is_cell_promising(s,
> state, e_cell->cellN);
> +
> + av_log(s, AV_LOG_DEBUG, "new cell: prev=%d new=%d
> promising=%d\n",
> + state->celln, e_cell->cellN,
> is_cell_promising);
> +
> + if (!state->in_ps && !state->in_pgc) {
> + if (cur_title == c->opt_title && cur_ptt ==
> c->opt_chapter_start
> + && cur_pgcn == state->pgcn && cur_pgn ==
> state->entry_pgn
> + && is_cell_promising) {
> + state->in_pgc = 1;
> + }
> +
> + if (c->opt_trim && !is_cell_promising)
> + av_log(s, AV_LOG_INFO, "Skipping padding cell
> #%d\n", e_cell->cellN);
> + } else if (state->celln >= e_cell->cellN || state->pgn >
> cur_pgn) {
> + return AVERROR_EOF;
> + }
> +
> + state->celln = e_cell->cellN;
> + state->ptt = cur_ptt;
> + state->pgn = cur_pgn;
> +
> + continue;
> + case DVDNAV_NAV_PACKET:
> + if (!state->in_pgc)
> + continue;
> +
> + if ((state->ptt > 0 && state->ptt > cur_ptt)
> + || (c->opt_chapter_end > 0 && cur_ptt >
> c->opt_chapter_end)) {
> + return AVERROR_EOF;
> + }
> +
> + e_pci = dvdnav_get_current_nav_pci(state->dvdnav);
> + e_dsi = dvdnav_get_current_nav_dsi(state->dvdnav);
> +
> + if (e_pci == NULL || e_dsi == NULL
> + || e_pci->pci_gi.vobu_s_ptm >
> e_pci->pci_gi.vobu_e_ptm) {
> + av_log(s, AV_LOG_ERROR, "Invalid NAV packet\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + state->vobu_duration = e_pci->pci_gi.vobu_e_ptm -
> e_pci->pci_gi.vobu_s_ptm;
> + state->pgc_elapsed += state->vobu_duration;
> + state->nav_pts = dvdnav_get_current_time(state->dvdnav);
> + state->ptt = cur_ptt;
> + state->pgn = cur_pgn;
> + state->nb_vobus_played++;
> +
> + av_log(s, AV_LOG_DEBUG, "NAV pack: s_ptm=%d e_ptm=%d "
> + "scr=%d lbn=%d vobu_duration=%d
> nav_pts=%ld\n",
> + e_pci->pci_gi.vobu_s_ptm,
> e_pci->pci_gi.vobu_e_ptm,
> + e_dsi->dsi_gi.nv_pck_scr,
> + e_pci->pci_gi.nv_pck_lbn,
> state->vobu_duration, state->nav_pts);
> +
> + if (!state->in_ps) {
> + av_log(s, AV_LOG_DEBUG, "navigation: locked to
> program stream\n");
> +
> + state->in_ps = 1;
> + } else {
> + if (state->vobu_e_ptm != e_pci->pci_gi.vobu_s_ptm) {
> + if (flush_cb)
> + flush_cb(s);
> +
> + state->ts_offset += state->vobu_e_ptm -
> e_pci->pci_gi.vobu_s_ptm;
> + }
> + }
> +
> + state->vobu_e_ptm = e_pci->pci_gi.vobu_e_ptm;
> +
> + (*p_nav_event) = nav_event;
> +
> + return nav_len;
> + case DVDNAV_BLOCK_OK:
> + if (!state->in_ps) {
> + if (state->in_pgc)
> + i = 0; /* necessary in case we are skpping junk
> cells at the beginning */
> + continue;
> + }
> +
> + if (nav_len != DVDVIDEO_BLOCK_SIZE) {
> + av_log(s, AV_LOG_ERROR, "Invalid block size\n");
> +
> + 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);
> +
> + /* in case NAV packet is missed */
> + state->ptt = cur_ptt;
> + state->pgn = cur_pgn;
> +
> + (*p_nav_event) = nav_event;
> +
> + return nav_len;
> + case DVDNAV_STILL_FRAME:
> + case DVDNAV_WAIT:
> + case DVDNAV_HOP_CHANNEL:
> + case DVDNAV_HIGHLIGHT:
> + if (state->in_ps)
> + return AVERROR_EOF;
> +
> + if (nav_event == DVDNAV_STILL_FRAME)
> + dvdnav_still_skip(state->dvdnav);
> + if (nav_event == DVDNAV_WAIT)
> + dvdnav_wait_skip(state->dvdnav);
> +
> + 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_INVALIDDATA;
> +}
> +
> +static int dvdvideo_pgc_preindex(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret = 0, interrupt = 0;
> + int nb_chapters = 0, last_ptt = c->opt_chapter_start;
> + uint64_t cur_chapter_offset = 0, cur_chapter_duration = 0;
> + DVDVideoPlaybackState *state;
> +
> + uint8_t nav_buf[DVDVIDEO_BLOCK_SIZE];
> + int nav_event;
> +
> + if (c->opt_chapter_start == c->opt_chapter_end)
> + return 0;
> +
> + av_log(s, AV_LOG_INFO,
> + "Indexing chapter markers, this will take a long time. Please
> wait...\n");
> +
> + state = av_mallocz(sizeof(DVDVideoPlaybackState));
> + if ((ret = dvdvideo_play_open(s, state)) < 0) {
> + av_freep(&state);
> + return ret;
> + }
> +
> + while (!(interrupt = ff_check_interrupt(&s->interrupt_callback))) {
> + ret = dvdvideo_play_next_ps_block(s, state, nav_buf,
> DVDVIDEO_BLOCK_SIZE,
> + &nav_event, NULL);
> + if (ret < 0 && ret != AVERROR_EOF)
> + goto end_free;
> +
> + if (nav_event != DVDNAV_NAV_PACKET && ret != AVERROR_EOF)
> + continue;
> +
> + if (state->ptt == last_ptt) {
> + cur_chapter_duration += state->vobu_duration;
> + /* ensure we add the last chapter */
> + if (ret != AVERROR_EOF)
> + continue;
> + }
> +
> + if (!avpriv_new_chapter(s, nb_chapters, DVDVIDEO_TIME_BASE_Q,
> cur_chapter_offset,
> + cur_chapter_offset +
> cur_chapter_duration, NULL)) {
> + av_log(s, AV_LOG_ERROR, "Unable to allocate chapter\n");
> +
> + goto end_free;
> + }
> +
> + nb_chapters++;
> + cur_chapter_offset += cur_chapter_duration;
> + cur_chapter_duration = state->vobu_duration;
> + last_ptt = state->ptt;
> +
> + if (ret == AVERROR_EOF)
> + break;
> + }
> +
> + if (interrupt) {
> + ret = AVERROR_EXIT;
> + goto end_free;
> + }
> +
> + if (ret < 0 && ret != AVERROR_EOF)
> + goto end_free;
> +
> + s->duration = av_rescale_q(state->pgc_elapsed, DVDVIDEO_TIME_BASE_Q,
> AV_TIME_BASE_Q);
> + c->duration_ptm = state->pgc_elapsed;
> +
> + av_log(s, AV_LOG_INFO, "Chapter marker indexing complete\n");
> + ret = 0;
> +
> +end_free:
> + dvdvideo_play_close(s, state);
> + av_freep(&state);
> +
> + return ret;
> +}
> +
> +static int dvdvideo_pgc_chapters_setup(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + uint64_t time_prev = 0;
> + int64_t total_duration = 0;
> +
> + int chapter_start = c->opt_chapter_start - 1;
> + int chapter_end = c->opt_chapter_end > 0 ? c->opt_chapter_end :
> c->play_state.pgc_nb_pg_est - 1;
> +
> + if (chapter_start == chapter_end || c->play_state.pgc_nb_pg_est == 1)
> {
> + s->duration = av_rescale_q(c->play_state.pgc_duration_est,
> + DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
> + return 0;
> + }
> +
> + for (int i = chapter_start; i < chapter_end; i++) {
> + uint64_t time_effective = c->play_state.pgc_pg_times_est[i] -
> c->play_state.nav_pts;
> +
> + 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;
> + total_duration = time_effective;
> + }
> +
> + if (c->opt_chapter_start == 1 && c->opt_chapter_end == 0)
> + s->duration = av_rescale_q(c->play_state.pgc_duration_est,
> + DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
> + else
> + s->duration = av_rescale_q(total_duration,
> + DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
> +
> + return 0;
> +}
> +
> +static int dvdvideo_video_stream_analyze(AVFormatContext *s, 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) {
> + av_log(s, AV_LOG_ERROR, "Invalid video dimensions\n");
> +
> + 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 = !is_pal && (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 = NULL;
> + int ret = 0;
> +
> + entry = av_mallocz(sizeof(DVDVideoVTSVideoStreamEntry));
> +
> + if ((ret = dvdvideo_video_stream_analyze(s,
> 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_HEADERS)) < 0) {
> + av_freep(&entry);
> + av_log(s, AV_LOG_ERROR, "Unable to allocate video stream\n");
> +
> + return ret;
> + }
> +
> + av_freep(&entry);
> +
> + return ret;
> +}
> +
> +static int dvdvideo_audio_stream_analyze(AVFormatContext *s, 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 = (AVChannelLayout) {0};
> + char lang_dvd[3] = {0};
> +
> + int position = (audio_control & 0x7F00) >> 8;
> +
> + /* XXX(PATCHWELCOME): SDDS is not supported due to lack of sample
> material */
> + switch (audio_attr.audio_format) {
> + case 0: /* AC3 */
> + codec_id = AV_CODEC_ID_AC3;
> + sample_fmt = AV_SAMPLE_FMT_FLTP;
> + sample_rate = 48000;
> + startcode = 0x80 + position;
> + break;
> + case 2: /* MP1 */
> + 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: /* MP2 */
> + 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: /* DVD PCM */
> + codec_id = AV_CODEC_ID_PCM_DVD;
> + 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: /* DCA */
> + 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) {
> + av_log(s, AV_LOG_ERROR, "Invalid audio parameters\n");
> +
> + 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 == 7)
> + ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_6POINT1;
> + else if (nb_channels == 8)
> + ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_7POINT1;
> +
> + if (audio_attr.code_extension == 2)
> + entry->disposition |= AV_DISPOSITION_VISUAL_IMPAIRED;
> + if (audio_attr.code_extension == 3 || audio_attr.code_extension == 4)
> + entry->disposition |= AV_DISPOSITION_COMMENT;
> +
> + 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;
> + st->disposition = entry->disposition;
> +
> + 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 = 0;
> +
> + for (int i = 0; i < c->vts_ifo->vtsi_mat->nr_of_vts_audio_streams;
> i++) {
> + DVDVideoPGCAudioStreamEntry *entry = NULL;
> +
> + if (!(c->play_state.pgc->audio_control[i] & 0x8000))
> + continue;
> +
> + entry = av_mallocz(sizeof(DVDVideoPGCAudioStreamEntry));
> + if (!entry)
> + return AVERROR(ENOMEM);
> +
> + if ((ret = dvdvideo_audio_stream_analyze(s,
> c->vts_ifo->vtsi_mat->vts_audio_attr[i],
> + c->play_state.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, "Karaoke metadata in stream %d will
> not be retained\n",
> + entry->startcode);
> +
> + /* IFO structures can declare duplicate entries for the same
> 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
> + || (ret = dvdvideo_audio_stream_add(s, entry,
> AVSTREAM_PARSE_HEADERS)) < 0)
> + goto break_free_and_error;
> +
> +continue_free:
> + av_freep(&entry);
> + continue;
> +
> +break_free_and_error:
> + av_freep(&entry);
> + av_log(s, AV_LOG_ERROR, "Unable to allocate audio stream\n");
> + return ret;
> + }
> +
> + return ret;
> +}
> +
> +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);
> +
> + if (subp_attr.lang_extension == 9)
> + entry->disposition |= AV_DISPOSITION_FORCED;
> +
> + 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;
> + int ret = 0;
> +
> + 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;
> +
> + if (entry->lang_iso)
> + av_dict_set(&st->metadata, "language", entry->lang_iso, 0);
> +
> + av_dict_set(&st->metadata, "VIEWPORT",
> VIEWPORT_LABELS[entry->viewport], 0);
> +
> + st->disposition = entry->disposition;
> +
> + 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 ret;
> +}
> +
> +static int dvdvideo_subp_stream_add_internal(AVFormatContext *s, uint32_t
> offset,
> + subp_attr_t subp_attr,
> + enum
> DVDVideoSubpictureViewport viewport)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + DVDVideoPGCSubtitleStreamEntry *entry = NULL;
> + int ret = 0;
> +
> + entry = av_mallocz(sizeof(DVDVideoPGCSubtitleStreamEntry));
> + entry->viewport = viewport;
> +
> + if ((ret = dvdvideo_subp_stream_analyze(s, offset, subp_attr, entry))
> < 0)
> + goto end_free_error;
> +
> + /* IFO structures can declare duplicate entries for the same
> startcode */
> + 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_HEADERS)) < 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_freep(&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 = 0;
> + uint32_t subp_control;
> + subp_attr_t subp_attr;
> + video_attr_t video_attr;
> +
> + subp_control = c->play_state.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,
> +
> DVDVIDEO_SUBP_VIEWPORT_FULLSCREEN)) < 0)
> + return ret;
> +
> + continue;
> + }
> +
> + /* 16:9 */
> + if ((ret = dvdvideo_subp_stream_add_internal(s, subp_control >>
> 16, subp_attr,
> +
> DVDVIDEO_SUBP_VIEWPORT_WIDESCREEN)) < 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,
> +
> DVDVIDEO_SUBP_VIEWPORT_LETTERBOX)) < 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,
> +
> DVDVIDEO_SUBP_VIEWPORT_PANSCAN)) < 0)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void dvdvideo_subdemux_flush(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + if (!c->play_seg_started)
> + return;
> +
> + av_log(s, AV_LOG_DEBUG, "flushing sub-demuxer\n");
> + avio_flush(&c->mpeg_pb.pub);
> + ff_read_frame_flush(c->mpeg_ctx);
> + c->play_seg_started = 0;
> +}
> +
> +static int dvdvideo_subdemux_read_data(void *opaque, uint8_t *buf, int
> buf_size)
> +{
> + AVFormatContext *s = opaque;
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret = 0;
> + int nav_event;
> +
> + if (c->play_end)
> + return AVERROR_EOF;
> +
> + ret = dvdvideo_play_next_ps_block(opaque, &c->play_state, buf,
> buf_size,
> + &nav_event,
> dvdvideo_subdemux_flush);
> +
> + if (ret == AVERROR_EOF) {
> + c->mpeg_pb.pub.eof_reached = 1;
> + c->play_end = 1;
> +
> + return AVERROR_EOF;
> + }
> +
> + if (ret >= 0 && nav_event == DVDNAV_NAV_PACKET)
> + return FFERROR_REDO;
> +
> + return ret;
> +}
> +
> +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 = 0;
> +
> + if (!(c->mpeg_fmt = av_find_input_format("mpeg")))
> + return AVERROR_DEMUXER_NOT_FOUND;
> +
> + if (!(c->mpeg_ctx = avformat_alloc_context()))
> + return AVERROR(ENOMEM);
> +
> + if (!(c->mpeg_buf = av_mallocz(DVDVIDEO_BLOCK_SIZE))) {
> + avformat_free_context(c->mpeg_ctx);
> +
> + return AVERROR(ENOMEM);
> + }
> +
> + ffio_init_context(&c->mpeg_pb, c->mpeg_buf, DVDVIDEO_BLOCK_SIZE, 0, s,
> + dvdvideo_subdemux_read_data, NULL, NULL);
> + c->mpeg_pb.pub.seekable = 0;
> +
> + if ((ret = ff_copy_whiteblacklists(c->mpeg_ctx, s)) < 0) {
> + avformat_free_context(c->mpeg_ctx);
> +
> + return ret;
> + }
> +
> + c->mpeg_ctx->flags = AVFMT_FLAG_CUSTOM_IO | AVFMT_FLAG_GENPTS;
> + c->mpeg_ctx->ctx_flags |= AVFMTCTX_UNSEEKABLE;
> + c->mpeg_ctx->probesize = 0;
> + c->mpeg_ctx->max_analyze_duration = 0;
> + c->mpeg_ctx->interrupt_callback = s->interrupt_callback;
> + c->mpeg_ctx->pb = &c->mpeg_pb.pub;
> + c->mpeg_ctx->io_open = NULL;
> +
> + if ((ret = avformat_open_input(&c->mpeg_ctx, "", c->mpeg_fmt, NULL))
> < 0) {
> + avformat_free_context(c->mpeg_ctx);
> +
> + return ret;
> + }
> +
> + return ret;
> +}
> +
> +static int dvdvideo_read_header(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret = 0;
> +
> + if (c->opt_title == 0) {
> + av_log(s, AV_LOG_INFO, "Defaulting to title #1. "
> + "This is not always the main feature,
> validation suggested.\n");
> +
> + c->opt_title = 1;
> + }
> +
> + if (c->opt_pgc) {
> + if (c->opt_pg == 0)
> + av_log(s, AV_LOG_ERROR, "Invalid coordinates. If -pgc is set,
> -pg must be set too.\n");
> + else if (c->opt_chapter_start > 1 || c->opt_chapter_end > 0 ||
> c->opt_preindex)
> + av_log(s, AV_LOG_ERROR, "-pgc is not compatible with
> -preindex, -chapter_start/end\n");
> +
> + return AVERROR(EINVAL);
> + }
> +
> + if ((ret = dvdvideo_ifo_open(s)) < 0)
> + return ret;
> +
> + if (c->opt_preindex && (ret = dvdvideo_pgc_preindex(s)) < 0)
> + return ret;
> +
> + if ((ret = dvdvideo_play_open(s, &c->play_state)) < 0
> + || (ret = dvdvideo_subdemux_open(s)) < 0
> + || (ret = dvdvideo_video_stream_setup(s)) < 0
> + || (ret = dvdvideo_audio_stream_add_all(s)) < 0
> + || (ret = dvdvideo_subp_stream_add_all(s)) < 0)
> + return ret;
> +
> + if (!c->opt_preindex)
> + return dvdvideo_pgc_chapters_setup(s);
> +
> + return ret;
> +}
> +
> +static int dvdvideo_read_packet(AVFormatContext *s, AVPacket *pkt)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret;
> + enum AVMediaType st_type;
> + int64_t cur_delta = 0;
> +
> + if (c->play_end)
> + return AVERROR_EOF;
> +
> + ret = av_read_frame(c->mpeg_ctx, pkt);
> +
> + if (ret < 0)
> + return ret;
> +
> + if (!c->play_seg_started)
> + c->play_seg_started = 1;
> +
> + if (pkt->stream_index >= s->nb_streams) {
> + av_log(s, AV_LOG_DEBUG, "discarding frame with unknown stream\n");
> + goto skip_redo;
> + }
> +
> + st_type =
> c->mpeg_ctx->streams[pkt->stream_index]->codecpar->codec_type;
> +
> + if (!c->play_started) {
> + if (st_type != AVMEDIA_TYPE_VIDEO || pkt->pts == AV_NOPTS_VALUE
> + || pkt->dts == AV_NOPTS_VALUE
> + || !(pkt->flags &
> AV_PKT_FLAG_KEY)) {
> + av_log(s, AV_LOG_DEBUG, "discarding non-video-keyframe at
> start\n");
> + goto skip_redo;
> + }
> +
> + c->first_pts = pkt->pts;
> + c->play_started = 1;
> +
> + pkt->pts = 0;
> + pkt->dts = c->play_state.ts_offset - pkt->pts;
> + } else {
> + cur_delta = c->play_state.ts_offset - c->first_pts;
> +
> + if (c->play_state.nb_vobus_played == 1 && (pkt->pts ==
> AV_NOPTS_VALUE
> + || pkt->dts ==
> AV_NOPTS_VALUE
> + || (pkt->pts + cur_delta)
> < 0)) {
> + av_log(s, AV_LOG_DEBUG, "discarding frame with negative or
> unset timestamp "
> + "(this is OK at the start of the
> first playback VOBU)\n");
> + goto skip_redo;
> + }
> +
> + if (pkt->pts != AV_NOPTS_VALUE)
> + pkt->pts += cur_delta;
> + if (pkt->dts != AV_NOPTS_VALUE)
> + pkt->dts += cur_delta;
> + }
> +
> + av_log(s, AV_LOG_TRACE, "st=%d pts=%ld dts=%ld ts_offset=%ld
> first_pts=%ld\n",
> + pkt->stream_index, pkt->pts, pkt->dts,
> + c->play_state.ts_offset, c->first_pts);
> + if (pkt->pts < 0)
> + av_log(s, AV_LOG_WARNING, "Invalid frame PTS @ st=%d pts=%ld
> dts=%ld\n",
> + pkt->stream_index, pkt->pts, pkt->dts);
> +
> + return c->play_end ? AVERROR_EOF : 0;
> +
> +skip_redo:
> + av_packet_unref(pkt);
> + return FFERROR_REDO;
> +}
> +
> +static int dvdvideo_close(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + dvdvideo_subdemux_close(s);
> + dvdvideo_play_close(s, &c->play_state);
> + dvdvideo_ifo_close(s);
> +
> + return 0;
> +}
> +
> +#define OFFSET(x) offsetof(DVDVideoDemuxContext, x)
> +static const AVOption dvdvideo_options[] = {
> + {"angle", "playback angle number",
> OFFSET(opt_angle), AV_OPT_TYPE_INT, { .i64=1 },
> 1, 9, AV_OPT_FLAG_DECODING_PARAM },
> + {"chapter_end", "exit chapter (PTT) number",
> OFFSET(opt_chapter_end), AV_OPT_TYPE_INT, { .i64=0 },
> 0, 99, AV_OPT_FLAG_DECODING_PARAM },
> + {"chapter_start", "entry chapter (PTT) number",
> OFFSET(opt_chapter_start), AV_OPT_TYPE_INT, { .i64=1 },
> 1, 99, 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 },
> + {"pgc", "entry PGC number (0=auto)",
> OFFSET(opt_pgc), AV_OPT_TYPE_INT, { .i64=0 },
> 0, 999, AV_OPT_FLAG_DECODING_PARAM },
> + {"preindex", "enable for accurate chapter markers, slow
> (2-pass read)", OFFSET(opt_preindex), AV_OPT_TYPE_BOOL, { .i64=0
> }, 0, 1, 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 },
> + {"title", "title number",
> OFFSET(opt_title), AV_OPT_TYPE_INT, { .i64=0 },
> 0, 99, AV_OPT_FLAG_DECODING_PARAM },
> + {"trim", "trim padding cells from start",
> OFFSET(opt_trim), AV_OPT_TYPE_BOOL, { .i64=1 },
> 0, 1, 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_close = dvdvideo_close,
> + .read_header = dvdvideo_read_header,
> + .read_packet = dvdvideo_read_packet
> +};
> --
> 2.34.1
>
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* [FFmpeg-devel] [PATCH v7 1/2] libavformat/dvdvideo: add DVD-Video demuxer powered by libdvdnav and libdvdread
2024-02-05 0:02 ` [FFmpeg-devel] [PATCH v6] libavformat/dvdvideo: add DVD-Video demuxer " Marth64
2024-02-05 0:09 ` Marth64
@ 2024-02-05 5:48 ` Marth64
2024-02-05 5:48 ` [FFmpeg-devel] [PATCH v7 2/2] libavformat/dvdvideo: add DVD CLUT utilities and enable palette support Marth64
` (2 more replies)
1 sibling, 3 replies; 27+ messages in thread
From: Marth64 @ 2024-02-05 5:48 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Marth64
Sorry for the quick follow-up to v6, I wanted to get this update out now hopefully
before v6 is actually reviewed. v7 fixes a few documentation typos, options descriptions,
and log messages: nothing major. I also flattened this out to be a 2-patch set
with subtitle palette support.
This is the result of >1yr of research and I am now very satisfied with it's result.
I will not make any more changes at this point unless it is outcome from a review.
If the community decides to merge it, I am happy to continue to improve it with support for
seeking, probing, and tests.
Thank you so much,
Signed-off-by: Marth64 <marth64@proxyid.net>
---
Changelog | 2 +
configure | 8 +
doc/demuxers.texi | 129 ++++
libavformat/Makefile | 1 +
libavformat/allformats.c | 1 +
libavformat/dvdvideodec.c | 1410 +++++++++++++++++++++++++++++++++++++
6 files changed, 1551 insertions(+)
create mode 100644 libavformat/dvdvideodec.c
diff --git a/Changelog b/Changelog
index c5fb21d198..88653bc6d3 100644
--- a/Changelog
+++ b/Changelog
@@ -24,6 +24,8 @@ version <next>:
- ffmpeg CLI options may now be used as -/opt <path>, which is equivalent
to -opt <contents of file <path>>
- showinfo bitstream filter
+- DVD-Video demuxer, powered by libdvdnav and libdvdread
+
version 6.1:
- libaribcaption decoder
diff --git a/configure b/configure
index 68f675a4bc..70c33ec96d 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
@@ -3520,6 +3524,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"
@@ -6761,6 +6767,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/doc/demuxers.texi b/doc/demuxers.texi
index e4c5b560a6..ad0906f6ec 100644
--- a/doc/demuxers.texi
+++ b/doc/demuxers.texi
@@ -285,6 +285,135 @@ This demuxer accepts the following option:
@end table
+@section dvdvideo
+
+DVD-Video demuxer, powered by libdvdnav and libdvdread.
+
+Can directly ingest DVD titles, specifically sequential PGCs,
+into a conversion pipeline. Menus and seeking are not supported at this time.
+
+Block devices (DVD drives), ISO files, and directory structures are accepted.
+Activate with @code{-f dvdvideo} in front of one of these inputs.
+
+Underlying playback is fully handled by libdvdnav, and structure parsing by libdvdread.
+ffmpeg must be built with GPL library support available as well as the switches
+@code{--enable-libdvdnav} and @code{--enable-libdvdread}.
+
+You will need to provide either the desired "title number" or exact PGC/PG coordinates.
+Many open-source DVD players and tools can aid in providing this information.
+If not specified, the demuxer will default to title 1 which works for many discs.
+However, due to the flexibility of DVD-Video, it is recommended to check manually.
+There are many discs that are authored strangely or with invalid headers.
+
+If the input is a real DVD drive, please note that there are some drives which may
+silently fail on reading bad sectors from the disc, returning random bits instead
+which is effectively corrupt data. This is especially prominent on aging or rotting discs.
+A second pass and integrity checks would be needed to detect the corruption.
+This is not an ffmpeg issue.
+
+@subsection Background
+
+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 MPEG-PS streams that are stored in headerless VOB files.
+To the end-user, these streams are known simply as "titles", but the actual
+logical playback sequence is defined by one or more "PGCs", or Program Group Chains,
+within the title. The PGC is in turn comprised of multiple "PGs", or Programs",
+which are the actual video segments which, for a typical movie, are sequentially
+ordered. The PGC structure, along with stream layout and metadata, are stored in
+IFO files that need to be parsed. PGCs can be thought of as playlists in easier terms.
+
+An actual DVD player relies on user GUI interaction via menus and an internal VM
+to drive the direction of demuxing. Generally, the user would either navigate (via menus)
+or automatically be redirected to the PGC of their choice. During this process and
+the subsequent playback, the DVD player's internal VM also maintains a state and
+executes instructions that can create jumps to different sectors during playback.
+This is why libdvdnav is involved, as a linear read of the MPEG-PS blobs on the
+disc (VOBs) is not enough to produce the right sequence in many cases.
+
+There are many other DVD structures (a long subject) that will not be discussed here.
+NAV packets, in particular, are handled by this demuxer to build accurate timing
+but not emitted as a stream. For a good high-level understanding, refer to:
+@url{https://code.videolan.org/videolan/libdvdnav/-/blob/master/doc/dvd_structures}
+
+@subsection Options
+
+This demuxer accepts the following options:
+
+@table @option
+
+@item title
+The title number to play. Must be set if @option{pgc} and @option{pg} are not set.
+Default is 0 (auto), which currently only selects the first available title (title 1)
+and notifies the user about the implications.
+
+@item chapter_start
+The chapter, or PTT (part-of-title), number to start at. Default is 1.
+
+@item chapter_end
+The chapter, or PTT (part-of-title), number to end at. Default is 0,
+which is a special value to signal end at the last possible chapter.
+
+@item angle
+The video angle number, referring to what is essentially an additional
+video stream that is composed from alternate frames interleaved in the VOBs.
+Default is 1.
+
+@item region
+The region code to use for playback. Some discs may use this to default playback
+at a particular angle in different regions. This option will not affect the region code
+of a real DVD drive, if used as an input. Default is 0, "world".
+
+@item pgc
+The entry PGC to start playback, in conjunction with @option{pg}.
+Alternative to setting @option{title}.
+Chapter markers not supported at this time.
+Default is 0, automatically resolve from value of @option{title}.
+
+@item pg
+The entry PG to start playback, in conjunction with @option{pgc}.
+Alternative to setting @option{title}.
+Chapter markers not supported at this time.
+Default is 0, automatically resolve from value of @option{title}.
+
+@item preindex
+Enable this to have accurate chapter (PTT) markers and duration measurement,
+which requires a slow second pass read in order to index the chapter
+timestamps from NAV packets. This is non-ideal extra work for physical DVD drives.
+Not compatible with @option{pgc} and @option{pg}.
+Default is 0, false.
+
+@item trim
+Skip padding cells (i.e. cells shorter than 1 second) from the beginning.
+There exist many discs with filler segments at the beginning of the PGC,
+often with junk data intended for controlling a real DVD player's
+buffering speed and with no other material data value.
+Default is 1, true.
+
+@end table
+
+@subsection Examples
+
+@itemize
+@item
+Open title 3 from a given DVD structure:
+@example
+ffmpeg -f dvdvideo -title 3 -i <path to DVD> ...
+@end example
+
+@item
+Open chapters 3-6 from title 1 from a given DVD structure:
+@example
+ffmpeg -f dvdvideo -chapter_start 3 -chapter_end 6 -title 1 -i <path to DVD> ...
+@end example
+
+@item
+Open only chapter 5 from title 1 from a given DVD structure:
+@example
+ffmpeg -f dvdvideo -chapter_start 5 -chapter_end 5 -title 1 -i <path to DVD> ...
+@end example
+@end itemize
+
@section ea
Electronic Arts Multimedia format demuxer.
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 05b9b8a115..df69734877 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 b04b43cab3..3e905c23f8 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/dvdvideodec.c b/libavformat/dvdvideodec.c
new file mode 100644
index 0000000000..2e363a4a22
--- /dev/null
+++ b/libavformat/dvdvideodec.c
@@ -0,0 +1,1410 @@
+/*
+ * DVD-Video demuxer, powered by libdvdnav and libdvdread
+ *
+ * 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
+ */
+
+/*
+ * See doc/demuxers.texi for a high-level overview.
+ *
+ * The tactical approach is as follows:
+ * 1) Open the volume with dvdread
+ * 2) Analyze the user-requested title and PGC coordinates in the IFO structures
+ * 3) Request playback at the coordinates and chosen angle with dvdnav
+ * 5) Begin the playback (reading and demuxing) of MPEG-PS blocks
+ * 6) End playback if navigation goes backwards, to a menu, or a different PGC or angle
+ * 7) Close the dvdnav VM, and free dvdread's IFO structures
+ */
+
+#include <dvdnav/dvdnav.h>
+#include <dvdread/dvd_reader.h>
+#include <dvdread/ifo_read.h>
+#include <dvdread/ifo_types.h>
+#include <dvdread/nav_read.h>
+
+#include "libavcodec/avcodec.h"
+#include "libavutil/avstring.h"
+#include "libavutil/avutil.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+#include "libavutil/samplefmt.h"
+#include "libavutil/time.h"
+#include "libavutil/timestamp.h"
+
+#include "avformat.h"
+#include "avio_internal.h"
+#include "avlanguage.h"
+#include "demux.h"
+#include "internal.h"
+#include "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 64 /* VOBUs use 32 (PES allows 33) */
+#define DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE 1024
+
+enum DVDVideoSubpictureViewport {
+ DVDVIDEO_SUBP_VIEWPORT_FULLSCREEN,
+ DVDVIDEO_SUBP_VIEWPORT_WIDESCREEN,
+ DVDVIDEO_SUBP_VIEWPORT_LETTERBOX,
+ DVDVIDEO_SUBP_VIEWPORT_PANSCAN
+};
+const char* VIEWPORT_LABELS[4] = { "Fullscreen", "Widescreen", "Letterbox", "Pan and Scan" };
+
+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;
+ int disposition;
+ char *lang_iso;
+} DVDVideoPGCAudioStreamEntry;
+
+typedef struct DVDVideoPGCSubtitleStreamEntry {
+ int startcode;
+ int disposition;
+ char *lang_iso;
+ enum DVDVideoSubpictureViewport viewport;
+} DVDVideoPGCSubtitleStreamEntry;
+
+typedef struct DVDVideoPlaybackState {
+ int celln; /* ID of the active cell */
+ int entry_pgn; /* ID of the PG we are starting in */
+ int in_pgc; /* if our navigator is in the PGC */
+ int in_ps; /* if our navigator is in the program stream */
+ int in_vts; /* if our navigator is in the VTS */
+ int64_t nav_pts; /* PTS according to IFO, not frame-accurate */
+ int nb_vobus_played; /* number of VOBUs played back so far */
+ uint64_t pgc_duration_est; /* estimated duration as reported by IFO */
+ uint64_t pgc_elapsed; /* the elapsed time of the PGC, cell-relative */
+ int pgc_nb_pg_est; /* number of PGs as reported by IFOs */
+ int pgcn; /* ID of the PGC we are playing */
+ int pgn; /* ID of the PG we are in now */
+ int ptt; /* ID of the chapter we are in now */
+ int64_t ts_offset; /* PTS discontinuity offset (ex. VOB change) */
+ uint32_t vobu_duration; /* duration of the current VOBU */
+ uint32_t vobu_e_ptm; /* end PTS of the current VOBU */
+ int vtsn; /* ID of the active VTS (video title set) */
+ uint64_t *pgc_pg_times_est; /* PG start times as reported by IFO */
+ pgc_t *pgc; /* handle to the active PGC */
+ dvdnav_t *dvdnav; /* handle to libdvdnav */
+} DVDVideoPlaybackState;
+
+typedef struct DVDVideoDemuxContext {
+ const AVClass *class;
+
+ /* options */
+ int opt_title; /* the user-provided title number (1-indexed) */
+ int opt_chapter_start; /* the user-provided entry PTT (1-indexed) */
+ int opt_chapter_end; /* the user-provided exit PTT (0 for last) */
+ 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 */
+ int opt_preindex; /* pre-indexing mode (2-pass read) */
+ int opt_trim; /* trim padding cells at beginning and end */
+
+ /* 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) */
+
+ /* playback control */
+ int play_end; /* signal EOF to the parent demuxer */
+ DVDVideoPlaybackState play_state; /* the active playback state */
+ int play_started; /* signal that playback has started */
+ int play_seg_started; /* signal that segment has started */
+ int64_t first_pts; /* the starting PTS offset of the PGC */
+ uint64_t duration_ptm; /* total duration in DVD MPEG timebase */
+} DVDVideoDemuxContext;
+
+static void dvdvideo_libdvdread_log(void *opaque, dvd_logger_level_t level,
+ const char *msg, va_list msg_va)
+{
+ AVFormatContext *s = opaque;
+ int lavu_level;
+
+ char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
+ vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
+
+ switch (level) {
+ case DVD_LOGGER_LEVEL_ERROR:
+ lavu_level = AV_LOG_ERROR;
+ break;
+ case DVD_LOGGER_LEVEL_WARN:
+ lavu_level = AV_LOG_WARNING;
+ break;
+ /* dvdread's info messages are relatively very verbose, muffle them as debug */
+ case DVD_LOGGER_LEVEL_INFO:
+ case DVD_LOGGER_LEVEL_DEBUG:
+ default:
+ lavu_level = AV_LOG_DEBUG;
+ }
+
+ av_log(s, lavu_level, "libdvdread: %s\n", msg_buf);
+}
+
+static void dvdvideo_libdvdnav_log(void *opaque, dvdnav_logger_level_t level,
+ const char *msg, va_list msg_va)
+{
+ AVFormatContext *s = opaque;
+ int lavu_level;
+
+ char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
+ vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
+
+ switch (level) {
+ case DVDNAV_LOGGER_LEVEL_ERROR:
+ lavu_level = AV_LOG_ERROR;
+ break;
+ case DVDNAV_LOGGER_LEVEL_WARN:
+ /* some discs have invalid language codes set for their menus, muffle the noise */
+ lavu_level = av_strstart(msg, "Language", NULL) ? AV_LOG_DEBUG : AV_LOG_WARNING;
+ break;
+ /* dvdnav's info messages are relatively very verbose, muffle them as debug */
+ case DVDNAV_LOGGER_LEVEL_INFO:
+ case DVDNAV_LOGGER_LEVEL_DEBUG:
+ default:
+ lavu_level = AV_LOG_DEBUG;
+ }
+
+ av_log(s, lavu_level, "libdvdnav: %s\n", msg_buf);
+}
+
+static void dvdvideo_ifo_close(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ 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_ifo_open(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ dvd_logger_cb dvdread_log_cb;
+ title_info_t title_info;
+
+ dvdread_log_cb = (dvd_logger_cb) { .pf_log = dvdvideo_libdvdread_log };
+ c->dvdread = DVDOpen2(s, &dvdread_log_cb, s->url);
+
+ if (!c->dvdread) {
+ av_log(s, AV_LOG_ERROR, "Unable to open the DVD-Video structure\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ if (!(c->vmg_ifo = ifoOpen(c->dvdread, 0))) {
+ av_log(s, AV_LOG_ERROR, "Unable to open the VMG (VIDEO_TS.IFO)\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ if (c->opt_title > c->vmg_ifo->tt_srpt->nr_of_srpts) {
+ av_log(s, AV_LOG_ERROR, "Title %d not found\n", c->opt_title);
+
+ 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 %d not found\n", c->opt_angle);
+
+ return AVERROR_STREAM_NOT_FOUND;
+ }
+
+ if (title_info.nr_of_ptts < 1) {
+ av_log(s, AV_LOG_ERROR, "Title %d has invalid headers (no parts)\n", c->opt_title);
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (c->opt_chapter_start > title_info.nr_of_ptts
+ || (c->opt_chapter_end > 0 && c->opt_chapter_end > title_info.nr_of_ptts)) {
+ av_log(s, AV_LOG_ERROR, "Chapter (PTT) range [%d, %d] is invalid\n",
+ c->opt_chapter_start, c->opt_chapter_end);
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (!(c->vts_ifo = ifoOpen(c->dvdread, title_info.title_set_nr))) {
+ av_log(s, AV_LOG_ERROR, "Unable to process the VTS IFO structure\n");
+
+ return AVERROR_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 %d has invalid headers in VTS\n", c->opt_title);
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ return 0;
+}
+
+static void dvdvideo_play_close(AVFormatContext *s, DVDVideoPlaybackState *state)
+{
+ if (!state->dvdnav)
+ return;
+
+ /* not allocated by av_malloc() */
+ if (state->pgc_pg_times_est)
+ free(state->pgc_pg_times_est);
+
+ if (dvdnav_close(state->dvdnav) < 0)
+ av_log(s, AV_LOG_ERROR, "Unable to cleanly close dvdnav\n");
+}
+
+static int dvdvideo_play_open(AVFormatContext *s, DVDVideoPlaybackState *state)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret = 0;
+ dvdnav_logger_cb dvdnav_log_cb;
+ dvdnav_status_t dvdnav_open_status;
+ int cur_title, cur_pgcn, cur_pgn;
+ pgc_t *pgc;
+
+ int32_t disc_region_mask;
+ int32_t player_region_mask;
+
+ dvdnav_log_cb = (dvdnav_logger_cb) { .pf_log = dvdvideo_libdvdnav_log };
+ dvdnav_open_status = dvdnav_open2(&state->dvdnav, s, &dvdnav_log_cb, s->url);
+
+ if (!state->dvdnav
+ || dvdnav_open_status != DVDNAV_STATUS_OK
+ || dvdnav_set_readahead_flag(state->dvdnav, 0) != DVDNAV_STATUS_OK
+ || dvdnav_set_PGC_positioning_flag(state->dvdnav, 1) != DVDNAV_STATUS_OK
+ || dvdnav_get_region_mask(state->dvdnav, &disc_region_mask) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to open the DVD for playback\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ player_region_mask = c->opt_region > 0 ? (1 << (c->opt_region - 1)) : disc_region_mask;
+ if (dvdnav_set_region_mask(state->dvdnav, player_region_mask) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to set the playback region code %d\n", c->opt_region);
+
+ return AVERROR_EXTERNAL;
+ }
+
+ if (c->opt_pgc > 0 && c->opt_pg > 0) {
+ if (dvdnav_program_play(state->dvdnav, c->opt_title,
+ c->opt_pgc, c->opt_pg) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to start playback at title %d, PGC %d, PG %d\n",
+ c->opt_title, c->opt_pgc, c->opt_pg);
+
+ return AVERROR_EXTERNAL;
+ }
+
+ state->pgcn = c->opt_pgc;
+ state->entry_pgn = c->opt_pg;
+ } else {
+ if (dvdnav_part_play(state->dvdnav, c->opt_title, c->opt_chapter_start) != DVDNAV_STATUS_OK
+ || dvdnav_current_title_program(state->dvdnav, &cur_title,
+ &cur_pgcn, &cur_pgn) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to start playback at title %d, chapter (PTT) %d\n",
+ c->opt_title, c->opt_chapter_start);
+
+ return AVERROR_EXTERNAL;
+ }
+
+ state->pgcn = cur_pgcn;
+ state->entry_pgn = cur_pgn;
+ }
+
+ pgc = c->vts_ifo->vts_pgcit->pgci_srp[state->pgcn - 1].pgc;
+
+ if (pgc->pg_playback_mode != 0) {
+ av_log(s, AV_LOG_ERROR, "Non-sequential PGCs, such as shuffles, are not supported\n");
+
+ return AVERROR_PATCHWELCOME;
+ }
+
+ if (dvdnav_angle_change(state->dvdnav, c->opt_angle) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to start playback at the given angle\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ /* dvdnav_describe_title_chapters() performs several safety checks on the title structure */
+ /* take advantage of this side effect to ensure a safe navigation path */
+ state->pgc_nb_pg_est = dvdnav_describe_title_chapters(state->dvdnav, c->opt_title,
+ &state->pgc_pg_times_est,
+ &state->pgc_duration_est);
+ if (!state->pgc_nb_pg_est) {
+ av_log(s, AV_LOG_ERROR, "Unable to validate title structure\n");
+
+ return AVERROR_EXTERNAL;
+ }
+
+ state->nav_pts = dvdnav_get_current_time(state->dvdnav);
+ state->vtsn = c->vmg_ifo->tt_srpt->title[c->opt_title - 1].title_set_nr;
+
+ state->pgc = pgc;
+
+ return ret;
+}
+
+static int dvdvideo_play_is_cell_promising(AVFormatContext *s, DVDVideoPlaybackState *state,
+ int celln)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+ dvd_time_t cell_duration;
+
+ if (!c->opt_trim)
+ return 1;
+
+ cell_duration = state->pgc->cell_playback[celln - 1].playback_time;
+
+ return cell_duration.second >= 1 || cell_duration.minute >= 1 || cell_duration.hour >= 1;
+}
+
+static int dvdvideo_play_next_ps_block(AVFormatContext *s, DVDVideoPlaybackState *state,
+ uint8_t *buf, int buf_size,
+ int *p_nav_event,
+ void (*flush_cb)(AVFormatContext *s))
+{
+ 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;
+ int is_cell_promising = 0;
+ pci_t *e_pci;
+ dsi_t *e_dsi;
+
+ 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(state->dvdnav, nav_buf, &nav_event, &nav_len) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to read next block of PGC\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ /* some discs follow NOPs with a premature stop event */
+ if (nav_event == DVDNAV_STOP)
+ return AVERROR_EOF;
+
+ if (nav_len > DVDVIDEO_BLOCK_SIZE) {
+ av_log(s, AV_LOG_ERROR, "Invalid block size\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (dvdnav_current_title_info(state->dvdnav, &cur_title,
+ &cur_ptt) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to validate title coordinates\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ /* we somehow navigated to a menu */
+ if (cur_title == 0 || !dvdnav_is_domain_vts(state->dvdnav))
+ return AVERROR_EOF;
+
+ if (dvdnav_current_title_program(state->dvdnav, &cur_title_unused,
+ &cur_pgcn, &cur_pgn) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to validate PGC coordinates\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ /* we somehow left the PGC */
+ if (state->in_pgc && cur_pgcn != state->pgcn)
+ return AVERROR_EOF;
+
+ if (dvdnav_get_angle_info(state->dvdnav, &cur_angle, &cur_nb_angles) != DVDNAV_STATUS_OK) {
+ av_log(s, AV_LOG_ERROR, "Unable to validate angle coordinates\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ av_log(s, nav_event == DVDNAV_BLOCK_OK ? AV_LOG_TRACE : AV_LOG_DEBUG,
+ "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, state->celln, cur_pgcn, cur_pgn,
+ state->in_vts, state->in_pgc, state->in_ps);
+
+ switch (nav_event) {
+ case DVDNAV_VTS_CHANGE:
+ if (state->in_vts)
+ return AVERROR_EOF;
+
+ e_vts = (dvdnav_vts_change_event_t *) nav_buf;
+
+ if (e_vts->new_vtsN == state->vtsn && e_vts->new_domain == DVD_DOMAIN_VTSTitle)
+ state->in_vts = 1;
+
+ continue;
+ case DVDNAV_CELL_CHANGE:
+ if (!state->in_vts)
+ continue;
+
+ e_cell = (dvdnav_cell_change_event_t *) nav_buf;
+ is_cell_promising = dvdvideo_play_is_cell_promising(s, state, e_cell->cellN);
+
+ av_log(s, AV_LOG_DEBUG, "new cell: prev=%d new=%d promising=%d\n",
+ state->celln, e_cell->cellN, is_cell_promising);
+
+ if (!state->in_ps && !state->in_pgc) {
+ if (cur_title == c->opt_title && cur_ptt == c->opt_chapter_start
+ && cur_pgcn == state->pgcn && cur_pgn == state->entry_pgn
+ && is_cell_promising) {
+ state->in_pgc = 1;
+ }
+
+ if (c->opt_trim && !is_cell_promising)
+ av_log(s, AV_LOG_INFO, "Skipping padding cell #%d\n", e_cell->cellN);
+ } else if (state->celln >= e_cell->cellN || state->pgn > cur_pgn) {
+ return AVERROR_EOF;
+ }
+
+ state->celln = e_cell->cellN;
+ state->ptt = cur_ptt;
+ state->pgn = cur_pgn;
+
+ continue;
+ case DVDNAV_NAV_PACKET:
+ if (!state->in_pgc)
+ continue;
+
+ if ((state->ptt > 0 && state->ptt > cur_ptt)
+ || (c->opt_chapter_end > 0 && cur_ptt > c->opt_chapter_end)) {
+ return AVERROR_EOF;
+ }
+
+ e_pci = dvdnav_get_current_nav_pci(state->dvdnav);
+ e_dsi = dvdnav_get_current_nav_dsi(state->dvdnav);
+
+ if (e_pci == NULL || e_dsi == NULL
+ || e_pci->pci_gi.vobu_s_ptm > e_pci->pci_gi.vobu_e_ptm) {
+ av_log(s, AV_LOG_ERROR, "Invalid NAV packet\n");
+
+ return AVERROR_INVALIDDATA;
+ }
+
+ state->vobu_duration = e_pci->pci_gi.vobu_e_ptm - e_pci->pci_gi.vobu_s_ptm;
+ state->pgc_elapsed += state->vobu_duration;
+ state->nav_pts = dvdnav_get_current_time(state->dvdnav);
+ state->ptt = cur_ptt;
+ state->pgn = cur_pgn;
+ state->nb_vobus_played++;
+
+ av_log(s, AV_LOG_DEBUG, "NAV pack: s_ptm=%d e_ptm=%d "
+ "scr=%d lbn=%d vobu_duration=%d nav_pts=%ld\n",
+ e_pci->pci_gi.vobu_s_ptm, e_pci->pci_gi.vobu_e_ptm,
+ e_dsi->dsi_gi.nv_pck_scr,
+ e_pci->pci_gi.nv_pck_lbn, state->vobu_duration, state->nav_pts);
+
+ if (!state->in_ps) {
+ av_log(s, AV_LOG_DEBUG, "navigation: locked to program stream\n");
+
+ state->in_ps = 1;
+ } else {
+ if (state->vobu_e_ptm != e_pci->pci_gi.vobu_s_ptm) {
+ if (flush_cb)
+ flush_cb(s);
+
+ state->ts_offset += state->vobu_e_ptm - e_pci->pci_gi.vobu_s_ptm;
+ }
+ }
+
+ state->vobu_e_ptm = e_pci->pci_gi.vobu_e_ptm;
+
+ (*p_nav_event) = nav_event;
+
+ return nav_len;
+ case DVDNAV_BLOCK_OK:
+ if (!state->in_ps) {
+ if (state->in_pgc)
+ i = 0; /* necessary in case we are skipping junk cells at the beginning */
+ continue;
+ }
+
+ if (nav_len != DVDVIDEO_BLOCK_SIZE) {
+ av_log(s, AV_LOG_ERROR, "Invalid block size\n");
+
+ 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);
+
+ /* in case NAV packet is missed */
+ state->ptt = cur_ptt;
+ state->pgn = cur_pgn;
+
+ (*p_nav_event) = nav_event;
+
+ return nav_len;
+ case DVDNAV_STILL_FRAME:
+ case DVDNAV_WAIT:
+ case DVDNAV_HOP_CHANNEL:
+ case DVDNAV_HIGHLIGHT:
+ if (state->in_ps)
+ return AVERROR_EOF;
+
+ if (nav_event == DVDNAV_STILL_FRAME)
+ dvdnav_still_skip(state->dvdnav);
+ if (nav_event == DVDNAV_WAIT)
+ dvdnav_wait_skip(state->dvdnav);
+
+ 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_INVALIDDATA;
+}
+
+static int dvdvideo_pgc_preindex(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret = 0, interrupt = 0;
+ int nb_chapters = 0, last_ptt = c->opt_chapter_start;
+ uint64_t cur_chapter_offset = 0, cur_chapter_duration = 0;
+ DVDVideoPlaybackState *state;
+
+ uint8_t nav_buf[DVDVIDEO_BLOCK_SIZE];
+ int nav_event;
+
+ if (c->opt_chapter_start == c->opt_chapter_end)
+ return 0;
+
+ av_log(s, AV_LOG_INFO,
+ "Indexing chapter markers, this will take a long time. Please wait...\n");
+
+ state = av_mallocz(sizeof(DVDVideoPlaybackState));
+ if ((ret = dvdvideo_play_open(s, state)) < 0) {
+ av_freep(&state);
+ return ret;
+ }
+
+ while (!(interrupt = ff_check_interrupt(&s->interrupt_callback))) {
+ ret = dvdvideo_play_next_ps_block(s, state, nav_buf, DVDVIDEO_BLOCK_SIZE,
+ &nav_event, NULL);
+ if (ret < 0 && ret != AVERROR_EOF)
+ goto end_free;
+
+ if (nav_event != DVDNAV_NAV_PACKET && ret != AVERROR_EOF)
+ continue;
+
+ if (state->ptt == last_ptt) {
+ cur_chapter_duration += state->vobu_duration;
+ /* ensure we add the last chapter */
+ if (ret != AVERROR_EOF)
+ continue;
+ }
+
+ if (!avpriv_new_chapter(s, nb_chapters, DVDVIDEO_TIME_BASE_Q, cur_chapter_offset,
+ cur_chapter_offset + cur_chapter_duration, NULL)) {
+ av_log(s, AV_LOG_ERROR, "Unable to allocate chapter\n");
+
+ goto end_free;
+ }
+
+ nb_chapters++;
+ cur_chapter_offset += cur_chapter_duration;
+ cur_chapter_duration = state->vobu_duration;
+ last_ptt = state->ptt;
+
+ if (ret == AVERROR_EOF)
+ break;
+ }
+
+ if (interrupt) {
+ ret = AVERROR_EXIT;
+ goto end_free;
+ }
+
+ if (ret < 0 && ret != AVERROR_EOF)
+ goto end_free;
+
+ s->duration = av_rescale_q(state->pgc_elapsed, DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
+ c->duration_ptm = state->pgc_elapsed;
+
+ av_log(s, AV_LOG_INFO, "Chapter marker indexing complete\n");
+ ret = 0;
+
+end_free:
+ dvdvideo_play_close(s, state);
+ av_freep(&state);
+
+ return ret;
+}
+
+static int dvdvideo_pgc_chapters_setup(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ uint64_t time_prev = 0;
+ int64_t total_duration = 0;
+
+ int chapter_start = c->opt_chapter_start - 1;
+ int chapter_end = c->opt_chapter_end > 0 ? c->opt_chapter_end : c->play_state.pgc_nb_pg_est - 1;
+
+ if (chapter_start == chapter_end || c->play_state.pgc_nb_pg_est == 1) {
+ s->duration = av_rescale_q(c->play_state.pgc_duration_est,
+ DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
+ return 0;
+ }
+
+ for (int i = chapter_start; i < chapter_end; i++) {
+ uint64_t time_effective = c->play_state.pgc_pg_times_est[i] - c->play_state.nav_pts;
+
+ 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;
+ total_duration = time_effective;
+ }
+
+ if (c->opt_chapter_start == 1 && c->opt_chapter_end == 0)
+ s->duration = av_rescale_q(c->play_state.pgc_duration_est,
+ DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
+ else
+ s->duration = av_rescale_q(total_duration,
+ DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
+
+ return 0;
+}
+
+static int dvdvideo_video_stream_analyze(AVFormatContext *s, 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) {
+ av_log(s, AV_LOG_ERROR, "Invalid video dimensions\n");
+
+ 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 = !is_pal && (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 = NULL;
+ int ret = 0;
+
+ entry = av_mallocz(sizeof(DVDVideoVTSVideoStreamEntry));
+
+ if ((ret = dvdvideo_video_stream_analyze(s, 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_HEADERS)) < 0) {
+ av_freep(&entry);
+ av_log(s, AV_LOG_ERROR, "Unable to allocate video stream\n");
+
+ return ret;
+ }
+
+ av_freep(&entry);
+
+ return ret;
+}
+
+static int dvdvideo_audio_stream_analyze(AVFormatContext *s, 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 = (AVChannelLayout) {0};
+ char lang_dvd[3] = {0};
+
+ int position = (audio_control & 0x7F00) >> 8;
+
+ /* XXX(PATCHWELCOME): SDDS is not supported due to lack of sample material */
+ switch (audio_attr.audio_format) {
+ case 0: /* AC3 */
+ codec_id = AV_CODEC_ID_AC3;
+ sample_fmt = AV_SAMPLE_FMT_FLTP;
+ sample_rate = 48000;
+ startcode = 0x80 + position;
+ break;
+ case 2: /* MP1 */
+ 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: /* MP2 */
+ 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: /* DVD PCM */
+ codec_id = AV_CODEC_ID_PCM_DVD;
+ 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: /* DCA */
+ 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) {
+ av_log(s, AV_LOG_ERROR, "Invalid audio parameters\n");
+
+ 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 == 7)
+ ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_6POINT1;
+ else if (nb_channels == 8)
+ ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_7POINT1;
+
+ if (audio_attr.code_extension == 2)
+ entry->disposition |= AV_DISPOSITION_VISUAL_IMPAIRED;
+ if (audio_attr.code_extension == 3 || audio_attr.code_extension == 4)
+ entry->disposition |= AV_DISPOSITION_COMMENT;
+
+ 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;
+ st->disposition = entry->disposition;
+
+ 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 = 0;
+
+ for (int i = 0; i < c->vts_ifo->vtsi_mat->nr_of_vts_audio_streams; i++) {
+ DVDVideoPGCAudioStreamEntry *entry = NULL;
+
+ if (!(c->play_state.pgc->audio_control[i] & 0x8000))
+ continue;
+
+ entry = av_mallocz(sizeof(DVDVideoPGCAudioStreamEntry));
+ if (!entry)
+ return AVERROR(ENOMEM);
+
+ if ((ret = dvdvideo_audio_stream_analyze(s, c->vts_ifo->vtsi_mat->vts_audio_attr[i],
+ c->play_state.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, "Karaoke metadata in stream %d will not be retained\n",
+ entry->startcode);
+
+ /* IFO structures can declare duplicate entries for the same 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
+ || (ret = dvdvideo_audio_stream_add(s, entry, AVSTREAM_PARSE_HEADERS)) < 0)
+ goto break_free_and_error;
+
+continue_free:
+ av_freep(&entry);
+ continue;
+
+break_free_and_error:
+ av_freep(&entry);
+ av_log(s, AV_LOG_ERROR, "Unable to allocate audio stream\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+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);
+
+ if (subp_attr.lang_extension == 9)
+ entry->disposition |= AV_DISPOSITION_FORCED;
+
+ 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;
+ int ret = 0;
+
+ 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;
+
+ if (entry->lang_iso)
+ av_dict_set(&st->metadata, "language", entry->lang_iso, 0);
+
+ av_dict_set(&st->metadata, "VIEWPORT", VIEWPORT_LABELS[entry->viewport], 0);
+
+ st->disposition = entry->disposition;
+
+ 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 ret;
+}
+
+static int dvdvideo_subp_stream_add_internal(AVFormatContext *s, uint32_t offset,
+ subp_attr_t subp_attr,
+ enum DVDVideoSubpictureViewport viewport)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ DVDVideoPGCSubtitleStreamEntry *entry = NULL;
+ int ret = 0;
+
+ entry = av_mallocz(sizeof(DVDVideoPGCSubtitleStreamEntry));
+ entry->viewport = viewport;
+
+ if ((ret = dvdvideo_subp_stream_analyze(s, offset, subp_attr, entry)) < 0)
+ goto end_free_error;
+
+ /* IFO structures can declare duplicate entries for the same startcode */
+ 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_HEADERS)) < 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_freep(&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 = 0;
+ uint32_t subp_control;
+ subp_attr_t subp_attr;
+ video_attr_t video_attr;
+
+ subp_control = c->play_state.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,
+ DVDVIDEO_SUBP_VIEWPORT_FULLSCREEN)) < 0)
+ return ret;
+
+ continue;
+ }
+
+ /* 16:9 */
+ if ((ret = dvdvideo_subp_stream_add_internal(s, subp_control >> 16, subp_attr,
+ DVDVIDEO_SUBP_VIEWPORT_WIDESCREEN)) < 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,
+ DVDVIDEO_SUBP_VIEWPORT_LETTERBOX)) < 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,
+ DVDVIDEO_SUBP_VIEWPORT_PANSCAN)) < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void dvdvideo_subdemux_flush(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ if (!c->play_seg_started)
+ return;
+
+ av_log(s, AV_LOG_DEBUG, "flushing sub-demuxer\n");
+ avio_flush(&c->mpeg_pb.pub);
+ ff_read_frame_flush(c->mpeg_ctx);
+ c->play_seg_started = 0;
+}
+
+static int dvdvideo_subdemux_read_data(void *opaque, uint8_t *buf, int buf_size)
+{
+ AVFormatContext *s = opaque;
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret = 0;
+ int nav_event;
+
+ if (c->play_end)
+ return AVERROR_EOF;
+
+ ret = dvdvideo_play_next_ps_block(opaque, &c->play_state, buf, buf_size,
+ &nav_event, dvdvideo_subdemux_flush);
+
+ if (ret == AVERROR_EOF) {
+ c->mpeg_pb.pub.eof_reached = 1;
+ c->play_end = 1;
+
+ return AVERROR_EOF;
+ }
+
+ if (ret >= 0 && nav_event == DVDNAV_NAV_PACKET)
+ return FFERROR_REDO;
+
+ return ret;
+}
+
+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 = 0;
+
+ if (!(c->mpeg_fmt = av_find_input_format("mpeg")))
+ return AVERROR_DEMUXER_NOT_FOUND;
+
+ if (!(c->mpeg_ctx = avformat_alloc_context()))
+ return AVERROR(ENOMEM);
+
+ if (!(c->mpeg_buf = av_mallocz(DVDVIDEO_BLOCK_SIZE))) {
+ avformat_free_context(c->mpeg_ctx);
+
+ return AVERROR(ENOMEM);
+ }
+
+ ffio_init_context(&c->mpeg_pb, c->mpeg_buf, DVDVIDEO_BLOCK_SIZE, 0, s,
+ dvdvideo_subdemux_read_data, NULL, NULL);
+ c->mpeg_pb.pub.seekable = 0;
+
+ if ((ret = ff_copy_whiteblacklists(c->mpeg_ctx, s)) < 0) {
+ avformat_free_context(c->mpeg_ctx);
+
+ return ret;
+ }
+
+ c->mpeg_ctx->flags = AVFMT_FLAG_CUSTOM_IO | AVFMT_FLAG_GENPTS;
+ c->mpeg_ctx->ctx_flags |= AVFMTCTX_UNSEEKABLE;
+ c->mpeg_ctx->probesize = 0;
+ c->mpeg_ctx->max_analyze_duration = 0;
+ c->mpeg_ctx->interrupt_callback = s->interrupt_callback;
+ c->mpeg_ctx->pb = &c->mpeg_pb.pub;
+ c->mpeg_ctx->io_open = NULL;
+
+ if ((ret = avformat_open_input(&c->mpeg_ctx, "", c->mpeg_fmt, NULL)) < 0) {
+ avformat_free_context(c->mpeg_ctx);
+
+ return ret;
+ }
+
+ return ret;
+}
+
+static int dvdvideo_read_header(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret = 0;
+
+ if (c->opt_title == 0) {
+ av_log(s, AV_LOG_INFO, "Defaulting to title #1. "
+ "This is not always the main feature, validation suggested.\n");
+
+ c->opt_title = 1;
+ }
+
+ if (c->opt_pgc) {
+ if (c->opt_pg == 0)
+ av_log(s, AV_LOG_ERROR, "Invalid coordinates. If -pgc is set, -pg must be set too.\n");
+ else if (c->opt_chapter_start > 1 || c->opt_chapter_end > 0 || c->opt_preindex)
+ av_log(s, AV_LOG_ERROR, "-pgc is not compatible with the -preindex or "
+ "-chapter_start/-chapter_end options\n");
+
+ return AVERROR(EINVAL);
+ }
+
+ if ((ret = dvdvideo_ifo_open(s)) < 0)
+ return ret;
+
+ if (c->opt_preindex && (ret = dvdvideo_pgc_preindex(s)) < 0)
+ return ret;
+
+ if ((ret = dvdvideo_play_open(s, &c->play_state)) < 0
+ || (ret = dvdvideo_subdemux_open(s)) < 0
+ || (ret = dvdvideo_video_stream_setup(s)) < 0
+ || (ret = dvdvideo_audio_stream_add_all(s)) < 0
+ || (ret = dvdvideo_subp_stream_add_all(s)) < 0)
+ return ret;
+
+ if (!c->opt_preindex)
+ return dvdvideo_pgc_chapters_setup(s);
+
+ return ret;
+}
+
+static int dvdvideo_read_packet(AVFormatContext *s, AVPacket *pkt)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ int ret;
+ enum AVMediaType st_type;
+ int64_t cur_delta = 0;
+
+ if (c->play_end)
+ return AVERROR_EOF;
+
+ ret = av_read_frame(c->mpeg_ctx, pkt);
+
+ if (ret < 0)
+ return ret;
+
+ if (!c->play_seg_started)
+ c->play_seg_started = 1;
+
+ if (pkt->stream_index >= s->nb_streams) {
+ av_log(s, AV_LOG_DEBUG, "discarding frame with unknown stream\n");
+ goto skip_redo;
+ }
+
+ st_type = c->mpeg_ctx->streams[pkt->stream_index]->codecpar->codec_type;
+
+ if (!c->play_started) {
+ if (st_type != AVMEDIA_TYPE_VIDEO || pkt->pts == AV_NOPTS_VALUE
+ || pkt->dts == AV_NOPTS_VALUE
+ || !(pkt->flags & AV_PKT_FLAG_KEY)) {
+ av_log(s, AV_LOG_DEBUG, "discarding non-video-keyframe at start\n");
+ goto skip_redo;
+ }
+
+ c->first_pts = pkt->pts;
+ c->play_started = 1;
+
+ pkt->pts = 0;
+ pkt->dts = c->play_state.ts_offset - pkt->pts;
+ } else {
+ cur_delta = c->play_state.ts_offset - c->first_pts;
+
+ if (c->play_state.nb_vobus_played == 1 && (pkt->pts == AV_NOPTS_VALUE
+ || pkt->dts == AV_NOPTS_VALUE
+ || (pkt->pts + cur_delta) < 0)) {
+ av_log(s, AV_LOG_DEBUG, "discarding frame with negative or unset timestamp "
+ "(this is OK at the start of the first playback VOBU)\n");
+ goto skip_redo;
+ }
+
+ if (pkt->pts != AV_NOPTS_VALUE)
+ pkt->pts += cur_delta;
+ if (pkt->dts != AV_NOPTS_VALUE)
+ pkt->dts += cur_delta;
+ }
+
+ av_log(s, AV_LOG_TRACE, "st=%d pts=%ld dts=%ld ts_offset=%ld first_pts=%ld\n",
+ pkt->stream_index, pkt->pts, pkt->dts,
+ c->play_state.ts_offset, c->first_pts);
+ if (pkt->pts < 0)
+ av_log(s, AV_LOG_WARNING, "Invalid frame PTS @ st=%d pts=%ld dts=%ld\n",
+ pkt->stream_index, pkt->pts, pkt->dts);
+
+ return c->play_end ? AVERROR_EOF : 0;
+
+skip_redo:
+ av_packet_unref(pkt);
+ return FFERROR_REDO;
+}
+
+static int dvdvideo_close(AVFormatContext *s)
+{
+ DVDVideoDemuxContext *c = s->priv_data;
+
+ dvdvideo_subdemux_close(s);
+ dvdvideo_play_close(s, &c->play_state);
+ dvdvideo_ifo_close(s);
+
+ return 0;
+}
+
+#define OFFSET(x) offsetof(DVDVideoDemuxContext, x)
+static const AVOption dvdvideo_options[] = {
+ {"angle", "playback angle number", OFFSET(opt_angle), AV_OPT_TYPE_INT, { .i64=1 }, 1, 9, AV_OPT_FLAG_DECODING_PARAM },
+ {"chapter_end", "exit chapter (PTT) number (0=end)", OFFSET(opt_chapter_end), AV_OPT_TYPE_INT, { .i64=0 }, 0, 99, AV_OPT_FLAG_DECODING_PARAM },
+ {"chapter_start", "entry chapter (PTT) number", OFFSET(opt_chapter_start), AV_OPT_TYPE_INT, { .i64=1 }, 1, 99, 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 },
+ {"pgc", "entry PGC number (0=auto)", OFFSET(opt_pgc), AV_OPT_TYPE_INT, { .i64=0 }, 0, 999, AV_OPT_FLAG_DECODING_PARAM },
+ {"preindex", "enable for accurate chapter markers, slow (2-pass read)", OFFSET(opt_preindex), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, 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 },
+ {"title", "title number (0=auto)", OFFSET(opt_title), AV_OPT_TYPE_INT, { .i64=0 }, 0, 99, AV_OPT_FLAG_DECODING_PARAM },
+ {"trim", "trim padding cells from start", OFFSET(opt_trim), AV_OPT_TYPE_BOOL, { .i64=1 }, 0, 1, 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_close = dvdvideo_close,
+ .read_header = dvdvideo_read_header,
+ .read_packet = dvdvideo_read_packet
+};
--
2.34.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* [FFmpeg-devel] [PATCH v7 2/2] libavformat/dvdvideo: add DVD CLUT utilities and enable palette support
2024-02-05 5:48 ` [FFmpeg-devel] [PATCH v7 1/2] " Marth64
@ 2024-02-05 5:48 ` Marth64
2024-02-07 18:09 ` Andreas Rheinhardt
2024-02-07 0:52 ` [FFmpeg-devel] [PATCH v7 1/2] libavformat/dvdvideo: add DVD-Video demuxer powered by libdvdnav and libdvdread Stefano Sabatini
2024-02-07 18:58 ` Andreas Rheinhardt
2 siblings, 1 reply; 27+ messages in thread
From: Marth64 @ 2024-02-05 5:48 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Marth64
DVD subtitle palettes, which are natively YUV, are currently carried as
a hex string in their respective subtitle streams and have
no concept of colorspace tagging. In fact, the convention is to convert
them to RGB prior to storage. Common players will only render
the palettes properly if they are stored as RGB. Even ffmpeg itself
expects this, and already does -in libavformat- the YUV-RGB conversions,
specifically in mov.c and movenc.c.
The point of this patch is to provide a consolidation of the code
that deals with creating the extradata as well as the RGB conversion.
That can then (1) enable usable palette support for DVD demuxer if it is merged
and (2) start the process of consolidating the related conversions in
MOV muxer/demuxer and eventually find a way to properly tag
the colorspace.
Signed-off-by: Marth64 <marth64@proxyid.net>
---
doc/demuxers.texi | 5 ++
libavformat/Makefile | 2 +-
libavformat/dvdclut.c | 104 ++++++++++++++++++++++++++++++++++++++
libavformat/dvdclut.h | 37 ++++++++++++++
libavformat/dvdvideodec.c | 14 +++++
5 files changed, 161 insertions(+), 1 deletion(-)
create mode 100644 libavformat/dvdclut.c
create mode 100644 libavformat/dvdclut.h
diff --git a/doc/demuxers.texi b/doc/demuxers.texi
index ad0906f6ec..9c666a29c1 100644
--- a/doc/demuxers.texi
+++ b/doc/demuxers.texi
@@ -390,6 +390,11 @@ often with junk data intended for controlling a real DVD player's
buffering speed and with no other material data value.
Default is 1, true.
+@item clut_rgb
+Output subtitle palettes (CLUTs) as RGB, required for Matroska and MP4.
+Disable to output the palette in its original YUV colorspace.
+Default is 1, true.
+
@end table
@subsection Examples
diff --git a/libavformat/Makefile b/libavformat/Makefile
index df69734877..8f17e43e49 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -192,7 +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_DVDVIDEO_DEMUXER) += dvdvideodec.o dvdclut.o
OBJS-$(CONFIG_DXA_DEMUXER) += dxa.o
OBJS-$(CONFIG_EA_CDATA_DEMUXER) += eacdata.o
OBJS-$(CONFIG_EA_DEMUXER) += electronicarts.o
diff --git a/libavformat/dvdclut.c b/libavformat/dvdclut.c
new file mode 100644
index 0000000000..71fdda7925
--- /dev/null
+++ b/libavformat/dvdclut.c
@@ -0,0 +1,104 @@
+/*
+ * DVD-Video subpicture CLUT (Color Lookup Table) utilities
+ *
+ * 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/bprint.h"
+#include "libavutil/colorspace.h"
+#include "libavutil/intreadwrite.h"
+
+#include "avformat.h"
+#include "dvdclut.h"
+#include "internal.h"
+
+/* crop table for YUV to RGB subpicture palette conversion */
+#define FF_DVDCLUT_YUV_NEG_CROP_MAX 1024
+#define times4(x) x, x, x, x
+#define times256(x) times4(times4(times4(times4(times4(x)))))
+
+const uint8_t ff_dvdclut_yuv_crop_tab[256 + 2 * FF_DVDCLUT_YUV_NEG_CROP_MAX] = {
+times256(0x00),
+0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
+0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
+0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
+0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
+0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
+0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,
+0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
+0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,
+0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
+0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
+0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
+0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
+0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
+0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
+0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,
+0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF,
+times256(0xFF)
+};
+
+int ff_dvdclut_palette_extradata_cat(const uint32_t *clut,
+ const size_t clut_size,
+ AVCodecParameters *par)
+{
+ int ret = 0;
+ AVBPrint bp;
+
+ if (clut_size != FF_DVDCLUT_CLUT_SIZE)
+ return AVERROR(EINVAL);
+
+ av_bprint_init(&bp, 0, FF_DVDCLUT_EXTRADATA_SIZE);
+
+ av_bprintf(&bp, "palette: ");
+
+ for (int i = 0; i < FF_DVDCLUT_CLUT_LEN; i++)
+ av_bprintf(&bp, "%06"PRIx32"%s", clut[i], i != 15 ? ", " : "");
+
+ av_bprintf(&bp, "\n");
+
+ ret = ff_bprint_to_codecpar_extradata(par, &bp);
+
+ av_bprint_finalize(&bp, NULL);
+
+ return ret;
+}
+
+int ff_dvdclut_yuv_to_rgb(uint32_t *clut, const size_t clut_size)
+{
+ const uint8_t *cm = ff_dvdclut_yuv_crop_tab + FF_DVDCLUT_YUV_NEG_CROP_MAX;
+
+ int i, y, cb, cr;
+ uint8_t r, g, b;
+ int r_add, g_add, b_add;
+
+ if (clut_size != FF_DVDCLUT_CLUT_SIZE)
+ return AVERROR(EINVAL);
+
+ for (i = 0; i < FF_DVDCLUT_CLUT_LEN; i++) {
+ y = (clut[i] >> 16) & 0xFF;
+ cr = (clut[i] >> 8) & 0xFF;
+ cb = clut[i] & 0xFF;
+
+ YUV_TO_RGB1_CCIR(cb, cr);
+ YUV_TO_RGB2_CCIR(r, g, b, y);
+
+ clut[i] = (r << 16) | (g << 8) | b;
+ }
+
+ return 0;
+}
diff --git a/libavformat/dvdclut.h b/libavformat/dvdclut.h
new file mode 100644
index 0000000000..40eff6de34
--- /dev/null
+++ b/libavformat/dvdclut.h
@@ -0,0 +1,37 @@
+/*
+ * DVD-Video subpicture CLUT (Color Lookup Table) utilities
+ *
+ * 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
+ */
+
+#ifndef AVFORMAT_DVDCLUT_H
+#define AVFORMAT_DVDCLUT_H
+
+#include "avformat.h"
+
+/* ("palette: ") + ("rrggbb, "*15) + ("rrggbb") + \n + \0 */
+#define FF_DVDCLUT_EXTRADATA_SIZE (9 + (8 * 15) + 6 + 1 + 1)
+#define FF_DVDCLUT_CLUT_LEN 16
+#define FF_DVDCLUT_CLUT_SIZE FF_DVDCLUT_CLUT_LEN * sizeof(uint32_t)
+
+int ff_dvdclut_palette_extradata_cat(const uint32_t *clut,
+ const size_t clut_size,
+ AVCodecParameters *par);
+
+int ff_dvdclut_yuv_to_rgb(uint32_t *clut, const size_t clut_size);
+
+#endif /* AVFORMAT_DVDCLUT_H */
diff --git a/libavformat/dvdvideodec.c b/libavformat/dvdvideodec.c
index 2e363a4a22..657825ba3e 100644
--- a/libavformat/dvdvideodec.c
+++ b/libavformat/dvdvideodec.c
@@ -50,6 +50,7 @@
#include "avio_internal.h"
#include "avlanguage.h"
#include "demux.h"
+#include "dvdclut.h"
#include "internal.h"
#include "url.h"
@@ -91,6 +92,7 @@ typedef struct DVDVideoPGCAudioStreamEntry {
typedef struct DVDVideoPGCSubtitleStreamEntry {
int startcode;
+ uint32_t *clut;
int disposition;
char *lang_iso;
enum DVDVideoSubpictureViewport viewport;
@@ -132,6 +134,7 @@ typedef struct DVDVideoDemuxContext {
int opt_region; /* the user-provided region digit */
int opt_preindex; /* pre-indexing mode (2-pass read) */
int opt_trim; /* trim padding cells at beginning and end */
+ int opt_clut_rgb; /* output subtitle palette (CLUT) as RGB */
/* subdemux */
const AVInputFormat *mpeg_fmt; /* inner MPEG-PS (VOB) demuxer */
@@ -1034,6 +1037,11 @@ static int dvdvideo_subp_stream_analyze(AVFormatContext *s, uint32_t offset, sub
if (subp_attr.lang_extension == 9)
entry->disposition |= AV_DISPOSITION_FORCED;
+ memcpy(entry->clut, c->play_state.pgc->palette, FF_DVDCLUT_CLUT_SIZE);
+
+ if (c->opt_clut_rgb)
+ ff_dvdclut_yuv_to_rgb(entry->clut, FF_DVDCLUT_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);
@@ -1055,6 +1063,9 @@ static int dvdvideo_subp_stream_add(AVFormatContext *s, DVDVideoPGCSubtitleStrea
st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
st->codecpar->codec_id = AV_CODEC_ID_DVD_SUBTITLE;
+ if ((ret = ff_dvdclut_palette_extradata_cat(entry->clut, FF_DVDCLUT_CLUT_SIZE, st->codecpar)) < 0)
+ return ret;
+
if (entry->lang_iso)
av_dict_set(&st->metadata, "language", entry->lang_iso, 0);
@@ -1082,6 +1093,7 @@ static int dvdvideo_subp_stream_add_internal(AVFormatContext *s, uint32_t offset
int ret = 0;
entry = av_mallocz(sizeof(DVDVideoPGCSubtitleStreamEntry));
+ entry->clut = av_mallocz(FF_DVDCLUT_CLUT_SIZE);
entry->viewport = viewport;
if ((ret = dvdvideo_subp_stream_analyze(s, offset, subp_attr, entry)) < 0)
@@ -1102,6 +1114,7 @@ end_free_error:
av_log(s, AV_LOG_ERROR, "Unable to allocate subtitle stream\n");
end_free:
+ av_freep(&entry->clut);
av_freep(&entry);
return ret;
@@ -1381,6 +1394,7 @@ static const AVOption dvdvideo_options[] = {
{"angle", "playback angle number", OFFSET(opt_angle), AV_OPT_TYPE_INT, { .i64=1 }, 1, 9, AV_OPT_FLAG_DECODING_PARAM },
{"chapter_end", "exit chapter (PTT) number (0=end)", OFFSET(opt_chapter_end), AV_OPT_TYPE_INT, { .i64=0 }, 0, 99, AV_OPT_FLAG_DECODING_PARAM },
{"chapter_start", "entry chapter (PTT) number", OFFSET(opt_chapter_start), AV_OPT_TYPE_INT, { .i64=1 }, 1, 99, AV_OPT_FLAG_DECODING_PARAM },
+ {"clut_rgb", "output subtitle palette (CLUT) as RGB", OFFSET(opt_clut_rgb), AV_OPT_TYPE_BOOL, { .i64=1 }, 0, 1, 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 },
{"pgc", "entry PGC number (0=auto)", OFFSET(opt_pgc), AV_OPT_TYPE_INT, { .i64=0 }, 0, 999, AV_OPT_FLAG_DECODING_PARAM },
{"preindex", "enable for accurate chapter markers, slow (2-pass read)", OFFSET(opt_preindex), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, AV_OPT_FLAG_DECODING_PARAM },
--
2.34.1
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH v7 1/2] libavformat/dvdvideo: add DVD-Video demuxer powered by libdvdnav and libdvdread
2024-02-05 5:48 ` [FFmpeg-devel] [PATCH v7 1/2] " Marth64
2024-02-05 5:48 ` [FFmpeg-devel] [PATCH v7 2/2] libavformat/dvdvideo: add DVD CLUT utilities and enable palette support Marth64
@ 2024-02-07 0:52 ` Stefano Sabatini
2024-02-07 0:54 ` Marth64
2024-02-07 18:58 ` Andreas Rheinhardt
2 siblings, 1 reply; 27+ messages in thread
From: Stefano Sabatini @ 2024-02-07 0:52 UTC (permalink / raw)
To: FFmpeg development discussions and patches; +Cc: Marth64
On date Sunday 2024-02-04 23:48:25 -0600, Marth64 wrote:
> Sorry for the quick follow-up to v6, I wanted to get this update out now hopefully
> before v6 is actually reviewed. v7 fixes a few documentation typos, options descriptions,
> and log messages: nothing major. I also flattened this out to be a 2-patch set
> with subtitle palette support.
>
> This is the result of >1yr of research and I am now very satisfied with it's result.
> I will not make any more changes at this point unless it is outcome from a review.
> If the community decides to merge it, I am happy to continue to improve it with support for
> seeking, probing, and tests.
>
> Thank you so much,
>
>
> Signed-off-by: Marth64 <marth64@proxyid.net>
> ---
> Changelog | 2 +
> configure | 8 +
> doc/demuxers.texi | 129 ++++
> libavformat/Makefile | 1 +
> libavformat/allformats.c | 1 +
> libavformat/dvdvideodec.c | 1410 +++++++++++++++++++++++++++++++++++++
> 6 files changed, 1551 insertions(+)
> create mode 100644 libavformat/dvdvideodec.c
>
> diff --git a/Changelog b/Changelog
> index c5fb21d198..88653bc6d3 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -24,6 +24,8 @@ version <next>:
> - ffmpeg CLI options may now be used as -/opt <path>, which is equivalent
> to -opt <contents of file <path>>
> - showinfo bitstream filter
> +- DVD-Video demuxer, powered by libdvdnav and libdvdread
> +
>
> version 6.1:
> - libaribcaption decoder
> diff --git a/configure b/configure
> index 68f675a4bc..70c33ec96d 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
> @@ -3520,6 +3524,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"
> @@ -6761,6 +6767,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/doc/demuxers.texi b/doc/demuxers.texi
> index e4c5b560a6..ad0906f6ec 100644
> --- a/doc/demuxers.texi
> +++ b/doc/demuxers.texi
> @@ -285,6 +285,135 @@ This demuxer accepts the following option:
>
> @end table
>
> +@section dvdvideo
> +
> +DVD-Video demuxer, powered by libdvdnav and libdvdread.
> +
> +Can directly ingest DVD titles, specifically sequential PGCs,
> +into a conversion pipeline. Menus and seeking are not supported at this time.
> +
> +Block devices (DVD drives), ISO files, and directory structures are accepted.
> +Activate with @code{-f dvdvideo} in front of one of these inputs.
> +
> +Underlying playback is fully handled by libdvdnav, and structure parsing by libdvdread.
> +ffmpeg must be built with GPL library support available as well as the switches
> +@code{--enable-libdvdnav} and @code{--enable-libdvdread}.
FFmpeg must be built with GPL library support available as well as the
configure switches @code{--enable-libdvdnav} and @code{--enable-libdvdread}.
> +
> +You will need to provide either the desired "title number" or exact PGC/PG coordinates.
> +Many open-source DVD players and tools can aid in providing this information.
unrelated, but it might be useful to have a -list-titles option to
show this information
> +If not specified, the demuxer will default to title 1 which works for many discs.
> +However, due to the flexibility of DVD-Video, it is recommended to check manually.
> +There are many discs that are authored strangely or with invalid headers.
> +
> +If the input is a real DVD drive, please note that there are some drives which may
> +silently fail on reading bad sectors from the disc, returning random bits instead
> +which is effectively corrupt data. This is especially prominent on aging or rotting discs.
> +A second pass and integrity checks would be needed to detect the corruption.
> +This is not an ffmpeg issue.
nit: FFmpeg (ffmpeg is usually a reference to the CLI command, while
FFmpeg refers to the projects and set of all libraries/tools)
> +
> +@subsection Background
> +
> +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 MPEG-PS streams that are stored in headerless VOB files.
> +To the end-user, these streams are known simply as "titles", but the actual
> +logical playback sequence is defined by one or more "PGCs", or Program Group Chains,
> +within the title. The PGC is in turn comprised of multiple "PGs", or Programs",
> +which are the actual video segments which, for a typical movie, are sequentially
> +ordered. The PGC structure, along with stream layout and metadata, are stored in
> +IFO files that need to be parsed. PGCs can be thought of as playlists in easier terms.
> +
> +An actual DVD player relies on user GUI interaction via menus and an internal VM
> +to drive the direction of demuxing. Generally, the user would either navigate (via menus)
> +or automatically be redirected to the PGC of their choice. During this process and
> +the subsequent playback, the DVD player's internal VM also maintains a state and
> +executes instructions that can create jumps to different sectors during playback.
> +This is why libdvdnav is involved, as a linear read of the MPEG-PS blobs on the
> +disc (VOBs) is not enough to produce the right sequence in many cases.
> +
> +There are many other DVD structures (a long subject) that will not be discussed here.
> +NAV packets, in particular, are handled by this demuxer to build accurate timing
> +but not emitted as a stream. For a good high-level understanding, refer to:
> +@url{https://code.videolan.org/videolan/libdvdnav/-/blob/master/doc/dvd_structures}
> +
> +@subsection Options
> +
> +This demuxer accepts the following options:
> +
> +@table @option
> +
> +@item title
here and below: you might use @var{TYPE} to denote the argument/type of the
option
> +The title number to play. Must be set if @option{pgc} and @option{pg} are not set.
> +Default is 0 (auto), which currently only selects the first available title (title 1)
> +and notifies the user about the implications.
> +
> +@item chapter_start
> +The chapter, or PTT (part-of-title), number to start at. Default is 1.
> +
> +@item chapter_end
> +The chapter, or PTT (part-of-title), number to end at. Default is 0,
> +which is a special value to signal end at the last possible chapter.
> +
> +@item angle
> +The video angle number, referring to what is essentially an additional
> +video stream that is composed from alternate frames interleaved in the VOBs.
> +Default is 1.
> +
> +@item region
> +The region code to use for playback. Some discs may use this to default playback
> +at a particular angle in different regions. This option will not affect the region code
> +of a real DVD drive, if used as an input. Default is 0, "world".
> +
> +@item pgc
> +The entry PGC to start playback, in conjunction with @option{pg}.
> +Alternative to setting @option{title}.
> +Chapter markers not supported at this time.
nit: are not supported ...
> +Default is 0, automatically resolve from value of @option{title}.
> +
> +@item pg
> +The entry PG to start playback, in conjunction with @option{pgc}.
> +Alternative to setting @option{title}.
> +Chapter markers not supported at this time.
are not
> +Default is 0, automatically resolve from value of @option{title}.
> +
> +@item preindex
> +Enable this to have accurate chapter (PTT) markers and duration measurement,
> +which requires a slow second pass read in order to index the chapter
> +timestamps from NAV packets. This is non-ideal extra work for physical DVD drives.
note: if this is non-idela for physical DVD drivers, what's the
recommended use case?
> +Not compatible with @option{pgc} and @option{pg}.
> +Default is 0, false.
> +
> +@item trim
> +Skip padding cells (i.e. cells shorter than 1 second) from the beginning.
> +There exist many discs with filler segments at the beginning of the PGC,
> +often with junk data intended for controlling a real DVD player's
> +buffering speed and with no other material data value.
> +Default is 1, true.
> +
> +@end table
> +
> +@subsection Examples
> +
> +@itemize
> +@item
> +Open title 3 from a given DVD structure:
> +@example
> +ffmpeg -f dvdvideo -title 3 -i <path to DVD> ...
> +@end example
> +
> +@item
> +Open chapters 3-6 from title 1 from a given DVD structure:
> +@example
> +ffmpeg -f dvdvideo -chapter_start 3 -chapter_end 6 -title 1 -i <path to DVD> ...
> +@end example
> +
> +@item
> +Open only chapter 5 from title 1 from a given DVD structure:
> +@example
> +ffmpeg -f dvdvideo -chapter_start 5 -chapter_end 5 -title 1 -i <path to DVD> ...
> +@end example
> +@end itemize
> +
> @section ea
>
> Electronic Arts Multimedia format demuxer.
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index 05b9b8a115..df69734877 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 b04b43cab3..3e905c23f8 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/dvdvideodec.c b/libavformat/dvdvideodec.c
> new file mode 100644
> index 0000000000..2e363a4a22
> --- /dev/null
> +++ b/libavformat/dvdvideodec.c
> @@ -0,0 +1,1410 @@
> +/*
> + * DVD-Video demuxer, powered by libdvdnav and libdvdread
> + *
> + * 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
> + */
> +
> +/*
> + * See doc/demuxers.texi for a high-level overview.
> + *
> + * The tactical approach is as follows:
> + * 1) Open the volume with dvdread
> + * 2) Analyze the user-requested title and PGC coordinates in the IFO structures
> + * 3) Request playback at the coordinates and chosen angle with dvdnav
> + * 5) Begin the playback (reading and demuxing) of MPEG-PS blocks
> + * 6) End playback if navigation goes backwards, to a menu, or a different PGC or angle
> + * 7) Close the dvdnav VM, and free dvdread's IFO structures
> + */
> +
> +#include <dvdnav/dvdnav.h>
> +#include <dvdread/dvd_reader.h>
> +#include <dvdread/ifo_read.h>
> +#include <dvdread/ifo_types.h>
> +#include <dvdread/nav_read.h>
> +
> +#include "libavcodec/avcodec.h"
> +#include "libavutil/avstring.h"
> +#include "libavutil/avutil.h"
> +#include "libavutil/intreadwrite.h"
> +#include "libavutil/mem.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/samplefmt.h"
> +#include "libavutil/time.h"
> +#include "libavutil/timestamp.h"
> +
> +#include "avformat.h"
> +#include "avio_internal.h"
> +#include "avlanguage.h"
> +#include "demux.h"
> +#include "internal.h"
> +#include "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 64 /* VOBUs use 32 (PES allows 33) */
> +#define DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE 1024
> +
> +enum DVDVideoSubpictureViewport {
> + DVDVIDEO_SUBP_VIEWPORT_FULLSCREEN,
> + DVDVIDEO_SUBP_VIEWPORT_WIDESCREEN,
> + DVDVIDEO_SUBP_VIEWPORT_LETTERBOX,
> + DVDVIDEO_SUBP_VIEWPORT_PANSCAN
> +};
> +const char* VIEWPORT_LABELS[4] = { "Fullscreen", "Widescreen", "Letterbox", "Pan and Scan" };
> +
> +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;
> + int disposition;
> + char *lang_iso;
> +} DVDVideoPGCAudioStreamEntry;
> +
> +typedef struct DVDVideoPGCSubtitleStreamEntry {
> + int startcode;
> + int disposition;
> + char *lang_iso;
> + enum DVDVideoSubpictureViewport viewport;
> +} DVDVideoPGCSubtitleStreamEntry;
> +
> +typedef struct DVDVideoPlaybackState {
> + int celln; /* ID of the active cell */
> + int entry_pgn; /* ID of the PG we are starting in */
> + int in_pgc; /* if our navigator is in the PGC */
> + int in_ps; /* if our navigator is in the program stream */
> + int in_vts; /* if our navigator is in the VTS */
> + int64_t nav_pts; /* PTS according to IFO, not frame-accurate */
> + int nb_vobus_played; /* number of VOBUs played back so far */
> + uint64_t pgc_duration_est; /* estimated duration as reported by IFO */
> + uint64_t pgc_elapsed; /* the elapsed time of the PGC, cell-relative */
> + int pgc_nb_pg_est; /* number of PGs as reported by IFOs */
> + int pgcn; /* ID of the PGC we are playing */
> + int pgn; /* ID of the PG we are in now */
> + int ptt; /* ID of the chapter we are in now */
> + int64_t ts_offset; /* PTS discontinuity offset (ex. VOB change) */
> + uint32_t vobu_duration; /* duration of the current VOBU */
> + uint32_t vobu_e_ptm; /* end PTS of the current VOBU */
> + int vtsn; /* ID of the active VTS (video title set) */
> + uint64_t *pgc_pg_times_est; /* PG start times as reported by IFO */
> + pgc_t *pgc; /* handle to the active PGC */
> + dvdnav_t *dvdnav; /* handle to libdvdnav */
> +} DVDVideoPlaybackState;
> +
> +typedef struct DVDVideoDemuxContext {
> + const AVClass *class;
> +
> + /* options */
> + int opt_title; /* the user-provided title number (1-indexed) */
> + int opt_chapter_start; /* the user-provided entry PTT (1-indexed) */
> + int opt_chapter_end; /* the user-provided exit PTT (0 for last) */
> + 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 */
> + int opt_preindex; /* pre-indexing mode (2-pass read) */
> + int opt_trim; /* trim padding cells at beginning and end */
> +
> + /* 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) */
> +
> + /* playback control */
> + int play_end; /* signal EOF to the parent demuxer */
> + DVDVideoPlaybackState play_state; /* the active playback state */
> + int play_started; /* signal that playback has started */
> + int play_seg_started; /* signal that segment has started */
> + int64_t first_pts; /* the starting PTS offset of the PGC */
> + uint64_t duration_ptm; /* total duration in DVD MPEG timebase */
> +} DVDVideoDemuxContext;
> +
> +static void dvdvideo_libdvdread_log(void *opaque, dvd_logger_level_t level,
> + const char *msg, va_list msg_va)
> +{
> + AVFormatContext *s = opaque;
> + int lavu_level;
> +
> + char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
> + vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
> +
> + switch (level) {
> + case DVD_LOGGER_LEVEL_ERROR:
> + lavu_level = AV_LOG_ERROR;
> + break;
> + case DVD_LOGGER_LEVEL_WARN:
> + lavu_level = AV_LOG_WARNING;
> + break;
> + /* dvdread's info messages are relatively very verbose, muffle them as debug */
> + case DVD_LOGGER_LEVEL_INFO:
> + case DVD_LOGGER_LEVEL_DEBUG:
> + default:
> + lavu_level = AV_LOG_DEBUG;
> + }
> +
> + av_log(s, lavu_level, "libdvdread: %s\n", msg_buf);
> +}
> +
> +static void dvdvideo_libdvdnav_log(void *opaque, dvdnav_logger_level_t level,
> + const char *msg, va_list msg_va)
> +{
> + AVFormatContext *s = opaque;
> + int lavu_level;
> +
> + char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
> + vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
> +
> + switch (level) {
> + case DVDNAV_LOGGER_LEVEL_ERROR:
> + lavu_level = AV_LOG_ERROR;
> + break;
> + case DVDNAV_LOGGER_LEVEL_WARN:
> + /* some discs have invalid language codes set for their menus, muffle the noise */
> + lavu_level = av_strstart(msg, "Language", NULL) ? AV_LOG_DEBUG : AV_LOG_WARNING;
> + break;
> + /* dvdnav's info messages are relatively very verbose, muffle them as debug */
> + case DVDNAV_LOGGER_LEVEL_INFO:
> + case DVDNAV_LOGGER_LEVEL_DEBUG:
> + default:
> + lavu_level = AV_LOG_DEBUG;
> + }
> +
> + av_log(s, lavu_level, "libdvdnav: %s\n", msg_buf);
> +}
> +
> +static void dvdvideo_ifo_close(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + 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_ifo_open(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + dvd_logger_cb dvdread_log_cb;
> + title_info_t title_info;
> +
> + dvdread_log_cb = (dvd_logger_cb) { .pf_log = dvdvideo_libdvdread_log };
> + c->dvdread = DVDOpen2(s, &dvdread_log_cb, s->url);
> +
> + if (!c->dvdread) {
> + av_log(s, AV_LOG_ERROR, "Unable to open the DVD-Video structure\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + if (!(c->vmg_ifo = ifoOpen(c->dvdread, 0))) {
> + av_log(s, AV_LOG_ERROR, "Unable to open the VMG (VIDEO_TS.IFO)\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + if (c->opt_title > c->vmg_ifo->tt_srpt->nr_of_srpts) {
> + av_log(s, AV_LOG_ERROR, "Title %d not found\n", c->opt_title);
> +
> + 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 %d not found\n", c->opt_angle);
> +
> + return AVERROR_STREAM_NOT_FOUND;
> + }
> +
> + if (title_info.nr_of_ptts < 1) {
> + av_log(s, AV_LOG_ERROR, "Title %d has invalid headers (no parts)\n", c->opt_title);
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + if (c->opt_chapter_start > title_info.nr_of_ptts
> + || (c->opt_chapter_end > 0 && c->opt_chapter_end > title_info.nr_of_ptts)) {
> + av_log(s, AV_LOG_ERROR, "Chapter (PTT) range [%d, %d] is invalid\n",
> + c->opt_chapter_start, c->opt_chapter_end);
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + if (!(c->vts_ifo = ifoOpen(c->dvdread, title_info.title_set_nr))) {
> + av_log(s, AV_LOG_ERROR, "Unable to process the VTS IFO structure\n");
> +
> + return AVERROR_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 %d has invalid headers in VTS\n", c->opt_title);
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + return 0;
> +}
> +
> +static void dvdvideo_play_close(AVFormatContext *s, DVDVideoPlaybackState *state)
> +{
> + if (!state->dvdnav)
> + return;
> +
> + /* not allocated by av_malloc() */
> + if (state->pgc_pg_times_est)
> + free(state->pgc_pg_times_est);
> +
> + if (dvdnav_close(state->dvdnav) < 0)
> + av_log(s, AV_LOG_ERROR, "Unable to cleanly close dvdnav\n");
nit: in case this has consquence, you might print the error code in
the log
> +}
> +
> +static int dvdvideo_play_open(AVFormatContext *s, DVDVideoPlaybackState *state)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret = 0;
> + dvdnav_logger_cb dvdnav_log_cb;
> + dvdnav_status_t dvdnav_open_status;
> + int cur_title, cur_pgcn, cur_pgn;
> + pgc_t *pgc;
> +
> + int32_t disc_region_mask;
> + int32_t player_region_mask;
> +
> + dvdnav_log_cb = (dvdnav_logger_cb) { .pf_log = dvdvideo_libdvdnav_log };
> + dvdnav_open_status = dvdnav_open2(&state->dvdnav, s, &dvdnav_log_cb, s->url);
> +
> + if (!state->dvdnav
> + || dvdnav_open_status != DVDNAV_STATUS_OK
> + || dvdnav_set_readahead_flag(state->dvdnav, 0) != DVDNAV_STATUS_OK
> + || dvdnav_set_PGC_positioning_flag(state->dvdnav, 1) != DVDNAV_STATUS_OK
> + || dvdnav_get_region_mask(state->dvdnav, &disc_region_mask) != DVDNAV_STATUS_OK) {
nit+: here and below, weird style
usually || is put at the EOL and conditions are aligned, as in
if (foo ||
bar ||
baz) {
...
}
> + av_log(s, AV_LOG_ERROR, "Unable to open the DVD for playback\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + player_region_mask = c->opt_region > 0 ? (1 << (c->opt_region - 1)) : disc_region_mask;
> + if (dvdnav_set_region_mask(state->dvdnav, player_region_mask) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to set the playback region code %d\n", c->opt_region);
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + if (c->opt_pgc > 0 && c->opt_pg > 0) {
> + if (dvdnav_program_play(state->dvdnav, c->opt_title,
> + c->opt_pgc, c->opt_pg) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to start playback at title %d, PGC %d, PG %d\n",
> + c->opt_title, c->opt_pgc, c->opt_pg);
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + state->pgcn = c->opt_pgc;
> + state->entry_pgn = c->opt_pg;
> + } else {
> + if (dvdnav_part_play(state->dvdnav, c->opt_title, c->opt_chapter_start) != DVDNAV_STATUS_OK
> + || dvdnav_current_title_program(state->dvdnav, &cur_title,
> + &cur_pgcn, &cur_pgn) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to start playback at title %d, chapter (PTT) %d\n",
> + c->opt_title, c->opt_chapter_start);
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + state->pgcn = cur_pgcn;
> + state->entry_pgn = cur_pgn;
> + }
> +
> + pgc = c->vts_ifo->vts_pgcit->pgci_srp[state->pgcn - 1].pgc;
> +
> + if (pgc->pg_playback_mode != 0) {
> + av_log(s, AV_LOG_ERROR, "Non-sequential PGCs, such as shuffles, are not supported\n");
> +
> + return AVERROR_PATCHWELCOME;
> + }
> +
> + if (dvdnav_angle_change(state->dvdnav, c->opt_angle) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to start playback at the given angle\n");
nit: you might mention the failing angle here
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + /* dvdnav_describe_title_chapters() performs several safety checks on the title structure */
> + /* take advantage of this side effect to ensure a safe navigation path */
> + state->pgc_nb_pg_est = dvdnav_describe_title_chapters(state->dvdnav, c->opt_title,
> + &state->pgc_pg_times_est,
> + &state->pgc_duration_est);
> + if (!state->pgc_nb_pg_est) {
> + av_log(s, AV_LOG_ERROR, "Unable to validate title structure\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + state->nav_pts = dvdnav_get_current_time(state->dvdnav);
> + state->vtsn = c->vmg_ifo->tt_srpt->title[c->opt_title - 1].title_set_nr;
> +
> + state->pgc = pgc;
> +
> + return ret;
> +}
> +
> +static int dvdvideo_play_is_cell_promising(AVFormatContext *s, DVDVideoPlaybackState *state,
> + int celln)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> + dvd_time_t cell_duration;
> +
> + if (!c->opt_trim)
> + return 1;
> +
> + cell_duration = state->pgc->cell_playback[celln - 1].playback_time;
> +
> + return cell_duration.second >= 1 || cell_duration.minute >= 1 || cell_duration.hour >= 1;
> +}
> +
> +static int dvdvideo_play_next_ps_block(AVFormatContext *s, DVDVideoPlaybackState *state,
> + uint8_t *buf, int buf_size,
> + int *p_nav_event,
> + void (*flush_cb)(AVFormatContext *s))
> +{
> + 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;
> + int is_cell_promising = 0;
> + pci_t *e_pci;
> + dsi_t *e_dsi;
> +
> + if (buf_size != DVDVIDEO_BLOCK_SIZE) {
> + av_log(s, AV_LOG_ERROR, "Invalid buffer size\n");
you might mention the failing size (Invalid buffer size %d, the DVD block size %d was expected)
> +
> + return AVERROR(ENOMEM);
Not sure this is the correct error (it's not an allocation error)
maybe more invaliddata or EINVAL.
> + }
> +
> + 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(state->dvdnav, nav_buf, &nav_event, &nav_len) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to read next block of PGC\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + /* some discs follow NOPs with a premature stop event */
> + if (nav_event == DVDNAV_STOP)
> + return AVERROR_EOF;
> +
> + if (nav_len > DVDVIDEO_BLOCK_SIZE) {
> + av_log(s, AV_LOG_ERROR, "Invalid block size\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + if (dvdnav_current_title_info(state->dvdnav, &cur_title,
> + &cur_ptt) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to validate title coordinates\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + /* we somehow navigated to a menu */
> + if (cur_title == 0 || !dvdnav_is_domain_vts(state->dvdnav))
> + return AVERROR_EOF;
> +
> + if (dvdnav_current_title_program(state->dvdnav, &cur_title_unused,
> + &cur_pgcn, &cur_pgn) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to validate PGC coordinates\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + /* we somehow left the PGC */
> + if (state->in_pgc && cur_pgcn != state->pgcn)
> + return AVERROR_EOF;
> +
> + if (dvdnav_get_angle_info(state->dvdnav, &cur_angle, &cur_nb_angles) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to validate angle coordinates\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + av_log(s, nav_event == DVDNAV_BLOCK_OK ? AV_LOG_TRACE : AV_LOG_DEBUG,
> + "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, state->celln, cur_pgcn, cur_pgn,
> + state->in_vts, state->in_pgc, state->in_ps);
nit: weird indent
> +
> + switch (nav_event) {
> + case DVDNAV_VTS_CHANGE:
> + if (state->in_vts)
> + return AVERROR_EOF;
> +
> + e_vts = (dvdnav_vts_change_event_t *) nav_buf;
> +
> + if (e_vts->new_vtsN == state->vtsn && e_vts->new_domain == DVD_DOMAIN_VTSTitle)
> + state->in_vts = 1;
> +
> + continue;
> + case DVDNAV_CELL_CHANGE:
> + if (!state->in_vts)
> + continue;
> +
> + e_cell = (dvdnav_cell_change_event_t *) nav_buf;
> + is_cell_promising = dvdvideo_play_is_cell_promising(s, state, e_cell->cellN);
> +
> + av_log(s, AV_LOG_DEBUG, "new cell: prev=%d new=%d promising=%d\n",
> + state->celln, e_cell->cellN, is_cell_promising);
> +
> + if (!state->in_ps && !state->in_pgc) {
> + if (cur_title == c->opt_title && cur_ptt == c->opt_chapter_start
> + && cur_pgcn == state->pgcn && cur_pgn == state->entry_pgn
> + && is_cell_promising) {
nit: weird align
> + state->in_pgc = 1;
> + }
> +
> + if (c->opt_trim && !is_cell_promising)
> + av_log(s, AV_LOG_INFO, "Skipping padding cell #%d\n", e_cell->cellN);
> + } else if (state->celln >= e_cell->cellN || state->pgn > cur_pgn) {
> + return AVERROR_EOF;
> + }
> +
> + state->celln = e_cell->cellN;
> + state->ptt = cur_ptt;
> + state->pgn = cur_pgn;
> +
> + continue;
> + case DVDNAV_NAV_PACKET:
> + if (!state->in_pgc)
> + continue;
> +
> + if ((state->ptt > 0 && state->ptt > cur_ptt)
> + || (c->opt_chapter_end > 0 && cur_ptt > c->opt_chapter_end)) {
> + return AVERROR_EOF;
> + }
> +
> + e_pci = dvdnav_get_current_nav_pci(state->dvdnav);
> + e_dsi = dvdnav_get_current_nav_dsi(state->dvdnav);
> +
> + if (e_pci == NULL || e_dsi == NULL
> + || e_pci->pci_gi.vobu_s_ptm > e_pci->pci_gi.vobu_e_ptm) {
> + av_log(s, AV_LOG_ERROR, "Invalid NAV packet\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + state->vobu_duration = e_pci->pci_gi.vobu_e_ptm - e_pci->pci_gi.vobu_s_ptm;
> + state->pgc_elapsed += state->vobu_duration;
> + state->nav_pts = dvdnav_get_current_time(state->dvdnav);
> + state->ptt = cur_ptt;
> + state->pgn = cur_pgn;
> + state->nb_vobus_played++;
> +
> + av_log(s, AV_LOG_DEBUG, "NAV pack: s_ptm=%d e_ptm=%d "
> + "scr=%d lbn=%d vobu_duration=%d nav_pts=%ld\n",
> + e_pci->pci_gi.vobu_s_ptm, e_pci->pci_gi.vobu_e_ptm,
> + e_dsi->dsi_gi.nv_pck_scr,
> + e_pci->pci_gi.nv_pck_lbn, state->vobu_duration, state->nav_pts);
> +
> + if (!state->in_ps) {
> + av_log(s, AV_LOG_DEBUG, "navigation: locked to program stream\n");
> +
> + state->in_ps = 1;
> + } else {
> + if (state->vobu_e_ptm != e_pci->pci_gi.vobu_s_ptm) {
> + if (flush_cb)
> + flush_cb(s);
> +
> + state->ts_offset += state->vobu_e_ptm - e_pci->pci_gi.vobu_s_ptm;
> + }
> + }
> +
> + state->vobu_e_ptm = e_pci->pci_gi.vobu_e_ptm;
> +
> + (*p_nav_event) = nav_event;
> +
> + return nav_len;
> + case DVDNAV_BLOCK_OK:
> + if (!state->in_ps) {
> + if (state->in_pgc)
> + i = 0; /* necessary in case we are skipping junk cells at the beginning */
> + continue;
> + }
> +
> + if (nav_len != DVDVIDEO_BLOCK_SIZE) {
> + av_log(s, AV_LOG_ERROR, "Invalid block size\n");
> +
> + 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);
> +
> + /* in case NAV packet is missed */
> + state->ptt = cur_ptt;
> + state->pgn = cur_pgn;
> +
> + (*p_nav_event) = nav_event;
> +
> + return nav_len;
> + case DVDNAV_STILL_FRAME:
> + case DVDNAV_WAIT:
> + case DVDNAV_HOP_CHANNEL:
> + case DVDNAV_HIGHLIGHT:
> + if (state->in_ps)
> + return AVERROR_EOF;
> +
> + if (nav_event == DVDNAV_STILL_FRAME)
> + dvdnav_still_skip(state->dvdnav);
> + if (nav_event == DVDNAV_WAIT)
> + dvdnav_wait_skip(state->dvdnav);
> +
> + 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_INVALIDDATA;
> +}
> +
> +static int dvdvideo_pgc_preindex(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret = 0, interrupt = 0;
> + int nb_chapters = 0, last_ptt = c->opt_chapter_start;
> + uint64_t cur_chapter_offset = 0, cur_chapter_duration = 0;
> + DVDVideoPlaybackState *state;
> +
> + uint8_t nav_buf[DVDVIDEO_BLOCK_SIZE];
> + int nav_event;
> +
> + if (c->opt_chapter_start == c->opt_chapter_end)
> + return 0;
> +
> + av_log(s, AV_LOG_INFO,
> + "Indexing chapter markers, this will take a long time. Please wait...\n");
> +
> + state = av_mallocz(sizeof(DVDVideoPlaybackState));
> + if ((ret = dvdvideo_play_open(s, state)) < 0) {
> + av_freep(&state);
> + return ret;
> + }
> +
> + while (!(interrupt = ff_check_interrupt(&s->interrupt_callback))) {
> + ret = dvdvideo_play_next_ps_block(s, state, nav_buf, DVDVIDEO_BLOCK_SIZE,
> + &nav_event, NULL);
> + if (ret < 0 && ret != AVERROR_EOF)
> + goto end_free;
> +
> + if (nav_event != DVDNAV_NAV_PACKET && ret != AVERROR_EOF)
> + continue;
> +
> + if (state->ptt == last_ptt) {
> + cur_chapter_duration += state->vobu_duration;
> + /* ensure we add the last chapter */
> + if (ret != AVERROR_EOF)
> + continue;
> + }
> +
> + if (!avpriv_new_chapter(s, nb_chapters, DVDVIDEO_TIME_BASE_Q, cur_chapter_offset,
> + cur_chapter_offset + cur_chapter_duration, NULL)) {
> + av_log(s, AV_LOG_ERROR, "Unable to allocate chapter\n");
ret is not set in this case
> +
> + goto end_free;
> + }
> +
> + nb_chapters++;
> + cur_chapter_offset += cur_chapter_duration;
> + cur_chapter_duration = state->vobu_duration;
> + last_ptt = state->ptt;
> +
> + if (ret == AVERROR_EOF)
> + break;
> + }
> +
> + if (interrupt) {
> + ret = AVERROR_EXIT;
> + goto end_free;
> + }
> +
> + if (ret < 0 && ret != AVERROR_EOF)
> + goto end_free;
> +
> + s->duration = av_rescale_q(state->pgc_elapsed, DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
> + c->duration_ptm = state->pgc_elapsed;
> +
> + av_log(s, AV_LOG_INFO, "Chapter marker indexing complete\n");
> + ret = 0;
> +
> +end_free:
> + dvdvideo_play_close(s, state);
> + av_freep(&state);
> +
> + return ret;
> +}
> +
> +static int dvdvideo_pgc_chapters_setup(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + uint64_t time_prev = 0;
> + int64_t total_duration = 0;
> +
> + int chapter_start = c->opt_chapter_start - 1;
> + int chapter_end = c->opt_chapter_end > 0 ? c->opt_chapter_end : c->play_state.pgc_nb_pg_est - 1;
> +
> + if (chapter_start == chapter_end || c->play_state.pgc_nb_pg_est == 1) {
> + s->duration = av_rescale_q(c->play_state.pgc_duration_est,
> + DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
> + return 0;
> + }
> +
> + for (int i = chapter_start; i < chapter_end; i++) {
> + uint64_t time_effective = c->play_state.pgc_pg_times_est[i] - c->play_state.nav_pts;
> +
> + 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;
> + total_duration = time_effective;
> + }
> +
> + if (c->opt_chapter_start == 1 && c->opt_chapter_end == 0)
> + s->duration = av_rescale_q(c->play_state.pgc_duration_est,
> + DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
> + else
> + s->duration = av_rescale_q(total_duration,
> + DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
> +
> + return 0;
> +}
> +
> +static int dvdvideo_video_stream_analyze(AVFormatContext *s, 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) {
> + av_log(s, AV_LOG_ERROR, "Invalid video dimensions\n");
you might print the actual size to help debug impossible cases
> +
> + 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 = !is_pal && (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 = NULL;
> + int ret = 0;
> +
> + entry = av_mallocz(sizeof(DVDVideoVTSVideoStreamEntry));
you might check for failure
> +
> + if ((ret = dvdvideo_video_stream_analyze(s, 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_HEADERS)) < 0) {
> + av_freep(&entry);
> + av_log(s, AV_LOG_ERROR, "Unable to allocate video stream\n");
misleading error message, I'd say: Unable to add video stream
> +
> + return ret;
> + }
> +
> + av_freep(&entry);
> +
> + return ret;
> +}
> +
> +static int dvdvideo_audio_stream_analyze(AVFormatContext *s, 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 = (AVChannelLayout) {0};
> + char lang_dvd[3] = {0};
> +
> + int position = (audio_control & 0x7F00) >> 8;
> +
> + /* XXX(PATCHWELCOME): SDDS is not supported due to lack of sample material */
> + switch (audio_attr.audio_format) {
> + case 0: /* AC3 */
> + codec_id = AV_CODEC_ID_AC3;
> + sample_fmt = AV_SAMPLE_FMT_FLTP;
> + sample_rate = 48000;
> + startcode = 0x80 + position;
> + break;
> + case 2: /* MP1 */
> + 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: /* MP2 */
> + 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: /* DVD PCM */
> + codec_id = AV_CODEC_ID_PCM_DVD;
> + 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: /* DCA */
> + 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) {
> + av_log(s, AV_LOG_ERROR, "Invalid audio parameters\n");
please provide some context to help debug impossible cases
> +
> + 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 == 7)
> + ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_6POINT1;
> + else if (nb_channels == 8)
> + ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_7POINT1;
> +
> + if (audio_attr.code_extension == 2)
> + entry->disposition |= AV_DISPOSITION_VISUAL_IMPAIRED;
> + if (audio_attr.code_extension == 3 || audio_attr.code_extension == 4)
> + entry->disposition |= AV_DISPOSITION_COMMENT;
> +
> + 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;
> + st->disposition = entry->disposition;
> +
> + 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 = 0;
> +
> + for (int i = 0; i < c->vts_ifo->vtsi_mat->nr_of_vts_audio_streams; i++) {
> + DVDVideoPGCAudioStreamEntry *entry = NULL;
> +
> + if (!(c->play_state.pgc->audio_control[i] & 0x8000))
> + continue;
> +
> + entry = av_mallocz(sizeof(DVDVideoPGCAudioStreamEntry));
> + if (!entry)
> + return AVERROR(ENOMEM);
> +
> + if ((ret = dvdvideo_audio_stream_analyze(s, c->vts_ifo->vtsi_mat->vts_audio_attr[i],
> + c->play_state.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, "Karaoke metadata in stream %d will not be retained\n",
> + entry->startcode);
> +
> + /* IFO structures can declare duplicate entries for the same 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
> + || (ret = dvdvideo_audio_stream_add(s, entry, AVSTREAM_PARSE_HEADERS)) < 0)
> + goto break_free_and_error;
> +
> +continue_free:
> + av_freep(&entry);
> + continue;
> +
> +break_free_and_error:
> + av_freep(&entry);
> + av_log(s, AV_LOG_ERROR, "Unable to allocate audio stream\n");
ditto: unable to "add" audio stream?
> + return ret;
> + }
> +
> + return ret;
> +}
> +
> +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);
> +
> + if (subp_attr.lang_extension == 9)
> + entry->disposition |= AV_DISPOSITION_FORCED;
> +
> + 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;
> + int ret = 0;
> +
> + 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;
> +
> + if (entry->lang_iso)
> + av_dict_set(&st->metadata, "language", entry->lang_iso, 0);
> +
> + av_dict_set(&st->metadata, "VIEWPORT", VIEWPORT_LABELS[entry->viewport], 0);
> +
> + st->disposition = entry->disposition;
> +
> + 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 ret;
> +}
> +
> +static int dvdvideo_subp_stream_add_internal(AVFormatContext *s, uint32_t offset,
> + subp_attr_t subp_attr,
> + enum DVDVideoSubpictureViewport viewport)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + DVDVideoPGCSubtitleStreamEntry *entry = NULL;
> + int ret = 0;
> +
> + entry = av_mallocz(sizeof(DVDVideoPGCSubtitleStreamEntry));
missing check
> + entry->viewport = viewport;
> +
> + if ((ret = dvdvideo_subp_stream_analyze(s, offset, subp_attr, entry)) < 0)
> + goto end_free_error;
> +
> + /* IFO structures can declare duplicate entries for the same startcode */
> + 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_HEADERS)) < 0)
> + goto end_free_error;
> +
> + goto end_free;
> +
> +end_free_error:
> + av_log(s, AV_LOG_ERROR, "Unable to allocate subtitle stream\n");
ditto
> +
> +end_free:
> + av_freep(&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 = 0;
> + uint32_t subp_control;
> + subp_attr_t subp_attr;
> + video_attr_t video_attr;
> +
> + subp_control = c->play_state.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,
> + DVDVIDEO_SUBP_VIEWPORT_FULLSCREEN)) < 0)
> + return ret;
> +
> + continue;
> + }
> +
> + /* 16:9 */
> + if ((ret = dvdvideo_subp_stream_add_internal(s, subp_control >> 16, subp_attr,
> + DVDVIDEO_SUBP_VIEWPORT_WIDESCREEN)) < 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,
> + DVDVIDEO_SUBP_VIEWPORT_LETTERBOX)) < 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,
> + DVDVIDEO_SUBP_VIEWPORT_PANSCAN)) < 0)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void dvdvideo_subdemux_flush(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + if (!c->play_seg_started)
> + return;
> +
> + av_log(s, AV_LOG_DEBUG, "flushing sub-demuxer\n");
> + avio_flush(&c->mpeg_pb.pub);
> + ff_read_frame_flush(c->mpeg_ctx);
> + c->play_seg_started = 0;
> +}
> +
> +static int dvdvideo_subdemux_read_data(void *opaque, uint8_t *buf, int buf_size)
> +{
> + AVFormatContext *s = opaque;
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret = 0;
> + int nav_event;
> +
> + if (c->play_end)
> + return AVERROR_EOF;
> +
> + ret = dvdvideo_play_next_ps_block(opaque, &c->play_state, buf, buf_size,
> + &nav_event, dvdvideo_subdemux_flush);
> +
> + if (ret == AVERROR_EOF) {
> + c->mpeg_pb.pub.eof_reached = 1;
> + c->play_end = 1;
> +
> + return AVERROR_EOF;
> + }
> +
> + if (ret >= 0 && nav_event == DVDNAV_NAV_PACKET)
> + return FFERROR_REDO;
> +
> + return ret;
> +}
> +
> +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 = 0;
> +
> + if (!(c->mpeg_fmt = av_find_input_format("mpeg")))
> + return AVERROR_DEMUXER_NOT_FOUND;
> +
> + if (!(c->mpeg_ctx = avformat_alloc_context()))
> + return AVERROR(ENOMEM);
> +
> + if (!(c->mpeg_buf = av_mallocz(DVDVIDEO_BLOCK_SIZE))) {
> + avformat_free_context(c->mpeg_ctx);
> +
> + return AVERROR(ENOMEM);
> + }
> +
> + ffio_init_context(&c->mpeg_pb, c->mpeg_buf, DVDVIDEO_BLOCK_SIZE, 0, s,
> + dvdvideo_subdemux_read_data, NULL, NULL);
> + c->mpeg_pb.pub.seekable = 0;
> +
> + if ((ret = ff_copy_whiteblacklists(c->mpeg_ctx, s)) < 0) {
> + avformat_free_context(c->mpeg_ctx);
> +
> + return ret;
> + }
> +
> + c->mpeg_ctx->flags = AVFMT_FLAG_CUSTOM_IO | AVFMT_FLAG_GENPTS;
> + c->mpeg_ctx->ctx_flags |= AVFMTCTX_UNSEEKABLE;
> + c->mpeg_ctx->probesize = 0;
> + c->mpeg_ctx->max_analyze_duration = 0;
> + c->mpeg_ctx->interrupt_callback = s->interrupt_callback;
> + c->mpeg_ctx->pb = &c->mpeg_pb.pub;
> + c->mpeg_ctx->io_open = NULL;
> +
> + if ((ret = avformat_open_input(&c->mpeg_ctx, "", c->mpeg_fmt, NULL)) < 0) {
> + avformat_free_context(c->mpeg_ctx);
> +
> + return ret;
you can drop this
> + }
> +
> + return ret;
> +}
> +
> +static int dvdvideo_read_header(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret = 0;
> +
> + if (c->opt_title == 0) {
> + av_log(s, AV_LOG_INFO, "Defaulting to title #1. "
> + "This is not always the main feature, validation suggested.\n");
> +
> + c->opt_title = 1;
> + }
> +
> + if (c->opt_pgc) {
> + if (c->opt_pg == 0)
> + av_log(s, AV_LOG_ERROR, "Invalid coordinates. If -pgc is set, -pg must be set too.\n");
> + else if (c->opt_chapter_start > 1 || c->opt_chapter_end > 0 || c->opt_preindex)
> + av_log(s, AV_LOG_ERROR, "-pgc is not compatible with the -preindex or "
> + "-chapter_start/-chapter_end options\n");
> +
> + return AVERROR(EINVAL);
> + }
> +
> + if ((ret = dvdvideo_ifo_open(s)) < 0)
> + return ret;
> +
> + if (c->opt_preindex && (ret = dvdvideo_pgc_preindex(s)) < 0)
> + return ret;
> +
> + if ((ret = dvdvideo_play_open(s, &c->play_state)) < 0
> + || (ret = dvdvideo_subdemux_open(s)) < 0
> + || (ret = dvdvideo_video_stream_setup(s)) < 0
> + || (ret = dvdvideo_audio_stream_add_all(s)) < 0
> + || (ret = dvdvideo_subp_stream_add_all(s)) < 0)
> + return ret;
> +
> + if (!c->opt_preindex)
> + return dvdvideo_pgc_chapters_setup(s);
> +
> + return ret;
> +}
> +
> +static int dvdvideo_read_packet(AVFormatContext *s, AVPacket *pkt)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret;
> + enum AVMediaType st_type;
> + int64_t cur_delta = 0;
> +
> + if (c->play_end)
> + return AVERROR_EOF;
> +
> + ret = av_read_frame(c->mpeg_ctx, pkt);
> +
> + if (ret < 0)
> + return ret;
> +
> + if (!c->play_seg_started)
> + c->play_seg_started = 1;
> +
> + if (pkt->stream_index >= s->nb_streams) {
> + av_log(s, AV_LOG_DEBUG, "discarding frame with unknown stream\n");
print the unrecongnized stream index
> + goto skip_redo;
> + }
> +
> + st_type = c->mpeg_ctx->streams[pkt->stream_index]->codecpar->codec_type;
> +
> + if (!c->play_started) {
> + if (st_type != AVMEDIA_TYPE_VIDEO || pkt->pts == AV_NOPTS_VALUE
> + || pkt->dts == AV_NOPTS_VALUE
> + || !(pkt->flags & AV_PKT_FLAG_KEY)) {
> + av_log(s, AV_LOG_DEBUG, "discarding non-video-keyframe at start\n");
of packet with unset PTS/DTS at start
> + goto skip_redo;
> + }
> +
> + c->first_pts = pkt->pts;
> + c->play_started = 1;
> +
> + pkt->pts = 0;
> + pkt->dts = c->play_state.ts_offset - pkt->pts;
> + } else {
> + cur_delta = c->play_state.ts_offset - c->first_pts;
> +
> + if (c->play_state.nb_vobus_played == 1 && (pkt->pts == AV_NOPTS_VALUE
> + || pkt->dts == AV_NOPTS_VALUE
> + || (pkt->pts + cur_delta) < 0)) {
> + av_log(s, AV_LOG_DEBUG, "discarding frame with negative or unset timestamp "
> + "(this is OK at the start of the first playback VOBU)\n");
> + goto skip_redo;
> + }
> +
> + if (pkt->pts != AV_NOPTS_VALUE)
> + pkt->pts += cur_delta;
> + if (pkt->dts != AV_NOPTS_VALUE)
> + pkt->dts += cur_delta;
> + }
> +
> + av_log(s, AV_LOG_TRACE, "st=%d pts=%ld dts=%ld ts_offset=%ld first_pts=%ld\n",
> + pkt->stream_index, pkt->pts, pkt->dts,
> + c->play_state.ts_offset, c->first_pts);
> + if (pkt->pts < 0)
> + av_log(s, AV_LOG_WARNING, "Invalid frame PTS @ st=%d pts=%ld dts=%ld\n",
> + pkt->stream_index, pkt->pts, pkt->dts);
> +
> + return c->play_end ? AVERROR_EOF : 0;
> +
> +skip_redo:
> + av_packet_unref(pkt);
> + return FFERROR_REDO;
> +}
> +
> +static int dvdvideo_close(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + dvdvideo_subdemux_close(s);
> + dvdvideo_play_close(s, &c->play_state);
> + dvdvideo_ifo_close(s);
> +
> + return 0;
> +}
> +
> +#define OFFSET(x) offsetof(DVDVideoDemuxContext, x)
> +static const AVOption dvdvideo_options[] = {
> + {"angle", "playback angle number", OFFSET(opt_angle), AV_OPT_TYPE_INT, { .i64=1 }, 1, 9, AV_OPT_FLAG_DECODING_PARAM },
> + {"chapter_end", "exit chapter (PTT) number (0=end)", OFFSET(opt_chapter_end), AV_OPT_TYPE_INT, { .i64=0 }, 0, 99, AV_OPT_FLAG_DECODING_PARAM },
> + {"chapter_start", "entry chapter (PTT) number", OFFSET(opt_chapter_start), AV_OPT_TYPE_INT, { .i64=1 }, 1, 99, 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 },
> + {"pgc", "entry PGC number (0=auto)", OFFSET(opt_pgc), AV_OPT_TYPE_INT, { .i64=0 }, 0, 999, AV_OPT_FLAG_DECODING_PARAM },
> + {"preindex", "enable for accurate chapter markers, slow (2-pass read)", OFFSET(opt_preindex), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, 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 },
> + {"title", "title number (0=auto)", OFFSET(opt_title), AV_OPT_TYPE_INT, { .i64=0 }, 0, 99, AV_OPT_FLAG_DECODING_PARAM },
> + {"trim", "trim padding cells from start", OFFSET(opt_trim), AV_OPT_TYPE_BOOL, { .i64=1 }, 0, 1, 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_close = dvdvideo_close,
> + .read_header = dvdvideo_read_header,
> + .read_packet = dvdvideo_read_packet
> +};
LGTM otherwise, thanks.
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH v7 1/2] libavformat/dvdvideo: add DVD-Video demuxer powered by libdvdnav and libdvdread
2024-02-07 0:52 ` [FFmpeg-devel] [PATCH v7 1/2] libavformat/dvdvideo: add DVD-Video demuxer powered by libdvdnav and libdvdread Stefano Sabatini
@ 2024-02-07 0:54 ` Marth64
0 siblings, 0 replies; 27+ messages in thread
From: Marth64 @ 2024-02-07 0:54 UTC (permalink / raw)
To: FFmpeg development discussions and patches, Marth64
Thank you for your time and detailed review. I will clean all this up and
send v9
On Tue, Feb 6, 2024 at 6:53 PM Stefano Sabatini <stefasab@gmail.com> wrote:
> On date Sunday 2024-02-04 23:48:25 -0600, Marth64 wrote:
> > Sorry for the quick follow-up to v6, I wanted to get this update out now
> hopefully
> > before v6 is actually reviewed. v7 fixes a few documentation typos,
> options descriptions,
> > and log messages: nothing major. I also flattened this out to be a
> 2-patch set
> > with subtitle palette support.
> >
> > This is the result of >1yr of research and I am now very satisfied with
> it's result.
> > I will not make any more changes at this point unless it is outcome from
> a review.
> > If the community decides to merge it, I am happy to continue to improve
> it with support for
> > seeking, probing, and tests.
> >
> > Thank you so much,
> >
> >
> > Signed-off-by: Marth64 <marth64@proxyid.net>
> > ---
> > Changelog | 2 +
> > configure | 8 +
> > doc/demuxers.texi | 129 ++++
> > libavformat/Makefile | 1 +
> > libavformat/allformats.c | 1 +
> > libavformat/dvdvideodec.c | 1410 +++++++++++++++++++++++++++++++++++++
> > 6 files changed, 1551 insertions(+)
> > create mode 100644 libavformat/dvdvideodec.c
> >
> > diff --git a/Changelog b/Changelog
> > index c5fb21d198..88653bc6d3 100644
> > --- a/Changelog
> > +++ b/Changelog
> > @@ -24,6 +24,8 @@ version <next>:
> > - ffmpeg CLI options may now be used as -/opt <path>, which is
> equivalent
> > to -opt <contents of file <path>>
> > - showinfo bitstream filter
> > +- DVD-Video demuxer, powered by libdvdnav and libdvdread
> > +
> >
> > version 6.1:
> > - libaribcaption decoder
> > diff --git a/configure b/configure
> > index 68f675a4bc..70c33ec96d 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
> > @@ -3520,6 +3524,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"
> > @@ -6761,6 +6767,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/doc/demuxers.texi b/doc/demuxers.texi
> > index e4c5b560a6..ad0906f6ec 100644
> > --- a/doc/demuxers.texi
> > +++ b/doc/demuxers.texi
> > @@ -285,6 +285,135 @@ This demuxer accepts the following option:
> >
> > @end table
> >
> > +@section dvdvideo
> > +
> > +DVD-Video demuxer, powered by libdvdnav and libdvdread.
> > +
> > +Can directly ingest DVD titles, specifically sequential PGCs,
> > +into a conversion pipeline. Menus and seeking are not supported at this
> time.
> > +
> > +Block devices (DVD drives), ISO files, and directory structures are
> accepted.
> > +Activate with @code{-f dvdvideo} in front of one of these inputs.
> > +
> > +Underlying playback is fully handled by libdvdnav, and structure
> parsing by libdvdread.
>
> > +ffmpeg must be built with GPL library support available as well as the
> switches
> > +@code{--enable-libdvdnav} and @code{--enable-libdvdread}.
>
> FFmpeg must be built with GPL library support available as well as the
> configure switches @code{--enable-libdvdnav} and
> @code{--enable-libdvdread}.
>
> > +
> > +You will need to provide either the desired "title number" or exact
> PGC/PG coordinates.
>
> > +Many open-source DVD players and tools can aid in providing this
> information.
>
> unrelated, but it might be useful to have a -list-titles option to
> show this information
>
> > +If not specified, the demuxer will default to title 1 which works for
> many discs.
> > +However, due to the flexibility of DVD-Video, it is recommended to
> check manually.
> > +There are many discs that are authored strangely or with invalid
> headers.
> > +
> > +If the input is a real DVD drive, please note that there are some
> drives which may
> > +silently fail on reading bad sectors from the disc, returning random
> bits instead
> > +which is effectively corrupt data. This is especially prominent on
> aging or rotting discs.
> > +A second pass and integrity checks would be needed to detect the
> corruption.
>
> > +This is not an ffmpeg issue.
>
> nit: FFmpeg (ffmpeg is usually a reference to the CLI command, while
> FFmpeg refers to the projects and set of all libraries/tools)
>
> > +
> > +@subsection Background
> > +
> > +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 MPEG-PS streams that are stored in headerless VOB files.
> > +To the end-user, these streams are known simply as "titles", but the
> actual
> > +logical playback sequence is defined by one or more "PGCs", or Program
> Group Chains,
> > +within the title. The PGC is in turn comprised of multiple "PGs", or
> Programs",
> > +which are the actual video segments which, for a typical movie, are
> sequentially
> > +ordered. The PGC structure, along with stream layout and metadata, are
> stored in
> > +IFO files that need to be parsed. PGCs can be thought of as playlists
> in easier terms.
> > +
> > +An actual DVD player relies on user GUI interaction via menus and an
> internal VM
> > +to drive the direction of demuxing. Generally, the user would either
> navigate (via menus)
> > +or automatically be redirected to the PGC of their choice. During this
> process and
> > +the subsequent playback, the DVD player's internal VM also maintains a
> state and
> > +executes instructions that can create jumps to different sectors during
> playback.
> > +This is why libdvdnav is involved, as a linear read of the MPEG-PS
> blobs on the
> > +disc (VOBs) is not enough to produce the right sequence in many cases.
> > +
> > +There are many other DVD structures (a long subject) that will not be
> discussed here.
> > +NAV packets, in particular, are handled by this demuxer to build
> accurate timing
> > +but not emitted as a stream. For a good high-level understanding, refer
> to:
> > +@url{
> https://code.videolan.org/videolan/libdvdnav/-/blob/master/doc/dvd_structures
> }
> > +
> > +@subsection Options
> > +
> > +This demuxer accepts the following options:
> > +
> > +@table @option
> > +
> > +@item title
>
> here and below: you might use @var{TYPE} to denote the argument/type of the
> option
>
> > +The title number to play. Must be set if @option{pgc} and @option{pg}
> are not set.
> > +Default is 0 (auto), which currently only selects the first available
> title (title 1)
> > +and notifies the user about the implications.
> > +
> > +@item chapter_start
> > +The chapter, or PTT (part-of-title), number to start at. Default is 1.
> > +
> > +@item chapter_end
> > +The chapter, or PTT (part-of-title), number to end at. Default is 0,
> > +which is a special value to signal end at the last possible chapter.
> > +
> > +@item angle
> > +The video angle number, referring to what is essentially an additional
> > +video stream that is composed from alternate frames interleaved in the
> VOBs.
> > +Default is 1.
> > +
> > +@item region
> > +The region code to use for playback. Some discs may use this to default
> playback
> > +at a particular angle in different regions. This option will not affect
> the region code
> > +of a real DVD drive, if used as an input. Default is 0, "world".
> > +
> > +@item pgc
> > +The entry PGC to start playback, in conjunction with @option{pg}.
> > +Alternative to setting @option{title}.
>
> > +Chapter markers not supported at this time.
>
> nit: are not supported ...
>
> > +Default is 0, automatically resolve from value of @option{title}.
> > +
> > +@item pg
> > +The entry PG to start playback, in conjunction with @option{pgc}.
> > +Alternative to setting @option{title}.
> > +Chapter markers not supported at this time.
>
> are not
>
> > +Default is 0, automatically resolve from value of @option{title}.
> > +
> > +@item preindex
> > +Enable this to have accurate chapter (PTT) markers and duration
> measurement,
> > +which requires a slow second pass read in order to index the chapter
>
> > +timestamps from NAV packets. This is non-ideal extra work for physical
> DVD drives.
>
> note: if this is non-idela for physical DVD drivers, what's the
> recommended use case?
>
> > +Not compatible with @option{pgc} and @option{pg}.
> > +Default is 0, false.
> > +
> > +@item trim
> > +Skip padding cells (i.e. cells shorter than 1 second) from the
> beginning.
> > +There exist many discs with filler segments at the beginning of the PGC,
> > +often with junk data intended for controlling a real DVD player's
> > +buffering speed and with no other material data value.
> > +Default is 1, true.
> > +
> > +@end table
> > +
> > +@subsection Examples
> > +
> > +@itemize
> > +@item
> > +Open title 3 from a given DVD structure:
> > +@example
> > +ffmpeg -f dvdvideo -title 3 -i <path to DVD> ...
> > +@end example
> > +
> > +@item
> > +Open chapters 3-6 from title 1 from a given DVD structure:
> > +@example
> > +ffmpeg -f dvdvideo -chapter_start 3 -chapter_end 6 -title 1 -i <path to
> DVD> ...
> > +@end example
> > +
> > +@item
> > +Open only chapter 5 from title 1 from a given DVD structure:
> > +@example
> > +ffmpeg -f dvdvideo -chapter_start 5 -chapter_end 5 -title 1 -i <path to
> DVD> ...
> > +@end example
> > +@end itemize
> > +
> > @section ea
> >
> > Electronic Arts Multimedia format demuxer.
> > diff --git a/libavformat/Makefile b/libavformat/Makefile
> > index 05b9b8a115..df69734877 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 b04b43cab3..3e905c23f8 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/dvdvideodec.c b/libavformat/dvdvideodec.c
> > new file mode 100644
> > index 0000000000..2e363a4a22
> > --- /dev/null
> > +++ b/libavformat/dvdvideodec.c
> > @@ -0,0 +1,1410 @@
> > +/*
> > + * DVD-Video demuxer, powered by libdvdnav and libdvdread
> > + *
> > + * 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
> > + */
> > +
> > +/*
> > + * See doc/demuxers.texi for a high-level overview.
> > + *
> > + * The tactical approach is as follows:
> > + * 1) Open the volume with dvdread
> > + * 2) Analyze the user-requested title and PGC coordinates in the IFO
> structures
> > + * 3) Request playback at the coordinates and chosen angle with dvdnav
> > + * 5) Begin the playback (reading and demuxing) of MPEG-PS blocks
> > + * 6) End playback if navigation goes backwards, to a menu, or a
> different PGC or angle
> > + * 7) Close the dvdnav VM, and free dvdread's IFO structures
> > + */
> > +
> > +#include <dvdnav/dvdnav.h>
> > +#include <dvdread/dvd_reader.h>
> > +#include <dvdread/ifo_read.h>
> > +#include <dvdread/ifo_types.h>
> > +#include <dvdread/nav_read.h>
> > +
> > +#include "libavcodec/avcodec.h"
> > +#include "libavutil/avstring.h"
> > +#include "libavutil/avutil.h"
> > +#include "libavutil/intreadwrite.h"
> > +#include "libavutil/mem.h"
> > +#include "libavutil/opt.h"
> > +#include "libavutil/samplefmt.h"
> > +#include "libavutil/time.h"
> > +#include "libavutil/timestamp.h"
> > +
> > +#include "avformat.h"
> > +#include "avio_internal.h"
> > +#include "avlanguage.h"
> > +#include "demux.h"
> > +#include "internal.h"
> > +#include "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 64 /* VOBUs use
> 32 (PES allows 33) */
> > +#define DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE 1024
> > +
> > +enum DVDVideoSubpictureViewport {
> > + DVDVIDEO_SUBP_VIEWPORT_FULLSCREEN,
> > + DVDVIDEO_SUBP_VIEWPORT_WIDESCREEN,
> > + DVDVIDEO_SUBP_VIEWPORT_LETTERBOX,
> > + DVDVIDEO_SUBP_VIEWPORT_PANSCAN
> > +};
> > +const char* VIEWPORT_LABELS[4] = { "Fullscreen", "Widescreen",
> "Letterbox", "Pan and Scan" };
> > +
> > +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;
> > + int disposition;
> > + char *lang_iso;
> > +} DVDVideoPGCAudioStreamEntry;
> > +
> > +typedef struct DVDVideoPGCSubtitleStreamEntry {
> > + int startcode;
> > + int disposition;
> > + char *lang_iso;
> > + enum DVDVideoSubpictureViewport viewport;
> > +} DVDVideoPGCSubtitleStreamEntry;
> > +
> > +typedef struct DVDVideoPlaybackState {
> > + int celln; /* ID of the active
> cell */
> > + int entry_pgn; /* ID of the PG we
> are starting in */
> > + int in_pgc; /* if our navigator
> is in the PGC */
> > + int in_ps; /* if our navigator
> is in the program stream */
> > + int in_vts; /* if our navigator
> is in the VTS */
> > + int64_t nav_pts; /* PTS according to
> IFO, not frame-accurate */
> > + int nb_vobus_played; /* number of VOBUs
> played back so far */
> > + uint64_t pgc_duration_est; /* estimated
> duration as reported by IFO */
> > + uint64_t pgc_elapsed; /* the elapsed time
> of the PGC, cell-relative */
> > + int pgc_nb_pg_est; /* number of PGs as
> reported by IFOs */
> > + int pgcn; /* ID of the PGC we
> are playing */
> > + int pgn; /* ID of the PG we
> are in now */
> > + int ptt; /* ID of the
> chapter we are in now */
> > + int64_t ts_offset; /* PTS
> discontinuity offset (ex. VOB change) */
> > + uint32_t vobu_duration; /* duration of the
> current VOBU */
> > + uint32_t vobu_e_ptm; /* end PTS of the
> current VOBU */
> > + int vtsn; /* ID of the active
> VTS (video title set) */
> > + uint64_t *pgc_pg_times_est; /* PG start times
> as reported by IFO */
> > + pgc_t *pgc; /* handle to the
> active PGC */
> > + dvdnav_t *dvdnav; /* handle to
> libdvdnav */
> > +} DVDVideoPlaybackState;
> > +
> > +typedef struct DVDVideoDemuxContext {
> > + const AVClass *class;
> > +
> > + /* options */
> > + int opt_title; /* the
> user-provided title number (1-indexed) */
> > + int opt_chapter_start; /* the
> user-provided entry PTT (1-indexed) */
> > + int opt_chapter_end; /* the
> user-provided exit PTT (0 for last) */
> > + 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 */
> > + int opt_preindex; /* pre-indexing
> mode (2-pass read) */
> > + int opt_trim; /* trim padding
> cells at beginning and end */
> > +
> > + /* 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) */
> > +
> > + /* playback control */
> > + int play_end; /* signal EOF to
> the parent demuxer */
> > + DVDVideoPlaybackState play_state; /* the active
> playback state */
> > + int play_started; /* signal that
> playback has started */
> > + int play_seg_started; /* signal that
> segment has started */
> > + int64_t first_pts; /* the starting PTS
> offset of the PGC */
> > + uint64_t duration_ptm; /* total duration
> in DVD MPEG timebase */
> > +} DVDVideoDemuxContext;
> > +
> > +static void dvdvideo_libdvdread_log(void *opaque, dvd_logger_level_t
> level,
> > + const char *msg, va_list msg_va)
> > +{
> > + AVFormatContext *s = opaque;
> > + int lavu_level;
> > +
> > + char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
> > + vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
> > +
> > + switch (level) {
> > + case DVD_LOGGER_LEVEL_ERROR:
> > + lavu_level = AV_LOG_ERROR;
> > + break;
> > + case DVD_LOGGER_LEVEL_WARN:
> > + lavu_level = AV_LOG_WARNING;
> > + break;
> > + /* dvdread's info messages are relatively very verbose, muffle
> them as debug */
> > + case DVD_LOGGER_LEVEL_INFO:
> > + case DVD_LOGGER_LEVEL_DEBUG:
> > + default:
> > + lavu_level = AV_LOG_DEBUG;
> > + }
> > +
> > + av_log(s, lavu_level, "libdvdread: %s\n", msg_buf);
> > +}
> > +
> > +static void dvdvideo_libdvdnav_log(void *opaque, dvdnav_logger_level_t
> level,
> > + const char *msg, va_list msg_va)
> > +{
> > + AVFormatContext *s = opaque;
> > + int lavu_level;
> > +
> > + char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
> > + vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
> > +
> > + switch (level) {
> > + case DVDNAV_LOGGER_LEVEL_ERROR:
> > + lavu_level = AV_LOG_ERROR;
> > + break;
> > + case DVDNAV_LOGGER_LEVEL_WARN:
> > + /* some discs have invalid language codes set for their
> menus, muffle the noise */
> > + lavu_level = av_strstart(msg, "Language", NULL) ?
> AV_LOG_DEBUG : AV_LOG_WARNING;
> > + break;
> > + /* dvdnav's info messages are relatively very verbose, muffle
> them as debug */
> > + case DVDNAV_LOGGER_LEVEL_INFO:
> > + case DVDNAV_LOGGER_LEVEL_DEBUG:
> > + default:
> > + lavu_level = AV_LOG_DEBUG;
> > + }
> > +
> > + av_log(s, lavu_level, "libdvdnav: %s\n", msg_buf);
> > +}
> > +
> > +static void dvdvideo_ifo_close(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + 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_ifo_open(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + dvd_logger_cb dvdread_log_cb;
> > + title_info_t title_info;
> > +
> > + dvdread_log_cb = (dvd_logger_cb) { .pf_log =
> dvdvideo_libdvdread_log };
> > + c->dvdread = DVDOpen2(s, &dvdread_log_cb, s->url);
> > +
> > + if (!c->dvdread) {
> > + av_log(s, AV_LOG_ERROR, "Unable to open the DVD-Video
> structure\n");
> > +
> > + return AVERROR_EXTERNAL;
> > + }
> > +
> > + if (!(c->vmg_ifo = ifoOpen(c->dvdread, 0))) {
> > + av_log(s, AV_LOG_ERROR, "Unable to open the VMG
> (VIDEO_TS.IFO)\n");
> > +
> > + return AVERROR_EXTERNAL;
> > + }
> > +
> > + if (c->opt_title > c->vmg_ifo->tt_srpt->nr_of_srpts) {
> > + av_log(s, AV_LOG_ERROR, "Title %d not found\n", c->opt_title);
> > +
> > + 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 %d not found\n", c->opt_angle);
> > +
> > + return AVERROR_STREAM_NOT_FOUND;
> > + }
> > +
> > + if (title_info.nr_of_ptts < 1) {
> > + av_log(s, AV_LOG_ERROR, "Title %d has invalid headers (no
> parts)\n", c->opt_title);
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + if (c->opt_chapter_start > title_info.nr_of_ptts
> > + || (c->opt_chapter_end > 0 && c->opt_chapter_end >
> title_info.nr_of_ptts)) {
> > + av_log(s, AV_LOG_ERROR, "Chapter (PTT) range [%d, %d] is
> invalid\n",
> > + c->opt_chapter_start,
> c->opt_chapter_end);
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + if (!(c->vts_ifo = ifoOpen(c->dvdread, title_info.title_set_nr))) {
> > + av_log(s, AV_LOG_ERROR, "Unable to process the VTS IFO
> structure\n");
> > +
> > + return AVERROR_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 %d has invalid headers in
> VTS\n", c->opt_title);
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static void dvdvideo_play_close(AVFormatContext *s,
> DVDVideoPlaybackState *state)
> > +{
> > + if (!state->dvdnav)
> > + return;
> > +
> > + /* not allocated by av_malloc() */
> > + if (state->pgc_pg_times_est)
> > + free(state->pgc_pg_times_est);
> > +
>
> > + if (dvdnav_close(state->dvdnav) < 0)
> > + av_log(s, AV_LOG_ERROR, "Unable to cleanly close dvdnav\n");
>
> nit: in case this has consquence, you might print the error code in
> the log
>
> > +}
> > +
> > +static int dvdvideo_play_open(AVFormatContext *s, DVDVideoPlaybackState
> *state)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + int ret = 0;
> > + dvdnav_logger_cb dvdnav_log_cb;
> > + dvdnav_status_t dvdnav_open_status;
> > + int cur_title, cur_pgcn, cur_pgn;
> > + pgc_t *pgc;
> > +
> > + int32_t disc_region_mask;
> > + int32_t player_region_mask;
> > +
> > + dvdnav_log_cb = (dvdnav_logger_cb) { .pf_log =
> dvdvideo_libdvdnav_log };
> > + dvdnav_open_status = dvdnav_open2(&state->dvdnav, s,
> &dvdnav_log_cb, s->url);
> > +
>
> > + if (!state->dvdnav
> > + || dvdnav_open_status != DVDNAV_STATUS_OK
> > + || dvdnav_set_readahead_flag(state->dvdnav, 0) !=
> DVDNAV_STATUS_OK
> > + || dvdnav_set_PGC_positioning_flag(state->dvdnav, 1) !=
> DVDNAV_STATUS_OK
> > + || dvdnav_get_region_mask(state->dvdnav, &disc_region_mask)
> != DVDNAV_STATUS_OK) {
>
> nit+: here and below, weird style
>
> usually || is put at the EOL and conditions are aligned, as in
>
> if (foo ||
> bar ||
> baz) {
> ...
> }
>
> > + av_log(s, AV_LOG_ERROR, "Unable to open the DVD for
> playback\n");
> > +
> > + return AVERROR_EXTERNAL;
> > + }
> > +
> > + player_region_mask = c->opt_region > 0 ? (1 << (c->opt_region - 1))
> : disc_region_mask;
> > + if (dvdnav_set_region_mask(state->dvdnav, player_region_mask) !=
> DVDNAV_STATUS_OK) {
> > + av_log(s, AV_LOG_ERROR, "Unable to set the playback region code
> %d\n", c->opt_region);
> > +
> > + return AVERROR_EXTERNAL;
> > + }
> > +
> > + if (c->opt_pgc > 0 && c->opt_pg > 0) {
> > + if (dvdnav_program_play(state->dvdnav, c->opt_title,
> > + c->opt_pgc, c->opt_pg) !=
> DVDNAV_STATUS_OK) {
> > + av_log(s, AV_LOG_ERROR, "Unable to start playback at title
> %d, PGC %d, PG %d\n",
> > + c->opt_title, c->opt_pgc,
> c->opt_pg);
> > +
> > + return AVERROR_EXTERNAL;
> > + }
> > +
> > + state->pgcn = c->opt_pgc;
> > + state->entry_pgn = c->opt_pg;
> > + } else {
> > + if (dvdnav_part_play(state->dvdnav, c->opt_title,
> c->opt_chapter_start) != DVDNAV_STATUS_OK
> > + || dvdnav_current_title_program(state->dvdnav,
> &cur_title,
> > + &cur_pgcn, &cur_pgn) !=
> DVDNAV_STATUS_OK) {
> > + av_log(s, AV_LOG_ERROR, "Unable to start playback at title
> %d, chapter (PTT) %d\n",
> > + c->opt_title, c->opt_chapter_start);
> > +
> > + return AVERROR_EXTERNAL;
> > + }
> > +
> > + state->pgcn = cur_pgcn;
> > + state->entry_pgn = cur_pgn;
> > + }
> > +
> > + pgc = c->vts_ifo->vts_pgcit->pgci_srp[state->pgcn - 1].pgc;
> > +
> > + if (pgc->pg_playback_mode != 0) {
> > + av_log(s, AV_LOG_ERROR, "Non-sequential PGCs, such as shuffles,
> are not supported\n");
> > +
> > + return AVERROR_PATCHWELCOME;
> > + }
> > +
> > + if (dvdnav_angle_change(state->dvdnav, c->opt_angle) !=
> DVDNAV_STATUS_OK) {
>
> > + av_log(s, AV_LOG_ERROR, "Unable to start playback at the given
> angle\n");
>
> nit: you might mention the failing angle here
>
> > +
> > + return AVERROR_EXTERNAL;
> > + }
> > +
> > + /* dvdnav_describe_title_chapters() performs several safety checks
> on the title structure */
> > + /* take advantage of this side effect to ensure a safe navigation
> path */
> > + state->pgc_nb_pg_est =
> dvdnav_describe_title_chapters(state->dvdnav, c->opt_title,
> > +
> &state->pgc_pg_times_est,
> > +
> &state->pgc_duration_est);
> > + if (!state->pgc_nb_pg_est) {
> > + av_log(s, AV_LOG_ERROR, "Unable to validate title structure\n");
> > +
> > + return AVERROR_EXTERNAL;
> > + }
> > +
> > + state->nav_pts = dvdnav_get_current_time(state->dvdnav);
> > + state->vtsn = c->vmg_ifo->tt_srpt->title[c->opt_title -
> 1].title_set_nr;
> > +
> > + state->pgc = pgc;
> > +
> > + return ret;
> > +}
> > +
> > +static int dvdvideo_play_is_cell_promising(AVFormatContext *s,
> DVDVideoPlaybackState *state,
> > + int celln)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > + dvd_time_t cell_duration;
> > +
> > + if (!c->opt_trim)
> > + return 1;
> > +
> > + cell_duration = state->pgc->cell_playback[celln - 1].playback_time;
> > +
> > + return cell_duration.second >= 1 || cell_duration.minute >= 1 ||
> cell_duration.hour >= 1;
> > +}
> > +
> > +static int dvdvideo_play_next_ps_block(AVFormatContext *s,
> DVDVideoPlaybackState *state,
> > + uint8_t *buf, int buf_size,
> > + int *p_nav_event,
> > + void (*flush_cb)(AVFormatContext
> *s))
> > +{
> > + 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;
> > + int is_cell_promising = 0;
> > + pci_t *e_pci;
> > + dsi_t *e_dsi;
> > +
>
> > + if (buf_size != DVDVIDEO_BLOCK_SIZE) {
> > + av_log(s, AV_LOG_ERROR, "Invalid buffer size\n");
>
> you might mention the failing size (Invalid buffer size %d, the DVD block
> size %d was expected)
>
> > +
> > + return AVERROR(ENOMEM);
>
> Not sure this is the correct error (it's not an allocation error)
> maybe more invaliddata or EINVAL.
>
> > + }
> > +
> > + 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(state->dvdnav, nav_buf, &nav_event,
> &nav_len) != DVDNAV_STATUS_OK) {
> > + av_log(s, AV_LOG_ERROR, "Unable to read next block of
> PGC\n");
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + /* some discs follow NOPs with a premature stop event */
> > + if (nav_event == DVDNAV_STOP)
> > + return AVERROR_EOF;
> > +
> > + if (nav_len > DVDVIDEO_BLOCK_SIZE) {
> > + av_log(s, AV_LOG_ERROR, "Invalid block size\n");
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + if (dvdnav_current_title_info(state->dvdnav, &cur_title,
> > + &cur_ptt) != DVDNAV_STATUS_OK) {
> > + av_log(s, AV_LOG_ERROR, "Unable to validate title
> coordinates\n");
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + /* we somehow navigated to a menu */
> > + if (cur_title == 0 || !dvdnav_is_domain_vts(state->dvdnav))
> > + return AVERROR_EOF;
> > +
> > + if (dvdnav_current_title_program(state->dvdnav,
> &cur_title_unused,
> > + &cur_pgcn, &cur_pgn) !=
> DVDNAV_STATUS_OK) {
> > + av_log(s, AV_LOG_ERROR, "Unable to validate PGC
> coordinates\n");
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + /* we somehow left the PGC */
> > + if (state->in_pgc && cur_pgcn != state->pgcn)
> > + return AVERROR_EOF;
> > +
> > + if (dvdnav_get_angle_info(state->dvdnav, &cur_angle,
> &cur_nb_angles) != DVDNAV_STATUS_OK) {
> > + av_log(s, AV_LOG_ERROR, "Unable to validate angle
> coordinates\n");
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + av_log(s, nav_event == DVDNAV_BLOCK_OK ? AV_LOG_TRACE :
> AV_LOG_DEBUG,
>
> > + "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, state->celln,
> cur_pgcn, cur_pgn,
> > + state->in_vts, state->in_pgc,
> state->in_ps);
>
> nit: weird indent
>
> > +
> > + switch (nav_event) {
> > + case DVDNAV_VTS_CHANGE:
> > + if (state->in_vts)
> > + return AVERROR_EOF;
> > +
> > + e_vts = (dvdnav_vts_change_event_t *) nav_buf;
> > +
> > + if (e_vts->new_vtsN == state->vtsn && e_vts->new_domain
> == DVD_DOMAIN_VTSTitle)
> > + state->in_vts = 1;
> > +
> > + continue;
> > + case DVDNAV_CELL_CHANGE:
> > + if (!state->in_vts)
> > + continue;
> > +
> > + e_cell = (dvdnav_cell_change_event_t *) nav_buf;
> > + is_cell_promising = dvdvideo_play_is_cell_promising(s,
> state, e_cell->cellN);
> > +
> > + av_log(s, AV_LOG_DEBUG, "new cell: prev=%d new=%d
> promising=%d\n",
> > + state->celln, e_cell->cellN,
> is_cell_promising);
> > +
> > + if (!state->in_ps && !state->in_pgc) {
>
> > + if (cur_title == c->opt_title && cur_ptt ==
> c->opt_chapter_start
> > + && cur_pgcn == state->pgcn && cur_pgn ==
> state->entry_pgn
> > + && is_cell_promising) {
>
> nit: weird align
>
> > + state->in_pgc = 1;
> > + }
> > +
> > + if (c->opt_trim && !is_cell_promising)
> > + av_log(s, AV_LOG_INFO, "Skipping padding cell
> #%d\n", e_cell->cellN);
> > + } else if (state->celln >= e_cell->cellN || state->pgn
> > cur_pgn) {
> > + return AVERROR_EOF;
> > + }
> > +
> > + state->celln = e_cell->cellN;
> > + state->ptt = cur_ptt;
> > + state->pgn = cur_pgn;
> > +
> > + continue;
> > + case DVDNAV_NAV_PACKET:
> > + if (!state->in_pgc)
> > + continue;
> > +
> > + if ((state->ptt > 0 && state->ptt > cur_ptt)
> > + || (c->opt_chapter_end > 0 && cur_ptt >
> c->opt_chapter_end)) {
> > + return AVERROR_EOF;
> > + }
> > +
> > + e_pci = dvdnav_get_current_nav_pci(state->dvdnav);
> > + e_dsi = dvdnav_get_current_nav_dsi(state->dvdnav);
> > +
> > + if (e_pci == NULL || e_dsi == NULL
> > + || e_pci->pci_gi.vobu_s_ptm >
> e_pci->pci_gi.vobu_e_ptm) {
> > + av_log(s, AV_LOG_ERROR, "Invalid NAV packet\n");
> > +
> > + return AVERROR_INVALIDDATA;
> > + }
> > +
> > + state->vobu_duration = e_pci->pci_gi.vobu_e_ptm -
> e_pci->pci_gi.vobu_s_ptm;
> > + state->pgc_elapsed += state->vobu_duration;
> > + state->nav_pts = dvdnav_get_current_time(state->dvdnav);
> > + state->ptt = cur_ptt;
> > + state->pgn = cur_pgn;
> > + state->nb_vobus_played++;
> > +
> > + av_log(s, AV_LOG_DEBUG, "NAV pack: s_ptm=%d e_ptm=%d "
> > + "scr=%d lbn=%d vobu_duration=%d
> nav_pts=%ld\n",
> > + e_pci->pci_gi.vobu_s_ptm,
> e_pci->pci_gi.vobu_e_ptm,
> > + e_dsi->dsi_gi.nv_pck_scr,
> > + e_pci->pci_gi.nv_pck_lbn,
> state->vobu_duration, state->nav_pts);
> > +
> > + if (!state->in_ps) {
> > + av_log(s, AV_LOG_DEBUG, "navigation: locked to
> program stream\n");
> > +
> > + state->in_ps = 1;
> > + } else {
> > + if (state->vobu_e_ptm != e_pci->pci_gi.vobu_s_ptm) {
> > + if (flush_cb)
> > + flush_cb(s);
> > +
> > + state->ts_offset += state->vobu_e_ptm -
> e_pci->pci_gi.vobu_s_ptm;
> > + }
> > + }
> > +
> > + state->vobu_e_ptm = e_pci->pci_gi.vobu_e_ptm;
> > +
> > + (*p_nav_event) = nav_event;
> > +
> > + return nav_len;
> > + case DVDNAV_BLOCK_OK:
> > + if (!state->in_ps) {
> > + if (state->in_pgc)
> > + i = 0; /* necessary in case we are skipping
> junk cells at the beginning */
> > + continue;
> > + }
> > +
> > + if (nav_len != DVDVIDEO_BLOCK_SIZE) {
> > + av_log(s, AV_LOG_ERROR, "Invalid block size\n");
> > +
> > + 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);
> > +
> > + /* in case NAV packet is missed */
> > + state->ptt = cur_ptt;
> > + state->pgn = cur_pgn;
> > +
> > + (*p_nav_event) = nav_event;
> > +
> > + return nav_len;
> > + case DVDNAV_STILL_FRAME:
> > + case DVDNAV_WAIT:
> > + case DVDNAV_HOP_CHANNEL:
> > + case DVDNAV_HIGHLIGHT:
> > + if (state->in_ps)
> > + return AVERROR_EOF;
> > +
> > + if (nav_event == DVDNAV_STILL_FRAME)
> > + dvdnav_still_skip(state->dvdnav);
> > + if (nav_event == DVDNAV_WAIT)
> > + dvdnav_wait_skip(state->dvdnav);
> > +
> > + 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_INVALIDDATA;
> > +}
> > +
> > +static int dvdvideo_pgc_preindex(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + int ret = 0, interrupt = 0;
> > + int nb_chapters = 0, last_ptt = c->opt_chapter_start;
> > + uint64_t cur_chapter_offset = 0, cur_chapter_duration = 0;
> > + DVDVideoPlaybackState *state;
> > +
> > + uint8_t nav_buf[DVDVIDEO_BLOCK_SIZE];
> > + int nav_event;
> > +
> > + if (c->opt_chapter_start == c->opt_chapter_end)
> > + return 0;
> > +
> > + av_log(s, AV_LOG_INFO,
> > + "Indexing chapter markers, this will take a long time.
> Please wait...\n");
> > +
> > + state = av_mallocz(sizeof(DVDVideoPlaybackState));
> > + if ((ret = dvdvideo_play_open(s, state)) < 0) {
> > + av_freep(&state);
> > + return ret;
> > + }
> > +
> > + while (!(interrupt = ff_check_interrupt(&s->interrupt_callback))) {
> > + ret = dvdvideo_play_next_ps_block(s, state, nav_buf,
> DVDVIDEO_BLOCK_SIZE,
> > + &nav_event, NULL);
> > + if (ret < 0 && ret != AVERROR_EOF)
> > + goto end_free;
> > +
> > + if (nav_event != DVDNAV_NAV_PACKET && ret != AVERROR_EOF)
> > + continue;
> > +
> > + if (state->ptt == last_ptt) {
> > + cur_chapter_duration += state->vobu_duration;
> > + /* ensure we add the last chapter */
> > + if (ret != AVERROR_EOF)
> > + continue;
> > + }
> > +
>
> > + if (!avpriv_new_chapter(s, nb_chapters, DVDVIDEO_TIME_BASE_Q,
> cur_chapter_offset,
> > + cur_chapter_offset +
> cur_chapter_duration, NULL)) {
> > + av_log(s, AV_LOG_ERROR, "Unable to allocate chapter\n");
>
> ret is not set in this case
>
> > +
> > + goto end_free;
> > + }
> > +
> > + nb_chapters++;
> > + cur_chapter_offset += cur_chapter_duration;
> > + cur_chapter_duration = state->vobu_duration;
> > + last_ptt = state->ptt;
> > +
> > + if (ret == AVERROR_EOF)
> > + break;
> > + }
> > +
> > + if (interrupt) {
> > + ret = AVERROR_EXIT;
> > + goto end_free;
> > + }
> > +
> > + if (ret < 0 && ret != AVERROR_EOF)
> > + goto end_free;
> > +
> > + s->duration = av_rescale_q(state->pgc_elapsed,
> DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
> > + c->duration_ptm = state->pgc_elapsed;
> > +
> > + av_log(s, AV_LOG_INFO, "Chapter marker indexing complete\n");
> > + ret = 0;
> > +
> > +end_free:
> > + dvdvideo_play_close(s, state);
> > + av_freep(&state);
> > +
> > + return ret;
> > +}
> > +
> > +static int dvdvideo_pgc_chapters_setup(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + uint64_t time_prev = 0;
> > + int64_t total_duration = 0;
> > +
> > + int chapter_start = c->opt_chapter_start - 1;
> > + int chapter_end = c->opt_chapter_end > 0 ? c->opt_chapter_end :
> c->play_state.pgc_nb_pg_est - 1;
> > +
> > + if (chapter_start == chapter_end || c->play_state.pgc_nb_pg_est ==
> 1) {
> > + s->duration = av_rescale_q(c->play_state.pgc_duration_est,
> > + DVDVIDEO_TIME_BASE_Q,
> AV_TIME_BASE_Q);
> > + return 0;
> > + }
> > +
> > + for (int i = chapter_start; i < chapter_end; i++) {
> > + uint64_t time_effective = c->play_state.pgc_pg_times_est[i] -
> c->play_state.nav_pts;
> > +
> > + 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;
> > + total_duration = time_effective;
> > + }
> > +
> > + if (c->opt_chapter_start == 1 && c->opt_chapter_end == 0)
> > + s->duration = av_rescale_q(c->play_state.pgc_duration_est,
> > + DVDVIDEO_TIME_BASE_Q,
> AV_TIME_BASE_Q);
> > + else
> > + s->duration = av_rescale_q(total_duration,
> > + DVDVIDEO_TIME_BASE_Q,
> AV_TIME_BASE_Q);
> > +
> > + return 0;
> > +}
> > +
> > +static int dvdvideo_video_stream_analyze(AVFormatContext *s,
> 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) {
>
> > + av_log(s, AV_LOG_ERROR, "Invalid video dimensions\n");
>
> you might print the actual size to help debug impossible cases
>
> > +
> > + 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 = !is_pal && (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 = NULL;
> > + int ret = 0;
> > +
>
> > + entry = av_mallocz(sizeof(DVDVideoVTSVideoStreamEntry));
>
> you might check for failure
>
> > +
> > + if ((ret = dvdvideo_video_stream_analyze(s,
> 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_HEADERS)) < 0) {
> > + av_freep(&entry);
>
> > + av_log(s, AV_LOG_ERROR, "Unable to allocate video stream\n");
>
> misleading error message, I'd say: Unable to add video stream
>
> > +
> > + return ret;
> > + }
> > +
> > + av_freep(&entry);
> > +
> > + return ret;
> > +}
> > +
> > +static int dvdvideo_audio_stream_analyze(AVFormatContext *s,
> 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 = (AVChannelLayout) {0};
> > + char lang_dvd[3] = {0};
> > +
> > + int position = (audio_control & 0x7F00) >> 8;
> > +
> > + /* XXX(PATCHWELCOME): SDDS is not supported due to lack of sample
> material */
> > + switch (audio_attr.audio_format) {
> > + case 0: /* AC3 */
> > + codec_id = AV_CODEC_ID_AC3;
> > + sample_fmt = AV_SAMPLE_FMT_FLTP;
> > + sample_rate = 48000;
> > + startcode = 0x80 + position;
> > + break;
> > + case 2: /* MP1 */
> > + 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: /* MP2 */
> > + 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: /* DVD PCM */
> > + codec_id = AV_CODEC_ID_PCM_DVD;
> > + 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: /* DCA */
> > + 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) {
>
> > + av_log(s, AV_LOG_ERROR, "Invalid audio parameters\n");
>
> please provide some context to help debug impossible cases
>
> > +
> > + 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 == 7)
> > + ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_6POINT1;
> > + else if (nb_channels == 8)
> > + ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_7POINT1;
> > +
> > + if (audio_attr.code_extension == 2)
> > + entry->disposition |= AV_DISPOSITION_VISUAL_IMPAIRED;
> > + if (audio_attr.code_extension == 3 || audio_attr.code_extension ==
> 4)
> > + entry->disposition |= AV_DISPOSITION_COMMENT;
> > +
> > + 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;
> > + st->disposition = entry->disposition;
> > +
> > + 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 = 0;
> > +
> > + for (int i = 0; i < c->vts_ifo->vtsi_mat->nr_of_vts_audio_streams;
> i++) {
> > + DVDVideoPGCAudioStreamEntry *entry = NULL;
> > +
> > + if (!(c->play_state.pgc->audio_control[i] & 0x8000))
> > + continue;
> > +
> > + entry = av_mallocz(sizeof(DVDVideoPGCAudioStreamEntry));
> > + if (!entry)
> > + return AVERROR(ENOMEM);
> > +
> > + if ((ret = dvdvideo_audio_stream_analyze(s,
> c->vts_ifo->vtsi_mat->vts_audio_attr[i],
> > + c->play_state.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, "Karaoke metadata in stream %d
> will not be retained\n",
> > + entry->startcode);
> > +
> > + /* IFO structures can declare duplicate entries for the same
> 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
> > + || (ret = dvdvideo_audio_stream_add(s, entry,
> AVSTREAM_PARSE_HEADERS)) < 0)
> > + goto break_free_and_error;
> > +
> > +continue_free:
> > + av_freep(&entry);
> > + continue;
> > +
> > +break_free_and_error:
> > + av_freep(&entry);
>
> > + av_log(s, AV_LOG_ERROR, "Unable to allocate audio stream\n");
>
> ditto: unable to "add" audio stream?
>
> > + return ret;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +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);
> > +
> > + if (subp_attr.lang_extension == 9)
> > + entry->disposition |= AV_DISPOSITION_FORCED;
> > +
> > + 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;
> > + int ret = 0;
> > +
> > + 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;
> > +
> > + if (entry->lang_iso)
> > + av_dict_set(&st->metadata, "language", entry->lang_iso, 0);
> > +
> > + av_dict_set(&st->metadata, "VIEWPORT",
> VIEWPORT_LABELS[entry->viewport], 0);
> > +
> > + st->disposition = entry->disposition;
> > +
> > + 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 ret;
> > +}
> > +
> > +static int dvdvideo_subp_stream_add_internal(AVFormatContext *s,
> uint32_t offset,
> > + subp_attr_t subp_attr,
> > + enum
> DVDVideoSubpictureViewport viewport)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + DVDVideoPGCSubtitleStreamEntry *entry = NULL;
> > + int ret = 0;
> > +
>
> > + entry = av_mallocz(sizeof(DVDVideoPGCSubtitleStreamEntry));
>
> missing check
>
> > + entry->viewport = viewport;
> > +
> > + if ((ret = dvdvideo_subp_stream_analyze(s, offset, subp_attr,
> entry)) < 0)
> > + goto end_free_error;
> > +
> > + /* IFO structures can declare duplicate entries for the same
> startcode */
> > + 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_HEADERS)) < 0)
> > + goto end_free_error;
> > +
> > + goto end_free;
> > +
> > +end_free_error:
>
> > + av_log(s, AV_LOG_ERROR, "Unable to allocate subtitle stream\n");
>
> ditto
>
> > +
> > +end_free:
> > + av_freep(&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 = 0;
> > + uint32_t subp_control;
> > + subp_attr_t subp_attr;
> > + video_attr_t video_attr;
> > +
> > + subp_control = c->play_state.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,
> > +
> DVDVIDEO_SUBP_VIEWPORT_FULLSCREEN)) < 0)
> > + return ret;
> > +
> > + continue;
> > + }
> > +
> > + /* 16:9 */
> > + if ((ret = dvdvideo_subp_stream_add_internal(s, subp_control >>
> 16, subp_attr,
> > +
> DVDVIDEO_SUBP_VIEWPORT_WIDESCREEN)) < 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,
> > +
> DVDVIDEO_SUBP_VIEWPORT_LETTERBOX)) < 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,
> > +
> DVDVIDEO_SUBP_VIEWPORT_PANSCAN)) < 0)
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static void dvdvideo_subdemux_flush(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + if (!c->play_seg_started)
> > + return;
> > +
> > + av_log(s, AV_LOG_DEBUG, "flushing sub-demuxer\n");
> > + avio_flush(&c->mpeg_pb.pub);
> > + ff_read_frame_flush(c->mpeg_ctx);
> > + c->play_seg_started = 0;
> > +}
> > +
> > +static int dvdvideo_subdemux_read_data(void *opaque, uint8_t *buf, int
> buf_size)
> > +{
> > + AVFormatContext *s = opaque;
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + int ret = 0;
> > + int nav_event;
> > +
> > + if (c->play_end)
> > + return AVERROR_EOF;
> > +
> > + ret = dvdvideo_play_next_ps_block(opaque, &c->play_state, buf,
> buf_size,
> > + &nav_event,
> dvdvideo_subdemux_flush);
> > +
> > + if (ret == AVERROR_EOF) {
> > + c->mpeg_pb.pub.eof_reached = 1;
> > + c->play_end = 1;
> > +
> > + return AVERROR_EOF;
> > + }
> > +
> > + if (ret >= 0 && nav_event == DVDNAV_NAV_PACKET)
> > + return FFERROR_REDO;
> > +
> > + return ret;
> > +}
> > +
> > +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 = 0;
> > +
> > + if (!(c->mpeg_fmt = av_find_input_format("mpeg")))
> > + return AVERROR_DEMUXER_NOT_FOUND;
> > +
> > + if (!(c->mpeg_ctx = avformat_alloc_context()))
> > + return AVERROR(ENOMEM);
> > +
> > + if (!(c->mpeg_buf = av_mallocz(DVDVIDEO_BLOCK_SIZE))) {
> > + avformat_free_context(c->mpeg_ctx);
> > +
> > + return AVERROR(ENOMEM);
> > + }
> > +
> > + ffio_init_context(&c->mpeg_pb, c->mpeg_buf, DVDVIDEO_BLOCK_SIZE, 0,
> s,
> > + dvdvideo_subdemux_read_data, NULL, NULL);
> > + c->mpeg_pb.pub.seekable = 0;
> > +
> > + if ((ret = ff_copy_whiteblacklists(c->mpeg_ctx, s)) < 0) {
> > + avformat_free_context(c->mpeg_ctx);
> > +
> > + return ret;
> > + }
> > +
> > + c->mpeg_ctx->flags = AVFMT_FLAG_CUSTOM_IO | AVFMT_FLAG_GENPTS;
> > + c->mpeg_ctx->ctx_flags |= AVFMTCTX_UNSEEKABLE;
> > + c->mpeg_ctx->probesize = 0;
> > + c->mpeg_ctx->max_analyze_duration = 0;
> > + c->mpeg_ctx->interrupt_callback = s->interrupt_callback;
> > + c->mpeg_ctx->pb = &c->mpeg_pb.pub;
> > + c->mpeg_ctx->io_open = NULL;
> > +
> > + if ((ret = avformat_open_input(&c->mpeg_ctx, "", c->mpeg_fmt,
> NULL)) < 0) {
> > + avformat_free_context(c->mpeg_ctx);
> > +
> > + return ret;
>
> you can drop this
>
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static int dvdvideo_read_header(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + int ret = 0;
> > +
> > + if (c->opt_title == 0) {
> > + av_log(s, AV_LOG_INFO, "Defaulting to title #1. "
> > + "This is not always the main feature,
> validation suggested.\n");
> > +
> > + c->opt_title = 1;
> > + }
> > +
> > + if (c->opt_pgc) {
> > + if (c->opt_pg == 0)
> > + av_log(s, AV_LOG_ERROR, "Invalid coordinates. If -pgc is
> set, -pg must be set too.\n");
> > + else if (c->opt_chapter_start > 1 || c->opt_chapter_end > 0 ||
> c->opt_preindex)
> > + av_log(s, AV_LOG_ERROR, "-pgc is not compatible with the
> -preindex or "
> > + "-chapter_start/-chapter_end
> options\n");
> > +
> > + return AVERROR(EINVAL);
> > + }
> > +
> > + if ((ret = dvdvideo_ifo_open(s)) < 0)
> > + return ret;
> > +
> > + if (c->opt_preindex && (ret = dvdvideo_pgc_preindex(s)) < 0)
> > + return ret;
> > +
> > + if ((ret = dvdvideo_play_open(s, &c->play_state)) < 0
> > + || (ret = dvdvideo_subdemux_open(s)) < 0
> > + || (ret = dvdvideo_video_stream_setup(s)) < 0
> > + || (ret = dvdvideo_audio_stream_add_all(s)) < 0
> > + || (ret = dvdvideo_subp_stream_add_all(s)) < 0)
> > + return ret;
> > +
> > + if (!c->opt_preindex)
> > + return dvdvideo_pgc_chapters_setup(s);
> > +
> > + return ret;
> > +}
> > +
> > +static int dvdvideo_read_packet(AVFormatContext *s, AVPacket *pkt)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + int ret;
> > + enum AVMediaType st_type;
> > + int64_t cur_delta = 0;
> > +
> > + if (c->play_end)
> > + return AVERROR_EOF;
> > +
> > + ret = av_read_frame(c->mpeg_ctx, pkt);
> > +
> > + if (ret < 0)
> > + return ret;
> > +
> > + if (!c->play_seg_started)
> > + c->play_seg_started = 1;
> > +
> > + if (pkt->stream_index >= s->nb_streams) {
>
> > + av_log(s, AV_LOG_DEBUG, "discarding frame with unknown
> stream\n");
>
> print the unrecongnized stream index
>
> > + goto skip_redo;
> > + }
> > +
> > + st_type =
> c->mpeg_ctx->streams[pkt->stream_index]->codecpar->codec_type;
> > +
> > + if (!c->play_started) {
> > + if (st_type != AVMEDIA_TYPE_VIDEO || pkt->pts == AV_NOPTS_VALUE
> > + || pkt->dts == AV_NOPTS_VALUE
> > + || !(pkt->flags &
> AV_PKT_FLAG_KEY)) {
>
> > + av_log(s, AV_LOG_DEBUG, "discarding non-video-keyframe at
> start\n");
>
> of packet with unset PTS/DTS at start
>
> > + goto skip_redo;
> > + }
> > +
> > + c->first_pts = pkt->pts;
> > + c->play_started = 1;
> > +
> > + pkt->pts = 0;
> > + pkt->dts = c->play_state.ts_offset - pkt->pts;
> > + } else {
> > + cur_delta = c->play_state.ts_offset - c->first_pts;
> > +
> > + if (c->play_state.nb_vobus_played == 1 && (pkt->pts ==
> AV_NOPTS_VALUE
> > + || pkt->dts ==
> AV_NOPTS_VALUE
> > + || (pkt->pts +
> cur_delta) < 0)) {
> > + av_log(s, AV_LOG_DEBUG, "discarding frame with negative or
> unset timestamp "
> > + "(this is OK at the start of the
> first playback VOBU)\n");
> > + goto skip_redo;
> > + }
> > +
> > + if (pkt->pts != AV_NOPTS_VALUE)
> > + pkt->pts += cur_delta;
> > + if (pkt->dts != AV_NOPTS_VALUE)
> > + pkt->dts += cur_delta;
> > + }
> > +
> > + av_log(s, AV_LOG_TRACE, "st=%d pts=%ld dts=%ld ts_offset=%ld
> first_pts=%ld\n",
> > + pkt->stream_index, pkt->pts, pkt->dts,
> > + c->play_state.ts_offset, c->first_pts);
> > + if (pkt->pts < 0)
> > + av_log(s, AV_LOG_WARNING, "Invalid frame PTS @ st=%d pts=%ld
> dts=%ld\n",
> > + pkt->stream_index, pkt->pts,
> pkt->dts);
> > +
> > + return c->play_end ? AVERROR_EOF : 0;
> > +
> > +skip_redo:
> > + av_packet_unref(pkt);
> > + return FFERROR_REDO;
> > +}
> > +
> > +static int dvdvideo_close(AVFormatContext *s)
> > +{
> > + DVDVideoDemuxContext *c = s->priv_data;
> > +
> > + dvdvideo_subdemux_close(s);
> > + dvdvideo_play_close(s, &c->play_state);
> > + dvdvideo_ifo_close(s);
> > +
> > + return 0;
> > +}
> > +
> > +#define OFFSET(x) offsetof(DVDVideoDemuxContext, x)
> > +static const AVOption dvdvideo_options[] = {
> > + {"angle", "playback angle number",
> OFFSET(opt_angle), AV_OPT_TYPE_INT, { .i64=1 },
> 1, 9, AV_OPT_FLAG_DECODING_PARAM },
> > + {"chapter_end", "exit chapter (PTT) number (0=end)",
> OFFSET(opt_chapter_end), AV_OPT_TYPE_INT, { .i64=0 },
> 0, 99, AV_OPT_FLAG_DECODING_PARAM },
> > + {"chapter_start", "entry chapter (PTT) number",
> OFFSET(opt_chapter_start), AV_OPT_TYPE_INT, { .i64=1 },
> 1, 99, 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 },
> > + {"pgc", "entry PGC number (0=auto)",
> OFFSET(opt_pgc), AV_OPT_TYPE_INT, { .i64=0 },
> 0, 999, AV_OPT_FLAG_DECODING_PARAM },
> > + {"preindex", "enable for accurate chapter markers, slow
> (2-pass read)", OFFSET(opt_preindex), AV_OPT_TYPE_BOOL, { .i64=0
> }, 0, 1, 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 },
> > + {"title", "title number (0=auto)",
> OFFSET(opt_title), AV_OPT_TYPE_INT, { .i64=0 },
> 0, 99, AV_OPT_FLAG_DECODING_PARAM },
> > + {"trim", "trim padding cells from start",
> OFFSET(opt_trim), AV_OPT_TYPE_BOOL, { .i64=1 },
> 0, 1, 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_close = dvdvideo_close,
> > + .read_header = dvdvideo_read_header,
> > + .read_packet = dvdvideo_read_packet
> > +};
>
> LGTM otherwise, thanks.
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH v7 2/2] libavformat/dvdvideo: add DVD CLUT utilities and enable palette support
2024-02-05 5:48 ` [FFmpeg-devel] [PATCH v7 2/2] libavformat/dvdvideo: add DVD CLUT utilities and enable palette support Marth64
@ 2024-02-07 18:09 ` Andreas Rheinhardt
2024-02-07 18:32 ` Marth64
0 siblings, 1 reply; 27+ messages in thread
From: Andreas Rheinhardt @ 2024-02-07 18:09 UTC (permalink / raw)
To: ffmpeg-devel
Marth64:
> DVD subtitle palettes, which are natively YUV, are currently carried as
> a hex string in their respective subtitle streams and have
> no concept of colorspace tagging. In fact, the convention is to convert
> them to RGB prior to storage. Common players will only render
> the palettes properly if they are stored as RGB. Even ffmpeg itself
> expects this, and already does -in libavformat- the YUV-RGB conversions,
> specifically in mov.c and movenc.c.
>
> The point of this patch is to provide a consolidation of the code
> that deals with creating the extradata as well as the RGB conversion.
> That can then (1) enable usable palette support for DVD demuxer if it is merged
> and (2) start the process of consolidating the related conversions in
> MOV muxer/demuxer and eventually find a way to properly tag
> the colorspace.
>
>
> Signed-off-by: Marth64 <marth64@proxyid.net>
> ---
> doc/demuxers.texi | 5 ++
> libavformat/Makefile | 2 +-
> libavformat/dvdclut.c | 104 ++++++++++++++++++++++++++++++++++++++
> libavformat/dvdclut.h | 37 ++++++++++++++
> libavformat/dvdvideodec.c | 14 +++++
> 5 files changed, 161 insertions(+), 1 deletion(-)
> create mode 100644 libavformat/dvdclut.c
> create mode 100644 libavformat/dvdclut.h
>
> diff --git a/doc/demuxers.texi b/doc/demuxers.texi
> index ad0906f6ec..9c666a29c1 100644
> --- a/doc/demuxers.texi
> +++ b/doc/demuxers.texi
> @@ -390,6 +390,11 @@ often with junk data intended for controlling a real DVD player's
> buffering speed and with no other material data value.
> Default is 1, true.
>
> +@item clut_rgb
> +Output subtitle palettes (CLUTs) as RGB, required for Matroska and MP4.
> +Disable to output the palette in its original YUV colorspace.
> +Default is 1, true.
> +
> @end table
>
> @subsection Examples
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index df69734877..8f17e43e49 100644
> --- a/libavformat/Makefile
> +++ b/libavformat/Makefile
> @@ -192,7 +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_DVDVIDEO_DEMUXER) += dvdvideodec.o dvdclut.o
> OBJS-$(CONFIG_DXA_DEMUXER) += dxa.o
> OBJS-$(CONFIG_EA_CDATA_DEMUXER) += eacdata.o
> OBJS-$(CONFIG_EA_DEMUXER) += electronicarts.o
> diff --git a/libavformat/dvdclut.c b/libavformat/dvdclut.c
> new file mode 100644
> index 0000000000..71fdda7925
> --- /dev/null
> +++ b/libavformat/dvdclut.c
> @@ -0,0 +1,104 @@
> +/*
> + * DVD-Video subpicture CLUT (Color Lookup Table) utilities
> + *
> + * 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/bprint.h"
> +#include "libavutil/colorspace.h"
> +#include "libavutil/intreadwrite.h"
Seems unneeded, just like avformat.h.
> +
> +#include "avformat.h"
> +#include "dvdclut.h"
> +#include "internal.h"
> +
> +/* crop table for YUV to RGB subpicture palette conversion */
> +#define FF_DVDCLUT_YUV_NEG_CROP_MAX 1024
An FF-prefix for an internal define is not needed.
> +#define times4(x) x, x, x, x
> +#define times256(x) times4(times4(times4(times4(times4(x)))))
This is actually a times1024 (which matches the size of the LUT).
> +
> +const uint8_t ff_dvdclut_yuv_crop_tab[256 + 2 * FF_DVDCLUT_YUV_NEG_CROP_MAX] = {
> +times256(0x00),
> +0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
> +0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
> +0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
> +0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
> +0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
> +0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,
> +0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
> +0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,
> +0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
> +0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
> +0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
> +0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
> +0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
> +0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
> +0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,
> +0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF,
> +times256(0xFF)
> +};
1. This LUT is not used outside this module, so should be static if
used. And therefore should not use an ff_ prefix at all.
2. Given that this code here is absolutely not speed relevant, the LUT
can be avoided by using av_clip_uint8(value - 1024).
> +
> +int ff_dvdclut_palette_extradata_cat(const uint32_t *clut,
> + const size_t clut_size,
> + AVCodecParameters *par)
> +{
> + int ret = 0;
> + AVBPrint bp;
> +
> + if (clut_size != FF_DVDCLUT_CLUT_SIZE)
> + return AVERROR(EINVAL);
> +
> + av_bprint_init(&bp, 0, FF_DVDCLUT_EXTRADATA_SIZE);
> +
> + av_bprintf(&bp, "palette: ");
> +
> + for (int i = 0; i < FF_DVDCLUT_CLUT_LEN; i++)
> + av_bprintf(&bp, "%06"PRIx32"%s", clut[i], i != 15 ? ", " : "");
Don't hardcode 15 for the last iteration.
> +
> + av_bprintf(&bp, "\n");
> +
> + ret = ff_bprint_to_codecpar_extradata(par, &bp);
> +
> + av_bprint_finalize(&bp, NULL);
Unnecessary, as ff_bprint_to_codecpar_extradata() has already finalized
the bprint.
> +
> + return ret;
> +}
> +
> +int ff_dvdclut_yuv_to_rgb(uint32_t *clut, const size_t clut_size)
> +{
> + const uint8_t *cm = ff_dvdclut_yuv_crop_tab + FF_DVDCLUT_YUV_NEG_CROP_MAX;
> +
> + int i, y, cb, cr;
> + uint8_t r, g, b;
> + int r_add, g_add, b_add;
i should have loop scope and the other variables loop body scope.
> +
> + if (clut_size != FF_DVDCLUT_CLUT_SIZE)
> + return AVERROR(EINVAL);
> +
> + for (i = 0; i < FF_DVDCLUT_CLUT_LEN; i++) {
> + y = (clut[i] >> 16) & 0xFF;
> + cr = (clut[i] >> 8) & 0xFF;
> + cb = clut[i] & 0xFF;
> +
> + YUV_TO_RGB1_CCIR(cb, cr);
> + YUV_TO_RGB2_CCIR(r, g, b, y);
> +
> + clut[i] = (r << 16) | (g << 8) | b;
> + }
> +
> + return 0;
> +}
> diff --git a/libavformat/dvdclut.h b/libavformat/dvdclut.h
> new file mode 100644
> index 0000000000..40eff6de34
> --- /dev/null
> +++ b/libavformat/dvdclut.h
> @@ -0,0 +1,37 @@
> +/*
> + * DVD-Video subpicture CLUT (Color Lookup Table) utilities
> + *
> + * 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
> + */
> +
> +#ifndef AVFORMAT_DVDCLUT_H
> +#define AVFORMAT_DVDCLUT_H
> +
> +#include "avformat.h"
lavc/codec_par.h is enough
> +
> +/* ("palette: ") + ("rrggbb, "*15) + ("rrggbb") + \n + \0 */
> +#define FF_DVDCLUT_EXTRADATA_SIZE (9 + (8 * 15) + 6 + 1 + 1)
> +#define FF_DVDCLUT_CLUT_LEN 16
> +#define FF_DVDCLUT_CLUT_SIZE FF_DVDCLUT_CLUT_LEN * sizeof(uint32_t)
> +
> +int ff_dvdclut_palette_extradata_cat(const uint32_t *clut,
> + const size_t clut_size,
> + AVCodecParameters *par);
> +
> +int ff_dvdclut_yuv_to_rgb(uint32_t *clut, const size_t clut_size);
> +
> +#endif /* AVFORMAT_DVDCLUT_H */
> diff --git a/libavformat/dvdvideodec.c b/libavformat/dvdvideodec.c
> index 2e363a4a22..657825ba3e 100644
> --- a/libavformat/dvdvideodec.c
> +++ b/libavformat/dvdvideodec.c
> @@ -50,6 +50,7 @@
> #include "avio_internal.h"
> #include "avlanguage.h"
> #include "demux.h"
> +#include "dvdclut.h"
> #include "internal.h"
> #include "url.h"
>
> @@ -91,6 +92,7 @@ typedef struct DVDVideoPGCAudioStreamEntry {
>
> typedef struct DVDVideoPGCSubtitleStreamEntry {
> int startcode;
> + uint32_t *clut;
> int disposition;
> char *lang_iso;
> enum DVDVideoSubpictureViewport viewport;
> @@ -132,6 +134,7 @@ typedef struct DVDVideoDemuxContext {
> int opt_region; /* the user-provided region digit */
> int opt_preindex; /* pre-indexing mode (2-pass read) */
> int opt_trim; /* trim padding cells at beginning and end */
> + int opt_clut_rgb; /* output subtitle palette (CLUT) as RGB */
>
> /* subdemux */
> const AVInputFormat *mpeg_fmt; /* inner MPEG-PS (VOB) demuxer */
> @@ -1034,6 +1037,11 @@ static int dvdvideo_subp_stream_analyze(AVFormatContext *s, uint32_t offset, sub
> if (subp_attr.lang_extension == 9)
> entry->disposition |= AV_DISPOSITION_FORCED;
>
> + memcpy(entry->clut, c->play_state.pgc->palette, FF_DVDCLUT_CLUT_SIZE);
> +
> + if (c->opt_clut_rgb)
> + ff_dvdclut_yuv_to_rgb(entry->clut, FF_DVDCLUT_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);
>
> @@ -1055,6 +1063,9 @@ static int dvdvideo_subp_stream_add(AVFormatContext *s, DVDVideoPGCSubtitleStrea
> st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
> st->codecpar->codec_id = AV_CODEC_ID_DVD_SUBTITLE;
>
> + if ((ret = ff_dvdclut_palette_extradata_cat(entry->clut, FF_DVDCLUT_CLUT_SIZE, st->codecpar)) < 0)
> + return ret;
> +
> if (entry->lang_iso)
> av_dict_set(&st->metadata, "language", entry->lang_iso, 0);
>
> @@ -1082,6 +1093,7 @@ static int dvdvideo_subp_stream_add_internal(AVFormatContext *s, uint32_t offset
> int ret = 0;
>
> entry = av_mallocz(sizeof(DVDVideoPGCSubtitleStreamEntry));
> + entry->clut = av_mallocz(FF_DVDCLUT_CLUT_SIZE);
> entry->viewport = viewport;
>
> if ((ret = dvdvideo_subp_stream_analyze(s, offset, subp_attr, entry)) < 0)
> @@ -1102,6 +1114,7 @@ end_free_error:
> av_log(s, AV_LOG_ERROR, "Unable to allocate subtitle stream\n");
>
> end_free:
> + av_freep(&entry->clut);
> av_freep(&entry);
>
> return ret;
> @@ -1381,6 +1394,7 @@ static const AVOption dvdvideo_options[] = {
> {"angle", "playback angle number", OFFSET(opt_angle), AV_OPT_TYPE_INT, { .i64=1 }, 1, 9, AV_OPT_FLAG_DECODING_PARAM },
> {"chapter_end", "exit chapter (PTT) number (0=end)", OFFSET(opt_chapter_end), AV_OPT_TYPE_INT, { .i64=0 }, 0, 99, AV_OPT_FLAG_DECODING_PARAM },
> {"chapter_start", "entry chapter (PTT) number", OFFSET(opt_chapter_start), AV_OPT_TYPE_INT, { .i64=1 }, 1, 99, AV_OPT_FLAG_DECODING_PARAM },
> + {"clut_rgb", "output subtitle palette (CLUT) as RGB", OFFSET(opt_clut_rgb), AV_OPT_TYPE_BOOL, { .i64=1 }, 0, 1, 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 },
> {"pgc", "entry PGC number (0=auto)", OFFSET(opt_pgc), AV_OPT_TYPE_INT, { .i64=0 }, 0, 999, AV_OPT_FLAG_DECODING_PARAM },
> {"preindex", "enable for accurate chapter markers, slow (2-pass read)", OFFSET(opt_preindex), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, AV_OPT_FLAG_DECODING_PARAM },
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH v7 2/2] libavformat/dvdvideo: add DVD CLUT utilities and enable palette support
2024-02-07 18:09 ` Andreas Rheinhardt
@ 2024-02-07 18:32 ` Marth64
0 siblings, 0 replies; 27+ messages in thread
From: Marth64 @ 2024-02-07 18:32 UTC (permalink / raw)
To: FFmpeg development discussions and patches
Thank you, Andreas. This was very helpful. I will clean it up this evening.
On Wed, Feb 7, 2024 at 12:07 Andreas Rheinhardt <
andreas.rheinhardt@outlook.com> wrote:
> Marth64:
> > DVD subtitle palettes, which are natively YUV, are currently carried as
> > a hex string in their respective subtitle streams and have
> > no concept of colorspace tagging. In fact, the convention is to convert
> > them to RGB prior to storage. Common players will only render
> > the palettes properly if they are stored as RGB. Even ffmpeg itself
> > expects this, and already does -in libavformat- the YUV-RGB conversions,
> > specifically in mov.c and movenc.c.
> >
> > The point of this patch is to provide a consolidation of the code
> > that deals with creating the extradata as well as the RGB conversion.
> > That can then (1) enable usable palette support for DVD demuxer if it is
> merged
> > and (2) start the process of consolidating the related conversions in
> > MOV muxer/demuxer and eventually find a way to properly tag
> > the colorspace.
> >
> >
> > Signed-off-by: Marth64 <marth64@proxyid.net>
> > ---
> > doc/demuxers.texi | 5 ++
> > libavformat/Makefile | 2 +-
> > libavformat/dvdclut.c | 104 ++++++++++++++++++++++++++++++++++++++
> > libavformat/dvdclut.h | 37 ++++++++++++++
> > libavformat/dvdvideodec.c | 14 +++++
> > 5 files changed, 161 insertions(+), 1 deletion(-)
> > create mode 100644 libavformat/dvdclut.c
> > create mode 100644 libavformat/dvdclut.h
> >
> > diff --git a/doc/demuxers.texi b/doc/demuxers.texi
> > index ad0906f6ec..9c666a29c1 100644
> > --- a/doc/demuxers.texi
> > +++ b/doc/demuxers.texi
> > @@ -390,6 +390,11 @@ often with junk data intended for controlling a
> real DVD player's
> > buffering speed and with no other material data value.
> > Default is 1, true.
> >
> > +@item clut_rgb
> > +Output subtitle palettes (CLUTs) as RGB, required for Matroska and MP4.
> > +Disable to output the palette in its original YUV colorspace.
> > +Default is 1, true.
> > +
> > @end table
> >
> > @subsection Examples
> > diff --git a/libavformat/Makefile b/libavformat/Makefile
> > index df69734877..8f17e43e49 100644
> > --- a/libavformat/Makefile
> > +++ b/libavformat/Makefile
> > @@ -192,7 +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_DVDVIDEO_DEMUXER) += dvdvideodec.o dvdclut.o
> > OBJS-$(CONFIG_DXA_DEMUXER) += dxa.o
> > OBJS-$(CONFIG_EA_CDATA_DEMUXER) += eacdata.o
> > OBJS-$(CONFIG_EA_DEMUXER) += electronicarts.o
> > diff --git a/libavformat/dvdclut.c b/libavformat/dvdclut.c
> > new file mode 100644
> > index 0000000000..71fdda7925
> > --- /dev/null
> > +++ b/libavformat/dvdclut.c
> > @@ -0,0 +1,104 @@
> > +/*
> > + * DVD-Video subpicture CLUT (Color Lookup Table) utilities
> > + *
> > + * 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/bprint.h"
> > +#include "libavutil/colorspace.h"
> > +#include "libavutil/intreadwrite.h"
>
> Seems unneeded, just like avformat.h.
>
> > +
> > +#include "avformat.h"
> > +#include "dvdclut.h"
> > +#include "internal.h"
> > +
> > +/* crop table for YUV to RGB subpicture palette conversion */
> > +#define FF_DVDCLUT_YUV_NEG_CROP_MAX 1024
>
> An FF-prefix for an internal define is not needed.
>
> > +#define times4(x) x, x, x, x
> > +#define times256(x)
> times4(times4(times4(times4(times4(x)))))
>
> This is actually a times1024 (which matches the size of the LUT).
>
> > +
> > +const uint8_t ff_dvdclut_yuv_crop_tab[256 + 2 *
> FF_DVDCLUT_YUV_NEG_CROP_MAX] = {
> > +times256(0x00),
> >
> +0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
> >
> +0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
> >
> +0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
> >
> +0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
> >
> +0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
> >
> +0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,
> >
> +0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
> >
> +0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,
> >
> +0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
> >
> +0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
> >
> +0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
> >
> +0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
> >
> +0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
> >
> +0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
> >
> +0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,
> >
> +0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF,
> > +times256(0xFF)
> > +};
>
> 1. This LUT is not used outside this module, so should be static if
> used. And therefore should not use an ff_ prefix at all.
> 2. Given that this code here is absolutely not speed relevant, the LUT
> can be avoided by using av_clip_uint8(value - 1024).
>
> > +
> > +int ff_dvdclut_palette_extradata_cat(const uint32_t *clut,
> > + const size_t clut_size,
> > + AVCodecParameters *par)
> > +{
> > + int ret = 0;
> > + AVBPrint bp;
> > +
> > + if (clut_size != FF_DVDCLUT_CLUT_SIZE)
> > + return AVERROR(EINVAL);
> > +
> > + av_bprint_init(&bp, 0, FF_DVDCLUT_EXTRADATA_SIZE);
> > +
> > + av_bprintf(&bp, "palette: ");
> > +
> > + for (int i = 0; i < FF_DVDCLUT_CLUT_LEN; i++)
> > + av_bprintf(&bp, "%06"PRIx32"%s", clut[i], i != 15 ? ", " : "");
>
> Don't hardcode 15 for the last iteration.
>
> > +
> > + av_bprintf(&bp, "\n");
> > +
> > + ret = ff_bprint_to_codecpar_extradata(par, &bp);
> > +
> > + av_bprint_finalize(&bp, NULL);
>
> Unnecessary, as ff_bprint_to_codecpar_extradata() has already finalized
> the bprint.
>
> > +
> > + return ret;
> > +}
> > +
> > +int ff_dvdclut_yuv_to_rgb(uint32_t *clut, const size_t clut_size)
> > +{
> > + const uint8_t *cm = ff_dvdclut_yuv_crop_tab +
> FF_DVDCLUT_YUV_NEG_CROP_MAX;
> > +
> > + int i, y, cb, cr;
> > + uint8_t r, g, b;
> > + int r_add, g_add, b_add;
>
> i should have loop scope and the other variables loop body scope.
>
> > +
> > + if (clut_size != FF_DVDCLUT_CLUT_SIZE)
> > + return AVERROR(EINVAL);
> > +
> > + for (i = 0; i < FF_DVDCLUT_CLUT_LEN; i++) {
> > + y = (clut[i] >> 16) & 0xFF;
> > + cr = (clut[i] >> 8) & 0xFF;
> > + cb = clut[i] & 0xFF;
> > +
> > + YUV_TO_RGB1_CCIR(cb, cr);
> > + YUV_TO_RGB2_CCIR(r, g, b, y);
> > +
> > + clut[i] = (r << 16) | (g << 8) | b;
> > + }
> > +
> > + return 0;
> > +}
> > diff --git a/libavformat/dvdclut.h b/libavformat/dvdclut.h
> > new file mode 100644
> > index 0000000000..40eff6de34
> > --- /dev/null
> > +++ b/libavformat/dvdclut.h
> > @@ -0,0 +1,37 @@
> > +/*
> > + * DVD-Video subpicture CLUT (Color Lookup Table) utilities
> > + *
> > + * 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
> <https://www.google.com/maps/search/hope+that+it+will+?entry=gmail&source=g>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
> > + */
> > +
> > +#ifndef AVFORMAT_DVDCLUT_H
> > +#define AVFORMAT_DVDCLUT_H
> > +
> > +#include "avformat.h"
>
> lavc/codec_par.h is enough
>
> > +
> > +/* ("palette: ") + ("rrggbb, "*15) + ("rrggbb") + \n + \0 */
> > +#define FF_DVDCLUT_EXTRADATA_SIZE (9 + (8 * 15) + 6 + 1 + 1)
> > +#define FF_DVDCLUT_CLUT_LEN 16
> > +#define FF_DVDCLUT_CLUT_SIZE FF_DVDCLUT_CLUT_LEN *
> sizeof(uint32_t)
> > +
> > +int ff_dvdclut_palette_extradata_cat(const uint32_t *clut,
> > + const size_t clut_size,
> > + AVCodecParameters *par);
> > +
> > +int ff_dvdclut_yuv_to_rgb(uint32_t *clut, const size_t clut_size);
> > +
> > +#endif /* AVFORMAT_DVDCLUT_H */
> > diff --git a/libavformat/dvdvideodec.c b/libavformat/dvdvideodec.c
> > index 2e363a4a22..657825ba3e 100644
> > --- a/libavformat/dvdvideodec.c
> > +++ b/libavformat/dvdvideodec.c
> > @@ -50,6 +50,7 @@
> > #include "avio_internal.h"
> > #include "avlanguage.h"
> > #include "demux.h"
> > +#include "dvdclut.h"
> > #include "internal.h"
> > #include "url.h"
> >
> > @@ -91,6 +92,7 @@ typedef struct DVDVideoPGCAudioStreamEntry {
> >
> > typedef struct DVDVideoPGCSubtitleStreamEntry {
> > int startcode;
> > + uint32_t *clut;
> > int disposition;
> > char *lang_iso;
> > enum DVDVideoSubpictureViewport viewport;
> > @@ -132,6 +134,7 @@ typedef struct DVDVideoDemuxContext {
> > int opt_region; /* the
> user-provided region digit */
> > int opt_preindex; /* pre-indexing
> mode (2-pass read) */
> > int opt_trim; /* trim padding
> cells at beginning and end */
> > + int opt_clut_rgb; /* output subtitle
> palette (CLUT) as RGB */
> >
> > /* subdemux */
> > const AVInputFormat *mpeg_fmt; /* inner MPEG-PS
> (VOB) demuxer */
> > @@ -1034,6 +1037,11 @@ static int
> dvdvideo_subp_stream_analyze(AVFormatContext *s, uint32_t offset, sub
> > if (subp_attr.lang_extension == 9)
> > entry->disposition |= AV_DISPOSITION_FORCED;
> >
> > + memcpy(entry->clut, c->play_state.pgc->palette,
> FF_DVDCLUT_CLUT_SIZE);
> > +
> > + if (c->opt_clut_rgb)
> > + ff_dvdclut_yuv_to_rgb(entry->clut, FF_DVDCLUT_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);
> >
> > @@ -1055,6 +1063,9 @@ static int
> dvdvideo_subp_stream_add(AVFormatContext *s, DVDVideoPGCSubtitleStrea
> > st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
> > st->codecpar->codec_id = AV_CODEC_ID_DVD_SUBTITLE;
> >
> > + if ((ret = ff_dvdclut_palette_extradata_cat(entry->clut,
> FF_DVDCLUT_CLUT_SIZE, st->codecpar)) < 0)
> > + return ret;
> > +
> > if (entry->lang_iso)
> > av_dict_set(&st->metadata, "language", entry->lang_iso, 0);
> >
> > @@ -1082,6 +1093,7 @@ static int
> dvdvideo_subp_stream_add_internal(AVFormatContext *s, uint32_t offset
> > int ret = 0;
> >
> > entry = av_mallocz(sizeof(DVDVideoPGCSubtitleStreamEntry));
> > + entry->clut = av_mallocz(FF_DVDCLUT_CLUT_SIZE);
> > entry->viewport = viewport;
> >
> > if ((ret = dvdvideo_subp_stream_analyze(s, offset, subp_attr,
> entry)) < 0)
> > @@ -1102,6 +1114,7 @@ end_free_error:
> > av_log(s, AV_LOG_ERROR, "Unable to allocate subtitle stream\n");
> >
> > end_free:
> > + av_freep(&entry->clut);
> > av_freep(&entry);
> >
> > return ret;
> > @@ -1381,6 +1394,7 @@ static const AVOption dvdvideo_options[] = {
> > {"angle", "playback angle number",
> OFFSET(opt_angle), AV_OPT_TYPE_INT, { .i64=1 },
> 1, 9, AV_OPT_FLAG_DECODING_PARAM },
> > {"chapter_end", "exit chapter (PTT) number (0=end)",
> OFFSET(opt_chapter_end), AV_OPT_TYPE_INT, { .i64=0 },
> 0, 99, AV_OPT_FLAG_DECODING_PARAM },
> > {"chapter_start", "entry chapter (PTT) number",
> OFFSET(opt_chapter_start), AV_OPT_TYPE_INT, { .i64=1 },
> 1, 99, AV_OPT_FLAG_DECODING_PARAM },
> > + {"clut_rgb", "output subtitle palette (CLUT) as RGB",
> OFFSET(opt_clut_rgb), AV_OPT_TYPE_BOOL, { .i64=1 },
> 0, 1, 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 },
> > {"pgc", "entry PGC number (0=auto)",
> OFFSET(opt_pgc), AV_OPT_TYPE_INT, { .i64=0 },
> 0, 999, AV_OPT_FLAG_DECODING_PARAM },
> > {"preindex", "enable for accurate chapter markers, slow
> (2-pass read)", OFFSET(opt_preindex), AV_OPT_TYPE_BOOL, { .i64=0
> }, 0, 1, AV_OPT_FLAG_DECODING_PARAM },
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [FFmpeg-devel] [PATCH v7 1/2] libavformat/dvdvideo: add DVD-Video demuxer powered by libdvdnav and libdvdread
2024-02-05 5:48 ` [FFmpeg-devel] [PATCH v7 1/2] " Marth64
2024-02-05 5:48 ` [FFmpeg-devel] [PATCH v7 2/2] libavformat/dvdvideo: add DVD CLUT utilities and enable palette support Marth64
2024-02-07 0:52 ` [FFmpeg-devel] [PATCH v7 1/2] libavformat/dvdvideo: add DVD-Video demuxer powered by libdvdnav and libdvdread Stefano Sabatini
@ 2024-02-07 18:58 ` Andreas Rheinhardt
2 siblings, 0 replies; 27+ messages in thread
From: Andreas Rheinhardt @ 2024-02-07 18:58 UTC (permalink / raw)
To: ffmpeg-devel
Marth64:
> Sorry for the quick follow-up to v6, I wanted to get this update out now hopefully
> before v6 is actually reviewed. v7 fixes a few documentation typos, options descriptions,
> and log messages: nothing major. I also flattened this out to be a 2-patch set
> with subtitle palette support.
>
> This is the result of >1yr of research and I am now very satisfied with it's result.
> I will not make any more changes at this point unless it is outcome from a review.
> If the community decides to merge it, I am happy to continue to improve it with support for
> seeking, probing, and tests.
>
> Thank you so much,
>
>
> Signed-off-by: Marth64 <marth64@proxyid.net>
> ---
> Changelog | 2 +
> configure | 8 +
> doc/demuxers.texi | 129 ++++
> libavformat/Makefile | 1 +
> libavformat/allformats.c | 1 +
> libavformat/dvdvideodec.c | 1410 +++++++++++++++++++++++++++++++++++++
> 6 files changed, 1551 insertions(+)
> create mode 100644 libavformat/dvdvideodec.c
>
> diff --git a/Changelog b/Changelog
> index c5fb21d198..88653bc6d3 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -24,6 +24,8 @@ version <next>:
> - ffmpeg CLI options may now be used as -/opt <path>, which is equivalent
> to -opt <contents of file <path>>
> - showinfo bitstream filter
> +- DVD-Video demuxer, powered by libdvdnav and libdvdread
> +
>
> version 6.1:
> - libaribcaption decoder
> diff --git a/configure b/configure
> index 68f675a4bc..70c33ec96d 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
> @@ -3520,6 +3524,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"
> @@ -6761,6 +6767,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/doc/demuxers.texi b/doc/demuxers.texi
> index e4c5b560a6..ad0906f6ec 100644
> --- a/doc/demuxers.texi
> +++ b/doc/demuxers.texi
> @@ -285,6 +285,135 @@ This demuxer accepts the following option:
>
> @end table
>
> +@section dvdvideo
> +
> +DVD-Video demuxer, powered by libdvdnav and libdvdread.
> +
> +Can directly ingest DVD titles, specifically sequential PGCs,
> +into a conversion pipeline. Menus and seeking are not supported at this time.
> +
> +Block devices (DVD drives), ISO files, and directory structures are accepted.
> +Activate with @code{-f dvdvideo} in front of one of these inputs.
> +
> +Underlying playback is fully handled by libdvdnav, and structure parsing by libdvdread.
> +ffmpeg must be built with GPL library support available as well as the switches
> +@code{--enable-libdvdnav} and @code{--enable-libdvdread}.
> +
> +You will need to provide either the desired "title number" or exact PGC/PG coordinates.
> +Many open-source DVD players and tools can aid in providing this information.
> +If not specified, the demuxer will default to title 1 which works for many discs.
> +However, due to the flexibility of DVD-Video, it is recommended to check manually.
> +There are many discs that are authored strangely or with invalid headers.
> +
> +If the input is a real DVD drive, please note that there are some drives which may
> +silently fail on reading bad sectors from the disc, returning random bits instead
> +which is effectively corrupt data. This is especially prominent on aging or rotting discs.
> +A second pass and integrity checks would be needed to detect the corruption.
> +This is not an ffmpeg issue.
> +
> +@subsection Background
> +
> +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 MPEG-PS streams that are stored in headerless VOB files.
> +To the end-user, these streams are known simply as "titles", but the actual
> +logical playback sequence is defined by one or more "PGCs", or Program Group Chains,
> +within the title. The PGC is in turn comprised of multiple "PGs", or Programs",
> +which are the actual video segments which, for a typical movie, are sequentially
> +ordered. The PGC structure, along with stream layout and metadata, are stored in
> +IFO files that need to be parsed. PGCs can be thought of as playlists in easier terms.
> +
> +An actual DVD player relies on user GUI interaction via menus and an internal VM
> +to drive the direction of demuxing. Generally, the user would either navigate (via menus)
> +or automatically be redirected to the PGC of their choice. During this process and
> +the subsequent playback, the DVD player's internal VM also maintains a state and
> +executes instructions that can create jumps to different sectors during playback.
> +This is why libdvdnav is involved, as a linear read of the MPEG-PS blobs on the
> +disc (VOBs) is not enough to produce the right sequence in many cases.
> +
> +There are many other DVD structures (a long subject) that will not be discussed here.
> +NAV packets, in particular, are handled by this demuxer to build accurate timing
> +but not emitted as a stream. For a good high-level understanding, refer to:
> +@url{https://code.videolan.org/videolan/libdvdnav/-/blob/master/doc/dvd_structures}
> +
> +@subsection Options
> +
> +This demuxer accepts the following options:
> +
> +@table @option
> +
> +@item title
> +The title number to play. Must be set if @option{pgc} and @option{pg} are not set.
> +Default is 0 (auto), which currently only selects the first available title (title 1)
> +and notifies the user about the implications.
> +
> +@item chapter_start
> +The chapter, or PTT (part-of-title), number to start at. Default is 1.
> +
> +@item chapter_end
> +The chapter, or PTT (part-of-title), number to end at. Default is 0,
> +which is a special value to signal end at the last possible chapter.
> +
> +@item angle
> +The video angle number, referring to what is essentially an additional
> +video stream that is composed from alternate frames interleaved in the VOBs.
> +Default is 1.
> +
> +@item region
> +The region code to use for playback. Some discs may use this to default playback
> +at a particular angle in different regions. This option will not affect the region code
> +of a real DVD drive, if used as an input. Default is 0, "world".
> +
> +@item pgc
> +The entry PGC to start playback, in conjunction with @option{pg}.
> +Alternative to setting @option{title}.
> +Chapter markers not supported at this time.
> +Default is 0, automatically resolve from value of @option{title}.
> +
> +@item pg
> +The entry PG to start playback, in conjunction with @option{pgc}.
> +Alternative to setting @option{title}.
> +Chapter markers not supported at this time.
> +Default is 0, automatically resolve from value of @option{title}.
> +
> +@item preindex
> +Enable this to have accurate chapter (PTT) markers and duration measurement,
> +which requires a slow second pass read in order to index the chapter
> +timestamps from NAV packets. This is non-ideal extra work for physical DVD drives.
> +Not compatible with @option{pgc} and @option{pg}.
> +Default is 0, false.
> +
> +@item trim
> +Skip padding cells (i.e. cells shorter than 1 second) from the beginning.
> +There exist many discs with filler segments at the beginning of the PGC,
> +often with junk data intended for controlling a real DVD player's
> +buffering speed and with no other material data value.
> +Default is 1, true.
> +
> +@end table
> +
> +@subsection Examples
> +
> +@itemize
> +@item
> +Open title 3 from a given DVD structure:
> +@example
> +ffmpeg -f dvdvideo -title 3 -i <path to DVD> ...
> +@end example
> +
> +@item
> +Open chapters 3-6 from title 1 from a given DVD structure:
> +@example
> +ffmpeg -f dvdvideo -chapter_start 3 -chapter_end 6 -title 1 -i <path to DVD> ...
> +@end example
> +
> +@item
> +Open only chapter 5 from title 1 from a given DVD structure:
> +@example
> +ffmpeg -f dvdvideo -chapter_start 5 -chapter_end 5 -title 1 -i <path to DVD> ...
> +@end example
> +@end itemize
> +
> @section ea
>
> Electronic Arts Multimedia format demuxer.
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index 05b9b8a115..df69734877 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 b04b43cab3..3e905c23f8 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/dvdvideodec.c b/libavformat/dvdvideodec.c
> new file mode 100644
> index 0000000000..2e363a4a22
> --- /dev/null
> +++ b/libavformat/dvdvideodec.c
> @@ -0,0 +1,1410 @@
> +/*
> + * DVD-Video demuxer, powered by libdvdnav and libdvdread
> + *
> + * 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
> + */
> +
> +/*
> + * See doc/demuxers.texi for a high-level overview.
> + *
> + * The tactical approach is as follows:
> + * 1) Open the volume with dvdread
> + * 2) Analyze the user-requested title and PGC coordinates in the IFO structures
> + * 3) Request playback at the coordinates and chosen angle with dvdnav
> + * 5) Begin the playback (reading and demuxing) of MPEG-PS blocks
> + * 6) End playback if navigation goes backwards, to a menu, or a different PGC or angle
> + * 7) Close the dvdnav VM, and free dvdread's IFO structures
> + */
> +
> +#include <dvdnav/dvdnav.h>
> +#include <dvdread/dvd_reader.h>
> +#include <dvdread/ifo_read.h>
> +#include <dvdread/ifo_types.h>
> +#include <dvdread/nav_read.h>
> +
> +#include "libavcodec/avcodec.h"
Seems unused.
> +#include "libavutil/avstring.h"
> +#include "libavutil/avutil.h"
> +#include "libavutil/intreadwrite.h"
> +#include "libavutil/mem.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/samplefmt.h"
> +#include "libavutil/time.h"
> +#include "libavutil/timestamp.h"
> +
> +#include "avformat.h"
> +#include "avio_internal.h"
> +#include "avlanguage.h"
> +#include "demux.h"
> +#include "internal.h"
> +#include "url.h"
What is this header used for?
> +
> +#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 64 /* VOBUs use 32 (PES allows 33) */
> +#define DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE 1024
> +
> +enum DVDVideoSubpictureViewport {
> + DVDVIDEO_SUBP_VIEWPORT_FULLSCREEN,
> + DVDVIDEO_SUBP_VIEWPORT_WIDESCREEN,
> + DVDVIDEO_SUBP_VIEWPORT_LETTERBOX,
> + DVDVIDEO_SUBP_VIEWPORT_PANSCAN
> +};
> +const char* VIEWPORT_LABELS[4] = { "Fullscreen", "Widescreen", "Letterbox", "Pan and Scan" };
1. Should be static.
2. No uppercase names; and the '*' should be put at the variable name,
not the type (int* a,b declares a as pointer to int and b as int!).
3. No relocations, please: static const char viewport_labels[4][13].
> +
> +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;
> + int disposition;
> + char *lang_iso;
Should be const
> +} DVDVideoPGCAudioStreamEntry;
> +
> +typedef struct DVDVideoPGCSubtitleStreamEntry {
> + int startcode;
> + int disposition;
> + char *lang_iso;
Should be const
> + enum DVDVideoSubpictureViewport viewport;
> +} DVDVideoPGCSubtitleStreamEntry;
> +
> +typedef struct DVDVideoPlaybackState {
> + int celln; /* ID of the active cell */
> + int entry_pgn; /* ID of the PG we are starting in */
> + int in_pgc; /* if our navigator is in the PGC */
> + int in_ps; /* if our navigator is in the program stream */
> + int in_vts; /* if our navigator is in the VTS */
> + int64_t nav_pts; /* PTS according to IFO, not frame-accurate */
> + int nb_vobus_played; /* number of VOBUs played back so far */
> + uint64_t pgc_duration_est; /* estimated duration as reported by IFO */
> + uint64_t pgc_elapsed; /* the elapsed time of the PGC, cell-relative */
> + int pgc_nb_pg_est; /* number of PGs as reported by IFOs */
> + int pgcn; /* ID of the PGC we are playing */
> + int pgn; /* ID of the PG we are in now */
> + int ptt; /* ID of the chapter we are in now */
> + int64_t ts_offset; /* PTS discontinuity offset (ex. VOB change) */
> + uint32_t vobu_duration; /* duration of the current VOBU */
> + uint32_t vobu_e_ptm; /* end PTS of the current VOBU */
> + int vtsn; /* ID of the active VTS (video title set) */
> + uint64_t *pgc_pg_times_est; /* PG start times as reported by IFO */
> + pgc_t *pgc; /* handle to the active PGC */
> + dvdnav_t *dvdnav; /* handle to libdvdnav */
> +} DVDVideoPlaybackState;
> +
> +typedef struct DVDVideoDemuxContext {
> + const AVClass *class;
> +
> + /* options */
> + int opt_title; /* the user-provided title number (1-indexed) */
> + int opt_chapter_start; /* the user-provided entry PTT (1-indexed) */
> + int opt_chapter_end; /* the user-provided exit PTT (0 for last) */
> + 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 */
> + int opt_preindex; /* pre-indexing mode (2-pass read) */
> + int opt_trim; /* trim padding cells at beginning and end */
> +
> + /* 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) */
> +
> + /* playback control */
> + int play_end; /* signal EOF to the parent demuxer */
> + DVDVideoPlaybackState play_state; /* the active playback state */
> + int play_started; /* signal that playback has started */
> + int play_seg_started; /* signal that segment has started */
> + int64_t first_pts; /* the starting PTS offset of the PGC */
> + uint64_t duration_ptm; /* total duration in DVD MPEG timebase */
> +} DVDVideoDemuxContext;
> +
> +static void dvdvideo_libdvdread_log(void *opaque, dvd_logger_level_t level,
> + const char *msg, va_list msg_va)
> +{
> + AVFormatContext *s = opaque;
> + int lavu_level;
> +
> + char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
This initialization is unnecessary, as vsnprintf works with
uninitialized buffers and zero-terminates them on its own.
> + vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
Should use sizeof(msf_buf). Same for the other log callback.
> +
> + switch (level) {
> + case DVD_LOGGER_LEVEL_ERROR:
> + lavu_level = AV_LOG_ERROR;
> + break;
> + case DVD_LOGGER_LEVEL_WARN:
> + lavu_level = AV_LOG_WARNING;
> + break;
> + /* dvdread's info messages are relatively very verbose, muffle them as debug */
> + case DVD_LOGGER_LEVEL_INFO:
> + case DVD_LOGGER_LEVEL_DEBUG:
> + default:
> + lavu_level = AV_LOG_DEBUG;
> + }
> +
> + av_log(s, lavu_level, "libdvdread: %s\n", msg_buf);
> +}
> +
> +static void dvdvideo_libdvdnav_log(void *opaque, dvdnav_logger_level_t level,
> + const char *msg, va_list msg_va)
> +{
> + AVFormatContext *s = opaque;
> + int lavu_level;
> +
> + char msg_buf[DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE] = {0};
> + vsnprintf(msg_buf, DVDVIDEO_LIBDVDX_LOG_BUFFER_SIZE, msg, msg_va);
> +
> + switch (level) {
> + case DVDNAV_LOGGER_LEVEL_ERROR:
> + lavu_level = AV_LOG_ERROR;
> + break;
> + case DVDNAV_LOGGER_LEVEL_WARN:
> + /* some discs have invalid language codes set for their menus, muffle the noise */
> + lavu_level = av_strstart(msg, "Language", NULL) ? AV_LOG_DEBUG : AV_LOG_WARNING;
> + break;
> + /* dvdnav's info messages are relatively very verbose, muffle them as debug */
> + case DVDNAV_LOGGER_LEVEL_INFO:
> + case DVDNAV_LOGGER_LEVEL_DEBUG:
> + default:
> + lavu_level = AV_LOG_DEBUG;
> + }
> +
> + av_log(s, lavu_level, "libdvdnav: %s\n", msg_buf);
> +}
Please refactor the common code of these loggers.
> +
> +static void dvdvideo_ifo_close(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + 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_ifo_open(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + dvd_logger_cb dvdread_log_cb;
> + title_info_t title_info;
> +
> + dvdread_log_cb = (dvd_logger_cb) { .pf_log = dvdvideo_libdvdread_log };
> + c->dvdread = DVDOpen2(s, &dvdread_log_cb, s->url);
> +
> + if (!c->dvdread) {
> + av_log(s, AV_LOG_ERROR, "Unable to open the DVD-Video structure\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + if (!(c->vmg_ifo = ifoOpen(c->dvdread, 0))) {
> + av_log(s, AV_LOG_ERROR, "Unable to open the VMG (VIDEO_TS.IFO)\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + if (c->opt_title > c->vmg_ifo->tt_srpt->nr_of_srpts) {
> + av_log(s, AV_LOG_ERROR, "Title %d not found\n", c->opt_title);
> +
> + 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 %d not found\n", c->opt_angle);
> +
> + return AVERROR_STREAM_NOT_FOUND;
> + }
> +
> + if (title_info.nr_of_ptts < 1) {
> + av_log(s, AV_LOG_ERROR, "Title %d has invalid headers (no parts)\n", c->opt_title);
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + if (c->opt_chapter_start > title_info.nr_of_ptts
> + || (c->opt_chapter_end > 0 && c->opt_chapter_end > title_info.nr_of_ptts)) {
> + av_log(s, AV_LOG_ERROR, "Chapter (PTT) range [%d, %d] is invalid\n",
> + c->opt_chapter_start, c->opt_chapter_end);
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + if (!(c->vts_ifo = ifoOpen(c->dvdread, title_info.title_set_nr))) {
> + av_log(s, AV_LOG_ERROR, "Unable to process the VTS IFO structure\n");
> +
> + return AVERROR_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 %d has invalid headers in VTS\n", c->opt_title);
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + return 0;
> +}
> +
> +static void dvdvideo_play_close(AVFormatContext *s, DVDVideoPlaybackState *state)
> +{
> + if (!state->dvdnav)
> + return;
> +
> + /* not allocated by av_malloc() */
> + if (state->pgc_pg_times_est)
> + free(state->pgc_pg_times_est);
> +
> + if (dvdnav_close(state->dvdnav) < 0)
> + av_log(s, AV_LOG_ERROR, "Unable to cleanly close dvdnav\n");
> +}
> +
> +static int dvdvideo_play_open(AVFormatContext *s, DVDVideoPlaybackState *state)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret = 0;
> + dvdnav_logger_cb dvdnav_log_cb;
> + dvdnav_status_t dvdnav_open_status;
> + int cur_title, cur_pgcn, cur_pgn;
> + pgc_t *pgc;
> +
> + int32_t disc_region_mask;
> + int32_t player_region_mask;
> +
> + dvdnav_log_cb = (dvdnav_logger_cb) { .pf_log = dvdvideo_libdvdnav_log };
> + dvdnav_open_status = dvdnav_open2(&state->dvdnav, s, &dvdnav_log_cb, s->url);
> +
> + if (!state->dvdnav
> + || dvdnav_open_status != DVDNAV_STATUS_OK
> + || dvdnav_set_readahead_flag(state->dvdnav, 0) != DVDNAV_STATUS_OK
> + || dvdnav_set_PGC_positioning_flag(state->dvdnav, 1) != DVDNAV_STATUS_OK
> + || dvdnav_get_region_mask(state->dvdnav, &disc_region_mask) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to open the DVD for playback\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + player_region_mask = c->opt_region > 0 ? (1 << (c->opt_region - 1)) : disc_region_mask;
> + if (dvdnav_set_region_mask(state->dvdnav, player_region_mask) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to set the playback region code %d\n", c->opt_region);
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + if (c->opt_pgc > 0 && c->opt_pg > 0) {
> + if (dvdnav_program_play(state->dvdnav, c->opt_title,
> + c->opt_pgc, c->opt_pg) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to start playback at title %d, PGC %d, PG %d\n",
> + c->opt_title, c->opt_pgc, c->opt_pg);
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + state->pgcn = c->opt_pgc;
> + state->entry_pgn = c->opt_pg;
> + } else {
> + if (dvdnav_part_play(state->dvdnav, c->opt_title, c->opt_chapter_start) != DVDNAV_STATUS_OK
> + || dvdnav_current_title_program(state->dvdnav, &cur_title,
> + &cur_pgcn, &cur_pgn) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to start playback at title %d, chapter (PTT) %d\n",
> + c->opt_title, c->opt_chapter_start);
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + state->pgcn = cur_pgcn;
> + state->entry_pgn = cur_pgn;
> + }
> +
> + pgc = c->vts_ifo->vts_pgcit->pgci_srp[state->pgcn - 1].pgc;
> +
> + if (pgc->pg_playback_mode != 0) {
> + av_log(s, AV_LOG_ERROR, "Non-sequential PGCs, such as shuffles, are not supported\n");
> +
> + return AVERROR_PATCHWELCOME;
> + }
> +
> + if (dvdnav_angle_change(state->dvdnav, c->opt_angle) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to start playback at the given angle\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + /* dvdnav_describe_title_chapters() performs several safety checks on the title structure */
> + /* take advantage of this side effect to ensure a safe navigation path */
> + state->pgc_nb_pg_est = dvdnav_describe_title_chapters(state->dvdnav, c->opt_title,
> + &state->pgc_pg_times_est,
> + &state->pgc_duration_est);
> + if (!state->pgc_nb_pg_est) {
> + av_log(s, AV_LOG_ERROR, "Unable to validate title structure\n");
> +
> + return AVERROR_EXTERNAL;
> + }
> +
> + state->nav_pts = dvdnav_get_current_time(state->dvdnav);
> + state->vtsn = c->vmg_ifo->tt_srpt->title[c->opt_title - 1].title_set_nr;
> +
> + state->pgc = pgc;
> +
> + return ret;
> +}
> +
> +static int dvdvideo_play_is_cell_promising(AVFormatContext *s, DVDVideoPlaybackState *state,
> + int celln)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> + dvd_time_t cell_duration;
> +
> + if (!c->opt_trim)
> + return 1;
> +
> + cell_duration = state->pgc->cell_playback[celln - 1].playback_time;
> +
> + return cell_duration.second >= 1 || cell_duration.minute >= 1 || cell_duration.hour >= 1;
> +}
> +
> +static int dvdvideo_play_next_ps_block(AVFormatContext *s, DVDVideoPlaybackState *state,
> + uint8_t *buf, int buf_size,
> + int *p_nav_event,
> + void (*flush_cb)(AVFormatContext *s))
> +{
> + 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;
> + int is_cell_promising = 0;
> + pci_t *e_pci;
> + dsi_t *e_dsi;
> +
> + 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(state->dvdnav, nav_buf, &nav_event, &nav_len) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to read next block of PGC\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + /* some discs follow NOPs with a premature stop event */
> + if (nav_event == DVDNAV_STOP)
> + return AVERROR_EOF;
> +
> + if (nav_len > DVDVIDEO_BLOCK_SIZE) {
> + av_log(s, AV_LOG_ERROR, "Invalid block size\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + if (dvdnav_current_title_info(state->dvdnav, &cur_title,
> + &cur_ptt) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to validate title coordinates\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + /* we somehow navigated to a menu */
> + if (cur_title == 0 || !dvdnav_is_domain_vts(state->dvdnav))
> + return AVERROR_EOF;
> +
> + if (dvdnav_current_title_program(state->dvdnav, &cur_title_unused,
> + &cur_pgcn, &cur_pgn) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to validate PGC coordinates\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + /* we somehow left the PGC */
> + if (state->in_pgc && cur_pgcn != state->pgcn)
> + return AVERROR_EOF;
> +
> + if (dvdnav_get_angle_info(state->dvdnav, &cur_angle, &cur_nb_angles) != DVDNAV_STATUS_OK) {
> + av_log(s, AV_LOG_ERROR, "Unable to validate angle coordinates\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + av_log(s, nav_event == DVDNAV_BLOCK_OK ? AV_LOG_TRACE : AV_LOG_DEBUG,
> + "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, state->celln, cur_pgcn, cur_pgn,
> + state->in_vts, state->in_pgc, state->in_ps);
> +
> + switch (nav_event) {
> + case DVDNAV_VTS_CHANGE:
> + if (state->in_vts)
> + return AVERROR_EOF;
> +
> + e_vts = (dvdnav_vts_change_event_t *) nav_buf;
> +
> + if (e_vts->new_vtsN == state->vtsn && e_vts->new_domain == DVD_DOMAIN_VTSTitle)
> + state->in_vts = 1;
> +
> + continue;
> + case DVDNAV_CELL_CHANGE:
> + if (!state->in_vts)
> + continue;
> +
> + e_cell = (dvdnav_cell_change_event_t *) nav_buf;
> + is_cell_promising = dvdvideo_play_is_cell_promising(s, state, e_cell->cellN);
> +
> + av_log(s, AV_LOG_DEBUG, "new cell: prev=%d new=%d promising=%d\n",
> + state->celln, e_cell->cellN, is_cell_promising);
> +
> + if (!state->in_ps && !state->in_pgc) {
> + if (cur_title == c->opt_title && cur_ptt == c->opt_chapter_start
> + && cur_pgcn == state->pgcn && cur_pgn == state->entry_pgn
> + && is_cell_promising) {
> + state->in_pgc = 1;
> + }
> +
> + if (c->opt_trim && !is_cell_promising)
> + av_log(s, AV_LOG_INFO, "Skipping padding cell #%d\n", e_cell->cellN);
> + } else if (state->celln >= e_cell->cellN || state->pgn > cur_pgn) {
> + return AVERROR_EOF;
> + }
> +
> + state->celln = e_cell->cellN;
> + state->ptt = cur_ptt;
> + state->pgn = cur_pgn;
> +
> + continue;
> + case DVDNAV_NAV_PACKET:
> + if (!state->in_pgc)
> + continue;
> +
> + if ((state->ptt > 0 && state->ptt > cur_ptt)
> + || (c->opt_chapter_end > 0 && cur_ptt > c->opt_chapter_end)) {
> + return AVERROR_EOF;
> + }
> +
> + e_pci = dvdnav_get_current_nav_pci(state->dvdnav);
> + e_dsi = dvdnav_get_current_nav_dsi(state->dvdnav);
> +
> + if (e_pci == NULL || e_dsi == NULL
> + || e_pci->pci_gi.vobu_s_ptm > e_pci->pci_gi.vobu_e_ptm) {
> + av_log(s, AV_LOG_ERROR, "Invalid NAV packet\n");
> +
> + return AVERROR_INVALIDDATA;
> + }
> +
> + state->vobu_duration = e_pci->pci_gi.vobu_e_ptm - e_pci->pci_gi.vobu_s_ptm;
> + state->pgc_elapsed += state->vobu_duration;
> + state->nav_pts = dvdnav_get_current_time(state->dvdnav);
> + state->ptt = cur_ptt;
> + state->pgn = cur_pgn;
> + state->nb_vobus_played++;
> +
> + av_log(s, AV_LOG_DEBUG, "NAV pack: s_ptm=%d e_ptm=%d "
> + "scr=%d lbn=%d vobu_duration=%d nav_pts=%ld\n",
> + e_pci->pci_gi.vobu_s_ptm, e_pci->pci_gi.vobu_e_ptm,
> + e_dsi->dsi_gi.nv_pck_scr,
> + e_pci->pci_gi.nv_pck_lbn, state->vobu_duration, state->nav_pts);
> +
> + if (!state->in_ps) {
> + av_log(s, AV_LOG_DEBUG, "navigation: locked to program stream\n");
> +
> + state->in_ps = 1;
> + } else {
> + if (state->vobu_e_ptm != e_pci->pci_gi.vobu_s_ptm) {
> + if (flush_cb)
> + flush_cb(s);
> +
> + state->ts_offset += state->vobu_e_ptm - e_pci->pci_gi.vobu_s_ptm;
> + }
> + }
> +
> + state->vobu_e_ptm = e_pci->pci_gi.vobu_e_ptm;
> +
> + (*p_nav_event) = nav_event;
> +
> + return nav_len;
> + case DVDNAV_BLOCK_OK:
> + if (!state->in_ps) {
> + if (state->in_pgc)
> + i = 0; /* necessary in case we are skipping junk cells at the beginning */
> + continue;
> + }
> +
> + if (nav_len != DVDVIDEO_BLOCK_SIZE) {
> + av_log(s, AV_LOG_ERROR, "Invalid block size\n");
> +
> + 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);
> +
> + /* in case NAV packet is missed */
> + state->ptt = cur_ptt;
> + state->pgn = cur_pgn;
> +
> + (*p_nav_event) = nav_event;
> +
> + return nav_len;
> + case DVDNAV_STILL_FRAME:
> + case DVDNAV_WAIT:
> + case DVDNAV_HOP_CHANNEL:
> + case DVDNAV_HIGHLIGHT:
> + if (state->in_ps)
> + return AVERROR_EOF;
> +
> + if (nav_event == DVDNAV_STILL_FRAME)
> + dvdnav_still_skip(state->dvdnav);
> + if (nav_event == DVDNAV_WAIT)
> + dvdnav_wait_skip(state->dvdnav);
> +
> + 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_INVALIDDATA;
> +}
> +
> +static int dvdvideo_pgc_preindex(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret = 0, interrupt = 0;
> + int nb_chapters = 0, last_ptt = c->opt_chapter_start;
> + uint64_t cur_chapter_offset = 0, cur_chapter_duration = 0;
> + DVDVideoPlaybackState *state;
> +
> + uint8_t nav_buf[DVDVIDEO_BLOCK_SIZE];
> + int nav_event;
> +
> + if (c->opt_chapter_start == c->opt_chapter_end)
> + return 0;
> +
> + av_log(s, AV_LOG_INFO,
> + "Indexing chapter markers, this will take a long time. Please wait...\n");
> +
> + state = av_mallocz(sizeof(DVDVideoPlaybackState));
Should be put on the stack. Also: Missing check.
> + if ((ret = dvdvideo_play_open(s, state)) < 0) {
> + av_freep(&state);
> + return ret;
> + }
> +
> + while (!(interrupt = ff_check_interrupt(&s->interrupt_callback))) {
> + ret = dvdvideo_play_next_ps_block(s, state, nav_buf, DVDVIDEO_BLOCK_SIZE,
> + &nav_event, NULL);
> + if (ret < 0 && ret != AVERROR_EOF)
> + goto end_free;
> +
> + if (nav_event != DVDNAV_NAV_PACKET && ret != AVERROR_EOF)
> + continue;
> +
> + if (state->ptt == last_ptt) {
> + cur_chapter_duration += state->vobu_duration;
> + /* ensure we add the last chapter */
> + if (ret != AVERROR_EOF)
> + continue;
> + }
> +
> + if (!avpriv_new_chapter(s, nb_chapters, DVDVIDEO_TIME_BASE_Q, cur_chapter_offset,
> + cur_chapter_offset + cur_chapter_duration, NULL)) {
> + av_log(s, AV_LOG_ERROR, "Unable to allocate chapter\n");
> +
> + goto end_free;
> + }
> +
> + nb_chapters++;
> + cur_chapter_offset += cur_chapter_duration;
> + cur_chapter_duration = state->vobu_duration;
> + last_ptt = state->ptt;
> +
> + if (ret == AVERROR_EOF)
> + break;
> + }
> +
> + if (interrupt) {
> + ret = AVERROR_EXIT;
> + goto end_free;
> + }
> +
> + if (ret < 0 && ret != AVERROR_EOF)
> + goto end_free;
> +
> + s->duration = av_rescale_q(state->pgc_elapsed, DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
> + c->duration_ptm = state->pgc_elapsed;
> +
> + av_log(s, AV_LOG_INFO, "Chapter marker indexing complete\n");
> + ret = 0;
> +
> +end_free:
> + dvdvideo_play_close(s, state);
> + av_freep(&state);
> +
> + return ret;
> +}
> +
> +static int dvdvideo_pgc_chapters_setup(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + uint64_t time_prev = 0;
> + int64_t total_duration = 0;
> +
> + int chapter_start = c->opt_chapter_start - 1;
> + int chapter_end = c->opt_chapter_end > 0 ? c->opt_chapter_end : c->play_state.pgc_nb_pg_est - 1;
> +
> + if (chapter_start == chapter_end || c->play_state.pgc_nb_pg_est == 1) {
> + s->duration = av_rescale_q(c->play_state.pgc_duration_est,
> + DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
> + return 0;
> + }
> +
> + for (int i = chapter_start; i < chapter_end; i++) {
> + uint64_t time_effective = c->play_state.pgc_pg_times_est[i] - c->play_state.nav_pts;
> +
> + 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");
Pointless log.
> + return AVERROR(ENOMEM);
> + }
> +
> + time_prev = time_effective;
> + total_duration = time_effective;
> + }
> +
> + if (c->opt_chapter_start == 1 && c->opt_chapter_end == 0)
> + s->duration = av_rescale_q(c->play_state.pgc_duration_est,
> + DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
> + else
> + s->duration = av_rescale_q(total_duration,
> + DVDVIDEO_TIME_BASE_Q, AV_TIME_BASE_Q);
> +
> + return 0;
> +}
> +
> +static int dvdvideo_video_stream_analyze(AVFormatContext *s, 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) {
> + av_log(s, AV_LOG_ERROR, "Invalid video dimensions\n");
> +
> + 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 = !is_pal && (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 = NULL;
> + int ret = 0;
> +
> + entry = av_mallocz(sizeof(DVDVideoVTSVideoStreamEntry));
Should be put on the stack. Also: Missing check.
> +
> + if ((ret = dvdvideo_video_stream_analyze(s, 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_HEADERS)) < 0) {
> + av_freep(&entry);
> + av_log(s, AV_LOG_ERROR, "Unable to allocate video stream\n");
> +
> + return ret;
> + }
> +
> + av_freep(&entry);
> +
> + return ret;
> +}
> +
> +static int dvdvideo_audio_stream_analyze(AVFormatContext *s, 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 = (AVChannelLayout) {0};
> + char lang_dvd[3] = {0};
> +
> + int position = (audio_control & 0x7F00) >> 8;
> +
> + /* XXX(PATCHWELCOME): SDDS is not supported due to lack of sample material */
> + switch (audio_attr.audio_format) {
> + case 0: /* AC3 */
> + codec_id = AV_CODEC_ID_AC3;
> + sample_fmt = AV_SAMPLE_FMT_FLTP;
> + sample_rate = 48000;
> + startcode = 0x80 + position;
> + break;
> + case 2: /* MP1 */
> + 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: /* MP2 */
> + 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: /* DVD PCM */
> + codec_id = AV_CODEC_ID_PCM_DVD;
> + 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: /* DCA */
> + 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) {
> + av_log(s, AV_LOG_ERROR, "Invalid audio parameters\n");
> +
> + 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 == 7)
> + ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_6POINT1;
> + else if (nb_channels == 8)
> + ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_7POINT1;
> +
> + if (audio_attr.code_extension == 2)
> + entry->disposition |= AV_DISPOSITION_VISUAL_IMPAIRED;
> + if (audio_attr.code_extension == 3 || audio_attr.code_extension == 4)
> + entry->disposition |= AV_DISPOSITION_COMMENT;
> +
> + 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;
> + st->disposition = entry->disposition;
> +
> + 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 = 0;
> +
> + for (int i = 0; i < c->vts_ifo->vtsi_mat->nr_of_vts_audio_streams; i++) {
> + DVDVideoPGCAudioStreamEntry *entry = NULL;
> +
> + if (!(c->play_state.pgc->audio_control[i] & 0x8000))
> + continue;
> +
> + entry = av_mallocz(sizeof(DVDVideoPGCAudioStreamEntry));
Should be put on the stack.
> + if (!entry)
> + return AVERROR(ENOMEM);
> +
> + if ((ret = dvdvideo_audio_stream_analyze(s, c->vts_ifo->vtsi_mat->vts_audio_attr[i],
> + c->play_state.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, "Karaoke metadata in stream %d will not be retained\n",
> + entry->startcode);
Why not? We have AV_DISPOSITION_KARAOKE.
> +
> + /* IFO structures can declare duplicate entries for the same 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
> + || (ret = dvdvideo_audio_stream_add(s, entry, AVSTREAM_PARSE_HEADERS)) < 0)
> + goto break_free_and_error;
> +
> +continue_free:
> + av_freep(&entry);
> + continue;
> +
> +break_free_and_error:
> + av_freep(&entry);
> + av_log(s, AV_LOG_ERROR, "Unable to allocate audio stream\n");
> + return ret;
> + }
> +
> + return ret;
> +}
> +
> +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);
> +
> + if (subp_attr.lang_extension == 9)
> + entry->disposition |= AV_DISPOSITION_FORCED;
> +
> + 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;
> + int ret = 0;
> +
> + 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;
> +
> + if (entry->lang_iso)
> + av_dict_set(&st->metadata, "language", entry->lang_iso, 0);
> +
> + av_dict_set(&st->metadata, "VIEWPORT", VIEWPORT_LABELS[entry->viewport], 0);
> +
> + st->disposition = entry->disposition;
> +
> + 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 ret;
> +}
> +
> +static int dvdvideo_subp_stream_add_internal(AVFormatContext *s, uint32_t offset,
> + subp_attr_t subp_attr,
> + enum DVDVideoSubpictureViewport viewport)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + DVDVideoPGCSubtitleStreamEntry *entry = NULL;
> + int ret = 0;
> +
> + entry = av_mallocz(sizeof(DVDVideoPGCSubtitleStreamEntry));
Unchecked allocation. Also use sizeof(*entry) to avoid typing.
But actually this need not be allocated at all, as this thing can just
be put on the stack.
> + entry->viewport = viewport;
> +
> + if ((ret = dvdvideo_subp_stream_analyze(s, offset, subp_attr, entry)) < 0)
> + goto end_free_error;
> +
> + /* IFO structures can declare duplicate entries for the same startcode */
> + 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
This makes this code add an AVStream to the child AVFormatContext. This
is not how it is supposed to be. You may not add AVStreams behind an
input AVFormatContext's back.
> + || (ret = dvdvideo_subp_stream_add(s, entry, AVSTREAM_PARSE_HEADERS)) < 0)
> + goto end_free_error;
> +
> + goto end_free;
> +
> +end_free_error:
> + av_log(s, AV_LOG_ERROR, "Unable to allocate subtitle stream\n");
No av_log for a some small allocations.
> +
> +end_free:
> + av_freep(&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 = 0;
> + uint32_t subp_control;
> + subp_attr_t subp_attr;
> + video_attr_t video_attr;
> +
> + subp_control = c->play_state.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,
> + DVDVIDEO_SUBP_VIEWPORT_FULLSCREEN)) < 0)
> + return ret;
> +
> + continue;
> + }
> +
> + /* 16:9 */
> + if ((ret = dvdvideo_subp_stream_add_internal(s, subp_control >> 16, subp_attr,
> + DVDVIDEO_SUBP_VIEWPORT_WIDESCREEN)) < 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,
> + DVDVIDEO_SUBP_VIEWPORT_LETTERBOX)) < 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,
> + DVDVIDEO_SUBP_VIEWPORT_PANSCAN)) < 0)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void dvdvideo_subdemux_flush(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + if (!c->play_seg_started)
> + return;
> +
> + av_log(s, AV_LOG_DEBUG, "flushing sub-demuxer\n");
> + avio_flush(&c->mpeg_pb.pub);
> + ff_read_frame_flush(c->mpeg_ctx);
> + c->play_seg_started = 0;
> +}
> +
> +static int dvdvideo_subdemux_read_data(void *opaque, uint8_t *buf, int buf_size)
> +{
> + AVFormatContext *s = opaque;
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret = 0;
> + int nav_event;
> +
> + if (c->play_end)
> + return AVERROR_EOF;
> +
> + ret = dvdvideo_play_next_ps_block(opaque, &c->play_state, buf, buf_size,
> + &nav_event, dvdvideo_subdemux_flush);
> +
> + if (ret == AVERROR_EOF) {
> + c->mpeg_pb.pub.eof_reached = 1;
> + c->play_end = 1;
> +
> + return AVERROR_EOF;
> + }
> +
> + if (ret >= 0 && nav_event == DVDNAV_NAV_PACKET)
> + return FFERROR_REDO;
> +
> + return ret;
> +}
> +
> +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 = 0;
> +
> + if (!(c->mpeg_fmt = av_find_input_format("mpeg")))
> + return AVERROR_DEMUXER_NOT_FOUND;
> +
> + if (!(c->mpeg_ctx = avformat_alloc_context()))
> + return AVERROR(ENOMEM);
> +
> + if (!(c->mpeg_buf = av_mallocz(DVDVIDEO_BLOCK_SIZE))) {
> + avformat_free_context(c->mpeg_ctx);
> +
This is dangerous: It frees the context, but leaves a dangling pointer
behind, which will cause a use-after-free in the read-close function
which is automatically called due to FF_FMT_INIT_CLEANUP.
> + return AVERROR(ENOMEM);
> + }
> +
> + ffio_init_context(&c->mpeg_pb, c->mpeg_buf, DVDVIDEO_BLOCK_SIZE, 0, s,
> + dvdvideo_subdemux_read_data, NULL, NULL);
> + c->mpeg_pb.pub.seekable = 0;
> +
> + if ((ret = ff_copy_whiteblacklists(c->mpeg_ctx, s)) < 0) {
> + avformat_free_context(c->mpeg_ctx);
> +
> + return ret;
> + }
> +
> + c->mpeg_ctx->flags = AVFMT_FLAG_CUSTOM_IO | AVFMT_FLAG_GENPTS;
> + c->mpeg_ctx->ctx_flags |= AVFMTCTX_UNSEEKABLE;
> + c->mpeg_ctx->probesize = 0;
> + c->mpeg_ctx->max_analyze_duration = 0;
> + c->mpeg_ctx->interrupt_callback = s->interrupt_callback;
> + c->mpeg_ctx->pb = &c->mpeg_pb.pub;
> + c->mpeg_ctx->io_open = NULL;
> +
> + if ((ret = avformat_open_input(&c->mpeg_ctx, "", c->mpeg_fmt, NULL)) < 0) {
> + avformat_free_context(c->mpeg_ctx);
Unnecessary, as avformat_open_input() frees the context on error.
> +
> + return ret;
> + }
> +
> + return ret;
> +}
> +
> +static int dvdvideo_read_header(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret = 0;
> +
> + if (c->opt_title == 0) {
> + av_log(s, AV_LOG_INFO, "Defaulting to title #1. "
> + "This is not always the main feature, validation suggested.\n");
> +
> + c->opt_title = 1;
> + }
> +
> + if (c->opt_pgc) {
> + if (c->opt_pg == 0)
> + av_log(s, AV_LOG_ERROR, "Invalid coordinates. If -pgc is set, -pg must be set too.\n");
> + else if (c->opt_chapter_start > 1 || c->opt_chapter_end > 0 || c->opt_preindex)
> + av_log(s, AV_LOG_ERROR, "-pgc is not compatible with the -preindex or "
> + "-chapter_start/-chapter_end options\n");
> +
> + return AVERROR(EINVAL);
> + }
> +
> + if ((ret = dvdvideo_ifo_open(s)) < 0)
> + return ret;
> +
> + if (c->opt_preindex && (ret = dvdvideo_pgc_preindex(s)) < 0)
> + return ret;
> +
> + if ((ret = dvdvideo_play_open(s, &c->play_state)) < 0
> + || (ret = dvdvideo_subdemux_open(s)) < 0
> + || (ret = dvdvideo_video_stream_setup(s)) < 0
> + || (ret = dvdvideo_audio_stream_add_all(s)) < 0
> + || (ret = dvdvideo_subp_stream_add_all(s)) < 0)
> + return ret;
> +
> + if (!c->opt_preindex)
> + return dvdvideo_pgc_chapters_setup(s);
> +
> + return ret;
> +}
> +
> +static int dvdvideo_read_packet(AVFormatContext *s, AVPacket *pkt)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + int ret;
> + enum AVMediaType st_type;
> + int64_t cur_delta = 0;
> +
> + if (c->play_end)
> + return AVERROR_EOF;
> +
> + ret = av_read_frame(c->mpeg_ctx, pkt);
> +
> + if (ret < 0)
> + return ret;
> +
> + if (!c->play_seg_started)
> + c->play_seg_started = 1;
> +
> + if (pkt->stream_index >= s->nb_streams) {
> + av_log(s, AV_LOG_DEBUG, "discarding frame with unknown stream\n");
> + goto skip_redo;
> + }
> +
> + st_type = c->mpeg_ctx->streams[pkt->stream_index]->codecpar->codec_type;
> +
> + if (!c->play_started) {
> + if (st_type != AVMEDIA_TYPE_VIDEO || pkt->pts == AV_NOPTS_VALUE
> + || pkt->dts == AV_NOPTS_VALUE
> + || !(pkt->flags & AV_PKT_FLAG_KEY)) {
> + av_log(s, AV_LOG_DEBUG, "discarding non-video-keyframe at start\n");
> + goto skip_redo;
> + }
> +
> + c->first_pts = pkt->pts;
> + c->play_started = 1;
> +
> + pkt->pts = 0;
> + pkt->dts = c->play_state.ts_offset - pkt->pts;
> + } else {
> + cur_delta = c->play_state.ts_offset - c->first_pts;
> +
> + if (c->play_state.nb_vobus_played == 1 && (pkt->pts == AV_NOPTS_VALUE
> + || pkt->dts == AV_NOPTS_VALUE
> + || (pkt->pts + cur_delta) < 0)) {
> + av_log(s, AV_LOG_DEBUG, "discarding frame with negative or unset timestamp "
> + "(this is OK at the start of the first playback VOBU)\n");
> + goto skip_redo;
> + }
> +
> + if (pkt->pts != AV_NOPTS_VALUE)
> + pkt->pts += cur_delta;
> + if (pkt->dts != AV_NOPTS_VALUE)
> + pkt->dts += cur_delta;
> + }
> +
> + av_log(s, AV_LOG_TRACE, "st=%d pts=%ld dts=%ld ts_offset=%ld first_pts=%ld\n",
> + pkt->stream_index, pkt->pts, pkt->dts,
> + c->play_state.ts_offset, c->first_pts);
> + if (pkt->pts < 0)
> + av_log(s, AV_LOG_WARNING, "Invalid frame PTS @ st=%d pts=%ld dts=%ld\n",
> + pkt->stream_index, pkt->pts, pkt->dts);
> +
> + return c->play_end ? AVERROR_EOF : 0;
> +
> +skip_redo:
> + av_packet_unref(pkt);
Unnecessary, as the packet will be generically unreferenced if you
return an error code.
> + return FFERROR_REDO;
> +}
> +
> +static int dvdvideo_close(AVFormatContext *s)
> +{
> + DVDVideoDemuxContext *c = s->priv_data;
> +
> + dvdvideo_subdemux_close(s);
> + dvdvideo_play_close(s, &c->play_state);
> + dvdvideo_ifo_close(s);
> +
> + return 0;
> +}
> +
> +#define OFFSET(x) offsetof(DVDVideoDemuxContext, x)
> +static const AVOption dvdvideo_options[] = {
> + {"angle", "playback angle number", OFFSET(opt_angle), AV_OPT_TYPE_INT, { .i64=1 }, 1, 9, AV_OPT_FLAG_DECODING_PARAM },
> + {"chapter_end", "exit chapter (PTT) number (0=end)", OFFSET(opt_chapter_end), AV_OPT_TYPE_INT, { .i64=0 }, 0, 99, AV_OPT_FLAG_DECODING_PARAM },
> + {"chapter_start", "entry chapter (PTT) number", OFFSET(opt_chapter_start), AV_OPT_TYPE_INT, { .i64=1 }, 1, 99, 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 },
> + {"pgc", "entry PGC number (0=auto)", OFFSET(opt_pgc), AV_OPT_TYPE_INT, { .i64=0 }, 0, 999, AV_OPT_FLAG_DECODING_PARAM },
> + {"preindex", "enable for accurate chapter markers, slow (2-pass read)", OFFSET(opt_preindex), AV_OPT_TYPE_BOOL, { .i64=0 }, 0, 1, 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 },
> + {"title", "title number (0=auto)", OFFSET(opt_title), AV_OPT_TYPE_INT, { .i64=0 }, 0, 99, AV_OPT_FLAG_DECODING_PARAM },
> + {"trim", "trim padding cells from start", OFFSET(opt_trim), AV_OPT_TYPE_BOOL, { .i64=1 }, 0, 1, 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_close = dvdvideo_close,
> + .read_header = dvdvideo_read_header,
> + .read_packet = dvdvideo_read_packet
> +};
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 27+ messages in thread
end of thread, other threads:[~2024-02-07 18:57 UTC | newest]
Thread overview: 27+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-12-09 10:06 [FFmpeg-devel] [PATCH] [WIP] [RFC] dvdvideo: initial contribution (DVD demuxer) Marth64
2023-12-10 2:27 ` Marth64
2023-12-10 3:03 ` Leo Izen
2023-12-10 3:16 ` Marth64
2023-12-10 3:47 ` Marth64
2023-12-13 20:45 ` Nicolas George
2024-01-06 22:32 ` Marth64
2024-01-10 8:46 ` [FFmpeg-devel] [PATCH v2] dvdvideo: add DVD-Video demuxer, powered by libdvdnav and libdvdread Marth64
2024-01-10 8:53 ` Marth64
2024-01-10 10:16 ` Nicolas George
2024-01-10 16:25 ` Marth64
2024-01-10 16:48 ` Marth64
2024-01-11 3:46 ` [FFmpeg-devel] [PATCH v3] " Marth64
2024-01-11 3:48 ` Marth64
2024-01-24 0:17 ` Stefano Sabatini
2024-01-24 0:58 ` Marth64
2024-01-28 22:59 ` [FFmpeg-devel] [PATCH v5] libavformat: " Marth64
2024-01-31 23:57 ` Stefano Sabatini
2024-02-05 0:02 ` [FFmpeg-devel] [PATCH v6] libavformat/dvdvideo: add DVD-Video demuxer " Marth64
2024-02-05 0:09 ` Marth64
2024-02-05 5:48 ` [FFmpeg-devel] [PATCH v7 1/2] " Marth64
2024-02-05 5:48 ` [FFmpeg-devel] [PATCH v7 2/2] libavformat/dvdvideo: add DVD CLUT utilities and enable palette support Marth64
2024-02-07 18:09 ` Andreas Rheinhardt
2024-02-07 18:32 ` Marth64
2024-02-07 0:52 ` [FFmpeg-devel] [PATCH v7 1/2] libavformat/dvdvideo: add DVD-Video demuxer powered by libdvdnav and libdvdread Stefano Sabatini
2024-02-07 0:54 ` Marth64
2024-02-07 18:58 ` Andreas Rheinhardt
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
This inbox may be cloned and mirrored by anyone:
git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git
# If you have public-inbox 1.1+ installed, you may
# initialize and index your mirror using the following commands:
public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
ffmpegdev@gitmailbox.com
public-inbox-index ffmpegdev
Example config snippet for mirrors.
AGPL code for this site: git clone https://public-inbox.org/public-inbox.git