/**
 * @brief           Movie Player Sample API
 * @author          AXELL CORPORATION
 * @history         2017_02_22  初版
 * @history         2017_10_26  Ver2.0
 * @history         2019_03_08  [SDK2.2] MPSでのファイル名解析不具合を修正 (#1933)
 * @history         2019_03_08  [SDK2.2] MPSでのGVDサーチレンジの設定によってデコーダが終了しなくなる不具合等を修正 (#2449)
 * @history         2019_03_08  [SDK2.2] MPSでのコンテナ解析エラー処理をより良い方法に変更 (#2473)
 * @history         2020_07_22  [SDK3.1] MPSでのフラグ待ちにタイムアウトを追加 (#3034)
 * @note            エラーコードはAG903_error.hを参照のこと.
 */

/*
 * This program was created by AXELL CORPORATION.
 * Copyright (C) 2017-2020 AXELL CORPORATION, all rights reserved.
 */

#include "mps/mps_api.h"

/** @brief GVD初期化パラメータ */
static gvdInitializeParam _GVDInitParam;
/** @brief MPS設定パラメータ */
static mpsConfig _MPSConfig;
/** @brief コンテキスト情報 */
static mpsContext _MPSContext[16] = {{0}};
/** @brief BOXの位置情報 */
static mpsInfoPos _MPSInfoPos[16] = {{0}};
/** @brief SPS/PPS保持領域 */
static mpsAVCHeader _MPSAVCHeader[16] = {{0}};

static gvdFatalErrorInfo fatalinfo = {0,0};

/** @brief デコード完了コールバック関数 */
static gvdCbFunc cbDecodeDone;

/** @brief 非同期待ち周期ディレイ */
#define TIME_ASYNC_WAIT 1

/** @brief タイムアウト時間（非同期待ち周期ディレイ単位）, 0:無効 */
#define MPS_API_TIMEOUT 1000

/*
 * 指定されたフラグがtrue(!=0)になるまで待機する.
 * 致命的エラーが発生 / タイムアウトでも返る.
 */
static int32_t waitforflag(bool volatile *flag, uint32_t timeout) {
    int32_t  rtcd = AG903_ENONE;
    uint32_t cnt  = 0;
	while(1)
    {
		GVD_GetFatalError(&fatalinfo);
		if(fatalinfo.cause != 0) {
			break;
		}
		if(*flag) {
			break;
		}
        if(timeout > 0 && cnt > timeout) {
            rtcd = -AG903_ETIMEDOUT;
            break;
        }
		MPS_Delay(TIME_ASYNC_WAIT);
        cnt++;
	}

    return rtcd;
}

/**
 * MPS初期化時に自動的に設定されますが、
 * ユーザはアプリ内から設定内容を参照することができます.
 * @brief GVD設定値の取得
 * @return GVD設定値構造体
 */
gvdInitializeParam *MPS_getGVDInitParam(void)
{
	return &_GVDInitParam;
}

/**
 * MPS設定値の取得
 * @return MPS設定値構造体
 */
mpsConfig *MPS_getConfig(void)
{
	return &_MPSConfig;
}

/**
 * MPSコンテキストの取得
 * @param movie_id [in]ストリームID
 * @return MPSコンテキスト構造体
 */
mpsContext *MPS_getContext(uint32_t movie_id)
{
	return &_MPSContext[movie_id];
}

/**
 * Boxオフセット位置情報の取得
 * @param movie_id [in]ストリームID
 * @return Boxオフセット位置情報構造体
 */
mpsInfoPos *MPS_getInfoPos(uint32_t movie_id)
{
	return &_MPSInfoPos[movie_id];
}

/**
 * スタートコードを含むSPS/PPSを取得します. <p>
 * _MPSAVCHeaderは、parse()@mps_mp4parser.cで確保、整形されます. <p>
 * ストリームにSPS/PPSを含まないMP4ファイルを再生する場合に参照します.
 * @brief AVCヘッダの取得
 * @return AVCヘッダ構造体
 */
mpsAVCHeader *MPS_getAVCHeader(uint32_t movie_id)
{
	return &_MPSAVCHeader[movie_id];
}

/**
 * MPSの初期化処理<p>
 * MPSの全ての関数はこの関数より後に実行してください.
 * @brief MPSの初期化
 * @param setting MPS設定構造体
 * @return エラーコード
 */
int32_t MPS_Initialize(mpsSetting setting)
{
	if(setting.max_stream == 0 || setting.max_stream > 16)
	    return -AG903_EINVAL;

	gvdError err;
	int32_t i, size;

	/* パラメータ初期化 */
	_MPSConfig.memory_work_top = NULL;
	_MPSConfig.memory_work_size = 0;
	_MPSConfig.memory_heap_top = NULL;
    _MPSConfig.memory_heap_size = 0;
	_MPSConfig.output_select = GVD_OUTMOD_NONE;
    _MPSConfig.num_out_buffer = 0;

	size = (int32_t)sizeof(_MPSContext);
	for(i=0; i<size; i++)
	    *((uint8_t *)(_MPSContext) + i) = 0x00;

	size = (int32_t)sizeof(_MPSInfoPos);
	for(i=0; i<size; i++)
	    *((uint8_t *)(_MPSInfoPos) + i) = 0x00;

	size = (int32_t)sizeof(_MPSAVCHeader);
	for(i=0; i<size; i++)
	    *((uint8_t *)(_MPSAVCHeader) + i) = 0x00;

	cbDecodeDone = NULL;

	_GVDInitParam.func_notify_fatalerror = setting.func_fatalerror_handler;
	_GVDInitParam.param_notify_fatalerror = NULL;
	_GVDInitParam.vramaddr_mask = 0x00000000;
	_GVDInitParam.bmuaddr_mask = 0x00000000;
	_GVDInitParam.stream_buf_size = setting.stream_buffer_size;
	_GVDInitParam.stream_buf_num = setting.stream_buffer_num;
	_GVDInitParam.stream_num = setting.max_stream;

	cbDecodeDone = setting.func_decode_done_handler;

	err = GVD_InitInitializeParam(&_GVDInitParam);
	if (err != GVD_ERR_SUCCESS) {
	    return -AG903_EINVAL;
	}

	return AG903_ENONE;
}

/**
 * GVDが要求するメモリサイズの取得
 * @brief ワークメモリ必要サイズ取得
 * @return 必要メモリサイズ
 */
uint32_t MPS_GetRequiredWorkMemorySize(void)
{
	return GVD_GetRequiredMemorySize();
}

/**
 * GVDの初期化処理<p>
 * config内で設定したメモリ領域はこのAPI実行時から使用されます.
 * @brief GVDの開始処理
 * @param config GVD初期化パラメータ構造体
 * @return エラーコード
 */
