ffplay音頻解碼模塊源碼原理分析
一、初始化
二、音頻數據寫入輸出設備
sdl_audio_callback(…)輸出數據的回調函數,將被SDL循環調用。
//參數stream爲音頻緩衝區,len爲緩衝區長度,將音頻數據拷貝到stream,由SDL將stream中的數據送入硬件播放
//opaque 爲userdata,在函數audio_open(...)中賦值給SDL_AudioSpec結構體的userdata字段
static void sdl_audio_callback(void *opaque, Uint8 *stream, int len)
{
VideoState *is = opaque;
int audio_size, len1;
audio_callback_time = av_gettime_relative();
//該while循環,從AVFrame中解碼出原始音頻數據,拷貝到stream緩衝中,直到填滿音頻緩衝區
//若填滿stream緩衝後數據有剩餘將存放在is->audio_buf中,並由is->audio_buf_index指定開始位置
while (len > 0) {
if (is->audio_buf_index >= is->audio_buf_size) {
//if語句:上次解碼出的原始音頻數據已全部送入stream或已播放完成,開始從FrameQueue中取出AVFrame解碼音頻數據
//從一幀AVFrame中解碼出原始的音頻數據,返回字節大小
audio_size = audio_decode_frame(is);
if (audio_size < 0) {
/* if error, just output silence */
is->audio_buf = NULL;
is->audio_buf_size = SDL_AUDIO_MIN_BUFFER_SIZE / is->audio_tgt.frame_size * is->audio_tgt.frame_size;
} else {
if (is->show_mode != SHOW_MODE_VIDEO)
update_sample_display(is, (int16_t *)is->audio_buf, audio_size);
is->audio_buf_size = audio_size;
}
is->audio_buf_index = 0;
}
len1 = is->audio_buf_size - is->audio_buf_index;//未播放音頻數據字節大小
if (len1 > len)
len1 = len;
if (!is->muted && is->audio_buf && is->audio_volume == SDL_MIX_MAXVOLUME)
memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1);
else {
memset(stream, 0, len1);
if (!is->muted && is->audio_buf)
SDL_MixAudioFormat(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, AUDIO_S16SYS, len1, is->audio_volume);
}
len -= len1;//音頻緩衝區大小減小
stream += len1;//推進緩衝區地址
is->audio_buf_index += len1;//推進音頻緩衝區開始索引
}
//is->audio_buf中剩餘的數據,及填滿stream後剩餘的數據
is->audio_write_buf_size = is->audio_buf_size - is->audio_buf_index;
/* Let's assume the audio driver that is used by SDL has two periods. */
if (!isnan(is->audio_clock)) {
//此時的is->audio_clock存放的是當前解碼的所有AVFrame播放完成後對應的pts(單位秒)
//其值在audio_decode_frame(...)函數中修改
//此處的set_clock_at(...)修改音頻時鐘的pts等信息,就要計算當前audio的pts
//當前audio的pts=所有解碼的AVFrame播放完成後的pts - 音頻緩衝區數據播放需花費的時間 - 剩餘的音頻數據(is->audio_write_buf_size)播放需花費的時間。這裏2 * is->audio_hw_buf_size是一個估計值,認爲stream中的數據到達硬件驅動播放需要2倍的時間
set_clock_at(&is->audclk,
is->audio_clock - (double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec,
is->audio_clock_serial, audio_callback_time / 1000000.0);
sync_clock_to_slave(&is->extclk, &is->audclk);
}
}