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

基于ffmpeg和rk3588的mpp编解码库多路融屏程序设计

        rk3588因为其支持8k级视频解码,4k级编码,其强悍的编解码性能常常用来做大屏监控的嵌入式盒子的解决方案,可以部分替代笨重且昂贵的传统视频电视墙的多媒体矩阵服务器,适合小型的监控场景多路监控的应用。

     ffmpeg媒体框架的强大的拉流推流以及协议解析组包能力,可以实现多种流的拉取和解析,采用他可以提高程序的兼容性和普适性,通过多线程的方式可以开启多路独立的流的拉取,并实时获取raw数据供解码器解码,为了实现多路并行机制,采用了线程安全的队列实现简单的生产消费模式,可以无阻塞的让数据流在传输和解码,拼接间流畅的运行。

    rk的libmpp是瑞芯微的一个开源的通用编解码封装的软件包,实现了强大的零拷贝解码以及rga视频图像的缩放拼接等功能。同时还支持v4l2的采集功能,是一个rk硬件平台理想的音频处理库。利用上述的代码组合可以实现强大的音视频软件功能,兼顾软件生态和灵活控制的多重需求。是硬件播放器,拉流转推,mcu融合的好帮手。

    mpp通过dmafd句柄的操作,可以避免在内存和vpu gpu间的拷贝动作,将编解码速度降到5ms以内,满足实时的多路解码融合的要求,通过灵活的编程,分配内部的硬件存储空间,实现yuv大数据量的处理能力。

    下面是一个拉流处理的流程框架程序供参考

// 文件:main.cpp
#include "ffmpeg_input.h"
#include "mpp_decoder.h"
#include "stitch_encoder.h"
#include "ffmpeg_output.h"
#include "config.h"
#include "web_server.h"
#include "status.h"
#include <vector>
#include <iostream>
#include <csignal>
#include <atomic>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/rotating_file_sink.h>
#include <mutex>std::atomic<bool> g_running{true};
std::atomic<bool> g_reload_config{false};
std::mutex g_log_mutex;
void signal_handler(int) { g_running = false; }// 状态监控输出
void print_status(const MixerConfig& config) {std::lock_guard<std::mutex> lock(g_log_mutex);spdlog::info("当前输入流数: {}", config.inputs.size());for (size_t i = 0; i < config.inputs.size(); ++i) {spdlog::info("输入流{}: {} ({}x{}, audio:{})", i, config.inputs[i].url, config.inputs[i].video_width, config.inputs[i].video_height, config.inputs[i].audio);}spdlog::info("输出流: {} ({}x{}, {}kbps, {}kbps, fps:{})", config.output.url, config.output.video_width, config.output.video_height, config.output.video_bitrate/1000, config.output.audio_bitrate/1000, config.output.fps);spdlog::info("布局: {} grid_size:{}", (int)config.layout, config.grid_size);
}// 音频参数一致性检查
bool check_audio_params(const MixerConfig& config, int& sample_rate, int& channels) {sample_rate = config.output.audio_bitrate > 0 ? config.output.audio_bitrate : 44100;channels = 2;for (const auto& in : config.inputs) {if (in.audio) {// 可扩展:从流信息动态获取参数// 这里只能用配置或默认值}}return true;
}int main() {auto logger = spdlog::rotating_logger_mt("mainlog", "logs/streammixer.log", 10*1024*1024, 5);spdlog::set_default_logger(logger);MixerConfig config;if (!load_config("config.json", config)) {std::cerr << "Failed to load config.json" << std::endl;return 1;}int NUM_STREAMS = config.inputs.size();int OUTPUT_WIDTH = config.output.video_width;int OUTPUT_HEIGHT = config.output.video_height;int audio_sample_rate = config.output.audio_bitrate > 0 ? config.output.audio_bitrate : 44100;int audio_channels = 2;check_audio_params(config, audio_sample_rate, audio_channels);AVSampleFormat audio_fmt = AV_SAMPLE_FMT_FLTP;// 创建队列std::vector<SafeQueue<AVPacket*>> video_packet_queues(NUM_STREAMS);std::vector<SafeQueue<AVPacket*>> audio_packet_queues(NUM_STREAMS);std::vector<SafeQueue<MppFrame>> decoded_frame_queues(NUM_STREAMS);std::vector<SafeQueue<AVFrame*>> audio_frame_queues(NUM_STREAMS);SafeQueue<MppPacket> output_packet_queue;SafeQueue<AVFrame*> mixed_audio_queue;SafeQueue<AVPacket*> mixed_audio_packet_queue;// 输入URL列表std::vector<std::string> input_urls;for (auto& in : config.inputs) input_urls.push_back(in.url);// 初始化各模块FFmpegInput* input = new FFmpegInput(NUM_STREAMS, video_packet_queues, audio_packet_queues, config.inputs);input->init(input_urls);MppDecoder* video_decoder = new MppDecoder(NUM_STREAMS, video_packet_queues, decoded_frame_queues);video_decoder->init();// 音频解码(每路一个线程)std::vector<std::unique_ptr<AudioDecoder>> audio_decoders;for (int i = 0; i < NUM_STREAMS; ++i) {audio_decoders.emplace_back(new AudioDecoder("aac", audio_packet_queues[i], audio_frame_queues[i], audio_sample_rate, audio_channels, audio_fmt));}// 音频混音AudioMixer* audio_mixer = new AudioMixer(NUM_STREAMS, audio_frame_queues, mixed_audio_queue, audio_sample_rate, audio_channels, audio_fmt);// 音频编码AudioEncoder* audio_encoder = new AudioEncoder("aac", audio_sample_rate, audio_channels, mixed_audio_queue, mixed_audio_packet_queue);StitchEncoder* encoder = new StitchEncoder(NUM_STREAMS, decoded_frame_queues, output_packet_queue, config.layout, config.grid_size, OUTPUT_WIDTH, OUTPUT_HEIGHT);encoder->init();FFmpegOutput* output = new FFmpegOutput(config.output.url, output_packet_queue, mixed_audio_packet_queue, config.output);output->init();WebServer web("config.json", 8080);web.start();// 启动所有线程try {input->start();video_decoder->start();for (auto& dec : audio_decoders) dec->start();audio_mixer->start();audio_encoder->start();encoder->start();output->start();} catch (const std::exception& e) {spdlog::error("线程启动异常: {}", e.what());return 2;}std::signal(SIGINT, signal_handler);print_status(config);while (g_running) {std::this_thread::sleep_for(std::chrono::seconds(1));print_status(config);if (g_reload_config) {g_reload_config = false;spdlog::warn("配置热加载: 停止所有线程并重建...");// 停止所有线程output->stop(); encoder->stop(); audio_encoder->stop(); audio_mixer->stop();for (auto& dec : audio_decoders) dec->stop();video_decoder->stop(); input->stop();// 资源释放delete output; delete encoder; delete audio_encoder; delete audio_mixer; delete video_decoder; delete input;audio_decoders.clear();// 重新加载配置MixerConfig new_config;if (load_config("config.json", new_config)) {config = new_config;NUM_STREAMS = config.inputs.size();OUTPUT_WIDTH = config.output.video_width;OUTPUT_HEIGHT = config.output.video_height;check_audio_params(config, audio_sample_rate, audio_channels);// 重新创建队列video_packet_queues.assign(NUM_STREAMS, SafeQueue<AVPacket*>());audio_packet_queues.assign(NUM_STREAMS, SafeQueue<AVPacket*>());decoded_frame_queues.assign(NUM_STREAMS, SafeQueue<MppFrame>());audio_frame_queues.assign(NUM_STREAMS, SafeQueue<AVFrame*>());// 重新初始化模块input = new FFmpegInput(NUM_STREAMS, video_packet_queues, audio_packet_queues, config.inputs);input->init(input_urls);video_decoder = new MppDecoder(NUM_STREAMS, video_packet_queues, decoded_frame_queues);video_decoder->init();for (int i = 0; i < NUM_STREAMS; ++i) {audio_decoders.emplace_back(new AudioDecoder("aac", audio_packet_queues[i], audio_frame_queues[i], audio_sample_rate, audio_channels, audio_fmt));}audio_mixer = new AudioMixer(NUM_STREAMS, audio_frame_queues, mixed_audio_queue, audio_sample_rate, audio_channels, audio_fmt);audio_encoder = new AudioEncoder("aac", audio_sample_rate, audio_channels, mixed_audio_queue, mixed_audio_packet_queue);encoder = new StitchEncoder(NUM_STREAMS, decoded_frame_queues, output_packet_queue, config.layout, config.grid_size, OUTPUT_WIDTH, OUTPUT_HEIGHT);encoder->init();output = new FFmpegOutput(config.output.url, output_packet_queue, mixed_audio_packet_queue, config.output);output->init();print_status(config);// 启动所有线程try {input->start(); video_decoder->start();for (auto& dec : audio_decoders) dec->start();audio_mixer->start(); audio_encoder->start(); encoder->start(); output->start();} catch (const std::exception& e) {spdlog::error("热加载线程启动异常: {}", e.what());}} else {spdlog::error("配置热加载失败,未能重新初始化");}}}// 程序退出前web.stop();output->stop(); encoder->stop(); audio_encoder->stop(); audio_mixer->stop();for (auto& dec : audio_decoders) dec->stop();video_decoder->stop(); input->stop();delete output; delete encoder; delete audio_encoder; delete audio_mixer; delete video_decoder; delete input;audio_decoders.clear();spdlog::info("Stream stopped");
}

视频解码


void MppDecoder::run(int stream_idx) {auto& ctx = contexts[stream_idx];auto& in_queue = input_queues[stream_idx];auto& out_queue = output_queues[stream_idx];while (running) {AVPacket* av_pkt = nullptr;in_queue.pop(av_pkt);if (!av_pkt) {// 输出黑帧MppFrame black_frame = nullptr;mpp_frame_init(&black_frame);mpp_frame_set_width(black_frame, 640); // 建议用实际分辨率mpp_frame_set_height(black_frame, 360);mpp_frame_set_fmt(black_frame, MPP_FMT_YUV420SP);MppBuffer buf = nullptr;size_t buf_size = 640 * 360 * 3 / 2;mpp_buffer_get(NULL, &buf, buf_size);memset(mpp_buffer_get_ptr(buf), 0, buf_size);mpp_frame_set_buffer(black_frame, buf);out_queue.push(std::move(black_frame));continue;}// 将FFmpeg包转换为MPP包MppPacket mpp_pkt = nullptr;mpp_packet_init(&mpp_pkt, av_pkt->data, av_pkt->size);mpp_packet_set_pts(mpp_pkt, av_pkt->pts);MPP_RET ret = ctx.mpi->decode_put_packet(ctx.ctx, mpp_pkt);if (ret != MPP_OK) {mpp_packet_deinit(&mpp_pkt);av_packet_free(&av_pkt);continue;}MppFrame frame = nullptr;do {ret = ctx.mpi->decode_get_frame(ctx.ctx, &frame);if (ret != MPP_OK || !frame) break;if (mpp_frame_get_info_change(frame)) {// 处理格式变化ctx.mpi->control(ctx.ctx, MPP_DEC_SET_INFO_CHANGE_READY, NULL);} else if (mpp_frame_get_eos(frame)) {// 结束流} else {out_queue.push(std::move(frame));frame = nullptr;}} while (1);mpp_packet_deinit(&mpp_pkt);av_packet_free(&av_pkt);// 解码失败时也输出黑帧if (ret != MPP_OK) {MppFrame black_frame = nullptr;mpp_frame_init(&black_frame);mpp_frame_set_width(black_frame, 640);mpp_frame_set_height(black_frame, 360);mpp_frame_set_fmt(black_frame, MPP_FMT_YUV420SP);MppBuffer buf = nullptr;size_t buf_size = 640 * 360 * 3 / 2;mpp_buffer_get(NULL, &buf, buf_size);memset(mpp_buffer_get_ptr(buf), 0, buf_size);mpp_frame_set_buffer(black_frame, buf);out_queue.push(std::move(black_frame));continue;}}
}

http://www.dtcms.com/a/321636.html

相关文章:

  • Git 基础操作笔记(速查)
  • 嵌入式Linux学习 - 数据结构6
  • 【设计模式】抽象工厂模式 (工具(Kit)模式)
  • PPT科研绘图实践笔记(持续更新)
  • AI 编程工具使用心得与对比评测
  • Python实现点云PCA配准——粗配准
  • 三种经典寻路算法对比
  • 微服务的好与坏
  • kafak
  • 经常问的14000
  • HTML5 Web Workers 深度剖析:助力网页性能飞速提升
  • imx6ull-驱动开发篇14——原子操作
  • FFmpeg 视频旋转信息处理:3.4 vs 7.0.2
  • 开发避坑指南(22):Vue3响应式编程中this绑定机制与解决方案
  • C++ 部署LSTM(.onnx)
  • 大模型中的核心参数temperature 您知道是什么东东吗?
  • KEIL 环境下 printf 导致程序无法执行的解决方案
  • GPT5评测对比与使用
  • 2025年城市建设与智慧交通国际会议(ICUCIT 2025)
  • OpenAI重磅开源回归!GPT-OSS-120B/20B登陆星辰MaaS
  • 【长度最小的子数组】
  • C++ 红黑树实现详解:理论+代码+图解
  • 主流多模态大模型使用总结
  • GPT-5测评:AI新纪元的开启还是炒作?
  • 【SpringBoot】01 基础入门-SpringBoot2:从核心技术到响应式编程
  • Jenkins自动化构建部署Java、Web前后端项目
  • 使用Python将中文语音翻译成英语音频
  • 达梦DISQL执行SQL和SQL脚本
  • 医疗数据中台架构实战:Java实现高可用、低耦合的数据治理方案
  • 30人大型视频会议设备清单