【问题标题】:NAudio - Stream audio outward in realtime via RTPNAudio - 通过 RTP 实时向外传输音频
【发布时间】:2015-09-12 08:37:04
【问题描述】:

我使用NAudio 来满足我的音频需求,但我遇到了一个棘手的问题。我有一个可以接收 RTP 音频的远程设备。我想将音频文件流式传输到该设备(在 u-law 或类似编码 + RTP 包装之后)。但是,似乎没有一种机制来维护 RTP 数据包的传出时间。

例如,WaveOut 播放器通过简单地响应来自底层声音/directx 层的请求来“管理”时间。以这种方式,声音驱动程序实际上使用“拉”方法来维持时间。

我正在寻找的是一个组件,它可以在(例如)IWaveProvider(或类似的)上提供正确的“拉”时间,以便我可以接收每个数据包,RTP 化它,然后发送它电线。

所以,这里是核心代码:

IPEndPoint target = new IPEndPoint(addr, port);
Socket sender = new Socket( AddressFamily.InterNetwork,
                            SocketType.Dgram,
                            ProtocolType.Udp );

IWaveProvider provider = new AudioFileReader(filename);
MuLawChatCodec codec = new MuLawChatCodec();  // <-- from chat example
int alignment = provider.BlockAlign * 32;  // <-- arbitrary
byte [] buffer = new byte [alignment];

try
{
    int numbytes;
    while( (numbytes = provider.Read(buffer, 0, alignment)) != 0 )
    {
        byte [] encoded = m_Codec.Encode(buffer, 0, numbytes);
        m_Sender.SendTo(buffer, numbytes, SocketFlags.None, target);
    }
}
catch( Exception )
{
    // We just assume that any exception is an exit signal.
}

发生的情况是while 循环尽可能快地抓取“音频”并将其从 UDP 端口中吹出。这不适用于 RTP,因为我们需要保持正确的输出时序。

作为测试,我尝试了带有 NotifyingSampleProvider 的 WaveOut,将每个 L/R 对馈送到编码器/RTP-ifier/sender,它似乎工作正常。但是,我正在处理的应用程序不能接受从本地扬声器(通过 WaveOut)播放音频的副作用(例如,我们可能希望同时将多个不同的文件流式传输到不同的设备)。我们也可能将音频硬件用于(例如)同时进行的软电话通话。基本上,我们不想在这个实现中实际使用本地音频硬件。

那么,有没有人知道(或编写)一个可以为发送方提供适当时间的组件?可以以适当的速率抓取音频以便我可以输入编码器/发送器链的东西?

【问题讨论】:

    标签: c# streaming audio-streaming naudio


    【解决方案1】:

    如果有人有兴趣,我可以让它工作。我必须添加一些组件,并将时间优化到非常精细的水平。

    添加:

    • 现在使用NAudio.Wave.Mp3FileReader。我使用它是因为它为您阅读的每一帧提供时间信息。
    • 可配置的“缓冲”(它缓冲帧时间,而不是实际音频)
    • 使用System.Diagnostics.StopwatchSocket.Poll 更精确地管理时间

    这是代码的压缩(单线程)版本,没有尝试/捕获等。实际的东西使用播放器线程和一些其他同步机制,并让您通过终止套接字来“取消”播放。

    // Target endpoint.  Use the default Barix UDP 'high priority'
    // port.
    IPEndPoint target = new IPEndPoint( IPAddress.Parse("192.168.1.100"), 3030 );
    
    // Create reader...
    NAudio.Wave.Mp3FileReader reader = new Mp3FileReader("hello.mp3");
    
    // Build a simple udp-socket for sending.
    Socket sender = new Socket( AddressFamily.InterNetwork,
                                SocketType.Dgram,
                                ProtocolType.Udp );
    
    // Now for some 'constants.'
    double ticksperms = (double)Stopwatch.Frequency;
    ticksperms /= 1000.0;
    
    // We manage 'buffering' by just accumulating a linked list of
    // mp3 frame times.  The maximum time is defined by our buffer
    // time.  For this example, use a 2-second 'buffer'.
    // 'framebufferticks' tracks the total time in the buffer.
    double framebuffermaxticks = ticksperms * 2000.0;
    LinkedList<double> framebuffer = new LinkedList<double>();
    double framebufferticks = 0.0f;
    
    // We'll track the total mp3 time in ticks.  We'll also need a
    // stopwatch for the internal timing.
    double expectedticks = 0.0;
    Stopwatch sw = new Stopwatch();
    long startticks = Stopwatch.GetTimestamp();
    
    // Now we just read frames until a null is returned.
    int totalbytes = 0;
    Mp3Frame frame;
    while( (frame = reader.ReadNextFrame()) != null )
    {
        // Make sure the frame buffer is valid.  If not, we'll
        // quit sending.
        byte [] rawdata = frame.RawData;
        if( rawdata == null ) break;
    
        // Send the complete frame.
        totalbytes += rawdata.Length;
        sender.SendTo(rawdata, target);
    
        // Timing is next.  Get the current total time and calculate
        // this frame.  We'll also need to calculate the delta for
        // later.
        double expectedms = reader.CurrentTime.TotalMilliseconds;
        double newexpectedticks = expectedms * ticksperms;
        double deltaticks = newexpectedticks - expectedticks;
        expectedticks = newexpectedticks;
    
        // Add the frame time to the list (and adjust the total
        // frame buffer time).  If we haven't exceeded our buffer
        // time, then just go get the next packet.
        framebuffer.AddLast(deltaticks);
        framebufferticks += deltaticks;
        if( framebufferticks < framebuffermaxticks ) continue;
    
        // Pop one delay out of the queue and adjust values.
        double framedelayticks = framebuffer.First.Value;
        framebuffer.RemoveFirst();
        framebufferticks -= framedelayticks;
    
        // Now we just wait....
        double lastelapsed = 0.0f;
        sw.Reset();
        sw.Start();
        while( lastelapsed < framedelayticks )
        {
            // We do short burst delays with Socket.Poll() because it
            // supports a much higher timing precision than
            // Thread.Sleep().
            sender.Poll(100, SelectMode.SelectError);
            lastelapsed = (double)sw.ElapsedTicks;
        }
    
        // We most likely waited more ticks than we expected.  Timewise,
        // this isn't a lot.  But it could cause accumulate into a large
        // 'drift' if this is a very large file.  We lower the total
        // buffer/queue tick total by the overage.
        if( lastelapsed > framedelayticks )
        {
            framebufferticks -= (lastelapsed - framedelayticks);
        }
    }
    
    // Done sending the file.  Now we'll just do one final wait to let
    // our 'buffer' empty.  The total time is our frame buffer ticks
    // plus any latency.
    double elapsed = 0.0f;
    sw.Reset();
    sw.Start();
    while( elapsed < framebufferticks )
    {
        sender.Poll(1000, SelectMode.SelectError);
        elapsed = (double)sw.ElapsedTicks;
    }
    
    // Dump some final timing information:
    double diffticks = (double)(Stopwatch.GetTimestamp() - startticks);
    double diffms = diffticks / ticksperms;
    Console.WriteLine("Sent {0} byte(s) in {1}ms (filetime={2}ms)",
                     totalbytes, diffms, reader.CurrentTime.TotalMilliseconds);
    

    【讨论】:

      【解决方案2】:

      基本上,您需要生成一个时钟来驱动样品交付。 AudioFileReader 继承自 WaveStream,后者通过 CurrentTime(根据 WaveFormat.AverageBytesPerSecond 计算)为样本提供当前时间。您应该可以使用它来为音频数据包添加时间戳。

      此外,根据您的项目需求,您可能需要查看 StreamCoder 的MediaSuite.NET 产品。我过去用它来做类似的事情,它为这类事情抽象了大部分传输级别的复杂性。

      【讨论】:

      • 安德鲁,我试试看。我还找到了一个 Barix 端口,我可以向它发送一个完整的 .mp3 (tcp),它会在阅读时播放它。那个似乎工作得更好(我可以为那个使用一个简单的非 NAudio 文件流)。但这也有缺点。
      猜你喜欢
      • 2012-04-10
      • 1970-01-01
      • 1970-01-01
      • 2023-03-12
      • 2013-07-23
      • 1970-01-01
      • 1970-01-01
      • 2012-11-20
      • 1970-01-01
      相关资源
      最近更新 更多