#!/usr/bin/env perl # mp3buff - ram-buffered MP3 file playback # $Id: mp3buff,v 1.10 2001/12/15 12:45:49 kw Exp $ # # Copyright (c) 1999, 2000, 2001 Klaus Weidner # # Distributable under the terms of the GNU Public License (GPL) # NO WARRANTY OF ANY KIND! # # I know this script is an ugly hack. It could badly use a rewrite. # Much tearing of hair resulted from discovering that the kernel # doesn't support nonblocking reads from a disk file. # How many bytes of RAM to use for buffering? #$cachesize = 3 *1024*1024; $cachesize = 24 *1024*1024; # # (Note that due to to some side effects due # to forking and copy-on-write, the kernel will # actually allocate twice that.) # Max data rate to be used for reading input MP3s # (if I/O bandwidth used is excessive, playback will skip) #$max_rate = 150 *1024; # bytes/sec $max_rate = 2*1024*1024; # bytes/sec # Backend player and command line options #$player = "madplay"; #@playeropts = qw(-Q >/dev/null 2>&1); $player = "amp"; @playeropts = qw(-q -b 64); use Fcntl; use POSIX ":sys_wait_h"; use integer; $|=1; $^W=1; $blksize = 4096; $nlow = 20000 * 9 / $blksize; $precache = 20000 * 5 / $blksize; $nblocks = $cachesize / $blksize; system("killall $player 2>/dev/null"); foreach $f (@ARGV) { if ($f =~ /(.*\/)?(.*\.m3u)/i) { $dir = defined $1 ? $1 : ""; open(I, $f) || die "$f: $!\n"; while () { chomp; $o = $dir . $_; $o =~ s/\/[^\/]*\/\.\.//g; push @tracks, $o; } close I; } else { push @tracks, $f; } } sub basename { my ($n) = shift; $n =~ s|.*/||; $n; } $readblk = 0; $playblk = 0; $buffered = 0; $partial = 0; $all_read = 0; $songs_read = 0; $songs_played = 0; $playing_new_song=1; $nprog = 40; $progress = "-" x $nprog; print "[$progress] "; $kill_request = 0; $player_running = 0; $child_pid = 0; $SIG{INT} = sub { $kill_request = 1 }; $SIG{PIPE} = sub { $player_running = 0 }; # $SIG{CHLD} = sub { waitpid(-1, &WNOHANG); $child_pid = 0; }; $SIG{CHLD} = sub { wait; $child_pid = 0; }; $last_time = 0; $is_child = 0; $read_this_sec = 0; $curr_block = 0; $want_block = 0; $input_open = 0; while(1) { # get new buffer if (!$last_song && !defined $file) { $file = shift @tracks; $last_song = 1 unless @tracks; #print "reading ", basename($file), "\n"; sysopen(I, $file, O_RDONLY) || die "sysopen $file: $!\n"; $input_open = 1; $cc = basename($file); if ($cc =~ /\d(\d)/) { $cc = $1; } elsif ($cc =~ /(\d)/) { $cc = $1; } else { $cc = substr($cc, 0, 1); } push @playfiles, $file; $songs_read ++; } # vvv !$fill && if (!$fill && $buffered < $nlow+$precache && !$child_pid && !$is_child && $input_open) { $child_pid = fork(); if ($child_pid) { # parent: do nothing # print "launch child pid=$child_pid\n"; } else { $is_child = 1; close P; close STDOUT; open (STDOUT, ">/dev/null"); # exit 0 unless $input_open; $SIG{INT} = sub { kill 'INT', getppid }; $fpos = sysseek(I, 0, 1); exit 0 unless $fpos; close I; sysopen(I, $file, O_RDONLY) || die "sysopen $file: $!\n"; sysseek(I, $fpos, 0); $fill=1; $player_running = 1; } } if ($is_child && ($buffered >= $nblocks-1 || $all_read)) { exit 0; } if (!$fill && $buffered < $nlow) { $fill = 1; # print "start fill\n"; $want_block = 0; } if ($fill && ($buffered >= $nblocks-1 || $all_read)) { exit 0 if $is_child; $fill = 0; # print "stop fill\n"; $want_block = 1; } $new_time = time; if ($new_time != $last_time) { $read_this_sec = 0; $last_time = $new_time; if (!$is_child) { my ($tmp) = $progress; substr($tmp, int((($readblk+$nblocks-$nlow)%$nblocks)*$nprog/$nblocks), 1) = "#"; substr($tmp, int($playblk*$nprog/$nblocks),1) = ">"; print "\r[$tmp]", $fill ? "+" : " "; print $child_pid ? "." : " "; # print "buf=$buffered fill=$fill inp=$input_open\n"; } } if (!$is_child) { $rin=""; vec ($rin, fileno(STDIN), 1) = 1; $nfound = select ($rin, undef, undef, 0); if ($nfound) { ; $kill_request = 1; } } if ($kill_request && !$is_child) { # print "skipping...\n"; $kill_request = 0; $kill_song = 1; if($songs_read == $songs_played+1) { close I; $input_open = 0; if ($child_pid) { # print "kill $child_pid\n"; kill 9, $child_pid; } } } if ($fill && !$all_read && $read_this_sec < $max_rate) { # print "READ $readblk buffered=$buffered\n"; $bytes = sysread(I, $buffers[$readblk], $blksize); #print "READ done $bytes !=$!\n"; if (defined $bytes && $bytes==0 || !defined $bytes) { undef $file; $last_of_file[$readblk]++; $all_read = 1 if $last_song; $input_open = 0; } else { substr($progress, int($readblk*$nprog/$nblocks),1) = $cc; $readblk = ($readblk+1) % $nblocks; $buffered++; $read_this_sec += $bytes; } } if ($read_this_sec >= $max_rate) { if ($is_child) { sleep 1; } else { $want_block = 1; } } elsif ($fill) { $want_block = 0; } if ($is_child) { next; } if (!$player_running && !$kill_song) { close P; open(P, "|$player @playeropts -") || die "can't start $player: $!\n"; fcntl(P, F_SETFL, $want_block ? 0 : O_NONBLOCK); $player_running = 1; } if ($playing_new_song) { print basename(shift @playfiles), "\n"; $playing_new_song=0; } next unless $buffered>1 || $all_read; if ($kill_song) { $written = length($buffers[$playblk]) - $partial; } else { if ($curr_block != $want_block) { fcntl(P, F_SETFL, $want_block ? 0 : O_NONBLOCK); $curr_block = $want_block; } $written = syswrite(P, $buffers[$playblk], length($buffers[$playblk])-$partial, $partial); } next unless defined $written; $partial += $written; if ($partial == length($buffers[$playblk])) { $playblk = ($playblk+1) % $nblocks; $buffered--; $partial = 0; #print " play $playblk buffered=$buffered\n"; if ($last_of_file[$playblk]) { $songs_played++; if ($last_song && $songs_played == $songs_read) { print "done\n"; exit 0; } # restart player close P; $kill_song = 0; $player_running = 0; $playing_new_song=1; $last_of_file[$playblk]--; } } }