Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Camera settings #42

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions FlashCap.Core/CaptureDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ protected abstract Task OnInitializeAsync(

protected abstract Task OnStartAsync(CancellationToken ct);
protected abstract Task OnStopAsync(CancellationToken ct);
public abstract void DisplayPropertyPage_CaptureFilter(IntPtr hwndOwner);
public abstract int GetPropertyValue(VideoProcessingAmplifierProperty property);
public abstract void SetPropertyValue(VideoProcessingAmplifierProperty property, object? value);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eventually SetPropertyValue if more properties are supported, it should receive a wider Type/Enum

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree. Perhaps the key that is considered standard is Enum, and we need to consider a separate specifiable interface if the device provides an extended key, but we have not yet figured out how far to abstract it. So we can reserve this issue here.


public CaptureDeviceProperties Properties { get; protected set; } = new CaptureDeviceProperties();

protected abstract void OnCapture(
IntPtr pData, int size, long timestampMicroseconds, long frameIndex, PixelBuffer buffer);
Expand Down
45 changes: 45 additions & 0 deletions FlashCap.Core/CaptureDeviceProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Collections.Generic;

namespace FlashCap
{
public enum VideoProcessingAmplifierProperty
{
Brightness,
Contrast,
Hue,
Saturation,
Sharpness,
Gamma,
ColorEnable,
WhiteBalance,
BacklightCompensation,
Gain
}

public sealed class CaptureDeviceProperties : Dictionary<VideoProcessingAmplifierProperty, CaptureDeviceProperty>
{
}

public abstract class CaptureDeviceProperty
{
public VideoProcessingAmplifierProperty Property { get; private set; }
public int Min { get; private set; }
public int Max { get; private set; }
public int Step { get; private set; }

protected CaptureDeviceProperty(VideoProcessingAmplifierProperty property, int min, int max, int step)
{
Property = property;
Min = min;
Max = max;
Step = step;
}

internal bool IsPropertyValueValid(object? obj)
{
var value = obj as int?;

return value != null && value <= Max && value >= Min && value % Step == 0;
}
}
}
103 changes: 102 additions & 1 deletion FlashCap.Core/Devices/DirectShowDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ [PreserveSig] public int BufferCB(
private bool transcodeIfYUV;
private FrameProcessor frameProcessor;
private NativeMethods_DirectShow.IGraphBuilder? graphBuilder;
private NativeMethods_DirectShow.ICaptureGraphBuilder2? captureGraphBuilder;
private SampleGrabberSink? sampleGrabberSink;
private IntPtr pBih;

Expand Down Expand Up @@ -174,7 +175,7 @@ protected override Task OnInitializeAsync(

///////////////////////////////

var captureGraphBuilder = NativeMethods_DirectShow.CreateCaptureGraphBuilder();
captureGraphBuilder = NativeMethods_DirectShow.CreateCaptureGraphBuilder();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed a way to access the captureGraph, is there a better way?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ldlac No problem.

if (captureGraphBuilder.SetFiltergraph(this.graphBuilder) < 0)
{
throw new ArgumentException(
Expand Down Expand Up @@ -312,6 +313,106 @@ protected override Task OnStopAsync(CancellationToken ct)
}
}

public override int GetPropertyValue(VideoProcessingAmplifierProperty property)
{
if (Properties.TryGetValue(property, out CaptureDeviceProperty _)
&& graphBuilder != null
&& captureGraphBuilder != null)
{
graphBuilder.FindFilterByName("Capture source", out NativeMethods_DirectShow.IBaseFilter? captureSourceFilter);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in my application (wpf), it's working fine, but in the the sample I've created based on OneShot, I'm getting this
Unable to cast COM object of type 'System.__ComObject' to interface type 'IBaseFilter'.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kekyo I was currently stuck here, any idea why I'm experiencing this?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I will check it.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ldlac In this source file, there are 3 places that use FindFilterByName, do all of them throw the same exception? In my environment, the FindFilterByName in DisplayPropertyPage_CaptureFilter also threw an exception.
If so, there may be something wrong with FindFilterByName (I don't know the cause yet). If not, then we need to suspect another cause...

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ldlac I debugged the code and found a problem with the added property page display and the property getter/setter accesses ignoring the COM apartments, so I fixed it.

472f25d (I think the develop was badly positioned and that caused the weird merge (My mistake), so I fixed it in another branch. develop has been repositioned, so please rebase it.)

FlashCap has been modified to initialize another thread as a single-threaded apartment and access DS objects only on it, as past implementation experience has shown that accessing the calling COM apartment as it is causes many frame drops The following is a list of the DS objects that can be accessed beyond the apartment.

When accessing the DS object beyond the apartment, the standard COM way is to marshal the interface, but it is tedious to do this in .NET, so we use SynchronizationContext to send messages. See IndeependentSingleApartmentContext.InvokeAsync().

In the environment at hand, this modification allows the capture filter property page to be displayed.
I have not tested the getter and setter, but the modification should give you an idea of what to do.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this commit, really helped!

The sample is working fine, I'll need to handle flags

image

if (captureSourceFilter != null)
{
captureGraphBuilder.FindInterface(Guid.Empty, Guid.Empty, captureSourceFilter, NativeMethods_DirectShow.IAMVideoProcAmpHelper.GUID, out object? videoProcAmpObject);
if (videoProcAmpObject != null)
{
var videoProcAmp = (NativeMethods_DirectShow.IAMVideoProcAmp)videoProcAmpObject;
videoProcAmp.Get(DirectShowProperty.FromVideoProcessingAmplifierProperty(property), out int value, out NativeMethods_DirectShow.VideoProcAmpFlags _);
Copy link
Author

@ldlac ldlac Apr 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really know the use of this one yet, NativeMethods_DirectShow.VideoProcAmpFlags

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even in the environment at hand, I was able to obtain a range of values that we believe to be brightness. Nice work!

Marshal.ReleaseComObject(videoProcAmpObject);
return value;
}
Marshal.ReleaseComObject(captureGraphBuilder);
}
}

throw new ArgumentException(
$"FlashCap: Property is not supported by device: Property={property}");
}

public override void SetPropertyValue(VideoProcessingAmplifierProperty property, object? obj)
{
if (Properties.TryGetValue(property, out CaptureDeviceProperty? captureDeviceProperty)
&& obj != null
&& captureDeviceProperty != null
&& captureDeviceProperty.IsPropertyValueValid(obj)
&& graphBuilder != null
&& captureGraphBuilder != null)
{
graphBuilder.FindFilterByName("Capture source", out NativeMethods_DirectShow.IBaseFilter? captureSourceFilter);
if (captureSourceFilter != null)
{
captureGraphBuilder.FindInterface(Guid.Empty, Guid.Empty, captureSourceFilter, NativeMethods_DirectShow.IAMVideoProcAmpHelper.GUID, out object? videoProcAmpObject);
if (videoProcAmpObject != null)
{
var videoProcAmp = (NativeMethods_DirectShow.IAMVideoProcAmp)videoProcAmpObject;
videoProcAmp.Get(DirectShowProperty.FromVideoProcessingAmplifierProperty(property), out int _, out NativeMethods_DirectShow.VideoProcAmpFlags videoProcAmpFlags);
videoProcAmp.Set(DirectShowProperty.FromVideoProcessingAmplifierProperty(property), (int)obj, videoProcAmpFlags);
Marshal.ReleaseComObject(videoProcAmpObject);
}
Marshal.ReleaseComObject(captureGraphBuilder);
}
}

throw new ArgumentException(
$"FlashCap: Property is not supported by device: Property={property}");
}

public override void DisplayPropertyPage_CaptureFilter(IntPtr hwndOwner)
{
if (graphBuilder != null
&& graphBuilder.FindFilterByName("Capture source", out NativeMethods_DirectShow.IBaseFilter? captureSourceFilter) >= 0)
{
DisplayPropertyPage_Filter(captureSourceFilter, hwndOwner);
}
}

private void DisplayPropertyPage_Filter(object? obj, IntPtr hwndOwner)
{
if (obj == null)
return;

if (obj is not NativeMethods_DirectShow.ISpecifyPropertyPages pProp)
return;

if (obj is not NativeMethods_DirectShow.IBaseFilter filter)
return;

if (filter.QueryFilterInfo(out NativeMethods_DirectShow.FILTER_INFO filterInfo) < 0)
{
throw new Exception(
$"FlashCap: Couldn't query filter info");
}

if (pProp.GetPages(out NativeMethods_DirectShow.DsCAUUID caGUID) < 0)
{
throw new Exception(
$"FlashCap: Couldn't get pages");
}

object oDevice = obj;
if (NativeMethods_DirectShow.OleCreatePropertyFrame(hwndOwner, 0, 0, filterInfo.chName, 1, ref oDevice, caGUID.cElems, caGUID.pElems, 0, 0, IntPtr.Zero) < 0)
{
throw new Exception(
$"FlashCap: Couldn't create property frame");
}

Marshal.FreeCoTaskMem(caGUID.pElems);
Marshal.ReleaseComObject(pProp);
if (filterInfo.graph != null)
{
Marshal.ReleaseComObject(filterInfo.graph);
}
}

#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
Expand Down
14 changes: 14 additions & 0 deletions FlashCap.Core/Devices/DirectShowProperty.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using FlashCap.Internal;

namespace FlashCap.Devices
{
public sealed class DirectShowProperty : CaptureDeviceProperty
{
public DirectShowProperty(VideoProcessingAmplifierProperty property, int min, int max, int step) : base(property, min, max, step) { }

internal static NativeMethods_DirectShow.VideoProcAmpProperty FromVideoProcessingAmplifierProperty(VideoProcessingAmplifierProperty property)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

each device property should be able to map from VideoProcessingAmplifierProperty to their own implementation

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps there is a more desirable interface, but this is fine for now 👍

{
return (NativeMethods_DirectShow.VideoProcAmpProperty)(int)property;
}
}
}
41 changes: 28 additions & 13 deletions FlashCap.Core/Devices/V4L2Device.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ public sealed class V4L2Device : CaptureDevice
private FrameProcessor frameProcessor;

private long frameIndex;

private readonly IntPtr[] pBuffers = new IntPtr[BufferCount];
private readonly int[] bufferLength = new int[BufferCount];

private int fd;
private IntPtr pBih;
private Task task;
Expand Down Expand Up @@ -93,11 +93,11 @@ protected override unsafe Task OnInitializeAsync(
fmt_pix.height = (uint)characteristics.Height;
fmt_pix.pixelformat = pix_fmt;
fmt_pix.field = (uint)v4l2_field.ANY;

var format = Interop.Create_v4l2_format();
format.type = (uint)v4l2_buf_type.VIDEO_CAPTURE;
format.fmt_pix = fmt_pix;

if (ioctl(fd, Interop.VIDIOC_S_FMT, format) == 0)
{
applied = true;
Expand Down Expand Up @@ -128,14 +128,14 @@ protected override unsafe Task OnInitializeAsync(
buffer.type = (uint)v4l2_buf_type.VIDEO_CAPTURE;
buffer.memory = (uint)v4l2_memory.MMAP;
buffer.index = (uint)index;

if (ioctl(fd, Interop.VIDIOC_QUERYBUF, buffer) < 0)
{
var code = Marshal.GetLastWin32Error();
throw new ArgumentException(
$"FlashCap: Couldn't assign video buffer: Code={code}, DevicePath={this.devicePath}");
}

if (mmap(IntPtr.Zero, buffer.length, PROT.READ, MAP.SHARED,
fd, buffer.m_offset) is { } pBuffer &&
pBuffer == MAP_FAILED)
Expand Down Expand Up @@ -178,7 +178,7 @@ protected override unsafe Task OnInitializeAsync(
pBih->biWidth = characteristics.Width;
pBih->biHeight = characteristics.Height;
pBih->biSizeImage = pBih->CalculateImageSize();

this.fd = fd;
this.pBih = pih;

Expand All @@ -203,7 +203,7 @@ protected override unsafe Task OnInitializeAsync(
this.bufferLength[index] = default;
}
}

close(fd);
throw;
}
Expand Down Expand Up @@ -234,7 +234,7 @@ await this.frameProcessor.DisposeAsync().
this.fd, Interop.VIDIOC_STREAMOFF,
(int)v4l2_buf_type.VIDEO_CAPTURE);
}

write(this.abortwfd, new byte[] { 0x01 }, 1);

await this.task.
Expand All @@ -254,7 +254,7 @@ static bool IsIgnore(int code) =>
EINVAL => true,
_ => false,
};

var fds = new[]
{
new pollfd
Expand Down Expand Up @@ -361,12 +361,12 @@ static bool IsIgnore(int code) =>
this.bufferLength[index] = default;
}
}

close(this.abortrfd);
close(this.fd);

NativeMethods.FreeMemory(this.pBih);

this.abortrfd = -1;
this.fd = -1;
this.pBih = IntPtr.Zero;
Expand Down Expand Up @@ -419,6 +419,21 @@ protected override Task OnStopAsync(CancellationToken ct)
return TaskCompat.CompletedTask;
}

public override int GetPropertyValue(VideoProcessingAmplifierProperty property)
{
throw new Exception("not supported for V4l2Device");
}

public override void SetPropertyValue(VideoProcessingAmplifierProperty property, object? obj)
{
throw new Exception("not supported for V4l2Device");
}

public override void DisplayPropertyPage_CaptureFilter(IntPtr hwndOwner)
{
throw new Exception("not supported for V4l2Device");
}

#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
Expand Down
27 changes: 21 additions & 6 deletions FlashCap.Core/Devices/VideoForWindowsDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,15 @@ protected override unsafe Task OnInitializeAsync(
throw;
}

///////////////////////////////////////
///////////////////////////////////////

this.handle = handle;
this.handle = handle;

// https://stackoverflow.com/questions/4097235/is-it-necessary-to-gchandle-alloc-each-callback-in-a-class
this.thisPin = GCHandle.Alloc(this, GCHandleType.Normal);
this.callback = this.CallbackEntry;
// https://stackoverflow.com/questions/4097235/is-it-necessary-to-gchandle-alloc-each-callback-in-a-class
this.thisPin = GCHandle.Alloc(this, GCHandleType.Normal);
this.callback = this.CallbackEntry;

NativeMethods_VideoForWindows.capSetCallbackFrame(handle, this.callback);
NativeMethods_VideoForWindows.capSetCallbackFrame(handle, this.callback);
}, ct);
}

Expand Down Expand Up @@ -279,6 +279,21 @@ protected override Task OnStopAsync(CancellationToken ct)
}
}

public override int GetPropertyValue(VideoProcessingAmplifierProperty property)
{
throw new Exception("not supported for V4l2Device");
}

public override void SetPropertyValue(VideoProcessingAmplifierProperty property, object? obj)
{
throw new Exception("not supported for V4l2Device");
}

public override void DisplayPropertyPage_CaptureFilter(IntPtr hwndOwner)
{
throw new Exception("not supported for V4l2Device");
}

#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
Expand Down
Loading