//---------------------------------------------------------------------------
// Jpeg -> AVI出力処理
//	Copyright (C) 2009,2011 by AIG-Soft
//---------------------------------------------------------------------------
// 音声は未対応(構造そのものは解っているが、時間同期が必須になるので)
//---------------------------------------------------------------------------
// MS-MedeaPlayerではデフォルトで字幕がOFFになっているので
//	右クリック-再生-～字幕-利用できる場合はON
// を設定する必要がある。QuickTimeと異なり、表示位置は制御できない。
// AVIの?Long/?Wordの数値格納順は86系(L->H)である（Windowsで開発されたから）

#include <string.h>	// strcpy()/memcpy()
#include "jpeg.h"
#include "qalib.h"
#include "avi.h"
#import "RootViewController.h"

//---------------------------------------------------------------------------
/*
・画像は出力できたが最後のidx1が書けない場合、不完全なファイルになるので注意
・必要ならファイルを削除する
*/
//---------------------------------------------------------------------------
// 構造体&ワーク
//---------------------------------------------------------------------------

enum {
	TRACK_VIDEO	=0
#ifndef	NOAVITEXT
,	TRACK_TEXT
#endif
//,	TRACK_SOUND	// 予約
//
,	TRACKS		// 総トラック数;AVIでは「ストリーム」と呼んでいる
};

// ファイルで1つだけ
static	ULong			maxPics;
static	RIFF			riff;
static	LIST			hdrl;
static	LIST			movi;
static	AVIMAINHEADER	avih;
static	AVI_STRL_MJPG	strlMjpg;
#ifndef	NOAVITEXT
static	AVI_STRL_TEXT	strlText;
#endif

// 各画像サイズテーブル
// 画像属性文字列はサイズ固定なので、画像サイズのみあればidx1が構築出来る
static	ULong	*SizeTbl; // malloc()による確保

// AVI用APP0格納ワーク
JPEGSOIAPP0	JpegSOIAPP0;

#ifndef	NOAVITEXT
// 画像属性文字列用ワーク
typedef	struct { // (境界整合に注意)
	CHUNK	text;
	UByte	atrb[LEN_ATRBSTR_AVI];
} CHUNKATRBMES;
#define	SZ_CHUNKATRBMES	(sizeof(CHUNKATRBMES))
static	CHUNKATRBMES	AtrbMes;
#endif

//---------------------------------------------------------------------------
// 内部関数
//---------------------------------------------------------------------------
// !!error!!
// writeDataは、エラーが発生すると例外が発生する
// staticな関数では処理せず、グローバル関数に任せている
//---------------------------------------------------------------------------
// ファイル書き出し関数群
// 68対応のため独立化(iOS上では無駄かもしれない)
// 変換種類が多いので、個別処理する
//---------------------------------------------------------------------------;

// CHUNK構造体のファイル出力
void	fwriteCHUNK(CHUNK *chunk,NSFileHandle *ofp)
{
	if (fBigEndianness) {
		for (NSUInteger i=0;i<2;i++) {
			// 変換が必要な数値を一時的に変換する
			cnv68L(&chunk->fcc);
			cnv68L(&chunk->Size);
			if (i==1) break; // 逆変換して終了
			[ofp writeData:[NSData dataWithBytes:chunk length:SZ_CHUNK]];
		}
	} else {
		// fwrite((UByte *)chunk,SZ_CHUNK,1,ofp);
	//	nslog(@"chunk=%p(%d)",chunk,SZ_CHUNK);
		[ofp writeData:[NSData dataWithBytes:chunk length:SZ_CHUNK]];
	}
}

