/*	
 *	clipcvid.c
 *
 *	Cinepakデコーダ
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2007 Naoyuki Sawa
 *
 *	* Fri Nov 16 16:28:45 JST 2007 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sun Nov 18 21:13:22 JST 2007 Naoyuki Sawa
 *	- cvid_decode()をASM化しました。
 *	* Mon Nov 19 20:01:34 JST 2007 Naoyuki Sawa
 *	- cvid_decode()のASM版の、最適化バグを修正しました。
 *	  retpを積むために、あらかじめ一つ多くpushnしておけば良いかと思ったのですが、
 *	  pushnはスタックトップ側に%r0を退避するので、popnと対応がズレてしまいます。
 *	  この最適化は誤りだったので、普通の方法に戻しました。
 *	  <誤った最適化>
 *		pushn	%r4
 *		...
 *		xld.w	[%sp+0], %r12
 *		xjp.d	memcpy
 *		sub	%r14, %r13
 *		...
 *		popn	%r3
 *		ret
 *	  <正しい普通の方法>
 *		pushn	%r3
 *		...
 *		xsub	%sp, %sp, 4
 *		xld.w	[%sp+0], %r12
 *		xjp.d	memcpy
 *		sub	%r14, %r13
 *		...
 *		popn	%r3
 *		ret
 *	* Tue Nov 20 12:00:01 JST 2007 Naoyuki Sawa
 *	- シンボル"PIECE"が定義されている場合だけ、シンボル"CVID_ASM"を定義するよう修正しました。
 *	  VC++での確認プログラム"cvidwin"からは、cvid_decode()のC版が使われるようにするためです。
 */
#include "clip.h"

/*=================================================================================
 * Cinepak
 *---------------------------------------------------------------------------------
 * 出典: フリー百科事典『ウィキペディア（Wikipedia）』
 * 
 * 『Cinepak』(シネパック)はSuperMac Technologiesの一事業部であるSuperMatchが開発し
 * たビデオコーデックであり、1992年にアップルコンピュータのQuickTimeの一部としてリ
 * リースされた。1倍速(150 kbyte/s)のCD-ROMの転送速度で320x240の解像度のビデオをエ
 * ンコードできるように設計された。このコーデックは1993年にWindowsへ移植された。ま
 * たAtari Jaguar CD、メガCD、セガサターン、3DOといったCD-ROMを搭載する第一〜二世代
 * の家庭用ゲーム機でも利用できた。
 * 
 * これは初期のQuickTime やマイクロソ\フトのVideo for Windowsで主力のビデオコーデッ
 * クであったが、Sorenson Video、インテルIndeo、そして最近のMPEG-4やH.264に取って代
 * わられた。しかしながらCinepakで圧縮されたムービーはほとんどのムービー再生ソ\フト
 * でまだ一般的に再生可能\である。
 * 
 * Cinepakは現在ほとんどのコーデックが(特にJPEGやMPEGファミリーが)利用しているDCTア
 * ルゴリズムとは全く異なるベクトル量子化採用している。比較的低速なCPUで実行できた
 * （68040の25MHzクラスのCPUにおいて、320x240のビデオを秒間30フレームで再生できた）
 * が、ビットレートを落とすとブロックノイズが生じる傾向があり、フルモーションビデオ
 * ゲームで批判を浴びた。
 * 
 * Cinepakはムービーをキーイメージとイントラコードイメージに分割する。各イメージは
 * キーイメージ内に転送される独立した256色のカラーパレットを持つたくさんの水平な帯
 * で分割される。それぞれの帯は4x4ピクセルのブロックで細分化される。圧縮プログラム
 * は各ブロックにベストマッチする１〜２つの帯のパレットカラーを決定するためにベクト
 * ル量子化を使用し、1カラーバイトまたは2カラーバイトのいずれかに加え、どのピクセル
 * がどの色になるのかを決める16ビットのベクタでブロックの連続をエンコードする。イン
 * トラコードのフレームに対するキーのレートを調整し、各ブロックとブロックのランレン
 * グスで防げなかったエラーを調整して、狭い範囲内でデータレートが制御される。
 * 
 * このコーデックの名前は以前はCompactVideoであり、FourCC識別子が"CVID"であるのはこ
 * のためである。
 * 
 * 外部リンク
 *---------------------------------------------------------------------------------
 * ・Compression Technologies, Inc. - Cinepakの現在の開発元
 *   http://www.cinepak.com/
 * ・Cinepakコーデックの技術資料
 *   http://www.csse.monash.edu.au/~timf/videocodec/cinepak.txt
 *=================================================================================
 */

#ifdef PIECE
#define CVID_ASM
#endif /*PIECE*/

/****************************************************************************
 *	
 ****************************************************************************/

CVIDDRIVER cvid_driver;

/*static*/ void cvid_decode();

/****************************************************************************
 *	
 ****************************************************************************/

/* AVIファイル内の、Cinepakビデオフレームを列挙する、テスト関数です。
 */
#ifdef DEBUG
void
cvid_enum(const char* fname)
{
	int mmr;
	int len;
	MMCKINFO mmci_hdrl;
	MMCKINFO mmci_avih;
	MMCKINFO mmci_strl;
	MMCKINFO mmci_strh;
	MMCKINFO mmci_strf;
	MMCKINFO mmci;

	memset(&cvid_driver, 0, sizeof cvid_driver); /* 念のため */

	cvid_driver.mmio = mmioOpen((char*)fname, NULL, MMIO_READ);
	if(!cvid_driver.mmio) DIE();
	cvid_driver.mmci_riff.fccType = mmioFOURCC('A', 'V', 'I', ' ');
	mmr = mmioDescend(cvid_driver.mmio, &cvid_driver.mmci_riff, NULL, MMIO_FINDRIFF);
	if(mmr) DIE();
	/*{{RIFF('AVI ')*/
		mmci_hdrl.fccType = mmioFOURCC('h', 'd', 'r', 'l');
		mmr = mmioDescend(cvid_driver.mmio, &mmci_hdrl, &cvid_driver.mmci_riff, MMIO_FINDLIST);
		if(mmr) DIE();
		/*{{LIST('hdrl')*/
			mmci_avih.ckid = mmioFOURCC('a', 'v', 'i', 'h');
			mmr = mmioDescend(cvid_driver.mmio, &mmci_avih, &mmci_hdrl, MMIO_FINDCHUNK);
			if(mmr) DIE();
			/*{{'avih'*/
			/** no job **/
			/*}}'avih'*/
			mmr = mmioAscend(cvid_driver.mmio, &mmci_avih, 0);
			if(mmr) DIE();
			for(cvid_driver.stri = 0; /** no job **/; cvid_driver.stri++) {
				mmci_strl.fccType = mmioFOURCC('s', 't', 'r', 'l');
				mmr = mmioDescend(cvid_driver.mmio, &mmci_strl, &mmci_hdrl, MMIO_FINDLIST);
				if(mmr) DIE();
				/*{{LIST('strl')*/
					mmci_strh.ckid = mmioFOURCC('s', 't', 'r', 'h');
					mmr = mmioDescend(cvid_driver.mmio, &mmci_strh, &mmci_strl, MMIO_FINDCHUNK);
					if(mmr) DIE();
					/*{{'cvid_driver.strh'*/
						len = mmioRead(cvid_driver.mmio, (char*)&cvid_driver.strh, sizeof cvid_driver.strh);
						if(len != sizeof cvid_driver.strh) DIE();
					/*}}'cvid_driver.strh'*/
					mmr = mmioAscend(cvid_driver.mmio, &mmci_strh, 0);
					if(mmr) DIE();
					if(cvid_driver.strh.fccType == mmioFOURCC('v', 'i', 'd', 's')) {
						mmci_strf.ckid = mmioFOURCC('s', 't', 'r', 'f');
						mmr = mmioDescend(cvid_driver.mmio, &mmci_strf, &mmci_strl, MMIO_FINDCHUNK);
						if(mmr) DIE();
						/*{{'cvid_driver.strf'*/
							len = mmioRead(cvid_driver.mmio, (char*)&cvid_driver.strf, sizeof cvid_driver.strf);
							if(len != sizeof cvid_driver.strf) DIE();
						/*}}'cvid_driver.strf'*/
						mmr = mmioAscend(cvid_driver.mmio, &mmci_strf, 0);
						if(mmr) DIE();
					}
				/*}}LIST('strl')*/
				mmr = mmioAscend(cvid_driver.mmio, &mmci_strl, 0);
				if(mmr) DIE();
				if((cvid_driver.strh.fccType       == mmioFOURCC('v', 'i', 'd', 's')) &&
				   (cvid_driver.strf.biCompression == mmioFOURCC('c', 'v', 'i', 'd'))) break; /* 'cvid' = Cinepak */
			}
		/*}}LIST('hdrl')*/
		mmr = mmioAscend(cvid_driver.mmio, &mmci_hdrl, 0);
		if(mmr) DIE();
		cvid_driver.mmci_movi.fccType = mmioFOURCC('m', 'o', 'v', 'i');
		mmr = mmioDescend(cvid_driver.mmio, &cvid_driver.mmci_movi, &cvid_driver.mmci_riff, MMIO_FINDLIST);
		if(mmr) DIE();
		/*{{LIST('movi')*/
		for(;;) {
			mmr = mmioDescend(cvid_driver.mmio, &mmci, &cvid_driver.mmci_movi, 0);
			if(mmr) break;
			if((mmci.ckid    == mmioFOURCC('L', 'I', 'S', 'T')) &&
			   (mmci.fccType == mmioFOURCC('r', 'e', 'c', ' '))) {
				cvid_driver.mmci_rec = mmci;
				/*{{LIST('rec ')*/
				for(;;) {
					mmr = mmioDescend(cvid_driver.mmio, &mmci, &cvid_driver.mmci_rec, 0);
					if(mmr) break;
					if((((char*)&mmci.ckid)[0] == '0') && (((char*)&mmci.ckid)[1] == '0' + cvid_driver.stri)) {
						/*{{'0?xx'*/
						TRACE("rec %.4s %10d %10d\n", &mmci.ckid, mmci.dwDataOffset, mmci.cksize);
						/*}}'0?xx'*/
					} else {
						/*{{'0*xx'or'JUNK'*/
						/** no job **/
						/*}}'0*xx'or'JUNK'*/
					}
					mmr = mmioAscend(cvid_driver.mmio, &mmci, 0);
					if(mmr) DIE();
				}
				/*}}LIST('rec ')*/
				mmr = mmioAscend(cvid_driver.mmio, &cvid_driver.mmci_rec, 0);
				if(mmr) DIE();
				continue;
			}
			if((((char*)&mmci.ckid)[0] == '0') && (((char*)&mmci.ckid)[1] == '0' + cvid_driver.stri)) {
				/*{{'0?xx'*/
				TRACE("    %.4s %10d %10d\n", &mmci.ckid, mmci.dwDataOffset, mmci.cksize);
				/*}}'0?xx'*/
			} else {
				/*{{'0*xx'or'JUNK'*/
				/** no job **/
				/*}}'0*xx'or'JUNK'*/
			}
			mmr = mmioAscend(cvid_driver.mmio, &mmci, 0);
			if(mmr) DIE();
		}
		/*}}LIST('movi')*/
		mmr = mmioAscend(cvid_driver.mmio, &cvid_driver.mmci_movi, 0); /* 省略可 */
		if(mmr) DIE();
	/*}}RIFF('AVI ')*/
	mmr = mmioAscend(cvid_driver.mmio, &cvid_driver.mmci_riff, 0); /* 省略可 */
	if(mmr) DIE();
	mmr = mmioClose(cvid_driver.mmio, 0);
	if(mmr) DIE();

	memset(&cvid_driver, 0, sizeof cvid_driver); /* 念のため */
}
#endif /*DEBUG*/

/****************************************************************************
 *	
 ****************************************************************************/

