All About Stream Settings¶
If you've never struggled with the difference between "H.264" and "h264" or "true" and "True", then you've never used Set-VmsCameraStream
. Read on to learn about stream settings and how to configure them from PowerShell so that you can spend less time interpreting validation errors and more time on the important things.
Introduction¶
Milestone supports a lot of devices, and most of these supported devices are implemented as "device drivers" which are installed as a part of a "Milestone XProtect VMS Device Pack". Each device pack driver enables support for one or more devices. Most of the time a single device pack driver provides support for a whole series of hardware models. For example nearly every modern Axis camera should be using the "AXIS" driver, and during hardware discovery the driver will build a unique hardware definition for each device based on the capabilities discovered. The capabilities reported by the camera can even change over time as firmware is upgraded, or device packs are upgraded.
This implementation enables a lot of flexibility around device driver development as there are very few constraints around what can, or must be implemented. If a new type of codec or stream-related feature arrives on the market, we're able to add support for that new feature on specific drivers without having to update all the device drivers that came before. We saw this several years ago with the introduction of "smart codecs" like Axis Zipstream.
The tradeoff for this flexibility is consistency from driver to driver. Since every driver is free to define it's own set of properties for each camera, it leaves room for inconsistency in the names or supported values from one driver to the next. For example, most cameras include a "Resolution" and "FPS" property, but not all cameras supported by Milestone expose properties like that through their API. The device may only provide the option to choose from "Stream 1" or "Stream 2", or it might abstract the stream settings away into a simple "HD" and "SD" designation. Furthermore, values are case-sensitive and without hard-coded guard rails, the "Codec" property for one camera in Milestone might expect a value of "h264" while a property with the same name on another model might expect "H264".
Scenario¶
We have a camera named "Parking Lot - East", and it uses the same driver as the other 130 parking cameras. There's a recording server on site, but video is accessed remotely over a low-bandwidth connection. We need to add a second stream for all these cameras and set "Video stream 1" to the highest resolution possible. Video stream 2 should be used for live and playback by default, with a resolution no higher than 720p.
Stream | Codec | Resolution | FPS | GOPLength | LiveDefault | PlaybackDefault | RecordingTrack |
---|---|---|---|---|---|---|---|
Video stream 1 | H.265/H.264 | Highest | 15 | 1 seconds | False | False | Primary |
Video stream 2 | H.265/H.264 | 1280x720 | 4 | 8 seconds | True | True | Secondary |
Using the adaptive streaming feature, and the adaptive playback feature introduced in 2023 R2, I can configure these cameras to record one stream at high resolution, and the other at a lower resolution, and the low resolution stream can be made the default for both live and playback. That way, whether users are local or remote, they will only pull the high quality stream from the recording server if the view tile is larger than the low quality stream, or when performing a video export.
Get-VmsCameraStream¶
The bad news is that you are responsible for sorting out what values Set-VmsCameraStream
expects for each of the settings you want to change. The good news? The Get-VmsCameraStream
command tells you everything you need to know! This pair of commands provides a layer of abstraction over two different concepts in Milestone's Configuration API - streams, and stream usages. In the following exercise we'll explore the output of Get-VmsCameraStream
and where the information comes from in the underlying .NET classes from MIP SDK.
Get all streams¶
Let's start by retrieving all the streams from our reference camera:
$camera = Get-VmsCamera -Name 'Parking Lot - East'
$streams = $camera | Get-VmsCameraStream
$streams
Camera | Name | DisplayName | Enabled | LiveMode | LiveDefault | Recorded | RecordingTrack | PlaybackDefault | UseEdge |
---|---|---|---|---|---|---|---|---|---|
Parking Lot - East | Video stream 1 | Video stream 1 | True | WhenNeeded | True | True | Primary recording | True | False |
Parking Lot - East | Video stream 2 | False | False | False | No recording | False | False | ||
Parking Lot - East | Video stream 3 | False | False | False | No recording | False | False | ||
Parking Lot - East | Video stream 4 | False | False | False | No recording | False | False | ||
Parking Lot - East | Video stream 5 | False | False | False | No recording | False | False | ||
Parking Lot - East | Video stream 6 | False | False | False | No recording | False | False | ||
Parking Lot - East | Video stream 7 | False | False | False | No recording | False | False | ||
Parking Lot - East | Video stream 8 | False | False | False | No recording | False | False |
Settings¶
Internally, stream settings are stored as key/value pairs where both the keys and and values are somewhat short, sometimes abbreviated, and often written in pascal case. This isn't an accessible or user-friendly way to describe the settings, so many values for settings will have a "display value" so that in a user interface, the administrator sees "Variable bit rate" instead of "VariableBitRate".
When using Get-VmsCameraStream
, the settings for the stream are available using the Settings
hashtable property where the keys are the same keys used when changing settings using Set-VmsCameraStream
, but the values are, by default, the display values shown in Management Client. When you need to know the raw values, you can use the -RawValues
switch. Here's a comparison of the display vs raw values for our sample camera:
$camera | Get-VmsCameraStream -Name 'Video stream 1' | Select-Object -ExpandProperty Settings
<# OUTPUT
Name Value
---- -----
ZFpsMode Fixed
StreamReferenceId 28DC44C3-079E-4C94-8EC9-60363451EB40
StreamingMode RTP/RTSP/TCP
ControlMode Variable bit rate
TargetBitrate 2000
Compression 30
Resolution 1920x1080
FPS 8.0
MaxGOPMode Default (determined by driver)
ControlPriority None
IncludeTime No
EdgeStorageSupported true
ZGopLength 300
ZGopMode Fixed
MaxBitrate 0
MaxGOPSize 30
ZStrength Low
RetentionTime 7
IncludeDate No
Codec H.264
#>
$camera | Get-VmsCameraStream -Name 'Video stream 1' -RawValues | Select-Object -ExpandProperty Settings
<# OUTPUT
Name Value
---- -----
ZFpsMode fixed
StreamReferenceId 28DC44C3-079E-4C94-8EC9-60363451EB40
StreamingMode TCP
ControlMode VariableBitRate
TargetBitrate 2000
Compression 30
Resolution 1920x1080
FPS 8.0
MaxGOPMode default
ControlPriority None
IncludeTime no
EdgeStorageSupported True
ZGopLength 300
ZGopMode fixed
MaxBitrate 0
MaxGOPSize 30
ZStrength low
RetentionTime 7
IncludeDate no
Codec h264
#>
Setting Display Names¶
The output from Get-VmsCameraStream
provides us with the setting "keys", the display values, and the raw values. We even get the ID's needed to look up translations. But the one thing it doesn't give us is the display names associated with the keys for each setting. This is because the strongly-typed StreamChildItem
classes from MIP SDK don't give them to us, and MilestonePSTools doesn't retrieve them for you automatically for performance reasons. It's an additional API call, and when making hundreds or thousands of changes, the wasted time can really add up.
To get the display names for the stream properties, we can use Get-ConfigurationItem
.
$item = Get-ConfigurationItem -Path "DeviceDriverSettings[$($camera.Id)]"
$stream1 = $item.Children | Where-Object DisplayName -eq $streams[0].Name
$stream1.Properties | Select-Object DisplayName, Key, Value, ValueType | Sort-Object DisplayName
DisplayName | Key | Value | ValueType |
---|---|---|---|
Average bit rate maximum | stream:0.0.0/MaxBitrate/62cdc195-39b4-4872-b594-dadc95dfbf6e | 0 | Int |
Average bit rate retention time (days) | stream:0.0.0/RetentionTime/8158bc51-ba0e-4342-a531-25f09e433794 | 7 | Int |
Bit rate control mode | stream:0.0.0/ControlMode/364ae0f9-ee2f-430c-8db9-358c031c4bb5 | VariableBitRate | Enum |
Bit rate control priority | stream:0.0.0/ControlPriority/fffafdce-8bcf-4a77-91ed-876e286d2437 | None | Enum |
Codec | stream:0.0.0/Codec/78622c19-58ae-40d4-8eea-17351f4273b6 | h264 | Enum |
Compression | stream:0.0.0/Compression/14aece26-1bb2-4732-b267-b5bf3e80ea46 | 30 | Slider |
Edge storage support | stream:0.0.0/EdgeStorageSupported/e89cdfa5-491f-4b14-9d28-fb98f3948449 | True | Enum |
Frames per second | stream:0.0.0/FPS/6527c12a-06f1-4f58-a2fe-6640c61707e0 | 8.0 | Double |
Include Date | stream:0.0.0/IncludeDate/134f2f85-2676-4235-bbd1-9713a4f7f066 | no | Enum |
Include Time | stream:0.0.0/IncludeTime/56acd22c-0a96-43d0-b14a-b01fdddc9186 | no | Enum |
Max. frames between keyframes | stream:0.0.0/MaxGOPSize/a338f8db-bae3-4ab0-9cb8-c398ff352e53 | 30 | Int |
Max. frames between keyframes mode | stream:0.0.0/MaxGOPMode/5254832a-17e6-4fd5-81b8-b26b63ac9aed | default | Enum |
Resolution | stream:0.0.0/Resolution/2b25c3c5-35ba-4ec1-a748-f225732161ed | 1920x1080 | Enum |
Stream reference ID | StreamReferenceId | 28DC44C3-079E-4C94-8EC9-60363451EB40 | String |
Streaming Mode | stream:0.0.0/StreamingMode/fcce5814-2608-4108-9900-2e0a38eec330 | TCP | Enum |
Target bit rate | stream:0.0.0/TargetBitrate/476c996a-b68e-47fe-a74f-a8806d3f6ad1 | 2000 | Int |
Zipstream compression | stream:0.0.0/ZStrength/20af527c-dc7b-4ce0-a736-10f804cbf039 | low | Enum |
Zipstream FPS mode | stream:0.0.0/ZFpsMode/ce409a39-042c-4c18-b058-2f256a7ec2e7 | fixed | Enum |
Zipstream GOP mode | stream:0.0.0/ZGopMode/2b43780b-2e7a-439e-8a77-8160147b6572 | fixed | Enum |
Zipstream max dynamic GOP length | stream:0.0.0/ZGopLength/716f70ca-28c2-41fc-bc8a-4f652bffcf9e | 300 | Int |
ValueTypeInfo¶
You will find a second hashtable property on the objects returned by Get-VmsCameraStream
named ValueTypeInfo
. This helpful tagalong provides information about how each setting is validated by Milestone. Most numeric settings like FPS or bitrate will have ValueTypeInfo entries named "MinValue", "MaxValue", and sometimes "StepValue" indicating whether the values must be in increments of 1, 0.01, or 10 for example. Other settings like StreamingMode
have a fixed list of options to choose from, with a ValueTypeInfo
entry providing a display name and value for each one.
In the settings example above, you'll notice the display value was "H.264" and the raw value was "h264". Let's look at the ValueTypeInfo collection for the "Codec" setting and see what our options are for this camera:
According to the ValueTypeInfo's, this stream supports H.265, H.264, and MJPEG. And when we want to change settings using Set-VmsCameraStream
, we should use the values 'h265', 'h264', and 'jpeg' respectively.
A note about TranslationId
Occasionally you will come across a property named "TranslationId" as you can see in the ValueTypeInfo collection above. These strings are sometimes GUIDs, and sometimes keys like "ItemTypeMotionDetection", and you can use these TranslationId properties to check for a localized string using Get-Translations
. If no translation is available, the English version is returned. And if no value is return at all for the associated TransalationId, you would fall back to the original string.
Developer Details¶
In the table above, you'll notice that there is a Name
and a DisplayName
column. The Name
value comes from the "DisplayName" property of the MIP SDK StreamChildItem
class, and it cannot be changed. These are also the objects holding the stream settings we're interested in like Codec, Resolution, and FPS, along with the ValueTypeInfo
associated with each setting (where applicable).
You can access these StreamChildItem
objects directly through the DeviceDriverSettingsFolder
property of the Camera
like this:
$streamChildItems = $camera.DeviceDriverSettingsFolder.DeviceDriverSettings[0].StreamChildItems
$streamChildItems[0] # (1)!
<# OUTPUT
StreamReferenceId : 28DC44C3-079E-4C94-8EC9-60363451EB40
Properties : VideoOS.Platform.ConfigurationItems.ConfigurationApiProperties
ServerId : Id: 8e6dd764-81ea-4ab3-bb54-710e54095d85 Uri: https://vms.443
Name :
DisplayName : Video stream 1
Path : DeviceDriverSettings[2f07a911-702b-440b-982b-f6cf4de82d11]
ParentPath : Camera[2f07a911-702b-440b-982b-f6cf4de82d11]/DeviceDriverSettingsFolder
ItemCategory : ChildItem
Description :
ParentItemPath : Camera[2f07a911-702b-440b-982b-f6cf4de82d11]
#>
- The
StreamChildItem
at index 0 in this instance represents Video stream 1, but the order of streams isn't always guaranteed. In some cases, there may be a stream named JPEG or MPEG at the first element in theStreamChildItem
collection.
$streamChildItems = $camera.DeviceDriverSettingsFolder.DeviceDriverSettings[0].StreamChildItems
$propertyKeys = $streamChildItems[0].Properties.Keys | Sort-Object # (1)!
$propertyKeys | ForEach-Object {
[pscustomobject]@{
Key = $_
Value = $streamChildItems[0].Properties.GetValue($_)
}
}
<# OUTPUT
Key Value
--- -----
Codec h264
Compression 30
ControlMode VariableBitRate
ControlPriority None
EdgeStorageSupported True
FPS 8.0
IncludeDate no
IncludeTime no
MaxBitrate 0
MaxGOPMode default
MaxGOPSize 30
Resolution 1920x1080
RetentionTime 7
StreamingMode TCP
StreamReferenceId 28DC44C3-079E-4C94-8EC9-60363451EB40
TargetBitrate 2000
ZFpsMode fixed
ZGopLength 300
ZGopMode fixed
ZStrength low
#>
- StreamChildItem.Properties is a
ConfigurationApiProperties
object with methods includingGetValue(string)
,SetValue(string, string)
, andGetValueTypeInfoCollection()
. In this code block we use theKeys
property to discover the available keys, and then use those keys to retrieve the values so that we can see them all at once.
The value in the DisplayName column comes from the StreamUsageChildItem
associated with the stream. If the stream isn't being used yet, it won't have a value in the DisplayName column.
You can inspect StreamUsageChildItems
through the StreamFolder
property. Note that there are some minor differences when using MilestonePSTools v23.2+ with VMS versions 2023 R1 and earlier as you can see below:
StreamReferenceIdValues : {[Video stream 1, 28DC44C3-079E-4C94-8EC9-60363451EB40], [Video stream 2, 28DC44C3-079E-4C94-8EC9-60363451EB41], [Video stream 3, 28DC44C3-079E-4C94-8EC9-60363451EB42], [Video
stream 4, 28DC44C3-079E-4C94-8EC9-60363451EB43]...}
StreamReferenceId : 28DC44C3-079E-4C94-8EC9-60363451EB40
LiveDefault : True
LiveModeValues : {[Always, Always], [Never, Never], [WhenNeeded, WhenNeeded]}
LiveMode : WhenNeeded
RecordToValues : {[Primary recording, 16ce3aa1-5f93-458a-abe5-5c95d9ed1372], [Secondary recording, 84fff8b9-8cd1-46b2-a451-c4a87d4cbbb0], [No recording, ]}
RecordTo : 16ce3aa1-5f93-458a-abe5-5c95d9ed1372
DefaultPlayback : True
UseEdge : False
Record : True
ServerId : Id: 8e6dd764-81ea-4ab3-bb54-710e54095d85 Uri: https://vms.443
Name : Video stream 1
DisplayName : Video stream 1
Path : Stream[2f07a911-702b-440b-982b-f6cf4de82d11]
ParentPath : Camera[2f07a911-702b-440b-982b-f6cf4de82d11]/StreamFolder
ItemCategory : ChildItem
Description :
ParentItemPath : Camera[2f07a911-702b-440b-982b-f6cf4de82d11]
StreamReferenceIdValues : {[Video stream 1, 28DC44C3-079E-4C94-8EC9-60363451EB40], [Video stream 2, 28DC44C3-079E-4C94-8EC9-60363451EB41], [Video stream 3, 28DC44C3-079E-4C94-8EC9-60363451EB42], [Video
stream 4, 28DC44C3-079E-4C94-8EC9-60363451EB43]...}
StreamReferenceId : 28DC44C3-079E-4C94-8EC9-60363451EB40
LiveDefault : True
LiveModeValues : {[Always, Always], [Never, Never], [WhenNeeded, WhenNeeded]}
LiveMode : WhenNeeded
RecordToValues : {}
RecordTo :
DefaultPlayback : False
UseEdge : False
Record : True
ServerId : Id: 8e6dd764-81ea-4ab3-bb54-710e54095d85 Uri: https://vms.443
Name : Video stream 1
DisplayName : Video stream 1
Path : Stream[2f07a911-702b-440b-982b-f6cf4de82d11]
ParentPath : Camera[2f07a911-702b-440b-982b-f6cf4de82d11]/StreamFolder
ItemCategory : ChildItem
Description :
ParentItemPath : Camera[2f07a911-702b-440b-982b-f6cf4de82d11]
Each StreamUsageChildItem
has a StreamReferenceId
matching the StreamReferenceId
of one of the StreamChildItem
records we looked at earlier, and we can change which stream the usage is associated with by changing the StreamReferenceId
to one of the values in the StreamReferenceIdValues
dictionary property attached to each StreamUsageChildItem
.
Show-StreamDetail¶
Now that we know how to find the setting names, keys, display values, raw values, and the ValueTypeInfo collections for each setting, we can write a function to take the output from Get-VmsCameraStream
and build a detailed table describing each setting, and all the information we need to start changing settings.
function Show-StreamDetail {
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline, Position = 0)]
[object]
$Stream
)
begin {
$driverSettings = @{}
}
process {
$path = 'DeviceDriverSettings[{0}]' -f $Stream.Camera.Id
if (-not $driverSettings.ContainsKey($path)) {
$driverSettings[$path] = Get-ConfigurationItem -Path $path
}
$item = $driverSettings[$path]
$streamSettings = ($item.Children | Where-Object DisplayName -eq $Stream.Name).Properties
$keys = $Stream.Settings.Keys | Sort-Object
foreach ($key in $keys) {
$value = $Stream.Settings[$key]
$info = $Stream.ValueTypeInfo[$key] | Where-Object {
($_.Name -eq $value -or $_.Value -eq $value) -and $_.Name -notmatch '^(Min|Max|Step)Value$'
}
$detailedSetting = $streamSettings | Where-Object Key -Match ('\b{0}\b' -f [regex]::Escape($key))
[pscustomobject]@{
DisplayName = $detailedSetting.DisplayName
Key = $key
DisplayValue = if ($info) { $info.Name } else { $value }
Value = if ($info) { $info.Value } else { $value }
ValueType = $detailedSetting.ValueType
MinValue = ($Stream.ValueTypeInfo[$key] | Where-Object Name -eq 'MinValue').Value
MaxValue = ($Stream.ValueTypeInfo[$key] | Where-Object Name -eq 'MaxValue').Value
StepValue = ($Stream.ValueTypeInfo[$key] | Where-Object Name -eq 'StepValue').Value
EnumOptions = if ($detailedSetting.ValueType -eq 'Enum') { ($detailedSetting.ValueTypeInfos | Where-Object { $_.Name -notmatch '^(Min|Max|Step)Value$' }).Value -join ', ' } else { $null }
}
}
}
}
$streams[0] | Show-StreamDetail | Format-Table
DisplayName | Key | DisplayValue | Value | ValueType | MinValue | MaxValue | StepValue | EnumOptions |
---|---|---|---|---|---|---|---|---|
Codec | Codec | H.264 | h264 | Enum | jpeg, h264, h265 | |||
Compression | Compression | 30 | 30 | Slider | 0 | 100 | 10 | |
Bit rate control mode | ControlMode | Variable bit rate | VariableBitRate | Enum | AverageBitRate, VariableBitRate, ConstantBitRate | |||
Bit rate control priority | ControlPriority | None | None | Enum | None, Framerate, ImageQuality | |||
Edge storage support | EdgeStorageSupported | true | True | Enum | True, False | |||
Frames per second | FPS | 8.0 | 8.0 | Double | 0.00028 | 180 | ||
Include Date | IncludeDate | No | no | Enum | no, yes | |||
Include Time | IncludeTime | No | no | Enum | no, yes | |||
Average bit rate maximum | MaxBitrate | 0 | 0 | Int | 0 | 50000 | ||
Max. frames between keyframes mode | MaxGOPMode | Default (determined by driver) | default | Enum | default, custom | |||
Max. frames between keyframes | MaxGOPSize | 30 | 30 | Int | 2 | 61440 | ||
Resolution | Resolution | 1920x1080 | 1920x1080 | Enum | 1920x1080, 1280x960, 1280x800, 1280x720, 1024x768, 1024x640, 800x600, 800x500, 800x450, 640x480, 640x400, 640x360, 480x360, 480x300, 480x270, 320x240, 320x200, 320x180, 240x180, 160x120, 160x100, 160x90 | |||
Average bit rate retention time (days) | RetentionTime | 7 | 7 | Int | 1 | 3652 | ||
Streaming Mode | StreamingMode | RTP/RTSP/TCP | TCP | Enum | UDP, TCP, HTTP, RTP_MULTICAST, SRTP, SRTP_MULTICAST | |||
Stream reference ID | StreamReferenceId | 28DC44C3-079E-4C94-8EC9-60363451EB40 | 28DC44C3-079E-4C94-8EC9-60363451EB40 | String | ||||
Target bit rate | TargetBitrate | 2000 | 2000 | Int | 1 | 50000 | ||
Zipstream FPS mode | ZFpsMode | Fixed | fixed | Enum | fixed, dynamic | |||
Zipstream max dynamic GOP length | ZGopLength | 300 | 300 | Int | 62 | 1200 | 1 | |
Zipstream GOP mode | ZGopMode | Fixed | fixed | Enum | fixed, dynamic | |||
Zipstream compression | ZStrength | Low | low | Enum | off, low, medium, high, higher, extreme |
Set-VmsCameraStream¶
Now that we have all the information we need about the stream settings we want to change, we can start configuring our cameras for adaptive streaming and playback!
Video stream 1¶
We'll start by configuring Video stream 1
:
$stream = $camera | Get-VmsCameraStream -Name 'Video stream 1'
$settings = @{
Codec = ($stream.ValueTypeInfo.Codec | Where-Object Value -in @('h264', 'h265')).Value | Sort-Object | Select-Object -Last 1 # (1)!
Resolution = $stream.ValueTypeInfo.Resolution.Value | Sort-Object { [int]$w, [int]$h = $_ -split 'x'; $w * $h } -Descending | Select-Object -First 1 # (2)!
FPS = 15
MaxGOPMode = 'default'
}
$stream | Set-VmsCameraStream -Settings $settings -DisplayName 'High Quality' -RecordingTrack Primary -LiveDefault -PlaybackDefault # (3)!
- As we discovered earlier, the values allowed for the Codec setting are jpeg, h264, and h265. But maybe some of the cameras are a different model, or firmware, and are missing the H.265 option? This will allow us to fall back to H.264 in that event.
- This will select the highest available resolution by parsing the width and height from the resolution string, then multiplying those values together. The resolution options are then sorted in descending order by total number of pixels, and we pick the first option from the list.
- With the settings defined in a hashtable, we can pass them in to the
Set-VmsCameraStream
command. You might notice that we're marking this stream as the default live and playback stream even though we plan for "Video stream 2" to be the default for both live and playback. The reason is that we can't be certain which streams are enabled on all 130 cameras so we're going to disable all streams besides the first two, and you can't disable a stream if it is currently the primary recorded stream, or the default live or playback stream.
Video stream 2¶
$stream = $camera | Get-VmsCameraStream -Name 'Video stream 2'
$settings = @{
Codec = ($stream.ValueTypeInfo.Codec | Where-Object Value -in @('h264', 'h265')).Value | Sort-Object | Select-Object -Last 1 # (1)!
Resolution = $stream.ValueTypeInfo.Resolution.Value | ForEach-Object {
[int]$w, [int]$h = $_ -split 'x'
$pixels = $w * $h
if ($pixels -le (1280 * 720)) {
$_
}
} | Sort-Object { [int]$w, [int]$h = $_ -split 'x'; $w * $h } -Descending | Select-Object -First 1 # (2)!
FPS = 4
MaxGOPMode = 'custom'
MaxGOPSize = 4 * 8 # (3)!
}
$stream | Set-VmsCameraStream -Settings $settings -DisplayName 'Low Quality' -RecordingTrack Secondary -LiveDefault -PlaybackDefault # (4)!
- Once again we'll prefer H.265 and fall back to H.264 if necessary by sorting the options and picking the last one in the list.
- The resolution selection got even more complicated here. We're essentially doing the same thing we did for the primary stream but the twist is that we're filtering out all the resolution options greater than 1280x720, and then selecting the highest resolution remaining options.
- For the low quality stream, we're manually setting the total number of frames to include in a single GOP or "group of pictures". Since we'll be asking for 4 frames per second, and we want to have a "key frame" once every 8 seconds, that means our GOP length should be
4 * 8
or 32 frames. - The VMS version in this scenario is 2023 R2 and supports adaptive playback. If your system doesn't support adaptive playback, or you don't want to record the second stream, you should omit the
-RecordingTrack Secondary
and-PlaybackDefault
parameters.
Disable all other streams¶
Our high and low quality streams are now configured how we want them, so now we'll go ahead and disable all other streams on the camera if available.
$streamsToDisable = $camera | Get-VmsCameraStream | Where-Object Name -notmatch '^Video stream 0*(1|2)$' # (1)!
$streamsToDisable | Set-VmsCameraStream -Disabled
- The Where-Object expression
Where-Object Name -notmatch '^Video stream 0*(1|2)$'
allows us to select all streams with a name that doesn't match strings like "Video stream 1", "Video stream 2", or "Video stream 01" which you may find occasionally.
Results¶
Now that the first two streams on our sample camera are configured, let's take a look at their current settings:
$properties = ('Name', 'DisplayName') + ('Codec', 'Resolution', 'FPS', 'MaxGOPMode', 'MaxGOPSize' | ForEach-Object {
@{
Name = $_.ToString()
Expression = [scriptblock]::Create("`$_.Settings['$_']")
}
})
$camera | Get-VmsCameraStream -Enabled | Select-Object $properties
Name | DisplayName | Codec | Resolution | FPS | MaxGOPMode | MaxGOPSize |
---|---|---|---|---|---|---|
Video stream 1 | High Quality | H.265 | 1920x1080 | 15 | Default (determined by driver) | 30 |
Video stream 2 | Low Quality | H.265 | 1280x720 | 4 | Custom | 32 |
Bulk Change¶
Our sample camera is now configured how we want it and it's time to apply the same changes to the rest of the cameras. We have a camera group named "Parking Cameras", so we'll use that device group as a way to select the cameras, and then we'll put the scripts above together and run them all at once.
foreach ($camera in Get-VmsDeviceGroup -Type Camera -Name 'Parking Cameras' | Get-VmsDeviceGroupMember) {
$stream = $camera | Get-VmsCameraStream -Name 'Video stream 1'
$settings = @{
Codec = ($stream.ValueTypeInfo.Codec | Where-Object Value -in @('h264', 'h265')).Value | Sort-Object | Select-Object -Last 1
Resolution = $stream.ValueTypeInfo.Resolution.Value | Sort-Object { [int]$w, [int]$h = $_ -split 'x'; $w * $h } -Descending | Select-Object -First 1
FPS = 15
MaxGOPMode = 'default'
}
$stream | Set-VmsCameraStream -Settings $settings -DisplayName 'High Quality' -RecordingTrack Primary -LiveDefault -PlaybackDefault
$stream = $camera | Get-VmsCameraStream -Name 'Video stream 2'
$settings = @{
Codec = ($stream.ValueTypeInfo.Codec | Where-Object Value -in @('h264', 'h265')).Value | Sort-Object | Select-Object -Last 1
Resolution = $stream.ValueTypeInfo.Resolution.Value | ForEach-Object {
[int]$w, [int]$h = $_ -split 'x'
$pixels = $w * $h
if ($pixels -le (1280 * 720)) {
$_
}
} | Sort-Object { [int]$w, [int]$h = $_ -split 'x'; $w * $h } -Descending | Select-Object -First 1
FPS = 4
MaxGOPMode = 'custom'
MaxGOPSize = 4 * 8
}
$stream | Set-VmsCameraStream -Settings $settings -DisplayName 'Low Quality' -RecordingTrack Secondary -LiveDefault -PlaybackDefault
$streamsToDisable = $camera | Get-VmsCameraStream | Where-Object Name -notmatch '^Video stream 0*(1|2)$'
$streamsToDisable | Set-VmsCameraStream -Disabled
}
Conclusion¶
We've made some assumptions in this scenario that simplify the process. For example, if we needed to make these same changes on a variety of cameras from different manufacturers, there's a very good chance we would need to handle situations where the resolution values were formatted differently, or simply don't exist. In those cases, your code can't guess what your intentions were - you will need to think through what you would want to do if you were doing it by hand, and then translate those thoughts into PowerShell.
Having said that, hopefully you learned something new from this post and feel more confident about making these kinds of configuration changes in the future. As always, if you run into any issues or would like to share feedback, join us on GitHub - your participation is most appreciated!