UserMediaCaptureManager.cpp   [plain text]


/*
 * Copyright (C) 2017 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 met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "UserMediaCaptureManager.h"

#if PLATFORM(COCOA) && ENABLE(MEDIA_STREAM)

#include "SharedRingBufferStorage.h"
#include "UserMediaCaptureManagerMessages.h"
#include "UserMediaCaptureManagerProxyMessages.h"
#include "WebCoreArgumentCoders.h"
#include "WebProcess.h"
#include "WebProcessCreationParameters.h"
#include <WebCore/CaptureDevice.h>
#include <WebCore/MediaConstraints.h>
#include <WebCore/RealtimeMediaSourceCenter.h>
#include <WebCore/WebAudioBufferList.h>
#include <WebCore/WebAudioSourceProviderAVFObjC.h>

using namespace WebCore;

namespace WebKit {

static uint64_t nextSessionID()
{
    static uint64_t nextID = 0;
    return ++nextID;
}

class UserMediaCaptureManager::Source : public RealtimeMediaSource {
public:
    Source(const String& sourceID, Type type, const String& name, uint64_t id, UserMediaCaptureManager& manager)
        : RealtimeMediaSource(sourceID, type, name)
        , m_id(id)
        , m_manager(manager)
        , m_ringBuffer(makeUniqueRef<SharedRingBufferStorage>(nullptr))
    {
    }

    ~Source()
    {
        storage().invalidate();
    }

    SharedRingBufferStorage& storage() { return static_cast<SharedRingBufferStorage&>(m_ringBuffer.storage()); }
    const RealtimeMediaSourceCapabilities& capabilities() const final {
        if (!m_capabilities)
            m_capabilities = m_manager.capabilities(m_id);
        return m_capabilities.value();
    }

    const RealtimeMediaSourceSettings& settings() const final { return m_settings; }
    void setSettings(RealtimeMediaSourceSettings&& settings)
    {
        m_settings = WTFMove(settings);
        settingsDidChange();
    }

    const CAAudioStreamDescription& description() const { return m_description; }
    void setStorage(const SharedMemory::Handle& handle, const WebCore::CAAudioStreamDescription& description, uint64_t numberOfFrames)
    {
        m_description = description;

        if (handle.isNull()) {
            m_ringBuffer.deallocate();
            storage().setReadOnly(false);
            storage().setStorage(nullptr);
            return;
        }

        RefPtr<SharedMemory> memory = SharedMemory::map(handle, SharedMemory::Protection::ReadOnly);
        storage().setStorage(WTFMove(memory));
        storage().setReadOnly(true);

        m_ringBuffer.allocate(description, numberOfFrames);
    }

    void setRingBufferFrameBounds(uint64_t startFrame, uint64_t endFrame)
    {
        m_ringBuffer.setCurrentFrameBounds(startFrame, endFrame);
    }

    void audioSamplesAvailable(MediaTime time, uint64_t numberOfFrames)
    {
        WebAudioBufferList audioData(m_description, numberOfFrames);
        m_ringBuffer.fetch(audioData.list(), numberOfFrames, time.timeValue());

        RealtimeMediaSource::audioSamplesAvailable(time, audioData, m_description, numberOfFrames);
    }

    void applyConstraintsSucceeded(const WebCore::RealtimeMediaSourceSettings& settings)
    {
        auto callbacks = m_pendingApplyConstraintsCallbacks.takeFirst();
        setSettings(WebCore::RealtimeMediaSourceSettings(settings));
        callbacks.successHandler();
    }

    void applyConstraintsFailed(const String& failedConstraint, const String& errorMessage)
    {
        auto callbacks = m_pendingApplyConstraintsCallbacks.takeFirst();
        callbacks.failureHandler(failedConstraint, errorMessage);
    }

private:
    void startProducingData() final { m_manager.startProducingData(m_id); }
    void stopProducingData() final { m_manager.stopProducingData(m_id); }
    bool isCaptureSource() const final { return true; }

    // RealtimeMediaSource
    void beginConfiguration() final { }
    void commitConfiguration() final { }

    void applyConstraints(const WebCore::MediaConstraints& constraints, SuccessHandler&& successHandler, FailureHandler&& failureHandler) final {
        m_manager.applyConstraints(m_id, constraints);
        m_pendingApplyConstraintsCallbacks.append({ WTFMove(successHandler), WTFMove(failureHandler)});
    }

    uint64_t m_id;
    UserMediaCaptureManager& m_manager;
    mutable std::optional<RealtimeMediaSourceCapabilities> m_capabilities;
    RealtimeMediaSourceSettings m_settings;
    CAAudioStreamDescription m_description;
    CARingBuffer m_ringBuffer;

    struct ApplyConstraintsCallback {
        SuccessHandler successHandler;
        FailureHandler failureHandler;
    };
    Deque<ApplyConstraintsCallback> m_pendingApplyConstraintsCallbacks;
};

UserMediaCaptureManager::UserMediaCaptureManager(WebProcess* process)
    : m_process(*process)
{
    m_process.addMessageReceiver(Messages::UserMediaCaptureManager::messageReceiverName(), *this);
}

UserMediaCaptureManager::~UserMediaCaptureManager()
{
    RealtimeMediaSourceCenter::singleton().unsetAudioFactory(*this);
    m_process.removeMessageReceiver(Messages::UserMediaCaptureManager::messageReceiverName());
}

const char* UserMediaCaptureManager::supplementName()
{
    return "UserMediaCaptureManager";
}

void UserMediaCaptureManager::initialize(const WebProcessCreationParameters& parameters)
{
    if (parameters.shouldCaptureAudioInUIProcess)
        RealtimeMediaSourceCenter::singleton().setAudioFactory(*this);
}

WebCore::CaptureSourceOrError UserMediaCaptureManager::createCaptureSource(const String& deviceID, WebCore::RealtimeMediaSource::Type sourceType, const WebCore::MediaConstraints* constraints)
{
    if (!constraints)
        return { };

    uint64_t id = nextSessionID();
    bool succeeded;

    RealtimeMediaSourceSettings settings;
    String errorMessage;
    if (!m_process.sendSync(Messages::UserMediaCaptureManagerProxy::CreateMediaSourceForCaptureDeviceWithConstraints(id, deviceID, sourceType, *constraints), Messages::UserMediaCaptureManagerProxy::CreateMediaSourceForCaptureDeviceWithConstraints::Reply(succeeded, errorMessage, settings), 0))
        return WTFMove(errorMessage);

    auto source = adoptRef(*new Source(String::number(id), sourceType, emptyString(), id, *this));
    source->setSettings(WTFMove(settings));
    m_sources.set(id, source.copyRef());
    return WebCore::CaptureSourceOrError(WTFMove(source));
}

void UserMediaCaptureManager::sourceStopped(uint64_t id)
{
    ASSERT(m_sources.contains(id));
    m_sources.get(id)->stop();
}

void UserMediaCaptureManager::captureFailed(uint64_t id)
{
    ASSERT(m_sources.contains(id));
    m_sources.get(id)->captureFailed();
}

void UserMediaCaptureManager::sourceMutedChanged(uint64_t id, bool muted)
{
    ASSERT(m_sources.contains(id));
    m_sources.get(id)->setMuted(muted);
}

void UserMediaCaptureManager::sourceSettingsChanged(uint64_t id, const RealtimeMediaSourceSettings& settings)
{
    ASSERT(m_sources.contains(id));
    m_sources.get(id)->setSettings(RealtimeMediaSourceSettings(settings));
}

void UserMediaCaptureManager::storageChanged(uint64_t id, const SharedMemory::Handle& handle, const WebCore::CAAudioStreamDescription& description, uint64_t numberOfFrames)
{
    ASSERT(m_sources.contains(id));
    m_sources.get(id)->setStorage(handle, description, numberOfFrames);
}

void UserMediaCaptureManager::ringBufferFrameBoundsChanged(uint64_t id, uint64_t startFrame, uint64_t endFrame)
{
    ASSERT(m_sources.contains(id));
    m_sources.get(id)->setRingBufferFrameBounds(startFrame, endFrame);
}

void UserMediaCaptureManager::audioSamplesAvailable(uint64_t id, MediaTime time, uint64_t numberOfFrames, uint64_t startFrame, uint64_t endFrame)
{
    ASSERT(m_sources.contains(id));
    auto& source = *m_sources.get(id);
    source.setRingBufferFrameBounds(startFrame, endFrame);
    source.audioSamplesAvailable(time, numberOfFrames);
}

void UserMediaCaptureManager::startProducingData(uint64_t id)
{
    m_process.send(Messages::UserMediaCaptureManagerProxy::StartProducingData(id), 0);
}

void UserMediaCaptureManager::stopProducingData(uint64_t id)
{
    m_process.send(Messages::UserMediaCaptureManagerProxy::StopProducingData(id), 0);
}

WebCore::RealtimeMediaSourceCapabilities UserMediaCaptureManager::capabilities(uint64_t id)
{
    WebCore::RealtimeMediaSourceCapabilities capabilities;
    m_process.sendSync(Messages::UserMediaCaptureManagerProxy::Capabilities(id), Messages::UserMediaCaptureManagerProxy::Capabilities::Reply(capabilities), 0);
    return capabilities;
}

void UserMediaCaptureManager::setMuted(uint64_t id, bool muted)
{
    m_process.send(Messages::UserMediaCaptureManagerProxy::SetMuted(id, muted), 0);
}

void UserMediaCaptureManager::applyConstraints(uint64_t id, const WebCore::MediaConstraints& constraints)
{
    m_process.send(Messages::UserMediaCaptureManagerProxy::ApplyConstraints(id, constraints), 0);
}

void UserMediaCaptureManager::applyConstraintsSucceeded(uint64_t id, const WebCore::RealtimeMediaSourceSettings& settings)
{
    ASSERT(m_sources.contains(id));
    auto& source = *m_sources.get(id);
    source.applyConstraintsSucceeded(settings);
}

void UserMediaCaptureManager::applyConstraintsFailed(uint64_t id, const String& failedConstraint, const String& message)
{
    ASSERT(m_sources.contains(id));
    auto& source = *m_sources.get(id);
    source.applyConstraintsFailed(failedConstraint, message);
}

}

#endif