百行代码实现FFmpeg播放器:C++与SDL的完美协作
引言:多媒体开发的入门捷径
嘿,大伙好啊,我是Henry。现在这短视频内容多得都快“爆炸”啦,搞明白音视频处理的原理可太重要了。
今儿个我这文章呢,直接用一个就一百行左右的C++程序,再结合FFmpeg和SDL这俩库,做出一个基础但该有的功能都有的视频播放器。
这个项目不光适合咱们学习多媒体开发的基础,也是弄懂编解码、帧处理这些核心概念的超棒开头。希望能让大家有所收获哈。
项目架构与依赖
核心组件
- FFmpeg:处理视频解码的核心库
- SDL2:提供跨平台的渲染和显示功能
- C++17:现代C++特性简化资源管理
环境配置
#windows安装
直接去ffmpeg和SDL官网下载对应版本的编译库即可,添加对应的头文件和库,直接链接即可运行成功
下面是下载链接
FFmpeg: https://ffmpeg.org/download.html
SDL:https://github.com/libsdl-org/SDL/releases/latest
# Ubuntu安装依赖
sudo apt install libavcodec-dev libavformat-dev libswscale-dev libsdl2-dev
# macOS使用Homebrew
brew install ffmpeg sdl2
完整代码实现(98行)
#include <iostream>
#include <memory>
#include <SDL.h>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
}
// 自定义资源释放器
struct AVDeleter {
void operator()(AVFormatContext* ctx) { avformat_close_input(&ctx); }
void operator()(AVCodecContext* ctx) { avcodec_free_context(&ctx); }
void operator()(AVFrame* frame) { av_frame_free(&frame); }
void operator()(SwsContext* ctx) { sws_freeContext(ctx); }
void operator()(SDL_Texture* tex) { SDL_DestroyTexture(tex); }
void operator()(SDL_Renderer* ren) { SDL_DestroyRenderer(ren); }
void operator()(SDL_Window* win) { SDL_DestroyWindow(win); }
};
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <video_file>\n";
return 1;
}
// 1. 初始化FFmpeg
avformat_network_init();
// 2. 打开视频文件
AVFormatContext* fmt_ctx_raw = nullptr;
if (avformat_open_input(&fmt_ctx_raw, argv[1], nullptr, nullptr) != 0) {
std::cerr << "Error opening file\n";
return 1;
}
std::unique_ptr<AVFormatContext, AVDeleter> fmt_ctx(fmt_ctx_raw);
// 3. 查找视频流
if (avformat_find_stream_info(fmt_ctx.get(), nullptr) < 0) {
std::cerr << "Error finding stream info\n";
return 1;
}
int video_stream = -1;
AVCodecParameters* codecpar = nullptr;
for (unsigned i = 0; i < fmt_ctx->nb_streams; i++) {
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream = i;
codecpar = fmt_ctx->streams[i]->codecpar;
break;
}
}
if (video_stream == -1) {
std::cerr << "No video stream found\n";
return 1;
}
// 4. 初始化解码器
const AVCodec* codec = avcodec_find_decoder(codecpar->codec_id);
if (!codec) {
std::cerr << "Unsupported codec\n";
return 1;
}
AVCodecContext* codec_ctx_raw = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codec_ctx_raw, codecpar);
if (avcodec_open2(codec_ctx_raw, codec, nullptr) < 0) {
std::cerr << "Error opening codec\n";
return 1;
}
std::unique_ptr<AVCodecContext, AVDeleter> codec_ctx(codec_ctx_raw);
// 5. 初始化SDL
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
std::cerr << "SDL init failed: " << SDL_GetError() << "\n";
return 1;
}
SDL_Window* win_raw = SDL_CreateWindow("FFmpeg Player",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
codecpar->width, codecpar->height, SDL_WINDOW_SHOWN);
std::unique_ptr<SDL_Window, AVDeleter> win(win_raw);
SDL_Renderer* ren_raw = SDL_CreateRenderer(win.get(), -1,
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
std::unique_ptr<SDL_Renderer, AVDeleter> ren(ren_raw);
SDL_Texture* tex_raw = SDL_CreateTexture(ren.get(),
SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING,
codecpar->width, codecpar->height);
std::unique_ptr<SDL_Texture, AVDeleter> tex(tex_raw);
// 6. 准备帧处理
std::unique_ptr<AVFrame, AVDeleter> frame(av_frame_alloc());
std::unique_ptr<AVFrame, AVDeleter> frame_yuv(av_frame_alloc());
frame_yuv->format = AV_PIX_FMT_YUV420P;
frame_yuv->width = codecpar->width;
frame_yuv->height = codecpar->height;
av_frame_get_buffer(frame_yuv.get(), 0);
std::unique_ptr<SwsContext, AVDeleter> sws_ctx(sws_getContext(
codecpar->width, codecpar->height, codec_ctx->pix_fmt,
codecpar->width, codecpar->height, AV_PIX_FMT_YUV420P,
SWS_BILINEAR, nullptr, nullptr, nullptr));
// 7. 主播放循环
AVPacket packet;
bool quit = false;
SDL_Event event;
while (!quit) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) quit = true;
}
if (av_read_frame(fmt_ctx.get(), &packet) < 0) break;
if (packet.stream_index == video_stream) {
if (avcodec_send_packet(codec_ctx.get(), &packet) == 0) {
while (avcodec_receive_frame(codec_ctx.get(), frame.get()) == 0) {
// 转换帧格式
sws_scale(sws_ctx.get(), frame->data, frame->linesize,
0, frame->height, frame_yuv->data, frame_yuv->linesize);
// 更新纹理
SDL_UpdateYUVTexture(tex.get(), nullptr,
frame_yuv->data[0], frame_yuv->linesize[0],
frame_yuv->data[1], frame_yuv->linesize[1],
frame_yuv->data[2], frame_yuv->linesize[2]);
// 渲染显示
SDL_RenderClear(ren.get());
SDL_RenderCopy(ren.get(), tex.get(), nullptr, nullptr);
SDL_RenderPresent(ren.get());
// 简单帧率控制
SDL_Delay(1000 / 30); // 约30fps
}
}
}
av_packet_unref(&packet);
}
SDL_Quit();
return 0;
}
关键技术与原理解析
1. 媒体文件解析(FFmpeg)
avformat_open_input(&fmt_ctx, "video.mp4", nullptr, nullptr);
avformat_find_stream_info(fmt_ctx, nullptr);
- 探测文件格式(MP4、MKV等)
- 解析媒体流信息(视频、音频、字幕)
- 获取编解码参数
2. 视频解码流程
3. 帧处理与渲染
sws_scale(sws_ctx, frame->data, ...); // YUV格式转换
SDL_UpdateYUVTexture(tex, ...); // 更新纹理
SDL_RenderCopy(ren, tex, ...); // 渲染到屏幕
- 颜色空间转换:原始YUV转RGB
- 硬件加速:SDL利用GPU渲染
- 帧率控制:简单延时实现
4. 现代C++资源管理
std::unique_ptr<AVFormatContext, AVDeleter> fmt_ctx(fmt_ctx_raw);
- 使用智能指针自动管理资源
- 自定义删除器处理FFmpeg/SDL对象
- 避免内存泄漏和资源未释放
编译与运行指南
编译命令(Linux/macOS)
g++ -std=c++17 -o player player.cpp \
$(pkg-config --cflags --libs libavcodec libavformat libswscale sdl2)
运行示例
./player sample.mp4
测试视频获取
# 下载测试视频
wget https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4
功能扩展方向
1、音频同步播放
// 初始化SDL音频子系统
SDL_Init(SDL_INIT_AUDIO);
// 创建音频解码上下文
2、播放控制
// 响应键盘事件
case SDLK_SPACE: paused = !paused;
case SDLK_RIGHT: seek_position += 10000;
3、视频滤镜
// 添加水印
avfilter_graph_create_filter(&filter_ctx, "overlay", ...);
4、性能优化
// 硬件加速解码
av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_CUDA, ...);
学习资源推荐
- 官方文档
- FFmpeg: https://ffmpeg.org/documentation.html
- SDL: https://wiki.libsdl.org/APIByCategory
- 进阶书籍
- 《FFmpeg从入门到精通》
- 《音视频开发进阶指南》
- 开源项目
- VLC播放器源码
- OBS Studio项目
结语
这个百行播放器项目把多媒体处理的核心流程给展现出来了,它包含了从解析文件到渲染帧的一整套过程。咱们要是给它加上音频支持、播放控制这些功能,慢慢地就能做出一个功能齐全的媒体播放器。搞懂了这里面的基本原理,以后再深入去研究音视频处理、流媒体开发这些领域,就有了很扎实的底子啦。