যদি আপনার অ্যাপটি আসল Camera ক্লাস ("Camera1") ব্যবহার করে, যা Android 5.0 (API লেভেল 21) থেকে অপ্রচলিত (deprecated) হয়ে গেছে, তাহলে আমরা একটি আধুনিক Android ক্যামেরা API-তে আপডেট করার জন্য জোরালোভাবে সুপারিশ করছি। Android-এ CameraX (একটি মানসম্মত, শক্তিশালী Jetpack ক্যামেরা API) এবং Camera2 (একটি লো-লেভেল, ফ্রেমওয়ার্ক API) রয়েছে। বেশিরভাগ ক্ষেত্রেই, আমরা আপনার অ্যাপটিকে CameraX-এ স্থানান্তরিত করার পরামর্শ দিই। এর কারণ নিচে দেওয়া হলো:
- ব্যবহারের সহজতা: CameraX খুঁটিনাটি বিষয়গুলো সামলে নেয়, ফলে আপনি একেবারে গোড়া থেকে একটি ক্যামেরা অভিজ্ঞতা তৈরির চেয়ে আপনার অ্যাপটিকে স্বতন্ত্র করে তোলার দিকে বেশি মনোযোগ দিতে পারেন।
- CameraX আপনার জন্য ফ্র্যাগমেন্টেশন সামলায়: CameraX দীর্ঘমেয়াদী রক্ষণাবেক্ষণ খরচ এবং ডিভাইস-নির্দিষ্ট কোড কমিয়ে ব্যবহারকারীদের জন্য উন্নত মানের অভিজ্ঞতা নিয়ে আসে। এ বিষয়ে আরও জানতে, আমাদের ‘Better Device Compatibility with CameraX’ ব্লগ পোস্টটি দেখুন।
- উন্নত কার্যকারিতা: CameraX-কে এমনভাবে যত্নসহকারে ডিজাইন করা হয়েছে যাতে আপনার অ্যাপে উন্নত কার্যকারিতা সহজে অন্তর্ভুক্ত করা যায়। উদাহরণস্বরূপ, আপনি CameraX এক্সটেনশন ব্যবহার করে আপনার ফটোতে সহজেই বোকেহ, ফেস রিটাচ, HDR (হাই ডাইনামিক রেঞ্জ), এবং কম আলোতে ছবি উজ্জ্বল করার নাইট ক্যাপচার মোড প্রয়োগ করতে পারেন।
- আপডেটযোগ্যতা: অ্যান্ড্রয়েড সারা বছর ধরে CameraX-এ নতুন সক্ষমতা এবং বাগ ফিক্স প্রকাশ করে। CameraX-এ স্থানান্তরিত হওয়ার মাধ্যমে, আপনার অ্যাপটি শুধুমাত্র বার্ষিক অ্যান্ড্রয়েড সংস্করণ প্রকাশের সময়ই নয়, বরং CameraX-এর প্রতিটি নতুন সংস্করণের সাথেই সর্বশেষ অ্যান্ড্রয়েড ক্যামেরা প্রযুক্তি পেয়ে যায়।
এই নির্দেশিকায় আপনি ক্যামেরা অ্যাপ্লিকেশনের সাধারণ পরিস্থিতিগুলো পাবেন। তুলনার জন্য প্রতিটি পরিস্থিতিতে একটি Camera1 বাস্তবায়ন এবং একটি CameraX বাস্তবায়ন অন্তর্ভুক্ত করা হয়েছে।
মাইগ্রেশনের ক্ষেত্রে, বিদ্যমান কোডবেসের সাথে ইন্টিগ্রেট করার জন্য কখনও কখনও অতিরিক্ত নমনীয়তার প্রয়োজন হয়। এই গাইডের সমস্ত CameraX কোডে একটি CameraController ইমপ্লিমেন্টেশন রয়েছে—যা CameraX ব্যবহারের একটি সহজ উপায় চাইলে দারুণ—এবং একটি CameraProvider ইমপ্লিমেন্টেশনও রয়েছে—যা আরও বেশি নমনীয়তার জন্য চমৎকার। আপনার জন্য কোনটি সঠিক, তা সিদ্ধান্ত নিতে সাহায্য করার জন্য, এখানে প্রতিটির সুবিধাগুলো তুলে ধরা হলো:
ক্যামেরা কন্ট্রোলার | ক্যামেরাপ্রোভাইডার |
| খুব কম সেটআপ কোডের প্রয়োজন হয়। | আরও বেশি নিয়ন্ত্রণের সুযোগ দেয় |
| ক্যামেরাএক্স-কে সেটআপ প্রক্রিয়ার আরও বেশি অংশ সামলাতে দেওয়ার ফলে ট্যাপ-টু-ফোকাস এবং পিঞ্চ-টু-জুমের মতো কার্যকারিতাগুলো স্বয়ংক্রিয়ভাবে কাজ করে। | যেহেতু অ্যাপ ডেভেলপার সেটআপের কাজটি করেন, তাই কনফিগারেশন কাস্টমাইজ করার আরও সুযোগ থাকে, যেমন ImageAnalysis এ আউটপুট ইমেজ রোটেশন চালু করা বা আউটপুট ইমেজের ফরম্যাট সেট করা। |
ক্যামেরা প্রিভিউয়ের জন্য PreviewView প্রয়োজনীয়তা CameraX-কে নির্বিঘ্ন এন্ড-টু-এন্ড ইন্টিগ্রেশন প্রদান করতে সক্ষম করে, যেমন আমাদের ML Kit ইন্টিগ্রেশন, যা ML মডেলের ফলাফলের স্থানাঙ্ক (যেমন মুখের বাউন্ডিং বক্স) সরাসরি প্রিভিউ স্থানাঙ্কের উপর ম্যাপ করতে পারে। | ক্যামেরা প্রিভিউয়ের জন্য একটি কাস্টম `সারফেস` ব্যবহার করার ক্ষমতা আরও বেশি নমনীয়তা প্রদান করে, যেমন আপনার বিদ্যমান `সারফেস` কোড ব্যবহার করা, যা আপনার অ্যাপের অন্যান্য অংশের ইনপুট হিসেবে কাজ করতে পারে। |
মাইগ্রেট করতে গিয়ে আটকে গেলে, CameraX ডিসকাশন গ্রুপে আমাদের সাথে যোগাযোগ করুন।
আপনি স্থানান্তরিত হওয়ার আগে
ক্যামেরাএক্স এবং ক্যামেরা১ এর ব্যবহারের তুলনা
কোড দেখতে ভিন্ন হলেও, Camera1 এবং CameraX-এর অন্তর্নিহিত ধারণাগুলো খুবই একই রকম। CameraX ক্যামেরার সাধারণ কার্যকারিতাগুলোকে ইউজ-কেস (use case)-এর মধ্যে বিমূর্ত করে , এবং এর ফলে, Camera1-এ যেসব কাজ ডেভেলপারের উপর ছেড়ে দেওয়া হতো, তার অনেক কিছুই CameraX স্বয়ংক্রিয়ভাবে সম্পন্ন করে। CameraX-এ চারটি UseCase কেস (UseCase) রয়েছে, যেগুলো আপনি ক্যামেরার বিভিন্ন কাজের জন্য ব্যবহার করতে পারেন: Preview , ImageCapture , VideoCapture , এবং ImageAnalysis ।
ডেভেলপারদের জন্য CameraX যে সূক্ষ্ম বিবরণগুলো সামলে নেয়, তার একটি উদাহরণ হলো ViewPort , যা সক্রিয় UseCase গুলোর মধ্যে শেয়ার করা হয়। এটি নিশ্চিত করে যে সমস্ত UseCase হুবহু একই পিক্সেল দেখতে পায়। Camera1-এ, আপনাকে এই বিবরণগুলো নিজে থেকেই সামলাতে হয়। ডিভাইসভেদে অ্যাস্পেক্ট রেশিও ভিন্ন হওয়ায়, ধারণ করা মিডিয়ার সাথে প্রিভিউ মেলানো কঠিন হয়ে পড়ে।
আরেকটি উদাহরণ হিসেবে বলা যায়, CameraX আপনার দেওয়া Lifecycle ইনস্ট্যান্সের ওপর ভিত্তি করে Lifecycle কলব্যাকগুলো স্বয়ংক্রিয়ভাবে পরিচালনা করে। এই আর্কিটেকচারের মাধ্যমে, CameraX সম্পূর্ণ অ্যান্ড্রয়েড অ্যাক্টিভিটি লাইফসাইকেল জুড়ে আপনার অ্যাপের ক্যামেরার সাথে সংযোগ পরিচালনা করে, যার মধ্যে নিম্নলিখিত ক্ষেত্রগুলো অন্তর্ভুক্ত: আপনার অ্যাপ ব্যাকগ্রাউন্ডে গেলে ক্যামেরা বন্ধ করা; স্ক্রিনে ক্যামেরা প্রিভিউ প্রদর্শনের প্রয়োজন না থাকলে তা সরিয়ে ফেলা; এবং কোনো ইনকামিং ভিডিও কলের মতো অন্য কোনো অ্যাক্টিভিটি ফোরগ্রাউন্ডে প্রাধান্য পেলে ক্যামেরা প্রিভিউ থামিয়ে দেওয়া।
অবশেষে, CameraX আপনার পক্ষ থেকে কোনো অতিরিক্ত কোড ছাড়াই রোটেশন এবং স্কেলিং পরিচালনা করে। আনলক করা ওরিয়েন্টেশন সহ একটি Activity ক্ষেত্রে, প্রতিবার ডিভাইসটি ঘোরানোর সময় UseCase সেটআপ করা হয়, কারণ ওরিয়েন্টেশন পরিবর্তনের সাথে সাথে সিস্টেম Activity ধ্বংস করে এবং পুনরায় তৈরি করে। এর ফলে UseCases প্রতিবার ডিফল্টরূপে ডিসপ্লের ওরিয়েন্টেশনের সাথে মিলিয়ে তাদের টার্গেট রোটেশন সেট করে। CameraX-এ রোটেশন সম্পর্কে আরও পড়ুন ।
বিস্তারিত আলোচনায় যাওয়ার আগে, CameraX-এর UseCase ) এবং একটি Camera1 অ্যাপ এর সাথে কীভাবে সম্পর্কিত হবে, সে সম্পর্কে একটি সাধারণ ধারণা দেওয়া হলো। (CameraX-এর ধারণাগুলো নীল রঙে এবং Camera1-এর ধারণাগুলো সবুজ রঙে দেখানো হয়েছে।)
ক্যামেরাএক্স | |||
| ক্যামেরা কন্ট্রোলার / ক্যামেরা প্রোভাইডার কনফিগারেশন | |||
| ↓ | ↓ | ↓ | ↓ |
| প্রিভিউ | ইমেজক্যাপচার | ভিডিও ক্যাপচার | চিত্র বিশ্লেষণ |
| ⁞ | ⁞ | ⁞ | ⁞ |
| প্রিভিউ সারফেস পরিচালনা করুন এবং এটিকে ক্যামেরায় সেট করুন। | PictureCallback সেট করুন এবং ক্যামেরার উপর takePicture() কল করুন। | নির্দিষ্ট ক্রমে ক্যামেরা ও মিডিয়া রেকর্ডারের কনফিগারেশন পরিচালনা করুন | প্রিভিউ সারফেসের উপর ভিত্তি করে তৈরি কাস্টম বিশ্লেষণ কোড |
| ↑ | ↑ | ↑ | ↑ |
| ডিভাইস-নির্দিষ্ট কোড | |||
| ↑ | |||
| ডিভাইস ঘূর্ণন এবং স্কেলিং ব্যবস্থাপনা | |||
| ↑ | |||
| ক্যামেরা সেশন ম্যানেজমেন্ট (ক্যামেরা নির্বাচন, লাইফসাইকেল ম্যানেজমেন্ট) | |||
ক্যামেরা১ | |||
CameraX-এ সামঞ্জস্যতা এবং কর্মক্ষমতা
CameraX অ্যান্ড্রয়েড ৫.০ (এপিআই লেভেল ২১) এবং তার পরবর্তী সংস্করণের ডিভাইসগুলো সাপোর্ট করে। এটি বিদ্যমান অ্যান্ড্রয়েড ডিভাইসগুলোর ৯৮%-এরও বেশি। CameraX ডিভাইসগুলোর মধ্যকার পার্থক্য স্বয়ংক্রিয়ভাবে সামলানোর জন্য তৈরি করা হয়েছে, যা আপনার অ্যাপে ডিভাইস-নির্দিষ্ট কোডের প্রয়োজনীয়তা কমিয়ে দেয়। এছাড়াও, আমরা আমাদের CameraX টেস্ট ল্যাবে অ্যান্ড্রয়েড ৫.০ থেকে শুরু করে পরবর্তী সকল সংস্করণে ১৫০টিরও বেশি বাস্তব ডিভাইস পরীক্ষা করি। আপনি টেস্ট ল্যাবে ডিভাইসগুলোর সম্পূর্ণ তালিকা দেখতে পারেন।
CameraX ক্যামেরা স্ট্যাক পরিচালনা করার জন্য একটি Executor ব্যবহার করে। আপনার অ্যাপের নির্দিষ্ট থ্রেডিং প্রয়োজনীয়তা থাকলে আপনি CameraX-এ আপনার নিজস্ব এক্সিকিউটর সেট করতে পারেন। সেট করা না থাকলে, CameraX একটি অপ্টিমাইজড ডিফল্ট অভ্যন্তরীণ Executor তৈরি করে এবং ব্যবহার করে। CameraX যে প্ল্যাটফর্ম API-গুলোর উপর নির্মিত, সেগুলোর অনেকগুলোর জন্য হার্ডওয়্যারের সাথে ব্লকিং ইন্টারপ্রসেস কমিউনিকেশন (IPC) প্রয়োজন হয়, যা সাড়া দিতে কখনও কখনও কয়েকশো মিলিসেকেন্ড সময় নিতে পারে। এই কারণে, CameraX শুধুমাত্র ব্যাকগ্রাউন্ড থ্রেড থেকে এই API-গুলোকে কল করে, যা নিশ্চিত করে যে প্রধান থ্রেড ব্লক না হয় এবং UI সাবলীল থাকে। থ্রেড সম্পর্কে আরও পড়ুন ।
আপনার অ্যাপের টার্গেট মার্কেটে যদি লো-এন্ড ডিভাইস অন্তর্ভুক্ত থাকে, তাহলে CameraX একটি ক্যামেরা লিমিটারের মাধ্যমে সেটআপের সময় কমানোর একটি উপায় প্রদান করে। যেহেতু হার্ডওয়্যার কম্পোনেন্টগুলোর সাথে সংযোগ স্থাপন করতে বেশ খানিকটা সময় লাগতে পারে, বিশেষ করে লো-এন্ড ডিভাইসগুলোতে, তাই আপনার অ্যাপের জন্য প্রয়োজনীয় ক্যামেরাগুলোর সেট আপনি নির্দিষ্ট করে দিতে পারেন। CameraX সেটআপের সময় শুধুমাত্র এই ক্যামেরাগুলোর সাথেই সংযোগ স্থাপন করে। উদাহরণস্বরূপ, যদি অ্যাপ্লিকেশনটি শুধুমাত্র পেছনের ক্যামেরা ব্যবহার করে, তবে এটি DEFAULT_BACK_CAMERA দিয়ে এই কনফিগারেশনটি সেট করতে পারে এবং এর ফলে CameraX ল্যাটেন্সি কমানোর জন্য সামনের ক্যামেরাগুলো ইনিশিয়ালাইজ করা এড়িয়ে চলে।
অ্যান্ড্রয়েড বিকাশের ধারণা
এই নির্দেশিকাটি অ্যান্ড্রয়েড ডেভেলপমেন্ট সম্পর্কে সাধারণ ধারণা আছে বলে ধরে নেয়। মৌলিক বিষয়গুলোর বাইরে, নিম্নলিখিত কোডে যাওয়ার আগে কয়েকটি ধারণা বোঝা সহায়ক হবে:
- ভিউ বাইন্ডিং আপনার XML লেআউট ফাইলগুলির জন্য একটি বাইন্ডিং ক্লাস তৈরি করে, যা আপনাকে অ্যাক্টিভিটিগুলিতে আপনার ভিউগুলিকে রেফারেন্স করার সুযোগ দেয়, যেমনটি পরবর্তী কয়েকটি কোড স্নিপেটে দেখানো হয়েছে। ভিউ বাইন্ডিং এবং
findViewById()(ভিউ রেফারেন্স করার পূর্ববর্তী পদ্ধতি)-এর মধ্যে কিছু পার্থক্য রয়েছে, কিন্তু নিম্নলিখিত কোডে আপনি ভিউ বাইন্ডিং লাইনগুলিকে একটি অনুরূপfindViewById()কল দিয়ে প্রতিস্থাপন করতে পারবেন। - অ্যাসিঙ্ক্রোনাস কো-রুটিন হলো কোটলিন ১.৩-এ যুক্ত হওয়া একটি কনকারেন্সি ডিজাইন প্যাটার্ন, যা এমন CameraX মেথডগুলো পরিচালনা করতে ব্যবহার করা যায় যেগুলো একটি
ListenableFutureরিটার্ন করে। জেটপ্যাক কনকারেন্ট লাইব্রেরির ১.১.০ সংস্করণ থেকে এই কাজটি আরও সহজ হয়ে গেছে। আপনার অ্যাপে একটি অ্যাসিঙ্ক্রোনাস কো-রুটিন যোগ করতে:- আপনার Gradle ফাইলে
implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")যোগ করুন। - যে কোনো CameraX কোড যা
ListenableFutureরিটার্ন করে, তা একটিlaunchব্লক বা সাসপেন্ডিং ফাংশনের মধ্যে রাখুন। - যে ফাংশন কলটি একটি
ListenableFutureরিটার্ন করে, সেটিতে একটিawait()কল যোগ করুন। - কো-রুটিন কীভাবে কাজ করে সে সম্পর্কে আরও বিস্তারিত জানতে, ‘স্টার্ট এ কো-রুটিন’ গাইডটি দেখুন।
- আপনার Gradle ফাইলে
সাধারণ পরিস্থিতি স্থানান্তর করুন
এই বিভাগে Camera1 থেকে CameraX-এ সাধারণ সিনারিওগুলো কীভাবে মাইগ্রেট করতে হয় তা ব্যাখ্যা করা হয়েছে। প্রতিটি সিনারিওতে একটি Camera1 ইমপ্লিমেন্টেশন, একটি CameraX CameraProvider ইমপ্লিমেন্টেশন এবং একটি CameraX CameraController ইমপ্লিমেন্টেশন অন্তর্ভুক্ত রয়েছে।
ক্যামেরা নির্বাচন করা
আপনার ক্যামেরা অ্যাপ্লিকেশনে, আপনি প্রথমেই যে সুবিধাগুলো দিতে চাইতে পারেন তার মধ্যে একটি হলো বিভিন্ন ক্যামেরা বেছে নেওয়ার সুযোগ।
ক্যামেরা১
Camera1-এ, আপনি প্রথম পেছনের ক্যামেরাটি খোলার জন্য কোনো প্যারামিটার ছাড়াই Camera.open() কল করতে পারেন, অথবা যে ক্যামেরাটি খুলতে চান তার জন্য একটি ইন্টিজার আইডি পাস করতে পারেন। এটি দেখতে কেমন হতে পারে তার একটি উদাহরণ নিচে দেওয়া হলো:
// Camera1: select a camera from id. // Note: opening the camera is a non-trivial task, and it shouldn't be // called from the main thread, unlike CameraX calls, which can be // on the main thread since CameraX kicks off background threads // internally as needed. private fun safeCameraOpen(id: Int): Boolean { return try { releaseCameraAndPreview() camera = Camera.open(id) true } catch (e: Exception) { Log.e(TAG, "failed to open camera", e) false } } private fun releaseCameraAndPreview() { preview?.setCamera(null) camera?.release() camera = null }
ক্যামেরাএক্স: ক্যামেরা কন্ট্রোলার
CameraX-এ, CameraSelector ক্লাসটি ক্যামেরা নির্বাচনের কাজটি করে। CameraX ডিফল্ট ক্যামেরা ব্যবহারের সাধারণ প্রক্রিয়াটিকে সহজ করে তোলে। আপনি ডিফল্ট ফ্রন্ট ক্যামেরা নাকি ডিফল্ট ব্যাক ক্যামেরা ব্যবহার করতে চান, তা নির্দিষ্ট করে দিতে পারেন। এছাড়াও, CameraX-এর CameraControl অবজেক্টটি আপনাকে আপনার অ্যাপের জন্য জুম লেভেল সেট করার সুযোগ দেয়, ফলে আপনার অ্যাপটি যদি লজিক্যাল ক্যামেরা সমর্থনকারী কোনো ডিভাইসে চলে, তবে এটি সঠিক লেন্সে সুইচ করবে।
CameraController এর সাথে ডিফল্ট ব্যাক ক্যামেরা ব্যবহার করার জন্য CameraX কোডটি নিচে দেওয়া হলো:
// CameraX: select a camera with CameraController var cameraController = LifecycleCameraController(baseContext) val selector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK).build() cameraController.cameraSelector = selector
ক্যামেরাএক্স: ক্যামেরাপ্রোভাইডার
CameraProvider ব্যবহার করে ডিফল্ট ফ্রন্ট ক্যামেরা নির্বাচন করার একটি উদাহরণ এখানে দেওয়া হলো ( CameraController বা CameraProvider উভয়ের সাথেই ফ্রন্ট বা ব্যাক ক্যামেরা ব্যবহার করা যেতে পারে):
// CameraX: select a camera with CameraProvider. // Use await() within a suspend function to get CameraProvider instance. // For more details on await(), see the preceding "Android development concepts" // section. private suspend fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this).await() // Set up UseCases (more on UseCases in later scenarios) var useCases:Array= ... // Set the cameraSelector to use the default front-facing (selfie) // camera. val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA try { // Unbind UseCases before rebinding. cameraProvider.unbindAll() // Bind UseCases to camera. This function returns a camera // object which can be used to perform operations like zoom, // flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, useCases) } catch(exc: Exception) { Log.e(TAG, "UseCase binding failed", exc) } }) ... // Call startCamera in the setup flow of your app, such as in onViewCreated. override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) ... lifecycleScope.launch { startCamera() } }
কোন ক্যামেরাটি নির্বাচিত হবে তার উপর যদি আপনি নিয়ন্ত্রণ রাখতে চান, তবে CameraX-এ CameraProvider ব্যবহার করে getAvailableCameraInfos() কল করার মাধ্যমে সেটিও করা সম্ভব। এটি আপনাকে একটি CameraInfo অবজেক্ট দেয়, যা দিয়ে আপনি isFocusMeteringSupported() এর মতো ক্যামেরার নির্দিষ্ট কিছু প্রোপার্টি পরীক্ষা করতে পারেন। এরপর আপনি CameraInfo.getCameraSelector() মেথড ব্যবহার করে এটিকে একটি CameraSelector এ রূপান্তর করতে পারেন, যা পূর্ববর্তী উদাহরণগুলোতে দেখানো উপায়ে ব্যবহার করা যাবে।
Camera2CameraInfo ক্লাস ব্যবহার করে আপনি প্রতিটি ক্যামেরা সম্পর্কে আরও বিস্তারিত তথ্য পেতে পারেন। আপনার কাঙ্ক্ষিত ক্যামেরার ডেটার জন্য একটি কী (key) দিয়ে getCameraCharacteristic() কল করুন। আপনি যেসব কী-এর জন্য কোয়েরি করতে পারেন, তার একটি তালিকার জন্য CameraCharacteristics ক্লাসটি দেখুন।
এখানে একটি কাস্টম checkFocalLength() ফাংশন ব্যবহার করে একটি উদাহরণ দেওয়া হল যা আপনি নিজেই সংজ্ঞায়িত করতে পারেন:
// CameraX: get a cameraSelector for first camera that matches the criteria // defined in checkFocalLength(). val cameraInfo = cameraProvider.getAvailableCameraInfos() .first { cameraInfo -> val focalLengths = Camera2CameraInfo.from(cameraInfo) .getCameraCharacteristic( CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS ) return checkFocalLength(focalLengths) } val cameraSelector = cameraInfo.getCameraSelector()
একটি প্রিভিউ দেখানো হচ্ছে
বেশিরভাগ ক্যামেরা অ্যাপ্লিকেশনেরই কোনো না কোনো সময়ে স্ক্রিনে ক্যামেরা ফিড দেখানোর প্রয়োজন হয়। Camera1-এর ক্ষেত্রে, আপনাকে লাইফসাইকেল কলব্যাকগুলো সঠিকভাবে পরিচালনা করতে হবে এবং আপনার প্রিভিউয়ের জন্য রোটেশন ও স্কেলিংও নির্ধারণ করতে হবে।
এছাড়াও, Camera1-এ আপনাকে সিদ্ধান্ত নিতে হবে যে প্রিভিউ সারফেস হিসেবে TextureView নাকি SurfaceView ব্যবহার করবেন। উভয় বিকল্পেরই কিছু সুবিধা-অসুবিধা রয়েছে, এবং উভয় ক্ষেত্রেই Camera1-এর জন্য রোটেশন এবং স্কেলিং সঠিকভাবে পরিচালনা করা প্রয়োজন। অন্যদিকে, CameraX-এর PreviewView TextureView এবং SurfaceView উভয়েরই অন্তর্নিহিত ইমপ্লিমেন্টেশন রয়েছে। আপনার ডিভাইসের ধরন এবং অ্যাপটি যে অ্যান্ড্রয়েড সংস্করণে চলছে, তার মতো বিষয়গুলোর উপর নির্ভর করে CameraX সিদ্ধান্ত নেয় কোন ইমপ্লিমেন্টেশনটি সেরা। যদি কোনো একটি ইমপ্লিমেন্টেশন সামঞ্জস্যপূর্ণ হয়, তবে আপনি PreviewView.ImplementationMode ব্যবহার করে আপনার পছন্দটি জানাতে পারেন। COMPATIBLE বিকল্পটি প্রিভিউয়ের জন্য একটি TextureView ব্যবহার করে, এবং PERFORMANCE মানটি একটি SurfaceView ব্যবহার করে (যখন সম্ভব)।
ক্যামেরা১
প্রিভিউ দেখানোর জন্য, আপনাকে android.view.SurfaceHolder.Callback ইন্টারফেসের ইমপ্লিমেন্টেশন সহ আপনার নিজস্ব Preview ক্লাস লিখতে হবে, যা ক্যামেরা হার্ডওয়্যার থেকে অ্যাপ্লিকেশনে ছবির ডেটা পাঠানোর জন্য ব্যবহৃত হয়। এরপর, লাইভ ইমেজ প্রিভিউ শুরু করার আগে, Preview ক্লাসটিকে অবশ্যই Camera অবজেক্টে পাস করতে হবে।
// Camera1: set up a camera preview. class Preview( context: Context, private val camera: Camera ) : SurfaceView(context), SurfaceHolder.Callback { private val holder: SurfaceHolder = holder.apply { addCallback(this@Preview) setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS) } override fun surfaceCreated(holder: SurfaceHolder) { // The Surface has been created, now tell the camera // where to draw the preview. camera.apply { try { setPreviewDisplay(holder) startPreview() } catch (e: IOException) { Log.d(TAG, "error setting camera preview", e) } } } override fun surfaceDestroyed(holder: SurfaceHolder) { // Take care of releasing the Camera preview in your activity. } override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) { // If your preview can change or rotate, take care of those // events here. Make sure to stop the preview before resizing // or reformatting it. if (holder.surface == null) { return // The preview surface does not exist. } // Stop preview before making changes. try { camera.stopPreview() } catch (e: Exception) { // Tried to stop a non-existent preview; nothing to do. } // Set preview size and make any resize, rotate or // reformatting changes here. // Start preview with new settings. camera.apply { try { setPreviewDisplay(holder) startPreview() } catch (e: Exception) { Log.d(TAG, "error starting camera preview", e) } } } } class CameraActivity : AppCompatActivity() { private lateinit var viewBinding: ActivityMainBinding private var camera: Camera? = null private var preview: Preview? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(viewBinding.root) // Create an instance of Camera. camera = getCameraInstance() preview = camera?.let { // Create the Preview view. Preview(this, it) } // Set the Preview view as the content of the activity. val cameraPreview: FrameLayout = viewBinding.cameraPreview cameraPreview.addView(preview) } }
ক্যামেরাএক্স: ক্যামেরা কন্ট্রোলার
CameraX-এ, ডেভেলপার হিসেবে আপনার পরিচালনা করার মতো কাজ অনেক কম। আপনি যদি CameraController ব্যবহার করেন, তাহলে আপনাকে অবশ্যই PreviewView ও ব্যবহার করতে হবে। এর মানে হলো, Preview UseCase এতে অন্তর্নিহিত থাকে, ফলে সেটআপের কাজ অনেক কমে যায়।
// CameraX: set up a camera preview with a CameraController. class MainActivity : AppCompatActivity() { private lateinit var viewBinding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(viewBinding.root) // Create the CameraController and set it on the previewView. var cameraController = LifecycleCameraController(baseContext) cameraController.bindToLifecycle(this) val previewView: PreviewView = viewBinding.cameraPreview previewView.controller = cameraController } }
ক্যামেরাএক্স: ক্যামেরাপ্রোভাইডার
CameraX-এর CameraProvider ব্যবহার করলে আপনাকে PreviewView ব্যবহার করতে হবে না, কিন্তু এটি Camera1-এর তুলনায় প্রিভিউ সেটআপকে অনেক সহজ করে তোলে। প্রদর্শনের উদ্দেশ্যে, এই উদাহরণে একটি PreviewView ব্যবহার করা হয়েছে, কিন্তু আপনার যদি আরও জটিল প্রয়োজন হয়, তবে আপনি setSurfaceProvider() এ পাস করার জন্য একটি কাস্টম SurfaceProvider লিখতে পারেন।
এখানে, CameraController এর মতো Preview UseCase অন্তর্নির্মিত নয়, তাই আপনাকে এটি সেট আপ করতে হবে:
// CameraX: set up a camera preview with a CameraProvider. // Use await() within a suspend function to get CameraProvider instance. // For more details on await(), see the preceding "Android development concepts" // section. private suspend fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this).await() // Create Preview UseCase. val preview = Preview.Builder() .build() .also { it.setSurfaceProvider( viewBinding.viewFinder.surfaceProvider ) } // Select default back camera. val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { // Unbind UseCases before rebinding. cameraProvider.unbindAll() // Bind UseCases to camera. This function returns a camera // object which can be used to perform operations like zoom, // flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, useCases) } catch(exc: Exception) { Log.e(TAG, "UseCase binding failed", exc) } }) ... // Call startCamera() in the setup flow of your app, such as in onViewCreated. override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) ... lifecycleScope.launch { startCamera() } }
ট্যাপ-টু-ফোকাস
যখন স্ক্রিনে ক্যামেরা প্রিভিউ দেখা যায়, তখন ব্যবহারকারী প্রিভিউটির উপর ট্যাপ করলে ফোকাস পয়েন্ট সেট করা একটি প্রচলিত উপায়।
ক্যামেরা১
Camera1-এ ট্যাপ-টু-ফোকাস প্রয়োগ করতে, Camera কোথায় ফোকাস করার চেষ্টা করবে তা নির্দেশ করার জন্য আপনাকে সর্বোত্তম ফোকাস Area (Optimal focus Area) গণনা করতে হবে। এই Area setFocusAreas() ফাংশনে পাস করা হয়। এছাড়াও, আপনাকে Camera একটি সামঞ্জস্যপূর্ণ ফোকাস মোড সেট করতে হবে। ফোকাস এলাকা কেবল তখনই কার্যকর হয় যখন বর্তমান ফোকাস মোড FOCUS_MODE_AUTO , FOCUS_MODE_MACRO , FOCUS_MODE_CONTINUOUS_VIDEO , অথবা FOCUS_MODE_CONTINUOUS_PICTURE হয়।
প্রতিটি Area হলো একটি নির্দিষ্ট ওয়েট (weight) সহ একটি আয়তক্ষেত্র। ওয়েট হলো ১ থেকে ১০০০-এর মধ্যে একটি মান, এবং একাধিক Areas সেট করা থাকলে ফোকাসের অগ্রাধিকার নির্ধারণ করতে এটি ব্যবহৃত হয়। এই উদাহরণে শুধুমাত্র একটি Area ব্যবহার করা হয়েছে, তাই ওয়েটের মানটি গুরুত্বপূর্ণ নয়। আয়তক্ষেত্রটির স্থানাঙ্ক -১০০০ থেকে ১০০০ পর্যন্ত বিস্তৃত। উপরের বাম বিন্দুটি হলো (-১০০০, -১০০০)। নিচের ডান বিন্দুটি হলো (১০০০, ১০০০)। দিকটি সেন্সরের অভিমুখের সাপেক্ষে নির্ধারিত হয়, অর্থাৎ সেন্সর যা দেখতে পায়। Camera.setDisplayOrientation() এর ঘূর্ণন বা প্রতিবিম্বন দ্বারা দিকটি প্রভাবিত হয় না, তাই আপনাকে টাচ ইভেন্টের স্থানাঙ্কগুলোকে সেন্সরের স্থানাঙ্কে রূপান্তর করতে হবে।
// Camera1: implement tap-to-focus. class TapToFocusHandler : Camera.AutoFocusCallback { private fun handleFocus(event: MotionEvent) { val camera = camera ?: return val parameters = try { camera.getParameters() } catch (e: RuntimeException) { return } // Cancel previous auto-focus function, if one was in progress. camera.cancelAutoFocus() // Create focus Area. val rect = calculateFocusAreaCoordinates(event.x, event.y) val weight = 1 // This value's not important since there's only 1 Area. val focusArea = Camera.Area(rect, weight) // Set the focus parameters. parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO) parameters.setFocusAreas(listOf(focusArea)) // Set the parameters back on the camera and initiate auto-focus. camera.setParameters(parameters) camera.autoFocus(this) } private fun calculateFocusAreaCoordinates(x: Int, y: Int) { // Define the size of the Area to be returned. This value // should be optimized for your app. val focusAreaSize = 100 // You must define functions to rotate and scale the x and y values to // be values between 0 and 1, where (0, 0) is the upper left-hand side // of the preview, and (1, 1) is the lower right-hand side. val normalizedX = (rotateAndScaleX(x) - 0.5) * 2000 val normalizedY = (rotateAndScaleY(y) - 0.5) * 2000 // Calculate the values for left, top, right, and bottom of the Rect to // be returned. If the Rect would extend beyond the allowed values of // (-1000, -1000, 1000, 1000), then crop the values to fit inside of // that boundary. val left = max(normalizedX - (focusAreaSize / 2), -1000) val top = max(normalizedY - (focusAreaSize / 2), -1000) val right = min(left + focusAreaSize, 1000) val bottom = min(top + focusAreaSize, 1000) return Rect(left, top, left + focusAreaSize, top + focusAreaSize) } override fun onAutoFocus(focused: Boolean, camera: Camera) { if (!focused) { Log.d(TAG, "tap-to-focus failed") } } }
ক্যামেরাএক্স: ক্যামেরা কন্ট্রোলার
CameraController স্বয়ংক্রিয়ভাবে ট্যাপ-টু-ফোকাস পরিচালনা করার জন্য PreviewView এর টাচ ইভেন্টগুলো শোনে। আপনি setTapToFocusEnabled() ব্যবহার করে ট্যাপ-টু-ফোকাস চালু ও বন্ধ করতে পারেন এবং এর সংশ্লিষ্ট গেটার isTapToFocusEnabled() দিয়ে মানটি পরীক্ষা করতে পারেন।
getTapToFocusState() মেথডটি CameraController এর ফোকাস অবস্থার পরিবর্তন ট্র্যাক করার জন্য একটি LiveData অবজেক্ট রিটার্ন করে।
// CameraX: track the state of tap-to-focus over the Lifecycle of a PreviewView, // with handlers you can define for focused, not focused, and failed states. val tapToFocusStateObserver = Observer{ state -> when (state) { CameraController.TAP_TO_FOCUS_NOT_STARTED -> Log.d(TAG, "tap-to-focus init") CameraController.TAP_TO_FOCUS_STARTED -> Log.d(TAG, "tap-to-focus started") CameraController.TAP_TO_FOCUS_FOCUSED -> Log.d(TAG, "tap-to-focus finished (focus successful)") CameraController.TAP_TO_FOCUS_NOT_FOCUSED -> Log.d(TAG, "tap-to-focus finished (focused unsuccessful)") CameraController.TAP_TO_FOCUS_FAILED -> Log.d(TAG, "tap-to-focus failed") } } cameraController.getTapToFocusState().observe(this, tapToFocusStateObserver)
ক্যামেরাএক্স: ক্যামেরাপ্রোভাইডার
CameraProvider ব্যবহার করার সময়, ট্যাপ-টু-ফোকাস চালু করার জন্য কিছু সেটআপের প্রয়োজন হয়। এই উদাহরণটি ধরে নিচ্ছে যে আপনি PreviewView ব্যবহার করছেন। যদি তা না হয়, তবে আপনার কাস্টম Surface জন্য এই লজিকটি পরিবর্তন করে নিতে হবে।
PreviewView ব্যবহার করার ধাপগুলো নিচে দেওয়া হলো:
- ট্যাপ ইভেন্টগুলো পরিচালনা করার জন্য একটি জেসচার ডিটেক্টর সেট আপ করুন।
- ট্যাপ ইভেন্টের মাধ্যমে,
MeteringPointFactory.createPoint()ব্যবহার করে একটিMeteringPointতৈরি করুন। -
MeteringPointব্যবহার করে একটিFocusMeteringActionতৈরি করুন। - আপনার
Cameraউপর থাকাCameraControlঅবজেক্টটি (যাbindToLifecycle()থেকে রিটার্ন করা হয়েছে) ব্যবহার করে,FocusMeteringActionপাস করেstartFocusAndMetering()ফাংশনটি কল করুন। - (ঐচ্ছিক)
FocusMeteringResultএর প্রতি সাড়া দিন। -
PreviewView.setOnTouchListener()-এ টাচ ইভেন্টে সাড়া দেওয়ার জন্য আপনার জেসচার ডিটেক্টর সেট করুন।
// CameraX: implement tap-to-focus with CameraProvider. // Define a gesture detector to respond to tap events and call // startFocusAndMetering on CameraControl. If you want to use a // coroutine with await() to check the result of focusing, see the // preceding "Android development concepts" section. val gestureDetector = GestureDetectorCompat(context, object : SimpleOnGestureListener() { override fun onSingleTapUp(e: MotionEvent): Boolean { val previewView = previewView ?: return val camera = camera ?: return val meteringPointFactory = previewView.meteringPointFactory val focusPoint = meteringPointFactory.createPoint(e.x, e.y) val meteringAction = FocusMeteringAction .Builder(meteringPoint).build() lifecycleScope.launch { val focusResult = camera.cameraControl .startFocusAndMetering(meteringAction).await() if (!result.isFocusSuccessful()) { Log.d(TAG, "tap-to-focus failed") } } } } ) ... // Set the gestureDetector in a touch listener on the PreviewView. previewView.setOnTouchListener { _, event -> // See pinch-to-zoom scenario for scaleGestureDetector definition. var didConsume = scaleGestureDetector.onTouchEvent(event) if (!scaleGestureDetector.isInProgress) { didConsume = gestureDetector.onTouchEvent(event) } didConsume }
চিমটি দিয়ে জুম করুন
প্রিভিউ জুম ইন এবং জুম আউট করা হলো ক্যামেরা প্রিভিউয়ের আরেকটি সাধারণ সরাসরি ব্যবহার। ডিভাইসগুলোতে ক্যামেরার সংখ্যা বাড়ার সাথে সাথে, ব্যবহারকারীরা এটাও আশা করেন যে জুম করার ফলে সেরা ফোকাল লেংথের লেন্সটি স্বয়ংক্রিয়ভাবে নির্বাচিত হবে।
ক্যামেরা১
Camera1 ব্যবহার করে জুম করার দুটি উপায় আছে। Camera.startSmoothZoom() মেথডটি বর্তমান জুম লেভেল থেকে আপনার দেওয়া জুম লেভেলে অ্যানিমেট করে। Camera.Parameters.setZoom() মেথডটি সরাসরি আপনার দেওয়া জুম লেভেলে চলে যায়। এই দুটির যেকোনো একটি ব্যবহার করার আগে, আপনার ক্যামেরাতে প্রয়োজনীয় সংশ্লিষ্ট জুম মেথডগুলো উপলব্ধ আছে কিনা তা নিশ্চিত করতে যথাক্রমে isSmoothZoomSupported() বা isZoomSupported() কল করুন।
পিঞ্চ-টু-জুম প্রয়োগ করার জন্য, এই উদাহরণে setZoom() ব্যবহার করা হয়েছে, কারণ পিঞ্চ জেসচারটি ঘটার সাথে সাথে প্রিভিউ সারফেসের টাচ লিসেনারটি ক্রমাগত ইভেন্ট ফায়ার করতে থাকে, ফলে এটি প্রতিবারই জুম লেভেল তাৎক্ষণিকভাবে আপডেট করে। ZoomTouchListener ক্লাসটি এই সেকশনের পরবর্তী অংশে সংজ্ঞায়িত করা হয়েছে, এবং আপনার প্রিভিউ সারফেসের টাচ লিসেনারের জন্য এটিকে একটি কলব্যাক হিসেবে সেট করতে হবে।
// Camera1: implement pinch-to-zoom. // Define a scale gesture detector to respond to pinch events and call // setZoom on Camera.Parameters. val scaleGestureDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.OnScaleGestureListener { override fun onScale(detector: ScaleGestureDetector): Boolean { val camera = camera ?: return false val parameters = try { camera.parameters } catch (e: RuntimeException) { return false } // In case there is any focus happening, stop it. camera.cancelAutoFocus() // Set the zoom level on the Camera.Parameters, and set // the Parameters back onto the Camera. val currentZoom = parameters.zoom parameters.setZoom(detector.scaleFactor * currentZoom) camera.setParameters(parameters) return true } } ) // Define a View.OnTouchListener to attach to your preview view. class ZoomTouchListener : View.OnTouchListener { override fun onTouch(v: View, event: MotionEvent): Boolean = scaleGestureDetector.onTouchEvent(event) } // Set a ZoomTouchListener to handle touch events on your preview view // if zoom is supported by the current camera. if (camera.getParameters().isZoomSupported()) { view.setOnTouchListener(ZoomTouchListener()) }
ক্যামেরাএক্স: ক্যামেরা কন্ট্রোলার
ট্যাপ-টু-ফোকাসের মতোই, CameraController স্বয়ংক্রিয়ভাবে পিঞ্চ-টু-জুম পরিচালনা করার জন্য PreviewView-এর টাচ ইভেন্টগুলো শোনে। আপনি setPinchToZoomEnabled() ব্যবহার করে পিঞ্চ-টু-জুম চালু ও বন্ধ করতে পারেন এবং এর সংশ্লিষ্ট গেটার isPinchToZoomEnabled() দিয়ে মানটি পরীক্ষা করতে পারেন।
getZoomState() মেথডটি CameraController এর ZoomState এর পরিবর্তন ট্র্যাক করার জন্য একটি LiveData অবজেক্ট রিটার্ন করে।
// CameraX: track the state of pinch-to-zoom over the Lifecycle of // a PreviewView, logging the linear zoom ratio. val pinchToZoomStateObserver = Observer{ state -> val zoomRatio = state.getZoomRatio() Log.d(TAG, "ptz-zoom-ratio $zoomRatio") } cameraController.getZoomState().observe(this, pinchToZoomStateObserver)
ক্যামেরাএক্স: ক্যামেরাপ্রোভাইডার
CameraProvider সাথে পিঞ্চ-টু-জুম চালু করতে কিছু সেটআপের প্রয়োজন। আপনি যদি PreviewView ব্যবহার না করেন, তবে আপনার কাস্টম Surface এ প্রয়োগ করার জন্য লজিকটি পরিবর্তন করে নিতে হবে।
PreviewView ব্যবহার করার ধাপগুলো নিচে দেওয়া হলো:
- পিঞ্চ ইভেন্টগুলো পরিচালনা করার জন্য একটি স্কেল জেসচার ডিটেক্টর সেট আপ করুন।
-
Camera.CameraInfoঅবজেক্ট থেকেZoomStateনিন, যেখানেbindToLifecycle()কল করলেCameraইনস্ট্যান্সটি রিটার্ন হয়। - যদি
ZoomStateএzoomRatioভ্যালু থাকে, তাহলে সেটিকে বর্তমান জুম রেশিও হিসেবে সেভ করুন। যদিZoomStateএ কোনোzoomRatioনা থাকে, তাহলে ক্যামেরার ডিফল্ট জুম রেট (1.0) ব্যবহার করুন। - নতুন জুম রেশিও নির্ধারণ করতে বর্তমান জুম রেশিওর সাথে
scaleFactorএর গুণফল নিন এবং সেই মানটিCameraControl.setZoomRatio()-এর মধ্যে পাস করুন। -
PreviewView.setOnTouchListener()-এ টাচ ইভেন্টে সাড়া দেওয়ার জন্য আপনার জেসচার ডিটেক্টর সেট করুন।
// CameraX: implement pinch-to-zoom with CameraProvider. // Define a scale gesture detector to respond to pinch events and call // setZoomRatio on CameraControl. val scaleGestureDetector = ScaleGestureDetector(context, object : SimpleOnGestureListener() { override fun onScale(detector: ScaleGestureDetector): Boolean { val camera = camera ?: return val zoomState = camera.cameraInfo.zoomState val currentZoomRatio: Float = zoomState.value?.zoomRatio ?: 1f camera.cameraControl.setZoomRatio( detector.scaleFactor * currentZoomRatio ) } } ) ... // Set the scaleGestureDetector in a touch listener on the PreviewView. previewView.setOnTouchListener { _, event -> var didConsume = scaleGestureDetector.onTouchEvent(event) if (!scaleGestureDetector.isInProgress) { // See pinch-to-zoom scenario for gestureDetector definition. didConsume = gestureDetector.onTouchEvent(event) } didConsume }
ছবি তোলা
এই অংশে দেখানো হয়েছে কীভাবে ছবি তোলা শুরু করতে হয়, তা শাটার বাটন চাপে হোক, টাইমার শেষ হওয়ার পরে হোক, বা আপনার পছন্দের অন্য কোনো ঘটনার ভিত্তিতেই হোক।
ক্যামেরা১
Camera1-এ, ছবির ডেটা অনুরোধ করা হলে তা পরিচালনা করার জন্য আপনাকে প্রথমে একটি Camera.PictureCallback সংজ্ঞায়িত করতে হবে। JPEG ছবির ডেটা পরিচালনার জন্য PictureCallback এর একটি সহজ উদাহরণ নিচে দেওয়া হলো:
// Camera1: define a Camera.PictureCallback to handle JPEG data. private val picture = Camera.PictureCallback { data, _ -> val pictureFile: File = getOutputMediaFile(MEDIA_TYPE_IMAGE) ?: run { Log.d(TAG, "error creating media file, check storage permissions") return@PictureCallback } try { val fos = FileOutputStream(pictureFile) fos.write(data) fos.close() } catch (e: FileNotFoundException) { Log.d(TAG, "file not found", e) } catch (e: IOException) { Log.d(TAG, "error accessing file", e) } }
এরপর, যখনই আপনি একটি ছবি তুলতে চাইবেন, আপনার Camera ইনস্ট্যান্সের takePicture() মেথডটি কল করবেন। এই takePicture() মেথডটিতে বিভিন্ন ডেটা টাইপের জন্য তিনটি ভিন্ন প্যারামিটার রয়েছে। প্রথম প্যারামিটারটি একটি ShutterCallback এর জন্য (যা এই উদাহরণে সংজ্ঞায়িত করা হয়নি)। দ্বিতীয় প্যারামিটারটি হলো একটি PictureCallback যা ক্যামেরার র (uncompressed) ডেটা পরিচালনা করে। তৃতীয় প্যারামিটারটি এই উদাহরণে ব্যবহৃত হয়েছে, কারণ এটি JPEG ইমেজ ডেটা পরিচালনা করার জন্য একটি PictureCallback ।
// Camera1: call takePicture on Camera instance, passing our PictureCallback. camera?.takePicture(null, null, picture)
ক্যামেরাএক্স: ক্যামেরা কন্ট্রোলার
CameraX-এর CameraController ছবি তোলার ক্ষেত্রে Camera1-এর সরলতা বজায় রাখে নিজস্ব একটি takePicture() মেথড প্রয়োগ করার মাধ্যমে। এখানে, একটি MediaStore এন্ট্রি কনফিগার করতে এবং সেখানে সংরক্ষণ করার জন্য একটি ছবি তুলতে একটি ফাংশন সংজ্ঞায়িত করুন।
// CameraX: define a function that uses CameraController to take a photo. private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" private fun takePhoto() { // Create time stamped name and MediaStore entry. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, name) put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image") } } // Create output options object which contains file + metadata. val outputOptions = ImageCapture.OutputFileOptions .Builder(context.getContentResolver(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) .build() // Set up image capture listener, which is triggered after photo has // been taken. cameraController.takePicture( outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onError(e: ImageCaptureException) { Log.e(TAG, "photo capture failed", e) } override fun onImageSaved( output: ImageCapture.OutputFileResults ) { val msg = "Photo capture succeeded: ${output.savedUri}" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) } } ) }
ক্যামেরাএক্স: ক্যামেরাপ্রোভাইডার
CameraProvider ব্যবহার করে ছবি তোলার পদ্ধতি CameraController এর মতোই, তবে takePicture() কল করার জন্য একটি অবজেক্ট পেতে আপনাকে প্রথমে একটি ImageCapture UseCase তৈরি এবং বাইন্ড করতে হবে।
// CameraX: create and bind an ImageCapture UseCase. // Make a reference to the ImageCapture UseCase at a scope that can be accessed // throughout the camera logic in your app. private var imageCapture: ImageCapture? = null ... // Create an ImageCapture instance (can be added with other // UseCase definitions). imageCapture = ImageCapture.Builder().build() ... // Bind UseCases to camera (adding imageCapture along with preview here, but // preview is not required to use imageCapture). This function returns a camera // object which can be used to perform operations like zoom, flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageCapture)
এরপর, যখনই আপনি একটি ছবি তুলতে চাইবেন, আপনি ImageCapture.takePicture() কল করতে পারেন। takePhoto() ফাংশনের একটি সম্পূর্ণ উদাহরণের জন্য এই বিভাগের CameraController কোডটি দেখুন।
// CameraX: define a function that uses CameraController to take a photo. private fun takePhoto() { // Get a stable reference of the modifiable ImageCapture UseCase. val imageCapture = imageCapture ?: return ... // Call takePicture on imageCapture instance. imageCapture.takePicture( ... ) }
ভিডিও রেকর্ড করা
ভিডিও রেকর্ড করা এখন পর্যন্ত আলোচিত পরিস্থিতিগুলোর চেয়ে অনেক বেশি জটিল। এই প্রক্রিয়ার প্রতিটি অংশ সাধারণত একটি নির্দিষ্ট ক্রমে সঠিকভাবে প্রস্তুত করতে হয়। এছাড়াও, ভিডিও এবং অডিও সিঙ্কে আছে কিনা তা আপনাকে যাচাই করতে হতে পারে অথবা ডিভাইসের অতিরিক্ত অসঙ্গতিগুলোও সামলাতে হতে পারে।
আপনি দেখতেই পাবেন, CameraX আবারও আপনার জন্য এই জটিলতার অনেকটাই সামলে নেয়।
ক্যামেরা১
Camera1 ব্যবহার করে ভিডিও ধারণ করার জন্য Camera এবং MediaRecorder এর সতর্ক ব্যবস্থাপনা প্রয়োজন, এবং মেথডগুলোকে একটি নির্দিষ্ট ক্রমে কল করতে হবে। আপনার অ্যাপ্লিকেশনটি সঠিকভাবে কাজ করার জন্য আপনাকে অবশ্যই এই ক্রমটি অনুসরণ করতে হবে:
- ক্যামেরাটি খুলুন।
- একটি প্রিভিউ প্রস্তুত করুন এবং শুরু করুন (যদি আপনার অ্যাপে ভিডিও রেকর্ড হতে দেখা যায়, যা সাধারণত হয়ে থাকে)।
-
MediaRecorderব্যবহারের জন্য ক্যামেরাটি আনলক করতেCamera.unlock()কল করুন। -
MediaRecorderএ এই মেথডগুলো কল করে রেকর্ডিং কনফিগার করুন:-
setCamera(camera)ব্যবহার করে আপনারCameraইনস্ট্যান্সটি সংযুক্ত করুন। -
setAudioSource(MediaRecorder.AudioSource.CAMCORDER)কল করুন। -
setVideoSource(MediaRecorder.VideoSource.CAMERA)কল করুন। - কোয়ালিটি সেট করতে
setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P))কল করুন। সমস্ত কোয়ালিটি অপশনের জন্যCamcorderProfileদেখুন। -
setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())কল করুন। - আপনার অ্যাপে যদি ভিডিওটির প্রিভিউ থাকে, তাহলে
setPreviewDisplay(preview?.holder?.surface)কল করুন। -
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)কল করুন। -
setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)কল করুন। - কল
setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)। - আপনার
MediaRecorderএর কনফিগারেশন চূড়ান্ত করতেprepare()ফাংশনটি কল করুন।
-
- রেকর্ডিং শুরু করতে
MediaRecorder.start()কল করুন। - রেকর্ডিং বন্ধ করতে, এই মেথডগুলো কল করুন। আবার, এই নির্দিষ্ট ক্রমটি অনুসরণ করুন:
-
MediaRecorder.stop()কল করুন। - ঐচ্ছিকভাবে,
MediaRecorder.reset()কল করে বর্তমানMediaRecorderকনফিগারেশনটি মুছে ফেলুন। -
MediaRecorder.release()কল করুন। -
Camera.lock()কল করে ক্যামেরাটি লক করুন, যাতে ভবিষ্যতেরMediaRecorderসেশনগুলো এটি ব্যবহার করতে পারে।
-
- প্রিভিউ বন্ধ করতে
Camera.stopPreview()কল করুন। - অবশেষে,
Cameraমুক্ত করতে যাতে অন্যান্য প্রসেসগুলো এটি ব্যবহার করতে পারে,Camera.release()কল করুন।
এই সবগুলো ধাপ একত্রিত করলে যা হয় তা হলো:
// Camera1: set up a MediaRecorder and a function to start and stop video // recording. // Make a reference to the MediaRecorder at a scope that can be accessed // throughout the camera logic in your app. private var mediaRecorder: MediaRecorder? = null private var isRecording = false ... private fun prepareMediaRecorder(): Boolean { mediaRecorder = MediaRecorder() // Unlock and set camera to MediaRecorder. camera?.unlock() mediaRecorder?.run { setCamera(camera) // Set the audio and video sources. setAudioSource(MediaRecorder.AudioSource.CAMCORDER) setVideoSource(MediaRecorder.VideoSource.CAMERA) // Set a CamcorderProfile (requires API Level 8 or higher). setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH)) // Set the output file. setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()) // Set the preview output. setPreviewDisplay(preview?.holder?.surface) setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT) setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT) // Prepare configured MediaRecorder. return try { prepare() true } catch (e: IllegalStateException) { Log.d(TAG, "preparing MediaRecorder failed", e) releaseMediaRecorder() false } catch (e: IOException) { Log.d(TAG, "setting MediaRecorder file failed", e) releaseMediaRecorder() false } } return false } private fun releaseMediaRecorder() { mediaRecorder?.reset() mediaRecorder?.release() mediaRecorder = null camera?.lock() } private fun startStopVideo() { if (isRecording) { // Stop recording and release camera. mediaRecorder?.stop() releaseMediaRecorder() camera?.lock() isRecording = false // This is a good place to inform user that video recording has stopped. } else { // Initialize video camera. if (prepareVideoRecorder()) { // Camera is available and unlocked, MediaRecorder is prepared, now // you can start recording. mediaRecorder?.start() isRecording = true // This is a good place to inform the user that recording has // started. } else { // Prepare didn't work, release the camera. releaseMediaRecorder() // Inform user here. } } }
ক্যামেরাএক্স: ক্যামেরা কন্ট্রোলার
CameraX-এর CameraController ব্যবহার করে, আপনি ImageCapture , VideoCapture , এবং ImageAnalysis UseCase গুলো স্বাধীনভাবে টগল করতে পারেন, যতক্ষণ পর্যন্ত UseCase-গুলোর তালিকাটি একই সাথে ব্যবহার করা যায় । ImageCapture এবং ImageAnalysis UseCase গুলো ডিফল্টরূপে সক্রিয় থাকে, যে কারণে ছবি তোলার জন্য আপনার setEnabledUseCases() কল করার প্রয়োজন হয়নি।
ভিডিও রেকর্ডিংয়ের জন্য CameraController ব্যবহার করতে হলে, প্রথমে আপনাকে setEnabledUseCases() ব্যবহার করে VideoCapture UseCase অনুমতি দিতে হবে।
// CameraX: Enable VideoCapture UseCase on CameraController. cameraController.setEnabledUseCases(VIDEO_CAPTURE);
যখন আপনি ভিডিও রেকর্ডিং শুরু করতে চান, তখন আপনি CameraController.startRecording() ফাংশনটি কল করতে পারেন। এই ফাংশনটি রেকর্ড করা ভিডিও একটি File সংরক্ষণ করতে পারে, যেমনটি আপনি নিম্নলিখিত উদাহরণে দেখতে পাচ্ছেন। এছাড়াও, সফল এবং ত্রুটিপূর্ণ কলব্যাকগুলো পরিচালনা করার জন্য আপনাকে একটি Executor এবং OnVideoSavedCallback ইমপ্লিমেন্ট করে এমন একটি ক্লাস পাস করতে হবে। রেকর্ডিং শেষ হলে, CameraController.stopRecording() কল করুন।
দ্রষ্টব্য: আপনি যদি CameraX 1.3.0-alpha02 বা তার পরবর্তী সংস্করণ ব্যবহার করেন, তাহলে একটি অতিরিক্ত AudioConfig প্যারামিটার রয়েছে যা আপনাকে আপনার ভিডিওতে অডিও রেকর্ডিং চালু বা বন্ধ করতে দেয়। অডিও রেকর্ডিং চালু করতে, আপনাকে নিশ্চিত করতে হবে যে আপনার মাইক্রোফোন ব্যবহারের অনুমতি আছে। এছাড়াও, 1.3.0-alpha02 সংস্করণ থেকে stopRecording() মেথডটি সরিয়ে ফেলা হয়েছে এবং startRecording() একটি Recording অবজেক্ট রিটার্ন করে যা ভিডিও রেকর্ডিং পজ, রিজুম এবং স্টপ করার জন্য ব্যবহার করা যেতে পারে।
// CameraX: implement video capture with CameraController. private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" // Define a VideoSaveCallback class for handling success and error states. class VideoSaveCallback : OnVideoSavedCallback { override fun onVideoSaved(outputFileResults: OutputFileResults) { val msg = "Video capture succeeded: ${outputFileResults.savedUri}" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) } override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) { Log.d(TAG, "error saving video: $message", cause) } } private fun startStopVideo() { if (cameraController.isRecording()) { // Stop the current recording session. cameraController.stopRecording() return } // Define the File options for saving the video. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val outputFileOptions = OutputFileOptions .Builder(File(this.filesDir, name)) .build() // Call startRecording on the CameraController. cameraController.startRecording( outputFileOptions, ContextCompat.getMainExecutor(this), VideoSaveCallback() ) }
ক্যামেরাএক্স: ক্যামেরাপ্রোভাইডার
আপনি যদি একটি CameraProvider ব্যবহার করেন, তাহলে আপনাকে একটি VideoCapture UseCase তৈরি করতে হবে এবং একটি Recorder অবজেক্ট পাস করতে হবে। Recorder.Builder এ, আপনি ভিডিওর কোয়ালিটি এবং ঐচ্ছিকভাবে একটি FallbackStrategy সেট করতে পারেন, যা এমন পরিস্থিতি সামাল দেয় যখন কোনো ডিভাইস আপনার কাঙ্ক্ষিত কোয়ালিটির স্পেসিফিকেশন পূরণ করতে পারে না। এরপর আপনার অন্যান্য UseCase গুলোর সাথে VideoCapture ইনস্ট্যান্সটিকে CameraProvider সাথে বাইন্ড করুন।
// CameraX: create and bind a VideoCapture UseCase with CameraProvider. // Make a reference to the VideoCapture UseCase and Recording at a // scope that can be accessed throughout the camera logic in your app. private lateinit var videoCapture: VideoCaptureprivate var recording: Recording? = null ... // Create a Recorder instance to set on a VideoCapture instance (can be // added with other UseCase definitions). val recorder = Recorder.Builder() .setQualitySelector(QualitySelector.from(Quality.FHD)) .build() videoCapture = VideoCapture.withOutput(recorder) ... // Bind UseCases to camera (adding videoCapture along with preview here, but // preview is not required to use videoCapture). This function returns a camera // object which can be used to perform operations like zoom, flash, and focus. var camera = cameraProvider.bindToLifecycle( this, cameraSelector, preview, videoCapture)
এই পর্যায়ে, videoCapture.output প্রপার্টির মাধ্যমে Recorder অ্যাক্সেস করা যায়। Recorder File , ParcelFileDescriptor , বা MediaStore এ সেভ করা ভিডিও রেকর্ডিং শুরু করতে পারে। এই উদাহরণে MediaStore ব্যবহার করা হয়েছে।
Recorder প্রস্তুত করার জন্য এতে কয়েকটি মেথড রয়েছে। MediaStore আউটপুট অপশনগুলো সেট করতে prepareRecording() কল করুন। যদি আপনার অ্যাপের ডিভাইসের মাইক্রোফোন ব্যবহারের অনুমতি থাকে, তাহলে withAudioEnabled() কল করুন। এরপর, রেকর্ডিং শুরু করতে start() কল করুন এবং ভিডিও রেকর্ড ইভেন্টগুলো হ্যান্ডেল করার জন্য একটি কনটেক্সট ও একটি Consumer<VideoRecordEvent> ইভেন্ট লিসেনার পাস করুন। রেকর্ডিং সফল হলে, রিটার্ন করা Recording ব্যবহার করে রেকর্ডিং পজ, রিজুম বা স্টপ করা যাবে।
// CameraX: implement video capture with CameraProvider. private val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS" private fun startStopVideo() { val videoCapture = this.videoCapture ?: return if (recording != null) { // Stop the current recording session. recording.stop() recording = null return } // Create and start a new recording session. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, name) put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4") if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video") } } val mediaStoreOutputOptions = MediaStoreOutputOptions .Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI) .setContentValues(contentValues) .build() recording = videoCapture.output .prepareRecording(this, mediaStoreOutputOptions) .withAudioEnabled() .start(ContextCompat.getMainExecutor(this)) { recordEvent -> when(recordEvent) { is VideoRecordEvent.Start -> { viewBinding.videoCaptureButton.apply { text = getString(R.string.stop_capture) isEnabled = true } } is VideoRecordEvent.Finalize -> { if (!recordEvent.hasError()) { val msg = "Video capture succeeded: " + "${recordEvent.outputResults.outputUri}" Toast.makeText( baseContext, msg, Toast.LENGTH_SHORT ).show() Log.d(TAG, msg) } else { recording?.close() recording = null Log.e(TAG, "video capture ends with error", recordEvent.error) } viewBinding.videoCaptureButton.apply { text = getString(R.string.start_capture) isEnabled = true } } } } }
অতিরিক্ত সম্পদ
আমাদের ক্যামেরা স্যাম্পলস গিটহাব রিপোজিটরিতে বেশ কয়েকটি সম্পূর্ণ CameraX অ্যাপ রয়েছে। এই স্যাম্পলগুলো আপনাকে দেখাবে যে এই গাইডের সিনারিওগুলো কীভাবে একটি পূর্ণাঙ্গ অ্যান্ড্রয়েড অ্যাপে খাপ খায়।
CameraX-এ স্থানান্তরিত হতে আপনার যদি অতিরিক্ত সহায়তার প্রয়োজন হয় অথবা অ্যান্ড্রয়েড ক্যামেরা এপিআই-এর সেট সম্পর্কে কোনো প্রশ্ন থাকে, তাহলে অনুগ্রহ করে CameraX ডিসকাশন গ্রুপে আমাদের সাথে যোগাযোগ করুন।