FFplay源码分析-stream_component_open - 弦外之音

/ 0评 / 0

本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8

FFplay 源码分析系列以一条简单的命令开始,ffplay -i a.mp4。a.mp4下载链接:百度网盘,提取码:nl0s 。


上一篇文章已经讲解完了 stream_component_open() 的逻辑,这篇文章主要讲解 audio_thread(),音频解码线程的内部逻辑。

因为解码线程里面涉及到了 struct Decoder,PacketQueue,FrameQueue的操作,所以必须先简单介绍一下这些数据结构的关系。

struct Decoder

ffplay.c 668 ~ 672行
if (avcodec_send_packet(d->avctx, &pkt) == AVERROR(EAGAIN)) {
	av_log(d->avctx, AV_LOG_ERROR, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
	d->packet_pending = 1; //注意这行代码。
	av_packet_move_ref(&d->pkt, &pkt); //注意这行代码。
}
ffplay.c 637 ~ 647行
do {
    if (d->queue->nb_packets == 0)
       SDL_CondSignal(d->empty_queue_cond);
    if (d->packet_pending) { //注意这行代码。
       av_packet_move_ref(&pkt, &d->pkt);
       d->packet_pending = 0;
    } else {
       if (packet_queue_get(d->queue, &pkt, 1, &d->pkt_serial) < 0)
          return -1;
    }
} while (d->queue->serial != d->pkt_serial);
//read_thread 休眠 10ms
SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
//注意最后一个参数,continue_read_thread 赋值给 empty_queue_cond 
decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread);
decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread);
ret = avcodec_receive_frame(d->avctx, frame);
if (ret >= 0) {
	AVRational tb = (AVRational){1, frame->sample_rate};
	if (frame->pts != AV_NOPTS_VALUE)
		frame->pts = av_rescale_q(frame->pts, d->avctx->pkt_timebase, tb);
	else if (d->next_pts != AV_NOPTS_VALUE) //注意这行代码
		frame->pts = av_rescale_q(d->next_pts, d->next_pts_tb, tb);
	if (frame->pts != AV_NOPTS_VALUE) {
		d->next_pts = frame->pts + frame->nb_samples;
		d->next_pts_tb = tb;
	}
}

struct PacketQueue



预备工作,数据结构已经讲完了,下面正式开始讲解 audio_thread() 解码线程里面的逻辑。

static int audio_thread(void *arg)
{
    VideoState *is = arg;
    AVFrame *frame = av_frame_alloc();
    ...省略代码..
    do {
        if ((got_frame = decoder_decode_frame(&is->auddec, frame, NULL)) < 0)
            goto the_end;
        ...省略代码...
    } while (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF);
}

从上面的代码可以看到 audio_thread() 一开始就进入一个 do() while{} 的循环。在这个循环里面会不断的拿 PacketQueue 的数据,传给解码器,解出 AVFrame,然后把 AVFrame 过一遍filter,本文命令filter是空,然后再把 frame 插入 FrameQueue 队列,让播放线程取拿frame播放。

里面比较重要的一个函数是 decoder_decode_frame(),这是一个阻塞函数,它内部会不断循环等待,直到读出一个AVFrame。

重点知识:

decoder_decode_frame()返回的 got_frame 的有几个值?。


下面分析获取到 AVFrame 之后的逻辑。

获取到 AVFrame 之后,ffplay 会对 AVFrame的采样率,格式做一次校验,这里又有一次校验,ffplay真是严谨。主要校验什么呢?校验stream容器层的采样率,格式,是不是跟 AVFrame 的数据是一致的?应该是ffpaly担心,有些音频流,虽然容器层的采样率字段是48000,但实际解码出来的AVFrame 却是 44100。校验如果不一致,就会 调 configure_audio_filters() 重新初始filter。

校验代码如下:

reconfigure =
             cmp_audio_fmts(is->audio_filter_src.fmt, is->audio_filter_src.channels,frame->format, frame->channels) ||
             is->audio_filter_src.channel_layout != dec_channel_layout ||
             is->audio_filter_src.freq           != frame->sample_rate ||
             is->auddec.pkt_serial               != last_serial;

is->audio_filter_src 的值是从解码器 avctx 里面来的,请看下图。

ffpaly.c 2639 行
is->audio_filter_src.freq           = avctx->sample_rate;
is->audio_filter_src.channels       = avctx->channels;
is->audio_filter_src.channel_layout = get_valid_channel_layout(avctx->channel_layout, avctx->channels);
is->audio_filter_src.fmt            = avctx->sample_fmt;

解码器 avctx 的采样率又是从容器层来的,请看以下代码:

ffpaly.c 2581 行
ret = avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar);

校验完采样率之后,就是调 av_buffersrc_add_frame() 把 AVFrame 往 filter 里面送了,然后 循环 调 av_buffersink_get_frame_flags(),不断收割经过filter的AVFrame。然后调 frame_queue_push() 插入 FrameQueue 队列,让播放线程取。


ffplay 源码分析,audio_thread() 解码线程分析完毕。

©版权所属:知识星球:弦外之音,QQ:2338195090。

由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,可以加我微信 Loken1。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注