diff options
| author | Daniel Țuțuianu <tutuianu_daniel@yahoo.com> | 2026-06-17 06:16:42 +0300 |
|---|---|---|
| committer | Daniel Țuțuianu <tutuianu_daniel@yahoo.com> | 2026-06-17 06:16:42 +0300 |
| commit | 1ea525a4083dbdc929605eb0eb5c6add93bc8392 (patch) | |
| tree | 97056e3e9b8e06ae825199214ec3f9d34b53e4c8 /MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | |
| parent | 372c1681d8272c6fa8f120a132bc40351067fb10 (diff) | |
| parent | 3307406ac8d7aa62184f99946f69a1cbf92a060b (diff) | |
Merge branch 'master' into fix/livetv-channel-icon-refresh
Resolve GuideManager conflict by keeping LiveTvChannelImageHelper so
channel icons re-fetch on every guide refresh, including when the URL
is unchanged.
Diffstat (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs')
| -rw-r--r-- | MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 143 |
1 files changed, 84 insertions, 59 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 8f6e36bce4..320e65231c 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -86,6 +86,7 @@ namespace MediaBrowser.Controller.MediaEncoding private readonly Version _minFFmpegQsvVppScaleModeOption = new Version(6, 0); private readonly Version _minFFmpegRkmppHevcDecDoviRpu = new Version(7, 1, 1); private readonly Version _minFFmpegReadrateCatchupOption = new Version(8, 0); + private readonly Version _minFFmpegNoiseBsfDrop = new Version(5, 0); private static readonly string[] _videoProfilesH264 = [ @@ -443,6 +444,13 @@ namespace MediaBrowser.Controller.MediaEncoding || state.VideoStream.VideoRangeType == VideoRangeType.HLG); } + private static bool IsDeinterlaceAvailable(EncodingJobInfo state) + { + var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); + var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); + return doDeintH264 || doDeintHevc; + } + private bool IsVideoStreamHevcRext(EncodingJobInfo state) { var videoStream = state.VideoStream; @@ -1547,20 +1555,61 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceContainer) { - var bitStreamArgs = string.Empty; + var filters = new List<string>(); + + var noiseFilter = GetCopiedAudioTrimBsf(state); + if (!string.IsNullOrEmpty(noiseFilter)) + { + filters.Add(noiseFilter); + } + var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.'); // Apply aac_adtstoasc bitstream filter when media source is in mpegts. if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase) && (string.Equals(mediaSourceContainer, "ts", StringComparison.OrdinalIgnoreCase) || string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase) - || string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase))) + || string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase)) + && IsAAC(state.AudioStream)) { - bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Audio); - bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs; + filters.Add("aac_adtstoasc"); } - return bitStreamArgs; + return filters.Count == 0 + ? string.Empty + : " -bsf:a " + string.Join(',', filters); + } + + // When video is transcoded, accurate_seek (the default) trims video to the + // exact seek point via decoder-side frame discard. But stream-copied audio + // bypasses the decoder, so it starts from the nearest keyframe — potentially + // seconds before the target. Use the noise bsf to drop copied audio packets + // before the seek target, achieving the same trim precision without + // re-encoding. The noise bsf's drop= parameter requires ffmpeg >= 5.0. + // Important: make sure not to use it with wtv because it breaks seeking + private string GetCopiedAudioTrimBsf(EncodingJobInfo state) + { + if (state.TranscodingType is not TranscodingJobType.Hls + || !state.IsVideoRequest + || IsCopyCodec(state.OutputVideoCodec) + || !IsCopyCodec(state.OutputAudioCodec) + || string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase) + || _mediaEncoder.EncoderVersion < _minFFmpegNoiseBsfDrop) + { + return null; + } + + var startTicks = state.BaseRequest.StartTimeTicks ?? 0; + if (startTicks <= 0) + { + return null; + } + + var seekSeconds = startTicks / (double)TimeSpan.TicksPerSecond; + return string.Format( + CultureInfo.InvariantCulture, + "noise=drop='lt(pts*tb\\,{0:F3})'", + seekSeconds); } public static string GetSegmentFileExtension(string segmentContainer) @@ -2014,11 +2063,15 @@ namespace MediaBrowser.Controller.MediaEncoding args += keyFrameArg + gopArg; } - // global_header produced by AMD HEVC VA-API encoder causes non-playable fMP4 on iOS + // The in-band Parameter Sets generated by the AMD HEVC VA-API encoder is inconsistent + // with the extradata generated by ffmpeg, causing decoding failures when using hvc1. if (string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) && _mediaEncoder.IsVaapiDeviceAmd) { - args += " -flags:v -global_header"; + // Extracting the extradata from the in-band PS to bypass the issue. + // This can be removed once the issue is resolved in libva or Mesa. + // Transcoding is unavoidable here, so using BSF will not conflict with BSF in remuxing. + args += " -flags:v -global_header -bsf:v extract_extradata=remove=0"; } return args; @@ -3002,23 +3055,6 @@ namespace MediaBrowser.Controller.MediaEncoding } seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(seekTick)); - - if (state.IsVideoRequest) - { - // If we are remuxing, then the copied stream cannot be seeked accurately (it will seek to the nearest - // keyframe). If we are using fMP4, then force all other streams to use the same inaccurate seeking to - // avoid A/V sync issues which cause playback issues on some devices. - // When remuxing video, the segment start times correspond to key frames in the source stream, so this - // option shouldn't change the seeked point that much. - // Important: make sure not to use it with wtv because it breaks seeking - if (state.TranscodingType is TranscodingJobType.Hls - && string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase) - && (IsCopyCodec(state.OutputVideoCodec) || IsCopyCodec(state.OutputAudioCodec)) - && !string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase)) - { - seekParam += " -noaccurate_seek"; - } - } } return seekParam; @@ -3821,9 +3857,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase); var isV4l2Encoder = vidEncoder.Contains("h264_v4l2m2m", StringComparison.OrdinalIgnoreCase); - var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); - var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); - var doDeintH2645 = doDeintH264 || doDeintHevc; + var doDeintH2645 = IsDeinterlaceAvailable(state); var doToneMap = IsSwTonemapAvailable(state, options); var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI; @@ -3975,9 +4009,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isCuInCuOut = isNvDecoder && isNvencEncoder; var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30; - var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); - var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); - var doDeintH2645 = doDeintH264 || doDeintHevc; + var doDeintH2645 = IsDeinterlaceAvailable(state); var doCuTonemap = IsHwTonemapAvailable(state, options); var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state); @@ -4186,9 +4218,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase); var isDxInDxOut = isD3d11vaDecoder && isAmfEncoder; - var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); - var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); - var doDeintH2645 = doDeintH264 || doDeintHevc; + var doDeintH2645 = IsDeinterlaceAvailable(state); var doOclTonemap = IsHwTonemapAvailable(state, options); var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state); @@ -4434,9 +4464,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase); var isQsvInQsvOut = isHwDecoder && isQsvEncoder; - var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); - var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); - var doDeintH2645 = doDeintH264 || doDeintHevc; + var doDeintH2645 = IsDeinterlaceAvailable(state); var doVppTonemap = IsIntelVppTonemapAvailable(state, options); var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options); var doTonemap = doVppTonemap || doOclTonemap; @@ -4728,12 +4756,10 @@ namespace MediaBrowser.Controller.MediaEncoding var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase); var isQsvInQsvOut = isHwDecoder && isQsvEncoder; - var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); - var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); var doVaVppTonemap = IsIntelVppTonemapAvailable(state, options); var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options); var doTonemap = doVaVppTonemap || doOclTonemap; - var doDeintH2645 = doDeintH264 || doDeintHevc; + var doDeintH2645 = IsDeinterlaceAvailable(state); var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state); var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; @@ -5059,12 +5085,10 @@ namespace MediaBrowser.Controller.MediaEncoding var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase); var isVaInVaOut = isVaapiDecoder && isVaapiEncoder; - var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); - var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); var doVaVppTonemap = isVaapiDecoder && IsIntelVppTonemapAvailable(state, options); var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options); var doTonemap = doVaVppTonemap || doOclTonemap; - var doDeintH2645 = doDeintH264 || doDeintHevc; + var doDeintH2645 = IsDeinterlaceAvailable(state); var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state); var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; @@ -5296,10 +5320,8 @@ namespace MediaBrowser.Controller.MediaEncoding var isSwEncoder = !isVaapiEncoder; var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase); - var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); - var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); var doVkTonemap = IsVulkanHwTonemapAvailable(state, options); - var doDeintH2645 = doDeintH264 || doDeintHevc; + var doDeintH2645 = IsDeinterlaceAvailable(state); var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state); var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; @@ -5536,9 +5558,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965; var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd; - var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); - var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); - var doDeintH2645 = doDeintH264 || doDeintHevc; + var doDeintH2645 = IsDeinterlaceAvailable(state); var doOclTonemap = IsHwTonemapAvailable(state, options); var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state); @@ -5769,9 +5789,7 @@ namespace MediaBrowser.Controller.MediaEncoding var reqMaxH = state.BaseRequest.MaxHeight; var threeDFormat = state.MediaSource.Video3DFormat; - var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); - var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); - var doDeintH2645 = doDeintH264 || doDeintHevc; + var doDeintH2645 = IsDeinterlaceAvailable(state); var doVtTonemap = IsVideoToolboxTonemapAvailable(state, options); var doMetalTonemap = !doVtTonemap && IsHwTonemapAvailable(state, options); var usingHwSurface = isVtDecoder && (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface); @@ -5970,9 +5988,7 @@ namespace MediaBrowser.Controller.MediaEncoding && (vidEncoder.Contains("h264", StringComparison.OrdinalIgnoreCase) || vidEncoder.Contains("hevc", StringComparison.OrdinalIgnoreCase)); - var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); - var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); - var doDeintH2645 = doDeintH264 || doDeintHevc; + var doDeintH2645 = IsDeinterlaceAvailable(state); var doOclTonemap = IsHwTonemapAvailable(state, options); var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state); @@ -6236,12 +6252,21 @@ namespace MediaBrowser.Controller.MediaEncoding overlayFilters?.RemoveAll(string.IsNullOrEmpty); var framerate = GetFramerateParam(state); - if (framerate.HasValue) + if (mainFilters is not null && framerate.HasValue) { - mainFilters.Insert(0, string.Format( - CultureInfo.InvariantCulture, - "fps={0}", - framerate.Value)); + var doDeintH2645 = IsDeinterlaceAvailable(state); + var fpsFilter = string.Format(CultureInfo.InvariantCulture, "fps={0}", framerate.Value); + + // For filter chain containing the deinterlace filter, + // place the fps filter at the end to preserve temporal info. + if (doDeintH2645) + { + mainFilters.Add(fpsFilter); + } + else + { + mainFilters.Insert(0, fpsFilter); + } } var mainStr = string.Empty; |
