FFmpeg+QT输出音频
音频输出
使用 SDL
作为音视频输出。
audio_decode_frame
struct AudioParams audio_src; // 保存最新解码的音频参数
struct AudioParams audio_tgt; // 保存SDL音频输出需要的参数
重置音频重采样器的条件:
- 采用格式不一致
- 采样率不一致
- 通道布局不一致
因为使用 SDL 做输出最有可能的是采样格式区别,我们需要把 float planner
重采样成 s16 packed
格式。
buffer 管理
- audio_buf1:实际 处理分配的 buffer
- audio_buf1_size:buffer 的最大长度
- audio_buf_size:有效的 PCM buffer
- audio_buf:执行要拷贝的 buffer
具体的 buffer 使用流程举例如下:
- 第一次触发回调的时候
audio_buf_index = 0
,本次需要拷贝 4096 个字节,然后拷贝结束后audio_buf_index = 4096
。 - 第二次触发回调的时候
audio_buf_index = 4096
audio_decode_frame
// is->audio_tgt是SDL可接受的音频帧数,是audio_open()中取得的参数// 在audio_open()函数中又有"is->audio_src = is->audio_tgt""// 此处表示:如果frame中的音频参数 == is->audio_src == is->audio_tgt,// 那音频重采样的过程就免了(因此时is->swr_ctr是NULL)// 否则使用frame(源)和is->audio_tgt(目标)中的音频参数来设置is->swr_ctx,// 并使用frame中的音频参数来赋值is->audio_srcif (af->frame->format != is->audio_src.fmt || // 采样格式dec_channel_layout != is->audio_src.channel_layout || // 通道布局af->frame->sample_rate != is->audio_src.freq // 采样率) {swr_free(&is->swr_ctx);is->swr_ctx = swr_alloc_set_opts(NULL,is->audio_tgt.channel_layout, // 目标输出is->audio_tgt.fmt,is->audio_tgt.freq,dec_channel_layout, // 数据源(enum AVSampleFormat)af->frame->format,af->frame->sample_rate,0, NULL);if (!is->swr_ctx || swr_init(is->swr_ctx) < 0) {av_log(NULL, AV_LOG_ERROR,"Cannot create sample rate converter for conversion of %d Hz %s %d channels to %d Hz %s %d channels!\n",af->frame->sample_rate, av_get_sample_fmt_name((enum AVSampleFormat)af->frame->format), af->frame->channels,is->audio_tgt.freq, av_get_sample_fmt_name(is->audio_tgt.fmt), is->audio_tgt.channels);swr_free(&is->swr_ctx);ret = -1;goto fail;}// 保存最新的audio解码后的参数is->audio_src.channel_layout = dec_channel_layout;is->audio_src.channels = af->frame->channels;is->audio_src.freq = af->frame->sample_rate;is->audio_src.fmt = (enum AVSampleFormat)af->frame->format;}
第一次判断 is->audio_src = is->audio_tgt
第二次判断 解码后的参数==is->audio_src==is->audio_tgt
因为解码前后的参数可能会改变
// 重采样输出参数1:输出音频缓冲区
uint8_t **out = &is->audio_buf1; //真正分配缓存audio_buf1,指向是用audio_buf// 重采样输出参数2:输出音频缓冲区尺寸, 高采样率往低采样率转换时得到更少的样本数量,比如 96k->48k, wanted_nb_samples=1024
// 则wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate 为1024*48000/96000 = 512
// +256 的目的是重采样内部是有一定的缓存,就存在上一次的重采样还缓存数据和这一次重采样一起输出的情况,所以目的是多分配输出buffer
int out_count = (int64_t)wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate+ 256;
// 计算对应的样本数 对应的采样格式 以及通道数,需要多少buffer空间
// 计算输出的buffer大小
int out_size = av_samples_get_buffer_size(NULL, is->audio_tgt.channels,out_count, is->audio_tgt.fmt, 0);
int len2;
if (out_size < 0) {av_log(NULL, AV_LOG_ERROR, "av_samples_get_buffer_size() failed\n");ret = -1;goto fail;
}
// if(audio_buf1_size < out_size) {重新分配out_size大小的缓存给audio_buf1, 并将audio_buf1_size设置为out_size }
av_fast_malloc(&is->audio_buf1, &is->audio_buf1_size, out_size);
if (!is->audio_buf1) {ret = AVERROR(ENOMEM);goto fail;
}
// 音频重采样:len2返回值是重采样后得到的音频数据中单个声道的样本数
// len2就是实际输出的样本数
len2 = swr_convert(is->swr_ctx, out, out_count, in, af->frame->nb_samples);
if (len2 < 0) {av_log(NULL, AV_LOG_ERROR, "swr_convert() failed\n");ret = -1;goto fail;
}if (len2 == out_count) { // 这里的意思是我已经多分配了buffer,实际输出的样本数不应该超过我多分配的数量av_log(NULL, AV_LOG_WARNING, "audio buffer is probably too small\n");if (swr_init(is->swr_ctx) < 0)swr_free(&is->swr_ctx);
}
// 重采样返回的一帧音频数据大小(以字节为单位)
is->audio_buf = is->audio_buf1;
resampled_data_size = len2 * is->audio_tgt.channels * av_get_bytes_per_sample(is->audio_tgt.fmt);
if (is->audio_buf_index >= is->audio_buf_size) {audio_size = audio_decode_frame(is); // 返回有效的PCM数据长度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 {is->audio_buf_size = audio_size; // 讲字节 多少字节}is->audio_buf_index = 0; // 重置为0
参考资料:https://github.com/0voice