/**
 * @brief MP4コンテナ解析処理
 * @file mps_mp4perser.c
 * @author AXELL
 * @history 17_02_22  初版
 * @history 2017_10_26  Ver2.0
 * @history 2018_02_09  不具合対策パッチ
 * @history 2019_03_08  [SDK2.2] MPSでのIフレームが多い動画再生時及び再生位置処理の不具合を修正 (#1840)
 * @history 2019_03_08  [SDK2.2] MPSでのGVDサーチレンジの設定によってデコーダが終了しなくなる不具合等を修正 (#2449)
 * @history 2019_12_27  [SDK3.0] MPSで位置情報テーブル生成時にバッファオーバーランする不具合を修正 (#2609)
 * @description MP4ファイルに対応するための簡易パーサ
 * @note エラーコードはAG903_error.hを参照のこと.
 */

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

#include "mps/mps_mp4perser.h"
#include <ffsys.h>

/** @brief コンテナ解析用バッファサイズ[Byte] */
#define MPS_BUFFERSIZE 512

/** @brief IDR位置キャッシュテーブル */
static volatile mpsFramePos cache_idr_pos[16][MPS_CACHE_FRAME_SIZE];

static void prescan(uint32_t id);
static inline uint32_t get_bit(const uint8_t *base, uint32_t offset);
static int32_t get_box(uint32_t id, uint32_t pos, uint32_t offset, uint32_t max_siz, uint8_t *buf);
static int32_t golomb_s(const uint8_t *ptr, uint32_t *offset, int32_t *value);
static int32_t golomb_u(const uint8_t *ptr, uint32_t *offset, uint32_t *value);
static void parse(uint32_t id, uint8_t *buf);

/**
 * MPS_Parse, MPS_Openから呼び出され、MP4コンテナの解析を行う
 * @brief コンテナ解析メインルーチン
 * @param id [in]ストリームID
 * @return エラーコード
 */
int32_t mps_ParseMP4Container(uint32_t id)
{
	mpsInfoPos *pos = MPS_getInfoPos(id);
	uint8_t buf[MPS_BUFFERSIZE];
#ifdef MPS_USE_STDLIB
	memset(buf, 0, sizeof(buf));
#else
	{
	    int32_t i;
		int32_t size = (int32_t)sizeof(buf);
	    for(i=0; i<size; i++)
	    {
			*((uint8_t *)(buf) + i) = 0x00;
	    }
	}
#endif

	/* 各コンテナの位置を取得 */
	prescan(id);

	if(
	    pos->mdat == 0 ||
	    pos->mvhd == 0 ||
	    pos->mdhd == 0 ||
	    pos->avc1 == 0 ||
	    pos->avcC == 0 ||
	    pos->stts == 0 ||
	    pos->stsc == 0 ||
	    pos->stsz == 0 ||
	    pos->stco == 0 )
	{
	    return -AG903_ENOTSUP; /* 非対応のファイル */
	}

	/* 動画ストリーム情報を取得 */
	parse(id, buf);
	return AG903_ENONE;
}

/**
 * @brief (内部関数) 各Boxの位置を取得
 * @param id [in]ストリームID
 */
static void prescan(uint32_t id)
{
	mpsContext *ctx = MPS_getContext(id);
	mpsInfoPos *pos = MPS_getInfoPos(id);
	uint32_t filesize = ctx->filesize;

	uint8_t buf[16];
	int32_t res;
	uint32_t offset = 0;
	uint32_t type, size, lsize_l, lsize_u;
	uint64_t lsize;

	bool fLastbox = false;
	bool fTrakFound = false;

	offset = 0;

	/* ファイルサイズ取得 */
	if(filesize == 0)
	{
		res = mps_fseek(ctx->fp, 0, SEEK_END);
		if(res != 0) return;
		filesize = ffs_ftell(ctx->fp);
	}

	while(1)
	{
	    lsize = 0;

		/* バッファ更新 */
		res = mps_fseek(ctx->fp, offset, SEEK_SET);
		if(res != 0) return;
		res = mps_fread(buf, 16, 1, ctx->fp);
		if(res == 0) break;

		/* コンテナ先頭 */
		size = buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3];
		type = buf[4]<<24 | buf[5]<<16 | buf[6]<<8 | buf[7];
		if(size == 1) {
			/* 32bitより大きいサイズの場合 */
			lsize_u = buf[8]<<24 | buf[9]<<16 | buf[10]<<8 | buf[11];
			lsize_l = buf[12]<<24 | buf[13]<<16 | buf[14]<<8 | buf[15];
			lsize = (uint64_t)lsize_u << (uint64_t)32 | lsize_l;
		}
		else if(size == 0) {
			/* 最後のコンテナ */
			lsize = filesize - offset;
			fLastbox = true;
		}

		/* TRAKを確認 */
		if(type == MPS_BOX_TYPE_TRAK)
		{
			if(fTrakFound) /* 先頭のTRAKのみが対象 */
				return;
			else
				fTrakFound = true;
		}

	    /* mpsInfoPos構造体にアドレスを格納 */
	    if(type == MPS_BOX_TYPE_MDAT)
			pos->mdat = (uint32_t)offset;
	    else if(type == MPS_BOX_TYPE_MVHD)
			pos->mvhd = (uint32_t)offset;
	    else if(type == MPS_BOX_TYPE_MDHD)
			pos->mdhd = (uint32_t)offset;
	    else if(type == MPS_BOX_TYPE_AVC1)
			pos->avc1 = (uint32_t)offset;
	    else if(type == MPS_BOX_TYPE_AVCC)
			pos->avcC = (uint32_t)offset;
	    else if(type == MPS_BOX_TYPE_STTS)
			pos->stts = (uint32_t)offset;
	    else if(type == MPS_BOX_TYPE_STSS)
			pos->stss = (uint32_t)offset;
	    else if(type == MPS_BOX_TYPE_STSC)
			pos->stsc = (uint32_t)offset;
	    else if(type == MPS_BOX_TYPE_STSZ)
			pos->stsz = (uint32_t)offset;
	    else if(type == MPS_BOX_TYPE_STCO)
			pos->stco = (uint32_t)offset;

	    /* 終了判定 */
	    if(fLastbox)
			break;
	    else
	    {
			/* 次のBOXまでのオフセット */
			switch(type)
			{
				case MPS_BOX_TYPE_MOOV:
				case MPS_BOX_TYPE_TRAK:
				case MPS_BOX_TYPE_MDIA:
				case MPS_BOX_TYPE_MINF:
				case MPS_BOX_TYPE_STBL:
					if(lsize > 0)
						offset += 16;
					else
						offset += 8;
					break;

				case MPS_BOX_TYPE_STSD:
					offset += 16;
					break;

				case MPS_BOX_TYPE_AVC1:
					offset += MPS_BOX_AVCC_OFFSET_FROM_AVC1;
					break;

				default:
					offset += size;
			}
	    }
	}

	return;
}

/**
 * @brief boxを読み込む
 * @param id [in]ストリームID
 * @param pos [in]BOX先頭ファイルオフセット
 * @param offset [in]読み出し開始オフセット
 * @param max_siz [in]読み出し制限サイズ
 * @param buf [out]読み出し先
 * @return AG903_ENONE 正常終了
 */
static int32_t get_box(uint32_t id, uint32_t pos, uint32_t offset, uint32_t max_siz, uint8_t *buf)
{
	mpsContext *ctx = MPS_getContext(id);
	uint8_t sizbuf[4];
	uint32_t siz;
	mps_fseek(ctx->fp, pos, SEEK_SET);
	mps_fread(&sizbuf, 4, 1, ctx->fp);
	siz = sizbuf[0]<<24 | sizbuf[1]<<16 | sizbuf[2]<<8 | sizbuf[3];
	if(siz > max_siz)
	    siz = max_siz;
	mps_fseek(ctx->fp, pos + offset, SEEK_SET);
	mps_fread(buf, siz, 1, ctx->fp);
    return AG903_ENONE;
}