int32_t MPS_Start(mpsConfig config)
{
	if(config.memory_work_top == NULL || config.memory_heap_top == NULL)
	    return -AG903_EINVAL;

	gvdError err;
	int32_t idx;

	_MPSConfig.memory_work_top = config.memory_work_top;
	_MPSConfig.memory_work_size = config.memory_work_size;
	_MPSConfig.memory_heap_top = config.memory_heap_top;
	_MPSConfig.memory_heap_size = config.memory_heap_size;
	_MPSConfig.output_select = config.output_select;
	_MPSConfig.num_out_buffer = config.num_out_buffer;

	err = GVD_SetWorkMemory(_MPSConfig.memory_work_top,
							_MPSConfig.memory_work_size);
	if (err != GVD_ERR_SUCCESS) {
	    return -AG903_EINVAL;
	}

	err = GVD_Initialize();
	if (err != GVD_ERR_SUCCESS) {
	    return -AG903_EINVAL;
	}

	{
	    /* コールバックの設定 */
	    gvdCbFuncs cbFuncs;
	    {
			int32_t i;
			int32_t size = (int32_t)sizeof(gvdCbFuncs);
			for(i=0; i<size; i++)
			{
				*((uint8_t *)(&cbFuncs) + i) = 0x00;
			}
	    }
	    cbFuncs.commit_output_buffer = cbDecodeDone;
	    cbFuncs.connect_done =
			(int32_t (*)(void *, void *, gvdError))cbMPSConnectDone;
	    cbFuncs.disconnect_done =
			(int32_t (*)(void *, void *, gvdError))cbMPSDisConnectDone;
	    cbFuncs.play_accepted =
			(int32_t (*)(void *, void *, gvdError))cbMPSPlayAccepted;
	    cbFuncs.rewind_accepted =
			(int32_t (*)(void *, void *, gvdError))cbMPSRewindAccepted;
	    cbFuncs.forward_accepted =
			(int32_t (*)(void *, void *, gvdError))cbMPSForwardAccepted;
	    cbFuncs.decode_stream_end =
			(int32_t (*)(void *, void *, gvdError))cbMPSDecodeStreamEnd;
	    cbFuncs.pause_done =
			(int32_t (*)(void *, void *, gvdError))cbMPSPauseDone;
	    cbFuncs.seek_done =
			(int32_t (*)(void *, void *, gvdError))cbMPSSeekDone;
		cbFuncs.rewind_done =
			(int32_t (*)(void *, void *, gvdError))cbMPSRewindDone;

	    /* コールバックパラメータの設定 */
	    for (idx=0; idx<(int32_t)_GVDInitParam.stream_num; idx++)
	    {
			gvdCbParams cbParams;
			{
				int32_t i;
				int32_t size = (int32_t)sizeof(gvdCbParams);
				for(i=0; i<size; i++)
				{
					*((uint8_t *)(&cbParams) + i) = 0x00;
				}
			}
			cbParams.decode_stream_end = (void *)&_MPSContext[idx];
			cbParams.commit_output_buffer = (void *)&_MPSContext[idx];
			cbParams.rewind_done = (void *)&_MPSContext[idx];
			_MPSContext[idx].id = idx;

			/* コンテキストの取得 */
			GVD_CreateContext(&_MPSContext[idx].gvdctx);

			/* コールバックの登録 */
			GVD_SetCallbackFunctions(_MPSContext[idx].gvdctx, &cbFuncs);
			GVD_SetCallbackParams(_MPSContext[idx].gvdctx, &cbParams);

			/* 出力先を指定 */
			if(_MPSConfig.output_select == GVD_OUTMOD_VRAM)
				GVD_SelectOutputModule(_MPSContext[idx].gvdctx, GVD_OUTMOD_VRAM);
			else if(_MPSConfig.output_select == GVD_OUTMOD_BMU)
				GVD_SelectOutputModule(_MPSContext[idx].gvdctx, GVD_OUTMOD_BMU);
			else
				GVD_SelectOutputModule(_MPSContext[idx].gvdctx, GVD_OUTMOD_NONE);

			uint32_t heapsize = _MPSConfig.memory_heap_size / _GVDInitParam.stream_num;
			heapsize &= 0xFFFFF800;
			GVD_SetOutputHeap(_MPSContext[idx].gvdctx,
							  (void *)((uint32_t)_MPSConfig.memory_heap_top+(heapsize*idx)),
							  heapsize);

			/* 位置情報バッファがあればGVDに設定 */
			if(_MPSContext[idx].info.position_count != 0 &&
			   _MPSContext[idx].info.position_buf != NULL)
				GVD_SetPositionBuffer(_MPSContext[idx].gvdctx,
									  _MPSContext[idx].info.position_buf,
									  _MPSContext[idx].info.position_count);
	    }
	}

	return AG903_ENONE;
}

/**
 * 対応するコンテキストにコンテナ情報を書き込みます.
 * @brief MP4ファイルのコンテナの解析
 * @param movie_id [in]ストリームID
 * @param filename [in]対象ファイル名
 * @return エラーコード
 */
