com.adrenak.univoice 3.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +36 -54
  2. package/Runtime/Adrenak.UniVoice.Runtime.asmdef +20 -3
  3. package/Runtime/ClientSession.cs +138 -0
  4. package/Runtime/{Interfaces/IChatroomNetwork.cs.meta → ClientSession.cs.meta} +1 -1
  5. package/Runtime/Common/Utils.cs +55 -0
  6. package/Runtime/{Types/ChatroomAgentMode.cs.meta → Common/Utils.cs.meta} +1 -1
  7. package/Runtime/Common.meta +8 -0
  8. package/Runtime/Impl/Filters/Concentus Opus Filters/ConcentusDecodeFilter.cs +80 -0
  9. package/Runtime/{ChatroomAgent.cs.meta → Impl/Filters/Concentus Opus Filters/ConcentusDecodeFilter.cs.meta } +1 -1
  10. package/Runtime/Impl/Filters/Concentus Opus Filters/ConcentusEncodeFilter.cs +156 -0
  11. package/Runtime/Impl/Filters/Concentus Opus Filters/ConcentusEncodeFilter.cs.meta +11 -0
  12. package/Runtime/Impl/Filters/Concentus Opus Filters/ConcentusFrequencies.cs +31 -0
  13. package/Runtime/Impl/Filters/Concentus Opus Filters/ConcentusFrequencies.cs.meta +11 -0
  14. package/Runtime/Impl/Filters/Concentus Opus Filters.meta +8 -0
  15. package/Runtime/Impl/Filters/GaussianAudioBlur.cs +90 -0
  16. package/Runtime/Impl/Filters/GaussianAudioBlur.cs.meta +11 -0
  17. package/Runtime/Impl/Filters.meta +8 -0
  18. package/Runtime/Impl/Inputs/UniMicInput.cs +50 -0
  19. package/Runtime/Impl/Inputs/UniMicInput.cs.meta +11 -0
  20. package/Runtime/Impl/Inputs.meta +8 -0
  21. package/Runtime/Impl/Networks/Mirror/MirrorClient.cs +161 -0
  22. package/Runtime/Impl/Networks/Mirror/MirrorClient.cs.meta +11 -0
  23. package/Runtime/Impl/Networks/Mirror/MirrorMessage.cs +21 -0
  24. package/Runtime/Impl/Networks/Mirror/MirrorMessage.cs.meta +11 -0
  25. package/Runtime/Impl/Networks/Mirror/MirrorMessageTags.cs +16 -0
  26. package/Runtime/Impl/Networks/Mirror/MirrorMessageTags.cs.meta +11 -0
  27. package/Runtime/Impl/Networks/Mirror/MirrorModeObserver.cs +43 -0
  28. package/Runtime/Impl/Networks/Mirror/MirrorModeObserver.cs.meta +11 -0
  29. package/Runtime/Impl/Networks/Mirror/MirrorServer.cs +234 -0
  30. package/Runtime/Impl/Networks/Mirror/MirrorServer.cs.meta +11 -0
  31. package/Runtime/Impl/Networks/Mirror.meta +8 -0
  32. package/Runtime/Impl/Networks.meta +8 -0
  33. package/Runtime/Impl/Outputs/StreamedAudioSourceOutput.cs +56 -0
  34. package/Runtime/Impl/Outputs/StreamedAudioSourceOutput.cs.meta +11 -0
  35. package/Runtime/Impl/Outputs.meta +8 -0
  36. package/Runtime/Impl.meta +8 -0
  37. package/Runtime/Interfaces/IAudioClient.cs +77 -0
  38. package/Runtime/Interfaces/IAudioClient.cs.meta +11 -0
  39. package/Runtime/Interfaces/IAudioFilter.cs +10 -0
  40. package/Runtime/Interfaces/IAudioFilter.cs.meta +11 -0
  41. package/Runtime/Interfaces/IAudioInput.cs +1 -24
  42. package/Runtime/Interfaces/IAudioOutput.cs +4 -30
  43. package/Runtime/Interfaces/IAudioOutputFactory.cs +2 -7
  44. package/Runtime/Interfaces/IAudioServer.cs +41 -0
  45. package/Runtime/Interfaces/IAudioServer.cs.meta +11 -0
  46. package/Runtime/Types/{ChatroomAudioSegment.cs → AudioFrame.cs} +7 -6
  47. package/Runtime/Types/VoiceSettings.cs +53 -0
  48. package/Runtime/Types/VoiceSettings.cs.meta +11 -0
  49. package/Samples~/Group Chat Sample/Prefabs/Mic Toggle.prefab +235 -0
  50. package/{CHANGELOG.md.meta → Samples~/Group Chat Sample/Prefabs/Mic Toggle.prefab.meta } +2 -2
  51. package/Samples~/Group Chat Sample/Prefabs/Peer View.prefab +851 -0
  52. package/Samples~/Group Chat Sample/Prefabs/Peer View.prefab.meta +7 -0
  53. package/Samples~/Group Chat Sample/Prefabs.meta +8 -0
  54. package/Samples~/Group Chat Sample/Scenes/GroupVoiceCallSample-Mirror.unity +2202 -0
  55. package/Samples~/Group Chat Sample/Scenes/GroupVoiceCallSample-Mirror.unity.meta +7 -0
  56. package/Samples~/Group Chat Sample/Scenes.meta +8 -0
  57. package/Samples~/Group Chat Sample/Scripts/GroupVoiceCallMirrorSample.cs +195 -0
  58. package/Samples~/Group Chat Sample/Scripts/GroupVoiceCallMirrorSample.cs.meta +11 -0
  59. package/Samples~/Group Chat Sample/Scripts/PeerView.cs +74 -0
  60. package/Samples~/Group Chat Sample/Scripts/PeerView.cs.meta +11 -0
  61. package/Samples~/Group Chat Sample/Scripts.meta +8 -0
  62. package/Samples~/Group Chat Sample/Sprites/mic.png +0 -0
  63. package/Samples~/Group Chat Sample/Sprites/mic.png.meta +88 -0
  64. package/Samples~/Group Chat Sample/Sprites/off.png +0 -0
  65. package/Samples~/Group Chat Sample/Sprites/off.png.meta +88 -0
  66. package/Samples~/Group Chat Sample/Sprites/on.png +0 -0
  67. package/Samples~/Group Chat Sample/Sprites/on.png.meta +88 -0
  68. package/Samples~/Group Chat Sample/Sprites/speaker.png +0 -0
  69. package/Samples~/Group Chat Sample/Sprites/speaker.png.meta +88 -0
  70. package/Samples~/Group Chat Sample/Sprites.meta +8 -0
  71. package/Samples~/Group Chat Sample.meta +8 -0
  72. package/package.json +19 -9
  73. package/CHANGELOG.md +0 -67
  74. package/Runtime/ChatroomAgent.cs +0 -260
  75. package/Runtime/Interfaces/IChatroomNetwork.cs +0 -125
  76. package/Runtime/Types/ChatroomAgentMode.cs +0 -22
  77. package/Runtime/Types/ChatroomPeerSettings.cs +0 -18
  78. package/Runtime/Types/ChatroomPeerSettings.cs.meta +0 -11
  79. /package/Runtime/Types/{ChatroomAudioSegment.cs.meta → AudioFrame.cs.meta} +0 -0
package/README.md CHANGED
@@ -1,5 +1,3 @@
1
- Note: Inbuilt implementations and samples have been removed from this repository. They'll be added to separate repositories soon.
2
-
3
1
  # UniVoice
4
2
  UniVoice is a voice chat/VoIP solution for Unity.
5
3
 
@@ -9,76 +7,60 @@ Some features of UniVoice:
9
7
  - ⚙ Peer specific settings. Don't want to listen to a peer? Mute them. Don't want someone listening to you? Mute yourself against them.
10
8
 
11
9
  - 🎨 Customize your audio input, output and networking layer.
12
- * 🎤 __Configurable Audio Input__: Decide what the input of your outgoing audio is. Let it be from [Unity's Microphone](https://docs.unity3d.com/ScriptReference/Microphone.html) class, or a live streaming audio, or an MP4 file on the disk.
10
+ * 🎤 __Configurable Audio Input__: UniVoice is audio input agnostic. It supports mic audio input out of the box and you can change the source of outgoing audio by implementing the `IAudioInput` interrace.
13
11
 
14
- * 🔊 __Configurable Audio Output__: Decide where the incoming peer audio goes. Let the output of incoming audio be [Unity AudioSource](https://docs.unity3d.com/ScriptReference/AudioSource.html) to play the audio in-game, or write it into an MP4 on the disk, or stream it to an online service.
12
+ * 🔊 __Configurable Audio Output__: UniVoice is audio output agnostic. Out of the box is supports playing peer audio using Unity AudioSource. You can divert incoming audio to anywhere you want by implementing the `IAudioOutput` interface.
15
13
 
16
- * 🌐 __Configurable Network__: Want to use UniVoice in a WLAN project using [Telepathy?](https://github.com/vis2k/Telepathy) Just adapt its API for UniVoice with a simple the `IChatroomNetwork` interface. Using your own backend for multiplayer? Create and expose your audio API and write a UniVoice implementation, again with the same interface.
17
-
18
- # Docs
19
- Manuals and sample projects are not available yet. For the API reference, please visit http://www.vatsalambastha.com/univoice
20
-
21
- # Usage
22
- ## Creating a chatroom agent
23
- - To be able to host and join voice chatrooms, you need a `ChatroomAgent` instance.
14
+ * 🌐 __Configurable Network__: UniVoice is network agnostic and supports Mirror out of the box. You can implement the `IAudioClient` and `IAudioServer` interfaces using the networking plugin of your choice to make it compatible with it.
15
+
16
+ * ✏️ __Audio Filters__: Modify outgoing and incoming audio by implementing the `IAudioFilter` interface. Gaussian blurring for denoising and Opus (Concentus) encoding & decoding for lower bandwidth consumption are provided out of the box.
24
17
 
18
+ ## Installation
19
+ ⚠️ [OpenUPM](https://openupm.com/packages/com.adrenak.univoice/?subPage=versions) may not have up to date releases. Install using NPM registry instead 👇
20
+
21
+ Ensure you have the NPM registry in the `manifest.json` file of your Unity project with the following scopes:
25
22
  ```
26
- var agent = new ChatroomAgent(IChatroomNetwork network, IAudioInput audioInput, IAudioOutput audioOutput);
23
+ "scopedRegistries": [
24
+ {
25
+ "name": "npmjs",
26
+ "url": "https://registry.npmjs.org",
27
+ "scopes": [
28
+ "com.npmjs",
29
+ "com.adrenak.univoice",
30
+ "com.adrenak.brw",
31
+ "com.adrenak.unimic",
32
+ "com.adrenak.concentus-unity"
33
+ ]
34
+ }
35
+ }
27
36
  ```
37
+ Then add `com.adrenak.univoice:x.y.z` to the `dependencies` in your `manifest.json` file (where x.y.z is the version you wish to install). The list of versions is available on [the UniVoice NPM page](https://www.npmjs.com/package/com.adrenak.univoice?activeTab=versions).
28
38
 
29
- ## Hosting and joining chatrooms
39
+ ## Docs
40
+ Am API reference is available: http://www.vatsalambastha.com/univoice
30
41
 
31
- Every peer in the chatroom is assigned an ID by the host. And every peer has a peer list, representing the other peers in the chatroom.
32
-
33
- - To get your ID
34
- `agent.Network.OwnID;`
42
+ ## Samples
43
+ This repository contains a sample scene for the Mirror network, which is the best place to see how UniVoice can be integrated into your project.
35
44
 
36
- - To get a list of the other peers in the chatroom, use this:
37
- `agent.Network.PeersIDs`
38
-
39
- `agent.Network` also provides methods to host or join a chatroom. Here is how you use them:
45
+ To try the sample, import Mirror and add the `UNIVOICE_MIRROR_NETWORK` compilation symbol to your project.
40
46
 
41
- ```
42
- // Host a chatroom using a name
43
- agent.Network.HostChatroom(optional_data);
44
-
45
- // Join an existing chatroom using a name
46
- agent.Network.JoinChatroom(optional_data);
47
+ ## Dependencies
48
+ [com.adrenak.brw](https://www.github.com/adrenak/brw) for reading and writing messages for communication. See `MirrorServer.cs` and `MirrorClient.cs` where they're used.
47
49
 
48
- // Leave the chatroom, if connected to one
49
- agent.Network.LeaveChatroom(optional_data);
50
+ [com.adrenak.unimic](https://www.github.com/adrenak/unimic) for easily capturing audio from any connected mic devices. See `UniMicInput.cs` for usage. Also used for streaming audio playback. See `StreamedAudioSourceOutput.cs` for usage.
50
51
 
51
- // Closes a chatroom, if is hosting one
52
- agent.Network.CloseChatroom(optional_data);
52
+ [com.adrenak.concentus-unity](https://www.github.com/adrenak/concentus-unity) for Opus encoding and decoding. See `ConcentusEncodeFilter.cs` and `ConcentusDecodeFilter.cs` for usage
53
53
 
54
- ```
55
- ## Muting Audio
56
- To mute everyone in the chatroom, use `agent.MuteOthers = true;` or set it to `false` to unmute them all.
57
-
58
- To mute yourself use `agent.MuteSelf = true;` or set it to `false` to unmute yourself. This will stop sending your audio to all the peers in the chatroom.
59
-
60
- For muting a specific peer, first get the peers settings object using this:
61
- ```
62
- agent.PeerSettings[id].muteThem = true; // where id belongs to the peer in question
63
- ```
64
-
65
- If you want to mute yourself towards a specific peer, use this:
66
- `agent.PeerSettings[id].muteSelf = true; // where id belongs to the peer in question`
67
-
68
- ## Events
69
- `agent.Network` provides several network related events. Refer to the [API reference](http://www.vatsalambastha.com/univoice/api/Adrenak.UniVoice.ChatroomAgent.html) for them.
70
-
71
- # License and Support
54
+ ## License and Support
72
55
  This project is under the [MIT license](https://github.com/adrenak/univoice/blob/master/LICENSE).
73
56
 
74
- Updates and maintenance are not guaranteed and the project is maintained by the original developer in his free time. Community contributions are welcome.
75
-
76
- __Commercial consultation and development can be arranged__ but is subject to schedule and availability.
57
+ Community contributions are welcome.
77
58
 
78
- # Contact
59
+ ## Contact
79
60
  The developer can be reached at the following links:
80
61
 
81
62
  [Website](http://www.vatsalambastha.com)
82
63
  [LinkedIn](https://www.linkedin.com/in/vatsalAmbastha)
83
64
  [GitHub](https://www.github.com/adrenak)
84
65
  [Twitter](https://www.twitter.com/vatsalAmbastha)
66
+ Discord: `adrenak#1934`
@@ -1,3 +1,20 @@
1
- {
2
- "name": "Adrenak.UniVoice.Runtime"
3
- }
1
+ {
2
+ "name": "Adrenak.UniVoice.Runtime",
3
+ "rootNamespace": "",
4
+ "references": [
5
+ "GUID:f87ecb857e752164ab814a3de8eb0262",
6
+ "GUID:1f776cd02c03a7b4280b6b649d7758e2",
7
+ "GUID:30817c1a0e6d646d99c048fc403f5979",
8
+ "GUID:725ee7191c021de4dbf9269590ded755",
9
+ "GUID:b118fd5a40c85ad4e9b38e8c4a42bbb1"
10
+ ],
11
+ "includePlatforms": [],
12
+ "excludePlatforms": [],
13
+ "allowUnsafeCode": false,
14
+ "overrideReferences": false,
15
+ "precompiledReferences": [],
16
+ "autoReferenced": true,
17
+ "defineConstraints": [],
18
+ "versionDefines": [],
19
+ "noEngineReferences": false
20
+ }
@@ -0,0 +1,138 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+
4
+ using UnityEngine;
5
+
6
+ namespace Adrenak.UniVoice {
7
+ /// <summary>
8
+ /// Handles a client session.
9
+ /// Requires an implementation of <see cref="IAudioClient{T}"/>, <see cref="IAudioInput"/> and <see cref="IAudioOutputFactory"/> each.
10
+ /// Allows adding input and output filters and handles their execution.
11
+ /// </summary>
12
+ /// <typeparam name="T"></typeparam>
13
+ public class ClientSession<T> : IDisposable {
14
+ /// <summary>
15
+ /// The <see cref="IAudioOutput"/> instances of each peer in the session
16
+ /// </summary>
17
+ public Dictionary<T, IAudioOutput> PeerOutputs { get; private set; } = new Dictionary<T, IAudioOutput>();
18
+
19
+ /// <summary>
20
+ /// The input <see cref="IAudioFilter"/> that will be applied to the outgoing audio for all the peers.
21
+ /// Note that filters are executed in the order they are present in this list
22
+ /// </summary>
23
+ public List<IAudioFilter> InputFilters { get; set; } = new List<IAudioFilter>();
24
+
25
+ /// <summary>
26
+ /// The output <see cref="IAudioFilter"/> that will be applied to the incoming audio for all the peers.
27
+ /// Note that filters are executed in the order they are present in this list.
28
+ /// </summary>
29
+ public List<IAudioFilter> OutputFilters { get; set; } = new List<IAudioFilter>();
30
+
31
+ public ClientSession(IAudioClient<T> client, IAudioInput input, IAudioOutputFactory outputFactory) {
32
+ Client = client;
33
+ Input = input;
34
+ OutputFactory = outputFactory;
35
+ }
36
+
37
+ /// <summary>
38
+ /// The <see cref="IAudioClient{T}"/> that's used for networking
39
+ /// </summary>
40
+ IAudioClient<T> client;
41
+ public IAudioClient<T> Client {
42
+ get => client;
43
+ set {
44
+ if(client != null)
45
+ client.Dispose();
46
+ client = value;
47
+
48
+ Client.OnLeft += () => {
49
+ foreach (var output in PeerOutputs)
50
+ output.Value.Dispose();
51
+ PeerOutputs.Clear();
52
+ };
53
+
54
+ Client.OnPeerJoined += id => {
55
+ try {
56
+ var output = outputFactory.Create();
57
+ PeerOutputs.Add(id, output);
58
+ }
59
+ catch (Exception e) {
60
+ Debug.LogException(e);
61
+ }
62
+ };
63
+
64
+ Client.OnPeerLeft += id => {
65
+ if (!PeerOutputs.ContainsKey(id))
66
+ return;
67
+
68
+ PeerOutputs[id].Dispose();
69
+ PeerOutputs.Remove(id);
70
+ };
71
+
72
+ client.OnReceivedPeerAudioFrame += (id, audioFrame) => {
73
+ if (!PeerOutputs.ContainsKey(id))
74
+ return;
75
+
76
+ if (OutputFilters != null) {
77
+ foreach (var filter in OutputFilters)
78
+ audioFrame = filter.Run(audioFrame);
79
+ }
80
+ if(audioFrame.samples.Length > 0)
81
+ PeerOutputs[id].Feed(audioFrame);
82
+ };
83
+ }
84
+ }
85
+
86
+ IAudioInput input;
87
+ /// <summary>
88
+ /// The <see cref="IAudioInput"/> that's used for sourcing outgoing audio
89
+ /// </summary>
90
+ public IAudioInput Input {
91
+ get => input;
92
+ set {
93
+ if(input != null)
94
+ input.Dispose();
95
+ input = value;
96
+ input.OnFrameReady += frame => {
97
+ if (InputFilters != null) {
98
+ foreach (var filter in InputFilters)
99
+ frame = filter.Run(frame);
100
+ }
101
+
102
+ if(frame.samples.Length > 0)
103
+ Client.SendAudioFrame(frame);
104
+ };
105
+ }
106
+ }
107
+
108
+ IAudioOutputFactory outputFactory;
109
+ /// <summary>
110
+ /// The <see cref="IAudioOutputFactory"/> that creates the <see cref="IAudioOutput"/> of peers
111
+ /// </summary>
112
+ public IAudioOutputFactory OutputFactory {
113
+ get => outputFactory;
114
+ set {
115
+ outputFactory = value;
116
+
117
+ foreach (var output in PeerOutputs)
118
+ output.Value.Dispose();
119
+ PeerOutputs.Clear();
120
+
121
+ foreach (var id in Client.PeerIDs) {
122
+ try {
123
+ var output = outputFactory.Create();
124
+ PeerOutputs.Add(id, output);
125
+ }
126
+ catch (Exception e) {
127
+ Debug.LogException(e);
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ public void Dispose() {
134
+ Client.Dispose();
135
+ Input.Dispose();
136
+ }
137
+ }
138
+ }
@@ -1,5 +1,5 @@
1
1
  fileFormatVersion: 2
2
- guid: 11ba8951ad10dc84a963ca3c32cda48a
2
+ guid: 923d8509d2c05f2479d6af7331dabe58
3
3
  MonoImporter:
4
4
  externalObjects: {}
5
5
  serializedVersion: 2
@@ -0,0 +1,55 @@
1
+ using System.IO;
2
+ using System.IO.Compression;
3
+ using System.Net.Sockets;
4
+ using System.Net;
5
+ using System.Runtime.Serialization.Formatters.Binary;
6
+ using System;
7
+ using UnityEngine;
8
+
9
+ namespace Adrenak.UniVoice {
10
+ public class Utils {
11
+ public class Bytes {
12
+ public static byte[] FloatsToBytes(float[] floats) {
13
+ int byteCount = sizeof(float) * floats.Length;
14
+ byte[] byteArray = new byte[byteCount];
15
+
16
+ Buffer.BlockCopy(floats, 0, byteArray, 0, byteCount);
17
+
18
+ return byteArray;
19
+ }
20
+
21
+ public static float[] BytesToFloats(byte[] bytes) {
22
+ int floatCount = bytes.Length / sizeof(float);
23
+ float[] floatArray = new float[floatCount];
24
+
25
+ Buffer.BlockCopy(bytes, 0, floatArray, 0, bytes.Length);
26
+
27
+ return floatArray;
28
+ }
29
+ }
30
+
31
+ public static class Audio {
32
+ static float[] audioF;
33
+ static float sumOfSquares;
34
+ public static float CalculateRMS(byte[] audio) {
35
+ audioF = Bytes.BytesToFloats(audio);
36
+
37
+ foreach(var x in audioF)
38
+ sumOfSquares += x * x;
39
+ return Mathf.Sqrt(sumOfSquares / audioF.Length);
40
+ }
41
+ }
42
+
43
+ public static class Network {
44
+ public static string LocalIPv4Address {
45
+ get {
46
+ using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, 0)) {
47
+ socket.Connect("8.8.8.8", 65530);
48
+ IPEndPoint endPoint = socket.LocalEndPoint as IPEndPoint;
49
+ return endPoint.Address.ToString();
50
+ }
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
@@ -1,5 +1,5 @@
1
1
  fileFormatVersion: 2
2
- guid: 95ab24cb9f03b8c4c8f22f567259def4
2
+ guid: a3530a416ca433448ab4c50826faefb4
3
3
  MonoImporter:
4
4
  externalObjects: {}
5
5
  serializedVersion: 2
@@ -0,0 +1,8 @@
1
+ fileFormatVersion: 2
2
+ guid: 7e8f97d020fcd2740b34e8d69e4f882d
3
+ folderAsset: yes
4
+ DefaultImporter:
5
+ externalObjects: {}
6
+ userData:
7
+ assetBundleName:
8
+ assetBundleVariant:
@@ -0,0 +1,80 @@
1
+ using Concentus;
2
+ using System;
3
+
4
+ /*
5
+ * Opus encoding and decoding are VERY important for any real world use of UniVoice as without
6
+ * encoding the size of audio data is much (over 10x) larger.
7
+ * For more info see https://www.github.com/adrenak/concentus-unity
8
+ */
9
+ namespace Adrenak.UniVoice.Filters {
10
+ /// <summary>
11
+ /// Decodes Opus encoded audio. Use this as a filter for incoming client audio.
12
+ /// </summary>
13
+ public class ConcentusDecodeFilter : IAudioFilter {
14
+ IOpusDecoder decoder;
15
+ float[] decodeBuffer;
16
+ int inputChannelCount;
17
+ int inputFrequency;
18
+ byte[] floatsToBytes;
19
+
20
+ /// <summary>
21
+ /// Creates a Concentus decode filter.
22
+ /// </summary>
23
+ /// <param name="decodeBufferLength">
24
+ /// The length of the decode buffer. Default is 11520 to fit a large sample
25
+ /// with frequency 48000, duration 120ms and 2 channels. This should be enough
26
+ /// for almost all scenarios. Increase if you need more.
27
+ /// </param>
28
+ public ConcentusDecodeFilter(int decodeBufferLength = 11520) {
29
+ decodeBuffer = new float[decodeBufferLength];
30
+ }
31
+
32
+ public AudioFrame Run(AudioFrame input) {
33
+ inputChannelCount = input.channelCount;
34
+ inputFrequency = input.frequency;
35
+
36
+ CreateNewDecoderIfNeeded();
37
+
38
+ var decodeResult = Decode(input.samples, out Span<float> decoded);
39
+ if (decodeResult > 0) {
40
+ floatsToBytes = Utils.Bytes.FloatsToBytes(decoded.ToArray());
41
+ return new AudioFrame {
42
+ timestamp = input.timestamp,
43
+ samples = floatsToBytes,
44
+ channelCount = inputChannelCount,
45
+ frequency = inputFrequency
46
+ };
47
+ }
48
+ else {
49
+ return new AudioFrame {
50
+ timestamp = input.timestamp,
51
+ samples = new byte[0],
52
+ channelCount = inputChannelCount,
53
+ frequency = inputFrequency
54
+ };
55
+ }
56
+ }
57
+
58
+ int Decode(Span<byte> toDecode, out Span<float> decoded) {
59
+ // Decode the Opus packet into preallocated buffer
60
+ int samplesPerChannel = decoder.Decode(toDecode, decodeBuffer, decodeBuffer.Length);
61
+
62
+ if (samplesPerChannel > 0) {
63
+ int totalSamples = samplesPerChannel * inputChannelCount; // Total samples across all channels
64
+ decoded = decodeBuffer.AsSpan(0, totalSamples); // Trim to valid samples
65
+ }
66
+ else {
67
+ decoded = Span<float>.Empty;
68
+ }
69
+
70
+ return samplesPerChannel; // Return number of samples per channel or 0 on failure
71
+ }
72
+
73
+ void CreateNewDecoderIfNeeded() {
74
+ if (decoder == null || decoder.SampleRate != inputFrequency || decoder.NumChannels != inputChannelCount) {
75
+ decoder?.Dispose();
76
+ decoder = OpusCodecFactory.CreateDecoder(inputFrequency, inputChannelCount);
77
+ }
78
+ }
79
+ }
80
+ }
@@ -1,5 +1,5 @@
1
1
  fileFormatVersion: 2
2
- guid: 76706adfc97ef7d44823aa51d618267d
2
+ guid: 31124d8beda4bc045b5ee5696140f0d8
3
3
  MonoImporter:
4
4
  externalObjects: {}
5
5
  serializedVersion: 2
@@ -0,0 +1,156 @@
1
+ using Concentus;
2
+ using Concentus.Enums;
3
+ using System;
4
+
5
+ /*
6
+ * Opus encoding and decoding are VERY important for any real world use of UniVoice as without
7
+ * encoding the size of audio data is much (over 10x) larger.
8
+ * For more info see https://www.github.com/adrenak/concentus-unity
9
+ */
10
+ namespace Adrenak.UniVoice.Filters {
11
+ /// <summary>
12
+ /// A filter that encodes audio using Opus. Use this as an output filter
13
+ /// to reduce the size of outgoing client audio
14
+ /// </summary>
15
+ public class ConcentusEncodeFilter : IAudioFilter {
16
+ public ConcentusFrequencies SamplingFrequency { get; private set; }
17
+ IOpusEncoder encoder;
18
+ IResampler resampler;
19
+ byte[] encodeBuffer;
20
+ float[] resampleBuffer;
21
+ int inputDuration;
22
+ int inputChannels;
23
+ int inputFrequency;
24
+ int resamplerChannelCount;
25
+ float[] bytesToFloats;
26
+ byte[] floatsToBytes;
27
+ int resamplerQuality;
28
+ int encoderComplexity;
29
+ int encoderBitrate;
30
+
31
+ /// <summary>
32
+ /// Creates a Concentus encode filter
33
+ /// </summary>
34
+ /// <param name="encodeFrequency">
35
+ /// The frequency the encoder runs at.
36
+ /// If the input audio frequency is different from this value, it will be resampled before encode.
37
+ /// </param>
38
+ /// <param name="resamplerQuality">Resampler quality [1, 10]</param>
39
+ /// <param name="encoderComplexity">Encoder complexity [1, 10]</param>
40
+ /// <param name="encoderBitrate">Encoder bitrate [16000, 256000]. Set to -1 to enable variable bitrate.</param>
41
+ /// <param name="encodeBufferLength">
42
+ /// The length of the encode buffer. Default is 46080 to fit a large sample
43
+ /// with frequency 48000, duration 120ms and 2 channels. This should be enough
44
+ /// for almost all scenarios. Increase if you need more.
45
+ /// </param>
46
+ public ConcentusEncodeFilter(
47
+ ConcentusFrequencies encodeFrequency = ConcentusFrequencies.Frequency_16000,
48
+ int resamplerQuality = 2,
49
+ int encoderComplexity = 3,
50
+ int encoderBitrate = 64000,
51
+ int encodeBufferLength = 46080
52
+ ) {
53
+ SamplingFrequency = encodeFrequency;
54
+ this.resamplerQuality = Math.Clamp(resamplerQuality, 1, 10);
55
+ this.encoderComplexity = Math.Clamp(encoderComplexity, 1, 10);
56
+ this.encoderBitrate = Math.Clamp(encoderBitrate, 16000, 256000);
57
+ encodeBuffer = new byte[encodeBufferLength];
58
+ }
59
+
60
+ public AudioFrame Run(AudioFrame input) {
61
+ inputChannels = input.channelCount;
62
+ inputFrequency = input.frequency;
63
+ inputDuration = ((input.samples.Length / 4) * 1000) / (input.frequency * input.channelCount);
64
+
65
+ CreateNewResamplerAndEncoderIfNeeded();
66
+
67
+ Span<float> toEncode;
68
+ bytesToFloats = Utils.Bytes.BytesToFloats(input.samples);
69
+ toEncode = bytesToFloats;
70
+
71
+ if (inputFrequency != (int)SamplingFrequency)
72
+ toEncode = Resample(bytesToFloats);
73
+
74
+ var encodeResult = Encode(toEncode, out Span<byte> encoded);
75
+ if (encodeResult > 0) {
76
+ floatsToBytes = encoded.ToArray();
77
+ return new AudioFrame {
78
+ timestamp = input.timestamp,
79
+ channelCount = inputChannels,
80
+ samples = floatsToBytes,
81
+ frequency = (int)SamplingFrequency
82
+ };
83
+ }
84
+ else {
85
+ return new AudioFrame {
86
+ timestamp = input.timestamp,
87
+ channelCount = inputChannels,
88
+ samples = new byte[0],
89
+ frequency = (int)SamplingFrequency
90
+ };
91
+ }
92
+ }
93
+
94
+ void CreateNewResamplerAndEncoderIfNeeded() {
95
+ if (resampleBuffer == null || resampleBuffer.Length != (int)SamplingFrequency * inputDuration * inputChannels / 1000)
96
+ resampleBuffer = new float[(int)SamplingFrequency * inputDuration * inputChannels / 1000];
97
+
98
+ if (resampler == null) {
99
+ resamplerChannelCount = inputChannels;
100
+ resampler = ResamplerFactory.CreateResampler(inputChannels, inputFrequency, (int)SamplingFrequency, resamplerQuality);
101
+ }
102
+ else {
103
+ resampler.GetRates(out int in_rate, out int out_rate);
104
+ if (in_rate != inputFrequency || out_rate != (int)SamplingFrequency || resamplerChannelCount != inputChannels) {
105
+ resampler.Dispose();
106
+ resamplerChannelCount = inputChannels;
107
+ resampler = ResamplerFactory.CreateResampler(inputChannels, inputFrequency, (int)SamplingFrequency, resamplerQuality);
108
+ }
109
+ }
110
+
111
+ if (encoder == null || encoder.SampleRate != (int)SamplingFrequency || encoder.NumChannels != inputChannels) {
112
+ encoder?.Dispose();
113
+ encoder = OpusCodecFactory.CreateEncoder((int)SamplingFrequency, inputChannels, OpusApplication.OPUS_APPLICATION_VOIP);
114
+ encoder.Complexity = encoderComplexity;
115
+ if(encoderBitrate == -1)
116
+ encoder.UseVBR = true;
117
+ else {
118
+ encoder.UseVBR = false;
119
+ encoder.Bitrate = encoderBitrate;
120
+ }
121
+ }
122
+ }
123
+
124
+ Span<float> Resample(Span<float> samples) {
125
+ // Calculate input and output lengths
126
+ int in_len = samples.Length / inputChannels; // Input samples per channel
127
+ int out_len = (int)SamplingFrequency * inputDuration / 1000; // Output samples per channel
128
+
129
+ // Perform resampling into preallocated buffer
130
+ resampler.ProcessInterleaved(samples, ref in_len, resampleBuffer, ref out_len);
131
+
132
+ // Return only the valid portion of resampled data
133
+ return resampleBuffer.AsSpan(0, out_len * inputChannels); // Trim to valid samples
134
+ }
135
+
136
+ int Encode(Span<float> toEncode, out Span<byte> encoded) {
137
+ int frameSize = (int)SamplingFrequency * inputDuration / 1000; // Samples per channel
138
+ int totalSamples = frameSize * inputChannels; // Total interleaved samples
139
+
140
+ if (toEncode.Length < totalSamples) {
141
+ encoded = Span<byte>.Empty;
142
+ return 0;
143
+ }
144
+
145
+ // Use preallocated encodeBuffer
146
+ int result = encoder.Encode(toEncode.Slice(0, totalSamples), frameSize, encodeBuffer, encodeBuffer.Length);
147
+
148
+ if (result > 0)
149
+ encoded = encodeBuffer.AsSpan(0, result); // Trim to actual encoded size
150
+ else
151
+ encoded = Span<byte>.Empty;
152
+
153
+ return result; // Return number of bytes written or 0 on failure
154
+ }
155
+ }
156
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: 7e548eaedfb8c1b49812a12b3fba2c8a
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
@@ -0,0 +1,31 @@
1
+ namespace Adrenak.UniVoice.Filters {
2
+ /// <summary>
3
+ /// Defines supported sampling frequencies for the Opus codec via Concentus.
4
+ /// </summary>
5
+ public enum ConcentusFrequencies : int {
6
+ /// <summary>
7
+ /// 8 kHz sampling frequency, typically used for narrowband audio.
8
+ /// </summary>
9
+ Frequency_8000 = 8000,
10
+
11
+ /// <summary>
12
+ /// 12 kHz sampling frequency, suitable for medium-band audio.
13
+ /// </summary>
14
+ Frequency_12000 = 12000,
15
+
16
+ /// <summary>
17
+ /// 16 kHz sampling frequency, commonly used for wideband speech.
18
+ /// </summary>
19
+ Frequency_16000 = 16000,
20
+
21
+ /// <summary>
22
+ /// 24 kHz sampling frequency, providing good quality for music and audio.
23
+ /// </summary>
24
+ Frequency_24000 = 24000,
25
+
26
+ /// <summary>
27
+ /// 48 kHz sampling frequency, offering the highest audio quality.
28
+ /// </summary>
29
+ Frequency_48000 = 48000,
30
+ }
31
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: 53ba54c62b14cf4439d3b71fe7b1d08c
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant: