Psygnosis SMV

From MultimediaWiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

SMV is a short-lived FMV format used in the PC game Wipeout by Psygnosis. It employs LZ-like compression to encode 8-bit palettized video and audio.

File Format

An SMV file consist of a number of chunks with unique TwoCC. All numbers are little-endian.

 u16 chunkid
 u16 chunklen
 u8  payload[chunklen]

ST chunk

File header. Encodes basic movie properties.

 u16 width           -- movie width
 u16 height          --  and height
 u16 mbwidth         -- macroblock width
 u16 mbheight        --  and height
 u16 frames          -- number of video frames
 u16 colors          -- number of used colors
 u16 nibbles         -- offset of frame's nibble data
 -- if chunk size is >= 15
 u8  codec           -- video codec format
  • Codec field may be absent, in such case assume codec 0.
  • mbwidth is always 16, mbheight may be either 16 or 8.
  • Movie playback rate is 12 FPS.

GP chunk

New palette. Contains a number of 6-bit RGB entries, as specified in ST chunk.

MU chunk

Sound chunk. Contains audio fragment in 15862Hz, 8-bit, unsigned, mono format. If the payload size is equal to the amount of audio data for a frame (i.e. the size is freq / fps, which is equal to 1321) then the audio data is stored in raw PCM format. Otherwise, it's additionally LZ-word-packed.

FR chunk

Frame chunk. Contains encoded video frame.

FE chunk

End of the movie. No payload.

Video Compression

Video Codec 0

LZUnpack(payload, temp, words)
DrawBlocks(temp)

Video Codec 1

LZUnpack(payload, temp, bytes)
DrawBlocks(temp)

Video Codec 2

LZUnpack(payload, temp, bytes)
UnpackPartitions(temp, temp2)
DrawBlocks(temp2)

UnpackPartitions decodes Macroblock's nibbles.

Source frame is broken on to several equal partitions, 2 across by 4 down. Encoded partitions format:

 u8  pixels[16 * num_mblocks]          -- passed as is to DrawBlocks()
 for each partition
   u8  codebook[256][2]                -- nibbles codebook for each partition
 for each partition
   for each macroblock
     u8  index[mb_width*mb_height/4]   -- index of codebook entry

Each entry of codebook encodes 4 nibbles. First byte encodes two nibbles of upper macroblock's line, second - next line, and so on.

Video Codec 3

This codec is never used, but the code is still in place.

if GetByte()
  LZUnpack(payload, temp, bytes)
  UnpackSpecial(temp, temp2)
  DrawBlocks(temp2)
else
  same as codec 1

UnpackSpecial performs advanced decompression. TODO this may be some common algorithm

Macroblocks drawing

Macroblocks are stored in the following format:

 u8 pixels[16 * num_mblocks]
 u4 nibbles[num_mblocks]

Size of pixels[] is equal to ST chunk's nibbles. For each macroblock draw it by indexing its pixels by nibbles. First nibble stored in top 4 bits of byte.

Common Algorithms

LZ decompression

This is a more-or-less common LZ77-like algorithm. It can operate in two modes: byte mode and words mode. Mode affects backchain offset encoding.

  • "next bit" means next bit from the internal 8-bit bitreader queue. Queue refilled with the input stream bytes, bits are consumed starting from the highest.
  • "next byte" means raw octet from the input stream
 repeat

   if next bit == 0
     output next byte
   else
     if next bit == 0
       offset = next byte
     else
       if byte mode
         offset = next 3 bits || next byte  + 1
       if words mode
         offset = next 2 bits || next byte  + 1  *2

     if next bit == 0        length = 2
     else if next bit == 0   length = 3 + next bit
     else if next bit == 0   length = 5 + next bit
     else if next bit == 0   length = 7
     else if next byte != 0  length = 7 + this byte
     else finish unpacking

     copy length bytes from output - offset to output
      note that, bytes may overlap to repeat itself