کار با دوربین در اندروید توسط Camera2 API

آموزش گرفتن عکس در برنامه نویسی اندروید توسط Camera2 API

در این قسمت از سری مباحث برنامه نویسی اندروید به نحوه کار با Camera2 API جهت اتصال مستقیم به سخت افزار دوربین اندروید و ذخیره تصویر روی حافظه دیوایس می‌پردازیم.

آنچه در این آموزش می‌خوانید:

  • معرفی Camera2 API اندروید
  • معرفی Camera API بررسی مزایای Camera2 API نسبت به آن
  • مجوز یا Permission های موردنیاز برای اتصال به دوربین و ذخیره عکس روی حافظه
  • استفاده از تگ uses-feature جهت فیلتر کردن برنامه در فروشگاه Google Play
  • آشنایی با ویجت TextureView جهت نمایش خروجی دوربین در اکتیویتی
  • معرفی متد شنونده TextureView.SurfaceTextureListener
  • معرفی کلاس CameraManager و نحوه کار با آن جهت مدیریت دوربین‌ها
  • استفاده از متد getCameraIdList برای دریافت لیست شناسه (ID) دوربین‌ها
  • استفاده از متد CameraCharacteristics برای دریافت مشخصات دوربین‌ها
  • کار با متد StreamConfigurationMap جهت دریافت محتوای استریم شده از دوربین
  • دریافت سایز خروجی دوربین بوسیله متد getOutputSizes
  • معرفی اولیه قابلیت امنیتی Runtime Permission برای دریافت مجوز در اندروید ۶ و به بالا
  • باز کردن دوربین توسط متد openCamera
  • آشنایی با CameraAccessException و نحوه مدیریت (Handle) آن
  • معرفی کاربرد متد CameraDevice
  • معرفی کاربرد متد setDefaultBufferSize
  • استفاده از متد CaptureRequest.Builder جهت درخواست گرفتن عکس
  • دسترسی به داده‌های تصویر رندر شده توسط ImageReader
  • استفاده از تاریخ ثبت تصویر به عنوان نام فایل توسط SimpleDateFormat
  • تعیین محل ذخیره فایل بوسیله متد Environment.getExternalStorageDirectory
  • ذخیره محتوای استریم شده از دوربین بواسطه OutputStream
  • کار با متدهای OnImageAvailableListener و setOnImageAvailableListener

این جلسه در قالب PDF و در ۵۰ صفحه تهیه شده که در ادامه چند صفحه‌ ابتدایی را مشاهده می‌کنید:

در جلسه قبل (آموزش کار با دوربین در برنامه نویسی اندروید) به نحوه کار با دوربین و گرفتن عکس از طریق برنامه داخلی مانند برنامه پیش فرض دوربین پرداختیم. به صورتی که کاربر برای استفاده از دوربین و ثبت تصویر، ابتدا به یک برنامه مدیریت دوربین منتقل شده و سپس نتیجه آن به اپلیکیشن اصلی برگردانده می‌شد.
در این قسمت از مباحث آموزش برنامه نویسی اندروید به آموزش کار با دوربین توسط Camera2 API می‌پردازیم. با بکارگیری API دوربین سیستم عامل اندروید، اتصال به دوربین دیوایس اندرویدی به صورت مستقیم و بدون نیاز به برنامه جانبی امکان پذیر بوده و کاربر برای ثبت تصویر یا ویدئو از محیط برنامه اصلی خارج نمی‌گردد.

معرفی Camera2 API اندروید

در گذشته و تا قبل از معرفی اندروید ۵ یعنی Lollipop یا به عبارتی API 21، توسعه دهندگان اندروید برای ارتباط با دوربین دیوایس‌های اندرویدی در برنامه خود از Camera API استفاده می‌کردند. همزمان با معرفی اندروید ۵ در سال ۲۰۱۴، گوگل از API جدید خود به جهت ارتباط با دوربین رونمایی کرد که Camera2 API نام دارد.
Camera API در حال حاضر Deprecate (منقضی) شده و حتی در نسخه‌های اخیر اندروید به درستی پشتیبانی نمی‌شود. بنابراین لازم است از نسل جدید آن یعنی Camera2 API در پروژه‌های اندرویدی خود استفاده کنیم.

مزایای Camera2 API نسبت به Camera API

نسخه جدید این API مزایایی نسبت به نسل قبل دارد که به تعدادی از مهمترین آنها اشاره می‌کنم:

  • با سخت افزار جدید سازگاری بهتری داشته و در نتیجه شاهد خروجی بهینه‌تر و با کیفیت بالاتری هستیم.
  • ثبت تصاویر با سرعت بالاتری انجام می‌شود.
  • تعدادی افکت (Effect) و فیلتر (Filter) را به صورت مستقیم روی دوربین می‌توان اعمال کرد.
  • بدست آوردن لیست دوربین‌های موجود توسط متد getCameraIdList.
  • بدست آوردن مشخصات، ویژگی‌ها و تنظیمات (ازجمله رزولوشن) هریک از دوربین‌های موجود توسط متد CameraCharacteristics و استفاده از آنها بر اساس مشخصات دریافتی، جهت حصول بهترین نتیجه. به عبارتی در این نسخه، کنترل بهتری روی دوربین‌ها داریم.

پروژه کار با دوربین و ذخیره عکس روی حافظه دیوایس اندرویدی

در این قسمت، پروژه‌ای را تمرین و بررسی می‌کنیم که در ساده‌ترین حالت ممکن، اَپ ما به صورت مستقیم و از طریق Camera2 API با دوربین گوشی یا تبلت اندرویدی ارتباط برقرار کرده و با زدن یک دکمه، تصویر موردنظر روی حافظه داخلی دیوایس ذخیره می‌گردد. سایر امکانات موجود در API را در آموزش‌های آتی و همچنین در قالب پروژه‌های کاربردی به صورت کاملتر بررسی خواهیم کرد.
یک پروژه جدید اندرویدی با نام Camera2 API در اندروید استودیو با یک Empty Activity و Minimum API 21 ایجاد می‌کنم. من با زبان جاوا کار می‌کنم بنابراین در قسمت زبان، گزینه Java را انتخاب می‌کنم.
در اولین قدم باید Permission یا مجوزهای موردنیاز برای اپلیکیشن اندرویدی خود را در مانیفست پروژه تعریف کنیم. برخلاف پروژه قسمت قبل که برای ثبت تصویر از یک برنامه واسط استفاده کردیم و نیازی به تعیین مجوز نبود، در این پروژه ما به طور مستقیم و توسط API با سخت افزار دوربین سروکار داریم بنابراین لازم است ابتدا مجوز مربوطه از کاربر گرفته شود، در غیر اینصورت سیستم عامل اندروید حق دسترسی به دوربین و کارت حافظه را در اختیار برنامه ما قرار نخواهد داد.

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.hardware.camera2.full" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

سه مجوز فوق را به مانیفست پروژه اضافه کردم. دو مورد نخست جهت دسترسی به دوربین (CAMERA) و مورد سوم مربوط به دریافت مجوز دسترسی به حافظه دیوایس جهت ذخیره سازی تصویر دریافتی از دوربین می‌باشد. WRITE EXTERNAL STORAGE به معنی “نوشتن روی حافظه ذخیره سازی” است.
قبلا در مطلب مربوط به معرفی تگ uses-feature در اندروید پرداختیم. گفتیم از این تگ برای فیلتر اپلیکیشن‌ها در فروشگاه Google Play استفاده می‌شود. فرض را بر این می‌گذاریم که کار با دوربین جزء قابلیت‌های اصلی برنامه ماست و بدون آن، عملکردش با اختلال مواجه می‌گردد. بنابراین برنامه ما در فروشگاه گوگل پلی فقط به کاربرانی باید نمایش داده شود که دیوایس‌شان دارای حداقل یک دوربین باشد. در غیر اینصورت و بعد از نصب برنامه، امکان بهره مندی از اپ ما را نخواهند داشت. لذا تگ زیر را نیز به مانیفست اضافه می‌کنم:

