mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-05 15:30:00 +01:00
242 lines
7.1 KiB
C++
242 lines
7.1 KiB
C++
#include <thread>
|
|
|
|
// Desktop OpenGL function loader
|
|
#include <glad/glad.h>
|
|
|
|
// gstreamer
|
|
#include <gst/gstformat.h>
|
|
#include <gst/video/video.h>
|
|
|
|
#include "defines.h"
|
|
#include "Settings.h"
|
|
#include "GstToolkit.h"
|
|
#include "SystemToolkit.h"
|
|
#include "FrameBuffer.h"
|
|
#include "Log.h"
|
|
|
|
#include "Loopback.h"
|
|
|
|
bool Loopback::system_loopback_initialized = false;
|
|
|
|
#if defined(LINUX)
|
|
|
|
/**
|
|
*
|
|
* Linux video 4 linux loopback device
|
|
*
|
|
* 1) Linux system has to have the v4l2loopback package
|
|
* See documentation at https://github.com/umlaeute/v4l2loopback
|
|
*
|
|
* $ sudo -A apt install v4l2loopback-dkms
|
|
*
|
|
* 2) User (sudo) has to install a v4l2loopback
|
|
*
|
|
* $ sudo -A modprobe v4l2loopback exclusive_caps=1 video_nr=10
|
|
*
|
|
* 3) But to do that, the user has to enter sudo passwd
|
|
*
|
|
* The command line above should be preceeded by
|
|
* export SUDO_ASKPASS="/tmp/mysudo.sh"
|
|
*
|
|
* where mysudo.sh contains the following:
|
|
* #!/bin/bash
|
|
* zenity --password --title=Authentication
|
|
*
|
|
* 4) Optionaly, we can set the dynamic properties of the stream
|
|
*
|
|
* $ sudo v4l2loopback-ctl set-caps "RGBA:640x480" /dev/video10
|
|
* $ sudo v4l2loopback-ctl set-fps 30 /dev/video10
|
|
*
|
|
* 5) Finally, the gstreamer pipeline can write into v4l2sink
|
|
*
|
|
* gst-launch-1.0 videotestsrc ! v4l2sink device=/dev/video10
|
|
*
|
|
*
|
|
* Useful command lines for debug
|
|
* $ v4l2-ctl --all -d 10
|
|
* $ gst-launch-1.0 v4l2src device=/dev/video10 ! videoconvert ! autovideosink
|
|
* $ gst-launch-1.0 videotestsrc ! v4l2sink device=/dev/video10
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
std::string Loopback::system_loopback_name = "/dev/video10";
|
|
std::string Loopback::system_loopback_pipeline = "appsrc name=src ! videoconvert ! videorate ! video/x-raw,framerate=30/1 ! v4l2sink sync=false name=sink";
|
|
|
|
bool Loopback::initializeSystemLoopback()
|
|
{
|
|
if (!Loopback::systemLoopbackInitialized()) {
|
|
|
|
// create script for asking sudo password
|
|
std::string sudoscript = SystemToolkit::full_filename(SystemToolkit::settings_path(), "sudo.sh");
|
|
FILE *file = fopen(sudoscript.c_str(), "w");
|
|
if (file) {
|
|
fprintf(file, "#!/bin/bash\n");
|
|
fprintf(file, "zenity --password --title=Authentication\n");
|
|
fclose(file);
|
|
|
|
// make script executable
|
|
int fildes = 0;
|
|
fildes = open(sudoscript.c_str(), O_RDWR);
|
|
fchmod(fildes, S_IRWXU | S_IRWXG | S_IROTH | S_IWOTH);
|
|
close(fildes);
|
|
|
|
// create command line for installing v4l2loopback
|
|
std::string cmdline = "export SUDO_ASKPASS=\"" + sudoscript + "\"\n";
|
|
cmdline += "sudo -A apt install v4l2loopback-dkms 2>&1\n";
|
|
cmdline += "sudo -A modprobe -r v4l2loopback 2>&1\n";
|
|
cmdline += "sudo -A modprobe v4l2loopback exclusive_caps=1 video_nr=10 card_label=\"vimix loopback\" 2>&1\n";
|
|
|
|
// execute v4l2 command line
|
|
std::string report;
|
|
FILE *fp = popen(cmdline.c_str(), "r");
|
|
if (fp != NULL) {
|
|
|
|
// get stdout content from command line
|
|
char linestdout[PATH_MAX];
|
|
while (fgets(linestdout, PATH_MAX, fp) != NULL)
|
|
report += linestdout;
|
|
|
|
// error reported by pclose?
|
|
if (pclose(fp) != 0 )
|
|
Log::Warning("Failed to initialize system v4l2loopback\n%s", report.c_str());
|
|
// okay, probaly all good...
|
|
else
|
|
system_loopback_initialized = true;
|
|
}
|
|
else
|
|
Log::Warning("Failed to initialize system v4l2loopback\nCannot execute command line");
|
|
|
|
}
|
|
else
|
|
Log::Warning("Failed to initialize system v4l2loopback\nCannot create script", sudoscript.c_str());
|
|
}
|
|
|
|
return system_loopback_initialized;
|
|
}
|
|
|
|
bool Loopback::systemLoopbackInitialized()
|
|
{
|
|
// test if already initialized
|
|
if (!system_loopback_initialized) {
|
|
// check the existence of loopback device
|
|
if ( SystemToolkit::file_exists(system_loopback_name) )
|
|
system_loopback_initialized = true;
|
|
}
|
|
|
|
return system_loopback_initialized;
|
|
}
|
|
|
|
#else
|
|
|
|
std::string Loopback::system_loopback_name = "undefined";
|
|
std::string Loopback::system_loopback_pipeline = "";
|
|
|
|
|
|
bool Loopback::initializeSystemLoopback()
|
|
{
|
|
system_loopback_initialized = false;
|
|
return false;
|
|
}
|
|
|
|
bool Loopback::systemLoopbackInitialized()
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
|
|
Loopback::Loopback() : FrameGrabber()
|
|
{
|
|
frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 60);
|
|
|
|
}
|
|
|
|
void Loopback::init(GstCaps *caps)
|
|
{
|
|
// ignore
|
|
if (caps == nullptr)
|
|
return;
|
|
|
|
if (!Loopback::systemLoopbackInitialized()){
|
|
Log::Warning("Loopback system shall be initialized first.");
|
|
finished_ = true;
|
|
return;
|
|
}
|
|
|
|
// create a gstreamer pipeline
|
|
std::string description = Loopback::system_loopback_pipeline;
|
|
|
|
// parse pipeline descriptor
|
|
GError *error = NULL;
|
|
pipeline_ = gst_parse_launch (description.c_str(), &error);
|
|
if (error != NULL) {
|
|
Log::Warning("Loopback Could not construct pipeline %s:\n%s", description.c_str(), error->message);
|
|
g_clear_error (&error);
|
|
finished_ = true;
|
|
return;
|
|
}
|
|
|
|
// setup device sink
|
|
g_object_set (G_OBJECT (gst_bin_get_by_name (GST_BIN (pipeline_), "sink")),
|
|
"device", Loopback::system_loopback_name.c_str(),
|
|
NULL);
|
|
|
|
// setup custom app source
|
|
src_ = GST_APP_SRC( gst_bin_get_by_name (GST_BIN (pipeline_), "src") );
|
|
if (src_) {
|
|
|
|
g_object_set (G_OBJECT (src_),
|
|
"stream-type", GST_APP_STREAM_TYPE_STREAM,
|
|
"is-live", TRUE,
|
|
"format", GST_FORMAT_TIME,
|
|
// "do-timestamp", TRUE,
|
|
NULL);
|
|
|
|
// Direct encoding (no buffering)
|
|
gst_app_src_set_max_bytes( src_, 0 );
|
|
|
|
// instruct src to use the required caps
|
|
caps_ = gst_caps_copy( caps );
|
|
gst_app_src_set_caps (src_, caps_);
|
|
|
|
// setup callbacks
|
|
GstAppSrcCallbacks callbacks;
|
|
callbacks.need_data = FrameGrabber::callback_need_data;
|
|
callbacks.enough_data = FrameGrabber::callback_enough_data;
|
|
callbacks.seek_data = NULL; // stream type is not seekable
|
|
gst_app_src_set_callbacks (src_, &callbacks, this, NULL);
|
|
|
|
}
|
|
else {
|
|
Log::Warning("Loopback Could not configure source");
|
|
finished_ = true;
|
|
return;
|
|
}
|
|
|
|
// start recording
|
|
GstStateChangeReturn ret = gst_element_set_state (pipeline_, GST_STATE_PLAYING);
|
|
if (ret == GST_STATE_CHANGE_FAILURE) {
|
|
Log::Warning("Loopback Could not open %s", Loopback::system_loopback_name.c_str());
|
|
finished_ = true;
|
|
return;
|
|
}
|
|
|
|
// all good
|
|
#if defined(LINUX)
|
|
Log::Notify("Loopback started (v4l2loopback on %s)", Loopback::system_loopback_name.c_str());
|
|
#else
|
|
Log::Notify("Loopback started (%s)", Loopback::system_loopback_name.c_str());
|
|
#endif
|
|
// start
|
|
active_ = true;
|
|
}
|
|
|
|
void Loopback::terminate()
|
|
{
|
|
Log::Notify("Loopback to %s terminated.", Loopback::system_loopback_name.c_str());
|
|
}
|