/**
 * @brief 対象1バイトの指定ビットを読み込む
 * @param base [in] 対象アドレス
 * @param offset [in]指定ビット
 * @return 指定ビットの値
 */
static inline uint32_t get_bit(const uint8_t *base, uint32_t offset)
{
	return ((*(base + (offset >> 0x3))) >> (0x7 - (offset & 0x7))) & 0x1;
}

/**
 * @brief 指数ゴロム符号のデコード (符号あり)
 * @param ptr [in] 対象アドレス
 * @param offset [in,out] ptrからのオフセット[bit]
 * @param value [out] デコード値
 * @return AG903_ENONE 正常終了
 */
static int32_t golomb_s(const uint8_t *ptr, uint32_t *offset, int32_t *value)
{
	uint32_t cur_ofs = *offset;
	uint32_t buf;
	uint32_t zeros = 0;
	int32_t idx;

	/* count zero bits */
	while(0 == get_bit(ptr, cur_ofs++))
	{
	    zeros++;
	}

	buf = (1 << zeros);
	cur_ofs++;

	for(idx=zeros-1; idx>=0; idx--)
	{
	    buf |= get_bit(ptr, cur_ofs++) << idx;
	}

	buf = buf - 1;
	if(buf == 0) /* zero */
		*value = buf;
	else if(buf % 2 == 0) /* odd */
		*value = -(buf/2);
	else /* even */
		*value = buf/2;
	*offset = cur_ofs;

	return AG903_ENONE;
}

/**
 * @brief 指数ゴロム符号のデコード (符号なし)
 * @param ptr [in] 対象アドレス
 * @param offset [in,out] ptrからのビットオフセット
 * @param value [out] デコード値
 * @return AG903_ENONE 正常終了
 */
static int32_t golomb_u(const uint8_t *ptr, uint32_t *offset, uint32_t *value)
{
	uint32_t cur_ofs = *offset;
	uint32_t buf;
	uint32_t zeros = 0;
	int32_t idx;

	/* count zero bits */
	while(0 == get_bit(ptr, cur_ofs++))
	{
	    zeros++;
	}

	buf = (1 << zeros);

	for(idx=zeros-1; idx>=0; idx--)
	{
	    buf |= get_bit(ptr, cur_ofs++) << idx;
	}

	*value = (buf - 1);
	*offset = cur_ofs;

	return AG903_ENONE;
}


/**
 * @brief (内部関数) オープンに必要なコンテナ情報を取得
 * @param id [in]ストリームID
 * @param buf [in,out]ファイル用バッファ
 */
static void parse(uint32_t id, uint8_t *buf)
{
	mpsContext *ctx = MPS_getContext(id);
	int32_t idx;
	uint32_t siz;
	mpsMovieInfo *info = &ctx->info;
	mpsInfoPos *pos = MPS_getInfoPos(id);

	/* media data */
	get_box(id, pos->mdat, 0, 256, buf);
	info->stream_size = buf[0]<<24|buf[1]<<16|buf[2]<<8|buf[3];

	/* media header */
	get_box(id, pos->mdhd, 0, 256, buf);
	info->time_scale = buf[20]<<24|buf[21]<<16|buf[22]<<8|buf[23];
	info->duration = buf[24]<<24|buf[25]<<16|buf[26]<<8|buf[27];

	/* decoding time-to-sample */
	get_box(id, pos->stts, 0, 256, buf);
	siz = buf[12]<<24|buf[13]<<16|buf[14]<<8|buf[15]; /* entry count */
	if(siz != 1)
	{
		/* 可変フレームレートには非対応 */
	    MPS_ErrPrintf("WARNING:stts contains multiple entries.\r\n");
		info->total_frame = buf[16]<<24|buf[17]<<16|buf[18]<<8|buf[19];
		info->frame_length = buf[20]<<24|buf[21]<<16|buf[22]<<8|buf[23];
	}
	else
	{
		info->total_frame = buf[16]<<24|buf[17]<<16|buf[18]<<8|buf[19];
		info->frame_length = buf[20]<<24|buf[21]<<16|buf[22]<<8|buf[23];
	}

	/* AVC sample */
	get_box(id, pos->avc1, 0, 256, buf);
	info->width = buf[32]<<8|buf[33];
	info->height = buf[34]<<8|buf[35];

	/* AVC decoder configuration */
	{
	    uint32_t cur = 20;
		uint32_t offset = 0;
	    uint32_t uValue, tmp;
	    int32_t sValue;
	    get_box(id, pos->avcC, 0, 256, buf);
	    siz = buf[0]<<24|buf[1]<<16|buf[2]<<8|buf[3]; /* box size */

		/* SPS/PPSを保持 (Annex B format) */
		{
			mpsAVCHeader *AVCHeader = MPS_getAVCHeader(id);
			int32_t spssiz, ppssiz;
			uint8_t *ptr;
	        spssiz = (int32_t)(buf[14]<<8|buf[15]); /* +14-15h:size of sps */
			ppssiz = (int32_t)(buf[16+spssiz+1]<<8|buf[16+spssiz+2]); /* size of pps */
			AVCHeader->size = 4+spssiz+4+ppssiz; /* paramset + start code */
			AVCHeader->data = (uint8_t *)MPS_Malloc(AVCHeader->size); /* free at MPS_Close() */

			/* SPS NAL unitを作成 */
			ptr = AVCHeader->data;
			ptr[0] = ptr[1] = ptr[2] = 0x00;
			ptr[3] = 0x01;
#ifdef MPS_USE_STDLIB
			memcpy(&ptr[4], &buf[16], spssiz);
#else
			{
				int32_t i;
				for(i=0; i<spssiz; i++)
					*(&ptr[4+i]) = *(&buf[16+i]);
			}
#endif

			/* PPS NAL unitを作成 */
			ptr[4+spssiz+0] = ptr[4+spssiz+1] = ptr[4+spssiz+2] = 0x00;
			ptr[4+spssiz+3] = 0x01;
#ifdef MPS_USE_STDLIB
			memcpy(&ptr[4+spssiz+4], &buf[16+spssiz+3], ppssiz);
#else
			{
				int32_t i;
				for(i=0; i<ppssiz; i++)
					*(&ptr[4+spssiz+4+i]) = *(&buf[16+spssiz+3+i]);
			}
#endif
		}

		info->profile = buf[9];
	    info->level = buf[11];

	    golomb_u(&buf[cur], &offset, &uValue); /* seq_parameter_set_id */
	    golomb_u(&buf[cur], &offset, &uValue); /* log2_max_frame_num_minus4 */
	    golomb_u(&buf[cur], &offset, &uValue); /* pic_order_cnt_type */
	    if(uValue == 0)
	    {
			golomb_u(&buf[cur], &offset, &uValue); /* log2_max_pic_order_cnt_lsb_minus4 */
	    }
	    else if(uValue == 1)
	    {
			offset += 1;
			golomb_s(&buf[cur], &offset, &sValue); /* offset_for_non_ref_pic */
			golomb_s(&buf[cur], &offset, &sValue); /* offset_for_top_to_bottom_field */
			golomb_u(&buf[cur], &offset, &uValue); /* num_ref_frames_in_pic_order_cnt_cycle */
			tmp = uValue;
			for(idx=0; idx<(int32_t)tmp; idx++)
			{
				golomb_s(&buf[cur], &offset, &sValue); /* offset_for_ref_frame[i] */
			}
	    }
	    golomb_u(&buf[cur], &offset, &uValue); /* num_ref_frames */
	    info->max_ref_frames = uValue;
	}

	if(pos->stss != 0)
	{
		get_box(id, pos->stss, 0, 256, buf);
		siz = buf[12]<<24|buf[13]<<16|buf[14]<<8|buf[15];
		info->keyframe_count = siz;
		info->keyframe = (uint32_t *)MPS_Malloc(siz*4); /* MPS_Close()で解放 */

		if(info->keyframe != NULL)
	    {
#ifdef MPS_USE_STDLIB
			memset(info->keyframe, 0, siz*4);
#else
			int32_t i;
			for(i=0; i<(int32_t)(siz*4); i++)
			{
				*((uint8_t *)(info->keyframe) + i) = 0x00;
			}
#endif
	    }

		uint32_t cnt = 0;
		for(idx=0; idx<(int32_t)siz; idx++)
		{
			if(idx == 60)
			{
				cnt++;
				idx = 0;
				siz -= 60;
				get_box(id, pos->stss, 240*cnt, 256, buf);
			}
			info->keyframe[idx+(cnt*60)] = buf[16+(idx*4)+0]<<24 |
										   buf[16+(idx*4)+1]<<16 |
										   buf[16+(idx*4)+2]<<8  |
										   buf[16+(idx*4)+3];
		}
	}
	else /* stss == NULL */
	{
		info->keyframe_count = info->total_frame;
		info->keyframe = (uint32_t *)MPS_Malloc(info->keyframe_count*4); /* MPS_Close()で解放 */

		if(info->keyframe != NULL)
	    {
#ifdef MPS_USE_STDLIB
			memset(info->keyframe, 0, siz*4);
#else
			int32_t i;
			for(i=0; i<(int32_t)(siz*4); i++)
			{
				*((uint8_t *)(info->keyframe) + i) = 0x00;
			}
#endif
	    }

		for(idx=0; idx<(int32_t)info->keyframe_count; idx++)
		{
			info->keyframe[idx] = idx+1;
		}
	}

/*
  MPS_ErrPrintf("Profile       : %u\n", info->profile);
  MPS_ErrPrintf("Level         : %u.%u\n", info->level/10, info->level%10);
  MPS_ErrPrintf("Total frame   : %u\n", info->total_frame);
  MPS_ErrPrintf("Height        : %u\n", info->height);
  MPS_ErrPrintf("Width         : %u\n", info->width);
  MPS_ErrPrintf("Time scale    : %u\n", info->time_scale);
  MPS_ErrPrintf("Duration      : %u\n", info->duration);
  MPS_ErrPrintf("Frame length  : %u\n", info->frame_length);
  MPS_ErrPrintf("Max reframes  : %u\n", info->max_ref_frames);
  MPS_ErrPrintf("Keyframe table: 0x%08X\n", info->keyframe);
*/
	return;
}

