В одной игре мне понадобилось сделать проигрывание видео. Для реализации задачи, я использовал связку Ogg/Theora/Vorbis. Во-первых, потому что это бесплатные форматы, имеющие открытые библиотеки, а во-вторых, потому что их использование дает широкие возможности по работе с видео. Например легко можно создавать видео-материалы и проигрывать видео на полигонах моделей.
Но речь пойдет не о том, как я это делал. Думаю документации, примеров и статей по этому вопросу написано достаточно. Я буду говорить про чтение данных из файла и почему это делать нужно асинхронно.
После первичной реализации плеера видео в игре и оптимизации через пиксельные шейдеры, игра проигрывала HD видео на моей машине более чем с 1200fps (кадров в секунду). Первая опасность, которая в этом кроется и о которой я хочу упомянуть - это высокий fps. Дело в том, что такая частота отрисовки может сжечь видеокарту при длительном использовании. У меня экран периодически начинал моргать, появлялись секундные задержки, а один раз весь экран покрылся разноцветными пикселями. Поэтому частоту отрисовки нужно ограничивать, допустим 512-я кадрами в секунду. Для PlayStation3 есть даже требование, чтобы fps не превышал 60.
Для примера я покажу результаты проигрывания видео из игры Need for Speed: Most Wanted.
Первичная реализация была однопоточной и делала следующее:
- Если нужен новый кадр, то считываем из файла кусок данных
- Пытаемся из него сделать кадр. Если данных не достаточно, то читаем еще данные из файла
- Рисуем последний построенный кадр
Поскольку видео файл может быть большим, то он не грузится целиком в память, а подгружается по мере необходимости. Это минимизирует начальную задержку при старте видео и не сильно нагружает память. Пункты 1 и 2 выполняются с частотой видео, в то время как пункт 3 выполняется в каждом кадре.
На глаз - видео играется прекрасно, и fps высокий. На этом месте можно было бы остановиться, но я продолжил профилировать и получил следующую картину:
Видим, что профайлер показывает как 4.5ms-5ms (миллисекунды) тратится на обычный кадр, но периодически возникает задержка до 30ms, когда данные считываются из файла синхронно с рисованием видео в главном потоке игры. Это крайне плохой результат. Нестабильный fps в играх может создавать неприятные ощущения пользователя, причем тот даже не поймет в чем дело.
Правильное решение очевидно - нужно стримить данные из файла в отдельном потоке. Этим очень часто пренебрегают считая, что “и так сойдет”, однако реализовать подобное не так уж сложно. В моем случае это не потребовало реорганизации кода, только вынесения функции формирования данных кадра в отдельный поток. Эта простая реорганизация кода с тестированием заняла 2 часа. В результате профайлер показал, что на кадр тратится примерно 4.0ms (смотри картинки ниже) с очень маленьким разбросом. Это говорит о стабильности алгоритма и реализации.
Вместо вывода:
Времена однопоточного программирования прошли навсегда. Не пытайтесь обманывать себя, даже если что-то выглядит нормально при однопоточной реализации, часто может быть значительно улучшено при разнесении функциональности в разные потоки. Если речь идет о чтении данных из файла, то это вообще обязательно даже для бизнес-приложений. Не нужно заставлять пользователя смотреть на то, как программа что-то загружает. Стремитесь к комфорту пользователя.