The Godot Barn
Sign in
Sign in
Home
News & updates
Explore
Articles
Snippets
Shaders
Themes
Submit content
Sign in to submit content
Give or get help
Tutorials
Questions
Conversations
Coming soon!
GodotSteam Tutorials - Voice
0
Description
"This tutorial covers adding Steam Voice chat to your game instead of just typing away mid-game. This example is based partially on this [Github repo for networked voice chat in Godot](https:\/\/github.com\/ikbencasdoei\/godot-voip\/) and Valve's SpaceWar example. There are additional ideas, details, and such from cool people **Punny** and **ynot01**.\r\n\r\n??? guide Relevant GodotSteam classes and functions\r\n* [Friends class](https:\/\/godotsteam.com\/classes\/friends.md)\r\n\t* [setInGameVoiceSpeaking()](https:\/\/godotsteam.com\/classes\/friends.md#setingamevoicespeaking)\r\n* [User class](https:\/\/godotsteam.com\/classes\/user.md)\r\n\t* [decompressVoice()](https:\/\/godotsteam.com\/classes\/user.md#decompressvoice)\r\n\t* [getAvailableVoice()](https:\/\/godotsteam.com\/classes\/user.md#getavailablevoice)\r\n\t* [getVoice()](https:\/\/godotsteam.com\/classes\/user.md#getvoice)\r\n\t* [getVoiceOptimalSampleRate()](https:\/\/godotsteam.com\/classes\/user.md#getvoiceoptimalsamplerate)\r\n\t* [startVoiceRecording()](https:\/\/godotsteam.com\/classes\/user.md#startvoicerecording)\r\n\t* [stopVoiceRecording()](https:\/\/godotsteam.com\/classes\/user.md#stopvoicerecording)\r\n???\r\n\r\n!!! warning Note\r\nYou may want to [double-check our Initialization tutorial](https:\/\/godotsteam.com\/tutorials\/initializing) to set up initialization and callbacks functionality if you haven't done so already.\r\n!!!\r\n\r\n## Preparations{.block}\r\n\r\nFirst we will set up a bunch of variables and a constant that will get used later on:\r\n\r\n```gdscript\r\nconst SAMPLE_RATE: int = 48000\r\n\r\nvar current_sample_rate: int = SAMPLE_RATE\r\nvar voice_playback: AudioStreamGeneratorPlayback = null\r\n```\r\n\r\nIn our **_ready()** function we will add a call to **setup_stream()** which will create our **AudioStreamPlayer** used to hearing the voice data. The **setup_stream()** function should look something like this:\r\n\r\n```gdscript\r\nfunc setup_stream() -> void:\r\n\t# Optionally we can get the sample rate from Steam\r\n\t# current_sample_rate = Steam.getVoiceOptimalSampleRate()\r\n\r\n\tvar voice_stream_player := AudioStreamPlayer.new()\r\n\tadd_child(voice_stream_player)\r\n\tvoice_stream_player.stream = AudioStreamGenerator.new()\r\n\tvoice_stream_player.stream.mix_rate = current_sample_rate\r\n\tvoice_stream_player.play()\r\n\tvoice_playback = voice_stream_player.get_stream_playback()\r\n```\r\n\r\nYou can also get Steam's assumed optimized sample rate by calling **getVoiceOptimalSampleRate()** and placing it before creating our AudioStreamPlayer. You could also place a toggle in your game's settings \/ options to use it upon request.\r\n\r\n## Recording Voice{.block}\r\n\r\nSo how do we actually get our voice data to Steam? Like most games, you will probably want to offer both open mic and press-to-talk options. For our example we will create two buttons, one for each option. Both buttons will call the same **record_voice()** function too:\r\n\r\n```gdscript\r\n@onready var press_to_talk: Button = %PressToTalk\r\n@onready var toggle_open_mic: Button = %ToggleOpenMic\r\n\r\n\r\n# Add signals for those new buttons\r\nfunc _ready() -> void:\r\n\t...\r\n\tpress_to_talk.button_down.connect(record_voice.bind(true))\r\n\tpress_to_talk.button_up.connect(record_voice.bind(false))\r\n\ttoggle_open_mic.toggled.connect(record_voice)\r\n```\r\n\r\nWe could also implement this behavior as a hotkey instead. Just add a new variable to keep track of the open mic status called **is_open_mic** and then add two new input actions: **voice_toggle** and **voice_talk**. Then, when they are pressed (or released), we can call our same **record_voice()** function.\r\n\r\n```gdscript\r\nvar is_open_mic: bool = false\r\n\r\nfunc _input(event: InputEvent) -> void:\r\n\tif event.is_action_pressed(\"voice_toggle\"):\r\n\t\tis_open_mic = not is_open_mic\r\n\t\trecord_voice(is_open_mic)\r\n\tif event.is_action_pressed(\"voice_talk\"):\r\n\t\trecord_voice(true)\r\n\tif event.is_action_released(\"voice_talk\"):\r\n\t\trecord_voice(false)\r\n```\r\n\r\nFinally we have our actual **record_voice()** function which just swaps between **startVoiceRecording()** and **stopVoiceRecording()**. We will also called **setInGameVoiceSpeaking()** to suppress any sounds or such coming from the Steam client.\r\n\r\n```gdscript\r\nfunc record_voice(is_recording: bool) -> void:\r\n\t# If talking, suppress all other audio or voice comms from the Steam UI\r\n\tSteam.setInGameVoiceSpeaking(steam_id, is_recording)\r\n\r\n\tif is_recording:\r\n\t\tSteam.startVoiceRecording()\r\n\telse:\r\n\t\tSteam.stopVoiceRecording()\r\n```\r\n\r\nNow time to get the voice data we just sent.\r\n\r\n## Getting Voice Data{.block}\r\n\r\nIn our **\\_process()** function we will add the **check_for_voice()** function call:\r\n\r\n```gdscript\r\nfunc _process(_delta: float) -> void:\r\n\tcheck_for_voice()\r\n```\r\n\r\nThis function basically just looks to see if there is voice data from Steam available. If so, it will grab it and send it off to be processed.\r\n\r\n::: tabs\r\n@tab:active RPC Sending\r\nUsing RPCs, we can just call our process_voice_data() function and pass the voice buffer over.\r\n\r\n```gdscript\r\nfunc check_for_voice() -> void:\r\n\tvar available_voice: Dictionary = Steam.getAvailableVoice()\r\n\r\n\tif available_voice['result'] == Steam.VoiceResult.VOICE_RESULT_OK and available_voice['size'] > 0:\r\n\t\tvar voice_data: Dictionary = Steam.getVoice()\r\n\r\n\t\tif voice_data['result'] == Steam.VOICE_RESULT_OK and voice_data['size'] > 0:\r\n\t\t\t# Here we pass the voice data off to the network\r\n\t\t\tprocess_voice_data.rpc(voice_data['buffer'])\r\n```\r\n\r\n@tab Networking Messages\r\nSending the data based on our [Networking Message tutorial](https:\/\/godotsteam.com\/tutorials\/networking_messages\/#sending-messages).\r\n\r\n```gdscript\r\nfunc check_for_voice() -> void:\r\n\tvar available_voice: Dictionary = Steam.getAvailableVoice()\r\n\r\n\tif available_voice['result'] == Steam.VoiceResult.VOICE_RESULT_OK and available_voice['size'] > 0:\r\n\t\tvar voice_data: Dictionary = Steam.getVoice()\r\n\r\n\t\tif voice_data['result'] == Steam.VOICE_RESULT_OK and voice_data['size'] > 0:\r\n\t\t\t# Here we pass the voice data off to the network\r\n\t\t\tNetworking.send_message(0, {\"voice\": voice_data['result']})\r\n```\r\n\r\n@tab Loopback Testing\r\nBest used in your game's settings \/ options menu to test the player's voice audio.\r\n\r\n```gdscript\r\nfunc check_for_voice() -> void:\r\n\tvar available_voice: Dictionary = Steam.getAvailableVoice()\r\n\r\n\tif available_voice['result'] == Steam.VoiceResult.VOICE_RESULT_OK and available_voice['size'] > 0:\r\n\t\tvar voice_data: Dictionary = Steam.getVoice()\r\n\r\n\t\tif voice_data['result'] == Steam.VoiceResult.VOICE_RESULT_OK and voice_data['size'] > 0:\r\n\t\t\t# Here we directly pass the voice data to our processing function\r\n\t\t\tprocess_voice_data(voice_data['buffer'])\r\n```\r\n:::\r\n\r\n### GodotSteam Version Differences\r\n\r\nThe above code requires some changes based on what version of GodotSteam you are using. **getAvailableVoice()** was removed from versions 4.16 to 4.18.1 (added back in 4.19) so we need to remove that function from the above code for this to work. We just skip down to **getVoice()** directly and tweaking one key from **size** to **written**, making it simply:\r\n\r\n```gdscript\r\nfunc check_for_voice() -> void:\r\n\tvar voice_data: Dictionary = Steam.getVoice()\r\n\r\n\tif voice_data['result'] == Steam.VoiceResult.VOICE_RESULT_OK and voice_data['written']:\r\n\t\t# Pass the voice data along\r\n```\r\n\r\nNow time to process the data so we can actually hear it.\r\n\r\n## Processing Voice Data{.block}\r\n\r\nRegardless of how we sent it or if we are just testing with loopback, this function will process the voice buffer and play it back in our **voice_playback** stream.\r\n\r\n```gdscript\r\n# If using MultiplayerPeer, we will add this line\r\n# @rpc(\"any_peer\", \"call_remote\", \"unreliable\")\r\nfunc process_voice_data(voice_data: PackedByteArray) -> void:\r\n\tvar decompressed_voice: Dictionary = Steam.decompressVoice(voice_data, current_sample_rate)\r\n\r\n\tif decompressed_voice['result'] == Steam.VoiceResult.VOICE_RESULT_OK and decompressed_voice['size'] > 0:\r\n\t\tvar frames_to_push: PackedVector2Array = PackedVector2Array()\r\n\t\tframes_to_push.resize(decompressed_voice['size'] \/ 2)\r\n\r\n\t\tfor i in range(0, decompressed_voice['size'], 2):\r\n\t\t\tvar sample_int: int = decompressed_voice['uncompressed'].decode_s16(i)\r\n\t\t\tvar amplitude: float = float(sample_int) \/ 32768.0\r\n\t\t\tframes_to_push[i \/ 2] = Vector2(amplitude, amplitude)\r\n\r\n\t\tif voice_playback.get_frames_available() >= frames_to_push.size():\r\n\t\t\tvoice_playback.push_buffer(frames_to_push)\r\n\t\telif voice_playback.get_frames_available() > 0:\r\n\t\t\tvoice_playback.push_buffer(frames_to_push.slice(0, voice_playback.get_frames_available()))\r\n```\r\n\r\nAs noted, if you are using MultiplayerPeer to send your voice data, just uncomment that line above the function to use RPCs.\r\n\r\n## Troubleshooting{.block}\r\n\r\n### Choppy Voice\r\n\r\nThough the newer version of this tutorial should fix the choppy audio issue, I was still getting choppy voice when testing in the Steam client's settings. For me, it seemed to be caused by Steam's echo cancellation setting; I just had to turn this off to get smooth audio going. You can find this in: **Settings > Voice > Advanced Settings > Echo Cancellation**\r\n\r\n\r\n\r\n---\r\n\r\n## Additional Resources{.block}\r\n\r\n### Video Tutorials\r\n\r\nPrefer video tutorials? Feast your eyes and ears!\r\n\r\n[youtube: Proximity Voice Chat in Godot using Steam Lobbies](https:\/\/www.youtube.com\/watch?v=D4i9you1_IE){ data-author=\"Rembot Games\"}\r\n\r\n### Related Projects\r\n\r\n[Godot VOIP](https:\/\/github.com\/ikbencasdoei\/godot-voip) by ikbencasdoei\r\n\r\n\r\n### Example Project\r\n\r\n[Later this year you can see this tutorial in action with more in-depth information by checking out our upcoming free-to-play game Skillet on Codeberg.](https:\/\/codeberg.org\/godotsteam\/skillet) There you will be able to view of the code used which can serve as a starting point for you to branch out from."
Comments
Log in to post a comment
Licensed under the CC-BY license
See the full license details
Submitted by
Gramps
Table of contents
Compatibility
Works in Godot
3.x / 4.x
Tags
GodotSteam
SteamĀ®
steamworks
voice chat
Share this tutorial
Share on Bluesky
Share on X / Twitter
or share this direct link:
https://thegodotbarn.com/contributions/tutorial/874/godotsteam-tutorials-voice
Please wait ...
Okay
Okay
No
Yes
Okay