فایل نهایی AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="ir.android_studio.camera2api">

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.hardware.camera2.full" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-feature android:name="android.hardware.camera" android:required="true" />
    
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

سپس layout اکتیویتی را به صورتی ویرایش می‌کنم که شامل یک View برای نمایش دوربین و یک Button برای ثبت تصویر باشد. برای نمایش محتوای دریافتی از دوربین یا به اصطلاح Camera Preview از TextureView استفاده می‌کنیم. از TextureView برای نمایش محتواهایی مانند ویدئو، ویدئوی زنده (Stream) و سایر موارد مشابه بکار می‌رود که در اینجا برای نمایش خروجی دوربین از آن استفاده می‌کنیم:

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextureView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_alignParentStart="true"
        android:id="@+id/texture_view"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:id="@+id/capture_btn"
        android:text="Capture" />

</RelativeLayout>

اضافه کردن ویجت TextureView به اکتیویتی در اندروید

در این پروژه من به تولبار نیازی ندارم و ترجیح می‌دهم آنرا غیر فعال کنم تا TextureView بخش بیشتری از صفحه نمایش را اشغال کند. بنابراین استایل تم پیش فرض را به NoActionBar تغییر می‌دهم:
styles.xml:

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
</style>

غیر فعال کردن تولبار تم متریال اندروید توسط NoActionBar

خب! حالا نوبت به اکتیویتی می‌رسد. ابتدا متدهای موردنیاز را درون بدنه اصلی کلاس MainActivity تعریف می‌کنم:

package ir.android_studio.camera2api;

import androidx.appcompat.app.AppCompatActivity;

import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.media.ImageReader;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.widget.Button;

import java.io.File;

public class MainActivity extends AppCompatActivity {

    private TextureView mTextureView;
    private Button captureButton;
    private static final SparseIntArray ORIENTATION = new SparseIntArray();

    static {
        ORIENTATION.append(Surface.ROTATION_0, 90);
        ORIENTATION.append(Surface.ROTATION_90, 0);
        ORIENTATION.append(Surface.ROTATION_180, 270);
        ORIENTATION.append(Surface.ROTATION_270, 180);
    }

    private String cameraId;
    private CameraDevice camDevice;
    private CameraCaptureSession camCapSession;
    private CaptureRequest capRequest;
    private CaptureRequest.Builder capRequestBuilder;
    private Size imgDimentions;
    private ImageReader imgReader;
    private File picFile;
    private Handler backgroundHandler;
    private HandlerThread backgroundThread;
    private static final int camPermissionReqCode = 200;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextureView = findViewById(R.id.texture_view);
        captureButton = findViewById(R.id.capture_btn);

    }
}

نترسید! خط به خط کدها را در ادامه آموزش توضیح می‌دهم.
ابتدا دو ویجت TextureView و Button که در layout قرار دارند را درون متد onCreate تعریف کرده‌ام.
برای برقراری ارتباط بین دوربین و TextureView نیاز به یک شنونده (Listener) دارم. درون بدنه اصلی کلاس اکتیویتی یک متد از نوع TextureView.SurfaceTextureListener با نام دلخواه textureListener اضافه می‌کنم:

متد SurfaceTextureListener در Camera2 API

متد به صورت زیر توسط اندروید استودیو ساخته می‌شود:

TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
        
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {

    }
};

