2 * $Logfile: /Freespace2/code/movie/mveplayer.cpp $
7 * MVE movie playing routines
10 * Revision 1.7 2005/10/01 21:48:01 taylor
12 * fix decoder to swap opcode 0xb since it screws up on PPC
13 * the previous opcode 0xc change was wrong since we had already determined that it messes up FS1 movies
15 * Revision 1.6 2005/08/12 08:47:24 taylor
16 * use new audiostr code rather than old windows/unix version
17 * update all OpenAL commands with new error checking macros
18 * fix play_position to properly account for real position, fixes the talking heads and message text cutting out early
19 * movies will now use better filtering when scaled
21 * Revision 1.5 2005/03/31 21:26:02 taylor
22 * s/alGetSourceiv/alGetSourcei/
24 * Revision 1.4 2005/03/31 00:06:20 taylor
25 * go back to more accurate timer and allow video scaling for movies
27 * Revision 1.3 2005/03/29 07:50:34 taylor
28 * Update to newest movie code with much better video support and audio support from
29 * Pierre Willenbrock. Movies are enabled always now (no longer a build option)
30 * and but can be skipped with the "--nomovies" or "-n" cmdline options.
36 #include "SDL_opengl.h"
47 #include "osregistry.h"
52 static int mve_playing;
56 static int micro_frame_delay = 0;
57 static int timer_started = 0;
58 static int timer_created = 0;
59 static int timer_expire;
60 static Uint64 micro_timer_start = 0;
61 static Uint64 micro_timer_freq = 0;
64 #define MVE_AUDIO_BUFFERS 8 // total buffers to interact with stream
66 static std::vector<ALuint> mve_audio_bufl_free;
67 static ubyte *mve_audio_buf = NULL;
68 static size_t mve_audio_buf_size = 0;
69 static size_t mve_audio_buf_offset = 0;
71 static int mve_audio_playing = 0;
72 static int mve_audio_canplay = 0;
73 static int mve_audio_compressed = 0;
74 static int audiobuf_created = 0;
76 // struct for the audio stream information
84 ALuint buffers[MVE_AUDIO_BUFFERS];
87 mve_audio_t *mas = NULL; // mve_audio_stream
92 int g_width, g_height;
93 void *g_vBuffers = NULL;
94 void *g_vBackBuf1, *g_vBackBuf2;
95 ushort *pixelbuf = NULL;
96 static ubyte *g_pCurMap=NULL;
97 static int g_nMapLength=0;
98 static int videobuf_created;
99 static int mve_scale_video = 0;
100 static int mve_viewport_w = 0;
101 static float mve_viewport_scale = 1.0f;
102 static int mve_needs_clear = 0;
103 static GLuint tex = 0;
110 static g_coords_t g_coords[4];
114 void decodeFrame16(ubyte *pFrame, ubyte *pMap, int mapRemain, ubyte *pData, int dataRemain);
116 /*************************
118 *************************/
124 /*************************
126 *************************/
128 int mve_timer_create(ubyte *data)
130 micro_frame_delay = mve_get_int(data) * (int)mve_get_short(data+4);
132 micro_timer_start = SDL_GetPerformanceCounter();
133 micro_timer_freq = SDL_GetPerformanceFrequency();
135 if (micro_timer_freq < 1000) {
136 micro_timer_freq = 1000;
144 static int mve_timer_get_microseconds()
147 Uint64 us = SDL_GetPerformanceCounter() - micro_timer_start;
149 if (micro_timer_freq >= 1000000) {
150 us /= (micro_timer_freq / 1000000);
152 us *= (1000000 / micro_timer_freq);
158 static void mve_timer_start(void)
163 timer_expire = mve_timer_get_microseconds();
164 timer_expire += micro_frame_delay;
169 static int mve_do_timer_wait(void)
176 tv = mve_timer_get_microseconds();
178 if (tv > timer_expire)
181 ts = timer_expire - tv;
183 SDL_Delay(ts / 1000);
185 // try and burn off excess in attempt to keep sync
187 for (int i = 0; i < 10; i++) {
192 timer_expire += micro_frame_delay;
197 static void mve_timer_stop()
203 micro_frame_delay = 0;
205 micro_timer_start = 0;
206 micro_timer_freq = 0;
209 /*************************
211 *************************/
213 // setup the audio information from the data stream
214 void mve_audio_createbuf(ubyte minor, ubyte *data)
216 if (audiobuf_created)
219 // if game sound disabled don't try and play movie audio
220 if ( !Sound_enabled ) {
221 mve_audio_canplay = 0;
222 audiobuf_created = 1;
226 int flags, desired_buffer, sample_rate;
228 mas = (mve_audio_t *) malloc ( sizeof(mve_audio_t) );
231 mve_audio_canplay = 0;
232 audiobuf_created = 1;
236 memset(mas, 0, sizeof(mve_audio_t));
238 mas->format = AL_INVALID;
240 flags = mve_get_ushort(data + 2);
241 sample_rate = mve_get_ushort(data + 4);
242 desired_buffer = mve_get_int(data + 6);
244 if (desired_buffer > 0) {
245 mve_audio_buf = (ubyte*) malloc (desired_buffer);
247 if (mve_audio_buf == NULL) {
248 mve_audio_canplay = 0;
249 audiobuf_created = 1;
253 mve_audio_buf_size = desired_buffer;
254 mve_audio_buf_offset = 0;
256 mve_audio_canplay = 0;
257 audiobuf_created = 1;
261 mas->channels = (flags & 0x0001) ? 2 : 1;
262 mas->bitsize = (flags & 0x0002) ? 16 : 8;
264 mas->sample_rate = sample_rate;
267 mve_audio_compressed = flags & 0x0004 ? 1 : 0;
269 mve_audio_compressed = 0;
272 if (mas->bitsize == 16) {
273 if (mas->channels == 2) {
274 mas->format = AL_FORMAT_STEREO16;
275 } else if (mas->channels == 1) {
276 mas->format = AL_FORMAT_MONO16;
278 } else if (mas->bitsize == 8) {
279 if (mas->channels == 2) {
280 mas->format = AL_FORMAT_STEREO8;
281 } else if (mas->channels == 1) {
282 mas->format = AL_FORMAT_MONO8;
286 // somethings wrong, bail now
287 if (mas->format == AL_INVALID) {
288 mve_audio_canplay = 0;
289 audiobuf_created = 1;
293 oal_check_for_errors("mve_audio_createbuf() begin");
295 for (int i = 0; i < MVE_AUDIO_BUFFERS; i++) {
296 alGenBuffers(1, &mas->buffers[i]);
298 if ( !mas->buffers[i] ) {
299 mve_audio_canplay = 0;
300 audiobuf_created = 1;
305 mve_audio_bufl_free.assign(mas->buffers, mas->buffers+MVE_AUDIO_BUFFERS);
307 mas->chan = oal_get_free_channel(1.0f, -1, SND_PRIORITY_MUST_PLAY);
309 if (mas->chan == NULL) {
310 mve_audio_canplay = 0;
311 audiobuf_created = 1;
315 alSourcef(mas->chan->source_id, AL_GAIN, 1.0f);
316 alSource3f(mas->chan->source_id, AL_POSITION, 0.0f, 0.0f, 0.0f);
317 alSource3f(mas->chan->source_id, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
318 alSource3f(mas->chan->source_id, AL_DIRECTION, 0.0f, 0.0f, 0.0f);
319 alSourcef(mas->chan->source_id, AL_ROLLOFF_FACTOR, 0.0f);
320 alSourcei(mas->chan->source_id, AL_SOURCE_RELATIVE, AL_TRUE);
321 alSourcei(mas->chan->source_id, AL_LOOPING, AL_FALSE);
323 oal_check_for_errors("mve_audio_createbuf() end");
325 audiobuf_created = 1;
326 mve_audio_canplay = 1;
329 // play and stream the audio
330 void mve_audio_play()
332 if (mve_audio_canplay) {
334 ALint status = AL_INVALID;
336 oal_check_for_errors("mve_audio_play() begin");
338 alGetSourcei(mas->chan->source_id, AL_BUFFERS_QUEUED, &queued);
339 alGetSourcei(mas->chan->source_id, AL_SOURCE_STATE, &status);
341 if ( (status != AL_PLAYING) && (queued > 0) ) {
342 alSourcePlay(mas->chan->source_id);
343 mve_audio_playing = 1;
346 oal_check_for_errors("mve_audio_play() end");
350 // call this in shutdown to stop and close audio
351 static void mve_audio_stop()
353 if (!audiobuf_created)
356 oal_check_for_errors("mve_audio_stop() begin");
358 mve_audio_playing = 0;
359 mve_audio_canplay = 0;
360 mve_audio_compressed = 0;
362 audiobuf_created = 0;
364 mve_audio_bufl_free.clear();
368 alSourceStop(mas->chan->source_id);
370 // detach buffers from source so that we can delete them
371 alSourcei(mas->chan->source_id, AL_BUFFER, 0);
374 for (int i = 0; i < MVE_AUDIO_BUFFERS; i++) {
375 if ( alIsBuffer(mas->buffers[i]) ) {
376 alDeleteBuffers(1, &mas->buffers[i]);
386 if (mve_audio_buf != NULL) {
388 mve_audio_buf = NULL;
391 mve_audio_buf_size = 0;
392 mve_audio_buf_offset = 0;
394 oal_check_for_errors("mve_audio_stop() end");
397 int mve_audio_data(ubyte major, ubyte *data)
399 static const int selected_chan = 1;
405 if (mve_audio_canplay) {
406 chan = mve_get_ushort(data + 2);
407 nsamp = mve_get_ushort(data + 4);
409 if (chan & selected_chan) {
410 oal_check_for_errors("mve_audio_data() begin");
412 if ( (mve_audio_buf_offset+nsamp+4) <= mve_audio_buf_size ) {
414 if (mve_audio_compressed) {
415 /* HACK: +4 mveaudio_uncompress adds 4 more bytes */
418 mveaudio_uncompress(mve_audio_buf+mve_audio_buf_offset, data, -1);
423 memcpy(mve_audio_buf+mve_audio_buf_offset, data, nsamp);
427 memset(mve_audio_buf+mve_audio_buf_offset, 0, nsamp);
430 mve_audio_buf_offset += nsamp;
432 mprintf(("MVE audio_buf overrun!!\n"));
435 alGetSourcei(mas->chan->source_id, AL_BUFFERS_PROCESSED, &processed);
438 alSourceUnqueueBuffers(mas->chan->source_id, 1, &bid);
440 mve_audio_bufl_free.push_back(bid);
444 if ( !mve_audio_bufl_free.empty() ) {
445 bid = mve_audio_bufl_free.back();
447 alBufferData(bid, mas->format, mve_audio_buf, mve_audio_buf_offset, mas->sample_rate);
448 alSourceQueueBuffers(mas->chan->source_id, 1, &bid);
450 mve_audio_buf_offset = 0;
451 mve_audio_bufl_free.pop_back();
454 if ( !mve_audio_playing ) {
458 oal_check_for_errors("mve_audio_data() end");
465 /*************************
467 *************************/
469 int mve_video_createbuf(ubyte minor, ubyte *data)
471 if (videobuf_created)
474 if (gr_screen.mode != GR_OPENGL) {
475 mprintf(("MVE-ERROR: Movie playback requires OpenGL renderer\n"));
476 videobuf_created = 1;
480 if (gr_screen.use_sections) {
481 mprintf(("MVE-ERROR: Bitmap sections not supported\n"));
482 videobuf_created = 1;
489 w = mve_get_short(data);
490 h = mve_get_short(data+2);
495 // with Pierre's decoder16 fix in opcode 0xc, 8 should no longer be needed
496 g_vBackBuf1 = g_vBuffers = malloc(g_width * g_height * 4);
498 if (g_vBackBuf1 == NULL) {
499 mprintf(("MVE-ERROR: Can't allocate video buffer\n"));
500 videobuf_created = 1;
504 g_vBackBuf2 = (ushort *)g_vBackBuf1 + (g_width * g_height);
506 memset(g_vBackBuf1, 0, g_width * g_height * 4);
508 // DDOI - Allocate RGB565 pixel buffer
509 pixelbuf = (ushort *)malloc (g_width * g_height * 2);
511 if (pixelbuf == NULL) {
512 mprintf(("MVE-ERROR: Can't allocate memory for pixelbuf\n"));
513 videobuf_created = 1;
517 memset(pixelbuf, 0, g_width * g_height * 2);
519 // set height and width to a power of 2
520 tex_w = next_pow2(g_width);
521 tex_h = next_pow2(g_height);
523 SDL_assert(tex_w > 0);
524 SDL_assert(tex_h > 0);
526 glGenTextures(1, &tex);
529 mprintf(("MVE-ERROR: Can't create a GL texture\n"));
530 videobuf_created = 1;
534 glBindTexture(GL_TEXTURE_2D, tex);
536 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
537 glDepthFunc(GL_ALWAYS);
538 glDepthMask(GL_FALSE);
539 glDisable(GL_DEPTH_TEST);
540 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
541 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
546 x = ((gr_screen.max_w - g_width) / 2);
547 y = ((gr_screen.max_h - g_height) / 2);
549 if ( os_config_read_uint("Video", "ScaleMovies", 1) ) {
550 extern int GL_viewport_w;
554 mve_viewport_scale = GL_viewport_w / (float)g_width;
555 mve_viewport_w = GL_viewport_w;
558 y = ((480 - g_height) / 2);
563 g_coords[0].u = 0.0f;
564 g_coords[0].v = 0.0f;
567 g_coords[1].y = y + g_height;
568 g_coords[1].u = 0.0f;
569 g_coords[1].v = i2fl(g_height) / i2fl(tex_h);
571 g_coords[2].x = x + g_width;
573 g_coords[2].u = i2fl(g_width) / i2fl(tex_w);
574 g_coords[2].v = 0.0f;
576 g_coords[3].x = x + g_width;
577 g_coords[3].y = y + g_height;
578 g_coords[3].u = i2fl(g_width) / i2fl(tex_w);
579 g_coords[3].v = i2fl(g_height) / i2fl(tex_h);
581 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
582 glEnableClientState(GL_VERTEX_ARRAY);
584 glTexCoordPointer(2, GL_FLOAT, sizeof(g_coords_t), &g_coords[0].u);
585 glVertexPointer(2, GL_INT, sizeof(g_coords_t), &g_coords[0].x);
587 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5_A1, tex_w, tex_h, 0, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, NULL);
589 videobuf_created = 1;
594 static void mve_convert_and_draw()
598 ushort *pixels = (ushort *)g_vBackBuf1;
605 for (y=0; y<g_height; y++) {
606 for (x = 0; x < g_width; x++) {
607 pDests[x] = (1<<15)|*pSrcs;
615 void mve_video_display()
617 static uint mve_video_skiptimer = 0;
619 fix t1 = timer_get_fixed_seconds();
621 // micro_frame_delay is divided by 10 to match mve_video_skiptimer overflow catch
622 if ( mve_video_skiptimer > (uint)(micro_frame_delay/10) ) {
623 // we are running slow so subtract desired time from actual and skip this frame
624 mve_video_skiptimer -= (micro_frame_delay/10);
627 // zero out so we can get a new count
628 mve_video_skiptimer = 0;
631 mve_convert_and_draw();
633 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, g_width, g_height, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, pixelbuf);
635 if (mve_scale_video) {
638 glScalef(mve_viewport_scale, mve_viewport_scale, 1.0f);
641 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
643 if (mve_scale_video) {
649 if (mve_needs_clear) {
654 fix t2 = timer_get_fixed_seconds();
656 // only get a new count if we are definitely through with old count
657 if ( mve_video_skiptimer == 0 ) {
658 // for a more accurate count convert the frame rate to a float and multiply
659 // by one-hundred-thousand before converting to an uint.
660 mve_video_skiptimer = (uint)(f2fl(t2-t1) * 100000);
664 void mve_video_codemap(ubyte *data, int len)
670 void mve_video_data(ubyte *data, int len)
675 nFlags = mve_get_ushort(data+12);
678 temp = (ubyte *)g_vBackBuf1;
679 g_vBackBuf1 = g_vBackBuf2;
683 decodeFrame16((ubyte *)g_vBackBuf1, g_pCurMap, g_nMapLength, data+14, len-14);
691 void mve_init(MVESTREAM *mve)
693 // reset to default values
694 mve_audio_playing = 0;
695 mve_audio_canplay = 0;
696 mve_audio_compressed = 0;
697 mve_audio_buf_offset = 0;
698 audiobuf_created = 0;
700 videobuf_created = 0;
703 mve_viewport_scale = 1.0f;
708 void mve_play(MVESTREAM *mve)
710 int init_timer = 0, timer_error = 0;
716 while (cont && mve_playing && !timer_error) {
717 cont = mve_play_next_chunk(mve);
719 if (micro_frame_delay && !init_timer) {
724 timer_error = mve_do_timer_wait();
728 if (key_inkey() == SDLK_ESCAPE) {
732 // check if viewport size changed and adjust scaling accordingly
733 extern int GL_viewport_w;
735 if (mve_viewport_w != GL_viewport_w) {
736 mve_viewport_w = GL_viewport_w;
739 if (mve_scale_video) {
740 mve_viewport_scale = GL_viewport_w / (float)g_width;
752 if (pixelbuf != NULL) {
757 if (g_vBuffers != NULL) {
762 if (gr_screen.mode == GR_OPENGL) {
764 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
765 glDisableClientState(GL_VERTEX_ARRAY);
767 glBindTexture(GL_TEXTURE_2D, 0);
768 glDeleteTextures(1, &tex);
772 if (mve_scale_video) {
773 glMatrixMode(GL_MODELVIEW);
777 glEnable(GL_DEPTH_TEST);