Skip to content

Commit

Permalink
Refactored VideoCapture, basic app code moved to common reprostim::C…
Browse files Browse the repository at this point in the history
…aptureApp class #61
  • Loading branch information
vmdocua committed Jan 22, 2024
1 parent 44996f9 commit 369178d
Show file tree
Hide file tree
Showing 6 changed files with 447 additions and 368 deletions.
1 change: 1 addition & 0 deletions Capture/capturelib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ project (capturelib)

add_library(${PROJECT_NAME} STATIC
src/CaptureLib.cpp
src/CaptureApp.cpp
)
add_library(reprostim::capturelib ALIAS ${PROJECT_NAME})

Expand Down
75 changes: 75 additions & 0 deletions Capture/capturelib/include/CaptureApp.h
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
284 changes: 284 additions & 0 deletions Capture/capturelib/src/CaptureApp.cpp
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;
}

}
18 changes: 1 addition & 17 deletions Capture/screencapture/ScreenCapture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,10 @@
#include "yaml-cpp/yaml.h"
#include "LibMWCapture/MWCapture.h"
#include "CaptureLib.h"
#include "CaptureApp.h"

using namespace reprostim;

// 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;
};

// App command-line options and args
struct AppOpts {
std::string configPath;
std::string homePath;
std::string outPath;
bool verbose = false;
};


int main1() {
int fd = open("/dev/video4", O_RDWR);
if (fd == -1) {
Expand Down
Loading

0 comments on commit 369178d

Please sign in to comment.