این متد ۴ تابع با نام‌های onSurfaceTextureAvailable، onSurfaceTextureSizeChanged، onSurfaceTextureDestroyed و onSurfaceTextureUpdated دارد که کاربرد هرکدام از آنها از نحوه نامگذاری مشخص می‌شود. واژه Available در متد onSurfaceTextureAvailable به معنی “در دسترس” است لذا این متد زمانی اجرا خواهد شد که TextureView در حال اجرا باشد. یعنی زمانی که اکتیویتی حاوی TextureView مدنظر ما اجرا شد، این متد صدا زده می‌شود.

نکته: اگر بعد از اضافه کردن متد TextureView.SurfaceTextureListener و یا سایر مواقع با اروری مواجه شدید، نشانگر را روی محل ارور ببرید تا دلیل خطا را مشاهده کنید:

رفع خطاهای اندروید استودیو ازجمله expected

در این مورد صرفا لازم است یک “;” به انتهای بلاک متد اضافه شود.

گفتیم با اجرای اکتیویتی و در دسترس قرار گرفتن TextureView متد onSurfaceTextureAvailable هم اجرا می‌شود. بنابراین در این لحظه لازم است دوربین فعال شده و محتوای دریافتی از آن (همان پیش نمایش) به آن ارسال گردد تا کاربر خروجی دوربین را مشاهده نماید. لذا یک متد با نام دلخواه openTheCamera به این تابع اضافه کرده و کدهای مربوط به دریافت اطلاعات دوربین را درون آن می‌نویسم:

public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
    openTheCamera();
}

برای ساخت متد بجای نوشتن دستی آن، کافیست در حالی که نشانگر موس روی نام این تابع هست دکمه‌های alt + Enter را زده و روی گزینه create method کلیک کنیم:

استفاده از متد openCamera در برنامه نویسی اندروید برای باز کردن دوربین

در مرحله بعد روی گزینه همنام با اکتیویتی یعنی MainActivity را انتخاب می‌کنم تا متد درون بدنه اصلی کلاس ساخته شود:

استفاده از متد openCamera در Camera2 API برای باز کردن دوربین

به اینصورت متد openTheCamera درون کلاس اکتیویتی و بعد از متد TextureView.SurfaceTextureListener اضافه می‌شود:

TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
        openTheCamera();
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {

    }
};

private void openTheCamera() {
}

قبل از ادامه کار کد کامل اکتیویتی را قرار می‌دهم تا درک بهتری از وضعیت فعلی اکتیویتی داشته باشید:

package ir.android_studio.camera2api;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.media.ImageReader;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.Button;

import java.io.File;

public class MainActivity extends AppCompatActivity {

    private TextureView mTextureView;
    private Button captureButton;
    private static final SparseIntArray ORIENTATION = new SparseIntArray();

    static {
        ORIENTATION.append(Surface.ROTATION_0, 90);
        ORIENTATION.append(Surface.ROTATION_90, 0);
        ORIENTATION.append(Surface.ROTATION_180, 270);
        ORIENTATION.append(Surface.ROTATION_270, 180);
    }

    private String cameraId;
    private CameraDevice camDevice;
    private CameraCaptureSession camCapSession;
    private CaptureRequest capRequest;
    private CaptureRequest.Builder capRequestBuilder;
    private Size imgDimentions;
    private ImageReader imgReader;
    private File picFile;
    private Handler backgroundHandler;
    private HandlerThread backgroundThread;
    private static final int camPermissionReqCode = 200;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextureView = findViewById(R.id.texture_view);
        captureButton = findViewById(R.id.capture_btn);

    }

    TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
            openTheCamera();
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {

        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
            return false;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {

        }
    };

    private void openTheCamera() {
    }

}
نکته: این آموزش را با دقت و حوصله بیشتری دنبال کنید. همه آموزش‌ها به همراه سورس منتشر شده بنابراین هرجا دچار ابهام شدید سورس را بررسی کنید. برای مشاهده سورس نیازی نیست حتما آنرا داخل اندروید استودیو Import کنید. کافیست فایل‌های پروژه را با یک ویرایشگر ساده مانند ++Notepad باز کنید.

متد openTheCamera را به صورت زیر تکمیل کردم:

private void openTheCamera() {

    CameraManager camManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    cameraId = camManager.getCameraIdList() [0];
    CameraCharacteristics camCharacteristics = camManager.getCameraCharacteristics(cameraId);
    StreamConfigurationMap streamMap = camCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    imgDimentions = streamMap.getOutputSizes(SurfaceTexture.class) [0];

    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
            && ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(MainActivity.this, new String[] {Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, camPermissionReqCode);
        return;

    }

    camManager.openCamera(cameraId, camStateCallback, null);

}

در خط اول یک شیء از کلاس CameraManager با نام camManager ساختم. این کلاس برای مدیریت دوربین‌ها استفاده می‌گردد. در خط بعد متد getCameraIdList را مشاهده می‌کنید. توسط این متد، لیست شناسه (ID) دوربین(های) موجود در دیوایس برگردانده می‌شود. من می‌خواهم صرفا از دوربین اصلی استفاده شود بنابراین مقدار [۰] را تعریف کرده‌ام. بنابراین دوربین اصلی در متغیر cameraId قرار می‌گیرد.
در ابتدای مبحث عرض کردم در Camera2 API می‌توان مشخصات و جزئیات مربوط به هر دوربین را دریافت کرد. در ادامه و با استفاده از کلاس CameraCharacteristics مربوط به API، مشخصات دوربین اصلی یعنی دوربینی که شناسه آن در cameraId ذخیره شده در camCharacteristics قرار می‌گیرد.
در ادامه به StreamConfigurationMap نیاز داریم تا بوسیله آن بتوان محتوا یا فریم‌های دریافتی از دوربین (به عبارتی محتوای استریم شده از دوربین) را جهت ثبت عکس موردنظر ذخیره کرد.
ما باید اندازه تصویر دریافتی از دوربین را هم داشته باشیم. در خط بعد توسط getOutputSizes سایز خروجی دوربین اصلی (۰) دریافت شده و در متغیر imgDimentions که از نوع Size تعریف شده بود ذخیره می‌گردد.
از اندروید Marshmallow یا همان اندروید ۶ (API 21) به بعد، یک قابلیت امنیتی به نام Runtime Permission اضافه شد که دسترسی به قابلیت‌هایی مانند دوربین، خواندن و نوشتن روی کارت حافظه و… را به وسیله متد requestPermissions هنگام اجرای اپلیکیشن از کاربر می‌گیرد و شخص حق انتخاب و تایید یا عدم تایید هرکدام از موارد را به صورت جداگانه دارد. بر خلاف روال گذشته که تمامی مجوزها قبل از نصب اپ نمایش داده می‌شد و کاربر یا ملزم به پذیرش همه موارد بود تا بتواند فرایند نصب برنامه را ادامه دهد یا باید به کلی از نصب برنامه منصرف می‌شد. این قابلیت به طور مفصل در مطلب آموزش Runtime Permission در اندروید معرفی شده است.

در نهایت با استفاده از متد openCamera ارتباط با دوربین مدنظر برقرار می‌گردد. این متد سه پارامتر دارد. مورد اول شناسه دوربین که آنرا در cameraId ذخیره کردم، مورد دوم یک StateCallBack جهت دریافت وضعیت دوربین و مورد سوم یک کنترل کننده است که در اینجا مقدار null گرفته.
صرفا با توجه به نحوه نامگذاری متدها و کلاس‌ها، تا حدود زیادی نوع کاربرد مشخص می‌شود. state به معنی “وضعیت” و callback به معنی “پاسخ به تماس” است.

پارامتر StateCallBack متد openCamera

بنابراین می‌توان نتیجه گرفت که این متد قرار است وضعیت فعلی دوربین را برگرداند.
برای پارامتر دوم نام دلخواه camStateCallback را تعریف کردم و در ادامه، متد مربوط به آن را به اکتیویتی اضافه خواهم کرد.
در حال حاضر در قسمت getCameraIdList یک ارور وجود دارد:

