1. 列举

业界通用的编解码器有很多,有的存粹用软件比如x264,有的依赖硬件,比如Nvidia的NVENC开发包使用自家显卡GPU加速,Intel的Quick Sync开发包使用Intel CPU上集成的GPU加速。

ffmpeg通过封装上述第三方编解码器,和少数自身实现的编解码器,抽象定义出一个AVCodec结构体,并支持通过命令ffmpeg -decoders来列举支持的所有解码器,由于内容太多,下面使用ffmpeg -decoders | grep h264来显示h264解码器:

1
2
3
VFS..D h264                 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
V....D h264_qsv             H264 video (Intel Quick Sync Video acceleration) (codec h264)
V..... h264_cuvid           Nvidia CUVID H264 decoder (codec h264)

第一个的解码实现是ffmpeg官方开发。通过名字也可以看出来,第二个基于Intel QSV,第三个基于Nvidia显卡。每个解码器开头部分的含义如下,当命令行中通过管道使用grep后,这些输出被过滤了:

1
2
3
4
5
6
7
8
9
Decoders
V..... = Video
 A..... = Audio
 S..... = Subtitle
 .F.... = Frame-level multithreading
 ..S... = Slice-level multithreading
 ...X.. = Codec is experimental
 ....B. = Supports draw_horiz_band
 .....D = Supports direct rendering method 1

同理,使用ffmpeg -encoders | grep h264显示h264编码器:

1
2
3
4
5
6
7
V..... libx264              libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (codec h264)
V....D h264_amf             AMD AMF H.264 Encoder (codec h264)
V....D h264_mf              H264 via MediaFoundation (codec h264)
V....D h264_nvenc           NVIDIA NVENC H.264 encoder (codec h264)
V..... h264_qsv             H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (Intel Quick Sync Video acceleration) (codec h264)
V..... nvenc                NVIDIA NVENC H.264 encoder (codec h264)
V..... nvenc_h264           NVIDIA NVENC H.264 encoder (codec h264)

第一个基于x264,带qsv字样的基于Intel QSV,带nvenc字样的基于Nvidia显卡。开头部分的含义如下:

1
2
3
4
5
6
7
8
9
Encoders
V..... = Video
 A..... = Audio
 S..... = Subtitle
 .F.... = Frame-level multithreading
 ..S... = Slice-level multithreading
 ...X.. = Codec is experimental
 ....B. = Supports draw_horiz_band
 .....D = Supports direct rendering method 1

2. AVCodec

每个编解码器通过定义自己的AVCodec结构体变量,提供名称,类型、CodecId、若干关键回调函数等描述自身数据,使得外部访问者只要找到对应编解码器的AVCodec,即可屏蔽不同实现的差异:

Type

  • 音频AVMEDIA_TYPE_AUDIO
  • 视频AVMEDIA_TYPE_VIDEO
  • 字幕AVMEDIA_TYPE_AUDIO

CodecId

  • AV_CODEC_ID_H264
  • AV_CODEC_ID_AC3

回调函数

  • init
  • close
  • flush
  • receive_packet
  • decode
  • receive_frame

区分那么多AVCodec的标准很简单,那些提供了decodereceive_frame函数指针的AVCodec代表解码器,否则就是编码器。下面是FFMPEG内置的h264解码器(采用CPU解码)定义,源自libavcodec/h264dec.c,只保留了关键数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
AVCodec ff_h264_decoder = {
    .name                  = "h264",
    .long_name             = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),
    .type                  = AVMEDIA_TYPE_VIDEO,
    .id                    = AV_CODEC_ID_H264,
    .priv_data_size        = sizeof(H264Context),
    .init                  = h264_decode_init,
    .close                 = h264_decode_end,
    .decode                = h264_decode_frame,
    .capabilities          = /*AV_CODEC_CAP_DRAW_HORIZ_BAND |*/ AV_CODEC_CAP_DR1 |
                             AV_CODEC_CAP_DELAY | AV_CODEC_CAP_SLICE_THREADS |
                             AV_CODEC_CAP_FRAME_THREADS,
                               HWACCEL_DXVA2(h264),
    .caps_internal         = FF_CODEC_CAP_INIT_THREADSAFE | FF_CODEC_CAP_EXPORTS_CROPPING |
                             FF_CODEC_CAP_ALLOCATE_PROGRESS | FF_CODEC_CAP_INIT_CLEANUP,
    .flush                 = h264_decode_flush,
    .update_thread_context = ONLY_IF_THREADS_ENABLED(ff_h264_update_thread_context),
    .profiles              = NULL_IF_CONFIG_SMALL(ff_h264_profiles),
    .priv_class            = &h264_class,
};

3. 硬件加速之Nvidia NvENC

ffmpeg支持Nvidia NvENC硬件编解码h264、h265、av1、vp9等多种视频格式,其在libavcodec/cuviddec.c中为每种视频格式分别定义不同的解码器对应AVCodec变量:

  • ff_av1_cuvid_decoder
  • ff_h264_cuvid_decoder
  • ff_hevc_cuvid_decoder
  • ff_vp9_cuvid_decoder

每个变量中的name字符串对应ffmpeg -decoders | grep cuvid列举出来的解码器名称:

  • av1_cuvid
  • h264_cuvid
  • hevc_cuvid
  • vp9_cuvid

4. 编解码器数组codec_list

在libavcodec/allcodecs.c中通过extern语法逐个声明散落在不同源代码中编解码器的变量,比如:

1
2
3
4
extern AVCodec ff_av1_cuvid_decoder;
extern AVCodec ff_h264_cuvid_decoder;
extern AVCodec ff_hevc_cuvid_decoder;
extern AVCodec ff_vp9_cuvid_decoder;

随后执行configure时提取名称带encoder的编码器变量名填充到codec_list.ccodec_list数组,同理继续提取解码器并填充,此后codec_list数组包含了ffmpeg支持的所有编解码器,并由libavcodec/allcodecs.c提供API来访问她。

下面是脚本中对应代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
find_things_extern(){
    thing=$1
    pattern=$2
    file=$source_path/$3
    out=${4:-$thing}
    sed -n "s/^[^#]*extern.*$pattern *ff_\([^ ]*\)_$thing;/\1_$out/p" "$file"
}

find_filters_extern(){
    file=$source_path/$1
    sed -n 's/^extern AVFilter ff_[avfsinkrc]\{2,5\}_\([[:alnum:]_]\{1,\}\);/\1_filter/p' $file
}

ENCODER_LIST=$(find_things_extern encoder AVCodec libavcodec/allcodecs.c)
DECODER_LIST=$(find_things_extern decoder AVCodec libavcodec/allcodecs.c)
CODEC_LIST="
    $ENCODER_LIST
    $DECODER_LIST
"