-
-
Notifications
You must be signed in to change notification settings - Fork 21.7k
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
Audio Stream Playback Generator Broken #65155
Comments
I can confirm this issue as well in Godot 4. I was able to resolve by setting Autoplay to True on the AudioStreamPlayer object -- rest of the code works fine after that change. I can also confirm when opening the tutorial project in Godot 3.4 they do NOT set Autoplay to True. Not sure if a bug or not, but hope this helps! |
I can confirm this also. However, setting Autoplay to True does not really fix the problem for me. It gets rid of the error, but the sound will only play for a fraction of a second before cutting off. In Godot 3.5 the tone continues to play until you exit. Godot v3.5.stable.official [991bb6a] EDIT
It prints out the playback object until it's not null. Godot 4 Output without AutoPlay:
Godot 4 Output with AutoPlay = true:
Godot 3.5 Output:
I think the problem in Godot 4 is two-fold.
Hopefully this helps somehow. |
I have been playing with the audio generator as well. I also noticed that setting autoplay to true "fixed" the issue by allowing the audio to play for just a fraction of a second. However, I was able to get the demo to play a prolonged note by also commenting out the Garuda Linux, Godot 4.0.beta3 |
I’ve noted that the auto play workaround is spotty. If streaming a simple sine wave it’s about 50/50. When loading a file first, then starting to stream, it basically never works. I did get it to work at 50/50 by streaming silence until the file was loaded, then switching to that. It seems possible that the sooner you start streaming the more likely it is to work. |
I decided to take a deep dive into the problems I was having this afternoon and made a minimal reproduction project with some of the code I was tinkering with. I've included it below. Apologies if the evil music generated makes your ears bleed. In the default configuration, the project is likely to play on startup. Of note--like many of you, I've noticed that, on startup, sound may "chirp" briefly as the initial audio buffer is consumed and overtaken by permanent silence. However, in my experience, it's totally random whether generated audio chirps and dies or continues to play on in good health. I have found this is mostly affected by the sample rate. For me
On the other hand, I've had the sample rate as low as 4000hz and still had the audio randomly chirp and die. I haven't found any way to recover from this error either, so generated audio is a non-starter until it's fixed. The length of the audio buffer, and how many samples it receives in ready() may also matter, but I might as well be talking about superstition and witchcraft. I might trying straining at the source code tomorrow to see if there are any obvious problems I can understandstand related to to the sample rate. |
As of Godot 4.0 beta 17 I can confirm that this is still an issue. Working with the example project (https://github.com/godotengine/godot-demo-projects/tree/3.4-b0d4a7c/audio/generator), attempting to run the code as is will yield the following error:
Turning "Autoplay" to "On" on the Player node will result in what sounds like a single frame of audio being played. If you then comment out "$Player.play()" on line 29, the tone will play as intended. I also attempted to run the example linked in the comment above, but only got what sounded like a single frame to play. I'm having similar issues with my own project. To summarize, it seems like the issue is that the call to get the audio stream generator playback object requires that the audio already be playing before you fill the buffer with audio to be played. This is partially fixed by setting "Autoplay" to "On" since the audio will be playing when you try to get the object, but then the audio stops when you call "play()", hence why only a single frame of audio gets played for the linked projects. Looking through the git history, my best guess is that this was broken when the check for "!stream_playbacks.is_empty()" was added before returning stream_playback. Unfortunately I'm not familiar enough with the architecture at this time to suggest a fix. (Running Godot 4.0 beta 17 standard build for windows 64) |
The problem here is that you need to call play() or use autoplay before using the stream. Because AudioStream is now polyphonic it can contain multiple ones. |
I can confirm that in the example project if you move "$Player.play()" from line 29 up to above "playback = $Player.get_stream_playback()" the project works as intended. Additionally, if you leave the second "$Player.play()" command in place, it just chirps and dies. I think the cause is that as part of making AudioStream polyphonic the following was added to the play function in audio_stream_player.cpp:
Meaning that if you call play twice (or autoplay and call play) the stream will be cut off. |
I'm trying to migrate my music project from Godot 3 to 4, and I'm running into a related issue. However, my problem is not that the I have a fully self-contained minimal reproduction example here on GitHub or here as AudioStreamBug.zip. The example doesn't require any scene tree, it's generating everything dynamically: extends Node
var audio_stream_generator_playback: AudioStreamGeneratorPlayback
var i := 0
const SAMPLE_RATE = 44100.0
func _ready():
var audio_stream_player = AudioStreamPlayer.new()
var audio_stream_generator = AudioStreamGenerator.new()
audio_stream_generator.set_mix_rate(SAMPLE_RATE)
audio_stream_generator.set_buffer_length(0.1)
audio_stream_player.set_stream(audio_stream_generator)
add_child(audio_stream_player)
audio_stream_player.play(0.0)
audio_stream_generator_playback = audio_stream_player.get_stream_playback()
func _process(_delta):
var frames_available = audio_stream_generator_playback.get_frames_available()
if frames_available > 0:
print("Filling buffer of length %s" % frames_available)
var buffer = PackedVector2Array()
buffer.resize(frames_available)
var freq = 440.0
for buffer_index in range(frames_available):
var phase = 0.5 * sin(2.0 * PI * i / SAMPLE_RATE * freq)
buffer[buffer_index] = Vector2(phase, phase);
i += 1
audio_stream_generator_playback.push_buffer(buffer) When it works, the output logs is a constant stream of
When it doesn't work, the output is just a single
And then nothing more. In other words, |
I have investigated this a little bit and found out that the commit that broke the audio stream generator is afd2bba, i.e., this PR #55846. The commit immediately before (3017530) still works fine. After skimming the code and adding some debug statements, I have a rough understanding of what is happening: The reason why it sometimes works and sometimes doesn't is a race condition between the audio server thread and the rendering thread. Typically user code calls godot/servers/audio_server.cpp Lines 351 to 371 in 1d0e7f0
The godot/servers/audio_server.cpp Lines 448 to 458 in 1d0e7f0
So in summary, if the user thread doesn't manage to push something into the generator playback, the audio server will immediately remove the generator playback the first time it renders an audio buffer. Now the question is what's the best option to resolve the issue. Since I've only read Godot's audio code for a few minutes now and don't understand the general design yet, I can only make some guesses. Would be great if one of the audio devs could chime in. Some thoughts:
The PR that broke the behavior (#55846) actually modified |
I think your second proposal is the way to go to fix this. I think it's silly right now that it's returning an integer when really there are two things that the caller wants to know: How many frames did you mix, and are there more? Especially because you can't consistently infer either from the other. @bluenote10 What do you think the behavior should be during an AudioStreamGenerator buffer underrun? The fade out logic is in the audio server and it's pretty tightly coupled to the lifecycle of an AudioStreamPlayback so I don't think it would be easy to add any fadein/out to soften a buffer underrun. I think I remember seeing another bug go through my inbox that was caused by the same bit of code but I can't remember. Something about sounds doing something weird when the number of samples in them is evenly divided by the buffer size maybe? |
@ellenhp Thanks for the feedback!
I probably wouldn't do anything fancy. A buffer underrun always sounds bad, because even with a fade out + fade in, there is an audible interruption of the audio output. Since buffer underruns are therefore to be avoided with a high priority anyway, it is probably not worth the effort to do something complex. I.e., just outputting silence is as good/bad as anything.
That would suggest to perhaps use a small struct to basically return a Currently my understanding of the audio system is much too shallow though to really judge what is best here. I hope I can find some time to do further experiments / code reading... |
This makes sense.
This would probably just be fine, because you can still just call I've been pretty out of the loop with what's going on with releases and stuff so I don't know the criteria for inclusion of a bugfix in the RC's, but this may (?) have to wait for a 4.0.x patch release because it could be a medium-risk change depending on how it's implemented. I'm up for trying to fix it today though, this piece of code has been bugging me for a while. |
Also I wanted to add that this is great work on your part @bluenote10 tracking down the source of this issue. I scrolled up to the top and was surprised to see the way this issue ended up presenting itself. The cause is not at all obvious. |
The simplest thing ended up working. @bluenote10 could you test #73162? |
Godot version
Godot 4.X
System information
Windows 11, Default WASAPI drivers,
Issue description
When running the audio generator demo. using
get_stream_playback()
returnsnull
which causesplayback.get_frames_available()
to error out withAttempt to call function 'get_frames_available' in base 'null instance' on a null instance..
I've tried writing a condition in the
_process()
function to check if it isnull
and retry getting the playback node. After a few cycles, it will return positive but will only fill the buffer once and will not process the frames any further, leavingplayback.get_frames_available()
to return0
for the remainder of the runtime.Code:
Log:
Steps to reproduce
Open Audio Generator Demo in any Godot 4,X version
https://github.com/godotengine/godot-demo-projects/tree/3.4-b0d4a7c/audio/generator
Minimal reproduction project
generator - issue.zip
The text was updated successfully, but these errors were encountered: