Saturday, July 13, 2013

Работа с камерой в 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

<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

<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