SDL 與 FFMPEG 音樂播放器開發(2)——混播多個音訊
阿新 • • 發佈:2019-02-20
第一篇總體提了一下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]