namespace Nonemm.Recorder.Tests; public class OpusFileWriterTests : IDisposable { private readonly string path = Path.Combine(Path.GetTempPath(), $"recorder-{Guid.NewGuid():N}.opus"); public void Dispose() => File.Delete(path); private void Write(int channels, int frames, Func sample) { using OpusFileWriter writer = new(File.Create(path), channels, 32); for (int frame = 0; frame < frames; frame++) { float[] interleaved = new float[OpusFileWriter.FrameSamples * channels]; for (int i = 0; i < OpusFileWriter.FrameSamples; i++) { for (int channel = 0; channel < channels; channel++) { interleaved[i * channels + channel] = sample(channel, frame * OpusFileWriter.FrameSamples + i); } } writer.WriteFrame(interleaved); } } private static float Tone(int hertz, int index) => 0.5f * (float)Math.Sin(2 * Math.PI * hertz * index / RecorderEngine.SampleRate); [Fact] public void Each_channel_comes_back_on_its_own_channel() { Write(3, 10, (channel, index) => channel == 1 ? 0 : Tone(channel == 0 ? 440 : 1000, index)); OggOpusReader file = OggOpusReader.Read(path); Assert.Equal(3, file.Channels); Assert.Equal(255, file.MappingFamily); Assert.True(OggOpusReader.Rms(file.Pcm[0], 4800, 9600) > 0.25f); Assert.True(OggOpusReader.Rms(file.Pcm[1], 4800, 9600) < 0.01f); Assert.True(OggOpusReader.Rms(file.Pcm[2], 4800, 9600) > 0.25f); } [Fact] public void The_end_granule_is_the_length_of_the_audio_plus_the_pre_skip() { Write(2, 10, (_, index) => Tone(440, index)); OggOpusReader file = OggOpusReader.Read(path); Assert.True(file.HasEndMark); Assert.Equal(10L * OpusFileWriter.FrameSamples + file.PreSkip, file.FinalGranule); } [Fact] public void One_channel_is_written_as_a_plain_mono_file() { Write(1, 5, (_, index) => Tone(440, index)); OggOpusReader file = OggOpusReader.Read(path); Assert.Equal(1, file.Channels); Assert.Equal(0, file.MappingFamily); Assert.True(OggOpusReader.Rms(file.Pcm[0], 4800, 9600) > 0.25f); } }