FC2ブログ

ホーム > 未分類 > MP4ファイル音声の再生 第2部

MP4ファイル音声の再生 第2部

  • 著者名: ひろし
  • 2012-06-20 Wed 21:22:14
  • 未分類


MP4ファイルをデコードするために必要なインクルードファイルです。

#include "..\faad2-2.7\include\neaacdec.h"
#include "..\faad2-2.7\common\mp4ff\mp4ff.h"


MP4ファイル音声を再生するために必要なインクルードファイルです。

#include "AACFile.h"


上記インクルード定義で必要となるファイルです。

devioctl.h
ntddcdrm.h
ntddstor.h
WaveFile.h WaveFile.cpp
WaveOut.h WaveOut.cpp
CDDAFile.h CDDAFile.cpp
MP3File.h MP3File.cpp
AACFile.h AACFile.cpp


MP4ファイルを読む際に必要となる、ユーザー定義のコールバック関数です。

//MP4ファイル読み込みコールバック関数
uint32_t _read_callback(void *user_data, void *buffer, uint32_t length)
{
return ((CFile*)user_data)->Read(buffer,length);
}

//MP4ファイルシークコールバック関数
uint32_t _seek_callback(void *user_data, uint64_t position)
{
return (uint32_t)((CFile*)user_data)->Seek(position,CFile::begin);
}


MP4ファイル音声の再生に必要となるstdcall関数です。

//MP4ファイルストリームから、AAC音源のあるトラック番号を取得します。
int __stdcall _GetAACTrack(mp4ff_t *mp4Stream)
{
//トラック数を取得します。
int nTracks=::mp4ff_total_tracks(mp4Stream);

for (int i=0;i<nTracks;i++){
//トラックのデコーダー構成を取得します。
BYTE* buffer =NULL;
UINT bufferSize =0;
::mp4ff_get_decoder_config(mp4Stream,i,&buffer, &bufferSize);

if (buffer){
//AACオーディオ特性を調べます。
mp4AudioSpecificConfig mp4ASC;
int result=::NeAACDecAudioSpecificConfig(buffer,bufferSize,&mp4ASC);
::free(buffer);
//エラーでなければ、トラック番号を返して終了します。
if (result>-1) return i;
}
}

//AACデータのあるトラックが見当たらないので、エラー終了します。
return -1;
}

//UTF-8からUTF-16へ変換します。
CStringW __stdcall _Utf8ToUtf16(const char* utf8)
{
//UTF-8からUTF-16へ変換
int nSize = ::MultiByteToWideChar(CP_UTF8,0,(LPCSTR)utf8,-1,NULL,0);

CStringW strW;
LPWSTR lpsz=strW.GetBufferSetLength(nSize+1);
::MultiByteToWideChar(CP_UTF8,0,(LPCSTR)utf8,-1,(LPWSTR)lpsz,nSize);

strW.ReleaseBuffer();
return strW;
}

//UTF-16からUTF-8へ変換します。
CStringA __stdcall _Utf16ToUtf8(const WCHAR* utf16)
{
//UTF-16からUTF-8へ変換
int nSize = ::WideCharToMultiByte(CP_UTF8,0,(LPWSTR)utf16,-1,NULL,0,NULL,NULL);

CStringA strA;
LPTSTR lpsz=strA.GetBufferSetLength(nSize+1);
::WideCharToMultiByte(CP_UTF8,0,(LPCWSTR)utf16,-1,(LPSTR)lpsz,nSize,NULL,NULL);

strA.ReleaseBuffer();
return strA;
}

//画像ファイルヘッダーがあるかどうか?
BOOL __stdcall _IsImageHeader(PBYTE pHeader)
{
//PNG
if (::memcmp(pHeader,"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A",8)==0)
return TRUE;
//JPEG
else if (((::memcmp(pHeader,"\xFF\xD8\xFF\xE0",4)==0)&&
(::memcmp(pHeader+6,"JFIF\0",5)==0))||
((::memcmp(pHeader,"\xFF\xD8\xFF\xE1",4)==0)&&
(::memcmp(pHeader+6,"Exif\0\0",6)==0)))
return TRUE;
//GIF
else if ((::memcmp(pHeader,"GIF87a",6)==0)||
(::memcmp(pHeader,"GIF89a",6)==0))
return TRUE;
return FALSE;
}


