1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372
|
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#ifndef SCI_SOUND_DECODERS_ROBOT_H
#define SCI_SOUND_DECODERS_ROBOT_H
#include "audio/audiostream.h" // for AudioStream
#include "audio/rate.h" // for st_sample_t
#include "common/array.h" // for Array
#include "common/mutex.h" // for StackLock, Mutex
#include "common/rect.h" // for Point, Rect (ptr only)
#include "common/scummsys.h" // for int16, int32, byte, uint16
#include "sci/engine/vm_types.h" // for NULL_REG, reg_t
#include "sci/graphics/helpers.h" // for GuiResourceId
#include "sci/graphics/screen_item32.h" // for ScaleInfo, ScreenItem (ptr o...
namespace Common { class SeekableSubReadStreamEndian; }
namespace Sci {
class Plane;
class SegManager;
// There were 3 different Robot video versions, used in the following games:
// - v4: PQ:SWAT demo
// - v5: KQ7 DOS, Phantasmagoria, PQ:SWAT, Lighthouse
// - v6: RAMA
//
// Notes on Robot v5/v6 format:
//
// Robot is a packetized streaming AV format that encodes multiple bitmaps +
// positioning data, plus synchronised audio, for rendering in the SCI graphics
// system.
//
// Unlike traditional AV formats, Robot videos almost always require playback
// within the game engine because certain information (like the resolution of
// the Robot coordinates and the background for the video) is dependent on data
// that does not exist within the Robot file itself. In version 6, robots could
// also participate in palette remapping by drawing remap pixels, and the
// information for processing these pixels is also not stored within the Robot
// file.
//
// The Robot container consists of a file header, an optional primer audio
// section, an optional colour palette, a frame seek index, a set of cuepoints,
// and variable-sized packets of compressed video+audio data.
//
// Integers in Robot files are coded using native endianness (LSB for x86
// versions, MSB for 68k/PPC versions).
//
// Robot video coding is a relatively simple variable-length compression with no
// interframe compression. Each cel in a frame is constructed from multiple
// contiguous data blocks, each of which can be independently compressed with
// LZS or left uncompressed. An entire cel can also be line decimated, where
// lines are deleted from the source bitmap at compression time and are
// reconstructed by decompression using line interpolation. Each cel also
// includes coordinates where it should be placed within the video frame,
// relative to the top-left corner of the frame.
//
// Audio coding is fixed-length, and all audio blocks except for the primer
// audio are the same size. Audio is encoded with Sierra SOL DPCM16 compression,
// and is split into two channels ('even' and 'odd'), each at a 11025Hz sample
// rate. The original signal is restored by interleaving samples from the two
// channels together. Channel packets are 'even' if they have an ''absolute
// position of audio'' that is evenly divisible by 2; otherwise, they are 'odd'.
// Because the channels use DPCM compression, there is an 8-byte runway at the
// start of every audio block that is never written to the output stream, which
// is used to move the signal to the correct location by the 9th sample.
//
// File header (v5/v6):
//
// byte | description
// 0 | signature 0x16
// 1 | unused
// 2-5 | signature 'SOL\0'
// 6-7 | version (4, 5, and 6 are the only known versions)
// 8-9 | size of audio blocks
// 10-11 | primer is compressed flag
// 12-13 | unused
// 14-15 | total number of video frames
// 16-17 | embedded palette size, in bytes
// 18-19 | primer reserved size
// 20-21 | coordinate X-resolution (if 0, uses game coordinates)
// 22-23 | coordinate Y-resolution (if 0, uses game coordinates)
// 24 | if non-zero, Robot includes a palette
// 25 | if non-zero, Robot includes audio
// 26-27 | unused
// 28-29 | the frame rate, in frames per second
// 30-31 | coordinate conversion flag; if true, screen item coordinates
// | from the robot should be used as-is with NO conversion when
// | explicitly displaying a specific frame
// 32-33 | the maximum number of packets that can be skipped without causing
// | audio drop-out
// 34-35 | the maximum possible number of cels that will be displayed in any
// | frame of the robot
// 36-39 | the maximum possible size, in bytes, of the first fixed cel
// 40-43 | the maximum possible size, in bytes, of the second fixed cel
// 44-47 | the maximum possible size, in bytes, of the third fixed cel
// 48-51 | the maximum possible size, in bytes, of the fourth fixed cel
// 52-59 | unused
//
// If the ''file includes audio'' flag is false, seek ''primer reserved size''
// bytes from the end of the file header to get past a padding zone.
//
// If the ''file includes audio'' flag is true, and the ''primer reserved size''
// is not zero, the data immediately after the file header consists of an audio
// primer header plus compressed audio data:
//
// Audio primer header:
//
// byte | description
// 0-3 | the size, in bytes, of the entire primer audio section
// 4-5 | the compression format of the primer audio (must be zero)
// 6-9 | the size, in bytes, of the "even" primer
// 10-13 | the size, in bytes, of the "odd" primer
//
// If the combined sizes of the even and odd primers do not match the ''primer
// reserved size'', the next header block can be found ''primer reserved size''
// bytes from the *start* of the audio primer header.
//
// Otherwise, if the Robot has audio, and the ''primer reserved size'' is zero,
// and the ''primer is compressed flag'' is set, the "even" primer size is
// 19922, the "odd" primer size is 21024, and the "even" and "odd" buffers
// should be zero-filled.
//
// Any other combination of these flags is an error.
//
// If the Robot has a palette, the next ''palette size'' bytes should be read
// as a SCI HunkPalette. Otherwise, seek ''palette size'' bytes from the current
// position to get to the frame index.
//
// The next section of the Robot is the video frame size index. In version 5
// robots, read ''total number of frames'' 16-bit integers to get the size of
// the compressed video for each frame. For version 6 robots, use 32-bit
// integers.
//
// The next section of the Robot is the packet size index (combined compressed
// size of video + audio for each frame). In version 5 Robots, read ''total
// number of frames'' 16-bit integers. In version 6 robots, use 32-bit integers.
//
// The next section of the Robot is the cue times index. Read 256 32-bit
// integers, which represent the number of ticks from the start of playback that
// the given cue point falls on.
//
// The next section of the Robot is the cue values index. Read 256 16-bit
// integers, which represent the actual cue values that will be passed back to
// the game engine when a cue is requested.
//
// Finally, to get to the first frame packet, seek from the current position to
// the start of the next 2048-byte-aligned sector.
//
// Frame packet:
//
// byte | description
// 0..n | video data (size is in the ''video frame size index'')
// n+1.. | optional audio data (size is ''size of audio blocks'')
//
// Video data:
//
// byte | description
// 0-2 | number of cels in the frame (max 10)
// 3..n | cels
//
// Cel:
//
// 0-17 | cel header
// 18..n | data chunks
//
// Cel header:
//
// byte | description
// 0 | unused
// 1 | vertical scale factor, in percent decimation (100 = no decimation,
// | 50 = 50% of lines were removed)
// 2-3 | cel width
// 4-5 | cel height
// 6-9 | unused
// 10-11 | cel x-position, in Robot coordinates
// 12-13 | cel y-position, in Robot coordinates
// 14-15 | cel total data chunk size, in bytes
// 16-17 | number of data chunks
//
// Cel data chunk:
//
// 0-9 | cel data chunk header
// 10..n | cel data
//
// Cel data chunk header:
//
// byte | description
// 0-3 | compressed size
// 4-7 | decompressed size
// 8-9 | compression type (0 = LZS, 2 = uncompressed)
//
// Random frame seeking can be done by calculating the address of the frame
// packet by adding up the ''packet size index'' entries up to the current
// frame. This will normally disable audio playback, as audio data in a packet
// does not correspond to the video in the same packet.
//
// Audio data is placed immediately after the end of the video data in a packet,
// and consists of an audio header plus compressed audio data:
//
// Audio data:
//
// byte | description
// 0-7 | audio data header
// 8-15 | DPCM runway
// 16..n | compressed audio data
//
// Audio data header:
//
// byte | description
// 0-3 | absolute position of audio in the audio stream
// 4-7 | the size of the audio block, excluding the header
//
// When a block of audio is processed, first check to ensure that the
// decompressed audio block's `position * 2 + length * 4` runs past the end of
// the last packet of the same evenness/oddness. Discard the audio block
// entirely if data has already been written past the end of this block for this
// channel, or if the read head has already read past the end of this audio
// block.
//
// If the block is not discarded, apply DPCM decompression to the entire block,
// starting from beginning of the DPCM runway, using an initial sample value of
// 0. Then, copy every sample from the decompressed source outside of the DPCM
// runway into every *other* sample of the final audio buffer (1 -> 2, 2 -> 4,
// 3 -> 6, etc.).
//
// Finally, for any skipped samples where the opposing (even/odd) channel did
// not yet write, interpolate the skipped areas by adding together the
// neighbouring samples from this audio block and dividing by two. (This allows
// the audio quality to degrade to 11kHz in case it takes too long to decode all
// the frames in the stream). Interpolated samples must not be written on top of
// true data from the opposing channel. Audio from later packets must also not
// be written on top of data in the same channel that was already written by an
// earlier packet, in particular because the first 8 bytes of the next packet
// are garbage data used to move the waveform to the correct position (due to
// the use of DPCM compression).
#pragma mark -
#pragma mark RobotAudioStream
/**
* A Robot audio stream is a simple loop buffer that accepts audio blocks from
* the Robot engine.
*/
class RobotAudioStream : public Audio::AudioStream {
public:
enum {
/**
* The sample rate used for all robot audio.
*/
kRobotSampleRate = 22050,
/**
* Multiplier for the size of a packet that is being expanded by writing
* to every other byte of the target buffer.
*/
kEOSExpansion = 2
};
/**
* Playback state information. Used for framerate calculation.
*/
struct StreamState {
/**
* The current position of the read head of the audio stream.
*/
int bytesPlaying;
/**
* The sample rate of the audio stream. Always 22050.
*/
uint16 rate;
/**
* The bit depth of the audio stream. Always 16.
*/
uint8 bits;
};
/**
* A single packet of compressed audio from a Robot data stream.
*/
struct RobotAudioPacket {
/**
* Raw DPCM-compressed audio data.
*/
byte *data;
/**
* The size of the compressed audio data,
* in bytes.
*/
int dataSize;
/**
* The uncompressed, file-relative position of this audio packet.
*/
int position;
RobotAudioPacket(byte *data_, const int dataSize_, const int position_) :
data(data_), dataSize(dataSize_), position(position_) {}
};
RobotAudioStream(const int32 bufferSize);
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. This is different from `_maxWriteAbs`, which is the
* highest uncompressed position which *can* be written right now.
*/
int32 _writeHeadAbs;
/**
* The highest file position, in uncompressed bytes, that has been written
* to the even & odd sides of the stream.
*
* Index 0 corresponds to the 'even' side; index 1 corresponds to the 'odd'
* side.
*/
int32 _jointMin[2];
/**
* When `true`, the stream is waiting for all primer blocks to be received
* before allowing playback to begin.
*/
bool _waiting;
/**
* When `true`, the stream will accept no more audio blocks.
*/
bool _finished;
/**
* The uncompressed position of the first packet of robot data. Used to
* decide whether all primer blocks have been received and the stream should
* be started.
*/
int32 _firstPacketPosition;
/**
* Decompression buffer, used to temporarily store an uncompressed block of
* audio data.
*/
byte *_decompressionBuffer;
/**
* The size of the decompression buffer, in bytes.
*/
int32 _decompressionBufferSize;
/**
* The position of the packet currently in the decompression buffer. Used to
* avoid re-decompressing audio data that has already been decompressed
* during a partial packet read.
*/
int32 _decompressionBufferPosition;
/**
* Calculates the absolute ranges for new fills into the loop buffer.
*/
void fillRobotBuffer(const RobotAudioPacket &packet, const int8 bufferIndex);
/**
* Interpolates `numSamples` samples from the read head, if no true samples
* were written for one (or both) of the joint channels.
*/
void interpolateMissingSamples(const int32 numSamples);
#pragma mark -
#pragma mark RobotAudioStream - AudioStream implementation
public:
int readBuffer(Audio::st_sample_t *outBuffer, int numSamples) override;
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.
*/
class RobotDecoder {
public:
RobotDecoder(SegManager *segMan);
~RobotDecoder();
GuiResourceId getResourceId() const {
return _robotId;
}
private:
SegManager *_segMan;
/**
* The ID of the currently loaded robot.
*/
GuiResourceId _robotId;
#pragma mark Constants
public:
/**
* The playback status of the robot.
*/
enum RobotStatus {
kRobotStatusUninitialized = 0,
kRobotStatusPlaying = 1,
kRobotStatusEnd = 2,
kRobotStatusPaused = 3
};
enum {
// Special high value used to represent parameters that should be left
// unchanged when calling `showFrame`
kUnspecified = 50000
};
private:
enum {
/**
* Maximum number of on-screen screen items.
*/
kScreenItemListSize = 10,
/**
* Maximum number of queued audio blocks.
*/
kAudioListSize = 10,
/**
* Maximum number of samples used for frame timing.
*/
kDelayListSize = 10,
/**
* Maximum number of cues.
*/
kCueListSize = 256,
/**
* Maximum number of 'fixed' cels that never change for the duration of
* a robot.
*/
kFixedCelListSize = 4,
/**
* The size of a hunk palette in the Robot stream.
*/
kRawPaletteSize = 1200,
/**
* The size of a frame of Robot data. This value was used to align the
* first block of data after the main Robot header to the next CD
* sector.
*/
kRobotFrameSize = 2048,
/**
* The size of a block of zero-compressed audio. Used to fill audio when
* the size of an audio packet does not match the expected packet size.
*/
kRobotZeroCompressSize = 2048,
/**
* The size of the audio block header, in bytes. The audio block header
* consists of the compressed size of the audio in the record, plus the
* position of the audio in the compressed data stream.
*/
kAudioBlockHeaderSize = 8,
/**
* The size of a Robot cel header, in bytes.
*/
kCelHeaderSize = 22,
/**
* The maximum amount that the frame rate is allowed to drift from the
* nominal frame rate in order to correct for AV drift or slow playback.
*/
kMaxFrameRateDrift = 1
};
/**
* The version number for the currently loaded robot.
*
* There are several known versions of robot:
*
* v2: before Nov 1994; no known examples
* v3: before Nov 1994; no known examples
* v4: Jan 1995; KQ7 1.65, PQ:SWAT demo
* v5: Mar 1995; SCI2.1 and SCI3 games
* v6: SCI3 games
*/
uint16 _version;
#pragma mark -
#pragma mark Initialisation
private:
/**
* Sets up the read stream for the robot.
*/
void initStream(const GuiResourceId robotId);
/**
* Sets up the initial values for playback control.
*/
void initPlayback();
/**
* Sets up the initial values for audio decoding.
*/
void initAudio();
/**
* Sets up the initial values for video rendering.
*/
void initVideo(const int16 x, const int16 y, const int16 scale, const reg_t plane, const bool hasPalette, const uint16 paletteSize);
/**
* Sets up the robot's data record and cue positions.
*/
void initRecordAndCuePositions();
#pragma mark -
#pragma mark Playback
public:
/**
* Opens a robot file for playback. Newly opened robots are paused by
* default.
*/
void open(const GuiResourceId robotId, const reg_t plane, const int16 priority, const int16 x, const int16 y, const int16 scale);
/**
* Closes the currently open robot file.
*/
void close();
/**
* Pauses the robot. Once paused, the audio for a robot is disabled until
* the end of playback.
*/
void pause();
/**
* Resumes a paused robot.
*/
void resume();
/**
* Moves robot to the specified frame and pauses playback.
*
* @note Called DisplayFrame in SSCI.
*/
void showFrame(const uint16 frameNo, const uint16 newX, const uint16 newY, const uint16 newPriority);
/**
* Retrieves the value associated with the current cue point.
*/
int16 getCue() const;
/**
* Gets the currently displayed frame.
*/
int16 getFrameNo() const;
/**
* Gets the playback status of the player.
*/
RobotStatus getStatus() const;
private:
/**
* The read stream containing raw robot data.
*/
Common::SeekableSubReadStreamEndian *_stream;
/**
* The current status of the player.
*/
RobotStatus _status;
typedef Common::Array<int> PositionList;
/**
* A map of frame numbers to byte offsets within `_stream`.
*/
PositionList _recordPositions;
/**
* The offset of the Robot file within a resource bundle.
*/
int32 _fileOffset;
/**
* A list of cue times that is updated to prevent earlier cue values from
* being given to the game more than once.
*/
mutable int32 _cueTimes[kCueListSize];
/**
* The original list of cue times from the raw Robot data.
*/
int32 _masterCueTimes[kCueListSize];
/**
* The list of values to provide to a game when a cue value is requested.
*/
int32 _cueValues[kCueListSize];
/**
* The current playback frame rate.
*/
int16 _frameRate;
/**
* The nominal playback frame rate.
*/
int16 _normalFrameRate;
/**
* The minimal playback frame rate. Used to correct for AV sync drift when
* the video is more than one frame ahead of the audio.
*/
int16 _minFrameRate;
/**
* The maximum playback frame rate. Used to correct for AV sync drift when
* the video is more than one frame behind the audio.
*/
int16 _maxFrameRate;
/**
* The maximum number of record blocks that can be skipped without causing
* audio to drop out.
*/
int16 _maxSkippablePackets;
/**
* The currently displayed frame number.
*/
int _currentFrameNo;
/**
* The last displayed frame number.
*/
int _previousFrameNo;
/**
* The time, in ticks, when the robot was last started or resumed.
*/
int32 _startTime;
/**
* The first frame displayed when the robot was resumed.
*/
int32 _startFrameNo;
/**
* The last frame displayed when the robot was resumed.
*/
int32 _startingFrameNo;
/**
* Seeks the raw data stream to the record for the given frame number.
*/
bool seekToFrame(const int frameNo);
/**
* Sets the start time and frame of the robot when the robot is started or
* resumed.
*/
void setRobotTime(const int frameNo);
#pragma mark -
#pragma mark Timing
private:
/**
* This class tracks the amount of time it takes for a frame of robot
* animation to be rendered. This information is used by the player to
* speculatively skip rendering of future frames to keep the animation in
* sync with the robot audio.
*/
class DelayTime {
public:
DelayTime(RobotDecoder *decoder);
/**
* Starts performance timing.
*/
void startTiming();
/**
* Ends performance timing.
*/
void endTiming();
/**
* Returns whether or not timing is currently in progress.
*/
bool timingInProgress() const;
/**
* Returns the median time, in ticks, of the currently stored timing
* samples.
*/
int predictedTicks() const;
private:
RobotDecoder *_decoder;
/**
* The start time, in ticks, of the current timing loop. If no loop is
* in progress, the value is 0.
*
* @note This is slightly different than SSCI where the not-timing value
* was -1.
*/
uint32 _startTime;
/**
* A sorted list containing the timing data for the last
* `kDelayListSize` frames, in ticks.
*/
int _delays[kDelayListSize];
/**
* A list of monotonically increasing identifiers used to identify and
* replace the oldest sample in the `_delays` array when finishing the
* next timing operation.
*/
uint _timestamps[kDelayListSize];
/**
* The identifier of the oldest timing.
*/
uint _oldestTimestamp;
/**
* The identifier of the newest timing.
*/
uint _newestTimestamp;
/**
* Sorts the list of timings.
*/
void sortList();
};
/**
* Calculates the next frame number that needs to be rendered, using the
* timing data collected by DelayTime.
*/
uint16 calculateNextFrameNo(const uint32 extraTicks = 0) const;
/**
* Calculates and returns the number of frames that should be rendered in
* `ticks` time, according to the current target frame rate of the robot.
*/
uint32 ticksToFrames(const uint32 ticks) const;
/**
* Gets the current game time, in ticks.
*/
uint32 getTickCount() const;
/**
* The performance timer for the robot.
*/
DelayTime _delayTime;
#pragma mark -
#pragma mark Audio
private:
enum {
/**
* The number of ticks that should elapse between each AV sync check.
*/
kAudioSyncCheckInterval = 5 * 60 /* 5 seconds */
};
/**
* The status of the audio track of a Robot animation.
*/
enum RobotAudioStatus {
kRobotAudioReady = 1,
kRobotAudioStopped = 2,
kRobotAudioPlaying = 3,
kRobotAudioPaused = 4,
kRobotAudioStopping = 5
};
#pragma mark -
#pragma mark Audio - AudioList
private:
/**
* This class manages packetized audio playback for robots.
*/
class AudioList {
public:
AudioList();
/**
* Starts playback of robot audio.
*/
void startAudioNow();
/**
* Stops playback of robot audio, allowing any queued audio to finish
* playing back.
*/
void stopAudio();
/**
* Stops playback of robot audio immediately.
*/
void stopAudioNow();
/**
* Submits as many blocks of audio as possible to the audio engine.
*/
void submitDriverMax();
/**
* Adds a new AudioBlock to the queue.
*
* @param position The absolute position of the audio for the block, in
* compressed bytes.
* @param size The size of the buffer.
* @param buffer A pointer to compressed audio data that will be copied
* into the new AudioBlock.
*/
void addBlock(const int position, const int size, const byte *buffer);
/**
* Immediately stops any active playback and purges all audio data in
* the audio list.
*/
void reset();
/**
* Pauses the robot audio channel in preparation for the first block of
* audio data to be read.
*/
void prepareForPrimer();
/**
* Sets the audio offset which is used to offset the position of audio
* packets sent to the audio stream.
*/
void setAudioOffset(const int offset);
#pragma mark -
#pragma mark Audio - AudioList - AudioBlock
private:
/**
* AudioBlock represents a block of audio from the Robot's audio track.
*/
class AudioBlock {
public:
AudioBlock(const int position, const int size, const byte *const data);
~AudioBlock();
/**
* Submits the block of audio to the audio manager.
* @returns true if the block was fully read, or false if the block
* was not read or only partially read.
*/
bool submit(const int startOffset);
private:
/**
* The absolute position, in compressed bytes, of this audio block's
* audio data in the audio stream.
*/
int _position;
/**
* The compressed size, in bytes, of this audio block's audio data.
*/
int _size;
/**
* A buffer containing raw SOL-compressed audio data.
*/
byte *_data;
};
/**
* The list of compressed audio blocks submitted for playback.
*/
AudioBlock *_blocks[kAudioListSize];
/**
* The number of blocks in `_blocks` that are ready to be submitted.
*/
uint8 _blocksSize;
/**
* The index of the oldest submitted audio block.
*/
uint8 _oldestBlockIndex;
/**
* The index of the newest submitted audio block.
*/
uint8 _newestBlockIndex;
/**
* The offset used when sending packets to the audio stream.
*/
int _startOffset;
/**
* The status of robot audio playback.
*/
RobotAudioStatus _status;
/**
* Frees all audio blocks in the `_blocks` list.
*/
void freeAudioBlocks();
};
/**
* Whether or not this robot animation has
* an audio track.
*/
bool _hasAudio;
/**
* The audio list for the current robot.
*/
AudioList _audioList;
/**
* The size, in bytes, of a block of audio data, excluding the audio block
* header.
*/
uint16 _audioBlockSize;
/**
* The expected size of a block of audio data, in bytes, excluding the audio
* block header.
*/
int16 _expectedAudioBlockSize;
/**
* The number of compressed audio bytes that are needed per frame to fill
* the audio buffer without causing audio to drop out.
*/
int16 _audioRecordInterval;
/**
* If true, primer audio buffers should be filled with silence instead of
* trying to read buffers from the Robot data.
*/
uint16 _primerZeroCompressFlag;
/**
* The size, in bytes, of the primer audio in the Robot, including any extra
* alignment padding.
*/
uint16 _primerReservedSize;
/**
* The combined size, in bytes, of the even and odd primer channels.
*/
int32 _totalPrimerSize;
/**
* The absolute offset of the primer audio data in the robot data stream.
*/
int32 _primerPosition;
/**
* The size, in bytes, of the even primer.
*/
int32 _evenPrimerSize;
/**
* The size, in bytes, of the odd primer.
*/
int32 _oddPrimerSize;
/**
* The absolute position in the audio stream of the first audio packet.
*/
int32 _firstAudioRecordPosition;
/**
* A temporary buffer used to hold one frame of raw (DPCM-compressed) audio
* when reading audio records from the robot stream.
*/
byte *_audioBuffer;
/**
* The next tick count when AV sync should be checked and framerate
* adjustments made, if necessary.
*/
uint32 _checkAudioSyncTime;
/**
* Primes the audio buffer with the first frame of audio data.
*
* @note `primeAudio` was `InitAudio` in SSCI
*/
bool primeAudio(const uint32 startTick);
/**
* Reads primer data from the robot data stream and puts it into the given
* buffers.
*/
bool readPrimerData(byte *outEvenBuffer, byte *outOddBuffer);
/**
* Reads audio data for the given frame number into the given buffer.
*
* @param outAudioPosition The position of the audio, in compressed bytes,
* in the data stream.
* @param outAudioSize The size of the audio data, in compressed bytes.
*/
bool readAudioDataFromRecord(const int frameNo, byte *outBuffer, int &outAudioPosition, int &outAudioSize);
/**
* Submits part of the audio packet of the given frame to the audio list,
* starting `startPosition` bytes into the audio.
*/
bool readPartialAudioRecordAndSubmit(const int startFrame, const int startPosition);
#pragma mark -
#pragma mark Rendering
public:
/**
* Gets the plane used to render the robot.
*/
const reg_t getPlaneId() const {
return _planeId;
}
/**
* Gets the origin of the robot.
*/
Common::Point getPosition() const {
return _position;
}
/**
* Gets the scale of the robot.
*/
int16 getScale() const {
return _scaleInfo.x;
}
/**
* Puts the current dimensions of the robot, in game script coordinates,
* into the given rect, and returns the total number of frames in the robot
* animation.
*/
uint16 getFrameSize(Common::Rect &outRect) const;
/**
* Pumps the robot player for the next frame of video. This is the main
* rendering function.
*/
void doRobot();
/**
* Submits any outstanding audio blocks that should be added to the queue
* before the robot frame becomes visible.
*/
void frameAlmostVisible();
/**
* Evaluates frame drift and makes modifications to the player in order to
* ensure that future frames will arrive on time.
*/
void frameNowVisible();
/**
* Scales a vertically compressed cel to its original uncompressed
* dimensions.
*/
void expandCel(byte *target, const byte* source, const int16 celWidth, const int16 celHeight) const;
int16 getPriority() const;
/**
* Sets the visual priority of the robot.
* @see Plane::_priority
*/
void setPriority(const int16 newPriority);
private:
enum CompressionType {
kCompressionLZS = 0,
kCompressionNone = 2
};
/**
* Describes the state of a Robot video cel.
*/
struct CelHandleInfo {
/**
* The persistence level of Robot cels.
*/
enum CelHandleLifetime {
kNoCel = 0,
kFrameLifetime = 1,
kRobotLifetime = 2
};
/**
* A reg_t pointer to an in-memory bitmap containing the cel.
*/
reg_t bitmapId;
/**
* The lifetime of the cel, either just for this frame or for the entire
* duration of the robot playback.
*/
CelHandleLifetime status;
/**
* The size, in pixels, of the decompressed cel.
*/
int area;
CelHandleInfo() : bitmapId(NULL_REG), status(kNoCel), area(0) {}
};
typedef Common::Array<ScreenItem *> RobotScreenItemList;
typedef Common::Array<CelHandleInfo> CelHandleList;
typedef Common::Array<int> VideoSizeList;
typedef Common::Array<uint> MaxCelAreaList;
typedef Common::Array<reg_t> FixedCelsList;
typedef Common::Array<Common::Point> CelPositionsList;
typedef Common::Array<byte> ScratchMemory;
/**
* Renders a version 5/6 robot frame.
*/
void doVersion5(const bool shouldSubmitAudio = true);
/**
* Creates screen items for a version 5/6 robot.
*/
void createCels5(const byte *rawVideoData, const int16 numCels, const bool usePalette);
/**
* Creates a single screen item for a cel in a version 5/6 robot.
*
* Returns the size, in bytes, of the raw cel data.
*/
uint32 createCel5(const byte *rawVideoData, const int16 screenItemIndex, const bool usePalette);
/**
* Preallocates memory for the next `numCels` cels in the robot data stream.
*/
void preallocateCelMemory(const byte *rawVideoData, const int16 numCels);
/**
* The decompressor for LZS-compressed cels.
*/
DecompressorLZS _decompressor;
/**
* The ID of the robot plane.
*/
reg_t _planeId;
/**
* The origin of the robot animation, in screen coordinates.
*/
Common::Point _position;
/**
* Global scaling applied to the robot.
*/
ScaleInfo _scaleInfo;
/**
* The native resolution of the robot.
*/
int16 _xResolution, _yResolution;
/**
* Whether or not the coordinates read from robot data are high resolution.
*/
bool _isHiRes;
/**
* The maximum number of cels that will be rendered on any given frame in
* this robot. Used for preallocation of cel memory.
*/
int16 _maxCelsPerFrame;
/**
* The maximum areas, in pixels, for each of the fixed cels in the robot.
* Used for preallocation of cel memory.
*/
MaxCelAreaList _maxCelArea;
/**
* The hunk palette to use when rendering the current frame, if the
* `usePalette` flag was set in the robot header.
*/
uint8 *_rawPalette;
/**
* A list of the raw video data sizes, in bytes, for each frame of the
* robot.
*/
VideoSizeList _videoSizes;
/**
* A list of cels that will be present for the entire duration of the robot
* animation.
*/
FixedCelsList _fixedCels;
/**
* A list of handles for each cel in the current frame.
*/
CelHandleList _celHandles;
/**
* Scratch memory used to temporarily store decompressed cel data for
* vertically squashed cels.
*/
ScratchMemory _celDecompressionBuffer;
/**
* The size, in bytes, of the squashed cel decompression buffer.
*/
int _celDecompressionArea;
/**
* If true, the robot just started playing and is awaiting output for the
* first frame.
*/
bool _syncFrame;
/**
* Scratch memory used to store the compressed robot video data for the
* current frame.
*/
ScratchMemory _doVersion5Scratch;
/**
* When set to a non-negative value, forces the next call to doRobot to
* render the given frame number instead of whatever frame would have
* normally been rendered.
*/
mutable int _cueForceShowFrame;
/**
* The plane where the robot animation will be drawn.
*/
Plane *_plane;
/**
* A list of pointers to ScreenItems used by the robot.
*/
RobotScreenItemList _screenItemList;
/**
* The positions of the various screen items in this robot, in screen
* coordinates.
*/
Common::Array<int16> _screenItemX, _screenItemY;
/**
* The raw position values from the cel header for each screen item
* currently on-screen.
*/
Common::Array<int16> _originalScreenItemX, _originalScreenItemY;
/**
* The duration of the current robot, in frames.
*/
uint16 _numFramesTotal;
/**
* The screen priority of the video.
* @see ScreenItem::_priority
*/
int16 _priority;
/**
* The amount of visual vertical compression applied to the current cel. A
* value of 100 means no compression; a value above 100 indicates how much
* the cel needs to be scaled along the y-axis to return to its original
* dimensions.
*/
uint8 _verticalScaleFactor;
};
} // end of namespace Sci
#endif
|