Работа с камерой в Android

    Работа с камерой на телефоне всегда представляла для меня интерес. Как же это все устроено… И вот мне в руки попал телефон с Android'ом. Я не преминул возможностью попробовать разобраться в этом. Вот что получилось в итоге.

    Рассмотрим небольшую программу, которая позволяет делать снимки.

    Все операции проводятся с помощью класса Camera.
    Необходимо завести переменную
    Camera camera;
    

    и инициализировать ее
    camera = Camera.open();
    

    После завершения работы с камерой необходимо сделать
    camera.release();
    

    в противном случае камера останется заблокированной и недоступной для других приложений.

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

    Обязательным условием при работе с камерой является создание окна предпросмотра (preview). Это окно должно являться объектом класса Surfaceи для отображения на экране подходит SurfaceView.
    Объявим
    SurfaceView preview;
    

    Чтобы задать preview, необходимо вызвать метод setPreviewDisplay, параметром которого является объект класса SurfaceHolder.
    SurfaceHolder surfaceHolder;
    surfaceHolder = preview.getHolder();
    camera.setPreviewDisplay(surfaceHolder);
    

    Чтобы включить отображение preview, вызываем
    camera.startPreview();
    

    Если этого не сделать, то камера не сможет делать снимки.

    Собственно для того, чтобы сделать снимок, необходимо вызвать метод
    void takePicture(Camera.ShutterCalback shutter, Camera.PictureCallback raw, Camera.PictureCallback postview, Camera.PictureCallback jpg);
    

    С помощью параметров (кстати, любой из них может быть null) задаются обработчики разных событий:
    • shutter — вызывается в момент получения изображения с матрицы
    • raw — программе передаются для обработки raw данные (если поддерживается аппаратно)
    • postview — программе передаются полностью обработанные данные (если поддерживается аппаратно)
    • jpg — программе передается изображение в виде jpg. Здесь можно организовать запись изображения на карту памяти.

    Вызов takePicture можно поместить непосредственно в обработчик onClick кнопки — в этом случае фотографирование произойдет сразу после нажатия на нее, но можно и воспользоваться предварительной автофокусировкой.
    В этом случае задается обработчик Camera.AutoFocusCallback, в котором необходимо реализовать метод
    public void onAutoFocus(boolean paramBoolean, Camera paramCamera);
    

    Тогда после вызова в обработчике нажатия на кнопку camera.autoFocus(), однократно будет вызван обработчик, в котором мы уже и примем решение об удачной фокусировке и необходимости сделать снимок.

    Для работы с SurfaceHolder можно задать SurfaceHolder.Callback
    surfaceHolder.addCallback();

    В этом случае необходимо реализовать методы
    public void surfaceCreated(SurfaceHolder holder);
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height);
    public void surfaceDestroyed(SurfaceHolder holder);
    

    C помощью них приложению будет сообщаться о том, что Surface успешно создано, если оно изменено или то, что оно удалено.

    Размер нашего preview можно менять в процессе выполнения программы:
    LayoutParams lp = preview.getLayoutParams();
    lp.width = задаваемая ширина;
    lp.height = задаваемая высота;
    preview.setLayoutParams(lp);
    

    Для приложения камеры удобнее всего сразу задать расположение экрана как
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    В противном случае нам придется, например, в surfaceCreated проверять расположение экрана и поворачивать preview с помощью, например, camera.setDisplayOrientation(0).
    Это не очень удобно, потому что поворот экрана занимает какое-то время. В этот момент происходит вызов onPause и onResume, пересоздается Surface.

    Также имеется возможность объявить обработчик Camera.PreviewCallback, с помощью которого путем реализации метода
    void onPreviewFrame(byte[] paramArrayOfByte, Camera paramCamera);
    

    можно получать и обрабатывать каждый кадр, отображаемый в preview.

    И последний важный момент. Чаще всего получается так, что отношение сторон SurfaceView отличается от отношения сторон в preview камеры. Поэтому для того, чтобы избежать искажений изображения на экране, необходимо подкорректировать размер отображаемого окна предпросмотра.

    Чуть не забыл. В манифест необходимо добавить permission
    <uses-permission android:name="android.permission.CAMERA" />
    


    MainScreen.java
    package test.camera;
    
    import android.app.Activity;
    import android.content.pm.ActivityInfo;
    import android.content.res.Configuration;
    import android.os.Bundle;
    import android.view.SurfaceHolder;
    import android.view.SurfaceView;
    import android.view.ViewGroup.LayoutParams;
    import android.view.Window;
    import android.view.WindowManager;
    import android.widget.Button;
    import android.view.View;
    
    import android.hardware.Camera;
    import android.hardware.Camera.Size;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    
    public class MainScreen extends Activity implements SurfaceHolder.Callback, View.OnClickListener, Camera.PictureCallback, Camera.PreviewCallback, Camera.AutoFocusCallback
    {
        private Camera camera;
        private SurfaceHolder surfaceHolder;
        private SurfaceView preview;
        private Button shotBtn;
    
        @Override
        public void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
    
            // если хотим, чтобы приложение постоянно имело портретную ориентацию
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    
            // если хотим, чтобы приложение было полноэкранным
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    
            // и без заголовка
            requestWindowFeature(Window.FEATURE_NO_TITLE);
    
            setContentView(R.layout.main);
    
            // наше SurfaceView имеет имя SurfaceView01
            preview = (SurfaceView) findViewById(R.id.SurfaceView01);
    
            surfaceHolder = preview.getHolder();
            surfaceHolder.addCallback(this);
            surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    
            // кнопка имеет имя Button01
            shotBtn = (Button) findViewById(R.id.Button01);
            shotBtn.setText("Shot");
            shotBtn.setOnClickListener(this);
        }
    
        @Override
        protected void onResume()
        {
            super.onResume();
            camera = Camera.open();
        }
    
        @Override
        protected void onPause()
        {
            super.onPause();
    
            if (camera != null)
            {
                camera.setPreviewCallback(null);
                camera.stopPreview();
                camera.release();
                camera = null;
            }
        }
    
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
        {
        }
    
        @Override
        public void surfaceCreated(SurfaceHolder holder)
        {
            try
            {
                camera.setPreviewDisplay(holder);
                camera.setPreviewCallback(this);
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
    
            Size previewSize = camera.getParameters().getPreviewSize();
            float aspect = (float) previewSize.width / previewSize.height;
    
            int previewSurfaceWidth = preview.getWidth();
            int previewSurfaceHeight = preview.getHeight();
    
            LayoutParams lp = preview.getLayoutParams();
    
            // здесь корректируем размер отображаемого preview, чтобы не было искажений
    
            if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE)
            {
                // портретный вид
                camera.setDisplayOrientation(90);
                lp.height = previewSurfaceHeight;
                lp.width = (int) (previewSurfaceHeight / aspect);
                ;
            }
            else
            {
                // ландшафтный
                camera.setDisplayOrientation(0);
                lp.width = previewSurfaceWidth;
                lp.height = (int) (previewSurfaceWidth / aspect);
            }
    
            preview.setLayoutParams(lp);
            camera.startPreview();
        }
    
        @Override
        public void surfaceDestroyed(SurfaceHolder holder)
        {
        }
    
        @Override
        public void onClick(View v)
        {
            if (v == shotBtn)
            {
                // либо делаем снимок непосредственно здесь
                // 	либо включаем обработчик автофокуса
    
                //camera.takePicture(null, null, null, this);
                camera.autoFocus(this);
            }
        }
    
        @Override
        public void onPictureTaken(byte[] paramArrayOfByte, Camera paramCamera)
        {
            // сохраняем полученные jpg в папке /sdcard/CameraExample/
            // имя файла - System.currentTimeMillis()
    
            try
            {
                File saveDir = new File("/sdcard/CameraExample/");
    
                if (!saveDir.exists())
                {
                    saveDir.mkdirs();
                }
    
                FileOutputStream os = new FileOutputStream(String.format("/sdcard/CameraExample/%d.jpg", System.currentTimeMillis()));
                os.write(paramArrayOfByte);
                os.close();
            }
            catch (Exception e)
            {
            }
    
            // после того, как снимок сделан, показ превью отключается. необходимо включить его
            paramCamera.startPreview();
        }
    
        @Override
        public void onAutoFocus(boolean paramBoolean, Camera paramCamera)
        {
            if (paramBoolean)
            {
                // если удалось сфокусироваться, делаем снимок
                paramCamera.takePicture(null, null, null, this);
            }
        }
    
        @Override
        public void onPreviewFrame(byte[] paramArrayOfByte, Camera paramCamera)
        {
            // здесь можно обрабатывать изображение, показываемое в preview
        }
    }
    


    main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout android:id="@+id/FrameLayout01" 
        android:layout_width="fill_parent" android:layout_height="fill_parent"
        xmlns:android="http://schemas.android.com/apk/res/android">
        <SurfaceView android:id="@+id/SurfaceView01" 
            android:layout_width="wrap_content" android:layout_height="wrap_content">
        </SurfaceView>
        <Button android:text="@+id/Button01" android:id="@+id/Button01" 
            android:layout_width="wrap_content" android:layout_height="wrap_content">
        </Button>
    </FrameLayout>
    


    AndroidManifest.xml
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="test.camera"
          android:versionCode="1"
          android:versionName="1.0">
        <application android:icon="@drawable/icon" android:label="@string/app_name" android:debuggable="true">
            <activity android:name=".MainScreen" android:label="@string/app_name">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
        <uses-permission android:name="android.permission.CAMERA" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    </manifest> 
    


    Программа отлаживалась и тестировалась на телефоне LG Optimus One P500.

    При написании использовались следующие источники информации:

    1. developer.android.com/reference/android/hardware/Camera.html
    2. Shawn Van Every. Pro Android Media: Developing Graphics, Music, Video and Rich Media Apps for Smartfones and Tablets. Apress 2009.
    3. developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/CameraPreview.html


    Скачать проект можно по ссылке: перезалил вот сюда zalil.ru/30377379
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 19

      0
      >Скачать проект можно по ссылке
      404…
      –7
      Директор завода по сжиганию фотографов радуется этом исходникам!
        0
        во, как раз искал подобный материал, все руки чешутся покрутить камеру на андроиде. Где можно найти что-то подобное под винмобайл или айфон?
          0
          > искал подобный материал, все руки чешутся покрутить камеру на андроиде
          Как-то вы очень лениво искали, в стандартных сэмплах есть практически тоже самое, впрочем, автор статьи в конце на один из них ссылку и приложил.
          Ежели чего, там ещё много всякого на разные другие темы есть: developer.android.com/resources/samples/
        0
        Скажите, а на сколько сложно обстоят дела с видеозаписью?
        Меня очень неустраивает битрейт звука на HTC Desire.
        Вот всё пытаюсь сесть и навоять простенькую камеру, но с хорошим звуком.
          +1
          судя по всему, дело обстоит довольно просто.
          предлагается использовать класс MediaRecorder, который имеет множество методов для задания параметров видео и аудио.
          setAudioSource()
          setVideoSource()
          setOutputFormat()
          setVideoEncoder()
          setAudioEncoder()
          setVideoEncodingBitRate()
          setAudioEncodingBitRate()
          setAudioSamplingRate()
          setAudioChannels()
          setVideoFrameRate()
          setVideoSize()

          на коленке получилось по быстрому модифицировать эту программу и даже какое-то видео записалось.
          на выходных попробую сделать полноценный пример.
          0
          А можно ли каким-то образом отлаживать в эмуляторе, «эмулируя» камеру телефона при помощи камеры компа?
            0
            на developer.android.com/guide/developing/tools/emulator.html сказано:
            No support for camera/video capture (input).

            вообще отладка непосредственно на телефоне оказалась довольно удобной и быстрой, поэтому сильно не переживал )
              +1
              Насколько я знаю, в эмуляторе есть какой-то имитатор работы видеокамеры. Сам не пользовался.
                0
                встроенный имитатор просто показывает вместо превью поле с квадратиками, а вместо снимка дает картинку с зеленым роботом. т.е. отлаживать на эмуляторе, в принципе, можно.
              0
              Сохранение картинки в файл лучше обрамить в AsyncTask, что бы не подвешивать основной поток программы.
                0
                как-то вот так:
                <source>
                  +2
                  Извиняюсь, в первый раз рука сорвалась… )
                  Вот исходник «обрамленной» версии, что бы не быть голословным:
                  @Override
                      public void onPictureTaken(byte[] paramArrayOfByte, Camera paramCamera)
                      {
                  	
                  		new SaveInBackground().execute(paramArrayOfByte);
                  		camera.startPreview();
                      }
                  
                  	class SaveInBackground extends AsyncTask<byte[], String, String> {
                  		@Override
                  		protected String doInBackground(byte[]... arrayOfByte) {
                  			try {
                  				File saveDir = new File("/sdcard/CameraExample/");
                  
                  				if (!saveDir.exists())
                  				{
                  					saveDir.mkdirs();
                  				}
                  
                  				FileOutputStream os = 
                             new FileOutputStream(String.format("/sdcard/CameraExample/%d.jpg", System.currentTimeMillis()));
                  				os.write(arrayOfByte[0]);
                  				os.close();
                          } catch (Exception e) {
                  			//
                          }
                  			return(null);
                  		}
                  	}
                  
                  0
                  Кстати перезапуск активити при смене ориентации экрана не является обязательным. Можно поставить флаг на активити и будет вместо это вызываться один метод. Пишу на телефоне, посмотреть название флага сложно.
                    0
                    Умные люди, подскажите, пожалуйста, можно ли вытащить RAW (Camera.PictureCallback raw) перед сжатием в jpg? По ходу дела эта функция не поддерживается (хотя в документации об этом ни слова), т.к. хип приложения лимитирован (16мб вроде) и рав дата просто не влезает. Может в NDK нужно глянуть?
                    Есть огромное желание разобраться — почему снимки такие размытые получаются. Сравниваю Мотоандроиды с Motorola Zn5
                      0
                      скажите, а если вызвать метод startPreview() без предварительного setPreviewDisplay() (то есть без окна предпросмотра), то все отработает корректно, за исключением, что не будет окна предпросмотра? я спрашиваю к тому, что хочу сделать приложение «фонарик». то есть через камеру иметь доступ к светодиоду. и не хотелось бы во время вкл/выкл фонарика наблюдать окно предпросмотра камеры
                        0
                        Вы разобрались с этим вопросом? Без создания предпросмотра камера отработает?

                      Only users with full accounts can post comments. Log in, please.