Explorar o código

Merge pull request #113288 from goatchurchprime/gtch/audioservermic

AudioServer to have function to access microphone buffer directly
Thaddeus Crews hai 1 semana
pai
achega
5a7e1bf0f4

+ 29 - 0
doc/classes/AudioServer.xml

@@ -124,6 +124,12 @@
 				Returns the name of the current audio driver. The default usually depends on the operating system, but may be overridden via the [code]--audio-driver[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url]. [code]--headless[/code] also automatically sets the audio driver to [code]Dummy[/code]. See also [member ProjectSettings.audio/driver/driver].
 			</description>
 		</method>
+		<method name="get_input_buffer_length_frames" experimental="">
+			<return type="int" />
+			<description>
+				Returns the absolute size of the microphone input buffer. This is set to a multiple of the audio latency and can be used to estimate the minimum rate at which the frames need to be fetched.
+			</description>
+		</method>
 		<method name="get_input_device_list">
 			<return type="PackedStringArray" />
 			<description>
@@ -131,6 +137,21 @@
 				[b]Note:[/b] [member ProjectSettings.audio/driver/enable_input] must be [code]true[/code] for audio input to work. See also that setting's description for caveats related to permissions and operating system privacy settings.
 			</description>
 		</method>
+		<method name="get_input_frames" experimental="">
+			<return type="PackedVector2Array" />
+			<param index="0" name="frames" type="int" />
+			<description>
+				Returns a [PackedVector2Array] containing exactly [param frames] audio samples from the internal microphone buffer if available, otherwise returns an empty [PackedVector2Array].
+				The buffer is filled at the rate of [method get_input_mix_rate] frames per second when [method set_input_device_active] has successfully been set to [code]true[/code].
+				The samples are signed floating-point PCM values between [code]-1[/code] and [code]1[/code].
+			</description>
+		</method>
+		<method name="get_input_frames_available" experimental="">
+			<return type="int" />
+			<description>
+				Returns the number of frames available to read using [method get_input_frames].
+			</description>
+		</method>
 		<method name="get_input_mix_rate" qualifiers="const">
 			<return type="float" />
 			<description>
@@ -330,6 +351,14 @@
 				[b]Note:[/b] This is enabled by default in the editor, as it is used by editor plugins for the audio stream previews.
 			</description>
 		</method>
+		<method name="set_input_device_active" experimental="">
+			<return type="int" enum="Error" />
+			<param index="0" name="active" type="bool" />
+			<description>
+				If [param active] is [code]true[/code], starts the microphone input stream specified by [member input_device] or returns an error if it failed.
+				If [param active] is [code]false[/code], stops the input stream if it is running.
+			</description>
+		</method>
 		<method name="swap_bus_effects">
 			<return type="void" />
 			<param index="0" name="bus_idx" type="int" />

+ 6 - 0
drivers/pulseaudio/audio_driver_pulseaudio.cpp

@@ -555,6 +555,8 @@ void AudioDriverPulseAudio::thread_func(void *p_udata) {
 			}
 
 			// User selected a new input device, finish the current one so we'll init the new input device
