qt+vlc实现国标客户端测试工具

一 概述

本文章实现了一个国标视频播放客户端.支持IPC相机通过GB28181协议获取的视频流到客户端,将获取的PS流解析为h264/h265流,通过vlc解码后显示到界面.

二 代码架构

该客户端由两部分组成:1.GB28181Player 界面模块 ; 2.GB28181Agent 播放引擎.

GB28181Player模块实现了客户端界面布局和功能操作;

GB28181Agent模块实现的功能有:

1.实现osip服务端;

2.实现udp服务端;

3.获取注册相机;

4.播放/关闭相机.

客户端界面如下:

三 关键代码

1.GB28181Agent对外接口

gbplayerengine.h

cpp 复制代码
#ifndef GBPLAYERENGINE_H
#define GBPLAYERENGINE_H

#ifdef __cplusplus
extern "C" {
#endif

#if defined(_WIN32) || defined(WIN32)
#  define GB_API __declspec(dllexport)
#else
#  define GB_API __attribute__((visibility("default")))
#endif

#pragma pack(push, 1)

// 相机信息
typedef struct
{
    char    deviceId[64];
    char    deviceName[64];
    char    ipAddr[32];
    int     cid;// call id
    int     did;//dialog id
    int     port;
    int     status;     // 0=离线 1=在线
    int     playStatus; // 0=未播放 1=播放中
    int     channelId;
    char    reserve[128];
} GB_CameraInfo;

// 相机列表
typedef struct
{
    int count;
    GB_CameraInfo cameras[128];
} GB_CameraList;

// 播放参数
typedef struct
{
    char    deviceId[64];
    char    devIp[32];
    int     cid;//给IPC发送请求媒体流成功后的call id
    int     did;
    void*   renderWnd;
    int     transportType;
    int     cacheTime;
    int     enableAudio;
    int     port;
    char    reserve[256];
} GB_PlayParam;

#pragma pack(pop)

// 引擎初始化(启动OSIP + UDP服务)
GB_API int GB_InitEngine(const char* localId,const char* localIp, int localSipPort, int localUdpMediaPort);

// 反初始化
GB_API int GB_UnInitEngine();

// 1. 获取已注册相机列表
GB_API int GB_GetRegisteredCameraList(GB_CameraList* pOutList);

// 2. 播放指定相机
GB_API int GB_StartPlay(GB_PlayParam* pParam);

// 3. 关闭指定相机
GB_API int GB_StopPlay(GB_PlayParam* pParam);

// 工具
GB_API const char* GB_GetVersion();
GB_API const char* GB_GetLastError();

#ifdef __cplusplus
}
#endif

#endif // GBPLAYERENGINE_H

gbplayerengine.cpp

cpp 复制代码
#include "gbplayerengine.h"
#include "gbsipserver.h"
#include "gbudpmediaserver.h"
#include "gbcameramanager.h"

static char g_lastError[256] = {0};
GlobalData g_globalData;

INITIALIZE_EASYLOGGINGPP
void initializeLogging() {
    // 加载配置文件
     el::Loggers::configureFromGlobal("global.conf");
}
GB_API int GB_InitEngine(const char* localId,
                         const char* localIp,
                         int localSipPort,
                         int localUdpMediaPort)
{
    if (!localIp) return -1;

    // 初始化相机管理器
    GBCameraManager::getInstance();
    g_globalData.local_sipId = localId;
    g_globalData.localIp = localIp;
    g_globalData.localSipPort = localSipPort;
    g_globalData.localUdpMediaPort = localUdpMediaPort;
    // 启动OSIP服务
    if (!GBSipServer::getInstance()->startServer(localIp, localSipPort)) {
        sprintf(g_lastError, "SIP服务启动失败");
        LOG(ERROR) << "SIP服务启动失败";
        return -2;
    }

    // 启动UDP媒体服务
    if (!GBUdpMediaServer::getInstance()->startServer(localIp, localUdpMediaPort)) {
        sprintf(g_lastError, "UDP媒体服务启动失败");
        LOG(ERROR) << "UDP媒体服务启动失败";
        return -3;
    }

    sprintf(g_lastError, "初始化成功");
    LOG(INFO) << "SIP服务 UDP服务 初始化成功";
    return 0;
}

GB_API int GB_UnInitEngine()
{
    GBSipServer::getInstance()->stopServer();
    GBUdpMediaServer::getInstance()->stopServer();
    GBCameraManager::getInstance()->clearAll();
    sprintf(g_lastError, "反初始化成功");
    return 0;
}

GB_API int GB_GetRegisteredCameraList(GB_CameraList* pOutList)
{
    if (!pOutList) return -1;
    return GBCameraManager::getInstance()->getCameraList(pOutList);
}

GB_API int GB_StartPlay(GB_PlayParam* pParam)
{
    if (!pParam) return -1;
    return GBCameraManager::getInstance()->startPlay(pParam);
}

GB_API int GB_StopPlay(GB_PlayParam* pParam)
{
    if (!pParam) return -1;
    return GBCameraManager::getInstance()->stopPlay(pParam->deviceId);
}

GB_API const char* GB_GetVersion() {
    return "GB28181 Player Engine v1.0";
}

GB_API const char* GB_GetLastError() {
    return g_lastError;
}

2.udp服务类

gbudpmediaserver.h

cpp 复制代码
#ifndef GBUDPMEDIASERVER_H
#define GBUDPMEDIASERVER_H

#include <QObject>
#include <QUdpSocket>
#include "gbcameramanager.h"
#include "PsParser.h"
class GBUdpMediaServer : public QObject
{
    Q_OBJECT
public:
    static GBUdpMediaServer* getInstance();
    bool startServer(const QString& ip, int port);
    void stopServer();

private slots:
    void onReadData();

private:
    explicit GBUdpMediaServer(QObject *parent = nullptr);
    static GBUdpMediaServer* instance;
    QUdpSocket* m_socket = nullptr;
    bool m_running = false;

};

#endif // GBUDPMEDIASERVER_H

gbudpmediaserver.cpp

cpp 复制代码
#include "gbudpmediaserver.h"

GBUdpMediaServer* GBUdpMediaServer::instance = nullptr;

GBUdpMediaServer* GBUdpMediaServer::getInstance()
{
    if (!instance) instance = new GBUdpMediaServer;
    return instance;
}

GBUdpMediaServer::GBUdpMediaServer(QObject *parent) : QObject(parent) {}

bool GBUdpMediaServer::startServer(const QString& ip, int port)
{
    if (m_running) return true;
    m_socket = new QUdpSocket(this);
    bool ok = m_socket->bind(QHostAddress(ip), port, QUdpSocket::ShareAddress);
    m_running = ok;
    connect(m_socket, &QUdpSocket::readyRead, this, &GBUdpMediaServer::onReadData);
    return ok;
}

void GBUdpMediaServer::stopServer()
{
    if (m_socket) {
        m_socket->close();
        delete m_socket;
        m_socket = nullptr;
    }
    m_running = false;
}

void GBUdpMediaServer::onReadData()
{
    while (m_socket->hasPendingDatagrams()) {
        QByteArray data;
        data.resize(m_socket->pendingDatagramSize());
        QHostAddress clientAddr;
        quint16 clientPort;
        string ipaddr ;
        m_socket->readDatagram(data.data(), data.size(),&clientAddr,&clientPort);
        ipaddr = clientAddr.toString().toStdString();
        // 分发媒体流到对应相机播放会话
        GBCameraManager::getInstance()->inputStream(data.data(),data.size(),ipaddr);
    }
}

3.sip服务类

gbsipserver.h

cpp 复制代码
#ifndef GBSIPSERVER_H
#define GBSIPSERVER_H

#include <QObject>
#include <QString>
#include "basic.h"
#include "eXosip2/eXosip.h"
class GBSipServer : public QObject
{
    Q_OBJECT
public:
    static GBSipServer* getInstance();
    bool startServer(const QString& ip, int port, bool isUDP = true);
    void stopServer();
    /**
     * @brief send_invite_to_ipc 给IPC发送INVITE消息,请求视频流
     * @param ipc_ip
     * @param local_ip
     * @param rtp_port
     * @return
     */
    int send_invite_to_ipc(string ipc_ip, string ipc_id, int ipc_port);
    int stop_ipc_media_stream(int cid, int did);
private:
    static void exosip_event_loop(void *ctx);
    void osip_event_loop_impl();
    void handle_exosip_event(eXosip_event_t* event);
    void processMessage(eXosip_event_t *osipEvent);
    void parse_register_info(eXosip_event_t *e);
    void parse_message_info(eXosip_event_t *osipEvent);
private:
    explicit GBSipServer(QObject *parent = nullptr);
    static GBSipServer* instance;
    bool m_running = false;

    eXosip_t* m_excontext = nullptr;
};

#endif // GBSIPSERVER_H

gbsipserver.cpp

cpp 复制代码
#include "gbsipserver.h"
#include "gbcameramanager.h"
#include <QDebug>
GBSipServer* GBSipServer::instance = nullptr;
extern GlobalData g_globalData;

extern void initializeLogging();

int get_ipc_ip_from_event_400(eXosip_event_t *evt, char *ip_buf, int buf_len);
int get_ipc_media_ip_from_sdp(eXosip_event_t *evt, char *media_ip, char *sipId);
GBSipServer* GBSipServer::getInstance()
{
    if (!instance) instance = new GBSipServer;
    return instance;
}

GBSipServer::GBSipServer(QObject *parent) : QObject(parent)
{
   initializeLogging();
}

bool GBSipServer::startServer(const QString& ip, int port,bool isUDP)
{
    if (m_running) return true;
    m_running = true;
    // 1. 初始化eXosip上下文
    m_excontext = eXosip_malloc();
    int ret = eXosip_init(m_excontext);
    if (ret != 0) {
        std::cerr << "eXosip初始化失败:" << ret << std::endl;
        LOG(ERROR) << "这是默认日志记录器日志";
        return false;
    }

    // 2. 监听UDP端口(国标SIP默认5060)
    if(isUDP){
        ret = eXosip_listen_addr(m_excontext, IPPROTO_UDP, ip.toStdString().c_str(), port, AF_INET, 0);
        if (ret != 0) {
            std::cerr << "监听SIP端口失败:" << ret << std::endl;
            LOG(ERROR) << "监听SIP端口失败:" << ret;
            eXosip_quit(m_excontext);
            return -1;
        }
    }else{
        ret = eXosip_listen_addr(m_excontext, IPPROTO_TCP, ip.toStdString().c_str(), port, AF_INET, 0);
        if (ret != 0) {
            std::cerr << "监听SIP端口失败:" << ret << std::endl;
            LOG(ERROR) << "监听SIP端口失败:" << ret;
            eXosip_quit(m_excontext);
            return -1;
        }
    }

    std::cout << "SIP Server启动成功,监听:" << ip.toStdString() << ":" << port  <<": "<< (isUDP==true ?"UDP":"TCP") << std::endl;
    LOG(INFO) << "SIP Server启动成功,监听:" << ip.toStdString() << ":" << port  <<": "<< (isUDP==true ?"UDP":"TCP");

    std::thread event_thread(&GBSipServer::exosip_event_loop,this);
    event_thread.detach();

    return true;
}

void GBSipServer::stopServer()
{
    m_running = false;
}

int GBSipServer::send_invite_to_ipc(std::string ipc_ip, std::string ipc_id, int ipc_port)
{
    if (m_excontext == nullptr) {
        LOG(ERROR) << "【致命错误】m_excontext 是空指针!!!" << std::endl;
        return -1;
    }
    qDebug() << "【调试】m_excontext 指针有效:" << m_excontext;

    std::string to = "sip:"+ipc_id+"@"+ipc_ip+":"+to_string(ipc_port);
    std::string from = "sip:"+g_globalData.local_sipId+"@"+g_globalData.localIp+":"+to_string(g_globalData.localUdpMediaPort);

    // 1. 创建呼叫会话
    osip_message_t * invite = nullptr;
    int ret = eXosip_call_build_initial_invite(
                m_excontext,
                &invite,
                to.c_str(),
                from.c_str(),
                nullptr,
                nullptr
                );
    if (ret != 0) {
        std::cerr << "创建呼叫会话失败:" << ret << std::endl;
        return -1;
    }

    // 2. 构造SDP(核心:告诉IPC推流到你的IP和端口)
    char sdp[1024] = {0};
    snprintf(sdp, sizeof(sdp),
             "v=0\r\n"
             "o=%s 0 0 IN IP4 %s\r\n"
             "s=Play\r\n"
             "c=IN IP4 %s\r\n"
             "t=0 0\r\n"
             "m=video %d RTP/AVP 96\r\n"
             "a=rtpmap:96 PS/90000\r\n"
             "a=recvonly\r\n",
             g_globalData.local_sipId.c_str(), g_globalData.localIp.c_str(), g_globalData.localIp.c_str(), g_globalData.localUdpMediaPort);

    osip_message_set_body(invite, sdp, strlen(sdp));

    osip_message_set_content_type(invite, "application/sdp");

    // 3. 发送Invite请求
    eXosip_lock(m_excontext);
    ret = eXosip_call_send_initial_invite(m_excontext, invite);
    eXosip_unlock(m_excontext);
    if (ret > 0) {
        std::cout << "Invite请求发送成功,等待IPC响应..." << std::endl;
        qDebug() << "IPC:" << ipc_ip.data() <<" Invite请求发送成功,等待IPC响应... call id:" << ret;
    } else {
        qDebug() << "发送Invite失败:" << ret ;
        LOG(ERROR) <<"发送Invite失败:" << ret ;
    }
    return ret;
}

int GBSipServer::stop_ipc_media_stream(int cid,int did)
{
    int ret;
        osip_message_t *bye_msg = NULL;

        if (cid <= 0)
            return -1;

        // ================================
        // 发送 BYE 到 IPC 相机
        // ================================
        ret = eXosip_call_terminate(m_excontext, cid, did);
        if (ret != 0)
        {
            qDebug("发送 BYE 失败 ret:%d cid:%d did:%d",ret,cid,did);
            return -3;
        }

        qDebug("发送 BYE 成功 → IPC 相机已停止推流\n");
        return 0;
}

