Compare commits

..

15 commits

Author SHA1 Message Date
tytan652 cd401a2131 UI: Remove obs-ui-support
The remaining files are only used by the UI, so those are restored in
the obs-studio target
2024-06-08 12:49:12 +02:00
tytan652 20c3efd391 UI,shared: Move OBSPropertiesView to its own directory 2024-06-08 12:48:50 +02:00
tytan652 db6846fd20 frontend-tools: Remove unused source files in legacy path 2024-06-08 12:48:34 +02:00
tytan652 b9b761fe77 UI,shared: Move SliderIgnoreScroll to its own directory 2024-06-08 12:48:34 +02:00
tytan652 deb54b8e79 UI,shared: Move VScrollArea to its own directory 2024-06-08 12:48:34 +02:00
tytan652 437ef58666 UI,shared: Move OBSPlainTextEdit to its own directory 2024-06-08 12:48:33 +02:00
tytan652 69eae11019 UI,shared: Move Qt Wrappers to its own directory 2024-06-08 12:48:13 +02:00
tytan652 1c4966d471 UI: Move QTToGSWindow outside of Qt wrappers 2024-06-08 12:47:30 +02:00
tytan652 988b5e4563 deps,shared,plugins: Move opts-parser to shared folder 2024-06-08 12:47:30 +02:00
tytan652 f022870923 deps,shared,frontend-tools: Move obs-scripting to shared folder 2024-06-08 12:47:30 +02:00
tytan652 6b4f3711d5 deps,shared,obs-ffmpeg: Move media-playback to shared folder 2024-06-08 12:47:30 +02:00
tytan652 f154b4a331 deps,shared,win-capture: Move ipc-util to shared folder 2024-06-08 12:47:30 +02:00
tytan652 51c17032be deps,shared,obs-outputs: Move happy-eyeballs to shared folder 2024-06-08 12:47:30 +02:00
tytan652 f650cdf6bb deps,shared,plugins: Move file-updater to shared folder 2024-06-08 12:47:30 +02:00
tytan652 e38f6754eb Add shared folder in gitignore and format scripts 2024-06-08 12:47:30 +02:00
60 changed files with 1423 additions and 1935 deletions

View file

@ -9,7 +9,7 @@ env:
task:
install_script:
- pkg install -y
- pkg-install -y
cmake ninja binutils pkgconf curl
ffmpeg qt6-base qt6-svg jansson libsysinfo e2fsprogs-libuuid pulseaudio
alsa-lib pipewire v4l_compat libpci librist srt nlohmann-json uthash

View file

@ -25,7 +25,6 @@ runs:
with:
path: "repo"
fetch-depth: 0
ref: ${{ inputs.tagName }}
- name: Download Release Artifact
shell: pwsh
@ -41,9 +40,9 @@ runs:
- name: Setup bouf
shell: pwsh
env:
BOUF_TAG: 'v0.6.4'
BOUF_HASH: 'aca6810e741dc38ff843fab7b25a0ad8570ee84f5595132cf0cc4a5b0131b4c4'
BOUF_NSIS_HASH: 'ed453784486556bd959d56743a8478ad3f68fe0305e9b43ac19d8771d0515257'
BOUF_TAG: 'v0.6.3'
BOUF_HASH: '7f1d266467620aa553a705391ee06128e8ee14af66129a0e64a282997fb6fd83'
BOUF_NSIS_HASH: 'a234126de89f122b6a552df3416de3eabcb4195217626c7f4eaec71b20fe36eb'
GH_TOKEN: ${{ github.token }}
run: |
# Download bouf release
@ -87,10 +86,7 @@ runs:
run: |
# Release notes are just the tag body on Windows
Set-Location repo
git tag -l --format='%(contents:subject)' ${{ inputs.tagName }} > "${{ github.workspace }}/notes.rst"
Write-Output "###################################################" >> "${{ github.workspace }}/notes.rst"
Write-Output "" >> "${{ github.workspace }}/notes.rst"
git tag -l --format='%(contents:body)' ${{ inputs.tagName }} >> "${{ github.workspace }}/notes.rst"
git tag -l --format='%(contents:body)' ${{ inputs.version }} > "${{ github.workspace }}/notes.rst"
- name: Run bouf
shell: pwsh

View file

@ -27,9 +27,9 @@ runs:
- name: Setup bouf
shell: pwsh
env:
BOUF_TAG: 'v0.6.3-nsis-update'
BOUF_HASH: 'c5b58f613e7d7ad8090900cbd6b5f4b057528ee9f0b7e4ba52038ddfdd50b924'
BOUF_NSIS_HASH: 'd77cda42d33af77774beddb3fd6edb89e75050f03c174260df7d66bc8247334f'
BOUF_TAG: 'v0.6.3'
BOUF_HASH: '7f1d266467620aa553a705391ee06128e8ee14af66129a0e64a282997fb6fd83'
BOUF_NSIS_HASH: 'a234126de89f122b6a552df3416de3eabcb4195217626c7f4eaec71b20fe36eb'
GH_TOKEN: ${{ github.token }}
run: |
# Download bouf release

View file

@ -20,7 +20,6 @@ jobs:
outputs:
validTag: ${{ steps.check.outputs.validTag }}
flatpakMatrix: ${{ steps.check.outputs.flatpakMatrix }}
updateChannel: ${{ steps.check.outputs.updateChannel }}
steps:
- name: Check Release Tag ☑️
id: check

View file

@ -207,7 +207,7 @@ jobs:
sign-windows-build:
name: Windows Signing ✍️
uses: obsproject/obs-studio/.github/workflows/sign-windows.yaml@d2b05a6e0c6a0f51c42f247efd581bed8f4a1877
uses: obsproject/obs-studio/.github/workflows/sign-windows.yaml@b5b457d7b059397b70f6e3dd09b65e172ad734c3
if: github.repository_owner == 'obsproject' && github.ref_type == 'tag'
needs: build-project
permissions:

View file

@ -65,6 +65,9 @@ bool AbsoluteSlider::eventFilter(QObject *obj, QEvent *event)
}
}
if (event->type() == QEvent::Wheel)
return true;
return QSlider::eventFilter(obj, event);
}

View file

@ -350,7 +350,7 @@ std::shared_ptr<Auth> YoutubeAuth::Login(QWidget *owner,
}
#ifdef BROWSER_AVAILABLE
YoutubeChatDock::YoutubeChatDock(const QString &title) : BrowserDock(title)
void YoutubeChatDock::SetWidget(QCefWidget *widget_)
{
lineEdit = new LineEditAutoResize();
lineEdit->setVisible(false);
@ -364,14 +364,6 @@ YoutubeChatDock::YoutubeChatDock(const QString &title) : BrowserDock(title)
chatLayout->addWidget(lineEdit, 1);
chatLayout->addWidget(sendButton);
QWidget::connect(lineEdit, &LineEditAutoResize::returnPressed, this,
&YoutubeChatDock::SendChatMessage);
QWidget::connect(sendButton, &QPushButton::pressed, this,
&YoutubeChatDock::SendChatMessage);
}
void YoutubeChatDock::SetWidget(QCefWidget *widget_)
{
QVBoxLayout *layout = new QVBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(widget_, 1);
@ -381,6 +373,11 @@ void YoutubeChatDock::SetWidget(QCefWidget *widget_)
widget->setLayout(layout);
setWidget(widget);
QWidget::connect(lineEdit, &LineEditAutoResize::returnPressed, this,
&YoutubeChatDock::SendChatMessage);
QWidget::connect(sendButton, &QPushButton::pressed, this,
&YoutubeChatDock::SendChatMessage);
cefWidget.reset(widget_);
}

View file

@ -21,7 +21,7 @@ private:
QHBoxLayout *chatLayout;
public:
YoutubeChatDock(const QString &title);
inline YoutubeChatDock(const QString &title) : BrowserDock(title) {}
void SetWidget(QCefWidget *widget_);
void SetApiChatId(const std::string &id);

View file

@ -117,7 +117,7 @@ OBSBasicControls::OBSBasicControls(OBSBasic *main)
connect(main, &OBSBasic::ReplayBufStarted, this,
&OBSBasicControls::ReplayBufferStarted);
connect(main, &OBSBasic::ReplayBufStopping, this,
connect(main, &OBSBasic::ReplayBufferStopping, this,
&OBSBasicControls::ReplayBufferStopping);
connect(main, &OBSBasic::ReplayBufStopped, this,
&OBSBasicControls::ReplayBufferStopped);

View file

@ -1,6 +1,6 @@
target_sources(obs-studio PRIVATE platform-x11.cpp)
target_compile_definitions(obs-studio PRIVATE OBS_INSTALL_PREFIX="${OBS_INSTALL_PREFIX}")
target_link_libraries(obs-studio PRIVATE Qt::GuiPrivate Qt::DBus procstat)
target_link_libraries(obs-studio PRIVATE Qt::GuiPrivate procstat)
target_sources(obs-studio PRIVATE system-info-posix.cpp)

View file

@ -736,7 +736,7 @@ Basic.Main.StopRecording="Stop Recording"
Basic.Main.PauseRecording="Pause Recording"
Basic.Main.UnpauseRecording="Unpause Recording"
Basic.Main.SplitFile="Split Recording File"
Basic.Main.AddChapterMarker="Add Chapter Marker (Hybrid MP4 only)"
Basic.Main.AddChapterMarker="Add Chapter Marker"
Basic.Main.StoppingRecording="Stopping Recording..."
Basic.Main.StopReplayBuffer="Stop Replay Buffer"
Basic.Main.StoppingReplayBuffer="Stopping Replay Buffer..."
@ -1566,10 +1566,10 @@ FailedToStartStream.MissingConfigURL="No config URL available for the current se
FailedToStartStream.NoCustomRTMPURLInSettings="Custom RTMP URL not specified"
FailedToStartStream.InvalidCustomConfig="Invalid custom config"
FailedToStartStream.FailedToCreateMultitrackVideoService="Failed to create multitrack video service"
FailedToStartStream.FailedToCreateMultitrackVideoOutput="Failed to create multitrack video RTMP output"
FailedToStartStream.FailedToCreateMultitrackVideoOutput="Failed to create multitrack video rtmp output"
FailedToStartStream.EncoderNotAvailable="NVENC not available.\n\nFailed to find encoder type '%1'"
FailedToStartStream.FailedToCreateVideoEncoder="Failed to create video encoder '%1' (type: '%2')"
FailedToStartStream.FailedToGetOBSVideoInfo="Failed to get OBS video info while creating encoder '%1' (type: '%2')"
FailedToStartStream.FailedToGetOBSVideoInfo="Failed to get obs video info while creating encoder '%1' (type: '%2')"
FailedToStartStream.FailedToCreateAudioEncoder="Failed to create audio encoder"
FailedToStartStream.NoRTMPURLInConfig="Config does not contain stream target RTMP(S) URL"
FailedToStartStream.FallbackToDefault="Starting the stream using %1 failed; do you want to retry using single encode settings?"
@ -1585,6 +1585,3 @@ MultitrackVideo.IncompatibleSettings.Title="Incompatible Settings"
MultitrackVideo.IncompatibleSettings.Text="%1 is not currently compatible with:\n\n%2\nTo continue streaming with %1, disable incompatible settings:\n\n%3\nand Start Streaming again."
MultitrackVideo.IncompatibleSettings.DisableAndStartStreaming="Disable for this stream and Start Streaming"
MultitrackVideo.IncompatibleSettings.UpdateAndStartStreaming="Update Settings and Start Streaming"
MultitrackVideo.IncompatibleSettings.AudioChannels="%1 is not currently compatible with [Audio → General → Channels] set to '%2', %3"
MultitrackVideo.IncompatibleSettings.AudioChannelsSingle="[Audio → General → Channels] needs to be set to '%1'"
MultitrackVideo.IncompatibleSettings.AudioChannelsMultiple="%1 requires multiple different settings for [Audio → General → Channels]"

View file

@ -396,13 +396,13 @@ QCalendarWidget QToolButton {
QCalendarWidget #qt_calendar_prevmonth {
padding: 2px;
qproperty-icon: url(theme:Dark/left.svg);
icon-size: 16px;
icon-size: 16px, 16px;
}
QCalendarWidget #qt_calendar_nextmonth {
padding: 2px;
qproperty-icon: url(theme:Dark/right.svg);
icon-size: 16px;
icon-size: 16px, 16px;
}
/* Status Bar */

View file

