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 "bladerunner/vqa_player.h"
24
25 #include "bladerunner/bladerunner.h"
26 #include "bladerunner/time.h"
27 #include "bladerunner/audio_player.h"
28
29 #include "audio/decoders/raw.h"
30
31 #include "common/system.h"
32
33 namespace BladeRunner {
34
open()35 bool VQAPlayer::open() {
36 _s = _vm->getResourceStream(_name);
37 if (!_s) {
38 return false;
39 }
40
41 if (!_decoder.loadStream(_s)) {
42 delete _s;
43 _s = nullptr;
44 return false;
45 }
46
47 #if !BLADERUNNER_ORIGINAL_BUGS
48 // TB05 has wrong end of a loop and this will load empty zbuffer from next loop, which will lead to broken pathfinding
49 if (_name.equals("TB05_2.VQA")) {
50 _decoder._loopInfo.loops[1].end = 60;
51 } else if (_name.equals("DR04OVER.VQA")) {
52 // smoke (overlay) after explosion of Dermo Labs in DR04
53 // This has still frames in the end that so it looked as if the smoke was "frozen"
54 _decoder._loopInfo.loops[0].end = 58; // 59 up to 74 are still frames
55 }
56 #endif
57
58 _hasAudio = _decoder.hasAudio();
59 if (_hasAudio) {
60 _audioStream = Audio::makeQueuingAudioStream(_decoder.frequency(), false);
61 _lastAudioFrameSuccessfullyQueued = 1;
62 }
63
64 _repeatsCount = 0;
65 _loopNext = -1;
66 _frame = -1;
67 _frameBeginNext = -1;
68 _frameEnd = getFrameCount() - 1;
69 _frameEndQueued = -1;
70 _repeatsCountQueued = -1;
71
72 if (_loopInitial >= 0) {
73 // loopInitial is set to the loop Id value that should play,
74 // when the SeekableReadStream (_s) is nullptr
75 // see setLoop()
76 setLoop(_loopInitial, _repeatsCountInitial, kLoopSetModeImmediate, nullptr, nullptr);
77 } else {
78 _frameNext = 0;
79 // TODO? Removed as redundant
80 // setBeginAndEndFrame(0, _frameEnd, 0, kLoopSetModeJustStart, nullptr, nullptr);
81 }
82
83 return true;
84 }
85
close()86 void VQAPlayer::close() {
87 _vm->_mixer->stopHandle(_soundHandle);
88 delete _s;
89 _s = nullptr;
90 }
91
update(bool forceDraw,bool advanceFrame,bool useTime,Graphics::Surface * customSurface)92 int VQAPlayer::update(bool forceDraw, bool advanceFrame, bool useTime, Graphics::Surface *customSurface) {
93 uint32 now = 60 * _vm->_time->currentSystem();
94 int result = -1;
95
96 if (_frameNext < 0) {
97 _frameNext = _frameBeginNext;
98 }
99
100 if ((_repeatsCount > 0 || _repeatsCount == -1) && (_frameNext > _frameEnd)) {
101 int loopEndQueued = _frameEndQueued;
102 if (_frameEndQueued != -1) {
103 _frameEnd = _frameEndQueued;
104 _frameEndQueued = -1;
105 #if !BLADERUNNER_ORIGINAL_BUGS
106 // Fix glitch in transition from inShot to mainloop
107 // in Act 5 at McCoy's apartment (moving from bedroom to balcony).
108 // This emulates a fast-forward, which is required
109 // in order to have proper z-buffer info,
110 // and display the new first frame of the loop (60) without artifacts.
111 // The code is similar to Scene::advanceFrame()
112 // This will be done once, since this first loop (loopId 1)
113 // is only executed once before moving on to loopId 2
114 if (_name.equals("MA05_3.VQA") && _loopNext == 1) {
115 while (update(false, true, false) != 59) {
116 updateZBuffer(_vm->_zbuffer);
117 }
118 // This works because the loopId 1 executes once before moving to _loop 2
119 // See Scene::advanceFrame()
120 // Scene::loopEnded()
121 //
122 _frameBeginNext = 60;
123 }
124 #endif
125 }
126 if (_frameNext != _frameBeginNext) {
127 _frameNext = _frameBeginNext;
128 }
129
130 if (loopEndQueued == -1) {
131 if (_repeatsCount != -1) {
132 --_repeatsCount;
133 }
134 //callback for repeat, it is not used in the blade runner
135 } else {
136 _repeatsCount = _repeatsCountQueued;
137 _repeatsCountQueued = -1;
138
139 if (_callbackLoopEnded != nullptr) {
140 _callbackLoopEnded(_callbackData, 0, _loopNext);
141 }
142 }
143 result = -1;
144 } else if (_frameNext > _frameEnd) {
145 result = -3;
146 // _repeatsCount == 0, so return here at the end of the video, to release the resource
147 return result;
148 } else if (useTime && (now - (_frameNextTime - kVqaFrameTimeDiff) < kVqaFrameTimeDiff)) {
149 // Not yet time to move to next frame.
150 // Note, we use unsigned difference to avoid potential time overflow issues
151 result = -1;
152 } else if (advanceFrame) {
153 _frame = _frameNext;
154 _decoder.readFrame(_frameNext, kVQAReadVideo);
155 _decoder.decodeVideoFrame(customSurface != nullptr ? customSurface : _surface, _frameNext);
156
157 int maxAllowedAudioPreloadedFrames = kMaxAudioPreloadedFrames;
158 if (_frameEnd - _frameNext < kMaxAudioPreloadedFrames - 1) {
159 maxAllowedAudioPreloadedFrames = _frameEnd - _frameNext + 1;
160 }
161
162 if (_hasAudio) {
163 if (!_audioStarted) {
164 // start with preloading up to (kMaxAudioPreloadedFrames - 1) frames at most, before reaching the _frameEnd frame
165 for (int i = 0; i < kMaxAudioPreloadedFrames - 1; ++i) {
166 if (_frameNext + i < _frameEnd) {
167 _decoder.readFrame(_frameNext + i, kVQAReadAudio);
168 queueAudioFrame(_decoder.decodeAudioFrame());
169 _lastAudioFrameSuccessfullyQueued = _frameNext + i;
170 }
171 }
172 if (_vm->_mixer->isReady()) {
173 // Audio stream starts playing, consuming queued "audio frames"
174 // Note: On its own, the audio will not re-synch with video;
175 // It plays independently so it can get ahead!
176 _vm->_mixer->playStream(kVQASoundType, &_soundHandle, _audioStream);
177 }
178 _audioStarted = true;
179 }
180
181 // Due to our audio stream being queuable, the queued audio frames will play,
182 // even if the game is "paused" eg. by moving the ScummVM window.
183 // However, the video will stop playing immediately in that case.
184 // That would result in a audio video desynch, with audio being ahead of video.
185 // When the video resumes, we need to catch up to the audio "frame" of the queue that was last played,
186 // without queuing more audio, and then start queuing audio again.
187
188 // The following still covers the case of adding the final 15th audio frame to the queue
189 // when first starting the audio stream.
190 int tmpCurrentQueuedAudioFrames = getQueuedAudioFrames();
191 if (_lastAudioFrameSuccessfullyQueued != _frameEnd) {
192 // if video is behind audio, then resynch,
193 // which here means: don't queue and don't play audio until video catches up.
194 if (_lastAudioFrameSuccessfullyQueued - tmpCurrentQueuedAudioFrames < _frameNext) {
195 int addToQueueRep = 0;
196 while (addToQueueRep < (maxAllowedAudioPreloadedFrames - tmpCurrentQueuedAudioFrames)
197 && _lastAudioFrameSuccessfullyQueued + 1 <= _frameEnd) {
198 _decoder.readFrame(_lastAudioFrameSuccessfullyQueued + 1, kVQAReadAudio);
199 queueAudioFrame(_decoder.decodeAudioFrame());
200 ++_lastAudioFrameSuccessfullyQueued;
201 ++addToQueueRep;
202 }
203 }
204 }
205 }
206
207 if (useTime) {
208 _frameNextTime += kVqaFrameTimeDiff;
209
210 // In some cases (as overlay paused by kia or game window is moved) new time might be still in the past.
211 // This can cause rapid playback of video where every refresh renders different frame of the video.
212 // Can be avoided by setting next time to the future.
213 // Note, we use unsigned difference to avoid time overflow issues
214 if (now - (_frameNextTime - kVqaFrameTimeDiff) > kVqaFrameTimeDiff) {
215 _frameNextTime = now + kVqaFrameTimeDiff;
216 }
217 }
218 ++_frameNext;
219 result = _frame;
220 }
221
222 if (result < 0 && forceDraw && _frame != -1) {
223 _decoder.decodeVideoFrame(customSurface != nullptr ? customSurface : _surface, _frame, true);
224 result = _frame;
225 }
226 return result; // Note: result here could be negative.
227 // Negative valid value should only be -1, since there are various assertions
228 // assert(frame >= -1) in overlay modes (elevator, scores, spinner)
229 }
230
updateZBuffer(ZBuffer * zbuffer)231 void VQAPlayer::updateZBuffer(ZBuffer *zbuffer) {
232 _decoder.decodeZBuffer(zbuffer);
233 }
234
updateView(View * view)235 void VQAPlayer::updateView(View *view) {
236 _decoder.decodeView(view);
237 }
238
updateScreenEffects(ScreenEffects * screenEffects)239 void VQAPlayer::updateScreenEffects(ScreenEffects *screenEffects) {
240 _decoder.decodeScreenEffects(screenEffects);
241 }
242
updateLights(Lights * lights)243 void VQAPlayer::updateLights(Lights *lights) {
244 _decoder.decodeLights(lights);
245 }
246
setLoop(int loop,int repeatsCount,int loopSetMode,void (* callback)(void *,int,int),void * callbackData)247 bool VQAPlayer::setLoop(int loop, int repeatsCount, int loopSetMode, void (*callback)(void *, int, int), void *callbackData) {
248 if (_s == nullptr) {
249 _loopInitial = loop;
250 _repeatsCountInitial = repeatsCount;
251 return true;
252 }
253
254 int begin, end;
255 if (!_decoder.getLoopBeginAndEndFrame(loop, &begin, &end)) {
256 return false;
257 }
258 if (setBeginAndEndFrame(begin, end, repeatsCount, loopSetMode, callback, callbackData)) {
259 _loopNext = loop;
260 return true;
261 }
262 return false;
263 }
264
setBeginAndEndFrame(int begin,int end,int repeatsCount,int loopSetMode,void (* callback)(void *,int,int),void * callbackData)265 bool VQAPlayer::setBeginAndEndFrame(int begin, int end, int repeatsCount, int loopSetMode, void (*callback)(void *, int, int), void *callbackData) {
266 if ( begin >= getFrameCount()
267 || end >= getFrameCount()
268 || begin >= end
269 || loopSetMode < 0
270 || loopSetMode >= 3
271 ) {
272 warning("VQAPlayer::setBeginAndEndFrame - Invalid arguments for video");
273 return false; // VQA_DECODER_ERROR_BAD_INPUT case
274 }
275
276 if (repeatsCount < 0) {
277 repeatsCount = -1;
278 }
279
280 if (_repeatsCount == 0 && loopSetMode == kLoopSetModeEnqueue) {
281 // if the member var _repeatsCount is 0 (which means "current playing loop will not be repeated")
282 // then do not enqueue and, instead, treat the request as kLoopSetModeImmediate
283 loopSetMode = kLoopSetModeImmediate;
284 }
285
286 _frameBeginNext = begin;
287
288 if (loopSetMode == kLoopSetModeJustStart) {
289 _repeatsCount = repeatsCount;
290 _frameEnd = end;
291 } else if (loopSetMode == kLoopSetModeEnqueue) {
292 _repeatsCountQueued = repeatsCount;
293 _frameEndQueued = end;
294 } else if (loopSetMode == kLoopSetModeImmediate) {
295 _repeatsCount = repeatsCount;
296 _frameEnd = end;
297 seekToFrame(begin);
298 }
299
300 _callbackLoopEnded = callback;
301 _callbackData = callbackData;
302
303 return true;
304 }
305
seekToFrame(int frame)306 bool VQAPlayer::seekToFrame(int frame) {
307 _frameNext = frame;
308 _frameNextTime = 60 * _vm->_time->currentSystem();
309 return true;
310 }
311
getCurrentBeginAndEndFrame(int frame,int * begin,int * end)312 bool VQAPlayer::getCurrentBeginAndEndFrame(int frame, int *begin, int *end) {
313 int playingLoop = _decoder.getLoopIdFromFrame(frame);
314 if (playingLoop != -1) {
315 return _decoder.getLoopBeginAndEndFrame(playingLoop, begin, end);
316 }
317 return false;
318 }
319
getLoopBeginFrame(int loop)320 int VQAPlayer::getLoopBeginFrame(int loop) {
321 int begin, end;
322 if (!_decoder.getLoopBeginAndEndFrame(loop, &begin, &end)) {
323 return -1;
324 }
325 return begin;
326 }
327
getLoopEndFrame(int loop)328 int VQAPlayer::getLoopEndFrame(int loop) {
329 int begin, end;
330 if (!_decoder.getLoopBeginAndEndFrame(loop, &begin, &end)) {
331 return -1;
332 }
333 return end;
334 }
335
getFrameCount() const336 int VQAPlayer::getFrameCount() const {
337 return _decoder.numFrames();
338 }
339
getQueuedAudioFrames() const340 int VQAPlayer::getQueuedAudioFrames() const {
341 return _audioStream->numQueuedStreams();
342 }
343
344 // Adds another audio "frame" to the queue of the audio stream
queueAudioFrame(Audio::AudioStream * audioStream)345 void VQAPlayer::queueAudioFrame(Audio::AudioStream *audioStream) {
346 if (audioStream == nullptr) {
347 return;
348 }
349
350 int n = _audioStream->numQueuedStreams();
351 // TODO Maybe remove this warning or make it a debug-only message?
352 if (n == 0) {
353 warning("numQueuedStreams: %d", n);
354 }
355
356 _audioStream->queueAudioStream(audioStream, DisposeAfterUse::YES);
357 }
358
359 } // End of namespace BladeRunner
360