...making Linux just a little more fun! |
By Yan-Fa Li |
Obviously I'm not the only one. There are now some commercial offerings like this, and quite a few people appear to have done projects to timeshift radio. There's even a how-to, and slashdot had a big thread about it recently.
These notes are all based on using RedHat Linux 7.3, so your mileage may vary if using something like SuSe or Mandrake. I believe they already come with Alsa, for example, so you can skip those parts that involve installing them if your system already comes pre-installed with it.
The basic capture system ended up looking like this:
Alsa HW Interface -> [ ecasound ] -> < Wav Stream > -> [ lame encoder ] -> < mp3 >Nice and simple. It just requires a little syntactic sugar to hold it all together. Since I wanted to encode VBR mp3s on the fly I had a few worries about CPU usage. I also planned installing it on my main file server since it's always on and therefore an ideal candidate. Using an 850MHz celeron, my tests showed the load to be about 40-50% while capturing and encoding. This still left plenty of CPU for other tasks like serving files, ssh and http.
Your mileage will of course vary if you're running X for example, which notoriously causes skipping with sound cards. However, since my system is a dedicated file server I've long stopped running a GUI on it. It never became an issue, but keep it in mind for your target system.
Check out compgeeks or CSO for low end systems which would be suitable for this duty.
The next problem is figuring out what to use to do the actual recording. After looking at a variety of solutions such as sox and alsarecord, I settled on using Ecasound. While it's probably overkill for this project, some of the features I liked were the built-in audio conversion routines, the ability to specify realtime scheduling under Linux if run by root, and support for writing data to stdout. Audio is really a real-time sort of task with hard requirements on meeting certain scheduling goals, so being able to specify this was a big plus for Ecasound. Unix pipes also avoids the creation of large temporary data files keeping disk requirements down to manageable levels.
I ended up using a Cirrus Logic 46XX (Hercules Fortissmo) series card. I picked one these up in the sfbay area for ~35USD at retail. I'm pretty sure they're not much more than that elsewhere. Alsa has pretty good support for them and for FM recording they work just fine. I had started out with a CMI8738, an even cheaper card at around ~20USD, but I could not make it record audio without a horrible whine and very poor input gain. It was just fine for playback, but pretty much useless as a recording device.
Detailed instructions for setting up Alsa are on their website listed
card by card. But here are a few notes before you start. Firstly, Alsa
really needs the kernel source you are compiling against and your running
kernel to be the same. So for example, on a typical RedHat installation,
say 2.4.18-26.7, you would need the same kernel sources installed in
your /usr/src/linux-2.4/
directory. By default, this is
not the case, because RedHat specifically renames the kernel sources to
use the extra version string "custom".
To avoid this problem just re-compile, re-install and boot your new redhat kernel before attempting to install Alsa. If you don't know how to do this there are lot of good instructions on the net and I suggest you look some up before proceeding. If you want the default RedHat options, simply use something like:
cd /usr/src/linux-2.4 make mrproper && \ make oldconfig && \ make dep clean bzImage modules MAKE='make -j2'This will rebuild your kernel sources. You will need to install this and reboot using it. How you do that depends on whether you're using lilo or grub, and is beyond the scope of these instructions.
tar jxvf alsa-driver-0.9.5.tar.bz2 alsa-driver-0.9.5/include/linux/workqueue.hworks for the 0.9.5 release. This will prevent the dreaded complaints about being unable to install modules due to failed dependencies.
As an additional note, by default, recent versions of Alsa install themselves in the default system paths. Older versions installed themselves in /usr/local, and on RedHat systems this would cause problems because the dynamic loader wasn't configured to look there for libraries. This would cause configure scripts to fail to find libraries and generally not compile. This is actually pretty easy to fix, just add the /usr/local/lib path to /etc/ld.so.conf and re-run ldconfig. This will update your ld cache and also dynamic libs that have been installed there to run. FYI, this is also one of the main reasons why a lot of open source packages fail to compile on RedHat systems.
The new drivers will install in your currently running kernel directory. You will need to reconfigure your modules.conf to reflect the new sound card set up. This normally involves removing the entries added by kudzu, or disabling them using a '#' sign and then adding the new driver entries. Here's the one from my CS46XX setup:
# ALSA portion alias char-major-116 snd cards_limit=1 device_mode=0660 post-install snd alsactl restore alias snd-card-0 snd-cs46xx # module options # OSS/Free portion alias char-major-14 soundcore alias sound-slot-0 snd-card-0 # card #1 alias sound-service-0-0 snd-mixer-oss alias sound-service-0-1 snd-seq-oss alias sound-service-0-3 snd-pcm-oss alias sound-service-0-8 snd-seq-oss alias sound-service-0-12 snd-pcm-ossNotice the post-install directive. This lets you restore audio settings on reboots as soon as the driver loads. You can also achieve this by modifying the
/etc/rc.local
too, but I like this way better
in case I need to unload the driver. You can also add a pre-remove
directive if you like to save any settings you may have changed before
unloading the sound modules. I prefer to restore to known defaults.
Next we need to add an entry to the rc.local
file. For
whatever reason, the OSS emulation drivers don't load automatically. KDE
complains for example when starting artsd because the sound system hasn't
initialized while it's trying to load. You can force OSS emulation to
pre-load by adding:
modprobe snd-pcm-oss setpci -s 01:09 latency_timer=60to the end of rc.local. The first entry loads the pcm oss driver and keeps apps which depend on OSS being there to stay none the wiser. The second entry adjusts the PCI timers for the sound card to give it a little more time on the bus; that part is optional. I find tweaking the PCI bus helps avoid pops and clicks in the audio. If you do choose to tweak your PCI latency this way, remember to use lspci to find the correct device number for your card. The one listed here is for my system bus and will likely be different on your system.
It's probably easiest at this point just to just reboot one more time,
however if you're confident about what you're doing, manually remove
the old OSS audio drivers using rmmod
and do a
modprobe snd-pcm-ossFollow up with a
lsmod
and you should see a lot of bright
and shiny new alsa drivers loaded:
Module Size Used by Not tainted snd-pcm-oss 45668 0 snd-mixer-oss 16536 0 [snd-pcm-oss] snd-cs46xx 79156 0 (autoclean) snd-rawmidi 18656 0 (autoclean) [snd-cs46xx] snd-seq-device 6316 0 (autoclean) [snd-rawmidi] snd-ac97-codec 44640 0 (autoclean) [snd-cs46xx] snd-pcm 83264 0 (autoclean) [snd-pcm-oss snd-cs46xx] snd-timer 19560 0 (autoclean) [snd-pcm] snd-page-alloc 8520 0 (autoclean) [snd-cs46xx snd-pcm] gameport 3412 0 (autoclean) [snd-cs46xx] snd 43140 0 [snd-pcm-oss snd-mixer-oss snd-cs46xx snd-rawmidi snd-seq-device snd-ac97-codec snd-pcm snd-timer] soundcore 6532 6 [snd]This is an excerpt from a working system. Test it by playing some audio, anything you have handy will do, like mpg123 for example. Though on a RedHat system, you'll actually have to download and compile and install it yourself since they no longer ship mpeg decoders due to patent issues with Fraunhofer and Philips.
If you want a slightly more optimized build, and you're using a version of GCC that supports more advanced x86 optimizations, (gcc 3.2 or higher), I would recommend the following configure line on an PII and above system. This especially includes the new FPGA2 Celerons.
CXXFLAGS='-O2 -march=i686' CFLAGS='-O2 -march=i686' ./configureOr the even more aggressive:
CXXFLAGS='-O2 -march=i686 -msse -mmmx' CFLAGS='-O2 -march=i686 -mmmx -msse' ./configureHowever, be warned this second version may not compile correctly and could cause more problems than it's worth. On the other hand, it probably wouldn't hurt to at least give it a try and see if it makes a difference on your system. In my particular case I see gains in the 2-4% range with these optimizations.
alsamixer
tool. It's a curses based
program that lets you adjust sliders for various audio devices and
lets you take a trial and error approach to your particular sound card.
The basic trick is to put the devices you are interested in capturing
from, into capture mode. If anyone has better information on how to
configure this using a command line interface, please drop me a line.
If you go into the alsamixer
interface you'll see a group of
sliders that have values from 0 to 100. Bars which have 6 hyphens above
them are potential capture sources. Because each sound card appears to
have slightly different DACs it's not always clear which ones to activate
to enable recording.
Using a combination of trial and error and ecasound
, I was
able to test which devices were capable of recording. Having a pair
of speakers hooked up to the speaker out at this point is very useful,
but headphones would be just as useful at this point too. Typically you
want line-in device. Crank up the volume to around 70%. If you're using
a Video 4 Linux radio you can use the 'radio' util to tune in and turn
on the radio device giving you a source of audio. The Dlink radio is a
line device, meaning it has fixed volume so how loud it sounds will be
directly related to how loud you set the line-in volume.
If you've hooked it up correctly to the line-in of your sound card you should now hear some audio. Adjust the volume until it doesn't sound distorted. Distorted audio will a) sound horrible and b) make your mp3s sound really crappy. Just play it by ear (sic), and get it to where it sounds reasonably clear and undistorted. Remember it's FM so it's already lost about 5KHz of fidelity from being converted, so don't expect miracles. You may need to look for sources of noise and reposition your antenna. This will vary from installation to installation.
#!/bin/bash echo "Recordshow2 (c)2003 Yan-Fa Li ([email protected]) under GNU LGPL" # FREQUENCY TIMEINMINS "PROGRAM NAME" #set -x tune_channel() { echo -n "Tuning to FM Channel $1..." # Reset and Turn on and Tune Radio $RADIO -qm 2>/dev/null && sleep 1 && $RADIO -qf $1 } record_program() { echo "Recording $TITLE for $1 Minutes ($TIME seconds) to:" echo -e "\t$FILENAME" # Record and Pipe to Lame TITLE2=${TITLE#*/} $ARECORD $APARMS | $LAME $LPARMS - "$FILENAME" \ --tt "$TITLE2 on $DATE" \ --ta "KQED/NPR" \ --ty `date +"%Y"` \ --tg 101 \ --tc "$COMMENT" if [ $? -ne 0 ] then echo -n "Error Recording - Check the Soundcard Isn't Recording" echo " Already" turn_radio_off exit 1 fi } fix_permissions() { # Correct Permissions if [ -f "$FILENAME" ] then chown $OWNER "$FILENAME" chmod 664 "$FILENAME" fi } turn_radio_off() { echo -n "Turning off Radio..." # Turn off Radio $RADIO -qm } # # Main Program # # Arg Check if [ $# -ne 3 ] then echo "usage: `basename $0` FREQUENCY TIME_IN_MINS \"NAME_OF_PROGRAM\"" exit -1 fi DEST=/mnt/music/radio declare -i TIME=$2 TIME=TIME*60 OWNER="yan:music" RADIO=/usr/bin/radio ARECORD=/usr/local/bin/ecasound APARMS="-b 512 -i alsahw,default -o:stdout -t $TIME" LAME=/usr/local/bin/lame LPARMS="-r -x -mj -s44.1" # required for ecasound # -r raw pcm input # -x swap bytes # -mj join stereo # -s incoming sample rate LPARMS=$LPARMS" -V5 --vbr-new -q0 -b112 --lowpass 15 --cwlimit 10" # Thanks to: http://www.jthz.com/mp3/ for the settings # -V5 encoding speed # --vbr-new # -q0 highest quality # -b112 bitrate of 112Kbps # --lowpass 15 filter all frequencies above 15KHz (FM cutoff) # --cwlimit 10 acoustic model DATE=`date +"%a %b %d %Y (%k:%M)"` SIMPLEDATE=`date +"%Y-%m-%d-%a"` FILENAME="$DEST/$3-$SIMPLEDATE.mp3" TITLE="$3" COMMENT="$1 MHz" echo "`basename $0`: Recording Started on "$DATE # Call it twice to avoid radio coming up mute tune_channel $1 tune_channel $1 record_program $2 fix_permissions turn_radio_off echo "`basename $0`: Recording Ended on "`date +"%a %b %e, %Y %k:%M"`
# usage: recordshow2 FREQUENCY TIME_IN_MINS "NAME_OF_PROGRAM" # leave a minute at the end otherwise you'll overlap the audio # device and fail to record # # Weekdays SHELL=/bin/bash PATH=/sbin:/bin:/usr/sbin:/usr/bin:/etc/radio MAILTO=root HOME=/ # Daily 0 9 * * Mon-Fri recordshow2 88.5 59 "Forum/Forum H1" 0 10 * * Mon-Fri recordshow2 88.5 59 "Forum/Forum H2" 0 11 * * Mon-Fri recordshow2 88.5 59 "Talk of the Nation/Talk Of The Nation H1" 0 12 * * Mon-Fri recordshow2 88.5 59 "Talk of the Nation/Talk Of The Nation H2" 0 13 * * Mon-Fri recordshow2 88.5 59 "Fresh Air/Fresh Air" 0 16 * * Mon-Fri recordshow2 88.5 29 "Market Place/Marketplace" 30 16 * * Mon-Fri recordshow2 88.5 119 "All Things Considered/All Things Considered" 0 21 * * Mon-Fri recordshow2 88.5 59 "BBC World Service/BBC World Service" # Weekly Recordings # Thursday 30 18 * * Thu recordshow2 88.5 29 "Pacific Time/Pacific Time" # Saturday 0 11 * * Sat recordshow2 88.5 59 "WaitWait/Wait Wait Don't Tell Me" 0 12 * * Sat recordshow2 88.5 59 "This American Life/This American Life" 0 18 * * Sat recordshow2 88.5 119 "Prarie Home Companion/Prarie Home Companion" # Sunday - in case you messed up saturday # Sunday 0 11 * * Sun recordshow2 88.5 119 "Prarie Home Companion/Prarie Home Companion" # Maintenance 0 1 * * Sun weekly_file_cleanup.rb
Because there are some recordings I don't ever want to delete, usually weekly programs, I added the ability to ignore a directory by simply writing the file .donotreap into the directory. It'll bail on this directory if it finds it. As a secondary safe guard it will also only delete mp3 files. Everything else will be ignored. It's not fancy but it works quite well.
#!/usr/bin/ruby -w =begin Simple script to clean up the Timeshifted Radio Directories Looks for files more than two weeks old and removes them =end puts "Timeshifted Radio File Cleaner v0.1" puts "(c) 2003 Yan-Fa Li ([email protected]) under GNU LGPL" Dir.chdir("/mnt/raid5/music/radio") TWOWEEKS = 60 * 60 * 24 * 7 * 2 file_list=Dir["**"] # Find All Directories dir_list = [] file_list.each { |x| dir_list << x if File.ftype(x) == "directory" } topdir = Dir.pwd # Recurse through all directories dir_list.each do |x| Dir.chdir(topdir + "/" + x) # Do Not Reap Flagged Directories next if File.zero?(".donotreap") puts "Entering Directory: #{x}" # Build File List and Filter on name mp3 file_list=Dir["**"] puts "\tFound #{file_list.length} Files Total" file_list.each { |y| file_list.delete(y) if not y.include?("mp3") } puts "\tFound #{file_list.length} MP3 Files" # Find Files Older than 2 Weeks del_list = [] file_list.each { |y| del_list << y if (Time.now - File.stat(y).mtime) > TWOWEEKS } puts "\t#{del_list.length} Files Scheduled For Deletion" next if del_list.length == 0 del_list.each { |z| File.delete(z) } end
Since it pretty much all just works, I haven't messed with it much. I recently used a lot of the recordings on a long road trip: iPods rule. But I do have a few ideas on things that would be nice to have. First, it'd be great to scrape NPR program listings and get the details for each recording, attaching a reference file to each mp3 or changing the id3 comment to match the program listing.
Second, a dedicated scheduler would also be great. Right now if you have a clash in using the recording device, due to overruns, the second recording fails because the audio device is busy. Having a dedicated scheduler that is recording centric would pretty much fix this problem. I know Tivo has something similar so it's obviously a known problem with known solutions. Cron is the wrong solution for this, so the work around is of course to deliberately stop recording a minute sooner than necessary. In general this works very well.
Third, it would be great to have a web based interface for interacting with the recordings, changing programs to be recorded and listening, say via shoutcast, to stuff that's already been recorded. I'm far too lazy to write it myself, so I leave it as a challenge to all you out there :D
The good news about building one is that things will be getting much easier in the 2.6 timeframe because of the integration of Alsa sound system into the mainline kernel. While the files it generates by default are quite large, you can reduce that footprint by choosing a lower encoding bitrate than my default of 112kbps. As low as 64kbps should sound fine for just voice, though music will sound pretty horrible at this bitrate. I haven't experimented with OGG or any other formats as I don't have portable players that support alternative formats, but changing it to support them should be a simple matter of modifying the backend a bit.
Any feedback or comments are appreciated, and if you have a solution to my occasional bad recordings drop me a line.