Skip to content

Instantly share code, notes, and snippets.

@Exorion1er
Created October 23, 2024 20:36
Show Gist options
  • Save Exorion1er/b56b4e2d6e9b4bd43063afb69e1cbb0b to your computer and use it in GitHub Desktop.
Save Exorion1er/b56b4e2d6e9b4bd43063afb69e1cbb0b to your computer and use it in GitHub Desktop.
NAudio and Concentus Record -> Encode -> Decode -> Playback trivial example
using Concentus;
using Concentus.Enums;
using NAudio.Wave;
internal class Program {
private static bool stopFlag = false;
private static List<short> pcmAccumulationBuffer = new List<short>();
private static IOpusEncoder encoder;
private static IOpusDecoder decoder;
private static BufferedWaveProvider waveProvider;
private static WaveInEvent waveIn;
private static WaveOutEvent waveOut;
private const int FrameSize = 960; // 20ms frame for Opus at 48kHz
private static void Main(string[] args) {
Console.WriteLine("Recording, encoding, decoding, and playing back. Press Enter to stop.");
Initialize();
StartRecording();
// Wait for user input to stop recording
Console.ReadLine();
StopRecording();
}
private static void Initialize() {
// Initialize encoder and decoder
encoder = OpusCodecFactory.CreateEncoder(48000, 1, OpusApplication.OPUS_APPLICATION_AUDIO);
decoder = OpusCodecFactory.CreateDecoder(48000, 1);
// Initialize audio devices
waveIn = new WaveInEvent {
DeviceNumber = 0, // Use default recording device
WaveFormat = new WaveFormat(48000, 1) // 48kHz, Mono
};
waveProvider = new BufferedWaveProvider(waveIn.WaveFormat) {
BufferDuration = TimeSpan.FromSeconds(5), // Buffer duration
DiscardOnBufferOverflow = false
};
waveOut = new WaveOutEvent();
waveOut.Init(waveProvider);
}
private static void StartRecording() {
waveIn.DataAvailable += OnDataAvailable;
waveOut.Play();
try {
waveIn.StartRecording();
Console.WriteLine("Recording started...");
} catch (Exception ex) {
Console.WriteLine($"Error starting recording: {ex.Message}");
}
}
private static void StopRecording() {
stopFlag = true;
waveIn.StopRecording();
Console.WriteLine("Recording stopped.");
// Wait until playback completes
while (waveOut.PlaybackState == PlaybackState.Playing || waveProvider.BufferedBytes > 0) {
Thread.Sleep(100);
}
Console.WriteLine("Playback finished.");
// Cleanup
waveIn.Dispose();
waveOut.Dispose();
}
private static void OnDataAvailable(object sender, WaveInEventArgs a) {
if (stopFlag)
return;
// Convert buffer to short PCM samples and accumulate
AddPcmDataToBuffer(a.Buffer, a.BytesRecorded);
ProcessAudioFrames();
}
private static void AddPcmDataToBuffer(byte[] buffer, int bytesRecorded) {
var pcmBuffer = new short[bytesRecorded / 2];
Buffer.BlockCopy(buffer, 0, pcmBuffer, 0, bytesRecorded);
pcmAccumulationBuffer.AddRange(pcmBuffer);
}
private static void ProcessAudioFrames() {
// Process accumulated PCM data in chunks of frameSize
while (pcmAccumulationBuffer.Count >= FrameSize) {
var frame = pcmAccumulationBuffer.GetRange(0, FrameSize).ToArray();
pcmAccumulationBuffer.RemoveRange(0, FrameSize);
EncodeDecodeAndPlay(frame);
}
}
private static void EncodeDecodeAndPlay(short[] frame) {
byte[] encoded = new byte[4000];
try {
int encodedLength = encoder.Encode(frame, FrameSize, encoded, encoded.Length);
DecodeAndPlay(encoded, encodedLength);
} catch (Exception ex) {
Console.WriteLine($"Error during encoding: {ex.Message}");
}
}
private static void DecodeAndPlay(byte[] encoded, int encodedLength) {
short[] decodedBuffer = new short[FrameSize];
try {
int decodedLength = decoder.Decode(encoded.AsSpan(0, encodedLength), decodedBuffer.AsSpan(), FrameSize);
// Add decoded audio to the wave provider
var byteBuffer = new byte[decodedLength * sizeof(short)];
Buffer.BlockCopy(decodedBuffer, 0, byteBuffer, 0, byteBuffer.Length);
waveProvider.AddSamples(byteBuffer, 0, byteBuffer.Length);
} catch (Exception ex) {
Console.WriteLine($"Error during decoding: {ex.Message}");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment