CD抓轨杂谈

来源:互联网 发布:战舰世界弗莱彻数据 编辑:程序博客网 时间:2024/05/06 21:06

 音频CD的结构:   一张CD被划分为若干音轨,每个音轨就是一首乐曲,CD长度是用分钟(MINUTE),秒(SECOND)和扇区(SECTOR)来度量。(*   扇区是时间单位,相当于七十五分之一秒*)。    
   
  CD   的中断请求:   CD驱动程序装载后,可通过INT   2FH中的有关功能进行设备请求,INT   2FH中的有关功能如下:    
      (1)INT   2FH   中的1500号功能:    
      功能:检测CD   ROM是否存在或者是否已驱动。    
      输入:BX=0,AH=15H,AL=00H。    
      返回:BX:CD   ROM的个数。CX:CD   ROM的盘号(0-A,1-B,2-C等等),CX为0则说明   不存在CD   ROM驱动   器或者驱动程序没有安装。    
           
      (2)INT   2FH   中的1510号功能:    
      功能:向MSCDEX发送CD   ROM设备操作请求。    
      输入:AH=15H,AL=10H,ES:BX=设备头地址。    
      返回:直接在ES:BX指向的缓冲区地特定字节中返回音频CD盘的有关信息或控制反   馈信息。    
      下面是一些常用操作设备请求头及缓冲区格式:    
           
      1)获取CD有关信息操作:    
      设备请求头结构:    
      00H   1A    
      01H   00    
      02H   03      
      03H~0DH(保留)    
      0EH~0FH(缓冲区偏移地址)    
      10H~11H(缓冲区段址)    
      12H~14H(缓冲区长度)    
      15H~19H(保留)    
      缓冲区内容如下:    
      A)获取CD   ROM状态:    
      输入:06   00   00   00   00(缓冲区长度为5)    
      返回:06   X1   X2   00   00   (X1字节第0位为1时CDROM处于开门状态,为   0为关门状态;X2字节第三位为1时CD   ROM中   无盘,为0有盘)    
      B)   获取CD唱盘信息:    
      输入:0A   00   00   00   00   00   00(缓冲区长度为7)    
      返回:0A   00   X1   X2   X3   X4   X5   (X1为盘中歌曲数目;X2为总扇区数;X3   为总秒数;X4为总分钟数;CD盘播放扇   区数:(X4*60+X3)*75+X2)    
      C)获取指定歌曲的起始时间:    
      输入:0B   00   00   00   00   00   00(长度为7)    
      返回:0B   X1   X2   X3   X4   00   00   (X1为歌曲序号;X2为起始扇区数   X3为起始秒数;X4为起始分钟数)    
           
      2)控制CD   ROM有关操作:    
      设备请求头结构:    
      00H   1AH    
      01H   00H    
      02H   0CH    
      03H~0DH(保留)    
      0EH~0FH(缓冲区偏移地址)    
      10H~11H(缓冲区段址)    
      12H~14H(缓冲区长度)    
      15H~19H(保留)    
      缓冲区内容包括两点:   1.CD   ROM出盒:输入:00(缓冲长度为1)   2.CD   ROM入盒:输入:05(缓冲长度为1)      
           
      3)播放音频CD的操作:    
      设备请求头结构:    
      00H   16H    
      01H   00H    
      02H   84H    
      03H~0CH(保留)    
      ODH   01H    
      OEH   起始扇区数;    
      0FH   起始秒数;    
      10H   起始分钟数;    
      11H   00H    
      12H~13H   要播放扇区数;    
           
      4)停止播放CD盘(不使用缓冲区)    
      设备头结构:    
      00H   0DH    
      01H   00H    
      02H   85H    
      03H~0CH   (保留)    
-------------------------------------------------------------------------   
现在介绍一下C++实现CD抓轨转WAV,CD抓轨的方法有好几种,现在介绍其中一种。我们可以通过API函数CreateFile获得设备句柄,再用API函数DeviceIoControl来实现对设备的访问获取信息。再此还会用到WAVE文件结构WAVEFORMATEX,再把读到的信息写到文件里生成WAVE格式的文件。

我们要用到的头文件有: ntddcdrm.h(NTDDK开发包) winioctl.h Mmreg.h

1、搜索光驱
我们可以用GetDriveType来判断设备类型,5为CDROM类型。返回类型可以参看MSDN,里面有详细介绍。

2、打开设备
用CreateFile获得设备句柄,例子如下:

HANDLE m_hDevice;
CString FileName=”F:”;
m_hDevice =CreateFile("////.//"+FileName, // 文件名路径
GENERIC_READ, // 读写方式
FILE_SHARE_READ | FILE_SHARE_WRITE, // 共享方式
NULL, // 默认的安全描述符
OPEN_EXISTING, // 创建方式
0, // 不需设置文件属性
NULL); // 不需参照模板文件

3、读取CD参数
得到了设备句柄,我们就可以用DeviceIoControl来获息相关信息.
DeviceIoControl函数原型: BOOL DeviceIoControl(
HANDLE hDevice, // 设备句柄
DWORD dwIoControlCode, // 控制码
LPVOID lpInBuffer, // 输入数据缓冲区指针
DWORD nInBufferSize, // 输入数据缓冲区长度
LPVOID lpOutBuffer, // 输出数据缓冲区指针
DWORD nOutBufferSize, // 输出数据缓冲区长度
LPDWORD lpBytesReturned, // 输出数据实际长度单元长度
LPOVERLAPPED lpOverlapped // 重叠操作结构指针
);


4、获取曲目
使用IOCTL_CDROM_READ_TOC控制码输出CDROM_TOC结构

BOOL bResult;
DWORD dwOutBytes;
CDROM_TOC CdromTOC; //曲目信息结构,详细请看MSDN
bResult=DeviceIoControl(m_hDevice,
IOCTL_CDROM_READ_TOC,NULL,0,
&CdromTOC,
sizeof(CdromTOC),
&dwOutBytes,
(LPOVERLAPPED)NULL);

5、获取曲目始点
DWORD CCdToWavDlg::GetStartSector(int track)
{
return (CdromTOC.TrackData[track-1].Address[1]*60*75 +
CdromTOC.TrackData[track-1].Address[2]*75 +
CdromTOC.TrackData[track-1].Address[3])-150;
}

6、获取曲目终点 DWORD CCdToWavDlg::GetEndSector(int track)
{
return (CdromTOC.TrackData[track].Address[1]*60*75 +
CdromTOC.TrackData[track].Address[2]*75 +
CdromTOC.TrackData[track].Address[3])-151;
}

7、读取曲目信息
使用IOCTL_CDROM_RAW_READ输入RAW_READ_INFO结构信息,输出来获取区域内容 BOOL CCdToWavDlg::ReadSector(int sector,BYTE Buffer[], int NumSectors)
{
DWORD dwOutBytes;
RAW_READ_INFO rri; //结构详细请看MSDN
rri.TrackMode =(TRACK_MODE_TYPE)2;
rri.SectorCount = (DWORD)NumSectors;
rri.DiskOffset =(DWORD64)(sector*CB_CDROMSECTOR);
if (DeviceIoControl(m_hDevice,IOCTL_CDROM_RAW_READ,
&rri,
sizeof(rri),
Buffer,
(DWORD)NumSectors*CB_AUDIO,&dwOutBytes,
(LPOVERLAPPED)NULL)) return true;
return false;
}

8、文件生成
  WAVE文件是非常简单的一种RIFF文件,它的格式类型为"WAVE"。RIFF块包含两个子块,这两个子块的ID分别是"fmt"和"data",其中"fmt"子块由结构WAVEFORMATEX所组成,其子块的大小就是sizeofof(WAVEFORMATEX),数据组成就是WAVEFORMATEX结构中的数据。WAVE文件的结构如下所示:

标志符(RIFF)

数据大小

格式类型("WAVE")

"fmt"

Sizeof(WAVEFORMATEX)

WAVEFORMATEX

"data"

声音数据大小

声音数据


WAVEFORMATEX结构原型:


