Tuesday, December 25, 2007

MP3 Music On The LG Chocolate Phone Using Linux


I recently got an LG Chocolate (LGC) phone and spent a few days getting the music player working as I like. It was enough of an effort that I thought I'd write up an article so others can benefit. The LGC in this article is model VX8550. The linux kernel is 2.6.22.2, but any 2.6 kernel should be similarly configurable.

This isn't an article about how to communicate with the LGC directly using linux. The LGC uses an add-on microSD card for storage. This article discusses storing music on this card from linux, and then inserting the card into the LGC. This makes the linux side easier as we only have to figure out how to talk to a generic USB storage device.

Rant

The mp3 support on the LGC is limited to ID3v1. Unfortunately the track number addition (commonly known as ID3v1.1) that appeared in 1997 is not supported. As for ID3v2 - ID3v2.4 spec'd out by 2000, forget it. Giving time for product development the mp3 ID3 support on the LGC is at best 10 years too late to the party. Pretty sad for an "updated" product that came out fall of 2007.

Since there's no track number support songs are listed alphabetically within an album listing. This is unacceptable to me as albums are usually laid out in a specific order by the artist/production team to enhance the listening experience. We can get the track sorted album listing by embedding the track number as the first two characters of the song title causing the alphabetical sorting to sort the tracks as we desire. By doing this, though, we sacrifice the utility of the "all songs" dialogue as we can no longer easily find a track by name.

If all you plan to use the LGC for is shuffle, then you probably don't care what order the tracks are sorted in. If you do care, though, see the section at the end, Title Tracking, for a quick script to add the track number to the song title.

Getting Started

The LGC doesn't come with a microSD card, so I bought ("won") a 2GB microSD card + USB adapter on eBay for $25. The microSD card plugs into an SD adapter card which plugs into the SD->USB adapter. Once plugged into the USB port, we need to load some drivers to make the microSD card appear as a generic USB storage device.

Before we begin, put the microSD into the LGC. Go to MENU -> SETTINGS & TOOLS -> *. Memory -> 3. Card Memory. This will cause the phone to format the microSD flash for use. When it's finished, pop the microSD card out of the LGC and put it into the SD->USB adapter. Wait to connect the USB stick until we have the linux system configured.

Mounting The Flash

If you're using a stock kernel with a linux distribution, odds are good you already have the drivers compiled that you'll need to talk to usb storage devices. If you've compiled your own kernel, you may need to dig around in make menuconfig to compile the right drivers for loading. I compiled them all as modules as I don't mind loading them when I want to store to the usb flash. YMMV.

The USB "host" drivers you'll need may vary from machine to machine. USB has gone through a few iterations so odds are your computer has one of ehci, ohci, or uhci support. The easiest way to go is to just compile and load them all and be done with it. You can figure out which your computer has later if you care to optimize the process.

modprobe ehci-hcd
modprobe ohci-hcd
modprobe uhci-hcd
Additionally you'll need the usb-storage module as well as some scsi modules. The scsi modules are needed to create the device nodes in /dev. In linux, USB storage devices appear as scsi disks.
modprobe usb-storage
modprobe sd_mod
modprobe sg
Lastly, you'll need the vfat filesystem module loaded to mount the USB flash:
modprobe vfat
Now run the following command to monitor the log output of the usb/scsi modules:
tail -f /var/log/messages
Insert the USB flash key. With any luck, you'll see something like the following:
Dec 24 01:11:06 lappie usb-storage: device found at 14
Dec 24 01:11:06 lappie usb-storage: waiting for device to settle before scanning
Dec 24 01:11:11 lappie scsi 13:0:0:0: Direct-Access SD/MMC Card Reader 1.00 PQ: 0 ANSI: 0
Dec 24 01:11:11 lappie sd 13:0:0:0: [sda] 3970048 512-byte hardware sectors (2033 MB)
Dec 24 01:11:11 lappie sd 13:0:0:0: [sda] Write Protect is off
Dec 24 01:11:11 lappie sd 13:0:0:0: [sda] Mode Sense: 03 00 00 00
Dec 24 01:11:11 lappie sd 13:0:0:0: [sda] Assuming drive cache: write through
Dec 24 01:11:11 lappie sd 13:0:0:0: [sda] 3970048 512-byte hardware sectors (2033 MB)
Dec 24 01:11:11 lappie sd 13:0:0:0: [sda] Write Protect is off
Dec 24 01:11:11 lappie sd 13:0:0:0: [sda] Mode Sense: 03 00 00 00
Dec 24 01:11:11 lappie sd 13:0:0:0: [sda] Assuming drive cache: write through
Dec 24 01:11:11 lappie sda: sda1
Dec 24 01:11:11 lappie sd 13:0:0:0: [sda] Attached SCSI removable disk
Dec 24 01:11:11 lappie sd 13:0:0:0: Attached scsi generic sg0 type 0
Dec 24 01:11:11 lappie usb-storage: device scan complete
In this case, the usb storage device is installed as sda. The useable space is on the first partition (/dev/sda1). Before mounting this partition decide which user will be ripping cds to the flash. We will mount the device as this user so the ripping can proceed without root privileges. The mount option for fat uses the uid/gid instead of the user/group name, so we'll have to do a little legwork by finding the uid for the chosen user. I find it's easiest just to pull it out of /etc/passwd:
lappie ~ # grep sah /etc/passwd
sah:x:1000:10::/home/sah:/bin/bash
The third field is the user id, the fourth field is the default group id.

