EternalWindows
AVI / 編集可能ストリーム

VFWのAVIFile関数には、ストリーム内の任意のサンプルを削除するような編集関数も含まれています。 こうした編集関数を呼び出すためには、まずAVIファイルからストリームを取得し、 そのストリームを元に編集可能ストリームを作成することになります。 元のストリーム対して編集を行うと、編集内容が直ちにファイルに反映されてしまうことが予測されるため、 ファイルに関連付けられていない編集用のストリームが必要になるのです。 編集可能ストリームを作成するには、CreateEditableStreamを呼び出します。

STDAPI CreateEditableStream(
  PAVISTREAM * ppsEditable,  
  PAVISTREAM psSource        
);

ppsEditableは、編集可能ストリームを受け取る変数のアドレスを指定します。 psSourceは、元にするストリームのハンドルを指定します。

編集に使用する関数は、その編集内容に応じて変化します。 次に示すEditStreamCutは、指定した位置のサンプルを削除します。

STDAPI EditStreamCut(
  PAVISTREAM pavi,       
  LONG * plStart,        
  LONG * plLength,       
  PAVISTREAM * ppResult  
);

paviは、編集可能ストリームのハンドルを指定します。 plStartは、削除する位置を格納した変数のアドレスを表します。 plLengthは、削除する量を格納した変数のアドレスを表します。 ppResultは、テンポラリストリームのハンドルを受け取る変数のアドレスを指定します。

テンポラリストリームとは、貼り付け時に使用するストリームのことです。 削除またはコピーを実行した場合、操作範囲のサンプルがテンポラリストリームとして格納されます。 コピーを行うには、EditStreamCopyを呼び出します。

STDAPI EditStreamCopy(
  PAVISTREAM pavi,       
  LONG * plStart,        
  LONG * plLength,       
  PAVISTREAM * ppResult  
);

paviは、編集可能ストリームのハンドルを指定します。 plStartは、コピーする位置を格納した変数のアドレスを表します。 plLengthは、コピーする量を格納した変数のアドレスを表します。 ppResultは、テンポラリストリームのハンドルを受け取る変数のアドレスを指定します。

テンポラリストリームを貼り付けるには、EditStreamPasteを呼び出します。

STDAPI EditStreamPaste(
  PAVISTREAM pavi,
  LONG * plPos,
  LONG * plLength,
  PAVISTREAM pstream,
  LONG lStart,
  LONG lLength
);

paviは、編集可能ストリームのハンドルを指定します。 plStartは、貼り付ける位置を格納した変数のアドレスを表します。 plLengthは、貼り付ける量を格納した変数のアドレスを表します。 pstreamは、貼り付け元とするテンポラリストリームのハンドルを指定します。 通常これはテンポラリストリームですが、その他のストリームで構いません。 lStartは、pstreamにおける貼り付けに使用するための位置を指定します。 lLengthは、pstreamにおける貼り付けに使用するための量を指定します。 -1を指定した場合は、存在する全てのサンプルを貼り付けることになります。

編集可能ストリームはファイルに関連付けられていないため、 これを保存する場合はAVISaveを呼び出すことになります。

HRESULT AVISave(
  LPCTSTR szFile,                
  CLSID * pclsidHandler,         
  AVISAVECALLBACK lpfnCallback,  
  int nStreams,                  
  PAVISTREAM pavi,               
  LPAVICOMPRESSOPTIONS lpOptions,  
  . . .                          
);

szFileは、保存するファイル名を指定します。 pclsidHandlerは、使用するハンドラのCLSIDを指定します。 lpfnCallbackは、圧縮の進行を確認するためのコールバック関数のアドレスを指定します。 nStreamsは、ファイルに保存するストリームの数を指定します。 paviは、保存するストリームのハンドルを指定します。 lpOptionsは、圧縮オプションを格納した変数のアドレスを指定します。 ...は、paviとlpOptionsの指定が、nStreams - 1の数だけ続してもよいことを意味しています。

AVISaveの呼び出しに必要なAVICOMPRESSOPTIONS構造体は、AVISaveOptionsで取得することができます。 この関数が表示するダイアログは、ビデオストリームの場合ならばICCompressorChoose、 オーディオストリームならばacmFormatChooseに相当します。

BOOL AVISaveOptions(
  HWND hwnd,                         
  UINT uiFlags,                      
  int nStreams,                      
  PAVISTREAM * ppavi,                
  LPAVICOMPRESSOPTIONS * plpOptions  
);

hwndは、ダイアログの親ウインドウとするハンドルを指定します。 uiFlagsは、ICCompressorChooseのuiFlagsと同じ意味を持ちます。 nStreamsは、ダイアログに表示するストリームの数を指定します。 ppaviは、ストリームの配列を指定します。 plpOptionsは、圧縮オプションの配列を指定します。 指定した配列の後に、動的に確保されたメモリが続くと思われます。

AVISaveOptionsで確保されたメモリは、AVISaveOptionsFreeで開放することになります。

LONG AVISaveOptionsFree(
  int nStreams,                     
  LPAVICOMPRESSOPTIONS *plpOptions  
);

nStreamsは、plpOptionsの要素数を指定します。 plpOptionsは、圧縮オプションの配列を指定します。

今回のプログラムは、編集可能ストリームに対して編集を行い、それを保存します。

#include <windows.h>
#include <vfw.h>

#pragma comment (lib, "vfw32.lib")

#define ID_SAVE 100
#define ID_EDIT 200
#define ID_CUT 300
#define ID_COPY 400
#define ID_PASTE 500

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId, HMENU hmenuSub);
BOOL SaveAviFile(LPTSTR lpszFileName, PAVISTREAM pavi);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW | WS_HSCROLL, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static PAVISTREAM paviEditable = NULL;
	static PAVISTREAM paviTemporary = NULL;
	static PGETFRAME  pgf = NULL;
	static int        nIndex = 0;

	switch (uMsg) {

	case WM_CREATE: {
		HMENU            hmenu;
		HMENU            hmenuPopup;
		SCROLLINFO       si;
		BITMAPINFOHEADER biOut;
		PAVISTREAM       pavi;
	
		AVIFileInit();

		if (AVIStreamOpenFromFile(&pavi, TEXT("sample.avi"), streamtypeVIDEO, 0, OF_READ, NULL) != 0) {
			MessageBox(NULL, TEXT("ファイルまたはビデオストリームが存在しません。"), NULL, MB_ICONWARNING);
			return -1;
		}

		if (CreateEditableStream(&paviEditable, pavi) !=0) {
			MessageBox(NULL, TEXT("編集可能ストリームの作成に失敗しました。"), NULL, MB_ICONWARNING);
			AVIStreamRelease(pavi);
			return -1;
		}

		AVIStreamRelease(pavi);
		
		ZeroMemory(&biOut, sizeof(BITMAPINFOHEADER));
		biOut.biSize        = sizeof(BITMAPINFOHEADER);
		biOut.biPlanes      = 1;
		biOut.biBitCount    = 24;
		biOut.biCompression = BI_RGB;

		pgf = AVIStreamGetFrameOpen(paviEditable, &biOut);
		if (pgf == NULL) {
			MessageBox(NULL, TEXT("GetFrameオブジェクトの取得に失敗しました。"), NULL, MB_ICONWARNING);
			return -1;
		}
		
		si.cbSize = sizeof(SCROLLINFO);
		si.fMask  = SIF_POS | SIF_RANGE | SIF_PAGE | SIF_DISABLENOSCROLL;
		si.nMin   = 0;
		si.nMax   = AVIStreamLength(paviEditable) - 1;
		si.nPage  = AVIStreamLength(paviEditable) / 10;
		si.nPos	  = 0;
		SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);	
	
		hmenu = CreateMenu();
		hmenuPopup = CreatePopupMenu();

		InitializeMenuItem(hmenuPopup, TEXT("切り取り(&T)"), ID_CUT, NULL);
		InitializeMenuItem(hmenuPopup, TEXT("コピー(&Y)"), ID_COPY, NULL);
		InitializeMenuItem(hmenuPopup, TEXT("貼り付け(&P)"), ID_PASTE, NULL);

		InitializeMenuItem(hmenu, TEXT("編集ストリームの保存(&F)"), ID_SAVE, NULL);
		InitializeMenuItem(hmenu, TEXT("編集(&E)"), ID_EDIT, hmenuPopup);
		
		SetMenu(hwnd, hmenu);
		
		return 0;
	}
	
	case WM_PAINT: {
		HDC                hdc;
		PAINTSTRUCT        ps;
		LPBITMAPINFOHEADER lpbmi;

		hdc = BeginPaint(hwnd, &ps);
		lpbmi = (LPBITMAPINFOHEADER)AVIStreamGetFrame(pgf, nIndex);
		if (lpbmi != NULL)
			StretchDIBits(hdc, 0, 0, lpbmi->biWidth, lpbmi->biHeight, 0, 0, lpbmi->biWidth, lpbmi->biHeight, lpbmi + 1, (LPBITMAPINFO)lpbmi, DIB_RGB_COLORS, SRCCOPY);
		EndPaint(hwnd, &ps);

		return 0;
	}
	
	case WM_COMMAND:
		if (LOWORD(wParam) == ID_SAVE)
			SaveAviFile(TEXT("edit.avi"), paviEditable);
		else if (LOWORD(wParam) == ID_CUT) {
			LONG       lStart = nIndex;
			LONG       lLength = 1;
			SCROLLINFO si;

			EditStreamCut(paviEditable, &lStart, &lLength, &paviTemporary);
			
			si.cbSize = sizeof(SCROLLINFO);
			si.fMask  = SIF_RANGE;
			GetScrollInfo(hwnd, SB_HORZ, &si);

			si.nMax = AVIStreamLength(paviEditable) - 1;
			SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);

			if (nIndex >= si.nMax)
				nIndex = si.nMax;
			InvalidateRect(hwnd, NULL, nIndex > 0 ? TRUE : FALSE);
		}
		else if (LOWORD(wParam) == ID_COPY) {
			LONG lStart = nIndex;
			LONG lLength = 1;
			
			if (AVIStreamLength(paviEditable) > 0)
				EditStreamCopy(paviEditable, &lStart, &lLength, &paviTemporary);
		}
		else if (LOWORD(wParam) == ID_PASTE) {
			LONG             lStart = nIndex;
			LONG             lLength = 1;
			BITMAPINFOHEADER biOut;

			if (paviTemporary == NULL) {
				MessageBox(NULL, TEXT("切り取りまたはコピーを行っていません。"), NULL, MB_ICONWARNING);
				return 0;
			}

			if (AVIStreamLength(paviEditable) > 0) {
				EditStreamPaste(paviEditable, &lStart, &lLength, paviTemporary, 0, -1);
				AVIStreamGetFrameClose(pgf);
					
				ZeroMemory(&biOut, sizeof(BITMAPINFOHEADER));
				biOut.biSize     = sizeof(BITMAPINFOHEADER);
				biOut.biPlanes   = 1;
				biOut.biBitCount = 24;
				pgf = AVIStreamGetFrameOpen(paviEditable, &biOut);

				InvalidateRect(hwnd, NULL, FALSE);
			}
		}
		else
			;
		return 0;

	case WM_HSCROLL: {
		SCROLLINFO si;
		int        d = 0;
		
		si.cbSize = sizeof(SCROLLINFO);
		si.fMask  = SIF_PAGE | SIF_POS | SIF_RANGE;
		GetScrollInfo(hwnd, SB_HORZ, &si);
		
		switch (LOWORD(wParam)) {
		case SB_TOP:
			d = -si.nPos;
			break;
		case SB_BOTTOM:
			d = si.nMax - si.nPos;
			break;
		case SB_LINEUP:
			d = -1;
			break;
		case SB_LINEDOWN:
			d = 1;
			break;
		case SB_PAGEUP:
			d = -1 * si.nPage;
			break;
		case SB_PAGEDOWN:
			d = si.nPage;
			break;
		case SB_THUMBTRACK:
			d = HIWORD(wParam) - si.nPos;
			break;
		default:
			return 0;
		}
		
		si.nPos += d;
		if (si.nPos > si.nMax)
			si.nPos = si.nMax;
		else if (si.nPos < si.nMin)
			si.nPos = si.nMin;
		else
			;
		SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);

		nIndex = si.nPos;
		InvalidateRect(hwnd, NULL, FALSE);
		return 0;
	}

	case WM_DESTROY:
		if (pgf != NULL)
			AVIStreamGetFrameClose(pgf);
		if (paviTemporary != NULL)
			AVIStreamRelease(paviTemporary);
		if (paviEditable != NULL)
			AVIStreamRelease(paviEditable);

		AVIFileExit();

		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

BOOL SaveAviFile(LPTSTR lpszFileName, PAVISTREAM pavi)
{
	AVICOMPRESSOPTIONS   options;
	LPAVICOMPRESSOPTIONS lpOptions;
	
	ZeroMemory(&options, sizeof(AVICOMPRESSOPTIONS));
	lpOptions = &options;

	if (!AVISaveOptions(NULL, 0, 1, &pavi, &lpOptions))
		return FALSE;

	if (AVISave(lpszFileName, NULL, NULL, 1, pavi, lpOptions) != AVIERR_OK) {
		MessageBox(NULL, TEXT("AVIファイルの作成に失敗しました"), NULL, MB_ICONWARNING);
		AVISaveOptionsFree(1, &lpOptions);
		return FALSE;
	}

	AVISaveOptionsFree(1, &lpOptions);

	return TRUE;
}

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId, HMENU hmenuSub)
{
	MENUITEMINFO mii;
	
	mii.cbSize = sizeof(MENUITEMINFO);
	mii.fMask  = MIIM_ID | MIIM_TYPE;
	mii.wID    = nId;

	if (lpszItemName != NULL) {
		mii.fType      = MFT_STRING;
		mii.dwTypeData = lpszItemName;
	}
	else
		mii.fType = MFT_SEPARATOR;

	if (hmenuSub != NULL) {
		mii.fMask   |= MIIM_SUBMENU;
		mii.hSubMenu = hmenuSub;
	}

	InsertMenuItem(hmenu, nId, FALSE, &mii);
}

このプログラムは前節のプログラムをベースにしているため、 前節と同じようにスクロールによって表示するフレームを決定することになります。 そして、メニューの「編集」から選択できる各項目は、 現在表示されているフレームに対して操作を行います。 まず、CreateEditableStreamの呼び出しを確認します。

if (AVIStreamOpenFromFile(&pavi, TEXT("sample.avi"), streamtypeVIDEO, 0, OF_READ, NULL) != 0) {
	MessageBox(NULL, TEXT("ファイルまたはビデオストリームが存在しません。"), NULL, MB_ICONWARNING);
	return -1;
}

if (CreateEditableStream(&paviEditable, pavi) !=0) {
	MessageBox(NULL, TEXT("編集可能ストリームの作成に失敗しました。"), NULL, MB_ICONWARNING);
	AVIStreamRelease(pavi);
	return -1;
}

編集可能ストリームを作成するには、元になるストリームが必要となるため、 AVIStreamOpenFromFileで取得したストリームを指定することになります。 これにより、第1引数に返された編集可能ストリームには、 元のストリーム内のサンプルがコピーされることになります。 つまり、編集可能であると同時に、元のストリームと同じように扱うことができます。 EditStreamCutの呼び出しは、次のようになっています。

LONG lStart = nIndex;
LONG lLength = 1;

EditStreamCut(paviEditable, &lStart, &lLength, &paviTemporary);

nIndexは、現在表示されているサンプルのインデックスであり、 これを削除するためlStartに指定します。 lLengthが1になっていることから、 lStartに指定したサンプルのみpaviEditableが削除されることになります。 また、削除されたサンプルはpaviTemporaryに格納されることになります。 EditStreamCopyの呼び出しは、次のようになっています。

LONG lStart = nIndex;
LONG lLength = 1;

if (AVIStreamLength(paviEditable) > 0)
	EditStreamCopy(paviEditable, &lStart, &lLength, &paviTemporary);

このコードも先の処理と同じように、現在表示されているサンプルのみをpaviTemporaryに格納しますが、 削除ではないのでpaviEditableの中身は変化しません。 また、編集可能ストリームにサンプルが存在しない場合は、関数が失敗することになっています。 次に、EditStreamPasteの処理を確認します。

LONG             lStart = nIndex;
LONG             lLength = 1;
BITMAPINFOHEADER biOut;

if (paviTemporary == NULL) {
	MessageBox(NULL, TEXT("切り取りまたはコピーを行っていません。"), NULL, MB_ICONWARNING);
	return 0;
}

