aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/video/robot_decoder.h
diff options
context:
space:
mode:
authorColin Snover2016-07-03 17:57:58 -0500
committerColin Snover2016-08-19 14:08:22 -0500
commit0f2748b15a630f9d12ff0511d4e4cde79b8e915f (patch)
tree28044c339c74b76bb963af54f382dcd5530e40de /engines/sci/video/robot_decoder.h
parent7da359755d31b6a5a98fc791f795139441c82b56 (diff)
downloadscummvm-rg350-0f2748b15a630f9d12ff0511d4e4cde79b8e915f.tar.gz
scummvm-rg350-0f2748b15a630f9d12ff0511d4e4cde79b8e915f.tar.bz2
scummvm-rg350-0f2748b15a630f9d12ff0511d4e4cde79b8e915f.zip
SCI32: Implement kRobot
Diffstat (limited to 'engines/sci/video/robot_decoder.h')
-rw-r--r--engines/sci/video/robot_decoder.h1454
1 files changed, 1376 insertions, 78 deletions
diff --git a/engines/sci/video/robot_decoder.h b/engines/sci/video/robot_decoder.h
index 4faea5008a..5fd6ad49c4 100644
--- a/engines/sci/video/robot_decoder.h
+++ b/engines/sci/video/robot_decoder.h
@@ -20,109 +20,1407 @@
*
*/
-#ifndef SCI_VIDEO_ROBOT_DECODER_H
-#define SCI_VIDEO_ROBOT_DECODER_H
+#ifndef SCI_SOUND_DECODERS_ROBOT_H
+#define SCI_SOUND_DECODERS_ROBOT_H
-#include "common/rational.h"
-#include "common/rect.h"
-#include "video/video_decoder.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 Audio {
-class QueuingAudioStream;
-}
+namespace Common { class SeekableSubReadStreamEndian; }
+namespace Sci {
+class Plane;
+class SegManager;
-namespace Common {
-class SeekableSubReadStreamEndian;
-}
+// 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.
+//
+// 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 doubling. 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).
-namespace Sci {
+#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);
+ virtual ~RobotAudioStream();
+
+ /**
+ * 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.
+ * 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 correspond 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;
+ virtual bool isStereo() const override { return false; };
+ virtual int getRate() const override { return 22050; };
+ virtual bool endOfData() const override {
+ Common::StackLock lock(_mutex);
+ return _readHeadAbs >= _writeHeadAbs;
+ };
+ virtual bool endOfStream() const override {
+ Common::StackLock lock(_mutex);
+ return _finished && endOfData();
+ }
+};
+
+#pragma mark -
+#pragma mark RobotDecoder
+
+/**
+ * RobotDecoder implements the logic required
+ * for Robot animations.
+ *
+ * @note A paused or finished RobotDecoder was
+ * classified as serializable in SCI3, but the
+ * save/load code would attempt to use uninitialised
+ * values, so it seems that robots were not ever
+ * actually able to be saved.
+ */
+class RobotDecoder {
+public:
+ RobotDecoder(SegManager *segMan);
+ ~RobotDecoder();
+
+private:
+ SegManager *_segMan;
+
+#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,
-class RobotDecoder : public Video::VideoDecoder {
+ /**
+ * 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; 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:
- RobotDecoder(bool isBigEndian);
- virtual ~RobotDecoder();
+ /**
+ * 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);
- bool loadStream(Common::SeekableReadStream *stream);
- bool load(GuiResourceId id);
+ /**
+ * Closes the currently open robot file.
+ */
void close();
- void setPos(uint16 x, uint16 y) { _pos = Common::Point(x, y); }
- Common::Point getPos() const { return _pos; }
+ /**
+ * 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);
-protected:
- void readNextPacket();
+ /**
+ * 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:
- class RobotVideoTrack : public FixedRateVideoTrack {
+ /**
+ * 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:
- RobotVideoTrack(int frameCount);
- ~RobotVideoTrack();
-
- uint16 getWidth() const;
- uint16 getHeight() const;
- Graphics::PixelFormat getPixelFormat() const;
- int getCurFrame() const { return _curFrame; }
- int getFrameCount() const { return _frameCount; }
- const Graphics::Surface *decodeNextFrame() { return _surface; }
- const byte *getPalette() const { _dirtyPalette = false; return _palette; }
- bool hasDirtyPalette() const { return _dirtyPalette; }
-
- void readPaletteChunk(Common::SeekableSubReadStreamEndian *stream, uint16 chunkSize);
- void calculateVideoDimensions(Common::SeekableSubReadStreamEndian *stream, uint32 *frameSizes);
- Graphics::Surface *getSurface() { return _surface; }
- void increaseCurFrame() { _curFrame++; }
-
- protected:
- Common::Rational getFrameRate() const { return Common::Rational(60, 10); }
+ 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:
- int _frameCount;
- int _curFrame;
- byte _palette[256 * 3];
- mutable bool _dirtyPalette;
- Graphics::Surface *_surface;
+ 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();
};
- class RobotAudioTrack : public AudioTrack {
+ /**
+ * 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:
- RobotAudioTrack();
- ~RobotAudioTrack();
+ 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);
- Audio::Mixer::SoundType getSoundType() const { return Audio::Mixer::kMusicSoundType; }
+ /**
+ * Immediately stops any active playback and
+ * purges all audio data in the audio list.
+ */
+ void reset();
- void queueBuffer(byte *buffer, int size);
+ /**
+ * Pauses the robot audio channel in
+ * preparation for the first block of audio
+ * data to be read.
+ */
+ void prepareForPrimer();
- protected:
- Audio::AudioStream *getAudioStream() const;
+ /**
+ * 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:
- Audio::QueuingAudioStream *_audioStream;
+ /**
+ * 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();
};
- struct RobotHeader {
- // 6 bytes, identifier bytes
- uint16 version;
- uint16 audioChunkSize;
- uint16 audioSilenceSize;
- // 2 bytes, unknown
- uint16 frameCount;
- uint16 paletteDataSize;
- uint16 unkChunkDataSize;
- // 5 bytes, unknown
- byte hasSound;
- // 34 bytes, unknown
- } _header;
-
- void readHeaderChunk();
- void readFrameSizesChunk();
-
- Common::Point _pos;
- bool _isBigEndian;
- uint32 *_frameTotalSize;
-
- Common::SeekableSubReadStreamEndian *_fileStream;
-};
+ /**
+ * 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;
-} // End of namespace Sci
+ /**
+ * 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:
+ /**
+ * 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;
+
+ /**
+ * 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 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