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
相关推荐
用户805533698032 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner2 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz7 天前
QML Hello World 入门示例
qt
xcyxiner10 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner11 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner11 天前
DicomViewer (添加模型类)3
qt
xcyxiner12 天前
DicomViewer (目录调整) 2
qt
xcyxiner12 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
桥田智能14 天前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构
森G14 天前
75、服务器源码解析---------云视频服务项目
linux·服务器·网络·c++·qt