大家好,我是小涂,今天继续给大家分享播放器里面的相关知识,本篇文章主要是分享ffplay里面的视频解码线程相关源码,废话就不多说,开始开肝!
为了大家好理解整个ffplay是如何播放本地的媒体文件,这个过程经历了哪些过程,所以下面这副图,大家要认真捋一捋,可以按照这个路线去看源码流程,是可以找到的:

看的的时候从Ffplay.c源码里面的main函数里面开始看起,然后找到stream_open函数就行:


从上面这副图观察,我们可以看到,ffplay的解码线程是独立于数据读取线程,我们可以看到视频解码有自己的解码线程,同样音频解码也有自己的线程,比如说:
为了理解,下面是梳理了一下视频和音频解码线程对比:
类型 | PacketQueue | FrameQueue | Clock | 解码线程 |
|---|---|---|---|---|
视频 | videoqueue | pictq | vidclk | video_thread |
音频 | audioqueue | sampq | audclk | audio_thread |
其中PackQueue队列用于存放从read_thread线程读取到的各自播放时间内的AVPacket;而FrameQueue队列用于存放各自编码后的AVFrame;Clock用于同步音视频;解码线程负责将PacketQueue队列里面的数据解码为AVFrmae,然后并存入到AVFrame队列中去。
好了,下面我们开来看解码器的一个工程流程,这里面会涉及到一些API:
在说这些API之前,我们先回忆一下解码器这个结构体到底有些啥东西:
typedef struct Decoder
{
AVPacket pkt ;// 存放被压缩的数据包
PacketQueue *queue; //数据包队列
AVCodecContext *avctx; //解码器上下文
int pkt_serial; //数据包序列
int finished; //等于0的话,解码器就处于工作状态,不等于0的话,解码器就处于空闲状态
int packet_pending; //如果等于0的话,解码器就处于异常状态;
需要考虑重新设置解码器;如果等于1的话,
解码器就处于正常状态
SDL_cond *empty_queue_cond; //检查到队列为空的时候;
就要发送sigal信号来告诉read_thread线程读取数据进入队列
int64_t start_pts; //初始化流的start time 起始时间
AVRational start_pts_tb; // 初始化流的time_base
int64_t next_pts; //记录最近一次解码后的
frame的pts,当解出来的部分帧没有有效的pts时候
,那么就使用next_pts
SDL_Thread *decoder_tid; //解码线程id
}Decoder;
static int decoder_init(Decoder *d, AVCodecContext *avctx, PacketQueue *queue, SDL_cond *empty_queue_cond) {
memset(d, 0, sizeof(Decoder)); //清空解码器Decoder,以防有脏数据影响
d->pkt = av_packet_alloc(); //为数据包分配内存
if (!d->pkt)
return AVERROR(ENOMEM); //分配失败就退出
d->avctx = avctx; //初始化解码器上下文
d->queue = queue; //绑定对应的packet_queue
d->empty_queue_cond = empty_queue_cond; //绑定read_thread线程的continue_read_thread
d->start_pts = AV_NOPTS_VALUE;//码流的start time设置为无效值
d->pkt_serial = -1; //数据包序列值设置为-1
return 0;
}
static int decoder_start(Decoder *d, int (*fn)(void *), const char *thread_name, void* arg)
{
packet_queue_start(d->queue);//启用对应的packet队列
d->decoder_tid = SDL_CreateThread(fn, thread_name, arg); //创建解码线程
if (!d->decoder_tid) {
av_log(NULL, AV_LOG_ERROR, "SDL_CreateThread(): %s\n", SDL_GetError());
return AVERROR(ENOMEM);//创建失败,就退出
}
return 0;
}
static int decoder_decode_frame(Decoder *d, AVFrame *frame, AVSubtitle *sub) {
int ret = AVERROR(EAGAIN);
//流连续情况下获取解码后的帧
for (;;) {
//先判断是否是同一播放序列的数据
if (d->queue->serial == d->pkt_serial) {
do {
if (d->queue->abort_request)
return -1;
switch (d->avctx->codec_type) {
case AVMEDIA_TYPE_VIDEO:
ret = avcodec_receive_frame(d->avctx, frame);
if (ret >= 0) {
if (decoder_reorder_pts == -1) {
frame->pts = frame->best_effort_timestamp;
} else if (!decoder_reorder_pts) {
frame->pts = frame->pkt_dts;
}
}
break;
case AVMEDIA_TYPE_AUDIO:
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;
}
}
break;
}
if (ret == AVERROR_EOF) {
d->finished = d->pkt_serial;
avcodec_flush_buffers(d->avctx);
return 0;
}
if (ret >= 0)
return 1;
} while (ret != AVERROR(EAGAIN));
}
do {
if (d->queue->nb_packets == 0)
SDL_CondSignal(d->empty_queue_cond);
if (d->packet_pending) {
d->packet_pending = 0;
} else {
int old_serial = d->pkt_serial;
if (packet_queue_get(d->queue, d->pkt, 1, &d->pkt_serial) < 0)
return -1;
if (old_serial != d->pkt_serial) {
avcodec_flush_buffers(d->avctx);
d->finished = 0;
d->next_pts = d->start_pts;
d->next_pts_tb = d->start_pts_tb;
}
}
if (d->queue->serial == d->pkt_serial)
break;
av_packet_unref(d->pkt);
} while (1);
if (d->avctx->codec_type == AVMEDIA_TYPE_SUBTITLE) {
int got_frame = 0;
ret = avcodec_decode_subtitle2(d->avctx, sub, &got_frame, d->pkt);
if (ret < 0) {
ret = AVERROR(EAGAIN);
} else {
if (got_frame && !d->pkt->data) {
d->packet_pending = 1;
}
ret = got_frame ? 0 : (d->pkt->data ? AVERROR(EAGAIN) : AVERROR_EOF);
}
av_packet_unref(d->pkt);
} else {
if (avcodec_send_packet(d->avctx, d->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;
} else {
av_packet_unref(d->pkt);
}
}
}
}
这里暂时只注解了一部分视频解帧注解,后面讲到解码线程的时候,再做详细解析。
static void decoder_abort(Decoder *d, FrameQueue *fq)
{
packet_queue_abort(d->queue); //终止packet队列,packetQueue的abort_request被置为1
frame_queue_signal(fq); //// 唤醒Frame队列, 以便退出
SDL_WaitThread(d->decoder_tid, NULL);// 等待解码线程退出
d->decoder_tid = NULL; // 线程ID重置
packet_queue_flush(d->queue);// 刷新packet队列,并释放数据
}
static void decoder_destroy(Decoder *d) {
av_packet_free(&d->pkt);//取消数据包引用的缓冲区,并且并且重新设置数据包里面的成员属性为默认值
avcodec_free_context(&d->avctx);//释放编解码器上下文和与之相关的一切,并将NULL写入
提供的指针
}
上面介绍的解码器API使用流程都在接口stream_component_open()接口里面调用:

大家可以按照那张播放框架图,去跟踪源码学习一下,大致就清楚了整个流程是怎样回事了。
好了,本期的分享就到这里,我们下期见,下期继续进行video_thread源码解析!