Звук

Какое-то время я думал о том, в каком виде писать звук. Очевидно, что вместе с видео в MP4, намного удобней. Но туда только AAC.
Отдельно писать WAV — качество выше.

На данный момент я пришел к мнению, что для качества правильней иметь отдельный рекордер. Тогда нет зависимости от расстояния, на котором находится объект съемки. Поэтому для чернового звука подойдет и AAC. Будем писать звук вместе с видео в контейнер MP4.

В изучении вопроса записи звука мне очень помогла статья:
https://startandroid.ru/ru/uroki/vse-uroki-spiskom/247-urok-130-media-zapis-zvuka-s-pomoschju-audiorecorder.html

Я сейчас пишу заметку и параллельно пытаюсь понять, что нам нужно и что нам не нужно. Учитывая, что сохранение видео в MP4 мы смогли сделать асинхронно, то это же мы должны проделать с аудио.

Основные параметры/настройки для записи аудио:

1. Источник звука. Здесь мы не будем выпендриваться и возьмем источником микрофон. Сколько я понимаю, это всегда будет тот микрофон, который на данный момент считается активным. Внешний, значит внешний. Встроенный, значит встроенный.

2. Частота дискретизации (Sample Rate, как часто считывается звук). Учитывая, что мы не планируем записывать звук низкого качества (пока-что), то возьмем два стандартных значения: 44100 и 48000 Гц. Это значит, что столько число раз в секунду наша система считывает данные с микрофона

3. Разрядность. Стандарт 16 бит. Это 65536 уровней громкости. Этот параметр вроде не меняется, и нужен нам для подсчета других значений.

4. Количество каналов. Вот здесь на данном этапе я выберу 2, потому что в моем телефоне два микрофона, и он пишет отличное стерео. Сони! Не хухры-мухры.

5. Количество информации в секунду в битах. Формула выглядит так: Частота_дискретизации*разрядность*количество_каналов. Возьмем 44100 как самый популярный стандарт за основу и получим: 44100*16*2 = 1411200 б/с = (делим на 8, 1Б=8б) 176400Б/с = (делим на 1024, 1КБ = 1024Б) 172,266 Кб /с. Т.е. минута аудио будет весить 172,266*60 = 10335,96 = (делим на 1024, 1МБ = 1024КБ) ~= 10МБ.
Я так прикинул, что час будет весить всего 620МБ, и в реале мы могли бы писать по максимуму. Но ACC позволяет писать максимум 541696 б/с (529 килобит). Обычно для незаметной потери качества достаточно 320 килобит (327680 бит). Т.е. минута аудио у нас будет весить примерно 327680/8/1024*60/1024 = 2,34МБ, час — 140,63МБ.

Все эти расчеты нам нужны, чтобы правильно синхронизировать звук с видео.

Теперь нам надо было бы изучить, что и как ждет от нас MediaCodec, а за ним MediaMuxer. Запись звука мы будем пытаться производить в отдельной ветке, там же кодировать и потом отправлять в MediaMuxer. Сколько я понял, MediaMuxer’у все равно, в каком порядке он будет получать информацию от кодеков видео и аудио. Мы постараемся приравнять размеры буферов таким образом, чтобы у видео и у аудио был буфер размером/длительностью в один кадр видео.

Ладно, перемещаемся в параметры MediaFormat и MediaCodec.
Ииии…. Прошло 28 часов. Я отключил все, что связанно с видео, и боролся за то, чтобы файл MP4 играл просто аудио. Долго я понимал это, смотрел примеры, читал документацию. Конечно же не все 28 часов.

В результате мы имеем звук, который снимаем с микрофона через AudioRecord, еще один MediaCodec, который кодирует звук в ACC и отправляет в MediaMuxer. Как и с видео, MediaCodec мы пытаемся организовать в асинхронном режиме, с которым помогла разобраться официальная документация.

