Title: [172657] trunk/Source/WebCore
Revision
172657
Author
[email protected]
Date
2014-08-15 16:09:19 -0700 (Fri, 15 Aug 2014)

Log Message

[MSE] Implement a maximum buffer size for SourceBuffer
https://bugs.webkit.org/show_bug.cgi?id=135614

Reviewed by Jer Noble.

Implement the MSE coded frame eviction algorithm: when new buffers are appended attempt
to keep the amount of data buffered below a maximum size (which is determined by the media
session manager) by freeing coded frames as follows:
    1 - Free frames in 30 second chunks from 0 to 30 seconds before the current time.
    2 - If there are time ranges after the range with the current time, free frames in
        30 second chunks from duration back to the beginning of the time range after
        current time.

For now we DO NOT throw a QUOTA_EXCEEDED_ERR when we are not able to free up enough
space to remain below the prescribed quota, because some big name, widely deployed, 
code bases ignore the error and continue appending data as though the failed append
succeeded, leading to a corrupted bitstream and failure to play.

* Modules/mediasource/SampleMap.cpp:
(WebCore::SampleMap::addSample): Drive-by performance optimization: sample->presentationTime()
    is used more than once, stash it in a local variable.
(WebCore::SampleMap::removeSample): Ditto.

* Modules/mediasource/SourceBuffer.cpp:
(WebCore::logRanges): Debug-only function to log TimeRanges.
(WebCore::SourceBuffer::SourceBuffer):
(WebCore::SourceBuffer::remove): Optimize logging. Buffer full and coded frame eviction logic
    is in SourceBuffer instead of SourceBufferPrivate.
(WebCore::SourceBuffer::appendBufferInternal): Ditto.
(WebCore::SourceBuffer::sourceBufferPrivateAppendComplete): Set m_bufferFull when more data
    has been buffered than allowed.
(WebCore::removeSamplesFromTrackBuffer): Add logging.
(WebCore::SourceBuffer::removeCodedFrames): Improve logging. Avoid debug build assert when
    current time is after last enqueued presentation time.
(WebCore::SourceBuffer::evictCodedFrames): The coded frame eviction algorithm.
(WebCore::SourceBuffer::maximumBufferSize): Return the maximum amount of data that can 
    be buffered.
(WebCore::SourceBuffer::sourceBufferPrivateDidReceiveSample): Improve logging. Don't attempt
    to create a PlatformTimeRanges with inverted start and end to avoid an assert in debug.
(WebCore::SourceBuffer::hasAudio): New, return true if there are any audio tracks.
(WebCore::SourceBuffer::hasVideo): New, return true if there are any video tracks.
(WebCore::SourceBuffer::sourceBufferPrivateHasAudio): Call hasAudio.
(WebCore::SourceBuffer::sourceBufferPrivateHasVideo): Call hasVideo.
(WebCore::SourceBuffer::hasCurrentTime): Return true if currentTime is greater than duration.
(WebCore::SourceBuffer::hasFutureTime): Ditto.
(WebCore::SourceBuffer::extraMemoryCost): Return the amount of data buffered: the size of
    the input buffer plus the size of all track samples.
(WebCore::SourceBuffer::reportExtraMemoryCost): Move buffered size calculation to extraMemoryCost.
(WebCore::SourceBuffer::document): Document accessor.
* Modules/mediasource/SourceBuffer.h: Drive-by size optimization by moving all bool member
    variables to the end of class.

* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::refreshCachedTime): Drive-by removal of overly chatty logging.
(WebCore::HTMLMediaElement::maximumSourceBufferSize): New, maximum source buffer size.
* html/HTMLMediaElement.h:

* html/HTMLMediaSession.cpp:
(WebCore::HTMLMediaSession::maximumMediaSourceBufferSize): New, get maximum source buffer 
    size from settings, return full amount for an SourceBuffer with video and audio, return 5%
    of the maximum for an audio-only SourceBuffer.
* html/HTMLMediaSession.h:

* page/Settings.in: Add maximumSourceBufferSize. Default value is enough for approximately
    five minutes of 1080p video and stereo audio.

* platform/graphics/PlatformTimeRanges.cpp:
(WebCore::PlatformTimeRanges::totalDuration): Drive-by optimization.
(WebCore::PlatformTimeRanges::dump): New, allow a PlatformTimeRanges to be printed.
* platform/graphics/PlatformTimeRanges.h:

* platform/graphics/SourceBufferPrivate.h: Delete evictCodedFrames and isFull, that logic
    is not in SourceBuffer.
* platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.h:
* platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm:
(WebCore::SourceBufferPrivateAVFObjC::processCodedFrame): Drive-by logging fix.
(WebCore::SourceBufferPrivateAVFObjC::evictCodedFrames): Deleted.
(WebCore::SourceBufferPrivateAVFObjC::isFull): Deleted.

* platform/mock/mediasource/MockSourceBufferPrivate.cpp:
(WebCore::MockSourceBufferPrivate::evictCodedFrames): Deleted.
(WebCore::MockSourceBufferPrivate::isFull): Deleted.
* platform/mock/mediasource/MockSourceBufferPrivate.h:

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (172656 => 172657)