MP4ファイルの再生をWAVEファイルと同様の操作にするために、CWaveFileクラスの派生クラスであるCAACFileクラスを基底クラスとする、CMP4Fileクラスを独自に定義しました。

//このヘッダーでmp4ff.hのインクルードは避けたいので、構造体の型のみ宣言しておきます。
typedef void* mp4ff_t;
typedef struct _mp4ff_callback_t mp4ff_callback_t;

class CMP4File : public CAACFile
{
public:
//コンストラクタ
CMP4FileCMP4File(CWnd* pWndOwner=NULL);
//デストラクタ
virtual ~CMP4File();

//メンバー変数の初期化
void Init();
//メンバー変数の削除
void Delete();

//AACファイルがオープンされているかどうか?
virtual BOOL IsAACFile(){return ((m_hDecoder)&&(!m_mp4Stream));};
//MP4ファイルがオープンされているかどうか?
virtual BOOL IsMP4File(){return ((m_hDecoder)&&(m_mp4Stream));};

//MP4ファイルを開く
// lpszFileName:ファイルのパス名

virtual BOOL OpenFile(LPCTSTR lpszFileName);
//MP4ファイルからWave音源に変換してストリーム読み込み
virtual UINT StreamIn(LPVOID pBuffer,UINT nBufSize,UINT* pIndexSample);

//MP4Metaデータを現在のファイルに保存します。
BOOL SaveMP4Meta();

//MP4ファイルの書き込み
virtual BOOL SaveFile(LPCTSTR lpszFileName);
//MP4ファイルの書き込み
BOOL SaveMP4File(LPCTSTR lpszFileName);
//MP4ストリームの書き込み
BOOL StreamOut(MP4FileHandle outfile,faacEncConfigurationPtr pConfig=NULL);

//faacEncConfiguration構造体の初期化
void InitEncConfiguration();
//faacEncConfiguration構造体の取得
virtual BOOL GetEncConfiguration(faacEncConfigurationPtr pConfigAAC,faacEncConfigurationPtr pConfigMP4);
//faacEncConfiguration構造体の設定
virtual BOOL SetEncConfiguration(faacEncConfigurationPtr pConfigAAC,faacEncConfigurationPtr pConfigMP4);

//現在保持しているメタ文字列数の取得
UINT GetMetaCount();
//メタ文字列の取得
BOOL GetMetaTextByIndex(UINT index,CString& strItem,CString& strValue);
//メタ文字列の検索
CString FindMetaTextByName(LPCTSTR lpszItem);
//メタ文字列の挿入
BOOL InsertMetaTextByIndex(UINT index,CString& strItem,CString& strValue);
//メタ文字列の追加
BOOL AddMetaText(CString& strItem,CString& strValue);
BOOL AddMetaText(LPCTSTR lpszItem,LPCTSTR lpszValue);
//メタ文字列の設定
BOOL SetMetaTextByIndex(UINT index,CString& strItem,CString& strValue);
//メタ文字列の削除
void DeleteMetaTextByIndex(UINT index);
//すべてのメタ文字列の削除
void DeleteAllMetaText(){for(int i=0;i<2;i++) m_listMeta[i].RemoveAll();};
//画像をファイルから読み込みます。
BOOL LoadImage(LPCTSTR lpszPathName,CString& strImage);

//メタ文字列を格納するCStringArrayクラス
// m_listMeta[0]:アイテム [1]:メタデータ

CStringArray m_listMeta[2];

protected:
//基底クラスに読み込み用のバッファサイズを戻します。
virtual UINT GetSizeOfLoadBuffer();
//基底クラスに書き込み用のバッファサイズを戻します。
virtual UINT GetSizeOfSaveBuffer();

//ヘッダーの読み込み
BOOL ReadHeader(CFile* infile);

mp4ff_t* m_mp4Stream; //MP4ファイルストリーム
mp4ff_callback_t* m_mp4cb; //コールバック構造体
DWORD m_mp4NumSamples; //フレーム数
DWORD m_mp4IndexSample; //現在着目しているフレーム番号
int m_aacTrack; //AAC音源のあるトラック番号
faacEncConfiguration* m_pConfig;//エンコーダー構成
};


