简体   繁体   English

NAudio流媒体到mp3文件通过yeti lame包装

[英]NAudio streaming to mp3 file throught yeti lame wrapper

I have the following code to record audio in and out 我有以下代码来录制和录制音频

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
            {
            }
        }
    }
}

I'm using NAudio to capture audio in/out and yetis' lame wrapper to convert it to mp3 file on the fly, the problem is that the resulting audio out file is corrupted and unreadable, probably, missing mp3 headers or something other that i've missed... 我正在使用NAudio来捕获音频输入/输出而且是'lame包装器将其转换为mp3文件即时,问题是所产生的音频输出文件已损坏且不可读,可能是,缺少mp3标题或其他我的东西错过了......

The problem is that you're getting batches of data from the loopback capture interface in the default format (ie: PCM), then writing that to a wave file with a format block that claims that the data is in ALAW format. 问题是你从环回捕获接口以默认格式(即:PCM)获取批量数据,然后将其写入波形文件,其格式块声称数据为ALAW格式。 At no point do you actually do a conversion from the PCM data to ALAW data, resulting in a garbage file. 您实际上没有从PCM数据转换为ALAW数据,从而导致垃圾文件。

The WaveFileWriter class doesn't do any form of recoding or resampling for you. WaveFileWriter类不会为您执行任何形式的重新编码或重新采样。 It uses the format specifier to build a format block for the WAV file, and assumes that you are providing it with data in that format. 它使用格式说明符为WAV文件构建格式块,并假定您正在为其提供该格式的数据。

Your two options are: 你有两个选择:

  1. Convert the incoming data from PCM-44100-Stereo (or whatever the default is) to ALAW-8000-Mono before writing to the WaveFileWriter instance. 在写入WaveFileWriter实例之前,将输入数据从PCM-44100-Stereo(或任何默认值)转换为ALAW-8000-Mono。

  2. Initialize _waveOutFile with _waveOut.WaveFormat to match the data formats. 初始化_waveOutFile_waveOut.WaveFormat相匹配的数据格式。


Updated 26-Sep... 9月26日更新......

So after much messing around, I finally have a working solution to the original problem of correctly converting the wave format from the loopback capture into something that can be compressed. 所以经过多次搞乱后,我终于找到了一个解决原始问题的工作解决方案,即将波形格式从环回捕获正确转换为可以压缩的东西。

Here's the code for the first stage of the conversion: 这是转换第一阶段的代码:

[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;
}

That will convert the 32-bit floating point samples to 16-bit signed integer samples that can be handled by most audio code. 这将把32位浮点样本转换为16位有符号整数样本,这些样本可由大多数音频代码处理。 Fortunately, this includes the Yeti MP3 code. 幸运的是,这包括Yeti MP3代码。

To encode on-the-fly and ensure that the MP3 output is valid, create the Mp3Writer and its output Stream (a FileStream to write directly to disk for instance) at the same time and just keep feeding it data (run through the converter above) as it comes in from the loopback interface. 要即时编码并确保MP3输出有效,请同时创建Mp3Writer及其输出Stream (例如,将FileStream直接写入磁盘)并继续为其提供数据(通过上面的转换器运行) )它来自loopback接口。 Close the Mp3Writer and the Stream in the waveInStopRecording event handler. waveInStopRecording事件处理程序中关闭Mp3WriterStream

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;
}

Incidentally, the Mp3Writer class is all you need for this. 顺便说一句, Mp3Writer类就是您所需要的。 Throw out the other Lame code you've got there. 丢掉你在那里的其他Lame代码。 It will just get in your way. 它会妨碍你。

WasapiLoopbackCapture will likely be capturing audio at 32 bit floating point, 44.1kHz, stereo. WasapiLoopbackCapture可能会捕获32位浮点音频,44.1kHz,立体声音频。 WaveFormatConversionStream will not convert that into a-law 8kHz mono in one step. WaveFormatConversionStream不会一步将其转换为a-law 8kHz单声道。 You need to do this conversion in multiple steps. 您需要分多步执行此转换。

  • First get to 16 bit PCM (I tend to do this manually) 首先得到16位PCM(我倾向于手动执行此操作)
  • Then get to mono (mix or discard one channel - it's up to you) (Again I'd do this manually) 然后转到单声道(混合或丢弃一个频道 - 这取决于你)(再次我会手动执行此操作)
  • Then resample down to 8kHz (WaveFormatConversionStream can do this) 然后重新采样到8kHz(WaveFormatConversionStream可以做到这一点)
  • Then encode to a-law (use a second instance of WaveFormatConversionStream) 然后编码为a-law(使用WaveFormatConversionStream的第二个实例)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM