1. 程式人生 > >SDL 與 FFMPEG 音樂播放器開發(2)——混播多個音訊

SDL 與 FFMPEG 音樂播放器開發(2)——混播多個音訊

第一篇總體提了一下SDL,完全沒有提到FFMPEG。我的思路是,在說解碼之前,你起碼要知道怎麼使用解碼後的檔案。

相信大家如果看了網上的一些教程,應該已經能夠播放出PCM檔案。今天我來談談如何播放多個PCM檔案。

這回先上程式碼

#define MAX_MUSIC_DATA 10
#define PCM_BUFFER_SIZE 4096
struct AudioData
{
	  Uint8  *audio_chunk;
	  Uint32  audio_len;
	  Uint8  *audio_pos;
	SDL_Thread *thread;
	AudioState state;
	FILE *file;
	char *pcm_buffer;
	char *filename;
	float skipSecond;
	float HzPercent;
	int originalFreq;
	int volume;
}m_data[MAX_MUSIC_DATA], *pData;


/* Audio Callback
 * The audio function callback takes the following parameters: 
 * stream: A pointer to the audio buffer to be filled 
 * len: The length (in bytes) of the audio buffer 
 * 
*/ 

void  fill_audio(void *udata,Uint8 *stream,int len){ 
	//SDL 2.0
	SDL_memset(stream, 0, len);
	for (int i = 0; i < MAX_MUSIC_DATA; i++)
	{
		if (m_data[i].audio_len == 0)		/*  Only  play  if  we  have  data  left  */
			continue;
		len = (len > m_data[i].audio_len ? m_data[i].audio_len : len);	/*  Mix  as  much  data  as  possible  */
		SDL_MixAudio(stream, m_data[i].audio_pos, len, m_data[i].volume);

		m_data[i].audio_pos += len;
		m_data[i].audio_len -= len;
	}

	
} 

在fill_audio()當中將每個資料疊加在audio預設的資料流stream,就是混播音訊的關鍵。

而如何更新每個資料中的pcm_buffer呢 看下一段程式碼。

int PlayMusicbyName(void* fileName)
{
	for (int i = 0; i < MAX_MUSIC_DATA; i++)
	{
		if (m_data[i].file)
		{
			if (!strcmp(m_data[i].filename, (char*)fileName))
			{
				return -1;
			}
		}
		if (!m_data[i].file)
		{
			thread[5] = SDL_CreateThread(decodeMusicFile, "decodeMusicFile", fileName);
			SDL_Delay(500);
			//SDL_WaitThread(thread[5],0);
			char buf[256];
			sprintf(buf, "output/%soutput.pcm", fileName);
			m_data[i].file = fopen(buf, "rb+");
			if (!m_data[i].file)
			{
				printf("This file is not exist!!\n");
				return -1;
			}
			m_data[i].filename = (char *)malloc(sizeof(fileName));
			strcpy(m_data[i].filename, (char *)fileName);
			m_data[i].pcm_buffer = (char *)malloc(PCM_BUFFER_SIZE);
			m_data[i].state = play;
			m_data[i].skipSecond = -1;
			m_data[i].HzPercent = -1;
			m_data[i].volume = 128;

			pData = &m_data[i];
			break;
		}
	}
	return 0;
}


這段程式碼本質上就是開啟PCM檔案,並將檔案指標資訊存入m_data當中,同時malloc申請一部分記憶體用來儲存讀出來的資料。

int PlayMusicWithPCM(void* ptr)
{
	if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
		printf("Could not initialize SDL - %s\n", SDL_GetError());
		return -1;
	}
	//SDL_AudioSpec
	SDL_AudioSpec wanted_spec;
	wanted_spec.freq = 44100;
	wanted_spec.format = AUDIO_S16SYS;
	wanted_spec.channels = 2;
	wanted_spec.silence = 0;
	wanted_spec.samples = 1024;
	wanted_spec.callback = fill_audio;
	m_OriginalFreq = wanted_spec.freq;
	if (SDL_OpenAudio(&wanted_spec, NULL) < 0){
		printf("can't open audio.-%s\n", SDL_GetError());
		return -1;
	}
	while (1){
		for (int i = 0; i < MAX_MUSIC_DATA; i++)
		{
			bool shouldwait = false;
			if (m_data[i].state == play)
			{
				if (fread(m_data[i].pcm_buffer, 1, PCM_BUFFER_SIZE, m_data[i].file) != PCM_BUFFER_SIZE){
					// Loop
					fseek(m_data[i].file, 0, SEEK_SET);
					fread(m_data[i].pcm_buffer, 1, PCM_BUFFER_SIZE, m_data[i].file);
				}
				
				//Set audio buffer (PCM data)
				m_data[i].audio_chunk = (Uint8 *)m_data[i].pcm_buffer;
				//Audio buffer length
				m_data[i].audio_len = PCM_BUFFER_SIZE;
				m_data[i].audio_pos = m_data[i].audio_chunk;
			}
			
				
		}
		//Play
		SDL_PauseAudio(0);

		while (1)//Wait until finish
		{
			bool WAIT = false;
			for (int i = 0; i < MAX_MUSIC_DATA; i++)
			{
				if (m_data[i].audio_len > 0)
				{
					WAIT = true;
					break;
				}
			}
			if (WAIT)
			{
				SDL_Delay(1);
			}
			else
			{
				break;
			}

		}
	}
	
	SDL_CloseAudio();
	return 0;
}

每次迴圈的時候都更新資料的buffer、pos等資訊,在每次SDL_PauseAudio回撥的時候都能夠播放下一時刻的資料(注意 這裡必須把每一個PCM檔案的資料都進行更新)。

總結一下,SDL混播PCM資料的思路如下:

讀取每一個檔案的資訊----->將待播放部分存入buffer----->將每一個檔案的buffer進行mix(使用mixAudio)------>更新資料

到這裡,我們就可以把未經過壓縮的PCM資料進行播放了,大家可以試一試,感受一下同時播放多個聲音帶來的成就感與雜亂感。

以及大家可以聯絡我[email protected]