xzhang | 3a2a470 | 2017-04-07 16:34:30 | [diff] [blame] | 1 | // Copyright (c) 2017 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "ppapi/proxy/audio_output_resource.h" |
| 6 | |
Peter Boström | fb60ea0 | 2021-04-05 21:06:12 | [diff] [blame] | 7 | #include <memory> |
| 8 | |
xzhang | 3a2a470 | 2017-04-07 16:34:30 | [diff] [blame] | 9 | #include "base/bind.h" |
Hans Wennborg | 708fa82 | 2020-04-27 17:23:15 | [diff] [blame] | 10 | #include "base/check_op.h" |
xzhang | 3a2a470 | 2017-04-07 16:34:30 | [diff] [blame] | 11 | #include "base/numerics/safe_conversions.h" |
| 12 | #include "ipc/ipc_platform_file.h" |
| 13 | #include "media/base/audio_bus.h" |
| 14 | #include "media/base/audio_parameters.h" |
| 15 | #include "ppapi/c/pp_errors.h" |
| 16 | #include "ppapi/proxy/ppapi_messages.h" |
| 17 | #include "ppapi/proxy/resource_message_params.h" |
| 18 | #include "ppapi/proxy/serialized_handle.h" |
| 19 | #include "ppapi/shared_impl/ppapi_globals.h" |
| 20 | #include "ppapi/shared_impl/ppb_audio_config_shared.h" |
| 21 | #include "ppapi/shared_impl/resource_tracker.h" |
| 22 | #include "ppapi/shared_impl/tracked_callback.h" |
| 23 | #include "ppapi/thunk/enter.h" |
| 24 | #include "ppapi/thunk/ppb_audio_config_api.h" |
| 25 | |
| 26 | namespace ppapi { |
| 27 | namespace proxy { |
| 28 | |
| 29 | AudioOutputResource::AudioOutputResource(Connection connection, |
| 30 | PP_Instance instance) |
| 31 | : PluginResource(connection, instance), |
| 32 | open_state_(BEFORE_OPEN), |
| 33 | playing_(false), |
| 34 | shared_memory_size_(0), |
| 35 | audio_output_callback_(NULL), |
| 36 | user_data_(NULL), |
| 37 | enumeration_helper_(this), |
| 38 | bytes_per_second_(0), |
| 39 | sample_frame_count_(0), |
| 40 | client_buffer_size_bytes_(0) { |
| 41 | SendCreate(RENDERER, PpapiHostMsg_AudioOutput_Create()); |
| 42 | } |
| 43 | |
| 44 | AudioOutputResource::~AudioOutputResource() { |
| 45 | Close(); |
| 46 | } |
| 47 | |
| 48 | thunk::PPB_AudioOutput_API* AudioOutputResource::AsPPB_AudioOutput_API() { |
| 49 | return this; |
| 50 | } |
| 51 | |
| 52 | void AudioOutputResource::OnReplyReceived( |
| 53 | const ResourceMessageReplyParams& params, |
| 54 | const IPC::Message& msg) { |
| 55 | if (!enumeration_helper_.HandleReply(params, msg)) |
| 56 | PluginResource::OnReplyReceived(params, msg); |
| 57 | } |
| 58 | |
| 59 | int32_t AudioOutputResource::EnumerateDevices( |
| 60 | const PP_ArrayOutput& output, |
| 61 | scoped_refptr<TrackedCallback> callback) { |
| 62 | return enumeration_helper_.EnumerateDevices(output, callback); |
| 63 | } |
| 64 | |
| 65 | int32_t AudioOutputResource::MonitorDeviceChange( |
| 66 | PP_MonitorDeviceChangeCallback callback, |
| 67 | void* user_data) { |
| 68 | return enumeration_helper_.MonitorDeviceChange(callback, user_data); |
| 69 | } |
| 70 | |
| 71 | int32_t AudioOutputResource::Open( |
| 72 | PP_Resource device_ref, |
| 73 | PP_Resource config, |
| 74 | PPB_AudioOutput_Callback audio_output_callback, |
| 75 | void* user_data, |
| 76 | scoped_refptr<TrackedCallback> callback) { |
| 77 | return CommonOpen(device_ref, config, audio_output_callback, user_data, |
| 78 | callback); |
| 79 | } |
| 80 | |
| 81 | PP_Resource AudioOutputResource::GetCurrentConfig() { |
| 82 | // AddRef for the caller. |
| 83 | if (config_.get()) |
| 84 | PpapiGlobals::Get()->GetResourceTracker()->AddRefResource(config_); |
| 85 | return config_; |
| 86 | } |
| 87 | |
| 88 | PP_Bool AudioOutputResource::StartPlayback() { |
| 89 | if (open_state_ == CLOSED || (open_state_ == BEFORE_OPEN && |
| 90 | !TrackedCallback::IsPending(open_callback_))) { |
| 91 | return PP_FALSE; |
| 92 | } |
| 93 | if (playing_) |
| 94 | return PP_TRUE; |
| 95 | |
| 96 | playing_ = true; |
| 97 | |
| 98 | StartThread(); |
| 99 | |
| 100 | Post(RENDERER, PpapiHostMsg_AudioOutput_StartOrStop(true)); |
| 101 | return PP_TRUE; |
| 102 | } |
| 103 | |
| 104 | PP_Bool AudioOutputResource::StopPlayback() { |
| 105 | if (open_state_ == CLOSED) |
| 106 | return PP_FALSE; |
| 107 | if (!playing_) |
| 108 | return PP_TRUE; |
| 109 | |
| 110 | // If the audio output device hasn't been opened, set |playing_| to false and |
| 111 | // return directly. |
| 112 | if (open_state_ == BEFORE_OPEN) { |
| 113 | playing_ = false; |
| 114 | return PP_TRUE; |
| 115 | } |
| 116 | |
| 117 | Post(RENDERER, PpapiHostMsg_AudioOutput_StartOrStop(false)); |
| 118 | |
| 119 | StopThread(); |
| 120 | playing_ = false; |
| 121 | |
| 122 | return PP_TRUE; |
| 123 | } |
| 124 | |
| 125 | void AudioOutputResource::Close() { |
| 126 | if (open_state_ == CLOSED) |
| 127 | return; |
| 128 | |
| 129 | open_state_ = CLOSED; |
| 130 | Post(RENDERER, PpapiHostMsg_AudioOutput_Close()); |
| 131 | StopThread(); |
| 132 | |
| 133 | if (TrackedCallback::IsPending(open_callback_)) |
| 134 | open_callback_->PostAbort(); |
| 135 | } |
| 136 | |
| 137 | void AudioOutputResource::LastPluginRefWasDeleted() { |
| 138 | enumeration_helper_.LastPluginRefWasDeleted(); |
| 139 | } |
| 140 | |
| 141 | void AudioOutputResource::OnPluginMsgOpenReply( |
| 142 | const ResourceMessageReplyParams& params) { |
| 143 | if (open_state_ == BEFORE_OPEN && params.result() == PP_OK) { |
| 144 | IPC::PlatformFileForTransit socket_handle_for_transit = |
| 145 | IPC::InvalidPlatformFileForTransit(); |
| 146 | params.TakeSocketHandleAtIndex(0, &socket_handle_for_transit); |
| 147 | base::SyncSocket::Handle socket_handle = |
| 148 | IPC::PlatformFileForTransitToPlatformFile(socket_handle_for_transit); |
| 149 | CHECK(socket_handle != base::SyncSocket::kInvalidHandle); |
| 150 | |
| 151 | SerializedHandle serialized_shared_memory_handle = |
Alexandr Ilin | 20f2841c | 2018-06-01 11:56:18 | [diff] [blame] | 152 | params.TakeHandleOfTypeAtIndex(1, |
| 153 | SerializedHandle::SHARED_MEMORY_REGION); |
xzhang | 3a2a470 | 2017-04-07 16:34:30 | [diff] [blame] | 154 | CHECK(serialized_shared_memory_handle.IsHandleValid()); |
| 155 | |
| 156 | open_state_ = OPENED; |
Alexandr Ilin | 20f2841c | 2018-06-01 11:56:18 | [diff] [blame] | 157 | SetStreamInfo(base::UnsafeSharedMemoryRegion::Deserialize( |
| 158 | serialized_shared_memory_handle.TakeSharedMemoryRegion()), |
| 159 | socket_handle); |
xzhang | 3a2a470 | 2017-04-07 16:34:30 | [diff] [blame] | 160 | } else { |
| 161 | playing_ = false; |
| 162 | } |
| 163 | |
| 164 | // The callback may have been aborted by Close(). |
| 165 | if (TrackedCallback::IsPending(open_callback_)) |
| 166 | open_callback_->Run(params.result()); |
| 167 | } |
| 168 | |
| 169 | void AudioOutputResource::SetStreamInfo( |
Alexandr Ilin | 20f2841c | 2018-06-01 11:56:18 | [diff] [blame] | 170 | base::UnsafeSharedMemoryRegion shared_memory_region, |
xzhang | 3a2a470 | 2017-04-07 16:34:30 | [diff] [blame] | 171 | base::SyncSocket::Handle socket_handle) { |
Peter Boström | fb60ea0 | 2021-04-05 21:06:12 | [diff] [blame] | 172 | socket_ = std::make_unique<base::CancelableSyncSocket>(socket_handle); |
xzhang | 3a2a470 | 2017-04-07 16:34:30 | [diff] [blame] | 173 | |
Max Morin | 30996fb | 2017-09-01 13:28:35 | [diff] [blame] | 174 | // Ensure that the allocated memory is enough for the audio bus and buffer |
| 175 | // parameters. Note that there might be slightly more allocated memory as |
| 176 | // some shared memory implementations round up to the closest 2^n when |
| 177 | // allocating. |
| 178 | // Example: DCHECK_GE(8208, 8192 + 16) for |sample_frame_count_| = 2048. |
| 179 | shared_memory_size_ = media::ComputeAudioOutputBufferSize( |
| 180 | kAudioOutputChannels, sample_frame_count_); |
Alexandr Ilin | 20f2841c | 2018-06-01 11:56:18 | [diff] [blame] | 181 | DCHECK_GE(shared_memory_region.GetSize(), shared_memory_size_); |
Max Morin | 30996fb | 2017-09-01 13:28:35 | [diff] [blame] | 182 | |
xzhang | 3a2a470 | 2017-04-07 16:34:30 | [diff] [blame] | 183 | // If we fail to map the shared memory into the caller's address space we |
| 184 | // might as well fail here since nothing will work if this is the case. |
Alexandr Ilin | 20f2841c | 2018-06-01 11:56:18 | [diff] [blame] | 185 | shared_memory_mapping_ = shared_memory_region.MapAt(0, shared_memory_size_); |
| 186 | CHECK(shared_memory_mapping_.IsValid()); |
xzhang | 3a2a470 | 2017-04-07 16:34:30 | [diff] [blame] | 187 | |
| 188 | // Create a new audio bus and wrap the audio data section in shared memory. |
| 189 | media::AudioOutputBuffer* buffer = |
Alexandr Ilin | 20f2841c | 2018-06-01 11:56:18 | [diff] [blame] | 190 | static_cast<media::AudioOutputBuffer*>(shared_memory_mapping_.memory()); |
xzhang | 3a2a470 | 2017-04-07 16:34:30 | [diff] [blame] | 191 | audio_bus_ = media::AudioBus::WrapMemory(kAudioOutputChannels, |
| 192 | sample_frame_count_, buffer->audio); |
| 193 | |
xzhang | 3a2a470 | 2017-04-07 16:34:30 | [diff] [blame] | 194 | // Setup integer audio buffer for user audio data |
| 195 | client_buffer_size_bytes_ = audio_bus_->frames() * audio_bus_->channels() * |
| 196 | kBitsPerAudioOutputSample / 8; |
| 197 | client_buffer_.reset(new uint8_t[client_buffer_size_bytes_]); |
| 198 | } |
| 199 | |
| 200 | void AudioOutputResource::StartThread() { |
| 201 | // Don't start the thread unless all our state is set up correctly. |
Alexandr Ilin | 20f2841c | 2018-06-01 11:56:18 | [diff] [blame] | 202 | if (!audio_output_callback_ || !socket_.get() || |
| 203 | !shared_memory_mapping_.memory() || !audio_bus_.get() || |
| 204 | !client_buffer_.get() || bytes_per_second_ == 0) |
xzhang | 3a2a470 | 2017-04-07 16:34:30 | [diff] [blame] | 205 | return; |
| 206 | |
| 207 | // Clear contents of shm buffer before starting audio thread. This will |
| 208 | // prevent a burst of static if for some reason the audio thread doesn't |
| 209 | // start up quickly enough. |
Alexandr Ilin | 20f2841c | 2018-06-01 11:56:18 | [diff] [blame] | 210 | memset(shared_memory_mapping_.memory(), 0, shared_memory_size_); |
xzhang | 3a2a470 | 2017-04-07 16:34:30 | [diff] [blame] | 211 | memset(client_buffer_.get(), 0, client_buffer_size_bytes_); |
| 212 | |
| 213 | DCHECK(!audio_output_thread_.get()); |
Peter Boström | fb60ea0 | 2021-04-05 21:06:12 | [diff] [blame] | 214 | audio_output_thread_ = std::make_unique<base::DelegateSimpleThread>( |
| 215 | this, "plugin_audio_output_thread"); |
xzhang | 3a2a470 | 2017-04-07 16:34:30 | [diff] [blame] | 216 | audio_output_thread_->Start(); |
| 217 | } |
| 218 | |
| 219 | void AudioOutputResource::StopThread() { |
| 220 | // Shut down the socket to escape any hanging |Receive|s. |
| 221 | if (socket_.get()) |
| 222 | socket_->Shutdown(); |
| 223 | if (audio_output_thread_.get()) { |
| 224 | audio_output_thread_->Join(); |
| 225 | audio_output_thread_.reset(); |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | void AudioOutputResource::Run() { |
| 230 | // The shared memory represents AudioOutputBufferParameters and the actual |
| 231 | // data buffer stored as an audio bus. |
| 232 | media::AudioOutputBuffer* buffer = |
Alexandr Ilin | 20f2841c | 2018-06-01 11:56:18 | [diff] [blame] | 233 | static_cast<media::AudioOutputBuffer*>(shared_memory_mapping_.memory()); |
xzhang | 3a2a470 | 2017-04-07 16:34:30 | [diff] [blame] | 234 | |
| 235 | // This is a constantly increasing counter that is used to verify on the |
| 236 | // browser side that buffers are in sync. |
| 237 | uint32_t buffer_index = 0; |
| 238 | |
| 239 | while (true) { |
| 240 | int pending_data = 0; |
| 241 | size_t bytes_read = socket_->Receive(&pending_data, sizeof(pending_data)); |
| 242 | if (bytes_read != sizeof(pending_data)) { |
| 243 | DCHECK_EQ(bytes_read, 0U); |
| 244 | break; |
| 245 | } |
| 246 | if (pending_data < 0) |
| 247 | break; |
| 248 | |
| 249 | { |
Peter Kasting | e5a38ed | 2021-10-02 03:06:35 | [diff] [blame] | 250 | base::TimeDelta delay = base::Microseconds(buffer->params.delay_us); |
xzhang | 3a2a470 | 2017-04-07 16:34:30 | [diff] [blame] | 251 | |
| 252 | audio_output_callback_(client_buffer_.get(), client_buffer_size_bytes_, |
| 253 | delay.InSecondsF(), user_data_); |
| 254 | } |
| 255 | |
| 256 | // Deinterleave the audio data into the shared memory as floats. |
Raul Tambre | b1da244 | 2019-04-07 18:18:03 | [diff] [blame] | 257 | static_assert(kBitsPerAudioOutputSample == 16, |
| 258 | "FromInterleaved expects 2 bytes."); |
| 259 | audio_bus_->FromInterleaved<media::SignedInt16SampleTypeTraits>( |
| 260 | reinterpret_cast<int16_t*>(client_buffer_.get()), audio_bus_->frames()); |
xzhang | 3a2a470 | 2017-04-07 16:34:30 | [diff] [blame] | 261 | |
| 262 | // Inform other side that we have read the data from the shared memory. |
| 263 | // Let the other end know which buffer we just filled. The buffer index is |
| 264 | // used to ensure the other end is getting the buffer it expects. For more |
| 265 | // details on how this works see AudioSyncReader::WaitUntilDataIsReady(). |
| 266 | ++buffer_index; |
| 267 | size_t bytes_sent = socket_->Send(&buffer_index, sizeof(buffer_index)); |
| 268 | if (bytes_sent != sizeof(buffer_index)) { |
| 269 | DCHECK_EQ(bytes_sent, 0U); |
| 270 | break; |
| 271 | } |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | int32_t AudioOutputResource::CommonOpen( |
| 276 | PP_Resource device_ref, |
| 277 | PP_Resource config, |
| 278 | PPB_AudioOutput_Callback audio_output_callback, |
| 279 | void* user_data, |
| 280 | scoped_refptr<TrackedCallback> callback) { |
| 281 | std::string device_id; |
| 282 | // |device_id| remains empty if |device_ref| is 0, which means the default |
| 283 | // device. |
| 284 | if (device_ref != 0) { |
| 285 | thunk::EnterResourceNoLock<thunk::PPB_DeviceRef_API> enter_device_ref( |
| 286 | device_ref, true); |
| 287 | if (enter_device_ref.failed()) |
| 288 | return PP_ERROR_BADRESOURCE; |
| 289 | device_id = enter_device_ref.object()->GetDeviceRefData().id; |
| 290 | } |
| 291 | |
| 292 | if (TrackedCallback::IsPending(open_callback_)) |
| 293 | return PP_ERROR_INPROGRESS; |
| 294 | if (open_state_ != BEFORE_OPEN) |
| 295 | return PP_ERROR_FAILED; |
| 296 | |
| 297 | if (!audio_output_callback) |
| 298 | return PP_ERROR_BADARGUMENT; |
| 299 | thunk::EnterResourceNoLock<thunk::PPB_AudioConfig_API> enter_config(config, |
| 300 | true); |
| 301 | if (enter_config.failed()) |
| 302 | return PP_ERROR_BADARGUMENT; |
| 303 | |
| 304 | config_ = config; |
| 305 | audio_output_callback_ = audio_output_callback; |
| 306 | user_data_ = user_data; |
| 307 | open_callback_ = callback; |
| 308 | bytes_per_second_ = kAudioOutputChannels * (kBitsPerAudioOutputSample / 8) * |
| 309 | enter_config.object()->GetSampleRate(); |
| 310 | sample_frame_count_ = enter_config.object()->GetSampleFrameCount(); |
| 311 | |
| 312 | PpapiHostMsg_AudioOutput_Open msg( |
| 313 | device_id, enter_config.object()->GetSampleRate(), |
| 314 | enter_config.object()->GetSampleFrameCount()); |
| 315 | Call<PpapiPluginMsg_AudioOutput_OpenReply>( |
| 316 | RENDERER, msg, |
Anand K Mistry | 9182c2a7 | 2021-03-17 04:40:08 | [diff] [blame] | 317 | base::BindOnce(&AudioOutputResource::OnPluginMsgOpenReply, |
| 318 | base::Unretained(this))); |
xzhang | 3a2a470 | 2017-04-07 16:34:30 | [diff] [blame] | 319 | return PP_OK_COMPLETIONPENDING; |
| 320 | } |
| 321 | } // namespace proxy |
| 322 | } // namespace ppapi |