// RIFF構造体のファイル出力
static
void	fwriteRIFF(RIFF *riff,NSFileHandle *ofp)
{
	if (fBigEndianness) {
		for (NSUInteger i=0;i<2;i++) {
			// 変換が必要な数値を一時的に変換する
			cnv68L(&riff->fcc);
			cnv68L(&riff->fileSize);
			cnv68L(&riff->fileType);
			if (i==1) break; // 逆変換して終了
			[ofp writeData:[NSData dataWithBytes:riff length:SZ_RIFF]];
		}
	} else {
		// fwrite((UByte *)(riff),SZ_RIFF,1,ofp);
		[ofp writeData:[NSData dataWithBytes:riff length:SZ_RIFF]];
	}
}

// LIST構造体のファイル出力
static
void	fwriteLIST(LIST *list,NSFileHandle *ofp)
{
	if (fBigEndianness) {
		for (NSUInteger i=0;i<2;i++) {
			// 変換が必要な数値を一時的に変換する
			cnv68L(&list->fcc);
			cnv68L(&list->listSize);
			cnv68L(&list->listType);
			if (i==1) break; // 逆変換して終了
			[ofp writeData:[NSData dataWithBytes:list length:SZ_LIST]];
		}
	} else {
		// fwrite((UByte *)(list),SZ_LIST,1,ofp);
		[ofp writeData:[NSData dataWithBytes:list length:SZ_LIST]];
	}
}

// AVIMAINHEADER構造体のファイル出力
static
void	fwriteAVIMAINHEADER(AVIMAINHEADER *avih,NSFileHandle *ofp)
{
	if (fBigEndianness) {
		for (NSUInteger i=0;i<2;i++) {
			// 変換が必要な数値を一時的に変換する
			cnv68L(&avih->fcc);
			cnv68L(&avih->Size);
			cnv68L(&avih->dwMicroSecPerFrame);
			cnv68L(&avih->dwMaxBytesPerSec);
			cnv68L(&avih->dwFlags);
			cnv68L(&avih->dwTotalFrames);
			cnv68L(&avih->dwInitialFrames);
			cnv68L(&avih->dwStreams);
			cnv68L(&avih->dwSuggestedBufferSize);
			cnv68L(&avih->dwWidth);
			cnv68L(&avih->dwHeight);
			if (i==1) break; // 逆変換して終了
			[ofp writeData:[NSData dataWithBytes:avih length:SZ_AVIMAINHEADER]];
		}
	} else {
		// fwrite((UByte *)(avih),SZ_AVIMAINHEADER,1,ofp);
		[ofp writeData:[NSData dataWithBytes:avih length:SZ_AVIMAINHEADER]];
	}
}

// AVISTREAMHEADER構造体のファイル出力
static
void	fwriteAVISTREAMHEADER(AVISTREAMHEADER *strh,NSFileHandle *ofp)
{
	if (fBigEndianness) {
		for (NSUInteger i=0;i<2;i++) {
			// 変換が必要な数値を一時的に変換する
			cnv68L(&strh->fcc);
			cnv68L(&strh->Size);
			cnv68L(&strh->fccType);
			cnv68L(&strh->fccHandler);
			cnv68L(&strh->dwFlags);
			cnv68W(&strh->wPriority);
			cnv68W(&strh->wLanguage);
			cnv68L(&strh->dwInitialFrames);
			cnv68L(&strh->dwStart);
			cnv68L(&strh->dwLength);
			cnv68L(&strh->dwSuggestedBufferSize);
			cnv68L(&strh->dwQuality);
			cnv68L(&strh->dwSampleSize);
			if (i==1) break; // 逆変換して終了
			[ofp writeData:[NSData dataWithBytes:strh length:SZ_AVISTREAMHEADER]];
		}
	} else {
		// fwrite((UByte *)(strh),SZ_AVISTREAMHEADER,1,ofp);
		[ofp writeData:[NSData dataWithBytes:strh length:SZ_AVISTREAMHEADER]];
	}
}

// STREAMFORMAT_MJPEG構造体のファイル出力
static
void	fwriteSTRF_MJPG(STREAMFORMAT_MJPEG *mjpg,NSFileHandle *ofp)
{
	if (fBigEndianness) {
		for (NSUInteger i=0;i<2;i++) {
			// 変換が必要な数値を一時的に変換する
			cnv68L(&mjpg->fcc);
			cnv68L(&mjpg->Size);
			cnv68L(&mjpg->mjpg.biSize);
			cnv68L(&mjpg->mjpg.biWidth);
			cnv68L(&mjpg->mjpg.biHeight);
			cnv68W(&mjpg->mjpg.biPlanes);
			cnv68W(&mjpg->mjpg.biBitCount);
			cnv68L(&mjpg->mjpg.biCompression);
			cnv68L(&mjpg->mjpg.biSizeImage);
			if (i==1) break; // 逆変換して終了
			[ofp writeData:[NSData dataWithBytes:mjpg length:SZ_STREAMFORMAT_MJPEG]];
		}
	} else {
		// fwrite((UByte *)(mjpg),SZ_STREAMFORMAT_MJPEG,1,ofp);
		[ofp writeData:[NSData dataWithBytes:mjpg length:SZ_STREAMFORMAT_MJPEG]];
	}
}

static
void	fwriteAVIINDEXENTRY(AVIINDEXENTRY *idxent,NSFileHandle *ofp)
{
	if (fBigEndianness) {
		for (NSUInteger i=0;i<2;i++) {
			// 変換が必要な数値を一時的に変換する(i=1のときは逆変換=戻し)
			cnv68L(&idxent->dwChunkId);
			cnv68L(&idxent->dwFlags);
			cnv68L(&idxent->dwOffset);
			cnv68L(&idxent->dwSize);
			if (i==1) break; // 逆変換して終了
			// ファイル出力
			[ofp writeData:[NSData dataWithBytes:idxent length:SZ_AVIINDEXENTRY]];
		}
	} else {
		// fwrite((UByte *)(idxent),SZ_AVIINDEXENTRY,1,ofp);
		[ofp writeData:[NSData dataWithBytes:idxent length:SZ_AVIINDEXENTRY]];
	}
}

//---------------------------------------------------------------------------;

// ヘッダー出力
static
void AVIWriteHeader(NSFileHandle *ofp)
{
	printFuncs(@"ofp=%p",ofp);
	// 全体
	fwriteRIFF(&riff,ofp);
	fwriteLIST(&hdrl,ofp);
	fwriteAVIMAINHEADER(&avih,ofp);
	// 画像
	fwriteLIST(&strlMjpg.list,ofp);
	fwriteAVISTREAMHEADER(&strlMjpg.strh,ofp);
	fwriteSTRF_MJPG(&strlMjpg.strf,ofp);
	#ifndef	NOAVITEXT
	// テキスト
	fwriteLIST(&strlText.list,ofp);
	fwriteAVISTREAMHEADER(&strlText.strh,ofp);
	fwriteCHUNK(&strlText.strf,ofp);
	#endif
	// 本体前
	fwriteLIST(&movi,ofp);
}

//---------------------------------------------------------------------------;
// 公開関数
//---------------------------------------------------------------------------;

// 必要ワークの確保
void InitAVI(ULong maxPics_)
{
	printFuncs(@"maxPics=%lu",maxPics_);
	if (SizeTbl==NULL) {
		maxPics=maxPics_;
		SizeTbl=(ULong *)malloc(SZ_LONG*maxPics_);
		if (SizeTbl==NULL) {
			nslog(@"メモリが確保できません");
			// 本当はここにエラー処理が必要
		}
	} // 念のため、前の開放が出来てないときは再確保しない
#ifdef DEBUG
	else {
		nslog(@"再確保できない");
	}
#endif
}

// maxPicsを返す

ULong GetMaxPics(void)
{
	nslog(@"maxPics=%lu",maxPics);
	return(maxPics);
}

// 必要ワークの開放

void DeallocAVI(void)
{
	printFunc();
	if (SizeTbl!=NULL) {
		free(SizeTbl);
		SizeTbl=NULL;
	}
}

//---------------------------------------------------------------------------;

// AVIファイルヘッダーを書き込む
// imageSize : JPEG画像の縦横ドット数;横幅は4の倍数のみ
// tScale : 再生速度（秒枚数）;秒60枚なら60,0=秒1枚
// max1JPEGsize : JPEG画像1枚の最大サイズ(B)；だいたいで良い(80KB=80*1024とか)
// リターン値：0=エラー , 1=OK
void StartAVI(NSFileHandle *ofp,CGRect imageSize,ULong tScale,ULong max1JPEGsize)
{
	nslog(@"StartAVI");
	UWord wx,wy;
	wx=(UWord)imageSize.size.width;
	wy=(UWord)imageSize.size.height;
	nslog(@"wx,wy=%d,%d",wx,wy);
	
	// RIFF
	riff.fcc			=FOURCC_RIFF;		// RIFF
	riff.fileSize		=0;					// 出力したデータ全体のサイズ；後で入れるので、とりあえず0
	riff.fileType		=filetypeAVI;		// AVI 
	// LIST hdrl
	hdrl.fcc			=FOURCC_LIST;		// LIST
	hdrl.listType		=listtypeAVIHEADER;	// hdlr
	hdrl.listSize		=SZ_FOURCC+SZ_AVIMAINHEADER+SZ_AVI_STRL_MJPG	// 'hdlr'分も必要
#ifndef	NOAVITEXT
														+SZ_AVI_STRL_TEXT
#endif
	;
	// AVIMAINHEADER avih
	avih.fcc					=ckidAVIMAINHDR;		// 'avih'
	avih.Size					=DataSize(AVIMAINHEADER);
	if (tScale==0) tScale=1;	// 0は秒1枚にする
	avih.dwMicroSecPerFrame		=Sec2Micro(1)/tScale;	// 1フレーム時間(μ秒単位);60fps=1/60秒=16667	15fps=60fps*4=66666,30fps=33333
														// MediaPlayerでの再生速度はこれではない
	avih.dwMaxBytesPerSec		=tScale*max1JPEGsize;	// ファイルの概算秒あたりの最大バイト数
	avih.dwPaddingGranularity	=0;			// データのアライメント	バイト単位	セクター境界に置くなら512でも良いかもしれない
	avih.dwFlags				=AVIF_HASINDEX|AVIF_WASCAPTUREFILE;	// フラグ;idx1がある
	avih.dwTotalFrames			=0;			// 全フレーム数;登録しながらカウントする
	avih.dwInitialFrames		=0;			// 再生開始フレーム	0=先頭から
	avih.dwStreams				=TRACKS;	// ファイル内のストリーム数
	avih.dwSuggestedBufferSize	=max1JPEGsize+SZ_CHUNK; // 1画像(CHUNK単位)を読み取るためのバッファサイズ(画像とテキストの大きい方を使う)
	avih.dwWidth				=wx;	// 画面幅	ピクセル単位
	avih.dwHeight				=wy;	// 画面高さ	ピクセル単位
	// avih.dwReserved[4]は全0であることを想定している(static変数なので)
	
	// 画像strl
	strlMjpg.list.fcc			=FOURCC_LIST;			// LIST
	strlMjpg.list.listSize		=DataSize(AVI_STRL_MJPG);
	strlMjpg.list.listType		=listtypeSTREAMHEADER;	// strl
	//
	strlMjpg.strh.fcc			=ckidSTREAMHEADER;	// strh
	strlMjpg.strh.Size			=DataSize(AVISTREAMHEADER);
	strlMjpg.strh.fccType		=streamtypeVIDEO;	// ストリームに含まれるデータのタイプ
	strlMjpg.strh.fccHandler	=codectypeMSMJPEG2;	// コーデックタイプ
	strlMjpg.strh.dwFlags		=0;		// データストリームに対するフラグ
	strlMjpg.strh.wPriority		=0;		// ストリームタイプの優先順位を指定する
	strlMjpg.strh.wLanguage		=0;		// 言語
	strlMjpg.strh.dwInitialFrames=0;	// 再生開始フレーム
	strlMjpg.strh.dwScale		=avih.dwMicroSecPerFrame;
										// dwRateと共に、MediaPlayerでの再生速度となる
	strlMjpg.strh.dwRate		=Sec2Micro(1);// dwRate/dwScale = frames/秒
	if ((strlMjpg.strh.dwRate%strlMjpg.strh.dwScale)==0) strlMjpg.strh.dwRate--;
										// dwRateとdwScaleは互いに素(割り切れない)数にする。割り切れるときは調整する
	strlMjpg.strh.dwStart		=0;		// ストリーム開始時間	音声の場合、ストリームが始まる前に再生される無音フレームの数を示す
	strlMjpg.strh.dwLength		=0;		// dwRateとdwScaleで定義される単位でのストリームのサイズ=総フレーム数=avih.dwTotalFrames(後で代入する)
	strlMjpg.strh.dwSuggestedBufferSize	// このストリームを読み取るために必要なバッファの大きさを指定する
								=avih.dwSuggestedBufferSize;
	strlMjpg.strh.dwQuality		=AVISF_QUALITY_MAX;	// ストリームデータ品質(重要ではない)
	strlMjpg.strh.dwSampleSize	=0;		// データの1サンプルのサイズ＝ストリームの最小単位（これ以上細分すべきでない単位）
	// rcFrameは全0であることを想定している(static変数なので)
	//
	strlMjpg.strf.fcc					=ckidSTREAMFORMAT;	// strf
	strlMjpg.strf.Size					=DataSize(STREAMFORMAT_MJPEG);
	strlMjpg.strf.mjpg.biSize			=SZ_BITMAPINFOHEADER;
	strlMjpg.strf.mjpg.biWidth			=wx;	// 画像横ドット数(4の倍数のみ)
	strlMjpg.strf.mjpg.biHeight			=wy;	// 画像縦ドット数
	strlMjpg.strf.mjpg.biPlanes			=1;		// プレーン数=1
	strlMjpg.strf.mjpg.biBitCount		=8*3;	// bit数(24bit)
	strlMjpg.strf.mjpg.biCompression	=BI_COMPRESS_MJPG;	// 画像圧縮方式
	strlMjpg.strf.mjpg.biSizeImage		=wx*wy*strlMjpg.strf.mjpg.biBitCount/8;
		// イメージバッファーのサイズ	0xe1000=640*480*3(ビットマップ上の連続する3バイト=24bitが1画素
	strlMjpg.strf.mjpg.biXPelsPerMeter	=0;		// 0
	strlMjpg.strf.mjpg.biYPelsPerMeter	=0;		// 0
	strlMjpg.strf.mjpg.biClrUsed		=0;		// 0=RGBQUADを使わない
	strlMjpg.strf.mjpg.biClrImportant	=0;		// 0

#ifndef	NOAVITEXT
	// テキストstrl（MSの用語では「字幕」）
	strlText.list.fcc			=FOURCC_LIST;			// LIST
	strlText.list.listSize		=DataSize(AVI_STRL_TEXT);
	strlText.list.listType		=listtypeSTREAMHEADER;	// strl
	//
	strlText.strh.fcc			=ckidSTREAMHEADER;	// strh
	strlText.strh.Size			=DataSize(AVISTREAMHEADER);
	strlText.strh.fccType		=streamtypeTEXT;	// ストリームに含まれるデータのタイプ	
	strlText.strh.fccHandler	=codectypeTEXT;		// コーデックタイプ;TEXTにはコーデック不要
	strlText.strh.dwFlags		=0;		// データストリームに対するフラグ
	strlText.strh.wPriority		=0;		// ストリーム タイプの優先順位を指定する
	strlText.strh.wLanguage		=0;		// 言語
	strlText.strh.dwInitialFrames=0;	// 音声が画像からどのくらい遅延するか
	strlText.strh.dwScale		=strlMjpg.strh.dwScale;	// 画像と同期させるため
	strlText.strh.dwRate		=strlMjpg.strh.dwRate;
	strlText.strh.dwStart		=0;		// ストリーム開始時間	音声の場合、ストリームが始まる前に再生される無音フレームの数を示す
	strlText.strh.dwLength		=0;		// dwRateとdwScaleで定義される単位でのストリームのサイズ＝総フレーム数
	strlText.strh.dwSuggestedBufferSize	// このストリームを読み取るために必要なバッファの大きさを指定する
								=SZ_CHUNKATRBMES;
	strlText.strh.dwQuality		=AVISF_QUALITY_MAX;	// ストリームデータ品質(重要ではない)
	strlText.strh.dwSampleSize	=1;		// データの 1 サンプルのサイズ＝ストリームの最小単位（これ以上細分すべきでない単位）のバイト数
	// rcFrameは全0であることを想定している(static変数なので)
	//
	strlText.strf.fcc			=ckidSTREAMFORMAT;	// strf
	strlText.strf.Size			=0;	// ここに付属情報はない
#endif
	
	// ストリームデータ
	movi.fcc		=FOURCC_LIST;		// LIST
	movi.listType	=listtypeAVIMOVIE;	// movi
	movi.listSize	=SZ_FOURCC;			// 出力時に加算する('movi'分も必要)
	
	// ワーク初期化(固定内容分)
	// ストリーム番号を変えるときはここを変更する
	JpegSOIAPP0.soi		=SOI;	// SOI
	JpegSOIAPP0.app0	=APP0;	// APP0
	JpegSOIAPP0.app0Size=SZ_JPEGSOIAPP0-SZ_WORD*2;	// SOI+.app0を除くAPP0のサイズ(B範囲)
	memcpy(JpegSOIAPP0.symbol,SYMBOL_AVI_APP0,LEN_SYMBOL_AVI_APP0);
	if (!fBigEndianness) { // APP0は68形式
		cnv68W(&JpegSOIAPP0.soi);
		cnv68W(&JpegSOIAPP0.app0);
		cnv68W(&JpegSOIAPP0.app0Size);
	} else { // 68形式時
		// APP0内サイズは86形式
		cnv68W(&JpegSOIAPP0.JpegSizeWithPack);
		cnv68W(&JpegSOIAPP0.JpegSize);
	}
	// 以後このメンバーは値として参照してはいけない(逆格納されているから)

#ifndef NOAVITEXT
	// 画像属性文字列
	AtrbMes.text.fcc	= fourcc('0','1',ckidTEXTH,ckidTEXTL);	// ストリーム1
	AtrbMes.text.Size	= LEN_ATRBSTR_AVI;	// サイズは固定
	memset(AtrbMes.atrb,0,LEN_ATRBSTR_AVI);	// 主にデバッグのため
#endif
	
	// ヘッダー出力(位置決めのための仮出力)
	@try {
		AVIWriteHeader(ofp);
	} // @try
	@catch (NSException *exception) { // !!error!!
		nslog(@"ヘッダー出力エラー(StartAVI):exception=%@",exception);
		FileError(@"ERR_TITLE_FILE",@"ERR_MES_FILE",exception);
	}
	nslog(@"StartAVI end");
}

