mirror of
https://github.com/obsproject/obs-studio.git
synced 2024-07-03 01:53:35 +00:00
Compare commits
33 commits
0e44c5d6cd
...
56891231ee
Author | SHA1 | Date | |
---|---|---|---|
|
56891231ee | ||
|
9d67bf2662 | ||
|
8892edde05 | ||
|
06291c7201 | ||
|
fb5bbc8575 | ||
|
7d19add10b | ||
|
4830d6903e | ||
|
47fb194223 | ||
|
edcda5a825 | ||
|
2e6e79b4f5 | ||
|
b34fbb116e | ||
|
4517918c7a | ||
|
6cc0e2b803 | ||
|
ed2478535f | ||
|
fd34bab615 | ||
|
5f98d34e2c | ||
|
6c389271b3 | ||
|
6457d7b429 | ||
|
92352f18b8 | ||
|
a189489dd2 | ||
|
aa096e2ad0 | ||
|
9d1ac8816e | ||
|
8a8019db3f | ||
|
a9b5968552 | ||
|
fc05ca601a | ||
|
32b53ea936 | ||
|
fb3e571ce8 | ||
|
48b1298faf | ||
|
608d3bfc26 | ||
|
e215502b62 | ||
|
751dbdad10 | ||
|
9d88b632ae | ||
|
ad910b674e |
|
@ -9,7 +9,7 @@ env:
|
||||||
|
|
||||||
task:
|
task:
|
||||||
install_script:
|
install_script:
|
||||||
- pkg-install -y
|
- pkg install -y
|
||||||
cmake ninja binutils pkgconf curl
|
cmake ninja binutils pkgconf curl
|
||||||
ffmpeg qt6-base qt6-svg jansson libsysinfo e2fsprogs-libuuid pulseaudio
|
ffmpeg qt6-base qt6-svg jansson libsysinfo e2fsprogs-libuuid pulseaudio
|
||||||
alsa-lib pipewire v4l_compat libpci librist srt nlohmann-json uthash
|
alsa-lib pipewire v4l_compat libpci librist srt nlohmann-json uthash
|
||||||
|
|
12
.github/actions/windows-patches/action.yaml
vendored
12
.github/actions/windows-patches/action.yaml
vendored
|
@ -25,6 +25,7 @@ runs:
|
||||||
with:
|
with:
|
||||||
path: "repo"
|
path: "repo"
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
ref: ${{ inputs.tagName }}
|
||||||
|
|
||||||
- name: Download Release Artifact
|
- name: Download Release Artifact
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
@ -40,9 +41,9 @@ runs:
|
||||||
- name: Setup bouf
|
- name: Setup bouf
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
env:
|
env:
|
||||||
BOUF_TAG: 'v0.6.3'
|
BOUF_TAG: 'v0.6.4'
|
||||||
BOUF_HASH: '7f1d266467620aa553a705391ee06128e8ee14af66129a0e64a282997fb6fd83'
|
BOUF_HASH: 'aca6810e741dc38ff843fab7b25a0ad8570ee84f5595132cf0cc4a5b0131b4c4'
|
||||||
BOUF_NSIS_HASH: 'a234126de89f122b6a552df3416de3eabcb4195217626c7f4eaec71b20fe36eb'
|
BOUF_NSIS_HASH: 'ed453784486556bd959d56743a8478ad3f68fe0305e9b43ac19d8771d0515257'
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
run: |
|
run: |
|
||||||
# Download bouf release
|
# Download bouf release
|
||||||
|
@ -86,7 +87,10 @@ runs:
|
||||||
run: |
|
run: |
|
||||||
# Release notes are just the tag body on Windows
|
# Release notes are just the tag body on Windows
|
||||||
Set-Location repo
|
Set-Location repo
|
||||||
git tag -l --format='%(contents:body)' ${{ inputs.tagName }} > "${{ github.workspace }}/notes.rst"
|
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"
|
||||||
|
|
||||||
- name: Run bouf
|
- name: Run bouf
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
target_sources(obs-studio PRIVATE platform-x11.cpp)
|
target_sources(obs-studio PRIVATE platform-x11.cpp)
|
||||||
target_compile_definitions(obs-studio PRIVATE OBS_INSTALL_PREFIX="${OBS_INSTALL_PREFIX}")
|
target_compile_definitions(obs-studio PRIVATE OBS_INSTALL_PREFIX="${OBS_INSTALL_PREFIX}")
|
||||||
target_link_libraries(obs-studio PRIVATE Qt::GuiPrivate procstat)
|
target_link_libraries(obs-studio PRIVATE Qt::GuiPrivate Qt::DBus procstat)
|
||||||
|
|
||||||
target_sources(obs-studio PRIVATE system-info-posix.cpp)
|
target_sources(obs-studio PRIVATE system-info-posix.cpp)
|
||||||
|
|
||||||
|
|
|
@ -1566,10 +1566,10 @@ FailedToStartStream.MissingConfigURL="No config URL available for the current se
|
||||||
FailedToStartStream.NoCustomRTMPURLInSettings="Custom RTMP URL not specified"
|
FailedToStartStream.NoCustomRTMPURLInSettings="Custom RTMP URL not specified"
|
||||||
FailedToStartStream.InvalidCustomConfig="Invalid custom config"
|
FailedToStartStream.InvalidCustomConfig="Invalid custom config"
|
||||||
FailedToStartStream.FailedToCreateMultitrackVideoService="Failed to create multitrack video service"
|
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.EncoderNotAvailable="NVENC not available.\n\nFailed to find encoder type '%1'"
|
||||||
FailedToStartStream.FailedToCreateVideoEncoder="Failed to create video encoder '%1' (type: '%2')"
|
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.FailedToCreateAudioEncoder="Failed to create audio encoder"
|
||||||
FailedToStartStream.NoRTMPURLInConfig="Config does not contain stream target RTMP(S) URL"
|
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?"
|
FailedToStartStream.FallbackToDefault="Starting the stream using %1 failed; do you want to retry using single encode settings?"
|
||||||
|
@ -1585,3 +1585,6 @@ 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.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.DisableAndStartStreaming="Disable for this stream and Start Streaming"
|
||||||
MultitrackVideo.IncompatibleSettings.UpdateAndStartStreaming="Update Settings 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]"
|
||||||
|
|
|
@ -477,7 +477,7 @@ OBSDock > QWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
#transitionsFrame {
|
#transitionsFrame {
|
||||||
padding: 4px 8px;
|
padding: var(--padding_large);
|
||||||
}
|
}
|
||||||
|
|
||||||
OBSDock QLabel {
|
OBSDock QLabel {
|
||||||
|
@ -827,7 +827,7 @@ QComboBox::drop-down,
|
||||||
QDateTimeEdit::drop-down {
|
QDateTimeEdit::drop-down {
|
||||||
border:none;
|
border:none;
|
||||||
border-left: 1px solid var(--grey6);
|
border-left: 1px solid var(--grey6);
|
||||||
width: 32px;
|
width: var(--input_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
QComboBox::down-arrow,
|
QComboBox::down-arrow,
|
||||||
|
@ -923,7 +923,7 @@ QDoubleSpinBox::up-button {
|
||||||
subcontrol-origin: padding;
|
subcontrol-origin: padding;
|
||||||
subcontrol-position: top right; /* position at the top right corner */
|
subcontrol-position: top right; /* position at the top right corner */
|
||||||
|
|
||||||
width: 32px;
|
width: var(--input_height);
|
||||||
height: var(--spinbox_button_height);
|
height: var(--spinbox_button_height);
|
||||||
border-left: 1px solid var(--grey6);
|
border-left: 1px solid var(--grey6);
|
||||||
border-bottom: 1px solid transparent;
|
border-bottom: 1px solid transparent;
|
||||||
|
@ -936,7 +936,7 @@ QDoubleSpinBox::down-button {
|
||||||
subcontrol-origin: padding;
|
subcontrol-origin: padding;
|
||||||
subcontrol-position: bottom right; /* position at the top right corner */
|
subcontrol-position: bottom right; /* position at the top right corner */
|
||||||
|
|
||||||
width: 32px;
|
width: var(--input_height);
|
||||||
height: var(--spinbox_button_height);
|
height: var(--spinbox_button_height);
|
||||||
border-left: 1px solid var(--grey6);
|
border-left: 1px solid var(--grey6);
|
||||||
border-top: 1px solid var(--grey6);
|
border-top: 1px solid var(--grey6);
|
||||||
|
@ -1078,6 +1078,8 @@ QPushButton:hover {
|
||||||
|
|
||||||
QToolButton:hover,
|
QToolButton:hover,
|
||||||
QToolButton:focus,
|
QToolButton:focus,
|
||||||
|
QPushButton[toolButton="true"]:hover,
|
||||||
|
QPushButton[toolButton="true"]:focus,
|
||||||
MuteCheckBox::indicator:hover,
|
MuteCheckBox::indicator:hover,
|
||||||
MuteCheckBox::indicator:focus {
|
MuteCheckBox::indicator:focus {
|
||||||
border-color: var(--button_border);
|
border-color: var(--button_border);
|
||||||
|
@ -1104,7 +1106,9 @@ QPushButton:pressed:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
QToolButton:pressed,
|
QToolButton:pressed,
|
||||||
QToolButton:pressed:hover {
|
QToolButton:pressed:hover,
|
||||||
|
QPushButton[toolButton="true"]:pressed,
|
||||||
|
QPushButton[toolButton="true"]:pressed:hover {
|
||||||
background-color: var(--button_bg_down);
|
background-color: var(--button_bg_down);
|
||||||
border-color: var(--button_border);
|
border-color: var(--button_border);
|
||||||
}
|
}
|
||||||
|
@ -1275,7 +1279,6 @@ VolControl #volLabel {
|
||||||
#vMixerScrollArea VolControl #volLabel {
|
#vMixerScrollArea VolControl #volLabel {
|
||||||
padding: var(--padding_base) 0px var(--padding_base);
|
padding: var(--padding_base) 0px var(--padding_base);
|
||||||
min-width: var(--volume_slider_label);
|
min-width: var(--volume_slider_label);
|
||||||
max-width: var(--volume_slider_label);
|
|
||||||
margin-left: var(--padding_xlarge);
|
margin-left: var(--padding_xlarge);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
--grey2: rgb(134,135,134);
|
--grey2: rgb(134,135,134);
|
||||||
--grey3: rgb(122,121,122);
|
--grey3: rgb(122,121,122);
|
||||||
--grey4: rgb(76,76,76);
|
--grey4: rgb(76,76,76);
|
||||||
--grey5: rgb(88,87,88);
|
--grey5: rgb(70,69,70);
|
||||||
--grey6: rgb(31,30,31);
|
--grey6: rgb(31,30,31);
|
||||||
--grey7: rgb(58,57,58);
|
--grey7: rgb(58,57,58);
|
||||||
--grey8: rgb(46,45,46);
|
--grey8: rgb(46,45,46);
|
||||||
|
@ -31,8 +31,12 @@
|
||||||
/* OS Fixes */
|
/* OS Fixes */
|
||||||
--os_mac_font_base_value: 11;
|
--os_mac_font_base_value: 11;
|
||||||
|
|
||||||
|
--font_small: calc(0.8pt * var(--font_base_value));
|
||||||
|
|
||||||
--icon_base: calc(6px + 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_wide: calc(18px + calc(0.25 * var(--padding_base_value)));
|
||||||
--padding_menu: calc(8px + calc(1 * var(--padding_base_value)));
|
--padding_menu: calc(8px + calc(1 * var(--padding_base_value)));
|
||||||
|
|
||||||
|
@ -45,14 +49,14 @@
|
||||||
--border_radius_large: 2px;
|
--border_radius_large: 2px;
|
||||||
|
|
||||||
--input_bg: var(--grey4);
|
--input_bg: var(--grey4);
|
||||||
--input_bg_hover: var(--grey5);
|
--input_bg_hover: var(--grey1);
|
||||||
--input_bg_focus: var(--grey6);
|
--input_bg_focus: var(--grey6);
|
||||||
|
|
||||||
--list_item_bg_selected: var(--primary);
|
--list_item_bg_selected: var(--primary);
|
||||||
--list_item_bg_hover: var(--primary_light);
|
--list_item_bg_hover: var(--primary_light);
|
||||||
|
|
||||||
--input_border: var(--grey4);
|
--input_border: var(--grey4);
|
||||||
--input_border_hover: var(--grey5);
|
--input_border_hover: var(--grey1);
|
||||||
--input_border_focus: var(--grey6);
|
--input_border_focus: var(--grey6);
|
||||||
|
|
||||||
--spacing_input: var(--spacing_base);
|
--spacing_input: var(--spacing_base);
|
||||||
|
@ -91,11 +95,17 @@ QStatusBar {
|
||||||
background-color: var(--bg_window);
|
background-color: var(--bg_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OBSDock > QWidget {
|
||||||
|
border-top: 1px solid var(--border_color);
|
||||||
|
padding-top: var(--spacing_large);
|
||||||
|
}
|
||||||
|
|
||||||
QDockWidget {
|
QDockWidget {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDockWidget::title {
|
QDockWidget::title {
|
||||||
|
background-color: var(--grey5);
|
||||||
padding: var(--dock_title_padding);
|
padding: var(--dock_title_padding);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
@ -104,6 +114,10 @@ QDockWidget > QWidget {
|
||||||
background: var(--bg_window);
|
background: var(--bg_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#transitionsFrame {
|
||||||
|
padding: var(--padding_xlarge);
|
||||||
|
}
|
||||||
|
|
||||||
SceneTree::item,
|
SceneTree::item,
|
||||||
SourceTreeItem {
|
SourceTreeItem {
|
||||||
border-width: 0px;
|
border-width: 0px;
|
||||||
|
@ -193,12 +207,20 @@ OBSBasicSettings QListWidget::item {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPushButton:checked {
|
||||||
|
border-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
QToolButton,
|
QToolButton,
|
||||||
QPushButton[toolButton="true"] {
|
QPushButton[toolButton="true"] {
|
||||||
background-color: var(--bg_window);
|
background-color: var(--bg_window);
|
||||||
border-color: var(--bg_window);
|
border-color: var(--bg_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#stackedMixerArea QScrollArea {
|
||||||
|
background: var(--bg_base);
|
||||||
|
}
|
||||||
|
|
||||||
#stackedMixerArea QPushButton {
|
#stackedMixerArea QPushButton {
|
||||||
min-width: var(--icon_base);
|
min-width: var(--icon_base);
|
||||||
padding: var(--padding_large) var(--padding_large);
|
padding: var(--padding_large) var(--padding_large);
|
||||||
|
@ -215,6 +237,33 @@ QPushButton[toolButton="true"] {
|
||||||
border: none;
|
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,
|
||||||
MuteCheckBox::indicator:unchecked {
|
MuteCheckBox::indicator:unchecked {
|
||||||
background-color: var(--bg_base);
|
background-color: var(--bg_base);
|
||||||
|
@ -255,3 +304,7 @@ VolumeMeter {
|
||||||
qproperty-minorTickColor: rgb(122,121,122); /* light */
|
qproperty-minorTickColor: rgb(122,121,122); /* light */
|
||||||
qproperty-meterThickness: 3;
|
qproperty-meterThickness: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OBSBasicStats {
|
||||||
|
background: var(--bg_window);
|
||||||
|
}
|
||||||
|
|
|
@ -128,6 +128,9 @@
|
||||||
<property name="themeID" stdset="0">
|
<property name="themeID" stdset="0">
|
||||||
<string>addIconSmall</string>
|
<string>addIconSmall</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="toolButton" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
@ -157,6 +160,9 @@
|
||||||
<property name="themeID" stdset="0">
|
<property name="themeID" stdset="0">
|
||||||
<string>removeIconSmall</string>
|
<string>removeIconSmall</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="toolButton" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
@ -186,6 +192,9 @@
|
||||||
<property name="themeID" stdset="0">
|
<property name="themeID" stdset="0">
|
||||||
<string>upArrowIconSmall</string>
|
<string>upArrowIconSmall</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="toolButton" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
@ -215,6 +224,9 @@
|
||||||
<property name="themeID" stdset="0">
|
<property name="themeID" stdset="0">
|
||||||
<string>downArrowIconSmall</string>
|
<string>downArrowIconSmall</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="toolButton" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
@ -350,6 +362,9 @@
|
||||||
<property name="themeID" stdset="0">
|
<property name="themeID" stdset="0">
|
||||||
<string>addIconSmall</string>
|
<string>addIconSmall</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="toolButton" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
@ -379,6 +394,9 @@
|
||||||
<property name="themeID" stdset="0">
|
<property name="themeID" stdset="0">
|
||||||
<string>removeIconSmall</string>
|
<string>removeIconSmall</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="toolButton" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
@ -408,6 +426,9 @@
|
||||||
<property name="themeID" stdset="0">
|
<property name="themeID" stdset="0">
|
||||||
<string>upArrowIconSmall</string>
|
<string>upArrowIconSmall</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="toolButton" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
@ -437,6 +458,9 @@
|
||||||
<property name="themeID" stdset="0">
|
<property name="themeID" stdset="0">
|
||||||
<string>downArrowIconSmall</string>
|
<string>downArrowIconSmall</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="toolButton" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
|
|
@ -1679,6 +1679,9 @@
|
||||||
<property name="maximum">
|
<property name="maximum">
|
||||||
<number>1000000</number>
|
<number>1000000</number>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<number>50</number>
|
||||||
|
</property>
|
||||||
<property name="value">
|
<property name="value">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
|
|
|
@ -128,7 +128,8 @@ GoLiveApi::Config DownloadGoLiveConfig(QWidget *parent, QString url,
|
||||||
|
|
||||||
QString MultitrackVideoAutoConfigURL(obs_service_t *service)
|
QString MultitrackVideoAutoConfigURL(obs_service_t *service)
|
||||||
{
|
{
|
||||||
static const QString url = [service]() -> QString {
|
static const std::optional<QString> cli_url =
|
||||||
|
[]() -> std::optional<QString> {
|
||||||
auto args = qApp->arguments();
|
auto args = qApp->arguments();
|
||||||
for (int i = 0; i < args.length() - 1; i++) {
|
for (int i = 0; i < args.length() - 1; i++) {
|
||||||
if (args[i] == "--config-url" &&
|
if (args[i] == "--config-url" &&
|
||||||
|
@ -136,11 +137,18 @@ QString MultitrackVideoAutoConfigURL(obs_service_t *service)
|
||||||
return args[i + 1];
|
return args[i + 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OBSDataAutoRelease settings = obs_service_get_settings(service);
|
return std::nullopt;
|
||||||
return obs_data_get_string(
|
|
||||||
settings, "multitrack_video_configuration_url");
|
|
||||||
}();
|
}();
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
blog(LOG_INFO, "Go live URL: %s", url.toUtf8().constData());
|
blog(LOG_INFO, "Go live URL: %s", url.toUtf8().constData());
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,32 @@ constructGoLivePost(QString streamKey,
|
||||||
client.name = "obs-studio";
|
client.name = "obs-studio";
|
||||||
client.version = obs_get_version_string();
|
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;
|
auto &preferences = post_data.preferences;
|
||||||
preferences.vod_track_audio = vod_track_enabled;
|
preferences.vod_track_audio = vod_track_enabled;
|
||||||
|
|
||||||
|
|
|
@ -109,8 +109,9 @@ using json = nlohmann::json;
|
||||||
struct Client {
|
struct Client {
|
||||||
string name = "obs-studio";
|
string name = "obs-studio";
|
||||||
string version;
|
string version;
|
||||||
|
std::vector<string> supported_codecs;
|
||||||
|
|
||||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE(Client, name, version)
|
NLOHMANN_DEFINE_TYPE_INTRUSIVE(Client, name, version, supported_codecs)
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Cpu {
|
struct Cpu {
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <obs-app.hpp>
|
#include <obs-app.hpp>
|
||||||
#include <obs.hpp>
|
#include <obs.hpp>
|
||||||
#include <remote-text.hpp>
|
#include <remote-text.hpp>
|
||||||
|
#include <window-basic-main.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
@ -116,13 +117,15 @@ create_service(const GoLiveApi::Config &go_live_config,
|
||||||
|
|
||||||
/* The stream key itself may contain query parameters, such as
|
/* The stream key itself may contain query parameters, such as
|
||||||
* "bandwidthtest" that need to be carried over. */
|
* "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};
|
QUrl parsed_key{stream_key};
|
||||||
QUrlQuery key_query{parsed_key};
|
|
||||||
|
|
||||||
QUrl parsed_url{url};
|
QUrl parsed_url{url};
|
||||||
QUrlQuery parsed_query{parsed_url};
|
QUrlQuery parsed_query{parsed_url};
|
||||||
|
|
||||||
for (const auto &[key, value] : key_query.queryItems())
|
for (const auto &[key, value] : user_key_query.queryItems())
|
||||||
parsed_query.addQueryItem(key, value);
|
parsed_query.addQueryItem(key, value);
|
||||||
|
|
||||||
if (!go_live_config.meta.config_id.empty()) {
|
if (!go_live_config.meta.config_id.empty()) {
|
||||||
|
@ -329,11 +332,12 @@ struct OBSOutputs {
|
||||||
};
|
};
|
||||||
|
|
||||||
static OBSOutputs
|
static OBSOutputs
|
||||||
SetupOBSOutput(obs_data_t *dump_stream_to_file_config,
|
SetupOBSOutput(QWidget *parent, const QString &multitrack_video_name,
|
||||||
|
obs_data_t *dump_stream_to_file_config,
|
||||||
const GoLiveApi::Config &go_live_config,
|
const GoLiveApi::Config &go_live_config,
|
||||||
std::vector<OBSEncoderAutoRelease> &audio_encoders,
|
std::vector<OBSEncoderAutoRelease> &audio_encoders,
|
||||||
std::vector<OBSEncoderAutoRelease> &video_encoders,
|
std::shared_ptr<obs_encoder_group_t> &video_encoder_group,
|
||||||
const char *audio_encoder_id,
|
const char *audio_encoder_id, size_t main_audio_mixer,
|
||||||
std::optional<size_t> vod_track_mixer);
|
std::optional<size_t> vod_track_mixer);
|
||||||
static void SetupSignalHandlers(bool recording, MultitrackVideoOutput *self,
|
static void SetupSignalHandlers(bool recording, MultitrackVideoOutput *self,
|
||||||
obs_output_t *output, OBSSignal &start,
|
obs_output_t *output, OBSSignal &start,
|
||||||
|
@ -346,7 +350,7 @@ void MultitrackVideoOutput::PrepareStreaming(
|
||||||
std::optional<uint32_t> maximum_aggregate_bitrate,
|
std::optional<uint32_t> maximum_aggregate_bitrate,
|
||||||
std::optional<uint32_t> maximum_video_tracks,
|
std::optional<uint32_t> maximum_video_tracks,
|
||||||
std::optional<std::string> custom_config,
|
std::optional<std::string> custom_config,
|
||||||
obs_data_t *dump_stream_to_file_config,
|
obs_data_t *dump_stream_to_file_config, size_t main_audio_mixer,
|
||||||
std::optional<size_t> vod_track_mixer)
|
std::optional<size_t> vod_track_mixer)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
@ -461,11 +465,13 @@ void MultitrackVideoOutput::PrepareStreaming(
|
||||||
const auto &output_config = custom ? *custom : *go_live_config;
|
const auto &output_config = custom ? *custom : *go_live_config;
|
||||||
const auto &service_config = go_live_config ? *go_live_config : *custom;
|
const auto &service_config = go_live_config ? *go_live_config : *custom;
|
||||||
|
|
||||||
auto audio_encoders = std::vector<OBSEncoderAutoRelease>();
|
std::vector<OBSEncoderAutoRelease> audio_encoders;
|
||||||
auto video_encoders = std::vector<OBSEncoderAutoRelease>();
|
std::shared_ptr<obs_encoder_group_t> video_encoder_group;
|
||||||
auto outputs = SetupOBSOutput(dump_stream_to_file_config, output_config,
|
auto outputs = SetupOBSOutput(parent, multitrack_video_name,
|
||||||
audio_encoders, video_encoders,
|
dump_stream_to_file_config, output_config,
|
||||||
audio_encoder_id, vod_track_mixer);
|
audio_encoders, video_encoder_group,
|
||||||
|
audio_encoder_id, main_audio_mixer,
|
||||||
|
vod_track_mixer);
|
||||||
auto output = std::move(outputs.output);
|
auto output = std::move(outputs.output);
|
||||||
auto recording_output = std::move(outputs.recording_output);
|
auto recording_output = std::move(outputs.recording_output);
|
||||||
if (!output)
|
if (!output)
|
||||||
|
@ -496,13 +502,6 @@ void MultitrackVideoOutput::PrepareStreaming(
|
||||||
start_recording, stop_recording,
|
start_recording, stop_recording,
|
||||||
deactivate_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;
|
decltype(audio_encoders) recording_audio_encoders;
|
||||||
recording_audio_encoders.reserve(audio_encoders.size());
|
recording_audio_encoders.reserve(audio_encoders.size());
|
||||||
for (auto &encoder : audio_encoders) {
|
for (auto &encoder : audio_encoders) {
|
||||||
|
@ -515,7 +514,7 @@ void MultitrackVideoOutput::PrepareStreaming(
|
||||||
current_stream_dump_mutex};
|
current_stream_dump_mutex};
|
||||||
current_stream_dump.emplace(OBSOutputObjects{
|
current_stream_dump.emplace(OBSOutputObjects{
|
||||||
std::move(recording_output),
|
std::move(recording_output),
|
||||||
std::move(recording_video_encoders),
|
video_encoder_group,
|
||||||
std::move(recording_audio_encoders),
|
std::move(recording_audio_encoders),
|
||||||
nullptr,
|
nullptr,
|
||||||
std::move(start_recording),
|
std::move(start_recording),
|
||||||
|
@ -528,7 +527,7 @@ void MultitrackVideoOutput::PrepareStreaming(
|
||||||
const std::lock_guard current_lock{current_mutex};
|
const std::lock_guard current_lock{current_mutex};
|
||||||
current.emplace(OBSOutputObjects{
|
current.emplace(OBSOutputObjects{
|
||||||
std::move(output),
|
std::move(output),
|
||||||
std::move(video_encoders),
|
video_encoder_group,
|
||||||
std::move(audio_encoders),
|
std::move(audio_encoders),
|
||||||
std::move(multitrack_video_service),
|
std::move(multitrack_video_service),
|
||||||
std::move(start_streaming),
|
std::move(start_streaming),
|
||||||
|
@ -692,11 +691,10 @@ bool MultitrackVideoOutput::HandleIncompatibleSettings(
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
create_video_encoders(const GoLiveApi::Config &go_live_config,
|
create_video_encoders(const GoLiveApi::Config &go_live_config,
|
||||||
std::vector<OBSEncoderAutoRelease> &video_encoders,
|
std::shared_ptr<obs_encoder_group_t> &video_encoder_group,
|
||||||
obs_output_t *output, obs_output_t *recording_output)
|
obs_output_t *output, obs_output_t *recording_output)
|
||||||
{
|
{
|
||||||
DStr video_encoder_name_buffer;
|
DStr video_encoder_name_buffer;
|
||||||
obs_encoder_t *first_encoder = nullptr;
|
|
||||||
if (go_live_config.encoder_configurations.empty()) {
|
if (go_live_config.encoder_configurations.empty()) {
|
||||||
blog(LOG_WARNING,
|
blog(LOG_WARNING,
|
||||||
"MultitrackVideoOutput: Missing video encoder configurations");
|
"MultitrackVideoOutput: Missing video encoder configurations");
|
||||||
|
@ -704,6 +702,11 @@ create_video_encoders(const GoLiveApi::Config &go_live_config,
|
||||||
QTStr("FailedToStartStream.MissingEncoderConfigs"));
|
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();
|
for (size_t i = 0; i < go_live_config.encoder_configurations.size();
|
||||||
i++) {
|
i++) {
|
||||||
auto encoder = create_video_encoder(
|
auto encoder = create_video_encoder(
|
||||||
|
@ -712,19 +715,16 @@ create_video_encoders(const GoLiveApi::Config &go_live_config,
|
||||||
if (!encoder)
|
if (!encoder)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!first_encoder)
|
if (!obs_encoder_set_group(encoder, encoder_group.get()))
|
||||||
first_encoder = encoder;
|
return false;
|
||||||
else
|
|
||||||
obs_encoder_group_keyframe_aligned_encoders(
|
|
||||||
first_encoder, encoder);
|
|
||||||
|
|
||||||
obs_output_set_video_encoder2(output, encoder, i);
|
obs_output_set_video_encoder2(output, encoder, i);
|
||||||
if (recording_output)
|
if (recording_output)
|
||||||
obs_output_set_video_encoder2(recording_output, encoder,
|
obs_output_set_video_encoder2(recording_output, encoder,
|
||||||
i);
|
i);
|
||||||
video_encoders.emplace_back(std::move(encoder));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
video_encoder_group = encoder_group;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -732,9 +732,47 @@ static void
|
||||||
create_audio_encoders(const GoLiveApi::Config &go_live_config,
|
create_audio_encoders(const GoLiveApi::Config &go_live_config,
|
||||||
std::vector<OBSEncoderAutoRelease> &audio_encoders,
|
std::vector<OBSEncoderAutoRelease> &audio_encoders,
|
||||||
obs_output_t *output, obs_output_t *recording_output,
|
obs_output_t *output, obs_output_t *recording_output,
|
||||||
const char *audio_encoder_id,
|
const char *audio_encoder_id, size_t main_audio_mixer,
|
||||||
std::optional<size_t> vod_track_mixer)
|
std::optional<size_t> vod_track_mixer,
|
||||||
|
std::vector<speaker_layout> &speaker_layouts,
|
||||||
|
speaker_layout ¤t_layout)
|
||||||
{
|
{
|
||||||
|
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 =
|
using encoder_configs_type =
|
||||||
decltype(go_live_config.audio_configurations.live);
|
decltype(go_live_config.audio_configurations.live);
|
||||||
DStr encoder_name_buffer;
|
DStr encoder_name_buffer;
|
||||||
|
@ -760,6 +798,10 @@ create_audio_encoders(const GoLiveApi::Config &go_live_config,
|
||||||
create_audio_encoder(encoder_name_buffer->array,
|
create_audio_encoder(encoder_name_buffer->array,
|
||||||
audio_encoder_id, settings,
|
audio_encoder_id, settings,
|
||||||
mixer_idx);
|
mixer_idx);
|
||||||
|
|
||||||
|
sanitize_audio_channels(audio_encoder,
|
||||||
|
configs[i].channels);
|
||||||
|
|
||||||
obs_output_set_audio_encoder(output, audio_encoder,
|
obs_output_set_audio_encoder(output, audio_encoder,
|
||||||
output_encoder_index);
|
output_encoder_index);
|
||||||
if (recording_output)
|
if (recording_output)
|
||||||
|
@ -772,7 +814,8 @@ create_audio_encoders(const GoLiveApi::Config &go_live_config,
|
||||||
};
|
};
|
||||||
|
|
||||||
create_encoders("multitrack video live audio",
|
create_encoders("multitrack video live audio",
|
||||||
go_live_config.audio_configurations.live, 0);
|
go_live_config.audio_configurations.live,
|
||||||
|
main_audio_mixer);
|
||||||
|
|
||||||
if (!vod_track_mixer.has_value())
|
if (!vod_track_mixer.has_value())
|
||||||
return;
|
return;
|
||||||
|
@ -786,12 +829,84 @@ create_audio_encoders(const GoLiveApi::Config &go_live_config,
|
||||||
return;
|
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
|
static OBSOutputs
|
||||||
SetupOBSOutput(obs_data_t *dump_stream_to_file_config,
|
SetupOBSOutput(QWidget *parent, const QString &multitrack_video_name,
|
||||||
|
obs_data_t *dump_stream_to_file_config,
|
||||||
const GoLiveApi::Config &go_live_config,
|
const GoLiveApi::Config &go_live_config,
|
||||||
std::vector<OBSEncoderAutoRelease> &audio_encoders,
|
std::vector<OBSEncoderAutoRelease> &audio_encoders,
|
||||||
std::vector<OBSEncoderAutoRelease> &video_encoders,
|
std::shared_ptr<obs_encoder_group_t> &video_encoder_group,
|
||||||
const char *audio_encoder_id,
|
const char *audio_encoder_id, size_t main_audio_mixer,
|
||||||
std::optional<size_t> vod_track_mixer)
|
std::optional<size_t> vod_track_mixer)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -801,13 +916,19 @@ SetupOBSOutput(obs_data_t *dump_stream_to_file_config,
|
||||||
recording_output =
|
recording_output =
|
||||||
create_recording_output(dump_stream_to_file_config);
|
create_recording_output(dump_stream_to_file_config);
|
||||||
|
|
||||||
if (!create_video_encoders(go_live_config, video_encoders, output,
|
if (!create_video_encoders(go_live_config, video_encoder_group, output,
|
||||||
recording_output))
|
recording_output))
|
||||||
return {nullptr, nullptr};
|
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,
|
create_audio_encoders(go_live_config, audio_encoders, output,
|
||||||
recording_output, audio_encoder_id,
|
recording_output, audio_encoder_id,
|
||||||
vod_track_mixer);
|
main_audio_mixer, vod_track_mixer,
|
||||||
|
requested_speaker_layouts, current_layout);
|
||||||
|
|
||||||
|
handle_speaker_layout_issues(parent, multitrack_video_name,
|
||||||
|
requested_speaker_layouts, current_layout);
|
||||||
|
|
||||||
return {std::move(output), std::move(recording_output)};
|
return {std::move(output), std::move(recording_output)};
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ public:
|
||||||
std::optional<uint32_t> maximum_video_tracks,
|
std::optional<uint32_t> maximum_video_tracks,
|
||||||
std::optional<std::string> custom_config,
|
std::optional<std::string> custom_config,
|
||||||
obs_data_t *dump_stream_to_file_config,
|
obs_data_t *dump_stream_to_file_config,
|
||||||
|
size_t main_audio_mixer,
|
||||||
std::optional<size_t> vod_track_mixer);
|
std::optional<size_t> vod_track_mixer);
|
||||||
signal_handler_t *StreamingSignalHandler();
|
signal_handler_t *StreamingSignalHandler();
|
||||||
void StartedStreaming();
|
void StartedStreaming();
|
||||||
|
@ -53,7 +54,7 @@ public:
|
||||||
private:
|
private:
|
||||||
struct OBSOutputObjects {
|
struct OBSOutputObjects {
|
||||||
OBSOutputAutoRelease output_;
|
OBSOutputAutoRelease output_;
|
||||||
std::vector<OBSEncoderAutoRelease> video_encoders_;
|
std::shared_ptr<obs_encoder_group_t> video_encoder_group_;
|
||||||
std::vector<OBSEncoderAutoRelease> audio_encoders_;
|
std::vector<OBSEncoderAutoRelease> audio_encoders_;
|
||||||
OBSServiceAutoRelease multitrack_video_service_;
|
OBSServiceAutoRelease multitrack_video_service_;
|
||||||
OBSSignal start_signal, stop_signal, deactivate_signal;
|
OBSSignal start_signal, stop_signal, deactivate_signal;
|
||||||
|
|
|
@ -1138,7 +1138,7 @@ FutureHolder<bool> SimpleOutput::SetupStreaming(obs_service_t *service)
|
||||||
: std::nullopt;
|
: std::nullopt;
|
||||||
|
|
||||||
auto holder = SetupMultitrackVideo(
|
auto holder = SetupMultitrackVideo(
|
||||||
service, GetSimpleAACEncoderForBitrate(audio_bitrate),
|
service, GetSimpleAACEncoderForBitrate(audio_bitrate), 0,
|
||||||
vod_track_mixer);
|
vod_track_mixer);
|
||||||
auto future =
|
auto future =
|
||||||
PreventFutureDeadlock(holder.future)
|
PreventFutureDeadlock(holder.future)
|
||||||
|
@ -2286,9 +2286,13 @@ FutureHolder<bool> AdvancedOutput::SetupStreaming(obs_service_t *service)
|
||||||
|
|
||||||
const char *audio_encoder_id =
|
const char *audio_encoder_id =
|
||||||
config_get_string(main->Config(), "AdvOut", "AudioEncoder");
|
config_get_string(main->Config(), "AdvOut", "AudioEncoder");
|
||||||
|
int streamTrackIndex =
|
||||||
|
config_get_int(main->Config(), "AdvOut", "TrackIndex") - 1;
|
||||||
|
|
||||||
auto holder = SetupMultitrackVideo(service, audio_encoder_id,
|
auto holder =
|
||||||
VodTrackMixerIdx(service));
|
SetupMultitrackVideo(service, audio_encoder_id,
|
||||||
|
static_cast<size_t>(streamTrackIndex),
|
||||||
|
VodTrackMixerIdx(service));
|
||||||
auto future =
|
auto future =
|
||||||
PreventFutureDeadlock(holder.future)
|
PreventFutureDeadlock(holder.future)
|
||||||
.then(main, [&](std::optional<bool>
|
.then(main, [&](std::optional<bool>
|
||||||
|
@ -2701,10 +2705,9 @@ std::string BasicOutputHandler::GetRecordingFilename(
|
||||||
|
|
||||||
extern std::string DeserializeConfigText(const char *text);
|
extern std::string DeserializeConfigText(const char *text);
|
||||||
|
|
||||||
FutureHolder<std::optional<bool>>
|
FutureHolder<std::optional<bool>> BasicOutputHandler::SetupMultitrackVideo(
|
||||||
BasicOutputHandler::SetupMultitrackVideo(obs_service_t *service,
|
obs_service_t *service, std::string audio_encoder_id,
|
||||||
std::string audio_encoder_id,
|
size_t main_audio_mixer, std::optional<size_t> vod_track_mixer)
|
||||||
std::optional<size_t> vod_track_mixer)
|
|
||||||
{
|
{
|
||||||
if (!multitrackVideo)
|
if (!multitrackVideo)
|
||||||
return {[] {}, CreateFuture().then([] {
|
return {[] {}, CreateFuture().then([] {
|
||||||
|
@ -2784,7 +2787,8 @@ BasicOutputHandler::SetupMultitrackVideo(obs_service_t *service,
|
||||||
audio_encoder_id.c_str(),
|
audio_encoder_id.c_str(),
|
||||||
maximum_aggregate_bitrate,
|
maximum_aggregate_bitrate,
|
||||||
maximum_video_tracks, custom_config,
|
maximum_video_tracks, custom_config,
|
||||||
stream_dump_config, vod_track_mixer);
|
stream_dump_config, main_audio_mixer,
|
||||||
|
vod_track_mixer);
|
||||||
} catch (const MultitrackVideoError &error) {
|
} catch (const MultitrackVideoError &error) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,10 +98,9 @@ protected:
|
||||||
bool overwrite, const char *format,
|
bool overwrite, const char *format,
|
||||||
bool ffmpeg);
|
bool ffmpeg);
|
||||||
|
|
||||||
FutureHolder<std::optional<bool>>
|
FutureHolder<std::optional<bool>> SetupMultitrackVideo(
|
||||||
SetupMultitrackVideo(obs_service_t *service,
|
obs_service_t *service, std::string audio_encoder_id,
|
||||||
std::string audio_encoder_id,
|
size_t main_audio_mixer, std::optional<size_t> vod_track_mixer);
|
||||||
std::optional<size_t> vod_track_mixer);
|
|
||||||
OBSDataAutoRelease GenerateMultitrackVideoStreamDumpConfig();
|
OBSDataAutoRelease GenerateMultitrackVideoStreamDumpConfig();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1549,7 +1549,7 @@ extern void CheckExistingCookieId();
|
||||||
#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0
|
#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0
|
||||||
#define DEFAULT_CONTAINER "mkv"
|
#define DEFAULT_CONTAINER "mkv"
|
||||||
#else
|
#else
|
||||||
#define DEFAULT_CONTAINER "fragmented_mp4"
|
#define DEFAULT_CONTAINER "hybrid_mp4"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool OBSBasic::InitBasicConfigDefaults()
|
bool OBSBasic::InitBasicConfigDefaults()
|
||||||
|
|
|
@ -6325,6 +6325,10 @@ void OBSBasicSettings::UpdateMultitrackVideo()
|
||||||
ui->enableMultitrackVideo->setChecked(false);
|
ui->enableMultitrackVideo->setChecked(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
available = available && MultitrackVideoDeveloperModeEnabled();
|
||||||
|
#endif
|
||||||
|
|
||||||
if (IsCustomService())
|
if (IsCustomService())
|
||||||
available = available && MultitrackVideoDeveloperModeEnabled();
|
available = available && MultitrackVideoDeveloperModeEnabled();
|
||||||
|
|
||||||
|
|
|
@ -277,7 +277,7 @@ endif()
|
||||||
|
|
||||||
if(EXISTS "${FFmpeg_avutil_INCLUDE_DIR}/libavutil/ffversion.h")
|
if(EXISTS "${FFmpeg_avutil_INCLUDE_DIR}/libavutil/ffversion.h")
|
||||||
file(STRINGS "${FFmpeg_avutil_INCLUDE_DIR}/libavutil/ffversion.h" _version_string
|
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}")
|
string(REGEX REPLACE ".*FFMPEG_VERSION[ \t]+\"n?([0-9]+\\.[0-9]).*\".*" "\\1" FFmpeg_VERSION "${_version_string}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ target_sources(
|
||||||
obs-av1.h
|
obs-av1.h
|
||||||
obs-avc.c
|
obs-avc.c
|
||||||
obs-avc.h
|
obs-avc.h
|
||||||
|
obs-config.h
|
||||||
obs-data.c
|
obs-data.c
|
||||||
obs-data.h
|
obs-data.h
|
||||||
obs-defs.h
|
obs-defs.h
|
||||||
|
|
|
@ -325,11 +325,10 @@ static void add_connection(struct obs_encoder *encoder)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encoder->encoder_group) {
|
if (encoder->encoder_group) {
|
||||||
bool ready = false;
|
|
||||||
pthread_mutex_lock(&encoder->encoder_group->mutex);
|
pthread_mutex_lock(&encoder->encoder_group->mutex);
|
||||||
encoder->encoder_group->encoders_started += 1;
|
encoder->encoder_group->num_encoders_started += 1;
|
||||||
ready = encoder->encoder_group->encoders_started ==
|
bool ready = encoder->encoder_group->num_encoders_started >=
|
||||||
encoder->encoder_group->encoders_added;
|
encoder->encoder_group->encoders.num;
|
||||||
pthread_mutex_unlock(&encoder->encoder_group->mutex);
|
pthread_mutex_unlock(&encoder->encoder_group->mutex);
|
||||||
if (ready)
|
if (ready)
|
||||||
add_ready_encoder_group(encoder);
|
add_ready_encoder_group(encoder);
|
||||||
|
@ -338,6 +337,7 @@ static void add_connection(struct obs_encoder *encoder)
|
||||||
set_encoder_active(encoder, true);
|
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)
|
static void remove_connection(struct obs_encoder *encoder, bool shutdown)
|
||||||
{
|
{
|
||||||
if (encoder->info.type == OBS_ENCODER_AUDIO) {
|
if (encoder->info.type == OBS_ENCODER_AUDIO) {
|
||||||
|
@ -353,10 +353,8 @@ static void remove_connection(struct obs_encoder *encoder, bool shutdown)
|
||||||
|
|
||||||
if (encoder->encoder_group) {
|
if (encoder->encoder_group) {
|
||||||
pthread_mutex_lock(&encoder->encoder_group->mutex);
|
pthread_mutex_lock(&encoder->encoder_group->mutex);
|
||||||
encoder->encoder_group->encoders_started -= 1;
|
if (--encoder->encoder_group->num_encoders_started == 0)
|
||||||
if (encoder->encoder_group->encoders_started == 0)
|
|
||||||
encoder->encoder_group->start_timestamp = 0;
|
encoder->encoder_group->start_timestamp = 0;
|
||||||
|
|
||||||
pthread_mutex_unlock(&encoder->encoder_group->mutex);
|
pthread_mutex_unlock(&encoder->encoder_group->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,6 +365,8 @@ static void remove_connection(struct obs_encoder *encoder, bool shutdown)
|
||||||
* up again */
|
* up again */
|
||||||
if (shutdown)
|
if (shutdown)
|
||||||
obs_encoder_shutdown(encoder);
|
obs_encoder_shutdown(encoder);
|
||||||
|
encoder->initialized = false;
|
||||||
|
|
||||||
set_encoder_active(encoder, false);
|
set_encoder_active(encoder, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,22 +395,7 @@ static void obs_encoder_actually_destroy(obs_encoder_t *encoder)
|
||||||
blog(LOG_DEBUG, "encoder '%s' destroyed",
|
blog(LOG_DEBUG, "encoder '%s' destroyed",
|
||||||
encoder->context.name);
|
encoder->context.name);
|
||||||
|
|
||||||
if (encoder->encoder_group) {
|
obs_encoder_set_group(encoder, NULL);
|
||||||
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);
|
free_audio_buffers(encoder);
|
||||||
|
|
||||||
|
@ -801,14 +786,20 @@ void obs_encoder_start(obs_encoder_t *encoder,
|
||||||
pthread_mutex_unlock(&encoder->init_mutex);
|
pthread_mutex_unlock(&encoder->init_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool obs_encoder_stop_internal(
|
void obs_encoder_stop(obs_encoder_t *encoder,
|
||||||
obs_encoder_t *encoder,
|
void (*new_packet)(void *param,
|
||||||
void (*new_packet)(void *param, struct encoder_packet *packet),
|
struct encoder_packet *packet),
|
||||||
void *param)
|
void *param)
|
||||||
{
|
{
|
||||||
bool last = false;
|
bool last = false;
|
||||||
size_t idx;
|
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);
|
pthread_mutex_lock(&encoder->callbacks_mutex);
|
||||||
|
|
||||||
idx = get_callback_idx(encoder, new_packet, param);
|
idx = get_callback_idx(encoder, new_packet, param);
|
||||||
|
@ -821,34 +812,32 @@ static inline bool obs_encoder_stop_internal(
|
||||||
|
|
||||||
if (last) {
|
if (last) {
|
||||||
remove_connection(encoder, true);
|
remove_connection(encoder, true);
|
||||||
encoder->initialized = false;
|
pthread_mutex_unlock(&encoder->init_mutex);
|
||||||
|
|
||||||
if (encoder->destroy_on_stop) {
|
struct obs_encoder_group *group = encoder->encoder_group;
|
||||||
pthread_mutex_unlock(&encoder->init_mutex);
|
|
||||||
|
if (encoder->destroy_on_stop)
|
||||||
obs_encoder_actually_destroy(encoder);
|
obs_encoder_actually_destroy(encoder);
|
||||||
return true;
|
|
||||||
|
/* 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* init_mutex already unlocked */
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
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)
|
|
||||||
{
|
|
||||||
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)
|
const char *obs_encoder_get_codec(const obs_encoder_t *encoder)
|
||||||
|
@ -1345,7 +1334,6 @@ void full_stop(struct obs_encoder *encoder)
|
||||||
pthread_mutex_unlock(&encoder->callbacks_mutex);
|
pthread_mutex_unlock(&encoder->callbacks_mutex);
|
||||||
|
|
||||||
remove_connection(encoder, false);
|
remove_connection(encoder, false);
|
||||||
encoder->initialized = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1461,7 +1449,7 @@ static void receive_video(void *param, struct video_data *frame)
|
||||||
struct encoder_frame enc_frame;
|
struct encoder_frame enc_frame;
|
||||||
|
|
||||||
if (encoder->encoder_group && !encoder->start_ts) {
|
if (encoder->encoder_group && !encoder->start_ts) {
|
||||||
struct encoder_group *group = encoder->encoder_group;
|
struct obs_encoder_group *group = encoder->encoder_group;
|
||||||
bool ready = false;
|
bool ready = false;
|
||||||
pthread_mutex_lock(&group->mutex);
|
pthread_mutex_lock(&group->mutex);
|
||||||
ready = group->start_timestamp == frame->timestamp;
|
ready = group->start_timestamp == frame->timestamp;
|
||||||
|
@ -2058,132 +2046,99 @@ uint32_t obs_encoder_get_roi_increment(const obs_encoder_t *encoder)
|
||||||
return encoder->roi_increment;
|
return encoder->roi_increment;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool obs_encoder_group_keyframe_aligned_encoders(
|
bool obs_encoder_set_group(obs_encoder_t *encoder, obs_encoder_group_t *group)
|
||||||
obs_encoder_t *encoder, obs_encoder_t *encoder_to_be_grouped)
|
|
||||||
{
|
{
|
||||||
if (!obs_encoder_valid(encoder,
|
if (!obs_encoder_valid(encoder, "obs_encoder_set_group"))
|
||||||
"obs_encoder_group_keyframe_aligned_encoders") ||
|
|
||||||
!obs_encoder_valid(encoder_to_be_grouped,
|
|
||||||
"obs_encoder_group_keyframe_aligned_encoders"))
|
|
||||||
return false;
|
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,
|
blog(LOG_ERROR,
|
||||||
"obs_encoder_group_keyframe_aligned_encoders: encoder '%s' "
|
"obs_encoder_set_group: encoder '%s' is already active",
|
||||||
"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));
|
obs_encoder_get_name(encoder));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool unlock = false;
|
if (encoder->encoder_group) {
|
||||||
if (!encoder->encoder_group) {
|
struct obs_encoder_group *old_group = encoder->encoder_group;
|
||||||
encoder->encoder_group = bzalloc(sizeof(struct encoder_group));
|
pthread_mutex_lock(&old_group->mutex);
|
||||||
if (pthread_mutex_init(&encoder->encoder_group->mutex, NULL) <
|
if (old_group->num_encoders_started) {
|
||||||
0) {
|
pthread_mutex_unlock(&old_group->mutex);
|
||||||
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,
|
blog(LOG_ERROR,
|
||||||
"obs_encoder_group_keyframe_aligned_encoders: "
|
"obs_encoder_set_group: encoder '%s' existing group has started 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));
|
obs_encoder_get_name(encoder));
|
||||||
pthread_mutex_unlock(&encoder->encoder_group->mutex);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
da_erase_item(old_group->encoders, &encoder);
|
||||||
|
obs_encoder_release(encoder);
|
||||||
|
pthread_mutex_unlock(&old_group->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
encoder->encoder_group->encoders_added += 1;
|
if (!group)
|
||||||
encoder_to_be_grouped->encoder_group = encoder->encoder_group;
|
return true;
|
||||||
|
|
||||||
if (unlock)
|
pthread_mutex_lock(&group->mutex);
|
||||||
pthread_mutex_unlock(&encoder->encoder_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);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool obs_encoder_group_remove_keyframe_aligned_encoder(
|
obs_encoder_group_t *obs_encoder_group_create()
|
||||||
obs_encoder_t *encoder, obs_encoder_t *encoder_to_be_ungrouped)
|
|
||||||
{
|
{
|
||||||
if (!obs_encoder_valid(
|
struct obs_encoder_group *group =
|
||||||
encoder,
|
bzalloc(sizeof(struct obs_encoder_group));
|
||||||
"obs_encoder_group_remove_keyframe_aligned_encoder") ||
|
|
||||||
!obs_encoder_valid(
|
|
||||||
encoder_to_be_ungrouped,
|
|
||||||
"obs_encoder_group_remove_keyframe_aligned_encoder"))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (obs_encoder_active(encoder) ||
|
pthread_mutex_init_value(&group->mutex);
|
||||||
obs_encoder_active(encoder_to_be_ungrouped)) {
|
if (pthread_mutex_init(&group->mutex, NULL) != 0) {
|
||||||
blog(LOG_ERROR,
|
bfree(group);
|
||||||
"obs_encoder_group_remove_keyframe_aligned_encoder: encoders are active, "
|
return NULL;
|
||||||
"could not ungroup encoder '%s' from '%s'",
|
|
||||||
obs_encoder_get_name(encoder_to_be_ungrouped),
|
|
||||||
obs_encoder_get_name(encoder));
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encoder->encoder_group != encoder_to_be_ungrouped->encoder_group) {
|
return group;
|
||||||
blog(LOG_ERROR,
|
}
|
||||||
"obs_encoder_group_remove_keyframe_aligned_encoder: "
|
|
||||||
"encoder '%s' does not belong to the same group as encoder '%s'",
|
void obs_encoder_group_actually_destroy(obs_encoder_group_t *group)
|
||||||
obs_encoder_get_name(encoder_to_be_ungrouped),
|
{
|
||||||
obs_encoder_get_name(encoder));
|
for (size_t i = 0; i < group->encoders.num; i++) {
|
||||||
return false;
|
struct obs_encoder *encoder = group->encoders.array[i];
|
||||||
}
|
encoder->encoder_group = NULL;
|
||||||
|
obs_encoder_release(encoder);
|
||||||
struct encoder_group *current_group = encoder->encoder_group;
|
}
|
||||||
struct encoder_group *free_group = NULL;
|
|
||||||
|
da_free(group->encoders);
|
||||||
pthread_mutex_lock(¤t_group->mutex);
|
pthread_mutex_unlock(&group->mutex);
|
||||||
|
pthread_mutex_destroy(&group->mutex);
|
||||||
if (current_group->encoders_started != 0) {
|
|
||||||
blog(LOG_ERROR,
|
bfree(group);
|
||||||
"obs_encoder_group_remove_keyframe_aligned_encoder: "
|
}
|
||||||
"could not ungroup encoder '%s' from '%s' while "
|
|
||||||
"the group contains active encoders",
|
void obs_encoder_group_destroy(obs_encoder_group_t *group)
|
||||||
obs_encoder_get_name(encoder_to_be_ungrouped),
|
{
|
||||||
obs_encoder_get_name(encoder));
|
if (!group)
|
||||||
pthread_mutex_unlock(¤t_group->mutex);
|
return;
|
||||||
return false;
|
|
||||||
}
|
pthread_mutex_lock(&group->mutex);
|
||||||
|
|
||||||
current_group->encoders_added -= 1;
|
if (group->num_encoders_started) {
|
||||||
encoder_to_be_ungrouped->encoder_group = NULL;
|
group->destroy_on_stop = true;
|
||||||
if (current_group->encoders_added == 1) {
|
pthread_mutex_unlock(&group->mutex);
|
||||||
free_group = current_group;
|
return;
|
||||||
encoder->encoder_group = NULL;
|
}
|
||||||
}
|
|
||||||
pthread_mutex_unlock(¤t_group->mutex);
|
obs_encoder_group_actually_destroy(group);
|
||||||
|
|
||||||
if (free_group) {
|
|
||||||
pthread_mutex_destroy(&free_group->mutex);
|
|
||||||
bfree(free_group);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1242,10 +1242,15 @@ struct encoder_callback {
|
||||||
void *param;
|
void *param;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct encoder_group {
|
struct obs_encoder_group {
|
||||||
pthread_mutex_t mutex;
|
pthread_mutex_t mutex;
|
||||||
uint32_t encoders_added;
|
/* allows group to be destroyed even if some encoders are active */
|
||||||
uint32_t encoders_started;
|
bool destroy_on_stop;
|
||||||
|
|
||||||
|
/* holds strong references to all encoders */
|
||||||
|
DARRAY(struct obs_encoder *) encoders;
|
||||||
|
|
||||||
|
uint32_t num_encoders_started;
|
||||||
uint64_t start_timestamp;
|
uint64_t start_timestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1314,7 +1319,7 @@ struct obs_encoder {
|
||||||
uint64_t start_ts;
|
uint64_t start_ts;
|
||||||
|
|
||||||
/* track encoders that are part of a gop-aligned multi track group */
|
/* track encoders that are part of a gop-aligned multi track group */
|
||||||
struct encoder_group *encoder_group;
|
struct obs_encoder_group *encoder_group;
|
||||||
|
|
||||||
pthread_mutex_t outputs_mutex;
|
pthread_mutex_t outputs_mutex;
|
||||||
DARRAY(obs_output_t *) outputs;
|
DARRAY(obs_output_t *) outputs;
|
||||||
|
|
|
@ -2226,7 +2226,7 @@ check_encoder_group_keyframe_alignment(obs_output_t *output,
|
||||||
|
|
||||||
pthread_mutex_lock(&packet->encoder->encoder_group->mutex);
|
pthread_mutex_lock(&packet->encoder->encoder_group->mutex);
|
||||||
insert_data.required_tracks =
|
insert_data.required_tracks =
|
||||||
packet->encoder->encoder_group->encoders_started;
|
packet->encoder->encoder_group->num_encoders_started;
|
||||||
pthread_mutex_unlock(&packet->encoder->encoder_group->mutex);
|
pthread_mutex_unlock(&packet->encoder->encoder_group->mutex);
|
||||||
|
|
||||||
da_insert(output->keyframe_group_tracking, idx, &insert_data);
|
da_insert(output->keyframe_group_tracking, idx, &insert_data);
|
||||||
|
|
|
@ -92,7 +92,7 @@ static void *gpu_encode_thread(void *data)
|
||||||
pkt.encoder = encoder;
|
pkt.encoder = encoder;
|
||||||
|
|
||||||
if (encoder->encoder_group && !encoder->start_ts) {
|
if (encoder->encoder_group && !encoder->start_ts) {
|
||||||
struct encoder_group *group =
|
struct obs_encoder_group *group =
|
||||||
encoder->encoder_group;
|
encoder->encoder_group;
|
||||||
bool ready = false;
|
bool ready = false;
|
||||||
pthread_mutex_lock(&group->mutex);
|
pthread_mutex_lock(&group->mutex);
|
||||||
|
|
|
@ -932,12 +932,13 @@ static inline void video_sleep(struct obs_core_video *video, uint64_t *p_time,
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (encoder->encoder_group) {
|
if (encoder->encoder_group) {
|
||||||
struct encoder_group *group = encoder->encoder_group;
|
struct obs_encoder_group *group =
|
||||||
|
encoder->encoder_group;
|
||||||
pthread_mutex_lock(&group->mutex);
|
pthread_mutex_lock(&group->mutex);
|
||||||
if (group->encoders_added == group->encoders_started &&
|
if (group->num_encoders_started >=
|
||||||
!group->start_timestamp) {
|
group->encoders.num &&
|
||||||
|
!group->start_timestamp)
|
||||||
group->start_timestamp = *p_time;
|
group->start_timestamp = *p_time;
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&group->mutex);
|
pthread_mutex_unlock(&group->mutex);
|
||||||
}
|
}
|
||||||
obs_encoder_release(encoder);
|
obs_encoder_release(encoder);
|
||||||
|
|
|
@ -878,13 +878,13 @@ static void obs_free_video(void)
|
||||||
obs_free_video_mix(video);
|
obs_free_video_mix(video);
|
||||||
obs->video.mixes.array[i] = NULL;
|
obs->video.mixes.array[i] = NULL;
|
||||||
}
|
}
|
||||||
|
da_free(obs->video.mixes);
|
||||||
if (num_views > 0)
|
if (num_views > 0)
|
||||||
blog(LOG_WARNING, "Number of remaining views: %ld", num_views);
|
blog(LOG_WARNING, "Number of remaining views: %ld", num_views);
|
||||||
pthread_mutex_unlock(&obs->video.mixes_mutex);
|
pthread_mutex_unlock(&obs->video.mixes_mutex);
|
||||||
|
|
||||||
pthread_mutex_destroy(&obs->video.mixes_mutex);
|
pthread_mutex_destroy(&obs->video.mixes_mutex);
|
||||||
pthread_mutex_init_value(&obs->video.mixes_mutex);
|
pthread_mutex_init_value(&obs->video.mixes_mutex);
|
||||||
da_free(obs->video.mixes);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < obs->video.ready_encoder_groups.num; i++) {
|
for (size_t i = 0; i < obs->video.ready_encoder_groups.num; i++) {
|
||||||
obs_weak_encoder_release(
|
obs_weak_encoder_release(
|
||||||
|
|
16
libobs/obs.h
16
libobs/obs.h
|
@ -46,6 +46,7 @@ struct obs_scene;
|
||||||
struct obs_scene_item;
|
struct obs_scene_item;
|
||||||
struct obs_output;
|
struct obs_output;
|
||||||
struct obs_encoder;
|
struct obs_encoder;
|
||||||
|
struct obs_encoder_group;
|
||||||
struct obs_service;
|
struct obs_service;
|
||||||
struct obs_module;
|
struct obs_module;
|
||||||
struct obs_fader;
|
struct obs_fader;
|
||||||
|
@ -59,6 +60,7 @@ typedef struct obs_scene obs_scene_t;
|
||||||
typedef struct obs_scene_item obs_sceneitem_t;
|
typedef struct obs_scene_item obs_sceneitem_t;
|
||||||
typedef struct obs_output obs_output_t;
|
typedef struct obs_output obs_output_t;
|
||||||
typedef struct obs_encoder obs_encoder_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_service obs_service_t;
|
||||||
typedef struct obs_module obs_module_t;
|
typedef struct obs_module obs_module_t;
|
||||||
typedef struct obs_fader obs_fader_t;
|
typedef struct obs_fader obs_fader_t;
|
||||||
|
@ -2607,10 +2609,16 @@ EXPORT void obs_encoder_set_last_error(obs_encoder_t *encoder,
|
||||||
|
|
||||||
EXPORT uint64_t obs_encoder_get_pause_offset(const obs_encoder_t *encoder);
|
EXPORT uint64_t obs_encoder_get_pause_offset(const obs_encoder_t *encoder);
|
||||||
|
|
||||||
EXPORT bool obs_encoder_group_keyframe_aligned_encoders(
|
/**
|
||||||
obs_encoder_t *encoder, obs_encoder_t *encoder_to_be_grouped);
|
* Creates an "encoder group", allowing synchronized startup of encoders within
|
||||||
EXPORT bool obs_encoder_group_remove_keyframe_aligned_encoder(
|
* the group. Encoder groups are single owner, and hold strong references to
|
||||||
obs_encoder_t *encoder, obs_encoder_t *encoder_to_be_ungrouped);
|
* 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);
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------- */
|
/* ------------------------------------------------------------------------- */
|
||||||
/* Stream Services */
|
/* Stream Services */
|
||||||
|
|
|
@ -40,6 +40,7 @@ NVENC.8bitUnsupportedHdr="OBS does not support 8-bit output of Rec. 2100."
|
||||||
NVENC.I010Unsupported="NVENC does not support I010. Use P010 instead."
|
NVENC.I010Unsupported="NVENC does not support I010. Use P010 instead."
|
||||||
NVENC.10bitUnsupported="Cannot perform 10-bit encode on this encoder."
|
NVENC.10bitUnsupported="Cannot perform 10-bit encode on this encoder."
|
||||||
NVENC.16bitUnsupported="Cannot perform 16-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.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.p1="P1: Fastest (Lowest Quality)"
|
||||||
NVENC.Preset2.p2="P2: Faster (Lower Quality)"
|
NVENC.Preset2.p2="P2: Faster (Lower Quality)"
|
||||||
|
|
|
@ -1371,12 +1371,19 @@ static bool init_encoder(struct nvenc_data *enc, enum codec_type codec,
|
||||||
int bf = (int)obs_data_get_int(settings, "bf");
|
int bf = (int)obs_data_get_int(settings, "bf");
|
||||||
const bool support_10bit =
|
const bool support_10bit =
|
||||||
nv_get_cap(enc, NV_ENC_CAPS_SUPPORT_10BIT_ENCODE);
|
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);
|
const int bf_max = nv_get_cap(enc, NV_ENC_CAPS_NUM_MAX_BFRAMES);
|
||||||
|
|
||||||
video_t *video = obs_encoder_video(enc->encoder);
|
video_t *video = obs_encoder_video(enc->encoder);
|
||||||
const struct video_output_info *voi = video_output_get_info(video);
|
const struct video_output_info *voi = video_output_get_info(video);
|
||||||
enc->in_format = get_preferred_format(voi->format);
|
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) {
|
if (is_10_bit(enc) && !support_10bit) {
|
||||||
NV_FAIL(obs_module_text("NVENC.10bitUnsupported"));
|
NV_FAIL(obs_module_text("NVENC.10bitUnsupported"));
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -99,6 +99,7 @@ VADisplay vaapi_open_device(int *fd, const char *device_path,
|
||||||
if (va_status != VA_STATUS_SUCCESS) {
|
if (va_status != VA_STATUS_SUCCESS) {
|
||||||
blog(LOG_ERROR, "VAAPI: Failed to initialize display in %s",
|
blog(LOG_ERROR, "VAAPI: Failed to initialize display in %s",
|
||||||
func_name);
|
func_name);
|
||||||
|
vaapi_close_device(fd, va_dpy);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1718,6 +1718,10 @@ static size_t mp4_write_trak(struct mp4_mux *mux, struct mp4_track *track,
|
||||||
struct serializer *s = mux->serializer;
|
struct serializer *s = mux->serializer;
|
||||||
int64_t start = serializer_get_pos(s);
|
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");
|
write_box(s, 0, "trak");
|
||||||
|
|
||||||
// tkhd
|
// tkhd
|
||||||
|
@ -2409,7 +2413,7 @@ static void write_packets(struct mp4_mux *mux, struct mp4_track *track)
|
||||||
struct serializer *s = mux->serializer;
|
struct serializer *s = mux->serializer;
|
||||||
|
|
||||||
size_t count = track->packets.size / sizeof(struct encoder_packet);
|
size_t count = track->packets.size / sizeof(struct encoder_packet);
|
||||||
if (!count)
|
if (!count || !track->fragment_samples.num)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
struct chunk *chk = da_push_back_new(track->chunks);
|
struct chunk *chk = da_push_back_new(track->chunks);
|
||||||
|
|
|
@ -112,20 +112,23 @@ static inline void ts_offset_update(struct mp4_output *out,
|
||||||
struct encoder_packet *packet)
|
struct encoder_packet *packet)
|
||||||
{
|
{
|
||||||
int64_t *offset;
|
int64_t *offset;
|
||||||
|
int64_t ts;
|
||||||
bool *found;
|
bool *found;
|
||||||
|
|
||||||
if (packet->type == OBS_ENCODER_VIDEO) {
|
if (packet->type == OBS_ENCODER_VIDEO) {
|
||||||
offset = &out->video_pts_offsets[packet->track_idx];
|
offset = &out->video_pts_offsets[packet->track_idx];
|
||||||
found = &out->found_video[packet->track_idx];
|
found = &out->found_video[packet->track_idx];
|
||||||
|
ts = packet->pts;
|
||||||
} else {
|
} else {
|
||||||
offset = &out->audio_dts_offsets[packet->track_idx];
|
offset = &out->audio_dts_offsets[packet->track_idx];
|
||||||
found = &out->found_audio[packet->track_idx];
|
found = &out->found_audio[packet->track_idx];
|
||||||
|
ts = packet->dts;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*found)
|
if (*found)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
*offset = packet->dts;
|
*offset = ts;
|
||||||
*found = true;
|
*found = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -309,7 +309,9 @@ static inline void set_param(struct obs_x264 *obsx264, struct obs_option option)
|
||||||
if (strcmp(name, "preset") != 0 && strcmp(name, "profile") != 0 &&
|
if (strcmp(name, "preset") != 0 && strcmp(name, "profile") != 0 &&
|
||||||
strcmp(name, "tune") != 0 && strcmp(name, "fps") != 0 &&
|
strcmp(name, "tune") != 0 && strcmp(name, "fps") != 0 &&
|
||||||
strcmp(name, "force-cfr") != 0 && strcmp(name, "width") != 0 &&
|
strcmp(name, "force-cfr") != 0 && strcmp(name, "width") != 0 &&
|
||||||
strcmp(name, "height") != 0 && strcmp(name, "opencl") != 0) {
|
strcmp(name, "height") != 0 && strcmp(name, "opencl") != 0 &&
|
||||||
|
strcmp(name, "stats") != 0 && strcmp(name, "qpfile") != 0 &&
|
||||||
|
strcmp(name, "pass") != 0) {
|
||||||
if (strcmp(option.name, OPENCL_ALIAS) == 0)
|
if (strcmp(option.name, OPENCL_ALIAS) == 0)
|
||||||
name = "opencl";
|
name = "opencl";
|
||||||
if (x264_param_parse(&obsx264->params, name, val) != 0)
|
if (x264_param_parse(&obsx264->params, name, val) != 0)
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"$schema": "schema/package-schema.json",
|
"$schema": "schema/package-schema.json",
|
||||||
"url": "https://obsproject.com/obs2_update/rtmp-services/v5",
|
"url": "https://obsproject.com/obs2_update/rtmp-services/v5",
|
||||||
"version": 255,
|
"version": 256,
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"name": "services.json",
|
"name": "services.json",
|
||||||
"version": 255
|
"version": 256
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2922,6 +2922,103 @@
|
||||||
"keyint": 2,
|
"keyint": 2,
|
||||||
"x264opts": "scenecut=0"
|
"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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,10 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
enum AVHWDeviceType hw_priority[] = {
|
enum AVHWDeviceType hw_priority[] = {
|
||||||
AV_HWDEVICE_TYPE_CUDA, AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_DXVA2,
|
AV_HWDEVICE_TYPE_D3D11VA,
|
||||||
AV_HWDEVICE_TYPE_QSV, AV_HWDEVICE_TYPE_NONE,
|
AV_HWDEVICE_TYPE_DXVA2,
|
||||||
|
AV_HWDEVICE_TYPE_QSV,
|
||||||
|
AV_HWDEVICE_TYPE_NONE,
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool has_hw_type(const AVCodec *c, enum AVHWDeviceType type)
|
static bool has_hw_type(const AVCodec *c, enum AVHWDeviceType type)
|
||||||
|
|
Loading…
Reference in a new issue