-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Program.cs
138 lines (130 loc) · 5.11 KB
/
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading;
using DirectShowLib;
using Emgu.CV;
using Emgu.CV.Structure;
using Emgu.CV.Util;
using FFMediaToolkit;
using FFMediaToolkit.Encoding;
using FFMediaToolkit.Graphics;
namespace Webcam
{
class Program
{
private static VideoCapture _captureDevice;
private static MediaOutput _videoOutput;
/// <summary>
/// Record a video for 5 seconds and store it as c:\temp\example.mp4
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
// Install-Package Emgu.CV
// Install-Package Emgu.CV.runtime.windows
// Install-Package FFMediaToolkit
// Install-Package DirectShowLib
var camIndex = SelectCameraIndex();
_captureDevice = new VideoCapture(camIndex, VideoCapture.API.DShow)
{FlipVertical = true};
_captureDevice.ImageGrabbed += CaptureDeviceImageGrabbed;
var settings = new VideoEncoderSettings(width:
_captureDevice.Width
, height: _captureDevice.Height
, framerate: 15
, codec: VideoCodec.H264)
{
EncoderPreset = EncoderPreset.Fast,
CRF = 17 // Constant Rate Factor
};
// Download from https://github.com/BtbN/FFmpeg-Builds/releases
FFmpegLoader.FFmpegPath =
@"C:\Users\fiach\source\repos\Webcam\FFmpeg\";
_videoOutput = MediaBuilder.CreateContainer(@"c:\temp\example.mp4").WithVideo(settings).Create();
_captureDevice.Start();
Thread.Sleep(TimeSpan.FromSeconds(5));
_captureDevice.Stop();
_captureDevice.Dispose();
_videoOutput.Dispose();
}
/// <summary>
/// If there are more than one webcam attached, then ask the user to select
/// </summary>
/// <returns></returns>
private static int SelectCameraIndex()
{
var cameras = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);
if (cameras.Length == 1) return 0;
foreach (var (camera, index) in WithIndex(cameras))
{
Console.WriteLine($"{index}:{camera.Name}");
}
Console.WriteLine("Select a camera from the list above:");
var camIndex = Convert.ToInt32(Console.ReadLine());
return camIndex;
}
/// <summary>
/// Not used, but a simple method to take a picture from the video capture device
/// and save to disk
/// </summary>
/// <param name="filename"></param>
static void TakePhoto(string filename)
{
using var capture = new VideoCapture(0, VideoCapture.API.DShow);
var image = capture.QueryFrame(); //take a picture
image.Save(filename);
}
/// <summary>
/// When an Image is grabbed from the video capture device, then add it to the
/// video stream
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void CaptureDeviceImageGrabbed(object sender, System.EventArgs e)
{
var frame = new Mat();
_captureDevice.Retrieve(frame);
var buffer = new VectorOfByte();
var input = frame.ToImage<Bgr, byte>();
CvInvoke.Imencode(".bmp", input, buffer);
var bitmapData = buffer.ToArray();
// 'Pixel buffer size doesn't match size required by this image format.'
// Remove 54 byte header
var headerLessData = RedBlueSwap(bitmapData.Skip(54).ToArray());
var imageData = ImageData.FromArray(headerLessData, ImagePixelFormat.Rgb24, frame.Size);
_videoOutput.Video.AddFrame(imageData);
}
/// <summary>
/// The Bitmap file format is recorded backwards, so red and green are swapped
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
private static byte[] RedBlueSwap(byte[] input)
{
var output = new byte[input.Length];
for (var i = 0; i < input.Length - 3; i += 3)
{
var r = input[i];
var g = input[i + 1];
var b = input[i + 2];
output[i] = b;
output[i + 1] = g;
output[i + 2] = r;
}
return output;
}
/// <summary>
/// Handy code from https://thomaslevesque.com/2019/11/18/using-foreach-with-index-in-c/
/// Gives access to an indexer in a for each loop
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <returns></returns>
private static IEnumerable<(T item, int index)> WithIndex<T>(IEnumerable<T> source)
{
return source.Select((item, index) => (item, index));
}
}
}