if (AVIStreamLength(paviEditable) > 0) {
	EditStreamPaste(paviEditable, &lStart, &lLength, paviTemporary, 0, -1);
	AVIStreamGetFrameClose(pgf);
	
	ZeroMemory(&biOut, sizeof(BITMAPINFOHEADER));
	biOut.biSize     = sizeof(BITMAPINFOHEADER);
	biOut.biPlanes   = 1;
	biOut.biBitCount = 24;
	pgf = AVIStreamGetFrameOpen(paviEditable, &biOut);

	InvalidateRect(hwnd, NULL, FALSE);
}

EditStreamPasteでは、貼り付けに使用するテンポラリストリームが必要であるため、 事前にそれが初期化されているかどうかを確認しています。 lStartがnIndexで初期化されているため、現在表示されているサンプルに対して paviTemporaryに格納されているサンプルが張り付けられることになります。 第6引数が-1となっているため、貼り付け量は全てのサンプルということになりますが、 EditStreamCutやEditStreamCopyでコピーしたサンプルの数は1つであったため、 1を指定しても問題ありません。 貼り付けによってサンプルに変化が生じるため、 GetFrameオブジェクトを取得し直します。

編集作業が終了すれば、メニューの「編集ストリームの保存」を選択することになります。 このときに呼ばれるSaveAviFileは、次のようになっています。

BOOL SaveAviFile(LPTSTR lpszFileName, PAVISTREAM pavi)
{
	AVICOMPRESSOPTIONS   options;
	LPAVICOMPRESSOPTIONS lpOptions;
	
	ZeroMemory(&options, sizeof(AVICOMPRESSOPTIONS));
	lpOptions = &options;

	if (!AVISaveOptions(NULL, 0, 1, &pavi, &lpOptions))
		return FALSE;

	if (AVISave(lpszFileName, NULL, NULL, 1, pavi, lpOptions) != AVIERR_OK) {
		MessageBox(NULL, TEXT("AVIファイルの作成に失敗しました"), NULL, MB_ICONWARNING);
		AVISaveOptionsFree(1, &lpOptions);
		return FALSE;
	}

	AVISaveOptionsFree(1, &lpOptions);

	return TRUE;
}

AVICOMPRESSOPTIONS構造体のアドレスを敢えてLPAVICOMPRESSOPTIONSで指すようにしています。 これは少し奇妙な形ですが、このようにしなければAVISaveOptionsは失敗するように思えます。 AVICOMPRESSOPTIONS構造体は事前に0で初期化するだけで十分ですが、 dwFlagsにAVICOMPRESSF_VALIDを指定すると表示されるダイアログに既定値を指定することができるようになります。 たとえば、fccHandlerに既定で選択状態にしたいコーデックのFOURCCを指定することができます。

クリップボードからの貼り付け

クリップボードにビットマップが格納されている場合、 そのビットマップのサイズやビット数によっては、 EditStreamPasteの第4引数に指定することができます。 第4引数の型はPAVISTREAM型ですが、 AVIMakeStreamFromClipboardを呼び出せば、 クリップボードのデータのハンドルからストリームを作成することができます。

HANDLE hData;

OpenClipboard(hwnd);
hData = GetClipboardData(CF_DIB);
if (hData == NULL) {
	CloseClipboard();
	return 0;
}

if (AVIMakeStreamFromClipboard(CF_DIB, hData, &paviTemporary) != 0) {
	CloseClipboard();
	return 0;
}

EditStreamPaste(paviEditable, &lStart, &lLength, paviTemporary, 0, -1);

CloseClipboard();

まず、OpenClipboardを呼び出してクリップボードをオープンし、 GetClipboardDataを呼び出してクリップボードに格納されているデータを取得します。 CF_DIBを指定しているため、ビットマップが格納されていない場合は、NULLが返ることになります。 AVIMakeStreamFromClipboardにCF_DIBとデータのハンドルを指定すれば、 ビットマップが1つ格納された新しいストリームが第3引数に返されます。 後はこれをEditStreamPasteの第4引数に指定すれば、 クリップボードからビットマップを貼り付けたように見えることになります。 貼り付けるビットマップのサイズやビット数が、 貼り付け先とわずかにでも異なる場合は失敗する点に注意してください。



戻る