Compare commits

...

33 commits

Author SHA1 Message Date
Exeldro 56891231ee
Merge ad910b674e into 9d67bf2662 2024-06-27 12:22:49 +02:00
Ryan Foster 9d67bf2662 Revert "plugins/win-dshow: Add CUDA decoder"
This reverts commit ce4c99be4e.

This was causing infinitely looping log errors in systems with no
CUDA-capable hardware when hardware decoding was enabled on video
capture devices with custom config enabled.
2024-06-26 18:43:09 -04:00
tt2468 8892edde05 libobs: Merge obs_encoder_stop() and ..._stop_internal()
There is no longer any need for them to be separate functions. This is
just code cleanup.
2024-06-26 16:42:05 -04:00
tt2468 06291c7201 libobs: Fix race when to-be-destroyed encoder group finishes stopping
Fixes a crash when the following steps occur:
- Encoder group created and then started with encoders, only the group
owning encoder refs
- Encoder group destroy called: group has `destroy_on_stop` set
without actual destroy
- Outputs holding encoders from stopping are stopped
- `remove_connection()` function destroys encoder group, releasing
encoders and causing the currently processed encoder to be destroyed
early
- parent scopes try to access destroyed encoder pointer and crash

This change moves some logic around to improve the release/destruct
order of `obs_encoder_stop()` to fix the race above.

Note: Cases of encoder errors will not destruct the group if it has
`destroy_on_stop` set, as the encoder is also not destroyed if it also
is set to destroy on stop. This part of the code should be revisited
at a later date and fixed up to prevent memory leaks.
2024-06-26 16:42:05 -04:00
tt2468 fb5bbc8575 libobs: Set encoder initialized call closer to shutdown
This is mainly code cleanup.
2024-06-26 16:42:05 -04:00
Ruwen Hahn 7d19add10b UI: Display dialog for multitrack video output audio channels mismatch 2024-06-26 16:10:15 -04:00
Vainock 4830d6903e UI: Fix capitalization of 'OBS' and 'RTMP' 2024-06-26 15:01:01 -04:00
Warchamp7 47fb194223 UI: Adjust Yami (Classic) styling 2024-06-26 14:58:13 -04:00
derrod edcda5a825 obs-x264: Ignore stats/qp file and multipass options 2024-06-26 13:58:08 -04:00
derrod 2e6e79b4f5 obs-outputs: Skip trak box if track has no data 2024-06-26 13:57:59 -04:00
derrod b34fbb116e obs-ffmpeg: Check if current NVENC configuration supports 4:4:4 encode 2024-06-26 13:57:43 -04:00
Penwywern 4517918c7a cmake: Fix FFmpeg version regex 2024-06-26 12:26:56 -04:00
derrod 6cc0e2b803 obs-outputs: Fix file splitting ts offset using video DTS instead of PTS 2024-06-25 23:36:33 -04:00
derrod ed2478535f obs-outputs: Do not create MP4 track chunks without samples 2024-06-25 23:36:33 -04:00
Ed Maste fd34bab615 UI: Link Qt::DBus on FreeBSD
As with Linux we need to link Qt::DBus on FreeBSD now that there's a
HighContrastEnabled implementation that makes use of it.

Fixes: 41ba8bdfdd ("UI: Add HighContrastEnabled implementation fo...")
2024-06-25 22:37:14 -04:00
Alex Luccisano 5f98d34e2c UI: Fix multitrack-video audio track index
Fix a minor oversight from a recent commit. Audio
track indexing in the UI is 1-based while underlying
code uses 0-based indexing.
2024-06-20 06:44:33 +02:00
tt2468 6c389271b3 obs-ffmpeg: Close VAAPI device on vaInitialize fail
On some systems (eg. mine), VAAPI fails on vaInitialize. Valgrind was
able to spot that the device was not being closed, and it appears to
have been correct. This fixes a memory leak.
2024-06-19 19:46:05 -07:00
Fabien Lavocat 6457d7b429 rtmp-services: Add Dolby Millicast 2024-06-19 13:13:16 -04:00
gxalpha 92352f18b8 cmake: Add obs-config.h to libobs headers
Adds the header to make it findable in IDEs
2024-06-19 13:08:30 -04:00
Ed Maste a189489dd2 CI: Fix FreeBSD package installation
FreeBSD's package tool is pkg(8), and install is the command verb passed
to it.

Fixes: fb4d65875e ("CI: Update Linux build scripts to use CMake p...")
2024-06-19 13:06:10 -04:00
Ruwen Hahn aa096e2ad0 UI: Disable multitrack video settings on non-win32 platforms 2024-06-19 12:50:34 -04:00
Ruwen Hahn 9d1ac8816e UI: Add supported codecs to GetClientConfiguration request 2024-06-19 12:27:13 -04:00
Ruwen Hahn 8a8019db3f UI: Only cache multitrack config URL startup argument 2024-06-19 12:08:52 -04:00
derrod a9b5968552 CI: Add tag subject to Windows patch notes 2024-06-19 10:42:11 -04:00
derrod fc05ca601a CI: Update Windows patch creation bouf version 2024-06-19 10:42:11 -04:00
Ryan Foster 32b53ea936 CI: Fix Windows Patches action release notes generation
By not specifying a checkout ref, actions/checkout does a second
checkout when this action is invoked by the Publish workflow (release
event). When this happens, it checks out the commit object from the tag,
and git can no longer locate the annotated tag that contains the release
notes. This then causes the release notes to be just the commit message
and not the annotated tag message.

The sparkle-appcast action in general and this actioo when invoked via
the Dispatch workflow do not have this issue, and they both specify the
tag as the ref.
2024-06-19 10:37:11 -04:00
Ruwen Hahn fb3e571ce8 UI: Use advanced mode audio track in multitrack video output 2024-06-18 14:13:42 -04:00
Ryan Foster 48b1298faf UI: Fix parsing of Multitrack Video stream key query parameters
The code was checking stream_key, but stream_key could be the
user-supplied value (in_stream_key) or the server-supplied value
(endpoint.authentication). The server-supplied value may lack the query
parameters set in the user-supplied value. To ensure that user-specified
query parameters (such as bandwidthtest) are passed along, parse the
user-supplied key instead of the server-supplied key.
2024-06-18 12:39:56 -04:00
derrod 608d3bfc26 UI: Set default container for beta builds to hybrid MP4 2024-06-17 12:12:20 -04:00
tt2468 e215502b62 libobs, UI: Normalize encoder group API
Modifies the encoder group API added previously to better follow the
existing libobs API naming paradigms. This also produces much more
readable code, and allows a few small benefits like only needing to
hold a reference to the encoder group, instead of every encoder.
2024-06-17 08:20:01 -07:00
tt2468 751dbdad10 libobs: Update video encoder group struct member names
Updates the struct member names of the video encoder group to be more
like what is commonly seen in OBS elsewhere.
2024-06-17 08:20:01 -07:00
Ryan Foster 9d88b632ae UI: Add step value to Multitrack Video maximum bitrate control
All other bitrate setting fields in the UI have a single-step value of
50. Without setting this, the single-step value is 1, which makes using
the scroll wheel a bit tedious.

While this could arguably be higher, let's make this consistent first
and then consider changing the values across the application later.
2024-06-17 10:36:17 -04:00
Exeldro ad910b674e libobs: Fix crash when mix is NULL 2024-05-26 08:38:36 +02:00
35 changed files with 593 additions and 252 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,6 +25,7 @@ runs:
with:
path: "repo"
fetch-depth: 0
ref: ${{ inputs.tagName }}
- name: Download Release Artifact
shell: pwsh
@ -40,9 +41,9 @@ runs:
- name: Setup bouf
shell: pwsh
env:
BOUF_TAG: 'v0.6.3'
BOUF_HASH: '7f1d266467620aa553a705391ee06128e8ee14af66129a0e64a282997fb6fd83'
BOUF_NSIS_HASH: 'a234126de89f122b6a552df3416de3eabcb4195217626c7f4eaec71b20fe36eb'
BOUF_TAG: 'v0.6.4'
BOUF_HASH: 'aca6810e741dc38ff843fab7b25a0ad8570ee84f5595132cf0cc4a5b0131b4c4'
BOUF_NSIS_HASH: 'ed453784486556bd959d56743a8478ad3f68fe0305e9b43ac19d8771d0515257'
GH_TOKEN: ${{ github.token }}
run: |
# Download bouf release
@ -86,7 +87,10 @@ runs:
run: |
# Release notes are just the tag body on Windows
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
shell: pwsh

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 procstat)
target_link_libraries(obs-studio PRIVATE Qt::GuiPrivate Qt::DBus procstat)
target_sources(obs-studio PRIVATE system-info-posix.cpp)

View file

@ -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,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.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

@ -477,7 +477,7 @@ OBSDock > QWidget {
}
#transitionsFrame {
padding: 4px 8px;
padding: var(--padding_large);
}
OBSDock QLabel {
@ -827,7 +827,7 @@ QComboBox::drop-down,
QDateTimeEdit::drop-down {
border:none;
border-left: 1px solid var(--grey6);
width: 32px;
width: var(--input_height);
}
QComboBox::down-arrow,
@ -923,7 +923,7 @@ QDoubleSpinBox::up-button {
subcontrol-origin: padding;
subcontrol-position: top right; /* position at the top right corner */
width: 32px;
width: var(--input_height);
height: var(--spinbox_button_height);
border-left: 1px solid var(--grey6);
border-bottom: 1px solid transparent;
@ -936,7 +936,7 @@ QDoubleSpinBox::down-button {
subcontrol-origin: padding;
subcontrol-position: bottom right; /* position at the top right corner */
width: 32px;
width: var(--input_height);
height: var(--spinbox_button_height);
border-left: 1px solid var(--grey6);
border-top: 1px solid var(--grey6);
@ -1078,6 +1078,8 @@ 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);
@ -1104,7 +1106,9 @@ QPushButton:pressed:hover {
}
QToolButton:pressed,
QToolButton:pressed:hover {
QToolButton:pressed:hover,
QPushButton[toolButton="true"]:pressed,
QPushButton[toolButton="true"]:pressed:hover {
background-color: var(--button_bg_down);
border-color: var(--button_border);
}
@ -1275,7 +1279,6 @@ VolControl #volLabel {
#vMixerScrollArea VolControl #volLabel {
padding: var(--padding_base) 0px var(--padding_base);
min-width: var(--volume_slider_label);
max-width: var(--volume_slider_label);
margin-left: var(--padding_xlarge);
text-align: center;
}

View file

@ -11,7 +11,7 @@
--grey2: rgb(134,135,134);
--grey3: rgb(122,121,122);
--grey4: rgb(76,76,76);
--grey5: rgb(88,87,88);
--grey5: rgb(70,69,70);
--grey6: rgb(31,30,31);
--grey7: rgb(58,57,58);
--grey8: rgb(46,45,46);
@ -31,8 +31,12 @@
/* 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)));
@ -45,14 +49,14 @@
--border_radius_large: 2px;
--input_bg: var(--grey4);
--input_bg_hover: var(--grey5);
--input_bg_hover: var(--grey1);
--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(--grey5);
--input_border_hover: var(--grey1);
--input_border_focus: var(--grey6);
--spacing_input: var(--spacing_base);
@ -91,11 +95,17 @@ 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;
}
@ -104,6 +114,10 @@ QDockWidget > QWidget {
background: var(--bg_window);
}
#transitionsFrame {
padding: var(--padding_xlarge);
}
SceneTree::item,
SourceTreeItem {
border-width: 0px;
@ -193,12 +207,20 @@ 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);
@ -215,6 +237,33 @@ 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);
@ -255,3 +304,7 @@ VolumeMeter {
qproperty-minorTickColor: rgb(122,121,122); /* light */
qproperty-meterThickness: 3;
}
OBSBasicStats {
background: var(--bg_window);
}

View file

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

View file

@ -1679,6 +1679,9 @@
<property name="maximum">
<number>1000000</number>
</property>
<property name="singleStep">
<number>50</number>
</property>
<property name="value">
<number>0</number>
</property>

View file

@ -128,7 +128,8 @@ GoLiveApi::Config DownloadGoLiveConfig(QWidget *parent, QString url,
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();
for (int i = 0; i < args.length() - 1; i++) {
if (args[i] == "--config-url" &&
@ -136,11 +137,18 @@ QString MultitrackVideoAutoConfigURL(obs_service_t *service)
return args[i + 1];
}
}
OBSDataAutoRelease settings = obs_service_get_settings(service);
return obs_data_get_string(
settings, "multitrack_video_configuration_url");
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");
}
blog(LOG_INFO, "Go live URL: %s", url.toUtf8().constData());
return url;
}

View file

@ -24,6 +24,32 @@ constructGoLivePost(QString streamKey,
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;

View file

@ -109,8 +109,9 @@ using json = nlohmann::json;
struct Client {
string name = "obs-studio";
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 {

View file

@ -8,6 +8,7 @@
#include <obs-app.hpp>
#include <obs.hpp>
#include <remote-text.hpp>
#include <window-basic-main.hpp>
#include <algorithm>
#include <cinttypes>
@ -116,13 +117,15 @@ 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] : key_query.queryItems())
for (const auto &[key, value] : user_key_query.queryItems())
parsed_query.addQueryItem(key, value);
if (!go_live_config.meta.config_id.empty()) {
@ -329,11 +332,12 @@ struct 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,
std::vector<OBSEncoderAutoRelease> &audio_encoders,
std::vector<OBSEncoderAutoRelease> &video_encoders,
const char *audio_encoder_id,
std::shared_ptr<obs_encoder_group_t> &video_encoder_group,
const char *audio_encoder_id, size_t main_audio_mixer,
std::optional<size_t> vod_track_mixer);
static void SetupSignalHandlers(bool recording, MultitrackVideoOutput *self,
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_video_tracks,
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)
{
{
@ -461,11 +465,13 @@ void MultitrackVideoOutput::PrepareStreaming(
const auto &output_config = custom ? *custom : *go_live_config;
const auto &service_config = go_live_config ? *go_live_config : *custom;
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);
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 output = std::move(outputs.output);
auto recording_output = std::move(outputs.recording_output);
if (!output)
@ -496,13 +502,6 @@ 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) {
@ -515,7 +514,7 @@ void MultitrackVideoOutput::PrepareStreaming(
current_stream_dump_mutex};
current_stream_dump.emplace(OBSOutputObjects{
std::move(recording_output),
std::move(recording_video_encoders),
video_encoder_group,
std::move(recording_audio_encoders),
nullptr,
std::move(start_recording),
@ -528,7 +527,7 @@ void MultitrackVideoOutput::PrepareStreaming(
const std::lock_guard current_lock{current_mutex};
current.emplace(OBSOutputObjects{
std::move(output),
std::move(video_encoders),
video_encoder_group,
std::move(audio_encoders),
std::move(multitrack_video_service),
std::move(start_streaming),
@ -692,11 +691,10 @@ bool MultitrackVideoOutput::HandleIncompatibleSettings(
static bool
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)
{
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");
@ -704,6 +702,11 @@ 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(
@ -712,19 +715,16 @@ create_video_encoders(const GoLiveApi::Config &go_live_config,
if (!encoder)
return false;
if (!first_encoder)
first_encoder = encoder;
else
obs_encoder_group_keyframe_aligned_encoders(
first_encoder, encoder);
if (!obs_encoder_set_group(encoder, encoder_group.get()))
return false;
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,9 +732,47 @@ 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,
std::optional<size_t> vod_track_mixer)
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)
{
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;
@ -760,6 +798,10 @@ create_audio_encoders(const GoLiveApi::Config &go_live_config,
create_audio_encoder(encoder_name_buffer->array,
audio_encoder_id, settings,
mixer_idx);
sanitize_audio_channels(audio_encoder,
configs[i].channels);
obs_output_set_audio_encoder(output, audio_encoder,
output_encoder_index);
if (recording_output)
@ -772,7 +814,8 @@ create_audio_encoders(const GoLiveApi::Config &go_live_config,
};
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())
return;
@ -786,12 +829,84 @@ create_audio_encoders(const GoLiveApi::Config &go_live_config,
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(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,
std::vector<OBSEncoderAutoRelease> &audio_encoders,
std::vector<OBSEncoderAutoRelease> &video_encoders,
const char *audio_encoder_id,
std::shared_ptr<obs_encoder_group_t> &video_encoder_group,
const char *audio_encoder_id, size_t main_audio_mixer,
std::optional<size_t> vod_track_mixer)
{
@ -801,13 +916,19 @@ SetupOBSOutput(obs_data_t *dump_stream_to_file_config,
recording_output =
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))
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,
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)};
}

View file

@ -35,6 +35,7 @@ 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();
@ -53,7 +54,7 @@ public:
private:
struct OBSOutputObjects {
OBSOutputAutoRelease output_;
std::vector<OBSEncoderAutoRelease> video_encoders_;
std::shared_ptr<obs_encoder_group_t> video_encoder_group_;
std::vector<OBSEncoderAutoRelease> audio_encoders_;
OBSServiceAutoRelease multitrack_video_service_;
OBSSignal start_signal, stop_signal, deactivate_signal;

View file

@ -1138,7 +1138,7 @@ FutureHolder<bool> SimpleOutput::SetupStreaming(obs_service_t *service)
: std::nullopt;
auto holder = SetupMultitrackVideo(
service, GetSimpleAACEncoderForBitrate(audio_bitrate),
service, GetSimpleAACEncoderForBitrate(audio_bitrate), 0,
vod_track_mixer);
auto future =
PreventFutureDeadlock(holder.future)
@ -2286,9 +2286,13 @@ 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,
VodTrackMixerIdx(service));
auto holder =
SetupMultitrackVideo(service, audio_encoder_id,
static_cast<size_t>(streamTrackIndex),
VodTrackMixerIdx(service));
auto future =
PreventFutureDeadlock(holder.future)
.then(main, [&](std::optional<bool>
@ -2701,10 +2705,9 @@ 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,
std::optional<size_t> vod_track_mixer)
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)
{
if (!multitrackVideo)
return {[] {}, CreateFuture().then([] {
@ -2784,7 +2787,8 @@ BasicOutputHandler::SetupMultitrackVideo(obs_service_t *service,
audio_encoder_id.c_str(),
maximum_aggregate_bitrate,
maximum_video_tracks, custom_config,
stream_dump_config, vod_track_mixer);
stream_dump_config, main_audio_mixer,
vod_track_mixer);
} catch (const MultitrackVideoError &error) {
return error;
}

View file

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

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 "fragmented_mp4"
#define DEFAULT_CONTAINER "hybrid_mp4"
#endif
bool OBSBasic::InitBasicConfigDefaults()

View file

@ -6325,6 +6325,10 @@ void OBSBasicSettings::UpdateMultitrackVideo()
ui->enableMultitrackVideo->setChecked(false);
}
#ifndef _WIN32
available = available && MultitrackVideoDeveloperModeEnabled();
#endif
if (IsCustomService())
available = available && MultitrackVideoDeveloperModeEnabled();

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

@ -49,6 +49,7 @@ 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,11 +325,10 @@ 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->encoders_started += 1;
ready = encoder->encoder_group->encoders_started ==
encoder->encoder_group->encoders_added;
encoder->encoder_group->num_encoders_started += 1;
bool ready = encoder->encoder_group->num_encoders_started >=
encoder->encoder_group->encoders.num;
pthread_mutex_unlock(&encoder->encoder_group->mutex);
if (ready)
add_ready_encoder_group(encoder);
@ -338,6 +337,7 @@ 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,10 +353,8 @@ static void remove_connection(struct obs_encoder *encoder, bool shutdown)
if (encoder->encoder_group) {
pthread_mutex_lock(&encoder->encoder_group->mutex);
encoder->encoder_group->encoders_started -= 1;
if (encoder->encoder_group->encoders_started == 0)
if (--encoder->encoder_group->num_encoders_started == 0)
encoder->encoder_group->start_timestamp = 0;
pthread_mutex_unlock(&encoder->encoder_group->mutex);
}
@ -367,6 +365,8 @@ 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,22 +395,7 @@ static void obs_encoder_actually_destroy(obs_encoder_t *encoder)
blog(LOG_DEBUG, "encoder '%s' destroyed",
encoder->context.name);
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);
}
}
obs_encoder_set_group(encoder, NULL);
free_audio_buffers(encoder);
@ -801,14 +786,20 @@ void obs_encoder_start(obs_encoder_t *encoder,
pthread_mutex_unlock(&encoder->init_mutex);
}
static inline bool obs_encoder_stop_internal(
obs_encoder_t *encoder,
void (*new_packet)(void *param, struct encoder_packet *packet),
void *param)
void obs_encoder_stop(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);
@ -821,34 +812,32 @@ static inline bool obs_encoder_stop_internal(
if (last) {
remove_connection(encoder, true);
encoder->initialized = false;
pthread_mutex_unlock(&encoder->init_mutex);
if (encoder->destroy_on_stop) {
pthread_mutex_unlock(&encoder->init_mutex);
struct obs_encoder_group *group = encoder->encoder_group;
if (encoder->destroy_on_stop)
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;
}
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);
pthread_mutex_unlock(&encoder->init_mutex);
}
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);
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;
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;
pthread_mutex_lock(&group->mutex);
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;
}
bool obs_encoder_group_keyframe_aligned_encoders(
obs_encoder_t *encoder, obs_encoder_t *encoder_to_be_grouped)
bool obs_encoder_set_group(obs_encoder_t *encoder, obs_encoder_group_t *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"))
if (!obs_encoder_valid(encoder, "obs_encoder_set_group"))
return false;
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;
if (obs_encoder_active(encoder)) {
blog(LOG_ERROR,
"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_set_group: encoder '%s' is already active",
obs_encoder_get_name(encoder));
return false;
}
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) {
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_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_set_group: encoder '%s' existing group has started encoders",
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);
}
encoder->encoder_group->encoders_added += 1;
encoder_to_be_grouped->encoder_group = encoder->encoder_group;
if (!group)
return true;
if (unlock)
pthread_mutex_unlock(&encoder->encoder_group->mutex);
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);
return true;
}
bool obs_encoder_group_remove_keyframe_aligned_encoder(
obs_encoder_t *encoder, obs_encoder_t *encoder_to_be_ungrouped)
obs_encoder_group_t *obs_encoder_group_create()
{
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;
struct obs_encoder_group *group =
bzalloc(sizeof(struct obs_encoder_group));
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;
pthread_mutex_init_value(&group->mutex);
if (pthread_mutex_init(&group->mutex, NULL) != 0) {
bfree(group);
return NULL;
}
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;
}
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;
}
pthread_mutex_unlock(&current_group->mutex);
if (free_group) {
pthread_mutex_destroy(&free_group->mutex);
bfree(free_group);
}
return true;
return group;
}
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];
encoder->encoder_group = NULL;
obs_encoder_release(encoder);
}
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);
}

