ক্যামেরা ক্যাপচার সেশন ও অনুরোধ

মনে রাখবেন: এই পৃষ্ঠা Camera2 প্যাকেজকে রেফার করে। আপনার অ্যাপের জন্য Camera2 থেকে নির্দিষ্ট, লো-লেভেল ফিচারের প্রয়োজন না হলে, আমরা CameraX ব্যবহার করার সাজেশন দিই। CameraX এবং Camera2, দুটিই Android 5.0 (API লেভেল ২১) এবং তার পরবর্তী যেকোনও ভার্সনে কাজ করে।

Android-পরিচালিত একটি ডিভাইসে একাধিক ক্যামেরা থাকতে পারে। প্রতিটি ক্যামেরা হল একটি CameraDevice এবং একটি CameraDevice একই সাথে একাধিক স্ট্রিম আউটপুট করতে পারে।

এটি করার একটি কারণ হল, CameraDevice থেকে আসা একটি স্ট্রিম, সিকোয়েন্সিয়াল ক্যামেরা ফ্রেম, নির্দিষ্ট টাস্কের জন্য অপ্টিমাইজ করা হয়, যেমন ভিউফাইন্ডার দেখানো, অন্যগুলি ফটো তুলতে বা ভিডিও রেকর্ডিং করতে ব্যবহার করা হতে পারে। স্ট্রিমগুলি সমান্তরাল পাইপলাইন হিসাবে কাজ করে যা ক্যামেরা থেকে আসা কাঁচা ফ্রেমগুলি প্রসেস করে, একবারে একটি ফ্রেম:

প্যারালাল স্ট্রিম ক্যামেরা ডিভাইস থেকে র ফ্রেম প্রসেস করে
       আলাদা আউটপুট ফর্ম্যাটে পরিণত করে
