增加ffmeida 3588编解码进行推拉流

This commit is contained in:
杰仔 2026-05-31 00:15:25 +08:00
parent 78676ed65d
commit 68a6dba1e6
8 changed files with 814 additions and 0 deletions

View File

@ -0,0 +1,72 @@
# FFMediaStream.pro
# 基于 ffmedia_release (Rockchip MPP/RGA/RTSP) 的二次封装
# 提供 RTSP 推流 (IVrFFMediaPusher) RTSP 拉流 (IVrFFMediaPuller) 接口
CONFIG -= qt
TEMPLATE = lib
DEFINES += FFMEDIASTREAM_LIBRARY
CONFIG += c++17
# 全平台使用动态库
CONFIG += shared
win32-msvc {
QMAKE_CXXFLAGS += /utf-8
QMAKE_CFLAGS += /utf-8
}
# 头文件路径
INCLUDEPATH += ./Inc
INCLUDEPATH += ./_Inc
INCLUDEPATH += $$PWD/../../Utils/VrCommon/Inc
INCLUDEPATH += $$PWD/../../Utils/VrUtils/Inc
# ffmedia 头文件
INCLUDEPATH += $$PWD/../../SDK/ffmedia_release/include
INCLUDEPATH += $$PWD/../../SDK/ffmedia_release/include/base
INCLUDEPATH += $$PWD/../../SDK/ffmedia_release/include/module
SOURCES += \
Src/CVrFFMediaPusher.cpp \
Src/CVrFFMediaPuller.cpp
HEADERS += \
Inc/IVrFFMediaPusher.h \
Inc/IVrFFMediaPuller.h \
_Inc/CVrFFMediaPusher.h \
_Inc/CVrFFMediaPuller.h
# 链接
win32:CONFIG(debug, debug|release) {
LIBS += -L../../Utils/VrUtils/debug -lVrUtils
}else:win32:CONFIG(release, debug|release){
LIBS += -L../../Utils/VrUtils/release -lVrUtils
}else:unix:!macx {
LIBS += -L../../Utils/VrUtils -lVrUtils
# ffmedia 主库(仅 ARM/Linux 真正可用Windows 下纯占位)
FFMEDIA_LIB_DIR = $$PWD/../../SDK/ffmedia_release/lib
RKNN_LIB_DIR = $$PWD/../../SDK/ffmedia_release/inference_examples/lib/RK3588
LIBS += -L$${FFMEDIA_LIB_DIR} -lff_media
QMAKE_LFLAGS += -Wl,-rpath-link,$${FFMEDIA_LIB_DIR}
QMAKE_LFLAGS += -Wl,-rpath-link,$${RKNN_LIB_DIR}
# 系统依赖RK3588rga / mpp / drm
# 这些库由 firefly 系统镜像自带,无需提供
LIBS += -lrga -lrockchip_mpp -ldrm -lpthread -ldl
}
# Windows 下提供编译占位(实现内部用 #ifdef __linux__ 守卫具体调用)
win32 {
DEFINES += FFMEDIA_PLATFORM_DUMMY
LIBS += -lws2_32
}
unix:!macx {
message("build FFMediaStream for unix (ffmedia_release)")
QMAKE_CXXFLAGS += -fPIC
target.path = /usr/lib
}
!isEmpty(target.path): INSTALLS += target

View File

