FC2ブログ

ホーム > AACファイルフォーマット > MP4ファイル音声の書き込み 第2部

MP4ファイル音声の書き込み 第2部



MP4ファイルにAAC音声を書き込むために必要な、インクルードファイルです。

#include "..\faac\include\faac.h"
#include "..\faac\common\mp4v2\mp4.h"




faacEncConfiguration構造体を扱うためのメンバー関数です。

//faacEncConfiguration構造体の初期化
void CMP4File::InitEncConfiguration()
{
//faacバージョンの取得
int version=::faacEncGetVersion(&m_pszFaacId,&m_pszFaacCopyright);
ASSERT(version==FAAC_CFG_VERSION);

unsigned long inputSamples;
unsigned long maxOutputBytes;

//エンコーダーを開いてFaacエンコーダー構成構造体を取得します。
faacEncHandle hEncoder=::faacEncOpen(44100,2,&inputSamples,&maxOutputBytes);

m_pConfig=new faacEncConfiguration;
::ZeroMemory(m_pConfig,sizeof(faacEncConfiguration));
if (hEncoder){
//取得したエンコーダー構成構造体を初期化しておきます。
faacEncConfigurationPtr pConfig=::faacEncGetCurrentConfiguration(hEncoder);
::CopyMemory(m_pConfig,pConfig,sizeof(faacEncConfiguration));
m_pConfig->allowMidside =TRUE;
m_pConfig->useTns =FALSE;
m_pConfig->useLfe =FALSE;
m_pConfig->outputFormat =FALSE;
m_pConfig->mpegVersion =MPEG4;
m_pConfig->aacObjectType=LOW;
m_pConfig->quantqual =100;
m_pConfig->bandWidth =0;
m_pConfig->bitRate =0;
m_pConfig->inputFormat =FAAC_INPUT_32BIT;
m_pConfig->shortctl =SHORTCTL_NORMAL;
::faacEncClose(hEncoder);
}
}

//faacEncConfiguration構造体の取得
BOOL CMP4File::GetEncConfiguration(faacEncConfigurationPtr pConfigAAC,faacEncConfigurationPtr pConfigMP4)
{
CAACFile::GetEncConfiguration(pConfigAAC);
if (!AfxIsValidAddress(pConfigMP4,sizeof(faacEncConfiguration),FALSE)) return FALSE;
::CopyMemory(pConfigMP4,m_pConfig,sizeof(faacEncConfiguration));
return TRUE;
}

//faacEncConfiguration構造体の設定
BOOL CMP4File::SetEncConfiguration(faacEncConfigurationPtr pConfigAAC,faacEncConfigurationPtr pConfigMP4)
{
CAACFile::SetEncConfiguration(pConfigAAC);
if (!AfxIsValidAddress(pConfigMP4,sizeof(faacEncConfiguration),FALSE)) return FALSE;
::CopyMemory(m_pConfig,pConfigMP4,sizeof(faacEncConfiguration));
return TRUE;
}


メンバー変数上にある、MP4メタ文字列を扱うための関数です。

//現在保持しているメタ文字列数の取得
UINT CMP4File::GetMetaCount()
{
return (UINT)(UINT_PTR)m_listMeta[0].GetCount();
}

//メタ文字列の取得
BOOL CMP4File::GetMetaTextByIndex(UINT index,CString& strItem,CString& strValue)
{
if (index>=GetMetaCount()) return FALSE;
strItem =m_listMeta[0].GetAt(index);
strValue=m_listMeta[1].GetAt(index);
return TRUE;
}

//メタ文字列の設定
BOOL CMP4File::SetMetaTextByIndex(UINT index,CString& strItem,CString& strValue)
{
if (index>=GetMetaCount()) return FALSE;
m_listMeta[0].SetAt(index,strItem);
m_listMeta[1].SetAt(index,strValue);
return TRUE;
}

//メタ文字列の挿入
BOOL CMP4File::InsertMetaTextByIndex(UINT index,CString& strItem,CString& strValue)
{
UINT nMetas=GetMetaCount();
if (index>nMetas) index=nMetas;
m_listMeta[0].InsertAt(index,strItem);
m_listMeta[1].InsertAt(index,strValue);
return TRUE;
}