ছবি ১. Building a Universal Camera App (Google I/O '18) থেকে নেওয়া ইলাস্ট্রেশন

প্যারালাল প্রসেসিং থেকে বোঝা যায় যে সিপিইউ (CPU), জিপিইউ (GPU) বা অন্য প্রসেসর থেকে উপলভ্য প্রসেসিং পাওয়ারের উপর নির্ভর করে পারফর্ম্যান্সের সীমা থাকতে পারে। কোনও পাইপলাইন ইনকামিং ফ্রেমের সাথে তাল মিলিয়ে চলতে না পারলে, এটি ফ্রেম ড্রপ করা শুরু করে।

প্রতিটি পাইপলাইনের নিজস্ব আউটপুট ফর্ম্যাট আছে। ইনকামিং র ডেটা প্রতিটি পাইপলাইনের সাথে যুক্ত অন্তর্নিহিত লজিকের মাধ্যমে অটোমেটিক উপযুক্ত আউটপুট ফর্ম্যাটে রূপান্তরিত হয়। এই পৃষ্ঠার কোড স্যাম্পেল জুড়ে ব্যবহার করা CameraDevice নির্দিষ্ট নয়, তাই এগিয়ে যাওয়ার আগে আপনাকে প্রথমে উপলভ্য সব ক্যামেরা এনিউমারেট করতে হবে।

আপনি CameraDevice ব্যবহার করে একটি CameraCaptureSession তৈরি করতে পারেন, যা সেই CameraDevice-এর জন্য নির্দিষ্ট। CameraCaptureSession ব্যবহার করে প্রতিটি র ফ্রেমের জন্য CameraDevice-কে অবশ্যই ফ্রেম কনফিগারেশন পেতে হবে। কনফিগারেশন অটোফোকাস, অ্যাপারচার, এফেক্ট এবং এক্সপোজারের মতো ক্যামেরা অ্যাট্রিবিউট নির্দিষ্ট করে। হার্ডওয়্যার সংক্রান্ত সীমাবদ্ধতার কারণে, কোনও নির্দিষ্ট সময়ে ক্যামেরা সেন্সরে শুধুমাত্র একটি কনফিগারেশন অ্যাক্টিভ থাকে, যাকে অ্যাক্টিভ কনফিগারেশন বলা হয়।

তবে, স্ট্রিম ব্যবহারের কেস ক্যাপচার সেশন স্ট্রিম করার জন্য CameraDevice ব্যবহার করার আগের পদ্ধতিগুলিকে উন্নত ও প্রসারিত করে, যা আপনাকে আপনার নির্দিষ্ট ব্যবহারের কেসের জন্য ক্যামেরা স্ট্রিম অপ্টিমাইজ করতে দেয়। যেমন, ভিডিও কল অপ্টিমাইজ করার সময় এটি ব্যাটারির আয়ু উন্নত করতে পারে।

CameraCaptureSession-এ CameraDevice-এর সাথে যুক্ত সব সম্ভাব্য পাইপলাইনের বিবরণ থাকে। সেশন তৈরি করা হয়ে গেলে, আপনি পাইপলাইন যোগ করতে বা সরাতে পারবেন না। CameraCaptureSession, CaptureRequest-এর একটি সারি বজায় রাখে, যা অ্যাক্টিভ কনফিগারেশন হয়ে যায়।

CaptureRequest, সারিতে একটি কনফিগারেশন যোগ করে এবং CameraDevice থেকে ফ্রেম পাওয়ার জন্য উপলভ্য পাইপলাইনের মধ্যে থেকে এক, একাধিক বা সবকটি বেছে নেয়। আপনি ক্যাপচার সেশনের লাইফটাইমে অনেক ক্যাপচার অনুরোধ পাঠাতে পারবেন। প্রতিটি অনুরোধ অ্যাক্টিভ কনফিগারেশন এবং আউটপুট পাইপলাইনের সেট পরিবর্তন করতে পারে যা র' ছবি গ্রহণ করে।

আরও ভালো পারফর্ম্যান্সের জন্য স্ট্রিম ব্যবহারের কেস ব্যবহার করুন

স্ট্রিম ব্যবহারের কেস হল Camera2 ক্যাপচার সেশনের পারফর্ম্যান্স উন্নত করার একটি উপায়। এগুলি হার্ডওয়্যার ডিভাইসকে প্যারামিটার টিউন করার জন্য আরও তথ্য দেয়, যা আপনার নির্দিষ্ট টাস্কের জন্য আরও ভাল ক্যামেরা অভিজ্ঞতা প্রদান করে।

এটি ক্যামেরা ডিভাইসকে প্রতিটি স্ট্রিমের জন্য ব্যবহারকারীর পরিস্থিতির উপর ভিত্তি করে ক্যামেরা হার্ডওয়্যার এবং সফ্টওয়্যার পাইপলাইন অপ্টিমাইজ করার অনুমতি দেয়। স্ট্রিম ব্যবহারের উদাহরণ সম্পর্কে আরও জানতে, setStreamUseCase দেখুন।

স্ট্রিম ব্যবহারের কেস আপনাকে নির্দিষ্ট ক্যামেরা স্ট্রিম কীভাবে ব্যবহার করা হয় তা আরও বিশদে নির্দিষ্ট করতে দেয়, CameraDevice.createCaptureRequest()-এ টেমপ্লেট সেট করার পাশাপাশি। এটি ক্যামেরা হার্ডওয়্যারকে টিউনিং, সেন্সর মোড বা ক্যামেরা সেন্সর সেটিংসের মতো প্যারামিটার অপ্টিমাইজ করতে দেয়। নির্দিষ্ট ব্যবহারের ক্ষেত্রে উপযুক্ত কোয়ালিটি বা লেটেন্সি ট্রেডঅফের উপর ভিত্তি করে এটি করা হয়।

স্ট্রিম ব্যবহারের উদাহরণে এগুলি অন্তর্ভুক্ত:

  • DEFAULT: আগে থেকে থাকা সব অ্যাপ্লিকেশনের আচরণ কভার করে। এটি কোনও স্ট্রিম ব্যবহারের কেস সেট না করার সমতুল্য।

  • PREVIEW: ভিউফাইন্ডার বা অ্যাপ-মধ্যস্থ ছবি বিশ্লেষণের জন্য সাজেস্ট করা হয়েছে।

  • STILL_CAPTURE: খুব ভাল কোয়ালিটি ও হাই-রেজোলিউশন ক্যাপচারের জন্য অপ্টিমাইজ করা হয়েছে এবং প্রিভিউয়ের মতো ফ্রেম রেট বজায় রাখার আশা করা হয় না।

  • VIDEO_RECORD: খুব ভালো কোয়ালিটির ভিডিও ক্যাপচার করার জন্য অপ্টিমাইজ করা হয়েছে, যার মধ্যে খুব ভালো কোয়ালিটির ইমেজ স্ট্যাবিলাইজেশন অন্তর্ভুক্ত, যদি ডিভাইসে কাজ করে এবং অ্যাপ্লিকেশন দ্বারা চালু করা থাকে। এই বিকল্পটি রিয়েল টাইম থেকে যথেষ্ট ল্যাগ সহ আউটপুট ফ্রেম তৈরি করতে পারে, যাতে সর্বোচ্চ-কোয়ালিটির স্ট্যাবিলাইজেশন বা অন্যান্য প্রসেসিংয়ের সুবিধা পাওয়া যায়।

  • VIDEO_CALL: দীর্ঘ সময় ধরে ক্যামেরা ব্যবহার করার জন্য সাজেস্ট করা হয় যেখানে ব্যাটারি শেষ হয়ে যাওয়া একটি সমস্যা।

  • PREVIEW_VIDEO_STILL: সোশ্যাল মিডিয়া অ্যাপ বা সিঙ্গেল স্ট্রিম ব্যবহারের ক্ষেত্রে সাজেস্ট করা হয়। এটি একটি বহুমুখী স্ট্রিম।

  • VENDOR_START: OEM-এর দ্বারা নির্ধারিত ব্যবহারের ক্ষেত্রে ব্যবহার করা হয়।

CameraCaptureSession তৈরি করুন

ক্যামেরা সেশন তৈরি করতে, আপনার অ্যাপকে এক বা একাধিক আউটপুট বাফার প্রদান করুন যাতে এটি আউটপুট ফ্রেম লিখতে পারে। প্রতিটি বাফার একটি পাইপলাইনকে উপস্থাপন করে। ক্যামেরা ব্যবহার শুরু করার আগে আপনাকে অবশ্যই এটি করতে হবে যাতে ফ্রেমওয়ার্ক ডিভাইসের ইন্টার্নাল পাইপলাইন কনফিগার করতে পারে এবং প্রয়োজনীয় আউটপুট টার্গেটে ফ্রেম পাঠানোর জন্য মেমরি বাফার বরাদ্দ করতে পারে।

নিচে দেওয়া কোড স্নিপেট থেকে আপনি জানতে পারবেন যে কীভাবে দুটি আউটপুট বাফার সহ ক্যামেরা সেশন প্রস্তুত করতে হয়, যার মধ্যে একটি SurfaceView-এর এবং অন্যটি ImageReader-এর। previewSurface-এ PREVIEW স্ট্রিম ব্যবহারের কেস এবং imReaderSurface-এ STILL_CAPTURE স্ট্রিম ব্যবহারের কেস যোগ করলে, ডিভাইস হার্ডওয়্যার এইসব স্ট্রিম আরও ভালোভাবে অপ্টিমাইজ করতে পারে।

// Retrieve the target surfaces, which might be coming from a number of places:
// 1. SurfaceView, if you want to display the image directly to the user
// 2. ImageReader, if you want to read each frame or perform frame-by-frame
// analysis
// 3. OpenGL Texture or TextureView, although discouraged for maintainability
// reasons
// 4. RenderScript.Allocation, if you want to do parallel processing
val surfaceView = findViewById<SurfaceView>(
    // ...
)
val imageReader = ImageReader.newInstance(
    // ...
)

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated().
val previewSurface = surfaceView.holder.surface
val imReaderSurface = imageReader.surface
val targets = listOf(previewSurface, imReaderSurface)

// Create a capture session using the predefined targets; this also involves
// defining the session state callback to be notified of when the session is
// ready.
// Setup Stream Use Case while setting up your Output Configuration.
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun configureSession(device: CameraDevice, targets: List<Surface>) {
    val configs = mutableListOf<OutputConfiguration>()
    val streamUseCase = CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    targets.forEach {
        val config = OutputConfiguration(it)
        config.streamUseCase = streamUseCase.toLong()
        configs.add(config)
    }
    // ...
    device.createCaptureSession(session)
}

এই মুহূর্তে, আপনি ক্যামেরার অ্যাক্টিভ কনফিগারেশন নির্দিষ্ট করেননি। সেশন কনফিগার করা হয়ে গেলে, আপনি ক্যাপচার করার অনুরোধ তৈরি করে পাঠাতে পারবেন।

ইনপুটগুলি তাদের বাফারে লেখার সময় প্রয়োগ করা রূপান্তরটি প্রতিটি টার্গেটের ধরন দ্বারা নির্ধারিত হয়, যা অবশ্যই একটি Surface হতে হবে। Android ফ্রেমওয়ার্ক জানে যে কীভাবে অ্যাক্টিভ কনফিগারেশনে থাকা র' ইমেজকে প্রতিটি টার্গেটের জন্য উপযুক্ত ফর্ম্যাটে কনভার্ট করতে হয়। কনভার্সনটি নির্দিষ্ট Surface-এর পিক্সেল ফর্ম্যাট এবং সাইজ দ্বারা নিয়ন্ত্রিত হয়।

ফ্রেমওয়ার্কটি যথাসাধ্য চেষ্টা করে, তবে কিছু Surface কনফিগারেশন কম্বিনেশন কাজ নাও করতে পারে, যার ফলে সেশন তৈরি না হওয়া, আপনি কোনও অনুরোধ পাঠালে রানটাইম সমস্যা দেখানো বা পারফর্ম্যান্স খারাপ হওয়ার মতো সমস্যা হতে পারে। ফ্রেমওয়ার্কটি ডিভাইস, সারফেস এবং অনুরোধের প্যারামিটারের নির্দিষ্ট কম্বিনেশনের জন্য গ্যারান্টি প্রদান করে। createCaptureSession()-এর ডকুমেন্টেশন থেকে আরও তথ্য পাওয়া যাবে।

সিঙ্গেল CaptureRequests

প্রতিটি ফ্রেমের জন্য ব্যবহৃত কনফিগারেশন CaptureRequest-এ এনকোড করা হয়, যা ক্যামেরায় পাঠানো হয়। ক্যাপচার অনুরোধ তৈরি করতে, আপনি আগে থেকে নির্ধারিত টেমপ্লেট ব্যবহার করতে পারেন অথবা সম্পূর্ণ কন্ট্রোলের জন্য TEMPLATE_MANUAL ব্যবহার করতে পারেন। আপনি কোনও টেমপ্লেট বেছে নিলে, অনুরোধের সাথে ব্যবহার করার জন্য আপনাকে এক বা একাধিক আউটপুট বাফার প্রদান করতে হবে। আপনি শুধুমাত্র সেইসব বাফার ব্যবহার করতে পারবেন যেগুলি আপনি যে ক্যাপচার সেশন ব্যবহার করতে চান সেখানে আগে থেকেই সংজ্ঞায়িত করা আছে।

ক্যাপচার করার অনুরোধ বিল্ডার প্যাটার্ন ব্যবহার করে এবং ডেভেলপারদের অটো-এক্সপোজার, অটো-ফোকাস ও লেন্স অ্যাপারচার সহ অনেক ধরনের বিকল্প সেট করার সুযোগ দেয়। কোনও ফিল্ড সেট করার আগে, CameraCharacteristics.getAvailableCaptureRequestKeys() কল করে ডিভাইসের জন্য নির্দিষ্ট বিকল্প উপলভ্য আছে কিনা তা নিশ্চিত করুন এবং উপযুক্ত ক্যামেরা বৈশিষ্ট্য, যেমন উপলভ্য অটো-এক্সপোজার মোড চেক করে বেছে নেওয়া ভ্যালু কাজ করছে কিনা তা নিশ্চিত করুন।

কোনও পরিবর্তন না করে প্রিভিউয়ের জন্য ডিজাইন করা টেমপ্লেট ব্যবহার করে SurfaceView-এর জন্য ক্যাপচার অনুরোধ তৈরি করতে, CameraDevice.TEMPLATE_PREVIEW ব্যবহার করুন:

val session: CameraCaptureSession = /* ... */ // from CameraCaptureSession.StateCallback
val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureRequest.addTarget(previewSurface)

ক্যাপচার অনুরোধ নির্দিষ্ট করার পরে, আপনি এখন এটি ক্যামেরা সেশনে ডিসপ্যাচ করতে পারবেন:

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest.Builder = ...  // from CameraDevice.createCaptureRequest()

// The first null argument corresponds to the capture callback, which you
// provide if you want to retrieve frame metadata or keep track of failed capture
// requests that can indicate dropped frames; the second null argument
// corresponds to the Handler used by the asynchronous callback, which falls
// back to the current thread's looper if null.
session.capture(captureRequest.build(), null, null)

নির্দিষ্ট বাফারে আউটপুট ফ্রেম যোগ করা হলে, ক্যাপচার কলব্যাক ট্রিগার করা হয়। অনেক ক্ষেত্রে, অতিরিক্ত কলব্যাক, যেমন ImageReader.OnImageAvailableListener, ট্রিগার করা হয় যখন এতে থাকা ফ্রেম প্রসেস করা হয়। এই সময় আপনি নির্দিষ্ট বাফার থেকে ছবির ডেটা ফিরিয়ে আনতে পারবেন।

CaptureRequests রিপিট করুন

সিঙ্গেল ক্যামেরা অনুরোধ করা সহজ, তবে লাইভ প্রিভিউ বা ভিডিও দেখানোর জন্য এগুলি খুব একটা উপযোগী নয়। সেক্ষেত্রে, আপনাকে শুধু একটি ফ্রেম নয়, বরং ফ্রেমের একটি অবিচ্ছিন্ন স্ট্রিম পেতে হবে। নিম্নলিখিত কোড স্নিপেট দেখায় যে সেশনে কীভাবে রিপিটিং অনুরোধ যোগ করতে হয়:

val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest.Builder = ... // from CameraDevice.createCaptureRequest()

// This keeps sending the capture request as frequently as possible until
// the session is torn down or session.stopRepeating() is called.
session.setRepeatingRequest(captureRequest.build(), null, null)

ক্যাপচার করার অনুরোধ বারবার করা হলে, ক্যামেরা ডিভাইসটি প্রদত্ত CaptureRequest-এর সেটিংস ব্যবহার করে ক্রমাগত ছবি ক্যাপচার করে। Camera2 API ব্যবহারকারীদের ক্যামেরা থেকে ভিডিও ক্যাপচার করার অনুমতি দেয়, এর জন্য বারবার CaptureRequests পাঠাতে হয়, যেমনটি GitHub-এ এই Camera2 স্যাম্পেল রিপোজিটরিতে দেখা যায়। এটি GitHub-এ Camera2 স্লো মোশন ভিডিও স্যাম্পেল অ্যাপে দেখানো রিপিটিং বার্স্ট CaptureRequests ব্যবহার করে হাই-স্পিড (স্লো মোশন) ভিডিও ক্যাপচার করার মাধ্যমে স্লো মোশন ভিডিও রেন্ডার করতে পারে।

CaptureRequests ইন্টারলিভ করুন

রিপিট করা ক্যাপচার অনুরোধ অ্যাক্টিভ থাকাকালীন দ্বিতীয় ক্যাপচার অনুরোধ পাঠাতে, যেমন ভিউফাইন্ডার দেখানো এবং ব্যবহারকারীদের ফটো ক্যাপচার করতে দেওয়া, আপনাকে চলমান রিপিট করা অনুরোধ বন্ধ করতে হবে না। পরিবর্তে, আপনি একটি পুনরাবৃত্তি না হওয়া ক্যাপচার ইস্যু করেন পুনরাবৃত্তি অনুরোধ চলতে থাকাকালীন অনুরোধ।

সেশন প্রথমবার তৈরি করার সময়, ব্যবহৃত যেকোনও আউটপুট বাফার ক্যামেরা সেশনের অংশ হিসেবে কনফিগার করতে হবে। সিঙ্গেল-ফ্রেম বা বার্স্ট অনুরোধের তুলনায় রিপিটিং অনুরোধের অগ্রাধিকার কম, যা নিম্নলিখিত স্যাম্পেলকে কাজ করতে দেয়:

val session: CameraCaptureSession = /* ... */ // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it.
val repeatingRequest = session.device.createCaptureRequest(
    CameraDevice.TEMPLATE_PREVIEW
)
repeatingRequest.addTarget(previewSurface)
session.setRepeatingRequest(repeatingRequest.build(), null, null)

// Some time later...

// Create the single request and dispatch it.
// NOTE: This can disrupt the ongoing repeating request momentarily.
val singleRequest = session.device.createCaptureRequest(
    CameraDevice.TEMPLATE_STILL_CAPTURE
)
singleRequest.addTarget(imReaderSurface)
session.capture(singleRequest.build(), null, null)

তবে এই পদ্ধতিতে একটি অসুবিধা আছে: আপনি ঠিক কখন একটি অনুরোধ করা হয়েছে তা জানতে পারবেন না। নিচের ছবিতে, A যদি রিপিটিং ক্যাপচার অনুরোধ হয় এবং B যদি সিঙ্গল-ফ্রেম ক্যাপচার অনুরোধ হয়, তাহলে সেশন এইভাবে অনুরোধের সারি প্রসেস করে:

অ্যাক্টিভ রিপিটিং ক্যাপচার অনুরোধের সারি একটি ক্যাপচার অনুরোধের
       কারণে বাধাগ্রস্ত হয়েছে
ছবি ২. চলমান ক্যামেরা সেশনের জন্য অনুরোধের সারির ইলাস্ট্রেশন

B অনুরোধ অ্যাক্টিভেট হওয়ার আগে A-এর শেষবার রিপিট করা অনুরোধ এবং পরের বার A আবার ব্যবহার করার মধ্যে লেটেন্সির কোনও গ্যারান্টি নেই, তাই আপনি কিছু ফ্রেম মিস করতে পারেন। এই সমস্যা সমাধানের জন্য আপনি কিছু পদক্ষেপ নিতে পারেন:

  • অনুরোধ ক থেকে অনুরোধ খ-তে আউটপুট টার্গেট যোগ করুন। এইভাবে, যখন B-এর ফ্রেম রেডি হয়ে যায়, তখন এটি A-এর আউটপুট টার্গেটে কপি করা হয়। যেমন, ভিডিও স্ন্যাপশট নেওয়ার সময় ফ্রেম রেট স্থির রাখতে এটি প্রয়োজন। আগের কোডে, অনুরোধ তৈরি করার আগে আপনি singleRequest.addTarget(previewSurface) যোগ করেন।

  • এই নির্দিষ্ট পরিস্থিতির জন্য ডিজাইন করা টেমপ্লেটের সংমিশ্রণ ব্যবহার করুন, যেমন জিরো-শাটার-ল্যাগ।