ppb_audio_proxy.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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 "media/audio/shared_memory_util.h"
10#include "ppapi/c/pp_errors.h"
11#include "ppapi/c/ppb_audio.h"
12#include "ppapi/c/ppb_audio_config.h"
13#include "ppapi/c/ppb_var.h"
14#include "ppapi/c/trusted/ppb_audio_trusted.h"
15#include "ppapi/proxy/enter_proxy.h"
16#include "ppapi/proxy/plugin_dispatcher.h"
17#include "ppapi/proxy/ppapi_messages.h"
18#include "ppapi/shared_impl/api_id.h"
19#include "ppapi/shared_impl/platform_file.h"
20#include "ppapi/shared_impl/ppapi_globals.h"
21#include "ppapi/shared_impl/ppb_audio_shared.h"
22#include "ppapi/shared_impl/resource.h"
23#include "ppapi/thunk/ppb_audio_config_api.h"
24#include "ppapi/thunk/enter.h"
25#include "ppapi/thunk/resource_creation_api.h"
26#include "ppapi/thunk/thunk.h"
27
28using ppapi::IntToPlatformFile;
29using ppapi::proxy::SerializedHandle;
30using ppapi::thunk::EnterResourceNoLock;
31using ppapi::thunk::PPB_Audio_API;
32using ppapi::thunk::PPB_AudioConfig_API;
33
34namespace ppapi {
35namespace proxy {
36
37class Audio : public Resource, public PPB_Audio_Shared {
38 public:
39  Audio(const HostResource& audio_id,
40        PP_Resource config_id,
41        PPB_Audio_Callback callback,
42        void* user_data);
43  virtual ~Audio();
44
45  // Resource overrides.
46  virtual PPB_Audio_API* AsPPB_Audio_API();
47
48  // PPB_Audio_API implementation.
49  virtual PP_Resource GetCurrentConfig() OVERRIDE;
50  virtual PP_Bool StartPlayback() OVERRIDE;
51  virtual PP_Bool StopPlayback() OVERRIDE;
52  virtual int32_t Open(
53      PP_Resource config_id,
54      scoped_refptr<TrackedCallback> create_callback) OVERRIDE;
55  virtual int32_t GetSyncSocket(int* sync_socket) OVERRIDE;
56  virtual int32_t GetSharedMemory(int* shm_handle, uint32_t* shm_size) OVERRIDE;
57
58 private:
59  // Owning reference to the current config object. This isn't actually used,
60  // we just dish it out as requested by the plugin.
61  PP_Resource config_;
62
63  DISALLOW_COPY_AND_ASSIGN(Audio);
64};
65
66Audio::Audio(const HostResource& audio_id,
67             PP_Resource config_id,
68             PPB_Audio_Callback callback,
69             void* user_data)
70    : Resource(OBJECT_IS_PROXY, audio_id),
71      config_(config_id) {
72  SetCallback(callback, user_data);
73  PpapiGlobals::Get()->GetResourceTracker()->AddRefResource(config_);
74}
75
76Audio::~Audio() {
77#if defined(OS_NACL)
78  // Invoke StopPlayback() to ensure audio back-end has a chance to send the
79  // escape value over the sync socket, which will terminate the client side
80  // audio callback loop.  This is required for NaCl Plugins that can't escape
81  // by shutting down the sync_socket.
82  StopPlayback();
83#endif
84  PpapiGlobals::Get()->GetResourceTracker()->ReleaseResource(config_);
85}
86
87PPB_Audio_API* Audio::AsPPB_Audio_API() {
88  return this;
89}
90
91PP_Resource Audio::GetCurrentConfig() {
92  // AddRef for the caller.
93  PpapiGlobals::Get()->GetResourceTracker()->AddRefResource(config_);
94  return config_;
95}
96
97PP_Bool Audio::StartPlayback() {
98  if (playing())
99    return PP_TRUE;
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    PPB_Audio_Callback 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)
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
267  // Note that we must call TotalSharedMemorySizeInBytes because
268  // Audio allocates extra space in shared memory for book-keeping, so the
269  // actual size of the shared memory buffer is larger than audio_buffer_length.
270  // When sending to NaCl, NaClIPCAdapter expects this size to match the size
271  // of the full shared memory buffer.
272  SerializedHandle handle_wrapper(
273      shared_memory,
274      media::TotalSharedMemorySizeInBytes(audio_buffer_length));
275  dispatcher()->Send(new PpapiMsg_PPBAudio_NotifyAudioStreamCreated(
276      API_ID_PPB_AUDIO, resource, result_code, fd_wrapper, handle_wrapper));
277}
278
279int32_t PPB_Audio_Proxy::GetAudioConnectedHandles(
280    const HostResource& resource,
281    IPC::PlatformFileForTransit* foreign_socket_handle,
282    base::SharedMemoryHandle* foreign_shared_memory_handle,
283    uint32_t* shared_memory_length) {
284  // Get the audio interface which will give us the handles.
285  EnterHostFromHostResource<PPB_Audio_API> enter(resource);
286  if (enter.failed())
287    return PP_ERROR_NOINTERFACE;
288
289  // Get the socket handle for signaling.
290  int32_t socket_handle;
291  int32_t result = enter.object()->GetSyncSocket(&socket_handle);
292  if (result != PP_OK)
293    return result;
294
295  // socket_handle doesn't belong to us: don't close it.
296  *foreign_socket_handle = dispatcher()->ShareHandleWithRemote(
297      IntToPlatformFile(socket_handle), false);
298  if (*foreign_socket_handle == IPC::InvalidPlatformFileForTransit())
299    return PP_ERROR_FAILED;
300
301  // Get the shared memory for the buffer.
302  int shared_memory_handle;
303  result = enter.object()->GetSharedMemory(&shared_memory_handle,
304                                           shared_memory_length);
305  if (result != PP_OK)
306    return result;
307
308  // shared_memory_handle doesn't belong to us: don't close it.
309  *foreign_shared_memory_handle = dispatcher()->ShareHandleWithRemote(
310      IntToPlatformFile(shared_memory_handle), false);
311  if (*foreign_shared_memory_handle == IPC::InvalidPlatformFileForTransit())
312    return PP_ERROR_FAILED;
313
314  return PP_OK;
315}
316#endif  // !defined(OS_NACL)
317
318// Processed in the plugin (message from host).
319void PPB_Audio_Proxy::OnMsgNotifyAudioStreamCreated(
320    const HostResource& audio_id,
321    int32_t result_code,
322    SerializedHandle socket_handle,
323    SerializedHandle handle) {
324  CHECK(socket_handle.is_socket());
325  CHECK(handle.is_shmem());
326  EnterPluginFromHostResource<PPB_Audio_API> enter(audio_id);
327  if (enter.failed() || result_code != PP_OK) {
328    // The caller may still have given us these handles in the failure case.
329    // The easiest way to clean these up is to just put them in the objects
330    // and then close them. This failure case is not performance critical.
331    base::SyncSocket temp_socket(
332        IPC::PlatformFileForTransitToPlatformFile(socket_handle.descriptor()));
333    base::SharedMemory temp_mem(handle.shmem(), false);
334  } else {
335    EnterResourceNoLock<PPB_AudioConfig_API> config(
336        static_cast<Audio*>(enter.object())->GetCurrentConfig(), true);
337    // See the comment above about how we must call
338    // TotalSharedMemorySizeInBytes to get the actual size of the buffer. Here,
339    // we must call PacketSizeInBytes to get back the size of the audio buffer,
340    // excluding the bytes that audio uses for book-keeping.
341    static_cast<Audio*>(enter.object())->SetStreamInfo(
342        enter.resource()->pp_instance(), handle.shmem(),
343        media::PacketSizeInBytes(handle.size()),
344        IPC::PlatformFileForTransitToPlatformFile(socket_handle.descriptor()),
345        config.object()->GetSampleFrameCount());
346  }
347}
348
349}  // namespace proxy
350}  // namespace ppapi
351