int32_t MPS_Parse(uint32_t movie_id, char *filename)
{
	volatile mpsAsyncFlag *f = &_MPSContext[movie_id].flag;
	uint32_t fnamelen;
	if(filename == NULL || movie_id > 15)
	    return -AG903_EINVAL;
#ifdef MPS_USE_STDLIB
	char *sp = strrchr(filename, '.');
	char ext[5];
	strncpy(ext, &sp[1], 4);
	ext[4] = '\0';
	if(!strcmp(ext, "h264"))
#else
	fnamelen = 0;
	while(filename[fnamelen]!='\0'){
		fnamelen++;
	}
	uint32_t dot;
	for(dot=fnamelen; dot>0; dot--)
	{
	    if(filename[dot-1] == '.') break;
	}
	if(dot == 0 || fnamelen+1 > dot+4)
	    if(filename[dot-1] == '.' &&
	       filename[dot-0] == 'h' &&
	       filename[dot+1] == '2' &&
	       filename[dot+2] == '6' &&
	       filename[dot+3] == '4' &&
	       filename[dot+4] == '\0' )
#endif
        {
        	/* .h264 */
			int32_t i;
			for(i=0; i<MPS_MAX_FILENAME_LENGTH; i++)
			{
				_MPSContext[movie_id].filename[i] = filename[i];
				if(filename[i] =='\0')
					break;
			}
			mps_ParseMP4Container(movie_id); /* filesize check */
			f->bParsed = true;
			return AG903_ENONE;
        }
	if(!f->bParsed)
	{
	    _MPSContext[movie_id].fp = mps_fopen(filename, "rb");
	    if(_MPSContext[movie_id].fp == 0)
	    {
	    	return -AG903_ENOENT;
	    }

		int32_t rtcd;
		int32_t i;
		for(i=0; i<MPS_MAX_FILENAME_LENGTH; i++)
		{
			_MPSContext[movie_id].filename[i] = filename[i];
			if(filename[i] =='\0')
				break;
		}

		mps_invalidateCache(movie_id);

		rtcd = mps_ParseMP4Container(movie_id);
		mps_fclose(_MPSContext[movie_id].fp);
		_MPSContext[movie_id].fp = NULL;
		if(rtcd != 0)
		{
			MPS_ErrPrintf("ERROR: MP4 file parse error.\r\n");
			return rtcd;
		}

	    f->bParsed = true;
	}

	return AG903_ENONE;
}


/**
 * 指定したパスのMP4ファイルを開く<p>
 * ファイルの形式に対応できない場合は-AG903_EINVALを返します.<p>
 * この関数は処理が完了するまでブロックします.
 * @brief MP4ファイルを開く
 * @param movie_id [in]ストリームID
 * @param filename [in]対象ファイル（フルパス）
 * @return エラーコード
 */
int32_t MPS_Open(uint32_t movie_id, char *filename)
{
	gvdError err;
	volatile mpsAsyncFlag *f = &_MPSContext[movie_id].flag;

	if(filename == NULL || movie_id > 15)
	    return -AG903_EINVAL;

	if(f->bOpened)
	{
	    return -AG903_EBUSY;
	}
	else
	{
	    if(!f->bParsed)
	    {
			_MPSContext[movie_id].fp = mps_fopen(filename, "rb");
			if(_MPSContext[movie_id].fp != 0)
			{
				int32_t rtcd;
				int32_t i;
				for(i=0; i<MPS_MAX_FILENAME_LENGTH; i++)
				{
					_MPSContext[movie_id].filename[i] = filename[i];
					if(filename[i] =='\0')
						break;
				}
				rtcd = mps_ParseMP4Container(movie_id);
				mps_fclose(_MPSContext[movie_id].fp);
				_MPSContext[movie_id].fp = NULL;
				if(rtcd != 0)
				{
					MPS_ErrPrintf("ERROR: MP4 file parse error.\r\n");
					return rtcd;
				}
			}
			if(_MPSContext[movie_id].info.position_count != 0 &&
			   _MPSContext[movie_id].info.position_buf != NULL)
				GVD_SetPositionBuffer(_MPSContext[movie_id].gvdctx,
									  _MPSContext[movie_id].info.position_buf,
									  _MPSContext[movie_id].info.position_count);
			f->bParsed = true;
	    }

	    err = GVD_OpenFile(_MPSContext[movie_id].gvdctx, filename, &_MPSContext[movie_id]);
	    if(err != GVD_ERR_SUCCESS) {
			MPS_ErrPrintf("Error: GVD_OpenFile() : %d\n", err);
			return -AG903_ENOENT;
	    }
        int32_t ret = waitforflag(&f->bOpened, 0);
        if(0 > ret) {
            return ret;
        }
	    f->bClosed = false;
	}

	return AG903_ENONE;
}

/**
 * GVDに再生を要求する<p>
 * この関数は要求が受理されるまでブロックします.
 * @brief 再生を開始
 * @param movie_id [in]ストリームID
 * @return エラーコード
 */