//メタ文字列の追加
BOOL CMP4File::AddMetaText(CString& strItem,CString& strValue)
{
ASSERT(m_listMeta[0].GetCount()==m_listMeta[1].GetCount());
if (m_listMeta[0].GetCount()!=m_listMeta[1].GetCount()) return FALSE;
m_listMeta[0].Add(strItem);
m_listMeta[1].Add(strValue);
return TRUE;
}

//メタ文字列の追加
BOOL CMP4File::AddMetaText(LPCTSTR lpszItem,LPCTSTR lpszValue)
{
ASSERT(m_listMeta[0].GetCount()==m_listMeta[1].GetCount());
if (m_listMeta[0].GetCount()!=m_listMeta[1].GetCount()) return FALSE;
m_listMeta[0].Add(lpszItem);
m_listMeta[1].Add(lpszValue);
return TRUE;
}

//メタ文字列の検索
CString CMP4File::FindMetaTextByName(LPCTSTR lpszItem)
{
UINT nCount=GetMetaCount();
if (!nCount) return FALSE;

CString str,strItem,strValue;
for (UINT i=0;i<GetMetaCount();i++){
if ((GetMetaTextByIndex(i,strItem,strValue))&&(strItem==lpszItem)){
if (strItem==_T("cover")){
int length=strValue.GetAllocLength();
void* buffer=str.GetBufferSetLength(length/sizeof(TCHAR));
::CopyMemory(buffer,strValue.GetBuffer(),length);
}
else str=strValue;
str.ReleaseBuffer();
return str;
}
}
return FALSE;
}

//画像をファイルから読み込みます。
BOOL CMP4File::LoadImage(LPCTSTR lpszPathName,CString& strImage)
{
//ファイルからバイナリーデータを読み込みます。
CFile infile;
if (!infile.Open(lpszPathName,CFile::modeRead|CFile::shareDenyNone)) return FALSE;
UINT length=(UINT)infile.GetLength();
//引数のCStringクラスに必要なメモリー領域を確保し、データ読み込みます。
PBYTE buffer=(PBYTE)strImage.GetBufferSetLength(length/sizeof(TCHAR));
UINT bytesRead=infile.Read(buffer,length);
infile.Close();
if ((!bytesRead)||(!_IsImageHeader(buffer))){
strImage.Empty();
return FALSE;
}
return TRUE;
}

//メタ文字列の削除
void CMP4File::DeleteMetaTextByIndex(UINT index)
{
CString strItem =m_listMeta[0].GetAt(index);
m_listMeta[0].RemoveAt(index);
m_listMeta[1].RemoveAt(index);
}

//MP4Metaデータを現在のファイルに保存します。
BOOL CMP4File::SaveMP4Meta()
{
return SaveFile(m_strPathName);
}


現在演奏中のファイルをAACストリームにエンコードしてから、MP4ファイルの音声として保存します。

//MP4ファイルの音声として書き込む
BOOL CMP4File::SaveFile(LPCTSTR lpszFileName)
{
//書き込み対象のファイルがない場合は、FALSEを戻して終了します。
if (!IsOpen()) return FALSE;

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

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

//現在演奏中のファイルがMP4ファイルの場合。
if (IsMP4File()){
m_infile->Close();
delete m_infile;
m_infile=0;
if ((m_strPathName!=lpszFileName)&&
(!::CopyFile(m_strPathName,lpszFileName,FALSE)))
return FALSE;
}
else{
//MP4メタデータの内容を保存しておきます。
CStringArray listMeta[2];
listMeta[0].Copy(m_listMeta[0]);
listMeta[1].Copy(m_listMeta[1]);

//ACMドライバー等の内部に残ったデコードデータの残りが、
//保存ファイルデータに混じらないようにするため、
//念のため現在演奏中のファイルを、再度開き直しておきます。

if (!OpenFile(m_strPathName)) return FALSE;

//保存しておいたMP4メタデータの内容を戻します。
m_listMeta[0].Copy(listMeta[0]);
m_listMeta[1].Copy(listMeta[1]);
}
return SaveMP4File(lpszFileName);
}

//MP4ファイルの書き込み
BOOL CMP4File::SaveMP4File(LPCTSTR lpszFileName)
{
if (!IsOpen()) return FALSE;

//現在演奏中のMP4ファイルから、違うファイル名のMP4ファイルを作成する場合は、
//エラー終了します。

ASSERT(!((IsMP4File())&&(m_strPathName!=lpszFileName)));
if ((IsMP4File())&&(m_strPathName!=lpszFileName)) return FALSE;

//現在再生中のファイルがMP4ファイルの場合は、そのファイルを開き
//それ以外は、新たにファイルを作成します。

MP4FileHandle outfile=
(IsMP4File())? ::MP4Modify(lpszFileName,MP4_DETAILS_ERROR,0):
::MP4Create(lpszFileName,MP4_DETAILS_ERROR,0);

if (!MP4_IS_VALID_FILE_HANDLE(outfile)) return FALSE;

//MP4ファイルのメタデータを削除します。
::MP4MetadataDelete(outfile);

//メタデータをファイルに書き込みます。
CString strItem,strValue;
CStringA strAItem,strAValue;

for(UINT i=0;i<GetMetaCount();i++){
CString strItem,strValue;
GetMetaTextByIndex(i,strItem,strValue);
//アイテムがカバー画像の場合。
if ((strItem==_T("cover"))&&(_IsImageHeader((PBYTE)strValue.GetBuffer())))
::MP4SetMetadataCoverArt(outfile,(uint8_t*)strValue.GetBuffer(),
(uint32_t)strValue.GetAllocLength());
//それ以外の場合。
else{
#ifdef _UNICODE
strAItem =::_Utf16ToUtf8(strItem);
strAValue=::_Utf16ToUtf8(strValue);
#else//_UNICODE
CStringW strWItem =(CStringW)strItem;
CStringW strWValue=(CStringW)strValue;
strAItem =::_Utf16ToUtf8(strWItem);
strAValue=::_Utf16ToUtf8(strWValue);
#endif//_UNICODE

const char* value=strAValue.GetBuffer();
if (strAItem=="title") ::MP4SetMetadataName(outfile,value);
else if (strAItem=="artist") ::MP4SetMetadataArtist(outfile,value);
else if (strAItem=="writer") ::MP4SetMetadataWriter(outfile,value);
else if (strAItem=="album") ::MP4SetMetadataAlbum(outfile,value);
else if (strAItem=="date") ::MP4SetMetadataYear(outfile,value);
else if (strAItem=="tool") ::MP4SetMetadataTool(outfile,value);
else if (strAItem=="comment") ::MP4SetMetadataComment(outfile,value);
else if (strAItem=="genre") ::MP4SetMetadataGenre(outfile,value);
else if (strAItem=="track"){
CString strTotalTracks=FindMetaTextByName(_T("totaltracks"));
uint32_t totalTracks=0;
::_stscanf(strTotalTracks,_T("%d"),&totalTracks);
uint32_t track=0;
int res=::sscanf(strAValue.GetBuffer(),"%d",&track);
if (res==2) ::MP4SetMetadataTrack(outfile,(uint16_t)track,(uint16_t)totalTracks);
}
else if (strAItem=="totaltracks"){}
else if (strAItem=="disc"){
CString strTotalDiscs=FindMetaTextByName(_T("totaldiscs"));
uint32_t totalDiscs=0;
::_stscanf(strTotalDiscs,_T("%d"),&totalDiscs);
uint32_t disc=0;
int res=::sscanf(strAValue.GetBuffer(),"%d",&disc);
if (res==2) ::MP4SetMetadataDisk(outfile,(uint16_t)disc,(uint16_t)totalDiscs);
}
else if (strAItem=="totaldiscs"){}
else if (strAItem=="compilation"){
uint8_t cpl=(strAValue=="TRUE")?1:0;
::MP4SetMetadataCompilation(outfile,cpl);
}
else if (strAItem=="tempo"){
uint32_t tempo=0;
int res=sscanf(strAValue.GetBuffer(),"%d",&tempo);
if (res==1) ::MP4SetMetadataTempo(outfile,(uint16_t)tempo);
}
else ::MP4SetMetadataFreeForm(outfile,strAItem.GetBuffer(),
(uint8_t*)strAValue.GetBuffer(),strAValue.GetLength());
}
}

//MP4ファイルの以外のファイルの場合は、AACストリームにエンコードしてから、
//MP4ファイルに音声を書き込みます。

if (!IsMP4File()){
//ツール名に、現在のFAACエンコーダのバージョンを書き込みます。
CStringA strA="FAAC ";
strA.Append(m_pszFaacId);
::MP4SetMetadataTool(outfile,strA.GetBuffer());
if (!StreamOut(outfile)){
::MP4Close(outfile);
::DeleteFile(lpszFileName);
return FALSE;
}
}
::MP4Close(outfile);
return TRUE;
}

