FFmpeg开发 Windows环境集成H.266/VVC编码器vvenc完整指南
概览流程
环境准备 → vvenc库安装 → FFmpeg编译 → H.266支持 → 验证测试 → 开发集成
环境准备
安装MSYS2环境
# 1. 下载并安装MSYS2
# 访问 https://www.msys2.org/ 下载安装包
# 安装完成后运行 MSYS2 UCRT64
# 2. 更新系统包
pacman -Syu
pacman -Su
# 3. 安装编译工具链
pacman -S mingw-w64-ucrt-x86_64-toolchain
pacman -S mingw-w64-ucrt-x86_64-pkg-config
pacman -S make
pacman -S mingw-w64-ucrt-x86_64-yasm
pacman -S mingw-w64-ucrt-x86_64-nasm
pacman -S git
pacman -S mingw-w64-ucrt-x86_64-cmake
pacman -S mingw-w64-ucrt-x86_64-python
创建工作目录
# 在MSYS2环境中创建目录
mkdir -p /c/ffmpeg_dev/{sources,build,output}
cd /c/ffmpeg_dev
vvenc库安装
方法一:手动编译vvenc库
# 下载vvenc源码
cd /c/ffmpeg_dev/sources
# 克隆vvenc源码(官方GitHub仓库)
git clone https://github.com/fraunhoferhhi/vvenc.git
cd vvenc
# 创建构建目录
mkdir -p build
cd build
# 配置CMake
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/c/ffmpeg_dev/build/vvenc \
-DBUILD_SHARED_LIBS=OFF \
-DBUILD_APPS=OFF \
-DCMAKE_TOOLCHAIN_FILE=/ucrt64/share/cmake/toolchain-x86_64-w64-mingw32.cmake
# 编译和安装
make -j$(nproc)
make install
# 检查生成的文件
ls -la /c/ffmpeg_dev/build/vvenc/
方法二:使用预编译的vvenc库
# 如果有预编译的vvenc库文件,直接复制到指定目录
mkdir -p /c/ffmpeg_dev/build/vvenc/{include,lib}
# 复制头文件和库文件(示例路径)
# cp /path/to/vvenc/include/*.h /c/ffmpeg_dev/build/vvenc/include/
# cp /path/to/vvenc/lib/*.lib /c/ffmpeg_dev/build/vvenc/lib/
# cp /path/to/vvenc/lib/*.dll /c/ffmpeg_dev/build/vvenc/lib/
FFmpeg源码编译
下载FFmpeg源码
# 进入源码目录
cd /c/ffmpeg_dev/sources
# 克隆FFmpeg源码
if [ ! -d "ffmpeg" ]; then
git clone https://git.ffmpeg.org/ffmpeg.git
fi
cd ffmpeg
修改FFmpeg源码支持vvenc
由于FFmpeg原生不支持vvenc,需要添加自定义支持:
# 创建vvenc编码器实现文件
cat > libavcodec/vvenc.c << 'EOF'
/*
* H.266/VVC encoder using Fraunhofer VVenC
* Copyright (c) 2023 Your Company
*/
#include "avcodec.h"
#include "internal.h"
#include "encode.h"
// 如果有vvenc头文件,包含它
// #include "vvenc/vvenc.h"
typedef struct VVEnCContext {
AVClass *class;
void *enc;
// 其他需要的变量
} VVEnCContext;
static av_cold int vvenc_encode_init(AVCodecContext *avctx)
{
VVEnCContext *s = avctx->priv_data;
av_log(avctx, AV_LOG_INFO, "H.266/VVC encoder initialization\n");
// 初始化vvenc编码器
// s->enc = vvenc_encoder_create();
// if (!s->enc) {
// av_log(avctx, AV_LOG_ERROR, "Failed to create VVenC encoder\n");
// return AVERROR_EXTERNAL;
// }
// 设置编码参数
avctx->pix_fmt = AV_PIX_FMT_YUV420P;
return 0;
}
static int vvenc_encode_frame(AVCodecContext *avctx, AVPacket *pkt,
const AVFrame *frame, int *got_packet)
{
VVEnCContext *s = avctx->priv_data;
int ret;
av_log(avctx, AV_LOG_DEBUG, "Encoding frame\n");
// 调用vvenc编码器编码
// ret = vvenc_encode_frame(s->enc, frame, pkt);
// if (ret < 0) {
// av_log(avctx, AV_LOG_ERROR, "VVenC encoding failed\n");
// return ret;
// }
*got_packet = 1;
return 0;
}
static av_cold int vvenc_encode_close(AVCodecContext *avctx)
{
VVEnCContext *s = avctx->priv_data;
av_log(avctx, AV_LOG_INFO, "H.266/VVC encoder cleanup\n");
// 清理vvenc编码器
// if (s->enc) {
// vvenc_encoder_destroy(s->enc);
// s->enc = NULL;
// }
return 0;
}
static const AVClass vvenc_encoder_class = {
.class_name = "vvenc encoder",
.item_name = av_default_item_name,
.version = LIBAVUTIL_VERSION_INT,
};
AVCodec ff_libvvenc_encoder = {
.name = "libvvenc",
.long_name = NULL_IF_CONFIG_SMALL("Fraunhofer VVenC H.266/VVC"),
.type = AVMEDIA_TYPE_VIDEO,
.id = AV_CODEC_ID_H266,
.priv_data_size = sizeof(VVEnCContext),
.init = vvenc_encode_init,
.encode2 = vvenc_encode_frame,
.close = vvenc_encode_close,
.capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_OTHER_THREADS,
.caps_internal = FF_CODEC_CAP_INIT_THREADSAFE | FF_CODEC_CAP_AUTO_THREADS,
.priv_class = &vvenc_encoder_class,
.wrapper_name = "libvvenc",
};
EOF
修改Makefile添加vvenc支持
# 修改libavcodec/Makefile
# 在适当位置添加:
# OBJS-$(CONFIG_LIBVVENC_ENCODER) += vvenc.o
配置FFmpeg(启用vvenc支持)
# 配置FFmpeg编译选项
./configure \
--prefix=/c/ffmpeg_dev/output \
--enable-shared \
--enable-static \
--enable-gpl \
--enable-nonfree \
--enable-version3 \
--enable-runtime-cpudetect \
--enable-postproc \
--enable-avfilter \
--enable-pthreads \
--enable-network \
--enable-libx264 \
--enable-libx265 \
--enable-encoder=libx264,libx265 \
--enable-decoder=h264,hevc \
--enable-parser=h264,hevc \
--enable-demuxer=h264,hevc,mp4,mov \
--enable-muxer=mp4,mov,h264,hevc \
--enable-protocol=file,http,https \
--arch=x86_64 \
--target-os=mingw32 \
--cross-prefix=x86_64-w64-mingw32- \
--extra-cflags="-I/c/ffmpeg_dev/build/vvenc/include" \
--extra-ldflags="-L/c/ffmpeg_dev/build/vvenc/lib" \
--extra-libs="-lvvenc -lpthread -lm"
编译和安装
# 清理之前的构建
make clean
# 并行编译
make -j$(nproc)
# 安装到指定目录
make install
使用vcpkg编译(推荐方法)
安装vcpkg
# 在Windows命令提示符中执行
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
bootstrap-vcpkg.bat
注意:vvenc在vcpkg中的情况
# 检查vcpkg中是否有vvenc支持
vcpkg search vvenc
# 如果没有官方支持,需要自定义端口
# 或者使用基础FFmpeg包
vcpkg install ffmpeg[core,x264,x265]:x64-windows
完整构建脚本
# 创建完整构建脚本
cat > build_ffmpeg_windows_vvenc.sh << 'EOF'
#!/bin/bash
# 设置环境变量
WORK_DIR=/c/ffmpeg_dev
SOURCES_DIR=$WORK_DIR/sources
BUILD_DIR=$WORK_DIR/build
OUTPUT_DIR=$WORK_DIR/output
# 创建目录结构
mkdir -p $SOURCES_DIR $BUILD_DIR $OUTPUT_DIR
echo "开始FFmpeg H.266/vvenc集成编译..."
# 安装依赖包
echo "安装依赖库..."
pacman -Syu --noconfirm
pacman -S --noconfirm \
mingw-w64-ucrt-x86_64-toolchain \
mingw-w64-ucrt-x86_64-pkg-config \
make \
mingw-w64-ucrt-x86_64-yasm \
mingw-w64-ucrt-x86_64-nasm \
git \
mingw-w64-ucrt-x86_64-cmake
# 编译vvenc库
echo "编译vvenc库..."
cd $SOURCES_DIR
if [ ! -d "vvenc" ]; then
git clone https://github.com/fraunhoferhhi/vvenc.git
fi
cd vvenc
mkdir -p build && cd build
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=$BUILD_DIR/vvenc \
-DBUILD_SHARED_LIBS=OFF \
-DBUILD_APPS=OFF \
-DCMAKE_TOOLCHAIN_FILE=/ucrt64/share/cmake/toolchain-x86_64-w64-mingw32.cmake
make -j$(nproc)
make install
# 下载FFmpeg源码
cd $SOURCES_DIR
if [ ! -d "ffmpeg" ]; then
git clone https://git.ffmpeg.org/ffmpeg.git
fi
cd ffmpeg
# 配置FFmpeg(基础配置,H.266需要自定义)
echo "配置FFmpeg..."
./configure \
--prefix=$OUTPUT_DIR \
--enable-shared \
--enable-static \
--enable-gpl \
--enable-nonfree \
--enable-version3 \
--enable-runtime-cpudetect \
--enable-postproc \
--enable-avfilter \
--enable-pthreads \
--enable-network \
--enable-libx264 \
--enable-libx265 \
--enable-encoder=libx264,libx265 \
--enable-decoder=h264,hevc \
--enable-parser=h264,hevc \
--enable-demuxer=h264,hevc,mp4,mov \
--enable-muxer=mp4,mov,h264,hevc \
--enable-protocol=file,http,https \
--arch=x86_64 \
--target-os=mingw32 \
--cross-prefix=x86_64-w64-mingw32- \
--extra-cflags="-I$BUILD_DIR/vvenc/include" \
--extra-ldflags="-L$BUILD_DIR/vvenc/lib" \
--extra-libs="-lvvenc -lpthread -lm"
# 编译和安装
echo "编译FFmpeg..."
make clean
make -j$(nproc)
make install
echo "FFmpeg编译完成!"
echo "输出目录: $OUTPUT_DIR"
echo "可执行文件: $OUTPUT_DIR/bin/ffmpeg.exe"
EOF
chmod +x build_ffmpeg_windows_vvenc.sh
验证安装
功能验证脚本
# 创建验证脚本
cat > verify_ffmpeg_vvenc.sh << 'EOF'
#!/bin/bash
OUTPUT_DIR=/c/ffmpeg_dev/output
FFMPEG_EXE=$OUTPUT_DIR/bin/ffmpeg.exe
echo "验证FFmpeg H.266/vvenc功能支持"
# 检查FFmpeg是否可执行
if [ ! -f "$FFMPEG_EXE" ]; then
echo "错误: FFmpeg未找到"
exit 1
fi
echo "FFmpeg可执行文件存在"
# 检查H.266相关支持
echo "检查H.266支持..."
$FFMPEG_EXE -encoders | grep -i "266\|vvc" > /dev/null && echo "✓ H.266编码器支持正常" || echo "✗ H.266编码器支持异常"
$FFMPEG_EXE -decoders | grep -i "266\|vvc" > /dev/null && echo "✓ H.266解码器支持正常" || echo "✗ H.266解码器支持异常"
# 显示版本信息
echo "FFmpeg版本信息:"
$FFMPEG_EXE -version | head -5
# 显示编译配置
echo "H.266编译配置:"
$FFMPEG_EXE -buildconf | grep -i vvenc
echo "验证完成"
EOF
chmod +x verify_ffmpeg_vvenc.sh
测试H.266功能
# H.266编码测试(需要H.266编码器支持)
test_h266_encoding() {
echo "测试H.266编码功能..."
# 如果有H.266编码器,测试编码
/c/ffmpeg_dev/output/bin/ffmpeg.exe \
-f lavfi -i testsrc=duration=10:size=1280x720:rate=30 \
-c:v libvvenc -b:v 2000k \
-f h266 test_output.h266
if [ -f "test_output.h266" ]; then
echo "H.266编码测试成功"
ls -lh test_output.h266
rm test_output.h266
else
echo "H.266编码测试失败(可能不支持)"
fi
}
# H.266兼容性测试
test_h266_compatibility() {
echo "测试H.266兼容性..."
# 创建测试文件
/c/ffmpeg_dev/output/bin/ffmpeg.exe \
-f lavfi -i testsrc=duration=5:size=1920x1080:rate=25 \
-c:v libx265 -preset ultrafast \
-f hevc test_hevc.h265
if [ -f "test_hevc.h265" ]; then
echo "H.265测试文件创建成功(用于对比)"
ls -lh test_hevc.h265
rm test_hevc.h265
else
echo "H.265测试文件创建失败"
fi
}
Visual Studio开发集成
项目配置
// 在Visual Studio项目属性中设置:
// 包含目录:
// C:\ffmpeg_dev\output\include
// C:\msys64\ucrt64\include
// 库目录:
// C:\ffmpeg_dev\output\lib
// C:\msys64\ucrt64\lib
// 附加依赖项:
// avformat.lib
// avcodec.lib
// avutil.lib
// swscale.lib
// swresample.lib
// avfilter.lib
// avdevice.lib
CMake配置
# CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(FFmpegH266Test)
set(CMAKE_CXX_STANDARD 17)
# FFmpeg配置
set(FFMPEG_ROOT "C:/ffmpeg_dev/output")
set(MSYS2_ROOT "C:/msys64/ucrt64")
# 包含目录
include_directories(
${FFMPEG_ROOT}/include
${MSYS2_ROOT}/include
)
# 库目录
link_directories(
${FFMPEG_ROOT}/lib
${MSYS2_ROOT}/lib
)
# 源文件
add_executable(${PROJECT_NAME} main.cpp)
# 链接库
target_link_libraries(${PROJECT_NAME}
avformat
avcodec
avutil
swscale
swresample
avfilter
avdevice
)
# 复制DLL文件到输出目录
file(GLOB FFMPEG_DLLS "${FFMPEG_ROOT}/bin/*.dll")
file(GLOB MSYS2_DLLS "${MSYS2_ROOT}/bin/*.dll")
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${FFMPEG_DLLS}
$<TARGET_FILE_DIR:${PROJECT_NAME}>
)
H.266处理示例代码
// h266_example.cpp - H.266处理示例
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
}
#include <iostream>
#include <string>
class H266Processor {
private:
AVFormatContext* format_ctx;
AVCodecContext* codec_ctx;
const AVCodec* codec;
public:
H266Processor() : format_ctx(nullptr), codec_ctx(nullptr), codec(nullptr) {}
~H266Processor() {
cleanup();
}
bool checkH266Support() {
std::cout << "检查H.266支持..." << std::endl;
// 检查H.266编码器
const AVCodec* h266_encoder = avcodec_find_encoder(AV_CODEC_ID_H266);
if (h266_encoder) {
std::cout << "H.266编码器: " << h266_encoder->name << std::endl;
return true;
} else {
std::cout << "H.266编码器不可用" << std::endl;
}
// 检查H.266解码器
const AVCodec* h266_decoder = avcodec_find_decoder(AV_CODEC_ID_H266);
if (h266_decoder) {
std::cout << "H.266解码器: " << h266_decoder->name << std::endl;
return true;
} else {
std::cout << "H.266解码器不可用" << std::endl;
}
// 检查VVC相关支持
const AVCodec* vvc_encoder = avcodec_find_encoder_by_name("libvvenc");
if (vvc_encoder) {
std::cout << "VVC编码器: " << vvc_encoder->name << std::endl;
return true;
}
// 检查HEVC作为备选
const AVCodec* hevc_encoder = avcodec_find_encoder(AV_CODEC_ID_HEVC);
const AVCodec* hevc_decoder = avcodec_find_decoder(AV_CODEC_ID_HEVC);
if (hevc_encoder) {
std::cout << "HEVC编码器: " << hevc_encoder->name << std::endl;
}
if (hevc_decoder) {
std::cout << "HEVC解码器: " << hevc_decoder->name << std::endl;
}
return (h266_encoder != nullptr || h266_decoder != nullptr ||
vvc_encoder != nullptr || hevc_encoder != nullptr);
}
bool encodeToH266(const std::string& output_file) {
std::cout << "编码到H.266格式: " << output_file << std::endl;
// 分配输出格式上下文
AVFormatContext* ofmt_ctx = nullptr;
avformat_alloc_output_context2(&ofmt_ctx, NULL, "h266", output_file.c_str());
if (!ofmt_ctx) {
std::cerr << "无法创建输出格式上下文" << std::endl;
return false;
}
// 查找H.266编码器
const AVCodec* encoder = avcodec_find_encoder(AV_CODEC_ID_H266);
if (!encoder) {
// 尝试查找VVC编码器
encoder = avcodec_find_encoder_by_name("libvvenc");
}
if (!encoder) {
std::cerr << "找不到H.266/VVC编码器" << std::endl;
avformat_free_context(ofmt_ctx);
return false;
}
// 创建视频流
AVStream* video_stream = avformat_new_stream(ofmt_ctx, encoder);
if (!video_stream) {
std::cerr << "无法创建视频流" << std::endl;
avformat_free_context(ofmt_ctx);
return false;
}
// 分配编码器上下文
AVCodecContext* enc_ctx = avcodec_alloc_context3(encoder);
if (!enc_ctx) {
std::cerr << "无法分配编码器上下文" << std::endl;
avformat_free_context(ofmt_ctx);
return false;
}
// 设置编码参数
enc_ctx->width = 1920;
enc_ctx->height = 1080;
enc_ctx->time_base = (AVRational){1, 25};
enc_ctx->framerate = (AVRational){25, 1};
enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
enc_ctx->bit_rate = 5000000; // 5Mbps
// 打开编码器
int ret = avcodec_open2(enc_ctx, encoder, NULL);
if (ret < 0) {
std::cerr << "无法打开编码器" << std::endl;
avcodec_free_context(&enc_ctx);
avformat_free_context(ofmt_ctx);
return false;
}
// 复制参数到流
ret = avcodec_parameters_from_context(video_stream->codecpar, enc_ctx);
if (ret < 0) {
std::cerr << "无法复制编解码器参数" << std::endl;
avcodec_close(enc_ctx);
avcodec_free_context(&enc_ctx);
avformat_free_context(ofmt_ctx);
return false;
}
// 写入文件头
if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open(&ofmt_ctx->pb, output_file.c_str(), AVIO_FLAG_WRITE);
if (ret < 0) {
std::cerr << "无法打开输出文件" << std::endl;
avcodec_close(enc_ctx);
avcodec_free_context(&enc_ctx);
avformat_free_context(ofmt_ctx);
return false;
}
}
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
std::cerr << "无法写入文件头" << std::endl;
avcodec_close(enc_ctx);
avcodec_free_context(&enc_ctx);
avformat_free_context(ofmt_ctx);
return false;
}
std::cout << "H.266编码器初始化成功" << std::endl;
// 清理资源
avcodec_close(enc_ctx);
avcodec_free_context(&enc_ctx);
avformat_free_context(ofmt_ctx);
return true;
}
void cleanup() {
if (codec_ctx) {
avcodec_free_context(&codec_ctx);
codec_ctx = nullptr;
}
if (format_ctx) {
avformat_close_input(&format_ctx);
format_ctx = nullptr;
}
}
};
int main() {
std::cout << "FFmpeg H.266处理示例" << std::endl;
H266Processor processor;
// 检查H.266支持
if (!processor.checkH266Support()) {
std::cout << "H.266支持检查失败" << std::endl;
return -1;
}
// 测试编码功能
if (processor.encodeToH266("test_output.h266")) {
std::cout << "H.266编码测试成功" << std::endl;
} else {
std::cout << "H.266编码测试失败" << std::endl;
}
std::cout << "H.266支持测试完成" << std::endl;
return 0;
}
H.266/VVC使用示例
命令行示例
# 注意:以下命令需要实际的H.266支持
# 如果FFmpeg支持H.266,可以使用:
# H.266编码
ffmpeg.exe -i input.mp4 -c:v libvvenc -b:v 5000k output.h266
# H.266解码
ffmpeg.exe -i input.h266 -c:v copy output.mp4
# H.266流媒体传输
ffmpeg.exe -f lavfi -i testsrc=duration=30:size=1920x1080:rate=30 \
-c:v libvvenc -preset fast -b:v 8000k \
-f h266 "udp://127.0.0.1:1234"
H.266参数说明
# H.266编码参数(假设支持):
# -preset 范围:ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow
# -crf 范围:0-51 (0=无损, 51=最差质量)
# -b:v 比特率控制
# -maxrate 最大比特率
# -bufsize 缓冲区大小
环境变量配置
# 在Windows系统环境变量中添加:
# 系统PATH变量添加:
C:\ffmpeg_dev\output\bin
C:\msys64\ucrt64\bin
# 创建批处理文件设置环境
@echo off
set PATH=C:\ffmpeg_dev\output\bin;C:\msys64\ucrt64\bin;%PATH%
set FFmpeg_HOME=C:\ffmpeg_dev\output
echo 环境变量已设置
常见问题解决
1. vvenc库编译问题
# 检查vvenc库是否存在
ls /c/ffmpeg_dev/build/vvenc/lib/
ls /c/ffmpeg_dev/build/vvenc/include/
# 检查编译错误
cd /c/ffmpeg_dev/sources/vvenc/build
make VERBOSE=1
2. FFmpeg配置问题
# 检查配置选项
./configure --help | grep -i vvenc
# 查看详细的配置日志
./configure [选项] 2>&1 | tee config.log
3. 运行时DLL错误
# 复制必要的DLL文件
copy C:\ffmpeg_dev\output\bin\*.dll .\
copy C:\msys64\ucrt64\bin\*.dll .\
4. 编码器不可用
# 检查FFmpeg编译配置
/c/ffmpeg_dev/output/bin/ffmpeg.exe -buildconf | grep -i vvenc
/c/ffmpeg_dev/output/bin/ffmpeg.exe -encoders | grep -i vvenc
# 如果显示disable,则需要重新编译
5. 版权和许可问题
# 注意:H.266/VVC可能涉及专利问题
# 使用前请确认相关许可和授权
# Fraunhofer HHI的vvenc可能有特定的使用条款
性能优化建议
编译优化选项
# 性能优化编译配置
./configure \
--prefix=/c/ffmpeg_dev/output \
--enable-shared \
--enable-static \
--enable-optimizations \
--disable-debug \
--enable-stripping \
--enable-small \
--extra-cflags="-O3 -ffast-math -march=native" \
--extra-ldflags="-s" \
# H.266相关配置
H.266编码参数优化
# FFmpeg H.266优化参数(假设支持)
ffmpeg.exe -i input.mp4 \
-c:v libvvenc \
-preset medium \
-crf 28 \
-b:v 5000k \
-maxrate 6000k \
-bufsize 10000k \
output.h266
H.266/VVC特殊说明
由于H.266/VVC是较新的视频编码标准,需要注意:
- 兼容性:H.266支持在FFmpeg中可能需要自定义开发
- 性能:H.266编码通常比H.264/HEVC慢很多
- 专利:H.266可能涉及专利费用
- 硬件支持:目前硬件解码支持有限
替代方案
如果无法获得H.266支持,可以考虑:
# 使用HEVC作为替代
/c/ffmpeg_dev/output/bin/ffmpeg.exe -i input.mp4 -c:v libx265 output.hevc
# 使用AV1作为替代
/c/ffmpeg_dev/output/bin/ffmpeg.exe -i input.mp4 -c:v libaom-av1 output.av1
完成这些配置后,你就可以在Windows环境下使用带有H.266/VVC支持的FFmpeg进行开发了。需要注意的是,H.266的具体实现可能需要官方授权的编码器库。