c# - NAudio - Stream audio outward in realtime via RTP -


i using naudio audio needs, i've run thorny issue. have remote device can receive rtp audio. stream audio file device (after u-law or similar encoding + rtp wrapping). however, there doesn't seem mechanism maintain outgoing timing rtp packets.

for example, waveout player "manages" timing responding requests underlying sound/directx layers. in manner, timing maintained sound drivers using "pull" method.

what i'm looking component can provide correct "pull" timing on (e.g.) iwaveprovider (or similar) can take each packet, rtp-ify it, , send on wire.

so, here's 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();  // <-- 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 ) {     // assume exception exit signal. } 

what happens while loop grabs "audio" fast can , blows out udp port. won't work rtp, since need maintain proper output timing.

as test, tried waveout notifyingsampleprovider, feeding each l/r pair encoder/rtp-ifier/sender, , seemed work fine. however, side effect of audio playing out of local speaker (via waveout) not acceptable application i'm working on (e.g. may want stream multiple different files different devices simultaneously). might using audio hardware (e.g.) simultaneous softphone converstations. basically, don't want use local audio hardware in implementation.

so, know of (or written) component can provide proper timing sender side of things? can grab audio @ proper rate can feed encoder/sender chain?

in case interested, got work. had add few components, , refine timing down fine level.

added:

  • now using naudio.wave.mp3filereader. uses because provides timing information each frame read.
  • configurable 'buffering' (it buffers frame times, not actual audio)
  • managing timing more precisely system.diagnostics.stopwatch , socket.poll

here's condensed (single-threaded) version of code no try/catches, etc. actual stuff uses player thread , other synch mechanisms, , lets 'cancel' playing killing socket.

// target endpoint.  use 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 simple udp-socket sending. socket sender = new socket( addressfamily.internetwork,                             sockettype.dgram,                             protocoltype.udp );  // 'constants.' double ticksperms = (double)stopwatch.frequency; ticksperms /= 1000.0;  // manage 'buffering' accumulating linked list of // mp3 frame times.  maximum time defined our buffer // time.  example, use 2-second 'buffer'. // 'framebufferticks' tracks total time in buffer. double framebuffermaxticks = ticksperms * 2000.0; linkedlist<double> framebuffer = new linkedlist<double>(); double framebufferticks = 0.0f;  // we'll track total mp3 time in ticks.  we'll need // stopwatch internal timing. double expectedticks = 0.0; stopwatch sw = new stopwatch(); long startticks = stopwatch.gettimestamp();  // read frames until null returned. int totalbytes = 0; mp3frame frame; while( (frame = reader.readnextframe()) != null ) {     // make sure frame buffer valid.  if not, we'll     // quit sending.     byte [] rawdata = frame.rawdata;     if( rawdata == null ) break;      // send complete frame.     totalbytes += rawdata.length;     sender.sendto(rawdata, target);      // timing next.  current total time , calculate     // frame.  we'll need calculate delta     // later.     double expectedms = reader.currenttime.totalmilliseconds;     double newexpectedticks = expectedms * ticksperms;     double deltaticks = newexpectedticks - expectedticks;     expectedticks = newexpectedticks;      // add frame time list (and adjust total     // frame buffer time).  if haven't exceeded our buffer     // time, go next packet.     framebuffer.addlast(deltaticks);     framebufferticks += deltaticks;     if( framebufferticks < framebuffermaxticks ) continue;      // pop 1 delay out of queue , adjust values.     double framedelayticks = framebuffer.first.value;     framebuffer.removefirst();     framebufferticks -= framedelayticks;      // wait....     double lastelapsed = 0.0f;     sw.reset();     sw.start();     while( lastelapsed < framedelayticks )     {         // short burst delays socket.poll() because         // supports higher timing precision         // thread.sleep().         sender.poll(100, selectmode.selecterror);         lastelapsed = (double)sw.elapsedticks;     }      // waited more ticks expected.  timewise,     // isn't lot.  cause accumulate large     // 'drift' if large file.  lower total     // buffer/queue tick total overage.     if( lastelapsed > framedelayticks )     {         framebufferticks -= (lastelapsed - framedelayticks);     } }  // done sending file.  we'll 1 final wait let // our 'buffer' empty.  total time our frame buffer ticks // plus latency. double elapsed = 0.0f; sw.reset(); sw.start(); while( elapsed < framebufferticks ) {     sender.poll(1000, selectmode.selecterror);     elapsed = (double)sw.elapsedticks; }  // dump 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); 

Comments

Popular posts from this blog

How has firefox/gecko HTML+CSS rendering changed in version 38? -

javascript - Complex json ng-repeat -

jquery - Cloning of rows and columns from the old table into the new with colSpan and rowSpan -