Psygnosis SMV

From MultimediaWiki
Jump to navigation Jump to search

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