| // Copyright (c) 2012 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_input_resource.h" | |
| #include <string> | |
| #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 { | |
| AudioInputResource::AudioInputResource(Connection connection, | |
| PP_Instance instance) | |
| : PluginResource(connection, instance), | |
| open_state_(BEFORE_OPEN), | |
| capturing_(false), | |
| shared_memory_size_(0), | |
| audio_input_callback_0_3_(NULL), | |
| audio_input_callback_(NULL), | |
| user_data_(NULL), | |
| enumeration_helper_(this), | |
| bytes_per_second_(0), | |
| sample_frame_count_(0), | |
| client_buffer_size_bytes_(0) { | |
| SendCreate(RENDERER, PpapiHostMsg_AudioInput_Create()); | |
| } | |
| AudioInputResource::~AudioInputResource() { | |
| Close(); | |
| } | |
| thunk::PPB_AudioInput_API* AudioInputResource::AsPPB_AudioInput_API() { | |
| return this; | |
| } | |
| void AudioInputResource::OnReplyReceived( | |
| const ResourceMessageReplyParams& params, | |
| const IPC::Message& msg) { | |
| if (!enumeration_helper_.HandleReply(params, msg)) | |
| PluginResource::OnReplyReceived(params, msg); | |
| } | |
| int32_t AudioInputResource::EnumerateDevices( | |
| const PP_ArrayOutput& output, | |
| scoped_refptr<TrackedCallback> callback) { | |
| return enumeration_helper_.EnumerateDevices(output, callback); | |
| } | |
| int32_t AudioInputResource::MonitorDeviceChange( | |
| PP_MonitorDeviceChangeCallback callback, | |
| void* user_data) { | |
| return enumeration_helper_.MonitorDeviceChange(callback, user_data); | |
| } | |
| int32_t AudioInputResource::Open0_3( | |
| PP_Resource device_ref, | |
| PP_Resource config, | |
| PPB_AudioInput_Callback_0_3 audio_input_callback_0_3, | |
| void* user_data, | |
| scoped_refptr<TrackedCallback> callback) { | |
| return CommonOpen(device_ref, config, audio_input_callback_0_3, NULL, | |
| user_data, callback); | |
| } | |
| int32_t AudioInputResource::Open(PP_Resource device_ref, | |
| PP_Resource config, | |
| PPB_AudioInput_Callback audio_input_callback, | |
| void* user_data, | |
| scoped_refptr<TrackedCallback> callback) { | |
| return CommonOpen(device_ref, config, NULL, audio_input_callback, user_data, | |
| callback); | |
| } | |
| PP_Resource AudioInputResource::GetCurrentConfig() { | |
| // AddRef for the caller. | |
| if (config_.get()) | |
| PpapiGlobals::Get()->GetResourceTracker()->AddRefResource(config_); | |
| return config_; | |
| } | |
| PP_Bool AudioInputResource::StartCapture() { | |
| if (open_state_ == CLOSED || (open_state_ == BEFORE_OPEN && | |
| !TrackedCallback::IsPending(open_callback_))) { | |
| return PP_FALSE; | |
| } | |
| if (capturing_) | |
| return PP_TRUE; | |
| capturing_ = true; | |
| // Return directly if the audio input device hasn't been opened. Capturing | |
| // will be started once the open operation is completed. | |
| if (open_state_ == BEFORE_OPEN) | |
| return PP_TRUE; | |
| StartThread(); | |
| Post(RENDERER, PpapiHostMsg_AudioInput_StartOrStop(true)); | |
| return PP_TRUE; | |
| } | |
| PP_Bool AudioInputResource::StopCapture() { | |
| if (open_state_ == CLOSED) | |
| return PP_FALSE; | |
| if (!capturing_) | |
| return PP_TRUE; | |
| // If the audio input device hasn't been opened, set |capturing_| to false and | |
| // return directly. | |
| if (open_state_ == BEFORE_OPEN) { | |
| capturing_ = false; | |
| return PP_TRUE; | |
| } | |
| Post(RENDERER, PpapiHostMsg_AudioInput_StartOrStop(false)); | |
| StopThread(); | |
| capturing_ = false; | |
| return PP_TRUE; | |
| } | |
| void AudioInputResource::Close() { | |
| if (open_state_ == CLOSED) | |
| return; | |
| open_state_ = CLOSED; | |
| Post(RENDERER, PpapiHostMsg_AudioInput_Close()); | |
| StopThread(); | |
| if (TrackedCallback::IsPending(open_callback_)) | |
| open_callback_->PostAbort(); | |
| } | |
| void AudioInputResource::LastPluginRefWasDeleted() { | |
| enumeration_helper_.LastPluginRefWasDeleted(); | |
| } | |
| void AudioInputResource::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_REGION); | |
| CHECK(serialized_shared_memory_handle.IsHandleValid()); | |
| open_state_ = OPENED; | |
| SetStreamInfo(base::ReadOnlySharedMemoryRegion::Deserialize( | |
| serialized_shared_memory_handle.TakeSharedMemoryRegion()), | |
| socket_handle); | |
| } else { | |
| capturing_ = false; | |
| } | |
| // The callback may have been aborted by Close(). | |
| if (TrackedCallback::IsPending(open_callback_)) | |
| open_callback_->Run(params.result()); | |
| } | |
| void AudioInputResource::SetStreamInfo( | |
| base::ReadOnlySharedMemoryRegion shared_memory_region, | |
| base::SyncSocket::Handle socket_handle) { | |
| socket_.reset(new base::CancelableSyncSocket(socket_handle)); | |
| DCHECK(!shared_memory_mapping_.IsValid()); | |
| // Ensure that the allocated memory is enough for the audio bus and buffer | |
| // parameters. Note that there might be slightly more allocated memory as | |
| // some shared memory implementations round up to the closest 2^n when | |
| // allocating. | |
| // Example: DCHECK_GE(8208, 8192 + 16) for |sample_frame_count_| = 2048. | |
| shared_memory_size_ = media::ComputeAudioInputBufferSize( | |
| kAudioInputChannels, sample_frame_count_, 1u); | |
| DCHECK_GE(shared_memory_region.GetSize(), shared_memory_size_); | |
| // 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. | |
| shared_memory_mapping_ = shared_memory_region.MapAt(0, shared_memory_size_); | |
| CHECK(shared_memory_mapping_.IsValid()); | |
| // Create a new audio bus and wrap the audio data section in shared memory. | |
| const media::AudioInputBuffer* buffer = | |
| static_cast<const media::AudioInputBuffer*>( | |
| shared_memory_mapping_.memory()); | |
| audio_bus_ = media::AudioBus::WrapReadOnlyMemory( | |
| kAudioInputChannels, sample_frame_count_, buffer->audio); | |
| // Create an extra integer audio buffer for user audio data callbacks. | |
| // Data in shared memory will be copied to this buffer, after interleaving | |
| // and truncation, before each input callback to match the format expected | |
| // by the client. | |
| client_buffer_size_bytes_ = audio_bus_->frames() * audio_bus_->channels() * | |
| kBitsPerAudioInputSample / 8; | |
| client_buffer_.reset(new uint8_t[client_buffer_size_bytes_]); | |
| // There is a pending capture request before SetStreamInfo(). | |
| if (capturing_) { | |
| // Set |capturing_| to false so that the state looks consistent to | |
| // StartCapture(), which will reset it to true. | |
| capturing_ = false; | |
| StartCapture(); | |
| } | |
| } | |
| void AudioInputResource::StartThread() { | |
| // Don't start the thread unless all our state is set up correctly. | |
| if ((!audio_input_callback_0_3_ && !audio_input_callback_) || | |
| !socket_.get() || !capturing_ || !shared_memory_mapping_.memory() || | |
| !audio_bus_.get() || !client_buffer_.get()) { | |
| return; | |
| } | |
| DCHECK(!audio_input_thread_.get()); | |
| audio_input_thread_.reset(new base::DelegateSimpleThread( | |
| this, "plugin_audio_input_thread")); | |
| audio_input_thread_->Start(); | |
| } | |
| void AudioInputResource::StopThread() { | |
| // Shut down the socket to escape any hanging |Receive|s. | |
| if (socket_.get()) | |
| socket_->Shutdown(); | |
| if (audio_input_thread_.get()) { | |
| audio_input_thread_->Join(); | |
| audio_input_thread_.reset(); | |
| } | |
| } | |
| void AudioInputResource::Run() { | |
| // The shared memory represents AudioInputBufferParameters and the actual data | |
| // buffer stored as an audio bus. | |
| const media::AudioInputBuffer* buffer = | |
| static_cast<const media::AudioInputBuffer*>( | |
| shared_memory_mapping_.memory()); | |
| const uint32_t audio_bus_size_bytes = | |
| base::checked_cast<uint32_t>(shared_memory_size_ - | |
| sizeof(media::AudioInputBufferParameters)); | |
| // 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; | |
| // Convert an AudioBus from deinterleaved float to interleaved integer data. | |
| // Store the result in a preallocated |client_buffer_|. | |
| audio_bus_->ToInterleaved(audio_bus_->frames(), | |
| kBitsPerAudioInputSample / 8, | |
| client_buffer_.get()); | |
| // Inform other side that we have read the data from the shared memory. | |
| ++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; | |
| } | |
| // While closing the stream, we may receive buffers whose size is different | |
| // from |data_buffer_size|. | |
| CHECK_LE(buffer->params.size, audio_bus_size_bytes); | |
| if (buffer->params.size > 0) { | |
| if (audio_input_callback_) { | |
| PP_TimeDelta latency = | |
| static_cast<double>(pending_data) / bytes_per_second_; | |
| audio_input_callback_(client_buffer_.get(), | |
| client_buffer_size_bytes_, | |
| latency, | |
| user_data_); | |
| } else { | |
| audio_input_callback_0_3_( | |
| client_buffer_.get(), client_buffer_size_bytes_, user_data_); | |
| } | |
| } | |
| } | |
| } | |
| int32_t AudioInputResource::CommonOpen( | |
| PP_Resource device_ref, | |
| PP_Resource config, | |
| PPB_AudioInput_Callback_0_3 audio_input_callback_0_3, | |
| PPB_AudioInput_Callback audio_input_callback, | |
| void* user_data, | |
| scoped_refptr<TrackedCallback> callback) { | |
| std::string device_id; | |
| // |device_id| remains empty if |device_ref| is 0, which means the default | |
| // device. | |
| if (device_ref != 0) { | |
| thunk::EnterResourceNoLock<thunk::PPB_DeviceRef_API> enter_device_ref( | |
| device_ref, true); | |
| if (enter_device_ref.failed()) | |
| return PP_ERROR_BADRESOURCE; | |
| device_id = enter_device_ref.object()->GetDeviceRefData().id; | |
| } | |
| if (TrackedCallback::IsPending(open_callback_)) | |
| return PP_ERROR_INPROGRESS; | |
| if (open_state_ != BEFORE_OPEN) | |
| return PP_ERROR_FAILED; | |
| if (!audio_input_callback_0_3 && !audio_input_callback) | |
| return PP_ERROR_BADARGUMENT; | |
| thunk::EnterResourceNoLock<thunk::PPB_AudioConfig_API> enter_config(config, | |
| true); | |
| if (enter_config.failed()) | |
| return PP_ERROR_BADARGUMENT; | |
| config_ = config; | |
| audio_input_callback_0_3_ = audio_input_callback_0_3; | |
| audio_input_callback_ = audio_input_callback; | |
| user_data_ = user_data; | |
| open_callback_ = callback; | |
| bytes_per_second_ = kAudioInputChannels * (kBitsPerAudioInputSample / 8) * | |
| enter_config.object()->GetSampleRate(); | |
| sample_frame_count_ = enter_config.object()->GetSampleFrameCount(); | |
| PpapiHostMsg_AudioInput_Open msg( | |
| device_id, enter_config.object()->GetSampleRate(), | |
| enter_config.object()->GetSampleFrameCount()); | |
| Call<PpapiPluginMsg_AudioInput_OpenReply>( | |
| RENDERER, msg, | |
| base::Bind(&AudioInputResource::OnPluginMsgOpenReply, | |
| base::Unretained(this))); | |
| return PP_OK_COMPLETIONPENDING; | |
| } | |
| } // namespace proxy | |
| } // namespace ppapi |