API

멀티미디어 프로그래밍 참조

디버그정 2010. 4. 9. 12:28

멀티미디어 프로그래밍


1 WAV 파일을 작동하자

20

멀티미디어란 과연 어디까지이고 어떤 것들이 있는 것일까?

멀티미디어란 동영상, 음악, 텍스트를 자유자재로 이용할 수 있는 매체라고 말할 수 있습니다. 여기에 통신, TV, HOME AUTOMATION 등 각종 기능을 내재하고 상호 대화를 할 수 있는 컴퓨터를 말하기도 합니다.

윈도 95에서는 쉽게 멀티미디어 프로그래밍을 할 수가 있습니다. 윈도 95에서는 MCI(Media control Interface) 서비스를 제공하기 때문에 프로그래머는 MCI만 조절함으로써 영상과 음성을 조작할 수 있습니다. MCI에서 제공하는 매체들은 음성 파장 파일(*.WAV), CD-AUDIO, 동영상 파일(*.AVI), 각종 비디오 매체 등등 입니다. 이런 매체들을 작동할 때 프로그래머가 사용하는 라이브러리는 MMSYS- TEM입니다. MMSYSTEM은 Low-Level 함수와 High-Level 함수 2개로 나눌 수 있습니다.

멀티미디어 지원 프로그램


멀티미디어 프로그램



















하이레벨 오디오 비디오

서비스




하이레벨 오디오, 비디오 서비스(MCI, SND)














사운드 카드, 비디오 카드 제조 업체가 제공하는 MCI 디바이스 드라이버
















로레벨 오디오 비디오

서비스


로레벨 오디오, 비디오 서비스(avi, midi, wave)











사운드 카드, 비디오 카드 제조업체가 제공하는 로레벨 오디오 디바이스 드라이버








그림에서 보듯이 멀티미디어 프로그램에서 사운드나 비디오 등을 구동시키기 위한 명령을 내리면 그 명령은 각 회사에서 제공하는 멀티미디어 디바이스 드라이버에 전달되고 디바이스 드라이버는 선택된 멀티미디어 매체를 구동시킵니다.

예를 든다면 음성을 스피커로 출력시킬 때 하이레벨 오디오 서비스에 명령을 내리면 하이레벨 오디오 서비스는 로레벨 오디오 서비스에 명령을 전달하고 로레벨 오디오 서비스는 사운드 카드 회사에서 제공하는 오디오 디바이스 드라이버에 명령을 전달하며, 다시 오디오 디바이스 드라이버는 시스템에 부착되어 있는 사운드 카드를 구동시켜 음성이 나오게 됩니다.

프로그래머는 단지 공통적으로 사용하는 일반 명령 함수를 이용하여 디바이스 드라이버에 명령만 전달하면 되고 나머지 부분은 디바이스 드라이버가 담당하게 됩니다.

새로 사운드 카드나 비디오 카드를 사면 윈도용 드라이버를 주죠? 그리고 그것을 윈도에 설치하잖아요. 그런 드라이버들이 바로 로레벨, 하이레벨 미디어 서비스 드라이버입니다.

WAV 파일을 작동하자

1 WAV 파일을 작동하자

윈도 95 안에서 소리를 지원하는 파형 사운드 파일은 보통 WAVE 파일이라고 합니다.

WAVE 파일을 출력하는 방법은 3가지로 볼 수 있습니다.

① SndPlaySound() 등 간단한 함수 사용

② MCI 명령 사용

③ 로레벨 오디오 서비스 함수 사용

①로 실행할 때는 SndPlaySound("test.wav",SND_ASYNC)로 하면 TEST.WAV 파일이 자동적으로 출력됩니다. ②의 경우는 약간 복잡하기는 하지만 몇 가지 함수를 사용하여 간단하게 출력할 수 있습니다. ③과 같은 경우 진짜 복잡하다고 볼 수 있지요. 그럼 각각 어떤 경우에 사용할까요? 한 10초도 안 되는 짧은 음성의 재생시에는 ①만 사용하여 간단하게 출력할 수 있습니다. 그러나 무지막지하게 긴 것 예를 든다면 김건모의 스피드를 WAVE 파일로 녹음하면 ①로 출력하기가 무척 힘듭니다. 간단히 말해서 SndPlaySound("speed.wav",SND_ASYNC)라고 실행하고 약 30분 기다린 후에야 "오 제발! 오 제발!" 이럴 것입니다. 이럴 때는 ②를 사용합니다. 또한 간단하게 녹음을 할 때도 ②를 사용합니다.

그럼 ③은 어떨 때 사용하는가? WAVE 데이터를 편집할 때 사용합니다. 예를 든다면 WAVE의 파형을 화면에 도시할 때 WAVE 데이터의 값을 알아야 파장을 그리잖아요. 또한 녹음을 하거나 다른 데이터와 믹싱을 할 때 또는 에코를 넣을 때 사용하죠. 3가지 방법 중 어떤 방법이 제일 좋냐고 물어 보는 것은 멍청한 생각입니다. 3가지 방법 다 쓸모가 있기 때문이죠. 이 책에서는 위의 3가지중 1번과 2번만 사용합니다. 로레벨 오디어 서비스에 대한 내용을 보고자 한다면 필자가쓴 "Visual Programming Bible 5.x "를 참조하세요.

q SndPlay 함수

SndPlay 함수는 다음과 같습니다.

WORD sndPlaysound(LPSTR lpszSound,UINT fsSound);lpszSound는 출력할 sound의 이

름을 나타냅니다. 이 이름은 WIN.INI 파일에 기록되어 있는 SOUND 이름일 수도 있고 파일 이름일 수도 있습니다. 또한 메모리 포인터(APSTUDIO에서 만든 ID)일 수도 있습니다. 그럼 lpszSound 이름이 무엇인지 알기 위해서는 어떻게 해야 할까요? fsSound 플래그를 보면 알 수 있습니다. SND_MEMORY라고 설정하면 메모리에 올라가 있는 사운드 이미지의 이름을 이야기하고 그 외에는 모두 파일로 간주합니다.

fsSound 플래그에는 다음과 같은 것들이 있습니다.

∙SND_ASYNC : 재생시 다른 작업을 할 수 있게 비동기식으로 작동을 하라는 플래그

∙SND_SYNC : 재생이 끝날 때까지 아무일도 하지 못하게 하는 설정 플래그

∙SND_LOOP : 재생이 끝나면 자동적으로 처음으로 가서 다시 재생을 하라는 플래그. 카세트의 오토리버스(AUTO REVERSE) 기능을 말한다.

∙SND_NODEFAULT : 재생이 실패로 돌아가면 DEFAULT 소리를 내는데 이것을 하지 말라는 플래그

파일에서 읽어서 재생

C:\WINDOWS\SYSTEM\ROCKNROL.WAV가 있을 경우 다음과 같이 하면

sndPlaySound("C:\\WINDOWS\\SYSTEM\\ROCKROL.WAV",SND_ASYNC);

ROCKROL.WAV 파일을 재생합니다. 이 때 ROCKROL.WAV가 없으면 "띵!" 이런 소리가 납니다. 이 소리는 WIN.INI에 설정되어 있는 소리입니다. 이 소리를 내지 않게 하기 위해서는 다음과 같이 합니다.

sndPlaySound("C:\WINDOWS\SYSTEM\ROCKROL.WAV",SND_ASYNC|SND_NODEFAULT);

그럼 파일이 없으면 아무 소리도 없이 그냥 실행이 종료됩니다. 재생 도중에 아무 작업도 할 수 없게 하려면 다음과 같이 해주면 됩니다.

sndPlaySound("C:\WINDOWS\SYSTEM\ROCKROL.WAV",SND_SYNC|SND_NODEFAULT);

재생이 자동 반복을 하게 만들려면 다음과 같이 합니다.

sndPlaySound("C:\WINDOWS\SYSTEM\ROCKROL.WAV",SND_ASYNC|SND_LOOP);

메모리에서 읽어서 재생

게임을 만들거나 멀티미디어 CD를 만들 때 자주 사용하는 음성들은 미리 메모리에 올려놓고 있다가 사용하면 매우 편리합니다. 이럴 때 사용하는 것이 RESO- URCE에 음성을 등록시킨 후에 그것을 로드하는 방법입니다. 리소스 파일에 다음과 같이 등록시킵니다.

