| 89 | | #define AUDIO_MODE SA_MODE_WRONLY |
|---|
| 90 | | #define AUDIO_FORMAT SA_PCM_FORMAT_S16_LE |
|---|
| 91 | | #define AUDIO_RATE 44100 |
|---|
| 92 | | #define AUDIO_CHANNELS 2 |
|---|
| | 90 | #define VIDEO_RATE 25 |
|---|
| | 91 | #define AUDIO_MODE SA_MODE_WRONLY |
|---|
| | 92 | #define AUDIO_FORMAT SA_PCM_FORMAT_S16_LE |
|---|
| | 93 | #define AUDIO_RATE 44100 |
|---|
| | 94 | #define AUDIO_CHANNELS 2 |
|---|
| | 95 | #define AUDIO_BYTES_PER_CH sizeof(short) |
|---|
| | 96 | |
|---|
| | 97 | |
|---|
| | 98 | /* |
|---|
| | 99 | * ----------------------------------------------------------------------------- |
|---|
| | 100 | * Helper functions |
|---|
| | 101 | * ----------------------------------------------------------------------------- |
|---|
| | 102 | */ |
|---|
| | 103 | |
|---|
| | 104 | static void |
|---|
| | 105 | millisleep(long ms) { |
|---|
| | 106 | struct timespec ts = {0, ms * 1000000}; |
|---|
| | 107 | nanosleep(&ts, NULL); |
|---|
| | 108 | } |
|---|
| | 109 | |
|---|
| | 110 | static int64_t |
|---|
| | 111 | get_curr_time() { |
|---|
| | 112 | struct timeval tv; |
|---|
| | 113 | gettimeofday(&tv, NULL); |
|---|
| | 114 | return (int64_t)tv.tv_sec * 1000 + (int64_t)tv.tv_usec / 1000; |
|---|
| | 115 | } |
|---|
| | 274 | static void |
|---|
| | 275 | init_audio(PluginWindowInfo *info, ThreadData *td) { |
|---|
| | 276 | |
|---|
| | 277 | if ( |
|---|
| | 278 | sa_stream_create_pcm(&td->audio_handle, NULL, AUDIO_MODE, |
|---|
| | 279 | AUDIO_FORMAT, AUDIO_RATE, AUDIO_CHANNELS) != SA_SUCCESS |
|---|
| | 280 | || |
|---|
| | 281 | sa_stream_open(td->audio_handle) != SA_SUCCESS |
|---|
| | 282 | ) { |
|---|
| | 283 | sa_stream_destroy(td->audio_handle); |
|---|
| | 284 | td->audio_handle = NULL; |
|---|
| | 285 | } |
|---|
| | 286 | } |
|---|
| | 287 | |
|---|
| | 288 | |
|---|
| | 289 | static void |
|---|
| | 290 | shutdown_audio(PluginWindowInfo *info, ThreadData *td) { |
|---|
| | 291 | |
|---|
| | 292 | if (td->audio_handle != NULL) { |
|---|
| | 293 | sa_stream_destroy(td->audio_handle); |
|---|
| | 294 | td->audio_handle = NULL; |
|---|
| | 295 | } |
|---|
| | 296 | } |
|---|
| | 297 | |
|---|
| | 298 | |
|---|
| 304 | | * Grab the next frame of data and feed it to GL and the audio stream. |
|---|
| 305 | | * In the unlikely event of a failure in the audio output, please remain |
|---|
| 306 | | * seated while we disable audio and keep displaying the video. |
|---|
| | 356 | * Grab the next frame's worth of data, and update the GL video display. |
|---|
| | 357 | * We want to do this even when there isn't any video data; if we don't, |
|---|
| | 358 | * the media area can retain pixels from the previous display (e.g. if you |
|---|
| | 359 | * follow a link to get to a page with embedded video, the old page can |
|---|
| | 360 | * remain visible in the media area). |
|---|
| 311 | | if (audio_result == SA_SUCCESS) { |
|---|
| 312 | | audio_result = sa_stream_write(audio_handle, |
|---|
| 313 | | td.frame_data.samples, td.frame_data.size); |
|---|
| 314 | | } |
|---|
| | 365 | |
|---|
| | 366 | bool have_video = (td.frame_data.frame != NULL); |
|---|
| | 367 | bool have_audio = (td.frame_data.samples != NULL && td.frame_data.size > 0); |
|---|
| | 368 | |
|---|
| | 369 | /* |
|---|
| | 370 | * Spin until we have some data (but wait for a bit first so we don't |
|---|
| | 371 | * chew too many cycles). |
|---|
| | 372 | */ |
|---|
| | 373 | if (!have_video && !have_audio) { |
|---|
| | 374 | offset = 5; |
|---|
| | 375 | goto loop_end; |
|---|
| | 376 | } |
|---|
| | 377 | |
|---|
| | 378 | /* |
|---|
| | 379 | * If the media source doesn't contain any audio, we shouldn't keep |
|---|
| | 380 | * the audio output unit open. |
|---|
| | 381 | */ |
|---|
| | 382 | if (!have_audio && td.audio_handle != NULL) { |
|---|
| | 383 | shutdown_audio(info, &td); |
|---|
| | 384 | playback_target = 0; |
|---|
| | 385 | time_ref = -1; |
|---|
| | 386 | } |
|---|
| | 387 | |
|---|
| | 388 | /* |
|---|
| | 389 | * If we have some audio data, send it to the output device and calculate |
|---|
| | 390 | * our current playing time using the number of audio bytes consumed. |
|---|
| | 391 | */ |
|---|
| | 392 | cur_time = -1; |
|---|
| | 393 | if (have_audio && td.audio_handle != NULL) { |
|---|
| | 394 | if ( |
|---|
| | 395 | sa_stream_write(td.audio_handle, td.frame_data.samples, td.frame_data.size) == SA_SUCCESS |
|---|
| | 396 | && |
|---|
| | 397 | sa_stream_get_position(td.audio_handle, SA_POSITION_WRITE_SOFTWARE, &bytes) == SA_SUCCESS |
|---|
| | 398 | ) { |
|---|
| | 399 | cur_time = bytes * 1000 / (AUDIO_RATE * AUDIO_BYTES_PER_CH * AUDIO_CHANNELS); |
|---|
| | 400 | } else { |
|---|
| | 401 | /* |
|---|
| | 402 | * Something's gone wrong with the audio; revert to using the system |
|---|
| | 403 | * clock for controlling our playback time. |
|---|
| | 404 | */ |
|---|
| | 405 | shutdown_audio(info, &td); |
|---|
| | 406 | playback_target = 0; |
|---|
| | 407 | time_ref = -1; |
|---|
| | 408 | } |
|---|
| | 409 | } |
|---|
| | 410 | |
|---|
| | 411 | /* |
|---|
| | 412 | * If we can't use the audio device to determine our playback time, track |
|---|
| | 413 | * it with the system clock. Use the time at which the first frame is |
|---|
| | 414 | * displayed as a reference. |
|---|
| | 415 | */ |
|---|
| | 416 | if (cur_time == -1) { |
|---|
| | 417 | if (time_ref == -1) { |
|---|
| | 418 | time_ref = get_curr_time(); |
|---|
| | 419 | } |
|---|
| | 420 | cur_time = get_curr_time() - time_ref; |
|---|
| | 421 | } |
|---|
| | 422 | |
|---|
| | 423 | /* |
|---|
| | 424 | * playback_target is the time at which we want to display the next video |
|---|
| | 425 | * frame. |
|---|
| | 426 | */ |
|---|
| | 427 | offset = playback_target - cur_time; |
|---|
| | 428 | if (offset < 0) { |
|---|
| | 429 | offset = 0; |
|---|
| | 430 | } |
|---|
| | 431 | playback_target += 1000 / VIDEO_RATE; |
|---|
| | 432 | |
|---|
| | 433 | loop_end: |
|---|