[FFmpeg-devel] [PATCH] avformat/bdmvdec: add BDMV demuxer, powered by libbluray (experimental)

Marth64 marth64 at proxyid.net
Thu Nov 28 08:03:31 EET 2024


Experimental for now to give it time to mature.
Documentation to follow.

This is intended to become the replacement to the limited
avformat/bluray protocol handler that exists today.

Signed-off-by: Marth64 <marth64 at proxyid.net>
---
 configure                |    4 +-
 libavformat/Makefile     |    1 +
 libavformat/allformats.c |    1 +
 libavformat/bdmvdec.c    | 1134 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 1139 insertions(+), 1 deletion(-)
 create mode 100644 libavformat/bdmvdec.c

diff --git a/configure b/configure
index 44829b8eb9..7789ce3cdb 100755
--- a/configure
+++ b/configure
@@ -218,7 +218,7 @@ External library support:
   --enable-libaribcaption  enable ARIB text and caption decoding via libaribcaption [no]
   --enable-libass          enable libass subtitles rendering,
                            needed for subtitles and ass filter [no]
-  --enable-libbluray       enable BluRay reading using libbluray [no]
+  --enable-libbluray       enable libbluray, needed for BDMV demuxing [no]
   --enable-libbs2b         enable bs2b DSP library [no]
   --enable-libcaca         enable textual display using libcaca [no]
   --enable-libcelt         enable CELT decoding via libcelt [no]
@@ -3626,6 +3626,8 @@ av1_demuxer_select="av1_frame_merge_bsf av1_parser"
 avi_demuxer_select="riffdec exif"
 avi_muxer_select="riffenc"
 avif_muxer_select="mov_muxer"
+bdmv_demuxer_select="mpegts_demuxer"
+bdmv_demuxer_deps="libbluray"
 caf_demuxer_select="iso_media"
 caf_muxer_select="iso_media"
 dash_muxer_select="mp4_muxer"
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 7ca68a7036..f14132176d 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -146,6 +146,7 @@ OBJS-$(CONFIG_AVS2_MUXER)                += rawenc.o
 OBJS-$(CONFIG_AVS3_DEMUXER)              += avs3dec.o rawdec.o
 OBJS-$(CONFIG_AVS3_MUXER)                += rawenc.o
 OBJS-$(CONFIG_BETHSOFTVID_DEMUXER)       += bethsoftvid.o
+OBJS-$(CONFIG_BDMV_DEMUXER)              += bdmvdec.o
 OBJS-$(CONFIG_BFI_DEMUXER)               += bfi.o
 OBJS-$(CONFIG_BINK_DEMUXER)              += bink.o
 OBJS-$(CONFIG_BINKA_DEMUXER)             += binka.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 445f13f42a..5e6796ac3f 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -101,6 +101,7 @@ extern const FFOutputFormat ff_avs2_muxer;
 extern const FFInputFormat  ff_avs3_demuxer;
 extern const FFOutputFormat ff_avs3_muxer;
 extern const FFInputFormat  ff_bethsoftvid_demuxer;
+extern const FFInputFormat  ff_bdmv_demuxer;
 extern const FFInputFormat  ff_bfi_demuxer;
 extern const FFInputFormat  ff_bintext_demuxer;
 extern const FFInputFormat  ff_bink_demuxer;
