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

C Sharp Ripper

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (54投票s)

2003年11月18日

5分钟阅读

viewsIcon

469394

downloadIcon

9743

C# 代码用于处理 CDROM 驱动器并读取 CD 音轨。

Demo program

引言

这段代码演示了如何用 C# 制作一个 CD Ripper。有一些供应商提供的 API 可以读取音频 CD 轨道,但也可以使用允许低级别访问 CD 驱动器的 API 来实现,例如 Adaptec 的 ASPI 或 IOCTL 控制码。这里采用了后一种方法,因为不需要安装任何第三方软件,它完全由 Win32 API 函数(CreateFileCloseHandleDeviceIoControlGetDriveType)覆盖。

互操作性用于调用上述 Win32 函数来打开 CD 驱动器并发送 IOCTL 代码以打开、关闭驱动器、读取状态和进行音频轨道的原始读取。确实,完成相同事情更快、更“逻辑”的方法是使用 C++.NET,但使用 C# 和互操作性是一种有效的方法,而且不一定效率低下,当然,它需要 API 翻译。

在这篇文章中,有来自 Ianier MunozC# 中制作低级别音频播放器 一文中的代码。

背景

为了读取 CD 的任何音频轨道,首先要做的是读取 CD 的 TOC(目录)。TOC 是一个结构,其中包含 CD 的第一首和最后一首曲目,以及一个固定长度的结构列表,这些结构描述了每首曲目的信息(曲目开始的第一个扇区、曲目类型等)。这些结构在 ntddcdrm.h 中定义,分别命名为 CDROM_TOCTRACK_DATA。我认为描述这些结构到 C# 的翻译很有趣,因为需要做一些技巧才能在 C# 中定义这些结构以便使用 Platform Invoke 将它们作为参数传递:请参阅文章末尾的 一些翻译细节

使用代码

主代码是一个名为 ripper 的类库。CDDrive 类是整个过程中的主要类,它包含了所有 CD 操作的逻辑(访问驱动器、通知驱动器更改、获取 CD 信息和驱动器状态、获取 CDROM 驱动器盘符,以及读取轨道)。下面是一个简单的代码示例,展示了如何使用此类来读取特定曲目。

CDDrive Drive;
Drive = new CDDrive();
if ( Drive.Open(CDDrive.GetCDDriveLetters()[0]) ) //Get the first CD drive 
in the system.
{// We have access to CD drive
  if ( Drive.IsCDReady() )
  { //There is a CD in the drive
    if ( Drive.Refresh() )
    { //TOC have been read
      int Tracks = Drive.GetNumTracks();
      byte[] Buffer = new byte[4096];
      Drive.LockCD();
      try
      {
        for (int i = 1; i <= Tracks; i++)
        {
          Stream Strm = new FileStream(string.Format("track{0:00}.raw", i), 
                                       FileMode.Create, FileAccess.Write);
          try
          {
            uint Size = 0;
            while ( Drive.ReadTrack(i, Buffer, ref Size, null) > 0 ) 
            //If there is no error and datawas read 
            //successfully a positive number is returned
            {
             Strm.Write(Buffer, 0, (int)Size);
            }
          }
          finally
          {
            Strm.Close();
          }
        }
      }
      finally
      {
        Drive.UnlockCD();
      }
    }
  }
}

上面的代码将第一个 CD 驱动器中插入的 CD 的所有曲目以原始格式保存在名为 trackXX.raw 的文件中。这是使用上述类来读取 CD 曲目数据的最简单方法之一。我认为通过查看代码可以无需进一步解释就能理解(如果我错了,请告诉我)。在演示项目中(一个简单的 ripper),它以一种更复杂和完整的方式来读取曲目:它用作通知进度的委托(前面的代码使用 null 作为最后一个参数或 ReadTrack 以避免进度通知),以及一个保存读取数据的 WAV 文件的委托。它还与类的更多函数一起使用,例如插入通知、打开/关闭 CD 驱动器门等。使用这些函数来处理的复杂性不比调用一些方法或为类中定义的事件分配处理程序高。

虽然此代码处理 CD 插入/移除通知,但自动播放功能并未以编程方式禁用,因此建议在使用代码之前在 CD 中禁用自动播放。根据 Microsoft 的说法,要以编程方式禁用自动播放功能,必须处理 Windows 消息 QueryCancelAutoPlay,但这仅发送给当前活动窗口,并且至少在 Windows 2000 中,对于包含 AUTORUN.INI 文件的数据 CD;对于音频 CD,会启动默认的 CD 播放器。在 Windows XP 中,有用于优雅处理所有媒体插入和自动播放的 API,但这些仅在 Windows XP 中可用。

演示程序的 UI 不是很直观,也不太漂亮 :-) 但只是为了展示如何使用代码。即使代码和演示有效,也需要注意的是,它不是作为一个可以用于最终解决方案的项目而创建的。它缺少完整的错误检测和处理等功能。另一个未包含的重要内容是错误校正算法;众所周知,在数字读取 CD 时,可能会出现由于磁头对齐等原因导致的错误,因此大多数 ripper 都包含错误校正算法。即使没有错误校正算法,也可以从许多驱动器(旧驱动器表现不佳)和 CD 中获得可接受的结果,一些驱动器还包含硬件错误校正,因此数字读取的信息是可靠的。

一些翻译细节

这是 TRACK_DATA CDROM_TOC 结构体的 C 定义。

#define MAXIMUM_NUMBER_TRACKS 100

typedef struct _TRACK_DATA
{
  UCHAR Reserved;
  UCHAR Control : 4;
  UCHAR Adr : 4;
  UCHAR TrackNumber;
  UCHAR Reserved1;
  UCHAR Address[4];
} TRACK_DATA;


typedef struct _CDROM_TOC
{
  UCHAR Length[2];
  UCHAR FirstTrack;
  UCHAR LastTrack;
  TRACK_DATA TrackData[MAXIMUM_NUMBER_TRACKS];
} CDROM_TOC;

TRACK_DATA 结构的翻译很简单;唯一需要注意的是,在 C# 中无法指示结构字段的位大小。为了获得相同的行为,我们可以定义一个私有字段来保存所有位大小的字段,并使用公共属性来表示位大小的字段(如本工作中所做的那样)。这里的问题是 CDROM_TOC 的翻译,最合乎逻辑的翻译将是

[StructLayout( LayoutKind.Sequential )]
public class CDROM_TOC
{
  public ushort Length;
  public byte FirstTrack = 0;
  public byte LastTrack = 0;
  [MarshalAs(UnmanagedType.ByValArray, SizeConst=MAXIMUM_NUMBER_TRACKS)]
  public TRACK_DATA[] TrackData;

  public CDROM_TOC()
  {
    TrackData = new TRACK_DATA[MAXIMUM_NUMBER_TRACKS];
    Length = (ushort)Marshal.SizeOf(this);
  }
}

当然,这是一种更合乎逻辑的翻译,但您会在构造函数中遇到运行时错误,因为 MarshalAs 无法确定结构的大小。问题在于 SizeConst 只能为基本类型指定,它不适用于结构数组(互操作性的一项不足)。这可以通过使用自定义封送来解决,但自定义封送仅用于函数参数,而不用于结构字段(互操作性的又一项不足)。等待 Microsoft 修复互操作性的这些细节或任何其他更好的解决方案。这里有一个解决方案。

[ StructLayout( LayoutKind.Sequential )]
public class TrackDataList
{
  [MarshalAs(UnmanagedType.ByValArray, SizeConst=MAXIMUM_NUMBER_TRACKS*8)]
  private byte[] Data;
  public TRACK_DATA this [int Index]
  {
    get
    { /* Code in the source files */
    }
  }
  public TrackDataList()
  {
    Data = new byte[MAXIMUM_NUMBER_TRACKS*Marshal.SizeOf(typeof(TRACK_DATA))];
  }
}

[StructLayout( LayoutKind.Sequential )]
public class CDROM_TOC
{
  public ushort Length;
  public byte FirstTrack = 0;
  public byte LastTrack = 0;
  public TrackDataList TrackData;
  public CDROM_TOC()
  {
    TrackData = new TrackDataList();
    Length = (ushort)Marshal.SizeOf(this);
  }
}

本工作中使用的所有结构、常量、枚举和外部函数都定义在 Win32Funtions 类中。

结论

这项工作表明,C# 和 .NET 平台可以成为低级别任务(如音频提取或操作)的高效且良好的解决方案。如果我们必须做肮脏的工作,最好使用最好的工具 :-)

© . All rights reserved.