2. 硬解码器选择与初始化
任务目标
编写一个FFmpeg命令行工具,自动探测当前系统可用的硬件解码器(如h264_cuvid、h264_qsv、h264_videotoolbox等),并优先选择第一个可用的硬解码器完成视频解码初始化。若所有硬解码器均不可用,则回退到软件解码器,并输出明确的初始化状态信息。
环境依赖
- FFmpeg 4.x 或 5.x(需启用对应硬件解码模块,如 --enable-cuvid / --enable-libmfx / --enable-videotoolbox)
- 支持 C99 的编译器(gcc/clang)
- Linux/macOS/Windows(需对应平台硬件驱动)
核心实现
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <stdio.h>
#include <string.h>
static const char *hw_decoders[] = {
"h264_cuvid", "h264_qsv", "h264_videotoolbox",
"hevc_cuvid", "hevc_qsv", "hevc_videotoolbox",
NULL
};
static int try_open_decoder(AVCodecContext *ctx, const AVCodec *decoder,
AVCodecParameters *params, char *errbuf, int errbuf_size) {
ctx->codec_id = decoder->id;
ctx->codec_type = AVMEDIA_TYPE_VIDEO;
if (avcodec_parameters_to_context(ctx, params) < 0) {
av_strerror(AVERROR(EINVAL), errbuf, errbuf_size);
return -1;
}
ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
if (avcodec_open2(ctx, decoder, NULL) < 0) {
av_strerror(AVERROR(EINVAL), errbuf, errbuf_size);
return -1;
}
return 0;
}
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <input_file>\n", argv[0]);
return 1;
}
avformat_network_init();
AVFormatContext *fmt_ctx = NULL;
if (avformat_open_input(&fmt_ctx, argv[1], NULL, NULL) < 0) {
fprintf(stderr, "Cannot open input file\n");
return 1;
}
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
fprintf(stderr, "Cannot find stream info\n");
avformat_close_input(&fmt_ctx);
return 1;
}
int video_stream = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (video_stream < 0) {
fprintf(stderr, "No video stream found\n");
avformat_close_input(&fmt_ctx);
return 1;
}
AVCodecParameters *params = fmt_ctx->streams[video_stream]->codecpar;
const AVCodec *decoder = NULL;
AVCodecContext *dec_ctx = avcodec_alloc_context3(NULL);
char errbuf[AV_ERROR_MAX_STRING_SIZE] = {0};
const char *selected_name = NULL;
// 1. 尝试硬件解码器
for (int i = 0; hw_decoders[i] != NULL; i++) {
decoder = avcodec_find_decoder_by_name(hw_decoders[i]);
if (!decoder) continue;
if (decoder->id != params->codec_id) continue; // 仅匹配当前编码格式
if (try_open_decoder(dec_ctx, decoder, params, errbuf, sizeof(errbuf)) == 0) {
selected_name = hw_decoders[i];
break;
}
avcodec_free_context(&dec_ctx);
dec_ctx = avcodec_alloc_context3(NULL);
}
// 2. 回退到软件解码
if (!selected_name) {
decoder = avcodec_find_decoder(params->codec_id);
if (!decoder) {
fprintf(stderr, "No decoder found for codec_id %d\n", params->codec_id);
avcodec_free_context(&dec_ctx);
avformat_close_input(&fmt_ctx);
return 1;
}
if (try_open_decoder(dec_ctx, decoder, params, errbuf, sizeof(errbuf)) < 0) {
fprintf(stderr, "Software decoder init failed: %s\n", errbuf);
avcodec_free_context(&dec_ctx);
avformat_close_input(&fmt_ctx);
return 1;
}
selected_name = decoder->name;
}
printf("Selected decoder: %s\n", selected_name);
printf("Codec: %s, Width: %d, Height: %d, PixFmt: %d\n",
avcodec_get_name(dec_ctx->codec_id),
dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt);
// 3. 验证:解码第一帧
AVPacket *pkt = av_packet_alloc();
AVFrame *frame = av_frame_alloc();
int got_frame = 0;
while (av_read_frame(fmt_ctx, pkt) >= 0) {
if (pkt->stream_index == video_stream) {
if (avcodec_send_packet(dec_ctx, pkt) == 0) {
if (avcodec_receive_frame(dec_ctx, frame) == 0) {
got_frame = 1;
printf("First frame decoded: %dx%d, format=%d\n",
frame->width, frame->height, frame->format);
break;
}
}
}
av_packet_unref(pkt);
}
if (!got_frame) {
fprintf(stderr, "Failed to decode any frame\n");
}
av_frame_free(&frame);
av_packet_free(&pkt);
avcodec_free_context(&dec_ctx);
avformat_close_input(&fmt_ctx);
avformat_network_deinit();
return got_frame ? 0 : 1;
}
编译与运行
# 编译(需pkg-config配置FFmpeg路径)
gcc -o hw_select hw_select.c $(pkg-config --cflags --libs libavformat libavcodec libavutil)
# 运行(以H.264视频为例)
./hw_select input.mp4
验证方法
- 成功标准:程序输出
Selected decoder: h264_cuvid(或对应平台硬解码器名称),并成功打印第一帧的宽高与像素格式,退出码为0。 - 回退验证:在无硬件加速环境(如纯软件虚拟机)运行,应输出
Selected decoder: h264,表明自动回退到软件解码。 - 错误路径:输入不存在的文件或非视频文件时,程序应打印明确错误信息并返回非0退出码。
关键说明
- 硬解码器列表按优先级排列,当前仅匹配H.264/HEVC编码;如需支持其他编码格式,在
hw_decoders数组中追加对应名称即可。 - 初始化失败时通过
avcodec_open2返回错误码,但FFmpeg对硬件解码器不可用通常返回AVERROR(EINVAL)或AVERROR(ENOTSUP),此处统一按失败处理并尝试下一个候选。 - 实际工程中应根据
dec_ctx->hw_device_ctx配置硬件设备上下文(如CUDA/QSV设备),本例聚焦解码器选择与初始化流程,故省略设备创建环节。