#include "StdAfx.h"
#include "TsConverter.h"

//////////////////////////////////////////////////////////////////////////////
//FFMPEG起動関連

STARTUPINFO ffmpeg_si;
PROCESS_INFORMATION ffmpeg_pi;

bool OpenFFmpeg(LPWSTR vlc)
{
	// ffmpegの起動。
	::ZeroMemory(&ffmpeg_si, sizeof(STARTUPINFO));
	ffmpeg_si.cb           = sizeof(STARTUPINFO);
	ffmpeg_si.dwFlags      = 0;//STARTF_USESTDHANDLES;
	ffmpeg_si.wShowWindow  = SW_SHOWNORMAL;

	ffmpeg_si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
	ffmpeg_si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	ffmpeg_si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
	
	int ret = ::CreateProcess(NULL, vlc, NULL, NULL, FALSE, /*DETACHED_PROCESS*/0, NULL, NULL, &ffmpeg_si, &ffmpeg_pi);

	return TRUE;
}

void CloseFFmpeg(void)
{
	if(::WaitForSingleObject(ffmpeg_pi.hProcess, 0) == WAIT_TIMEOUT){
//		log_out("ffmpegの終了を待っています。\n");
		::WaitForSingleObject(ffmpeg_pi.hProcess, INFINITE);
//		log_out("ffmpeg終了。\n");
	}
}



CTsConverter::CTsConverter(IEventHandler *pEventHandler)
	: m_pEventHandler(pEventHandler)
	, m_FileReader(this)
	, m_TsPacketParser(this)
	, m_ProgManager(this)
	, m_TsDescrambler(this)
	, m_VideoFile(this)
	, m_AudioFile(this)
	, m_AacConverter(this)
	, m_WaveWriter(this)
	, m_WavWriter(this)
	, m_WavSplitWriter(this)
	, m_TsServiceSelect(this)
{
	m_AudHoseiFromVid = FALSE;
	m_pWaveSend = NULL;
	m_pVideoSend = NULL;
	
	m_pVideoBand = NULL;
	m_pAudioBand = NULL;

	m_pTsDemuxer = NULL;
	m_Useffmpg = FALSE;
	m_vframe_hokan = FALSE;
}

CTsConverter::~CTsConverter(void)
{
	CancelConvert();
}

static BYTE WaveHead[] =		// from CWaveWriter@BonTsDemux
{
	'R', 'I', 'F', 'F',				// +0	RIFF
	0x00U, 0xf8U, 0xffU, 0xffU,		// +4	これ以降のファイルサイズ(ファイルサイズ - 8)
	'W', 'A', 'V', 'E',				// +8	WAVE
	'f', 'm', 't', ' ',				// +12	fmt
	0x10U, 0x00U, 0x00U, 0x00U,		// +16	fmt チャンクのバイト数
	0x01U, 0x00U,					// +18	フォーマットID
	0x02U, 0x00U,					// +20	ステレオ
	0x80U, 0xBBU, 0x00U, 0x00U,		// +24	48KHz
	0x00U, 0xEEU, 0x02U, 0x00U,		// +28	192000Byte/s
	0x04U, 0x00U,					// +30	ブロックサイズ
	0x10U, 0x00U,					// +32	サンプルあたりのビット数
	'd', 'a', 't', 'a',				// +36	data
	0x00U, 0xffU, 0xffU, 0xffU		// +40	波形データのバイト数
};

