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.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 = 500; // UI更新時間(ms)
readonly int CVCAMERA_ID = 0; // 追加カメラのID
readonly int CVCAMERA_WIDTH = 1920; // 追加カメラの画像幅
readonly int CVCAMERA_HEIGHT = 1080; // 追加カメラの画像高さ
List<ISCamera> _Cameras; // ISカメラオブジェクト
SensorData _Sensor; // センサーオブジェクト
CvCamera _CvCamera; // Cvカメラオブジェクト
Task _TriggerThread; // トリガースレッド
Task _SerialThread; // シリアル通信スレッド
Task _CvCameraThread; // opencvカメラスレッド
Task _RecodingThread; // 録画スレッド
List<SoundPlayer> _Sounds; // 音源オブジェクト
public Setting Setting { get; private set; } // 設定ダイアログ
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 OutputDir { get { return Path.Combine(Setting.SaveDir , $"rec{RecodingTimeStr}_{TxtSubjectName.Text}_{TxtCounter.Text}"); } } // データ保存フォルダ
public bool IsReverseBlood { get; private set; } = false;
/// <summary>
/// コンストラクタ
/// </summary>
public MainForm() {
MediaFoundationApi.Startup();
InitializeComponent();
_Cameras = new List<ISCamera>();
_Sensor = new SensorData(this, PLOT_LENGTH);
_CvCamera = new CvCamera(this);
Setting = new Setting();
_Sounds = new List<SoundPlayer>();
_Sounds.Add(new SoundPlayer(@"StartRecoding.wav"));
_Sounds.Add(new SoundPlayer(@"StopRecoding.wav"));
_Sounds.Add(new SoundPlayer(@"EndSaving.wav"));
_Sounds.Add(new SoundPlayer(@"ReverseBlood.wav"));
_Sounds.Add(new SoundPlayer(@"SnapShot.wav"));
_Sounds.ForEach(s => { s.Load(); });
}
/// <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());
// タイマー&スレッド起動
_TriggerThread = Task.Run(TriggerThread);
_SerialThread = Task.Run(SerialThread);
_CvCameraThread = Task.Run(CvCameraThread);
// コントロール変数初期化
#if DEBUG
TxtSubjectName.Text = "デバッグ";
#endif
this.UpdateControlState();
}
/// <summary>
/// ソフトウェアトリガー生成スレッド
/// UI更新も行う
/// </summary>
private void TriggerThread() {
var swatch = new Stopwatch();
swatch.Start();
var lastUIUpdate = DateTime.Now;
while (State != STATE.Exit) {
var interval = (long)(1000F / Setting.SWTriggerFPS);
while (State != STATE.Exit) {
if (swatch.ElapsedMilliseconds >= interval) break;
}
swatch.Restart();
_Cameras.ForEach(c => c.SWTrigger());
// UI更新
var now = DateTime.Now;
if ((now - lastUIUpdate).TotalMilliseconds > UI_UPDATE_INTERVAL) {
this.UpdateTextInfo();
lastUIUpdate = now;
}
}
}
/// <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.Connect();
_Sensor.Loop();
}
/// <summary>
/// UI更新
/// </summary>
public void UpdateTextInfo() {
if (State == STATE.Exit) return;
this.Invoke((MethodInvoker)delegate {
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 検出対象:{Setting.RBDetectCh}";
TxtCvCamera.Text = $"追加カメラ {_CvCamera.CameraInfo()}";
if (State == STATE.Init && _CvCamera.CurrentFPS > 0) {
State = STATE.Idle;
this.UpdateControlState();
}
if (State == STATE.Recoding) {
var elapse = DateTime.Now - RecodingTime;
LblState.Text = $"録画中 {elapse.TotalSeconds:0}s";
}
});
}
/// <summary>
/// 状態表示の更新
/// </summary>
private void UpdateControlState() {
switch (State) {
case STATE.Init:
LblState.Text = "初期化中";
PnlState.BackColor = Color.LightBlue;
BtnRecodeMovie.Text = $"{ButtonSymbol(Setting.ACTION.movie)}録画";
BtnRecodeMovie.Enabled = false;
BtnSnapImage.Enabled = false;
BtnSetProperty.Enabled = true;
BtnNextSubject.Enabled = true;
BtnPrevSubject.Enabled = true;
BtnSetting.Enabled = true;
TxtSubjectName.ReadOnly = false;
TxtCounter.ReadOnly = false;
break;
case STATE.Idle:
LblState.Text = "待機中";
PnlState.BackColor = Color.MediumSpringGreen;
BtnRecodeMovie.Text = $"{ButtonSymbol(Setting.ACTION.movie)}録画 {Setting.RecodingLimit:0}s";
BtnRecodeMovie.Enabled = true;
BtnSnapImage.Enabled = true;
BtnSetProperty.Enabled = true;
BtnNextSubject.Enabled = true;
BtnPrevSubject.Enabled = true;
BtnSetting.Enabled = true;
TxtSubjectName.ReadOnly = false;
TxtCounter.ReadOnly = false;
break;
case STATE.Recoding:
LblState.Text = $"録画中";
PnlState.BackColor = Color.OrangeRed;
BtnRecodeMovie.Text = $"{ButtonSymbol(Setting.ACTION.movie)}停止";
BtnRecodeMovie.Enabled = true;
BtnSnapImage.Enabled = false;
BtnSetProperty.Enabled = false;
BtnNextSubject.Enabled = false;
BtnPrevSubject.Enabled = false;
BtnSetting.Enabled = false;
TxtSubjectName.ReadOnly = true;
TxtCounter.ReadOnly = true;
break;
case STATE.Saving:
LblState.Text = "保存中";
PnlState.BackColor = Color.Goldenrod;
BtnRecodeMovie.Text = $"{ButtonSymbol(Setting.ACTION.movie)}停止";
BtnRecodeMovie.Enabled = false;
BtnSnapImage.Enabled = false;
BtnSetProperty.Enabled = false;
BtnNextSubject.Enabled = false;
BtnPrevSubject.Enabled = false;
BtnSetting.Enabled = false;
TxtSubjectName.ReadOnly = true;
TxtCounter.ReadOnly = true;
break;
}
BtnSnapImage.Text = $"{ButtonSymbol(Setting.ACTION.image)}撮影";
}
/// <summary>
/// 録画開始・中断
/// </summary>
private 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()
{
// 録画開始
_Sounds[0].Play();
this.Invoke((MethodInvoker)delegate {
for (var i = 0; i < _Cameras.Count; i++) {
if (Setting.RecCameras[i]) _Cameras[i].ChangeSink(true);
}
RecodingTime = DateTime.Now;
Directory.CreateDirectory(OutputDir);
State = STATE.Recoding;
this.UpdateControlState();
});
_Sensor.StartRecoding(TxtSubjectName.Text);
// 録画
Task[] tasks = new Task[_Cameras.Count];
for (var i = 0; i < _Cameras.Count; i++) {
if (Setting.RecCameras[i]) {
var cam = _Cameras[i];
tasks[i] = Task.Run(() => cam.RecordToMemory(Setting.RecodingLimit));
} else {
tasks[i] = Task.Run(() => { });
}
}
Task.WaitAll(tasks);
// 保存準備
_Sounds[1].Play();
_Sensor.StopRecoding();
IsReverseBlood = false;
this.Invoke((MethodInvoker)delegate {
State = STATE.Saving;
this.UpdateControlState();
});
// 保存
for (var i = 0; i < _Cameras.Count; i++)
{
if (Setting.RecCameras[i]) {
var cam = _Cameras[i];
tasks[i] = Task.Run(() => cam.SaveToFile());
} else {
tasks[i] = Task.Run(() => { });
}
}
Task.WaitAll(tasks);
if (Setting.RecCameras[4]) _CvCamera.SaveToFile();
// プレビューモードへ変更
this.Invoke((MethodInvoker)delegate {
for (var i = 0; i < _Cameras.Count; i++) {
if (Setting.RecCameras[i]) _Cameras[i].ChangeSink(false);
}
TxtCounter.Text = (int.Parse(TxtCounter.Text) + 1).ToString();
State = STATE.Idle;
this.UpdateControlState();
});
_Sounds[2].Play();
Debug.WriteLine("_RecodingThread ends");
}
/// <summary>
/// 逆血時
/// </summary>
public void ReverseBlood() {
IsReverseBlood = true;
_Sounds[3].Play();
}
/// <summary>
/// 静止画撮影
/// </summary>
private void SnapImage() {
if (State != STATE.Idle) return;
_Sounds[4].Play();
var outDir = Path.Combine(Setting.SaveDir, "image");
Directory.CreateDirectory(outDir);
this.Invoke((MethodInvoker)delegate {
for (var i = 0; i < _Cameras.Count; i++) {
if (Setting.SnapCameras[i]) _Cameras[i].SnapImage();
}
});
if (Setting.SnapCameras[4]) _CvCamera.Snap();
}
/// <summary>
/// 動画撮影ボタン
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnRecodeMovie_Click(object sender, EventArgs e) {
StartRecoding();
}
/// <summary>
/// 静止画撮影ボタン
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnSnapImage_Click(object sender, EventArgs e) {
this.SnapImage();
}
/// <summary>
/// ハードウェアボタン押下時
/// </summary>
public void HWButton() {
if (Setting.ButtonAction == Setting.ACTION.movie) {
StartRecoding();
} else {
SnapImage();
}
}
/// <summary>
/// プログラム終了
/// </summary>
private void ExitProgram() {
State = STATE.Exit;
const int timeout = 2000;
_SerialThread.Wait(timeout);
Debug.WriteLine("_SerialThread ends");
_CvCameraThread.Wait(timeout);
Debug.WriteLine("_CvCameraThread ends");
_TriggerThread.Wait(timeout);
Debug.WriteLine("_TriggerThread ends");
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 BtnSetting_Click(object sender, EventArgs e) {
for (int i = 0; i < _Cameras.Count; i++) {
Setting.ConsumedMemoryPerSecond[i] = _Cameras[i].ConsumedMemoryPerSecond();
}
Setting.ConsumedMemoryPerSecond[4] = _CvCamera.ConsumedMemoryPerSecond();
var lastTriggerSetting = Setting.SWTrigger;
Setting.ShowDialog();
this.UpdateControlState();
if (Setting.SWTrigger != lastTriggerSetting) {
_Cameras.ForEach(c => c.SetTriggerMode(Setting.SWTrigger));
}
}
/// <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>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BtnNextSubject_Click(object sender, EventArgs e) {
TxtSubjectName.Text = Setting.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 = Setting.Previous(TxtSubjectName.Text);
TxtCounter.Text = "1";
}
/// <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>
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="action"></param>
/// <returns></returns>
private string ButtonSymbol(Setting.ACTION action) {
return Setting.ButtonAction == action ? "● " : "";
}
/// <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>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SplitterMoved(object sender, SplitterEventArgs e) {
_Cameras.ForEach(c => c.SetDisplaySize());
}
}
}