#include "stdafx.h"
#include "refC音声ストリーム抽出.h"

namespace FDK
{
// public:

	/// <summary>
	/// 動画ファイル fileName から最初のオーディオストリームをデコードして MemoryStream に格納して返す。
	/// </summary>
	void C音声ストリーム抽出::tWAVイメージ抽出( String^ fileName, [ Out ] MemoryStream^% msWaveImage )
	{
		HRESULT hr = S_OK;
		IMFSourceReader *pReader = NULL;
		IMFMediaType *pAudioType = NULL;    // PCM オーディオフォーマットを表す。
		BinaryWriter^ bwWaveFile = nullptr;

		try
		{
			// 入力ファイルを指定して SourceReader を作成する。
			{
				pin_ptr<const wchar_t> fname = ::PtrToStringChars( fileName );
				tFAILEDなら例外発生( L"入力ファイルからの SourceReader の作成", 
					hr = ::MFCreateSourceReaderFromURL( fname, NULL, &pReader ) );
			}


			// 出力WAVEファイルイメージライター作成。

			bwWaveFile = gcnew BinaryWriter( msWaveImage );


			// ソースファイルから非圧縮 PCM オーディオを取得するよう、SourceReader を設定する。PCMオーディオタイプを pAudioType に受け取る。
				
			pAudioType = NULL;
			C音声ストリーム抽出::ConfigureAudioStream( pReader, &pAudioType );


			// WAVEファイルヘッダを書き出す。
				
			DWORD cbHeader = 0;		// 書き出された WAVE ファイルヘッダのサイズを受け取る。バイト単位。
			C音声ストリーム抽出::WriteWaveHeader( bwWaveFile, pAudioType, &cbHeader );
				

			// オーディオデータをファイルにデコードする。
				
			DWORD cbAudioData = 0;	// ファイルに出力された総 PCM オーディオデータサイズを受け取る。バイト単位。
			C音声ストリーム抽出::WriteWaveData( bwWaveFile, pReader, &cbAudioData );
				
				
			// 正しいサイズの RIFF ヘッダを取り付ける。
				
			C音声ストリーム抽出::FixUpChunkSizes( bwWaveFile, cbHeader, cbAudioData );

			#if _DEBUG
			// ファイルに出力（デバッグ用）
			//msWaveImage->WriteTo( gcnew FileStream( gcnew String( L"C:\\ProgramData\\StrokeStyleT\\test.wav" ), FileMode::Create ) );
			#endif
		}
		catch( Exception^ e原因 )
		{
			throw gcnew Exception( L"WAVイメージの抽出に失敗しました。", e原因 );
		}
		finally
		{
			if( nullptr != bwWaveFile )
			{
				bwWaveFile->Close();
				delete bwWaveFile;
			}
			SAFE_COM_RELEASE( pAudioType );
			SAFE_COM_RELEASE( pReader );
		}
	}

// private;

	void C音声ストリーム抽出::ConfigureAudioStream( IMFSourceReader *pReader, [Out] IMFMediaType** ppAudioType )
	{
		HRESULT hr = S_OK;
        IMFMediaType *pUncompressedAudioType = NULL;
        IMFMediaType *pPartialType = NULL;

		try
		{
			// 最初のオーディオストリームを選択し、その他のすべてのストリームを非選択にする。

			tFAILEDなら例外発生( L"すべてのオーディオストリームの選択の否定",
				hr = pReader->SetStreamSelection( MF_SOURCE_READER_ALL_STREAMS, FALSE ) );

			tFAILEDなら例外発生( L"最初のオーディオストリームの選択",
				hr = pReader->SetStreamSelection( MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE ) );


	        // 非圧縮 PCM オーディを表す不完全メディアタイプを作成する。
				
			tFAILEDなら例外発生( L"不完全メディアタイプの作成",
				hr = ::MFCreateMediaType( &pPartialType ) );

			tFAILEDなら例外発生( L"メジャータイプ(Audio)の設定",
				hr = pPartialType->SetGUID( MF_MT_MAJOR_TYPE, MFMediaType_Audio ) );

			tFAILEDなら例外発生( L"サブタイプ(PCM)の設定",
				hr = pPartialType->SetGUID( MF_MT_SUBTYPE, MFAudioFormat_PCM ) );


			// このメディアタイプを SourceReader にセットする。SourceReader は必要なデコーダを自動的にロードする。

			tFAILEDなら例外発生( L"最初のストリームへのメディアタイプの設定",
				hr = pReader->SetCurrentMediaType( (DWORD) MF_SOURCE_READER_FIRST_AUDIO_STREAM, NULL, pPartialType ) );
				
				
			// 完全な非圧縮フォーマットを取得する。
				
			tFAILEDなら例外発生( L"完全な非圧縮フォーマットの取得",
				hr = pReader->GetCurrentMediaType( (DWORD) MF_SOURCE_READER_FIRST_AUDIO_STREAM, &pUncompressedAudioType ) );
				
				
			// ストリームが選択されていることを保証する。
				
			tFAILEDなら例外発生( L"最初のストリームの選択（２回目）",
				hr = pReader->SetStreamSelection( (DWORD) MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE ) );
				
				
			// PCM フォーマットを呼び出し元に返す。

			*ppAudioType = pUncompressedAudioType;
			( *ppAudioType )->AddRef();
		}
		catch( Exception^ e原因 )
		{
			throw gcnew Exception( L"オーディオストリームの選択とPCMフォーマットの取得に失敗しました。", e原因 );
		}
		finally
		{
			SAFE_COM_RELEASE( pUncompressedAudioType );
			SAFE_COM_RELEASE( pPartialType );
		}
	}
	void C音声ストリーム抽出::WriteWaveHeader( BinaryWriter^ bwFile, IMFMediaType *pAudioType, [Out] DWORD *pcbWritten )
	{
		HRESULT hr = S_OK;
		UINT32 cbFormat = 0;
		WAVEFORMATEX *pWav = NULL;
		*pcbWritten = 0;

		try
		{
			// PCM オーディオフォーマットを WAVEFORMATEX 構造体に変換する。
			
			tFAILEDなら例外発生( L"PCM オーディオフォーマットから WAVEFORMATEX 構造体への変換",
				hr = ::MFCreateWaveFormatExFromMFMediaType( pAudioType, &pWav, &cbFormat ) );
				
				
			// 'RIFF' ヘッダと最初の 'fmt ' チャンクを書き出す。
				
			bwFile->Write( (UInt32) 0x46464952 );    // 'RIFF'
			bwFile->Write( (UInt32) 0 );             // ファイルサイズ - 8（未定）
			bwFile->Write( (UInt32) 0x45564157 );    // 'WAVE'
			bwFile->Write( (UInt32) 0x20746d66 );    // 'fmt '
			bwFile->Write( (UInt32) cbFormat );      // fmtチャンクサイズ
				
				
			// WAVEFORMATEX 構造体を書き出す。

			for( UINT32 i = 0; i < cbFormat; i++ )
				bwFile->Write( (UCHAR) ((UCHAR*)pWav)[i] );

				
			// 'data' チャンクの開始を書き出す。
				
			bwFile->Write( (UInt32) 0x61746164 );    // 'data'
			bwFile->Write( (UInt32) 0 );             // data チャンクサイズ（未定）
				
				
			// 最終的な書き込み量を計算。

			*pcbWritten = sizeof(UInt32) * 5 + cbFormat + sizeof(UInt32) * 2;
		}
		catch( Exception^ e原因 )
		{
			throw gcnew Exception( L"WAVEヘッダの書き出しに失敗しました。", e原因 );
		}
		finally
		{
			if( pWav )
				::CoTaskMemFree( pWav );
		}
	}
	void C音声ストリーム抽出::WriteWaveData( BinaryWriter^ bwFile, IMFSourceReader *pReader, [Out] DWORD *pcbDataWritten )
	{
		HRESULT hr = S_OK;
		DWORD cbAudioData = 0;
		DWORD cbBuffer = 0;
		BYTE *pAudioData = NULL;
        IMFSample *pSample = NULL;
        IMFMediaBuffer *pBuffer = NULL;

		*pcbDataWritten = 0;

		try
		{
			// SourceReader からオーディオサンプルを取得する。

			while( true )
			{
				DWORD dwFlags = 0;
					

				// 次のサンプルを読み込む。
					
				tFAILEDなら例外発生( L"サンプルの読み込み",
					hr = pReader->ReadSample( (DWORD) MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, NULL, &dwFlags, NULL, &pSample ) );

				if( dwFlags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED )		// メディアタイプが変わったらここで抜ける。
					break;

				if( dwFlags & MF_SOURCE_READERF_ENDOFSTREAM )					// 最後まで読み込んだら抜ける。
					break;
					
				if( NULL == pSample )											// サンプルがなかったらもう一度。
					continue;

					
				// サンプルからオーディオデータをロックしてそのポインタを取得する。
					
				tFAILEDなら例外発生( L"サンプルからのオーディオデータポインタの取得",
					hr = pSample->ConvertToContiguousBuffer( &pBuffer ) );

				tFAILEDなら例外発生( L"オーディオデータのロック",
					hr = pBuffer->Lock( &pAudioData, NULL, &cbBuffer ) );


				// このデータを出力ファイルに書き込む。

				for( DWORD i = 0; i < cbBuffer; i++ )
					bwFile->Write( (UCHAR) pAudioData[i] );


				// バッファのロックを解除する。

				tFAILEDなら例外発生( L"オーディオデータのロック解除",
					hr = pBuffer->Unlock() );

				pAudioData = NULL;
					
					
				// オーディオデータの書き込み総量を加算する。

				cbAudioData += cbBuffer;


				// サンプルとバッファを解放し、次のサンプルへ。

				SAFE_COM_RELEASE( pSample );
				SAFE_COM_RELEASE( pBuffer );
			}


			// 書き込んだ総データ量を返す。

			*pcbDataWritten = cbAudioData;
		}
		catch( Exception^ e原因 )
		{
			throw gcnew Exception( L"WAVEデータの書き出しに失敗しました。", e原因 );
		}
		finally
		{
			if( pAudioData )
				pBuffer->Unlock();	// Lock() 時に得た pAudioData も解放される。

			SAFE_COM_RELEASE( pSample );
			SAFE_COM_RELEASE( pBuffer );
		}
	}
	void C音声ストリーム抽出::FixUpChunkSizes( BinaryWriter^ bwFile, UInt32 cbHeader, DWORD cbAudioData )
	{
		// データサイズを書き込む。

		bwFile->Seek( cbHeader - sizeof(UInt32), System::IO::SeekOrigin::Begin );
		bwFile->Write( (UInt32) cbAudioData );


		// ファイルサイズを書き込む。

		bwFile->Seek( sizeof(UInt32), System::IO::SeekOrigin::Begin );
		bwFile->Write( (UInt32)( cbHeader + cbAudioData - 8 ) );
	}
}