Armed with this, we can mount the filesystem as follows:
mkdir -p /mnt/flash
mount -o 'uid=1000,gid=10' /dev/sda1 /mnt/flash
and the files are owned appropriately:
lappie ~ # ls -l /mnt/flash/
total 160
drwxr-xr-x 2 sah wheel 32768 Dec 22 21:30 media
drwxr-xr-x 2 sah wheel 32768 Dec 22 21:30 my_flix
drwxr-xr-x 2 sah wheel 32768 Dec 24 11:32 my_music
drwxr-xr-x 2 sah wheel 32768 Dec 22 21:30 my_pix
drwxr-xr-x 2 sah wheel 32768 Dec 22 21:30 my_sounds
We can now exit superuser mode and continue as the selected regular user.

Ripping and Encoding

In these examples we'll use the following programs: grip, lame, and normalize. Check your favorite distribution for packages of these programs. An overview of ripping and encoding can be found online, but here's the skinny: CD tracks are ripped to .wav files which are then encoded into other formats (mp3,ogg,etc).

The default music volume range on the LGC is way, WAY too loud. It basically goes from "wow, that's loud for the lowest setting" to "holy #$%*@ @#%%^ are you serious?" To work around this we'll need to use the normalize program to reduce the volume level of the mp3s to make the LGC volume range acceptable. Unfortunately, the LGC doesn't handle the mp3 ID3 tag for volume correction so we can't simply correct the mp3s. We must correct the raw wav files before we encode them. Fortunately, grip makes this easy for us.

Step 1. Configuring the Ripper

Start grip. Click on the Config tab, then on the Rip subtab, and then on the Options subsubtab. There you'll find a field for "Wav filter command". This is where we'll stick our call to normalize so that the wav file is preprocessed before it's sent to the encoder.

In the filter command field, put the following:
/usr/bin/normalize -a -30dBFS %w
Replace /usr/bin/normalize with the path to your normalize program. You can find the path by running 'which normalize' at a command prompt. I ended up with -30dBFS by trial and error; normalize, unmount, move flash to LGC, listen, move flash to USB, mount, repeat. I selected a level that made the lowest volume setting quiet enough that I could hear external events and the highest volume setting mildly painful. I'd recommend starting with this setting; Rip/encode a song to the flash and listen to it to decide if you need to tinker further. Keep in mind that you'll want all your music to be at the same normalized level.

Step 2. Configuring the Encoder

For the mp3 encoder we'll use lame.

The LGC supports ID3 tags enough to read the song title, album title, artist, and genre. Grip does not insert an ID3 tag into the mp3 by default; to turn this on, click on the Config tab, then on the ID3 subtab, then click "Add ID3 tags to encoded files".

The LGC doesn't support subdirectories for mp3 files. All mp3 files must be stored in the my_music directory on the flash. I elected to configure grip to direct lame to encode straight to this directory on the flash. It might be nice to have a local backup of these files to manage them, but I'll leave that as an exercise for the reader.

In grip, click the Config tab, then the Encode subtab, and then the Encoder subsubtab. Ensure lame is the selected encoder. Verify the executable path is correct and ensure the file extension is set to mp3. The following are my settings for the command-line and file format fields:

Encoder command-line:
-h -b %b %w %m
Encoder file format:
/mnt/flash/my_music/%n.%x
The %[a-zA-Z] settings are escape codes for grip. See the grip documentation for a full list of defined escape codes.

Now click on the Options subsubtab. I would recommend changing the bitrate to 112. The 112 encoding permits about 4 more hours worth of music on a 2GB flash -- back of the envelope calculation not subject to precision -- and I can't personally hear any degradation of quality. If you plan to hook your LGC into a larger sound system you may want to keep the default of 128. FYI - I have read online that the LGC does not support a bitrate of 200 or higher, though I have not verified this.

Let Her Rip+Encode

With the flash mounted and grip configured, we're ready to insert a CD and get to business. Once inserted, grip will attempt to read the CDDB entry for the CD. With luck (and and internet connection) the track listing and album info will be populated properly. Inspect the results closely as this information is user supplied and some users like to get creative with spelling / naming. The differences between the entries for The Beatles White Album discs 1&2 are significant (one has "The Beatles / " prefixing every track listing :/).

