faad2ライブラリーを使用してAACファイルのデコードを行い、同時に再生するプログラムについて解説します。
AACファイルをデコードするために必要なインクルードファイルとライブラリです。
尚ライブラリーについては前回のfaad2-2.7のインストールで作成したものです。
extern "C"
{
#include "..\\faad2-2.7\include\neaacdec.h"
}
#pragma comment(lib,"SoundLib\\libfaad-vc7.lib")
AACファイルを再生するために必要なインクルードファイルです。
#include "SoundLib\MP3File.h"
倍精度実数から四捨五入した整数を求めるマクロです。
//四捨五入した整数を求める
#define ROUND(data) ((int)((double)data+(double)0.5))
AACファイルの周波数設定時に参照する定数テーブルです。
//周波数テーブル
static const long g_aacFreq[]={
96000,88200,64000,48000,44100,32000,
24000,22050,16000,12000,11025, 8000,
-1, -1, -1, -1
};
ADTSヘッダーを解析して、デコードに必要な各値を格納する構造体とstdcall関数です。
//ADTS_HEADER構造体
typedef struct tagADTS_HEADER{
int iVersion; //4:MPEG4,2:MPEG2
BOOL bProtect; //保護属性 0:なし,1:有り
int iProfile; //音質と圧縮率 0:main,01:LC,2:SSR,3:(予約)
DWORD sampleRate; //周波数
WORD nChannel; //チャンネル数
BOOL bCopy; //0:オリジナル,1:コピー
DWORD nFrameSize; //AACフレーム長(バイト、ヘッダ込み)
DWORD nFullness; //ADTSバッファ残量 0x7FF VBR
}ADTS_HEADER;
//ADTSヘッダーを解析して、デコードに必要な各値をADTS_HEADER構造体に格納します。
BOOL __stdcall _ParseADTSHeader(const PBYTE pHeader,ADTS_HEADER* pADTSHeader)
{<
ASSERT(!::IsBadReadPtr(pHeader,7));
ASSERT(!::IsBadWritePtr(pADTSHeader,sizeof(ADTS_HEADER)));
//先頭バイトは0xFF
if ((pHeader[0]&0xFF)!=0xFF) return FALSE;
//次のバイトの上位3ビットは1
if ((pHeader[1]&0xF0)!=0xF0) return FALSE;
//MPEGバージョン番号を取得します。
//header[1]:b3=0:MPEG4,1:MPEG2
int version=4-((pHeader[1]>>3)&1)*2;
//layer (常に00)
//header[1]:b2b1=00
int layer=(pHeader[1]>>1)&3;
if (layer!=0) return FALSE;
//保護属性を取得します。
//header[1]:b0
BOOL bProtect=pHeader[1]&1;
//音質と圧縮率を取得します。
//header[2]:b7b6
int profile=(pHeader[2]>>6)&3;
//周波数を取得します。
//header[2]:b5b4b3b2
int index=(pHeader[2]>>2)&0x0F;
int sampleRate=g_aacFreq[index];
if (sampleRate==-1) return FALSE;
//private bit
//header[2]:b1
BOOL bPrivateBit=(pHeader[2]>>1)&1;
//チャンネル数を取得します。
//header[2]:b0,header[3]:b7b6
int channel=((pHeader[2]&1)<<2)|((pHeader[3]>>6)&3);
if (channel==0) return FALSE;
//0:オリジナル,1:コピー
//header[3]:b5
BOOL bCopy=(pHeader[3]>>5)&1;
//home
//header[3]:b4
BOOL bHome=(pHeader[3]>>4)&1;
//copyright identification bit
//header[3]:b3
BOOL bCopyrightIDbit=(pHeader[3]>>3)&1;
//copyright identification start
//header[3]:b2
BOOL bCopyrightIDstart=(pHeader[3]>>2)&1;
//フレームサイズ
//header[3]:b1b0
//header[4]:b7~b0
//header[5]:b7b6b5
DWORD frameSize=((((DWORD)pHeader[3])<<11)&0x1800)|
((((DWORD)pHeader[4])<<3)&0x7F8)|
((((DWORD)pHeader[5])>>5)&0x007);
//ADTSバッファ残量
//header[5]:b4~b0
//header[6]:b7~b2
DWORD nFullness=((((DWORD)pHeader[5])<<6)&0x7c0)|
((((DWORD)pHeader[6])>>2)&0x03F);
//no raw data blocks in frame
//header[6]:b1b2
int remainBlocks=pHeader[6]&3;
//データをADTS_HEADER構造体に格納します。
pADTSHeader->iVersion=version;
pADTSHeader->bProtect=bProtect;
pADTSHeader->iProfile=profile;
pADTSHeader->sampleRate=sampleRate;
pADTSHeader->nChannel=channel;
pADTSHeader->bCopy=bCopy;
pADTSHeader->nFrameSize=frameSize;
pADTSHeader->nFullness=nFullness;
return TRUE;
}
AACファイルの再生をWAVEファイルと同様の操作にするために、CWaveFileクラスの派生クラスであるCMP3Fileクラスを基底クラスとする、CAACFileクラスを独自に定義しました。
//このヘッダーでneaacdec.hのインクルードは避けたいので、構造体の型のみ宣言しておきます。
typedef struct faacEncConfiguration faacEncConfiguration;
typedef faacEncConfiguration *faacEncConfigurationPtr;
typedef void *NeAACDecHandle;
class CAACFile : public CMP3File
{
public:
//コンストラクタ
CAACFile(CWnd* pWndOwner=NULL);
//デストラクタ
virtual ~CAACFile();
//メンバー変数の初期化
void Init();
//メンバー変数の削除
virtual void Delete();
enum streamFormat {
RAW_STREAM=0,
ADTS_STREAM=1,
};
//AACファイルがオープンされているかどうか?(オーバーライド)
virtual BOOL IsAACFile(){return (m_hDecoder!=0);};
//読み込み中のデバイスハンドルの有効/無効を基底クラスに返します。(オーバーライド)
virtual BOOL IsOpen(){return (m_hDecoder)? TRUE:CMP3File::IsOpen();};
//音楽ファイルからWave音源に変換してストリーム読み込み(オーバーライド)
virtual UINT StreamIn(LPVOID pBuffer,UINT nBufSize,UINT* pIndexSample);
//faacEncConfiguration構造体の初期化
void InitEncConfiguration();
//faacEncConfiguration構造体の取得(オーバーライド)
virtual BOOL GetEncConfiguration(faacEncConfigurationPtr pConfigAAC,faacEncConfigurationPtr pConfigMP4=NULL);
//faacEncConfiguration構造体の設定(オーバーライド)
virtual BOOL SetEncConfiguration(faacEncConfigurationPtr pConfigAAC,faacEncConfigurationPtr pConfigMP4=NULL);
//faacバージョンの取得
const char* GetFaacVersion(){return m_pszFaacId;};
//faacエンコーダー名の取得
const char* GetFaacCopyright(){return m_pszFaacCopyright;};
protected:
//AACファイルを開く(オーバーライド)
// lpszFileName:ファイルのパス名
virtual BOOL ReadFile(LPCTSTR lpszFileName);
//ヘッダーの読み込み
BOOL ReadHeader(CFile* infile);
//AACファイルの書き込み(オーバーライド)
virtual BOOL WriteFile(LPCTSTR lpszFileName);
//AACストリームの書き込み
BOOL StreamOut(CFile* outfile,faacEncConfigurationPtr pConfig=NULL);
//デコーダー
NeAACDecHandle m_hDecoder;
int m_headerType; //0:RAW,1:ADIF,2:ADTS
CByteArray m_byteInCache; //入力側キャッシュ
//エンコーダー
char* m_pszFaacId; //バージョン
char* m_pszFaacCopyright; //著作権
faacEncConfiguration* m_pConfig;//エンコーダー構成
};
CAACFileクラスのコンストラクタ/デストラクタです。
//コンストラクタ
CAACFile::CAACFile(CWnd* pWndOwner)
:CMP3File(pWndOwner)
{
Init();
//faacEncConfiguration構造体の初期化
InitEncConfiguration();
}
//デストラクタ
CAACFile::~CAACFile()
{
delete m_pConfig;
Delete();
}
CAACFileクラスのメンバ関数です。
//メンバー変数の初期化
void CAACFile::Init()
{
//デコーダー
m_hDecoder=0;
m_headerType=RAW;
m_byteInCache.RemoveAll();
}
//メンバー変数の削除
void CAACFile::Delete()
{
CMP3File::Delete();
if (m_hDecoder) ::NeAACDecClose(m_hDecoder);
Init();
}
CWaveFile::OpenFile関数から呼び出されるCWaveFile::ReadFile関数にオーバーライドして、AACファイルを開いた後、CAACFile::ReadHeader関数を呼び出します。
//AACファイルをオープンします。
// lpszFileName:オープンするファイル名。
BOOL CAACFile::ReadFile(LPCTSTR lpszFileName)
{
//ファイルのオープンに失敗した場合は、CDDAFile::ReadFile関数を呼び出します。
CFile* infile=new CFile;
if (!infile->Open(lpszFileName,CFile::modeRead|CFile::shareDenyNone)){
delete infile;
return CDDAFile::ReadFile(lpszFileName);
}
//ヘッダー読み込みに失敗した場合は、CMP3File::ReadFile関数を呼び出します。
if (!ReadHeader(infile)){
infile->Close();
delete infile;
return CMP3File::ReadFile(lpszFileName);
}
m_infile=infile;
return TRUE;
}
AACファイルのヘッダ情報を読み取り、その情報を元にデコーダを開き、AACファイル再生のため準備をします。
//AACヘッダーの読み込み
BOOL CAACFile::ReadHeader(CFile* infile)
{
ASSERT(infile->IsKindOf(RUNTIME_CLASS(CFile)));
//読み飛ばし位置は、現在のファイル位置とします。
m_dwSkipOffset=(DWORD)infile->GetPosition();
//ファイルサイズを取得します。
DWORD fileSize=(DWORD)(DWORD_PTR)infile->GetLength();
//ADTSヘッダーを探します。
BYTE header[128];
ADTS_HEADER ADTSHeader={0};
int bitrate =0; //ビットレート
while(TRUE){
//読み飛ばし位置に移動してから10バイト読みます。
infile->Seek(m_dwSkipOffset,CFile::begin);
if (infile->Read(header,10)!=10) return FALSE;
//ADTSヘッダーがある場合。
if (_ParseADTSHeader((PBYTE)&header,&ADTSHeader)){
m_headerType=ADTS;
break;
}
//ADIFヘッダーがある場合。
else if (::memcmp(header,"ADIF",4)==0){
int skipSize=(header[4]&0x80)?9:0;
bitrate=((DWORD)(header[4+skipSize]&0x0F)<<19)|
((DWORD) header[5+skipSize]<<11)|
((DWORD) header[6+skipSize]<<3 )|
((DWORD) header[7+skipSize]&0xE0);
float length=(float)fileSize;
if (length) length=((float)length*8.f)/((float)bitrate)+0.5f;
bitrate=(int)((float)bitrate/1000.0f+0.5f);
m_headerType=ADIF;
break;
}
//"ID3"タグがある場合。
else if (::memcmp(header,"ID3",3)==0){
//ID3V2タグサイズを算出します。
DWORD sizeTag=((header[6]&0x7F)<<21)|((header[7]&0x7F)<<14)|
((header[8]&0x7F)<< 7)|(header[9]&0x7F);
//読み飛ばし位置を算出します。
m_dwSkipOffset+=(sizeTag+10);
//バッファに生データを保存します。
BYTE* pBuffer=NULL;
switch(header[3]){
case 2: //ID3v2.2タグの場合
if (!m_byteID3v22.GetSize()){
m_byteID3v22.SetSize(sizeTag+10);
pBuffer=m_byteID3v22.GetData();
m_ID3v22Version=(header[3]<<8)|(header[4]);
}
break;
case 3: //ID3v2.3タグの場合
if (!m_byteID3v23.GetSize()){
m_byteID3v23.SetSize(sizeTag+10);
pBuffer=m_byteID3v23.GetData();
m_ID3v23Version=(header[3]<<8)|(header[4]);
}
break;
case 4: //ID3v2.4タグの場合
if (!m_byteID3v24.GetSize()){
m_byteID3v24.SetSize(sizeTag+10);
pBuffer=m_byteID3v24.GetData();
m_ID3v24Version=(header[3]<<8)|(header[4]);
}
break;
default:
pBuffer=NULL;
}
//ID3v2タグバッファが確保されている場合は、
//ID3v2タグの内容をバッファに読み込みます。
if (pBuffer){
::CopyMemory(pBuffer,&header,10);
UINT size=infile->Read(pBuffer+10,sizeTag);
if (size<sizeTag) return FALSE;
}
}
//生データの場合
else if (::memcmp(header,"\xDE\x36\0\0",4)==0){
m_headerType=RAW;
break;
}
//ADTSヘッダー/ADIFヘッダー/ID3v2タグ/生データでもない場合。
else
//エラー終了します。
return FALSE;
}
BYTE channels=2;
int sampleRate=44100;
//ADTSヘッダーがある場合。
if (m_headerType==ADTS){
//フレームヘッダーから得られた値をメンバー変数に保存します。
m_iVersion =ADTSHeader.iVersion; //MPEGバージョン番号
m_iLayer =0; //MPEGレイヤー
m_iBitRate =bitrate; //ビットレート
channels=(BYTE)ADTSHeader.nChannel;
if ((channels!=1)&&(channels!=2)) return FALSE;
sampleRate=ADTSHeader.sampleRate;
m_uFlagBitRate=(ADTSHeader.nFullness==0x7FF)?VBR:CBR;
//フレーム数
int nFrames=1;
//データ総数
m_dwStreamSize=ADTSHeader.nFrameSize;
//ファイルポインタ
DWORD offset=m_dwSkipOffset+ADTSHeader.nFrameSize;
//すべてのフレームについて調べます。
while(TRUE){
//次のフレームの先頭位置を探します。
BOOL bFind=FALSE;
while(TRUE){
//読み飛ばし位置に移動してから8バイト読みます。
infile->Seek(offset,CFile::begin);
if (infile->Read(header,8)!=8) break;
//ADTSヘッダーかどうか調べます。
if (_ParseADTSHeader((PBYTE)&header,&ADTSHeader)){
bFind=TRUE;
break;
}
//見つからない場合は、ファイルポインタを1つ進めます。
offset++;
}
if (!bFind) break;
if (!ADTSHeader.nFrameSize) break;
offset+=ADTSHeader.nFrameSize;
m_dwStreamSize+=ADTSHeader.nFrameSize;
nFrames++;
}
m_nFrames=nFrames;
}
//生データの場合。
else
//AACデータストリームサイズは、ファイル長さから読み飛ばし位置を差し引きます。
m_dwStreamSize=fileSize-m_dwSkipOffset;
//ID3v1タグがあるか調べます。
//ファイル末尾から128バイト戻った位置にシークします。
infile->Seek(fileSize-128,CFile::begin);
UINT size=infile->Read(header,128);
if (size<128) return FALSE;
//"TAG"がある場合。
if (::memcmp(header,"TAG",3)==0){
//バージョン番号を保存します。
m_id3v1Version=0x100|((header[125]=='\0')?1:0);
//バッファに生データを保存します。
m_byteId3v1.SetSize(128);
::CopyMemory(m_byteId3v1.GetData(),&header,128);
}
//総演奏時間を算出します。
float totalTime=0;
//ADTSヘッダーがある場合のみ。
if (m_headerType==ADTS){
float framesPerSec=(float)sampleRate/1024.0f;
float bytesPerFrame=(m_nFrames)? (float)m_dwStreamSize/(float)(m_nFrames*1000):0;
//平均ビットレート
m_iAveBitRate=(int)(8.0*bytesPerFrame*framesPerSec+0.5);
totalTime=(framesPerSec)? (float)m_nFrames/framesPerSec:1;
}
//現在のファイル位置を、読み飛ばし位置に移動します。
infile->Seek(m_dwSkipOffset,CFile::begin);
//デコーダーを開きます。
NeAACDecHandle hDecoder=::NeAACDecOpen();
if (!hDecoder) return FALSE;
//現在のデコーダー構成構造体を設定します。
NeAACDecConfigurationPtr config=::NeAACDecGetCurrentConfiguration(hDecoder);
config->defObjectType=MAIN;
config->outputFormat =1;
::NeAACDecSetConfiguration(hDecoder, config);
//ファイル読み込み用バッファを確保します。
DWORD cbSrcSize=FAAD_MIN_STREAMSIZE*channels;
m_byteSrcBuf.SetSize(cbSrcSize);
BYTE *pbSrc=m_byteSrcBuf.GetData();
::ZeroMemory(pbSrc,cbSrcSize);
//バッファにファイルを読み込みます。
UINT bytesInBuffer=infile->Read(pbSrc,cbSrcSize);
//入力側キャッシュにバッファの内容をコピーします。
m_byteInCache.Copy(m_byteSrcBuf);
m_byteInCache.SetSize(bytesInBuffer);
//出力側キャッシュを初期化します。
m_byteCache.SetSize(0);
//入力側キャッシュの内容でデコーダーを初期化します。
BYTE* buffer=m_byteInCache.GetData();
DWORD samplerate;
int bytesconsumed=::NeAACDecInit(hDecoder,buffer,bytesInBuffer,&samplerate, &channels);
if (bytesconsumed<0){
::NeAACDecClose(hDecoder);
return FALSE;
}
//デコーダーが消費した分のデータを削除して、残りデータを左シフトします。
m_byteInCache.RemoveAt(0,bytesConsumed);
//Wave再生の為のWAVEFOTMATEX構造体を作成します。
m_wfx.cbSize=sizeof(WAVEFORMATEX);
m_wfx.wFormatTag=WAVE_FORMAT_PCM;
m_wfx.nSamplesPerSec=samplerate;
m_wfx.nChannels=channels;
m_wfx.wBitsPerSample=16;
m_wfx.nBlockAlign=channels*2;
m_wfx.nAvgBytesPerSec=m_wfx.nBlockAlign*m_wfx.nSamplesPerSec;
m_uFlagBitRate=VBR;
m_hDecoder=hDecoder;
//ADTSヘッダーがある場合。
if (m_headerType==ADTS){
//AACの総演奏時間から変換後の総データサイズを取得します。
m_nDataSize=(int)(totalTime*(float)m_wfx.nAvgBytesPerSec+0.5);
//フレームサイズに相当するデコード後のデータサイズの平均値を算出します。
m_sizeAveFrameDecoded=m_nDataSize/m_nFrames;
m_sizeFrame=m_dwStreamSize/m_nFrames;
}
//生データの場合。
else{
m_nDataSize=m_dwStreamSize*16;
m_sizeFrame=cbSrcSize;
}
return TRUE;
}
AACファイル再生時に音源データ読み込みのために、基底クラスから呼び出されるオーバーライド関数です。
//AACファイルからWave音源に変換してストリーム読み込み
UINT CAACFile::StreamIn(LPVOID pBuffer,UINT nBufSize,UINT* pIndexSample)
{
ASSERT(!::IsBadWritePtr(pBuffer,nBufSize));
ASSERT(AfxIsValidAddress(pIndexSample,sizeof(UINT)));
//ACMストリームハンドルが無効な場合は、
//CMP3File::StreamIn関数を呼び出します。
if (!IsAACFile()) return CMP3File::StreamIn(pBuffer,nBufSize,pIndexSample);
//現在位置と指定位置を算出します。
DWORD dwPosRecent=(DWORD)m_infile->GetPosition();
UINT indexFrame=ROUND((double)(*pIndexSample*m_wfx.nBlockAlign)/(double)m_sizeAveFrameDecoded);
DWORD dwPosRead=m_dwSkipOffset+(m_sizeFrame*indexFrame);
//現在演奏中のADTSヘッダーのあるAACファイルで、
//現在位置と指定位置が100_秒以上違えば、指定位置にシークします。
if ((IsPlaying())&&(m_headerType==ADTS)&&
((DWORD)abs((int)(dwPosRead-dwPosRecent))>(m_wfx.nAvgBytesPerSec/10))){
//キャッシュを初期化します。
m_byteCache.SetSize(0);
m_byteInCache.SetSize(0);
//指定位置から末尾に向かって、一番近い位置にあるADTSヘッダーを探します。
BYTE header[8];
ADTS_HEADER ADTSHeader={0};
while(TRUE){
//読み飛ばし位置に移動してから8バイト読みます。
m_infile->Seek(dwPosRead,CFile::begin);
if (m_infile->Read(header,8)!=8) return FALSE;
//ADTSヘッダーかどうか調べます。
if (_ParseADTSHeader((PBYTE)&header,&ADTSHeader)) break;
//ADTSヘッダーではない場合は、
//読み飛ばし位置を1バイト進めてから再び読み直します。
dwPosRead++;
}
//探し出したADTSヘッダーの先頭位置に移動します。
m_infile->Seek(dwPosRead,CFile::begin);
}
//出力側キャッシュ内のデータサイズを取得します。
UINT nCaches=(UINT)(UINT_PTR)m_byteCache.GetSize();
//AACストリーム最後尾の位置
DWORD dwFileEnd=m_dwStreamSize+m_dwSkipOffset;
//出力側キャッシュ内のデータサイズがバッファサイズに満たない場合。
while(nCaches<nBufSize){
//入力側キャッシュ内のバッファの先頭アドレスを取得します。
BYTE* buffer=m_byteInCache.GetData();
//入力側キャッシュ内のバッファのデータサイズを取得します。
UINT bytesInBuffer=(UINT)(UINT_PTR)m_byteInCache.GetSize();
//ファイル読み込み用バッファの先頭アドレスを取得します。
BYTE* pbSrc=m_byteSrcBuf.GetData();
//ファイル読み込み用バッファのデータサイズを取得します。
UINT cbSrcLength=(UINT)(UINT_PTR)m_byteSrcBuf.GetSize();
//入力側キャッシュ内のデータサイズがストリームサイズに満たない場合は、
//ファイルから読み込みます。
if (bytesInBuffer<cbSrcLength){
UINT size=m_infile->Read(pbSrc,cbSrcLength);
//読み込んだデータがある場合。
if(size>0){
//入力側キャッシュの末尾に、読み込んだデータを追加します。
m_byteInCache.SetSize(bytesInBuffer+size);
buffer=m_byteInCache.GetData();
::CopyMemory(buffer+bytesInBuffer,pbSrc,size);
bytesInBuffer=(UINT)(UINT_PTR)m_byteInCache.GetSize();
}
//ファイルの末尾に来た場合。
else{
TRACE1("Reach EOF bytesInBuffer=%08x\n",bytesInBuffer);
}
}
//デコードします。
NeAACDecFrameInfo frameInfo={0};
const void* pDecoded=(const void*)::NeAACDecDecode(m_hDecoder,&frameInfo,buffer,bytesInBuffer);
//入力側キャッシュ内のデータのうち、デコーダーが読み込んだデータを削除して、
//残りのデータを左シフトします。
m_byteInCache.RemoveAt(0,frameInfo.bytesconsumed);
//デコードエラー時の場合。
if (frameInfo.error){
TRACE1("Decode Error=%d\n",frameInfo.error);
if (!bytesInBuffer) return FALSE;
m_byteInCache.SetSize(0);
}
//デコード済みデータがある場合
if (frameInfo.samples>0){
//出力側キャッシュの末尾に、デコード済みデータを追加します。
nCaches=(UINT)(UINT_PTR)m_byteCache.GetSize();
DWORD sampleBytes=frameInfo.samples*m_wfx.wBitsPerSample/8;
m_byteCache.SetSize(nCaches+sampleBytes);
::CopyMemory(m_byteCache.GetData()+nCaches,pDecoded,sampleBytes);
nCaches=(UINT)(UINT_PTR)m_byteCache.GetSize();
}
//デコード済みデータがなく、入力側キャッシュにもデータが残っていない場合は、
//エラー終了します。
else if (!bytesInBuffer){
TRACE0("Not Find Decoded Data\n");
return FALSE;
}
}
//バッファに取り込むサイズを算定して、キャッシュからデータをコピーします。
UINT readBytes=min(nBufSize,nCaches);
::CopyMemory(pBuffer,m_byteCache.GetData(),readBytes);
//出力側キャッシュ内のデータのうち、バッファに取り込んだデータを削除してから、
//残りのデータを左シフトします。
m_byteCache.RemoveAt(0,readBytes);
//指定の演奏位置を、読み込んだAACデータに相当する音源データ分進めます。
dwPosRecent=(DWORD)m_infile->GetPosition();
//ADTSヘッダーがある場合。
if (m_headerType==ADTS){
indexFrame=(dwPosRecent-m_dwSkipOffset)/m_sizeFrame;
*pIndexSample=(m_sizeAveFrameDecoded*indexFrame)/m_wfx.nBlockAlign;
}
//生データの場合。
else{
*pIndexSample=(UINT)((float)m_nDataSize*(float)(dwPosRecent-m_dwSkipOffset)/
(float)m_dwStreamSize)/m_wfx.nBlockAlign;
}
//読み込んだバイト数を返して終了します。
return readBytes;
}
WaveOut.h
WaveOut.cpp
WaveFile.h
WaveFile.cpp
CDDAFile.h
CDDAFile.cpp
MP3File.h
MP3File.cpp
AACFile.h
AACFile.cpp