FC2ブログ

ホーム > CDDAファイルフォーマット > CD-DAの再生 第2部

CD-DAの再生 第2部



CD-DAの読み込み/再生手順は以下の通りです。

  1. CreateFile関数を使ってCDROMのドライブを開いて、CDROMのデバイスハンドルを取得します。
  2. CDROMのトラック数や各トラックの情報を格納したCDROM_TOC構造体を取得し、目的の曲の開始セクタ終了セクタを算出します。
  3. WAVE出力デバイスを開きます。
  4. WAVE出力デバイスからのコールバックイベントトリガにして、CD-DAの各セクタから音源データDeviceIoControl関数を使って読み出します。
  5. 終了セクタまで読み込むか、演奏停止イベントがあればWAVE出力デバイスを停止してから閉じます。



CD-DAのTOC(Table Of Contents)を読み込んで、CDDA_TOC構造体CDROM_SESSION構造体を作成します。

//TOCの読み込み
BOOL CDDAFile::ReadTOC()
{
ASSERT(m_hDrive!=INVALID_HANDLE_VALUE);
if (m_hDrive==INVALID_HANDLE_VALUE) return FALSE;

//CDROM_READ_TOC_EX構造体を作成します。
CDROM_READ_TOC_EX TOCEx={0};
TOCEx.Format=CDROM_READ_TOC_EX_FORMAT_FULL_TOC;
TOCEx.Msf=1;

//読み込むCDROM_TOC_FULL_TOC_DATA構造体のサイズを取得します。
int sizeFtd=sizeof(CDROM_TOC_FULL_TOC_DATA);
//バッファを確保します。
CByteArray byteArray;
byteArray.SetSize(sizeFtd);
CDROM_TOC_FULL_TOC_DATA* pFtd=(CDROM_TOC_FULL_TOC_DATA*)byteArray.GetData();
::ZeroMemory(pFtd,sizeFtd);

DWORD BytesReturned=0;
BOOL bResult=::DeviceIoControl(m_hDrive,IOCTL_CDROM_READ_TOC_EX,&TOCEx,sizeof(CDROM_READ_TOC_EX),
pFtd,sizeFtd,&BytesReturned,NULL);
if (!bResult) return FALSE;

//必要なメモリーサイズを算出します。
sizeFtd=(pFtd->Length[0]<<8)+pFtd->Length[1]+sizeof(UCHAR)*2;
if (sizeFtd%4) sizeFtd=(sizeFtd/4+1)*4;

//バッファを再確保します。
CByteArray byteArray;
byteArray.SetSize(sizeFtd);
pFtd=(CDROM_TOC_FULL_TOC_DATA*)byteArray.GetData();
::ZeroMemory(pFtd,sizeFtd);

//CDROM_TOC_FULL_TOC_DATA構造体をバッファに読み込みます。
bResult=::DeviceIoControl(m_hDrive,IOCTL_CDROM_READ_TOC_EX,&TOCEx,sizeof(CDROM_READ_TOC_EX),
pFtd,sizeFtd,&BytesReturned,NULL);
if (!bResult) return FALSE;

//CDDA_TOC構造体メンバの初期化
::ZeroMemory(&m_TOC,sizeof(CDDA_TOC));
//CDROM_SESSION構造体メンバの初期化
::ZeroMemory(&m_session,sizeof(CDROM_SESSION));

//セッション番号の初期化
m_sessionCDDA=0; //CDDAデータのあるセッションが見当たらない場合は0
m_session.FirstSession=1; //開始セッション番号は1
m_session.LastSession=0; //終了セッション番号を0にします。

//トラック番号の初期化
m_TOC.FirstTrack=1; //開始トラック番号は1
m_TOC.LastTrack=0; //終了トラック番号を0にします。

//読み込んだCDROM_TOC_FULL_TOC_DATA構造体のサイズから、
//Descriptors要素のCDROM_TOC_FULL_TOC_DATA_BLOCK構造体の個数を算出します。

int nBlock=(BytesReturned-sizeof(CDROM_TOC_FULL_TOC_DATA))/sizeof(CDROM_TOC_FULL_TOC_DATA_BLOCK);

UCHAR EndAddress[4];
for (int i=0;i<nBlock;i++){
//「NCITS XXX T10/1363-D Revision - 02A」の「Table 350」を参照して下さい。
CDROM_TOC_FULL_TOC_DATA_BLOCK* pDesc=&pFtd->Descriptors[i];
int sessionNumber=pDesc->SessionNumber;
switch(pDesc->Point){
case 0xA0: //セッション開始トラック番号
m_session.sessionInfo[sessionNumber-1].SessionStart=pDesc->Msf[0];
break;
case 0xA1: //セッション終了トラック番号
m_session.sessionInfo[sessionNumber-1].SessionStop=pDesc->Msf[0];
break;
case 0xA2: //セッションの終端位置の場合
//セッションの終端位置のセクタ番号を格納します。
EndAddress[0]=0;
EndAddress[1]=pDesc->Msf[0];
EndAddress[2]=pDesc->Msf[1];
EndAddress[3]=pDesc->Msf[2];
m_session.sessionInfo[sessionNumber-1].SessionEndFAD=MSF2FAD(EndAddress)-150;
break;
default:
//Pointフィールドが1から99までで、現在の位置データの場合。
if ((pDesc->Point>=1)&&(pDesc->Point<=0x63)&&(pDesc->Adr==ADR_ENCODES_CURRENT_POSITION))
{
//トラック番号の取得
UINT track=pDesc->Point;
//Q Sub-Channel属性を格納します。
m_TOC.TrackData[track-1].Addr=pDesc->Adr;
//トラックの属性を格納します。
m_TOC.TrackData[track-1].Control=pDesc->Control;
//開始セクタ番号を格納します。
m_TOC.TrackData[track-1].Address[0]=0;
m_TOC.TrackData[track-1].Address[1]=pDesc->Msf[0];
m_TOC.TrackData[track-1].Address[2]=pDesc->Msf[1];
m_TOC.TrackData[track-1].Address[3]=pDesc->Msf[2];
m_TOC.TrackData[track-1].Session=sessionNumber;
//トラック番号が終了トラック番号より大きい場合、終了トラック番号に設定します。
if (m_TOC.LastTrack<track) m_TOC.LastTrack=track;

//セッション番号に変更がある場合。
if (m_session.LastSession!=sessionNumber){
m_session.LastSession=sessionNumber;
//セッション開始セクタ番号に、現在のトラック開始セクタ番号を代入します。
m_session.sessionInfo[sessionNumber-1].SessionFAD=MSF2FAD(m_TOC.TrackData[track-1].Address);
//トラックがデータトラックでない場合は、セッション番号を保存します。
//「NCITS XXX T10/1363-D Revision - 02A」の「Table 337」を参照して下さい。

if ((!m_sessionCDDA)&&(!(m_TOC.TrackData[track-1].Control&4)))
m_sessionCDDA=sessionNumber;
}
}
break;
}
}
//最終トラックの次のトラックに、最終セッション終了セクタ位置を書き込みます。
m_TOC.TrackData[m_TOC.LastTrack].Address[0]=EndAddress[0];
m_TOC.TrackData[m_TOC.LastTrack].Address[1]=EndAddress[1];
m_TOC.TrackData[m_TOC.LastTrack].Address[2]=EndAddress[2];
m_TOC.TrackData[m_TOC.LastTrack].Address[3]=EndAddress[3];

return TRUE;
}


