안녕하세요. 명월입니다.
이 글은 C++에서 wav파일을 재생하는 방법(MCI - 미디어 컨트롤 인터페이스)에 대한 글입니다.
이전에 제가 WinApi의 MCI를 이용해서 마이크를 통해 음성을 녹음하고 파일로 저장하는 방법에 대해서 글을 작성한 적이 있습니다.
링크 - [C++] 녹음기 프로그램 작성하는 방법(MCI - 미디어 컨트롤 인터페이스)
이번에는 그 반대입니다. 파일을 읽어와서 스피커로 파일을 재생하는 방법입니다.
소스는 녹음하는 것과 정반대라고 생각하면 됩니다.
#include <iostream>
#include <Windows.h>
#include <fstream>
using namespace std;
// wav에 접근하기 위해서는 필요합니다.
#pragma comment(lib, "winmm.lib")
// 함수 예약 - 녹음된 데이터를 파일로 작성하기 위한 함수
int OpenWavFile(const char*, WAVEFORMATEX*, PWAVEHDR);
// 메모리 구조체
typedef struct BufferNode
{
int length;
void* buffer;
};
// 시작 함수
int main()
{
// 음성 포멧 지정
WAVEFORMATEX pFormat;
// 음성 데이터 구조체
WAVEHDR WaveOutHdr;
// 파일로부터 음성 포멧과 데이터를 취득한다.
// 총 음성 길이를 받는다.
int length = OpenWavFile("c:\\work\\temp.wav", &pFormat, &WaveOutHdr);
// WAVE_FORMAT_PCM에서는 무시되는 값
pFormat.cbSize = 0;
// WAVE_FORMAT_PCM이라면 무압축이기 때문에 nSamplesPerSec와 같을 것이다.
pFormat.nAvgBytesPerSec = pFormat.nSamplesPerSec;
// 라이브러리에서 실제 녹음된 사이즈를 구하는 함수(사용자가 사용하는 값이 아님)
WaveOutHdr.dwBytesRecorded = 0;
// 라이브러리에서 callback 함수 사용시 사용되는 status flag
WaveOutHdr.dwFlags = 0;
// 반복 재생시 사용됨 (사용하지 않는다.)
WaveOutHdr.dwLoops = 0;
// 예약 재생시 사용됨 (사용하지 않는다.)
WaveOutHdr.reserved = 0;
// 음성을 출력하는 장치 구조체
HWAVEOUT hWaveOut;
cout << "Playing..." << endl;
// waveOutOpen는 위 구조체로 장치를 Open하는 함수.
// 파라미터는 HWAVEOUT, 두번째는 장치 선택인데 보통은 WAVE_MAPPER를 넣어도 됩니다.
// 저는 특이하게 스피커가 여러개 있어서(노트북 기본 스피커, 해드셋 스피커) 가장 나중에 접속한 단자를 선택했습니다.
// 세번째 파라미터는 음성 포멧을 넣습니다.
// 마지막 파라미터는 WAVE_FORMAT_DIRECT를 설정해서 동기화된 소스를 작성합니다.
if (waveOutOpen(&hWaveOut, waveInGetNumDevs() - 1 /*WAVE_MAPPER*/, &pFormat, 0, 0, WAVE_FORMAT_DIRECT))
{
// 에러 콘솔 출력
cout << "Failed to open waveform output device." << endl;
// 접속 실패
return 1;
}
// 장치에 출력 준비를 알리는 함수
if (waveOutPrepareHeader(hWaveOut, &WaveOutHdr, sizeof(WAVEHDR)))
{
// 에러 콘솔 출력
cout << "waveOutPrepareHeader error" << endl;
// 장치 닫기
waveOutClose(hWaveOut);
return 1;
}
// 출력 시작
if (waveOutWrite(hWaveOut, &WaveOutHdr, sizeof(WAVEHDR)))
{
// 에러 콘솔 출력
cout << "waveOutWrite error" << endl;
// 장치 닫기
waveOutClose(hWaveOut);
return 1;
}
// WAVE_FORMAT_DIRECT를 선택했기 때문에 출력이 끝날때까지 기다려야 합니다.
// length로 음성의 길이를 알기 때문에 1초단위로 콘솔에 표시합니다.
for (int i = 0; i <= length; i++)
{
cout << "\r";
// 플레이 초 표시
cout << "Sec - " << i;
Sleep(1000);
}
// 장치 닫기
waveOutClose(hWaveOut);
return 0;
}
int OpenWavFile(const char* filename, WAVEFORMATEX* format, PWAVEHDR WaveHeader)
{
ifstream istream;
istream.open(filename, fstream::binary);
// wav파일 구조체대로 작성한다.
istream.seekg(0, ios::beg);
// chunk id
char riff[5];
memset(riff, 0x00, 5);
istream.read(riff, 4);
cout << "chunk id - " << riff << endl;
int chunksize;
// chunk size (36 + SubChunk2Size))
istream.read((char*)&chunksize, 4);
cout << "chunksize " << chunksize << endl;
// format
char wave[5];
memset(wave, 0x00, 5);
istream.read(wave, 4);
cout << "format - " << wave << endl;
// subchunk1ID - fmt
char subchunk1ID[5];
memset(subchunk1ID, 0x00, 5);
istream.read(subchunk1ID, 4);
cout << "subchunk1ID - " << subchunk1ID << endl;
// subchunk1size (무압축 PCM이면 16 고정)
int subchunk1size;
istream.read((char*)&subchunk1size, 4);
cout << "subchunk1size (fixed - 16) - " << subchunk1size << endl;
// AudioFormat (무압축 PCM이면 1 고정)
istream.read((char*)&format->wFormatTag, 2);
cout << "format->wFormatTag (fixed - 1) - " << format->wFormatTag << endl;
// NumChannels
istream.read((char*)&format->nChannels, 2);
cout << "format->nChannels - " << format->nChannels << endl;
// sample rate
istream.read((char*)&format->nSamplesPerSec, 4);
cout << "format->nSamplesPerSec - " << format->nSamplesPerSec << endl;
// byte rate (SampleRate * block align)
int byteRate;
istream.read((char*)&byteRate, 4);
// block align
istream.read((char*)&format->nBlockAlign, 2);
cout << "byteRate - " << byteRate << " = format->nSamplesPerSec * format->nBlockAlign - " << format->nSamplesPerSec * format->nBlockAlign << endl;
// bits per sample
istream.read((char*)&format->wBitsPerSample, 2);
cout << "format->wBitsPerSample - " << format->wBitsPerSample << endl;
// subchunk2ID
char data[5];
memset(data, 0x00, 5);
istream.read(data, 4);
cout << "data - " << data << endl;
// subchunk2size (NumSamples * nBlockAlign)
int subchunk2size;
istream.read((char*)&subchunk2size, 4);
cout << "chunksize - " << chunksize << " = 36 + chunksize = subchunk2size - " << subchunk2size << endl;
// 실제 음악 데이터 읽어오기
WaveHeader->dwBufferLength = subchunk2size / format->nChannels;
WaveHeader->lpData = (char*)malloc(WaveHeader->dwBufferLength);
istream.read(WaveHeader->lpData, WaveHeader->dwBufferLength);
// 파일 닫기
istream.close();
// byteRate는 1초의 데이터 길이
// WaveHeader->dwBufferLength는 데이터의 전체 길이
// WaveHeader->dwBufferLength / byteRate는 wav의 음악 길이가 나온다.
return WaveHeader->dwBufferLength / byteRate;
}
녹음을 할 때는 waveInOpen의 설정을 CALLBACK_FUNCTION로 하여 Callback 함수에서 데이터를 받는 것으로 설정했었습니다.
녹음은 우리가 시간을 정해서 녹음하는 것이 아니라, 그 길이를 예측할 수 없기 때문에 1초 단위로 데이터를 받아서 realloc으로 데이터를 만드는 작업을 했었습니다.
그러나 재생은 파일에 이미 총 데이터 길이가 정해져 있기 때문에 Callback으로 1초단위로 재생할 필요없이 한번에 Single thread에서 데이터를 읽어도 문제가 없기 때문에 WAVE_FORMAT_DIRECT로 설정했습니다.
그러나 내부적으로는 Thread로 재생이 되는 듯하니 Single thread가 재생이 끝날 때까지 기다려야 할 필요는 있습니다.
참조 - https://wiki.fileformat.com/audio/wav/
여기까지 C++에서 wav파일을 재생하는 방법(MCI - 미디어 컨트롤 인터페이스)에 대한 글이었습니다.
궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.
'Development note > C , C++ , MFC' 카테고리의 다른 글
[C++] 테스리스 게임 만들기 (0) | 2021.03.11 |
---|---|
[C++] Wav 구조체를 이용한 음성 채팅(MCI - 미디어 컨트롤 인터페이스) (0) | 2020.05.25 |
[C++] Wave 파일 믹싱(Mixing) (0) | 2020.05.19 |
[C++] Window 음성 레코더 프로그램 작성하는 방법(스팩트럼 미완성) (0) | 2020.05.11 |
[C++] 녹음기 프로그램 작성하는 방법(MCI - 미디어 컨트롤 인터페이스) (1) | 2020.04.29 |
[C++] opencv를 사용하는 방법(image를 동영상, 캠 이미지를 동영상으로 작성) (0) | 2020.04.27 |
[C++] Bitmap 파일 작성과 읽는 방법(화면 스크린 샷을 하는 방법) (0) | 2020.04.27 |
[C++] 필수 라이브러리 Boost 설치하기(boost::asio::threadpool 예제) (0) | 2020.04.23 |