
Задача распознавания голосовых сообщений в 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 и получаем распознанный текст.
На этом пока всё. Спасибо за внимание!
