[Scummvm-git-logs] scummvm master -> 772a38588c6712fdd89076ad49b0d03bacdaca18

antoniou79 a.antoniou79 at gmail.com
Sun May 30 16:55:27 UTC 2021


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
772a38588c BLADERUNNER: Attempt to fix video audio desynch


Commit: 772a38588c6712fdd89076ad49b0d03bacdaca18
    https://github.com/scummvm/scummvm/commit/772a38588c6712fdd89076ad49b0d03bacdaca18
Author: antoniou79 (a.antoniou79 at gmail.com)
Date: 2021-05-30T19:55:08+03:00

Commit Message:
BLADERUNNER: Attempt to fix video audio desynch

Should fix the issue that (queued) audio gets ahead of video if eg. window is paused by moving it around

Also added some fixes for time overflow issues for when we compare the times for the next frame to play.

Changed paths:
    engines/bladerunner/audio_mixer.h
    engines/bladerunner/vqa_decoder.cpp
    engines/bladerunner/vqa_player.cpp
    engines/bladerunner/vqa_player.h


diff --git a/engines/bladerunner/audio_mixer.h b/engines/bladerunner/audio_mixer.h
index 13ec9161af..6026b318ac 100644
--- a/engines/bladerunner/audio_mixer.h
+++ b/engines/bladerunner/audio_mixer.h
@@ -79,8 +79,9 @@ public:
 	void adjustVolume(int channel, int newVolume, uint32 time);
 	void adjustPan(int channel, int newPan, uint32 time);
 
-	void resume(int channel, uint32 delay);
-	void pause(int channel, uint32 delay);
+	// TODO Are these completely unused?
+//	void resume(int channel, uint32 delay);
+//	void pause(int channel, uint32 delay);
 
 private:
 	int playInChannel(int channel, Audio::Mixer::SoundType type, Audio::RewindableAudioStream *stream, int priority, bool loop, int volume, int pan, void(*endCallback)(int, void *), void *callbackData, uint32 trackDurationMs);