هندل کردن ارور CameraAccessException

در اینجا یک استثناء کنترل نشده (Unhandled exception) داریم. این استثناء شامل دو حالت است:
۱- هنگامی که CameraManager نتواند دوربین را باز کند.
۲- هنگامی که ارتباط بین برنامه و دوربین به هردلیل قطع شده و ادامه اتصال ممکن نیست.
برای مدیریت یا به اصطلاح Handle کردن این استثناء، نشانگر موس را روی getCameraIdList قرار داده و کلیدهای alt + Enter را می‌زنم:

اضافه شدن throws CameraAccessException به متد openCamera

دو راه برای هندل کردن این استثناء وجود دارد. با انتخاب گزینه اول یعنی Add exception to method signature استثناء CameraAccessException در عنوان متد تعریف می‌شود:
private void openTheCamera() throws CameraAccessException {}

یا اینکه با انتخاب گزینه دوم و قراردادن getCameraIdList در یک try catch نیز می‌توان مدیریت استثناء احتمالی را انجام داد. من گزینه اول را انتخاب می‌کنم.
این ارور در متد onSurfaceTextureAvailable که openTheCamera را تعریف کردم نیز وجود دارد. مشابه قسمت قبل، با کلیدهای alt + Enter و انتخاب تنها گزینه موجود یعنی Surround with try/catch، متد را به صورت خودکار و بدون نیاز به نوشتن دستی درون یک دستور try catch قرار می‌دهم:

رفع ارور CameraAccessException توسط قرار دادن متد درون try catch

نتیجه:

public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
    try {
        openTheCamera();
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

حالا نوبت ساخت پارامتر دوم openCamera یعنی camStateCallback است. یک متد با همین نام و از جنس CameraDevice.StateCallback قبل از متد openTheCamera به کلاس اکتیویتی اضافه می‌کنم:

متد CameraDevice.StateCallback در Camera2 API دوربین اندروید

کلاس CameraDevice برای پردازشِ عکسِ گرفته شده استفاده می‌گردد.
متد به اینصورت ساخته می‌شود:

private final CameraDevice.StateCallback camStateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(@NonNull CameraDevice cameraDevice) {

    }

    @Override
    public void onDisconnected(@NonNull CameraDevice cameraDevice) {

    }

    @Override
    public void onError(@NonNull CameraDevice cameraDevice, int i) {

    }
};

به نامگذاری توابع درون این متد دقت کنید. در اینجا سه وضعیت دوربین را باید مدیریت کنیم:

  • onOpened: هنگامی که اتصال برقرار بوده و دوربین باز است.
  • onDisconnected: هنگامی که ارتباط با دوربین قطع شده.
  • onError: هنگامی که اروری دریافت می‌شود.

camStateCallBack را به صورت زیر تکمیل می‌کنم:

private final CameraDevice.StateCallback camStateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(@NonNull CameraDevice cameraDevice) {
        camDevice = cameraDevice;
        createCameraPreview();
    }

    @Override
    public void onDisconnected(@NonNull CameraDevice cameraDevice) {
        camDevice.close();
    }

    @Override
    public void onError(@NonNull CameraDevice cameraDevice, int i) {
        camDevice.close();
        camDevice = null;
    }
};

متد onOpened یک پارامتر از جنس CameraDevice و با نام cameraDevice دارد. camDevice ای که قبلا در اکتیویتی تعریف کردم را برابر این پارامتر قرار دادم. برای ساخت پیش نمایش دوربین و انتقال آن به TextureView نیاز به چند خط کد دیگر داریم. بنابراین یک متد به onOpened اضافه می‌کنم که نام آن را createCameraPreview انتخاب کرده‌ام.
برای onDisconnected و onError توسط متد close ارتباط با دوربین را کامل قطع کردم.
برای ساخت متد createCameraPreview مانند قبل روی آن alt + Enter، انتخاب گزینه create method… و در مرحله بعد انتخاب گزینه MainActivity تا متد درون کلاس ایجاد شود:

