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" 配置项(会报 invalidCBR 下也不需要
// 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.21080p@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/PPSin-band header
// 这样 VLC / ffplay 等客户端中途接入时能立即拿到参数集开始解码,
// 不依赖 SDPRTSP 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 inputextradata 取不到。
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;
// 计算编码器 stride16 对齐)
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独立 AVPacketpts 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);
}