За окном 2020 год. Удалённая работа — это уже совершенно нормально. Десятилетия рекламы ведущих телекоммуникационных компаний наконец сделали своё дело — научили мою маму совершать видеозвонки. Правда, события последних месяцев, вызвавшие неожиданный интерес общественности к видеосвязи, привели к небывалому дефициту веб-камер. Но мы не можем допустить того, чтобы это помешало бы нам вести нормальную жизнь. Ведь каждому из нас надо много кому позвонить. У меня есть решение проблемы нехватки веб-камер. Это — Mitsubishi VisiTel!


Mitsubishi VisiTel

В 1988 году, когда появилось это устройство, оно, определённо, олицетворяло собой будущее. А при его низкой цене — всего $399 — как могло оно мгновенно не стать популярным? В журнале «Популярная механика», вышедшем в феврале 1988, Mitsubishi VisiTel была посвящена целая страница. Там устройство было описано с упоминанием неожиданно большого объёма технических деталей. А ведущие передачи Gadget Guru на телеканале WSMV, сразу после того, как завели разговор о VisiTel, задались невероятно важным вопросом: «Можно ли продолжить пользоваться обычным телефоном в то время, когда эта штука подключена к линии?».

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

Я — не первый, кто полагает, что в VisiTel есть что-то особенно китчевое. Люди уже давно возятся с этим устройством. Несмотря на то, что оно не передаёт видеосигнал, а лишь позволяет обмениваться фотографиями, это — один из тех продуктов, которые относятся к выдающимся достижениями инженерной мысли. Это — разработка, которая серьёзно опередила своё время.

Мой интерес к VisiTel появился после этого видео. Его автор показал, как, замедляя или ускоряя захват изображения, отправляемого VisiTel, можно сделать так, чтобы изображение на экране исказилось бы. Узнав это, я тут же понял, что устройство использует простую схему амплитудной модуляции. Зная об этом, я отправился на eBay и нашёл там подходящий мне экземпляр VisiTel. Моей целью было сделать так, чтобы эта удивительная «капсула времени» с техническими достижениями конца 1980-х заработала бы в связке с выбранными мной современными технологиями. В частности, мне хотелось организовать общение с коллегами в Zoom. Более того, мне хотелось достичь этого без необходимости модификации аппаратной части VisiTel. Есть в этом устройстве что-то особенное, такое, что хочется оставить в его первозданном виде.

Шаг 1. Подключение



Начало экспериментов с VisiTel

VisiTel до смешного легко привести в рабочее состояние. Из задней части устройства выходит длинный кабель с Y-образным разветвителем, находящимся ближе к концу (подробнее об этом — ниже), и с парой разъёмов. Один из них — это разъём питания диаметром 2,1 мм. Устройству нужно 15 вольт. Несколько странно то, что разработчики устройства решили написать число 15 таким шрифтом, что надпись читается как «IS Volts». Второй разъём — это обычный RJ-11, в котором задействованы два контакта. Это — стандартный разъём для подключения телефонов. Если вы помните обычные проводные телефоны, то вы можете вспомнить ещё и о том, что трубка подключается к аппарату с помощью разъёма RJ-9, а телефон подключается к линии с помощью разъёма RJ-11. Смысл этого в том, чтобы к телефону можно было бы подключить не только трубку, но и что-то ещё, а так же в том, чтобы можно было бы заменить кабель или трубку в том случае, если с ними что-то случится. Правда, нельзя сказать, что с кабелями, идущими от телефонов к трубкам, часто что-то случалось, так как при их изготовлении использовались особые, очень гибкие провода.


Сведения о VisiTel


Кабель, выходящий из задней части VisiTel

Если вернуться к задней части VisiTel, то там, помимо кабеля, есть разъём RJ-11. После того, как я некоторое время размышлял о том, зачем он там нужен, меня осенило. Устройство играет роль посредника между обычным телефонным аппаратом и телефонной линией. Это позволяет VisiTel прослушивать линию или передавать изображения во время обычных телефонных звонков. Это ведёт к очевидной проблеме. А именно, речь идёт о том, что, так как VisiTel пользуется телефонной линией одновременно с телефоном, абонент будет слышать звуки, сопутствующие приёму и отправке изображений. Но инженеры Mitsubishi об этом позаботились. Перед отправкой или получением изображения можно услышать громкий щелчок, указывающий на то, что какое-то реле отключает на некоторое время телефон. Мы собираемся применять VisiTel так, что получаемый с устройства аудиосигнал нам не интересен, поэтому мы не будем ничего подключать к разъёму RJ-11, который находится на задней стенке устройства. Это делает соответствующее реле ненужным, а значит, его можно убрать ради избавления от щелчков, сопутствующих отправке изображения.

