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 f2019fd0ef0..c13088e87a9 100644 --- a/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp +++ b/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp @@ -3916,7 +3916,9 @@ 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 mainTrackIsForced = m_selectedTrack[kTrackTypeSubtitle].m_forced; + bool isForcedTrack = false; m_trackLock.unlock(); int gotSubtitles = 0; @@ -3941,7 +3943,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 +3953,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) @@ -3969,7 +3975,8 @@ 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, - m_parent->GetAllowForcedSubtitles(), false); + isForcedTrack, + (m_parent->GetAllowForcedSubtitles() && !mainTrackIsForced), false); m_parent->EnableForcedSubtitles(forcedon || isForcedTrack); } diff --git a/mythtv/libs/libmythtv/decoders/decoderbase.cpp b/mythtv/libs/libmythtv/decoders/decoderbase.cpp index 5bde9e38db9..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]; @@ -1036,6 +1043,64 @@ 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. + * + * 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 preferredLanguage) +{ + 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); + 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]) + 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,36 +1155,24 @@ 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++) + // Find best track favoring forced. + selTrack = BestTrack(Type, true); + + if (Type == kTrackTypeSubtitle) { - 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) + if (m_tracks[Type][selTrack].m_forced) { - bestScore = score; - selTrack = static_cast(i); + // 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; + } } } } diff --git a/mythtv/libs/libmythtv/decoders/decoderbase.h b/mythtv/libs/libmythtv/decoders/decoderbase.h index d504da2b382..4946f1d65eb 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, int preferredLanguage = 0); virtual int AutoSelectTrack(uint Type); void AutoSelectTracks(void); void ResetTracks(void); @@ -360,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; 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.)