65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 IMAPI2 创建音频 CD

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (18投票s)

2008年4月14日

CPOL

3分钟阅读

viewsIcon

132161

downloadIcon

3290

使用 Image Mastering API 创建 Red Book Audio CD

引言

这是我关于使用 IMAPI2(Microsoft 映像掌握 API 的第二个版本)刻录媒体的第三篇文章。在我上一篇文章《使用 C# 和 IMAPI2 刻录和擦除 CD/DVD/Blu-ray 媒体》之后,我收到了一些关于创建音频 CD 的咨询,所以我决定写这篇后续文章。本文将更详细地介绍 IDiscFormat2TrackAtOnce 接口,这是 IMAPI2 用于创建音频 CD 的接口。

背景

阅读上一篇文章会有帮助,但即使不读,也有几点必须了解。IMAPI2 COM DLL 已包含在 Microsoft Vista 中,但如果您使用的是 Windows XP 或 Windows 2003,可以从微软网站下载 IMAPI2 更新包。IMAPI2 通过两个 COM DLL 实现:imapi2.dllimapi2fs.dllimapi2.dll 处理大部分设备和刻录 API,而 imapi2fs.dll 处理所有文件系统和 IStream API。**不要**将 IMAPI2 COM 引用添加到您的项目中。这会导致 IStream 接口发生冲突,如果您尝试在 IMAPI2 接口中使用由 IMAPI2FS 创建的 IStream,您将遇到类似以下内容的错误

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

我在上一篇文章中详细介绍了这个问题。您需要使用我的互操作文件 Imapi2interop.cs,它提供了 IMAPI2 的所有接口和事件。

Using the Code

using IMAPI2.Interop;
[assembly: ComVisible(true)] 
  • 请确保 Windows XP 和 2003 已安装文章开头提到的 IMAPI2 更新。
  • 不要将 imapi2.dllimapi2fs.dll COM DLL 添加到您的项目中。您将收到上述 IStream 错误。
  • imapi2interop.cs 文件添加到您的项目,并在您的应用程序中定义 'IMAPI2.Interop' 命名空间。
  • 为了能够从 COM 接收事件处理器的通知,您需要打开 AssemblyInfo.cs 文件并将 ComVisible 属性更改为 true

WAV 文件格式

Red Book 是 CD 音频的标准,它规定 CD 数据必须是 44.1 KHz、16 位、立体声、未压缩的 PCM 格式。这通常是带 WAV 头的文件。在本篇文章中,程序不会将其他格式转换为此格式,因此您必须拥有已采用此格式的 WAV 音频文件。

当您选择要刻录的文件时,我使用 MediaFile.IsWavProperFormat 方法来检查它们是否为正确的格式。

/// <summary>
/// Determines if the Wav file is the proper format to be written to CD
/// The proper format is uncompressed PCM, 44.1KHz, Stereo
/// </summary>
/// <param name="wavFile">the selected wav file</param>
/// <returns>true if proper format, otherwise false</returns>
public static bool IsWavProperFormat(string wavFile)
{
    FileStream fileStream = null;
    try
    {
       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);
       binaryReader.Close();
       fileStream.Close();

       //
       // Convert to the wav header structure
       //
       WAV_HEADER wavHeader = 
         (WAV_HEADER)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), 
                                            typeof(WAV_HEADER));

       //
       // 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)
    {
       MessageBox.Show(ex.Message);
       return false;
    }
}

准备流

流的大小必须是 2352 的倍数,这是音频 CD 的扇区大小。我们使用一个简单的算法来计算它。

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

计算出大小后,我们将 WAV 的数据(减去头部)复制到分配的数据区域,然后创建 IStream

/// <summary>
/// Prepares a stream to be written to the media
/// </summary>
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);
}

写入音轨

我在 BackgroundWorkerDoWork 事件中执行 CD 刻录。我首先准备好所有流,然后为每个音轨调用 MsftDiscFormat2TrackAtOnceAddAudioTrack 方法。

