SDL音頻播放
gcc -o tutorial03 tutorial03.c -lavutil -lavformat -lavcodec -lz -lm \
`sdl-config --cflags --libs`
AUDIO名詞解釋:
samples:采樣,通過PCM來采樣,通常樣本16bit,PCM的采樣精度從14-bit發展到16-bit、18-bit、20-bit直到24-bit
Samples rate:采樣率,22.05KHz and 44.1KHz,每秒從連續信號中提取并組成離散信號的采樣個數
位速:采樣率*樣本bit*通道數,CD上未經壓縮的音頻位速是1411.2 kbit/s(16 位/采樣點 × 44100 采樣點/秒 × 2 通道)
pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_AUDIO , 在for循環找找到audiostream的索引i
AVCodecContext *aCodecCtx=pFormatCtx->streams[audioStream]->codec; 得到音頻編碼的信息
SDL_AudioSpec wanted_spec,spec;
wanted_spec.freq = aCodecCtx->sample_rate; //采樣率
wanted_spec.format = AUDIO_S16SYS;
//告訴SDL使用什么格式,S指代signed,16為樣本16bit,SYS指代大小端由系統決定
wanted_spec.channels = aCodecCtx->channels; //有多少個通道
wanted_spec.silence = 0; //silence值,由于為signed,故為0
wanted_spec.samples =1024; //緩存大小
wanted_spec.callback = audio_callback; //音頻的回調函數
wanted_spec.userdata = aCodecCtx; //給回調函數處理的數據
SDL_OpenAudio(&wanted_spec, &spec)
返回-1則打開失敗,spec為NULL則以wanted_spec指定的方式播放,若spec不為NULL,則使用根據硬件改變的spec指定的方式播放,而wanted_spec可以刪除
VCodec *aCodec = avcodec_find_decoder(aCodecCtx->codec_id);
avcodec_open(aCodecCtx, aCodec);
找到解碼器,并進行解碼
typedef struct PacketQueue {
AVPacketList *first_pkt, *last_pkt;
int nb_packets; //為包的總數
int size; //為所有包的大小
SDL_mutex *mutex; //互斥鎖
SDL_cond *cond; //條件變量
} PacketQueue;
我們自己創建的用于構建Packet隊列的數據結構
AVPacketList
A simple linked list for packets.
AVPacket pkt
AVPacketList * next
void packet_queue_init(PacketQueue *q)
{
memset(q,0,sizeof(PacketQueue));
q->mutex = SDL_CreateMutex();
q->cond=SDL_CreateCond();
}
對PacketQueue數據結構進行初始化
用于給PacketQueue數據結構中填入包的函數
int packet_queue_put(PacketQueue *q,AVPacket *pkt)
{
AVPacketList *pkt1;
if(av_dup_packet(pkt)<0)
{
return -1; //檢查是否為NULL,為NULL則自己填充,否則返回-1
}
pkt1 = av_malloc(sizeof(AVPacketList));//給AVPacketList分配空間
if (!pkt1)
return -1;
pkt1->pkt = *pkt;
pkt1->next = NULL;
SDL_LockMutex(q->mutex); //對PacketQueue進行操作,先鎖定互斥變量
if (!q->last_pkt)
q->first_pkt = pkt1;
else
q->last_pkt->next = pkt1;
q->last_pkt = pkt1;
q->nb_packets++;
q->size += pkt1->pkt.size;
SDL_CondSignal(q->cond); //發送條件信號,方便等待數據的地方喚醒
SDL_UnlockMutex(q->mutex); //解鎖
return 0;
}
接收數據
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
AVPacketList *pkt1;
int ret;
SDL_LockMutex(q->mutex);//鎖定mutex
for(;;)
{
if(quit)
{
ret =-1;
break;
}
pkt1=q->first_pkt;
if (pkt1) {
q->first_pkt = pkt1->next;
if (!q->first_pkt)
q->last_pkt = NULL;
q->nb_packets--;
q->size -= pkt1->pkt.size;
*pkt = pkt1->pkt;
av_free(pkt1);
ret = 1;
break;
}
else if (!block) {
ret = 0;
break;
}
else {
SDL_CondWait(q->cond, q->mutex);
}
}
SDL_UnlockMutex(q->mutex);
return ret;
}
SDL_CondWait先檢測是否滿足條件,若不滿足,解鎖mutex,wait,直至被SDL_CondSignal()函數或者SDL_CondBroadcast()函數通知,則鎖定mutex并返回
void SDL_PauseAudio(int pause_on)
控制播放與暫停,當pause_on為0時,播放數據,若數據未準備,則播放靜音
播放的回調函數,格式必須為void callback(void *userdata, Uint8 *stream, int len),這里的userdata就是我們給到SDL的指針,stream是我們要把聲音數據寫入的緩沖區指針,len是緩沖區的大小。
void audio_callback(void *userdata, Uint8 *stream, int len) {
struct mydata *data=(struct mydata*)userdata;
AVCodecContext *aCodecCtx = (AVCodecContext *)data->pFormatCtx;
PacketQueue *audioq=data->audioq;
int len1, audio_size;
//靜態的數據為了可以多次調用回調函數,而每次不一定處理完了數據
static uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
//audio_buf的大小為1.5倍的聲音幀的大小以便于有一個比較好的緩沖
static unsigned int audio_buf_size = 0;
static unsigned int audio_buf_index = 0;
while(len > 0) {
if(audio_buf_index >= audio_buf_size) {
audio_size = audio_decode_frame(aCodecCtx, audio_buf,
sizeof(audio_buf));
if(audio_size < 0) {
audio_buf_size = 1024;
memset(audio_buf, 0, audio_buf_size);
} else {
audio_buf_size = audio_size;
}
audio_buf_index = 0;
}
len1 = audio_buf_size - audio_buf_index;
if(len1 > len)
len1 = len;
memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
len -= len1;
stream += len1;
audio_buf_index += len1;
}
}
對音頻數據進行解碼,被解碼的數據存在audio_buf中,buf_size告訴函數audio_buf緩沖區多大,返回值為解碼的數據數量,結束時返回-1,否則返回被解碼的bytes數。
int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf,
int buf_size,PacketQueue *audioq) {
static AVPacket pkt;
static uint8_t *audio_pkt_data = NULL;
static int audio_pkt_size = 0;
int len1, data_size;
for(;;) {
while(audio_pkt_size > 0) { //該循環從末尾開始
data_size = buf_size; //avcodec_decode_audio2必須先指定輸出緩沖大小
len1 = avcodec_decode_audio2(aCodecCtx, (int16_t *)audio_buf, &data_size, audio_pkt_data, audio_pkt_size);
//對數據進行解碼
if(len1 < 0) {
audio_pkt_size = 0;
break;
}
audio_pkt_data += len1;
audio_pkt_size -= len1; //靜態數據,可能包里面有多個幀,故下次調用繼續
if(data_size <= 0) {
continue;
}
return data_size;
}
if(pkt.data)
av_free_packet(&pkt);
if(quit) {
return -1;
}
if(packet_queue_get(&audioq, &pkt, 1) < 0) {
return -1;
}
audio_pkt_data = pkt.data;
audio_pkt_size = pkt.size;
}
}
int avcodec_decode_audio2(AVCodecContext *avctx, int16_t *samples, int *frame_size_ptr, uint8_t *buf, int buf_size)
輸出為samples,如果沒有可以解碼的frame_size_ptr返回0,否則其為被解碼的大小。你必須分配frame_size_ptr為samples的緩存大小在你調用該函數時。
返回負數為錯誤,返回解碼的字節數或0指示沒有被解碼的。
The input buffer must be FF_INPUT_BUFFER_PADDING_SIZE larger than the actual read bytes because some optimized bitstream readers read 32 or 64 bits at once and could read over the end. The end of the input buffer buf should be set to 0 to ensure that no overreading happens for damaged MPEG streams.
Note:You might have to align the input buffer buf and output buffer samples. The alignment requirements depend on the CPU: on some CPUs it isn't necessary at all, on others it won't work at all if not aligned and on others it will work but it will have an impact on performance. In practice, the bitstream should have 4 byte alignment at minimum and all sample data should be 16 byte aligned unless the CPU doesn't need it (AltiVec and SSE do). If the linesize is not a multiple of 16 then there's no sense in aligning the start of the buffer to 16.