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.
This commit is contained in:
tt2468 2024-05-07 22:31:23 -07:00 committed by Lain
parent 751dbdad10
commit e215502b62
6 changed files with 122 additions and 161 deletions

View file

@ -332,7 +332,7 @@ static OBSOutputs
SetupOBSOutput(obs_data_t *dump_stream_to_file_config,
const GoLiveApi::Config &go_live_config,
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,
std::optional<size_t> vod_track_mixer);
static void SetupSignalHandlers(bool recording, MultitrackVideoOutput *self,
@ -461,10 +461,10 @@ 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>();
std::vector<OBSEncoderAutoRelease> audio_encoders;
std::shared_ptr<obs_encoder_group_t> video_encoder_group;
auto outputs = SetupOBSOutput(dump_stream_to_file_config, output_config,
audio_encoders, video_encoders,
audio_encoders, video_encoder_group,
audio_encoder_id, vod_track_mixer);
auto output = std::move(outputs.output);
auto recording_output = std::move(outputs.recording_output);
@ -496,13 +496,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 +508,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 +521,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 +685,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 +696,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 +709,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;
}
@ -790,7 +784,7 @@ static OBSOutputs
SetupOBSOutput(obs_data_t *dump_stream_to_file_config,
const GoLiveApi::Config &go_live_config,
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,
std::optional<size_t> vod_track_mixer)
{
@ -801,7 +795,7 @@ 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};

View file

@ -53,7 +53,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

@ -327,8 +327,8 @@ static void add_connection(struct obs_encoder *encoder)
if (encoder->encoder_group) {
pthread_mutex_lock(&encoder->encoder_group->mutex);
encoder->encoder_group->num_encoders_started += 1;
bool ready = encoder->encoder_group->num_encoders_started ==
encoder->encoder_group->num_encoders;
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);
@ -337,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) {
@ -352,11 +353,15 @@ static void remove_connection(struct obs_encoder *encoder, bool shutdown)
if (encoder->encoder_group) {
pthread_mutex_lock(&encoder->encoder_group->mutex);
encoder->encoder_group->num_encoders_started -= 1;
if (encoder->encoder_group->num_encoders_started == 0)
if (--encoder->encoder_group->num_encoders_started == 0) {
encoder->encoder_group->start_timestamp = 0;
if (encoder->encoder_group->destroy_on_stop)
obs_encoder_group_actually_destroy(
encoder->encoder_group);
}
pthread_mutex_unlock(&encoder->encoder_group->mutex);
if (encoder->encoder_group)
pthread_mutex_unlock(&encoder->encoder_group->mutex);
}
/* obs_encoder_shutdown locks init_mutex, so don't call it on encode
@ -394,23 +399,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 obs_encoder_group *group =
encoder->encoder_group;
bool release = false;
encoder->encoder_group = NULL;
pthread_mutex_lock(&group->mutex);
group->num_encoders -= 1;
release = group->num_encoders == 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);
@ -2058,133 +2047,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 obs_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->num_encoders = 1;
} else {
pthread_mutex_lock(&encoder->encoder_group->mutex);
unlock = true;
if (encoder->encoder_group->num_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->num_encoders += 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 obs_encoder_group *current_group = encoder->encoder_group;
struct obs_encoder_group *free_group = NULL;
pthread_mutex_lock(&current_group->mutex);
if (current_group->num_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->num_encoders -= 1;
encoder_to_be_ungrouped->encoder_group = NULL;
if (current_group->num_encoders == 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

@ -1244,7 +1244,12 @@ struct encoder_callback {
struct obs_encoder_group {
pthread_mutex_t mutex;
uint32_t num_encoders;
/* 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;
};

View file

@ -935,11 +935,10 @@ static inline void video_sleep(struct obs_core_video *video, uint64_t *p_time,
struct obs_encoder_group *group =
encoder->encoder_group;
pthread_mutex_lock(&group->mutex);
if (group->num_encoders ==
group->num_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

@ -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 */