diff --git a/libavformat/bdmvdec.c b/libavformat/bdmvdec.c
new file mode 100644
index 0000000000..fdb9ea513f
--- /dev/null
+++ b/libavformat/bdmvdec.c
@@ -0,0 +1,1134 @@
+/*
+ * Blu-ray Disc Movie (BDMV) demuxer, powered by libbluray
+ *
+ * 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 <libbluray/bluray.h>
+#include <libbluray/filesystem.h>
+
+#include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/avutil.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+#include "libavutil/samplefmt.h"
+
+#include "avformat.h"
+#include "avio_internal.h"
+#include "avlanguage.h"
+#include "demux.h"
+#include "internal.h"
+#include "url.h"
+
+#define BDMV_FILE_BLOCK_SIZE    6144
+#define BDMV_PTS_WRAP_BITS      64
+#define BDMV_TIME_BASE_Q        (AVRational) { 1, 90000 }
+
+enum BDMVDemuxDomain {
+    BDMV_DOMAIN_MPLS,
+    BDMV_DOMAIN_M2TS
+};
+
+enum BDMVMPLSDemuxMode {
+    BDMV_MPLS_DEMUX_MODE_FILTERED,
+    BDMV_MPLS_DEMUX_MODE_DIRECT
+};
+
+typedef struct BDMVVideoStreamEntry {
+    int                   pid;
+    enum AVCodecID        codec_id;
+    int                   width;
+    int                   height;
+    AVRational            dar;
+    AVRational            framerate;
+} BDMVVideoStreamEntry;
+
+typedef struct BDMVAudioStreamEntry {
+    int                   pid;
+    enum AVCodecID        codec_id;
+    int                   sample_rate;
+    int                   disposition;
+    const char            *lang_iso;
+} BDMVAudioStreamEntry;
+
+typedef struct BDMVSubtitleStreamEntry {
+    int                   pid;
+    enum AVCodecID        codec_id;
+    int                   disposition;
+    const char            *lang_iso;
+} BDMVSubtitleStreamEntry;
+
+typedef struct BDMVClipHandle {
+    int                   idx;
+    int                   is_open;
+    BD_FILE_H             *file;
+} BDMVClipHandle;
+
+typedef struct BDMVDemuxContext {
+    const AVClass         *class;
+
+    /* options */
+    int                   opt_angle;            /* the user selected angle */
+    int                   opt_domain;           /* the user selected demuxing domain */
+    int                   opt_mpls_mode;        /* the user selected MPLS demuxing mode */
+    int                   opt_item;             /* the user selected MPLS or M2TS ID */
+
+    /* subdemux */
+    AVFormatContext       *mpegts_ctx;          /* context for inner demuxer */
+    uint8_t               *mpegts_buf;          /* buffer for inner demuxer */
+    FFIOContext           mpegts_pb;            /* buffer context for inner demuxer */
+
+    /* BD handle */
+    BLURAY                *bd;                  /* the libbluray handle to the BDMV */
+    int                   bd_nb_titles;         /* the libbluray reported number of "titles" */
+    BLURAY_TITLE_INFO     *bd_mpls;             /* the libbluray MPLS handle (MPLS mode) */
+
+    /* read state */
+    BDMVClipHandle        cur_clip;             /* handle to clip file (direct MPLS or M2TS mode) */
+    BLURAY_CLIP_INFO      cur_clip_info;        /* handle to clip information (MPLS mode) */
+    uint64_t              bd_next_clip_pts;     /* starting PTS of the next clip in BD ticks */
+    int64_t               bd_pts_offset;        /* the current PTS offset in BD ticks */
+
+    /* playback control */
+    int                   corrupt_warned;       /* signal that we warned about libbluray limits */
+    int64_t               first_pts;            /* the PTS of the first video keyframe */
+    int                   play_started;         /* signal that playback has started */
+    int64_t               pts_offset;           /* PTS discontinuity offset (per outer demuxer) */
+    int                   seek_warned;          /* signal that we warned about seeking limits */
+    int                   subdemux_reset;       /* signal that subdemuxer should be reset */
+} BDMVDemuxContext;
+
+static inline void bdmv_clip_format_path(char* dst, int m2ts_id)
+{
+    snprintf(dst, 23, "BDMV/STREAM/%05d.m2ts", m2ts_id);
+}
+
+static inline void bdmv_clip_format_path2(char* dst, char* m2ts_id)
+{
+    snprintf(dst, 23, "BDMV/STREAM/%s.m2ts", m2ts_id);
+}
+
+static int bdmv_clip_video_stream_analyze(AVFormatContext *s, const BLURAY_STREAM_INFO bd_st_video,
+                                          BDMVVideoStreamEntry *entry)
+{
+    enum AVCodecID codec_id = AV_CODEC_ID_NONE;
+    int height              = 0;
+    int width               = 0;
+    AVRational dar          = (AVRational) { 0, 0 };
+    AVRational framerate    = (AVRational) { 0, 0 };
+
+    switch (bd_st_video.coding_type) {
+        case BLURAY_STREAM_TYPE_VIDEO_MPEG1:
+            codec_id = AV_CODEC_ID_MPEG1VIDEO;
+            break;
+        case BLURAY_STREAM_TYPE_VIDEO_MPEG2:
+            codec_id = AV_CODEC_ID_MPEG2VIDEO;
+            break;
+        case BLURAY_STREAM_TYPE_VIDEO_VC1:
+            codec_id = AV_CODEC_ID_VC1;
+            break;
+        case BLURAY_STREAM_TYPE_VIDEO_H264:
+            codec_id = AV_CODEC_ID_H264;
+            break;
+        case BLURAY_STREAM_TYPE_VIDEO_HEVC:
+            codec_id = AV_CODEC_ID_HEVC;
+            break;
+    }
+
+    switch (bd_st_video.format) {
+        case BLURAY_VIDEO_FORMAT_480I:
+        case BLURAY_VIDEO_FORMAT_480P:
+            height = 720;
+            width  = 480;
+            break;
+        case BLURAY_VIDEO_FORMAT_576I:
+        case BLURAY_VIDEO_FORMAT_576P:
+            height = 720;
+            width  = 576;
+            break;
+        case BLURAY_VIDEO_FORMAT_720P:
+            height = 1280;
+            width  = 720;
+            break;
+        case BLURAY_VIDEO_FORMAT_1080I:
+        case BLURAY_VIDEO_FORMAT_1080P:
+            height = 1920;
+            width  = 1080;
+            break;
+        case BLURAY_VIDEO_FORMAT_2160P:
+            height = 3840;
+            width  = 2160;
+            break;
+    }
+
+    switch (bd_st_video.rate) {
+        case BLURAY_VIDEO_RATE_24000_1001:
+            framerate = (AVRational) { 24000, 1001 };
+            break;
+        case BLURAY_VIDEO_RATE_24:
+            framerate = (AVRational) { 24, 1 };
+            break;
+        case BLURAY_VIDEO_RATE_25:
+            framerate = (AVRational) { 25, 1 };
+            break;
+        case BLURAY_VIDEO_RATE_30000_1001:
+            framerate = (AVRational) { 30000, 1001 };
+            break;
+        case BLURAY_VIDEO_RATE_50:
+            framerate = (AVRational) { 50, 1 };
+            break;
+        case BLURAY_VIDEO_RATE_60000_1001:
+            framerate = (AVRational) { 60000, 1001 };
+            break;
+    }
+
+    switch (bd_st_video.aspect) {
+        case BLURAY_ASPECT_RATIO_4_3:
+            dar = (AVRational) { 4, 3 };
+            break;
+        case BLURAY_ASPECT_RATIO_16_9:
+            dar = (AVRational) { 16, 9 };
+            break;
+    }
+
+    if (codec_id == AV_CODEC_ID_NONE || !width || !height || framerate.num == 0 || dar.num == 0) {
+        av_log(s, AV_LOG_ERROR, "Invalid video stream parameters for PID %02x\n", bd_st_video.pid);
+
+        return AVERROR_INVALIDDATA;
+    }
+
+    entry->pid       = bd_st_video.pid;
+    entry->codec_id  = codec_id;
+    entry->width     = width;
+    entry->height    = height;
+    entry->dar       = dar;
+    entry->framerate = framerate;
+
+    return 0;
+}
+
+static int bdmv_clip_video_stream_add(AVFormatContext *s, BDMVVideoStreamEntry *entry)
+{
+    AVStream *st;
+    FFStream *sti;
+
+    st = avformat_new_stream(s, NULL);
+    if (!st)
+        return AVERROR(ENOMEM);
+
+    st->id                    = entry->pid;
+    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;
+
+#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         = AVSTREAM_PARSE_FULL;
+    sti->display_aspect_ratio = entry->dar;
+
+    avpriv_set_pts_info(st, BDMV_PTS_WRAP_BITS,
+                        BDMV_TIME_BASE_Q.num, BDMV_TIME_BASE_Q.den);
+
+    return 0;
+}
+
+static int bdmv_clip_video_stream_add_group(AVFormatContext *s, const int nb_bd_streams,
+                                            BLURAY_STREAM_INFO *bd_streams)
+{
+    int ret;
+
+    for (int i = 0; i < nb_bd_streams; i++) {
+        BDMVVideoStreamEntry entry           = {0};
+        const BLURAY_STREAM_INFO bd_st_video = bd_streams[i];
+
+        ret = bdmv_clip_video_stream_analyze(s, bd_st_video, &entry);
+        if (ret < 0) {
+            av_log(s, AV_LOG_ERROR, "Unable to analyze video stream\n");
+            return ret;
+        }
+
+        ret = bdmv_clip_video_stream_add(s, &entry);
+        if (ret < 0) {
+            av_log(s, AV_LOG_ERROR, "Unable to add video stream\n");
+            return ret;
+        }
+    }
+
+    return 0;
+}
+
+static int bdmv_clip_video_stream_add_all(AVFormatContext *s, const BLURAY_CLIP_INFO bd_clip)
+{
+    int ret;
+
+    ret = bdmv_clip_video_stream_add_group(s, bd_clip.video_stream_count,
+                                              bd_clip.video_streams);
+    if (ret < 0)
+        return ret;
+
+    ret = bdmv_clip_video_stream_add_group(s, bd_clip.sec_video_stream_count,
+                                              bd_clip.sec_video_streams);
+    if (ret < 0)
+        return ret;
+
+    return 0;
+}
+
+static int bdmv_clip_audio_stream_analyze(AVFormatContext *s, const BLURAY_STREAM_INFO bd_st_audio,
+                                          BDMVAudioStreamEntry *entry)
+{
+    enum AVCodecID codec_id   = AV_CODEC_ID_NONE;
+    int sample_rate           = 0;
+
+    switch (bd_st_audio.coding_type) {
+        case BLURAY_STREAM_TYPE_AUDIO_MPEG1:
+            codec_id = AV_CODEC_ID_MP1;
+            break;
+        case BLURAY_STREAM_TYPE_AUDIO_MPEG2:
+            codec_id = AV_CODEC_ID_MP2;
+            break;
+        case BLURAY_STREAM_TYPE_AUDIO_AC3:
+            codec_id = AV_CODEC_ID_AC3;
+            break;
+        case BLURAY_STREAM_TYPE_AUDIO_AC3PLUS:
+        case BLURAY_STREAM_TYPE_AUDIO_AC3PLUS_SECONDARY:
+            codec_id = AV_CODEC_ID_EAC3;
+            break;
+        case BLURAY_STREAM_TYPE_AUDIO_TRUHD:
+            codec_id = AV_CODEC_ID_TRUEHD;
+            break;
+        case BLURAY_STREAM_TYPE_AUDIO_DTS:
+        case BLURAY_STREAM_TYPE_AUDIO_DTSHD:
+        case BLURAY_STREAM_TYPE_AUDIO_DTSHD_MASTER:
+        case BLURAY_STREAM_TYPE_AUDIO_DTSHD_SECONDARY:
+            codec_id = AV_CODEC_ID_DTS;
+            break;
+        case BLURAY_STREAM_TYPE_AUDIO_LPCM:
+            codec_id = AV_CODEC_ID_PCM_BLURAY;
+            break;
+    }
+
+    switch (bd_st_audio.rate) {
+        case BLURAY_AUDIO_RATE_48:
+            sample_rate = 48000;
+            break;
+        case BLURAY_AUDIO_RATE_96:
+        case BLURAY_AUDIO_RATE_96_COMBO:
+            sample_rate = 96000;
+            break;
+        case BLURAY_AUDIO_RATE_192:
+        case BLURAY_AUDIO_RATE_192_COMBO:
+            sample_rate = 192000;
+            break;
+    }
+
+    if (codec_id == AV_CODEC_ID_NONE || !sample_rate) {
+        av_log(s, AV_LOG_ERROR, "Invalid audio stream parameters for PID %02x\n", bd_st_audio.pid);
+
+        return AVERROR_INVALIDDATA;
+    }
+
+    entry->pid         = bd_st_audio.pid;
+    entry->codec_id    = codec_id;
+    entry->sample_rate = sample_rate;
+    entry->lang_iso    = ff_convert_lang_to(bd_st_audio.lang, AV_LANG_ISO639_2_BIBL);
+
+    return 0;
+}
+
+static int bdmv_clip_audio_stream_add(AVFormatContext *s, BDMVAudioStreamEntry *entry)
+{
+    AVStream *st;
+    FFStream *sti;
+
+    st = avformat_new_stream(s, NULL);
+    if (!st)
+        return AVERROR(ENOMEM);
+
+    st->id                              = entry->pid;
+    st->codecpar->codec_type            = AVMEDIA_TYPE_AUDIO;
+    st->codecpar->codec_id              = entry->codec_id;
+    st->codecpar->sample_rate           = entry->sample_rate;
+    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  = AVSTREAM_PARSE_FULL;
+
+    avpriv_set_pts_info(st, BDMV_PTS_WRAP_BITS,
+                        BDMV_TIME_BASE_Q.num, BDMV_TIME_BASE_Q.den);
+
+    return 0;
+}
+
+static int bdmv_clip_audio_stream_add_group(AVFormatContext *s, const int nb_bd_streams,
+                                            BLURAY_STREAM_INFO *bd_streams)
+{
+    int ret;
+
+    for (int i = 0; i < nb_bd_streams; i++) {
+        BDMVAudioStreamEntry entry             = {0};
+        BDMVAudioStreamEntry entry_truehd_core = {0};
+        const BLURAY_STREAM_INFO bd_st_audio   = bd_streams[i];
+
+        ret = bdmv_clip_audio_stream_analyze(s, bd_st_audio, &entry);
+        if (ret < 0) {
+            av_log(s, AV_LOG_ERROR, "Unable to analyze audio stream\n");
+            return ret;
+        }
+
+        ret = bdmv_clip_audio_stream_add(s, &entry);
+        if (ret < 0) {
+            av_log(s, AV_LOG_ERROR, "Unable to add audio stream\n");
+            return ret;
+        }
+
+        /* MPEG-TS demuxer will add TrueHD AC3 core as a stream with the same PID */
+        if (entry.codec_id == AV_CODEC_ID_TRUEHD) {
+            entry_truehd_core.pid      = entry.pid;
+            entry_truehd_core.codec_id = AV_CODEC_ID_AC3;
+            entry_truehd_core.lang_iso = entry.lang_iso;
+
+            ret = bdmv_clip_audio_stream_add(s, &entry_truehd_core);
+            if (ret < 0) {
+                av_log(s, AV_LOG_ERROR, "Unable to add audio stream\n");
+                return ret;
+            }
+        }
+    }
+
+    return 0;
+}
+
+static int bdmv_clip_audio_stream_add_all(AVFormatContext *s, const BLURAY_CLIP_INFO bd_clip)
+{
+    int ret;
+
+    ret = bdmv_clip_audio_stream_add_group(s, bd_clip.audio_stream_count,
+                                              bd_clip.audio_streams);
+    if (ret < 0)
+        return ret;
+
+    ret = bdmv_clip_audio_stream_add_group(s, bd_clip.sec_audio_stream_count,
+                                              bd_clip.sec_audio_streams);
+    if (ret < 0)
+        return ret;
+
+    return 0;
+}
+
+static int bdmv_clip_subtitle_stream_analyze(AVFormatContext *s, const BLURAY_STREAM_INFO bd_st_sub,
+                                             BDMVSubtitleStreamEntry *entry)
+{
+    enum AVCodecID codec_id;
+
+    switch (bd_st_sub.coding_type) {
+        case BLURAY_STREAM_TYPE_SUB_TEXT:
+            codec_id = AV_CODEC_ID_HDMV_TEXT_SUBTITLE;
+            break;
+        case BLURAY_STREAM_TYPE_SUB_PG:
+            codec_id = AV_CODEC_ID_HDMV_PGS_SUBTITLE;
+            break;
+    }
+
+    if (codec_id == AV_CODEC_ID_NONE) {
+        av_log(s, AV_LOG_ERROR, "Invalid subtitle stream parameters for PID %02x\n", bd_st_sub.pid);
+
+        return AVERROR_INVALIDDATA;
+    }
+
+    entry->pid       = bd_st_sub.pid;
+    entry->codec_id  = codec_id;
+    entry->lang_iso  = ff_convert_lang_to(bd_st_sub.lang, AV_LANG_ISO639_2_BIBL);
+
+    return 0;
+}
+
+static int bdmv_clip_subtitle_stream_add(AVFormatContext *s, BDMVSubtitleStreamEntry *entry)
+{
+    AVStream *st;
+    FFStream *sti;
+
+    st = avformat_new_stream(s, NULL);
+    if (!st)
+        return AVERROR(ENOMEM);
+
+    st->id                   = entry->pid;
+    st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
+    st->codecpar->codec_id   = entry->codec_id;
+
+    if (entry->lang_iso)
+        av_dict_set(&st->metadata, "language", entry->lang_iso, 0);
+
+    st->disposition          = entry->disposition;
+
+    sti = ffstream(st);
+    sti->request_probe = 0;
+    sti->need_parsing  = AVSTREAM_PARSE_HEADERS;
+
+    avpriv_set_pts_info(st, BDMV_PTS_WRAP_BITS,
+                        BDMV_TIME_BASE_Q.num, BDMV_TIME_BASE_Q.den);
+
+    return 0;
+}
+
+static int bdmv_clip_subtitle_stream_add_all(AVFormatContext *s, const BLURAY_CLIP_INFO bd_clip)
+{
+    int ret;
+
+    for (int i = 0; i < bd_clip.pg_stream_count; i++) {
+        BDMVSubtitleStreamEntry entry = {0};
+        const BLURAY_STREAM_INFO bd_st_sub = bd_clip.pg_streams[i];
+
+        ret = bdmv_clip_subtitle_stream_analyze(s, bd_st_sub, &entry);
+        if (ret < 0) {
+            av_log(s, AV_LOG_ERROR, "Unable to analyze subtitle stream\n");
+            return ret;
+        }
+
+        ret = bdmv_clip_subtitle_stream_add(s, &entry);
+        if (ret < 0) {
+            av_log(s, AV_LOG_ERROR, "Unable to add subtitle stream\n");
+            return ret;
+        }
+    }
+
+    return 0;
+}
+
+static void bdmv_clip_close(AVFormatContext *s)
+{
+    BDMVDemuxContext *c = s->priv_data;
+
+    if (c->cur_clip.file)
+        c->cur_clip.file->close(c->cur_clip.file);
+
+    c->cur_clip.idx     = -1;
+    c->cur_clip.is_open = 0;
+    c->cur_clip.file    = NULL;
+}
+
+static int bdmv_clip_open(AVFormatContext *s, const int idx, const char *path)
+{
+    BDMVDemuxContext *c = s->priv_data;
+
+    BD_FILE_H *file;
+
+    av_assert2(idx < c->bd_mpls->clip_count);
+
+    file = bd_open_file_dec(c->bd, path);
+    if (!file) {
+        av_log(s, AV_LOG_ERROR, "Unable to open next M2TS clip\n");
+
+        return AVERROR_EXTERNAL;
+    }
+
+    c->cur_clip.idx     = idx;
+    c->cur_clip.is_open = 1;
+    c->cur_clip.file    = file;
+
+    return 0;
+}
+
+static int bdmv_mpls_next_ts_block_filtered(AVFormatContext *s, int *need_reset,
+                                            uint8_t *buf, int buf_size)
+{
+    BDMVDemuxContext *c = s->priv_data;
+
+    if (buf_size != BDMV_FILE_BLOCK_SIZE)
+        return AVERROR(ENOMEM);
+
+    return bd_read(c->bd, buf, buf_size);
+}
+
+static int bdmv_mpls_next_ts_block_direct(AVFormatContext *s, int *need_reset,
+                                          uint8_t *buf, int buf_size)
+{
+    BDMVDemuxContext *c = s->priv_data;
+
+    int  ret;
+    int  cur_clip_idx;
+    int  next_clip_idx;
+    char next_clip_path[23];
+    char entry_clip_path[23];
+
+    if (buf_size != BDMV_FILE_BLOCK_SIZE)
+        return AVERROR(ENOMEM);
+
+    if (!c->cur_clip.is_open) {
+        bdmv_clip_format_path2(entry_clip_path, c->bd_mpls->clips[0].clip_id);
+
+        ret = bdmv_clip_open(s, 0, entry_clip_path);
+        if (ret < 0)
+            return ret;
+
+        c->bd_next_clip_pts = c->bd_mpls->clip_count > 0 ? c->bd_mpls->clips[1].start_time : 0;
+    }
+
+    /* read the next block */
+    ret = c->cur_clip.file->read(c->cur_clip.file, buf, BDMV_FILE_BLOCK_SIZE);
+    if (ret < 0) {
+        av_log(s, AV_LOG_ERROR, "Unable to read next block\n");
+
+        return AVERROR_EXTERNAL;
+    }
+
+    /* we have a block of the transport stream, pass it along */
+    if (ret > 0)
+        return ret;
+
+    /* we are at the end of this clip */
+    cur_clip_idx = c->cur_clip.idx;
+    bdmv_clip_close(s);
+
+    /* we are at the end of the playlist */
+    next_clip_idx = cur_clip_idx + 1;
+    if (next_clip_idx == c->bd_mpls->clip_count)
+        return AVERROR_EOF;
+
+    /* there is an upcoming clip, calculate the PTS offset and switch */
+    c->bd_pts_offset += c->bd_next_clip_pts - c->bd_mpls->clips[cur_clip_idx].start_time;
+
+    bdmv_clip_format_path2(next_clip_path, c->bd_mpls->clips[next_clip_idx].clip_id);
+
+    ret = bdmv_clip_open(s, next_clip_idx, next_clip_path);
+    if (ret < 0)
+        return ret;
+
+    c->bd_next_clip_pts = c->bd_mpls->clips[next_clip_idx + 1].start_time;
+
+    (*need_reset) = 1;
+
+    return AVERROR_EOF;
+}
+
+static int bdmv_mpls_chapters_setup(AVFormatContext *s)
+{
+    BDMVDemuxContext *c = s->priv_data;
+
+    for (int i = 0; i < c->bd_mpls->chapter_count; i++) {
+        const BLURAY_TITLE_CHAPTER bd_chapter = c->bd_mpls->chapters[i];
+        uint64_t bd_chapter_end;
+
+        if (!bd_chapter.duration)
+            continue;
+
+        bd_chapter_end = bd_chapter.start + bd_chapter.duration;
+
+        if (!avpriv_new_chapter(s, i, BDMV_TIME_BASE_Q, bd_chapter.start, bd_chapter_end, NULL))
+            return AVERROR(ENOMEM);
+    }
+
+    s->duration = av_rescale_q(c->bd_mpls->duration, BDMV_TIME_BASE_Q, AV_TIME_BASE_Q);
+
+    return 0;
+}
+
+static int bdmv_mpls_open(AVFormatContext *s)
+{
+    BDMVDemuxContext *c = s->priv_data;
+    int ret;
+    int main_title_id;
+    BLURAY_TITLE_INFO *title_info = NULL;
+
+    if (!c->opt_item) {
+        main_title_id = bd_get_main_title(c->bd);
+        if (main_title_id < 0) {
+            av_log(s, AV_LOG_ERROR, "Unable to detect main title, please set it manually\n");
+
+            return AVERROR_STREAM_NOT_FOUND;
+        }
+
+        title_info = bd_get_title_info(c->bd, main_title_id, c->opt_angle);
+        if (title_info)
+            c->opt_item = title_info->playlist;
+    } else {
+        /* find our MPLS */
+        for (int i = 0; i < c->bd_nb_titles; i++) {
+            BLURAY_TITLE_INFO *cur_title_info = bd_get_title_info(c->bd, i, c->opt_angle);
+            if (!cur_title_info)
+                continue;
+
+            if (cur_title_info->playlist == c->opt_item) {
+                title_info = cur_title_info;
+                break;
+            } else {
+                bd_free_title_info(cur_title_info);
+            }
+        }
+    }
+
+    if (!title_info || title_info->clip_count < 1) {
+        av_log(s, AV_LOG_ERROR, "Unable to load the selected MPLS, it is invalid or not found\n");
+
+        return AVERROR_INVALIDDATA;
+    }
+
+    c->bd_mpls = title_info;
+
+    ret = bdmv_mpls_chapters_setup(s);
+    if (ret < 0)
+        return ret;
+
+    ret = bdmv_clip_video_stream_add_all(s, title_info->clips[0]);
+    if (ret < 0)
+        return ret;
+
+    ret = bdmv_clip_audio_stream_add_all(s, title_info->clips[0]);
+    if (ret < 0)
+        return ret;
+
+    ret = bdmv_clip_subtitle_stream_add_all(s, title_info->clips[0]);
+    if (ret < 0)
+        return ret;
+
+    if (c->opt_mpls_mode == BDMV_MPLS_DEMUX_MODE_FILTERED) {
+        bd_select_playlist(c->bd, c->opt_item);
+        bd_select_angle(c->bd, c->opt_angle);
+    }
+
+    return 0;
+}
+
+static int bdmv_m2ts_next_ts_block(AVFormatContext *s, int *need_reset,
+                                   uint8_t *buf, int buf_size)
+{
+    BDMVDemuxContext *c = s->priv_data;
+
+    int  ret;
+    char entry_clip_path[23];
+
+    if (buf_size != BDMV_FILE_BLOCK_SIZE)
+        return AVERROR(ENOMEM);
+
+    /* open the segment */
+    if (!c->cur_clip.file) {
+        bdmv_clip_format_path(entry_clip_path, c->opt_item);
+
+        ret = bdmv_clip_open(s, 0, entry_clip_path);
+        if (ret < 0)
+            return ret;
+    }
+
+    /* read the next block */
+    ret = c->cur_clip.file->read(c->cur_clip.file, buf, BDMV_FILE_BLOCK_SIZE);
+    if (ret < 0) {
+        av_log(s, AV_LOG_ERROR, "Unable to read next block\n");
+
+        return AVERROR_EXTERNAL;
+    }
+
+    /* we have a block of the transport stream, pass it along */
+    if (ret > 0)
+        return ret;
+
+    /* we are at EOF */
+    bdmv_clip_close(s);
+
+    return AVERROR_EOF;
+}
+
+static int bdmv_m2ts_open(AVFormatContext *s)
+{
+    BDMVDemuxContext *c = s->priv_data;
+    int ret;
+
+    for (int i = 0; i < c->mpegts_ctx->nb_streams; i++) {
+        AVStream *st  = avformat_new_stream(s, NULL);
+        AVStream *ist = c->mpegts_ctx->streams[i];
+        if (!st)
+            return AVERROR(ENOMEM);
+
+        st->id = i;
+        ret = avcodec_parameters_copy(st->codecpar, ist->codecpar);
+        if (ret < 0)
+            return ret;
+
+        avpriv_set_pts_info(st, ist->pts_wrap_bits, ist->time_base.num, ist->time_base.den);
+    }
+
+    return 0;
+}
+
+static int bdmv_subdemux_read_data(void *opaque, uint8_t *buf, int buf_size)
+{
+    AVFormatContext *s  = opaque;
+    BDMVDemuxContext *c = s->priv_data;
+
+    int ret;
+    int need_reset = 0;
+
+    if (c->opt_domain == BDMV_DOMAIN_M2TS) {
+        ret = bdmv_m2ts_next_ts_block(s, &need_reset, buf, buf_size);
+    } else {
+        switch (c->opt_mpls_mode) {
+            case BDMV_MPLS_DEMUX_MODE_FILTERED:
+                ret = bdmv_mpls_next_ts_block_filtered(s, &need_reset, buf, buf_size);
+                break;
+            case BDMV_MPLS_DEMUX_MODE_DIRECT:
+                ret = bdmv_mpls_next_ts_block_direct  (s, &need_reset, buf, buf_size);
+                break;
+            default:
+                ret = AVERROR(ENOSYS);
+                goto subdemux_eof;
+        }
+    }
+
+    if (ret < 0) {
+        c->subdemux_reset = ret == AVERROR_EOF && need_reset;
+
+        goto subdemux_eof;
+    }
+
+    return ret;
+
+subdemux_eof:
+    c->mpegts_pb.pub.eof_reached = 1;
+    c->mpegts_pb.pub.error       = ret;
+    c->mpegts_pb.pub.read_packet = NULL;
+    c->mpegts_pb.pub.buf_end     = c->mpegts_pb.pub.buf_ptr = c->mpegts_pb.pub.buffer;
+
+    return ret;
+}
+
+static void bdmv_subdemux_close(AVFormatContext *s)
+{
+    BDMVDemuxContext *c = s->priv_data;
+
+    av_freep(&c->mpegts_pb.pub.buffer);
+    avformat_close_input(&c->mpegts_ctx);
+}
+
+static int bdmv_subdemux_open(AVFormatContext *s)
+{
+    BDMVDemuxContext *c = s->priv_data;
+    extern const FFInputFormat ff_mpegts_demuxer;
+    int ret;
+
+    if (!(c->mpegts_buf = av_mallocz(BDMV_FILE_BLOCK_SIZE)))
+        return AVERROR(ENOMEM);
+
+    ffio_init_context(&c->mpegts_pb, c->mpegts_buf, BDMV_FILE_BLOCK_SIZE, 0, s,
+                      bdmv_subdemux_read_data, NULL, NULL);
+    c->mpegts_pb.pub.seekable = 0;
+
+    if (!(c->mpegts_ctx = avformat_alloc_context()))
+        return AVERROR(ENOMEM);
+
+    ret = ff_copy_whiteblacklists(c->mpegts_ctx, s);
+    if (ret < 0) {
+        avformat_free_context(c->mpegts_ctx);
+        c->mpegts_ctx = NULL;
+
+        return ret;
+    }
+
+    c->mpegts_ctx->flags                = AVFMT_FLAG_CUSTOM_IO;
+    c->mpegts_ctx->ctx_flags           |= AVFMTCTX_UNSEEKABLE;
+    c->mpegts_ctx->probesize            = c->opt_domain == BDMV_DOMAIN_M2TS ? s->probesize : 0;
+    c->mpegts_ctx->max_analyze_duration = c->opt_domain == BDMV_DOMAIN_M2TS ? s->max_analyze_duration : 0;
+    c->mpegts_ctx->interrupt_callback   = s->interrupt_callback;
+    c->mpegts_ctx->pb                   = &c->mpegts_pb.pub;
+    c->mpegts_ctx->io_open              = NULL;
+
+    return avformat_open_input(&c->mpegts_ctx, "", &ff_mpegts_demuxer.p, NULL);
+}
+
+static int bdmv_subdemux_reset(AVFormatContext *s)
+{
+    int ret;
+
+    av_log(s, AV_LOG_VERBOSE, "Resetting sub-demuxer\n");
+
+    bdmv_subdemux_close(s);
+
+    ret = bdmv_subdemux_open(s);
+    if (ret < 0)
+        return ret;
+
+    return 0;
+}
+
+static int bdmv_structure_open(AVFormatContext *s)
+{
+    BDMVDemuxContext        *c = s->priv_data;
+    const BLURAY_DISC_INFO  *disc_info;
+
+    c->bd = bd_open(s->url, NULL);
+    if (!c->bd) {
+        av_log(s, AV_LOG_ERROR, "Unable to open BDMV structure\n");
+
+        return AVERROR_EXTERNAL;
+    }
+
+    disc_info = bd_get_disc_info(c->bd);
+    if (!disc_info || !disc_info->bluray_detected) {
+        av_log(s, AV_LOG_ERROR, "Invalid BDMV structure\n");
+
+        return AVERROR_EXTERNAL;
+    }
+
+    if ((disc_info->aacs_detected   && !disc_info->aacs_handled) ||
+        (disc_info->bdplus_detected && !disc_info->bdplus_handled)) {
+        av_log(s, AV_LOG_ERROR, "Protected BDMV structures are not supported\n");
+
+        return AVERROR_EXTERNAL;
+    }
+
+    /* needed before bd_get_main_title() and bd_get_title_info() */
+    c->bd_nb_titles = bd_get_titles(c->bd, TITLES_ALL, 0);
+    if (c->bd_nb_titles < 1) {
+        av_log(s, AV_LOG_ERROR, "Disc structure has no usable MPLS playlists\n");
+
+        return AVERROR_EXTERNAL;
+    }
+
+    return 0;
+}
+
+static int bdmv_read_header(AVFormatContext *s)
+{
+    BDMVDemuxContext *c = s->priv_data;
+    int ret;
+
+    if (c->opt_domain == BDMV_DOMAIN_MPLS) {
+        ret = bdmv_structure_open(s);
+        if (ret < 0)
+            return ret;
+
+        ret = bdmv_mpls_open(s);
+        if (ret < 0)
+            return ret;
+
+        ret = bdmv_subdemux_open(s);
+        if (ret < 0)
+            return ret;
+
+        return 0;
+    } else if (c->opt_domain == BDMV_DOMAIN_M2TS) {
+        ret = bdmv_structure_open(s);
+        if (ret < 0)
+            return ret;
+
+        ret = bdmv_subdemux_open(s);
+        if (ret < 0)
+            return ret;
+
+        ret = bdmv_m2ts_open(s);
+        if (ret < 0)
+            return ret;
+
+        return 0;
+    }
+
+    return AVERROR(ENOSYS);
+}
+
+static int bdmv_read_packet(AVFormatContext *s, AVPacket *pkt)
+{
+    BDMVDemuxContext *c = s->priv_data;
+
+    int ret;
+    int is_key    = 0;
+    int st_mapped = 0;
+    AVStream *st_subdemux;
+
+    ret = av_read_frame(c->mpegts_ctx, pkt);
+    if (ret < 0) {
+        if (c->subdemux_reset && ret == AVERROR_EOF) {
+            c->subdemux_reset = 0;
+            c->pts_offset     = c->bd_pts_offset;
+
+            ret = bdmv_subdemux_reset(s);
+            if (ret < 0)
+                return ret;
+
+            return FFERROR_REDO;
+        }
+
+        return ret;
+    }
+
+    st_subdemux = c->mpegts_ctx->streams[pkt->stream_index];
+    is_key      = pkt->flags & AV_PKT_FLAG_KEY;
+
+    if (c->opt_domain == BDMV_DOMAIN_MPLS) {
+        if (!c->corrupt_warned && (pkt->flags & AV_PKT_FLAG_CORRUPT)) {
+            av_log(s, AV_LOG_WARNING, "Corrupt packets were found in the stream, this could be "
+                                      "due to libbluray's filtering feature or a disc issue. "
+                                      "Try demuxing the MPLS in direct mode.\n");
+            c->corrupt_warned = 1;
+        }
+
+        /* map the subdemuxer stream to the parent demuxer's stream (by PID and codec) */
+        for (int i = 0; i < s->nb_streams; i++) {
+            if (s->streams[i]->id == st_subdemux->id &&
+                s->streams[i]->codecpar->codec_id == st_subdemux->codecpar->codec_id) {
+
+                pkt->stream_index = s->streams[i]->index;
+                st_mapped         = 1;
+                break;
+            }
+        }
+    } else {
+        st_mapped = 1;
+    }
+
+    if (!st_mapped || pkt->pts == AV_NOPTS_VALUE || pkt->dts == AV_NOPTS_VALUE)
+        goto discard;
+
+    if (!c->play_started) {
+        /* try to start at the beginning of a GOP */
+        if (st_subdemux->codecpar->codec_type != AVMEDIA_TYPE_VIDEO || !is_key || pkt->pts < 0)
+            goto discard;
+
+        c->first_pts    = pkt->pts;
+        c->play_started = 1;
+    }
+
+    pkt->pts += c->pts_offset - c->first_pts;
+    pkt->dts += c->pts_offset - c->first_pts;
+
+    if (pkt->pts < 0)
+        goto discard;
+
+    av_log(s, AV_LOG_TRACE, "st=%d pts=%" PRId64 " dts=%" PRId64 " "
+                            "pts_offset=%" PRId64 " first_pts=%" PRId64 "\n",
+                            pkt->stream_index, pkt->pts, pkt->dts,
+                            c->pts_offset, c->first_pts);
+
+    return 0;
+
+discard:
+    av_log(s, st_mapped ? AV_LOG_VERBOSE : AV_LOG_DEBUG,
+           "Discarding frame @ st=%d pts=%" PRId64 " dts=%" PRId64 " st_mapped=%d\n",
+           st_mapped ? pkt->stream_index : -1, pkt->pts, pkt->dts, st_mapped);
+
+    return FFERROR_REDO;
+}
+
+static int bdmv_read_close(AVFormatContext *s)
+{
+    BDMVDemuxContext *c = s->priv_data;
+
+    if (c->cur_clip.is_open)
+        bdmv_clip_close(s);
+
+    if (c->bd_mpls)
+        bd_free_title_info(c->bd_mpls);
+
+    if (c->bd)
+        bd_close(c->bd);
+
+    return 0;
+}
+
+static int bdmv_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags)
+{
+    BDMVDemuxContext *c = s->priv_data;
+    int64_t  result_seek;
+    uint64_t result_tell;
+
+    if (c->opt_domain != BDMV_DOMAIN_MPLS || c->opt_mpls_mode != BDMV_MPLS_DEMUX_MODE_FILTERED) {
+        av_log(s, AV_LOG_ERROR, "Seeking only supported with filtered MPLS demuxing\n");
+
+        return AVERROR_PATCHWELCOME;
+    }
+
+    if ((flags & AVSEEK_FLAG_BYTE))
+        return AVERROR(ENOSYS);
+
+    if (timestamp < 0 || timestamp > s->duration)
+        return AVERROR(EINVAL);
+
+    if (!c->seek_warned) {
+        av_log(s, AV_LOG_WARNING, "Seeking is inherently unreliable and will result "
+                                  "in imprecise timecodes from this point\n");
+
+        c->seek_warned = 1;
+    }
+
+    result_seek = bd_seek_time(c->bd, timestamp);
+    if (result_seek < 0) {
+        av_log(s, AV_LOG_ERROR, "libbluray: seeking to %" PRId64 " failed\n", timestamp);
+
+        return AVERROR_EXTERNAL;
+    }
+
+    result_tell = bd_tell_time(c->bd);
+
+    c->first_pts     = 0;
+    c->play_started  = 0;
+    c->pts_offset    = result_tell;
+    c->bd_pts_offset = result_tell;
+
+    avio_flush(&c->mpegts_pb.pub);
+    ff_read_frame_flush(c->mpegts_ctx);
+
+    av_log(s, AV_LOG_DEBUG, "seeking: requested=%" PRId64 " "
+                            "result_seek=%" PRId64 " result_tell=%" PRId64 "\n",
+                            timestamp, result_seek, result_tell);
+
+    return 0;
+}
+
+#define OFFSET(x) offsetof(BDMVDemuxContext, x)
+#define DEC AV_OPT_FLAG_DECODING_PARAM
+static const AVOption bdmv_options[] = {
+    { "domain",             "domain within the BDMV structure",   OFFSET(opt_domain),         AV_OPT_TYPE_INT,   { .i64=BDMV_DOMAIN_MPLS }, BDMV_DOMAIN_MPLS, BDMV_DOMAIN_M2TS, AV_OPT_FLAG_DECODING_PARAM, .unit = "domain" },
+       { "mpls",            "open an MPLS",                       0,                          AV_OPT_TYPE_CONST, { .i64=BDMV_DOMAIN_MPLS }, .flags = AV_OPT_FLAG_DECODING_PARAM, .unit = "domain" },
+       { "m2ts",            "open an MPEG-TS clip by ID",         1,                          AV_OPT_TYPE_CONST, { .i64=BDMV_DOMAIN_M2TS }, .flags = AV_OPT_FLAG_DECODING_PARAM, .unit = "domain" },
+    { "mpls_mode",          "MPLS demuxing mode",                 OFFSET(opt_mpls_mode),      AV_OPT_TYPE_INT,   { .i64=BDMV_MPLS_DEMUX_MODE_FILTERED }, BDMV_MPLS_DEMUX_MODE_FILTERED, BDMV_MPLS_DEMUX_MODE_DIRECT, AV_OPT_FLAG_DECODING_PARAM, .unit = "mpls_mode" },
+      { "filtered",         "read filtered MPLS",                 0,                          AV_OPT_TYPE_CONST, { .i64=BDMV_MPLS_DEMUX_MODE_FILTERED }, .flags = AV_OPT_FLAG_DECODING_PARAM, .unit = "mpls_mode" },
+      { "direct",           "read MPEG-TS clips directly",        1,                          AV_OPT_TYPE_CONST, { .i64=BDMV_MPLS_DEMUX_MODE_DIRECT },   .flags = AV_OPT_FLAG_DECODING_PARAM, .unit = "mpls_mode" },
+    {"angle",              "angle number for MPLS",               OFFSET(opt_angle),          AV_OPT_TYPE_INT,   { .i64=1 },                  1,                99,        AV_OPT_FLAG_DECODING_PARAM },
+    {"item",               "item number for domain (0=auto)",     OFFSET(opt_item),           AV_OPT_TYPE_INT,   { .i64=0 },                  0,                9999,      AV_OPT_FLAG_DECODING_PARAM },
+    { NULL },
+};
+
+static const AVClass bdmv_demuxer_class = {
+    .class_name = "BDMV demuxer",
+    .item_name  = av_default_item_name,
+    .option     = bdmv_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+const FFInputFormat ff_bdmv_demuxer = {
+    .p.name         = "bdmv",
+    .p.long_name    = NULL_IF_CONFIG_SMALL("Blu-ray Disc Movie (BDMV)"),
+    .p.flags        = AVFMT_SHOW_IDS | AVFMT_TS_DISCONT   | AVFMT_SEEK_TO_PTS |
+                      AVFMT_NOFILE   | AVFMT_NO_BYTE_SEEK | AVFMT_NOGENSEARCH | AVFMT_NOBINSEARCH |
+                      AVFMT_EXPERIMENTAL,
+    .p.priv_class   = &bdmv_demuxer_class,
+    .priv_data_size = sizeof(BDMVDemuxContext),
+    .read_header    = bdmv_read_header,
+    .read_packet    = bdmv_read_packet,
+    .read_close     = bdmv_read_close,
+    .read_seek      = bdmv_read_seek
+};
-- 
2.34.1



More information about the ffmpeg-devel mailing list