CDテキストを読み込む際に必要になるCRC計算関数です。

//CRC table for the CRC ITU-T V.41 0x0x1021 (x^16+x^12+x^15+1)
const WORD crc_itu_t_table[256]={
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};

//CRC チェック(CRC-ITU-T x16+x12+x5+1)
// pData:計算するバイトデータへのポインタ
// length:計算するバイト数

WORD __stdcall _GetCRC_ITU_T(BYTE* pData,UINT length,WORD wCRCDefault)
{
WORD crc=wCRCDefault; //CRCを計算する変数
BYTE* buffer=pData;
while (length--)
crc=(crc<<8)^crc_itu_t_table[((crc>>8)^(*buffer++))&0xff];
return crc;
}


CDROMに書かれているCDテキストを読み出します。

//CDテキストの読み込み
// retry:読み込みに失敗した場合のリトライ回数。

BOOL CDDAFile::ReadCDText(int retry)
{
if (m_hDrive==INVALID_HANDLE_VALUE) return FALSE;

//CDテキストを格納する文字列リストを削除します。
for(int i=0;i<16;i++) m_listCDText[i].RemoveAll();

//CDDBのテキストデータも削除します。
m_listExtd.RemoveAll();
m_listExtt.RemoveAll();
m_strCDDB.Empty();
m_strYear.Empty();
m_strPlayOrder.Empty();

//CDROM_READ_TOC_EX構造体を作成します。
CDROM_READ_TOC_EX TOCEx={0};
TOCEx.Format=CDROM_READ_TOC_EX_FORMAT_CDTEXT;

//読み込むCDROM_TOC_CD_TEXT_DATA構造体のサイズを取得します。
int sizeCDText=sizeof(CDROM_TOC_CD_TEXT_DATA);
//CDテキストを格納するバッファを確保します。
CByteArray byteArray;
byteArray.SetSize(sizeCDText);
CDROM_TOC_CD_TEXT_DATA* pCDTextData=(CDROM_TOC_CD_TEXT_DATA*)byteArray.GetData();
::ZeroMemory(pCDTextData,sizeCDText);

DWORD BytesReturned=0;
BOOL bResult=::DeviceIoControl(m_hDrive,IOCTL_CDROM_READ_TOC_EX,&TOCEx,sizeof(CDROM_READ_TOC_EX),
pCDTextData,sizeCDText,&BytesReturned,NULL);
if (!bResult) return FALSE;

//必要なメモリーサイズを算出します。
sizeCDText=(pCDTextData->Length[0]<<8)+pCDTextData->Length[1]+sizeof(UCHAR)*2;
if (sizeCDText%4) sizeCDText=(sizeCDText/4+1)*4;

//バッファを再確保します。
byteArray.SetSize(sizeCDText);
pCDTextData=(CDROM_TOC_CD_TEXT_DATA*)byteArray.GetData();
//先頭のCDROM_TOC_CD_TEXT_DATA_BLOCK構造体へのポインタを取得します。
CDROM_TOC_CD_TEXT_DATA_BLOCK* pDesc=pCDTextData->Descriptors;

int nBlock;
BOOL bError=FALSE;
//読み込みエラーが出た場合は、retry回数分繰り返します。
for (int i=0;i<retry;i++){
::ZeroMemory(pCDTextData,sizeCDText);

//CDROM_TOC_CD_TEXT_DATA構造体をバッファに読み込みます。
bResult=::DeviceIoControl(m_hDrive,IOCTL_CDROM_READ_TOC_EX,&TOCEx,sizeof(CDROM_READ_TOC_EX),
pCDTextData,sizeCDText,&BytesReturned,NULL))
if (!bResult) return FALSE;

//読み込んだCDROM_TOC_CD_TEXT_DATA構造体のサイズから、
//Descriptors要素のCDROM_TOC_CD_TEXT_DATA_BLOCK構造体の個数を算出します。

nBlock=(BytesReturned-sizeof(CDROM_TOC_CD_TEXT_DATA))/sizeof(CDROM_TOC_CD_TEXT_DATA_BLOCK);

bError=FALSE;
//読み込んだCDテキストのエラーチェック
for(int i=0;i<nBlock;i++){
//ブロックの先頭アドレスを取得します。
CDROM_TOC_CD_TEXT_DATA_BLOCK* pBlock=&pDesc[i];
UCHAR packType=pBlock->PackType;
//パックタイプの上位ビットが8でない場合は、エラーとします。
if ((PackType&0xF0)!=0x80){
bError=TRUE;
break;
}
//CRCを取得して、正しいかどうかチェックします。
WORD crc=~((pBlock->CRC[0]<<8)|pBlock->CRC[1]);
WORD crcRes=_GetCRC_ITU_T((BYTE*)pBlock,sizeof(CDROM_TOC_CD_TEXT_DATA_BLOCK)-2,0);
if (crc!=crcRes){
bError=TRUE;
break;
}
}
if (!bError) break;
}
if (bError) return FALSE;

//文字列リストにトラック数分配列を確保します。
for(int i=0;i<16;i++) m_listCDText[i].SetSize(1+m_TOC.LastTrack);

//前のブロックの繰越文字列を保管するバッファを用意します。
CStringA strALast; //マルチバイト
CStringW strWLast; //UNICODE
for(int i=0;i<nBlock;i++){
//ブロックの先頭アドレスを取得します。
CDROM_TOC_CD_TEXT_DATA_BLOCK* pBlock=&pDesc[i];
UCHAR PackType=pBlock->PackType; //パックタイプ
UCHAR track=pBlock->TrackNumber; //関連するトラック番号
UCHAR chPos=pBlock->CharacterPosition; //繰越文字列に対するこのブロックの文字列位置(省略可)
//文字列の位置が0の場合は、前のブロックの繰越文字列を削除します。

if (!chPos){
strALast.Empty();
strWLast.Empty();
}
//ブロック番号を取得します。(省略可)
UCHAR block=pBlock->BlockNumber;
//パックタイプが文字列があるブロックの場合。
if ((0x80<=PackType)&&(PackType<0x8F)&&(PackType!=0x88)&&(PackType!=0x89)){
//テキストデータがUNICODEの場合。
if (pBlock->Unicode){
CStringW strWText; //文字列一時保管用バッファ
for(int j=0;j<6;j++){
//一文字取り出します。
WCHAR wch=pBlock->WText[j];
//デリミタ文字の場合。
if (wch=='\0'){
//文字位置をチェックします。(省略可)
int sizeLast=strWLast.GetLength();
if (chPos<15) ASSERT(chPos==sizeLast);
else if (chPos==15) ASSERT(sizeLast>=12);
chPos=0;
//繰越文字列に、このブロックで取得した文字列を追加します。
strWLast.Append(strWText);
//繰越文字列に文字がある場合。
if (strWLast.GetLength()){
#ifdef _UNICODE
//システムがUNICODEの場合は、
//文字列リストにそのまま代入します。

m_listCDText[PackType-0x80][track++]=strWLast;
#else//_UNICODE
//システムがマルチバイトの場合は、
//文字列を一度マルチバイトに変換してから代入します。

CStringA strA=(CStringA)strWLast;
m_listCDText[PackType-0x80][track++]=strA;
#endif//_UNICODE
//代入済みの繰越文字列は削除します。

strWLast.Empty();
strWText.Empty();
}
}
//代入するトラック番号が最初のトラックより大きくて、
//ブロックの先頭文字がタブ文字の場合は、
//一つ前のトラックの内容をコピーします。

else if ((track>m_TOC.FirstTrack)&&(wch=='\t')){
CString strPrev=m_listCDText[PackType-0x80][track-1];
m_listCDText[PackType-0x80][track]=strPrev;
strWLast.Empty();
strWText.Empty();
break;
}
//その他の文字の場合は、保管用バッファの末尾に追加します。
else{
//ワードの上位バイトと下位バイトを入れ替えます。
WORD wlo=wch&0x00FF;
WORD whi=(wch>>8)&0x00FF;
wch=(wlo<<8)|whi;
strWText.AppendChar(wch);
}
}
//ブロックの末尾に達したら、このブロックで取得した文字列を、
//繰越文字列の末尾に追加します。

strWLast.Append(strWText);
}
//ブロックの文字列がマルチバイトの場合。
else{
CStringA strAText; //文字列一時保管用バッファ
for(int j=0;j<12;j++){
//一文字取り出します。
UCHAR ch=pBlock->Text[j];
//デリミタ文字の場合。
if (ch=='\0'){
//文字位置をチェックします。(省略可)
int sizeLast=strALast.GetLength();
if (chPos<15) ASSERT(chPos==sizeLast);
else if (chPos==15) ASSERT(sizeLast>=12);
chPos=0;
//繰越文字列に、このブロックで取得した文字列を追加します。
strALast.Append(strAText);
//繰越文字列に文字がある場合。
if (strALast.GetLength()){
#ifdef _UNICODE
//システムがUNICODEの場合は、
//文字列を一度UNICODEに変換してから代入します。

CStringW strW=(CStringW)strALast;
m_listCDText[PackType-0x80][track++]=strW;
#else//_UNICODE
//システムがマルチバイトの場合は、
//文字列リストにそのまま代入します。

m_listCDText[PackType-0x80][track++]=strALast;
#endif//_UNICODE
//代入済みの繰越文字列は削除します。

strALast.Empty();
strAText.Empty();
}
}
//代入するトラック番号が最初のトラックより大きくて、
//ブロックの先頭文字がタブ文字の場合は、
//一つ前のトラックの内容をコピーします。

else if ((track>m_TOC.FirstTrack)&&(ch=='\t')){
CString strPrev=m_listCDText[PackType-0x80][track-1];
m_listCDText[PackType-0x80][track]=strPrev;
strALast.Empty();
strAText.Empty();
break;
}
//その他の文字の場合は、保管用バッファの末尾に追加します。
else strAText.AppendChar(ch);
}
//ブロックの末尾に達したら、このブロックで取得した文字列を、
//繰越文字列の末尾に追加します。

strALast.Append(strAText);
}
}
//パックタイプが0x88,0x89,0x8F(文字列ではない)の場合。
else{
//バイナリーデータをダンプ文字列に変換します。
CString str;
for(int j=0;j<12;j++){
BYTE bin=pBlock->Text[j];
str.AppendFormat(_T("%02X"),bin);
if (j<11) str.AppendChar(',');
}
m_listCDText[PackType-0x80][track++]=str;
}
}
return TRUE;
}