void
cvid_init(const char* fname, int loop)
{
	int mmr;
	int len;
	MMCKINFO mmci_hdrl;
	MMCKINFO mmci_avih;
	MMCKINFO mmci_strl;
	MMCKINFO mmci_strh;
	MMCKINFO mmci_strf;

	/* まず、確実にファイルを閉じます。 */
	cvid_free();

	/*{{----- cvid_enum()のアルゴリズムを参照してください -----*/
	cvid_driver.mmio = mmioOpen((char*)fname, NULL, MMIO_READ);
	if(!cvid_driver.mmio) DIE();
	cvid_driver.mmci_riff.fccType = mmioFOURCC('A', 'V', 'I', ' ');
	mmr = mmioDescend(cvid_driver.mmio, &cvid_driver.mmci_riff, NULL, MMIO_FINDRIFF);
	if(mmr) DIE();
	/*{{RIFF('AVI ')*/
		mmci_hdrl.fccType = mmioFOURCC('h', 'd', 'r', 'l');
		mmr = mmioDescend(cvid_driver.mmio, &mmci_hdrl, &cvid_driver.mmci_riff, MMIO_FINDLIST);
		if(mmr) DIE();
		/*{{LIST('hdrl')*/
			mmci_avih.ckid = mmioFOURCC('a', 'v', 'i', 'h');
			mmr = mmioDescend(cvid_driver.mmio, &mmci_avih, &mmci_hdrl, MMIO_FINDCHUNK);
			if(mmr) DIE();
			/*{{'avih'*/
			/** no job **/
			/*}}'avih'*/
			mmr = mmioAscend(cvid_driver.mmio, &mmci_avih, 0);
			if(mmr) DIE();
			for(cvid_driver.stri = 0; /** no job **/; cvid_driver.stri++) {
				mmci_strl.fccType = mmioFOURCC('s', 't', 'r', 'l');
				mmr = mmioDescend(cvid_driver.mmio, &mmci_strl, &mmci_hdrl, MMIO_FINDLIST);
				if(mmr) DIE();
				/*{{LIST('strl')*/
					mmci_strh.ckid = mmioFOURCC('s', 't', 'r', 'h');
					mmr = mmioDescend(cvid_driver.mmio, &mmci_strh, &mmci_strl, MMIO_FINDCHUNK);
					if(mmr) DIE();
					/*{{'cvid_driver.strh'*/
						len = mmioRead(cvid_driver.mmio, (char*)&cvid_driver.strh, sizeof cvid_driver.strh);
						if(len != sizeof cvid_driver.strh) DIE();
					/*}}'cvid_driver.strh'*/
					mmr = mmioAscend(cvid_driver.mmio, &mmci_strh, 0);
					if(mmr) DIE();
					if(cvid_driver.strh.fccType == mmioFOURCC('v', 'i', 'd', 's')) {
						mmci_strf.ckid = mmioFOURCC('s', 't', 'r', 'f');
						mmr = mmioDescend(cvid_driver.mmio, &mmci_strf, &mmci_strl, MMIO_FINDCHUNK);
						if(mmr) DIE();
						/*{{'cvid_driver.strf'*/
							len = mmioRead(cvid_driver.mmio, (char*)&cvid_driver.strf, sizeof cvid_driver.strf);
							if(len != sizeof cvid_driver.strf) DIE();
						/*}}'cvid_driver.strf'*/
						mmr = mmioAscend(cvid_driver.mmio, &mmci_strf, 0);
						if(mmr) DIE();
					}
				/*}}LIST('strl')*/
				mmr = mmioAscend(cvid_driver.mmio, &mmci_strl, 0);
				if(mmr) DIE();
				if((cvid_driver.strh.fccType       == mmioFOURCC('v', 'i', 'd', 's')) &&
				   (cvid_driver.strf.biCompression == mmioFOURCC('c', 'v', 'i', 'd'))) break; /* 'cvid' = Cinepak */
			}
		/*}}LIST('hdrl')*/
		mmr = mmioAscend(cvid_driver.mmio, &mmci_hdrl, 0);
		if(mmr) DIE();
		cvid_driver.mmci_movi.fccType = mmioFOURCC('m', 'o', 'v', 'i');
		mmr = mmioDescend(cvid_driver.mmio, &cvid_driver.mmci_movi, &cvid_driver.mmci_riff, MMIO_FINDLIST);
		if(mmr) DIE();
		/*{{LIST('movi')*/
	/*}}----- cvid_enum()のアルゴリズムを参照してください -----*/

	/* 残りループ回数と、ループ位置のファイルポインタを格納します。 */
	cvid_driver.loop = loop;
	cvid_driver.loop_pos = mmioSeek(cvid_driver.mmio, 0, SEEK_CUR);
	if(cvid_driver.loop_pos < 0) DIE();

	/* 展開用サーフェイスのメモリを確保します。
	 * * デコーダの実装を簡単にするために、4x4ピクセルの倍数に切り上げて確保することにします。
	 *   以下に、理由を説明します。
	 * - 本モジュール先頭に引用したWikipediaの記事にあるように、Cinepakは4x4ピクセルのブロックを基本としています。
	 *   Cinepakフレーム自体は、4x4ピクセルの倍数でないフレームサイズになることは、有り得ません。
	 *   たとえば、77x58のビデオをCinepak圧縮すると、80x60のCinepakフレームデータが出力されます。
	 *   具体的には、(Frame Header).(Width of coded frame)=80、(Height of coded frame)=60、となります。
	 * - はみ出た部分(横3ピクセル、縦2ピクセル)は、内容不定のゴミとして圧縮されています。
	 *   はみ出た部分を黒や白固定として扱うよりも、内容不定とする方が、圧縮効率が良くなるからだと思います。
	 * - 本当のビデオサイズ(77x58)は、Cinepakよりも上位の概念である、AVIストリームフォーマットに格納されています。
	 *   従って、Cinepakデコーダは、4x4ピクセルの倍数であるCinepakフレームをデコードしたあと、
	 *   AVIストリームフォーマットを参照して、必要に応じてはみ出た部分を取り除くのが厳密な方法です。
	 * - しかし、P/ECEのプログラムにとっては、はみ出た部分を取り除くためのメモリ負荷が高くつきます。
	 *   展開用の80x60サーフェイスと別に、出力用の77x58サーフェイスも確保する必要が生じるからです。
	 *   80x60と77x58のメモリを共有して、出力結果をラインごとにmemmove()でずらす方法は使えません。
	 *   Cinepakはフレーム間の差分更新ですから、80x60の出力結果を破壊することができないからです。
	 * - あるいは、77x58のサーフェイスに直接展開する方法も考えられますが、その場合はクリッピングが必要となります。
	 *   プログラムが複雑になり、実行速度も低下します。
	 * - 以上の理由により、4x4ピクセルの倍数に切り上げたサーフェイスを、一つだけ確保する方法を採ることにしました。
	 *   現実的には、ほとんどのビデオは4x4の倍数なので、影響は少ないと思います。
	 *   もし、4x4の倍数でなくても、画面の右または下にわずかなゴミが生じるだけで、ほとんど気にならないと思います。
	 *   P/ECEで再生するビデオは、サイズ低減のために低品質圧縮とせざるを得ず、圧縮ノイズと区別が付かないからです。
	 * - もし、ゴミを取り除いた実サイズで再生する必要が生じた場合は、cvid_driver.strf.biWidth,biHeightを参照して、
	 *   アプリケーションプログラムにて、実サイズ部分のみをコピーする等の処理を行ってください。
	 */
	cvid_driver.surface.w = (cvid_driver.strf.biWidth  + 3) & ~3;
	cvid_driver.surface.h = (cvid_driver.strf.biHeight + 3) & ~3;
	cvid_driver.surface.vbuff = malloc(cvid_driver.surface.w * cvid_driver.surface.h);
	if(!cvid_driver.surface.vbuff) DIE();

	/* フレームデータの読み出しバッファを確保します。 */
	cvid_driver.buffer = malloc(cvid_driver.strh.dwSuggestedBufferSize);
	if(!cvid_driver.buffer) DIE();
}

/****************************************************************************
 *	
 ****************************************************************************/

void
cvid_free()
{
	int mmr;

	/* ファイルが開かれていなければ、何もしません。 */
	if(!cvid_driver.mmio) return;

	/*{{----- cvid_enum()のアルゴリズムを参照してください -----*/
		/*}}LIST('movi')*/
		mmr = mmioAscend(cvid_driver.mmio, &cvid_driver.mmci_movi, 0); /* 省略可 */
		if(mmr) DIE();
	/*}}RIFF('AVI ')*/
	mmr = mmioAscend(cvid_driver.mmio, &cvid_driver.mmci_riff, 0); /* 省略可 */
	if(mmr) DIE();
	mmr = mmioClose(cvid_driver.mmio, 0);
	if(mmr) DIE();
	/*}}----- cvid_enum()のアルゴリズムを参照してください -----*/

	/* メモリを開放します。 */
	free(cvid_driver.surface.vbuff);
	free(cvid_driver.buffer);
	free(cvid_driver.strips);

	/* ドライバ構\造体をクリアします。(必須) */
	memset(&cvid_driver, 0, sizeof cvid_driver);
}

/****************************************************************************
 *	
 ****************************************************************************/

int
cvid_update()
{
	int mmr;
	int len;
	MMCKINFO mmci;

	if(!cvid_driver.loop) return -1; /* 再生終了 */

	/*{{----- cvid_enum()のアルゴリズムを参照してください -----*/
	for(;;) {
		if(!cvid_driver.mmci_rec.ckid) { /* Outer LIST('rec ') */
			mmr = mmioDescend(cvid_driver.mmio, &mmci, &cvid_driver.mmci_movi, 0);
			if(mmr) { /* EOF */
				if(cvid_driver.loop > 0) {
					cvid_driver.loop--;
					if(!cvid_driver.loop) return -1; /* 再生終了 */
				}
				mmr = mmioSeek(cvid_driver.mmio, cvid_driver.loop_pos, SEEK_SET);
				if(mmr != cvid_driver.loop_pos) DIE();
				continue;
			}
			if((mmci.ckid    == mmioFOURCC('L', 'I', 'S', 'T')) &&
			   (mmci.fccType == mmioFOURCC('r', 'e', 'c', ' '))) {
				cvid_driver.mmci_rec = mmci; /* Inner LIST('rec ') */
				continue;
				/*{{LIST('rec ')*/
			}
		} else {  /* Inner LIST('rec ') */
			mmr = mmioDescend(cvid_driver.mmio, &mmci, &cvid_driver.mmci_rec, 0);
			if(mmr) {
				/*}}LIST('rec ')*/
				mmr = mmioAscend(cvid_driver.mmio, &cvid_driver.mmci_rec, 0);
				if(mmr) DIE();
				cvid_driver.mmci_rec.ckid = 0; /* Outer LIST('rec ') */
				continue;
			}
		}
		if((((char*)&mmci.ckid)[0] == '0') && (((char*)&mmci.ckid)[1] == '0' + cvid_driver.stri)) {
			/*{{'0?xx'*/
			if(mmci.cksize > cvid_driver.strh.dwSuggestedBufferSize) DIE();
			len = mmioRead(cvid_driver.mmio, cvid_driver.buffer, mmci.cksize);
			if(len != (int)mmci.cksize) DIE();
			cvid_decode();
			return 0; /* 再生継続 */
			/*}}'0?xx'*/
		} else {
			/*{{'0*xx'or'JUNK'*/
			/** no job **/
			/*}}'0*xx'or'JUNK'*/
		}
		mmr = mmioAscend(cvid_driver.mmio, &mmci, 0);
		if(mmr) DIE();
	}
	/*}}----- cvid_enum()のアルゴリズムを参照してください -----*/

	/* NOTREACHED */
}

/****************************************************************************
 *	
 ****************************************************************************/

#ifndef CVID_ASM

#define get_byte(v) do { (v)=*data++; } while(0)
#define get_half(v) do { (v)=*data++; (v)=((v)<<8)|*data++; } while(0)
#define get_word(v) do { (v)=*data++; (v)=((v)<<8)|*data++; (v)=((v)<<8)|*data++; (v)=((v)<<8)|*data++; } while(0)

/*static*/ void
cvid_decode()
{
	unsigned char* p = cvid_driver.surface.vbuff;
	const unsigned char* data = cvid_driver.buffer;
	const unsigned char* strip_end;
	const unsigned char* chunk_end;
	CVIDSTRIP* strip;
	CVIDCODEBOOK* codebook;
	int i;
	unsigned flag;
	unsigned mask;
	/* Frame Header */
	int frame_flags;
	int frame_width;
	int frame_height;
	int frame_strips;
	/* Strip Header */
	int strip_id;
	int strip_size;
	int strip_x;
	int strip_y;
	int strip_w;
	int strip_h;
	/* CVID Chunk */
	int chunk_id;
	int chunk_size;

	/* Frame Header */
	get_byte(frame_flags);	/* Flags */
	data += 3;		/* Length of CVID data */
	get_half(frame_width);	/* Width of coded frame */
	get_half(frame_height);	/* Height of coded frame */
	get_half(frame_strips);	/* Number of coded strips */

	if((frame_width  != cvid_driver.surface.w) ||
	   (frame_height != cvid_driver.surface.h)) DIE();

	if(frame_strips > cvid_driver.num_strips) {
		cvid_driver.num_strips = frame_strips;
		cvid_driver.strips = realloc(cvid_driver.strips, sizeof(CVIDSTRIP) * frame_strips);
		if(!cvid_driver.strips) DIE();
	}
	strip = cvid_driver.strips;
	do {
		/* 不思議な仕様ですが、必要です。
		 * 試しにこの処理を削ってみると、高解像度(おおよそ640×480以上)、かつ、低品質圧縮を行ったビデオなどで、画面の下半分が崩れたりします。
		 * キーフレームの二つ目以降のストリップが、一つ目のストリップで初期化したコードブックを共有することで、サイズを節約しているようです。
		 * 低解像度のビデオ(ストリップが一つしかない)や、高品質圧縮のビデオでは、上述の不具合は再現できません。
		 */
		if(!(frame_flags & 1) && (strip != cvid_driver.strips)) {
			strip[0] = strip[-1];
		}

		/* Strip Header */
		get_half(strip_id);	/* Strip CVID ID */
		get_half(strip_size);	/* Size of strip data */
		get_half(strip_y);	/* Strips top Y position */
		get_half(strip_x);	/* Strips top X position */
		get_half(strip_h);	/* Strips bottom Y position */
		get_half(strip_w);	/* Strips bottom X position */

		if((strip_id != 0x1000/*Intra-coded strip*/) &&
		   (strip_id != 0x1100/*Inter-coded strip*/)) DIE();

		if(strip_size & 1) DIE();

		if((strip_x != 0) || (strip_w != frame_width) ||
		   (strip_y != 0) || (strip_h > frame_height)) DIE();

		strip_end = data + (strip_size - 12/*Strip Header*/);
		while(data < strip_end) {
			/* CVID Chunk */
			get_half(chunk_id);	/* CVID Chunk ID */
			get_half(chunk_size);	/* Size of chunk data */

			if(chunk_size & 1) DIE();

			chunk_end = data + (chunk_size - 4/*CVID Chunk*/);
			switch(chunk_id) {
			case 0x2000:	/* List of blocks in 12 bit V4 codebook */
			case 0x2100:	/* Selective list of blocks to update 12 bit V4 codebook */
			case 0x2200:	/* List of blocks in 12 bit V1 codebook */
			case 0x2300:	/* Selective list of blocks to update 12 bit V1 codebook */
			case 0x2400:	/* List of blocks in 8 bit V4 codebook */
			case 0x2500:	/* Selective list of blocks to update 8 bit V4 codebook */
			case 0x2600:	/* List of blocks in 8 bit V1 codebook */
			case 0x2700:	/* Selective list of blocks to update 8 bit V1 codebook */
				codebook = (chunk_id & 0x0200) ? strip->v1 : strip->v4;
				while(data < chunk_end) {
					if(chunk_id & 0x0100) {
						get_word(flag);	/* Update Flags */
					} else {
						flag = -1;
					}
					mask = (1<<31);
					do {
						if(data >= chunk_end) break; /* 必要。データ数は32の倍数とは限らない。データ数が0の場合さえ有る */
						if(flag & mask) {
							get_byte(i); codebook->y0 = (~i>>4);	/* Luminance value 0 */
							get_byte(i); codebook->y1 = (~i>>4);	/* Luminance value 1 */
							get_byte(i); codebook->y2 = (~i>>4);	/* Luminance value 2 */
							get_byte(i); codebook->y3 = (~i>>4);	/* Luminance value 3 */
							if(!(chunk_id & 0x0400)) data += 2;	/* U Chrominance value, V Chrominance value */
						}
						codebook++;
					} while(mask >>= 1);
				}
				break;
			case 0x3000:	/* Vectors used to encode a frame */
			case 0x3100:	/* Selective set of vectors used to encode a frame */
			case 0x3200:	/* List of blocks from only the V1 codebook */
				for(;;) {
					if(chunk_id & 0x0200) {
						flag = 0;
					} else {
						get_word(flag);
					}
					mask = (1<<31);
					do {
						if(chunk_id & 0x0100) {
							if(!(flag & mask)) {
								p += 4;
								goto L_SKIP;
							}
							if(!(mask >>= 1)) {
								get_word(flag);
								mask = (1<<31);
							}
						}
						if(flag & mask) {
							get_byte(i);	/* r0 */
							codebook = &strip->v4[i];
							*p = codebook->y0; p += 1;
							*p = codebook->y1; p -= 1 - cvid_driver.surface.w;
							*p = codebook->y2; p += 1;
							*p = codebook->y3; p += 1 - cvid_driver.surface.w;
							get_byte(i);	/* r1 */
							codebook = &strip->v4[i];
							*p = codebook->y0; p += 1;
							*p = codebook->y1; p -= 1 - cvid_driver.surface.w;
							*p = codebook->y2; p += 1;
							*p = codebook->y3; p -= 3 - cvid_driver.surface.w;
							get_byte(i);	/* r2 */
							codebook = &strip->v4[i];
							*p = codebook->y0; p += 1;
							*p = codebook->y1; p -= 1 - cvid_driver.surface.w;
							*p = codebook->y2; p += 1;
							*p = codebook->y3; p += 1 - cvid_driver.surface.w;
							get_byte(i);	/* r3 */
							codebook = &strip->v4[i];
							*p = codebook->y0; p += 1;
							*p = codebook->y1; p -= 1 - cvid_driver.surface.w;
							*p = codebook->y2; p += 1;
							*p = codebook->y3; p += 1 - cvid_driver.surface.w * 3;
						} else {
							get_byte(i);	/* r */
							codebook = &strip->v1[i];
							*p = codebook->y0; p += 1;
							*p = codebook->y0; p -= 1 - cvid_driver.surface.w;
							*p = codebook->y0; p += 1;
							*p = codebook->y0; p += 1 - cvid_driver.surface.w;
							*p = codebook->y1; p += 1;
							*p = codebook->y1; p -= 1 - cvid_driver.surface.w;
							*p = codebook->y1; p += 1;
							*p = codebook->y1; p -= 3 - cvid_driver.surface.w;
							*p = codebook->y2; p += 1;
							*p = codebook->y2; p -= 1 - cvid_driver.surface.w;
							*p = codebook->y2; p += 1;
							*p = codebook->y2; p += 1 - cvid_driver.surface.w;
							*p = codebook->y3; p += 1;
							*p = codebook->y3; p -= 1 - cvid_driver.surface.w;
							*p = codebook->y3; p += 1;
							*p = codebook->y3; p += 1 - cvid_driver.surface.w * 3;
						}
L_SKIP:						if(!(strip_w -= 4)) {
							p += cvid_driver.surface.w * 3;
							if(!(strip_h -= 4)) {
								/* チャンク内の実データが奇数サイズの場合、末尾に1バイトのpadが存在します。
								 * chunk_sizeはpad込みのサイズを示しているので、検査する前に1バイト送る必要が有ります。
								 * 0x3?00以外のチャンクデータは必ず偶数サイズですから、調整が必要なのは0x3?00だけです。
								 */
								data = (unsigned char*)(((int)data + 1) & ~1);
								goto L_EXIT;
							}
							strip_w = cvid_driver.surface.w;
						}
					} while(mask >>= 1);
				}
L_EXIT:				if(p > &cvid_driver.surface.vbuff[cvid_driver.surface.w * cvid_driver.surface.h]) DIE();
				break;
			default:
				DIE();
			}
			if(data != chunk_end) DIE();

		}
		if(data != strip_end) DIE();

		strip++;
	} while(--frame_strips);
	if(p != &cvid_driver.surface.vbuff[cvid_driver.surface.w * cvid_driver.surface.h]) DIE();
}

#else /*CVID_ASM*/

/* * Sun Nov 18 21:13:22 JST 2007 Naoyuki Sawa
 * - cvid_decode()をASM化しました。
 *   高速化の効果は、次のとおりです。
 *	<例> おでマルのオープニング全体を、80x60ピクセル低品質で圧縮したaviを、最初から最後まで(8FPS、全866フレーム)デコードする時間
 *	     (TURBO=-1、LDIRECT=Off。cvid_update()だけを呼ぶデコード処理の時間のみで、拡大ストレッチや画面転送の時間は含めていません)
 *		 C版		ASM版
 *		--------	--------
 *		1749[ms]	 448[ms]
 *
 * [note]
 * - 32回分のループを回す際、
 *	i = 32;			// xld.w %rd, 32	二命令
 *	do {
 *		...		// sub %rd, 1
 *	} while(--i);		// xjrne addr
 *   という形式ではなく、
 *	i = 31;			// ld.w %rd, 31		一命令
 *	do {
 *		...		// sub %rd, 1
 *	} while(--i >= 0);	// xjrge addr
 *   という形式を用いました。
 *   ループカウンタに代入する際、32ではextが必要ですが、31ならばld.w一命令で済むからです。
 *   別の方法として、今回は使いませんでしたが、-32からの増加カウンタとする方法もあります。
 *	i = -32;		// ld.w %rd, -32	一命令
 *	do {
 *		...		// add %rd, 1
 *	} while(--i);		// xjrne addr
 *
 * [note]
 * - 0x3?00チャンク内のデータ以外は、少なくともハーフワードアライメントされています。
 *   たとえばget_word()は、以下のように書くことができます。
 *	ld.uh	%rd, [%rs]+
 *	ld.uh	%r9, [%rs]+
 *	xsla	%r9, 16
 *	or	%rd, %r9
 *	swap	%rd, %rd
 *   ワードアライメントは保証されていないので、ハーフワード単位で読み出すことに必要があります。
 * - 0x3?00チャンク内のデータは、アライメントが保証されていないので、バイト単位で読み出します。
 *   たとえばget_word()は、以下のように書く必要があります。
 *	ld.ub	%rd, [%rs]+
 *	sla	%rd, 8
 *	ld.ub	%r9, [%rs]+
 *	or	%rd, %r9
 *	sla	%rd, 8
 *	ld.ub	%r9, [%rs]+
 *	or	%rd, %r9
 *	sla	%rd, 8
 *	ld.ub	%r9, [%rs]+
 *	or	%rd, %r9
 */
static void cvid_decode_DIE() __attribute__((noreturn,unused));
static void cvid_decode_DIE() { DIE(); }
/*static*/ void cvid_decode();
asm("
	.code
	.align	1
cvid_decode:
	pushn	%r3
	xld.w	%r0, cvid_driver			; %r0  := &cvid_driver
	xld.w	%r1, [%r0+12]				; %r1  := data = buffer
	xld.w	%r2, [%r0+16]				; %r2  := num_strips							*anti-interlock*
	;/* Frame Header */
	add	%r1, 8					; %r1  := data += 8	// Flags, Length of CVID data, Width of coded frame, Height of coded frame
	ld.uh	%r3, [%r1]+				; %r3  := frame_strips	// Number of coded strips
	xsla	%r3, 16
	swap	%r3, %r3
	;// %r0  := &cvid_driver
	;// %r1  := data
	;// %r2  := num_strips
	;// %r3  := frame_strips
	cmp	%r3, %r2				; if(frame_strips > num_strips) {
	xjrle	cvid_decode_REALLOC
	xld.w	[%r0+16], %r3				;   cvid_driver.num_strips = frame_strips
	xld.w	%r12, [%r0+20]				;   %r12 := strips
	ld.w	%r13, %r3				;   %r13 := frame_strips
;//	xsla	%r13, 10				;   %r13 := frame_strips * 1024 --------------------------+
	sla	%r13, 8					;   <-----------------------------------------------------+
	xcall.d	realloc					;   %r10 := strips = realloc(strips, frame_strips * 1024) |
	sla	%r13, 2					;   <-----------------------------------------------------+		*delay*
	cmp	%r10, 0					;   if(!strips) DIE()
	xjreq	cvid_decode_DIE
	xld.w	[%r0+20], %r10				;   cvid_driver.strips = strips
cvid_decode_REALLOC:					; }
	xld.w	%r2, [%r0+4]				; %r2  := p = surface.vbuff
	xld.w	%r0, [%r0+20]				; %r0  := strip = strips
	;// %r0  := strip
	;// %r1  := data
	;// %r2  := p
	;// %r3  := frame_strips
	xld.w	%r12, 0x0e00				; %r12 := FRAM4
	xld.w	%r13, cvid_decode_START			; %r13 := START
	xld.w	%r14, cvid_decode_END			; %r14 := END
	xsub	%sp, %sp, 4
	xld.w	[%sp+0], %r12				; retp := FRAM4
	xjp.d	memcpy					; memcpy(FRAM4, START, END - START)
	sub	%r14, %r13				; %r14 := END - START							*delay*
;//{{ここからFRAM4領域へ転送
cvid_decode_START:
	xld.w	%r4, 0xF0F0F0F0
	;// %r0  := strip
	;// %r1  := data
	;// %r2  := p
	;// %r3  := frame_strips
	;// %r4  := 0xF0F0F0F0
cvid_decode_STRIP_FOR:					; for(;;) {
	;/* Strip Header */
	add	%r1, 2					;			// Strip CVID ID
	ld.uh	%r5, [%r1]+				;   %r5  := strip_size	// Size of strip data
	xsla	%r5, 16
	swap	%r5, %r5
	add	%r1, 4					;			// Strips top Y position, Strips top X position
	ld.uh	%r6, [%r1]+				;   %r6  := strip_h	// Strips bottom Y position
	xsla	%r6, 16
	swap	%r6, %r6
	ld.uh	%r7, [%r1]+				;   %r7  := strip_w	// Strips bottom X position
	xsla	%r7, 16
	swap	%r7, %r7
	sub	%r5, 12					;   %r5  := strip_size - 12/*Strip Header*/
	add	%r5, %r1				;   %r5  := strip_end = data + (strip_size - 12/*Strip Header*/)
	;// %r0  := strip
	;// %r1  := data
	;// %r2  := p
	;// %r3  := frame_strips
	;// %r4  := 0xF0F0F0F0
	;// %r5  := strip_end
	;// %r6  := strip_h
	;// %r7  := strip_w
cvid_decode_CHUNK_WHILE:
	cmp	%r1, %r5				;   while(data < strip_end) {
	xjruge	cvid_decode_CHUNK_BREAK
	;/* CVID Chunk */
	ld.w	%r10, %r1				;     %r10 := &chunk_id = data	// CVID Chunk ID
	add	%r1, 2
	;// %r0  := strip
	;// %r1  := data
	;// %r2  := p
	;// %r3  := frame_strips
	;// %r4  := 0xF0F0F0F0
	;// %r5  := strip_end
	;// %r6  := strip_h
	;// %r7  := strip_w
	;// %r10 := &chunk_id
	ld.uh	%r11, [%r1]+				;     %r11 := SWAP(chunk_size)	// Size of chunk data
	btst	[%r10], 4				;     switch(chunk_id) {
	xjrne	cvid_decode_FRAME
	;// CODEBOOK -----------------------------------;
;//cvid_decode_CODEBOOK:				;     case 0x2000,0x2100,0x2200,0x2300,0x2400,0x2500,0x2600,0x2700:
	xsla	%r11, 16				;       %r11 := chunk_size
	swap	%r11, %r11
	sub	%r11, 4					;       %r11 := chunk_size - 4/*CVID Chunk*/
	add	%r11, %r1				;       %r11 := chunk_end = data + (chunk_size - 4/*CVID Chunk*/)
	btst	[%r10], 1				;       %r12 := codebook = (chunk_id & 0x0200) ? strip->v1 : strip->v4
	jreq.d	4
	 ld.w	%r12, %r0				;									*delay*
	 ext	8	;// xadd %r4,%r4,512
	 add	%r12, 0
	;// %r0  := strip
	;// %r1  := data
	;// %r2  := p
	;// %r3  := frame_strips
	;// %r4  := 0xF0F0F0F0
	;// %r5  := strip_end
	;// %r6  := strip_h
	;// %r7  := strip_w
	;// %r10 := &chunk_id
	;// %r11 := chunk_end
	;// %r12 := codebook
cvid_decode_CODEBOOK_WHILE:
	cmp	%r1, %r11				;       while(data < chunk_end) {
	xjruge	cvid_decode_CHUNK_WHILE
	btst	[%r10], 0				;         %r13 := flag = (chunk_id & 0x0100) ? get_word() : -1
	jreq.d	7
	 ld.w	%r13, -1				;									*delay*
	 ld.uh	%r13, [%r1]+
	 ld.uh	%r9, [%r1]+
	 xsla	%r9, 16
	 or	%r13, %r9
	 swap	%r13, %r13
	ld.w	%r14, 31				;         %r14 := mask = 31
	;// %r0  := strip
	;// %r1  := data
	;// %r2  := p
	;// %r3  := frame_strips
	;// %r4  := 0xF0F0F0F0
	;// %r5  := strip_end
	;// %r6  := strip_h
	;// %r7  := strip_w
	;// %r10 := &chunk_id
	;// %r11 := chunk_end
	;// %r12 := codebook
	;// %r13 := flag
	;// %r14 := mask
cvid_decode_CODEBOOK_DO:				;         do {
	cmp	%r1, %r11				;           if(data >= chunk_end) break
	xjruge	cvid_decode_CHUNK_WHILE
	add	%r13, %r13				;           %psr(C), %r13 := flag <<= 1
	jruge	12					;           if(%psr(C)) {
	 ld.uh	%r15, [%r1]+				;             %r15 := y1y1y1y1 y0y0y0y0			// Luminance value 0, Luminance value 1
	 ld.uh	%r9, [%r1]+				;             %r9  := y3y3y3y3 y2y2y2y2			// Luminance value 2, Luminance value 3
	 btst	[%r10], 2				;             if(!(chunk_id & 0x0400)) data += 2	// U Chrominance value, V Chrominance value
	 jrne.d	3
	  and	%r15, %r4				;             %r15 := y1y10000 y0y00000					*delay*
	  add	%r1, 2
	 and	%r9, %r4				;             %r9  := y3y30000 y2y20000
	 rr	%r15, 4					;             %r15 := 0000y1y1 0000y0y0
	 or	%r15, %r9				;             %r15 := y3y3y1y1 y2y2y0y0
	 not	%r15, %r15				;             輝度反転
	 ld.h	[%r12], %r15				;             *codebook = y3y3y1y1 y2y2y0y0 }
;//	add	%r12, 2					;           codebook++ --------+
	sub	%r14, 1					;         } while(--mask >= 0) |
	xjrge.d	cvid_decode_CODEBOOK_DO			;                              |
	add	%r12, 2					;           <------------------+					*delay*
	xjp	cvid_decode_CODEBOOK_WHILE		;       }
	;// FRAME --------------------------------------;
cvid_decode_FRAME:					;     case 0x3000,0x3100,0x3200:
	xld.h	%r12, [cvid_driver+0]			;       %r12 := surface.w
	;// %r0  := strip
	;// %r1  := data
	;// %r2  := p
	;// %r3  := frame_strips
	;// %r4  := 0xF0F0F0F0
	;// %r5  := strip_end
	;// %r6  := strip_h
	;// %r7  := strip_w
	;// %r10 := &chunk_id
	;// %r12 := surface.w
cvid_decode_FRAME_FOR:					;       for(;;) {
	btst	[%r10], 1				;         %r13 := flag = (chunk_id & 0x0200) ? 0 : get_word()
	jrne.d	12
	 ld.w	%r13, 0					;									*delay*
	 ld.ub	%r13, [%r1]+
	 sla	%r13, 8
	 ld.ub	%r9, [%r1]+
	 or	%r13, %r9
	 sla	%r13, 8
	 ld.ub	%r9, [%r1]+
	 or	%r13, %r9
	 sla	%r13, 8
	 ld.ub	%r9, [%r1]+
	 or	%r13, %r9
	ld.w	%r14, 31				;         %r14 := mask = 31
	;// %r0  := strip
	;// %r1  := data
	;// %r2  := p
	;// %r3  := frame_strips
	;// %r4  := 0xF0F0F0F0
	;// %r5  := strip_end
	;// %r6  := strip_h
	;// %r7  := strip_w
	;// %r10 := &chunk_id
	;// %r12 := surface.w
	;// %r13 := flag
	;// %r14 := mask
cvid_decode_FRAME_DO:					;         do {
	btst	[%r10], 0				;           if(chunk_id & 0x0100) {
	jreq	18
	 add	%r13, %r13				;             %psr(C), %r13 := flag <<= 1
	 jrult	3					;             if(!%psr(C)) {
	  jp.d	cvid_decode_FRAME_SKIP			;               goto L_SKIP
	  add	%r2, 4					;               %r2  := p += 4 }					*delay*
	 sub	%r14, 1					;             if(--mask < 0) {
	 jrge	12
	  ld.ub	%r13, [%r1]+				;               %r13 := flag = get_word()
	  sla	%r13, 8
	  ld.ub	%r9, [%r1]+
	  or	%r13, %r9
	  sla	%r13, 8
	  ld.ub	%r9, [%r1]+
	  or	%r13, %r9
	  sla	%r13, 8
	  ld.ub	%r9, [%r1]+
	  or	%r13, %r9
	  ld.w	%r14, 31				;               %r14 := mask = 31 } }
;//{{共通化
	ld.ub	%r15, [%r1]+				;           %r15 := i
	sla	%r15, 1					;           %r15 := codebook
;//	add	%r15, %r0				;           ----------------------------+
;//}}共通化						;                                       |
	add	%r13, %r13				;           %psr(C), %r13 := flag <<= 1 |
	xjrult.d cvid_decode_FRAME_V4			;           if(!%psr(C)) {              |
	add	%r15, %r0				;           <---------------------------+				*delay*
;//cvid_decode_FRAME_V1:
;//{{共通化
;//	ld.ub	%r15, [%r1]+				;             %r15 := i
;//	sla	%r15, 1					;             %r15 := codebook
;//	add	%r15, %r0
;//}}共通化
	xld.uh	%r15, [%r15+512]			;             %r15 := 00000000 00000000 y3y3y1y1 y2y2y0y0
	swap	%r9, %r15				;             %r9  := y2y2y0y0 y3y3y1y1 00000000 00000000
	or	%r15, %r9				;             %r15 := y2y2y0y0 y3y3y1y1 y3y3y1y1 y2y2y0y0
	rl	%r15, 8					;             %r15 := y3y3y1y1 y3y3y1y1 y2y2y0y0 y2y2y0y0
	ld.w	%r9, %r15				;             %r9  := y3y3y1y1 y3y3y1y1 y2y2y0y0 y2y2y0y0
	and	%r9, %r4				;             %r9  := y3y30000 y3y30000 y2y20000 y2y20000
	sub	%r15, %r9				;             %r15 := 0000y1y1 0000y1y1 0000y0y0 0000y0y0
	rr	%r9, 4					;             %r9  := 0000y3y3 0000y3y3 0000y2y2 0000y2y2
	ld.w	[%r2], %r15				;             *p = [y0,y0,y1,y1]
	add	%r2, %r12				;             %r2  := p += surface.w
	ld.w	[%r2], %r15				;             *p = [y0,y0,y1,y1]
	add	%r2, %r12				;             %r2  := p += surface.w
	ld.w	[%r2], %r9				;             *p = [y2,y2,y3,y3]
	add	%r2, %r12				;             %r2  := p += surface.w
	ld.w	[%r2]+, %r9				;             *p++ = [y2,y2,y3,y3]
	xjp	cvid_decode_FRAME_NEXT
cvid_decode_FRAME_V4:					;           } else {
;//{{共通化
;//	ld.ub	%r15, [%r1]+				;             %r15 := i
;//	sla	%r15, 1					;             %r15 := codebook
;//	add	%r15, %r0
;//}}共通化
;//	ld.uh	%r15, [%r15]				;             %r15 := 00000000 00000000 r0y3r0y1 r0y2r0y0 -+
	ld.ub	%r9, [%r1]+				;             %r9  := i                                    |
	sla	%r9, 1					;             %r9  := codebook                             |
	add	%r9, %r0				;                                                          |
	ld.uh	%r9, [%r9]				;             %r9  := 00000000 00000000 r1y3r1y1 r1y2r1y0  |
	ld.uh	%r15, [%r15]				;             <--------------------------------------------+		*anti-interlock*
	xsla	%r9, 16					;             %r9  := r1y3r1y1 r1y2r1y0 00000000 00000000
	or	%r15, %r9				;             %r15 := r1y3r1y1 r1y2r1y0 r0y3r0y1 r0y2r0y0
	ld.w	%r9, %r15				;             %r9  := r1y3r1y1 r1y2r1y0 r0y3r0y1 r0y2r0y0
	and	%r9, %r4				;             %r9  := r1y30000 r1y20000 r0y30000 r0y20000
	sub	%r15, %r9				;             %r15 := 0000r1y1 0000r1y0 0000r0y1 0000r0y0
	rr	%r9, 4					;             %r9  := 0000r1y3 0000r1y2 0000r0y3 0000r0y2
	ld.w	[%r2], %r15				;             *p = [r0y0,r0y1,r1y0,r1y1]
	add	%r2, %r12				;             %r2  := p += surface.w
	ld.w	[%r2], %r9				;             *p = [r0y2,r0y3,r1y2,r1y3]
	add	%r2, %r12				;             %r2  := p += surface.w
	;
	ld.ub	%r15, [%r1]+				;             %r15 := i
	sla	%r15, 1					;             %r15 := codebook
	add	%r15, %r0
;//	ld.uh	%r15, [%r15]				;             %r15 := 00000000 00000000 r2y3r2y1 r2y2r2y0 -+
	ld.ub	%r9, [%r1]+				;             %r9  := i                                    |
	sla	%r9, 1					;             %r9  := codebook                             |
	add	%r9, %r0				;                                                          |
	ld.uh	%r9, [%r9]				;             %r9  := 00000000 00000000 r3y3r3y1 r3y2r3y0  |
	ld.uh	%r15, [%r15]				;             <--------------------------------------------+		*anti-interlock*
	xsla	%r9, 16					;             %r9  := r3y3r3y1 r3y2r3y0 00000000 00000000
	or	%r15, %r9				;             %r15 := r3y3r3y1 r3y2r3y0 r2y3r2y1 r2y2r2y0
	ld.w	%r9, %r15				;             %r9  := r3y3r3y1 r3y2r3y0 r2y3r2y1 r2y2r2y0
	and	%r9, %r4				;             %r9  := r3y30000 r3y20000 r2y30000 r2y20000
	sub	%r15, %r9				;             %r15 := 0000r3y1 0000r3y0 0000r2y1 0000r2y0
	rr	%r9, 4					;             %r9  := 0000r3y3 0000r3y2 0000r2y3 0000r2y2
	ld.w	[%r2], %r15				;             *p = [r2y0,r2y1,r3y0,r3y1]
	add	%r2, %r12				;             %r2  := p += surface.w
	ld.w	[%r2]+, %r9				;             *p++ = [r0y2,r0y3,r3y2,r3y3]
cvid_decode_FRAME_NEXT:					;           }
	sub	%r2, %r12				;           %r2  := p -= surface.w * 3
	sub	%r2, %r12
	sub	%r2, %r12
cvid_decode_FRAME_SKIP:					; L_SKIP:
	sub	%r7, 4					;           if(!(strip_w -= 4)) {
	jrne	11
	 add	%r2, %r12				;             %r2  := p += cvid_driver.surface.w * 3
	 add	%r2, %r12
	 add	%r2, %r12
	 sub	%r6, 4					;             if(!(strip_h -= 4)) {
	 jrne	5
	  add	%r1, 1					;               %r1  := data = (data + 1) & ~1
;//	  and	%r1, -2					;               ---------+
	  ext	cvid_decode_CHUNK_WHILE@rm		;                        |	ext無しのjp一命令では届きませんでした
	  jp.d	cvid_decode_CHUNK_WHILE@rl		;               exit for |
	  and	%r1, -2					;               <--------+ }						*delay*
	 ld.w	%r7, %r12				;             %r7  := strip_w = surface.w }
	sub	%r14, 1					;         } while(--mask >= 0)
	xjrge	cvid_decode_FRAME_DO
	xjp	cvid_decode_FRAME_FOR			;       }
cvid_decode_CHUNK_BREAK:				;     } }
	;// %r0  := strip
	;// %r1  := data
	;// %r2  := p
	;// %r3  := frame_strips
	;// %r4  := 0xF0F0F0F0
	sub	%r3, 1					;   if(!--frame_strips) break
	xjreq	cvid_decode_STRIP_BREAK
	xld.w	%r9, [cvid_driver+12]			;   %r9  := &frame_flags = buffer
	xld.w	%r5, 1024				;   %r5  := cnt = 1024							*anti-interlock*
	btst	[%r9], 0				;   if(frame_flags & 1) continue
	xjrne.d	cvid_decode_STRIP_FOR
	add	%r0, %r5				;   %r0  := strip += 1024						*delay*
	ld.w	%r6, %r0				;   %r6  := dst = strip + 1024
	sub	%r0, %r5				;   %r0  := src = strip
	;// %r0  := src
	;// %r1  := data
	;// %r2  := p
	;// %r3  := frame_strips
	;// %r4  := 0xF0F0F0F0
	;// %r5  := cnt
	;// %r6  := dst
	;//{{高速版memcpy()と同じアルゴリズムです。clippce.cを参照してください。
	sub	%r5, 4					;   %r5  := (cnt-4) ------+
	 ld.w	%r9, [%r0]+				;     word = *src++       |
	 ld.w	[%r6]+, %r9				;     *dst++ = word       |
	jrne.d	-2					;   while(cnt-4) <--------+ フラグ影響
	sub	%r5, 4					;   %r5  := (cnt-4) -= 4 -+						*delay*
	;//}}高速版memcpy()と同じアルゴリズムです。clippce.cを参照してください。
	xjp	cvid_decode_STRIP_FOR			; }
cvid_decode_STRIP_BREAK:
	popn	%r3
	ret
cvid_decode_END:
;//}}ここまでFRAM4領域へ転送
");

#endif /*CVID_ASM*/
