Search code examples
phpmp3lame

Generate .mp3 with LAME from multiple .wav files using PHP


I'm trying to generate an .mp3 that can be used as a metronome (with accent/off-accent notes). I'm new to PHP streams as well as LAME. Using this documentation, I've taken a stab at it - but I don't think I'm using the streams correctly:

<?php  
// mp3=/Bottle_120_4_4_16.mp3 (Bottle 4/4 time, 120bpm - 4 measures (16 beats))
$mp3 = strtolower(str_replace('/', '', $_GET['mp3']));
$m = explode('_', $mp3);

$sound = $m[0];//bottle
$BPM = $m[1];//120
$time_a = $m[2];//4
$time_b = $m[3];//4
$nBeats = $m[4];//16 

header('Content-Type: audio/mpeg');
header('Content-Disposition:attachment;filename='.$mp3);
header('Pragma: no-cache');

$stream = fopen('php://output', 'a');

// GENERATE PCM
$sampleRate = 48000;
$channels = 1;//Mono
$bytePerSample = 2;

$bytesInSecond = ($sampleRate * $bytePerSample * $channels);

$beatLenInSec = (60 / $BPM);
if ($time_b == 8){
    $beatLenInSec = ($beatLenInSec / 2);
}

$bytesInBeat = intval($beatLenInSec * $bytesInSecond);
$bytesInBeat = (intval($bytesInBeat / $bytePerSample) * $bytePerSample);

$accentFileName = 'sound/'.$sound.'/wav/'.$sound.'-accent_ws.wav';
$noteFileName = 'sound/'.$sound.'/wav/'.$sound.'-note_ws.wav';

$PCMDataPosition = 58;
$fileA = fopen($accentFileName, 'r');
$fileB = fopen($noteFileName, 'r');

$data = '';
for ($i = 0; $i < $nBeats; $i++) {
    if (($i % $time_a) == 1){
        fseek($fileA, $PCMDataPosition);
        $data = fread($fileA, $bytesInBeat);
    } else {
        fseek($fileB, $PCMDataPosition);
        $data = fread($fileB, $bytesInBeat);
    }
    fwrite($stream, $data);
}

$lame = fopen('php://output', 'a');

// LAME ENCODE
$path = '/home/html/';

system($path.'bin/lame -r -s 48.00 -m m -b 16 - - '.$stream.' '.$lame);

fclose($stream);
fclose($lame);
?>

Are two streams necessary for what I'm doing?


Solution

  • No, two streams are not needed, one is enough.

    However it's not a good idea to convert the file on the standard output, it's better to use a temporary file to easily handle errors.

    This is an example of how to do it (tested on Ubuntu 13.10)

    <?php  
    
    class Wav2Mp3 {
        public $mp3;
        public $BPM;
        public $sound;
        public $time_a;
        public $time_b;
        public $nBeats;
        public $bytesInBeat;
    
        public function __construct($mp3) {
            // mp3=/Bottle_120_4_4_16.mp3 (Bottle 4/4 time, 120bpm - 4 measures (16 beats))
            $this->mp3 = basename(strtolower($mp3));
            $m = explode('_', $this->mp3);
            if (is_array($m) && (count($m) == 5)) {
                $this->sound  = $m[0];    // bottle
                $this->BPM    = $m[1];    // 120
                $this->time_a = $m[2];    // 4
                $this->time_b = $m[3];    // 4
                $this->nBeats = $m[4];    // 16 
    
                if ($this->nBeats <= 0) {
                    throw new Exception("Invalid Beats ({$this->nBeats})");
                }
    
                // GENERATE PCM
                $sampleRate = 48000;
                $channels = 1;  // Mono
                $bytePerSample = 2;
                $bytesInSecond = ($sampleRate * $bytePerSample * $channels);
    
                if ($this->BPM > 0) {
                    $beatLenInSec = (60 / $this->BPM);
                    if ($this->time_b == 8) {
                        $beatLenInSec /= 2;
                    }
                    $bytesInBeat = intval($beatLenInSec * $bytesInSecond);
                    $this->bytesInBeat = intval($bytesInBeat / $bytePerSample) * $bytePerSample;
                    //die(print_r($this,true));
                } else {
                    throw new Exception("Invalid BPM ({$this->BPM})");
                }
            } else {
                throw new Exception("Invalid file name format '{$mp3}'");
            }
        }
    
        public function readWav($filename) {
            if ($fp = @fopen($filename, 'r')) {
                $data = FALSE;
                if (fseek($fp, 58) == 0) {
                    $data = fread($fp, $this->bytesInBeat);
                } else {
                    fclose($fp);
                    throw new Exception("Seek failure '{$filename}'");
                }
                fclose($fp);
                if ($data === FALSE) {
                    throw new Exception("Read failure '{$filename}'");
                }
                return($data);
            } else {
                throw new Exception("Open failure '{$filename}'");
            }
        }
    
        public function writeMp3($filename, $accent, $note) {
            $descriptorspec = array(
                0 => array("pipe", "r"),
                1 => array("file", $filename, "w"),
                2 => array("file", "/dev/null", "w")
            );
            // LAME ENCODE
            $process = proc_open("/usr/bin/lame -r -s 48.00 -m m -b 16 - -", $descriptorspec, $pipes);
            if (is_resource($process)) {
                for ($i = 0; $i < $this->nBeats; $i++) {
                    fwrite($pipes[0], (($i % $this->time_a) == 1) ? $accent : $note);
                }
                foreach ($descriptorspec as $i => $dsc) {
                    if (isset($pipes[$i]) && is_resource($pipes[$i])) {
                        fclose($pipes[$i]);
                    }
                }
                proc_close($process);
            } else {
                throw new Exception("Process open failure");
            }
        }
    
        public function convert() {
            $path = 'sound/'.$this->sound.'/wav';
            $accent = $this->readWav($path.'/'.$this->sound.'-accent_ws.wav');
            $note = $this->readWav($path.'/'.$this->sound.'-note_ws.wav');
            $outfile = tempnam('/tmp', 'snd');
            $this->writeMp3($outfile, $accent, $note);
            if (file_exists($outfile) && filesize($outfile)) {
                return($outfile);
            } else {
                @unlink($outfile);
                throw new Exception("Conversion failure");
            }
        }
    }
    
    
    try {
        $w = new Wav2Mp3('/Bottle_120_4_4_16.mp3');
        $filename = $w->convert();
        header('Content-Type: audio/mpeg');
        header('Content-Disposition: attachment; filename='.basename($filename));
        header('Pragma: no-cache');
        readfile($filename);
        unlink($filename);
    } catch (Exception $e) {
        die($e->getMessage().PHP_EOL);
    }
    
    ?>
    

    Also have a look at Convert WAV to MP3 using LAME from PHP