diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 91c0d2ca2..d4d9d1050 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -42,6 +42,7 @@ jobs: build_only: True target_branch: 'master' target_path: '../home/_build' + pre_build_commands: 'pip install -Iv sphinx==5.1.1' - uses: actions/upload-artifact@v3 with: name: OBS Studio Documentation (HTML) diff --git a/UI/data/OBSPublicRSAKey.pem b/UI/data/OBSPublicRSAKey.pem old mode 100755 new mode 100644 diff --git a/UI/data/themes/Acri.qss b/UI/data/themes/Acri.qss index 50f0ea79a..4e480a4ba 100644 --- a/UI/data/themes/Acri.qss +++ b/UI/data/themes/Acri.qss @@ -300,8 +300,8 @@ QDockWidget { font-size: 10.5pt; font-weight: bold; - titlebar-close-icon: url('./Dark/Close.svg'); - titlebar-normal-icon: url('./Dark/Popout.svg'); + titlebar-close-icon: url('./Dark/close.svg'); + titlebar-normal-icon: url('./Dark/popout.svg'); } QDockWidget::title { diff --git a/UI/data/themes/Grey.qss b/UI/data/themes/Grey.qss index 34a6cdf1e..e5986648e 100644 --- a/UI/data/themes/Grey.qss +++ b/UI/data/themes/Grey.qss @@ -300,8 +300,8 @@ QDockWidget { font-size: 10.5pt; font-weight: bold; - titlebar-close-icon: url('./Dark/Close.svg'); - titlebar-normal-icon: url('./Dark/Popout.svg'); + titlebar-close-icon: url('./Dark/close.svg'); + titlebar-normal-icon: url('./Dark/popout.svg'); } QDockWidget::title { diff --git a/UI/data/themes/Light.qss b/UI/data/themes/Light.qss index 1e6c8eb61..d8e7e968a 100644 --- a/UI/data/themes/Light.qss +++ b/UI/data/themes/Light.qss @@ -300,8 +300,8 @@ QDockWidget { font-size: 10.5pt; font-weight: bold; - titlebar-close-icon: url('./Light/Close.svg'); - titlebar-normal-icon: url('./Light/Popout.svg'); + titlebar-close-icon: url('./Light/close.svg'); + titlebar-normal-icon: url('./Light/popout.svg'); } QDockWidget::title { diff --git a/UI/data/themes/Light/popout.svg b/UI/data/themes/Light/popout.svg index afba03a27..fbf285d6d 100644 --- a/UI/data/themes/Light/popout.svg +++ b/UI/data/themes/Light/popout.svg @@ -1 +1 @@ - + diff --git a/UI/data/themes/Rachni.qss b/UI/data/themes/Rachni.qss index 9f729a1e8..db7baac4f 100644 --- a/UI/data/themes/Rachni.qss +++ b/UI/data/themes/Rachni.qss @@ -302,8 +302,8 @@ QDockWidget { font-size: 10.5pt; font-weight: bold; - titlebar-close-icon: url('./Dark/Close.svg'); - titlebar-normal-icon: url('./Dark/Popout.svg'); + titlebar-close-icon: url('./Dark/close.svg'); + titlebar-normal-icon: url('./Dark/popout.svg'); } QDockWidget::title { diff --git a/UI/data/themes/Yami.qss b/UI/data/themes/Yami.qss index 1d1111731..dfadf0eed 100644 --- a/UI/data/themes/Yami.qss +++ b/UI/data/themes/Yami.qss @@ -304,8 +304,8 @@ QDockWidget { font-size: 10.5pt; font-weight: bold; - titlebar-close-icon: url('./Dark/Close.svg'); - titlebar-normal-icon: url('./Dark/Popout.svg'); + titlebar-close-icon: url('./Dark/close.svg'); + titlebar-normal-icon: url('./Dark/popout.svg'); } QDockWidget::title { diff --git a/UI/frontend-plugins/frontend-tools/data/locale/en-US.ini b/UI/frontend-plugins/frontend-tools/data/locale/en-US.ini index b43843591..80f2a4118 100644 --- a/UI/frontend-plugins/frontend-tools/data/locale/en-US.ini +++ b/UI/frontend-plugins/frontend-tools/data/locale/en-US.ini @@ -39,6 +39,10 @@ PythonSettings="Python Settings" PythonSettings.PythonInstallPath32bit="Python Install Path (32bit)" PythonSettings.PythonInstallPath64bit="Python Install Path (64bit)" PythonSettings.BrowsePythonPath="Browse Python Path" +PythonSettings.PythonVersion="Loaded Python Version: %1" +PythonSettings.PythonNotLoaded="Python not currently loaded" +PythonSettings.AlreadyLoaded.Title="Python Already Loaded" +PythonSettings.AlreadyLoaded.Message="A copy of Python %1 is already loaded. To load the newly selected Python version, please restart OBS." ScriptLogWindow="Script Log" Description="Description" ScriptDescriptionLink.Text="Open this link in your default web browser?" diff --git a/UI/frontend-plugins/frontend-tools/forms/scripts.ui b/UI/frontend-plugins/frontend-tools/forms/scripts.ui index ce8987c50..e07844cfa 100644 --- a/UI/frontend-plugins/frontend-tools/forms/scripts.ui +++ b/UI/frontend-plugins/frontend-tools/forms/scripts.ui @@ -268,6 +268,13 @@ + + + + + + + diff --git a/UI/frontend-plugins/frontend-tools/scripts.cpp b/UI/frontend-plugins/frontend-tools/scripts.cpp index 4c561de7d..c4174da4f 100644 --- a/UI/frontend-plugins/frontend-tools/scripts.cpp +++ b/UI/frontend-plugins/frontend-tools/scripts.cpp @@ -194,6 +194,7 @@ ScriptsTool::ScriptsTool() : QDialog(nullptr), ui(new Ui_ScriptsTool) config_get_string(config, "Python", "Path" ARCH_NAME); ui->pythonPath->setText(path); ui->pythonPathLabel->setText(obs_module_text(PYTHONPATH_LABEL_TEXT)); + updatePythonVersionLabel(); #else delete ui->pythonSettingsTab; ui->pythonSettingsTab = nullptr; @@ -219,6 +220,20 @@ ScriptsTool::~ScriptsTool() ui->scripts->currentRow()); } +void ScriptsTool::updatePythonVersionLabel() +{ + QString label; + if (obs_scripting_python_loaded()) { + char version[8]; + obs_scripting_python_version(version, sizeof(version)); + label = QString(obs_module_text("PythonSettings.PythonVersion")) + .arg(version); + } else { + label = obs_module_text("PythonSettings.PythonNotLoaded"); + } + ui->pythonVersionLabel->setText(label); +} + void ScriptsTool::RemoveScript(const char *path) { for (size_t i = 0; i < scriptData->scripts.size(); i++) { @@ -455,11 +470,29 @@ void ScriptsTool::on_pythonPathBrowse_clicked() ui->pythonPath->setText(newPath); - if (obs_scripting_python_loaded()) + bool loaded = obs_scripting_python_loaded(); + + if (loaded && !newPath.isEmpty() && curPath.compare(newPath) != 0) { + char version[8]; + obs_scripting_python_version(version, sizeof(version)); + QString message = + QString(obs_module_text( + "PythonSettings.AlreadyLoaded.Message")) + .arg(version); + OBSMessageBox::information( + this, + obs_module_text("PythonSettings.AlreadyLoaded.Title"), + message); return; + } else if (loaded) { + return; + } + if (!obs_scripting_load_python(path)) return; + updatePythonVersionLabel(); + for (OBSScript &script : scriptData->scripts) { enum obs_script_lang lang = obs_script_get_lang(script); if (lang == OBS_SCRIPT_LANG_PYTHON) { diff --git a/UI/frontend-plugins/frontend-tools/scripts.hpp b/UI/frontend-plugins/frontend-tools/scripts.hpp index 3ce5385ad..052145352 100644 --- a/UI/frontend-plugins/frontend-tools/scripts.hpp +++ b/UI/frontend-plugins/frontend-tools/scripts.hpp @@ -31,6 +31,8 @@ class ScriptsTool : public QDialog { std::unique_ptr ui; QWidget *propertiesView = nullptr; + void updatePythonVersionLabel(); + public: ScriptsTool(); ~ScriptsTool(); diff --git a/UI/item-widget-helpers.cpp b/UI/item-widget-helpers.cpp index 18a3f9ac8..b1444f933 100644 --- a/UI/item-widget-helpers.cpp +++ b/UI/item-widget-helpers.cpp @@ -37,10 +37,14 @@ void DeleteListItem(QListWidget *widget, QListWidgetItem *item) void ClearListItems(QListWidget *widget) { + // Workaround for the SceneTree workaround for QTBUG-105870 + widget->setProperty("clearing", true); + widget->setCurrentItem(nullptr, QItemSelectionModel::Clear); for (int i = 0; i < widget->count(); i++) delete widget->itemWidget(widget->item(i)); widget->clear(); + widget->setProperty("clearing", false); } diff --git a/UI/scene-tree.cpp b/UI/scene-tree.cpp index f458b8e0b..544405173 100644 --- a/UI/scene-tree.cpp +++ b/UI/scene-tree.cpp @@ -252,6 +252,7 @@ void SceneTree::rowsInserted(const QModelIndex &parent, int start, int end) void SceneTree::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { - if (selected.count() == 0 && deselected.count() > 0) + if (selected.count() == 0 && deselected.count() > 0 && + !property("clearing").toBool()) setCurrentRow(deselected.indexes().front().row()); } diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index ae4acfe41..cf80c57d6 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -7122,17 +7122,31 @@ void OBSBasic::StreamingStop(int code, QString last_error) void OBSBasic::AutoRemux(QString input, bool no_show) { - bool autoRemux = config_get_bool(Config(), "Video", "AutoRemux"); + auto config = Config(); + + bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); if (!autoRemux) return; - const char *recType = config_get_string(Config(), "AdvOut", "RecType"); + bool isSimpleMode = false; - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; + const char *mode = config_get_string(config, "Output", "Mode"); + if (!mode) { + isSimpleMode = true; + } else { + isSimpleMode = strcmp(mode, "Simple") == 0; + } - if (ffmpegOutput) - return; + if (!isSimpleMode) { + const char *recType = + config_get_string(config, "AdvOut", "RecType"); + + bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; + + if (ffmpegOutput) + return; + } if (input.isEmpty()) return; diff --git a/deps/obs-scripting/obs-scripting-python.c b/deps/obs-scripting/obs-scripting-python.c index 35f549938..124164ebd 100644 --- a/deps/obs-scripting/obs-scripting-python.c +++ b/deps/obs-scripting/obs-scripting-python.c @@ -1590,6 +1590,17 @@ bool obs_scripting_python_runtime_linked(void) return (bool)RUNTIME_LINK; } +void obs_scripting_python_version(char *version, size_t version_length) +{ +#if RUNTIME_LINK + snprintf(version, version_length, "%d.%d", python_version.major, + python_version.minor); +#else + snprintf(version, version_length, "%d.%d", PY_MAJOR_VERSION, + PY_MINOR_VERSION); +#endif +} + bool obs_scripting_python_loaded(void) { return python_loaded; diff --git a/deps/obs-scripting/obs-scripting.c b/deps/obs-scripting/obs-scripting.c index 74398b858..2c1d353ec 100644 --- a/deps/obs-scripting/obs-scripting.c +++ b/deps/obs-scripting/obs-scripting.c @@ -458,4 +458,9 @@ bool obs_scripting_python_runtime_linked(void) { return (bool)true; } + +void obs_scripting_python_version(char *version, size_t version_length) +{ + version[0] = 0; +} #endif diff --git a/deps/obs-scripting/obs-scripting.h b/deps/obs-scripting/obs-scripting.h index 415c313e3..420c4b19a 100644 --- a/deps/obs-scripting/obs-scripting.h +++ b/deps/obs-scripting/obs-scripting.h @@ -46,6 +46,7 @@ EXPORT void obs_scripting_set_log_callback(scripting_log_handler_t handler, void *param); EXPORT bool obs_scripting_python_runtime_linked(void); +EXPORT void obs_scripting_python_version(char *version, size_t version_length); EXPORT bool obs_scripting_python_loaded(void); EXPORT bool obs_scripting_load_python(const char *python_path); diff --git a/libobs/media-io/video-scaler-ffmpeg.c b/libobs/media-io/video-scaler-ffmpeg.c index 2bc653ab2..c83fced2e 100644 --- a/libobs/media-io/video-scaler-ffmpeg.c +++ b/libobs/media-io/video-scaler-ffmpeg.c @@ -19,6 +19,7 @@ #include "video-scaler.h" #include +#include #include struct video_scaler { @@ -188,16 +189,27 @@ int video_scaler_create(video_scaler_t **scaler_out, goto fail; } - scaler->swscale = sws_getCachedContext(NULL, src->width, src->height, - format_src, dst->width, - dst->height, format_dst, - scale_type, NULL, NULL, NULL); + scaler->swscale = sws_alloc_context(); if (!scaler->swscale) { blog(LOG_ERROR, "video_scaler_create: Could not create " "swscale"); goto fail; } + av_opt_set_int(scaler->swscale, "sws_flags", scale_type, 0); + av_opt_set_int(scaler->swscale, "srcw", src->width, 0); + av_opt_set_int(scaler->swscale, "srch", src->height, 0); + av_opt_set_int(scaler->swscale, "dstw", dst->width, 0); + av_opt_set_int(scaler->swscale, "dsth", dst->height, 0); + av_opt_set_int(scaler->swscale, "src_format", format_src, 0); + av_opt_set_int(scaler->swscale, "dst_format", format_dst, 0); + av_opt_set_int(scaler->swscale, "src_range", range_src, 0); + av_opt_set_int(scaler->swscale, "dst_range", range_dst, 0); + if (sws_init_context(scaler->swscale, NULL, NULL) < 0) { + blog(LOG_ERROR, "video_scaler_create: sws_init_context failed"); + goto fail; + } + ret = sws_setColorspaceDetails(scaler->swscale, coeff_src, range_src, coeff_dst, range_dst, 0, FIXED_1_0, FIXED_1_0); diff --git a/libobs/obs-config.h b/libobs/obs-config.h index 2a33fdc6a..7d64d9c70 100644 --- a/libobs/obs-config.h +++ b/libobs/obs-config.h @@ -41,7 +41,7 @@ * * Reset to zero each major or minor version */ -#define LIBOBS_API_PATCH_VER 1 +#define LIBOBS_API_PATCH_VER 3 #define MAKE_SEMANTIC_VERSION(major, minor, patch) \ ((major << 24) | (minor << 16) | patch) diff --git a/libobs/obs-encoder.c b/libobs/obs-encoder.c index 38371180a..8b9bae3f5 100644 --- a/libobs/obs-encoder.c +++ b/libobs/obs-encoder.c @@ -1534,3 +1534,8 @@ void obs_encoder_set_last_error(obs_encoder_t *encoder, const char *message) else encoder->last_error_message = NULL; } + +uint64_t obs_encoder_get_pause_offset(const obs_encoder_t *encoder) +{ + return encoder ? encoder->pause.ts_offset : 0; +} diff --git a/libobs/obs.h b/libobs/obs.h index 4fcce92e3..46c006ce7 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -2413,6 +2413,8 @@ EXPORT const char *obs_encoder_get_last_error(obs_encoder_t *encoder); EXPORT void obs_encoder_set_last_error(obs_encoder_t *encoder, const char *message); +EXPORT uint64_t obs_encoder_get_pause_offset(const obs_encoder_t *encoder); + /* ------------------------------------------------------------------------- */ /* Stream Services */ diff --git a/plugins/linux-capture/xcomposite-input.c b/plugins/linux-capture/xcomposite-input.c index b63bda614..fc804e0b0 100644 --- a/plugins/linux-capture/xcomposite-input.c +++ b/plugins/linux-capture/xcomposite-input.c @@ -310,11 +310,10 @@ xcb_window_t xcomp_find_window(xcb_connection_t *conn, Display *disp, char *wcls = bzalloc(strEnd - thirdStr + 1); memcpy(wname, secondStr, secondMark - secondStr); memcpy(wcls, thirdStr, strEnd - thirdStr); - xcb_window_t wid = (xcb_window_t)strtol(str, NULL, 10); + ret = (xcb_window_t)strtol(str, NULL, 10); // first try to find a match by the window-id - if (da_find(tlw, &wid, 0) != DARRAY_INVALID) { - ret = wid; + if (da_find(tlw, &ret, 0) != DARRAY_INVALID) { goto cleanup2; } @@ -325,8 +324,8 @@ xcb_window_t xcomp_find_window(xcb_connection_t *conn, Display *disp, struct dstr cwname = xcomp_window_name(conn, disp, cwin); struct dstr cwcls = xcomp_window_class(conn, cwin); - bool found = (strcmp(wname, cwname.array) == 0 && - strcmp(wcls, cwcls.array)); + bool found = strcmp(wname, cwname.array) == 0 && + strcmp(wcls, cwcls.array) == 0; dstr_free(&cwname); dstr_free(&cwcls); @@ -345,7 +344,7 @@ cleanup2: bfree(wcls); cleanup1: da_free(tlw); - return wid; + return ret; } void xcomp_cleanup_pixmap(Display *disp, struct xcompcap *s) diff --git a/plugins/linux-pipewire/screencast-portal.c b/plugins/linux-pipewire/screencast-portal.c index f020a77e8..08ed2f442 100644 --- a/plugins/linux-pipewire/screencast-portal.c +++ b/plugins/linux-pipewire/screencast-portal.c @@ -627,6 +627,20 @@ static bool reload_session_cb(obs_properties_t *properties, g_clear_pointer(&capture->restore_token, bfree); g_clear_pointer(&capture->obs_pw, obs_pipewire_destroy); + if (capture->session_handle) { + blog(LOG_DEBUG, "[pipewire] Cleaning previous session %s", + capture->session_handle); + g_dbus_connection_call(portal_get_dbus_connection(), + "org.freedesktop.portal.Desktop", + capture->session_handle, + "org.freedesktop.portal.Session", + "Close", NULL, NULL, + G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, + NULL); + + g_clear_pointer(&capture->session_handle, g_free); + } + init_screencast_capture(capture); return false; diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-mux.c b/plugins/obs-ffmpeg/obs-ffmpeg-mux.c index b214105b6..67c1a2e2d 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-mux.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-mux.c @@ -375,18 +375,33 @@ inline static void ts_offset_update(struct ffmpeg_muxer *stream, stream->found_audio[packet->track_idx] = true; } -static bool ffmpeg_mux_start(void *data) +static inline void update_encoder_settings(struct ffmpeg_muxer *stream, + const char *path) { - struct ffmpeg_muxer *stream = data; - obs_data_t *settings; - const char *path; + obs_encoder_t *vencoder = obs_output_get_video_encoder(stream->output); + const char *ext = strrchr(path, '.'); + + /* if using m3u8, repeat headers */ + if (ext && strcmp(ext, ".m3u8") == 0) { + obs_data_t *settings = obs_encoder_get_settings(vencoder); + obs_data_set_bool(settings, "repeat_headers", true); + obs_encoder_update(vencoder, settings); + obs_data_release(settings); + } +} + +static inline bool ffmpeg_mux_start_internal(struct ffmpeg_muxer *stream, + obs_data_t *settings) +{ + const char *path = obs_data_get_string(settings, "path"); + + update_encoder_settings(stream, path); if (!obs_output_can_begin_data_capture(stream->output, 0)) return false; if (!obs_output_initialize_encoders(stream->output, 0)) return false; - settings = obs_output_get_settings(stream->output); if (stream->is_network) { obs_service_t *service; service = obs_output_get_service(stream->output); @@ -395,7 +410,6 @@ static bool ffmpeg_mux_start(void *data) path = obs_service_get_url(service); stream->split_file = false; } else { - path = obs_data_get_string(settings, "path"); stream->max_time = obs_data_get_int(settings, "max_time_sec") * 1000000LL; @@ -427,7 +441,6 @@ static bool ffmpeg_mux_start(void *data) } start_pipe(stream, path); - obs_data_release(settings); if (!stream->pipe) { obs_output_set_last_error( @@ -446,6 +459,17 @@ static bool ffmpeg_mux_start(void *data) return true; } +static bool ffmpeg_mux_start(void *data) +{ + struct ffmpeg_muxer *stream = data; + + obs_data_t *settings = obs_output_get_settings(stream->output); + bool success = ffmpeg_mux_start_internal(stream, settings); + obs_data_release(settings); + + return success; +} + int deactivate(struct ffmpeg_muxer *stream, int code) { int ret = -1; diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-video-encoders.c b/plugins/obs-ffmpeg/obs-ffmpeg-video-encoders.c index 64bf832b1..d0de9c4d4 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-video-encoders.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-video-encoders.c @@ -221,6 +221,8 @@ bool ffmpeg_video_encode(struct ffmpeg_video_encoder *enc, AVPacket av_pkt = {0}; bool timeout = false; const int64_t cur_ts = (int64_t)os_gettime_ns(); + const int64_t pause_offset = + (int64_t)obs_encoder_get_pause_offset(enc->encoder); int got_packet; int ret; @@ -270,7 +272,7 @@ bool ffmpeg_video_encode(struct ffmpeg_video_encoder *enc, debug("cur: %lld, packet: %lld, diff: %lld", cur_ts, recv_ts_nsec, cur_ts - recv_ts_nsec); #endif - if ((cur_ts - recv_ts_nsec) > TIMEOUT_MAX_NSEC) { + if ((cur_ts - recv_ts_nsec - pause_offset) > TIMEOUT_MAX_NSEC) { char timeout_str[16]; snprintf(timeout_str, sizeof(timeout_str), "%d", TIMEOUT_MAX_SEC); diff --git a/plugins/obs-ffmpeg/texture-amf.cpp b/plugins/obs-ffmpeg/texture-amf.cpp index a0e7a8a16..f5487e5b1 100644 --- a/plugins/obs-ffmpeg/texture-amf.cpp +++ b/plugins/obs-ffmpeg/texture-amf.cpp @@ -964,7 +964,7 @@ static obs_properties_t *amf_properties_internal(bool hevc) obs_property_set_modified_callback(p, rate_control_modified); p = obs_properties_add_int(props, "bitrate", obs_module_text("Bitrate"), - 50, 300000, 50); + 50, 100000, 50); obs_property_int_set_suffix(p, " Kbps"); obs_properties_add_int(props, "cqp", obs_module_text("NVENC.CQLevel"), @@ -1158,6 +1158,11 @@ static bool amf_avc_init(void *data, obs_data_t *settings) : 250; set_avc_property(enc, IDR_PERIOD, gop_size); + + bool repeat_headers = obs_data_get_bool(settings, "repeat_headers"); + if (repeat_headers) + set_avc_property(enc, HEADER_INSERTION_SPACING, gop_size); + set_avc_property(enc, DE_BLOCKING_FILTER, true); const char *ffmpeg_opts = obs_data_get_string(settings, "ffmpeg_opts"); diff --git a/plugins/obs-filters/noise-suppress-filter.c b/plugins/obs-filters/noise-suppress-filter.c index 3d0854663..e37d8eb6a 100644 --- a/plugins/obs-filters/noise-suppress-filter.c +++ b/plugins/obs-filters/noise-suppress-filter.c @@ -119,6 +119,7 @@ struct noise_suppress_data { char *model; bool nvafx_initialized; const char *fx; + char *sdk_path; /* Resampler */ audio_resampler_t *nvafx_resampler; @@ -217,6 +218,7 @@ static void noise_suppress_destroy(void *data) audio_resampler_destroy(ng->nvafx_resampler_back); } bfree(ng->model); + bfree(ng->sdk_path); bfree((void *)ng->fx); if (ng->nvafx_enabled) { if (ng->use_nvafx) @@ -425,7 +427,25 @@ static inline enum speaker_layout convert_speaker_layout(uint8_t channels) return SPEAKERS_UNKNOWN; } } +#ifdef LIBNVAFX_ENABLED +static void set_model(void *data, const char *method) +{ + struct noise_suppress_data *ng = data; + const char *file; + if (strcmp(NVAFX_EFFECT_DEREVERB, method) == 0) + file = NVAFX_EFFECT_DEREVERB_MODEL; + else if (strcmp(NVAFX_EFFECT_DEREVERB_DENOISER, method) == 0) + file = NVAFX_EFFECT_DEREVERB_DENOISER_MODEL; + else + file = NVAFX_EFFECT_DENOISER_MODEL; + size_t size = strlen(ng->sdk_path) + strlen(file) + 1; + char *buffer = (char *)bmalloc(size); + strcpy(buffer, ng->sdk_path); + strcat(buffer, file); + ng->model = buffer; +} +#endif static void noise_suppress_update(void *data, obs_data_t *s) { struct noise_suppress_data *ng = data; @@ -444,6 +464,8 @@ static void noise_suppress_update(void *data, obs_data_t *s) strcmp(method, S_METHOD_NVAFX_DEREVERB) == 0 || strcmp(method, S_METHOD_NVAFX_DEREVERB_DENOISER) == 0; #ifdef LIBNVAFX_ENABLED + if (nvafx_requested) + set_model(ng, method); float intensity = (float)obs_data_get_double(s, S_NVAFX_INTENSITY); if (ng->use_nvafx && ng->nvafx_initialized) { if (intensity != ng->intensity_ratio && @@ -469,6 +491,7 @@ static void noise_suppress_update(void *data, obs_data_t *s) bfree((void *)ng->fx); ng->fx = bstrdup(method); ng->intensity_ratio = intensity; + set_model(ng, method); os_atomic_set_bool(&ng->reinit_done, false); pthread_mutex_lock(&ng->nvafx_mutex); for (int i = 0; i < (int)ng->channels; i++) { @@ -736,13 +759,9 @@ static void *noise_suppress_create(obs_data_t *settings, obs_source_t *filter) ng->nvafx_enabled = false; do_log(LOG_ERROR, "NVAFX redist is not installed."); } else { - const char *file = "\\models\\denoiser_48k.trtpkg"; - size_t size = strlen(sdk_path) + strlen(file) + 1; - char *buffer = (char *)bmalloc(size); - - strcpy(buffer, sdk_path); - strcat(buffer, file); - ng->model = buffer; + size_t size = sizeof(sdk_path) + 1; + ng->sdk_path = bmalloc(size); + strcpy(ng->sdk_path, sdk_path); ng->nvafx_enabled = true; ng->nvafx_initialized = false; ng->nvafx_loading = false; diff --git a/plugins/obs-filters/nvafx-load.h b/plugins/obs-filters/nvafx-load.h index 9725d814c..1b128e137 100644 --- a/plugins/obs-filters/nvafx-load.h +++ b/plugins/obs-filters/nvafx-load.h @@ -18,6 +18,12 @@ static HMODULE nv_cuda = NULL; #define NVAFX_EFFECT_AEC "aec" #define NVAFX_EFFECT_SUPERRES "superres" +/** Model paths */ +#define NVAFX_EFFECT_DENOISER_MODEL "\\models\\denoiser_48k.trtpkg" +#define NVAFX_EFFECT_DEREVERB_MODEL "\\models\\dereverb_48k.trtpkg" +#define NVAFX_EFFECT_DEREVERB_DENOISER_MODEL \ + "\\models\\dereverb_denoiser_48k.trtpkg" + #define NVAFX_CHAINED_EFFECT_DENOISER_16k_SUPERRES_16k_TO_48k \ "denoiser16k_superres16kto48k" #define NVAFX_CHAINED_EFFECT_DEREVERB_16k_SUPERRES_16k_TO_48k \ diff --git a/plugins/win-wasapi/win-wasapi.cpp b/plugins/win-wasapi/win-wasapi.cpp index 08aeca496..85c252e66 100644 --- a/plugins/win-wasapi/win-wasapi.cpp +++ b/plugins/win-wasapi/win-wasapi.cpp @@ -182,7 +182,7 @@ class WASAPISource { std::atomic isDefaultDevice = false; bool previouslyFailed = false; - WinHandle reconnectThread; + WinHandle reconnectThread = NULL; class CallbackStartCapture : public ARtwqAsyncCallback { public: @@ -238,6 +238,7 @@ class WASAPISource { WinHandle stopSignal; WinHandle receiveSignal; WinHandle restartSignal; + WinHandle reconnectExitSignal; WinHandle exitSignal; WinHandle initSignal; DWORD reconnectDuration = 0; @@ -298,6 +299,9 @@ public: void Update(obs_data_t *settings); void OnWindowChanged(obs_data_t *settings); + void Activate(); + void Deactivate(); + void SetDefaultDevice(EDataFlow flow, ERole role, LPCWSTR id); void OnStartCapture(); @@ -390,6 +394,10 @@ WASAPISource::WASAPISource(obs_data_t *settings, obs_source_t *source_, if (!restartSignal.Valid()) throw "Could not create restart signal"; + reconnectExitSignal = CreateEvent(nullptr, true, false, nullptr); + if (!reconnectExitSignal.Valid()) + throw "Could not create reconnect exit signal"; + exitSignal = CreateEvent(nullptr, true, false, nullptr); if (!exitSignal.Valid()) throw "Could not create exit signal"; @@ -402,11 +410,6 @@ WASAPISource::WASAPISource(obs_data_t *settings, obs_source_t *source_, if (!reconnectSignal.Valid()) throw "Could not create reconnect signal"; - reconnectThread = CreateThread( - nullptr, 0, WASAPISource::ReconnectThread, this, 0, nullptr); - if (!reconnectThread.Valid()) - throw "Failed to create reconnect thread"; - notify = new WASAPINotify(this); if (!notify) throw "Could not create WASAPINotify"; @@ -532,11 +535,19 @@ void WASAPISource::Stop() if (rtwq_supported) SetEvent(receiveSignal); - WaitForSingleObject(idleSignal, INFINITE); + if (reconnectThread.Valid()) { + WaitForSingleObject(idleSignal, INFINITE); + } else { + const HANDLE sigs[] = {reconnectSignal, idleSignal}; + WaitForMultipleObjects(_countof(sigs), sigs, false, INFINITE); + } SetEvent(exitSignal); - WaitForSingleObject(reconnectThread, INFINITE); + if (reconnectThread.Valid()) { + SetEvent(reconnectExitSignal); + WaitForSingleObject(reconnectThread, INFINITE); + } if (rtwq_supported) rtwq_unlock_work_queue(sampleReady.GetQueueId()); @@ -657,6 +668,25 @@ void WASAPISource::OnWindowChanged(obs_data_t *settings) SetEvent(restartSignal); } +void WASAPISource::Activate() +{ + if (!reconnectThread.Valid()) { + ResetEvent(reconnectExitSignal); + reconnectThread = CreateThread(nullptr, 0, + WASAPISource::ReconnectThread, + this, 0, nullptr); + } +} + +void WASAPISource::Deactivate() +{ + if (reconnectThread.Valid()) { + SetEvent(reconnectExitSignal); + WaitForSingleObject(reconnectThread, INFINITE); + reconnectThread = NULL; + } +} + ComPtr WASAPISource::InitDevice(IMMDeviceEnumerator *enumerator, bool isDefaultDevice, SourceType type, @@ -1005,10 +1035,15 @@ DWORD WINAPI WASAPISource::ReconnectThread(LPVOID param) WASAPISource *source = (WASAPISource *)param; const HANDLE sigs[] = { - source->exitSignal, + source->reconnectExitSignal, source->reconnectSignal, }; + const HANDLE reconnect_sigs[] = { + source->reconnectExitSignal, + source->stopSignal, + }; + bool exit = false; while (!exit) { const DWORD ret = WaitForMultipleObjects(_countof(sigs), sigs, @@ -1020,8 +1055,10 @@ DWORD WINAPI WASAPISource::ReconnectThread(LPVOID param) default: assert(ret == (WAIT_OBJECT_0 + 1)); if (source->reconnectDuration > 0) { - WaitForSingleObject(source->stopSignal, - source->reconnectDuration); + WaitForMultipleObjects( + _countof(reconnect_sigs), + reconnect_sigs, false, + source->reconnectDuration); } source->Start(); } @@ -1426,6 +1463,16 @@ static void UpdateWASAPISource(void *obj, obs_data_t *settings) static_cast(obj)->Update(settings); } +static void ActivateWASAPISource(void *obj) +{ + static_cast(obj)->Activate(); +} + +static void DeactivateWASAPISource(void *obj) +{ + static_cast(obj)->Deactivate(); +} + static bool UpdateWASAPIMethod(obs_properties_t *props, obs_property_t *, obs_data_t *settings) { @@ -1542,6 +1589,8 @@ void RegisterWASAPIInput() info.create = CreateWASAPIInput; info.destroy = DestroyWASAPISource; info.update = UpdateWASAPISource; + info.activate = ActivateWASAPISource; + info.deactivate = DeactivateWASAPISource; info.get_defaults = GetWASAPIDefaultsInput; info.get_properties = GetWASAPIPropertiesInput; info.icon_type = OBS_ICON_TYPE_AUDIO_INPUT; @@ -1559,6 +1608,8 @@ void RegisterWASAPIDeviceOutput() info.create = CreateWASAPIDeviceOutput; info.destroy = DestroyWASAPISource; info.update = UpdateWASAPISource; + info.activate = ActivateWASAPISource; + info.deactivate = DeactivateWASAPISource; info.get_defaults = GetWASAPIDefaultsDeviceOutput; info.get_properties = GetWASAPIPropertiesDeviceOutput; info.icon_type = OBS_ICON_TYPE_AUDIO_OUTPUT; @@ -1576,6 +1627,8 @@ void RegisterWASAPIProcessOutput() info.create = CreateWASAPIProcessOutput; info.destroy = DestroyWASAPISource; info.update = UpdateWASAPISource; + info.activate = ActivateWASAPISource; + info.deactivate = DeactivateWASAPISource; info.get_defaults = GetWASAPIDefaultsProcessOutput; info.get_properties = GetWASAPIPropertiesProcessOutput; info.icon_type = OBS_ICON_TYPE_PROCESS_AUDIO_OUTPUT;