| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright 2010 The Native Client Authors. All rights reserved. | |
| 3 * Use of this source code is governed by a BSD-style license that can | |
| 4 * be found in the LICENSE file. | |
| 5 */ | |
| 6 | |
| 7 /* | |
| 8 * Concrete implemenatation of IMultimedia interface using SDL | |
| 9 */ | |
| 10 | |
| 11 #include <SDL/SDL.h> | |
| 12 #include <SDL/SDL_timer.h> | |
| 13 | |
| 14 #include <string.h> | |
| 15 | |
| 16 #include <functional> | |
| 17 #include <queue> | |
| 18 #include "ppapi/c/pp_input_event.h" | |
| 19 | |
| 20 #include "native_client/src/shared/platform/nacl_log.h" | |
| 21 #include "native_client/src/shared/platform/nacl_sync.h" | |
| 22 #include "native_client/src/shared/platform/nacl_sync_checked.h" | |
| 23 | |
| 24 // TODO(robertm): the next include file should be moved to src/untrusted | |
| 25 #include "native_client/src/trusted/sel_universal/multimedia.h" | |
| 26 #include "native_client/src/trusted/sel_universal/workqueue.h" | |
| 27 | |
| 28 // from sdl_ppapi_event_translator.cc | |
| 29 // TODO(robertm): add header when this becomes more complex | |
| 30 /* @IGNORE_LINES_FOR_CODE_HYGIENE[2] */ | |
| 31 extern bool ConvertSDLEventToPPAPI( | |
| 32 const SDL_Event& sdl_event, PP_InputEvent* pp_event); | |
| 33 | |
| 34 // This file implements a IMultimedia interface using SDL | |
| 35 | |
| 36 struct InfoVideo { | |
| 37 int32_t width; | |
| 38 int32_t height; | |
| 39 int32_t format; | |
| 40 int32_t bits_per_pixel; | |
| 41 int32_t bytes_per_pixel; | |
| 42 int32_t rmask; | |
| 43 int32_t gmask; | |
| 44 int32_t bmask; | |
| 45 SDL_Surface* screen; | |
| 46 }; | |
| 47 | |
| 48 | |
| 49 struct InfoAudio { | |
| 50 int32_t frequency; | |
| 51 int32_t channels; | |
| 52 int32_t frame_size; | |
| 53 }; | |
| 54 | |
| 55 | |
| 56 struct SDLInfo { | |
| 57 int32_t initialized_sdl; | |
| 58 InfoVideo video; | |
| 59 InfoAudio audio; | |
| 60 }; | |
| 61 | |
| 62 | |
| 63 class MultimediaSDL; | |
| 64 | |
| 65 | |
| 66 struct TimerEventState { | |
| 67 MultimediaSDL* mm; | |
| 68 | |
| 69 int code; | |
| 70 int data1; | |
| 71 int data2; | |
| 72 }; | |
| 73 | |
| 74 | |
| 75 static Uint32 TimerCallBack(Uint32 interval, void* data); | |
| 76 | |
| 77 | |
| 78 // Wrap each call to SDL into Job so we can submit them to a workqueue | |
| 79 // which guarantees that they are all executed by the same thread. | |
| 80 class JobSdlInit: public Job { | |
| 81 public: | |
| 82 int result; | |
| 83 JobSdlInit(SDLInfo* info, int width, int height, const char* title) | |
| 84 : info_(info), | |
| 85 width_(width), | |
| 86 height_(height), | |
| 87 title_(title) | |
| 88 {} | |
| 89 | |
| 90 private: | |
| 91 SDLInfo* info_; | |
| 92 int width_; | |
| 93 int height_; | |
| 94 const char* title_; | |
| 95 | |
| 96 virtual void Action() { | |
| 97 NaClLog(3, "JobSdlInit::Action\n"); | |
| 98 const int flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER; | |
| 99 const uint32_t sdl_video_flags = SDL_DOUBLEBUF | SDL_HWSURFACE; | |
| 100 | |
| 101 memset(info_, 0, sizeof(*info_)); | |
| 102 if (SDL_Init(flags)) { | |
| 103 NaClLog(LOG_FATAL, "MultimediaModuleInit: SDL_Init failed\n"); | |
| 104 } | |
| 105 info_->initialized_sdl = 1; | |
| 106 | |
| 107 info_->video.screen = SDL_SetVideoMode(width_, | |
| 108 height_, | |
| 109 0, | |
| 110 sdl_video_flags); | |
| 111 | |
| 112 if (!info_->video.screen) { | |
| 113 NaClLog(LOG_FATAL, "NaClSysVideo_Init: SDL_SetVideoMode failed\n"); | |
| 114 } | |
| 115 | |
| 116 // width, height and format validated in top half | |
| 117 info_->video.width = width_; | |
| 118 info_->video.height = height_; | |
| 119 // video format always BGRA | |
| 120 info_->video.rmask = 0x00FF0000; | |
| 121 info_->video.gmask = 0x0000FF00; | |
| 122 info_->video.bmask = 0x000000FF; | |
| 123 info_->video.bits_per_pixel = 32; | |
| 124 info_->video.bytes_per_pixel = 4; | |
| 125 | |
| 126 // TODO(robertm): verify non-ownership of title_ | |
| 127 SDL_WM_SetCaption(title_, title_); | |
| 128 } | |
| 129 }; | |
| 130 | |
| 131 | |
| 132 class JobSdlQuit: public Job { | |
| 133 private: | |
| 134 SDLInfo* info_; | |
| 135 | |
| 136 public: | |
| 137 explicit JobSdlQuit(SDLInfo* info) : info_(info) {} | |
| 138 | |
| 139 virtual void Action() { | |
| 140 NaClLog(3, "JobSdlQuit::Action\n"); | |
| 141 if (!info_->initialized_sdl) { | |
| 142 NaClLog(LOG_FATAL, "sdl not initialized\n"); | |
| 143 } | |
| 144 | |
| 145 SDL_Quit(); | |
| 146 memset(info_, 0, sizeof(*info_)); | |
| 147 } | |
| 148 }; | |
| 149 | |
| 150 | |
| 151 class JobSdlAudioStart: public Job { | |
| 152 private: | |
| 153 SDLInfo* info_; | |
| 154 | |
| 155 public: | |
| 156 explicit JobSdlAudioStart(SDLInfo* info) : info_(info) {} | |
| 157 | |
| 158 virtual void Action() { | |
| 159 NaClLog(3, "JobSdlAudioStart::Action\n"); | |
| 160 if (!info_->initialized_sdl) { | |
| 161 NaClLog(LOG_FATAL, "sdl not initialized\n"); | |
| 162 } | |
| 163 SDL_PauseAudio(0); | |
| 164 } | |
| 165 }; | |
| 166 | |
| 167 | |
| 168 class JobSdlAudioStop: public Job { | |
| 169 private: | |
| 170 SDLInfo* info_; | |
| 171 | |
| 172 public: | |
| 173 explicit JobSdlAudioStop(SDLInfo* info) : info_(info) {} | |
| 174 | |
| 175 virtual void Action() { | |
| 176 NaClLog(3, "JobSdlAudioStop::Action\n"); | |
| 177 if (!info_->initialized_sdl) { | |
| 178 NaClLog(LOG_FATAL, "sdl not initialized\n"); | |
| 179 } | |
| 180 SDL_PauseAudio(1); | |
| 181 } | |
| 182 }; | |
| 183 | |
| 184 | |
| 185 class JobSdlAudioInit: public Job { | |
| 186 private: | |
| 187 SDLInfo* info_; | |
| 188 AUDIO_CALLBACK cb_; | |
| 189 | |
| 190 public: | |
| 191 explicit JobSdlAudioInit(SDLInfo* info, | |
| 192 int frequency, | |
| 193 int channels, | |
| 194 int frame_size, | |
| 195 AUDIO_CALLBACK cb) : info_(info), cb_(cb) { | |
| 196 info->audio.frequency = frequency; | |
| 197 info->audio.channels = channels; | |
| 198 info->audio.frame_size = frame_size; | |
| 199 } | |
| 200 | |
| 201 virtual void Action() { | |
| 202 NaClLog(3, "JobSdlAudioInit::Action\n"); | |
| 203 if (!info_->initialized_sdl) { | |
| 204 NaClLog(LOG_FATAL, "sdl not initialized\n"); | |
| 205 } | |
| 206 | |
| 207 SDL_AudioSpec fmt; | |
| 208 fmt.freq = info_->audio.frequency; | |
| 209 fmt.format = AUDIO_S16; | |
| 210 fmt.channels = info_->audio.channels; | |
| 211 // NOTE: SDL seems to halve that the sample count for the callback | |
| 212 // so we compensate here by doubling | |
| 213 fmt.samples = info_->audio.frame_size * 2; | |
| 214 fmt.callback = cb_; | |
| 215 fmt.userdata = NULL; | |
| 216 NaClLog(LOG_INFO, "JobSdlAudioInit %d %d %d %d\n", | |
| 217 fmt.freq, fmt.format, fmt.channels, fmt.samples); | |
| 218 if (SDL_OpenAudio(&fmt, NULL) < 0) { | |
| 219 NaClLog(LOG_FATAL, "could not initialize SDL audio\n"); | |
| 220 } | |
| 221 } | |
| 222 }; | |
| 223 | |
| 224 | |
| 225 class JobSdlUpdate: public Job { | |
| 226 public: | |
| 227 JobSdlUpdate(SDLInfo* info, const void* data) : info_(info), data_(data) {} | |
| 228 | |
| 229 virtual void Action() { | |
| 230 NaClLog(3, "JobSdlUpdate::Action\n"); | |
| 231 SDL_Surface* image = NULL; | |
| 232 InfoVideo* video_info = &info_->video; | |
| 233 image = NULL; | |
| 234 | |
| 235 if (!info_->initialized_sdl) { | |
| 236 NaClLog(LOG_FATAL, "MultimediaVideoUpdate: video not initialized\n"); | |
| 237 } | |
| 238 | |
| 239 image = SDL_CreateRGBSurfaceFrom((unsigned char*) data_, | |
| 240 video_info->width, | |
| 241 video_info->height, | |
| 242 video_info->bits_per_pixel, | |
| 243 video_info->width * | |
| 244 video_info->bytes_per_pixel, | |
| 245 video_info->rmask, | |
| 246 video_info->gmask, | |
| 247 video_info->bmask, | |
| 248 0); | |
| 249 if (NULL == image) { | |
| 250 NaClLog(LOG_FATAL, "SDL_CreateRGBSurfaceFrom failed\n"); | |
| 251 } | |
| 252 | |
| 253 if (0 != SDL_SetAlpha(image, 0, 255)) { | |
| 254 NaClLog(LOG_FATAL, "SDL_SetAlpha failed\n"); | |
| 255 } | |
| 256 | |
| 257 if (0 != SDL_BlitSurface(image, NULL, video_info->screen, NULL)) { | |
| 258 NaClLog(LOG_FATAL, "SDL_BlitSurface failed\n"); | |
| 259 } | |
| 260 | |
| 261 if (0 != SDL_Flip(video_info->screen)) { | |
| 262 NaClLog(LOG_FATAL, "SDL_Flip failed\n"); | |
| 263 } | |
| 264 | |
| 265 SDL_FreeSurface(image); | |
| 266 } | |
| 267 | |
| 268 private: | |
| 269 SDLInfo* info_; | |
| 270 const void* data_; | |
| 271 }; | |
| 272 | |
| 273 | |
| 274 class JobSdlEventPoll: public Job { | |
| 275 public: | |
| 276 JobSdlEventPoll(SDLInfo* info, PP_InputEvent* pp_event, bool poll) | |
| 277 : info_(info), pp_event_(pp_event), poll_(poll) {} | |
| 278 | |
| 279 virtual void Action() { | |
| 280 NaClLog(3, "JobSdlEventPoll::Action\n"); | |
| 281 | |
| 282 if (!info_->initialized_sdl) { | |
| 283 NaClLog(LOG_FATAL, "MultimediaEventPoll: sdl not initialized\n"); | |
| 284 } | |
| 285 | |
| 286 for (;;) { | |
| 287 SDL_Event sdl_event; | |
| 288 const int32_t result = poll_ ? | |
| 289 SDL_PollEvent(&sdl_event) : | |
| 290 SDL_WaitEvent(&sdl_event); | |
| 291 | |
| 292 if (result == 0) { | |
| 293 if (poll_) { | |
| 294 MakeInvalidEvent(pp_event_); | |
| 295 return; | |
| 296 } else { | |
| 297 NaClLog(LOG_WARNING, "SDL_WaitEvent failed\n"); | |
| 298 } | |
| 299 } | |
| 300 | |
| 301 if (!ConvertSDLEventToPPAPI(sdl_event, pp_event_)) { | |
| 302 continue; | |
| 303 } | |
| 304 | |
| 305 break; | |
| 306 } | |
| 307 } | |
| 308 | |
| 309 private: | |
| 310 SDLInfo* info_; | |
| 311 PP_InputEvent* pp_event_; | |
| 312 bool poll_; | |
| 313 }; | |
| 314 | |
| 315 | |
| 316 // Again, the issue that all this wrapping magic is trying to work around | |
| 317 // is that SDL requires certain calls to be made from the same thread | |
| 318 class MultimediaSDL : public IMultimedia { | |
| 319 public: | |
| 320 MultimediaSDL(int width, int heigth, const char* title) { | |
| 321 NaClLog(2, "MultimediaSDL::Constructor\n"); | |
| 322 sdl_workqueue_.StartInAnotherThread(); | |
| 323 JobSdlInit job(&sdl_info_, width, heigth, title); | |
| 324 sdl_workqueue_.JobPut(&job); | |
| 325 job.Wait(); | |
| 326 } | |
| 327 | |
| 328 virtual ~MultimediaSDL() { | |
| 329 JobSdlQuit job(&sdl_info_); | |
| 330 sdl_workqueue_.JobPut(&job); | |
| 331 job.Wait(); | |
| 332 } | |
| 333 | |
| 334 virtual int VideoBufferSize() { | |
| 335 InfoVideo* video_info = &sdl_info_.video; | |
| 336 return video_info->bytes_per_pixel * | |
| 337 video_info->width * | |
| 338 video_info->height; | |
| 339 } | |
| 340 | |
| 341 virtual void VideoUpdate(const void* data) { | |
| 342 JobSdlUpdate job(&sdl_info_, data); | |
| 343 sdl_workqueue_.JobPut(&job); | |
| 344 job.Wait(); | |
| 345 } | |
| 346 | |
| 347 virtual void PushUserEvent(int delay, int code, int data1, int data2) { | |
| 348 // NOTE: this is intentionally not using the queue | |
| 349 // so we can unblock a queue that is waiting for an event | |
| 350 NaClLog(3, "JobSdlPushUserEvent::Action\n"); | |
| 351 if (!sdl_info_.initialized_sdl) { | |
| 352 NaClLog(LOG_FATAL, "sdl not initialized\n"); | |
| 353 } | |
| 354 if (delay == 0) { | |
| 355 SDL_Event event; | |
| 356 event.type = SDL_USEREVENT; | |
| 357 event.user.code = code; | |
| 358 event.user.data1 = reinterpret_cast<void*>(data1); | |
| 359 event.user.data2 = reinterpret_cast<void*>(data2); | |
| 360 SDL_PushEvent(&event); | |
| 361 } else { | |
| 362 // schedule a timer to inject the event into the event stream | |
| 363 TimerEventState* state = new TimerEventState(); | |
| 364 state->mm = this; | |
| 365 state->code = code; | |
| 366 state->data1 = data1; | |
| 367 state->data2 = data2; | |
| 368 SDL_AddTimer(delay, TimerCallBack, state); | |
| 369 } | |
| 370 } | |
| 371 | |
| 372 virtual void EventPoll(PP_InputEvent* event) { | |
| 373 JobSdlEventPoll job(&sdl_info_, event, true); | |
| 374 sdl_workqueue_.JobPut(&job); | |
| 375 job.Wait(); | |
| 376 } | |
| 377 | |
| 378 virtual void EventGet(PP_InputEvent* event) { | |
| 379 JobSdlEventPoll job(&sdl_info_, event, false); | |
| 380 sdl_workqueue_.JobPut(&job); | |
| 381 job.Wait(); | |
| 382 } | |
| 383 | |
| 384 virtual void AudioInit16Bit(int frequency, | |
| 385 int channels, | |
| 386 int frame_size, | |
| 387 AUDIO_CALLBACK cb) { | |
| 388 JobSdlAudioInit job(&sdl_info_, frequency, channels, frame_size, cb); | |
| 389 sdl_workqueue_.JobPut(&job); | |
| 390 job.Wait(); | |
| 391 } | |
| 392 | |
| 393 virtual void AudioStart() { | |
| 394 JobSdlAudioStart job(&sdl_info_); | |
| 395 sdl_workqueue_.JobPut(&job); | |
| 396 job.Wait(); | |
| 397 } | |
| 398 | |
| 399 virtual void AudioStop() { | |
| 400 JobSdlAudioStop job(&sdl_info_); | |
| 401 sdl_workqueue_.JobPut(&job); | |
| 402 job.Wait(); | |
| 403 } | |
| 404 | |
| 405 private: | |
| 406 ThreadedWorkQueue sdl_workqueue_; | |
| 407 SDLInfo sdl_info_; | |
| 408 }; | |
| 409 | |
| 410 | |
| 411 static Uint32 TimerCallBack(Uint32 interval, void* data) { | |
| 412 UNREFERENCED_PARAMETER(interval); | |
| 413 TimerEventState* state = reinterpret_cast<TimerEventState*>(data); | |
| 414 state->mm->PushUserEvent(0, state->code, state->data1, state->data2); | |
| 415 delete state; | |
| 416 // stop timer | |
| 417 return 0; | |
| 418 } | |
| 419 | |
| 420 // Factor, so we can hide class MultimediaSDL from the outside world | |
| 421 IMultimedia* MakeMultimediaSDL(int width, int heigth, const char* title) { | |
| 422 return new MultimediaSDL(width, heigth, title); | |
| 423 } | |
| OLD | NEW |