⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 293270 in webkit


Ignore:
Timestamp:
Apr 22, 2022, 5:04:16 PM (4 years ago)
Author:
Russell Epstein
Message:

Apply patch. rdar://problem/88904160

Location:
branches/safari-613.2.7.1-branch/Source/WebKit
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • branches/safari-613.2.7.1-branch/Source/WebKit/ChangeLog

    r293269 r293270  
     12022-04-22  Russell Epstein  <repstein@apple.com>
     2
     3        Apply patch. rdar://problem/88904160
     4
     5    2022-04-22  Kimmo Kinnunen  <kkinnunen@apple.com>
     6
     7            Multiple concurrency violations in LibWebRTCCodecsProxy
     8            https://bugs.webkit.org/show_bug.cgi?id=236767
     9            <rdar://88904160>
     10
     11            Reviewed by Antti Koivisto.
     12
     13            - ThreadMessageReceivers should not add IPC listeners in constructors,
     14            as the delivery starts right away and uses the unconstructed virtual pointer.
     15            - The work queue functions should not use GPUConnectionToWebProcess, as that is
     16            main thread object.
     17            - Locked m_encoders, m_decoders are sometimes accessed without lock.
     18
     19            Instead:
     20            - Add the IPC listeners in initialize function.
     21            - Remove the IPC listeners when GPUConnectionToWebProcess disconnects.
     22            - Store the thread-safe conection, video frame object heap, process identity
     23            objects as member variables.
     24            - Do not lock m_encoders, m_decoders. If they are work queue instances,
     25            just access them in the work queue functions. Add thread requirements
     26            to the variables so that the compiler checks the access.
     27            - Use IPC testing assertions when skipping incorrect messages.
     28            - Use separate atomic counter (bool) to check if allowsExitUnderMemoryPressure.
     29
     30            No new tests, tested with existing tests and ASAN.
     31
     32            * GPUProcess/GPUConnectionToWebProcess.cpp:
     33            (WebKit::GPUConnectionToWebProcess::~GPUConnectionToWebProcess):
     34            (WebKit::GPUConnectionToWebProcess::didClose):
     35            * GPUProcess/GPUConnectionToWebProcess.h:
     36            * GPUProcess/webrtc/LibWebRTCCodecsProxy.h:
     37            * GPUProcess/webrtc/LibWebRTCCodecsProxy.mm:
     38            (WebKit::LibWebRTCCodecsProxy::create):
     39            (WebKit::LibWebRTCCodecsProxy::LibWebRTCCodecsProxy):
     40            (WebKit::LibWebRTCCodecsProxy::stopListeningForIPC):
     41            (WebKit::LibWebRTCCodecsProxy::initialize):
     42            (WebKit::LibWebRTCCodecsProxy::dispatchToThread):
     43            (WebKit::LibWebRTCCodecsProxy::createDecoderCallback):
     44            (WebKit::LibWebRTCCodecsProxy::createH264Decoder):
     45            (WebKit::LibWebRTCCodecsProxy::createH265Decoder):
     46            (WebKit::LibWebRTCCodecsProxy::createVP9Decoder):
     47            (WebKit::LibWebRTCCodecsProxy::releaseDecoder):
     48            (WebKit::LibWebRTCCodecsProxy::createEncoder):
     49            (WebKit::LibWebRTCCodecsProxy::releaseEncoder):
     50            (WebKit::LibWebRTCCodecsProxy::initializeEncoder):
     51            (WebKit::LibWebRTCCodecsProxy::findEncoder):
     52            (WebKit::LibWebRTCCodecsProxy::encodeFrame):
     53            (WebKit::LibWebRTCCodecsProxy::setEncodeRates):
     54            (WebKit::LibWebRTCCodecsProxy::setSharedVideoFrameSemaphore):
     55            (WebKit::LibWebRTCCodecsProxy::setSharedVideoFrameMemory):
     56            (WebKit::LibWebRTCCodecsProxy::allowsExitUnderMemoryPressure const):
     57
    1582022-04-22  Russell Epstein  <repstein@apple.com>
    259
  • branches/safari-613.2.7.1-branch/Source/WebKit/GPUProcess/GPUConnectionToWebProcess.cpp

    r287288 r293270  
    275275    m_sampleBufferDisplayLayerManager->close();
    276276#endif
    277 #if PLATFORM(COCOA) && USE(LIBWEBRTC)
    278     m_libWebRTCCodecsProxy->close();
    279 #endif
    280277
    281278    --gObjectCountForTesting;
     
    310307    });
    311308#endif
    312 
     309#if PLATFORM(COCOA) && USE(LIBWEBRTC)
     310    m_libWebRTCCodecsProxy = nullptr;
     311#endif
    313312    gpuProcess().connectionToWebProcessClosed(connection);
    314313    gpuProcess().removeGPUConnectionToWebProcess(*this); // May destroy |this|.
  • branches/safari-613.2.7.1-branch/Source/WebKit/GPUProcess/GPUConnectionToWebProcess.h

    r287288 r293270  
    286286    PAL::SessionID m_sessionID;
    287287#if PLATFORM(COCOA) && USE(LIBWEBRTC)
    288     Ref<LibWebRTCCodecsProxy> m_libWebRTCCodecsProxy;
     288    IPC::ScopedActiveMessageReceiveQueue<LibWebRTCCodecsProxy> m_libWebRTCCodecsProxy;
    289289#endif
    290290#if PLATFORM(COCOA) && ENABLE(MEDIA_STREAM)
  • branches/safari-613.2.7.1-branch/Source/WebKit/GPUProcess/webrtc/LibWebRTCCodecsProxy.h

    r283036 r293270  
    3232#include "RTCDecoderIdentifier.h"
    3333#include "RTCEncoderIdentifier.h"
    34 #include <wtf/Lock.h>
     34#include <atomic>
     35#include <wtf/ThreadAssertions.h>
    3536
    3637namespace IPC {
     
    5253class GPUConnectionToWebProcess;
    5354
    54 class LibWebRTCCodecsProxy : public IPC::Connection::ThreadMessageReceiverRefCounted {
     55class LibWebRTCCodecsProxy final : public IPC::Connection::ThreadMessageReceiverRefCounted {
    5556    WTF_MAKE_FAST_ALLOCATED;
    5657public:
    57     static Ref<LibWebRTCCodecsProxy> create(GPUConnectionToWebProcess& process) { return adoptRef(*new LibWebRTCCodecsProxy(process)); }
     58    static Ref<LibWebRTCCodecsProxy> create(GPUConnectionToWebProcess&);
    5859    ~LibWebRTCCodecsProxy();
    59 
    60     void close();
    61 
     60    void stopListeningForIPC(Ref<LibWebRTCCodecsProxy>&& refFromConnection);
    6261    bool allowsExitUnderMemoryPressure() const;
    6362
    6463private:
    6564    explicit LibWebRTCCodecsProxy(GPUConnectionToWebProcess&);
     65    void initialize();
     66    auto createDecoderCallback(RTCDecoderIdentifier, bool useRemoteFrames);
     67    WorkQueue& workQueue() const { return m_queue; }
    6668
    6769    // IPC::Connection::ThreadMessageReceiver
     
    8688    CFDictionaryRef ioSurfacePixelBufferCreationOptions(IOSurfaceRef);
    8789
    88     GPUConnectionToWebProcess& m_gpuConnectionToWebProcess;
    89 
    90     mutable Lock m_lock;
    91     HashMap<RTCDecoderIdentifier, webrtc::LocalDecoder> m_decoders WTF_GUARDED_BY_LOCK(m_lock); // Only modified on the libWebRTCCodecsQueue but may get accessed from the main thread.
    92     HashMap<RTCEncoderIdentifier, webrtc::LocalEncoder> m_encoders WTF_GUARDED_BY_LOCK(m_lock); // Only modified on the libWebRTCCodecsQueue but may get accessed from the main thread.
    9390
    9491    Ref<WorkQueue> m_queue;
     92    HashMap<RTCDecoderIdentifier, webrtc::LocalDecoder> m_decoders WTF_GUARDED_BY_LOCK(workQueue());
     93    HashMap<RTCEncoderIdentifier, webrtc::LocalEncoder> m_encoders WTF_GUARDED_BY_LOCK(workQueue());
     94    std::atomic<bool> m_hasEncodersOrDecoders { false };
    9595};
    9696
  • branches/safari-613.2.7.1-branch/Source/WebKit/GPUProcess/webrtc/LibWebRTCCodecsProxy.mm

    r286838 r293270  
    3131#import "GPUConnectionToWebProcess.h"
    3232#import "GPUProcess.h"
     33#import "IPCTester.h"
    3334#import "LibWebRTCCodecsMessages.h"
    3435#import "LibWebRTCCodecsProxyMessages.h"
     
    4445namespace WebKit {
    4546
    46 LibWebRTCCodecsProxy::LibWebRTCCodecsProxy(GPUConnectionToWebProcess& connection)
    47     : m_gpuConnectionToWebProcess(connection)
    48     , m_queue(connection.gpuProcess().libWebRTCCodecsQueue())
    49 {
    50     m_gpuConnectionToWebProcess.connection().addThreadMessageReceiver(Messages::LibWebRTCCodecsProxy::messageReceiverName(), this);
    51 }
    52 
    53 LibWebRTCCodecsProxy::~LibWebRTCCodecsProxy()
    54 {
    55 }
    56 
    57 void LibWebRTCCodecsProxy::dispatchToThread(Function<void()>&& function)
    58 {
    59     m_queue->dispatch(WTFMove(function));
    60 }
    61 
    62 void LibWebRTCCodecsProxy::close()
    63 {
    64     m_gpuConnectionToWebProcess.connection().removeThreadMessageReceiver(Messages::LibWebRTCCodecsProxy::messageReceiverName());
    65 
    66     dispatchToThread([this, protectedThis = Ref { *this }] {
    67         Locker locker { m_lock };
     47Ref<LibWebRTCCodecsProxy> LibWebRTCCodecsProxy::create(GPUConnectionToWebProcess& webProcessConnection)
     48{
     49    auto instance = adoptRef(*new LibWebRTCCodecsProxy(webProcessConnection));
     50    instance->initialize();
     51    return instance;
     52}
     53
     54LibWebRTCCodecsProxy::LibWebRTCCodecsProxy(GPUConnectionToWebProcess& webProcessConnection)
     55    : m_connection(webProcessConnection.connection())
     56    , m_queue(webProcessConnection.gpuProcess().libWebRTCCodecsQueue())
     57    , m_resourceOwner(webProcessConnection.webProcessIdentity())
     58{
     59}
     60
     61LibWebRTCCodecsProxy::~LibWebRTCCodecsProxy() = default;
     62
     63void LibWebRTCCodecsProxy::stopListeningForIPC(Ref<LibWebRTCCodecsProxy>&& refFromConnection)
     64{
     65    m_connection->removeThreadMessageReceiver(Messages::LibWebRTCCodecsProxy::messageReceiverName());
     66
     67    dispatchToThread([this, protectedThis = WTFMove(refFromConnection)] {
     68        assertIsCurrent(workQueue());
    6869        auto decoders = WTFMove(m_decoders);
    6970        for (auto decoder : decoders.values())
     
    7576}
    7677
    77 static Function<void(CVPixelBufferRef pixelBuffer, uint32_t timeStampNs, uint32_t timeStamp)> createDecoderCallback(RTCDecoderIdentifier identifier, GPUConnectionToWebProcess& gpuConnectionToWebProcess)
    78 {
    79     return [connection = Ref { gpuConnectionToWebProcess.connection() }, resourceOwner = gpuConnectionToWebProcess.webProcessIdentity(), identifier] (CVPixelBufferRef pixelBuffer, uint32_t timeStampNs, uint32_t timeStamp) {
    80         if (auto sample = WebCore::RemoteVideoSample::create(pixelBuffer, MediaTime(timeStampNs, 1))) {
    81             if (resourceOwner)
    82                 sample->setOwnershipIdentity(resourceOwner);
    83             connection->send(Messages::LibWebRTCCodecs::CompletedDecoding { identifier, timeStamp, *sample }, 0);
    84         }
     78void LibWebRTCCodecsProxy::initialize()
     79{
     80    m_connection->addThreadMessageReceiver(Messages::LibWebRTCCodecsProxy::messageReceiverName(), this);
     81}
     82
     83void LibWebRTCCodecsProxy::dispatchToThread(Function<void()>&& function)
     84{
     85    m_queue->dispatch(WTFMove(function));
     86}
     87
     88auto LibWebRTCCodecsProxy::createDecoderCallback(RTCDecoderIdentifier identifier)
     89{
     90    return [identifier, connection = m_connection, resourceOwner = m_resourceOwner] (CVPixelBufferRef pixelBuffer, uint32_t timeStampNs, uint32_t timeStamp) mutable {
     91        auto sample = WebCore::MediaSampleAVFObjC::createImageSample(pixelBuffer, WebCore::MediaSample::VideoRotation::None, false, MediaTime(timeStampNs, 1), { });
     92        if (!sample)
     93            return;
     94        if (resourceOwner)
     95            sample->setOwnershipIdentity(resourceOwner);
     96        connection->send(Messages::LibWebRTCCodecs::CompletedDecoding { identifier, timeStamp, *sample }, 0);
    8597    };
    8698}
     
    88100void LibWebRTCCodecsProxy::createH264Decoder(RTCDecoderIdentifier identifier)
    89101{
    90     ASSERT(!isMainRunLoop());
    91     Locker locker { m_lock };
    92     ASSERT(!m_decoders.contains(identifier));
    93     m_decoders.add(identifier, webrtc::createLocalH264Decoder(makeBlockPtr(createDecoderCallback(identifier, m_gpuConnectionToWebProcess)).get()));
     102    assertIsCurrent(workQueue());
     103    auto result = m_decoders.add(identifier, webrtc::createLocalH264Decoder(makeBlockPtr(createDecoderCallback(identifier)).get()));
     104    ASSERT_UNUSED(result, result.isNewEntry || isTestingIPC());
     105    m_hasEncodersOrDecoders = true;
    94106}
    95107
    96108void LibWebRTCCodecsProxy::createH265Decoder(RTCDecoderIdentifier identifier)
    97109{
    98     ASSERT(!isMainRunLoop());
    99     Locker locker { m_lock };
    100     ASSERT(!m_decoders.contains(identifier));
    101     m_decoders.add(identifier, webrtc::createLocalH265Decoder(makeBlockPtr(createDecoderCallback(identifier, m_gpuConnectionToWebProcess)).get()));
     110    assertIsCurrent(workQueue());
     111    auto result = m_decoders.add(identifier, webrtc::createLocalH265Decoder(makeBlockPtr(createDecoderCallback(identifier)).get()));
     112    ASSERT_UNUSED(result, result.isNewEntry || isTestingIPC());
     113    m_hasEncodersOrDecoders = true;
    102114}
    103115
    104116void LibWebRTCCodecsProxy::createVP9Decoder(RTCDecoderIdentifier identifier)
    105117{
    106     ASSERT(!isMainRunLoop());
    107     Locker locker { m_lock };
    108     ASSERT(!m_decoders.contains(identifier));
    109     m_decoders.add(identifier, webrtc::createLocalVP9Decoder(makeBlockPtr(createDecoderCallback(identifier, m_gpuConnectionToWebProcess)).get()));
     118    assertIsCurrent(workQueue());
     119    auto result = m_decoders.add(identifier, webrtc::createLocalVP9Decoder(makeBlockPtr(createDecoderCallback(identifier)).get()));
     120    ASSERT_UNUSED(result, result.isNewEntry || isTestingIPC());
     121    m_hasEncodersOrDecoders = true;
    110122}
    111123
    112124void LibWebRTCCodecsProxy::releaseDecoder(RTCDecoderIdentifier identifier)
    113125{
    114     ASSERT(!isMainRunLoop());
    115     Locker locker { m_lock };
    116     ASSERT(m_decoders.contains(identifier));
    117     if (auto decoder = m_decoders.take(identifier))
    118         webrtc::releaseLocalDecoder(decoder);
    119 }
    120 
    121 // For performance reasons, this function accesses m_decoders without locking. This is safe because this function runs on the libWebRTCCodecsQueue
    122 // and m_decoders only get modified on this queue.
    123 void LibWebRTCCodecsProxy::decodeFrame(RTCDecoderIdentifier identifier, uint32_t timeStamp, const IPC::DataReference& data) WTF_IGNORES_THREAD_SAFETY_ANALYSIS
    124 {
    125     ASSERT(!isMainRunLoop());
    126     ASSERT(m_decoders.contains(identifier));
     126    assertIsCurrent(workQueue());
     127    auto decoder = m_decoders.take(identifier);
     128    if (!decoder) {
     129        ASSERT_IS_TESTING_IPC();
     130        return;
     131    }
     132    webrtc::releaseLocalDecoder(decoder);
     133    m_hasEncodersOrDecoders = !m_encoders.isEmpty() || !m_decoders.isEmpty();}
     134}
     135
     136void LibWebRTCCodecsProxy::decodeFrame(RTCDecoderIdentifier identifier, uint32_t timeStamp, const IPC::DataReference& data)
     137{
     138    assertIsCurrent(workQueue());
    127139    auto decoder = m_decoders.get(identifier);
    128     if (!decoder)
    129         return;
    130 
     140    if (!decoder) {
     141        ASSERT_IS_TESTING_IPC();
     142        return;
     143    }
    131144    if (webrtc::decodeFrame(decoder, timeStamp, data.data(), data.size()))
    132         m_gpuConnectionToWebProcess.connection().send(Messages::LibWebRTCCodecs::FailedDecoding { identifier }, 0);
    133 }
    134 
    135 // For performance reasons, this function accesses m_decoders without locking. This is safe because this function runs on the libWebRTCCodecsQueue
    136 // and m_decoders only get modified on this queue.
    137 void LibWebRTCCodecsProxy::setFrameSize(RTCDecoderIdentifier identifier, uint16_t width, uint16_t height) WTF_IGNORES_THREAD_SAFETY_ANALYSIS
    138 {
    139     ASSERT(!isMainRunLoop());
    140     ASSERT(m_decoders.contains(identifier));
     145        m_connection->send(Messages::LibWebRTCCodecs::FailedDecoding { identifier }, 0);
     146}
     147
     148void LibWebRTCCodecsProxy::setFrameSize(RTCDecoderIdentifier identifier, uint16_t width, uint16_t height)
     149{
     150    assertIsCurrent(workQueue());
    141151    auto decoder = m_decoders.get(identifier);
    142     if (!decoder)
    143         return;
    144 
     152    if (!decoder) {
     153        ASSERT_IS_TESTING_IPC();
     154        return;
     155    }
    145156    webrtc::setDecoderFrameSize(decoder, width, height);
    146157}
     
    148159void LibWebRTCCodecsProxy::createEncoder(RTCEncoderIdentifier identifier, const String& formatName, const Vector<std::pair<String, String>>& parameters, bool useLowLatency)
    149160{
    150     ASSERT(!isMainRunLoop());
    151     Locker locker { m_lock };
    152     ASSERT(!m_encoders.contains(identifier));
    153 
     161    assertIsCurrent(workQueue());
    154162    std::map<std::string, std::string> rtcParameters;
    155163    for (auto& parameter : parameters)
    156164        rtcParameters.emplace(parameter.first.utf8().data(), parameter.second.utf8().data());
    157165
    158     auto* encoder = webrtc::createLocalEncoder(webrtc::SdpVideoFormat { formatName.utf8().data(), rtcParameters }, makeBlockPtr([connection = Ref { m_gpuConnectionToWebProcess.connection() }, identifier](const uint8_t* buffer, size_t size, const webrtc::WebKitEncodedFrameInfo& info) {
     166    auto* encoder = webrtc::createLocalEncoder(webrtc::SdpVideoFormat { formatName.utf8().data(), rtcParameters }, makeBlockPtr([connection = m_connection, identifier](const uint8_t* buffer, size_t size, const webrtc::WebKitEncodedFrameInfo& info) {
    159167        connection->send(Messages::LibWebRTCCodecs::CompletedEncoding { identifier, IPC::DataReference { buffer, size }, info }, 0);
    160168    }).get());
    161169    webrtc::setLocalEncoderLowLatency(encoder, useLowLatency);
    162     m_encoders.add(identifier, encoder);
     170    auto result = m_encoders.add(identifier, Encoder { encoder, nullptr });
     171    ASSERT_UNUSED(result, result.isNewEntry || isTestingIPC());
     172    m_hasEncodersOrDecoders = true;
    163173}
    164174
    165175void LibWebRTCCodecsProxy::releaseEncoder(RTCEncoderIdentifier identifier)
    166176{
    167     ASSERT(!isMainRunLoop());
    168     Locker locker { m_lock };
    169     ASSERT(m_encoders.contains(identifier));
    170     if (auto encoder = m_encoders.take(identifier))
    171         webrtc::releaseLocalEncoder(encoder);
    172 }
    173 
    174 // For performance reasons, this function accesses m_encoders without locking. This is safe because this function runs on the libWebRTCCodecsQueue
    175 // and m_encoders only get modified on this queue.
    176 void LibWebRTCCodecsProxy::initializeEncoder(RTCEncoderIdentifier identifier, uint16_t width, uint16_t height, unsigned startBitrate, unsigned maxBitrate, unsigned minBitrate, uint32_t maxFramerate) WTF_IGNORES_THREAD_SAFETY_ANALYSIS
    177 {
    178     ASSERT(!isMainRunLoop());
    179     ASSERT(m_encoders.contains(identifier));
    180     auto encoder = m_encoders.get(identifier);
    181     if (!encoder)
    182         return;
    183 
    184     webrtc::initializeLocalEncoder(encoder, width, height, startBitrate, maxBitrate, minBitrate, maxFramerate);
     177    assertIsCurrent(workQueue());
     178    auto encoder = m_encoders.take(identifier);
     179    if (!encoder.webrtcEncoder) {
     180        ASSERT_IS_TESTING_IPC();
     181        return;
     182    }
     183    webrtc::releaseLocalEncoder(encoder.webrtcEncoder);
     184    m_hasEncodersOrDecoders = !m_encoders.isEmpty() || !m_decoders.isEmpty();
     185}
     186
     187void LibWebRTCCodecsProxy::initializeEncoder(RTCEncoderIdentifier identifier, uint16_t width, uint16_t height, unsigned startBitrate, unsigned maxBitrate, unsigned minBitrate, uint32_t maxFramerate)
     188{
     189    assertIsCurrent(workQueue());
     190    auto* encoder = findEncoder(identifier);
     191    if (!encoder) {
     192        ASSERT_IS_TESTING_IPC();
     193        return;
     194    }
     195    webrtc::initializeLocalEncoder(encoder->webrtcEncoder, width, height, startBitrate, maxBitrate, minBitrate, maxFramerate);
    185196}
    186197
     
    201212}
    202213
    203 // For performance reasons, this function accesses m_encoders without locking. This is safe because this function runs on the libWebRTCCodecsQueue
    204 // and m_encoders only get modified on this queue.
    205 void LibWebRTCCodecsProxy::encodeFrame(RTCEncoderIdentifier identifier, WebCore::RemoteVideoSample&& sample, uint32_t timeStamp, bool shouldEncodeAsKeyFrame) WTF_IGNORES_THREAD_SAFETY_ANALYSIS
    206 {
    207     ASSERT(!isMainRunLoop());
     214void LibWebRTCCodecsProxy::encodeFrame(RTCEncoderIdentifier identifier, WebCore::RemoteVideoSample&& sample, uint32_t timeStamp, bool shouldEncodeAsKeyFrame)
     215{
     216    assertIsCurrent(workQueue());
    208217    ASSERT(m_encoders.contains(identifier));
    209218    auto encoder = m_encoders.get(identifier);
     
    221230}
    222231
    223 // For performance reasons, this function accesses m_encoders without locking. This is safe because this function runs on the libWebRTCCodecsQueue
    224 // and m_encoders only get modified on this queue.
    225 void LibWebRTCCodecsProxy::setEncodeRates(RTCEncoderIdentifier identifier, uint32_t bitRate, uint32_t frameRate) WTF_IGNORES_THREAD_SAFETY_ANALYSIS
    226 {
    227     ASSERT(!isMainRunLoop());
     232void LibWebRTCCodecsProxy::setEncodeRates(RTCEncoderIdentifier identifier, uint32_t bitRate, uint32_t frameRate)
     233{
     234    assertIsCurrent(workQueue());
    228235    auto encoder = m_encoders.get(identifier);
    229236    if (!encoder)
     
    235242bool LibWebRTCCodecsProxy::allowsExitUnderMemoryPressure() const
    236243{
    237     ASSERT(isMainRunLoop());
    238     Locker locker { m_lock };
    239     return m_encoders.isEmpty() && m_decoders.isEmpty();
     244    assertIsMainRunLoop();
     245    return !m_hasEncodersOrDecoders;
    240246}
    241247
Note: See TracChangeset for help on using the changeset viewer.