DivIDEo: Streaming video for the ZX Spectrum

Matt "Gasman" Westcott (@westdotcodottt / matt@west.co.tt)

DivIDEo is a video converter and player system for the ZX Spectrum 128K with DivIDE interface, allowing video to be streamed directly from an IDE device.


See it in action

To watch the demonstration video (first presented at the Outline 2010 demo party) on an emulator:

On the real hardware:

The converter

This is a command-line app - after unpacking it somewhere suitable, start converting videos with:

divideo myvideo.avi myvideo.dvo

See divideo --help for more options to tweak the output.

To build from source, you'll need a recent Subversion snapshot of FFMPEG (I'm using the 2010-04-11 one), Imagemagick, and Argtable 2.x. You'll probably also need to hack the Makefile to specify the paths where you've installed things (sorry...)


zxbruno, nuggetreggae, natxcross, brownb2, Yerzmyey, Zilog, Factor6, LaesQ, Phoenix, Baze, Garry Lancaster, LCD, Velesoft, Hood and Speccy IDE / video enthusiasts everywhere...

The gory details

Player source code - as ZIP file / as text

The player, and the .dvo file format, are designed around minimising the work that needs to be done on the Spectrum side. There is no buffering going on (other than what you get for free from the 128K's second screen) - all data is arranged so that the Spectrum will receive it at exactly the required time, and at exactly the required rate. As a result, the player and file format are best understood as a single entity; plenty of odd design decisions have been made in the interest of optimising away a few Z80 cycles. (Rest assured that it was even more confusing for me to design in the first place :-) )

Player pseudocode

	let L = number of sectors in first frame
	if L = 0, exit
	Issue an IDE request for the next L sectors
	Wait for retrace (i.e. a HALT instruction)
	Flip screens so that old screen is displayed and new screen is paged in at 0xc000
	0) Jump to NEXT_FRAME
	2) Execute INI (i.e. read byte from disk to address HL, advance HL)
	4) Execute INI
	6) Execute INI
	8) Execute INI
	252) Execute INI
MAINLOOP (and table offset 254):
	Wait for IDE 'ready' state
	Read bytes L, H, A from disk
	Read byte from disk and output to AY register 8 (channel A volume)
	Jump to offset A in TABLE

File format

The .dvo file begins with a 512-byte (one sector) header:

0x000x08Magic number - the ASCII string "DivIDEog"
0x080x01Version number - must be 0x01. (These first two fields together are used to identify the start of a .dvo file when scanning a disk sector-by-sector)
0x090x01Size of first frame, in sectors
0x0a0x01Border colour - 0x00-0x07
0x0b0x15Empty (set to 0x00)
0x200x20Video name - padded with 0x00 if less than 0x20 bytes
0x400x1c0Empty (set to 0x00)

The remainder of the .dvo file is a sequence of frames, each one consisting of an arbitrary number of video packets followed by one end marker packet. A video packet contains a sequence of zero or more bytes to update screen memory with at a specified address, plus a single audio sample.

Note that as double buffering is in use, the video data in each frame needs to encompass all the changes to screen memory since two frames ago. (For the purposes of the first two frames - the initial screen state is entirely 0x00 bytes.)

Format of a video packet:

0x000x02Screen address (little-endian) to copy video bytes to, with 0xc000 being the start of screen memory
0x020x01The value 254-(len * 2), where len is the length of the video data (which can be 0). (This is used as an offset into the table of INI instructions, so that the required number of them are executed)
0x030x01Sample level (0x00-0x0f) to output to the AY, plus 0x80. (Adding 0x80 allows us to output to the AY with an OUT (0xfd),A instruction, freeing up the C register for DivIDE data reading; incomplete port decoding means that this will still be seen as a write to the AY's data port, and the AY will ignore the high bit. It also ensures that when we come to the next IN instruction, A is in the range 0x80-0xbf which avoids a contended port read. Phew!)
0x04...Video bytes to be written to screen

Format of an end marker packet:

0x000x01Size of next frame, in sectors, or 0x00 to indicate end of video
0x010x01Unused (set to 0x00)
0x020x010x00 (used as an offset into the INI table, which handily begins with a jump to the 'next frame' routine)
0x030x01Sample level (0x00-0x0f) to output to the AY, plus 0x80

- gasman 2010-05-01