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。
发表回复