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/ppb_audio_proxy.h"
6
7#include "base/compiler_specific.h"
8#include "base/threading/simple_thread.h"
9#include "ppapi/c/pp_errors.h"
10#include "ppapi/c/ppb_audio.h"
11#include "ppapi/c/ppb_audio_config.h"
12#include "ppapi/c/ppb_var.h"
13#include "ppapi/proxy/enter_proxy.h"
14#include "ppapi/proxy/plugin_dispatcher.h"
15#include "ppapi/proxy/ppapi_messages.h"
16#include "ppapi/shared_impl/api_id.h"
17#include "ppapi/shared_impl/platform_file.h"
18#include "ppapi/shared_impl/ppapi_globals.h"
19#include "ppapi/shared_impl/ppb_audio_shared.h"
20#include "ppapi/shared_impl/resource.h"
21#include "ppapi/thunk/ppb_audio_config_api.h"
22#include "ppapi/thunk/enter.h"
23#include "ppapi/thunk/resource_creation_api.h"
24#include "ppapi/thunk/thunk.h"
25
26using ppapi::IntToPlatformFile;
27using ppapi::proxy::SerializedHandle;
28using ppapi::thunk::EnterResourceNoLock;
29using ppapi::thunk::PPB_Audio_API;
30using ppapi::thunk::PPB_AudioConfig_API;
31
32namespace ppapi {
33namespace proxy {
34
35class Audio : public Resource, public PPB_Audio_Shared {
36 public:
37  Audio(const HostResource& audio_id,
38        PP_Resource config_id,
39        const AudioCallbackCombined& callback,
40        void* user_data);
41  virtual ~Audio();
42
43  // Resource overrides.
44  virtual PPB_Audio_API* AsPPB_Audio_API();
45
46  // PPB_Audio_API implementation.
47  virtual PP_Resource GetCurrentConfig() OVERRIDE;
48  virtual PP_Bool StartPlayback() OVERRIDE;
49  virtual PP_Bool StopPlayback() OVERRIDE;
50  virtual int32_t Open(
51      PP_Resource config_id,
52      scoped_refptr<TrackedCallback> create_callback) OVERRIDE;
53  virtual int32_t GetSyncSocket(int* sync_socket) OVERRIDE;
54  virtual int32_t GetSharedMemory(int* shm_handle, uint32_t* shm_size) OVERRIDE;
55
56 private:
57  // Owning reference to the current config object. This isn't actually used,
58  // we just dish it out as requested by the plugin.
59  PP_Resource config_;
60
61  DISALLOW_COPY_AND_ASSIGN(Audio);
62};
63
64Audio::Audio(const HostResource& audio_id,
65             PP_Resource config_id,
66             const AudioCallbackCombined& callback,
67             void* user_data)
68    : Resource(OBJECT_IS_PROXY, audio_id),
69      config_(config_id) {
70  SetCallback(callback, user_data);
71  PpapiGlobals::Get()->GetResourceTracker()->AddRefResource(config_);
72}
73
74Audio::~Audio() {
75#if defined(OS_NACL)
76  // Invoke StopPlayback() to ensure audio back-end has a chance to send the
77  // escape value over the sync socket, which will terminate the client side
78  // audio callback loop.  This is required for NaCl Plugins that can't escape
79  // by shutting down the sync_socket.
80  StopPlayback();
81#endif
82  PpapiGlobals::Get()->GetResourceTracker()->ReleaseResource(config_);
83}
84
85PPB_Audio_API* Audio::AsPPB_Audio_API() {
86  return this;
87}
88
89PP_Resource Audio::GetCurrentConfig() {
90  // AddRef for the caller.
91  PpapiGlobals::Get()->GetResourceTracker()->AddRefResource(config_);
92  return config_;
93}
94
95PP_Bool Audio::StartPlayback() {
96  if (playing())
97    return PP_TRUE;
98  if (!PPB_Audio_Shared::IsThreadFunctionReady())
99    return PP_FALSE;
100  SetStartPlaybackState();
101  PluginDispatcher::GetForResource(this)->Send(
102      new PpapiHostMsg_PPBAudio_StartOrStop(
103          API_ID_PPB_AUDIO, host_resource(), true));
104  return PP_TRUE;
105}
106
107PP_Bool Audio::StopPlayback() {
108  if (!playing())
109    return PP_TRUE;
110  PluginDispatcher::GetForResource(this)->Send(
111      new PpapiHostMsg_PPBAudio_StartOrStop(
112          API_ID_PPB_AUDIO, host_resource(), false));
113  SetStopPlaybackState();
114  return PP_TRUE;
115}
116
117int32_t Audio::Open(PP_Resource config_id,
118                    scoped_refptr<TrackedCallback> create_callback) {
119  return PP_ERROR_NOTSUPPORTED;  // Don't proxy the trusted interface.
120}
121
122int32_t Audio::GetSyncSocket(int* sync_socket) {
123  return PP_ERROR_NOTSUPPORTED;  // Don't proxy the trusted interface.
124}
125
126int32_t Audio::GetSharedMemory(int* shm_handle, uint32_t* shm_size) {
127  return PP_ERROR_NOTSUPPORTED;  // Don't proxy the trusted interface.
128}
129
130PPB_Audio_Proxy::PPB_Audio_Proxy(Dispatcher* dispatcher)
131    : InterfaceProxy(dispatcher),
132      callback_factory_(this) {
133}
134
135PPB_Audio_Proxy::~PPB_Audio_Proxy() {
136}
137
138// static
139PP_Resource PPB_Audio_Proxy::CreateProxyResource(
140    PP_Instance instance_id,
141    PP_Resource config_id,
142    const AudioCallbackCombined& audio_callback,
143    void* user_data) {
144  PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(instance_id);
145  if (!dispatcher)
146    return 0;
147
148  EnterResourceNoLock<PPB_AudioConfig_API> config(config_id, true);
149  if (config.failed())
150    return 0;
151
152  if (!audio_callback.IsValid())
153    return 0;
154
155  HostResource result;
156  dispatcher->Send(new PpapiHostMsg_PPBAudio_Create(
157      API_ID_PPB_AUDIO, instance_id,
158      config.object()->GetSampleRate(), config.object()->GetSampleFrameCount(),
159      &result));
160  if (result.is_null())
161    return 0;
162
163  return (new Audio(result, config_id,
164                    audio_callback, user_data))->GetReference();
165}
166
167bool PPB_Audio_Proxy::OnMessageReceived(const IPC::Message& msg) {
168  bool handled = true;
169  IPC_BEGIN_MESSAGE_MAP(PPB_Audio_Proxy, msg)
170// Don't build host side into NaCl IRT.
171#if !defined(OS_NACL)
172    IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBAudio_Create, OnMsgCreate)
173    IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBAudio_StartOrStop,
174                        OnMsgStartOrStop)
175#endif
176    IPC_MESSAGE_HANDLER(PpapiMsg_PPBAudio_NotifyAudioStreamCreated,
177                        OnMsgNotifyAudioStreamCreated)
178    IPC_MESSAGE_UNHANDLED(handled = false)
179  IPC_END_MESSAGE_MAP()
180  return handled;
181}
182
183#if !defined(OS_NACL)
184void PPB_Audio_Proxy::OnMsgCreate(PP_Instance instance_id,
185                                  int32_t sample_rate,
186                                  uint32_t sample_frame_count,
187                                  HostResource* result) {
188  thunk::EnterResourceCreation resource_creation(instance_id);
189  if (resource_creation.failed())
190    return;
191
192  // Make the resource and get the API pointer to its trusted interface.
193  result->SetHostResource(
194      instance_id,
195      resource_creation.functions()->CreateAudioTrusted(instance_id));
196  if (result->is_null())
197    return;
198
199  // At this point, we've set the result resource, and this is a sync request.
200  // Anything below this point must issue the AudioChannelConnected callback
201  // to the browser. Since that's an async message, it will be issued back to
202  // the plugin after the Create function returns (which is good because it
203  // would be weird to get a connected message with a failure code for a
204  // resource you haven't finished creating yet).
205  //
206  // The ...ForceCallback class will help ensure the callback is always called.
207  // All error cases must call SetResult on this class.
208  EnterHostFromHostResourceForceCallback<PPB_Audio_API> enter(
209      *result, callback_factory_,
210      &PPB_Audio_Proxy::AudioChannelConnected, *result);
211  if (enter.failed())
212    return;  // When enter fails, it will internally schedule the callback.
213
214  // Make an audio config object.
215  PP_Resource audio_config_res =
216      resource_creation.functions()->CreateAudioConfig(
217          instance_id, static_cast<PP_AudioSampleRate>(sample_rate),
218          sample_frame_count);
219  if (!audio_config_res) {
220    enter.SetResult(PP_ERROR_FAILED);
221    return;
222  }
223
224  // Initiate opening the audio object.
225  enter.SetResult(enter.object()->Open(audio_config_res,
226                                       enter.callback()));
227
228  // Clean up the temporary audio config resource we made.
229  const PPB_Core* core = static_cast<const PPB_Core*>(
230      dispatcher()->local_get_interface()(PPB_CORE_INTERFACE));
231  core->ReleaseResource(audio_config_res);
232}
233
234void PPB_Audio_Proxy::OnMsgStartOrStop(const HostResource& audio_id,
235                                       bool play) {
236  EnterHostFromHostResource<PPB_Audio_API> enter(audio_id);
237  if (enter.failed())
238    return;
239  if (play)
240    enter.object()->StartPlayback();
241  else
242    enter.object()->StopPlayback();
243}
244
245void PPB_Audio_Proxy::AudioChannelConnected(
246    int32_t result,
247    const HostResource& resource) {
248  IPC::PlatformFileForTransit socket_handle =
249      IPC::InvalidPlatformFileForTransit();
250  base::SharedMemoryHandle shared_memory = IPC::InvalidPlatformFileForTransit();
251  uint32_t audio_buffer_length = 0;
252
253  int32_t result_code = result;
254  if (result_code == PP_OK) {
255    result_code = GetAudioConnectedHandles(resource, &socket_handle,
256                                           &shared_memory,
257                                           &audio_buffer_length);
258  }
259
260  // Send all the values, even on error. This simplifies some of our cleanup
261  // code since the handles will be in the other process and could be
262  // inconvenient to clean up. Our IPC code will automatically handle this for
263  // us, as long as the remote side always closes the handles it receives
264  // (in OnMsgNotifyAudioStreamCreated), even in the failure case.
265  SerializedHandle fd_wrapper(SerializedHandle::SOCKET, socket_handle);
266  SerializedHandle handle_wrapper(shared_memory, audio_buffer_length);
267  dispatcher()->Send(new PpapiMsg_PPBAudio_NotifyAudioStreamCreated(
268      API_ID_PPB_AUDIO, resource, result_code, fd_wrapper, handle_wrapper));
269}
270
271int32_t PPB_Audio_Proxy::GetAudioConnectedHandles(
272    const HostResource& resource,
273    IPC::PlatformFileForTransit* foreign_socket_handle,
274    base::SharedMemoryHandle* foreign_shared_memory_handle,
275    uint32_t* shared_memory_length) {
276  // Get the audio interface which will give us the handles.
277  EnterHostFromHostResource<PPB_Audio_API> enter(resource);
278  if (enter.failed())
279    return PP_ERROR_NOINTERFACE;
280
281  // Get the socket handle for signaling.
282  int32_t socket_handle;
283  int32_t result = enter.object()->GetSyncSocket(&socket_handle);
284  if (result != PP_OK)
285    return result;
286
287  // socket_handle doesn't belong to us: don't close it.
288  *foreign_socket_handle = dispatcher()->ShareHandleWithRemote(
289      IntToPlatformFile(socket_handle), false);
290  if (*foreign_socket_handle == IPC::InvalidPlatformFileForTransit())
291    return PP_ERROR_FAILED;
292
293  // Get the shared memory for the buffer.
294  int shared_memory_handle;
295  result = enter.object()->GetSharedMemory(&shared_memory_handle,
296                                           shared_memory_length);
297  if (result != PP_OK)
298    return result;
299
300  // shared_memory_handle doesn't belong to us: don't close it.
301  *foreign_shared_memory_handle = dispatcher()->ShareHandleWithRemote(
302      IntToPlatformFile(shared_memory_handle), false);
303  if (*foreign_shared_memory_handle == IPC::InvalidPlatformFileForTransit())
304    return PP_ERROR_FAILED;
305
306  return PP_OK;
307}
308#endif  // !defined(OS_NACL)
309
310// Processed in the plugin (message from host).
311void PPB_Audio_Proxy::OnMsgNotifyAudioStreamCreated(
312    const HostResource& audio_id,
313    int32_t result_code,
314    SerializedHandle socket_handle,
315    SerializedHandle handle) {
316  CHECK(socket_handle.is_socket());
317  CHECK(handle.is_shmem());
318  EnterPluginFromHostResource<PPB_Audio_API> enter(audio_id);
319  if (enter.failed() || result_code != PP_OK) {
320    // The caller may still have given us these handles in the failure case.
321    // The easiest way to clean these up is to just put them in the objects
322    // and then close them. This failure case is not performance critical.
323    base::SyncSocket temp_socket(
324        IPC::PlatformFileForTransitToPlatformFile(socket_handle.descriptor()));
325    base::SharedMemory temp_mem(handle.shmem(), false);
326  } else {
327    EnterResourceNoLock<PPB_AudioConfig_API> config(
328        static_cast<Audio*>(enter.object())->GetCurrentConfig(), true);
329    static_cast<Audio*>(enter.object())->SetStreamInfo(
330        enter.resource()->pp_instance(), handle.shmem(), handle.size(),
331        IPC::PlatformFileForTransitToPlatformFile(socket_handle.descriptor()),
332        config.object()->GetSampleRate(),
333        config.object()->GetSampleFrameCount());
334  }
335}
336
337}  // namespace proxy
338}  // namespace ppapi
339