PHP has no native audio processing — video/audio conversion means FFmpeg. You can call it via shell_exec (simple, works everywhere FFmpeg is installed) or use the php-ffmpeg wrapper for a more object-oriented API. For environments where you can't install FFmpeg (shared hosting, serverless), the API is the right call.

Method 1: FFmpeg via shell_exec (direct, full control)

The simplest approach — call FFmpeg directly. One line per conversion, full access to every FFmpeg option.

# Check FFmpeg is available
ffmpeg -version | head -1
# Install on Ubuntu/Debian
apt install ffmpeg
&1";
    $output = shell_exec($cmd);

    if (!file_exists($outPath) || filesize($outPath) === 0) {
        throw new RuntimeException("FFmpeg failed: " . substr($output, -500));
    }
    return $outPath;
}

function mp4ToMp3Cbr(string $inPath, string $outPath, int $bitrate = 192): string {
    // Fixed bitrate (CBR) — predictable file size
    $in = escapeshellarg($inPath);
    $out = escapeshellarg($outPath);
    $cmd = "ffmpeg -y -i {$in} -vn -acodec libmp3lame -b:a {$bitrate}k {$out} 2>&1";
    $output = shell_exec($cmd);
    if (!file_exists($outPath) || filesize($outPath) === 0) {
        throw new RuntimeException("FFmpeg failed: " . substr($output, -500));
    }
    return $outPath;
}

// VBR ~190kbps (best quality/size balance)
mp4ToMp3('video.mp4', 'audio.mp3');

// Fixed 128kbps (for speech, podcasts)
mp4ToMp3Cbr('video.mp4', 'speech.mp3', 128);

Quality guide for VBR (-q:a):

  • 0-1: ~245kbps — highest quality, largest files. Use for music.
  • 2: ~190kbps — transparent quality for most content. Good default.
  • 4-5: ~130kbps — noticeably lower quality but fine for speech.
  • 8-9: ~65kbps — low quality, small files. Voice memos only.

Method 2: php-ffmpeg wrapper (object-oriented API)

php-ffmpeg wraps the FFmpeg binary in a PHP object model. Good for complex workflows with progress callbacks and format negotiation.

composer require php-ffmpeg/php-ffmpeg
 '/usr/bin/ffmpeg',
        'ffprobe.binaries' => '/usr/bin/ffprobe',
        'timeout'          => 300,
        'ffmpeg.threads'   => 2,
    ]);

    $video = $ffmpeg->open($inPath);

    $format = new Mp3();
    $format->setAudioKiloBitrate($kilobitrate);
    $format->setAudioChannels(2);

    // Optional: progress callback
    $format->on('progress', function($video, $format, $percentage) {
        echo "Progress: {$percentage}%\n";
    });

    $video->save($format, $outPath);
}

mp4ToMp3PhpFfmpeg('video.mp4', 'audio.mp3', 192);
echo "Done\n";

php-ffmpeg wraps FFmpeg — you still need the binary installed. The main advantage over shell_exec is type safety, progress events, and automatic binary detection. For simple one-off conversions, shell_exec is less overhead.

Method 3: ChangeThisFile API (no FFmpeg install)

The API runs FFmpeg server-side. Source format is auto-detected from the filename — just send the file and target=mp3. Free tier: 1,000 conversions/month, no card required.

 true,
        CURLOPT_TIMEOUT => 300, // large videos may take time
        CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => [
            'file' => new CURLFile($inPath, 'video/mp4', basename($inPath)),
            'target' => 'mp3',
        ],
    ]);
    $result = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if ($code !== 200) {
        throw new RuntimeException("API error: HTTP {$code}: " . substr($result, 0, 200));
    }
    file_put_contents($outPath, $result);
}

mp4ToMp3Api('video.mp4', 'audio.mp3', 'ctf_sk_your_key_here');
echo "Done\n";

When to use each

ApproachBest forTradeoff
FFmpeg shell_execServers with FFmpeg installed, batch processingshell_exec must be enabled; FFmpeg binary required
php-ffmpegComplex workflows, progress tracking, type safetyComposer required; still needs FFmpeg binary
ChangeThisFile APIShared hosting, serverless, no FFmpeg availableNetwork call; 25MB file limit on free tier (upgrade for larger)

Production tips

  • Use -y flag to overwrite existing output. Without -y, FFmpeg waits for interactive confirmation — it hangs indefinitely when called from PHP.
  • Validate output exists and has non-zero size. FFmpeg sometimes exits 0 even when conversion fails. Always check file_exists() and filesize() on the output.
  • Set PHP execution timeout. Large video conversions take minutes. Use set_time_limit(0) and also ensure your web server (nginx, Apache) has a matching proxy timeout.
  • Check for shell_exec availability. Some shared hosts disable shell_exec. Test with: $check = function_exists('shell_exec') && !in_array('shell_exec', explode(',', ini_get('disable_functions')));
  • Use proc_open for better error handling. shell_exec mixes stdout and stderr with 2>&1. proc_open() gives separate handles so you can capture errors without polluting the output stream.

Shell_exec with FFmpeg is the fastest path on any server where FFmpeg is available. php-ffmpeg is worth adding for complex pipelines with progress tracking. For environments where you can't install FFmpeg, the API handles it server-side. Free tier: 1,000 conversions/month.