Android surface control отключить
Google Ассистент - одна из самых противоречивых функций смартфонов на Android . С одной стороны, это может быть удобно: не нужно заходить в браузер , вводить поисковой запрос самостоятельно , или запускать приложение вручную . Для этого достаточно просто сказать пару фраз .
С другой стороны, ассистент постоянно висит в фоне системы , может Вас " прослушивать ", запоминает Ваши запросы, на их основании предлагает Вам огромное количество рекламы и попросту может мешать нормальному использованию смартфона , включаясь в самые ненужные моменты.
Я сам постоянно сталкивался с его внезапным включением по свайпу или нажатию кнопки , и это сильно раздражает.
Более того, он не всегда точно распознает команды , из-за чего пользоваться им пока не очень удобно . Если есть необходимость - его можно отключить , и сделать это довольно просто.
Camera API V2
Зеленый прямоугольник это конечно весело и интригующе, но пора попробовать вывести preview с камеры на оставшейся surface.
Давайте выпишем этапы работы с камерой:
- Ожидаем получение permission-а. У нас это будет состояние WaitingStart
- Получаем инстанс camera manager-а, находим логический id (обычно их два — для back и front, а логический он потому что на современных девайсах камера может состоять из множества датчиков) нужной камеры, выбираем подходящий размер, открываем камеру, получаем cameraDevice. Состояние WaitingOpen
- Имея открытую камеру мы обратимся в обратимся за получением Surface-а для вывода изображения. Состояние WaitingSurface
- Теперь имея cameraDevice, Surface мы должны открыть сессию чтобы камера наконец начала передавать данные. Состояние WaitingSession
- Теперь мы можем захватить preview. Состояние StartingPreview
Проиллюстрируем нашу текущую схему:
MediaCodec
MediaCodec класс для низкоуровневой работы с системными кодеками, в общем виде его API это набор input/output буферов (звучит, к сожалению, проще чем работать с ним) в которые помещаются данные (сырые или закодированные зависит от режима работы encoder/decoder), а на выходе мы получаем результат.
Несмотря на то, что к качестве буферов обычно выступают ByteBuffer, для работы с видео можно использовать Surface который вернет нам MediaCodec::createInputSurface, на нем мы должны отрисовывать кадры, которое хотим записать (при таком подходе документация обещает нам ускорение кодирования за счет использования gpu).
Хорошо, теперь мы должны научиться отрисовывать уже существующие Surface-ы которое мы создали в GLSurfaceMachine на Surface от MediaCodec-а. При этом важно помнить: Surface это объект который создает consumer-ом и прочитать что-то из него в общем случаи нельзя т.е нет условного метода getBitmap/readImage/…
Мы поступим следующим образом: на основе существующего GL контекста мы создадим новый который будем иметь общую с ним память, а потому мы сможем использовать переиспользовать там id-шники текстур которые мы создали ранее. Затем используя новый GL контекст и Surface от MediaCodec-а, мы создадим EGLSurface — внеэкранный буфер на котором мы так же сможем создать наш класс OpenGLScene. Затем при каждой отрисовке кадра мы попробуем параллельно записывать кадр на файл.
EGL означает интерфейс взаимодействия OpenGL API с оконной подсистемой платформы, работу с ним мы украдем из grafika. Конвейер (EncoderHelper) с MediaCodec-ом напрямую описывать тоже не буду, приведу лишь итоговую схему взаимодействия наших компонентов:
Работа с камерой на телефоне всегда представляла для меня интерес. Как же это все устроено… И вот мне в руки попал телефон с Android'ом. Я не преминул возможностью попробовать разобраться в этом. Вот что получилось в итоге.
Рассмотрим небольшую программу, которая позволяет делать снимки.
Все операции проводятся с помощью класса Camera.
Необходимо завести переменную
и инициализировать ее
После завершения работы с камерой необходимо сделать
в противном случае камера останется заблокированной и недоступной для других приложений.
Для обычных приложений типа фотокамеры инициализацию лучше всего производить в onResume, а освобождение в onPause.
Обязательным условием при работе с камерой является создание окна предпросмотра (preview). Это окно должно являться объектом класса Surfaceи для отображения на экране подходит SurfaceView.
Объявим
Чтобы задать preview, необходимо вызвать метод setPreviewDisplay, параметром которого является объект класса SurfaceHolder.
Чтобы включить отображение preview, вызываем
Если этого не сделать, то камера не сможет делать снимки.
Собственно для того, чтобы сделать снимок, необходимо вызвать метод
- shutter — вызывается в момент получения изображения с матрицы
- raw — программе передаются для обработки raw данные (если поддерживается аппаратно)
- postview — программе передаются полностью обработанные данные (если поддерживается аппаратно)
- jpg — программе передается изображение в виде jpg. Здесь можно организовать запись изображения на карту памяти.
Тогда после вызова в обработчике нажатия на кнопку camera.autoFocus(), однократно будет вызван обработчик, в котором мы уже и примем решение об удачной фокусировке и необходимости сделать снимок.
Для работы с SurfaceHolder можно задать SurfaceHolder.Callback
surfaceHolder.addCallback();
В этом случае необходимо реализовать методы
C помощью них приложению будет сообщаться о том, что Surface успешно создано, если оно изменено или то, что оно удалено.
Размер нашего preview можно менять в процессе выполнения программы:
Для приложения камеры удобнее всего сразу задать расположение экрана как
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
В противном случае нам придется, например, в surfaceCreated проверять расположение экрана и поворачивать preview с помощью, например, camera.setDisplayOrientation(0) .
Это не очень удобно, потому что поворот экрана занимает какое-то время. В этот момент происходит вызов onPause и onResume, пересоздается Surface.
Также имеется возможность объявить обработчик Camera.PreviewCallback, с помощью которого путем реализации метода
можно получать и обрабатывать каждый кадр, отображаемый в preview.
И последний важный момент. Чаще всего получается так, что отношение сторон SurfaceView отличается от отношения сторон в preview камеры. Поэтому для того, чтобы избежать искажений изображения на экране, необходимо подкорректировать размер отображаемого окна предпросмотра.
Чуть не забыл. В манифест необходимо добавить permission
MainScreen.java
main.xml
AndroidManifest.xml
Программа отлаживалась и тестировалась на телефоне LG Optimus One P500.
«Это одна из последних статей на тему отключения различного программного хлама в MUI 12 на базе Android 10. Нам осталось отключить ещё около 5 приложений и сервисов, которые впустую тратят мобильный интернет, оперативную память и заряд батареи, после чего, я выпущу статью с финальным списком и пресетом для ADB App Control и перейду к изучению Android 11.
Как показывают отзывы читателей, многим удалось значительно улучшить работу своих смартфонов, чему я очень рад и надеюсь, что эта статья тоже окажется вам полезной».
Начнём с простого
Приложение «Smart-Divert» присутствует во многих смартфонах с двумя Sim-картами и если говорить простым языком, служит для того, чтобы в момент когда вы говорите по одной симке, а на вторую поступает входящий звонок, происходила переадресация.
Но его бессмысленность, заключается в техническом устройстве наших гаджетов, ведь в большинстве из них установлен только один радиомодуль, следовательно, он физически не может поддерживать одновременную работу двух Sim-карт. Проверьте есть ли это приложение в вашем смартфоне, воспользовавшись поиском в пункте "Все приложения". Только показ системных включить не забудьте.
Секреты MIUI 🉑 Убираем из смартфона бессмысленные приложенияКак вы видите, «Smart-Divert» постоянно находится в активном состоянии, расходуя ресурсы системы и оперативную память, которой как известно, много не бывает.
Поэтому я рекомендую отключить его, через уже знакомое вам приложение ADB App Control (если не знаете что это, ссылка на статью будет ниже). Замечу, что на всех своих смартфонах это приложение я отключил и никаких сбоев в работе не обнаружил.
Секреты MIUI 🉑 Убираем из смартфона бессмысленные приложенияПеред тем как я перейду к «вишенке на торте», небольшая предыстория: Обратился ко мне человек с проблемой плохой работы определения местоположения после одного из последних обновлений. Перепробовали всё, и местоположение Google отключали, и данные A-GPS чистили - результата ноль.
В итоге, на одном из форумов я вычитал, что проблема может крыться в приложении «LocationServices» от Qualcomm. А зайдя на своём смартфоне в «Настройки» —> Приложения —> Все приложения —> Три точки (Показать все приложения), обнаружил что оно постоянно висит в фоне и потребляет (в моём случае) 272 Мб оперативной памяти.
Секреты MIUI 🉑 Убираем из смартфона бессмысленные приложенияНачал интересоваться и выяснил, что работа GPS после отключения этого сервиса, остаётся такой же как была (подтверждение ниже).
Секреты MIUI 🉑 Убираем из смартфона бессмысленные приложенияНа всех своих смартфонах Xiaomi я его отключил, весь день пользовался навигатором, тестировал приём спутников - никаких проблем нет. В итоге проблема обратившегося человека была решена, а в добавок ко всему, я нашёл ещё одну службу, которая расходовала достаточно большой объём памяти.
Более того, после отключения (в моём случае) расход аккумулятора, заметно уменьшился и уже потом я прочёл, что статистика расхода батареи «LocationServices» входит в строку «Система Android».
Секреты MIUI 🉑 Убираем из смартфона бессмысленные приложенияМожете последовать моему примеру и отключить её на своём смартфоне через ADB App Control, тем более, любое отключённое приложение можно восстановить без проблем.
GLSurfaceView
Самый простой способ вывести что-то на экран используя OpenGL в android это класс GLSurfaceView — он автоматически создает новые поток для рисования, запуск/пауза которого происходит по методам GLSurfaceView::onResume/onPause.
Для простоты мы будем задавать нашей вьюхе соотношение 16:9
Сам процесс отрисовки вынесен в отдельный колбек — GLSurfaceView.Renderer.
Завернув его в StateMachine-у мы получим что-то вроде этого:
Давайте нарисуем диаграмму переходов:
Теперь наш код пытается что-то выводить на экран, правда пока у него это получается плохо — ни чего кроме черного экрана мы не увидим. Как не сложно догадаться дело в том, что в наши Surface-ы сейчас ни чего не попадает т.к мы пока не реализовали источники изображений. Давайте это исправим — первым делом создадим CanvasDrawable:
Теперь секцию в GLSurfaceMachine мы можем дополнить отрисовкой canvasDrawable на canvas-е которые предоставляет surface у соответствующей текстуры:
После чего увидим что-то наподобие:
Наложение нескольких Surface
Surface — фактически дескриптор области в памяти, которую нужно заполнить изображением. Скорее всего, мы получаем его пытаясь вывести что-то на экран или в файл, таким образом он работает как буфер для некоторого “процесса” который производит данные.
Чтобы создать наложение из нескольких Surface воспользуемся OpenGL.
Для этого мы создадим две квадратные external-текстуры и получим из них Surface-ы
В коде это будет выглядеть как то так:
XYZ координаты
Теперь нам нужно понять как создать и расположить текстуры, а для этого придется вспомнить как устроена координатная сетка в OpenGL: ее центр совпадает с центром сцены (окна), а границы нормированы т.е от -1 до 1.
На этой сцене мы хотим задать два прямоугольника (работа идет на плоскости поэтому все z координаты логично установлены в 0f) — красным мы обозначим тот куда будем помещать preview для камеры, а синим для анимированного drawable-а:
Выпишем наши координаты явно:
UV координаты
Достаточно ли этого? Оказывается, что нет :(
Текстура это отображение картинки на область сцены и чтобы его правильно совершить нужно указать в какое точно место точки на картинке попадут внутри этой области — для этого в OpenGL применяются UV координаты — они выходят из левого нижнего угла и имеют границы от 0 до 1 по каждой из осей.
Работает это следующим образом — каждой вершине нашей области мы зададим UV координаты и будем искать соответствующие точки на изображении, считая что там ширина и высота равны по 1.
Рассмотрим на примере — будем считать что камера отдает нам изображение в перевернутом и отраженном состоянии и при этом мы хотим показать только правую-верхнюю часть т.е взять 0.8 по широты и высоте изображения.
Тонкий момент — на данном этапе мы не знаем соотношения сторон области на экране — у нас есть только квадрат в относительных координатах, который заполнит собой всю сцену и соответственно растянется. Если бы мы делали fullscreen камеру то наши относительные размеры (2 по каждой стороне) растянулись бы до условных 1080x1920. Будем считать что размеры сцены мы зададим такие что их соотношение будет равно соотношению камеры.
Посмотрим куда перейдут координаты — правая верхняя точка нашей области (1, 1, 0) должна перейти в UV координату (0, 0), левая нижняя в (0.8f, 0.8f) и т. д
Таким образом получим соответствие XYZ и UV:
Если соотношение сторон между preview с камеры и областью на экране совпадало изначально то оно очевидным образом продолжит сохранятся т.к в нашем случаи мы просто умножили на 0.8f.
А что будет есть мы зададим значения больше 1? В зависимости от настроек которые мы передали OpenGL-у мы получим точки какой то части изображения. В нашем примере будет повторяться последняя линия по соответствующей оси и мы увидим артефакты в виде “полосок”
Итог: если мы хотим сжать/вырезать изображение сохраняя при этом позицию области на экране то UV координаты наш выбор!
Зададим координаты для наших текстур
Шейдеры
Иметь статичные XYZ и UV-координаты не очень удобно — мы например можем захотеть перемещать и масштабировать жестами наши текстуры. Чтобы их трансформировать заведем две матрицы для каждой текстуры: MVPMatrix и TexMatrix для для XYZ и UV координат соответственно.
Каждая OpenGL2 должна содержать шейдеры для того, чтобы вывести что-то на экран. Конечно, это не там тема которую можно раскрыть в одном абзаце, тем не менее в нашем случае они будут тривиальными, а потому можно быстро понять что они что они делают, без особого знания материала.
Прежде всего шейдера два — vertex и fragment.
Первый (vertex) будет обрабатывает наши вершины, а именно просто перемножать наши XYZ / UV координаты с соответствующими им матрицами и заполнять OpenGL переменную gl_Position которая как раз отвечает за финальное положение нашей текстуры на экране.
Второй (fragment) должен заполнить gl_FragColor пикселями изображения.
Итого имеем: переменные внутри vertex шейдера мы должны заполнить поля нашими данными, а именно:
- MVPMatrix ->uMVPMatrix
- TexMatrix -> uTexMatrix
- наши XYZ координаты вершины ->aPosition
- UV координаты ->aTextureCoord
vTextureCoord — нужна для проброса данных из vertex шейдера в fragment шейдер
В fragment шейдере мы берем преобразованные UV координаты и используем их для отображения пикселей изображения в области текстуры.
Ради справки укажем чем отличаются типы:
- uniform — переменная такого типа будет сохранять значения при многократном вызове, мы используем один шейдер которые вызывается последовательно для двух текстур, так что все равно будем перезаписывать при каждой отрисовки
- attribute — данные такого типа читаются из вершинного буфера, их нужно загружать при каждой отрисовки
- varying — нужны для передачи данных из vertex шейдера в fragment
Как передать параметры в шейдер? Для этого вначале нужно получить id (указатель) переменной:
Теперь по этому id нужно загрузить данные:
Непосредственно отрисовка
После того как мы заполнили наши шейдеры всеми данными мы должны попросить текстуру обновить изображение, а OpenGL отрисовать наши вершины:
В нашем примере мы разобьем работу с OpenGL сценой на два классы — непосредственно сцены и текстуры:
StateMachine / Машина состояний / Конечный автомат
Все API которое мы предполагаем использовать в нашем примере принципиально асинхронное (ну может за исключением анимированного Drawable-а). Мы будем заворачивать такие вызовы в отдельные StateMachine-ы — подходе когда явно выписывают состояния системы, а переходы между ними происходят через отправку событий.
Давайте на простом примере посмотрим как это будет выглядеть, предположим у нас есть такое код:
В целом все хорошо — красиво и компактно, но мы попробуем переписать его в следующим образом:
С одной стороны получилось сильно больше, тем не менее появилось несколько неявных, но полезных свойств: многократное нажатие теперь не приводит к лишним запускам loadImage, хотя и не очевидно с таким объемом, но мы избавились от вложенного вызова колбеков, чем и будем в последствии пользоваться, а еще стиль написания метода transition позволяет построить диаграмму переходов которая один в один повторяет код т.е в нашем случаи:
Серым указаны переходы, которые не выписаны явно. Часто их логируют или кидают исключение, считая признаком ошибки. Мы пока обойдемся простым игнорированием и в дальнейшем не будем указывать на схемах.
Создадим базовый интерфейсы для StateMachine:
Пример использования с разным API
Для вывода на экраны мы воспользуемся GLSurfaceView, для записи классами MediaCodec и EGLSurface, а с камерой общаться через API V2. Общая схема примерно следующая:
Отключаем ассистент
Все манипуляции, приводимые в статье, были проведены на смартфоне Xiaomi Mi 9T Pro с оболочкой MIUI 12 .
Для начала, заходим в настройки устройства, и ищем заветную вкладку " Google ", пролистываем меню ниже и выбираем пункт " Сервисы в аккаунте ":
Данная статья предназначена для начинающих андроид разработчиков с небольшим опытом работы с видео и/или камерой, особенно тех кто начал разбирать примеры grafika и кому они показались сложными — здесь будет рассмотрен похожий код с упрощенным описанием основных шагов, проиллюстрированных диаграммами состояний.
Почему в заголовке вынесен класс Surface? В android множество классов имеют в своем названии слово Surface (Surface, SurfaceHolder, SurfaceTexture, SurfaceView, GLSurfaceView) они не связаны общей иерархией тем не менее объединены низкоуровневой логикой работы с вывод изображений. Мне показалось разумным использовать его в названии чтобы подчеркнуть попытку раскрытия работы именно с этой частью SDK.
Читайте также: