
Привет, Хабр!
В прошлой статье я описал аппаратную реализацию своего голосового ассистента на базе бюджетного одноплатника Orange Pi Zero 2W с 4Гб оперативной памяти. Эту же статью хочу посвятить программной реализации данного устройства. Если стало интересно, то добро пожаловать под кат.
Наверное, многие из вас представляют примерный принцип работы голосовых ассистентов, ниже на изображении представлена упрошенная логика работы данного устройства.

Как можно видеть, одной из ключевых функций является преобразование речи в текстовые данные, другими словами — транскрибация. Далее нам необходимо обработать полученные текстовые данные и определить в них наличие команды, выполнив сравнение со словарем команд. Иногда, данный словарь называют навыками. Ну и последний этап — это выполнение распознанной команды. Каждая команда в словаре имеет «ссылку» на определенную функцию, которая и будет выполняться при совпадении команды. Собственно, вот и примитивное описание работы нашего голосового ассистента. Конечно, наше устройство не должно безмолвно выполнять команды, а иметь какое-то взаимодействие с пользователем и желательно естественным для него способом, поэтому, для этих целей устройство имеет функцию синтеза речи.
❯ Подготовка
Прежде чем продолжить, необходимо выполнить действия из предыдущей статьи:
- Подключить звуковой модуль;
- Установить операционную систему;
- Сконфигурировать аудиоустройства.
В качестве языка программирования для нашего устройства выбран Python — необходимо установить и его. Рекомендуется использовать версию не ниже 3.11. Если пер��численные условия выполнены, то продолжим по порядку.
❯ Транскрибация
Так как в нашем устройстве мы придерживаемся концепции независимости от внешних сервисов, то и распознавание речи должно выполняться исключительно локально. Сейчас существует много библиотек на базе ML для распознавания речи в оффлайн режиме, но мои поиски и тесты привели к отличному решению под названием Vosk от компании Alphacephei. Учитывая ограниченные системные ресурсы нашего одноплатного компьютера, мы можем с уверенностью сказать что данная библиотека замечательно вписывается в наш проект.
Преимущества библиотеки:
- Поддерживает 20+ языков и диалектов — русский, английский, индийский английский, немецкий, французский, португальский, испанский, китайский, турецкий, вьетнамский, итальянский, голландский, валенсийский, арабский, греческий, персидский, филиппинский, украинский, казахский, шведский, японский, эсперанто, хинди, чешский, польский, узбекский, корейский, таджикский, гуджарати. В скором времени будут добавлены и другие.
- Работает без доступа к сети даже на мобильных устройствах — Raspberry Pi, Android, iOS.
- Устанавливается с помощью простой команды pip3 install vosk без дополнительных шагов.
- Модели для каждого языка занимают всего 50Мб, но есть и гораздо более точные большие модели для более точного распознавания.
- Сделана для потоковой обработки звука, что позволяет реализовать мгновенную реакцию на команды.
- Поддерживает несколько популярных языков программирования — Java, C#, Javascript и других.
- Позволяет быстро настраивать словарь распознавания для улучшения точности распознавания.
- Позволяет идентифицировать говорящего.
Итак, для установки пакета нам необходимо выполнить команду:
pip3 install vosk
Ниже представлен пример использования потокового распознавания речи с системного микрофона:
import sounddevice as sd import vosk import json import queue device_m = 2 # Индекс аудиоустройства (микрофон) model = vosk.Model("model_stt/vosk-model-small-ru-0.22") # Модель нейросети samplerate = 44100 # Частота дискретизации микрофона q = queue.Queue() # Потоковый контейнер def q_callback(indata, frames, time, status): q.put(bytes(indata)) def voce_listen(): with sd.RawInputStream(callback=q_callback, channels=1, samplerate=samplerate, device=device_m, dtype='int16'): rec = vosk.KaldiRecognizer(model, samplerate) sd.sleep(-20) while True: data = q.get() if rec.AcceptWaveform(data): res = json.loads(rec.Result())["text"] if res: print(f"Фраза целиком: {res}") else: res = json.loads(rec.PartialResult())["partial"] if res: print(f"Поток: {res}") if __name__ == "__main__": voce_listen()
Результат вывода данного примера:
Поток: один два Поток: один два три это Поток: один два три это тест Поток: один два три это тест Фраза целиком: один два три это тест
Как можно видеть из примера, для вывода текстовых данных используется два метода rec.Result() и rec.PartialResult(). Оба метода выводят текстовые данные в формате json, первый метод отвечает за вывод фразы целиком, а второй за вывод слов по мере распознавания. Для нашего проекта целесообразней использовать первый метод.
Кстати, объединив синтез и распознавание речи, можно делать забавные вещи, например ;):
❯ Поиск и выполнение команды
Выше мы определились что входные данные для распознавания команд будем получать с помощью метода rec.Result(), который выводит фразу целиком. Теперь осталось составить словарь команд с которым мы и будем работать. Ниже представлен пример данного словаря.
command_dic = { "help": ('список команд', 'команды', 'что ты умеешь', 'твои навыки', 'навыки'), "about": ('расскажи о себе', 'что ты такое', 'ты кто', 'о себе'), "ctime": ('время', 'текущее время', 'сейчас времени', 'который час', 'сколько время'), "ask_chat_gpt": ('спроси чат джи пи ти', 'спроси чат', 'спроси умную нейросеть', 'узнай у чат джи пи ти', 'давай поговорим с чат джи пи ти'), "lightON": ('включи свет', 'свет включи', 'зажги свет', 'свет включи'), "lightOFF": ('выключи свет', 'свет отключи', 'погаси свет', 'свет выключи'), "SmartSwichON": ('включи розетку', 'вкл розетку', 'розетку включить', 'розетку вкл'), "SmartSwichOFF": ('выключи розетку', 'выкл розетку', 'розетку отключи', 'розетку выкл'), "SmartPARAM": ('информация о розетке', 'данные розетки', 'информация сети питания', 'данные умной розетки'), "Weather": ('какая погода', 'какая погода на улице', 'информация о погоде', 'какая погода сейчас'), "Weather_temp": ('температура на улице', 'какая сейчас температура на улице', 'информация о темпер��туре на улице', 'уличная температура'), "temp_home": ('температура в доме', 'какая сейчас температура в доме', 'температура в комнате', 'комнатная температура'), "air_home": ('атмосфера в доме', 'качество воздуха в доме', 'воздух в комнате', 'загрязнение воздуха в комнате'), "LightShowOn": ('включи красоту', 'включи огоньки', 'включи праздник', 'включи яркие огоньки'), "LightShowOff": ('выключи красоту', 'выключи огоньки', 'выключи праздник', 'выключи яркие огоньки', 'отключи красоту', 'отключи огоньки', 'отключи праздник', 'отключи яркие огоньки' ), "HumOn": ('включи увлажнитель', 'запусти увлажнитель', 'начать увлажнение'), "HumOff": ('выключи увлажнитель', 'отключи увлажнитель', 'прекратить увлажнение'), "robovacuum_start": ('запускай максима', 'выпускай монстра', 'выпускай зверя','начни уборку', 'начинай уборку', 'запусти пылесос'), "robovacuum_stop": ('останови максима', 'загони обратно монстра', 'угомони монстра', 'прекрати уборку', 'останови пылесос', 'останови уборку', 'хватит уборки'), "volup": ('громче', 'добавь громкость', 'сделай громче', 'добавь звук'), "voldown": ('тише', 'убавь громкость', 'сделай тише', 'убавь звук'), "volset": ('установи уровень громкости', 'уровень громкости', 'громкость на'), "usd_curs": ('курс доллара', 'цена доллара', 'стоимость доллара'), "usd_euro": ('курс евро', 'цена евро', 'стоимость евро'), "marko": ('марко', 'марка', 'марке'), "btc_usd": ('курс биткойна', 'цена биткойна', 'стоимость биткойна', 'биткоин'), "youtube_counter": ('статистика ютуб', 'как дела с ютубом', 'сколько подписчиков на ютубе', 'что с подписчиками'), "tv_pause": ('поставь на паузу', 'поставь телевизор на паузу', 'телевизор пауза', 'пауза'), "tv_play": ('сними с паузы', 'сними телевизор с паузы', 'запусти воспроизведение', 'воспроизведение') }
Как можно видеть, наш словарь состоит из ключей и кортежей строк, в которых указаны варианты произношения команд. Как вы понимаете, стандартные методы сравнения строк здесь не сработают, точнее, могут сработать если вы заучите все команды наизусть ;) — но это не наш путь, поэтому мы будем использовать алгоритмы нечеткого сравнения. Для наших целей идеально подходит библиотека FuzzyWuzzy, которая в своем функционале использует метрику «Расстояние Левенштейна».
Для установки пакета воспользуемся следующей командой:
pip3 install fuzzywuzzy
И чтобы ускорить работу библиотеки в 4-10 раз (согласно официальной документации), установим дополнительный пакет:
pip3 install python-Levenshtein
Для нечеткого сравнения строк используется следующий метод:
fuzz.ratio('Строка один','Строка два')
Выводом данного метода является процент схожести строки. Для наглядности, запустим следующий пример функции с использованием нашего словаря команд:
def recognize_command(cmd: str): for c, v in command_dic.items(): for x in v: similarity = fuzz.ratio(cmd, x) print(f"Совпадение команды: {similarity}% | Ключ: {c}") if __name__ == "__main__": recognize_command(" время ")
После выполнения в терминале мы получим следующий вывод:
Совпадение команды: 15% | Ключ: help Совпадение команды: 31% | Ключ: about Совпадение команды: 60% | Ключ: ctime Совпадение команды: 26% | Ключ: ask_chat_gpt Совпадение команды: 33% | Ключ: lightON Совпадение команды: 32% | Ключ: lightOFF Совпадение команды: 33% | Ключ: SmartSwichON Совпадение команды: 32% | Ключ: SmartSwichOFF Совпадение команды: 22% | Ключ: SmartPARAM Совпадение команды: 15% | Ключ: Weather Совпадение команды: 23% | Ключ: Weather_temp Совпадение команды: 21% | Ключ: temp_home Совпадение команды: 17% | Ключ: air_home Совпадение команды: 30% | Ключ: LightShowOn Совпадение команды: 29% | Ключ: LightShowOff Совпадение команды: 25% | Ключ: HumOn Совпадение команды: 21% | Ключ: HumOff Совпадение команды: 18% | Ключ: robovacuum_start Совпадение команды: 20% | Ключ: robovacuum_stop Совпадение команды: 22% | Ключ: volup Совпадение команды: 24% | Ключ: voldown Совпадение команды: 32% | Ключ: volset Совпадение команды: 17% | Ключ: usd_curs Совпадение команды: 29% | Ключ: usd_euro Совпадение команды: 33% | Ключ: marko Совпадение команды: 0% | Ключ: btc_usd Совпадение команды: 16% | Ключ: youtube_counter Совпадение команды: 0% | Ключ: tv_pause Совпадение команды: 27% | Ключ: tv_play
где успешно определилась команда «ctime» с 60% схожести. И немного изменив функцию до следующего варианта:
def recognize_command(cmd: str): similarity_percent = 60 command = 'no_data' for c, v in command_dic.items(): for x in v: similarity = fuzz.ratio(cmd, x) print(f"Совпадение команды: {similarity}% | Ключ: {c}") if similarity >= similarity_percent: command = c return command
Мы уже можем отправлять ключ распознанной команды в обработчик команд. В качестве обработчика команд используется оператор match, ниже пример функции обработчика:
def command_processing(key: str): match key: case 'help': f_help() case 'about': f_about() case 'ctime': f_ctime() case 'lightON': f_lightON() case 'lightOFF': f_lightOFF() case _: print('Нет данных')
В данном обработчике выполняется код в операторе case, значение в котором соответствует проверяемому ключу. Ниже пример моей функции включения света:
def f_lightON(): try: contents = urllib.request.urlopen("http://192.168.1.56/status").read() response0 = json.loads(contents) if response0['channel1'] == 'Off' and response0['channel2'] == 'Off': text = "Включила свет" if response0['channel1'] == 'On' and response0['channel2'] == 'Off': text = "Первый светильник уже включен, включила второй!" if response0['channel1'] == 'Off' and response0['channel2'] == 'On': text = "Второй светильник уже включен, включила первый!" if response0['channel1'] == 'On' and response0['channel2'] == 'On': text = "Свет уже включен! Но я могу выключить, если попросите!" if response0['channel1'] == 'Off': response2 = requests.get('http://192.168.1.56/powerS') if response0['channel2'] == 'Off': response2 = requests.get('http://192.168.1.56/powerS2') tts.speak(text) except: tts.speak("Сожалею, но возникла ошибка, попробуйте позже!")
❯ Синтез речи
Как средство натурального взаимодействия между человеком и машиной, голосовой интерфейс заслуженно занимает высокую популярность, одной из составляющих данного взаимодействия является система синтеза речи. Современные системы синтеза речи, как правило, построены на базе алгоритмов машинного обучения, поэтому и в нашем проекте будем использовать подобные решения. В ходе поисков оптимального варианта для используемого в проекте одноплатного компьютера, я наткнулся на решение, которое отлично сочетало в себе скорость и качество синтеза речи — это бесплатные LM модели от компании Silero. Сама система синтеза речи построена на популярном фреймворке машинного обучения PyTorch. Вот пример реализации системы синтеза речи для данного проекта:
import os import torch import sounddevice as sd import time current_directory = os.getcwd() path = 'model_tts' isExist = os.path.exists(path) if not isExist: os.makedirs(path) local_file_ru = 'model_tts/4_ru_model.pt' sample_rate = 24000 # 8000, 24000, 48000 - частота дискретизации генерируемого аудиопотока speaker = 'kseniya' # aidar, baya, kseniya, xenia, random - модель голоса put_accent = True put_yo = False device = torch.device('cpu') # cpu или gpu torch.set_num_threads(8) # количество задействованных потоков CPU if not os.path.isfile(local_file_ru): torch.hub.download_url_to_file('https://models.silero.ai/models/tts/ru/v4_ru.pt', local_file_ru) model = torch.package.PackageImporter(local_file_ru).load_pickle("tts_models", "model") torch._C._jit_set_profiling_mode(False) torch.set_grad_enabled(False) model.to(device) sd.default.device = 4 # аудиоустройство для вывода def speak(text: str): audio = model.apply_tts(text=text + "..", speaker=speaker, sample_rate=sample_rate, put_accent=put_accent, put_yo=put_yo) sd.play(audio, sample_rate) time.sleep((len(audio) / (sample_rate)) + 0.5) sd.stop() del audio # освобождаем память if __name__ == "__main__": speak("Это тестовый синтез речи")
Для работы данного скрипта, необходимо установить следующие зависимости:
pip3 install numpy torch
Как можно видеть из примера, реализация в коде не сложная, что позволяет легко интегрировать данный метод синтеза речи в проект. Но есть один нюанс :) Пытливые умы уже, наверное, догадались о чем я. Дело в том, что при воспроизведении синтезированной речи наш алгоритм распознавания речи неизбежно будет задействован — это приведет к избыточной нагрузке на системные ресурсы нашего одноплатника, что крайне негативно скажется на работу системы в целом. Чтобы избежать подобного, воспользуемся небольшим трюком, изменим код функции системы распознавания речи следующим образом:
mic = 2 # адрес аудиоустройства микрофона on_mic = f'amixer -c {mic} set Mic cap' # команда отключения глушилки off_mic = f'amixer -c {mic} set Mic nocap'# команда на глушение микрофона def speak(text: str): audio = model.apply_tts(text=text + "..", speaker=speaker, sample_rate=sample_rate, put_accent=put_accent, put_yo=put_yo) os.system(off_mic) # глушим микрофон sd.play(audio, sample_rate) time.sleep((len(audio) / sample_rate) + 0.5) sd.stop() os.system(on_mic) # отключаем глушилку микрофона del audio # освобождаем память
Как можно видеть из комментариев к коду, мы реализовали «глушение» микрофона с помощью простой команды для командной строки ), это позволяет решить выше описанную проблему.
Еще одной неожиданной проблемой синтеза речи оказалось то, что модель «не понимает» числа, например если на вход подать строку «было 10 яблок», то на выходе мы получим синтез речи с фразой «было яблок». В дополнение к этой проблеме требовалось еще реализовать и склонение единиц измерения. Ниже представлен код, который решает эти проблемы:
import decimal units = ( u'ноль', (u'один', u'одна'), (u'два', u'две'), u'три', u'четыре', u'пять', u'шесть', u'семь', u'восемь', u'девять' ) teens = ( u'десять', u'одиннадцать', u'двенадцать', u'тринадцать', u'четырнадцать', u'пятнадцать', u'шестнадцать', u'семнадцать', u'восемнадцать', u'девятнадцать' ) tens = ( teens, u'двадцать', u'тридцать', u'сорок', u'пятьдесят', u'шестьдесят', u'семьдесят', u'восемьдесят', u'девяносто' ) hundreds = ( u'сто', u'двести', u'триста', u'четыреста', u'пятьсот', u'шестьсот', u'семьсот', u'восемьсот', u'девятьсот' ) orders = ( ((u'тысяча', u'тысячи', u'тысяч'), 'f'), ((u'миллион', u'миллиона', u'миллионов'), 'm'), ((u'миллиард', u'миллиарда', u'миллиардов'), 'm'), ) minus = u'минус' def thousand(rest, sex): prev = 0 plural = 2 name = [] use_teens = 10 <= rest % 100 <= 19 if not use_teens: data = ((units, 10), (tens, 100), (hundreds, 1000)) else: data = ((teens, 10), (hundreds, 1000)) for names, x in data: cur = int(((rest - prev) % x) * 10 / x) prev = rest % x if x == 10 and use_teens: plural = 2 name.append(teens[cur]) elif cur == 0: continue elif x == 10: name_ = names[cur] if isinstance(name_, tuple): name_ = name_[0 if sex == 'm' else 1] name.append(name_) if 2 <= cur <= 4: plural = 1 elif cur == 1: plural = 0 else: plural = 2 else: name.append(names[cur - 1]) return plural, name def num2text(num, main_units=((u'', u'', u''), 'm')): _orders = (main_units,) + orders if num == 0: return ' '.join((units[0], _orders[0][0][2])).strip() rest = abs(num) ord = 0 name = [] while rest > 0: plural, nme = thousand(rest % 1000, _orders[ord][1]) if nme or ord == 0: name.append(_orders[ord][0][plural]) name += nme rest = int(rest / 1000) ord += 1 if num < 0: name.append(minus) name.reverse() return ' '.join(name).strip() def decimal2text(value, places=2, int_units=(('', '', ''), 'm'), exp_units=(('', '', ''), 'm')): value = decimal.Decimal(value) q = decimal.Decimal(10) ** -places integral, exp = str(value.quantize(q)).split('.') return u'{} {}'.format( num2text(int(integral), int_units), num2text(int(exp), exp_units))
Пример использования данного решения в функции текущего времени:
def f_ctime(): now = datetime.datetime.now() text = "Сейч+ас " male_units = ((u'час', u'часа', u'часов'), 'm') text += digit_to_text.num2text(int(now.hour), male_units) + '.' male_units = ((u'минута', u'минуты', u'минут'), 'm') text += digit_to_text.num2text(int(now.minute), male_units) + '.' tts.speak(text)
По предыдущей статье, наверное, многие помнят, что на аудио плате присутствует сигнал управления усилителем (mute) и светодиод для индикации активности умной колонки. Для управления данными штуками скорректируем код следующим образом:
import os import torch import sounddevice as sd import time mic = 2 # адрес аудиоустройства микрофона on_mic = f'amixer -c {mic} set Mic cap' # команда отключения глушилки off_mic = f'amixer -c {mic} set Mic nocap'# команда на глушение микрофона os.system("gpio mode 18 out") # устанавливаем режим пина разрешения усилителя os.system("gpio mode 16 out") # устанавливаем режим пина светодиодного индикатора os.system("gpio write 18 0") # Отключаем усилитель чтобы не шумел current_directory = os.getcwd() path = 'model_tts' isExist = os.path.exists(path) if not isExist: os.makedirs(path) local_file_ru = 'model_tts/4_ru_model.pt' sample_rate = 24000 # 8000, 24000, 48000 - частота дискретизации генерируемого аудиопотока speaker = 'kseniya' # aidar, baya, kseniya, xenia, random - модель голоса put_accent = True put_yo = False device = torch.device('cpu') # cpu или gpu torch.set_num_threads(8) # количество задействованных потоков CPU if not os.path.isfile(local_file_ru): torch.hub.download_url_to_file('https://models.silero.ai/models/tts/ru/v4_ru.pt', local_file_ru) model = torch.package.PackageImporter(local_file_ru).load_pickle("tts_models", "model") torch._C._jit_set_profiling_mode(False) torch.set_grad_enabled(False) model.to(device) sd.default.device = 4 # аудиоустройство для вывода def speak(text: str): audio = model.apply_tts(text=text + "..", speaker=speaker, sample_rate=sample_rate, put_accent=put_accent, put_yo=put_yo) os.system(off_mic) # глушим микрофон os.system("gpio write 18 1") # разрешающий сигнал для усилителя os.system("gpio write 16 1") # зажикаем светодиод индикатора sd.play(audio, sample_rate) time.sleep((len(audio) / sample_rate) + 0.5) sd.stop() os.system(on_mic) # отключаем глушилку микрофона os.system("gpio write 18 0") # отключаем усилитель os.system("gpio write 16 0") # отключаем индикатор del audio # освобождаем память if __name__ == "__main__": speak("Это тестовый синтез речи")
как можно заметить, управление пинами осуществляется теме же методами, что я описывал в прошлой статье.
❯ Зови её по имени
Как вы могли догадаться, для работы с голосовым ассистентом необходимо использовать фразу для активации. В своей умной колонке для этих целей я решил использовать имя Альфа. На выбор данного имени очень повлиял сериал от Apple «Экстраполяции», где была корпорация «Альфа» с вездесущим одноименным голосовым ассистентом. Ниже показан пример активации:
Для начала нам нужно создать кортеж из вариаций произношения имени:
sys_alias= ('альфа', 'альф', 'альфа', 'альфу', 'альфи')
Далее давайте поговорим об алгоритме активации. По сути, алгоритм прост и его можно выразить следующим описанием: из потока текстовых данных определяем наличие имени Альфа -> При наличии имени активируем следующий этап: определение команды -> и далее, выполнение команды. Как только наш ассистент обнаружит своё имя, он издаст звуковой сигнал совместно со световой индикацией и будет ожидать последующей фразы с командой. Как это выглядит в коде:
функция распознавания имени
def name_recognize(name: str): words = name.split() stat = False for item in sys_alias: similarity = fuzz.ratio(item, words[0]) if similarity > 70: stat = True return stat
Данная функция также использует нечеткое сравнение строк и после совпадения имени, возвращает логическое значение True. Так как на вход функции могут поступать текстовые данные в виде фразы, то нам необходимо выполнить преобразование строки в кортеж слов с помощью метода name.split() и для сравнения использовать нулевой элемент созданного кортежа. Ниже пример кода обработки текстовых данных, для активации и направления текста фразы для определения команд:
def response(voice: str): if glob_var.read_bool_wake_up(): # этап второй, распознавание команды command_processing(recognize_command(voice)) # распознавание и выполнение команды glob_var.set_bool_wake_up(False) # после выполнения команды, перехохим в режим распознования имени glob_var.set_bool_wake_up(name_recognize(voice)) # проверяем наличие имени в потоке if glob_var.read_bool_wake_up(): # если имя обнаружено, воспроизводим звуковой сигнал tts.play_wakeup_sound('notification.wav')
Как можно заметить, для облегчения работы с глобальными данными, был создан скрипт с именем glob_var:
hears = False gpt_bool = False wake_up = False voice = '' volset = 0 def set_bool_mic(bools): global hears hears = bools print(hears) def read_bool_mic(): global hears return hears def set_bool_gpt(bools): global gpt_bool gpt_bool = bools print(gpt_bool) def read_bool_gpt(): global gpt_bool return gpt_bool def set_bool_wake_up(bools): global wake_up wake_up = bools print(wake_up) def read_bool_wake_up(): global wake_up return wake_up def set_volset(vol): global volset volset = vol print(vol) def read_volset(): global volset return volset def set_voice(voices): global voice voice = voices print(voices) def read_voice(): global voice return voice
И для воспроизведения звукового сигнала при обнаружении имени, используется следующая функция:
file_path_n = current_directory + '/sound/notification.wav' # Извлечение данных и частоты дискретизации из файла data_n, fs_n = sf.read(file_path_n, dtype='float32') file_path_logo = current_directory + '/sound/start_logo.wav' # Извлечение данных и частоты дискретизации из файла data_logo, fs_logo = sf.read(file_path_logo, dtype='float32') def play_wakeup_sound(sound: str): global data_logo, fs_logo global data_n, fs_n if sound.__eq__('notification.wav'): data = data_n fs = fs_n else: data = data_logo fs = fs_logo sd.play(data, fs) os.system("gpio write 18 1") os.system("gpio write 16 1") sd.wait() # Ждем окончания воспроизведения файла os.system("gpio write 18 0") sd.stop()
куда в качестве аргумента передается имя файла для воспроизведения. Данная функция также используется для воспроизведения приветственного аудио файла при запуске системы. Ниже на видео показан первоначальный запуск голосового ассистента.
Также в нашем проекте реализована функция регулировки громкости на программном уровне, но это уже другая история. Демонстрацию реализации вы можете видеть ниже:
❯ Итоги
В этой статье я попытался очень кратко и доходчиво описать основное «ядро» своей DIY умной колонки без фантиков и плюшек. Надеюсь, статья не утомила и будет вам полезна. Есть предложения, идеи, замечания? Добро пожаловать в комментарии. Благодарю за внимание и всем бобра!
Ссылки к статье
- Предыдущая статья (hardware)
- Vosk
- LM модель Silero
- GitHub с исходным кодом проекта + проект аудио модуля (KiCad) > 30к views

