using MeetingAssistant.Recording; using Microsoft.Extensions.Logging.Abstractions; namespace MeetingAssistant.Tests; public sealed class AudioMixingTests { [Fact] public async Task CompositeAudioSourceMixesMicrophoneAndSystemAudioIntoOnePcmStream() { var microphone = new FixedAudioSource(Pcm16(10_000)); var system = new FixedAudioSource(Pcm16(20_000)); var source = new CompositeMeetingAudioSource( microphone, system, NullLogger.Instance); var chunks = await ReadChunks(source); Assert.Single(chunks); Assert.Equal(30_000, BitConverter.ToInt16(chunks[0].Pcm)); } [Fact] public async Task CompositeAudioSourceClampsMixedSamples() { var microphone = new FixedAudioSource(Pcm16(30_000)); var system = new FixedAudioSource(Pcm16(10_000)); var source = new CompositeMeetingAudioSource( microphone, system, NullLogger.Instance); var chunks = await ReadChunks(source); Assert.Equal(short.MaxValue, BitConverter.ToInt16(chunks[0].Pcm)); } private static async Task> ReadChunks(IMeetingAudioSource source) { var chunks = new List(); await foreach (var chunk in source.CaptureAsync(CancellationToken.None)) { chunks.Add(chunk); } return chunks; } private static byte[] Pcm16(short sample) { return BitConverter.GetBytes(sample); } private sealed class FixedAudioSource : IMeetingAudioSource { private readonly AudioChunk chunk; public FixedAudioSource(byte[] pcm) { chunk = new AudioChunk(pcm, 16000, 1); } public async IAsyncEnumerable CaptureAsync( [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken) { await Task.Yield(); yield return chunk; } } }