Как организовать обмен данными с этим устройством? На моём компьютере, например, нет разъёма RJ-11. Правда, это не страшно, так как в продаже имеются переходники для подключения телефонных трубок с разъёмами RJ-9 к смартфонам. Один из вариантов подключения заключается в том, что можно отрезать разъём RJ-11 и припаять к проводу 3,5-мм штекер, организовав монофонический режим передачи звука. Обратите внимание на то, что я изначально полагал, что у VisiTel имеется разъём RJ-9, а не RJ-11.

При использовании 3,5-мм штекера подключить VisiTel к компьютеру до крайности просто. А именно, надо раздобыть USB-адаптер с отдельными выходами для подключения микрофона и наушника (не TRSS) и подключить VisiTel к подходящему разъёму.

Выше я говорил о том, что ещё вернусь к Y-образному разветвителю. У моего VisiTel была какая-то проблема с проводами около этого разветвителя. Причём, провод был не разорван, когда всё просто отказывается работать, и когда проблема совершенно очевидна. Это была одна из тех неприятных неисправностей, когда вечером всё работает, а на следующее утро — уже нет. Хорошо было бы, если бы создатели VisiTel использовали в своём изделии особые гибкие провода. Эта проблема, которая то появлялась, то исчезала, стоила мне многих дней, проведённых в бессмысленном поиске её решения. Я, без особого успеха, искал проблемы с перегревом устройства, трижды проверял настройки аудиокарты. В итоге я обнаружил проблему, когда, в ходе испытаний, приподнял устройство. После этого я нашёл в записи характерные признаки повреждения соединения. Из этой истории я вынес один урок, касающийся работы со старым аппаратным обеспечением. Он заключается в том, что, во время работы устройства, его надо немного подвигать и посмотреть, всё ли в это время функционирует так же, как прежде.


Место повреждения проводов отмечено кружком

Шаг 2. Исследование механизма передачи изображений


После того, как мы решили проблему с хитрыми проводами и соединили устройство с компьютером, пришло время разобраться с тем, какой протокол используется для кодирования передаваемых изображений. Видеоматериалы и статьи о VisiTel позволяют сделать вывод о том, что изображения кодируются с использованием амплитудной модуляции звукового сигнала. Амплитудная модуляция — это кодирование данных, выполняемое путём изменения амплитуды несущего сигнала. Обычно амплитудную модуляцию используют для кодирования звука, передаваемого с помощью радиоволн, но такой же подход можно использовать для передачи изображений с использованием радиоволн или даже звуковых волн. Тут можно найти прекрасное подробное описание этого процесса с примерами кода на Python, предназначенное для тех, кто привык учиться новому через практику.

Зная о том, что данные закодированы с использованием механизма амплитудной модуляции звукового сигнала, мы можем сделать первый шаг к раскодированию этих данных. Этот шаг заключается в записи модулированного сигнала, передающего изображение-образец с заранее известными свойствами. Для захвата аудиосигнала я воспользовался Audacity — моей привычной программой для работы со звуком.


Графическое представление аудиосигнала

Выше показано, как я, стремясь лучше понять то, как именно кодируются данные, отправил с VisiTel очень простое, специально подготовленное изображение. Это изображение, показанное ниже, представляло собой пару карточек, чёрную и белую, которые я держал перед камерой устройства. Сигнал, соответствующий ему, отмечен в программе как blackWhiteV.


Изображение, используемое в эксперименте

Присмотримся к полученному аудиосигналу поближе.


Более пристальное исследование аудиосигнала

Послушать этот сигнал можно здесь.

Как видите, в начале каждого блока имеется некий заголовок, или последовательность, указывающая на инициализацию (с 17,55 до 17,80 в данном примере). Это позволяет принимающему устройству узнать о том, что другое устройство собирается передать изображение и определить максимальную амплитуду сигнала. Этот показатель потом, при декодировании изображения, используется как коэффициент масштабирования. В результате недостатки телефонной линии не приведут к потере изображением контраста или яркости. Сигнал, передаваемый по некоторым линиям, может оказаться зашумлённым или ослабленным. Обратите внимание на то, что сигнал до 17,55 — это просто шум, он неважен для протокола передачи данных, используемым VisiTel.

Для того чтобы убедиться в том, что обнаруженная мной заголовочная последовательность действительно указывает VisiTel на то, что другое устройство собирается отправить ему изображение, я дал устройству «послушать» лишь этот небольшой фрагмент записи и был вознаграждён щелчком реле. Очевидно было то, что эта последовательность приводит в действие некие механизмы. Но, отправив устройству лишь 30 миллисекунд заголовка, я понял, что устройство обнаруживает то, что изображение после заголовка не передаётся. Реле  выключается через несколько миллисекунд после окончания передачи заголовка. Если же проиграть заголовок и первые несколько миллисекунд звука, идущего за ним, то VisiTel начинает выводить изображение на экране. Если выключить звук в то время, когда устройство выводит изображение на экран, то работа продолжается, VisiTel продолжает получать какие-то данные до заполнения буфера, используемого для хранения изображения. Это в очередной раз доказывает то, что после того, как VisiTel начинает обрабатывать графические данные, устройство не полагается на внешний осциллятор для того чтобы узнать о том, где именно в сигнале закодированы нужные ему данные. У него имеется собственный генератор тактовых импульсов, который указывает ему на то, где именно находится то, что ему нужно.

Так как заголовочная часть сигнала отвечает лишь за установку соединения, и учитывая то, что она не зависит от передаваемого изображения, у меня не возникло желания углубляться в исследование её особенностей. Нам, для достижения наших целей, достаточно знать о том, что заголовок отвечает за установление соединения, а так же достаточно иметь примерное представление о том, как он выглядит.

А теперь пришло время переходить к самому сложному — к выяснению того, как именно закодирована графическая информация, как «звучат» пиксели. Для начала мне нужно было разобраться с тем, как пиксельные данные представлены в звуковом сигнале. Моя первая догадка заключалась в том, что каждая полная волна представляет один пиксель. Я проверил эту идею, подсчитав количество волн между повторами тестового паттерна. То, что у меня получилось, соответствовало характеристикам VisiTel из старой рекламы. А именно — это 96x96 пикселей, при этом несколько строк выводятся до начала вывода самого изображения.

Всё это значит, что устройство анализирует амплитуду каждой волны и записывает данные в виде пикселя буфера, представляющего в VisiTel цифровое изображение. Из маркетинговых материалов по VisiTel мы знаем о том, что каждый пиксель выводимого им изображения имеет 16 градаций серого. Но я, анализируя пиксели из «аналогового» сигнала, не ощутил необходимости применять эффект постеризации их при декодировании или кодировании.

Интересно то, что яркость пикселей перед модуляцией инвертируется. В итоге самые большие волны соответствуют самым тёмным пикселям. Кроме того, изображение отражается слева направо, то есть — формируется зеркальное изображение. Мне хотелось бы узнать о том, что читатели этого материала думают о причине инвертирования яркости пикселей перед передачей сигнала. Я подозреваю, что дело тут в том, что человеческое зрение спокойнее воспринимает случайным образом разбросанные чёрные пиксели, чем такие же белые. Ведь шум в телефонных линиях — это проблема, с которой, VisiTel, определённо приходилось бороться в 1988 году.


Исследование сигнала

Правда, в вышеописанной схеме кодирования изображений было одно исключение, которое ускользало от меня несколько недель. При использовании такой вот инвертированной схемы кодирования совершенно белые пиксели должны быть представлены тишиной. Сигнал, представляющий такие пиксели, должен попросту отсутствовать. Но у меня возникло такое ощущение, что создателям VisiTel эта идея не понравилась. Вместо этого для кодирования полностью белых пикселей выполнялось смещение несущего сигнала на 1/4 длины волны, в результате такие волны не совпадали по фазе с обычными. Сигнал при этом, как и прежде, можно было отправить другому устройству. Устройство-приёмник, получая такой сигнал, сохраняло синхронизацию с «обычным» сигналом и брало сведения об амплитуде сигнала там же, где обычно, но теперь в эти моменты волна проходила через 0, что давало белый цвет пикселей. Как я уже говорил, если отключить звук в процессе передачи изображения, то VisiTel продолжает выводить изображение до заполнения буфера. Это — белые пиксели. В результате оказывается, что тишина, и без присутствия в линии полезного сигнала, воспринимается устройством как белый цвет. Не знаю, для чего создатели устройства решили усложнить схему модуляции, введя в неё такой вот режим смещения по фазе. Как по мне, так усилий на это было потрачено много, а пользы от такого шага или вовсе нет, или она весьма несущественна.

Я, не зная о модуляции со сдвигом фазы, изначально пытался найти максимум каждой из волн и представить полученное значение в виде пикселя. Переходы на новую строку выполнялись с шагом, вычисляемым по формуле 96*(число семплов на волну). Это вело к тому, что в некоторых строках оказывалось или немного больше, или немного меньше пикселей. Кроме того, такой вот грубый подход к анализу сигнала был весьма чувствительным к шуму, так как даже маленькие пики на волнах могли приводить к появлению лишних пикселей. Правда, такой механизм декодирования изображений было очень просто реализовать. Реализация этой простой идеи могла бы декодировать изображения без необходимости синхронизации приёмника и источника сигнала. Но у меня, после применения такой системы декодирования сигнала, ничего пристойного не получилось.

Для того чтобы более точно декодировать изображения, нам нужно поступать так же, как поступает VisiTel. А именно, нужно синхронизироваться с сигналом в момент обработки заголовка, а затем семплировать сигнал через равные промежутки времени. Неудивительно то, что для такой обработки сигнала нужно очень точно подбирать время. В моём случае, при записи звука с частотой 44100 Гц, имеется 25,23158 семплов на волну (это значит, что частота несущей волны — 1747,80968929 Гц). В результате каждый раз, когда мы считываем информацию о пикселе, мы будем искать в аудиобуфере данные по следующему пикселю на расстоянии 25,23158 семплов от предыдущего. Так как позиции семплов описываются целочисленными значениями, мы просто округляем соответствующее число до ближайшего целого и используем его. Самое главное здесь — не позволить ошибкам округления накапливаться, так как это приведёт к тому, что позиция семплинга быстро сместится по фазе относительно звуковой волны. А если при декодировании изображения произойдёт подобное смещение, пусть и небольшое, готовое изображение окажется наполненным артефактами.


Изображение, полное артефактов

К моему счастью, число семплов на волну оказалось весьма стабильным. Оно не менялось после прогрева устройства, хотя меня беспокоило то, что это может стать проблемой. Такая стабильность позволила мне попросту жёстко задать соответствующее значение в коде декодирования изображений. В идеале количество семплов на волну нужно узнавать из заголовка, но я обнаружил, что тут недостаточно семплов для достижения точности в 5 знаков после запятой. А значение, жёстко заданное в коде, позволило добиваться стабильных результатов. Благодаря такой, более точной реализации системы декодирования сигнала, переходы на новую строку просто выполнялись после того, как функция, формирующая 1 пиксель, вызывалась 96 раз.

До сих пор я работал с заранее записанными фрагментами звука, декодированными из WAV-файлов. Для того чтобы работать в интерактивном режиме и декодировать изображения, поступающие на аудиоинтерфейс компьютера, декодер нужно было оснастить возможностью обнаружения заголовка и нахождения начала данных изображения. Если взглянуть на заголовок, то окажется, что в нём имеется три чётко различимых фрагмента.


Анализ заголовка: несущая волна — тишина — несущая волна

Для того чтобы это обнаружить, я реализовал простой детектор, основанный на FFT, и конечный автомат. Сначала осуществляется обработка исходного материала, а затем детектор исследует каждый блок аудиосигнала до тех пор, пока не найдёт сильный сигнал частоты 1747 Гц. Затем блоки исследуются до нахождения места, где сигнал исчезает, а потом снова появляется. Это означает нахождение момента начала передачи. После этого для нахождения момента начала передачи данных изображения используется простое статическое смещение, а для декодирования изображения применяются вышеописанные механизмы. После завершения декодирования изображение выводится, а конечный автомат сбрасывается к исходному состоянию и ожидает начала следующей передачи.

Шаг 3. Представление VisiTel в виде веб-камеры


Теперь, когда мы смогли декодировать изображение, принимаемое с VisiTel, осталось поставить на место последний кусок нашего паззла. Нам надо представить декодированные изображения в форме видеокадров и передать видеопоток программе для видеоконференций. В Linux это делается на удивление просто. Видео-вход абстрагируется с помощью интерфейса V4L2, но это, к сожалению, происходит в пространстве ядра. Для того чтобы не связываться со сложностями, сопутствующими созданию модуля ядра, можно воспользоваться готовым решением — v4l2loopback. Этот модуль представляет собой и устройство ввода, и устройство вывода. Входные графические данные, которые ему передают, становятся выходными, которые могут принять другие программы, вроде Zoom. Есть даже Python-пакеты, которые абстрагируют всё это ещё сильнее, позволяя работать с подобными данными с помощью OpenCV и NumPy. Я воспользовался одним из таких пакетов — pyfakewebcam. Он отличается чрезвычайно простым интерфейсом. Для создания виртуальной веб-камеры достаточно сделать следующее:

import
      pyfakewebcam
self.camera
    =
    pyfakewebcam.FakeWebcam(self.v4l2_device, 640, 480)

А при получении нового кадра надо сделать так:

self.camera.schedule_frame(output)

Теперь, после этого небольшого дополнения к декодеру, всё заработало как надо. Zoom может получать изображения с VisiTel, а мы можем организовать видеочат в стиле 1980-х.


Видеочат в духе 1980-х

Мне ещё хотелось бы установить Linux-драйвер Direct Rendering Manager, что позволит не только получать изображения с VisiTel, но и выводить их на него. Но пока, чтобы признать этот проект успешным, мне достаточно того, что я могу общаться в Zoom, пользуясь устройством из 1988 года. Вот, если интересно, код проекта на GitHub.

А вы пытались дать вторую жизнь каким-нибудь устройствам из далёкого прошлого?