CMP4Fileクラスのコンストラクタ/デストラクタです。

//コンストラクタ
CMP4File::CMP4File(CWnd* pWndOwner)
:CAACFile(pWndOwner)
{
//faacEncConfiguration構造体の初期化
InitEncConfiguration();
Init();
}

//デストラクタ
CMP4File::~CMP4File()
{
Delete();
delete m_pConfig;
}


CMP4Fileクラスのメンバ関数です。

//メンバー変数の初期化
void CMP4File::Init()
{
m_mp4Stream=0;
m_mp4cb=0;
m_mp4NumSamples=0;
m_mp4IndexSample=0;
m_aacTrack=0;
}

//メンバー変数の削除
void CMP4File::Delete()
{
CAACFile::Delete();
if (m_mp4Stream) ::mp4ff_close(m_mp4Stream);
if (m_mp4cb) delete m_mp4cb;
DeleteAllMetaText();
Init();
}

//基底クラスに読み込み用のバッファサイズを戻します。
UINT CMP4File::GetSizeOfLoadBuffer()
{
//MP3ファイルがオープンされている場合。
if (IsMP4File()){
//CWaveFileクラスの読み込みバッファサイズを取得し、
//Waveブロックサイズで丸めたサイズ(剰余切り上げ)を戻します。

DWORD dwBufferLength=CWaveOut::GetSizeOfLoadBuffer();
return ((dwBufferLength/m_wavBlockSize)+((dwBufferLength%m_wavBlockSize)?1:0))*m_wavBlockSize;
}
//それ以外は既定クラスを呼び出します。
return CAACFile::GetSizeOfLoadBuffer();
}


MP4ファイルをオープンして、CMP4File::ReadHeader関数を呼び出します。

//MP4ファイルをオープンします。
// lpszFileName:オープンするファイル名。

BOOL CMP4File::OpenFile(LPCTSTR lpszFileName)
{
Delete();
if (!lpszFileName) return FALSE;

//拡張子名の取得
LPTSTR pExt=::PathFindExtension(lpszFileName);
//拡張子名が「.mp4/.m4a/.m4b/.m4p」でない場合は、
//CAACFile::OpenFile関数を呼び出します。

if ((StrCmpNI(pExt,_T(".mp4"),4)!=0)&&
(StrCmpNI(pExt,_T(".m4a"),4)!=0)&&
(StrCmpNI(pExt,_T(".m4b"),4)!=0)&&
(StrCmpNI(pExt,_T(".m4p"),4)!=0))
{
return CAACFile::OpenFile(lpszFileName);
}

//ファイルのオープンに失敗した場合は、FALSEを戻して終了します。
CFile* infile=new CFile;
if (!infile->Open(lpszFileName,CFile::modeRead|CFile::shareDenyNone)){
delete infile;
return FALSE;
}

//ヘッダー読み込みに失敗した場合は、CAACFile::OpenFile関数を呼び出します。
if (!ReadHeader(infile)){
infile->Close();
delete infile;
return CAACFile::OpenFile(lpszFileName);
}
m_infile=infile;
m_strPathName=lpszFileName;
return TRUE;
}


MP4ファイルのヘッダ情報を読み取り、その情報を元にデコーダを開き、MP4ファイル音声の再生のため準備をします。

