搜索
 找回密码
 立即注册

ffmpeg 硬件加速播放框架分析

音视频开发老舅 2022-6-27 19:32:31 75

概述

ffmpeg对外提供了API接口,用户可以通过调用这些API来实现ffmpeg的各种功能。要设计video 基于ffmpeg的硬件解码框架,需要先深入了解ffmpeg的硬件解码框架。为此,本文先分析ffplay的代码框架,从ffplay入手,深入ffmpeg源码,分析ffplay如何调用NVIDIA硬件解码框架,在此基础上设计video 的硬件解码框架。

本文只分析ffplay调用NVIDIA硬件解码框架,ffmpeg的硬件加速框架除了播放外,还在转码等方面使用,本文暂不分析ffmpeg转码部分的硬件加速框架。

1. 多媒体播放一般流程

image.png

2. ffplay框架分析

ffplay架构图

1一个主线程,主循环负责视频播放和SDL消息处理

2主线程中起一个解复用子线程,通过av_read_frame()接口将音视频分离开来,读取的视频包和音频包分别存入一个缓存队列video packets queue和audio packets queue。

3在解复用子线程中另起两个线程,分别为视频解码线程和音频解码线程,video queue 和audio queue中取出音视频包进行解码,将解码后的frame存入缓存队列 video frames queue和audio frames queue。

4 主线程通过获取video frames queue中的图像信息播放视频,ffplay默认以音频为主时钟,视频时钟向音频时钟同步。

ffplay框架如下所示。

image.png

ffplay主要结构体

1 struct VideoState

struct VideoState 结构是贯穿ffplay始终的最重要的一个结构体,它存储了程序所需的输入流信息,输出帧数据,时钟信息,解码前数据包缓存队列,解码后帧缓存队列,SDL控制信息,SDL窗口信息,视频参数信息,如视频高,宽等,音频采样信息,码率信息等,以及滤镜信息,视频图像缩放信息等等。

typedef struct VideoState {
    SDL_Thread *read_tid;           // demux解复用线程
    AVInputFormat *iformat;
    int abort_request;
    int force_refresh;
    int paused;
    int last_paused;
    int queue_attachments_req;
    int seek_req;                   // 标识一次SEEK请求
    int seek_flags;                 // SEEK标志,诸如AVSEEK_FLAG_BYTE等
    int64_t seek_pos;               // SEEK的目标位置(当前位置+增量)
    int64_t seek_rel;               // 本次SEEK的位置增量
    int read_pause_return;
    AVFormatContext *ic;
    int realtime;
    Clock audclk;                   // 音频时钟
    Clock vidclk;                   // 视频时钟
    Clock extclk;                   // 外部时钟

    FrameQueue pictq;               // 视频frame队列
    FrameQueue subpq;               // 字幕frame队列
    FrameQueue sampq;               // 音频frame队列

    Decoder auddec;                 // 音频解码器
    Decoder viddec;                 // 视频解码器
    Decoder subdec;                 // 字幕解码器

    int audio_stream;               // 音频流索引

    int av_sync_type;

    double audio_clock;             // 每个音频帧更新一下此值,以pts形式表示
    int audio_clock_serial;         // 播放序列,seek可改变此值
    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;             // 音频packet队列
    int audio_hw_buf_size;          // SDL音频缓冲区大小(单位字节)
    uint8_t *audio_buf;             // 指向待播放的一帧音频数据,指向的数据区将被拷入SDL音频缓冲区。若经过重采样则指向audio_buf1,否则指向frame中的音频
    uint8_t *audio_buf1;            // 音频重采样的输出缓冲区
    unsigned int audio_buf_size; /* in bytes */ // 待播放的一帧音频数据(audio_buf指向)的大小
    unsigned int audio_buf1_size;   // 申请到的音频缓冲区audio_buf1的实际尺寸
    int audio_buf_index; /* in bytes */ // 当前音频帧中已拷入SDL音频缓冲区的位置索引(指向第一个待拷贝字节)
    int audio_write_buf_size;       // 当前音频帧中尚未拷入SDL音频缓冲区的数据量,audio_buf_size = audio_buf_index + audio_write_buf_size
    int audio_volume;               // 音量
    int muted;                      // 静音状态
    struct AudioParams audio_src;   // 音频frame的参数
#if CONFIG_AVFILTER
    struct AudioParams audio_filter_src;
#endif
    struct AudioParams audio_tgt;   // SDL支持的音频参数,重采样转换:audio_src->audio_tgt
    struct SwrContext *swr_ctx;     // 音频重采样context
    int frame_drops_early;          // 丢弃视频packet计数
    int frame_drops_late;           // 丢弃视频frame计数

    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;
    SDL_Texture *sub_texture;
    SDL_Texture *vid_texture;

    int subtitle_stream;                // 字幕流索引
    AVStream *subtitle_st;              // 字幕流
    PacketQueue subtitleq;              // 字幕packet队列

    double frame_timer;                 // 记录最后一帧播放的时刻
    double frame_last_returned_time;
    double frame_last_filter_delay;
    int video_stream;
    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

    int last_video_stream, last_audio_stream, last_subtitle_stream;

    SDL_cond *continue_read_thread;
} VideoState;
  1. ffmpeg解码框架(API调用流程)

