Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Source/WebCore/platform/graphics/TrackBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,18 @@ void TrackBuffer::addSample(MediaSample& sample)
if (sample.presentationTime() < previousSample->presentationTime())
m_hasOutOfOrderFrames = true;
}

// Track reorder depth in decode order. We can't publish a trustworthy
// minimum upcoming PTS until we've seen at least m_maxObservedReorderDepth
// + 1 samples past the head — a B-frame with lower PTS may still be on
// its way.
if (m_maxPresentationTimeSeenInDecodeOrder.isInvalid() || sample.presentationTime() > m_maxPresentationTimeSeenInDecodeOrder) {
m_maxPresentationTimeSeenInDecodeOrder = sample.presentationTime();
m_samplesSinceMaxPresentationTime = 0;
} else {
++m_samplesSinceMaxPresentationTime;
m_maxObservedReorderDepth = std::max(m_maxObservedReorderDepth, m_samplesSinceMaxPresentationTime);
}
}

// NOTE: the spec considers the need to check the last frame duration but doesn't specify if that last frame
Expand Down Expand Up @@ -183,6 +195,17 @@ void TrackBuffer::updateMinimumUpcomingPresentationTime()
m_minimumEnqueuedPresentationTime = MediaTime::invalidTime();
return;
}

// For streams with B-frames we can't trust the sliding-window minimum until
// we've seen enough samples past the head to cover the observed reorder
// depth — a later-arriving B-frame could have a lower PTS than what's
// currently in the queue, and publishing the too-high minimum to the
// renderer triggers UpcomingPTSExpectation warnings for every B-frame.
if (m_hasOutOfOrderFrames && m_decodeQueue.size() < m_maxObservedReorderDepth + 1) {
m_minimumEnqueuedPresentationTime = MediaTime::invalidTime();
return;
}

size_t forwardIndex = 0;
m_minimumEnqueuedPresentationTime = MediaTime::positiveInfiniteTime();
for (auto it = m_decodeQueue.begin(); it != m_decodeQueue.end() && forwardIndex < MaximumSlidingWindowLength; ++forwardIndex, ++it) {
Expand Down Expand Up @@ -529,6 +552,11 @@ void TrackBuffer::clearDecodeQueue()
m_minimumEnqueuedPresentationTime = MediaTime::invalidTime();
m_highestEnqueuedPresentationTime = MediaTime::invalidTime();
m_lastEnqueuedDecodeKey = { MediaTime::invalidTime(), MediaTime::invalidTime() };
// Reset the running reorder observation but keep m_maxObservedReorderDepth —
// the codec-declared / previously-seen reorder depth remains valid across a
// decode-queue flush (same stream, same codec config).
m_maxPresentationTimeSeenInDecodeOrder = MediaTime::invalidTime();
m_samplesSinceMaxPresentationTime = 0;
}

void TrackBuffer::setRoundedTimestampOffset(const MediaTime& time, uint32_t timeScale, const MediaTime& roundingMargin)
Expand Down
13 changes: 13 additions & 0 deletions Source/WebCore/platform/graphics/TrackBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ class TrackBuffer final
void setHighestEnqueuedPresentationTime(MediaTime timestamp) { m_highestEnqueuedPresentationTime = WTF::move(timestamp); }
const MediaTime& minimumEnqueuedPresentationTime() const LIFETIME_BOUND { return m_minimumEnqueuedPresentationTime; }

// Raises the tracked reorder depth. Call once per init segment with the
// codec-declared max_num_reorder_frames / sps_max_num_reorder_pics when
// available. The running observation in addSample() can only grow it further.
void setInitialReorderDepth(size_t depth) { m_maxObservedReorderDepth = std::max(m_maxObservedReorderDepth, depth); }

const DecodeOrderSampleMap::KeyType& lastEnqueuedDecodeKey() const LIFETIME_BOUND { return m_lastEnqueuedDecodeKey; }
void setLastEnqueuedDecodeKey(DecodeOrderSampleMap::KeyType key) { m_lastEnqueuedDecodeKey = WTF::move(key); }

Expand Down Expand Up @@ -138,6 +143,14 @@ class TrackBuffer final
MediaTime m_highestEnqueuedPresentationTime { MediaTime::invalidTime() };
MediaTime m_minimumEnqueuedPresentationTime { MediaTime::invalidTime() };

// Running observation of decode-order reorder depth. Seeded by
// setInitialReorderDepth; grown when a deeper reorder is seen. Gates
// publication of m_minimumEnqueuedPresentationTime so a yet-to-arrive
// B-frame can't invalidate the value handed to the renderer.
MediaTime m_maxPresentationTimeSeenInDecodeOrder { MediaTime::invalidTime() };
size_t m_samplesSinceMaxPresentationTime { 0 };
size_t m_maxObservedReorderDepth { 3 };

DecodeOrderSampleMap::KeyType m_lastEnqueuedDecodeKey { MediaTime::invalidTime(), MediaTime::invalidTime() };

MediaTime m_enqueueDiscontinuityBoundary;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ @interface AVSampleBufferAudioRenderer (WebCoreSampleBufferKeySession) <AVConten
}
m_keyframeNeeded = false;
if (RefPtr videoRenderer = m_videoRenderer; videoRenderer && isEnabledVideoTrackId(trackId)) {
videoRenderer->enqueueSample(sample, minimumUpcomingTime.value_or(sample->presentationTime()));
videoRenderer->enqueueSample(sample, minimumUpcomingTime);
if (!m_hasEverSubmittedVideoSample) {
m_hasEverSubmittedVideoSample = true;
m_previousRendererConfiguration.isRenderingCompressedVideo = !isUsingDecompressionSession();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class VideoMediaSampleRenderer final

bool isReadyForMoreMediaData() const;
void requestMediaDataWhenReady(Function<void()>&&);
void enqueueSample(const MediaSample&, const MediaTime&);
void enqueueSample(const MediaSample&, std::optional<MediaTime> minimumUpcomingTime);
void stopRequestingMediaData();

void notifyFirstFrameAvailable(Function<void(const MediaTime&, double)>&&);
Expand Down Expand Up @@ -209,7 +209,7 @@ class VideoMediaSampleRenderer final
TimebaseAndTimerSource m_timebaseAndTimerSource WTF_GUARDED_BY_LOCK(m_lock);
RefPtr<EffectiveRateChangedListener> m_effectiveRateChangedListener;
std::atomic<FlushId> m_flushId { 0 };
Deque<std::tuple<Ref<const MediaSample>, MediaTime, FlushId, bool>> m_compressedSampleQueue WTF_GUARDED_BY_CAPABILITY(dispatcher().get());
Deque<std::tuple<Ref<const MediaSample>, std::optional<MediaTime>, FlushId, bool>> m_compressedSampleQueue WTF_GUARDED_BY_CAPABILITY(dispatcher().get());
std::atomic<uint32_t> m_compressedSamplesCount { 0 };
std::atomic<uint32_t> m_pendingSamplesCount { 0 };
MediaSampleReorderQueue m_decodedSampleQueue WTF_GUARDED_BY_CAPABILITY(dispatcher().get());
Expand Down
32 changes: 18 additions & 14 deletions Source/WebCore/platform/graphics/cocoa/VideoMediaSampleRenderer.mm
Original file line number Diff line number Diff line change
Expand Up @@ -417,10 +417,10 @@ static bool isRendererThreadSafe(WebSampleBufferVideoRendering *renderering)
return MediaTime::invalidTime();
}

void VideoMediaSampleRenderer::enqueueSample(const MediaSample& sample, const MediaTime& minimumUpcomingTime)
void VideoMediaSampleRenderer::enqueueSample(const MediaSample& sample, std::optional<MediaTime> minimumUpcomingTime)
{
assertIsMainThread();
DEBUG_LOG(LOGIDENTIFIER, "sample: ", sample, " minimumUpcomingTime: ", minimumUpcomingTime);
DEBUG_LOG(LOGIDENTIFIER, "sample: ", sample, " minimumUpcomingTime: ", minimumUpcomingTime.value_or(MediaTime::invalidTime()));

ASSERT(sample.type() == MediaSample::Type::CMSampleBuffer);
if (sample.type() != MediaSample::Type::CMSampleBuffer)
Expand Down Expand Up @@ -501,27 +501,31 @@ static bool isRendererThreadSafe(WebSampleBufferVideoRendering *renderering)
if (currentTime.isValid() && !m_wasProtected && !m_decompressionSessionWasBlocked) {
auto endTime = lastDecodedSampleTime();
if (endTime.isValid() && endTime > highWaterMarkTime) {
auto [sample, upcomingMinimum, flushId, blocked] = m_compressedSampleQueue.first();
upcomingMinimum = std::min(sample->presentationTime(), upcomingMinimum.isValid() ? upcomingMinimum : MediaTime::positiveInfiniteTime());

if (endTime < upcomingMinimum) {
if (m_lastMinimumUpcomingPresentationTime.isInvalid() || upcomingMinimum != m_lastMinimumUpcomingPresentationTime) {
ASSERT(m_lastMinimumUpcomingPresentationTime.isInvalid() || m_lastMinimumUpcomingPresentationTime < upcomingMinimum);
m_lastMinimumUpcomingPresentationTime = upcomingMinimum;
LogPerformance("VideoMediaSampleRenderer::decodeNextSampleIfNeeded currentTime:%0.2f expectMinimumUpcomingSampleBufferPresentationTime:%0.2f decoded queued:%zu upcoming:%zu high watermark reached", currentTime.toDouble(), m_lastMinimumUpcomingPresentationTime.toDouble(), decodedSamplesCount(), compressedSamplesCount());
[rendererOrDisplayLayer() expectMinimumUpcomingSampleBufferPresentationTime:PAL::toCMTime(m_lastMinimumUpcomingPresentationTime)];
const auto& [sample, upcomingMinimumOpt, flushId, blocked] = m_compressedSampleQueue.first();
if (upcomingMinimumOpt) {
auto upcomingMinimum = std::min(sample->presentationTime(), *upcomingMinimumOpt);
if (endTime < upcomingMinimum) {
if (m_lastMinimumUpcomingPresentationTime.isInvalid() || upcomingMinimum != m_lastMinimumUpcomingPresentationTime) {
ASSERT(m_lastMinimumUpcomingPresentationTime.isInvalid() || m_lastMinimumUpcomingPresentationTime < upcomingMinimum);
m_lastMinimumUpcomingPresentationTime = upcomingMinimum;
LogPerformance("VideoMediaSampleRenderer::decodeNextSampleIfNeeded currentTime:%0.2f expectMinimumUpcomingSampleBufferPresentationTime:%0.2f decoded queued:%zu upcoming:%zu high watermark reached", currentTime.toDouble(), m_lastMinimumUpcomingPresentationTime.toDouble(), decodedSamplesCount(), compressedSamplesCount());
[rendererOrDisplayLayer() expectMinimumUpcomingSampleBufferPresentationTime:PAL::toCMTime(m_lastMinimumUpcomingPresentationTime)];
}
return;
}
return;
DEBUG_LOG(LOGIDENTIFIER, "Out of order frames detected, forcing extra decode");
}
DEBUG_LOG(LOGIDENTIFIER, "Out of order frames detected, forcing extra decode");
// Without a caller lookahead we can't safely publish a floor — a
// later-arriving B-frame could have a lower PTS and would be
// rejected by the renderer. Fall through and keep decoding.
}
if (endTime.isValid() && endTime >= lowWaterMarkTime && playbackRate > 0.9 && playbackRate < 1.1) {
LogPerformance("VideoMediaSampleRenderer::decodeNextSampleIfNeeded expectMinimumUpcomingSampleBufferPresentationTime:%0.2f decoded queued:%zu upcoming:%zu currentTime:%0.2f endTime:%0.2f low:%0.2f high:%0.2f low watermark reached", m_lastMinimumUpcomingPresentationTime.toDouble(), decodedSamplesCount(), compressedSamplesCount(), currentTime.toDouble(), endTime.toDouble(), lowWaterMarkTime.toDouble(), highWaterMarkTime.toDouble());
decodingFlags.add(WebCoreDecompressionSession::DecodingFlag::RealTime);
}
}

auto [sample, upcomingMinimum, flushId, blocked] = m_compressedSampleQueue.takeFirst();
auto [sample, upcomingMinimumOpt, flushId, blocked] = m_compressedSampleQueue.takeFirst();
m_compressedSamplesCount = m_compressedSampleQueue.size();
maybeBecomeReadyForMoreMediaData();

Expand Down