Jelajahi Sumber

libobs: Fix audio duplication

This fixes the following bug:
- a source might be copied into the same scene or through a nested scene.
The audio level will then increase by +6 dBFS.
An earlier fix [1] dealt with this bug at the scene audio rendering
level, which leaves some edge cases since the fix is not located
directly in the core audio callback.
The current fix consists in:
- tagging individual sources which appear several times in the audio
tree at each tick;
- promote them to root_nodes sources;
- bypass their mixing in scenes and transitions.
Due to being mixed as root_nodes, the audio of duplicated sources
appears only once in the final audio mix.

[1] https://github.com/obsproject/obs-studio/pull/10537

Signed-off-by: pkv <[email protected]>
pkv 5 bulan lalu
induk
melakukan
50cdabbb5f
5 mengubah file dengan 61 tambahan dan 16 penghapusan
  1. 41 5
      libobs/obs-audio.c
  2. 2 0
      libobs/obs-internal.h
  3. 14 10
      libobs/obs-scene.c
  4. 1 1
      libobs/obs-source-transition.c
  5. 3 0
      libobs/obs-source.c

+ 41 - 5
libobs/obs-audio.c

@@ -33,13 +33,48 @@ static void push_audio_tree(obs_source_t *parent, obs_source_t *source, void *p)
 
 	if (da_find(audio->render_order, &source, 0) == DARRAY_INVALID) {
 		obs_source_t *s = obs_source_get_ref(source);
-		if (s)
+		if (s) {
 			da_push_back(audio->render_order, &s);
+			s->audio_is_duplicated = false;
+		}
 	}
 
 	UNUSED_PARAMETER(parent);
 }
 
+static inline bool is_individual_audio_source(obs_source_t *source)
+{
+	return source->info.type == OBS_SOURCE_TYPE_INPUT && (source->info.output_flags & OBS_SOURCE_AUDIO) &&
+	       !(source->info.output_flags & OBS_SOURCE_COMPOSITE);
+}
+
+/*
+ * This version of push_audio_tree has the purpose of detecting sources which appear several times in the audio tree.
+ * They are then tagged as such to avoid their mixing in scenes and transitions and mixed directly as root_nodes.
+ */
+static void push_audio_tree2(obs_source_t *parent, obs_source_t *source, void *p)
+{
+	struct obs_core_audio *audio = p;
+	size_t idx = da_find(audio->render_order, &source, 0);
+
+	if (idx == DARRAY_INVALID) {
+		/* First time we see this source → add to render order */
+		obs_source_t *s = obs_source_get_ref(source);
+		if (s) {
+			da_push_back(audio->render_order, &s);
+			s->audio_is_duplicated = false;
+		}
+	} else {
+		/* Source already present in tree → mark as duplicated if applicable */
+		obs_source_t *s = audio->render_order.array[idx];
+		if (is_individual_audio_source(s) && !s->audio_is_duplicated) {
+			da_push_back(audio->root_nodes, &source);
+			s->audio_is_duplicated = true;
+		}
+	}
+	UNUSED_PARAMETER(parent);
+}
+
 static inline size_t convert_time_to_frames(size_t sample_rate, uint64_t t)
 {
 	return (size_t)util_mul_div64(t, sample_rate, 1000000000ULL);
@@ -508,12 +543,13 @@ bool audio_callback(void *param, uint64_t start_ts_in, uint64_t end_ts_in, uint6
 				continue;
 			if (!obs_source_active(source))
 				continue;
-
-			obs_source_enum_active_tree(source, push_audio_tree, audio);
-			push_audio_tree(NULL, source, audio);
-
+			/* first, add top - level sources as root_nodes */
 			if (obs->video.mixes.array[j]->mix_audio)
 				da_push_back(audio->root_nodes, &source);
+			/* build audio tree and tag duplicate individual sources */
+			obs_source_enum_active_tree(source, push_audio_tree2, audio);
+			/* add top - level sources to audio tree */
+			push_audio_tree(NULL, source, audio);
 		}
 		pthread_mutex_unlock(&view->channels_mutex);
 	}

+ 2 - 0
libobs/obs-internal.h

@@ -824,6 +824,8 @@ struct obs_source {
 	int64_t sync_offset;
 	int64_t last_sync_offset;
 	float balance;
+	/* audio_is_duplicated: tracks whether a source appears multiple times in the audio tree during this tick */
+	bool audio_is_duplicated;
 
 	/* async video data */
 	gs_texture_t *async_textures[MAX_AV_PLANES];

+ 14 - 10
libobs/obs-scene.c

@@ -1697,19 +1697,23 @@ static bool scene_audio_render(void *data, uint64_t *ts_out, struct obs_source_a
 		}
 
 		obs_source_get_audio_mix(source, &child_audio);
-		for (size_t mix = 0; mix < MAX_AUDIO_MIXES; mix++) {
-			if ((mixers & (1 << mix)) == 0)
-				continue;
 
-			for (size_t ch = 0; ch < channels; ch++) {
-				float *out = audio_output->output[mix].data[ch];
-				float *in = child_audio.output[mix].data[ch];
-				if (apply_buf)
-					mix_audio_with_buf(out, in, buf, pos, count);
-				else
-					mix_audio(out, in, pos, count);
+		if (!source->audio_is_duplicated) {
+			for (size_t mix = 0; mix < MAX_AUDIO_MIXES; mix++) {
+				if ((mixers & (1 << mix)) == 0)
+					continue;
+
+				for (size_t ch = 0; ch < channels; ch++) {
+					float *out = audio_output->output[mix].data[ch];
+					float *in = child_audio.output[mix].data[ch];
+					if (apply_buf)
+						mix_audio_with_buf(out, in, buf, pos, count);
+					else
+						mix_audio(out, in, pos, count);
+				}
 			}
 		}
+
 		item = item->next;
 	}
 

+ 1 - 1
libobs/obs-source-transition.c

@@ -854,7 +854,7 @@ static void process_audio(obs_source_t *transition, obs_source_t *child, struct
 			  uint64_t min_ts, uint32_t mixers, size_t channels, size_t sample_rate,
 			  obs_transition_audio_mix_callback_t mix)
 {
-	bool valid = child && !child->audio_pending && child->audio_ts;
+	bool valid = child && !child->audio_pending && child->audio_ts && !child->audio_is_duplicated;
 	struct obs_source_audio_mix child_audio;
 	uint64_t ts;
 	size_t pos;

+ 3 - 0
libobs/obs-source.c

@@ -393,6 +393,9 @@ static obs_source_t *obs_source_create_internal(const char *id, const char *name
 	source->flags = source->default_flags;
 	source->enabled = true;
 
+	/* audio deduplication initialization */
+	source->audio_is_duplicated = false;
+
 	obs_source_init_finalize(source, canvas);
 	if (!private) {
 		if (canvas)