From patchwork Tue Feb 6 06:48:18 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marth64 X-Patchwork-Id: 46066 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:7b08:b0:19e:8a94:b663 with SMTP id s8csp1221697pzh; Mon, 5 Feb 2024 22:49:07 -0800 (PST) X-Google-Smtp-Source: AGHT+IH0aFG5N4iX5aprid3nx/vA7sqUKRqrqXpYSdwqWt+KvX+0CiUY2hAQE6F7UQr6PM1PPxm8 X-Received: by 2002:a17:906:c7d4:b0:a37:d28b:961e with SMTP id dc20-20020a170906c7d400b00a37d28b961emr1110650ejb.10.1707202147157; Mon, 05 Feb 2024 22:49:07 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1707202147; cv=none; d=google.com; s=arc-20160816; b=AB9f5ZjhcT8a9n46iQdd7S26EY2j2IYX6GwXlJUsC5BybPfzfcRFXPZuoygZ5BX43U fwQIQjnwu7ZSTocL0hA82EozKhDbTVJtmdAwg6VBrP/DYYt95QKM0QNNg20KuSYLy9ad XAu9ZRhR5fEtJcj+KMgdSL1yKVkkj7HXG2fm+Nb4Dn6/umTrdpDNWHDrfjfa8YrWbNtg AKZOnGB18lXqeEB26XwBV8A658AIqtr7If5Ocf1iolTth1/6LQqdW7tCHKTuxM3yzW72 kohT9HPFwKyIR05Hwf8F5MuR8jF+9oCaZK++mmAOoBn2lbWpU+cWgCQv/i2R2PQ8lRQ5 4XnA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:message-id:date:to:from :dkim-signature:delivered-to; bh=pAJ1c5/xdtJZ3BapyfJ5MUIgk7MXLx5OHlJAsdd36TE=; fh=YtIuYQ1daIMpB6aRUKtoatmtdkWi4K1okYSDnABnUjY=; b=0Mghdftqjz2A/HXBW7jUPsnHsxBE8wydTyNE3NnzLnNXS2rJlbi8YiOjd6STT5yDLx x4epZhhCiJ5x9bxLhDOXX7bR7BfsxyjH49CccENiIQmZpMKBCE7vDylTGhcUtL+QpUVu W7pfXj6X6EKhAhMk938BVthgjJzK+EU4doCZZVzzkPCPs9T5Usf0cyGpyMSpDkp9fHrY LqaJzgLrpdtNxeicYTZS8a4yoxD0sHy6uRPF1Dx+wt3JgSVXQKXkCjdjM/LdwIjR2DT5 RzHwcm3gKB1izwcG4MOHdDPCLCDgYpN74URu1kqUb/Md+2GIin0yuw05HU0DzwfX11EZ Kq4g==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@proxyid.net header.s=google header.b=Y8BPpR5E; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org X-Forwarded-Encrypted: i=0; AJvYcCVbAUKSFilO9nqt1EjjDTH3E2OOUkhEa5SafGioRd04ZShbrGJZSVWNwfqQh1dU8WyeZkvKRObnUtchmd9jCt25/lAlrPtvrjbmOUcjpFBjQhkJFEvWGTkXUPNHQoY92BOjKK+ZKs6HI+qwF9nYBZrEur3qJ5WtA4b4nVq9gDeE4cpWhibGSVm7ZSIdA4kgeet8nu6PAsuv0tgVABJvOQ7TL1u16S5TUF9o22ZXaWDnSDm/2qTOFIyqu4jeuI6ZkdyQDO5XvA4/j8MFCkFsyyodRmCW4tFcsli19E+jEMbB2PLjHO/jBEt4ilo3MKHgRvjBfTJ4R9JNuhJZeosv1YhTsBEIprM/D25tpYMsmBzXNzj72mkCa5Hyhd+w9Gkss/sGnY27UaVVV3dFk8CuMz5Wb/NoK0totCpFPheqAKAu146ukLqDGOgS0lX9Wqx2rmdNAuCtx62oVSpzVMbbtDSHNNe58npFvLOBgbPOvHUclZlO+AWBv6HlAEzHLBjqTUUD4KrojkNFUFR+qywPFIifDDTSXJEXsFW5C3scDpA7X1keVEeZhd5Dbclz567iTQukBqwDewVnqWWxX9UUUiqRekzoDMRQki2yWkceK1JMSV/G1y3YHcPlm+1Pc5KBqBJYN4Z2ddITAE9egiVdZa2zIRVFAUTojD7EEvcHp2FzbHYg+j+YxqtdLew3Ue5VIK8UpJiwKe5WpyXhdCVkEf2XZm7ItaYkz8o7wVJ1EBPCizSLtdOlEBPQ0RZECXB/ReXA7a6Ng7bhrQe1EpoaohnSJNnnaLVhAEHeujd29nr5e00cprPWhxReSsUg5ykYW9cOd8B7x1mN1fPzWUhen7dsKhNXivwMTAJj4jEvoc9mpgn+ZmevmhhsQaUGHhlimr03W3EDXf6NZlcVKrNgfF5FbQC4N7aBqx17O+DUeu5+gqvcHmGQSkY3WD1F52PLWV7svv t+NtBVsh97VRiAc1o1+Jh/VmQI7Ny/7cklPL8wauHcW72K/FP6mVCWQTFRnFhORVSuNPyS1XW5m7JyAb3aOS2wWpHhjVIkLnYT+jnBlYlB8dpRbLXZ67JdxvyZiX+fcPe6yPK0lpm0jAbxvsJYsac46/6Nyzmh31+d8qPUWEDicU+GRnvLGa+qJtns+yAntzSMqEwnJYcDcFEpOY3AJP5HMVXDXK4quOYnnfD3ztHlL0zhTL7dKhBoqA+uhiRnG0MSK0Eu8rW3OjIWsaQ0ja+bfjLmtuerhaemEsmSGhMBSNwY6tf8ze3kYTYjnIRILqWB1pvMlu5mGO3kF1Pje5Us8/rSIqxNTUrxJcm/WhFnucXdDsygjKG4fY7DfNuPXFa7xJY7g9d4MPXWR2gxZXKpyTZhtOH4lc4vXxukRtXQye+3uifmdp9/tqHf/1al7mP91VXitCQjzQDyXXkdYRQHYUoybU1C3oi6mJ9/s472HtZg03YEAIMwjaYoRAeXqgsVHlrRWJnQGmPeaH1Bbgi1kQT0YJcsnHTAI8tFC14Kx6pzgLX/OKgARrpw/vVSK+e9GVwTCvG0EhCcqyuHUqGSeRkVrSEnG/hu7IeFPPyzarLbzuU2GPVnBErvRSJwFZoNF7g= Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id i18-20020a170906265200b00a36fac20069si743289ejc.753.2024.02.05.22.49.06; Mon, 05 Feb 2024 22:49:07 -0800 (PST) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@proxyid.net header.s=google header.b=Y8BPpR5E; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 6626D68CB90; Tue, 6 Feb 2024 08:49:02 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-pj1-f99.google.com (mail-pj1-f99.google.com [209.85.216.99]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 2D95468C954 for ; Tue, 6 Feb 2024 08:48:55 +0200 (EET) Received: by mail-pj1-f99.google.com with SMTP id 98e67ed59e1d1-296a02b7104so1348378a91.2 for ; Mon, 05 Feb 2024 22:48:54 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=proxyid.net; s=google; t=1707202133; x=1707806933; darn=ffmpeg.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=DIbb4SOonrx3B8ZvxWD6Ef//Wfp7cqqyKNUxIDb79wE=; b=Y8BPpR5E9KqNeLqK6ADgoUKNld6CSQ/7gmZiHLU69aOwEMBvgBotCSaFXHLGitvQVg tLmcZMUYF95HHm3MtIp3SlKpSXRK4bq/NPLKNwqi6UaOoolZojDzUZWzT/ozcExeyTRw zwSr41dSpAHHjkT31zaBV2X9R9CE/xtdUh9ZH42gb2cHEzPLwnBRMKag4WwcKvYBj3Lv 5lpepYXjTfAP8WhYmpTKiPZZses0S2VPupRGpONfflgaambB6W0N9ulrXR6BcoLnodLC E3FNgCdQVAIuerPld0NuxOkU9lQpUDiurV5M/FGHwTnQbU5l2FdorMzu0tI9v+RZiPMy pN9Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1707202133; x=1707806933; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=DIbb4SOonrx3B8ZvxWD6Ef//Wfp7cqqyKNUxIDb79wE=; b=TGA9KpzHojRDNfHBSK/P0Bl6qe50xnMW6FpDT/ZOs8frgASeG1XjF8sDQaXbtusrPZ J4ilyQ7UdI+Jjf6usIiJLvYicxp0yEqWKvffIwnWIPQ1MmkRa7ZYrcI5govtVOIY88yP eSyHRaEM2ti/cptMWoOHQvijZHzHRl5f9IvJVPIJqxOU4HufUuCoHB397e0BVS83WOVt GYaiJrbZeVluI5PfHfFCBzLTWJN6gAF+ejKsSZM+ZoAgzxH2yCWxm9WmK8he9/E7w3jK mziS0Cs1WlEc/ZzQ0CRK+TsronoVkf8OrFdi7Pw8dqWPl+iN8d1vyCjvDGw6snj3tEDf 4GWw== X-Gm-Message-State: AOJu0Ywub5+JUIr/vYiBCChuJw3sW5O7sJatN/hWTWrhhvVwpUXvjnem MQ+xv/j8OIBgiCo7EqWcEahrXNz3UR05aptGZ6d3+1j91CgzUgfyALI0ymWmyVViDe2UYJu4TD2 qcdpqV2+t9b/tLa6Sym4wwK0Fk9IPcAT9wlrtI7pW X-Received: by 2002:a17:90a:f98f:b0:296:2acc:2ba9 with SMTP id cq15-20020a17090af98f00b002962acc2ba9mr1478367pjb.11.1707202132931; Mon, 05 Feb 2024 22:48:52 -0800 (PST) Received: from wsx-cc1-001.. (c-76-141-249-38.hsd1.il.comcast.net. [76.141.249.38]) by smtp-relay.gmail.com with ESMTPS id i8-20020a17090a974800b002961301cc30sm31857pjw.0.2024.02.05.22.48.52 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 05 Feb 2024 22:48:52 -0800 (PST) X-Relaying-Domain: proxyid.net From: Marth64 To: ffmpeg-devel@ffmpeg.org Date: Tue, 6 Feb 2024 00:48:18 -0600 Message-Id: <20240206064818.1356723-1-marth64@proxyid.net> X-Mailer: git-send-email 2.34.1 MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v8 1/2] libavformat: add DVD-Video demuxer, powered by libdvdnav and libdvdread X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Marth64 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: bNzeg8DvNdad Hopefully, this is the last one. - All prior documentation fixes and feedback addressed (AFAIK) - Prevents demuxing of empty titles and informs the user - Fixes does not add chapter markers if only one chapter - Fixes logic for chapter selection on certain titles Signed-off-by: Marth64 --- Changelog | 2 + configure | 8 + doc/demuxers.texi | 129 ++++ libavformat/Makefile | 1 + libavformat/allformats.c | 1 + libavformat/dvdvideodec.c | 1424 +++++++++++++++++++++++++++++++++++++ 6 files changed, 1565 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 : - ffmpeg CLI options may now be used as -/opt , which is equivalent to -opt > - showinfo bitstream filter +- DVD-Video demuxer, powered by libdvdnav and libdvdread + version 6.1: - libaribcaption decoder diff --git a/configure b/configure index f72533b7d2..831b9f6cb6 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 @@ -3523,6 +3527,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" @@ -6771,6 +6777,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 ... +@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 ... +@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 ... +@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..8796a24b24 --- /dev/null +++ b/libavformat/dvdvideodec.c @@ -0,0 +1,1424 @@ +/* + * 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 +#include +#include +#include +#include + +#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_cells_played; /* number of cells 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_angle; /* the user-provided angle number (1-indexed) */ + int opt_chapter_end; /* the user-provided exit PTT (0 for last) */ + int opt_chapter_start; /* the user-provided entry PTT (1-indexed) */ + int opt_pg; /* the user-provided PG number (1-indexed) */ + int opt_pgc; /* the user-provided PGC number (1-indexed) */ + int opt_preindex; /* pre-indexing mode (2-pass read) */ + int opt_region; /* the user-provided region digit */ + int opt_title; /* the user-provided title number (1-indexed) */ + 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 segment_started; /* signal that subdemuxer is on a segment */ + 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 int dvdvideo_is_cell_promising(AVFormatContext *s, pgc_t *pgc, + int celln) +{ + DVDVideoDemuxContext *c = s->priv_data; + dvd_time_t cell_duration; + + if (!c->opt_trim) + return 1; + + cell_duration = pgc->cell_playback[celln - 1].playback_time; + + return cell_duration.second >= 1 || cell_duration.minute >= 1 || cell_duration.hour >= 1; +} + +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; + int32_t disc_region_mask; + int32_t player_region_mask; + int cur_title, cur_pgcn, cur_pgn; + pgc_t *pgc; + int is_pgc_promising = 0; + + 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; + } + + /* since we have the PGC now, let's also check that this is not a empty padding title */ + for (int i = 1; i <= pgc->nr_of_cells; i++) { + if (dvdvideo_is_cell_promising(s, pgc, i)) { + is_pgc_promising = 1; + break; + } + } + + if (!is_pgc_promising) { + av_log(s, AV_LOG_ERROR, "Title %d, PGC %d looks empty (consists of padding cells), " + "if you want to try anyway, disable the -trim option\n", + c->opt_title, state->pgcn); + + return AVERROR_INVALIDDATA; + } + + if (dvdnav_angle_change(state->dvdnav, c->opt_angle) != DVDNAV_STATUS_OK) { + av_log(s, AV_LOG_ERROR, "Unable to start playback at angle %d\n", c->opt_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_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_is_cell_promising(s, state->pgc, 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; + state->nb_cells_played++; + + 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; + } 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 ret; + + 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 || state->pgc->nr_of_programs == 1) { + 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) +{ + 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->segment_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->segment_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->segment_started) + c->segment_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_cells_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 +}; From patchwork Tue Feb 6 06:48:19 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marth64 X-Patchwork-Id: 46067 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:7b08:b0:19e:8a94:b663 with SMTP id s8csp1221751pzh; Mon, 5 Feb 2024 22:49:17 -0800 (PST) X-Google-Smtp-Source: AGHT+IGdKmYRR7LVzxDlhbBO7HpnEGDYQ1/Ey+K+lK0aExACmRL+CrF1+xBJVvvCV2J24sV6Esio X-Received: by 2002:a17:907:77d1:b0:a38:1de2:97e8 with SMTP id kz17-20020a17090777d100b00a381de297e8mr900263ejc.34.1707202157541; Mon, 05 Feb 2024 22:49:17 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1707202157; cv=none; d=google.com; s=arc-20160816; b=j1wYBSGqCkI1lO8UCHelp6oY8rrg2yGedTh0PcN7XAGksQEm9vcGUvQveYC/lXXlmO aZQ4YC0jdKKWuVU9PhQb717Bq2RXwbz/aZc74aCb8ktAAGy5q7pHXKRI56obC+X1fOTt sVuWr7+Pu4i7R5tuX47JqWVMQUy1GeHaqysRYH2F9jX0AwlyLgWwzumsS1Va+6vbkOND aWhkgl1vTTM8DoM91NpeVDHwFdTunsC/CK5iQ0KbD+oIFgB0GjH883sjNT+aM7Ks8Y3f eXGpFTlbNHZ3gxUqUdxjiz7nXQ+XyeBdk588wiVpMcjqw1cVwWhgrJtN8jPw9w3niNSw 0QWw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:from:dkim-signature:delivered-to; bh=VPbxA1FZ+ZEhPo7RLxkpPSi7L0XavDxCqoYpEJRaFIw=; fh=SItON1iFvD2WZWfUWYYJmSZ4hm7At4bnj/cmf2LCLPQ=; b=FVN5nGfu2zEKUhhVwgA8aIsDlG73CMLk35jZ34MZjYiRMnxOclgqcRNX3VRRcl+/Ee 79RXl1M/fCyZICVWyQioMdsP9TfNSqMFojtLE3sRRiuj+H0cDVtl/g55Ymlh4mJRx6Ks O8p9oivzTOrdPYWARuQ37Er7lgidgc6TAqOGVyypX4GHG2bK05/vL4bwSlAbfEhpKIZz M4LD2EeA8MWdfbCDb7OaDpg0UBRRcGRpxgLMwCeg1zMizbk8V6XcD1XB8/cD0vX1E7V6 UU+PoGbqzRj8z9fWnp/cLutOsRlkdjUaQHoFi12KU5Bp19SiKy6owTN1vnD1qo+gLFUL tu3Q==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@proxyid.net header.s=google header.b=PMaUlzXL; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org X-Forwarded-Encrypted: i=0; AJvYcCX6sTkCk3tQEs1m4VbTIpewBrG8Slfp6QF2ZH8gSlJz394+f2Ey/+lWDJK/aSX/WJ5kdVCtBL/a+3qVfxCgr1LvRDjyMQdXNZMmM3g/7OCHV6Wgk4pm6cJHBgqh3WWS2v50bO73oADzJaPttosHIvvTBAOmA2wTJBxcvuceIlxNKPjxD7wxXEoabOJV9IpWtBAY75VNy0+EzVsC3axUy6Xj3Vi6eMK/XyrWjYc7Mfo2CIb6J/WRz7E0Sso8p29UPXbDUHzOaO+ZbIshpGhNP97fp8Qxn1pDsfcp3NcLs3i4dWlAJTLjyAATGcINtDbmFvUMGAtcBMVWLOYK8tJcijLiGeTY2YHRGvT08FKWzjL1o/aiu17xoauXa7dlJhwXcFWryX5jpZaXnf5Qc6dZ1OWM+TfdF1TD7TR2wGlEIkOBus6O/5/WLxhX7EC5j3kF7bM/Dg0UiD7gYcthbEh0PU3Ti8NFfN0IQOact7MnOq44phQ4fZ6cr4w8qfsK6Vg46Ytra2KD/TlUI4Jen58uqsLpDVICcINuyxnfacpIlAZVWneV3PLT0T+qsUEcckimrMNiwDeVCFvYkIb8vKeSJM4S8ZaZKtnlvnZ/jy1wgBvINZfr1WFJaNFbTl/vNDYjEhO8Y5rC5GSNOct1o2BP689hKbnLBtAKa8cVEc0mwUWfMto4CuIMwy2tc4YMM03z26hmb4N7zrAcL2rQBp0pXTVRfkt/Evp0bQhK3TsgIzdNgsL/JIBK/aktKGFIp+Cr0WoateuiENjKjd96i6A5shNEdwVcV7GunVZchCUOJAXjE4llpHNvN40piIZbShgDo/QSsJJeU4bj0UF4k65rdIiBnlZZyJ7ak61bh7D8GaRfCOEoz3/irtZkEh1SFukpfIJ4bXeKOxyyuR7WjcndG6cTc7LaJehZYGV0HY1BM3Fwl+DJY75kaeWWlEd0PyqxWfJJ+B ujmizzH48pu8NriRgwrjVw6pJblouDpvFHh0JLbkL8WqzcaYc4y2KTt6ZogtyR0mx3JNYoQ46ZMrbPnm5qkDkNhBuBw5jyoV6eWAXw+4xALed3XCbfuiqtPAL5f0ay6uEvqgs+f5hCfVSPqdCzWI0O/CNMJ8lB7PNkXFng3dZQ48gp/n1fZCuMeI3GqeCvMlL9dnpuk9xOdUpJFadz6gLNC/j6MbWPFxiQ4VZt+eT50UUrCIe7gBx29c85Wy/1zyKxH1d4MfZ3UeKtk2VSrXoUF6lzDVdwm2Zrk16FpENBIC1PrWW37uBAEK/nCdIlo0xz8Zuk/qc1ocRyNyeiv4NmHv0bcbfzQPTU9AONUpqVULdnFxNKU/zbVQIWX2XkrXrwnLMCj0WHPBbeOIUt8KtANL96ZXEwXnnPb3etvoXT5iw1CRYWKIidVf0JKbqYLApfqyAipJZi29mDBaXzBtCoLCwt2SAd19wNM+C4GjtUEn8jhc8u1X3A4M9y7KQz1xQKEm1n4typfxEYbkCrg06mmqzm5eBOSteVPCXNjToTJIPNudiESacps6nffed8tKaT0iE2AQumkRxViuKLNB7ulTxiPaYi5qqRg7ZSh4lwRXW8dblD1ZbTMeYrI9fSZjF1 Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id x10-20020a170906134a00b00a359e081020si750343ejb.181.2024.02.05.22.49.17; Mon, 05 Feb 2024 22:49:17 -0800 (PST) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@proxyid.net header.s=google header.b=PMaUlzXL; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 88D2668AD09; Tue, 6 Feb 2024 08:49:05 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-pf1-f228.google.com (mail-pf1-f228.google.com [209.85.210.228]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id CA3F168C9F6 for ; Tue, 6 Feb 2024 08:48:58 +0200 (EET) Received: by mail-pf1-f228.google.com with SMTP id d2e1a72fcca58-6e04c4f494cso909611b3a.2 for ; Mon, 05 Feb 2024 22:48:58 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=proxyid.net; s=google; t=1707202136; x=1707806936; darn=ffmpeg.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=75HJSTM0HWWUa/YhPM9YhdWYGOA4UfWqlx5JerhaM/c=; b=PMaUlzXLPuFMuC3w6TW4XIVVetxxZY3w2mH6x5gf7orNPmZBYqTcw8I0nPwEIOkClW lDqIaggtWjiEJNprQc9fWndO4ia4bjQ9bx4XXBsqSThl0fTcjPVq7sTw2VquJW/y7LdH t2RaaCPEQ9w9nnAVSylkVnR4fC7PUPDgCIUtyZIkTwPYKYcLSW1/7ofqnLH4PG+3N2yb 19ocufkyBd5y6wle9krPbdjUZWIpec8ABUVFf08WLsD9afGrlmdZORyf3yTGRHBzyH8N pIqkOV3PhwzaL5E894LgZCVY1JFM4LnD6RuLqFCxFxjSzHEC/n35Py2VJGrhvCs/CzDz /MHA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1707202136; x=1707806936; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=75HJSTM0HWWUa/YhPM9YhdWYGOA4UfWqlx5JerhaM/c=; b=fk2eXPDOf9PNX7WvYHgEmlcm3W+P47F+B5ODg2HKG321Mp6SoumKAq9DGAAiYOpzzT aRWPmxZo5K3vH23YlmABay5lkN3ctB3JqPsY7RXjjRja38YeZi2NUNhncRwxyiCWWNNn Nhcslins+r99o591j7EJSQg5lTbHvYuyvm6bry0YKDd7T3xVvnnaJ3xh6n1n1AM+E3Aj KY2gNebJjrd3qse0wYuXRbhF3sOlFDlrCDOlrqtZ7uZfn0KB025suD546+SoWXQPPLMe JDmPS4fauhJnJ3wxQ2SryXXC5wR5EMiz/t6/SjwJnBkKSRWraNxRyeF8XjGA6PMHKNEo piQA== X-Gm-Message-State: AOJu0Yy5G6xGSA/56TwXOB9/SujMPH+u2BX+3lf3M4LTJwUo8BJsb2A7 +nAbS7hmoWaoY/ndwKtCmK6uw+gVwlD2Dj08enWKaG0veDNLTcEh+p8jTBzq8uTh2WwIDXQmsc7 Fn7f9EKFRL8GAW+YuSMBDLh1N4jubnBbX7++QOzFY X-Received: by 2002:a05:6a20:8e1b:b0:19e:972b:3e8d with SMTP id y27-20020a056a208e1b00b0019e972b3e8dmr902328pzj.28.1707202136662; Mon, 05 Feb 2024 22:48:56 -0800 (PST) Received: from wsx-cc1-001.. (c-76-141-249-38.hsd1.il.comcast.net. [76.141.249.38]) by smtp-relay.gmail.com with ESMTPS id i8-20020a17090a974800b002961301cc30sm31857pjw.0.2024.02.05.22.48.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 05 Feb 2024 22:48:56 -0800 (PST) X-Relaying-Domain: proxyid.net From: Marth64 To: ffmpeg-devel@ffmpeg.org Date: Tue, 6 Feb 2024 00:48:19 -0600 Message-Id: <20240206064818.1356723-2-marth64@proxyid.net> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240206064818.1356723-1-marth64@proxyid.net> References: <20240206064818.1356723-1-marth64@proxyid.net> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v8 2/2] libavformat/dvdvideo: add DVD CLUT utilities and enable palette support X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Marth64 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: pR7YbWnghDbf 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 --- doc/demuxers.texi | 5 ++ libavformat/Makefile | 2 +- libavformat/dvdclut.c | 104 ++++++++++++++++++++++++++++++++++++++ libavformat/dvdclut.h | 37 ++++++++++++++ libavformat/dvdvideodec.c | 16 ++++++ 5 files changed, 163 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 8796a24b24..d01416a37e 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; @@ -126,6 +128,7 @@ typedef struct DVDVideoDemuxContext { int opt_angle; /* the user-provided angle number (1-indexed) */ int opt_chapter_end; /* the user-provided exit PTT (0 for last) */ int opt_chapter_start; /* the user-provided entry PTT (1-indexed) */ + int opt_clut_rgb; /* output subtitle palette (CLUT) as RGB */ int opt_pg; /* the user-provided PG number (1-indexed) */ int opt_pgc; /* the user-provided PGC number (1-indexed) */ int opt_preindex; /* pre-indexing mode (2-pass read) */ @@ -1041,6 +1044,8 @@ break_free_and_error: 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); @@ -1048,6 +1053,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); @@ -1069,6 +1079,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); @@ -1096,6 +1109,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) @@ -1116,6 +1130,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; @@ -1395,6 +1410,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 },