/// <summary>
/// The thread that does the burning of the media
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    e.Result = 0;
 
    MsftDiscMaster2 discMaster = new MsftDiscMaster2();
    MsftDiscRecorder2 discRecorder2 = new MsftDiscRecorder2();
 
    BurnData burnData = (BurnData)e.Argument;
    discRecorder2.InitializeDiscRecorder(burnData.uniqueRecorderId);
 
    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)
        {
            break;
        }
 
        //
        // Report back to the UI that we're preparing stream
        //
        m_burnData.task = BURN_MEDIA_TASK.BURN_MEDIA_TASK_PREPARING;
        m_burnData.filename = mediaFile.ToString();
        m_burnData.currentTrackNumber++;
 
        backgroundWorker.ReportProgress(0, m_burnData);
        mediaFile.PrepareStream();
    }
 

    //
    // Add the Update event handler
    //
    trackAtOnce.Update += new DiscFormat2TrackAtOnce_EventHandler(trackAtOnce_Update);
 
    trackAtOnce.PrepareMedia();
 
    //
    // 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;
           break;
        }
 
        //
        // Add audio track
        //
        m_burnData.filename = mediaFile.ToString();
        IStream stream = mediaFile.GetTrackIStream();
        trackAtOnce.AddAudioTrack(stream);
    }
 
    //
    // Remove the Update event handler
    //
    trackAtOnce.Update -= new DiscFormat2TrackAtOnce_EventHandler(trackAtOnce_Update);

 
    trackAtOnce.ReleaseMedia();
    discRecorder2.EjectMedia();
}

更新用户界面

我为 IDiscFormat2TrackAtOnce 接口的 Update 事件创建了一个事件处理程序。当事件处理程序被调用时,它会传递一个 IDiscFormat2TrackAtOnceEventArgs 对象,该对象提供了当前音轨号、经过时间等值。我将这些值复制到我的 BurnData 对象中,并调用 BackgroundWorkerReportProgress 方法。

/// <summary>
/// Update notification from IDiscFormat2TrackAtOnce
/// </summary>
/// <param name="sender"></param>
/// <param name="progress"></param>
void trackAtOnce_Update(object sender, object progress)
{
    //
    // Check if we've cancelled
    //
    if (backgroundWorker.CancellationPending)
    {
        IDiscFormat2TrackAtOnce trackAtOnce = (IDiscFormat2TrackAtOnce)sender;
        trackAtOnce.CancelAddTrack();
        return;
    }
    IDiscFormat2TrackAtOnceEventArgs eventArgs = (IDiscFormat2TrackAtOnceEventArgs)progress;
    m_burnData.task = BURN_MEDIA_TASK.BURN_MEDIA_TASK_WRITING;
 
    //
    // 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);
}

这会导致 BackgroundWorkerProgressChanged 事件被触发,从而允许应用程序在 UI 线程中更新用户界面。

/// <summary>
/// Update the user interface with the current progress
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void backgroundWorker_ProgressChanged(object sender, 
                              ProgressChangedEventArgs e)
{
    BurnData burnData = (BurnData)e.UserState;
    if (burnData.task == BURN_MEDIA_TASK.BURN_MEDIA_TASK_PREPARING)
    {
        //
        // 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)
        {
        case IMAPI_FORMAT2_TAO_WRITE_ACTION.IMAPI_FORMAT2_TAO_WRITE_ACTION_PREPARING:
            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;
            break;
        case IMAPI_FORMAT2_TAO_WRITE_ACTION.IMAPI_FORMAT2_TAO_WRITE_ACTION_WRITING:
            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;
            }
            else
            {
                labelStatusText.Text = "Track Progress 0%";
                statusProgressBar.Value = 0;
            }
            break;
        case IMAPI_FORMAT2_TAO_WRITE_ACTION.IMAPI_FORMAT2_TAO_WRITE_ACTION_FINISHING:
            labelStatusText.Text = "Finishing...";
            break;
        }
    }
}

参考文献

历史

  • 2007 年 4 月 15 日 - 首次发布
© . All rights reserved.