3 Commits

  1. 2
      .gitignore
  2. 1
      .kdev4/screen-recorder.kdev4
  3. 2
      Makefile
  4. 2
      README.md
  5. 6
      config.cfg
  6. 34
      main-window.cpp
  7. 9
      main-window.hpp
  8. 2
      main.cpp
  9. 132
      obs-manager.cpp
  10. 38
      obs-manager.hpp
  11. 3
      preview-window.cpp
  12. 1
      settings-manager.hpp
  13. 206
      settings-window.cpp
  14. 4
      settings-window.hpp

2
.gitignore

@ -1,3 +1,5 @@
config.cfg
# Created by https://www.toptal.com/developers/gitignore/api/linux,c++
# Edit at https://www.toptal.com/developers/gitignore?templates=linux,c++

1
.kdev4/screen-recorder.kdev4

@ -65,6 +65,7 @@ Name=GCC
5=/usr/include/glibmm-2.4/
6=/usr/include/gdkmm-3.0/
7=/usr/include/gtk-3.0/
8=/usr/include/pulse/
[Launch]
Launch Configurations=Launch Configuration 0

2
Makefile

@ -1,6 +1,6 @@
CC = g++
CFLAGS = -g -Wall -std=c++17
GCFLAGS = `pkg-config --cflags --libs gtkmm-3.0` -lobs -lv4l2 -lglut -lGL
GCFLAGS = `pkg-config --cflags --libs gtkmm-3.0` -lobs -lv4l2 -lglut -lGL -lasound -lpulse
OBJFILES = main.o main-window.o obs-manager.o preview-window.o settings-manager.o settings-window.o
TARGET = screenrecorder

2
README.md

@ -2,7 +2,7 @@
## Dependencies
`apt install libobs-dev libgtkmm-3.0-dev libv4l-dev mesa-common-dev freeglut3-dev`
`apt install libobs-dev libgtkmm-3.0-dev libv4l-dev mesa-common-dev freeglut3-dev libasound2-dev libpulse-dev`
## Build

6
config.cfg

@ -1,6 +0,0 @@
plugin_dir=/usr/lib/x86_64-linux-gnu/obs-plugins/
output_dir=/home/parallels/Projects/Progrium/screen-recorder
screen_enabled=true
webcam_enabled=true
video_device_id=/dev/video2
audio_enabled=true

34
main-window.cpp

@ -7,7 +7,7 @@
using namespace std;
MainWindow::MainWindow(OBSManager *obs)
MainWindow::MainWindow(Glib::RefPtr<Gtk::Application> app, OBSManager *obs)
: mBoxMain(Gtk::Orientation::ORIENTATION_VERTICAL, 2),
mButtonPreview("Start Preview"),
mButtonStart("Start Recording"),
@ -21,9 +21,11 @@ MainWindow::MainWindow(OBSManager *obs)
set_default_size(1048, 720);
set_border_width(10);
mApp = app;
mOBS = obs;
mOBS->startRecording.connect(sigc::mem_fun(*this, &MainWindow::recordingStarted));
mOBS->stopRecording.connect(sigc::mem_fun(*this, &MainWindow::recordingStopped));
mOBS->sigStartPreview.connect(sigc::mem_fun(*this, &MainWindow::onPreviewStarted));
mOBS->sigStartRecording.connect(sigc::mem_fun(*this, &MainWindow::onRecordingStarted));
mOBS->sigStopRecording.connect(sigc::mem_fun(*this, &MainWindow::onRecordingStopped));
mButtonPreview.signal_clicked().connect(
sigc::mem_fun(*this, &MainWindow::onPreviewClicked));
@ -34,6 +36,8 @@ MainWindow::MainWindow(OBSManager *obs)
mButtonExit.signal_clicked().connect(
sigc::mem_fun(*this, &MainWindow::onExitClicked));
mButtonStart.set_sensitive(false);
mBoxMain.add(mButtonPreview);
mBoxMain.add(mButtonStart);
mBoxMain.add(mButtonSettings);
@ -48,7 +52,12 @@ MainWindow::MainWindow(OBSManager *obs)
version.append(obs->GetVersion());
mLabelVersion.set_text(version);
mOBS->Initialize();
Gdk::Rectangle rect;
auto screen = get_screen();
auto monitor = screen->get_monitor_at_window(screen->get_active_window());
screen->get_monitor_geometry(monitor, rect);
mOBS->Initialize(rect);
}
MainWindow::~MainWindow()
@ -77,16 +86,27 @@ void MainWindow::onSettingsClicked()
void MainWindow::onExitClicked()
{
mOBS->sigCleanup.connect(sigc::mem_fun(*this, &MainWindow::onCleanupDone));
mOBS->Cleanup();
Gtk::Main::quit();
}
void MainWindow::recordingStarted()
void MainWindow::onCleanupDone()
{
//Gtk::Main::quit();
mApp->quit();
}
void MainWindow::onPreviewStarted()
{
mButtonStart.set_sensitive(true);
}
void MainWindow::onRecordingStarted()
{
mButtonStart.set_label("Stop Recording");
}
void MainWindow::recordingStopped()
void MainWindow::onRecordingStopped()
{
mButtonStart.set_label("Start Recording");
}

9
main-window.hpp

@ -10,14 +10,16 @@
class MainWindow : public Gtk::Window
{
public:
MainWindow(OBSManager *obs);
MainWindow(Glib::RefPtr<Gtk::Application> app, OBSManager *obs);
virtual ~MainWindow();
private:
Glib::RefPtr<Gtk::Application> mApp;
OBSManager *mOBS;
void onPreviewClicked();
void onStartClicked();
void onSettingsClicked();
void onExitClicked();
void onCleanupDone();
Gtk::Box mBoxMain;
Gtk::Button mButtonPreview;
Gtk::Button mButtonStart;
@ -28,8 +30,9 @@ private:
PreviewWindow mPreviewWindow;
// Signals
void recordingStarted();
void recordingStopped();
void onPreviewStarted();
void onRecordingStarted();
void onRecordingStopped();
};
#endif

2
main.cpp

@ -13,7 +13,7 @@ int main(int argc, char *argv[])
// The program won't run if OpenGL hasn't been initialized.
glutInit(&argc, argv);
MainWindow win(obs);
MainWindow win(app, obs);
return app->run(win);
}

132
obs-manager.cpp

@ -8,7 +8,10 @@
using namespace std;
static void obs_render(void *param, uint32_t cx, uint32_t cy)
//
// Static Functions
//
static void OBSRender(void *param, uint32_t cx, uint32_t cy)
{
obs_render_main_texture();
}
@ -16,21 +19,26 @@ static void obs_render(void *param, uint32_t cx, uint32_t cy)
static void OBSStartRecording(void *data, calldata_t *params)
{
OBSManager *o = static_cast<OBSManager*>(data);
o->startRecording.emit();
o->sigStartRecording.emit();
}
static void OBSStopRecording(void *data, calldata_t *params)
{
OBSManager *o = static_cast<OBSManager*>(data);
o->stopRecording.emit();
o->sigStopRecording.emit();
}
//
// OBSManager
//
OBSManager::OBSManager()
{
mPlugins = {
"obs-ffmpeg.so",
"obs-outputs.so",
"obs-x264.so",
"linux-alsa.so",
"linux-pulseaudio.so",
"linux-v4l2.so",
"linux-capture.so",
};
@ -46,11 +54,25 @@ string OBSManager::GetVersion()
return string(obs_get_version_string());
}
void OBSManager::Initialize()
void OBSManager::LoadSettings(SettingsManager *settings)
{
mPluginDir = settings->GetWithDefault(SETTINGS_KEY_PLUGIN_DIR, SETTINGS_DEFAULT_PLUGIN_DIR);
mOutputDir = settings->GetWithDefault(SETTINGS_KEY_OUTPUT_DIR, std::filesystem::current_path());
mWebcamDeviceID = settings->Get(SETTINGS_KEY_VIDEO_DEVICE_ID);
mAudioDeviceID = settings->Get(SETTINGS_KEY_AUDIO_DEVICE_ID);
mScreenEnabled = settings->GetBool(SETTINGS_KEY_SCREEN_ENABLED);
mWebcamEnabled = settings->GetBool(SETTINGS_KEY_WEBCAM_ENABLED);
mAudioEnabled = settings->GetBool(SETTINGS_KEY_AUDIO_ENABLED);
}
void OBSManager::Initialize(Gdk::Rectangle rect)
{
if (isInitialized)
return;
mScreenWidth = rect.get_width();
mScreenHeight = rect.get_height();
mSources = list<OBSSource>();
auto settings = new SettingsManager();
LoadSettings(settings);
@ -64,10 +86,10 @@ void OBSManager::Initialize()
v.graphics_module = "libobs-opengl.so.0";
v.fps_num = 30000;
v.fps_den = 1001;
v.base_width = PreviewWidth;
v.base_height = PreviewHeight;
v.output_width = PreviewWidth;
v.output_height = PreviewHeight;
v.base_width = mScreenWidth;
v.base_height = mScreenHeight;
v.output_width = mScreenWidth;
v.output_height = mScreenHeight;
v.output_format = VIDEO_FORMAT_NV12;
v.adapter = 0;
v.gpu_conversion = true;
@ -83,15 +105,14 @@ void OBSManager::Initialize()
obs_reset_audio(&a);
isInitialized = true;
//printTypes();
}
void OBSManager::SetPreviewWindow(XID wid, Display *wdisplay)
void OBSManager::StartPreview(XID wid, Display *wdisplay)
{
auto settings = new SettingsManager();
gs_init_data init = {};
init.cx = PreviewWidth;
init.cy = PreviewHeight;
init.cx = mScreenWidth;
init.cy = mScreenHeight;
init.format = GS_BGRA;
init.zsformat = GS_ZS_NONE;
init.window.id = wid;
@ -101,20 +122,21 @@ void OBSManager::SetPreviewWindow(XID wid, Display *wdisplay)
if (mDisplay == nullptr)
throw runtime_error("Failed to create display");
obs_display_add_draw_callback(mDisplay, obs_render, nullptr);
obs_display_resize(mDisplay, PreviewWidth, PreviewHeight);
obs_display_add_draw_callback(mDisplay, OBSRender, nullptr);
OBSScene scene = obs_scene_create("scene1");
if (scene == NULL)
throw runtime_error("Couldn't create scene\n");
if (settings->GetBool(SETTINGS_KEY_SCREEN_ENABLED))
if (mScreenEnabled)
{
auto source = CreateScreenSource();
obs_scene_add(scene, source);
mSources.push_back(source);
}
if (settings->GetBool(SETTINGS_KEY_WEBCAM_ENABLED))
if (mWebcamEnabled)
{
vec2 scale;
vec2_set(&scale, 0.5f, 0.5f);
@ -125,7 +147,7 @@ void OBSManager::SetPreviewWindow(XID wid, Display *wdisplay)
mSources.push_back(source);
}
if (settings->GetBool(SETTINGS_KEY_AUDIO_ENABLED))
if (mAudioEnabled)
{
auto source = CreateAudioSource();
obs_scene_add(scene, source);
@ -133,6 +155,20 @@ void OBSManager::SetPreviewWindow(XID wid, Display *wdisplay)
}
obs_set_output_source(0, obs_scene_get_source(scene));
sigStartPreview.emit();
}
void OBSManager::StopPreview()
{
for (auto source : mSources)
{
obs_source_remove(source);
}
if (mDisplay != nullptr)
obs_display_destroy(mDisplay);
sigStopPreview.emit();
}
void OBSManager::StartRecording()
@ -158,7 +194,7 @@ void OBSManager::StartRecording()
obs_data_set_string(settings, "directory", path.c_str());
obs_data_set_string(settings, "url", fileName.c_str());
mOutput = obs_output_create("ffmpeg_output", "ffmpeg_output", nullptr, nullptr);
mOutput = obs_output_create("ffmpeg_output", "output", nullptr, nullptr);
obs_output_set_video_encoder(mOutput, venc);
obs_output_set_audio_encoder(mOutput, aenc, 0);
obs_output_update(mOutput, settings);
@ -182,6 +218,28 @@ void OBSManager::StopRecording()
isRecording = false;
}
bool OBSManager::IsRecording()
{
return isRecording;
}
void OBSManager::Cleanup()
{
if (!isInitialized)
return;
StopRecording();
StopPreview();
obs_shutdown();
isInitialized = false;
sigCleanup.emit();
}
//
// Sources
//
OBSSource OBSManager::CreateScreenSource()
{
obs_data_t *settings = obs_data_create();
@ -209,7 +267,9 @@ OBSSource OBSManager::CreateWebcamSource()
OBSSource OBSManager::CreateAudioSource()
{
obs_data_t *settings = obs_data_create();
OBSSource source = obs_source_create("audio_line", "Audio Source", settings, NULL);
obs_data_set_string(settings, "device_id", mAudioDeviceID.c_str());
OBSSource source = obs_source_create("pulse_input_capture", "Audio Source", settings, NULL);
if (source == NULL)
throw runtime_error("Couldn't create screen source");
@ -217,37 +277,9 @@ OBSSource OBSManager::CreateAudioSource()
return source;
}
void OBSManager::Cleanup()
{
if (!isInitialized)
return;
StopRecording();
for (auto source : mSources)
{
obs_source_remove(source);
}
if (mDisplay != nullptr)
obs_display_destroy(mDisplay);
obs_shutdown();
isInitialized = false;
}
void OBSManager::LoadSettings(SettingsManager *settings)
{
mPluginDir = settings->GetWithDefault(SETTINGS_KEY_PLUGIN_DIR, SETTINGS_DEFAULT_PLUGIN_DIR);
mOutputDir = settings->GetWithDefault(SETTINGS_KEY_OUTPUT_DIR, std::filesystem::current_path());
mWebcamDeviceID = settings->Get(SETTINGS_KEY_VIDEO_DEVICE_ID);
}
bool OBSManager::IsRecording()
{
return isRecording;
}
//
// Private (Helpers)
//
void OBSManager::loadPlugin(string name)
{
obs_module_t *module;

38
obs-manager.hpp

@ -16,37 +16,49 @@ class OBSManager
public:
OBSManager();
virtual ~OBSManager();
string GetVersion();
void Initialize();
void SetPreviewWindow(XID wid, Display *wdisplay);
void LoadSettings(SettingsManager *settings);
void Initialize(Gdk::Rectangle rect);
void StartPreview(XID wid, Display *wdisplay);
void StopPreview();
void StartRecording();
void StopRecording();
bool IsRecording();
void Cleanup();
OBSSource CreateScreenSource();
OBSSource CreateWebcamSource();
OBSSource CreateAudioSource();
void Cleanup();
void LoadSettings(SettingsManager *settings);
bool IsRecording();
int PreviewWidth = 1280;
int PreviewHeight = 720;
const int PreviewWidth = 1280;
const int PreviewHeight = 720;
// Signals
sigc::signal<void()> startRecording;
sigc::signal<void()> stopRecording;
sigc::signal<void()> sigStartPreview;
sigc::signal<void()> sigStopPreview;
sigc::signal<void()> sigStartRecording;
sigc::signal<void()> sigStopRecording;
sigc::signal<void()> sigCleanup;
private:
void printTypes();
bool isInitialized = false;
bool isRecording = false;
int mScreenWidth = 0;
int mScreenHeight = 0;
bool mScreenEnabled = false;
bool mWebcamEnabled = false;
bool mAudioEnabled = false;
string mPluginDir;
string mOutputDir;
string mWebcamDeviceID;
string mAudioDeviceID;
OBSDisplay mDisplay;
OBSOutput mOutput;
list<OBSSource> mSources;
OBSSignal obsStartRecording;
OBSSignal obsStopRecording;
list<OBSSource> mSources;
string mPluginDir;
string mOutputDir;
string mWebcamDeviceID;
// Plugins
void loadPlugin(string name);

3
preview-window.cpp

@ -17,8 +17,7 @@ void PreviewWindow::on_realize()
{
Gtk::Widget::on_realize();
mOBS->Initialize();
mOBS->SetPreviewWindow(
mOBS->StartPreview(
GDK_WINDOW_XID(get_window()->gobj()),
GDK_WINDOW_XDISPLAY(get_window()->gobj())
);

1
settings-manager.hpp

@ -7,6 +7,7 @@
#define SETTINGS_KEY_WEBCAM_ENABLED "webcam_enabled"
#define SETTINGS_KEY_VIDEO_DEVICE_ID "video_device_id"
#define SETTINGS_KEY_AUDIO_ENABLED "audio_enabled"
#define SETTINGS_KEY_AUDIO_DEVICE_ID "audio_device_id"
#define SETTINGS_DEFAULT_PLUGIN_DIR "/usr/lib/x86_64-linux-gnu/obs-plugins/"

206
settings-window.cpp

@ -2,6 +2,10 @@
#include "settings-manager.hpp"
#include <linux/videodev2.h>
#include <alsa/asoundlib.h>
#include <pulse/pulseaudio.h>
#include <string>
#include <list>
#include <iostream>
#include <filesystem>
#include <stdexcept>
@ -21,6 +25,7 @@ SettingsWindow::SettingsWindow(OBSManager* obs)
mCheckButtonDesktop("Enable desktop recording"),
mBoxWebcam(Gtk::Orientation::ORIENTATION_VERTICAL, 2),
mCheckButtonWebcam("Enable webcam recording"),
mBoxAudio(Gtk::Orientation::ORIENTATION_VERTICAL),
mCheckButtonAudio("Enable audio recording"),
mButtonClose("Close"),
mButtonSave("Save")
@ -72,11 +77,15 @@ SettingsWindow::SettingsWindow(OBSManager* obs)
mBoxWebcam.add(mComboBoxVideoDevice);
mFrameWebcam.add(mBoxWebcam);
mComboBoxAudioDevice.set_border_width(10);
mFrameAudio.set_label("Microphone");
mFrameAudio.set_border_width(10);
mBoxAudio.set_border_width(10);
mCheckButtonAudio.set_border_width(10);
mCheckButtonAudio.set_active(settings->GetBoolWithDefault(SETTINGS_KEY_AUDIO_ENABLED, true));
mFrameAudio.add(mCheckButtonAudio);
mBoxAudio.add(mCheckButtonAudio);
mBoxAudio.add(mComboBoxAudioDevice);
mFrameAudio.add(mBoxAudio);
mBoxSettings.add(mBoxPluginDir);
mBoxSettings.add(mBoxOutputDir);
@ -106,6 +115,7 @@ void SettingsWindow::on_show()
{
Gtk::Window::on_show();
populateVideoDevices();
populatePulseAudioDevices();
}
bool SettingsWindow::onKeyPressed(GdkEventKey *event)
@ -132,12 +142,16 @@ void SettingsWindow::onSavePressed()
settings->UpdateBool(SETTINGS_KEY_WEBCAM_ENABLED, mCheckButtonWebcam.get_active());
settings->Update(SETTINGS_KEY_VIDEO_DEVICE_ID, mComboBoxVideoDevice.get_active_id());
settings->UpdateBool(SETTINGS_KEY_AUDIO_ENABLED, mCheckButtonAudio.get_active());
settings->Update(SETTINGS_KEY_AUDIO_DEVICE_ID, mComboBoxAudioDevice.get_active_id());
settings->SaveAll();
close();
}
void SettingsWindow::populateVideoDevices()
{
cout << "populateVideoDevices" << endl;
mComboBoxVideoDevice.remove_all();
v4l2_capability cap;
dirent *dp;
@ -181,3 +195,193 @@ void SettingsWindow::populateVideoDevices()
}
}
void SettingsWindow::populateALSAAudioDevices()
{
cout << "populateALSAAudioDevices" << endl;
mComboBoxAudioDevice.remove_all();
void **hints;
void **hint;
char *name = nullptr;
char *desc = nullptr;
char *ioid = nullptr;
if (snd_device_name_hint(-1, "pcm", &hints) < 0)
throw runtime_error("Failed reading audio devices");
hint = hints;
while (*hint != NULL)
{
name = snd_device_name_get_hint(*hint, "NAME");
desc = snd_device_name_get_hint(*hint, "DESC");
ioid = snd_device_name_get_hint(*hint, "IOID");
if (name == NULL || strstr(name, "front:") == NULL)
goto next;
if (ioid != NULL && strcmp(ioid, "Input") != 0)
goto next;
mComboBoxAudioDevice.append(name, desc);
if (settings->Get(SETTINGS_KEY_AUDIO_DEVICE_ID) == name)
mComboBoxAudioDevice.set_active_id(name);
next:
if (name != NULL)
{
free(name);
name = NULL;
}
if (desc != NULL)
{
free(desc);
desc = NULL;
}
if (ioid != NULL)
{
free(ioid);
ioid = NULL;
}
hint++;
}
snd_device_name_free_hint((void**)hints);
}
class PADevice
{
public:
string name;
string desc;
uint32_t index;
};
// Pulse Audio
void pa_state_cb(pa_context *c, void *userdata)
{
int *pa_ready = (int*)userdata;
pa_context_state_t state = pa_context_get_state(c);
switch (state)
{
case PA_CONTEXT_UNCONNECTED:
case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
default:
break;
case PA_CONTEXT_FAILED:
case PA_CONTEXT_TERMINATED:
*pa_ready = 2;
break;
case PA_CONTEXT_READY:
*pa_ready = 1;
break;
}
}
void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata)
{
list<PADevice> *dlist = (list<PADevice>*)userdata;
if (eol != 0 || l->monitor_source == PA_INVALID_INDEX)
return;
dlist->push_back({
.name = l->name,
.desc = l->description,
.index = l->index
});
}
void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata)
{
list<PADevice> *dlist = (list<PADevice>*)userdata;
if (eol != 0 || l->monitor_of_sink != PA_INVALID_INDEX)
return;
dlist->push_back({
.name = l->name,
.desc = l->description,
.index = l->index
});
}
void SettingsWindow::populatePulseAudioDevices()
{
cout << "populatePulseAudioDevices" << endl;
mComboBoxAudioDevice.remove_all();
int state = 0;
int pa_ready = 0;
auto input = new list<PADevice>();
auto output = new list<PADevice>();
pa_operation *pa_op;
pa_mainloop *pa_ml = pa_mainloop_new();
pa_mainloop_api *pa_mlapi = pa_mainloop_get_api(pa_ml);
pa_context *pa_ctx = pa_context_new(pa_mlapi, "screenrecorder");
pa_context_connect(pa_ctx, NULL, (pa_context_flags_t)0, NULL);
pa_context_set_state_callback(pa_ctx, pa_state_cb, &pa_ready);
for (;;)
{
if (pa_ready == 0)
{
pa_mainloop_iterate(pa_ml, 1, NULL);
continue;
}
if (pa_ready == 2)
{
pa_context_disconnect(pa_ctx);
pa_context_unref(pa_ctx);
pa_mainloop_free(pa_ml);
throw runtime_error("Failed to get pulse audio devices");
}
switch (state)
{
case 0:
pa_op = pa_context_get_sink_info_list(pa_ctx, pa_sinklist_cb, output);
state++;
break;
case 1:
if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE)
pa_operation_unref(pa_op);
pa_op = pa_context_get_source_info_list(pa_ctx, pa_sourcelist_cb, input);
state++;
break;
case 2:
if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE)
{
pa_operation_unref(pa_op);
pa_context_disconnect(pa_ctx);
pa_context_unref(pa_ctx);
pa_mainloop_free(pa_ml);
for (auto device : *input)
{
mComboBoxAudioDevice.append(device.name, device.desc);
if (settings->Get(SETTINGS_KEY_AUDIO_DEVICE_ID) == device.name)
mComboBoxAudioDevice.set_active_id(device.name);
}
return;
}
break;
default:
return;
}
pa_mainloop_iterate(pa_ml, 1, NULL);
}
}

4
settings-window.hpp

@ -32,7 +32,9 @@ private:
Gtk::ComboBoxText mComboBoxVideoDevice;
Gtk::Frame mFrameAudio;
Gtk::Box mBoxAudio;
Gtk::CheckButton mCheckButtonAudio;
Gtk::ComboBoxText mComboBoxAudioDevice;
Gtk::ActionBar mActionBar;
Gtk::Button mButtonClose;
@ -40,6 +42,8 @@ private:
void on_show();
void populateVideoDevices();
void populateALSAAudioDevices();
void populatePulseAudioDevices();
bool onKeyPressed(GdkEventKey* event);
void onClosePressed();
void onSavePressed();

Loading…
Cancel
Save