@ -127,14 +127,13 @@
--padding_base_border: calc(var(--padding_base) + 1px);
--spinbox_button_height: calc(var(--input_height_half) - 1px);
--spinbox_button_height: calc(var(--input_height) - 2px);
--volume_slider: calc(calc(10px + var(--font_base_value)) / 4);
--volume_slider: calc(calc(6px + var(--font_base_value)) / 2);
--volume_slider_box: calc(var(--volume_slider) * 4);
--volume_slider_label: calc(var(--volume_slider_box) * 2);
--volume_slider_label: calc(var(--volume_slider) * 6);
--scrollbar_size: 12px;
--settings_scrollbar_size: calc(var(--scrollbar_size) + 9px);
/* Inputs / Controls */
--border_color: var(--grey4);
@ -452,16 +451,6 @@ OBSBasicSettings QListWidget::item {
padding: var(--padding_large);
}
OBSBasicSettings QScrollBar:vertical {
width: var(--settings_scrollbar_size);
margin-left: 9px;
}
OBSBasicSettings QScrollBar:horizontal {
height: var(--settings_scrollbar_size);
margin-top: 9px;
}
/* Settings properties view */
OBSBasicSettings #PropertiesContainer {
background-color: var(--bg_base);
@ -477,7 +466,7 @@ OBSDock > QWidget {
}
#transitionsFrame {
padding: var(--padding_large);
padding: 4px 8px;
}
OBSDock QLabel {
@ -649,12 +638,12 @@ QScrollBar::handle:horizontal {
QPushButton#sourcePropertiesButton {
qproperty-icon: url(theme:Dark/settings/general.svg);
icon-size: var(--icon_base);
icon-size: var(--icon_base), var(--icon_base);
}
QPushButton#sourceFiltersButton {
qproperty-icon: url(theme:Dark/filter.svg);
icon-size: var(--icon_base);
icon-size: var(--icon_base), var(--icon_base);
}
/* Scenes and Sources toolbar */
@ -809,6 +798,7 @@ QDateTimeEdit {
border-radius: var(--border_radius);
padding: var(--padding_large) var(--padding_large);
padding-left: 10px;
max-height: var(--input_height);
}
QComboBox QAbstractItemView::item:selected,
@ -827,7 +817,7 @@ QComboBox::drop-down,
QDateTimeEdit::drop-down {
border:none;
border-left: 1px solid var(--grey6);
width: var(--input_height);
width: 32px;
}
QComboBox::down-arrow,
@ -871,7 +861,6 @@ QPlainTextEdit {
border: none;
border-radius: var(--border_radius);
padding: var(--input_padding) var(--padding_small) var(--input_padding) var(--input_padding);
padding-left: 8px;
border: 1px solid var(--input_bg);
height: var(--input_height);
}
@ -901,9 +890,10 @@ QDoubleSpinBox {
background-color: var(--input_bg);
border: 1px solid var(--input_bg);
border-radius: var(--border_radius);
margin-right: var(--spacing_base);
padding: var(--input_padding) 0px var(--input_padding) var(--input_padding);
padding-left: 8px;
max-height: var(--input_height);
height: var(--spinbox_button_height);
max-height: var(--spinbox_button_height);
}
QSpinBox:hover,
@ -923,12 +913,11 @@ QDoubleSpinBox::up-button {
subcontrol-origin: padding;
subcontrol-position: top right; /* position at the top right corner */
width: var(--input_height);
height: var(--spinbox_button_height);
width: 32px;
border-left: 1px solid var(--grey6);
border-bottom: 1px solid transparent;
border-radius: 0px;
border-top-right-radius: var(--border_radius_small);
margin-top: -1px;
}
QSpinBox::down-button,
@ -936,12 +925,11 @@ QDoubleSpinBox::down-button {
subcontrol-origin: padding;
subcontrol-position: bottom right; /* position at the top right corner */
width: var(--input_height);
height: var(--spinbox_button_height);
width: 32px;
border-left: 1px solid var(--grey6);
border-top: 1px solid var(--grey6);
border-radius: 0px;
border-bottom-right-radius: var(--border_radius_small);
margin-bottom: -1px;
}
QSpinBox::up-button:hover,
@ -1041,7 +1029,7 @@ QPushButton {
height: var(--input_height);
max-height: var(--input_height);
padding: var(--input_padding) var(--padding_wide);
icon-size: var(--icon_base);
icon-size: var(--icon_base), var(--icon_base);
}
QPushButton {
@ -1059,7 +1047,7 @@ QPushButton[toolButton="true"] {
margin: 0px var(--spacing_base);
border: 1px solid var(--button_border);
border-radius: var(--border_radius);
icon-size: var(--icon_base);
icon-size: var(--icon_base), var(--icon_base);
}
QToolButton:last-child,
@ -1078,8 +1066,6 @@ QPushButton:hover {
QToolButton:hover,
QToolButton:focus,
QPushButton[toolButton="true"]:hover,
QPushButton[toolButton="true"]:focus,
MuteCheckBox::indicator:hover,
MuteCheckBox::indicator:focus {
border-color: var(--button_border);
@ -1106,9 +1092,7 @@ QPushButton:pressed:hover {
}
QToolButton:pressed,
QToolButton:pressed:hover,
QPushButton[toolButton="true"]:pressed,
QPushButton[toolButton="true"]:pressed:hover {
QToolButton:pressed:hover {
background-color: var(--button_bg_down);
border-color: var(--button_border);
}
@ -1213,10 +1197,10 @@ QSlider::handle:disabled {
height: var(--icon_base);
background-color: var(--button_bg);
padding: var(--padding_base_border) var(--padding_base_border);
margin: 0px;
margin: 0px var(--spacing_base);
border: 1px solid var(--button_border);
border-radius: var(--border_radius);
icon-size: var(--icon_base);
icon-size: var(--icon_base), var(--icon_base);
}
/* This is an incredibly cursed but necessary fix */
@ -1248,12 +1232,12 @@ VolControl #volLabel {
/* Horizontal Mixer */
#hMixerScrollArea VolControl {
padding: 0px var(--padding_xlarge) var(--padding_base);
padding: 0px var(--padding_large);
border-bottom: 1px solid var(--border_color);
}
#hMixerScrollArea VolControl QSlider {
margin: 0px 0px var(--padding_base);
margin: 0px 0px;
}
#hMixerScrollArea VolControl QSlider::groove:horizontal {
@ -1261,6 +1245,10 @@ VolControl #volLabel {
height: var(--volume_slider);
}
#hMixerScrollArea VolControl QPushButton {
margin-right: var(--padding_xlarge);
}
/* Vertical Mixer */
#stackedMixerArea QScrollBar:vertical {
border-left: 1px solid var(--border_color);
@ -1273,13 +1261,13 @@ VolControl #volLabel {
#vMixerScrollArea VolControl QSlider {
width: var(--volume_slider_box);
margin: 0px var(--padding_xlarge);
}
#vMixerScrollArea VolControl #volLabel {
padding: var(--padding_base) 0px var(--padding_base);
min-width: var(--volume_slider_label);
margin-left: var(--padding_xlarge);
max-width: var(--volume_slider_label);
margin-right: 0;
text-align: center;
}
@ -1289,7 +1277,7 @@ VolControl #volLabel {
}
#vMixerScrollArea VolControl #volMeterFrame {
padding: var(--padding_large) var(--padding_xlarge) var(--padding_large) 0px;
padding: var(--padding_large) var(--padding_xlarge);
}
#vMixerScrollArea VolControl QLabel {
@ -1396,14 +1384,9 @@ QLabel#errorLabel {
font-weight: bold;
}
QFrame [noticeFrame="true"] {
QFrame [themeID="notice"] {
background: var(--bg_preview);
border-radius: var(--border_radius);
padding: var(--padding_xlarge) var(--padding_large);
}
QFrame [noticeFrame="true"] QLabel {
padding: var(--padding_large) 0px;
}
/* About dialog */
@ -1603,19 +1586,19 @@ MuteCheckBox::indicator:unchecked {
height: var(--icon_base);
background-color: var(--button_bg);
padding: var(--padding_base_border) var(--padding_base_border);
margin: 0px;
margin: 0px var(--spacing_base);
border: 1px solid var(--button_border);
border-radius: var(--border_radius);
icon-size: var(--icon_base);
icon-size: var(--icon_base), var(--icon_base);
}
MuteCheckBox::indicator:hover,
MuteCheckBox::indicator:unchecked:hover {
background-color: var(--button_bg_hover);
padding: var(--padding_base_border) var(--padding_base_border);
margin: 0px;
margin: 0px var(--spacing_base);
border: 1px solid var(--button_border_hover);
icon-size: var(--icon_base);
icon-size: var(--icon_base), var(--icon_base);
}
MuteCheckBox::indicator:pressed,
@ -1886,13 +1869,13 @@ QCalendarWidget QToolButton {
QCalendarWidget #qt_calendar_prevmonth {
padding: var(--padding_small);
qproperty-icon: url(theme:Dark/left.svg);
icon-size: var(--icon_base);
icon-size: var(--icon_base), var(--icon_base);
}
QCalendarWidget #qt_calendar_nextmonth {
padding: var(--padding_small);
qproperty-icon: url(theme:Dark/right.svg);
icon-size: var(--icon_base);
icon-size: var(--icon_base), var(--icon_base);
}
QCalendarWidget QToolButton:hover {

View file

@ -11,7 +11,7 @@
--grey2: rgb(134,135,134);
--grey3: rgb(122,121,122);
--grey4: rgb(76,76,76);
--grey5: rgb(70,69,70);
--grey5: rgb(88,87,88);
--grey6: rgb(31,30,31);
--grey7: rgb(58,57,58);
--grey8: rgb(46,45,46);
@ -31,12 +31,8 @@
/* OS Fixes */
--os_mac_font_base_value: 11;
--font_small: calc(0.8pt * var(--font_base_value));
--icon_base: calc(6px + var(--font_base_value));
--padding_xlarge: calc(2px + calc(0.5px * var(--padding_base_value)));
--padding_wide: calc(18px + calc(0.25 * var(--padding_base_value)));
--padding_menu: calc(8px + calc(1 * var(--padding_base_value)));
@ -49,14 +45,14 @@
--border_radius_large: 2px;
--input_bg: var(--grey4);
--input_bg_hover: var(--grey1);
--input_bg_hover: var(--grey5);
--input_bg_focus: var(--grey6);
--list_item_bg_selected: var(--primary);
--list_item_bg_hover: var(--primary_light);
--input_border: var(--grey4);
--input_border_hover: var(--grey1);
--input_border_hover: var(--grey5);
--input_border_focus: var(--grey6);
--spacing_input: var(--spacing_base);
@ -95,17 +91,11 @@ QStatusBar {
background-color: var(--bg_window);
}
OBSDock > QWidget {
border-top: 1px solid var(--border_color);
padding-top: var(--spacing_large);
}
QDockWidget {
font-weight: normal;
}
QDockWidget::title {
background-color: var(--grey5);
padding: var(--dock_title_padding);
text-align: center;
}
@ -114,10 +104,6 @@ QDockWidget > QWidget {
background: var(--bg_window);
}
#transitionsFrame {
padding: var(--padding_xlarge);
}
SceneTree::item,
SourceTreeItem {
border-width: 0px;
@ -127,10 +113,6 @@ QMenu::item {
padding: var(--padding_menu_y) var(--padding_menu);
}
QMenu::item {
padding-right: 20px;
}
QGroupBox {
background: var(--bg_window);
border: 1px solid var(--border_color);
@ -207,24 +189,16 @@ OBSBasicSettings QListWidget::item {
padding: 4px;
}
QPushButton:checked {
border-color: var(--primary);
}
QToolButton,
QPushButton[toolButton="true"] {
background-color: var(--bg_window);
border-color: var(--bg_window);
}
#stackedMixerArea QScrollArea {
background: var(--bg_base);
}
#stackedMixerArea QPushButton {
min-width: var(--icon_base);
padding: var(--padding_large) var(--padding_large);
icon-size: var(--icon_base);
icon-size: var(--icon_base), var(--icon_base);
}
#stackedMixerArea QPushButton:!hover {
@ -237,40 +211,13 @@ QPushButton[toolButton="true"] {
border: none;
}
#hMixerScrollArea VolControl {
padding: 0px 2px;
margin-bottom: 1px;
}
#hMixerScrollArea QLabel {
margin: var(--padding_xlarge) 0px;
}
#hMixerScrollArea #volMeterFrame {
margin-top: var(--spacing_large);
}
#vMixerScrollArea VolControl {
padding: 0px var(--padding_xlarge) var(--spacing_large);
border-right: 1px solid var(--bg_window);
}
#vMixerScrollArea QLabel {
font-size: var(--font_small);
margin: var(--padding_xlarge) 0px;
}
#vMixerScrollArea #volLabel {
font-size: var(--font_base);
}
MuteCheckBox::indicator,
MuteCheckBox::indicator:unchecked {
background-color: var(--bg_base);
border: none;
width: var(--icon_base_mixer);
height: var(--icon_base_mixer);
icon-size: var(--icon_base_mixer);
icon-size: var(--icon_base_mixer), var(--icon_base_mixer);
}
MuteCheckBox::indicator:checked {
@ -284,7 +231,7 @@ MuteCheckBox::indicator:unchecked:hover {
MuteCheckBox::indicator:hover,
MuteCheckBox::indicator:unchecked:hover {
icon-size: var(--icon_base_mixer);
icon-size: var(--icon_base_mixer), var(--icon_base_mixer);
border: none;
}
@ -304,7 +251,3 @@ VolumeMeter {
qproperty-minorTickColor: rgb(122,121,122); /* light */
qproperty-meterThickness: 3;
}
OBSBasicStats {
background: var(--bg_window);
}

View file

@ -128,9 +128,6 @@
<property name="themeID" stdset="0">
<string>addIconSmall</string>
</property>
<property name="toolButton" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item>
@ -160,9 +157,6 @@
<property name="themeID" stdset="0">
<string>removeIconSmall</string>
</property>
<property name="toolButton" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item>
@ -192,9 +186,6 @@
<property name="themeID" stdset="0">
<string>upArrowIconSmall</string>
</property>
<property name="toolButton" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item>
@ -224,9 +215,6 @@
<property name="themeID" stdset="0">
<string>downArrowIconSmall</string>
</property>
<property name="toolButton" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item>
@ -362,9 +350,6 @@
<property name="themeID" stdset="0">
<string>addIconSmall</string>
</property>
<property name="toolButton" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item>
@ -394,9 +379,6 @@
<property name="themeID" stdset="0">
<string>removeIconSmall</string>
</property>
<property name="toolButton" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item>
@ -426,9 +408,6 @@
<property name="themeID" stdset="0">
<string>upArrowIconSmall</string>
</property>
<property name="toolButton" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item>
@ -458,9 +437,6 @@
<property name="themeID" stdset="0">
<string>downArrowIconSmall</string>
</property>
<property name="toolButton" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item>

File diff suppressed because it is too large Load diff

View file

@ -128,8 +128,7 @@ GoLiveApi::Config DownloadGoLiveConfig(QWidget *parent, QString url,
QString MultitrackVideoAutoConfigURL(obs_service_t *service)
{
static const std::optional<QString> cli_url =
[]() -> std::optional<QString> {
static const QString url = [service]() -> QString {
auto args = qApp->arguments();
for (int i = 0; i < args.length() - 1; i++) {
if (args[i] == "--config-url" &&
@ -137,17 +136,10 @@ QString MultitrackVideoAutoConfigURL(obs_service_t *service)
return args[i + 1];
}
}
return std::nullopt;
}();
QString url;
if (cli_url.has_value()) {
url = *cli_url;
} else {
OBSDataAutoRelease settings = obs_service_get_settings(service);
url = obs_data_get_string(settings,
"multitrack_video_configuration_url");
}
return obs_data_get_string(
settings, "multitrack_video_configuration_url");
}();
blog(LOG_INFO, "Go live URL: %s", url.toUtf8().constData());
return url;

View file

@ -14,58 +14,29 @@ constructGoLivePost(QString streamKey,
{
GoLiveApi::PostData post_data{};
post_data.service = "IVS";
post_data.schema_version = "2024-06-04";
post_data.schema_version = "2023-05-10";
post_data.authentication = streamKey.toStdString();
system_info(post_data.capabilities);
auto &client = post_data.client;
auto &client = post_data.capabilities.client;
client.name = "obs-studio";
client.version = obs_get_version_string();
auto add_codec = [&](const char *codec) {
auto it = std::find(std::begin(client.supported_codecs),
std::end(client.supported_codecs), codec);
if (it != std::end(client.supported_codecs))
return;
client.supported_codecs.push_back(codec);
};
const char *encoder_id = nullptr;
for (size_t i = 0; obs_enum_encoder_types(i, &encoder_id); i++) {
auto codec = obs_get_encoder_codec(encoder_id);
if (!codec)
continue;
if (qstricmp(codec, "h264") == 0) {
add_codec("h264");
#ifdef ENABLE_HEVC
} else if (qstricmp(codec, "hevc")) {
add_codec("h265");
#endif
} else if (qstricmp(codec, "av1")) {
add_codec("av1");
}
}
auto &preferences = post_data.preferences;
preferences.vod_track_audio = vod_track_enabled;
client.vod_track_audio = vod_track_enabled;
obs_video_info ovi;
if (obs_get_video_info(&ovi)) {
preferences.width = ovi.output_width;
preferences.height = ovi.output_height;
preferences.framerate.numerator = ovi.fps_num;
preferences.framerate.denominator = ovi.fps_den;
client.width = ovi.output_width;
client.height = ovi.output_height;
client.fps_numerator = ovi.fps_num;
client.fps_denominator = ovi.fps_den;
preferences.canvas_width = ovi.base_width;
preferences.canvas_height = ovi.base_height;
preferences.composition_gpu_index = ovi.adapter;
client.canvas_width = ovi.base_width;
client.canvas_height = ovi.base_height;
}
auto &preferences = post_data.preferences;
if (maximum_aggregate_bitrate.has_value())
preferences.maximum_aggregate_bitrate =
maximum_aggregate_bitrate.value();

View file

@ -1,6 +1,6 @@
#include "lineedit-autoresize.hpp"
LineEditAutoResize::LineEditAutoResize() : m_maxLength(32767)
LineEditAutoResize::LineEditAutoResize()
{
connect(this, &LineEditAutoResize::textChanged, this,
&LineEditAutoResize::checkTextLength);

View file

@ -109,9 +109,18 @@ using json = nlohmann::json;
struct Client {
string name = "obs-studio";
string version;
std::vector<string> supported_codecs;
bool vod_track_audio;
uint32_t width;
uint32_t height;
uint32_t fps_numerator;
uint32_t fps_denominator;
uint32_t canvas_width;
uint32_t canvas_height;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(Client, name, version, supported_codecs)
NLOHMANN_DEFINE_TYPE_INTRUSIVE(Client, name, version, vod_track_audio,
width, height, fps_numerator,
fps_denominator, canvas_width,
canvas_height)
};
struct Cpu {
@ -173,44 +182,35 @@ struct System {
};
struct Capabilities {
Client client;
Cpu cpu;
Memory memory;
optional<GamingFeatures> gaming_features;
System system;
optional<std::vector<Gpu>> gpu;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(Capabilities, cpu, memory,
NLOHMANN_DEFINE_TYPE_INTRUSIVE(Capabilities, client, cpu, memory,
gaming_features, system, gpu)
};
struct Preferences {
optional<uint64_t> maximum_aggregate_bitrate;
optional<uint32_t> maximum_video_tracks;
bool vod_track_audio;
uint32_t width;
uint32_t height;
media_frames_per_second framerate;
uint32_t canvas_width;
uint32_t canvas_height;
optional<uint32_t> composition_gpu_index;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(Preferences, maximum_aggregate_bitrate,
maximum_video_tracks, vod_track_audio,
width, height, framerate, canvas_width,
canvas_height, composition_gpu_index)
maximum_video_tracks)
};
struct PostData {
string service;
string schema_version;
string service = "IVS";
string schema_version = "2023-05-10";
string authentication;
Client client;
Capabilities capabilities;
Preferences preferences;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(PostData, service, schema_version,
authentication, client, capabilities,
authentication, capabilities,
preferences)
};
@ -259,29 +259,47 @@ struct VideoEncoderConfiguration {
string type;
uint32_t width;
uint32_t height;
uint32_t bitrate;
optional<media_frames_per_second> framerate;
optional<obs_scale_type> gpu_scale_type;
json settings;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(VideoEncoderConfiguration,
type, width, height,
framerate, gpu_scale_type,
settings)
bitrate, framerate,
gpu_scale_type)
};
struct AudioEncoderConfiguration {
string codec;
uint32_t track_id;
uint32_t channels;
json settings;
uint32_t bitrate;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(AudioEncoderConfiguration, codec,
track_id, channels, settings)
track_id, channels, bitrate)
};
template<typename T> struct EncoderConfiguration {
T config;
json data;
friend void to_json(nlohmann::json &nlohmann_json_j,
const EncoderConfiguration<T> &nlohmann_json_t)
{
nlohmann_json_j = nlohmann_json_t.data;
to_json(nlohmann_json_j, nlohmann_json_t.config);
}
friend void from_json(const nlohmann::json &nlohmann_json_j,
EncoderConfiguration<T> &nlohmann_json_t)
{
nlohmann_json_t.data = nlohmann_json_j;
nlohmann_json_j.get_to(nlohmann_json_t.config);
}
};
struct AudioConfigurations {
std::vector<AudioEncoderConfiguration> live;
optional<std::vector<AudioEncoderConfiguration>> vod;
std::vector<EncoderConfiguration<AudioEncoderConfiguration>> live;
std::vector<EncoderConfiguration<AudioEncoderConfiguration>> vod;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(AudioConfigurations, live,
vod)
@ -291,7 +309,8 @@ struct Config {
Meta meta;
optional<Status> status;
std::vector<IngestEndpoint> ingest_endpoints;
std::vector<VideoEncoderConfiguration> encoder_configurations;
std::vector<EncoderConfiguration<VideoEncoderConfiguration>>
encoder_configurations;
AudioConfigurations audio_configurations;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Config, meta, status,

View file

@ -1,7 +1,6 @@
#include "multitrack-video-error.hpp"
#include <QMessageBox>
#include <QPushButton>
#include "obs-app.hpp"
MultitrackVideoError MultitrackVideoError::critical(QString error)
@ -32,12 +31,9 @@ bool MultitrackVideoError::ShowDialog(
QTStr("FailedToStartStream.WarningRetryNonMultitrackVideo")
.arg(multitrack_video_name));
mb.setIcon(QMessageBox::Warning);
QAbstractButton *yesButton =
mb.addButton(QTStr("Yes"), QMessageBox::YesRole);
mb.addButton(QTStr("No"), QMessageBox::NoRole);
mb.exec();
return mb.clickedButton() == yesButton;
mb.setStandardButtons(QMessageBox::StandardButton::Yes |
QMessageBox::StandardButton::No);
return mb.exec() == QMessageBox::StandardButton::Yes;
} else if (type == Type::Critical) {
mb.setText(error);
mb.setIcon(QMessageBox::Critical);

View file

@ -8,7 +8,6 @@
#include <obs-app.hpp>
#include <obs.hpp>
#include <remote-text.hpp>
#include <window-basic-main.hpp>
#include <algorithm>
#include <cinttypes>
@ -117,20 +116,18 @@ create_service(const GoLiveApi::Config &go_live_config,
/* The stream key itself may contain query parameters, such as
* "bandwidthtest" that need to be carried over. */
QUrl parsed_user_key{in_stream_key};
QUrlQuery user_key_query{parsed_user_key};
QUrl parsed_key{stream_key};
QUrlQuery key_query{parsed_key};
QUrl parsed_url{url};
QUrlQuery parsed_query{parsed_url};
for (const auto &[key, value] : user_key_query.queryItems())
for (const auto &[key, value] : key_query.queryItems())
parsed_query.addQueryItem(key, value);
if (!go_live_config.meta.config_id.empty()) {
parsed_query.addQueryItem(
"clientConfigId",
"obsConfigId",
QString::fromStdString(go_live_config.meta.config_id));
}
@ -262,11 +259,12 @@ static bool encoder_available(const char *type)
return false;
}
static OBSEncoderAutoRelease
create_video_encoder(DStr &name_buffer, size_t encoder_index,
const GoLiveApi::VideoEncoderConfiguration &encoder_config)
static OBSEncoderAutoRelease create_video_encoder(
DStr &name_buffer, size_t encoder_index,
const GoLiveApi::EncoderConfiguration<
GoLiveApi::VideoEncoderConfiguration> &encoder_config)
{
auto encoder_type = encoder_config.type.c_str();
auto encoder_type = encoder_config.config.type.c_str();
if (!encoder_available(encoder_type)) {
blog(LOG_ERROR, "Encoder type '%s' not available",
encoder_type);
@ -278,8 +276,8 @@ create_video_encoder(DStr &name_buffer, size_t encoder_index,
dstr_printf(name_buffer, "multitrack video video encoder %zu",
encoder_index);
OBSDataAutoRelease encoder_settings = obs_data_create_from_json(
encoder_config.settings.dump().c_str());
OBSDataAutoRelease encoder_settings =
obs_data_create_from_json(encoder_config.data.dump().c_str());
obs_data_set_bool(encoder_settings, "disable_scenecut", true);
OBSEncoderAutoRelease video_encoder = obs_video_encoder_create(
@ -303,19 +301,22 @@ create_video_encoder(DStr &name_buffer, size_t encoder_index,
.arg(name_buffer->array, encoder_type));
}
adjust_video_encoder_scaling(ovi, video_encoder, encoder_config,
adjust_video_encoder_scaling(ovi, video_encoder, encoder_config.config,
encoder_index);
adjust_encoder_frame_rate_divisor(ovi, video_encoder, encoder_config,
encoder_index);
adjust_encoder_frame_rate_divisor(ovi, video_encoder,
encoder_config.config, encoder_index);
return video_encoder;
}
static OBSEncoderAutoRelease create_audio_encoder(const char *name,
const char *audio_encoder_id,
obs_data_t *settings,
uint32_t audio_bitrate,
size_t mixer_idx)
{
OBSDataAutoRelease settings = obs_data_create();
obs_data_set_int(settings, "bitrate", audio_bitrate);
OBSEncoderAutoRelease audio_encoder = obs_audio_encoder_create(
audio_encoder_id, name, settings, mixer_idx, nullptr);
if (!audio_encoder) {
@ -332,12 +333,11 @@ struct OBSOutputs {
};
static OBSOutputs
SetupOBSOutput(QWidget *parent, const QString &multitrack_video_name,
obs_data_t *dump_stream_to_file_config,
SetupOBSOutput(obs_data_t *dump_stream_to_file_config,
const GoLiveApi::Config &go_live_config,
std::vector<OBSEncoderAutoRelease> &audio_encoders,
std::shared_ptr<obs_encoder_group_t> &video_encoder_group,
const char *audio_encoder_id, size_t main_audio_mixer,
std::vector<OBSEncoderAutoRelease> &video_encoders,
const char *audio_encoder_id,
std::optional<size_t> vod_track_mixer);
static void SetupSignalHandlers(bool recording, MultitrackVideoOutput *self,
obs_output_t *output, OBSSignal &start,
@ -350,7 +350,7 @@ void MultitrackVideoOutput::PrepareStreaming(
std::optional<uint32_t> maximum_aggregate_bitrate,
std::optional<uint32_t> maximum_video_tracks,
std::optional<std::string> custom_config,
obs_data_t *dump_stream_to_file_config, size_t main_audio_mixer,
obs_data_t *dump_stream_to_file_config,
std::optional<size_t> vod_track_mixer)
{
{
@ -465,13 +465,11 @@ void MultitrackVideoOutput::PrepareStreaming(
const auto &output_config = custom ? *custom : *go_live_config;
const auto &service_config = go_live_config ? *go_live_config : *custom;
std::vector<OBSEncoderAutoRelease> audio_encoders;
std::shared_ptr<obs_encoder_group_t> video_encoder_group;
auto outputs = SetupOBSOutput(parent, multitrack_video_name,
dump_stream_to_file_config, output_config,
audio_encoders, video_encoder_group,
audio_encoder_id, main_audio_mixer,
vod_track_mixer);
auto audio_encoders = std::vector<OBSEncoderAutoRelease>();
auto video_encoders = std::vector<OBSEncoderAutoRelease>();
auto outputs = SetupOBSOutput(dump_stream_to_file_config, output_config,
audio_encoders, video_encoders,
audio_encoder_id, vod_track_mixer);
auto output = std::move(outputs.output);
auto recording_output = std::move(outputs.recording_output);
if (!output)
@ -502,6 +500,13 @@ void MultitrackVideoOutput::PrepareStreaming(
start_recording, stop_recording,
deactivate_recording);
decltype(video_encoders) recording_video_encoders;
recording_video_encoders.reserve(video_encoders.size());
for (auto &encoder : video_encoders) {
recording_video_encoders.emplace_back(
obs_encoder_get_ref(encoder));
}
decltype(audio_encoders) recording_audio_encoders;
recording_audio_encoders.reserve(audio_encoders.size());
for (auto &encoder : audio_encoders) {
@ -514,7 +519,7 @@ void MultitrackVideoOutput::PrepareStreaming(
current_stream_dump_mutex};
current_stream_dump.emplace(OBSOutputObjects{
std::move(recording_output),
video_encoder_group,
std::move(recording_video_encoders),
std::move(recording_audio_encoders),
nullptr,
std::move(start_recording),
@ -527,7 +532,7 @@ void MultitrackVideoOutput::PrepareStreaming(
const std::lock_guard current_lock{current_mutex};
current.emplace(OBSOutputObjects{
std::move(output),
video_encoder_group,
std::move(video_encoders),
std::move(audio_encoders),
std::move(multitrack_video_service),
std::move(start_streaming),
@ -683,18 +688,16 @@ bool MultitrackVideoOutput::HandleIncompatibleSettings(
return true;
}
MultitrackVideoOutput::ReleaseOnMainThread(take_current());
MultitrackVideoOutput::ReleaseOnMainThread(take_current_stream_dump());
return false;
}
static bool
create_video_encoders(const GoLiveApi::Config &go_live_config,
std::shared_ptr<obs_encoder_group_t> &video_encoder_group,
std::vector<OBSEncoderAutoRelease> &video_encoders,
obs_output_t *output, obs_output_t *recording_output)
{
DStr video_encoder_name_buffer;
obs_encoder_t *first_encoder = nullptr;
if (go_live_config.encoder_configurations.empty()) {
blog(LOG_WARNING,
"MultitrackVideoOutput: Missing video encoder configurations");
@ -702,11 +705,6 @@ create_video_encoders(const GoLiveApi::Config &go_live_config,
QTStr("FailedToStartStream.MissingEncoderConfigs"));
}
std::shared_ptr<obs_encoder_group_t> encoder_group(
obs_encoder_group_create(), obs_encoder_group_destroy);
if (!encoder_group)
return false;
for (size_t i = 0; i < go_live_config.encoder_configurations.size();
i++) {
auto encoder = create_video_encoder(
@ -715,16 +713,19 @@ create_video_encoders(const GoLiveApi::Config &go_live_config,
if (!encoder)
return false;
if (!obs_encoder_set_group(encoder, encoder_group.get()))
return false;
if (!first_encoder)
first_encoder = encoder;
else
obs_encoder_group_keyframe_aligned_encoders(
first_encoder, encoder);
obs_output_set_video_encoder2(output, encoder, i);
if (recording_output)
obs_output_set_video_encoder2(recording_output, encoder,
i);
video_encoders.emplace_back(std::move(encoder));
}
video_encoder_group = encoder_group;
return true;
}
@ -732,47 +733,9 @@ static void
create_audio_encoders(const GoLiveApi::Config &go_live_config,
std::vector<OBSEncoderAutoRelease> &audio_encoders,
obs_output_t *output, obs_output_t *recording_output,
const char *audio_encoder_id, size_t main_audio_mixer,
std::optional<size_t> vod_track_mixer,
std::vector<speaker_layout> &speaker_layouts,
speaker_layout &current_layout)
const char *audio_encoder_id,
std::optional<size_t> vod_track_mixer)
{
speaker_layout speakers = SPEAKERS_UNKNOWN;
obs_audio_info oai = {};
if (obs_get_audio_info(&oai))
speakers = oai.speakers;
current_layout = speakers;
auto sanitize_audio_channels = [&](obs_encoder_t *encoder,
uint32_t channels) {
speaker_layout target_speakers = SPEAKERS_UNKNOWN;
for (size_t i = 0; i <= (size_t)SPEAKERS_7POINT1; i++) {
if (get_audio_channels((speaker_layout)i) != channels)
continue;
target_speakers = (speaker_layout)i;
break;
}
if (target_speakers == SPEAKERS_UNKNOWN) {
blog(LOG_WARNING,
"MultitrackVideoOutput: Could not find "
"speaker layout for %" PRIu32 "channels "
"while configuring encoder '%s'",
channels, obs_encoder_get_name(encoder));
return;
}
if (speakers != SPEAKERS_UNKNOWN &&
(channels > get_audio_channels(speakers) ||
speakers == target_speakers))
return;
auto it = std::find(std::begin(speaker_layouts),
std::end(speaker_layouts), target_speakers);
if (it == std::end(speaker_layouts))
speaker_layouts.push_back(target_speakers);
};
using encoder_configs_type =
decltype(go_live_config.audio_configurations.live);
DStr encoder_name_buffer;
@ -792,16 +755,11 @@ create_audio_encoders(const GoLiveApi::Config &go_live_config,
for (size_t i = 0; i < configs.size(); i++) {
dstr_printf(encoder_name_buffer, "%s %zu", name_prefix,
i);
OBSDataAutoRelease settings = obs_data_create_from_json(
configs[i].settings.dump().c_str());
OBSEncoderAutoRelease audio_encoder =
create_audio_encoder(encoder_name_buffer->array,
audio_encoder_id, settings,
audio_encoder_id,
configs[i].config.bitrate,
mixer_idx);
sanitize_audio_channels(audio_encoder,
configs[i].channels);
obs_output_set_audio_encoder(output, audio_encoder,
output_encoder_index);
if (recording_output)
@ -814,99 +772,24 @@ create_audio_encoders(const GoLiveApi::Config &go_live_config,
};
create_encoders("multitrack video live audio",
go_live_config.audio_configurations.live,
main_audio_mixer);
go_live_config.audio_configurations.live, 0);
if (!vod_track_mixer.has_value())
return;
// we already check for empty inside of `create_encoders`
encoder_configs_type empty = {};
create_encoders("multitrack video vod audio",
go_live_config.audio_configurations.vod.value_or(empty),
go_live_config.audio_configurations.vod,
*vod_track_mixer);
return;
}
static const char *speaker_layout_to_string(speaker_layout layout)
{
switch (layout) {
case SPEAKERS_MONO:
return "Mono";
case SPEAKERS_2POINT1:
return "2.1";
case SPEAKERS_4POINT0:
return "4.0";
case SPEAKERS_4POINT1:
return "4.1";
case SPEAKERS_5POINT1:
return "5.1";
case SPEAKERS_7POINT1:
return "7.1";
case SPEAKERS_UNKNOWN:
case SPEAKERS_STEREO:
return "Stereo";
}
return "Stereo";
}
static void handle_speaker_layout_issues(
QWidget *parent, const QString &multitrack_video_name,
const std::vector<speaker_layout> &requested_layouts,
speaker_layout layout)
{
if (requested_layouts.empty())
return;
QString message;
if (requested_layouts.size() == 1) {
message =
QTStr("MultitrackVideo.IncompatibleSettings.AudioChannelsSingle")
.arg(QTStr(speaker_layout_to_string(
requested_layouts.front())));
} else {
message =
QTStr("MultitrackVideo.IncompatibleSettings.AudioChannelsMultiple")
.arg(multitrack_video_name);
}
QMetaObject::invokeMethod(
parent,
[&] {
QMessageBox mb(parent);
mb.setIcon(QMessageBox::Critical);
mb.setWindowTitle(QTStr(
"MultitrackVideo.IncompatibleSettings.Title"));
mb.setText(
QTStr("MultitrackVideo.IncompatibleSettings.AudioChannels")
.arg(multitrack_video_name)
.arg(QTStr(speaker_layout_to_string(
layout)))
.arg(message));
mb.setStandardButtons(
QMessageBox::StandardButton::Cancel);
mb.exec();
},
BlockingConnectionTypeFor(parent));
blog(LOG_INFO,
"MultitrackVideoOutput: Attempted to start stream with incompatible "
"audio channel setting. Action taken: cancel");
throw MultitrackVideoError::cancel();
}
static OBSOutputs
SetupOBSOutput(QWidget *parent, const QString &multitrack_video_name,
obs_data_t *dump_stream_to_file_config,
SetupOBSOutput(obs_data_t *dump_stream_to_file_config,
const GoLiveApi::Config &go_live_config,
std::vector<OBSEncoderAutoRelease> &audio_encoders,
std::shared_ptr<obs_encoder_group_t> &video_encoder_group,
const char *audio_encoder_id, size_t main_audio_mixer,
std::vector<OBSEncoderAutoRelease> &video_encoders,
const char *audio_encoder_id,
std::optional<size_t> vod_track_mixer)
{
@ -916,19 +799,13 @@ SetupOBSOutput(QWidget *parent, const QString &multitrack_video_name,
recording_output =
create_recording_output(dump_stream_to_file_config);
if (!create_video_encoders(go_live_config, video_encoder_group, output,
if (!create_video_encoders(go_live_config, video_encoders, output,
recording_output))
return {nullptr, nullptr};
std::vector<speaker_layout> requested_speaker_layouts;
speaker_layout current_layout = SPEAKERS_UNKNOWN;
create_audio_encoders(go_live_config, audio_encoders, output,
recording_output, audio_encoder_id,
main_audio_mixer, vod_track_mixer,
requested_speaker_layouts, current_layout);
handle_speaker_layout_issues(parent, multitrack_video_name,
requested_speaker_layouts, current_layout);
vod_track_mixer);
return {std::move(output), std::move(recording_output)};
}

View file

@ -35,7 +35,6 @@ public:
std::optional<uint32_t> maximum_video_tracks,
std::optional<std::string> custom_config,
obs_data_t *dump_stream_to_file_config,
size_t main_audio_mixer,
std::optional<size_t> vod_track_mixer);
signal_handler_t *StreamingSignalHandler();
void StartedStreaming();
@ -54,7 +53,7 @@ public:
private:
struct OBSOutputObjects {
OBSOutputAutoRelease output_;
std::shared_ptr<obs_encoder_group_t> video_encoder_group_;
std::vector<OBSEncoderAutoRelease> video_encoders_;
std::vector<OBSEncoderAutoRelease> audio_encoders_;
OBSServiceAutoRelease multitrack_video_service_;
OBSSignal start_signal, stop_signal, deactivate_signal;

View file

@ -417,7 +417,7 @@ static vector<OBSThemeVariable> ParseThemeVariables(const char *themeData)
void OBSApp::FindThemes()
{
string themeDir;
unique_ptr<OBSTheme> theme;
themeDir.resize(512);
QStringList filters;
filters << "*.obt" // OBS Base Theme
@ -425,27 +425,30 @@ void OBSApp::FindThemes()
<< "*.oha" // OBS High-contrast Adjustment layer
;
GetDataFilePath("themes/", themeDir);
QDirIterator it(QString::fromStdString(themeDir), filters, QDir::Files);
while (it.hasNext()) {
theme.reset(ParseThemeMeta(it.next()));
if (theme && !themes.contains(theme->id))
themes[theme->id] = std::move(*theme);
}
themeDir.resize(1024);
if (GetConfigPath(themeDir.data(), themeDir.capacity(),
"obs-studio/themes/") > 0) {
QDirIterator it(QT_UTF8(themeDir.c_str()), filters,
QDir::Files);
while (it.hasNext()) {
theme.reset(ParseThemeMeta(it.next()));
OBSTheme *theme = ParseThemeMeta(it.next());
if (theme && !themes.contains(theme->id))
themes[theme->id] = std::move(*theme);
else
delete theme;
}
}
GetDataFilePath("themes/", themeDir);
QDirIterator it(QString::fromStdString(themeDir), filters, QDir::Files);
while (it.hasNext()) {
OBSTheme *theme = ParseThemeMeta(it.next());
if (theme && !themes.contains(theme->id))
themes[theme->id] = std::move(*theme);
else
delete theme;
}
/* Build dependency tree for all themes, removing ones that have items missing. */
QSet<QString> invalid;
@ -482,16 +485,6 @@ void OBSApp::FindThemes()
break;
}
if (parent->id == theme.id ||
theme.dependencies.contains(parent->id)) {
blog(LOG_ERROR,
R"(Dependency chain of "%s" ("%s") contains recursion!)",
QT_TO_UTF8(theme.id),
QT_TO_UTF8(parent->id));
invalid.insert(theme.id);
break;
}
/* Mark this theme as a variant of first parent that is a base theme. */
if (!theme.isBaseTheme && parent->isBaseTheme &&
theme.parent.isEmpty())

View file

@ -58,7 +58,6 @@
#ifdef _WIN32
#include <windows.h>
#include <filesystem>
#include <util/windows/win-version.h>
#else
#include <signal.h>
#include <pthread.h>
@ -352,6 +351,7 @@ static inline bool too_many_repeated_entries(fstream &logFile, const char *msg,
static mutex log_mutex;
static const char *last_msg_ptr = nullptr;
static int last_char_sum = 0;
static char cmp_str[8192];
static int rep_count = 0;
int new_sum = sum_chars(output_str);
@ -377,6 +377,8 @@ static inline bool too_many_repeated_entries(fstream &logFile, const char *msg,
}
last_msg_ptr = msg;
strncpy(cmp_str, output_str, sizeof(cmp_str));
cmp_str[sizeof(cmp_str) - 1] = 0;
last_char_sum = new_sum;
rep_count = 0;
@ -2903,35 +2905,6 @@ void OBSApp::commitData(QSessionManager &manager)
}
#endif
#ifdef _WIN32
static constexpr char vcRunErrorTitle[] = "Outdated Visual C++ Runtime";
static constexpr char vcRunErrorMsg[] =
"OBS Studio requires a newer version of the Microsoft Visual C++ "
"Redistributables.\n\nYou will now be directed to the download page.";
static constexpr char vcRunInstallerUrl[] =
"https://obsproject.com/visual-studio-2022-runtimes";
static bool vc_runtime_outdated()
{
win_version_info ver;
if (!get_dll_ver(L"msvcp140.dll", &ver))
return true;
/* Major is always 14 (hence 140.dll), so we only care about minor. */
if (ver.minor >= 40)
return false;
int choice = MessageBoxA(NULL, vcRunErrorMsg, vcRunErrorTitle,
MB_OKCANCEL | MB_ICONERROR | MB_TASKMODAL);
if (choice == IDOK) {
/* Open the URL in the default browser. */
ShellExecuteA(NULL, "open", vcRunInstallerUrl, NULL, NULL,
SW_SHOWNORMAL);
}
return true;
}
#endif
int main(int argc, char *argv[])
{
#ifndef _WIN32
@ -2958,9 +2931,6 @@ int main(int argc, char *argv[])
#endif
#ifdef _WIN32
// Abort as early as possible if MSVC runtime is outdated
if (vc_runtime_outdated())
return 1;
// Try to keep this as early as possible
install_dll_blocklist_hook();

View file

@ -251,10 +251,6 @@ VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical)
volLabel->setObjectName("volLabel");
volLabel->setAlignment(Qt::AlignCenter);
#ifdef __APPLE__
mute->setAttribute(Qt::WA_LayoutUsesWidgetRect);
#endif
QString sourceName = obs_source_get_name(source);
setObjectName(sourceName);
@ -263,8 +259,6 @@ VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical)
config->setProperty("themeID", "menuIconSmall");
config->setAutoDefault(false);
config->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
config->setAccessibleName(
QTStr("VolControl.Properties").arg(sourceName));
@ -302,16 +296,24 @@ VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical)
controlLayout->setContentsMargins(0, 0, 0, 0);
controlLayout->setSpacing(0);
controlLayout->setAlignment(mute, Qt::AlignVCenter);
// Add Headphone (audio monitoring) widget here
controlLayout->addWidget(mute);
controlLayout->addItem(
new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding,
QSizePolicy::Minimum));
if (showConfig) {
controlLayout->addWidget(config);
controlLayout->setAlignment(config, Qt::AlignVCenter);
}
meterLayout->setContentsMargins(0, 0, 0, 0);
meterLayout->setSpacing(0);
meterLayout->addWidget(slider);
meterLayout->addItem(
new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding,
QSizePolicy::Minimum));
meterLayout->addWidget(volMeter);
meterFrame->setLayout(meterLayout);
@ -338,10 +340,9 @@ VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical)
setMaximumWidth(110);
} else {
QHBoxLayout *textLayout = new QHBoxLayout;
QHBoxLayout *controlLayout = new QHBoxLayout;
QFrame *meterFrame = new QFrame;
QVBoxLayout *meterLayout = new QVBoxLayout;
QVBoxLayout *buttonLayout = new QVBoxLayout;
QHBoxLayout *meterLayout = new QHBoxLayout;
QHBoxLayout *botLayout = new QHBoxLayout;
volMeter = new VolumeMeter(nullptr, obs_volmeter, false);
volMeter->setSizePolicy(QSizePolicy::MinimumExpanding,
@ -363,25 +364,23 @@ VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical)
meterLayout->setContentsMargins(0, 0, 0, 0);
meterLayout->setSpacing(0);
meterLayout->addWidget(volMeter);
meterLayout->addWidget(slider);
buttonLayout->setContentsMargins(0, 0, 0, 0);
buttonLayout->setSpacing(0);
if (showConfig) {
buttonLayout->addWidget(config);
meterLayout->addWidget(config);
meterLayout->setAlignment(config, Qt::AlignVCenter);
}
buttonLayout->addItem(
new QSpacerItem(0, 0, QSizePolicy::Minimum,
QSizePolicy::MinimumExpanding));
buttonLayout->addWidget(mute);
meterLayout->addWidget(volMeter);
controlLayout->addItem(buttonLayout);
controlLayout->addWidget(meterFrame);
botLayout->setContentsMargins(0, 0, 0, 0);
botLayout->setSpacing(0);
botLayout->addWidget(mute);
botLayout->addWidget(slider);
botLayout->setAlignment(slider, Qt::AlignVCenter);
botLayout->setAlignment(mute, Qt::AlignVCenter);
mainLayout->addItem(textLayout);
mainLayout->addItem(controlLayout);
mainLayout->addWidget(meterFrame);
mainLayout->addItem(botLayout);
volMeter->setFocusProxy(slider);
}

View file

@ -26,22 +26,10 @@ target_compile_definitions(updater PRIVATE NOMINMAX "PSAPI_VERSION=2")
target_include_directories(updater PRIVATE "${CMAKE_SOURCE_DIR}/libobs" "${CMAKE_SOURCE_DIR}/UI/win-update")
target_link_libraries(
updater
PRIVATE OBS::blake2_static
nlohmann_json::nlohmann_json
zstd::libzstd_static
comctl32
shell32
version
winhttp
wintrust)
target_link_libraries(updater PRIVATE OBS::blake2 nlohmann_json::nlohmann_json zstd::libzstd_static comctl32 shell32
winhttp)
# zstd is hardcoded with /DEFAULTLIB:LIBCMT
target_link_options(updater PRIVATE $<$<CONFIG:DEBUG>:/NODEFAULTLIB:LIBCMT>)
target_link_options(updater PRIVATE /NODEFAULTLIB:LIBCMT)
set_target_properties(
updater
PROPERTIES FOLDER frontend
OUTPUT_NAME updater
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
set_target_properties(updater PROPERTIES FOLDER frontend OUTPUT_NAME updater)

View file

@ -36,15 +36,7 @@ if(MSVC)
target_link_options(updater PRIVATE "LINKER:/IGNORE:4098")
endif()
target_link_libraries(
updater
PRIVATE OBS::blake2
nlohmann_json::nlohmann_json
zstd::libzstd_static
comctl32
shell32
version
winhttp
wintrust)
target_link_libraries(updater PRIVATE OBS::blake2 nlohmann_json::nlohmann_json zstd::libzstd_static comctl32 shell32
winhttp)
set_target_properties(updater PROPERTIES FOLDER "frontend")

View file

@ -18,8 +18,6 @@
#include "manifest.hpp"
#include <psapi.h>
#include <WinTrust.h>
#include <SoftPub.h>
#include <util/windows/CoTaskMemPtr.hpp>
@ -41,9 +39,6 @@ constexpr const wchar_t *kCDNUpdateBaseUrl =
L"https://cdn-fastly.obsproject.com/update_studio";
constexpr const wchar_t *kPatchManifestURL =
L"https://obsproject.com/update_studio/getpatchmanifest";
constexpr const wchar_t *kVSRedistURL =
L"https://aka.ms/vs/17/release/vc_redist.x64.exe";
constexpr const wchar_t *kMSHostname = L"aka.ms";
/* ----------------------------------------------------------------------- */
@ -77,28 +72,44 @@ void FreeWinHttpHandle(HINTERNET handle)
/* ----------------------------------------------------------------------- */
static bool IsVSRedistOutdated()
static inline bool HasVS2019Redist2()
{
VS_FIXEDFILEINFO *info = nullptr;
UINT len = 0;
vector<std::byte> buf;
wchar_t base[MAX_PATH];
wchar_t path[MAX_PATH];
WIN32_FIND_DATAW wfd;
HANDLE handle;
const wchar_t vc_dll[] = L"msvcp140";
SHGetFolderPathW(NULL, CSIDL_SYSTEM, NULL, SHGFP_TYPE_CURRENT, base);
auto size = GetFileVersionInfoSize(vc_dll, nullptr);
if (!size)
return true;
#define check_dll_installed(dll) \
do { \
StringCbCopyW(path, sizeof(path), base); \
StringCbCatW(path, sizeof(path), L"\\" dll ".dll"); \
handle = FindFirstFileW(path, &wfd); \
if (handle == INVALID_HANDLE_VALUE) { \
return false; \
} else { \
FindClose(handle); \
} \
} while (false)
buf.resize(size);
if (!GetFileVersionInfo(vc_dll, 0, size, buf.data()))
return true;
check_dll_installed(L"msvcp140");
check_dll_installed(L"vcruntime140");
check_dll_installed(L"vcruntime140_1");
bool success = VerQueryValue(buf.data(), L"\\",
reinterpret_cast<LPVOID *>(&info), &len);
if (!success || !info || !len)
return true;
#undef check_dll_installed
return LOWORD(info->dwFileVersionMS) < 40;
return true;
}
static bool HasVS2019Redist()
{
PVOID old = nullptr;
bool redirect = !!Wow64DisableWow64FsRedirection(&old);
bool success = HasVS2019Redist2();
if (redirect)
Wow64RevertWow64FsRedirection(old);
return success;
}
static void Status(const wchar_t *fmt, ...)
@ -1128,7 +1139,7 @@ try {
return false;
}
static bool UpdateVSRedists()
static bool UpdateVS2019Redists(const string &vc_redist_hash)
{
/* ------------------------------------------ *
* Initialize session */
@ -1142,7 +1153,7 @@ static bool UpdateVSRedists()
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS, 0);
if (!hSession) {
Status(L"VC Redist Update failed: Couldn't create session");
Status(L"Update failed: Couldn't open obsproject.com");
return false;
}
@ -1152,10 +1163,10 @@ static bool UpdateVSRedists()
WinHttpSetOption(hSession, WINHTTP_OPTION_DECOMPRESSION,
(LPVOID)&compressionFlags, sizeof(compressionFlags));
HttpHandle hConnect = WinHttpConnect(hSession, kMSHostname,
HttpHandle hConnect = WinHttpConnect(hSession, kCDNHostname,
INTERNET_DEFAULT_HTTPS_PORT, 0);
if (!hConnect) {
Status(L"Update failed: Couldn't connect to %S", kMSHostname);
Status(L"Update failed: Couldn't connect to %S", kCDNHostname);
return false;
}
@ -1169,50 +1180,54 @@ static bool UpdateVSRedists()
/* ------------------------------------------ *
* Download redist */
Status(L"Downloading Visual C++ Redistributable");
Status(L"Downloading Visual C++ 2019 Redistributable");
wstring sourceURL =
L"https://cdn-fastly.obsproject.com/downloads/VC_redist.x64.exe";
wstring destPath;
destPath += tempPath;
destPath += L"\\VC_redist.x64.exe";
if (!HTTPGetFile(hConnect, kVSRedistURL, destPath.c_str(),
if (!HTTPGetFile(hConnect, sourceURL.c_str(), destPath.c_str(),
L"Accept-Encoding: gzip", &responseCode)) {
DeleteFile(destPath.c_str());
Status(L"Update failed: Could not download "
L"%s (error code %d)",
L"Visual C++ Redistributable", responseCode);
L"Visual C++ 2019 Redistributable", responseCode);
return false;
}
/* ------------------------------------------ *
* Verify file signature */
* Get expected hash */
GUID action = WINTRUST_ACTION_GENERIC_VERIFY_V2;
B2Hash expectedHash;
StringToHash(vc_redist_hash, expectedHash);
WINTRUST_FILE_INFO fileInfo = {};
fileInfo.cbStruct = sizeof(fileInfo);
fileInfo.pcwszFilePath = destPath.c_str();
/* ------------------------------------------ *
* Get download hash */
WINTRUST_DATA data = {};
data.cbStruct = sizeof(data);
data.dwUIChoice = WTD_UI_NONE;
data.dwUnionChoice = WTD_CHOICE_FILE;
data.dwStateAction = WTD_STATEACTION_VERIFY;
data.pFile = &fileInfo;
LONG result = WinVerifyTrust(nullptr, &action, &data);
if (result != ERROR_SUCCESS) {
Status(L"Update failed: Signature verification failed for "
L"%s (error code %d / %d)",
L"Visual C++ Redistributable", result, GetLastError());
B2Hash downloadHash;
if (!CalculateFileHash(destPath.c_str(), downloadHash)) {
DeleteFile(destPath.c_str());
Status(L"Update failed: Couldn't verify integrity of %s",
L"Visual C++ 2019 Redistributable");
return false;
}
/* ------------------------------------------ *
* If verification succeeded, install redist */
* If hashes do not match, integrity failed */
if (downloadHash == expectedHash) {
DeleteFile(destPath.c_str());
Status(L"Update failed: Couldn't verify integrity of %s",
L"Visual C++ 2019 Redistributable");
return false;
}
/* ------------------------------------------ *
* If hashes match, install redist */
wchar_t commandline[MAX_PATH + MAX_PATH];
StringCbPrintf(commandline, sizeof(commandline),
@ -1226,7 +1241,7 @@ static bool UpdateVSRedists()
nullptr, false, CREATE_NO_WINDOW,
nullptr, nullptr, &si, &pi);
if (success) {
Status(L"Installing %s...", L"Visual C++ Redistributable");
Status(L"Installing %s...", L"Visual C++ 2019 Redistributable");
CloseHandle(pi.hThread);
WaitForSingleObject(pi.hProcess, INFINITE);
@ -1234,7 +1249,7 @@ static bool UpdateVSRedists()
} else {
Status(L"Update failed: Could not execute "
L"%s (error code %d)",
L"Visual C++ Redistributable", (int)GetLastError());
L"Visual C++ 2019 Redistributable", (int)GetLastError());
}
DeleteFile(destPath.c_str());
@ -1499,10 +1514,10 @@ static bool Update(wchar_t *cmdLine)
}
/* ------------------------------------- *
* Check VS redistributables version */
* Check for VS2019 redistributables */
if (IsVSRedistOutdated()) {
if (!UpdateVSRedists()) {
if (!HasVS2019Redist()) {
if (!UpdateVS2019Redists(manifest.vc2019_redist_x64)) {
return false;
}
}

View file

@ -456,17 +456,8 @@ bool AutoConfigStreamPage::validatePage()
int multitrackVideoBitrate = 0;
for (auto &encoder_config :
config.encoder_configurations) {
auto it =
encoder_config.settings.find("bitrate");
if (it == encoder_config.settings.end())
continue;
if (!it->is_number_integer())
continue;
int bitrate = 0;
it->get_to(bitrate);
multitrackVideoBitrate += bitrate;
multitrackVideoBitrate +=
encoder_config.config.bitrate;
}
// grab a streamkey from the go live config if we can

View file

@ -1138,7 +1138,7 @@ FutureHolder<bool> SimpleOutput::SetupStreaming(obs_service_t *service)
: std::nullopt;
auto holder = SetupMultitrackVideo(
service, GetSimpleAACEncoderForBitrate(audio_bitrate), 0,
service, GetSimpleAACEncoderForBitrate(audio_bitrate),
vod_track_mixer);
auto future =
PreventFutureDeadlock(holder.future)
@ -2286,13 +2286,9 @@ FutureHolder<bool> AdvancedOutput::SetupStreaming(obs_service_t *service)
const char *audio_encoder_id =
config_get_string(main->Config(), "AdvOut", "AudioEncoder");
int streamTrackIndex =
config_get_int(main->Config(), "AdvOut", "TrackIndex") - 1;
auto holder =
SetupMultitrackVideo(service, audio_encoder_id,
static_cast<size_t>(streamTrackIndex),
VodTrackMixerIdx(service));
auto holder = SetupMultitrackVideo(service, audio_encoder_id,
VodTrackMixerIdx(service));
auto future =
PreventFutureDeadlock(holder.future)
.then(main, [&](std::optional<bool>
@ -2705,9 +2701,10 @@ std::string BasicOutputHandler::GetRecordingFilename(
extern std::string DeserializeConfigText(const char *text);
FutureHolder<std::optional<bool>> BasicOutputHandler::SetupMultitrackVideo(
obs_service_t *service, std::string audio_encoder_id,
size_t main_audio_mixer, std::optional<size_t> vod_track_mixer)
FutureHolder<std::optional<bool>>
BasicOutputHandler::SetupMultitrackVideo(obs_service_t *service,
std::string audio_encoder_id,
std::optional<size_t> vod_track_mixer)
{
if (!multitrackVideo)
return {[] {}, CreateFuture().then([] {
@ -2787,8 +2784,7 @@ FutureHolder<std::optional<bool>> BasicOutputHandler::SetupMultitrackVideo(
audio_encoder_id.c_str(),
maximum_aggregate_bitrate,
maximum_video_tracks, custom_config,
stream_dump_config, main_audio_mixer,
vod_track_mixer);
stream_dump_config, vod_track_mixer);
} catch (const MultitrackVideoError &error) {
return error;
}

View file

@ -98,9 +98,10 @@ protected:
bool overwrite, const char *format,
bool ffmpeg);
FutureHolder<std::optional<bool>> SetupMultitrackVideo(
obs_service_t *service, std::string audio_encoder_id,
size_t main_audio_mixer, std::optional<size_t> vod_track_mixer);
FutureHolder<std::optional<bool>>
SetupMultitrackVideo(obs_service_t *service,
std::string audio_encoder_id,
std::optional<size_t> vod_track_mixer);
OBSDataAutoRelease GenerateMultitrackVideoStreamDumpConfig();
};

View file

@ -445,8 +445,7 @@ void OBSBasic::SetTransition(OBSSource transition)
ui->transitionDurationLabel->setVisible(!fixed);
ui->transitionDuration->setVisible(!fixed);
bool configurable = transition ? obs_source_configurable(transition)
: false;
bool configurable = obs_source_configurable(transition);
ui->transitionRemove->setEnabled(configurable);
ui->transitionProps->setEnabled(configurable);

View file

@ -1549,7 +1549,7 @@ extern void CheckExistingCookieId();
#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0
#define DEFAULT_CONTAINER "mkv"
#else
#define DEFAULT_CONTAINER "hybrid_mp4"
#define DEFAULT_CONTAINER "fragmented_mp4"
#endif
bool OBSBasic::InitBasicConfigDefaults()
@ -2889,14 +2889,22 @@ void OBSBasic::CreateHotkeys()
this);
LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile");
addChapterHotkey = obs_hotkey_register_frontend(
"OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"),
[](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) {
if (pressed)
obs_frontend_recording_add_chapter(nullptr);
},
this);
LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker");
/* Adding chapters is only supported by the native MP4 output */
const string_view output_id =
obs_output_get_id(outputHandler->fileOutput);
if (output_id == "mp4_output") {
addChapterHotkey = obs_hotkey_register_frontend(
"OBSBasic.AddChapterMarker",
Str("Basic.Main.AddChapterMarker"),
[](void *, obs_hotkey_id, obs_hotkey_t *,
bool pressed) {
if (pressed)
obs_frontend_recording_add_chapter(
nullptr);
},
this);
LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker");
}
replayBufHotkeys = obs_hotkey_pair_register_frontend(
"OBSBasic.StartReplayBuffer",
@ -8407,8 +8415,13 @@ void OBSBasic::ProgramViewContextMenuRequested()
&OBSBasic::OpenStudioProgramProjector);
popup.addMenu(studioProgramProjector);
popup.addAction(QTStr("StudioProgramWindow"), this,
&OBSBasic::OpenStudioProgramWindow);
QAction *studioProgramWindow =
popup.addAction(QTStr("StudioProgramWindow"), this,
&OBSBasic::OpenStudioProgramWindow);
popup.addAction(studioProgramWindow);
popup.addAction(QTStr("Screenshot.StudioProgram"), this,
&OBSBasic::ScreenshotProgram);
@ -8626,7 +8639,7 @@ void OBSBasic::UpdateEditMenu()
const bool canTransformSingle = videoCount == 1 && totalCount == 1;
OBSSceneItem curItem = GetCurrentSceneItem();
bool locked = curItem && obs_sceneitem_locked(curItem);
bool locked = obs_sceneitem_locked(curItem);
ui->actionCopySource->setEnabled(totalCount > 0);
ui->actionEditTransform->setEnabled(canTransformSingle && !locked);

View file

@ -654,7 +654,7 @@ void OBSBasicPreview::mousePressEvent(QMouseEvent *event)
void OBSBasicPreview::UpdateCursor(uint32_t &flags)
{
if (!stretchItem || obs_sceneitem_locked(stretchItem)) {
if (obs_sceneitem_locked(stretchItem)) {
unsetCursor();
return;
}

View file

@ -1021,10 +1021,6 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
UpdateAudioWarnings();
UpdateAdvNetworkGroup();
ui->audioMsg->setVisible(false);
ui->advancedMsg->setVisible(false);
ui->advancedMsg2->setVisible(false);
}
OBSBasicSettings::~OBSBasicSettings()
@ -2840,30 +2836,24 @@ void OBSBasicSettings::UpdateColorFormatSpaceWarning()
if ((format == "P010") || (format == "P216") ||
(format == "P416")) {
ui->advancedMsg2->clear();
ui->advancedMsg2->setVisible(false);
} else if (format == "I010") {
ui->advancedMsg2->setText(
QTStr("Basic.Settings.Advanced.FormatWarning"));
ui->advancedMsg2->setVisible(true);
} else {
ui->advancedMsg2->setText(QTStr(
"Basic.Settings.Advanced.FormatWarning2100"));
ui->advancedMsg2->setVisible(true);
}
break;
default:
if (format == "NV12") {
ui->advancedMsg2->clear();
ui->advancedMsg2->setVisible(false);
} else if ((format == "I010") || (format == "P010") ||
(format == "P216") || (format == "P416")) {
ui->advancedMsg2->setText(QTStr(
"Basic.Settings.Advanced.FormatWarningPreciseSdr"));
ui->advancedMsg2->setVisible(true);
} else {
ui->advancedMsg2->setText(
QTStr("Basic.Settings.Advanced.FormatWarning"));
ui->advancedMsg2->setVisible(true);
}
}
}
@ -4690,8 +4680,6 @@ void OBSBasicSettings::AudioChanged()
void OBSBasicSettings::AudioChangedRestart()
{
ui->audioMsg->setVisible(false);
if (!loading) {
int currentChannelIndex = ui->channelSetup->currentIndex();
int currentSampleRateIndex = ui->sampleRate->currentIndex();
@ -4703,7 +4691,6 @@ void OBSBasicSettings::AudioChangedRestart()
currentLLAudioBufVal != llBufferingEnabled) {
ui->audioMsg->setText(
QTStr("Basic.Settings.ProgramRestart"));
ui->audioMsg->setVisible(true);
} else {
ui->audioMsg->setText("");
}
@ -4829,13 +4816,10 @@ void RestrictResetBitrates(initializer_list<QComboBox *> boxes, int maxbitrate)
void OBSBasicSettings::AdvancedChangedRestart()
{
ui->advancedMsg->setVisible(false);
if (!loading) {
advancedChanged = true;
ui->advancedMsg->setText(
QTStr("Basic.Settings.ProgramRestart"));
ui->advancedMsg->setVisible(true);
sender()->setProperty("changed", QVariant(true));
EnableApplyButton(true);
}
@ -6038,7 +6022,6 @@ void OBSBasicSettings::UpdateAudioWarnings()
}
ui->audioMsg_2->setText(text);
ui->audioMsg_2->setVisible(!text.isEmpty());
}
void OBSBasicSettings::LowLatencyBufferingChanged(bool checked)
@ -6325,13 +6308,6 @@ void OBSBasicSettings::UpdateMultitrackVideo()
ui->enableMultitrackVideo->setChecked(false);
}
#ifndef _WIN32
available = available && MultitrackVideoDeveloperModeEnabled();
#endif
if (IsCustomService())
available = available && MultitrackVideoDeveloperModeEnabled();
ui->multitrackVideoGroupBox->setVisible(available);
ui->enableMultitrackVideo->setEnabled(toggle_available);

View file

@ -277,7 +277,7 @@ endif()
if(EXISTS "${FFmpeg_avutil_INCLUDE_DIR}/libavutil/ffversion.h")
file(STRINGS "${FFmpeg_avutil_INCLUDE_DIR}/libavutil/ffversion.h" _version_string
REGEX "^.*FFMPEG_VERSION[ \t]+\"n?[0-9a-z\\~.-]+\"[ \t]*$")
REGEX "^.*FFMPEG_VERSION[ \t]+\"n?[0-9a-z\\.-]+\"[ \t]*$")
string(REGEX REPLACE ".*FFMPEG_VERSION[ \t]+\"n?([0-9]+\\.[0-9]).*\".*" "\\1" FFmpeg_VERSION "${_version_string}")
endif()

View file

@ -74,8 +74,6 @@ function(set_target_properties_obs target)
COMMAND "${CMAKE_COMMAND}" -E make_directory "${OBS_OUTPUT_DIR}/$<CONFIG>/${OBS_LIBRARY_DESTINATION}"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "$<TARGET_FILE:${target}>"
"${OBS_OUTPUT_DIR}/$<CONFIG>/${OBS_LIBRARY_DESTINATION}/"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "$<TARGET_SONAME_FILE:${target}>"
"${OBS_OUTPUT_DIR}/$<CONFIG>/${OBS_LIBRARY_DESTINATION}/"
COMMENT "Copy ${target} to library directory (${OBS_LIBRARY_DESTINATION})"
VERBATIM)

View file

@ -11,16 +11,3 @@ target_sources(
target_include_directories(blake2 PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
set_target_properties(blake2 PROPERTIES FOLDER deps)
if(OS_WINDOWS)
add_library(blake2_static OBJECT)
add_library(OBS::blake2_static ALIAS blake2_static)
target_sources(
blake2_static
PRIVATE src/blake2-impl.h src/blake2b-ref.c
PUBLIC src/blake2.h)
target_include_directories(blake2_static PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
set_target_properties(blake2_static PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()

View file

@ -49,7 +49,6 @@ target_sources(
obs-av1.h
obs-avc.c
obs-avc.h
obs-config.h
obs-data.c
obs-data.h
obs-defs.h

View file

@ -325,10 +325,11 @@ static void add_connection(struct obs_encoder *encoder)
}
if (encoder->encoder_group) {
bool ready = false;
pthread_mutex_lock(&encoder->encoder_group->mutex);
encoder->encoder_group->num_encoders_started += 1;
bool ready = encoder->encoder_group->num_encoders_started >=
encoder->encoder_group->encoders.num;
encoder->encoder_group->encoders_started += 1;
ready = encoder->encoder_group->encoders_started ==
encoder->encoder_group->encoders_added;
pthread_mutex_unlock(&encoder->encoder_group->mutex);
if (ready)
add_ready_encoder_group(encoder);
@ -337,7 +338,6 @@ static void add_connection(struct obs_encoder *encoder)
set_encoder_active(encoder, true);
}
void obs_encoder_group_actually_destroy(obs_encoder_group_t *group);
static void remove_connection(struct obs_encoder *encoder, bool shutdown)
{
if (encoder->info.type == OBS_ENCODER_AUDIO) {
@ -353,8 +353,10 @@ static void remove_connection(struct obs_encoder *encoder, bool shutdown)
if (encoder->encoder_group) {
pthread_mutex_lock(&encoder->encoder_group->mutex);
if (--encoder->encoder_group->num_encoders_started == 0)
encoder->encoder_group->encoders_started -= 1;
if (encoder->encoder_group->encoders_started == 0)
encoder->encoder_group->start_timestamp = 0;
pthread_mutex_unlock(&encoder->encoder_group->mutex);
}
@ -365,8 +367,6 @@ static void remove_connection(struct obs_encoder *encoder, bool shutdown)
* up again */
if (shutdown)
obs_encoder_shutdown(encoder);
encoder->initialized = false;
set_encoder_active(encoder, false);
}
@ -395,7 +395,22 @@ static void obs_encoder_actually_destroy(obs_encoder_t *encoder)
blog(LOG_DEBUG, "encoder '%s' destroyed",
encoder->context.name);
obs_encoder_set_group(encoder, NULL);
if (encoder->encoder_group) {
struct encoder_group *group = encoder->encoder_group;
bool release = false;
encoder->encoder_group = NULL;
pthread_mutex_lock(&group->mutex);
group->encoders_added -= 1;
release = group->encoders_added == 0;
pthread_mutex_unlock(&group->mutex);
if (release) {
pthread_mutex_destroy(&group->mutex);
bfree(group);
}
}
free_audio_buffers(encoder);
@ -786,20 +801,14 @@ void obs_encoder_start(obs_encoder_t *encoder,
pthread_mutex_unlock(&encoder->init_mutex);
}
void obs_encoder_stop(obs_encoder_t *encoder,
void (*new_packet)(void *param,
struct encoder_packet *packet),
void *param)
static inline bool obs_encoder_stop_internal(
obs_encoder_t *encoder,
void (*new_packet)(void *param, struct encoder_packet *packet),
void *param)
{
bool last = false;
size_t idx;
if (!obs_encoder_valid(encoder, "obs_encoder_stop"))
return;
if (!obs_ptr_valid(new_packet, "obs_encoder_stop"))
return;
pthread_mutex_lock(&encoder->init_mutex);
pthread_mutex_lock(&encoder->callbacks_mutex);
idx = get_callback_idx(encoder, new_packet, param);
@ -812,32 +821,34 @@ void obs_encoder_stop(obs_encoder_t *encoder,
if (last) {
remove_connection(encoder, true);
pthread_mutex_unlock(&encoder->init_mutex);
encoder->initialized = false;
struct obs_encoder_group *group = encoder->encoder_group;
if (encoder->destroy_on_stop)
if (encoder->destroy_on_stop) {
pthread_mutex_unlock(&encoder->init_mutex);
obs_encoder_actually_destroy(encoder);
/* Destroying the group all the way back here prevents a race
* where destruction of the group can prematurely destroy the
* encoder within internal functions. This is the point where it
* is safe to destroy the group, even if the encoder is then
* also destroyed. */
if (group) {
pthread_mutex_lock(&group->mutex);
if (group->destroy_on_stop &&
group->num_encoders_started == 0)
obs_encoder_group_actually_destroy(group);
else
pthread_mutex_unlock(&group->mutex);
return true;
}
/* init_mutex already unlocked */
return;
}
pthread_mutex_unlock(&encoder->init_mutex);
return false;
}
void obs_encoder_stop(obs_encoder_t *encoder,
void (*new_packet)(void *param,
struct encoder_packet *packet),
void *param)
{
bool destroyed;
if (!obs_encoder_valid(encoder, "obs_encoder_stop"))
return;
if (!obs_ptr_valid(new_packet, "obs_encoder_stop"))
return;
pthread_mutex_lock(&encoder->init_mutex);
destroyed = obs_encoder_stop_internal(encoder, new_packet, param);
if (!destroyed)
pthread_mutex_unlock(&encoder->init_mutex);
}
const char *obs_encoder_get_codec(const obs_encoder_t *encoder)
@ -1334,6 +1345,7 @@ void full_stop(struct obs_encoder *encoder)
pthread_mutex_unlock(&encoder->callbacks_mutex);
remove_connection(encoder, false);
encoder->initialized = false;
}
}
@ -1449,7 +1461,7 @@ static void receive_video(void *param, struct video_data *frame)
struct encoder_frame enc_frame;
if (encoder->encoder_group && !encoder->start_ts) {
struct obs_encoder_group *group = encoder->encoder_group;
struct encoder_group *group = encoder->encoder_group;
bool ready = false;
pthread_mutex_lock(&group->mutex);
ready = group->start_timestamp == frame->timestamp;
@ -2046,99 +2058,132 @@ uint32_t obs_encoder_get_roi_increment(const obs_encoder_t *encoder)
return encoder->roi_increment;
}
bool obs_encoder_set_group(obs_encoder_t *encoder, obs_encoder_group_t *group)
bool obs_encoder_group_keyframe_aligned_encoders(
obs_encoder_t *encoder, obs_encoder_t *encoder_to_be_grouped)
{
if (!obs_encoder_valid(encoder, "obs_encoder_set_group"))
if (!obs_encoder_valid(encoder,
"obs_encoder_group_keyframe_aligned_encoders") ||
!obs_encoder_valid(encoder_to_be_grouped,
"obs_encoder_group_keyframe_aligned_encoders"))
return false;
if (obs_encoder_active(encoder)) {
if (obs_encoder_active(encoder) ||
obs_encoder_active(encoder_to_be_grouped)) {
obs_encoder_t *active = obs_encoder_active(encoder)
? encoder
: encoder_to_be_grouped;
obs_encoder_t *other = active == encoder ? encoder_to_be_grouped
: encoder;
blog(LOG_ERROR,
"obs_encoder_set_group: encoder '%s' is already active",
"obs_encoder_group_keyframe_aligned_encoders: encoder '%s' "
"is already active, could not group with '%s'",
obs_encoder_get_name(active), obs_encoder_get_name(other));
return false;
}
if (encoder_to_be_grouped->encoder_group) {
blog(LOG_ERROR,
"obs_encoder_group_keyframe_aligned_encoders: encoder '%s' "
"is already part of a keyframe aligned group while trying "
"to group with encoder '%s'",
obs_encoder_get_name(encoder_to_be_grouped),
obs_encoder_get_name(encoder));
return false;
}
if (encoder->encoder_group) {
struct obs_encoder_group *old_group = encoder->encoder_group;
pthread_mutex_lock(&old_group->mutex);
if (old_group->num_encoders_started) {
pthread_mutex_unlock(&old_group->mutex);
blog(LOG_ERROR,
"obs_encoder_set_group: encoder '%s' existing group has started encoders",
obs_encoder_get_name(encoder));
bool unlock = false;
if (!encoder->encoder_group) {
encoder->encoder_group = bzalloc(sizeof(struct encoder_group));
if (pthread_mutex_init(&encoder->encoder_group->mutex, NULL) <
0) {
bfree(encoder->encoder_group);
encoder->encoder_group = NULL;
return false;
}
encoder->encoder_group->encoders_added = 1;
} else {
pthread_mutex_lock(&encoder->encoder_group->mutex);
unlock = true;
if (encoder->encoder_group->encoders_started != 0) {
blog(LOG_ERROR,
"obs_encoder_group_keyframe_aligned_encoders: "
"Can't add encoder '%s' to active group "
"from encoder '%s'",
obs_encoder_get_name(encoder_to_be_grouped),
obs_encoder_get_name(encoder));
pthread_mutex_unlock(&encoder->encoder_group->mutex);
return false;
}
da_erase_item(old_group->encoders, &encoder);
obs_encoder_release(encoder);
pthread_mutex_unlock(&old_group->mutex);
}
if (!group)
return true;
encoder->encoder_group->encoders_added += 1;
encoder_to_be_grouped->encoder_group = encoder->encoder_group;
pthread_mutex_lock(&group->mutex);
if (group->num_encoders_started) {
pthread_mutex_unlock(&group->mutex);
blog(LOG_ERROR,
"obs_encoder_set_group: specified group has started encoders");
return false;
}
obs_encoder_t *ref = obs_encoder_get_ref(encoder);
if (!ref) {
pthread_mutex_unlock(&group->mutex);
return false;
}
da_push_back(group->encoders, &ref);
encoder->encoder_group = group;
pthread_mutex_unlock(&group->mutex);
if (unlock)
pthread_mutex_unlock(&encoder->encoder_group->mutex);
return true;
}
obs_encoder_group_t *obs_encoder_group_create()
bool obs_encoder_group_remove_keyframe_aligned_encoder(
obs_encoder_t *encoder, obs_encoder_t *encoder_to_be_ungrouped)
{
struct obs_encoder_group *group =
bzalloc(sizeof(struct obs_encoder_group));
if (!obs_encoder_valid(
encoder,
"obs_encoder_group_remove_keyframe_aligned_encoder") ||
!obs_encoder_valid(
encoder_to_be_ungrouped,
"obs_encoder_group_remove_keyframe_aligned_encoder"))
return false;
pthread_mutex_init_value(&group->mutex);
if (pthread_mutex_init(&group->mutex, NULL) != 0) {
bfree(group);
return NULL;
if (obs_encoder_active(encoder) ||
obs_encoder_active(encoder_to_be_ungrouped)) {
blog(LOG_ERROR,
"obs_encoder_group_remove_keyframe_aligned_encoder: encoders are active, "
"could not ungroup encoder '%s' from '%s'",
obs_encoder_get_name(encoder_to_be_ungrouped),
obs_encoder_get_name(encoder));
return false;
}
return group;
}
if (encoder->encoder_group != encoder_to_be_ungrouped->encoder_group) {
blog(LOG_ERROR,
"obs_encoder_group_remove_keyframe_aligned_encoder: "
"encoder '%s' does not belong to the same group as encoder '%s'",
obs_encoder_get_name(encoder_to_be_ungrouped),
obs_encoder_get_name(encoder));
return false;
}
void obs_encoder_group_actually_destroy(obs_encoder_group_t *group)
{
for (size_t i = 0; i < group->encoders.num; i++) {
struct obs_encoder *encoder = group->encoders.array[i];
struct encoder_group *current_group = encoder->encoder_group;
struct encoder_group *free_group = NULL;
pthread_mutex_lock(&current_group->mutex);
if (current_group->encoders_started != 0) {
blog(LOG_ERROR,
"obs_encoder_group_remove_keyframe_aligned_encoder: "
"could not ungroup encoder '%s' from '%s' while "
"the group contains active encoders",
obs_encoder_get_name(encoder_to_be_ungrouped),
obs_encoder_get_name(encoder));
pthread_mutex_unlock(&current_group->mutex);
return false;
}
current_group->encoders_added -= 1;
encoder_to_be_ungrouped->encoder_group = NULL;
if (current_group->encoders_added == 1) {
free_group = current_group;
encoder->encoder_group = NULL;
obs_encoder_release(encoder);
}
pthread_mutex_unlock(&current_group->mutex);
if (free_group) {
pthread_mutex_destroy(&free_group->mutex);
bfree(free_group);
}
da_free(group->encoders);
pthread_mutex_unlock(&group->mutex);
pthread_mutex_destroy(&group->mutex);
bfree(group);
}
void obs_encoder_group_destroy(obs_encoder_group_t *group)
{
if (!group)
return;
pthread_mutex_lock(&group->mutex);
if (group->num_encoders_started) {
group->destroy_on_stop = true;
pthread_mutex_unlock(&group->mutex);
return;
}
obs_encoder_group_actually_destroy(group);
return true;
}

View file

@ -1242,15 +1242,10 @@ struct encoder_callback {
void *param;
};
struct obs_encoder_group {
struct encoder_group {
pthread_mutex_t mutex;
/* allows group to be destroyed even if some encoders are active */
bool destroy_on_stop;
/* holds strong references to all encoders */
DARRAY(struct obs_encoder *) encoders;
uint32_t num_encoders_started;
uint32_t encoders_added;
uint32_t encoders_started;
uint64_t start_timestamp;
};
@ -1319,7 +1314,7 @@ struct obs_encoder {
uint64_t start_ts;
/* track encoders that are part of a gop-aligned multi track group */
struct obs_encoder_group *encoder_group;
struct encoder_group *encoder_group;
pthread_mutex_t outputs_mutex;
DARRAY(obs_output_t *) outputs;

View file

@ -2226,7 +2226,7 @@ check_encoder_group_keyframe_alignment(obs_output_t *output,
pthread_mutex_lock(&packet->encoder->encoder_group->mutex);
insert_data.required_tracks =
packet->encoder->encoder_group->num_encoders_started;
packet->encoder->encoder_group->encoders_started;
pthread_mutex_unlock(&packet->encoder->encoder_group->mutex);
da_insert(output->keyframe_group_tracking, idx, &insert_data);

View file

@ -92,7 +92,7 @@ static void *gpu_encode_thread(void *data)
pkt.encoder = encoder;
if (encoder->encoder_group && !encoder->start_ts) {
struct obs_encoder_group *group =
struct encoder_group *group =
encoder->encoder_group;
bool ready = false;
pthread_mutex_lock(&group->mutex);

View file

@ -932,13 +932,12 @@ static inline void video_sleep(struct obs_core_video *video, uint64_t *p_time,
continue;
if (encoder->encoder_group) {
struct obs_encoder_group *group =
encoder->encoder_group;
struct encoder_group *group = encoder->encoder_group;
pthread_mutex_lock(&group->mutex);
if (group->num_encoders_started >=
group->encoders.num &&
!group->start_timestamp)
if (group->encoders_added == group->encoders_started &&
!group->start_timestamp) {
group->start_timestamp = *p_time;
}
pthread_mutex_unlock(&group->mutex);
}
obs_encoder_release(encoder);

View file

@ -46,7 +46,6 @@ struct obs_scene;
struct obs_scene_item;
struct obs_output;
struct obs_encoder;
struct obs_encoder_group;
struct obs_service;
struct obs_module;
struct obs_fader;
@ -60,7 +59,6 @@ typedef struct obs_scene obs_scene_t;
typedef struct obs_scene_item obs_sceneitem_t;
typedef struct obs_output obs_output_t;
typedef struct obs_encoder obs_encoder_t;
typedef struct obs_encoder_group obs_encoder_group_t;
typedef struct obs_service obs_service_t;
typedef struct obs_module obs_module_t;
typedef struct obs_fader obs_fader_t;
@ -2609,16 +2607,10 @@ EXPORT void obs_encoder_set_last_error(obs_encoder_t *encoder,
EXPORT uint64_t obs_encoder_get_pause_offset(const obs_encoder_t *encoder);
/**
* Creates an "encoder group", allowing synchronized startup of encoders within
* the group. Encoder groups are single owner, and hold strong references to
* encoders within the group. Calling destroy on an active group will not actually
* destroy the group until it becomes completely inactive.
*/
EXPORT bool obs_encoder_set_group(obs_encoder_t *encoder,
obs_encoder_group_t *group);
EXPORT obs_encoder_group_t *obs_encoder_group_create();
EXPORT void obs_encoder_group_destroy(obs_encoder_group_t *group);
EXPORT bool obs_encoder_group_keyframe_aligned_encoders(
obs_encoder_t *encoder, obs_encoder_t *encoder_to_be_grouped);
EXPORT bool obs_encoder_group_remove_keyframe_aligned_encoder(
obs_encoder_t *encoder, obs_encoder_t *encoder_to_be_ungrouped);
/* ------------------------------------------------------------------------- */
/* Stream Services */

View file

@ -40,7 +40,6 @@ NVENC.8bitUnsupportedHdr="OBS does not support 8-bit output of Rec. 2100."
NVENC.I010Unsupported="NVENC does not support I010. Use P010 instead."
NVENC.10bitUnsupported="Cannot perform 10-bit encode on this encoder."
NVENC.16bitUnsupported="Cannot perform 16-bit encode on this encoder."
NVENC.444Unsupported="Cannot perform 4:4:4 encode on this encoder."
NVENC.NoAV1FallbackPossible="AV1 encoding is not available with the current settings. Try disabling any re-scaling or GPU options that may be set. Check the log for more details."
NVENC.Preset2.p1="P1: Fastest (Lowest Quality)"
NVENC.Preset2.p2="P2: Faster (Lower Quality)"

View file

@ -270,22 +270,14 @@ static inline bool cuda_error_check(struct nvenc_data *enc, CUresult res,
if (res == CUDA_SUCCESS)
return true;
struct dstr message = {0};
const char *name, *desc;
if (cuda_get_error_desc(res, &name, &desc)) {
dstr_printf(&message,
"%s: CUDA call \"%s\" failed with %s (%d): %s",
func, call, name, res, desc);
error("%s: CUDA call \"%s\" failed with %s (%d): %s", func,
call, name, res, desc);
} else {
dstr_printf(&message, "%s: CUDA call \"%s\" failed with %d",
func, call, res);
error("%s: CUDA call \"%s\" failed with %d", func, call, res);
}
error("%s", message.array);
obs_encoder_set_last_error(enc->encoder, message.array);
dstr_free(&message);
return false;
}
@ -1371,19 +1363,12 @@ static bool init_encoder(struct nvenc_data *enc, enum codec_type codec,
int bf = (int)obs_data_get_int(settings, "bf");
const bool support_10bit =
nv_get_cap(enc, NV_ENC_CAPS_SUPPORT_10BIT_ENCODE);
const bool support_444 =
nv_get_cap(enc, NV_ENC_CAPS_SUPPORT_YUV444_ENCODE);
const int bf_max = nv_get_cap(enc, NV_ENC_CAPS_NUM_MAX_BFRAMES);
video_t *video = obs_encoder_video(enc->encoder);
const struct video_output_info *voi = video_output_get_info(video);
enc->in_format = get_preferred_format(voi->format);
if (enc->in_format == VIDEO_FORMAT_I444 && !support_444) {
NV_FAIL(obs_module_text("NVENC.444Unsupported"));
return false;
}
if (is_10_bit(enc) && !support_10bit) {
NV_FAIL(obs_module_text("NVENC.10bitUnsupported"));
return false;

View file

@ -99,7 +99,6 @@ VADisplay vaapi_open_device(int *fd, const char *device_path,
if (va_status != VA_STATUS_SUCCESS) {
blog(LOG_ERROR, "VAAPI: Failed to initialize display in %s",
func_name);
vaapi_close_device(fd, va_dpy);
return NULL;
}

View file

@ -1718,10 +1718,6 @@ static size_t mp4_write_trak(struct mp4_mux *mux, struct mp4_track *track,
struct serializer *s = mux->serializer;
int64_t start = serializer_get_pos(s);
/* If track has no data, omit it from full moov. */
if (!fragmented && !track->chunks.num)
return 0;
write_box(s, 0, "trak");
// tkhd
@ -2413,7 +2409,7 @@ static void write_packets(struct mp4_mux *mux, struct mp4_track *track)
struct serializer *s = mux->serializer;
size_t count = track->packets.size / sizeof(struct encoder_packet);
if (!count || !track->fragment_samples.num)
if (!count)
return;
struct chunk *chk = da_push_back_new(track->chunks);

View file

@ -112,23 +112,20 @@ static inline void ts_offset_update(struct mp4_output *out,
struct encoder_packet *packet)
{
int64_t *offset;
int64_t ts;
bool *found;
if (packet->type == OBS_ENCODER_VIDEO) {
offset = &out->video_pts_offsets[packet->track_idx];
found = &out->found_video[packet->track_idx];
ts = packet->pts;
} else {
offset = &out->audio_dts_offsets[packet->track_idx];
found = &out->found_audio[packet->track_idx];
ts = packet->dts;
}
if (*found)
return;
*offset = ts;
*offset = packet->dts;
*found = true;
}

View file

@ -89,7 +89,7 @@ static void null_output_data(void *data, struct encoder_packet *packet)
struct obs_output_info null_output_info = {
.id = "null_output",
.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_MULTI_TRACK_AV,
.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED,
.get_name = null_output_getname,
.create = null_output_create,
.destroy = null_output_destroy,

@ -1 +1 @@
Subproject commit f8bc7c4f593c7957ecdb45a24faaff3e95f24e8b
Subproject commit 3b7c1c53815c59fb2498caf9831b0f6a0bc35025

View file

@ -309,9 +309,7 @@ static inline void set_param(struct obs_x264 *obsx264, struct obs_option option)
if (strcmp(name, "preset") != 0 && strcmp(name, "profile") != 0 &&
strcmp(name, "tune") != 0 && strcmp(name, "fps") != 0 &&
strcmp(name, "force-cfr") != 0 && strcmp(name, "width") != 0 &&
strcmp(name, "height") != 0 && strcmp(name, "opencl") != 0 &&
strcmp(name, "stats") != 0 && strcmp(name, "qpfile") != 0 &&
strcmp(name, "pass") != 0) {
strcmp(name, "height") != 0 && strcmp(name, "opencl") != 0) {
if (strcmp(option.name, OPENCL_ALIAS) == 0)
name = "opencl";
if (x264_param_parse(&obsx264->params, name, val) != 0)

View file

@ -1,11 +1,11 @@
{
"$schema": "schema/package-schema.json",
"url": "https://obsproject.com/obs2_update/rtmp-services/v5",
"version": 256,
"version": 255,
"files": [
{
"name": "services.json",
"version": 256
"version": 255
}
]
}

View file

@ -2922,103 +2922,6 @@
"keyint": 2,
"x264opts": "scenecut=0"
}
},
{
"name": "Dolby Millicast",
"common": false,
"more_info_link": "https://docs.dolby.io/streaming-apis/docs/using-obs",
"stream_key_link": "https://streaming.dolby.io",
"servers": [
{
"name": "Global (RTMPS)",
"url": "rtmps://rtmp-auto.millicast.com:443/v2/pub"
},
{
"name": "Global (RTMP)",
"url": "rtmp://rtmp-auto.millicast.com:1935/v2/pub"
},
{
"name": "Amsterdam, Netherlands (RTMPS)",
"url": "rtmps://rtmp-ams-1.millicast.com:443/v2/pub"
},
{
"name": "Amsterdam, Netherlands (RTMP)",
"url": "rtmp://rtmp-ams-1.millicast.com:1935/v2/pub"
},
{
"name": "Bangalore, India (RTMPS)",
"url": "rtmps://rtmp-blr-1.millicast.com:443/v2/pub"
},
{
"name": "Bangalore, India (RTMP)",
"url": "rtmp://rtmp-blr-1.millicast.com:1935/v2/pub"
},
{
"name": "Frankfurt, Germany (RTMPS)",
"url": "rtmps://rtmp-fra-1.millicast.com:443/v2/pub"
},
{
"name": "Frankfurt, Germany (RTMP)",
"url": "rtmp://rtmp-fra-1.millicast.com:1935/v2/pub"
},
{
"name": "Ashburn, Virginia, USA (RTMPS)",
"url": "rtmps://rtmp-iad-1.millicast.com:443/v2/pub"
},
{
"name": "Ashburn, Virginia, USA (RTMP)",
"url": "rtmp://rtmp-iad-1.millicast.com:1935/v2/pub"
},
{
"name": "London, England (RTMPS)",
"url": "rtmps://rtmp-lon-1.millicast.com:443/v2/pub"
},
{
"name": "London, England (RTMP)",
"url": "rtmp://rtmp-lon-1.millicast.com:1935/v2/pub"
},
{
"name": "Phoenix, AZ, USA (RTMPS)",
"url": "rtmps://rtmp-phx-1.millicast.com:443/v2/pub"
},
{
"name": "Phoenix, AZ, USA (RTMP)",
"url": "rtmp://rtmp-phx-1.millicast.com:1935/v2/pub"
},
{
"name": "Sao Paulo, Brazil (RTMPS)",
"url": "rtmps://rtmp-sao-1.millicast.com:443/v2/pub"
},
{
"name": "Sao Paulo, Brazil (RTMP)",
"url": "rtmp://rtmp-sao-1.millicast.com:1935/v2/pub"
},
{
"name": "Singapore (RTMPS)",
"url": "rtmps://rtmp-sgp-1.millicast.com:443/v2/pub"
},
{
"name": "Singapore (RTMP)",
"url": "rtmp://rtmp-sgp-1.millicast.com:1935/v2/pub"
},
{
"name": "Sydney, Australia (RTMPS)",
"url": "rtmps://rtmp-syd-1.millicast.com:443/v2/pub"
},
{
"name": "Sydney, Australia (RTMP)",
"url": "rtmp://rtmp-syd-1.millicast.com:1935/v2/pub"
}
],
"supported video codecs": [
"h264",
"hevc",
"av1"
],
"recommended": {
"keyint": 1,
"bframes": 0
}
}
]
}

View file

@ -23,10 +23,8 @@
#endif
enum AVHWDeviceType hw_priority[] = {
AV_HWDEVICE_TYPE_D3D11VA,
AV_HWDEVICE_TYPE_DXVA2,
AV_HWDEVICE_TYPE_QSV,
AV_HWDEVICE_TYPE_NONE,
AV_HWDEVICE_TYPE_CUDA, AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_DXVA2,
AV_HWDEVICE_TYPE_QSV, AV_HWDEVICE_TYPE_NONE,
};
static bool has_hw_type(const AVCodec *c, enum AVHWDeviceType type)

View file

@ -246,14 +246,15 @@ static inline int get_sws_range(enum AVColorRange r)
static bool mp_media_init_scaling(mp_media_t *m)
{
int space = get_sws_colorspace(m->v.frame->colorspace);
int range = get_sws_range(m->v.frame->color_range);
int space = get_sws_colorspace(m->v.decoder->colorspace);
int range = get_sws_range(m->v.decoder->color_range);
const int *coeff = sws_getCoefficients(space);
m->swscale = sws_getCachedContext(NULL, m->v.frame->width,
m->v.frame->height,
m->v.frame->format, m->v.frame->width,
m->v.frame->height, m->scale_format,
m->swscale = sws_getCachedContext(NULL, m->v.decoder->width,
m->v.decoder->height,
m->v.decoder->pix_fmt,
m->v.decoder->width,
m->v.decoder->height, m->scale_format,
SWS_POINT, NULL, NULL, NULL);
if (!m->swscale) {
blog(LOG_WARNING, "MP: Failed to initialize scaler");
@ -264,7 +265,7 @@ static bool mp_media_init_scaling(mp_media_t *m)
FIXED_1_0, FIXED_1_0);
int ret = av_image_alloc(m->scale_pic, m->scale_linesizes,
m->v.frame->width, m->v.frame->height,
m->v.decoder->width, m->v.decoder->height,
m->scale_format, 32);
if (ret < 0) {
blog(LOG_WARNING, "MP: Failed to create scale pic data");

View file

@ -39,11 +39,13 @@
#define SO_EXT "so"
#endif
static const char *startup_script_template = "\
static const char *startup_script_template =
"\
for val in pairs(package.preload) do\n\
package.preload[val] = nil\n\
end\n\
%s\
package.cpath = package.cpath .. \";\" .. \"%s/?." SO_EXT
"\" .. \";\" .. \"%s\" .. \"/?." SO_EXT "\"\n\
require \"obslua\"\n";
static const char *get_script_path_func = "\
@ -1343,11 +1345,6 @@ void obs_lua_script_save(obs_script_t *s)
/* -------------------------------------------- */
static inline void add_package_cpath(struct dstr *cpath, const char *path)
{
dstr_catf(cpath, " .. \";\" .. \"%s\" .. \"/?." SO_EXT "\"", path);
}
void obs_lua_load(void)
{
struct dstr tmp = {0};
@ -1363,7 +1360,6 @@ void obs_lua_load(void)
#define PATH_MAX MAX_PATH
#endif
struct dstr package_cpath = {0};
char import_path[PATH_MAX];
#ifdef __APPLE__
@ -1381,23 +1377,7 @@ void obs_lua_load(void)
#else
strcpy(import_path, "./");
#endif
dstr_cat(&package_cpath, "package.cpath = package.cpath");
add_package_cpath(&package_cpath, import_path);
#if !defined(_WIN32) && !defined(__APPLE__)
char *relative_script_path =
os_get_executable_path_ptr("../" SCRIPT_DIR);
if (relative_script_path)
add_package_cpath(&package_cpath, relative_script_path);
bfree(relative_script_path);
#endif
add_package_cpath(&package_cpath, SCRIPT_DIR);
dstr_cat(&package_cpath, "\n");
dstr_printf(&tmp, startup_script_template, package_cpath.array);
dstr_free(&package_cpath);
dstr_printf(&tmp, startup_script_template, import_path, SCRIPT_DIR);
startup_script = tmp.array;
obs_add_tick_callback(lua_tick, NULL);

View file

@ -1641,15 +1641,18 @@ void OBSPropertiesView::AddProperty(obs_property_t *property,
QHBoxLayout *boxLayout = new QHBoxLayout(newWidget);
boxLayout->setContentsMargins(0, 0, 0, 0);
boxLayout->setAlignment(Qt::AlignLeft);
#ifdef __APPLE__
/* TODO: This fixes the issue of tooltip not aligning
* correcty on macOS, the root cause needs further
* investigation. */
boxLayout->setSpacing(10);
#else
boxLayout->setSpacing(0);
#endif
QCheckBox *check = qobject_cast<QCheckBox *>(widget);
check->setText(desc);
check->setToolTip(
obs_property_long_description(property));
#ifdef __APPLE__
check->setAttribute(Qt::WA_LayoutUsesWidgetRect);
#endif
QLabel *help = new QLabel(check);
help->setText(bStr.arg(file));