1// Copyright (c) 2012 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_input_resource.h"
6
7#include "base/bind.h"
8#include "base/logging.h"
9#include "ipc/ipc_platform_file.h"
10#include "media/audio/audio_parameters.h"
11#include "media/audio/shared_memory_util.h"
12#include "ppapi/c/pp_errors.h"
13#include "ppapi/proxy/ppapi_messages.h"
14#include "ppapi/proxy/resource_message_params.h"
15#include "ppapi/proxy/serialized_handle.h"
16#include "ppapi/shared_impl/ppapi_globals.h"
17#include "ppapi/shared_impl/ppb_audio_config_shared.h"
18#include "ppapi/shared_impl/resource_tracker.h"
19#include "ppapi/shared_impl/tracked_callback.h"
20#include "ppapi/thunk/enter.h"
21#include "ppapi/thunk/ppb_audio_config_api.h"
22
23namespace ppapi {
24namespace proxy {
25
26AudioInputResource::AudioInputResource(
27    Connection connection,
28    PP_Instance instance)
29    : PluginResource(connection, instance),
30      open_state_(BEFORE_OPEN),
31      capturing_(false),
32      shared_memory_size_(0),
33      audio_input_callback_0_2_(NULL),
34      audio_input_callback_(NULL),
35      user_data_(NULL),
36      enumeration_helper_(this),
37      bytes_per_second_(0) {
38  SendCreate(RENDERER, PpapiHostMsg_AudioInput_Create());
39}
40
41AudioInputResource::~AudioInputResource() {
42  Close();
43}
44
45thunk::PPB_AudioInput_API* AudioInputResource::AsPPB_AudioInput_API() {
46  return this;
47}
48
49void AudioInputResource::OnReplyReceived(
50    const ResourceMessageReplyParams& params,
51    const IPC::Message& msg) {
52  if (!enumeration_helper_.HandleReply(params, msg))
53    PluginResource::OnReplyReceived(params, msg);
54}
55
56int32_t AudioInputResource::EnumerateDevices0_2(
57    PP_Resource* devices,
58    scoped_refptr<TrackedCallback> callback) {
59  return enumeration_helper_.EnumerateDevices0_2(devices, callback);
60}
61
62int32_t AudioInputResource::EnumerateDevices(
63    const PP_ArrayOutput& output,
64    scoped_refptr<TrackedCallback> callback) {
65  return enumeration_helper_.EnumerateDevices(output, callback);
66}
67
68int32_t AudioInputResource::MonitorDeviceChange(
69    PP_MonitorDeviceChangeCallback callback,
70    void* user_data) {
71  return enumeration_helper_.MonitorDeviceChange(callback, user_data);
72}
73
74int32_t AudioInputResource::Open0_2(
75    PP_Resource device_ref,
76    PP_Resource config,
77    PPB_AudioInput_Callback_0_2 audio_input_callback_0_2,
78    void* user_data,
79    scoped_refptr<TrackedCallback> callback) {
80  return CommonOpen(device_ref, config, audio_input_callback_0_2, NULL,
81                    user_data, callback);
82}
83
84int32_t AudioInputResource::Open(PP_Resource device_ref,
85                                 PP_Resource config,
86                                 PPB_AudioInput_Callback audio_input_callback,
87                                 void* user_data,
88                                 scoped_refptr<TrackedCallback> callback) {
89  return CommonOpen(device_ref, config, NULL, audio_input_callback, user_data,
90                    callback);
91}
92
93PP_Resource AudioInputResource::GetCurrentConfig() {
94  // AddRef for the caller.
95  if (config_.get())
96    PpapiGlobals::Get()->GetResourceTracker()->AddRefResource(config_);
97  return config_;
98}
99
100PP_Bool AudioInputResource::StartCapture() {
101  if (open_state_ == CLOSED || (open_state_ == BEFORE_OPEN &&
102                                !TrackedCallback::IsPending(open_callback_))) {
103    return PP_FALSE;
104  }
105  if (capturing_)
106    return PP_TRUE;
107
108  capturing_ = true;
109  // Return directly if the audio input device hasn't been opened. Capturing
110  // will be started once the open operation is completed.
111  if (open_state_ == BEFORE_OPEN)
112    return PP_TRUE;
113
114  StartThread();
115
116  Post(RENDERER, PpapiHostMsg_AudioInput_StartOrStop(true));
117  return PP_TRUE;
118}
119
120PP_Bool AudioInputResource::StopCapture() {
121  if (open_state_ == CLOSED)
122    return PP_FALSE;
123  if (!capturing_)
124    return PP_TRUE;
125
126  // If the audio input device hasn't been opened, set |capturing_| to false and
127  // return directly.
128  if (open_state_ == BEFORE_OPEN) {
129    capturing_ = false;
130    return PP_TRUE;
131  }
132
133  Post(RENDERER, PpapiHostMsg_AudioInput_StartOrStop(false));
134
135  StopThread();
136  capturing_ = false;
137
138  return PP_TRUE;
139}
140
141void AudioInputResource::Close() {
142  if (open_state_ == CLOSED)
143    return;
144
145  open_state_ = CLOSED;
146  Post(RENDERER, PpapiHostMsg_AudioInput_Close());
147  StopThread();
148
149  if (TrackedCallback::IsPending(open_callback_))
150    open_callback_->PostAbort();
151}
152
153void AudioInputResource::LastPluginRefWasDeleted() {
154  enumeration_helper_.LastPluginRefWasDeleted();
155}
156
157void AudioInputResource::OnPluginMsgOpenReply(
158    const ResourceMessageReplyParams& params) {
159  if (open_state_ == BEFORE_OPEN && params.result() == PP_OK) {
160    IPC::PlatformFileForTransit socket_handle_for_transit =
161        IPC::InvalidPlatformFileForTransit();
162    params.TakeSocketHandleAtIndex(0, &socket_handle_for_transit);
163    base::SyncSocket::Handle socket_handle =
164        IPC::PlatformFileForTransitToPlatformFile(socket_handle_for_transit);
165    CHECK(socket_handle != base::SyncSocket::kInvalidHandle);
166
167    SerializedHandle serialized_shared_memory_handle =
168        params.TakeHandleOfTypeAtIndex(1, SerializedHandle::SHARED_MEMORY);
169    CHECK(serialized_shared_memory_handle.IsHandleValid());
170
171    // See the comment in pepper_audio_input_host.cc about how we must call
172    // TotalSharedMemorySizeInBytes to get the actual size of the buffer. Here,
173    // we must call PacketSizeInBytes to get back the size of the audio buffer,
174    // excluding the bytes that audio uses for book-keeping.
175    size_t shared_memory_size = media::PacketSizeInBytes(
176        serialized_shared_memory_handle.size());
177
178    open_state_ = OPENED;
179    SetStreamInfo(serialized_shared_memory_handle.shmem(), shared_memory_size,
180                  socket_handle);
181  } else {
182    capturing_ = false;
183  }
184
185  // The callback may have been aborted by Close().
186  if (TrackedCallback::IsPending(open_callback_))
187    open_callback_->Run(params.result());
188}
189
190void AudioInputResource::SetStreamInfo(
191    base::SharedMemoryHandle shared_memory_handle,
192    size_t shared_memory_size,
193    base::SyncSocket::Handle socket_handle) {
194  socket_.reset(new base::CancelableSyncSocket(socket_handle));
195  shared_memory_.reset(new base::SharedMemory(shared_memory_handle, false));
196  shared_memory_size_ = shared_memory_size;
197
198  if (!shared_memory_->Map(shared_memory_size_)) {
199    PpapiGlobals::Get()->LogWithSource(
200        pp_instance(),
201        PP_LOGLEVEL_WARNING,
202        std::string(),
203        "Failed to map shared memory for PPB_AudioInput_Shared.");
204  }
205
206  // There is a pending capture request before SetStreamInfo().
207  if (capturing_) {
208    // Set |capturing_| to false so that the state looks consistent to
209    // StartCapture(), which will reset it to true.
210    capturing_ = false;
211    StartCapture();
212  }
213}
214
215void AudioInputResource::StartThread() {
216  // Don't start the thread unless all our state is set up correctly.
217  if ((!audio_input_callback_0_2_ && !audio_input_callback_) ||
218      !socket_.get() || !capturing_ || !shared_memory_->memory()) {
219    return;
220  }
221  DCHECK(!audio_input_thread_.get());
222  audio_input_thread_.reset(new base::DelegateSimpleThread(
223      this, "plugin_audio_input_thread"));
224  audio_input_thread_->Start();
225}
226
227void AudioInputResource::StopThread() {
228  // Shut down the socket to escape any hanging |Receive|s.
229  if (socket_.get())
230    socket_->Shutdown();
231  if (audio_input_thread_.get()) {
232    audio_input_thread_->Join();
233    audio_input_thread_.reset();
234  }
235}
236
237void AudioInputResource::Run() {
238  // The shared memory represents AudioInputBufferParameters and the actual data
239  // buffer.
240  media::AudioInputBuffer* buffer =
241      static_cast<media::AudioInputBuffer*>(shared_memory_->memory());
242  uint32_t data_buffer_size =
243      shared_memory_size_ - sizeof(media::AudioInputBufferParameters);
244  int pending_data;
245
246  while (sizeof(pending_data) == socket_->Receive(&pending_data,
247                                                  sizeof(pending_data)) &&
248         pending_data >= 0) {
249    // While closing the stream, we may receive buffers whose size is different
250    // from |data_buffer_size|.
251    CHECK_LE(buffer->params.size, data_buffer_size);
252    if (buffer->params.size > 0) {
253      if (audio_input_callback_) {
254        PP_TimeDelta latency =
255            static_cast<double>(pending_data) / bytes_per_second_;
256        audio_input_callback_(&buffer->audio[0], buffer->params.size, latency,
257                              user_data_);
258      } else {
259        audio_input_callback_0_2_(&buffer->audio[0], buffer->params.size,
260                                  user_data_);
261      }
262    }
263  }
264}
265
266int32_t AudioInputResource::CommonOpen(
267    PP_Resource device_ref,
268    PP_Resource config,
269    PPB_AudioInput_Callback_0_2 audio_input_callback_0_2,
270    PPB_AudioInput_Callback audio_input_callback,
271    void* user_data,
272    scoped_refptr<TrackedCallback> callback) {
273  std::string device_id;
274  // |device_id| remains empty if |device_ref| is 0, which means the default
275  // device.
276  if (device_ref != 0) {
277    thunk::EnterResourceNoLock<thunk::PPB_DeviceRef_API> enter_device_ref(
278        device_ref, true);
279    if (enter_device_ref.failed())
280      return PP_ERROR_BADRESOURCE;
281    device_id = enter_device_ref.object()->GetDeviceRefData().id;
282  }
283
284  if (TrackedCallback::IsPending(open_callback_))
285    return PP_ERROR_INPROGRESS;
286  if (open_state_ != BEFORE_OPEN)
287    return PP_ERROR_FAILED;
288
289  if (!audio_input_callback_0_2 && !audio_input_callback)
290    return PP_ERROR_BADARGUMENT;
291  thunk::EnterResourceNoLock<thunk::PPB_AudioConfig_API> enter_config(config,
292                                                                      true);
293  if (enter_config.failed())
294    return PP_ERROR_BADARGUMENT;
295
296  config_ = config;
297  audio_input_callback_0_2_ = audio_input_callback_0_2;
298  audio_input_callback_ = audio_input_callback;
299  user_data_ = user_data;
300  open_callback_ = callback;
301  bytes_per_second_ = kAudioInputChannels * (kBitsPerAudioInputSample / 8) *
302                      enter_config.object()->GetSampleRate();
303
304  PpapiHostMsg_AudioInput_Open msg(
305      device_id, enter_config.object()->GetSampleRate(),
306      enter_config.object()->GetSampleFrameCount());
307  Call<PpapiPluginMsg_AudioInput_OpenReply>(
308      RENDERER, msg,
309      base::Bind(&AudioInputResource::OnPluginMsgOpenReply,
310                 base::Unretained(this)));
311  return PP_OK_COMPLETIONPENDING;
312}
313}  // namespace proxy
314}  // namespace ppapi
315