add 3rd party folders back

This commit is contained in:
2025-10-12 21:09:32 +03:00
parent 9cca8147d2
commit aaa0f93a0f
77 changed files with 13519 additions and 0 deletions

65
3rd/libomtnet/.gitattributes vendored Normal file
View File

@@ -0,0 +1,65 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
*.html linguist-detectable=false

363
3rd/libomtnet/.gitignore vendored Normal file
View File

@@ -0,0 +1,363 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd

21
3rd/libomtnet/LICENSE.txt Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Open Media Transport Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

184
3rd/libomtnet/PROTOCOL.md Normal file
View File

@@ -0,0 +1,184 @@
# Open Media Transport (OMT) Protocol 1.0
## Introduction
This guide should serve as an overview of the protocol used by Open Media Transport.
Implementers should first look at using libomtnet or libomt as these are complete implementations covering the vast majority of platforms.
## Basics
Open Media Transport consists of three major components:
1. TCP Protocol for sending/receiving video, audio and metadata.
2. Special Metadata commands to control various aspects of the connection.
3. DNS-SD for discovery (RFC 6763).
## Data Types
All data types are stored in Little-Endian byte order.
This is in contrast to the often used Big-Endian network order.
## TCP Protocol
All data is encapsulated into a frame consisting of the following parts:
### HEADER (16 bytes)
BYTE Version // Must be 1
BYTE FrameType // Metadata = 1, Video = 2, Audio = 4
INT64 Timestamp // Timestamp where 1 second = 10,000,000
UINT16 MetadataLength // Length of XML UTF-8 per-frame metadata including null character.
INT32 DataLength //ExtendedHeader + Data length + MetadataLength, excluding this header
### VIDEO EXTENDED HEADER (32 bytes) (Mandatory for video frame type)
INT32 Codec //Video codec FourCC
INT32 Width //Video width in pixels
INT32 Height //Video height in pixels
INT32 FrameRateN //Frame rate numerator/denominator in frames per second, for example 60/1 is 60 frames per second.
INT32 FrameRateD
FLOAT32 AspectRatio //Display aspect ratio expressed as a ratio of width/height. For example 1.777777777777778 for 16/9
INT32 Flags //Interlaced=1, Alpha=2, PreMultiplied=4, Preview=8, HighBitDepth=16
INT32 ColorSpace //Color space flag. 601 for BT601, 709 for BT709, 0 for undefined (typically BT601 for SD, BT709 for HD)
### AUDIO EXTENDED HEADER (24 bytes) (Mandatory for audio frame type)
INT32 Codec //Audio codec FourCC, currently only 'FPA1' is supported which is 32bit floating point planar audio
INT32 SampleRate //Audio sample rate
INT32 SamplesPerChannel //Number of samples per channel stored in this frame
INT32 Channels //Number of channels of audio
UINT32 ActiveChannels //Bit field denoting the number of actual channels stored in this frames data out of the total Channels specified. This is so silent channels can be skipped, saving bandwidth.
INT32 Reserved1 //Reserved for future use
### DATA
The frame data followed by the per-frame metadata
### Latency considerations
To optimize latency and prevent network stalls, implementers should ensure the receiver never blocks when accepting data.
To achieve this one approach involves keeping a small frame queue with at least one frame reserved for the asynchronous network callback.
That reserved frame can the be reused repeatedly if the separate decode thread is taking too long to process frames.
## Metadata
Metadata is stored as UTF-8 encoded, null terminated XML data.
DataLength should always include the null character.
Special metadata commands are used to control various aspects of the connection.
These are fixed strings that must be specified exactly.
This is an optimization so that the end point can employ simple string matching rather than full XML parsing.
### Subscribe Commands
A sender should not send any data until a subscribe command is received.
\<OMTSubscribe Video="true" /\>
Sent by a receiver to request the sender start sending video frames.
\<OMTSubscribe Audio="true" /\>
Sent by a receiver to request the sender start sending audio frames.
\<OMTSubscribe Metadata="true" /\>
### Preview Commands
\<OMTSettings Preview="true" /\>
\<OMTSettings Preview="false" /\>
Enable/disable sending preview video data instead of the full resolution frame.
### Tally Commands
\<OMTTally Preview="true" Program="false" /\>
\<OMTTally Preview="false" Program="true" /\>
\<OMTTally Preview="true" Program="true" /\>
\<OMTTally Preview="false" Program="false" /\>
Sent by a receiver to indicate tally status.
The sender should then combine this tally status with those set by other receivers and then broadcast this new combined tally to all receivers.
This tally should also be sent to any new connections as well.
### Suggested Quality
\<OMTSettings Quality="Default" /\>
Sent by receivers to indicate the preferred compression quality:
Default,
Low,
Medium,
High
Senders may respond to this by gathering the preferred quality of all receivers, determining the highest quality requested and then adjusting the encoder to match.
Default is Medium quality.
### Sender Information
\<OMTInfo ProductName="MyProduct" Manufacturer="MyCompany" Version="1.0" /\>
Senders can optionally send information about the encoder to receivers when connected.
## DNS-SD
Open Media Transport uses the service type _omt._tcp
The port should be the tcp port that sender is listening on
The full service name should take the form HOSTNAME (Source Name)._omt._tcp.local
## Discovery Server
Discovery Server uses the same communication protocol and frame headers to send and receive XML data.
The server does the following:
1. Keep track of register/deregister XML requests from each client
2. Determine the IP address to use for each registered source based on the client's connection ip.
3. Repeat registered requests to all connected clients, including the client that submitted the request.
4. Repeat all current registration requests to new clients.
5. Remove all requests from a client that has disconnected, and repeat that removed request to all remaining clients.
### Register XML
\<OMTAddress>
\<Name>MYMACHINENAME (My Source Name)\</Name>
\<Port>1234\</Port>
\<Addresses>
\<Address>0.0.0.0\</Address>
\</Addresses>
\</OMTAddress>
### DeRegister XML
\<OMTAddress>
\<Name>MYMACHINENAME (My Source Name)\</Name>
\<Port>1234\</Port>
\<Removed>True\</Removed>
\</OMTAddress>
### IP Addresses
IP Addresses should be determined by the server to ensure only the address accessible to the server is used.
Therefore when registering a source, the client provided Addresses portion should be ignored.

40
3rd/libomtnet/README.md Normal file
View File

@@ -0,0 +1,40 @@
# Open Media Transport (OMT) Libary for .NET
libomtnet is a .NET library that implements the Open Media Transport protocol for low latency, high performance Local Area Network
video/audio transmission.
It is built using a basic subset of .NET and as a result supports both .NET Framework 4+ and .NET Standard 2.0+ applications, covering all .NET versions from 4 onwards.
libomt is a native compiled version of the .NET library and is available separately.
## Getting Started
### Installation
Official binary releases for Windows and MacOS can be found in the Releases section of this repository.
There are only two dependencies when using this library in a .NET app:
**libomtnet.dll**
This is a cross platform file that will work on Windows, Mac and Linux
**libvmx.dll (Windows)**
**libvmx.dylib (MacOS)**
**libvmx.so (Linux)**
These are platform specific native shared libraries. The correct library for the CPU type and OS platform needs to be placed in the same directory as the application.
### Creating a Source
1. Create an instance of the OMTSend class specifying a name
2. Fill the struct OMTMediaFrame with the video data in either of the available YUV or RGBx formats
3. Send using OMTSend.Send
4. That's it, the source is now available on the network for receivers to connect to
### Creating a Receiver
1. Create an instance of the OMTReceive class specifying the full name of the source (including machine name)
The full name of all sources on the network can be found by using the OMTDiscovery class
2. In a loop, poll OMTReceive.Receive specifying the types of frames to receive and also a timeout
3. Process said frames as required

View File

@@ -0,0 +1 @@
dotnet build ../libomtnet.sln -c Release

View File

@@ -0,0 +1 @@
dotnet build ../libomtnet.sln -c Release

View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net40</TargetFrameworks>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AssemblyVersion>1.0.0.13</AssemblyVersion>
<FileVersion>1.0.0.13</FileVersion>
<Version>1.0.0.13</Version>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.11.35431.28
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "libomtnet", "libomtnet.csproj", "{4103721D-84E5-4037-B682-4FCF14AFEEDA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4103721D-84E5-4037-B682-4FCF14AFEEDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4103721D-84E5-4037-B682-4FCF14AFEEDA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4103721D-84E5-4037-B682-4FCF14AFEEDA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4103721D-84E5-4037-B682-4FCF14AFEEDA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3BF6FC38-ECEA-48A0-8F36-0921E8C0D0CA}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,338 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Xml;
using System.IO;
namespace libomtnet
{
public class OMTAddress
{
private string name;
private readonly string machineName;
private int port;
private IPAddress[] addresses = { };
private const int MAX_FULLNAME_LENGTH = 63;
internal bool removed = false;
public OMTAddress(string name, int port)
{
this.name = SanitizeName(name);
this.port = port;
this.machineName = SanitizeName(OMTPlatform.GetInstance().GetMachineName());
this.addresses = new IPAddress[] { };
LimitNameLength();
}
public OMTAddress(string machineName, string name, int port)
{
this.name = SanitizeName(name);
this.port = port;
this.machineName = SanitizeName(machineName);
this.addresses = new IPAddress[] { };
LimitNameLength();
}
public string ToURL()
{
return OMTConstants.URL_PREFIX + this.machineName + ":" + port;
}
private void LimitNameLength()
{
int oversize = ToString().Length - MAX_FULLNAME_LENGTH;
if (oversize > 0)
{
if (oversize < this.name.Length)
{
this.name = this.name.Substring(0, this.name.Length - oversize).Trim();
}
}
}
public void ClearAddresses()
{
addresses = new IPAddress[]{ };
}
public bool AddAddress(IPAddress address)
{
if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
byte[] b = address.GetAddressBytes();
byte[] b128 = new byte[16];
b128[10] = 0xFF;
b128[11] = 0xFF;
b128[12] = b[0];
b128[13] = b[1];
b128[14] = b[2];
b128[15] = b[3];
address = new IPAddress(b128);
} else if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
{
if (address.IsIPv6LinkLocal) return false;
}
if (!HasAddress(address))
{
List<IPAddress> list = new List<IPAddress>();
bool v4 = OMTUtils.IsIPv4(address);
foreach (IPAddress a in this.addresses)
{
if (OMTUtils.IsIPv4(a))
{
list.Add(a);
}
}
if (v4) list.Add(address);
foreach (IPAddress a in this.addresses)
{
if (!OMTUtils.IsIPv4(a))
{
list.Add(a);
}
}
if (!v4) list.Add(address);
addresses = list.ToArray();
return true;
}
return false;
}
internal bool HasAddress(IPAddress address)
{
foreach (IPAddress a in addresses)
{
if (a.Equals(address))
{
return true;
}
}
return false;
}
public static string EscapeFullName(string fullName)
{
return fullName.Replace("\\", "\\\\").Replace(".", "\\.");
}
public static string SanitizeName(string name)
{
return name; // return name.Replace(".", " ");
}
public static string UnescapeFullName(string fullName)
{
StringBuilder sb = new StringBuilder();
bool beginEscape = false;
string num = "";
foreach (char c in fullName.ToCharArray())
{
if (beginEscape)
{
if (Char.IsDigit(c))
{
num = num + c.ToString();
if (num.Length == 3)
{
int n = 0;
if (int.TryParse(num, out n))
{
sb.Append(Convert.ToChar(n));
}
beginEscape = false;
}
} else
{
sb.Append(c);
beginEscape = false;
}
} else
{
if (c == '\\')
{
beginEscape = true;
}
else
{
sb.Append(c);
}
}
}
return sb.ToString();
}
public string MachineName { get { return machineName; } }
public string Name { get { return name; } }
public IPAddress[] Addresses { get { return addresses; } }
public int Port { get { return port; } set { port = value; } }
public override string ToString()
{
return ToString(machineName, name);
}
public static string ToString(string machineName, string name)
{
return machineName + " (" + name + ")";
}
public static bool IsValid(string fullName)
{
if (!string.IsNullOrEmpty(fullName))
{
if (fullName.Contains("("))
{
if (fullName.Contains(")"))
{
return true;
}
}
}
return false;
}
public static OMTAddress Create(string fullName, int port)
{
if (!IsValid(fullName)) return null;
int index = fullName.IndexOf('(');
string machineName = fullName.Substring(0, index).Trim();
if (index > 0)
{
string name = fullName.Substring(index + 1);
name = name.Substring(0, name.Length - 1);
return new OMTAddress(machineName, name, port);
}
return null;
}
public static string GetMachineName(string fullName)
{
string[] s = fullName.Split('(');
return s[0].Trim();
}
public static string GetName(string fullName)
{
int index = fullName.IndexOf('(');
if (index > 0)
{
string name = fullName.Substring(index + 1);
name = name.Substring(0, name.Length - 1);
return name;
}
return "";
}
public string ToXML()
{
using (StringWriter sw = new StringWriter())
{
using (XmlTextWriter t = new XmlTextWriter(sw))
{
t.Formatting = Formatting.Indented;
t.WriteStartElement(OMTMetadataTemplates.ADDRESS_NAME);
t.WriteElementString("Name", ToString());
t.WriteElementString("Port", port.ToString());
if (removed)
{
t.WriteElementString("Removed", "True");
}
t.WriteStartElement("Addresses");
foreach (IPAddress ip in addresses)
{
t.WriteElementString("IPAddress", ip.ToString());
}
t.WriteEndElement();
t.WriteEndElement();
return sw.ToString();
}
}
}
public static OMTAddress FromXML(string xml)
{
XmlDocument doc = OMTMetadataUtils.TryParse(xml);
if (doc != null)
{
XmlNode e = doc.DocumentElement;
if (e != null)
{
if (e.Name == OMTMetadataTemplates.ADDRESS_NAME)
{
XmlNode nm = e.SelectSingleNode("Name");
if (nm != null)
{
XmlNode prt = e.SelectSingleNode("Port");
if (prt != null)
{
int port = int.Parse(prt.InnerText);
OMTAddress a = OMTAddress.Create(nm.InnerText, port);
if (a != null)
{
foreach (XmlNode ipn in e.SelectNodes("Addresses/IPAddress"))
{
IPAddress ip = null;
if (IPAddress.TryParse(ipn.InnerText, out ip))
{
a.AddAddress(ip);
}
}
XmlNode del = e.SelectSingleNode("Removed");
if (del != null)
{
if (del.InnerText.ToLower() == "true")
{
a.removed = true;
}
}
return a;
}
}
}
}
}
}
return null;
}
}
internal class OMTAddressSorter : IComparer<OMTAddress>
{
public int Compare(OMTAddress x, OMTAddress y)
{
if (x != null && y != null)
{
return String.Compare(x.ToString(), y.ToString());
}
return 0;
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
namespace libomtnet
{
public class OMTBase : IDisposable
{
private bool disposedValue;
private bool exiting;
protected virtual void DisposeInternal()
{
}
protected bool Exiting { get { return exiting; } }
protected void SetExiting()
{
exiting = true;
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
exiting = true;
if (disposing)
{
DisposeInternal();
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@@ -0,0 +1,140 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace libomtnet
{
internal class OMTBinary
{
private byte[] buffer;
private int offset;
public void SetBuffer(byte[] buffer, int offset)
{
this.buffer = buffer;
this.offset = offset;
}
public byte ReadByte()
{
byte value = buffer[offset];
offset += 1;
return value;
}
public Int32 ReadInt32()
{
Int32 result = (int)this.buffer[offset] | ((int)this.buffer[offset + 1] << 8) | ((int)this.buffer[offset + 2] << 16) | ((int)this.buffer[offset + 3] << 24);
offset += 4;
return result;
}
public UInt16 ReadUInt16()
{
UInt16 result = (ushort)((int)this.buffer[offset] | ((int)this.buffer[offset + 1] << 8));
offset += 2;
return result;
}
public Int64 ReadInt64()
{
uint num = (uint)((int)this.buffer[offset] | ((int)this.buffer[offset+1] << 8) | ((int)this.buffer[offset + 2] << 16) | ((int)this.buffer[offset + 3] << 24));
uint num2 = (uint)((int)this.buffer[offset + 4] | ((int)this.buffer[offset + 5] << 8) | ((int)this.buffer[offset + 6] << 16) | ((int)this.buffer[offset + 7] << 24));
Int64 val = (long)(((ulong)num2 << 32) | (ulong)num);
offset += 8;
return val;
}
public UInt32 ReadUInt32()
{
UInt32 val = (uint)((int)this.buffer[offset + 0] | ((int)this.buffer[offset + 1] << 8) | ((int)this.buffer[offset + 2] << 16) | ((int)this.buffer[offset + 3] << 24));
offset += 4;
return val;
}
public unsafe Single ReadSingle()
{
uint num = (uint)((int)this.buffer[offset + 0] | ((int)this.buffer[offset + 1] << 8) | ((int)this.buffer[offset + 2] << 16) | ((int)this.buffer[offset + 3] << 24));
offset += 4;
return *(float*)(&num);
}
public void Write(byte value)
{
this.buffer[offset] = value;
offset++;
}
public void Write(short value)
{
this.buffer[offset] = (byte)value;
this.buffer[offset + 1] = (byte)(value >> 8);
offset += 2;
}
public void Write(ushort value)
{
this.buffer[offset] = (byte)value;
this.buffer[offset + 1] = (byte)(value >> 8);
offset += 2;
}
public void Write(int value)
{
this.buffer[offset+ 0] = (byte)value;
this.buffer[offset + 1] = (byte)(value >> 8);
this.buffer[offset + 2] = (byte)(value >> 16);
this.buffer[offset + 3] = (byte)(value >> 24);
offset += 4;
}
public void Write(uint value)
{
this.buffer[offset + 0] = (byte)value;
this.buffer[offset + 1] = (byte)(value >> 8);
this.buffer[offset + 2] = (byte)(value >> 16);
this.buffer[offset + 3] = (byte)(value >> 24);
offset += 4;
}
public void Write(long value)
{
this.buffer[offset + 0] = (byte)value;
this.buffer[offset + 1] = (byte)(value >> 8);
this.buffer[offset + 2] = (byte)(value >> 16);
this.buffer[offset + 3] = (byte)(value >> 24);
this.buffer[offset + 4] = (byte)(value >> 32);
this.buffer[offset + 5] = (byte)(value >> 40);
this.buffer[offset + 6] = (byte)(value >> 48);
this.buffer[offset + 7] = (byte)(value >> 56);
offset += 8;
}
public unsafe void Write(float value)
{
uint num = *(uint*)(&value);
this.buffer[offset + 0] = (byte)num;
this.buffer[offset + 1] = (byte)(num >> 8);
this.buffer[offset + 2] = (byte)(num >> 16);
this.buffer[offset + 3] = (byte)(num >> 24);
offset += 4;
}
}
}

View File

@@ -0,0 +1,145 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace libomtnet
{
internal class OMTBuffer : OMTBase
{
private byte[] buffer;
private int offset;
private int length;
private int maximumLength;
private bool resizable = false;
public OMTBuffer(byte[] buffer, int offset, int maximumLength)
{
this.buffer = buffer;
this.offset = offset;
this.maximumLength = maximumLength;
this.length = maximumLength;
}
public OMTBuffer(int maximumLength, bool resizable)
{
this.buffer = new byte[maximumLength];
this.offset = 0;
this.length = maximumLength;
this.maximumLength = maximumLength;
this.resizable = resizable;
}
public void Resize(int newMaximumLength)
{
if (resizable)
{
if (newMaximumLength > this.maximumLength)
{
Debug.WriteLine("Resizing: " + this.maximumLength + " to " + newMaximumLength);
this.maximumLength = newMaximumLength;
this.buffer = new byte[maximumLength];
this.length = 0;
this.offset = 0;
}
} else
{
throw new Exception("This buffer does not support resizing.");
}
}
public void Append(byte[] buffer, int offset, int count)
{
System.Buffer.BlockCopy(buffer, offset, this.buffer, this.offset, count);
this.offset += count;
this.length += count;
}
public void Append(IntPtr buffer, int offset, int count)
{
Marshal.Copy(buffer + offset, this.buffer, this.offset, count);
this.offset += count;
this.length += count;
}
/// <summary>
/// Set the buffer where length is the entire length of valid data, not just from offset
/// </summary>
/// <param name="offset"></param>
/// <param name="length"></param>
public void SetBuffer(int offset, int length)
{
this.offset = offset;
this.length = length;
}
public void SetBuffer(byte[] buffer, int offset, int length)
{
this.buffer = buffer;
SetBuffer(offset, length);
}
public byte[] Buffer { get { return buffer; } }
public int Offset { get { return offset; } }
public int Length { get { return length; } }
public int MaximumLength { get { return maximumLength; } }
public static OMTBuffer FromMetadata(string xml)
{
byte[] b = UTF8Encoding.UTF8.GetBytes(xml);
return new OMTBuffer(b, 0, b.Length);
}
public string ToMetadata()
{
return UTF8Encoding.UTF8.GetString(this.buffer, this.offset, this.length);
}
protected override void DisposeInternal()
{
buffer = null;
base.DisposeInternal();
}
}
internal class OMTPinnedBuffer : OMTBuffer
{
private GCHandle handle;
public OMTPinnedBuffer(int length) : base(length,false)
{
handle = GCHandle.Alloc(this.Buffer, GCHandleType.Pinned);
}
public IntPtr Pointer { get { return handle.AddrOfPinnedObject(); } }
protected override void DisposeInternal()
{
if (handle.IsAllocated)
{
handle.Free();
}
base.DisposeInternal();
}
}
}

View File

@@ -0,0 +1,594 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using libomtnet.codecs;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Xml;
namespace libomtnet
{
internal class OMTChannel : OMTBase
{
private Socket socket;
private SocketAsyncEventArgs receiveargs;
private OMTSocketAsyncPool sendpool;
private OMTSocketAsyncPool metapool;
private OMTBuffer tempReceiveBuffer;
private OMTFramePool framePool;
private OMTFrame pendingFrame;
private readonly Queue<OMTFrame> readyFrames;
private AutoResetEvent frameReadyEvent;
private OMTFrameType subscriptions = OMTFrameType.None;
private readonly Queue<OMTMetadata> metadatas;
private AutoResetEvent metadataReadyEvent;
private OMTTally tally;
private bool preview;
private object lockSync = new object();
private object sendSync = new object();
private OMTQuality suggestedQuality = OMTQuality.Default;
private OMTSenderInfo senderInfo = null;
private IPEndPoint endPoint = null;
private OMTStatistics statistics = new OMTStatistics();
public delegate void ChangedEventHandler(object sender, OMTEventArgs e);
public event ChangedEventHandler Changed;
private OMTEventArgs tempEvent = new OMTEventArgs(OMTEventType.None);
private string redirectAddress = null;
public string RedirectAddress { get { return redirectAddress; } }
public IPEndPoint RemoteEndPoint { get { return endPoint; } }
public OMTChannel(Socket sck, OMTFrameType receiveFrameType, AutoResetEvent frameReady, AutoResetEvent metadataReady, bool metadataServer)
{
socket = sck;
endPoint = (IPEndPoint)sck.RemoteEndPoint;
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
socket.SendBufferSize = OMTConstants.NETWORK_SEND_BUFFER;
if (receiveFrameType == OMTFrameType.Metadata)
{
socket.ReceiveBufferSize = OMTConstants.NETWORK_SEND_RECEIVE_BUFFER;
} else
{
socket.ReceiveBufferSize = OMTConstants.NETWORK_RECEIVE_BUFFER;
}
tempReceiveBuffer = new OMTBuffer(OMTConstants.VIDEO_MIN_SIZE, true);
try
{
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
socket.SetSocketOption(SocketOptionLevel.Tcp, (SocketOptionName)3, (int)5); //TCP_KEEPIDLE=3 in Win32 and is translated in .NET8+ to platform specific values
OMTLogging.Write("KeepAlive.Enabled", "OMTChannel");
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTChannel.KeepAlive");
}
receiveargs = new SocketAsyncEventArgs();
receiveargs.Completed += Receive_Completed;
int poolCount = 1;
int startingFrameSize = 0;
if (receiveFrameType == OMTFrameType.Video)
{
poolCount = OMTConstants.VIDEO_FRAME_POOL_COUNT;
startingFrameSize = OMTConstants.VIDEO_MIN_SIZE;
receiveargs.SetBuffer(new byte[OMTConstants.VIDEO_MAX_SIZE], 0, OMTConstants.VIDEO_MAX_SIZE);
} else if (receiveFrameType == OMTFrameType.Audio) {
poolCount = OMTConstants.AUDIO_FRAME_POOL_COUNT;
startingFrameSize = OMTConstants.AUDIO_MIN_SIZE;
receiveargs.SetBuffer(new byte[OMTConstants.AUDIO_MAX_SIZE], 0, OMTConstants.AUDIO_MAX_SIZE);
} else {
poolCount = 1;
startingFrameSize = OMTConstants.AUDIO_MIN_SIZE;
receiveargs.SetBuffer(new byte[OMTConstants.AUDIO_MAX_SIZE], 0, OMTConstants.AUDIO_MAX_SIZE);
}
int sendPoolCount = OMTConstants.NETWORK_ASYNC_COUNT;
int startingSendPoolSize = OMTConstants.AUDIO_MIN_SIZE;
if (metadataServer)
{
sendPoolCount = OMTConstants.NETWORK_ASYNC_COUNT_META_ONLY;
startingSendPoolSize = OMTConstants.NETWORK_ASYNC_BUFFER_META_ONLY;
}
sendpool = new OMTSocketAsyncPool(sendPoolCount, startingSendPoolSize);
metapool = new OMTSocketAsyncPool(OMTConstants.NETWORK_ASYNC_COUNT_META_ONLY, OMTConstants.NETWORK_ASYNC_BUFFER_META_ONLY);
framePool = new OMTFramePool(poolCount, startingFrameSize, true);
readyFrames = new Queue<OMTFrame>();
frameReadyEvent = frameReady;
metadatas = new Queue<OMTMetadata>();
metadataReadyEvent = metadataReady;
}
protected void OnEvent(OMTEventType type)
{
tempEvent.Type = type;
Changed?.Invoke(this, tempEvent);
}
public OMTQuality SuggestedQuality { get { return suggestedQuality; } }
public OMTSenderInfo SenderInformation { get { return senderInfo; } }
public bool Connected { get {
if (socket == null) return false;
return socket.Connected;
} }
private void CloseSocket()
{
lock (lockSync)
{
if (socket != null)
{
socket.Close();
socket = null;
}
}
}
public Socket Socket { get { return socket; } }
public int Send(OMTMetadata metadata)
{
OMTBuffer m = OMTBuffer.FromMetadata(metadata.XML);
OMTFrame frame = new OMTFrame(OMTFrameType.Metadata, m);
frame.Timestamp = metadata.Timestamp;
return Send(frame);
}
public bool IsVideo()
{
if (subscriptions.HasFlag(OMTFrameType.Video)) return true;
return false;
}
public bool IsMetadata()
{
if (subscriptions.HasFlag(OMTFrameType.Metadata)) return true;
return false;
}
public bool IsAudio()
{
if (subscriptions.HasFlag(OMTFrameType.Audio)) return true;
return false;
}
public int Send(OMTFrame frame)
{
lock (sendSync)
{
if (Exiting) return 0;
int written = 0;
try
{
if ((frame.FrameType != OMTFrameType.Metadata) && (subscriptions & frame.FrameType) != frame.FrameType)
{
return 0;
}
frame.SetPreviewMode(preview);
int length = frame.Length;
if (length > OMTConstants.VIDEO_MAX_SIZE)
{
statistics.FramesDropped += 1;
Debug.WriteLine("OMTChannel.Send.DroppedOversizedFrame");
return 0;
}
OMTSocketAsyncPool pool = sendpool;
if (frame.FrameType == OMTFrameType.Metadata)
{
pool = metapool;
}
SocketAsyncEventArgs e = pool.GetEventArgs();
if (e == null)
{
statistics.FramesDropped += 1;
Debug.WriteLine("OMTChannel.Send.DroppedFrame");
return 0;
}
pool.Resize(e, length);
frame.WriteHeaderTo(e.Buffer, 0, e.Count);
int headerLength = frame.HeaderLength + frame.ExtendedHeaderLength;
frame.WriteDataTo(e.Buffer, 0, headerLength, length - headerLength);
e.SetBuffer(0, length);
pool.SendAsync(socket, e);
written = length;
if (frame.FrameType != OMTFrameType.Metadata)
{
statistics.Frames += 1;
statistics.FramesSinceLast += 1;
}
statistics.BytesSent += written;
statistics.BytesSentSinceLast += written;
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTChannel.Send");
}
return written;
}
}
public int ReadyFrameCount { get
{
lock (readyFrames)
{
return readyFrames.Count;
}
} }
public int ReadyMetadataCount { get {
lock (metadatas)
{
return metadatas.Count;
}
} }
public OMTStatistics GetStatistics() {
OMTStatistics s = statistics;
statistics.FramesSinceLast = 0;
statistics.BytesSentSinceLast = 0;
statistics.BytesReceivedSinceLast = 0;
return s;
}
public OMTFrame ReceiveFrame()
{
lock (readyFrames)
{
if (Exiting) return null;
if (readyFrames.Count > 0)
{
return readyFrames.Dequeue();
}
}
return null;
}
public void ReturnFrame(OMTFrame frame)
{
lock (readyFrames)
{
if (frame != null)
{
if (Exiting)
{
frame.Dispose();
}
else
{
framePool.Return(frame);
}
}
}
}
public OMTMetadata ReceiveMetadata()
{
lock (metadatas)
{
if (Exiting) return null;
if (metadatas.Count > 0)
{
return metadatas.Dequeue();
}
}
return null;
}
public OMTTally GetTally()
{
return tally;
}
private void UpdateTally(OMTTally t)
{
if (t.Preview != tally.Preview || t.Program != tally.Program)
{
tally = t;
OnEvent(OMTEventType.TallyChanged);
}
}
private bool ProcessMetadata(OMTFrame frame)
{
if (frame.FrameType == OMTFrameType.Metadata)
{
string xml = frame.Data.ToMetadata();
if (xml == OMTMetadataConstants.CHANNEL_SUBSCRIBE_VIDEO)
{
subscriptions |= OMTFrameType.Video;
return true;
}
else if (xml == OMTMetadataConstants.CHANNEL_SUBSCRIBE_AUDIO)
{
subscriptions |= OMTFrameType.Audio;
return true;
} else if (xml == OMTMetadataConstants.CHANNEL_SUBSCRIBE_METADATA)
{
subscriptions |= OMTFrameType.Metadata;
return true;
} else if (xml == OMTMetadataConstants.TALLY_PREVIEWPROGRAM)
{
UpdateTally(new OMTTally(1, 1));
return true;
} else if (xml == OMTMetadataConstants.TALLY_PROGRAM)
{
UpdateTally(new OMTTally(0, 1));
return true;
} else if (xml == OMTMetadataConstants.TALLY_PREVIEW)
{
UpdateTally(new OMTTally(1, 0));
return true;
} else if (xml == OMTMetadataConstants.TALLY_NONE)
{
UpdateTally(new OMTTally(0, 0));
return true;
} else if (xml == OMTMetadataConstants.CHANNEL_PREVIEW_VIDEO_ON)
{
preview = true;
return true;
} else if (xml == OMTMetadataConstants.CHANNEL_PREVIEW_VIDEO_OFF)
{
preview = false;
return true;
} else if (xml.StartsWith(OMTMetadataTemplates.SUGGESTED_QUALITY_PREFIX))
{
XmlDocument doc = OMTMetadataUtils.TryParse(xml);
if (doc != null)
{
XmlNode n = doc.DocumentElement;
if (n != null)
{
XmlNode a = n.Attributes.GetNamedItem("Quality");
if (a != null)
{
if (a.InnerText!= null)
{
foreach (OMTQuality e in Enum.GetValues(typeof(OMTQuality)))
{
if (e.ToString() == a.InnerText)
{
suggestedQuality = e;
break;
}
}
}
}
}
}
return true;
} else if (xml.StartsWith(OMTMetadataTemplates.SENDER_INFO_PREFIX))
{
senderInfo = OMTSenderInfo.FromXML(xml);
//Don't return here, as this info should passthrough to receiver as well.
} else if (xml.StartsWith(OMTMetadataTemplates.REDIRECT_PREFIX))
{
this.redirectAddress = OMTRedirect.FromXML(xml);
OnEvent(OMTEventType.RedirectChanged);
return true;
}
lock (metadatas)
{
if (metadatas.Count < OMTConstants.METADATA_MAX_COUNT)
{
metadatas.Enqueue(new OMTMetadata(frame.Timestamp, xml, endPoint));
}
if (metadataReadyEvent != null)
{
metadataReadyEvent.Set();
}
}
return true;
}
return false;
}
private void ProtocolFailure(string reason)
{
CloseSocket();
OMTLogging.Write("ProtocolFailure: " + reason, "OMTReceive");
}
private void Receive_Completed(object sender, SocketAsyncEventArgs e)
{
try
{
lock (lockSync)
{
if (Exiting) return;
if (socket == null) return;
if (e.SocketError == SocketError.Success && e.BytesTransferred > 0)
{
statistics.BytesReceived += e.BytesTransferred;
statistics.BytesReceivedSinceLast += e.BytesTransferred;
int len = e.Offset + e.BytesTransferred;
while (len > 0)
{
if (pendingFrame == null)
{
pendingFrame = framePool.Get();
}
if (pendingFrame.ReadHeaderFrom(e.Buffer, 0, len))
{
if (pendingFrame.FrameType != OMTFrameType.Video & pendingFrame.FrameType != OMTFrameType.Audio & pendingFrame.FrameType != OMTFrameType.Metadata)
{
ProtocolFailure("Invalid packet or unsupported frame type: " + pendingFrame.FrameType);
return;
}
if (pendingFrame.ReadExtendedHeaderFrom(e.Buffer, 0, len))
{
if (pendingFrame.ReadDataFrom(e.Buffer, 0, len))
{
int read = pendingFrame.Length;
int remaining = len - read;
if (remaining > 0)
{
tempReceiveBuffer.Resize(remaining);
Buffer.BlockCopy(e.Buffer, read, tempReceiveBuffer.Buffer, 0, remaining);
Buffer.BlockCopy(tempReceiveBuffer.Buffer, 0, e.Buffer, 0, remaining);
len = remaining;
}
else
{
len = 0;
}
if (ProcessMetadata(pendingFrame))
{
framePool.Return(pendingFrame);
pendingFrame = null;
}
else
{
if (framePool.Count > 0)
{
lock (readyFrames)
{
readyFrames.Enqueue(pendingFrame);
}
pendingFrame = null;
if (frameReadyEvent != null)
{
frameReadyEvent.Set();
}
statistics.Frames += 1;
statistics.FramesSinceLast += 1;
}
else
{
statistics.FramesDropped += 1;
Debug.WriteLine("Receive.DroppedFrame: Ready " + readyFrames.Count);
}
}
}
else { break; }
}
else { break; }
}
else { break; }
}
e.SetBuffer(len, e.Buffer.Length - len);
StartReceive(e);
}
else
{
OMTLogging.Write("SocketClosing: " + e.SocketError.ToString() + "," + e.BytesTransferred,"OMTChannel.Receive");
CloseSocket();
OnEvent(OMTEventType.Disconnected);
}
}
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTChannel.Receive");
}
}
public void StartReceive(SocketAsyncEventArgs e)
{
if (!Exiting)
{
if (socket != null)
{
if (socket.Connected)
{
if (socket.ReceiveAsync(e) == false)
{
Receive_Completed(this, e);
}
}
}
}
}
public void StartReceive()
{
receiveargs.SetBuffer(0, receiveargs.Buffer.Length);
StartReceive(receiveargs);
}
protected override void DisposeInternal()
{
lock (sendSync) { }
lock (readyFrames) { }
lock (metadatas)
{
metadatas.Clear();
}
CloseSocket();
if (receiveargs != null)
{
receiveargs.Completed -= Receive_Completed;
receiveargs.Dispose();
receiveargs = null;
}
if (sendpool != null)
{
sendpool.Dispose();
sendpool = null;
}
if (metapool != null)
{
metapool.Dispose();
metapool = null;
}
if (framePool != null)
{
framePool.Dispose();
framePool = null;
}
if (readyFrames != null)
{
lock (readyFrames)
{
foreach (OMTFrame frame in readyFrames)
{
if (frame != null)
{
frame.Dispose();
}
}
readyFrames.Clear();
}
}
if (pendingFrame != null)
{
pendingFrame.Dispose();
pendingFrame = null;
}
if (tempReceiveBuffer != null)
{
tempReceiveBuffer.Dispose();
tempReceiveBuffer = null;
}
base.DisposeInternal();
}
}
}

View File

@@ -0,0 +1,103 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
namespace libomtnet
{ internal class OMTClock : OMTBase
{
private long lastTimestamp = -1;
private Stopwatch clock = Stopwatch.StartNew();
private long clockTimestamp = -1;
private int frameRateN = -1;
private int frameRateD = -1;
private int sampleRate = -1;
private long frameInterval = -1;
private bool audio;
public OMTClock(bool audio)
{
this.audio = audio;
}
public void Process(ref OMTMediaFrame frame)
{
if (audio && frame.SampleRate != sampleRate)
{
Reset(frame);
} else if ((frame.FrameRateN != frameRateN) || frame.FrameRateD != frameRateD)
{
Reset(frame);
}
if (frame.Timestamp == -1)
{
if (lastTimestamp == -1)
{
Reset(frame);
frame.Timestamp = 0;
} else
{
if (audio && sampleRate > 0 && frame.SamplesPerChannel > 0)
{
frameInterval = 10000000L * frame.SamplesPerChannel;
frameInterval /= sampleRate;
}
frame.Timestamp = lastTimestamp + frameInterval;
clockTimestamp += frameInterval;
long diff = clockTimestamp - (clock.ElapsedMilliseconds * 10000);
while (diff < -frameInterval)
{
frame.Timestamp += frameInterval;
clockTimestamp += frameInterval;
diff += frameInterval;
}
while (!Exiting && (clockTimestamp > clock.ElapsedMilliseconds * 10000))
{
Thread.Sleep(1);
}
}
}
lastTimestamp = frame.Timestamp;
}
private void Reset(OMTMediaFrame frame)
{
frameRateD = frame.FrameRateD;
frameRateN = frame.FrameRateN;
sampleRate = frame.SampleRate;
if (frame.FrameRate > 0)
{
frameInterval = (long)(10000000 / frame.FrameRate);
}
clock = Stopwatch.StartNew();
clockTimestamp = 0;
Debug.WriteLine("OMTClock.Reset");
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace libomtnet
{
public class OMTPublicConstants
{
public static int DISCOVERY_SERVER_DEFAULT_PORT = 6399;
}
internal class OMTConstants
{
public static int NETWORK_SEND_BUFFER = 65536;
public static int NETWORK_SEND_RECEIVE_BUFFER = 65536;
public static int NETWORK_RECEIVE_BUFFER = 1048576 * 8; //8MB is a safe maximum for MacOS platforms
public static int NETWORK_ASYNC_COUNT = 4;
public static int NETWORK_ASYNC_BUFFER_AV = 1048576;
public static int NETWORK_ASYNC_BUFFER_META = 65536;
//For OMTDiscoveryServer
public static int NETWORK_ASYNC_COUNT_META_ONLY = 64;
public static int NETWORK_ASYNC_BUFFER_META_ONLY = 1024;
public static int VIDEO_FRAME_POOL_COUNT = 4;
public static int VIDEO_MIN_SIZE = 65536;
public static int VIDEO_MAX_SIZE = 10485760;
public static int AUDIO_FRAME_POOL_COUNT = 10;
public static int AUDIO_MIN_SIZE = 65536;
public static int AUDIO_MAX_SIZE = 1048576;
public static int NETWORK_PORT_START = 6400;
public static int NETWORK_PORT_END = 6600;
public static int AUDIO_SAMPLE_SIZE = 4;
public static int METADATA_MAX_COUNT = 60;
public static int METADATA_FRAME_SIZE = 65536;
public static string URL_PREFIX = "omt://";
}
}

View File

@@ -0,0 +1,656 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Net;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
namespace libomtnet
{
public class OMTDiscovery : OMTBase
{
private string[] addresses = { };
private OMTAddressSorter addressSorter = new OMTAddressSorter();
internal List<OMTDiscoveryEntry> entries = new List<OMTDiscoveryEntry>();
private List<IOMTDiscoveryNotify> notifications = new List<IOMTDiscoveryNotify>();
protected object lockSync = new object();
private static object sharedLockSync = new object();
private static OMTDiscovery instance = null;
private DateTime lastCleared;
private OMTDiscoveryClient discoveryClient = null;
protected OMTDiscovery()
{
entries = new List<OMTDiscoveryEntry>();
try
{
OMTSettings settings = OMTSettings.GetInstance();
string server = settings.GetString("DiscoveryServer", "");
if (!String.IsNullOrEmpty(server))
{
discoveryClient = new OMTDiscoveryClient(server, this);
}
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTDiscovery");
}
}
internal bool IsUsingServer()
{
if (discoveryClient == null) return false;
return true;
}
///<summary>
///Get the shared instance of OMTDiscovery used by all Senders and Receivers with a process.
///This should never be disposed, or only disposed only when last sender or receiver has been disposed and no further use of this library is expected.
/// </summary>
public static OMTDiscovery GetInstance()
{
lock (sharedLockSync)
{
if (instance == null)
{
switch (OMTPlatform.GetPlatformType())
{
case OMTPlatformType.Win32:
instance = new win32.OMTDiscoveryWin32();
break;
case OMTPlatformType.MacOS:
case OMTPlatformType.iOS:
instance = new mac.OMTDiscoveryMac();
break;
case OMTPlatformType.Linux:
instance = new linux.OMTDiscoveryAvahi();
break;
default:
instance = new OMTDiscovery();
break;
}
}
}
return instance;
}
internal void Subscribe(IOMTDiscoveryNotify notify)
{
lock (lockSync)
{
if (notifications.Contains(notify) == false)
{
notifications.Add(notify);
}
}
}
internal void Unsubscribe(IOMTDiscoveryNotify notify)
{
lock (lockSync)
{
if (notifications.Contains(notify))
{
notifications.Remove(notify);
}
}
}
internal void OnNewAddress(OMTAddress address)
{
if (address.Addresses.Length > 0)
{
IOMTDiscoveryNotify[] n = null;
lock (lockSync)
{
n = notifications.ToArray();
}
if (n != null)
{
foreach (IOMTDiscoveryNotify notify in n)
{
notify.Notify(address);
}
}
}
}
internal OMTDiscoveryEntry GetEntry(OMTAddress address)
{
lock (lockSync)
{
foreach (OMTDiscoveryEntry entry in entries)
{
if (entry.Address.ToString() == address.ToString())
{
return entry;
}
}
}
return null;
}
internal OMTDiscoveryEntry GetEntry(string fullName)
{
lock (lockSync)
{
foreach (OMTDiscoveryEntry entry in entries)
{
if (entry.Address.ToString() == fullName)
{
return entry;
}
}
}
return null;
}
internal bool RemoveDiscoveredEntry(string fullName)
{
lock (lockSync)
{
OMTDiscoveryEntry entry = GetEntry(fullName);
if (entry != null)
{
if (entry.Status == OMTDiscoveryEntryStatus.Discovered)
{
RemoveEntry(entry.Address, true);
OMTLogging.Write("Remove: " + entry.Address.ToString() + ":" + entry.Address.Port, "OMTDiscovery");
return true;
}
}
}
return false;
}
internal OMTDiscoveryEntry UpdateDiscoveredEntry(string fullName, int port, IPAddress[] addresses)
{
lock (lockSync)
{
OMTDiscoveryEntry entry = GetEntry(fullName);
if (entry == null)
{
OMTAddress address = OMTAddress.Create(fullName, port);
entry = new OMTDiscoveryEntry(address);
entry.ChangeStatus(OMTDiscoveryEntryStatus.Discovered);
if (AddEntry(entry))
{
OMTLogging.Write("New: " + fullName + ":" + port, "OMTDiscovery");
if (addresses != null)
{
foreach (IPAddress ip in addresses)
{
if (address.AddAddress(ip))
{
OMTLogging.Write("NewIP: " + fullName + ":" + port + "," + ip.ToString(), "OMTDiscovery");
}
}
}
return entry;
}
} else
{
bool newPort = false;
if (entry.Address.Port != port)
{
entry.Address.Port = port;
newPort = true;
OMTLogging.Write("ChangePort: " + fullName + ":" + port, "OMTDiscovery");
}
if (addresses != null)
{
bool newIp = false;
foreach (IPAddress ip in addresses)
{
if (entry.Address.AddAddress(ip))
{
OMTLogging.Write("AddIP: " + fullName + ":" + port + "," + ip.ToString(), "OMTDiscovery");
newIp = true;
}
}
if (newIp || newPort)
{
OnNewAddress(entry.Address);
}
}
return entry;
}
}
return null;
}
internal bool AddEntry(OMTDiscoveryEntry entry)
{
lock (lockSync)
{
if (entries.Contains(entry) == false)
{
entries.Add(entry);
RefreshAddresses();
return true;
}
}
return false;
}
private void RefreshAddresses ()
{
List<string> addresses = new List<string>();
foreach (OMTDiscoveryEntry entry in entries)
{
addresses.Add(entry.Address.ToString());
}
addresses.Sort();
this.addresses = addresses.ToArray();
}
internal bool RemoveEntry(OMTAddress address, bool dispose)
{
lock (lockSync)
{
foreach (OMTDiscoveryEntry entry in entries)
{
if (entry.Address.ToString() == address.ToString())
{
entries.Remove(entry);
if (dispose)
{
entry.Dispose();
}
RefreshAddresses();
return true;
}
}
}
return false;
}
internal virtual bool DeregisterAddressInternal(OMTAddress address)
{
return DeregisterAddressDefault(address);
}
internal virtual bool RegisterAddressInternal(OMTAddress address)
{
return RegisterAddressDefault(address);
}
private bool RegisterAddressDefault(OMTAddress address)
{
lock (lockSync)
{
OMTDiscoveryEntry entry = GetEntry(address);
if (entry != null)
{
if (entry.Status == OMTDiscoveryEntryStatus.Discovered)
{
RemoveEntry(address, true);
entry = null;
}
}
if (entry == null)
{
entry = new OMTDiscoveryEntry(address);
AddEntry(entry);
return true;
}
return false;
}
}
private bool DeregisterAddressDefault(OMTAddress address)
{
return RemoveEntry(address, true);
}
internal bool RegisterAddress(OMTAddress address)
{
if (IsUsingServer())
{
if (RegisterAddressDefault(address))
{
address.removed = false;
discoveryClient.SendAddress(address);
return true;
}
return false;
}
return RegisterAddressInternal(address);
}
internal bool DeregisterAddress(OMTAddress address)
{
if (IsUsingServer())
{
if (DeregisterAddressDefault(address))
{
address.removed = true;
discoveryClient.SendAddress(address);
return true;
}
return false;
}
return DeregisterAddressInternal(address);
}
static internal OMTAddress CreateFromUrl(string address, int defaultPort)
{
Uri u = null;
if (Uri.TryCreate(address, UriKind.Absolute, out u))
{
int port = u.Port;
if (port <= 0)
{
port = defaultPort;
}
if (port > 0)
{
OMTAddress a = new OMTAddress(u.Host, port.ToString(), port);
IPAddress[] ips = OMTUtils.ResolveHostname(u.Host);
if (ips != null && ips.Length > 0)
{
foreach (IPAddress ip in ips)
{
a.AddAddress(ip);
}
return a;
}
}
}
return null;
}
internal OMTAddress FindByFullNameOrUrl(string address)
{
if (string.IsNullOrEmpty(address)) { return null; }
if (address.ToLower().StartsWith(OMTConstants.URL_PREFIX))
{
return CreateFromUrl(address, 0);
}
else
{
return FindByFullName(address);
}
}
internal OMTAddress FindByFullName(string fullName)
{
if (string.IsNullOrEmpty(fullName)) { return null; }
lock (lockSync)
{
foreach (OMTDiscoveryEntry entry in entries)
{
if (entry.Address.ToString().Equals(OMTAddress.SanitizeName(fullName)))
{
return entry.Address;
}
}
}
return null;
}
internal string ParseAddressName(string name)
{
int pos = name.IndexOf("._omt.");
if (pos > 0)
{
return name.Substring(0, pos);
}
return name;
}
internal void RemoveServerAddresses()
{
lock (lockSync)
{
for (int i = entries.Count - 1; i >= 0; i--)
{
OMTDiscoveryEntry entry = entries[i];
if (entry.FromServer)
{
if (entry.Status == OMTDiscoveryEntryStatus.Discovered)
{
entries.RemoveAt(i);
OMTLogging.Write("RemovedAddressFromServer: " + entry.Address.ToString(), "OMTDiscovery");
}
}
}
RefreshAddresses();
}
}
internal void RemoveExpiredAddresses()
{
lock (lockSync)
{
lastCleared = DateTime.Now;
for (int i = entries.Count - 1; i >= 0; i--)
{
OMTDiscoveryEntry entry = entries[i];
if (entry.Status == OMTDiscoveryEntryStatus.Discovered)
{
if (entry.Expiry > DateTime.MinValue && entry.Expiry < DateTime.Now)
{
entries.RemoveAt(i);
OMTLogging.Write("ExpiredAddress: " + entry.Address.ToString(), "OMTDiscovery");
}
}
}
RefreshAddresses();
}
}
protected int GetRegisteredEntryCount()
{
int count = 0;
lock (lockSync)
{
foreach (OMTDiscoveryEntry entry in entries)
{
if (entry.Status == OMTDiscoveryEntryStatus.Registered)
{
count++;
}
}
}
return 0;
}
internal OMTDiscoveryEntry GetEntryByPort(int port, bool discovered)
{
lock (lockSync)
{
foreach (OMTDiscoveryEntry entry in entries)
{
if (entry.Address.Port == port)
{
if (discovered)
{
if (entry.Status == OMTDiscoveryEntryStatus.Discovered)
{
return entry;
}
}
else
{
if (entry.Status != OMTDiscoveryEntryStatus.Discovered)
{
return entry;
}
}
}
}
}
return null;
}
///<summary>
/// Retrieve a list of Sources currently available on the network
/// </summary>
public string[] GetAddresses()
{
if (lastCleared < DateTime.Now.AddSeconds(-10))
{
RemoveExpiredAddresses();
}
return addresses;
}
internal OMTAddress[] GetAddressesInternal()
{
List<OMTAddress> a = new List<OMTAddress>();
lock (lockSync)
{
foreach (OMTDiscoveryEntry rr in entries)
{
if (rr.Status != OMTDiscoveryEntryStatus.Discovered)
{
a.Add(rr.Address);
}
}
}
return a.ToArray();
}
private void DisposeEntries()
{
OMTDiscoveryEntry[] e = null;
lock (lockSync)
{
e = entries.ToArray();
}
if (e != null)
{
foreach (OMTDiscoveryEntry rr in e)
{
if (rr != null)
{
rr.Dispose(); //The cancel request calls OnComplete, so run outside of lock here
}
}
}
lock (lockSync)
{
entries.Clear();
}
}
protected override void DisposeInternal()
{
if (discoveryClient != null)
{
discoveryClient.Dispose();
discoveryClient = null;
}
DisposeEntries();
base.DisposeInternal();
}
}
internal interface IOMTDiscoveryNotify
{
void Notify(OMTAddress address);
}
internal enum OMTDiscoveryEntryStatus
{
None = 0,
PendingRegister,
PendingDeRegister,
PendingRegisterAfterDeRegister,
PendingDeRegisterAfterRegister,
Registered,
Discovered
}
internal class OMTDiscoveryEntry : OMTBase
{
private OMTAddress address;
private OMTDiscoveryEntryStatus status;
private GCHandle handle;
private DateTime expiry;
private bool fromServer;
public OMTAddress Address { get { return address; } }
public OMTDiscoveryEntryStatus Status { get { return status; } }
public OMTDiscoveryEntry(OMTAddress address)
{
this.address = address;
this.status = OMTDiscoveryEntryStatus.None;
}
public DateTime Expiry
{
get { return expiry; }
set { expiry = value; }
}
public bool FromServer
{
get { return fromServer; }
set { fromServer = value; }
}
public void ChangeStatus(OMTDiscoveryEntryStatus status)
{
this.status = status;
}
public static OMTDiscoveryEntry FromIntPtr(IntPtr p)
{
GCHandle h = GCHandle.FromIntPtr(p);
if (h.IsAllocated)
{
OMTDiscoveryEntry q = (OMTDiscoveryEntry)h.Target;
return q;
}
return null;
}
public IntPtr ToIntPtr()
{
if (!handle.IsAllocated)
{
handle = GCHandle.Alloc(this);
}
return GCHandle.ToIntPtr(handle);
}
protected override void DisposeInternal()
{
if (handle.IsAllocated)
{
handle.Free();
}
base.DisposeInternal();
}
}
}

View File

@@ -0,0 +1,397 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
namespace libomtnet
{
internal enum OMTVersion
{
Version1 = 1
}
[Flags]
internal enum OMTActiveAudioChannels : uint
{
C1 = 1,
C2 = 2,
C3 = 4,
C4 = 8,
C5 = 16,
C6 = 32,
C7 = 64,
C8 = 128,
C9 = 256,
C10 = 512,
C11 = 1024,
C12 = 2048,
C13 = 4096,
C14 = 8192,
C15 = 16384,
C16 = 32768,
C17 = 65536,
C18 = 131072,
C19 = 262144,
C20 = 524288,
C21 = 1048576,
C22 = 2097152,
C23 = 4194304,
C24 = 8388608,
C25 = 16777216,
C26 = 33554432,
C27 = 67108864,
C28 = 134217728,
C29 = 268435456,
C30 = 536870912,
C31 = 1073741824,
C32 = 2147483658
}
internal struct OMTFrameHeader
{
public byte Version; //=1
public byte FrameType;
public long Timestamp;
//public byte Reserved1;
//public byte Reserved2;
public ushort MetadataLength; //Length in bytes of UTF-8 metadata including null character
public int DataLength; //Including extended header and metadata
}
internal struct OMTVideoHeader
{
public int Codec;
public int Width;
public int Height;
public int FrameRateN;
public int FrameRateD;
public float AspectRatio;
public int Flags;
public int ColorSpace;
}
internal struct OMTAudioHeader
{
public int Codec;
public int SampleRate;
public int SamplesPerChannel;
public int Channels;
public uint ActiveChannels;
public int Reserved1;
}
internal enum OMTFrameLength
{
None = 0,
Header = 16,
ExtendedHeaderVideo = 32,
ExtendedHeaderAudio = 24
}
internal class OMTFrameBase : OMTBase
{
public virtual OMTFrameType FrameType { get { return OMTFrameType.None; } }
public virtual long Timestamp
{ get { return 0; } set { } }
}
internal class OMTFrame : OMTFrameBase
{
protected OMTFrameHeader header;
protected OMTBuffer buffer;
protected OMTVideoHeader videoHeader;
protected OMTAudioHeader audioHeader;
protected int previewLength;
protected bool preview;
protected OMTBinary binary = new OMTBinary();
public OMTFrame(OMTFrameType frameType, int maxDataLength, bool resizable)
{
header.Version = (byte)OMTVersion.Version1;
header.FrameType = (byte)frameType;
buffer = new OMTBuffer(maxDataLength, resizable);
buffer.SetBuffer(0, 0);
UpdateDataLength();
}
public OMTFrame(int maxDataLength, bool resizable)
{
buffer = new OMTBuffer(maxDataLength, resizable);
buffer.SetBuffer(0, 0);
}
public OMTFrame(OMTFrameType frameType, OMTBuffer buff)
{
header.Version = (byte)OMTVersion.Version1;
header.FrameType = (byte)frameType;
buffer = buff;
UpdateDataLength();
}
public int HeaderLength
{
get { return (int)OMTFrameLength.Header; }
}
public int MetadataLength
{
get { return (int)header.MetadataLength; }
}
public int ExtendedHeaderLength
{
get
{
if (header.FrameType == (byte)OMTFrameType.Video)
{
return (int)OMTFrameLength.ExtendedHeaderVideo;
}
else if (header.FrameType == (byte)OMTFrameType.Audio)
{
return (int)OMTFrameLength.ExtendedHeaderAudio;
}
return 0;
}
}
public int Length
{
get
{
if (preview)
{
return HeaderLength + previewLength;
} else
{
return HeaderLength + header.DataLength;
}
}
}
protected void WriteHeaderInternal()
{
OMTBinary b = binary;
b.Write(header.Version);
b.Write(header.FrameType);
b.Write(header.Timestamp);
b.Write(header.MetadataLength);
if (preview)
{
b.Write(previewLength);
}
else
{
b.Write(header.DataLength);
}
}
public override OMTFrameType FrameType
{
get { return (OMTFrameType)header.FrameType; }
}
public override long Timestamp
{ get { return header.Timestamp; } set { header.Timestamp = value; } }
protected void WriteExtendedHeaderInternal()
{
OMTBinary b = binary;
if (header.FrameType == (byte)OMTFrameType.Video)
{
b.Write(videoHeader.Codec);
b.Write(videoHeader.Width);
b.Write(videoHeader.Height);
b.Write(videoHeader.FrameRateN);
b.Write(videoHeader.FrameRateD);
b.Write(videoHeader.AspectRatio);
if (preview)
{
b.Write(videoHeader.Flags | (int)OMTVideoFlags.Preview);
}
else
{
b.Write(videoHeader.Flags);
}
b.Write(videoHeader.ColorSpace);
}
else if (header.FrameType == (byte)OMTFrameType.Audio)
{
b.Write(audioHeader.Codec);
b.Write(audioHeader.SampleRate);
b.Write(audioHeader.SamplesPerChannel);
b.Write(audioHeader.Channels);
b.Write(audioHeader.ActiveChannels);
b.Write(audioHeader.Reserved1);
}
}
protected bool ReadHeaderInternal(byte[] data, int offset)
{
OMTBinary b = binary;
b.SetBuffer(data, offset);
header.Version = b.ReadByte();
if (header.Version == (byte)OMTVersion.Version1)
{
header.FrameType = b.ReadByte();
header.Timestamp = b.ReadInt64();
header.MetadataLength = b.ReadUInt16();
header.DataLength = b.ReadInt32();
return true;
}
return false;
}
protected bool ReadExtendedHeaderInternal(byte[] data, int offset)
{
OMTBinary b = binary;
b.SetBuffer(data, offset);
if (header.Version == (byte)OMTVersion.Version1)
{
if (header.FrameType == (byte)OMTFrameType.Video)
{
videoHeader.Codec = b.ReadInt32();
videoHeader.Width = b.ReadInt32();
videoHeader.Height = b.ReadInt32();
videoHeader.FrameRateN = b.ReadInt32();
videoHeader.FrameRateD = b.ReadInt32();
videoHeader.AspectRatio = b.ReadSingle();
videoHeader.Flags = b.ReadInt32();
videoHeader.ColorSpace = b.ReadInt32();
return true;
}
else if (header.FrameType == (byte)OMTFrameType.Audio)
{
audioHeader.Codec = b.ReadInt32();
audioHeader.SampleRate = b.ReadInt32();
audioHeader.SamplesPerChannel = b.ReadInt32();
audioHeader.Channels = b.ReadInt32();
audioHeader.ActiveChannels = b.ReadUInt32();
audioHeader.Reserved1 = b.ReadInt32();
return true;
}
}
return false;
}
public OMTBuffer Data
{
get { return buffer; }
}
private void UpdateDataLength()
{
header.DataLength = this.buffer.Length + ExtendedHeaderLength;
}
/// <summary>
/// Includes MetadataLength
/// </summary>
/// <param name="length"></param>
public void SetPreviewDataLength(int length)
{
previewLength = ExtendedHeaderLength + length;
}
/// <summary>
/// Includes MetadataLength
/// </summary>
/// <param name="length"></param>
public void SetDataLength(int length)
{
this.buffer.SetBuffer(0, length);
UpdateDataLength();
}
public void SetMetadataLength(int length)
{
header.MetadataLength = (ushort)length;
}
public void SetPreviewMode(bool preview)
{
this.preview = preview;
}
public void WriteHeaderTo(byte[] buffer, int offset, int count)
{
binary.SetBuffer(buffer, offset);
WriteHeaderInternal();
WriteExtendedHeaderInternal();
}
public void WriteDataTo(byte[] buffer, int srcOffset, int dstOffset, int count)
{
Buffer.BlockCopy(this.buffer.Buffer, this.buffer.Offset + srcOffset, buffer, dstOffset, count);
}
public bool ReadHeaderFrom(byte[] buffer, int offset, int count)
{
if (count < HeaderLength) return false;
return ReadHeaderInternal(buffer, offset);
}
public bool ReadExtendedHeaderFrom(byte[] buffer, int offset, int count)
{
if (count < HeaderLength + ExtendedHeaderLength) return false;
if (ExtendedHeaderLength == 0) return true;
return ReadExtendedHeaderInternal(buffer, offset + HeaderLength);
}
public bool ReadDataFrom(byte[] buffer, int offset, int count)
{
if (count < HeaderLength + header.DataLength) return false;
int len = header.DataLength - ExtendedHeaderLength;
this.buffer.Resize(len);
this.buffer.SetBuffer(0, 0);
this.buffer.Append(buffer, offset + HeaderLength + ExtendedHeaderLength, len);
this.buffer.SetBuffer(0, len);
return true;
}
public OMTVideoHeader GetVideoHeader()
{
return videoHeader;
}
public OMTAudioHeader GetAudioHeader()
{
return audioHeader;
}
public void ConfigureVideo(int codec, int width, int height, int framerateN, int framerateD, float aspectRatio, OMTVideoFlags flags, OMTColorSpace colorSpace)
{
videoHeader.Codec = codec;
videoHeader.Width = width;
videoHeader.Height = height;
videoHeader.FrameRateN = framerateN;
videoHeader.FrameRateD = framerateD;
videoHeader.AspectRatio = aspectRatio;
videoHeader.Flags = (int)flags;
videoHeader.ColorSpace = (int)colorSpace;
}
public void ConfigureAudio(int sampleRate, int channels, int samplesPerChannel, OMTActiveAudioChannels activeAudioChannels)
{
audioHeader.SampleRate = sampleRate;
audioHeader.Channels = channels;
audioHeader.SamplesPerChannel = samplesPerChannel;
audioHeader.ActiveChannels = (uint)activeAudioChannels;
audioHeader.Codec = (int)OMTCodec.FPA1;
}
protected override void DisposeInternal()
{
if (this.buffer != null)
{
this.buffer.Dispose();
}
base.DisposeInternal();
}
}
}

View File

@@ -0,0 +1,80 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
namespace libomtnet
{
internal class OMTFramePool : OMTBase
{
Queue<OMTFrame> pool;
public OMTFramePool(int count, int maxDataLength, bool resizable)
{
pool = new Queue<OMTFrame>();
for (int i = 0; i < count; i++) {
pool.Enqueue(new OMTFrame(maxDataLength, resizable));
}
}
protected override void DisposeInternal()
{
if (pool != null)
{
foreach (OMTFrame frame in pool)
{
if (frame != null)
{
frame.Dispose();
}
}
pool.Clear();
}
base.DisposeInternal();
}
public OMTFrame Get()
{
lock (pool)
{
if (pool.Count > 0)
{
return pool.Dequeue();
}
}
return null;
}
public void Return(OMTFrame frame)
{
lock (pool)
{
pool.Enqueue(frame);
}
}
public int Count { get { lock (pool) { return pool.Count; } } }
}
}

View File

@@ -0,0 +1,56 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
namespace libomtnet
{ internal enum OMTEventType
{
None = 0,
TallyChanged = 1,
Disconnected = 2,
RedirectChanged = 3
}
internal class OMTEventArgs : EventArgs
{
private OMTEventType eventType;
public OMTEventArgs(OMTEventType eventType)
{
this.eventType = eventType;
}
public OMTEventType Type { get { return eventType; } set { eventType = value; } }
}
internal class OMTRedirectChangedEventArgs : EventArgs
{
private string newAddress;
public OMTRedirectChangedEventArgs(string newAddress)
{
this.newAddress = newAddress;
}
public string NewAddress { get { return newAddress; } }
}
}

View File

@@ -0,0 +1,170 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Threading;
namespace libomtnet
{
public class OMTLogging
{
private static FileStream logStream;
private static StreamWriter logWriter;
private static object lockSync = new object();
private static Thread loggingThread;
private static bool threadRunning;
private static Queue<string> queue = new Queue<string>();
private static AutoResetEvent readyEvent = new AutoResetEvent(false);
private static bool initialized = false;
static OMTLogging()
{
loggingThread = new Thread(ProcessLog);
loggingThread.IsBackground = true;
threadRunning = true;
loggingThread.Start();
}
private static void SetDefaultLogFilename()
{
lock (lockSync)
{
initialized = true;
try
{
string name = GetProcessNameAndId();
if (name != null)
{
string szPath = OMTPlatform.GetInstance().GetStoragePath();
if (Directory.Exists(szPath) == false)
{
Directory.CreateDirectory(szPath);
}
szPath = szPath + Path.DirectorySeparatorChar + "logs";
if (Directory.Exists(szPath) == false)
{
Directory.CreateDirectory(szPath);
}
SetFilename(szPath + Path.DirectorySeparatorChar + name + ".log");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
private static string GetProcessNameAndId()
{
Process process = Process.GetCurrentProcess();
if (process != null)
{
ProcessModule module = process.MainModule;
if (module != null) //Some platforms, notably iOS return null
{
return module.ModuleName + process.Id;
} else
{
return process.Id.ToString();
}
}
return null;
}
private static void ProcessLog()
{
try
{
while (threadRunning)
{
readyEvent.WaitOne();
lock (lockSync)
{
if (logWriter != null)
{
while (queue.Count > 0)
{
logWriter.WriteLine(queue.Dequeue());
}
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString(), "OMTLogging.ProcessLog");
}
}
public static void SetFilename(string filename)
{
lock (lockSync)
{
initialized = true;
if (logStream != null)
{
logStream.Close();
}
logWriter = null;
if (!String.IsNullOrEmpty(filename)) {
logStream = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Write);
logStream.Position = logStream.Length;
logWriter = new StreamWriter(logStream);
logWriter.AutoFlush = true;
OMTLogging.Write("Log Started", "OMTLogging");
}
}
}
public static void Write(string message, string source)
{
try
{
string line = DateTime.Now.ToString() + ",[" + source + "]," + message;
Debug.WriteLine(line);
lock (lockSync)
{
if (!initialized)
{
SetDefaultLogFilename();
}
if (logWriter != null)
{
queue.Enqueue(line);
readyEvent.Set();
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
}
}
}

View File

@@ -0,0 +1,140 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Net;
using System.Xml;
namespace libomtnet
{
/// <summary>
/// Fixed static XML commands for protocol use.
/// Receivers will check for these exact string matches and won't bother to parse the XML.
/// This means any changes to these, even slightly will result in the commands being ignored entirely.
/// </summary>
internal class OMTMetadataConstants
{
public const string CHANNEL_SUBSCRIBE_VIDEO = @"<OMTSubscribe Video=""true"" />";
public const string CHANNEL_SUBSCRIBE_AUDIO = @"<OMTSubscribe Audio=""true"" />";
public const string CHANNEL_SUBSCRIBE_METADATA = @"<OMTSubscribe Metadata=""true"" />";
public const string CHANNEL_PREVIEW_VIDEO_ON = @"<OMTSettings Preview=""true"" />";
public const string CHANNEL_PREVIEW_VIDEO_OFF = @"<OMTSettings Preview=""false"" />";
public const string TALLY_PREVIEW = @"<OMTTally Preview=""true"" Program==""false"" />";
public const string TALLY_PROGRAM = @"<OMTTally Preview=""false"" Program==""true"" />";
public const string TALLY_PREVIEWPROGRAM = @"<OMTTally Preview=""true"" Program==""true"" />";
public const string TALLY_NONE = @"<OMTTally Preview=""false"" Program==""false"" />";
}
internal class OMTMetadataTemplates
{
public const string SUGGESTED_QUALITY_PREFIX = @"<OMTSettings Quality=";
public const string SUGGESTED_QUALITY = @"<OMTSettings Quality=""Default"" />";
public const string SENDER_INFO_NAME = @"OMTInfo";
public const string SENDER_INFO_PREFIX = @"<OMTInfo";
public const string ADDRESS_NAME = @"OMTAddress";
public const string REDIRECT_NAME = @"OMTRedirect";
public const string REDIRECT_PREFIX = @"<OMTRedirect";
}
internal class OMTMetadataUtils
{
public static XmlDocument TryParse(string xml)
{
try
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
return doc;
}
catch (Exception)
{
OMTLogging.Write("Invalid XML: " + xml, "OMTMetadata");
return null;
}
}
}
internal class OMTMetadata : OMTFrameBase
{
public long timestamp;
public string XML;
public IPEndPoint Endpoint;
public OMTMetadata(long timestamp, string xML)
{
Timestamp = timestamp;
XML = xML;
}
public OMTMetadata(long timestamp, string xML, IPEndPoint endpoint)
{
Timestamp = timestamp;
XML = xML;
Endpoint = endpoint;
}
public override long Timestamp
{ get { return timestamp; } set { timestamp = value; } }
public override OMTFrameType FrameType
{ get { return OMTFrameType.Metadata; } }
public IntPtr ToIntPtr(ref int length)
{
return OMTUtils.XMLToIntPtr(XML, ref length);
}
public static OMTMetadata FromTally(OMTTally tally)
{
if (tally.Preview == 0 && tally.Program == 0)
{
return new OMTMetadata(0, OMTMetadataConstants.TALLY_NONE);
}
else if (tally.Preview == 1 && tally.Program == 0)
{
return new OMTMetadata(0, OMTMetadataConstants.TALLY_PREVIEW);
}
else if (tally.Program == 1 && tally.Preview == 0)
{
return new OMTMetadata(0, OMTMetadataConstants.TALLY_PROGRAM);
}
else
{
return new OMTMetadata(0, OMTMetadataConstants.TALLY_PREVIEWPROGRAM);
}
}
public static OMTMetadata FromMediaFrame(OMTMediaFrame metadata)
{
if (metadata.Data != IntPtr.Zero && metadata.DataLength > 0)
{
string xml = OMTUtils.IntPtrToXML(metadata.Data, metadata.DataLength);
return new OMTMetadata(metadata.Timestamp, xml);
}
return null;
}
public static void FreeIntPtr(IntPtr ptr)
{
OMTUtils.FreeXMLIntPtr(ptr);
}
}
}

View File

@@ -0,0 +1,110 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.IO;
namespace libomtnet
{
public class OMTPlatform
{
private static OMTPlatform instance;
private static object globalLock = new object();
private static OMTPlatformType platformType;
static OMTPlatform()
{
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
platformType = OMTPlatformType.Win32;
}
else
{
platformType = OMTPlatformType.Linux;
string path = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
if (path.Contains("/Containers/Data/Application/"))
{
platformType = OMTPlatformType.iOS;
} else if (Directory.Exists("/System/Applications/Utilities/Terminal.app"))
{
platformType = OMTPlatformType.MacOS;
}
}
}
protected virtual string GetLibraryExtension()
{
return ".dll";
}
public virtual string GetMachineName()
{
return Environment.MachineName.ToUpper();
}
public virtual IntPtr OpenLibrary(string filename)
{
return IntPtr.Zero;
}
public virtual string GetStoragePath()
{
string sz = Environment.GetEnvironmentVariable("OMT_STORAGE_PATH");
if (!String.IsNullOrEmpty(sz)) return sz;
return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + Path.DirectorySeparatorChar + "OMT";
}
public static OMTPlatformType GetPlatformType()
{
return platformType;
}
public static OMTPlatform GetInstance()
{
lock (globalLock)
{
if (instance == null)
{
switch (GetPlatformType())
{
case OMTPlatformType.Win32:
instance = new win32.Win32Platform();
break;
case OMTPlatformType.MacOS:
case OMTPlatformType.iOS:
instance = new mac.MacPlatform();
break;
case OMTPlatformType.Linux:
instance = new linux.LinuxPlatform();
break;
default:
instance = new OMTPlatform();
break;
}
}
return instance;
}
}
}
}

View File

@@ -0,0 +1,452 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Runtime.InteropServices;
using System.Xml;
using System.IO;
namespace libomtnet
{
[Flags]
public enum OMTFrameType
{
None = 0,
Metadata = 1,
Video = 2,
Audio = 4
}
/// <summary>
/// Flags set on video frames:
///
/// Interlaced: Frames are interlaced
///
/// Alpha: Frames contain an alpha channel. If this is not set, BGRA will be encoded as BGRX and UYVA will be encoded as UYVY.
///
/// PreMultiplied: When combined with Alpha, alpha channel is premultiplied, otherwise straight
///
/// Preview: Frame is a special 1/8th preview frame
///
/// HighBitDepth: Sender automatically adds this flag for frames encoded using P216 or PA16 pixel formats.
///
/// Set this manually for VMX1 compressed data where the the frame was originally encoded using P216 or PA16.
/// This determines which pixel format is selected on the decode side.
///
/// </summary>
[Flags]
public enum OMTVideoFlags
{
None = 0,
Interlaced = 1,
Alpha = 2,
PreMultiplied = 4,
Preview = 8,
HighBitDepth = 16
}
/// <summary>
/// Supported Codecs:
///
/// VMX1 = Fast video codec
///
/// UYVY = 8-bit YUV format
///
/// YUY2 = 8-bit YUV format with YUYV pixel order
///
/// UYVA = 8-bit YUV format immediately followed by an alpha plane
///
/// NV12 = Planar 4:2:0 YUV format. Y plane followed by interleaved half height U/V plane.
///
/// YV12 = Planar 4:2:0 YUV format. Y plane followed by half height U and V planes.
///
/// BGRA = 32bpp RGBA format (Same as ARGB32 on Win32)
///
/// P216 = Planar 4:2:2 YUV format. 16bit Y plane followed by interlaved 16bit UV plane.
///
/// PA16 = Same as P216 followed by an additional 16bit alpha plane.
///
/// FPA1 = Floating-point Planar Audio 32bit
///
/// </summary>
public enum OMTCodec
{
VMX1 = 0x31584D56,
FPA1 = 0x31415046, //Planar audio
UYVY = 0x59565955,
YUY2 = 0x32595559,
BGRA = 0x41524742,
NV12 = 0x3231564E,
YV12 = 0x32315659,
UYVA = 0x41565955,
P216 = 0x36313250,
PA16 = 0x36314150
}
public enum OMTPlatformType
{
Unknown = 0,
Win32 = 1,
MacOS = 2,
Linux = 3,
iOS = 4
}
/// <summary>
/// Specify the color space of the uncompressed Frame. This is used to determine the color space for YUV<>RGB conversions internally.
///
/// If undefined, the codec will assume BT601 for heights < 720, BT709 for everything else.
///
/// </summary>
public enum OMTColorSpace
{
Undefined = 0,
BT601 = 601,
BT709 = 709
}
/// <summary>
/// Specify the preferred uncompressed video format of decoded frames.
///
/// UYVY is always the fastest, if no alpha channel is required.
///
/// UYVYorBGRA will provide BGRA only when alpha channel is present.
///
/// BGRA will always convert back to BGRA
///
/// UYVYorUYVA will provide UYVA only when alpha channel is present.
///
/// UYVYorUYVAorP216orPA16 will provide P216 if sender encoded with high bit depth, or PA16 if sender encoded with high bit depth and alpha. Otherwise same as UYVYorUYVA.
///
/// P216 To receive only P216 frames
///
/// </summary>
public enum OMTPreferredVideoFormat
{
UYVY = 0,
UYVYorBGRA = 1,
BGRA = 2,
UYVYorUYVA = 3,
UYVYorUYVAorP216orPA16 = 4,
P216 = 5
}
/// <summary>
/// Flags to enable certain features on a Receiver:
///
/// Preview: Receive only a 1/8th preview of the video.
///
/// IncludeCompressed: Include a copy of the compressed VMX video frames for further processing or recording.
///
/// CompressedOnly: Include only the compressed VMX video frame without decoding. In this instance DataLength will always be 0.
///
/// </summary>
[Flags]
public enum OMTReceiveFlags
{
None = 0,
Preview = 1,
IncludeCompressed = 2,
CompressedOnly = 4
}
/// <summary>
/// Specify the video encoding quality.
///
/// If set to Default, the Sender is configured to allow suggestions from all Receivers.
///
/// The highest suggestion amongst all receivers is then selected.
///
/// If a Receiver is set to Default, then it will defer the quality to whatever is set amongst other Receivers.
///
/// </summary>
public enum OMTQuality
{
Default = 0,
Low = 1,
Medium = 50,
High = 100
}
public struct OMTStatistics
{
public long BytesSent;
public long BytesReceived;
public long BytesSentSinceLast;
public long BytesReceivedSinceLast;
public long Frames;
public long FramesSinceLast;
public long FramesDropped;
public long CodecTime;
public long CodecTimeSinceLast;
public void ToIntPtr(IntPtr ptr)
{
if (ptr != IntPtr.Zero)
{
Marshal.WriteInt64(ptr, BytesSent);
Marshal.WriteInt64(ptr + 8, BytesReceived);
Marshal.WriteInt64(ptr + 16, BytesSentSinceLast);
Marshal.WriteInt64(ptr + 24, BytesReceivedSinceLast);
Marshal.WriteInt64(ptr + 32, Frames);
Marshal.WriteInt64(ptr + 40, FramesSinceLast);
Marshal.WriteInt64(ptr + 48, FramesDropped);
Marshal.WriteInt64(ptr + 56, CodecTime);
Marshal.WriteInt64(ptr + 64, CodecTimeSinceLast);
}
}
}
public class OMTSenderInfo
{
public string ProductName;
public string Manufacturer;
public string Version;
public OMTSenderInfo() { }
public OMTSenderInfo(string productName, string manufacturer, string version)
{
ProductName = productName;
Manufacturer = manufacturer;
Version = version;
}
public string ToXML()
{
StringWriter sw = new StringWriter();
XmlTextWriter t = new XmlTextWriter(sw);
t.Formatting = Formatting.Indented;
t.WriteStartElement(OMTMetadataTemplates.SENDER_INFO_NAME);
t.WriteAttributeString("ProductName", ProductName);
t.WriteAttributeString("Manufacturer", Manufacturer);
t.WriteAttributeString("Version", Version);
t.WriteEndElement();
t.Close();
return sw.ToString();
}
public static OMTSenderInfo FromXML(string xml)
{
XmlDocument doc = OMTMetadataUtils.TryParse(xml);
if (doc != null)
{
XmlNode e = doc.DocumentElement;
if (e != null)
{
OMTSenderInfo senderInfo = new OMTSenderInfo();
XmlNode a = e.Attributes.GetNamedItem("ProductName");
if (a != null) senderInfo.ProductName = a.InnerText;
a = e.Attributes.GetNamedItem("Manufacturer");
if (a != null) senderInfo.Manufacturer = a.InnerText;
a = e.Attributes.GetNamedItem("Version");
if (a != null) senderInfo.Version = a.InnerText;
return senderInfo;
}
}
return null;
}
}
/// <summary>
/// Stores one frame of Video, Audio or Metadata
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct OMTMediaFrame
{
/// <summary>
/// Specify the type of frame. This determines which values of this struct are valid/used.
/// </summary>
public OMTFrameType Type;
/// <summary>
/// This is a timestamp where 1 second = 10,000,000
///
/// This should not be left 0 unless this is the very first frame.
///
/// This should represent the accurate time the frame or audio sample was generated at the original source and be used on the receiving end to synchronize
/// and record to file as a presentation timestamp (pts).
///
/// A special value of -1 can be specified to tell the Sender to generate timestamps and throttle as required to maintain
/// the specified FrameRate or SampleRate of the frame.
///
/// </summary>
public long Timestamp;
/// <summary>
/// Sending:
///
/// Video: 'UYVY', 'YUY2', 'NV12', 'YV12, 'BGRA', 'UYVA', 'VMX1' are supported (BGRA will be treated as BGRX and UYVA as UYVY where alpha flags are not set)
///
/// Audio: Only 'FPA1' is supported (32bit floating point planar audio)
///
/// Receiving:
///
/// Video: Only 'UYVY', 'UYVA', 'BGRA' and 'BGRX' are supported
///
/// Audio: Only 'FPA1' is supported (32bit floating point planar audio)
///
/// </summary>
public int Codec;
//Video Properties
public int Width;
public int Height;
/// <summary>
/// Stride in bytes of each row of pixels. Typically width*2 for UYVY, width*4 for BGRA and just width for planar formats.
/// </summary>
public int Stride;
public OMTVideoFlags Flags;
/// <summary>
/// Frame Rate Numerator/Denominator in Frames Per Second, for example Numerator 60 and Denominator 1 is 60 frames per second.
/// </summary>
public int FrameRateN;
public int FrameRateD;
/// <summary>
/// Display aspect ratio expressed as a ratio of width/height. For example 1.777777777777778 for 16/9
/// </summary>
public float AspectRatio;
/// <summary>
/// Color space of the frame. If undefined a height < 720 is BT601 and everything else BT709
/// </summary>
public OMTColorSpace ColorSpace;
//Audio Properties
// Sample rate, i.e 48000, 44100 etc
public int SampleRate;
// Audio Channels. A maximum of 32 channels are supported.
public int Channels;
// Number of 32bit floating point samples per channel/plane. Each plane should contain SamplesPerChannel*4 bytes.
public int SamplesPerChannel;
//Data Properties
/// <summary>
/// Video: Uncompressed pixel data (or compressed VMX1 data when sending and Codec set to VMX1)
///
/// Audio: Planar 32bit floating point audio
///
/// Metadata: UTF-8 encoded XML string with terminating null character
///
/// </summary>
public IntPtr Data;
/// <summary>
/// Video: Number of bytes total including stride
///
/// Audio: Number of bytes (SamplesPerChannel * Channels * 4)
///
/// Metadata: Number of bytes in UTF-8 encoded string + 1 for terminating null character.
///
/// </summary>
public int DataLength;
/// <summary>
/// Receive only. Use standard Data/DataLength if sending VMX1 frames with a Sender
///
/// If IncludeCompressed or CompressedOnly OMTReceiveFlags is set, this will include the original compressed video frame in VMX1 format.
///
/// This could then be muxed into an AVI or MOV file using FFmpeg or similar APIs
///
/// </summary>
public IntPtr CompressedData;
public int CompressedLength;
/// <summary>
/// Per frame metadata as UTF-8 encoded string + 1 for null character. Up to 65536 bytes supported.
/// </summary>
public IntPtr FrameMetadata;
/// <summary>
/// Length in bytes of per frame metadata including null character
/// </summary>
public int FrameMetadataLength;
public static IntPtr ToIntPtr(OMTMediaFrame frame)
{
IntPtr dst = Marshal.AllocHGlobal(Marshal.SizeOf(frame));
Marshal.StructureToPtr(frame, dst, false);
return dst;
}
public static void FreeIntPtr(IntPtr ptr)
{
if (ptr != IntPtr.Zero)
{
Marshal.FreeHGlobal(ptr);
}
}
public static OMTMediaFrame FromIntPtr(IntPtr ptr)
{
return (OMTMediaFrame)Marshal.PtrToStructure(ptr, typeof(OMTMediaFrame));
}
public float FrameRate { get {
return OMTUtils.ToFrameRate(FrameRateN, FrameRateD);
}
set
{
OMTUtils.FromFrameRate(value,ref FrameRateN,ref FrameRateD);
}
}
}
public struct OMTSize
{
public int Width;
public int Height;
public OMTSize(int width, int height)
{
Width = width;
Height = height;
}
}
/// <summary>
/// Tally where 0 = 0 off, 1 = on.
/// </summary>
public struct OMTTally
{
public int Preview;
public int Program;
public OMTTally(int preview, int program)
{
this.Preview = preview;
this.Program = program;
}
public override string ToString()
{
return "Preview: " + Preview + " Program: " + Program;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,204 @@
using System;
using System.IO;
using System.Xml;
namespace libomtnet
{
internal class OMTRedirect : OMTBase
{
private string redirectAddress = null;
private string redirectAddressUpstream = null;
private OMTReceive redirectConnection = null;
private object redirectLock = new object();
private OMTSend sender = null;
private string originalAddress = null;
private OMTReceive receiver = null;
public OMTRedirect(OMTSend sender)
{
this.sender = sender;
this.originalAddress = sender.Address;
}
public OMTRedirect(OMTReceive receiver)
{
this.receiver = receiver;
this.originalAddress = receiver.Address;
}
private void ClearRedirectConnection()
{
if (redirectConnection != null)
{
redirectAddressUpstream = null;
redirectConnection.RedirectChanged -= OnRedirectChanged;
redirectConnection.Dispose();
redirectConnection = null;
}
}
private OMTMetadata CreateRedirectMetadata()
{
string address = this.redirectAddress;
if (!String.IsNullOrEmpty(this.redirectAddressUpstream))
{
address = this.redirectAddressUpstream;
}
string xml = OMTRedirect.ToXML(address);
return new OMTMetadata(0, xml);
}
private void SendRedirect()
{
if (sender != null)
{
OMTMetadata metadata = CreateRedirectMetadata();
sender.SendMetadata(metadata, null);
}
}
public void OnNewConnection(OMTChannel ch)
{
OMTMetadata m = CreateRedirectMetadata();
int result = ch.Send(m);
}
public void OnReceiveChanged()
{
lock (redirectLock)
{
if (Exiting) return;
string newAddress = receiver.RedirectAddress;
this.redirectAddress = newAddress;
CreateRedirectConnection(newAddress, originalAddress);
}
}
public void CheckConnection()
{
if (redirectConnection != null)
{
redirectConnection.CheckConnection();
}
}
private void CreateRedirectConnection(string newAddress, string sideChannelAddress)
{
if (String.IsNullOrEmpty(newAddress))
{
OMTLogging.Write("Redirect stopped for " + originalAddress, "OMTRedirect");
ClearRedirectConnection();
}
else
{
OMTLogging.Write("Redirecting " + originalAddress + " to " + newAddress + " and monitoring for updates from " + sideChannelAddress, "OMTRedirect");
if (redirectConnection != null)
{
if (redirectConnection.Address != sideChannelAddress)
{
ClearRedirectConnection();
}
}
if (redirectConnection == null)
{
redirectConnection = new OMTReceive(sideChannelAddress, OMTFrameType.Metadata, OMTPreferredVideoFormat.UYVY, OMTReceiveFlags.None);
redirectConnection.redirectMetadataOnly = true;
redirectConnection.RedirectChanged += OnRedirectChanged;
}
}
}
public void SetRedirect(string newAddress)
{
lock (redirectLock)
{
if (Exiting) return;
if (this.originalAddress == newAddress)
{
newAddress = null; //No redirect in case of loopback
}
if (this.redirectAddress != newAddress)
{
this.redirectAddressUpstream = null;
}
this.redirectAddress = newAddress;
SendRedirect();
CreateRedirectConnection(newAddress, newAddress);
}
}
private void OnRedirectChanged(object sender, OMTRedirectChangedEventArgs e)
{
try
{
//This is called by the Receive_Completed on the channel own by this object's receiver,
//so care needs to be taken to ensure this does not go back into that receiver where that lock may be used.
if (Exiting) return;
if (redirectConnection != null)
{
string newAddress = e.NewAddress;
if (newAddress != originalAddress)
{
if (newAddress != redirectAddress)
{
if (this.sender != null)
{
if (newAddress != redirectAddressUpstream)
{
this.redirectAddressUpstream = newAddress;
OMTLogging.Write("Redirect changed upstream for " + originalAddress + " to " + newAddress, "OMTRedirect");
SendRedirect();
}
} else if (this.receiver != null)
{
this.redirectAddress = newAddress;
receiver.OnRedirectConnection(newAddress);
}
}
}
}
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTRedirect");
}
}
public static string FromXML(string xml)
{
XmlDocument doc = OMTMetadataUtils.TryParse(xml);
if (doc != null)
{
if (doc.DocumentElement != null)
{
XmlNode a = doc.DocumentElement.Attributes.GetNamedItem("NewAddress");
if (a != null)
{
return a.InnerText;
}
}
}
return null;
}
public static string ToXML(string address)
{
using (StringWriter sw = new StringWriter())
{
using (XmlTextWriter t = new XmlTextWriter(sw))
{
t.Formatting = Formatting.Indented;
t.WriteStartElement(OMTMetadataTemplates.REDIRECT_NAME);
t.WriteAttributeString("NewAddress", address);
t.WriteEndElement();
return sw.ToString();
}
}
}
protected override void DisposeInternal()
{
lock (redirectLock) { }
sender = null;
ClearRedirectConnection();
base.DisposeInternal();
}
}
}

View File

@@ -0,0 +1,829 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using libomtnet.codecs;
using System.Runtime.InteropServices;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Xml;
namespace libomtnet
{
public class OMTSend : OMTSendReceiveBase
{
private readonly OMTAddress address;
private Socket listener;
private OMTChannel[] channels = { };
private object channelsLock = new object();
private OMTDiscovery discovery;
private OMTDiscoveryServer discoveryServer;
private OMTFrame tempVideo;
private OMTFrame tempAudio;
private OMTBuffer tempAudioBuffer;
private OMTVMX1Codec codec = null;
private OMTQuality quality = OMTQuality.Default;
private SocketAsyncEventArgs listenEvent;
private OMTQuality suggestedQuality = OMTQuality.Default;
private string senderInfoXml = null;
private List<string> connectionMetadata = new List<string>();
private OMTClock videoClock;
private OMTClock audioClock;
private bool metadataServer = false;
internal OMTSend(IPEndPoint endpoint, OMTDiscoveryServer discoveryServer)
{
this.metadataServer = true;
this.discoveryServer = discoveryServer;
metadataHandle = new AutoResetEvent(false);
listenEvent = new SocketAsyncEventArgs();
listenEvent.Completed += OnAccept;
this.listener = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
this.listener.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
this.listener.Bind(endpoint);
this.listener.Listen(5);
BeginAccept();
}
/// <summary>
/// Create a new instance of the OMT Sender
/// </summary>
/// <param name="name">Specify the name of the source not including hostname</param>
/// <param name="quality"> Specify the quality to use for video encoding. If Default, this can be automatically adjusted based on Receiver requirements.</param>
public OMTSend(string name, OMTQuality quality)
{
videoClock = new OMTClock(false);
audioClock = new OMTClock(true);
metadataHandle = new AutoResetEvent(false);
tallyHandle = new AutoResetEvent(false);
listenEvent = new SocketAsyncEventArgs();
listenEvent.Completed += OnAccept;
tempVideo = new OMTFrame(OMTFrameType.Video, new OMTBuffer(OMTConstants.VIDEO_MIN_SIZE, true));
tempAudio = new OMTFrame(OMTFrameType.Audio, new OMTBuffer(OMTConstants.AUDIO_MIN_SIZE, true));
tempAudioBuffer = new OMTBuffer(OMTConstants.AUDIO_MIN_SIZE, true);
this.discovery = OMTDiscovery.GetInstance();
this.quality = quality;
this.suggestedQuality = quality;
this.listener = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
OMTSettings settings = OMTSettings.GetInstance();
int startPort = settings.GetInteger("NetworkPortStart", OMTConstants.NETWORK_PORT_START);
int endPort = settings.GetInteger("NetworkPortEnd", OMTConstants.NETWORK_PORT_END);
for (int i = startPort; i <= endPort; i++)
{
try
{
this.listener.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
this.listener.Bind(new IPEndPoint(IPAddress.IPv6Any, i));
this.listener.Listen(5);
break;
}
catch (SocketException se)
{
if (se.SocketErrorCode != SocketError.AddressAlreadyInUse | i == OMTConstants.NETWORK_PORT_END)
{
throw se;
}
}
}
BeginAccept();
IPEndPoint ip = (IPEndPoint)this.listener.LocalEndPoint;
this.address = new OMTAddress(name, ip.Port);
this.address.AddAddress(IPAddress.Loopback);
this.discovery.RegisterAddress(address); }
public override OMTStatistics GetVideoStatistics()
{
OMTChannel[] ch = channels;
if (ch != null)
{
foreach (OMTChannel c in ch)
{
if (c.IsVideo() && c.Connected)
{
OMTStatistics s = c.GetStatistics();
UpdateCodecTimerStatistics(ref s);
return s;
}
}
}
return base.GetVideoStatistics();
}
public override OMTStatistics GetAudioStatistics()
{
OMTChannel[] ch = channels;
if (ch != null)
{
foreach (OMTChannel c in ch)
{
if (c.IsAudio() && c.Connected)
{
return c.GetStatistics();
}
}
}
return base.GetAudioStatistics();
}
public int Port { get { return this.address.Port; } }
/// <summary>
/// Specify information to describe the Sender to any Receivers
/// </summary>
/// <param name="senderInfo"></param>
public void SetSenderInformation(OMTSenderInfo senderInfo)
{
if (senderInfo == null)
{
this.senderInfoXml = null;
} else
{
this.senderInfoXml = senderInfo.ToXML();
SendMetadata(new OMTMetadata(0, this.senderInfoXml), null);
}
}
private void SendConnectionMetadata()
{
lock (connectionMetadata)
{
foreach (string metadata in connectionMetadata)
{
if (!String.IsNullOrEmpty(metadata))
{
SendMetadata(new OMTMetadata(0, metadata), null);
}
}
}
}
private void SendConnectionMetadata(OMTChannel ch)
{
lock (connectionMetadata)
{
foreach (string metadata in connectionMetadata)
{
if (!String.IsNullOrEmpty(metadata))
{
ch.Send(new OMTMetadata(0, metadata));
}
}
}
}
public void AddConnectionMetadata(string xml)
{
lock (connectionMetadata)
{
connectionMetadata.Add(xml);
}
}
public void ClearConnectionMetadata()
{
lock (connectionMetadata)
{
connectionMetadata.Clear();
}
}
/// <summary>
/// Use this to inform receivers to connect to a different address.
///
/// This is used to create a "virtual source" that can be dynamically switched as needed.
///
/// This is useful for scenarios where receiver needs to be changed remotely.
/// </summary>
/// <param name="newAddress">The new address. Set to null or empty to disable redirect.</param>
public void SetRedirect(string newAddress)
{
if (redirect == null) redirect = new OMTRedirect(this);
redirect.SetRedirect(newAddress);
}
protected override void DisposeInternal()
{
if (tallyHandle != null)
{
tallyHandle.Set();
}
if (metadataHandle != null)
{
metadataHandle.Set();
}
if (videoClock != null)
{
videoClock.Dispose();
}
if (audioClock != null)
{
audioClock.Dispose();
}
lock (videoLock) { }
lock (audioLock) { }
lock (metaLock) { }
if (redirect != null)
{
redirect.Dispose();
redirect = null;
}
if (discovery != null)
{
discovery.DeregisterAddress(address);
discovery = null;
}
discoveryServer = null;
if (listener != null)
{
listener.Dispose();
listener = null;
}
if (listenEvent != null)
{
listenEvent.Completed -= OnAccept;
listenEvent.Dispose();
listenEvent = null;
}
lock (channelsLock)
{
if (channels != null)
{
foreach (OMTChannel channel in channels)
{
if (channel != null)
{
channel.Changed -= Channel_Changed;
channel.Dispose();
}
}
channels = null;
}
}
if (codec != null)
{
codec.Dispose();
codec = null;
}
discovery = null;
OMTMetadata.FreeIntPtr(lastMetadata);
lastMetadata = IntPtr.Zero;
if (metadataHandle != null)
{
metadataHandle.Close();
metadataHandle = null;
}
if (tallyHandle != null)
{
tallyHandle.Close();
tallyHandle = null;
}
if (tempVideo != null)
{
tempVideo.Dispose();
tempVideo = null;
}
if (tempAudio != null)
{
tempAudio.Dispose();
tempAudio = null;
}
if (tempAudioBuffer != null)
{
tempAudioBuffer.Dispose();
tempAudioBuffer = null;
}
base.DisposeInternal();
}
/// <summary>
/// Discovery address in the format HOSTNAME (NAME)
/// </summary>
public string Address { get { return address.ToString(); } }
/// <summary>
/// Direct connection address in the format omt://hostname:port
/// </summary>
public string URL { get { return address.ToURL(); } }
/// <summary>
/// Total number of connections to this sender. Receivers establish one connection for video/metadata and a second for audio.
/// </summary>
public int Connections { get {
OMTChannel[] ch = channels;
if (ch != null)
{
return ch.Length;
}
return 0;
} }
private void OnAccept(object sender, SocketAsyncEventArgs e)
{
try
{
if (e.SocketError == SocketError.Success)
{
Socket socket = null;
OMTChannel channel = null;
try
{
socket = e.AcceptSocket;
channel = new OMTChannel(socket, OMTFrameType.Metadata, null, metadataHandle, metadataServer);
channel.StartReceive();
if (senderInfoXml != null)
{
channel.Send(new OMTMetadata(0, senderInfoXml));
}
SendConnectionMetadata(channel);
channel.Send(OMTMetadata.FromTally(lastTally));
if (redirect != null)
{
redirect.OnNewConnection(channel);
}
OMTLogging.Write("AddConnection: " + socket.RemoteEndPoint.ToString(), "OMTSend.BeginAccept");
AddChannel(channel);
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTSend.BeginAccept");
if (channel != null)
{
channel.Changed -= Channel_Changed;
channel.Dispose();
}
if (socket != null)
{
socket.Dispose();
}
}
}
if (!Exiting)
{
BeginAccept();
}
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTSend.OnAccept");
}
}
private void BeginAccept()
{
Socket listener = this.listener;
if (listener != null)
{
listenEvent.AcceptSocket = null;
if (this.listener.AcceptAsync(listenEvent) == false) {
OnAccept(this.listener, listenEvent);
}
}
}
internal void AddChannel(OMTChannel channel)
{
lock (channelsLock)
{
List<OMTChannel> list = new List<OMTChannel>();
list.AddRange(channels);
list.Add(channel);
channels = list.ToArray();
}
channel.Changed += Channel_Changed;
UpdateTally();
if (discoveryServer != null)
{
discoveryServer.Connected(channel.RemoteEndPoint);
}
}
internal bool RemoveChannel(OMTChannel channel)
{
lock (channelsLock)
{
if (channel != null)
{
List<OMTChannel> list = new List<OMTChannel>();
list.AddRange(channels);
if (list.Contains(channel))
{
list.Remove(channel);
channels = list.ToArray();
channel.Changed -= Channel_Changed;
channel.Dispose();
OMTLogging.Write("RemoveConnection", "OMTSend.RemoveChannel");
return true;
}
}
}
return false;
}
/// <summary>
/// Sets the video encoding quality from the next frame. If set to Default will defer to the suggested quality amongst receivers. See OMTQuality for more details.
/// </summary>
public OMTQuality Quality { get { return quality; } set {
quality = value;
if (quality != OMTQuality.Default)
{
suggestedQuality = quality;
}
} }
internal override void OnTallyChanged(OMTTally tally)
{
SendMetadata(OMTMetadata.FromTally(tally),null);
}
internal override void OnDisconnected(OMTChannel ch)
{
if (ch != null)
{
if (RemoveChannel(ch))
{
if (discoveryServer != null)
{
discoveryServer.Disconnected(ch.RemoteEndPoint);
}
UpdateTally();
}
}
}
internal int Send(OMTFrame frame)
{
int len = 0;
OMTQuality suggested = OMTQuality.Default;
OMTChannel[] channels = this.channels;
if (channels != null)
{
for (int i = 0; i < channels.Length; i++)
{
if (channels[i].Connected)
{
len += channels[i].Send(frame);
if (channels[i].IsVideo())
{
if (channels[i].SuggestedQuality > suggested)
{
suggested = channels[i].SuggestedQuality;
}
}
}
}
if (quality == OMTQuality.Default)
{
suggestedQuality = suggested;
}
}
return len;
}
private void CreateCodec(int width, int height, int framesPerSecond, VMXColorSpace colorSpace)
{
VMXProfile prof = VMXProfile.Default;
if (suggestedQuality != OMTQuality.Default)
{
if (suggestedQuality >= OMTQuality.Low) prof = VMXProfile.OMT_LQ;
if (suggestedQuality >= OMTQuality.Medium) prof = VMXProfile.OMT_SQ;
if (suggestedQuality >= OMTQuality.High) prof = VMXProfile.OMT_HQ;
}
if (codec == null)
{
codec = new OMTVMX1Codec(width, height, framesPerSecond, prof, colorSpace);
}
else if (codec.Width != width || codec.Height != height || codec.Profile != prof || codec.ColorSpace != colorSpace || codec.FramesPerSecond != framesPerSecond)
{
int lastQuality = codec.GetQuality();
codec.Dispose();
codec = new OMTVMX1Codec(width, height, framesPerSecond, prof, colorSpace);
codec.SetQuality(lastQuality); //Preserve the last quality in cases of profile change, so there isn't a temporarily drop in quality.
}
}
/// <summary>
/// Receive any available metadata in the buffer, or wait for metadata if empty
///
/// Returns true if metadata was found, false of timed out
/// </summary>
/// <param name="millisecondsTimeout">The maximum time to wait for a new frame if empty</param>
/// <param name="outFrame">The frame struct to fill with the received data</param>
public bool Receive(int millisecondsTimeout, ref OMTMediaFrame outFrame)
{
OMTMetadata metadata = null;
if (Receive(millisecondsTimeout, ref metadata))
{
return ReceiveMetadata(metadata, ref outFrame);
}
return false;
}
internal bool Receive(int millisecondsTimeout, ref OMTMetadata metadata)
{
lock (metaLock)
{
if (Exiting) return false;
if (ReceiveInternal(ref metadata)) return true;
for (int i = 0; i < 2; i++)
{
if (metadataHandle.WaitOne(millisecondsTimeout) == false) return false;
if (Exiting) return false;
if (ReceiveInternal(ref metadata)) return true;
}
}
return false;
}
private bool ReceiveInternal(ref OMTMetadata frame)
{
OMTChannel[] channels = this.channels;
for (int i = 0; i < channels.Length; i++)
{
OMTChannel ch = channels[i];
if (ch != null)
{
if (ch.ReadyMetadataCount > 0)
{
frame = ch.ReceiveMetadata();
return true;
}
}
}
return false;
}
internal override OMTTally GetTallyInternal()
{
OMTTally tally = new OMTTally();
OMTChannel[] channels = this.channels;
if (channels != null)
{
for (int i = 0; i < channels.Length; i++)
{
OMTTally t = channels[i].GetTally();
if (t.Program == 1) tally.Program = 1;
if (t.Preview == 1) tally.Preview = 1;
}
}
return tally;
}
/// <summary>
/// Send a frame to any receivers currently connected.
///
/// Video: 'UYVY', 'YUY2', 'NV12', 'YV12, 'BGRA', 'UYVA', 'VMX1' are supported (BGRA will be treated as BGRX and UYVA as UYVY where alpha flags are not set)
///
/// Audio: Supports planar 32bit floating point audio
///
/// Metadata: Supports UTF8 encoded XML
/// </summary>
/// <param name="frame">The frame to send</param>
public int Send(OMTMediaFrame frame)
{
if (Exiting) return 0;
if (frame.Type == OMTFrameType.Video)
{
return SendVideo(frame);
}
else if (frame.Type == OMTFrameType.Audio)
{
return SendAudio(frame);
}
else if (frame.Type == OMTFrameType.Metadata)
{
return SendMetadata(frame);
}
return 0;
}
private int SendMetadata(OMTMediaFrame metadata)
{
OMTMetadata m = OMTMetadata.FromMediaFrame(metadata);
if (m != null)
{
return SendMetadata(m, null);
}
return 0;
}
internal int SendMetadata(OMTMetadata metadata, IPEndPoint endpoint)
{
lock (metaLock)
{
if (Exiting) return 0;
int len = 0;
OMTChannel[] channels = this.channels;
if (channels != null)
{
for (int i = 0; i < channels.Length; i++)
{
OMTChannel ch = channels[i];
if (ch.IsMetadata())
{
if (endpoint == null || ch.RemoteEndPoint == endpoint)
{
len += channels[i].Send(metadata);
}
}
}
}
return len;
}
}
private int SendVideo(OMTMediaFrame frame)
{
lock (videoLock)
{
if (Exiting) return 0;
if (frame.Data != IntPtr.Zero && frame.DataLength > 0)
{
tempVideo.Data.Resize(frame.DataLength + frame.FrameMetadataLength);
if ((frame.Codec == (int)OMTCodec.UYVY) || (frame.Codec == (int)OMTCodec.BGRA) ||
(frame.Codec == (int)OMTCodec.YUY2) || (frame.Codec == (int)OMTCodec.NV12) ||
(frame.Codec == (int)OMTCodec.YV12) || (frame.Codec == (int)OMTCodec.UYVA) ||
(frame.Codec == (int)OMTCodec.P216) || (frame.Codec == (int)OMTCodec.PA16)
)
{
if (frame.Width >= 16 && frame.Height >= 16 && frame.Stride >= frame.Width)
{
bool interlaced = frame.Flags.HasFlag(OMTVideoFlags.Interlaced);
bool alpha = frame.Flags.HasFlag(OMTVideoFlags.Alpha);
CreateCodec(frame.Width, frame.Height, (int)frame.FrameRate, (VMXColorSpace)frame.ColorSpace);
byte[] buffer = tempVideo.Data.Buffer;
int len;
BeginCodecTimer();
VMXImageType itype = VMXImageType.None;
if (frame.Codec == (int)OMTCodec.UYVY)
{
itype = VMXImageType.UYVY;
}
else if (frame.Codec == (int)OMTCodec.YUY2)
{
itype = VMXImageType.YUY2;
}
else if (frame.Codec == (int)OMTCodec.NV12)
{
itype = VMXImageType.NV12;
} else if (frame.Codec == (int)OMTCodec.YV12)
{
itype = VMXImageType.YV12;
}
else if (frame.Codec == (int)OMTCodec.BGRA)
{
if (alpha)
{
itype = VMXImageType.BGRA;
} else
{
itype = VMXImageType.BGRX;
}
}
else if (frame.Codec == (int)OMTCodec.UYVA)
{
if (alpha)
{
itype = VMXImageType.UYVA;
}
else
{
itype = VMXImageType.UYVY;
}
}
else if (frame.Codec == (int)OMTCodec.PA16)
{
frame.Flags |= OMTVideoFlags.HighBitDepth;
if (alpha)
{
itype = VMXImageType.PA16;
}
else
{
itype = VMXImageType.P216;
}
} else if (frame.Codec == (int)OMTCodec.P216)
{
frame.Flags |= OMTVideoFlags.HighBitDepth;
itype = VMXImageType.P216;
}
len = codec.Encode(itype, frame.Data, frame.Stride, buffer, interlaced);
EndCodecTimer();
if (len > 0)
{
if (frame.FrameMetadataLength > 0)
{
tempVideo.Data.SetBuffer(len, len);
tempVideo.Data.Append(frame.FrameMetadata,0,frame.FrameMetadataLength);
}
tempVideo.SetDataLength(len + frame.FrameMetadataLength);
tempVideo.SetMetadataLength(frame.FrameMetadataLength);
tempVideo.SetPreviewDataLength(codec.GetEncodedPreviewLength() + frame.FrameMetadataLength);
tempVideo.ConfigureVideo((int)OMTCodec.VMX1, frame.Width, frame.Height, frame.FrameRateN, frame.FrameRateD, frame.AspectRatio, frame.Flags, frame.ColorSpace);
videoClock.Process(ref frame);
tempVideo.Timestamp = frame.Timestamp;
return Send(tempVideo);
}
else
{
OMTLogging.Write("Encoding failed at timestamp: " + frame.Timestamp, "OMTSend.SendVideo");
}
}
else
{
OMTLogging.Write("Frame dimensions invalid: " + frame.Width + "x" + frame.Height + " Stride: " + frame.Stride, "OMTSend.SendVideo");
}
} else if (frame.Codec == (int)OMTCodec.VMX1)
{
if (frame.DataLength > 0)
{
tempVideo.SetDataLength(frame.DataLength + frame.FrameMetadataLength);
tempVideo.SetMetadataLength(frame.FrameMetadataLength);
tempVideo.SetPreviewDataLength(frame.DataLength + frame.FrameMetadataLength);
Marshal.Copy(frame.Data, tempVideo.Data.Buffer, 0, frame.DataLength);
if (frame.FrameMetadataLength > 0)
{
Marshal.Copy(frame.FrameMetadata, tempVideo.Data.Buffer,frame.DataLength, frame.FrameMetadataLength);
}
tempVideo.ConfigureVideo((int)OMTCodec.VMX1, frame.Width, frame.Height, frame.FrameRateN, frame.FrameRateD, frame.AspectRatio, frame.Flags, frame.ColorSpace);
videoClock.Process(ref frame);
tempVideo.Timestamp = frame.Timestamp;
return Send(tempVideo);
} else
{
OMTLogging.Write("Frame DataLength invalid", "OMTSend.SendVideo");
}
}
else
{
OMTLogging.Write("Codec not supported: " + frame.Codec, "OMTSend.SendVideo");
}
}
}
return 0;
}
private int SendAudio(OMTMediaFrame frame)
{
lock (audioLock)
{
if (Exiting) return 0;
if (frame.Data != IntPtr.Zero && frame.DataLength > 0 && frame.Channels > 0 && frame.SampleRate > 0 && frame.SamplesPerChannel > 0 && frame.Channels <= 32)
{
if (frame.DataLength > OMTConstants.AUDIO_MAX_SIZE)
{
OMTLogging.Write("Audio DataLength exceeded maximum: " + frame.DataLength, "OMTSend");
return 0;
}
tempAudioBuffer.Resize(frame.DataLength);
tempAudio.Data.Resize(frame.DataLength + frame.FrameMetadataLength);
Marshal.Copy(frame.Data, tempAudioBuffer.Buffer, 0, frame.DataLength);
tempAudioBuffer.SetBuffer(0, frame.DataLength);
tempAudio.Data.SetBuffer(0, 0);
OMTActiveAudioChannels ch = OMTFPA1Codec.Encode(tempAudioBuffer, frame.Channels, frame.SamplesPerChannel, tempAudio.Data);
if (frame.FrameMetadataLength > 0 && frame.FrameMetadata != IntPtr.Zero)
{
tempAudio.Data.Append(frame.FrameMetadata,0, frame.FrameMetadataLength);
}
tempAudio.SetDataLength(tempAudio.Data.Length);
tempAudio.SetMetadataLength(frame.FrameMetadataLength);
tempAudio.ConfigureAudio(frame.SampleRate, frame.Channels, frame.SamplesPerChannel, ch);
audioClock.Process(ref frame);
tempAudio.Timestamp = frame.Timestamp;
return Send(tempAudio);
}
}
return 0;
}
}
}

View File

@@ -0,0 +1,177 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
namespace libomtnet
{
public class OMTSendReceiveBase : OMTBase
{
protected object videoLock = new object();
protected object audioLock = new object();
protected object metaLock = new object();
protected AutoResetEvent metadataHandle;
protected AutoResetEvent tallyHandle;
protected IntPtr lastMetadata;
protected OMTTally lastTally = new OMTTally();
private Stopwatch timer = Stopwatch.StartNew();
private long codecTime = 0;
private long codecTimeSinceLast = 0;
private long codecStartTime = 0;
internal OMTRedirect redirect = null;
/// <summary>
/// Receives the current tally state across all connections to a Sender.
/// If this function times out, the last known tally state will be received.
/// </summary>
/// <param name="millisecondsTimeout">milliseconds to wait for tally change. set to 0 to receive current tally</param>
/// <param name="tally"></param>
/// <returns></returns>
public bool GetTally(int millisecondsTimeout, ref OMTTally tally)
{
if (Exiting) return false;
if (millisecondsTimeout > 0)
{
if (tallyHandle != null)
{
if (tallyHandle.WaitOne(millisecondsTimeout))
{
tally = lastTally;
return true;
}
}
}
tally = lastTally;
return false;
}
internal virtual OMTTally GetTallyInternal()
{
return new OMTTally();
}
internal void Channel_Changed(object sender, OMTEventArgs e)
{
if (Exiting) return; //Avoid deadlock where Channel may call back into sender while dispose is in progress.
if (e.Type == OMTEventType.TallyChanged)
{
UpdateTally();
} else if (e.Type == OMTEventType.Disconnected)
{
if (sender != null)
{
OMTChannel ch = (OMTChannel)sender;
OnDisconnected(ch);
}
} else if (e.Type == OMTEventType.RedirectChanged)
{
OMTChannel ch = (OMTChannel)sender;
OnRedirectChanged(ch);
}
}
internal virtual void OnRedirectChanged(OMTChannel ch)
{
}
internal virtual void OnDisconnected(OMTChannel ch)
{
}
internal virtual void OnTallyChanged( OMTTally tally)
{
}
internal void UpdateTally()
{
OMTTally tally = GetTallyInternal();
if (tally.Preview != lastTally.Preview || tally.Program != lastTally.Program)
{
lastTally = tally;
OnTallyChanged(lastTally);
if (tallyHandle != null)
{
tallyHandle.Set();
}
}
}
public virtual OMTStatistics GetVideoStatistics()
{
return new OMTStatistics();
}
public virtual OMTStatistics GetAudioStatistics()
{
return new OMTStatistics();
}
internal void BeginCodecTimer()
{
codecStartTime = timer.ElapsedMilliseconds;
}
internal void EndCodecTimer()
{
long v = (timer.ElapsedMilliseconds - codecStartTime);
codecTime += v;
codecTimeSinceLast += v;
}
internal void UpdateCodecTimerStatistics(ref OMTStatistics v)
{
v.CodecTime = codecTime;
v.CodecTimeSinceLast = codecTimeSinceLast;
codecTimeSinceLast = 0;
}
internal bool ReceiveMetadata(OMTMetadata frame, ref OMTMediaFrame outFrame)
{
lock (metaLock)
{
if (Exiting) return false;
OMTMetadata.FreeIntPtr(lastMetadata);
lastMetadata = IntPtr.Zero;
outFrame.Type = OMTFrameType.Metadata;
outFrame.Timestamp = frame.Timestamp;
outFrame.Data = frame.ToIntPtr(ref outFrame.DataLength);
lastMetadata = outFrame.Data;
return true;
}
}
protected override void DisposeInternal()
{
OMTMetadata.FreeIntPtr(lastMetadata);
lastMetadata = IntPtr.Zero;
base.DisposeInternal();
}
}
}

View File

@@ -0,0 +1,154 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
namespace libomtnet
{
/// <summary>
/// These functions override the default settings which are stored in ~/.OMT/settings.xml on Mac and Linux and C:\ProgramData\OMT\settings.xml on WIndows
///
/// To override the default folder used for for settings, set the OMT_STORAGE_PATH environment variable prior to calling any OMT functions.
///
/// The following settings are currently supported:
///
/// DiscoveryServer [string] specify a URL in the format omt://hostname:port to connect to for discovery. If left blank, default DNS-SD discovery behavior is enabled.
///
/// NetworkPortStart[integer] specify the first port to create Send instances on.Defaults to 6400
///
/// NetworkPortEnd[integer] specify the last port to create Send instances on.Defaults to 6600
///
/// </summary>
public class OMTSettings
{
private static object globalLock = new object();
private object instanceLock = new object();
private string filename;
private XmlDocument document;
private XmlNode rootNode;
private static OMTSettings instance;
public static OMTSettings GetInstance()
{
lock (globalLock)
{
if (instance == null)
{
string sz = OMTPlatform.GetInstance().GetStoragePath() + Path.DirectorySeparatorChar + "settings.xml";
instance = new OMTSettings(sz);
}
return instance;
}
}
public OMTSettings(string filename)
{
this.filename = filename;
lock (globalLock)
{
document = new XmlDocument();
try
{
if (File.Exists(filename))
{
document.Load(filename);
rootNode = document.DocumentElement;
}
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTSettings.New");
}
if (rootNode == null)
{
rootNode = document.CreateElement("Settings");
document.AppendChild(rootNode);
}
}
}
public void Save()
{
lock (globalLock)
{
using (XmlTextWriter writer = new XmlTextWriter(filename, null))
{
writer.Formatting = Formatting.Indented;
document.Save(writer);
}
}
}
public string GetString(string key, string defaultValue)
{
lock (instanceLock)
{
if (rootNode != null)
{
XmlNode node = rootNode.SelectSingleNode(key);
if (node != null)
{
return node.InnerText;
}
}
return defaultValue;
}
}
public void SetString(string key, string value)
{
lock (instanceLock)
{
if (rootNode != null)
{
XmlNode node = rootNode.SelectSingleNode(key);
if (node == null)
{
node = document.CreateElement(key);
rootNode.AppendChild(node);
}
node.InnerText = value;
}
}
}
public int GetInteger(string key, int defaultValue)
{
string value = GetString(key, null);
if (!string.IsNullOrEmpty(value))
{
int v = 0;
if (int.TryParse(value, out v))
{
return v;
}
}
return defaultValue;
}
public void SetInteger(string key, int value)
{
SetString(key, value.ToString());
}
}
}

View File

@@ -0,0 +1,148 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Net.Sockets;
using System.Text;
namespace libomtnet
{
internal class OMTSocketAsyncPool : OMTBase
{
private Queue<SocketAsyncEventArgs> pool;
private int bufferSize;
private object lockSync = new object();
protected virtual void OnCompleted(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError != SocketError.Success)
{
OMTLogging.Write("Socket Pool Error: " + e.SocketError.ToString() + "," + e.BytesTransferred, "OMTSocketAsyncPool");
}
ReturnEventArgs(e);
}
public object SyncObject { get { return lockSync; } }
public OMTSocketAsyncPool(int count, int bufferSize)
{
this.bufferSize = bufferSize;
pool = new Queue<SocketAsyncEventArgs>();
for (int i = 0; i < count; i++)
{
SocketAsyncEventArgs e = new SocketAsyncEventArgs();
if (bufferSize > 0)
{
byte[] buf = new byte[bufferSize];
e.SetBuffer(buf,0,buf.Length);
}
e.Completed += OnCompleted;
pool.Enqueue(e);
}
}
public void Resize(SocketAsyncEventArgs e, int length)
{
if (e != null)
{
if (e.Buffer.Length < length)
{
byte[] buf = new byte[length];
e.SetBuffer(buf, 0, buf.Length);
Debug.WriteLine("SocketPool.Resize: " + length);
}
}
}
public void SendAsync(Socket socket, SocketAsyncEventArgs e)
{
lock (lockSync)
{
if (socket != null)
{
if (socket.SendAsync(e) == false)
{
OnCompleted(this, e);
}
}
}
}
internal SocketAsyncEventArgs GetEventArgs()
{
lock (lockSync)
{
if (pool == null) return null;
if (pool.Count > 0) {
SocketAsyncEventArgs e = pool.Dequeue();
e.SetBuffer(0, e.Buffer.Length);
return e;
}
}
return null;
}
public int Count { get { lock (pool) { return pool.Count; } } }
internal void ReturnEventArgs(SocketAsyncEventArgs e)
{
lock (lockSync)
{
if (pool == null)
{
e.Dispose();
} else
{
pool.Enqueue(e);
}
}
}
protected override void DisposeInternal()
{
lock (lockSync)
{
if (pool != null)
{
while (pool.Count > 0)
{
SocketAsyncEventArgs e = pool.Dequeue();
if (e != null)
{
e.Completed -= OnCompleted;
e.Dispose();
}
}
pool.Clear();
pool = null;
}
}
base.DisposeInternal();
}
}
}

View File

@@ -0,0 +1,227 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
namespace libomtnet
{
public class OMTUtils
{
internal static IPAddress[] ResolveHostname(string hostname)
{
try
{
return Dns.GetHostAddresses(hostname);
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTUtils.ResolveHostname");
}
return null;
}
public static IntPtr StringToPtrUTF8(string s)
{
byte[] b = UTF8Encoding.UTF8.GetBytes(s);
IntPtr dst = Marshal.AllocHGlobal(b.Length + 1);
Marshal.Copy(b, 0, dst, b.Length);
Marshal.WriteByte(dst, b.Length, 0);
return dst;
}
public static IntPtr StringToPtrUTF8(string s, out int length)
{
byte[] b = UTF8Encoding.UTF8.GetBytes(s);
IntPtr dst = Marshal.AllocHGlobal(b.Length + 1);
Marshal.Copy(b, 0, dst, b.Length);
Marshal.WriteByte(dst, b.Length, 0);
length = b.Length + 1;
return dst;
}
public static void WriteStringToPtrUTF8(string s, IntPtr dst)
{
byte[] b = UTF8Encoding.UTF8.GetBytes(s);
Marshal.Copy(b, 0, dst, b.Length);
Marshal.WriteByte(dst, b.Length, 0);
}
public static void WriteStringToPtrUTF8(string s, IntPtr dst, int maxLength)
{
if (maxLength <= 0) return;
byte[] b = UTF8Encoding.UTF8.GetBytes(s);
int len = Math.Min(maxLength - 1, b.Length);
Marshal.Copy(b, 0, dst, len);
Marshal.WriteByte(dst, len, 0);
}
public static string PtrToStringUTF8(IntPtr ptr)
{
using (MemoryStream m = new MemoryStream())
{
int offset = 0;
while (true)
{
byte b = Marshal.ReadByte(ptr, offset);
if (b == 0) break;
m.WriteByte(b);
offset++;
}
return UTF8Encoding.UTF8.GetString(m.ToArray());
}
}
public static string PtrToStringUTF8(IntPtr ptr, int maxLength)
{
using (MemoryStream m = new MemoryStream())
{
for (int i = 0; i < maxLength; i++)
{
byte b = Marshal.ReadByte(ptr, i);
if (b == 0) break;
m.WriteByte(b);
}
return UTF8Encoding.UTF8.GetString(m.ToArray());
}
}
public static void InterleavedToPlanarAudio32F32F(int numSamples, int channels, int sampleStride, float[] src, float[] dst)
{
int offset = 0;
for (int i = 0; i < numSamples; i++)
{
for (int c = 0; c < channels; c++)
{
dst[(sampleStride * c) + i] = src[offset];
offset += 1;
}
}
}
public static void InterleavedToPlanarAudio1632F(int numSamples, int channels, int sampleStride, short[] src, float[] dst)
{
int offset = 0;
for (int i = 0; i < numSamples; i++)
{
for (int c = 0; c < channels; c++)
{
float s = src[offset];
dst[(sampleStride * c) + i] = s / short.MaxValue;
offset += 1;
}
}
}
public static IntPtr XMLToIntPtr(string xml, ref int length)
{
byte[] utf8 = UTF8Encoding.UTF8.GetBytes(xml);
length = utf8.Length + 1;
IntPtr data = Marshal.AllocHGlobal(length);
Marshal.Copy(utf8, 0, data, utf8.Length);
Marshal.WriteByte(data, utf8.Length, 0);
return data;
}
public static string IntPtrToXML(IntPtr ptr, int length)
{
if (ptr != IntPtr.Zero && length > 0)
{
byte[] b = new byte[length];
Marshal.Copy(ptr, b, 0, length);
string xml = UTF8Encoding.UTF8.GetString(b);
return xml;
}
return null;
}
public static void FreeXMLIntPtr(IntPtr x)
{
if (x != IntPtr.Zero)
{
Marshal.FreeHGlobal(x);
}
}
public static float ToFrameRate(int frameRateN, int frameRateD)
{
if (frameRateD == 0) return 0;
double d = (double)frameRateN / (double)frameRateD;
d = Math.Round(d, 2);
return (float)d;
}
public static void FromFrameRate(float fps, ref int frameRateN, ref int frameRateD)
{
switch (Math.Round(fps, 2))
{
case 29.97:
frameRateN = 30000;
frameRateD = 1001;
break;
case 59.94:
frameRateN = 60000;
frameRateD = 1001;
break;
case 119.88:
frameRateN = 120000;
frameRateD = 1001;
break;
case 239.76:
frameRateN = 240000;
frameRateD = 1001;
break;
case 23.98:
case 23.976:
frameRateN = 24000;
frameRateD = 1001;
break;
default:
frameRateN = (int)fps;
frameRateD = 1;
break;
}
}
public static bool IsIPv4(IPAddress address)
{
if (address != null)
{
if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) return true;
if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
{
byte[] b = address.GetAddressBytes();
for (int i = 0; i < 10; i++)
{
if (b[i] != 0) return false;
}
if (b[10] == 0xFF && b[11] == 0xFF) return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace libomtnet.codecs
{
/// <summary>
/// Interface for native VMX codec. Needed for platforms which require different library paths, such as iOS
/// </summary>
internal interface IVMXCodec
{
IntPtr VMX_Create(OMTSize dimensions, VMXProfile profile, VMXColorSpace colorSpace);
void VMX_Destroy(IntPtr instance);
void VMX_SetQuality(IntPtr instance, int q);
int VMX_GetQuality(IntPtr instance);
void VMX_SetThreads(IntPtr instance, int t);
int VMX_GetThreads(IntPtr instance);
int VMX_LoadFrom(IntPtr instance, byte[] data, int dataLen);
int VMX_SaveTo(IntPtr instance, byte[] data, int maxLen);
int VMX_EncodeBGRA(IntPtr Instance, IntPtr src, int stride, int interlaced);
int VMX_EncodeBGRX(IntPtr Instance, IntPtr src, int stride, int interlaced);
int VMX_EncodeUYVY(IntPtr Instance, IntPtr src, int stride, int interlaced);
int VMX_EncodeUYVA(IntPtr Instance, IntPtr src, int stride, int interlaced);
int VMX_EncodeP216(IntPtr Instance, IntPtr src, int stride, int interlaced);
int VMX_EncodePA16(IntPtr Instance, IntPtr src, int stride, int interlaced);
int VMX_EncodeYUY2(IntPtr Instance, IntPtr src, int stride, int interlaced);
int VMX_EncodeNV12(IntPtr Instance, IntPtr srcY, int strideY, IntPtr srcUV, int strideUV, int interlaced);
int VMX_EncodeYV12(IntPtr Instance, IntPtr srcY, int strideY, IntPtr srcU, int strideU, IntPtr srcV, int strideV, int interlaced);
int VMX_DecodeUYVY(IntPtr Instance, byte[] dst, int stride);
int VMX_DecodeUYVA(IntPtr Instance, byte[] dst, int stride);
int VMX_DecodeP216(IntPtr Instance, byte[] dst, int stride);
int VMX_DecodePA16(IntPtr Instance, byte[] dst, int stride);
int VMX_DecodeYUY2(IntPtr Instance, byte[] dst, int stride);
int VMX_DecodeBGRX(IntPtr Instance, byte[] dst, int stride);
int VMX_DecodeBGRA(IntPtr Instance, byte[] dst, int stride);
int VMX_DecodePreviewUYVY(IntPtr Instance, byte[] dst, int stride);
int VMX_DecodePreviewYUY2(IntPtr Instance, byte[] dst, int stride);
int VMX_DecodePreviewBGRA(IntPtr Instance, byte[] dst, int stride);
int VMX_DecodePreviewBGRX(IntPtr Instance, byte[] dst, int stride);
int VMX_DecodePreviewUYVA(IntPtr Instance, byte[] dst, int stride);
int VMX_GetEncodedPreviewLength(IntPtr Instance);
float VMX_CalculatePSNR(byte[] image1, byte[] image2, int stride, int bytesPerPixel, OMTSize sz);
}
}

View File

@@ -0,0 +1,94 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace libomtnet.codecs
{
internal class OMTFPA1Codec : OMTBase
{
byte[] zeroBuffer;
public OMTFPA1Codec(int maxLength)
{
zeroBuffer = new byte[maxLength];
}
public void Decode(OMTBuffer src, int srcChannels, int srcSamplesPerChannel, OMTActiveAudioChannels srcActiveChannels, OMTBuffer dst)
{
int offset = 0;
int dstoffset = 0;
for (int i = 0; i < srcChannels; i++)
{
OMTActiveAudioChannels chflag = (OMTActiveAudioChannels)(1 << i);
if ((srcActiveChannels & chflag) == chflag)
{
Buffer.BlockCopy(src.Buffer, src.Offset + offset, dst.Buffer, dst.Offset + dstoffset, srcSamplesPerChannel * OMTConstants.AUDIO_SAMPLE_SIZE);
offset += srcSamplesPerChannel * 4;
}
else
{
//>twice as fast as Array.Clear
Buffer.BlockCopy(zeroBuffer, 0, dst.Buffer, dst.Offset + dstoffset, srcSamplesPerChannel * OMTConstants.AUDIO_SAMPLE_SIZE);
}
dstoffset += srcSamplesPerChannel * OMTConstants.AUDIO_SAMPLE_SIZE;
}
dst.SetBuffer(0, srcChannels * srcSamplesPerChannel * OMTConstants.AUDIO_SAMPLE_SIZE);
}
private static bool IsEmpty(OMTBuffer buff, int offset, int length)
{
for (int i = buff.Offset + offset; i < buff.Offset + offset + length; i++)
{
if (buff.Buffer[i] != 0) return false;
}
return true;
}
public static OMTActiveAudioChannels Encode(OMTBuffer src, int srcChannels, int srcSamplesPerChannel, OMTBuffer dst)
{
OMTActiveAudioChannels activeChannels = 0;
int offset = 0;
int dstoffset = 0;
for (int i = 0; i < srcChannels; i++)
{
if (!IsEmpty(src, src.Offset + offset, srcSamplesPerChannel * OMTConstants.AUDIO_SAMPLE_SIZE))
{
OMTActiveAudioChannels chflag = (OMTActiveAudioChannels)(1 << i);
Buffer.BlockCopy(src.Buffer, src.Offset + offset, dst.Buffer, dst.Offset + dstoffset, srcSamplesPerChannel * OMTConstants.AUDIO_SAMPLE_SIZE);
activeChannels = activeChannels | chflag;
dstoffset += srcSamplesPerChannel * OMTConstants.AUDIO_SAMPLE_SIZE;
}
offset += srcSamplesPerChannel * OMTConstants.AUDIO_SAMPLE_SIZE;
}
dst.SetBuffer(dst.Offset, dstoffset);
return activeChannels;
}
protected override void DisposeInternal()
{
zeroBuffer = null;
base.DisposeInternal();
}
}
}

View File

@@ -0,0 +1,286 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
namespace libomtnet.codecs
{
/// <summary>
/// VMX Profile sets bitrate targets and DC coding precision
/// HQ, SQ, LQ: VMX Legacy recording profiles
/// OMT Profiles (SQ Default):
/// 2160p60 HQ 600 SQ 300 LQ 200 Mbps
/// 1080p60 HQ 288 SQ 200 LQ 80 Mbps
/// SD HQ 72 SQ 36 LQ 24 Mbps
/// </summary>
public enum VMXProfile
{
None = 0,
Default = 1,
LQ = 33,
SQ = 66,
HQ = 99,
OMT_LQ = 133,
OMT_SQ = 166,
OMT_HQ = 199
}
public enum VMXColorSpace
{
Undefined = 0,
BT601 = 601,
BT709 = 709
}
public enum VMXImageType
{
None = 0,
UYVY = 1,
YUY2 = 2,
NV12 = 3,
YV12 = 4,
BGRA = 5,
BGRX = 6,
UYVA = 7,
P216 = 8,
PA16 = 9
}
/// <summary>
/// VMX Encoder/Decoder for OMT
/// </summary>
public class OMTVMX1Codec : OMTBase
{
private const string DLLPATH = @"libvmx";
private readonly int width;
private readonly int height;
private readonly int framesPerSecond;
private readonly VMXProfile profile;
private readonly VMXColorSpace colorSpace;
private IntPtr instance;
private IVMXCodec codec;
public OMTVMX1Codec(int width, int height, int framesPerSecond, VMXProfile profile = VMXProfile.Default, VMXColorSpace colorSpace = VMXColorSpace.Undefined)
{
if (OMTPlatform.GetPlatformType() == OMTPlatformType.iOS)
{
codec = new VMXCodecIOS();
} else
{
codec = new VMXCodec();
}
this.width = width;
this.height = height;
this.profile = profile;
this.colorSpace = colorSpace;
this.framesPerSecond = framesPerSecond;
if (profile == VMXProfile.Default) { profile = VMXProfile.OMT_SQ; }
this.instance = codec.VMX_Create(new OMTSize(width, height), profile, colorSpace);
if (framesPerSecond > 60)
{
int threads = codec.VMX_GetThreads(this.instance);
threads *= 2;
codec.VMX_SetThreads(this.instance, threads);
Debug.WriteLine("Codec.SetThreads: " + threads);
}
}
public float CalculatePSNR(byte[] image1, byte[] image2, int stride, int bytesPerPixel, OMTSize sz)
{
return codec.VMX_CalculatePSNR(image1, image2, stride, bytesPerPixel, sz);
}
public void SetQuality(int quality)
{
codec.VMX_SetQuality(this.instance, quality);
}
public int GetQuality()
{
return codec.VMX_GetQuality(this.instance);
}
public int Encode(VMXImageType itype, IntPtr src, int srcStride, byte[] dst, bool interlaced)
{
int i = 0;
if (interlaced) i = 1;
int hr = 0;
switch (itype)
{
case VMXImageType.UYVY:
hr = codec.VMX_EncodeUYVY(instance, src, srcStride, i);
break;
case VMXImageType.UYVA:
hr = codec.VMX_EncodeUYVA(instance, src, srcStride, i);
break;
case VMXImageType.P216:
hr = codec.VMX_EncodeP216(instance, src, srcStride, i);
break;
case VMXImageType.PA16:
hr = codec.VMX_EncodePA16(instance, src, srcStride, i);
break;
case VMXImageType.YUY2:
hr = codec.VMX_EncodeYUY2(instance, src, srcStride, i);
break;
case VMXImageType.NV12:
IntPtr srcUV = src + (srcStride * height);
hr = codec.VMX_EncodeNV12(instance, src, srcStride, srcUV, srcStride, i);
break;
case VMXImageType.YV12:
IntPtr srcV = src + (srcStride * height);
int strideV = srcStride >> 1;
IntPtr srcU = srcV + (strideV * (height >> 1));
int strideU = srcStride >> 1;
hr = codec.VMX_EncodeYV12(instance, src, srcStride, srcU, strideU, srcV, strideV, i);
break;
case VMXImageType.BGRA:
hr = codec.VMX_EncodeBGRA(instance, src, srcStride, i);
break;
case VMXImageType.BGRX:
hr = codec.VMX_EncodeBGRA(instance, src, srcStride, i);
break;
default:
return 0;
}
if (hr == 0)
{
int len = codec.VMX_SaveTo(instance, dst, dst.Length);
return len;
}
return 0;
}
public bool DecodePreview(VMXImageType itype, byte[] src, int srcLen, ref byte[] dst, int dstStride)
{
int hr = codec.VMX_LoadFrom(instance, src, srcLen);
if (hr == 0)
{
switch (itype)
{
case VMXImageType.BGRA:
hr = codec.VMX_DecodePreviewBGRA(instance, dst, dstStride);
break;
case VMXImageType.BGRX:
hr = codec.VMX_DecodePreviewBGRX(instance, dst, dstStride);
break;
case VMXImageType.UYVY:
hr = codec.VMX_DecodePreviewUYVY(instance, dst, dstStride);
break;
case VMXImageType.UYVA:
hr = codec.VMX_DecodePreviewUYVA(instance, dst, dstStride);
break;
case VMXImageType.YUY2:
hr = codec.VMX_DecodePreviewYUY2(instance, dst, dstStride);
break;
default:
return false;
}
if (hr == 0) return true;
}
return false;
}
public bool Decode(VMXImageType itype, byte[] src, int srcLen, ref byte[] dst, int dstStride)
{
int hr = codec.VMX_LoadFrom(instance, src, srcLen);
if (hr == 0)
{
switch (itype)
{
case VMXImageType.BGRA:
hr = codec.VMX_DecodeBGRA(instance, dst, dstStride);
break;
case VMXImageType.BGRX:
hr = codec.VMX_DecodeBGRX(instance, dst, dstStride);
break;
case VMXImageType.UYVY:
hr = codec.VMX_DecodeUYVY(instance, dst, dstStride);
break;
case VMXImageType.YUY2:
hr = codec.VMX_DecodeYUY2(instance, dst, dstStride);
break;
case VMXImageType.UYVA:
hr = codec.VMX_DecodeUYVA(instance, dst, dstStride);
break;
case VMXImageType.P216:
hr = codec.VMX_DecodeP216(instance, dst, dstStride);
break;
case VMXImageType.PA16:
hr = codec.VMX_DecodePA16(instance, dst, dstStride);
break;
default:
return false;
}
if (hr == 0) return true;
}
return false;
}
public OMTSize GetPreviewSize(bool interlaced)
{
OMTSize size = new OMTSize();
size.Width = width >> 3;
size.Height = height >> 3;
if ((size.Width %2) != 0)
{
size.Width++;
}
if (interlaced)
{
if ((size.Height % 2) != 0)
{
size.Height--;
}
}
return size;
}
public int GetEncodedPreviewLength()
{
return codec.VMX_GetEncodedPreviewLength(instance);
}
protected override void DisposeInternal()
{
if (instance != null)
{
codec.VMX_Destroy(instance);
instance = IntPtr.Zero;
}
base.DisposeInternal();
}
public int Width { get { return width; } }
public int Height { get { return height; } }
public int FramesPerSecond { get { return framesPerSecond; } }
public VMXProfile Profile { get { return profile; } }
public VMXColorSpace ColorSpace { get { return colorSpace; } }
}
}

View File

@@ -0,0 +1,188 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace libomtnet.codecs
{
internal class VMXCodec : IVMXCodec
{
public float VMX_CalculatePSNR(byte[] image1, byte[] image2, int stride, int bytesPerPixel, OMTSize sz)
{
return VMXUnmanaged.VMX_CalculatePSNR(image1, image2, stride, bytesPerPixel, sz);
}
public IntPtr VMX_Create(OMTSize dimensions, VMXProfile profile, VMXColorSpace colorSpace)
{
return VMXUnmanaged.VMX_Create(dimensions, profile, colorSpace);
}
public int VMX_DecodeBGRA(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanaged.VMX_DecodeBGRA(Instance, dst, stride);
}
public int VMX_DecodeBGRX(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanaged.VMX_DecodeBGRX(Instance, dst, stride);
}
public int VMX_DecodePreviewBGRA(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanaged.VMX_DecodePreviewBGRA(Instance, dst, stride);
}
public int VMX_DecodePreviewBGRX(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanaged.VMX_DecodePreviewBGRX(Instance, dst, stride);
}
public int VMX_DecodePreviewUYVY(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanaged.VMX_DecodePreviewUYVY(Instance, dst, stride);
}
public int VMX_DecodePreviewUYVA(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanaged.VMX_DecodePreviewUYVA(Instance, dst, stride);
}
public int VMX_DecodePreviewYUY2(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanaged.VMX_DecodePreviewYUY2(Instance, dst, stride);
}
public int VMX_DecodeUYVY(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanaged.VMX_DecodeUYVY(Instance, dst, stride);
}
public int VMX_DecodeUYVA(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanaged.VMX_DecodeUYVA(Instance, dst, stride);
}
public int VMX_DecodeP216(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanaged.VMX_DecodeP216(Instance, dst, stride);
}
public int VMX_DecodePA16(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanaged.VMX_DecodePA16(Instance, dst, stride);
}
public int VMX_DecodeYUY2(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanaged.VMX_DecodeYUY2(Instance, dst, stride);
}
public void VMX_Destroy(IntPtr instance)
{
VMXUnmanaged.VMX_Destroy(instance);
}
public int VMX_EncodeBGRA(IntPtr Instance, IntPtr src, int stride, int interlaced)
{
return VMXUnmanaged.VMX_EncodeBGRA(Instance,src,stride, interlaced);
}
public int VMX_EncodeBGRX(IntPtr Instance, IntPtr src, int stride, int interlaced)
{
return VMXUnmanaged.VMX_EncodeBGRX(Instance, src, stride, interlaced);
}
public int VMX_EncodeNV12(IntPtr Instance, IntPtr srcY, int strideY, IntPtr srcUV, int strideUV, int interlaced)
{
return VMXUnmanaged.VMX_EncodeNV12(Instance,srcY,strideY,srcUV,strideUV,interlaced);
}
public int VMX_EncodeUYVY(IntPtr Instance, IntPtr src, int stride, int interlaced)
{
return VMXUnmanaged.VMX_EncodeUYVY(Instance,src,stride,interlaced);
}
public int VMX_EncodeUYVA(IntPtr Instance, IntPtr src, int stride, int interlaced)
{
return VMXUnmanaged.VMX_EncodeUYVA(Instance, src, stride, interlaced);
}
public int VMX_EncodeP216(IntPtr Instance, IntPtr src, int stride, int interlaced)
{
return VMXUnmanaged.VMX_EncodeP216(Instance, src, stride, interlaced);
}
public int VMX_EncodePA16(IntPtr Instance, IntPtr src, int stride, int interlaced)
{
return VMXUnmanaged.VMX_EncodePA16(Instance, src, stride, interlaced);
}
public int VMX_EncodeYUY2(IntPtr Instance, IntPtr src, int stride, int interlaced)
{
return VMXUnmanaged.VMX_EncodeYUY2(Instance, src, stride, interlaced);
}
public int VMX_EncodeYV12(IntPtr Instance, IntPtr srcY, int strideY, IntPtr srcU, int strideU, IntPtr srcV, int strideV, int interlaced)
{
return VMXUnmanaged.VMX_EncodeYV12(Instance,srcY,strideY,srcU,strideU,srcV,strideV,interlaced);
}
public int VMX_GetEncodedPreviewLength(IntPtr Instance)
{
return VMXUnmanaged.VMX_GetEncodedPreviewLength(Instance);
}
public int VMX_GetThreads(IntPtr instance)
{
return VMXUnmanaged.VMX_GetThreads(instance);
}
public int VMX_LoadFrom(IntPtr instance, byte[] data, int dataLen)
{
return VMXUnmanaged.VMX_LoadFrom(instance,data,dataLen);
}
public int VMX_SaveTo(IntPtr instance, byte[] data, int maxLen)
{
return VMXUnmanaged.VMX_SaveTo(instance, data, maxLen);
}
public void VMX_SetQuality(IntPtr instance, int q)
{
VMXUnmanaged.VMX_SetQuality(instance, q);
}
public void VMX_SetThreads(IntPtr instance, int t)
{
VMXUnmanaged.VMX_SetThreads(instance, t);
}
public int VMX_GetQuality(IntPtr instance)
{
return VMXUnmanaged.VMX_GetQuality(instance);
}
}
}

View File

@@ -0,0 +1,189 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace libomtnet.codecs
{
internal class VMXCodecIOS : IVMXCodec
{
public float VMX_CalculatePSNR(byte[] image1, byte[] image2, int stride, int bytesPerPixel, OMTSize sz)
{
return VMXUnmanagedIOS.VMX_CalculatePSNR(image1, image2, stride, bytesPerPixel, sz);
}
public IntPtr VMX_Create(OMTSize dimensions, VMXProfile profile, VMXColorSpace colorSpace)
{
return VMXUnmanagedIOS.VMX_Create(dimensions, profile, colorSpace);
}
public int VMX_DecodeBGRA(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanagedIOS.VMX_DecodeBGRA(Instance, dst, stride);
}
public int VMX_DecodeBGRX(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanagedIOS.VMX_DecodeBGRX(Instance, dst, stride);
}
public int VMX_DecodePreviewBGRA(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanagedIOS.VMX_DecodePreviewBGRA(Instance, dst, stride);
}
public int VMX_DecodePreviewBGRX(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanagedIOS.VMX_DecodePreviewBGRX(Instance, dst, stride);
}
public int VMX_DecodePreviewUYVY(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanagedIOS.VMX_DecodePreviewUYVY(Instance, dst, stride);
}
public int VMX_DecodePreviewUYVA(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanagedIOS.VMX_DecodePreviewUYVA(Instance, dst, stride);
}
public int VMX_DecodePreviewYUY2(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanagedIOS.VMX_DecodePreviewYUY2(Instance, dst, stride);
}
public int VMX_DecodeUYVY(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanagedIOS.VMX_DecodeUYVY(Instance, dst, stride);
}
public int VMX_DecodeUYVA(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanagedIOS.VMX_DecodeUYVA(Instance, dst, stride);
}
public int VMX_DecodeP216(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanagedIOS.VMX_DecodeP216(Instance, dst, stride);
}
public int VMX_DecodePA16(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanagedIOS.VMX_DecodePA16(Instance, dst, stride);
}
public int VMX_DecodeYUY2(IntPtr Instance, byte[] dst, int stride)
{
return VMXUnmanagedIOS.VMX_DecodeYUY2(Instance, dst, stride);
}
public void VMX_Destroy(IntPtr instance)
{
VMXUnmanagedIOS.VMX_Destroy(instance);
}
public int VMX_EncodeBGRA(IntPtr Instance, IntPtr src, int stride, int interlaced)
{
return VMXUnmanagedIOS.VMX_EncodeBGRA(Instance, src, stride, interlaced);
}
public int VMX_EncodeBGRX(IntPtr Instance, IntPtr src, int stride, int interlaced)
{
return VMXUnmanagedIOS.VMX_EncodeBGRX(Instance, src, stride, interlaced);
}
public int VMX_EncodeNV12(IntPtr Instance, IntPtr srcY, int strideY, IntPtr srcUV, int strideUV, int interlaced)
{
return VMXUnmanagedIOS.VMX_EncodeNV12(Instance, srcY, strideY, srcUV, strideUV, interlaced);
}
public int VMX_EncodeP216(IntPtr Instance, IntPtr src, int stride, int interlaced)
{
return VMXUnmanagedIOS.VMX_EncodeP216(Instance, src, stride, interlaced);
}
public int VMX_EncodePA16(IntPtr Instance, IntPtr src, int stride, int interlaced)
{
return VMXUnmanagedIOS.VMX_EncodePA16(Instance, src, stride, interlaced);
}
public int VMX_EncodeUYVY(IntPtr Instance, IntPtr src, int stride, int interlaced)
{
return VMXUnmanagedIOS.VMX_EncodeUYVY(Instance, src, stride, interlaced);
}
public int VMX_EncodeUYVA(IntPtr Instance, IntPtr src, int stride, int interlaced)
{
return VMXUnmanagedIOS.VMX_EncodeUYVA(Instance, src, stride, interlaced);
}
public int VMX_EncodeYUY2(IntPtr Instance, IntPtr src, int stride, int interlaced)
{
return VMXUnmanagedIOS.VMX_EncodeYUY2(Instance, src, stride, interlaced);
}
public int VMX_EncodeYV12(IntPtr Instance, IntPtr srcY, int strideY, IntPtr srcU, int strideU, IntPtr srcV, int strideV, int interlaced)
{
return VMXUnmanagedIOS.VMX_EncodeYV12(Instance, srcY, strideY, srcU, strideU, srcV, strideV, interlaced);
}
public int VMX_GetEncodedPreviewLength(IntPtr Instance)
{
return VMXUnmanagedIOS.VMX_GetEncodedPreviewLength(Instance);
}
public int VMX_GetThreads(IntPtr instance)
{
return VMXUnmanagedIOS.VMX_GetThreads(instance);
}
public int VMX_LoadFrom(IntPtr instance, byte[] data, int dataLen)
{
return VMXUnmanagedIOS.VMX_LoadFrom(instance, data, dataLen);
}
public int VMX_SaveTo(IntPtr instance, byte[] data, int maxLen)
{
return VMXUnmanagedIOS.VMX_SaveTo(instance, data, maxLen);
}
public void VMX_SetQuality(IntPtr instance, int q)
{
VMXUnmanagedIOS.VMX_SetQuality(instance, q);
}
public void VMX_SetThreads(IntPtr instance, int t)
{
VMXUnmanagedIOS.VMX_SetThreads(instance, t);
}
public int VMX_GetQuality(IntPtr instance)
{
return VMXUnmanagedIOS.VMX_GetQuality(instance);
}
}
}

View File

@@ -0,0 +1,99 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace libomtnet.codecs
{
internal class VMXUnmanaged
{
private const string DLLPATH = @"libvmx";
[DllImport(DLLPATH)]
internal static extern IntPtr VMX_Create(OMTSize dimensions, VMXProfile profile, VMXColorSpace colorSpace);
[DllImport(DLLPATH)]
internal static extern void VMX_Destroy(IntPtr instance);
[DllImport(DLLPATH)]
internal static extern void VMX_SetQuality(IntPtr instance, int q);
[DllImport(DLLPATH)]
internal static extern int VMX_GetQuality(IntPtr instance);
[DllImport(DLLPATH)]
internal static extern void VMX_SetThreads(IntPtr instance, int t);
[DllImport(DLLPATH)]
internal static extern int VMX_GetThreads(IntPtr instance);
[DllImport(DLLPATH)]
internal static extern int VMX_LoadFrom(IntPtr instance, byte[] data, int dataLen);
[DllImport(DLLPATH)]
internal static extern int VMX_SaveTo(IntPtr instance, byte[] data, int maxLen);
[DllImport(DLLPATH)]
internal static extern int VMX_EncodeBGRA(IntPtr Instance, IntPtr src, int stride, int interlaced);
[DllImport(DLLPATH)]
internal static extern int VMX_EncodeBGRX(IntPtr Instance, IntPtr src, int stride, int interlaced);
[DllImport(DLLPATH)]
internal static extern int VMX_EncodeP216(IntPtr Instance, IntPtr src, int stride, int interlaced);
[DllImport(DLLPATH)]
internal static extern int VMX_EncodePA16(IntPtr Instance, IntPtr src, int stride, int interlaced);
[DllImport(DLLPATH)]
internal static extern int VMX_EncodeUYVY(IntPtr Instance, IntPtr src, int stride, int interlaced);
[DllImport(DLLPATH)]
internal static extern int VMX_EncodeUYVA(IntPtr Instance, IntPtr src, int stride, int interlaced);
[DllImport(DLLPATH)]
internal static extern int VMX_EncodeYUY2(IntPtr Instance, IntPtr src, int stride, int interlaced);
[DllImport(DLLPATH)]
internal static extern int VMX_EncodeNV12(IntPtr Instance, IntPtr srcY, int strideY, IntPtr srcUV, int strideUV, int interlaced);
[DllImport(DLLPATH)]
internal static extern int VMX_EncodeYV12(IntPtr Instance, IntPtr srcY, int strideY, IntPtr srcU, int strideU, IntPtr srcV, int strideV, int interlaced);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodeUYVY(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodeUYVA(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodeP216(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodePA16(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodeYUY2(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodeBGRX(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodeBGRA(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodePreviewUYVY(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodePreviewYUY2(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodePreviewBGRA(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodePreviewBGRX(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodePreviewUYVA(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_GetEncodedPreviewLength(IntPtr Instance);
[DllImport(DLLPATH)]
internal static extern float VMX_CalculatePSNR(byte[] image1, byte[] image2, int stride, int bytesPerPixel, OMTSize sz);
}
}

View File

@@ -0,0 +1,99 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace libomtnet.codecs
{
internal class VMXUnmanagedIOS
{
private const string DLLPATH = @"@rpath/libvmx.framework/libvmx";
[DllImport(DLLPATH)]
internal static extern IntPtr VMX_Create(OMTSize dimensions, VMXProfile profile, VMXColorSpace colorSpace);
[DllImport(DLLPATH)]
internal static extern void VMX_Destroy(IntPtr instance);
[DllImport(DLLPATH)]
internal static extern void VMX_SetQuality(IntPtr instance, int q);
[DllImport(DLLPATH)]
internal static extern int VMX_GetQuality(IntPtr instance);
[DllImport(DLLPATH)]
internal static extern void VMX_SetThreads(IntPtr instance, int t);
[DllImport(DLLPATH)]
internal static extern int VMX_GetThreads(IntPtr instance);
[DllImport(DLLPATH)]
internal static extern int VMX_LoadFrom(IntPtr instance, byte[] data, int dataLen);
[DllImport(DLLPATH)]
internal static extern int VMX_SaveTo(IntPtr instance, byte[] data, int maxLen);
[DllImport(DLLPATH)]
internal static extern int VMX_EncodeBGRA(IntPtr Instance, IntPtr src, int stride, int interlaced);
[DllImport(DLLPATH)]
internal static extern int VMX_EncodeBGRX(IntPtr Instance, IntPtr src, int stride, int interlaced);
[DllImport(DLLPATH)]
internal static extern int VMX_EncodeP216(IntPtr Instance, IntPtr src, int stride, int interlaced);
[DllImport(DLLPATH)]
internal static extern int VMX_EncodePA16(IntPtr Instance, IntPtr src, int stride, int interlaced);
[DllImport(DLLPATH)]
internal static extern int VMX_EncodeUYVY(IntPtr Instance, IntPtr src, int stride, int interlaced);
[DllImport(DLLPATH)]
internal static extern int VMX_EncodeUYVA(IntPtr Instance, IntPtr src, int stride, int interlaced);
[DllImport(DLLPATH)]
internal static extern int VMX_EncodeYUY2(IntPtr Instance, IntPtr src, int stride, int interlaced);
[DllImport(DLLPATH)]
internal static extern int VMX_EncodeNV12(IntPtr Instance, IntPtr srcY, int strideY, IntPtr srcUV, int strideUV, int interlaced);
[DllImport(DLLPATH)]
internal static extern int VMX_EncodeYV12(IntPtr Instance, IntPtr srcY, int strideY, IntPtr srcU, int strideU, IntPtr srcV, int strideV, int interlaced);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodeUYVY(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodeUYVA(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodeP216(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodePA16(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodeYUY2(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodeBGRX(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodeBGRA(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodePreviewUYVY(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodePreviewYUY2(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodePreviewBGRA(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodePreviewBGRX(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_DecodePreviewUYVA(IntPtr Instance, byte[] dst, int stride);
[DllImport(DLLPATH)]
internal static extern int VMX_GetEncodedPreviewLength(IntPtr Instance);
[DllImport(DLLPATH)]
internal static extern float VMX_CalculatePSNR(byte[] image1, byte[] image2, int stride, int bytesPerPixel, OMTSize sz);
}
}

View File

@@ -0,0 +1,106 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Runtime.InteropServices;
namespace libomtnet.linux
{
internal class AvahiClient
{
public const string DLL_PATH_CLIENT = "libavahi-client.so.3";
public const string DLL_PATH_COMMON = "libavahi-common.so.3";
public const int AVAHI_PROTO_UNSPEC = -1;
public const int AVAHI_PROTO_INET = 0;
public const int AVAHI_PROTO_INET6 = 1;
public const int AVAHI_IF_UNSPEC = -1;
public delegate void AvahiClientCallback(IntPtr s, int state, IntPtr userData);
public delegate void AvahiServiceBrowserCallback(IntPtr b, int iface, int protocol, AvahiBrowserEvent evt, IntPtr name, IntPtr type, IntPtr domain, int flags, IntPtr userData);
public delegate void AvahiServiceResolverCallback(IntPtr r, int iface, int protocol, AvahiResolverEvent evt, IntPtr name, IntPtr type, IntPtr domain,
IntPtr host_name, IntPtr a, UInt16 port, IntPtr txt, int flags, IntPtr userData);
public delegate void AvahiEntryGroupCallback(IntPtr group, int state, IntPtr userData);
public enum AvahiBrowserEvent
{
AVAHI_BROWSER_NEW,
AVAHI_BROWSER_REMOVE,
AVAHI_BROWSER_CACHE_EXHAUSTED,
AVAHI_BROWSER_ALL_FOR_NOW,
AVAHI_BROWSER_FAILURE
}
public enum AvahiResolverEvent
{
AVAHI_RESOLVER_FOUND,
AVAHI_RESOLVER_FAILURE
}
[DllImport(DLL_PATH_COMMON)]
public static extern IntPtr avahi_simple_poll_new();
[DllImport(DLL_PATH_COMMON)]
public static extern void avahi_simple_poll_free(IntPtr sp);
[DllImport(DLL_PATH_COMMON)]
public static extern void avahi_simple_poll_quit(IntPtr sp);
[DllImport(DLL_PATH_COMMON)]
public static extern IntPtr avahi_simple_poll_get(IntPtr s);
[DllImport(DLL_PATH_COMMON)]
public static extern int avahi_simple_poll_iterate(IntPtr sp, int sleepTime);
[DllImport(DLL_PATH_CLIENT)]
public static extern IntPtr avahi_client_new(IntPtr poll, int flags, IntPtr callback, IntPtr userdata, ref int error);
[DllImport(DLL_PATH_CLIENT)]
public static extern IntPtr avahi_service_browser_new(IntPtr client, int iface, int protocol, IntPtr type, IntPtr domain, int flags, IntPtr callback, IntPtr userData);
[DllImport(DLL_PATH_CLIENT)]
public static extern void avahi_client_free(IntPtr client);
[DllImport(DLL_PATH_CLIENT)]
public static extern int avahi_service_browser_free(IntPtr browser);
[DllImport(DLL_PATH_CLIENT)]
public static extern IntPtr avahi_service_resolver_new(IntPtr client, int iface, int protocol, IntPtr name, IntPtr type, IntPtr domain, int aprotocol, int flags, IntPtr callback, IntPtr userData);
[DllImport(DLL_PATH_CLIENT)]
public static extern int avahi_service_resolver_free(IntPtr resolver);
[DllImport(DLL_PATH_CLIENT)]
public static extern IntPtr avahi_entry_group_new(IntPtr client, IntPtr callback, IntPtr userData);
[DllImport(DLL_PATH_CLIENT)]
public static extern int avahi_entry_group_free(IntPtr group);
[DllImport(DLL_PATH_CLIENT)]
public static extern int avahi_entry_group_add_service(IntPtr group, int iface, int protocol, int flags, IntPtr name,
IntPtr type, IntPtr domain, IntPtr host, UInt16 port, IntPtr args);
[DllImport(DLL_PATH_CLIENT)]
public static extern int avahi_entry_group_commit(IntPtr group);
}
}

View File

@@ -0,0 +1,85 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace libomtnet.linux
{ internal class LinuxPlatform : OMTPlatform
{
private const int RTLD_NOW = 2;
private const int RTLD_GLOBAL = 8;
[DllImport("libdl.so")]
static extern IntPtr dlopen(string filename, int flags);
[DllImport("libc")]
private static extern int gethostname(IntPtr name, IntPtr size);
public override string GetMachineName()
{
int len = 4096;
IntPtr buf = Marshal.AllocHGlobal(len);
try
{
int result = gethostname(buf, (IntPtr)len);
if (result == 0)
{
string name = OMTUtils.PtrToStringUTF8(buf);
if (!String.IsNullOrEmpty(name))
{
return name.ToUpper();
}
}
}
finally
{
Marshal.FreeHGlobal(buf);
}
OMTLogging.Write("Unable to retrieve full hostname", "LinuxPlatform");
return base.GetMachineName();
}
public override string GetStoragePath()
{
string sz = Environment.GetEnvironmentVariable("OMT_STORAGE_PATH");
if (!String.IsNullOrEmpty(sz)) return sz;
return Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + Path.DirectorySeparatorChar + ".OMT";
}
public override IntPtr OpenLibrary(string filename)
{
return dlopen(filename, RTLD_GLOBAL | RTLD_NOW);
}
protected override string GetLibraryExtension()
{
return ".so";
}
}
}

View File

@@ -0,0 +1,292 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Net;
using System.Runtime.InteropServices;
using static libomtnet.linux.AvahiClient;
using System.Threading;
namespace libomtnet.linux
{
internal class OMTDiscoveryAvahi : OMTDiscovery
{
private IntPtr client = IntPtr.Zero;
private IntPtr simplePoll = IntPtr.Zero;
private IntPtr poll = IntPtr.Zero;
private IntPtr browser = IntPtr.Zero;
private AvahiClient.AvahiClientCallback clientCallback;
private AvahiClient.AvahiServiceBrowserCallback serviceBrowserCallback;
private AvahiClient.AvahiServiceResolverCallback serviceResolverCallback;
private AvahiClient.AvahiEntryGroupCallback entryGroupCallback;
private IntPtr serviceType;
private Thread eventThread;
private bool eventThreadRunning = false;
private class EntryAvahi : OMTDiscoveryEntry
{
public EntryAvahi(OMTAddress address) : base(address) { }
public IntPtr Group;
protected override void DisposeInternal()
{
if (Group != null)
{
AvahiClient.avahi_entry_group_free(Group);
Group = IntPtr.Zero;
}
base.DisposeInternal();
}
}
internal OMTDiscoveryAvahi()
{
serviceType = OMTUtils.StringToPtrUTF8("_omt._tcp");
clientCallback = new AvahiClient.AvahiClientCallback(ClientCallback);
entryGroupCallback = new AvahiClient.AvahiEntryGroupCallback(EntryGroupCallback);
serviceBrowserCallback = new AvahiClient.AvahiServiceBrowserCallback(ServiceBrowserCallback);
serviceResolverCallback = new AvahiClient.AvahiServiceResolverCallback(ServiceResolverCallback);
simplePoll = AvahiClient.avahi_simple_poll_new();
if (simplePoll == IntPtr.Zero) {
OMTLogging.Write("Failure creating simple poll", "OMTDiscoveryAvahi");
return;
}
poll = AvahiClient.avahi_simple_poll_get(simplePoll);
if (poll == IntPtr.Zero) {
OMTLogging.Write("Failure retrieving poll", "OMTDiscoveryAvahi");
return;
}
int hr = 0;
client = AvahiClient.avahi_client_new(poll, 0, Marshal.GetFunctionPointerForDelegate(clientCallback), IntPtr.Zero, ref hr);
if (client == IntPtr.Zero)
{
OMTLogging.Write("Failure creating client: " + hr, "OMTDiscoveryAvahi");
}
browser = AvahiClient.avahi_service_browser_new(client, AvahiClient.AVAHI_IF_UNSPEC, AvahiClient.AVAHI_PROTO_UNSPEC
, serviceType, IntPtr.Zero, 0, Marshal.GetFunctionPointerForDelegate(serviceBrowserCallback), IntPtr.Zero);
if (browser == IntPtr.Zero)
{
OMTLogging.Write("Failure creating browser: " + hr, "OMTDiscoveryAvahi");
}
eventThreadRunning = true;
eventThread = new Thread(EventThread);
eventThread.IsBackground = true;
eventThread.Start();
OMTLogging.Write("BrowserStarted", "OMTDiscoveryAvahi");
}
private void EventThread()
{
try
{
int hr = 0;
while (eventThreadRunning)
{
hr = AvahiClient.avahi_simple_poll_iterate(simplePoll, -1);
if (hr != 0)
{
OMTLogging.Write("EventThead exiting...", "OMTDiscoveryAvahi");
break;
}
}
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTDiscoveryAvahi.EventThread");
}
}
protected override void DisposeInternal()
{
eventThreadRunning = false;
if (simplePoll != null)
{
AvahiClient.avahi_simple_poll_quit(simplePoll);
}
if (eventThread != null)
{
if (eventThread.Join(5000) == false)
{
eventThread.Abort();
}
eventThread = null;
}
if (browser != IntPtr.Zero)
{
AvahiClient.avahi_service_browser_free(browser);
browser = IntPtr.Zero;
}
if (client != IntPtr.Zero)
{
AvahiClient.avahi_client_free(client);
client = IntPtr.Zero;
}
if (serviceType != IntPtr.Zero)
{
Marshal.FreeHGlobal(serviceType);
serviceType = IntPtr.Zero;
}
if (simplePoll != IntPtr.Zero)
{
AvahiClient.avahi_simple_poll_free(simplePoll);
simplePoll = IntPtr.Zero;
poll = IntPtr.Zero;
}
base.DisposeInternal();
}
internal override bool DeregisterAddressInternal(OMTAddress address)
{
lock (lockSync)
{
OMTDiscoveryEntry entry = GetEntry(address);
if (entry != null)
{
RemoveEntry(entry.Address, true);
OMTLogging.Write("DeRegisterAddress: " + address.ToString(), "OMTDiscoveryAvahi");
return true;
}
return false;
}
}
internal override bool RegisterAddressInternal(OMTAddress address)
{
lock (lockSync)
{
OMTDiscoveryEntry entry = GetEntry(address);
if (entry == null)
{
EntryAvahi ctx = new EntryAvahi(address);
ctx.Group = AvahiClient.avahi_entry_group_new(client, Marshal.GetFunctionPointerForDelegate(entryGroupCallback), IntPtr.Zero);
if (ctx.Group == IntPtr.Zero)
{
OMTLogging.Write("Could not create avahi group", "OMTDiscoveryAvahi");
return false;
}
IntPtr pName = OMTUtils.StringToPtrUTF8(address.ToString());
ushort port = (ushort)address.Port;
int hr = AvahiClient.avahi_entry_group_add_service(ctx.Group, AvahiClient.AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, pName, serviceType, IntPtr.Zero, IntPtr.Zero, port, IntPtr.Zero);
Marshal.FreeHGlobal(pName);
if (hr != 0)
{
OMTLogging.Write("Could not add entry to avahi group", "OMTDiscoveryAvahi");
return false;
}
hr = AvahiClient.avahi_entry_group_commit(ctx.Group);
if (hr != 0)
{
OMTLogging.Write("Could not commit new avahi group", "OMTDiscoveryAvahi");
return false;
}
OMTLogging.Write("RegisterAddress.Success: " + address.ToString(), "OMTDiscoveryAvahi");
ctx.ChangeStatus(OMTDiscoveryEntryStatus.Registered);
AddEntry(ctx);
return true;
}
}
return false;
}
private void ClientCallback(IntPtr s, int state, IntPtr userData)
{
}
private void EntryGroupCallback(IntPtr group, int state, IntPtr userData)
{
}
private void ServiceBrowserCallback(IntPtr b, int iface, int protocol, AvahiBrowserEvent evt, IntPtr name, IntPtr type, IntPtr domain, int flags, IntPtr userData)
{
if (evt == AvahiBrowserEvent.AVAHI_BROWSER_NEW)
{
IntPtr resolver = AvahiClient.avahi_service_resolver_new(client, iface, protocol
, name, type, domain, AvahiClient.AVAHI_PROTO_UNSPEC, 0, Marshal.GetFunctionPointerForDelegate(serviceResolverCallback), IntPtr.Zero);
if (resolver == IntPtr.Zero)
{
OMTLogging.Write("Failure creating resolver", "OMTDiscoveryAvahi");
return;
}
} else if (evt == AvahiBrowserEvent.AVAHI_BROWSER_REMOVE)
{
if (name != IntPtr.Zero)
{
string szName = OMTUtils.PtrToStringUTF8(name);
RemoveDiscoveredEntry(szName);
}
}
}
private void ServiceResolverCallback(IntPtr r, int iface, int protocol, AvahiResolverEvent evt, IntPtr name, IntPtr type, IntPtr domain,
IntPtr host_name, IntPtr a, UInt16 port, IntPtr txt, int flags, IntPtr userData)
{
try
{
if (evt == AvahiResolverEvent.AVAHI_RESOLVER_FOUND)
{
if (name != null)
{
IPAddress ip = null;
if (a != IntPtr.Zero)
{
int proto = Marshal.ReadInt32(a);
if (proto == AVAHI_PROTO_INET)
{
byte[] addr = new byte[4];
Marshal.Copy(a + 4, addr, 0, 4);
ip = new IPAddress(addr);
}
else if (proto == AVAHI_PROTO_INET6)
{
byte[] addr = new byte[16];
Marshal.Copy(a + 4, addr, 0, 16);
ip = new IPAddress(addr, 0);
}
}
string addressName = OMTUtils.PtrToStringUTF8(name);
if (OMTAddress.IsValid(addressName))
{
UpdateDiscoveredEntry(addressName, port, new IPAddress[] { ip });
} else
{
OMTLogging.Write("InvalidAddressReceived: " + addressName, "OMTDiscoveryAvahi");
}
}
}
if (r != IntPtr.Zero)
{
AvahiClient.avahi_service_resolver_free(r);
}
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTDiscoveryAvahi.Resolver");
}
}
}
}

View File

@@ -0,0 +1,431 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace libomtnet.mac
{
internal class DnsSd
{
public const string DLL_PATH = @"libSystem.dylib";
public enum DNSServiceFlags
{
kDNSServiceFlagsMoreComing = 0x1,
/* MoreComing indicates to a callback that at least one more result is
* queued and will be delivered following immediately after this one.
* When the MoreComing flag is set, applications should not immediately
* update their UI, because this can result in a great deal of ugly flickering
* on the screen, and can waste a great deal of CPU time repeatedly updating
* the screen with content that is then immediately erased, over and over.
* Applications should wait until MoreComing is not set, and then
* update their UI when no more changes are imminent.
* When MoreComing is not set, that doesn't mean there will be no more
* answers EVER, just that there are no more answers immediately
* available right now at this instant. If more answers become available
* in the future they will be delivered as usual.
*/
kDNSServiceFlagsAutoTrigger = 0x1,
/* Valid for browses using kDNSServiceInterfaceIndexAny.
* Will auto trigger the browse over AWDL as well once the service is discoveryed
* over BLE.
* This flag is an input value to DNSServiceBrowse(), which is why we can
* use the same value as kDNSServiceFlagsMoreComing, which is an output flag
* for various client callbacks.
*/
kDNSServiceFlagsAdd = 0x2,
kDNSServiceFlagsDefault = 0x4,
/* Flags for domain enumeration and browse/query reply callbacks.
* "Default" applies only to enumeration and is only valid in
* conjunction with "Add". An enumeration callback with the "Add"
* flag NOT set indicates a "Remove", i.e. the domain is no longer
* valid.
*/
kDNSServiceFlagsNoAutoRename = 0x8,
/* Flag for specifying renaming behavior on name conflict when registering
* non-shared records. By default, name conflicts are automatically handled
* by renaming the service. NoAutoRename overrides this behavior - with this
* flag set, name conflicts will result in a callback. The NoAutorename flag
* is only valid if a name is explicitly specified when registering a service
* (i.e. the default name is not used.)
*/
kDNSServiceFlagsShared = 0x10,
kDNSServiceFlagsUnique = 0x20,
/* Flag for registering individual records on a connected
* DNSServiceRef. Shared indicates that there may be multiple records
* with this name on the network (e.g. PTR records). Unique indicates that the
* record's name is to be unique on the network (e.g. SRV records).
*/
kDNSServiceFlagsBrowseDomains = 0x40,
kDNSServiceFlagsRegistrationDomains = 0x80,
/* Flags for specifying domain enumeration type in DNSServiceEnumerateDomains.
* BrowseDomains enumerates domains recommended for browsing, RegistrationDomains
* enumerates domains recommended for registration.
*/
kDNSServiceFlagsLongLivedQuery = 0x100,
/* Flag for creating a long-lived unicast query for the DNSServiceQueryRecord call. */
kDNSServiceFlagsAllowRemoteQuery = 0x200,
/* Flag for creating a record for which we will answer remote queries
* (queries from hosts more than one hop away; hosts not directly connected to the local link).
*/
kDNSServiceFlagsForceMulticast = 0x400,
/* Flag for signifying that a query or registration should be performed exclusively via multicast
* DNS, even for a name in a domain (e.g. foo.apple.com.) that would normally imply unicast DNS.
*/
kDNSServiceFlagsForce = 0x800, // This flag is deprecated.
kDNSServiceFlagsKnownUnique = 0x800,
/*
* Client guarantees that record names are unique, so we can skip sending out initial
* probe messages. Standard name conflict resolution is still done if a conflict is discovered.
* Currently only valid for a DNSServiceRegister call.
*/
kDNSServiceFlagsReturnIntermediates = 0x1000,
/* Flag for returning intermediate results.
* For example, if a query results in an authoritative NXDomain (name does not exist)
* then that result is returned to the client. However the query is not implicitly
* cancelled -- it remains active and if the answer subsequently changes
* (e.g. because a VPN tunnel is subsequently established) then that positive
* result will still be returned to the client.
* Similarly, if a query results in a CNAME record, then in addition to following
* the CNAME referral, the intermediate CNAME result is also returned to the client.
* When this flag is not set, NXDomain errors are not returned, and CNAME records
* are followed silently without informing the client of the intermediate steps.
* (In earlier builds this flag was briefly calledkDNSServiceFlagsReturnCNAME)
*/
kDNSServiceFlagsNonBrowsable = 0x2000,
/* A service registered with the NonBrowsable flag set can be resolved using
* DNSServiceResolve(), but will not be discoverable using DNSServiceBrowse().
* This is for cases where the name is actually a GUID; it is found by other means;
* there is no end-user benefit to browsing to find a long list of opaque GUIDs.
* Using the NonBrowsable flag creates SRV+TXT without the cost of also advertising
* an associated PTR record.
*/
kDNSServiceFlagsShareConnection = 0x4000,
/* For efficiency, clients that perform many concurrent operations may want to use a
* single Unix Domain Socket connection with the background daemon, instead of having a
* separate connection for each independent operation. To use this mode, clients first
* call DNSServiceCreateConnection(&MainRef) to initialize the main DNSServiceRef.
* For each subsequent operation that is to share that same connection, the client copies
* the MainRef, and then passes the address of that copy, setting the ShareConnection flag
* to tell the library that this DNSServiceRef is not a typical uninitialized DNSServiceRef;
* it's a copy of an existing DNSServiceRef whose connection information should be reused.
*
* For example:
*
* DNSServiceErrorType error;
* DNSServiceRef MainRef;
* error = DNSServiceCreateConnection(&MainRef);
* if (error) ...
* DNSServiceRef BrowseRef = MainRef; // Important: COPY the primary DNSServiceRef first...
* error = DNSServiceBrowse(&BrowseRef, kDNSServiceFlagsShareConnection, ...); // then use the copy
* if (error) ...
* ...
* DNSServiceRefDeallocate(BrowseRef); // Terminate the browse operation
* DNSServiceRefDeallocate(MainRef); // Terminate the shared connection
* Also see Point 4.(Don't Double-Deallocate if the MainRef has been Deallocated) in Notes below:
*
* Notes:
*
* 1. Collective kDNSServiceFlagsMoreComing flag
* When callbacks are invoked using a shared DNSServiceRef, the
* kDNSServiceFlagsMoreComing flag applies collectively to *all* active
* operations sharing the same parent DNSServiceRef. If the MoreComing flag is
* set it means that there are more results queued on this parent DNSServiceRef,
* but not necessarily more results for this particular callback function.
* The implication of this for client programmers is that when a callback
* is invoked with the MoreComing flag set, the code should update its
* internal data structures with the new result, and set a variable indicating
* that its UI needs to be updated. Then, later when a callback is eventually
* invoked with the MoreComing flag not set, the code should update *all*
* stale UI elements related to that shared parent DNSServiceRef that need
* updating, not just the UI elements related to the particular callback
* that happened to be the last one to be invoked.
*
* 2. Canceling operations and kDNSServiceFlagsMoreComing
* Whenever you cancel any operation for which you had deferred UI updates
* waiting because of a kDNSServiceFlagsMoreComing flag, you should perform
* those deferred UI updates. This is because, after cancelling the operation,
* you can no longer wait for a callback *without* MoreComing set, to tell
* you do perform your deferred UI updates (the operation has been canceled,
* so there will be no more callbacks). An implication of the collective
* kDNSServiceFlagsMoreComing flag for shared connections is that this
* guideline applies more broadly -- any time you cancel an operation on
* a shared connection, you should perform all deferred UI updates for all
* operations sharing that connection. This is because the MoreComing flag
* might have been referring to events coming for the operation you canceled,
* which will now not be coming because the operation has been canceled.
*
* 3. Only share DNSServiceRef's created with DNSServiceCreateConnection
* Calling DNSServiceCreateConnection(&ref) creates a special shareable DNSServiceRef.
* DNSServiceRef's created by other calls like DNSServiceBrowse() or DNSServiceResolve()
* cannot be shared by copying them and using kDNSServiceFlagsShareConnection.
*
* 4. Don't Double-Deallocate if the MainRef has been Deallocated
* Calling DNSServiceRefDeallocate(ref) for a particular operation's DNSServiceRef terminates
* just that operation. Calling DNSServiceRefDeallocate(ref) for the main shared DNSServiceRef
* (the parent DNSServiceRef, originally created by DNSServiceCreateConnection(&ref))
* automatically terminates the shared connection and all operations that were still using it.
* After doing this, DO NOT then attempt to deallocate any remaining subordinate DNSServiceRef's.
* The memory used by those subordinate DNSServiceRef's has already been freed, so any attempt
* to do a DNSServiceRefDeallocate (or any other operation) on them will result in accesses
* to freed memory, leading to crashes or other equally undesirable results.
*
* 5. Thread Safety
* The dns_sd.h API does not presuppose any particular threading model, and consequently
* does no locking internally (which would require linking with a specific threading library).
* If the client concurrently, from multiple threads (or contexts), calls API routines using
* the same DNSServiceRef, it is the client's responsibility to provide mutual exclusion for
* that DNSServiceRef.
* For example, use of DNSServiceRefDeallocate requires caution. A common mistake is as follows:
* Thread B calls DNSServiceRefDeallocate to deallocate sdRef while Thread A is processing events
* using sdRef. Doing this will lead to intermittent crashes on thread A if the sdRef is used after
* it was deallocated.
* A telltale sign of this crash type is to see DNSServiceProcessResult on the stack preceding the
* actual crash location.
* To state this more explicitly, mDNSResponder does not queue DNSServiceRefDeallocate so
* that it occurs discretely before or after an event is handled.
*/
kDNSServiceFlagsSuppressUnusable = 0x8000,
/*
* This flag is meaningful only in DNSServiceQueryRecord which suppresses unusable queries on the
* wire. If "hostname" is a wide-area unicast DNS hostname (i.e. not a ".local." name)
* but this host has no routable IPv6 address, then the call will not try to look up IPv6 addresses
* for "hostname", since any addresses it found would be unlikely to be of any use anyway. Similarly,
* if this host has no routable IPv4 address, the call will not try to look up IPv4 addresses for
* "hostname".
*/
kDNSServiceFlagsTimeout = 0x10000,
/*
* When kDNServiceFlagsTimeout is passed to DNSServiceQueryRecord or DNSServiceGetAddrInfo, the query is
* stopped after a certain number of seconds have elapsed. The time at which the query will be stopped
* is determined by the system and cannot be configured by the user. The query will be stopped irrespective
* of whether a response was given earlier or not. When the query is stopped, the callback will be called
* with an error code of kDNSServiceErr_Timeout and a NULL sockaddr will be returned for DNSServiceGetAddrInfo
* and zero length rdata will be returned for DNSServiceQueryRecord.
*/
kDNSServiceFlagsIncludeP2P = 0x20000,
/*
* Include P2P interfaces when kDNSServiceInterfaceIndexAny is specified.
* By default, specifying kDNSServiceInterfaceIndexAny does not include P2P interfaces.
*/
kDNSServiceFlagsWakeOnResolve = 0x40000,
/*
* This flag is meaningful only in DNSServiceResolve. When set, it tries to send a magic packet
* to wake up the client.
*/
kDNSServiceFlagsBackgroundTrafficClass = 0x80000,
/*
* This flag is meaningful for Unicast DNS queries. When set, it uses the background traffic
* class for packets that service the request.
*/
kDNSServiceFlagsIncludeAWDL = 0x100000,
/*
* Include AWDL interface when kDNSServiceInterfaceIndexAny is specified.
*/
kDNSServiceFlagsValidate = 0x200000,
/*
* This flag is meaningful in DNSServiceGetAddrInfo and DNSServiceQueryRecord. This is the ONLY flag to be valid
* as an input to the APIs and also an output through the callbacks in the APIs.
*
* When this flag is passed to DNSServiceQueryRecord and DNSServiceGetAddrInfo to resolve unicast names,
* the response will be validated using DNSSEC. The validation results are delivered using the flags field in
* the callback and kDNSServiceFlagsValidate is marked in the flags to indicate that DNSSEC status is also available.
* When the callback is called to deliver the query results, the validation results may or may not be available.
* If it is not delivered along with the results, the validation status is delivered when the validation completes.
*
* When the validation results are delivered in the callback, it is indicated by marking the flags with
* kDNSServiceFlagsValidate and kDNSServiceFlagsAdd along with the DNSSEC status flags (described below) and a NULL
* sockaddr will be returned for DNSServiceGetAddrInfo and zero length rdata will be returned for DNSServiceQueryRecord.
* DNSSEC validation results are for the whole RRSet and not just individual records delivered in the callback. When
* kDNSServiceFlagsAdd is not set in the flags, applications should implicitly assume that the DNSSEC status of the
* RRSet that has been delivered up until that point is not valid anymore, till another callback is called with
* kDNSServiceFlagsAdd and kDNSServiceFlagsValidate.
*
* The following four flags indicate the status of the DNSSEC validation and marked in the flags field of the callback.
* When any of the four flags is set, kDNSServiceFlagsValidate will also be set. To check the validation status, the
* other applicable output flags should be masked. See kDNSServiceOutputFlags below.
*/
kDNSServiceFlagsSecure = 0x200010,
/*
* The response has been validated by verifying all the signatures in the response and was able to
* build a successful authentication chain starting from a known trust anchor.
*/
kDNSServiceFlagsInsecure = 0x200020,
/*
* A chain of trust cannot be built starting from a known trust anchor to the response.
*/
kDNSServiceFlagsBogus = 0x200040,
/*
* If the response cannot be verified to be secure due to expired signatures, missing signatures etc.,
* then the results are considered to be bogus.
*/
kDNSServiceFlagsIndeterminate = 0x200080,
/*
* There is no valid trust anchor that can be used to determine whether a response is secure or not.
*/
kDNSServiceFlagsUnicastResponse = 0x400000,
/*
* Request unicast response to query.
*/
kDNSServiceFlagsValidateOptional = 0x800000,
/*
* This flag is identical to kDNSServiceFlagsValidate except for the case where the response
* cannot be validated. If this flag is set in DNSServiceQueryRecord or DNSServiceGetAddrInfo,
* the DNSSEC records will be requested for validation. If they cannot be received for some reason
* during the validation (e.g., zone is not signed, zone is signed but cannot be traced back to
* root, recursive server does not understand DNSSEC etc.), then this will fallback to the default
* behavior where the validation will not be performed and no DNSSEC results will be provided.
*
* If the zone is signed and there is a valid path to a known trust anchor configured in the system
* and the application requires DNSSEC validation irrespective of the DNSSEC awareness in the current
* network, then this option MUST not be used. This is only intended to be used during the transition
* period where the different nodes participating in the DNS resolution may not understand DNSSEC or
* managed properly (e.g. missing DS record) but still want to be able to resolve DNS successfully.
*/
kDNSServiceFlagsWakeOnlyService = 0x1000000,
/*
* This flag is meaningful only in DNSServiceRegister. When set, the service will not be registered
* with sleep proxy server during sleep.
*/
kDNSServiceFlagsThresholdOne = 0x2000000,
kDNSServiceFlagsThresholdFinder = 0x4000000,
kDNSServiceFlagsThresholdReached = kDNSServiceFlagsThresholdOne,
/*
* kDNSServiceFlagsThresholdOne is meaningful only in DNSServiceBrowse. When set,
* the system will stop issuing browse queries on the network once the number
* of answers returned is one or more. It will issue queries on the network
* again if the number of answers drops to zero.
* This flag is for Apple internal use only. Third party developers
* should not rely on this behavior being supported in any given software release.
*
* kDNSServiceFlagsThresholdFinder is meaningful only in DNSServiceBrowse. When set,
* the system will stop issuing browse queries on the network once the number
* of answers has reached the threshold set for Finder.
* It will issue queries on the network again if the number of answers drops below
* this threshold.
* This flag is for Apple internal use only. Third party developers
* should not rely on this behavior being supported in any given software release.
*
* When kDNSServiceFlagsThresholdReached is set in the client callback add or remove event,
* it indicates that the browse answer threshold has been reached and no
* browse requests will be generated on the network until the number of answers falls
* below the threshold value. Add and remove events can still occur based
* on incoming Bonjour traffic observed by the system.
* The set of services return to the client is not guaranteed to represent the
* entire set of services present on the network once the threshold has been reached.
*
* Note, while kDNSServiceFlagsThresholdReached and kDNSServiceFlagsThresholdOne
* have the same value, there isn't a conflict because kDNSServiceFlagsThresholdReached
* is only set in the callbacks and kDNSServiceFlagsThresholdOne is only set on
* input to a DNSServiceBrowse call.
*/
kDNSServiceFlagsDenyCellular = 0x8000000,
/*
* This flag is meaningful only for Unicast DNS queries. When set, the kernel will restrict
* DNS resolutions on the cellular interface for that request.
*/
kDNSServiceFlagsServiceIndex = 0x10000000,
/*
* This flag is meaningful only for DNSServiceGetAddrInfo() for Unicast DNS queries.
* When set, DNSServiceGetAddrInfo() will interpret the "interfaceIndex" argument of the call
* as the "serviceIndex".
*/
kDNSServiceFlagsDenyExpensive = 0x20000000,
/*
* This flag is meaningful only for Unicast DNS queries. When set, the kernel will restrict
* DNS resolutions on interfaces defined as expensive for that request.
*/
kDNSServiceFlagsPathEvaluationDone = 0x40000000
/*
* This flag is meaningful for only Unicast DNS queries.
* When set, it indicates that Network PathEvaluation has already been performed.
*/
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void DNSServiceBrowseReply(IntPtr sdRef, DNSServiceFlags flags, uint interfaceIndex, int errorCode, IntPtr serviceName, IntPtr regType, IntPtr replyDomain, IntPtr context);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void DNSServiceResolveReply(IntPtr sdRef, DNSServiceFlags flags, uint interfaceIndex, int errorCode, IntPtr fullName, IntPtr hostTarget, UInt16 port, UInt16 txtLen, IntPtr txtRecord, IntPtr context);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void DNSServiceRegisterReply(IntPtr sdRef, DNSServiceFlags flags, int errorCode, IntPtr name, IntPtr regtype, IntPtr domain, IntPtr context);
[DllImport(DLL_PATH)]
public static extern int DNSServiceBrowse(ref IntPtr sdRef, DNSServiceFlags flags, uint interfaceIndex, IntPtr regType, IntPtr domain, [MarshalAs(UnmanagedType.FunctionPtr)] DNSServiceBrowseReply callback, IntPtr context);
[DllImport(DLL_PATH)]
public static extern int DNSServiceProcessResult(IntPtr sdRef);
[DllImport(DLL_PATH)]
public static extern int DNSServiceResolve(ref IntPtr sdRef, DNSServiceFlags flags, uint interfaceIndex, IntPtr name, IntPtr regType, IntPtr domain, [MarshalAs(UnmanagedType.FunctionPtr)] DNSServiceResolveReply callback, IntPtr context);
[DllImport(DLL_PATH)]
public static extern void DNSServiceRefDeallocate(IntPtr sdRef);
[DllImport(DLL_PATH)]
public static extern int DNSServiceRegister(ref IntPtr sdRef, uint interfaceIndex, DNSServiceFlags flags,IntPtr name,IntPtr regType, IntPtr domain, IntPtr host, UInt16 port, UInt16 txtLen, IntPtr txtRecord, IntPtr callback, IntPtr context);
}
}

View File

@@ -0,0 +1,121 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace libomtnet.mac
{
internal class MacPlatform : OMTPlatform
{
private const int RTLD_NOW = 2;
private const int RTLD_GLOBAL = 8;
[DllImport("libdl.dylib")]
static extern IntPtr dlopen(string filename, int flags);
[DllImport("libc")]
private static extern uint getuid();
[DllImport("libc")]
private static extern IntPtr getpwuid(uint uid);
[DllImport("libc")]
private static extern int gethostname(IntPtr name, IntPtr size);
public override string GetMachineName()
{
int len = 4096;
IntPtr buf = Marshal.AllocHGlobal(len);
try
{
int result = gethostname(buf, (IntPtr)len);
if (result == 0)
{
string name = OMTUtils.PtrToStringUTF8(buf);
if (!String.IsNullOrEmpty(name))
{
return name.ToUpper();
}
}
}
finally
{
Marshal.FreeHGlobal(buf);
}
OMTLogging.Write("Unable to retrieve full hostname", "MacPlatform");
return base.GetMachineName();
}
public override string GetStoragePath()
{
string sz = Environment.GetEnvironmentVariable("OMT_STORAGE_PATH");
if (!String.IsNullOrEmpty(sz)) return sz;
sz = GetRealUserHome();
if (!String.IsNullOrEmpty(sz)) {
return Path.Combine(sz, ".OMT");
}
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),".OMT");
}
/// <summary>
/// Attempts to get the real user home directory by manually reading pointer offset into passwd struct.
/// </summary>
/// <returns>Real user home directory path or null if unavailable.</returns>
private static string GetRealUserHome()
{
try
{
uint uid = getuid();
IntPtr pwdPtr = getpwuid(uid);
if (pwdPtr == IntPtr.Zero) return null;
int offsetOfPwDir = IntPtr.Size * 6;
IntPtr pwDirPtr = Marshal.ReadIntPtr(pwdPtr, offsetOfPwDir);
if (pwDirPtr == IntPtr.Zero) return null;
string home = Marshal.PtrToStringAnsi(pwDirPtr);
return home;
}
catch
{
return null;
}
}
public override IntPtr OpenLibrary(string filename)
{
return dlopen(filename, RTLD_NOW | RTLD_GLOBAL);
}
protected override string GetLibraryExtension()
{
return ".dylib";
}
}
}

View File

@@ -0,0 +1,341 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using libomtnet.linux;
using System;
using System.Collections.Generic;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using static libomtnet.mac.DnsSd;
namespace libomtnet.mac
{
/// <summary>
/// DnsSd/Bonjour based Discovery implementation for MacOS
/// Issues:
/// 1. Windows requests for service responses using unicast (QU), but there can be multiple services on a computer listening on port 5353 such as Chrome
/// that will result in these responses being missed by the desired client.
/// Solution: On all platforms ensure we refresh periodically so that multicast packets are sent.
/// </summary>
internal class OMTDiscoveryDnsSd :OMTDiscovery
{
private DnsSd.DNSServiceBrowseReply browseCallback;
private DnsSd.DNSServiceResolveReply resolveCallback;
private class EntryDnsSd : OMTDiscoveryEntry
{
public EntryDnsSd(OMTAddress address) : base(address) { }
public IntPtr sdRef;
public ushort RegisteredPort;
public string RegisteredName;
public void CancelRequest()
{
if (sdRef != IntPtr.Zero)
{
DnsSd.DNSServiceRefDeallocate(sdRef);
sdRef = IntPtr.Zero;
}
}
protected override void DisposeInternal()
{
CancelRequest();
base.DisposeInternal();
}
}
private List<OMTAddress> registeredAddresses = new List<OMTAddress>();
private IntPtr browseRef;
private Thread processingThread;
private Timer refreshTimer;
private bool processing;
internal OMTDiscoveryDnsSd()
{
BeginDNSBrowse();
}
internal void BeginDNSBrowse()
{
browseCallback = new DnsSd.DNSServiceBrowseReply(OnBrowse);
resolveCallback = new DnsSd.DNSServiceResolveReply(OnResolve);
IntPtr pType = OMTUtils.StringToPtrUTF8("_omt._tcp");
int hr = DnsSd.DNSServiceBrowse(ref browseRef, 0, 0,pType, IntPtr.Zero, browseCallback, IntPtr.Zero);
if (hr == 0)
{
OMTLogging.Write("BeginDNSBrowse.OK", "OMTDiscoveryDnsSd");
processingThread = new Thread(OnProcessThread);
processingThread.IsBackground = true;
processing = true;
processingThread.Start();
}
else
{
OMTLogging.Write("BeginDNSBrowse.Error: " + hr, "OMTDiscoveryDnsSd");
}
Marshal.FreeHGlobal(pType);
}
private void OnProcessThread(object state)
{
try
{
while (processing)
{
if (browseRef == IntPtr.Zero) return;
int hr = DnsSd.DNSServiceProcessResult(browseRef);
if (hr != 0)
{
OMTLogging.Write("DNSBrowse.ProcessingError: " + hr, "OMTDiscoveryDnsSd");
return;
}
}
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTDiscoveryDnsSd.ProcessThread");
}
}
internal void EndDnsBrowse()
{
if (browseRef != null)
{
processing = false;
DnsSd.DNSServiceRefDeallocate(browseRef);
browseRef = IntPtr.Zero;
processingThread.Join();
processingThread = null;
OMTLogging.Write("EndDNSBrowse", "OMTDiscoveryDnsSd");
}
}
internal override bool DeregisterAddressInternal(OMTAddress address)
{
lock (lockSync)
{
OMTDiscoveryEntry entry = GetEntry(address);
if (entry != null)
{
RemoveEntry(entry.Address, true);
OMTLogging.Write("DeRegisterAddress: " + address.ToString(), "OMTDiscoveryDnsSd");
if (GetRegisteredEntryCount() == 0)
{
//StopRefreshTimer();
}
return true;
}
}
return false;
}
private byte[] CreateTXTRecord(string record)
{
byte[] data = UTF8Encoding.UTF8.GetBytes(" " + record);
data[0] = (byte)(data.Length - 1);
return data;
}
internal override bool RegisterAddressInternal(OMTAddress address)
{
lock (lockSync)
{
OMTDiscoveryEntry ctx = GetEntry(address);
if (ctx == null)
{
string addressName = address.ToString();
ushort port = (ushort)address.Port;
byte[] b = BitConverter.GetBytes(port);
Array.Reverse(b);
port = BitConverter.ToUInt16(b, 0);
IntPtr pType = OMTUtils.StringToPtrUTF8("_omt._tcp");
IntPtr pAddress = OMTUtils.StringToPtrUTF8(addressName);
IntPtr newRequest = IntPtr.Zero;
int hr = DnsSd.DNSServiceRegister(ref newRequest, 0, 0, pAddress, pType, IntPtr.Zero, IntPtr.Zero, port, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if (hr == 0)
{
EntryDnsSd sd = new EntryDnsSd(address);
sd.sdRef = newRequest;
sd.RegisteredPort = port;
sd.RegisteredName = addressName;
sd.ChangeStatus(OMTDiscoveryEntryStatus.Registered);
AddEntry(sd);
OMTLogging.Write("RegisterAddress: " + address.ToString(), "OMTDiscoveryDnsSd");
//StartRefreshTimer(); //No longer required since moving to QM requests on Win32
}
else
{
OMTLogging.Write("RegisterAddress.Error: " + hr, "OMTDiscoveryDnsSd");
}
Marshal.FreeHGlobal(pType);
Marshal.FreeHGlobal(pAddress);
return true;
}
return false;
}
}
private void StartRefreshTimer()
{
if (refreshTimer == null)
{
refreshTimer = new Timer(RefreshTimerCallback, null, 10000, 10000);
OMTLogging.Write("StartRefreshTimer", "OMTDiscoveryDnsSd");
}
}
private void StopRefreshTimer()
{
if (refreshTimer != null)
{
refreshTimer.Dispose();
refreshTimer = null;
OMTLogging.Write("StopRefreshTimer", "OMTDiscoveryDnsSd");
}
}
private void RefreshTimerCallback(object state)
{
try
{
lock (lockSync)
{
foreach (OMTDiscoveryEntry entry in entries)
{
if (entry.Status == OMTDiscoveryEntryStatus.Registered)
{
EntryDnsSd sd = (EntryDnsSd)entry;
if (sd.sdRef != IntPtr.Zero)
{
IntPtr pType = OMTUtils.StringToPtrUTF8("_omt._tcp");
IntPtr pAddress = OMTUtils.StringToPtrUTF8(sd.RegisteredName);
DnsSd.DNSServiceRefDeallocate(sd.sdRef);
sd.sdRef = IntPtr.Zero;
int hr = DnsSd.DNSServiceRegister(ref sd.sdRef, 0, 0, pAddress, pType, IntPtr.Zero, IntPtr.Zero, sd.RegisteredPort, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if (hr != 0)
{
OMTLogging.Write("RefreshAddress.Error: " + hr, "OMTDiscoveryDnsSd");
}
Marshal.FreeHGlobal(pType);
Marshal.FreeHGlobal(pAddress);
}
}
}
}
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTDiscoveryDnsSd");
}
}
protected override void DisposeInternal()
{
EndDnsBrowse();
//StopRefreshTimer();
base.DisposeInternal();
}
void OnResolve(IntPtr sdRef, DNSServiceFlags flags, uint interfaceIndex, int errorCode, IntPtr fullName, IntPtr hostTarget, UInt16 port, UInt16 txtLen, IntPtr txtRecord, IntPtr context)
{
try
{
string addressName = "";
if (context != IntPtr.Zero)
{
addressName = OMTUtils.PtrToStringUTF8(context);
}
if (errorCode == 0)
{
string szHostTarget = OMTUtils.PtrToStringUTF8(hostTarget);
string szFullName = OMTUtils.PtrToStringUTF8(fullName);
if (OMTAddress.IsValid(addressName)) {
byte[] b = BitConverter.GetBytes(port);
Array.Reverse(b);
port = BitConverter.ToUInt16(b, 0);
IPHostEntry a = Dns.GetHostEntry(szHostTarget);
UpdateDiscoveredEntry(addressName, port, a.AddressList);
} else
{
OMTLogging.Write("InvalidAddressReceived: " + addressName, "OMTDiscoveryDnsSd");
}
}
else
{
OMTLogging.Write("OnResolve.Error: " + errorCode, "OMTDiscoveryDnsSd");
}
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTDiscoveryDnsSd");
}
}
void OnBrowse(IntPtr sdRef, DNSServiceFlags flags, uint interfaceIndex, int errorCode, IntPtr serviceName, IntPtr regType, IntPtr replyDomain, IntPtr context)
{
try
{
if (errorCode == 0)
{
if (serviceName != IntPtr.Zero && regType != IntPtr.Zero && replyDomain != IntPtr.Zero)
{
string szServiceName = OMTUtils.PtrToStringUTF8(serviceName);
if (flags.HasFlag(DnsSd.DNSServiceFlags.kDNSServiceFlagsAdd))
{
IntPtr sdRRef = IntPtr.Zero;
IntPtr ctx = OMTUtils.StringToPtrUTF8(szServiceName);
int hr = DnsSd.DNSServiceResolve(ref sdRRef, 0, 0, serviceName, regType, replyDomain, resolveCallback, ctx);
if (hr == 0)
{
hr = DnsSd.DNSServiceProcessResult(sdRRef);
}
Marshal.FreeHGlobal(ctx);
}
else
{
RemoveDiscoveredEntry(szServiceName);
}
}
}
else
{
OMTLogging.Write("OnBrowse.Error: " + errorCode, "OMTDiscoveryDnsSd");
}
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTDiscoveryDnsSd");
}
}
}
}

View File

@@ -0,0 +1,32 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
namespace libomtnet.mac
{
internal class OMTDiscoveryMac : OMTDiscoveryDnsSd
{
}
}

View File

@@ -0,0 +1,232 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading;
namespace libomtnet.src.mdns
{
/// <summary>
/// This class periodically sends out a MDNS QM (multicast) query for the service type.
/// This overcomes a limitation of DNS-SD API on Windows, where it will stop sending out queries after some time.
/// </summary>
internal class MDNSClient : OMTBase
{
private const int DEFAULT_PORT = 5353;
private const string MULTICAST_ADDRESS = "224.0.0.251";
private const string MULTICAST_ADDRESS_V6 = "ff02::fb";
private const int SEND_INTERVAL_MILLISECONDS = 8000;
private Socket[] sockets;
private Timer refreshTimer;
private byte[] query;
private IPEndPoint mdns4;
private IPEndPoint mdns6;
private object lockSync = new object();
public MDNSClient(string serviceType)
{
query = CreateDNSQuery(serviceType);
mdns4 = new IPEndPoint(IPAddress.Parse(MULTICAST_ADDRESS), DEFAULT_PORT);
mdns6 = new IPEndPoint(IPAddress.Parse(MULTICAST_ADDRESS_V6), DEFAULT_PORT);
sockets = CreateMulticastSockets();
refreshTimer = new Timer(RefreshTimerCallback, null, 0, SEND_INTERVAL_MILLISECONDS);
}
private void SendQueryToSocket(Socket s)
{
if (s.AddressFamily == AddressFamily.InterNetworkV6)
{
s.SendTo(query, mdns6);
}
else
{
s.SendTo(query, mdns4);
}
}
private Socket[] CreateMulticastSockets()
{
List<Socket> l = new List<Socket>();
Socket s;
NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
foreach (NetworkInterface n in nics)
{
if (n.NetworkInterfaceType != NetworkInterfaceType.Loopback)
{
if (n.SupportsMulticast)
{
IPInterfaceProperties ip = n.GetIPProperties();
if (ip != null)
{
IPv4InterfaceProperties ipv4 = ip.GetIPv4Properties();
if (ipv4 != null)
{
s = CreateMulticastSocket(AddressFamily.InterNetwork, ipv4.Index, DEFAULT_PORT);
if (s != null) l.Add(s);
}
IPv6InterfaceProperties ipv6 = ip.GetIPv6Properties();
if (ipv6 != null)
{
s = CreateMulticastSocket(AddressFamily.InterNetworkV6, ipv6.Index, DEFAULT_PORT);
if (s != null) l.Add(s);
}
}
}
}
}
return l.ToArray();
}
private byte[] CreateDNSQuery(string serviceType)
{
byte[] sn = StringToDNS(serviceType);
int messageLength = sn.Length + 16;
byte[] query = new byte[messageLength];
int pos = 5;
query[pos] = 1;
pos = 12;
Buffer.BlockCopy(sn, 0, query, pos, sn.Length);
pos += sn.Length;
pos += 1;
query[pos] = 12;
pos += 2;
query[pos] = 1;
return query;
}
private Socket CreateMulticastSocket(AddressFamily af, int interfaceIndex, int port)
{
try
{
Socket socket = new Socket(af, SocketType.Dgram, ProtocolType.Udp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
if (af == AddressFamily.InterNetworkV6)
{
socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.MulticastInterface, interfaceIndex);
socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.MulticastLoopback, false);
socket.Bind(new IPEndPoint(IPAddress.IPv6Any, DEFAULT_PORT));
}
else
{
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastInterface, IPAddress.HostToNetworkOrder(interfaceIndex));
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastLoopback, false);
socket.Bind(new IPEndPoint(IPAddress.Any, DEFAULT_PORT));
}
return socket;
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "MDNSClient");
}
return null;
}
private byte[] StringToDNS(string str)
{
using (MemoryStream m = new MemoryStream())
{
string[] strs = str.Split('.');
foreach (string s in strs)
{
byte[] b = ASCIIEncoding.ASCII.GetBytes(s);
m.WriteByte((byte)b.Length);
m.Write(b, 0, b.Length);
}
m.WriteByte(0);
return m.ToArray();
}
}
private void RefreshTimerCallback(object state)
{
try
{
if (Exiting) return;
lock (lockSync)
{
if (sockets != null)
{
foreach (Socket s in sockets)
{
try
{
SendQueryToSocket(s);
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "MDNSClient");
List<Socket> list = new List<Socket>();
list.AddRange(sockets);
list.Remove(s);
sockets = list.ToArray();
OMTLogging.Write("Removed failed socket", "MDNSClient");
break;
}
}
}
}
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "MDNSClient");
}
}
protected override void DisposeInternal()
{
try
{
if (refreshTimer != null)
{
refreshTimer.Dispose();
refreshTimer = null;
}
if (sockets != null)
{
lock (lockSync)
{
foreach (Socket s in sockets)
{
s.Close();
}
}
sockets = null;
}
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "MDNSClient");
}
base.DisposeInternal();
}
}
}

View File

@@ -0,0 +1,194 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Runtime.InteropServices;
using System.Threading;
using System.Xml;
namespace libomtnet
{
/// <summary>
/// This is an internal class used to manage connection to the OMT Discovery Server
/// This should not be used directly by client apps, and is declared public for internal testing purposes only.
/// </summary>
public class OMTDiscoveryClient : OMTBase
{
private OMTReceive client = null;
private OMTDiscovery discovery = null;
private Thread processingThread = null;
private bool threadExit = false;
public OMTDiscoveryClient(string address, OMTDiscovery discovery)
{
this.discovery = discovery;
this.client = new OMTReceive(address, this);
StartClient();
OMTLogging.Write("Started: " + address, "OMTDiscoveryClient");
}
private void StartClient()
{
if (processingThread == null)
{
threadExit = false;
processingThread = new Thread(ProcessThread);
processingThread.IsBackground = true;
processingThread.Start();
}
}
private void StopClient()
{
if (processingThread != null)
{
threadExit = true;
processingThread.Join();
processingThread = null;
}
}
internal void SendAddress(OMTAddress address)
{
try
{
string xml = address.ToXML();
int bytes = client.SendMetadata(new OMTMetadata(0, xml));
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTDiscoveryClient");
}
}
internal void SendAll()
{
try
{
OMTAddress[] addresses = discovery.GetAddressesInternal();
if (addresses != null)
{
foreach (OMTAddress a in addresses)
{
SendAddress(a);
}
}
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTDiscoveryClient");
}
}
internal void Connected()
{
try
{
OMTLogging.Write("Connected to server", "OMTDiscoveryClient");
SendAll();
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTDiscoveryClient");
}
}
internal void Disconnected()
{
try
{
OMTLogging.Write("Disconnected from server", "OMTDiscoveryClient");
discovery.RemoveServerAddresses();
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTDiscoveryClient");
}
}
private void ProcessThread()
{
try
{
OMTMetadata frame = null;
while (threadExit == false)
{
if (client.Receive(400, ref frame))
{
if (frame != null)
{
try
{
OMTAddress a = OMTAddress.FromXML(frame.XML);
if (a != null)
{
if (a.removed)
{
discovery.RemoveEntry(a, true);
OMTLogging.Write("RemovedFromServer: " + a.ToString(), "OMTDiscoveryClient");
}
else
{
OMTLogging.Write("NewFromServer: " + a.ToString(), "OMTDiscoveryClient");
OMTDiscoveryEntry e = discovery.UpdateDiscoveredEntry(a.ToString(), a.Port, a.Addresses);
if (e != null)
{
e.FromServer = true;
}
}
} else
{
OMTLogging.Write("Invalid XML Received: " + frame.XML, "OMTDiscoveryClient");
}
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTDiscoveryClient");
}
}
}
}
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTDiscoveryClient");
}
}
protected override void DisposeInternal()
{
StopClient();
if (client != null)
{
client.Dispose();
client = null;
}
discovery = null;
base.DisposeInternal();
}
}
}

View File

@@ -0,0 +1,243 @@
/*
* MIT License
*
* Copyright (c) 2025 Open Media Transport Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using System.Xml;
namespace libomtnet
{
public class OMTDiscoveryServer : OMTBase
{
private OMTSend send;
private Thread processingThread;
private bool threadExit = false;
private List<AddressEntry> addresses;
private class AddressEntry
{
public OMTAddress Address;
public IPEndPoint EndPoint;
}
public OMTDiscoveryServer(IPEndPoint endpoint)
{
addresses = new List<AddressEntry>();
send = new OMTSend(endpoint, this);
}
public void StartServer()
{
if (processingThread == null)
{
threadExit = false;
processingThread = new Thread(ProcessThread);
processingThread.IsBackground = true;
processingThread.Start();
}
}
public void StopServer()
{
if (processingThread != null)
{
threadExit = true;
processingThread.Join();
processingThread = null;
}
}
private AddressEntry GetEntry(string fullName, int port)
{
lock (addresses)
{
foreach (AddressEntry address in addresses)
{
if (address.Address.ToString().Equals(fullName))
{
if (address.Address.Port == port)
{
return address;
}
}
}
}
return null;
}
private void RemoveEntriesByEndPoint(IPEndPoint endpoint)
{
lock (addresses)
{
List<AddressEntry> toRemove = new List<AddressEntry>();
string match = endpoint.ToString();
foreach (AddressEntry address in addresses)
{
if (address.EndPoint.ToString() == match)
{
toRemove.Add(address);
}
}
foreach (AddressEntry address in toRemove)
{
RemoveEntry(address, endpoint);
}
}
}
private void AddEntry(OMTAddress address, IPEndPoint endpoint)
{
lock (addresses)
{
AddressEntry entry = new AddressEntry();
entry.Address = address;
entry.EndPoint = endpoint;
addresses.Add(entry);
SendEntry(entry, null);
OMTLogging.Write("Added " + address.ToString() + " From " + endpoint.ToString(), "OMTDiscoveryServer");
Console.WriteLine(endpoint.ToString() + " ADDED " + address.ToString());
}
}
private void RemoveEntry(AddressEntry entry, IPEndPoint endpoint)
{
lock (addresses)
{
addresses.Remove(entry);
entry.Address.removed = true;
SendEntry(entry, null);
OMTLogging.Write("Removed " + entry.Address.ToString() + " From " + endpoint.ToString(), "OMTDiscoveryServer");
Console.WriteLine(endpoint.ToString() + " REMOVED " + entry.Address.ToString());
}
}
private void SendEntry(AddressEntry entry, IPEndPoint endpoint)
{
string xml = entry.Address.ToXML();
send.SendMetadata(new OMTMetadata(0, xml), endpoint);
}
private void SendAllToEndpoint(IPEndPoint endpoint)
{
lock (addresses)
{
foreach (AddressEntry entry in addresses)
{
SendEntry(entry, endpoint);
}
}
}
internal void Connected(IPEndPoint endpoint)
{
try
{
SendAllToEndpoint(endpoint);
Console.WriteLine("Connected: " + endpoint.ToString());
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTDiscoveryServer");
}
}
internal void Disconnected(IPEndPoint endpoint)
{
try
{
RemoveEntriesByEndPoint(endpoint);
Console.WriteLine("Disconnected: " + endpoint.ToString());
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTDiscoveryServer");
}
}
private void ProcessThread()
{
try
{
OMTMetadata frame = null;
XmlDocument xml = new XmlDocument();
while (threadExit == false)
{
if (send.Receive(100, ref frame))
{
try
{
if (frame != null)
{
OMTAddress a = OMTAddress.FromXML(frame.XML);
if (a != null)
{
AddressEntry entry = GetEntry(a.ToString(), a.Port);
if (entry == null)
{
if (!a.removed)
{
a.ClearAddresses(); //Any IP addresses provided by client (typically loopback) are cleared so only detected IP is used.
a.AddAddress(frame.Endpoint.Address);
AddEntry(a, frame.Endpoint);
}
} else
{
if (a.removed)
{
RemoveEntry(entry, frame.Endpoint);
}
}
} else
{
OMTLogging.Write("Invalid XML Received: " + frame.XML, "OMTDiscoveryServer");
}
}
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTDiscoveryServer");
}
}
}
}
catch (Exception ex)
{
OMTLogging.Write(ex.ToString(), "OMTDiscoveryServer");
}
}
protected override void DisposeInternal()
{
StopServer();
if (send != null)
{
send.Dispose();
send = null;
}
base.DisposeInternal();
}
}
}