终于来到了最终篇章,之前的第四篇中说了音频的初始化,却没有提及音频播放,播放器不播放音频怎么可以呢,对吧!最后一篇把音频播放补齐。
sdl_audio_callback
这是个回调函数,而是由SDL2调用,来要音频数据,然后再音频数据写入到音频设备中。它内部怎么把音频数据写到设备中我们不管,它要多少数据给它就是!
故,回调的主要工作是满足SDL2。
这个回调函数在音频初始化时已经注册了,这里不赘述了,可以说这个回调函数一直触发,音频数据其实消耗的很快的,所以要不停的满足它,先看看这个回调的定义:
staticvoidsdl_audio_callback(void *opaque, Uint8 *stream, int len)opaque参数,相当于自定义数据。
stream是音频数据缓冲区,
len是需要的音频数据长度。
开始看代码:
// 我们的大管家
VideoState *is = opaque;
int audio_size, len1;
// 拿到当前时间
audio_callback_time = av_gettime_relative();然后就是循环处理了,循环的意思就是可能一帧解码的数据不够用,要多解码几帧才可满足需求:
while (len > 0) {
// 当前的缓冲区数据不足,需要再解码一帧
if (is->audio_buf_index >= is->audio_buf_size) {
// 拿到一帧音频解码数据
audio_size = audio_decode_frame(is);
if (audio_size < 0) {
// 解码失败,音频造假(静音填充)
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;
// 如果缓冲区数据超过SDL需要的数据量,需要截断
if (len1 > len)
len1 = len;
// 填充SDL缓冲区
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_write_buf_size = is->audio_buf_size - is->audio_buf_index;
if (!isnan(is->audio_clock)) {
// is->audio_clock是解码时间,要注意的是解码时间 != 播放时间,也就是说解码时间更超前一些
// is->audio_hw_buf_size是音频设备的缓冲区大小,通常来说有2个缓冲周期等待播放
// is->audio_write_buf_size是已经解码过但还没上交的数据
// / is->audio_tgt.bytes_per_sec 是把音频长度换算成时间单位秒
// 当前播放时间 = 解码时间 - 未播放的时间
// 更新音频时钟
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);
}audio_decode_frame
然后我们再回过头来看看,音频帧的处理:
//先拿到一帧有效音频帧
do {
#if defined(_WIN32)
while (frame_queue_nb_remaining(&is->sampq) == 0) {
// 如果超过半个硬件缓冲的时长,就放弃等。
if ((av_gettime_relative() - audio_callback_time) > 1000000LL * is->audio_hw_buf_size / is->audio_tgt.bytes_per_sec / 2)
return-1;
av_usleep (1000);
}
#endif
if (!(af = frame_queue_peek_readable(&is->sampq)))
return-1;
frame_queue_next(&is->sampq);
} while (af->serial != is->audioq.serial);
// 根据参数计算音频数据长度
data_size = av_samples_get_buffer_size(NULL, af->frame->ch_layout.nb_channels,
af->frame->nb_samples,
af->frame->format, 1);
// 音频同步处理
wanted_nb_samples = synchronize_audio(is, af->frame->nb_samples);
// 下面是当参数有变化时,需要重新初始化音频重采样器
...
swr_free(&is->swr_ctx);
swr_alloc_set_opts2
swr_init
...
// 执行重采样
if (is->swr_ctx) {
constuint8_t **in = (constuint8_t **)af->frame->extended_data;
uint8_t **out = &is->audio_buf1;
// 计算输出数据长度 +256 留出足够的安全余量
int out_count = (int64_t)wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate + 256;
int out_size = av_samples_get_buffer_size(NULL, is->audio_tgt.ch_layout.nb_channels, out_count, is->audio_tgt.fmt, 0);
if (wanted_nb_samples != af->frame->nb_samples) {
// 补偿机制
// (wanted_nb_samples - af->frame->nb_samples) * is->audio_tgt.freq / af->frame->sample_rate
// 需要补偿的采样数,正数表示需要增加采样数,负数表示需要减少采样数
// wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate:
// 补偿基数
if (swr_set_compensation(is->swr_ctx, (wanted_nb_samples - af->frame->nb_samples) * is->audio_tgt.freq / af->frame->sample_rate,
wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate) < 0) {
av_log(NULL, AV_LOG_ERROR, "swr_set_compensation() failed\n");
return-1;
}
}
// 执行重采样
av_fast_malloc(&is->audio_buf1, &is->audio_buf1_size, out_size);
len2 = swr_convert(is->swr_ctx, out, out_count, in, af->frame->nb_samples);
...
is->audio_buf = is->audio_buf1;
// 计算最终的数据大小
resampled_data_size = len2 * is->audio_tgt.ch_layout.nb_channels * av_get_bytes_per_sample(is->audio_tgt.fmt);
} else {
is->audio_buf = af->frame->data[0];
resampled_data_size = data_size;
}
audio_clock0 = is->audio_clock;
//更新时钟
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;synchronize_audio
最后我们再看看音频同步的处理,核心思想是当音视频不同步时,悄摸摸的修改音频采样点数,比如1024个采样点变成1026个或1022个,通过这种方式达到音频等待视频或者音频追赶视频。
staticintsynchronize_audio(VideoState *is, int nb_samples)
{
// 原始采样点数
int wanted_nb_samples = nb_samples;
// 只有音频不是主时钟时,才处理
if (get_master_sync_type(is) != AV_SYNC_AUDIO_MASTER) {
double diff, avg_diff;
int min_nb_samples, max_nb_samples;
// 和主时钟的时钟差,>0 表示音频超前视频 <0 音频落后视频
diff = get_clock(&is->audclk) - get_master_clock(is);
// 音频差在处理的范围内 10s内
if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD) {
// 平滑累计值
is->audio_diff_cum = diff + is->audio_diff_avg_coef * is->audio_diff_cum;
// 预热滤波器,前20次只累积数据不执行修正
if (is->audio_diff_avg_count < AUDIO_DIFF_AVG_NB) {
is->audio_diff_avg_count++;
} else {
// 提取平均时值
avg_diff = is->audio_diff_cum * (1.0 - is->audio_diff_avg_coef);
// 时差太小的话,也无需修正
if (fabs(avg_diff) >= is->audio_diff_threshold) {
// 修正的上下限控制在10%内,超过10%容易发生变调
wanted_nb_samples = nb_samples + (int)(diff * is->audio_src.freq);
min_nb_samples = ((nb_samples * (100 - SAMPLE_CORRECTION_PERCENT_MAX) / 100));
max_nb_samples = ((nb_samples * (100 + SAMPLE_CORRECTION_PERCENT_MAX) / 100));
wanted_nb_samples = av_clip(wanted_nb_samples, min_nb_samples, max_nb_samples);
}
av_log(NULL, AV_LOG_TRACE, "diff=%f adiff=%f sample_diff=%d apts=%0.3f %f\n",
diff, avg_diff, wanted_nb_samples - nb_samples,
is->audio_clock, is->audio_diff_threshold);
}
} else {
// 时差大于10s, 就不做微小的修正了。
is->audio_diff_avg_count = 0;
is->audio_diff_cum = 0;
}
}
return wanted_nb_samples;
}完结
ffplay源码中的核心代码算是软磨硬泡的给整完了,中间细节可能有不到位的地方,但整体的处理思想咱也算是给理解了一遍。后面再回过头来翻看时,这些注释应该也能起到些许的帮助!!
(发现没有(七),直接从六跳到八,就这样吧^_^)
夜雨聆风