Sound Programming 제 8 장 Ogg Vorbis Playback in OpenAL 1 Overview of Ogg Vorbis An open-source sound codec created by Xiphophorus www.xiph.org popular on Linux due to its open-source heritage an alternative to MP3 Wav vs. Ogg Vorbis Ogg Vorbis allows you to decode the format yourself without having to pay a fee. Ogg Vorbis vs. MP3 vs. WMA? Which one do you like better? 2
Ogg Vorbis 의구조 Ogg Vorbis API 는크게 4 개의라이브러리로구성 Ogg library Ogg 파일을다루기위한 general-purpose routine들을지원 Ogg 라이브러리는 Vorbis를알지못함. Ogg 라이브러리는실제다루는파일이사운드인지동영상인지, 이미지인지도구분하지않음 멀티미디어데이터처리를위한 base framework 를제공할뿐임. Vorbis library Vorbisenc 를이용하여 Vorbis 방식으로압축된사운드스트림을디코딩할때필요한함수들을제공한다. 3 Ogg Vorbis 의구조 Ogg Vorbis 의구조 ( 계속 ) Vorbisenc library Vorbis 파일로의인코딩을지원하는라이브러리 Vorbis 인코더함수일체를지원한다. CD를 Ogg Vorgis 파일로만든다거나, Wav 파일을 Ogg Vorbis 파일로만드는등의일을할때에는꼭필요한라이브러리임. Vorbisfile library Ogg Vorbis 파일을쉽게다룰수있게해주는편의라이브러리 Ogg와 Vorbis 라이브러리들을감싸고있으며, 사용하기편리한인터페이스를제공한다. 특히, Vorbis 파일을디코딩할때편리한기능을제공 4
Using the Vorbisfile API Ogg Vorbis API 를감싸매우사용하기편하게만들어놓은라이브러리 Ogg Vorbis 파일을읽고압축을해제하여, 이를 PCM 스트림으로변환하는과정을아주편리한몇개의함수만으로지원함. Vorbisfile API를사용하는 flowchart 이번장에서우리는 Ogg Vorbis API 중 Vorbisfile API 만을사용할것임! 5 Using the Vorbisfile API Setup/Teardown API ov_open_callbacks() 주어진 file handle로부터 Ogg Vorbis bitstream을초기화하고, custom file/bitstream manipulation 루틴들을초기화한다. int ov_open_callbacks( void *datasource, // 이미오픈된.ogg 파일의 FILE * 를넘기면됨 OggVorbis_File *vf, // 정보가채워질 OggVorbis_File 구조체를가리키는포인터 char *initial, // Typically set to NULL long ibytes, // Typically set to 0 ov_callbacks callbacks // A completed ov_callbacks struct which indicates desired // custom file manipulation routines ); 리턴값 0 for success less than zero for failure 6
Using the Vorbisfile API Setup/Teardown API ( 계속 ) ov_open_callbacks() ( 계속 ) ov_callbacks 구조체 typedef struct size_t (*read_func) (void *ptr, size_t size, size_t nmemb, void *datasource); int (*seek_func) (void *datasource, ogg_int64_t offset, int whence); int (*close_func) (void *datasource); long 0 (*tell_func) for success (void *datasource); ov_callbacks; less than zero for failure ov_clear() bitstream 과.ogg 파일을닫고, cleans up loose ends. Must be called when finished with the bitstream int ov_clear(oggvorbis_file *vf); 7 Using the Vorbisfile API ogg 파일정보얻기 ov_info() 주어진 ogg bitstream 의정보를담은 vorbis_info 구조체포인터를리턴한다. vorbis_info *ov_info( OggVorbis_File *vf, // OggVorbis_File 구조체를가리키는포인터 : // ov_open_callbacks() 으로부터넘겨받은것이어야함. int link // 대부분의경우 -1 ); 리턴값 : 정보가채워진 vorbis_info 구조체를가리키는포인터 vorbis_info 구조체 : vorbis/codec.h 파일에정의되어있음 파일버전, 채널수, sample rate, Bitrate(CBR 인경우 ) 등의정보를담고있음. 8
Using the Vorbisfile API ogg 파일정보얻기 ( 계속 ) ov_pcm_total() 주어진 ogg bitstream에있는총pcm 샘플링횟수를리턴한다. 디코딩했을때차지할바이트수계산에활용할수있음. ogg_int64_t ov_pcm_total( OggVorbis_File *vf, int link // 대부분의경우 -1 ); 9 Using the Vorbisfile API Decoding ov_read() long ov_read( OggVorbis_File *vf, // OggVorbis_File 구조체를가리키는포인터 char *buffer, // 압축해제된 PCM 데이터가저장될버퍼포인터 int length, // buffer로읽어들일바이트수. 버퍼의크기와같은크기 // 이어야함. 대개 4096이면적당. int bigendianp, // Specifies big or little endian byte packing. // 0 for little endian, 1 for big endian. 보통은 0. int word, // word 크기를지정한다. 1 for 8-bit samples, // or 2 for 16-bit samples. 보통은 2. int sgned, // Signed or unsigned data. // 0 for unsigned, 1 for signed. 보통은 1. int *bitstream); // A pointer to the number of the current logical bitstream. 응용프로그램에서 ogg 파일을디코딩할때사용되는주함수. 압축해제된 PCM 오디오데이터를지정된길이만큼리턴한다. endianness, signedness, 그리고 word 크기도고려함. 인코딩된오디오가 multichannel 이면, 각채널들이출력버퍼에인터리빙되어채워진다. 10
Using the Vorbisfile API Decoding ( 계속 ) ov_read()( 계속 ) Return Values OV_HOLE 파일안에빈곳이있어디코딩에실패함 ( 사유 : 파일페이지사이에쓰레기데이터가있음, recapture가바로따라오는바람에동기화정보가사라짐, 또는파일페이지자체가깨짐 ) OV_EBADLINK indicates that an invalid stream section was supplied to libvorbisfile, or the requested link is corrupt. 0 indicates EOF n indicates actual number of bytes read ov_read() 는일단호출되면 vorbis packet 단위로디코딩하기때문에, 이리턴값이인자에지정한 length 값보다작을수있음. 11.ogg 파일재생과정 단계 1:.ogg Wav 스트림변환과정 Ogg Vorbis 파일을 open하고 decoding 한다. 디코딩된 Ogg Vorbis 스트림은압축되어있지않은 PCM stream 이라고할수있다. 단계 2: 이 PCM stream을 OpenAL Buffer에담는다. 단계 3: OpenAL Buffer와 Source를연결하여재생 12
8 장예제 (PlayOggVorbis) main() #include "Framework.h" #include "Vorbis\vorbisfile.h" #include "alloadoggvorbis.h" #define SERVICE_UPDATE_PERIOD 250 #define TEST_OGGVORBIS_FILE " 아이유-06-삼촌 (Feat. 이적 ).ogg" int main() ALuint uibuffer; ALuint uisource; Alint istate; // Initialize Framework ALFWInit(); ALFWprintf("PlayOggVorbis Test Application\n"); // Initialize OggVorbis libary InitVorbisFile(); if (!g_bvorbisinit) ALFWprintf("Failed to find OggVorbis DLLs (vorbisfile.dll,ogg.dll,or vorbis.dll)\n"); ALFWShutdown(); // Initialise OpenAL if (!ALFWInitOpenAL()) ALFWprintf("Failed to initialize OpenAL\n"); ALFWShutdown(); 13 8 장예제 (PlayOggVorbis) main() ( 계속 ) // Open the OggVorbis file FILE *poggvorbisfile = fopen(alfwaddmediapath(test_oggvorbis_file), "rb"); if (!poggvorbisfile) ALFWprintf("Could not find %s\n", ALFWaddMediaPath(TEST_OGGVORBIS_FILE)); ShutdownVorbisFile(); ALFWShutdownOpenAL(); ALFWShutdown(); // ( 가장중요한부분 ) OggVorbis 스트림을디코딩하여 Wav PCM 스트림으로바꾸고 // OpenAL buffer 에이를채운후 buffer ID 를리턴한다. uibuffer = LoadOggVorbis(pOggVorbisFile); if (uibuffer < 0) ALFWprintf("Could not decode %s to PCM.\n", ALFWaddMediaPath(TEST_OGGVORBIS_FILE)); ShutdownVorbisFile(); ALFWShutdownOpenAL(); ALFWShutdown(); // Generate a Source to playback the Buffers algensources( 1, &uisource ); // 소스와버퍼를연결한다. alsourcei(uisource, AL_BUFFER, uibuffer); // Start playing source alsourceplay(uisource); 14
8 장예제 (PlayOggVorbis) main() ( 계속 ) // 종료검사 while (!ALFWKeyPress()) ALFWprintf("."); Sleep( SERVICE_UPDATE_PERIOD ); algetsourcei(uisource, AL_SOURCE_STATE, &istate); if (istate!= AL_PLAYING) // Finished playing break; ALFWGetKey(); ALFWprintf("\n"); // Stop the Source and clear the Queue alsourcestop(uisource); // Clean up buffers and sources aldeletesources(1, &uisource); aldeletebuffers(1, &uibuffer); // Shutdown VorbisFile Library ShutdownVorbisFile(); // Shutdown AL ALFWShutdownOpenAL(); // Shutdown Framework ALFWShutdown(); return 0; 15 8 장예제 (PlayOggVorbis) 의중요함수들 // Try and load Vorbis DLLs (VorbisFile.dll will load ogg.dll and vorbis.dll) void InitVorbisFile(void) if (g_bvorbisinit) return; g_hvorbisfiledll = LoadLibrary("vorbisfile.dll"); if (g_hvorbisfiledll) fn_ov_clear = (LPOVCLEAR)GetProcAddress(g_hVorbisFileDLL, "ov_clear"); fn_ov_read = (LPOVREAD)GetProcAddress(g_hVorbisFileDLL, "ov_read"); fn_ov_pcm_total = (LPOVPCMTOTAL)GetProcAddress(g_hVorbisFileDLL, "ov_pcm_total"); fn_ov_info = (LPOVINFO)GetProcAddress(g_hVorbisFileDLL, "ov_info"); fn_ov_comment = (LPOVCOMMENT)GetProcAddress(g_hVorbisFileDLL, "ov_comment"); fn_ov_open_callbacks = (LPOVOPENCALLBACKS)GetProcAddress(g_hVorbisFileDLL, "ov_open_callbacks"); if (fn_ov_clear && fn_ov_read && fn_ov_pcm_total && fn_ov_info && fn_ov_comment && fn_ov_open_callbacks) g_bvorbisinit = true; // 동적으로로드했던 vorbisfile.dll 을내린다. ogg.dll 과 vorbis.dll 도같이자동으로내려감. void ShutdownVorbisFile(void) if (g_hvorbisfiledll) FreeLibrary(g_hVorbisFileDLL); g_hvorbisfiledll = NULL; g_bvorbisinit = false; 16
8 장예제 (PlayOggVorbis) 의중요함수들 ogg 파일로딩 & 디코딩 & OpenAL Buffer 생성 // ogg 파일을읽고디코딩한후메모리에담는다. 그런후 OpenAL buffer를생성하고이 buffer에 // 디코딩한 Wav PCM 데이터를넣고이 buffer의 ID를리턴한다. ALint LoadOggVorbis(FILE *f) OggVorbis_File vf; // Ogg 파일객체정의 vorbis_info *vi; // ogg 파일정보객체 ov_callbacks scallbacks; ALuint uibuffer; unsigned long ulformat = 0; unsigned long ulbuffersize = 0; unsigned long ulfrequency = 0; unsigned long ulchannels = 0; unsigned long ultotalsamples = 0; unsigned long ulbyteswritten; char *pdecodebuffer; scallbacks.read_func = ov_read_func; scallbacks.seek_func = ov_seek_func; scallbacks.close_func = ov_close_func; scallbacks.tell_func = ov_tell_func; // Create an OggVorbis file stream if (fn_ov_open_callbacks(f, &vf, NULL, 0, scallbacks)!= 0) ALFWprintf("LoadOggVorbis: Input file does not appear to be an Ogg bitstream.\n"); fclose(f); vi = fn_ov_info(&vf, -1); // ogg 파일정보를얻는다. 17 8 장예제 (PlayOggVorbis) 의중요함수들 ogg 파일로딩 & 디코딩 & OpenAL Buffer 생성 ( 계속 ) ultotalsamples = (unsigned int)fn_ov_pcm_total(&vf, -1); // 샘플링전체개수 ulchannels = vi->channels; // 채널수 ulfrequency = vi->rate; // 초당샘플링횟수 ulbuffersize = ultotalsamples * ulchannels * 2; // 압축해제후의 PCM 데이터크기 // 주의 : The Buffer Size must be an exact multiple of the BlockAlignment... ulbuffersize -= (ulbuffersize % (ulchannels * 2)); if (ulchannels == 1) ulformat = AL_FORMAT_MONO16; else if (ulchannels == 2) ulformat = AL_FORMAT_STEREO16; else if (ulchannels == 4) ulformat = algetenumvalue("al_format_quad16"); else if (ulchannels == 6) ulformat = algetenumvalue("al_format_51chn16"); if (ulformat!= 0) // 압축해제후의음원포맷을알아낼수있는경우에만디코딩한다. // Allocate a buffer to be used to store decoded data for all Buffers pdecodebuffer = (char*)malloc(ulbuffersize); if (!pdecodebuffer) ALFWprintf("LoadOggVorbis: Failed to allocate memory for decoded OggVorbis data\n"); fn_ov_clear(&vf); // Generate some AL Buffers for streaming algenbuffers(1, &uibuffer); 18
8 장예제 (PlayOggVorbis) 의중요함수들 ogg 파일로딩 & 디코딩 & OpenAL Buffer 생성 ( 계속 ) // ogg 파일을디코딩하여 Wav 데이터를얻고, 이를 OpenAL 버퍼에넣는다. ulbyteswritten = DecodeOggVorbis(&vf, pdecodebuffer, ulbuffersize, ulchannels); if (ulbyteswritten) albufferdata(uibuffer, ulformat, pdecodebuffer, ulbyteswritten, ulfrequency); // Close OggVorbis stream fn_ov_clear(&vf); // 디코딩에사용했던메모리반환 if (pdecodebuffer) free(pdecodebuffer); pdecodebuffer = NULL; return uibuffer; else // 압축후음원의포맷을알아낼수없으면오류처리함 ALFWprintf("LoadOggVorbis: Failed to find format information, or unsupported format\n"); // Close OggVorbis stream fn_ov_clear(&vf); 19 8 장예제 (PlayOggVorbis) 의중요함수들 ogg 파일로딩 & 디코딩 & OpenAL Buffer 생성 ( 계속 ) size_t ov_read_func(void *ptr, size_t size, size_t nmemb, void *datasource) return fread(ptr, size, nmemb, (FILE*)datasource); int ov_seek_func(void *datasource, ogg_int64_t offset, int whence) return fseek((file*)datasource, (long)offset, whence); int ov_close_func(void *datasource) return fclose((file*)datasource); long ov_tell_func(void *datasource) return ftell((file*)datasource); 20
8장예제 (PlayOggVorbis) 의중요함수들 OggVorbis 디코딩 // Ogg 스트림을디코딩하여 Wav PCM stream으로변환한다. unsigned long DecodeOggVorbis( OggVorbis_File *psoggvorbisfile, // 디코딩할 OggVorbis 스트림 char *pdecodebuffer, // 디코딩된 PCM Wav 데이터가저장될버퍼포인터 unsigned long ulbuffersize, // 디코딩된 PCM Wav 데이터가저장될버퍼의길이 ( 바이트 ) unsigned long ulchannels) // 채널수 ( 모노 (1), 스테레오 (2), Quad(4), 5.1(6)) int current_section; long ldecodesize; unsigned long ulsamples; short *psamples; unsigned long ulbytesdone = 0; while (1) ldecodesize = fn_ov_read(psoggvorbisfile, pdecodebuffer + ulbytesdone, ulbuffersize - ulbytesdone, 0, 2, 1, ¤t_section); if (ldecodesize > 0) ulbytesdone += ldecodesize; if (ulbytesdone >= ulbuffersize) break; else break; 21 8장예제 (PlayOggVorbis) 의중요함수들 OggVorbis 디코딩 ( 계속 ) // Mono, Stereo and 4-Channel files decode into the same channel order // as WAVEFORMATEXTENSIBLE, however 6-Channels files need to be re-ordered if (ulchannels == 6) psamples = (short*)pdecodebuffer; for (ulsamples = 0; ulsamples < (ulbuffersize>>1); ulsamples+=6) // WAVEFORMATEXTENSIBLE Order : FL, FR, FC, LFE, RL, RR // OggVorbis Order : FL, FC, FR, RL, RR, LFE Swap(pSamples[ulSamples+1], psamples[ulsamples+2]); Swap(pSamples[ulSamples+3], psamples[ulsamples+5]); Swap(pSamples[ulSamples+4], psamples[ulsamples+5]); return ulbytesdone; 22
ogg 샘플소스를이용하여.ogg 파일 2개를스트림재생하시오. 제출물 Full Comment가달린소스코드가포함된프로젝트폴더 프로그램제작설명서 제출기한 여기엔소스코드포함하면안됨. 5 월 9 일밤 12 시 Homework 23 Q & A 24