--- trunk/Source/WebCore/ChangeLog	2014-08-15 22:43:50 UTC (rev 172656)
+++ trunk/Source/WebCore/ChangeLog	2014-08-15 23:09:19 UTC (rev 172657)
@@ -1,3 +1,89 @@
+2014-08-15  Eric Carlson  <[email protected]>
+
+        [MSE] Implement a maximum buffer size for SourceBuffer
+        https://bugs.webkit.org/show_bug.cgi?id=135614
+
+        Reviewed by Jer Noble.
+
+        Implement the MSE coded frame eviction algorithm: when new buffers are appended attempt
+        to keep the amount of data buffered below a maximum size (which is determined by the media
+        session manager) by freeing coded frames as follows:
+            1 - Free frames in 30 second chunks from 0 to 30 seconds before the current time.
+            2 - If there are time ranges after the range with the current time, free frames in
+                30 second chunks from duration back to the beginning of the time range after
+                current time.
+
+        For now we DO NOT throw a QUOTA_EXCEEDED_ERR when we are not able to free up enough
+        space to remain below the prescribed quota, because some big name, widely deployed, 
+        code bases ignore the error and continue appending data as though the failed append
+        succeeded, leading to a corrupted bitstream and failure to play.
+
+        * Modules/mediasource/SampleMap.cpp:
+        (WebCore::SampleMap::addSample): Drive-by performance optimization: sample->presentationTime()
+            is used more than once, stash it in a local variable.
+        (WebCore::SampleMap::removeSample): Ditto.
+
+        * Modules/mediasource/SourceBuffer.cpp:
+        (WebCore::logRanges): Debug-only function to log TimeRanges.
+        (WebCore::SourceBuffer::SourceBuffer):
+        (WebCore::SourceBuffer::remove): Optimize logging. Buffer full and coded frame eviction logic
+            is in SourceBuffer instead of SourceBufferPrivate.
+        (WebCore::SourceBuffer::appendBufferInternal): Ditto.
+        (WebCore::SourceBuffer::sourceBufferPrivateAppendComplete): Set m_bufferFull when more data
+            has been buffered than allowed.
+        (WebCore::removeSamplesFromTrackBuffer): Add logging.
+        (WebCore::SourceBuffer::removeCodedFrames): Improve logging. Avoid debug build assert when
+            current time is after last enqueued presentation time.
+        (WebCore::SourceBuffer::evictCodedFrames): The coded frame eviction algorithm.
+        (WebCore::SourceBuffer::maximumBufferSize): Return the maximum amount of data that can 
+            be buffered.
+        (WebCore::SourceBuffer::sourceBufferPrivateDidReceiveSample): Improve logging. Don't attempt
+            to create a PlatformTimeRanges with inverted start and end to avoid an assert in debug.
+        (WebCore::SourceBuffer::hasAudio): New, return true if there are any audio tracks.
+        (WebCore::SourceBuffer::hasVideo): New, return true if there are any video tracks.
+        (WebCore::SourceBuffer::sourceBufferPrivateHasAudio): Call hasAudio.
+        (WebCore::SourceBuffer::sourceBufferPrivateHasVideo): Call hasVideo.
+        (WebCore::SourceBuffer::hasCurrentTime): Return true if currentTime is greater than duration.
+        (WebCore::SourceBuffer::hasFutureTime): Ditto.
+        (WebCore::SourceBuffer::extraMemoryCost): Return the amount of data buffered: the size of
+            the input buffer plus the size of all track samples.
+        (WebCore::SourceBuffer::reportExtraMemoryCost): Move buffered size calculation to extraMemoryCost.
+        (WebCore::SourceBuffer::document): Document accessor.
+        * Modules/mediasource/SourceBuffer.h: Drive-by size optimization by moving all bool member
+            variables to the end of class.
+
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::refreshCachedTime): Drive-by removal of overly chatty logging.
+        (WebCore::HTMLMediaElement::maximumSourceBufferSize): New, maximum source buffer size.
+        * html/HTMLMediaElement.h:
+
+        * html/HTMLMediaSession.cpp:
+        (WebCore::HTMLMediaSession::maximumMediaSourceBufferSize): New, get maximum source buffer 
+            size from settings, return full amount for an SourceBuffer with video and audio, return 5%
+            of the maximum for an audio-only SourceBuffer.
+        * html/HTMLMediaSession.h:
+
+        * page/Settings.in: Add maximumSourceBufferSize. Default value is enough for approximately
+            five minutes of 1080p video and stereo audio.
+
+        * platform/graphics/PlatformTimeRanges.cpp:
+        (WebCore::PlatformTimeRanges::totalDuration): Drive-by optimization.
+        (WebCore::PlatformTimeRanges::dump): New, allow a PlatformTimeRanges to be printed.
+        * platform/graphics/PlatformTimeRanges.h:
+
+        * platform/graphics/SourceBufferPrivate.h: Delete evictCodedFrames and isFull, that logic
+            is not in SourceBuffer.
+        * platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.h:
+        * platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm:
+        (WebCore::SourceBufferPrivateAVFObjC::processCodedFrame): Drive-by logging fix.
+        (WebCore::SourceBufferPrivateAVFObjC::evictCodedFrames): Deleted.
+        (WebCore::SourceBufferPrivateAVFObjC::isFull): Deleted.
+
+        * platform/mock/mediasource/MockSourceBufferPrivate.cpp:
+        (WebCore::MockSourceBufferPrivate::evictCodedFrames): Deleted.
+        (WebCore::MockSourceBufferPrivate::isFull): Deleted.
+        * platform/mock/mediasource/MockSourceBufferPrivate.h:
+
 2014-08-15  Zalan Bujtas  <[email protected]>
 
         REGRESSION: Parts of the route/route options windows are invisible at maps.google.com

Modified: trunk/Source/WebCore/Modules/mediasource/SampleMap.cpp (172656 => 172657)


--- trunk/Source/WebCore/Modules/mediasource/SampleMap.cpp	2014-08-15 22:43:50 UTC (rev 172656)
+++ trunk/Source/WebCore/Modules/mediasource/SampleMap.cpp	2014-08-15 23:09:19 UTC (rev 172657)
@@ -113,9 +113,11 @@
     RefPtr<MediaSample> sample = prpSample;
     ASSERT(sample);
 
-    presentationOrder().m_samples.insert(PresentationOrderSampleMap::MapType::value_type(sample->presentationTime(), sample));
+    MediaTime presentationTime = sample->presentationTime();
 
-    auto decodeKey = DecodeOrderSampleMap::KeyType(sample->decodeTime(), sample->presentationTime());
+    presentationOrder().m_samples.insert(PresentationOrderSampleMap::MapType::value_type(presentationTime, sample));
+
+    auto decodeKey = DecodeOrderSampleMap::KeyType(sample->decodeTime(), presentationTime);
     decodeOrder().m_samples.insert(DecodeOrderSampleMap::MapType::value_type(decodeKey, sample));
 
     m_totalSize += sample->sizeInBytes();
