美文网首页ffmpeg视频音频
ffplay.c 源码分析- 视频部分

ffplay.c 源码分析- 视频部分

作者: deep_sadness | 来源:发表于2018-11-19 18:54 被阅读88次

    FFmpeg 代码 version 3.3:

    ffplay中的线程模型

    ffplay线程模型-视频为例.png

    概述

    ffplay.c 中线程模型简单命令。主要是有如下几个线程:
    1. 渲染的线程-主线程
    简单的理解,来说就是main方法运行所在的线程。(注意:在Android中这里是SDLThread
    实际上是SDL_CreateWindow 调用所在的线程。以Android为例(笔者比较熟悉),创建的是OpenGLSurface。也就是EGLContext所在的线程了。

    在EventLoop中,将会负责下面两个功能

    • 负责将解码后的数据送显
      从解码后的队列中,取得数据。经过同步时间钟的同步,睡眠后(需要同步的话),然后通过SDL_UpdateTexture/SDL_RenderCopy/SDL_RenderPresent ,更新纹理数据,送显。

    • 对按键进行相应

    2. 读取线程-read_thread
    在main方法中会启动的读取的线程。

    • 循环读取
      这个线程中,会进行读取的循环。不断的通过av_read_frame方法,读取解码前的数据packet。
    • 送入队列
      最后将得到的数据,送入对应的流的packet队列(视频/音频/字幕都对应视频流自己的队列)

    3. 对应流的解码线程-decode thread
    在读取线程中,对AVFormatContext进行初始化,获取AVStream信息后,对应不同的码流会开启对应的解码线程Decode Thread。
    ffplay中这里包括了3种流。视频流。音频流和字幕流。

    以视频流为例子。
    • 循环读取
      会从对应流的packet队列中,得到数据。
      然后送入解码器通过avcodec_decode_video2(旧的API)进行解码。

    • 送入队列
      解码之后,得到解码前的数据AVFrame,并确定对应的pts
      最后然后其再次送入队列当中。

    整体的流程就是这样简单。


    ffplay初始化(main_thread)

    注意:在Android中这里是SDLThread

    1. 对FFmpeg的初始化

    调用av_register_allavformat_network_init
    如果有AVDeviceAVFilter也会对其进行初始化。

        /* register all codecs, demux and protocols */
    #if CONFIG_AVDEVICE
        avdevice_register_all();
    #endif
    #if CONFIG_AVFILTER
        avfilter_register_all();
    #endif
        av_register_all();
        avformat_network_init();
    
    2. 对传递的参数进行初始化

    parse_options(NULL, argc, argv, options, opt_input_file);
    这个方法就是去解析我们传入的参数的。具体的方法写在cmdutils.h 中。
    ffplay支持的参数类型,可以在定义的地方看到。

    static const OptionDef options[]  //这个数组中,有许多选项,不是重点,暂时不做详细的介绍了。
    
    3. SDL的初始化

    SDL会先初始化SDL_init 进行初始化。

    flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER;
        if (audio_disable)
            flags &= ~SDL_INIT_AUDIO;
        else {
            /* Try to work around an occasional ALSA buffer underflow issue when the
             * period size is NPOT due to ALSA resampling by forcing the buffer size. */
            if (!SDL_getenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE"))
                SDL_setenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE","1", 1);
        }
        if (display_disable)
            flags &= ~SDL_INIT_VIDEO;
        if (SDL_Init (flags)) {
            av_log(NULL, AV_LOG_FATAL, "Could not initialize SDL - %s\n", SDL_GetError());
            av_log(NULL, AV_LOG_FATAL, "(Did you set the DISPLAY variable?)\n");
            exit(1);
        }
    

    后续的创建SDL_Window/SDL_Renderer/SDL_texture 的部分,会在后续初始化。

    4. 通过stream_open函数,开启read_thread读取线程
     is = stream_open(input_filename, file_iformat);
    

    通过 stream_open函数,会对VideoState进行初始化,包括解码数据队列(PacketQueue)和解码后数据队列(FrameQueue)和时间钟(Clock)等的初始化,并且开启read_thread

    • VideoState 结构体
      这个结构体相当于一个全局的变量都囊括在一起了。
      从源码中可以看到。
      包括几个方面的参数。
    1. 视频操作类的暂停,运行和seek
    2. 三种同步的时间钟
    3. 三种码流对应的FrameQueue/Decoder/ AVStream/PacketQueue,以及对应码流各自的信息。
    4. 用来Render的一些变量。如SDL_Texture,窗口的位置参数等
    5. read_threadSDL_cond *continue_read_thread条件锁
    6. 还有些中间变量
    // 视频状态结构
    typedef struct VideoState {
        
        SDL_Thread *read_tid;                   // 读取线程
        AVInputFormat *iformat;             // 输入格式
        int abort_request;                          // 请求取消
        int force_refresh;                          // 强制刷新
        int paused;                                 // 停止
        int last_paused;                                // 最后停止
        int queue_attachments_req;          // 队列附件请求
        int seek_req;                                   // 查找请求
        int seek_flags;                             // 查找标志
        int64_t seek_pos;                           // 查找位置
        int64_t seek_rel;                           // 
        int read_pause_return;                  // 读停止返回
        AVFormatContext *ic;                    // 解码格式上下文
        int realtime;                                   // 是否实时码流
    
        Clock audclk;                               // 音频时钟
        Clock vidclk;                                   // 视频时钟
        Clock extclk;                                   // 外部时钟
    
        FrameQueue pictq;                       // 视频队列
        FrameQueue subpq;                       // 字幕队列
        FrameQueue sampq;                       // 音频队列
    
        Decoder auddec;                         // 音频解码器
        Decoder viddec;                         // 视频解码器
        Decoder subdec;                         // 字幕解码器
    
        int audio_stream;                           // 音频码流Id
    
        int av_sync_type;                           // 同步类型
    
        double audio_clock;                     // 音频时钟
        int audio_clock_serial;                 // 音频时钟序列
        double audio_diff_cum;                  // 用于音频差分计算 /* used for AV difference average computation */
        double audio_diff_avg_coef;         //  
        double audio_diff_threshold;            // 音频差分阈值
        int audio_diff_avg_count;               // 平均差分数量
        AVStream *audio_st;                     // 音频码流
        PacketQueue audioq;                 // 音频包队列
        int audio_hw_buf_size;                  // 硬件缓冲大小
        uint8_t *audio_buf;                     // 音频缓冲区
        uint8_t *audio_buf1;                        // 音频缓冲区1
        unsigned int audio_buf_size;            // 音频缓冲大小 /* in bytes */
        unsigned int audio_buf1_size;       // 音频缓冲大小1
        int audio_buf_index;                        // 音频缓冲索引 /* in bytes */
        int audio_write_buf_size;               // 音频写入缓冲大小
        int audio_volume;                           // 音量
        int muted;                                      // 是否静音
        struct AudioParams audio_src;       // 音频参数
    #if CONFIG_AVFILTER                         
        struct AudioParams audio_filter_src; // 音频过滤器
    #endif
        struct AudioParams audio_tgt;       // 音频参数
        struct SwrContext *swr_ctx;         // 音频转码上下文
        int frame_drops_early;                  // 
        int frame_drops_late;                       // 
    
        enum ShowMode {                     // 显示类型
            SHOW_MODE_NONE = -1,        // 无显示
            SHOW_MODE_VIDEO = 0,            // 显示视频
            SHOW_MODE_WAVES,                // 显示波浪,音频
            SHOW_MODE_RDFT,                 // 自适应滤波器
            SHOW_MODE_NB                        // 
        } show_mode;
        int16_t sample_array[SAMPLE_ARRAY_SIZE]; // 采样数组
        int sample_array_index;                 // 采样索引
        int last_i_start;                               // 上一开始
        RDFTContext *rdft;                      // 自适应滤波器上下文
        int rdft_bits;                                  // 自使用比特率
        FFTSample *rdft_data;                   // 快速傅里叶采样
        int xpos;                                       // 
        double last_vis_time;                       // 
        SDL_Texture *vis_texture;               // 音频Texture
        SDL_Texture *sub_texture;               // 字幕Texture
        SDL_Texture *vid_texture;               // 视频Texture
    
        int subtitle_stream;                        // 字幕码流Id
        AVStream *subtitle_st;                  // 字幕码流
        PacketQueue subtitleq;                  // 字幕包队列
    
        double frame_timer;                     // 帧计时器
        double frame_last_returned_time;    // 上一次返回时间
        double frame_last_filter_delay;     // 上一个过滤器延时
        int video_stream;                           // 视频码流Id
        AVStream *video_st;                     // 视频码流
        PacketQueue videoq;                 // 视频包队列
        double max_frame_duration;          // 最大帧显示时间 // maximum duration of a frame - above this, we consider the jump a timestamp discontinuity
        struct SwsContext *img_convert_ctx; // 视频转码上下文
        struct SwsContext *sub_convert_ctx; // 字幕转码上下文
        int eof;                                            // 结束标志
    
        char *filename;                             // 文件名
        int width, height, xleft, ytop;         // 宽高,其实坐标
        int step;                                       // 步进
    
    #if CONFIG_AVFILTER
        int vfilter_idx;                                // 过滤器索引
        AVFilterContext *in_video_filter;   // 第一个视频滤镜 // the first filter in the video chain
        AVFilterContext *out_video_filter;  // 最后一个视频滤镜 // the last filter in the video chain
        AVFilterContext *in_audio_filter;   // 第一个音频过滤器 // the first filter in the audio chain
        AVFilterContext *out_audio_filter;  // 最后一个音频过滤器 // the last filter in the audio chain
        AVFilterGraph *agraph;                  // 音频过滤器 // audio filter graph
    #endif
    
        // 上一个视频码流Id、上一个音频码流Id、上一个字幕码流Id
        int last_video_stream, last_audio_stream, last_subtitle_stream;
    
        SDL_cond *continue_read_thread; // 连续读线程
    } VideoState;
    
    5. 开启EventLoop
    refresh_loop_wait_event(cur_stream, &event);
    
    static void refresh_loop_wait_event(VideoState *is, SDL_Event *event) {
        double remaining_time = 0.0;
      //取出事件
        SDL_PumpEvents();
        //  如果事件不是要处理的,就会进行渲染的判断,然后继续取出事件的循环
        while (!SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)) {
          //是否显示鼠标
            if (!cursor_hidden && av_gettime_relative() - cursor_last_shown > CURSOR_HIDE_DELAY) {
                SDL_ShowCursor(0);
                cursor_hidden = 1;
            }
            //使用av_usleep来控制视频刷新的帧率
            if (remaining_time > 0.0)
                av_usleep((int64_t)(remaining_time * 1000000.0));
            remaining_time = REFRESH_RATE;
            //通知刷新的话。is->force_refresh==1.
            if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))
                //进入绘制
                video_refresh(is, &remaining_time);
            //继续下一个事件循环
            SDL_PumpEvents();
        }
    }
    

    通过这个方法,不断的来取出SDL的事件。同时通过这样的事件循环,不断video_refresh送显绘制。

    读取线程read_thread

    如上部分所示,通过stream_open方法,开启read_thread

    初始化参数

    read_thread作为读取线程。

    1. 需要初始化AVformatContextAVStream
    2. 同时,在得到对应的AVStream之后,它还负责初始化好对应的AVCodecAVCodecContext,然后开启对应的解码线程。
    3. 最后,通过不断的av_read_frame,将数据读取出,送入队列。等待解码。
    需要初始化AVformatContextAVStream

    1. 显示先创建 AVformatContext

      ic = avformat_alloc_context();
    

    2. 设置解码中断回调方法
    这个方法,会在网络中断的时候,发生调用

        ic->interrupt_callback.callback = decode_interrupt_cb;
        // 设置中断回调参数
        ic->interrupt_callback.opaque = is;
    

    decode_interrupt_cb 方法

    static int decode_interrupt_cb(void *ctx)
    {
        VideoState *is = ctx;
        return is->abort_request;
    }
    

    3. 打开avformat_open_input

      err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts);
      //同时,将其注入成全局的一个变量。但是具体如何使用呢??
     av_format_inject_global_side_data(ic);
    

    4. 查找AVStream
    找到不同的AVStream,并将其放入对应的数组中。等待后续使用。
    找到视频流之后,还会再次同步显示的比例和视频的长宽

        for (i = 0; i < ic->nb_streams; i++) {
            AVStream *st = ic->streams[i];
            enum AVMediaType type = st->codecpar->codec_type;
            st->discard = AVDISCARD_ALL;
            if (type >= 0 && wanted_stream_spec[type] && st_index[type] == -1)
                if (avformat_match_stream_specifier(ic, st, wanted_stream_spec[type]) > 0)
                    st_index[type] = i;
        }
        for (i = 0; i < AVMEDIA_TYPE_NB; i++) {
            if (wanted_stream_spec[i] && st_index[i] == -1) {
                av_log(NULL, AV_LOG_ERROR, "Stream specifier %s does not match any %s stream\n", wanted_stream_spec[i], av_get_media_type_string(i));
                st_index[i] = INT_MAX;
            }
        }
    
        if (!video_disable)
            st_index[AVMEDIA_TYPE_VIDEO] =
                av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO,
                                    st_index[AVMEDIA_TYPE_VIDEO], -1, NULL, 0);
        if (!audio_disable)
            st_index[AVMEDIA_TYPE_AUDIO] =
                av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO,
                                    st_index[AVMEDIA_TYPE_AUDIO],
                                    st_index[AVMEDIA_TYPE_VIDEO],
                                    NULL, 0);
        if (!video_disable && !subtitle_disable)
            st_index[AVMEDIA_TYPE_SUBTITLE] =
                av_find_best_stream(ic, AVMEDIA_TYPE_SUBTITLE,
                                    st_index[AVMEDIA_TYPE_SUBTITLE],
                                    (st_index[AVMEDIA_TYPE_AUDIO] >= 0 ?
                                     st_index[AVMEDIA_TYPE_AUDIO] :
                                     st_index[AVMEDIA_TYPE_VIDEO]),
                                    NULL, 0);
    
        is->show_mode = show_mode;
        //找到视频流之后,还会再次同步显示的比例和视频的长宽
        if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {
            AVStream *st = ic->streams[st_index[AVMEDIA_TYPE_VIDEO]];
            AVCodecParameters *codecpar = st->codecpar;
            AVRational sar = av_guess_sample_aspect_ratio(ic, st, NULL);
            if (codecpar->width)
                set_default_window_size(codecpar->width, codecpar->height, sar);
        }
    
    
    开启对应的解码线程

    打开stream_component_open对应的AVStream。打开解码线程。
    ffplay中对应三种码流。(视频、音频和字幕,对应打开自己的解码线程)

       /* open the streams */
        if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) {
            stream_component_open(is, st_index[AVMEDIA_TYPE_AUDIO]);
        }
    
        ret = -1;
        if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {
            ret = stream_component_open(is, st_index[AVMEDIA_TYPE_VIDEO]);
        }
        if (is->show_mode == SHOW_MODE_NONE)
            is->show_mode = ret >= 0 ? SHOW_MODE_VIDEO : SHOW_MODE_RDFT;
    
        if (st_index[AVMEDIA_TYPE_SUBTITLE] >= 0) {
            stream_component_open(is, st_index[AVMEDIA_TYPE_SUBTITLE]);
        }
    

    开启对应的线程之前,会初始化好每个码流的AVCodecAVCodecContext

    • 初始化AVCodecAVCodecContext
        //创建AVCodecContext
       avctx = avcodec_alloc_context3(NULL);
        if (!avctx)
            return AVERROR(ENOMEM);
        //从找到对应的流中的codecpar,codecpar其实是avcodec_parameters,
        // 然后将它完全复制到创建的AVCodecContext
        ret = avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar);
        if (ret < 0)
            goto fail;
        avctx->pkt_timebase = ic->streams[stream_index]->time_base;
        
        //根据codec_id找出最合适的codec
        codec = avcodec_find_decoder(avctx->codec_id);
    
        switch(avctx->codec_type){
            case AVMEDIA_TYPE_AUDIO   : is->last_audio_stream    = stream_index; forced_codec_name =    audio_codec_name; break;
            case AVMEDIA_TYPE_SUBTITLE: is->last_subtitle_stream = stream_index; forced_codec_name = subtitle_codec_name; break;
            case AVMEDIA_TYPE_VIDEO   : is->last_video_stream    = stream_index; forced_codec_name =    video_codec_name; break;
        }
        //强制通过解码器的名字,来打开对应的解码器
        if (forced_codec_name)
            codec = avcodec_find_decoder_by_name(forced_codec_name);
        if (!codec) {
            if (forced_codec_name) av_log(NULL, AV_LOG_WARNING,
                                          "No codec could be found with name '%s'\n", forced_codec_name);
            else                   av_log(NULL, AV_LOG_WARNING,
                                          "No codec could be found with id %d\n", avctx->codec_id);
            ret = AVERROR(EINVAL);
            goto fail;
        }
        avctx->codec_id = codec->id;
    
        // 下面是设置属性,也不大懂。。这些属性的意思
        if (stream_lowres > codec->max_lowres) {
            av_log(avctx, AV_LOG_WARNING, "The maximum value for lowres supported by the decoder is %d\n",
                    codec->max_lowres);
            stream_lowres = codec->max_lowres;
        }
        avctx->lowres = stream_lowres;
    
        if (fast)
            avctx->flags2 |= AV_CODEC_FLAG2_FAST;
    
        opts = filter_codec_opts(codec_opts, avctx->codec_id, ic, ic->streams[stream_index], codec);
       //av_dict_set方法,传入参数的效果,等同于我们用命令行时 - 的传递方式
        if (!av_dict_get(opts, "threads", NULL, 0))
            av_dict_set(&opts, "threads", "auto", 0);
        if (stream_lowres)
            av_dict_set_int(&opts, "lowres", stream_lowres, 0);
        if (avctx->codec_type == AVMEDIA_TYPE_VIDEO || avctx->codec_type == AVMEDIA_TYPE_AUDIO)
            av_dict_set(&opts, "refcounted_frames", "1", 0);
        if ((ret = avcodec_open2(avctx, codec, &opts)) < 0) {
            goto fail;
        }
        if ((t = av_dict_get(opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) {
            av_log(NULL, AV_LOG_ERROR, "Option %s not found.\n", t->key);
            ret =  AVERROR_OPTION_NOT_FOUND;
            goto fail;
        }
    
    
    • 初始化对应的解码线程
      有了AVCodecContext 和AVCodec 之后,就可以初始化解码线程了
      对解码器的参数再次配置(音频需要),
      然后decoder_init方法初始化Decoder结构体
    static void decoder_init(Decoder *d, AVCodecContext *avctx, PacketQueue *queue, SDL_cond *empty_queue_cond) {
        memset(d, 0, sizeof(Decoder));
        d->avctx = avctx;
        d->queue = queue;
        d->empty_queue_cond = empty_queue_cond;
        d->start_pts = AV_NOPTS_VALUE;
        d->pkt_serial = -1;
    }
    

    decoder_start 正式开启线程。

    static int decoder_start(Decoder *d, int (*fn)(void *), void *arg)
    {
        packet_queue_start(d->queue);
        d->decoder_tid = SDL_CreateThread(fn, "decoder", arg);
        if (!d->decoder_tid) {
            av_log(NULL, AV_LOG_ERROR, "SDL_CreateThread(): %s\n", SDL_GetError());
            return AVERROR(ENOMEM);
        }
        return 0;
    }
    

    packet_queue_start 开启线程之前,将flush_pkt送入PacketQueue队列当中。

    static void packet_queue_start(PacketQueue *q)
    {
        SDL_LockMutex(q->mutex);
        q->abort_request = 0;
        packet_queue_put_private(q, &flush_pkt);
        SDL_UnlockMutex(q->mutex);
    }
    
    不断的av_read_frame,将数据读取出,送入队列

    ps:快进的逻辑后面分析

    读取av_read_frame

     ret = av_read_frame(ic, pkt);
    

    读取失败,各个流中送入空包,同时锁住continue_read_thread,等待10ms

     if (ret < 0) {
     // 读取结束或失败。会想各个流,送入一个空的packet.为什么要送入空的packet??
                if ((ret == AVERROR_EOF || avio_feof(ic->pb)) && !is->eof) {
                    if (is->video_stream >= 0)
                        packet_queue_put_nullpacket(&is->videoq, is->video_stream);
                    if (is->audio_stream >= 0)
                        packet_queue_put_nullpacket(&is->audioq, is->audio_stream);
                    if (is->subtitle_stream >= 0)
                        packet_queue_put_nullpacket(&is->subtitleq, is->subtitle_stream);
                    is->eof = 1;
                }
                if (ic->pb && ic->pb->error)
                    break;
                //读取失败的话,读取失败的原因有很多,其他地方可能会重新Signal这个锁condition。如果没有singal这个condition的话,就会等待10ms之后,再释放,重新循环读取. 那这个continue_read_thread 到底是锁了哪呢?
                SDL_LockMutex(wait_mutex);
                SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
                SDL_UnlockMutex(wait_mutex);
                continue;
            } else {
                is->eof = 0;
            }
    

    todo:continue_read_thread 等同于队列中的empty_queue_cond???

    读取成功,送入队列
    变量pkt中就是我们读取到的数据。

    /* check if packet is in play range specified by user, then queue, otherwise discard */
            //记录stream_start_time
            stream_start_time = ic->streams[pkt->stream_index]->start_time;
            //如果没有pts, 就用dts
            pkt_ts = pkt->pts == AV_NOPTS_VALUE ? pkt->dts : pkt->pts;
            //判断是否在范围内。如果duration还没被定义的话,通过
             //或者在定义的duration内才可以,用当前的pts-start_time .
            //duration 会在解码器打开之后,才会被初始化
            pkt_in_play_range = duration == AV_NOPTS_VALUE ||
                    (pkt_ts - (stream_start_time != AV_NOPTS_VALUE ? stream_start_time : 0)) *
                    av_q2d(ic->streams[pkt->stream_index]->time_base) -
                    (double)(start_time != AV_NOPTS_VALUE ? start_time : 0) / 1000000
                    <= ((double)duration / 1000000);
            // 将解复用得到的数据包添加到对应的待解码队列中
            if (pkt->stream_index == is->audio_stream && pkt_in_play_range) {
                packet_queue_put(&is->audioq, pkt);
            } else if (pkt->stream_index == is->video_stream && pkt_in_play_range
                       && !(is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC)) {
                packet_queue_put(&is->videoq, pkt);
            } else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) {
                packet_queue_put(&is->subtitleq, pkt);
            } else {
                av_packet_unref(pkt);
            }
    

    最后退出时,需要关闭对应资源
    这个线程关闭的时候。会清空AVFormatContext 和锁资源

    if (ic && !is->ic)
            avformat_close_input(&ic);
    
        if (ret != 0) {
            SDL_Event event;
    
            event.type = FF_QUIT_EVENT;
            event.user.data1 = is;
            SDL_PushEvent(&event);
        }
        SDL_DestroyMutex(wait_mutex);
        return 0;
    
    入列的操作

    整体的流程如上,让我们在特别关注一下,具体的入列的操作

    MyAVPacketList结构体
    PacketList中存储的单元是自己定义的MyAVPacketList结构体

    typedef struct MyAVPacketList {
        AVPacket pkt;
        struct MyAVPacketList *next;
        int serial;
    } MyAVPacketList;
    

    结构体中主要是保存了AVPacket数据和队列的下一个的指针。
    同时还保留了一个serial变量。它可以理解成操作数。在初始化和快进的时候,会增加操作数。小于的操作数,在下一次显示的时候,会直接被抛弃。

    队列操作packet_queue_put

    static int packet_queue_put(PacketQueue *q, AVPacket *pkt)
    {
        int ret;
        //锁住队列中的互斥锁。这个锁是每个队列自己的
        SDL_LockMutex(q->mutex);
        ret = packet_queue_put_private(q, pkt);
        SDL_UnlockMutex(q->mutex);
    
        // 如果不是flush类型的包,并且没有成功入队,则销毁当前的包
        if (pkt != &flush_pkt && ret < 0)
            av_packet_unref(pkt);
    
        return ret;
    }
    
    static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt)
    {
        MyAVPacketList *pkt1;
    
        // 如果队列本身处于舍弃状态,则直接返回-1
        if (q->abort_request)
           return -1;
    
        // 创建一个包
        pkt1 = av_malloc(sizeof(MyAVPacketList));
        if (!pkt1)
            return -1;
        pkt1->pkt = *pkt;
        pkt1->next = NULL;
    
        // 判断包是否数据flush类型,调整包序列
        // 在创建decoder thread时,会刷入一个flush_pkt,这个时候会提高serial
        if (pkt == &flush_pkt)
            q->serial++;
        pkt1->serial = q->serial;
    
        // 调整指针。存入队尾,若没有队尾,就放在开头。
        if (!q->last_pkt)
            q->first_pkt = pkt1;
        else
            q->last_pkt->next = pkt1;
        q->last_pkt = pkt1;
        q->nb_packets++;
        q->size += pkt1->pkt.size + sizeof(*pkt1);
        q->duration += pkt1->pkt.duration;
        /* XXX: should duplicate packet data in DV case */
        // 条件信号
       //通知读的部分,有数据了,可以继续读取了
        //q->cond 在读取的时候,如果没有数据就会锁住,这样,生产了数据,就会通知读取。相等于一个读锁
        SDL_CondSignal(q->cond);
        return 0;
    }
    

    队列操作packet_queue_put_nullpacket
    在读取错误时,也会丢入一个空白。

    static int packet_queue_put_nullpacket(PacketQueue *q, int stream_index)
    {
        // 创建一个空数据的包
        AVPacket pkt1, *pkt = &pkt1;
        av_init_packet(pkt);
        pkt->data = NULL;
        pkt->size = 0;
        pkt->stream_index = stream_index;
        return packet_queue_put(q, pkt);
    }
    

    弄清楚q->mutex 是什么锁
    主线程初始化中,进行初始化的,是每个PacketQueue 都有一个自己的互斥锁和条件锁的。

    static int packet_queue_init(PacketQueue *q)
    {
        // 为一个包队列分配内存
        memset(q, 0, sizeof(PacketQueue));
        // 创建互斥锁
        q->mutex = SDL_CreateMutex();
        if (!q->mutex) {
            av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());
            return AVERROR(ENOMEM);
        }
        // 创建条件锁
        q->cond = SDL_CreateCond();
        if (!q->cond) {
            av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
            return AVERROR(ENOMEM);
        }
        // 默认情况舍弃入队的数据
        q->abort_request = 1;
        return 0;
    }
    

    条件锁q->cond在读取的时候,如果没有数据就会锁住,这样,生产了数据,就会通知读取。相等于一个读锁

    取数据packet_queue_get
    取数据的时候,根据是否需要阻塞,在空队列的时候,会用上面的条件锁,锁住。当产生新的数据时,如果阻塞的话,会通知解锁。

    /* return < 0 if aborted, 0 if no packet and > 0 if packet.  */
    static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial)
    {
        MyAVPacketList *pkt1;
        int ret;
    
        SDL_LockMutex(q->mutex);
    
        for (;;) {
            if (q->abort_request) {
                ret = -1;
                break;
            }
    
            pkt1 = q->first_pkt;
            if (pkt1) {
                q->first_pkt = pkt1->next;
                if (!q->first_pkt)
                    q->last_pkt = NULL;
                q->nb_packets--;
                q->size -= pkt1->pkt.size + sizeof(*pkt1);
                q->duration -= pkt1->pkt.duration;
                *pkt = pkt1->pkt;
                if (serial)
                    *serial = pkt1->serial;
                av_free(pkt1);
                ret = 1;
                break;
            } else if (!block) {
                ret = 0;
                break;
            } else {
                SDL_CondWait(q->cond, q->mutex);
            }
        }
        SDL_UnlockMutex(q->mutex);
        return ret;
    }
    

    视频解码线程video_thread

    read_thread的中对应视频流时,初始化好了AVCodecAVCodecContext。通过decoder_start方法,开启了video_thread
    video_thread中需要创建AVFrame来接受解码后的数据,确定视频的帧率。
    然后开启解码循环。
    不断的从队列中获取解码前的数据,然后送入解码器解码。
    再得到解码后的数据,在送入对应的队列当中。

    初始化参数

    创建AVFrame和得到大致的视频帧率

        //创建AVFrame
        AVFrame *frame = av_frame_alloc();
        //设置好time_base和frame_rate
        AVRational tb = is->video_st->time_base;
        // 猜测视频帧率
        AVRational frame_rate = av_guess_frame_rate(is->ic, is->video_st, NULL);
    

    开始循环解码

    从队列中取得解码器的数据packet_queue_get
    取到要解码的数据,放到 &d->pkt_temp上

    do {
            int ret = -1;
            // 如果处于舍弃状态,直接返回
            if (d->queue->abort_request)
                return -1;
            // 如果当前没有包在等待,或者队列的序列不相同时,取出下一帧
            if (!d->packet_pending || d->queue->serial != d->pkt_serial) {
                AVPacket pkt;
                do {
                    // 队列为空
                    if (d->queue->nb_packets == 0)
                        //当队列为空的时候,就会通知释放这个条件锁。之前在读取线程,读取失败的时候,也有锁住这个锁进行等待。
                        SDL_CondSignal(d->empty_queue_cond);
                    // 从队列中取数据
                    if (packet_queue_get(d->queue, &pkt, 1, &d->pkt_serial) < 0)
                        return -1;
                    // 如果是第一个数据,则会把数据清空一遍。然后开始获取
                    if (pkt.data == flush_pkt.data) {
                        //重置解码器的状态,因为第一次开始解码或者快进的时候,会先存入一个flush_data,当取到这个的时候,就需要去/重置解码器的状态
                    //Reset the internal decoder state / flush internal buffers. Should be called
                        avcodec_flush_buffers(d->avctx);
                        d->finished = 0;
                        d->next_pts = d->start_pts;
                        d->next_pts_tb = d->start_pts_tb;
                    }
                } while (pkt.data == flush_pkt.data || d->queue->serial != d->pkt_serial);
                // 释放包
                av_packet_unref(&d->pkt);
                // 更新包
                d->pkt_temp = d->pkt = pkt;
                // 包等待
                d->packet_pending = 1;
            }
    

    得到pkt_temp之后,送入解码器进行解码
    解码成功后,还需要得去对应的pts。
    这里有个值得注意的地方是,视频的帧的时间戳,可以通过av_frame_get_best_effort_timestamp来计算。
    而音频部分的时间戳,则是直接通过time_base来计算。

    // 根据解码器类型判断是音频还是视频还是字幕
            switch (d->avctx->codec_type) {
                // 视频解码
                case AVMEDIA_TYPE_VIDEO:
                    // 解码视频
                    ret = avcodec_decode_video2(d->avctx, frame, &got_frame, &d->pkt_temp);
                    // 解码成功,更新时间戳
                    if (got_frame) {
                       //这里需要注意的是,默认下decoder_reorder_pts =-1.
                        if (decoder_reorder_pts == -1) {
                            //所以默认情况下,frame的pts可以通过av_frame_get_best_effort_timestamp来取的
                            frame->pts = av_frame_get_best_effort_timestamp(frame);
                        } else if (!decoder_reorder_pts) {      // 如果不重新排列时间戳,则需要更新帧的pts
                            frame->pts = frame->pkt_dts;
                        }
                    }
                    break;
    
                // 音频解码
                case AVMEDIA_TYPE_AUDIO:
                    // 音频解码
                    ret = avcodec_decode_audio4(d->avctx, frame, &got_frame, &d->pkt_temp);
                    // 音频解码完成,更新时间戳
                    if (got_frame) {
                        AVRational tb = (AVRational){1, frame->sample_rate};
                        // 更新帧的时间戳
                        if (frame->pts != AV_NOPTS_VALUE)
                            //音频解码的pts,则只能自己通过time_base进行计算
                            frame->pts = av_rescale_q(frame->pts, av_codec_get_pkt_timebase(d->avctx), 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;
                // 字幕解码
                case AVMEDIA_TYPE_SUBTITLE:
                    ret = avcodec_decode_subtitle2(d->avctx, sub, &got_frame, &d->pkt_temp);
                    break;
            }
    
            // 判断是否解码成功
           //  如果解码失败的话,包继续等待
            if (ret < 0) {
                d->packet_pending = 0;
            } else {
                d->pkt_temp.dts =
                d->pkt_temp.pts = AV_NOPTS_VALUE;
                //下面这个操作不明白???
                if (d->pkt_temp.data) {
                    if (d->avctx->codec_type != AVMEDIA_TYPE_AUDIO)
                        ret = d->pkt_temp.size;
    
                    d->pkt_temp.data += ret;
                    d->pkt_temp.size -= ret;
                    
                    if (d->pkt_temp.size <= 0)
                        d->packet_pending = 0;
                } else {
                    if (!got_frame) {
                        d->packet_pending = 0;
                        d->finished = d->pkt_serial;
                    }
                }
            }
    

    得到pkt_temp之后,还需要判断,是否需要抛弃

    static int get_video_frame(VideoState *is, AVFrame *frame)
    {
        int got_picture;
        // 解码视频帧
        if ((got_picture = decoder_decode_frame(&is->viddec, frame, NULL)) < 0)
            return -1;
        // 判断是否解码成功
        if (got_picture) {
            double dpts = NAN;
    
            if (frame->pts != AV_NOPTS_VALUE)
                //通过 pts*av_q2d(timebase)可以得到准确的时间
                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);
            // 判断是否需要舍弃该帧。如果当前的不是用视频的时间为主,则会开始考虑丢帧。因为太慢了,所以才会进行丢帧,所以diff小于阀值的话,就会去丢帧 10.0
            if (framedrop>0 || (framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) {
                if (frame->pts != AV_NOPTS_VALUE) {
                    //得到的是当前的时间和时间钟之间的差值。
                    double diff = dpts - get_master_clock(is);
                    if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD &&
                        diff - is->frame_last_filter_delay < 0 &&
                        is->viddec.pkt_serial == is->vidclk.serial &&
                        is->videoq.nb_packets) {
                        is->frame_drops_early++;
                        av_frame_unref(frame);
                        got_picture = 0;
                    }
                }
            }
        }
    
        return got_picture;
    }
    

    计算时间和入列
    这里的帧的长度就是用上面预计得到的frame_rate进行计算。

    // 计算帧的pts、duration等
                //每一帧的时长,应该就是等于帧率的倒数。
                duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);
                pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
                // 放入到已解码队列
                ret = queue_picture(is, frame, pts, duration, av_frame_get_pkt_pos(frame), is->viddec.pkt_serial);
                av_frame_unref(frame);
    
    

    将已经解码的数据放到队列当中queue_picture

    static int queue_picture(VideoState *is, AVFrame *src_frame, double pts, double duration, int64_t pos, int serial)
    {
        Frame *vp;
    
    #if defined(DEBUG_SYNC)
        printf("frame_type=%c pts=%0.3f\n",
               av_get_picture_type_char(src_frame->pict_type), pts);
    #endif
        
      //先从队列中取。因为要queue的大小有限,所以先判断是否可以继续写入
        if (!(vp = frame_queue_peek_writable(&is->pictq)))
            return -1;
    
        vp->sar = src_frame->sample_aspect_ratio;
        vp->uploaded = 0;
    
        vp->width = src_frame->width;
        vp->height = src_frame->height;
        vp->format = src_frame->format;
    
        vp->pts = pts;
        vp->duration = duration;
        vp->pos = pos;
        vp->serial = serial;
    
        set_default_window_size(vp->width, vp->height, vp->sar);
        
        //将src中的数据送入vp当中,并且重置src
        av_frame_move_ref(vp->frame, src_frame);
        //重新推入
        frame_queue_push(&is->pictq);
        return 0;
    }
    
    /**
     * 查找可写帧
     * @param  f [description]
     * @return   [description]
     */
    static Frame *frame_queue_peek_writable(FrameQueue *f)
    {
        /* wait until we have space to put a new frame */
        SDL_LockMutex(f->mutex);
        // 如果帧队列大于最大
        while (f->size >= f->max_size &&
               !f->pktq->abort_request) {
            SDL_CondWait(f->cond, f->mutex);
        }
        SDL_UnlockMutex(f->mutex);
    
        if (f->pktq->abort_request)
            return NULL;
        
        //writable index 是 windex
        return &f->queue[f->windex];
    }
    
    static void frame_queue_push(FrameQueue *f)
    {
        //会将可写的index偏移
        if (++f->windex == f->max_size)
            f->windex = 0;
        SDL_LockMutex(f->mutex);
        f->size++;
        SDL_CondSignal(f->cond);
        SDL_UnlockMutex(f->mutex);
    }
    

    已放入队列,最后
    av_frame_unref(frame) 原来的创建的frame就可以了释放了。

    其他

    attached_pic入列
    attached_pic的意思是有附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面。所以,就是如果有,就去显示吧。

    is->queue_attachments_req = 1;
    

    主线程视频显示部分

    在主线程的EventLoop开启之后,会进行绘制。先会根据主的时间钟进行同步,然后进行显示。这里我们因为只分析视频部分。所以就不关注时间钟的同步了。

    视频的显示

    同步后时间,取到具体的frame时,就送入显示。

    /* display the current picture, if any */
    static void video_display(VideoState *is)
    {
         //当window还没创建的时候,就是第一次,会去初始化
        if (!window)
            video_open(is);
      
        //SDL常规操作两则
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);
        if (is->audio_st && is->show_mode != SHOW_MODE_VIDEO)
            video_audio_display(is);
        else if (is->video_st)
            //video走这里
            video_image_display(is);
        //送显
        SDL_RenderPresent(renderer);
    }
    

    1.第一次会进入video_open
    先去创建SDL_WindowSDL_Renderer
    举Android的例子来说,在Android中SDL使用的是OpenGL。
    SDL_CreateWindow就是通过ANativeWindow 来创建一个GL Surface。同时创建GLContext。
    SDL_CreateRenderer 就是glmakeCurrent, 同时对Renderer的各个方法进行初始化,以供后面调用。

    2.视频的延迟
    视频需要根据pts来显示,之间有间隔,正常进行的话,还需要间隔固定的时间。
    这里就是remaining_time

    static void refresh_loop_wait_event(VideoState *is, SDL_Event *event) {
        double remaining_time = 0.0;
        SDL_PumpEvents();
        while (!SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)) {
            if (!cursor_hidden && av_gettime_relative() - cursor_last_shown > CURSOR_HIDE_DELAY) {
                SDL_ShowCursor(0);
                cursor_hidden = 1;
            }
            if (remaining_time > 0.0)
                av_usleep((int64_t)(remaining_time * 1000000.0));
            remaining_time = REFRESH_RATE;
            if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))
                video_refresh(is, &remaining_time);
            SDL_PumpEvents();
        }
    }
    
    /* called to display each frame */
    static void video_refresh(void *opaque, double *remaining_time)
    {
        VideoState *is = opaque;
        double time;
    
        Frame *sp, *sp2;
    
        if (!is->paused && get_master_sync_type(is) == AV_SYNC_EXTERNAL_CLOCK && is->realtime)
            check_external_clock_speed(is);
    
        if (!display_disable && is->show_mode != SHOW_MODE_VIDEO && is->audio_st) {
            time = av_gettime_relative() / 1000000.0;
            if (is->force_refresh || is->last_vis_time + rdftspeed < time) {
                video_display(is);
                is->last_vis_time = time;
            }
            *remaining_time = FFMIN(*remaining_time, is->last_vis_time + rdftspeed - time);
        }
    
        if (is->video_st) {
    retry:
            if (frame_queue_nb_remaining(&is->pictq) == 0) {
                // nothing to do, no picture to display in the queue
            } else {
                double last_duration, duration, delay;
                Frame *vp, *lastvp;
    
                /* dequeue the picture */
                lastvp = frame_queue_peek_last(&is->pictq);
                vp = frame_queue_peek(&is->pictq);
    
                if (vp->serial != is->videoq.serial) {
                    frame_queue_next(&is->pictq);
                    goto retry;
                }
    
                if (lastvp->serial != vp->serial)
                    is->frame_timer = av_gettime_relative() / 1000000.0;
    
                if (is->paused)
                    goto display;
    
                /* compute nominal last_duration */
                last_duration = vp_duration(is, lastvp, vp);
                delay = compute_target_delay(last_duration, is);
    
                time= av_gettime_relative()/1000000.0;
                if (time < is->frame_timer + delay) {
                    *remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);
                    goto display;
                }
    
                is->frame_timer += delay;
                if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX)
                    is->frame_timer = time;
    
                SDL_LockMutex(is->pictq.mutex);
                if (!isnan(vp->pts))
                    update_video_pts(is, vp->pts, vp->pos, vp->serial);
                SDL_UnlockMutex(is->pictq.mutex);
    
                if (frame_queue_nb_remaining(&is->pictq) > 1) {
                    Frame *nextvp = frame_queue_peek_next(&is->pictq);
                    duration = vp_duration(is, vp, nextvp);
                    if(!is->step && (framedrop>0 || (framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) && time > is->frame_timer + duration){
                        is->frame_drops_late++;
                        frame_queue_next(&is->pictq);
                        goto retry;
                    }
                }
    
                if (is->subtitle_st) {
                        while (frame_queue_nb_remaining(&is->subpq) > 0) {
                            sp = frame_queue_peek(&is->subpq);
    
                            if (frame_queue_nb_remaining(&is->subpq) > 1)
                                sp2 = frame_queue_peek_next(&is->subpq);
                            else
                                sp2 = NULL;
    
                            if (sp->serial != is->subtitleq.serial
                                    || (is->vidclk.pts > (sp->pts + ((float) sp->sub.end_display_time / 1000)))
                                    || (sp2 && is->vidclk.pts > (sp2->pts + ((float) sp2->sub.start_display_time / 1000))))
                            {
                                if (sp->uploaded) {
                                    int i;
                                    for (i = 0; i < sp->sub.num_rects; i++) {
                                        AVSubtitleRect *sub_rect = sp->sub.rects[i];
                                        uint8_t *pixels;
                                        int pitch, j;
    
                                        if (!SDL_LockTexture(is->sub_texture, (SDL_Rect *)sub_rect, (void **)&pixels, &pitch)) {
                                            for (j = 0; j < sub_rect->h; j++, pixels += pitch)
                                                memset(pixels, 0, sub_rect->w << 2);
                                            SDL_UnlockTexture(is->sub_texture);
                                        }
                                    }
                                }
                                frame_queue_next(&is->subpq);
                            } else {
                                break;
                            }
                        }
                }
    
                frame_queue_next(&is->pictq);
                is->force_refresh = 1;
    
                if (is->step && !is->paused)
                    stream_toggle_pause(is);
            }
    display:
            /* display picture */
            if (!display_disable && is->force_refresh && is->show_mode == SHOW_MODE_VIDEO && is->pictq.rindex_shown)
                video_display(is);
        }
        is->force_refresh = 0;
        if (show_status) {
            static int64_t last_time;
            int64_t cur_time;
            int aqsize, vqsize, sqsize;
            double av_diff;
    
            cur_time = av_gettime_relative();
            if (!last_time || (cur_time - last_time) >= 30000) {
                aqsize = 0;
                vqsize = 0;
                sqsize = 0;
                if (is->audio_st)
                    aqsize = is->audioq.size;
                if (is->video_st)
                    vqsize = is->videoq.size;
                if (is->subtitle_st)
                    sqsize = is->subtitleq.size;
                av_diff = 0;
                if (is->audio_st && is->video_st)
                    av_diff = get_clock(&is->audclk) - get_clock(&is->vidclk);
                else if (is->video_st)
                    av_diff = get_master_clock(is) - get_clock(&is->vidclk);
                else if (is->audio_st)
                    av_diff = get_master_clock(is) - get_clock(&is->audclk);
                av_log(NULL, AV_LOG_INFO,
                       "%7.2f %s:%7.3f fd=%4d aq=%5dKB vq=%5dKB sq=%5dB f=%"PRId64"/%"PRId64"   \r",
                       get_master_clock(is),
                       (is->audio_st && is->video_st) ? "A-V" : (is->video_st ? "M-V" : (is->audio_st ? "M-A" : "   ")),
                       av_diff,
                       is->frame_drops_early + is->frame_drops_late,
                       aqsize / 1024,
                       vqsize / 1024,
                       sqsize,
                       is->video_st ? is->viddec.avctx->pts_correction_num_faulty_dts : 0,
                       is->video_st ? is->viddec.avctx->pts_correction_num_faulty_pts : 0);
                fflush(stdout);
                last_time = cur_time;
            }
        }
    }
    

    视频的延迟和丢包
    视频的延迟如果不需要进行同步的情况下,其实应该就是等于两个pts之间的差距,或者就是当前帧的duration
    丢包的情况是,当前时间都已经超过了一帧可能需要的时间。这种情况下,就需要进行丢包。

       if (frame_queue_nb_remaining(&is->pictq) > 1) {
                    Frame *nextvp = frame_queue_peek_next(&is->pictq);
                    duration = vp_duration(is, vp, nextvp);
                    if(!is->step && (framedrop>0 || (framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) && time > is->frame_timer + duration){
                        is->frame_drops_late++;
                        frame_queue_next(&is->pictq);
                        goto retry;
                    }
                }
    
    视频解码后显示的简要流程.png

    3.确认打开做好SDL_WindowSDL_Renderer之后,就会调用
    video_image_display进行显示。

    static void video_image_display(VideoState *is)
    {
        Frame *vp;
        Frame *sp = NULL;
        SDL_Rect rect;
    
        vp = frame_queue_peek_last(&is->pictq);
        
        //省略了关于字幕的部分...
        
        //计算SDL_Rect  就是当前显示的范围
        calculate_display_rect(&rect, is->xleft, is->ytop, is->width, is->height, vp->width, vp->height, vp->sar);
        
        //如果这一帧还没显示过
        if (!vp->uploaded) {
            int sdl_pix_fmt = vp->frame->format == AV_PIX_FMT_YUV420P ? SDL_PIXELFORMAT_YV12 : SDL_PIXELFORMAT_ARGB8888;
            //如果需要重新创建纹理的话
            if (realloc_texture(&is->vid_texture, sdl_pix_fmt, vp->frame->width, vp->frame->height, SDL_BLENDMODE_NONE, 0) < 0)
                return;
            //刷新纹理
            if (upload_texture(is->vid_texture, vp->frame, &is->img_convert_ctx) < 0)
                return;
            vp->uploaded = 1;
            vp->flip_v = vp->frame->linesize[0] < 0;
        }
    
        SDL_RenderCopyEx(renderer, is->vid_texture, NULL, &rect, 0, NULL, vp->flip_v ? SDL_FLIP_VERTICAL : 0);
        if (sp) {
    #if USE_ONEPASS_SUBTITLE_RENDER
            SDL_RenderCopy(renderer, is->sub_texture, NULL, &rect);
    #else
            int i;
            double xratio = (double)rect.w / (double)sp->width;
            double yratio = (double)rect.h / (double)sp->height;
            for (i = 0; i < sp->sub.num_rects; i++) {
                SDL_Rect *sub_rect = (SDL_Rect*)sp->sub.rects[i];
                SDL_Rect target = {.x = rect.x + sub_rect->x * xratio,
                                   .y = rect.y + sub_rect->y * yratio,
                                   .w = sub_rect->w * xratio,
                                   .h = sub_rect->h * yratio};
                SDL_RenderCopy(renderer, is->sub_texture, sub_rect, &target);
            }
    #endif
        }
    }
    

    通过calculate_display_rectton这里的就是计算需要显示的区域。
    然后如果还没创建纹理的话,在realloc_texture内调用SDL_CreateTexture来创建纹理。
    upload_texture内调用SDL_UpdateTexture来更新纹理。
    最后是进行SDL_RendererCopy.或者SDL_RendererCopyEx(SDL_RendererCopy的加强版,可以通知显示区域和翻转)。
    然后送显SDL_CreateTexture
    这里可以看到依旧是SDL视频送显的经典套路。

    SDL视频送显的经典套路.png
    处理按键的事件

    这部分暂时掠过...

    总结

    整体的流程.jpg image.png

    相关文章

      网友评论

        本文标题:ffplay.c 源码分析- 视频部分

        本文链接:https://www.haomeiwen.com/subject/yirufqtx.html