본문 바로가기

Application Programming Interface/Windows API

waveOut 함수 사용 예제 #2

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

 이번에는 웨이브 출력에 따라 콜백함수를 선언해 호출되도록 하겠습니다. (첨부파일:main.zip)

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

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <assert.h>
#include <conio.h>
#include <tchar.h>

#pragma pack(push, 1)
typedef struct
{
	CHAR szChunkID[4]; // 항상 'R', 'I', 'F', 'F'라는 4개의 ASCII 문자가 옵니다.
	DWORD dwChunkSize; // 이 필드 바로 다음부터 파일의 맨 끝까지의 크기를 저장합니다.
	CHAR dwFormat[4]; // 이 필드 다음에 오는 형식의 종류를 지정합니다. 여기에서는 'W', 'A', 'V', 'E'의 4개의 ASCII 문자가 옵니다.
} RIFF_HEADER;
#pragma pack(pop)

#pragma pack(push, 1)
typedef struct
{
	CHAR szChunkID[4]; // 항상 'f', 'm', 't', ' '라는 4개의 ASCII 문자가 옵니다. 맨 마지막 글자는 공백(0x20)입니다.
	DWORD dwChunkSize; // 이 필드 바로 다음부터 파일의 맨 끝까지의 크기를 저장합니다.
	WORD wFormatTag; // 데이터의 종류를 지정합니다. PCM 형식은 1입니다.
	WORD nChannels; // 채널의 수를 지정합니다.
	DWORD nSamplesPerSec; // 초당 샘플링 회수를 지정합니다.
	DWORD nAvgBytesPerSec; // 1초당 소리 크기를 바이트 수로 지정합니다.
	WORD nBlockAlign; // 채널의 총 수를 고려하여 sample 1회당 바이트 수를 나타낸 것입니다.
	WORD wBitsPerSample; // 1개 채널당 sample 1회당 바이트 수를 나타낸 것입니다.
} RIFF_FORMAT;
#pragma pack(pop)

#pragma pack(push, 1)
typedef struct
{
	CHAR szChunkID[4]; // 항상 'd', 'a', 't', 'a'라는 4개의 ASCII 문자가 옵니다.
	DWORD dwChunkSize; // 이 필드 바로 다음부터 파일의 맨 끝까지의 크기를 저장합니다. 헤더 정보를 뺀 순수 데이터의 크기입니다.
} RIFF_DATA;
#pragma pack(pop)

RIFF_HEADER riffHeader;
RIFF_FORMAT riffFormat;
RIFF_DATA riffData;

void CALLBACK WaveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
	static WAVEHDR waveHeader;

	switch (uMsg)
	{
	case WOM_OPEN: // waveOutOpen 함수가 실행되었을 경우에는 여기 부분이 처리
		_tprintf(TEXT("waveOutProc: WOM_OPEN\n"));
		break;
	case WOM_DONE: // waveOutWrite 함수로 작성한 데이터의 재생이 완료되었을 경우에는 여기 부분이 처리
		_tprintf(TEXT("waveOutProc: WOM_DONE\n"));
		break;
	case WOM_CLOSE: // waveOutClose 함수가 실행되었을 경우에는 여기 부분이 처리
		_tprintf(TEXT("waveOutProc: WOM_CLOSE\n"));
		break;
	default:
		break;
	}
}

int main(int argc, char * argv[])
{
	TCHAR szWaveFile[MAX_PATH];
	HANDLE hWaveFile = NULL;
	DWORD dwWaveFileSize = 0;
	DWORD dwWaveFileRead = 0;

	DWORD dwWaveData = 0;
	HGLOBAL hWaveData = NULL;
	LPBYTE lpWaveData = NULL;

	HWAVEOUT hWaveOut = NULL;
	WAVEFORMATEX waveFormatEx;

	DWORD dwDelay = 0;
	WAVEHDR waveHeader;

	_tsetlocale(LC_ALL, TEXT(""));
	_tcscpy(szWaveFile, TEXT("track01.wav"));

	hWaveFile = CreateFile(szWaveFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); assert(hWaveFile != NULL);
	{
		dwWaveFileSize = GetFileSize(hWaveFile, NULL); assert(dwWaveFileSize > 0);
	}
	CloseHandle(hWaveFile);

	hWaveData = GlobalAlloc(GMEM_MOVEABLE, dwWaveFileSize); assert(hWaveData != NULL);
	{
		// 파일을 읽어 메모리에 복사하기
		lpWaveData = GlobalLock(hWaveData); assert(lpWaveData);
		hWaveFile = CreateFile(szWaveFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); assert(hWaveFile != NULL);
		{
			SetFilePointer(hWaveFile, 0, NULL, SEEK_SET);
			ReadFile(hWaveFile, lpWaveData, dwWaveFileSize, &dwWaveFileRead, NULL); assert(dwWaveFileSize == dwWaveFileRead);
		}
		assert(CloseHandle(hWaveFile) != 0);
		assert(GlobalUnlock(hWaveData) == 0);

		// 헤더 부분을 읽어 구조체로 복사하기
		lpWaveData = GlobalLock(hWaveData); assert(lpWaveData != NULL);
		{
			CopyMemory(&riffHeader, lpWaveData, sizeof(RIFF_HEADER));
			lpWaveData += sizeof(RIFF_HEADER);

			CopyMemory(&riffFormat, lpWaveData, sizeof(RIFF_FORMAT));
			lpWaveData += sizeof(RIFF_FORMAT);

			CopyMemory(&riffData, lpWaveData, sizeof(RIFF_DATA));
			lpWaveData += sizeof(RIFF_DATA);
		}
		assert(GlobalUnlock(hWaveData) == 0);
		
		// 웨이브 출력 장치 열기
		lpWaveData = GlobalLock(hWaveData); assert(lpWaveData != NULL);
		{
			// 읽은 데이터 중 일부를 WAVEFORMAT 구조체로 옮김
			waveFormatEx.nAvgBytesPerSec = riffFormat.nAvgBytesPerSec;
			waveFormatEx.nBlockAlign = riffFormat.nBlockAlign;
			waveFormatEx.nChannels = riffFormat.nChannels;
			waveFormatEx.nSamplesPerSec = riffFormat.nSamplesPerSec;
			waveFormatEx.wFormatTag = riffFormat.wFormatTag;
			waveFormatEx.wBitsPerSample = riffFormat.wBitsPerSample;
			waveFormatEx.cbSize = 0;

			// 소리 출력 장치를 열기
			assert(waveOutOpen(&hWaveOut, WAVE_MAPPER, &waveFormatEx, (DWORD)WaveOutProc, (DWORD)lpWaveData, CALLBACK_FUNCTION) == MMSYSERR_NOERROR);
			{
				// 1 [sec]당 소요된 바이트 수를 구합니다.
				// (1회 샘플크기[비트] / 8) * (1[sec]당 샘플 회수) * (채널 수:1=모노, 2=스테레오)
				dwDelay = (riffFormat.wBitsPerSample / 8) * riffFormat.nSamplesPerSec * riffFormat.nChannels;
				_tprintf(TEXT("bytesPerSec = %d\n"), dwDelay);

				// 프로그램이 종료되는 것을 지연시키는 시간을 구합니다.
				// ("data"로 시작하는 구조체의 dwChunkSize값[바이트]) / (1[sec]당 소요된 바이트)
				dwDelay = riffData.dwChunkSize / dwDelay;
				_tprintf(TEXT("TotalSec = %d (%02d:%02d)\n"), dwDelay, (dwDelay / 60), (dwDelay % 60));

				waveHeader.lpData = (LPVOID)lpWaveData; // 소리 데이터가 있는 위치
				waveHeader.dwBufferLength = riffData.dwChunkSize; // 소리 데이터의 크기
				waveHeader.dwBytesRecorded = 0;
				waveHeader.dwFlags = 0L;
				waveHeader.dwLoops = 0L;
				waveHeader.dwUser = 0;
				waveHeader.lpNext = NULL;
				waveHeader.reserved = 0L;

				waveOutPrepareHeader(hWaveOut, &waveHeader, sizeof(WAVEHDR));
				{
					waveOutWrite(hWaveOut, &waveHeader, sizeof(WAVEHDR));
					// 소리가 완전히 끝날때까지 프로그램 종료를 지연 (1000[msec]의 여유 시분을 더 추가함)
					Sleep(dwDelay * 1000 + 1000);
				}
				waveOutUnprepareHeader(hWaveOut, &waveHeader, sizeof(WAVEHDR));
			}
			assert(waveOutClose(hWaveOut) == MMSYSERR_NOERROR);
		}
		assert(GlobalUnlock(hWaveData) == 0);
	}
	assert(GlobalFree(hWaveData) == NULL);

	_tprintf(TEXT("END"));
	_getch();

	return 0;
}

실행결과: WOM_OPEN과 WOM_DONE 문장 사이에 사운드가 출력됩니다.