Android: обрезать видео из MediaCode c в SurfaceView - PullRequest
1 голос
/ 21 апреля 2020

У меня проблема в следующем:

У меня есть пользовательская плата с разрешением экрана 900x500 пикселей. На плате работает Android 8.0.

Я получаю видеопоток (с android автоматически, когда к плате подключен сотовый телефон) с разрешением 1280x720, но с содержимым видео моего экрана размером 900x500.

В основном, если вы видите полное видео, внутри большего кадра появляется меньшее изображение с черными полями сбоку (1280-900) / 2 = 190 и (720-500) / 2 = 110, как на следующем изображении:

Пример изображения видеокадра

(здесь у меня было изображение до go, но так как это мой первый пост, мне не разрешено, я нарисую его с помощью ascii.) (РЕДАКТИРОВАТЬ: понял, что ссылка создана для прикрепленного изображения, но я оставлю ascii на всякий случай))

                                    1280
****************************************************************************
*FULL VIDEO                           |                                    *
*                                    110                                   *
*                                     |                                    *
*            **************************************************            *
*            *                       900                      *            *
*            *                                                *            *
*            *                                                *            *
*            *                                                *            *
*            *                                                *            *
*            *                                                *            *
*----190-----*                     CONTENT                500 *----190-----* 720
*            *                                                *            *
*            *                                                *            *
*            *                                                *            *
*            *                                                *            *
*            *                                                *            *
*            **************************************************            *
*                                     |                                    *
*                                    110                                   *
*                                     |                                    *
****************************************************************************

Я использую MediaCode c для декодирования видеопотока и SurfaceView для рендеринга видео на экран (также требуются сенсорные события).

При этой настройке полное видео 1280x720 масштабируется до экрана 900x500, поэтому все контент виден на экране (т.е. весь контент 1280x720 вызывается до 900x500). Что мне на самом деле нужно, так это обрезать черные поля сбоку и использовать внутри только изображения 900x500.

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

ПРИМЕЧАНИЕ. Чтобы упростить объяснение и сделать фрагменты кода более понятными, я использую жестко запрограммированные числа с разрешениями, описанными выше, код на самом деле настроен на разные разрешения.

Вещи, которые я пробовал:

  1. Используя метод SurfaceView setLayoutParams и передать ему макет с полями:

фрагмент кода

ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) mSurfaceView.getLayoutParams();
layoutParams.width = 900;
layoutParams.height = 500;
layoutParams.setMargins(190, 110, 190, 110);
mSurfaceView.setLayoutParams(layoutParams);
mSurfaceView.requestLayout();

This конечно не сработало. Параметр компоновки относится к самому SurfaceView, поэтому при вызове setMargins они применяются к SurfaceView, уменьшая его: 900 - 190 * 2 = 520 и 500 - 110 * 2 = 280. В итоге я получил поверхность 520x280. (даже экран дырки не используется) и полное видео уменьшено до этого размера. Таким образом, в конце не было обрезано ни одного поля самого видео.

Я подумал, что нужно настроить вид поверхности шириной и высотой 1280 и 720 соответственно (SurfaceView тертый, чем сам экран), а затем кадрирование SurfaceView с помощью setMargins по размеру экрана. Это в основном то, что мне нужно.

ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) mSurfaceView.getLayoutParams();
layoutParams.width = 1280;
layoutParams.height = 720;
layoutParams.setMargins(190, 110, 190, 110);
mSurfaceView.setLayoutParams(layoutParams);
mSurfaceView.requestLayout();

Это тоже не сработало, потому что ширина и высота больше, чем размер экрана, Android не делает его 1280x720, а скорее Максимальный размер, который он может (размер экрана), поэтому я получил тот же результат, что и раньше.

Использование метода SurfaceView setScaleX и setScaleY

Как я уже говорил, видео автоматически уменьшается при рендеринге на поверхность, поэтому я попытался увеличить его. Ширина масштабируется от 1280 до 900, чтобы соответствовать поверхности, поэтому я уменьшил ее до 1280 (т.е. масштабировал ее до 1280/900 = 1.42222 ...). То же самое для высоты.

ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) mSurfaceView.getLayoutParams();
layoutParams.width = 900;
layoutParams.height = 500;
mSurfaceView.setLayoutParams(layoutParams);
mSurfaceView.setScaleX(1.44f);
mSurfaceView.setScaleY(1.42f);
mSurfaceView.requestLayout();

Это, конечно, был самый близкий подход к получению, где я хотел. Тем не менее, видео испортилось, особенно его цвета. Основная причина, я полагаю, заключается в том, что, например, 1280/900 имеет бесконечные десятичные дроби, поэтому тип масштабирования не выглядит очень хорошим. действительно звучит хорошо с самого начала, потому что информация может быть потеряна. Если масштабирование могло бы быть выполнено с лучшими числами (например, масштабирование от 1000 до 500, а затем обратно до 1000), это могло бы иметь лучший результат, но все же не очень хороший подход, потому что я хочу, чтобы это работало с различными разрешениями.

MediaCode c масштабирование по размеру и масштабирование по кадрированию

На стороне MediaCode c я пробовал оба режима масштабирования:

mCodec.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);

and

mCodec.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);

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

Кажется, что оно было бы хорошо, если бы в MediaCode c был еще один вариант, например VIDEO_SCALING_MODE_NO_SCALING, но нет ... только два варианта выше.

ПРИМЕЧАНИЕ. Во время тестирования я создал простое приложение для проверки проще, где у меня будет SurfaceView, но вместо того, чтобы использовать видео, я просто использовал изображение 1280x720 и нарисовал его с помощью метода SurfaceView.draw. Это привело к другому результату, так как изображение не масштабировалось, поэтому рисовался только контент, который помещался на экране (первые 900 пикселей по высоте и первые 500 пикселей по высоте). Это было бы шагом вперед, если бы я мог сделать то же самое с MediaCode c, потому что тогда мне просто нужно было бы центрировать изображение, а поля видео были бы опущены.

Работа с MediaCode c inputBuffer.

Еще одна вещь, которую я попытался, - это изменить видео из буферов данных, которые MediaCode c декодирует. Идея состояла в том, чтобы взять inputBuffer, преобразовать его в растровое изображение, удалить поля растрового изображения и затем преобразовать растровое изображение обратно в ByteBuffer.

У меня были некоторые проблемы при реализации этого подхода при преобразовании inputBuffer. в растровое изображение. Я начал читать об этом и о том, как медиа-код c использует YUV, а не ARGB, но это будет зависеть от источника и типа YUV.

В итоге я отбросил эту идею, прежде чем получил какие-либо результаты, главным образом потому, что казалось, что вам нужно знать, с каким типом YUV вы работаете, и если оно изменится, решение не будет работать. Кроме того, это казалось, что это не будет иметь лучшую производительность, так как вы будете выполнять процесс ByteBuffer -> Bitmap -> ByteBuffer для каждого кадра.

В любом случае я пробовал еще несколько вещей, но я не хочу чтобы сделать это дольше, чем это уже есть. Большинство вещей, которые я пробовал, были в большей степени связаны с первыми двумя вещами, которые я упомянул: работа с SurfaceView.

Есть идеи, как мне решить эту проблему?

ПРИМЕЧАНИЕ. Я могу сделать любое необходимое изменения, такие как, например, изменение SurfaceView на TextureView. Также я работаю с пользовательской платой и имею полный контроль над AOSP, а не только над приложением. На самом деле приложение находится внутри AOSP, поэтому я могу использовать любые скрытые методы или даже изменить AOSP. Однако эта проблема, похоже, ни в чем не нуждается, и все это может быть решено в приложении любым обычным методом SDK.

Спасибо и извините за длинный вопрос! Это мой первый: P

...