-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactored VideoCapture, basic app code moved to common reprostim::C…
…aptureApp class #61
- Loading branch information
Showing
6 changed files
with
447 additions
and
368 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
#ifndef CAPTURE_CAPTUREAPP_H | ||
#define CAPTURE_CAPTUREAPP_H | ||
|
||
namespace reprostim { | ||
|
||
struct FfmpegOpts { | ||
std::string a_fmt; | ||
std::string a_nchan; | ||
std::string a_dev; | ||
bool has_a_dev = false; | ||
std::string a_opt; | ||
std::string v_fmt; | ||
std::string v_opt; | ||
std::string v_dev; | ||
bool has_v_dev = false; | ||
std::string v_enc; | ||
std::string pix_fmt; | ||
std::string n_threads; | ||
std::string a_enc; | ||
std::string out_fmt; | ||
}; | ||
|
||
// App configuration loaded from config.yaml, for | ||
// historical reasons keep names in Python style | ||
struct AppConfig { | ||
std::string device_serial_number; | ||
bool has_device_serial_number = false; | ||
std::string video_device_path_pattern; | ||
FfmpegOpts ffm_opts; | ||
}; | ||
|
||
// App command-line options and args | ||
struct AppOpts { | ||
std::string configPath; | ||
std::string homePath; | ||
std::string outPath; | ||
bool verbose = false; | ||
}; | ||
|
||
|
||
class CaptureApp { | ||
protected: | ||
// setup data | ||
std::string appName; | ||
|
||
// config data | ||
AppOpts opts; | ||
AppConfig cfg; | ||
bool verbose; | ||
|
||
// session runtime data | ||
std::string configHash; | ||
std::string frameRate; | ||
std::string init_ts; | ||
int recording; | ||
std::string start_ts; | ||
MWCAP_VIDEO_SIGNAL_STATUS vssCur; // current video signal status | ||
MWCAP_VIDEO_SIGNAL_STATUS vssPrev; // previous video signal status | ||
VideoDevice targetDev; | ||
std::string targetBusInfo; | ||
std::string targetVideoDevPath; | ||
std::string targetAudioDevPath; | ||
|
||
|
||
|
||
public: | ||
virtual bool loadConfig(AppConfig& cfg, const std::string& pathConfig); | ||
virtual void onCaptureStart(); | ||
virtual void onCaptureStop(const std::string& message); | ||
virtual int parseOpts(AppOpts& opts, int argc, char* argv[]); | ||
int run(int argc, char* argv[]); | ||
}; | ||
|
||
} | ||
#endif //CAPTURE_CAPTUREAPP_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,284 @@ | ||
#include <iostream> | ||
#include <iostream> | ||
#include <iomanip> | ||
#include <sstream> | ||
#include <filesystem> | ||
#include <stdio.h> | ||
#include <string.h> | ||
#include <unistd.h> | ||
#include <glob.h> | ||
#include <fcntl.h> | ||
#include <ctime> | ||
#include <cmath> | ||
#include <memory> | ||
#include <stdexcept> | ||
#include <string> | ||
#include <regex> | ||
#include <array> | ||
#include <cstring> | ||
#include <chrono> | ||
#include <csignal> | ||
#include <thread> | ||
#include <sysexits.h> | ||
#include <alsa/asoundlib.h> | ||
#include "yaml-cpp/yaml.h" | ||
#include "LibMWCapture/MWCapture.h" | ||
#include "CaptureLib.h" | ||
#include "CaptureApp.h" | ||
|
||
namespace reprostim { | ||
|
||
void signalHandler(int signum) { | ||
//_INFO("Signal received: " << signum); | ||
if (signum == SIGINT) { | ||
_INFO("Ctrl+C received. Terminating..."); | ||
if(isSysBreakExec() ) { | ||
_INFO("Force exit, 2nd attempt"); | ||
std::exit(signum); | ||
} | ||
setSysBreakExec(true); | ||
} | ||
if( signum == SIGTERM || signum == SIGKILL ) { | ||
_INFO("Termination or kill: " << signum); | ||
if(isSysBreakExec() ) { | ||
_INFO("Force exit, 2nd attempt"); | ||
std::exit(signum); | ||
} | ||
setSysBreakExec(true); | ||
} | ||
} | ||
|
||
bool CaptureApp::loadConfig(AppConfig& cfg, const std::string& pathConfig) { | ||
YAML::Node doc; | ||
try { | ||
doc = YAML::LoadFile(pathConfig); | ||
} catch(const std::exception& e) { | ||
_ERROR("ERROR[008]: Failed load/parse config file " | ||
<< pathConfig << ": " << e.what()); | ||
return false; | ||
} | ||
|
||
if( doc["device_serial_number"] ) { | ||
cfg.device_serial_number = doc["device_serial_number"].as<std::string>(); | ||
cfg.has_device_serial_number = !cfg.device_serial_number.empty() && | ||
cfg.device_serial_number!="auto"; | ||
} else { | ||
cfg.has_device_serial_number = false; | ||
} | ||
|
||
if( doc["video_device_path_pattern"] ) { | ||
cfg.video_device_path_pattern = doc["video_device_path_pattern"].as<std::string>(); | ||
} | ||
|
||
if( doc["ffm_opts"] ) { | ||
YAML::Node node = doc["ffm_opts"]; | ||
FfmpegOpts& opts = cfg.ffm_opts; | ||
opts.a_fmt = node["a_fmt"].as<std::string>(); | ||
opts.a_nchan = node["a_nchan"].as<std::string>(); | ||
opts.a_opt = node["a_opt"].as<std::string>(); | ||
opts.a_dev = node["a_dev"].as<std::string>(); | ||
opts.has_a_dev = !opts.a_dev.empty() && opts.a_dev != "auto"; | ||
opts.v_fmt = node["v_fmt"].as<std::string>(); | ||
opts.v_opt = node["v_opt"].as<std::string>(); | ||
opts.v_dev = node["v_dev"].as<std::string>(); | ||
opts.v_enc = node["v_enc"].as<std::string>(); | ||
opts.has_v_dev = !opts.v_dev.empty() && opts.v_dev != "auto"; | ||
opts.pix_fmt = node["pix_fmt"].as<std::string>(); | ||
opts.n_threads = node["n_threads"].as<std::string>(); | ||
opts.a_enc = node["a_enc"].as<std::string>(); | ||
opts.out_fmt = node["out_fmt"].as<std::string>(); | ||
} | ||
return true; | ||
} | ||
|
||
void CaptureApp::onCaptureStart() { | ||
_INFO("TODO: onCaptureStart"); | ||
} | ||
|
||
void CaptureApp::onCaptureStop(const std::string& message) { | ||
_INFO("TODO: onCaptureStop" << message); | ||
} | ||
|
||
int CaptureApp::parseOpts(AppOpts& opts, int argc, char* argv[]) { | ||
_INFO("TODO: parseOpts"); | ||
return EX_OK; | ||
} | ||
|
||
int CaptureApp::run(int argc, char* argv[]) { | ||
|
||
std::signal(SIGINT, signalHandler); | ||
std::signal(SIGTERM, signalHandler); | ||
std::signal(SIGKILL, signalHandler); | ||
|
||
const int res1 = parseOpts(opts, argc, argv); | ||
verbose = opts.verbose; | ||
|
||
if( res1==1 ) return EX_OK; // help message | ||
if( res1!=EX_OK ) return res1; | ||
|
||
_VERBOSE("Config file: " << opts.configPath); | ||
|
||
if( !loadConfig(cfg, opts.configPath) ) { | ||
// config.yaml load/parse problems | ||
return EX_CONFIG; | ||
} | ||
|
||
_VERBOSE("Output path: " << opts.outPath); | ||
if( !checkOutDir(verbose, opts.outPath) ) { | ||
// invalid output path | ||
_ERROR("ERROR[009]: Failed create/locate output path: " << opts.outPath); | ||
return EX_CANTCREAT; | ||
} | ||
|
||
const int res2 = checkSystem(verbose); | ||
if( res2!=EX_OK ) { | ||
// problem with system configuration and installed packages | ||
return res2; | ||
} | ||
|
||
// calculated options | ||
configHash = getFileChangeHash(opts.configPath); | ||
|
||
// current video signal status | ||
vssCur = {}; | ||
|
||
// previous video signal status | ||
vssPrev = {}; | ||
|
||
bool fRun = true; | ||
bool fConfigChanged = false; | ||
recording = 0; | ||
|
||
MW_RESULT mr = MW_SUCCEEDED; | ||
|
||
init_ts = getTimeStr(); | ||
_INFO(init_ts << ": <><><> Starting " << appName << " <><><>"); | ||
_INFO(" <> Saving output to ===> " << opts.outPath); | ||
_INFO(" <> Recording from Video Device ===> " << cfg.ffm_opts.v_dev | ||
<< ", S/N=" << (cfg.has_device_serial_number?cfg.device_serial_number:"auto")); | ||
_INFO(" <> Recording from Audio Device ===> " << cfg.ffm_opts.a_dev); | ||
|
||
if( cfg.has_device_serial_number ) { | ||
_VERBOSE("Use device with specified S/N: " << cfg.device_serial_number); | ||
} else { | ||
_VERBOSE("Use any first available Magewell USB Capture device"); | ||
} | ||
|
||
BOOL fInit = MWCaptureInitInstance(); | ||
if( !fInit ) | ||
_ERROR("ERROR[005]: Failed MWCaptureInitInstance"); | ||
|
||
_VERBOSE("MWCapture SDK version: " << mwcSdkVersion()); | ||
|
||
do { | ||
SLEEP_SEC(1); | ||
HCHANNEL hChannel = NULL; | ||
if( !findTargetVideoDevice(opts.verbose, | ||
cfg.has_device_serial_number?cfg.device_serial_number:"", | ||
targetDev) ) { | ||
onCaptureStop(":\tStopped recording. No channels!"); | ||
_VERBOSE("Wait, no channels found"); | ||
continue; | ||
} | ||
|
||
if( targetDev.channelIndex<0 ) { | ||
_VERBOSE("Wait, no valid USB devices found"); | ||
continue; | ||
} | ||
|
||
_VERBOSE("Found target device: " << vdToString(targetDev)); | ||
|
||
char wPath[256] = {0}; | ||
mr = MWGetDevicePath(targetDev.channelIndex, wPath); | ||
_VERBOSE("Device path: " << wPath); | ||
|
||
// TODO: check res | ||
hChannel = MWOpenChannelByPath(wPath); | ||
|
||
// TODO: check res | ||
MWGetVideoSignalStatus(hChannel, &vssCur); | ||
|
||
frameRate = vssFrameRate(vssCur); | ||
|
||
// just dump current video signal status | ||
_VERBOSE(vssToString(vssCur) << ". frameRate=" << frameRate); | ||
|
||
if ( ( vssCur.cx > 0 ) && ( vssCur.cx < 9999 ) && (vssCur.cy > 0) && (vssCur.cy < 9999)) { | ||
if (recording == 0) { | ||
// find target video device name/path when not specified explicitly | ||
if( cfg.ffm_opts.has_v_dev ) { | ||
targetVideoDevPath = cfg.ffm_opts.v_dev; | ||
targetBusInfo = "N/A"; | ||
} else { | ||
VDevPath vdp = getVideoDevicePathBySerial(opts.verbose, | ||
cfg.video_device_path_pattern, | ||
targetDev.serial); | ||
targetVideoDevPath = vdp.path; | ||
targetBusInfo = vdp.busInfo; | ||
if( targetVideoDevPath.empty() ) { | ||
targetVideoDevPath = "/dev/video_not_found_911"; | ||
_ERROR("ERROR[007]: video device path not found by S/N: " << targetDev.serial | ||
<< ", use fallback one: " << targetVideoDevPath); | ||
} | ||
} | ||
|
||
if( !cfg.ffm_opts.has_v_dev || !cfg.has_device_serial_number ) { | ||
_INFO(" <> Found Video Device ===> " | ||
<< targetVideoDevPath << ", S/N: " << targetDev.serial | ||
<< ", busInfo: " << targetBusInfo | ||
<< ", " << targetDev.name); | ||
} | ||
|
||
if( cfg.ffm_opts.has_a_dev ) { | ||
targetAudioDevPath = cfg.ffm_opts.a_dev; | ||
} else { | ||
targetAudioDevPath = getAudioDevicePath(opts.verbose, targetBusInfo); | ||
_INFO(" <> Found Audio Device ===> " << targetAudioDevPath); | ||
} | ||
_VERBOSE("Target ALSA audio device path: " << targetAudioDevPath); | ||
|
||
onCaptureStart(); | ||
} | ||
else { | ||
if( !vssEquals(vssCur, vssPrev) ) { | ||
onCaptureStop(":\tStopped recording because something changed."); | ||
} | ||
} | ||
} | ||
else { | ||
_VERBOSE("No valid video signal detected from target device"); | ||
|
||
std::ostringstream message; | ||
message << ":\tWhack resolution: " << vssCur.cx << "x" << vssCur.cy; | ||
message << ". Stopped recording"; | ||
onCaptureStop(message.str()); | ||
} | ||
|
||
vssPrev = vssCur; | ||
safeMWCloseChannel(hChannel); | ||
|
||
// check config changed | ||
std::string configHash2 = getFileChangeHash(opts.configPath); | ||
if( configHash!=configHash2 ) { | ||
_INFO("Config file was modified (" << configHash << " -> " << configHash2 << ") : " << opts.configPath); | ||
_INFO("Reloading config and restarting capture ..."); | ||
fConfigChanged = true; | ||
fRun = false; | ||
} | ||
} while (fRun && !isSysBreakExec()); | ||
|
||
onCaptureStop("Program terminated"); | ||
|
||
if( fInit ) | ||
MWCaptureExitInstance(); | ||
|
||
if( isSysBreakExec() ) | ||
return EX_SYS_BREAK_EXEC; | ||
|
||
if( fConfigChanged ) | ||
return EX_CONFIG_RELOAD; | ||
|
||
return EX_OK; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.