Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1035)

Unified Diff: ppapi/proxy/audio_output_resource.cc

Issue 2755613002: Support audio output device enumeration and selection in PPAPI (Closed)
Patch Set: Fix format, Rebase Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « ppapi/proxy/audio_output_resource.h ('k') | ppapi/proxy/interface_list.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « ppapi/proxy/audio_output_resource.h ('k') | ppapi/proxy/interface_list.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698