#include "CVrFFMediaPusher.h" #include "VrError.h" #include "VrLog.h" #include #include #include #include #if !defined(FFMEDIA_PLATFORM_DUMMY) #include #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 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(m_encWidth)); mpp_enc_cfg_set_s32(cfg, "prep:height", static_cast(m_encHeight)); mpp_enc_cfg_set_s32(cfg, "prep:hor_stride", static_cast(m_encHorStride)); mpp_enc_cfg_set_s32(cfg, "prep:ver_stride", static_cast(m_encVerStride)); mpp_enc_cfg_set_s32(cfg, "prep:format", static_cast(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(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(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(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(m_config.fps)); mpp_enc_cfg_set_s32(cfg, "rc:fps_out_denom", 1); mpp_enc_cfg_set_s32(cfg, "rc:gop", static_cast(m_config.gop)); // 注:本 MPP 构建不支持 "rc:quality" 配置项(会报 invalid),CBR 下也不需要 // codec 参数 mpp_enc_cfg_set_s32(cfg, "codec:type", static_cast(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(m_config.bitrateKbps) * 1000; // RTMP muxer 需要知道码率 // 设置流时基为微秒(PushFrame 以微秒传入 PTS,写帧时再 rescale 到 muxer 实际时基) stream->time_base = AVRational{1, 1000000}; stream->avg_frame_rate = AVRational{static_cast(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(av_mallocz(hdrSize + AV_INPUT_BUFFER_PADDING_SIZE)); if (codecpar->extradata) { memcpy(codecpar->extradata, hdrData, hdrSize); codecpar->extradata_size = static_cast(hdrSize); } } LOG_INFO("FFMediaPusher: got extradata %d bytes\n", static_cast(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(m_config.width); int srcH = static_cast(m_config.height); int srcFmt = ToAvPixelFormat(m_config.pixelFormat); int dstW = static_cast(m_encWidth); int dstH = static_cast(m_encHeight); m_swsCtx = sws_getContext(srcW, srcH, static_cast(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 lk(m_mutex); if (m_bInited.load()) return SUCCESS; m_config = config; // 计算编码器 stride(16 对齐) m_encWidth = static_cast(config.width); m_encHeight = static_cast(config.height); m_encHorStride = static_cast(AlignTo(config.width, 16)); m_encVerStride = static_cast(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(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(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(static_cast(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(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(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(y) * m_encHorStride, src + static_cast(y) * srcLn, copyW); // UV(高度为 height/2) const uint8_t* srcUV = src + static_cast(srcLn) * m_encHeight; for (int y = 0; y < m_encHeight / 2; ++y) memcpy(dstUV + static_cast(y) * m_encHorStride, srcUV + static_cast(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(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(std::numeric_limits::max()) || av_new_packet(avpkt, static_cast(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(1000000 / m_config.fps) : 0; // 判断关键帧(I/IDR 帧) if (pktSize >= 5) { const uint8_t* p = static_cast(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 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 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 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); }