版權聲明:本文爲博主原創文章,如要轉載請標明出處。 https://blog.csdn.net/benkaoya/article/details/72490332
1 專欄導讀
本專欄第一篇文章「專欄開篇」列出了專欄的完整目錄,按目錄順序閱讀,有助於你的理解,專欄前面文章講過的知識點(或代碼段),後面文章不會贅述。爲了節省篇幅,突出重點,在文章中展示的示例代碼僅僅是關鍵代碼,你可以在「專欄開篇」中獲取完整代碼。
如有錯誤,歡迎你的留言糾正!讓我們共同成長!你的「點贊」或「打賞」是對我最大的支持和鼓勵!
2 原理簡介
IPC有關多媒體的參數,都是由媒體配置文件(media profile)來管理。想通過ONVIF協議修改IPC諸如分辨率這樣的媒體參數,就得先弄懂媒體配置文件。
媒體配置文件(media profile)是用於管理音視頻流相關的一系列配置的集合,一個配置文件由一系列相關聯的配置類實體構成。配置類包括:
- Video source configuration(視頻源配置)
- Audio source configuration(音頻源配置)
- Video encoder configuration(視頻編碼器配置)
- Audio encoder configuration(視頻編碼器配置)
- PTZ configuration(PTZ配置)
- Video analytics configuration(視頻分析配置)
- Metadata configuration(元數據配置)
- Audio output configuration(音頻輸出配置)
- Audio decoder configuration(音頻解碼器配置)
對應的結構體爲:
struct tt__Profile {
char *Name;
struct tt__VideoSourceConfiguration *VideoSourceConfiguration;
struct tt__AudioSourceConfiguration *AudioSourceConfiguration;
struct tt__VideoEncoderConfiguration *VideoEncoderConfiguration;
struct tt__AudioEncoderConfiguration *AudioEncoderConfiguration;
struct tt__VideoAnalyticsConfiguration *VideoAnalyticsConfiguration;
struct tt__PTZConfiguration *PTZConfiguration;
struct tt__MetadataConfiguration *MetadataConfiguration;
struct tt__ProfileExtension *Extension;
char *token;
enum xsd__boolean *fixed;
};
一個tt__Profile結構體就是一個媒體配置文件,一個配置文件由全部的或部分的配置類實體組成,“部分”的意思是,對於不支持的功能(如PTZ),允許其配置信息爲空(PTZConfiguration爲NULL)。
這裏有一個概念一定要理清楚,配置類和實體的區別:
- 就某個配置類而言,一個設備可以有多個實體。如「視頻編碼器配置」類,一個IPC設備至少包含兩個「視頻編碼器配置」實體,分別關聯主碼流和輔碼流,這兩個「視頻編碼器配置」實體參數有所區別,如分辨率不同,碼率不同等等。
- 一個媒體配置文件由一不同配置類的實體組成,同一配置類的不同實體,只能有一個實體跟媒體配置文件關聯。
- 一個設備可以有多個媒體配置文件,如主碼流、輔碼流就是兩個不同的媒體配置文件。
爲了唯一標識某個配置實體,每個配置實體都有對應的唯一標識符token,很多的ONVIF媒體接口也是通過這些token來訪問(修改)這些配置的。比如,視頻源配置token、音頻源配置token、視頻編碼器配置token,甚至連媒體配置文件本身都有token。如下圖紅色矩形框所示(GetProfiles接口):
3 函數接口
對於某個具體的配置類,ONVIF都提供了一套完整的函數接口,類似如下:
- AddXXXConfiguration:新增XXX配置
- RemoveXXXConfiguration:刪除指定的XXX配置
- GetXXXConfigurations:獲取所有的XXX配置
- GetXXXConfiguration:獲取指定的XXX配置
- GetXXXConfigurationOptions:獲取XXX配置選項集
- GetCompatibleXXXConfigurations:獲取所有兼容的XXX配置
- SetXXXConfiguration:修改指定的XXX配置
將XXX替換成VideoEncoder,就得到了「視頻編碼器配置」類相關的ONVIF接口,其他配置類也如此:
- AddVideoEncoderConfiguration:新增視頻編碼器配置
- RemoveVideoEncoderConfiguration:刪除指定的視頻編碼器配置
- GetVideoEncoderConfigurations:獲取所有的視頻編碼器配置
- GetVideoEncoderConfiguration:獲取指定的視頻編碼器配置
- GetVideoEncoderConfigurationOptions:獲取視頻編碼器配置選項集
- GetCompatibleVideoEncoderConfigurations:獲取所有兼容的視頻編碼器配置
- SetVideoEncoderConfiguration:修改指定的視頻編碼器配置
「媒體配置文件」與「視頻編碼器配置」的關係:
ONVIF對應的函數接口 | 備註 | |
---|---|---|
一個設備可以有多個「媒體配置文件」 | GetProfile GetProfiles | 比如主碼流、輔碼流,是兩個相互獨立的媒體配置文件。每個媒體配置文件的token不同。 |
一個設備可以有多個「視頻編碼器配置」實體 | GetVideoEncoderConfiguration GetVideoEncoderConfigurations | |
一個「媒體配置文件」只能關聯一個「視頻編碼器配置」實體 | AddVideoEncoderConfiguration | |
每個「視頻編碼器配置」實體獨享屬於自己的一套參數選項集 | GetVideoEncoderConfigurationOptions | 比如IPC的主碼流,視頻分辨率只能設置爲預先設定好的幾個分辨率(選項集)中的一種,不能隨意配置。可選擇的分辨率是IPC出廠時就與設定好的,沒法通過ONVIF接口增/刪/改。 “獨享”的意思,就是視頻編碼器配置1的分辨率,只能在視頻編碼器配置1中使用,不能在視頻編碼器配置2中使用。比如主碼流的1080P分辨率,不能配置到輔碼流中。 |
4 編碼流程
IPC客戶端通過ONVIF修改分辨率的步驟如下:
- 先通過GetProfiles獲取所有媒體配置文件,可得知主/輔碼流的視頻編碼器配置token;
- 由視頻編碼器配置token通過SetVideoEncoderConfiguration修改視頻編碼器配置(如修改分辨率、幀率、碼率等);
- 修改的參數必須在GetVideoEncoderConfigurationOptions中的選項集範圍內(即IPC出廠時預設定好的那幾個選項集),如分辨率只能設置爲那幾種中的一種,不能隨意設置。否則SetVideoEncoderConfiguration會返回失敗。
- 每個視頻編碼器配置的分辨率可選集,只能自個使用,不能用於其他視頻編碼器配置中。比如主碼流的1080P分辨率,不能配置到輔碼流中,SetVideoEncoderConfiguration會調用失敗。
5 示例代碼
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "onvif_comm.h"
#include "onvif_dump.h"
/************************************************************************
**函數:ONVIF_GetVideoEncoderConfigurationOptions
**功能:獲取指定視頻編碼器配置的參數選項集
**參數:
[in] MediaXAddr - 媒體服務地址
[in] ConfigurationToken - 視頻編碼器配置的令牌字符串,如果爲NULL,則獲取所有視頻編碼器配置的選項集(會雜在一起,無法區分選項集是歸屬哪個視頻編碼配置器的)
**返回:
0表明成功,非0表明失敗
**備註:
1). 有兩種方式可以獲取指定視頻編碼器配置的參數選項集:一種是根據ConfigurationToken,另一種是根據ProfileToken
************************************************************************/
int ONVIF_GetVideoEncoderConfigurationOptions(const char *MediaXAddr, char *ConfigurationToken)
{
int result = 0;
struct soap *soap = NULL;
struct _trt__GetVideoEncoderConfigurationOptions req;
struct _trt__GetVideoEncoderConfigurationOptionsResponse rep;
SOAP_ASSERT(NULL != MediaXAddr);
SOAP_ASSERT(NULL != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));
memset(&req, 0x00, sizeof(req));
memset(&rep, 0x00, sizeof(rep));
req.ConfigurationToken = ConfigurationToken;
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
result = soap_call___trt__GetVideoEncoderConfigurationOptions(soap, MediaXAddr, NULL, &req, &rep);
SOAP_CHECK_ERROR(result, soap, "GetVideoEncoderConfigurationOptions");
dump_trt__GetVideoEncoderConfigurationOptionsResponse(&rep);
EXIT:
if (NULL != soap) {
ONVIF_soap_delete(soap);
}
return result;
}
/************************************************************************
**函數:ONVIF_GetVideoEncoderConfiguration
**功能:獲取設備上指定的視頻編碼器配置信息
**參數:
[in] MediaXAddr - 媒體服務地址
[in] ConfigurationToken - 視頻編碼器配置的令牌字符串
**返回:
0表明成功,非0表明失敗
**備註:
************************************************************************/
int ONVIF_GetVideoEncoderConfiguration(const char *MediaXAddr, char *ConfigurationToken)
{
int result = 0;
struct soap *soap = NULL;
struct _trt__GetVideoEncoderConfiguration req;
struct _trt__GetVideoEncoderConfigurationResponse rep;
SOAP_ASSERT(NULL != MediaXAddr);
SOAP_ASSERT(NULL != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
memset(&req, 0x00, sizeof(req));
memset(&rep, 0x00, sizeof(rep));
req.ConfigurationToken = ConfigurationToken;
result = soap_call___trt__GetVideoEncoderConfiguration(soap, MediaXAddr, NULL, &req, &rep);
SOAP_CHECK_ERROR(result, soap, "GetVideoEncoderConfiguration");
dump_trt__GetVideoEncoderConfigurationResponse(&rep);
EXIT:
if (NULL != soap) {
ONVIF_soap_delete(soap);
}
return result;
}
/************************************************************************
**函數:ONVIF_SetVideoEncoderConfiguration
**功能:修改指定的視頻編碼器配置信息
**參數:
[in] MediaXAddr - 媒體服務地址
[in] venc - 視頻編碼器配置信息
**返回:
0表明成功,非0表明失敗
**備註:
1). 所設置的分辨率必須是GetVideoEncoderConfigurationOptions返回的“分辨率選項集”中的一種,否則調用SetVideoEncoderConfiguration會失敗。
************************************************************************/
int ONVIF_SetVideoEncoderConfiguration(const char *MediaXAddr, struct tagVideoEncoderConfiguration *venc)
{
int result = 0;
struct soap *soap = NULL;
struct _trt__GetVideoEncoderConfiguration gVECfg_req;
struct _trt__GetVideoEncoderConfigurationResponse gVECfg_rep;
struct _trt__SetVideoEncoderConfiguration sVECfg_req;
struct _trt__SetVideoEncoderConfigurationResponse sVECfg_rep;
SOAP_ASSERT(NULL != MediaXAddr);
SOAP_ASSERT(NULL != venc);
SOAP_ASSERT(NULL != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));
memset(&gVECfg_req, 0x00, sizeof(gVECfg_req));
memset(&gVECfg_rep, 0x00, sizeof(gVECfg_rep));
gVECfg_req.ConfigurationToken = venc->token;
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
result = soap_call___trt__GetVideoEncoderConfiguration(soap, MediaXAddr, NULL, &gVECfg_req, &gVECfg_rep);
SOAP_CHECK_ERROR(result, soap, "GetVideoEncoderConfiguration");
if (NULL == gVECfg_rep.Configuration) {
SOAP_DBGERR("video encoder configuration is NULL.\n");
goto EXIT;
}
memset(&sVECfg_req, 0x00, sizeof(sVECfg_req));
memset(&sVECfg_rep, 0x00, sizeof(sVECfg_rep));
sVECfg_req.ForcePersistence = xsd__boolean__true_;
sVECfg_req.Configuration = gVECfg_rep.Configuration;
if (NULL != sVECfg_req.Configuration->Resolution) {
sVECfg_req.Configuration->Resolution->Width = venc->Width;
sVECfg_req.Configuration->Resolution->Height = venc->Height;
}
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
result = soap_call___trt__SetVideoEncoderConfiguration(soap, MediaXAddr, NULL, &sVECfg_req, &sVECfg_rep);
SOAP_CHECK_ERROR(result, soap, "SetVideoEncoderConfiguration");
EXIT:
if (SOAP_OK == result) {
SOAP_DBGLOG("\nSetVideoEncoderConfiguration success!!!\n");
}
if (NULL != soap) {
ONVIF_soap_delete(soap);
}
return result;
}
void cb_discovery(char *DeviceXAddr)
{
int stmno = 0; // 碼流序號,0爲主碼流,1爲輔碼流
int profile_cnt = 0; // 設備配置文件個數
struct tagProfile *profiles = NULL; // 設備配置文件列表
struct tagCapabilities capa; // 設備能力信息
ONVIF_GetCapabilities(DeviceXAddr, &capa); // 獲取設備能力信息(獲取媒體服務地址)
profile_cnt = ONVIF_GetProfiles(capa.MediaXAddr, &profiles); // 獲取媒體配置信息(主/輔碼流配置信息)
if (profile_cnt > stmno) {
struct tagVideoEncoderConfiguration venc;
char *vencToken = profiles[stmno].venc.token;
ONVIF_GetVideoEncoderConfigurationOptions(capa.MediaXAddr, vencToken); // 獲取該碼流支持的視頻編碼器參數選項集
ONVIF_GetVideoEncoderConfiguration(capa.MediaXAddr, vencToken); // 獲取該碼流當前的視頻編碼器參數
venc = profiles[stmno].venc;
venc.Height = 960;
venc.Width = 1280;
ONVIF_SetVideoEncoderConfiguration(capa.MediaXAddr, &venc); // 設置該碼流當前的視頻編碼器參數
ONVIF_GetVideoEncoderConfiguration(capa.MediaXAddr, vencToken); // 觀察是否修改成功
}
if (NULL != profiles) {
free(profiles);
profiles = NULL;
}
}
int main(int argc, char **argv)
{
ONVIF_DetectDevice(cb_discovery);
return 0;
}