Beefy Boxes and Bandwidth Generously Provided by pair Networks Bob
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Audio Stream Buffer

by reds (Novice)
on Jul 01, 2004 at 01:22 UTC ( #370949=perlquestion: print w/ replies, xml ) Need Help??
reds has asked for the wisdom of the Perl Monks concerning the following question:

Greetings fellow monks.

I am writing a small little ditty that grabs an audio stream from one machine (using Audio Hijack Pro, if you're curious), pipes it over the network to a listening daemon which then plays said audio over a stereo. I'm working over an 802.11b network (higher latency than Ethernet).

My question arises from poor results I've been seeing. I can't figure out how to buffer a couple seconds of audio so that the bursty, lagged nature of the 802.11b transmission doesn't lead to buffer underruns in aplay. Right now I'm seeing an underrun every second or so. Nasty.

On the client side (sender), I've got a basic:

my $buf; while ( read(STDIN, $buf, 128) ) { print SOCKET, $buf; }

On the server side (receiver), I've currently got this:

# open up our pipe and turn off the default buffering open AUDIO_OUT, "|/usr/bin/aplay -f S32_BE -r22050 -c2 --buffer-time=5 +00000 -" or die "aplay pipe broken\n"; select(AUDIO_OUT); $| = 1; select(STDOUT); my $stream; # these 2 lines are an attempt to fill up aplay's buffer # it doesn't appear to work at all read(CLIENT, $stream, 512); print AUDIO_OUT $stream; # read the stream and dump it out to aplay while ( defined( $stream = <CLIENT> ) ) { print AUDIO_OUT $stream; }

I've seen some disucssion here about sysread() vs. read(), but I can't seem to figure out how I would dump the audio to AUDIO_OUT without print() (you're not supposed to mix buffered functions with unbuffered ones).

Any help would be greatly appreciated! Chris H.

Comment on Audio Stream Buffer
Select or Download Code
Re: Audio Stream Buffer
by Zaxo (Archbishop) on Jul 01, 2004 at 01:36 UTC

    I think that you will get better results from four-arg select or the higher level IO::Select.

    The select loop would allow several input packets to accumulate (with the .= operator) while a stalled write to *AUDIO_OUT is cooking. Once the delay in output is resolved, you'd have all the received data to pass along.

    That will be most effective if you have an intermediate child process which can quickly read and buffer what you write to it and handle writes to the audio player in its own good time.

    Update: Here's a fairly dumb example of a select loop. It reads from STDIN and writes to a child of open.

    #!/usr/bin/perl open my $ofh, '-|', '/usr/bin/perl', 'lazyread.pl' or die $!; my ($inv, $ouv, $erv); $inv = $ouv = $erv = ''; vec($inv, fileno(*STDIN), 1) = 1; vec($ouv, fileno($fh), 1) = 1; $erv = $inv | $ouv; my $string = ''; { my $num = select my $in = $inv, my $out = $ouv, my $err = $erv, undef; sysread *STDIN, $string, -1, 10, length($string) or last if $in ne $inv; $string = substr($string, syswrite( $ofh, $string, length($string) +)) if $string && $out ne $ouv; redo; } __END__ #!/usr/bin/perl # lazyread.pl open my $fh, '>', 'lazy' or die $!; while (<>) { print $fh $_; sleep 5; }
    Sorry I couldn't write closer to the problem, but my setup doesn't have alsasound installed.

    reds, your fork example looks pretty good, but I think I see the problem. The child process will be unable to see changes the parent makes in its copy of $buffer. You'd need to set up IPC between the two, like a pipe or socket, to pass data.

    After Compline,
    Zaxo

      Could you show me your suggested select loop please? I am still trying to get my head properly around select.

      cheers

      tachyon

      I, too, am a little fuzzy on how the select loop system works. Select just chooses the current FH, so how can I use that to buffer the input stream?

      Also, once I have my stream buffered, could you or someone please provide a small bit of sample code for how to set up a child process? To date, my strength has been processing text files - not fancy pants networking or multiple threads!

      I just tried the code below, but am beginning to understand that because the reading takes more time than it does to play 512 bytes worth of data, I get the interrupted playback. In fact, this code has also somehow corrupted the audio stream so that it plays a tone of some sort, rather like cartoon car horn sound. Learning how to set up a child process to smoothly feed aplay would really help me out!

      # build up a buffer sysread(CLIENT, $read_buffer, 2048); syswrite(AUDIO_OUT, $read_buffer); # with buffer established, stream as usual while ( sysread(CLIENT, $read_buffer, 512) ) { $buffer .= $read_buffer; syswrite( AUDIO_OUT, $buffer, 512);

      Thanks for your help so far. Cheers!

      --------------------------

      Some while later:

      I've been reading and learned that threads are different from child processes. It would seem as if a thread would serve me best, since I don't want copies of all the filehandles and stuff. Is this correct? The code I have doesn't seem to execute the child code...

      my $child_pid; if ( not defined($child_pid = fork()) ) { die "cannot fork: $!"; } elsif ($child_pid) { # I'm the parent # keep reading the stream while ( sysread(CLIENT, $read_buffer, 512) ) { $buffer .= $read_buffer; print "."; } } else { # I'm the child # with buffer established, stream as usual while ( $buffer ) { syswrite( AUDIO_OUT, $buffer, 512); print "-"; } exit; } waitpid($child_pid, 0);
Re: Audio Stream Buffer
by tachyon (Chancellor) on Jul 01, 2004 at 02:02 UTC

    sysread and syswrite (rather than print) let you avoid bufferring by stdio. I suggest you should be using it on both sides as you don't want stdio to be involved buffering. It would seem logical to read/write the same number of bytes rather than sending 128 and waiting on 512. You could do:

    while( sysread(CLIENT,$stream,512) ){ syswrite( AUDIO_OUT, $stream ) }

    These simple steps may or may not resolve you issues.

    Buffering only helps if you 1) get ahead of the game and 2) stay ahead of the game (unless you got far enough ahead to start) of course. Depending on the behaviour of aplay you could possibly just buffer X bytes to start, send that to aplay to fill its buffer, and then loop as above. This would only work if aplay accepts the entire initial stream without blocking so that the following loop is effectively just running in top up mode. Failing that you may need a dedicated local reader that can buffer and supply a consistent stream to your writer as suggested by Zaxo

    cheers

    tachyon

      I'll change the read/write bytes to be the same. At one point, they were the same, but I changed it in the hopes that reading more initially would fill up aplay's buffer and provide me with the padding I needed to cover for the bursty 802.11b stream.

      I believe I will need the full-blown local reader that Zaxo suggested because aplay's maximum buffer size is very small. I think I need something several seconds long.

      As far as the capacity goes, the the audio format is a stereo signed 32 bit, big endian stream at 22050 Hz. I believe that this translates into 1.3Mb of data per second, which should be well within the capacity of an unladen, full strength, 802.11b connection.

Re: Audio Stream Buffer
by aquarium (Curate) on Jul 01, 2004 at 06:07 UTC
    here's my suggestions:
    - make sure that indeed you're in non-buffered read/write over the sockets.
    - on the receiving side, write the received data to a local file in non-buffered mode; i.e. NOT pipe the received socket data straight into aplay.
    - hope for the best....as current 802.11G implementations still seem rather bursty at the best of times, as you noted.
Re: Audio Stream Buffer
by monkey_boy (Curate) on Jul 01, 2004 at 08:27 UTC
    Have a look at slimserver, its open source and does audio streaming. The code wont be directly usable for your situation, but it written in perl so should be hackable.
    I should really do something about this apathy ... but i just cant be bothered

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://370949]
Approved by Zaxo
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others lurking in the Monastery: (5)
As of 2014-04-19 13:15 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    April first is:







    Results (480 votes), past polls