まず最初にCDROMデバイスを開いて、CDROMのTOCとCDテキストを読み出します。

//CDDAを開いてTOCとCDTextを読み込みます。
// tchDrive:CDROMのドライブ番号

BOOL CDDAFile::LoadCDDA(TCHAR tchDrive)
{
//CDROMのデバイスハンドルが有効なら、クローズします。
if (m_hDrive!=INVALID_HANDLE_VALUE) CloseCDDA();
//演奏を停止して、セクタ情報を初期化します。
Delete();
//CDROMデバイスのオープン
if (!OpenCDDA(tchDrive)) return FALSE;
//TOCの読み込みに失敗したら、
//CDROMデバイスをクローズしてから、FALSEを返して終了します。

if (!ReadTOC()){
CloseCDDA();
return FALSE;
}
//CDテキストを読み込みます。
ReadCDText();

return TRUE;
}


CDROMデバイスを開いた後、CD-DAの開始トラック番号と終了トラック番号を取得します。

//トラック範囲の取得
BOOL CDDAFile::GetTrackRange(LPDWORD lpdwFirstTrack,LPDWORD lpdwLastTrack,UINT session)
{
if(m_hDrive==INVALID_HANDLE_VALUE) return FALSE;
if ((m_TOC.FirstTrack==0)||(m_TOC.LastTrack==0)) return FALSE;

//引数のセッション番号が無効な値の場合は、CDDAデータのあるセッション番号とします。
if ((m_session.FirstSession>session)||(m_session.LastSession<session))
session=m_sessionCDDA;

if (!session) return FALSE;

//CDROM_SESSION構造体から開始トラック番号を取得します。
DWORD FirstTrack=m_session.sessionInfo[session-1].SessionStart;
//CDROM_SESSION構造体から終了トラック番号を取得します。
DWORD LastTrack =m_session.sessionInfo[session-1].SessionStop;

//引数の開始トラックへのポインタが有効な場合は、開始トラック番号を取り込みます。
if (AfxIsValidAddress(lpdwFirstTrack,sizeof(DWORD)))
*lpdwFirstTrack=FirstTrack;
//引数の終了トラックへのポインタが有効な場合は、終了トラック番号を取り込みます。
if (AfxIsValidAddress(lpdwLastTrack,sizeof(DWORD)))
*lpdwLastTrack=LastTrack;

return TRUE;
}


