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