private void createCameraPreview() {}

متد را به اینصورت کامل می‌کنم:

private void createCameraPreview() {

    SurfaceTexture surTexure = mTextureView.getSurfaceTexture();
    surTexure.setDefaultBufferSize(imgDimentions.getWidth(), imgDimentions.getHeight());
    Surface mSurface = new Surface(surTexure);
    capRequestBuilder = camDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    capRequestBuilder.addTarget(mSurface);

    camDevice.createCaptureSession(Arrays.asList(mSurface), new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {

            if (camDevice == null) {
                return;
            }

            camCapSession = cameraCaptureSession;
            updatePreview();

        }

        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {

            Toast.makeText(MainActivity.this, "Configuration cannot be completed!", Toast.LENGTH_SHORT).show();
            
        }
    }, null);

}

در خط اول از متد SurfaceTexture یک شیء با نام surTexure ساختم. این متد داده‌های دریافتی از متد Surface را به فریم‌های قابل نمایش در TextureView تبدیل کرده و به آن ارسال می‌کند.
در خط بعد اندازه پیش فرض تصویر دریافتی به واسطه متد setDefaultBufferSize تنظیم می‌شود. یعنی عرض (getWidth) و ارتفاع آن (getHeight).
در ادامه از متد Surface یک شیء با نام mSurface ساختم که بافرهای خام دریافتی از دوربین را توسط OpenGL ES پردازش کرده و از طریق SurfaceTexture به ویجت TextureView ارسال می‌کند.
قبلا در بدنه اکتیویتی یک CaptureRequest.Builder با نام capRequestBuilder تعریف کرده بودم. از این متد برای ساخت “درخواست گرفتن عکس” استفاده می‌شود. در اینجا درخواستی ارسال می‌گردد که مناسب قالب پیش نمایش دوربین یعنی TEMPLATE_PREVIEW باشد.
در خط بعد توسط addTarget نمایه مدنظر یعنی mSurface را به لیست هدف‌های این درخواست (Request) اضافه می‌کنیم.
سپس توسط متد createCaptureSession وضعیت “گرفتن عکس” را بررسی می‌کنیم. به دلیل پیکربندی دوربین و همچنین اختصاص بافر حافظه جهت ارسال تصویر به مقصد موردنظر، ایجاد یک session سنگین بوده و ممکن است زمان زیادی (حدود چندصد میلی ثانیه) طول بکشد.
متد را به اینصورت در createCameraPreview تعریف می‌کنم:

کار با createCaptureSession در اتصال اپلیکیشین به دوربین اندروید

بعد از اضافه شدن پارامتر دوم یعنی StateCallback، خطایی گرفته می‌شود که لازم است پارامتر سوم هم تعریف شود. مطابق کد زیر پارامتر سوم null تعریف شده:

camDevice.createCaptureSession(Arrays.asList(mSurface), new CameraCaptureSession.StateCallback() {
    @Override
    public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {

    }

    @Override
    public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {

    }
}, null);

جهت مطالعه ادامه آموزش، فایل PDF را دانلود نمائید

توجه : سورس پروژه درون پوشه Exercises قرار دارد

با توجه به اینکه آموزش‌های پایه با قیمت پایین در اختیار کاربر قرار گرفته و درآمد حاصل صرف تامین هزینه‌های وب سایت و تهیه آموزش‌های آتی می‌شود، به اشتراک گذاری این فایل با دیگران خلاف اخلاق است.