取得した開始トラック番号と終了トラック番号をもとにトラック番号から「Track??.cdda」という名のファイル名を作成して、CD-DAのトラックを順にオープンして再生していきます。

//指定ファイル名に相当するトラックをオープンします。
// lpszFileName:CDDAトラックのパス名
// :ドライブ番号:\Track??.cda(cdaファイルを読み込みます。)
// :ドライブ番号:\Track??.cdda(cdaファイルがない)
// :上記以外はCWaveFile::OpenFile関数を呼び出します。

BOOL CDDAFile::OpenFile(LPCTSTR lpszFileName)
{
//演奏を停止して、セクタ情報を初期化します。
Delete();

TCHAR drive='\0'; //入力されたCDDAパス名のドライブ番号
UINT uTrack=1; //トラック番号
CString strExt; //拡張子
//入力パス名を解析して、ドライブ名、トラック番号、拡張子文字列を取得します。
if (!_ParseCDDAPathName(lpszFileName,&drive,&uTrack,strExt))
{
CloseCDDA();
return CWaveFile::OpenFile(lpszFileName);
}

//CDROMのデバイスハンドルが無効か、CDROMのドライブ番号が変更になる場合、
//CD-DAを開いてTOCとCDTextを読み込みます。

if ((m_hDrive==INVALID_HANDLE_VALUE)||(m_tchDrive!=drive))
{
if (!LoadCDDA(drive)) return FALSE;
}

//トラックがデータトラックの場合は、エラーを返して終了します。
//「NCITS XXX T10/1363-D Revision - 02A」の「Table 337」を参照して下さい。

if(m_TOC.TrackData[uTrack-1].Control&4) return FALSE;

//トラックのセクタ範囲を取得します。
GetSectorRange(uTrack,&m_nStartSector,&m_nStopSector);

//セクタ数を算出します。
m_nTotalSector=m_nStopSector-m_nStartSector;

//WAVEFORMATEX構造体を作成します。
//(CD-DAの音源は周波数44100Hz、2チャンネル、16ビットデータです。)

m_wfx.cbSize=sizeof(m_WAVEFORMATEX);
m_wfx.wFormatTag=WAVE_FORMAT_PCM;
m_wfx.nChannels=2;
m_wfx.wBitsPerSample=16;
m_wfx.nBlockAlign=m_wfx.nChannels*m_wfx.wBitsPerSample/8;
m_wfx.nSamplesPerSec=44100;
m_wfx.nAvgBytesPerSec=m_wfx.nSamplesPerSec*m_wfx.nBlockAlign;

//音源データサイズと実際のデータバイト数を設定します。
m_nDataSize=m_nDataSaved=m_nTotalSector*RAW_SECTOR_SIZE;
//オーナーウィンドウがあれば、プログレスバーの位置を通知します。
if (m_pWndOwner) m_pWndOwner->PostMessage(PBM_SETPOS,m_nDataSaved,0);

return TRUE;
}


CD-DA再生時に音源データ読み込みのために、基底クラスから呼び出されるオーバーライド関数です。

//CDDA音源データのストリーム読み込み
UINT CDDAFile::StreamIn(LPVOID pBuffer,UINT nBufSize,UINT* pIndexSample)
{
ASSERT(AfxIsValidAddress(pBuffer,nBufSize));
ASSERT(AfxIsValidAddress(pIndexSample,sizeof(UINT)));

//CDROMのデバイスハンドルが無効な場合は、
//CWaveFile::StreamIn関数を呼び出します。

if (m_hDrive==INVALID_HANDLE_VALUE)
return CWaveFile::StreamIn(pBuffer,nBufSize,pIndexSample);

//指定位置にシークします。
m_indexSector=*pIndexSample*m_wfx.nBlockAlign/RAW_SECTOR_SIZE;
//CDDAトラックを読み込む。
UINT readBytes=ReadCDDA((PBYTE)pBuffer,nBufSize);

//読み込みに失敗したら、FALSEを返して終了します。
if (!readBytes) return FALSE;
//指定の演奏位置を読み込んだ分だけ進めます。
*pIndexSample+=readBytes/m_wfx.nBlockAlign;
//読み込んだバイト数を返して終了します。
return readBytes;
}


CDDAFileクラスのインスタンスを作成した後、LoadCDDA関数でCDROMデバイスを開いて、CDROMのTOCとCDテキストを読み出します。
次にGetTrackRange関数で読み込んだTOCから、CD-DAの開始トラック番号と終了トラック番号を取得してから、再生するトラック番号から「Track??.cdda」という名のファイル名を作成し、OpenFile関数でCD-DAの指定トラックを開きます。
エラーがなければCWaveFile::Play関数で演奏を開始します。演奏を中断したり演奏終了後、WAVE出力デバイスを閉じる場合は、CWaveFile::Stop関数を呼び出して下さい。
指定したトラックが演奏されれば成功です。

サンプルプログラム(VisualC++net2003ソリューション)CDDAFileTest 01.zip

上記のzipファイルをダウンロードしてからWinRAR等で解凍し、「CDDAFileTest 01」フォルダー内にある「CDDAFileTest.sln」を開きます。
「F5」キーを押すと、ビルド確認のダイヤログが表示されるので「Yes」を選択してソリューションをビルドします。



ビルドが終わると直ちにプログラムが自動起動して下の図にあるダイヤログが表示されます。



パソコン本体のCDROMドライブに音楽CDを入れてCDROMドライブのドアをクローズし、ダイヤログにある「CDDA」ボタンをクリックするとCDが読み込まれてトラック1から再生されます。
(※この時ウィンドウズのCDROMドライブのAUTOPLAY機能は解除しておいて下さい。)

CDテキストがあればダイヤログのタイトルバーにアーティスト名とアルバムタイトル名が表示され、コンボボックスにCD内の全ての曲名が表示されます。またCDテキストがない場合は、ダイヤログのタイトルバーに変更はなく、曲名はトラック1から順に「Track??.cdda」と表示されます。
スライドバーの動きに合わせて、CD-DA音源が再生されるか確認します。



メニューから「ファイル」/「CDの一括保存...」を選択し、「書き込みの設定」ダイヤログを表示させます。



「フォルダー名」、「書き込む場所」、「曲名チェックボックス」、「曲名」等を確認/変更してから「適用する」ボタンを押すと、一括保存シーケンスが開始します。



一曲目のトラックの保存が終了すると、2曲目の保存開始とともに、保存済みのWAVEファイルの再生が始まります。



「曲名チェックボックス」にチェックのあった全てのトラックの保存が終了すると、一番最初に保存したWAVEファイルに戻ってから、また順に再生が始まります。

<<CD-DAの再生 第1部ページの先頭CDDBの読み込み>>



スポンサーサイト



コメント: 0

この記事へのコメント
ブログ著者にのみ知らせます。

Trackback+Pingback: 0

TrackBack URL for this entry
http://hiroshi0945.blog75.fc2.com/tb.php/50-4132b9e8
Listed below are links to weblogs that reference
CD-DAの再生 第2部 from マルチメディアファイルフォーマット

Home > CDDAファイルフォーマット > CD-DAの再生 第2部

タグクラウド
ブロとも申請フォーム

この人とブロともになる

ブロとも一覧

このページの先頭へ戻る