//MP4ヘッダーの読み込み
BOOL CMP4File::ReadHeader(CFile* infile)
{
ASSERT(::AfxIsValidAddress(infile,sizeof(CFile),0));

//ファイル先頭を8バイト読み込みます。
ULONGLONG posSafe=infile->GetPosition();
BYTE header[8]={0};
if (infile->Read(header,8)!=8) return FALSE;
infile->Seek(posSafe,CFile::begin);

//ftypヘッダがなければ、エラー終了します。
BOOL bResult=FALSE;
if (::memcmp(&header[4],"ftyp",4)!=0) return FALSE;

//コールバック構造体にCFileクラスへのポインタを設定します。
mp4ff_callback_t *mp4cb=new mp4ff_callback_t;
mp4cb->read=_read_callback;
mp4cb->seek=_seek_callback;
mp4cb->user_data=infile;

//MP4メタデータを開きます。
mp4ff_t *mp4Stream=::mp4ff_open_read(mp4cb);
if (!mp4Stream) return FALSE;

//MP4メタデータの取得
int32_t nItems=::mp4ff_meta_get_num_items(mp4Stream);
for (int i=0;i<nItems;i++){
char *item=NULL;
char *value=NULL;
//先頭のメタデータから順に読み込みます。
if (::mp4ff_meta_get_by_index(mp4Stream,i,&item,&value)){
//カバー画像の場合
if (::memcmp(item,"cover",5)==0){
char *coverImage=NULL;
int length=::mp4ff_meta_get_coverart(mp4Stream,&coverImage);
//画像データの場合は内容をリストに読み込みます。
if (::_IsImageHeader((PBYTE)coverImage)){
CStringW strWItem=::_Utf8ToUtf16(item);
#ifdef _UNICODE
m_listMeta[0].Add(strWItem);
#else//_UNICODE
CStringA strAItem =(CStringA)strWItem;
m_listMeta[0].Add(strAItem);
#endif//_UNICODE
CString strValue;
void* dest=strValue.GetBufferSetLength(length/sizeof(TCHAR));
::CopyMemory(dest,coverImage,length);
m_listMeta[1].Add(strValue);
}
::free(coverImage);
}
//カバー画像以外の場合。
else{
CStringW strWItem=::_Utf8ToUtf16(item);
CStringW strWValue=::_Utf8ToUtf16(value);
#ifdef _UNICODE
m_listMeta[0].Add(strWItem);
m_listMeta[1].Add(strWValue);
#else//_UNICODE
CStringA strAItem =(CStringA)strWItem;
CStringA strAValue=(CStringA)strWValue;
m_listMeta[0].Add(strAItem);
m_listMeta[1].Add(strAValue);
#endif//_UNICODE
}
::free(item);
::free(value);
}
}

//AAC音源のあるトラック番号を取得します。
int track=::_GetAACTrack(mp4Stream);
if (track<0){
::mp4ff_close(mp4Stream);
delete mp4cb;
return FALSE;
}

//AAC音源のあるトラックのデコーダー構成を取得します。
BYTE* buffer=NULL;
UINT bufferSize=0;
::mp4ff_get_decoder_config(mp4Stream,track,&buffer,&bufferSize);

//デコーダーを開きます。
NeAACDecHandle hDecoder=::NeAACDecOpen();


//デコーダーを初期化します。
DWORD samplerate;
BYTE channels;
if(::NeAACDecInit2(hDecoder,buffer,bufferSize,&samplerate,&channels)<0){
::NeAACDecClose(hDecoder);
::mp4ff_close(mp4Stream);
delete mp4cb;
return FALSE;
}
if (buffer) ::free(buffer);

//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_mp4NumSamples=::mp4ff_num_samples(mp4Stream,track);
m_nFrames=m_mp4NumSamples;
m_iVersion=4;//MPEG4
m_uFlagBitRate=VBR;
//平均ビットレートを取得します。
m_iAveBitRate=::mp4ff_get_avg_bitrate(mp4Stream,track)/1000;

//1フレームあたりのWaveデータサイズは4096
m_wavBlockSize=4096;
//総Waveデータサイズを算出します。
m_nDataSize=m_mp4NumSamples*m_wavBlockSize;

//各フレーム毎のフレームサイズを加算して、
//総ストリームサイズを算出します。

m_dwStreamSize=0;
for (UINT i=0;i<m_nFrames;i++)
m_dwStreamSize+=(DWORD)::mp4ff_read_sample_getsize(mp4Stream,track,i);

//平均フレームサイズを算出します。
m_dwFrameSize=m_dwStreamSize/m_nFrames;
//MP4ストリームに関わる各値を、メンバー変数に保存します。
m_aacTrack=track;
m_mp4Stream=mp4Stream;
m_hDecoder=hDecoder;
m_mp4cb=mp4cb;
//現在再生するフレーム番号を0にします。
m_mp4IndexSample=0;

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

return TRUE;
}


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