void GBSipServer::exosip_event_loop(void *ctx)
{
    GBSipServer* sipServer = (GBSipServer*)ctx;
    sipServer->osip_event_loop_impl();
}

void GBSipServer::osip_event_loop_impl()
{
    while (true) {
            if(nullptr == m_excontext)
                return;
            // 阻塞等待事件(超时100ms,避免卡死)
            eXosip_event_t* event = eXosip_event_wait(m_excontext, 0,100);
            if (!event) {
                continue;
            }
            eXosip_lock(m_excontext);
            eXosip_automatic_action(m_excontext);
            eXosip_unlock(m_excontext);

            // 处理事件(核心逻辑)
            handle_exosip_event(event);

            // 释放事件(必须!否则内存泄漏)
            eXosip_event_free(event);
    }
}

void GBSipServer::handle_exosip_event(eXosip_event_t *event)
{
    if (!event) return;
#if 0
    qDebug("收到OSIP事件... %d  内容长度:%d\n",
            event->type,(event->request->content_length? event->request->content_length->value:0));
#endif
    if(nullptr == event->request)
        return;
    // 根据事件类型处理(覆盖国标核心场景)
    switch (event->type) {
    // 1. 处理IPC的注册请求(Register)
    case EXOSIP_REGISTRATION_SUCCESS: {
        std::cout << "收到注册请求:" << event->request->req_uri << std::endl;
        break;
    }

        // 2. 处理IPC对Invite的响应(推流确认)
    case EXOSIP_CALL_ANSWERED: {
        osip_body* body = nullptr;
        osip_message_get_body (event->response, 0, &body);
        string newBuffer;
        Gb2312ToUtf8(body->body, newBuffer);
        qDebug("body:\n%s",newBuffer.c_str());

        eXosip_lock(m_excontext);
        int ret = eXosip_call_send_ack(m_excontext, event->did, nullptr);
        eXosip_unlock(m_excontext);
        if(0 != ret) {
            qDebug("调用eXosip_call_send_ack()失败,返回值:%d\n",ret);
            return;
        }
        char ipcAddr[32];
        char ipcSipId[32];
        get_ipc_media_ip_from_sdp(event,ipcAddr,ipcSipId);
        qDebug() << "IPC ip地址:"<<ipcAddr <<" sip id:"<<ipcSipId <<"同意推流请求,开始接收RTP流...cid:" << event->cid <<" did:" << event->did;

        //缓存当前相机的call id dialog id 后面关闭流是会用
        GBCameraManager::getInstance()->updateCameraInfo(ipcSipId,event->cid,event->did);

        // 这里启动RTP接收器
//        init_rtp_receiver(g_local_ip.c_str(), g_rtp_port); // 你的本地RTP接收端口
        break;
    }

        // 3. 处理IPC断连(Bye)
    case EXOSIP_CALL_CLOSED: {
        std::cout << "IPC停止推流,会话关闭" << std::endl;
        break;
    }
    case EXOSIP_CALL_PROCEEDING:

        break;
    case EXOSIP_CALL_MESSAGE_ANSWERED:
        {
         qDebug("收到 IPC 对 MESSAGE/INFO 的 200 OK");
        }
        break;
        // 其他事件(可选处理)
    case EXOSIP_REGISTRATION_FAILURE:
        std::cerr << "IPC注册失败:" << event->ss_status << std::endl;
        break;
    case EXOSIP_CALL_REQUESTFAILURE:
        std::cerr << "Invite请求失败:" << event->ss_status << std::endl;
        break;
    case EXOSIP_MESSAGE_NEW:
        processMessage(event);
        break;
    default:
        // 忽略其他无关事件
        break;
    }
}

void GBSipServer::processMessage(eXosip_event_t *osipEvent)
{
    if (!osipEvent)
        return;
    if(MSG_IS_BYE(osipEvent->request))
    {
        qDebug("IS_BYE\n");
    }
    else if(MSG_IS_NOTIFY(osipEvent->request))
    {
        qDebug("IS_NOTIFY\n");
    }
    else if(MSG_IS_MESSAGE(osipEvent->request))
    {
        qDebug("IS_MESSAGE\n");
        if (g_globalData.isRegister) {//IPC注册成功后,才去处理Message消息
            parse_message_info(osipEvent);
        }
    }
    else if (MSG_IS_REGISTER(osipEvent->request))
    {
        qDebug("IS_REGISTER\n");
        //处理注册消息
        parse_register_info(osipEvent);
    }
    else
    {
        qDebug("MESSAGE TYPE:%s\n",osipEvent->request->sip_method);
    }
}
int get_ipc_media_ip_from_sdp(eXosip_event_t *evt, char *media_ip, char *sipId)
{
    osip_message_t *msg = NULL;
    sdp_message_t *sdp = NULL;
    int ret = -1;

    if (!evt || !media_ip || !sipId)
        return -1;

    // 1. 获取响应消息(ANSWERED 事件里取 response)
    msg = evt->response;
    if (!msg) {
        qDebug("无消息体\n");
        return -1;
    }
    osip_body* body = nullptr;
    osip_message_get_body (evt->response, 0, &body);
    sdp_message_init(&sdp);
    // 2. 从消息 body 中解析 SDP
    sdp_message_parse(sdp,body->body);
    if (!sdp) {
        qDebug("无SDP消息体\n");
        sdp_message_free(sdp);
        return -1;
    }

    // ======================================================
    // 3. 【核心】从 SDP 中获取 IPC 媒体信息
    if (sdp->c_connection->c_addr) {
        strncpy(media_ip, sdp->c_connection->c_addr, 63);
    } else {
        qDebug("SDP无c=地址\n");
        sdp_message_free(sdp);
        return -1;
    }

    //从SDP中获取IPC 的sip id
    if(sdp->o_username){
        strcpy(sipId,sdp->o_username);
    }else{
        sdp_message_free(sdp);
        return -1;
    }

    sdp_message_free(sdp);


    return 0; // 成功拿到IPC媒体IP+端口
}
int get_ipc_ip_from_event_400(eXosip_event_t *evt, char *ip_buf, int buf_len)
{
    osip_message_t *msg = NULL;
    osip_contact_t *contact = NULL;
    osip_via_t *via = NULL;
    int ret = -1;

    if (!evt || !ip_buf || buf_len < 16)
        return -1;
    memset(ip_buf, 0, buf_len);

    // 优先取 request(REGISTER 请求)
    msg = evt->request;
    if (!msg)
        msg = evt->response;
    if (!msg)
        return -1;

    // --------------------------
    // 方法1:从 Contact 头取 IP(最常用)
    // --------------------------
    if (osip_message_get_contact(msg, 0, &contact) == 0 &&
        contact != NULL && contact->url != NULL && contact->url->host != NULL)
    {
        strncpy(ip_buf, contact->url->host, buf_len - 1);
        ret = 0;
    }
    // --------------------------
    // 方法2:Contact 取不到,从 Via 头取(网络路径 IP)
    // --------------------------
    else if (osip_message_get_via(msg, 0, &via) == 0 &&
             via != NULL && via->host != NULL)
    {
        strncpy(ip_buf, via->host, buf_len - 1);
        ret = 0;
    }
    // --------------------------
    // 方法3:Via 取不到,从 From 头取
    // --------------------------
    else if (msg->from != NULL && msg->from->url != NULL && msg->from->url->host != NULL)
    {
        strncpy(ip_buf, msg->from->url->host, buf_len - 1);
        ret = 0;
    }

    return ret;
}
void GBSipServer::parse_register_info(eXosip_event_t *e)
{
    if (e == NULL || osip_strcasecmp(e->request->cseq->method, "REGISTER") != 0) {
        qDebug("不是REGISTER请求 请求方法:%s\n",e->request->cseq->method);
        return;
    }

    char* t = nullptr;
    size_t len = 0;
    osip_message_to_str(e->request, &t, &len);
    qDebug() << QString("InfoBegin(type=%1\n%2\nInfoEnd").arg(e->type).arg(t);
    osip_free(t);

    GB_CameraInfo camInfo;
    // 1. 提取设备ID(From头域)
    osip_uri_t *from_uri = NULL;
    from_uri =  osip_from_get_url(e->request->from);
    qDebug("注册客户端From地址:%s\n", from_uri->host);
    qDebug("注册客户端FromID: %s",from_uri->username);

    g_globalData.isRegister = true;

    // 2. 提取Contact(客户端实际地址)
    osip_uri_t *contact_uri = NULL;
    contact_uri = osip_contact_get_url(e->request->from);

    qDebug("注册客户端Contact ID: %s",from_uri->username);
    qDebug("注册客户端Contact Port: %s",from_uri->port != nullptr ?from_uri->port:"nullptr");



    string camIp = contact_uri->host;
    char ip_buf[32] ;
    get_ipc_ip_from_event_400(e,ip_buf,sizeof(ip_buf));
    camIp = ip_buf;
    qDebug() << "注册客户端IPC ip地址:" << camIp.c_str();
    string camId = contact_uri->username;
    strcpy(camInfo.ipAddr,camIp.c_str());
    if(contact_uri->port){
        camInfo.port = atoi(contact_uri->port);
    }else{
        camInfo.port = 5060;
    }
    strcpy(camInfo.deviceId,camId.c_str());
    camInfo.status = 1;
    //将注册的新IPC相机加入缓存中
    GBCameraManager::getInstance()->setCameraInfo(camInfo);

    // 3. 提取注册有效期
    int expires = 0;
    osip_header_t* header = nullptr;
    osip_message_header_get_byname(e->request, "expires", 0, &header);
    if(nullptr != header && nullptr != header->hvalue)
        expires = atoi(header->hvalue);
    else
        qDebug("注册信息中没有expires字段\n");
    qDebug("注册周期是expires=%d\n", expires);

    // 4. 提取Call-ID
#if 0
    char *call_id = NULL;
    call_id = osip_call_id_get_host(e->request->call_id);
    qDebug("注册Call-ID:%s\n", call_id );
#else
    int cid = e->cid;
    qDebug("cid=%d\n", cid);
    int rid = e->rid;
    qDebug("rid=%d\n", rid);
#endif





    // 响应200 OK,完成IPC注册
    osip_message_t *answer = nullptr;
    eXosip_lock(m_excontext);
    int result = eXosip_message_build_answer(m_excontext, e->tid, 200, &answer);
    //增加过期字段
    char buff[512];
    sprintf(buff, "%d", expires );
    osip_message_set_header(answer, "Expires", buff);
    //增加时间字段
    string dt;
    //"%4d-%02d-%02dT%02d:%02d:%02d.%03d";
    osip_message_set_header(answer, "Date", dt.c_str());

    if (OSIP_SUCCESS != result)
        eXosip_message_send_answer(m_excontext, e->tid, 400, nullptr);
    else
        eXosip_message_send_answer(m_excontext, e->tid, 200, answer);//发送消息体

    eXosip_unlock(m_excontext);
}

void GBSipServer::parse_message_info(eXosip_event_t *osipEvent)
{
    osip_body* body = nullptr;
    osip_message_get_body (osipEvent->request, 0, &body);
    string buffer =  body->body;
    bool is_error=false;
    std::string cmdType("");
    string newBuffer ="";

    string eName = "";
    if ( buffer.find("<Notify>") != string::npos )
    {
        eName = "Notify";
    }
    else if(buffer.find("<Response>") != string::npos )
    {
        eName = "Response";
    }
    else if(buffer.find("<Control>") != string::npos )
    {
        eName = "Control";
    }
    else if(buffer.find("<Query>") != string::npos )
    {
        eName = "Query";
    }
    else
    {
        qDebug("接收到MESSAGE报文,报文类别为(%s),未处理。报文内容为%s\n",eName.c_str(),buffer.c_str());
        return ;
    }

    Gb2312ToUtf8(buffer,newBuffer);
    //xml解析
    XMLDocument doc;
    XMLError ret = doc.Parse(newBuffer.c_str(),newBuffer.size());
    if (ret != XML_SUCCESS) {
        qDebug("xml 解析失败\n");
        return ;
    }
    if (eName == "Notify") {
        XMLElement* root = doc.FirstChildElement("Notify");

        // 4. 读取各个字段(GB28181 心跳字段)
        cmdType   = root->FirstChildElement("CmdType")->GetText();  // Keepalive
        const char* SN        = root->FirstChildElement("SN")->GetText();
        const char* DeviceID  = root->FirstChildElement("DeviceID")->GetText(); // 设备ID
        const char* Status    = root->FirstChildElement("Status")->GetText();   // OK
    }

    if(cmdType == "Keepalive"&& eName == "Notify")//心跳
    {
        eXosip_message_send_answer(m_excontext,osipEvent->tid, 200, NULL);
        qDebug("收到来自IPC的心跳包\n");
        return ;
    }
}

4.相机管理类

gbcameramanager.h

cpp 复制代码
#ifndef GBCAMERAMANAGER_H
#define GBCAMERAMANAGER_H

#include <QObject>
#include <QMap>
#include <mutex>
#include "basic.h"
#include "gbplayerengine.h"
#include "gbplayersession.h"

class GBCameraManager : public QObject
{
    Q_OBJECT
public:
    static GBCameraManager* getInstance();

    int getCameraList(GB_CameraList* out);
    int startPlay(GB_PlayParam* param);
    int stopPlay(const char* deviceId);
    void clearAll();
    /**
     * @brief setCameraInfo 设置相机信息
     * @param info
     * @return
     */
    int setCameraInfo(GB_CameraInfo info);
    /**
     * @brief updateCameraInfo 更新相机缓存信息
     * @param ipc_id
     * @param callId
     * @return
     */
    int updateCameraInfo(string ipc_id, int callId, int dialogId);
    /**
     * @brief inputStream 接收到IPC相机的媒体流
     * @param data
     * @param maxlen
     * @param clientIp
     * @return
     */
    int inputStream(char *data, qint64 maxlen,string clientIp);
    /**
     * @brief getIdByDeviceId 获取当前IPC播放时的cid did
     * @param deviceId
     * @param cid
     * @param did
     */
    void getIdByDeviceId(string deviceId,int &cid,int &did);
private:
    explicit GBCameraManager(QObject *parent = nullptr);
    static GBCameraManager* instance;
    mutex m_cameraMapMutex;
    QMap<QString, GB_CameraInfo> m_cameraMap;
    mutex m_sessionMapMutex;
    QMap<QString, GBPlayerSession*> m_sessionMap;
};

#endif // GBCAMERAMANAGER_H

gbcameramanager.cpp

cpp 复制代码
#include "gbcameramanager.h"
#include <QDebug>
#include "gbsipserver.h"
GBCameraManager* GBCameraManager::instance = nullptr;

GBCameraManager* GBCameraManager::getInstance()
{
    if (!instance) instance = new GBCameraManager;
    return instance;
}

GBCameraManager::GBCameraManager(QObject *parent) : QObject(parent) {}

int GBCameraManager::getCameraList(GB_CameraList* out)
{
    if (!out) return -1;
    out->count = 0;
    lock_guard<mutex> lck(m_cameraMapMutex);
    for (auto info : m_cameraMap) {
        if (out->count >= 128) break;
        out->cameras[out->count++] = info;
    }
    return 0;
}

int GBCameraManager::startPlay(GB_PlayParam* param)
{
    QString id = param->deviceId;
    if (!m_cameraMap.contains(id)) return -1;

    if(m_cameraMap[id].playStatus == 1){
        qDebug() << "当前相机正在播放";
        return -1;
    }
    {
        lock_guard<mutex> lck(m_sessionMapMutex);
        if (m_sessionMap.contains(id)) {
            m_sessionMap[id]->stop();
            delete m_sessionMap.take(id);
        }

        GBPlayerSession* session = new GBPlayerSession(param);
        if(session->initialize()){
            session->start();

            m_sessionMap.insert(id, session);
        }
    }

    m_cameraMap[id].playStatus = 1;

    std::string ipc_ip =param->devIp;
    std::string ipc_id = param->deviceId;
    int ipc_port = param->port;

    GBSipServer::getInstance()->send_invite_to_ipc(ipc_ip,ipc_id,ipc_port);
    return 0;
}

int GBCameraManager::stopPlay(const char* deviceId)
{
    int cid = 0;
    int did = 0;

    getIdByDeviceId(deviceId,cid,did);
    if(cid >0){
        GBSipServer::getInstance()->stop_ipc_media_stream(cid,did);
    }
    QString id = deviceId;
    {
        lock_guard<mutex> lck(m_sessionMapMutex);
        if (m_sessionMap.contains(id)) {
            m_sessionMap[id]->stop();
            delete m_sessionMap.take(id);
        }
    }
    {
        lock_guard<mutex> lck(m_cameraMapMutex);
        if (m_cameraMap.contains(id)) {
            m_cameraMap[id].playStatus = 0;
        }
    }
    return 0;
}

void GBCameraManager::clearAll()
{
    for (auto s : m_sessionMap) {
        s->stop();
        delete s;
    }

    m_sessionMap.clear();
    {
        lock_guard<mutex> lck(m_cameraMapMutex);
        m_cameraMap.clear();
    }
}

int GBCameraManager::setCameraInfo(GB_CameraInfo info)
{
    lock_guard<mutex> lck(m_cameraMapMutex);
    if(m_cameraMap.find(info.deviceId) != m_cameraMap.end()){
        m_cameraMap[info.deviceId] = info;
    }else{
        m_cameraMap.insert(info.deviceId,info);
    }
    return 0;
}

int GBCameraManager::updateCameraInfo(string ipc_id, int callId,int dialogId)
{
    lock_guard<mutex> lck(m_cameraMapMutex);
    if(m_cameraMap.find(ipc_id.data()) != m_cameraMap.end()){
        m_cameraMap[ipc_id.data()].cid = callId ;
        m_cameraMap[ipc_id.data()].did = dialogId ;
    }
    return 0;
}

int GBCameraManager::inputStream(char *data, qint64 maxlen, string clientIp)
{
//    qDebug() << "来自IPC :" << clientIp.data() <<"的媒体流";
    string ipc_id = "" ;
    //通过相机IP获取到对应的id
    {
        lock_guard<mutex> lck(m_cameraMapMutex);
        for(auto elem:m_cameraMap){
            if(elem.ipAddr == clientIp){
                ipc_id = elem.deviceId;
                break;
            }
        }
    }
    if(ipc_id == "")
        return -1;
    lock_guard<mutex> lck(m_sessionMapMutex);
    if(m_sessionMap.find(ipc_id.data()) != m_sessionMap.end()){
        if(m_sessionMap[ipc_id.data()]){
            m_sessionMap[ipc_id.data()]->inputStream(data,maxlen);
        }
    }
    return 0;
}

void GBCameraManager::getIdByDeviceId(string deviceId, int &cid, int &did)
{
    lock_guard<mutex> lck(m_cameraMapMutex);
    if(m_cameraMap.find(deviceId.data()) != m_cameraMap.end()){
        cid = m_cameraMap[deviceId.data()].cid  ;
        did = m_cameraMap[deviceId.data()].did ;
    }

}

5.播放会话类

gbplayersession.h

cpp 复制代码
#ifndef GBPLAYERSESSION_H
#define GBPLAYERSESSION_H

#include <QThread>
#include <memory>
#include "gbplayerengine.h"
#include "PsParser.h"
#include "vlcstreamdecoder.h"
class GBPlayerSession : public QThread
{
    Q_OBJECT
public:
    explicit GBPlayerSession(GB_PlayParam* param);
    ~GBPlayerSession();
    bool initialize();

    bool isInitialize(){return m_isInitialize;}
    void stop();
    /**
     * @brief inputStream
     * @param data
     * @param len
     */
    void inputStream(const char *data , int len);
private:
    static void onMediaData(PsParser::MediaType type, const uint8_t* data, int len, uint64_t timestamp,void* opaque);
    void handleMediaData(PsParser::MediaType type,
                             const uint8_t* data,
                             int len,
                             uint64_t timestamp);
protected:
    void run() override;

private:
    GB_PlayParam m_param;
    bool m_running = false;
    PsParser *m_psParser;
    QList<QByteArray> m_streamDataList;
    std::unique_ptr<VLCStreamDecoder> m_decoder;
    bool m_isInitialize = false;
};

#endif // GBPLAYERSESSION_H

gbplayersession.cpp

cpp 复制代码
#include "gbplayersession.h"
#include <QDebug>
GBPlayerSession::GBPlayerSession(GB_PlayParam* param)
{
    if (param) m_param = *param;
}

GBPlayerSession::~GBPlayerSession()
{
    if(m_psParser){
        delete m_psParser;
    }
}

bool GBPlayerSession::initialize()
{
    m_decoder = std::make_unique<VLCStreamDecoder>(this);
    m_decoder->OpenStream((WId)m_param.renderWnd);
    m_psParser = new PsParser();
    m_isInitialize = true;
    return  true;
}

void GBPlayerSession::stop()
{
    m_running = false;
    wait();
}

void GBPlayerSession::inputStream(const char *data, int len)
{
    if(m_streamDataList.size() > 80){
        qDebug() << "IPC相机:" << m_param.devIp <<" 媒体流缓存超出上限,被丢弃";
        return;
    }
      QByteArray byteArr(data,len);
      m_streamDataList.push_back(byteArr);
}
void GBPlayerSession::onMediaData(PsParser::MediaType type, const uint8_t* data, int len, uint64_t timestamp, void *opaque)
{
    // 从 opaque 拿回 this 指针
    GBPlayerSession* self = (GBPlayerSession*)opaque;
    self->handleMediaData(type,data,len,timestamp);
}

void GBPlayerSession::handleMediaData(PsParser::MediaType type, const uint8_t *data, int len, uint64_t timestamp)
{
    if (type == PsParser::MEDIA_TYPE_VIDEO_H264)
    {
#if 0
        qDebug() << "[H264] 帧长度:" << len << " 时间戳:" << timestamp ;
        // 这里可以直接送给解码器/保存文件/推流
        if (len > 30) {
            QByteArray ba((const char*)data, 30);
            qDebug() << "数据十六进制:" << ba.toHex(' ');
        }
#endif
        QByteArray ba((const char*)data, len);
        if(isInitialize()){
            if (m_decoder) {
                m_decoder->InputStream(ba.constData(), ba.size(),"h264");
            } else {
                qDebug() << "警告:m_decoder 为 NULL,但 isInitialize() 返回 true!";
            }
        }
    }
    else if (type == PsParser::MEDIA_TYPE_VIDEO_H265)
    {
#if 0
        qDebug() << "[H265] 帧长度:" << len << " 时间戳:" << timestamp;
        if (len > 30) {
            QByteArray ba((const char*)data, 30);
            qDebug() << "数据十六进制:" << ba.toHex(' ');
        }else {
            QByteArray ba((const char*)data, len);
            qDebug() << "数据十六进制:" << ba.toHex(' ');
        }
#endif
        QByteArray ba((const char*)data, len);
        if(isInitialize()){
            if (m_decoder) {
                m_decoder->InputStream(ba.constData(), ba.size(),"h265");
            } else {
                qDebug() << "警告:m_decoder 为 NULL,但 isInitialize() 返回 true!";
            }
        }
    }
    else if (type == PsParser::MEDIA_TYPE_AUDIO_AAC)
    {
        qDebug() << "[AAC] 帧长度:" << len << " 时间戳:" << timestamp;
    }
}
void GBPlayerSession::run()
{
    m_running = true;

    m_psParser->setOpaque(this);
    m_psParser->setCallback(&GBPlayerSession::onMediaData);
    while (m_running) {
        // 1. 从UDP服务获取PS流
        // 2. 解封装PS
        // 3. H264/H265解码
        // 4. 渲染到窗口句柄

        if(m_streamDataList.size() == 0){
            msleep(10);
            continue;
        }
        QByteArray byteArr = m_streamDataList.front();
        m_streamDataList.pop_front();

        m_psParser->parseRtpPsData((const uint8_t*)byteArr.data(),byteArr.length());
//        msleep(10);
    }
}

6.PS流解析类

cpp 复制代码
#ifndef GBMEDIACLIENT_PSPARSER_H
#define GBMEDIACLIENT_PSPARSER_H
#include <iostream>
#include <cstdint>
#include <cstring>
#include <vector>
#include <QObject>
// 禁用命名空间污染,仅使用必要的类型
using namespace std;

/**
 * @brief PS 封装格式解析器
 * @note 适配:RTP over UDP + PS 封装(海康/大华/IPC网络摄像头通用)
 *        输出标准 H264/H265 + AAC 裸码流
 */
class GBPlayerSession;
class PsParser
{
public:
    enum MediaType
    {
        MEDIA_TYPE_VIDEO_H264 = 0x01,
        MEDIA_TYPE_VIDEO_H265 = 0x02,
        MEDIA_TYPE_AUDIO_AAC  = 0x10,
        MEDIA_TYPE_UNKNOWN
    };

    using OnMediaDataCallback = void(*)(MediaType type, const uint8_t* data, int len, uint64_t timestamp,void* opaque);

    const int MAX_CACHE_SIZE = 2*1024 * 1024; // 最大缓存 1MB

public:
    PsParser() : m_callback(nullptr), m_lastTimestamp(0) {

    }
    ~PsParser() = default;
    void setOpaque(void *ctx){m_ctx = (GBPlayerSession*)ctx;}
    // 设置回调
    void setCallback(OnMediaDataCallback cb) { m_callback = cb; }