const bool CTsConverter::ConvertTsFile(LPCTSTR lpszTsFile, QWORD qwInputSizeLimit,WORD wServiceSelect, LPCTSTR lpszVideoFile, LPCTSTR lpszAudioFile, const bool bDecodeAac, const bool bLipSync,LPCTSTR ffmpeg_param,DWORD SoundMethod,DWORD AudioEs,BOOL bRf64,BOOL vhokan)
{
/* 
	−AACデコードなし−
	m_FileReader → [m_TsPacketParser] → m_TsDescrambler → m_ProgManager → m_TsDemuxer → m_VideoFile
　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　→ m_AudioFile
	−AACデコードあり−
	m_FileReader → [m_TsPacketParser] → m_TsDescrambler → m_ProgManager → m_TsDemuxer → m_VideoFile
　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　→ m_AacConverter → m_WaveWriter
*/

	// デコーダを一旦クローズ
	CloseDecoders();

	m_vframe_hokan = vhokan;

	m_AudioEsNum = AudioEs;
	m_ServiceSelect = wServiceSelect;

	m_AacConverter.SetOutputChannel(2);
	m_AacConverter.SetStereoMethod(0);		// Stereo To Mono
	WaveHead[22] = 0x02;
	m_achannel = 2*2;
	m_Useffmpg = FALSE;

	if(SoundMethod == 3 || SoundMethod == 4){			// 5.1ch指定
		m_AacConverter.SetOutputChannel(6);
		WaveHead[22] = 0x06;
		m_achannel = 6*2;
	} else if (SoundMethod == 1 || SoundMethod == 2){
		m_AacConverter.SetStereoMethod(SoundMethod);
	}

	try{
		// TSファイルを開く
		if(!m_FileReader.OpenFile(lpszTsFile,qwInputSizeLimit))throw 0UL;
		m_pTsDemuxer = new CTsDemuxer(this);

		if(ffmpeg_param == NULL || ffmpeg_param[0] == NULL){

			if(lpszVideoFile != NULL && lpszVideoFile[0] != NULL){
				if(!m_VideoFile.OpenFile(lpszVideoFile))throw 1UL;
				m_pTsDemuxer->SetOutputDecoder(&m_VideoFile, CTsDemuxer::OUTPUT_VIDEO);
			}

			if(lpszAudioFile != NULL && lpszAudioFile[0] != NULL){
				if(SoundMethod==4){		// 5.1ch split
					if(!m_WavSplitWriter.OpenFile(lpszAudioFile, 16, 6 , 48000))throw 2UL;					
					m_AacConverter.SetOutputDecoder(&m_WavSplitWriter);
				} else {				// not split
					if(!m_WavWriter.OpenFile(lpszAudioFile, 16, SoundMethod==3 ? 6 : 2 , 48000 , bRf64 ? FALSE : TRUE))throw 2UL;
					m_AacConverter.SetOutputDecoder(&m_WavWriter);
				}
				m_pTsDemuxer->SetOutputDecoder(&m_AacConverter, CTsDemuxer::OUTPUT_AUDIO);
			}
		} else {
			m_Useffmpg = TRUE;

			m_pVideoBand = new CKeepRate(64*1024,			//packet
											0,				//margin
											20*1024*1024,	//max
											0,	//max_bps
											TRUE,			//copydata
											L"M2V",			//name
											this);	//VIDEO用クッションバッファ作る
			m_pVideoSend = new CHttpSend(this,1234,64*1024);
				
			m_pTsDemuxer->SetOutputDecoder(m_pVideoBand, CTsDemuxer::OUTPUT_VIDEO);
			m_pVideoBand->SetOutputDecoder(m_pVideoSend);

			m_pTsDemuxer->SetOutputDecoder(&m_AacConverter, CTsDemuxer::OUTPUT_AUDIO);

			m_pAudioBand = new CKeepRate(1024*64,		//packet
												0,		//margin
												10*1024*1024,		//max
												0,		//max_bps
												TRUE,	//copydata
												L"WAV",	//name
												this);	//Audio用クッションバッファ作る
			m_AacConverter.SetOutputDecoder(m_pAudioBand);

			m_pWaveSend = new CHttpSend(this,1234,64*1024);
			m_pAudioBand->SetOutputDecoder(m_pWaveSend);

			CMediaData *m = new CMediaData(WaveHead, (DWORD)sizeof(WaveHead));
			m_pWaveSend->InputMedia(m);
			delete m;
		}


		// B-CASカードが利用可能なときはTSデスクランブラを接続する
		if(m_TsDescrambler.OpenBcasCard()){
			// B-CASカード利用可能
			m_TsPacketParser.SetOutputDecoder(&m_TsDescrambler);
			m_TsDescrambler.SetOutputDecoder(&m_TsServiceSelect);	
			m_TsServiceSelect.SetOutputDecoder(&m_ProgManager);	
			}
		else{
			// B-CASカード利用不可
			m_TsPacketParser.SetOutputDecoder(&m_TsServiceSelect);	
			m_TsServiceSelect.SetOutputDecoder(&m_ProgManager);
			}

		// デコーダグラフ接続
		m_FileReader.SetOutputDecoder(&m_TsPacketParser);
		m_ProgManager.SetOutputDecoder(m_pTsDemuxer);
		m_pTsDemuxer->EnableLipSync(bLipSync);
		}
	catch(DWORD dwErrorCode){
		// エラー発生
		CloseDecoders();
		return false;
		}
	if(ffmpeg_param && ffmpeg_param[0] != NULL){

		wchar_t szModulePath[256],drv[10],path[256],ext[256],appname[256],execname[256];
		wchar_t enc_param[256];
		enc_param[0] = NULL;
		GetModuleFileName(NULL,szModulePath,256);
		_wsplitpath(szModulePath,drv,path,appname,ext);
		_wmakepath(execname,drv,path,L"ffmpeg",L"exe");


		CString arg;
		arg = ffmpeg_param;
		arg = L" -f mpeg2video -i \"http://localhost:1234/video.m2v\" -f wav -i \"http://localhost:1234/audio.wav\" " + arg;
		arg = L"\"" + arg;
		arg = execname + arg;
		arg = L"\"" + arg;

		if(lpszVideoFile){
			arg = arg + L" \"";
			arg += lpszVideoFile;
			arg = arg + L"\"";
		} else {
			arg = arg + L" testout.mpg";
		}
		OpenFFmpeg(arg.GetBuffer());

		m_pVideoSend->IsOpen();
	}


	// デコーダグラフリセット
	m_FileReader.Reset();
	
	// コンバート開始
	return m_FileReader.StartReadAnsync();
}