دانلود نسخه کامل این آموزش به همراه سورس پروژه
تعداد صفحات : ۵۰
حجم : ۲ مگابایت
قیمت : ۲۸ هزار تومان
تاریخ بروزرسانی آموزش : ۹۸/۹/۱۶
توجه: صرفا در صورتی از درگاه پشتیبان استفاده کنید که قادر به پرداخت از طریق سبد دانلود نباشید.
افزودن به سبد دانلود درگاه پشتیبان
این مطلب چقدر برایتان مفید بود؟ لطفا امتیاز دهید
4.5/5 - (13 امتیاز)
پرسش‌ها و دیدگاه‌های کاربران
دوره آموزش برنامه نویسی اندروید
دوره آموزش برنامه نویسی اندروید

با دریافت این دوره به تمامی آموزش‌های غیر رایگان و رایگان موجود در وب سایت دسترسی دارید که تخفیفی برای آموزش‌های غیر رایگان نیز درنظر گرفته شده. این پکیج به دو صورت دانلودی و ارسال پستی ارائه می‌گردد.
آموزش‌های اندروید استودیو در دو دسته «پایه» و «تکمیلی» منتشر می‌شوند.
آموزش‌های پایه شامل مباحث اصلی و ضروری و آموزش‌های تکمیلی مطالبی است که می‌بایست در کنار مطالب اصلی بررسی شود.
با خرید این دوره، به تمامی آموزش‌های غیر رایگانی که در آینده منتشر می‌شود نیز به صورت رایگان دسترسی خواهید داشت!

یک دیدگاه بنویسید

پرسش‌های زیر تایید و پاسخ داده نـــخواهند شد:
۱: جزء موارد مطرح شده در صفحات مشکلات و پرسش‌های رایج و بروزرسانی‌های محتوای آموزشی باشد
۲: سوال قبلا توسط کاربران در دیدگاه‌ها مطرح و پاسخ داده شده باشد
۳: پرسش خارج از مبحث آموزشی موجود در این صفحه باشد

  • امیرحسین گفت:

    سلام . من به صورت کامل کد شما رو نوشتم و حتی برای اطمینان هم سورس کد شما رو به کار بردم ولی وقتی اجرا میگیرم روی دیوایس خودم رو دکمه میزنم تا عکس بگیره همونجا وایمیسه نه تست نشون داده میشه هیچ نه ذخیره میشه نمی‌دونم چیکار کنم حتی روی یه سیستم دیگه هم اجرا گرفتیم و حتی گوشی چند نفر دیگه ولی نمیشه. اندروید استودیو هم نسخه جدید رو به کار بردم هم قدیم نمی‌دونم مشکل کجاس میشه کمک کنید؟

  • sahar گفت:

    ممنون از آموزش خوبتون.یه سوالی داشتم.آیا امکان استفاده همزمان از دوربین جلو و عقب گوشی وجود داره؟من سرچ کردم و یه کدهایی پیدا کردم که کار نمیکردن.با استفاده از این آموزش هم دقیقا همین کدها رو برای یک tuxtureview دیگه نوشتم و cameraId رو برابر ۱ قراردادم ولی فقط دومی رو نشون میده….از روش نمیشه برای اینکار استفاده کرد یعنی؟
    از پاسخگوییتون ممنونم

  • امیرحسین قویدل گفت:

    سلام
    خداقوت واقعا زحمت کشیدید
    من که فقط نوت برداری میکردم چند صفحه پاپکو شد دیگ شما چه زحمتی کشیدید

  • سجاد گفت:

    سلام
    با تشکر از آموزش خوبتون من فکر نمیکردم که یه همچین برنامه ای کوچکی این همه کد نیاز داشته باشه
    چرا توی برنامه کاری نکردین که وقتی عکس گرفته می شه و ذخیره میشه داخل گالری هم نشون داده بشه
    آیا کد خاصی می خواد؟

    • سیدمهدی مطهری گفت:

      مطمئنید نشون نمیده؟ مهم نیست عکس کجای حافظه ذخیره میشه. با اینحال برای ذخیره عکس در یک مسیر و فولدر خاص، نیاز هست مسیر رو براش تعریف کنید. سرچ کنید
      مثلا: android file storage tutorial