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 |