void CTsConverter::CancelConvert(void)
{
	// コンバート中止
	m_FileReader.StopReadAnsync();
}

void CTsConverter::CloseDecoders(void)
{
	// デコーダを閉じる
	m_TsDescrambler.CloseBcasCard();
	m_FileReader.CloseFile();
	m_VideoFile.CloseFile();
	m_AudioFile.CloseFile();
	m_WaveWriter.CloseFile();
	m_WavSplitWriter.CloseFile();
	m_WavWriter.CloseFile();

	while((m_pVideoBand && m_pVideoBand->m_bThreadIdle == FALSE) && (m_pAudioBand && m_pAudioBand->m_bThreadIdle == FALSE)){
		Sleep(100);
	}

	if(m_pVideoBand)	m_pVideoBand->StopTrans();
	if(m_pAudioBand)	m_pAudioBand->StopTrans();

	if(m_pWaveSend)	m_pWaveSend->Close();
	if(m_pVideoSend)	m_pVideoSend->Close();

	if(m_pVideoBand)	delete m_pVideoBand;
	if(m_pAudioBand) 	delete m_pAudioBand;
	
	if(m_pWaveSend)		delete m_pWaveSend;
	if(m_pVideoSend)	delete m_pVideoSend;

	if(m_pTsDemuxer)	delete m_pTsDemuxer;

	m_pWaveSend = NULL;
	m_pVideoSend = NULL;

	m_pVideoBand = NULL;
	m_pAudioBand = NULL;

	m_pTsDemuxer = NULL;

	CloseFFmpeg();
}

