Add project files.

This commit is contained in:
Daniel Clark
2022-11-01 15:26:08 +00:00
parent daa7b59f22
commit 7e4f880821
408 changed files with 16863 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows10.0.18362.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Rssdp" Version="4.0.4" />
<PackageReference Include="System.Drawing.Common" Version="7.0.0-rc.2.22472.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\UI\UI\UI.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Core">
<HintPath>..\..\..\..\..\Downloads\AForge.NET-master (1)\AForge.NET-master\Sources\Video.DirectShow\bin\Release\net5.0-windows\Core.dll</HintPath>
</Reference>
<Reference Include="Video">
<HintPath>..\..\..\..\..\Downloads\AForge.NET-master (1)\AForge.NET-master\Sources\Video.DirectShow\bin\Release\net5.0-windows\Video.dll</HintPath>
</Reference>
<Reference Include="Video.DirectShow">
<HintPath>..\..\..\..\..\Downloads\AForge.NET-master (1)\AForge.NET-master\Sources\Video.DirectShow\bin\Release\net5.0-windows\Video.DirectShow.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
+5
View File
@@ -0,0 +1,5 @@
using System.Drawing;
namespace TheXamlGuy.Media.Capture;
public record CapturedPhoto(Bitmap Photo, int Width, int Height);
+7
View File
@@ -0,0 +1,7 @@
namespace TheXamlGuy.Media.Capture
{
public interface ILowLagPhotoCapture
{
Task<CapturedPhoto?> CaptureAsync();
}
}
+15
View File
@@ -0,0 +1,15 @@
namespace TheXamlGuy.Media.Capture
{
public interface IMediaCapture
{
IMediaFrameSource? FrameSource { get; }
Task<IMediaFrameReader?> CreateFrameReaderAsync();
Task<IMediaFrameReader?> CreateFrameReaderAsync(IMediaFrameSource frameSource);
void Initialize(IMediaCaptureInitializationSettings initializationSettings);
Task<LowLagPhotoCapture?> PrepareLowLagPhotoCaptureAsync();
}
}
@@ -0,0 +1,6 @@
namespace TheXamlGuy.Media.Capture;
public interface IMediaCaptureInitializationSettings
{
IMediaFrameSource? Source { get; set; }
}
+14
View File
@@ -0,0 +1,14 @@
using TheXamlGuy.UI;
namespace TheXamlGuy.Media.Capture;
public interface IMediaFrameReader : IDisposable
{
event TypedEventHandler<IMediaFrameReader, MediaFrameArrivedEventArgs>? FrameArrived;
Task StartAsync();
Task StopAsync();
Task<MediaFrame?> TryAcquireLatestFrameAsync();
}
+14
View File
@@ -0,0 +1,14 @@
namespace TheXamlGuy.Media.Capture;
public interface IMediaFrameSource
{
MediaFrameFormat CurrentFormat { get; }
string DisplayName { get; }
MediaFrameSourceInfo Info { get; }
IReadOnlyList<MediaFrameFormat> SupportedFormats { get; }
void SetFormat(MediaFrameFormat format);
}
+13
View File
@@ -0,0 +1,13 @@
namespace TheXamlGuy.Media.Capture
{
public interface IRemoteMediaCapture
{
IRemoteMediaFrameSource? FrameSource { get; }
Task<IRemoteMediaFrameReader?> CreateFrameReaderAsync(IRemoteMediaFrameSource frameSource);
void Initialize(IRemoteMediaCaptureInitializationSettings initializationSettings);
Task<LowLagPhotoCapture?> PrepareLowLagPhotoCaptureAsync();
}
}
@@ -0,0 +1,6 @@
namespace TheXamlGuy.Media.Capture;
public interface IRemoteMediaCaptureInitializationSettings
{
IRemoteMediaFrameSource? Source { get; set; }
}
+6
View File
@@ -0,0 +1,6 @@
namespace TheXamlGuy.Media.Capture;
public interface IRemoteMediaFrameReader : IMediaFrameReader
{
}
@@ -0,0 +1,6 @@
namespace TheXamlGuy.Media.Capture;
internal interface IRemoteMediaFrameReaderFactory
{
Func<IRemoteMediaFrameReader> Factory { get; }
}
+8
View File
@@ -0,0 +1,8 @@
namespace TheXamlGuy.Media.Capture;
public interface IRemoteMediaFrameSource
{
string DisplayName { get; }
MediaFrameSourceInfo Info { get; }
}
@@ -0,0 +1,10 @@
namespace TheXamlGuy.Media.Capture;
public interface IRemoteMediaFrameSourceDescriptor
{
string DisplayName { get; }
Func<IRemoteMediaFrameReader> FrameReaderFactory { get; }
string Id { get; }
}
+21
View File
@@ -0,0 +1,21 @@
namespace TheXamlGuy.Media.Capture;
public class LowLagPhotoCapture : ILowLagPhotoCapture
{
private readonly IMediaFrameReader frameReader;
internal LowLagPhotoCapture(IMediaFrameReader frameReader)
{
this.frameReader = frameReader;
}
public async Task<CapturedPhoto?> CaptureAsync()
{
if (await frameReader.TryAcquireLatestFrameAsync() is MediaFrame frame)
{
return new CapturedPhoto(frame.Bitmap, frame.Width, frame.Height);
}
return await Task.FromResult<CapturedPhoto?>(default);
}
}
+82
View File
@@ -0,0 +1,82 @@
namespace TheXamlGuy.Media.Capture;
public class MediaCapture : IMediaCapture
{
private readonly Dictionary<IMediaFrameSource, IMediaFrameReader> frameReaderCache = new();
public IMediaFrameSource? FrameSource { get; private set; }
public async Task<IMediaFrameReader?> CreateFrameReaderAsync()
{
IMediaFrameSource? frameSource = FrameSource;
if (FrameSource is null)
{
if (await MediaFrameSource.FindAllAsync() is IReadOnlyList<IMediaFrameSource> { Count: > 0 } sourceGroups)
{
frameSource = sourceGroups[0];
}
}
if (frameSource is not null)
{
if (frameReaderCache.TryGetValue(frameSource, out IMediaFrameReader? frameReader))
{
return frameReader;
}
frameReader = new MediaFrameReader(frameSource);
frameReaderCache.Add(frameSource, frameReader);
return frameReader;
}
return default;
}
public async Task<IMediaFrameReader?> CreateFrameReaderAsync(IMediaFrameSource frameSource)
{
if (frameSource is not null)
{
if (frameReaderCache.TryGetValue(frameSource, out IMediaFrameReader? frameReader))
{
return frameReader;
}
frameReader = new MediaFrameReader(frameSource);
frameReaderCache.Add(frameSource, frameReader);
return await Task.FromResult(frameReader);
}
return default;
}
public void Initialize(IMediaCaptureInitializationSettings initializationSettings)
{
FrameSource = initializationSettings.Source;
}
public async Task<LowLagPhotoCapture?> PrepareLowLagPhotoCaptureAsync()
{
IMediaFrameSource? frameSource = FrameSource;
if (FrameSource is null)
{
if (await MediaFrameSource.FindAllAsync() is IReadOnlyList<IMediaFrameSource> { Count: > 0 } sourceGroups)
{
frameSource = sourceGroups[0];
}
}
if (frameSource is not null)
{
IMediaFrameReader? frameReader = await CreateFrameReaderAsync(frameSource);
if (frameReader is not null)
{
LowLagPhotoCapture photoCapture = new(frameReader);
return photoCapture;
}
}
return default;
}
}
@@ -0,0 +1,6 @@
namespace TheXamlGuy.Media.Capture;
public class MediaCaptureInitializationSettings : IMediaCaptureInitializationSettings
{
public IMediaFrameSource? Source { get; set; }
}
+11
View File
@@ -0,0 +1,11 @@
using System.Drawing;
namespace TheXamlGuy.Media.Capture;
public class MediaFrameFormat
{
public Size Size { get; set; }
public long FrameRate { get; set; }
}
+5
View File
@@ -0,0 +1,5 @@
using System.Drawing;
namespace TheXamlGuy.Media.Capture;
public record MediaFrame(Bitmap Bitmap, int Width, int Height);
@@ -0,0 +1,6 @@
namespace TheXamlGuy.Media.Capture;
public class MediaFrameArrivedEventArgs : EventArgs
{
}
+88
View File
@@ -0,0 +1,88 @@
using TheXamlGuy.UI;
using AForge.Video.DirectShow;
using System.Drawing;
using AForge.Video;
using System.Drawing.Imaging;
namespace TheXamlGuy.Media.Capture;
public class MediaFrameReader : IMediaFrameReader
{
private readonly VideoCaptureDevice captureDevice;
private bool isAquiringFrames;
internal MediaFrameReader(IMediaFrameSource frameSource)
{
captureDevice = new(frameSource.Info.Id);
captureDevice.VideoResolution = captureDevice
.VideoCapabilities
.FirstOrDefault(x => frameSource.CurrentFormat.FrameRate == x.AverageFrameRate && x.FrameSize.Equals(frameSource.CurrentFormat.Size));
captureDevice.NewFrame += OnNewFrame;
}
public event TypedEventHandler<IMediaFrameReader, MediaFrameArrivedEventArgs>? FrameArrived;
public void Dispose()
{
captureDevice.Stop();
}
public async Task StartAsync()
{
if (!captureDevice.IsRunning)
{
isAquiringFrames = true;
captureDevice.Start();
}
await Task.CompletedTask;
}
public async Task StopAsync()
{
if (captureDevice.IsRunning)
{
isAquiringFrames = false;
captureDevice.Stop();
}
await Task.CompletedTask;
}
public async Task<MediaFrame?> TryAcquireLatestFrameAsync()
{
TaskCompletionSource<Bitmap> completionSource = new();
void HandleNewFrame(object sender, NewFrameEventArgs args)
{
if (args.Frame.PixelFormat is not PixelFormat.DontCare)
{
captureDevice.NewFrame -= HandleNewFrame;
completionSource.SetResult(new Bitmap(args.Frame));
}
}
captureDevice.NewFrame += HandleNewFrame;
if (!isAquiringFrames)
{
captureDevice.Start();
}
Bitmap frame = await completionSource.Task;
if (!isAquiringFrames)
{
captureDevice.SignalToStop();
captureDevice.WaitForStop();
}
return new MediaFrame(frame, frame.Width, frame.Height);
}
private void OnNewFrame(object sender, NewFrameEventArgs args)
{
FrameArrived?.Invoke(this, new MediaFrameArrivedEventArgs());
}
}
+54
View File
@@ -0,0 +1,54 @@
using AForge.Video.DirectShow;
namespace TheXamlGuy.Media.Capture;
public class MediaFrameSource : IMediaFrameSource
{
public List<MediaFrameFormat> supportedFormats = new();
private MediaFrameFormat? currentFormat = null;
internal MediaFrameSource(string id, string displayName)
{
Info = new MediaFrameSourceInfo(id);
DisplayName = displayName;
InitializeSupportedFormats(id);
}
public MediaFrameFormat CurrentFormat => currentFormat ?? supportedFormats.FirstOrDefault()!;
public string DisplayName { get; }
public MediaFrameSourceInfo Info { get; }
public IReadOnlyList<MediaFrameFormat> SupportedFormats => supportedFormats;
public static async Task<IReadOnlyList<IMediaFrameSource>> FindAllAsync()
{
List<IMediaFrameSource> result = new();
foreach (FilterInfo videoInputDevice in new FilterInfoCollection(FilterCategory.VideoInputDevice))
{
result.Add(new MediaFrameSource(videoInputDevice.MonikerString, videoInputDevice.Name));
}
return await Task.FromResult(result);
}
public void SetFormat(MediaFrameFormat format)
{
currentFormat = format;
}
private void InitializeSupportedFormats(string id)
{
VideoCaptureDevice videoCaptureDevice = new(id);
foreach (VideoCapabilities videoCapabilities in videoCaptureDevice.VideoCapabilities)
{
supportedFormats.Add(new MediaFrameFormat
{
Size = videoCapabilities.FrameSize,
FrameRate = videoCapabilities.AverageFrameRate
});
}
}
}
+11
View File
@@ -0,0 +1,11 @@
namespace TheXamlGuy.Media.Capture;
public class MediaFrameSourceInfo
{
internal MediaFrameSourceInfo(string id)
{
Id = id;
}
public string Id { get; }
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
+60
View File
@@ -0,0 +1,60 @@
namespace TheXamlGuy.Media.Capture;
public class RemoteMediaCapture : IRemoteMediaCapture
{
private readonly Dictionary<IRemoteMediaFrameSource, IRemoteMediaFrameReader> frameReaderCache = new();
public IRemoteMediaFrameSource? FrameSource { get; private set; }
public async Task<IRemoteMediaFrameReader?> CreateFrameReaderAsync(IRemoteMediaFrameSource frameSource)
{
if (frameSource is not null)
{
if (frameReaderCache.TryGetValue(frameSource, out IRemoteMediaFrameReader? frameReader))
{
return frameReader;
}
if (frameSource is IRemoteMediaFrameReaderFactory frameReaderFactory)
{
frameReader = frameReaderFactory.Factory();
frameReaderCache.Add(frameSource, frameReader);
}
return await Task.FromResult(frameReader);
}
return default;
}
public void Initialize(IRemoteMediaCaptureInitializationSettings initializationSettings)
{
FrameSource = initializationSettings.Source;
}
public async Task<LowLagPhotoCapture?> PrepareLowLagPhotoCaptureAsync()
{
IRemoteMediaFrameSource? frameSource = FrameSource;
if (FrameSource is null)
{
if (await RemoteMediaFrameSource.FindAllAsync() is IReadOnlyList<IRemoteMediaFrameSource> { Count: > 0 } sourceGroups)
{
frameSource = sourceGroups[0];
}
}
if (frameSource is not null)
{
IMediaFrameReader? frameReader = await CreateFrameReaderAsync(frameSource);
if (frameReader is not null)
{
LowLagPhotoCapture photoCapture = new(frameReader);
await frameReader.StartAsync();
return photoCapture;
}
}
return default;
}
}
@@ -0,0 +1,6 @@
namespace TheXamlGuy.Media.Capture;
public class RemoteMediaCaptureInitializationSettings : IRemoteMediaCaptureInitializationSettings
{
public IRemoteMediaFrameSource? Source { get; set; }
}
+47
View File
@@ -0,0 +1,47 @@
using Rssdp;
using static TheXamlGuy.Media.Capture.SonyMediaFrameSourceDescriptor;
namespace TheXamlGuy.Media.Capture;
public class RemoteMediaFrameSource : IRemoteMediaFrameSource, IRemoteMediaFrameReaderFactory
{
private static readonly Dictionary<string, Func<Uri, Task<IRemoteMediaFrameSourceDescriptor>>> supportedDeviceSchemas = new()
{
{ "urn:schemas-sony-com:service:ScalarWebAPI:1", async args => (await CreateAsync(args))! }
};
internal RemoteMediaFrameSource(string id, string displayName, Func<IRemoteMediaFrameReader> factory)
{
Info = new MediaFrameSourceInfo(id);
DisplayName = displayName;
Factory = factory;
}
public string DisplayName { get; }
public MediaFrameSourceInfo Info { get; }
public Func<IRemoteMediaFrameReader> Factory { get; }
public static async Task<IReadOnlyList<IRemoteMediaFrameSource>> FindAllAsync()
{
List<IRemoteMediaFrameSource> result = new();
using (SsdpDeviceLocator deviceLocator = new())
{
IEnumerable<DiscoveredSsdpDevice> foundDevices = await deviceLocator.SearchAsync();
foreach (DiscoveredSsdpDevice foundDevice in foundDevices)
{
if (supportedDeviceSchemas.TryGetValue(foundDevice.NotificationType, out Func<Uri, Task<IRemoteMediaFrameSourceDescriptor>>? factory))
{
if (await factory.Invoke(foundDevice.DescriptionLocation) is IRemoteMediaFrameSourceDescriptor descriptor)
{
result.Add(new RemoteMediaFrameSource(descriptor.Id, descriptor.DisplayName, descriptor.FrameReaderFactory));
}
}
}
}
return await Task.FromResult(result);
}
}
+129
View File
@@ -0,0 +1,129 @@
using System.Drawing;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
using TheXamlGuy.UI;
namespace TheXamlGuy.Media.Capture;
public class SonyMediaFrameReader : IRemoteMediaFrameReader
{
private readonly HttpClient client;
private readonly string endpoint;
public SonyMediaFrameReader(string endpoint)
{
client = new HttpClient();
this.endpoint = endpoint;
}
public event TypedEventHandler<IMediaFrameReader, MediaFrameArrivedEventArgs>? FrameArrived;
public void Dispose()
{
}
public async Task StartAsync()
{
await PostRequestAsync<Initialize>();
}
public async Task StopAsync()
{
await Task.CompletedTask;
}
public async Task<MediaFrame?> TryAcquireLatestFrameAsync()
{
await PostRequestAsync<Initialize>();
if (await PostRequestAsync<Capture>() is Capture result && result.Captures is not null )
{
if (result.Captures.FirstOrDefault() is List<string> captures)
{
if (captures.FirstOrDefault() is string url)
{
HttpResponseMessage content = await client.GetAsync(url);
using Stream stream = content.Content.ReadAsStream();
using Image bitmap = Image.FromStream(stream);
return new MediaFrame((Bitmap)bitmap.Clone(), bitmap.Width, bitmap.Height);
}
}
}
return default;
}
private async Task<TReponse?> PostRequestAsync<TRequest, TReponse>() where TRequest : Request, new()
{
try
{
HttpResponseMessage response = await client.PostAsJsonAsync(endpoint, new TRequest());
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadFromJsonAsync<TReponse>();
}
}
catch
{
}
return default;
}
private async Task<TRequest?> PostRequestAsync<TRequest>() where TRequest : Request, new()
{
return await PostRequestAsync<TRequest, TRequest>();
}
private record Initialize() : Request("startRecMode", "1.0", Array.Empty<string>());
private record Capture() : Request("actTakePicture", "1.0", Array.Empty<string>())
{
[JsonPropertyName("result")]
public List<List<string>>? Captures { get; set; }
}
private record Request
{
private static int requestId;
public Request(string method, string version = "1.0", params string[] parameters)
{
Method = method;
Version = version;
Id = GenerateId();
Paramaters = parameters;
}
private static int GenerateId()
{
int id = Interlocked.Increment(ref requestId);
if (requestId > 1000000000)
{
requestId = 0;
}
return id;
}
[JsonPropertyName("method")]
public string? Method { get; init; }
[JsonPropertyName("version")]
public string? Version { get; init; }
[JsonPropertyName("id")]
public int? Id { get; init; }
[JsonPropertyName("params")]
public string[]? Paramaters { get; init; }
public static Request CreateRequest(string method, string version = "1.0", params string[] parameters)
{
return new(method, version, parameters);
}
}
}
@@ -0,0 +1,141 @@
using System.Net.Http.Json;
using System.Text.Json.Serialization;
using System.Xml.Serialization;
namespace TheXamlGuy.Media.Capture;
public class SonyMediaFrameSourceDescriptor : IRemoteMediaFrameSourceDescriptor
{
public SonyMediaFrameSourceDescriptor(string id, string displayName, Func<IRemoteMediaFrameReader> factory)
{
Id = id;
DisplayName = displayName;
FrameReaderFactory = factory;
}
public string DisplayName { get; }
public string Id { get; }
public Func<IRemoteMediaFrameReader> FrameReaderFactory { get; }
public static async Task<IRemoteMediaFrameSourceDescriptor?> CreateAsync(Uri descriptionLocation)
{
using HttpClient httpClient = new();
HttpResponseMessage responseMessage = await httpClient.GetAsync(descriptionLocation);
string content = await responseMessage.Content.ReadAsStringAsync();
XmlSerializer serializer = new(typeof(Descriptor));
using TextReader reader = new StringReader(content);
if ((Descriptor?)serializer.Deserialize(reader) is Descriptor result && result.Device is Device device && device.DeviceInfo is DeviceInfo deviceInfo
&& deviceInfo.ServiceList is ServiceList serviceList && serviceList.Service is List<Service> services
&& services.FirstOrDefault(x => x.ServiceType is string serviceType
&& serviceType.Equals("camera", StringComparison.InvariantCultureIgnoreCase)) is Service service)
{
return new SonyMediaFrameSourceDescriptor(device.UDN ?? "", device.FriendlyName ?? "", () => new SonyMediaFrameReader($"{service.Action}\\{service.ServiceType}"));
}
return default;
}
private record Initialize() : Request("startRecMode", "1.0", Array.Empty<string>());
private record Capture() : Request("actTakePicture", "1.0", Array.Empty<string>())
{
[JsonPropertyName("result")]
public List<List<string>>? Captures { get; set; }
}
private record Request
{
private static int requestId;
public Request(string method, string version = "1.0", params string[] parameters)
{
Method = method;
Version = version;
Id = GenerateId();
Paramaters = parameters;
}
private static int GenerateId()
{
int id = Interlocked.Increment(ref requestId);
if (requestId > 1000000000)
{
requestId = 0;
}
return id;
}
[JsonPropertyName("method")]
public string? Method { get; init; }
[JsonPropertyName("version")]
public string? Version { get; init; }
[JsonPropertyName("id")]
public int? Id { get; init; }
[JsonPropertyName("params")]
public string[]? Paramaters { get; init; }
public static Request CreateRequest(string method, string version = "1.0", params string[] parameters)
{
return new(method, version, parameters);
}
}
[XmlRoot(ElementName = "root", Namespace = "urn:schemas-upnp-org:device-1-0")]
public class Descriptor
{
[XmlElement(ElementName = "device", Namespace = "urn:schemas-upnp-org:device-1-0")]
public Device? Device { get; set; }
}
[XmlRoot(ElementName = "device", Namespace = "urn:schemas-upnp-org:device-1-0")]
public class Device
{
[XmlElement(ElementName = "X_ScalarWebAPI_DeviceInfo", Namespace = "urn:schemas-sony-com:av")]
public DeviceInfo? DeviceInfo { get; set; }
[XmlElement(ElementName = "friendlyName", Namespace = "urn:schemas-upnp-org:device-1-0")]
public string? FriendlyName { get; set; }
[XmlElement(ElementName = "manufacturer", Namespace = "urn:schemas-upnp-org:device-1-0")]
public string? Manufacturer { get; set; }
[XmlElement(ElementName = "modelDescription", Namespace = "urn:schemas-upnp-org:device-1-0")]
public string? ModelDescription { get; set; }
[XmlElement(ElementName = "modelName", Namespace = "urn:schemas-upnp-org:device-1-0")]
public string? ModelName { get; set; }
[XmlElement(ElementName = "UDN", Namespace = "urn:schemas-upnp-org:device-1-0")]
public string? UDN { get; set; }
}
[XmlRoot(ElementName = "X_ScalarWebAPI_DeviceInfo", Namespace = "urn:schemas-sony-com:av")]
public class DeviceInfo
{
[XmlElement(ElementName = "X_ScalarWebAPI_ServiceList", Namespace = "urn:schemas-sony-com:av")]
public ServiceList? ServiceList { get; set; }
}
[XmlRoot(ElementName = "X_ScalarWebAPI_Service", Namespace = "urn:schemas-sony-com:av")]
public class Service
{
[XmlElement(ElementName = "X_ScalarWebAPI_ActionList_URL", Namespace = "urn:schemas-sony-com:av")]
public string? Action { get; set; }
[XmlElement(ElementName = "X_ScalarWebAPI_ServiceType", Namespace = "urn:schemas-sony-com:av")]
public string? ServiceType { get; set; }
}
[XmlRoot(ElementName = "X_ScalarWebAPI_ServiceList", Namespace = "urn:schemas-sony-com:av")]
public class ServiceList
{
[XmlElement(ElementName = "X_ScalarWebAPI_Service", Namespace = "urn:schemas-sony-com:av")]
public List<Service>? Service { get; set; }
}
}