ONVIF -- 自動偵測IPCam 的流程分析
update:2021-9-30
使用一台符合 ONVIF 標準的 NVR 與 IPCam,測試並記錄其自動偵測 IPCam 以及加入 IPCam 的流程,以(Gxxx NVR為例)

自動偵測 IPCam

  1. NVR 會使用 multicast的方式,
       傳送資料到 239.255.255.250 port 3702(ws-discovery)

 

  1. 若網路上存在 IPCam,則會回應此訊息
  2. NVR 與 IPCam 完成 ws-discovery 流程之後,其實此時就已經找到 IPCam了
       後續便可以改成使用 ONVIF 的訊息進行溝通

設定IPCam 

當確定 IPCam 的 IP位址之後,就可以透過 ONVIF 指令設定 IPCam

  1. 取得系統基本資訊

    1. GetNetworkInterfaces(此時沒帶帳號密碼,因此可能會失敗)
    2. GetSystemDateAndTime(此時沒帶帳號密碼,因此可能會失敗)
    3. GetNetworkInterfaces(此時會帶帳號密碼,可得知IPCam有幾張網路卡,對應的ipaddress)
    4. GetCapabilities(詢問IPCam支援ONVIF定義的那些能力,例如Imaging,Media等等)
  2. 取得 video 設定資訊

    1. GetProfiles(取得IPCam預設的Profiles,一個Profile主要設定溝通時所採用的meida,
      可能會設定對應的video source, video encoder等資訊)
    2. GetVideoEncoderConfigurationOptions
    3. GetVideoEncoderConfigurations
    4. GetVideoSourceConfigurations
    5. GetVideoSources
    6. GetGuaranteedNumberOfVideoEncoderInstances
  3. 取得網路與audio 設定資訊
    GetProfiles

GetNetworkInterfaces
GetNetworkDefaultGateway
GetDeviceInformation
GetRelayOutputs
GetAudioEncoderConfigurationOptions
GetAudioSources
GetAudioSourceConfigurations
GetAudioEncoderConfigurations

  1. 取得 PTZ 資訊
    GetProfiles

GetConfigurations
GetConfiguration
GetNode
GetPresets
GetOptions
GetMoveOptions
GetOptions

  1. 設定 Video/Audio,其作法固定會先Get一次,之後才做Set
    GetVideoSourceConfigurations

SetVideoSourceConfigurations
GetAudioSourceConfigurations
SetVideoSourceConfigurations
GetAudioSourceConfigurations
SetAudioSourceConfigurations

  1. 開始播放串流
    GetStreamUri

SetSynchronizationPoint
此時 NVR 便開始與 IPCam 建立RTSP連線,當連線建立之後便會回復 SetSynchronizationPointResponse,所以當收到 SetSynchronizationPointResponse 時,RTP 可能就已經在傳送封包了。

心得

使用其他廠商NVR測試,Get/Set流程略有不同,但基本上都屬上述六個步驟的範疇。
上述流程沒有記錄到 CreateProfiles。
幾個值得特別注意的點
一開始設定尋找設備時,便已經設定帳號密碼了,但是GetNetworkInterfaces仍會試著作一次無帳號密碼的詢問?
GetAudioSourceConfigurations 之後,卻呼叫 SetVideoSourceConfigurations?
GetVideoXXX 與 GetAudioXXX 的流程並不一致,Why?
Referenced from:http://albert-oma.blogspot.com/2012/07/onvif-ipcam.html

海康相机支持onvif协议,因此可以通过该协议来获得视频数据以及控制相机云台,在正式写代码控制之前,先使用onvif测试工具进行简单的测试,保证相机相关配置正确。

1 工具下载
测试工具使用ONVIF Device Test Tool,具体下载地址可自行百度,一路默认安装即可。

2 相机配置
在这里插入图片描述
进入海康相机设置平台,选择配置-高级配置-勾选启用ONVIF-添加用户-输入登陆用户名和密码-保存

以上即可完成相机ONVIF的相关配置

3 连接设备
接下来可在ONVIF Devive Test Tool测试工具中测试相机了,打开测试软件
在这里插入图片描述
首先在NIC中选择与相机同一网段的网卡,点击Discover Devices,即可搜索到在该网段支持ONVIF协议的网络相机
在这里插入图片描述
首先在发现设备列表中选择目标相机,在右侧信息中会自动填写相机的参数,然后填写用户名和密码,点击check,即可获得相机的Brand、Model等参数,即第4点钟的信息填写完成,表面设备配置成功
在这里插入图片描述
首先在发现设备列表中选择目标相机,在右侧信息中会自动填写相机的参数,然后填写用户名和密码(因为基于ONVIF协议搜索摄像头需要鉴权,即认证登录),点击check,即可获得相机的Brand、Model等参数,即第4点钟的信息填写完成,表面设备配置成功

4 视频测试
这一步我们使用测试工具获得相机视频,

首先切换到“Debug”选项卡点击“Media”(对应音频和视频等媒体的探测与控制)“Get” Media URL“Get” Media Profile,发现默认选中的是“TestMediaProfile”选择“main Stream(Profile_1)”,即可自动获得Video和Audio的相关参数点击下方Play Video即可打卡相机视频,如图所示
在这里插入图片描述

在左上方“Authentication”中选择权限验证方式(None为不发送用户名密码,WS-Username token是onvif标准的用户名密码验证方式,Digest是RTSP中的Digest形式,用户名密码就是从Discovery那里读过来的) 。不要旋转None,否则会”Access Denied。“

5 PTZ测试
云台测试方法与视频测试方法类似,切换到PTZ选项卡–Get URLs–Get Profile–选择main Stream–PTZ Control

在PTZ Control中用多种控制模式,分别是绝对位置控制、相对位置控制和连续控制,可自行测试看看效果

在这里插入图片描述

6 Requests模式
除了使用上述方法来控制云台外,我们还可以使用Requests的方式来发送指令,同时收到Response
在这里插入图片描述
如图所示为PTZ测试示例,有几个地方要注意:1 是要把Service和Service Address修改为要测试的模块地址,2 是注意模板中的Porfile Token要修改为自己的Profile,默认的prof0,3 是注意修改设置x y的数值

设置完成后,点击Send Request,当返回200 OK时表明测试成功。

同样的,我们可以使用类似的流程来测试其它功能,比如测试Device Management模块的GetCapabilities功能,发送及接收如图所示

在这里插入图片描述

Referenced from:https://blog.csdn.net/zhizhengguan/article/details/109289728

c++ - 使用libVLC读取RTSP视频流+将数据转换为OpenCV Mat
update:2021-9-30
我正在尝试使用libVLC库通过IP接口(interface)从Sony FCB-EV7520摄像机读取RTSP流,并将数据转换为OpenCV使用的格式,即Mat类型。
代码是:

include <stdio.h>

include <vlc/vlc.h>

include <stdint.h>

include <opencv2/opencv.hpp>

include <opencv2/highgui/highgui.hpp>

using namespace std;
using namespace cv;

struct VideoDataStruct {

int param;

};

int done = 0;
libvlc_media_player_t *mp;
unsigned int videoBufferSize = 0;
uint8_t *videoBuffer = 0;

void cbVideoPrerender(void p_video_data, uint8_t *pp_pixel_buffer, int size) {

// Locking

// Locking a mutex here once I get the code to work.    

if (size > videoBufferSize || !videoBuffer){

    printf("Reallocate raw video buffer\n");
    free(videoBuffer);
    videoBuffer = (uint8_t *) malloc(size);
    videoBufferSize = size;

}

*pp_pixel_buffer = videoBuffer;

}

void cbVideoPostrender(void p_video_data, uint8_t p_pixel_buffer, int width, int height, int pixel_pitch, int size, int64_t pts) {

// Unlocking

// Unlocking the mutex here once I get the code to work.

}