А именно с onInputBufferAvailable и onOutputBufferAvailable. Проблема понимания была в том, что в работе с видео используется только исходящий буфер (onOutputBufferAvailable), т.е. информация которую выдает кодек. Входящая информация попадает кодеку автоматически через Surface.
Со звуком все хитрее. Он не имеет никаких Surface, поэтому информацию кодеку надо передавать кусками (буферами). Сначала я был уверен в том, что если я отправлю информацию кодеку с помощью queueInputBuffer, то это вызовет в кодеке onInputBufferAvailable, и я смогу там чего-то куда-то передать. Оказалось, что onInputBufferAvailable срабатывает каждый раз, когда у кодека освобождается входящий буфер (один их четырех). Он пустой (вот тут я не уверен) и предназначен для заполнения его информацией и скармливания кодеку через queueInputBuffer. Но я на всякий случай (потому что там не уверен) его еще раз отчищаю. Вдруг он не пустой, а просто обработанный (что очень вероятно). И я вам скажу, что после этого осознания, понимание асинхронной работы кодека становится вполне понятной. На просторах интернета я не нашел примера асинхронной работы, везде через synchronize.
Поэтому считаю это для себя прорывом.

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

Далее я вернул весь функционал, связанный с видео, и… Столкнулся с другой проблемой — добавление треков в MedaMuxer, его старт и остановка. Проблема заключалась в том, что я не мог запускать MediaMuxer пока в него не были добавлены два трека: аудио и видео. Т.к. трэк добавляется в onOutputFormatChanged, и у каждого трэка свой кодек. Я добавил две переменных, которые сигнализировали о состоянии добавления трэка. Как только трэк добавлялся в MedaMuxer, каждая из них становилась в значение true. В onOutputFormatChanged видео кодека я запускаю отдельную функцию muxerStart, в которой работает цикл, проверяющий состояние этих двух переменных. Как только обе true, функция запускает MediaMuxer и выходит из цикла. Запускаю функцию с циклом в onOutputFormatChanged видео потому, что видео при подготовке имеет бóльшую задержку, и первым определенно добавится аудио. Если аудио запустит цикл для MediaMuxer, то цикл будет работать дольше, а так аудио уже давно добавилось, и мы ждем видео. Можно сказать в этом случае цикл почти не работает, он сразу имеет обе переменные как true.

Та же проблема с остановкой записи. Мы можем оставить запись только после того, как отправим для каждого трэка по пустому буферу, это означает, что трэк закончился. Для видео, как и было, через signalEndOfInputStream, потому что Surface не может закончится. А для аудио просто прерываем прослушивание микрофона и отправляем пустой буфер. Тут же превращаем обе переменные в false (кажду в своем блоке и в свое время) и запускаем функцию muxerStop с таким же циклом, который ждет, пока обе переменные станут false. После этого MediaMuxer останавливается.

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

Еще я хочу разобраться с размером буфера. Его величина имеет большое значение при записи аудио. В данный момент не выглядит, чтобы MediaMuxer получал данные аудио с той же частотой кадров, что и видео, или хотя бы кратно. Хочется проверить разные величины, возможно это повлияет на плавность видео. Потому что до добавления звука, видео было плавнее.

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

Хорошая статья, объясняющая основные понятия цифрового звука:
http://danalex.ru/sample_rate_bit_depth/

Статья про качество на Wikipedia:
https://ru.wikipedia.org/wiki/Кодирование_звуковой_информации

Калькулятор размера аудио файла для проверки себя:
https://www.colincrawley.com/audio-file-size-calculator/

Пример приложения, работающего с камерой через MediaCodec:
https://github.com/saki4510t/AudioVideoRecordingSample

PS. После того, как понял, что мой телефон способен писать отличный звук в WAV, подумываю все же добавить возможность это делать в отдельный файл. Допускаю, что из-за отсутствия сжатия (исключаем один кодек) съемка будет работать быстрее. В этом случае вызовом можно считать помощь в синхронизации двух файлов. Обычно это три хлопка на камеру или хлопок хлопушки. В нашем случае это должен быть звук, щелчок или вибрация (оказалось, что она отлично слышна при записи) в аудио и какие-то отдельные мигающие кадры в видео.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.