简体   繁体   English

NAudio-通过RTP实时向外传输音频

[英]NAudio - Stream audio outward in realtime via RTP

I am using NAudio for my audio needs, but I've run into a thorny issue. 我正在使用NAudio满足音频需求,但是遇到了一个棘手的问题。 I have a remote device that can receive RTP audio. 我有一个可以接收RTP音频的远程设备。 I would like to stream an audio file to that device (after u-law or similar encoding + RTP wrapping). 我想将音频文件流式传输到该设备(在使用u-law或类似的编码+ RTP包装之后)。 However, there doesn't seem to be a mechanism to maintain the outgoing timing for the RTP packets. 但是,似乎没有一种机制可以维持RTP数据包的传出定时。

For example, a WaveOut player "manages" timing by simply responding to requests from the underlying sound/directx layers. 例如,WaveOut播放器通过简单地响应来自底层声音/直接层的请求来“管理”时序。 In this manner, the timing is actually maintained by the sound drivers using a "pull" method. 以这种方式,实际上由声音驱动器使用“拉”方法来保持定时。

What I'm looking for is a component that can provide the correct "pull" timing on an (eg) IWaveProvider (or similar) so that I can take each packet, RTP-ify it, and send it over the wire. 我正在寻找的组件可以在(例如) IWaveProvider (或类似设备)上提供正确的“拉”定时,以便我可以接收每个数据包,对它进行RTP验证,然后通过电线发送。

So, here's the core code: 因此,这是核心代码:

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

What happens is that the while loop just grabs "audio" as fast as it can and blows it out the UDP port. 发生的是, while循环只是尽可能快地捕获“音频”,并将其吹出UDP端口。 This won't work for RTP, since we need to maintain the proper output timing. 这对于RTP不起作用,因为我们需要保持适当的输出时序。

As a test, I tried a WaveOut with a NotifyingSampleProvider , feeding each L/R pair to the encoder/RTP-ifier/sender, and it seemed to work fine. 作为测试,我尝试了使用带有NotifyingSampleProvider的WaveOut,将每个L / R对馈送到编码器/ RTP-ifier / sender,它似乎工作正常。 However, the side effect of the audio playing out of the local speaker (via WaveOut) is not acceptable for the application I'm working on (eg we may want to stream multiple different files to different devices simultaneously). 但是,我正在处理的应用程序无法接受通过本地扬声器(通过WaveOut)播放音频的副作用(例如,我们可能希望同时将多个不同的文件流式传输到不同的设备)。 We also might be using the audio hardware for (eg) simultaneous softphone converstations. 我们还可能将音频硬件用于(例如)同时进行的软电话转换。 Basically, we don't want to actually use the local audio hardware in this implementation. 基本上,我们不希望在此实现中实际使用本地音频硬件。

So, does anyone know of (or written) a component that can provide the proper timing for the sender side of things? 那么,有谁知道(或编写过)可以为事物的发送方提供适当计时的组件吗? Something that can grab audio at the proper rate so that I can feed the encoder/sender chain? 可以以适当的速率获取音频的东西,以便可以馈入编码器/发送器链吗?

In case anyone is interested, I got it to work. 万一有兴趣的人,我可以使用它。 I had to add a few components, and refine the timing down to a very fine level. 我必须添加一些组件,并将时序调整到非常精细的水平。

Added: 添加:

  • Now using NAudio.Wave.Mp3FileReader . 现在使用NAudio.Wave.Mp3FileReader I uses this because it provides timing information with each frame that you read. 我之所以使用它,是因为它为您阅读的每一帧提供了定时信息。
  • Configurable 'buffering' (it buffers frame times, not actual audio) 可配置的“缓冲”(缓冲帧时间,而不缓冲实际音频)
  • Managing timing more precisely with System.Diagnostics.Stopwatch and Socket.Poll 使用System.Diagnostics.StopwatchSocket.Poll更精确地管理计时

Here's a condensed (single-threaded) version of the code with no try/catches, etc. The actual stuff uses a player thread and some other synch mechanisms, and lets you 'cancel' playing by killing the socket. 这是代码的压缩(单线程)版本,没有try / catches等。实际的东西使用播放器线程和某些其他同步机制,并允许您通过取消套接字来“取消”播放。

// 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);

Basically you need to generate a clock to drive sample delivery. 基本上,您需要生成一个时钟来驱动样品传输。 The AudioFileReader inherits from WaveStream which provides the current time for the samples via CurrentTime (calculated off of WaveFormat.AverageBytesPerSecond). AudioFileReader继承自WaveStream,后者通过CurrentTime(根据WaveFormat.AverageBytesPerSecond计算)提供采样的当前时间。 You should be able to use this to timestamp the audio packets. 您应该可以使用它来为音频数据包添加时间戳。

Also, depending of your project needs you may want to look at StreamCoder's MediaSuite.NET product. 另外,根据您的项目需求,您可能需要查看StreamCoder的MediaSuite.NET产品。 I've used it in the past to do similar things, it abstracts out much of the transport level complexity for this sort of thing. 我过去曾用它来做类似的事情,它为这种事情抽象出了很多传输层的复杂性。

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

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