首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >ffplay播放器之解码器源码解读!

ffplay播放器之解码器源码解读!

作者头像
用户6280468
发布2022-03-21 18:44:46
发布2022-03-21 18:44:46
1.2K00
代码可运行
举报
文章被收录于专栏:txp玩Linuxtxp玩Linux
运行总次数:0
代码可运行

前言:

大家好,我是小涂,今天继续给大家分享播放器里面的相关知识,本篇文章主要是分享ffplay里面的视频解码线程相关源码,废话就不多说,开始开肝!

视频解码器流程:

为了大家好理解整个ffplay是如何播放本地的媒体文件,这个过程经历了哪些过程,所以下面这副图,大家要认真捋一捋,可以按照这个路线去看源码流程,是可以找到的:

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

从上面这副图观察,我们可以看到,ffplay的解码线程是独立于数据读取线程,我们可以看到视频解码有自己的解码线程,同样音频解码也有自己的线程,比如说:

  • video_thread用于解码video stream(视频流)
  • audio_thread用于解码audio stream(音频流)

为了理解,下面是梳理了一下视频和音频解码线程对比:

类型

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之前,我们先回忆一下解码器这个结构体到底有些啥东西:

代码语言:javascript
代码运行次数:0
运行
复制
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;
  • 初始化解码器:
代码语言:javascript
代码运行次数:0
运行
复制
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;
}
  • 启动解码器:
代码语言:javascript
代码运行次数: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;
}
  • 解帧:
代码语言:javascript
代码运行次数: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);
            }
        }
    }
}

这里暂时只注解了一部分视频解帧注解,后面讲到解码线程的时候,再做详细解析。

  • 终止解码器:
代码语言:javascript
代码运行次数:0
运行
复制
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队列,并释放数据
}

  • 销毁解码器:
代码语言:javascript
代码运行次数:0
运行
复制
static void decoder_destroy(Decoder *d) {
    av_packet_free(&d->pkt);//取消数据包引用的缓冲区,并且并且重新设置数据包里面的成员属性为默认值
    avcodec_free_context(&d->avctx);//释放编解码器上下文和与之相关的一切,并将NULL写入  
提供的指针
}

上面介绍的解码器API使用流程都在接口stream_component_open()接口里面调用:

总结:

大家可以按照那张播放框架图,去跟踪源码学习一下,大致就清楚了整个流程是怎样回事了。

好了,本期的分享就到这里,我们下期见,下期继续进行video_thread源码解析!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-10-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 txp玩Linux 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言:
  • 视频解码器流程:
  • 总结:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档