int32_t MPS_Play(uint32_t movie_id)
{
	volatile mpsAsyncFlag *f = &_MPSContext[movie_id].flag;

	if(movie_id > 15 || movie_id > _GVDInitParam.stream_num)
	    return -AG903_EINVAL;

	if(f->bPlayAccepted || !f->bOpened)
	{
	    return -AG903_EBUSY;
	}
	else
	{
	    GVD_Play(_MPSContext[movie_id].gvdctx, &_MPSContext[movie_id]);
        int32_t ret = waitforflag(&f->bPlayAccepted, 0);
        if(0 > ret) {
            return ret;
        }
	    f->bPaused = false;
	    f->bRewindAccepted = false;
	    f->bForwardAccepted = false;
	}

	return AG903_ENONE;
}

/**
 * GVDに一時停止を要求する<p>
 * この関数は要求した処理が完了するまでブロックします.
 * @brief 一時停止
 * @param movie_id [in]ストリームID
 * @return エラーコード
 */
int32_t MPS_Pause(uint32_t movie_id)
{
	volatile mpsAsyncFlag *f = &_MPSContext[movie_id].flag;

	if(movie_id > 15 || movie_id > _GVDInitParam.stream_num)
	    return -AG903_EINVAL;

	if(f->bPaused || !f->bOpened)
	{
	    return -AG903_EBUSY;
	}
	else
	{
	    GVD_Pause(_MPSContext[movie_id].gvdctx, &_MPSContext[movie_id]);
        int32_t ret = waitforflag(&f->bPaused, 0);
        if(0 > ret) {
            return ret;
        }
	    f->bPlayAccepted = false;
	    f->bRewindAccepted = false;
	    f->bForwardAccepted = false;
	}

	return AG903_ENONE;
}

/**
 * GVDにシークを要求する<p>
 * シーク種別はフレーム または 時刻[ms]が選択可能<p>
 * この関数は要求した処理が完了するまでブロックします.
 * @brief シーク（頭出し）
 * @param movie_id [in]ストリームID
 * @param pos [in]シーク先（modeに依る）
 * @param mode [in]シーク先の指定種別
 * @return エラーコード
 * @see mpsSeekMode
 */
int32_t MPS_Seek(uint32_t movie_id, uint32_t pos, mpsSeekMode mode)
{
	volatile mpsAsyncFlag *f = &_MPSContext[movie_id].flag;

	if(movie_id > 15 || movie_id > _GVDInitParam.stream_num)
	    return -AG903_EINVAL;

	if(!f->bPaused || !f->bOpened)
	{
	    return -AG903_EBUSY;
	}
	else
	{
		bool bImme;
		gvdSeekType type;
		uint32_t offset, target, idr;
		gvdSeekParam seekparam;

		switch(mode)
		{
			case MPS_SEEK_INDEX:
			type = GVD_SEEK_BUFINDEX_ABSOLUTE;
			target = pos;
			bImme = false;
			break;

			case MPS_SEEK_RELATIVE_INDEX:
			type = GVD_SEEK_BUFINDEX_RELATIVE;
			target = pos;
			bImme = false;
			break;

			case MPS_SEEK_FRAME:
			type = GVD_SEEK_IMMEDIATE;
			offset = MPS_GetSeekPositionFromFrameID(movie_id, pos, &idr);
			target = idr;
			bImme = true;
			break;

			case MPS_SEEK_TIME:
			type = GVD_SEEK_IMMEDIATE;
			offset = MPS_GetSeekPositionFromTime(movie_id, pos, &idr);
			target = idr;
			bImme = true;
			break;

			default:
			return -AG903_EINVAL;
		}

		if(bImme)
		{
		    if(offset == 0)
		    {
				/* H264 ES:Seek非対応 */
				return -AG903_ENOTSUP;
		    }
		    else
		    {
				/* MP4:オフセットはMPS内で全て算出されている */
				seekparam.type            = type;
				seekparam.index_offset    = 0;
				seekparam.immediate_frame = target;
				seekparam.immediate_byte  = offset;
		    }
		}
		else
		{
			seekparam.type            = type;
			seekparam.index_offset    = target;
			seekparam.immediate_frame = 0;
			seekparam.immediate_byte  = 0;
		}

		f->bSought = false;
		GVD_Seek(_MPSContext[movie_id].gvdctx, &seekparam, &_MPSContext[movie_id]);
        int32_t ret = waitforflag(&f->bSought, 0);
        if(0 > ret) {
            return ret;
        }
	}

	return AG903_ENONE;
}

/**
 * GVDに早送りを要求する<p>
 * この関数は要求が受理されるまでブロックします.
 * @brief 早送りを開始
 * @param movie_id [in]ストリームID
 * @param speed [in]速度
 * @return エラーコード
 */
int32_t MPS_Forward(uint32_t movie_id, uint32_t speed)
{
	volatile mpsAsyncFlag *f = &_MPSContext[movie_id].flag;

	if(movie_id > 15 || movie_id > _GVDInitParam.stream_num)
	    return -AG903_EINVAL;

	if(!f->bOpened)
	{
	    return -AG903_EBUSY;
	}
	else
	{
	    GVD_Forward(_MPSContext[movie_id].gvdctx, speed, &_MPSContext[movie_id]);
        int32_t ret = waitforflag(&f->bForwardAccepted, 0);
        if(0 > ret) {
            return ret;
        }
	    f->bPaused = false;
	    f->bPlayAccepted = false;
	    f->bRewindAccepted = false;
	}

	return AG903_ENONE;
}

/**
 * GVDに早戻しを要求する<p>
 * この関数は要求が受理されるまでブロックします.
 * @brief 早戻しを開始
 * @param movie_id [in]ストリームID
 * @param speed [in]速度
 * @return エラーコード
 */
int32_t MPS_Rewind(uint32_t movie_id, uint32_t speed)
{
	volatile mpsAsyncFlag *f = &_MPSContext[movie_id].flag;

	if(movie_id > 15 || movie_id > _GVDInitParam.stream_num || speed == 0)
	    return -AG903_EINVAL;

	if(f->bRewindAccepted || !f->bOpened)
	{
	    return -AG903_EBUSY;
	}
	else
	{
	    GVD_Rewind(_MPSContext[movie_id].gvdctx, speed, (void *)&_MPSContext[movie_id]);
        int32_t ret = waitforflag(&f->bRewindAccepted, 0);
        if(0 > ret) {
            return ret;
        }
	    f->bPaused = false;
	    f->bPlayAccepted = false;
	    f->bForwardAccepted = false;
	}

	return AG903_ENONE;
}

/**
 * 指定したIDのファイルを閉じる<p>
 * この関数は処理が完了するまでブロックします.
 * @brief MP4ファイルを閉じる
 * @param movie_id [in]ストリームID
 * @return エラーコード
 */
int32_t MPS_Close(uint32_t movie_id)
{
	volatile mpsAsyncFlag *f = &_MPSContext[movie_id].flag;

	if(movie_id > 15 || movie_id > _GVDInitParam.stream_num)
	    return -AG903_EINVAL;

	if(!f->bPaused)
	{
	    return -AG903_EBUSY;
	}
	else
	{
		if(f->bParsed)
		{
			mpsMovieInfo *info = &_MPSContext[movie_id].info;
			if(info->keyframe != NULL)
			{
				MPS_Free(info->keyframe);
				info->keyframe = NULL;
			}
			if(info->position_buf != NULL)
			{
				MPS_Free(info->position_buf);
				info->position_buf = NULL;
			}

			mpsAVCHeader *AVCHeader = MPS_getAVCHeader(movie_id);
			if(AVCHeader->data != NULL)
			{
				MPS_Free(AVCHeader->data);
				AVCHeader->data = NULL;
			}

		    f->bParsed = false;
		}
		if(f->bOpened)
		{
		    GVD_CloseFile(_MPSContext[movie_id].gvdctx, &_MPSContext[movie_id]);
            int32_t ret = waitforflag(&f->bClosed, MPS_API_TIMEOUT);
            if(0 > ret) {
                return ret;
            }
		    f->bPaused = false;
		    f->bOpened = false;
		}
	}

	return AG903_ENONE;
}

/**
 * MPSの終了処理を行う<p>
 * この関数は処理が完了するまでブロックします.
 * @brief MPSの終了
 * @return エラーコード
 */
int32_t MPS_Finalize(void)
{
	int32_t i, err;

	GVD_GetFatalError(&fatalinfo);

	for(i=0; i<(int32_t)_GVDInitParam.stream_num; i++)
	{
		if(_MPSContext[i].flag.bOpened)
		{
			if(fatalinfo.cause == 0)
			{
					return -AG903_EBUSY;
			}
			else
			{
				if(_MPSContext[i].fp != NULL) {
					mps_fclose(_MPSContext[i].fp);
					_MPSContext[i].fp = NULL;
				}

				GVD_CloseFile(_MPSContext[i].gvdctx, NULL);
			}

			_MPSContext[i].flag.bOpened = 0;
		}
	}

	for(i=0; i<(int32_t)_GVDInitParam.stream_num; i++)
	{
	    err = GVD_DestroyContext(_MPSContext[i].gvdctx);
		if(fatalinfo.cause == 0)
		{
			if(err) { return err; }
		}
	}

	for(i=0; i<(int32_t)_GVDInitParam.stream_num; i++)
	{
		mpsMovieInfo *info = &_MPSContext[i].info;
	    if(info->keyframe != NULL)
	    {
			MPS_Free(info->keyframe);
			info->keyframe = NULL;
		}
	    if(info->position_buf != NULL)
	    {
			MPS_Free(info->position_buf);
			info->position_buf = NULL;
		}

	    mpsAVCHeader *AVCHeader = MPS_getAVCHeader(i);
	    if(AVCHeader->data != NULL)
	    {
			MPS_Free(AVCHeader->data);
			AVCHeader->data = NULL;
	    }
	}

	err = GVD_Finalize();

	return AG903_ENONE;
}

/**
 * 出力バッファをセットする
 * @brief 出力バッファのセット
 * @param movie_id [in]ストリームID
 * @param bufinfo [in]バッファ情報
 * @return エラーコード
 * @return -AG903_EOVERFLOW バッファ上限数を超えてセットしようとした
 * @return -AG903_ENOTSUP BMU出力なのでコールできない
 * @return -AG903_EINVAL パラメータが不正
 */
int32_t MPS_PutOutputBuffer(int32_t movie_id, mpsOutBufInfo *bufinfo)
{
	int32_t err;
	if(bufinfo == NULL)
	    return -AG903_EINVAL;
	err = GVD_PutOutputBuffer(_MPSContext[movie_id].gvdctx,
							  (gvdOutBufInfo *)bufinfo,
							  &_MPSContext[movie_id]);
	switch(err)
	{
	case GVD_ERR_INVALID_STATE:
	    return -AG903_EOVERFLOW;
	case GVD_ERR_INVALID_CALL:
	    return -AG903_ENOTSUP;
	case GVD_ERR_BAD_PARAMETER:
	    return -AG903_EINVAL;
	}
	return AG903_ENONE;
}

/**
 * 非同期処理を1回コールする<p>
 * この非同期処理は必ず1つのタスクコンテキストからのみ呼び出すこと.
 *
 * @brief 非同期処理
 * @return エラーコード
 */
int32_t MPS_Async(void)
{
	gvdError err;
	err = GVD_Execute();
	return err;
}


/**
 * GVD接続処理完了コールバック関数<p>
 * MPS内部で非同期関数の完了待ちに使用
 * @brief [コールバック] 接続完了
 * @return エラーコード
 */
int32_t cbMPSConnectDone(void *param_gvd, void *param_user, gvdError error)
{
	((void)(param_gvd));
	((void)(error));

	mpsContext *ctx = (mpsContext *)param_user;
	volatile mpsAsyncFlag *f = &ctx->flag;
	f->bDecDone= false;

	f->bPaused = true;
	f->bOpened = true;
	return 0;
}

/**
 * GVD切断処理完了コールバック関数<p>
 * MPS内部で非同期関数の完了待ちに使用
 * @brief [コールバック] 切断完了
 * @return エラーコード
 */
int32_t cbMPSDisConnectDone(void *param_gvd, void *param_user, gvdError error)
{
	((void)(param_gvd));
	((void)(error));

	mpsContext *ctx = (mpsContext *)param_user;
	volatile mpsAsyncFlag *f = &ctx->flag;
	f->bDecDone= false;

	f->bClosed = true;
	return 0;
}

