blob: 33f7eda038c713ea4c66b7e1655d0f76cefd183a [file] [log] [blame]
xzhang3a2a4702017-04-07 16:34:301// 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ömfb60ea02021-04-05 21:06:127#include <memory>
8
xzhang3a2a4702017-04-07 16:34:309#include "base/bind.h"
Hans Wennborg708fa822020-04-27 17:23:1510#include "base/check_op.h"
xzhang3a2a4702017-04-07 16:34:3011#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
26namespace ppapi {
27namespace proxy {
28
29AudioOutputResource::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
44AudioOutputResource::~AudioOutputResource() {
45 Close();
46}
47
48thunk::PPB_AudioOutput_API* AudioOutputResource::AsPPB_AudioOutput_API() {
49 return this;
50}
51
52void 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
59int32_t AudioOutputResource::EnumerateDevices(
60 const PP_ArrayOutput& output,
61 scoped_refptr<TrackedCallback> callback) {
62 return enumeration_helper_.EnumerateDevices(output, callback);
63}
64
65int32_t AudioOutputResource::MonitorDeviceChange(
66 PP_MonitorDeviceChangeCallback callback,
67 void* user_data) {
68 return enumeration_helper_.MonitorDeviceChange(callback, user_data);
69}
70
71int32_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
81PP_Resource AudioOutputResource::GetCurrentConfig() {
82 // AddRef for the caller.
83 if (config_.get())
84 PpapiGlobals::Get()->GetResourceTracker()->AddRefResource(config_);
85 return config_;
86}
87
88PP_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
104PP_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
125void 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
137void AudioOutputResource::LastPluginRefWasDeleted() {
138 enumeration_helper_.LastPluginRefWasDeleted();
139}
140
141void 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 Ilin20f2841c2018-06-01 11:56:18152 params.TakeHandleOfTypeAtIndex(1,
153 SerializedHandle::SHARED_MEMORY_REGION);
xzhang3a2a4702017-04-07 16:34:30154 CHECK(serialized_shared_memory_handle.IsHandleValid());
155
156 open_state_ = OPENED;
Alexandr Ilin20f2841c2018-06-01 11:56:18157 SetStreamInfo(base::UnsafeSharedMemoryRegion::Deserialize(
158 serialized_shared_memory_handle.TakeSharedMemoryRegion()),
159 socket_handle);
xzhang3a2a4702017-04-07 16:34:30160 } 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
169void AudioOutputResource::SetStreamInfo(
Alexandr Ilin20f2841c2018-06-01 11:56:18170 base::UnsafeSharedMemoryRegion shared_memory_region,
xzhang3a2a4702017-04-07 16:34:30171 base::SyncSocket::Handle socket_handle) {
Peter Boströmfb60ea02021-04-05 21:06:12172 socket_ = std::make_unique<base::CancelableSyncSocket>(socket_handle);
xzhang3a2a4702017-04-07 16:34:30173
Max Morin30996fb2017-09-01 13:28:35174 // 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 Ilin20f2841c2018-06-01 11:56:18181 DCHECK_GE(shared_memory_region.GetSize(), shared_memory_size_);
Max Morin30996fb2017-09-01 13:28:35182
xzhang3a2a4702017-04-07 16:34:30183 // 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 Ilin20f2841c2018-06-01 11:56:18185 shared_memory_mapping_ = shared_memory_region.MapAt(0, shared_memory_size_);
186 CHECK(shared_memory_mapping_.IsValid());
xzhang3a2a4702017-04-07 16:34:30187
188 // Create a new audio bus and wrap the audio data section in shared memory.
189 media::AudioOutputBuffer* buffer =
Alexandr Ilin20f2841c2018-06-01 11:56:18190 static_cast<media::AudioOutputBuffer*>(shared_memory_mapping_.memory());
xzhang3a2a4702017-04-07 16:34:30191 audio_bus_ = media::AudioBus::WrapMemory(kAudioOutputChannels,
192 sample_frame_count_, buffer->audio);
193
xzhang3a2a4702017-04-07 16:34:30194 // 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
200void AudioOutputResource::StartThread() {
201 // Don't start the thread unless all our state is set up correctly.
Alexandr Ilin20f2841c2018-06-01 11:56:18202 if (!audio_output_callback_ || !socket_.get() ||
203 !shared_memory_mapping_.memory() || !audio_bus_.get() ||
204 !client_buffer_.get() || bytes_per_second_ == 0)
xzhang3a2a4702017-04-07 16:34:30205 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 Ilin20f2841c2018-06-01 11:56:18210 memset(shared_memory_mapping_.memory(), 0, shared_memory_size_);
xzhang3a2a4702017-04-07 16:34:30211 memset(client_buffer_.get(), 0, client_buffer_size_bytes_);
212
213 DCHECK(!audio_output_thread_.get());
Peter Boströmfb60ea02021-04-05 21:06:12214 audio_output_thread_ = std::make_unique<base::DelegateSimpleThread>(
215 this, "plugin_audio_output_thread");
xzhang3a2a4702017-04-07 16:34:30216 audio_output_thread_->Start();
217}
218
219void 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
229void AudioOutputResource::Run() {
230 // The shared memory represents AudioOutputBufferParameters and the actual
231 // data buffer stored as an audio bus.
232 media::AudioOutputBuffer* buffer =
Alexandr Ilin20f2841c2018-06-01 11:56:18233 static_cast<media::AudioOutputBuffer*>(shared_memory_mapping_.memory());
xzhang3a2a4702017-04-07 16:34:30234
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 Kastinge5a38ed2021-10-02 03:06:35250 base::TimeDelta delay = base::Microseconds(buffer->params.delay_us);
xzhang3a2a4702017-04-07 16:34:30251
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 Tambreb1da2442019-04-07 18:18:03257 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());
xzhang3a2a4702017-04-07 16:34:30261
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
275int32_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 Mistry9182c2a72021-03-17 04:40:08317 base::BindOnce(&AudioOutputResource::OnPluginMsgOpenReply,
318 base::Unretained(this)));
xzhang3a2a4702017-04-07 16:34:30319 return PP_OK_COMPLETIONPENDING;
320}
321} // namespace proxy
322} // namespace ppapi