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);
}
}