当前位置: 首页 > ops >正文

C++ 使用 ffmpeg 解码 rtsp 流并获取每帧的YUV数据

一、简介

FFmpeg 是一个‌开源的多媒体处理框架‌,非常适用于处理音视频的录制、转换、流化和播放。

二、代码

示例代码使用工作线程读取rtsp视频流,自动重连,支持手动退出,解码并将二进制文件保存下来。

注意: 代码中仅展示了 YUV420P 格式,其他 NV12/NV21 等格式可相应修改。

1. rtsp_decoder.cpp

#include <iostream>
#include <atomic>
#include <thread>
#include <cstdio>extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/error.h>
}const char* rtsp_url = "rtsp://172.0.0.1:8554/video";  // 替换为目标url
std::atomic<bool> running{true};
std::atomic<int> frame_number{0};bool connect_rtsp(AVFormatContext*& fmt_ctx, AVCodecContext*& codec_ctx, int& video_stream_idx) {fmt_ctx = avformat_alloc_context();if (!fmt_ctx) {std::cerr << "Failed to allocate format context" << std::endl;return false;}AVDictionary* opts = nullptr;av_dict_set(&opts, "rtsp_transport", "tcp", 0);    // 使用TCP连接av_dict_set(&opts, "stimeout", "5000000", 0);      // 5秒超时,网络差连接时间长也可以不设置超时av_dict_set(&opts, "reconnect", "1", 0);           // 开启自动重连av_dict_set(&opts, "reconnect_at_eof", "1", 0);    // EOF后重连if (avformat_open_input(&fmt_ctx, rtsp_url, nullptr, &opts) != 0) {std::cerr << "Failed to open input" << std::endl;av_dict_free(&opts);return false;}av_dict_free(&opts);if (avformat_find_stream_info(fmt_ctx, nullptr) < 0) {std::cerr << "Failed to find stream info" << std::endl;avformat_close_input(&fmt_ctx);return false;}video_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);if (video_stream_idx < 0) {std::cerr << "Failed to find video stream" << std::endl;avformat_close_input(&fmt_ctx);return false;}AVStream* video_stream = fmt_ctx->streams[video_stream_idx];const AVCodec* decoder = avcodec_find_decoder(video_stream->codecpar->codec_id);if (!decoder) {std::cerr << "Failed to find decoder" << std::endl;avformat_close_input(&fmt_ctx);return false;}codec_ctx = avcodec_alloc_context3(decoder);if (!codec_ctx) {std::cerr << "Failed to allocate codec context" << std::endl;avformat_close_input(&fmt_ctx);return false;}if (avcodec_parameters_to_context(codec_ctx, video_stream->codecpar) < 0) {std::cerr << "Failed to copy codec parameters" << std::endl;avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return false;}if (avcodec_open2(codec_ctx, decoder, nullptr) < 0) {std::cerr << "Failed to open codec" << std::endl;avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return false;}return true;
}void save_yuv_frame(AVFrame* frame) {if (frame->format != AV_PIX_FMT_YUV420P) {std::cerr << "Unsupported pixel format" << std::endl;return;}char filename[256];int current_frame = frame_number.fetch_add(1);snprintf(filename, sizeof(filename), "frame_%05d.yuv", current_frame);FILE* file = fopen(filename, "wb");if (!file) {std::cerr << "Failed to open file: " << filename << std::endl;return;}// 写入Y分量for (int i = 0; i < frame->height; i++) {fwrite(frame->data[0] + i * frame->linesize[0], 1, frame->width, file);}// 写入U分量for (int i = 0; i < frame->height/2; i++) {fwrite(frame->data[1] + i * frame->linesize[1], 1, frame->width/2, file);}// 写入V分量for (int i = 0; i < frame->height/2; i++) {fwrite(frame->data[2] + i * frame->linesize[2], 1, frame->width/2, file);}fclose(file);
}void worker_thread() {avformat_network_init();while (running) {AVFormatContext* fmt_ctx = nullptr;AVCodecContext* codec_ctx = nullptr;int video_stream_idx = -1;if (connect_rtsp(fmt_ctx, codec_ctx, video_stream_idx)) {AVPacket* packet = av_packet_alloc();AVFrame* frame = av_frame_alloc();while (running) {int ret = av_read_frame(fmt_ctx, packet);if (ret < 0) {if (ret == AVERROR(EAGAIN)) continue;char err_buf[AV_ERROR_MAX_STRING_SIZE] = {0};av_strerror(ret, err_buf, sizeof(err_buf));std::cerr << "Error reading packet: " << err_buf << std::endl;break;}if (packet->stream_index == video_stream_idx) {ret = avcodec_send_packet(codec_ctx, packet);if (ret < 0) {char err_buf[AV_ERROR_MAX_STRING_SIZE] = {0};av_strerror(ret, err_buf, sizeof(err_buf));std::cerr << "Error sending packet: " << err_buf << std::endl;av_packet_unref(packet);break;}while (ret >= 0) {ret = avcodec_receive_frame(codec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {break;} else if (ret < 0) {char err_buf[AV_ERROR_MAX_STRING_SIZE] = {0};av_strerror(ret, err_buf, sizeof(err_buf));std::cerr << "Error receiving frame: " << err_buf << std::endl;break;}save_yuv_frame(frame);}}av_packet_unref(packet);}av_packet_free(&packet);av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);}if (running) {std::cout << "Reconnecting in 5 seconds..." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(5));}}avformat_network_deinit();
}int main() {std::thread worker(worker_thread);std::cout << "Running... Enter 'q' to quit" << std::endl;while (running) {char cmd = std::cin.get();if (cmd == 'q') {running = false;}}worker.join();std::cout << "Stopped" << std::endl;return 0;
}

2. CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(FFmpegYUVDecoder)set(CMAKE_CXX_STANDARD 11)find_package(PkgConfig REQUIRED)
pkg_check_modules(AVCODEC REQUIRED libavcodec)
pkg_check_modules(AVFORMAT REQUIRED libavformat)
pkg_check_modules(AVUTIL REQUIRED libavutil)add_executable(rtsp_decoder rtsp_decoder.cpp)target_include_directories(yuv_decoder PRIVATE${AVCODEC_INCLUDE_DIRS}${AVFORMAT_INCLUDE_DIRS}${AVUTIL_INCLUDE_DIRS}
)target_link_libraries(yuv_decoder${AVCODEC_LIBRARIES}${AVFORMAT_LIBRARIES}${AVUTIL_LIBRARIES}pthread
)

依赖的库在安装 ffmpeg 后应该都有,用到的就 libavcodec,libavformat,libavutil三个。

3. 文件目录结构

|—— CMakeLists.txt
|—— rtsp_decoder.cpp

三、编译和运行

1. 安装 ffmpeg

如果还没有安装过,可以如下安装

# Ubuntu/Debian
sudo apt update && sudo apt install ffmpeg
# 查看版本号
ffmpeg -version

如果缺少某个库,可以执行

sudo apt install libavcodec-dev libavformat-dev libavutil-dev

2. 编译

mkdir build
cd build
cmake ..
make -j4

3. 运行

只有一个输入参数,及输入视频的文件路径,输出路径默认在当前路径。

./rtsp_decoder

4. 查看

可以使用 ffplay 命令查看保持的 yuv 数据是否正确。以 1920x1080 大小,yuv420p格式为例:

ffplay -video_size 1920x1080 -pixel_format yuv420p 00001.yuv
http://www.xdnf.cn/news/11819.html

相关文章:

  • 【Linux】自动化构建-Make/Makefile
  • NLP学习路线图(二十二): 循环神经网络(RNN)
  • 搭建nginx的负载均衡
  • 图论水题2
  • LeetCode 2297. 跳跃游戏 VIII(中等)
  • 《Docker》架构
  • Python try-except-else 语句详解
  • 星闪开发之Server-Client 指令交互控制OLED灯案例
  • 【会员专享数据】1960—2023年我国省市县三级逐年降水量数据(Shp/Excel格式)
  • Spring AI入门及案例、模型讲解、向量化和RAG等高级应用…
  • JavaSec-专题-反序列化
  • 猎板硬金镀层厚度:高频通信领域的性能分水岭
  • Python 区块链开发实战:从零到一构建智能合约
  • TypeScript 编译 ES6+ 语法到兼容的 JavaScript介绍
  • 【后端】RPC
  • Java设计模式深度解析:策略模式的核心原理与实战应用
  • unix/linux,sudo,其高级使用
  • 智能进化论:AI必须跨越的四大认知鸿沟
  • 第5篇《中间件负载均衡与连接池管理机制设计》
  • DuckDB + Spring Boot + MyBatis 构建高性能本地数据分析引擎
  • 测试面试题 手机号验证码登录测试用例
  • Python-多线程(一)
  • 网络编程及原理(一)
  • 协议融合驱动效能跃升:Modbus转Ethernet IP的挤出吹塑机应用
  • SSH登陆Linux常见问题大全
  • 分不清路由器、交换机、光猫的概念,一文带你迅速搞懂!!!
  • 黑客常备十大编程语言,每一个都不容易学,但每一个又很有用
  • Java String类(超级详细!)
  • linux mkfs命令参数及用法详解---linux格式化文件系统命令
  • 二叉排序树