-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathOggOpus.cs
175 lines (143 loc) · 5.61 KB
/
OggOpus.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
using System.IO;
using System.Text;
using System;
using System.Threading;
namespace NuclearVOIP
{
internal class OggOpus: AbstractStream<byte[]>, IDisposable
{
private static ReadOnlySpan<byte> EncoderVendor => "NUCLEARVOIP"u8;
private readonly AbstractStream<byte[]> frames = new();
private readonly Ogg.Stream oggStream;
private readonly int samplesPerFrame;
private int granulePos = 0;
public readonly OpusEncoder encoder;
public OggOpus(Ogg file, int frequency, byte channels, int msPerPacket)
{
oggStream = new Ogg.Stream(file);
samplesPerFrame = msPerPacket * 48;
encoder = new(frequency);
base.Write(EncodeOpusHeaders(channels, (short)encoder.lookahead));
frames.OnData += OnFrame;
encoder.Pipe(this);
}
public override void Write(byte[] data)
{
frames.Write(data);
}
public override void Write(byte[][] data)
{
frames.Write(data);
}
// Consider allowing a single frame to stay unflushed
public void Flush()
{
byte[][]? data = frames.Read(frames.Count());
if (data == null)
return;
base.Write(EncodeOpus(data, false));
}
public void Dispose() => Close();
public void Close()
{
encoder.Close();
byte[][]? data = frames.Read(frames.Count());
if (data == null)
return;
base.Write(EncodeOpus(data, true));
}
private void OnFrame(StreamArgs<byte[]> args)
{
int count = frames.Count();
if (count + args.data.Length < 51) // Leave last frame for closing reasons
return;
args.Handle();
byte[][]? prefix = frames.Read(count);
byte[][]? buf = prefix == null ? args.data[..^1] : [..prefix, ..(args.data[..^1])];
base.Write(EncodeOpus(buf, false));
frames.Write(args.data[^1]);
}
private byte[][] EncodeOpusHeaders(byte channels, short preskip)
{
if (oggStream.Sequence != 0)
throw new InvalidOperationException("OpusHeaders not first pages");
byte[][] headers = new byte[2][];
{
byte[] buf = new byte[19];
MemoryStream mem = new(buf);
BinaryWriter writer = new(mem);
writer.Write(Encoding.ASCII.GetBytes("OpusHead"));
writer.Write((byte)1);
writer.Write(channels);
writer.Write(preskip);
writer.Write(48000);
writer.Write((short)0);
writer.Write((byte)0);
writer.Close();
headers[0] = oggStream.EncodePage([buf], 0);
}
{
byte[] buf = new byte[27];
MemoryStream mem = new(buf);
BinaryWriter writer = new(mem);
writer.Write(Encoding.ASCII.GetBytes("OpusTags"));
writer.Write(EncoderVendor.Length);
writer.Write(EncoderVendor);
writer.Write(0);
writer.Close();
headers[1] = oggStream.EncodePage([buf], 0);
}
return headers;
}
private long GetGranule(byte[][] segments)
{
int frames = 0;
for (int i = segments.Length - 1; i >= 0; i--)
if (segments[i].Length != 255)
frames++;
if (frames == 0)
return -1;
return Interlocked.Add(ref granulePos, frames * samplesPerFrame);
}
private byte[][] EncodeOpus(byte[][] frames, bool end)
{
/*int endPoint = end ? frames.Length - 1 : frames.Length;
for (int i = 0; i < endPoint; i++)
{
byte[] oldFrame = frames[i];
int internalLength = oldFrame.Length - 1;
switch (internalLength)
{
case > 251:
frames[i] = new byte[oldFrame.Length + 2];
frames[i][0] = oldFrame[0];
Array.Copy(oldFrame, 1, frames[i], 3, oldFrame.Length - 1);
frames[i][1] = (byte)(internalLength & 0xFF);
frames[i][2] = (byte)((internalLength - frames[i][1]) >>> 2);
break;
case 0:
frames[i] = [0x00, 0x00];
break;
default:
frames[i] = new byte[oldFrame.Length + 1];
frames[i][0] = oldFrame[0];
Array.Copy(oldFrame, 1, frames[i], 2, oldFrame.Length - 1);
frames[i][1] = (byte)internalLength;
break;
}
}*/
byte[][] segments = Ogg.Stream.SplitSegments(frames);
byte[][] pages = new byte[(segments.Length / 255) + 1][];
bool continuing = false;
for (int i = 0; i < pages.Length; i++)
{
int nSegments = i == pages.Length - 1 ? segments.Length % 255 : 255;
int index = i * 255;
byte[][] subarray = segments[index..(index + nSegments)];
pages[i] = oggStream.EncodePage(subarray, GetGranule(segments), continuing, end && (i == pages.Length - 1));
continuing = subarray[^1].Length == 255;
}
return pages;
}
}
}