diff --git a/.kdev4/screen-recorder.kdev4 b/.kdev4/screen-recorder.kdev4 index 029b364..0afa0f1 100644 --- a/.kdev4/screen-recorder.kdev4 +++ b/.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 diff --git a/Makefile b/Makefile index ea5e32f..77f222a 100644 --- a/Makefile +++ b/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 diff --git a/README.md b/README.md index 51f93e4..8935fd5 100644 --- a/README.md +++ b/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 diff --git a/config.cfg b/config.cfg index bd93c10..95b4c70 100644 --- a/config.cfg +++ b/config.cfg @@ -4,3 +4,4 @@ screen_enabled=true webcam_enabled=true video_device_id=/dev/video2 audio_enabled=true +audio_device_id=alsa_input.pci-0000_00_1f.4.analog-stereo diff --git a/main-window.cpp b/main-window.cpp index b5eb070..d0df150 100644 --- a/main-window.cpp +++ b/main-window.cpp @@ -7,7 +7,7 @@ using namespace std; -MainWindow::MainWindow(OBSManager *obs) +MainWindow::MainWindow(Glib::RefPtr 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"); } diff --git a/main-window.hpp b/main-window.hpp index 57cb5a5..337aad0 100644 --- a/main-window.hpp +++ b/main-window.hpp @@ -10,14 +10,16 @@ class MainWindow : public Gtk::Window { public: - MainWindow(OBSManager *obs); + MainWindow(Glib::RefPtr app, OBSManager *obs); virtual ~MainWindow(); private: + Glib::RefPtr 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 diff --git a/main.cpp b/main.cpp index f6b6211..3a3d59d 100644 --- a/main.cpp +++ b/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); } diff --git a/obs-manager.cpp b/obs-manager.cpp index 871175d..f967055 100644 --- a/obs-manager.cpp +++ b/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(data); - o->startRecording.emit(); + o->sigStartRecording.emit(); } static void OBSStopRecording(void *data, calldata_t *params) { OBSManager *o = static_cast(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(); 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; diff --git a/obs-manager.hpp b/obs-manager.hpp index 72dae6f..d938ce7 100644 --- a/obs-manager.hpp +++ b/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 startRecording; - sigc::signal stopRecording; + sigc::signal sigStartPreview; + sigc::signal sigStopPreview; + sigc::signal sigStartRecording; + sigc::signal sigStopRecording; + sigc::signal 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 mSources; OBSSignal obsStartRecording; OBSSignal obsStopRecording; - list mSources; - string mPluginDir; - string mOutputDir; - string mWebcamDeviceID; // Plugins void loadPlugin(string name); diff --git a/preview-window.cpp b/preview-window.cpp index 22f8c18..39109df 100644 --- a/preview-window.cpp +++ b/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()) ); diff --git a/settings-manager.hpp b/settings-manager.hpp index b34a2a8..530671d 100644 --- a/settings-manager.hpp +++ b/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/" diff --git a/settings-window.cpp b/settings-window.cpp index aec066b..2676201 100644 --- a/settings-window.cpp +++ b/settings-window.cpp @@ -2,6 +2,10 @@ #include "settings-manager.hpp" #include +#include +#include +#include +#include #include #include #include @@ -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 *dlist = (list*)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 *dlist = (list*)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(); + auto output = new list(); + + 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); + } +} + diff --git a/settings-window.hpp b/settings-window.hpp index 9cceb70..dedb3f8 100644 --- a/settings-window.hpp +++ b/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();