Click here to Skip to main content
13,189,884 members (50,855 online)
Click here to Skip to main content
Add your own
alternative version


55 bookmarked
Posted 14 Apr 2008

Creating Audio CDs using IMAPI2

, 14 Apr 2008
Rate this:
Please Sign up or sign in to vote.
Using the Image Mastering API to create Red Book Audio CDs.


This is my third article now on burning media using IMAPI2, version 2 of Microsoft's Image Mastering API. I received a few inquiries on creating Audio CDs after I wrote my last article, Burning and Erasing CD/DVD/Blu-ray Media with C# and IMAPI2, so I decided to write this article next. This article goes into more detail about the IDiscFormat2TrackAtOnce interface, which is the IMAPI2 Interface for creating audio CDs.


It would help to read the last article, but if you don't, there are a couple of things you must know. The IMAPI2 COM DLLs are included with Microsoft Vista, but if you are running Windows XP or Windows 2003, you can download the IMAPI2 Update packages from Microsoft's website. IMAPI2 is implemented using two COM DLLs; imapi2.dll and imapi2fs.dll. imapi2.dll handles most of the device and recording APIs, and imapi2fs.dll handles all of the file system and IStream APIs. Do not add the IMAPI2 COM references to your project. There is a conflict with the IStream interfaces, and if you try to use the IStream created with IMAPI2FS in the IMAPI2 interface, you will get errors similar to this:

Unable to cast object of type 'IMAPI2FS.FsiStreamClass' to type 'IMAPI2.IStream'

I go into more detail in my last article about this problem. You will need to use my Interop file, Imapi2interop.cs, which provides all of the interfaces and events for IMAPI2.

Using the code

  • Make sure that Windows XP and 2003 have the IMAPI2 updates mentioned at the top of the article.
  • Do not add the imapi2.dll and imapi2fs.dll COM DLLs to your project. You will receive the IStream error mentioned above.
  • Add the file imapi2interop.cs to your project, and define the 'IMAPI2.Interop' namespace in your app:
  • using IMAPI2.Interop;
  • In order to receive notification to your event handler from COM, you need to open up the file AssemblyInfo.cs and change the ComVisible attribute to true:
  • [assembly: ComVisible(true)] 

WAV file format

Red Book is the standard for CD audio, and it specifies that CD data is in the 44.1 Hz, 16-bit, stereo, uncompressed PCM format. This is usually WAV files with a WAV header. For the purpose of this article, the program is not going to convert other formats to this format, so you must have Wav audio files that are already in this format.

When you select files to be burned, I check to make sure they are in the correct format, with the MediaFile.IsWavProperFormat method:

/// <span class="code-SummaryComment"><summary></span>
/// Determines if the Wav file is the proper format to be written to CD
/// The proper format is uncompressed PCM, 44.1KHz, Stereo
/// <span class="code-SummaryComment"></summary></span>
/// <span class="code-SummaryComment"><param name="wavFile">the selected wav file</param></span>
/// <span class="code-SummaryComment"><returns>true if proper format, otherwise false</returns></span>
public static bool IsWavProperFormat(string wavFile)
    FileStream fileStream = null;
       fileStream = File.OpenRead(wavFile);

       // Read the header data
       BinaryReader binaryReader = new BinaryReader(fileStream);
       byte[] byteData = binaryReader.ReadBytes(Marshal.SizeOf(typeof(WAV_HEADER)));
       GCHandle handle = GCHandle.Alloc(byteData, GCHandleType.Pinned);

       // Convert to the wav header structure
       WAV_HEADER wavHeader = 

       // Verify the WAV file is a 44.1KHz, Stereo, Uncompressed Wav file.
       if ((wavHeader.chunkID == 0x46464952) && // "RIFF"
         (wavHeader.format == 0x45564157) && // "WAVE"
         (wavHeader.formatChunkId == 0x20746d66) && // "fmt "
         (wavHeader.audioFormat == 1) && // 1 = PCM (uncompressed)
         (wavHeader.numChannels == 2) && // 2 = Stereo
         (wavHeader.sampleRate == 44100)) // 44.1 KHz
           return true;

       MessageBox.Show(wavFile + " is not the correct format!");return false;
    catch (Exception ex)
       return false;

Preparing the stream

The stream size must be a multiple of 2352, the sector size of an audio CD. We calculate it with a simple algorithm:

