-
-
Notifications
You must be signed in to change notification settings - Fork 21.4k
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
Expose functions from AudioStreamPlaybackMicrophone
to access the microphone AudioDriver::input_buffer
independently of the rest of the Audio system
#100508
base: master
Are you sure you want to change the base?
Conversation
Since you modified the scripting API, make sure to update the class reference as well and fill it out: https://docs.godotengine.org/en/stable/contributing/documentation/updating_the_class_reference.html#updating-class-reference-when-working-on-the-engine |
AudioStreamPlaybackMicrophone
to access the microphone AudioDriver::input_buffer
indepedently of the rest of the Audio system
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we discussed, the design is that instead of the AudioServer using the mix callback somewhere else, I assume a node::_process(delta) will process the mic driver.
I am not convinced by the arguments to bypass the audio server to access the microphone driver directly.
TLDR There is a long and buggy route for getting microphone audio data out of the engine. It would be shorter and more reliable to extract it directly from the AudioDriver's input_buffer. What happens now All microphone data goes into the ring-buffer AudioDriver::input_buffer advancing The AudioStreamPlaybackMicrophone object has its own local pointer At the moment the only way to extract microphone data from the engine is to wait for the AudioServer to request chunks of data from an AudioBus at its own update rate. This AudioBus then requests the data from an AudioStreamPlaybackMicrophone filtered through an AudioEffectCapture object that copies any audio that passes through into its own little buffer. Then, on a process loop, you extract the data from this buffer by calling AudioEffectCapture.get_buffer() There are two problems with this. Firstly, the AudioServer is highly sensitive to data not arriving in at the rate it requires, so if the microphone buffer isn't filled fast enough the stream is terminated. There is a pull-request to fix the problem on the Android platform where the microphone keeps switching off after two minutes by padding the missing frames with zeros. Not surprisingly, this degrades the quality of the microphone audio. Secondly, we don't generally want any microphone audio data in the AudioServer because it causes feedback and is too laggy to work as realtime amplifier. So if it is too difficult to debug this buffer properly (which it is), it's not worth it. The evidence is that standard operating procedure requires us to create a special bus for the AudioStreamMicrophone to output to that is set to Mute. The solution Expose |
…odot into gtch/micplaybackmaster
This fix is working well. It ran on my cheap Android phone for 4 hours without any issues at all. Previously it would last about 3 minutes on this phone before the Although there is some skepticism, I would like to push this PR into some place where it can be critically discussed and reviewed, because it actually works, and we have don't even have an outline for fixing the Microphone any other way. The primary function added into PackedVector2Array get_microphone_buffer(int p_frames) I would prefer to have the additional function: bool mix_microphone(AudioFrame* buffer, int p_frames); which would copy the values directly into an array rather than allocating and returning a temporary array by value. The main use of this interface is the twovoip addon for doing Voice over IP. There are a numerous virtual functions like this one: void _process(src_buffer: const void*, dst_buffer: AudioFrame*, frame_count: int) virtual where the engine calls out to the GDExtension with an array pointer, but no examples I have found where a GDExtension calls into the engine with an array pointer. My attempts to implement this function using: bool AudioStreamPlaybackMicrophone::::mix_microphone(GDExtensionConstPtr<AudioFrame> p_buffer, int p_frames);
ClassDB::bind_method(D_METHOD("mix_microphone", "p_buffer", "frames"), &AudioStreamPlaybackMicrophone::mix_microphone); do not compile, so I have left the call to the bind_method commented out until it's possible to find an answer. |
The use of thread locking is inconsistent when accessing the AudioDriver.input_buffer ring-buffunctionfer. In particular, values are pushed into this ring-buffer by the AudioDriver::input_buffer_write() function from various platforms without a mutex lock, while values are copied from this buffer by the function AudioStreamPlaybackMicrophone::_mix_internal() where it is protected by a Therefore this locking achieves nothing, and we are getting away with it because either (1) the ring-buffer does not realloc, (2) there is a single audio thread on some platforms, or (3) any issues are mistaken for other bugs in the microphone system. Confusingly, this ring-buffer has two indexes into it, My new AudioStreamPlaybackMicrophone::get_microphone_buffer() function however, does use the This lack of locking might explain the bizarre if-statement on line 114 protecting this value from being invalid in this way that could only ever happen if two threads were executing this function simultaneously! Such an event -- of two threads reading and posting microphone data into the buffer -- is already a bug, and would be fixed by Change 1 at the top. To solve this we either need to make this function safe for interruption, by rewriting it like (I don't know if this is safe ? @RandomShaper ): input_buffer.write[input_position] = sample;
input_position = (input_position % input_buffer.size()); or putting a lock into the A more consistent answer would be to put the Since the exact time that we get data from |
person planning on adding VC to a game here. |
AudioStreamPlaybackMicrophone
to access the microphone AudioDriver::input_buffer
indepedently of the rest of the Audio systemAudioStreamPlaybackMicrophone
to access the microphone AudioDriver::input_buffer
independently of the rest of the Audio system
Further issues discovered:
AudioStreamPlaybackMicrophone::start() calls AudioDriverOpenSL::input_start() which pops up your Allow to record audio? permissions request dialog box, and returns ERR_UNAUTHORIZED, which means that If you confirm the permission to record, then there is a call-back into Java_org_godotengine_godot_GodotLib_requestPermissionResult() which makes its own call to AudioDriverOpenSL::input_start() now that it is allowed to start reading the microphone. Unfortunately the My preferred solution is to disable the hidden call-back to |
If you made a pr fixing this, you can get the android team to review it. |
This solves all the issues raised in the proposal: godotengine/godot-proposals#11347
Changes made:
My new
AudioDriver::input_start_count
counter is to prevent multiple calls toAudioDriver::input_start()
from different AudioStreamPlaybackMicrophone instances that result in multiple conflicting processes to popping data from the same buffer.Change the return type of
AudioDriver::get_input_buffer()
toVector<int32_t>&
to avoid copying out entire bufferto access it.
GDREGISTER_CLASS(AudioStreamPlaybackMicrophone)
was missing fromregister_server_types.cpp
Expose functions
start()
,stop()
andis_playing()
fromAudioStreamPlaybackMicrophone
to GDScriptNew function
PackedVector2Array AudioStreamPlaybackMicrophone::get_microphone_buffer(int p_frames)
. This does the same as PackedVector2Array AudioEffectCapture::get_buffer(frames: int) but it fetches the frames directly from theAudioDriver::input_buffer
without going through an AudioStream, AudioBus and an AudioEffect, all of which operate on the duty cycle of the Audio system output frequency.Need help with:
I would like to expose
int AudioStreamPlaybackMicrophone::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames)
to GDExtension, but this has an
AudioFrame*
pointer in its parameters listThis ought to be possible, because there is already one like this with int AudioStreamPlaybackResampled::_mix_resampled(dst_buffer: AudioFrame*, frame_count: int),
but it is created by the special virtual function template
GDVIRTUAL2R(int, _mix_resampled, GDExtensionPtr<AudioFrame>, int)