From 0f2748b15a630f9d12ff0511d4e4cde79b8e915f Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Sun, 3 Jul 2016 17:57:58 -0500 Subject: SCI32: Implement kRobot --- engines/sci/video/robot_decoder.h | 1454 +++++++++++++++++++++++++++++++++++-- 1 file changed, 1376 insertions(+), 78 deletions(-) (limited to 'engines/sci/video/robot_decoder.h') 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 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 RobotScreenItemList; + typedef Common::Array CelHandleList; + typedef Common::Array VideoSizeList; + typedef Common::Array MaxCelAreaList; + typedef Common::Array FixedCelsList; + typedef Common::Array CelPositionsList; + typedef Common::Array 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 _screenItemX, _screenItemY; + + /** + * The raw position values from the cel header for + * each screen item currently on-screen. + */ + Common::Array _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 -- cgit v1.2.3