private long SECTOR_SIZE = 2352;
public Int64 SizeOnDisc
        if (m_fileLength > 0)
           return ((m_fileLength / SECTOR_SIZE) + 1) * SECTOR_SIZE;
        return 0;

Once we calculate the size, we copy the Wav's data, minus the header, to the allocated data, then we create the IStream:

/// <span class="code-SummaryComment"><summary></span>
/// Prepares a stream to be written to the media
/// <span class="code-SummaryComment"></summary></span>
public void PrepareStream()
    byte[] waveData = new byte[SizeOnDisc];
    // The size of the stream must be a multiple of the sector size 2352
    // SizeOnDisc rounds up to the next sector size
    IntPtr fileData = Marshal.AllocHGlobal((IntPtr)SizeOnDisc);
    FileStream fileStream = File.OpenRead(filePath);
    int sizeOfHeader = Marshal.SizeOf(typeof(WAV_HEADER));
    // Skip over the Wav header data, because it only needs the actual data
    fileStream.Read(waveData, sizeOfHeader, (int)m_fileLength - sizeOfHeader);
    Marshal.Copy(waveData, 0, fileData, (int)m_fileLength - sizeOfHeader);
    CreateStreamOnHGlobal(fileData, true, out wavStream);

Writing the tracks

I perform the burning of the CD in a BackgroundWorker's DoWork event. I prepare all the streams first, and then call the MsftDiscFormat2TrackAtOnce's AddAudioTrack method for each track:

/// <span class="code-SummaryComment"><summary></span>
/// The thread that does the burning of the media
/// <span class="code-SummaryComment"></summary></span>
/// <span class="code-SummaryComment"><param name="sender"></param></span>
/// <span class="code-SummaryComment"><param name="e"></param></span>
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    e.Result = 0;
    MsftDiscMaster2 discMaster = new MsftDiscMaster2();
    MsftDiscRecorder2 discRecorder2 = new MsftDiscRecorder2();
    BurnData burnData = (BurnData)e.Argument;
    MsftDiscFormat2TrackAtOnce trackAtOnce = new MsftDiscFormat2TrackAtOnce();
    trackAtOnce.ClientName = m_clientName;
    trackAtOnce.Recorder = discRecorder2;
    m_burnData.totalTracks = listBoxFiles.Items.Count;
    m_burnData.currentTrackNumber = 0;
    // Prepare the wave file streams
    foreach (MediaFile mediaFile in listBoxFiles.Items)
        // Check if we've cancelled
        if (backgroundWorker.CancellationPending)
        // Report back to the UI that we're preparing stream
        m_burnData.filename = mediaFile.ToString();
        backgroundWorker.ReportProgress(0, m_burnData);

    // Add the Update event handler
    trackAtOnce.Update += new DiscFormat2TrackAtOnce_EventHandler(trackAtOnce_Update);
    // Add Files and Directories to File System Image
    foreach (MediaFile mediaFile in listBoxFiles.Items)
        // Check if we've cancelled
        if (backgroundWorker.CancellationPending)
           e.Result = -1;
        // Add audio track
        m_burnData.filename = mediaFile.ToString();
        IStream stream = mediaFile.GetTrackIStream();
    // Remove the Update event handler
    trackAtOnce.Update -= new DiscFormat2TrackAtOnce_EventHandler(trackAtOnce_Update);


Updating the user interface

I created an event handler for the Update event of the IDiscFormat2TrackAtOnce interface. When the event handler gets called, it passes in a IDiscFormat2TrackAtOnceEventArgs object that gives me values like the current track number, elapsed time, etc. I take these values and copy them to my BurnData object, and call the BackgroundWorker's ReportProgress method.

