Хмарний геймінг із відкритим вихідним кодом на WebRTC: p2p, мультиплеєр, zero latency

Хмарний геймінг із відкритим вихідним кодом на WebRTC: p2p, мультиплеєр, zero latency
ПЗ як послуга, інфраструктура як послуга, платформа як послуга, комунікаційна платформа як послуга, відеоконференції як послуга, а щодо хмарних ігор як послуги? Вже було зроблено кілька спроб створення хмарних ігор (Cloud Gaming), наприклад Stadia, нещодавно запущена компанією Google. Stadia не новачок у WebRTC, але чи можуть інші використовувати WebRTC так само?

Тхань Нгуєн (Thanh Nguyen) вирішив перевірити цю можливість на своєму опенсорсному проекті CloudRetro. CloudRetro заснований на Pion, популярної WebRTC бібліотеці на базі Go (дякую) Шону з групи розробників Pion за допомогу у підготовці цієї статті). У цій статті Тхань робить огляд архітектури свого проекту, а також розповідає, що корисного він дізнався і з якими челленджами зіткнувся під час роботи.

Вступ

Минулого року, коли Google анонсував Stadia, мені просто знесло дах. Ідея настільки унікальна та інноваційна, що я постійно ставив собі питання, як таке взагалі можливо при існуючих технологіях. Бажання краще розібратися у цій темі спонукало мене створити свою версію опенсорсної хмарної гри. Результат був просто фантастичний. Нижче я хотів би поділитися процесом роботи над моїм річним проектом.

TLDR: коротка слайд-версія з основними моментами

Чому за хмарними іграми майбутнє

Я вірю, що Cloud Gaming незабаром стане новим поколінням не лише ігор, а й інших галузей інформатики. Хмарні ігри – це вершина моделі клієнт/сервер. Така модель максимізує управління бекендом та мінімізує роботу фронтенду за рахунок розміщення ігрової логіки на віддаленому сервері та потокової передачі зображень/аудіо клієнту. Сервер виконує важку обробку, тому клієнт не залежить від апаратних обмежень.

Google Stadia, по суті, дозволяє грати в AAA-ігри (Тобто висококласні ігри-блокбастери) на інтерфейсі на кшталт YouTube. Така сама методологія може бути застосована і до інших важких офлайнових додатків, таких як операційна система або 2D/3D графічний дизайн і т.д. щоб ми могли стабільно запускати їх на пристрої з низькими технічними характеристиками на різних платформах.

Хмарний геймінг із відкритим вихідним кодом на WebRTC: p2p, мультиплеєр, zero latency
Майбутнє цієї технології: уявляєте, якби Microsoft Windows 10 працював у браузері Chrome?

Хмарні ігри технічно складні

Геймінг - одна з тих рідкісних областей, де потрібна постійна швидка реакція користувача. Якщо зрідка ми зустрічаємося із затримкою в 2 секунди при натисканні на сторінці, це прийнятно. Відеопотоки у прямому ефірі, як правило, відстають на кілька секунд, але все одно пропонують достатню зручність у використанні. Однак якщо гра часто затримується на 500 мс, грати просто неможливо. Наша мета – досягти надзвичайно низької затримки, щоб розрив між введенням та медіа був якомога меншим. Тому традиційний підхід до потокового відео тут не застосовується.

Хмарний геймінг із відкритим вихідним кодом на WebRTC: p2p, мультиплеєр, zero latency
Загальний шаблон хмарної гри

Опенсорсний проект CloudRetro

Я вирішив створити тестовий зразок хмарної гри, щоб перевірити, чи можливо все це за таких жорстких мережевих обмежень. Для перевірки концепції я вибрав Golang, оскільки це найбільш знайома мені мова і добре придатна для цієї реалізації з багатьох інших причин, як з'ясувалося пізніше. Go простий та розвивається дуже швидко; канали Go відмінно підходять для управління багатопоточністю.

Проект CloudRetro.io - Хмарний ігровий сервіс з відкритим вихідним кодом для ретро-ігри. Мета проекту – привнести в традиційні ретро-ігри найкомфортніші ігрові відчуття та додати мультиплеєр.
Детально ознайомитись із проектом можна тут: https://github.com/giongto35/cloud-game.

Функціональність CloudRetro

