介绍
在本系列,我打算花大篇幅讲解我的 gitee 项目音视频播放器,在这个项目,您可以学到音视频解封装,解码,SDL渲染相关的知识。您对源代码感兴趣的话,请查看基于FFmpeg和SDL的音视频播放器
如果您不理解本文,可参考我的前一篇文章音视频项目—基于FFmpeg和SDL的音视频播放器解析(十四)
解析
我们这篇文章接着解析 audiooutput 剩余的函数。
有一个很关键的函数,fill_audio_pcm
FILE* dump_pcm = nullptr;
void fill_audio_pcm(void* udata, uint8_t* stream, int len){AudioOutput* is = (AudioOutput*)udata;int len1 = 0;int audio_size = 0;if(!dump_pcm){dump_pcm = fopen("dump.pcm", "wb");}while (len > 0){if(is->audio_buf_index == is->audio_buf_size){is->audio_buf_index = 0;AVFrame* frame = is->frame_queue->Pop(10);if(frame){is->pts = frame->pts;if(frame->format != is->dst_tgt.fmt|| frame->sample_rate != is->dst_tgt.freq|| frame->channel_layout != is->dst_tgt.channel_layout&& !is->swr_ctx){is->swr_ctx = swr_alloc_set_opts(NULL, is->dst_tgt.channel_layout,(enum AVSampleFormat)is->dst_tgt.fmt,is->dst_tgt.freq,frame->channel_layout,(enum AVSampleFormat)frame->format,frame->sample_rate,0, NULL);if(!is->swr_ctx || swr_init(is->swr_ctx) < 0){swr_free((SwrContext**)(&is->swr_ctx));return;}}if(is->swr_ctx){const uint8_t** in = (const uint8_t**) frame->extended_data;uint8_t** out = &is->audio_buf1;int out_samples = frame->nb_samples * is->dst_tgt.freq / frame->sample_rate + 256;int out_bytes = av_samples_get_buffer_size(NULL, is->dst_tgt.channels, out_samples, is->dst_tgt.fmt, 0);if(out_bytes < 0){return;}av_fast_malloc(&is->audio_buf1, &is->audio_buf1_size, out_bytes);int len2 = swr_convert(is->swr_ctx, out, out_samples, in, frame->nb_samples);if(len2 < 0){return;}is->audio_buf = is->audio_buf1;is->audio_buf_size = av_samples_get_buffer_size(NULL, is->dst_tgt.channels, len2, is->dst_tgt.fmt, 1);}else {audio_size = av_samples_get_buffer_size(NULL, is->dst_tgt.channels, frame->nb_samples, (enum AVSampleFormat) frame->format, 1);av_fast_malloc(&is->audio_buf1, &is->audio_buf1_size, audio_size);is->audio_buf = is->audio_buf1;is->audio_buf_size = audio_size;memcpy(is->audio_buf, frame->data[0], audio_size);}av_frame_free(&frame);}else {is->audio_buf = nullptr;is->audio_buf_size = 512;}}len1 = is->audio_buf_size - is->audio_buf_index;if(len1 > len){len1 = len;}if(!is->audio_buf){memset(stream, 0, len1);}else {memcpy(stream, is->audio_buf + is->audio_buf_index, len1);fwrite((uint8_t*)is->audio_buf + is->audio_buf_index, 1, len1, dump_pcm);fflush(dump_pcm);}len -= len1;stream += len1;is->audio_buf_index += len1;}if(is->pts != AV_NOPTS_VALUE){double pts = is->pts * av_q2d(is->time_base);is->avsync->SetClock(pts);}
}
这个函数有将近 80 行代码,负责将 pcm 数据填入音频。这个函数是用在 Init 函数上的,用于给 SDL_AudioSpec 的变量的 callback 赋值。
wanted_spec.callback = fill_audio_pcm;
接下来,我们逐条解析这个函数。
首先看前五行代码
AudioOutput* is = (AudioOutput*)udata;int len1 = 0;int audio_size = 0;if(!dump_pcm){dump_pcm = fopen("dump.pcm", "wb");}
就是一些正常的赋值操作,如果文件不存在,则打开一个名为 “dump.pcm” 的文件,设为二进制可写。
while (len > 0){if(is->audio_buf_index == is->audio_buf_size){is->audio_buf_index = 0;AVFrame* frame = is->frame_queue->Pop(10);
在长度 len 大于 0 的情况下,如果两个变量相等,则将 index 设置为 0,取出帧队列的头部数据,10 是 Pop 里的参数,关于条件变量的,这里不深究。
if(frame){is->pts = frame->pts;if(frame->format != is->dst_tgt.fmt|| frame->sample_rate != is->dst_tgt.freq|| frame->channel_layout != is->dst_tgt.channel_layout&& !is->swr_ctx){
然后,在帧数据存在的情况下,将 pts(显示时间戳)赋值,然后开始条件判断,满足这些条件后执行。
好了,这篇文章先讲 20 行代码,剩余的后几篇文章再讲。
欲知后事如何,请听下回分解。