From 3653357cb73565d9925f4b4f1007268f14f767f7 Mon Sep 17 00:00:00 2001 From: "Pohsiang (John) Hsu" Date: Fri, 18 Aug 2023 15:14:10 -0700 Subject: [PATCH] when video has discontinuity, we update the sample duration to reflect the discontinuity --- transform/BasePackager.cs | 6 ++ transform/ShakaPackager.cs | 5 ++ transform/TransMuxer.cs | 139 +++++++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+) diff --git a/transform/BasePackager.cs b/transform/BasePackager.cs index 4f3cf7c..40601f5 100644 --- a/transform/BasePackager.cs +++ b/transform/BasePackager.cs @@ -48,6 +48,8 @@ abstract class BasePackager : IPackager public bool TranscodeAudio { get; protected set; } + public bool TranscodeVideo { get; protected set; } + public TranscodeAudioInfo TranscodeAudioInfoData { get; protected set; } = new(); public IDictionary> FileToTrackMap => _fileToTrackMap; @@ -237,6 +239,10 @@ await Task.Run(() => _transMuxer.TranscodeAudioAsync( TranscodeAudioInfoData, cancellationToken)); } + if (TranscodeVideo && track.Type == StreamType.Video) + { + _transMuxer.TranscodeVideo(filePath); + } } else { diff --git a/transform/ShakaPackager.cs b/transform/ShakaPackager.cs index f1d71ba..3708464 100644 --- a/transform/ShakaPackager.cs +++ b/transform/ShakaPackager.cs @@ -90,6 +90,11 @@ public ShakaPackager(MigratorOptions options, AssetDetails assetDetails, TransMu _logger.LogDebug("video / audio tracks start time not within 0.1 sec and audio stream has discontinuities, transcode required"); TranscodeAudio = true; } + + if (videoStream.HasDiscontinuities()) + { + TranscodeVideo = true; + } } } } diff --git a/transform/TransMuxer.cs b/transform/TransMuxer.cs index 3a7b48e..22bbeac 100644 --- a/transform/TransMuxer.cs +++ b/transform/TransMuxer.cs @@ -218,5 +218,144 @@ public async Task TranscodeAudioAsync(string source, string destination, Transco // FFmpeg will zero out tdft baseMediaDecodeTime, add it back in place. AddOffsetToTfdtBox(destination, (ulong)transcodeAudioInfo.VideoStartTimeInAudioTimeScale); } + + private static List GetDecodeMediaTime(string fileName) + { + using var stream = File.Open(fileName, FileMode.Open, FileAccess.Read); + var reader = new MP4Reader(stream); + stream.Position = 0; + List decodeTimes = new(); + while (stream.Position < stream.Length) + { + Int64 startPosition = reader.BaseStream.Position; + Int64 size = reader.ReadUInt32(); // size of current box + UInt32 type = reader.ReadUInt32(); // type of current box + + // Parse extended size + if (size == 0) + { + throw new InvalidDataException("Invalid size."); + } + else if (size == 1) + { + size = reader.ReadInt64(); + } + + if (type == MP4BoxType.moof) + { + stream.Position = startPosition; // rewind + var moofBox = MP4BoxFactory.ParseSingleBox(reader); + + foreach (var c in moofBox.Children) + { + if (c.Type == MP4BoxType.traf) + { + foreach (var cc in c.Children) + { + if (cc.Type == MP4BoxType.tfdt) + { + tfdtBox tfdtBox = (tfdtBox)cc; // will throw + decodeTimes.Add(tfdtBox.DecodeTime); + break; + } + } + } + } + } + //skip till the beginning of the next box. + stream.Position = startPosition + size; + } + return decodeTimes; + } + + private void UpdateTrackRunDuration(string fileName) + { + List decodeTimes = GetDecodeMediaTime(fileName); + + using var stream = File.Open(fileName, FileMode.Open, FileAccess.ReadWrite); + var reader = new MP4Reader(stream); + var writer = new MP4Writer(stream); + stream.Position = 0; + + + int totalDecodeTime = decodeTimes.Count; + int curDecodeTimesIndex = 0; + + while (stream.Position < stream.Length) + { + Int64 startPosition = reader.BaseStream.Position; + Int64 size = reader.ReadUInt32(); // size of current box + UInt32 type = reader.ReadUInt32(); // type of current box + + // Parse extended size + if (size == 0) + { + throw new InvalidDataException("Invalid size."); + } + else if (size == 1) + { + size = reader.ReadInt64(); + } + + if (type == MP4BoxType.moof) + { + stream.Position = startPosition; // rewind + var moofBox = MP4BoxFactory.ParseSingleBox(reader); + + ulong offsetToTrun = moofBox.ComputeBaseSizeBox(); + foreach (var c in moofBox.Children) + { + if (c.Type == MP4BoxType.traf) + { + offsetToTrun += moofBox.ComputeBaseSizeBox(); + foreach (var cc in c.Children) + { + if (cc.Type == MP4BoxType.trun) + { + trunBox trunBox = (trunBox)cc; // will throw + trunBox.TrunFlags flag = (trunBox.TrunFlags) trunBox.Flags; + if ((flag & trunBox.TrunFlags.SampleDurationPresent) != trunBox.TrunFlags.SampleDurationPresent) + { + throw new InvalidDataException("Unexpected, sampleDurationPresent must be present"); + } + + long trunPosition = startPosition + (long)offsetToTrun; + stream.Position = trunPosition; + ulong totalDuration = 0; + for (int i = 0; i < trunBox.Entries.Count; ++i) + { + totalDuration += (ulong)trunBox.Entries[i].SampleDuration!; + } + if (curDecodeTimesIndex + 1 < decodeTimes.Count) + { + if (decodeTimes[curDecodeTimesIndex] + totalDuration < decodeTimes[curDecodeTimesIndex + 1]) + { + ulong offset = decodeTimes[curDecodeTimesIndex + 1] - decodeTimes[curDecodeTimesIndex] - totalDuration; + _logger.LogTrace("Update duration due to discontinuity at dt {0}, offset {1}.", decodeTimes[curDecodeTimesIndex], offset); + trunBox.Entries[trunBox.Entries.Count - 1].SampleDuration += (uint)offset; + } + } + trunBox.WriteTo(writer); + curDecodeTimesIndex++; + break; + } + offsetToTrun += cc.Size.Value; + } + } + else + { + offsetToTrun += c.Size.Value; + } + } + } + //skip till the beginning of the next box. + stream.Position = startPosition + size; + } + } + + public void TranscodeVideo(string destination) + { + UpdateTrackRunDuration(destination); + } } }