Задача распознавания голосовых сообщений в Telegram уже давно не новая. На эту тему написано много статей, разработано немало Telegram-ботов. С некоторыми решениями я ознакомился во время работы над функцией распознавания голосовых напоминаний для бота @RemindMegaBot и заметил, что в этих решениях используется не всегда оправданный подход:

Для распознавания речи аудиофайл загружается на диск.

Возникает справедливый вопрос — неужели нельзя обойтись без записи файла на диск? Ведь это освободит операционную систему от лишних операций и сократит время обработки данных!

Почему же разработчики используют именно такой подход?

Дело в том, что голосовые сообщения в Telegram записываются в ogg-формате и кодируются opus-кодеком. Наиболее популярные сервисы распознавания голоса не поддерживают этот формат (или кодек), поэтому приходится его преобразовывать в .wav, .mp3 или даже в тот же .ogg, но использовать уже vorbis-кодек. Для этого авторы решений рекомендуют использовать ffmpeg, который, в свою очередь, требует сохранения аудио-файлов на диск.

Но использовать ffmpeg необязательно. Есть альтернативные решения, которые позволяют декодировать opus-данные. Ниже я приведу одно из таких решений, реализованное на языке Go.

В нашем примере будем подключаться к сервису распознавания речи wit.ai. Сервис wit.ai поддерживает формат "audio/raw". Для декодирования голосового сообщения Telegram будем использовать библиотеку opus. Для работы с API wit.ai будем использовать официальную библиотеку wit-go.

Общий алгоритм функции распознавания речи выглядит прозрачно:

func Recognize(fileDirectURL string) (string, error) {
    // 1. Получаем содержимое аудиофайла по ссылке
    fileBody, err := getFileBody(fileDirectURL)
    if err != nil {
        return "", err
    }
    // 2. Преобразовываем данные в формат audio/raw
    audioRawBuffer, err := getAudioRawBuffer(fileBody)
    if err != nil {
        return "", err
    }
    // 3. Распознаем голосовое сообщение		
    client := witai.NewClient("YOUR_WIT_AI_TOKEN")
    msg, err := client.Speech(&witai.MessageRequest{
        Speech: &witai.Speech{
            File:        audioRawBuffer,
            ContentType: "audio/raw;encoding=signed-integer;bits=16;rate=48000;endian=little",
        },
    })
    if err != nil {
        return "", err
    }

    return msg.Text, nil
}

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

func getFileBody(fileDirectURL string) ([]byte, error) {
    resp, err := http.Get(fileDirectURL)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    fileBody, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    return fileBody, nil
}

Opus-данные декодируем по инструкции, выложенной разработчиками библиотеки.

func getAudioRawBuffer(fileBody []byte) (*bytes.Buffer, error) {
    channels := 1
    s, err := opus.NewStream(bytes.NewReader(fileBody))
    if err != nil {
        return nil, err
    }
    defer s.Close()

    audioRawBuffer := new(bytes.Buffer)
    pcmbuf := make([]int16, 16384)
    for {
        n, err := s.Read(pcmbuf)
        if err == io.EOF {
            break
        } else if err != nil {
            return nil, err
        }
        pcm := pcmbuf[:n*channels]
        err = binary.Write(audioRawBuffer, binary.LittleEndian, pcm)
        if err != nil {
            return nil, err
        }
    }
    return audioRawBuffer, nil
}

После этого содержимое буфера пересылаем в wit.ai и получаем распознанный текст.

На этом пока всё. Спасибо за внимание!

Комментарии (3)


  1. Germanjon
    05.11.2021 08:56
    +1

    Кажется, в каком-то ВУЗе начались промежуточные контрольные работы. Для получения пятёрки нужно опубликовать статью на Хабре...


    1. Story-teller
      05.11.2021 09:56

      Нам весной первокурсники КФУ делали бота, который наоборот озвучивает сообщения.


      1. Klvld894
        09.11.2021 01:42

        Ну хоть образование пытается не стоять на месте