从设备发送 NAudio / Opus 编码的音频作为 RTP

Posted

技术标签:

【中文标题】从设备发送 NAudio / Opus 编码的音频作为 RTP【英文标题】:Sending NAudio / Opus-encoded audio from device as RTP 【发布时间】:2016-04-03 01:24:51 【问题描述】:

首先,我要道歉。很久以前我曾经修补过 VB5,并且已经离开程序员多年了 - 我仍在重新学习基础知识,最近开始学习 C#/.NET。我也是这个网站的新手,请耐心等待和指导。我的背景故事已经够多了。

使用this wrapper for Opus,我将包装器项目添加到我自己的解决方案中,并且我相信我已经设置了 NAudio 以主动从我的设备(声卡)中获取音频并利用示例编码器代码进行编码音频进入 _playBuffer。

我的下一个任务是从中获取编码数据并使用 RDP 发送它,以便将其发送到另一台机器上的客户端应用程序中进行解码,然后在其声音设备上对其进行解码和播放。

我是否正确理解 _playBuffer 中的数据是准备就绪的编码数据?或者这是否需要对 RTP 数据包进行不同的拆分? (我看到uLAW example here,但我不确定我是否能适应我的需要。因为下载的源代码是用德语评论的——但我几乎不会说和写英语作为第一语言——即使那些也不是非常有帮助。)

(我什至使用正确的术语吗?)截至目前,您看到的股票代码通过 WaveOut 将 _playBuffer 数据放回,就像他的示例一样 - 我在这里忽略了它并留下来解释我的(可能缺乏了解。 (如果它是“可播放的”,它就是“可发送的”。)

另一个问题是我的意图是通过 Internet 为点对点多播流 - 尽管我不确定多播是我想要的。

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    using NAudio;
    using NAudio.CoreAudioApi;
    using NAudio.Wave;
    using FragLabs.Audio.Codecs;

    namespace VUmeterappStereo
    
        public partial class Form1 : Form
        private void Form1_Load(object sender, EventArgs e)
        
            for (int i = 0; i < WaveIn.DeviceCount; i++)
            
                comboBox1.Items.Add(WaveIn.GetCapabilities(i).ProductName);
            
            if (WaveIn.DeviceCount > 0)
                comboBox1.SelectedIndex = 0;
            for (int i = 0; i < WaveOut.DeviceCount; i++)
            
                comboBox2.Items.Add(WaveOut.GetCapabilities(i).ProductName);
            
            if (WaveOut.DeviceCount > 0)
                comboBox2.SelectedIndex = 0;
        

        private void button1_Click(object sender, EventArgs e)
        
            button2.Enabled = true;
            button1.Enabled = false;
            StartEncoding();
        

        private void button2_Click(object sender, EventArgs e)
        
            button1.Enabled = true;
            button2.Enabled = false;
            StopEncoding();
        

        WaveIn _waveIn;
        WaveOut _waveOut;
        BufferedWaveProvider _playBuffer;
        OpusEncoder _encoder;
        OpusDecoder _decoder;
        int _segmentFrames;
        int _bytesPerSegment;
        ulong _bytesSent;
        DateTime _startTime;
        Timer _timer = null;

        void StartEncoding()
        
            _startTime = DateTime.Now;
            _bytesSent = 0;
            _segmentFrames = 960;
            _encoder = OpusEncoder.Create(48000, 1, FragLabs.Audio.Codecs.Opus.Application.Voip);
            _encoder.Bitrate = 8192;
            _decoder = OpusDecoder.Create(48000, 1);
            _bytesPerSegment = _encoder.FrameByteCount(_segmentFrames);

            _waveIn = new WaveIn(WaveCallbackInfo.FunctionCallback());
            _waveIn.BufferMilliseconds = 50;
            _waveIn.DeviceNumber = comboBox1.SelectedIndex;
            _waveIn.DataAvailable += _waveIn_DataAvailable;
            _waveIn.WaveFormat = new WaveFormat(48000, 16, 1);

            _playBuffer = new BufferedWaveProvider(new WaveFormat(48000, 16, 1));

            _waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback());
            _waveOut.DeviceNumber = comboBox2.SelectedIndex;
            _waveOut.Init(_playBuffer);

            _waveOut.Play();
            _waveIn.StartRecording();

            if (_timer == null)
            
                _timer = new Timer();
                _timer.Interval = 1000;
                _timer.Tick += _timer_Tick;
            
            _timer.Start();
        

        void _timer_Tick(object sender, EventArgs e)
        
            var timeDiff = DateTime.Now - _startTime;
            var bytesPerSecond = _bytesSent / timeDiff.TotalSeconds;
            Console.WriteLine("0 Bps", bytesPerSecond);
        

        byte[] _notEncodedBuffer = new byte[0];
        void _waveIn_DataAvailable(object sender, WaveInEventArgs e)
        
            byte[] soundBuffer = new byte[e.BytesRecorded + _notEncodedBuffer.Length];
            for (int i = 0; i < _notEncodedBuffer.Length; i++)
                soundBuffer[i] = _notEncodedBuffer[i];
            for (int i = 0; i < e.BytesRecorded; i++)
                soundBuffer[i + _notEncodedBuffer.Length] = e.Buffer[i];

            int byteCap = _bytesPerSegment;
            int segmentCount = (int)Math.Floor((decimal)soundBuffer.Length / byteCap);
            int segmentsEnd = segmentCount * byteCap;
            int notEncodedCount = soundBuffer.Length - segmentsEnd;
            _notEncodedBuffer = new byte[notEncodedCount];
            for (int i = 0; i < notEncodedCount; i++)
            
                _notEncodedBuffer[i] = soundBuffer[segmentsEnd + i];
            

            for (int i = 0; i < segmentCount; i++)
            
                byte[] segment = new byte[byteCap];
                for (int j = 0; j < segment.Length; j++)
                    segment[j] = soundBuffer[(i * byteCap) + j];
                int len;
                byte[] buff = _encoder.Encode(segment, segment.Length, out len);
                _bytesSent += (ulong)len;
                buff = _decoder.Decode(buff, len, out len);
                _playBuffer.AddSamples(buff, 0, len);
            
        

        void StopEncoding()
        
            _timer.Stop();
            _waveIn.StopRecording();
            _waveIn.Dispose();
            _waveIn = null;
            _waveOut.Stop();
            _waveOut.Dispose();
            _waveOut = null;
            _playBuffer = null;
            _encoder.Dispose();
            _encoder = null;
            _decoder.Dispose();
            _decoder = null;

        



        private void timer1_Tick(object sender, EventArgs e)
        
            MMDeviceEnumerator de = new MMDeviceEnumerator();
            MMDevice device = de.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
            //float volume = (float)device.AudioMeterInformation.MasterPeakValue * 100;
            float volLeft = (float)device.AudioMeterInformation.PeakValues[0] * 100;
            float volRight = (float)device.AudioMeterInformation.PeakValues[1] * 100;
            progressBar1.Value = (int)volLeft;
            progressBar2.Value = (int)volRight;
        

        private void timer2_Tick(object sender, EventArgs e)
        

        
    

感谢您为帮助我了解如何通过 RTP 流获取数据而做出的任何贡献。

哦,是的,这首先开始于我从一个教程示例中重新创建一个 VU 表 - 因此命名空间名称和额外的代码,它确实起作用。

【问题讨论】:

【参考方案1】:

代码示例对音频进行编码而不是解码。您需要将 Buff 中包含的字节发送到网络。

上例中的这段代码是从声卡接收音频。

    byte[] _notEncodedBuffer = new byte[0];
    void _waveIn_DataAvailable(object sender, WaveInEventArgs e)
    
        byte[] soundBuffer = new byte[e.BytesRecorded + _notEncodedBuffer.Length];
        for (int i = 0; i < _notEncodedBuffer.Length; i++)
            soundBuffer[i] = _notEncodedBuffer[i];
        for (int i = 0; i < e.BytesRecorded; i++)
            soundBuffer[i + _notEncodedBuffer.Length] = e.Buffer[i];

        int byteCap = _bytesPerSegment;
        int segmentCount = (int)Math.Floor((decimal)soundBuffer.Length / byteCap);
        int segmentsEnd = segmentCount * byteCap;
        int notEncodedCount = soundBuffer.Length - segmentsEnd;
        _notEncodedBuffer = new byte[notEncodedCount];
        for (int i = 0; i < notEncodedCount; i++)
        
            _notEncodedBuffer[i] = soundBuffer[segmentsEnd + i];
        

        for (int i = 0; i < segmentCount; i++)
        
            byte[] segment = new byte[byteCap];
            for (int j = 0; j < segment.Length; j++)
                segment[j] = soundBuffer[(i * byteCap) + j];
            int len;
            byte[] buff = _encoder.Encode(segment, segment.Length, out len);
            _bytesSent += (ulong)len;
            buff = _decoder.Decode(buff, len, out len);
            _playBuffer.AddSamples(buff, 0, len);
        
    

在这一行

byte[] buff = _encoder.Encode(segment, segment.Length, out len);

此时您创建了 RTP 数据包

https://www.rfc-editor.org/rfc/rfc3550

然后用C#发到网络上

通常作为 UDP

Sending UDP Packet in C#

在从 RTP 数据包中提取 Buff 之后,剩余的代码属于接收应用程序。

buff = _decoder.Decode(buff, len, out len);
            _playBuffer.AddSamples(buff, 0, len);

【讨论】:

没有给出任何例子,没有真正的解释。

以上是关于从设备发送 NAudio / Opus 编码的音频作为 RTP的主要内容,如果未能解决你的问题,请参考以下文章

使用 NAudio 将 WAV 流转换为 Opus

使用 NAudio 发送正在播放的音频

WebRTC Native M96 音频发送流程(SendRtp)以及接收音频包播放流程(OnPacketReceived)

WebRTC Native M96 音频发送流程(SendRtp)以及接收音频包播放流程(OnPacketReceived)

带有 NAudio 的音频中继器

如何在 Java 中播放 Opus 编码的音频?