ScummVM API documentation
robot_decoder.h
1/* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 */
21
22#ifndef SCI_VIDEO_DECODERS_ROBOT_H
23#define SCI_VIDEO_DECODERS_ROBOT_H
24
25#include "audio/audiostream.h" // for AudioStream
26#include "audio/rate.h" // for st_sample_t
27#include "common/array.h" // for Array
28#include "common/mutex.h" // for StackLock, Mutex
29#include "common/rect.h" // for Point, Rect (ptr only)
30#include "common/scummsys.h" // for int16, int32, byte, uint16
31#include "sci/engine/vm_types.h" // for NULL_REG, reg_t
32#include "sci/graphics/helpers.h" // for GuiResourceId
33#include "sci/graphics/screen_item32.h" // for ScaleInfo, ScreenItem (ptr o...
34
35namespace Common { class SeekableReadStreamEndian; }
36namespace Sci {
37class Plane;
38class SegManager;
39
40// There were 3 different Robot video versions, used in the following games:
41// - v4: PQ:SWAT demo
42// - v5: KQ7 DOS, Phantasmagoria, PQ:SWAT, Lighthouse
43// - v6: RAMA
44//
45// Notes on Robot v5/v6 format:
46//
47// Robot is a packetized streaming AV format that encodes multiple bitmaps +
48// positioning data, plus synchronised audio, for rendering in the SCI graphics
49// system.
50//
51// Unlike traditional AV formats, Robot videos almost always require playback
52// within the game engine because certain information (like the resolution of
53// the Robot coordinates and the background for the video) is dependent on data
54// that does not exist within the Robot file itself. In version 6, robots could
55// also participate in palette remapping by drawing remap pixels, and the
56// information for processing these pixels is also not stored within the Robot
57// file.
58//
59// The Robot container consists of a file header, an optional primer audio
60// section, an optional colour palette, a frame seek index, a set of cuepoints,
61// and variable-sized packets of compressed video+audio data.
62//
63// Integers in Robot files are coded using native endianness (LSB for x86
64// versions, MSB for 68k/PPC versions).
65//
66// Robot video coding is a relatively simple variable-length compression with no
67// interframe compression. Each cel in a frame is constructed from multiple
68// contiguous data blocks, each of which can be independently compressed with
69// LZS or left uncompressed. An entire cel can also be line decimated, where
70// lines are deleted from the source bitmap at compression time and are
71// reconstructed by decompression using line interpolation. Each cel also
72// includes coordinates where it should be placed within the video frame,
73// relative to the top-left corner of the frame.
74//
75// Audio coding is fixed-length, and all audio blocks except for the primer
76// audio are the same size. Audio is encoded with Sierra SOL DPCM16 compression,
77// and is split into two channels ('even' and 'odd'), each at a 11025Hz sample
78// rate. The original signal is restored by interleaving samples from the two
79// channels together. Channel packets are 'even' if they have an ''absolute
80// position of audio'' that is evenly divisible by 2; otherwise, they are 'odd'.
81// Because the channels use DPCM compression, there is an 8-byte runway at the
82// start of every audio block that is never written to the output stream, which
83// is used to move the signal to the correct location by the 9th sample.
84//
85// File header (v5/v6):
86//
87// byte | description
88// 0 | signature 0x16
89// 1 | unused
90// 2-5 | signature 'SOL\0'
91// 6-7 | version (4, 5, and 6 are the only known versions)
92// 8-9 | size of audio blocks
93// 10-11 | primer is compressed flag
94// 12-13 | unused
95// 14-15 | total number of video frames
96// 16-17 | embedded palette size, in bytes
97// 18-19 | primer reserved size
98// 20-21 | coordinate X-resolution (if 0, uses game coordinates)
99// 22-23 | coordinate Y-resolution (if 0, uses game coordinates)
100// 24 | if non-zero, Robot includes a palette
101// 25 | if non-zero, Robot includes audio
102// 26-27 | unused
103// 28-29 | the frame rate, in frames per second
104// 30-31 | coordinate conversion flag; if true, screen item coordinates
105// | from the robot should be used as-is with NO conversion when
106// | explicitly displaying a specific frame
107// 32-33 | the maximum number of packets that can be skipped without causing
108// | audio drop-out
109// 34-35 | the maximum possible number of cels that will be displayed in any
110// | frame of the robot
111// 36-39 | the maximum possible size, in bytes, of the first fixed cel
112// 40-43 | the maximum possible size, in bytes, of the second fixed cel
113// 44-47 | the maximum possible size, in bytes, of the third fixed cel
114// 48-51 | the maximum possible size, in bytes, of the fourth fixed cel
115// 52-59 | unused
116//
117// If the ''file includes audio'' flag is false, seek ''primer reserved size''
118// bytes from the end of the file header to get past a padding zone.
119//
120// If the ''file includes audio'' flag is true, and the ''primer reserved size''
121// is not zero, the data immediately after the file header consists of an audio
122// primer header plus compressed audio data:
123//
124// Audio primer header:
125//
126// byte | description
127// 0-3 | the size, in bytes, of the entire primer audio section
128// 4-5 | the compression format of the primer audio (must be zero)
129// 6-9 | the size, in bytes, of the "even" primer
130// 10-13 | the size, in bytes, of the "odd" primer
131//
132// If the combined sizes of the even and odd primers do not match the ''primer
133// reserved size'', the next header block can be found ''primer reserved size''
134// bytes from the *start* of the audio primer header.
135//
136// Otherwise, if the Robot has audio, and the ''primer reserved size'' is zero,
137// and the ''primer is compressed flag'' is set, the "even" primer size is
138// 19922, the "odd" primer size is 21024, and the "even" and "odd" buffers
139// should be zero-filled.
140//
141// Any other combination of these flags is an error.
142//
143// If the Robot has a palette, the next ''palette size'' bytes should be read
144// as a SCI HunkPalette. Otherwise, seek ''palette size'' bytes from the current
145// position to get to the frame index.
146//
147// The next section of the Robot is the video frame size index. In version 5
148// robots, read ''total number of frames'' 16-bit integers to get the size of
149// the compressed video for each frame. For version 6 robots, use 32-bit
150// integers.
151//
152// The next section of the Robot is the packet size index (combined compressed
153// size of video + audio for each frame). In version 5 Robots, read ''total
154// number of frames'' 16-bit integers. In version 6 robots, use 32-bit integers.
155//
156// The next section of the Robot is the cue times index. Read 256 32-bit
157// integers, which represent the number of ticks from the start of playback that
158// the given cue point falls on.
159//
160// The next section of the Robot is the cue values index. Read 256 16-bit
161// integers, which represent the actual cue values that will be passed back to
162// the game engine when a cue is requested.
163//
164// Finally, to get to the first frame packet, seek from the current position to
165// the start of the next 2048-byte-aligned sector.
166//
167// Frame packet:
168//
169// byte | description
170// 0..n | video data (size is in the ''video frame size index'')
171// n+1.. | optional audio data (size is ''size of audio blocks'')
172//
173// Video data:
174//
175// byte | description
176// 0-2 | number of cels in the frame (max 10)
177// 3..n | cels
178//
179// Cel:
180//
181// 0-17 | cel header
182// 18..n | data chunks
183//
184// Cel header:
185//
186// byte | description
187// 0 | unused
188// 1 | vertical scale factor, in percent decimation (100 = no decimation,
189// | 50 = 50% of lines were removed)
190// 2-3 | cel width
191// 4-5 | cel height
192// 6-9 | unused
193// 10-11 | cel x-position, in Robot coordinates
194// 12-13 | cel y-position, in Robot coordinates
195// 14-15 | cel total data chunk size, in bytes
196// 16-17 | number of data chunks
197//
198// Cel data chunk:
199//
200// 0-9 | cel data chunk header
201// 10..n | cel data
202//
203// Cel data chunk header:
204//
205// byte | description
206// 0-3 | compressed size
207// 4-7 | decompressed size
208// 8-9 | compression type (0 = LZS, 2 = uncompressed)
209//
210// Random frame seeking can be done by calculating the address of the frame
211// packet by adding up the ''packet size index'' entries up to the current
212// frame. This will normally disable audio playback, as audio data in a packet
213// does not correspond to the video in the same packet.
214//
215// Audio data is placed immediately after the end of the video data in a packet,
216// and consists of an audio header plus compressed audio data:
217//
218// Audio data:
219//
220// byte | description
221// 0-7 | audio data header
222// 8-15 | DPCM runway
223// 16..n | compressed audio data
224//
225// Audio data header:
226//
227// byte | description
228// 0-3 | absolute position of audio in the audio stream
229// 4-7 | the size of the audio block, excluding the header
230//
231// When a block of audio is processed, first check to ensure that the
232// decompressed audio block's `position * 2 + length * 4` runs past the end of
233// the last packet of the same evenness/oddness. Discard the audio block
234// entirely if data has already been written past the end of this block for this
235// channel, or if the read head has already read past the end of this audio
236// block.
237//
238// If the block is not discarded, apply DPCM decompression to the entire block,
239// starting from beginning of the DPCM runway, using an initial sample value of
240// 0. Then, copy every sample from the decompressed source outside of the DPCM
241// runway into every *other* sample of the final audio buffer (1 -> 2, 2 -> 4,
242// 3 -> 6, etc.).
243//
244// Finally, for any skipped samples where the opposing (even/odd) channel did
245// not yet write, interpolate the skipped areas by adding together the
246// neighbouring samples from this audio block and dividing by two. (This allows
247// the audio quality to degrade to 11kHz in case it takes too long to decode all
248// the frames in the stream). Interpolated samples must not be written on top of
249// true data from the opposing channel. Audio from later packets must also not
250// be written on top of data in the same channel that was already written by an
251// earlier packet, in particular because the first 8 bytes of the next packet
252// are garbage data used to move the waveform to the correct position (due to
253// the use of DPCM compression).
254
255#pragma mark -
256#pragma mark RobotAudioStream
257
263public:
264 enum {
269
274 kEOSExpansion = 2
275 };
276
280 struct StreamState {
285
289 uint16 rate;
290
294 uint8 bits;
295 };
296
304 byte *data;
305
311
316
317 RobotAudioPacket(byte *data_, const int dataSize_, const int position_) :
318 data(data_), dataSize(dataSize_), position(position_) {}
319 };
320
321 RobotAudioStream(const int32 bufferSize);
322 ~RobotAudioStream() override;
323
329 bool addPacket(const RobotAudioPacket &packet);
330
335 void finish();
336
341
342private:
343 Common::Mutex _mutex;
344
348 byte *_loopBuffer;
349
353 int32 _loopBufferSize;
354
358 int32 _readHead;
359
363 int32 _readHeadAbs;
364
368 int32 _maxWriteAbs;
369
375 int32 _writeHeadAbs;
376
384 int32 _jointMin[2];
385
390 bool _waiting;
391
395 bool _finished;
396
402 int32 _firstPacketPosition;
403
408 byte *_decompressionBuffer;
409
413 int32 _decompressionBufferSize;
414
420 int32 _decompressionBufferPosition;
421
425 void fillRobotBuffer(const RobotAudioPacket &packet, const int8 bufferIndex);
426
431 void interpolateMissingSamples(const int32 numSamples);
432
433#pragma mark -
434#pragma mark RobotAudioStream - AudioStream implementation
435public:
436 int readBuffer(int16 *outBuffer, int numSamples) override;
437 bool isStereo() const override { return false; };
438 int getRate() const override { return 22050; };
439 bool endOfData() const override {
440 Common::StackLock lock(_mutex);
441 return _readHeadAbs >= _writeHeadAbs;
442 };
443 bool endOfStream() const override {
444 Common::StackLock lock(_mutex);
445 return _finished && endOfData();
446 }
447};
448
449#pragma mark -
450#pragma mark RobotDecoder
451
456public:
457 RobotDecoder(SegManager *segMan);
459
460 GuiResourceId getResourceId() const {
461 return _robotId;
462 }
463
464private:
465 SegManager *_segMan;
466
470 GuiResourceId _robotId;
471
472#pragma mark Constants
473public:
478 kRobotStatusUninitialized = 0,
479 kRobotStatusPlaying = 1,
480 kRobotStatusEnd = 2,
481 kRobotStatusPaused = 3
482 };
483
484 enum {
485 // Special high value used to represent parameters that should be left
486 // unchanged when calling `showFrame`
487 kUnspecified = 50000
488 };
489
490private:
491 enum {
495 kScreenItemListSize = 10,
496
500 kAudioListSize = 10,
501
505 kDelayListSize = 10,
506
510 kCueListSize = 256,
511
516 kFixedCelListSize = 4,
517
521 kRawPaletteSize = 1200,
522
528 kRobotFrameSize = 2048,
529
534 kRobotZeroCompressSize = 2048,
535
541 kAudioBlockHeaderSize = 8,
542
546 kCelHeaderSize = 22,
547
552 kMaxFrameRateDrift = 1
553 };
554
566 uint16 _version;
567
568#pragma mark -
569#pragma mark Initialisation
570private:
574 void initStream(const GuiResourceId robotId);
575
579 void initPlayback();
580
584 void initAudio();
585
589 void initVideo(const int16 x, const int16 y, const int16 scale, const reg_t plane, const bool hasPalette, const uint16 paletteSize);
590
594 void initRecordAndCuePositions();
595
596#pragma mark -
597#pragma mark Playback
598public:
603 void open(const GuiResourceId robotId, const reg_t plane, const int16 priority, const int16 x, const int16 y, const int16 scale);
604
608 void close();
609
614 void pause();
615
619 void resume();
620
626 void showFrame(const uint16 frameNo, const uint16 newX, const uint16 newY, const uint16 newPriority);
627
631 int16 getCue() const;
632
636 int16 getFrameNo() const;
637
642
643private:
648
652 RobotStatus _status;
653
655
659 PositionList _recordPositions;
660
664 int32 _fileOffset;
665
670 mutable int32 _cueTimes[kCueListSize];
671
675 int32 _masterCueTimes[kCueListSize];
676
680 int32 _cueValues[kCueListSize];
681
685 int16 _frameRate;
686
690 int16 _normalFrameRate;
691
696 int16 _minFrameRate;
697
702 int16 _maxFrameRate;
703
708 int16 _maxSkippablePackets;
709
713 int _currentFrameNo;
714
718 int _previousFrameNo;
719
723 int32 _startTime;
724
728 int32 _startFrameNo;
729
733 int32 _startingFrameNo;
734
738 bool seekToFrame(const int frameNo);
739
744 void setRobotTime(const int frameNo);
745
746#pragma mark -
747#pragma mark Timing
748private:
755 class DelayTime {
756 public:
757 DelayTime(RobotDecoder *decoder);
758
762 void startTiming();
763
767 void endTiming();
768
772 bool timingInProgress() const;
773
778 int predictedTicks() const;
779
780 private:
781 RobotDecoder *_decoder;
782
790 uint32 _startTime;
791
796 int _delays[kDelayListSize];
797
803 uint _timestamps[kDelayListSize];
804
808 uint _oldestTimestamp;
809
813 uint _newestTimestamp;
814
818 void sortList();
819 };
820
825 uint16 calculateNextFrameNo(const uint32 extraTicks = 0) const;
826
831 uint32 ticksToFrames(const uint32 ticks) const;
832
836 uint32 getTickCount() const;
837
841 DelayTime _delayTime;
842
843#pragma mark -
844#pragma mark Audio
845private:
846 enum {
850 kAudioSyncCheckInterval = 5 * 60 /* 5 seconds */
851 };
852
856 enum RobotAudioStatus {
857 kRobotAudioReady = 1,
858 kRobotAudioStopped = 2,
859 kRobotAudioPlaying = 3,
860 kRobotAudioPaused = 4,
861 kRobotAudioStopping = 5
862 };
863
864#pragma mark -
865#pragma mark Audio - AudioList
866private:
870 class AudioList {
871 public:
872 AudioList();
873
877 void startAudioNow();
878
883 void stopAudio();
884
888 void stopAudioNow();
889
893 void submitDriverMax();
894
904 void addBlock(const int position, const int size, const byte *buffer);
905
910 void reset();
911
916 void prepareForPrimer();
917
922 void setAudioOffset(const int offset);
923
924#pragma mark -
925#pragma mark Audio - AudioList - AudioBlock
926
927 private:
931 class AudioBlock {
932 public:
933 AudioBlock(const int position, const int size, const byte *const data);
934 ~AudioBlock();
935
941 bool submit(const int startOffset);
942
943 private:
948 int _position;
949
953 int _size;
954
958 byte *_data;
959 };
960
964 AudioBlock *_blocks[kAudioListSize];
965
969 uint8 _blocksSize;
970
974 uint8 _oldestBlockIndex;
975
979 uint8 _newestBlockIndex;
980
984 int _startOffset;
985
989 RobotAudioStatus _status;
990
994 void freeAudioBlocks();
995 };
996
1001 bool _hasAudio;
1002
1006 AudioList _audioList;
1007
1012 uint16 _audioBlockSize;
1013
1018 int16 _expectedAudioBlockSize;
1019
1024 int16 _audioRecordInterval;
1025
1030 uint16 _primerZeroCompressFlag;
1031
1036 uint16 _primerReservedSize;
1037
1041 int32 _totalPrimerSize;
1042
1046 int32 _primerPosition;
1047
1051 int32 _evenPrimerSize;
1052
1056 int32 _oddPrimerSize;
1057
1061 int32 _firstAudioRecordPosition;
1062
1067 byte *_audioBuffer;
1068
1073 uint32 _checkAudioSyncTime;
1074
1080 bool primeAudio(const uint32 startTick);
1081
1086 bool readPrimerData(byte *outEvenBuffer, byte *outOddBuffer);
1087
1095 bool readAudioDataFromRecord(const int frameNo, byte *outBuffer, int &outAudioPosition, int &outAudioSize);
1096
1101 bool readPartialAudioRecordAndSubmit(const int startFrame, const int startPosition);
1102
1103#pragma mark -
1104#pragma mark Rendering
1105public:
1109 const reg_t getPlaneId() const {
1110 return _planeId;
1111 }
1112
1117 return _position;
1118 }
1119
1123 int16 getScale() const {
1124 return _scaleInfo.x;
1125 }
1126
1132 uint16 getFrameSize(Common::Rect &outRect) const;
1133
1138 void doRobot();
1139
1145
1151
1156 void expandCel(byte *target, const byte* source, const int16 celWidth, const int16 celHeight) const;
1157
1158 int16 getPriority() const;
1159
1164 void setPriority(const int16 newPriority);
1165
1166private:
1167 enum CompressionType {
1168 kCompressionLZS = 0,
1169 kCompressionNone = 2
1170 };
1171
1175 struct CelHandleInfo {
1179 enum CelHandleLifetime {
1180 kNoCel = 0,
1181 kFrameLifetime = 1,
1182 kRobotLifetime = 2
1183 };
1184
1188 reg_t bitmapId;
1189
1194 CelHandleLifetime status;
1195
1199 int area;
1200
1201 CelHandleInfo() : bitmapId(NULL_REG), status(kNoCel), area(0) {}
1202 };
1203
1204 typedef Common::Array<ScreenItem *> RobotScreenItemList;
1205 typedef Common::Array<CelHandleInfo> CelHandleList;
1206 typedef Common::Array<int> VideoSizeList;
1207 typedef Common::Array<uint> MaxCelAreaList;
1208 typedef Common::Array<reg_t> FixedCelsList;
1209 typedef Common::Array<Common::Point> CelPositionsList;
1210 typedef Common::Array<byte> ScratchMemory;
1211
1215 void doVersion5(const bool shouldSubmitAudio = true);
1216
1220 void createCels5(const byte *rawVideoData, const int16 numCels, const bool usePalette);
1221
1227 uint32 createCel5(const byte *rawVideoData, const int16 screenItemIndex, const bool usePalette);
1228
1232 void preallocateCelMemory(const byte *rawVideoData, const int16 numCels);
1233
1237 DecompressorLZS _decompressor;
1238
1242 reg_t _planeId;
1243
1247 Common::Point _position;
1248
1252 ScaleInfo _scaleInfo;
1253
1257 int16 _xResolution, _yResolution;
1258
1262 bool _isHiRes;
1263
1268 int16 _maxCelsPerFrame;
1269
1274 MaxCelAreaList _maxCelArea;
1275
1280 uint8 *_rawPalette;
1281
1286 VideoSizeList _videoSizes;
1287
1292 FixedCelsList _fixedCels;
1293
1297 CelHandleList _celHandles;
1298
1303 ScratchMemory _celDecompressionBuffer;
1304
1308 int _celDecompressionArea;
1309
1314 bool _syncFrame;
1315
1320 ScratchMemory _doVersion5Scratch;
1321
1327 mutable int _cueForceShowFrame;
1328
1332 Plane *_plane;
1333
1337 RobotScreenItemList _screenItemList;
1338
1343 Common::Array<int16> _screenItemX, _screenItemY;
1344
1349 Common::Array<int16> _originalScreenItemX, _originalScreenItemY;
1350
1354 uint16 _numFramesTotal;
1355
1360 int16 _priority;
1361
1368 uint8 _verticalScaleFactor;
1369};
1370
1371} // end of namespace Sci
1372
1373#endif // SCI_VIDEO_DECODERS_ROBOT_H
Definition: audiostream.h:50
Definition: mutex.h:67
Definition: stream.h:944
Definition: mutex.h:51
Definition: robot_decoder.h:262
@ kEOSExpansion
Definition: robot_decoder.h:274
@ kRobotSampleRate
Definition: robot_decoder.h:268
bool addPacket(const RobotAudioPacket &packet)
bool isStereo() const override
Definition: robot_decoder.h:437
StreamState getStatus() const
bool endOfData() const override
Definition: robot_decoder.h:439
bool endOfStream() const override
Definition: robot_decoder.h:443
int getRate() const override
Definition: robot_decoder.h:438
int readBuffer(int16 *outBuffer, int numSamples) override
Definition: robot_decoder.h:455
void setPriority(const int16 newPriority)
uint16 getFrameSize(Common::Rect &outRect) const
int16 getFrameNo() const
void frameAlmostVisible()
void open(const GuiResourceId robotId, const reg_t plane, const int16 priority, const int16 x, const int16 y, const int16 scale)
RobotStatus
Definition: robot_decoder.h:477
void expandCel(byte *target, const byte *source, const int16 celWidth, const int16 celHeight) const
int16 getScale() const
Definition: robot_decoder.h:1123
void showFrame(const uint16 frameNo, const uint16 newX, const uint16 newY, const uint16 newPriority)
Common::Point getPosition() const
Definition: robot_decoder.h:1116
int16 getCue() const
const reg_t getPlaneId() const
Definition: robot_decoder.h:1109
RobotStatus getStatus() const
Definition: seg_manager.h:48
Definition: algorithm.h:29
Definition: console.h:28
Definition: rect.h:144
Definition: rect.h:536
Definition: robot_decoder.h:300
int position
Definition: robot_decoder.h:315
byte * data
Definition: robot_decoder.h:304
int dataSize
Definition: robot_decoder.h:310
Definition: robot_decoder.h:280
uint16 rate
Definition: robot_decoder.h:289
int bytesPlaying
Definition: robot_decoder.h:284
uint8 bits
Definition: robot_decoder.h:294
Definition: vm_types.h:39