GrabBag/Module/FFMediaStream/Src/CVrFFMediaPusher.cpp
2026-06-14 17:01:09 +08:00

784 lines
26 KiB
C++
Raw Permalink 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>
#include <limits>
#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);
RK_S64 timeout = 1000;
ret = m_mppApi->control(m_mppCtx, MPP_SET_INPUT_TIMEOUT, &timeout);
if (ret != MPP_SUCCESS)
LOG_WARN("FFMediaPusher: MPP_SET_INPUT_TIMEOUT fail %d\n", ret);
ret = m_mppApi->control(m_mppCtx, MPP_SET_OUTPUT_TIMEOUT, &timeout);
if (ret != MPP_SUCCESS)
LOG_WARN("FFMediaPusher: MPP_SET_OUTPUT_TIMEOUT fail %d\n", ret);
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;
}
// ---- RTMP 输出初始化 ----
int CVrFFMediaPusher::initRtspOutput()
{
// 1) 构建 RTMP URL: 主动推流到 mediamtx (rtmp://{ip}:{port}{path})
// RTMP 比 RTSP 更稳定,避免 interleaved frame 等兼容性问题
char url[256];
snprintf(url, sizeof(url), "rtmp://%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) 创建 RTMP 输出上下文(格式改为 flvRTMP 基于 FLV 容器)
int ret = avformat_alloc_output_context2(&m_fmtCtx, nullptr, "flv", 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;
codecpar->format = AV_PIX_FMT_YUV420P; // NV12 对应的 FFmpeg 格式
codecpar->bit_rate = static_cast<int64_t>(m_config.bitrateKbps) * 1000; // RTMP muxer 需要知道码率
// 设置流时基为微秒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;
// RTMP 需要先打开 IORTSP 不需要,但 RTMP/FLV 必须)
if (!(m_fmtCtx->oformat->flags & AVFMT_NOFILE))
{
int ret = avio_open(&m_fmtCtx->pb, m_fmtCtx->url, AVIO_FLAG_WRITE);
if (ret < 0)
{
LOG_ERROR("FFMediaPusher: avio_open fail %d url=%s\n", ret, m_fmtCtx->url);
return ERR_CODE(DEV_OPEN_ERR);
}
}
// 写 RTMP header
int ret = avformat_write_header(m_fmtCtx, nullptr);
if (ret < 0)
{
LOG_ERROR("FFMediaPusher: avformat_write_header fail %d (mediamtx 未运行或端口不对?)\n", ret);
if (m_fmtCtx->pb)
avio_closep(&m_fmtCtx->pb);
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由 DroneScrewServerPresenter 按帧计数递增生成)
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);
}
mpp_buffer_sync_begin(mppBuf);
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);
}
mpp_buffer_sync_end(mppBuf);
// 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) 编码。H.264/H.265 在当前 MPP 版本上需要显式提供输出 packet buffer
// 否则 encode() 可能持续返回 success + null packetRTMP 只有 header 没有媒体数据。
MppPacket packet = nullptr;
MppPacket outPacket = nullptr;
MppBuffer pktBuf = nullptr;
const size_t pktBufSize = static_cast<size_t>(frameSize);
mret = mpp_buffer_get(m_mppBufGroup, &pktBuf, pktBufSize);
if (mret != MPP_SUCCESS || !pktBuf)
{
mpp_frame_deinit(&frame);
mpp_buffer_put(mppBuf);
LOG_ERROR("FFMediaPusher: packet buffer get fail %d\n", mret);
return ERR_CODE(DEV_CTRL_ERR);
}
mret = mpp_packet_init_with_buffer(&packet, pktBuf);
if (mret != MPP_SUCCESS || !packet)
{
mpp_frame_deinit(&frame);
mpp_buffer_put(mppBuf);
mpp_buffer_put(pktBuf);
LOG_ERROR("FFMediaPusher: packet init fail %d\n", mret);
return ERR_CODE(DEV_CTRL_ERR);
}
mpp_packet_set_length(packet, 0);
mpp_packet_set_pos(packet, mpp_packet_get_data(packet));
MppMeta meta = mpp_frame_get_meta(frame);
if (meta)
mpp_meta_set_packet(meta, KEY_OUTPUT_PACKET, packet);
mret = m_mppApi->encode_put_frame(m_mppCtx, frame);
mpp_frame_deinit(&frame);
mpp_buffer_put(mppBuf);
if (mret != MPP_SUCCESS)
{
mpp_packet_deinit(&packet);
mpp_buffer_put(pktBuf);
LOG_DEBUG("FFMediaPusher: encode_put_frame fail %d\n", mret);
return ERR_CODE(DEV_CTRL_ERR);
}
mret = m_mppApi->encode_get_packet(m_mppCtx, &outPacket);
if (mret != MPP_SUCCESS)
{
mpp_packet_deinit(&packet);
mpp_buffer_put(pktBuf);
LOG_DEBUG("FFMediaPusher: encode_get_packet fail %d\n", mret);
return ERR_CODE(DEV_CTRL_ERR);
}
MppPacket encodedPacket = outPacket ? outPacket : packet;
// 编码器预热期可能无输出,跳过本帧(返回 SUCCESS不算失败
if (!encodedPacket || mpp_packet_get_length(encodedPacket) == 0)
{
mpp_packet_deinit(&packet);
mpp_buffer_put(pktBuf);
LOG_DEBUG("FFMediaPusher: encode produced no packet (warmup)\n");
return SUCCESS; // 跳过,不算失败
}
MppBuffer encodedBuf = mpp_packet_get_buffer(encodedPacket);
if (encodedBuf)
mpp_buffer_sync_ro_begin(encodedBuf);
auto releaseEncodedPacket = [&]() {
if (encodedBuf)
{
mpp_buffer_sync_ro_end(encodedBuf);
encodedBuf = nullptr;
}
if (outPacket && outPacket != packet)
mpp_packet_deinit(&outPacket);
if (packet)
mpp_packet_deinit(&packet);
if (pktBuf)
{
mpp_buffer_put(pktBuf);
pktBuf = nullptr;
}
};
void* pktData = mpp_packet_get_pos(encodedPacket);
if (!pktData)
pktData = mpp_packet_get_data(encodedPacket);
size_t pktSize = mpp_packet_get_length(encodedPacket);
// 双重检查:确保有效数据(理论上前面已检查过,这里是保险)
if (!pktData || pktSize == 0)
{
releaseEncodedPacket();
LOG_DEBUG("FFMediaPusher: encode produced empty packet data\n");
return SUCCESS; // 跳过,不算失败
}
// 4) 写入 RTSP独立 AVPacketpts rescale 到 muxer 时基)
AVPacket* avpkt = av_packet_alloc();
if (!avpkt)
{
releaseEncodedPacket();
return ERR_CODE(DEV_CTRL_ERR);
}
if (pktSize > static_cast<size_t>(std::numeric_limits<int>::max()) ||
av_new_packet(avpkt, static_cast<int>(pktSize)) < 0)
{
av_packet_free(&avpkt);
releaseEncodedPacket();
return ERR_CODE(DEV_CTRL_ERR);
}
memcpy(avpkt->data, pktData, pktSize);
avpkt->stream_index = 0;
avpkt->pts = ptsUs;
avpkt->dts = ptsUs;
avpkt->duration = m_config.fps > 0
? static_cast<int64_t>(1000000 / m_config.fps)
: 0;
// 判断关键帧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
}
}
}
releaseEncodedPacket();
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); // 使用 interleaved处理时间戳排序
// RTMP 推流需要强制刷新缓冲区,否则数据会积压不发送
if (aret >= 0 && m_fmtCtx->pb)
{
avio_flush(m_fmtCtx->pb);
}
}
else
{
aret = 0; // 已停止,丢弃本帧
}
}
av_packet_free(&avpkt);
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;
// 发送 trailer 并关闭 IO与 PushFrame 写帧互斥)
{
std::lock_guard<std::mutex> wlk(m_writeMutex);
if (m_headerWritten.load() && m_fmtCtx)
{
av_write_trailer(m_fmtCtx);
// RTMP 需要手动关闭 IORTSP 不需要AVFMT_NOFILE
if (m_fmtCtx->pb)
{
avio_closep(&m_fmtCtx->pb);
}
}
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), "rtmp://%s:%u%s",
localIp.c_str(), m_config.rtspPort, m_config.rtspPath.c_str());
return std::string(buf);
}