@@ -124,9 +126,11 @@
 void SampleMap::removeSample(MediaSample* sample)
 {
     ASSERT(sample);
-    presentationOrder().m_samples.erase(sample->presentationTime());
+    MediaTime presentationTime = sample->presentationTime();
 
-    auto decodeKey = DecodeOrderSampleMap::KeyType(sample->decodeTime(), sample->presentationTime());
+    presentationOrder().m_samples.erase(presentationTime);
+
+    auto decodeKey = DecodeOrderSampleMap::KeyType(sample->decodeTime(), presentationTime);
     decodeOrder().m_samples.erase(decodeKey);
 
     m_totalSize -= sample->sizeInBytes();

Modified: trunk/Source/WebCore/Modules/mediasource/SourceBuffer.cpp (172656 => 172657)


--- trunk/Source/WebCore/Modules/mediasource/SourceBuffer.cpp	2014-08-15 22:43:50 UTC (rev 172656)
+++ trunk/Source/WebCore/Modules/mediasource/SourceBuffer.cpp	2014-08-15 23:09:19 UTC (rev 172657)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2013 Google Inc. All rights reserved.
+ * Copyright (C) 2013-2014 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -48,12 +49,16 @@
 #include "TextTrackList.h"
 #include "TimeRanges.h"
 #include "VideoTrackList.h"
+#include <limits>
 #include <map>
 #include <runtime/JSCInlines.h>
 #include <runtime/JSLock.h>
 #include <runtime/VM.h>
 #include <wtf/CurrentTime.h>
 #include <wtf/NeverDestroyed.h>
+#if !LOG_DISABLED
+#include <wtf/text/StringBuilder.h>
+#endif
 
 namespace WebCore {
 
@@ -104,12 +109,9 @@
     , m_private(WTF::move(sourceBufferPrivate))
     , m_source(source)
     , m_asyncEventQueue(*this)
-    , m_updating(false)
     , m_appendBufferTimer(this, &SourceBuffer::appendBufferTimerFired)
     , m_highestPresentationEndTimestamp(MediaTime::invalidTime())
-    , m_receivedFirstInitializationSegment(false)
     , m_buffered(TimeRanges::create())
-    , m_active(false)
     , m_appendState(WaitingForSegment)
     , m_timeOfBufferingMonitor(monotonicallyIncreasingTime())
     , m_bufferedSinceLastMonitor(0)
@@ -118,6 +120,10 @@
     , m_pendingRemoveStart(MediaTime::invalidTime())
     , m_pendingRemoveEnd(MediaTime::invalidTime())
     , m_removeTimer(this, &SourceBuffer::removeTimerFired)
+    , m_updating(false)
+    , m_receivedFirstInitializationSegment(false)
+    , m_active(false)
+    , m_bufferFull(false)
 {
     ASSERT(m_source);
 
@@ -235,7 +241,8 @@
 
 void SourceBuffer::remove(double start, double end, ExceptionCode& ec)
 {
-    LOG(MediaSource, "SourceBuffer::remove(%p) - start(%s), end(%s)", this, toString(start).utf8().data(), toString(end).utf8().data());
+    LOG(MediaSource, "SourceBuffer::remove(%p) - start(%lf), end(%lf)", this, start, end);
+
     // Section 3.2 remove() method steps.
     // 1. If start is negative or greater than duration, then throw an InvalidAccessError exception and abort these steps.
     // 2. If end is less than or equal to start, then throw an InvalidAccessError exception and abort these steps.
@@ -407,13 +414,17 @@
     m_source->openIfInEndedState();
 
     // 4. Run the coded frame eviction algorithm.
-    m_private->evictCodedFrames();
+    evictCodedFrames(size);
 
+    // FIXME: enable this code when MSE libraries have been updated to support it.
+#if 0
     // 5. If the buffer full flag equals true, then throw a QUOTA_EXCEEDED_ERR exception and abort these step.
-    if (m_private->isFull()) {
+    if (m_bufferFull) {
+        LOG(MediaSource, "SourceBuffer::appendBufferInternal(%p) -  buffer full, failing with QUOTA_EXCEEDED_ERR error", this);
         ec = QUOTA_EXCEEDED_ERR;
         return;
     }
+#endif
 
     // NOTE: Return to 3.2 appendBuffer()
     // 3. Add data to the end of the input buffer.
@@ -514,6 +525,10 @@
     }
 
     reportExtraMemoryCost();
+    if (extraMemoryCost() > this->maximumBufferSize())
+        m_bufferFull = true;
+
+    LOG(Media, "SourceBuffer::sourceBufferPrivateAppendComplete(%p) - buffered = %s", this, toString(m_buffered->ranges()).utf8().data());
 }
 
 void SourceBuffer::sourceBufferPrivateDidReceiveRenderingError(SourceBufferPrivate*, int)
@@ -527,13 +542,27 @@
     return a.second->decodeTime() < b.second->decodeTime();
 }
 
-static PassRefPtr<TimeRanges> removeSamplesFromTrackBuffer(const DecodeOrderSampleMap::MapType& samples, SourceBuffer::TrackBuffer& trackBuffer)
+static PassRefPtr<TimeRanges> removeSamplesFromTrackBuffer(const DecodeOrderSampleMap::MapType& samples, SourceBuffer::TrackBuffer& trackBuffer, const SourceBuffer* buffer, const char* logPrefix)
 {
+#if !LOG_DISABLED
+    double earliestSample = std::numeric_limits<double>::infinity();
+    double latestSample = 0;
+    size_t bytesRemoved = 0;
+#else
+    UNUSED_PARAM(logPrefix);
+    UNUSED_PARAM(buffer);
+#endif
+
     RefPtr<TimeRanges> erasedRanges = TimeRanges::create();
     MediaTime microsecond(1, 1000000);
     for (auto sampleIt : samples) {
         const DecodeOrderSampleMap::KeyType& decodeKey = sampleIt.first;
+#if !LOG_DISABLED
+        size_t startBufferSize = trackBuffer.samples.sizeInBytes();
+#endif
+
         RefPtr<MediaSample>& sample = sampleIt.second;
+        LOG(MediaSource, "SourceBuffer::%s(%p) - removing sample(%s)", logPrefix, buffer, toString(*sampleIt.second).utf8().data());
 
         // Remove the erased samples from the TrackBuffer sample map.
         trackBuffer.samples.removeSample(sample.get());
@@ -544,7 +573,21 @@
         double startTime = sample->presentationTime().toDouble();
         double endTime = startTime + (sample->duration() + microsecond).toDouble();
         erasedRanges->add(startTime, endTime);
+
+#if !LOG_DISABLED
+        bytesRemoved += startBufferSize - trackBuffer.samples.sizeInBytes();
+        if (startTime < earliestSample)
+            earliestSample = startTime;
+        if (endTime > latestSample)
+            latestSample = endTime;
+#endif
     }
+
+#if !LOG_DISABLED
+    if (bytesRemoved)
+        LOG(MediaSource, "SourceBuffer::%s(%p) removed %zu bytes, start(%lf), end(%lf)", logPrefix, buffer, bytesRemoved, earliestSample, latestSample);
+#endif
+
     return erasedRanges.release();
 }
 
@@ -590,14 +633,16 @@
         DecodeOrderSampleMap::iterator removeDecodeStart = trackBuffer.samples.decodeOrder().findSampleWithDecodeKey(decodeKey);
 
         DecodeOrderSampleMap::MapType erasedSamples(removeDecodeStart, removeDecodeEnd);
-        RefPtr<TimeRanges> erasedRanges = removeSamplesFromTrackBuffer(erasedSamples, trackBuffer);
+        RefPtr<TimeRanges> erasedRanges = removeSamplesFromTrackBuffer(erasedSamples, trackBuffer, this, "removeCodedFrames");
 
         // Only force the TrackBuffer to re-enqueue if the removed ranges overlap with enqueued and possibly
         // not yet displayed samples.
-        PlatformTimeRanges possiblyEnqueuedRanges(currentMediaTime, trackBuffer.lastEnqueuedPresentationTime);
-        possiblyEnqueuedRanges.intersectWith(erasedRanges->ranges());
-        if (possiblyEnqueuedRanges.length())
-            trackBuffer.needsReenqueueing = true;
+        if (currentMediaTime < trackBuffer.lastEnqueuedPresentationTime) {
+            PlatformTimeRanges possiblyEnqueuedRanges(currentMediaTime, trackBuffer.lastEnqueuedPresentationTime);
+            possiblyEnqueuedRanges.intersectWith(erasedRanges->ranges());
+            if (possiblyEnqueuedRanges.length())
+                trackBuffer.needsReenqueueing = true;
+        }
 
         erasedRanges->invert();
         m_buffered->intersectWith(*erasedRanges);
@@ -611,6 +656,8 @@
 
     // 4. If buffer full flag equals true and this object is ready to accept more bytes, then set the buffer full flag to false.
     // No-op
+
+    LOG(Media, "SourceBuffer::removeCodedFrames(%p) - buffered = %s", this, toString(m_buffered->ranges()).utf8().data());
 }
 
 void SourceBuffer::removeTimerFired(Timer<SourceBuffer>*)
@@ -637,6 +684,109 @@
     scheduleEvent(eventNames().updateendEvent);
 }
 