//MP4ファイルのAAC音源をWave音源にデコードして、引数のバッファに格納します。
UINT CMP4File::StreamIn(LPVOID pBuffer,UINT nBufSize,UINT* pIndexSample)
{
ASSERT(::AfxIsValidAddress(pBuffer,nBufSize));
ASSERT(::AfxIsValidAddress(pIndexSample,sizeof(UINT)));

//現在再生中のファイルがMP4Fileでなければ、
//CAACFile::StreamIn関数を呼び出します。

if (!IsMP4File()) return CAACFile::StreamIn(pBuffer,nBufSize,pIndexSample);

//現在位置と指定位置を算出します。
UINT indexBlock=*pIndexSample*m_wfx.nBlockAlign/m_wavBlockSize;
if (m_mp4IndexSample) indexBlock++;
//現在演奏中で現在位置と指定位置が違えば、指定位置にシークします。
if ((IsActivate())&&(m_mp4IndexSample!=indexBlock))
m_mp4IndexSample=indexBlock;

UINT readBytes=0;
PBYTE dst=(PBYTE)pBuffer;

//ファイルからAAC音源を読み込み、デコードします。
while(readBytes<nBufSize){
BYTE* buffer=NULL;
UINT bufferSize=0;
if (m_mp4IndexSample>=m_mp4NumSamples) break;

//ファイルからAAC音源を読み込みます。
int nResult=::mp4ff_read_sample(m_mp4Stream,m_aacTrack,m_mp4IndexSample++,&buffer,&bufferSize);
if (!nResult) break;

//デコードします。
NeAACDecFrameInfo frameInfo={0};
PBYTE src=(PBYTE)::NeAACDecDecode(m_hDecoder,&frameInfo,buffer,bufferSize);
if (buffer) ::free(buffer);
if (frameInfo.error) break;
if (frameInfo.samples>0){
DWORD sampleBytes=frameInfo.samples*m_wfx.wBitsPerSample/8;
DWORD writeBytes=min(sampleBytes,nBufSize-readBytes);
::CopyMemory(dst,src,writeBytes);
dst+=writeBytes;
readBytes+=sampleBytes;
//Wave音源のサイズが4096でない場合はループを抜けます。
//(実際には有り得ないようです。)

if (sampleBytes!=4096) break;
}
}
//指定の演奏位置を、読み込んだAAC音源に相当する音源データ分進めます。
*pIndexSample+=(readBytes/m_wfx.nBlockAlign);
return readBytes;
}


CMP4Fileクラスのインスタンスを作成した後、CMP4File::OpenFile関数MP4ファイルを開きます。
エラーがなければCWaveFile::Play関数で演奏を開始します。演奏を中断したり演奏終了後、WAVE出力デバイスを閉じる場合は、CWaveFile::Stop関数を呼び出して下さい。
入力したMP4ファイルの音声が再生されれば成功です。

スポンサーサイト



コメント: 0

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

Trackback+Pingback: 0

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

Home > 未分類 > MP4ファイル音声の再生 第2部

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

この人とブロともになる

ブロとも一覧

このページの先頭へ戻る