คราวนี้ขอแปะแต่โค๊ดก่อนนะครับ อธิบายจะตามมาวันหลัง ง่วงแล้ว (ตีสองครึ่งแล้วนะ !!)
ที่ว่าแบบ stream ก็คือ จะไม่อ่านทีเดียวทั้งไฟล์ เก็บไว้ในเมมโมรี่ก่อน (มันเปลือง) แต่จะใช้วิธีค่อย ๆ บัฟเฟอร์ไฟล์มาเก็บไว้ แล้วค่อย ๆ เล่นทีละส่วน (ซึ่งเป็นเทคนิคทีเรียกว่าการทำ streaming)
หลักการคร่าว ๆ ก็คือ
- เติมข้อมูลทุก ๆ บัฟเฟอร์ เว้นบัฟเฟอร์สุดท้ายไว้ (จริง ๆ ไม่ต้องเว้นก็ได้ แต่ที่เว้นเพื่อความสะดวกในการเขียนโค้ด)
- เมื่อบัฟเฟอร์แรกถูกเล่น บัฟเฟอร์สุดท้ายจะถูกเติมข้อมูล
- เมื่อบัฟเฟอรฺ์แรกหมด จะมีการเรียก call back จากระบบมาบอกว่าเล่นบัฟเฟอร์หมดแล้ว ให้ก๊อปบัฟเฟอร์ที่สองใส่บัฟเฟอร์ระบบไป แล้วเติมบัฟเฟอร์แรกที่เล่นจบไปแล้ว
- ถ้าหากระหว่างเติมบัฟเฟอร์ ดันจบเพลงซะก่อน ก็ seek ไปยังตำแหน่งเริ่มต้น loop ใหม่ซะ แล้วบัฟเฟอร์ต่อจนเต็ม
เอาแค่นี้ก่อนละกัน เดี๋ยวค่อยมาแก้เพิ่มครับ
#include
#include
#include
#include
const int BUFFER_COUNT = 6;
const int NUM_SAMPLE = 4096*4;
const int BUFFER_SIZE = 2 * 2 * NUM_SAMPLE; //2 channels * 2byte per sample * NUM_SAMPLE
int current_buffer = 0;
char bgm_buffer[BUFFER_COUNT][BUFFER_SIZE];
const int loop_start = 291784; // loop point in #sample, this is song-specific setting
// and suits only for supplied ogg file.
// Updates this value to test with other file.
SDL_mutex *mutex;
OggVorbis_File file;
// Macros for a lazy guy like me
#define if_error_display_then_exit(x, y) \
if( (x) != 0 ) { std::cout<<(y)< . Usually double buffer or tripple buffer will do the trick.
// However, I decided to make it configurable on how much buffer is used. This technique is call
// 'Multiple Buffering' ... I guess
.
// Buffers need to be fill n-1 time before the song can plays. When song starts, the last buffer
// will be filled. When the sound system notifies that new buffer have to be filled (via call back function).
// a next buffer will be filled to sound system buffer, and the last one will be refilled with next audio data
// from ogg file.
for(int i = 1 ; i< BUFFER_COUNT; i++)
fill_bgm_buffer(NULL);
// Start music!!
SDL_PauseAudio(0);
while(true)
{
// Give time to other thread to work (required*
)
SDL_Delay(100);
SDL_Event event;
while(SDL_PollEvent(&event))
{
switch(event.type)
{
case SDL_QUIT:
goto LEAVE_LOOP;
}
}
}
LEAVE_LOOP:
// Clean Up
SDL_DestroyMutex(mutex);
SDL_CloseAudio();
ov_clear(&file);
return 0;
}
void fill_audio_callback(void *userdata, Uint8 *stream, int len)
{
static int filling_buffer_index = 0;
// input will be unsigned 16bit array, length = len.
unsigned short* buffer = (unsigned short*)stream;
memcpy(stream, bgm_buffer[filling_buffer_index], len);
filling_buffer_index++;
if(filling_buffer_index == BUFFER_COUNT)
filling_buffer_index = 0;
// buffer copy is done, decode new chunks of data
SDL_CreateThread(fill_bgm_buffer, NULL);
}
int fill_bgm_buffer(void* data)
{
// lock mutex
if_error_display_then_exit(SDL_LockMutex(mutex),
"Unable to lock mutex.");
// Start Critical Section
long read = 0;
long total_read = 0;
while(total_read != BUFFER_SIZE)
{
int current_bitstream;
//hard coded for little endian, 2 channels, signed data
read = ov_read(&file, bgm_buffer[current_buffer]+total_read,
BUFFER_SIZE - total_read, 0, 2, 1, ¤t_bitstream);
total_read += read;
// reach EOS ? seek to the loop start position
if(read == 0)
ov_pcm_seek(&file, loop_start);
}
current_buffer++;
if(current_buffer == BUFFER_COUNT)
current_buffer = 0;
// End of Critical section, release mutex.
if_error_display_then_exit(SDL_UnlockMutex(mutex),
"Unable to unlock mutex.");
return 0;
}
ต่อไปก็ Source Code กับตัวโปรแกรมนะครับ
Source
Binary
ตัวโปรแกรม (น่าจะ) ต้องใช้กับ VS 2008 Runtime นะครับ ถ้าไม่มีก็คงรันไม่ได้มั้ง ??? แต่ก็เอาโค๊ดไปคอมไพล์เองเป็นเวอร์ชั่นอื่นก็ได้ครับ