+			// (If `AudioServer.set_input_device()` did not set the value when the microphone was running,
+			//  this section with its problematic error handling could be deleted.)
 			if (ad->input_device_name != ad->new_input_device) {
 				ad->input_device_name = ad->new_input_device;
 				ad->finish_input_device();
@@ -691,6 +693,10 @@ void AudioDriverPulseAudio::finish() {
 }
 
 Error AudioDriverPulseAudio::init_input_device() {
+	if (pa_rec_str) {
+		return ERR_ALREADY_IN_USE;
+	}
+
 	// If there is a specified input device, check that it is really present
 	if (input_device_name != "Default") {
 		PackedStringArray list = get_input_device_list();

+ 5 - 3
drivers/wasapi/audio_driver_wasapi.cpp

@@ -542,6 +542,10 @@ Error AudioDriverWASAPI::init_output_device(bool p_reinit) {
 }
 
 Error AudioDriverWASAPI::init_input_device(bool p_reinit) {
+	if (audio_input.active.is_set()) {
+		return ERR_ALREADY_IN_USE;
+	}
+
 	Error err = audio_device_init(&audio_input, true, p_reinit);
 	if (err != OK) {
 		// We've tried to init the device, but have failed. Time to clean up.
@@ -1023,11 +1027,9 @@ Error AudioDriverWASAPI::input_stop() {
 	if (audio_input.active.is_set()) {
 		audio_input.audio_client->Stop();
 		audio_input.active.clear();
-
-		return OK;
 	}
 
-	return FAILED;
+	return OK;
 }
 
 PackedStringArray AudioDriverWASAPI::get_input_device_list() {

+ 15 - 13
platform/android/audio_driver_opensl.cpp

@@ -271,7 +271,11 @@ Error AudioDriverOpenSL::input_start() {
 	}
 
 	if (OS::get_singleton()->request_permission("RECORD_AUDIO")) {
-		return init_input_device();
+		Error err = init_input_device();
+		if (err != OK) {
+			input_stop();
+		}
+		return err;
 	}
 
 	WARN_PRINT("Unable to start audio capture - No RECORD_AUDIO permission");
@@ -279,20 +283,18 @@ Error AudioDriverOpenSL::input_start() {
 }
 
 Error AudioDriverOpenSL::input_stop() {
-	if (!recordItf || !recordBufferQueueItf) {
-		return ERR_CANT_OPEN;
+	if (recordItf) {
+		(*recordItf)->SetRecordState(recordItf, SL_RECORDSTATE_STOPPED);
+		recordItf = nullptr;
 	}
 
-	SLuint32 state;
-	SLresult res = (*recordItf)->GetRecordState(recordItf, &state);
-	ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN);
-
-	if (state != SL_RECORDSTATE_STOPPED) {
-		res = (*recordItf)->SetRecordState(recordItf, SL_RECORDSTATE_STOPPED);
-		ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN);
-
-		res = (*recordBufferQueueItf)->Clear(recordBufferQueueItf);
-		ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN);
+	if (recordBufferQueueItf) {
+		(*recordBufferQueueItf)->Clear(recordBufferQueueItf);
+		recordBufferQueueItf = nullptr;
+	}
+	if (recorder) {
+		(*recorder)->Destroy(recorder);
+		recorder = nullptr;
 	}
 
 	return OK;

+ 72 - 0
servers/audio/audio_server.cpp

@@ -109,6 +109,9 @@ void AudioDriver::input_buffer_write(int32_t sample) {
 			input_size++;
 		}
 	} else {
+		// This protection was added in GH-26505 due to a "possible crash".
+		// This cannot have happened unless two non-locked threads entered function simultaneously, which was possible when multiple calls to
+		// `AudioDriver::input_start()` did not raise an error condition.
 		WARN_PRINT("input_buffer_write: Invalid input_position=" + itos(input_position) + " input_buffer.size()=" + itos(input_buffer.size()));
 	}
 }
@@ -1842,6 +1845,71 @@ void AudioServer::set_input_device(const String &p_name) {
 	AudioDriver::get_singleton()->set_input_device(p_name);
 }
 
+Error AudioServer::set_input_device_active(bool p_is_active) {
+	if (input_device_active == p_is_active) {
+		return OK;
+	}
+	if (p_is_active) {
+		if (!GLOBAL_GET("audio/driver/enable_input")) {
+			WARN_PRINT("You must enable the project setting \"audio/driver/enable_input\" to use audio capture.");
+			return FAILED;
+		}
+
+		input_buffer_ofs = 0;
+		input_device_active = true;
+		return AudioDriver::get_singleton()->input_start();
+	} else {
+		input_device_active = false;
+		return AudioDriver::get_singleton()->input_stop();
+	}
+}
+
+int AudioServer::get_input_frames_available() {
+	AudioDriver *ad = AudioDriver::get_singleton();
+	ad->lock();
+	int64_t input_position = ad->get_input_position();
+	if (input_position < input_buffer_ofs) {
+		input_position += ad->get_input_buffer().size();
+	}
+	ad->unlock();
+	return (int)((input_position - input_buffer_ofs) / 2); // Buffer is stereo.
+}
+
+int AudioServer::get_input_buffer_length_frames() {
+	AudioDriver *ad = AudioDriver::get_singleton();
+	ad->lock();
+	int buffsize = ad->get_input_buffer().size();
+	ad->unlock();
+	return buffsize / 2;
+}
+
+PackedVector2Array AudioServer::get_input_frames(int p_frames) {
+	PackedVector2Array ret;
+	AudioDriver *ad = AudioDriver::get_singleton();
+	ad->lock();
+	int input_position = ad->get_input_position();
+	Vector<int32_t> buf = ad->get_input_buffer();
+	if (input_position < input_buffer_ofs) {
+		input_position += buf.size();
+	}
+	if ((input_buffer_ofs + p_frames * 2 <= input_position) && (p_frames >= 0)) {
+		ret.resize(p_frames);
+		for (int i = 0; i < p_frames; i++) {
+			float l = (buf[input_buffer_ofs++] >> 16) / 32768.f;
+			if (input_buffer_ofs >= buf.size()) {
+				input_buffer_ofs = 0;
+			}
+			float r = (buf[input_buffer_ofs++] >> 16) / 32768.f;
+			if (input_buffer_ofs >= buf.size()) {
+				input_buffer_ofs = 0;
+			}
+			ret.write[i] = Vector2(l, r);
+		}
+	}
+	ad->unlock();
+	return ret;
+}
+
 void AudioServer::set_enable_tagging_used_audio_streams(bool p_enable) {
 	tag_used_audio_streams = p_enable;
 }
@@ -2016,6 +2084,10 @@ void AudioServer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_input_device_list"), &AudioServer::get_input_device_list);
 	ClassDB::bind_method(D_METHOD("get_input_device"), &AudioServer::get_input_device);
 	ClassDB::bind_method(D_METHOD("set_input_device", "name"), &AudioServer::set_input_device);
+	ClassDB::bind_method(D_METHOD("set_input_device_active", "active"), &AudioServer::set_input_device_active);
+	ClassDB::bind_method(D_METHOD("get_input_frames_available"), &AudioServer::get_input_frames_available);
+	ClassDB::bind_method(D_METHOD("get_input_buffer_length_frames"), &AudioServer::get_input_buffer_length_frames);
+	ClassDB::bind_method(D_METHOD("get_input_frames", "frames"), &AudioServer::get_input_frames);
 
 	ClassDB::bind_method(D_METHOD("set_bus_layout", "bus_layout"), &AudioServer::set_bus_layout);
 	ClassDB::bind_method(D_METHOD("generate_bus_layout"), &AudioServer::generate_bus_layout);

+ 7 - 0
servers/audio/audio_server.h

@@ -232,6 +232,9 @@ private:
 	bool debug_mute = false;
 #endif // DEBUG_ENABLED
 
+	bool input_device_active = false;
+	int input_buffer_ofs = 0;
+
 	struct Bus {
 		StringName name;
 		bool solo = false;
@@ -492,6 +495,10 @@ public:
 	PackedStringArray get_input_device_list();
 	String get_input_device();
 	void set_input_device(const String &p_name);
+	Error set_input_device_active(bool p_is_active);
+	int get_input_frames_available();
+	int get_input_buffer_length_frames();
+	PackedVector2Array get_input_frames(int p_frames);
 
 	void set_enable_tagging_used_audio_streams(bool p_enable);
 

+ 2 - 7
servers/audio/audio_stream.cpp

@@ -442,14 +442,9 @@ void AudioStreamPlaybackMicrophone::start(double p_from_pos) {
 		return;
 	}
 
-	if (!GLOBAL_GET_CACHED(bool, "audio/driver/enable_input")) {
-		WARN_PRINT("You must enable the project setting \"audio/driver/enable_input\" to use audio capture.");
-		return;
-	}
-
 	input_ofs = 0;
 
-	if (AudioDriver::get_singleton()->input_start() == OK) {
+	if (AudioServer::get_singleton()->set_input_device_active(true) == OK) {
 		active = true;
 		begin_resample();
 	}
@@ -457,7 +452,7 @@ void AudioStreamPlaybackMicrophone::start(double p_from_pos) {
 
 void AudioStreamPlaybackMicrophone::stop() {
 	if (active) {
-		AudioDriver::get_singleton()->input_stop();
+		AudioServer::get_singleton()->set_input_device_active(false);
 		active = false;
 	}
 }