soundName DING C:\WINDOWS\DING.WAV

이렇게 하고 DING라는 명을 이용하여 메모리에 로드시킵니다. 이 때 사용하는 플래그가 SND_MEMORY입니다.

BOOL PlayRc(LPSTR filename)

{

HANDLE pWaveInfo,hWaveRes;

LPSTR lpWaveRes;

//웨이브를 찾아서

pWaveInfo = FindResource(NULL,filename,"WAVE");

//웨이브 리소스를 로드한다.

hWaveRes = LoadResource(NULL,pWaveInfo);

//웨이브 리소스를 잠근다.

lpWaveRes = LockResource(hWaveRes);

//음성을 출력한다.

sndPlaySound(lpWaveRes,SND_MEMORY|SND_SYNC);

//웨이브 리소스를 푼다.

UnlockResource(hWaveRes);

//웨이브 리소스를 해제한다.

ReeeResource(hWaveRes);

}

w MCI 명령을 사용

MCI 명령이란 멀티미디어를 구동할 때 일반적으로 사용하는 명령입니다. 이 함수들은 MMSYSTEM.H에 정의되어 있으므로 프로그램을 작성할 때 본 함수를 소스에 인클루드해야 합니다. MMSYSTEM.H를 포함 하기 위해서는 Project메뉴에서 Add To Project를 설정하고 Components and Controls항목을 클랙하여 대화상자가 나오면 Developer Studio Components 를 선택하고 그림 2와 같은 화면에서

Windows Multimedia library를 선택하여 삽입시키면 됩니다.

















(그림 2) Components and Controls

MCI란 하이레벨 오디오 서비스로서 명령을 하이레벨 오디오 디바이스 드라이버에 전한다는 이야기를 했습니다. 따라서, 명령만 내리면 되죠. 그렇다면 명령을 내리는 함수 하나면 있으면 되겠네요. 그 함수가 바로 mciSendCommand() 함수입니다. 이외에 명령을 내리는 함수가 하나 더 있는데 그 함수명이 mciSendString()입니다. 여기서는 mciSendString을 설명하지 않습니다. mciSendCommand() 함수 하나로 충분하기 때문이죠. 함수의 형태는 다음과 같습니다.

MCIERROR mciSendCommand(MCIDEVICEID IDDevice, UINT uMsg ,DWORD fdwCommand, DWORD dwParam);

IDDevice란 디바이스 드라이버 ID를 말합니다. 사운드 카드 드라이버, 비디오 카드 드라이버, MPGE 카드 드라이버 등등 제어판에 설정되어 있는 드라이버가 굉장히 많아요.

그럼 명령을 내리는데 어느 드라이버로 할 것인가가 중요해집니다. 소리를 출력하라고 명령을 내리고 ID를 비디오 카드로 해주면 소리가 날까요?

그래서 각 디바이스 드라이버에 ID를 부여해 주었습니다.

UMsg란 명령 메시지입니다. 소리를 내라, 소리를 멈추어라, 음성을 녹음해라 등등 여러 명령 메시지를 설정하는 것입니다.

fdwCommand란 명령을 내릴 때 플래그입니다. 예를 든다면 소리를 내라 - 소리를 낼 때 아무일도 할 수 없게 해라- 여기서 소리를 내라는 명령어는 UMsg에 설정하면 되겠지요. 그럼 소리를 낼 때 아무일도 할 수 없게 하라를 바로 fdwCo- mmand에 설정하면 됩니다.

dwParam란 보고서를 받는 포인터입니다. 예를 든다면 소리를 내라!하고 명령을 내리면 사운드 카드 디바이스 드라이버에서 "예 소리를 냈습니다. 현재 소리는 스테레오이고요, 약 20초 간 나올 것입니다."라고 보고를 해야 하죠. 바로 이 보고를 받는 포인터입니다.

바로 위 함수 한 개로 모든 하이레벨 오디오 서비스를 제어합니다. 여기에 부과된 두 가지 함수가 있는데 다음과 같습니다.

∙mciGetdeviceID() : 디바이스 드라이버 ID를 받는다.

∙mciGetErrorString() : 에러가 있을 때 에러 내용을 받는다.

dwParam을 보고서라고 했는데 어떤 실행이 있은 후 서로 메시지를 주고받는 서류가 있어야 할 것 아니겠습니까? 물건을 배달시킬 때 이런 서류를 만들듯이 말입니다.

물류 배달서

6물건명 : 바나나 한 박스

트럭기사 : _____ (인)

발송자 : 이길동 (인)

수납자 : _____ (인)


트럭 기사가 물건을 받고 이름을 쓰고 사인을 하죠? 그리고 수납자에게 가면 수납자는 이름을 쓰고 사인을 하고요. 이렇게 어떤 명령이 실행될 때 서로 주고받는 데이터를 저장할 보고서가 있어야 하지요.

음성 출력시 출력에 대한 보고서, 녹음시 녹음에 대한 보고서, 재생시 재생에 대한 보고서 등등이 있을 것입니다. 그런 보고서를 PARMS라고 하여 MMSYSTEM.H에 만들어 놓은 구조체가 있습니다.

그 구조체는 다음과 같습니다.

∙MCI_OPEN_PARMS : 디바이스 드라이버를 열 때 보고서

∙MCI_PLAY_PARMS : 음성을 재생할 때 보고서

∙MCI_RECORD_PARMS : 음성을 녹음할 때 보고서

∙MCI_STATUS_PARMS : 현재 상태를 체크할 때 보고서

uMsg란 명령 메시지라고 했는데 명령 메시지는 다음과 같습니다.

∙MCI_OPEN : 디바이스 드라이버를 열어라. -사용 보고서→MCI_OPEN_PARMS

∙MCI_PLAY : 음성을 출력해라. -사용 보고서 → MCI_PLAY_PARMS

∙MCI_RECORD : 음성을 녹음해라. -사용 보고서 → MCI_RECORD_PARMS

∙MCI_STATUS : 현재 상태 체크 -사용 보고서 → MCI_STATUS_PARMS

∙MCI_SAVE : 데이터를 저장 -사용 보고서 → MCI_SAVE_PARMS

∙MCI_PAUSE : 일시 중단

∙MCI_SEEK : 음성의 측정 위치로 가라.

∙MCI_CLOSE : 현재 디바이스를 닫아라.

그럼 명령 내리는 것은 쉽겠네요.

예를 든다면 음성을 출력해라!라고 하려면

mciSendCommand(ID,MCI_PLAY,모름,MCI_PLAY_PARMS 형);

음성 디바이스를 열어라!라고

mciSendCommand(NULL,MCI_OPEN,모름,MCI_OPEN_PARMS 형);

이렇게 하면 되죠. 여기서 모름이란 부명령 플래그입니다. 부명령에는 굉장히 많은 플래그가 있으므로 플래그에 대해서는 함수를 하나하나 만들어 갈 때마다 설명하겠습니다.

사운드 디바이스 드라이버 열기

음성을 출력하기 이전에 제일 먼저 해야 할 일이 사운드 드라이버를 여는 것입니다. 우선 프로그램부터 보겠습니다.

MCI_OPEN_PARMS mciOpenParms;

DWORD wDeviceID;

int erroCode;

mciOpenParms.lpstrElementName = filename;// 파일 설정

mciOpenParms.lpstrDeviceType = "waveaudio";//디바이스를 waveaudio로 설정

erroCode=mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_ELEMENT|MCI_OPEN_TYPE,

(DWORD)(LPVOID)&mciOpenParms);

wDeviceID = mciOpenParms.wDeviceID;//다바이스 ID를 받는다.

사운드 드라이버를 열 때 웨이브 파일 출력으로 열 때가 있고 미디 음악 출력으로 열 때가 있고 CD-AUDIO로 열 때가 있습니다. 따라서, 열어야 하는 형태를 말해 주어야 합니다.

열어야 할 때 여는 내용을 상호 전달하는 보고서는 MCI_OPEN_PARMS 구조체입니다.

MCI_OPEN_PARMS mciOpenParms;

위와 같이 mciOpenParms;를 오픈시 상호 전달 보고서 구조체로 등록합니다.

mciOpenParms.lpstrElementName = filename;

MCI_OPEN_PARMS 구조체 안의 lpstrElementName에 웨이브 파일을 설정해 줍니다.

