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 
35 namespace Common { class SeekableReadStreamEndian; }
36 namespace Sci {
37 class Plane;
38 class 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 
263 public:
264  enum {
268  kRobotSampleRate = 22050,
269 
274  kEOSExpansion = 2
275  };
276 
280  struct StreamState {
285 
289  uint16 rate;
290 
294  uint8 bits;
295  };
296 
304  byte *data;
305 
310  int dataSize;
311 
315  int position;
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 
340  StreamState getStatus() const;
341 
342 private:
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
435 public:
436  int readBuffer(Audio::st_sample_t *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 
456 public:
457  RobotDecoder(SegManager *segMan);
458  ~RobotDecoder();
459 
460  GuiResourceId getResourceId() const {
461  return _robotId;
462  }
463 
464 private:
465  SegManager *_segMan;
466 
470  GuiResourceId _robotId;
471 
472 #pragma mark Constants
473 public:
477  enum RobotStatus {
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 
490 private:
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
570 private:
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
598 public:
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 
641  RobotStatus getStatus() const;
642 
643 private:
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
748 private:
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
845 private:
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
866 private:
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
1105 public:
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 
1144  void frameAlmostVisible();
1145 
1150  void frameNowVisible();
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 
1166 private:
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 
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: robot_decoder.h:280
uint8 bits
Definition: robot_decoder.h:294
bool endOfData() const override
Definition: robot_decoder.h:439
bool isStereo() const override
Definition: robot_decoder.h:437
byte * data
Definition: robot_decoder.h:304
RobotStatus
Definition: robot_decoder.h:477
uint16 rate
Definition: robot_decoder.h:289
const reg_t getPlaneId() const
Definition: robot_decoder.h:1109
Definition: mutex.h:51
int getRate() const override
Definition: robot_decoder.h:438
Definition: plane32.h:103
Definition: rect.h:144
Definition: robot_decoder.h:262
int bytesPlaying
Definition: robot_decoder.h:284
Graphics::Surface * scale(const Graphics::Surface &srcImage, int xSize, int ySize)
Definition: screen_item32.h:37
Definition: algorithm.h:29
Definition: mutex.h:67
Definition: rect.h:45
Definition: audiostream.h:50
bool endOfStream() const override
Definition: robot_decoder.h:443
Definition: console.h:28
int dataSize
Definition: robot_decoder.h:310
Definition: seg_manager.h:48
Definition: stream.h:944
int position
Definition: robot_decoder.h:315
Definition: robot_decoder.h:300
int16 getScale() const
Definition: robot_decoder.h:1123
Definition: vm_types.h:39
Definition: robot_decoder.h:455
Common::Point getPosition() const
Definition: robot_decoder.h:1116