GrabBag/Module/FFMediaStream/Src/CVrFFMediaPusher.cpp

788 lines
26 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>
#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" 配置项(会报 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;
}
// ---- 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 输出上下文(格式改为 flv,RTMP 基于 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);
}
m_fmtCtx->flags |= AVFMT_FLAG_FLUSH_PACKETS;
m_fmtCtx->max_delay = 0;
m_fmtCtx->max_interleave_delta = 0;
av_opt_set_int(m_fmtCtx, "flush_packets", 1, 0);
// 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 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;
// RTMP 需要先打开 IO(RTSP 不需要,但 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 packet,RTMP 只有 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 ERR_CODE(DEV_RESULT_EMPTY);
}
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 ERR_CODE(DEV_RESULT_EMPTY);
}
// 4) 写入 RTSP(独立 AVPacket,pts 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);
// RTMP 推流需要强制刷新缓冲区,否则数据会积压不发送
if (aret >= 0 && m_fmtCtx->pb)
{
avio_flush(m_fmtCtx->pb);
}
}
else
{
aret = AVERROR_EOF; // 已停止,丢弃本帧
}
}
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 需要手动关闭 IO(RTSP 不需要,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);
}