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;