const DWORD CTsConverter::OnDecoderEvent(CMediaDecoder *pDecoder, const DWORD dwEventID, PVOID pParam)
{
	// デコーダからのイベントを受け取る(暫定)
	if(pDecoder == &m_ProgManager){
		
		// プログラムマネージャからのイベント
		switch(dwEventID){
			case CProgManager::EID_SERVICE_LIST_UPDATED : {
				// サービスの構成が変化した
				m_pEventHandler->OnTsConverterServiceInfo(&m_ProgManager);
				WORD wVideoPID = 0xFFFF;
				WORD wAudioPID = 0xFFFF;
				if(m_ServiceSelect==NO_SERVICE_SELECT){
					m_ProgManager.GetServiceEsPID(&wVideoPID, &wAudioPID);
				} else {
					for(WORD wSearchServiceIndex=0;wSearchServiceIndex<m_ProgManager.GetServiceNum();wSearchServiceIndex++){
						WORD wSearchServiceID;
						if(m_ProgManager.GetServiceID(&wSearchServiceID,wSearchServiceIndex)){
							if(wSearchServiceID==m_ServiceSelect){
								m_ProgManager.GetServiceEsPID(&wVideoPID, &wAudioPID, wSearchServiceIndex);	
								if(m_AudioEsNum){
									m_TsServiceSelect.GetAudioEs(m_AudioEsNum,NULL,&wAudioPID);
									}
								}
							}
						}
					}
				m_pTsDemuxer->SetVideoPID(wVideoPID);
				m_pTsDemuxer->SetAudioPID(wAudioPID);
				m_pEventHandler->OnTsConverterServiceName(TEXT("サーチ中..."));
				return 0UL;
				}

			case CProgManager::EID_SERVICE_INFO_UPDATED : {
				// サービス情報が更新された
				m_pEventHandler->OnTsConverterServiceInfo(&m_ProgManager);
				TCHAR szServiceName[1024] = {TEXT("不明")};
				for(WORD wSearchServiceIndex=0;wSearchServiceIndex<m_ProgManager.GetServiceNum();wSearchServiceIndex++){
					WORD wSearchServiceID;
					if(m_ProgManager.GetServiceID(&wSearchServiceID,wSearchServiceIndex)){
						if(wSearchServiceID==m_ServiceSelect){
								m_ProgManager.GetServiceName(szServiceName,wSearchServiceIndex);
								m_pEventHandler->OnTsConverterServiceName(szServiceName);
							}
						}
					}
				return 0UL;
				}
			}
	} else if(pDecoder == m_pTsDemuxer){
	
		switch(dwEventID){
		
			case CTsDemuxer::EID_SERVICE_FIRST_AAC:					// しょっぱな補完。基点をVIDEOのPTSにあわせるため、差分を埋める
				{
					LONG hokan;
					LONGLONG diff;
					diff = m_pTsDemuxer->GetM2VAACDif();
					diff = diff * 48 * m_achannel / 90;
					hokan = (LONG)diff;
					hokan -= (hokan % m_achannel);
					if(hokan > 1920000 || hokan < -1920000){
						TRACE(L"初回で10秒以上ずれている。\n");
						break;
					}
					TRACE1("初回補正 : %d\n",hokan);
					if(hokan > 0){							// Videoが遅れている場合
						BYTE* dummy = new BYTE[hokan];
						ZeroMemory(dummy,hokan);

						CMediaData *m = new CMediaData(dummy, hokan);

						delete [] dummy;

						if(m_Useffmpg) m_pAudioBand->InputMedia(m);
						else m_WaveWriter.InputMedia(m);


						delete m;
						m_AacConverter.m_total_frame += hokan / m_achannel;

					} else if (hokan < 0){					// Videoが進んでいる場合
						m_AacConverter.SetCutFrame((DWORD)(m_pTsDemuxer->GetM2VAACDif() *-1* 48 / 90));	// Video先頭にあわせるために、削除するAudioフレーム数を設定
						m_pTsDemuxer->m_FirstAudioPts = m_pTsDemuxer->m_FirstVideoPts;			// 先頭がVIDEOと同期するので、Audioの先頭PTSをVIDEOと同じにする
					}
				}

				break;
			case CTsDemuxer::EID_SERVICE_M2V_COMMIT:
				if(m_vframe_hokan){
					if(m_AacConverter.GetTotalSample() < 100000) break;

					LONGLONG pts_ms = m_pTsDemuxer->GetTotalVideoPts();
					LONGLONG frame_ms = (LONGLONG)m_pTsDemuxer->GetVideoTotalFrame() * 3003;		// * 1000 / 29.97(fps)  → ms
					LONGLONG diff;

					diff = (pts_ms - frame_ms);

					if(diff >= 70 || diff <= -70){
						m_pTsDemuxer->m_FirstAudioPts += (LONGLONG)diff;		// 何らかの原因で、Videoが欠落している。
						m_pTsDemuxer->m_FirstVideoPts += (LONGLONG)diff;		// 補正する。
						TRACE((LPCTSTR)L"frame_ms:%I64d   , pts_ms:%I64d\n",frame_ms,pts_ms);
					}
				}
				break;
			case CTsDemuxer::EID_SERVICE_AAC_COMMIT:
				{

				if(m_AacConverter.GetTotalSample() < 100000) break;

//				TRACE2("AAC	%I64d	%d\n",m_pTsDemuxer->m_NowAudioPts,m_AacConverter.GetTotalSample());

				LONGLONG diff;
				diff = (m_pTsDemuxer->GetTotalAudioPts() / 90 - m_AacConverter.GetTotalSample() / 48);	// PTS 90kHz Audio 48kHz らしいので、単位をmsにあわせて差分をとる。

//				m_AudioLpfBuf = 0.05 * diff + 0.95 * m_AudioLpfBuf;		// PESと実データの差分をLPFに通す。係数は適当。。
//				m_AudioLpfBuf = diff;
				if(m_pTsDemuxer->m_NowAudioPts == -1) break;

				if (diff > 5000 || diff < -5000){		// ±2sec以上は、やりすぎなので恐らくエラーが発生している
					diff = 0;
					m_AacConverter.ResetTotalFrame();
					m_pTsDemuxer->ResetTotalAudioPts();
					m_pTsDemuxer->EnableLipSync(TRUE);
				} else {
					if (diff == 0){
						m_AacConverter.SetHoseiPol(0);
						m_AudHoseiFromVid = 0;
					} else if(diff >= 70){					// 2 frame 以上ずれていたら
						TRACE((LPCTSTR)L"diff : %dms\n",(LONG)diff);
						if(diff >= 200){												// 200ms以上ずれていたら、NULLで一気に補完
							BYTE* dummy = new BYTE[(int)diff*48*m_achannel];			// 音声フレームが足りていない場合は、NULLで一気に補完するように変更
							ZeroMemory(dummy,(int)diff*48*m_achannel);

							CMediaData *m = new CMediaData(dummy, (int)diff*48*m_achannel);
							delete [] dummy;

							if(m_Useffmpg) m_pAudioBand->InputMedia(m);
							else m_WaveWriter.InputMedia(m);

							delete m;

							m_AacConverter.m_total_frame += diff*48;
						} else {														// 200ms未満だったら、徐々に補完
							m_AacConverter.SetHoseiPol((LONG)4);				
							m_AudHoseiFromVid = 1;
						}

					} else if (diff <= -70){			// -2 frame以上ずれていたら
						TRACE((LPCTSTR)L"diff : %dms\n",(LONG)diff);			// 音声フレームが先走っている場合は、徐々にずらしていく(カットするとノイズになるため)
						m_AacConverter.SetHoseiPol((LONG)-4);				
						m_AudHoseiFromVid = -1;
					} else if (m_AudHoseiFromVid < 0){	// 
						TRACE((LPCTSTR)L"diff : %dms\n",(LONG)diff);
						m_AacConverter.SetHoseiPol((LONG)-4);				
						m_AudHoseiFromVid = -1;
					} else if (m_AudHoseiFromVid > 0){
						TRACE((LPCTSTR)L"diff : %dms\n",(LONG)diff);
						m_AacConverter.SetHoseiPol((LONG)4);				
						m_AudHoseiFromVid = 1;
					}
				}

				m_AacConverter.ResetPesPerFrame();

				}
				break;
		}
	} else if(pDecoder == &m_FileReader){
		
		// ファイルリーダからのイベント
		switch(dwEventID){
			case CFileReader::EID_READ_ASYNC_START :
				// 非同期リード開始
				m_pEventHandler->OnTsConverterStart(m_FileReader.GetFileSize());
				return 0UL;
			
			case CFileReader::EID_READ_ASYNC_END :
				// 非同期リード終了
				CloseDecoders();
				m_pEventHandler->OnTsConverterEnd(m_FileReader.GetFileSize());
//				m_WaveSend.Close();
//				m_VideoSend.Close();
//				CloseFFmpeg();
				return 0UL;
			
			case CFileReader::EID_READ_ASYNC_POSTREAD :
				// 非同期リード後
				m_pEventHandler->OnTsConverterProgress(m_FileReader.GetReadPos(), m_FileReader.GetFileSize());
				return 0UL;
			}		
	} else if(pDecoder == &m_TsServiceSelect){
		switch(dwEventID){
			case CTsServiceSelect::EID_EPG_UPDATE :
				break;
		}
	}
	
	
	return 0UL;

}
