using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
using System.Reflection;
using System.IO;
using NAudio.MediaFoundation;
using System.Windows.Forms.DataVisualization.Charting;
using System.Threading;
using System.Media;
namespace ISCamRecorder {
/// <summary>
/// ソフトウェアの状態
/// </summary>
public enum STATE {
Init, // 初期化中
Idle, // 待機中
Recoding, // 録画中
Saving, // 保存中
Exit // 終了
}
/// <summary>
/// メインフォームクラス
/// </summary>
public partial class MainForm : Form {
readonly int PLOT_LENGTH = 200; // センサー値表示サンプル数
readonly int UI_UPDATE_INTERVAL = 2000; // UI更新時間
readonly int CVCAMERA_ID = 0; // 追加カメラのID
readonly int CVCAMERA_WIDTH = 1920; // 追加カメラの画像幅
readonly int CVCAMERA_HEIGHT = 1080; // 追加カメラの画像高さ
private System.Threading.Timer _UITimer; // UI更新タイマー
List<ISCamera> _Cameras = new List<ISCamera> (); // ISカメラオブジェクト
SensorData _Sensor; // センサーオブジェクト
CvCamera _CvCamera; // Cvカメラオブジェクト
Task _TriggerThread; // トリガースレッド
Task _SerialThread; // シリアル通信スレッド
Task _CvCameraThread; // opencvカメラスレッド
Task _RecodingThread; // 録画スレッド
float _TriggerFrameRate = 30.0F; // トリガーフレームレート
float _RecodingDulation = 0; // 録画時間
private ulong _availablePhysicalMemory; //合計物理メモリ
SubjectList _SubjectList; // 非測定者一覧フォーム
public Chart SensorChart { get { return chart1; } } // 外部からのアクセス用
public PictureBox CvCameraPic { get { return PicCvCamera; } } // 外部からのアクセス用
public STATE State { get; private set; } = STATE.Init; // ソフトウェアの状態
public DateTime RecodingTime { get; private set; } = DateTime.Now; // 録画開始時間
public string RecodingTimeStr { get { return RecodingTime.ToString("yyyyMMdd_HHmmss"); } } // 録画開始時間文字列
public string OutputBaseDir { get { return TxtOutputDir.Text; } } // データ保存親フォルダ
public string OutputDir { get { return Path.Combine(OutputBaseDir, $"rec{RecodingTimeStr}_{TxtSubjectName.Text}_{TxtCounter.Text}"); } } // データ保存フォルダ
public string ImageType { get; private set; } // 画像保存形式
public int MovieRate { get; private set; } // 動画レート
public bool SwitchEnabled { get; private set; }
/// <summary>
/// コンストラクタ
/// </summary>
public MainForm() {
MediaFoundationApi.Startup();
InitializeComponent();
_Sensor = new SensorData(this, PLOT_LENGTH);
_CvCamera = new CvCamera(this);
_SubjectList = new SubjectList();
}
/// <summary>
/// フォームロード時
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MainForm_Load(object sender, EventArgs e) {
SetTitle(this.Text);
// カメラ設定
_Cameras.Add(new ISCamera(this, icTop, "9220016"));
_Cameras.Add(new ISCamera(this, icLeft, "9220018"));
_Cameras.Add(new ISCamera(this, icFront, "9220021"));
_Cameras.Add(new ISCamera(this, icRight, "9220025"));
_Cameras.ForEach(c => c.Connect());
// タイマー&スレッド起動
_UITimer = new System.Threading.Timer(UITimerCB, this, 0, UI_UPDATE_INTERVAL);
_TriggerThread = Task.Run(TriggerThread);
_SerialThread = Task.Run(SerialThread);
_CvCameraThread = Task.Run(CvCameraThread);
// コントロール変数初期化
ImageType = CboImageType.Text;
MovieRate = int.Parse(TxtMovieRate.Text);
_RecodingDulation = float.Parse(TxtRecodingDulation.Text);
SwitchEnabled = ChkStartSW.Checked;
#if DEBUG
TxtSubjectName.Text = "デバッグ";
TxtRecodingDulation.Text = "3";
#endif
State = STATE.Idle;
}
/// <summary>
/// ソフトウェアトリガー生成スレッド
/// </summary>
private void TriggerThread() {
var swatch = new Stopwatch();
swatch.Start();
while (State != STATE.Exit) {
var interval = (long)(1000F / _TriggerFrameRate);
while (State != STATE.Exit) {
if (swatch.ElapsedMilliseconds >= interval) break;
}
swatch.Restart();
_Cameras.ForEach(c => c.SWTrigger());
}
}
/// <summary>
/// OpenCVカメラスレッド
/// </summary>
private void CvCameraThread() {
if (!_CvCamera.Open(CVCAMERA_ID, CVCAMERA_WIDTH, CVCAMERA_HEIGHT)) return;
_CvCamera.CameraLoop();
}
/// <summary>
/// シリアル通信スレッド
/// </summary>
private void SerialThread() {
if (!_Sensor.Connect()) return;
_Sensor.Loop();
}
/// <summary>
/// タイマーイベント
/// </summary>
/// <param name="obj"></param>
static void UITimerCB(object obj) {
if (((MainForm)obj).State == STATE.Exit) return;
((MainForm)obj).UpdateForm();
}
/// <summary>
/// UI更新
/// </summary>
public void UpdateForm() {
if (this.InvokeRequired) {
this.Invoke((MethodInvoker)delegate { UpdateForm(); });
return;
}
if (State != STATE.Exit) {
TxtTop.Text = $"上方カメラ {_Cameras[0].CameraInfo()}";
TxtLeft.Text = $"左方カメラ {_Cameras[1].CameraInfo()}";
TxtFront.Text = $"前方カメラ {_Cameras[2].CameraInfo()}";
TxtRight.Text = $"右方カメラ {_Cameras[3].CameraInfo()}";
TxtSensor.Text = $"逆血センサー {_Sensor.FrameRate:0.0} Hz";
TxtCvCamera.Text = $"追加カメラ {_CvCamera.CameraInfo()}";
TotalPhysicalMemory();
MemoryToUse();
_TriggerFrameRate = float.Parse(TxtTriggerFPS.Text);
}
}
/// <summary>
/// カメラプロパティの設定
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SetProperty_Click(object sender, EventArgs e) {
var config =_Cameras[0].SetProperty();
_Cameras.ForEach(c => c.SetProperty(config));
}
/// <summary>
/// フォームタイトルを設定
/// </summary>
private void SetTitle(string title) {
var version = Assembly.GetExecutingAssembly().GetName().Version;
#if DEBUG
var debug = " [DEBUG]";
#else
var debug = "";
#endif
Text = $"{title} ver {version.Major}.{version.Minor}{debug}";
}
/// <summary>
/// フォームを閉じる時
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MainForm_FormClosing(object sender, FormClosingEventArgs e) {
if (State == STATE.Idle) {
Task.Run(ExitProgram);
e.Cancel = true;
}else if(State != STATE.Exit) {
e.Cancel = true;
}
}
/// <summary>
/// サイズ変更時
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MainForm_SizeChanged(object sender, EventArgs e) {
_Cameras.ForEach(c => c.SetDisplaySize());
}
/// <summary>
/// 利用可能なメモリ量をラベルに表示
/// </summary>
private void TotalPhysicalMemory() {
Microsoft.VisualBasic.Devices.ComputerInfo info =
new Microsoft.VisualBasic.Devices.ComputerInfo();
ulong bcs = 1024 * 1024;
_availablePhysicalMemory = info.AvailablePhysicalMemory / bcs; //利用可能な物理メモリ
LblAvailableMemory.Text = "利用可能メモリ:" + _availablePhysicalMemory.ToString("#,0") + "MB";
}
/// <summary>
/// 消費メモリ量をラベルに表示
/// </summary>
private void MemoryToUse() {
var memoryConsumption = (_Cameras.Select(c => c.MemoryFor1SecRecoding()).Sum()
+ _CvCamera.MemoryFor1SecRecoding()) * float.Parse(TxtRecodingDulation.Text);
LblRecodingMemory.Text = "消費するメモリ:" + memoryConsumption.ToString("#,0") + "MB";
if (memoryConsumption > _availablePhysicalMemory && _availablePhysicalMemory != 0) LblRecodingMemory.ForeColor = Color.Red;
else LblRecodingMemory.ForeColor = Color.Black;
}
/// <summary>
/// 動画撮影
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnRecodeMovie_Click(object sender, EventArgs e) {
StartRecoding();
}
/// <summary>
/// 録画開始・中断
/// </summary>
public void StartRecoding() {
if (State == STATE.Idle) {
_RecodingThread = Task.Run(RecodingThread);
} else if (State == STATE.Recoding) {
// 中断
Debug.WriteLine("Stop recoding");
_Cameras.ForEach(c => c.StopRecoding());
}
}
/// <summary>
/// 録画スレッド
/// </summary>
private void RecodingThread()
{
// 録画開始サウンド
var player = new SoundPlayer(@"start.wav");
player.Play();
// 録画モードへ変更
Color originalButtonColor = Color.Gray;
this.Invoke((MethodInvoker)delegate {
_Cameras.ForEach(c => c.ChangeSink(true));
BtnSetProperty.Enabled = false;
BtnRecodeMovie.Text = "撮影中";
originalButtonColor = BtnRecodeMovie.BackColor;
BtnRecodeMovie.BackColor = Color.Orange;
});
// 録画開始
var frameRate = _Cameras.Select(c => c.FrameRate).Average();
var framesToCapture = (int)(_RecodingDulation * frameRate + 1.0F);
RecodingTime = DateTime.Now;
Directory.CreateDirectory(OutputDir);
State = STATE.Recoding;
// 録画
Task[] tasks = new Task[4];
for (var i = 0; i < _Cameras.Count; i++) {
var cam = _Cameras[i];
tasks[i] = Task.Run(() => cam.RecordToMemory(framesToCapture));
}
Task.WaitAll(tasks);
// 保存準備
State = STATE.Saving;
player = new SoundPlayer(@"end.wav");
player.Play();
this.Invoke((MethodInvoker)delegate {
BtnRecodeMovie.Enabled = false;
BtnRecodeMovie.Text = "保存中";
BtnRecodeMovie.BackColor = Color.Aqua;
});
// 保存
for (var i = 0; i < _Cameras.Count; i++)
{
var cam = _Cameras[i];
tasks[i] = Task.Run(() => cam.SaveToFile(frameRate));
}
Task.WaitAll(tasks);
_CvCamera.SaveToFile();
// プレビューモードへ変更
this.Invoke((MethodInvoker)delegate {
_Cameras.ForEach(c => c.ChangeSink(false));
BtnRecodeMovie.Text = "動画撮影";
BtnRecodeMovie.BackColor = originalButtonColor;
BtnRecodeMovie.Enabled = true;
BtnSetProperty.Enabled = true;
TxtCounter.Text = (int.Parse(TxtCounter.Text) + 1).ToString();
});
State = STATE.Idle;
}
/// <summary>
/// 静止画撮影ボタン
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnSnapImage_Click(object sender, EventArgs e) {
if (State != STATE.Idle) return;
var player = new SoundPlayer(@"snap.wav");
player.Play();
var outDir = Path.Combine(OutputBaseDir, "image");
Directory.CreateDirectory(outDir);
_Cameras.ForEach(c => c.SnapImage());
_CvCamera.Snap();
}
/// <summary>
/// プログラム終了
/// </summary>
private void ExitProgram() {
State = STATE.Exit;
_UITimer.Dispose();
const int timeout = 2000;
_SerialThread.Wait(timeout);
Debug.WriteLine("_SerialThread stop");
_CvCameraThread.Wait(timeout);
Debug.WriteLine("_CvCameraThread stop");
_TriggerThread.Wait(timeout);
Debug.WriteLine("_TriggerThread stop");
this.Invoke((MethodInvoker)delegate {
this.Close();
});
}
/// <summary>
/// 録画時間テキストボックスでキー押下時
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void NumberKeyOnly(object sender, KeyPressEventArgs e) {
//バックスペースが押された時は有効(Deleteキーも有効)
if (e.KeyChar == '\b' || e.KeyChar == '.') {
return;
}
//数値0~9以外が押された時はイベントをキャンセルする
if ((e.KeyChar < '0' || '9' < e.KeyChar)) {
e.Handled = true;
}
}
/// <summary>
/// トリガー使用チェック変更時
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ChkTrigger_CheckedChanged(object sender, EventArgs e) {
_Cameras.ForEach(c => c.SetTriggerMode(((CheckBox)sender).Checked));
}
/// <summary>
/// 画像形式選択時
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void CboImageType_SelectedIndexChanged(object sender, EventArgs e) {
ImageType = CboImageType.Text;
}
/// <summary>
/// 動画レート変更時
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TxtMovieRate_TextChanged(object sender, EventArgs e) {
MovieRate = int.Parse(TxtMovieRate.Text);
}
/// <summary>
/// 記録時間変更時
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TxtRecodingDulation_TextChanged(object sender, EventArgs e) {
_RecodingDulation = float.Parse(TxtRecodingDulation.Text);
}
/// <summary>
/// SW連動チェック変更
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ChkStartSW_CheckedChanged(object sender, EventArgs e) {
SwitchEnabled = ChkStartSW.Checked;
}
/// <summary>
/// 非測定者の一覧を開く
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnOpenSubjList_Click(object sender, EventArgs e) {
_SubjectList.ShowDialog();
}
/// <summary>
/// 非測定者の「次」をクリック
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnNextSubject_Click(object sender, EventArgs e) {
TxtSubjectName.Text = _SubjectList.Next(TxtSubjectName.Text);
TxtCounter.Text = "1";
}
/// <summary>
/// 非測定者の「前」をクリック
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnPrevSubject_Click(object sender, EventArgs e) {
TxtSubjectName.Text = _SubjectList.Previous(TxtSubjectName.Text);
TxtCounter.Text = "1";
}
}
}