diff --git a/chrome/common/ppapi_utils.cc b/chrome/common/ppapi_utils.cc index 58fe135c7396e6..3e0563acd91d3d 100644 --- a/chrome/common/ppapi_utils.cc +++ b/chrome/common/ppapi_utils.cc @@ -7,6 +7,7 @@ #include "build/build_config.h" #include "ppapi/c/dev/ppb_audio_input_dev.h" +#include "ppapi/c/dev/ppb_audio_output_dev.h" #include "ppapi/c/dev/ppb_buffer_dev.h" #include "ppapi/c/dev/ppb_char_set_dev.h" #include "ppapi/c/dev/ppb_crypto_dev.h" diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn index b00ed864d1a7b4..19de47d2612c0c 100644 --- a/content/renderer/BUILD.gn +++ b/content/renderer/BUILD.gn @@ -792,6 +792,8 @@ target(link_target_type, "renderer") { "pepper/pepper_audio_encoder_host.h", "pepper/pepper_audio_input_host.cc", "pepper/pepper_audio_input_host.h", + "pepper/pepper_audio_output_host.cc", + "pepper/pepper_audio_output_host.h", "pepper/pepper_broker.cc", "pepper/pepper_broker.h", "pepper/pepper_browser_connection.cc", @@ -822,6 +824,8 @@ target(link_target_type, "renderer") { "pepper/pepper_platform_audio_input.h", "pepper/pepper_platform_audio_output.cc", "pepper/pepper_platform_audio_output.h", + "pepper/pepper_platform_audio_output_dev.cc", + "pepper/pepper_platform_audio_output_dev.h", "pepper/pepper_platform_camera_device.cc", "pepper/pepper_platform_camera_device.h", "pepper/pepper_platform_video_capture.cc", diff --git a/content/renderer/pepper/content_renderer_pepper_host_factory.cc b/content/renderer/pepper/content_renderer_pepper_host_factory.cc index 16eff2bccc97bf..3423b01e29f6b7 100644 --- a/content/renderer/pepper/content_renderer_pepper_host_factory.cc +++ b/content/renderer/pepper/content_renderer_pepper_host_factory.cc @@ -15,6 +15,7 @@ #include "content/public/renderer/content_renderer_client.h" #include "content/renderer/pepper/pepper_audio_encoder_host.h" #include "content/renderer/pepper/pepper_audio_input_host.h" +#include "content/renderer/pepper/pepper_audio_output_host.h" #include "content/renderer/pepper/pepper_camera_device_host.h" #include "content/renderer/pepper/pepper_compositor_host.h" #include "content/renderer/pepper/pepper_file_chooser_host.h" @@ -211,6 +212,9 @@ ContentRendererPepperHostFactory::CreateResourceHost( case PpapiHostMsg_AudioInput_Create::ID: return base::MakeUnique(host_, instance, resource); + case PpapiHostMsg_AudioOutput_Create::ID: + return base::MakeUnique(host_, instance, + resource); case PpapiHostMsg_FileChooser_Create::ID: return base::MakeUnique(host_, instance, resource); diff --git a/content/renderer/pepper/pepper_audio_controller.cc b/content/renderer/pepper/pepper_audio_controller.cc index 88d80f11bd02c4..759666ea3a7fb0 100644 --- a/content/renderer/pepper/pepper_audio_controller.cc +++ b/content/renderer/pepper/pepper_audio_controller.cc @@ -4,6 +4,7 @@ #include "content/renderer/pepper/pepper_audio_controller.h" +#include "content/renderer/pepper/pepper_audio_output_host.h" #include "content/renderer/pepper/pepper_plugin_instance_impl.h" #include "content/renderer/pepper/ppb_audio_impl.h" #include "content/renderer/render_frame_impl.h" @@ -27,15 +28,22 @@ void PepperAudioController::AddInstance(PPB_Audio_Impl* audio) { if (ppb_audios_.count(audio)) return; - if (ppb_audios_.empty()) { - RenderFrameImpl* render_frame = instance_->render_frame(); - if (render_frame) - render_frame->PepperStartsPlayback(instance_); - } + StartPlaybackIfFirstInstance(); ppb_audios_.insert(audio); } +void PepperAudioController::AddInstance(PepperAudioOutputHost* audio_output) { + if (!instance_) + return; + if (audio_output_hosts_.count(audio_output)) + return; + + StartPlaybackIfFirstInstance(); + + audio_output_hosts_.insert(audio_output); +} + void PepperAudioController::RemoveInstance(PPB_Audio_Impl* audio) { if (!instance_) return; @@ -44,8 +52,19 @@ void PepperAudioController::RemoveInstance(PPB_Audio_Impl* audio) { ppb_audios_.erase(audio); - if (ppb_audios_.empty()) - NotifyPlaybackStopsOnEmpty(); + StopPlaybackIfLastInstance(); +} + +void PepperAudioController::RemoveInstance( + PepperAudioOutputHost* audio_output) { + if (!instance_) + return; + if (!audio_output_hosts_.count(audio_output)) + return; + + audio_output_hosts_.erase(audio_output); + + StopPlaybackIfLastInstance(); } void PepperAudioController::SetVolume(double volume) { @@ -54,15 +73,19 @@ void PepperAudioController::SetVolume(double volume) { for (auto* ppb_audio : ppb_audios_) ppb_audio->SetVolume(volume); + + for (auto* audio_output_host : audio_output_hosts_) + audio_output_host->SetVolume(volume); } void PepperAudioController::OnPepperInstanceDeleted() { DCHECK(instance_); - if (!ppb_audios_.empty()) + if (!audio_output_hosts_.empty() || !ppb_audios_.empty()) NotifyPlaybackStopsOnEmpty(); ppb_audios_.clear(); + audio_output_hosts_.clear(); instance_ = nullptr; } @@ -74,4 +97,21 @@ void PepperAudioController::NotifyPlaybackStopsOnEmpty() { render_frame->PepperStopsPlayback(instance_); } +void PepperAudioController::StartPlaybackIfFirstInstance() { + DCHECK(instance_); + + if (audio_output_hosts_.empty() && ppb_audios_.empty()) { + RenderFrameImpl* render_frame = instance_->render_frame(); + if (render_frame) + render_frame->PepperStartsPlayback(instance_); + } +} + +void PepperAudioController::StopPlaybackIfLastInstance() { + DCHECK(instance_); + + if (audio_output_hosts_.empty() && ppb_audios_.empty()) + NotifyPlaybackStopsOnEmpty(); +} + } // namespace content diff --git a/content/renderer/pepper/pepper_audio_controller.h b/content/renderer/pepper/pepper_audio_controller.h index 29241ab8e63963..8e8b8741200442 100644 --- a/content/renderer/pepper/pepper_audio_controller.h +++ b/content/renderer/pepper/pepper_audio_controller.h @@ -11,6 +11,7 @@ namespace content { +class PepperAudioOutputHost; class PepperPluginInstanceImpl; class PPB_Audio_Impl; @@ -25,9 +26,11 @@ class PepperAudioController { // Adds an audio instance to the controller. void AddInstance(PPB_Audio_Impl* audio); + void AddInstance(PepperAudioOutputHost* audio_output); // Removes an audio instance from the controller. void RemoveInstance(PPB_Audio_Impl* audio); + void RemoveInstance(PepperAudioOutputHost* audio_output); // Sets the volume of all audio instances. void SetVolume(double volume); @@ -42,9 +45,18 @@ class PepperAudioController { // only be called when |ppb_audios_| turns from non-empty to empty. void NotifyPlaybackStopsOnEmpty(); - // All active audio instances. + // Helper functions to deal with the first and last audio instance. + void StartPlaybackIfFirstInstance(); + void StopPlaybackIfLastInstance(); + + // All active audio instances that are using the old + // PPB_Audio interface. std::set ppb_audios_; + // All active audio output instances that are using the new + // PPB_AudioOutput interface. + std::set audio_output_hosts_; + // The Pepper instance which this controller is for. Will be null after // OnPepperInstanceDeleted() is called. PepperPluginInstanceImpl* instance_; diff --git a/content/renderer/pepper/pepper_audio_output_host.cc b/content/renderer/pepper/pepper_audio_output_host.cc new file mode 100644 index 00000000000000..6f264b97666e1a --- /dev/null +++ b/content/renderer/pepper/pepper_audio_output_host.cc @@ -0,0 +1,265 @@ +// 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 "content/renderer/pepper/pepper_audio_output_host.h" + +#include "base/logging.h" +#include "build/build_config.h" +#include "content/common/pepper_file_util.h" +#include "content/renderer/pepper/pepper_audio_controller.h" +#include "content/renderer/pepper/pepper_media_device_manager.h" +#include "content/renderer/pepper/pepper_platform_audio_output_dev.h" +#include "content/renderer/pepper/pepper_plugin_instance_impl.h" +#include "content/renderer/pepper/plugin_instance_throttler_impl.h" +#include "content/renderer/pepper/renderer_ppapi_host_impl.h" +#include "content/renderer/render_frame_impl.h" +#include "ipc/ipc_message.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/host/dispatch_host_message.h" +#include "ppapi/host/ppapi_host.h" +#include "ppapi/proxy/ppapi_messages.h" +#include "ppapi/proxy/serialized_structs.h" + +namespace content { + +namespace { + +base::PlatformFile ConvertSyncSocketHandle(const base::SyncSocket& socket) { + return socket.handle(); +} + +} // namespace + +PepperAudioOutputHost::PepperAudioOutputHost(RendererPpapiHostImpl* host, + PP_Instance instance, + PP_Resource resource) + : ResourceHost(host->GetPpapiHost(), instance, resource), + renderer_ppapi_host_(host), + audio_output_(NULL), + playback_throttled_(false), + enumeration_helper_(this, + PepperMediaDeviceManager::GetForRenderFrame( + host->GetRenderFrameForInstance(pp_instance())), + PP_DEVICETYPE_DEV_AUDIOOUTPUT, + host->GetDocumentURL(instance)) { + PepperPluginInstanceImpl* plugin_instance = + static_cast( + PepperPluginInstance::Get(pp_instance())); + if (plugin_instance && plugin_instance->throttler()) + plugin_instance->throttler()->AddObserver(this); +} + +PepperAudioOutputHost::~PepperAudioOutputHost() { + PepperPluginInstanceImpl* instance = static_cast( + PepperPluginInstance::Get(pp_instance())); + if (instance) { + if (instance->throttler()) { + instance->throttler()->RemoveObserver(this); + } + instance->audio_controller().RemoveInstance(this); + } + Close(); +} + +int32_t PepperAudioOutputHost::OnResourceMessageReceived( + const IPC::Message& msg, + ppapi::host::HostMessageContext* context) { + int32_t result = PP_ERROR_FAILED; + if (enumeration_helper_.HandleResourceMessage(msg, context, &result)) + return result; + + PPAPI_BEGIN_MESSAGE_MAP(PepperAudioOutputHost, msg) + PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_AudioOutput_Open, OnOpen) + PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_AudioOutput_StartOrStop, + OnStartOrStop) + PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_AudioOutput_Close, OnClose) + PPAPI_END_MESSAGE_MAP() + return PP_ERROR_FAILED; +} + +void PepperAudioOutputHost::StreamCreated( + base::SharedMemoryHandle shared_memory_handle, + size_t shared_memory_size, + base::SyncSocket::Handle socket) { + OnOpenComplete(PP_OK, shared_memory_handle, shared_memory_size, socket); +} + +void PepperAudioOutputHost::StreamCreationFailed() { + OnOpenComplete(PP_ERROR_FAILED, base::SharedMemory::NULLHandle(), 0, + base::SyncSocket::kInvalidHandle); +} + +void PepperAudioOutputHost::SetVolume(double volume) { + if (audio_output_) + audio_output_->SetVolume(volume); +} + +int32_t PepperAudioOutputHost::OnOpen(ppapi::host::HostMessageContext* context, + const std::string& device_id, + PP_AudioSampleRate sample_rate, + uint32_t sample_frame_count) { + if (open_context_.is_valid()) + return PP_ERROR_INPROGRESS; + + if (audio_output_) + return PP_ERROR_FAILED; + + GURL document_url = renderer_ppapi_host_->GetDocumentURL(pp_instance()); + if (!document_url.is_valid()) + return PP_ERROR_FAILED; + + // When it is done, we'll get called back on StreamCreated() or + // StreamCreationFailed(). + audio_output_ = PepperPlatformAudioOutputDev::Create( + renderer_ppapi_host_->GetRenderFrameForInstance(pp_instance()) + ->GetRoutingID(), + device_id, document_url, static_cast(sample_rate), + static_cast(sample_frame_count), this); + if (audio_output_) { + open_context_ = context->MakeReplyMessageContext(); + return PP_OK_COMPLETIONPENDING; + } else { + return PP_ERROR_FAILED; + } +} + +int32_t PepperAudioOutputHost::OnStartOrStop( + ppapi::host::HostMessageContext* /* context */, + bool playback) { + if (!audio_output_) + return PP_ERROR_FAILED; + + PepperPluginInstanceImpl* instance = static_cast( + PepperPluginInstance::Get(pp_instance())); + + if (playback) { + // If plugin is in power saver mode, defer audio IPC communication. + if (instance && instance->throttler() && + instance->throttler()->power_saver_enabled()) { + instance->throttler()->NotifyAudioThrottled(); + playback_throttled_ = true; + return PP_TRUE; + } + if (instance) + instance->audio_controller().AddInstance(this); + + audio_output_->StartPlayback(); + } else { + if (instance) + instance->audio_controller().RemoveInstance(this); + + audio_output_->StopPlayback(); + } + return PP_OK; +} + +int32_t PepperAudioOutputHost::OnClose( + ppapi::host::HostMessageContext* /* context */) { + Close(); + return PP_OK; +} + +void PepperAudioOutputHost::OnOpenComplete( + int32_t result, + base::SharedMemoryHandle shared_memory_handle, + size_t shared_memory_size, + base::SyncSocket::Handle socket_handle) { + // Make sure the handles are cleaned up. + base::SyncSocket scoped_socket(socket_handle); + base::SharedMemory scoped_shared_memory(shared_memory_handle, false); + + if (!open_context_.is_valid()) { + NOTREACHED(); + return; + } + + ppapi::proxy::SerializedHandle serialized_socket_handle( + ppapi::proxy::SerializedHandle::SOCKET); + ppapi::proxy::SerializedHandle serialized_shared_memory_handle( + ppapi::proxy::SerializedHandle::SHARED_MEMORY); + + if (result == PP_OK) { + IPC::PlatformFileForTransit temp_socket = + IPC::InvalidPlatformFileForTransit(); + base::SharedMemoryHandle temp_shmem = base::SharedMemory::NULLHandle(); + result = GetRemoteHandles(scoped_socket, scoped_shared_memory, &temp_socket, + &temp_shmem); + + serialized_socket_handle.set_socket(temp_socket); + serialized_shared_memory_handle.set_shmem(temp_shmem, shared_memory_size); + } + + // Send all the values, even on error. This simplifies some of our cleanup + // code since the handles will be in the other process and could be + // inconvenient to clean up. Our IPC code will automatically handle this for + // us, as long as the remote side always closes the handles it receives, even + // in the failure case. + open_context_.params.AppendHandle(serialized_socket_handle); + open_context_.params.AppendHandle(serialized_shared_memory_handle); + SendOpenReply(result); +} + +int32_t PepperAudioOutputHost::GetRemoteHandles( + const base::SyncSocket& socket, + const base::SharedMemory& shared_memory, + IPC::PlatformFileForTransit* remote_socket_handle, + base::SharedMemoryHandle* remote_shared_memory_handle) { + *remote_socket_handle = renderer_ppapi_host_->ShareHandleWithRemote( + ConvertSyncSocketHandle(socket), false); + if (*remote_socket_handle == IPC::InvalidPlatformFileForTransit()) + return PP_ERROR_FAILED; + + *remote_shared_memory_handle = + renderer_ppapi_host_->ShareSharedMemoryHandleWithRemote( + shared_memory.handle()); + if (!base::SharedMemory::IsHandleValid(*remote_shared_memory_handle)) + return PP_ERROR_FAILED; + + return PP_OK; +} + +void PepperAudioOutputHost::Close() { + if (!audio_output_) + return; + + audio_output_->ShutDown(); + audio_output_ = NULL; + + if (open_context_.is_valid()) + SendOpenReply(PP_ERROR_ABORTED); +} + +void PepperAudioOutputHost::SendOpenReply(int32_t result) { + open_context_.params.set_result(result); + host()->SendReply(open_context_, PpapiPluginMsg_AudioOutput_OpenReply()); + open_context_ = ppapi::host::ReplyMessageContext(); +} + +void PepperAudioOutputHost::OnThrottleStateChange() { + PepperPluginInstanceImpl* instance = static_cast( + PepperPluginInstance::Get(pp_instance())); + if (playback_throttled_ && instance && instance->throttler() && + !instance->throttler()->power_saver_enabled()) { + // If we have become unthrottled, and we have a pending playback, + // start it. + StartDeferredPlayback(); + } +} + +void PepperAudioOutputHost::StartDeferredPlayback() { + if (!audio_output_) + return; + + DCHECK(playback_throttled_); + playback_throttled_ = false; + + PepperPluginInstanceImpl* instance = static_cast( + PepperPluginInstance::Get(pp_instance())); + if (instance) + instance->audio_controller().AddInstance(this); + + audio_output_->StartPlayback(); +} + +} // namespace content diff --git a/content/renderer/pepper/pepper_audio_output_host.h b/content/renderer/pepper/pepper_audio_output_host.h new file mode 100644 index 00000000000000..e8ea573ffa7744 --- /dev/null +++ b/content/renderer/pepper/pepper_audio_output_host.h @@ -0,0 +1,97 @@ +// 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. + +#ifndef CONTENT_RENDERER_PEPPER_PEPPER_AUDIO_OUTPUT_HOST_H_ +#define CONTENT_RENDERER_PEPPER_PEPPER_AUDIO_OUTPUT_HOST_H_ + +#include +#include + +#include +#include + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/shared_memory.h" +#include "base/sync_socket.h" +#include "content/public/renderer/plugin_instance_throttler.h" +#include "content/renderer/pepper/pepper_device_enumeration_host_helper.h" +#include "ipc/ipc_platform_file.h" +#include "ppapi/c/ppb_audio_config.h" +#include "ppapi/host/host_message_context.h" +#include "ppapi/host/resource_host.h" + +namespace content { +class PepperPlatformAudioOutputDev; +class RendererPpapiHostImpl; + +class PepperAudioOutputHost : public ppapi::host::ResourceHost, + public PluginInstanceThrottler::Observer { + public: + PepperAudioOutputHost(RendererPpapiHostImpl* host, + PP_Instance instance, + PP_Resource resource); + ~PepperAudioOutputHost() override; + + int32_t OnResourceMessageReceived( + const IPC::Message& msg, + ppapi::host::HostMessageContext* context) override; + + // Called when the stream is created. + void StreamCreated(base::SharedMemoryHandle shared_memory_handle, + size_t shared_memory_size, + base::SyncSocket::Handle socket); + void StreamCreationFailed(); + void SetVolume(double volume); + + private: + int32_t OnOpen(ppapi::host::HostMessageContext* context, + const std::string& device_id, + PP_AudioSampleRate sample_rate, + uint32_t sample_frame_count); + int32_t OnStartOrStop(ppapi::host::HostMessageContext* context, + bool playback); + int32_t OnClose(ppapi::host::HostMessageContext* context); + + void OnOpenComplete(int32_t result, + base::SharedMemoryHandle shared_memory_handle, + size_t shared_memory_size, + base::SyncSocket::Handle socket_handle); + + int32_t GetRemoteHandles( + const base::SyncSocket& socket, + const base::SharedMemory& shared_memory, + IPC::PlatformFileForTransit* remote_socket_handle, + base::SharedMemoryHandle* remote_shared_memory_handle); + + void Close(); + + void SendOpenReply(int32_t result); + + // PluginInstanceThrottler::Observer implementation. + void OnThrottleStateChange() override; + + // Starts the deferred playback and unsubscribes from the throttler. + void StartDeferredPlayback(); + + // Non-owning pointer. + RendererPpapiHostImpl* renderer_ppapi_host_; + + ppapi::host::ReplyMessageContext open_context_; + + // Audio output object that we delegate audio IPC through. + // We don't own this pointer but are responsible for calling Shutdown on it. + PepperPlatformAudioOutputDev* audio_output_; + + // Stream is playing, but throttled due to Plugin Power Saver. + bool playback_throttled_; + + PepperDeviceEnumerationHostHelper enumeration_helper_; + + DISALLOW_COPY_AND_ASSIGN(PepperAudioOutputHost); +}; + +} // namespace content + +#endif // CONTENT_RENDERER_PEPPER_PEPPER_AUDIO_OUTPUT_HOST_H_ diff --git a/content/renderer/pepper/pepper_media_device_manager.cc b/content/renderer/pepper/pepper_media_device_manager.cc index 8888bb715bf252..55a2fd0f57fdb0 100644 --- a/content/renderer/pepper/pepper_media_device_manager.cc +++ b/content/renderer/pepper/pepper_media_device_manager.cc @@ -25,6 +25,8 @@ PP_DeviceType_Dev FromMediaDeviceType(MediaDeviceType type) { return PP_DEVICETYPE_DEV_AUDIOCAPTURE; case MEDIA_DEVICE_TYPE_VIDEO_INPUT: return PP_DEVICETYPE_DEV_VIDEOCAPTURE; + case MEDIA_DEVICE_TYPE_AUDIO_OUTPUT: + return PP_DEVICETYPE_DEV_AUDIOOUTPUT; default: NOTREACHED(); return PP_DEVICETYPE_DEV_INVALID; @@ -37,6 +39,8 @@ MediaDeviceType ToMediaDeviceType(PP_DeviceType_Dev type) { return MEDIA_DEVICE_TYPE_AUDIO_INPUT; case PP_DEVICETYPE_DEV_VIDEOCAPTURE: return MEDIA_DEVICE_TYPE_VIDEO_INPUT; + case PP_DEVICETYPE_DEV_AUDIOOUTPUT: + return MEDIA_DEVICE_TYPE_AUDIO_OUTPUT; default: NOTREACHED(); return MEDIA_DEVICE_TYPE_AUDIO_OUTPUT; @@ -82,9 +86,10 @@ void PepperMediaDeviceManager::EnumerateDevices( #if BUILDFLAG(ENABLE_WEBRTC) bool request_audio_input = type == PP_DEVICETYPE_DEV_AUDIOCAPTURE; bool request_video_input = type == PP_DEVICETYPE_DEV_VIDEOCAPTURE; - CHECK(request_audio_input || request_video_input); + bool request_audio_output = type == PP_DEVICETYPE_DEV_AUDIOOUTPUT; + CHECK(request_audio_input || request_video_input || request_audio_output); GetMediaDevicesDispatcher()->EnumerateDevices( - request_audio_input, request_video_input, false /* audio_output */, + request_audio_input, request_video_input, request_audio_output, url::Origin(document_url.GetOrigin()), base::Bind(&PepperMediaDeviceManager::DevicesEnumerated, AsWeakPtr(), callback, ToMediaDeviceType(type))); diff --git a/content/renderer/pepper/pepper_platform_audio_output_dev.cc b/content/renderer/pepper/pepper_platform_audio_output_dev.cc new file mode 100644 index 00000000000000..63bb2e4aca07d0 --- /dev/null +++ b/content/renderer/pepper/pepper_platform_audio_output_dev.cc @@ -0,0 +1,407 @@ +// 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 "content/renderer/pepper/pepper_platform_audio_output_dev.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "build/build_config.h" +#include "content/child/child_process.h" +#include "content/common/content_constants_internal.h" +#include "content/common/media/audio_messages.h" +#include "content/renderer/media/audio_message_filter.h" +#include "content/renderer/pepper/audio_helper.h" +#include "content/renderer/pepper/pepper_audio_output_host.h" +#include "content/renderer/pepper/pepper_media_device_manager.h" +#include "content/renderer/render_frame_impl.h" +#include "content/renderer/render_thread_impl.h" +#include "media/audio/audio_device_description.h" +#include "ppapi/shared_impl/ppb_audio_config_shared.h" + +namespace content { + +// static +PepperPlatformAudioOutputDev* PepperPlatformAudioOutputDev::Create( + int render_frame_id, + const std::string& device_id, + const GURL& document_url, + int sample_rate, + int frames_per_buffer, + PepperAudioOutputHost* client) { + scoped_refptr audio_output( + new PepperPlatformAudioOutputDev( + render_frame_id, device_id, document_url, + // Set authorization request timeout at 80% of renderer hung timeout, + // but no more than kMaxAuthorizationTimeout. + base::TimeDelta::FromMilliseconds(std::min( + kHungRendererDelayMs * 8 / 10, kMaxAuthorizationTimeoutMs)))); + + if (audio_output->Initialize(sample_rate, frames_per_buffer, client)) { + // Balanced by Release invoked in + // PepperPlatformAudioOutputDev::ShutDownOnIOThread(). + audio_output->AddRef(); + return audio_output.get(); + } + return NULL; +} + +void PepperPlatformAudioOutputDev::RequestDeviceAuthorization() { + if (ipc_) { + io_task_runner_->PostTask( + FROM_HERE, + base::Bind( + &PepperPlatformAudioOutputDev::RequestDeviceAuthorizationOnIOThread, + this)); + } +} + +bool PepperPlatformAudioOutputDev::StartPlayback() { + if (ipc_) { + io_task_runner_->PostTask( + FROM_HERE, + base::Bind(&PepperPlatformAudioOutputDev::StartPlaybackOnIOThread, + this)); + return true; + } + return false; +} + +bool PepperPlatformAudioOutputDev::StopPlayback() { + if (ipc_) { + io_task_runner_->PostTask( + FROM_HERE, + base::Bind(&PepperPlatformAudioOutputDev::StopPlaybackOnIOThread, + this)); + return true; + } + return false; +} + +bool PepperPlatformAudioOutputDev::SetVolume(double volume) { + if (ipc_) { + io_task_runner_->PostTask( + FROM_HERE, + base::Bind(&PepperPlatformAudioOutputDev::SetVolumeOnIOThread, this, + volume)); + return true; + } + return false; +} + +void PepperPlatformAudioOutputDev::ShutDown() { + // Called on the main thread to stop all audio callbacks. We must only change + // the client on the main thread, and the delegates from the I/O thread. + client_ = NULL; + io_task_runner_->PostTask( + FROM_HERE, + base::Bind(&PepperPlatformAudioOutputDev::ShutDownOnIOThread, this)); +} + +void PepperPlatformAudioOutputDev::OnError() { + DCHECK(io_task_runner_->BelongsToCurrentThread()); + + // Do nothing if the stream has been closed. + if (state_ < CREATING_STREAM) + return; + + DLOG(WARNING) << "PepperPlatformAudioOutputDev::OnError()"; +} + +void PepperPlatformAudioOutputDev::OnDeviceAuthorized( + media::OutputDeviceStatus device_status, + const media::AudioParameters& output_params, + const std::string& matched_device_id) { + DCHECK(io_task_runner_->BelongsToCurrentThread()); + + auth_timeout_action_.reset(); + + // Do nothing if late authorization is received after timeout. + if (state_ == IPC_CLOSED) + return; + + LOG_IF(WARNING, device_status == media::OUTPUT_DEVICE_STATUS_ERROR_TIMED_OUT) + << "Output device authorization timed out"; + + DCHECK_EQ(state_, AUTHORIZING); + + // It may happen that a second authorization is received as a result to a + // call to StartPlayback() after Shutdown(). If the status for the second + // authorization differs from the first, it will not be reflected in + // |device_status_| to avoid a race. + // This scenario is unlikely. If it occurs, the new value will be + // different from OUTPUT_DEVICE_STATUS_OK, so the PepperPlatformAudioOutputDev + // will enter the IPC_CLOSED state anyway, which is the safe thing to do. + // This is preferable to holding a lock. + if (!did_receive_auth_.IsSignaled()) + device_status_ = device_status; + + if (device_status == media::OUTPUT_DEVICE_STATUS_OK) { + state_ = AUTHORIZED; + if (!did_receive_auth_.IsSignaled()) { + output_params_ = output_params; + + // It's possible to not have a matched device obtained via session id. It + // means matching output device through |session_id_| failed and the + // default device is used. + DCHECK(media::AudioDeviceDescription::UseSessionIdToSelectDevice( + session_id_, device_id_) || + matched_device_id_.empty()); + matched_device_id_ = matched_device_id; + + DVLOG(1) << "PepperPlatformAudioOutputDev authorized, session_id: " + << session_id_ << ", device_id: " << device_id_ + << ", matched_device_id: " << matched_device_id_; + + did_receive_auth_.Signal(); + } + if (start_on_authorized_) + CreateStreamOnIOThread(params_); + } else { + // Closing IPC forces a Signal(), so no clients are locked waiting + // indefinitely after this method returns. + ipc_->CloseStream(); + OnIPCClosed(); + main_task_runner_->PostTask( + FROM_HERE, + base::Bind(&PepperPlatformAudioOutputDev::NotifyStreamCreationFailed, + this)); + } +} + +void PepperPlatformAudioOutputDev::OnStreamCreated( + base::SharedMemoryHandle handle, + base::SyncSocket::Handle socket_handle, + int length) { +#if defined(OS_WIN) + DCHECK(handle.IsValid()); + DCHECK(socket_handle); +#else + DCHECK(base::SharedMemory::IsHandleValid(handle)); + DCHECK_NE(-1, socket_handle); +#endif + DCHECK(length); + + if (base::ThreadTaskRunnerHandle::Get().get() == main_task_runner_.get()) { + // Must dereference the client only on the main thread. Shutdown may have + // occurred while the request was in-flight, so we need to NULL check. + if (client_) + client_->StreamCreated(handle, length, socket_handle); + } else { + DCHECK(io_task_runner_->BelongsToCurrentThread()); + if (state_ != CREATING_STREAM) + return; + + state_ = PAUSED; + if (play_on_start_) + StartPlaybackOnIOThread(); + + main_task_runner_->PostTask( + FROM_HERE, base::Bind(&PepperPlatformAudioOutputDev::OnStreamCreated, + this, handle, socket_handle, length)); + } +} + +void PepperPlatformAudioOutputDev::OnIPCClosed() { + DCHECK(io_task_runner_->BelongsToCurrentThread()); + state_ = IPC_CLOSED; + ipc_.reset(); + + // Signal to unblock any blocked threads waiting for parameters + did_receive_auth_.Signal(); +} + +PepperPlatformAudioOutputDev::~PepperPlatformAudioOutputDev() { + // Make sure we have been shut down. Warning: this will usually happen on + // the I/O thread! + DCHECK(!ipc_); + DCHECK(!client_); +} + +PepperPlatformAudioOutputDev::PepperPlatformAudioOutputDev( + int render_frame_id, + const std::string& device_id, + const GURL& document_url, + base::TimeDelta authorization_timeout) + : client_(NULL), + main_task_runner_(base::ThreadTaskRunnerHandle::Get()), + io_task_runner_(ChildProcess::current()->io_task_runner()), + render_frame_id_(render_frame_id), + state_(IDLE), + start_on_authorized_(true), + play_on_start_(false), + session_id_(0), + device_id_(device_id), + security_origin_(document_url), + did_receive_auth_(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED), + device_status_(media::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL), + auth_timeout_(authorization_timeout) {} + +bool PepperPlatformAudioOutputDev::Initialize(int sample_rate, + int frames_per_buffer, + PepperAudioOutputHost* client) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + RenderFrameImpl* const render_frame = + RenderFrameImpl::FromRoutingID(render_frame_id_); + if (!render_frame || !client) + return false; + + client_ = client; + + RenderThreadImpl* const render_thread = RenderThreadImpl::current(); + ipc_ = render_thread->audio_message_filter()->CreateAudioOutputIPC( + render_frame_id_); + CHECK(ipc_); + + params_.Reset(media::AudioParameters::AUDIO_PCM_LOW_LATENCY, + media::CHANNEL_LAYOUT_STEREO, sample_rate, + ppapi::kBitsPerAudioOutputSample, frames_per_buffer); + + io_task_runner_->PostTask( + FROM_HERE, + base::Bind(&PepperPlatformAudioOutputDev::CreateStreamOnIOThread, this, + params_)); + + return true; +} + +void PepperPlatformAudioOutputDev::RequestDeviceAuthorizationOnIOThread() { + DCHECK(io_task_runner_->BelongsToCurrentThread()); + DCHECK_EQ(state_, IDLE); + + if (!ipc_) + return; + + state_ = AUTHORIZING; + ipc_->RequestDeviceAuthorization(this, session_id_, device_id_, + security_origin_); + + if (auth_timeout_ > base::TimeDelta()) { + // Create the timer on the thread it's used on. It's guaranteed to be + // deleted on the same thread since users must call ShutDown() before + // deleting PepperPlatformAudioOutputDev; see ShutDownOnIOThread(). + auth_timeout_action_.reset(new base::OneShotTimer()); + auth_timeout_action_->Start( + FROM_HERE, auth_timeout_, + base::Bind(&PepperPlatformAudioOutputDev::OnDeviceAuthorized, this, + media::OUTPUT_DEVICE_STATUS_ERROR_TIMED_OUT, + media::AudioParameters(), std::string())); + } +} + +void PepperPlatformAudioOutputDev::CreateStreamOnIOThread( + const media::AudioParameters& params) { + DCHECK(io_task_runner_->BelongsToCurrentThread()); + switch (state_) { + case IPC_CLOSED: + main_task_runner_->PostTask( + FROM_HERE, + base::Bind(&PepperPlatformAudioOutputDev::NotifyStreamCreationFailed, + this)); + break; + + case IDLE: + if (did_receive_auth_.IsSignaled() && device_id_.empty() && + security_origin_.unique()) { + state_ = CREATING_STREAM; + ipc_->CreateStream(this, params); + } else { + RequestDeviceAuthorizationOnIOThread(); + start_on_authorized_ = true; + } + break; + + case AUTHORIZING: + start_on_authorized_ = true; + break; + + case AUTHORIZED: + state_ = CREATING_STREAM; + ipc_->CreateStream(this, params); + start_on_authorized_ = false; + break; + + case CREATING_STREAM: + case PAUSED: + case PLAYING: + NOTREACHED(); + break; + } +} + +void PepperPlatformAudioOutputDev::StartPlaybackOnIOThread() { + DCHECK(io_task_runner_->BelongsToCurrentThread()); + if (!ipc_) + return; + + if (state_ == PAUSED) { + ipc_->PlayStream(); + state_ = PLAYING; + play_on_start_ = false; + } else { + if (state_ < CREATING_STREAM) + CreateStreamOnIOThread(params_); + + play_on_start_ = true; + } +} + +void PepperPlatformAudioOutputDev::StopPlaybackOnIOThread() { + DCHECK(io_task_runner_->BelongsToCurrentThread()); + if (!ipc_) + return; + + if (state_ == PLAYING) { + ipc_->PauseStream(); + state_ = PAUSED; + } + play_on_start_ = false; +} + +void PepperPlatformAudioOutputDev::SetVolumeOnIOThread(double volume) { + DCHECK(io_task_runner_->BelongsToCurrentThread()); + if (!ipc_) + return; + + if (state_ >= CREATING_STREAM) + ipc_->SetVolume(volume); +} + +void PepperPlatformAudioOutputDev::ShutDownOnIOThread() { + DCHECK(io_task_runner_->BelongsToCurrentThread()); + + // Make sure we don't call shutdown more than once. + if (!ipc_) + return; + + // Close the stream, if we haven't already. + if (state_ >= AUTHORIZING) { + ipc_->CloseStream(); + ipc_.reset(); + state_ = IDLE; + } + start_on_authorized_ = false; + + // Destoy the timer on the thread it's used on. + auth_timeout_action_.reset(); + + // Release for the delegate, balances out the reference taken in + // PepperPlatformAudioOutputDev::Create. + Release(); +} + +void PepperPlatformAudioOutputDev::NotifyStreamCreationFailed() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + if (client_) + client_->StreamCreationFailed(); +} + +} // namespace content diff --git a/content/renderer/pepper/pepper_platform_audio_output_dev.h b/content/renderer/pepper/pepper_platform_audio_output_dev.h new file mode 100644 index 00000000000000..50d4537ac9b790 --- /dev/null +++ b/content/renderer/pepper/pepper_platform_audio_output_dev.h @@ -0,0 +1,162 @@ +// 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. + +#ifndef CONTENT_RENDERER_PEPPER_PEPPER_PLATFORM_AUDIO_OUTPUT_DEV_H_ +#define CONTENT_RENDERER_PEPPER_PEPPER_PLATFORM_AUDIO_OUTPUT_DEV_H_ + +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "media/audio/audio_output_ipc.h" +#include "media/base/audio_parameters.h" +#include "media/base/output_device_info.h" + +namespace base { +class OneShotTimer; +class SingleThreadTaskRunner; +} + +namespace { +#if defined(OS_WIN) || defined(OS_MACOSX) +const int64_t kMaxAuthorizationTimeoutMs = 4000; +#else +const int64_t kMaxAuthorizationTimeoutMs = 0; // No timeout. +#endif +} + +namespace content { +class PepperAudioOutputHost; + +// This class is used to support new PPAPI |PPB_AudioOutput_Dev|, while +// |PepperPlatformAudioOutput| is to support old PPAPI |PPB_Audio|. +class PepperPlatformAudioOutputDev + : public media::AudioOutputIPCDelegate, + public base::RefCountedThreadSafe { + public: + // Factory function, returns NULL on failure. StreamCreated() will be called + // when the stream is created. + static PepperPlatformAudioOutputDev* Create(int render_frame_id, + const std::string& device_id, + const GURL& document_url, + int sample_rate, + int frames_per_buffer, + PepperAudioOutputHost* client); + + // The following three methods are all called on main thread. + + // Request authorization to use the device. + void RequestDeviceAuthorization(); + + // Starts the playback. Returns false on error or if called before the + // stream is created or after the stream is closed. + bool StartPlayback(); + + // Stops the playback. Returns false on error or if called before the stream + // is created or after the stream is closed. + bool StopPlayback(); + + // Sets the volume. Returns false on error or if called before the stream + // is created or after the stream is closed. + bool SetVolume(double volume); + + // Closes the stream. Make sure to call this before the object is + // destructed. + void ShutDown(); + + // media::AudioOutputIPCDelegate implementation. + void OnError() override; + void OnDeviceAuthorized(media::OutputDeviceStatus device_status, + const media::AudioParameters& output_params, + const std::string& matched_device_id) override; + void OnStreamCreated(base::SharedMemoryHandle handle, + base::SyncSocket::Handle socket_handle, + int length) override; + void OnIPCClosed() override; + + protected: + ~PepperPlatformAudioOutputDev() override; + + private: + enum State { + IPC_CLOSED, // No more IPCs can take place. + IDLE, // Not started. + AUTHORIZING, // Sent device authorization request, waiting for reply. + AUTHORIZED, // Successful device authorization received. + CREATING_STREAM, // Waiting for OnStreamCreated() to be called back. + PAUSED, // Paused. OnStreamCreated() has been called. Can Play()/Stop(). + PLAYING, // Playing back. Can Pause()/Stop(). + }; + + friend class base::RefCountedThreadSafe; + + PepperPlatformAudioOutputDev(); + PepperPlatformAudioOutputDev(int render_frame_id, + const std::string& device_id, + const GURL& document_url, + base::TimeDelta authorization_timeout); + + // Creates audio stream. Used for new Pepper audio output interface + // |PPB_AudioOutput_Dev|. + bool Initialize(int sample_rate, + int frames_per_buffer, + PepperAudioOutputHost* client); + + void RequestDeviceAuthorizationOnIOThread(); + void CreateStreamOnIOThread(const media::AudioParameters& params); + void StartPlaybackOnIOThread(); + void StopPlaybackOnIOThread(); + void SetVolumeOnIOThread(double volume); + void ShutDownOnIOThread(); + + void NotifyStreamCreationFailed(); + + PepperAudioOutputHost* client_; + + // Used to send/receive IPC. THIS MUST ONLY BE ACCESSED ON THE + // I/O thread except to send messages and get the message loop. + std::unique_ptr ipc_; + + scoped_refptr main_task_runner_; + scoped_refptr io_task_runner_; + + // The frame containing the Pepper widget. + int render_frame_id_; + + // Initialized on the main thread and accessed on the I/O thread afterwards. + media::AudioParameters params_; + + // Current state (must only be accessed from the IO thread). + State state_; + + // State of Start() calls before OnDeviceAuthorized() is called. + bool start_on_authorized_; + + // State of StartPlayback() calls before OnStreamCreated() is called. + bool play_on_start_; + + // The media session ID used to identify which output device to be started. + int session_id_; + + // ID of hardware output device to be used (provided session_id_ is zero) + const std::string device_id_; + const url::Origin security_origin_; + + // If |device_id_| is empty and |session_id_| is not, |matched_device_id_| is + // received in OnDeviceAuthorized(). + std::string matched_device_id_; + + base::WaitableEvent did_receive_auth_; + media::AudioParameters output_params_; + media::OutputDeviceStatus device_status_; + + const base::TimeDelta auth_timeout_; + std::unique_ptr auth_timeout_action_; + + DISALLOW_COPY_AND_ASSIGN(PepperPlatformAudioOutputDev); +}; + +} // namespace content + +#endif // CONTENT_RENDERER_PEPPER_PEPPER_PLATFORM_AUDIO_OUTPUT_DEV_H_ diff --git a/content/renderer/pepper/plugin_module.cc b/content/renderer/pepper/plugin_module.cc index 7db5c1a8268ca0..839dc1b71d5b6c 100644 --- a/content/renderer/pepper/plugin_module.cc +++ b/content/renderer/pepper/plugin_module.cc @@ -35,6 +35,7 @@ #include "content/renderer/pepper/renderer_ppapi_host_impl.h" #include "content/renderer/render_view_impl.h" #include "ppapi/c/dev/ppb_audio_input_dev.h" +#include "ppapi/c/dev/ppb_audio_output_dev.h" #include "ppapi/c/dev/ppb_buffer_dev.h" #include "ppapi/c/dev/ppb_char_set_dev.h" #include "ppapi/c/dev/ppb_crypto_dev.h" diff --git a/content/renderer/pepper/resource_creation_impl.cc b/content/renderer/pepper/resource_creation_impl.cc index 0101196aaa5fc0..073a0b01d438b5 100644 --- a/content/renderer/pepper/resource_creation_impl.cc +++ b/content/renderer/pepper/resource_creation_impl.cc @@ -73,6 +73,10 @@ PP_Resource ResourceCreationImpl::CreateAudioInput(PP_Instance instance) { return 0; // Not supported in-process. } +PP_Resource ResourceCreationImpl::CreateAudioOutput(PP_Instance instance) { + return 0; // Not supported in-process. +} + PP_Resource ResourceCreationImpl::CreateCompositor(PP_Instance instance) { return 0; // Not supported in-process. } diff --git a/content/renderer/pepper/resource_creation_impl.h b/content/renderer/pepper/resource_creation_impl.h index 5f48569b18e184..3e05ba33cf1124 100644 --- a/content/renderer/pepper/resource_creation_impl.h +++ b/content/renderer/pepper/resource_creation_impl.h @@ -39,6 +39,7 @@ class ResourceCreationImpl : public ppapi::thunk::ResourceCreationAPI { PP_AudioSampleRate sample_rate, uint32_t sample_frame_count) override; PP_Resource CreateAudioInput(PP_Instance instance) override; + PP_Resource CreateAudioOutput(PP_Instance instance) override; PP_Resource CreateCompositor(PP_Instance instance) override; PP_Resource CreateBroker(PP_Instance instance) override; PP_Resource CreateBuffer(PP_Instance instance, uint32_t size) override; diff --git a/ppapi/api/dev/ppb_audio_output_dev.idl b/ppapi/api/dev/ppb_audio_output_dev.idl new file mode 100644 index 00000000000000..edd8679cef2316 --- /dev/null +++ b/ppapi/api/dev/ppb_audio_output_dev.idl @@ -0,0 +1,229 @@ +/* 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. + */ + +/** + * This file defines the PPB_AudioOutput_dev interface, which + * provides realtime stereo audio streaming capabilities. + */ + +[generate_thunk] + +label Chrome { + M59 = 0.1 +}; + +/** + * PPB_AudioOutput_Callback defines the type of an audio callback + * function used to fill the audio buffer with data. Please see the + * Create() function in the PPB_AudioOutput interface for + * more details on this callback. + * + * @param[out] sample_buffer A buffer to fill with audio data. + * @param[in] buffer_size_in_bytes The size of the buffer in bytes. + * @param[in] latency How long before the audio data is to be presented. + * @param[inout] user_data An opaque pointer that was passed into + * PPB_AudioOutput.Create(). + */ +typedef void PPB_AudioOutput_Callback([out] mem_t sample_buffer, + [in] uint32_t buffer_size_in_bytes, + [in] PP_TimeDelta latency, + [inout] mem_t user_data); + +/** + * The PPB_AudioOutput interface contains pointers to several + * functions for handling audio resources. + * Please see descriptions for each PPB_AudioOutput and + * PPB_AudioConfig function for more details. A C example using + * PPB_AudioOutput and PPB_AudioConfig follows. + * + * Example: + * + * @code + * void audio_output_callback(void* sample_buffer, + * uint32_t buffer_size_in_bytes, + * PP_TimeDelta latency, + * void* user_data) { + * ... quickly fill in the buffer with samples and return to caller ... + * } + * + * ...Assume the application has cached the audio configuration interface in + * audio_config_interface and the audio interface in + * audio_output_interface... + * + * uint32_t count = audio_config_interface->RecommendSampleFrameCount( + * PP_AUDIOSAMPLERATE_44100, 4096); + * PP_Resource pp_audio_config = audio_config_interface->CreateStereo16Bit( + * pp_instance, PP_AUDIOSAMPLERATE_44100, count); + * PP_Resource pp_audio_output = audio_interface->Create(pp_instance, + * pp_audio_config, audio_callback, NULL); + * audio_interface->EnumerateDevices(pp_audio_output, output_device_list, + * callback); + * audio_interface->Open(pp_audio_output, device_ref, pp_audio_config, + * audio_output_callback, user_data, callback); + * audio_output_interface->StartPlayback(pp_audio_output); + * + * ...audio_output_callback() will now be periodically invoked on a separate + * thread... + * @endcode + */ + +[macro="PPB_AUDIO_OUTPUT_DEV_INTERFACE"] +interface PPB_AudioOutput_Dev { + /** + * Creates an audio output resource. + * + * @param[in] instance A PP_Instance identifying one instance of + * a module. + * + * @return A PP_Resource corresponding to an audio output resource + * if successful, 0 if failed. + */ + PP_Resource Create( + [in] PP_Instance instance); + + /** + * Determines if the given resource is an audio output resource. + * + * @param[in] resource A PP_Resource containing a resource. + * + * @return A PP_Bool containing PP_TRUE if the given + * resource is an audio output resource, otherwise PP_FALSE. + */ + PP_Bool IsAudioOutput( + [in] PP_Resource resource); + + /** + * Enumerates audio output devices. + * + * @param[in] audio_output A PP_Resource corresponding to an audio + * output resource. + * @param[in] output An output array which will receive + * PPB_DeviceRef_Dev resources on success. Please note that the + * ref count of those resources has already been increased by 1 for the + * caller. + * @param[in] callback A PP_CompletionCallback to run on + * completion. + * + * @return An error code from pp_errors.h. + */ + int32_t EnumerateDevices( + [in] PP_Resource audio_output, + [in] PP_ArrayOutput output, + [in] PP_CompletionCallback callback); + + /** + * Requests device change notifications. + * + * @param[in] audio_output A PP_Resource corresponding to an audio + * output resource. + * @param[in] callback The callback to receive notifications. If not NULL, it + * will be called once for the currently available devices, and then every + * time the list of available devices changes. All calls will happen on the + * same thread as the one on which MonitorDeviceChange() is called. It will + * receive notifications until audio_output is destroyed or + * MonitorDeviceChange() is called to set a new callback for + * audio_output. You can pass NULL to cancel sending + * notifications. + * @param[inout] user_data An opaque pointer that will be passed to + * callback. + * + * @return An error code from pp_errors.h. + */ + int32_t MonitorDeviceChange( + [in] PP_Resource audio_output, + [in] PP_MonitorDeviceChangeCallback callback, + [inout] mem_t user_data); + + /** + * Open() opens an audio output device. No sound will be heard until + * StartPlayback() is called. The callback is called with the buffer address + * and given user data whenever the buffer needs to be filled. From within the + * callback, you should not call PPB_AudioOutput functions. The + * callback will be called on a different thread than the one which created + * the interface. For performance-critical applications (i.e. low-latency + * audio), the callback should avoid blocking or calling functions that can + * obtain locks, such as malloc. The layout and the size of the buffer passed + * to the audio callback will be determined by the device configuration and is + * specified in the AudioConfig documentation. + * + * @param[in] audio_output A PP_Resource corresponding to an audio + * output resource. + * @param[in] device_ref Identifies an audio output device. It could be one of + * the resource in the array returned by EnumerateDevices(), or 0 which means + * the default device. + * @param[in] config A PPB_AudioConfig audio configuration + * resource. + * @param[in] audio_output_callback A PPB_AudioOutput_Callback + * function that will be called when audio buffer needs to be filled. + * @param[inout] user_data An opaque pointer that will be passed into + * audio_output_callback. + * @param[in] callback A PP_CompletionCallback to run when this + * open operation is completed. + * + * @return An error code from pp_errors.h. + */ + int32_t Open( + [in] PP_Resource audio_output, + [in] PP_Resource device_ref, + [in] PP_Resource config, + [in] PPB_AudioOutput_Callback audio_output_callback, + [inout] mem_t user_data, + [in] PP_CompletionCallback callback); + + /** + * GetCurrrentConfig() returns an audio config resource for the given audio + * output resource. + * + * @param[in] config A PP_Resource corresponding to an audio + * output resource. + * + * @return A PP_Resource containing the audio config resource if + * successful. + */ + PP_Resource GetCurrentConfig( + [in] PP_Resource audio_output); + + /** + * StartPlayback() starts the playback of the audio output resource and begins + * periodically calling the callback. + * + * @param[in] config A PP_Resource corresponding to an audio + * output resource. + * + * @return A PP_Bool containing PP_TRUE if + * successful, otherwise PP_FALSE. Also returns + * PP_TRUE (and be a no-op) if called while playback is already + * in progress. + */ + PP_Bool StartPlayback( + [in] PP_Resource audio_output); + + /** + * StopPlayback() stops the playback of the audio resource. + * + * @param[in] config A PP_Resource corresponding to an audio + * output resource. + * + * @return A PP_Bool containing PP_TRUE if + * successful, otherwise PP_FALSE. Also returns + * PP_TRUE (and is a no-op) if called while playback is already + * stopped. If a callback is in progress, StopPlayback() will block until the + * callback completes. + */ + PP_Bool StopPlayback( + [in] PP_Resource audio_output); + + /** + * Close() closes the audio output device, and stops playback if necessary. It is + * not valid to call Open() again after a call to this method. + * If an audio output resource is destroyed while a device is still open, then + * it will be implicitly closed, so you are not required to call this method. + * + * @param[in] audio_output A PP_Resource corresponding to an audio + * output resource. + */ + void Close( + [in] PP_Resource audio_output); +}; diff --git a/ppapi/api/dev/ppb_device_ref_dev.idl b/ppapi/api/dev/ppb_device_ref_dev.idl index 98d127234f4be8..b6135843bf7665 100644 --- a/ppapi/api/dev/ppb_device_ref_dev.idl +++ b/ppapi/api/dev/ppb_device_ref_dev.idl @@ -36,7 +36,8 @@ typedef void PP_MonitorDeviceChangeCallback( enum PP_DeviceType_Dev { PP_DEVICETYPE_DEV_INVALID = 0, PP_DEVICETYPE_DEV_AUDIOCAPTURE = 1, - PP_DEVICETYPE_DEV_VIDEOCAPTURE = 2 + PP_DEVICETYPE_DEV_VIDEOCAPTURE = 2, + PP_DEVICETYPE_DEV_AUDIOOUTPUT = 3 }; interface PPB_DeviceRef_Dev { diff --git a/ppapi/c/BUILD.gn b/ppapi/c/BUILD.gn index 9894dd978764d0..cd3fda9d4e01eb 100644 --- a/ppapi/c/BUILD.gn +++ b/ppapi/c/BUILD.gn @@ -87,6 +87,7 @@ source_set("c") { "dev/pp_video_capture_dev.h", "dev/pp_video_dev.h", "dev/ppb_audio_input_dev.h", + "dev/ppb_audio_output_dev.h", "dev/ppb_buffer_dev.h", "dev/ppb_char_set_dev.h", "dev/ppb_crypto_dev.h", diff --git a/ppapi/c/dev/ppb_audio_output_dev.h b/ppapi/c/dev/ppb_audio_output_dev.h new file mode 100644 index 00000000000000..7edfc422b7ec57 --- /dev/null +++ b/ppapi/c/dev/ppb_audio_output_dev.h @@ -0,0 +1,249 @@ +/* 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. + */ + +/* From dev/ppb_audio_output_dev.idl modified Fri Apr 7 07:07:59 2017. */ + +#ifndef PPAPI_C_DEV_PPB_AUDIO_OUTPUT_DEV_H_ +#define PPAPI_C_DEV_PPB_AUDIO_OUTPUT_DEV_H_ + +#include "ppapi/c/dev/ppb_device_ref_dev.h" +#include "ppapi/c/pp_array_output.h" +#include "ppapi/c/pp_bool.h" +#include "ppapi/c/pp_completion_callback.h" +#include "ppapi/c/pp_instance.h" +#include "ppapi/c/pp_macros.h" +#include "ppapi/c/pp_resource.h" +#include "ppapi/c/pp_stdint.h" +#include "ppapi/c/pp_time.h" + +#define PPB_AUDIO_OUTPUT_DEV_INTERFACE_0_1 "PPB_AudioOutput(Dev);0.1" +#define PPB_AUDIO_OUTPUT_DEV_INTERFACE PPB_AUDIO_OUTPUT_DEV_INTERFACE_0_1 + +/** + * @file + * This file defines the PPB_AudioOutput_dev interface, which + * provides realtime stereo audio streaming capabilities. + */ + + +/** + * @addtogroup Typedefs + * @{ + */ +/** + * PPB_AudioOutput_Callback defines the type of an audio callback + * function used to fill the audio buffer with data. Please see the + * Create() function in the PPB_AudioOutput interface for + * more details on this callback. + * + * @param[out] sample_buffer A buffer to fill with audio data. + * @param[in] buffer_size_in_bytes The size of the buffer in bytes. + * @param[in] latency How long before the audio data is to be presented. + * @param[inout] user_data An opaque pointer that was passed into + * PPB_AudioOutput.Create(). + */ +typedef void (*PPB_AudioOutput_Callback)(void* sample_buffer, + uint32_t buffer_size_in_bytes, + PP_TimeDelta latency, + void* user_data); +/** + * @} + */ + +/** + * @addtogroup Interfaces + * @{ + */ +/** + * The PPB_AudioOutput interface contains pointers to several + * functions for handling audio resources. + * Please see descriptions for each PPB_AudioOutput and + * PPB_AudioConfig function for more details. A C example using + * PPB_AudioOutput and PPB_AudioConfig follows. + * + * Example: + * + * @code + * void audio_output_callback(void* sample_buffer, + * uint32_t buffer_size_in_bytes, + * PP_TimeDelta latency, + * void* user_data) { + * ... quickly fill in the buffer with samples and return to caller ... + * } + * + * ...Assume the application has cached the audio configuration interface in + * audio_config_interface and the audio interface in + * audio_output_interface... + * + * uint32_t count = audio_config_interface->RecommendSampleFrameCount( + * PP_AUDIOSAMPLERATE_44100, 4096); + * PP_Resource pp_audio_config = audio_config_interface->CreateStereo16Bit( + * pp_instance, PP_AUDIOSAMPLERATE_44100, count); + * PP_Resource pp_audio_output = audio_interface->Create(pp_instance, + * pp_audio_config, audio_callback, NULL); + * audio_interface->EnumerateDevices(pp_audio_output, output_device_list, + * callback); + * audio_interface->Open(pp_audio_output, device_ref, pp_audio_config, + * audio_output_callback, user_data, callback); + * audio_output_interface->StartPlayback(pp_audio_output); + * + * ...audio_output_callback() will now be periodically invoked on a separate + * thread... + * @endcode + */ +struct PPB_AudioOutput_Dev_0_1 { + /** + * Creates an audio output resource. + * + * @param[in] instance A PP_Instance identifying one instance of + * a module. + * + * @return A PP_Resource corresponding to an audio output + resource + * if successful, 0 if failed. + */ + PP_Resource (*Create)(PP_Instance instance); + /** + * Determines if the given resource is an audio output resource. + * + * @param[in] resource A PP_Resource containing a resource. + * + * @return A PP_Bool containing PP_TRUE if the given + * resource is an audio output resource, otherwise PP_FALSE. + */ + PP_Bool (*IsAudioOutput)(PP_Resource resource); + /** + * Enumerates audio output devices. + * + * @param[in] audio_output A PP_Resource corresponding to an + audio + * output resource. + * @param[in] output An output array which will receive + * PPB_DeviceRef_Dev resources on success. Please note that the + * ref count of those resources has already been increased by 1 for the + * caller. + * @param[in] callback A PP_CompletionCallback to run on + * completion. + * + * @return An error code from pp_errors.h. + */ + int32_t (*EnumerateDevices)(PP_Resource audio_output, + struct PP_ArrayOutput output, + struct PP_CompletionCallback callback); + /** + * Requests device change notifications. + * + * @param[in] audio_output A PP_Resource corresponding to an + audio + * output resource. + * @param[in] callback The callback to receive notifications. If not NULL, it + * will be called once for the currently available devices, and then every + * time the list of available devices changes. All calls will happen on the + * same thread as the one on which MonitorDeviceChange() is called. It will + * receive notifications until audio_output is destroyed or + * MonitorDeviceChange() is called to set a new callback for + * audio_output. You can pass NULL to cancel sending + * notifications. + * @param[inout] user_data An opaque pointer that will be passed to + * callback. + * + * @return An error code from pp_errors.h. + */ + int32_t (*MonitorDeviceChange)(PP_Resource audio_output, + PP_MonitorDeviceChangeCallback callback, + void* user_data); + /** + * Open() opens an audio output device. No sound will be heard until + * StartPlayback() is called. The callback is called with the buffer address + * and given user data whenever the buffer needs to be filled. From within the + * callback, you should not call PPB_AudioOutput functions. The + * callback will be called on a different thread than the one which created + * the interface. For performance-critical applications (i.e. low-latency + * audio), the callback should avoid blocking or calling functions that can + * obtain locks, such as malloc. The layout and the size of the buffer passed + * to the audio callback will be determined by the device configuration and is + * specified in the AudioConfig documentation. + * + * @param[in] audio_output A PP_Resource corresponding to an + audio + * output resource. + * @param[in] device_ref Identifies an audio output device. It could be one of + * the resource in the array returned by EnumerateDevices(), or 0 which means + * the default device. + * @param[in] config A PPB_AudioConfig audio configuration + * resource. + * @param[in] audio_output_callback A PPB_AudioOutput_Callback + * function that will be called when audio buffer needs to be filled. + * @param[inout] user_data An opaque pointer that will be passed into + * audio_output_callback. + * @param[in] callback A PP_CompletionCallback to run when this + * open operation is completed. + * + * @return An error code from pp_errors.h. + */ + int32_t (*Open)(PP_Resource audio_output, + PP_Resource device_ref, + PP_Resource config, + PPB_AudioOutput_Callback audio_output_callback, + void* user_data, + struct PP_CompletionCallback callback); + /** + * GetCurrrentConfig() returns an audio config resource for the given audio + * output resource. + * + * @param[in] config A PP_Resource corresponding to an audio + * output resource. + * + * @return A PP_Resource containing the audio config resource if + * successful. + */ + PP_Resource (*GetCurrentConfig)(PP_Resource audio_output); + /** + * StartPlayback() starts the playback of the audio output resource and begins + * periodically calling the callback. + * + * @param[in] config A PP_Resource corresponding to an audio + * output resource. + * + * @return A PP_Bool containing PP_TRUE if + * successful, otherwise PP_FALSE. Also returns + * PP_TRUE (and be a no-op) if called while playback is already + * in progress. + */ + PP_Bool (*StartPlayback)(PP_Resource audio_output); + /** + * StopPlayback() stops the playback of the audio resource. + * + * @param[in] config A PP_Resource corresponding to an audio + * output resource. + * + * @return A PP_Bool containing PP_TRUE if + * successful, otherwise PP_FALSE. Also returns + * PP_TRUE (and is a no-op) if called while playback is already + * stopped. If a callback is in progress, StopPlayback() will block until the + * callback completes. + */ + PP_Bool (*StopPlayback)(PP_Resource audio_output); + /** + * Close() closes the audio output device, + and stops playback if necessary. It is + * not valid to call Open() again after a call to this method. + * If an audio output resource is destroyed while a device is still open, then + * it will be implicitly closed, so you are not required to call this method. + * + * @param[in] audio_output A PP_Resource corresponding to an + audio + * output resource. + */ + void (*Close)(PP_Resource audio_output); +}; + +typedef struct PPB_AudioOutput_Dev_0_1 PPB_AudioOutput_Dev; +/** + * @} + */ + +#endif /* PPAPI_C_DEV_PPB_AUDIO_OUTPUT_DEV_H_ */ + diff --git a/ppapi/c/dev/ppb_device_ref_dev.h b/ppapi/c/dev/ppb_device_ref_dev.h index 62d75a6ad61ebb..2b8b18d0fa3192 100644 --- a/ppapi/c/dev/ppb_device_ref_dev.h +++ b/ppapi/c/dev/ppb_device_ref_dev.h @@ -3,7 +3,7 @@ * found in the LICENSE file. */ -/* From dev/ppb_device_ref_dev.idl modified Wed Nov 07 13:28:37 2012. */ +/* From dev/ppb_device_ref_dev.idl modified Mon Jan 09 12:04:09 2017. */ #ifndef PPAPI_C_DEV_PPB_DEVICE_REF_DEV_H_ #define PPAPI_C_DEV_PPB_DEVICE_REF_DEV_H_ @@ -55,7 +55,8 @@ typedef void (*PP_MonitorDeviceChangeCallback)(void* user_data, typedef enum { PP_DEVICETYPE_DEV_INVALID = 0, PP_DEVICETYPE_DEV_AUDIOCAPTURE = 1, - PP_DEVICETYPE_DEV_VIDEOCAPTURE = 2 + PP_DEVICETYPE_DEV_VIDEOCAPTURE = 2, + PP_DEVICETYPE_DEV_AUDIOOUTPUT = 3 } PP_DeviceType_Dev; PP_COMPILE_ASSERT_SIZE_IN_BYTES(PP_DeviceType_Dev, 4); /** diff --git a/ppapi/c/pp_macros.h b/ppapi/c/pp_macros.h index 16dc079d2f4d20..291d2d6c48421d 100644 --- a/ppapi/c/pp_macros.h +++ b/ppapi/c/pp_macros.h @@ -3,13 +3,13 @@ * found in the LICENSE file. */ -/* From pp_macros.idl modified Thu Oct 15 10:46:35 2015. */ +/* From pp_macros.idl modified Sat Dec 6 22:11:47 2014. */ #ifndef PPAPI_C_PP_MACROS_H_ #define PPAPI_C_PP_MACROS_H_ -#define PPAPI_RELEASE 55 +#define PPAPI_RELEASE 59 /** * @file diff --git a/ppapi/cpp/BUILD.gn b/ppapi/cpp/BUILD.gn index 586ca715e060aa..1252d7af87c09f 100644 --- a/ppapi/cpp/BUILD.gn +++ b/ppapi/cpp/BUILD.gn @@ -161,6 +161,8 @@ source_set("objects") { # Dev interfaces. "dev/audio_input_dev.cc", "dev/audio_input_dev.h", + "dev/audio_output_dev.cc", + "dev/audio_output_dev.h", "dev/buffer_dev.cc", "dev/buffer_dev.h", "dev/crypto_dev.cc", diff --git a/ppapi/cpp/dev/audio_output_dev.cc b/ppapi/cpp/dev/audio_output_dev.cc new file mode 100644 index 00000000000000..56e55b0923b91e --- /dev/null +++ b/ppapi/cpp/dev/audio_output_dev.cc @@ -0,0 +1,98 @@ +// 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/cpp/dev/audio_output_dev.h" + +#include "ppapi/c/pp_bool.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/cpp/instance_handle.h" +#include "ppapi/cpp/module_impl.h" + +namespace pp { + +namespace { + +template <> +const char* interface_name() { + return PPB_AUDIO_OUTPUT_DEV_INTERFACE_0_1; +} + +} // namespace + +AudioOutput_Dev::AudioOutput_Dev() {} + +AudioOutput_Dev::AudioOutput_Dev(const InstanceHandle& instance) { + if (has_interface()) { + PassRefFromConstructor(get_interface()->Create( + instance.pp_instance())); + } +} + +AudioOutput_Dev::~AudioOutput_Dev() {} + +// static +bool AudioOutput_Dev::IsAvailable() { + return has_interface(); +} + +int32_t AudioOutput_Dev::EnumerateDevices( + const CompletionCallbackWithOutput >& callback) { + if (has_interface()) { + return get_interface()->EnumerateDevices( + pp_resource(), callback.output(), callback.pp_completion_callback()); + } + + return callback.MayForce(PP_ERROR_NOINTERFACE); +} + +int32_t AudioOutput_Dev::MonitorDeviceChange( + PP_MonitorDeviceChangeCallback callback, + void* user_data) { + if (has_interface()) { + return get_interface()->MonitorDeviceChange( + pp_resource(), callback, user_data); + } + + return PP_ERROR_NOINTERFACE; +} + +int32_t AudioOutput_Dev::Open(const DeviceRef_Dev& device_ref, + const AudioConfig& config, + PPB_AudioOutput_Callback audio_output_callback, + void* user_data, + const CompletionCallback& callback) { + if (has_interface()) { + return get_interface()->Open( + pp_resource(), device_ref.pp_resource(), config.pp_resource(), + audio_output_callback, user_data, callback.pp_completion_callback()); + } + + return callback.MayForce(PP_ERROR_NOINTERFACE); +} + +bool AudioOutput_Dev::StartPlayback() { + if (has_interface()) { + return PP_ToBool( + get_interface()->StartPlayback(pp_resource())); + } + + return false; +} + +bool AudioOutput_Dev::StopPlayback() { + if (has_interface()) { + return PP_ToBool( + get_interface()->StopPlayback(pp_resource())); + } + + return false; +} + +void AudioOutput_Dev::Close() { + if (has_interface()) { + get_interface()->Close(pp_resource()); + } +} + +} // namespace pp diff --git a/ppapi/cpp/dev/audio_output_dev.h b/ppapi/cpp/dev/audio_output_dev.h new file mode 100644 index 00000000000000..ebe2fb505a91ba --- /dev/null +++ b/ppapi/cpp/dev/audio_output_dev.h @@ -0,0 +1,84 @@ +// 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. + +#ifndef PPAPI_CPP_DEV_AUDIO_OUTPUT_DEV_H_ +#define PPAPI_CPP_DEV_AUDIO_OUTPUT_DEV_H_ + +#include + +#include + +#include "ppapi/c/dev/ppb_audio_output_dev.h" +#include "ppapi/cpp/audio_config.h" +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/cpp/dev/device_ref_dev.h" +#include "ppapi/cpp/resource.h" + +namespace pp { + +class InstanceHandle; + +class AudioOutput_Dev : public Resource { + public: + // An empty constructor for an AudioOutput resource. + AudioOutput_Dev(); + + // Constructor to create an audio output resource. + explicit AudioOutput_Dev(const InstanceHandle& instance); + + virtual ~AudioOutput_Dev(); + + // Static function for determining whether the browser supports the required + // AudioOutput interface. + // + // @return true if the interface is available, false otherwise. + static bool IsAvailable(); + + int32_t EnumerateDevices( + const CompletionCallbackWithOutput >& cb); + + int32_t MonitorDeviceChange(PP_MonitorDeviceChangeCallback callback, + void* user_data); + + // If |device_ref| is null (i.e., is_null() returns true), the default device + // will be used. + int32_t Open(const DeviceRef_Dev& device_ref, + const AudioConfig& config, + PPB_AudioOutput_Callback audio_output_callback, + void* user_data, + const CompletionCallback& callback); + + // Getter function for returning the internal PPB_AudioConfig + // struct. + // + // @return A mutable reference to the PPB_AudioConfig struct. + AudioConfig& config() { return config_; } + + // Getter function for returning the internal PPB_AudioConfig + // struct. + // + // @return A const reference to the internal PPB_AudioConfig + // struct. + const AudioConfig& config() const { return config_; } + + // StartPlayback() starts playback of audio. + // + // @return true if successful, otherwise false. + bool StartPlayback(); + + // StopPlayback stops playback of audio. + // + // @return true if successful, otherwise false. + bool StopPlayback(); + + // Close closes the audio output device. + void Close(); + + private: + AudioConfig config_; +}; + +} // namespace pp + +#endif // PPAPI_CPP_DEV_AUDIO_OUTPUT_DEV_H_ diff --git a/ppapi/native_client/src/untrusted/pnacl_irt_shim/pnacl_shim.c b/ppapi/native_client/src/untrusted/pnacl_irt_shim/pnacl_shim.c index 56b190b8a37860..6e8a8aa7cde1b4 100644 --- a/ppapi/native_client/src/untrusted/pnacl_irt_shim/pnacl_shim.c +++ b/ppapi/native_client/src/untrusted/pnacl_irt_shim/pnacl_shim.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016 The Chromium Authors. All rights reserved. +/* 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. */ @@ -8,6 +8,7 @@ #include "ppapi/c/ppb.h" #include "ppapi/c/dev/ppb_audio_input_dev.h" +#include "ppapi/c/dev/ppb_audio_output_dev.h" #include "ppapi/c/dev/ppb_device_ref_dev.h" #include "ppapi/c/dev/ppb_file_chooser_dev.h" #include "ppapi/c/dev/ppb_ime_input_event_dev.h" @@ -156,6 +157,7 @@ static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_WebSocket_1_0; static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPP_Messaging_1_0; static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_AudioInput_Dev_0_3; static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_AudioInput_Dev_0_4; +static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_AudioOutput_Dev_0_1; static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_DeviceRef_Dev_0_1; static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_FileChooser_Dev_0_5; static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_FileChooser_Dev_0_6; @@ -2751,6 +2753,55 @@ static void Pnacl_M30_PPB_AudioInput_Dev_Close(PP_Resource audio_input) { /* End wrapper methods for PPB_AudioInput_Dev_0_4 */ +/* Begin wrapper methods for PPB_AudioOutput_Dev_0_1 */ + +static PP_Resource Pnacl_M59_PPB_AudioOutput_Dev_Create(PP_Instance instance) { + const struct PPB_AudioOutput_Dev_0_1 *iface = Pnacl_WrapperInfo_PPB_AudioOutput_Dev_0_1.real_iface; + return iface->Create(instance); +} + +static PP_Bool Pnacl_M59_PPB_AudioOutput_Dev_IsAudioOutput(PP_Resource resource) { + const struct PPB_AudioOutput_Dev_0_1 *iface = Pnacl_WrapperInfo_PPB_AudioOutput_Dev_0_1.real_iface; + return iface->IsAudioOutput(resource); +} + +static int32_t Pnacl_M59_PPB_AudioOutput_Dev_EnumerateDevices(PP_Resource audio_output, struct PP_ArrayOutput* output, struct PP_CompletionCallback* callback) { + const struct PPB_AudioOutput_Dev_0_1 *iface = Pnacl_WrapperInfo_PPB_AudioOutput_Dev_0_1.real_iface; + return iface->EnumerateDevices(audio_output, *output, *callback); +} + +static int32_t Pnacl_M59_PPB_AudioOutput_Dev_MonitorDeviceChange(PP_Resource audio_output, PP_MonitorDeviceChangeCallback callback, void* user_data) { + const struct PPB_AudioOutput_Dev_0_1 *iface = Pnacl_WrapperInfo_PPB_AudioOutput_Dev_0_1.real_iface; + return iface->MonitorDeviceChange(audio_output, callback, user_data); +} + +static int32_t Pnacl_M59_PPB_AudioOutput_Dev_Open(PP_Resource audio_output, PP_Resource device_ref, PP_Resource config, PPB_AudioOutput_Callback audio_output_callback, void* user_data, struct PP_CompletionCallback* callback) { + const struct PPB_AudioOutput_Dev_0_1 *iface = Pnacl_WrapperInfo_PPB_AudioOutput_Dev_0_1.real_iface; + return iface->Open(audio_output, device_ref, config, audio_output_callback, user_data, *callback); +} + +static PP_Resource Pnacl_M59_PPB_AudioOutput_Dev_GetCurrentConfig(PP_Resource audio_output) { + const struct PPB_AudioOutput_Dev_0_1 *iface = Pnacl_WrapperInfo_PPB_AudioOutput_Dev_0_1.real_iface; + return iface->GetCurrentConfig(audio_output); +} + +static PP_Bool Pnacl_M59_PPB_AudioOutput_Dev_StartPlayback(PP_Resource audio_output) { + const struct PPB_AudioOutput_Dev_0_1 *iface = Pnacl_WrapperInfo_PPB_AudioOutput_Dev_0_1.real_iface; + return iface->StartPlayback(audio_output); +} + +static PP_Bool Pnacl_M59_PPB_AudioOutput_Dev_StopPlayback(PP_Resource audio_output) { + const struct PPB_AudioOutput_Dev_0_1 *iface = Pnacl_WrapperInfo_PPB_AudioOutput_Dev_0_1.real_iface; + return iface->StopPlayback(audio_output); +} + +static void Pnacl_M59_PPB_AudioOutput_Dev_Close(PP_Resource audio_output) { + const struct PPB_AudioOutput_Dev_0_1 *iface = Pnacl_WrapperInfo_PPB_AudioOutput_Dev_0_1.real_iface; + iface->Close(audio_output); +} + +/* End wrapper methods for PPB_AudioOutput_Dev_0_1 */ + /* Not generating wrapper methods for PPB_Buffer_Dev_0_4 */ /* Not generating wrapper methods for PPB_Crypto_Dev_0_1 */ @@ -5408,6 +5459,18 @@ static const struct PPB_AudioInput_Dev_0_4 Pnacl_Wrappers_PPB_AudioInput_Dev_0_4 .Close = (void (*)(PP_Resource audio_input))&Pnacl_M30_PPB_AudioInput_Dev_Close }; +static const struct PPB_AudioOutput_Dev_0_1 Pnacl_Wrappers_PPB_AudioOutput_Dev_0_1 = { + .Create = (PP_Resource (*)(PP_Instance instance))&Pnacl_M59_PPB_AudioOutput_Dev_Create, + .IsAudioOutput = (PP_Bool (*)(PP_Resource resource))&Pnacl_M59_PPB_AudioOutput_Dev_IsAudioOutput, + .EnumerateDevices = (int32_t (*)(PP_Resource audio_output, struct PP_ArrayOutput output, struct PP_CompletionCallback callback))&Pnacl_M59_PPB_AudioOutput_Dev_EnumerateDevices, + .MonitorDeviceChange = (int32_t (*)(PP_Resource audio_output, PP_MonitorDeviceChangeCallback callback, void* user_data))&Pnacl_M59_PPB_AudioOutput_Dev_MonitorDeviceChange, + .Open = (int32_t (*)(PP_Resource audio_output, PP_Resource device_ref, PP_Resource config, PPB_AudioOutput_Callback audio_output_callback, void* user_data, struct PP_CompletionCallback callback))&Pnacl_M59_PPB_AudioOutput_Dev_Open, + .GetCurrentConfig = (PP_Resource (*)(PP_Resource audio_output))&Pnacl_M59_PPB_AudioOutput_Dev_GetCurrentConfig, + .StartPlayback = (PP_Bool (*)(PP_Resource audio_output))&Pnacl_M59_PPB_AudioOutput_Dev_StartPlayback, + .StopPlayback = (PP_Bool (*)(PP_Resource audio_output))&Pnacl_M59_PPB_AudioOutput_Dev_StopPlayback, + .Close = (void (*)(PP_Resource audio_output))&Pnacl_M59_PPB_AudioOutput_Dev_Close +}; + /* Not generating wrapper interface for PPB_Buffer_Dev_0_4 */ /* Not generating wrapper interface for PPB_Crypto_Dev_0_1 */ @@ -6322,6 +6385,12 @@ static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_AudioInput_Dev_0_4 = { .real_iface = NULL }; +static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_AudioOutput_Dev_0_1 = { + .iface_macro = PPB_AUDIO_OUTPUT_DEV_INTERFACE_0_1, + .wrapped_iface = (const void *) &Pnacl_Wrappers_PPB_AudioOutput_Dev_0_1, + .real_iface = NULL +}; + static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_DeviceRef_Dev_0_1 = { .iface_macro = PPB_DEVICEREF_DEV_INTERFACE_0_1, .wrapped_iface = (const void *) &Pnacl_Wrappers_PPB_DeviceRef_Dev_0_1, @@ -6690,6 +6759,7 @@ static struct __PnaclWrapperInfo *s_ppb_wrappers[] = { &Pnacl_WrapperInfo_PPB_WebSocket_1_0, &Pnacl_WrapperInfo_PPB_AudioInput_Dev_0_3, &Pnacl_WrapperInfo_PPB_AudioInput_Dev_0_4, + &Pnacl_WrapperInfo_PPB_AudioOutput_Dev_0_1, &Pnacl_WrapperInfo_PPB_DeviceRef_Dev_0_1, &Pnacl_WrapperInfo_PPB_FileChooser_Dev_0_5, &Pnacl_WrapperInfo_PPB_FileChooser_Dev_0_6, diff --git a/ppapi/proxy/BUILD.gn b/ppapi/proxy/BUILD.gn index da070f8f2245ed..b5727788952ca7 100644 --- a/ppapi/proxy/BUILD.gn +++ b/ppapi/proxy/BUILD.gn @@ -219,6 +219,8 @@ component("proxy") { sources += [ "audio_input_resource.cc", "audio_input_resource.h", + "audio_output_resource.cc", + "audio_output_resource.h", "broker_dispatcher.cc", "broker_dispatcher.h", "browser_font_singleton_resource.cc", diff --git a/ppapi/proxy/audio_output_resource.cc b/ppapi/proxy/audio_output_resource.cc new file mode 100644 index 00000000000000..6eeb0f08f72e14 --- /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 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 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(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(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 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 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 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( + RENDERER, msg, + base::Bind(&AudioOutputResource::OnPluginMsgOpenReply, + base::Unretained(this))); + return PP_OK_COMPLETIONPENDING; +} +} // namespace proxy +} // namespace ppapi diff --git a/ppapi/proxy/audio_output_resource.h b/ppapi/proxy/audio_output_resource.h new file mode 100644 index 00000000000000..ee654e1c2e07b4 --- /dev/null +++ b/ppapi/proxy/audio_output_resource.h @@ -0,0 +1,149 @@ +// 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. + +#ifndef PPAPI_PROXY_AUDIO_OUTPUT_RESOURCE_H_ +#define PPAPI_PROXY_AUDIO_OUTPUT_RESOURCE_H_ + +#include +#include + +#include + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/shared_memory.h" +#include "base/sync_socket.h" +#include "base/threading/simple_thread.h" +#include "ppapi/c/ppb_audio_config.h" +#include "ppapi/proxy/device_enumeration_resource_helper.h" +#include "ppapi/proxy/plugin_resource.h" +#include "ppapi/shared_impl/scoped_pp_resource.h" +#include "ppapi/thunk/ppb_audio_output_api.h" + +namespace media { +class AudioBus; +} + +namespace ppapi { +namespace proxy { + +class ResourceMessageReplyParams; + +class AudioOutputResource : public PluginResource, + public thunk::PPB_AudioOutput_API, + public base::DelegateSimpleThread::Delegate { + public: + AudioOutputResource(Connection connection, PP_Instance instance); + ~AudioOutputResource() override; + + // Resource overrides. + thunk::PPB_AudioOutput_API* AsPPB_AudioOutput_API() override; + void OnReplyReceived(const ResourceMessageReplyParams& params, + const IPC::Message& msg) override; + + // PPB_AudioOutput_API implementation. + int32_t EnumerateDevices(const PP_ArrayOutput& output, + scoped_refptr callback) override; + int32_t MonitorDeviceChange(PP_MonitorDeviceChangeCallback callback, + void* user_data) override; + int32_t Open(PP_Resource device_ref, + PP_Resource config, + PPB_AudioOutput_Callback audio_output_callback, + void* user_data, + scoped_refptr callback) override; + + PP_Resource GetCurrentConfig() override; + + bool playing() const { return playing_; } + + PP_Bool StartPlayback() override; + PP_Bool StopPlayback() override; + void Close() override; + + protected: + // Resource override. + void LastPluginRefWasDeleted() override; + + private: + enum OpenState { BEFORE_OPEN, OPENED, CLOSED }; + + void OnPluginMsgOpenReply(const ResourceMessageReplyParams& params); + + // Sets the shared memory and socket handles. + void SetStreamInfo(base::SharedMemoryHandle shared_memory_handle, + size_t shared_memory_size, + base::SyncSocket::Handle socket_handle); + + // Starts execution of the audio output thread. + void StartThread(); + + // Stops execution of the audio output thread. + void StopThread(); + + // DelegateSimpleThread::Delegate implementation. + // Run on the audio output thread. + void Run() override; + + int32_t CommonOpen(PP_Resource device_ref, + PP_Resource config, + PPB_AudioOutput_Callback audio_output_callback, + void* user_data, + scoped_refptr callback); + + OpenState open_state_; + + // True if playing the stream. + bool playing_; + + // Socket used to notify us when new samples are available. This pointer is + // created in SetStreamInfo(). + std::unique_ptr socket_; + + // Sample buffer in shared memory. This pointer is created in + // SetStreamInfo(). The memory is only mapped when the audio thread is + // created. + std::unique_ptr shared_memory_; + + // The size of the sample buffer in bytes. + size_t shared_memory_size_; + + // When the callback is set, this thread is spawned for calling it. + std::unique_ptr audio_output_thread_; + + // Callback to call when new samples are available. + PPB_AudioOutput_Callback audio_output_callback_; + + // User data pointer passed verbatim to the callback function. + void* user_data_; + + // The callback is not directly passed to OnPluginMsgOpenReply() because we + // would like to be able to cancel it early in Close(). + scoped_refptr open_callback_; + + // Owning reference to the current config object. This isn't actually used, + // we just dish it out as requested by the plugin. + ScopedPPResource config_; + + DeviceEnumerationResourceHelper enumeration_helper_; + + // The data size (in bytes) of one second of audio output. Used to calculate + // latency. + size_t bytes_per_second_; + + // AudioBus for shuttling data across the shared memory. + std::unique_ptr audio_bus_; + int sample_frame_count_; + + // Internal buffer for client's integer audio data. + int client_buffer_size_bytes_; + std::unique_ptr client_buffer_; + + DISALLOW_COPY_AND_ASSIGN(AudioOutputResource); +}; + +} // namespace proxy +} // namespace ppapi + +#endif // PPAPI_PROXY_AUDIO_OUTPUT_RESOURCE_H_ diff --git a/ppapi/proxy/interface_list.cc b/ppapi/proxy/interface_list.cc index 4984753685648d..d14a9318c54371 100644 --- a/ppapi/proxy/interface_list.cc +++ b/ppapi/proxy/interface_list.cc @@ -12,6 +12,7 @@ #include "base/memory/singleton.h" #include "build/build_config.h" #include "ppapi/c/dev/ppb_audio_input_dev.h" +#include "ppapi/c/dev/ppb_audio_output_dev.h" #include "ppapi/c/dev/ppb_buffer_dev.h" #include "ppapi/c/dev/ppb_char_set_dev.h" #include "ppapi/c/dev/ppb_crypto_dev.h" diff --git a/ppapi/proxy/ppapi_messages.h b/ppapi/proxy/ppapi_messages.h index c99a2764930b3c..39bcec7312a419 100644 --- a/ppapi/proxy/ppapi_messages.h +++ b/ppapi/proxy/ppapi_messages.h @@ -2246,6 +2246,18 @@ IPC_MESSAGE_CONTROL0(PpapiPluginMsg_AudioInput_OpenReply) IPC_MESSAGE_CONTROL1(PpapiHostMsg_AudioInput_StartOrStop, bool /* capture */) IPC_MESSAGE_CONTROL0(PpapiHostMsg_AudioInput_Close) +// Audio output. +IPC_MESSAGE_CONTROL0(PpapiHostMsg_AudioOutput_Create) +IPC_MESSAGE_CONTROL3(PpapiHostMsg_AudioOutput_Open, + std::string /* device_id */, + PP_AudioSampleRate /* sample_rate */, + uint32_t /* sample_frame_count */) +// Reply to an Open call. This supplies a socket handle and a shared memory +// handle. Both handles are passed in the ReplyParams struct. +IPC_MESSAGE_CONTROL0(PpapiPluginMsg_AudioOutput_OpenReply) +IPC_MESSAGE_CONTROL1(PpapiHostMsg_AudioOutput_StartOrStop, bool /* playback */) +IPC_MESSAGE_CONTROL0(PpapiHostMsg_AudioOutput_Close) + // BrowserFont ----------------------------------------------------------------- IPC_MESSAGE_CONTROL0(PpapiHostMsg_BrowserFontSingleton_Create) diff --git a/ppapi/proxy/resource_creation_proxy.cc b/ppapi/proxy/resource_creation_proxy.cc index 1c79bc5cf621b2..0e414285882a65 100644 --- a/ppapi/proxy/resource_creation_proxy.cc +++ b/ppapi/proxy/resource_creation_proxy.cc @@ -9,6 +9,7 @@ #include "ppapi/c/pp_size.h" #include "ppapi/proxy/audio_encoder_resource.h" #include "ppapi/proxy/audio_input_resource.h" +#include "ppapi/proxy/audio_output_resource.h" #include "ppapi/proxy/camera_device_resource.h" #include "ppapi/proxy/compositor_resource.h" #include "ppapi/proxy/connection.h" @@ -429,6 +430,10 @@ PP_Resource ResourceCreationProxy::CreateAudioInput( return (new AudioInputResource(GetConnection(), instance))->GetReference(); } +PP_Resource ResourceCreationProxy::CreateAudioOutput(PP_Instance instance) { + return (new AudioOutputResource(GetConnection(), instance))->GetReference(); +} + PP_Resource ResourceCreationProxy::CreateBroker(PP_Instance instance) { return PPB_Broker_Proxy::CreateProxyResource(instance); } diff --git a/ppapi/proxy/resource_creation_proxy.h b/ppapi/proxy/resource_creation_proxy.h index b7b0c31381f107..f83c53d96675f7 100644 --- a/ppapi/proxy/resource_creation_proxy.h +++ b/ppapi/proxy/resource_creation_proxy.h @@ -163,6 +163,7 @@ class ResourceCreationProxy : public InterfaceProxy, PP_Resource CreateX509CertificatePrivate(PP_Instance instance) override; #if !defined(OS_NACL) PP_Resource CreateAudioInput(PP_Instance instance) override; + PP_Resource CreateAudioOutput(PP_Instance instance) override; PP_Resource CreateBroker(PP_Instance instance) override; PP_Resource CreateBrowserFont( PP_Instance instance, diff --git a/ppapi/shared_impl/api_id.h b/ppapi/shared_impl/api_id.h index a049948522ea8c..ff8511d89daf85 100644 --- a/ppapi/shared_impl/api_id.h +++ b/ppapi/shared_impl/api_id.h @@ -15,6 +15,7 @@ enum ApiID { API_ID_PPB_AUDIO = 1, API_ID_PPB_AUDIO_CONFIG, API_ID_PPB_AUDIO_INPUT_DEV, + API_ID_PPB_AUDIO_OUTPUT_DEV, API_ID_PPB_BROKER, API_ID_PPB_BUFFER, API_ID_PPB_CONTEXT_3D, diff --git a/ppapi/shared_impl/resource.h b/ppapi/shared_impl/resource.h index 8d30ed46d06457..687f9615f859d5 100644 --- a/ppapi/shared_impl/resource.h +++ b/ppapi/shared_impl/resource.h @@ -24,6 +24,7 @@ F(PPB_AudioConfig_API) \ F(PPB_AudioEncoder_API) \ F(PPB_AudioInput_API) \ + F(PPB_AudioOutput_API) \ F(PPB_AudioTrusted_API) \ F(PPB_Broker_API) \ F(PPB_Broker_Instance_API) \ diff --git a/ppapi/thunk/BUILD.gn b/ppapi/thunk/BUILD.gn index 913748f44a7310..44ef7d05ecdcf6 100644 --- a/ppapi/thunk/BUILD.gn +++ b/ppapi/thunk/BUILD.gn @@ -30,6 +30,7 @@ source_set("thunk") { "ppb_audio_encoder_api.h", "ppb_audio_encoder_thunk.cc", "ppb_audio_input_api.h", + "ppb_audio_output_api.h", "ppb_audio_thunk.cc", "ppb_broker_api.h", "ppb_browser_font_singleton_api.h", @@ -163,6 +164,7 @@ source_set("thunk") { if (!is_nacl) { sources += [ "ppb_audio_input_dev_thunk.cc", + "ppb_audio_output_dev_thunk.cc", "ppb_broker_thunk.cc", "ppb_browser_font_trusted_thunk.cc", "ppb_buffer_thunk.cc", diff --git a/ppapi/thunk/interfaces_ppb_public_dev.h b/ppapi/thunk/interfaces_ppb_public_dev.h index ca28d014bc1abc..5c69935ae01531 100644 --- a/ppapi/thunk/interfaces_ppb_public_dev.h +++ b/ppapi/thunk/interfaces_ppb_public_dev.h @@ -26,6 +26,7 @@ PROXIED_API(PPB_VideoDecoder) PROXIED_IFACE(PPB_AUDIO_INPUT_DEV_INTERFACE_0_3, PPB_AudioInput_Dev_0_3) PROXIED_IFACE(PPB_AUDIO_INPUT_DEV_INTERFACE_0_4, PPB_AudioInput_Dev_0_4) +PROXIED_IFACE(PPB_AUDIO_OUTPUT_DEV_INTERFACE_0_1, PPB_AudioOutput_Dev_0_1) PROXIED_IFACE(PPB_BUFFER_DEV_INTERFACE_0_4, PPB_Buffer_Dev_0_4) PROXIED_IFACE(PPB_CHAR_SET_DEV_INTERFACE_0_4, PPB_CharSet_Dev_0_4) PROXIED_IFACE(PPB_CRYPTO_DEV_INTERFACE_0_1, PPB_Crypto_Dev_0_1) diff --git a/ppapi/thunk/ppb_audio_output_api.h b/ppapi/thunk/ppb_audio_output_api.h new file mode 100644 index 00000000000000..4fceee757d1111 --- /dev/null +++ b/ppapi/thunk/ppb_audio_output_api.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef PPAPI_THUNK_AUDIO_OUTPUT_API_H_ +#define PPAPI_THUNK_AUDIO_OUTPUT_API_H_ + +#include + +#include + +#include "base/memory/ref_counted.h" +#include "ppapi/c/dev/ppb_audio_output_dev.h" + +namespace ppapi { + +class TrackedCallback; + +namespace thunk { + +class PPB_AudioOutput_API { + public: + virtual ~PPB_AudioOutput_API() {} + + virtual int32_t EnumerateDevices(const PP_ArrayOutput& output, + scoped_refptr callback) = 0; + virtual int32_t MonitorDeviceChange(PP_MonitorDeviceChangeCallback callback, + void* user_data) = 0; + virtual int32_t Open(PP_Resource device_ref, + PP_Resource config, + PPB_AudioOutput_Callback audio_output_callback, + void* user_data, + scoped_refptr callback) = 0; + virtual PP_Resource GetCurrentConfig() = 0; + virtual PP_Bool StartPlayback() = 0; + virtual PP_Bool StopPlayback() = 0; + virtual void Close() = 0; +}; + +} // namespace thunk +} // namespace ppapi + +#endif // PPAPI_THUNK_AUDIO_OUTPUT_API_H_ diff --git a/ppapi/thunk/ppb_audio_output_dev_thunk.cc b/ppapi/thunk/ppb_audio_output_dev_thunk.cc new file mode 100644 index 00000000000000..a5f5b12c3753e8 --- /dev/null +++ b/ppapi/thunk/ppb_audio_output_dev_thunk.cc @@ -0,0 +1,116 @@ +// 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. + +// From dev/ppb_audio_output_dev.idl modified Fri Mar 31 08:08:16 2017. + +#include + +#include "ppapi/c/dev/ppb_audio_output_dev.h" +#include "ppapi/c/pp_completion_callback.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/shared_impl/tracked_callback.h" +#include "ppapi/thunk/enter.h" +#include "ppapi/thunk/ppapi_thunk_export.h" +#include "ppapi/thunk/ppb_audio_output_api.h" + +namespace ppapi { +namespace thunk { + +namespace { + +PP_Resource Create(PP_Instance instance) { + VLOG(4) << "PPB_AudioOutput_Dev::Create()"; + EnterResourceCreation enter(instance); + if (enter.failed()) + return 0; + return enter.functions()->CreateAudioOutput(instance); +} + +PP_Bool IsAudioOutput(PP_Resource resource) { + VLOG(4) << "PPB_AudioOutput_Dev::IsAudioOutput()"; + EnterResource enter(resource, false); + return PP_FromBool(enter.succeeded()); +} + +int32_t EnumerateDevices(PP_Resource audio_output, + struct PP_ArrayOutput output, + struct PP_CompletionCallback callback) { + VLOG(4) << "PPB_AudioOutput_Dev::EnumerateDevices()"; + EnterResource enter(audio_output, callback, true); + if (enter.failed()) + return enter.retval(); + return enter.SetResult( + enter.object()->EnumerateDevices(output, enter.callback())); +} + +int32_t MonitorDeviceChange(PP_Resource audio_output, + PP_MonitorDeviceChangeCallback callback, + void* user_data) { + VLOG(4) << "PPB_AudioOutput_Dev::MonitorDeviceChange()"; + EnterResource enter(audio_output, true); + if (enter.failed()) + return enter.retval(); + return enter.object()->MonitorDeviceChange(callback, user_data); +} + +int32_t Open(PP_Resource audio_output, + PP_Resource device_ref, + PP_Resource config, + PPB_AudioOutput_Callback audio_output_callback, + void* user_data, + struct PP_CompletionCallback callback) { + VLOG(4) << "PPB_AudioOutput_Dev::Open()"; + EnterResource enter(audio_output, callback, true); + if (enter.failed()) + return enter.retval(); + return enter.SetResult(enter.object()->Open( + device_ref, config, audio_output_callback, user_data, enter.callback())); +} + +PP_Resource GetCurrentConfig(PP_Resource audio_output) { + VLOG(4) << "PPB_AudioOutput_Dev::GetCurrentConfig()"; + EnterResource enter(audio_output, true); + if (enter.failed()) + return 0; + return enter.object()->GetCurrentConfig(); +} + +PP_Bool StartPlayback(PP_Resource audio_output) { + VLOG(4) << "PPB_AudioOutput_Dev::StartPlayback()"; + EnterResource enter(audio_output, true); + if (enter.failed()) + return PP_FALSE; + return enter.object()->StartPlayback(); +} + +PP_Bool StopPlayback(PP_Resource audio_output) { + VLOG(4) << "PPB_AudioOutput_Dev::StopPlayback()"; + EnterResource enter(audio_output, true); + if (enter.failed()) + return PP_FALSE; + return enter.object()->StopPlayback(); +} + +void Close(PP_Resource audio_output) { + VLOG(4) << "PPB_AudioOutput_Dev::Close()"; + EnterResource enter(audio_output, true); + if (enter.failed()) + return; + enter.object()->Close(); +} + +const PPB_AudioOutput_Dev_0_1 g_ppb_audiooutput_dev_thunk_0_1 = { + &Create, &IsAudioOutput, &EnumerateDevices, &MonitorDeviceChange, + &Open, &GetCurrentConfig, &StartPlayback, &StopPlayback, + &Close}; + +} // namespace + +PPAPI_THUNK_EXPORT const PPB_AudioOutput_Dev_0_1* +GetPPB_AudioOutput_Dev_0_1_Thunk() { + return &g_ppb_audiooutput_dev_thunk_0_1; +} + +} // namespace thunk +} // namespace ppapi diff --git a/ppapi/thunk/resource_creation_api.h b/ppapi/thunk/resource_creation_api.h index aba7f8b4d5baf2..682f6803bba6bf 100644 --- a/ppapi/thunk/resource_creation_api.h +++ b/ppapi/thunk/resource_creation_api.h @@ -185,6 +185,7 @@ class ResourceCreationAPI { virtual PP_Resource CreateX509CertificatePrivate(PP_Instance instance) = 0; #if !defined(OS_NACL) virtual PP_Resource CreateAudioInput(PP_Instance instance) = 0; + virtual PP_Resource CreateAudioOutput(PP_Instance instance) = 0; virtual PP_Resource CreateBroker(PP_Instance instance) = 0; virtual PP_Resource CreateBrowserFont( PP_Instance instance, diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index 96cbcd3eaac937..2a918a2e7d7a98 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -106879,6 +106879,7 @@ from previous Chrome versions. +