/**
 * @brief (内部関数) IDRキャッシュの全エントリを無効にする
 * @param id [in]ストリームID
 * @retuen none
 */
void mps_invalidateCache(uint32_t id)
{
	int32_t idx;
	for(idx=0; idx<MPS_CACHE_FRAME_SIZE; idx++)
		cache_idr_pos[id][idx].pos = 0;
}

/**
 * 前方の最も近いIDRフレーム番号と、ストリーム位置を取得します<p>
 * Seek操作時
 * MPS_GetSeekPositionFromTime, MPS_GetSeekPositionFromFrameID
 * からコールされることを想定<p>
 * 前後MPS_CACHE_FRAME_DISTANCEフレーム分のIDRをキャッシュする
 * @brief 前方の最も近いIDRフレーム番号と、ストリーム位置を取得
 * @param id [in]ストリームID
 * @param frame [in]フレーム番号
 * @param idr_frame [out]IDRフレーム番号
 * @param idr_pos [out]idr_frameのストリーム位置
 * @note コール前にMPS_ParseかMPS_OpenでMP4ファイルの解析が必要です
 */
int32_t MPS_GetNearIDRPos(uint32_t id, uint32_t frame, uint32_t *idr_frame, uint32_t *idr_pos)
{
	mpsContext *ctx = MPS_getContext(id);
	mpsMovieInfo *info = &ctx->info;

	/* Error: MP4コンテナ位置未取得 */
	/* Error: keyframeリスト未取得 */
	if(!ctx->flag.bParsed || info->keyframe == NULL)
	{
		return -AG903_ENOENT;
	}

	/* Error: フレームIDが範囲外 */
	if(frame == 0 || frame > info->total_frame)
	{
	    *idr_frame = 0;
	    *idr_pos = 0;
	    return -AG903_EINVAL;
	}

	/* IDRフレームを検索 */
	if(info->keyframe_count == info->total_frame)
	{
		/* 全てのフレームがIDR */
	    *idr_frame = frame;
	}
	else
	{
	    int32_t idx;

	    /* 近いIDRのフレームIDを取得 */
	    for(idx=0; idx<(int32_t)info->keyframe_count; idx++)
	    {
			if(*(info->keyframe+idx) <= frame)
				*idr_frame = *(info->keyframe+idx);
			else
				break;
	    }
	}

	/* IDRのストリーム位置を検索 */
	int32_t idx;
	volatile mpsFramePos *fpos = NULL;

	/* キャッシュから探索 */
	for(idx=0; idx<MPS_CACHE_FRAME_SIZE; idx++)
	{
	    if(cache_idr_pos[id][idx].pos != 0) /* valid? */
	    {
			if(*idr_frame == cache_idr_pos[id][idx].frame_id)
			{
				fpos = &cache_idr_pos[id][idx]; /* hit */
				break;
			}
	    }
	}

	if(fpos == NULL)
	{
	    uint8_t  *buf;
	    uint8_t  btmp[4];
	    int32_t  ofs;         /* offset to chunk top [frame] */
	    uint32_t samples;     /* sample count of included current chunk */
	    uint32_t entry;       /* current stsc entry */
	    uint32_t fchunk_prev; /* previous first chunk in stsc box */
	    uint32_t fchunk;      /* first chunk in stsc box */
	    uint32_t pos;
	    int32_t  idx;
	    mpsInfoPos *infopos = MPS_getInfoPos(id);

		/* 対象フレームのチャンク番号を算出 */
		uint32_t siz;
		get_box(id, infopos->stsc, 0, 4, btmp);
		siz = btmp[0]<<24|btmp[1]<<16|btmp[2]<<8|btmp[3];
		buf = (uint8_t *)MPS_Malloc(siz);
		get_box(id, infopos->stsc, 0, siz, buf);
		siz = buf[12]<<24|buf[13]<<16|buf[14]<<8|buf[15]; /* entry count */

		samples = 0;
		ofs = *idr_frame;

		uint32_t chunk = 0;
		fchunk_prev = 1;
		fchunk = 1;
		for(entry=1; entry<=siz; entry++)
		{
			/* update first chunk */
			fchunk = buf[16 + 12*(entry-1) +0 +0] <<24 |
					 buf[16 + 12*(entry-1) +0 +1] <<16 |
					 buf[16 + 12*(entry-1) +0 +2] << 8 |
					 buf[16 + 12*(entry-1) +0 +3] << 0 ;

			ofs -= samples*(fchunk-fchunk_prev);

			if(ofs < 0) /* found prev chunk */
			{
				chunk = fchunk - (1 + ofs/(int32_t)(samples*(fchunk-fchunk_prev)));
				break;
			}

			/* update samples/chunk */
			samples = buf[16 + 12*(entry-1) +4 +0] <<24 |
					  buf[16 + 12*(entry-1) +4 +1] <<16 |
					  buf[16 + 12*(entry-1) +4 +2] << 8 |
					  buf[16 + 12*(entry-1) +4 +3] << 0 ;

			fchunk_prev = fchunk;

			if(ofs < (int32_t)samples) /* found */
			{
				chunk = fchunk;
				break;
			}
		}
		MPS_Free(buf);
		buf = NULL;

		if(chunk == 0) /* not found */
			return -AG903_ENOENT;

		/* 対象チャンクの先頭位置を算出 */
		get_box(id, infopos->stco, 16+(chunk-1)*4, 4, btmp);
		pos = btmp[0]<<24|btmp[1]<<16|btmp[2]<<8|btmp[3];


		/* 対象フレームのストリーム位置を算出 */
		if(ofs-1 < 1){ siz = 4; }
		else { siz = (ofs-1) * 4; }
		buf = (uint8_t *)MPS_Malloc(siz);
		get_box(id, infopos->stsz, 20+(*idr_frame-ofs)*4, siz, buf);
		for(idx=0; idx<ofs-1; idx++)
		{
			pos += buf[0 + (4*idx)] <<24 |
				   buf[1 + (4*idx)] <<16 |
				   buf[2 + (4*idx)] << 8 |
				   buf[3 + (4*idx)] << 0 ;
		}
		*idr_pos = pos;
		MPS_Free(buf);
		buf = NULL;


		/* 対象と周辺IDRフレームをキャッシュ */
		int32_t keypos;
		for(idx=0; idx<(int32_t)info->keyframe_count; idx++)
		{
			if(*idr_frame == *(info->keyframe+idx))
				break;
		}
		keypos = idx;

		get_box(id, infopos->stsz, 0, 4, btmp);
		siz = btmp[0]<<24|btmp[1]<<16|btmp[2]<<8|btmp[3];
		buf = (uint8_t *)MPS_Malloc(siz);
		get_box(id, infopos->stsz, 20, siz, buf);
		int32_t frame_distance[MPS_CACHE_FRAME_SIZE] = {0};
		uint32_t fid;

		/* 対象IDRと周辺IDRの相対ストリーム位置を算出 */
		for(idx=0; idx<=MPS_CACHE_FRAME_DISTANCE; idx++)
		{
			if(idx == 0)
				frame_distance[idx] = 0;
			else
			{
                if((int32_t)info->keyframe_count > (keypos + idx))
				{
					fid = *(info->keyframe + (keypos + idx));
					int32_t j;

                    for(j=*idr_frame; j<(int32_t)fid; j++)
					{
						frame_distance[idx] += 	buf[0 + (4*(j-1))] <<24 |
												buf[1 + (4*(j-1))] <<16 |
												buf[2 + (4*(j-1))] << 8 |
												buf[3 + (4*(j-1))] << 0 ;
					}
				}
				if(0 <= (keypos - idx))
				{
					int32_t j;
					fid = *(info->keyframe + (keypos - idx));

                    for(j=fid; j<(int32_t)*idr_frame; j++)
					{
						frame_distance[MPS_CACHE_FRAME_DISTANCE+idx] +=
							buf[0 + (4*(j-1))] <<24 |
							buf[1 + (4*(j-1))] <<16 |
							buf[2 + (4*(j-1))] << 8 |
							buf[3 + (4*(j-1))] << 0 ;
					}
				}
			}
		}
		MPS_Free(buf);
		buf = NULL;

		/* キャッシュ */
		int32_t dist;
		for(dist=0; dist<=MPS_CACHE_FRAME_DISTANCE; dist++)
		{
			if(dist == 0) /* distance 0 */
			{
				cache_idr_pos[id][dist].frame_id = *idr_frame;
				cache_idr_pos[id][dist].pos = *idr_pos;
			}
			else
			{
				if(*idr_frame + dist <= info->total_frame)
				{
					cache_idr_pos[id][dist].frame_id =
						*(info->keyframe + (keypos + dist));
					cache_idr_pos[id][dist].pos =
						cache_idr_pos[id][0].pos + frame_distance[dist];
				}
				if((int32_t)*idr_frame > dist)
				{
					cache_idr_pos[id][MPS_CACHE_FRAME_DISTANCE+dist].frame_id =
						*(info->keyframe + (keypos - dist));
					cache_idr_pos[id][MPS_CACHE_FRAME_DISTANCE+dist].pos =
						cache_idr_pos[id][0].pos - frame_distance[MPS_CACHE_FRAME_DISTANCE+dist];
				}
			}
		}
	}
	else /* cache hit */
	{
	    *idr_pos = fpos->pos;
	}

	return AG903_ENONE;
}

/**
 * 全IDRフレーム番号と、対応するストリーム位置を取得し<p>
 * GVDで使用可能な位置情報バッファを作成する
 * @brief 位置情報バッファを作成する
 * @param id [in]ストリームID
 * @param position_buf [out]位置情報バッファポインタ
 * @param position_count [out]位置情報バッファ要素数(IDRフレーム数)
 * @note コール前にMPS_ParseでMP4ファイルの解析が必要です
 */
int32_t MPS_GetPositionBuffer(uint32_t id, gvdPositionBuffer **position_buf, uint32_t *position_count)
{
	mpsContext *ctx = MPS_getContext(id);
	mpsMovieInfo *info = &ctx->info;
	mpsInfoPos *infopos = MPS_getInfoPos(id);
	uint8_t  *buf;
	uint8_t btmp[4];
	int32_t idx;
	uint32_t siz;
	bool fNeedClose;

	gvdPositionBuffer cur_pos = {0, 0}; /* current position */
	uint32_t *idr_list = NULL;

	/* Error: MP4コンテナ位置未取得 */
	if(!ctx->flag.bParsed)
	{
		return -AG903_ENOENT;
	}

	if(ctx->fp == NULL)
	{
		fNeedClose = true;
		ctx->fp = mps_fopen(ctx->filename, "rb");
		if(ctx->fp == NULL)
			return -AG903_ENOENT;
	}
	else { fNeedClose = false; }

	/* IDRフレームを検索 */
	if(info->keyframe == NULL)
	{
		/* Error: IDRフレームリスト未取得 */
		return -AG903_ENOENT;
	}
	else
	{
		/* 解析済みのkeyframeから読み込み */
		*position_count = info->keyframe_count;
		idr_list = info->keyframe;
	}

	/* 位置情報バッファ領域を確保 */
	*position_buf = (gvdPositionBuffer *)MPS_Malloc(info->keyframe_count*sizeof(gvdPositionBuffer));
	if(*position_buf == NULL) {
		return -AG903_ENOMEM;
	}


	/* 対象フレームのストリーム位置を算出 */
	siz = info->total_frame*4;
	buf = (uint8_t *)MPS_Malloc(siz);
	if(buf == NULL) {
		MPS_Free(*position_buf);
		*position_buf = NULL;
		return -AG903_ENOMEM;
	}
	get_box(id, infopos->stsz, 20, siz, buf);

	get_box(id, infopos->stco, 16, 4, btmp);
	uint32_t pos = btmp[0]<<24|btmp[1]<<16|btmp[2]<<8|btmp[3];

	uint32_t idr_cnt = 0;
	idx = 0;
	do {
		if(idx+1 == (int32_t)idr_list[idr_cnt])
		{
			cur_pos.frame_id = idx+1;
			cur_pos.stream_pos = pos;
			*(*position_buf+idr_cnt) = cur_pos;
			idr_cnt++;
			if(idr_cnt >= info->keyframe_count) {
				break;
			}
		}

		pos += buf[0 + (4*idx)] <<24 |
			   buf[1 + (4*idx)] <<16 |
			   buf[2 + (4*idx)] << 8 |
			   buf[3 + (4*idx)] << 0 ;

		idx++;
    }while(idx < (int32_t)info->total_frame);

	*position_count = idr_cnt;

	MPS_Free(buf);
	buf = NULL;

	if(fNeedClose)
	{
		mps_fclose(ctx->fp);
		ctx->fp = NULL;
	}

	return AG903_ENONE;
}