/**
 * GVD再生受付コールバック関数<p>
 * MPS内部で非同期関数の完了待ちに使用
 * @brief [コールバック] 再生受付
 * @return エラーコード
 */
int32_t cbMPSPlayAccepted(void *param_gvd, void *param_user, gvdError error)
{
	((void)(param_gvd));
	((void)(error));

	mpsContext *ctx = (mpsContext *)param_user;
	volatile mpsAsyncFlag *f = &ctx->flag;
	f->bDecDone= false;
	f->bRewindDone = false;

	f->bPlayAccepted = true;
	return 0;
}

/**
 * GVD早戻し受付コールバック関数<p>
 * MPS内部で非同期関数の完了待ちに使用
 * @brief [コールバック] 早戻し受付
 * @return エラーコード
 */
int32_t cbMPSRewindAccepted(void *param_gvd, void *param_user, gvdError error)
{
	((void)(param_gvd));
	((void)(error));

	mpsContext *ctx = (mpsContext *)param_user;
	volatile mpsAsyncFlag *f = &ctx->flag;
	f->bDecDone= false;

	f->bRewindAccepted = true;
	return 0;
}

/**
 * GVD接続早送り受付コールバック関数<p>
 * MPS内部で非同期関数の完了待ちに使用
 * @brief [コールバック] 早戻し受付
 * @return エラーコード
 */
int32_t cbMPSForwardAccepted(void *param_gvd, void *param_user, gvdError error)
{
	((void)(param_gvd));
	((void)(error));

	mpsContext *ctx = (mpsContext *)param_user;
	volatile mpsAsyncFlag *f = &ctx->flag;
	f->bDecDone= false;
	f->bRewindDone = false;

	f->bForwardAccepted = true;
	return 0;
}

/**
 * GVDデコード完了コールバック関数<p>
 * MPS内部で非同期関数の完了待ちに使用
 * @brief [コールバック] デコード完了
 * @return エラーコード
 */
int32_t cbMPSDecodeStreamEnd(void *param_gvd, void *param_user, gvdError error)
{
	((void)(param_gvd));
	((void)(error));

	mpsContext *ctx = (mpsContext *)param_user;
	volatile mpsAsyncFlag *f = &ctx->flag;
	f->bRewindAccepted = false;
	f->bPlayAccepted = false;
	f->bForwardAccepted = false;
	f->bSought = false;
	f->bPaused = true;

	f->bDecDone= true;
	return 0;
}

/**
 * GVD一時停止処理完了コールバック関数<p>
 * MPS内部で非同期関数の完了待ちに使用
 * @brief [コールバック] 一時停止完了
 * @return エラーコード
 */
int32_t cbMPSPauseDone(void *param_gvd, void *param_user, gvdError error)
{
	((void)(param_gvd));
	((void)(error));

	mpsContext *ctx = (mpsContext *)param_user;
	volatile mpsAsyncFlag *f = &ctx->flag;
	f->bRewindAccepted = false;
	f->bPlayAccepted = false;
	f->bForwardAccepted = false;

	f->bPaused = true;
	return 0;
}

/**
 * GVDシーク処理完了コールバック関数<p>
 * MPS内部で非同期関数の完了待ちに使用
 * @brief [コールバック] シーク完了
 * @return エラーコード
 */
int32_t cbMPSSeekDone(void *param_gvd, void *param_user, gvdError error)
{
	((void)(param_gvd));
	((void)(error));

	mpsContext *ctx = (mpsContext *)param_user;
	volatile mpsAsyncFlag *f = &ctx->flag;
	f->bRewindDone = false;
	f->bDecDone= false;

	f->bSought = true;
	return 0;
}

/**
 * GVD早戻し処理完了コールバック関数<p>
 * MPS内部で非同期関数の完了待ちに使用
 * @brief [コールバック] シーク完了
 * @return エラーコード
 */
int32_t cbMPSRewindDone(void *param_gvd, void *param_user, gvdError error)
{
	((void)(param_gvd));
	((void)(error));

	mpsContext *ctx = (mpsContext *)param_user;
	volatile mpsAsyncFlag *f = &ctx->flag;
	f->bPaused = true;
	f->bRewindAccepted = false;

	f->bRewindDone = true;
	return 0;
}