/// <span class="code-SummaryComment"><summary></span>
/// Update notification from IDiscFormat2TrackAtOnce
/// <span class="code-SummaryComment"></summary></span>
/// <span class="code-SummaryComment"><param name="sender"></param></span>
/// <span class="code-SummaryComment"><param name="progress"></param></span>
void trackAtOnce_Update(object sender, object progress)
    // Check if we've cancelled
    if (backgroundWorker.CancellationPending)
        IDiscFormat2TrackAtOnce trackAtOnce = (IDiscFormat2TrackAtOnce)sender;
    IDiscFormat2TrackAtOnceEventArgs eventArgs = (IDiscFormat2TrackAtOnceEventArgs)progress;
    // IDiscFormat2TrackAtOnceEventArgs Interface
    m_burnData.currentTrackNumber = eventArgs.CurrentTrackNumber;
    m_burnData.elapsedTime = eventArgs.ElapsedTime;
    m_burnData.remainingTime = eventArgs.RemainingTime;
    // IWriteEngine2EventArgs Interface
    m_burnData.currentAction = eventArgs.CurrentAction;
    m_burnData.startLba = eventArgs.StartLba;
    m_burnData.sectorCount = eventArgs.SectorCount;
    m_burnData.lastReadLba = eventArgs.LastReadLba;
    m_burnData.lastWrittenLba = eventArgs.LastWrittenLba;
    m_burnData.totalSystemBuffer = eventArgs.TotalSystemBuffer;
    m_burnData.usedSystemBuffer = eventArgs.UsedSystemBuffer;
    m_burnData.freeSystemBuffer = eventArgs.FreeSystemBuffer;
    // Report back to the UI
    backgroundWorker.ReportProgress(0, m_burnData);

This causes the BackgroundWorker's ProgressChanged event to get fired, and allows the application to update the User Interface with the data in the UI's thread.

/// <span class="code-SummaryComment"><summary></span>
/// Update the user interface with the current progress
/// <span class="code-SummaryComment"></summary></span>
/// <span class="code-SummaryComment"><param name="sender"></param></span>
/// <span class="code-SummaryComment"><param name="e"></param></span>
private void backgroundWorker_ProgressChanged(object sender, 
                              ProgressChangedEventArgs e)
    BurnData burnData = (BurnData)e.UserState;
        // Notification that we're preparing a stream
        labelCDProgress.Text = 
          string.Format("Preparing stream for {0}", burnData.filename);
        progressBarCD.Value = (int)burnData.currentTrackNumber;
        progressBarCD.Maximum = burnData.totalTracks;
    else if (burnData.task == BURN_MEDIA_TASK.BURN_MEDIA_TASK_WRITING)
        switch (burnData.currentAction)
            labelCDProgress.Text = string.Format("Writing Track {0} - {1} of {2}",
            burnData.filename, burnData.currentTrackNumber, burnData.totalTracks);
            progressBarCD.Value = (int)burnData.currentTrackNumber;
            progressBarCD.Maximum = burnData.totalTracks;
            long writtenSectors = burnData.lastWrittenLba - burnData.startLba;
            if (writtenSectors > 0 && burnData.sectorCount > 0)
                int percent = (int)((100 * writtenSectors) / burnData.sectorCount);
                labelStatusText.Text = string.Format("Progress: {0}%", percent);
                statusProgressBar.Value = percent;
                labelStatusText.Text = "Track Progress 0%";
                statusProgressBar.Value = 0;
            labelStatusText.Text = "Finishing...";



  • April 15, 2007 - Initial release.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Eric Haddan
Software Developer (Senior) Tranxition Corp
United States United States
Thank you for voting on my articles!



The Tranxition Developer's Blog[^]

You may also be interested in...


Comments and Discussions

QuestionNo Progress Update Pin
prtsoft30-Sep-12 15:45
memberprtsoft30-Sep-12 15:45 
I am not getting the delegate to fire for the progress of the burning.

trackAtOnce.Update += new DiscFormat2TrackAtOnce_EventHandler(trackAtOnce_Update) exists, but the trackAtOnce_Update function is never called. The progress stays on Preparing Media.

This is on Windows 7 - Visual Studio 2010

QuestionVolume Name to Cd Pin
gauri_21vaidya19-Sep-12 23:20
membergauri_21vaidya19-Sep-12 23:20 
QuestionA few problems..... Pin
davebuk3-Apr-12 4:48
memberdavebuk3-Apr-12 4:48 
GeneralMy vote of 5 Pin
e1lvin30-Nov-11 8:50
membere1lvin30-Nov-11 8:50 
QuestionRecorder not supported Pin
stankomix127-Nov-11 21:32
memberstankomix127-Nov-11 21:32 
QuestionPrepareStream Pin
Nickm3248-Nov-11 12:35
memberNickm3248-Nov-11 12:35 
QuestionLoud Pop Between Tracks Pin
Mickey Marshall25-Jul-11 13:56
memberMickey Marshall25-Jul-11 13:56 
AnswerRe: Loud Pop Between Tracks Pin
Mickey Marshall10-Aug-11 13:09
memberMickey Marshall10-Aug-11 13:09 
GeneralRe: Loud Pop Between Tracks Pin
Stefan Haglund3-Apr-13 0:37
memberStefan Haglund3-Apr-13 0:37 
QuestionOn the Fly Stream Pin
stixoffire25-Jun-11 20:44
memberstixoffire25-Jun-11 20:44 
GeneralCD-TEXT Pin
djayme819-Apr-11 10:59
memberdjayme819-Apr-11 10:59 
QuestionAccess Denied Errors - When using virtual CD/DVD [modified] Pin
stixoffire7-Dec-10 2:19
memberstixoffire7-Dec-10 2:19 
Generalconvert them to 44.1Khz Stereo uncompressed PCM Pin
programmerdon25-Aug-10 6:01
memberprogrammerdon25-Aug-10 6:01 
GeneralTrack Names and Disc Name... Pin
mcgin15911-Oct-09 7:53
membermcgin15911-Oct-09 7:53 
GeneralRe: Track Names and Disc Name... Pin
Eric Haddan1-Oct-09 9:33
memberEric Haddan1-Oct-09 9:33 
GeneralRe: Track Names and Disc Name... Pin
mcgin15911-Oct-09 9:36
membermcgin15911-Oct-09 9:36 
GeneralPermission request Pin
acosano31-Aug-09 16:15
memberacosano31-Aug-09 16:15 
GeneralRe: Permission request Pin
Eric Haddan31-Aug-09 17:19
memberEric Haddan31-Aug-09 17:19 
QuestionBurned CDs Playback at high speed Pin
dc_chant4-Jul-09 4:58
memberdc_chant4-Jul-09 4:58 
AnswerRe: Burned CDs Playback at high speed Pin
Eric Haddan4-Jul-09 10:32
memberEric Haddan4-Jul-09 10:32 
QuestionCool project, couple issues. Pin
Gladely3-May-09 8:27
memberGladely3-May-09 8:27 
AnswerRe: Cool project, couple issues. Pin
Eric Haddan3-May-09 15:22
memberEric Haddan3-May-09 15:22 
GeneralCOM problem in "trackAtOnce.AddAudioTrack(stream);" Pin
henur24-Nov-08 21:50
memberhenur24-Nov-08 21:50 
QuestionWhen i add the.... Pin
Amrykid2-Oct-08 13:24
memberAmrykid2-Oct-08 13:24 
QuestionSpeed and Gapless CDs Pin
radio4231-Aug-08 21:24
memberradio4231-Aug-08 21:24 
QuestionSize in seconds? Pin
Bibubabibuba6-Jun-08 6:24
memberBibubabibuba6-Jun-08 6:24 
AnswerRe: Size in seconds? Pin
Eric Haddan6-Jun-08 11:56
memberEric Haddan6-Jun-08 11:56 
GeneralCool Pin
Dr.Luiji29-Apr-08 11:17
member Dr.Luiji 29-Apr-08 11:17 
GeneralRe: Cool Pin
Eric Haddan29-Apr-08 11:32
memberEric Haddan29-Apr-08 11:32 
GeneralCreate ISO Image Only Pin
lordlondon17-Apr-08 4:28
memberlordlondon17-Apr-08 4:28 
GeneralRe: Create ISO Image Only Pin
Eric Haddan17-Apr-08 4:38
memberEric Haddan17-Apr-08 4:38 
GeneralFirst of all ... Great Work! One Question Pin
MarcoPolo2003k16-Apr-08 18:52
memberMarcoPolo2003k16-Apr-08 18:52 
GeneralRe: First of all ... Great Work! One Question Pin
Eric Haddan17-Apr-08 4:34
memberEric Haddan17-Apr-08 4:34 
GeneralRe: First of all ... Great Work! One Question Pin
MarcoPolo2003k17-Apr-08 19:07
memberMarcoPolo2003k17-Apr-08 19:07 
GeneralNicely done Pin
Bert delaVega15-Apr-08 3:13
memberBert delaVega15-Apr-08 3:13 
GeneralRe: Nicely done Pin
Eric Haddan15-Apr-08 4:05
memberEric Haddan15-Apr-08 4:05 
GeneralMight Be a Stupid Question.... Pin
Johnny J.14-Apr-08 22:43
memberJohnny J.14-Apr-08 22:43 
GeneralRe: Might Be a Stupid Question.... Pin
Guillaume Geffard14-Apr-08 23:09
memberGuillaume Geffard14-Apr-08 23:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.171016.2 | Last Updated 14 Apr 2008
Article Copyright 2008 by Eric Haddan
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid