Elixir 通过 ExOnvif 库,Onvif 协议可以控制IP摄像机等设备,这篇文章记录:使用ExOnvif库,给视频流叠加文字,使用ExOnvif库的接口模块:ExOnvif.Media、ExOnvif.Media2。
ExOnvif官方文档
此文章内容:视频流叠加文字,关于Elixir通过ONVIF协议实现PTZ控制、视频流获取等指令,可以查看我的其他文章。点击查看主页
1. Media2和Media的核心关系:演进与替代
在 Onvif 协议中,Media2 是 Media 的升级版,
Media (Profile S): 是ONVIF基础版媒体服务,最早在ONVIF核心规范中定义。
它提供了基本的视频流获取、快照、音频、视频编码配置等功能。
绝大多数支持ONVIF的旧设备都实现了此服务。
Media2 (Profile T): 是ONVIF第二代媒体服务,在 media 的基础上进行了重构和大幅增强。
它是 Profile T 强制要求的标准服务。
新发布的、功能更丰富的设备(尤其是支持H.265等新编码的设备)
通常会同时支持 media 和 media2 或仅支持 media2。
关系总结: media2 并非要完全废弃 media,而是在保持向后兼容性的同时,提供了一个更先进的替代方案。两者在网络上可以共存。
2. 获取OSD
获取OSD有两种方式
- get_osd(device, token) # get the osd by token
- get_osds(device) # get the osds
设置OSD的方式
- set_osd(device, osd) #set the osd
- create_osd(device, osd) # create the osd
更新逻辑
- 首先先通过get_osds函数获取所有的osds,
- 取到需要更新的osd的token
- 调用set或create函数,修改或创建新的osd text
3. 完整的代码示例
erlang
defmodule MvOnvif.Action do
use GenServer
@moduledoc """
自定义的Onvif的部分协议
获取当前状态(exonvif)
absolute move调用摄像头到指定位置,
continuous move摄像头连续移动
调用指定预置位
停止运动
"""
import ExOnvif.Utils.XmlBuilder
import SweetXml
alias ExOnvif.Device
alias ExOnvif.Media2
# 初始化device设备
defp get_device(uri) when not is_nil(uri) do
%{host: host, userinfo: userinfo} = URI.parse(uri)
[user, pw] = String.split(userinfo, ":")
Device.new("http://" <> host, user, pw);
end
defp get_device(uri) do
:error
end
# 获取profiletoken标识符
defp get_main_stream_profile_token(device) do
profiles = Media2.get_profiles(device)
case profiles do
{:ok, list} -> {:ok, hd(list).reference_token}
_ -> "something went wrong"
end
end
# 获取文字叠加
def get_osds(uri) do
with {:ok, device} <- get_device(uri) do
ExOnvif.Media.get_osds(device)
end
end
# 创建/更新 文字叠加
def create_osd({ip, username, password}, text) do
with {:ok, device} <- get_device(ip, username, password),
{:ok, profile_token} <- get_main_stream_profile_token(device),
{:ok, source} <- ExOnvif.Media2.get_video_source_configurations(device, [profile_token: profile_token]),
{:ok, osd_list} <- ExOnvif.Media.get_osds(device)
do
%{source_token: source_token} = hd(source) #默认取主视频流
if length(osd_list) > 2 do # 我取的是第三个osd
%{token: osd_token} = List.last(osd_list)
osd = make_osd(source_token, text, osd_token)
ExOnvif.Media.set_osd(device, osd)
else
osd = make_osd(source_token, text)
ExOnvif.Media.create_osd(device, osd)
end
end
end
# %ExOnvif.Media.OSD实例
defp make_osd(source_token, text \\ "", token \\ nil) do
%ExOnvif.Media.OSD{
token: token,
video_source_configuration_token: source_token,
text_string: %ExOnvif.Media.OSD.TextString{
is_persistent_text: true,
type: :plain,
plain_text: text
},
type: :text,
position: %ExOnvif.Media.OSD.Position{
type: :upper_left,
pos: %{x: 21, y: 1}
}
}
end
end
4. xml文件示例
获取osds的xml
erlang
<wsdl:GetOSDs>
<wsdl:ConfigurationToken>VideoSourceToken_1</wsdl:ConfigurationToken>
</wsdl:GetOSDs>
修改osd的xml
erlang
<SOAP-ENV:Body>
<wsdl:SetOSD>
<wsdl:OSDToken>OSDToken_001</wsdl:OSDToken> <!-- 要修改的OSD令牌 -->
<wsdl:OSD>
<tt:Position>
<tt:Pos>
<tt:x>0.85</tt:x>
<tt:y>0.05</tt:y>
</tt:Pos>
</tt:Position>
<tt:TextString>
<tt:FontSize>16</tt:FontSize>
<tt:FontColor>0xFF0000</tt:FontColor> <!-- 改为红色 -->
<tt:PlainText>MAIN GATE - CAM01</tt:PlainText>
</tt:TextString>
</wsdl:OSD>
</wsdl:SetOSD>
</SOAP-ENV:Body>
创建osd的xml
erlang
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"
xmlns:wsdl="http://www.onvif.org/ver20/device/wsdl"
xmlns:tt="http://www.onvif.org/ver10/schema">
<SOAP-ENV:Header>
<!-- 安全认证头(同前) -->
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<wsdl:CreateOSD>
<wsdl:OSD>
<tt:VideoSourceConfigurationToken>VideoSourceToken_1</tt:VideoSourceConfigurationToken>
<tt:Type>Text</tt:Type>
<tt:Position>
<tt:Type>Custom</tt:Type> <!-- 或 UpperLeft/UpperRight/LowerLeft/LowerRight -->
<tt:Pos>
<tt:x>0.8</tt:x> <!-- 0-1.0 水平位置 -->
<tt:y>0.1</tt:y> <!-- 0-1.0 垂直位置 -->
</tt:Pos>
</tt:Position>
<tt:TextString>
<tt:Type>Plain</tt:Type> <!-- 或 Date/Time/DateAndTime -->
<tt:DateFormat>yyyy-MM-dd</tt:DateFormat>
<tt:TimeFormat>HH:mm:ss</tt:TimeFormat>
<tt:FontSize>14</tt:FontSize>
<tt:FontColor>0x00FF00</tt:FontColor> <!-- RGB 格式: 0xRRGGBB -->
<tt:BackgroundColor>0x000000</tt:BackgroundColor>
<tt:PlainText>Camera 01 - Main Gate</tt:PlainText>
<tt:Extension>
<tt:IsPersistentText>true</tt:IsPersistentText>
</tt:Extension>
</tt:TextString>
</wsdl:OSD>
</wsdl:CreateOSD>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>