C# NAudio通过yeti lame包装器流到mp3文件

C# NAudio通过yeti lame包装器流到mp3文件,c#,.net,mp3,naudio,lame,C#,.net,Mp3,Naudio,Lame,我有以下代码来记录音频输入和输出 using System; using System.Diagnostics; using System.IO; using NAudio.Wave; using Yeti.MMedia.Mp3; namespace SoundRecording { public class SoundManager { private WaveInEvent _waveIn; private WaveFileWriter _w

我有以下代码来记录音频输入和输出

using System;
using System.Diagnostics;
using System.IO;
using NAudio.Wave;
using Yeti.MMedia.Mp3;

namespace SoundRecording
{
    public class SoundManager
    {
        private WaveInEvent _waveIn;
        private WaveFileWriter _waveInFile;
        private WasapiLoopbackCapture _waveOut;
        private WaveFileWriter _waveOutFile;
        private Process _lameProcess;

        public void StartRecording()
        {
            InitLame();
            DateTime dtNow = DateTime.Now;

            try
            {
                InitAudioOut(dtNow);
            }
            catch
            {
            }

            try
            {
                InitAudioIn(dtNow);
            }
            catch
            {
            }
        }

        private void InitLame()
        {
            string outputFileName = @"c:\Rec\test.mp3";
            _lameProcess = new Process();
            _lameProcess.StartInfo.FileName = @"lame.exe";
            _lameProcess.StartInfo.UseShellExecute = false;
            _lameProcess.StartInfo.RedirectStandardInput = true;
            _lameProcess.StartInfo.Arguments = "-r -s 44.1 -h -b 256 --bitwidth 32 - \"" + outputFileName + "\"";
            _lameProcess.StartInfo.CreateNoWindow = true;
            _lameProcess.Start();
        }

        private void InitAudioIn(DateTime dtNow)
        {
            string pathIn = @"C:\Rec\(" + dtNow.ToString("HH-mm-ss") + " " + dtNow.ToString("dd-MM-yyyy") + " IN).wav";

            _waveIn = new WaveInEvent();
            _waveIn.WaveFormat = new WaveFormat(8000, 1);
            _waveIn.DataAvailable += WaveInDataAvailable;
            _waveIn.RecordingStopped += WaveInRecordStopped;

            _waveInFile = new WaveFileWriter(pathIn, _waveIn.WaveFormat);

            _waveIn.StartRecording();
        }

        private void InitAudioOut(DateTime recordMarker)
        {
            string pathOut = @"C:\Rec\(" + recordMarker.ToString("HH-mm-ss") + " " + recordMarker.ToString("dd-MM-yyyy") + " OUT).mp3";

            _waveOut = new WasapiLoopbackCapture();
            //_waveOut.WaveFormat = new WaveFormat(44100, 1);
            _waveOut.DataAvailable += WaveOutDataAvailable;
            _waveOut.RecordingStopped += WaveOutRecordStopped;

            _waveOutFile = new WaveFileWriter(pathOut, new Mp3WaveFormat(_waveOut.WaveFormat.SampleRate, _waveOut.WaveFormat.Channels, 0, 128));
            _waveOut.StartRecording();
        }

        private void WaveInDataAvailable(object sender, WaveInEventArgs e)
        {
            if (_waveInFile != null)
            {
                _waveInFile.Write(e.Buffer, 0, e.BytesRecorded);
                _waveInFile.Flush();
            }
        }

        private void WaveOutDataAvailable(object sender, WaveInEventArgs e)
        {
            if (_waveInFile != null)
            {
                using (var memStream = new MemoryStream(e.Buffer))
                {
                    using (WaveStream wStream = new RawSourceWaveStream(memStream, _waveOut.WaveFormat))
                    {
                        var format = new WaveFormat(_waveOut.WaveFormat.SampleRate, _waveOut.WaveFormat.Channels);
                        var transcodedStream = new ResamplerDmoStream(wStream, format);
                        var read = (int)transcodedStream.Length;
                        var bytes = new byte[read];
                        transcodedStream.Read(bytes, 0, read);

                        var fmt = new WaveLib.WaveFormat(transcodedStream.WaveFormat.SampleRate, transcodedStream.WaveFormat.BitsPerSample, transcodedStream.WaveFormat.Channels);
                        var beconf = new Yeti.Lame.BE_CONFIG(fmt, 128);

                        // Encode WAV to MP3
                        byte[] mp3Data;

                        using (var mp3Stream = new MemoryStream())
                        {
                            using (var mp3Writer = new Mp3Writer(mp3Stream, fmt, beconf))
                            {
                                int blen = transcodedStream.WaveFormat.AverageBytesPerSecond;

                                mp3Writer.Write(bytes, 0, read);
                                mp3Data = mp3Stream.ToArray();
                            }
                        }

                        _waveOutFile.Write(mp3Data, 0, mp3Data.Length);
                        _waveOutFile.Flush();
                    }
                }
            }
        }

        private byte[] WavBytesToMp3Bytes(IWaveProvider waveStream, uint bitrate = 128)
        {
            // Setup encoder configuration
            var fmt = new WaveLib.WaveFormat(waveStream.WaveFormat.SampleRate, waveStream.WaveFormat.BitsPerSample, waveStream.WaveFormat.Channels);
            var beconf = new Yeti.Lame.BE_CONFIG(fmt, bitrate);

            // Encode WAV to MP3
            int blen = waveStream.WaveFormat.AverageBytesPerSecond;
            var buffer = new byte[blen];
            byte[] mp3Data = null;

            using (var mp3Stream = new MemoryStream())
            {
                using (var mp3Writer = new Mp3Writer(mp3Stream, fmt, beconf))
                {
                    int readCount;

                    while ((readCount = waveStream.Read(buffer, 0, blen)) > 0)
                    {
                        mp3Writer.Write(buffer, 0, readCount);
                    }

                    mp3Data = mp3Stream.ToArray();
                }
            }
            return mp3Data;
        }

        private void WaveInRecordStopped(object sender, StoppedEventArgs e)
        {
            if (_waveIn != null)
            {
                _waveIn.Dispose();
                _waveIn = null;
            }

            if (_waveInFile != null)
            {
                _waveInFile.Dispose();
                _waveInFile = null;
            }

            _lameProcess.StandardInput.BaseStream.Close();
            _lameProcess.StandardInput.BaseStream.Dispose();

            _lameProcess.Close();
            _lameProcess.Dispose();
        }

        private void WaveOutRecordStopped(object sender, StoppedEventArgs e)
        {
            if (_waveOutFile != null)
            {
                _waveOutFile.Close();
                _waveOutFile = null;
            }

            _waveOut = null;
        }

        public void StopRecording()
        {
            try
            {
                _waveIn.StopRecording();
            }
            catch
            {
            }

            try
            {
                _waveOut.StopRecording();
            }
            catch
            {
            }
        }
    }
}

我使用NAudio捕获音频输入/输出,yetis的lame包装器将其动态转换为mp3文件,问题是生成的音频输出文件可能已损坏且无法读取,缺少mp3头或其他我遗漏的内容…

问题在于,您从环回捕获接口以默认格式(即:PCM)获取批量数据,然后将其写入带有格式块的wave文件,该格式块声明数据为ALAW格式。在任何情况下,您都不会实际执行从PCM数据到ALAW数据的转换,从而产生垃圾文件

WaveFileWriter类不会为您执行任何形式的重新编码或重新采样。它使用格式说明符为WAV文件构建格式块,并假设您正在以该格式向其提供数据