//---------------------------------------------------------------------------;

// JPEGの1枚画像をそのままAVIファイルに書き出す
// ofp		最後にファイル先頭部に戻すので'+'が必須
// imgdata	JPEG1画像(オンメモリ上)
static
ULong Jpeg2AVI(NSFileHandle *ofp,NSData *imgdata)
{
UByte fbyte;
CHUNK frame;
	nslog(@"Jpeg2AVI:ofp=%p",ofp);
	ULong size=imgdata.length; // 本当はNSUInteger
	size-=SZ_WORD;	// 元のSOI分を引いておく
	
	fbyte=(size&1);	// サイズが奇数バイトの時は1になる。SZ_JPEGSOIAPP0は偶数なので、画像本体だけで判定すればよい。
	nslog(@"size=%lu,fbyte=%d",size,fbyte);
	JpegSOIAPP0.JpegSize=size+SZ_JPEGSOIAPP0; // SOI～EOIのサイズ;SOI+APP0分のサイズを加える必要がある
	frame.Size =
	JpegSOIAPP0.JpegSizeWithPack=JpegSOIAPP0.JpegSize+(ULong)fbyte;	// 境界整合を含むサイズ;↑が奇数なら+1,偶数なら+0
	
	// 出力
	// 00dc
	nslog(@"00dc:frame=%p",&frame);
	frame.fcc=ckidVIDEOc;
	fwriteCHUNK(&frame,ofp);
	
	// SOI-APP0
	// fwrite((UByte *)&JpegSOIAPP0,SZ_JPEGSOIAPP0,1,ofp);
	nslog(@"SOI-App0");
	[ofp writeData:[NSData dataWithBytes:&JpegSOIAPP0 length:SZ_JPEGSOIAPP0]];

	// 画像本体
	nslog(@"画像本体");
	[ofp writeData:[imgdata subdataWithRange:NSMakeRange(2,size)]]; // 先頭のSOIを除いて出力する
	if (fbyte) {
		// 境界整合分;AVIは画像データそのものが偶数バイト境界になければならない。(QuickTimeは不明)
		NSData *byte1=[[NSData alloc]initWithBytes:"\0" length:1]; // [0]=0のデータ１つ
		[ofp writeData:byte1];
	}
	// 画像サイズ（オフセットは後で計算するので不要）
	return(frame.Size);
}