mciOpenParms.lpstrDeviceType= "waveaudio";

디바이스 형태를 waveaudio로 설정합니다. 즉 웨이브 파일을 출력하는 오디오로 설정하겠다는 이야기입니다. 이제 명령을 내려야겠죠.

mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_ELEMENT|MCI_OPEN_TYPE,

(DWORD)(LPVOID)&mciOpenParms);

위의 명령을 해석하면 "ID를 모르겠지만 mciOpenParms 안에 있는 lpstrDevi- ceType형으로 lpstrElementName 파일을 열되 여는 방법은 하나의 요소를 설정하기 위한 형으로 디바이스 드라이버를 열어라!"라는 이야기입니다. 즉 파형 파일을 출력하는 디바이스 드라이버를 열고 파형 파일 filename을 출력 디바이스에 걸어라 하는 말이죠.

이 때 드라이버가 위 명령을 받고 실행에 성공하면 드라이버는 0을 리턴하고 mciOpenParms 안의 wDeviceID에 현재 설정되어 있는 디바이스 드라이버 ID를 기록합니다.

음성 출력하기

음성을 출력하기 위한 프로그램을 먼저 보겠습니다.

MCI_PLAY_PARMS mciPlayParms;

mciSendCommand(wDeviceID,MCI_PLAY,MCI_NOTIFY,(DWORD)(LPVOID) &mciPlayParms);

위에서 받은 ID로 MCI_PLAY 명령을 해주면 재생이 됩니다. "디바이스 드라이버! 재생하다가 무슨 일이 있으면 나에게 보고해라, 알겠나?"하는 이야기가 바로 MCI_NOTIFY입니다. 이 때 보고는 mciPlayParms에 메시지로 기록합니다. mciPl- ayParms.dwCallback에 설정되어 있는 윈도에 MCI_NOTIFY 메시지가 발송됩니다. 그렇게 되면 설정되어 있는 윈도로 메시지 이벤트가 발생됩니다.

그렇다면 위의 함수를 좀 바꾸어야겠죠.

mciPlayParms.dwCallback=m_hWnd;

mciSendCommand(wDeviceID,MCI_PLAY,MCI_NOTIFY,(DWORD)(LPVOID) &mciPlayParms);

사실 음성 출력할 때 뭐 보고할 게 있겠습니까? 그냥 출력이 끝나면 되지 "아! 지금 1초 출력했습니다!", "아! 지금 2초 출력했습니다!" 이렇게 메시지를 윈도에 보내면 현재 윈도가 아마 이럴지도 모릅니다.

"야! 지금 딴 메시지 받는 것도 정신없는데... 시끄럽다!"

필자는 그래서 음성으로 출력시키고 아무런 메시지를 받지 않습니다. 그래서 다음과 같이 설정합니다.

mciPlayParms.dwCallback=NULL;

음성 녹음하기

음성을 녹음할 때의 프로그램은 다음과 같습니다.

MCI_RECORD_PARMS mciRecordParms;

mciRecordParms.dwTo=10000;//밀리 세컨드

mciSendCommand(wDeviceID,MCI_RECORD,MCI_TO | MCI_WAIT,(DWORD)(LPVOID)&mciRecordParms))

여기에서 MCI_WAIT란 레코딩하는 중간에 아무런 일도 하지 말고 기다리라는 이야기이고 MCI_TO란 mciRecordParms.dwTo에 설정한 시간까지 녹음을 하라는 말입니다.

음성 저장하기

음성을 저장할 때의 프로그램은 다음과 같습니다.

MCI_SAVE_PARMS mciSaveParms;

mciSaveParms.lpfilename=filename;

mciSendCommand(wDeviceID,MCI_SAVE,MCI_SAVE_FILE | MCI_WAIT,(DWORD)(LPVOID)&mciSaveParms);

MCI_SAVE_FILE은 파일에 저장하라는 플래그이고 MCI_WAIT란 파일에 저장하는 동안 아무런 동작을 하지 말라는 이야기입니다.

음성 데이터 포지션 이동

∙처음으로 이동할 경우

mciSendCommand (wDeviceID, MCI_SEEK, MCI_SEEK_TO_START, (DWORD) (LPVOID) NULL);

∙마지막으로 이동할 경우

mciSendCommand (wDeviceID, MCI_SEEK, MCI_SEEK_TO_END, (DWORD) (LPVOID) NULL);

재생 멈춤

재생 멈춤은 다음과 같습니다.

mciSendCommand(wDeviceID,MCI_PAUSE,MCI_NOTIFY,(DWORD)(LPVOID) &mciPlayParms);

e MCI 명령을 사용한 클래스 만들기

MCI 명령을 이용한 클래스를 만들어 두었다가 필요할 때 라이브러리로 사용하면 굉장히 편합니다. 이번에는 MCI 명령을 이용한 클래스를 만들어 보겠습니다. 클래스명은 CMySound로 설정했습니다. 먼저 클래스 헤더를 보겠습니다.

클래스명 : CMySound 파일명 mysound.h

class CMySound

{

public:

CMySound (LPSTR lpszFileName); // 출력용 생성자

CMySound (LPSTR lpszFileName,DWORD Sec);//녹음용 생성자

-CMySound ( );//소멸자

BOOL Play( );//재생

void Pause( );//처음으로

BOOL Home( );//마지막으로

UNIT m_nTime;//데이터의 총시간

protected:

MCI_OPEN_PARMS mciOpenParms;

MCI_PLAY_PARMS mciOpenParms;

MCI_RECORD_PARMS mciOpenParms;

MCI_SAVE_PARMS mciOpenParms;

DWORD wDeveceID;

CMySound 클래스에서는 두 가지 생성자를 두었습니다. 음성을 재생할 때 생성자, 음성을 녹음할 때 생성자로요. 재생 생성자의 소스는 다음과 같습니다.

//생성자 웨이브 파일을 출력시키는 생성자

CMySound::CMySound(LPSTR filename)

{

// 재생할 파일명을 설정

mciOpenParms.lpstrElementName = filename;

//디바이스를 waveaudio로 설정한다.

mciOpenParms.lpstrDeviceType = "waveaudio";

//MCI_OPEN 명령을 주어 디바이스를 열고 ID를 받는다.

mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_ELEMENT|MCI_OPEN_TYPE,

(DWORD)(LPVOID)&mciOpenParms);

//열린 디바이스 ID를 wDeviceID에 설정한다.

wDeviceID = mciOpenParms.wDeviceID;

//Callback 함수 없음.

mciPlayParms.dwCallback = NULL;

//이 다음에는 파일의 전체 출력 시간을 얻어 둔다. 프로그램 제작시에 현재

//재생되는 음성의 전체 시간을 아는 것은 매우 유용하다.

// 전체 출력 시간을 알기 위해 m_nTime에 설정한다.

MCI_STATUS_PARMS mciStatusParms;

mciStatusParms.dwItem= MCI_STATUS_LENGTH;

mciSendCommand(wDeviceID,MCI_STATUS,MCI_STATUS_ITEM,(DWORD)(LPVOID)&mciStatusParms);

m_nTime=(LONG)mciStatusParms.dwReturn;

}

녹음 생성자는 다음과 같습니다.

//녹음을 위한 생성자

CMySound::CMySound(LPSTR filename,DWORD Sec)

{

BOOL erroCode;

//파일 이름 없이 디바이스를 연다.

mciOpenParms.lpstrElementName = "";

mciOpenParms.lpstrDeviceType = "waveaudio";

//디바이스를 여는 것은 위의 재생을 위한 생성자와 같다.

if(erroCode=mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_ELEMENT|MCI_OPEN_TYPE,

(DWORD)(LPVOID)&mciOpenParms))

{

return;

}

wDeviceID = mciOpenParms.wDeviceID;

//녹음 시간 설정

mciRecordParms.dwTo=Sec*1000;

//재생 명령이 아닌 녹음 명령을 준다.

//녹음 시작 MCI_WAIT이기 때문에 녹음중에는

//아무 일도 하지 않는다.

if(erroCode=mciSendCommand(wDeviceID,MCI_RECORD,MCI_TO | MCI_WAIT,

(DWORD)(LPVOID)&mciRecordParms))

{

mciSendCommand(wDeviceID,MCI_CLOSE,0,NULL);

return;

}

//녹음이 끝나면 파일에 저장한다.

mciSaveParms.lpfilename=filename;

if(erroCode=mciSendCommand(wDeviceID,MCI_SAVE,MCI_SAVE_FILE | MCI_WAIT,

(DWORD)(LPVOID)&mciSaveParms))

{

mciSendCommand(wDeviceID,MCI_CLOSE,0,NULL);

return;

}


}

나머지 클래스에 대한 소스는 다음과 같습니다.

//소멸자

CMySound::~CMySound()

{

//디바이스를 닫는다.

mciSendCommand(wDeviceID,MCI_CLOSE,0,NULL);

}

//재생

BOOL CMySound::Play()

{

mciSendCommand(wDeviceID,MCI_PLAY,MCI_NOTIFY,

(DWORD)(LPVOID) &mciPlayParms);

return TRUE;

}

//멈춤

void CMySound::Pause()

{

mciSendCommand(wDeviceID,MCI_PAUSE,MCI_NOTIFY,

(DWORD)(LPVOID) &mciPlayParms);

}

//처음으로

BOOL CMySound::Home (void)

{

mciSendCommand (wDeviceID, MCI_SEEK, MCI_SEEK_TO_START, (DWORD) (LPVOID) NULL);

return TRUE;

}

//마지막으로

BOOL CMySound::End (void)

{

mciSendCommand (wDeviceID, MCI_SEEK, MCI_SEEK_TO_END, (DWORD) (LPVOID) NULL);

return TRUE;

}

r MCI 이용한 프로그램 제작

위에서 제작한 CMySound 클래스와 sndPlay 함수를 이용하여 녹음하고 재생하는 프로그램을 제작해 보겠습니다. 그림 2가 본 프로그램의 실행 모습입니다.


파일 부르기 버튼을 눌러서 웨이브 파일을 선택하고 SndPlaySound() 버튼을 눌러서 SndPlaySound() 함수를 실행하고 MCI_PLAY 버튼을 눌러서 현재 음성을 재생하고 <= 버튼을 누르면 재생중에 처음으로, => 버튼을 누르면 재생중에 마지막으로 이동하고 MCI_RECORD를 누르면 녹음이 되는 프로그램입니다.

본 프로그램은 Application 형태를 Dialog based로 작성합니다.


프로그램 소스

// mciwaveDlg.h : header file

// 하이레벨 오디오 서비스를 이용한 WAVE 파일 작동법

//

///////////////////////////////////////////////////////////////////////////

// CMciwaveDlg dialog

class CMciwaveDlg : public CDialog

{

// Construction

public:

CMciwaveDlg(CWnd* pParent = NULL); // standard constructor

// Dialog Data

//{{AFX_DATA(CMciwaveDlg)

enum { IDD = IDD_MCIWAVE_DIALOG };

int m_nStatus;

CMySound *m_pOutSound; //하이레벨 오디오 서비스 클래스 출력용

//}}AFX_DATA

char m_strFileName[80];// 파일명을 저장

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMciwaveDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

HICON m_hIcon;

// Generated message map functions

//{{AFX_MSG(CMciwaveDlg)

virtual BOOL OnInitDialog();

afx_msg void OnPaint();

afx_msg HCURSOR OnQueryDragIcon();

afx_msg void OnFileopen();

afx_msg void OnMciplay();

afx_msg void OnSndplay();

afx_msg void OnStop();

afx_msg void OnRwind();

afx_msg void OnFwind();

afx_msg void OnRecord();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

// mciwaveDlg.cpp : implementation file

// 하이레벨 오디오 서비스를 이용한 WAVE 파일 작동법

// 제작자 : 이상엽

//

#include "stdafx.h"

#include "mciwave.h"

#include "mmsystem.h"

#include "mysound.h"

#include "mciwaveDlg.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

#define PR_NONE 0

#define PR_MCIPLAY 1

#define PR_SNDPLAY 2

#define PR_RECORD 3

///////////////////////////////////////////////////////////////////////////

// CMciwaveDlg dialog

CMciwaveDlg::CMciwaveDlg(CWnd* pParent /*=NULL*/)

: CDialog(CMciwaveDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CMciwaveDlg)

//}}AFX_DATA_INIT

m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

}

void CMciwaveDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CMciwaveDlg)

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CMciwaveDlg, CDialog)

//{{AFX_MSG_MAP(CMciwaveDlg)

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

ON_BN_CLICKED(IDC_FILEOPEN, OnFileopen)

ON_BN_CLICKED(IDC_MCIPLAY, OnMciplay)

ON_BN_CLICKED(IDC_SNDPLAY, OnSndplay)

ON_BN_CLICKED(IDC_STOP, OnStop)

ON_BN_CLICKED(IDC_RWIND, OnRwind)

ON_BN_CLICKED(IDC_FWIND, OnFwind)

ON_BN_CLICKED(IDC_RECORD, OnRecord)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////

// CMciwaveDlg message handlers

BOOL CMciwaveDlg::OnInitDialog()

{

CDialog::OnInitDialog();

SetIcon(m_hIcon, TRUE); // Set big icon

SetIcon(m_hIcon, FALSE); // Set small icon

//모든 버튼을 비활성화 한다.

GetDlgItem(IDC_SNDPLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_MCIPLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_RWIND)->EnableWindow(FALSE);

GetDlgItem(IDC_FWIND)->EnableWindow(FALSE);

GetDlgItem(IDC_STOP)->EnableWindow(FALSE);

GetDlgItem(IDC_RECORD)->EnableWindow(FALSE);

//파일명을 NONAME.WAV로 설정한다.

lstrcpy((LPSTR)m_strFileName,(LPSTR)"NONAME.WAV");

return TRUE; // return TRUE unless you set the focus to a control

}

void CMciwaveDlg::OnPaint()

{

if (IsIconic())

{

CPaintDC dc(this); // device context for painting

SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

// Center icon in client rectangle

int cxIcon = GetSystemMetrics(SM_CXICON);

int cyIcon = GetSystemMetrics(SM_CYICON);

CRect rect;

GetClientRect(&rect);

int x = (rect.Width() - cxIcon + 1) / 2;

int y = (rect.Height() - cyIcon + 1) / 2;

// Draw the icon

dc.DrawIcon(x, y, m_hIcon);

}

else

{

CPaintDC dc(this);

//글자 출력시 바탕에 투명하게 출력

dc.SetBkMode(TRANSPARENT);

//IDC_FILENAME 자원의 박스 좌표를 구한다.

CRect rect;

CWnd* pWnd =GetDlgItem( IDC_FILENAME );

pWnd->GetWindowRect( &rect );

ScreenToClient( &rect );

//그 위치에 파일명을 출력한다.

dc.TextOut(rect.left+40,rect.top+10,m_strFileName);

}

}

// The system calls this to obtain the cursor to display while the user drags

// the minimized window.

HCURSOR CMciwaveDlg::OnQueryDragIcon()

{

return (HCURSOR) m_hIcon;

}

void CMciwaveDlg::OnFileopen()

{

//파일 다이얼로그 실행

CString szFileName;

CFileDialog dlg(TRUE,_T("WAV"),_T("*.WAV"),OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,

_T("WAVE FILE (*.WAV)|*.WAV|ALL FILE (*.*)|*.*|"));

if(dlg.DoModal()==IDOK)

{

//파일명을 설정하면

szFileName=dlg.GetPathName();

//파일명에 해당하는 파일명을 설정한다.

lstrcpy((LPSTR)m_strFileName,(LPSTR)szFileName.operator const char*());

//모든 버튼을 활성화한다.

GetDlgItem(IDC_SNDPLAY)->EnableWindow(TRUE);

GetDlgItem(IDC_MCIPLAY)->EnableWindow(TRUE);

GetDlgItem(IDC_RWIND)->EnableWindow(TRUE);

GetDlgItem(IDC_FWIND)->EnableWindow(TRUE);

GetDlgItem(IDC_STOP)->EnableWindow(TRUE);

//레코드는 활성화 하지 않는다.

GetDlgItem(IDC_RECORD)->EnableWindow(FALSE);

}

}

void CMciwaveDlg::OnMciplay()

{

//MCI로 재생

m_pOutSound=new CMySound((LPSTR)m_strFileName);

m_pOutSound->Play() ;

m_nStatus=PR_MCIPLAY;

GetDlgItem(IDC_SNDPLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_MCIPLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_RECORD)->EnableWindow(FALSE);

}

