zoukankan      html  css  js  c++  java
  • C# 录音和播放录音-NAudio

      在使用C#进行录音和播放录音功能上,使用NAudio是个不错的选择。

      NAudio是个开源,相对功能比较全面的类库,它包含录音、播放录音、格式转换、混音调整等操作,具体可以去Github上看看介绍和源码,附:Git地址

      我使用到的是录制和播放wav格式的音频,对应调用NAudio的WaveFileWriter和WaveFileReader类进行开发,从源码上看原理就是

    1. 根据上层传入的因为文件类型格式(mp3、wav等格式)定义进行创建流文件,并添加对应header和format等信息
    2. 调用WinAPI进行数据采集,实时读取其传入数据并保存至上述流文件中
    3. 保存

    附:WaveFileWriter源码

    using System;
    using System.IO;
    using NAudio.Wave.SampleProviders;
    using NAudio.Utils;
    
    // ReSharper disable once CheckNamespace
    namespace NAudio.Wave
    {
        /// <summary>
        /// This class writes WAV data to a .wav file on disk
        /// </summary>
        public class WaveFileWriter : Stream
        {
            private Stream outStream;
            private readonly BinaryWriter writer;
            private long dataSizePos;
            private long factSampleCountPos;
            private long dataChunkSize;
            private readonly WaveFormat format;
            private readonly string filename;
    
            /// <summary>
            /// Creates a 16 bit Wave File from an ISampleProvider
            /// BEWARE: the source provider must not return data indefinitely
            /// </summary>
            /// <param name="filename">The filename to write to</param>
            /// <param name="sourceProvider">The source sample provider</param>
            public static void CreateWaveFile16(string filename, ISampleProvider sourceProvider)
            {
                CreateWaveFile(filename, new SampleToWaveProvider16(sourceProvider));
            }
    
            /// <summary>
            /// Creates a Wave file by reading all the data from a WaveProvider
            /// BEWARE: the WaveProvider MUST return 0 from its Read method when it is finished,
            /// or the Wave File will grow indefinitely.
            /// </summary>
            /// <param name="filename">The filename to use</param>
            /// <param name="sourceProvider">The source WaveProvider</param>
            public static void CreateWaveFile(string filename, IWaveProvider sourceProvider)
            {
                using (var writer = new WaveFileWriter(filename, sourceProvider.WaveFormat))
                {
                    var buffer = new byte[sourceProvider.WaveFormat.AverageBytesPerSecond * 4];
                    while (true)
                    {
                        int bytesRead = sourceProvider.Read(buffer, 0, buffer.Length);
                        if (bytesRead == 0)
                        {
                            // end of source provider
                            break;
                        }
                        // Write will throw exception if WAV file becomes too large
                        writer.Write(buffer, 0, bytesRead);
                    }
                }
            }
            
            /// <summary>
            /// Writes to a stream by reading all the data from a WaveProvider
            /// BEWARE: the WaveProvider MUST return 0 from its Read method when it is finished,
            /// or the Wave File will grow indefinitely.
            /// </summary>
            /// <param name="outStream">The stream the method will output to</param>
            /// <param name="sourceProvider">The source WaveProvider</param>
            public static void WriteWavFileToStream(Stream outStream, IWaveProvider sourceProvider)
            {
                using (var writer = new WaveFileWriter(new IgnoreDisposeStream(outStream), sourceProvider.WaveFormat)) 
                {
                    var buffer = new byte[sourceProvider.WaveFormat.AverageBytesPerSecond * 4];
                    while(true) 
                    {
                        var bytesRead = sourceProvider.Read(buffer, 0, buffer.Length);
                        if (bytesRead == 0) 
                        {
                            // end of source provider
                            outStream.Flush();
                            break;
                        }
    
                        writer.Write(buffer, 0, bytesRead);
                    }
                }
            }
            
            /// <summary>
            /// WaveFileWriter that actually writes to a stream
            /// </summary>
            /// <param name="outStream">Stream to be written to</param>
            /// <param name="format">Wave format to use</param>
            public WaveFileWriter(Stream outStream, WaveFormat format)
            {
                this.outStream = outStream;
                this.format = format;
                writer = new BinaryWriter(outStream, System.Text.Encoding.UTF8);
                writer.Write(System.Text.Encoding.UTF8.GetBytes("RIFF"));
                writer.Write((int)0); // placeholder
                writer.Write(System.Text.Encoding.UTF8.GetBytes("WAVE"));
    
                writer.Write(System.Text.Encoding.UTF8.GetBytes("fmt "));
                format.Serialize(writer);
    
                CreateFactChunk();
                WriteDataChunkHeader();
            }
    
            /// <summary>
            /// Creates a new WaveFileWriter
            /// </summary>
            /// <param name="filename">The filename to write to</param>
            /// <param name="format">The Wave Format of the output data</param>
            public WaveFileWriter(string filename, WaveFormat format)
                : this(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Read), format)
            {
                this.filename = filename;
            }
    
            private void WriteDataChunkHeader()
            {
                writer.Write(System.Text.Encoding.UTF8.GetBytes("data"));
                dataSizePos = outStream.Position;
                writer.Write((int)0); // placeholder
            }
    
            private void CreateFactChunk()
            {
                if (HasFactChunk())
                {
                    writer.Write(System.Text.Encoding.UTF8.GetBytes("fact"));
                    writer.Write((int)4);
                    factSampleCountPos = outStream.Position;
                    writer.Write((int)0); // number of samples
                }
            }
    
            private bool HasFactChunk()
            {
                return format.Encoding != WaveFormatEncoding.Pcm && 
                    format.BitsPerSample != 0;
            }
    
            /// <summary>
            /// The wave file name or null if not applicable
            /// </summary>
            public string Filename => filename;
    
            /// <summary>
            /// Number of bytes of audio in the data chunk
            /// </summary>
            public override long Length => dataChunkSize;
    
            /// <summary>
            /// Total time (calculated from Length and average bytes per second)
            /// </summary>
            public TimeSpan TotalTime => TimeSpan.FromSeconds((double)Length / WaveFormat.AverageBytesPerSecond);
    
            /// <summary>
            /// WaveFormat of this wave file
            /// </summary>
            public WaveFormat WaveFormat => format;
    
            /// <summary>
            /// Returns false: Cannot read from a WaveFileWriter
            /// </summary>
            public override bool CanRead => false;
    
            /// <summary>
            /// Returns true: Can write to a WaveFileWriter
            /// </summary>
            public override bool CanWrite => true;
    
            /// <summary>
            /// Returns false: Cannot seek within a WaveFileWriter
            /// </summary>
            public override bool CanSeek => false;
    
            /// <summary>
            /// Read is not supported for a WaveFileWriter
            /// </summary>
            public override int Read(byte[] buffer, int offset, int count)
            {
                throw new InvalidOperationException("Cannot read from a WaveFileWriter");
            }
    
            /// <summary>
            /// Seek is not supported for a WaveFileWriter
            /// </summary>
            public override long Seek(long offset, SeekOrigin origin)
            {
                throw new InvalidOperationException("Cannot seek within a WaveFileWriter");
            }
    
            /// <summary>
            /// SetLength is not supported for WaveFileWriter
            /// </summary>
            /// <param name="value"></param>
            public override void SetLength(long value)
            {
                throw new InvalidOperationException("Cannot set length of a WaveFileWriter");
            }
    
            /// <summary>
            /// Gets the Position in the WaveFile (i.e. number of bytes written so far)
            /// </summary>
            public override long Position
            {
                get => dataChunkSize;
                set => throw new InvalidOperationException("Repositioning a WaveFileWriter is not supported");
            }
    
            /// <summary>
            /// Appends bytes to the WaveFile (assumes they are already in the correct format)
            /// </summary>
            /// <param name="data">the buffer containing the wave data</param>
            /// <param name="offset">the offset from which to start writing</param>
            /// <param name="count">the number of bytes to write</param>
            [Obsolete("Use Write instead")]
            public void WriteData(byte[] data, int offset, int count)
            {
                Write(data, offset, count);
            }
    
            /// <summary>
            /// Appends bytes to the WaveFile (assumes they are already in the correct format)
            /// </summary>
            /// <param name="data">the buffer containing the wave data</param>
            /// <param name="offset">the offset from which to start writing</param>
            /// <param name="count">the number of bytes to write</param>
            public override void Write(byte[] data, int offset, int count)
            {
                if (outStream.Length + count > UInt32.MaxValue)
                    throw new ArgumentException("WAV file too large", nameof(count));
                outStream.Write(data, offset, count);
                dataChunkSize += count;
            }
    
            private readonly byte[] value24 = new byte[3]; // keep this around to save us creating it every time
            
            /// <summary>
            /// Writes a single sample to the Wave file
            /// </summary>
            /// <param name="sample">the sample to write (assumed floating point with 1.0f as max value)</param>
            public void WriteSample(float sample)
            {
                if (WaveFormat.BitsPerSample == 16)
                {
                    writer.Write((Int16)(Int16.MaxValue * sample));
                    dataChunkSize += 2;
                }
                else if (WaveFormat.BitsPerSample == 24)
                {
                    var value = BitConverter.GetBytes((Int32)(Int32.MaxValue * sample));
                    value24[0] = value[1];
                    value24[1] = value[2];
                    value24[2] = value[3];
                    writer.Write(value24);
                    dataChunkSize += 3;
                }
                else if (WaveFormat.BitsPerSample == 32 && WaveFormat.Encoding == WaveFormatEncoding.Extensible)
                {
                    writer.Write(UInt16.MaxValue * (Int32)sample);
                    dataChunkSize += 4;
                }
                else if (WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat)
                {
                    writer.Write(sample);
                    dataChunkSize += 4;
                }
                else
                {
                    throw new InvalidOperationException("Only 16, 24 or 32 bit PCM or IEEE float audio data supported");
                }
            }
    
            /// <summary>
            /// Writes 32 bit floating point samples to the Wave file
            /// They will be converted to the appropriate bit depth depending on the WaveFormat of the WAV file
            /// </summary>
            /// <param name="samples">The buffer containing the floating point samples</param>
            /// <param name="offset">The offset from which to start writing</param>
            /// <param name="count">The number of floating point samples to write</param>
            public void WriteSamples(float[] samples, int offset, int count)
            {
                for (int n = 0; n < count; n++)
                {
                    WriteSample(samples[offset + n]);
                }
            }
    
            /// <summary>
            /// Writes 16 bit samples to the Wave file
            /// </summary>
            /// <param name="samples">The buffer containing the 16 bit samples</param>
            /// <param name="offset">The offset from which to start writing</param>
            /// <param name="count">The number of 16 bit samples to write</param>
            [Obsolete("Use WriteSamples instead")]
            public void WriteData(short[] samples, int offset, int count)
            {
                WriteSamples(samples, offset, count);
            }
    
    
            /// <summary>
            /// Writes 16 bit samples to the Wave file
            /// </summary>
            /// <param name="samples">The buffer containing the 16 bit samples</param>
            /// <param name="offset">The offset from which to start writing</param>
            /// <param name="count">The number of 16 bit samples to write</param>
            public void WriteSamples(short[] samples, int offset, int count)
            {
                // 16 bit PCM data
                if (WaveFormat.BitsPerSample == 16)
                {
                    for (int sample = 0; sample < count; sample++)
                    {
                        writer.Write(samples[sample + offset]);
                    }
                    dataChunkSize += (count * 2);
                }
                // 24 bit PCM data
                else if (WaveFormat.BitsPerSample == 24)
                {
                    for (int sample = 0; sample < count; sample++)
                    {
                        var value = BitConverter.GetBytes(UInt16.MaxValue * (Int32)samples[sample + offset]);
                        value24[0] = value[1];
                        value24[1] = value[2];
                        value24[2] = value[3];
                        writer.Write(value24);
                    }
                    dataChunkSize += (count * 3);
                }
                // 32 bit PCM data
                else if (WaveFormat.BitsPerSample == 32 && WaveFormat.Encoding == WaveFormatEncoding.Extensible)
                {
                    for (int sample = 0; sample < count; sample++)
                    {
                        writer.Write(UInt16.MaxValue * (Int32)samples[sample + offset]);
                    }
                    dataChunkSize += (count * 4);
                }
                // IEEE float data
                else if (WaveFormat.BitsPerSample == 32 && WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat)
                {
                    for (int sample = 0; sample < count; sample++)
                    {
                        writer.Write((float)samples[sample + offset] / (float)(Int16.MaxValue + 1));
                    }
                    dataChunkSize += (count * 4);
                }
                else
                {
                    throw new InvalidOperationException("Only 16, 24 or 32 bit PCM or IEEE float audio data supported");
                }
            }
    
            /// <summary>
            /// Ensures data is written to disk
            /// Also updates header, so that WAV file will be valid up to the point currently written
            /// </summary>
            public override void Flush()
            {
                var pos = writer.BaseStream.Position;
                UpdateHeader(writer);
                writer.BaseStream.Position = pos;
            }
    
            #region IDisposable Members
    
            /// <summary>
            /// Actually performs the close,making sure the header contains the correct data
            /// </summary>
            /// <param name="disposing">True if called from <see>Dispose</see></param>
            protected override void Dispose(bool disposing)
            {
                if (disposing)
                {
                    if (outStream != null)
                    {
                        try
                        {
                            UpdateHeader(writer);
                        }
                        finally
                        {
                            // in a finally block as we don't want the FileStream to run its disposer in
                            // the GC thread if the code above caused an IOException (e.g. due to disk full)
                            outStream.Dispose(); // will close the underlying base stream
                            outStream = null;
                        }
                    }
                }
            }
    
            /// <summary>
            /// Updates the header with file size information
            /// </summary>
            protected virtual void UpdateHeader(BinaryWriter writer)
            {
                writer.Flush();
                UpdateRiffChunk(writer);
                UpdateFactChunk(writer);
                UpdateDataChunk(writer);
            }
    
            private void UpdateDataChunk(BinaryWriter writer)
            {
                writer.Seek((int)dataSizePos, SeekOrigin.Begin);
                writer.Write((UInt32)dataChunkSize);
            }
    
            private void UpdateRiffChunk(BinaryWriter writer)
            {
                writer.Seek(4, SeekOrigin.Begin);
                writer.Write((UInt32)(outStream.Length - 8));
            }
    
            private void UpdateFactChunk(BinaryWriter writer)
            {
                if (HasFactChunk())
                {
                    int bitsPerSample = (format.BitsPerSample * format.Channels);
                    if (bitsPerSample != 0)
                    {
                        writer.Seek((int)factSampleCountPos, SeekOrigin.Begin);
                        
                        writer.Write((int)((dataChunkSize * 8) / bitsPerSample));
                    }
                }
            }
    
            /// <summary>
            /// Finaliser - should only be called if the user forgot to close this WaveFileWriter
            /// </summary>
            ~WaveFileWriter()
            {
                System.Diagnostics.Debug.Assert(false, "WaveFileWriter was not disposed");
                Dispose(false);
            }
    
            #endregion
        }
    }

      WaveFileReader和WaveFileWriter相似,只是把写流文件变成了读流文件,具体可在源码中查看。

      值得注意的是,在有需要对音频进行分析处理的需求时(如VAD)可以查看其DataAvailable事件,该事件会实时回调传递音频数据(byte[]),最后强调一点这个音频数据byte数组需要注意其写入时和读取时PCM所使用的bit数,PCM分别有8/16/24/32四种,在WaveFormat.BitsPerSample属性上可以查看,根据PCM不同类型这个byte数组的真实数据转换上也要转换不同类型,8bit是一个字节、16bit是两个字节、24.....32...等,在使用时根据这个进行对应转换才是正确的数值。

      附PCM类型初始化对应部分代码:

      public static ISampleProvider ConvertWaveProviderIntoSampleProvider(IWaveProvider waveProvider)
            {
                ISampleProvider sampleProvider;
                if (waveProvider.WaveFormat.Encoding == WaveFormatEncoding.Pcm)
                {
                    // go to float
                    if (waveProvider.WaveFormat.BitsPerSample == 8)
                    {
                        sampleProvider = new Pcm8BitToSampleProvider(waveProvider);
                    }
                    else if (waveProvider.WaveFormat.BitsPerSample == 16)
                    {
                        sampleProvider = new Pcm16BitToSampleProvider(waveProvider);
                    }
                    else if (waveProvider.WaveFormat.BitsPerSample == 24)
                    {
                        sampleProvider = new Pcm24BitToSampleProvider(waveProvider);
                    }
                    else if (waveProvider.WaveFormat.BitsPerSample == 32)
                    {
                        sampleProvider = new Pcm32BitToSampleProvider(waveProvider);
                    }
                    else
                    {
                        throw new InvalidOperationException("Unsupported bit depth");
                    }
                }
                else if (waveProvider.WaveFormat.Encoding == WaveFormatEncoding.IeeeFloat)
                {
                    if (waveProvider.WaveFormat.BitsPerSample == 64)
                        sampleProvider = new WaveToSampleProvider64(waveProvider);
                    else
                        sampleProvider = new WaveToSampleProvider(waveProvider);
                }
                else
                {
                    throw new ArgumentException("Unsupported source encoding");
                }
                return sampleProvider;
            }
        }

      以上是查看源码和使用上的一些记录,具体录制和播放示例如下:示例

      新接触,有些感悟,分享下

  • 相关阅读:
    iptables 增删查改
    在Ubuntu14.04上安装WordPress4搭建技术博客
    Revit 二次开发之 零件
    Revit 二次开发之 结构层次
    revit二次开发之 过滤器二FilteredElementCollector收集器
    Revit二次开发之 动态模型更新(DMU: Dynamic Model Update)功能
    revit二次开发之 过滤器一
    Revit 二次开发之标高参数
    Revit二次开发之 错误
    Visual Studio删除所有的注释和空行
  • 原文地址:https://www.cnblogs.com/Khan-Sadas/p/11428103.html
Copyright © 2011-2022 走看看