ffplay播放器-音视频解码线程

解码线程

ffplay的解码线程独⽴于数据读线程,并且每种类型的流(AVStream)都有其各⾃的解码线程,如:

  • video_thread⽤于解码video stream;
  • audio_thread⽤于解码audio stream;
  • subtitle_thread⽤于解码subtitle stream。

为⽅便阅读,先列⼀张表格,梳理各个变量、函数名称

类型 PacketQueue FrameQueue vidck 解码线程
视频 videoq pictq vidcllk video_thread
⾳频 audioq sampq audclk audio_thread
字幕 subtitleq subpq subtitle_thread

其中PacketQueue⽤于存放从read_thread取到的各⾃播放时间内的AVPacket。FrameQueue⽤于存放 各⾃解码后的AVFrame。Clock⽤于同步⾳视频。解码线程负责将PacketQueue数据解码为AVFrame, 并存⼊FrameQueue。
对于不同流,其解码过程⼤同⼩异。

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; // 检查到packet队列空时发送 signal缓存read_thread读取数据
    int64_t start_pts;       // 初始化时是stream的start time
    AVRational start_pts_tb; // 初始化时是stream的time_base
    int64_t next_pts;       // 记录最近⼀次解码后的frame的pts,当解出来的部分帧没有有效的pts 时则使⽤next_pts进⾏推算
    AVRational next_pts_tb;  // next_pts的单位
    SDL_Thread *decoder_tid;  // 线程句柄
} Decoder;

解码器相关的函数

(decoder我们ffplay⾃定义,重新封装的。 avcodec才是ffmpeg的提供的)

  • 初始化解码器
    void decoder_init(Decoder *d, AVCodecContext *avctx, PacketQueue *queue, SDL_cond *empty_queue_cond);
  • 启动解码器
    int decoder_start(Decoder *d, int (*fn)(void *), const char thread_name, void arg)
  • 解帧
    int decoder_decode_frame(Decoder *d, AVFrame *frame, AVSubtitle *sub);
  • 终⽌解码器
    void decoder_abort(Decoder *d, FrameQueue *fq);
  • 销毁解码器
    void decoder_destroy(Decoder *d);

使用方法

  • 启动解码线程
    • decoder_init()
    • decoder_start()
  • 解码线程具体流程
    • decoder_decode_frame()
  • 退出解码线程
    • decoder_abort()
    • decoder_destroy()

视频解码线程

数据来源:从read_thread线程⽽来
数据处理:在video_thread进⾏解码,具体调⽤get_video_frame
数据出⼝:在video_refresh读取frame进⾏显示

video_thread()

我们先看video_thead,对于滤镜部分(CONFIG_AVFILTER定义部分),这⾥不做分析 ,简化后的代码 如下:

static int video_thread(void *arg)
{
   
    VideoState *is = arg;
    AVFrame *frame = av_frame_alloc();    // 分配解码帧
    double pts;          // pts
    double duration;     // 帧持续时间
    int ret;
    // 1 获取stream timebase
    AVRational tb = is->video_st->time_base;
    // 2 获取帧率,以便计算每帧picture的duration
    AVRational frame_rate = av_guess_frame_rate(is->ic, is->video_st, NULL);
    
    if (!frame)
        return AVERROR(ENOMEM);

    for (;;) {
   // 循环取出视频解码的帧数据
        // 3 解码获取⼀帧视频画⾯
        ret = get_video_frame(is, frame);
        if (ret < 0)
            goto the_end;//解码结束, 什么时候会结束
        if (!ret)       //没有解码得到画⾯, 什么情况下会得不到解后的帧
            continue;
            
            // 4 计算帧持续时间和换算pts值为秒
            // 1/帧率 = duration 单位秒, 没有帧率时则设置为0, 有帧率帧计算出帧间隔
            duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){
   frame_rate.den, frame_rate.num}) : 0);
            // 根据AVStream timebase计算出pts值, 单位为秒
            pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
            // 5 将解码后的视频帧插⼊队列
            ret = queue_picture(is, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial);
            // 6 释放frame对应的数据
            av_frame_unref(frame);// 正常情况下frame对应的buf以被av_frame_m ove_ref
            if (ret < 0) // 返回值⼩于0则退出线程
                goto the_end;
    }
 the_end:
     av_frame_free(&frame);// 释放frame
     return 0;
}

在该流程中,当调⽤函数返回值⼩于<0时则退出线程。 线程的总体流程很清晰:

  1. 获取stream timebase,以便将frame的pts转成秒为单位
  2. 获取帧率,以便计算每帧picture的duration
  3. 获取解码后的视频帧,具体调⽤get_video_frame()实现
  4. 计算帧持续时间和换算pts值为秒
  5. 将解码后的视频帧插⼊队列,具体调⽤queue_picture()实现
  6. 释放frame对应的数据
get_video_frame()

主要流程:

  1. 调⽤ decoder_decode_frame 解码并获取解码后的视频帧;
  2. 分析如果获取到帧是否需要drop掉(逻辑就是如果刚解出来就落后主时钟,那就没有必要放⼊Frame队 列,再拿去播放,但是也是有⼀定的条件的,⻅下⾯分析)
static int get_video_frame(VideoState *is, AVFrame *frame)
{
   
    int got_picture;
    
    // 1. 获取解码后的视频帧
    if ((got_picture = decoder_decode_frame(&is->viddec, frame, NULL)) < 0)
        return -1;// 返回-1意味着要退出解码线程, 所以要分析decoder_decode_ frame什么情况下返回-1

    if (got_picture) {
   // 2. 分析获取到的该帧是否要drop掉
    // 2. 分析获取到的该帧是否要drop掉, 该机制的⽬的是在放⼊帧队列前先drop掉过时 的视频帧
        double dpts = NAN;

        if (frame->pts != AV_NOPTS_VALUE)
        	//计算出秒为单位的pts
            dpts = av_q2d(is->video_st->time_base) * frame->pts;

        frame->sample_aspect_ratio = av_guess_sample_aspect_ratio(is->ic, is->video_st, frame);

        if (framedrop>0 // 允许drop帧
        || (framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) {
   
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值