From 1b3225e2676056b6d2dfd8ceb3e79fb3a3d5e856 Mon Sep 17 00:00:00 2001 From: Paul Gardiner Date: Tue, 28 Nov 2023 12:12:46 +0000 Subject: [PATCH 1/6] Factor out the best track calculation for selecting the subtitle track Also generalise the algorithm to allow forced tracks to be disfavored. This is work towards improving the handling of forced tracks. --- .../libs/libmythtv/decoders/decoderbase.cpp | 86 ++++++++++++------- mythtv/libs/libmythtv/decoders/decoderbase.h | 1 + 2 files changed, 55 insertions(+), 32 deletions(-) diff --git a/mythtv/libs/libmythtv/decoders/decoderbase.cpp b/mythtv/libs/libmythtv/decoders/decoderbase.cpp index 5bde9e38db9..d6844d10ac2 100644 --- a/mythtv/libs/libmythtv/decoders/decoderbase.cpp +++ b/mythtv/libs/libmythtv/decoders/decoderbase.cpp @@ -1036,6 +1036,58 @@ bool DecoderBase::InsertTrack(uint Type, const StreamInfo &Info) return true; } +/** \fn DecoderBase::BestTrack(uint, bool) + * \brief Determine the best track according to weights + * + * Select the best track. Primary attribute is to favor or disfavor + * a forced track. Secondary attribute is language preference, + * in order of most preferred to least preferred language. + * Third attribute is track order, preferring the earliesttrack. + * + * Whether to favor or disfavor forced is controlled by the second + * parameter. + * + * This function must not be called without taking m_trackLock + * + * \return the highest weighted track, or -1 if none. +*/ +int DecoderBase::BestTrack(uint Type, bool forcedPreferred) +{ + LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Trying to select track (w/lang & %1forced)") + .arg(forcedPreferred ? "" : "!")); + const int kForcedWeight = forcedPreferred ? (1 << 20) : -(1 << 20); + const int kLanguageWeight = (1 << 10); + const int kPositionWeight = (1 << 0); + int bestScore = -1; + int selTrack = -1; + uint numStreams = static_cast(m_tracks[Type].size()); + + for (uint i = 0; i < numStreams; i++) + { + bool forced = (Type == kTrackTypeSubtitle && + m_tracks[Type][i].m_forced && + m_parent->ForcedSubtitlesFavored()); + int position = static_cast(numStreams) - static_cast(i); + int language = 0; + for (uint j = 0; (language == 0) && (j < m_languagePreference.size()); ++j) + { + if (m_tracks[Type][i].m_language == m_languagePreference[j]) + language = static_cast(m_languagePreference.size()) - static_cast(j); + } + int score = (1 << 20) + + (kForcedWeight * static_cast(forced)) + + (kLanguageWeight * language) + + (kPositionWeight * position); + if (score > bestScore) + { + bestScore = score; + selTrack = static_cast(i); + } + } + + return selTrack; +} + /** \fn DecoderBase::AutoSelectTrack(uint) * \brief Select best track. * @@ -1090,38 +1142,8 @@ int DecoderBase::AutoSelectTrack(uint Type) if (selTrack < 0) { - // Select the best track. Primary attribute is to favor a - // forced track. Secondary attribute is language preference, - // in order of most preferred to least preferred language. - // Third attribute is track order, preferring the earliest - // track. - LOG(VB_PLAYBACK, LOG_INFO, LOC + "Trying to select track (w/lang & forced)"); - const int kForcedWeight = (1 << 20); - const int kLanguageWeight = (1 << 10); - const int kPositionWeight = (1 << 0); - int bestScore = -1; - selTrack = 0; - for (uint i = 0; i < numStreams; i++) - { - bool forced = (Type == kTrackTypeSubtitle && - m_tracks[Type][i].m_forced && - m_parent->ForcedSubtitlesFavored()); - int position = static_cast(numStreams) - static_cast(i); - int language = 0; - for (uint j = 0; (language == 0) && (j < m_languagePreference.size()); ++j) - { - if (m_tracks[Type][i].m_language == m_languagePreference[j]) - language = static_cast(m_languagePreference.size()) - static_cast(j); - } - int score = (kForcedWeight * static_cast(forced)) + - (kLanguageWeight * language) + - (kPositionWeight * position); - if (score > bestScore) - { - bestScore = score; - selTrack = static_cast(i); - } - } + // Find best track favoring forced. + selTrack = BestTrack(Type, true); } int oldTrack = m_currentTrack[Type]; diff --git a/mythtv/libs/libmythtv/decoders/decoderbase.h b/mythtv/libs/libmythtv/decoders/decoderbase.h index d504da2b382..0df15486088 100644 --- a/mythtv/libs/libmythtv/decoders/decoderbase.h +++ b/mythtv/libs/libmythtv/decoders/decoderbase.h @@ -264,6 +264,7 @@ class DecoderBase static AVPixelFormat GetBestVideoFormat(AVPixelFormat* Formats, const VideoFrameTypes* RenderFormats); protected: + int BestTrack(uint Type, bool forcedPreferred); virtual int AutoSelectTrack(uint Type); void AutoSelectTracks(void); void ResetTracks(void); From 8de22989e7ac63ead6377bfcc2094139e55275ae Mon Sep 17 00:00:00 2001 From: Paul Gardiner Date: Tue, 28 Nov 2023 15:55:07 +0000 Subject: [PATCH 2/6] Handle forced AV subtitles without needing a forced track to be selected This improves the behaviour of mythfrontend in its automatic handling of forced subtitle tracks. The previous behaviour relied on automatically selecting the forced track as a default for the user's preference. The new behaviour attempts to find a non-forced track for the user's preference and changes the subtitle toggle feature to be between forced and non-forced, rather than on and off. --- .../libmythtv/decoders/avformatdecoder.cpp | 9 +++++++-- mythtv/libs/libmythtv/decoders/decoderbase.cpp | 18 ++++++++++++++++++ mythtv/libs/libmythtv/decoders/decoderbase.h | 1 + 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp b/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp index f2019fd0ef0..5002e083c25 100644 --- a/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp +++ b/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp @@ -3916,7 +3916,8 @@ bool AvFormatDecoder::ProcessSubtitlePacket(AVStream *curstream, AVPacket *pkt) m_trackLock.lock(); int subIdx = m_selectedTrack[kTrackTypeSubtitle].m_av_stream_index; - bool isForcedTrack = m_selectedTrack[kTrackTypeSubtitle].m_forced; + int forcedSubIdx = m_selectedForcedTrack[kTrackTypeSubtitle].m_av_stream_index; + bool isForcedTrack = false; m_trackLock.unlock(); int gotSubtitles = 0; @@ -3941,7 +3942,8 @@ bool AvFormatDecoder::ProcessSubtitlePacket(AVStream *curstream, AVPacket *pkt) } } } - else if (m_decodeAllSubtitles || pkt->stream_index == subIdx) + else if (m_decodeAllSubtitles || pkt->stream_index == subIdx + || pkt->stream_index == forcedSubIdx) { m_avCodecLock.lock(); AVCodecContext *ctx = m_codecMap.GetCodecContext(curstream); @@ -3950,6 +3952,9 @@ bool AvFormatDecoder::ProcessSubtitlePacket(AVStream *curstream, AVPacket *pkt) subtitle.start_display_time += pts; subtitle.end_display_time += pts; + + if (pkt->stream_index != subIdx) + isForcedTrack = true; } if (gotSubtitles) diff --git a/mythtv/libs/libmythtv/decoders/decoderbase.cpp b/mythtv/libs/libmythtv/decoders/decoderbase.cpp index d6844d10ac2..c637112cc44 100644 --- a/mythtv/libs/libmythtv/decoders/decoderbase.cpp +++ b/mythtv/libs/libmythtv/decoders/decoderbase.cpp @@ -1144,6 +1144,24 @@ int DecoderBase::AutoSelectTrack(uint Type) { // Find best track favoring forced. selTrack = BestTrack(Type, true); + + if (Type == kTrackTypeSubtitle) + { + if (m_tracks[Type][selTrack].m_forced) + { + // A forced AV Subtitle tracks is handled without the user + // explicitly enabling subtitles. Try to find a good non-forced + // track that can be swapped to in the case the user does + // explicitly enable subtitles. + int nonForcedTrack = BestTrack(Type, false); + + if (!m_tracks[Type][nonForcedTrack].m_forced) + { + m_selectedForcedTrack[Type] = m_tracks[Type][selTrack]; + selTrack = nonForcedTrack; + } + } + } } int oldTrack = m_currentTrack[Type]; diff --git a/mythtv/libs/libmythtv/decoders/decoderbase.h b/mythtv/libs/libmythtv/decoders/decoderbase.h index 0df15486088..9f236dc8bff 100644 --- a/mythtv/libs/libmythtv/decoders/decoderbase.h +++ b/mythtv/libs/libmythtv/decoders/decoderbase.h @@ -361,6 +361,7 @@ class DecoderBase std::array m_tracks; std::array m_wantedTrack; std::array m_selectedTrack; + std::array m_selectedForcedTrack; /// language preferences for auto-selection of streams std::vector m_languagePreference; From e201b26422502993949e98f68f3ed3a83773cb2b Mon Sep 17 00:00:00 2001 From: Paul Gardiner Date: Fri, 1 Dec 2023 19:59:39 +0000 Subject: [PATCH 3/6] Avoid displaying two AV subtitle tracks simultaneously Recent changes to the handling of forced AV subtitle tracks require that two tracks may be considered for processing simultaneously. This happens if there is a forced track and a user enabled one. This commit ensures that the user enabled one inhibits the forced one. --- mythtv/libs/libmythtv/captions/subtitlereader.cpp | 10 +++++++++- mythtv/libs/libmythtv/captions/subtitlereader.h | 3 ++- mythtv/libs/libmythtv/captions/textsubtitleparser.cpp | 2 +- mythtv/libs/libmythtv/decoders/avformatdecoder.cpp | 1 + 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/mythtv/libs/libmythtv/captions/subtitlereader.cpp b/mythtv/libs/libmythtv/captions/subtitlereader.cpp index c8519845815..03fb45aac6c 100644 --- a/mythtv/libs/libmythtv/captions/subtitlereader.cpp +++ b/mythtv/libs/libmythtv/captions/subtitlereader.cpp @@ -57,11 +57,19 @@ void SubtitleReader::SeekFrame(int64_t ts, int flags) bool SubtitleReader::AddAVSubtitle(AVSubtitle &subtitle, bool fix_position, + bool is_selected_forced_track, bool allow_forced, - bool isExternal) + bool isExternal) { bool enableforced = false; bool forced = false; + + if (m_avSubtitlesEnabled && is_selected_forced_track) + { + FreeAVSubtitle(subtitle); + return enableforced; + } + for (unsigned i = 0; i < subtitle.num_rects; i++) { forced = forced || static_cast(subtitle.rects[i]->flags & AV_SUBTITLE_FLAG_FORCED); diff --git a/mythtv/libs/libmythtv/captions/subtitlereader.h b/mythtv/libs/libmythtv/captions/subtitlereader.h index 5102e94c626..fca814456b2 100644 --- a/mythtv/libs/libmythtv/captions/subtitlereader.h +++ b/mythtv/libs/libmythtv/captions/subtitlereader.h @@ -56,7 +56,8 @@ class SubtitleReader : public QObject AVSubtitles* GetAVSubtitles(void) { return &m_avSubtitles; } bool AddAVSubtitle(AVSubtitle& subtitle, bool fix_position, - bool allow_forced, bool isExternal); + bool is_selected_forced_track, bool allow_forced, + bool isExternal); void ClearAVSubtitles(void); static void FreeAVSubtitle(AVSubtitle &sub); diff --git a/mythtv/libs/libmythtv/captions/textsubtitleparser.cpp b/mythtv/libs/libmythtv/captions/textsubtitleparser.cpp index edeb3822342..95800d8f0a1 100644 --- a/mythtv/libs/libmythtv/captions/textsubtitleparser.cpp +++ b/mythtv/libs/libmythtv/captions/textsubtitleparser.cpp @@ -255,7 +255,7 @@ int TextSubtitleParser::ReadNextSubtitle(void) sub.start_display_time = av_q2d(m_stream->time_base) * m_pkt->dts * 1000; sub.end_display_time = av_q2d(m_stream->time_base) * (m_pkt->dts + m_pkt->duration) * 1000; - m_parent->AddAVSubtitle(sub, m_decCtx->codec_id == AV_CODEC_ID_XSUB, false, true); + m_parent->AddAVSubtitle(sub, m_decCtx->codec_id == AV_CODEC_ID_XSUB, false, false, true); return ret; } diff --git a/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp b/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp index 5002e083c25..7c757f3fccd 100644 --- a/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp +++ b/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp @@ -3974,6 +3974,7 @@ bool AvFormatDecoder::ProcessSubtitlePacket(AVStream *curstream, AVPacket *pkt) bool forcedon = m_parent->GetSubReader(pkt->stream_index)->AddAVSubtitle( subtitle, curstream->codecpar->codec_id == AV_CODEC_ID_XSUB, + isForcedTrack, m_parent->GetAllowForcedSubtitles(), false); m_parent->EnableForcedSubtitles(forcedon || isForcedTrack); } From 80a088e1bb306e0126463f3f4dfbe62435d654fd Mon Sep 17 00:00:00 2001 From: Paul Gardiner Date: Sat, 23 Dec 2023 15:25:37 +0000 Subject: [PATCH 4/6] Remove condition ForcedSubtitlesFavored in best track selection The effect of this condition was to inhibit selection of a forced track if the global setting "Always Display Subtitles" was on. Doing so is not desirable, and is no longer necessary, since the new way of handling forced tracks doesn't interact unfavorably with full tracks. --- mythtv/libs/libmythtv/decoders/decoderbase.cpp | 3 +-- mythtv/libs/libmythtv/mythplayer.h | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/mythtv/libs/libmythtv/decoders/decoderbase.cpp b/mythtv/libs/libmythtv/decoders/decoderbase.cpp index c637112cc44..6b07fe5ac21 100644 --- a/mythtv/libs/libmythtv/decoders/decoderbase.cpp +++ b/mythtv/libs/libmythtv/decoders/decoderbase.cpp @@ -1065,8 +1065,7 @@ int DecoderBase::BestTrack(uint Type, bool forcedPreferred) for (uint i = 0; i < numStreams; i++) { bool forced = (Type == kTrackTypeSubtitle && - m_tracks[Type][i].m_forced && - m_parent->ForcedSubtitlesFavored()); + m_tracks[Type][i].m_forced); int position = static_cast(numStreams) - static_cast(i); int language = 0; for (uint j = 0; (language == 0) && (j < m_languagePreference.size()); ++j) diff --git a/mythtv/libs/libmythtv/mythplayer.h b/mythtv/libs/libmythtv/mythplayer.h index 1db6cbbfcbb..c64bf82f41b 100644 --- a/mythtv/libs/libmythtv/mythplayer.h +++ b/mythtv/libs/libmythtv/mythplayer.h @@ -201,9 +201,6 @@ class MTV_PUBLIC MythPlayer : public QObject // Public Audio/Subtitle/EIA-608/EIA-708 stream selection - thread safe void EnableForcedSubtitles(bool enable); - bool ForcedSubtitlesFavored(void) const { - return m_allowForcedSubtitles && !m_captionsEnabledbyDefault; - } // How to handle forced Subtitles (i.e. when in a movie someone speaks // in a different language than the rest of the movie, subtitles are // forced on even if the user doesn't have them turned on.) From d280f65da8e3418b264c197c4d8b866f12bc4856 Mon Sep 17 00:00:00 2001 From: Paul Gardiner Date: Sun, 24 Dec 2023 14:43:43 +0000 Subject: [PATCH 5/6] Avoid unintuitive behaviour when the user selects a forced subtitle track By default, TOGGLECC toggles between displaying the user's selected track and the forced track (if present). If the user selects a forced track, that behaviour can make TOGGLECC look like it does nothing. This commit special cases that scenario, allowing TOGGLECC to toggle between on and off for the user selected track. --- mythtv/libs/libmythtv/decoders/avformatdecoder.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp b/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp index 7c757f3fccd..c13088e87a9 100644 --- a/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp +++ b/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp @@ -3917,6 +3917,7 @@ bool AvFormatDecoder::ProcessSubtitlePacket(AVStream *curstream, AVPacket *pkt) m_trackLock.lock(); int subIdx = m_selectedTrack[kTrackTypeSubtitle].m_av_stream_index; int forcedSubIdx = m_selectedForcedTrack[kTrackTypeSubtitle].m_av_stream_index; + bool mainTrackIsForced = m_selectedTrack[kTrackTypeSubtitle].m_forced; bool isForcedTrack = false; m_trackLock.unlock(); @@ -3975,7 +3976,7 @@ bool AvFormatDecoder::ProcessSubtitlePacket(AVStream *curstream, AVPacket *pkt) bool forcedon = m_parent->GetSubReader(pkt->stream_index)->AddAVSubtitle( subtitle, curstream->codecpar->codec_id == AV_CODEC_ID_XSUB, isForcedTrack, - m_parent->GetAllowForcedSubtitles(), false); + (m_parent->GetAllowForcedSubtitles() && !mainTrackIsForced), false); m_parent->EnableForcedSubtitles(forcedon || isForcedTrack); } From 7a4fecf67047391a05ad9f158c9343d0f9f403bc Mon Sep 17 00:00:00 2001 From: Paul Gardiner Date: Wed, 27 Dec 2023 14:46:54 +0000 Subject: [PATCH 6/6] Attempt to match forced track language to that of the selected main track --- mythtv/libs/libmythtv/decoders/decoderbase.cpp | 16 +++++++++++++++- mythtv/libs/libmythtv/decoders/decoderbase.h | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/mythtv/libs/libmythtv/decoders/decoderbase.cpp b/mythtv/libs/libmythtv/decoders/decoderbase.cpp index 6b07fe5ac21..012725e2339 100644 --- a/mythtv/libs/libmythtv/decoders/decoderbase.cpp +++ b/mythtv/libs/libmythtv/decoders/decoderbase.cpp @@ -975,6 +975,13 @@ int DecoderBase::SetTrack(uint Type, int TrackNo) { m_wantedTrack[Type] = m_tracks[Type][static_cast(m_currentTrack[Type])]; m_selectedTrack[Type] = m_tracks[Type][static_cast(m_currentTrack[Type])]; + if (Type == kTrackTypeSubtitle) + { + // Rechoose the associated forced track, preferring the same language + int forcedTrackIndex = BestTrack(Type, true, m_selectedTrack[Type].m_language); + if (m_tracks[Type][forcedTrackIndex].m_forced) + m_selectedForcedTrack[Type] = m_tracks[Type][forcedTrackIndex]; + } } return m_currentTrack[Type]; @@ -1047,11 +1054,14 @@ bool DecoderBase::InsertTrack(uint Type, const StreamInfo &Info) * Whether to favor or disfavor forced is controlled by the second * parameter. * + * A preferredlanguage can be specified as third parameter, which + * will override the user's preferrence list. + * * This function must not be called without taking m_trackLock * * \return the highest weighted track, or -1 if none. */ -int DecoderBase::BestTrack(uint Type, bool forcedPreferred) +int DecoderBase::BestTrack(uint Type, bool forcedPreferred, int preferredLanguage) { LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Trying to select track (w/lang & %1forced)") .arg(forcedPreferred ? "" : "!")); @@ -1068,6 +1078,10 @@ int DecoderBase::BestTrack(uint Type, bool forcedPreferred) m_tracks[Type][i].m_forced); int position = static_cast(numStreams) - static_cast(i); int language = 0; + if (preferredLanguage != 0 && m_tracks[Type][i].m_language == preferredLanguage) + { + language = static_cast(m_languagePreference.size()) + 1; + } for (uint j = 0; (language == 0) && (j < m_languagePreference.size()); ++j) { if (m_tracks[Type][i].m_language == m_languagePreference[j]) diff --git a/mythtv/libs/libmythtv/decoders/decoderbase.h b/mythtv/libs/libmythtv/decoders/decoderbase.h index 9f236dc8bff..4946f1d65eb 100644 --- a/mythtv/libs/libmythtv/decoders/decoderbase.h +++ b/mythtv/libs/libmythtv/decoders/decoderbase.h @@ -264,7 +264,7 @@ class DecoderBase static AVPixelFormat GetBestVideoFormat(AVPixelFormat* Formats, const VideoFrameTypes* RenderFormats); protected: - int BestTrack(uint Type, bool forcedPreferred); + int BestTrack(uint Type, bool forcedPreferred, int preferredLanguage = 0); virtual int AutoSelectTrack(uint Type); void AutoSelectTracks(void); void ResetTracks(void);