/*
 * @history		2019_12_27  [SDK3.0] タスクをTA_FPU属性付で起動していない不具合を修正 (uC3版) (#2698)
 * @history		2019_12_27  [SDK3.0] BMUバッファのサイズとアラインが4096の倍数になるよう修正 (#2188)
 * @history     2020_07_22  [SDK3.1] GVDが占有するメモリ領域を指定するAPIを追加 (#2965)
 */
/*
 * This program was created by AXELL CORPORATION.
 * Copyright (C) 2017-2020 AXELL CORPORATION, all rights reserved.
 */

/* uC3 */
#include "kernel.h"
#include "ffsys.h"

/* Standards */
#include <stdint.h>

/* ライブラリ */
#include "bmu/bmumgr.h"
#include "dsp/dspmgr.h"
#include "int/intmgr.h"

/* MP4 */
#include "mps/mps_api.h"

/* アプリ固有定義 */
#include "com.h"
#include "sample_common.h"
#include "mps_trick.h"

/* タスク */

/* 非同期処理タスク
 * これ以外のタスクで余った時間にコールされる
 * デコーダの制御も行うため、コールされる頻度が落ちるとデコード性能に影響します */
extern void async_tsk(VP_INT);
static const T_CTSK ctsk_async = {
    TA_HLNG | TA_ACT | TA_FPU, (VP_INT)0, (FP)async_tsk, 9, 0x800, 0, "Async"
};

	/* ウィンドウ制御タスク
 * フレームバッファの管理とフレーム表示時間の制御 */
extern void trick_window_tsk(VP_INT);
static const T_CTSK ctsk_window = {
    TA_HLNG | TA_ACT | TA_FPU, (VP_INT)0, (FP)trick_window_tsk, 3, 0x400, 0, "Window"
};

/* 関数 */
static void VSyncHandler(void);
static void BufferReset(void);
static void WindowUpdate();
static void UpdateWindowAddr(int win_id, uintptr_t addr);
static void AppendBufferInfo(mpsContext *ctx, gvdOutBufInfo buf, gvdTimeStamp ts);

static int32_t cbMPSDecodeDone(void *param_gvd, void *param_user, gvdError error);
static int32_t cbMPSFatalerror(void *param_gvd, void *param_user, gvdError error);


/* 変数 */
extern gvdFatalErrorInfo	gFatal;		/* 致命的エラー保持用 */
extern int8_t	gFlagMPSDone;			/* 終了フラグ */
extern ER_ID	gSemID_BufSync;			/* フレームバッファ管理用 */

static bool		gFlagQuit;				/* 終了フラグ */
static ER_ID	gWindowTaskID;			/* ウィンドウ制御タスクID */
static ER_ID	gAsyncTaskID;			/* 非同期処理タスクID */
static ER_ID	gVSyncHdrID;			/* 垂直同期割り込みハンドラID */
static uint8_t	gCallCnt;				/* フレーム更新周期調整用 */
static void		*gWindowAttributeAddr;	/* ウィンドウアトリビュートアドレス保持用 */
static mpsOutBufInfo		gOutbufinfo[MPS_OUTBUF_COUNT];	/* 出力バッファ情報 */
static volatile uint32_t	gCurrentFrame;			/* 現在表示中のフレームID */

/* 表示ハンドラ */
static AG903_DSPMgrHandle *dsp_handle[2];

/* (バッファ管理)バッファ情報構造体 */
typedef struct _DecodedBufferInfo{
    mpsContext *ctx;
    gvdOutBufInfo buf;
    gvdTimeStamp ts;
}DecodedBufferInfo;

static DecodedBufferInfo gBufList[MPS_OUTBUF_COUNT]; /* 書き込み済みバッファ情報リスト */
static uint32_t gBufListNextAdd;	/* 書き込み済み出力バッファ管理用 */
static uint32_t gBufListCount;		/* 書き込み済みバッファの個数 */
static uint32_t gBufListCurrent;	/* 現在使用中のバッファ番号 */
static uint32_t gBufListNext;		/* 次回使用するバッファ番号 */
static uint32_t gBufListPrev;		/* 次回使用するバッファ番号 */


static int movie_init(void)
{
	int32_t err;

	mpsSetting mpssetting;
	mpsConfig mpsconfig;
	mpsContext *mpsctx;

	/* MPSの初期化 */
	mpssetting.max_stream = MPS_MAX_STREAM;
	mpssetting.stream_buffer_size = MPS_INBUF_SIZE;
	mpssetting.stream_buffer_num = MPS_INBUF_COUNT;
	mpssetting.func_decode_done_handler =
	    (int32_t (*)(void *, void *, gvdError))cbMPSDecodeDone;
	mpssetting.func_fatalerror_handler =
	    (int32_t (*)(void *, void *, gvdError))cbMPSFatalerror;

	err = MPS_Initialize(mpssetting);
	if(err)
	{
	    MPS_ErrPrintf("ERROR: MPS_Initialize() : %d\n", err);
	    return err;
	}

	ERRCHK(GVD_ERR_SUCCESS, GVD_SetFatalErrorAddress((void *)0xFE007000));
	ERRCHK(GVD_ERR_SUCCESS, GVD_SetOccupyMemoryAddress((void *)0x83FF0000));

	/* GVD用ワークメモリの確保 */
	mpsconfig.memory_work_size = MPS_GetRequiredWorkMemorySize();
	MPS_Printf("[Memory] GVD requested %u [Byte]\n", mpsconfig.memory_work_size);

	/* GVD用ワークメモリは32KB以上のアラインが必須 */
	mpsconfig.memory_work_top = MPS_Memalign(mpsconfig.memory_work_size, 32*1024);
	if(mpsconfig.memory_work_top == 0)
	{
	    MPS_ErrPrintf("ERROR: [GVD WorkBuffer] allocate faild.\r\n");
	    return -AG903_ENOMEM;
	}
	/* 出力バッファ数 */
	mpsconfig.num_out_buffer = MPS_OUTBUF_COUNT;

	/* GVD用ヒープメモリは2KB以上のアラインが必須 */
	mpsconfig.memory_heap_top = MPS_Memalign(MPS_HEAP_SIZE, 2048);
	if(mpsconfig.memory_heap_top == NULL)
	{
	    MPS_ErrPrintf("ERROR: Heap memory alloc failed.\r\n");
	    return -AG903_ENOMEM;
	}
	mpsconfig.memory_heap_size = MPS_HEAP_SIZE;

	/* MP4コンテナの解析
	 * 以降コンテナ情報が使える */
	err = MPS_Parse(0, STREAM_NAME);
	if (err != AG903_ENONE)
	{
		MPS_ErrPrintf("ERROR: MP4 Parse failed\n");
		return err;
	}

	mpsctx = MPS_getContext(0);
	MPS_GetPositionBuffer(0, &mpsctx->info.position_buf, &mpsctx->info.position_count);

	/* 出力バッファの確保 */
	{
		int32_t cnt;
	    uint32_t stream_stride;
		mpsctx = MPS_getContext(0);
		if(mpsctx->info.width == 0 && mpsctx->info.height == 0)
		{
			mpsctx->info.width = TRICK_STREAM_WIDTH;
			mpsctx->info.height = TRICK_STREAM_HEIGHT;
			mpsctx->info.frame_length = 1;
			mpsctx->info.time_scale = 30;
		}

		stream_stride = MPS_ALIGNn(64, mpsctx->info.width)
		              * MPS_ALIGNn(32, mpsctx->info.height)
		              * 2;

	    /* 出力バッファを確保（MPS_OUTBUF_COUNT[枚/ストリーム]） */
		mpsctx = MPS_getContext(0);
		for(cnt=0; cnt<MPS_OUTBUF_COUNT; cnt++)
		{
			gOutbufinfo[cnt].buffer = MPS_Memalign(stream_stride, 1024);
			gOutbufinfo[cnt].size_buffer = stream_stride;
			gOutbufinfo[cnt].height = mpsctx->info.height;
			gOutbufinfo[cnt].width = mpsctx->info.width;
			if(gOutbufinfo[cnt].buffer == NULL)
			{
				MPS_ErrPrintf("ERROR: [Outputbuf] allocate faild.\r\n");
				return -AG903_ENOMEM;
			}
		}

		mpsconfig.output_select = GVD_OUTMOD_VRAM;

		/* 初回表示フレームバッファの初期化 */
		uint32_t *p = (uint32_t *)gOutbufinfo[MPS_OUTBUF_COUNT-1].buffer;
		for(cnt=0; cnt<(int32_t)(stream_stride>>2); cnt++)
		{
			*(p + cnt) = 0x00000000;
		}

		/* 初回表示バッファ */
		{
			gvdTimeStamp ts = {0};
			BufferReset();
			AppendBufferInfo(mpsctx, gOutbufinfo[MPS_OUTBUF_COUNT-1], ts);
		}
	}

	/* GVDの初期化 */
	err = MPS_Start(mpsconfig);
	if(err)
	{
	    MPS_ErrPrintf("ERROR: MPS_Start() : %d\n", err);
	    return err;
	}

	/* 入出力バッファ数を設定 */
	mpsctx = MPS_getContext(0);
	ERRCHK(GVD_ERR_SUCCESS, GVD_SetMaxNumInputBuffers(mpsctx->gvdctx, MPS_INBUF_COUNT));
	ERRCHK(GVD_ERR_SUCCESS, GVD_SetMaxNumOutputBuffers(mpsctx->gvdctx, MPS_OUTBUF_COUNT));

	return 0;
}

/* コマンド説明 */
static void mps_cmd_help(void)
{
	MPS_Printf("[Command help]\n");
	MPS_Printf("  0 : Pause\n");
	MPS_Printf("  1 : Play\n");
	MPS_Printf("  2 : Seek(Index)\n");
	MPS_Printf("  3 : Seek(Relative Index)\n");
	MPS_Printf("  4 : Seek(Frame)\n");
	MPS_Printf("  5 : Seek(Time)\n");
	MPS_Printf("  6 : Forward\n");
	MPS_Printf("  7 : Rewind\n");
	MPS_Printf("  9 : Quit\n");
	MPS_Printf("\n");
}

static int32_t input_decimal(char *buf, uint32_t size)
{
	int32_t idx;
	char *str = NULL;
	char *chk = NULL;
	int32_t cmd;
	if(size < 3) return 0;

	str = MPS_Malloc(size-1);
	if(str == NULL) return -1;

	COM_GetChara_Wait(buf, size);
	for(idx=0; idx<(int32_t)size-1; idx++)
	{
		if(idx == (int32_t)size-2) str[idx] = '\0';
		else                       str[idx] = buf[idx];
	}
	cmd = (int32_t)(sys_strtoul(str, &chk, 10) & 0x7FFFFFFF);
	if(chk != &str[size-2])
	{
		for(idx=0; idx<(int32_t)size; idx++)
			buf[idx] = '\0';
		cmd = -1;
	}

	MPS_Free(str);
	return cmd;
}

static int movie_play(void)
{
	int cnt;
	int32_t err;

	gFlagMPSDone = false;

	/* 非同期処理開始 */
	gAsyncTaskID = acre_tsk((T_CTSK*)&ctsk_async);

	/* 表示更新処理開始 */
	gWindowTaskID = acre_tsk((T_CTSK*)&ctsk_window);

	/* 動画ストリームのオープン */
	/* ParseしていなければMPS_Open内でParseされる
	 * Parse済みであればコンテナ情報をそのまま使用する */
	err = MPS_Open(0, STREAM_NAME);
	if (err != AG903_ENONE)
	{
		MPS_ErrPrintf("ERROR: Movie file open failed\n");
		return err;
	}

	/* 出力バッファを供給 */
	for(cnt=0; cnt<MPS_OUTBUF_COUNT-1; cnt++)
	{
		err = MPS_PutOutputBuffer(0, &gOutbufinfo[cnt]);
		if(err)
			MPS_ErrPrintf("ERROR: MPS_PutOutputBuffer : %d\n", err);
	}

	/* コマンド処理 */
	{
		char inbuf[MPS_MAX_CHAR];
		int32_t cmd;

		gFlagQuit = false;
		mps_cmd_help();

		/* 非同期タスクは入力待ち時間中に処理される */
		while(!gFlagQuit)
		{
			int32_t num;
			MPS_Printf("CMD> ");
			cmd = input_decimal(inbuf, MPS_MAX_CMDCHAR);
			if(cmd < 0)
			{
				mps_cmd_help();
				continue;
			}

			switch(cmd)
			{
			case 0:
				MPS_Printf("Pause\n");
				MPS_Pause(0);
			    break;
			case 1:
				MPS_Printf("Play\n");
				MPS_Play(0);
			    break;
			case 2:
				MPS_Printf("Seek(Index)\n");
				do {
					MPS_Printf("Index No. (%u digits)> ", MPS_MAX_INDEXCHAR-2);
					num = input_decimal(inbuf, MPS_MAX_INDEXCHAR);
				}while(num < 0);
				MPS_Seek(0, num, MPS_SEEK_INDEX);
				break;
			case 3:
				MPS_Printf("Seek(Relative Index)\n");
				do {
					MPS_Printf("Relative index No. (%u digits)> ", MPS_MAX_INDEXCHAR-2);
					num = input_decimal(inbuf, MPS_MAX_INDEXCHAR);
				}while(num < 0);
				MPS_Seek(0, num, MPS_SEEK_RELATIVE_INDEX);
			    break;
			case 4:
				MPS_Printf("Seek(Frame)\n");
				do {
					MPS_Printf("Frame No. (%u digits)> ", MPS_MAX_FRAMECHAR-2);
					num = input_decimal(inbuf, MPS_MAX_FRAMECHAR);
				}while(num < 0);
				MPS_Seek(0, num, MPS_SEEK_FRAME);
			    break;
			case 5:
				MPS_Printf("Seek(Time)\n");
				do {
					MPS_Printf("Time in ms. (%u digits)> ", MPS_MAX_TIMECHAR-2);
					num = input_decimal(inbuf, MPS_MAX_TIMECHAR);
				}while(num < 0);
				MPS_Seek(0, num, MPS_SEEK_TIME);
			    break;
			case 6:
				MPS_Printf("Forward\n");
				do {
					MPS_Printf("Speed (%u digits)> ", MPS_MAX_SPEEDCHAR-2);
					num = input_decimal(inbuf, MPS_MAX_SPEEDCHAR);
				}while(num < 0);
				MPS_Forward(0, num);
			    break;
			case 7:
				MPS_Printf("Rewind\n");
				do {
					MPS_Printf("Speed (%u digits)> ", MPS_MAX_SPEEDCHAR-2);
					num = input_decimal(inbuf, MPS_MAX_SPEEDCHAR);
				}while(num < 0);
				MPS_Rewind(0, num);
			    break;
			case 9:
				MPS_Printf("Quit\n");
				MPS_Pause(0);
				gFlagQuit = true;
				break;
			default:
				mps_cmd_help();
			}
		}
	}

	/* 動画ストリームのクローズ */
    MPS_Close(0);

	/* 出力バッファの解放 */
	for(cnt=0; cnt<MPS_OUTBUF_COUNT; cnt++) {
		if(gOutbufinfo[cnt].buffer != NULL) {
			MPS_Free(gOutbufinfo[cnt].buffer);
			gOutbufinfo[cnt].buffer = NULL;
		}
	}

	MPS_Printf("Play done.\r\n");
	gFlagMPSDone = true; /* 再生処理終了を通知 */
	return 0;
}

/* TASK */
void mps_trick(void)
{
	/* 変数初期化 */
	{
		gWindowAttributeAddr	= NULL;
		gFlagQuit				= false;
		gWindowTaskID			= -1;
		gAsyncTaskID			= -1;
		gVSyncHdrID				= -1;
		gCallCnt				= 0;
		gCurrentFrame			= 0;
		gBufListNextAdd			= 0;
		gBufListCount			= 0;
		gBufListCurrent			= 0;
		gBufListNext			= 0;
		gBufListPrev			= 0;
		sys_memset(gOutbufinfo, 0, sizeof(gOutbufinfo));
		sys_memset(gBufList, 0, sizeof(gBufList));
		sys_memset(dsp_handle, 0, sizeof(dsp_handle));
	}

	mpsConfig *mpsconf = MPS_getConfig();

	MPS_Printf("\n"
			   "[MOVIE PLAYER SAMPLE : TRICKPLAY]\n"
			   "\n");
	MPS_Printf("Setting start >>>\n");

	/* File system */
	{
	    ER ercd;

	    ercd = mountp('A', 0, (ID)Fsif_GetDevid(FSIF_DEV_CF)); /* CF->'A' */
		if (ercd != E_OK) {
			MPS_ErrPrintf("ERROR: CF mount failed.\r\n");
			return;
		}
	}

	/* Semaphore */
	{
	    T_CSEM pk_csem = {
			.sematr = TA_TPRI,
			.isemcnt = 1,
			.maxsem = 1,
			.name = "sem_window",
	    };

		gSemID_BufSync = acre_sem(&pk_csem);

		if(gSemID_BufSync < 0)
	    {
			MPS_ErrPrintf("ERROR: Semaphore create failed.\r\n");
			return;
	    }
	}

	/* IRQ */
	{
		int32_t rc = AG903_ENONE;
		AG903_INTMgrHdrPrm intparam = {
			.atr	= AG903_INT_HLNG,
			.func	= (void *)VSyncHandler,
			.intno	= AG903_IRQ16_DSP0,
		};
		if(rc == AG903_ENONE) {
			gVSyncHdrID = AG903_INTMgrSetHandler(&intparam);
		}

		if(gVSyncHdrID >= 0) {
			AG903_INTMgrEnableInt(intparam.intno);
		} else {
			rc = gVSyncHdrID;
		}
	}

	/* GVD */
	{
	    int32_t err;
	    err = movie_init();
		if(err) {
			goto label_finalize;
		}
	}

	/* DSP */
	{
	    mpsContext *mpsctx;
	    gWindowAttributeAddr = MPS_Memalign(0x20 * MPS_MAX_STREAM, 8);
	    if(gWindowAttributeAddr == NULL)
	    {
			MPS_ErrPrintf("ERROR: Out of memory.\r\n");
			return;
	    }

		AG903_DSPMgrCtrlParam		dsp_ctrl	= {0};
	    AG903_DSPMgrCMOSParam		dsp_cmos	= {0};
	    AG903_DSPMgrWinAttribute	*dsp_attr	= NULL;
	    AG903_DSPMgrWindowParam		dsp_win		= {0};
		AG903_DSPMgrIntParam		dsp_int		= {0};

	    ERRCHK(AG903_ENONE, AG903_DSPMgrInit());

	    MPS_Printf("Initialize display settings...\r\n");

	    ERRCHK(AG903_ENONE, AG903_DSPMgrGetHandle(0, &dsp_handle[0]));

	    dsp_ctrl.ip_sel = AG903_DSP_VMODE_NONINTERLACE;
	    dsp_ctrl.hrz_framesize = 1280;
	    dsp_ctrl.vt_framesize  = 1024;
	    dsp_ctrl.syncparam = &DSP_SAMPLE_SYNCPARAM[DSP_SAMPLE_RES_SXGA];

		/* PLL */
		AG903_SPCPrmPllnParam pll = {0};
		AG903_SPCPrmSetPll1Ctrl(&pll);
		sys_delayus(50);
		AG903_SPCPrmSetPll1Ctrl(&DSP_SAMPLE_PLL[DSP_SAMPLE_RES_SXGA]);
		sys_delayus(50);

		/* PORT */
		AG903_DSPMgrSetPortSel(dsp_handle[0], AG903_VOD0_PORTSEL_LVCMOS24);

	    dsp_cmos.dotclk_polarity = AG903_VOD_DOTCLK_LATCH_FALL;
	    dsp_cmos.field_polarity  = AG903_DSP_POLARITY_POSI;
	    dsp_cmos.hsync_polarity  = AG903_DSP_POLARITY_NEGA;
		dsp_cmos.vsync_polarity  = AG903_DSP_POLARITY_NEGA;
	    dsp_cmos.rgbde_polarity  = AG903_DSP_POLARITY_POSI;
		dsp_cmos.hsync_en        = true;
		dsp_cmos.vsync_en        = true;
		dsp_cmos.rgbde_en        = true;
		dsp_cmos.pixeldata_en    = true;


	    ERRCHK(AG903_ENONE,
			   AG903_DSPMgrSetCMOSParam(dsp_handle[0], &dsp_ctrl, &dsp_cmos));

		dsp_int.trigger_out		= AG903_DSP_EVENT_VT;
		dsp_int.trigger_vt		= AG903_DSP_TRG_VT_START_OF_VBLANK;
		dsp_int.int_vt_blank	= AG903_DSP_INT_START_OF_VBLANK;
		dsp_int.mask_vt_blank	= false;
		dsp_int.mask_dspoff		= true;
		dsp_int.mask_error		= true;
		dsp_int.mask_hrz_line	= true;
	    ERRCHK(AG903_ENONE,
			   AG903_DSPMgrSetIntParam(dsp_handle[0], &dsp_int));

	    dsp_win.update_timing = AG903_DSP_ATTR_END_OF_VSYNC;
	    dsp_win.background = 0x00000000;
	    dsp_win.num_config = MPS_MAX_STREAM;
	    dsp_win.num_attr = MPS_MAX_STREAM;
	    dsp_win.palette_update = 0;
	    dsp_win.window_attr_base = (uint32_t)gWindowAttributeAddr;
	    dsp_win.window_attr_update = 1;
	    ERRCHK(AG903_ENONE,
			   AG903_DSPMgrSetWindowParam(dsp_handle[0], &dsp_win));

	    /* Window 0 */
	    mpsctx = MPS_getContext(0);
	    ERRCHK(AG903_ENONE, AG903_DSPMgrGetAttribute(dsp_handle[0], 0, &dsp_attr));
	    sys_memset(dsp_attr, 0, 0x20*MPS_MAX_STREAM);
	    if(TRICK_WINDOW_WIDTH == 0)
	    {
			if(mpsctx->info.width != 0)
			{
				dsp_attr[0].destination_width = mpsctx->info.width;
				/* センタリング */
				dsp_attr[0].position_x =
					(dsp_ctrl.hrz_framesize - mpsctx->info.width)>>1;
			}
			else
			{
				dsp_attr[0].destination_width = TRICK_STREAM_WIDTH;
				/* センタリング */
				dsp_attr[0].position_x =
					(dsp_ctrl.hrz_framesize - TRICK_STREAM_WIDTH)>>1;
			}
	    }
	    else
	    {
		    dsp_attr[0].destination_width = TRICK_WINDOW_WIDTH;
			/* センタリング */
		    dsp_attr[0].position_x =
				(dsp_ctrl.hrz_framesize - TRICK_WINDOW_WIDTH)>>1;
	    }
	    if(TRICK_WINDOW_HEIGHT == 0)
	    {
			if(mpsctx->info.height != 0)
			{
				dsp_attr[0].destination_height = mpsctx->info.height;
				/* センタリング */
				dsp_attr[0].position_y =
					(dsp_ctrl.vt_framesize - mpsctx->info.height)>>1;
			}
			else
			{
				dsp_attr[0].destination_height = TRICK_STREAM_HEIGHT;
				/* センタリング */
				dsp_attr[0].position_y =
					(dsp_ctrl.vt_framesize - TRICK_STREAM_HEIGHT)>>1;
			}
	    }
	    else
	    {
		    dsp_attr[0].destination_height = TRICK_WINDOW_HEIGHT;
			/* センタリング */
		    dsp_attr[0].position_y =
				(dsp_ctrl.vt_framesize - TRICK_WINDOW_HEIGHT)>>1;
	    }
		dsp_attr[0].framebuffer_base = (uint32_t)gOutbufinfo[MPS_OUTBUF_COUNT-1].buffer;
		dsp_attr[0].source_width =
			mpsctx->info.width!=0 ? mpsctx->info.width : TRICK_STREAM_WIDTH;
		dsp_attr[0].source_height =
			mpsctx->info.height!=0 ? mpsctx->info.height : TRICK_STREAM_WIDTH;
	    dsp_attr[0].hrz_size = dsp_attr[0].source_width * 2;
	    dsp_attr[0].conf.default_alpha = 0xFF;
	    dsp_attr[0].conf.framebuffer_format = AG903_DSP_FFMT_YUV422_BT601_LIMIT;
	    dsp_attr[0].conf.valid = true;

	    ERRCHK(AG903_ENONE, AG903_DSPMgrSetAttribute(dsp_handle[0], 0));

		/* 表示ON */
	    ERRCHK(AG903_ENONE, AG903_DSPMgrEnable(dsp_handle[0]));

	    /* CMOS出力有効 */
	    Board_SelectDviTX();

	    sys_dlytsk(1000); /* ディスプレイの安定待ち */
	}

	MPS_Printf("<<< Setting done.\r\n");

	movie_play(); /* 再生処理 */

	sys_dlytsk(50); /* 他タスク終了猶予 */

	/* 終了処理 */
label_finalize:
	{
	    if(gWindowTaskID >= 0)
		{
			ERRCHK(E_OK, del_tsk(gWindowTaskID));
			gWindowTaskID = -1;
		}
	    if(gAsyncTaskID >= 0)
			ERRCHK(E_OK, del_tsk(gAsyncTaskID));

		if(dsp_handle[0] != NULL) {
			ERRCHK(AG903_ENONE, AG903_DSPMgrDisable(dsp_handle[0]));
			while(AG903_DSPMgrCheckStopped(dsp_handle[0]));
			AG903_DSPMgrIntStat intclr = {
				.clr_dspoff		= true,
				.clr_hrz_line	= true,
				.clr_vt_blank	= true,
			};
			AG903_DSPMgrClearIntStat(dsp_handle[0], &intclr);
			ERRCHK(AG903_ENONE, AG903_DSPMgrReleaseHandle(dsp_handle[0]));
		}

		ERRCHK(AG903_ENONE, AG903_INTMgrDisableInt(AG903_IRQ16_DSP0));
		ERRCHK(AG903_ENONE, AG903_INTMgrDeleteHandler(gVSyncHdrID));
		gVSyncHdrID = -1;
	    ERRCHK(E_OK, del_sem(gSemID_BufSync));

	    ERRCHK(AG903_ENONE, MPS_Finalize());
	    mpsconf = MPS_getConfig();
	    ERRCHK(AG903_ENONE, MPS_Free(mpsconf->memory_heap_top));
	    mpsconf->memory_heap_top = NULL;
	    ERRCHK(AG903_ENONE, MPS_Free(mpsconf->memory_work_top));
	    mpsconf->memory_work_top = NULL;

	    ERRCHK(AG903_ENONE, umountp(0, (ID)Fsif_GetDevid(FSIF_DEV_CF))); /* CFアンマウント */

	    ERRCHK(AG903_ENONE, MPS_Free(gWindowAttributeAddr));
	    gWindowAttributeAddr = NULL;
	}

	MPS_Printf("Main task done.\r\n");
	MPS_Printf("\n[SAMPLE END]\n");
}



/***********************************
           WINDOW CONTROL
***********************************/

void trick_window_tsk(VP_INT exinf)
{
	((void)exinf);
	while(!gFlagMPSDone)
	{
	    ERRCHK(E_OK, wai_sem(gSemID_BufSync));
	    WindowUpdate();
	    ERRCHK(E_OK, sig_sem(gSemID_BufSync));
	    slp_tsk();
	}

	MPS_Printf("Window task done.\r\n");
	ext_tsk();
}

static void VSyncHandler(void)
{
	AG903_DSPMgrIntStat intstat = {0};
	AG903_DSPMgrGetIntStat(dsp_handle[0], &intstat);
	intstat.clr_vt_blank	= intstat.int_vt_blank;
	AG903_DSPMgrClearIntStat(dsp_handle[0], &intstat);
	if(gWindowTaskID > 0)
		wup_tsk(gWindowTaskID);

	return;
}

static void UpdateWindowAddr(int win_id, uintptr_t addr)
{
	AG903_DSPMgrWinAttribute *dsp_attr;
	AG903_DSPMgrGetAttribute(dsp_handle[0], 0, &dsp_attr);
	dsp_attr[win_id].framebuffer_base = (uint32_t)addr;
	AG903_DSPMgrSetAttribute(dsp_handle[0], 0);
}

/* デコード済みバッファの追加 */
static void AppendBufferInfo(mpsContext *ctx, gvdOutBufInfo buf, gvdTimeStamp ts)
{
	ERRCHK(E_OK, wai_sem(gSemID_BufSync));

	DecodedBufferInfo dbi = {ctx, buf, ts};

	gBufList[gBufListNextAdd] = dbi;
	gBufListNextAdd++;
	if(gBufListNextAdd == MPS_OUTBUF_COUNT)
	    gBufListNextAdd = 0;
	gBufListCount++;

	ERRCHK(E_OK, sig_sem(gSemID_BufSync));
}

/* 表示の更新 */
static void WindowUpdate()
{
	if(gCallCnt == 0) /* 1/2 VSync */
	{
	    gCallCnt++;
	    return;
	}
	else
	{
	    gCallCnt = 0;

		if(gBufListCount > 0)
		{
			/* 表示フレームの更新 */
			UpdateWindowAddr(gBufList[gBufListCurrent].ctx->id,
							 (uintptr_t)gBufList[gBufListCurrent].buf.buffer);
			gCurrentFrame = gBufList[gBufListCurrent].ts.frame_id;

			/* 使い終えた出力バッファの再供給 */
			if(gBufListPrev != gBufListCurrent)
			{
				MPS_PutOutputBuffer(gBufList[gBufListPrev].ctx->id,
									&gBufList[gBufListPrev].buf);
				gBufList[gBufListPrev].buf.buffer = NULL;
			}

			gBufListPrev = gBufListCurrent;
			gBufListCurrent = gBufListNext;
			gBufListNext++;
			if(gBufListNext == MPS_OUTBUF_COUNT)
				gBufListNext = 0;

			gBufListCount--;

		}
	}
	return;
}

static void BufferReset(void)
{
	gBufListNextAdd = MPS_OUTBUF_COUNT-1;
	gBufListCount = 0;
	gBufListCurrent = MPS_OUTBUF_COUNT-1;
	gBufListNext = 0;
	gBufListPrev = MPS_OUTBUF_COUNT-1;
}


/* GVDのcommit_output_bufferのコールバック */
static int32_t cbMPSDecodeDone(void *param_gvd, void *param_user, gvdError error)
{
	((void)(error));
	int32_t idx;
	gvdOutBufInfo *outbuf = NULL;
	mpsContext *ctx = (mpsContext *)param_user;
	gvdFrameInfo *frameInfo = (gvdFrameInfo *)param_gvd;
	gvdTimeStamp ts = {frameInfo->timestamp, frameInfo->frame_count, 0};

	/* 返却されたgvdFrameInfoと同じアドレスを持つgvdOutBufInfoを検索する */
	for (idx=0; idx<MPS_OUTBUF_COUNT; idx++)
	{
		if(gOutbufinfo[idx].buffer == frameInfo->buffer)
	    {
			outbuf = &gOutbufinfo[idx];
			break;
		}
	}

	/* デコード済みバッファを追加 */
	AppendBufferInfo(ctx, *outbuf, ts);

	return 0;
}

/* GVDの致命的エラーのコールバック */
static int32_t cbMPSFatalerror(void *param_gvd, void *param_user, gvdError error)
{
	((void)param_user);
	((void)error);
	gFatal = *(gvdFatalErrorInfo *)param_gvd;
	MPS_ErrPrintf("ERROR: Fatal error detected. cont=%hhu, cause=%hhu\n",
			   gFatal.context_id, gFatal.cause);
	return 0;
}
