首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

C++ 實作 CD 抓軌轉 WAV 完整教學

     編輯:關於VC++

前言

本文介紹如何使用 C++ 實作 CD 音軌抓取並轉換為 WAV 檔案。CD 抓軌有多種方法,這裡示範其中一種:透過 CreateFile 取得裝置控制代碼,再以 DeviceIoControl 存取裝置資訊,結合 WAVE 檔案結構 WAVEFORMATEX 將讀取的資料寫入檔案,生成標準 WAV 格式。

所需標頭檔

  • ntddcdrm.h (NTDDK 開發套件)
  • winioctl.h
  • Mmreg.h

步驟 1:搜尋光碟機

使用 GetDriveType 判斷裝置類型,傳回值 5 代表 CD-ROM。詳細傳回類型請參考 MSDN。

步驟 2:開啟裝置

以 CreateFile 取得裝置控制代碼:

HANDLE m_hDevice;
CString FileName = _T("F:");
m_hDevice = CreateFile(
    _T("\\\\.\\") + FileName,      // 裝置路徑
    GENERIC_READ,                 // 讀取權限
    FILE_SHARE_READ | FILE_SHARE_WRITE, // 共享模式
    NULL,                         // 預設安全性描述元
    OPEN_EXISTING,                // 開啟現有裝置
    0,                            // 無特殊屬性
    NULL                          // 無範本檔案
);

步驟 3:讀取 CD 參數

取得控制代碼後,使用 DeviceIoControl 獲取相關資訊。函數原型:

BOOL DeviceIoControl(
    HANDLE hDevice,           // 裝置控制代碼
    DWORD dwIoControlCode,    // 控制碼
    LPVOID lpInBuffer,        // 輸入緩衝區指標
    DWORD nInBufferSize,      // 輸入緩衝區長度
    LPVOID lpOutBuffer,       // 輸出緩衝區指標
    DWORD nOutBufferSize,     // 輸出緩衝區長度
    LPDWORD lpBytesReturned,  // 實際傳回位元組數
    LPOVERLAPPED lpOverlapped // 重疊結構指標
);

步驟 4:取得音軌資訊 (TOC)

使用 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 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 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 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);
    return DeviceIoControl(
        m_hDevice, IOCTL_CDROM_RAW_READ,
        &rri, sizeof(rri),
        Buffer, (DWORD)NumSectors * CB_AUDIO,
        &dwOutBytes, (LPOVERLAPPED)NULL
    );
}

步驟 8:WAV 檔案結構

WAV 是簡單的 RIFF 檔案,格式類型為 "WAVE"。RIFF 區塊包含兩個子區塊:"fmt " 與 "data"。"fmt " 子區塊由 WAVEFORMATEX 結構組成,大小為 sizeof(WAVEFORMATEX);"data" 子區塊存放實際聲音資料。

RIFF 標識 (4 bytes)
資料大小 (4 bytes)
格式類型 "WAVE" (4 bytes)
"fmt " 標識 (4 bytes)
fmt 區塊大小 (4 bytes)
WAVEFORMATEX 結構
"data" 標識 (4 bytes)
聲音資料大小 (4 bytes)
聲音資料

WAVEFORMATEX 結構定義:

typedef struct {
    WORD  wFormatTag;      // 編碼格式,如 WAVE_FORMAT_PCM
    WORD  nChannels;       // 聲道數:1=單聲道, 2=雙聲道
    DWORD nSamplesPerSec;  // 取樣頻率
    DWORD nAvgBytesPerSec; // 每秒資料量
    WORD  nBlockAlign;     // 區塊對齊
    WORD  wBitsPerSample;  // 取樣位元數
    WORD  cbSize;          // 擴充資訊大小
} WAVEFORMATEX, *PWAVEFORMATEX;

步驟 9:定義 WAV 檔案標頭參數

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 檔案標頭 (順序不可錯)

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:寫入音訊資料

DWORD m_seek = 46; // 標頭長度 46 bytes,從此位置寫入資料
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();

測試環境與原始碼下載

以上程式在 VC7 + Windows 2000 + NTDDK 環境下測試通過。完整原始碼請至:https://www.vckbase.net/code/viewcode.asp?id=2278

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved