The PXL-2000 was a kids' camcorder from the late 1980's. I had one that I received as a Christmas present in 1987 and I played with it frequently.
Sitting in my closet for many years, up until 2005, were many cassette tapes including the PXL-2000 tapes. The tapes were high bias chromium dioxide tapes (because they needed a very high frequency response in order to retain the video signal) which had deteriorated over the 15 to 17 years in which they had been in storage.
In 2005, I recorded the raw signals from the tapes to my computer. I no longer had the camera but a regular audio cassette tape deck can play back the signal from the tapes.
The camera's tape speed was around 9 times faster than the normal tape deck speed, which helped tremendously in getting the full signal resolution over to the computer. My sound card at the time was capable of a 92kHz sample rate, so I used that when digitizing the tapes.
Tonight, I finally completed this project. I had tried using Photoshop as a quick and dirty way to view the tape signal (I have an article about this on the main site: user link on www.nitrocosm.com) but the horizontal sync was non-existent. Tonight, however, I used PHP (not the most suitable for the project, perhaps, but it's the programming language with which I have the proficiency) to create a decoder.
I had tried using this program in Linux: user link on sevkeifert.blogspot.com which was written in Java and is pretty amazing. The person who made it is rather brilliant and had many insights into the signal format that I had also found in 2005.
Some of my notes (which were developed independently but closely match Mr. Seifert's findings):
Working with a 192000 kHz sample rate:
1125 samples per line (~170 per second, .006 seconds per line)
16kHz tone for 2250 - 2284 samples (0.012 seconds) - the length of two vertical lines of image
92 lines of video per frame (not including the 16kHz sync tone?)
Each line starts with 30 samples of a "sync mark" (higher amplitude + then -)
This is part of the 1125 samples for each line.
The video resolution is 120x90 pixels with two additional lines of video occupied by the 16 kHz sync tone. The frame rate is 15 frames per second.
After analyzing the signal very carefully in an audio editor, I was not only able to figure out the exact format but I also created, in Photoshop, a mock-up tape signal that also worked perfectly through Mr. Seifert's decoder and also, later on, my PHP-based solution.
The image in this post is one of the frames my decoder extracted. I did not use the first derivative of the signal but, rather, the raw amplitude modulation signal.
Below is a copy of the code for my solution.
<?php error_reporting(0); function readAmp($fd,$pos=0){ $v = ord($fd{$pos}); // $v = ($v < 127) ? 127 - $v : $v; // Solarize return $v; } function drawImg($lines,$filename=false){ global $finalw, $finalh; $h = count($lines); $w = count($lines[0]); $img = imagecreatetruecolor($w,$h); foreach($lines as $y => $line){ foreach($line as $x => $l){ $l = ($l > 255) ? 255 : $l; $l = ($l < 0) ? 0 : $l; $l = intval($l); $c = imagecolorallocate($img,$l,$l,$l); imagesetpixel($img,$x,$y,$c); } } $newimg = imagecreatetruecolor($finalw,$finalh); imagecopyresampled($newimg,$img,0,0,0,0,$finalw,$finalh,$w,$h); if(!empty($filename)){ imagepng($newimg,$filename); } else { header('Content-type: image/png'); imagepng($newimg); } imagedestroy($img); imagedestroy($newimg); } function scaleLine($d,$w){ if(empty($d)) return false; $r = ceil($w / count($d)); $line = Array(); if($r > 1){ // If data is smaller than desired line width (scale up) for($i=0;$i<$w;$i++){ for($k=0;($k<$r && $k<$w);$k++){ if(isset($d[$i])) $line[] = $d[$i]; } } } else { // If data is larger than desired line width (scale down) for($i=0;$i<$w;$i++){ if(isset($d[$i])) $line[] = $d[$i]; // TODO: Do scaling later. } } return $line; } function scaleLineExact($d,$w){ if(empty($d)) return false; $ow = count($d); $img = imagecreatetruecolor($ow,1); for($i=0;$i<$ow;$i++){ $l = $d[$i]; $l = ($l > 255) ? 255 : $l; $l = ($l < 0) ? 0 : $l; $l = intval($l); $c = imagecolorallocate($img,$l,$l,$l); imagesetpixel($img,$i,0,$c); } $d = Array(); // Reset pixel data $newimg = imagecreatetruecolor($w,1); imagecopyresampled($newimg,$img,0,0,0,0,$w,1,$ow,1); imagedestroy($img); for($i=0;$i<$w;$i++){ $c = imagecolorat($newimg,$i,0); $d[] = ($c & 0xFF); // Just use blue channel since all channels are equal values. } return $d; } $finalw = 640; $finalh = 480; $fh = fopen('./pxl2keqd.raw','r'); $fl = 106320; $fpos = 0; $fo = 0; $line_limit = 96; // Signal vertical resolution $threshold = 10; $min_line_length = 500; $max_line_length = 1200; while($fd = fread($fh,$fl)){ $lines = Array(); $line = Array(); $cpos = 0; for($i=0;$i<$fl;$i++){ $p = readAmp($fd,$i); $fpos++; $cpos++; $delta = ($i > 0) ? abs($p - readAmp($fd,$i-1)) : 0; if(($delta < $threshold) && (count($line) < $max_line_length)){ $line[] = $p; } else if(count($line) > $min_line_length){ $lines[] = scaleLineExact($line,$max_line_length); $line = Array(); if(count($lines) >= $line_limit){ $fpos = $fpos - ($fl - $cpos); fseek($fh,$fpos); break; } } } $filename = 'output/'.sprintf("%02d",$fo).'.png'; drawImg($lines,$filename); $fo++; } ?>