摄像头取流


前言

本节主要阐述摄像头取流的几种方式,分别为SDK取流、流媒体取流;

SDK取流

“SDK取流”是指通过软件开发工具包(SDK,Software Development Kit)从设备或服务中获取实时或录制的视频流数据的过程。此技术广泛应用于视频监控、实时视频传输、视频分析等领域。利用SDK提供的接口,开发者可以实现视频的接收、解码、显示以及存储等功能,使得视频数据的处理更加高效和灵活。通过SDK取流,能够方便地与各种视频源进行对接,满足不同应用场景下的需求。

海康

海康摄像头获取图像主要流程可以直接参考官方文档, 海康开放平台 。海康SDK取流主要流程图:

启动预览后通过实时数据回调拿到流并解码成图像;

预览实时流解码有两种方式:

  1. 预览接口NET_DVR_RealPlay_V40中预览参数的播放窗口句柄(hPlayWnd)赋值为有效句柄,则由SDK自动实现解码显示功能。在初始化SDK和注册设备两步骤后,直接调用启动预览和停止预览接口即可。正常开启预览之后可以调用NET_DVR_RigisterDrawFun注册画图回调函数(仅Windows版本支持),回调获取窗口DC,然后用户可以自己在窗口表层绘图或者写字。如果预览的码流是音视频复合流,也可以调用声音预览控制相关接口实现打开或者关闭声音、客户端音量控制等功能,相关接口有:NET_DVR_OpenSoundNET_DVR_CloseSoundNET_DVR_OpenSoundShareNET_DVR_CloseSoundShareNET_DVR_Volume等。
  2. 预览接口NET_DVR_RealPlay_V40中预览参数的播放窗口句柄(hPlayWnd)可以设置为空值,直接设置回调函数,或者调用预览接口之后,通过NET_DVR_SetRealDataCallBackNET_DVR_SetStandardDataCallBack设置回调函数,回调获取实时流数据(前两个接口设置的回调获取的是PS封装的码流,后者获取的是标准RTP封装的码流)之后用户后续自己处理,比如二进制流方式写入文件保存成录像或者调用播放库解码显示等操作。

代码实现:

#include <stdio.h>
#include <iostream>
#include "HCNetSDK.h"
#include "PlayM4.h"
using namespace std;

LONG lPort = -1; // 全局的播放库port号
typedef HWND(WINAPI *PROCGETCONSOLEWINDOW)();
PROCGETCONSOLEWINDOW GetConsoleWindowAPI;

void CALLBACK DecodeDataCallBack(long nPort, char *pBuf, long nSize, FRAME_INFO *pFrameInfo, void *nUser, void *nReserved2) {
    // TODO 将流转为BGR图像显示;
}

void CALLBACK g_RealDataCallBack_V30(LONG lRealHandle, DWORD dwDataType, BYTE *pBuffer, DWORD dwBufSize, void *dwUser) {
    // HWND hWnd = GetConsoleWindowAPI();

    switch (dwDataType) {
    case NET_DVR_SYSHEAD: // 系统头
        if (lPort >= 0) {
            break; // 该通道取流之前已经获取到句柄,后续接口不需要再调用
        }

        if (!PlayM4_GetPort(&lPort)) // 获取播放库未使用的通道号
        {
            break;
        }
        // m_iPort = lPort; //第一次回调的是系统头,将获取的播放库port号赋值给全局port,下次回调数据时即使用此port号播放
        if (dwBufSize > 0) {
            if (!PlayM4_SetStreamOpenMode(lPort, STREAME_REALTIME)) // 设置实时流播放模式
            {
                break;
            }

            if (!PlayM4_OpenStream(lPort, pBuffer, dwBufSize, 1024 * 1024)) // 打开流接口
            {
                break;
            }

            if (!PlayM4_SetDecodeEngineEx(lPort, SOFT_DECODE_ENGINE)) // 设置解码模式,SOFT_DECODE_ENGINE 软解、HARD_DECODE_ENGINE 硬解
            {
                cout << " cur decode mode is:" << PlayM4_GetDecodeEngine(lPort) << endl; // 打印解码类型
                break;
            }

            if (!PlayM4_SetDecCallBackMend(lPort, DecodeDataCallBack, dwUser)) // 解码回调函数
            {
                break;
            }

            if (!PlayM4_Play(lPort, 0)) // 播放开始
            {
                break;
            }
        }
        break;
    case NET_DVR_STREAMDATA: // 码流数据
        if (dwBufSize > 0 && lPort != -1) {
            if (!PlayM4_InputData(lPort, pBuffer, dwBufSize)) {
                break;
            }
        }
        break;
    default: // 其他数据
        if (dwBufSize > 0 && lPort != -1) {
            if (!PlayM4_InputData(lPort, pBuffer, dwBufSize)) {
                break;
            }
        }
        break;
    }
}

void CALLBACK g_ExceptionCallBack(DWORD dwType, LONG lUserID, LONG lHandle, void *pUser) {
    char tempbuf[256] = {0};
    switch (dwType) {
    case EXCEPTION_RECONNECT: // 预览时重连
        printf("----------reconnect--------%d\n", time(NULL));
        break;
    default:
        break;
    }
}

int main(int argc, char *argv[]) {
    // 设备初始化
    NET_DVR_Init();
    // 设置连接时间与重连时间
    NET_DVR_SetConnectTime(2000, 1);
    NET_DVR_SetReconnect(10000, true);
    // 设置异常消息回调函数
    NET_DVR_SetExceptionCallBack_V30(0, NULL, g_ExceptionCallBack, NULL);

    // 获取控制台窗口句柄
    // HMODULE hKernel32 = GetModuleHandle("kernel32");
    // GetConsoleWindowAPI = (PROCGETCONSOLEWINDOW)GetProcAddress(hKernel32, "GetConsoleWindow");
    // 注册设备
    LONG lUserID;

    // 登录参数,包括设备地址、登录用户、密码等
    NET_DVR_USER_LOGIN_INFO struLoginInfo = {0};
    struLoginInfo.bUseAsynLogin = 0;                       // 同步登录方式
    strcpy(struLoginInfo.sDeviceAddress, "xxx.xxx.xxx.xxx"); // 设备IP地址
    struLoginInfo.wPort = 8000;                            // 设备服务端口
    strcpy(struLoginInfo.sUserName, "username");              // 设备登录用户名
    strcpy(struLoginInfo.sPassword, "password");           // 设备登录密码

    // 设备信息, 输出参数
    NET_DVR_DEVICEINFO_V40 struDeviceInfoV40 = {0};

    // 用户注册设备
    lUserID = NET_DVR_Login_V40(&struLoginInfo, &struDeviceInfoV40);
    if (lUserID < 0) {
        printf("Login failed, error code: %d\n", NET_DVR_GetLastError());
        NET_DVR_Cleanup();
        return -1;
    }

    // 启动预览并设置回调数据流;
    /*
    预览接口NET_DVR_RealPlay_V40中预览参数的播放窗口句柄(hPlayWnd)赋值为有效句柄,则由SDK自动实现解码显示功能。
    若(hPlayWnd)设置为空值,直接设置回调函数,
    或者调用预览接口之后,通过NET_DVR_SetRealDataCallBack、NET_DVR_SetStandardDataCallBack设置回调函数,回调获取实时流数据
    */
    LONG lRealPlayHandle;
    NET_DVR_PREVIEWINFO struPlayInfo = {0};
    struPlayInfo.hPlayWnd = NULL;  // 需要SDK解码时句柄设为有效值,仅取流不解码时可设为空
    struPlayInfo.lChannel = 1;     // 预览通道号
    struPlayInfo.dwStreamType = 0; // 0-主码流,1-子码流,2-码流3,3-码流4,以此类推
    struPlayInfo.dwLinkMode = 0;   // 0- TCP方式,1- UDP方式,2- 多播方式,3- RTP方式,4-RTP/RTSP,5-RSTP/HTTP
    struPlayInfo.bBlocked = 1;     // 0- 非阻塞取流,1- 阻塞取流

    // 启动预览
    lRealPlayHandle = NET_DVR_RealPlay_V40(lUserID, &struPlayInfo, g_RealDataCallBack_V30, NULL);
    if (lRealPlayHandle < 0) {
        printf("NET_DVR_RealPlay_V40 error, %d\n", NET_DVR_GetLastError());
        NET_DVR_Logout(lUserID);
        NET_DVR_Cleanup();
        return -1;
    }

    Sleep(10000);
    //---------------------------------------
    // 关闭预览
    NET_DVR_StopRealPlay(lRealPlayHandle);

    // 释放播放库资源
    PlayM4_Stop(lPort);
    PlayM4_CloseStream(lPort);
    PlayM4_FreePort(lPort);

    // 注销用户
    NET_DVR_Logout(lUserID);
    NET_DVR_Cleanup();

    return 0;
}

大华

大华摄像头获取图像主要流程可以直接参考官方文档,设备网络SDK 。大华SDK取流主要流程图:

启动预览后通过实时数据回调拿到流并解码成图像;

预览实时流解码有两种方式:

  1. 在预览接口CLIENT_RealPlayEx中的播放窗口句柄为有效句柄,则由SDK实现解码功能。
  2. 用户可以通过设置预览接口CLIENT_RealPlayEx中的播放窗口句柄为NULL,并通过调用设置实时预览数据回调接口(CLIENT_SetRealDataCallBackCLIENT_SetRealDataCallBackEx),获取码流数据进行后续解码播放处理。

代码实现:

#include <stdio.h>
#include <iostream>
#include "dhnetsdk.h"
#include "dhplay.h"
using namespace std;

long g_lRealPort = -1; // 全局播放库port号

void CALLBACK RealDataCallBackEx2(LLONG lRealHandle, DWORD dwDataType, BYTE *pBuffer, DWORD dwBufSize, LLONG param, LDWORD dwUser) {
    BOOL bInput = FALSE;
    if (0 != lRealHandle) {
        switch (dwDataType) {
        case 0:
            // 原始音视频混合数据
            bInput = PLAY_InputData(g_lRealPort, pBuffer, dwBufSize);
            if (!bInput) {
                printf("input data error: %d\n", PLAY_GetLastError(g_lRealPort));
            }
            break;
        case 1:
            // 标准视频数据

            break;
        case 2:
            // yuv 数据

            break;
        case 3:
            // pcm 音频数据

            break;
        case 4:
            // 原始音频数据

            break;
        default:
            break;
        }
    }
}

void CALLBACK DecDataCallBack(long nPort, char *pBuf, long nSize, FRAME_INFO *pFrameInfo, void *pUser, long nReserved2) {
    // TODO 将流转为BGR图像显示;
}

int main(int argc, char *argv[]) {
    char szCommand[64] = {0};
    int nChannelID = 0;

    // 初始化
    CLIENT_Init(NULL, 0);

    NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY stInparam;
    memset(&stInparam, 0, sizeof(stInparam));
    stInparam.dwSize = sizeof(stInparam);
    strncpy(stInparam.szIP, "xxx.xxx.xxx.xxx", sizeof(stInparam.szIP) - 1);
    strncpy(stInparam.szUserName, "username", sizeof(stInparam.szUserName) - 1);
    strncpy(stInparam.szPassword, "password", sizeof(stInparam.szPassword) - 1);
    stInparam.nPort = 37777;
    stInparam.emSpecCap = EM_LOGIN_SPEC_CAP_TCP;
    NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY stOutparam;
    memset(&stOutparam, 0, sizeof(stOutparam));
    stOutparam.dwSize = sizeof(stOutparam);
    // 注册用户到设备
    LLONG lLoginHandle = CLIENT_LoginWithHighLevelSecurity(&stInparam, &stOutparam);
    if (lLoginHandle == 0) {
        printf("registration failed. \n");
    }

    // 获取播放库端口号
    if (!PLAY_GetFreePort(&g_lRealPort)) {
        printf("Failed to get free port. \n");
    }
    // 设置流播放模式;STREAME_REALTIME实时模式(默认);STREAME_FILE文件流模式;实时模式
    if (!PLAY_SetStreamOpenMode(g_lRealPort, STREAME_REALTIME)) {
        printf("Failed to set streaming mode. \n");
    }
    // 打开流播放
    if (!PLAY_OpenStream(g_lRealPort, NULL, 0, 1024 * 900)) {
        printf("Failed to open stream. \n");
    }
    // 设置解码回调流类型,流类型:1 视频流;2 音频流;3 复合流;
    if (!PLAY_SetDecCBStream(g_lRealPort, 1)) {
        printf("Failed to set decoding callback stream type. \n");
    }
    // 设置解码回调,相比于PLAY_SetDecCallBack函数,用户可以自定义回调参数;
    if (!PLAY_SetDecCallBackEx(g_lRealPort, DecDataCallBack, NULL)) {
        printf("Failed to set decoding callback. \n");
    }
    // 开始播放
    if (!PLAY_Play(g_lRealPort, 0)) {
        printf("Failed to play. \n");
    }
    // 启动实时预览或多画面预览。
    LLONG lPlayHandle = CLIENT_RealPlayEx(lLoginHandle, nChannelID, 0, DH_RType_Realplay_0);
    if (0 != lPlayHandle) {
        // 设置回调函数处理数据
        CLIENT_SetRealDataCallBackEx2(lPlayHandle, RealDataCallBackEx2, (LDWORD)0, REALDATA_FLAG_RAW_DATA | REALDATA_FLAG_DATA_WITH_FRAME_INFO);
    } else {
        printf("Fail to play!\n");
        PLAY_Stop(g_lRealPort);
        PLAY_CloseStream(g_lRealPort);
    }
    Sleep(10000);

    // 关闭解码库播放
    PLAY_Stop(g_lRealPort);

    // 释放播放库
    PLAY_CloseStream(g_lRealPort);

    // 关闭预览
    CLIENT_StopRealPlayEx(lPlayHandle);

    // 注销用户
    CLIENT_Logout(lLoginHandle);

    // 释放SDK资源
    CLIENT_Cleanup();

    return 0;
}

宇视

宇视摄像头获取图像主要流程可以直接参考官方文档, 宇视开放平台 。宇视SDK取流主要流程图:

启动预览后通过实时数据回调拿到流并解码成图像;

代码实现:

#include <stdio.h>
#include <iostream>
#include "NetDEVSDK.h"
#include <thread>
#include <chrono>
using namespace std;
void STDCALL YSRealDataCallBack(LPVOID lpPlayHandle, const BYTE *pucBuffer, INT32 dwBufSize, INT32 dwMediaDataType, LPVOID lpUserParam) {
}

void STDCALL YSDecDataCallBack(LPVOID lpPlayHandle, const NETDEV_PICTURE_DATA_S *pstPictureData, LPVOID lpUserParam) {
    // TODO 将YUV转为BGR图像显示;
}
int main(int argc, char *argv[]) {
    // 初始化
    NETDEV_Init();
    // 设置连接时间
    NETDEV_REV_TIMEOUT_S stRevTimeout = {0};
    stRevTimeout.dwRevTimeOut = 5;
    stRevTimeout.dwFileReportTimeOut = 30;
    NETDEV_SetRevTimeOut(&stRevTimeout);
    // 登录参数,包括设备地址、登录用户、密码等
    NETDEV_DEVICE_LOGIN_INFO_S stDevLoginInfo = {0};
    strncpy(stDevLoginInfo.szIPAddr, "xxx.xxx.xxx.xxx", sizeof(stDevLoginInfo.szIPAddr)); // 设备IP地址
    strncpy(stDevLoginInfo.szUserName, "username", sizeof(stDevLoginInfo.szUserName));    // 设备登录用户名
    strncpy(stDevLoginInfo.szPassword, "password", sizeof(stDevLoginInfo.szPassword));    // 设备登录密码
    stDevLoginInfo.dwPort = 80;                                                           // 设备服务端口
    stDevLoginInfo.dwLoginProto = NETDEV_LOGIN_PROTO_ONVIF;                               // 登录协议

    // 输出参数,仅私有协议登录有效
    NETDEV_SELOG_INFO_S stSELogInfo = {0};
    // 设备登陆
    LPVOID lUserID = NETDEV_Login_V30(&stDevLoginInfo, &stSELogInfo);
    if (NULL == lUserID) {
        printf("Login failed, error code: %d\n", NETDEV_GetLastError());

        // 释放SDK资源
        NETDEV_Cleanup();
        return -1;
    }
    // 通道查询
    NETDEV_VIDEO_CHL_DETAIL_INFO_EX_S stVideoChlDetailInfoEx[128] = {0}; // 分配128个通道
    INT32 dwCount = 128;                                                 // 128个通道
    BOOL bRet = NETDEV_QueryVideoChlDetailListEx(lUserID, &dwCount, stVideoChlDetailInfoEx);
    if (FALSE == bRet && NETDEV_E_NEEDMOREDATA == NETDEV_GetLastError()) { // 默认分配数组大小小于实际通道数,重新分配内存获取
        NETDEV_VIDEO_CHL_DETAIL_INFO_EX_S *pstVideoChlDetailInfoEx = new NETDEV_VIDEO_CHL_DETAIL_INFO_EX_S[dwCount];
        memset(pstVideoChlDetailInfoEx, 0, sizeof(NETDEV_VIDEO_CHL_DETAIL_INFO_EX_S) * dwCount);
        bRet = NETDEV_QueryVideoChlDetailListEx(lUserID, &dwCount, pstVideoChlDetailInfoEx);

        // 释放分配内存
        delete[] pstVideoChlDetailInfoEx;
    }
    // 实况预览
    // HWND hWnd = GetConsoleWindow(); // 获取窗口句柄 和海康大华一样 不用走窗口句柄
    NETDEV_PREVIEWINFO_S stNetInfo = {0};
    stNetInfo.dwChannelID = 1;
    stNetInfo.hPlayWnd = NULL; // 窗口句柄设为空
    stNetInfo.dwStreamType = NETDEV_LIVE_STREAM_INDEX_MAIN;
    stNetInfo.dwLinkMode = NETDEV_TRANSPROTOCAL_RTPTCP;
    stNetInfo.dwFluency = NETDEV_PICTURE_FLUENCY;

    // 启动预览并设置回调数据流
    LPVOID lpPlayHandle = NETDEV_RealPlay(lUserID, &stNetInfo, NULL, 0);
    if (NULL == lpPlayHandle) {
        printf("RealPlay failed, error code: %d\n", NETDEV_GetLastError());
    } else {
        printf("RealPlay success, handle: %p\n", lpPlayHandle);
    }
    // 注册原始码流
    if (!NETDEV_SetPlayDataCallBack(lpPlayHandle, YSRealDataCallBack, TRUE, NULL)) {
        printf("SetPlayDataCallBack failed, error code: %d\n", NETDEV_GetLastError());
    }
    // 注册解码后视频媒体流数据
    if (NETDEV_SetPlayDecodeVideoCB(lpPlayHandle, YSDecDataCallBack, FALSE, NULL)) {
        printf("SetPlayDecodeVideoCB failed, error code: %d\n", NETDEV_GetLastError());
    }

    std::this_thread::sleep_for(std::chrono::seconds(10));

    // 停止实况预览
    NETDEV_StopRealPlay(lpPlayHandle);

    // 注销用户
    NETDEV_Logout(lUserID);

    // 释放SDK资源
    NETDEV_Cleanup();

    return 0;
}

流媒体取流

流媒体取流是一种实时获取和传输多媒体内容的技术,广泛应用于视频监控、直播、点播等领域。它通过网络将音视频数据从源设备传输到目标设备,实现即时播放和处理。流媒体取流技术不仅能够有效降低延迟,保证播放的流畅性,还支持多种协议和编码格式,适应不同的网络环境和应用需求。通过优化的取流策略,流媒体取流可以在带宽受限的条件下依然提供高质量的音视频体验。

lal取流

lalserver是纯Golang开发的流媒体(直播音视频网络传输)服务器。目前已支持RTMP, RTSP(RTP/RTCP), HLS, HTTP[S]/WebSocket-FLV/TS, GB28181协议。并支持通过插件形式进行二次开发扩展。

启动lalserver.exe,通过下面的程序将rtsp地址转rtmp拉流。

#include <stdio.h>
#include <iostream>
#include <cpr/cpr.h>
#include <json/json.h>
using namespace std;

bool StartStream(std::string &url, std::string &stream_name, std::string &play_url) {
    std::string post_url = "http://127.0.0.1:8083/api/ctrl/start_relay_pull";
    Json::Value data;
    data["stream_name"] = stream_name;
    data["url"] = url;
    data["pull_retry_num"] = 0;
    std::string json_data = data.toStyledString();

    // 设置HTTP头和JSON数据
    cpr::Header headers{{"Content-Type", "application/json"}};
    cpr::Body body = cpr::Body{json_data};

    // 发送POST请求
    cpr::Response response = cpr::Post(cpr::Url{post_url}, body, headers);

    // 检查HTTP请求是否成功
    if (response.status_code == 200) {
        // 打印HTTP响应内容
        std::cout << "send HTTP ok:\n"
                  << response.text << std::endl;
        play_url =
            "rtmp://127.0.0.1:1935/live/" + stream_name;
        return true;
    }
    std::cout << "send HTTP err: " << response.status_code << std::endl;
    return false;
}
int main(int argc, char *argv[]) {
    std::string url = "rtsp://username:password@ip/media/video2";
    std::string stream_name = "234";
    std::string _play_url;
    StartStream(url, stream_name, _play_url);
    return 0;
}

也可以用ffmpeg 将 rtmp 流发布到 lalserver

ffmpeg -re -i demo.flv -c:a copy -c:v copy -f flv rtmp://127.0.0.1:1935/live/test110

EasyDarwin

EasyDarwin音视频流媒体服务器,支持视频点播、RTMP推流直播(RTSP、FLV、WebRTC)、串流拉流直播、服务端录像与回放、视频抽帧快照;

具体用法参照官网文档教程;

GStreamer

GStreamer 是一个用于构造媒体处理组件图形的库。它支持的应用范围从简单的 Ogg/Vorbis 播放、音频/视频流到复杂的音频(混音)和视频(非线性编辑)处理。

具体用法可以看官方文档,或者看这位大佬写的教程 gstreamer - 教程 - John.Leng - 博客园 (cnblogs.com)


文章作者: LSJune
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 LSJune !
评论
  目录