Не так давно (начиная с JellyBean 4.2) Google добавила в Android поддержку технологии Miracast.
Практическому исследованию этой технологии методами reverse engineering и посвящена статья.
Что такое Miracast в двух словах? Это очередное детище Wi-Fi альянса — стандарт для передачи мультимедийного контента по сети Wi-Fi в peer-to-peer режиме. Для пользователя это означает прежде всего то, что для соединения с телевизором (к примеру) ему не понадобится Wi-Fi маршрутизатор. Два устройства по задумке альянса должны связываться друг с другом напрямую. Это обеспечивается использованием стандарта Wi-Fi Direct за авторством той же организации. Иными словами, новый стандарт решает задачи очень похожие на AirPlay от Apple, WiDi от Intel, или старое-доброе DLNA.
Зачем было городить огород — спросите вы. Почему было не воспользоваться уже существующим решением? Тут мне будет трудно ответить. Понятно, что лицензировать решения от прямых конкурентов или даже от Intel — не кошерный вариант имеющий к тому же фатальный недостаток, но почему не взять то же DLNA, возможно, чуть доработав рашпилем. Быть может, хотелось чего-то новенького, с модными нонче словами peer-to-peer? Не буду гадать. Так или иначе, технология была реализована в Android, и свежие телефоны типа Nexus 4 и Samsung Galaxy S3 имеют ее на борту.
Хуже обстоит дело с производителями телевизоров. Если поддержка DLNА уже есть практически в каждом современном телевизоре достаточно высокого уровня, то с Miracast дела обстоят хуже. Несмотря на существование чипов, модели телевизоров и проекторов умеющие принимать Miracast можно пересчитать по пальцам. Впрочем, ситуация наверняка изменится в 2014 году, а пока — пользователь может довольствоваться многочисленными гаджетами, принимающими сигнал по Wi-Fi и преобразующими его в HDMI. Такая штука втыкается в HDMI-разъем телевизора, и вот уже у вас есть Miracast-enabled устройство!
Один из инженерных образцов с чипом Broadcom попал в мои цепкие руки:
Убедившись, что с Android-смартфоном все работает на ура, я задумался над вопросом — нельзя ли наладить вещание через Miracast прямо из под Linux? Ведь что такое Android внутри? Тот же Linux…
Для начала, хотелось понять как вообще выглядит стек протоколов Miracast? Что стоит за красивым названием? Гонится ли видео-сигнал напрямую в Ethernet-фреймах или используется IP и еще более высокоуровневые протоколы. К сожалению, сам стандарт, хоть и открытый, но далеко не бесплатный, так что пришлось изыскивать иные, более традиционные пути исследования. В какой-то презентации я ухватил ключевые слова — MPEG-TS и RTSP, и это дало возможность раскрутить клубок дальше. Если я хоть что-то в чем-то смыслю, то RTSP — это TCP, а TCP — это IP. А IP — это подходящий протокол, который можно послушать tcpdump-ом! Сказано-сделано, запустив на Nexus-е tcpdump и включив Wireless display в настройках, через 5 минут я имел дамп пакетов, приемлемый для дальнейшего анализа.
Временно отложив трудности с соединением через Wi-Fi я взялся сразу за анализ TCP-потока. И вот что увидел:
OPTIONS * RTSP/1.0 Date: Fri, 08 Mar 2013 12:37:54 +0000 Server: Mine/1.0 CSeq: 1 Require: org.wfa.wfd1.0 RTSP/1.0 200 OK CSeq: 1 Public: org.wfa.wfd1.0, GET_PARAMETER, SET_PARAMETER OPTIONS * RTSP/1.0 CSeq: 1 Require: org.wfa.wfd1.0 RTSP/1.0 200 OK Date: Fri, 08 Mar 2013 12:37:54 +0000 Server: Mine/1.0 CSeq: 1 Public: org.wfa.wfd1.0, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER GET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0 Date: Fri, 08 Mar 2013 12:37:54 +0000 Server: Mine/1.0 CSeq: 2 Content-Type: text/parameters Content-Length: 83 wfd_content_protection wfd_video_formats wfd_audio_codecs wfd_client_rtp_ports RTSP/1.0 200 OK CSeq: 2 Content-Type: text/parameters Content-Length: 751 wfd_content_protection: none wfd_video_formats: 00 00 02 10 0001bdeb 3fffffff 00000fff 00 0000 0000 11 none none, 02 08 0001bdeb 3fffffff 00000fff 00 0000 0000 11 none none, 02 04 0001bdeb 3fffffff 00000fff 00 0000 0000 11 none none, 02 02 0001bdeb 3fffffff 00000fff 00 0000 0000 11 none none, 02 01 0001bdeb 3fffffff 00000fff 00 0000 0000 11 none none, 01 10 0001bdeb 3fffffff 00000fff 00 0000 0000 11 none none, 01 08 0001bdeb 3fffffff 00000fff 00 0000 0000 11 none none, 01 04 0001bdeb 3fffffff 00000fff 00 0000 0000 11 none none, 01 02 0001bdeb 3fffffff 00000fff 00 0000 0000 11 none none, 01 01 0001bdeb 3fffffff 00000fff 00 0000 0000 11 none none wfd_audio_codecs: LPCM 00000003 00 wfd_client_rtp_ports: RTP/AVP/UDP;unicast 6500 0 mode=play SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0 Date: Fri, 08 Mar 2013 12:37:54 +0000 Server: Mine/1.0 CSeq: 3 Content-Type: text/parameters Content-Length: 248 wfd_video_formats: 28 00 02 02 00000020 00000000 00000000 00 0000 0000 00 none none wfd_audio_codecs: LPCM 00000002 00 wfd_presentation_URL: rtsp://192.168.16.40/wfd1.0/streamid=0 none wfd_client_rtp_ports: RTP/AVP/UDP;unicast 6500 0 mode=play RTSP/1.0 200 OK CSeq: 3 SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0 Date: Fri, 08 Mar 2013 12:37:54 +0000 Server: Mine/1.0 CSeq: 4 Content-Type: text/parameters Content-Length: 27 wfd_trigger_method: SETUP RTSP/1.0 200 OK CSeq: 4 SETUP rtsp://192.168.16.40/wfd1.0/streamid=0 RTSP/1.0 CSeq: 2 Transport: RTP/AVP/UDP;unicast;client_port=6500 RTSP/1.0 200 OK Date: Fri, 08 Mar 2013 12:37:55 +0000 Server: Mine/1.0 CSeq: 2 Session: 1219569791;timeout=30 Transport: RTP/AVP/UDP;unicast;client_port=6500;server_port=15550 PLAY rtsp://192.168.16.40/wfd1.0/streamid=0 RTSP/1.0 CSeq: 3 Session: 1219569791 RTSP/1.0 200 OK Date: Fri, 08 Mar 2013 12:37:55 +0000 Server: Mine/1.0 CSeq: 3 Session: 1219569791;timeout=30 Range: npt=now- SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0 Date: Fri, 08 Mar 2013 12:38:07 +0000 Server: Mine/1.0 CSeq: 5 Content-Type: text/parameters Content-Length: 30 wfd_trigger_method: TEARDOWN RTSP/1.0 200 OK CSeq: 5 TEARDOWN rtsp://192.168.16.40/wfd1.0/streamid=0 RTSP/1.0 CSeq: 4 Session: 1219569791 RTSP/1.0 200 OK Date: Fri, 08 Mar 2013 12:38:09 +0000 Server: Mine/1.0 CSeq: 4 Session: 1219569791;timeout=30 Connection: close
Неправда ли, напоминает обычный RTSP. Итак, часть дела сделана. Остается понять чем отличается Miracast-овская реализация RTSP от стандартной. Для тех, кто никогда не сталкивался с RTSP (Real Time Streaming Protocol) напомню, что он используется для управления мультимедийным потоком с сервера на клиенте. Сиречь — позволяет выдать такие команды как PLAY, PAUSE, TEARDOWN и т.п. Также имеется возможность обменяться опциями и настроить параметры. Именно {GET|SET}_PARAMETER и стали основной моей головной болью при анализе. Не имея под рукой стандарта, я не мог знать, что значат все эти wfd_video_formats, wfd_audio_codecs и т.п. Но мог догадываться!
Поскольку из анализа фреймов MPEG-TS я понял, что использовалось стандартное разрешение 720x480, и кодек H.264 (AVC), то было неплохой идеей создать видеофайл с ровно такими же параметрами, и тогда поля типа wfd_video_formats можно оставить без изменения! Порывшись в DVD-дисках я перекодировал небольшой VOB из телесериала «Cracker», в нужный мне формат посредством ffmpeg. Теперь оставалось только скормить файл серверу. Но для этого нужно найти сервер!
Чтобы не писать RTSP-сервер самостоятельно (что никак не входило в мои планы) я начал просматривать Open Source варианты, которые было бы легко доработать до состояния совместимого с Miracast. Если вы внимательно смотрели на логи из tcpdump-а, то могли заметить несколько странностей. Традиционная клиент-серверная модель RTSP заменена «peer-to-peer» взаимодействием. Это значит, что активность в запросах может исходить не только от клиента (им в данном случае выступает телевизор или проектор), а и от «сервера» (то бишь телефона или компьютера). Зачем понадобилось так делать — непонятно, но факт остается фактом — и «клиент» и «сервер» могут слать запросы когда им вздумается, что сводит на нет их традиционные роли. Тем не менее, сторону которая шлет видеосигнал я буду продолжать именовать сервером (в нашем случае это Linix-PC), а сторону, принимающую и декодирующую видео — клиентом (в нашем случае — это будет проектор).
Итак, после нескольких часов поисков я остановился на live555. Этот сервер написан на С++, распространяется под лицензией LGPL и поддерживает как RTSP, так и вещание в MPEG-TS. Поглядев на обработчик RTSP я понял, что его вполне реально переработать под peer-to-peer специфику Miracast. Но, оставалось еще заставить клиента (т.е. Мiracast-гаджет) соединяться с Linux!
Эта задача была посложней «Фауста» Гёте. Прежде я никогда не настраивал в Linux-е даже обычный Wi-Fi, справедливо полагая, что провода как-то понадежнее. Что уж говорить про Wi-Fi Direct. Однако, прочитав стопку manual-ов, я понял, что надо рыть в направлении загадочного WPA supplicant. Для чего нужен этот supplicant? Именно он обеспечивает аутентификацию при подключении по Wi-Fi к точке доступа или к другому узлу. Как я уже писал выше, Miracast работает в режиме p2p, т.е. устройства связываются напрямую, минуя маршрутизаторы. Эта возможность, к счастью, поддержана в последних версиях wpa_supplicant. Не знаю точно, с какого момента была добавлена поддержка p2p, но в версии 2.1-devel она уже есть.
Однако, обновить supplicant мало! Надо еще иметь конфигурационные файлы для него. С грехом пополам я написал конфигурацию приемлемую для моего устройства (NetGear, WNA1100 Wireless-N 150 [Atheros AR9271]), возможно, она подойдет и вам.
Итак, в файле /etc/wpa_p2p.conf пишем:
ctrl_interface=/var/run/wpa_supplicant
ap_scan=1
device_name=JellyFish
device_type=1-0050F204-1
Далее, нужен shell-скрип для запуска supplicant:
sudo iwconfig wlan0 mode ad-hoc
sudo ip link set wlan0 up
sudo wpa_supplicant -Dnl80211 -c /etc/wpa_p2p.conf -i wlan0 -dt
Вот вроде и все (уточню, что данная конфигурация работает в Ubuntu-based дистрибутиве Linux Mint 13 Maya, версия ядра — 3.2.0-57-generic).
Дальше нужно овладеть такой утилитой как wpa_cli, именно она позволяет управлять соединением «вручную».
После запуска wpa_supplicant через скрипт, нужно открыть отдельную консоль и выдать что-то вроде:
sudo wpa_cli
Это командный интерфейс к supplicant-у. Включив гаджет мы можем командой p2p_find найти все устройства в округе, готовые подключиться к нам в режиме p2p. Далее, используя команду p2p_connect мы производим само подключение.
Вот пример лога для моего устройства:
wpa_cli v2.1-devel Selected interface 'wlan0' Interactive mode > p2p_find OK <3>P2P-DEVICE-FOUND 02:90:4c:04:04:04 p2p_dev_addr=02:90:4c:04:04:04 pri_dev_type=7-0050F204-1 name='MLT-52-2123' config_methods=0x4688 dev_capab=0x25 group_capab=0xa > > p2p_connect 02:90:4c:04:04:04 pbc OK <3>P2P-FIND-STOPPED <--- Тут надо нажать кнопку на устройстве <3>P2P-GO-NEG-SUCCESS <4>Failed to initiate AP scan <4>Failed to initiate AP scan <4>Failed to initiate AP scan <4>Failed to initiate AP scan <3>CTRL-EVENT-SCAN-RESULTS <3>WPS-AP-AVAILABLE-PBC <3>SME: Trying to authenticate with 02:90:4c:04:84:04 (SSID='DIRECT-fCMLT-52-2123' freq=2412 MHz) <3>Trying to associate with 02:90:4c:04:84:04 (SSID='DIRECT-fCMLT-52-2123' freq=2412 MHz) <3>CTRL-EVENT-SCAN-RESULTS <3>WPS-AP-AVAILABLE-PBC <3>SME: Trying to authenticate with 02:90:4c:04:84:04 (SSID='DIRECT-fCMLT-52-2123' freq=2412 MHz) <3>Trying to associate with 02:90:4c:04:84:04 (SSID='DIRECT-fCMLT-52-2123' freq=2412 MHz) <3>Associated with 02:90:4c:04:84:04 <3>CTRL-EVENT-EAP-STARTED EAP authentication started <3>CTRL-EVENT-EAP-PROPOSED-METHOD vendor=14122 method=1 <3>CTRL-EVENT-EAP-METHOD EAP vendor 14122 method 1 (WSC) selected <3>WPS-CRED-RECEIVED <3>WPS-SUCCESS <3>P2P-GROUP-FORMATION-SUCCESS <3>CTRL-EVENT-EAP-FAILURE EAP authentication failed <3>CTRL-EVENT-DISCONNECTED bssid=02:90:4c:04:84:04 reason=3 locally_generated=1 <3>CTRL-EVENT-SCAN-RESULTS <3>WPS-AP-AVAILABLE <3>SME: Trying to authenticate with 02:90:4c:04:84:04 (SSID='DIRECT-fCMLT-52-2123' freq=2412 MHz) <3>Trying to associate with 02:90:4c:04:84:04 (SSID='DIRECT-fCMLT-52-2123' freq=2412 MHz) <3>Associated with 02:90:4c:04:84:04 <3>WPA: Key negotiation completed with 02:90:4c:04:84:04 [PTK=CCMP GTK=CCMP] <3>CTRL-EVENT-CONNECTED - Connection to 02:90:4c:04:84:04 completed [id=0 id_str=] <3>P2P-GROUP-STARTED wlan0 client ssid="DIRECT-fCMLT-52-2123" freq=2412 psk=fd435c6683ae5d7c9e3398dab15cc1b80d7f308b3fe7330db044ea90dcf7ac31 go_dev_addr=02:90:4c:04:04:04 [PERSISTENT]
В принципе, из лога все понятно, кроме разве что загадочного слова 'pbc' в команде p2p_connect после адреса устройства. Что же оно значит? Это один из вариантов аутентификации при подключении по Wi-Fi direct. Означает он — Push Button Control. Это упрощенная аутентификация, не требующая от пользователя ввода пароля или даже pin-кода. Просто в момент соединения нужно нажать кнопку на устройстве, и аутентификаця будет считаться успешной.
Итак, из лога мы видим, что соединение успешно произошло. И теперь мы имеем возможность получить IP-адрес для интерфейса wlan0.
DHCP-сервером в данном случае будет выступать телевизор или проектор. Введем в отдельном терминале:
sudo dhclient wlan0
Если после этого запустить tcpdump, то мы обнаружим попытки посылки SYN-пакета на порт 7236. Этот порт отличается от стандартного порта для RTSP (554), но пугать это нас не должно. Самое главное, что гаджет хочет с нами договориться! Запустив уже слегка доработанный livemedia сервер на этом порту (7236) мы получаем возможность отлаживать собственно «клиент-серверное» взаимодействие.
Я не буду утомлять читателя подробностями отладки протокола, скажу лишь, что все проблемы так или иначе были решены. И вот, наконец, результат налицо — я смог смотреть видео со своего PC через новомодный Miracast!
Нужно ли это вам? Не знаю. Во всяком случае, разобраться в новом стандарте всегда интересно (если конечно это не ASN.1).
Для тех, кому было лень вникать в технические подробности тезисно обрисую процедуру соединения для Miracast-based устройств:
- Используя Wi-Fi direct, устройства находят друг друга (обычно — источник видео-данных находит устройство отображения)
- Используя ту или иную форму аутентификации (в нашем случае — pbc) устройства объединяются в P2P-группу
- Одно из устройств получает IP-адрес по DHCP (в нашем случае — это источник видео-данных)
- На источнике данных на порту 7236 запускается RTSP-сервер
- Клиент подключается к RTSP-серверу, и запрашивает некий предопределенный URL (/wfd1.0/streamid=0)
- RTSP-сервер начинает передавать видео (и, возможно, аудио) данные в форме MPEG-TS упакованных в RTP-пакеты.
- Клиент распаковывает данные и отображает их на устройстве вывода.
Из явных недостатков Miracast (не упомянутых в Wiki) я бы отметил следующие:
- Если вы подключаетесь к Miracast-устройству то теряете возможность работы через обычный (не P2P) Wi-Fi. Чтобы одновременно пользоваться традиционным Wi-Fi и Wi-Fi direct нужен специальный двух-канальный Wi-Fi адаптер. Он имеется далеко не во всех телефонах!
- Качество картинки на динамичных сценах страдает даже при разрешении 720x480, 30 FPS. Я уж не говорю про Full HD. Разумеется, с появлением более мощных процессоров картина будет меняться, но пока все печально.
Вот собственно и все. Если у вас остались вопросы — задавайте в комментариях.
Автор: A_J