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 <mart...@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 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".