Skip to content

libobs: Fix audio duplication bug #12268

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

pkviet
Copy link
Member

@pkviet pkviet commented Jun 8, 2025

Description

This is split from the original PR #12175 to facilitate the reviewing.
PR #12175 fixed two audio bugs but the fixes are conceptually different.

The current bug consists in an increased audio level when a source is duplicated in a scene or through a nested scene.
The bug implies that the same audio samples will be mixed several times resulting in a coherent addition. For duplicate sources
this implies a doubling of the audio amplitude or a constant +6 dBFS level increase.

The bug was mostly fixed in commits 8a38e33 [1] and 72924ac [2]. But it was fixed at the scene level and this left an edge case where a nested scene has a Show/Hide transition. It implies an increase of +6 dBFS for any duplicated source.
An example of valid use case is a Media Source or Browser source which is duplicated and cropped to different areas. Or a nested scene. We want to be able to transition from scene A to scene B with nested scene A, without any change in audio level for instance, if the same audio is used across scenes.

[1] libobs: Mix audio of each source in a scene only once
[2] libobs: Deduplicate audio for nested scenes/groups if not transitioning

Motivation and Context

We fix the bug in this manner:

  • at each audio tick, when the audio tree is built, we tag duplicate audio sources;
  • they are then bypassed in the audio rendering of any scenes or transitions they're involved;
  • they're mixed in the final mix as root_nodes exactly like global audio sources.
    This intrinsically prevents any duplication within a scene, across scenes and in transitions.
    This is a better solution than the previous one because it is not limited to intra-scene deduplication.
    This fixes for instance the edge case of Show/Hide transition to a nested scene.

How Has This Been Tested?

This has been tested extensively on windows 11 pro 23H2 by myself using:

  • a sine tone at 1000 Hz, generated by lavfi filter in Media Source, of amplitude 1/8, which means -21.1 dBFS.
  • the music from a splatoon clip.
    The audio level of the sine was checked using the FFmpeg volumedetect filter:
    ffmpeg -i '.\input.mp4' -filter:a volumedetect -f null /dev/null
    Recordings were made in mp4 container with 16 bit pcm raw audio.
    The waveforms were checked and compared between OBS 31.0.3 (current release) and this PR in Reaper DAW.

@Warchamp7 did also extensive tests which helped me dodge a few dangerous bullets (crashes and what not). Thanks a lot for his help !

Profiling results

We found negligible impact of the current PR on the audio_callback.

  1. no output( no recording nor streaming:
    obs master:
audio_thread(Audio): min=0.002 ms, median=0.018 ms, max=0.422 ms, 99th percentile=0.09 ms
 ┗audio_callback: min=0 ms, median=0.012 ms, max=0.411 ms, 99th percentile=0.08 ms
  • this PR:
audio_thread(Audio): min=0.001 ms, median=0.021 ms, max=0.074 ms, 99th percentile=0.043 ms
 ┗audio_callback: min=0 ms, median=0.016 ms, max=0.065 ms, 99th percentile=0.032 ms
  1. after a 5 seconds recording:
  • obs master:
audio_thread(Audio): min=0.001 ms, median=0.031 ms, max=0.248 ms, 99th percentile=0.073 ms
 ┣audio_callback: min=0 ms, median=0.016 ms, max=0.11 ms, 99th percentile=0.04 ms
 ┗receive_audio: min=0.001 ms, median=0.016 ms, max=0.224 ms, 99th percentile=0.044 ms, 0.428969 calls per parent call
   ┣buffer_audio: min=0 ms, median=0 ms, max=0.038 ms, 99th percentile=0.003 ms
   ┗do_encode: min=0.002 ms, median=0.015 ms, max=0.224 ms, 99th percentile=0.043 ms
     ┣encode(Track1): min=0.001 ms, median=0.005 ms, max=0.033 ms, 99th percentile=0.01 ms 
  • this PR:
audio_thread(Audio): min=0.002 ms, median=0.028 ms, max=0.35 ms, 99th percentile=0.071 ms
 ┣audio_callback: min=0 ms, median=0.015 ms, max=0.199 ms, 99th percentile=0.03 ms
 ┗receive_audio: min=0.001 ms, median=0.015 ms, max=0.136 ms, 99th percentile=0.043 ms, 0.40137 calls per parent call
   ┣buffer_audio: min=0 ms, median=0 ms, max=0.004 ms, 99th percentile=0.002 ms
   ┗do_encode: min=0.002 ms, median=0.014 ms, max=0.135 ms, 99th percentile=0.042 ms
     ┣encode(Track1): min=0.001 ms, median=0.005 ms, max=0.032 ms, 99th percentile=0.012 ms
     ┗send_packet: min=0 ms, median=0.009 ms, max=0.129 ms, 99th percentile=0.034 ms

Tests

the following tests were done:

  1. scene with two sines,

  2. show/hide transition for a sine in a scene with duplicated sine,

  3. fade transition between a scene A with single sine to scene B w/ duplicated sine,

  4. scene A with sine, nested in scene B which also has the sine as a source.

  5. same as 4 with a show/hide transition on the sine in scene B (not shown, same result as 4.)
    dup5

  6. same tests on a audio clip (splatoon): identical results. There is no regression on this PR.

  7. same as 4. w/ a show/hide transition on the nested scene A:

  • left: current release: there is a bump at the transition; level does not stay nominal;
  • right: this PR: no bump, level stays nominal.
    dup6

Additional tests:

  • Fade transition (300 ms) between scene A with a 10 kHz sine at -21.12 dBFS to scene B with a 480 Hz sine at -30 dBFs.
  • top: current release,
  • bottom: this PR. No difference.
    dup7

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

Checklist:

  • My code has been run through clang-format.
  • I have read the contributing document.
  • My code is not on the master branch.
  • The code has been tested.
  • All commit messages are properly formatted and commits squashed where appropriate.
  • I have included updates to all appropriate documentation.

pkviet added 2 commits May 23, 2025 15:42
This reverts commit: 8a38e33 [1].
This also reverts commit: 72924ac [2].
(in favour of a fix in core audio pipeline, which allows to address
edge cases).

[1] libobs: Mix audio of each source in a scene only once
obsproject@8a38e33
[2] libobs: Deduplicate audio for nested scenes/groups if not
transitioning
obsproject@72924ac

Signed-off-by: pkv <pkv@obsproject.com>
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] obsproject#10537

Signed-off-by: pkv <pkv@obsproject.com>
@pkviet pkviet added Bug Fix Non-breaking change which fixes an issue Seeking Testers Build artifacts on CI labels Jun 8, 2025
@Warchamp7 Warchamp7 added the UI/UX Anything to do with changes or additions to UI/UX elements. label Jun 8, 2025
@Warchamp7
Copy link
Member

As noted on this and the previous PR, these are issues I've wanted fixed very badly for a long time and have gotten a fair bit of testing from myself on Windows 10.

These are deep audio pipeline changes so more eyes and testing are generally appreciated.

@pkviet pkviet added this to the OBS Studio 32.0 milestone Jun 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Fix Non-breaking change which fixes an issue Seeking Testers Build artifacts on CI UI/UX Anything to do with changes or additions to UI/UX elements.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants