百行代码实现FFmpeg播放器:C++与SDL的完美协作

百行代码实现FFmpeg播放器:C++与SDL的完美协作

编码文章call10242025-07-23 12:19:275A+A-

引言:多媒体开发的入门捷径

嘿,大伙好啊,我是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, ...);


学习资源推荐

  1. 官方文档
  2. FFmpeg: https://ffmpeg.org/documentation.html
  3. SDL: https://wiki.libsdl.org/APIByCategory
  4. 进阶书籍
  5. 《FFmpeg从入门到精通》
  6. 《音视频开发进阶指南》
  7. 开源项目
  8. VLC播放器源码
  9. OBS Studio项目

结语

这个百行播放器项目把多媒体处理的核心流程给展现出来了,它包含了从解析文件到渲染帧的一整套过程。咱们要是给它加上音频支持、播放控制这些功能,慢慢地就能做出一个功能齐全的媒体播放器。搞懂了这里面的基本原理,以后再深入去研究音视频处理、流媒体开发这些领域,就有了很扎实的底子啦。

点击这里复制本文地址 以上内容由文彬编程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

文彬编程网 © All Rights Reserved.  蜀ICP备2024111239号-4