FlashCap

repository·main·Indexed 42 days ago

https://github.com/kekyo/flashcap

A lightweight, independent .NET library for video frame capturing (frame grabbing). It provides a simple API for enumerating devices and capturing image data without heavy native libraries, supporting Media Foundation, DirectShow, VFW on Windows, and V4L2 on Linux.

Tokens
10.3K
Snippets
27
Records
35
Agent score
53%

What's inside FlashCap

  1. FlashCap supported platforms and environments

    main

    FlashCap supports a wide range of .NET runtimes and operating systems:

    Supported .NET Runtimes:

    • .NET 5 or higher
    • .NET Core 3.1, 3.0, 2.2, 2.1, 2.0
    • .NET Standard 2.1, 2.0, 1.3
    • .NET Framework 4.8, 4.6.1, 4.5, 4.0, 3.5

    Supported Operating Systems & APIs:

    • Windows: DirectShow (x64/x86), Video for Windows (x64/x86), Media Foundation (Windows 7+, x64)
    • Linux: V4L2 (x86_64, i686, aarch64, armv7l, mips)
    • macOS: AVFoundation (x86_64, arm64)
  2. Handle frame ordering in parallel (Scattering) mode

    main

    When isScattering is set to true, handlers are processed in parallel by multiple threads. This increases throughput but means frame order is not guaranteed.

    To reconstruct the correct sequence of frames in a multi-threaded environment, use the Timestamp and FrameIndex properties available on the PixelBuffer class.

  3. Reduce data copies when processing video frames

    main

    When processing high-frequency video frames (e.g., 30 or 60 FPS), minimizing data copies is critical for performance. FlashCap provides different methods to access frame data via PixelBufferScope within the OpenAsync callback.

    Image Access Methods Comparison

    MethodSpeedSafe outside scope?Return Type
    CopyImage()SlowYesbyte[]
    ExtractImage()VariableNo (Data may be corrupted)byte[]
    ReferImage()FastNoArraySegment<byte>

    Usage Guidelines

    1. For safety (accessing data outside the callback): Use CopyImage(). This ensures the data is copied into a new buffer that you own.
    2. For performance (processing data inside the callback): Use ReferImage(). This avoids an extra copy by providing an ArraySegment<byte> pointing to the existing buffer. To use it with a Stream, use the AsStream() extension method or manually construct a MemoryStream using image.Array, image.Offset, and image.Count.
    3. To achieve a single copy: Avoid decoding the image (e.g., converting to Bitmap). Instead, write the raw bytes directly to a file using the ArraySegment<byte> provided by ReferImage().
    using var device = await descriptor0.OpenAsync(
      descriptor0.Characteristics[0],
      async bufferScope =>  // <-- `PixelBufferScope` (1 copy already occurred)
      {
        // High-performance: No extra copy (unless transcoding is required)
        ArraySegment<byte> image = bufferScope.Buffer.ReferImage();
    
        // Example: Writing directly to file to maintain 1-copy efficiency
        using var fs = File.Create("output.jpg");
        await fs.WriteAsync(image.Array, image.Offset, image.Count);
      });
  4. Enable Native AOT support

    main

    To support Native AOT in net8.0 or later, FlashCap uses source-generated COM interop for callbacks.

    Important: The DirectShow backend relies on runtime-generated COM interop and is not compatible with Native AOT. If you are building a Native AOT application, do not use the default CaptureDevices enumeration. Instead, use MediaFoundationDevices directly.

  5. Filter supported video formats

    main

    FlashCap enumerates all formats reported by the device. To avoid errors, filter the Characteristics list to exclude unsupported formats. Unsupported formats are identified by PixelFormats.Unknown.

    // Select a device:
    var descriptor0 = devices.EnumerateDescriptors().ElementAt(0);
    
    // Exclude unsupported formats:
    var characteristics = descriptor0.Characteristics.
        Where(c => c.PixelFormat != PixelFormats.Unknown).
        ToArray();
  6. Port V4L2 to unsupported Linux platforms

    main

    FlashCap uses V4L2 (Video4Linux2) for Linux image capture. If you wish to port FlashCap to a Linux platform not currently supported (e.g., mips64, riscv32/64, sparc64), follow these guidelines.

    Core Mechanism: V4L2Generator

    Because standard .NET interop defaults are optimized for Windows and may not match the target Linux ABI, FlashCap uses FlashCap.V4L2Generator. This generator uses Clang JSON AST files to automatically generate C# interop source code that strictly adheres to the correct ABI structures for the target platform.

    Porting Requirements

    • Clang: You must use a Clang version compatible with your target Linux ABI. Verified versions include 13, 11, and 10.
    • Runtime: A working .NET or mono runtime is required on the target Linux platform.
    • Build Environment: It is highly recommended to use a QEMU-based virtual machine for a stable build environment. Automation scripts for this are located in the qemu-vm-builder/ directory.

    Implementation Steps

    1. Build the Generator: Build FlashCap.V4L2Generator first. If the target environment lacks the .NET SDK, use build-mono.sh to compile using mcs (mono).
    2. Generate Code: Follow the procedure outlined in the dumper.sh script (you may need to adapt it for your specific environment).
    3. Integrate Source: The generated code will be placed in FlashCap.Core/Internal/V4L2/.
    4. Update Type Initializer: Add a new platform branch to the switch statement in NativeMethods_V4L2.cs to handle the new architecture.
    // Example of adding a new platform branch in NativeMethods_V4L2.cs
    switch (buf.machine)
    {
        // ... existing cases ...
        case "loongarch64":
            Interop = new NativeMethods_V4L2_Interop_loongarch64();
            break;
    
        // Add your new port here
        case "your_new_arch":
            Interop = new NativeMethods_V4L2_Interop_your_new_arch();
            break;
    
        default:
            throw new InvalidOperationException($"FlashCap: Architecture '{buf.machine}' is not supported.");
    }

    Submitting a Pull Request

    When submitting a port, please include the following details for documentation:

    • Target physical machine model.
    • Capture unit/camera model.
    • Connection method (e.g., USB, PCIe, internal camera).
    • Source of the hardware (if not a common retail item).
    • Any known constraints or limitations (e.g., specific video characteristics that don't work).
  7. Use Reactive Extensions with FlashCap

    main

    When using AsObservableAsync(), the handler is treated as a Reactive Extension observer. Because the OnNext() method is synchronous, you cannot safely use async void for asynchronous processing within the observer without risking the expiration of the PixelBuffer.

    Warning: The lifetime of the PixelBuffer ends immediately after the first await in an async void implementation. The compiler will not detect this error.

    Best Practice: Use projection operators (like Select) to copy the image data out of the buffer as quickly as possible to ensure it is safe to use outside the immediate scope.

    deviceObservable.
        // Project immediately to copy data out of the buffer
        Select(bufferScope =>
            Bitmap.FromStream(bufferScope.Buffer.ReferImage().AsStream())).
        // Continue with asynchronous processing...
        // ...
  8. Build QEMU-based VM images for FlashCap V4L2 interop fragments

    main

    Use the bash scripts in this directory to build Debian 12 virtual machine images. These environments are used to generate FlashCap V4L2 interop fragments.

    Available scripts:

    • build-virt-images.sh: Constructs virt-manager based Debian 12 VM images for x86_64, i686, aarch64, and armv7l architectures.
    • build-mipsel-image.sh: Builds a Debian 12 VM image specifically for the mipsel architecture.
    • run-mipsel-image.sh: Runs the previously built Debian 12 VM image for the mipsel architecture.
  9. Filter unsupported video formats

    main

    FlashCap enumerates all formats reported by the device, including those it cannot handle. To avoid errors, filter out characteristics where PixelFormat is PixelFormats.Unknown before opening the device.

    var descriptor0 = devices.EnumerateDescriptors().ElementAt(0);
    
    // Filter out unsupported formats
    var validCharacteristics = descriptor0.Characteristics
        .Where(c => c.PixelFormat != PixelFormats.Unknown)
        .ToArray();
    
    // Use one of the valid characteristics to open the device
    var selected = validCharacteristics[0];
  10. Ensure Native AOT compatibility

    main

    If you are building a Native AOT application on net8.0 or later, you must avoid the default CaptureDevices enumeration because it includes DirectShow, which relies on runtime-generated COM interop and is incompatible with Native AOT.

    Instead, use MediaFoundationDevices directly to ensure compatibility with the source-generated COM interop used by FlashCap's Media Foundation backend.

  11. Port FlashCap to unsupported V4L2 platforms

    main

    FlashCap uses the V4L2 (Video4Linux2) API for Linux image capture. If you need to port it to an unsupported architecture (e.g., mips64, riscv, sparc64), you must use the FlashCap.V4L2Generator to create the necessary C# interoperability definitions from V4L2 header files.

    Prerequisites

    • Clang: Required to generate JSON AST files from V4L2 headers. Verified versions: 10, 11, and 13.
    • Runtime: A stable .NET or Mono runtime must be available on the target Linux platform.

    Porting Workflow

    1. Generate Interop Code: Build FlashCap.V4L2Generator. If the target environment lacks the .NET SDK, use the provided build-mono.sh to compile using the Mono mcs compiler.
    2. Automate with Scripts: Use the dumper.sh script as a template and customize it for your target environment to automate the generation process.
    3. Integrate into Core: The generated source code should be placed in FlashCap.Core/Internal/V4L2/.
    4. Register the Architecture: You must update the switch statement in the type initializer of NativeMethods_V4L2.cs to include your new platform branch.

    Verification

    • Use the Avalonia sample code for verification if supported.
    • If Avalonia is unavailable, use the OneShot sample code or extend it to save continuous bitmaps for inspection.
    // In FlashCap.Core/Internal/V4L2/NativeMethods_V4L2.cs
    switch (buf.machine)
    {
        // ... existing cases ...
    
        // (Insert your cool platform ported interop...)
        case "your_new_arch":
            Interop = new NativeMethods_V4L2_Interop_your_new_arch();
            break;
    
        default:
            throw new InvalidOperationException(
                $"FlashCap: Architecture '{buf.machine}' is not supported.");
    }
  12. Build FlashCap from source

    main

    To build FlashCap locally, you need a Windows development environment with Visual Studio 2022 installed. Ensure you have included the WPF and Windows Forms workloads, as they are required to build the sample code.

    Build Steps

    1. Clone the repository.
    2. Build the solution using one of the following methods:
      • Use the .NET CLI: dotnet build
      • Open FlashCap.sln in Visual Studio 2022 and build it via the IDE.

    Note: While the core FlashCap library can likely be built on Linux with the .NET SDK, the sample code contains Windows-specific implementations, so a Windows environment is assumed for full development.

    dotnet build