通过 Yeti lame 包装器将音频流式传输到 mp3 文件

Posted

技术标签:

【中文标题】通过 Yeti lame 包装器将音频流式传输到 mp3 文件【英文标题】:NAudio streaming to mp3 file throught yeti lame wrapper 【发布时间】:2013-09-22 20:52:30 【问题描述】:

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

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 的蹩脚包装器将其即时转换为 mp3 文件,问题是生成的音频输出文件已损坏且无法读取,可能缺少 mp3 标头或其他东西其他我错过的......

【问题讨论】:

【参考方案1】:

问题是您从环回捕获接口以默认格式(即:PCM)获取批量数据,然后将其写入带有 声称的格式块的波形文件数据为 ALAW 格式。在任何时候,您实际上都没有进行从 PCM 数据到 ALAW 数据的转换,从而产生了一个垃圾文件。

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

您的两个选择是:

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

    _waveOut.WaveFormat初始化_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;

这会将 32 位浮点样本转换为可由大多数音频代码处理的 16 位有符号整数样本。幸运的是,这包括Yeti MP3 代码。

要即时编码并确保 MP3 输出有效,请同时创建 Mp3Writer 及其输出 Stream(例如,FileStream 直接写入磁盘)并保持从环回接口输入数据(通过上面的转换器运行)。在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;

顺便说一句,Mp3Writer 类就是你所需要的。扔掉你那里的其他Lame 代码。它只会妨碍你。

【讨论】:

第二个工作正常,但它会产生一个巨大的文件(这就是我一开始拒绝它的原因)。您能否提供第一个选项的代码示例?我正在尝试这样做,因为它写在帖子中,但似乎我在某个地方错了或错过了什么,提前谢谢你 @HardLuck 应该很简单,但目前压缩对我来说有点搞砸了。我一直在使用 Yeti 的蹩脚包装器来创建 MP3 文件。 遗憾的是我不知道如何使用它,但似乎我应该尝试一下,谢谢您 嘿,那里有很棒的更新!但是你能告诉我 Float32toInt16 方法中的“v”变量是什么吗? @HardLuck 是的,这是一个错字:P 当我在这里输入代码而不是从我的工作代码中复制粘贴时,就会发生这种情况。现在修好了。【参考方案2】:

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

首先使用 16 位 PCM(我倾向于手动执行此操作) 然后进入单声道(混合或丢弃一个通道 - 由您决定)(我再次手动执行此操作) 然后重新采样到 8kHz(WaveFormatConversionStream 可以做到这一点) 然后编码为 a-law(使用 WaveFormatConversionStream 的第二个实例)

【讨论】:

感谢您的回答,这让情况更加清晰,我已经更新了我的问题以匹配其当前状态

以上是关于通过 Yeti lame 包装器将音频流式传输到 mp3 文件的主要内容,如果未能解决你的问题,请参考以下文章

如何模拟通话以将蓝牙音频流式传输到车辆中?

通过 socket.io 流式传输实时音频

将音频流式传输到多个 Web 浏览器

尝试通过多点连接将音频从麦克风流式传输到另一部手机

通过 POST 命令将音频从 ios 流式传输到 REST 服务器

通过网络将系统音频流式传输到 Web 浏览器 (javascript)