diff --git a/engines/bladerunner/vqa_decoder.cpp b/engines/bladerunner/vqa_decoder.cpp
index 769a314f82..b46e725f25 100644
--- a/engines/bladerunner/vqa_decoder.cpp
+++ b/engines/bladerunner/vqa_decoder.cpp
@@ -920,7 +920,14 @@ bool VQADecoder::VQAVideoTrack::decodeFrame(Graphics::Surface *surface) {
 }
 
 VQADecoder::VQAAudioTrack::VQAAudioTrack(VQADecoder *vqaDecoder) {
-	_frequency = vqaDecoder->_header.freq;
+	if (vqaDecoder != nullptr) {
+		_frequency = vqaDecoder->_header.freq;
+	} else {
+		warning("VQADecoder::VQAAudioTrack::VQAAudioTrack: null pointer for vqaDecoder parameter");
+		// TODO use some typical value?
+		_frequency = 0;
+	}
+	memset(_compressedAudioFrame, 0, sizeof(uint8));
 }
 
 VQADecoder::VQAAudioTrack::~VQAAudioTrack() {
@@ -928,13 +935,18 @@ VQADecoder::VQAAudioTrack::~VQAAudioTrack() {
 
 Audio::SeekableAudioStream *VQADecoder::VQAAudioTrack::decodeAudioFrame() {
 	int16 *audioFrame = (int16 *)malloc(4 * 735);
-	memset(audioFrame, 0, 4 * 735);
+	if (audioFrame != nullptr) {
+		memset(audioFrame, 0, 4 * 735);
 
-	_adpcmDecoder.decode(_compressedAudioFrame, 735, audioFrame, true);
+		_adpcmDecoder.decode(_compressedAudioFrame, 735, audioFrame, true);
 
-	uint flags = Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
+		uint flags = Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
 
-	return Audio::makeRawStream((byte *)audioFrame, 4 * 735, _frequency, flags, DisposeAfterUse::YES);
+		return Audio::makeRawStream((byte *)audioFrame, 4 * 735, _frequency, flags, DisposeAfterUse::YES);
+	} else {
+		warning("VQADecoder::VQAAudioTrack::decodeAudioFrame: Insufficient memory to allocate for audio frame");
+		return nullptr;
+	}
 }
 
 bool VQADecoder::VQAAudioTrack::readSND2(Common::SeekableReadStream *s, uint32 size) {
diff --git a/engines/bladerunner/vqa_player.cpp b/engines/bladerunner/vqa_player.cpp
index dd96cd9bdd..e766fcb3ac 100644
--- a/engines/bladerunner/vqa_player.cpp
+++ b/engines/bladerunner/vqa_player.cpp
@@ -67,13 +67,14 @@ bool VQAPlayer::open() {
 	_hasAudio = _decoder.hasAudio();
 	if (_hasAudio) {
 		_audioStream = Audio::makeQueuingAudioStream(_decoder.frequency(), false);
+		_lastAudioFrameSuccessfullyQueued = 1;
 	}
 
 	_repeatsCount = 0;
 	_loop = -1;
 	_frame = -1;
 	_frameBegin = -1;
-	_frameEnd = _decoder.numFrames() - 1;
+	_frameEnd = getFrameCount() - 1;
 	_frameEndQueued = -1;
 	_repeatsCountQueued = -1;
 
@@ -132,41 +133,75 @@ int VQAPlayer::update(bool forceDraw, bool advanceFrame, bool useTime, Graphics:
 		result = -3;
 		// _repeatsCount == 0, so return here at the end of the video, to release the resource
 		return result;
-	} else if (useTime && (now < _frameNextTime)) {
+	} else if (useTime && (now - (_frameNextTime - kVqaFrameTimeDiff) < kVqaFrameTimeDiff)) {
+		// Not yet time to move to next frame.
+		// Note, we use unsigned difference to avoid potential time overflow issues
 		result = -1;
 	} else if (advanceFrame) {
 		_frame = _frameNext;
 		_decoder.readFrame(_frameNext, kVQAReadVideo);
 		_decoder.decodeVideoFrame(customSurface != nullptr ? customSurface : _surface, _frameNext);
 
+		int maxAllowedAudioPreloadedFrames = kMaxAudioPreloadedFrames;
+		if (_frameEnd - _frameNext < kMaxAudioPreloadedFrames - 1) {
+			maxAllowedAudioPreloadedFrames = _frameEnd - _frameNext + 1;
+		}
+
 		if (_hasAudio) {
-			int audioPreloadFrames = 14;
 			if (!_audioStarted) {
-				for (int i = 0; i < audioPreloadFrames; ++i) {
+				// start with preloading up to (kMaxAudioPreloadedFrames - 1) frames at most, before reaching the _frameEnd frame
+				for (int i = 0; i < kMaxAudioPreloadedFrames - 1; ++i) {
 					if (_frameNext + i < _frameEnd) {
 						_decoder.readFrame(_frameNext + i, kVQAReadAudio);
 						queueAudioFrame(_decoder.decodeAudioFrame());
+						_lastAudioFrameSuccessfullyQueued = _frameNext + i;
 					}
 				}
 				if (_vm->_mixer->isReady()) {
 					// Use speech sound type as in original engine
+					// Audio stream starts playing, consuming queued "audio frames"
+					// Note: On its own, the audio will not re-synch with video;
+					// It plays independently so it can get ahead!
 					_vm->_mixer->playStream(Audio::Mixer::kSpeechSoundType, &_soundHandle, _audioStream);
 				}
 				_audioStarted = true;
 			}
-			if (_frameNext + audioPreloadFrames < _frameEnd) {
-				_decoder.readFrame(_frameNext + audioPreloadFrames, kVQAReadAudio);
-				queueAudioFrame(_decoder.decodeAudioFrame());
+
+			// Due to our audio stream being queuable, the queued audio frames will play,
+			// even if the game is "paused" eg. by moving the ScummVM window.
+			// However, the video will stop playing immediately in that case.
+			// That would result in a audio video desynch, with audio being ahead of video.
+			// When the video resumes, we need to catch up to the audio "frame" of the queue that was last played,
+			// without queuing more audio, and then start queuing audio again.
+
+			// The following still covers the case of adding the final 15th audio frame to the queue
+			// when first starting the audio stream.
+			int tmpCurrentQueuedAudioFrames = getQueuedAudioFrames();
+			if (_lastAudioFrameSuccessfullyQueued != _frameEnd) {
+				// if video is behind audio, then resynch,
+				// which here means: don't queue and don't play audio until video catches up.
+			    if (_lastAudioFrameSuccessfullyQueued - tmpCurrentQueuedAudioFrames < _frameNext) {
+					int addToQueueRep = 0;
+					while (addToQueueRep < (maxAllowedAudioPreloadedFrames - tmpCurrentQueuedAudioFrames)
+					       && _lastAudioFrameSuccessfullyQueued + 1 <= _frameEnd) {
+						_decoder.readFrame(_lastAudioFrameSuccessfullyQueued + 1, kVQAReadAudio);
+						queueAudioFrame(_decoder.decodeAudioFrame());
+						++_lastAudioFrameSuccessfullyQueued;
+						++addToQueueRep;
+					}
+				}
 			}
 		}
+
 		if (useTime) {
-			_frameNextTime += 60000 / 15;
+			_frameNextTime += kVqaFrameTimeDiff;
 
 			// In some cases (as overlay paused by kia or game window is moved) new time might be still in the past.
 			// This can cause rapid playback of video where every refresh renders different frame of the video.
 			// Can be avoided by setting next time to the future.
-			if (_frameNextTime < now) {
-				_frameNextTime = now + 60000 / 15;
+			// Note, we use unsigned difference to avoid time overflow issues
+			if (now - (_frameNextTime - kVqaFrameTimeDiff) > kVqaFrameTimeDiff) {
+				_frameNextTime = now + kVqaFrameTimeDiff;
 			}
 		}
 		++_frameNext;
@@ -279,14 +314,26 @@ int VQAPlayer::getLoopEndFrame(int loop) {
 	return end;
 }
 
-int VQAPlayer::getFrameCount() {
+int VQAPlayer::getFrameCount() const {
 	return _decoder.numFrames();
 }
 
+int VQAPlayer::getQueuedAudioFrames() const {
+	return _audioStream->numQueuedStreams();
+}
+
+// Adds another audio "frame" to the queue of the audio stream
 void VQAPlayer::queueAudioFrame(Audio::AudioStream *audioStream) {
+	if (audioStream == nullptr) {
+		return;
+	}
+
 	int n = _audioStream->numQueuedStreams();
-	if (n == 0)
+	// TODO Maybe remove this warning or make it a debug-only message?
+	if (n == 0) {
 		warning("numQueuedStreams: %d", n);
+	}
+
 	_audioStream->queueAudioStream(audioStream, DisposeAfterUse::YES);
 }
 
diff --git a/engines/bladerunner/vqa_player.h b/engines/bladerunner/vqa_player.h
index c11442da82..c3e1e99fd1 100644
--- a/engines/bladerunner/vqa_player.h
+++ b/engines/bladerunner/vqa_player.h
@@ -53,6 +53,9 @@ class VQAPlayer {
 	Audio::QueuingAudioStream   *_audioStream;
 	Graphics::Surface           *_surface;
 
+	static const uint32  kVqaFrameTimeDiff        = 4000; // 60 * 1000 / 15
+	static const int     kMaxAudioPreloadedFrames = 15;
+
 	int _frame;
 	int _frameNext;
 	int _frameBegin;
@@ -63,6 +66,8 @@ class VQAPlayer {
 	int _repeatsCountQueued;
 	int _frameEndQueued;
 
+	int _lastAudioFrameSuccessfullyQueued;
+
 	int _loopInitial;
 	int _repeatsCountInitial;
 
@@ -91,6 +96,7 @@ public:
 		  _repeatsCount(-1),
 		  _repeatsCountQueued(-1),
 		  _frameEndQueued(-1),
+		  _lastAudioFrameSuccessfullyQueued(-1),
 		  _loopInitial(-1),
 		  _repeatsCountInitial(-1),
 		  _frameNextTime(0),
@@ -120,7 +126,9 @@ public:
 	int getLoopBeginFrame(int loop);
 	int getLoopEndFrame(int loop);
 
-	int getFrameCount();
+	int getFrameCount() const;
+
+	int getQueuedAudioFrames() const;
 
 private:
 	void queueAudioFrame(Audio::AudioStream *audioStream);




More information about the Scummvm-git-logs mailing list