Организация аудио стрима из браузера

165
05 января 2019, 12:10

Необходимо добавить в веб-приложение систему односторонних голосовых сообщений - супервайзер нажимает на копку и что-то сообщает удалённому работнику. Сложность состоит в формировании аудио-контента нужного формата. Разобрался с как записывать с MediaRecorder API, но результат использования не совсем соответствует задаче - при просмотре информации о полученных файлах в MPC-HC, оказалось, что там частота аж 48 KHz, когда надо лишь 8 для голоса, чтобы трафика меньше ело, плюс Chrome и Firefox используют разные медиаконтейнеры для сохранения дянных - webm у "хрома" и ogg у "лисы", кодек opus.

Нужно просто снимать сырые WAV семплы по 100 мс с частотой 8 KHz, сжимать их Vorbis-ом, и не пакуя ни в какой контейнер просто отдавать по сети.

Как это сделать на JavaScript (и HTML5)?

Текущий код с MediaRecorder:

Файл audiostreamer.js

var mediaRecorder;
var recording = false;
var recorder = navigator.mediaDevices.getUserMedia({audio:true});
var interval;
function Start(){
    recorder.then(stream => {
        mediaRecorder = new MediaRecorder(stream);
        recording = true;
        mediaRecorder.start();
        console.log(mediaRecorder.mimeType);
        const audioChunks = [];
        mediaRecorder.ondataavailable = (event) => {
            console.log(audioChunks.length);
            var request = new XMLHttpRequest();
            request.open("POST", "test_recorder.php");
            request.responseType = "arraybuffer";
            request.send(event.data);
            audioChunks.push(event.data);
        };
        mediaRecorder.onstop = () => {
            const audioBlob = new Blob(audioChunks);
            const audioUrl = URL.createObjectURL(audioBlob);
            const audio = new Audio(audioUrl);
            audio.play();
        };
        interval = setInterval(function(){mediaRecorder.requestData();},100);
    });
}
function Stop(){
    if( recording ){
        recording = false;
        clearInterval(interval);
        mediaRecorder.stop();
    }
}

Приём сообщений (для теста) test_recorder.php

<?php
if( filter_input(INPUT_SERVER, "REQUEST_METHOD") === "POST" ){
    $agent = filter_input(INPUT_SERVER, "HTTP_USER_AGENT");
    if( strpos($agent, "Firefox") !== FALSE )
        file_put_contents("test.ogg", file_get_contents("php://input"), FILE_APPEND);
    else if(strpos($agent, "Chrom") !== FALSE )
        file_put_contents("test.webm", file_get_contents("php://input"), FILE_APPEND);
    else exit("Unsupported");
}

Тестовая страничка stream.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <title>stream</title>
        <script src="audiostreamer.js"></script>
    </head>
    <body>
        <input type="button" value="Hold to Rec" onmousedown="Start()" onmouseup="Stop()"><br>
    </body>
</html>
Answer 1

Нашёл как получить сырой PCM для дальнейших операций:

// Сразу получаем разрешение на микрофон, чтобы потом без запросов работать с контролами
var Media = navigator.mediaDevices.getUserMedia({ audio: true, video: false });
// триггер для старта/остановки записи
var rec = false;
var handleSuccess = function(stream){
    var context = new AudioContext();
    var source = context.createMediaStreamSource(stream);
    var processor = context.createScriptProcessor(1024, 1, 1);
    // подключаем аудиопроцессор - тут и начинается снятие звука с устройства
    source.connect(processor);
    rec = true;
    processor.onaudioprocess = function(e){
        if( rec ){
            // Тут обрабатываем e.inputBuffer.getChannelData(0) (если моно)
            // e.inputBuffer.getChannelData(0) это и есть 1-й аудио канал с сырой ИКМ
            // Прослушать PCM можно например в Audacity импортировав RawData
            var request = new XMLHttpRequest();
            request.open("POST", "test_recorder.php");
            request.responseType = "arraybuffer";
            request.send(e.inputBuffer.getChannelData(0));
        }
        else{
            // отключаем аудиопроцессор
            source.disconnect(processor);
        }
    };
};
function Start(){Media.then(handleSuccess);}
function Stop(){rec = false;}

Ну а алгоритм кодирования в vorbis на js думаю можно найти в гугле. ;)

READ ALSO
как в webpack при билде сделать читаемый js?

как в webpack при билде сделать читаемый js?

Можно ли избавиться от webpack модулей в mainjs? Или есть ещё какие-то методы , чтобы сделать мой код на выходе читаемым? Добавил свой пример и то что...

178
Vue.js использование refs

Vue.js использование refs

Всем привет ,как будет выглядеть если явную работу с DOM тут заменить на refs? У меня что-то не выходит так заменить

146
&ldquo;поделиться&rdquo; от Яндекса data-url не работает

“поделиться” от Яндекса data-url не работает

В data-url ссылка на конкретный объект, однако "поделиться" срабатывает на всю страницу

188