//sndPlaySound 함수로 재생

void CMciwaveDlg::OnSndplay()

{

sndPlaySound(m_strFileName,SND_ASYNC);

m_nStatus=PR_SNDPLAY;

GetDlgItem(IDC_MCIPLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_SNDPLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_RECORD)->EnableWindow(FALSE);

GetDlgItem(IDC_RWIND)->EnableWindow(FALSE);

GetDlgItem(IDC_FWIND)->EnableWindow(FALSE);

}

//멈춤을 눌렀을 때

void CMciwaveDlg::OnStop()

{

switch(m_nStatus)

{

//만약 sndPlay 함수이면

case PR_SNDPLAY:

sndPlaySound(NULL,NULL);

break;

//MCI로 녹음이나 재생이면

case PR_MCIPLAY:

delete m_pOutSound;

break;

case PR_RECORD:

delete m_pOutSound;

break;

}

//모든 버튼을 활성화한다.

m_nStatus=PR_NONE;

GetDlgItem(IDC_SNDPLAY)->EnableWindow(TRUE);

GetDlgItem(IDC_MCIPLAY)->EnableWindow(TRUE);

GetDlgItem(IDC_RWIND)->EnableWindow(TRUE);

GetDlgItem(IDC_FWIND)->EnableWindow(TRUE);

GetDlgItem(IDC_STOP)->EnableWindow(TRUE);

GetDlgItem(IDC_RECORD)->EnableWindow(TRUE);

}

//처음으로

void CMciwaveDlg::OnRwind()

{

// TODO: Add your control notification handler code here

if( m_nStatus=PR_MCIPLAY)

{

m_pOutSound->Home();

m_pOutSound->Play();

}

}

//마지막으로

void CMciwaveDlg::OnFwind()

{

if( m_nStatus=PR_MCIPLAY)

m_pOutSound->End();

}

//녹음

void CMciwaveDlg::OnRecord()

{

//파일 다이얼로그 실행

CString szFileName;

CFileDialog dlg(FALSE,_T("WAV"),_T("*.WAV"),OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,

_T("WAVE FILE (*.WAV)|*.WAV|ALL FILE (*.*)|*.*|"));

if(dlg.DoModal()==IDOK)

{

//파일명을 설정하면

szFileName=dlg.GetPathName();

//파일명에 해당하는 파일명을 설정한다.

lstrcpy((LPSTR)m_strFileName,(LPSTR)szFileName.operator const char*());

//모든 버튼을 활성화한다.

GetDlgItem(IDC_SNDPLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_MCIPLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_RWIND)->EnableWindow(FALSE);

GetDlgItem(IDC_FWIND)->EnableWindow(FALSE);

GetDlgItem(IDC_STOP)->EnableWindow(TRUE);

GetDlgItem(IDC_RECORD)->EnableWindow(FALSE);

m_pOutSound = new CMySound((LPSTR)m_strFileName,(DWORD)10);

m_nStatus=PR_RECORD;

}

}


AVI 출력하기


2 AVI 출력하기

39

AVI 파일은 MS-WINDOWS용 기본 비디오 파일입니다. 매체 재생기를 이용하여 확장자가 avi인 avi 파일을 직접 출력해 본 경우가 있을 것입니다. 이번 장에서는 이런 avi 파일을 MFC프로그램 안에서 출력할 수 있도록 해보겠습니다.

윈도의 system.ini에 보면 다음과 같은 항목이 있을 것입니다.

[mci]

waveaudio=mciwave.drv

sequencer=mciseq.drv

cdaudio=mcicda.drv

CDIVideo=XMDRIVER.DRV// cdi 비디오

avivideo=mciavi.drv // avi형

videodisc=mcipionr.drv //dickvideo

Animation1=MCIAAP.DRV //fil,flc형

MpegVideo=XMDRIVER.DRV //mpeg형

[mci]라는 항목 밑에 물론 각자의 컴퓨터에 설치되어 있는 비디오 드라이버는 여러 가지일 것입니다. 상기에 기록한 화면과는 다를 수도 있죠. 그러나 avivi- deo=mciavi.drv라는 항목은 같을 것입니다.

컴퓨터에서 멀티미디어는 크게 2가지로 나눌 수가 있습니다. 사운드와 동영상, 이중 동영상은 비디오 디스크와 CDIVIDEO, MPEGVIDEO 등이 있습니다.

디스크 자체가 동영상인 항목은 여기서 다루지 않으나 mci를 이용한다면 크게 다를 것이 없습니다. mci에 설치된 동영상과 오디오 사운드의 사용법은 본서의 멀티미디어 프로그래밍을 해본 다음 Microsoft Windows Software Devleopment Kit의 Multimedia Programmer's Guide와 Multimedia Programmer's Reference를 참조합니다.

파일로 설치된 동영상을 여기에서는 avi에 국한시키지 않았습니다. 만일 fli나 flc 또는 mpeg 등의 mci 드라이버가 설치되었다면 fil, flc, Mpeg 파일 또한 출력시키는 방법을 설명하였습니다.

요즘은 멀티미디어 시대라서 동영상을 화면에 출력시키는 방법에 대해 매우 많은 관심을 가지고 있습니다. 그중에서도 대단히 인기를 끌고 있는 것이 MPEG입니다. 현재 MPEG2까지 개발되어 있으며 1998년도까지는 MPEG4가 개발된다고 하니 MPEG에 대한 사람들의 관심이 커졌습니다.

그럼 MPEG는 도대체 어떤 방법으로 화면을 출력할까요? MPEG 파일에 대한 공부를 하려면

Multiresolution Signal Decomposition과 대우전자 영상연구소에서 발간한 MPEG 비디오와 한양대학교 전자통신공학과 정제창 교수가 번역한 최신 MPEG라는 책을 참조하세요.

q MCI 이용한 AVI 출력 방법

파일 열기

파일을 개방할 경우 MCI의 여러 파라미터 중 MCI_ANIM_OPEN_PARMS 구조체를 사용합니다.

MCI_ANIM_OPEN_PARMS mciOpenParms;

MCI_ANIM_OPEN_PARMS의 맴버인 lpstrDeviceType에 무엇을 설정하느냐에 따라 AVI, FLI, FLC MPEG 등이 결정됩니다. 물론 현재 컴퓨터에 위의 드라이버가 설치되었다고 가정하고 말입니다.

① mciOpenParms.lpstrDeviceType = "AVIVideo"; //AVI용 파일

② mciOpenParms.lpstrDeviceType = "Animation1"; //FLI,FLC용 파일

③ mciOpenParms.lpstrDeviceType = "MpegViedeo"; //FLI,FLC용 파일

①은 avi 파일을 설정할 때 사용하고 ②는 fli나 flc 파일을 출력할 때 사용합니다. fli나 flc는 3D STUDIO 프로그램을 이용하여 제작한 애니메이션 파일을 말합니다. ③은 MPEG 파일을 출력할 때 사용하죠. 요즘 많이 이용하는 MPEG 드라이버에 XWing이라는 것이 있습니다. 펜티엄급에서는 카드 없이도 MPEG 파일을 무난하게 볼 수 있더군요. 파일명을 설정할 때는 MCI_ANIM_OPEN_PARMS의 멤버인 lpstrElementName에 파일명을 설정하면 됩니다.

mciOpenParms.lpstrElementName = "test.avi";

이제 mciSendCommand 명령문을 이용하여 파일을 개방합니다.

mciSendCommand(0,

MCI_OPEN,

MCI_OPEN_ELEMENT | MCI_OPEN_TYPE | MCI_ANIM_OPEN_WS,

(DWORD)(LPVOID) &mciOpenParms);

위와 같이 실행하면 MCI_ANIM_OPEN_PARMS로 설정된 mciOpenParms의 부함수 wDeviceID가 현재 MCI의 ID 번호를 넘겨줍니다. wDeviceID가 바로 핸들러 번호지요. 앞으로 mciSendCommand 함수를 사용할 때는 본 wDeviceID를 계속적으로 사용합니다.

이와 같이 함으로써 파일을 열기는 했지만 avi란 화면에 동영상이 출력되기 때문에 화면에 출력될 좌표를 설정해 주어야 합니다. 즉 화면의 x1, x2, y1, y2를 설정해 줍니다. 이 때 사용하는 파라미터가 MCI_ANIM_RECT_PARMS입니다.

MCI_ANIM_RECT_PARMS mciRect;

mctRect 안에 x1, y1, x2, y2를 설정하는 방법은 다음과 같습니다.

mciRect.rc.top = 0;

mciRect.rc.left= 0;

mciRect.rc.right=300;

mciRect.rc.bottom =240;

다음 MCI_ANIM_OPEN_PARMS로 설정된 mciOpenParms의 부함수 wDeviceID를 받습니다.

UINT wDeviceID;

wDeviceID = mciOpenParms.wDeviceID;

이제 화면에 출력할 좌표를 mciSendCommand를 이용하여 설정합니다.

mciSendCommand(wDeviceID,MCI_PUT,MCI_ANIM_PUT_DESTINATION | MCI_ANIM_RECT,

(DWORD) (LPVOID) &mciRect);

출력할 좌표는 정해 주었는데 어느 윈도에 출력할 것인가가 또 하나의 문제이지요. 즉 avi를 출력할 윈도를 설정해 주어야 합니다. 이 윈도 설정은 HWND를 이용합니다.

예를 든다면 CTextView라는 뷰 프레임에 avi를 출력할 경우 뷰 프레임의 m_hWnd를 설정해 주어서 CTextView에 avi를 출력시키게 하는 것입니다.

윈도를 설정하는 파라미터는 MCI_ANIM_WINDOW_PARMS입니다.

MCI_ANIM_WINDOW_PARMS mciWindow;

MCI_ANIM_WINDOW_PARMS의 멤버인 hWnd에 m_hWnd를 설정시켜 준다.

mciWindow.hWnd = m_hWnd;

이제 mciSendCommand를 이용하여 윈도를 설정해야겠지요.

mciSendCommand(wDeviceID,MCI_WINDOW,MCI_ANIM_WINDOW_HWND,

(DWORD)(LPVOID) &mciWindow);

이와 같이 함으로써 동영상 파일을 출력하는 모든 준비가 완료되었습니다.

동영상 출력하기

이제 설정된 동영상을 출력하겠습니다. 출력할 때 사용하는 파라미터는 MCI_PLAY_PARMS입니다.

MCI_PLAY_PARMS mciPlayParms;

위와 같이 설정한 다음 mciSendCommand를 이용하여 동영상을 출력합니다.

mciSendCommand(wDeviceID,MCI_PLAY,MCI_NOTIFY,

(DWORD)(LPVOID) &mciPlayParms);

위와 같이 하면 동영상이 출력됩니다. 요즘 좋은 VGA 카드가 나와서 트루 칼라를 많이 사용합니다. 그런데 256칼라를 사용할 때는 위와 같이 영상을 출력시키면 다른 비트맵과 문제가 생깁니다. 팔레트 때문입니다. 예를 들어, 어떤 비트맵 이미지를 화면에 출력한 다음 그 위에 동영상을 출력한다고 했을 경우 이미지의 팔레트와 동영상의 팔레트가 다를 때는 이미지의 색이 변하든지 동영상의 색이 변하게 되어 있습니다. 이럴 때는 동영상과 이미지의 팔레트를 재활성화해 주어야 합니다. 팔레트를 활성화해 주는 방법은 다음과 같습니다.

mciSendCommand(wDeviceID,MCI_REALIZE,MCI_ANIM_REALIZE_NORM,NULL);

화면 이동하기

avi 동영상이 출력되면서 화면을 이동시키는 방법은 자주 사용되지 않으나 많이 사용할 경우가 있다면 위의 파일을 열 때 사용한 MCI_PUT 명령어를 그대로 사용합니다.

//좌표를 바꾸어 준다.

mciRect.rc.top = x;

mciRect.rc.left= y;

mciRect.rc.right=cx;

mciRect.rc.bottom = cy;

mciSendCommand(wDeviceID,MCI_PUT,MCI_ANIM_PUT_DESTINATION | MCI_ANIM_RECT,

(DWORD) (LPVOID) &mciRect);

처음으로 이동하기

영상이 출력되는 중에 동영상의 맨 처음으로 이동하고 싶을 경우가 있죠. 이 때는 다음과 같이 합니다.

mciSendCommand (wDeviceID, MCI_SEEK, MCI_SEEK_TO_START, (DWORD) (LPVOID) NULL);

끝으로 이동하기

다음은 끝으로 이동할 때 사용하는 방법입니다.

mciSendCommand (wDeviceID, MCI_SEEK, MCI_SEEK_TO_END, (DWORD) (LPVOID) NULL);

다음 프레임으로 이동

다음 프레임으로 이동할 때는 MCI_STEP 명령문을 사용합니다. 이 때 사용하는 파라미터는 MCI_ANIM_STEP_PARMS입니다.

MCI_ANIM_STEP_PARMS StepParms;

MCI_ANIM_STEP_PARMS의 멤버인 dwFrames에 넘어가고자 하는 프레임수를 설정합니다.

StepParms.dwFrames = 5;//5 프레임 다음으로 이전한다.

위와 같이 설정한 후 mciSendCommand를 실행시킵니다.

mciSendCommand (wDeviceID, MCI_STEP, MCI_ANIM_STEP_FRAMES, (DWORD) (LPVOID) &

StepParms);

이전 프레임으로 이동

이전 프레임 이동은 다음 프레임 이동과는 차이가 있습니다. 즉 MCI_STEP이 아닌 MCI_SEEK를 이용합니다. 이 방법은 현재 프레임을 얻은 다음 그 프레임에서 이전으로 이동할 프레임을 설정한 다음 MCI_SEEK를 사용하여 이동하는 것입니다.

MCI_STATUS_PARMS StatusParms;//상태 정보를 얻는 파라미터

// 현재의 포지션을 얻는다.

StatusParms.dwItem = MCI_STATUS_POSITION;

mciSendCommand (wDeviceID, MCI_STATUS, MCI_STATUS_ITEM,

(DWORD) (LPVOID) &StatusParms);

long Position;

Position = StatusParms.dwReturn; //현재 포지션을 얻는다.

MCI_SEEK_PARMS mciSeekParms;

mciSeekParms.dwTo = Position-point; //현재 포지션에서 이전으로 갈 프레임

//만큼 빼준 다음 프레임을 찾아간다.

mciSendCommand(wDeviceID,MCI_SEEK,MCI_TO|MCI_WAIT,

(DWORD)(LPVOID) &mciSeekParms);

일시 정지

일시 정지는 매우 간단합니다. MCI_PAUSE 명령어를 사용하여 다음과 같이 합니다.

mciSendCommand(wDeviceID,MCI_PAUSE,MCI_NOTIFY,

(DWORD)(LPVOID) &mciPlayParms);

MCI 종료

모든 것을 종료할 때에는 다음과 같이 사용합니다.

//mci를 닫는다.

mciSendCommand(wDeviceID,MCI_CLOSE,0,NULL);

동영상에 좀 특이한 점이 있다면 현재 동영상이 재생되고 있을 경우에는 종료되지 않는다는 것입니다.

그렇기 때문에 만약 재생되고 있다면 종료 전에 MCI_PAUSE를 실행시켜야 합니다. 만약 재생 도중에 종료를 시킨다면 윈도에서는 AVI 파일이 계속 출력될 것입니다.

여기서 제작할 예제 ExAvi에서 한번 재생 도중에 mci를 종료해 보세요. 아마 이상한 곳에서 동영상이 계속적으로 출력될 것입니다.

w MCI 이용한 동영상 출력 클래스 만들기

이제 위의 내용을 토대로 동영상 출력 클래스를 만들어 보겠습니다. 여기서 만드는 동영상 출력 클래스의 이름은 CMyVideo입니다. 본 클래스는 avi 동영상을 로드한 후 화면에 재생하고 이전으로, 다음으로, 멈춤, 종료, 특정 위치로 이동 등의 함수가 있습니다.

클래스 헤더

//myvid.h

//avi 파일 출력 클래스 헤더

class CMyVideo

{

public:

CMyVideo (int ,int,int,int,LPSTR lpszFileName,HWND hwnd);

~CMyVideo ();

void MoveWindow(int x,int y,int cx,int cy);//윈도를 이동

void MoveCursor(DWORD posion,HWND hWnd);//프레임을 이동

void Pause();//멈춤

BOOL Play();//재생

BOOL FramePrev (long point);//이전 프레임으로

BOOL Rewind (void);//처음으로

BOOL Foward (void); //끝으로

BOOL FrameNext (long point); //다음으로

protected:

UINT wDeviceID;

MCI_ANIM_OPEN_PARMS mciOpenParms;

MCI_PLAY_PARMS mciPlayParms;

MCI_ANIM_WINDOW_PARMS mciWindow;

MCI_ANIM_RECT_PARMS mciRect;

};


프로그램 소스


//myvid.cpp

//avi 출력 클래스 소스

#include "stdafx.h"

#include "mmsystem.h"

#include "myvid.h"

CMyVideo::CMyVideo(int x,int y,int cx,int cy,LPSTR filename,HWND wnd)

{

SetCursor(LoadCursor(NULL, IDC_WAIT));

mciWindow.hWnd = wnd ;

mciOpenParms.lpstrDeviceType = "AVIVideo";

mciOpenParms.lpstrElementName = filename;

//avi 형태의 파일로 mci를 연다.

mciSendCommand(0,

MCI_OPEN,

MCI_OPEN_ELEMENT | MCI_OPEN_TYPE | MCI_ANIM_OPEN_WS,

(DWORD)(LPVOID) &mciOpenParms);

//오픈된 mci를 받는다.

wDeviceID = mciOpenParms.wDeviceID;

mciRect.rc.top = y;

mciRect.rc.left= x;

mciRect.rc.right=cx;

mciRect.rc.bottom =cy;

//위치를 설정한다.

mciSendCommand(wDeviceID,MCI_PUT,MCI_ANIM_PUT_DESTINATION | MCI_ANIM_RECT,

(DWORD) (LPVOID) &mciRect);

mciSendCommand(wDeviceID,MCI_WINDOW,MCI_ANIM_WINDOW_HWND,

(DWORD)(LPVOID) &mciWindow);

}

CMyVideo::~CMyVideo()

{

//mci를 닫는다.

mciSendCommand(wDeviceID,MCI_CLOSE,0,NULL);

}

BOOL CMyVideo::Play()

{

//화면을 재생시킨다.

mciSendCommand(wDeviceID,MCI_PLAY,MCI_NOTIFY,

(DWORD)(LPVOID) &mciPlayParms);

//avi이기 때문에 팔레트를 다시 활성화시켜 준다.

mciSendCommand(wDeviceID,MCI_REALIZE,MCI_ANIM_REALIZE_NORM,NULL);

return TRUE;

}

void CMyVideo::MoveWindow(int x,int y,int cx,int cy)

{

mciRect.rc.top = x;

mciRect.rc.left= y;

mciRect.rc.right=cx;

mciRect.rc.bottom = cy;

//윈도의 위치를 바꾼다.

mciSendCommand(wDeviceID,MCI_PUT,MCI_ANIM_PUT_DESTINATION | MCI_ANIM_RECT,

(DWORD) (LPVOID) &mciRect);

}

void CMyVideo::MoveCursor(DWORD Position,HWND hWnd)

{

mciPlayParms.dwCallback =(DWORD)(LPVOID)hWnd;

mciPlayParms.dwFrom = Position;

//Avi의 위치를 바꾸어 준다.

mciSendCommand(wDeviceID,MCI_SEEK,MCI_SEEK_TO_START,

(DWORD)(LPVOID) &mciPlayParms);

}

void CMyVideo::Pause()

{

//일시 정지

mciSendCommand(wDeviceID,MCI_PAUSE,MCI_NOTIFY,

(DWORD)(LPVOID) &mciPlayParms);

}

BOOL CMyVideo::Rewind (void)

{

//처음으로 간다.

mciSendCommand (wDeviceID, MCI_SEEK, MCI_SEEK_TO_START, (DWORD) (LPVOID) NULL);

return TRUE;

}

BOOL CMyVideo::Foward (void)

{

//나중으로 간다.

mciSendCommand (wDeviceID, MCI_SEEK, MCI_SEEK_TO_END, (DWORD) (LPVOID) NULL);

return TRUE;

}

//포인트로 정의된 만큼 포인터 이전

BOOL CMyVideo::FrameNext (long point)

{

DWORD RetCode;

MCI_ANIM_STEP_PARMS StepParms;

MCI_STATUS_PARMS StatusParms;

DWORD Length, Position;

//전체 위치를 받는다.

StatusParms.dwItem = MCI_STATUS_LENGTH;

if (RetCode = mciSendCommand (wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD) (LPVOID) &StatusParms)){

return FALSE;

}

Length = StatusParms.dwReturn;

// 현재의 위치를 알아낸다.

StatusParms.dwItem = MCI_STATUS_POSITION;

if (RetCode = mciSendCommand (wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD) (LPVOID) &StatusParms)){

return FALSE;

}

Position = StatusParms.dwReturn;

// 현재 위치가 파일 끝이면

if (Length == Position)

return TRUE;

// 포인트 프레임만큼 이동한다.

StepParms.dwFrames = point;

if (RetCode = mciSendCommand (wDeviceID, MCI_STEP, MCI_ANIM_STEP_FRAMES, (DWORD) (LPVOID) &StepParms)){

return FALSE;

}

return TRUE;

}

//포인트 프레임만큼 이전으로 간다.

BOOL CMyVideo::FramePrev (long point)

{

DWORD RetCode;

MCI_STATUS_PARMS StatusParms;

DWORD Length, Position;

//전체 프레임을 얻는다.

StatusParms.dwItem = MCI_STATUS_LENGTH;

if (RetCode = mciSendCommand (wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD) (LPVOID) &StatusParms)){

return FALSE;

}

Length = StatusParms.dwReturn;

// 현재의 포지션을 얻는다.

StatusParms.dwItem = MCI_STATUS_POSITION;

if (RetCode = mciSendCommand (wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD) (LPVOID) &StatusParms)){

return FALSE;

}

Position = StatusParms.dwReturn;

// 현재의 포지션이 0이면 처음이므로 리턴

if (Position == 0)

return TRUE;

MCI_SEEK_PARMS mciSeekParms;

mciSeekParms.dwTo = Position-point;

//포인트 프레임만큼 이전으로 이동한다.

mciSendCommand(wDeviceID,MCI_SEEK,MCI_TO|MCI_WAIT,

(DWORD)(LPVOID) &mciSeekParms);

return TRUE;

}

e AVI 출력 예제 ExAvi 프로그램

이제 위에서 만든 CMyVideo클래스를 이용하여 Avi를 출력하는 프로그램을 제작해 보겠습니다. 본 프로그램은 ExAvi입니다. 그림 1이 본 프로그램을 실행한 모습입니다. 프로그램의 뷰 화면에서 마우스 좌측 버튼을 클릭하면 현 디렉토리에 있는 seminar.avi 파일을 출력합니다. 메뉴의 AVI VIDEO에서 비디오 보기를 선택하면 그림 1과 같은 대화상자가 뜨고 FILE을 선택하여 Avi 파일을 열고 Play 버튼을 누르면 화면에 동영상이 나타납니다. 본 대화상자는 처음으로, 끝으로, 이전 프레임에서, 다음 프레임으로 멈추기 기능을 가지고 있습니다.


Avi 파일 로드

Avi 파일을 로드하려면 위의 CMyVideo 클래스를 이용하여 다음과 같이 하면 됩니다.

CMyVideo *m_pVideo;

m_pVideo = new CMyVideo(0,0,300,240,"test.avi",this->m_hWnd);

앞의 4개의 정수 인자는 x1, y1과 가로의 길이, 세로의 길이를 설정하는 것이고 그 다음은 파일명이며 그 다음은 hwnd입니다.

Avi 파일 Play

위에서 설정한 함수의 맴버 함수 Play()를 실행시킵니다.

m_pVideo->Play();

Avi 파일 멈춤

Pause 함수를 실행시킵니다.

m_pVideo->Pause();

Avi 파일 처음으로

m_pVideo->Rewind();

Avi 파일 마지막으로

m_pVideo->Foward();

이전 프레임으로

이전으로 넘어가는 함수는 FramePrev입니다. 이 때 인자가 있는데 몇 프레임 이전으로 갈 것인가를 설정해 주는 것입니다. 1 프레임 이전으로 갔을 때는 이전 프레임으로 간 표시가 별로 나지 않습니다.

프로그램의 성격에 맞추어 설정해 주면 됩니다. 본 프로그램에서는 10으로 설정하였습니다.

m_pVideo->FramePrev(10);

다음 프레임으로

다음 프레임으로 가는 함수는 FrameNext입니다. 이 함수도 이전 프레임 함수와 같이 인자가 있습니다.

m_pVideo->FrameNext(10);

끝내기

끝낼 때는 설정된 m_pVideo를 지우기만 하면 됩니다.

delete m_pVideo;


프로그램 소스

// ExAviVw.cpp : implementation of the CExAviView class

//

#include "stdafx.h"

#include "ExAvi.h"

#include "ExAviDoc.h"

#include "mmsystem.h"

#include "myvid.h"

#include "ExAviVw.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

///////////////////////////////////////////////////////////////////////////

// CExAviView

IMPLEMENT_DYNCREATE(CExAviView, CView)

BEGIN_MESSAGE_MAP(CExAviView, CView)

//{{AFX_MSG_MAP(CExAviView)

ON_WM_CREATE()

ON_WM_LBUTTONDOWN()

ON_WM_DESTROY()

ON_COMMAND(ID_VIDEO_SHOW, OnVideoShow)

//}}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////

// CExAviView construction/destruction

CExAviView::CExAviView()

{

// TODO: add construction code here

}

CExAviView::~CExAviView()

{

}

BOOL CExAviView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

// the CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

///////////////////////////////////////////////////////////////////////////

// CExAviView drawing

void CExAviView::OnDraw(CDC* pDC)

{

CExAviDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

pDC->TextOut(0,0,"Click Left Mouse Button!");

// TODO: add draw code for native data here

}

///////////////////////////////////////////////////////////////////////////

// CExAviView printing

BOOL CExAviView::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CExAviView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CExAviView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

///////////////////////////////////////////////////////////////////////////

// CExAviView diagnostics

#ifdef _DEBUG

void CExAviView::AssertValid() const

{

CView::AssertValid();

}

void CExAviView::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CExAviDoc* CExAviView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CExAviDoc)));

return (CExAviDoc*)m_pDocument;

}

#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////

// CExAviView message handlers

int CExAviView::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CView::OnCreate(lpCreateStruct) == -1)

return -1;

//avi 파일을 로드한다.

m_pVideo= new CMyVideo(0,20,300,240,"seminar.avi",this->m_hWnd);

return 0;

}

void CExAviView::OnLButtonDown(UINT nFlags, CPoint point)

{

// TODO: Add your message handler code here and/or call default

//버튼을 누르면 맨 처음으로 가서 재생시킨다.

m_pVideo->Rewind();

m_pVideo->Play();

CView::OnLButtonDown(nFlags, point);

}

void CExAviView::OnDestroy()

{

CView::OnDestroy();

delete m_pVideo;

}

#include "showavi.h"

void CExAviView::OnVideoShow()

{

//비디오 보기 대화상자 실행

CShowAvi *pDlg=new CShowAvi;

pDlg->DoModal();

delete pDlg;

}

// ShowAvi.h : header file

//

///////////////////////////////////////////////////////////////////////////

// CShowAvi dialog

class CShowAvi : public CDialog

{

// Construction

public:

CShowAvi(CWnd* pParent = NULL); // standard constructor

// Dialog Data

//{{AFX_DATA(CShowAvi)

enum { IDD = IDD_VIDEO };

// NOTE: the ClassWizard will add data members here

//}}AFX_DATA

//avi를 로드시킬 클래스

CMyVideo *m_pVideo;

//화면에 출력할 좌표

CRect m_rectBox;

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CShowAvi)

protected:

virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

// Generated message map functions

//{{AFX_MSG(CShowAvi)

virtual BOOL OnInitDialog();

afx_msg void OnFile();

afx_msg void OnEnd();

afx_msg void OnHome();

afx_msg void OnNext();

afx_msg void OnPrev();

afx_msg void OnPause();

afx_msg void OnPlay();

afx_msg void OnClose();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

// ShowAvi.cpp : implementation file

//

#include "stdafx.h"

#include "ExAvi.h"

#include "mmsystem.h"

#include "myvid.h"

#include "ShowAvi.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

///////////////////////////////////////////////////////////////////////////

// CShowAvi dialog

CShowAvi::CShowAvi(CWnd* pParent /*=NULL*/)

: CDialog(CShowAvi::IDD, pParent)

{

//{{AFX_DATA_INIT(CShowAvi)

// NOTE: the ClassWizard will add member initialization here

//}}AFX_DATA_INIT

m_pVideo=NULL;

}

void CShowAvi::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CShowAvi)

// NOTE: the ClassWizard will add DDX and DDV calls here

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CShowAvi, CDialog)

//{{AFX_MSG_MAP(CShowAvi)

ON_BN_CLICKED(IDC_FILE, OnFile)

ON_BN_CLICKED(IDC_END, OnEnd)

ON_BN_CLICKED(IDC_HOME, OnHome)

ON_BN_CLICKED(IDC_NEXT, OnNext)

ON_BN_CLICKED(IDC_PREV, OnPrev)

ON_BN_CLICKED(IDC_PAUSE, OnPause)

ON_BN_CLICKED(IDC_PLAY, OnPlay)

ON_WM_CLOSE()

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////

// CShowAvi message handlers

BOOL CShowAvi::OnInitDialog()

{

CDialog::OnInitDialog();

//동영상이 위치할 좌표를 얻음.

CWnd *pWnd = GetDlgItem(IDC_VIDEORECT);

pWnd->GetWindowRect(&m_rectBox);

ScreenToClient(&m_rectBox);

//모든 버튼을 사용할 수 없게 함.

GetDlgItem(IDC_PLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_PAUSE)->EnableWindow(FALSE);

GetDlgItem(IDC_PREV)->EnableWindow(FALSE);

GetDlgItem(IDC_NEXT)->EnableWindow(FALSE);

GetDlgItem(IDC_HOME)->EnableWindow(FALSE);

GetDlgItem(IDC_END)->EnableWindow(FALSE);

m_pVideo=NULL;

return TRUE; // return TRUE unless you set the focus to a control

// EXCEPTION: OCX Property Pages should return FALSE

}

void CShowAvi::OnFile()

{

CFileDialog dlg(TRUE,_T("AVI"),_T("*.AVI"),OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,

_T("동영상 파일(*.AVI)|*.AVI|"));

if(dlg.DoModal()==IDOK)

{

//avi 파일명을 받은 다음

CString m_strFileName;

m_strFileName=dlg.GetPathName();

if(m_pVideo!=NULL)

delete m_pVideo;

else

{

//모든 버튼을 활성화시킴.

GetDlgItem(IDC_PLAY)->EnableWindow(TRUE);

GetDlgItem(IDC_PAUSE)->EnableWindow(TRUE);

GetDlgItem(IDC_PREV)->EnableWindow(TRUE);

GetDlgItem(IDC_NEXT)->EnableWindow(TRUE);

GetDlgItem(IDC_HOME)->EnableWindow(TRUE);

GetDlgItem(IDC_END)->EnableWindow(TRUE);

}

//avi 파일을 로드한다.

m_pVideo= new CMyVideo(m_rectBox.left,

m_rectBox.top,

m_rectBox.right-m_rectBox.left,

m_rectBox.bottom-m_rectBox.top,

(\\\FileName.operator const char*(),

this->m_hWnd);

}

}

//끝으로

void CShowAvi::OnEnd()

{

m_pVideo->Foward();

}

//처음으로

void CShowAvi::OnHome()

{

m_pVideo->Rewind();

}

//다음으로

void CShowAvi::OnNext()

{

m_pVideo->FrameNext(10);

}

//이전으로

void CShowAvi::OnPrev()

{

m_pVideo->FramePrev(10);

}

//멈춤

void CShowAvi::OnPause()

{

m_pVideo->Pause();

}

//Play

void CShowAvi::OnPlay()

{

m_pVideo->Play();

}

//대화상자가 종료될 때

void CShowAvi::OnClose()

{

if(m_pVideo)

delete m_pVideo;

CDialog::OnClose();

}