Конвертировать аудио pcm_s16le в mp3 (в php с помощью ffmpeg/sox/...)?

У меня есть корзина и файл cue (образ компакт-диска), и я создаю веб-сайт, на котором можно слушать мою музыку. Мне удалось разделить дорожки и создать wav-заголовок (44 байта), чтобы мой браузер мог читать аудио, но файлы wav слишком велики, чтобы их можно было читать при нестабильном соединении (это 1411 кбит/с, тогда как mp3 будет 320 кбит/с или ниже!).

Во-первых, есть ли способ конвертировать аудио в mp3 без создания временного файла на php? Может быть, ffmpeg или sox могут помочь?

Во-вторых, я хотел бы иметь возможность удовлетворять запросы на частичный контент (чтобы я мог пропустить, не загружая весь файл). Мне уже удалось это сделать с WAV-файлами, и мне нужно знать несколько вещей, чтобы сделать это с mp3:

  • Как сделать заголовок mp3?
  • Как определить длину файла?
  • Как конвертировать аудио pcm_s16le в mp3 без заголовков с постоянной скоростью передачи данных?

Я попробовал поискать в Интернете, но не нашел того, на что надеялся. Я надеюсь, что вы можете мне помочь.

Поскольку, как написано, речь не идет о программировании, вы с большей вероятностью получите ответ (как предложено в справке по тегу ffmpeg ) в Видеопроизводстве или Суперпользователе. Однако перед публикацией обязательно найдите существующие похожие вопросы и проверьте их справку. Спасибо!

NotTheDr01ds 15.07.2024 23:58
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
1
1
63
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Для моего первого вопроса: чтобы конвертировать wav в mp3 в php без создания временного файла, я использовал:

passthru(dd if = "$bin_path" bs=1 skip=$skip_bytes count=$count_bytes | ffmpeg -ss 0 -f s16le -ar 44100 -ac 2 -i pipe:0 -codec:a libmp3lame -b:a 320k -ac 2 -joint_stereo 0 -compression_level 0 -write_id3v1 0 -id3v2_version none -f mp3 -);

где $bin_path — путь к вашему двоичному файлу (вам нужно экранировать ", если он есть в пути).
$skip_bytes равно 44100 * 4 * song_start (с)
$count_bytes равно 44100 * 4 * song_duration (с)

Вот разбивка команды FFmpeg:
Для ввода:
-ss 0 начинать с 0,0 с, даже если нет звука (тишина)
-f s16le -ar 44100 -ac 2 подскажите, что вход pcm_s16le (-f s16le) стерео (-ac 2) с частотой дискретизации 44100 Гц (-ar 44100)
-i pipe:0 вход поступает из трубы
Для вывода:
-codec:a libmp3lame кодирование mp3
-b:a 320k битрейт (320 кбит/с)
-ac 2 -joint_stereo 0 2-канальное стерео
-compression_level 0 используйте постоянный битрейт
-id3v2_version none пропустите заголовок ID3
- вывод в sdin

Что касается второй части вашего вопроса, mp3 не имеют ничего общего с wav-файлами, и предсказать их размер практически невозможно. Однако мне все равно удалось это сделать, сделав песню длительностью кратной 1,28 секунды (mp3 (версия 1, слой III) имеет внутреннюю структуру, повторяющуюся каждые 1,28 секунды).
Вот как я это сделал:

$bin_path = "/path/to/binary.bin";
$song_start = "182.12"; // song starts after 3m0.12s 
$song_duration = "176.14"; // and lasts 2m56.02s

$skip_bytes = 44100 * 4 * $song_start;
$count_bytes = 44100 * 4 * $song_duration;

$chunk_size_mp3 = 51200; // Size (in bytes) for 1.28s of 320kbps MP3
$chunk_size_wav = 225792; // Size (in bytes) for 1.28s of WAV
$file_size = (floor($song_duration/1.28)+1) * $chunk_size_mp3;

$need_exit = false;

//--------------------HTTP HEADERS--------------------
ob_get_clean();
header('Content-Type: audio/mpeg');
header('Cache-Control: no-cache', true);
header('Expires: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT'); // Expire now!
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', @filemtime($file_path)) . ' GMT' );
$start_index = 0;
$end_index = $file_size - 1;
header('Accept-Ranges: bytes');

if (isset($_SERVER['HTTP_RANGE'])){

    $c_start = $start_index;
    $c_end = $end_index;

    list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
    if (strpos($range, ',') !== false){
        header('HTTP/1.1 416 Requested Range Not Satisfiable');
        header("Content-Range: bytes $start_index-$end_index/$file_size");
        $need_exit = true;
    }
    
    if (!$need_exit){
        if ($range == '-'){
            $c_start = $file_size - substr($range, 1);
        }else{
            $range = explode('-', $range);
            $c_start = $range[0];

            $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end;
        }
        $c_end = ($c_end > $end_index) ? $end_index : $c_end;
        if ($c_start > $c_end || $c_start > $file_size - 1 || $c_end >= $file_size){
            header('HTTP/1.1 416 Requested Range Not Satisfiable');
            header("Content-Range: bytes $start_index-$end_index/$file_size");
            $need_exit = true;
        }
        
        if (!$need_exit){
            if ($c_start <= $chunk_size_mp3){
                $start_index = 0;
            }else{
                $start_index = $c_start;
            }
            if ($c_end <= $chunk_size_mp3){
                $end_index = $chunk_size_mp3 - 1;
            }else{
                $end_index = $c_end;
            }
            $length = $end_index - $start_index + 1;
            header('HTTP/1.1 206 Partial Content');
            header('Content-Length: ' . $length);
            header("Content-Range: bytes $start_index-$end_index/".$file_size);
        }
    }
}else{
    header('Content-Length: ' . $file_size);
}
//--------------------HTTP HEADERS--------------------

//-----------------------STREAM-----------------------
if (!$need_exit){
    $i = $start_index;
    if ($start_index === 0){
        $mp3_data = shell_exec("php /path/to/dd.php \"$bin_path\" $skip_bytes ".($chunk_size_wav*2)." 0 | ffmpeg -ss 0 -f s16le -ar 44100 -ac 2 -i pipe:0 -codec:a libmp3lame -b:a 320k -ac 2 -joint_stereo 0 -compression_level 0 -write_id3v1 0 -id3v2_version none -f mp3 -");
        for($j = 0; $j < $chunk_size_mp3; $j++){
            echo $mp3_data[(int)$j];
        }
        $i += $chunk_size_mp3;
    }
    while($i <= $end_index){
        $start_chunk_number = floor($i/$chunk_size_mp3)-1;
        $end_chunk_number = $start_chunk_number + 2;
        $start_wav_byte = $skip_bytes + $start_chunk_number * $chunk_size_wav;
        $end_wav_byte = $start_wav_byte + 3 * $chunk_size_wav - 1;
        if ($end_wav_byte > $skip_bytes + $count_bytes - 1){
            $empty_bytes = $end_wav_byte - ($skip_bytes + $count_bytes - 1);
            $end_wav_byte = $skip_bytes + $count_bytes - 1;
        }else{
            $empty_bytes = 0;
        }
        $mp3_data = shell_exec("php /path/to/dd.php \"$bin_path\" $start_wav_byte ".($end_wav_byte - $start_wav_byte + 1)." $empty_bytes | ffmpeg -ss 0 -f s16le -ar 44100 -ac 2 -i pipe:0 -codec:a libmp3lame -b:a 320k -ac 2 -joint_stereo 0 -compression_level 0 -write_id3v1 0 -id3v2_version none -f mp3 -");
        $ign_mp3_bytes = $i - $start_chunk_number * $chunk_size_mp3;
        $j = $ign_mp3_bytes;
        while($i <= $end_index && $j < 2*$chunk_size_mp3){
            echo $mp3_data[(int)$j];
            $i++;
            $j++;
        }
    }
}
//-----------------------STREAM-----------------------
?>

дд.php:

<?php
if (isset($argv[3])){
    $bin_file = $argv[1];
    $skip_bytes = (int)$argv[2];
    $count_bytes = (int)$argv[3];
    if (!isset($argv[4])){
        $empty_bytes = 0;
    }else{
        $empty_bytes = (int)$argv[4];
    }

    $file_size = $count_bytes;
    $file_stream = fopen($bin_file, 'rb');
    
    fseek($file_stream, $skip_bytes);
    $i = 0;
    $buffer = 102400;
    set_time_limit(0);
    while(!feof($file_stream) && $i < $count_bytes){
        $bytesToRead = $buffer;
        if (($i+$bytesToRead) > $count_bytes){
            $bytesToRead = $count_bytes - $i;
        }
        echo fread($file_stream, $bytesToRead);
        flush();
        $i += $bytesToRead;
    }
    if ($empty_bytes > 0){
        for($k = 0; $k < $empty_bytes; $k++){
            echo "\x00";
        }
    }
}
?>

Несколько замечаний, которые следует сделать здесь:

  • mp3 имеют то, что можно рассматривать как заголовок в начале каждого кадра (самая маленькая часть mp3), поэтому нет необходимости беспокоиться о его создании или удалении.
  • Я всегда конвертирую три фрагмента mp3 за раз, чтобы отправить средний, потому что я не знаю, как работает сжатие mp3, и считаю, что это предотвратит любые зависания.
  • лучшим вариантом было бы извлечь песни с компакт-диска, даже если это займет больше места.
  • VideoStream.php помог мне сделать часть частичного запроса

Другие вопросы по теме