Placeholder-Software / Dissonance

Unity Voice Chat Asset
69 stars 5 forks source link

[help]Can the receiver get the original voice data of the sender? I want to record and save voice #270

Closed shoukailiang closed 1 year ago

shoukailiang commented 1 year ago

Currently, both parties are able to communicate by voice. But I would like the receiver to be able to record the sender's voice and play it back later. Is there a corresponding api.

martindevans commented 1 year ago

Dissonance itself does not have a built in way to do this because you can do it using standard Unity features.

If you create an audio filter (https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnAudioFilterRead.html) you get direct access to the audio stream from an AudioSource (read and write, so you can modify the audio too if you like). So you can create a filter to record the voice (into memory or to file, just make sure you don't do any file IO on the audio thread).

Once you have that filter you can create a Dissonance Playback Prefab. Attach to that prefab (in this order):

shoukailiang commented 1 year ago

Thank you very much for your reply.I will try later. I see 'IMicrophoneCapture' in the document. Is this capturing local voice?

martindevans commented 1 year ago

IMicrophoneCapture is for replacing the behaviour that gets voice from the microphone and feeds it into Dissonance. e.g. our FMOD Recording package uses this.

If you want to intercept the voice stream that has been recorded and preprocessed by Dissonance refer to these docs: https://placeholder-software.github.io/Dissonance/Tutorials/UsingIMicrophoneSubscriber.html

shoukailiang commented 1 year ago

Thank you very much for your reply.

shoukailiang commented 1 year ago

I'm sorry to bother you again @martindevans . I create a Dissonance Playback Prefab. image Audio Renderer is my filter.I drag the prefab into the Playback Prefab field on the Dissonance Comms component inspector. image. I have a question

martindevans commented 1 year ago

I can receive voice from remote users

I'm not completely sure I understand what you're asking, sorry. Do you mean you can hear voice, but your filter is not receiving it?

it seems that the voice is not sent from the audio source?

That's the correct. The AudioSource is playing back silence and then the SamplePlaybackComponent is an audio filter which injects the voice audio. This is a bit weird, but it's the lowest latency way to do it.

A far as I'm aware this shouldn't be a problem for you though? Audio flows down the gameobject, so as long as your filter is below the SamplePlaybackComponent you should be receiving the voice data in your filter.

This is my filter code

This looks broadly ok, but there are a few details that might not be right:

private const int SAMPLE_RATE = 44100; The sample rate of the audio is determined by whatever sample rate the Unity audio system wants to run at. You can read this from the AudioSettings object (https://docs.unity3d.com/ScriptReference/AudioSettings.html)

void OnAudioFilterRead This method is called on the audio thread, not the main thread. I don't think the way you're writing to the outputWriter on the audio thread and reading from it on the main thread is safe.

shoukailiang commented 1 year ago

Thank you very much. It's OK now.

This method is called on the audio thread, not the main thread. I don't think the way you're writing to the outputWriter on the audio thread and reading from it on the main thread is safe.

which mean the following code is problematic.

// main thread 
     private void Update()
     {  
         // Record 20 seconds for example
        time += Time.deltaTime;
        if (Rendering&&time >=20)
        {
            // save
            Save(@"E:\\1.wav");
            Debug.Log("save success");
            Rendering = false;
        }
     }
// audio thread 
     void OnAudioFilterRead(float[] data, int channels)
     {      
         if( this.Rendering)
         {
             // store the number of channels we are rendering
             this.channels = channels;

             // store the data stream
             this.Write(data);   
         }
     }

Therefore, do I need to put the all IO operation into the main thread?

martindevans commented 1 year ago

IO operation into the main thread?

You definitely don't want to do any IO on the audio thread - it can't be stalled under any circumstances!

If I'm understanding your current code correctly you're writing to an MemoryStream inside OnAudioFilterRead (audio thread) and then in Update (main thread) you're saving that to disk. That's not too bad (it might stall the main thread, which isn't great, but it's much better than stalling the audio thread).

However, the one problem is the writing and the reading are not threadsafe, so you may end up with corrupted state. You either need a threadsafe collection to transfer the data, or a lock to protect the datastructures. If you go with a lock you must hold it for the absolutely shortest time possible (e.g. grab the stream, put a new one in place, release the lock).

shoukailiang commented 1 year ago

Thanks!!!!