Работа с камерой в Android
Работа с камерой на телефоне всегда представляла для меня интерес. Как же это все устроено… И вот мне в руки попал телефон с Android’ом. Я не преминул возможностью попробовать разобраться в этом. Вот что получилось в итоге.
Рассмотрим небольшую программу, которая позволяет делать снимки.
Все операции проводятся с помощью класса Camera.
Необходимо завести переменную
и инициализировать ее
После завершения работы с камерой необходимо сделать
в противном случае камера останется заблокированной и недоступной для других приложений.
Для обычных приложений типа фотокамеры инициализацию лучше всего производить в onResume, а освобождение в onPause.
Обязательным условием при работе с камерой является создание окна предпросмотра (preview). Это окно должно являться объектом класса Surfaceи для отображения на экране подходит SurfaceView.
Объявим
Чтобы задать preview, необходимо вызвать метод setPreviewDisplay, параметром которого является объект класса SurfaceHolder.
Чтобы включить отображение preview, вызываем
Если этого не сделать, то камера не сможет делать снимки.
Собственно для того, чтобы сделать снимок, необходимо вызвать метод
С помощью параметров (кстати, любой из них может быть null) задаются обработчики разных событий:
- shutter — вызывается в момент получения изображения с матрицы
- raw — программе передаются для обработки raw данные (если поддерживается аппаратно)
- postview — программе передаются полностью обработанные данные (если поддерживается аппаратно)
- jpg — программе передается изображение в виде jpg. Здесь можно организовать запись изображения на карту памяти.
Вызов takePicture можно поместить непосредственно в обработчик onClick кнопки — в этом случае фотографирование произойдет сразу после нажатия на нее, но можно и воспользоваться предварительной автофокусировкой.
В этом случае задается обработчик Camera.AutoFocusCallback, в котором необходимо реализовать метод
Тогда после вызова в обработчике нажатия на кнопку 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.
При написании использовались следующие источники информации:
Источник
«Шпионская» камера в Android
Делаем фотографию
Сначала приведу вольный перевод официальной документации, касающейся вопроса пользования камерой.
- За фотографии в Android отвечает класс Camera.
Чтобы получить картинку нужно:
- Найти Id нужной камеры, используя методы getNumberOfCameras и getCameraInfo );
- Получить ссылку на объект камеры методом open .
- Получить текущие (по-умолчанию) настройки методом getParameters .
- При необходимости изменить параметры и установить их заново методом setParameters ;
- При необходимости установить ориентацию камеры методом setDisplayOrientation (НЕТ вертикальному видео!);
- ВАЖНО: Передать в метод setPreviewDisplay правильно инициализированный объект SurfaceHolder. Если этого не сделать, то камера не сможет начать превью.
- ВАЖНО: Вызвать метод startPreview ), который начнет обновлять SurfaceHolder. Вы ОБЯЗАНЫ начать превью перед тем как сделать снимок.
- Наконец-то вызвать метод takePicture и дождаться когда данные вернуться в onPictureTaken ;
- После вызова метода takePicture превью будет остановлено. Если нужно сделать еще фото, то придется вызвать startPreview снова;
- Если же камера больше не нужна, то сначала нужно остановить превью методом stopPreview;
- ВАЖНО: Вызвать метод release() чтобы освободить ресурсы камеры для других приложений. Приложение должно немедленно освобождать ресурсы камеры в методе onPause (и получать их обратно в методе onResume ).
Данный класс не потокобезопасный. Большинство операций (превью, фокусировка, получение фото) асинхронны и возвращают результат через коллбэки, которые будут вызваны в том же потоке, в котором был вызван метод open. Методы данного класса ни в коем случае не должны вызываться сразу из нескольких потоков.
Предупреждение: Разные устройства на ОС Android могут иметь разные возможности камеры (например, разрешение, возможность автофокусировки и т.п.).
Здесь перевод заканчивается и начинается самое интересное.
Из всего вышеперечисленного в глаза бросаются следующие проблемы:
- Надо показывать превью.
- На разных устройствах камера может работать по-разному.
С ними-то мы и будем бороться.
Когда возникает проблема из разряда «в доках написано, что так сделать нельзя», перво-наперво нужно заглянуть в исходники. Из них стало понятно, что прорисовка превью вынесена на уровень нативного кода setPreviewDisplay(Surface). Была принята попытка быстро разобраться в том, как вообще система определяет, стартовали мы превью или нет. Быстро пробраться через тернии C++ кода не получилось, поэтому я пошёл по пути наименьшего сопротивления — создал превью, но отобразил его незаметно для пользователя. Если поискать на stackoverflow, то можно найти другой способ – передавать в setPreviewDisplay SurfaceHolder, созданный динамически. А раз объект не добавлен в разметку Activity, то и отображаться он не будет. К сожалению, данный метод работает только для старых версий Android (до 3.0, если не ошибаюсь). В новых версиях разработчики исправили данное недоразумение.
Таким образом, приходим к единственному выводу – мы должны так или иначе отобразить превью на экране, вопрос теперь только в том, можно ли сделать это незаметно? К счастью, ответ – «да, можно». И вот что для этого нужно:
- Прозрачная Activity.
- FrameLayout размером 1 на 1 пиксель в левом верхнем углу нашей Activity.
Прозрачное Activity делается одной строчкой манифеста, для этого определим её так:
и создадим для нее следующую несложную разметку:
Объект SurfaceHolder создается и добавляется в разметку динамически. В принципе можно было добавить его сразу в разметку, данный момент был вынесен в код, чтобы не лезть в разметку при необходимости переопределить поведение объекта.
Итак, прозрачное Activity есть, SurfaceHolder создаем динамически, что дальше? Дальше дело за главным – инициализировать камеру и сделать фото. Идея здесь в том, чтобы сделать фото сразу на старте Activity и закрыть её как можно быстрее. Определим нашу Activity так:
Таким образом, в неё будут сыпаться события от SurfaceHolder’а (surfaceCreated, surfaceChanged, surfaceDestroyed) и Camera (onPictureTaken). Внутренний класс CameraPreview нужен исключительно для того, чтобы, как я отмечал выше, быстро и безболезненно внести правки в поведение нашего SurfaceView в случае необходимости. Далее приведу скопом методы Activity
The android.view.SurfaceHolder must already contain a surface when this method is called. If you are usingandroid.view.SurfaceView, you will need to register a android.view.SurfaceHolder.Callback withandroid.view.SurfaceHolder.addCallback(android.view.SurfaceHolder.Callback) and wait forandroid.view.SurfaceHolder.Callback.surfaceCreated(android.view.SurfaceHolder) before calling setPreviewDisplay() or starting preview.
This method must be called before startPreview().
Второй момент связан с жизненным циклом объекта SurfaceHolder относительно Activity. Жизненный цикл Activity можно найти в документации, а вот с SurfaceHolder’ом всё непонятно, поэтому пришлось выяснять это опытным путём:
Итак, настало время немного обобщить происходящее. Вот что происходит в приложении:
- Стартуем Activity на нужное событие (в моем случае — на включение экрана).
- В onCreate создаем SurfaceHolder и регистрируем Activity для получения коллбэков.
- Ждем вызова surfaceCreated и в нём инициализируем камеру.
- После того, как камера инициализирована, пытаемся вызвать takePicture. Поскольку порядок вызова методов сильно зависит от аппарата, версии ОС и типа блокировки экрана, пытаемся в методах onResume| surfaceChanged стартовать превью, а в onPause останавливать её. При этом onResume| onPause могут случиться как до, так и после surfaceCreated, поэтому везде проверяем камеру на «инициализированность».
- Метод surfaceChanged, согласно документации, гарантированно вызывается хотя бы раз после surfaceCreated, но теоретически может быть вызван еще сколько угодно раз в процессе получения фотографии. Добавляем переменную mPreviewIsRunning для того, чтобы ненароком не стартануть превью несколько раз. Стартуем превью, вызываем takePicture, ждём.
- Ловим фотографию в onPictureTaken. Освобождаем камеру, создаем AsyncTask для сохранения картинки, закрываем Activity.
Таким образом, общий порядок вызовов получается следующий:
Источник
Android-er
For Android development, from beginner to beginner.
Thursday, January 6, 2011
Start Camera auto-focusing, autoFocus()
To start auto-focusing for Android camera, simple call autoFocus() method of the camera object, and registers a callback function to run when the camera is focused. This method is only valid when preview is active (between startPreview() and before stopPreview()).
Callers should check getFocusMode() to determine if this method should be called. If the camera does not support auto-focus, it is a no-op and onAutoFocus(boolean, Camera) callback will be called immediately.
If your application should not be installed on devices without auto-focus, you must declare that your application uses auto-focus with the manifest element.
If the current flash mode is not FLASH_MODE_OFF, flash may be fired during auto-focus, depending on the driver and camera hardware.
Modify control.xml, define id for the background of the layout. Such that we can start auto-focus when user click on the background.
Modify AndroidCamera.java to call camera.autoFocus(myAutoFocusCallback), and register the AutoFocusCallback.
17 comments:
As this is my first comment in your blog, I must give you a very big THANK YOU! because I’m a beginner trying to make my first android program using camera and your tutorial is teaching me a lot. So, again, THANK YOU!
Well, and now for the real question: in your tutorial, as well as others (including the ApiDemo from google and others), when doing preview over surface I get a distorted image (on a Nexus One, don’t know others).
I’ve been playing with surfaceChanged method trying to get getSupportedPreviewSizes. Also, I found I could try the camera.setDisplayOrientation(degress) method, but could get success.
In all cases, altought preview is broken, the final captured image is fine.
Do you know if there is some standard way to get preview properly displayed.
Thank you very much in advanced.
I don’t know what you means «distorted image». As I know there are two problem in my exercise:
i — There is a color-line under the preview, I don’t know how to fix it up to now.
ii — The preview rotate 90 degree in some device; it can be: setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE).
Thank you for your reply.
What I mean distorted is this:
In ‘real view’ I see it even more distorted. You can check the 90º degrees monitor borders.
Regarding the color line you say, I don’t see it, check my photo as is (almost) exactly your sample. I only have change your code to force focus before shot (if you want the code just tell me).
I think my problem comes because width/height ratio is not the same on screen and on final image, but don’t know how to solve it. Any idea?
Thank you anyway
Hi,
it seems to be interesting ,but i wanna customize my autofocus with sound how could i do that,any ideas
Thanks
I don’t know if it has any build-in function to play sound when focusing.
Thank you a million time.
it helped me a lot.
Thank you very much. You save my night 🙂
Thaaaaaaaaaaaaaaaank you very much.
Really very very very good tutorail.. I learned alot from this tutorial.
Thanks again.
I was trying to copy this code, but I got errors at the @override
thanks for helping me make my first camera app!
Hi
Excellent tutorial, thanks for sharing your knowledge, I have a query I add an opaque background and an image on the frame but when I take a photo not recorded
hello Bill Soriano,
The image record from camera, not from your view. I think if you want to add something on the photos, you have to add on the recorded image, not on view/screen.
good thing . I will continue investigating
Hi! Your tutorial is very useful, and i learn alot from it. Thanks alot.
Can you please tell me which type of image format is used in byte[] arg0, and why this is giving a black image when i try to convert via BitmapFactory. What i want is to convert the byte[] image to bitmap. Any help regarding that?
Thank you very much in advance!
I am looking to commission an Android Application that measures two things (for benchmarking):
1) Time to autofocus, which includes the steps of running the AF algorithm, by moving the AF mech multiple times, measuring sharpness and going to the position of highest sharpness.
2) Avg time to move the AF mech from mac to inf using 100 measurements.
3) Avg time to move the AF mech from inf to mac using 100 measurements.
I am looking to have this app complete before Christmas.
caubuchon@tessera.com
when i run the code, it not opening up the camera.
why.
can u send an application that takes photo and vedio capturing in surface view
Источник