GrabBag/Module/FFMediaStream/Src/CVrFFMediaPusher.cpp
2026-06-08 17:36:13 +08:00

668 lines
22 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "CVrFFMediaPusher.h"
#include "VrError.h"
#include "VrLog.h"
#include <algorithm>
#include <chrono>
#include <cstring>
#if !defined(FFMEDIA_PLATFORM_DUMMY)
#include <linux/videodev2.h>
#endif
// Rockchip FFmpeg 分支要求应用层提供 ff_log_level 全局符号(定义于此)
// main.cpp 中 extern 声明并运行时设 0(仅 ERROR),此处提供定义默认 0
#if !defined(FFMEDIA_PLATFORM_DUMMY)
extern "C" {
unsigned int ff_log_level = 0;
// rkmppdec.c 中引用的 MPP 内部辅助函数,未随 .a 提供
// 返回编码类型对应的字符串名称,此处返回占位值
const char* querystring(MppCodingType /*coding*/)
{
return "unknown";
}
}
#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 + MPP + FFmpeg-rockchip 下可用
// 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 真正实现 MPP + FFmpeg-rockchip ===================
// ---- 辅助函数 ----
uint32_t CVrFFMediaPusher::AlignTo(uint32_t value, uint32_t align)
{
return (value + align - 1) & ~(align - 1);
}
MppCodingType CVrFFMediaPusher::ToMppCoding(EFFEncodeType t)
{
switch (t)
{
case EFFEncodeType::H264: return MPP_VIDEO_CodingAVC;
case EFFEncodeType::H265: return MPP_VIDEO_CodingHEVC;
case EFFEncodeType::MJPEG: return MPP_VIDEO_CodingMJPEG;
}
return MPP_VIDEO_CodingAVC;
}
int CVrFFMediaPusher::ToAvPixelFormat(EFFPushPixelFormat fmt)
{
switch (fmt)
{
case EFFPushPixelFormat::NV12: return AV_PIX_FMT_NV12;
case EFFPushPixelFormat::YUV420P: return AV_PIX_FMT_YUV420P;
case EFFPushPixelFormat::BGR24: return AV_PIX_FMT_BGR24;
case EFFPushPixelFormat::RGB24: return AV_PIX_FMT_RGB24;
case EFFPushPixelFormat::BGRA32: return AV_PIX_FMT_BGRA;
case EFFPushPixelFormat::RGBA32: return AV_PIX_FMT_RGBA;
}
return AV_PIX_FMT_BGR24;
}
// ---- MPP 编码器初始化 ----
int CVrFFMediaPusher::initMppEncoder()
{
// 1) 创建 MPP 上下文
MPP_RET ret = mpp_create(&m_mppCtx, &m_mppApi);
if (ret != MPP_SUCCESS || !m_mppCtx || !m_mppApi)
{
LOG_ERROR("FFMediaPusher: mpp_create fail %d\n", ret);
return ERR_CODE(DEV_OPEN_ERR);
}
MppCodingType coding = ToMppCoding(m_config.encodeType);
ret = m_mppApi->control(m_mppCtx, MPP_SET_INPUT_TIMEOUT, nullptr);
ret = m_mppApi->control(m_mppCtx, MPP_SET_OUTPUT_TIMEOUT, nullptr);
ret = mpp_init(m_mppCtx, MPP_CTX_ENC, coding);
if (ret != MPP_SUCCESS)
{
LOG_ERROR("FFMediaPusher: mpp_init fail %d\n", ret);
return ERR_CODE(DEV_OPEN_ERR);
}
// 2) 创建编码器配置
MppEncCfg cfg = nullptr;
ret = mpp_enc_cfg_init(&cfg);
if (ret != MPP_SUCCESS || !cfg)
{
LOG_ERROR("FFMediaPusher: mpp_enc_cfg_init fail %d\n", ret);
return ERR_CODE(DEV_OPEN_ERR);
}
// prep 参数(输入帧格式)
mpp_enc_cfg_set_s32(cfg, "prep:width", static_cast<RK_S32>(m_encWidth));
mpp_enc_cfg_set_s32(cfg, "prep:height", static_cast<RK_S32>(m_encHeight));
mpp_enc_cfg_set_s32(cfg, "prep:hor_stride", static_cast<RK_S32>(m_encHorStride));
mpp_enc_cfg_set_s32(cfg, "prep:ver_stride", static_cast<RK_S32>(m_encVerStride));
mpp_enc_cfg_set_s32(cfg, "prep:format", static_cast<RK_S32>(MPP_FMT_YUV420SP));
// rc 参数(码率控制)
MppEncRcMode rcMode = (m_config.rcMode == EFFRcMode::CBR)
? MPP_ENC_RC_MODE_CBR : MPP_ENC_RC_MODE_VBR;
const RK_S32 bpsTarget = static_cast<RK_S32>(m_config.bitrateKbps) * 1000;
// 码率上下界:尽量减少网络带宽 —— CBR 收紧到 ±6%(严格守住预算,避免超发);
// VBR 放开下界(简单画面省码率),上界仍限制在 +6% 防止突发占满带宽。
RK_S32 bpsMax, bpsMin;
if (rcMode == MPP_ENC_RC_MODE_CBR)
{
bpsMax = bpsTarget * 17 / 16; // +6.25%
bpsMin = bpsTarget * 15 / 16; // -6.25%
}
else // VBR
{
bpsMax = bpsTarget * 17 / 16; // +6.25%
bpsMin = bpsTarget * 1 / 16; // 简单画面大幅降码率
}
mpp_enc_cfg_set_s32(cfg, "rc:mode", static_cast<RK_S32>(rcMode));
mpp_enc_cfg_set_s32(cfg, "rc:bps_target", bpsTarget);
mpp_enc_cfg_set_s32(cfg, "rc:bps_max", bpsMax);
mpp_enc_cfg_set_s32(cfg, "rc:bps_min", bpsMin);
mpp_enc_cfg_set_s32(cfg, "rc:fps_in_num", static_cast<RK_S32>(m_config.fps));
mpp_enc_cfg_set_s32(cfg, "rc:fps_in_denom", 1);
mpp_enc_cfg_set_s32(cfg, "rc:fps_out_num", static_cast<RK_S32>(m_config.fps));
mpp_enc_cfg_set_s32(cfg, "rc:fps_out_denom", 1);
mpp_enc_cfg_set_s32(cfg, "rc:gop", static_cast<RK_S32>(m_config.gop));
// 注:本 MPP 构建不支持 "rc:quality" 配置项(会报 invalid),CBR 下也不需要
// codec 参数
mpp_enc_cfg_set_s32(cfg, "codec:type", static_cast<RK_S32>(coding));
if (coding == MPP_VIDEO_CodingAVC)
{
// H.264: High Profile, Level 4.2(1080p@60 兼容)
mpp_enc_cfg_set_s32(cfg, "h264:profile", 100);
mpp_enc_cfg_set_s32(cfg, "h264:level", 42);
}
else if (coding == MPP_VIDEO_CodingHEVC)
{
// H.265: Main Profile, Level 5.1
mpp_enc_cfg_set_s32(cfg, "h265:profile", 0); // Main
mpp_enc_cfg_set_s32(cfg, "h265:level", 51);
}
// 3) 应用编码器配置
ret = m_mppApi->control(m_mppCtx, MPP_ENC_SET_CFG, cfg);
mpp_enc_cfg_deinit(cfg);
if (ret != MPP_SUCCESS)
{
LOG_ERROR("FFMediaPusher: MPP_ENC_SET_CFG fail %d\n", ret);
return ERR_CODE(DEV_OPEN_ERR);
}
// 3.1) 每个 IDR 帧都内嵌 SPS/PPS(in-band header),
// 这样 VLC / ffplay 等客户端中途接入时能立即拿到参数集开始解码,
// 不依赖 SDP(RTSP listen 模式下对兼容性很关键)。仅对 H.264/H.265 有意义。
if (coding == MPP_VIDEO_CodingAVC || coding == MPP_VIDEO_CodingHEVC)
{
MppEncHeaderMode headerMode = MPP_ENC_HEADER_MODE_EACH_IDR;
m_mppApi->control(m_mppCtx, MPP_ENC_SET_HEADER_MODE, &headerMode);
}
// 4) 创建 MppBufferGroup(用于分配编码输入帧缓冲区)
ret = mpp_buffer_group_get_internal(&m_mppBufGroup, MPP_BUFFER_TYPE_DRM);
if (ret != MPP_SUCCESS)
{
LOG_ERROR("FFMediaPusher: buffer group get fail %d\n", ret);
return ERR_CODE(DEV_OPEN_ERR);
}
return SUCCESS;
}
// ---- RTSP 输出初始化 ----
int CVrFFMediaPusher::initRtspOutput()
{
// 1) 构建 RTSP URL: 主动推流到 mediamtx (rtsp://{ip}:{port}{path})
// 不再使用 0.0.0.0:listen 模式,而是推到 mediamtx 的接收端口
char url[256];
snprintf(url, sizeof(url), "rtsp://%s:%u%s",
m_config.rtspHost.empty() ? "127.0.0.1" : m_config.rtspHost.c_str(),
m_config.rtspPort, m_config.rtspPath.c_str());
// 2) 创建 RTSP 输出上下文
int ret = avformat_alloc_output_context2(&m_fmtCtx, nullptr, "rtsp", url);
if (ret < 0 || !m_fmtCtx)
{
LOG_ERROR("FFMediaPusher: avformat_alloc_output_context2 fail %d\n", ret);
return ERR_CODE(DEV_OPEN_ERR);
}
// 3) 创建视频流
AVStream* stream = avformat_new_stream(m_fmtCtx, nullptr);
if (!stream)
{
LOG_ERROR("FFMediaPusher: avformat_new_stream fail\n");
return ERR_CODE(DEV_OPEN_ERR);
}
stream->id = 0;
// 4) 设置 codecpar(从 MPP 编码器获取 extradata SPS/PPS)
AVCodecParameters* codecpar = stream->codecpar;
MppCodingType coding = ToMppCoding(m_config.encodeType);
codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
if (coding == MPP_VIDEO_CodingAVC)
codecpar->codec_id = AV_CODEC_ID_H264;
else if (coding == MPP_VIDEO_CodingHEVC)
codecpar->codec_id = AV_CODEC_ID_H265;
else
codecpar->codec_id = AV_CODEC_ID_MJPEG;
codecpar->width = m_encWidth;
codecpar->height = m_encHeight;
// 设置流时基为微秒(PushFrame 以微秒传入 PTS,写帧时再 rescale 到 muxer 实际时基)
stream->time_base = AVRational{1, 1000000};
stream->avg_frame_rate = AVRational{static_cast<int>(m_config.fps), 1};
// 获取 SPS/PPS 头:MPP_ENC_GET_HDR_SYNC 需要传入【已分配缓冲区】的 MppPacket,
// 直接传 &nullptr 会触发 mpp_packet_copy invalid input,extradata 取不到。
char hdrBuf[1024];
MppPacket hdrPkt = nullptr;
if (mpp_packet_init(&hdrPkt, hdrBuf, sizeof(hdrBuf)) == MPP_OK && hdrPkt)
{
mpp_packet_set_length(hdrPkt, 0);
ret = m_mppApi->control(m_mppCtx, MPP_ENC_GET_HDR_SYNC, hdrPkt);
if (ret == MPP_SUCCESS)
{
void* hdrData = mpp_packet_get_data(hdrPkt);
size_t hdrSize = mpp_packet_get_length(hdrPkt);
if (hdrData && hdrSize > 0)
{
codecpar->extradata = static_cast<uint8_t*>(av_mallocz(hdrSize + AV_INPUT_BUFFER_PADDING_SIZE));
if (codecpar->extradata)
{
memcpy(codecpar->extradata, hdrData, hdrSize);
codecpar->extradata_size = static_cast<int>(hdrSize);
}
}
LOG_INFO("FFMediaPusher: got extradata %d bytes\n",
static_cast<int>(codecpar->extradata_size));
}
else
{
LOG_WARN("FFMediaPusher: MPP_ENC_GET_HDR_SYNC fail %d\n", ret);
}
mpp_packet_deinit(&hdrPkt); // 外部缓冲区 hdrBuf 不会被释放(栈内存)
}
return SUCCESS;
}
// ---- 格式转换初始化 ----
int CVrFFMediaPusher::initSwscale()
{
m_needConvert = (m_config.pixelFormat != EFFPushPixelFormat::NV12);
if (!m_needConvert)
return SUCCESS;
int srcW = static_cast<int>(m_config.width);
int srcH = static_cast<int>(m_config.height);
int srcFmt = ToAvPixelFormat(m_config.pixelFormat);
int dstW = static_cast<int>(m_encWidth);
int dstH = static_cast<int>(m_encHeight);
m_swsCtx = sws_getContext(srcW, srcH, static_cast<AVPixelFormat>(srcFmt),
dstW, dstH, AV_PIX_FMT_NV12,
SWS_BILINEAR, nullptr, nullptr, nullptr);
if (!m_swsCtx)
{
LOG_ERROR("FFMediaPusher: sws_getContext fail\n");
return ERR_CODE(DEV_OPEN_ERR);
}
LOG_INFO("FFMediaPusher: swscale enabled (pix %d → NV12, %dx%d → %dx%d)\n",
srcFmt, srcW, srcH, dstW, dstH);
return SUCCESS;
}
// ---- 主流程 ----
int CVrFFMediaPusher::Init(const VrFFPushConfig& config)
{
std::lock_guard<std::mutex> lk(m_mutex);
if (m_bInited.load()) return SUCCESS;
m_config = config;
// 计算编码器 stride(16 对齐)
m_encWidth = static_cast<int>(config.width);
m_encHeight = static_cast<int>(config.height);
m_encHorStride = static_cast<int>(AlignTo(config.width, 16));
m_encVerStride = static_cast<int>(AlignTo(config.height, 16));
// 初始化 FFmpeg 网络 + 降低 FFmpeg 日志噪声(仅 ERROR)
avformat_network_init();
av_log_set_level(AV_LOG_ERROR);
// 初始化 MPP 编码器
int ret = initMppEncoder();
if (ret != SUCCESS)
{
LOG_ERROR("FFMediaPusher: initMppEncoder fail\n");
UnInit();
return ret;
}
// 初始化 RTSP 输出
ret = initRtspOutput();
if (ret != SUCCESS)
{
LOG_ERROR("FFMediaPusher: initRtspOutput fail\n");
UnInit();
return ret;
}
// 初始化格式转换(如需要)
ret = initSwscale();
if (ret != SUCCESS)
{
LOG_ERROR("FFMediaPusher: initSwscale fail\n");
UnInit();
return ret;
}
m_bInited = true;
LOG_INFO("FFMediaPusher: init done (%dx%d, stride=%dx%d, H%d, %ukbps, GOP=%d)\n",
m_encWidth, m_encHeight, m_encHorStride, m_encVerStride,
static_cast<int>(config.encodeType), config.bitrateKbps, config.gop);
return SUCCESS;
}
int CVrFFMediaPusher::Start()
{
if (!m_bInited.load()) return ERR_CODE(DEV_NO_OPEN);
if (m_bStarted.load()) return SUCCESS;
// 主动推流到 mediamtx:直接调用 avformat_write_header(不再开线程等 listen)
AVDictionary* opts = nullptr;
av_dict_set(&opts, "rtsp_transport", "tcp", 0);
int ret = avformat_write_header(m_fmtCtx, &opts);
av_dict_free(&opts);
if (ret < 0)
{
LOG_ERROR("FFMediaPusher: avformat_write_header fail %d (mediamtx 未运行或端口不对?)\n", ret);
return ERR_CODE(DEV_OPEN_ERR);
}
m_headerWritten = true;
LOG_INFO("FFMediaPusher: connected to mediamtx, header written\n");
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 (!data || size == 0)
return ERR_CODE(DEV_ARG_INVAILD);
// 生成 PTS
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;
// 1) 分配 MPP 输入缓冲区(hor_stride × ver_stride 对齐布局),直接把数据写进去
const int frameSize = m_encHorStride * m_encVerStride * 3 / 2;
MppBuffer mppBuf = nullptr;
MPP_RET mret = mpp_buffer_get(m_mppBufGroup, &mppBuf, frameSize);
if (mret != MPP_SUCCESS || !mppBuf)
{
LOG_ERROR("FFMediaPusher: mpp_buffer_get fail %d\n", mret);
return ERR_CODE(DEV_CTRL_ERR);
}
uint8_t* dst = static_cast<uint8_t*>(mpp_buffer_get_ptr(mppBuf));
if (!dst)
{
mpp_buffer_put(mppBuf);
return ERR_CODE(DEV_CTRL_ERR);
}
uint8_t* dstY = dst; // Y 平面
uint8_t* dstUV = dst + m_encHorStride * m_encVerStride; // UV 平面(按 ver_stride 偏移)
if (m_needConvert && m_swsCtx)
{
// BGR/RGB/RGBA 等打包格式 → NV12,直接转换写入 MppBuffer
int srcStride = 0;
switch (m_config.pixelFormat)
{
case EFFPushPixelFormat::BGR24:
case EFFPushPixelFormat::RGB24: srcStride = m_encWidth * 3; break;
case EFFPushPixelFormat::BGRA32:
case EFFPushPixelFormat::RGBA32: srcStride = m_encWidth * 4; break;
case EFFPushPixelFormat::YUV420P: srcStride = m_encWidth; break;
default: srcStride = m_encWidth * 3; break;
}
uint8_t* srcSlice[4] = { const_cast<uint8_t*>(static_cast<const uint8_t*>(data)),
nullptr, nullptr, nullptr };
int srcLinesize[4] = { srcStride, 0, 0, 0 };
uint8_t* dstData[4] = { dstY, dstUV, nullptr, nullptr };
int dstLinesize[4] = { m_encHorStride, m_encHorStride, 0, 0 };
sws_scale(m_swsCtx, srcSlice, srcLinesize, 0, m_encHeight, dstData, dstLinesize);
}
else
{
// 输入为 NV12(紧凑排列,stride = width),按 hor_stride/ver_stride 逐行重排
const size_t needBytes = static_cast<size_t>(m_encWidth) * m_encHeight * 3 / 2;
if (size < needBytes)
{
mpp_buffer_put(mppBuf);
LOG_DEBUG("FFMediaPusher: NV12 input too small %zu < %zu\n", size, needBytes);
return ERR_CODE(DEV_ARG_INVAILD);
}
const uint8_t* src = static_cast<const uint8_t*>(data);
const int srcLn = m_encWidth; // 源 NV12 行宽 = width
const int copyW = std::min(srcLn, m_encHorStride);
// Y
for (int y = 0; y < m_encHeight; ++y)
memcpy(dstY + static_cast<size_t>(y) * m_encHorStride,
src + static_cast<size_t>(y) * srcLn, copyW);
// UV(高度为 height/2)
const uint8_t* srcUV = src + static_cast<size_t>(srcLn) * m_encHeight;
for (int y = 0; y < m_encHeight / 2; ++y)
memcpy(dstUV + static_cast<size_t>(y) * m_encHorStride,
srcUV + static_cast<size_t>(y) * srcLn, copyW);
}
// 2) 构建 MppFrame
MppFrame frame = nullptr;
mpp_frame_init(&frame);
mpp_frame_set_width(frame, m_encWidth);
mpp_frame_set_height(frame, m_encHeight);
mpp_frame_set_hor_stride(frame, m_encHorStride);
mpp_frame_set_ver_stride(frame, m_encVerStride);
mpp_frame_set_fmt(frame, MPP_FMT_YUV420SP);
mpp_frame_set_buffer(frame, mppBuf);
mpp_frame_set_pts(frame, ptsUs);
// 3) 同步编码
MppPacket packet = nullptr;
mret = m_mppApi->encode(m_mppCtx, frame, &packet);
mpp_frame_deinit(&frame);
mpp_buffer_put(mppBuf);
if (mret != MPP_SUCCESS || !packet)
{
LOG_DEBUG("FFMediaPusher: encode fail %d\n", mret);
return ERR_CODE(DEV_CTRL_ERR);
}
void* pktData = mpp_packet_get_data(packet);
size_t pktSize = mpp_packet_get_length(packet);
if (!pktData || pktSize == 0)
{
mpp_packet_deinit(&packet);
return SUCCESS; // 编码器可能吞帧(如缓冲未就绪),正常情况
}
// 4) 写入 RTSP(独立 AVPacket,pts rescale 到 muxer 时基)
AVPacket* avpkt = av_packet_alloc();
if (!avpkt)
{
mpp_packet_deinit(&packet);
return ERR_CODE(DEV_CTRL_ERR);
}
avpkt->data = static_cast<uint8_t*>(pktData);
avpkt->size = static_cast<int>(pktSize);
avpkt->stream_index = 0;
avpkt->pts = ptsUs;
avpkt->dts = ptsUs;
// 判断关键帧(I/IDR 帧)
if (pktSize >= 5)
{
const uint8_t* p = static_cast<const uint8_t*>(pktData);
size_t offset = 0;
if (p[0] == 0 && p[1] == 0) {
if (p[2] == 1) offset = 3;
else if (p[2] == 0 && p[3] == 1) offset = 4;
}
if (offset < pktSize) {
MppCodingType coding = ToMppCoding(m_config.encodeType);
if (coding == MPP_VIDEO_CodingAVC) {
int nalType = p[offset] & 0x1F;
if (nalType == 5 || nalType == 7 || nalType == 8) // IDR / SPS / PPS
avpkt->flags |= AV_PKT_FLAG_KEY;
} else if (coding == MPP_VIDEO_CodingHEVC) {
int nalType = (p[offset] >> 1) & 0x3F; // HEVC: bit1~6
if ((nalType >= 16 && nalType <= 23) || nalType == 32 || nalType == 33 || nalType == 34)
avpkt->flags |= AV_PKT_FLAG_KEY; // IRAP / VPS / SPS / PPS
}
}
}
int aret;
{
std::lock_guard<std::mutex> wlk(m_writeMutex);
if (m_fmtCtx && m_headerWritten.load())
{
// 微秒 → muxer 实际时基
av_packet_rescale_ts(avpkt, AVRational{1, 1000000},
m_fmtCtx->streams[0]->time_base);
aret = av_interleaved_write_frame(m_fmtCtx, avpkt);
}
else
{
aret = 0; // 已停止,丢弃本帧
}
}
av_packet_free(&avpkt);
mpp_packet_deinit(&packet);
if (aret < 0)
{
LOG_DEBUG("FFMediaPusher: av_interleaved_write_frame fail %d\n", aret);
return ERR_CODE(DEV_CTRL_ERR);
}
return SUCCESS;
}
int CVrFFMediaPusher::Stop()
{
if (!m_bStarted.load()) return SUCCESS;
// 先置 started=false,使并发的 PushFrame 尽快早退
m_bStarted = false;
// 主动推流模式无 accept 线程,直接发送 trailer(与 PushFrame 写帧互斥)
{
std::lock_guard<std::mutex> wlk(m_writeMutex);
if (m_headerWritten.load() && m_fmtCtx)
{
av_write_trailer(m_fmtCtx); // RTSP muxer 为 AVFMT_NOFILE,内部自动关闭连接
}
m_headerWritten = false;
}
// 复位 MPP 编码器
if (m_mppApi && m_mppCtx)
{
m_mppApi->reset(m_mppCtx);
}
return SUCCESS;
}
int CVrFFMediaPusher::UnInit()
{
Stop();
std::lock_guard<std::mutex> lk(m_mutex);
// 释放 RTSP 输出上下文
if (m_fmtCtx)
{
avformat_free_context(m_fmtCtx);
m_fmtCtx = nullptr;
}
// 销毁 MPP 编码器上下文
if (m_mppCtx)
{
mpp_destroy(m_mppCtx);
m_mppCtx = nullptr;
m_mppApi = nullptr;
}
// 销毁 MPP buffer group
if (m_mppBufGroup)
{
mpp_buffer_group_put(m_mppBufGroup);
m_mppBufGroup = nullptr;
}
// 释放 swscale 上下文
if (m_swsCtx)
{
sws_freeContext(m_swsCtx);
m_swsCtx = nullptr;
}
// 关闭 FFmpeg 网络
avformat_network_deinit();
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);
}