你的两个选择是:

  • 在写入
    WaveFileWriter
    实例之前,将来自PCM-44100-Stereo(或任何默认值)的传入数据转换为ALAW-8000-Mono

  • 使用
    \u waveOut.WaveFormat
    初始化
    \u waveOutFile
    ,以匹配数据格式


  • 9月26日更新

    因此,在经历了大量的混乱之后,我终于找到了一个有效的解决方案,可以正确地将环回捕获的波形格式转换为可以压缩的格式

    以下是转换第一阶段的代码:

    [StructLayout(LayoutKind.Explicit)]
    internal struct UnionStruct
    {
        [FieldOffset(0)]
        public byte[] bytes;
        [FieldOffset(0)]
        public float[] floats;
    }
    
    public static byte[] Float32toInt16(byte[] data, int offset, int length)
    {
        UnionStruct u = new UnionStruct();
        int nSamples = length / 4;
    
        if (offset == 0)
            u.bytes = data;
        else
        {
            u.bytes = new byte[nSamples * 4];
            Buffer.BlockCopy(data, offset, u.bytes, 0, nSamples * 4);
        }
        byte[] res = new byte[nSamples * 2];
    
        for (i = 0, o = 0; i < nSamples; i++, o+= 2)
        {
            short val = (short)(u.floats[i] * short.MaxValue);
            res[o] = (byte)(val & 0xFF);
            res[o + 1] = (byte)((val >> 8) & 0xFF);
        }
    
        u.bytes = null;
        return res;
    }
    

    顺便说一句,
    Mp3Writer
    类就是您所需要的。扔掉你的另一个蹩脚的代码。它只会妨碍您。

    WasapiLoopbackCapture
    可能会捕获32位浮点、44.1kHz立体声音频
    WaveFormatConversionStream
    不会一步将其转换为a定律8kHz单声道。您需要分多个步骤进行此转换

    • 首先使用16位PCM(我倾向于手动执行此操作)
    • 然后进入单声道(混合或丢弃一个声道-这取决于你)(同样,我会手动操作)
    • 然后重新采样到8kHz(波形转换流可以做到这一点)
    • 然后编码为a定律(使用波形转换流的第二个实例)

    第二个可以正常工作,但它会产生一个巨大的文件(这就是为什么我在开始时拒绝了它)。您能为第一个选项提供一个代码示例吗?我正试图做到这一点,因为这是写在后,但似乎我错了什么地方或错过了什么,谢谢你在advance@HardLuck应该很简单,但目前压缩对我来说有点糟糕。我一直在使用雪人的蹩脚包装来创建MP3文件。很遗憾,我不知道如何使用它,但似乎我应该尝试一下,谢谢你。嘿,很棒的更新!但是你能告诉我Float32toInt16方法中的“v”变量是什么吗?@HardLuck是的,它是一个输入错误:P这就是当我在这里键入代码而不是从工作代码中复制粘贴时发生的情况。现在修复了。谢谢你的回答,这让情况更清楚了一点,我已经更新了我的问题以匹配它的“当前状态”
    Stream _mp3Output;
    Mp3Writer _mp3Writer;
    
    private void InitAudioOut(DateTime recordMarker)
    {
        string pathOut = string.Format(@"C:\Rec\({0:HH-mm-ss dd-MM-yyyy} OUT).mp3", recordMarker);
    
        _waveOut = new WasapiLoopbackCapture();
        _waveOut.DataAvailable += WaveOutDataAvailable;
        _waveOut.RecordingStopped += WaveOutRecordStopped;
    
        _mp3Output = File.Create(pathIn);
    
        var fmt = new WaveLib.WaveFormat(_waveOut.WaveFormat.SampleRate, 16, _waveOut.Channels);
        var beconf = new Yeti.Lame.BE_CONFIG(fmt, 128);
    
        _mp3Writer = new Mp3Writer(_mp3Stream, fmt, beconf);
    
        _waveOut.StartRecording();
    }
    
    private void WaveOutDataAvailable(object sender, WaveInEventArgs e)
    {
        if (_mp3Writer != null)
        {
            byte[] data = Float32toInt16(e.Buffer, 0, e.BytesRecorded);
            _mp3Writer.Write(data, 0, data.Length);
        }
    }
    
    private void WaveOutRecordStopped(object sender, StoppedEventArgs e)
    {
        if (InvokeRequired)
            BeginInvoke(new MethodInvoker(WaveOutStop));
        else
            WaveOutStop();
    }
    
    private void WaveOutStop()
    {
        if (_mp3Writer != null)
        {
            _mp3Writer.Close();
            _mp3Writer.Dispose();
            _mp3Writer = null;
        }
    
        if (_mp3Stream != null)
        {
            _mp3Stream.Dispose();
            _mp3Stream = null;
        }
    
        _waveOut.Dispose();
        _waveOut = null;
    }