转自:https://zhuanlan.zhihu.com/p/44139512
ffplay的audio输出同样也是通过SDL实现的。
同样地,本文主要介绍audio输出相关内容,且尽量不涉及音视频同步知识,音视频同步将在专门一篇分析。
audio的输出在SDL下是被动输出,即在开启SDL会在需要输出时,回调通知,在回调函数中,SDL会告知要发送多少的数据。(关于SDL音频输出可以参考这篇:https://www.jianshu.com/p/b006e9e9caa6)
但是在ffmpeg解码一个AVPacket的音频到AVFrame后,在AVFrame中存储的音频数据大小与SDL回调所需要的数据大概率是不相等的,这就需要再增加一级缓冲区解决问题。
在audio输出时,主要模型如下图:
在这个模型中,sdl通过sdl_audio_callback函数向ffplay要音频数据,ffplay将sampq中的数据通过audio_decode_frame
函数取出,放入is->audio_buf
,然后送出给sdl。在后续回调时先找audio_buf
要数据,数据不足的情况下,再调用audio_decode_frame
补充audio_buf
注意
audio_decode_frame
这个函数名很具有迷惑性,实际上,这个函数是没有解码功能的!这个函数主要是处理sampq到audio_buf的过程,最多只是执行了重采样。
了解了大致的audio输出模型后,再看详细代码。
先看打开sdl音频输出的代码:
//代码在stream_component_open
//先不看filter相关代码,即默认CONFIG_AVFILTER宏为0
//从avctx(即AVCodecContext)中获取音频格式参数
sample_rate = avctx->sample_rate;
nb_channels = avctx->channels;
channel_layout = avctx->channel_layout;
//调用audio_open打开sdl音频输出,实际打开的设备参数保存在audio_tgt,返回值表示输出设备的缓冲区大小
if ((ret = audio_open(is, channel_layout, nb_channels, sample_rate, &is->audio_tgt)) < 0)
goto fail;
is->audio_hw_buf_size = ret;
is->audio_src = is->audio_tgt;
//初始化audio_buf相关参数
is->audio_buf_size = 0;
is->audio_buf_index = 0;
由于不同的音频输出设备支持的参数不同,音轨的参数不一定能被输出设备支持(此时就需要重采样了),audio_tgt
就保存了输出设备参数。
audio_open是ffplay封装的函数,会优先尝试请求参数能否打开输出设备,尝试失败后会自动查找最佳的参数重新尝试。不再具体分析。
audio_src
一开始与audio_tgt
是一样的,如果输出设备支持音轨参数,那么audio_src
可以一直保持与audio_tgt
一致,否则将在后面代码中自动修正为音轨参数,并引入重采样机制。
最后初始化了几个audio_buf相关的参数。这里介绍下audio_buf相关的几个变量:
在audio_open
函数内,通过通过SDL_OpenAudioDevice
注册sdl_audio_callback
函数为音频输出的回调函数。那么,主要的音频输出的逻辑就在sdl_audio_callback
函数内了。
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 (len > 0) {//循环发送,直到发够所需数据长度
//如果audio_buf消耗完了,就调用audio_decode_frame重新填充audio_buf
if (is->audio_buf_index >= is->audio_buf_size) {
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;
//根据audio_volume决定如何输出audio_buf
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);
}
//调整各buffer
len -= len1;
stream += len1;
is->audio_buf_index += len1;
}
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)) {//更新audclk
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);
}
}
sdl_audio_callback
函数是一个典型的缓冲区输出过程,看代码和注释应该可以理解。具体看3个细节:
audio_decode_frame
重新填充audio_buf。接下来会继续分析audio_decode_frame函数2 * is->audio_hw_buf_size
.(这里为何还要加audio_write_buf_size,表示不能理解。有理解的希望能赐教)
接下来看下audio_decode_frame
(省略重采样代码):
static int audio_decode_frame(VideoState *is)
{
int data_size, resampled_data_size;
int64_t dec_channel_layout;
av_unused double audio_clock0;
int wanted_nb_samples;
Frame *af;
if (is->paused)//暂停状态,返回-1,sdl_audio_callback会处理为输出静音
return -1;
do {//1. 从sampq取一帧,必要时丢帧
if (!(af = frame_queue_peek_readable(&is->sampq)))
return -1;
frame_queue_next(&is->sampq);
} while (af->serial != is->audioq.serial);
//2. 计算这一帧的字节数
data_size = av_samples_get_buffer_size(NULL, af->frame->channels,
af->frame->nb_samples,
af->frame->format, 1);
//[]计算dec_channel_layout,用于确认是否需要重新初始化重采样(难道af->channel_layout不可靠?不理解)
dec_channel_layout =
(af->frame->channel_layout && af->frame->channels == av_get_channel_layout_nb_channels(af->frame->channel_layout)) ?
af->frame->channel_layout : av_get_default_channel_layout(af->frame->channels);
wanted_nb_samples = synchronize_audio(is, af->frame->nb_samples);
//[]判断是否需要重新初始化重采样
if (af->frame->format != is->audio_src.fmt ||
dec_channel_layout != is->audio_src.channel_layout ||
af->frame->sample_rate != is->audio_src.freq ||
(wanted_nb_samples != af->frame->nb_samples && !is->swr_ctx)) {
//……
}
//3. 获取这一帧的数据
if (is->swr_ctx) {//[]如果初始化了重采样,则对这一帧数据重采样输出
}else {
is->audio_buf = af->frame->data[0];
resampled_data_size = data_size;
}
audio_clock0 = is->audio_clock;//audio_clock0用于打印调试信息
//4. 更新audio_clock,audio_clock_serial
/* update the audio clock with the pts */
if (!isnan(af->pts))
is->audio_clock = af->pts + (double) af->frame->nb_samples / af->frame->sample_rate;
else
is->audio_clock = NAN;
is->audio_clock_serial = af->serial;
return resampled_data_size;//返回audio_buf的数据大小
}
audio_decode_frame
并没有真正意义上的decode
代码,最多是进行了重采样。主流程有以下步骤:
在省略了重采样代码后看,相对容易理解。
至此,音频输出的主要代码就分析完了。中间我们省略了filter和resample相关的代码,有研究后再补充。