Once the metadata has been verified, click on the Rip tab, and then Rip+Encode. You'll be prompted to rip the whole CD - click OK. Now go get a cup of coffee, because the process takes a while. Generally speaking, ripping is faster than encoding. Once a CD has been ripped you can insert a new CD and click Rip+Encode again without worry of interrupting existing encoding. The new work will just be queued up behind any encoding in process.

Be careful to ensure the system doesn't go down suddenly as there's no persistent state maintained by grip. If you used the default encoding setting of "Delete .wav after encoding" then any wav files that didn't finish encoding will be left in ~/mp3/. You can poke around to see where grip left off, but to clean up you'll have to re-rip+encode the songs/CDs that didn't finish. Fortunately grip is smart enough not to rerip tracks if it already has them, so a Rip+Encode of unfinished work will only retrigger the encoding phase.

Once the music has finished encoding to the flash, unmount the flash and transfer the microSD card to the LGC. Select the music player and the LGC will read in all the files. You should find that sorting by artist / album title / etc. works as expected.

Updating The ID3 Content

You may find at some later point that you'd like to change the ID3 attributes of an mp3 file. To do this, use the mp3info program. Read the mp3info manpage for a concise explanation of the program's abilities. The following is one example of how to use it.

Let's suppose we (rip+encode)-ed a cd to the my_music folder, but it was incorrectly labeled as Rock instead of Alternative Rock. We could locate and update each file one by one, or we can use the power of unix to script the update of all the songs in the album. We use the -p flag to mp3info to specifically format its output so we can parse it with awk.
% cd /mnt/flash/my_music
% files=`mp3info -p '%f:%l\n' | awk -F: '$2 ~ /One Hot Minute/ { print $1 }'`
% # verify the files are right
% echo $files
aeroplane.mp3 coffee_shop.mp3 deep_kick.mp3 falling_into_grace.mp3 my_friends.mp3 one_big_mob.mp3 one_hot_minute.mp3 pea.mp3 shallow_be_thy_game.mp3 tearjerker.mp3 transcending.mp3 walkabout.mp3 warped.mp3
% # just for sanity, let's see the genre setting
% for f in $files; do mp3info -p '%f:%g\n' $f ; done
aeroplane.mp3:Rock
coffee_shop.mp3:Rock
deep_kick.mp3:Rock
falling_into_grace.mp3:Rock
my_friends.mp3:Rock
one_big_mob.mp3:Rock
one_hot_minute.mp3:Rock
pea.mp3:Rock
shallow_be_thy_game.mp3:Rock
tearjerker.mp3:Rock
transcending.mp3:Rock
walkabout.mp3:Rock
warped.mp3:Rock
% # we must find the id for the new genre setting
% mp3info -G | grep -i 'alt.*rock'
40 Alt. Rock 80 Folk 92 Progressive Rock
20 Alternative 119 Freestyle 93 Psychedelic Rock
% for f in $files; do mp3info -g 40 $f ; done
% # ... and check to ensure it succeeded
% for f in $files; do mp3info -p '%f:%g\n' $f ; done
aeroplane.mp3:Alt. Rock
coffee_shop.mp3:Alt. Rock
deep_kick.mp3:Alt. Rock
falling_into_grace.mp3:Alt. Rock
my_friends.mp3:Alt. Rock
one_big_mob.mp3:Alt. Rock
one_hot_minute.mp3:Alt. Rock
pea.mp3:Alt. Rock
shallow_be_thy_game.mp3:Alt. Rock
tearjerker.mp3:Alt. Rock
transcending.mp3:Alt. Rock
walkabout.mp3:Alt. Rock
warped.mp3:Alt. Rock
We could have used the -i flag to "interactively" update the ID3 tag, but since we're just setting the genre here there's not much point. If we were updating song labels, then an interactive update would make sense. Overall I find mp3info quite simple and powerful. I'm a fan.

Title Tracking

As previously mentioned if we want the album tracks to be sorted in the correct track order we must prepend the track number to the song title. We can do this easily with mp3info. The following will process all the mp3 files in /mnt/flash/my_music, with the assumption that as we add files some will already be modified and some will not. The following shell snippet finds the songs where the 2 digit track number is not the first 2 characters of the song title and updates them to prepend the track number and a space to the song title.

Note that this will shift the title to the right by 3 characters. Since ID3v1 tags only support 30 byte song titles, some song titles will be clipped at the end.
% cd /mnt/flash/my_music
% for f in *; do
tn=`mp3info -p %.2t $f`
n=`mp3info -p %.2n $f`
if test $tn != $n; then
echo Updating `mp3info -p '%a:%l:%t, track %n' $f`
t=`mp3info -p %t $f`
mp3info -t "$n $t" $f
echo Title is now `mp3info -p $t $f`
fi
done

Good Luck!

Sam