From 07c5b72b3ff860fa8c2606ab5a40551d30e0e99b Mon Sep 17 00:00:00 2001 From: Scott Theisen Date: Thu, 14 Nov 2024 23:49:01 -0500 Subject: [PATCH] Improve code for DVB VBI data libavcodec/codec_desc.c: move to correct location and correct description libavcodec/codec_id.h: move to correct location in data codecs libavformat/mpegts-mythtv.c: reuse teletext_descriptor code for VBI_teletext_descriptor libmythbase/iso639.h: add used include libmythbase/stringutil.h: add new funtion split_sv() libmythtv/decoders/avformatdecoder.cpp: AvFormatDecoder::ScanTeletextCaptions() was specific to AV_CODEC_ID_DVB_TELETEXT, looking only at the teletext_descriptor in the PMT. ScanTeletextCaptions() no longer uses the MythTV exported PMT, using what FFmpeg already exported when it parsed PMTs. Both AV_CODEC_ID_DVB_TELETEXT and AV_CODEC_ID_DVB_VBI (if there is EBU teletext data in the VBI data) now add to the tracks list and multiple streams are now supported. --- .../external/FFmpeg/libavcodec/codec_desc.c | 12 +-- mythtv/external/FFmpeg/libavcodec/codec_id.h | 2 +- .../FFmpeg/libavformat/mpegts-mythtv.c | 10 +-- mythtv/libs/libmythbase/iso639.h | 1 + mythtv/libs/libmythbase/stringutil.h | 29 +++++++ .../libmythtv/decoders/avformatdecoder.cpp | 87 +++++++++---------- 6 files changed, 80 insertions(+), 61 deletions(-) diff --git a/mythtv/external/FFmpeg/libavcodec/codec_desc.c b/mythtv/external/FFmpeg/libavcodec/codec_desc.c index 10ef794a7f9..2d8cc4588b4 100644 --- a/mythtv/external/FFmpeg/libavcodec/codec_desc.c +++ b/mythtv/external/FFmpeg/libavcodec/codec_desc.c @@ -3622,12 +3622,6 @@ static const AVCodecDescriptor codec_descriptors[] = { .name = "mpeg2vbi", .long_name = NULL_IF_CONFIG_SMALL("ivtv proprietary embedded VBI captions"), }, - { - .id = AV_CODEC_ID_DVB_VBI, - .type = AVMEDIA_TYPE_DATA, - .name = "dvb_vbi", - .long_name = NULL_IF_CONFIG_SMALL("dvb teletext"), - }, { .id = AV_CODEC_ID_DSMCC_B, .type = AVMEDIA_TYPE_DATA, @@ -3720,6 +3714,12 @@ static const AVCodecDescriptor codec_descriptors[] = { .name = "lcevc", .long_name = NULL_IF_CONFIG_SMALL("LCEVC (Low Complexity Enhancement Video Coding) / MPEG-5 LCEVC / MPEG-5 part 2"), }, + { + .id = AV_CODEC_ID_DVB_VBI, + .type = AVMEDIA_TYPE_DATA, + .name = "dvb_vbi", + .long_name = NULL_IF_CONFIG_SMALL("DVB VBI data"), + }, { .id = AV_CODEC_ID_MPEG2TS, .type = AVMEDIA_TYPE_DATA, diff --git a/mythtv/external/FFmpeg/libavcodec/codec_id.h b/mythtv/external/FFmpeg/libavcodec/codec_id.h index 4ba76654d51..22b5abdcbbd 100644 --- a/mythtv/external/FFmpeg/libavcodec/codec_id.h +++ b/mythtv/external/FFmpeg/libavcodec/codec_id.h @@ -577,7 +577,6 @@ enum AVCodecID { /* MythTV */ /* teletext codecs */ AV_CODEC_ID_MPEG2VBI, - AV_CODEC_ID_DVB_VBI, /* DSMCC codec */ AV_CODEC_ID_DSMCC_B, @@ -598,6 +597,7 @@ enum AVCodecID { AV_CODEC_ID_BIN_DATA, AV_CODEC_ID_SMPTE_2038, AV_CODEC_ID_LCEVC, + AV_CODEC_ID_DVB_VBI, AV_CODEC_ID_PROBE = 0x19000, ///< codec_id is not known (like AV_CODEC_ID_NONE) but lavf should attempt to identify it diff --git a/mythtv/external/FFmpeg/libavformat/mpegts-mythtv.c b/mythtv/external/FFmpeg/libavformat/mpegts-mythtv.c index 83dfdae58b1..2087da7837f 100644 --- a/mythtv/external/FFmpeg/libavformat/mpegts-mythtv.c +++ b/mythtv/external/FFmpeg/libavformat/mpegts-mythtv.c @@ -1917,6 +1917,7 @@ int ff_mythtv_parse_mpeg2_descriptor(AVFormatContext *fc, AVStream *st, int stre } } break; + case VBI_TELETEXT_DESCRIPTOR: case 0x56: /* DVB teletext descriptor */ { uint8_t *extradata = NULL; @@ -2070,15 +2071,6 @@ int ff_mythtv_parse_mpeg2_descriptor(AVFormatContext *fc, AVStream *st, int stre sti->stream_identifier = 1 + get8(pp, desc_end); st->component_tag = sti->stream_identifier - 1; break; - case VBI_TELETEXT_DESCRIPTOR: - language[0] = get8(pp, desc_end); - language[1] = get8(pp, desc_end); - language[2] = get8(pp, desc_end); - language[3] = 0; - - if (language[0]) - av_dict_set(&st->metadata, "language", language, 0); - break; case METADATA_DESCRIPTOR: if (get16(pp, desc_end) == 0xFFFF) *pp += 4; diff --git a/mythtv/libs/libmythbase/iso639.h b/mythtv/libs/libmythbase/iso639.h index e82fc0c6c1d..61e206c3b38 100644 --- a/mythtv/libs/libmythbase/iso639.h +++ b/mythtv/libs/libmythbase/iso639.h @@ -2,6 +2,7 @@ #ifndef ISO_639_2_H #define ISO_639_2_H +#include #include #include diff --git a/mythtv/libs/libmythbase/stringutil.h b/mythtv/libs/libmythbase/stringutil.h index c08598fdb92..927fcb44f37 100644 --- a/mythtv/libs/libmythbase/stringutil.h +++ b/mythtv/libs/libmythbase/stringutil.h @@ -5,6 +5,9 @@ #include #endif +#include +#include + #include #include @@ -60,6 +63,32 @@ inline bool naturalSortCompare(const QString &a, const QString &b, MBASE_PUBLIC QString formatKBytes(int64_t sizeKB, int prec=1); MBASE_PUBLIC QString formatBytes(int64_t sizeB, int prec=1); +/** +Split a `std::string_view` into a `std::vector` of `std::string_view`s. + +@param s String to split, may be empty. +@param delimiter String to determine where to split. +@return Will always have a size >= 1. Only valid as long as the data + referenced by s remains valid. +*/ +inline std::vector split_sv(const std::string_view s, const std::string_view delimiter) +{ + // There are infinitely many empty strings at each position, avoid infinite loop + if (delimiter.empty()) + return {s}; + std::vector tokens; + size_t last_pos = 0; + size_t pos = 0; + do + { + pos = s.find(delimiter, last_pos); + tokens.emplace_back(s.substr(last_pos, pos - last_pos)); + last_pos = pos + delimiter.size(); + } + while (pos != std::string_view::npos); + return tokens; +} + } // namespace StringUtil #endif // STRINGUTIL_H_ diff --git a/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp b/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp index 1d883d80428..669def9b474 100644 --- a/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp +++ b/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp @@ -108,6 +108,8 @@ enum V4L2_MPEG_LINE_TYPES : std::uint8_t { #include "mythvideoprofile.h" #include "remoteencoder.h" +using namespace std::string_view_literals; + #define LOC QString("AFD: ") // Maximum number of sequential invalid data packet errors before we try @@ -1738,57 +1740,51 @@ void AvFormatDecoder::ScanTeletextCaptions(int av_index) if (!m_tracks[kTrackTypeTeletextCaptions].empty()) return; - MythAVBufferRef pmt_buffer {get_pmt_section_for_AVStream_index(m_ic, av_index)}; - if (!pmt_buffer.has_buffer()) + AVStream* st = m_ic->streams[av_index]; + const AVDictionaryEntry* language_dictionary_entry = + av_dict_get(st->metadata, "language", nullptr, 0); + + if (language_dictionary_entry == nullptr || + language_dictionary_entry->value == nullptr || + st->codecpar->extradata == nullptr + ) { return; } - const ProgramMapTable pmt(PSIPTable(pmt_buffer.data())); - - for (uint i = 0; i < pmt.StreamCount(); i++) - { - if (pmt.StreamType(i) != StreamID::PrivData) - continue; - const desc_list_t desc_list = MPEGDescriptor::ParseOnlyInclude( - pmt.StreamInfo(i), pmt.StreamInfoLength(i), - DescriptorID::teletext); + std::vector languages {StringUtil::split_sv(language_dictionary_entry->value, ","sv)}; - for (const auto *desc : desc_list) + if (st->codecpar->extradata_size != static_cast(languages.size() * 2)) + { + return; + } + for (size_t i = 0; i < languages.size(); i++) + { + if (languages[i].size() != 3) { - const TeletextDescriptor td(desc); - if (!td.IsValid()) - continue; - for (uint k = 0; k < td.StreamCount(); k++) - { - int type = td.TeletextType(k); - int language = td.CanonicalLanguageKey(k); - uint magazine = td.TeletextMagazineNum(k); - if (magazine == 0) - magazine = 8; - uint pagenum = td.TeletextPageNum(k); - uint lang_idx = (magazine << 8) | pagenum; - StreamInfo si {av_index, 0, language, lang_idx}; - if (type == 2 || type == 1) - { - TrackType track = (type == 2) ? kTrackTypeTeletextCaptions : - kTrackTypeTeletextMenu; - m_tracks[track].push_back(si); - LOG(VB_PLAYBACK, LOG_INFO, LOC + - QString("Teletext stream #%1 (%2) is in the %3 language" - " on page %4 %5.") - .arg(QString::number(k), - (type == 2) ? "Caption" : "Menu", - iso639_key_toName(language), - QString::number(magazine), - QString::number(pagenum))); - } - } + continue; + } + int language = iso639_str3_to_key(languages[i].data()); + uint8_t teletext_type = st->codecpar->extradata[i * 2] >> 3; + uint8_t teletext_magazine_number = st->codecpar->extradata[i * 2] & 0x7; + if (teletext_magazine_number == 0) + teletext_magazine_number = 8; + uint8_t teletext_page_number = st->codecpar->extradata[i * 2 + 1]; + if (teletext_type == 2 || teletext_type == 1) + { + TrackType track = (teletext_type == 2) ? + kTrackTypeTeletextCaptions : + kTrackTypeTeletextMenu; + m_tracks[track].emplace_back(av_index, 0, language, + (static_cast(teletext_magazine_number) << 8) | teletext_page_number); + LOG(VB_PLAYBACK, LOG_INFO, LOC + + QString("Teletext stream #%1 (%2) is in the %3 language on page %4 %5.") + .arg(QString::number(i), + (teletext_type == 2) ? "Caption" : "Menu", + iso639_key_toName(language), + QString::number(teletext_magazine_number), + QString::number(teletext_page_number))); } - - // Assume there is only one multiplexed teletext stream in PMT.. - if (!m_tracks[kTrackTypeTeletextCaptions].empty()) - break; } } @@ -2241,7 +2237,8 @@ int AvFormatDecoder::ScanStreams(bool novideo) } case AVMEDIA_TYPE_DATA: { - ScanTeletextCaptions(static_cast(strm)); + if (par->codec_id == AV_CODEC_ID_DVB_VBI) + ScanTeletextCaptions(static_cast(strm)); m_bitrate += par->bit_rate; LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("data codec (%1)") .arg(AVMediaTypeToString(par->codec_type)));