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