    // 入口:每次收到UDP数据就传入(自动缓存+拼包+找完整PS包)
    void parseRtpPsData(const uint8_t* data, int len)
    {
        if (!data || len <= 0)
            return;

        // 1. 剥离 RTP 固定头 12 字节
        if (len < 12)
            return;
        const uint8_t* psData = data + 12;
        int psLen = len - 12;

        // 2. 【仅追加数据,绝不主动清理未解析数据】
        appendCache(psData, psLen);

        // 3. 循环解析:只处理【完整PS包】
        while (true)
        {
            size_t cacheSize = m_cache.size();
            const uint8_t* cacheBuf = m_cache.data();

            // --------------------------
            // 步骤1:找【第一个 PS 包头】00 00 01 BA
            // --------------------------
            int firstBaPos = findPsHeader(cacheBuf, (int)cacheSize);
            if (firstBaPos < 0)
            {
                // 没找到第一个包头,保留所有数据,退出
                break;
            }

            // 如果包头不在起始位置,移除前面无效脏数据(不影响音视频)
            if (firstBaPos > 0)
            {
                m_cache.erase(m_cache.begin(), m_cache.begin() + firstBaPos);
                continue;
            }

            // --------------------------
            // 步骤2:从第一个包头后,找【下一个 PS 包头】00 00 01 BA
            // --------------------------
            int secondBaPos = -1;
            if (cacheSize > 14)
            {
                secondBaPos = findPsHeader(cacheBuf + 14, (int)(cacheSize - 14));
                if (secondBaPos != -1)
                {
                    secondBaPos += 14; // 偏移回原始缓存位置
                }
            }

            // 没找到下一个包头 → 数据不完整,等待下次接收
            if (secondBaPos == -1)
            {
                break;
            }

            // --------------------------
            // 步骤3:两个包头之间 = 【完整PS包】
            // --------------------------
            int completePsLen = secondBaPos;
            if (completePsLen < 14 || completePsLen > MAX_CACHE_SIZE)
            {
                // 异常包,丢弃当前包头
                m_cache.erase(m_cache.begin(), m_cache.begin() + 4);
                continue;
            }

            // --------------------------
            // 步骤4:解析完整PS包
            // --------------------------
            parseCompletePs(cacheBuf, completePsLen);

            // --------------------------
            // 步骤5:【只删除已解析完成的PS数据】
            // 剩余未解析数据 100% 保留
            // --------------------------
            m_cache.erase(m_cache.begin(), m_cache.begin() + completePsLen);
        }

        // 缓存溢出保护(极端情况)
        if (m_cache.size() > MAX_CACHE_SIZE)
        {
            m_cache.clear();
        }
    }


private:
    // 帧拼接缓存(关键:解决H265画面下半部分异常)
    std::vector<uint8_t> m_frameCache;
    uint64_t      m_lastPts = 0;
    MediaType     m_lastMediaType = MEDIA_TYPE_UNKNOWN;
    MediaType m_currentFrameType = MEDIA_TYPE_UNKNOWN;
    uint64_t m_currentPts = 0;
    GBPlayerSession *m_ctx;

private:
    // -------------------------- 核心优化:解析完整PS包 --------------------------
    void parseCompletePs(const uint8_t* data, int len)
    {
#if 0
        if (len > 30) {
            for (int i = 0;i<30;i++) {
                fprintf(stdout,"%02x ", data[i]);
            }
            fprintf(stdout,"\n");
            h264WriteFrame((unsigned char*)data,len);
        }
        return ;
#endif

        const uint8_t* p = data;
        const uint8_t* end = data + len;

        // 校验 PS 包头 00 00 01 BA
        if (len < 14 || p[0] != 0x00 || p[1] != 0x00 || p[2] != 0x01 || p[3] != 0xBA)
            return;

        // 提取时间戳
        uint64_t pts = getPtsFromPsHeader(p);
        m_lastTimestamp = pts;
        p += 14;

        // 【重要】清空上一帧缓存
        m_frameCache.clear();
        m_currentFrameType = MEDIA_TYPE_UNKNOWN;
        // 遍历解析包
        while (p + 4 <= end)
        {
            // 必须是起始码 00 00 01
            if (p[0] != 0x00 || p[1] != 0x00 || p[2] != 0x01)
            {
                p++;
                continue;
            }

            uint8_t type = p[3];
            // ==============================================
            // 关键修复:遇到 0xBB (PS系统头) 直接跳过,不退出!
            // ==============================================
            if (type == 0xBB)
            {
                // 跳过 PS 系统头:6字节固定头 + 长度
                if (p + 6 > end) break;
                int sysLen = (p[4] << 8) | p[5];
                p += (6 + sysLen);
                continue;
            }
            if (type == 0xBC) // PSM 包
            {
                int slen = (p[4] << 8) | p[5];
                p += 6 + slen;
                continue;
            }
            // ==============================================
            // 只有视频(0xE0/0xE1)、音频(0xC0)才解析PES
            // ==============================================
            if (type == 0xE0 || type == 0xE1 || type == 0xC0)
            {
                parsePesPacket(p, end - p, type);// 不 break!继续解析下一个PES!
            }
            // 其他未知类型,跳过
            else
            {
                p += 4;
            }
        }

        // ======================================================================
        // PS包解析完成 → 帧拼接完成 → 一次性输出完整帧
        // ======================================================================
        if (!m_frameCache.empty() && m_callback) {
            m_callback(m_currentFrameType,
                       m_frameCache.data(),
                       (int)m_frameCache.size(),
                       m_currentPts,
                       m_ctx
                       );
        }
    }

    // 查找 PS 包头 00 00 01 BA
    int findPsHeader(const uint8_t* data, int len)
    {
        for (int i = 0; i < len - 3; i++)
        {
            if (data[i] == 0x00 && data[i+1] == 0x00 && data[i+2] == 0x01 && data[i+3] == 0xBA)
                return i;
        }
        return -1;
    }

    // 计算一个完整 PS 包的长度(从包头开始)
    int getPsPacketLength(const uint8_t* ba, int bufLen)
    {
        if (bufLen < 14) return -1;

        int packLen = 14;
        const uint8_t* p = ba + 14;

        while (true)
        {
            if (p + 4 > ba + bufLen) break;
            if (p[0] == 0x00 && p[1] == 0x00 && p[2] == 0x01 && p[3] == 0xBB)
            {
                // PS 系统头
                int sysLen = (p[4] << 8) | p[5];
                packLen += 6 + sysLen;
                p += 6 + sysLen;
            }
            else if (p[0] == 0x00 && p[1] == 0x00 && p[2] == 0x01)
            {
                // PES 包
                if (p + 6 > ba + bufLen) break;
                int pesLen = (p[4] << 8) | p[5];
                packLen += 6 + pesLen;
                p += 6 + pesLen;
            }
            else
            {
                p++;
            }

            if (packLen > bufLen || packLen > MAX_CACHE_SIZE)
                return -1;
        }
        return packLen;
    }

    // 解析 PES
    bool parsePesPacket(const uint8_t*& p, int remain, uint8_t stream)
    {
        // PES 最小长度校验
        if (remain < 9) {
            return false;
        }

        // 记录当前指针位置(用于长度计算)
        const uint8_t* pesStart = p;

        // 1. 跳过 PES 起始码 00 00 01 xx (4字节)
        p += 4;
        // remain -= 4;

        // 2. 读取 PES 包总长度(2字节)
        uint16_t pes_total_len = ((uint16_t)p[0] << 8) | p[1];
        p += 2;
        // remain -= 2;

        if (pes_total_len == 0) {
            // 未指定长度(视频帧允许),直接用剩余长度
            pes_total_len = remain - 6;
        }
        // 3. 固定 PES 头 3 字节
        uint8_t pes_header_len = p[2];  // 头部附加长度
        p += 3;
        // remain -= 3;
        // 4. 计算完整头部长度
        int full_header_len = 3 + pes_header_len;
        if (full_header_len > pes_total_len) {
            return false;
        }

        p += pes_header_len;
        // remain -= pes_header_len;
        // ==============================================
        // 【核心修复】正确计算负载长度(解决H265画面异常)
        // ==============================================
        int payload_len =  pes_total_len - full_header_len;

        // 安全边界检查
        if (payload_len <= 0) {
            return true;
        }

        // ==============================================
        // 正确识别 H264 / H265 (严格区分)
        // ==============================================

        if (m_currentFrameType == MEDIA_TYPE_UNKNOWN) {
            MediaType type = MEDIA_TYPE_UNKNOWN;
            if (stream == 0xE0 || stream == 0xE1) {
                if (payload_len >= 4) {
                    bool isH264 = false;
                    bool isH265 = false;

                    // 3字节起始码 00 00 01
                    if (p[0] == 0x00 && p[1] == 0x00 && p[2] == 0x01) {
                        uint8_t nal_unit = p[3] & 0x1F;
                        // H264 NAL
                        if (nal_unit == 0x01 || nal_unit == 0x05 || nal_unit == 0x07 || nal_unit == 0x08) {
                            isH264 = true;
                        }
                        // H265 NAL (0x40~0x7F)
                        else {
                            uint8_t nal_hevc = (p[3] >> 1) & 0x3F;
                            if (nal_hevc >= 0 && nal_hevc <= 0x3F) {
                                isH265 = true;
                            }
                        }
                    }
                    // 4字节起始码 00 00 00 01
                    else if (p[0] == 0x00 && p[1] == 0x00 && p[2] == 0x00 && p[3] == 0x01) {

                        uint8_t nal_unit = p[4] & 0x1F;
                        // H264
                        if (nal_unit == 0x01 || nal_unit == 0x05 || nal_unit == 0x07 || nal_unit == 0x08) {
                            isH264 = true;
                        }
                        // H265
                        else {
                            uint8_t nal_hevc = (p[4] >> 1) & 0x3F;
                            if (nal_hevc >= 0 && nal_hevc <= 0x3F) {
                                isH265 = true;
                            }
                        }
                    }

                    if (isH264) type = MEDIA_TYPE_VIDEO_H264;
                    if (isH265) type = MEDIA_TYPE_VIDEO_H265;
                }
            }
            // AAC 音频识别
            else if (stream == 0xC0) {
                if (payload_len >= 2 && (p[0] == 0xFF) && ((p[1] & 0xF0) == 0xF0)) {
                    type = MEDIA_TYPE_AUDIO_AAC;
                }
            }
            m_currentFrameType = type;
        }
        // ------------------------------
        // 视频:缓存拼接(不立即输出)
        // ------------------------------
        if (m_currentFrameType == MEDIA_TYPE_VIDEO_H264 || m_currentFrameType == MEDIA_TYPE_VIDEO_H265) {
            if (m_frameCache.empty()) {
                m_currentPts = m_lastTimestamp;
            }
            // 追加到帧缓存
            m_frameCache.insert(m_frameCache.end(), p, p + payload_len);
        }

        // ------------------------------
        // 音频:直接输出
        // ------------------------------
        else if (m_currentFrameType == MEDIA_TYPE_AUDIO_AAC && m_callback) {
            m_callback(m_currentFrameType, p, payload_len, m_lastTimestamp,m_ctx);
        }

        // 指针后移
        p += payload_len;

        return true;
    }

    // PTS 提取
    uint64_t getPtsFromPsHeader(const uint8_t* ba)
    {
        uint64_t pts = 0;
        pts |= ((uint64_t)(ba[4] & 0x38)) << 27;
        pts |= ((uint64_t)(ba[4] & 0x07)) << 28;
        pts |= ((uint64_t)ba[5]) << 20;
        pts |= ((uint64_t)(ba[6] & 0xF8)) << 12;
        pts |= ((uint64_t)(ba[6] & 0x07)) << 13;
        pts |= ((uint64_t)ba[7]) << 5;
        pts |= ((uint64_t)(ba[8] & 0xF8) >> 3);
        return pts / 90;
    }

    // 追加缓存
    void appendCache(const uint8_t* data, int len)
    {
        if (m_cache.size() + len > MAX_CACHE_SIZE)
            m_cache.clear();
        m_cache.insert(m_cache.end(), data, data + len);
    }

private:
    vector<uint8_t> m_cache;        // 数据拼接缓存
    OnMediaDataCallback m_callback;
    uint64_t m_lastTimestamp;

};


#endif //GBMEDIACLIENT_PSPARSER_H

四 运行日志

1.相机注册日志

复制代码
SIP Server启动成功,监听:192.168.22.53:5060: UDP
2026-04-09 13:55:29,737 INFO [default] SIP Server启动成功,监听:192.168.22.53:5060: UDP
2026-04-09 13:55:29,737 INFO [default] SIP服务 UDP服务 初始化成功
IS_REGISTER

"InfoBegin(type=23\nREGISTER sip:11010801002000000001@1101080100 SIP/2.0\r\nVia: SIP/2.0/UDP 192.168.250.42:5060;rport=5060;branch=z9hG4bK850945342\r\nFrom: <sip:11010801131310000009@1101080100>;tag=387350747\r\nTo: <sip:11010801131310000009@1101080100>\r\nCall-ID: 1348197467\r\nCSeq: 1 REGISTER\r\nContact: <sip:11010801131310000009@192.168.250.42:5060>\r\nMax-forwards: 70\r\nUser-agent: IP Camera\r\nExpires: 3600\r\nContent-Length: 0\r\n\r\n\nInfoEnd"
注册客户端From地址:1101080100

注册客户端FromID: 11010801131310000009
注册客户端Contact ID: 11010801131310000009
注册客户端Contact Port: nullptr
注册客户端IPC ip地址: 192.168.250.42
注册周期是expires=3600

cid=0

rid=0

IS_MESSAGE

收到来自IPC的心跳包

2.相机播放/关闭日志

复制代码
选中播放相机ID: 11011401111316000012
VLC 打开流成功,窗口句柄: 69206041
【调试】m_excontext 指针有效: 0x55afd5c375c0
Invite请求发送成功,等待IPC响应...
IPC: 192.168.250.114  Invite请求发送成功,等待IPC响应... call id: 1
body:
v=0
o=11011401111316000012 0 0 IN IP4 192.168.250.114
s=Play
i=VCam Live Video
c=IN IP4 192.168.250.114
t=0 0
m=video 10048 RTP/AVP 96
a=sendonly
a=rtpmap:96 PS/90000
a=streamprofile:0
a=streamnumber:0
y=0000000000
f=v/0/0/0/0/0a/0/0/0

IPC ip地址: 192.168.250.114  sip id: 11011401111316000012 同意推流请求,开始接收RTP流...cid: 1  did: 2
设置当前播放器支持解码类型为: h264
libva info: VA-API version 1.7.0
libva info: Trying to open /usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so
libva info: Found init function __vaDriverInit_1_7
libva info: va_openDriver() returns 0
Failed to open VDPAU backend libvdpau_va_gl.so: 无法打开共享对象文件: 没有那个文件或目录
发送 BYE 成功 → IPC 相机已停止推流

VLC 流已关闭
收到 IPC 对 MESSAGE/INFO 的 200 OK
相关推荐
米优2 小时前
qt+vlc实现解码h264/h265裸码流播放
开发语言·qt·vlc
小小码农Come on3 小时前
QT面试题总结
开发语言·qt
特立独行的猫a4 小时前
HarmonyOS鸿蒙PC的QT应用开发:(二、开发环境搭建及第一个HelloWorld)
qt·华为·harmonyos·鸿蒙·鸿蒙pc
史迪仔01124 小时前
[QML] QT5和QT6 圆角的不同设置方法
前端·javascript·qt
一只小小的土拨鼠4 小时前
【避坑指南】Qt + MSVC + CUDA 项目链接与发布报错全记录
开发语言·qt
code_pgf5 小时前
RPC数据集整理与 Scalabel 标注说明
qt·网络协议·rpc
白杆杆红伞伞5 小时前
Qt Lock&Semaphore
qt·线程同步
专注VB编程开发20年5 小时前
WPS 2024 Windows版UI用QT5和自研DirectUI-vba,jsa
qt·vba·wps·jsa·directui
fzb5QsS1p1 天前
告别重复造轮子,Qt 快速开发脚手架
开发语言·qt·php