FFmpeg开发 给Visual Studio的C++工程集成FFmpeg完整指南
概览流程
环境准备 → FFmpeg获取 → 项目配置 → 代码集成 → 编译调试 → 优化部署
环境准备
Visual Studio版本要求
// 支持的Visual Studio版本:
// Visual Studio 2019 (16.0) 或更高版本
// Visual Studio 2022 (17.0) 推荐
// 需要安装C++开发工具
系统环境要求
# Windows 10 或 Windows 11
# 至少4GB内存(推荐8GB以上)
# 至少10GB磁盘空间用于FFmpeg和开发
FFmpeg获取方式
方法一:使用vcpkg(推荐)
# 1. 下载并安装vcpkg
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
bootstrap-vcpkg.bat
# 2. 安装FFmpeg
vcpkg install ffmpeg[core]:x64-windows
# 或安装更多功能
vcpkg install ffmpeg[core,x264,mp3lame,opus,vorbis]:x64-windows
# 3. 集成到Visual Studio
vcpkg integrate install
方法二:下载预编译包
# 1. 访问FFmpeg官网下载页面
# https://ffmpeg.org/download.html
# 2. 下载Windows预编译包
# 例如:https://github.com/BtbN/FFmpeg-Builds/releases
# 3. 解压到指定目录
# 例如:C:\ffmpeg
方法三:自行编译FFmpeg
# 参考之前章节的编译指南
# 编译完成后设置相应路径
Visual Studio项目配置
1. 创建新项目
// File -> New -> Project
// 选择 "Windows Console Application" 或 "Windows Desktop Application"
// 项目名称:FFmpegTest
2. 配置项目属性
// 右键项目 -> Properties
// 配置管理器设置
Configuration: Release
Platform: x64
// C/C++ -> General -> Additional Include Directories
// 如果使用vcpkg:
C:\vcpkg\installed\x64-windows\include
// 如果使用预编译包:
C:\ffmpeg\include
// C/C++ -> Preprocessor -> Preprocessor Definitions
_CRT_SECURE_NO_WARNINGS
_CRT_NONSTDC_NO_DEPRECATE
// Linker -> General -> Additional Library Directories
// 如果使用vcpkg:
C:\vcpkg\installed\x64-windows\lib
// 如果使用预编译包:
C:\ffmpeg\lib
// Linker -> Input -> Additional Dependencies
avformat.lib
avcodec.lib
avutil.lib
swscale.lib
swresample.lib
avdevice.lib
avfilter.lib
postproc.lib
3. 项目属性配置文件
<!-- 在.vcxproj文件中添加配置 -->
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<IncludePath>C:\vcpkg\installed\x64-windows\include;$(IncludePath)</IncludePath>
<LibraryPath>C:\vcpkg\installed\x64-windows\lib;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<AdditionalDependencies>
avformat.lib;avcodec.lib;avutil.lib;swscale.lib;
swresample.lib;avdevice.lib;avfilter.lib;postproc.lib;
%(AdditionalDependencies)
</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
代码集成示例
1. 基础头文件包含
// main.cpp
#ifdef __cplusplus
extern "C" {
#endif
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#ifdef __cplusplus
}
#endif
#include <iostream>
#include <string>
2. 简单视频信息读取示例
// video_info.cpp
#include "video_info.h"
class VideoInfoReader {
public:
VideoInfoReader() {
// 初始化网络(如果需要处理网络流)
avformat_network_init();
}
~VideoInfoReader() {
avformat_network_deinit();
}
bool getVideoInfo(const std::string& filename) {
AVFormatContext* format_ctx = nullptr;
int ret;
// 打开视频文件
ret = avformat_open_input(&format_ctx, filename.c_str(), nullptr, nullptr);
if (ret < 0) {
std::cerr << "无法打开文件: " << filename << std::endl;
return false;
}
// 获取流信息
ret = avformat_find_stream_info(format_ctx, nullptr);
if (ret < 0) {
std::cerr << "无法获取流信息" << std::endl;
avformat_close_input(&format_ctx);
return false;
}
// 打印基本信息
printVideoInfo(format_ctx);
// 清理资源
avformat_close_input(&format_ctx);
return true;
}
private:
void printVideoInfo(AVFormatContext* format_ctx) {
std::cout << "文件信息:" << std::endl;
std::cout << " 格式: " << format_ctx->iformat->name << std::endl;
std::cout << " 时长: " << format_ctx->duration / AV_TIME_BASE << " 秒" << std::endl;
std::cout << " 流数: " << format_ctx->nb_streams << std::endl;
// 查找视频和音频流
for (unsigned int i = 0; i < format_ctx->nb_streams; i++) {
AVStream* stream = format_ctx->streams[i];
AVCodecParameters* codecpar = stream->codecpar;
if (codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
std::cout << " 视频流 " << i << ":" << std::endl;
std::cout << " 编解码器: " << avcodec_get_name(codecpar->codec_id) << std::endl;
std::cout << " 分辨率: " << codecpar->width << "x" << codecpar->height << std::endl;
std::cout << " 帧率: " << av_q2d(stream->avg_frame_rate) << " fps" << std::endl;
std::cout << " 比特率: " << codecpar->bit_rate / 1000 << " kbps" << std::endl;
}
else if (codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
std::cout << " 音频流 " << i << ":" << std::endl;
std::cout << " 编解码器: " << avcodec_get_name(codecpar->codec_id) << std::endl;
std::cout << " 采样率: " << codecpar->sample_rate << " Hz" << std::endl;
std::cout << " 声道数: " << codecpar->channels << std::endl;
std::cout << " 比特率: " << codecpar->bit_rate / 1000 << " kbps" << std::endl;
}
}
}
};
// 主函数
int main() {
VideoInfoReader reader;
// 测试文件路径
std::string test_file = "test.mp4";
if (reader.getVideoInfo(test_file)) {
std::cout << "视频信息读取成功!" << std::endl;
} else {
std::cout << "视频信息读取失败!" << std::endl;
}
return 0;
}
3. 视频转码示例
// video_transcoder.cpp
#include "video_transcoder.h"
class VideoTranscoder {
private:
AVFormatContext* input_format_ctx;
AVFormatContext* output_format_ctx;
AVCodecContext* input_codec_ctx;
AVCodecContext* output_codec_ctx;
int video_stream_index;
public:
VideoTranscoder() : input_format_ctx(nullptr), output_format_ctx(nullptr),
input_codec_ctx(nullptr), output_codec_ctx(nullptr),
video_stream_index(-1) {}
~VideoTranscoder() {
cleanup();
}
bool transcodeVideo(const std::string& input_file, const std::string& output_file) {
// 打开输入文件
if (!openInputFile(input_file)) {
return false;
}
// 创建输出文件
if (!openOutputFile(output_file)) {
return false;
}
// 写入文件头
if (avformat_write_header(output_format_ctx, nullptr) < 0) {
std::cerr << "无法写入文件头" << std::endl;
return false;
}
// 转码循环
if (!transcodeFrames()) {
return false;
}
// 写入文件尾
av_write_trailer(output_format_ctx);
std::cout << "转码完成!" << std::endl;
return true;
}
private:
bool openInputFile(const std::string& filename) {
int ret;
// 打开输入文件
ret = avformat_open_input(&input_format_ctx, filename.c_str(), nullptr, nullptr);
if (ret < 0) {
std::cerr << "无法打开输入文件: " << filename << std::endl;
return false;
}
// 获取流信息
ret = avformat_find_stream_info(input_format_ctx, nullptr);
if (ret < 0) {
std::cerr << "无法获取输入流信息" << std::endl;
return false;
}
// 查找视频流
video_stream_index = av_find_best_stream(input_format_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
if (video_stream_index < 0) {
std::cerr << "未找到视频流" << std::endl;
return false;
}
// 获取解码器
AVCodecParameters* codecpar = input_format_ctx->streams[video_stream_index]->codecpar;
const AVCodec* decoder = avcodec_find_decoder(codecpar->codec_id);
if (!decoder) {
std::cerr << "未找到解码器" << std::endl;
return false;
}
// 创建解码器上下文
input_codec_ctx = avcodec_alloc_context3(decoder);
if (!input_codec_ctx) {
std::cerr << "无法分配解码器上下文" << std::endl;
return false;
}
// 复制参数
ret = avcodec_parameters_to_context(input_codec_ctx, codecpar);
if (ret < 0) {
std::cerr << "无法复制编解码器参数" << std::endl;
return false;
}
// 打开解码器
ret = avcodec_open2(input_codec_ctx, decoder, nullptr);
if (ret < 0) {
std::cerr << "无法打开解码器" << std::endl;
return false;
}
return true;
}
bool openOutputFile(const std::string& filename) {
int ret;
// 分配输出格式上下文
avformat_alloc_output_context2(&output_format_ctx, nullptr, nullptr, filename.c_str());
if (!output_format_ctx) {
std::cerr << "无法创建输出格式上下文" << std::endl;
return false;
}
// 查找编码器
const AVCodec* encoder = avcodec_find_encoder(AV_CODEC_ID_MPEG4);
if (!encoder) {
std::cerr << "未找到MPEG4编码器" << std::endl;
return false;
}
// 创建输出流
AVStream* output_stream = avformat_new_stream(output_format_ctx, encoder);
if (!output_stream) {
std::cerr << "无法创建输出流" << std::endl;
return false;
}
// 创建编码器上下文
output_codec_ctx = avcodec_alloc_context3(encoder);
if (!output_codec_ctx) {
std::cerr << "无法分配编码器上下文" << std::endl;
return false;
}
// 设置编码参数
output_codec_ctx->width = input_codec_ctx->width;
output_codec_ctx->height = input_codec_ctx->height;
output_codec_ctx->time_base = (AVRational){1, 25};
output_codec_ctx->pix_fmt = encoder->pix_fmts ? encoder->pix_fmts[0] : AV_PIX_FMT_YUV420P;
output_codec_ctx->bit_rate = 1000000;
// 打开编码器
ret = avcodec_open2(output_codec_ctx, encoder, nullptr);
if (ret < 0) {
std::cerr << "无法打开编码器" << std::endl;
return false;
}
// 复制参数到输出流
ret = avcodec_parameters_from_context(output_stream->codecpar, output_codec_ctx);
if (ret < 0) {
std::cerr << "无法复制编码器参数" << std::endl;
return false;
}
// 如果不是输出到标准输出,打开输出文件
if (!(output_format_ctx->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open(&output_format_ctx->pb, filename.c_str(), AVIO_FLAG_WRITE);
if (ret < 0) {
std::cerr << "无法打开输出文件: " << filename << std::endl;
return false;
}
}
return true;
}
bool transcodeFrames() {
AVPacket* input_packet = av_packet_alloc();
AVFrame* input_frame = av_frame_alloc();
AVFrame* output_frame = av_frame_alloc();
AVPacket* output_packet = av_packet_alloc();
if (!input_packet || !input_frame || !output_frame || !output_packet) {
std::cerr << "无法分配内存" << std::endl;
return false;
}
struct SwsContext* sws_ctx = sws_getContext(
input_codec_ctx->width, input_codec_ctx->height, input_codec_ctx->pix_fmt,
output_codec_ctx->width, output_codec_ctx->height, output_codec_ctx->pix_fmt,
SWS_BILINEAR, nullptr, nullptr, nullptr);
while (av_read_frame(input_format_ctx, input_packet) >= 0) {
if (input_packet->stream_index == video_stream_index) {
// 解码
int ret = avcodec_send_packet(input_codec_ctx, input_packet);
if (ret < 0) {
std::cerr << "解码发送包失败" << std::endl;
break;
}
while (ret >= 0) {
ret = avcodec_receive_frame(input_codec_ctx, input_frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
std::cerr << "解码接收帧失败" << std::endl;
break;
}
// 转换像素格式
sws_scale(sws_ctx, input_frame->data, input_frame->linesize,
0, input_codec_ctx->height, output_frame->data, output_frame->linesize);
// 编码
output_frame->pts = input_frame->pts;
ret = avcodec_send_frame(output_codec_ctx, output_frame);
if (ret < 0) {
std::cerr << "编码发送帧失败" << std::endl;
break;
}
while (ret >= 0) {
ret = avcodec_receive_packet(output_codec_ctx, output_packet);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
std::cerr << "编码接收包失败" << std::endl;
break;
}
// 写入输出文件
av_packet_rescale_ts(output_packet,
output_codec_ctx->time_base,
output_format_ctx->streams[0]->time_base);
output_packet->stream_index = 0;
av_interleaved_write_frame(output_format_ctx, output_packet);
av_packet_unref(output_packet);
}
av_frame_unref(input_frame);
}
}
av_packet_unref(input_packet);
}
// 清理资源
sws_freeContext(sws_ctx);
av_packet_free(&input_packet);
av_frame_free(&input_frame);
av_frame_free(&output_frame);
av_packet_free(&output_packet);
return true;
}
void cleanup() {
if (input_codec_ctx) {
avcodec_free_context(&input_codec_ctx);
}
if (output_codec_ctx) {
avcodec_free_context(&output_codec_ctx);
}
if (input_format_ctx) {
avformat_close_input(&input_format_ctx);
}
if (output_format_ctx && !(output_format_ctx->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&output_format_ctx->pb);
}
if (output_format_ctx) {
avformat_free_context(output_format_ctx);
}
}
};
// 使用示例
int main() {
VideoTranscoder transcoder;
std::string input_file = "input.mp4";
std::string output_file = "output.avi";
if (transcoder.transcodeVideo(input_file, output_file)) {
std::cout << "转码成功!" << std::endl;
} else {
std::cout << "转码失败!" << std::endl;
}
return 0;
}
DLL文件处理
1. 复制必需的DLL文件
# 如果使用vcpkg,复制DLL文件到输出目录
copy C:\vcpkg\installed\x64-windows\bin\*.dll $(OutDir)
# 如果使用预编译包,复制DLL文件
copy C:\ffmpeg\bin\*.dll $(OutDir)
# 或在项目设置中添加后期生成事件
xcopy /Y "$(SolutionDir)..\ffmpeg\bin\*.dll" "$(OutDir)"
2. 项目后期生成事件配置
// 项目属性 -> Build Events -> Post-Build Event
// Command Line:
xcopy /Y "$(VcpkgRoot)installed\$(VcpkgTriplet)\bin\*.dll" "$(OutDir)"
// 或
xcopy /Y "C:\ffmpeg\bin\*.dll" "$(OutDir)"
常见问题解决
1. 链接错误
// 错误:无法解析外部符号
// 解决方案:
// 1. 确保所有FFmpeg库都已添加到链接器输入
// 2. 确保库目录设置正确
// 3. 确保使用正确的库架构(x64)
// 错误:LNK2019 无法解析的外部符号
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "swscale.lib")
#pragma comment(lib, "swresample.lib")
2. 运行时错误
// 错误:找不到DLL文件
// 解决方案:
// 1. 将DLL文件复制到可执行文件同一目录
// 2. 将DLL目录添加到系统PATH环境变量
// 3. 使用依赖项查看器检查缺失的DLL
// 错误:0xc0000135 无法定位程序点
// 解决方案:
// 1. 确保使用相同版本的运行时库
// 2. 检查VC++运行时是否已安装
3. 编译器设置
// 项目属性设置:
// C/C++ -> Code Generation -> Runtime Library
// Release: Multi-threaded DLL (/MD)
// Debug: Multi-threaded Debug DLL (/MDd)
// C/C++ -> General -> Warning Level
// Level 3 (/W3) 或更高
// C/C++ -> Preprocessor -> Preprocessor Definitions
_CRT_SECURE_NO_WARNINGS
_WINDOWS
WIN32
性能优化建议
1. 编译优化
// 项目属性 -> C/C++ -> Optimization
Optimization: Maximize Speed (/O2)
Inline Function Expansion: Any Suitable (/Ob2)
Enable Intrinsic Functions: Yes (/Oi)
Favor Size Or Speed: Favor fast code (/Ot)
// 项目属性 -> Linker -> Optimization
References: Yes (/OPT:REF)
Enable COMDAT Folding: Yes (/OPT:ICF)
2. FFmpeg使用优化
// 多线程处理
av_dict_set(&opts, "threads", "0", 0); // 自动检测线程数
// 硬件加速(如果支持)
// 使用DXVA2, D3D11VA, CUDA等
// 内存管理优化
// 合理使用av_frame_alloc/av_frame_free
// 及时释放不需要的资源
3. 调试配置
// Debug配置
// C/C++ -> Preprocessor -> Preprocessor Definitions
_DEBUG
CRTDBG_MAP_ALLOC
// 添加内存泄漏检测
#ifdef _DEBUG
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
#define new DEBUG_NEW
#endif
// 在main函数开始处添加
#ifdef _DEBUG
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
完整项目结构示例
FFmpegProject/
├── FFmpegProject.sln
├── FFmpegProject/
│ ├── FFmpegProject.vcxproj
│ ├── FFmpegProject.vcxproj.filters
│ ├── main.cpp
│ ├── video_info.cpp
│ ├── video_info.h
│ ├── video_transcoder.cpp
│ ├── video_transcoder.h
│ └── resource.h
├── include/
│ └── (FFmpeg头文件,如果需要本地复制)
├── lib/
│ └── (FFmpeg库文件,如果需要本地复制)
└── bin/
└── (输出目录,包含DLL文件)
测试用例
// test_ffmpeg.cpp
#include "video_info.h"
#include "video_transcoder.h"
#include <cassert>
void testVideoInfo() {
VideoInfoReader reader;
// 测试文件存在性
assert(reader.getVideoInfo("test.mp4") == true);
// 测试文件不存在
assert(reader.getVideoInfo("nonexistent.mp4") == false);
std::cout << "VideoInfo测试通过!" << std::endl;
}
void testTranscoder() {
VideoTranscoder transcoder;
// 测试转码功能
bool result = transcoder.transcodeVideo("input.mp4", "output.avi");
assert(result == true);
std::cout << "Transcoder测试通过!" << std::endl;
}
int main() {
testVideoInfo();
testTranscoder();
std::cout << "所有测试通过!" << std::endl;
return 0;
}
完成这些配置后,你就可以在Visual Studio中成功集成和使用FFmpeg进行C++开发了。推荐使用vcpkg方法,因为它最简单且维护性最好。