+void SourceBuffer::evictCodedFrames(size_t newDataSize)
+{
+    // 3.5.13 Coded Frame Eviction Algorithm
+    // http://www.w3.org/TR/media-source/#sourcebuffer-coded-frame-eviction
+
+    if (isRemoved())
+        return;
+
+    // This algorithm is run to free up space in this source buffer when new data is appended.
+    // 1. Let new data equal the data that is about to be appended to this SourceBuffer.
+    // 2. If the buffer full flag equals false, then abort these steps.
+    if (!m_bufferFull)
+        return;
+
+    size_t maximumBufferSize = this->maximumBufferSize();
+
+    // 3. Let removal ranges equal a list of presentation time ranges that can be evicted from
+    // the presentation to make room for the new data.
+
+    // NOTE: begin by removing data from the beginning of the buffered ranges, 30 seconds at
+    // a time, up to 30 seconds before currentTime.
+    MediaTime thirtySeconds = MediaTime(30, 1);
+    MediaTime currentTime = MediaTime::createWithDouble(m_source->currentTime());
+    MediaTime maximumRangeEnd = currentTime - thirtySeconds;
+
+#if !LOG_DISABLED
+    LOG(MediaSource, "SourceBuffer::evictCodedFrames(%p) - currentTime = %lf, require %zu bytes, maximum buffer size is %zu", this, m_source->currentTime(), extraMemoryCost() + newDataSize, maximumBufferSize);
+    size_t initialBufferedSize = extraMemoryCost();
+#endif
+
+    MediaTime rangeStart = MediaTime::zeroTime();
+    MediaTime rangeEnd = rangeStart + thirtySeconds;
+    while (rangeStart < maximumRangeEnd) {
+        // 4. For each range in removal ranges, run the coded frame removal algorithm with start and
+        // end equal to the removal range start and end timestamp respectively.
+        removeCodedFrames(rangeStart, std::min(rangeEnd, maximumRangeEnd));
+        if (extraMemoryCost() + newDataSize < maximumBufferSize) {
+            m_bufferFull = false;
+            break;
+        }
+
+        rangeStart += thirtySeconds;
+        rangeEnd += thirtySeconds;
+    }
+
+    if (!m_bufferFull) {
+        LOG(MediaSource, "SourceBuffer::evictCodedFrames(%p) - evicted %zu bytes", this, initialBufferedSize - extraMemoryCost());
+        return;
+    }
+
+    // If there still isn't enough free space and there buffers in time ranges after the current range (ie. there is a gap after
+    // the current buffered range), delete 30 seconds at a time from duration back to the current time range or 30 seconds after
+    // currenTime whichever we hit first.
+    auto buffered = m_buffered->ranges();
+    size_t currentTimeRange = buffered.find(currentTime);
+    if (currentTimeRange == notFound || currentTimeRange == buffered.length() - 1) {
+        LOG(MediaSource, "SourceBuffer::evictCodedFrames(%p) - evicted %zu bytes but FAILED to free enough", this, initialBufferedSize - extraMemoryCost());
+        return;
+    }
+
+    MediaTime minimumRangeStart = currentTime + thirtySeconds;
+
+    rangeEnd = MediaTime::createWithDouble(m_source->duration());
+    rangeStart = rangeEnd - thirtySeconds;
+    while (rangeStart > minimumRangeStart) {
+
+        // Do not evict data from the time range that contains currentTime.
+        size_t startTimeRange = buffered.find(rangeStart);
+        if (startTimeRange == currentTimeRange) {
+            size_t endTimeRange = buffered.find(rangeEnd);
+            if (endTimeRange == currentTimeRange)
+                break;
+
+            rangeEnd = buffered.start(endTimeRange);
+        }
+
+        // 4. For each range in removal ranges, run the coded frame removal algorithm with start and
+        // end equal to the removal range start and end timestamp respectively.
+        removeCodedFrames(std::max(minimumRangeStart, rangeStart), rangeEnd);
+        if (extraMemoryCost() + newDataSize < maximumBufferSize) {
+            m_bufferFull = false;
+            break;
+        }
+
+        rangeStart -= thirtySeconds;
+        rangeEnd -= thirtySeconds;
+    }
+
+    LOG(MediaSource, "SourceBuffer::evictCodedFrames(%p) - evicted %zu bytes%s", this, initialBufferedSize - extraMemoryCost(), m_bufferFull ? "" : " but FAILED to free enough");
+}
+
+size_t SourceBuffer::maximumBufferSize() const
+{
+    if (isRemoved())
+        return 0;
+
+    HTMLMediaElement* element = m_source->mediaElement();
+    if (!element)
+        return 0;
+
+    return element->maximumSourceBufferSize(*this);
+}
+
 const AtomicString& SourceBuffer::decodeError()
 {
     static NeverDestroyed<AtomicString> decode("decode", AtomicString::ConstructFromLiteral);
@@ -1198,19 +1348,17 @@
             auto nextSyncIter = trackBuffer.samples.decodeOrder().findSyncSampleAfterDecodeIterator(lastDecodeIter);
             dependentSamples.insert(firstDecodeIter, nextSyncIter);
 
-            RefPtr<TimeRanges> erasedRanges = removeSamplesFromTrackBuffer(dependentSamples, trackBuffer);
-#if !LOG_DISABLED
-            for (auto& samplePair : dependentSamples)
-                LOG(MediaSource, "SourceBuffer::sourceBufferPrivateDidReceiveSample(%p) - removing sample(%s)", this, toString(*samplePair.second).utf8().data());
-#endif
+            RefPtr<TimeRanges> erasedRanges = removeSamplesFromTrackBuffer(dependentSamples, trackBuffer, this, "sourceBufferPrivateDidReceiveSample");
 
             // Only force the TrackBuffer to re-enqueue if the removed ranges overlap with enqueued and possibly
             // not yet displayed samples.
             MediaTime currentMediaTime = MediaTime::createWithDouble(m_source->currentTime());
-            PlatformTimeRanges possiblyEnqueuedRanges(currentMediaTime, trackBuffer.lastEnqueuedPresentationTime);
-            possiblyEnqueuedRanges.intersectWith(erasedRanges->ranges());
-            if (possiblyEnqueuedRanges.length())
-                trackBuffer.needsReenqueueing = true;
+            if (currentMediaTime < trackBuffer.lastEnqueuedPresentationTime) {
+                PlatformTimeRanges possiblyEnqueuedRanges(currentMediaTime, trackBuffer.lastEnqueuedPresentationTime);
+                possiblyEnqueuedRanges.intersectWith(erasedRanges->ranges());
+                if (possiblyEnqueuedRanges.length())
+                    trackBuffer.needsReenqueueing = true;
+            }
 
             erasedRanges->invert();
             m_buffered->intersectWith(*erasedRanges.get());
@@ -1262,16 +1410,26 @@
         m_source->setDurationInternal(highestPresentationEndTimestamp().toDouble());
 }
 
-bool SourceBuffer::sourceBufferPrivateHasAudio(const SourceBufferPrivate*) const
+bool SourceBuffer::hasAudio() const
 {
     return m_audioTracks && m_audioTracks->length();
 }
 
-bool SourceBuffer::sourceBufferPrivateHasVideo(const SourceBufferPrivate*) const
+bool SourceBuffer::hasVideo() const
 {
     return m_videoTracks && m_videoTracks->length();
 }
 
+bool SourceBuffer::sourceBufferPrivateHasAudio(const SourceBufferPrivate*) const
+{
+    return hasAudio();
+}
+
+bool SourceBuffer::sourceBufferPrivateHasVideo(const SourceBufferPrivate*) const
+{
+    return hasVideo();
+}
+
 void SourceBuffer::videoTrackSelectedChanged(VideoTrack* track)
 {
     // 2.4.5 Changes to selected/enabled track state
@@ -1515,6 +1673,10 @@
         return false;
 
     MediaTime currentTime = MediaTime::createWithDouble(m_source->currentTime());
+    MediaTime duration = MediaTime::createWithDouble(m_source->duration());
+    if (currentTime >= duration)
+        return true;
+
     std::unique_ptr<PlatformTimeRanges> ranges = bufferedAccountingForEndOfStream();
     return abs(ranges->nearest(currentTime) - currentTime) <= currentTimeFudgeFactor();
 }
@@ -1529,6 +1691,10 @@
         return false;
 
     MediaTime currentTime = MediaTime::createWithDouble(m_source->currentTime());
+    MediaTime duration = MediaTime::createWithDouble(m_source->duration());
+    if (currentTime >= duration)
+        return true;
+
     MediaTime nearest = ranges->nearest(currentTime);
     if (abs(nearest - currentTime) > currentTimeFudgeFactor())
         return false;
@@ -1538,7 +1704,6 @@
         return false;
 
     MediaTime localEnd = ranges->end(found);
-    MediaTime duration = MediaTime::createWithDouble(m_source->duration());
     if (localEnd == duration)
         return true;
 
@@ -1572,12 +1737,18 @@
     return unbufferedTime.toDouble() / m_averageBufferRate < timeRemaining.toDouble();
 }
 
-void SourceBuffer::reportExtraMemoryCost()
+size_t SourceBuffer::extraMemoryCost() const
 {
     size_t extraMemoryCost = m_pendingAppendData.capacity();
     for (auto& trackBuffer : m_trackBufferMap.values())
         extraMemoryCost += trackBuffer.samples.sizeInBytes();
 
+    return extraMemoryCost;
+}
+
+void SourceBuffer::reportExtraMemoryCost()
+{
+    size_t extraMemoryCost = this->extraMemoryCost();
     if (extraMemoryCost < m_reportedExtraMemoryCost)
         return;
 
@@ -1603,6 +1774,12 @@
     return sampleDescriptions;
 }
 
+Document& SourceBuffer::document() const
+{
+    ASSERT(scriptExecutionContext()->isDocument());
+    return *static_cast<Document*>(scriptExecutionContext());
+}
+
 } // namespace WebCore
 
 #endif

Modified: trunk/Source/WebCore/Modules/mediasource/SourceBuffer.h (172656 => 172657)


--- trunk/Source/WebCore/Modules/mediasource/SourceBuffer.h	2014-08-15 22:43:50 UTC (rev 172656)
+++ trunk/Source/WebCore/Modules/mediasource/SourceBuffer.h	2014-08-15 23:09:19 UTC (rev 172657)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2013 Google Inc. All rights reserved.
+ * Copyright (C) 2013-2014 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -90,6 +91,9 @@
     bool hasFutureTime() const;
     bool canPlayThrough();
 
+    bool hasVideo() const;
+    bool hasAudio() const;
+
     bool active() const { return m_active; }
 
     // ActiveDOMObject interface
@@ -105,6 +109,8 @@
 
     struct TrackBuffer;
 
+    Document& document() const;
+
 protected:
     // EventTarget interface
     virtual void refEventTarget() override { ref(); }
@@ -154,12 +160,15 @@
     void reenqueueMediaForTime(TrackBuffer&, AtomicString trackID, const MediaTime&);
     void provideMediaData(TrackBuffer&, AtomicString trackID);
     void didDropSample();
+    void evictCodedFrames(size_t newDataSize);
+    size_t maximumBufferSize() const;
 
     void monitorBufferingRate();
 
     void removeTimerFired(Timer<SourceBuffer>*);
     void removeCodedFrames(const MediaTime& start, const MediaTime& end);
 
+    size_t extraMemoryCost() const;
     void reportExtraMemoryCost();
 
     std::unique_ptr<PlatformTimeRanges> bufferedAccountingForEndOfStream() const;
@@ -172,8 +181,6 @@
     MediaSource* m_source;
     GenericEventQueue m_asyncEventQueue;
 
-    bool m_updating;
-
     Vector<unsigned char> m_pendingAppendData;
     Timer<SourceBuffer> m_appendBufferTimer;
 
@@ -189,9 +196,7 @@
     MediaTime m_highestPresentationEndTimestamp;
 
     HashMap<AtomicString, TrackBuffer> m_trackBufferMap;
-    bool m_receivedFirstInitializationSegment;
     RefPtr<TimeRanges> m_buffered;
-    bool m_active;
 
     enum AppendStateType { WaitingForSegment, ParsingInitSegment, ParsingMediaSegment };
     AppendStateType m_appendState;
@@ -205,6 +210,11 @@
     MediaTime m_pendingRemoveStart;
     MediaTime m_pendingRemoveEnd;
     Timer<SourceBuffer> m_removeTimer;
+
+    bool m_updating;
+    bool m_receivedFirstInitializationSegment;
+    bool m_active;
+    bool m_bufferFull;
 };
 
 } // namespace WebCore

Modified: trunk/Source/WebCore/html/HTMLMediaElement.cpp (172656 => 172657)


--- trunk/Source/WebCore/html/HTMLMediaElement.cpp	2014-08-15 22:43:50 UTC (rev 172656)
+++ trunk/Source/WebCore/html/HTMLMediaElement.cpp	2014-08-15 23:09:19 UTC (rev 172657)
@@ -2518,7 +2518,6 @@
         return; 
     } 
 
-    LOG(Media, "HTMLMediaElement::refreshCachedTime - caching time %f", m_cachedTime); 
     m_clockTimeAtLastCachedTimeUpdate = monotonicallyIncreasingTime();
 }
 
@@ -5972,6 +5971,13 @@
     return MediaSession::Audio;
 }
 