Для демонстрації всієї потужності хмарних ігор CloudRetro використовуються ретро-ігри. Що дозволяє отримати багато унікальних ігрових вражень.

  • Портативність гри
    • Миттєве відтворення під час відкриття сторінки; завантаження та встановлення не потрібні
    • Працює в мобільному браузері, так що для запуску не потрібне жодне програмне забезпечення

  • Ігрові сеанси можна спільно використовувати на кількох пристроях та зберігати у хмарі для наступного входу
  • Гра можна стримати, а можна грати в неї відразу кількома користувачами:
    • Crowdplay типу TwitchPlayPokemon, тільки більш кроссплатформенний і ріалтаймовий
    • Оффлайн ігри онлайн. Грати можуть багато користувачів без налаштування мережі. У Samurai Shodown тепер можна грати 2 гравцям по мережі CloudRetro

    Хмарний геймінг із відкритим вихідним кодом на WebRTC: p2p, мультиплеєр, zero latency
    Демо-версія розрахованої на багато користувачів онлайн-ігри на різних пристроях

    Інфраструктура

    Вимоги та стек технологій

    Нижче наведено список вимог, які я встановив перед початком проекту.

    1. Один гравець
    Ця вимога може здатися не надто важливою і очевидною тут, але це один із моїх ключових висновків, це дозволяє хмарним іграм триматися якнайдалі від традиційних потокових сервісів. Якщо ми зосередимося на однокористувацькій грі, ми зможемо позбутися централізованого сервера або CDN, тому що нам не потрібно робити потокову передачу в маси. Замість того, щоб завантажувати потоки на сервер, що поглинає, або передавати пакети на централізований сервер WebSocket, сервісні потоки передаються користувачеві безпосередньо через однорангове з'єднання WebRTC.

    2. Медіапотік із низькою затримкою
    Читаючи про Stadia, часто зустрічаю в деяких статтях згадку WebRTC. Я зрозумів, що WebRTC – видатна технологія, і вона чудово підходить для використання у хмарних іграх. WebRTC – це проект, який надає веб-браузерам та мобільним програмам зв'язок у реальному часі через простий API. Він забезпечує однорангове з'єднання, оптимізований для медіа та має вбудовані стандартні кодеки, такі як VP8 та H264.

    Я надав перевагу забезпеченню максимально комфортної роботи користувачів, а не збереженню високої якості графіки. В алгоритмі припустимі деякі втрати. У Google Stadia є додатковий крок для зменшення розміру зображення на сервері, і кадри масштабуються до вищої якості перед передачею однорангових вузлів.

    3. Розподілена інфраструктура з географічною маршрутизацією
    Незалежно від того, наскільки оптимізовано алгоритм стиснення та код, мережа все одно є вирішальним фактором, який найбільше сприяє затримці. Архітектура повинна мати механізм сполучення найближчого до користувача сервера для скорочення часу прийому-передачі (RTT). Архітектура повинна мати 1 координатора та кілька потокових серверів, розподілених у всьому світі: Захід США, Схід США, Європа, Сінгапур, Китай. Усі потокові сервери мають бути повністю ізольовані. Система може регулювати свій розподіл, коли сервер приєднується до мережі або виходить із неї. Таким чином, при великому трафіку додавання додаткових серверів дозволяє здійснювати горизонтальне масштабування.

    4. Браузерна сумісність
    Хмарні ігри постають у найкращому світлі, коли вимагає від користувачів щонайменше. Це означає, що є можливість запуску у браузері. Браузери допомагають зробити ігровий процес максимально комфортним для користувачів, позбавивши їх від встановлення програмного та апаратного забезпечення. Браузери також допомагають забезпечити крос-платформність для мобільних та десктопних версій. На щастя, WebRTC відмінно підтримується у різних браузерах.

    5. Чіткий поділ ігрового інтерфейсу та сервісу
    Я розглядаю обслуговування хмарних ігор як платформу. У кожного має бути можливість підключати до платформи будь-що. Зараз я інтегрував LibRetro з сервісом хмарних ігор, тому що LibRetro пропонує гарний інтерфейс емулятора для ретро-ігор, таких як SNES, GBA, PS.

    6. Кімнати для мультиплеєра, crowd play та зовнішнє зв'язування (deep-link) з грою
    CloudRetro підтримує безліч нових геймплеїв, таких як CrowdPlay та Online MultiPlayer для ретро-ігор. Якщо кілька користувачів відкриють один і той же deep-link на різних комп'ютерах, вони побачать ту саму запущену гру і навіть зможуть приєднатися до неї.

    Більше того, стан гри зберігається в хмарному сховищі. Це дозволяє користувачам продовжувати гру будь-коли на будь-якому іншому пристрої.

    7. Горизонтальне масштабування
    Як і будь-який SAAS в даний час, хмарні ігри повинні бути спроектовані так, щоб горизонтально масштабуються. Конструкція "координатор-воркер" дозволяє додавати більше воркерів, щоб обслуговувати більший трафік.

    8. Немає прив'язки до однієї хмари
    Інфраструктура CloudRetro розміщується на різних хмарних провайдерах (Digital Ocean, Alibaba, провайдер користувача) для різних регіонів. Я активую запуск у контейнері Docker для інфраструктури та налаштовую мережні параметри за допомогою bash-скрипту, щоб уникнути залежності від одного хмарного провайдера. Поєднуючи це з NAT Traversal WebRTC, ми можемо отримати гнучкість для розгортання CloudRetro на будь-якій хмарній платформі і навіть на машинах будь-якого користувача.

    Архітектурний дизайн

    Воркер: (або потоковий сервер, згаданий вище) множить ігри, запускає кодуючий пайплайн та передає закодоване медіа користувачам. Інстанси воркера поширюються по всьому світу, і кожен воркер може обробляти кілька сесій користувача одночасно.

    Координатор: відповідає за пару нового користувача з найбільш підходящим воркером для потокової передачі. Координатор взаємодіє із воркерами через WebSocket.

    Сховище ігрових станів: центральне віддалене сховище для всіх станів гри. Це сховище забезпечує такі важливі функції, як, наприклад, віддалене збереження/завантаження.

    Хмарний геймінг із відкритим вихідним кодом на WebRTC: p2p, мультиплеєр, zero latency
    Верхньорівнева архітектура CloudRetro

    Користувальницький сценарій

    Коли новий користувач відкриває CloudRetro на кроках 1 і 2, показаних нижче, координатор разом зі списком доступних воркерів запитується на першу сторінку. Після цього на кроці 3 клієнт розраховує затримки всіх кандидатів за допомогою HTTP запиту ping. Цей список затримок потім відправляється назад координатору, щоб він міг визначити відповідного воркера для обслуговування користувача. На кроці 4 нижче створюється гра. Між користувачем та призначеним воркером встановлюється потокове з'єднання WebRTC.
    Хмарний геймінг із відкритим вихідним кодом на WebRTC: p2p, мультиплеєр, zero latency
    Користувальницький сценарій після отримання доступу

    Що всередині воркера

    Ігрові та потокові пайплайни зберігаються всередині воркера ізольовано та обмінюються там інформацією через інтерфейс. В даний час цей зв'язок здійснюється за допомогою передачі даних у пам'яті каналам Golang у тому процесі. Наступною метою сегрегація, тобто. незалежний запуск гри у іншому процесі.

    Хмарний геймінг із відкритим вихідним кодом на WebRTC: p2p, мультиплеєр, zero latency
    Взаємодія компонентів воркеру

    Основні складові:

    • WebRTC: клієнтський компонент, що приймає введення користувача і виводить закодоване медіа з сервера.
    • Ігровий емулятор: ігровий компонент. Завдяки бібліотеці Libretro система здатна запускати гру всередині того самого процесу і внутрішньо перехоплювати медіа і потік введення.
    • Внутрішньоігрові кадри захоплюються і відправляються в кодувальник.
    • Зображення/аудіо кодувальник: кодуючий пайплайн, який приймає медіакадри, кодує їх у фоновому режимі та виводить закодовані зображення/аудіо.

    Реалізація

    CloudRetro покладається на WebRTC як на магістральну технологію, тому перед тим, як заглибитись у подробиці реалізації на Golang, я вирішив розповісти про WebRTC. Це приголомшлива технологія, яка сильно допомогла мені в досягненні затримки потокової передачі даних, що дорівнює лише частці секунди.

    WebRTC

    WebRTC призначений для забезпечення високоякісних однорангових з'єднань на мобільному додатку і в браузерах за допомогою простих API.

    Обхід NAT

    WebRTC відомий своєю функціональністю NAT Traversal. WebRTC призначений для одноранговій комунікації. Його мета – знайти найбільш підходящий прямий маршрут, уникаючи NAT-шлюзів та брандмауерів для однорангового зв'язку через процес під назвою ICE. В рамках цього процесу API WebRTC знаходять вашу публічну IP-адресу за допомогою серверів STUN і переадресовують її на сервер ретрансляції (ПЕРЕГЛЯД), коли пряме з'єднання не може бути встановлене.

    Однак, CloudRetro не повністю використовує цю можливість. Його однорангові з'єднання існують не між користувачами, а між користувачами та серверами хмар. Серверна частина моделі має менше обмежень на прямий зв'язок, ніж звичайний пристрій користувача. Це дозволяє робити попереднє відкриття вхідних портів або використання публічних IP-адрес безпосередньо, оскільки сервер не знаходиться за NAT.

    Раніше я хотів перетворити проект на платформу розповсюдження ігор для Cloud Gaming. Ідея полягала в тому, щоб дозволити творцям ігор надавати ігри та потокові ресурси. А користувачі взаємодіяли б із провайдерами безпосередньо. У такій децентралізованій манері CloudRetro є лише середовищем для підключення сторонніх потокових ресурсів до користувачів, що робить його більш масштабованим, коли на ньому більше не висить хостинг. Роль WebRTC NAT Traversal дуже важлива для полегшення ініціалізації однорангового з'єднання на сторонніх потокових ресурсах, що спрощує підключення творця до мережі.

    Стиснення відео

    Стиснення відео – це незамінна частина пайплайну, яка значною мірою сприяє плавності потоку. Незважаючи на те, що не обов'язково знати всі деталі кодування відео в VP8/H264, розуміння концепції допомагає розумітися на параметрах швидкості потокового відео, налагоджувати несподівану поведінку та налаштовувати затримку.

    Стиснення відео для потокового сервісу є складним завданням, тому що алгоритм повинен гарантувати, що загальний час кодування + час передачі через мережу + час декодування настільки мало, наскільки це можливо. Крім того, процес кодування має бути послідовним та безперервним. Деякі взаємні поступки при кодуванні не застосовні – наприклад, ми не можемо віддати перевагу тривалому часу кодування меншому розміру файлу та часу декодування, або використовувати непослідовне стиснення.

    Ідея стиснення відео полягає в тому, щоб унеможливити непотрібні біти інформації, зберігаючи при цьому допустимий рівень точності для користувачів. Крім кодування окремих статичних кадрів зображення, алгоритм робить висновок для поточного кадру з попереднього та наступного, тому надсилається тільки їх різниця. Як видно з прикладу з Pacman'ом, передаються лише диференціальні точки.

    Хмарний геймінг із відкритим вихідним кодом на WebRTC: p2p, мультиплеєр, zero latency
    Порівняння відеокадрів на прикладі Pacman

    Стиснення аудіо

    Аналогічним чином алгоритм стиснення звуку опускає дані, які не можуть бути сприйняті людиною. Opus на даний момент є аудіокодеком з найкращою продуктивністю. Він розроблений передачі аудіохвилі за протоколом упорядкованої датаграми, такому як RTP (Real Time Transport Protocol – протокол передачі трафіку реального часу). Його затримка менша, ніж у mp3 і aac, а якість вища. Затримка зазвичай становить близько 5 мс.

    Pion, WebRTC в Golang

    Піон – це проект із відкритим вихідним кодом, який перетягує WebRTC на Golang. Замість звичайного врапінгу нативних C++ бібліотек WebRTC, Pion є нативною Golang-реалізацією WebRTC з найкращою продуктивністю, інтеграцією з Go, а також контролем версій на протоколах WebRTC.

    Бібліотека також забезпечує потокову передачу даних із великою кількістю відмінних вбудованих модулів із затримкою менше секунди. Вона має власну реалізацію STUN, DTLS, SCTP і т.д. і деякі експерименти з QUIC і WebAssembly. Сама по собі ця опенсорсна бібліотека є справді добрим джерелом навчання з чудовою документацією, реалізацією мережевих протоколів та класними прикладами.

    Ком'юніті Pion, очолюване дуже пристрасним творцем, досить жваве, там ведеться багато якісних дискусій про WebRTC. Якщо вас цікавить ця технологія, приєднуйтесь до http://pion.ly/slack - Ви дізнаєтесь багато нового.

    Написання CloudRetro на Golang

    Хмарний геймінг із відкритим вихідним кодом на WebRTC: p2p, мультиплеєр, zero latency
    Реалізація воркера на Go

    Канали Go у дії

    Завдяки гарному дизайну каналів Go проблеми потокової передачі подій і паралелізму значно спрощуються. Як і на діаграмі, у різних GoRoutines паралельно працюють декілька компонентів. Кожен компонент управляє своїм станом і спілкується каналами. Вибіркове твердження Golang змушує обробити по одному атомарному події кожен час у грі (game tick). Це означає, що для такого дизайну блокування не потрібне. Наприклад, коли користувач зберігається, потрібен повний сніпшот стану гри. Цей стан повинен залишатися безперервним, виконуючи вхід доти, доки збереження не буде завершено. Під час кожного game tick'а бекенд може обробляти тільки операцію збереження або введення, що робить процес безпечним.

    func (e *gameEmulator) gameUpdate() {
    for {
    	select {
    		case <-e.saveOperation:
    			e.saveGameState()
    		case key := <-e.input:
    			e.updateGameState(key)
    		case <-e.done:
    			e.close()
    			return
    	}
        }
    }

    Fan-in / Fan-out

    Цей шаблон Golang відмінно підходить для мого сценарію використання CrowdPlay та Multiple Player. Дотримуючись цього шаблону, всі входи користувача в одній кімнаті вбудовуються в центральний вхідний канал. Ігрові медіа розгортаються на всіх користувачів в одній кімнаті. Таким чином, ми досягаємо поділу стану гри між кількома ігровими сесіями різних користувачів.

    Хмарний геймінг із відкритим вихідним кодом на WebRTC: p2p, мультиплеєр, zero latency
    Синхронізація між різними сеансами

    Недоліки Golang

    Golang не досконалий. Канал повільний. У порівнянні з блокуванням канал Go – це просто простий спосіб обробки паралельних та потокових подій, але канал не дає найкращої продуктивності. Під каналом є складна логіка блокування. Тому я вніс деякі корективи у реалізацію, повторно застосувавши блокування та атомарні значення при заміні каналів для оптимізації продуктивності.

    Крім того, garbage collector в Golang некерований, через що іноді виникають підозрілі довгі паузи. Це сильно заважає роботі потокового додатку у реальному часі.

    COG

    У проекті використовується VP8/H264 бібліотека Golang з відкритим вихідним кодом для стиснення медіа і Libretro для ігрових емуляторів. Всі ці бібліотеки є просто обгортками бібліотеки C Go з використанням COG. Деякі з недоліків перераховані в цьому посту Dave Cheney. Проблеми, з якими я зіткнувся:

    • неможливість зловити краш у CGO, навіть з допомогою Golang RecoveryCrash;
    • неможливість визначити вузьке місце у продуктивності, коли ми не можемо виявити деталізовані проблеми у CGO.

    Висновок

    Я досяг своєї мети – розібрався в хмарних ігрових сервісах та створив платформу, яка допомагає грати в ностальгічні ретро-ігри з моїми друзями онлайн. Створення цього проекту було б неможливим без бібліотеки Pion та підтримки спільноти Pion. Я дуже вдячний за його інтенсивний розвиток. Прості API, надані WebRTC та Pion, забезпечили плавну інтеграцію. Мій перший доказ концепції було випущено того ж тижня, незважаючи на те, що я заздалегідь не знав про одноранговий зв'язок (P2P).

    Незважаючи на простоту інтеграції, P2P-потокове мовлення справді є дуже складною областю у комп'ютерній науці. Їй доводиться мати справу зі складністю багаторічних мережевих архітектур, таких як IP та NAT для створення однорангової сесії. За час роботи над цим проектом я нагромадив багато цінних знань про мережу та оптимізацію продуктивності, тому рекомендую всім спробувати побудувати P2P-продукти за допомогою WebRTC.

    CloudRetro обслуговує всі сценарії використання, які я очікував, на мій погляд, як ретро-геймера. Тим не менш, я думаю, що є багато областей у проекті, які я можу покращити, наприклад, зробити мережу більш надійною та продуктивною, забезпечити більш високу якість графіки ігор, або можливість ділитися іграми між користувачами. Я наполегливо працюю над цим. Будь ласка, стежте за проектом та підтримайте його, якщо він вам подобається.

Джерело: habr.com

Додати коментар або відгук