| Index: ppapi/proxy/audio_output_resource.cc
|
| diff --git a/ppapi/proxy/audio_output_resource.cc b/ppapi/proxy/audio_output_resource.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..6eeb0f08f72e14f67bc510caf0d53fa02b540d30
|
| --- /dev/null
|
| +++ b/ppapi/proxy/audio_output_resource.cc
|
| @@ -0,0 +1,318 @@
|
| +// Copyright (c) 2017 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "ppapi/proxy/audio_output_resource.h"
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/logging.h"
|
| +#include "base/numerics/safe_conversions.h"
|
| +#include "ipc/ipc_platform_file.h"
|
| +#include "media/base/audio_bus.h"
|
| +#include "media/base/audio_parameters.h"
|
| +#include "ppapi/c/pp_errors.h"
|
| +#include "ppapi/proxy/ppapi_messages.h"
|
| +#include "ppapi/proxy/resource_message_params.h"
|
| +#include "ppapi/proxy/serialized_handle.h"
|
| +#include "ppapi/shared_impl/ppapi_globals.h"
|
| +#include "ppapi/shared_impl/ppb_audio_config_shared.h"
|
| +#include "ppapi/shared_impl/resource_tracker.h"
|
| +#include "ppapi/shared_impl/tracked_callback.h"
|
| +#include "ppapi/thunk/enter.h"
|
| +#include "ppapi/thunk/ppb_audio_config_api.h"
|
| +
|
| +namespace ppapi {
|
| +namespace proxy {
|
| +
|
| +AudioOutputResource::AudioOutputResource(Connection connection,
|
| + PP_Instance instance)
|
| + : PluginResource(connection, instance),
|
| + open_state_(BEFORE_OPEN),
|
| + playing_(false),
|
| + shared_memory_size_(0),
|
| + audio_output_callback_(NULL),
|
| + user_data_(NULL),
|
| + enumeration_helper_(this),
|
| + bytes_per_second_(0),
|
| + sample_frame_count_(0),
|
| + client_buffer_size_bytes_(0) {
|
| + SendCreate(RENDERER, PpapiHostMsg_AudioOutput_Create());
|
| +}
|
| +
|
| +AudioOutputResource::~AudioOutputResource() {
|
| + Close();
|
| +}
|
| +
|
| +thunk::PPB_AudioOutput_API* AudioOutputResource::AsPPB_AudioOutput_API() {
|
| + return this;
|
| +}
|
| +
|
| +void AudioOutputResource::OnReplyReceived(
|
| + const ResourceMessageReplyParams& params,
|
| + const IPC::Message& msg) {
|
| + if (!enumeration_helper_.HandleReply(params, msg))
|
| + PluginResource::OnReplyReceived(params, msg);
|
| +}
|
| +
|
| +int32_t AudioOutputResource::EnumerateDevices(
|
| + const PP_ArrayOutput& output,
|
| + scoped_refptr<TrackedCallback> callback) {
|
| + return enumeration_helper_.EnumerateDevices(output, callback);
|
| +}
|
| +
|
| +int32_t AudioOutputResource::MonitorDeviceChange(
|
| + PP_MonitorDeviceChangeCallback callback,
|
| + void* user_data) {
|
| + return enumeration_helper_.MonitorDeviceChange(callback, user_data);
|
| +}
|
| +
|
| +int32_t AudioOutputResource::Open(
|
| + PP_Resource device_ref,
|
| + PP_Resource config,
|
| + PPB_AudioOutput_Callback audio_output_callback,
|
| + void* user_data,
|
| + scoped_refptr<TrackedCallback> callback) {
|
| + return CommonOpen(device_ref, config, audio_output_callback, user_data,
|
| + callback);
|
| +}
|
| +
|
| +PP_Resource AudioOutputResource::GetCurrentConfig() {
|
| + // AddRef for the caller.
|
| + if (config_.get())
|
| + PpapiGlobals::Get()->GetResourceTracker()->AddRefResource(config_);
|
| + return config_;
|
| +}
|
| +
|
| +PP_Bool AudioOutputResource::StartPlayback() {
|
| + if (open_state_ == CLOSED || (open_state_ == BEFORE_OPEN &&
|
| + !TrackedCallback::IsPending(open_callback_))) {
|
| + return PP_FALSE;
|
| + }
|
| + if (playing_)
|
| + return PP_TRUE;
|
| +
|
| + playing_ = true;
|
| +
|
| + StartThread();
|
| +
|
| + Post(RENDERER, PpapiHostMsg_AudioOutput_StartOrStop(true));
|
| + return PP_TRUE;
|
| +}
|
| +
|
| +PP_Bool AudioOutputResource::StopPlayback() {
|
| + if (open_state_ == CLOSED)
|
| + return PP_FALSE;
|
| + if (!playing_)
|
| + return PP_TRUE;
|
| +
|
| + // If the audio output device hasn't been opened, set |playing_| to false and
|
| + // return directly.
|
| + if (open_state_ == BEFORE_OPEN) {
|
| + playing_ = false;
|
| + return PP_TRUE;
|
| + }
|
| +
|
| + Post(RENDERER, PpapiHostMsg_AudioOutput_StartOrStop(false));
|
| +
|
| + StopThread();
|
| + playing_ = false;
|
| +
|
| + return PP_TRUE;
|
| +}
|
| +
|
| +void AudioOutputResource::Close() {
|
| + if (open_state_ == CLOSED)
|
| + return;
|
| +
|
| + open_state_ = CLOSED;
|
| + Post(RENDERER, PpapiHostMsg_AudioOutput_Close());
|
| + StopThread();
|
| +
|
| + if (TrackedCallback::IsPending(open_callback_))
|
| + open_callback_->PostAbort();
|
| +}
|
| +
|
| +void AudioOutputResource::LastPluginRefWasDeleted() {
|
| + enumeration_helper_.LastPluginRefWasDeleted();
|
| +}
|
| +
|
| +void AudioOutputResource::OnPluginMsgOpenReply(
|
| + const ResourceMessageReplyParams& params) {
|
| + if (open_state_ == BEFORE_OPEN && params.result() == PP_OK) {
|
| + IPC::PlatformFileForTransit socket_handle_for_transit =
|
| + IPC::InvalidPlatformFileForTransit();
|
| + params.TakeSocketHandleAtIndex(0, &socket_handle_for_transit);
|
| + base::SyncSocket::Handle socket_handle =
|
| + IPC::PlatformFileForTransitToPlatformFile(socket_handle_for_transit);
|
| + CHECK(socket_handle != base::SyncSocket::kInvalidHandle);
|
| +
|
| + SerializedHandle serialized_shared_memory_handle =
|
| + params.TakeHandleOfTypeAtIndex(1, SerializedHandle::SHARED_MEMORY);
|
| + CHECK(serialized_shared_memory_handle.IsHandleValid());
|
| +
|
| + open_state_ = OPENED;
|
| + SetStreamInfo(serialized_shared_memory_handle.shmem(),
|
| + serialized_shared_memory_handle.size(), socket_handle);
|
| + } else {
|
| + playing_ = false;
|
| + }
|
| +
|
| + // The callback may have been aborted by Close().
|
| + if (TrackedCallback::IsPending(open_callback_))
|
| + open_callback_->Run(params.result());
|
| +}
|
| +
|
| +void AudioOutputResource::SetStreamInfo(
|
| + base::SharedMemoryHandle shared_memory_handle,
|
| + size_t shared_memory_size,
|
| + base::SyncSocket::Handle socket_handle) {
|
| + socket_.reset(new base::CancelableSyncSocket(socket_handle));
|
| + shared_memory_.reset(new base::SharedMemory(shared_memory_handle, false));
|
| + shared_memory_size_ = shared_memory_size;
|
| + DCHECK(!shared_memory_->memory());
|
| +
|
| + // If we fail to map the shared memory into the caller's address space we
|
| + // might as well fail here since nothing will work if this is the case.
|
| + CHECK(shared_memory_->Map(shared_memory_size_));
|
| +
|
| + // Create a new audio bus and wrap the audio data section in shared memory.
|
| + media::AudioOutputBuffer* buffer =
|
| + static_cast<media::AudioOutputBuffer*>(shared_memory_->memory());
|
| + audio_bus_ = media::AudioBus::WrapMemory(kAudioOutputChannels,
|
| + sample_frame_count_, buffer->audio);
|
| +
|
| + // Ensure that the size of the created audio bus matches the allocated
|
| + // size in shared memory.
|
| + // Example: DCHECK_EQ(8208 - 16, 8192) for |sample_frame_count_| = 2048.
|
| + const uint32_t audio_bus_size_bytes = media::AudioBus::CalculateMemorySize(
|
| + audio_bus_->channels(), audio_bus_->frames());
|
| + DCHECK_EQ(shared_memory_size_ - sizeof(media::AudioOutputBufferParameters),
|
| + audio_bus_size_bytes);
|
| +
|
| + // Setup integer audio buffer for user audio data
|
| + client_buffer_size_bytes_ = audio_bus_->frames() * audio_bus_->channels() *
|
| + kBitsPerAudioOutputSample / 8;
|
| + client_buffer_.reset(new uint8_t[client_buffer_size_bytes_]);
|
| +}
|
| +
|
| +void AudioOutputResource::StartThread() {
|
| + // Don't start the thread unless all our state is set up correctly.
|
| + if (!audio_output_callback_ || !socket_.get() || !shared_memory_->memory() ||
|
| + !audio_bus_.get() || !client_buffer_.get() || bytes_per_second_ == 0)
|
| + return;
|
| +
|
| + // Clear contents of shm buffer before starting audio thread. This will
|
| + // prevent a burst of static if for some reason the audio thread doesn't
|
| + // start up quickly enough.
|
| + memset(shared_memory_->memory(), 0, shared_memory_size_);
|
| + memset(client_buffer_.get(), 0, client_buffer_size_bytes_);
|
| +
|
| + DCHECK(!audio_output_thread_.get());
|
| + audio_output_thread_.reset(
|
| + new base::DelegateSimpleThread(this, "plugin_audio_output_thread"));
|
| + audio_output_thread_->Start();
|
| +}
|
| +
|
| +void AudioOutputResource::StopThread() {
|
| + // Shut down the socket to escape any hanging |Receive|s.
|
| + if (socket_.get())
|
| + socket_->Shutdown();
|
| + if (audio_output_thread_.get()) {
|
| + audio_output_thread_->Join();
|
| + audio_output_thread_.reset();
|
| + }
|
| +}
|
| +
|
| +void AudioOutputResource::Run() {
|
| + // The shared memory represents AudioOutputBufferParameters and the actual
|
| + // data buffer stored as an audio bus.
|
| + media::AudioOutputBuffer* buffer =
|
| + static_cast<media::AudioOutputBuffer*>(shared_memory_->memory());
|
| +
|
| + // This is a constantly increasing counter that is used to verify on the
|
| + // browser side that buffers are in sync.
|
| + uint32_t buffer_index = 0;
|
| +
|
| + while (true) {
|
| + int pending_data = 0;
|
| + size_t bytes_read = socket_->Receive(&pending_data, sizeof(pending_data));
|
| + if (bytes_read != sizeof(pending_data)) {
|
| + DCHECK_EQ(bytes_read, 0U);
|
| + break;
|
| + }
|
| + if (pending_data < 0)
|
| + break;
|
| +
|
| + {
|
| + base::TimeDelta delay =
|
| + base::TimeDelta::FromMicroseconds(buffer->params.delay);
|
| +
|
| + audio_output_callback_(client_buffer_.get(), client_buffer_size_bytes_,
|
| + delay.InSecondsF(), user_data_);
|
| + }
|
| +
|
| + // Deinterleave the audio data into the shared memory as floats.
|
| + audio_bus_->FromInterleaved(client_buffer_.get(), audio_bus_->frames(),
|
| + kBitsPerAudioOutputSample / 8);
|
| +
|
| + // Inform other side that we have read the data from the shared memory.
|
| + // Let the other end know which buffer we just filled. The buffer index is
|
| + // used to ensure the other end is getting the buffer it expects. For more
|
| + // details on how this works see AudioSyncReader::WaitUntilDataIsReady().
|
| + ++buffer_index;
|
| + size_t bytes_sent = socket_->Send(&buffer_index, sizeof(buffer_index));
|
| + if (bytes_sent != sizeof(buffer_index)) {
|
| + DCHECK_EQ(bytes_sent, 0U);
|
| + break;
|
| + }
|
| + }
|
| +}
|
| +
|
| +int32_t AudioOutputResource::CommonOpen(
|
| + PP_Resource device_ref,
|
| + PP_Resource config,
|
| + PPB_AudioOutput_Callback audio_output_callback,
|
| + void* user_data,
|
| + scoped_refptr<TrackedCallback> callback) {
|
| + std::string device_id;
|
| + // |device_id| remains empty if |device_ref| is 0, which means the default
|
| + // device.
|
| + if (device_ref != 0) {
|
| + thunk::EnterResourceNoLock<thunk::PPB_DeviceRef_API> enter_device_ref(
|
| + device_ref, true);
|
| + if (enter_device_ref.failed())
|
| + return PP_ERROR_BADRESOURCE;
|
| + device_id = enter_device_ref.object()->GetDeviceRefData().id;
|
| + }
|
| +
|
| + if (TrackedCallback::IsPending(open_callback_))
|
| + return PP_ERROR_INPROGRESS;
|
| + if (open_state_ != BEFORE_OPEN)
|
| + return PP_ERROR_FAILED;
|
| +
|
| + if (!audio_output_callback)
|
| + return PP_ERROR_BADARGUMENT;
|
| + thunk::EnterResourceNoLock<thunk::PPB_AudioConfig_API> enter_config(config,
|
| + true);
|
| + if (enter_config.failed())
|
| + return PP_ERROR_BADARGUMENT;
|
| +
|
| + config_ = config;
|
| + audio_output_callback_ = audio_output_callback;
|
| + user_data_ = user_data;
|
| + open_callback_ = callback;
|
| + bytes_per_second_ = kAudioOutputChannels * (kBitsPerAudioOutputSample / 8) *
|
| + enter_config.object()->GetSampleRate();
|
| + sample_frame_count_ = enter_config.object()->GetSampleFrameCount();
|
| +
|
| + PpapiHostMsg_AudioOutput_Open msg(
|
| + device_id, enter_config.object()->GetSampleRate(),
|
| + enter_config.object()->GetSampleFrameCount());
|
| + Call<PpapiPluginMsg_AudioOutput_OpenReply>(
|
| + RENDERER, msg,
|
| + base::Bind(&AudioOutputResource::OnPluginMsgOpenReply,
|
| + base::Unretained(this)));
|
| + return PP_OK_COMPLETIONPENDING;
|
| +}
|
| +} // namespace proxy
|
| +} // namespace ppapi
|
|
|