ffmpeg对外提供一套音视频处理的API。其中解码部分的接口主要的几个在下面列出。也是ffplay解码使用到的几个核心函数(不考虑线程,队列,SDL等处理),ffplay作为ffmpeg的一个播放器demo,为ffmpeg API的使用提供了很好的示例。

image.png

  1. ffmpeg h264 NVIDIA硬件解码播放框架 在avcodec_send_packet 接口之前,软件解码和硬件解码的调用流程基本上是一样的。只有在查找解码器部分,软件解码调用的是 avcodec_find_decoder(codecid) ,而硬件解码调用的是 avcodec_find_decoder_by_name(“codec_name”)。 在解码函数中,即avcodec_receive_frame()中,根据是否启动了硬件加速选择软件解码还是硬件解码。 ffmpeg h264 NVIDIA硬件解码框架。ffmpeg为NVIDIA的h264硬件解码专门定义了一个文件cuviddec.c。在cuviddec.c中定义了NVIDIA硬件解码器,如下图倒数第二个框内所示。
  2. image.png

简单提一下在解码函数里是怎么调到NVIDIA硬件解码器的。当调用 avcodec_find_decoder_by_name()获取到解码器后,会调用avcodec_open2()函数打开解码器,获取到的解码器就是在这个函数中赋值给avctx->codec的,

avctx->codec = codec;

这样,当解码器的 “receive_frame”字段不为空时,调用硬件解码:

if (avctx->codec->receive_frame) 
        ret = avctx->codec->receive_frame(avctx, frame);  
    else
        ret = decode_simple_receive_frame(avctx, frame);

在cuviddec.c 中

#define DEFINE_CUVID_CODEC(x, X, bsf_name) \
    static const AVClass x##_cuvid_class = { \
        .class_name = #x "_cuvid", \
        .item_name = av_default_item_name, \
        .option = options, \
        .version = LIBAVUTIL_VERSION_INT, \
    }; \
    const AVCodec ff_##x##_cuvid_decoder = { \
        .name           = #x "_cuvid", \
        .long_name      = NULL_IF_CONFIG_SMALL("Nvidia CUVID " #X " decoder"), \
        .type           = AVMEDIA_TYPE_VIDEO, \
        .id             = AV_CODEC_ID_##X, \
        .priv_data_size = sizeof(CuvidContext), \
        .priv_class     = &x##_cuvid_class, \
        .init           = cuvid_decode_init, \
        .close          = cuvid_decode_end, \
        .receive_frame  = cuvid_output_frame, \
        .flush          = cuvid_flush, \
        .bsfs           = bsf_name, \
        .capabilities   = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_AVOID_PROBING | AV_CODEC_CAP_HARDWARE, \
        .caps_internal  = FF_CODEC_CAP_SETS_FRAME_PROPS, \
        .pix_fmts       = (const enum AVPixelFormat[]){ AV_PIX_FMT_CUDA, \
                                                        AV_PIX_FMT_NV12, \
                                                        AV_PIX_FMT_P010, \
                                                        AV_PIX_FMT_P016, \
                                                        AV_PIX_FMT_NONE }, \
        .hw_configs     = cuvid_hw_configs, \
        .wrapper_name   = "cuvid", \
    };

#if CONFIG_HEVC_CUVID_DECODER
DEFINE_CUVID_CODEC(hevc, HEVC, "hevc_mp4toannexb")
#endif
#if CONFIG_H264_CUVID_DECODER
DEFINE_CUVID_CODEC(h264, H264, "h264_mp4toannexb")
#endif

在 cuvid_decode_init, cuvid_decode_end, cuvid_output_frame, cuvid_flush, 4个回调函数中,调用了NVIDIA提供的库函数(API)。

原文链接:https://blog.csdn.net/z373248439/article/details/121675419

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

使用道具 举报

随机推荐

0 回复

游客
返回顶部