scummvm/engines/sci/video/robot_decoder.h
2020-02-09 12:43:16 +01:00

1373 lines
36 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#ifndef SCI_SOUND_DECODERS_ROBOT_H
#define SCI_SOUND_DECODERS_ROBOT_H
#include "audio/audiostream.h" // for AudioStream
#include "audio/rate.h" // for st_sample_t
#include "common/array.h" // for Array
#include "common/mutex.h" // for StackLock, Mutex
#include "common/rect.h" // for Point, Rect (ptr only)
#include "common/scummsys.h" // for int16, int32, byte, uint16
#include "sci/engine/vm_types.h" // for NULL_REG, reg_t
#include "sci/graphics/helpers.h" // for GuiResourceId
#include "sci/graphics/screen_item32.h" // for ScaleInfo, ScreenItem (ptr o...
namespace Common { class SeekableSubReadStreamEndian; }
namespace Sci {
class Plane;
class SegManager;
// There were 3 different Robot video versions, used in the following games:
// - v4: PQ:SWAT demo
// - v5: KQ7 DOS, Phantasmagoria, PQ:SWAT, Lighthouse
// - v6: RAMA
//
// Notes on Robot v5/v6 format:
//
// Robot is a packetized streaming AV format that encodes multiple bitmaps +
// positioning data, plus synchronised audio, for rendering in the SCI graphics
// system.
//
// Unlike traditional AV formats, Robot videos almost always require playback
// within the game engine because certain information (like the resolution of
// the Robot coordinates and the background for the video) is dependent on data
// that does not exist within the Robot file itself. In version 6, robots could
// also participate in palette remapping by drawing remap pixels, and the
// information for processing these pixels is also not stored within the Robot
// file.
//
// The Robot container consists of a file header, an optional primer audio
// section, an optional colour palette, a frame seek index, a set of cuepoints,
// and variable-sized packets of compressed video+audio data.
//
// Integers in Robot files are coded using native endianness (LSB for x86
// versions, MSB for 68k/PPC versions).
//
// Robot video coding is a relatively simple variable-length compression with no
// interframe compression. Each cel in a frame is constructed from multiple
// contiguous data blocks, each of which can be independently compressed with
// LZS or left uncompressed. An entire cel can also be line decimated, where
// lines are deleted from the source bitmap at compression time and are
// reconstructed by decompression using line interpolation. Each cel also
// includes coordinates where it should be placed within the video frame,
// relative to the top-left corner of the frame.
//
// Audio coding is fixed-length, and all audio blocks except for the primer
// audio are the same size. Audio is encoded with Sierra SOL DPCM16 compression,
// and is split into two channels ('even' and 'odd'), each at a 11025Hz sample
// rate. The original signal is restored by interleaving samples from the two
// channels together. Channel packets are 'even' if they have an ''absolute
// position of audio'' that is evenly divisible by 2; otherwise, they are 'odd'.
// Because the channels use DPCM compression, there is an 8-byte runway at the
// start of every audio block that is never written to the output stream, which
// is used to move the signal to the correct location by the 9th sample.
//
// File header (v5/v6):
//
// byte | description
// 0 | signature 0x16
// 1 | unused
// 2-5 | signature 'SOL\0'
// 6-7 | version (4, 5, and 6 are the only known versions)
// 8-9 | size of audio blocks
// 10-11 | primer is compressed flag
// 12-13 | unused
// 14-15 | total number of video frames
// 16-17 | embedded palette size, in bytes
// 18-19 | primer reserved size
// 20-21 | coordinate X-resolution (if 0, uses game coordinates)
// 22-23 | coordinate Y-resolution (if 0, uses game coordinates)
// 24 | if non-zero, Robot includes a palette
// 25 | if non-zero, Robot includes audio
// 26-27 | unused
// 28-29 | the frame rate, in frames per second
// 30-31 | coordinate conversion flag; if true, screen item coordinates
// | from the robot should be used as-is with NO conversion when
// | explicitly displaying a specific frame
// 32-33 | the maximum number of packets that can be skipped without causing
// | audio drop-out
// 34-35 | the maximum possible number of cels that will be displayed in any
// | frame of the robot
// 36-39 | the maximum possible size, in bytes, of the first fixed cel
// 40-43 | the maximum possible size, in bytes, of the second fixed cel
// 44-47 | the maximum possible size, in bytes, of the third fixed cel
// 48-51 | the maximum possible size, in bytes, of the fourth fixed cel
// 52-59 | unused
//
// If the ''file includes audio'' flag is false, seek ''primer reserved size''
// bytes from the end of the file header to get past a padding zone.
//
// If the ''file includes audio'' flag is true, and the ''primer reserved size''
// is not zero, the data immediately after the file header consists of an audio
// primer header plus compressed audio data:
//
// Audio primer header:
//
// byte | description
// 0-3 | the size, in bytes, of the entire primer audio section
// 4-5 | the compression format of the primer audio (must be zero)
// 6-9 | the size, in bytes, of the "even" primer
// 10-13 | the size, in bytes, of the "odd" primer
//
// If the combined sizes of the even and odd primers do not match the ''primer
// reserved size'', the next header block can be found ''primer reserved size''
// bytes from the *start* of the audio primer header.
//
// Otherwise, if the Robot has audio, and the ''primer reserved size'' is zero,
// and the ''primer is compressed flag'' is set, the "even" primer size is
// 19922, the "odd" primer size is 21024, and the "even" and "odd" buffers
// should be zero-filled.
//
// Any other combination of these flags is an error.
//
// If the Robot has a palette, the next ''palette size'' bytes should be read
// as a SCI HunkPalette. Otherwise, seek ''palette size'' bytes from the current
// position to get to the frame index.
//
// The next section of the Robot is the video frame size index. In version 5
// robots, read ''total number of frames'' 16-bit integers to get the size of
// the compressed video for each frame. For version 6 robots, use 32-bit
// integers.
//
// The next section of the Robot is the packet size index (combined compressed
// size of video + audio for each frame). In version 5 Robots, read ''total
// number of frames'' 16-bit integers. In version 6 robots, use 32-bit integers.
//
// The next section of the Robot is the cue times index. Read 256 32-bit
// integers, which represent the number of ticks from the start of playback that
// the given cue point falls on.
//
// The next section of the Robot is the cue values index. Read 256 16-bit
// integers, which represent the actual cue values that will be passed back to
// the game engine when a cue is requested.
//
// Finally, to get to the first frame packet, seek from the current position to
// the start of the next 2048-byte-aligned sector.
//
// Frame packet:
//
// byte | description
// 0..n | video data (size is in the ''video frame size index'')
// n+1.. | optional audio data (size is ''size of audio blocks'')
//
// Video data:
//
// byte | description
// 0-2 | number of cels in the frame (max 10)
// 3..n | cels
//
// Cel:
//
// 0-17 | cel header
// 18..n | data chunks
//
// Cel header:
//
// byte | description
// 0 | unused
// 1 | vertical scale factor, in percent decimation (100 = no decimation,
// | 50 = 50% of lines were removed)
// 2-3 | cel width
// 4-5 | cel height
// 6-9 | unused
// 10-11 | cel x-position, in Robot coordinates
// 12-13 | cel y-position, in Robot coordinates
// 14-15 | cel total data chunk size, in bytes
// 16-17 | number of data chunks
//
// Cel data chunk:
//
// 0-9 | cel data chunk header
// 10..n | cel data
//
// Cel data chunk header:
//
// byte | description
// 0-3 | compressed size
// 4-7 | decompressed size
// 8-9 | compression type (0 = LZS, 2 = uncompressed)
//
// Random frame seeking can be done by calculating the address of the frame
// packet by adding up the ''packet size index'' entries up to the current
// frame. This will normally disable audio playback, as audio data in a packet
// does not correspond to the video in the same packet.
//
// Audio data is placed immediately after the end of the video data in a packet,
// and consists of an audio header plus compressed audio data:
//
// Audio data:
//
// byte | description
// 0-7 | audio data header
// 8-15 | DPCM runway
// 16..n | compressed audio data
//
// Audio data header:
//
// byte | description
// 0-3 | absolute position of audio in the audio stream
// 4-7 | the size of the audio block, excluding the header
//
// When a block of audio is processed, first check to ensure that the
// decompressed audio block's `position * 2 + length * 4` runs past the end of
// the last packet of the same evenness/oddness. Discard the audio block
// entirely if data has already been written past the end of this block for this
// channel, or if the read head has already read past the end of this audio
// block.
//
// If the block is not discarded, apply DPCM decompression to the entire block,
// starting from beginning of the DPCM runway, using an initial sample value of
// 0. Then, copy every sample from the decompressed source outside of the DPCM
// runway into every *other* sample of the final audio buffer (1 -> 2, 2 -> 4,
// 3 -> 6, etc.).
//
// Finally, for any skipped samples where the opposing (even/odd) channel did
// not yet write, interpolate the skipped areas by adding together the
// neighbouring samples from this audio block and dividing by two. (This allows
// the audio quality to degrade to 11kHz in case it takes too long to decode all
// the frames in the stream). Interpolated samples must not be written on top of
// true data from the opposing channel. Audio from later packets must also not
// be written on top of data in the same channel that was already written by an
// earlier packet, in particular because the first 8 bytes of the next packet
// are garbage data used to move the waveform to the correct position (due to
// the use of DPCM compression).
#pragma mark -
#pragma mark RobotAudioStream
/**
* A Robot audio stream is a simple loop buffer that accepts audio blocks from
* the Robot engine.
*/
class RobotAudioStream : public Audio::AudioStream {
public:
enum {
/**
* The sample rate used for all robot audio.
*/
kRobotSampleRate = 22050,
/**
* Multiplier for the size of a packet that is being expanded by writing
* to every other byte of the target buffer.
*/
kEOSExpansion = 2
};
/**
* Playback state information. Used for framerate calculation.
*/
struct StreamState {
/**
* The current position of the read head of the audio stream.
*/
int bytesPlaying;
/**
* The sample rate of the audio stream. Always 22050.
*/
uint16 rate;
/**
* The bit depth of the audio stream. Always 16.
*/
uint8 bits;
};
/**
* A single packet of compressed audio from a Robot data stream.
*/
struct RobotAudioPacket {
/**
* Raw DPCM-compressed audio data.
*/
byte *data;
/**
* The size of the compressed audio data,
* in bytes.
*/
int dataSize;
/**
* The uncompressed, file-relative position of this audio packet.
*/
int position;
RobotAudioPacket(byte *data_, const int dataSize_, const int position_) :
data(data_), dataSize(dataSize_), position(position_) {}
};
RobotAudioStream(const int32 bufferSize);
~RobotAudioStream() override;
/**
* Adds a new audio packet to the stream.
* @returns `true` if the audio packet was fully consumed, otherwise
* `false`.
*/
bool addPacket(const RobotAudioPacket &packet);
/**
* Prevents any additional audio packets from being added to the audio
* stream.
*/
void finish();
/**
* Returns the current status of the audio stream.
*/
StreamState getStatus() const;
private:
Common::Mutex _mutex;
/**
* Loop buffer for playback. Contains decompressed 16-bit PCM samples.
*/
byte *_loopBuffer;
/**
* The size of the loop buffer, in bytes.
*/
int32 _loopBufferSize;
/**
* The position of the read head within the loop buffer, in bytes.
*/
int32 _readHead;
/**
* The lowest file position that can be buffered, in uncompressed bytes.
*/
int32 _readHeadAbs;
/**
* The highest file position that can be buffered, in uncompressed bytes.
*/
int32 _maxWriteAbs;
/**
* The highest file position, in uncompressed bytes, that has been written
* to the stream. This is different from `_maxWriteAbs`, which is the
* highest uncompressed position which *can* be written right now.
*/
int32 _writeHeadAbs;
/**
* The highest file position, in uncompressed bytes, that has been written
* to the even & odd sides of the stream.
*
* Index 0 corresponds to the 'even' side; index 1 corresponds to the 'odd'
* side.
*/
int32 _jointMin[2];
/**
* When `true`, the stream is waiting for all primer blocks to be received
* before allowing playback to begin.
*/
bool _waiting;
/**
* When `true`, the stream will accept no more audio blocks.
*/
bool _finished;
/**
* The uncompressed position of the first packet of robot data. Used to
* decide whether all primer blocks have been received and the stream should
* be started.
*/
int32 _firstPacketPosition;
/**
* Decompression buffer, used to temporarily store an uncompressed block of
* audio data.
*/
byte *_decompressionBuffer;
/**
* The size of the decompression buffer, in bytes.
*/
int32 _decompressionBufferSize;
/**
* The position of the packet currently in the decompression buffer. Used to
* avoid re-decompressing audio data that has already been decompressed
* during a partial packet read.
*/
int32 _decompressionBufferPosition;
/**
* Calculates the absolute ranges for new fills into the loop buffer.
*/
void fillRobotBuffer(const RobotAudioPacket &packet, const int8 bufferIndex);
/**
* Interpolates `numSamples` samples from the read head, if no true samples
* were written for one (or both) of the joint channels.
*/
void interpolateMissingSamples(const int32 numSamples);
#pragma mark -
#pragma mark RobotAudioStream - AudioStream implementation
public:
int readBuffer(Audio::st_sample_t *outBuffer, int numSamples) override;
bool isStereo() const override { return false; };
int getRate() const override { return 22050; };
bool endOfData() const override {
Common::StackLock lock(_mutex);
return _readHeadAbs >= _writeHeadAbs;
};
bool endOfStream() const override {
Common::StackLock lock(_mutex);
return _finished && endOfData();
}
};
#pragma mark -
#pragma mark RobotDecoder
/**
* RobotDecoder implements the logic required for Robot animations.
*/
class RobotDecoder {
public:
RobotDecoder(SegManager *segMan);
~RobotDecoder();
GuiResourceId getResourceId() const {
return _robotId;
}
private:
SegManager *_segMan;
/**
* The ID of the currently loaded robot.
*/
GuiResourceId _robotId;
#pragma mark Constants
public:
/**
* The playback status of the robot.
*/
enum RobotStatus {
kRobotStatusUninitialized = 0,
kRobotStatusPlaying = 1,
kRobotStatusEnd = 2,
kRobotStatusPaused = 3
};
enum {
// Special high value used to represent parameters that should be left
// unchanged when calling `showFrame`
kUnspecified = 50000
};
private:
enum {
/**
* Maximum number of on-screen screen items.
*/
kScreenItemListSize = 10,
/**
* Maximum number of queued audio blocks.
*/
kAudioListSize = 10,
/**
* Maximum number of samples used for frame timing.
*/
kDelayListSize = 10,
/**
* Maximum number of cues.
*/
kCueListSize = 256,
/**
* Maximum number of 'fixed' cels that never change for the duration of
* a robot.
*/
kFixedCelListSize = 4,
/**
* The size of a hunk palette in the Robot stream.
*/
kRawPaletteSize = 1200,
/**
* The size of a frame of Robot data. This value was used to align the
* first block of data after the main Robot header to the next CD
* sector.
*/
kRobotFrameSize = 2048,
/**
* The size of a block of zero-compressed audio. Used to fill audio when
* the size of an audio packet does not match the expected packet size.
*/
kRobotZeroCompressSize = 2048,
/**
* The size of the audio block header, in bytes. The audio block header
* consists of the compressed size of the audio in the record, plus the
* position of the audio in the compressed data stream.
*/
kAudioBlockHeaderSize = 8,
/**
* The size of a Robot cel header, in bytes.
*/
kCelHeaderSize = 22,
/**
* The maximum amount that the frame rate is allowed to drift from the
* nominal frame rate in order to correct for AV drift or slow playback.
*/
kMaxFrameRateDrift = 1
};
/**
* The version number for the currently loaded robot.
*
* There are several known versions of robot:
*
* v2: before Nov 1994; no known examples
* v3: before Nov 1994; no known examples
* v4: Jan 1995; KQ7 1.65, PQ:SWAT demo
* v5: Mar 1995; SCI2.1 and SCI3 games
* v6: SCI3 games
*/
uint16 _version;
#pragma mark -
#pragma mark Initialisation
private:
/**
* Sets up the read stream for the robot.
*/
void initStream(const GuiResourceId robotId);
/**
* Sets up the initial values for playback control.
*/
void initPlayback();
/**
* Sets up the initial values for audio decoding.
*/
void initAudio();
/**
* Sets up the initial values for video rendering.
*/
void initVideo(const int16 x, const int16 y, const int16 scale, const reg_t plane, const bool hasPalette, const uint16 paletteSize);
/**
* Sets up the robot's data record and cue positions.
*/
void initRecordAndCuePositions();
#pragma mark -
#pragma mark Playback
public:
/**
* Opens a robot file for playback. Newly opened robots are paused by
* default.
*/
void open(const GuiResourceId robotId, const reg_t plane, const int16 priority, const int16 x, const int16 y, const int16 scale);
/**
* Closes the currently open robot file.
*/
void close();
/**
* Pauses the robot. Once paused, the audio for a robot is disabled until
* the end of playback.
*/
void pause();
/**
* Resumes a paused robot.
*/
void resume();
/**
* Moves robot to the specified frame and pauses playback.
*
* @note Called DisplayFrame in SSCI.
*/
void showFrame(const uint16 frameNo, const uint16 newX, const uint16 newY, const uint16 newPriority);
/**
* Retrieves the value associated with the current cue point.
*/
int16 getCue() const;
/**
* Gets the currently displayed frame.
*/
int16 getFrameNo() const;
/**
* Gets the playback status of the player.
*/
RobotStatus getStatus() const;
private:
/**
* The read stream containing raw robot data.
*/
Common::SeekableSubReadStreamEndian *_stream;
/**
* The current status of the player.
*/
RobotStatus _status;
typedef Common::Array<int> PositionList;
/**
* A map of frame numbers to byte offsets within `_stream`.
*/
PositionList _recordPositions;
/**
* The offset of the Robot file within a resource bundle.
*/
int32 _fileOffset;
/**
* A list of cue times that is updated to prevent earlier cue values from
* being given to the game more than once.
*/
mutable int32 _cueTimes[kCueListSize];
/**
* The original list of cue times from the raw Robot data.
*/
int32 _masterCueTimes[kCueListSize];
/**
* The list of values to provide to a game when a cue value is requested.
*/
int32 _cueValues[kCueListSize];
/**
* The current playback frame rate.
*/
int16 _frameRate;
/**
* The nominal playback frame rate.
*/
int16 _normalFrameRate;
/**
* The minimal playback frame rate. Used to correct for AV sync drift when
* the video is more than one frame ahead of the audio.
*/
int16 _minFrameRate;
/**
* The maximum playback frame rate. Used to correct for AV sync drift when
* the video is more than one frame behind the audio.
*/
int16 _maxFrameRate;
/**
* The maximum number of record blocks that can be skipped without causing
* audio to drop out.
*/
int16 _maxSkippablePackets;
/**
* The currently displayed frame number.
*/
int _currentFrameNo;
/**
* The last displayed frame number.
*/
int _previousFrameNo;
/**
* The time, in ticks, when the robot was last started or resumed.
*/
int32 _startTime;
/**
* The first frame displayed when the robot was resumed.
*/
int32 _startFrameNo;
/**
* The last frame displayed when the robot was resumed.
*/
int32 _startingFrameNo;
/**
* Seeks the raw data stream to the record for the given frame number.
*/
bool seekToFrame(const int frameNo);
/**
* Sets the start time and frame of the robot when the robot is started or
* resumed.
*/
void setRobotTime(const int frameNo);
#pragma mark -
#pragma mark Timing
private:
/**
* This class tracks the amount of time it takes for a frame of robot
* animation to be rendered. This information is used by the player to
* speculatively skip rendering of future frames to keep the animation in
* sync with the robot audio.
*/
class DelayTime {
public:
DelayTime(RobotDecoder *decoder);
/**
* Starts performance timing.
*/
void startTiming();
/**
* Ends performance timing.
*/
void endTiming();
/**
* Returns whether or not timing is currently in progress.
*/
bool timingInProgress() const;
/**
* Returns the median time, in ticks, of the currently stored timing
* samples.
*/
int predictedTicks() const;
private:
RobotDecoder *_decoder;
/**
* The start time, in ticks, of the current timing loop. If no loop is
* in progress, the value is 0.
*
* @note This is slightly different than SSCI where the not-timing value
* was -1.
*/
uint32 _startTime;
/**
* A sorted list containing the timing data for the last
* `kDelayListSize` frames, in ticks.
*/
int _delays[kDelayListSize];
/**
* A list of monotonically increasing identifiers used to identify and
* replace the oldest sample in the `_delays` array when finishing the
* next timing operation.
*/
uint _timestamps[kDelayListSize];
/**
* The identifier of the oldest timing.
*/
uint _oldestTimestamp;
/**
* The identifier of the newest timing.
*/
uint _newestTimestamp;
/**
* Sorts the list of timings.
*/
void sortList();
};
/**
* Calculates the next frame number that needs to be rendered, using the
* timing data collected by DelayTime.
*/
uint16 calculateNextFrameNo(const uint32 extraTicks = 0) const;
/**
* Calculates and returns the number of frames that should be rendered in
* `ticks` time, according to the current target frame rate of the robot.
*/
uint32 ticksToFrames(const uint32 ticks) const;
/**
* Gets the current game time, in ticks.
*/
uint32 getTickCount() const;
/**
* The performance timer for the robot.
*/
DelayTime _delayTime;
#pragma mark -
#pragma mark Audio
private:
enum {
/**
* The number of ticks that should elapse between each AV sync check.
*/
kAudioSyncCheckInterval = 5 * 60 /* 5 seconds */
};
/**
* The status of the audio track of a Robot animation.
*/
enum RobotAudioStatus {
kRobotAudioReady = 1,
kRobotAudioStopped = 2,
kRobotAudioPlaying = 3,
kRobotAudioPaused = 4,
kRobotAudioStopping = 5
};
#pragma mark -
#pragma mark Audio - AudioList
private:
/**
* This class manages packetized audio playback for robots.
*/
class AudioList {
public:
AudioList();
/**
* Starts playback of robot audio.
*/
void startAudioNow();
/**
* Stops playback of robot audio, allowing any queued audio to finish
* playing back.
*/
void stopAudio();
/**
* Stops playback of robot audio immediately.
*/
void stopAudioNow();
/**
* Submits as many blocks of audio as possible to the audio engine.
*/
void submitDriverMax();
/**
* Adds a new AudioBlock to the queue.
*
* @param position The absolute position of the audio for the block, in
* compressed bytes.
* @param size The size of the buffer.
* @param buffer A pointer to compressed audio data that will be copied
* into the new AudioBlock.
*/
void addBlock(const int position, const int size, const byte *buffer);
/**
* Immediately stops any active playback and purges all audio data in
* the audio list.
*/
void reset();
/**
* Pauses the robot audio channel in preparation for the first block of
* audio data to be read.
*/
void prepareForPrimer();
/**
* Sets the audio offset which is used to offset the position of audio
* packets sent to the audio stream.
*/
void setAudioOffset(const int offset);
#pragma mark -
#pragma mark Audio - AudioList - AudioBlock
private:
/**
* AudioBlock represents a block of audio from the Robot's audio track.
*/
class AudioBlock {
public:
AudioBlock(const int position, const int size, const byte *const data);
~AudioBlock();
/**
* Submits the block of audio to the audio manager.
* @returns true if the block was fully read, or false if the block
* was not read or only partially read.
*/
bool submit(const int startOffset);
private:
/**
* The absolute position, in compressed bytes, of this audio block's
* audio data in the audio stream.
*/
int _position;
/**
* The compressed size, in bytes, of this audio block's audio data.
*/
int _size;
/**
* A buffer containing raw SOL-compressed audio data.
*/
byte *_data;
};
/**
* The list of compressed audio blocks submitted for playback.
*/
AudioBlock *_blocks[kAudioListSize];
/**
* The number of blocks in `_blocks` that are ready to be submitted.
*/
uint8 _blocksSize;
/**
* The index of the oldest submitted audio block.
*/
uint8 _oldestBlockIndex;
/**
* The index of the newest submitted audio block.
*/
uint8 _newestBlockIndex;
/**
* The offset used when sending packets to the audio stream.
*/
int _startOffset;
/**
* The status of robot audio playback.
*/
RobotAudioStatus _status;
/**
* Frees all audio blocks in the `_blocks` list.
*/
void freeAudioBlocks();
};
/**
* Whether or not this robot animation has
* an audio track.
*/
bool _hasAudio;
/**
* The audio list for the current robot.
*/
AudioList _audioList;
/**
* The size, in bytes, of a block of audio data, excluding the audio block
* header.
*/
uint16 _audioBlockSize;
/**
* The expected size of a block of audio data, in bytes, excluding the audio
* block header.
*/
int16 _expectedAudioBlockSize;
/**
* The number of compressed audio bytes that are needed per frame to fill
* the audio buffer without causing audio to drop out.
*/
int16 _audioRecordInterval;
/**
* If true, primer audio buffers should be filled with silence instead of
* trying to read buffers from the Robot data.
*/
uint16 _primerZeroCompressFlag;
/**
* The size, in bytes, of the primer audio in the Robot, including any extra
* alignment padding.
*/
uint16 _primerReservedSize;
/**
* The combined size, in bytes, of the even and odd primer channels.
*/
int32 _totalPrimerSize;
/**
* The absolute offset of the primer audio data in the robot data stream.
*/
int32 _primerPosition;
/**
* The size, in bytes, of the even primer.
*/
int32 _evenPrimerSize;
/**
* The size, in bytes, of the odd primer.
*/
int32 _oddPrimerSize;
/**
* The absolute position in the audio stream of the first audio packet.
*/
int32 _firstAudioRecordPosition;
/**
* A temporary buffer used to hold one frame of raw (DPCM-compressed) audio
* when reading audio records from the robot stream.
*/
byte *_audioBuffer;
/**
* The next tick count when AV sync should be checked and framerate
* adjustments made, if necessary.
*/
uint32 _checkAudioSyncTime;
/**
* Primes the audio buffer with the first frame of audio data.
*
* @note `primeAudio` was `InitAudio` in SSCI
*/
bool primeAudio(const uint32 startTick);
/**
* Reads primer data from the robot data stream and puts it into the given
* buffers.
*/
bool readPrimerData(byte *outEvenBuffer, byte *outOddBuffer);
/**
* Reads audio data for the given frame number into the given buffer.
*
* @param outAudioPosition The position of the audio, in compressed bytes,
* in the data stream.
* @param outAudioSize The size of the audio data, in compressed bytes.
*/
bool readAudioDataFromRecord(const int frameNo, byte *outBuffer, int &outAudioPosition, int &outAudioSize);
/**
* Submits part of the audio packet of the given frame to the audio list,
* starting `startPosition` bytes into the audio.
*/
bool readPartialAudioRecordAndSubmit(const int startFrame, const int startPosition);
#pragma mark -
#pragma mark Rendering
public:
/**
* Gets the plane used to render the robot.
*/
const reg_t getPlaneId() const {
return _planeId;
}
/**
* Gets the origin of the robot.
*/
Common::Point getPosition() const {
return _position;
}
/**
* Gets the scale of the robot.
*/
int16 getScale() const {
return _scaleInfo.x;
}
/**
* Puts the current dimensions of the robot, in game script coordinates,
* into the given rect, and returns the total number of frames in the robot
* animation.
*/
uint16 getFrameSize(Common::Rect &outRect) const;
/**
* Pumps the robot player for the next frame of video. This is the main
* rendering function.
*/
void doRobot();
/**
* Submits any outstanding audio blocks that should be added to the queue
* before the robot frame becomes visible.
*/
void frameAlmostVisible();
/**
* Evaluates frame drift and makes modifications to the player in order to
* ensure that future frames will arrive on time.
*/
void frameNowVisible();
/**
* Scales a vertically compressed cel to its original uncompressed
* dimensions.
*/
void expandCel(byte *target, const byte* source, const int16 celWidth, const int16 celHeight) const;
int16 getPriority() const;
/**
* Sets the visual priority of the robot.
* @see Plane::_priority
*/
void setPriority(const int16 newPriority);
private:
enum CompressionType {
kCompressionLZS = 0,
kCompressionNone = 2
};
/**
* Describes the state of a Robot video cel.
*/
struct CelHandleInfo {
/**
* The persistence level of Robot cels.
*/
enum CelHandleLifetime {
kNoCel = 0,
kFrameLifetime = 1,
kRobotLifetime = 2
};
/**
* A reg_t pointer to an in-memory bitmap containing the cel.
*/
reg_t bitmapId;
/**
* The lifetime of the cel, either just for this frame or for the entire
* duration of the robot playback.
*/
CelHandleLifetime status;
/**
* The size, in pixels, of the decompressed cel.
*/
int area;
CelHandleInfo() : bitmapId(NULL_REG), status(kNoCel), area(0) {}
};
typedef Common::Array<ScreenItem *> RobotScreenItemList;
typedef Common::Array<CelHandleInfo> CelHandleList;
typedef Common::Array<int> VideoSizeList;
typedef Common::Array<uint> MaxCelAreaList;
typedef Common::Array<reg_t> FixedCelsList;
typedef Common::Array<Common::Point> CelPositionsList;
typedef Common::Array<byte> ScratchMemory;
/**
* Renders a version 5/6 robot frame.
*/
void doVersion5(const bool shouldSubmitAudio = true);
/**
* Creates screen items for a version 5/6 robot.
*/
void createCels5(const byte *rawVideoData, const int16 numCels, const bool usePalette);
/**
* Creates a single screen item for a cel in a version 5/6 robot.
*
* Returns the size, in bytes, of the raw cel data.
*/
uint32 createCel5(const byte *rawVideoData, const int16 screenItemIndex, const bool usePalette);
/**
* Preallocates memory for the next `numCels` cels in the robot data stream.
*/
void preallocateCelMemory(const byte *rawVideoData, const int16 numCels);
/**
* The decompressor for LZS-compressed cels.
*/
DecompressorLZS _decompressor;
/**
* The ID of the robot plane.
*/
reg_t _planeId;
/**
* The origin of the robot animation, in screen coordinates.
*/
Common::Point _position;
/**
* Global scaling applied to the robot.
*/
ScaleInfo _scaleInfo;
/**
* The native resolution of the robot.
*/
int16 _xResolution, _yResolution;
/**
* Whether or not the coordinates read from robot data are high resolution.
*/
bool _isHiRes;
/**
* The maximum number of cels that will be rendered on any given frame in
* this robot. Used for preallocation of cel memory.
*/
int16 _maxCelsPerFrame;
/**
* The maximum areas, in pixels, for each of the fixed cels in the robot.
* Used for preallocation of cel memory.
*/
MaxCelAreaList _maxCelArea;
/**
* The hunk palette to use when rendering the current frame, if the
* `usePalette` flag was set in the robot header.
*/
uint8 *_rawPalette;
/**
* A list of the raw video data sizes, in bytes, for each frame of the
* robot.
*/
VideoSizeList _videoSizes;
/**
* A list of cels that will be present for the entire duration of the robot
* animation.
*/
FixedCelsList _fixedCels;
/**
* A list of handles for each cel in the current frame.
*/
CelHandleList _celHandles;
/**
* Scratch memory used to temporarily store decompressed cel data for
* vertically squashed cels.
*/
ScratchMemory _celDecompressionBuffer;
/**
* The size, in bytes, of the squashed cel decompression buffer.
*/
int _celDecompressionArea;
/**
* If true, the robot just started playing and is awaiting output for the
* first frame.
*/
bool _syncFrame;
/**
* Scratch memory used to store the compressed robot video data for the
* current frame.
*/
ScratchMemory _doVersion5Scratch;
/**
* When set to a non-negative value, forces the next call to doRobot to
* render the given frame number instead of whatever frame would have
* normally been rendered.
*/
mutable int _cueForceShowFrame;
/**
* The plane where the robot animation will be drawn.
*/
Plane *_plane;
/**
* A list of pointers to ScreenItems used by the robot.
*/
RobotScreenItemList _screenItemList;
/**
* The positions of the various screen items in this robot, in screen
* coordinates.
*/
Common::Array<int16> _screenItemX, _screenItemY;
/**
* The raw position values from the cel header for each screen item
* currently on-screen.
*/
Common::Array<int16> _originalScreenItemX, _originalScreenItemY;
/**
* The duration of the current robot, in frames.
*/
uint16 _numFramesTotal;
/**
* The screen priority of the video.
* @see ScreenItem::_priority
*/
int16 _priority;
/**
* The amount of visual vertical compression applied to the current cel. A
* value of 100 means no compression; a value above 100 indicates how much
* the cel needs to be scaled along the y-axis to return to its original
* dimensions.
*/
uint8 _verticalScaleFactor;
};
} // end of namespace Sci
#endif