1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "common/scummsys.h"
24 #include "common/stream.h"
25 #include "common/textconsole.h"
26
27 #include "audio/audiostream.h"
28 #include "audio/decoders/raw.h"
29
30 #include "access/access.h"
31 #include "access/video/movie_decoder.h"
32
33 // for Test-Code
34 #include "common/system.h"
35 #include "common/events.h"
36 #include "common/keyboard.h"
37 #include "engines/engine.h"
38 #include "engines/util.h"
39 #include "graphics/palette.h"
40 #include "graphics/pixelformat.h"
41 #include "graphics/surface.h"
42
43 namespace Access {
44
AccessVIDMovieDecoder()45 AccessVIDMovieDecoder::AccessVIDMovieDecoder()
46 : _stream(0), _videoTrack(0), _audioTrack(0) {
47 _streamSeekOffset = 0;
48 _streamVideoIndex = 0;
49 _streamAudioIndex = 0;
50 }
51
~AccessVIDMovieDecoder()52 AccessVIDMovieDecoder::~AccessVIDMovieDecoder() {
53 close();
54 }
55
loadStream(Common::SeekableReadStream * stream)56 bool AccessVIDMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
57 uint32 videoCodecTag = 0;
58 uint32 videoHeight = 0;
59 uint32 videoWidth = 0;
60 uint16 regularDelay = 0;
61 uint32 audioSampleRate = 0;
62
63 close();
64
65 _stream = stream;
66 _streamSeekOffset = 15; // offset of first chunk
67 _streamVideoIndex = 0;
68 _streamAudioIndex = 0;
69
70 // read header
71 // ID [dword] "VID"
72 // ?? [byte]
73 // ?? [word]
74 // width [word]
75 // height [word]
76 // regular delay between frames (60 per second) [word]
77 // ?? [word]
78
79 videoCodecTag = _stream->readUint32BE();
80 if (videoCodecTag != MKTAG('V','I','D',0x00)) {
81 warning("AccessVIDMoviePlay: bad codec tag, not a video file?");
82 close();
83 return false;
84 }
85 _stream->skip(3);
86 videoWidth = _stream->readUint16LE();
87 videoHeight = _stream->readUint16LE();
88 regularDelay = _stream->readUint16LE();
89 _stream->skip(2);
90
91 if (!regularDelay) {
92 warning("AccessVIDMoviePlay: delay between frames is zero?");
93 close();
94 return false;
95 }
96
97 // create video track
98 _videoTrack = new StreamVideoTrack(videoWidth, videoHeight, regularDelay);
99 addTrack(_videoTrack);
100
101 //warning("width %d, height %d", videoWidth, videoHeight);
102
103 // Look through the first few packets
104 static const int maxPacketCheckCount = 10;
105
106 for (int i = 0; i < maxPacketCheckCount; i++) {
107 byte chunkId = _stream->readByte();
108
109 // Bail out if done
110 if (_stream->eos())
111 break;
112
113 // Bail also in case end of file chunk was found
114 if (chunkId == kVIDMovieChunkId_EndOfFile)
115 break;
116
117 uint32 chunkStartOffset = _stream->pos();
118 //warning("data chunk at %x", chunkStartOffset);
119
120 switch (chunkId) {
121 case kVIDMovieChunkId_FullFrame:
122 case kVIDMovieChunkId_FullFrameCompressed:
123 case kVIDMovieChunkId_PartialFrameCompressed:
124 case kVIDMovieChunkId_FullFrameCompressedFill: {
125 if (!_videoTrack->skipOverFrame(_stream, chunkId)) {
126 close();
127 return false;
128 }
129 break;
130 }
131
132 case kVIDMovieChunkId_Palette: {
133 if (!_videoTrack->skipOverPalette(_stream)) {
134 close();
135 return false;
136 }
137 break;
138 }
139
140 case kVIDMovieChunkId_AudioFirstChunk:
141 case kVIDMovieChunkId_Audio: {
142 // sync [word]
143 // sampling rate [byte]
144 // size of audio data [word]
145 // sample data [] (mono, 8-bit, unsigned)
146 //
147 // Only first chunk has sync + sampling rate
148 if (chunkId == kVIDMovieChunkId_AudioFirstChunk) {
149 byte soundblasterRate;
150
151 _stream->skip(2); // skip over sync
152 soundblasterRate = _stream->readByte();
153 audioSampleRate = 1000000 / (256 - soundblasterRate);
154
155 _audioTrack = new StreamAudioTrack(audioSampleRate, getSoundType());
156 addTrack(_audioTrack);
157
158 _stream->seek(chunkStartOffset); // seek back
159 }
160
161 if (!_audioTrack) {
162 warning("AccessVIDMoviePlay: regular audio chunk, before audio chunk w/ header");
163 close();
164 return false;
165 }
166 if (!_audioTrack->skipOverAudio(_stream, chunkId)) {
167 close();
168 return false;
169 }
170 break;
171 }
172
173 default:
174 warning("AccessVIDMoviePlay: Unknown chunk-id '%x' inside VID movie", chunkId);
175 close();
176 return false;
177 }
178
179 // Remember this chunk inside our cache
180 IndexCacheEntry indexCacheEntry;
181
182 indexCacheEntry.chunkId = chunkId;
183 indexCacheEntry.offset = chunkStartOffset;
184
185 _indexCacheTable.push_back(indexCacheEntry);
186
187 // Got an audio chunk now? -> exit b/c we are done
188 if (audioSampleRate)
189 break;
190 }
191
192 // Remember offset of latest not-indexed-yet chunk
193 _streamSeekOffset = _stream->pos();
194
195 // If sample rate was found, create an audio track
196 if (audioSampleRate) {
197 _audioTrack = new StreamAudioTrack(audioSampleRate, getSoundType());
198 addTrack(_audioTrack);
199 }
200
201 // Rewind back to the beginning right to the first chunk
202 _stream->seek(15);
203
204 return true;
205 }
206
close()207 void AccessVIDMovieDecoder::close() {
208 Video::VideoDecoder::close();
209
210 delete _stream; _stream = 0;
211 _videoTrack = 0;
212
213 _indexCacheTable.clear();
214 }
215
216 // We try to at least decode 1 frame
217 // and also try to get at least 0.5 seconds of audio queued up
readNextPacket()218 void AccessVIDMovieDecoder::readNextPacket() {
219 uint32 currentMovieTime = getTime();
220 uint32 wantedAudioQueued = currentMovieTime + 500; // always try to be 0.500 seconds in front of movie time
221
222 uint32 streamIndex = 0;
223 IndexCacheEntry indexEntry;
224 bool currentlySeeking = false;
225
226 bool videoDone = false;
227 bool audioDone = false;
228
229 // Seek to smallest stream offset
230 if ((_streamVideoIndex <= _streamAudioIndex) || (!_audioTrack)) {
231 streamIndex = _streamVideoIndex;
232 } else {
233 streamIndex = _streamAudioIndex;
234 }
235
236 if (_audioTrack) {
237 if (wantedAudioQueued <= _audioTrack->getTotalAudioQueued()) {
238 // already got enough audio queued up
239 audioDone = true;
240 }
241 } else {
242 // no audio track, audio is always done
243 audioDone = true;
244 }
245
246 while (1) {
247 // Check, if stream-index is already cached
248 if (streamIndex < _indexCacheTable.size()) {
249 indexEntry.chunkId = _indexCacheTable[streamIndex].chunkId;
250 indexEntry.offset = _indexCacheTable[streamIndex].offset;
251 currentlySeeking = false;
252
253 } else {
254 // read from file
255 _stream->seek(_streamSeekOffset);
256 indexEntry.chunkId = _stream->readByte();
257 indexEntry.offset = _stream->pos();
258 currentlySeeking = true;
259
260 // and store that as well
261 _indexCacheTable.push_back(indexEntry);
262 }
263
264 // end of stream -> exit
265 if (_stream->eos())
266 break;
267
268 // end of file chunk -> exit
269 if (indexEntry.chunkId == kVIDMovieChunkId_EndOfFile)
270 break;
271
272 // warning("chunk %x", indexEntry.chunkId);
273
274 switch (indexEntry.chunkId) {
275 case kVIDMovieChunkId_FullFrame:
276 case kVIDMovieChunkId_FullFrameCompressed:
277 case kVIDMovieChunkId_PartialFrameCompressed:
278 case kVIDMovieChunkId_FullFrameCompressedFill: {
279 if ((_streamVideoIndex <= streamIndex) && (!videoDone)) {
280 // We are at an index, that is still relevant for video decoding
281 // and we are not done with video yet
282 if (!currentlySeeking) {
283 // seek to stream position in case we used the cache
284 _stream->seek(indexEntry.offset);
285 }
286 //warning("video decode chunk %x at %lx", indexEntry.chunkId, _stream->pos());
287 _videoTrack->decodeFrame(_stream, indexEntry.chunkId);
288 videoDone = true;
289 _streamVideoIndex = streamIndex + 1;
290 } else {
291 if (currentlySeeking) {
292 // currently seeking, so we have to skip the frame bytes manually
293 _videoTrack->skipOverFrame(_stream, indexEntry.chunkId);
294 }
295 }
296 break;
297 }
298
299 case kVIDMovieChunkId_Palette: {
300 if ((_streamVideoIndex <= streamIndex) && (!videoDone)) {
301 // We are at an index, that is still relevant for video decoding
302 // and we are not done with video yet
303 if (!currentlySeeking) {
304 // seek to stream position in case we used the cache
305 _stream->seek(indexEntry.offset);
306 }
307 _videoTrack->decodePalette(_stream);
308 _streamVideoIndex = streamIndex + 1;
309 } else {
310 if (currentlySeeking) {
311 // currently seeking, so we have to skip the frame bytes manually
312 _videoTrack->skipOverPalette(_stream);
313 }
314 }
315 break;
316 }
317
318 case kVIDMovieChunkId_AudioFirstChunk:
319 case kVIDMovieChunkId_Audio: {
320 if ((_streamAudioIndex <= streamIndex) && (!audioDone)) {
321 // We are at an index that is still relevant for audio decoding
322 if (!currentlySeeking) {
323 // seek to stream position in case we used the cache
324 _stream->seek(indexEntry.offset);
325 }
326 _audioTrack->queueAudio(_stream, indexEntry.chunkId);
327 _streamAudioIndex = streamIndex + 1;
328
329 if (wantedAudioQueued <= _audioTrack->getTotalAudioQueued()) {
330 // Got enough audio
331 audioDone = true;
332 }
333 } else {
334 if (!_audioTrack) {
335 error("AccessVIDMoviePlay: audio chunks found without audio track active");
336 }
337 if (currentlySeeking) {
338 // currently seeking, so we have to skip the audio bytes manually
339 _audioTrack->skipOverAudio(_stream, indexEntry.chunkId);
340 }
341 }
342 break;
343 }
344
345 default:
346 error("AccessVIDMoviePlay: Unknown chunk-id '%x' inside VID movie", indexEntry.chunkId);
347 }
348
349 if (currentlySeeking) {
350 // remember currently stream offset in case we are seeking
351 _streamSeekOffset = _stream->pos();
352 }
353
354 // go to next index
355 streamIndex++;
356
357 if ((videoDone) && (audioDone)) {
358 return;
359 }
360 }
361
362 if (!videoDone) {
363 // no more video frames? set end of video track
364 _videoTrack->setEndOfTrack();
365 }
366 }
367
StreamVideoTrack(uint32 width,uint32 height,uint16 regularFrameDelay)368 AccessVIDMovieDecoder::StreamVideoTrack::StreamVideoTrack(uint32 width, uint32 height, uint16 regularFrameDelay) {
369 _width = width;
370 _height = height;
371 _regularFrameDelay = regularFrameDelay;
372 _curFrame = -1;
373 _nextFrameStartTime = 0;
374 _endOfTrack = false;
375 _dirtyPalette = false;
376
377 memset(&_palette, 0, sizeof(_palette));
378
379 _surface = new Graphics::Surface();
380 _surface->create(_width, _height, Graphics::PixelFormat::createFormatCLUT8());
381 }
382
~StreamVideoTrack()383 AccessVIDMovieDecoder::StreamVideoTrack::~StreamVideoTrack() {
384 delete _surface;
385 }
386
endOfTrack() const387 bool AccessVIDMovieDecoder::StreamVideoTrack::endOfTrack() const {
388 return _endOfTrack;
389 }
390
getPixelFormat() const391 Graphics::PixelFormat AccessVIDMovieDecoder::StreamVideoTrack::getPixelFormat() const {
392 return _surface->format;
393 }
394
decodeFrame(Common::SeekableReadStream * stream,byte chunkId)395 void AccessVIDMovieDecoder::StreamVideoTrack::decodeFrame(Common::SeekableReadStream *stream, byte chunkId) {
396 byte *framePixelsPtr = (byte *)_surface->getPixels();
397 byte *pixelsPtr = framePixelsPtr;
398 byte rleByte = 0;
399 uint16 additionalDelay = 0;
400 int32 expectedPixels = 0;
401
402 switch (chunkId) {
403 case kVIDMovieChunkId_FullFrame: {
404 // Full frame is:
405 // data [width * height]
406 additionalDelay = stream->readUint16LE();
407 stream->read(framePixelsPtr, _width * _height);
408 break;
409 }
410
411 case kVIDMovieChunkId_FullFrameCompressed:
412 case kVIDMovieChunkId_PartialFrameCompressed: {
413 // Skip manually over compressed data
414 // Full frame compressed is:
415 // additional delay [word]
416 // REPEAT:
417 // RLE [byte]
418 // RLE upper bit set: skip over RLE & 0x7F pixels
419 // RLE upper bit not set: draw RLE amount of pixels (those pixels follow right after RLE byte)
420 //
421 // Partial frame compressed is:
422 // sync [word]
423 // horizontal start position [word]
424 // REPEAT:
425 // see full frame compressed
426 uint16 horizontalStartPosition = 0;
427
428 additionalDelay = stream->readUint16LE();
429
430 if (chunkId == kVIDMovieChunkId_PartialFrameCompressed) {
431 horizontalStartPosition = stream->readUint16LE();
432 if (horizontalStartPosition >= _height) {
433 error("AccessVIDMoviePlay: starting position larger than height during partial frame compressed, data corrupt?");
434 return;
435 }
436 }
437
438 expectedPixels = _width * (_height - horizontalStartPosition);
439
440 // adjust frame destination pointer
441 pixelsPtr += (horizontalStartPosition * _width);
442
443 while (expectedPixels >= 0) {
444 rleByte = stream->readByte();
445 if (!rleByte) // NUL means end of stream
446 break;
447
448 if (rleByte & 0x80) {
449 rleByte = rleByte & 0x7F;
450 expectedPixels -= rleByte;
451 } else {
452 // skip over pixels
453 expectedPixels -= rleByte;
454 stream->read(pixelsPtr, rleByte); // read pixel data into frame
455 }
456 pixelsPtr += rleByte;
457 }
458 // expectedPixels may be positive here in case stream got terminated with a NUL
459 if (expectedPixels < 0) {
460 error("AccessVIDMoviePlay: pixel count mismatch during full/partial frame compressed, data corrupt?");
461 }
462 break;
463 }
464
465 case kVIDMovieChunkId_FullFrameCompressedFill: {
466 // Full frame compressed fill is:
467 // additional delay [word]
468 // REPEAT:
469 // RLE [byte]
470 // RLE upper bit set: draw RLE amount (& 0x7F) of pixels with specified color (color byte follows after RLE byte)
471 // RLE upper bit not set: draw RLE amount of pixels (those pixels follow right after RLE byte)
472 additionalDelay = stream->readUint16LE();
473 expectedPixels = _width * _height;
474
475 while (expectedPixels > 0) {
476 rleByte = stream->readByte();
477
478 if (rleByte & 0x80) {
479 rleByte = rleByte & 0x7F;
480 expectedPixels -= rleByte;
481
482 byte fillColor = stream->readByte();
483 memset(pixelsPtr, fillColor, rleByte);
484 } else {
485 // skip over pixels
486 expectedPixels -= rleByte;
487 stream->read(pixelsPtr, rleByte); // read pixel data into frame
488 }
489 pixelsPtr += rleByte;
490 }
491 if (expectedPixels < 0) {
492 error("AccessVIDMoviePlay: pixel count mismatch during full frame compressed fill, data corrupt?");
493 }
494 break;
495 }
496 default:
497 assert(0);
498 break;
499 }
500
501 _curFrame++;
502
503 // TODO: not sure, if additionalDelay is supposed to affect the follow-up frame or the current frame
504 // the videos, that I found, don't have it set
505 uint32 currentFrameStartTime = getNextFrameStartTime();
506 uint32 nextFrameStartTime = (_regularFrameDelay * _curFrame) * 1000 / 60;
507 if (additionalDelay) {
508 nextFrameStartTime += additionalDelay * 1000 / 60;
509 }
510 assert(currentFrameStartTime <= nextFrameStartTime);
511 setNextFrameStartTime(nextFrameStartTime);
512 }
513
skipOverFrame(Common::SeekableReadStream * stream,byte chunkId)514 bool AccessVIDMovieDecoder::StreamVideoTrack::skipOverFrame(Common::SeekableReadStream *stream, byte chunkId) {
515 byte rleByte = 0;
516 int32 expectedPixels = 0;
517
518 switch (chunkId) {
519 case kVIDMovieChunkId_FullFrame: {
520 // Full frame is:
521 // additional delay [word]
522 // data [width * height]
523 stream->skip(2);
524 stream->skip(_width * _height);
525 break;
526 }
527
528 case kVIDMovieChunkId_FullFrameCompressed:
529 case kVIDMovieChunkId_PartialFrameCompressed: {
530 // Skip manually over compressed data
531 // Full frame compressed is:
532 // additional delay [word]
533 // REPEAT:
534 // RLE [byte]
535 // RLE upper bit set: skip over RLE & 0x7F pixels
536 // RLE upper bit not set: draw RLE amount of pixels (those pixels follow right after RLE byte)
537 //
538 // Partial frame compressed is:
539 // sync [word]
540 // horizontal start position [word]
541 // REPEAT:
542 // see full frame compressed
543 uint16 horizontalStartPosition = 0;
544
545 stream->skip(2);
546
547 if (chunkId == kVIDMovieChunkId_PartialFrameCompressed) {
548 horizontalStartPosition = stream->readUint16LE();
549 if (horizontalStartPosition >= _height) {
550 warning("AccessVIDMoviePlay: starting position larger than height during partial frame compressed, data corrupt?");
551 return false;
552 }
553 }
554
555 expectedPixels = _width * (_height - horizontalStartPosition);
556
557 while (expectedPixels >= 0) {
558 rleByte = stream->readByte();
559 if (!rleByte) // NUL means end of stream
560 break;
561
562 if (rleByte & 0x80) {
563 expectedPixels -= rleByte & 0x7F;
564 } else {
565 // skip over pixels
566 expectedPixels -= rleByte;
567 stream->skip(rleByte); // skip over pixel data
568 }
569 }
570 // expectedPixels may be positive here in case stream got terminated with a NUL
571 if (expectedPixels < 0) {
572 warning("AccessVIDMoviePlay: pixel count mismatch during full/partial frame compressed, data corrupt?");
573 return false;
574 }
575 break;
576 }
577
578 case kVIDMovieChunkId_FullFrameCompressedFill: {
579 // Full frame compressed fill is:
580 // additional delay [word]
581 // REPEAT:
582 // RLE [byte]
583 // RLE upper bit set: draw RLE amount (& 0x7F) of pixels with specified color (color byte follows after RLE byte)
584 // RLE upper bit not set: draw RLE amount of pixels (those pixels follow right after RLE byte)
585 stream->skip(2);
586 expectedPixels = _width * _height;
587
588 while (expectedPixels > 0) {
589 rleByte = stream->readByte();
590
591 if (rleByte & 0x80) {
592 expectedPixels -= rleByte & 0x7F;
593 stream->skip(1);
594 } else {
595 // skip over pixels
596 expectedPixels -= rleByte;
597 stream->skip(rleByte); // skip over pixel data
598 }
599 }
600 if (expectedPixels < 0) {
601 warning("AccessVIDMoviePlay: pixel count mismatch during full frame compressed fill, data corrupt?");
602 return false;
603 }
604 break;
605 }
606 default:
607 assert(0);
608 break;
609 }
610 return true;
611 }
612
skipOverPalette(Common::SeekableReadStream * stream)613 bool AccessVIDMovieDecoder::StreamVideoTrack::skipOverPalette(Common::SeekableReadStream *stream) {
614 stream->skip(0x300); // 3 bytes per color, 256 colors
615 return true;
616 }
617
decodePalette(Common::SeekableReadStream * stream)618 void AccessVIDMovieDecoder::StreamVideoTrack::decodePalette(Common::SeekableReadStream *stream) {
619 byte red, green, blue;
620 assert(stream);
621
622 // VID files use a 6-bit palette and not a 8-bit one, we change it to 8-bit
623 for (uint16 curColor = 0; curColor < 256; curColor++) {
624 red = stream->readByte() & 0x3F;
625 green = stream->readByte() & 0x3F;
626 blue = stream->readByte() & 0x3F;
627 _palette[curColor * 3] = (red << 2) | (red >> 4);
628 _palette[curColor * 3 + 1] = (green << 2) | (green >> 4);
629 _palette[curColor * 3 + 2] = (blue << 2) | (blue >> 4);
630 }
631
632 _dirtyPalette = true;
633 }
634
getPalette() const635 const byte *AccessVIDMovieDecoder::StreamVideoTrack::getPalette() const {
636 _dirtyPalette = false;
637 return _palette;
638 }
639
hasDirtyPalette() const640 bool AccessVIDMovieDecoder::StreamVideoTrack::hasDirtyPalette() const {
641 return _dirtyPalette;
642 }
643
StreamAudioTrack(uint32 sampleRate,Audio::Mixer::SoundType soundType)644 AccessVIDMovieDecoder::StreamAudioTrack::StreamAudioTrack(uint32 sampleRate, Audio::Mixer::SoundType soundType) :
645 AudioTrack(soundType) {
646 _totalAudioQueued = 0; // currently 0 milliseconds queued
647
648 _sampleRate = sampleRate;
649 _stereo = false; // always mono
650
651 _audioStream = Audio::makeQueuingAudioStream(sampleRate, _stereo);
652 }
653
~StreamAudioTrack()654 AccessVIDMovieDecoder::StreamAudioTrack::~StreamAudioTrack() {
655 delete _audioStream;
656 }
657
queueAudio(Common::SeekableReadStream * stream,byte chunkId)658 void AccessVIDMovieDecoder::StreamAudioTrack::queueAudio(Common::SeekableReadStream *stream, byte chunkId) {
659 Common::SeekableReadStream *rawAudioStream = 0;
660 Audio::RewindableAudioStream *audioStream = 0;
661 uint32 audioLengthMSecs = 0;
662
663 if (chunkId == kVIDMovieChunkId_AudioFirstChunk) {
664 stream->skip(3); // skip over additional delay + sample rate
665 }
666
667 uint32 audioSize = stream->readUint16LE();
668
669 // Read the specified chunk into memory
670 rawAudioStream = stream->readStream(audioSize);
671 audioLengthMSecs = audioSize * 1000 / _sampleRate; // 1 byte == 1 8-bit sample
672
673 audioStream = Audio::makeRawStream(rawAudioStream, _sampleRate, Audio::FLAG_UNSIGNED | Audio::FLAG_LITTLE_ENDIAN, DisposeAfterUse::YES);
674 if (audioStream) {
675 _totalAudioQueued += audioLengthMSecs;
676 _audioStream->queueAudioStream(audioStream, DisposeAfterUse::YES);
677 } else {
678 // in case there was an error
679 delete rawAudioStream;
680 }
681 }
682
skipOverAudio(Common::SeekableReadStream * stream,byte chunkId)683 bool AccessVIDMovieDecoder::StreamAudioTrack::skipOverAudio(Common::SeekableReadStream *stream, byte chunkId) {
684 if (chunkId == kVIDMovieChunkId_AudioFirstChunk) {
685 stream->skip(3); // skip over additional delay + sample rate
686 }
687 uint32 audioSize = stream->readUint16LE();
688 stream->skip(audioSize);
689 return true;
690 }
691
getAudioStream() const692 Audio::AudioStream *AccessVIDMovieDecoder::StreamAudioTrack::getAudioStream() const {
693 return _audioStream;
694 }
695
playMovie(const Common::String & filename,const Common::Point & pos)696 bool AccessEngine::playMovie(const Common::String &filename, const Common::Point &pos) {
697 AccessVIDMovieDecoder *videoDecoder = new AccessVIDMovieDecoder();
698
699 Common::Point framePos(pos.x, pos.y);
700
701 if (!videoDecoder->loadFile(filename)) {
702 warning("AccessVIDMoviePlay: could not open '%s'", filename.c_str());
703 return false;
704 }
705
706 bool skipVideo = false;
707
708 _events->clearEvents();
709 videoDecoder->start();
710
711 while (!shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) {
712 if (videoDecoder->needsUpdate()) {
713 const Graphics::Surface *frame = videoDecoder->decodeNextFrame();
714
715 if (frame) {
716 _screen->blitFrom(*frame);
717
718 if (videoDecoder->hasDirtyPalette()) {
719 const byte *palette = videoDecoder->getPalette();
720 g_system->getPaletteManager()->setPalette(palette, 0, 256);
721 }
722
723 _screen->update();
724 }
725 }
726
727 _events->pollEventsAndWait();
728
729 Common::KeyState keyState;
730 if (_events->getKey(keyState)) {
731 if (keyState.keycode == Common::KEYCODE_ESCAPE)
732 skipVideo = true;
733 }
734 }
735
736 videoDecoder->close();
737 delete videoDecoder;
738
739 return !skipVideo;
740 }
741
742 } // End of namespace Access
743