- MediaCodec или понимаем как хотим
- Что такое MediaCodec?
- Зачем нужен MediaCodec?
- Как кодировать?
- Подготовка
- Цикл кодирования
- Освобождение ресурсов
- Что дальше?
- Как декодировать?
- Конфигурация
- Декодирование сэмпла
- Цикл декодирования
- Programmer Group
- Android Media Codec Hard Decodes AAC Audio Files (Real-time AAC Audio Frames) and Plays
- Packaging AudioTrack
- AAC decoder
- Read aac files
- epilogue
- Android MediaCodec Encode and Decode In Asynchronous Mode
- 2 Answers 2
- Android Camera2 API от чайника, часть 3. Media Codec и стрим видео по UDP
- Что нам для этого нужно?
- И закончим прицеплением Media Codec
- Теперь пойдём искать нужный адрес на компьютере
- Как же так?
MediaCodec или понимаем как хотим
С выходом Android 4.3 (API 18), Google привнесла долгожданный компонент под названием MediaCodec. Класс был открыт публике с выходом API 16, но для нормального использования и поддержки в Android системе требуется минимальный уровень API 18.
Материал рассчитан на опытного Android разработчика. Я попробую объяснить и показать примеры кодирования видео на лету с использованием Surface как входа и выхода потока данных. Если интересно, прошу под кат.
Что такое MediaCodec?
MediaCodec class can be used to access low-level media codec, i.e. encoder/decoder components.
MediaCodec класс может быть использован для доступа к низко-уровневому медиа кодеку, т.е. кодеру/декодеру
В принципе это кодер или декодер который манипулирует буферами данных. Если мы будем смотреть на формат H264, он же video/avc, то по сути буфер будет хранить NAL кадры и т.д.
Зачем нужен MediaCodec?
В большинстве случаев Android разработчики будут использовать VideoView, MediaPlayer с SurfaceView и этого вполне достаточно. Но как только речь зайдет о создании видео потока для дальнейшей передачи куда-либо, у нас не так много вариантов. Тут нам поможет MediaCodec.
Как кодировать?
MediaCodec дает возможность создать Surface объект для принятия данных для кодера. Я разделил логику кодирвоания на три этапа: подготовка, цикл кодирования, освобождение ресурсов.
Так как процесс кодирования по сути простой цикл обработки буферов данных, нам потребуется создать отдельный поток, который будет представлять непосредственно видео кодер.
Подготовка
Прежде чем приступить к кодированию нам потребуется описать формат и конфигурацию видео на выходе, создать сам кодер и получить Surface объект для ввода данных кодеру.
Цикл кодирования
mBufferInfo и data хранят данные о кадре(ах) который по сути может быть передан по сети или записаны в файл и др. Эти же данные могут быть декодированы h264 кодеком, чтобы получить изображение.
Важно заметить, что BufferInfo класс имеет поле flags. По сути может содержать три флага: BUFFER_FLAG_CODEC_CONFIG, BUFFER_FLAG_END_OF_STREAM и BUFFER_FLAG_SYNC_FRAME.
Давайте рассмотрим каждый из флагов:
- BUFFER_FLAG_CODEC_CONFIG флаг сигнализирует о том, что буфер данных содержит конфигурацию кодека. Первый доступный буфер кодера должен содержать данный флаг. К примеру для нашего H264 это может быть csd-0, который может быть использован во время декодирования при создании видео формата кодека.
- BUFFER_FLAG_END_OF_STREAM флаг сигнализирует о том, что достигнут конец потока данных. Я не уверен на счет использования данного флага во время кодирования, т.к. мы контролируем поток данных.
- BUFFER_FLAG_SYNC_FRAME флаг сигнализирует о том, что кадр является I-Frame. Важно использовать при передачи в файл или по сети, к примеру по протоколу RTP.
Освобождение ресурсов
Тут все просто, останавливаем кодер и освобождаем системные ресурсы.
Что дальше?
Имея Surface возможны как минимум три пути ввода данных: MediaPlayer (Camera?), OpenGL, Canvas. По примеру использования рендера в SurfaceView возможно создать подобный цикл на основе нашего Surface:
Вариантов использования может быть несколько. Еще интересный момент, Surface является Parcelable, что дает возможность передачи через Binder в другие процессы, таким образом ваш кодер может находиться в одном процессе, когда рендер в другом.
Как декодировать?
Процесс декодирования схожий на процесс кодирования. Мы так же манипулируем буферами данных. Данный процесс я разделил на четыре части: конфигурация, декодирование сэмпла, цикл декодирования, освобождение ресурсов.
Я опущу детали создания класса потока, архитектура схожа на ту, которую я использовал в процессе кодирования.
Конфигурация
Процесс конфигурации критичен, иначе кодек невозможно создать и использовать для декодирования поступающих данных.
Важно заметить, мы обязаны знать на какой Surface нужно выводить изображение, так же ширину и высоту изображения. csd-0 так же важен на этом этапе, как было описанно ранее в процессе кодирования, первый буфер данных должен иметь флаг BUFFER_FLAG_CODEC_CONFIG, этот буфер и является csd-0 который необходимо передать на этапе конфигурации декодера.
Декодирование сэмпла
Как только кодированные данные доступны их необходимо передавать декодеру. Мы запрашиваем буфер декодера, как только он доступен, передаем наши данные.
Если данные были переданы по сети, к примеру RTP протокол, необходимо учесть, что сэмплы передаются последовательно, иначе возможны артифакты при выводе изображения. На данном этапе, само изображение не готово для вывода на Surface, мы просто передаем известные данные декодеру.
Цикл декодирования
Цикл декодирования включает в себя запрос буфера данных который доступен на выходе, если доступен, можно его необходимо вернуть системе и сообщить если мы желаем рендерить данный кадр на Surface.
Источник
Programmer Group
A programming skills sharing group
Android Media Codec Hard Decodes AAC Audio Files (Real-time AAC Audio Frames) and Plays
Today, I will briefly introduce how to use Android Media Codec to decode AAC audio files or real-time AAC audio frames and play them through AudioTrack. The main idea is to get the data of a frame of AAC from the file or network, and send it to the decoder for decoding and playing.
Packaging AudioTrack
AudioTrack is mainly used to play sound, but only PCM format audio stream can be played. This is mainly a simple encapsulation of AudioTrack, with some exception judgments added:
Here we briefly introduce the meanings of several variables in AudioTrack (int streamType, int sampleRateInHz, int channel Config, int audioFormat, int bufferSizeInBytes, int mode):
1.streamType: Specifies the type of flow, mainly including the following:
— STREAM_ALARM: Warning
— STREAM_MUSCI: Musical Sound
— STREAM_RING: Ring
— STREAM_SYSTEM: System Sound
— STREAM_VOCIE_CALL: Telephone Voice
Because the android system manages different sounds separately, the function of this parameter is to set the type of sound played by AudioTrack.
2.sampleRateInHz: sampling rate
3. Channel Config: Vocal Track
4.audioFormat: Sampling Accuracy
5.bufferSizeInBytes: Buffer size can be obtained through AudioTrack.getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)
6.mode: MODE_STATIC and MODE_STREAM:
— MODE_STATIC: Loading all data directly into the buffer without multiple write s is generally used in situations of small memory footprint and high latency requirements.
— MODE_STREAM: Multiple write s are required, usually for situations such as data acquisition from the network or real-time decoding. This is the case in this example.
Here is just a brief introduction, you can go online to find more detailed introduction.
AAC decoder
MediaCodec is encapsulated to decode AAC in one frame.
In fact, this is very similar to my previous use of MediaCodec to decode H264, mainly because the decoding data type is different, so the initialization is different. Another point is that when decoding H624, the decoded data is displayed directly by surface, while decoding aac is to take the decoded data out and play it by AudioTrack.
Read aac files
Here, the thread reads the aac file, obtains the aac frame data of a frame, and then sends it to the decoder to play.
There is not much here, that is, to judge the aac frame by the frame head, and intercept each frame of data into the decoder. I just made a simple judgment here by coincidence. The judgment of frame head does not necessarily satisfy all aac frame heads. We can modify it according to the actual situation.
epilogue
In fact, to separate audio frames, we can use the class MediaExtractor, but because my actual data source is from the network, demo will be a little more complex.
Источник
Android MediaCodec Encode and Decode In Asynchronous Mode
I am trying to decode a video from a file and encode it into a different format with MediaCodec in the new Asynchronous Mode supported in API Level 21 and up (Android OS 5.0 Lollipop).
There are many examples for doing this in Synchronous Mode on sites such as Big Flake, Google’s Grafika, and dozens of answers on StackOverflow, but none of them support Asynchronous mode.
I do not need to display the video during the process.
I believe that the general procedure is to read the file with a MediaExtractor as the input to a MediaCodec (decoder), allow the output of the Decoder to render into a Surface that is also the shared input into a MediaCodec (encoder), and then finally to write the Encoder output file via a MediaMuxer . The Surface is created during setup of the Encoder and shared with the Decoder.
I can Decode the video into a TextureView , but sharing the Surface with the Encoder instead of the screen has not been successful.
I setup MediaCodec.Callback() s for both of my codecs. I believe that an issues is that I do not know what to do in the Encoder’s callback’s onInputBufferAvailable() function. I do not what to (or know how to) copy data from the Surface into the Encoder — that should happen automatically (as is done on the Decoder output with codec.releaseOutputBuffer(outputBufferId, true); ). Yet, I believe that onInputBufferAvailable requires a call to codec.queueInputBuffer in order to function. I just don’t know how to set the parameters without getting data from something like a MediaExtractor as used on the Decode side.
If you have an Example that opens up a video file, decodes it, encodes it to a different resolution or format using the asynchronous MediaCodec callbacks, and then saves it as a file, please share your sample code.
=== EDIT ===
Here is a working example in synchronous mode of what I am trying to do in asynchronous mode: ExtractDecodeEditEncodeMuxTest.java: https://android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java This example is working in my application
2 Answers 2
I believe you shouldn’t need to do anything in the encoder’s onInputBufferAvailable() callback — you should not call encoder.queueInputBuffer() . Just as you never call encoder.dequeueInputBuffer() and encoder.queueInputBuffer() manually when doing Surface input encoding in synchronous mode, you shouldn’t do it in asynchronous mode either.
When you call decoder.releaseOutputBuffer(outputBufferId, true); (in both synchronous and asynchronous mode), this internally (using the Surface you provided) dequeues an input buffer from the surface, renders the output into it, and enqueues it back to the surface (to the encoder). The only difference between synchronous and asynchronous mode is in how the buffer events are exposed in the public API, but when using Surface input, it uses a different (internal) API to access the same, so synchronous vs asynchronous mode shouldn’t matter for this at all.
So as far as I know (although I haven’t tried it myself), you should just leave the onInputBufferAvailable() callback empty for the encoder.
EDIT: So, I tried doing this myself, and it’s (almost) as simple as described above.
If the encoder input surface is configured directly as output to the decoder (with no SurfaceTexture inbetween), things just work, with a synchronous decode-encode loop converted into an asynchronous one.
The issue, as far as I see it, is in awaitNewImage as in https://android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests/media/src/android/media/cts/OutputSurface.java#240. If the onFrameAvailable callback is supposed to be called on the main thread, we have an issue if the awaitNewImage call also is run on the main thread. If the onOutputBufferAvailable callbacks also are called on the main thread and you call awaitNewImage from there, we have an issue, since you’ll end up waiting for a callback (with a wait() that blocks the whole thread) that can’t be run until the current method returns.
So we need to make sure that the onFrameAvailable callbacks come on a different thread than the one that calls awaitNewImage . One pretty simple way of doing this is to create a new separate thread, that does nothing but service the onFrameAvailable callbacks. To do that, you can do e.g. this:
I hope this is enough for you to be able to solve your issue, let me know if you need me to edit one of the public examples to implement asynchronous callbacks there.
EDIT2: Also, since the GL rendering might be done from within the onOutputBufferAvailable callback, this might be a different thread than the one that set up the EGL context. So in that case, one needs to release the EGL context in the thread that set it up, like this:
And reattach it in the other thread before rendering:
EDIT3: Additionally, if the encoder and decoder callbacks are received on the same thread, the decoder onOutputBufferAvailable that does rendering can block the encoder callbacks from being delivered. If they aren’t delivered, the rendering can be blocked infinitely since the encoder don’t get the output buffers returned. This can be fixed by making sure the video decoder callbacks are received on a different thread instead, and this avoids the issue with the onFrameAvailable callback instead.
Источник
Android Camera2 API от чайника, часть 3. Media Codec и стрим видео по UDP
Итак, со съемками фоточек и записью видео при помощи Camera2 API мы вроде бы, разобрались. Осталось только научиться передавать видеопоток c Android устройства страждущим получателям извне. Конечной целью, как уже неоднократно ранее говорилось, является интеллектуализация роботелеги — ставим на неё смартфон и так сказать, превращаем обезьяну в человека. В этом нам поможет Media Codec. И конечно, новое Camera2 API.
Кому интересно, прошу под кат.
Детали о проекте с роботележкой можно найти здесь, а мы пока займемся непосредственно стримингом видео с неё (вернее с прицепленного к ней Android смартфона) на персональную электронно-вычислительную машину.
Что нам для этого нужно?
Для того чтобы передать видео поток с экрана смартфона куда-либо ещё, как известно, его (поток) сначала необходимо преобразовать в подходящий ужатый формат (передавать покадрово выйдет слишком толсто), поставить time-stamps (временные метки) и отправить в бинарном виде получателю. Который произведёт обратную операцию декодирования.
Как раз этими низкоуровневыми чёрными делами и занимается класс Media Codec с 2013 года, с даты выхода Аndroid 4.3.
Другое дело, что раньше подступиться к кодированию видео, в отличие от сегодняшнего дня было не так-то просто. Чтобы вытащить картинку с камеры надо было использовать тонны загадочного кода в котором, как в заклинаниях якутских шаманов, единственная неточность могла привести к полному краху приложения. Добавьте к этому ещё предыдущее Camera API, где вместо готовых коллбэков приходилось ручками самостоятельно писать разные synchronized, а это занятие, скажем так, не для слабых духом.
И главное, издалека смотришь на рабочий код, вроде бы в общих чертах всё ясно. Начинаешь переносить по частям в свой проект — сыпется непонятно почему. А скорректировать не получается, потому что в деталях уже разобраться тяжело.
Да и от сплошных deprecated как-то не по себе. Короче говоря, непорядок
К счастью, для непонятливых гуглостроители ввели волшебную концепцию поверхности Surface, работая с которой, можно избежать низкоуровневых деталей. Какой ценой и что при этом теряет разработчик, мне как неспециалисту понять сложно, но зато теперь мы чуть ли не буквально можем сказать: «Android, возьми эту Surface на которую отображается видео с камеры и ничего там не меняя, ну вот как есть, закодируй и отправь дальше». И самое удивительное, что это работает. А с новым Camera2 API программа и сама знает, когда данные отправлять, коллбэки ж новые появились!
Так что теперь закодировать видео — раз плюнуть. Чем мы сейчас и займёмся.
Берём код из первой статьи и как обычно выкидываем из него всё кроме кнопочек и инициализации камеры.
И закончим прицеплением Media Codec
В прошлом посте мы выводили на Surface изображение с камеры и с него же писали видео при помощи MediaRecorder. Для этого мы просто указывали оба компонента в списке Surface.
Здесь то же самое, только вместо mMediaRecorder указываем:
Получается, что-то типа:
Что такое mEncoderSurface? А это та самая Surface, с которой будет работать Media Codec. Только для начала надо их обоих инициализировать примерно таким образом.
Теперь остается прописать единственный коллбэк. Когда Media Codec вдруг ощутит, что очередные данные для дальнейшей трансляции готовы, он нас об этом известит именно через него:
Байтовый массив outDate — это настоящее сокровище. В нём уже готовые кусочки закодированного в формате H264 видеопотока, с которым мы теперь можем делать всё, что захотим.
Некоторые кусочки может быть великоваты для передачи по сети, ну да ничего, система, если надо их порубит ещё сама и отправит дальше получателю.
Но если сильно страшно, то покромсать можно самому, впихнув такой фрагмент
Но нам пока надо убедиться воочию, что данные в буфере это действительно видеопоток в формате H264. Поэтому, давайте мы их отправим в файл:
Пропишем в сетапе:
А в коллбэке где буфер:
Открываем приложение, жмем кнопку: «ВКЛЮЧИТЬ КАМЕРУ И СТРИМ». Начинается автоматически запись. Ждем немного и давим кнопку остановки.
Сохраненный файл штатно скорее всего не проиграется, поскольку формат не MP4, но если открыть его VLC плеером или сконвертить онлайн каким нибудь ONLINE CONVERT, то мы убедимся, что находимся на правильном пути. Правда изображение лежит на боку, но это поправимо.
Вообще, для каждого события записи, фотографирования или стрима, лучше, конечно, открывать каждый раз новую сессию, а старую закрывать. То есть, сначала мы включаем камеру и запускаем голое превью. Потом, если надо сделать снимок, превью закрываем и открываем превью, но уже с пристегнутым Image Reader. Если переходим на запись видео, то закрываем текущую сессию и запускаем сессию с превью и прицепленным к нему Media Recorder. Я этого не делал, чтобы не страдала наглядность кода, а вам решать, как удобнее самим.
А вот и сам код целиком.
И не забудьте про разрешения в манифесте.
Итак, мы убедились, что Media Codec работает. Но использовать его для записи видео в файл как-то бездуховно. С такой задачей гораздо лучше справится Media Recorder, да ещё и звук добавит. Поэтому файловую часть мы снова выкинем и добавим блок кода для стриминга видео в сеть по udp протоколу. Это тоже очень просто.
Сначала инициализируем UDP практически сервер.
А в том же коллбэке, где мы отправляли данные по готовности в поток для файла, отправим их теперь в виде дэйтаграмм в нашу домашнюю сеть (надеюсь она есть у всех?)
Казалось бы, но нет. Приложение при запуске скрашится. Видите-ли, системе не нравится, что в главном потоке мы отправляем всякие дэйтаграмм пакеты. Но для паники нет оснований. Во-первых мы хоть и в главном потоке, но работаем все равно асинхронно, то есть по срабатыванию коллбэка. Во-вторых отправка udp пакетов, такой же асинхронный процесс. Мы только говорим операционной системе, что неплохо было бы отправить пакетик, но, что мы всецело в этом деле полагаемся на неё. Поэтому, чтобы Android не бунтовал, то в начале программы добавим две строчки:
В общем и целом, получится следующая маленькая элегантная демонстрационная программка:
Не знаю, как у других, но на моем Red Note 7 даже видно, как скачут килобайты по нужному адресу
И таких udp сокетов можно наплодить множество, на сколько хватит пропускной способности сети. Главное, чтобы были адреса куда. Будет у вас широкоадресная рассылка.
Теперь пойдём искать нужный адрес на компьютере
Надо сказать, что не каждая компьютерная программа способна всосать и переварить видео поток формата H264 по единственному udp каналу без какой-либо дополнительной информации. Но некоторые могут. Это например крайне широко известный медиаплеер VLC. Это настолько крутая штука, что если начать описывать её возможности, то из статьи получится целая книга. Наверняка у вас она есть. Если нет, поставьте.
И судя по описанию команд для него, udp пакеты переварить этот плеер может.
Причём все эти source address и bind address, по идее и не нужны. Нужен только прослушиваемый порт.
И ещё, конечно, нужно не забыть про разрешение этот порт слушать (малварь же)
А вы знали, что Винда не дает сделать принтскрин с монитора ресурсов?
Или можно вообще брандмауэр отключить (не рекомендую)
Итак, преодолев эти тернии, запускаем VLC плеер с нашим адресом и наслаждаемся пустым экраном. Видео нет.
Как же так?
А вот так. У вас, наверно, стоит последняя версия VLC 3.08 Vetinari? Вот как раз, в этой версии udp объявлен deprecated и мало того выпилен нахрен.
Так-то логика разработчиков плеера понятна. Мало кому нужно использовать голый udp канал в наше время потому-что:
- Нормально работает только в домашней неперегруженной сети. Стоит вам выйти во внешний мир и ненумерованные дэйтаграммы начнут теряться и приходить не в том порядке, в котором их послали. А для видео декодера это очень неприятно.
- Незашифрован и легко компрометируется
Поэтому нормальные люди, конечно, используют протоколы более высокого уровня RTP и другие. То есть на пальцах — вы пишете сервер, который всё равно на низком уровне использует udp (для скорости), но параллельно обменивается управляющей информацией с клиентом кому он стримит видео. Какая там у него пропускная способность, не надо ли увеличить-уменьшить кэш для данных, какая детализация изображения оптимальна сейчас и так далее и тому подобное. Опять же звук тоже иногда нужен. А ему требуется, сами понимаете, синхронизация с видео.
Вон ребятам из Одноклассников даже пришлось свой протокол запилить для стриминга. Но у них задачи-то, конечно, гораздо более важные — рассылать видео с котиками десяткам миллионов домохозяек по всему миру. Там одним udp каналом не обойдёшься.
Но нам-то писать свой RTP сервер на андроиде как-то грустно. Наверное, можно найти даже готовый и даже бесплатный, но попробуем пока не усложнять сущностей. Просто возьмем версию VLC плеера, где udp стриминг ещё работал.
Устанавливаем вместо или рядом со старым (то есть новым VLC), как вам заблагорассудится.
Запускаем и снова видим пустой экран.
А все это потому, что мы явно не настроили использование кодека H264. Так-то VLC смог бы выбрать кодек автоматически, если бы имел дело с файлом (в настройках изначально, как раз и указан автоматический выбор). Но ему-то кидают байтовый поток по единственному каналу, а кодеков, которые VLC поддерживает десятки. Как ему разобраться, какой применить?
Поэтому устанавливаем кодек силой.
И вот теперь наслаждаемся трансляцией «живого» видео. Единственное, оно зачем-то лежит на боку, но это уже легко поправить в настройках видеоплеера.
А ещё можно просто запускать плеер из командной строки по такому ключу:
Источник