Аудио и видео в мессенджере Tox
Пока одни люди думают о регулировании мессенджеров, другие люди разрабатывают распределенные мессенджеры. В предыдущей публикации рассматривалось использование API ядра мессенджера Tox на примере создания простого echo-бота. Разработка Tox не стоит на месте и 3-го ноября ядро Tox обогатилось новой подсистемой аудио и видео вызовов — ToxAV о которой я и хотел бы рассказать в данной публикации.
ToxAV основывается на кодеках Opus для аудио и VP8 для видео и их реализациях в библиотеках libopus и libvpx соответственно.
В качестве входного и выходного формата для аудио, API ToxAV использует формат PCM с 16-и битным отсчетом, поддержкой 1-го или 2-х каналов и частотой дискретизации 8, 12, 16, 24 и 48KHz. Длительность одного кадра данных должна составлять 2.5, 5, 10, 20, 40 или 60 ms (требование кодека opus).
В качестве входного и выходного формата для видео, API ToxAV использует кадры в формате YUV420 (он же IYUV и I420), которые относительно легко можно преобразовывать в более привычное пространство цветов RGB.
ToxAV не предусматривает каких-либо методов для захвата данных с аудио и видео устройств или их воспроизведения — все это оставлено на усмотрение разработчика приложения. Так, например, в клиенте µTox используется OpenAL для работы с аудио и v4l (video4Linux) для работы с видео устройствами. В клиенте qTox для работы с видео используется FFmpeg. В python (о котором косвенно пойдет речь ниже) мы можем использовать PyAudio для аудио и OpenCV для видео.
Общий цикл работы с ToxAV можно представить последовательно в виде:
- Инициализация ядра ToxCore (подробно описана в предыдущей публикации).
- Инициализация подсистемы ToxAV (toxav_new).
- Установка callback функций для обработки событий (toxav_callback_*) — обработчики событий вызываются из основного цикла (4) и в них обычно сосредоточена основная логика работы приложения.
- Основной рабочий цикл (toxav_iterate) и обработка событий.
- Пауза на время toxav_iteration_interval и возврат к предыдущему шагу.
Т.к. основным способом получения знаний о работе Tox API является чтение исходного кода (написанного на си), для упрощения дальнейшего изложения я воспользуюсь оберткой для языка python (pytoxcore на github). Для тех, кто не желает заниматься самостоятельной сборкой библиотеки из исходников, там же есть ссылки на готовые бинарные пакеты для распространенных дистрибутивов.
При использовании python-обертки получить справку по библиотеке можно следующим способом:
$ python
>>> from pytoxcore import ToxAV
>>> help(ToxAV)
class ToxAV(object)
| ToxAV object
...
| toxav_answer(...)
| toxav_answer(friend_number, audio_bit_rate, video_bit_rate)
| Accept an incoming call.
| If answering fails for any reason, the call will still be pending and it is possible to try and answer it later. Audio and video receiving are both enabled by default.
|
| toxav_audio_receive_frame_cb(...)
| toxav_audio_receive_frame_cb(friend_number, pcm, sample_count, channels, sampling_rate)
| This event is triggered when a audio data received.
...
Ниже остановимся на каждом шаге работы с API чуть более подробно.
Для инициализации подсистемы ToxAV в качестве параметра вызова toxav_new используется экземпляр инициализированного ранее ядра ToxCore. Для одного экземпляра ToxCore может быть создан только один экземпляр ToxAV. В python обертке вызов toxav_new скрыт внутри конструктора и инициализация выглядит как:
from pytoxcore import ToxCore, ToxAV
class EchoBot(ToxCore):
...
class EchoAVBot(ToxAV):
def __init__(self, core):
super(EchoAVBot, self).__init__(core)
...
bot = EchoBot(options)
botav = EchoAVBot(bot)
Для уничтожения экземпляра ToxAV используется вызов toxav_kill или уничтожение экземпляра класса в python обертке, где вызов toxav_kill скрыт в деструкторе.
В python-обертке подключение к поддерживаемым callback функциям производится автоматически. Сами же обработчики могут являться методами наследника ToxAV и имеют суффикс *_cb. Во всех обработчиках одним из параметров является friend_number — целочисленный идентификатор друга из аналогичных методов ToxCore.
toxav_call_cb (friend_number, audio_enabled, video_enabled) — входящий звонок. В качестве аргументов дополнительно передаются флаги поддержки аудио и видео со стороны контакта. В дальнейшем контакт с отключенным аудио или видео потоками может их включить, что вызовет событие изменения состояния звонка. Звонок может быть принят вызовом toxav_answer, или отклонен вызовом toxav_call_control.
toxav_call_state_cb (friend_number, state) — изменение состояния звонка. В качестве аргумента передается состояние, которое является битовой маской из констант:
- TOXAV_FRIEND_CALL_STATE_ERROR — таймаут передачи данных контакту. Данное состояние является последним состоянием для звонка (к этому моменту звонок завершен) и не комбинируется с другими состояниями.
- TOXAV_FRIEND_CALL_STATE_FINISHED — нормальное завершение звонка. Данное состояние является последним состоянием для звонка (к этому моменту звонок завершен) и не комбинируется с другими состояниями.
- TOXAV_FRIEND_CALL_STATE_SENDING_A — контакт начал передачу аудио потока.
- TOXAV_FRIEND_CALL_STATE_SENDING_V — контакт начал передачу видео потока.
- TOXAV_FRIEND_CALL_STATE_ACCEPTING_A — контакт начал прием аудио потока.
- TOXAV_FRIEND_CALL_STATE_ACCEPTING_V — контакт начал прием видео потока
toxav_bit_rate_status_cb (friend_number, audio_bit_rate, video_bit_rate) — событие перегрузки сети, когда ядро не успевает отправлять данные с требуемым битрейтом. В качестве параметров ядро предлагает новые битрейты для аудио и видео потока. При чем сначала ядро пытается уменьшить битрейт видео потока, как занимающего основную полосу и только после отключения видео потока производится попытка уменьшения битрейта для аудио. Приложение может игнорировать данные рекомендации или использовать вызов toxav_bit_rate_set (friend_number, audio_bit_rate, video_bit_rate) для установки новых значений битрейтов. Битрейты задаются в Kb/s (килобиты в секунду), значение 0 сигнализирует об отключении соответствующего потока данных, значение -1 оставляет предыдущий установленный битрейт без изменений.
Для аудио кодека opus минимальный битрейт составляет 6, а значения выше 16–32 на мой слух уже не различимы для звука с веб-камеры.
Для видео кодека V8 я не смог найти рекомендуемых значений полосы пропускания в зависимости от размера кадра и FPS. В общем же случае для различных разрешений видео рекомендуются следующие базовые битрейты:
размер кадра, px | битрейт, Kb/s |
---|---|
320×240 | 400 |
480×270 | 700 |
1024×576 | 1500 |
1280×720 | 2500 |
1920×1080 | 4000 |
toxav_audio_receive_frame_cb (friend_number, pcm, sample_count, channels, sampling_rate) — получение кадра аудио данных. В качестве параметров передается буфер данных, количество сэмплов, количество каналов и частота дискретизации (Hz). Для стерео звука 16-и битные отсчеты идут последовательно друг за другом для левого и правого канала. Размер буфера в байтах определяется как sample_count * channels * 2 byte, а длительность буфера в миллисекундах как sampling_rate / sample_count.
Поскольку PCM — это формат обычной импульсно-кодовой модуляции, то данные буфера можно передавать практически без преобразований в любой ЦАП или сохранять в файл WAV добавив соответствующий заголовок с описанием формата, однако следует быть готовым к тому, что в процессе разговора любые параметры аудио потока могут быть изменены вызывающей стороной.
toxav_video_receive_frame_cb (friend_number, width, height, y, u, v, ystride, ustride, vstride) — получение кадра видео данных в формате YUV420. Это оригинальный callback из ToxAV. Пример реализации преобразования из YUV420 в BGR можно найти, например, в исходном коде µTox (yuv420tobgr).
toxav_video_receive_frame_cb (friend_number, width, height, rgb) — получение кадра в формате RGB или BGR — это дополнительный callback обертки python, который отсутствует в ToxAV. Данный вариант используется для ускорения необходимых преобразований внутри библиотеки вместо их преобразований в pyhon (по факту используется yuv420tobgr из кода µTox). Формат RGB или BGR задается вызовом toxav_video_frame_format_set с параметром:
- TOXAV_VIDEO_FRAME_FORMAT_BGR — формат BGR (используется в OpenCV).
- TOXAV_VIDEO_FRAME_FORMAT_RGB — формат RGB.
- TOXAV_VIDEO_FRAME_FORMAT_YUV420 — оригинальный callback из ToxAV с форматом YUV420.
Здесь и далее под RGB / BGR понимается 24-х битный формат без альфа-канала, где каждой из составляющих красного, зеленого и синего цвета отведено по 8 бит. Иногда этот формат именуется как RGB24 / BGR24 в зависимости от порядка следования цветовых составляющих красного и синего.
Так же как для аудио потока, формат видео потока может быть в любой момент изменен вызывающей стороной (например, изменится размер кадра).
Для обработки событий ToxAV используется периодический вызов метода toxav_iterate (по аналогии с вызовом метода ядра tox_iterate) с рекомендуемым интервалом между вызовами равному значению, которое вернет вызов toxav_iteration_interval (по аналогии с вызовом ядра tox_iteration_interval).
Рабочий цикл для ToxAV рекомендуется обслуживать в отдельном потоке, т.к. в отсутствии аудио/видео вызовов значение toxav_iteration_interval будет составлять 200 ms и поток будет спать большую часть времени:
import threading
class EchoAVBot(ToxAV):
def __init__(self, core):
...
self.running = True
self.iterate_thread = threading.Thread(target = self.iterate_cb)
self.iterate_thread.start()
def iterate_cb(self):
while self.running:
self.toxav_iterate()
interval = self.toxav_iteration_interval()
time.sleep(float(interval) / 1000.0)
def stop(self):
self.running = False
self.iterate_thread.join()
self.toxav_kill();
Поскольку для python у нас существует GIL (Global Interpreter Lock), то нам не следует заботиться о необходимости синхронизации между потоками, однако в других языках могут возникать «неожиданные» сюрпризы, когда toxav_*_cb события могут быть вызваны из потока обслуживающего tox_iterate вместо потока обслуживающего toxav_iterate. Таким событием, например, является событие завершения звонка toxav_call_state_cb — используйте соответствующие примитивы синхронизации с учетом возможности deadlock внутри ядра Tox.
Для создания исходящего звонка используется метод toxav_call (friend_number, audio_bit_rate, video_bit_rate), где в качестве аргументов указывается идентификатор контакта из контакт-листа и исходящий аудио и видео битрейт (Kb/s). Значение битрейта 0 блокирует отправку соответствующего потока, который можно будет включить позже вызовом toxav_bit_rate_set.
У ToxAV отсутствует привычный нам контроль посылки вызова — не принятый исходящий вызов будет длиться пока вызов не будет отменен по какому-либо внутреннему таймауту самого приложения (приложение «положит трубку»), или контакт не уйдет в offline («обрыв сети» и событие toxav_call_state_cb с параметром TOXAV_FRIEND_CALL_STATE_FINISHED). По умолчанию прием аудио и видео данных от контакта разрешен и может быть изменен вызовом toxav_call_control.
Входящий вызов определяется событием toxav_call_cb, после чего вызов может быть как полностью проигнорирован, так и обработан ответом на звонок или изменением его состояния через toxav_call_control.
Для ответа на звонок используется вызов toxav_answer (friend_number, audio_bit_rate, video_bit_rate), где в качестве аргументов указывается идентификатор контакта из контакт-листа, совершающего входящий вызов, и исходящий аудио и видео битрейт (Kb/s). Значение битрейта 0 блокирует отправку соответствующего потока, который можно будет включить позже вызовом toxav_bit_rate_set.
Для контроля состояния звонка используется вызов toxav_call_control (friend_number, control), где в качестве управляющего параметра могут быть использованы следующие константы:
- TOXAV_CALL_CONTROL_RESUME — возобновление звонка, который ранее был установлен на паузу, не может использоваться до ответа на звонок.
- TOXAV_CALL_CONTROL_PAUSE — установка звонка на паузу (удержание вызова, hold), не может использоваться до ответа на звонок.
- TOXAV_CALL_CONTROL_CANCEL — сброс входящего вызова или завершение активного звонка.
- TOXAV_CALL_CONTROL_MUTE_AUDIO — отправка респонденту запроса на остановку передачи аудио данных (респондент может проигнорировать данный запрос).
- TOXAV_CALL_CONTROL_UNMUTE_AUDIO — оправка респонденту запроса на возобновление передачи аудио данных.
- TOXAV_CALL_CONTROL_HIDE_VIDEO — отправка респонденту запроса на остановку передачи видео данных (респондент может проигнорировать данный запрос).
- TOXAV_CALL_CONTROL_SHOW_VIDEO — оправка респонденту запроса на возобновление передачи видео данных.
Для передачи аудио потока используется вызов toxav_audio_send_frame (friend_number, pcm, sample_count, channels, sampling_rate), где в качестве аргументов указывается идентификатор контакта из контакт-листа, буфер PCM данных, количество сэмплов, количество каналов и частота дискретизации. По аналогии с toxav_audio_receive_frame_cb для стерео потока 16-и битные отсчеты для левого и правого канала должны идти последовательно друг за другом.
Следует напомнить, что максимальное количество каналов составляет 2 (стерео), частота дискретизации может принимать значения 8, 12, 16, 24 и 48 KHz и из за особенностей кодека opus, количество сэмплов должно соответствовать длительности кадра в 2.5, 5, 10, 20, 40 или 60 ms.
Для передачи кадра видео потока используется вызов toxav_video_send_frame, который в python обертке переименован в toxav_video_send_yuv420_frame (friend_number, width, height, y, u, v), где в качестве аргументов указывается идентификатор контакта из контакт-листа, ширина и высота кадра в пикселях, буферы Y (яркость) и цветоразностные U-V.
Пример реализации преобразования из BGR в YUV420 можно найти, например, в исходном коде µTox (bgrtoyuv420). В python обертке для удобства реализованы методы toxav_video_send_bgr_frame (friend_number, width, height, bgr) и toxav_video_send_rgb_frame (friend_number, width, height, rgb), которые осуществляют данные преобразования самостоятельно (по факту используется bgrtoyuv420 из кода µTox).
Python является достаточно удобным языком для прототипирования и изучения принципов работы. Для наглядой демонстрации всего вышеизложенного я написал несколько примеров:
echobot.py — обычный текстовый echo-бот, который отвечает отправленным ему текстом. Дополнительно демонстрирует управление контактами, статусами, аватарками, файлами. Является базовым для реализации остальных примеров.
echoavbot.py — аудио и видео echo-бот, который передает обратно вызывающему отправленные ему аудио и видео потоки. Может быть удобен для изучения работы влияния сети на качество передачи данных, возникающие задержки, тестирования собственного оборудования.
avbot.py — бот для имитации респондента, передает аудио и видео данные с собственных устройств. Может быть удобен для наблюдения за домашними животными во время своего отсутствия :)
Проект Tox жив и активно развивается. Разрыв отношений с Tox Foundation нисколько не затормозил развитие проекта и, кажется, дал ему второе дыхание — начали приниматься PR, разбираться тикеты, активно наполняется новый сайт и ведется работа над документацией. Надеюсь, в скором времени в ядро будет влита ветка работы с групповыми чатами и ядро примет законченный предрелизный вид.
- tox.chat — официальный сайт проекта Tox.
- Проект ядра toxcore на github.
- Проект python-обертки pytoxcore на github.
- Проект для сборки бинарных версий клиентов и библиотек tox.pkg на github включая oldstable дистрибутивы типа Debian Wheezy, Ubuntu Precise и CentOS 6, на которых «официальные» сборки клиентов могут не запускаться.