30 минут, чтобы добиться распознавания голоса в небольшой программе

внешний интерфейс JavaScript Egg.js FFmpeg

предисловие

Для того, чтобы участвовать в шоу-активности, я изучил волну, как реализовать реализацию голосового ввода и распознавания в сочетании с апплетом и iFLYTEK. Документ разработки iFLYTEK дает только демонстрацию Python и не дает SDK node.js, но это не большая проблема. В этой статье шаг за шагом будет представлен процесс подключения соответствующего кода апплета к API iFLYTEK и завершение функции распознавания речи апплета за полчаса! Больше не надо! Конечно, предпосылка заключается в том, что лучше иметь небольшие знания о небольших программах, node.js или даже аудио.

Сначала архитектура

Структура относительно проста, каждый может посмотреть на картинке ниже. Помимо апплета необходимо предоставить 3 сервиса, закачка файлов, кодирование аудио и сервис стыковки с iFLYTEK.

node.js подключен к API iFLYTEK. Некоторые студенты предоставили SDK на npm. Заинтересованные студенты могут найти и узнать. Автор напрямую вызывает интерфейс API iFLYTEK.

架构图

Засучите рукава и работайте усердно

1. Создайте небольшую программу

Документация по апплету фабрики гуся очень подробная, и автор не будет подробно описывать здесь этапы создания апплета. Нуждающиеся учащиеся могут осмотреть гусиную фабрикуДокументация по разработке мини-программы.

1.1 Связанный код

Мы извлекаем код частей голосового ввода и голосовой загрузки в апплет.

// 根据wx提供的api创建录音管理对象
const recorderManager = wx.getRecorderManager();

// 监听语音识别结束后的行为
recorderManager.onStop(recorderResponse => {
    // tempFilePath 是录制的音频文件
    const { tempFilePath } = recorderResponse;

    // 上传音频文件,完成语音识别翻译
    wx.uploadFile({
        url: 'http://127.0.0.1:7001/voice', // 该服务在后面搭建。另外,小程序发布时要求后台服务提供https服务!这里的地址仅为开发环境配置。
        filePath: tempFilePath,
        name: 'file',
        complete: res => {
            console.log(res); // 我们期待res,就是翻译后的内容
        }
    });
});

// 开始录音,触发条件可以是按钮或其他,由你自己决定
recorderManager.start({
    duration: 5000 // 最长录制时间
    // 其他参数可以默认,更多参数可以查看https://developers.weixin.qq.com/miniprogram/dev/api/media/recorder/RecorderManager.start.html
});

2. Создайте файловый сервер

Все должны помнить URL-адрес, упомянутый в коде на шаге 1.

http://127.0.0.1:7001/voice

Сам апплет не предоставляет функции распознавания речи, поэтому здесь нам нужно использовать возможности «бэкэнд» сервиса для завершения нашей функции распознавания речи и перевода.

2.1 Инициализация службы egg.js

Мы используем egg.js cli для быстрой инициализации проекта.Конечно, вы также можете использовать фреймворки express, koa, kraken и др. Выбор фреймворков здесь не в центре внимания, поэтому мы не будем подробно останавливаться. Учащиеся, не знакомые с egg.js, могут просмотреть его.Официальный сайт egg.js.

npm i egg-init -g
egg-init voice-server --type=simple
cd voice-server
npm i

После завершения установки выполните следующий код

npm run dev

Затем зайдите в браузер http://127.0.0.1:7001 и вы должны увидеть страницу Hi, egg. На этом инициализация нашего сервиса завершена.

2.2 Интерфейс загрузки файлов

а) Измените конфигурацию загрузки файла egg.js

Откройте config/config.default.js и добавьте следующие две конфигурации.

module.exports = appInfo => {
    ...
    config.multipart = {
        fileSize: '2gb', // 限制文件大小
        whitelist: [ '.aac', '.m4a', '.mp3' ], // 支持上传的文件后缀名
    };

    config.security = {
        csrf: {
            enable: false // 关闭csrf
        }
    };
    ...
}

б) Добавить VoiceController

Откройте папку app/controller и создайте новый файл voice.js. Напишите VoiceController для наследования от контроллера egg.js. Конкретный код выглядит следующим образом:

const Controller = require('egg').Controller;
const fs = require('fs');
const path = require('path');
const pump = require('mz-modules/pump');
const uuidv1 = require('uuid/v1'); // 依赖于uuid库,用于生成唯一文件名,使用npm i uuid安装即可

// 音频文件上传后存储的路径
const targetPath = path.resolve(__dirname, '..', '..', 'uploads');

class VoiceController extends Controller {
    constructor(params) {
        super(params);
        if (!fs.existsSync(targetPath)) {
            fs.mkdirSync(targetPath);
        }
    }

    async translate() {
        const parts = this.ctx.multipart({ autoFields: true });
        let stream;
        const voicePath = path.join(targetPath, uuidv1());
        while (!isEmpty((stream = await parts()))) {
            await pump(stream, fs.createWriteStream(voicePath));
        }
        // 到这里就完成了文件上传。如果你不需要文件落地,也可以在后续的操作中,直接使用stream操作文件流

        ...
        // 音频编码
        // 科大讯飞语音识别
        ...
    }
}

в) Последний шаг, добавьте правила маршрутизации

После написания контроллера добавляем новый маршрут в router.js по правилам egg.js.

module.exports = app => {
    const { router, controller } = app;
    router.get('/', controller.home.index);
    router.get('/voice', controller.voice.translate);
};

Хорошо, пока вы можете протестировать весь процесс записи из апплета и загрузить его на фоновый файловый сервер после завершения записи. Если все в порядке, поздравляем, вы сделали 80%!

3 Услуги аудиокодирования

Выше мы упомянули «дополнительные параметры» в методе recorderManager.start записи апплета. Одним из параметров является формат, который поддерживает как aac, так и mp3 (по умолчанию aac). Затем мы ознакомились с документацией iFLYTEK по API, кодировка звука поддерживает «несжатый формат PCM или WAV».

Какой аас, пкм, вав? эммм.. ладно, мы только передний конец.Поскольку формат не равен, нам нужно только завершить преобразование aac -> pcm, и мне сразу приходит на ум ffmpeg. После некоторого поиска команда выглядит так:

ffmpeg -i uploads/a3f588d0-edf8-11e8-b6f5-2929aef1b7f8.aac -f s16le -ar 8000 -ac 2 -y decoded.pcm

# -i 后面带的是源文件
# -f s16le 指的是编码格式
# -ar 8000 编码码率
# -ac 2 通道

Затем мы используем node.js для реализации вышеуказанной команды.

3.1 Представляем связанные пакеты зависимостей

npm i ffmpeg-static
npm i fluent-ffmpeg

3.2 Создание службы кодирования

В папке app/service создайте файл ffmpeg.js. Создайте новый FFmpegService, который наследуется от службы egg.js.

const { Service } = require('egg');
const ffmpeg = require('fluent-ffmpeg');
const ffmpegStatic = require('ffmpeg-static');
const path = require('path');
const fs = require('fs');

ffmpeg.setFfmpegPath(ffmpegStatic.path);

class FFmpegService extends Service {
    async aac2pcm(voicePath) {
        const command = ffmpeg(voicePath);

        // 方便测试,我们将转码后文件落地到磁盘
        const targetDir = path.join(path.dirname(voicePath), 'pcm');
        if (!fs.existsSync(targetDir)) {
            fs.mkdirSync(targetDir);
        }

        const target = path.join(targetDir, path.basename(voicePath)) + '.pcm';
        return new Promise((resolve, reject) => {
            command
                .audioCodec('pcm_s16le')
                .audioChannels(2)
                .audioBitrate(8000)
                .output(target)
                .on('error', error => {
                    reject(error);
                })
                .on('end', () => {
                    resolve(target);
                })
                .run();
        });
    }
}

module.exports = FFmpegService;

3.3 Вызовите ffmpegService, чтобы получить файл PCM

Вернувшись в файл app/controller/voice.js, после завершения загрузки файла мы вызываем метод aac2pcm, предоставленный ffmpegService, чтобы получить путь к файлу PCM.

// app/controller/voice.js
...
async translate() {
    ...
    ...
    const pcmPath = await this.ctx.service.ffmpeg.aac2pcm(voicePath);
    ...
}
...

4. СтыковкаiFLYTEK API

Во-первых, необходимоiFLYTEK Открытая платформаЗарегистрируйтесь и добавьте приложения, а также активируйте службу голосовой диктовки для приложений.

Напишем еще один сервис, создадим файл xfyun.js в папке app/service и реализуем сервис, который XFYunService наследует от egg.js.

4.1 Введение связанных зависимостей

npm i axios // 网络请求库
npm i md5 // 科大讯飞接口中需要md5计算
npm i form-urlencoded // 接口中需要对部分内容进行urlencoded

4.2 Реализация XFYunService

const { Service } = require('egg');
const fs = require('fs');
const formUrlencoded = require('form-urlencoded').default;
const axios = require('axios');
const md5 = require('md5');
const API_KEY = 'xxxx'; // 在科大讯飞控制台上可以查到服务的APIKey
const API_ID = 'xxxxx'; // 同样可以在控制台查到

class XFYunService extends Service {
    async voiceTranslate(voicePath) {
        // 继上文,暴力的读取文件
        let data = fs.readFileSync(voicePath);
        // 将内容进行base64编码
        data = new Buffer(data).toString('base64');
        // 进行url encode
        data = formUrlencoded({ audio: data });
        const params = {
            engine_type: 'sms16k',
            aue: 'raw'
        };
        const x_CurTime = Math.floor(new Date().getTime() / 1000) + '',
            x_Param = new Buffer(JSON.stringify(params)).toString('base64');
        return axios({
            url: 'http://api.xfyun.cn/v1/service/v1/iat',
            method: 'POST',
            data,
            headers: {
                'X-Appid': API_ID,
                'X-CurTime': x_CurTime,
                'X-Param': x_Param,
                'X-CheckSum': md5(API_KEY + x_CurTime + x_Param)
            }
        }).then(res => {
            // 查询成功后,返回response的data
            return res.data || {};
        });
    }
}

module.exports = XFYunService;

4.3 Вызов XFYunService для завершения распознавания речи

Вернувшись снова к файлу app/controller/voice.js, после завершения транскодирования ffmpeg мы вызываем метод voiceTranslate, предоставляемый XFYunService, для завершения распознавания речи.

// app/controller/voice.js
...
async translate() {
    ...
    ...
    const result = await this.ctx.service.xfyun.voiceTranslate(pcmPath);
    this.ctx.body = result;
    if (+result.code !== 0) {
      this.ctx.status = 500;
    }
}
...

На данный момент мы завершили написание кода распознавания речи. Основной процесс на самом деле очень прост: введите голосовой файл через апплет, загрузите его на файловый сервер, получите файл pcm через ffmpeg и, наконец, перенаправьте его на API-интерфейс iFLYTEK для идентификации.

Прикрепите код проекта:speech-recognizer

Выше любые ошибки, пожалуйста, поправьте меня!

@Author: _Jay