+#if ENABLE(MEDIA_SOURCE)
+size_t HTMLMediaElement::maximumSourceBufferSize(const SourceBuffer& buffer) const
+{
+    return m_mediaSession->maximumMediaSourceBufferSize(buffer);
+}
+#endif
+
 void HTMLMediaElement::pausePlayback()
 {
     LOG(Media, "HTMLMediaElement::pausePlayback - paused = %s", boolString(paused()));

Modified: trunk/Source/WebCore/html/HTMLMediaElement.h (172656 => 172657)


--- trunk/Source/WebCore/html/HTMLMediaElement.h	2014-08-15 22:43:50 UTC (rev 172656)
+++ trunk/Source/WebCore/html/HTMLMediaElement.h	2014-08-15 23:09:19 UTC (rev 172657)
@@ -72,6 +72,7 @@
 #endif
 #if ENABLE(MEDIA_SOURCE)
 class MediaSource;
+class SourceBuffer;
 class VideoPlaybackQuality;
 #endif
 
@@ -208,6 +209,7 @@
 //  Media Source.
     void closeMediaSource();
     void incrementDroppedFrameCount() { ++m_droppedVideoFrames; }
+    size_t maximumSourceBufferSize(const SourceBuffer&) const;
 #endif
 
 #if ENABLE(ENCRYPTED_MEDIA)

Modified: trunk/Source/WebCore/html/HTMLMediaSession.cpp (172656 => 172657)


--- trunk/Source/WebCore/html/HTMLMediaSession.cpp	2014-08-15 22:43:50 UTC (rev 172656)
+++ trunk/Source/WebCore/html/HTMLMediaSession.cpp	2014-08-15 23:09:19 UTC (rev 172657)
@@ -38,6 +38,7 @@
 #include "MediaSessionManager.h"
 #include "Page.h"
 #include "ScriptController.h"
+#include "SourceBuffer.h"
 
 #if PLATFORM(IOS)
 #include "AudioSession.h"
@@ -301,6 +302,38 @@
     
 }
 
+#if ENABLE(MEDIA_SOURCE)
+const unsigned fiveMinutesOf1080PVideo = 290 * 1024 * 1024; // 290 MB is approximately 5 minutes of 8Mbps (1080p) content.
+const unsigned fiveMinutesStereoAudio = 14 * 1024 * 1024; // 14 MB is approximately 5 minutes of 384kbps content.
+
+size_t HTMLMediaSession::maximumMediaSourceBufferSize(const SourceBuffer& buffer) const
+{
+    // A good quality 1080p video uses 8,000 kbps and stereo audio uses 384 kbps, so assume 95% for video and 5% for audio.
+    const float bufferBudgetPercentageForVideo = .95;
+    const float bufferBudgetPercentageForAudio = .05;
+
+    size_t maximum;
+    Settings* settings = buffer.document().settings();
+    if (settings)
+        maximum = settings->maximumSourceBufferSize();
+    else
+        maximum = fiveMinutesOf1080PVideo + fiveMinutesStereoAudio;
+
+    // Allow a SourceBuffer to buffer as though it is audio-only even if it doesn't have any active tracks (yet).
+    size_t bufferSize = static_cast<size_t>(maximum * bufferBudgetPercentageForAudio);
+    if (buffer.hasVideo())
+        bufferSize += static_cast<size_t>(maximum * bufferBudgetPercentageForVideo);
+
+    // FIXME: we might want to modify this algorithm to:
+    // - decrease the maximum size for background tabs
+    // - decrease the maximum size allowed for inactive elements when a process has more than one
+    //   element, eg. so a page with many elements which are played one at a time doesn't keep
+    //   everything buffered after an element has finished playing.
+
+    return bufferSize;
 }
+#endif
 
+}
+
 #endif // ENABLE(VIDEO)

Modified: trunk/Source/WebCore/html/HTMLMediaSession.h (172656 => 172657)


--- trunk/Source/WebCore/html/HTMLMediaSession.h	2014-08-15 22:43:50 UTC (rev 172656)
+++ trunk/Source/WebCore/html/HTMLMediaSession.h	2014-08-15 23:09:19 UTC (rev 172657)
@@ -34,6 +34,7 @@
 namespace WebCore {
 
 class HTMLMediaElement;
+class SourceBuffer;
 
 class HTMLMediaSession : public MediaSession {
 public:
@@ -82,6 +83,10 @@
     void addBehaviorRestriction(BehaviorRestrictions);
     void removeBehaviorRestriction(BehaviorRestrictions);
 
+#if ENABLE(MEDIA_SOURCE)
+    size_t maximumMediaSourceBufferSize(const SourceBuffer&) const;
+#endif
+
 private:
     BehaviorRestrictions m_restrictions;
 };

Modified: trunk/Source/WebCore/page/Settings.in (172656 => 172657)


--- trunk/Source/WebCore/page/Settings.in	2014-08-15 22:43:50 UTC (rev 172656)
+++ trunk/Source/WebCore/page/Settings.in	2014-08-15 23:09:19 UTC (rev 172657)
@@ -230,4 +230,8 @@
 # as a means to pass data from the web-portion of their app to the native portion.
 allowNavigationToInvalidURL initial=false
 
+# Allow SourceBuffers to store up to 304MB each, enough for approximately five minutes
+# of 1080p video and stereo audio.
+maximumSourceBufferSize type=int, initial=318767104, conditional=MEDIA_SOURCE
+
 longMousePressEnabled initial=false

Modified: trunk/Source/WebCore/platform/graphics/PlatformTimeRanges.cpp (172656 => 172657)


--- trunk/Source/WebCore/platform/graphics/PlatformTimeRanges.cpp	2014-08-15 22:43:50 UTC (rev 172656)
+++ trunk/Source/WebCore/platform/graphics/PlatformTimeRanges.cpp	2014-08-15 23:09:19 UTC (rev 172657)
@@ -27,6 +27,7 @@
 #include "PlatformTimeRanges.h"
 
 #include <math.h>
+#include <wtf/PrintStream.h>
 
 namespace WebCore {
 
@@ -252,11 +253,19 @@
 MediaTime PlatformTimeRanges::totalDuration() const
 {
     MediaTime total = MediaTime::zeroTime();
-    bool ignoreInvalid;
 
     for (unsigned n = 0; n < length(); n++)
-        total += abs(end(n, ignoreInvalid) - start(n, ignoreInvalid));
+        total += abs(end(n) - start(n));
     return total;
 }
 
+void PlatformTimeRanges::dump(PrintStream& out) const
+{
+    if (!length())
+        return;
+
+    for (size_t i = 0; i < length(); ++i)
+        out.print("[", start(i), "..", end(i), "] ");
 }
+
+}

Modified: trunk/Source/WebCore/platform/graphics/PlatformTimeRanges.h (172656 => 172657)


--- trunk/Source/WebCore/platform/graphics/PlatformTimeRanges.h	2014-08-15 22:43:50 UTC (rev 172656)
+++ trunk/Source/WebCore/platform/graphics/PlatformTimeRanges.h	2014-08-15 23:09:19 UTC (rev 172657)
@@ -32,6 +32,10 @@
 #include <wtf/RefCounted.h>
 #include <wtf/Vector.h>
 
+namespace WTF {
+class PrintStream;
+}
+
 namespace WebCore {
 
 class PlatformTimeRanges {
@@ -69,6 +73,8 @@
 
     MediaTime totalDuration() const;
 
+    void dump(WTF::PrintStream&) const;
+
 private:
     PlatformTimeRanges& copy(const PlatformTimeRanges&);
 

Modified: trunk/Source/WebCore/platform/graphics/SourceBufferPrivate.h (172656 => 172657)


--- trunk/Source/WebCore/platform/graphics/SourceBufferPrivate.h	2014-08-15 22:43:50 UTC (rev 172656)
+++ trunk/Source/WebCore/platform/graphics/SourceBufferPrivate.h	2014-08-15 23:09:19 UTC (rev 172657)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2013 Google Inc. All rights reserved.
+ * Copyright (C) 2013-2014 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -55,8 +56,6 @@
 
     virtual MediaPlayer::ReadyState readyState() const = 0;
     virtual void setReadyState(MediaPlayer::ReadyState) = 0;
-    virtual void evictCodedFrames() = 0;
-    virtual bool isFull() = 0;
 
     virtual void flushAndEnqueueNonDisplayingSamples(Vector<RefPtr<MediaSample>>, AtomicString) { }
     virtual void enqueueSample(PassRefPtr<MediaSample>, AtomicString) { }

Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.h (172656 => 172657)


--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.h	2014-08-15 22:43:50 UTC (rev 172656)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.h	2014-08-15 23:09:19 UTC (rev 172657)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2013-2014 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -112,8 +112,6 @@
     virtual void removedFromMediaSource() override;
     virtual MediaPlayer::ReadyState readyState() const override;
     virtual void setReadyState(MediaPlayer::ReadyState) override;
-    virtual void evictCodedFrames() override;
-    virtual bool isFull() override;
     virtual void flushAndEnqueueNonDisplayingSamples(Vector<RefPtr<MediaSample>>, AtomicString trackID) override;
     virtual void enqueueSample(PassRefPtr<MediaSample>, AtomicString trackID) override;
     virtual bool isReadyForMoreSamples(AtomicString trackID) override;

Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm (172656 => 172657)


--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm	2014-08-15 22:43:50 UTC (rev 172656)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm	2014-08-15 23:09:19 UTC (rev 172657)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2013-2014 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -632,7 +632,7 @@
         CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer);
         FloatSize formatSize = FloatSize(CMVideoFormatDescriptionGetPresentationDimensions(formatDescription, true, true));
         if (formatSize != m_cachedSize) {
-            LOG(MediaSource, "SourceBufferPrivateAVFObjC::processCodedFrame(%p) - size change detected: {width=%lf, height=%lf", formatSize.width(), formatSize.height());
+            LOG(MediaSource, "SourceBufferPrivateAVFObjC::processCodedFrame(%p) - size change detected: {width=%lf, height=%lf}", formatSize.width(), formatSize.height());
             m_cachedSize = formatSize;
             if (m_mediaSource)
                 m_mediaSource->player()->sizeChanged();
@@ -779,18 +779,6 @@
         m_mediaSource->player()->setReadyState(readyState);
 }
 
-void SourceBufferPrivateAVFObjC::evictCodedFrames()
-{
-    notImplemented();
-}
-
-bool SourceBufferPrivateAVFObjC::isFull()
-{
-    notImplemented();
-    return false;
-}
-
-
 bool SourceBufferPrivateAVFObjC::hasVideo() const
 {
     if (!m_client)

Modified: trunk/Source/WebCore/platform/mock/mediasource/MockSourceBufferPrivate.cpp (172656 => 172657)


--- trunk/Source/WebCore/platform/mock/mediasource/MockSourceBufferPrivate.cpp	2014-08-15 22:43:50 UTC (rev 172656)
+++ trunk/Source/WebCore/platform/mock/mediasource/MockSourceBufferPrivate.cpp	2014-08-15 23:09:19 UTC (rev 172657)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2013-2014 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -213,16 +213,6 @@
         m_mediaSource->player()->setReadyState(readyState);
 }
 
-void MockSourceBufferPrivate::evictCodedFrames()
-{
-    // No-op.
-}
-
-bool MockSourceBufferPrivate::isFull()
-{
-    return false;
-}
-
 void MockSourceBufferPrivate::setActive(bool isActive)
 {
     if (m_mediaSource)

Modified: trunk/Source/WebCore/platform/mock/mediasource/MockSourceBufferPrivate.h (172656 => 172657)


--- trunk/Source/WebCore/platform/mock/mediasource/MockSourceBufferPrivate.h	2014-08-15 22:43:50 UTC (rev 172656)
+++ trunk/Source/WebCore/platform/mock/mediasource/MockSourceBufferPrivate.h	2014-08-15 23:09:19 UTC (rev 172657)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2013-2014 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -68,8 +68,6 @@
     virtual void removedFromMediaSource() override;
     virtual MediaPlayer::ReadyState readyState() const override;
     virtual void setReadyState(MediaPlayer::ReadyState) override;
-    virtual void evictCodedFrames() override;
-    virtual bool isFull() override;
 
     virtual void flushAndEnqueueNonDisplayingSamples(Vector<RefPtr<MediaSample>>, AtomicString) override { }
     virtual void enqueueSample(PassRefPtr<MediaSample>, AtomicString) override;
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to