@ -0,0 +1,95 @@
#pragma once
#include <cstdint>
#include <cstddef>
#include <string>
#include <functional>
// DLL 导出宏FFMEDIASTREAM_LIBRARY 在 .pro 中已定义)
#ifdef _WIN32
# ifdef FFMEDIASTREAM_LIBRARY
# define VR_FFMEDIA_API __declspec(dllexport)
# else
# define VR_FFMEDIA_API __declspec(dllimport)
# endif
#else
# define VR_FFMEDIA_API
#endif
/**
* @brief
*/
enum class EFFPullPixelFormat
{
NV12 = 0, // 原生硬解输出,零拷贝最快
RGBA32, // 适合 QImage::Format_RGBA8888 直接显示
BGR24, // 适合 OpenCV
};
/**
* @brief
*/
struct VrFFPullConfig
{
std::string rtspUrl; // rtsp://user:pass@host:port/path
bool useTcp{true}; // true=TCP 模式false=UDP低延迟但容易丢包
bool enableVideo{true};
bool enableAudio{false};
// 解码后输出:是否做 RGA 转换到 RGBA32 / BGR24以及目标分辨率
EFFPullPixelFormat outputFormat{EFFPullPixelFormat::RGBA32};
unsigned int outputWidth{0}; // 0 = 跟随输入
unsigned int outputHeight{0};
};
/**
* @brief
*/
struct VrFFPulledFrame
{
const void* data{nullptr};
size_t size{0};
unsigned int width{0};
unsigned int height{0};
unsigned int stride{0}; // 一行字节数RGBA=width*4BGR=width*3NV12=hstride
EFFPullPixelFormat format{EFFPullPixelFormat::RGBA32};
int64_t ptsUs{0}; // PTS
};
using FFPulledFrameCallback = std::function<void(const VrFFPulledFrame&)>;
/**
* @brief IVrFFMediaPuller
*
* ffmedia_release RTSP RTSP -> MPP -> RGA ->
*
*
* IVrFFMediaPuller* puller = nullptr;
* IVrFFMediaPuller::CreateObject(&puller);
* puller->Init(cfg);
* puller->SetFrameCallback([](const VrFFPulledFrame& f){ ... });
* puller->Start();
* ...
* puller->Stop();
* delete puller;
*/
class VR_FFMEDIA_API IVrFFMediaPuller
{
public:
virtual ~IVrFFMediaPuller() = default;
static bool CreateObject(IVrFFMediaPuller** ppPuller);
virtual int Init(const VrFFPullConfig& config) = 0;
virtual void SetFrameCallback(FFPulledFrameCallback callback) = 0;
virtual int Start() = 0;
virtual int Stop() = 0;
virtual int UnInit() = 0;
/**
* @brief
*/
virtual bool IsStreaming() const = 0;
};

View File

@ -0,0 +1,133 @@
#pragma once
#include <cstdint>
#include <cstddef>
#include <string>
#include <memory>
// DLL 导出宏FFMEDIASTREAM_LIBRARY 在 .pro 中已定义)
#ifdef _WIN32
# ifdef FFMEDIASTREAM_LIBRARY
# define VR_FFMEDIA_API __declspec(dllexport)
# else
# define VR_FFMEDIA_API __declspec(dllimport)
# endif
#else
# define VR_FFMEDIA_API
#endif
/**
* @brief
*/
enum class EFFPushPixelFormat
{
NV12 = 0, // 4:2:0 半平面 (Y + UV)
YUV420P, // 4:2:0 平面
BGR24, // OpenCV 默认
RGB24,
BGRA32,
RGBA32,
};
/**
* @brief
*/
enum class EFFEncodeType
{
H264 = 0,
H265,
MJPEG,
};
/**
* @brief
*/
enum class EFFRcMode
{
CBR = 0,
VBR,
};
/**
* @brief
*/
struct VrFFPushConfig
{
// 帧参数
unsigned int width{1920};
unsigned int height{1080};
EFFPushPixelFormat pixelFormat{EFFPushPixelFormat::NV12};
// 编码参数
EFFEncodeType encodeType{EFFEncodeType::H264};
unsigned int fps{30};
unsigned int gop{60};
unsigned int bitrateKbps{4096}; // 码率 (kbps)
EFFRcMode rcMode{EFFRcMode::CBR};
// RTSP 服务参数
std::string rtspPath{"/live/stream"}; // URL 路径
unsigned short rtspPort{8554};
std::string authRealm;
std::string authUser;
std::string authPass;
};
/**
* @brief IVrFFMediaPusher
*
* ffmedia_release RTSP -> RGA -> MPP -> RTSP
*
*
* IVrFFMediaPusher* pusher = nullptr;
* IVrFFMediaPusher::CreateObject(&pusher);
* pusher->Init(cfg);
* pusher->Start();
* pusher->PushFrame(data, size);
* ...
* pusher->Stop();
* delete pusher;
*/
class VR_FFMEDIA_API IVrFFMediaPusher
{
public:
virtual ~IVrFFMediaPusher() = default;
// 工厂
static bool CreateObject(IVrFFMediaPusher** ppPusher);
/**
* @brief
* @return 0
*/
virtual int Init(const VrFFPushConfig& config) = 0;
/**
* @brief RTSP
*/
virtual int Start() = 0;
/**
* @brief
* @param data
* @param size
* @param ptsUs PTS 0
* @return 0
*/
virtual int PushFrame(const void* data, size_t size, int64_t ptsUs = 0) = 0;
/**
* @brief
*/
virtual int Stop() = 0;
/**
* @brief
*/
virtual int UnInit() = 0;
/**
* @brief URL IP//
*/
virtual std::string GetRtspUrl(const std::string& localIp) const = 0;
};

