mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-05-30 12:28:27 +01:00
Fix A/V desync when resuming HLS with video transcode + audio copy (#16580)
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
Format / format-check (push) Has been cancelled
Tests / run-tests (macos-latest) (push) Has been cancelled
Tests / run-tests (ubuntu-latest) (push) Has been cancelled
Tests / run-tests (windows-latest) (push) Has been cancelled
OpenAPI Publish / OpenAPI - Publish Artifact (push) Has been cancelled
OpenAPI Publish / OpenAPI - Publish Unstable Spec (push) Has been cancelled
OpenAPI Publish / OpenAPI - Publish Stable Spec (push) Has been cancelled
Project Automation / Project board (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
Stale PR Check / Check PRs with merge conflicts (push) Has been cancelled
Stale Issue Labeler / Check for stale issues (push) Has been cancelled
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
Format / format-check (push) Has been cancelled
Tests / run-tests (macos-latest) (push) Has been cancelled
Tests / run-tests (ubuntu-latest) (push) Has been cancelled
Tests / run-tests (windows-latest) (push) Has been cancelled
OpenAPI Publish / OpenAPI - Publish Artifact (push) Has been cancelled
OpenAPI Publish / OpenAPI - Publish Unstable Spec (push) Has been cancelled
OpenAPI Publish / OpenAPI - Publish Stable Spec (push) Has been cancelled
Project Automation / Project board (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
Stale PR Check / Check PRs with merge conflicts (push) Has been cancelled
Stale Issue Labeler / Check for stale issues (push) Has been cancelled
Fix A/V desync when resuming HLS with video transcode + audio copy
This commit is contained in:
@@ -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 =
|
||||
[
|
||||
@@ -1547,20 +1548,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)
|
||||
@@ -3006,23 +3048,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;
|
||||
|
||||
Reference in New Issue
Block a user