//---------------------------------------------------------------------------;

// 画像サイズ登録
static
void Jpeg2AVITable(ULong size)
{
	nslog(@"avih.dwTotalFrames=%lu",avih.dwTotalFrames);
	SizeTbl[avih.dwTotalFrames]=size;	// 画像サイズ（オフセットは後で計算するので不要）
}

//---------------------------------------------------------------------------;

// 属性文字列をAVIファイルに書き出す
// 必ず、画像を書き出した後に呼び出すこと(idx1出力順がそうなっているから)
// リターン値: 0=エラー , 1=OK
static
void Text2AVI(NSFileHandle *ofp,NSString *mes)
{
#ifndef	NOAVITEXT
	nslog(@"Text2AVI:%@",mes);
//	strcpy((char *)AtrbMes.atrb,[mes cStringUsingEncoding:NSUTF8StringEncoding]);	// EOSを含む
	// AVIのテキストはSHIFT-JISでなくてはならない
	strcpy((char *)AtrbMes.atrb,[mes cStringUsingEncoding:NSShiftJISStringEncoding]);	// EOSを含む
	// ファイルへの書き出しは固定長
	// fwrite((UByte *)&AtrbMes,SZ_CHUNKATRBMES,1,ofp);
	[ofp writeData:[NSData dataWithBytes:&AtrbMes length:SZ_CHUNKATRBMES]];
#endif
}

//---------------------------------------------------------------------------;

// フレーム数を加算（とオーバーフローチェック)
static
BOOL AddFrames(void)
{
	return(++avih.dwTotalFrames>=maxPics);
}

//---------------------------------------------------------------------------;

// AVIのファイル終端を書き込む(ヘッダーも修正する)
// リターン値：0=エラー , 1=OK
void EndAVI(NSFileHandle *ofp)
{
ULong i;
AVIINDEXENTRY idxent;
CHUNK	idx1;
	nslog(@"EndAVI:ofp=%p",ofp);
	// idx1の出力
	// 各画像サイズ:SizeTbl[]
	// 画像属性文字列:LEN_ATRBSTR(固定)
	idx1.fcc =ckidAVIINDEX;	// idx1
	idx1.Size=SZ_AVIINDEXENTRY*TRACKS*avih.dwTotalFrames;	// 以下に出力する実体のサイズ
	fwriteCHUNK(&idx1,ofp);
	idxent.dwOffset	=SZ_LONG; // ストリームデータ先頭位置(画像第1フレームのCHUNK.Sizeの位置=0なので、データ本体は＋SZ_LONG)
	for (i=0;i<avih.dwTotalFrames;i++) {
		// 映像トラック
		idxent.dwChunkId=ckidVIDEOc;
		idxent.dwFlags	=AVIIF_KEYFRAME;
		idxent.dwSize	=SizeTbl[i];
		fwriteAVIINDEXENTRY(&idxent,ofp);
		idxent.dwSize  +=SZ_CHUNK;		// 00dc分
		idxent.dwOffset+=idxent.dwSize;	// 次の位置
		movi.listSize  +=idxent.dwSize;	// ストリーム全体サイズ
#ifndef	NOAVITEXT
		// 属性文字列トラック
		// Cygwinでは境界整合のためSZ_CHUNKATRBMES!=SZ_CHUNK+LEN_ATRBSTR_AVIになっているのでSZ_CHUNKATRBMESを信用する
		idxent.dwChunkId=AtrbMes.text.fcc;
		idxent.dwFlags	=0;
		idxent.dwSize	=SZ_CHUNKATRBMES-SZ_CHUNK; // 固定
		fwriteAVIINDEXENTRY(&idxent,ofp);
		idxent.dwSize  +=SZ_CHUNK;		// 00tx分
		idxent.dwOffset+=idxent.dwSize;	// 次の位置
		movi.listSize  +=idxent.dwSize;	// ストリーム全体サイズ
		//printf("SZ_CHUNKATRBMES=%d,LEN_ATRBSTR_AVI+SZ_CHUNK=%d\n",SZ_CHUNKATRBMES,SZ_CHUNK+LEN_ATRBSTR_AVI);
#endif
	} // for

	// ヘッダー内容確定
	riff.fileSize=[ofp offsetInFile]-SZ_CHUNK;	// 出力したriff.Size以降のサイズ
#ifndef	NOAVITEXT
	strlText.strh.dwLength=
#endif
	strlMjpg.strh.dwLength=avih.dwTotalFrames;
	//
	@try {
		[ofp seekToFileOffset:0]; // 先頭に戻す
		AVIWriteHeader(ofp);
	} // @try
	@catch (NSException *exception) { // !!error!!
		nslog(@"ヘッダー出力エラー(EndAVI):exception=%@",exception);
		FileError(@"ERR_TITLE_FILE",@"ERR_MES_FILE",exception);
	}
	nslog(@"EndAVI end");
}

//---------------------------------------------------------------------------;

// imgdata	JPEG1画像(データそのもの)
// ofp		最後にファイル先頭に戻すので'+'が必須
// mes		属性文字列:なしのときはNULLにしておくこと
BOOL AviMain(NSFileHandle *ofp,NSData *imgdata,NSString *mes)
{
	nslog(@"ofp=%@",ofp);

	@try {
		// 画像出力
		ULong size=Jpeg2AVI(ofp,imgdata);	// これがエラーを発生している:-[NSConcreteData subdataWithRange:]: range {2, 4294967294} causes integer overflow
			
		// 画像サイズ登録
		Jpeg2AVITable(size);
		
		// 属性文字列登録(画像直後)
		Text2AVI(ofp,mes);
	} // @try
	@catch (NSException *exception) { // !!error!!
		nslog(@"AviMain:exception=%@",exception);
		FileError(@"ERR_TITLE_FILE",@"ERR_MES_FILE",exception);
	}
	// フレーム数加算
	nslog(@"AviMain end");
	return (AddFrames());
}

//---------------------------------------------------------------------------;
