基于FFmpeg和OpenCV的RTMP流播放器开发实录

1. 项目背景

本项目旨在实现一个高效的RTMP视频流播放器,支持1080P流畅播放,采用C++开发,底层依赖FFmpeg进行解码,OpenCV进行图像显示。项目支持多线程解码与帧缓冲,适合无人机、监控等实时视频场景。

2. 开发环境

  • 操作系统:Windows 10/11
  • 编译器:VSCode
  • 包管理:vcpkg
  • 主要依赖:
  • FFmpeg
  • OpenCV

3. 环境搭建

3.1 安装vcpkg

powershell

git clone https://github.com/microsoft/vcpkg.git
cd vcpkg
.\bootstrap-vcpkg.bat

3.2 安装依赖库

# 安装FFmpeg和OpenCV
.\vcpkg install ffmpeg:x64-windows
.\vcpkg install opencv4:x64-windows

4. CMake项目配置

CMakeLists.txt 示例:

cmake_minimum_required(VERSION 3.10)
project(ffmpeg_rtmp_demo)

set(CMAKE_CXX_STANDARD 11)

# FFmpeg和OpenCV路径(根据vcpkg安装路径调整)
include_directories("E:/greensoft/vcpkg/installed/x64-windows/include")
link_directories("E:/greensoft/vcpkg/installed/x64-windows/lib")

find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})

add_executable(ffmpeg_rtmp_demo main.cpp)

target_link_libraries(ffmpeg_rtmp_demo PRIVATE
    avformat
    avcodec
    avutil
    swscale
    swresample
    ${OpenCV_LIBS}
)

5. 代码实现

5.1 代码流程

  • 使用FFmpeg解码RTMP流,OpenCV显示
  • 解码与显示分线程,采用生产者-消费者队列缓冲
  • 支持帧率统计、PTS同步显示,提升流畅度
  • 可调节缓冲区大小,兼顾延迟与流畅

5.2 关键代码结构

main.cpp 主要结构如下:

#include <iostream>
#include <opencv2/opencv.hpp>
#include <queue>
#include <thread>
#include <mutex>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/time.h>
}

// 帧结构体
struct Frame {
    cv::Mat image;
    int64_t pts;
};

// 全局变量
std::queue<Frame> frameQueue;
std::mutex mtx;
bool running = true;
const size_t MAX_QUEUE_SIZE = 20;

// 解码线程
void decode_thread(AVFormatContext* fmt_ctx, AVCodecContext* codec_ctx, SwsContext* sws_ctx, int video_stream_index, uint8_t* bgr_buffer, int linesize) {
    AVPacket* pkt = av_packet_alloc();
    AVFrame* frame = av_frame_alloc();
    AVFrame* bgr_frame = av_frame_alloc();
    av_image_fill_arrays(bgr_frame->data, bgr_frame->linesize, bgr_buffer, AV_PIX_FMT_BGR24, codec_ctx->width, codec_ctx->height, 1);

    while (running && av_read_frame(fmt_ctx, pkt) >= 0) {
        if (pkt->stream_index == video_stream_index) {
            if (avcodec_send_packet(codec_ctx, pkt) == 0) {
                while (avcodec_receive_frame(codec_ctx, frame) == 0) {
                    while (running && frameQueue.size() >= MAX_QUEUE_SIZE) {
                        std::this_thread::sleep_for(std::chrono::milliseconds(1));
                    }
                    if (!running) break;
                    sws_scale(sws_ctx, frame->data, frame->linesize, 0, codec_ctx->height, bgr_frame->data, bgr_frame->linesize);
                    int64_t pts = frame->pts;
                    if (pts == AV_NOPTS_VALUE) pts = frame->best_effort_timestamp;
                    Frame f;
                    f.image = cv::Mat(codec_ctx->height, codec_ctx->width, CV_8UC3, bgr_frame->data[0], bgr_frame->linesize[0]).clone();
                    f.pts = pts;
                    std::lock_guard<std::mutex> lock(mtx);
                    frameQueue.push(f);
                }
            }
        }
        av_packet_unref(pkt);
    }
    av_frame_free(&frame);
    av_frame_free(&bgr_frame);
    av_packet_free(&pkt);
}

int main() {
    avformat_network_init();
    const char* url = "rtmp://192.168.1.1/test/drone";
    AVFormatContext* fmt_ctx = nullptr;

    // 网络参数可调
    AVDictionary* options = nullptr;
    av_dict_set(&options, "rtsp_transport", "tcp", 0);
    av_dict_set(&options, "stimeout", "3000000", 0);
    av_dict_set(&options, "buffer_size", "4096000", 0);
    av_dict_set(&options, "max_delay", "1000000", 0);
    av_dict_set(&options, "fflags", "nobuffer", 0);
    av_dict_set(&options, "tune", "zerolatency", 0);

    if (avformat_open_input(&fmt_ctx, url, nullptr, &options) < 0) return -1;
    av_dict_free(&options);
    if (avformat_find_stream_info(fmt_ctx, nullptr) < 0) return -1;

    int video_stream_index = -1;
    for (unsigned i = 0; i < fmt_ctx->nb_streams; ++i) {
        if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_index = i; break;
        }
    }
    if (video_stream_index == -1) return -1;

    AVCodecParameters* codecpar = fmt_ctx->streams[video_stream_index]->codecpar;
    const AVCodec* codec = avcodec_find_decoder(codecpar->codec_id);
    if (!codec) return -1;
    AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);
    avcodec_parameters_to_context(codec_ctx, codecpar);

    // 解码器参数
    codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
    codec_ctx->flags2 |= AV_CODEC_FLAG2_FAST;
    codec_ctx->thread_count = 4;
    codec_ctx->thread_type = FF_THREAD_FRAME;

    if (avcodec_open2(codec_ctx, codec, nullptr) < 0) return -1;

    SwsContext* sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,
                                         codec_ctx->width, codec_ctx->height, AV_PIX_FMT_BGR24,
                                         SWS_BILINEAR, nullptr, nullptr, nullptr);

    int num_bytes = av_image_get_buffer_size(AV_PIX_FMT_BGR24, codec_ctx->width, codec_ctx->height, 1);
    uint8_t* bgr_buffer = (uint8_t*)av_malloc(num_bytes * sizeof(uint8_t));

    std::thread decode_t(decode_thread, fmt_ctx, codec_ctx, sws_ctx, video_stream_index, bgr_buffer, num_bytes);

    cv::namedWindow("RTMP Stream", cv::WINDOW_NORMAL);
    cv::resizeWindow("RTMP Stream", codec_ctx->width, codec_ctx->height);

    // 帧同步显示
    AVRational time_base = fmt_ctx->streams[video_stream_index]->time_base;
    int64_t start_pts = -1;
    int64_t start_time_us = 0;
    while (running) {
        Frame frame;
        bool has_frame = false;
        {
            std::lock_guard<std::mutex> lock(mtx);
            if (!frameQueue.empty()) {
                frame = frameQueue.front();
                frameQueue.pop();
                has_frame = true;
            }
        }
        if (has_frame) {
            if (start_pts == -1) {
                start_pts = frame.pts;
                start_time_us = av_gettime();
            }
            int64_t pts_time = (int64_t)((frame.pts - start_pts) * av_q2d(time_base) * 1000000) + start_time_us;
            int64_t now = av_gettime();
            if (pts_time > now) {
                std::this_thread::sleep_for(std::chrono::microseconds(pts_time - now));
            } else if (now - pts_time > 300000) {
                continue;
            }
            cv::imshow("RTMP Stream", frame.image);
        }
        if (cv::waitKey(1) == 'q') {
            running = false;
            break;
        }
    }
    decode_t.join();
    cv::destroyAllWindows();
    av_free(bgr_buffer);
    sws_freeContext(sws_ctx);
    avcodec_free_context(&codec_ctx);
    avformat_close_input(&fmt_ctx);
    avformat_network_deinit();
    return 0;
}

6.常见问题与优化

  • 头文件找不到:确保 includePath、CMakeLists.txt 配置正确,vcpkg集成到VS。
  • 中文乱码/编码警告:所有源文件保存为 UTF-16 编码。
  • 延迟与卡顿:可调节 MAX_QUEUE_SIZE、网络参数、解码线程数,平衡流畅与实时性。
  • 1080P卡顿:适当增大缓冲、提升解码线程数,或用更高性能的CPU。


已发布

分类

,

来自

标签:

评论

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注