Как стать автором
Обновить

Комментарии 58

Спасибо, показательная статья, что нужно искать первопричину. Хорошо, что ещё существуют такие специалисты. А то обычная практика, к сожалению, это придумывать костыли для костылей, которые подвешены за верёвочку.

Искать первопричину — это важно, но не всегда эту причину могут устранить за приемлимые время и деньги, откуда и получаются костыли. И тут вторая проблема, что после того, как причину устранили (а это может быть не скоро), те самые костыли обрасли новыми костылями

Так уж получилось, что похоже я практически в то же время натолкнулся на (эту же?) проблему 40ms но в контесте WebRTC в приложении под Android после сворачивания: в одном случае решилось заменой использования pthread_cond_timedwait_relative_np на pthread_cond_timedwait, в другом случае починилось заменой nanosleep(0) на sched_yield. Правда до кода в Android, вызывающиего проблему я так тогда и не докопался.

Чорт. У нас сейчас тоже очень похожая проблема c ExoPlayer. Видео играет несколько секунд, stall на 0.5 сек, играет, stall,… а в логах — audio buffer underrun.

Гениально. Такие глубокие познания.....

С ума сойти, приложение 200-миллиардной компании работает только потому, что в многопоточный мобильной операционной системе кто-то гарантированно его дёргает ровно раз в 16,6 мс. А если не дёргает, то не работает.

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

… меня например больше всего поразил вот этот фрагмент:
Почему бы при каждом вызове обработчика не копировать больше данных? Это была справедливая критика, но реализация такого поведения повлекла бы глубокие изменения, на которые я не хотел идти, поэтому продолжил поиски первопричины. Я нырнул в исходный код Android

Что это за такие глубокие изменения, что вместо «увеличить буфер под данные в 2 раза» (например) быстрее вышло проанализировать исходники Андроида?
… разве что задачей на самом деле стояло написать отчет «почему это не моя вина, и почему я ничего у себя менять не буду»

Потому что меняя что-то тут, нужно будет протестировать все остальные девайсы и все остальные оси на предмет "оно всё ещё работает как надо", а если нет — это очень дорогие изменения.
Либо ставить костыль "если этот вендор на этой версии андроида, тогда".

Это совокупность плохого дизайна и накопленной сложности разработки (технический долг).
Код написано исходя из текущих ожиданий, но никто не гарантирует что завтра Google не придет и не скажет: «теперь мы делаем вызов каждые 20 мс» и все равно придется переписывать приложение под новые версии, или «теперь мы делаем вызов каждую 1 мс» и это вызовет другие проблемы.

Разумеется, гарантий нет, но риски никто не отменял. Лучше потратить день и докопаться, чем быстренько починить и потом разгребать. И гугл скажет про то, что будет в следующей версии (для этого есть change notes между версиями), а я говорил про риск поломать сейчас в другом месте.

Можно размер буфера сделать динамическим, в случае если аудиобуфер опустошается быстрее заполнения — динамически подстраивать размер выгрузки.
IMO Это будет чуть более future-proof решение чем полагаться на магические константы.

Кстати динамический буфер может усугубить проблему — выделение блока памяти и заполнение делаются не мгновенно. Если нет жёсткого лимита ОЗУ, кмк надёжнее выделить нужное количество буферов достаточного объёма.
Вы думаете звук или видео в любом другом приложении на любой другой платформе работают как-то иначе? Нет. И тут ничего плохого нет.
(upd: сорри, промахнулся уровнем комментария)
качественное видео на самом деле жрет ресурсы не хуже многих динамичных игр

Не жрёт. Пруф очень простой — на iPad можно часами напролёт смотреть видео в высоком разрешении с Youtube от батарейки.

На пользовательских устройствах его декодирует аппаратный декодер и пишет кадры напрямую в память видеокарты. Это крошечный ASIC с потреблением в доли Вт, который, как правило, лицензируют у ARM вместе с GPU.
Речь о том, что проблемы в принципе бы не было, если бы афтар приложения не копировал тупо по одному кадру равномерно с частотой воспроизведения, а просто поддерживал бы принимающие буфера на стороне устройства более-менее полными, копируя пореже по несколько кадров.
Причём афтар статьи сам об этом пишет, но архитектура кода не позволяет.
А ведь это ключевое приложение многомиллиардной корпорации, с зарплатами разрабов по 200+.

С ума сойти, все приложения компаний с капитализацией > 10 триллионов работают только потому, что вокруг триллионы кусочков кремния правильно делают +1 после выполнения инструкции INC на десятках разных архитектур и десятках тысяч разных моделей этих кусочков кремния. И делают это уже последние лет 50.

Что-то как-то мимо. Любая функция отложенного выполнения, будь то sleep, set_interval или set_timeout, никогда не гарантирует, что выполнение будет отложено ровно на указанное количество милли/наносекунд. Это всегда остается на совести планировщика/ос и сильно зависит от текущей нагрузки. Полагаться на определённое поведение очень самонадеянно, в чем и убедились герои статьи.

Ага, не гарантирует. И это "не гарантирует" кое-как компенсировать научились. Но нормально компенсировать можно только однократную небольшую проблему, а не крупную повторяющуюся.


Иными словами, есть некоторая разница между запозданием пробуждения потока на 1 миллисекунду (что нормально) и постоянным лагом в 40мс.

Вне систем реального времени никто не гарантирует, что любой системный вызов будет завершён в разумные сроки (я как сисадмин с этим постоянно сталкиваюсь в районе дискового IO, у которого могут случаться "чудеса" на scsi шине с участием умирающих дисков).


Таким образом, полагаться на то, что sleep закончится за 16 мс ровно так же наивно, как и полагаться, что ioctl для swap buffers в видеоадаптере будет выполнен за vblank время. А, вдруг, он 300мс займёт? А потом ещё раз. И ещё раз.


А нужно 60 fps. И что делать? Тут ситуация такая же.

Примерно поэтому, производитель ТВ приставки так подстраивался под нетфликс, и не выпускал пока не заработало. Никто ничего не гарантирует, и если что, надо было бы наращивать ресурсы, а не ПО.

На самом деле это очень распространненый паттерн работы плейеров :( он настолько древний, что сказать кто и когда его написал первый раз невозможно. Но он упорно копируется из плейера в плейер. Особенно подвержены этому плейеры в различных приставках, ТВ и мобильных устройствах.

НЛО прилетело и опубликовало эту надпись здесь

При любой периодической задаче нужно делать не работу, запланированную на время запуска, а работу, которую нужную было сделать к реальному моменту срабатывания.


Например, если у вас анимация, нужно узнать текущий момент времени по сравнению с конечным и подвинуть фигуру на соответствующее место.


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


Если вам нужно подгружать кадры в кодек, нужно подгрузить все что были пропущены за прошедшее время, а не один.

НЛО прилетело и опубликовало эту надпись здесь

Конкретно для озвученных таймингов (55мс ожидания и 16,6мс на кадр) подобная "подстройка" приведёт к пропуску трёх кадров из каждый четырёх. Ну да, формально видео будет показываться, но мне почему-то кажется, что 15фпс вместо 60 — не то что нужно пользователю.


Когда требуется показывать 60 кадров в секунду — лишняя задержка в 40мс является недопустимой и не поддаётся никакой компенсации.

Такое было бы возможно, если бы кодек от задержки данных не останавливался, а продолжал отмерять виртуальные секунды и показывал только те кадры, которые успели доставиться. Однако в реальности при задержке данных (например, проседание сети), кодек останавливается на том месте, где у него было недостаточно данных и продолжает воспроизведение когда эти данные до него доходят.


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

Мне сложно понять по описанию где именно у них была проблема, может быть буферы и правда бы всё исправили. Но там в любом случае обязан быть хотя бы один поток, который просыпается на каждый кадр. И если ОС "добавит" задержку в него — всё поломается в любом случае...

Что это за такие глубокие изменения, что вместо «увеличить буфер под данные в 2 раза» (например) быстрее вышло проанализировать исходники Андроида?

Ну допустим что в приложении заполнение буфера делается некой библиотекой, общей для всех Android устройств. Каковы дальнейшие действия?
а) увеличить буфер в апстриме либы.
б) увеличить буфер после проверки некоего флага в апстриме либы
в) создать форк либы под это устройство
Первые два варианта мало того что являются костылем так еще требуют мегатонны согласований(Netflix то не маленькая) окончательно хороня надежды вписаться в дедлайны релиза, третий вариант сам по себе редкое дерьмище в плане последующей поддержки этой либы.

А что в итоге?
В сорцах Android есть баг, ок.
Из оригинальной статьи не понятно:


  1. Код Netflix нативный или на Java/Kotlin?
  2. Какое API используется для декодирования: штатный MediaCodec из Android SDK/NDK?
    2.1 Если да, то почему бы не пихать данные в декодеры с максимальной скоростью? (синхронизация ведь все-равно произойдет на стороне рендеров)
  3. Как была решена проблема?
    3.1 Увеличением размера буферов
    3.2 Использованием других примитивов синхронизации (а какие были до этого?)
    3.3 Перепрошивкой всех приставок более новой версией ОС?
    ...

Это классическая ситуация, когда в прейбечном пэйплайне одна из веток (аудио/видео) подпирает другую.
Собсвенно для решения подобных проблем и делают асинхронные очереди по каждой ветке.
Забавно, что автору было дешевле изучить код AOSP, чем адаптировать свой код к суровой реальности.

По моему очевидно что они пофиксили создание тредов чтобы проблемный не становился бэкграунд тредом и шедулился нормально.

Нет, неочевидно.
Непонятно они пересобирали ядро на 5й версии или пересобирали все приставки на 6й.
Чувак молодец, что нашел известный баг, но говорить «нетфликс не будет работать на 5й версии» (но другими словами) тоже неправильно.
Все, кто длительное время разрабатывает под андроид, встречали разные баги ядра, от которых никуда не деться, но никто не говорил что «ну, живите с этим».

Но это не фикс для плейера. Это просто воркэраунд позволяющий скрыть проблему.

Почему это? Если перефразировать — они обнаружили что поток создавался с неправильным приоритетом и стали создавать его справильным приоритетом — так лучше выглядит?
Почему это?

Потому, что синхронизация плейбэчных мультимедиа графов должа происходить в конечной точке — аудио/видео рендере, а не где-то по пути перед декодером.
Так работают медиафреймворки DirectShow/GStreamer/OpenMAX и в них нет таких детских болячек, как в сабжевой статье.


Разумеется, если мы пишем аудио рендер, например на Qt, с использованием QAudioOutput, то должны повысить приоритет потоку, разгребающему входную очередь до QThread::TimeCriticalPriority. В противном случае получим заикания.


У нетфликс своеобразное решение, они подпирают аудио/видео ветки посередине.
При том, что потом будет еще один подпор на рендере (скорость работы DMA для звукового ЦАП никто под нас подстраивать не будет, оно потребляет данные монотонно со скоростью штатного кварцевого генератора).

Как была решена проблема?

В комментариях к оригинальной статье автор ответил, что Интегратор бэк-портировал багфикс андроида (вышедший для Marshmallow) под Lollipop (под которым работал телевизор). Я так понял, что приложение нефликса вообще менять не пришлось.


The integrator back-ported the patch (linked above) to L. It is literally only a few lines so this was straightforward. Some of the hardest problems take 1 or 2 lines to fix :)
Some of the hardest problems take 1 or 2 lines to fix

Как же это знакомо. Дни (и ночи) дебага с периодическими комментариями «да как так то бл...» заканчиваются фиксом в одну-две строчки.

Интегратор бэк-портировал багфикс андроида

Получается какая-то антиреклама.
Netflix выявил у себя архитектурную проблему и осознанно отказался фиксить.

Да, это очень странное решение. Получается только один производитель справился с багом, а еще несколько тысяч устройств с ним и живет.
Баг решили на конкретном устройстве с конкретной версией прошивки. А на сотнях тысяч устройств он так и останется т.к. они уже не обновляются и соотв. Netflix на них может так же лагать. И им всё равно придётся делать костыли.
А ещё можно починить такой баг, но у десятков/сотен приложений поломаются костыли которые на него опирались.
Классика: работает, не трогай.

Подобная "архитектурная проблема" есть у любого аудио- или видеопроигрывателя, а также в большинстве игр.


Всё-таки это ответственность ОС — следить чтобы те приложения, с которыми работает пользователь, получали больше процессорного времени.

Подобная "архитектурная проблема" есть у любого аудио- или видеопроигрывателя

Вы не правы. В комментарии выше описал почему

Рядовые программисты: красивые абстракции, виртуальные машины и прочие стремления к идеальному коду.
Самый Главный Отдел: так, 60FPS, некая магическая константа «15мс», здесь гвоздями прибьём, таак падажжи.
Звучит смешно, но удели они достаточное внимание кешированию нескольких кадров и куче подобных не всплывших упрощений, они могли бы и не успеть выйти на рынок.
Кхм, а если видеофайл с удвоенной скоростью, и телек на 120 кадров — тогда как?
НЛО прилетело и опубликовало эту надпись здесь

Похоже, что у нетфликса контент предзакодирован специально для их плеера(ов).
В результате декодеры работают с идеальными входными данными и плеерописателям можно "не напрягаться"

Ну это наверное самая большая проблема всех стриминговых сервисов :( приходится делать стримы под конкретные плейеры. И это реальная боль.

Так у них нет сторонних плееров. У них свое приложение под все распространённые программные платформы.

Я имел ввиду общую проблему всех стриминговых сервисов. Свой плейер решает некоторое количество проблем, но добавляет другие. Только просто протестировать плейер на разношерстной братии теликов, боксов, писишек и тп требует очень больших усилилий и ресурсов.

Конечно это не просто, но для такой большой компании не думаю что сложная задача.


Я подробностей не знаю, но думаю что у них есть сертификация клиентского оборудования и так далее (это видно из переведенной статьи). Просто так в рандомный телик ее не запихнут. Айфоны-айпады протестировать просто — их мало. Основная проблема, думаю, с разношерстными андроидами.


Сейчас работаю в телекоме, как раз в IPTV, и у нас есть в STBшке (внутре у ней андроед, но пользователям не доступен, только оболочка) приложуха Netflix. И есть тестовый аккаунт в Netflix для проверки и прочего. При случае узнаю у причастных как ее туда пропихивали...

Подобный баг устранили на OnePlus3T в приложении ivi. Через несколько секунд видео начинало сильно «тормозить». Методом проб и ошибок вычислил, что если тыкать по экрану пальцем, то видео не лагало. Корень проблемы был где-то в прошивке девайса, т.к. при обновлении прошивки баг исчезал. Но у нас все еще было несколько тысяч пользователей с этим устройством и не обновленной прошивкой, и они о себе напоминали в маркете. Решение было ужасным, но рабочим: для данного девайса во время воспроизведения видео добавили нагрузку процессора раз в несколько секунд.
	public static void spinCpu(final long spinMs) {
		final long startMs = System.currentTimeMillis();
		int count = 0;
		while (System.currentTimeMillis() - startMs < spinMs) {
			count++;
		}
	}

Проблема решена, но какова цена :)
НЛО прилетело и опубликовало эту надпись здесь
Девайс начинал тротлить при не активности пользователя в течение нескольких секунд. Даже при включенном экране и проигрывающемся видео.

WakeLock'и тоже не спасали?

WakeLock-и были, и система не засыпала, а также не выключала экран. Но процессор начинала тротлить через несколько секунд.

Биткоины фоном помайнили?

Я не знаю как там было реализовано в Android, но в древней винде емнип можно было 1) создать очередь воспроизведения из нескольких блоков (когда буфер в одном блоке заканчивается винда переходит к следующему) и 2) там можно было задать коллбэк который дергался ОС когда такое случалось. Двух блоков часто хватало чтобы пока воспроизводится один обработчик заполнил другой, но поскольку воспроизведение тогда часто шло с медленного IO (какого-нибудь CD-ROM например) то буфер можно было сделать и побольше. И никакой нужды в том антипаттерне который описал автор статьи. Сомневаюсь что дизайн у Android хуже чем дизайн древнего WinAPI. Присоединяюсь к тем кто поражается тому что людям проще полезть в исходники ОС чем написать нормально свой собственный код так чтобы он не закладывался на мягко говоря не гарантированные вещи.

В Android всё точно так же: с версии 2.3 поддерживается OpenSL ES, где можно создавать очередь из буфферов, в более поздних версиях появились новые API. Просто не нужно говнокодить как в Netfilx. Я писал систему потокового вещания поверх RTP, и не было никаких проблем ни в Jelly Bean, ни в Marshmallow.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории