HOME
  Security
   Software
    Hardware
  
FPGA
  CPU
   Android
    Raspberry Pi
  
nLite
  Xcode
   etc.
    ALL
  
LINK
BACK
 

2022/05/05

Accord.NETフレームワークを使って USBカメラから動画を録画してファイルに保存する方法 Accord.NETフレームワークを使って USBカメラから動画を録画してファイルに保存する方法

(C#で USBカメラや MS2109の HDMIキャプチャデバイスから画像をキャプチャして録画する方法)

Tags: [Windows], [無人インストール]




● Accord.NETフレームワークを使って USBカメラから動画を録画してファイルに保存する方法

 C#で USBカメラや MS2109の HDMIキャプチャデバイスから画像をキャプチャして録画する方法

 なお、結論から書くと無音の動画のキャプチャ(ファイル保存)はできますが、音声付きの動画のキャプチャは成功しませんでした。

 Accord.NETフレームワーク
Accord.NET Machine Learning Framework
Accord.NET Framework

 Accord.NETフレームワークは 2017年で開発が停止しています。

 Accord.NETフレームワークは 3.8.0を使用しています。
Accord 3.8.0

 3.8.2-alphaを使用しても音声付きの動画ができませんでした。(音声にブチブチノイズが入る。動画のフレームレートが変)

 とりあえず、これで MS2109の動画キャプチャで Windwos標準のカメラアプリや OBS等の巨大サイズのアプリを使わなくても済む様になりました。
 (音声を録音したい場合は OBSを使うしか方法がありませんが)

 なお、PictureBoxに PictureBoxSizeMode.StretchImageを使うと私の使い方が間違っているのか? pictureBox.Image.Dispose() をしても盛大にメモリリークしまくります。
 なので、PictureBoxには自前で拡大縮小の処理を入れています。
SCALE AN IMAGE IN C# PRESERVING ASPECT RATIO
Resize code ruins quality? [duplicate]

 と思い、再度追試したら大丈夫でした。私の PictureBoxの使い方が間違っていた様です。
 PictureBoxSizeMode.Zoomを設定すれば画像の縦横比(Aspect ratio)を保持して自動リサイズをしてくれます。

Accord.3.8.0
Accord.Audio.3.8.0
Accord.DirectSound.3.8.0
Accord.Math.3.8.0
Accord.Video.3.8.0
Accord.Video.DirectShow.3.8.0
Accord.Video.FFMPEG.3.8.0
SharpDX.4.2.0
SharpDX.DirectSound.4.2.0
※ Audio系は未使用です。

 Windowsフォームアプリケーションでプロジェクトを作成します。

 下記の画像の様に ComboBoxを 3個、Buttonを 2個、CheckBoxを 1個、PictureBoxを 2個、Formに配置します。
 右下の PictureBoxは Capture時のサムネイル表示用。

・Accord.NETフレームワークを使って USBカメラから動画を録画してファイルに保存する方法
Accord.NETフレームワークを使って USBカメラから動画を録画してファイルに保存する方法


Accord.NETフレームワークを使って USBカメラから動画を録画してファイルに保存する方法



using Accord.Audio;
using Accord.Audio.Generators;
using Accord.DirectSound;
using Accord.Video.DirectShow;
using Accord.Video.FFMPEG;
using System;
using System.Drawing;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TestAccordApp
{
    public partial class Form1 : Form
    {
        private FilterInfoCollection videoDevices;
        private AudioDeviceCollection audioDevices;
        private VideoCaptureDevice videoCapture;
        private VideoFileWriter videoFileWrite;
        private System.Timers.Timer timer1;
        private Object syncObj = new Object();
        volatile bool capture = false;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            button1.Enabled = false;
            button2.Enabled = false;
            comboBox1.DropDownStyle = ComboBoxStyle.DropDownList;
            comboBox2.DropDownStyle = ComboBoxStyle.DropDownList;
            comboBox2.Enabled = false;
            comboBox4.DropDownStyle = ComboBoxStyle.DropDownList;
            // pictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;
            // pictureBox2.SizeMode = PictureBoxSizeMode.CenterImage;
            pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
            pictureBox2.SizeMode = PictureBoxSizeMode.Zoom;
            pictureBox2.Visible = false;
            timer1 = new System.Timers.Timer(800);
            timer1.Interval = 800;
            timer1.Elapsed += (send, ev) =>
            {
                Invoke(new Action(TimerEvent));
            };

            videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
            foreach (var vd in videoDevices)
            {
                comboBox1.Items.Add(vd.Name);
            }
            comboBox1.SelectedIndex = 0;

            // audioDevices = new AudioDeviceCollection(AudioDeviceCategory.Capture);
            // foreach (var ad in audioDevices)
            // {
            //     comboBox3.Items.Add(ad.Description);
            // }

            comboBox4.Items.Add("V. High");
            comboBox4.Items.Add("High");
            comboBox4.Items.Add("Normal");
            comboBox4.Items.Add("Low");
            comboBox4.Items.Add("V. Low");
            comboBox4.SelectedIndex = 2;
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            Invoke((MethodInvoker)delegate
            {
                StopCamera();
                timer1.Dispose();
                audioDevices = null;
                videoDevices = null;

                if (pictureBox1.Image != null)
                {
                    pictureBox1.Image.Dispose();
                }
                if (pictureBox2.Image != null)
                {
                    pictureBox2.Image.Dispose();
                }
            });
        }

        private void StartCamera()
        {
            var index = comboBox1.SelectedIndex;
            if (index < 0)
            {
                return;
            }
            var index2 = comboBox2.SelectedIndex;
            if (index2 < 0)
            {
                return;
            }

            try
            {
                videoCapture = new VideoCaptureDevice(videoDevices[index].MonikerString);
                videoCapture.VideoResolution = videoCapture.VideoCapabilities[index2];
                videoCapture.NewFrame += Camera_NewFrame;
                videoCapture.Start();
            }
            catch (Exception _)
            {
            }

            var vr = videoCapture.VideoResolution;

            int width = vr.FrameSize.Width;
            int height = vr.FrameSize.Height;
            int frameRate = vr.AverageFrameRate;
            VideoCodec codec = VideoCodec.MPEG4;

            var qty = comboBox4.SelectedIndex - 2;
            int bitRate = (int)(width * height / (1.0 + 0.25 * qty));

            DateTime dt = DateTime.Now;
            string str = dt.ToString("yyyyMMdd_HHmmss");

            if (checkBox1.Checked)
            {
                lock (syncObj)
                {
                    videoFileWrite = new VideoFileWriter();
                    videoFileWrite.Open(str + ".mp4", width, height, frameRate, codec, bitRate);
                }
            }
        }

        private void StopCamera()
        {
            lock (syncObj)
            {
                if (videoFileWrite != null)
                {
                    videoFileWrite.Close();
                    videoFileWrite.Dispose();
                    videoFileWrite = null;
                }
            }

            if (videoCapture != null)
            {
                videoCapture.NewFrame -= Camera_NewFrame;
                videoCapture.SignalToStop();
                Task task = Task.Run(() =>
                {
                    videoCapture.WaitForStop();
                    videoCapture = null;
                });
            }
        }

        private void Camera_NewFrame(object sender, Accord.Video.NewFrameEventArgs eventArgs)
        {
            var bitmap = (Bitmap)eventArgs.Frame;
            // InvokeRequired
            Invoke(new Action<Bitmap>(UpdateFrame), bitmap);

            lock (syncObj)
            {
                if (videoFileWrite != null)
                {
                    videoFileWrite.WriteVideoFrame(bitmap);
                }
            }
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private void UpdateFrame(Bitmap bitmap)
        {
            if (pictureBox1.Image != null)
            {
                pictureBox1.Image.Dispose();
            }

            var maxWidth = pictureBox1.Width;
            var maxHeight = pictureBox1.Height;
            // pictureBox1.Image = ScaleImage(bitmap, maxWidth, maxHeight);
            pictureBox1.Image = (Bitmap)bitmap.Clone();

            if (capture)
            {
                DateTime dt = DateTime.Now;
                string str = dt.ToString("yyyyMMdd_HHmmss");
                string filename = @".\" + str + ".png";
                var imageFormat = System.Drawing.Imaging.ImageFormat.Png;
                bitmap.Save(filename, imageFormat);

                if (pictureBox2.Image != null)
                {
                    pictureBox2.Image.Dispose();
                }

                var maxWidth2 = pictureBox2.Width;
                var maxHeight2 = pictureBox2.Height;
                // pictureBox2.Image = ScaleImage(bitmap, maxWidth2, maxHeight2);
                pictureBox2.Image = (Bitmap)bitmap.Clone();

                pictureBox2.Visible = true;
                timer1.Stop();
                timer1.Start();

                capture = false;
            }
        }

        // SCALE AN IMAGE IN C# PRESERVING ASPECT RATIO
        // https://efundies.com/scale-an-image-in-c-sharp-preserving-aspect-ratio/
        private Bitmap ScaleImage(Bitmap bmp, int maxWidth, int maxHeight)
        {
            var ratioX = (double)maxWidth / bmp.Width;
            var ratioY = (double)maxHeight / bmp.Height;
            var ratio = Math.Min(ratioX, ratioY);

            var newWidth = (int)(bmp.Width * ratio);
            var newHeight = (int)(bmp.Height * ratio);

            var newImage = new Bitmap(newWidth, newHeight);

            using (var graphics = Graphics.FromImage(newImage))
                graphics.DrawImage(bmp, 0, 0, newWidth, newHeight);

            return newImage;
        }

        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            var cb = (ComboBox)sender;
            var index = cb.SelectedIndex;
            button1.Enabled = false;

            if (index < 0)
            {
            }
            else
            {
                comboBox2.Items.Clear();
                var vcd = new VideoCaptureDevice(videoDevices[index].MonikerString);
                foreach (VideoCapabilities vc in vcd.VideoCapabilities)
                {
                    var item = vc.FrameSize.Width + ", " + vc.FrameSize.Height + " " + vc.AverageFrameRate + "fps";
                    comboBox2.Items.Add(item);
                }
                vcd = null;
                comboBox2.Enabled = true;
                comboBox2.SelectedIndex = 0;
            }
        }

        private void comboBox2_SelectedIndexChanged(object sender, EventArgs e)
        {
            var cb = (ComboBox)sender;
            var index = cb.SelectedIndex;
            if (index < 0)
            {
                button1.Enabled = false;
            }
            else
            {
                button1.Enabled = true;
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (videoCapture != null && videoCapture.IsRunning)
            {
                StopCamera();
                button1.Text = "Start";
                button2.Enabled = false;
                comboBox1.Enabled = true;
                comboBox2.Enabled = true;
            }
            else
            {
                StartCamera();
                button1.Text = "Stop";
                button2.Enabled = true;
                comboBox1.Enabled = false;
                comboBox2.Enabled = false;
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            capture = true;
        }

        private void TimerEvent()
        {
            timer1.Stop();
            pictureBox2.Visible = false;
        }
    }
}


●音声付きの動画のキャプチャは動画と音声を別々にファイルに書き出して FFmpeg.exeで合体する方法で解決できました

 これで OBS Studioを捨てる事ができる!

using Accord.Audio;
using Accord.Audio.Generators;
using Accord.DirectSound;

    private AudioDeviceCollection audioDevices;
    private AudioCaptureDevice audioDevice;
    private BinaryWriter audioFileWrite;

    {
        audioDevices = new AudioDeviceCollection(AudioDeviceCategory.Capture);
        foreach (var ad in audioDevices)
        {
            comboBox3.Items.Add(ad.Description);
        }
    }

    private void StartCamera()
    {
        // MS2109の 96kHz モノラルでキャプチャする
        audioDevice = new AudioCaptureDevice(audioDevices.ElementAt(index3));
        audioDevice.Format = SampleFormat.Format32BitIeeeFloat;
        audioDevice.SampleRate = 96000; // 96.0kHz
        audioDevice.DesiredFrameSize = 4096;
        audioDevice.Channels = 1;
        audioDevice.NewFrame += Audio_NewFrame;
        audioDevice.Start();

        lock (syncObj)
        {
            audioFileWrite = new BinaryWriter(new FileStream("xxxx.raw", FileMode.CreateNew));
        }
    }

    void Audio_NewFrame(object sender, Accord.Audio.NewFrameEventArgs eventArgs)
    {
        lock (syncObj)
        {
            if (audioFileWrite != null)
            {
                var s = eventArgs.Signal;
                audioFileWrite.Write(s.RawData, 0, s.RawData.Length);
            }
        }
    }

    private void StopCamera()
    {
        lock (syncObj)
        {
            if (audioFileWrite != null)
            {
                audioFileWrite.Close();
                audioFileWrite = null;
            }
        }

        if (audioDevice != null)
        {
            audioDevice.NewFrame -= Audio_NewFrame;
            audioDevice.SignalToStop();
            Task task = Task.Run(() =>
            {
                audioDevice.WaitForStop();
                audioDevice = null;
            });
        }
    }
            comboBox5.Items.Add("44.1Khz Stereo");
            comboBox5.Items.Add("48Khz Stereo");
            comboBox5.Items.Add("44.1Khz Mono");
            comboBox5.Items.Add("48Khz Mono");
            comboBox5.Items.Add("96Khz Mono(MS2109)");
...
            audioDevice = new AudioCaptureDevice(audioDevices.ElementAt(index3));
            audioDevice.Format = SampleFormat.Format16Bit;
            // audioDevice.Format = SampleFormat.Format32BitIeeeFloat;

            var index5 = comboBox5.SelectedIndex;
            switch (index5) {
                case 0:
                    audioDevice.SampleRate = 44100;
                    audioDevice.Channels = 2;
                    break;
                case 1:
                    audioDevice.SampleRate = 48000;
                    audioDevice.Channels = 2;
                    break;
                case 2:
                    audioDevice.SampleRate = 44100;
                    audioDevice.Channels = 1;
                    break;
                case 3:
                    audioDevice.SampleRate = 48000;
                    audioDevice.Channels = 1;
                    break;
                case 4:
                    audioDevice.SampleRate = 96000; // 48kHz
                    audioDevice.Channels = 1; // Stereo
                    break;
            }

            audioDevice.DesiredFrameSize = 4096;
            audioDevice.NewFrame += Audio_NewFrame;
            audioDevice.Start();


● ffmpegを使って動画と音声を合体する方法

rem 動画と音声を合体させる
ffmpeg -i xxxx.mp4 -i xxxx.wav -c:v copy -c:a aac -map 0:v:0 -map 1:a:0 new_xxxx.mp4


● USB HDMIキャプチャの MS2109の 96kHz モノラルを 48kHz ステレオの wav形式に変換する
 MS2109の音声キャプチャのバグを修正します。
 ・96kHz モノラルを 48kHz ステレオにする
 ・ステレオの左右の左側右側を正しくする

rem MS2109の 96kHz モノラルを 48kHz ステレオの wav形式に変換する
ffmpeg -f f32le -ar 48000 -ac 2 -i xxxx.raw -map_channel 0.0.1 -map_channel 0.0.0 -ar 48000 -ac 2 xxxx.wav
 または、
ffmpeg -f f32le -ar 48000 -ac 2 -i xxxx.raw -af pan=stereo|c0=c1|c1=c0 -ar 48000 -ac 2 xxxx.wav

rem 動画と音声を合体させる
ffmpeg -i xxxx.mp4 -i xxxx.wav -c:v copy -c:a aac -map 0:v:0 -map 1:a:0 new_xxxx.mp4


●音声付きの動画のキャプチャは成功しませんでした 3.8.0

 WriteAudioFrameで AccessViolationExceptionや IndexOutOfRangeExceptionの例外が発生する。
 上記の別々キャプチャで動画と音声を合体する方法で回避する。

How to save Video (from webcam) and Audio (from directsound device) in avi file ? #1040

VideoFileWriter WriteAudioFrame AccessViolationException #1428
 Please help if somebody solved this puzzle.

Save both audio and video together not working #1834

        void Audio_NewFrame(object sender, Accord.Audio.NewFrameEventArgs eventArgs)
        {
            lock (syncObj)
            {
                if (videoFileWrite != null)
                {
                    var s = eventArgs.Signal;
                    // AccessViolationException
                    videoFileWrite.WriteAudioFrame(s.RawData);
                    // AccessViolationException
                    videoFileWrite.WriteAudioFrame((byte[])s.RawData.Clone());
                    // IndexOutOfRangeException
                    videoFileWrite.WriteAudioFrame(s.RawData);
                }
            }
        }


●音声付きの動画のキャプチャは成功しませんでした 3.8.2-alpha

 3.8.2-alphaを使用しても音声付きの動画ができませんでした。(音声にブチブチノイズが入る。動画のフレームレートが変)

VideoFileWriter.WriteAudioFrame - issue with stereo signal (3.8.2 prerelese) #1353

VideoFileWriter: why is my audio half the speed of my video? #600
 結論:I abandoned this framework.
 意訳:俺は Accord.NETフレームワークを捨てた


● AForge.NETフレームワーク

 Accordと類似の AForgeが有りますが、AForgeの方が古いです。
 AForge.NETフレームワークは 2013年で開発が停止しています。
AForge.NET - Computer Vision, Artificial Intelligence, Robotics
AForge.NET Framework
AForge.NET Framework

 AForge.NETフレームワークは Audio系、FFmpeg系のモジュールが有りません。

Adding audio to a video?
 There is no audio support in AForge.NET Framework.

Video Created has no Audio
 No. AForge.NET Framework never had anything to do with sound - not supported.

video file but no sounds
 The aim of the AForge.NET framework was to give tools to do image processing, computer vision tasks, some video related stuff and other things. But it was not aimed for building video players or for sound processing, so there is zero support of sound.

 意訳:AForge.NETは「画像処理」が目的なのでオーディオ系のサポートは無いよ。

DirectShow .NET
 DirectShow .NETは 2010年で開発停止。

 2022年現在も開発が続いている C#用の画像処理系のパッケージは OpenCvSharp位かな?
shimat / opencvsharp
 4.5.3.20211228
 29 Dec 2021

 手軽に動画と音声を動画ファイルとして保存できるパッケージは無いのかな?

 Nugetを FFmpegで検索すると 330個も出てくるので、どれが目的のパッケージがわからん。
330 packages returned for FFmpeg

 今の所、音声は NAudio等のパッケージを使用して、動画ファイルと音声ファイルを別々に生成してから、別途合体させる方法しか思いつかない。

 ※ 上記の通り NAudioを使わなくても AudioCaptureDeviceで音声キャプチャで解決しました。

NAudio is an open source .NET audio library written by Mark Heath



Tags: [Windows], [無人インストール]

●関連するコンテンツ(この記事を読んだ人は、次の記事も読んでいます)

USB HDMIキャプチャの MS2109でパソコンを Nintendo Switch等のゲーム機のディスプレイに変える
USB HDMIキャプチャの MS2109でパソコンを Nintendo Switch等のゲーム機のディスプレイに変える

  MS2109の映像を超低遅延で表示する方法、市販の Sh@d0wC@stや 0miP1@yと同じに使える

最近流行の 1000円で買える USBで HDMI映像信号をキャプチャする MS2109ドングルを買ってみた
最近流行の 1000円で買える USBで HDMI映像信号をキャプチャする MS2109ドングルを買ってみた

  接続してすぐに使える USB接続式 HDMI信号キャプチャ(映像+音声) MS2109使用

最近流行の 1000円で買える USBで HDMI映像信号をキャプチャする MS2109ドングルを買ってみた
最近流行の 1000円で買える USBで HDMI映像信号をキャプチャする MS2109ドングルを買ってみた

  接続してすぐに使える USB接続式 HDMI信号キャプチャ(映像+音声) MS2109使用

MS2109で有名なバグ、ステレオの音声がモノラルになるのを修正するアプリ mono-to-stereo
MS2109で有名なバグ、ステレオの音声がモノラルになるのを修正するアプリ mono-to-stereo

  MS2109の音声バグを修正する mono-to-stereoをデフォルトで日本語 Windows対応に改造してみた

.NET C#の Tweetinviライブラリで Twitterの投稿内容を投稿画像込みで丸っと取得する方法
.NET C#の Tweetinviライブラリで Twitterの投稿内容を投稿画像込みで丸っと取得する方法

  まるっと取得したかった!質問は受け付けない!

EKSA ゲーミングヘッドセットのレビュー E900Pro、2年間の品質保証
EKSA ゲーミングヘッドセットのレビュー E900Pro、2年間の品質保証

  軽量 素直な高音質 switch/PC/パソコン/Xbox One/スマホ対応、3.5mm接続、USB接続の両方に対応




[HOME] | [BACK]
リンクフリー(連絡不要、ただしトップページ以外は Web構成の変更で移動する場合があります)
Copyright (c) 2022 FREE WING,Y.Sakamoto
Powered by 猫屋敷工房 & HTML Generator

http://www.neko.ne.jp/~freewing/software/visual_c_sharp_accord_video_usb_camera_capture/