Title: [181005] trunk/Source/WebCore
Revision
181005
Author
[email protected]
Date
2015-03-04 11:22:59 -0800 (Wed, 04 Mar 2015)

Log Message

[MSE][EME][Mac] Calling close on a MediaKeysSession will cause many decoding errors to be emitted
https://bugs.webkit.org/show_bug.cgi?id=142285

Reviewed by Eric Carlson.

When a MediaKeySession (backed by CDMSessionMediaSourceAVFObjC) is closed and the
underlying AVStreamSession is invalidated, the decryption context for in-flight
CMSampleBuffers is also invalidated, and the AVSampleBufferDisplayLayer will issue
one error for each enqueued and un-displayed sample in its image-queue. -flush-ing
the AVSampleBufferDisplayLayer is not enough, as the flush only takes effect
asynchronously the next time the layer needs new samples.

Add a workaround until framework-level support lands to fully flush enqueued and
encrypted frames.

When the CDMSessionMediaSOurceAVFObjC object recieves an error from the layer,
check to see if the session has been stopped. If so, and if the error in question is
one that indicates that the samples decryption context has been invalidated, suppress
the error and instruct the sender to suppress the error as well. This workaround will
be removed once real support for synchronous flushing lands in <rdar://problem/20027434.>

Still, we'll make our best effort to flush undisplayed frames when our CDM session is
invalidated. Move away from std::map and instead use HashMap to store the set of
AVSampleBufferAudioRenderers. This allows us to use C++11 style loops against just
the HashMap's set of values.

* platform/graphics/avfoundation/objc/CDMSessionMediaSourceAVFObjC.h:
* platform/graphics/avfoundation/objc/CDMSessionMediaSourceAVFObjC.mm:
(WebCore::CDMSessionMediaSourceAVFObjC::releaseKeys): Flush and set m_stopped.
(WebCore::CDMSessionMediaSourceAVFObjC::layerDidReceiveError): Check m_stopped and the
    error code and bail before issuing the error.
(WebCore::CDMSessionMediaSourceAVFObjC::rendererDidReceiveError): Ditto.
* platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.h:
* platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm:
(-[WebAVSampleBufferErrorListener layerFailedToDecode:]): Drive-by fix. Check whether
    the layer is in the set of listened-to layers only back in the main thread; the
    listnener may have been unregistered by the time the main thread was called.
(WebCore::SourceBufferPrivateAVFObjC::destroyRenderers): std::map -> HashMap.
(WebCore::SourceBufferPrivateAVFObjC::trackDidChangeEnabled): Ditto.
(WebCore::SourceBufferPrivateAVFObjC::flushAndEnqueueNonDisplayingSamples): Ditto.
(WebCore::SourceBufferPrivateAVFObjC::enqueueSample): Ditto.
(WebCore::SourceBufferPrivateAVFObjC::isReadyForMoreSamples): Ditto.
(WebCore::SourceBufferPrivateAVFObjC::didBecomeReadyForMoreSamples): Ditto.
(WebCore::SourceBufferPrivateAVFObjC::notifyClientWhenReadyForMoreSamples): Ditto.
(WebCore::SourceBufferPrivateAVFObjC::flush): Added; call -flush on all the display
    layers and audio renderers.
(WebCore::SourceBufferPrivateAVFObjC::layerDidReceiveError): Check if any clients
    asked to ignore the error, and if so, bail.
(WebCore::SourceBufferPrivateAVFObjC::rendererDidReceiveError): Ditto.

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (181004 => 181005)


--- trunk/Source/WebCore/ChangeLog	2015-03-04 18:48:50 UTC (rev 181004)
+++ trunk/Source/WebCore/ChangeLog	2015-03-04 19:22:59 UTC (rev 181005)
@@ -1,3 +1,55 @@
+2015-03-04  Jer Noble  <[email protected]>
+
+        [MSE][EME][Mac] Calling close on a MediaKeysSession will cause many decoding errors to be emitted
+        https://bugs.webkit.org/show_bug.cgi?id=142285
+
+        Reviewed by Eric Carlson.
+
+        When a MediaKeySession (backed by CDMSessionMediaSourceAVFObjC) is closed and the
+        underlying AVStreamSession is invalidated, the decryption context for in-flight
+        CMSampleBuffers is also invalidated, and the AVSampleBufferDisplayLayer will issue
+        one error for each enqueued and un-displayed sample in its image-queue. -flush-ing
+        the AVSampleBufferDisplayLayer is not enough, as the flush only takes effect
+        asynchronously the next time the layer needs new samples.
+
+        Add a workaround until framework-level support lands to fully flush enqueued and
+        encrypted frames.
+
+        When the CDMSessionMediaSOurceAVFObjC object recieves an error from the layer,
+        check to see if the session has been stopped. If so, and if the error in question is
+        one that indicates that the samples decryption context has been invalidated, suppress
+        the error and instruct the sender to suppress the error as well. This workaround will
+        be removed once real support for synchronous flushing lands in <rdar://problem/20027434.>
+
+        Still, we'll make our best effort to flush undisplayed frames when our CDM session is
+        invalidated. Move away from std::map and instead use HashMap to store the set of 
+        AVSampleBufferAudioRenderers. This allows us to use C++11 style loops against just
+        the HashMap's set of values.
+
+        * platform/graphics/avfoundation/objc/CDMSessionMediaSourceAVFObjC.h:
+        * platform/graphics/avfoundation/objc/CDMSessionMediaSourceAVFObjC.mm:
+        (WebCore::CDMSessionMediaSourceAVFObjC::releaseKeys): Flush and set m_stopped.
+        (WebCore::CDMSessionMediaSourceAVFObjC::layerDidReceiveError): Check m_stopped and the
+            error code and bail before issuing the error.
+        (WebCore::CDMSessionMediaSourceAVFObjC::rendererDidReceiveError): Ditto.
+        * platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.h:
+        * platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm:
+        (-[WebAVSampleBufferErrorListener layerFailedToDecode:]): Drive-by fix. Check whether
+            the layer is in the set of listened-to layers only back in the main thread; the
+            listnener may have been unregistered by the time the main thread was called.
+        (WebCore::SourceBufferPrivateAVFObjC::destroyRenderers): std::map -> HashMap.
+        (WebCore::SourceBufferPrivateAVFObjC::trackDidChangeEnabled): Ditto.
+        (WebCore::SourceBufferPrivateAVFObjC::flushAndEnqueueNonDisplayingSamples): Ditto.
+        (WebCore::SourceBufferPrivateAVFObjC::enqueueSample): Ditto.
+        (WebCore::SourceBufferPrivateAVFObjC::isReadyForMoreSamples): Ditto.
+        (WebCore::SourceBufferPrivateAVFObjC::didBecomeReadyForMoreSamples): Ditto.
+        (WebCore::SourceBufferPrivateAVFObjC::notifyClientWhenReadyForMoreSamples): Ditto.
+        (WebCore::SourceBufferPrivateAVFObjC::flush): Added; call -flush on all the display
+            layers and audio renderers.
+        (WebCore::SourceBufferPrivateAVFObjC::layerDidReceiveError): Check if any clients
+            asked to ignore the error, and if so, bail.
+        (WebCore::SourceBufferPrivateAVFObjC::rendererDidReceiveError): Ditto.
+
 2015-03-04  Alex Christensen  <[email protected]>
 
         Update bindings tests after r181001.

Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/CDMSessionMediaSourceAVFObjC.h (181004 => 181005)


--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/CDMSessionMediaSourceAVFObjC.h	2015-03-04 18:48:50 UTC (rev 181004)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/CDMSessionMediaSourceAVFObjC.h	2015-03-04 19:22:59 UTC (rev 181005)
@@ -50,8 +50,8 @@
     virtual void releaseKeys() override;
     virtual bool update(Uint8Array*, RefPtr<Uint8Array>& nextMessage, unsigned short& errorCode, unsigned long& systemCode) override;
 
-    virtual void layerDidReceiveError(AVSampleBufferDisplayLayer *, NSError *);
-    virtual void rendererDidReceiveError(AVSampleBufferAudioRenderer *, NSError *);
+    virtual void layerDidReceiveError(AVSampleBufferDisplayLayer *, NSError *, bool& shouldIgnore);
+    virtual void rendererDidReceiveError(AVSampleBufferAudioRenderer *, NSError *, bool& shouldIgnore);
 
     void setStreamSession(AVStreamSession *);
 
@@ -74,6 +74,7 @@
     Vector<int> m_protocolVersions;
     String m_sessionId;
     enum { Normal, KeyRelease } m_mode;
+    bool m_stopped = { false };
 };
 
 inline CDMSessionMediaSourceAVFObjC* toCDMSessionMediaSourceAVFObjC(CDMSession* session)

Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/CDMSessionMediaSourceAVFObjC.mm (181004 => 181005)


--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/CDMSessionMediaSourceAVFObjC.mm	2015-03-04 18:48:50 UTC (rev 181004)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/CDMSessionMediaSourceAVFObjC.mm	2015-03-04 19:22:59 UTC (rev 181005)
@@ -141,6 +141,10 @@
 void CDMSessionMediaSourceAVFObjC::releaseKeys()
 {
     if (m_streamSession) {
+        m_stopped = true;
+        for (auto& sourceBuffer : m_sourceBuffers)
+            sourceBuffer->flush();
+
         LOG(Media, "CDMSessionMediaSourceAVFObjC::releaseKeys(%p) - expiring stream session", this);
         [m_streamSession expire];
 
@@ -282,20 +286,30 @@
     return true;
 }
 
-void CDMSessionMediaSourceAVFObjC::layerDidReceiveError(AVSampleBufferDisplayLayer *, NSError *error)
+void CDMSessionMediaSourceAVFObjC::layerDidReceiveError(AVSampleBufferDisplayLayer *, NSError *error, bool& shouldIgnore)
 {
     if (!m_client)
         return;
 
-    m_client->sendError(CDMSessionClient::MediaKeyErrorDomain, std::abs(systemCodeForError(error)));
+    unsigned long code = std::abs(systemCodeForError(error));
+
+    // FIXME(142246): Remove the following once <rdar://problem/20027434> is resolved.
+    shouldIgnore = m_stopped && code == 12785;
+    if (!shouldIgnore)
+        m_client->sendError(CDMSessionClient::MediaKeyErrorDomain, code);
 }
 
-void CDMSessionMediaSourceAVFObjC::rendererDidReceiveError(AVSampleBufferAudioRenderer *, NSError *error)
+void CDMSessionMediaSourceAVFObjC::rendererDidReceiveError(AVSampleBufferAudioRenderer *, NSError *error, bool& shouldIgnore)
 {
     if (!m_client)
         return;
 
-    m_client->sendError(CDMSessionClient::MediaKeyErrorDomain, std::abs(systemCodeForError(error)));
+    unsigned long code = std::abs(systemCodeForError(error));
+
+    // FIXME(142246): Remove the following once <rdar://problem/20027434> is resolved.
+    shouldIgnore = m_stopped && code == 12785;
+    if (!shouldIgnore)
+        m_client->sendError(CDMSessionClient::MediaKeyErrorDomain, code);
 }
 
 void CDMSessionMediaSourceAVFObjC::setStreamSession(AVStreamSession *streamSession)

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


--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.h	2015-03-04 18:48:50 UTC (rev 181004)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.h	2015-03-04 19:22:59 UTC (rev 181005)
@@ -29,7 +29,6 @@
 #if ENABLE(MEDIA_SOURCE) && USE(AVFOUNDATION)
 
 #include "SourceBufferPrivate.h"
-#include <map>
 #include <wtf/Deque.h>
 #include <wtf/HashMap.h>
 #include <wtf/MediaTime.h>
@@ -64,8 +63,8 @@
 class SourceBufferPrivateAVFObjCErrorClient {
 public:
     virtual ~SourceBufferPrivateAVFObjCErrorClient() { }
-    virtual void layerDidReceiveError(AVSampleBufferDisplayLayer *, NSError *) = 0;
-    virtual void rendererDidReceiveError(AVSampleBufferAudioRenderer *, NSError *) = 0;
+    virtual void layerDidReceiveError(AVSampleBufferDisplayLayer *, NSError *, bool& shouldIgnore) = 0;
+    virtual void rendererDidReceiveError(AVSampleBufferAudioRenderer *, NSError *, bool& shouldIgnore) = 0;
 };
 
 class SourceBufferPrivateAVFObjC final : public SourceBufferPrivate {
@@ -98,6 +97,8 @@
     int protectedTrackID() const { return m_protectedTrackID; }
     AVStreamDataParser* parser() const { return m_parser.get(); }
 
+    void flush();
+
     void registerForErrorNotifications(SourceBufferPrivateAVFObjCErrorClient*);
     void unregisterForErrorNotifications(SourceBufferPrivateAVFObjCErrorClient*);
     void layerDidReceiveError(AVSampleBufferDisplayLayer *, NSError *);
@@ -138,7 +139,7 @@
     RetainPtr<AVStreamDataParser> m_parser;
     RetainPtr<AVAsset> m_asset;
     RetainPtr<AVSampleBufferDisplayLayer> m_displayLayer;
-    std::map<int, RetainPtr<AVSampleBufferAudioRenderer>> m_audioRenderers;
+    HashMap<int, RetainPtr<AVSampleBufferAudioRenderer>> m_audioRenderers;
     RetainPtr<WebAVStreamDataParserListener> m_delegate;
     RetainPtr<WebAVSampleBufferErrorListener> m_errorListener;
 

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


--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm	2015-03-04 18:48:50 UTC (rev 181004)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm	2015-03-04 19:22:59 UTC (rev 181005)
@@ -29,6 +29,7 @@
 #if ENABLE(MEDIA_SOURCE) && USE(AVFOUNDATION)
 
 #import "BlockExceptions.h"
+#import "CDMSessionMediaSourceAVFObjC.h"
 #import "ExceptionCodePlaceholder.h"
 #import "Logging.h"
 #import "MediaDescription.h"
@@ -149,6 +150,7 @@
 - (NSError*)error;
 - (void)enqueueSampleBuffer:(CMSampleBufferRef)sampleBuffer;
 - (void)flush;
+- (void)flushAndRemoveImage;
 - (BOOL)isReadyForMoreMediaData;
 - (void)requestMediaDataWhenReadyOnQueue:(dispatch_queue_t)queue usingBlock:(void (^)(void))block;
 - (void)stopRequestingMediaData;
@@ -452,12 +454,12 @@
 - (void)layerFailedToDecode:(NSNotification*)note
 {
     RetainPtr<AVSampleBufferDisplayLayer> layer = (AVSampleBufferDisplayLayer *)[note object];
-    ASSERT(_layers.contains(layer.get()));
-
     RetainPtr<NSError> error = [[note userInfo] valueForKey:AVSampleBufferDisplayLayerFailedToDecodeNotificationErrorKey];
 
     RetainPtr<WebAVSampleBufferErrorListener> strongSelf = self;
     callOnMainThread([strongSelf, layer, error] {
+        if (!strongSelf->_parent || !strongSelf->_layers.contains(layer.get()))
+            return;
         strongSelf->_parent->layerDidReceiveError(layer.get(), error.get());
     });
 }
@@ -829,13 +831,12 @@
         m_displayLayer = nullptr;
     }
 
-    for (auto it = m_audioRenderers.begin(), end = m_audioRenderers.end(); it != end; ++it) {
-        AVSampleBufferAudioRenderer* renderer = it->second.get();
+    for (auto& renderer : m_audioRenderers.values()) {
         if (m_mediaSource)
-            m_mediaSource->player()->removeAudioRenderer(renderer);
+            m_mediaSource->player()->removeAudioRenderer(renderer.get());
         [renderer flush];
         [renderer stopRequestingMediaData];
-        [m_errorListener stopObservingRenderer:renderer];
+        [m_errorListener stopObservingRenderer:renderer.get()];
     }
 
     m_audioRenderers.clear();
@@ -908,28 +909,37 @@
     int trackID = track->trackID();
 
     if (!track->enabled()) {
-        AVSampleBufferAudioRenderer* renderer = m_audioRenderers[trackID].get();
+        RetainPtr<AVSampleBufferAudioRenderer> renderer = m_audioRenderers.get(trackID);
         [m_parser setShouldProvideMediaData:NO forTrackID:trackID];
         if (m_mediaSource)
-            m_mediaSource->player()->removeAudioRenderer(renderer);
+            m_mediaSource->player()->removeAudioRenderer(renderer.get());
     } else {
         [m_parser setShouldProvideMediaData:YES forTrackID:trackID];
         RetainPtr<AVSampleBufferAudioRenderer> renderer;
-        if (!m_audioRenderers.count(trackID)) {
+        if (!m_audioRenderers.contains(trackID)) {
             renderer = adoptNS([allocAVSampleBufferAudioRendererInstance() init]);
             [renderer requestMediaDataWhenReadyOnQueue:dispatch_get_main_queue() usingBlock:^{
                 didBecomeReadyForMoreSamples(trackID);
             }];
-            m_audioRenderers[trackID] = renderer;
+            m_audioRenderers.set(trackID, renderer);
             [m_errorListener beginObservingRenderer:renderer.get()];
         } else
-            renderer = m_audioRenderers[trackID].get();
+            renderer = m_audioRenderers.get(trackID);
 
         if (m_mediaSource)
             m_mediaSource->player()->addAudioRenderer(renderer.get());
     }
 }
 
+void SourceBufferPrivateAVFObjC::flush()
+{
+    if (m_displayLayer)
+        [m_displayLayer flushAndRemoveImage];
+
+    for (auto& renderer : m_audioRenderers.values())
+        [renderer flush];
+}
+
 void SourceBufferPrivateAVFObjC::registerForErrorNotifications(SourceBufferPrivateAVFObjCErrorClient* client)
 {
     ASSERT(!m_errorClients.contains(client));
@@ -945,9 +955,17 @@
 void SourceBufferPrivateAVFObjC::layerDidReceiveError(AVSampleBufferDisplayLayer *layer, NSError *error)
 {
     LOG(MediaSource, "SourceBufferPrivateAVFObjC::layerDidReceiveError(%p): layer(%p), error(%@)", this, layer, [error description]);
-    for (auto& client : m_errorClients)
-        client->layerDidReceiveError(layer, error);
 
+    // FIXME(142246): Remove the following once <rdar://problem/20027434> is resolved.
+    bool anyIgnored = false;
+    for (auto& client : m_errorClients) {
+        bool shouldIgnore = false;
+        client->layerDidReceiveError(layer, error, shouldIgnore);
+        anyIgnored |= shouldIgnore;
+    }
+    if (anyIgnored)
+        return;
+
     int errorCode = [[[error userInfo] valueForKey:@"OSStatus"] intValue];
 
     if (m_client)
@@ -957,8 +975,16 @@
 void SourceBufferPrivateAVFObjC::rendererDidReceiveError(AVSampleBufferAudioRenderer *renderer, NSError *error)
 {
     LOG(MediaSource, "SourceBufferPrivateAVFObjC::rendererDidReceiveError(%p): renderer(%p), error(%@)", this, renderer, [error description]);
-    for (auto& client : m_errorClients)
-        client->rendererDidReceiveError(renderer, error);
+
+    // FIXME(142246): Remove the following once <rdar://problem/20027434> is resolved.
+    bool anyIgnored = false;
+    for (auto& client : m_errorClients) {
+        bool shouldIgnore = false;
+        client->rendererDidReceiveError(renderer, error, shouldIgnore);
+        anyIgnored |= shouldIgnore;
+    }
+    if (anyIgnored)
+        return;
 }
 
 static RetainPtr<CMSampleBufferRef> createNonDisplayingCopy(CMSampleBufferRef sampleBuffer)
@@ -984,8 +1010,8 @@
 
     if (trackID == m_enabledVideoTrackID)
         flushAndEnqueueNonDisplayingSamples(mediaSamples, m_displayLayer.get());
-    else if (m_audioRenderers.count(trackID))
-        flushAndEnqueueNonDisplayingSamples(mediaSamples, m_audioRenderers[trackID].get());
+    else if (m_audioRenderers.contains(trackID))
+        flushAndEnqueueNonDisplayingSamples(mediaSamples, m_audioRenderers.get(trackID).get());
 }
 
 void SourceBufferPrivateAVFObjC::flushAndEnqueueNonDisplayingSamples(Vector<RefPtr<MediaSample>> mediaSamples, AVSampleBufferAudioRenderer* renderer)
@@ -1028,7 +1054,7 @@
 void SourceBufferPrivateAVFObjC::enqueueSample(PassRefPtr<MediaSample> prpMediaSample, AtomicString trackIDString)
 {
     int trackID = trackIDString.toInt();
-    if (trackID != m_enabledVideoTrackID && !m_audioRenderers.count(trackID))
+    if (trackID != m_enabledVideoTrackID && !m_audioRenderers.contains(trackID))
         return;
 
     RefPtr<MediaSample> mediaSample = prpMediaSample;
@@ -1044,7 +1070,7 @@
         if (m_mediaSource)
             m_mediaSource->player()->setHasAvailableVideoFrame(true);
     } else
-        [m_audioRenderers[trackID] enqueueSampleBuffer:platformSample.sample.cmSampleBuffer];
+        [m_audioRenderers.get(trackID) enqueueSampleBuffer:platformSample.sample.cmSampleBuffer];
 }
 
 bool SourceBufferPrivateAVFObjC::isReadyForMoreSamples(AtomicString trackIDString)
@@ -1052,8 +1078,8 @@
     int trackID = trackIDString.toInt();
     if (trackID == m_enabledVideoTrackID)
         return [m_displayLayer isReadyForMoreMediaData];
-    else if (m_audioRenderers.count(trackID))
-        return [m_audioRenderers[trackID] isReadyForMoreMediaData];
+    else if (m_audioRenderers.contains(trackID))
+        return [m_audioRenderers.get(trackID) isReadyForMoreMediaData];
     else
         ASSERT_NOT_REACHED();
 
@@ -1088,8 +1114,8 @@
 {
     if (trackID == m_enabledVideoTrackID)
         [m_displayLayer stopRequestingMediaData];
-    else if (m_audioRenderers.count(trackID))
-        [m_audioRenderers[trackID] stopRequestingMediaData];
+    else if (m_audioRenderers.contains(trackID))
+        [m_audioRenderers.get(trackID) stopRequestingMediaData];
     else {
         ASSERT_NOT_REACHED();
         return;
@@ -1106,8 +1132,8 @@
         [m_displayLayer requestMediaDataWhenReadyOnQueue:dispatch_get_main_queue() usingBlock:^{
             didBecomeReadyForMoreSamples(trackID);
         }];
-    } else if (m_audioRenderers.count(trackID)) {
-        [m_audioRenderers[trackID] requestMediaDataWhenReadyOnQueue:dispatch_get_main_queue() usingBlock:^{
+    } else if (m_audioRenderers.contains(trackID)) {
+        [m_audioRenderers.get(trackID) requestMediaDataWhenReadyOnQueue:dispatch_get_main_queue() usingBlock:^{
             didBecomeReadyForMoreSamples(trackID);
         }];
     } else
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to