static void handleEvent(const libvlc_event_t pEvt, void pUserData) {

libvlc_time_t time;
switch(pEvt->type){

    case libvlc_MediaPlayerTimeChanged:

        time = libvlc_media_player_get_time(mp);
        printf("MediaPlayerTimeChanged %lld ms\n", (long long)time);
        break;

    case libvlc_MediaPlayerEndReached:

        printf ("MediaPlayerEndReached\n");
        done = 1;
        break;

    default:

        printf("%s\n", libvlc_event_type_name(pEvt->type));

}

}

int main(int argc, char* argv[]) {

// VLC pointers: 

libvlc_instance_t *inst;
libvlc_media_t *m;
void *pUserData = 0;
VideoDataStruct dataStruct;

// VLC options:

char smem_options[1000];

// RV24:

sprintf(smem_options
        , "#transcode{vcodec=h264}:smem{"
        "video-prerender-callback=%lld,"
        "video-postrender-callback=%lld,"
        "video-data=%lld,"
        "no-time-sync},"
        , (long long int)(intptr_t)(void*)&cbVideoPrerender
        , (long long int)(intptr_t)(void*)&cbVideoPostrender
        , (long long int)(intptr_t)(void*)&dataStruct
       );

const char * const vlc_args[] = {
    "-I", "dummy",            // Don't use any interface
    "--ignore-config",        // Don't use VLC's config
    "--extraintf=logger",     // Log anything
    "--verbose=2",            // Be verbose
    "--sout", smem_options    // Stream to memory
};

// We launch VLC

inst = libvlc_new(sizeof(vlc_args) / sizeof(vlc_args[0]), vlc_args);

/* Create a new item */

m = libvlc_media_new_location(inst, "rtsp://*****:*******@IP/videoinput_1/h264_1/media.stm");

/* Create a media player playing environement */

mp = libvlc_media_player_new_from_media (m);

libvlc_event_manager_t* eventManager = libvlc_media_player_event_manager(mp);
libvlc_event_attach(eventManager, libvlc_MediaPlayerTimeChanged, handleEvent, pUserData);
libvlc_event_attach(eventManager, libvlc_MediaPlayerEndReached, handleEvent, pUserData);
libvlc_event_attach(eventManager, libvlc_MediaPlayerPositionChanged, handleEvent, pUserData);

libvlc_video_set_format(mp, "h264", 1920, 1080, 1920* 3 );

/* play the media_player */
libvlc_media_player_play (mp);

while(1){

    if(videoBuffer){            // Check for invalid input

        Mat img = Mat(Size(1920, 1080), CV_8UC3, videoBuffer);  // CV_8UC3 = 8 bits, 3 chanels
        namedWindow("Display window", WINDOW_AUTOSIZE);     // Create a window for display.
        imshow("Display window", img);              // Show our image inside it.

    }
}

libvlc_release (inst);

}
Referenced from:https://www.coder.work/article/2096092

代码:

#include <vlc/vlc.h>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <stdio.h>

using namespace cv; 
using namespace std;

struct VideoDataStruct {
  int param;
};

int done = 0;
libvlc_media_player_t *mp;
unsigned int videoBufferSize = 0;
uint8_t *videoBuffer = 0;

 void cbVideoPrerender(void *p_video_data, uint8_t **pp_pixel_buffer, int size) {
// Locking
  if (size > videoBufferSize || !videoBuffer)
  {
    printf("Reallocate raw video buffer\n");
    free(videoBuffer);
    videoBuffer = (uint8_t *) malloc(size);
    videoBufferSize = size;
  }

   *pp_pixel_buffer = videoBuffer;
}  

 void cbVideoPostrender(void *p_video_data, uint8_t *p_pixel_buffer, int width, int height, int pixel_pitch, int size, int64_t pts) {
     // Unlocking
     //CloseHandle(hMutex);
 }

 static void handleEvent(const libvlc_event_t* pEvt, void* pUserData) {
   libvlc_time_t time;
   switch(pEvt->type)
   {
      case libvlc_MediaPlayerTimeChanged:
          time = libvlc_media_player_get_time(mp);
          printf("MediaPlayerTimeChanged %lld ms\n", (long long)time);
          break;
      case libvlc_MediaPlayerEndReached:
          printf ("MediaPlayerEndReached\n");
          done = 1;
        break;
       default:
          printf("%s\n", libvlc_event_type_name(pEvt->type));
    }
}

int main(int argc, char* argv[]) {

// VLC pointers 
libvlc_instance_t *inst;
libvlc_media_t *m;
void *pUserData = 0;
VideoDataStruct dataStruct;

// VLC options
char smem_options[1000];
// RV24
sprintf(smem_options
    , "#transcode{vcodec=RV24}:smem{"
     "video-prerender-callback=%lld,"
     "video-postrender-callback=%lld,"
     "video-data=%lld,"
     "no-time-sync},"
    , (long long int)(intptr_t)(void*)&cbVideoPrerender
    , (long long int)(intptr_t)(void*)&cbVideoPostrender
    , (long long int)(intptr_t)(void*)&dataStruct
);

const char * const vlc_args[] = {
          "-I", "dummy",            // Don't use any interface
          "--ignore-config",        // Don't use VLC's config
          "--extraintf=logger",     // Log anything
          "--verbose=1",            // Be verbose
          "--sout", smem_options    // Stream to memory
           };

// We launch VLC
inst = libvlc_new(sizeof(vlc_args) / sizeof(vlc_args[0]), vlc_args);

/* Create a new item */
m = libvlc_media_new_location(inst, "5.mp4");

/* Create a media player playing environement */
mp = libvlc_media_player_new_from_media (m);

libvlc_event_manager_t* eventManager = libvlc_media_player_event_manager(mp);
libvlc_event_attach(eventManager, libvlc_MediaPlayerTimeChanged, handleEvent, pUserData);
libvlc_event_attach(eventManager, libvlc_MediaPlayerEndReached, handleEvent, pUserData);
libvlc_event_attach(eventManager, libvlc_MediaPlayerPositionChanged, handleEvent, pUserData);

libvlc_video_set_format(mp, "RV24", 1280, 720, 1280* 3 );

/* play the media_player */
libvlc_media_player_play (mp);

 while(1)
 {
     if(videoBuffer)            // Check for invalid input
     {
         // CV_8UC3 = 8 bits, 3 chanels
         Mat img = Mat(Size(1280, 720), CV_8UC3, videoBuffer);
         // cvtColor(img, img, CV_RGB2BGR);
         namedWindow("Display window", WINDOW_AUTOSIZE);    // Create a window for display.
         imshow("Display window", img);     // Show our image inside it.
     }
 }
 libvlc_release (inst);
}

https://stackoverflow.com/questions/28687473/open-mp4-with-libvlc-and-play-it-in-opencv-c

ffmpeg -i test.mp4 -r 30 image-%3d.jpg
ffmpeg -i test.mp4 -r 30 -t 4 image-%3d.jpg
ffmpeg -i test.mp4 -r 30 -ss 00:00:20 image-%3d.jpg
ffmpeg -i test.mp4 -r 30 -ss 00:00:20 -vframes 10 image-%3d.jpg
ffmpeg -i test.mp4 -r 30 image-%3d.jpg

test.mp4视频文件
-r 30 每秒提取30帧,一般是24帧 image-%3d 文件命名格式是image-001.jpg

ffmpeg -i test.mp4 -r 30 -t 4 image-%3d.jpg
-t,表示取t秒时间的帧

ffmpeg -i test.mp4 -r 30 -ss 00:00:20 image-%3d.jpg
-ss,表示截取帧初始时间

ffmpeg -i test.mp4 -r 30 -ss 00:00:20 -vframes 10 image-%3d.jpg
-vframes,表示截取多少帧

Referenced from:https://blog.csdn.net/Gary__123456/article/details/89154095