//MP4ストリームの書き込み
BOOL CMP4File::StreamOut(MP4FileHandle outfile,faacEncConfigurationPtr pConfig)
{
//エンコーダーを開きます。
unsigned long inputSamples;
unsigned long maxOutputBytes;
unsigned long totalBytesWritten=0;
faacEncHandle hEncoder=::faacEncOpen(m_wfx.nSamplesPerSec,m_wfx.nChannels,
&inputSamples, &maxOutputBytes);
if (!hEncoder) return FALSE;

//エンコーダー構成構造体の設定
faacEncConfigurationPtr config=::faacEncGetCurrentConfiguration(hEncoder);

//引数のエンコーダー構成構造体へのポインタが有効な値の場合。
if (AfxIsValidAddress(pConfig,sizeof(faacEncConfiguration),FALSE)){
m_pConfig->allowMidside =pConfig->allowMidside;
m_pConfig->useTns =pConfig->useTns;
m_pConfig->useLfe =pConfig->useLfe;
m_pConfig->aacObjectType=pConfig->aacObjectType;
m_pConfig->quantqual =pConfig->quantqual;
m_pConfig->bandWidth =pConfig->bandWidth;
m_pConfig->bitRate =pConfig->bitRate;
m_pConfig->shortctl =pConfig->shortctl;
}
m_pConfig->outputFormat =RAW_STREAM;
m_pConfig->mpegVersion =MPEG4;
if ((m_pConfig->shortctl!=SHORTCTL_NORMAL)&&
(m_pConfig->shortctl!=SHORTCTL_NOSHORT)&&
(m_pConfig->shortctl!=SHORTCTL_NOLONG))
m_pConfig->shortctl=SHORTCTL_NORMAL;
if (m_pConfig->bandWidth>(m_wfx.nSamplesPerSec/2))
m_pConfig->bandWidth=m_wfx.nSamplesPerSec/2;
m_pConfig->useLfe=(m_wfx.nChannels>=6);

//エンコーダー構成構造体を設定し直します。
config->allowMidside =m_pConfig->allowMidside;
config->useTns =m_pConfig->useTns;
config->useLfe =m_pConfig->useLfe;
config->outputFormat =m_pConfig->outputFormat;
config->mpegVersion =m_pConfig->mpegVersion;
config->aacObjectType =m_pConfig->aacObjectType;
config->quantqual =m_pConfig->quantqual;
config->bandWidth =m_pConfig->bandWidth;
config->bitRate =m_pConfig->bitRate;
config->shortctl =m_pConfig->shortctl;
config->inputFormat =FAAC_INPUT_FLOAT;

if (!::faacEncSetConfiguration(hEncoder,config)){
::faacEncClose(hEncoder);
return FALSE;
}

//フレームサイズを算出します。
UINT frameSize=inputSamples/m_wfx.nChannels;
UINT delaySamples=frameSize;

//タイムスケールの設定
::MP4SetTimeScale(outfile,90000);

//オーディオトラックをMP4ファイルに追加します。
MP4TrackId MP4track=0;
MP4track=::MP4AddAudioTrack(outfile,m_wfx.nSamplesPerSec,
MP4_INVALID_DURATION,MP4_MPEG4_AUDIO_TYPE);

//最小MPEG4オーディオプロファイルレベルの設定
::MP4SetAudioProfileLevel(outfile,0x0F);

//トラックの構成ストリームの設定
unsigned char *ASC=0;
unsigned long ASCLength=0;
::faacEncGetDecoderSpecificInfo(hEncoder,&ASC,&ASCLength);
::MP4SetTrackESConfiguration(outfile,MP4track,ASC,ASCLength);
::free(ASC);

//合計サンプル数
u_int64_t totalSamples =0;
//エンコード済みサンプル数
u_int64_t encodedSamples=0;

//エンコードするファイルの保存用サイズを取得します。
DWORD cbSrcSize=GetSizeOfSaveBuffer();
//保存バッファサイズがサンプルバッファより小さい場合は、
//サンプルバッファより大きくなるようにします。

DWORD cbSampleSize=inputSamples*m_wfx.wBitsPerSample/8;
if (cbSrcSize<cbSampleSize)
cbSrcSize=(cbSampleSize/cbSrcSize+(cbSampleSize%cbSrcSize)?1:0)*cbSrcSize;

//音源データを格納するバッファを作成します。
CByteArray byteSrcWav;
byteSrcWav.SetSize(cbSrcSize);
//バッファへのポインタを取得します。
PBYTE pSrcWav=byteSrcWav.GetData();

//サンプルバッファを作成します。
CByteArray byteSampleBuf;
byteSampleBuf.SetSize(inputSamples*sizeof(float));
float* pSampleBuf=(float*)byteSampleBuf.GetData();

//エンコードデータを格納するバッファを作成します。
DWORD cbDstSize=maxOutputBytes;
CByteArray byteDstMP4;
byteDstMP4.SetSize(cbDstSize);
//バッファへのポインタを取得します。
PBYTE pDstMP4=byteDstMP4.GetData();

//読み込み用のインデックスをゼロにします。
UINT indexSample=0;
UINT readTotalSize=0;
UINT nRemain=0;
BOOL bDrain=FALSE;
while(TRUE){
//バッファを初期化します。
::ZeroMemory(pSrcWav,cbSrcSize);
::ZeroMemory(pDstMP4,cbDstSize);

UINT writtenBytes=0;
//現在の演奏条件での音源データを取得します。
UINT readBytes=StreamIn(pSrcWav,cbSrcSize,&indexSample);
//最後まで読み込んだら、ループを抜けます。
if ((!readBytes)&&(!nRemain)&&(!writtenBytes)) break;
//最後のフレームの末尾を読み終えたら、払い出しフラグを立てます。
if (readBytes!=cbSrcSize) bDrain=TRUE;
PBYTE src=pSrcWav;
UINT bytesPerSample=m_wfx.wBitsPerSample/8;
UINT readSamples=readBytes/bytesPerSample;
//読み込み読み込んだサンプル数がまだある場合。
while(readSamples>0){
//読み込んだサンプル数がサンプルバッファのサイズを超える場合、
//バッファのサンプル数をエンコードサンプル数とします。

UINT nSamples=min(readSamples,inputSamples);
//前回読み込んだサンプルの残りがあり、
//エンコードサンプル数に残りを加算すると、
//サンプルバッファサイズを超える場合。

if ((nRemain)&&(nSamples+nRemain>inputSamples))
//エンコードサンプル数はサンプルバッファのサイズから
//前回の残りのサンプル数を差し引いたものとします。

nSamples=inputSamples-nRemain;

//Wave音源をサンプルバッファに格納します。
switch (bytesPerSample){
case 1: //サンプルバイト数=1の場合は、符号付バイトに変換してワード値に代入します。
for (UINT i=0;i<nSamples;i++)
pSampleBuf[i+nRemain]=((float)src[i]-128)*(float)256;
src+=nSamples;
break;
case 2: //サンプルバイト数=2の場合は、ワード値の上位バイトに代入します。
for (UINT i=0;i<nSamples;i++)
pSampleBuf[i+nRemain]=(float)((int16_t *)src)[i];
src+=(nSamples*2);
break;
default:
return FALSE;
}
//読み込んだサンプル数から、エンコードサンプル数を差し引きます。
readSamples-=nSamples;
//前回の残りサンプル数がある場合。
if (nRemain){
//エンコードサンプル数に、残りサンプル数を加算します。
nSamples+=nRemain;
//残りサンプル数をクリアします。
nRemain=0;
}

//合計サンプル数にサンプルバッファのエンコードサンプル数を加算します。
totalSamples+=nSamples/m_wfx.nChannels;

//サンプルバッファが一杯になれば、エンコードします。
if (nSamples==inputSamples){
//Wave音源データからAACデータに変換します。
writtenBytes=::faacEncEncode(hEncoder,(int32_t*)pSampleBuf,nSamples,pDstMP4,cbDstSize);

if (writtenBytes<0) break;
//ファイルに音源データを書き込みます。
if (writtenBytes){
u_int64_t samplesLeft=totalSamples-encodedSamples+delaySamples;
MP4Duration duration =(samplesLeft>frameSize)? frameSize:samplesLeft;
MP4Duration offset =(encodedSamples>0)? 0:delaySamples;
//AACストリームをMP4ファイルに書き込みます。
::MP4WriteSample(outfile,MP4track,pDstMP4,writtenBytes,duration,offset,1);
encodedSamples+=duration;
}
}
else nRemain=nSamples;
}
readTotalSize+=readBytes;
//オーナーウィンドウがあれば、プログレスバーの位置を通知します。
if (m_pWndOwner) m_pWndOwner->PostMessage(PBM_SETPOS,readTotalSize,0);
//払い出しフラグがあるの場合は、残りデータを吐き出させます。
if (bDrain){
UINT nSamples=nRemain;
nRemain=0;
while(writtenBytes){
//Wave音源データからAACデータに変換します。
writtenBytes=::faacEncEncode(hEncoder,(int32_t*)pSampleBuf,nSamples,pDstMP4,cbDstSize);
if (writtenBytes<=0) break;
//ファイルに音源データを書き込みます。
if (writtenBytes){
u_int64_t samplesLeft=totalSamples-encodedSamples+delaySamples;
MP4Duration duration =(samplesLeft>frameSize)? frameSize:samplesLeft;
MP4Duration offset =(encodedSamples>0)? 0:delaySamples;
//AACストリームをMP4ファイルに書き込みます。
::MP4WriteSample(outfile,MP4track,pDstMP4,writtenBytes,duration,offset,1);
encodedSamples+=duration;
}
if (nSamples) nSamples=0;
}
}
}
::faacEncClose(hEncoder);

return TRUE;
}


現在オープンされているファイル形式に適した、出力バッファサイズを返します。

//基底クラスに書き込み用のバッファサイズを戻します。
UINT CMP4File::GetSizeOfSaveBuffer()
{
//MP4ファイルがオープンされている場合。
if (IsMP4File()) return 4096;
//それ以外は既定クラスを呼び出します。
return CAACFile::GetSizeOfSaveBuffer();
}




スポンサーサイト



コメント: 0

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

Trackback+Pingback: 1

TrackBack URL for this entry
http://hiroshi0945.blog75.fc2.com/tb.php/80-7c99105d
Listed below are links to weblogs that reference
MP4ファイル音声の書き込み 第2部 from マルチメディアファイルフォーマット
Trackback from まとめwoネタ速neo 2012-07-01 Sun 00:52:43

まとめtyaiました【MP4ファイル音声の書き込み 第2部】

MP4ファイル音声の書き込み 第1部からの続きです。 (more…)

Home > AACファイルフォーマット > MP4ファイル音声の書き込み 第2部

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

この人とブロともになる

ブロとも一覧

このページの先頭へ戻る