// Copyright Epic Games, Inc. All Rights Reserved. #include "StreamAccessUnitBuffer.h" namespace Electra { FMultiTrackAccessUnitBuffer::FMultiTrackAccessUnitBuffer(EStreamType InForType) { Type = InForType; EmptyBuffer = CreateNewBuffer(); LastPoppedDTS.SetToInvalid(); LastPoppedPTS.SetToInvalid(); PlayableDurationPushedSinceEOT.SetToZero(); bEndOfData = false; bEndOfTrack = false; bLastPushWasBlocked = false; bIsParallelTrackMode = false; bPopAsDummyUntilSyncFrame = true; } TSharedPtrTS FMultiTrackAccessUnitBuffer::CreateNewBuffer() { TSharedPtrTS NewBuffer = MakeSharedTS(); return NewBuffer; } FMultiTrackAccessUnitBuffer::~FMultiTrackAccessUnitBuffer() { Clear(); } void FMultiTrackAccessUnitBuffer::SetParallelTrackMode() { bIsParallelTrackMode = true; } TSharedPtrTS FMultiTrackAccessUnitBuffer::FindOrCreateBufferFor(TSharedPtrTS& OutBufferSourceInfo, const TSharedPtrTS& InBufferInfo, bool bCreateIfNotExist) { // Note: The access mutex must have been locked already! for(int32 i=0; iPlaybackSequenceID == InBufferInfo->PlaybackSequenceID) { if (bIsParallelTrackMode) { if (BufferList[i].Info->HardIndex == InBufferInfo->HardIndex) { OutBufferSourceInfo = BufferList[i].Info; return BufferList[i].Buffer; } } else { OutBufferSourceInfo = BufferList[i].Info; return BufferList[i].Buffer; } } } TSharedPtrTS Buffer; if (bCreateIfNotExist) { FBufferByInfoType bit; OutBufferSourceInfo = bit.Info = InBufferInfo; Buffer = bit.Buffer = CreateNewBuffer(); BufferList.Emplace(MoveTemp(bit)); } return Buffer; } bool FMultiTrackAccessUnitBuffer::Push(FAccessUnit*& AU, const FAccessUnitBuffer::FConfiguration* BufferConfiguration, const FAccessUnitBuffer::FExternalBufferInfo* InCurrentTotalBufferUtilization) { check(AU->BufferSourceInfo.IsValid()); AccessLock.Lock(); TSharedPtrTS BufferSourceInfo; TSharedPtrTS Buffer = FindOrCreateBufferFor(BufferSourceInfo, AU->BufferSourceInfo, true); ActivateInitialBuffer(); // Pushing data to either buffer means that there is data and we are not at EOD here any more. // This does not necessarily mean that the selected track is not at EOD. Just that some track is not. bEndOfData = false; // Keep track of how much data since the last end-of-track was signaled has been added. // This is useful when looking at available content to detect underruns which may not be done if // a newly enabled track did not have enough time to collect new data. Only when sufficient data // was available might it make sense to check for underruns. if (bEndOfTrack) { bEndOfTrack = false; PlayableDurationPushedSinceEOT.SetToZero(); } check(AU->EarliestPTS.IsValid()); check(AU->LatestPTS.IsValid()); const bool bIsPlayableAU = AU->PTS >= AU->EarliestPTS && (AU->PTS + AU->Duration) < AU->LatestPTS; if (bIsPlayableAU) { PlayableDurationPushedSinceEOT += AU->Duration; } AccessLock.Unlock(); if (!Buffer.IsValid()) { return false; } bool bWasPushed = Buffer->Push(AU, BufferConfiguration, InCurrentTotalBufferUtilization); bLastPushWasBlocked = Buffer->WasLastPushBlocked(); return bWasPushed; } void FMultiTrackAccessUnitBuffer::PushEndOfDataFor(TSharedPtrTS InStreamSourceInfo) { AccessLock.Lock(); TSharedPtrTS BufferSourceInfo; TSharedPtrTS Buffer = FindOrCreateBufferFor(BufferSourceInfo, InStreamSourceInfo, true); ActivateInitialBuffer(); AccessLock.Unlock(); if (Buffer.IsValid()) { Buffer->PushEndOfData(); } } void FMultiTrackAccessUnitBuffer::PushEndOfDataAll() { FScopeLock lock(&AccessLock); bEndOfData = true; // Push an end-of-data into all tracks. for(auto& It : BufferList) { It.Buffer->PushEndOfData(); } } void FMultiTrackAccessUnitBuffer::SetEndOfTrackFor(TSharedPtrTS InStreamSourceInfo) { AccessLock.Lock(); TSharedPtrTS BufferSourceInfo; TSharedPtrTS Buffer = FindOrCreateBufferFor(BufferSourceInfo, InStreamSourceInfo, true); ActivateInitialBuffer(); AccessLock.Unlock(); if (Buffer.IsValid()) { Buffer->SetEndOfTrack(); } } void FMultiTrackAccessUnitBuffer::SetEndOfTrackAll() { FScopeLock lock(&AccessLock); bEndOfTrack = true; // Push an end-of-data into all tracks. for(auto& It : BufferList) { It.Buffer->SetEndOfTrack(); } } void FMultiTrackAccessUnitBuffer::Clear() { BufferList.Empty(); PendingBufferSwitch.Reset(); ActiveBuffer.Reset(); ActiveOutputBufferInfo.Reset(); LastPoppedBufferInfo.Reset(); LastPoppedDTS.SetToInvalid(); LastPoppedPTS.SetToInvalid(); PlayableDurationPushedSinceEOT.SetToZero(); bEndOfData = false; bEndOfTrack = false; bLastPushWasBlocked = false; bPopAsDummyUntilSyncFrame = true; } void FMultiTrackAccessUnitBuffer::Flush() { FScopeLock lock(&AccessLock); Clear(); } void FMultiTrackAccessUnitBuffer::SelectTrackWhenAvailable(uint32 PlaybackSequenceID, TSharedPtrTS InBufferSourceInfo) { FScopeLock lock(&AccessLock); if (InBufferSourceInfo.IsValid()) { PendingBufferSwitch.BufferInfo = MakeSharedTS(*InBufferSourceInfo); PendingBufferSwitch.BufferInfo->PlaybackSequenceID = PlaybackSequenceID; } else { PendingBufferSwitch.Reset(); } } void FMultiTrackAccessUnitBuffer::ActivateInitialBuffer() { // Note: The access mutex must have been locked already! if (!ActiveBuffer.IsValid() && PendingBufferSwitch.IsSet()) { TSharedPtrTS BufferSourceInfo; TSharedPtrTS Buffer = FindOrCreateBufferFor(BufferSourceInfo, PendingBufferSwitch.BufferInfo, false); if (Buffer.IsValid()) { ActiveBuffer = MoveTemp(Buffer); ActiveOutputBufferInfo = MoveTemp(BufferSourceInfo); PendingBufferSwitch.BufferInfo.Reset(); } } } void FMultiTrackAccessUnitBuffer::HandlePendingSwitch() { // Note: The access mutex must have been locked already! if (PendingBufferSwitch.IsSet()) { TSharedPtrTS BufferSourceInfo; TSharedPtrTS Buffer = FindOrCreateBufferFor(BufferSourceInfo, PendingBufferSwitch.BufferInfo, false); // Switching to the same buffer we are already on means we're already done. if (ActiveBuffer.IsValid() && ActiveBuffer == Buffer) { PendingBufferSwitch.BufferInfo.Reset(); return; } if (Buffer.IsValid() && LastPoppedDTS.IsValid()) { FTimeValue dd, dp; Buffer->DiscardUntil(LastPoppedDTS, FTimeValue(), dd, dp); // We switch on keyframes only. If there is none we keep the current buffer even if this may mean that // we run out of data altogether. FAccessUnit* NextAU = nullptr; if (Buffer->PeekAndAddRef(NextAU)) { if (NextAU->bIsSyncSample) { ActiveBuffer = MoveTemp(Buffer); ActiveOutputBufferInfo = MoveTemp(BufferSourceInfo); PendingBufferSwitch.BufferInfo.Reset(); } } } } RemoveOutdatedBuffers(); } void FMultiTrackAccessUnitBuffer::RemoveOutdatedBuffers() { if (ActiveOutputBufferInfo.IsValid()) { uint32 PlaybackSequenceID = ActiveOutputBufferInfo->PlaybackSequenceID; for(int32 i=0; iPlaybackSequenceID < PlaybackSequenceID) { BufferList.RemoveAt(i); --i; } } } } FTimeValue FMultiTrackAccessUnitBuffer::GetLastPoppedPTS() { FScopeLock lock(&AccessLock); return LastPoppedPTS; } FTimeValue FMultiTrackAccessUnitBuffer::GetLastPoppedDTS() { FScopeLock lock(&AccessLock); return LastPoppedDTS; } FTimeValue FMultiTrackAccessUnitBuffer::GetPlayableDurationPushedSinceEOT() { FScopeLock lock(&AccessLock); return PlayableDurationPushedSinceEOT; } TSharedPtrTS FMultiTrackAccessUnitBuffer::GetSelectedTrackBuffer() { FScopeLock lock(&AccessLock); return ActiveBuffer.IsValid() ? ActiveBuffer : EmptyBuffer; } void FMultiTrackAccessUnitBuffer::GetStats(FAccessUnitBufferInfo& OutStats) { FScopeLock lock(&AccessLock); ActivateInitialBuffer(); TSharedPtrTS Buf = GetSelectedTrackBuffer(); Buf->GetStats(OutStats); if (Buf == EmptyBuffer) { OutStats.bEndOfData = bEndOfData; OutStats.bEndOfTrack = bEndOfTrack; } } bool FMultiTrackAccessUnitBuffer::PeekAndAddRef(FAccessUnit*& OutAU) { FScopedLock lock(AsShared()); ActivateInitialBuffer(); HandlePendingSwitch(); TSharedPtrTS Buf = FMultiTrackAccessUnitBuffer::GetSelectedTrackBuffer(); if (Buf->Num()) { return Buf->PeekAndAddRef(OutAU); } else { OutAU = nullptr; return false; } } bool FMultiTrackAccessUnitBuffer::Pop(FAccessUnit*& OutAU) { // Note: We assume the access lock is held by the caller! ActivateInitialBuffer(); HandlePendingSwitch(); TSharedPtrTS Buf = FMultiTrackAccessUnitBuffer::GetSelectedTrackBuffer(); if (Buf.IsValid() && Buf->Num()) { bool bDidPop = Buf->Pop(OutAU); if (bDidPop && OutAU) { // Did we just pop from a different buffer than last time? if (LastPoppedBufferInfo != ActiveOutputBufferInfo) { if (LastPoppedBufferInfo) { OutAU->bTrackChangeDiscontinuity = true; } // Remember from which buffer we popped. LastPoppedBufferInfo = ActiveOutputBufferInfo; } // Do we need to wait for a sync frame and tag everything else as a dummy until then? if (bPopAsDummyUntilSyncFrame) { bPopAsDummyUntilSyncFrame = !OutAU->bIsSyncSample; OutAU->bIsDummyData = !OutAU->bIsSyncSample || OutAU->bIsDummyData; OutAU->bTrackChangeDiscontinuity = OutAU->bIsSyncSample || OutAU->bTrackChangeDiscontinuity; } LastPoppedDTS = OutAU->DTS; LastPoppedPTS = OutAU->PTS; // With this AU being popped off now we will also pop off all AUs that are now obsolete in the other tracks. for(auto& It : BufferList) { if (It.Buffer != Buf) { FTimeValue PoppedDTS, PoppedPTS; It.Buffer->DiscardUntil(LastPoppedDTS, LastPoppedPTS, PoppedDTS, PoppedPTS); } } } return bDidPop; } else { OutAU = nullptr; return false; } } void FMultiTrackAccessUnitBuffer::PopDiscardUntil(FTimeValue UntilTime) { // Note: We assume the access lock is held by the caller! for(auto& It : BufferList) { FTimeValue PoppedDTS, PoppedPTS; It.Buffer->DiscardUntil(UntilTime, UntilTime, PoppedDTS, PoppedPTS); if (PoppedDTS.IsValid() && LastPoppedDTS.IsValid() && PoppedDTS > LastPoppedDTS) { LastPoppedDTS = PoppedDTS; } if (PoppedPTS.IsValid() && LastPoppedPTS.IsValid() && PoppedPTS > LastPoppedPTS) { LastPoppedPTS = PoppedPTS; } } // Discarding from the buffer means that the next popping of an access unit needs to return // a sync sample. Anything that is not will instead be returned as a dummy sample. bPopAsDummyUntilSyncFrame = true; } bool FMultiTrackAccessUnitBuffer::IsEODFlagSet() { // Note: We assume the access lock is held by the caller! TSharedPtrTS Buf = GetSelectedTrackBuffer(); return Buf == EmptyBuffer ? bEndOfData : Buf->IsEODFlagSet(); } bool FMultiTrackAccessUnitBuffer::IsEndOfTrack() { // Note: We assume the access lock is held by the caller! TSharedPtrTS Buf = GetSelectedTrackBuffer(); return Buf == EmptyBuffer ? bEndOfTrack : Buf->IsEndOfTrack(); } int32 FMultiTrackAccessUnitBuffer::Num() { // Note: We assume the access lock is held by the caller! ActivateInitialBuffer(); return GetSelectedTrackBuffer()->Num(); } bool FMultiTrackAccessUnitBuffer::WasLastPushBlocked() { return bLastPushWasBlocked; } bool FMultiTrackAccessUnitBuffer::HasPendingTrackSwitch() { FScopeLock lock(&AccessLock); return PendingBufferSwitch.IsSet(); } } // namespace Electra