Entries Tagged as 'Technology'

OpenVPN + AWS

A friend put me onto AWS – Amazon Web Services. I had known about it for some time, but had not dabbled in it because I thought it would be too expensive. As of November last year though, Amazon, obviously aware that many people see their web services this way, decided to make a free use tier for basic testing and small production use.

So, I setup OpenVPN on an Ubuntu server micro-instance in the Asia Pacific region of Amazon AWS. I followed the instructions located here to setup the keys and certificates for the server and some local clients. At this stage I am only using the basic tunneling interface, I do plan to try the alternative TAP interface though, which would allow me to bridge my workstation at home with my mobile Internet tablet, the Nokia N900.

I found that using the sample configuration files from the OpenVPN HOWTO was the quickest way to get up and running. Apart from defining the necessary PKI files, These are the things I changed:

  • For the server configuration. set or change the following options
View Code CONFIG
push "redirect-gateway"
push "dhcp-option DNS 172.16.0.23"
  • For the Nokia N900 client configuration, The DNS must be directed away from the internal proxy on the tablet. add this to the end:
View Code CONFIG
script-security 2
up /etc/openvpn/maemo-update-resolvconf
down /etc/openvpn/maemo-update-resolvconf
  • The server must have masquerading enabled as well for the VPN IP range. This involves using the following rules in iptables:
Download rulesfile.txt
*nat
:POSTROUTING ACCEPT [0:0]
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A POSTROUTING -s 10.8.0.0/24 -d 0.0.0.0/0 -o eth0 -j MASQUERADE
COMMIT
  • On Ubuntu 10.10 server, one can use “sudo iptables-apply rulesfile.txt” to apply the above ruleset. You may want to add this to a startup script in /etc/init.d – perhaps the openvpn startup script.

That covers the basics and gets me a faster, more open Internet connection from my mobile 3G tablet in China.

Please note that setting up a micro instance in AWS Console is fairly easy, I suggest you be careful not to choose a large image if you want to remain within the 10Gb free limit of Elastic Block Storage. The firewall will need to be opened up for port 1194 as well as the standard ssh.

Teclast T51 Review

I remember when I first got into Ebay, living in Sydney I used to make the trip past the Post Office maybe once a week to pick up a new package from Hong Kong or the US – mostly camera bits and cheap computer parts that were hard to find in Australia – being a sound buff and a lover of music, I developed a soft spot for alternative music players – no I don’t mean Seattle grunge derivatives, I mean unusual players – the anti-iTthing, with possibly the latest hardware and cool features to discover – and delivery isn’t too much for an MP3/MP4.

The reason I tended toward Teclast, is that they appeared to be on the bleeding edge with features – I bought the C260, which claimed to play AVI files on a 2.4″ screen, it had a TF slot and at the time, put all other players to shame in the small MP3 with a TF slot category. I then bought the T39 – which, despite having no TF slot, had significantly better sound combined with an excellent touch-screen interface and better video support. This was my favourite for a long time. I made AVIs of my newborn daughter to show friends as she learned to crawl.

There is a price to pay for being on the bleeding edge and it appears that Teclast have done well to survive in this market by always releasing a product early and then doing solid follow up to fix any firmware problems that arise – and they were abundant, something many customers cannot abide by. I saw it as a challenge to see if I could make the best of the hardware they provided.

Earlier this year, I bought the M50HD – this player is greatly under rated IMO, for video reproduction, its the best I’ve ever seen due to the amazing 4.3″ 800×480 screen, perfect for converted DVDs and very solid construction – plus it has some cool features like FM transmission and Standard Def Composite output that make it pretty special and flexible – as usual with Teclast, it took a few firmware releases for it to really shine and it can be crashed even now if you try to. I still manually convert videos for the device, despite Teclasts claim that they support many formats, they usually perform badly unless you do this.

I believe the M50HD screen is going to be used in the upcoming T56, which is apparently going to support 1080P output via the HDMI port – after the disaster that was the C500, I am going to have to take a step back and test this one before I lay down any cash for it. The C500 was supposed to support 720P output via its ground-breaking HDMI port in a device this small – unfortunately, the video frame rate for HD movies was unwatchable and even worse through the HDMI port – apparently only if you use RealVideo can you get away with it – I am a Linux person and cannot abide by using this codec, it just makes life difficult for all my Linux automation scripts that make AVIs and MKV perfectly.

I had to return my C500 at a slight loss, because technically, there was nothing wrong with it, it just sucked due to very bad video performance and a terrible quality 5″ 800×480 screen. This was something a firmware could not possibly help, the colour rendition was just appalling – considering how brilliant the M50HD was, this was a Real(tm) let down.

T51 FrontNow comes the T51 – I have always appreciated good sound – I am not the anal type of audiophile though that likes their $500 Denon network cables or anything, but I know what I like and I understand that there is usually a better way to hear music than the best way I know. I remember pleading to get a decent CD Player when I was about 15, so I could really get into building my CD collection – I had a Japanese made Onkyo player that I plugged the headphones straight into and with my 15 year-old ears, I really got a lot out of it. These days, I own a Total Bithead to plug into my PC and listen to MP3s that I made with the lowest compression rate being 192Kbps average now is lame extreme MP3s around 240 Kbps. I listen with a pair of Sennheiser HD650 cans and its very strong and smooth to listen to every day.

T51 BackSo, before I bought the T51, I had just upgraded the cable for my HD650 cans to a custom built one using Kimber signal cable and a Switchcraft plug to go straight into my Total Bithead. The cable added strength to the sound and a sense of clarity that surprised me – The added strength contributed to the whole spectrum, but was very clear on the bass driving the normally laid back Sennheisers into the fast lane.

T51 InsideThen I bought the T51, having read about it as being a breakthrough in portable sound clarity with a claimed S/N ratio of over 110 dB! – it does this by utilising a dual high quality DAC (WM8740 x 2) through dual high quality Burr-Brown Op-Amps (OPA 2604 x 2), using nice caps and providing a high quality line-out that bypasses the average quality Phillips built-in headphone amp.

Listening to this device in combination with my Total Bithead is where its at for me – just stunning. Listening to albums like Queen’s Miracle, I was transported to when I was a keen 15 year-old listener – hearing all I heard before plus more. I have found that the DAC in the Bithead is its first failing – its not a bad DAC, but its actually the warm, very musical Amp in that thing that makes daily listening a pleasure. The T51 is crystal clear and with high rate MP3s or FLACs plugged through the Total Bithead’s lovely warm Headphone Amplifier and then through the HD650 cans – I must admit, I may never find a better portable combo. The bass is strong, maybe a little too strong for some, but I may drop it down a little if need be by using the onboard Phillips headpone output. This amp is ok, but it lets down the rest of the setup, providing a dry slightly dull sound to the near perfect signal coming from those Op-Amps. The only time I use the quite good quality basic eq on the T51, is when I am walking with my Sennheiser PX100 headphones attached.

T51 SidePlug the T51 into your HiFi Amp and be blown away – bypass the eq and suddenly the details of your music shine with accuracy, dropping any signs of a sound envelope and leaving you in an open, total precision sound environment – the high trebles will redefine the way you think about the limits of 16-bit 44.1KHz PCM. Its better than my midrange Onkyo CD Player that weighed around 7 Kg due to the need for a precision power transformer – in the age of Li-Ion batterries, the clean power needed for good sound is now cheap. Teclast have taken modern HiFi sound equipment and made and ideal situation happen, clean power into precision engineered parts with a short signal path to get to the line-out.

Functionally, the track selection is standard and jump to anywhere in audio in video is there, I only listen to compete albums, so it works fine for me, I haven’t tried any clever playlists or anything. The T51 video also supports MKV files and a host of other file types, although I am currently having issues with some high spec’d AVI files. So far, converting to H.264 with AAC in an MKV is the best option.

Its touchcreen has very good color rendition, quite strong, although the DPI and dark pixel surrounds are visible on its 3.5″ 480×320 screen, unlike the M50HD, which had slightly less color strength, but no visible pixels at all – just flat and accurate like analogue film. The touch response is good, but not as good as an iThing – its functional, with constant velocity sliding and fast response.

Currently, the video frame rate is consistent with most files I play like DVDs converted to H264. It is currently not synced properly with the display’s vertical blanking though, so fast motion tends to have diagonal lines through it. This is fixable and hopefully they will get around to it soon.

This device is all about the sound, I tried to construct a FLAC with a CUE sheet for an album, without much success yet – I need to try a little harder before I rule it out, still, I’m in no rush. FLACs are a good way to go anyway, they maybe big, but this maybe one of the few devices where a high rate MP3-FLAC comparison will give you results, I still can’t pick ‘em most of the time, so most of my portable collection is high rate MP3 – I can spot MP3 at around 192 Kbps – with a very slight dull envelope and subtle lack of spatial positioning.

The first one I bought had a problem where it could not recharge – I bought it and had it replaced in Zhong Guan Cun, which is the heart of electronics shopping in Beijing, maybe one of the best electronics districts in the world at present.

This T51 is now with me everyday and I think my holy grail of having near perfect portable sound is met, only the goals of portable perfect video and perfect portable networked Linux remain.

PSP Encode

I have been having loads of fun with my PSP slim. I researched playing video on this device, turns out it supports both standard MPEG4 and thanks to Sony being pretty keen on AVCHD, it now supports AVC MP4 file formats, which gives superior quality playback and/or compression.

Since I live in Australia, I wanted to convert both my widescreen camcorder files to view on my PSP and DVDs as well. We use the PAL standard, at 25 frames per second these files will not play on my Japanese PSP, which can only support NTSC frame rates of 23.976 and 29.97 fps.

So, I have developed a script that uses mencoder and ffmpeg, mainly to convert DVDs with subtitles to the PSP.

Features:

  • Supports creation of full 480×272 screen size AVC MP4 videos.
  • Supports subtitle rendering.
  • Easy to use, just run it a directory with free space, with or without a filename – no filename means access the dvd device.
  • Auto frame rate conversion – will convert 25 fps input to 23.976 by remuxing slower audio (a bit slow, 2 conversions).
  • Auto track selection – at present just converts the longest track only – for movies.
  • Adds a little gamma to the output, PSPs need it for video I think.

Requirements:

  • Linux, prefer Debian or Ubuntu I think, should work on others too.
  • mplayer/mencoder
  • ffmpeg
  • sox

I am using PSP slim firmware 3.71 – if you want to try this script, you probably need something recent like this or newer.

Usage:

Just make a new directory on a location with plenty of space – if it needs to down convert from PAL, this is a slow and space consuming process. It currently takes 2 conversions. It uses threads to speed things up a little, could take a few hours to complete on a 1.8Ghz dual core. So have around 10Gb free when you start.

Change to this new directory.

Now Simply run it with a video file or it will default to using the device /dev/cdrom.

It should give you some feedback about the 2 stages or 1 stage it needs to do.

When its done, you should have a file called output.mp4 in that directory. Maybe a log file or 2. Just move that mp4 to your PSPs video folder, maybe change the filename too. You should be able to watch it now.

Its still very basic, its just designed to work without much brilliant feedback at this stage.

Download pspencode.pl
#!/usr/bin/perl
 
# script to automate the encoding of PSP AVC videos with subtitles
# I release this under GPLv2 you know what that means.
 
#$tvaspect = "scale"; # Plays on PSP in 'full screen' mode and on a 16:9 TV
$tvaspect = "expand"; # Plays on PSP in 'zoom' mode and on a regular 4:3 TV
$profile = 1; # 2 different profiles for AVC MP4, 1=regular and 2=large.
$debug = 0; # 1 for messages and a short test, 2 for no final run
			# 3 for test run with output
#$noise = ",noise=5t";
#$noise = ",noise=3t";
#$noise = "";
$gamma = "eq=4:0";
$slang = "-slang en";
$alang = "-alang zh";
 
$bframe = "i_certify_that_my_video_stream_does_not_use_b_frames:";
#$bframe = "";
 
if ($profile == 1) {
	$tvaspect = "scale";
}
$disaspect = 1.7647; # this is used for calculating the subtitles position.
$embed = 1; # embedd fonts - only appropriate for encoding
$subexp = ":";
$output = "output.mp4";
$ffmpeglog = "ffmpeg.log";
$poll = 2;
$rfsw = 720; # rate frame size - the width of the first stage in 2 stage encode
$rfsh = 544; # rate frame size - the height of the first stage in 2 stage encode
 
sub execdebug {
	my $cmd = join (" ",@_);
	if ($debug > 0) {
		print "$cmd\n";
	}
#	my $out = spacepad(132,$cmd);
	if ($debug < 2) {
		$error =  `sh -c \"$cmd\" 2>&1`;
	}
	if ($debug > 2) {
		system $cmd;
	}
	return $error;
}
 
# first arg is the ref num, rest is command
sub execmencode {
	my $cmd = join (" ",@_[2..$#_]);
	my $out = "";
	if ($debug > 0) {
		print "$cmd\n";
		$out = "| tee stage1.log ";
	}
	if ($debug == 2) {
		return;
	}
	open (FILE,"$cmd 2>&1$out |");
	print "Stage ".@_[0].".\n";
	if ($debug == 3) {
		print;
	}
	$skip = 100;
	$i = 0;
	while (<FILE>) {
		$i++;
		if (($i % $skip) != 1) {
			next;
		}
		if (/^Pos:/) {
#			print;
			@row = split(/\(|\)/,$_);
			if ($complete != @row[1]) {
				$complete = @row[1];
				print "Stage ".@_[0].": $complete\n";
			}
		}
	}
	close FILE;
	print "Stage ".@_[0]." complete.\n";
}
 
sub execffmpeg {
	my $cmd = join (" ",@_[2..$#_]);
	unless ($pid = fork) {
		unless (fork) {
			`sh -c \"$cmd\" > $ffmpeglog 2>&1`;
			exit;
		}
		exit;
	}
	my $found = 0;
	for ($i=0;$i<16;$i++) {
		if (-s $ffmpeglog) {
			$found = 1;
			last;
		}
		sleep 1;
	}
	if ($found == 0) {
		print "Error, cannot find log to analyse.\n";
		exit -1;
	}
	if ($debug > 0) {
		print "$cmd\n";
	}
	if ($debug == 2) {
		return;
	}
	if ($debug == 3) {
		print;
	}
	print "Stage ".@_[0].".\n";
	while (1) {
		$_ = `tail -n 1 $ffmpeglog`;
		if (/^video/) {
			last;
		}
		my @row = split (/frame=/);
		my @info = split (/fps=/,@row[$#row]);
		$complete = (int((@info[0]/@_[1])*100));
		if ($complete != $prev) {
			$prev = $complete;
			print "Stage ".@_[0].": ".$complete."%\n";
		}
		sleep $poll;
	}
}
 
#$hori = 1024;
#$hori = 720;
$hori = 480;
$vert = int(($hori/$disaspect)+0.5);
if ($profile == 1) { 
	$bitrate = 592;
#	$bitrate = 296;
	$hori = 480;
	$vert = 272;
} elsif ($profile == 2) {
	$bitrate = 1184;
#	$bitrate = 592;
	$hori = 720;
	$vert = 480;
}
%titles = ();
 
if (@ARGV[0] eq "") {
	$dev = "/dev/cdrom";
} else {
	$dev = @ARGV[0];
}
my $maxlen = 0;
if (!($dev =~ /\.avi$|\.mpg$|\.mp4$|\.mov$/i)) {
# now extract info about this dvd with a cropdetect call
	$cmd = "mplayer -identify -monitoraspect $disaspect -zoom -vf cropdetect -frames 1 -ss 0:07:00 -vo aa -ao null -dvd-device \"$dev\" dvd://1";
	if ($debug > 0) {
		print "$cmd\n";
	}
	$detect =  `sh -c \"$cmd\" 2>&1`;
 
	my %titles = ();
	my $crop = "";
	my $aspect_internal = 0;
	my $aspect_viewing = 0;
	my $fps = 0;
	@detectdata = split(/\n/,$detect);
	foreach (@detectdata) {
		if (/^ID_DVD_TITLE/) {
			my @row = split(/\=/,$_);
			if (/^ID_DVD_TITLES/) {
				$numtitles = @row[1];
			} else {
				$titles{@row[0]} = @row[1];
			}
		}
	}
# extract title lengths
	my $longest = 1;
	for ($i=1;$i<=$numtitles;$i++) {
		my $len = $titles{"ID_DVD_TITLE_".$i."_LENGTH"};
		if ($len > $maxlen) {
			$maxlen = $len;
			$longest = $i;
		}
	}
	$dev = "-dvd-device \"$dev\" dvd://$longest";
	$cmd = "mplayer -identify -monitoraspect $disaspect -zoom -vf cropdetect -frames 40 -ss 0:05:00 -vo aa -ao null $dev";
} else {
	$fileflag = 1;
	my @row = split (/\./,$dev);
	if (@row[1] ne "mp4") {
		$output = "\"".@row[0].".mp4\"";
	}
	$dev = "\"$dev\"";
	if ($dev eq $output) {
		$output =~ s/\.mp4/_PSP\.mp4/;
	}
	$cmd = "mplayer -identify -monitoraspect $disaspect -zoom -vf cropdetect -frames 40 -ss 0:05:30 -vo aa -ao null $dev";
}
 
if ($debug > 0) {
	print "$cmd\n";
}
$detect =  `sh -c \"$cmd\" 2>&1`;
 
@detectdata = split(/\n/,$detect);
foreach (@detectdata) {
	if (/CROP/) {
		my @rowa = split(/\-vf\ crop\=/);
		my @rowb = split(/\)/,@rowa[1]);
		$crop = @rowb[0];
	}
	if (/^VO:/) {
		my @rowa = split(/\s+/,$_);
		my @rowb = split(/x/,@rowa[2]);
		if (@rowb[1] > 0) {
			$aspect_internal = @rowb[0]/@rowb[1];
		}
	}
	if (/^ID_VIDEO_ASPECT/) {
		my @row = split(/\=/,$_);
		$aspect_viewing = @row[1];
	}
	if (/^ID_VIDEO_FPS/) {
		my @row = split(/\=/,$_);
		$fps = @row[1]*1;
	}
	if (/^number\ of\ subtitles/) {
		my @row = split(/\s+/,$_);
		$subs = @row[$#row];
		if (($subs > 0)&&($slang ne "")) {
			$subexp = "0:0";
		}
	}
}
my $frames = int($maxlen*$fps);
 
if ($aspect_internal == 0) {
	print "could not determine the aspects for this DVD,\n";
	exit -1;
}
my @row = split (/\:/,$crop);
my $aspect_crop = (@row[0]/@row[1]);
my $aspect_original = ($aspect_crop*$aspect_viewing)/$aspect_internal;
$subpos = int(105-230*((1.96-$disaspect)*(1.01-((1+((1.78-$disaspect)/1.6))*($disaspect/$aspect_original))))); # this is a magic equation I just made up to help position subtitles
if ($debug > 0 ){
	print "ao: $aspect_original\n";
}
if ($subpos < 81) {
	$subpos = 81;
}
if ($subpos > 99) {
	$subpos = 99;
}
 
my $mencode = "";
if ($fps == 25) {
	$mencode = "mencoder -sws 9 -vf $gamma -noautoexpand -oac pcm -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=7800:keyint=250:threads=2 $slang $alang -af volnorm=1:.19 -sub-bg-alpha 255 -spualign 2 -spuaa 4 -spugauss 1.0 -ffactor 10 -o test.avi";
} else {
	$mencode = "mencoder -sws 9 $slang $alang -af volnorm=2:.19,channels=2 -ofps 24000/1001 -vf $gamma$noise -oac faac -faacopts br=192:mpeg=4:object=2:raw -ovc x264 -x264encopts bitrate=$bitrate:global_header:partitions=all:trellis=1:vbv_maxrate=".int(($bitrate*1.2973)+.5).":vbv_bufsize=2000:level_idc=30:threads=auto -of lavf -lavfopts $bframe"."format=psp -sub-bg-alpha 255 -spualign 2 -spuaa 4 -spugauss 0.2 -ffactor 10 -o $output";
}
$len = $maxlen;
if ($debug > 0) {
	$len = 60;
	$mencodelen = " -ss 00:25:00 -endpos $len";
} else {
	$mencodelen = " -endpos $len";
}
$frames = int($fps*$len);
my $finlen = " -endpos $len";
if ($fps == 25) {
	$finlen = " -endpos ".((($len*25000)/23976));
}
if ($fileflag > 0) {
	$finlen = "";
	$frames = 1;
} else {
	$mencode .= $mencodelen;
}
 
my $pull = "";
my $cropfilt = "";
if ($fps == 25) {
	if ($aspect_original > $disaspect) {
		$scalex = $rfsw;
		$scaley = int(($rfsw/$disaspect)+.5);
		$cropfilt = "-vf-add scale=$scalex:".(int((($scalex/$aspect_original)+.5)/2)*2).",expand=$scalex:$scaley:$subexp:$embed";
 
	} else {
		$scalex = int(($rfsh/$disaspect)+.5);
		$scaley = $rfsh;
		$cropfilt = "-vf-add scale=".(int(($scaley*$aspect_original)+.5)).":$scaley,expand=$scalex:$scaley:$subexp:$embed";
	}
} else {
	if ($aspect_original > $disaspect) {
#		$cropfilt = "-vf-add scale=$hori:-2,expand=$hori"."::$subexp:$embed:$disaspect,expand=$hori:$vert";
		$cropfilt = "-vf-add scale=$hori:-2,expand=$hori:$vert:$subexp:$embed";
	} else {
#		$cropfilt = "-vf-add scale=-2:$vert,expand=:$vert:$subexp:$embed:$disaspect,expand=$hori:$vert";
		$cropfilt = "-vf-add scale=-2:$vert,expand=$hori:$vert:$subexp:$embed";
	}
}
 
if ($fps == 29.97) {
	$pull = "-vf-pre filmdint";
}
#if ($aspect_original > $disaspect) {
#	$cropfilt = "-vf-add scale=$hori:-2,expand=$hori:$vert:$subexp:$embed";
#}
 
$cmd = "$mencode -subpos $subpos $cropfilt -vf-pre crop=$crop -vf-pre harddup $pull $dev -msglevel all=9";
execdebug "rm -f test.avi";
execmencode (1,$frames,$cmd);
if ($fps == 25) {
	execdebug "rm -f track.wav";
	execdebug "mplayer -vc null -vo null -ao pcm:fast:file=track.wav test.avi";
	execdebug "rm -f track.raw";
	execdebug "sox track.wav -r 50050 track.raw resample -qs";
	execdebug "rm -f track.wav";
	execdebug "sox -r 48000 -s -w -c 2 track.raw track.wav";
	execdebug "rm -f track.raw";
	execdebug "rm -f $output";
	execdebug "rm -f $ffmpeglog";
#	system "ffmpeg -i test.avi -vcodec rawvideo -pix_fmt yuv420p -f rawvideo - | mencoder -sws 9 -af volnorm=2:.19 -vf harddup,$tvaspect=$scalex:$scaley -aspect $disaspect -oac faac -faacopts br=192:mpeg=4:object=2:raw -ovc x264 -x264encopts bitrate=$bitrate:global_header:partitions=all:trellis=1:vbv_maxrate=".int(($bitrate*1.2973)+.5).":vbv_bufsize=2000:level_idc=30:threads=auto -of lavf -lavfopts i_certify_that_my_video_stream_does_not_use_b_frames:format=psp $limit -o $output -audiofile track.wav -demuxer rawvideo -rawvideo fps=23.976:i420:w=$hori:h=$vert -";
	sleep 1;
	my $out = " > /dev/null";
	if ($debug > 0) {
		$out = " > stage2.log";
	}
	execffmpeg (2,$frames,"ffmpeg -i test.avi -v 9 -vcodec rawvideo -pix_fmt yuv420p -f rawvideo - | mencoder -sws 9 -af volnorm=2:.19 -vf harddup$noise,$tvaspect=$hori:$vert -aspect $disaspect -oac faac -faacopts br=192:mpeg=4:object=2:raw -ovc x264 -x264encopts bitrate=$bitrate:global_header:partitions=all:trellis=1:vbv_maxrate=".int(($bitrate*1.2973)+.5).":vbv_bufsize=2000:level_idc=30:threads=auto -of lavf -lavfopts $bframe"."format=psp$finlen $limit -o $output -audiofile track.wav -demuxer rawvideo -rawvideo fps=23.976:i420:w=$scalex:h=$scaley -$out 2>&1");
	if ($debug == 0) {
		execdebug "rm -f track.wav";
		execdebug "rm -f test.avi";
	}
	execdebug "rm -f $ffmpeglog";
}
 
waitpid($pid,0);
print "complete!\n";

Teclast T39

This is the 2nd. Teclast device I own. I have had the T39 for nearly a year I think, prior to that I had a Teclast C260 which ended up bricked for some time – after buying the T39, I actually managed to fix the C260 by following some un-bricking guidelines from the interweb.

The T39 has been the best MP3 player I’ve ever owned and also a great MP4 player as well. I started with MP3 back at the turn of the millenium [Read more →]