Playing Audio CDs

One of the cool things about working in multimedia is that you sometimes have opportunities to play with cool bits of hardware.

This article isn't about one of them :).

Instead, it's about something really quite mundane.  Playing a CD programmatically.

It turns out that there are at least three different ways of playing a CD programmatically on Windows, ranging from easy to hard.  I'll start with the easy version and move to progressively harder versions.  The three mechanisms are:

  1. Letting the Windows Media Player play the CD.
  2. Playing the CD with the MCI API set
  3. Playing the CD with Digital Audio Extraction.

Let me start with a bit of details about an audio CD.

An audio CD consists of a set of audio tracks which can be read from the CD.  The track database on the CD contains the start offset within the CD of each of the tracks on the CD, that's what a player uses to determine where to seek on the CD to play.

Clearly, however you are going to play a track from the CD, you need to start by retrieving the track list.  So that's where we'll start.  I started with a console application built in visual studio 2003, adding ATL support (to hide the COM management goo).

#import "wmp.dll" // Needed to import the WMP interface definitions.CWinApp theApp;using namespace std;int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]){    int nRetCode = 0;    CComPtr<WMPLib::IWMPCdromCollection> cdRomCollection;    HRESULT hr;    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);    // initialize MFC and print and error on failure    if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))    {        _tprintf(_T("Fatal Error: MFC initialization failed\n"));        nRetCode = 1;    }    else    {        hr = cdRomCollection.CoCreateInstance(__uuidof(WMPLib::WindowsMediaPlayer));        if (hr != S_OK)        {            printf("Couldn't instantiate CDRom player: %x\n", hr);            return 1;        }        long driveCount;        hr = cdRomCollection->get_count(&driveCount);        if (hr != S_OK)        {            printf("Couldn't get drive count: %x\n", hr);            return 1;        }        if (driveCount == 0)        {            printf("Machine has no CDROM drives\n");            return 1;        }        CComPtr<WMPLib::IWMPCdrom> cdromDrive;        cdromDrive = cdRomCollection->Item(0);        CComPtr<WMPLib::IWMPPlaylist> playlist;        hr = cdromDrive->get_Playlist(&playlist);        if (hr != S_OK)        {            printf("Couldn't get playlist for CDRom drive: %x\n", hr);            return 1;        }        for (int i = 0 ; i < playlist->count ; i += 1)        {            CComPtr<WMPLib::IWMPMedia> item;            hr = playlist->get_Item(i, &item);            if (hr != S_OK)            {                printf("Couldn't get playlist item %d: %x\n", i, hr);                return 1;            }            printf(_T("Track %d (%S)\n"), i, item->name.GetBSTR()); // leaks :)        }    }    CoUninitialize();    return nRetCode;}

So what's going on here?  First off, we import the type library from wmp.dll.  This is because VS 2003 doesn't include the wmp.h header file that includes the type information, so instead we need to import it from the DLL directly.  Note that I'm NOT using the wrapper classes that #import gives you (except for using item->name in the printf above).  This is simply because the wrapper functions throw exceptions instead of returning errors and I prefer my error handling to be more explicit (it's a personal preference thingy).

Everything starts with a WMP CDRom collection, which is a list of CDRom drives available on the computer.  We enumerate the collection to ensure that there's at least one drive available, then access the first drive in the collection (usually the D drive).  Assuming that there's an audio CD in the drive, this will return an IWMPCdrom object.  On the CDRom object, there's a playlist, which is the collection of audio tracks.  To enumerate the tracks on the CD, you can simply enumerate the items in the playlist collection, and print that information out.

One of the cool things about letting Windows Media Player do the heavy listing is that it looks up the track info in the CD database.  Which means that you get a friendly name for the track.  That can be pretty darned cool.  It's also slow.  Another downside is that this example is written to the Windows Media Player 10 SDK, if you don't have WMP 10 on the target machine, it's very likely that it won't work.

Tomorrow, I'll add the code to actually play audio.