เล่น file ogg แบบ stream ให้ loop เนียนไร้รอยต่อ (ผ่าน SDL)

คราวนี้ขอแปะแต่โค๊ดก่อนนะครับ อธิบายจะตามมาวันหลัง ง่วงแล้ว (ตีสองครึ่งแล้วนะ !!)

ที่ว่าแบบ stream ก็คือ จะไม่อ่านทีเดียวทั้งไฟล์ เก็บไว้ในเมมโมรี่ก่อน (มันเปลือง) แต่จะใช้วิธีค่อย ๆ บัฟเฟอร์ไฟล์มาเก็บไว้ แล้วค่อย ๆ เล่นทีละส่วน (ซึ่งเป็นเทคนิคทีเรียกว่าการทำ streaming)

หลักการคร่าว ๆ ก็คือ

  1. เติมข้อมูลทุก ๆ บัฟเฟอร์ เว้นบัฟเฟอร์สุดท้ายไว้ (จริง ๆ ไม่ต้องเว้นก็ได้ แต่ที่เว้นเพื่อความสะดวกในการเขียนโค้ด)
  2. เมื่อบัฟเฟอร์แรกถูกเล่น บัฟเฟอร์สุดท้ายจะถูกเติมข้อมูล
  3. เมื่อบัฟเฟอรฺ์แรกหมด จะมีการเรียก call back จากระบบมาบอกว่าเล่นบัฟเฟอร์หมดแล้ว ให้ก๊อปบัฟเฟอร์ที่สองใส่บัฟเฟอร์ระบบไป แล้วเติมบัฟเฟอร์แรกที่เล่นจบไปแล้ว
  4. ถ้าหากระหว่างเติมบัฟเฟอร์ ดันจบเพลงซะก่อน ก็ 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)< )
		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 นะครับ ถ้าไม่มีก็คงรันไม่ได้มั้ง ??? แต่ก็เอาโค๊ดไปคอมไพล์เองเป็นเวอร์ชั่นอื่นก็ได้ครับ

Wutipong Wongsakuldej

Programmer, interested in frontend applications, music and multimedia.

Latest posts by Wutipong Wongsakuldej (see all)

Leave a Reply

Your email address will not be published. Required fields are marked *