View File

@ -0,0 +1,203 @@
#include "CVrFFMediaPuller.h"
#include "VrError.h"
#include "VrLog.h"
#if !defined(FFMEDIA_PLATFORM_DUMMY)
#include <linux/videodev2.h>
#endif
bool IVrFFMediaPuller::CreateObject(IVrFFMediaPuller** ppPuller)
{
if (!ppPuller) return false;
try { *ppPuller = new CVrFFMediaPuller(); return true; }
catch (...) { return false; }
}
CVrFFMediaPuller::CVrFFMediaPuller() = default;
CVrFFMediaPuller::~CVrFFMediaPuller()
{
try { UnInit(); } catch (...) {}
}
void CVrFFMediaPuller::SetFrameCallback(FFPulledFrameCallback callback)
{
std::lock_guard<std::mutex> lk(m_mutex);
m_callback = std::move(callback);
}
bool CVrFFMediaPuller::IsStreaming() const
{
return m_bStarted.load();
}
#if defined(FFMEDIA_PLATFORM_DUMMY)
// =================== Windows / 非 RK3588 占位实现 ===================
int CVrFFMediaPuller::Init(const VrFFPullConfig& config)
{
std::lock_guard<std::mutex> lk(m_mutex);
m_config = config;
m_bInited = true;
LOG_INFO("FFMediaPuller: Init (dummy)\n");
return SUCCESS;
}
int CVrFFMediaPuller::Start()
{
if (!m_bInited.load()) return ERR_CODE(DEV_NO_OPEN);
m_bStarted = true;
LOG_INFO("FFMediaPuller: Start (dummy)\n");
return SUCCESS;
}
int CVrFFMediaPuller::Stop()
{
m_bStarted = false;
return SUCCESS;
}
int CVrFFMediaPuller::UnInit()
{
Stop();
m_bInited = false;
return SUCCESS;
}
#else
// =================== RK3588 真正实现 ===================
static uint32_t ToV4l2OutPix(EFFPullPixelFormat fmt)
{
switch (fmt)
{
case EFFPullPixelFormat::NV12: return V4L2_PIX_FMT_NV12;
case EFFPullPixelFormat::RGBA32: return V4L2_PIX_FMT_RGBA32;
case EFFPullPixelFormat::BGR24: return V4L2_PIX_FMT_BGR24;
}
return V4L2_PIX_FMT_RGBA32;
}
static unsigned int PixelBpp(EFFPullPixelFormat fmt)
{
switch (fmt)
{
case EFFPullPixelFormat::NV12: return 1; // 平均 1.5 byte/px但 stride 用 width
case EFFPullPixelFormat::RGBA32: return 4;
case EFFPullPixelFormat::BGR24: return 3;
}
return 4;
}
int CVrFFMediaPuller::Init(const VrFFPullConfig& config)
{
std::lock_guard<std::mutex> lk(m_mutex);
if (m_bInited.load()) return SUCCESS;
m_config = config;
// 1) RTSP 拉流
m_rtspClient = std::make_shared<ModuleRtspClient>(
config.rtspUrl.c_str(),
config.useTcp ? RTSP_STREAM_TYPE_TCP : RTSP_STREAM_TYPE_UDP,
config.enableVideo,
config.enableAudio
);
if (m_rtspClient->init() != 0)
{
LOG_ERROR("FFMediaPuller: rtspClient init fail (%s)\n", config.rtspUrl.c_str());
return ERR_CODE(DEV_OPEN_ERR);
}
// 2) MPP 解码
ImagePara inPara = m_rtspClient->getOutputImagePara();
m_decoder = std::make_shared<ModuleMppDec>(inPara);
m_decoder->setProductor(m_rtspClient);
if (m_decoder->init() != 0)
{
LOG_ERROR("FFMediaPuller: mppdec init fail\n");
return ERR_CODE(DEV_OPEN_ERR);
}
// 3) RGA 转目标格式
ImagePara decOut = m_decoder->getOutputImagePara();
const uint32_t outFmt = ToV4l2OutPix(config.outputFormat);
const unsigned int outW = config.outputWidth ? config.outputWidth : decOut.width;
const unsigned int outH = config.outputHeight ? config.outputHeight : decOut.height;
ImagePara outPara(outW, outH, outW, outH, outFmt);
m_rga = std::make_shared<ModuleRga>(decOut, outPara, RGA_ROTATE_NONE);
m_rga->setBufferType(VideoBuffer::MALLOC_BUFFER);
m_rga->setProductor(m_decoder);
// 注册输出回调
m_rga->setOutputDataCallback(
this,
[](void* ctx, std::shared_ptr<MediaBuffer> buffer) {
if (!ctx || !buffer) return;
CVrFFMediaPuller* self = static_cast<CVrFFMediaPuller*>(ctx);
FFPulledFrameCallback cb;
{
std::lock_guard<std::mutex> lk(self->m_mutex);
cb = self->m_callback;
}
if (!cb) return;
VideoBuffer* vbuf = static_cast<VideoBuffer*>(buffer.get());
ImagePara para = vbuf->getImagePara();
VrFFPulledFrame frame;
frame.data = vbuf->getData();
frame.size = vbuf->getActiveSize();
frame.width = para.width;
frame.height = para.height;
frame.stride = para.hstride * PixelBpp(self->m_config.outputFormat);
frame.format = self->m_config.outputFormat;
frame.ptsUs = vbuf->getPUstimestamp();
try { cb(frame); } catch (...) {}
}
);
if (m_rga->init() != 0)
{
LOG_ERROR("FFMediaPuller: rga init fail\n");
return ERR_CODE(DEV_OPEN_ERR);
}
m_bInited = true;
return SUCCESS;
}
int CVrFFMediaPuller::Start()
{
if (!m_bInited.load()) return ERR_CODE(DEV_NO_OPEN);
if (m_bStarted.load()) return SUCCESS;
if (!m_rtspClient) return ERR_CODE(DEV_CTRL_ERR);
m_rtspClient->start();
m_bStarted = true;
return SUCCESS;
}
int CVrFFMediaPuller::Stop()
{
if (!m_bStarted.load()) return SUCCESS;
if (m_rtspClient) m_rtspClient->stop();
m_bStarted = false;
return SUCCESS;
}
int CVrFFMediaPuller::UnInit()
{
Stop();
std::lock_guard<std::mutex> lk(m_mutex);
m_rga.reset();
m_decoder.reset();
m_rtspClient.reset();
m_bInited = false;
return SUCCESS;
}
#endif // FFMEDIA_PLATFORM_DUMMY

View File

@ -0,0 +1,229 @@
#include "CVrFFMediaPusher.h"
#include "VrError.h"
#include "VrLog.h"
#include <chrono>
#if !defined(FFMEDIA_PLATFORM_DUMMY)
#include <linux/videodev2.h>
#endif
bool IVrFFMediaPusher::CreateObject(IVrFFMediaPusher** ppPusher)
{
if (!ppPusher) return false;
try { *ppPusher = new CVrFFMediaPusher(); return true; }
catch (...) { return false; }
}
CVrFFMediaPusher::CVrFFMediaPusher() = default;
CVrFFMediaPusher::~CVrFFMediaPusher()
{
try { UnInit(); } catch (...) {}
}
#if defined(FFMEDIA_PLATFORM_DUMMY)
// =================== Windows / 非 RK3588 占位实现 ===================
// 实际编码/推流仅在 RK3588 + ffmedia_release 下可用
// Windows 仅保证编译通过和接口存在
int CVrFFMediaPusher::Init(const VrFFPushConfig& config)
{
std::lock_guard<std::mutex> lk(m_mutex);
m_config = config;
m_bInited = true;
LOG_INFO("FFMediaPusher: Init (dummy on this platform)\n");
return SUCCESS;
}
int CVrFFMediaPusher::Start()
{
if (!m_bInited.load()) return ERR_CODE(DEV_NO_OPEN);
m_bStarted = true;
LOG_INFO("FFMediaPusher: Start (dummy)\n");
return SUCCESS;
}
int CVrFFMediaPusher::PushFrame(const void* /*data*/, size_t /*size*/, int64_t /*ptsUs*/)
{
return m_bStarted.load() ? SUCCESS : ERR_CODE(DEV_NO_OPEN);
}
int CVrFFMediaPusher::Stop()
{
m_bStarted = false;
return SUCCESS;
}
int CVrFFMediaPusher::UnInit()
{
Stop();
m_bInited = false;
return SUCCESS;
}
#else
// =================== RK3588 真正实现 ===================
static uint32_t ToV4l2Pix(EFFPushPixelFormat fmt)
{
switch (fmt)
{
case EFFPushPixelFormat::NV12: return V4L2_PIX_FMT_NV12;
case EFFPushPixelFormat::YUV420P: return V4L2_PIX_FMT_YUV420;
case EFFPushPixelFormat::BGR24: return V4L2_PIX_FMT_BGR24;
case EFFPushPixelFormat::RGB24: return V4L2_PIX_FMT_RGB24;
case EFFPushPixelFormat::BGRA32: return V4L2_PIX_FMT_BGR32;
case EFFPushPixelFormat::RGBA32: return V4L2_PIX_FMT_RGBA32;
}
return V4L2_PIX_FMT_NV12;
}
static EncodeType ToFFEncode(EFFEncodeType t)
{
switch (t)
{
case EFFEncodeType::H264: return ENCODE_TYPE_H264;
case EFFEncodeType::H265: return ENCODE_TYPE_H265;
case EFFEncodeType::MJPEG: return ENCODE_TYPE_MJPEG;
}
return ENCODE_TYPE_H264;
}
int CVrFFMediaPusher::Init(const VrFFPushConfig& config)
{
std::lock_guard<std::mutex> lk(m_mutex);
if (m_bInited.load()) return SUCCESS;
m_config = config;
const uint32_t inPix = ToV4l2Pix(config.pixelFormat);
const bool needRga = (config.pixelFormat != EFFPushPixelFormat::NV12);
// 1) 内存输入源
ImagePara inPara(config.width, config.height, config.width, config.height, inPix);
m_memReader = std::make_shared<ModuleMemReader>(inPara);
if (m_memReader->init() != 0)
{
LOG_ERROR("FFMediaPusher: memReader init fail\n");
return ERR_CODE(DEV_OPEN_ERR);
}
// 2) 可选RGA 转 NV12
std::shared_ptr<ModuleMedia> encInput = m_memReader;
if (needRga)
{
ImagePara nv12Para(config.width, config.height, config.width, config.height, V4L2_PIX_FMT_NV12);
m_rga = std::make_shared<ModuleRga>(inPara, nv12Para, RGA_ROTATE_NONE);
m_rga->setProductor(m_memReader);
if (m_rga->init() != 0)
{
LOG_ERROR("FFMediaPusher: rga init fail\n");
return ERR_CODE(DEV_OPEN_ERR);
}
encInput = m_rga;
}
// 3) MPP 硬件编码
m_encoder = std::make_shared<ModuleMppEnc>(
ToFFEncode(config.encodeType),
config.fps,
config.gop,
config.bitrateKbps,
(config.rcMode == EFFRcMode::CBR) ? ENCODE_RC_MODE_CBR : ENCODE_RC_MODE_VBR,
ENCODE_QUALITY_BEST,
ENCODE_PROFILE_HIGH
);
m_encoder->setProductor(encInput);
m_encoder->setDuration(1000000 / std::max<uint32_t>(1, config.fps));
if (m_encoder->init() != 0)
{
LOG_ERROR("FFMediaPusher: encoder init fail\n");
return ERR_CODE(DEV_OPEN_ERR);
}
// 4) RTSP 推流
m_rtspServer = std::make_shared<ModuleRtspServer>(config.rtspPath.c_str(), config.rtspPort);
if (!config.authUser.empty())
{
m_rtspServer->setAuthInfo(config.authRealm, config.authUser, config.authPass);
}
m_rtspServer->setProductor(m_encoder);
if (m_rtspServer->init() != 0)
{
LOG_ERROR("FFMediaPusher: rtsp server init fail\n");
return ERR_CODE(DEV_OPEN_ERR);
}
m_bInited = true;
return SUCCESS;
}
int CVrFFMediaPusher::Start()
{
if (!m_bInited.load()) return ERR_CODE(DEV_NO_OPEN);
if (m_bStarted.load()) return SUCCESS;
if (!m_memReader) return ERR_CODE(DEV_CTRL_ERR);
m_memReader->start();
m_bStarted = true;
return SUCCESS;
}
int CVrFFMediaPusher::PushFrame(const void* data, size_t size, int64_t ptsUs)
{
if (!m_bStarted.load()) return ERR_CODE(DEV_NO_OPEN);
if (!m_memReader) return ERR_CODE(DEV_CTRL_ERR);
if (ptsUs == 0)
{
auto now = std::chrono::steady_clock::now().time_since_epoch();
ptsUs = std::chrono::duration_cast<std::chrono::microseconds>(now).count();
}
m_lastPtsUs = ptsUs;
int ret = m_memReader->setInputBuffer(const_cast<void*>(data), size, -1, ptsUs);
if (ret != 0)
{
LOG_DEBUG("FFMediaPusher: setInputBuffer fail %d\n", ret);
return ret;
}
m_memReader->waitProcess(2000);
return SUCCESS;
}
int CVrFFMediaPusher::Stop()
{
if (!m_bStarted.load()) return SUCCESS;
if (m_memReader)
{
m_memReader->setProcessStatus(ModuleMemReader::PROCESS_STATUS_EXIT);
m_memReader->stop();
}
m_bStarted = false;
return SUCCESS;
}
int CVrFFMediaPusher::UnInit()
{
Stop();
std::lock_guard<std::mutex> lk(m_mutex);
m_rtspServer.reset();
m_encoder.reset();
m_rga.reset();
m_memReader.reset();
m_bInited = false;
return SUCCESS;
}
#endif // FFMEDIA_PLATFORM_DUMMY
std::string CVrFFMediaPusher::GetRtspUrl(const std::string& localIp) const
{
char buf[256];
snprintf(buf, sizeof(buf), "rtsp://%s:%u%s",
localIp.c_str(), m_config.rtspPort, m_config.rtspPath.c_str());
return std::string(buf);
}

View File

@ -0,0 +1,39 @@
#pragma once
#include "IVrFFMediaPuller.h"
#include <atomic>
#include <mutex>
#include <memory>
#if !defined(FFMEDIA_PLATFORM_DUMMY)
#include "module/vi/module_rtspClient.hpp"
#include "module/vp/module_mppdec.hpp"
#include "module/vp/module_rga.hpp"
#endif
class CVrFFMediaPuller : public IVrFFMediaPuller
{
public:
CVrFFMediaPuller();
~CVrFFMediaPuller() override;
int Init(const VrFFPullConfig& config) override;
void SetFrameCallback(FFPulledFrameCallback callback) override;
int Start() override;
int Stop() override;
int UnInit() override;
bool IsStreaming() const override;
private:
VrFFPullConfig m_config;
std::atomic<bool> m_bStarted{false};
std::atomic<bool> m_bInited{false};
FFPulledFrameCallback m_callback;
std::mutex m_mutex;
#if !defined(FFMEDIA_PLATFORM_DUMMY)
std::shared_ptr<ModuleRtspClient> m_rtspClient;
std::shared_ptr<ModuleMppDec> m_decoder;
std::shared_ptr<ModuleRga> m_rga;
#endif
};

View File

@ -0,0 +1,42 @@
#pragma once
#include "IVrFFMediaPusher.h"
#include <atomic>
#include <mutex>
#include <memory>
// ffmedia 头(仅在 Linux/RK3588 平台真正可用)
#if !defined(FFMEDIA_PLATFORM_DUMMY)
#include "module/vi/module_memReader.hpp"
#include "module/vp/module_mppenc.hpp"
#include "module/vo/module_rtspServer.hpp"
#include "module/vp/module_rga.hpp"
#endif
class CVrFFMediaPusher : public IVrFFMediaPusher
{
public:
CVrFFMediaPusher();
~CVrFFMediaPusher() override;
int Init(const VrFFPushConfig& config) override;
int Start() override;
int PushFrame(const void* data, size_t size, int64_t ptsUs = 0) override;
int Stop() override;
int UnInit() override;
std::string GetRtspUrl(const std::string& localIp) const override;
private:
VrFFPushConfig m_config;
std::atomic<bool> m_bStarted{false};
std::atomic<bool> m_bInited{false};
std::mutex m_mutex;
int64_t m_lastPtsUs{0};
#if !defined(FFMEDIA_PLATFORM_DUMMY)
std::shared_ptr<ModuleMemReader> m_memReader;
std::shared_ptr<ModuleRga> m_rga; // 输入格式转 NV12若非 NV12
std::shared_ptr<ModuleMppEnc> m_encoder;
std::shared_ptr<ModuleRtspServer> m_rtspServer;
#endif
};

1
SDK/ffmedia_release Submodule

@ -0,0 +1 @@
Subproject commit 6fe74d3fd783faa8725cba9be5f8487e9d92e6d2