在.NET Core中录制音频

15

正如标题所示,我希望在.NET Core中捕获来自麦克风的音频。对我来说跨平台很重要。我已经在Windows、Linux和OSX上使用OpenCV进行了大量的视频处理。音频是这个难题中缺失的一部分。


OpenCV是一个库,最终调用特定于操作系统的API。要从.NET中使用它,您必须使用Interop或托管包装器。您应该寻找所需音频库的托管包装器。 - Panagiotis Kanavos
@PanagiotisKanavos 我找到了 OpenTK.NETCore,它可以跨平台使用音频。 - Wojtek Turowicz
OpenTK.NetCore已被弃用,建议使用OpenTK.NetStandard。 - bre_dev
4个回答

4

OpenTK.NETCore是OpenTK的跨平台移植版,非常优秀。

使用它的AudioCapture类,我能够在不同的操作系统平台上捕获麦克风数据。

using (var audioCapture = new AudioCapture(_recorders.First(), _sampling_rate, ALFormat.Mono16, buffer_length_samples))
{
    audioCapture.Start();

    int available_samples = audioCapture.AvailableSamples;

    _buffer = _pool.Rent(MathHelper.NextPowerOfTwo((int)((available_samples * _sampleToByte / (double)BlittableValueType.StrideOf(_buffer)) + 0.5)));

    if (available_samples > 0)
    {
        audioCapture.ReadSamples(_buffer, available_samples);
    }
    else
    {
        _pool.Return(_buffer);
    }
}

编辑:基于 OpenTK 示例中的 Parrot 项目。


请发送OpenTK示例中鹦鹉项目的网址! - Ali Rasouli
我认为他指的是这些例子:https://github.com/mono/opentk/tree/master/Source/Examples/OpenAL/1.1 - Kappacake
在阅读和运行 Parrot 示例一段时间后,我意识到它只是在捕获音频时回放它。你怎么把这个音频保存到文件中?你实现过这个功能吗? - Kappacake
如何将捕获的音频保存为wav文件?有关如何保存它的建议吗? - bre_dev
要将其保存为WAV,请查看此答案:https://dev59.com/jb3pa4cB1Zd3GeqPbUzr#63962284 - Andy

1
我写了一个简单的程序,使用OpenTK.OpenAL nuGet包打印当前音量。
using OpenTK.Audio.OpenAL;
using System;

Console.WriteLine("Default Mic:\n"+ ALC.GetString(ALDevice.Null, AlcGetString.CaptureDefaultDeviceSpecifier));
Console.WriteLine("Mic List:\n"+ string.Join("\n", ALC.GetString(ALDevice.Null, AlcGetStringList.CaptureDeviceSpecifier)));

int bufferLength = 10 * 16000;//10 sec
ALCaptureDevice mic= ALC.CaptureOpenDevice(null,16000,ALFormat.Mono8, bufferLength);//opens default mic //null specifies default 
Console.WriteLine("Using:");
Console.WriteLine(ALC.GetString(new ALDevice(mic.Handle), AlcGetString.DeviceSpecifier));

ALC.CaptureStart(mic);
byte[] buffer = new byte[bufferLength];
for (int i = 0; i < 1000; ++i)
{
    Thread.Sleep(100);
    int samplesAvailable = ALC.GetAvailableSamples(mic);
    ALC.CaptureSamples(mic, buffer, samplesAvailable);

    if(samplesAvailable>0)
        //Console.WriteLine(new string('|', (buffer[..samplesAvailable].Max())*80/256 ));
        Console.WriteLine(new string('|', (buffer[..samplesAvailable].Select(x => Math.Abs((int)(x)-128)).Max()) * 80/ 128));
}
ALC.CaptureStop(mic);

ALC.CaptureCloseDevice(mic);

0

我之前在.NET Core上编写过一个录音应用程序,但现在它不再是我的财产了... 不过,这里是我在开发时使用的

LibsoundIO Sharp能够扩展配置到诸如输入或输出的采样率等内容,并且有很棒的示例,比如:

class Record
    {
        static SoundIORingBuffer ring_buffer = null;

        static SoundIOFormat [] prioritized_formats = {
            SoundIODevice.Float32NE,
            SoundIODevice.Float32FE,
            SoundIODevice.S32NE,
            SoundIODevice.S32FE,
            SoundIODevice.S24NE,
            SoundIODevice.S24FE,
            SoundIODevice.S16NE,
            SoundIODevice.S16FE,
            SoundIODevice.Float64NE,
            SoundIODevice.Float64FE,
            SoundIODevice.U32NE,
            SoundIODevice.U32FE,
            SoundIODevice.U24NE,
            SoundIODevice.U24FE,
            SoundIODevice.U16NE,
            SoundIODevice.U16FE,
            SoundIOFormat.S8,
            SoundIOFormat.U8,
            SoundIOFormat.Invalid,
        };

        static readonly int [] prioritized_sample_rates = {
            48000,
            44100,
            96000,
            24000,
            0,
        };

        public static int Main (string [] args)
        {
            string device_id = null;
            string backend_name = null;
            bool raw = false;
            string outfile = null;

            foreach (var arg in args) {
                switch (arg) {
                case "--raw":
                    raw = true;
                    continue;
                default:
                    if (arg.StartsWith ("--backend:"))
                        backend_name = arg.Substring (arg.IndexOf (':') + 1);
                    else if (arg.StartsWith ("--device:"))
                        device_id = arg.Substring (arg.IndexOf (':') + 1);
                    else
                        outfile = arg;
                    continue;
                }
            }

            var api = new SoundIO ();

            var backend = backend_name == null ? SoundIOBackend.None : (SoundIOBackend)Enum.Parse (typeof (SoundIOBackend), backend_name);
            if (backend == SoundIOBackend.None)
                api.Connect ();
            else
                api.ConnectBackend (backend);
            Console.WriteLine ("backend: " + api.CurrentBackend);

            api.FlushEvents ();

            var device = device_id == null ? api.GetInputDevice (api.DefaultInputDeviceIndex) :
                Enumerable.Range (0, api.InputDeviceCount)
                .Select (i => api.GetInputDevice (i))
                .FirstOrDefault (d => d.Id == device_id && d.IsRaw == raw);
            if (device == null) {
                Console.Error.WriteLine ("device " + device_id + " not found.");
                return 1;
            }
            Console.WriteLine ("device: " + device.Name);
            if (device.ProbeError != 0) {
                Console.Error.WriteLine ("Cannot probe device " + device_id + ".");
                return 1;
            }

            var sample_rate = prioritized_sample_rates.First (sr => device.SupportsSampleRate (sr));

            var fmt = prioritized_formats.First (f => device.SupportsFormat (f));

            var instream = device.CreateInStream ();
            instream.Format = fmt;
            instream.SampleRate = sample_rate;
            instream.ReadCallback = (fmin, fmax) => read_callback (instream, fmin, fmax);
            instream.OverflowCallback = () => overflow_callback (instream);

            instream.Open ();

            const int ring_buffer_duration_seconds = 30;
            int capacity = (int)(ring_buffer_duration_seconds * instream.SampleRate * instream.BytesPerFrame);
            ring_buffer = api.CreateRingBuffer (capacity);
            var buf = ring_buffer.WritePointer;

            instream.Start ();

            Console.WriteLine ("Type CTRL+C to quit by killing process...");
            using (var fs = File.OpenWrite (outfile)) {
                var arr = new byte [capacity];
                unsafe {
                    fixed (void* arrptr = arr) {
                        for (; ; ) {
                            api.FlushEvents ();
                            Thread.Sleep (1000);
                            int fill_bytes = ring_buffer.FillCount;
                            var read_buf = ring_buffer.ReadPointer;

                            Buffer.MemoryCopy ((void*)read_buf, arrptr, fill_bytes, fill_bytes);
                            fs.Write (arr, 0, fill_bytes);
                            ring_buffer.AdvanceReadPointer (fill_bytes);
                        }
                    }
                }
            }
            instream.Dispose ();
            device.RemoveReference ();
            api.Dispose ();
            return 0;
        }

        static void read_callback (SoundIOInStream instream, int frame_count_min, int frame_count_max)
        {
            var write_ptr = ring_buffer.WritePointer;
            int free_bytes = ring_buffer.FreeCount;
            int free_count = free_bytes / instream.BytesPerFrame;

            if (frame_count_min > free_count)
                throw new InvalidOperationException ("ring buffer overflow"); // panic()

            int write_frames = Math.Min (free_count, frame_count_max);
            int frames_left = write_frames;

            for (; ; ) {
                int frame_count = frames_left;

                var areas = instream.BeginRead (ref frame_count);

                if (frame_count == 0)
                    break;

                if (areas.IsEmpty) {
                    // Due to an overflow there is a hole. Fill the ring buffer with
                    // silence for the size of the hole.
                    for (int i = 0; i < frame_count * instream.BytesPerFrame; i++)
                        Marshal.WriteByte (write_ptr + i, 0);
                    Console.Error.WriteLine ("Dropped {0} frames due to internal overflow", frame_count);
                } else {
                    for (int frame = 0; frame < frame_count; frame += 1) {
                        int chCount = instream.Layout.ChannelCount;
                        int copySize = instream.BytesPerSample;
                        unsafe {
                            for (int ch = 0; ch < chCount; ch += 1) {
                                var area = areas.GetArea (ch);
                                Buffer.MemoryCopy ((void*)area.Pointer, (void*)write_ptr, copySize, copySize);
                                area.Pointer += area.Step;
                                write_ptr += copySize;
                            }
                        }
                    }
                }

                instream.EndRead ();

                frames_left -= frame_count;
                if (frames_left <= 0)
                    break;
            }

            int advance_bytes = write_frames * instream.BytesPerFrame;
            ring_buffer.AdvanceWritePointer (advance_bytes);
        }

        static int overflow_callback_count = 0;
        static void overflow_callback (SoundIOInStream instream)
        {
            Console.Error.WriteLine ("overflow {0}", overflow_callback_count++);
        }
    }

改编自sio-record/Program.cs

就.NET Core而言,您可以将其留在单例类中作为独立项目,或者如果您正在使用具有DI容器的项目,则可以将其作为单例简单地投入其中。

话虽如此,您需要libsoundio使事情正常工作,因为毕竟它就像音频的“Java”,而libsoundio-sharp是C#的包装器。干杯!


谢谢,我已经使用OpenTK.NETCore库和它的OpenAL支持解决了问题。 - Wojtek Turowicz

0

很遗憾,我不知道有哪个API或库可以为您完成这项工作。

如果其他人也不知道,那么您有两个选择:

  1. 自己编写跨平台的API(可以查看CoreFx或CoreFxLab存储库),在Windows上,您可以使用P/Invoke来使用系统dll,在其他平台上,您需要其他东西
  2. 使用跨平台录制器可执行文件,并从您的.NET Core功能与其进行交互

1
谢谢,我已经使用OpenTK.NETCore库和它的OpenAL支持解决了问题。 - Wojtek Turowicz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接