typedef struct
{
WORD wFormatTag; //编码格式,包括WAVE_FORMAT_PCM,WAVEFORMAT_ADPCM等
WORD nChannels; //声道数,单声道为1,双声道为2
DWORD nSamplesPerSec; //采样频率
DWORD nAvgBytesPerSec; //每秒的数据量
WORD nBlockAlign; //块对齐
WORD wBitsPerSample; //WAVE文件的采样大小
WORD cbSize;
} WAVEFORMATEX; *PWAVEFORMATEX;

9、定义WAVE文件结构 DWORD m_WaveHeaderSize = 38;
DWORD m_WaveFormatSize = 18;
DWORD m_AudioDataSize =0;
DWORD m_WrittenBytes = 0;
WAVEFORMATEX m_WaveFormatEx;
m_WaveFormatEx.wFormatTag=WAVE_FORMAT_PCM ;
m_WaveFormatEx.nSamplesPerSec=48000;
m_WaveFormatEx.wBitsPerSample=16;
m_WaveFormatEx.nChannels=2;
m_WaveFormatEx.cbSize=0;
m_WaveFormatEx.nBlockAlign=m_WaveFormatEx.nChannels*(m_WaveFormatEx.wBitsPerSample/8);
m_WaveFormatEx.nAvgBytesPerSec=m_WaveFormatEx.nSamplesPerSec*m_WaveFormatEx.nBlockAlign;

10、创建新文件
CFile m_file;
CFileException fileException;
CString m_csFileName= m_SavePath;
m_file.Open(m_csFileName,CFile::modeCreate|CFile::modeReadWrite, &fileException);
int StartSect=GetStartSector(m_List.GetCurSel()+1);
int EndSect=GetEndSector(m_List.GetCurSel()+1);
DWORD Bytes2Read=(EndSect - StartSect)*CB_AUDIO;
m_AudioDataSize=Bytes2Read;
BYTE Data[CB_AUDIO*NSECTORS];

11、写入WAV文件头
WAV文件头一定要按顺序写入
m_file.SeekToBegin();
m_file.Write("RIFF",4);
unsigned int Sec=(m_AudioDataSize + m_WaveHeaderSize);
m_file.Write(&Sec,sizeof(Sec));
m_file.Write("WAVE",4);
m_file.Write("fmt ",4);
m_file.Write(&m_WaveFormatSize,sizeof(m_WaveFormatSize));
m_file.Write(&m_WaveFormatEx.wFormatTag,sizeof(m_WaveFormatEx.wFormatTag));
m_file.Write(&m_WaveFormatEx.nChannels,sizeof(m_WaveFormatEx.nChannels));
m_file.Write(&m_WaveFormatEx.nSamplesPerSec,sizeof(m_WaveFormatEx.nSamplesPerSec));
m_file.Write(&m_WaveFormatEx.nAvgBytesPerSec,sizeof(m_WaveFormatEx.nAvgBytesPerSec));
m_file.Write(&m_WaveFormatEx.nBlockAlign,sizeof(m_WaveFormatEx.nBlockAlign));
m_file.Write(&m_WaveFormatEx.wBitsPerSample,sizeof(m_WaveFormatEx.wBitsPerSample));
m_file.Write(&m_WaveFormatEx.cbSize,sizeof(m_WaveFormatEx.cbSize));
m_file.Write("data",4);
m_file.Write(&m_AudioDataSize,sizeof(m_AudioDataSize));

12、写入音频数据
把音频数据放到WAV文件头后写入
DWORD m_seek=46; //文件头长度为46个字,必须从46后写入
for (int sector = StartSect; (sector < EndSect); sector+=NSECTORS)
{
int Sectors2Read = ( (sector + NSECTORS) < EndSect )?NSECTORS:(EndSect-sector);
if (ReadSector(sector, Data, Sectors2Read))
{
m_file.Write(Data,CB_AUDIO*Sectors2Read);
m_file.Seek(m_seek+=CB_AUDIO*Sectors2Read,CFile::begin);
}
}
m_file.Close();