Как декодировать двоичные аудиоданные? - PullRequest
3 голосов
/ 28 января 2020

Я все еще новичок в веб-разработке, и я создаю чат-бота, но я хочу сначала выполнить ответы с помощью googles-текста в речь, а затем воспроизвести звук на клиенте. Таким образом, клиент отправляет сообщение на сервер -> сервер создает ответ -> сервер отправляет сообщение в Google -> возвращает аудиоданные -> отправляет их клиенту -> клиент воспроизводит их. Я дошел до последнего шага, но теперь я не в себе.

Я занимался поиском в Google, и, похоже, есть много информации о воспроизведении аудио из двоичных данных, аудио контекстов и так далее, и я создал функцию, но она не работает. Вот что я сделал:

export const SendMessage: Client.Common.Footer.API.SendMessage = async message => {
    const baseRoute = process.env.REACT_APP_BASE_ROUTE;
    const port = process.env.REACT_APP_SERVER_PORT;
    const audioContext = new AudioContext();
    let audio: any;
    const url = baseRoute + ":" + port + "/ChatBot";
    console.log("%c Sending post request...", "background: #1fa67f; color: white", url, JSON.stringify(message));
    let responseJson = await fetch(url, {
        method: "POST",
        mode: "cors",
        headers: {
            Accept: "application/json",
            "Content-Type": "application/json"
        },
        body: JSON.stringify(message)
    });
    let response = await responseJson.json();
    await audioContext.decodeAudioData(
        new ArrayBuffer(response.data.audio.data),
        buffer => {
            audio = buffer;
        },
        error => console.log("===ERROR===\n", error)
    );
    const source = audioContext.createBufferSource();
    source.buffer = audio;
    source.connect(audioContext.destination);
    source.start(0);
    console.log("%c Post response:", "background: #1fa67f; color: white", url, response);
};

Эта функция отправляет сообщение на сервер и возвращает ответное сообщение и аудиоданные. У меня есть своего рода двоичные данные в моем response.data.audio.data, но я получаю сообщение об ошибке, говорящее, что аудиоданные не могут быть декодированы (ошибка в методе decodeAudioData вызывает). Я знаю, что данные действительны, потому что на моем сервере я использую следующий код, чтобы превратить их в mp3-файл, который прекрасно воспроизводится:

const writeFile = util.promisify(fs.writeFile);
await writeFile("output/TTS.mp3", response.audioContent, "binary");

Я почти не знаю, как здесь обрабатываются двоичные данные, и что может быть не так. Нужно ли указывать дополнительные параметры для правильного декодирования двоичных данных? Как я узнаю что? Я хотел бы понять, что на самом деле здесь происходит, а не просто скопировать вставить какое-либо решение.

РЕДАКТИРОВАТЬ:

Так что, похоже, буфер массива создается неправильно. Если я выполню этот код:

    console.log(response);
    const audioBuffer = new ArrayBuffer(response.data.audio.data);
    console.log("===audioBuffer===", audioBuffer);
    audio = await audioContext.decodeAudioData(audioBuffer);

Ответ будет выглядеть так:

{message: "Message successfully sent.", status: 1, data: {…}}
    message: "Message successfully sent."
    status: 1
    data:
        message: "Sorry, I didn't understand your question, try rephrasing."
        audio:
            type: "Buffer"
            data: Array(14304)
                [0 … 9999]
                [10000 … 14303]
                length: 14304
            __proto__: Array(0)
        __proto__: Object
    __proto__: Object
__proto__: Object

, но буфер записывается так:

===audioBuffer=== 
ArrayBuffer(0) {}
    [[Int8Array]]: Int8Array []
    [[Uint8Array]]: Uint8Array []
    [[Int16Array]]: Int16Array []
    [[Int32Array]]: Int32Array []
    byteLength: 0
__proto__: ArrayBuffer

Ясно JS не понимает формат в моем объекте ответа, но это то, что я получил из API Google text в речь. Может быть, я неправильно отправляю сообщение с моего сервера? Как я уже говорил, на моем сервере следующий код превращает этот массив в файл mp3:

    const writeFile = util.promisify(fs.writeFile);
    await writeFile("output/TTS.mp3", response.audioContent, "binary");
    return response.audioContent;

Где response.audioContent также отправляется клиенту следующим образом:


//in index.ts
...
const app = express();
app.use(bodyParser.json());
app.use(cors(corsOptions));

app.post("/TextToSpeech", TextToSpeechController);
...
//textToSpeech.ts
export const TextToSpeechController = async (req: Req<Server.API.TextToSpeech.RequestQuery>, res: Response) => {
    let response: Server.API.TextToSpeech.ResponseBody = {
        message: null,
        status: CONSTANTS.STATUS.ERROR,
        data: undefined
    };
    try {
        console.log("===req.body===", req.body);
        if (!req.body) throw new Error("No message recieved");
        const audio = await TextToSpeech({ message: req.body.message });
        response = {
            message: "Audio file successfully created!",
            status: CONSTANTS.STATUS.SUCCESS,
            data: audio
        };
        res.send(response);
    } catch (error) {
        response = {
            message: "Error converting text to speech: " + error.message,
            status: CONSTANTS.STATUS.ERROR,
            data: undefined
        };
        res.json(response);
    }
};
...

Я нахожу странным, что на моем сервере response.audioContent записывается как:

===response.audioContent=== <Buffer ff f3 44 c4 00 00 00 03 48 01 40 00 00 f0 
a3 0f fc 1a 00 11 e1 48 7f e0 e0 87 fc b8 88 40 1c 7f e0 4c 03 c1 d9 ef ff ec 
3e 4c 02 c7 88 7f ff f9 ff ff ... >

Но на клиенте это

audio:
            type: "Buffer"
            data: Array(14304)
                [0 … 9999]
                [10000 … 14303]
                length: 14304
            __proto__: Array(0)
        __proto__: Object

Я попытался передать response.data, response. data.audio и response.data.audio.data для нового ArrayBuffer (), но все они приводят к одному и тому же пустому буферу.

Ответы [ 2 ]

2 голосов
/ 08 февраля 2020

Пара вещей в вашем коде, вы не можете заполнить ArrayBuffer через эту функцию конструктора. Ваш звонок на decodeAudioData является асин c, и audio будет undefined. Я бы порекомендовал вам обновить вызов до decodeAudioData для более новой обещанной функции на основе.

РЕДАКТИРОВАТЬ: Вы, должно быть, делаете что-то странное с вашим вызовом Google Text to Speech и возвращенным результат для предыдущего примера, который я опубликовал, не работает, потому что, использую ли я mp3 или ответ от Google, они оба работают, как только передали правильную ссылку buffer.

Факт, который вы можете получить Он работает с файлом mp3, а не с преобразованием текста в речь. Возможно, вы не ссылаетесь на правильное свойство в результате, возвращаемом из вызова API Google. Ответом на вызов API является Array, поэтому убедитесь, что вы ссылаетесь на индекс 0 в массиве результатов (см. textToSpeech.js ниже).

Полное приложение описано ниже.

// textToSpeech.js
const textToSpeech = require('@google-cloud/text-to-speech');
const client = new textToSpeech.TextToSpeechClient();

module.exports = {
    say: async function(text) {
        const request = {
            input: { text },
            voice: { languageCode: 'en-US', ssmlGender: 'NEUTRAL' },
            audioConfig: { audioEncoding: 'MP3' },
          };
        const response = await client.synthesizeSpeech(request);
        return response[0].audioContent    
    }
}
// server.js
const express = require('express');
const path = require('path');
const app = express();
const textToSpeechService = require('./textToSpeech');

app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname + '/index.html'));
});

app.get('/speech', async (req, res) => {
    const buffer = await textToSpeechService.say('hello world');
    res.json({
        status: `y'all good :)`,
        data: buffer
    })
});

app.listen(3000);
// index.html
<!DOCTYPE html>
<html>
    <script>
        async function play() {
            const audioContext = new AudioContext();
            const request = await fetch('/speech');
            const response = await request.json();
            const arr = Uint8Array.from(response.data.data)
            const audio = await audioContext.decodeAudioData(arr.buffer);
            const source = audioContext.createBufferSource();
            source.buffer = audio;
            source.connect(audioContext.destination);
            source.start(0);
        }
    </script>
    <body>
        <h1>Hello Audio</h1>
        <button onclick="play()">play</button>
    </body>
</html>
1 голос
/ 11 февраля 2020
const audioBuffer = new ArrayBuffer(response.data.audio.data);
console.log("===audioBuffer===", audioBuffer);

возможно попробуйте

const audioBuffer = Buffer.from(response.data.audio);
console.log("===audioBuffer===", audioBuffer);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...