View file

@ -1242,10 +1242,15 @@ struct encoder_callback {
void *param;
};
struct encoder_group {
struct obs_encoder_group {
pthread_mutex_t mutex;
uint32_t encoders_added;
uint32_t encoders_started;
/* 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;
uint64_t start_timestamp;
};
@ -1314,7 +1319,7 @@ struct obs_encoder {
uint64_t start_ts;
/* 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;
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->encoders_started;
packet->encoder->encoder_group->num_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 encoder_group *group =
struct obs_encoder_group *group =
encoder->encoder_group;
bool ready = false;
pthread_mutex_lock(&group->mutex);

View file

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

View file

@ -878,13 +878,13 @@ static void obs_free_video(void)
obs_free_video_mix(video);
obs->video.mixes.array[i] = NULL;
}
da_free(obs->video.mixes);
if (num_views > 0)
blog(LOG_WARNING, "Number of remaining views: %ld", num_views);
pthread_mutex_unlock(&obs->video.mixes_mutex);
pthread_mutex_destroy(&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++) {
obs_weak_encoder_release(

View file

@ -46,6 +46,7 @@ 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;
@ -59,6 +60,7 @@ 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;
@ -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 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);
/**
* 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);
/* ------------------------------------------------------------------------- */
/* Stream Services */

View file

@ -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.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

@ -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");
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,6 +99,7 @@ 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,6 +1718,10 @@ 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
@ -2409,7 +2413,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)
if (!count || !track->fragment_samples.num)
return;
struct chunk *chk = da_push_back_new(track->chunks);

View file

@ -112,20 +112,23 @@ 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 = packet->dts;
*offset = ts;
*found = true;
}

View file

@ -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 &&
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, "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)
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": 255,
"version": 256,
"files": [
{
"name": "services.json",
"version": 255
"version": 256
}
]
}

View file

@ -2922,6 +2922,103 @@
"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,8 +23,10 @@
#endif
enum AVHWDeviceType hw_priority[] = {
AV_HWDEVICE_TYPE_CUDA, AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_DXVA2,
AV_HWDEVICE_TYPE_QSV, AV_HWDEVICE_TYPE_NONE,
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)