Android Camera2 API من الغلاية



منذ وقت ليس ببعيد ، كنت مشغولاً بجهازي الآلي ، في محاولة لوضع هاتف ذكي يعمل بنظام التشغيل ANDROID. كانت مهمتي هي جعل العربة متقدمة تطوريًا. حتى تستطيع ، إذا جاز التعبير ، أن تشعر بالعالم بأجهزته ، وأن تنظر إليه بعينها (الكاميرا) ، وتسمع بميكروفون ، وأقسم السماعة الخارجية. بطبيعة الحال ، لم تكن موارد AVR كافية لذلك ، وبالتالي انتقل المتحكم الموجود على العربة إلى مستوى أدنى ، في مكان ما في الحبل الشوكي للتحكم في المحركات والعديد من ردود الفعل غير المشروطة.

ولكن الأمر الغريب هو أنه عندما بدأت في كتابة تطبيق للهاتف الذكي ، بدأ برنامج IDE ANDROID STUDIO سيئ باستمرار في شطب الكود الخاص بي ووصفه بالمتقادم.

الكاميرا = الكاميرا. open () ؛

خاصة ، كما ترون ، في تلك الأجزاء حيث حاولت العمل مع الكاميرا. كان الأمر مخيباً للآمال للغاية لأنني قرأت على الإنترنت وتعلمت العديد من الدروس حول العمل مع android و camera هنا ، هنا ، هنا وحتى هنا . لم يتم شطب شيء هناك. وكان يطلق عليه API الكاميرا. كل شيء كان هناك بسيط ومنطقي. لكن Google دفعتني بعناد نحو نوع من واجهة برمجة تطبيقات Camera2 .

نظرت إلى هناك ووقعت في مشكلة مع عدد مرات الاسترجاعات المختلفة والبنائين والمعالجات والحلقات لكل سطر من التعليمات البرمجية التجريبية. لم يكن من المفهوم تمامًا أي طريقة للتعامل مع هذا الأمر إذا كنت من الهواة العاديين ، وليس من مطوري برامج android. علاوة على ذلك ، هناك بعض المقالات حول Camera2 API على الشبكة اليوم ، على الرغم من أن هذا التحديث بدا قبل أربعة أعوام. لكن كل ما وجدته هو مقال في Hacker في عام 2016 ، منشور في ثلاثة أجزاء من الإخوة الأوكرانيين في نفس العام ، وموقع مزدوج على Habré في عام 2017 ومقال فهم Camera2 من مكبر الصوت الياباني Tomoaki Imai. وهذا يعني أيضًا بعض المعلومات المنظمة وغير الرسمية ، وليس مقتطفات من الكود مثل "انظر كيف يمكنني" والأوراق في النمط "انظر إلى pliz code ، لا شيء مفيد لي" المنتشر على الإنترنت.

والآن ، إذا كنت لا تزال تتساءل لماذا احتجت إلى خفض مشاركتي في هذا الموضوع
بالفعل في عام 2019 ، ثم مرحبا بكم في القط.

في الواقع ، أعجبتني المقالة من Hacker لأنها أعطتني نظرة عامة على الأقل عن سبب تقديم نموذج الجحيم الجديد للعمل مع الكاميرا. أصبح من الواضح أيضًا سبب الحاجة إلى بعض الأساليب الجديدة التي لم تكن موجودة في واجهة برمجة التطبيقات السابقة. لكن كتابة برنامج عمل لهذه المقالة كان مستحيلًا تمامًا ، لأن المؤلفين ، على ما يبدو ، مثل المتسللين الحقيقيين ، ذكروا فقط أسماء الطرق ، حسنًا ، ربما لا يزال هناك سطرين من التعليمات البرمجية الإضافية. ولكن هذا بشكل قاطع لم يكن كافيا بالنسبة لي.

المنشور على Habré كان جيدًا ، لا أجادل ، بل لقد فهمت الفقرات القليلة الأولى ، لكن هذه كانت النقطة ، لأن مؤلف المنشور هزم الكاميرا باستخدام RxJava2 ، الأمر الذي استبعدني تلقائيًا من عدد القراء الآخرين. عادةً ما أتعامل مع OOP ، لكن يوجد هنا نوعًا من البرمجة التفاعلية.

كانت المقالة اليابانية أفضل ، حتى لو لم يكتب بلغته الأم. لكنني لا أعرف Kotlin أيضًا ، على الرغم من أنني ، بالطبع ، كنت سعيدًا للمطورين المحليين وقدرت تقديري لإيجاز بناء الجملة الخاص به ، على عكس JAVA-vermicelli التي كانت موجودة في صفحة مطوري Google (بالمناسبة ، مع هذه الصفحة ، كان من الواضح لي أيضًا أن هناك بوضوح ليس كثيرا).

كان المقال الأخير الذي جلب لي فائدة عملية وظيفة من ثلاثة أجزاء من أوكرانيا الشقيقة. لقد أطلقت شيء هناك ورأيت شيئًا هناك. لكن لسوء الحظ ، في الجزء الثالث ، كان كاتب المقال متعبًا للغاية وبدأ أيضًا في إصدار كود في أجزاء لم تضيف إلى مجمل عملي. علاوة على ذلك ، في النهاية ، كان المؤلف غاضبًا تمامًا ، لأنه حصل على صورة لوحة الألوان الخاطئة التي كان يعول عليها.



قل ، لأن إصدار Lollipop هو 5.0 ، وهناك خطأ. تحتاج إلى الترقية إلى Lollipop 5.1 وبعد ذلك سيكون كل شيء على ما يرام. لكن بطريقة ما ليس بعد. وأيضًا الأوكرانيين الضارين الذين تم تثبيتهم على مقال JAVA SCRIPT ، وعند نسخ الكود تم سكب كمية كبيرة من القمامة في نص البرنامج. الرجال ، حسنًا ، لا يمكنك فعل ذلك ... اضطررت إلى تثبيت مكون إضافي في Firefox.

لذلك ، كان عليّ أن أرفع الراية الساقطة وأنهي المهمة حتى النهاية ، وهذا بالطبع كان يتطلب مجهودًا عقليًا كبيرًا. ولأنه من المؤسف إلى حد ما أن أكون راضيًا عن إدراك أنه تم الحصول على نتيجة عملية ، أردت أن أشاركها مع أقداح الشاي الهاوية. علاوة على ذلك ، هذا أبعد ما يكون عن نهاية القصة. ما زلت بحاجة لمعرفة كيفية نقل الفيديو المباشر من كاميرا android إلى الكمبيوتر (يجب أن يتطور الروبوت الآلي ، وهذا هو قانون التطور) وليس في القطع ، مثل أنا أعمى ، ولكن بسلاسة. وهناك مثل هذه العوائق التي تواجه Mont Blanc في شكل برامج ترميز الوسائط وغيرها من الأشياء التي صفيح فقط.

ولكن من ناحية أخرى ، فإن الأمر برمته قد ينتهي بخيبة أمل كاملة. وليس لأنهم لن يتمكنوا من تسلق مونت بلانك.

ولأن شركات تصنيع الهواتف الذكية تعمل الآن على تطوير أساليب جديدة تمامًا لتصنيع الكاميرات.
قبل شهر ، تسربت شائعة حول هواتف نوكيا الذكية بخمس كاميرات رئيسية. كيف تتصل بهذا؟ شائعة مثيرة للاهتمام وواعدة أو شيء غريب آخر؟ بغض النظر عن الشكل الفريد لهذا التصميم ، لن تتمكن نوكيا بالتأكيد من أن تصبح رائدة في تقديم عدد غير عادي من العدسات وأجهزة الاستشعار في الأجهزة المدمجة. تم تجهيز كاميرا Light L16 بالفعل بـ 16 عدسة في عام 2015 ، ومن الواضح أن الشركة لديها نموذج أولي جديد قيد التشغيل. أعلاه يمكنك أن ترى كيف يمكن أن تبدو.

بعد ظهور الكاميرا الثلاثية في Huawei P20 Pro ، لم يعد الانتقال إلى هاتف ذكي به خمس كاميرات يبدو كوميديًا كما كان يمكن أن يكون قبل عامين. ومع ذلك ، يبقى السؤال الرئيسي - ما هي الفائدة؟
ماذا تفعل مع الكثير من العدسات؟

أول ما يتبادر إلى الذهن هو مجموعة متنوعة من أنواع أجهزة استشعار الكاميرا المتاحة في سوق الهواتف الذكية الحديثة ، والقدرة على إضافة المزيد. لماذا تختار بين الزاوية العريضة ، المقربة ، صورة مع خوخه أو أحادية اللون ، إذا كنت تستطيع الحصول على كل هذا في جهاز واحد؟

كونه ممكن من الناحية النظرية ، فإن مثل هذا التصميم سيكون محرجا للغاية للاستخدام. سيتعين على البرنامج التبديل بين الأوضاع تلقائيًا أو تقديم مجموعة معقدة من الخيارات للمستخدم. علاوة على ذلك ، فإن تطوير مثل هذا التصميم سيكون مكلفًا للغاية لجميع المزايا المشكوك فيها لهذا الحل. ستعمل كل كاميرا في معظمها بشكل مستقل ، ومن غير المحتمل أن يستخدم المشترون عددًا كبيرًا من الأوضاع. وليس من الواضح كم سيكونون على استعداد لدفع ثمن هذه الوظيفة. لذلك يجب أن تكون الكاميرات ذات الوحدات المتعددة قادرة على فعل المزيد لجذب المستخدم.

يقدم Huawei P20 Pro بالفعل نسخته الخاصة بكيفية عمل العديد من وحدات الكاميرا معًا لإعطاء نتيجة مثيرة للاهتمام. نحن نتحدث عن تقنيات من Huawei ، مثل Monochrome و Hybrid Zoom. الأول يعمل على تحسين النطاق الديناميكي للإطارات القياسية من خلال الجمع بين بيانات RGB العادية ومستشعر حساس بالأبيض والأسود. ويعد Hybrid Zoom أكثر من ذلك: فهو يجمع البيانات من كاميرات متعددة لزيادة دقة الصورة للحصول على تكبير أفضل. نتيجة لذلك ، في P20 Pro 8 MP ، يسمح لك مستشعر العدسة المقربة بتصوير بدقة 10 ميجابيكسل عند التكبير 3x و 5x.
دقة أعلى - مزيد من المرونة

عملت أول كاميرا Light L16 بشكل مشابه ، باستخدام مرايا periscope لتركيب وحدات الكاميرا في جسم مضغوط. أخذت الكاميرا البيانات من عدة وحدات في 28 و 70 و 150 ملم ، وهذا يتوقف على مستوى التكبير. كانت النتيجة لقطة 52 ميجا بكسل مأخوذة من 10 زوايا مختلفة قليلاً متاحة بمستويات تكبير يصل إلى 5x. مفهوم النموذج الجديد ، تم تطويره للهواتف الذكية ، ويعمل مع 5-9 عدسات. وحدة الكاميرا هذه قادرة على التقاط صور كبيرة بدقة 64 ميجابكسل.

تضيف فكرة لقطات متعددة هذه أيضًا فوائد عند التصوير في الإضاءة المنخفضة وفي HDR مع فتحات متعددة. يتوفر التأثير عالي الجودة لعمق الإطار أيضًا بسبب مضاهاة البرنامج المتزامن واستخدام أطوال بؤرية متعددة.

جلبت ضوء L16 خيبة أمل ، ولكن الفكرة نفسها كانت واعدة. والجيل القادم الناجح قد يثبت أنه شيء يستحق العناء. تدعي الشركة أنه سيتم الإعلان عن الهاتف الذكي في نهاية العام ، حيث سيتم تثبيت أحدث حلولها مع عدسات متعددة.

يمكن إرجاع نفس الفكرة إلى تجربة نوكيا في تنفيذ كاميرات متعددة ، في ضوء القصة القديمة للاستثمار في Pelican Imaging في عام 2013. على عكس الضوء ، فإن المستشعر هنا أصغر بكثير. ومع ذلك ، تعد التكنولوجيا بمزايا مماثلة للغاية ، بما في ذلك تغيير تركيز البرنامج وقياس العمق وزيادة حجم الصورة النهائية. لسوء الحظ ، اشترت Tessera الشركة في عام 2016 ، لكن الفكرة نفسها ربما لم تترك عقول مهندسي نوكيا.

تمتلك Zeiss ، شريك الصور الحالي من نوكيا ، براءة اختراع للتصغير القابل للتحويل ، لكننا لم نسمع أي شيء منها عن التصميم متعدد العدسات. ربما يبدو شريك نوكيا الآخر ، FIH Mobile ، أكثر واعدة. هذه الشركة مملوكة لشركة Foxconn ، وتطلق هواتف نوكيا ، كما استثمرت شركة Light في عام 2015 ، مما يمنحها ترخيصًا لاستخدام التكنولوجيا.

وإذا كنت تعتقد أن هناك مشكلة مشتركة بين تسريب نوكيا والنموذج Light ، فلا يعد ذلك مصادفة. يربط بين شركتي فوكسكون. هل سيكون هاتف نوكيا الذكي أول من استخدم تقنية Light؟
فهل هذا هو المستقبل؟

الدقة الفائقة ليست مفهوما جديدا ، ففي عام 2014 ، استخدم Oppo Find 7 مبدأ مشابهًا ، وسمحت تقنية Hybrid Zoom من Huawei بالعمل مع كاميرات متعددة. تاريخياً ، كانت المشكلة الرئيسية للتكنولوجيا هي متطلبات الأداء العالية وجودة الخوارزمية واستهلاك الطاقة. ولكن على جانب الهواتف الذكية الحديثة ، هناك معالجات أكثر قوة لمعالجة الإشارات ورقائق DSP الموفرة للطاقة وحتى قدرات محسّنة للشبكات العصبية ، مما يقلل تدريجياً من أهمية المشكلة.

تتصدر التفاصيل العالية وقدرات التكبير البصري وتأثير خوخه مخصص قائمة متطلبات الكاميرا لهاتف ذكي حديث ، ويمكن أن تساعد تقنية الكاميرا المتعددة في تحقيق ذلك. بدلاً من أداء وظائف مختلفة مع كاميرات منفصلة ، فإن مستقبل التصوير المحمول هو الجمع بين عدة كاميرات لتوفير إمكانات أكثر تقدماً ومرونة.

لا تزال هناك أسئلة حول تقنية Light ، وخاصة حول الصور الإلتصاق بأطوال بؤرية مختلفة. لا يمكننا الانتظار إلا - سنرى ما الذي سيتغير للأفضل في الجيل الثاني من التكنولوجيا.



لن يكون من السهل تكوين مثل هذا التجميع من الكاميرات مع مقابض في البرنامج. ربما سيتم إغلاق الدائرة وللتقاط صورة ، سيتعين علينا مرة أخرى كتابة نية صريحة ، مع المحتوى ، مثل "الكاميرا ، يرجى التقاط الصورة بنفسك قدر الإمكان ، ولكن بشكل جميل ، وإعادتها إلي في نشاطي".

لكن في الوقت الحالي ، على الرغم من القليل ما زال لدينا. لذلك ، نحن نمضي على الفور.

لماذا كل هذا يحتاج جوجل؟

يبدو أن الأمر كله آمن ومتعدد مؤشرات الترابط (وقد أصبح من الممكن أيضًا القيام بكل أنواع التأثيرات والمرشحات مباشرة). الآن ، إذا كنت تستخدم برنامج vanilla JAVA ، إذا لزم الأمر ، فأنت بحاجة إلى الضغط على المزامنة والتزامن والإشارة في كل مكان ، ثم تتولى Google كل شيء تقريبًا. تحتاج فقط إلى تسجيل عمليات الاسترجاعات في نص البرنامج ، والذي سيتم استدعاؤه عند الضرورة. هذا هو ، على سبيل المثال ، تقديم طلب لتشغيل الكاميرا:

mCameraManager.openCamera() 

لكن هذا ليس فريقًا ، إنه طلب. لا يمكنك البدء فوراً في العمل مع الكاميرا. أولاً ، تحتاج إلى وقت لتشغيلها ، وثانياً ، لدى android العديد من الأشياء المهمة التي يجب القيام بها ، ويتم وضع رغبتك في قائمة انتظار كبيرة إلى حد ما. ولكن بعد ذلك ، لا يتعين علينا الانتظار حتى تفتح الكاميرا في حلقة في الخيط الرئيسي ، وتعليق التطبيق بالكامل (ما زلنا نتذكر ما يمكن القيام به في دفق واجهة المستخدم وما لا). لذلك ، نرسل رغبتنا وأثناء قيامنا بأعمالنا ، نفتح طرق العرض ، ونكتب "hello world" ، وتكوين معالجات الأزرار وما شابه ذلك.

في هذه الأثناء ، بعد عشرات ومئات الميلي ثانية ، يصل نظام التشغيل أخيرًا إلى يد الكاميرا ويقوم بتهيئته. وبمجرد أن تكون الكاميرا جاهزة ، يتم تشغيل نفس رد الاتصال (ما لم تقم بالطبع بتسجيله مقدمًا)

  private CameraDevice.StateCallback mCameraCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCameraDevice = camera; createCameraPreviewSession(); ….. } 

أي أن الكاميرا مفتوحة الآن ويمكنها أن تفعل شيئًا هناك: عرض الصورة من الكاميرا على الشاشة ، وإعادة توجيهها لتوفيرها ، وهكذا.

نتيجة لذلك ، كل شيء يعمل مع الكاميرا ، في الواقع ، يتلخص في وصف جميع أنواع عمليات الاسترجاعات. ولكن رغبتهم النبيلة ، ذهب مطورو Google بعيدًا بشكل كبير ، وكما أشار الرفيق الياباني بشكل صحيح:
أحد الأسباب التي تجعل Camera2 في حيرة هو عدد مرات معاودة الاتصال التي تحتاج إلى استخدامها لالتقاط طلقة واحدة.

لكن هذا لا يكفي. المخطط الكامل للكاميرا لديه هذا المظهر الرائع



لكن لحسن الحظ ، بالنسبة للمبتدئين ، يمكن اختزاله إلى صورة أكثر جاذبية.



على الرغم من أن كل شيء باللغة الإنجليزية هناك ، ولكن كما يقولون ، فمن الواضح بدون كلمات. بالنسبة للمبتدئين ، لدينا ما يكفي من هذا التصميم. افتح الكاميرا ، وعرض الصورة على شاشة الهاتف الذكي ، والتقاط صورة وبمجرد أن تصبح جاهزة (رد الاتصال مرة أخرى) ، سوف يعمل المستمع لهذا الحدث وكتابة الصورة إلى ملف.

الشروع في إنشاء رمز

سننشئ مشروعًا جديدًا في Android Studio IDE ، وحدد الحد الأدنى من إصدار SDK 22 لتجنب الصور الخضراء وطلب النشاط فارغًا (أو الأفضل ، أخذ الإصدار 23 ، وإلا فقد تحدث مشكلات في الأذونات). يكفي لبداية. حتى الأذونات في البيان لا تحتاج إلى القيام به بعد.

نبدأ بإنشاء مثيل لفئة CameraManager. هذا هو مدير خدمة النظام الذي يتيح لك العثور على الكاميرات المتاحة ، والحصول على خصائصها التي تحتاجها للعمل وتعيين إعدادات التصوير للكاميرات.

وسوف نرى الخصائص التالية:

معرف الكاميرا (0 ، 1 ، 2 ....)
الاتجاه حيث يتم توجيه الكاميرا (للأمام ، للخلف)
قرار الكاميرا

أولاً ، نحصل على قائمة الكاميرات في شكل صفيف سلسلة ، ثم نطبع الخصائص المطلوبة في حلقة ونكتبها في السجل.

 package com.example.camera; import android.content.Context; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import android.graphics.ImageFormat; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraManager; import android.hardware.camera2.params.StreamConfigurationMap; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.util.Size; public class MainActivity extends AppCompatActivity { public static final String LOG_TAG = "myLogs"; String[] myCameras = null; private CameraManager mCameraManager = null; @RequiresApi(api = Build.VERSION_CODES.M) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try{ //      myCameras = new String[mCameraManager.getCameraIdList().length]; //     for (String cameraID : mCameraManager.getCameraIdList()) { Log.i(LOG_TAG, "cameraID: "+cameraID); int id = Integer.parseInt(cameraID); // e   CameraCharacteristics cc = mCameraManager.getCameraCharacteristics(cameraID); //    ,    StreamConfigurationMap configurationMap = cc.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); //      int Faceing = cc.get(CameraCharacteristics.LENS_FACING); if (Faceing == CameraCharacteristics.LENS_FACING_FRONT) { Log.i(LOG_TAG,"Camera with ID: "+cameraID + " is FRONT CAMERA "); } if (Faceing == CameraCharacteristics.LENS_FACING_BACK) { Log.i(LOG_TAG,"Camera with: ID "+cameraID + " is BACK CAMERA "); } //        jpeg Size[] sizesJPEG = configurationMap.getOutputSizes(ImageFormat.JPEG); if (sizesJPEG != null) { for (Size item:sizesJPEG) { Log.i(LOG_TAG, "w:"+item.getWidth()+" h:"+item.getHeight()); } } else { Log.i(LOG_TAG, "camera don`t support JPEG"); } } } catch(CameraAccessException e){ Log.e(LOG_TAG, e.getMessage()); e.printStackTrace(); } } } 

سيحصل السجل على شيء مثل هذا:
 2019-09-13 10:56:31.489 11130-11130/? I/myLogs: cameraID: 0 2019-09-13 10:56:31.504 11130-11130/? I/myLogs: Camera with: ID 0 is BACK CAMERA 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:4000 h:3000 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:4000 h:2250 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:3840 h:2160 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:2592 h:1944 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:2592 h:1940 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:2048 h:1536 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1920 h:1080 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1600 h:1200 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1440 h:1080 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1440 h:720 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1280 h:960 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1280 h:768 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1280 h:720 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1280 h:480 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:1280 h:400 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:800 h:480 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:720 h:480 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:640 h:480 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:480 h:640 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:480 h:360 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:480 h:320 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:352 h:288 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:320 h:240 2019-09-13 10:56:31.506 11130-11130/? I/myLogs: w:240 h:320 2019-09-13 10:56:31.507 11130-11130/? I/myLogs: w:176 h:144 2019-09-13 10:56:31.507 11130-11130/? I/myLogs: w:144 h:176 2019-09-13 10:56:31.507 11130-11130/? I/myLogs: cameraID: 1 2019-09-13 10:56:31.514 11130-11130/? I/myLogs: Camera with ID: 1 is FRONT CAMERA 2019-09-13 10:56:31.515 11130-11130/? I/myLogs: w:4224 h:3136 2019-09-13 10:56:31.515 11130-11130/? I/myLogs: w:4224 h:2376 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:4160 h:3120 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:4160 h:2340 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:4000 h:3000 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:4000 h:2250 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:3840 h:2160 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:2592 h:1944 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:2592 h:1940 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:2048 h:1536 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1920 h:1080 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1600 h:1200 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1440 h:1080 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1440 h:720 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1280 h:960 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1280 h:768 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1280 h:720 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1280 h:480 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:1280 h:400 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:800 h:480 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:720 h:480 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:640 h:480 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:480 h:640 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:480 h:360 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:480 h:320 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:352 h:288 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:320 h:240 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:240 h:320 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:176 h:144 2019-09-13 10:56:31.516 11130-11130/? I/myLogs: w:144 h:176 


نحن ، من حيث المبدأ ، لا نحتاج حقًا إلى هذه المعلومات ، فإن أي إبريق شاي حتى بواسطة واجهة برمجة التطبيقات السابقة يعرف بالفعل أن الكاميرات لها معرفات من الصفر وما بعدها ، وأنك لن تفاجئ أي شخص بدقة 1920 × 1080 ، وأكثر من ذلك بتنسيق JPEG. في الواقع ، هناك حاجة بالفعل إلى هذه البيانات من قبل تطبيق "للبالغين" جاهز للإنتاج والذي ، على أساسه ، سيجعل القوائم المفضلة للمستخدم وما إلى ذلك. في أبسط الحالات ، بشكل عام ، كل شيء واضح. ولكن بما أن جميع المقالات تبدأ بهذا ، فإننا سنبدأ.

بعد التأكد من أن الكاميرا الأمامية تحتوي على رقم المعرف "1" و "0" الخلفي (لسبب ما تم تحديدهما في تنسيق السلسلة) ، وأيضًا أن دقة 1920 × 1080 وحفظ ملف JPG متاحان لنا ، سنواصل الهجوم.

نحصل على الأذونات اللازمة

في البداية ، نحن بحاجة إلى القلق بشأن عدد من الأذونات. للقيام بذلك ، يجب عليك كتابة ما يلي في البيان:

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

الأول مفهوم بالنسبة للكاميرا ، والثاني هو لكتابة الصور على ملف (وهذه ليست بطاقة ذاكرة خارجية ، حيث قد يبدو ذلك من معنى كلمة خارجي ، ولكن ذاكرة الهاتف الذكي الخاصة تمامًا)

ولكن Android يهتم بنا أيضًا ، وبالتالي ، بدءًا من إصدار Lollipop ، لن تكون الأذونات المحددة في البيان كافية. مطلوب الآن أن يوافق المستخدم الأقلام على موافقته على فتح الكاميرا وكتابة البيانات إلى الذاكرة.

للقيام بذلك ، في أبسط الحالات ، تحتاج إلى إضافة هذا:

 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ……... if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) ) { requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); } ….. 

لماذا في أبسط؟ لأنه ليس من الضروري القيام بمثل هذه الأشياء في دفق واجهة المستخدم. أولاً ، يتم تعليق الخيط أثناء قيام المستخدم بالضغط على الشاشة بأصابعه الخرقاء ، وثانياً ، إذا تمت تهيئة الكاميرا مرة أخرى ، فقد يتعطل التطبيق عمومًا. في هذه الحالة التجريبية ، كل شيء على ما يرام ، ولكن يشرع عمومًا في استخدام نوع رد الاتصال المطلوب لهذه الحالة:

 @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == MY_REQUEST_CODE_FOR_CAMERA) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { startCameraActivity(); //     (  ) } } } 

على الرغم من أنني لم أكن أعرف كل هذا من قبل ، إلا أنني أطلقت النشاط المطلوب من خلال AsyncTask ، وحتى قبل ذلك ، قمت فقط بنحت سلسلة جديدة ، كما في Java.

طبخ الكاميرا

للراحة ، سنقوم بإخراج كل ما يتعلق بالكاميرات في فصل منفصل بناءً على نصيحة الأشخاص الأذكياء وإنشاء فئة CameraService. هناك سنضع تهيئة الكاميرات ثم نكتب جميع عمليات الاستدعاء المطلوبة.

 ….. CameraService[] myCameras = null; private CameraManager mCameraManager = null; private final int CAMERA1 = 0; private final int CAMERA2 = 1; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ….. } public class CameraService { private String mCameraID; private CameraDevice mCameraDevice = null; private CameraCaptureSession mCaptureSession; public CameraService(CameraManager cameraManager, String cameraID) { mCameraManager = cameraManager; mCameraID = cameraID; } public boolean isOpen() { if (mCameraDevice == null) { return false; } else { return true; } } public void openCamera() { try { if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { mCameraManager.openCamera(mCameraID,mCameraCallback,null); } } catch (CameraAccessException e) { Log.i(LOG_TAG,e.getMessage()); } } public void closeCamera() { if (mCameraDevice != null) { mCameraDevice.close(); mCameraDevice = null; } } } 

في السلسلة الرئيسية ، قم بإنشاء مثيل mCameraManager واستخدمه لملء صفيف كائنات myCameras. في هذه الحالة ، لا يوجد سوى اثنين منهم - الكاميرات الأمامية والسيلفي.

  mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try{ //      myCameras = new CameraService[mCameraManager.getCameraIdList().length]; for (String cameraID : mCameraManager.getCameraIdList()) { Log.i(LOG_TAG, "cameraID: "+cameraID); int id = Integer.parseInt(cameraID); //     myCameras[id] = new CameraService(mCameraManager,cameraID); } } catch(CameraAccessException e){ Log.e(LOG_TAG, e.getMessage()); e.printStackTrace(); } 

في الأسلوب openCamera () void العام ، يمكنك رؤية السطر:

  mCameraManager.openCamera(mCameraID,mCameraCallback,null); 

من هنا يؤدي المسار إلى رد الاتصال الأول لحالة كاميرا CameraDevice. StateCallback. سوف يخبرنا ما إذا كانت الكاميرا مفتوحة أو مغلقة أو ربما لا يوجد شيء على الإطلاق وسيخطئ. سنكتبها في أساليب فئة CameraService.

  private CameraDevice.StateCallback mCameraCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCameraDevice = camera; Log.i(LOG_TAG, "Open camera with id:"+mCameraDevice.getId()); createCameraPreviewSession(); } @Override public void onDisconnected(CameraDevice camera) { mCameraDevice.close(); Log.i(LOG_TAG, "disconnect camera with id:"+mCameraDevice.getId()); mCameraDevice = null; } @Override public void onError(CameraDevice camera, int error) { Log.i(LOG_TAG, "error! camera id:"+camera.getId()+" error:"+error); } }; 

وفقًا لذلك ، إذا كانت الكاميرا متاحة للتشغيل (نجحت طريقة void onOpened العامة (CameraDevice camera)} ، فسنكتب إجراءاتنا الأخرى هناك ، على سبيل المثال ، استدعاء طريقة createCameraPreviewSession (). سوف يساعدنا في نقل الصورة من الكاميرا إلى العرض والعمل معها بشكل أكبر.

CreateCameraPreviewSession

نحاول هنا عرض صورة (دفق بيانات) على نسيج mImageView ، والذي تم تعريفه بالفعل في التخطيط. يمكنك حتى تحديد أي دقة بالبكسل.

  private void createCameraPreviewSession() { SurfaceTexture texture = mImageView.getSurfaceTexture(); // texture.setDefaultBufferSize(1920,1080); Surface surface = new Surface(texture); try { final CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(surface); mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { mCaptureSession = session; try { mCaptureSession.setRepeatingRequest(builder.build(),null,null); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession session) { }}, null ); } catch (CameraAccessException e) { e.printStackTrace(); } } 

وعندما تكون هذه الجلسة جاهزة ، يتم استدعاء معاودة الاتصال المذكورة أعلاه ونبدأ بالتعبير عن مستخدمي googloders: "عرض معاينة الكاميرا". هنا ، يمكن لأولئك الذين يرغبون في ضبط ضبط تلقائي للصورة وإعدادات الفلاش ، ولكن الآن سنتعرف على الإعدادات الافتراضية.

إنشاء تخطيط

الآن نحتاج ، إذا جاز التعبير ، لرسم الألوان على القماش وإنشاء صورة رائعة بأسلوب
"ثلاثة أزرار الشاشة وطريقة عرض واحدة."
 <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextureView android:id="@+id/textureView" android:layout_width="356dp" android:layout_height="410dp" android:layout_marginTop="32dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.49" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <LinearLayout android:layout_width="292dp" android:layout_height="145dp" android:layout_marginStart="16dp" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textureView" app:layout_constraintVertical_bias="0.537"> <Button android:id="@+id/button4" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " /> <Button android:id="@+id/button5" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " /> <Button android:id="@+id/button6" android:layout_width="match_parent" android:layout_height="wrap_content" android:text=" " /> </LinearLayout> </androidx.constr 



العملية تافهة للغاية ويمكن لأي شخص أن يستدير هنا كما يريد. لكن كتابة أسماء الأزرار مباشرة في التخطيط هي أيضًا لغة سيئة وهكذا في إصدارات العمل لا تحتاج إلى ذلك.

وفقًا لذلك ، في النشاط نفسه ، نقوم بإنشاء مستمعين ، أي مستمعين للأزرار وطرق العرض.

  private Button mButtonOpenCamera1 = null; private Button mButtonOpenCamera2 = null; private Button mButtonToMakeShot = null; private TextureView mImageView = null; @RequiresApi(api = Build.VERSION_CODES.M) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ……... mButtonOpenCamera1 = findViewById(R.id.button1); mButtonOpenCamera2 = findViewById(R.id.button2); mButtonToMakeShot =findViewById(R.id.button3); mImageView = findViewById(R.id.textureView); mButtonOpenCamera1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].closeCamera(); if (myCameras[CAMERA1] != null) { if (!myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].openCamera(); } } }); mButtonOpenCamera2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].closeCamera(); if (myCameras[CAMERA2] != null) { if (!myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].openCamera(); } } }); mButtonToMakeShot.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //    } }); …….. 

تعيينات الزر واضحة من الأسماء ، سنترك الزر الثالث للصورة المستقبلية.

وإذا كنت الآن تجمع كل أجزاء الكود سوية

الحصول على ما يلي:
 package com.example.camera; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.Button; import java.util.Arrays; public class MainActivity extends AppCompatActivity { public static final String LOG_TAG = "myLogs"; CameraService[] myCameras = null; private CameraManager mCameraManager = null; private final int CAMERA1 = 0; private final int CAMERA2 = 1; private Button mButtonOpenCamera1 = null; private Button mButtonOpenCamera2 = null; private Button mButtonToMakeShot = null; private TextureView mImageView = null; @RequiresApi(api = Build.VERSION_CODES.M) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(LOG_TAG, " "); if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) ) { requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE},1); } mButtonOpenCamera1 = findViewById(R.id.button1); mButtonOpenCamera2 = findViewById(R.id.button2); mButtonToMakeShot =findViewById(R.id.button3); mImageView = findViewById(R.id.textureView); mButtonOpenCamera1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA2].isOpen()) {myCameras[CAMERA2].closeCamera();} if (myCameras[CAMERA1] != null) { if (!myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].openCamera(); } } }); mButtonOpenCamera2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA1].isOpen()) {myCameras[CAMERA1].closeCamera();} if (myCameras[CAMERA2] != null) { if (!myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].openCamera(); } } }); mButtonToMakeShot.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // if (myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].makePhoto(); // if (myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].makePhoto(); } }); mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try{ //      myCameras = new CameraService[mCameraManager.getCameraIdList().length]; for (String cameraID : mCameraManager.getCameraIdList()) { Log.i(LOG_TAG, "cameraID: "+cameraID); int id = Integer.parseInt(cameraID); //     myCameras[id] = new CameraService(mCameraManager,cameraID); } } catch(CameraAccessException e){ Log.e(LOG_TAG, e.getMessage()); e.printStackTrace(); } } public class CameraService { private String mCameraID; private CameraDevice mCameraDevice = null; private CameraCaptureSession mCaptureSession; public CameraService(CameraManager cameraManager, String cameraID) { mCameraManager = cameraManager; mCameraID = cameraID; } private CameraDevice.StateCallback mCameraCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCameraDevice = camera; Log.i(LOG_TAG, "Open camera with id:"+mCameraDevice.getId()); createCameraPreviewSession(); } @Override public void onDisconnected(CameraDevice camera) { mCameraDevice.close(); Log.i(LOG_TAG, "disconnect camera with id:"+mCameraDevice.getId()); mCameraDevice = null; } @Override public void onError(CameraDevice camera, int error) { Log.i(LOG_TAG, "error! camera id:"+camera.getId()+" error:"+error); } }; private void createCameraPreviewSession() { SurfaceTexture texture = mImageView.getSurfaceTexture(); texture.setDefaultBufferSize(1920,1080); Surface surface = new Surface(texture); try { final CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(surface); mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { mCaptureSession = session; try { mCaptureSession.setRepeatingRequest(builder.build(),null,null); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession session) { }}, null ); } catch (CameraAccessException e) { e.printStackTrace(); } } public boolean isOpen() { if (mCameraDevice == null) { return false; } else { return true; } } public void openCamera() { try { if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { mCameraManager.openCamera(mCameraID,mCameraCallback,null); } } catch (CameraAccessException e) { Log.i(LOG_TAG,e.getMessage()); } } public void closeCamera() { if (mCameraDevice != null) { mCameraDevice.close(); mCameraDevice = null; } } } @Override public void onPause() { if(myCameras[CAMERA1].isOpen()){myCameras[CAMERA1].closeCamera();} if(myCameras[CAMERA2].isOpen()){myCameras[CAMERA2].closeCamera();} super.onPause(); } } 


نحن تحميل ، بدء ، العمل!

بشكل عام ، إذا لم يكن ذلك كافياً بالنسبة لك ، يمكن أن تكون عملية التصوير نفسها متنوعة إلى حد كبير. لدينا Tomoaki الياباني يظهر ويشرح كيف ، وبذلك رسم تخطيطي جميل.



أولاً ، تحتاج الكاميرا إلى التركيز. هذا عادة لا يسبب مشاكل ، لكنه في بعض الأحيان يتطلب عدة محاولات ، والتي يتم تنفيذها أيضًا من خلال رد اتصال.

 CameraCaptureSession.StateCallback(). 

ثم تدخل الكاميرا في وضع معاينة PRECAPTURE. في هذه المرحلة ، تحسب الكاميرا التعرض وتوازن اللون الأبيض وفتحة العدسة (اعتدت أن أعرف ما كانت عليه في مرحلة الطفولة ، لكن الآن فقدت هذه المعرفة). في بعض الأحيان ، قد يُرجع رد الاتصال طلب CONTROL_AE_STATE_FLASH_REQUIRED ، مما يعني "سيكون من الجيد تشغيل الفلاش." يمكن تشغيله تلقائيًا بالمناسبة - setAutoFlash (mPreviewRequestBuilder).

عند تحديد جميع المعلمات اللازمة للتصوير ، فإن رد الاتصال سيعيد الحالة CONTROL_AE_STATE_CONVERGED للإشارة إلى أن الكاميرا جاهزة لالتقاط صورة.

في صفحة googloid ، كل هذا موجود بالفعل في الأمثلة ، وإذا كان لديك الصبر لاختراق حقول الألغام والأسوار السلكية ، فتكريمًا وثناءً لك.

التقط صورة واحفظ الصورة في ملف

هذا هو بالضبط حيث بدأت تواجه مشاكل. , - ( , ) . , . CamerCaptureSession Surface, ImageReader.

, ImageReader . OnImageAvailableListener. , ImageSaver , ImageSaver Runnable.

, ImageReader, CameraCaptureSession.StateCallback() . Android . ( ) createCameraPreviewSession(), .

:

 private void createCameraPreviewSession() { SurfaceTexture texture = mImageView.getSurfaceTexture(); Surface surface = new Surface(texture); try { final CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(surface); mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() ……. 

:

  private ImageReader mImageReader; private void createCameraPreviewSession() { mImageReader = ImageReader.newInstance(1920,1080,ImageFormat.JPEG,1); mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, null); SurfaceTexture texture = mImageView.getSurfaceTexture(); Surface surface = new Surface(texture); try { final CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(surface); mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() …… 

, ImageReader , . - surface, mImageReader.getSurface() . …

« ». makePhoto() ( ):

  mButtonToMakeShot.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].makePhoto(); if (myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].makePhoto(); } }); …… public class CameraService { public void makePhoto (){ try { // This is the CaptureRequest.Builder that we use to take a picture. final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(mImageReader.getSurface()); CameraCaptureSession.CaptureCallback CaptureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { } }; mCaptureSession.stopRepeating(); mCaptureSession.abortCaptures(); mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null); } catch (CameraAccessException e) { e.printStackTrace(); } } 

OnImageAvailableListener:

  private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { { Toast.makeText(MainActivity.this,"   ", Toast.LENGTH_SHORT).show();} } }; 

, , , , .

:

 public class CameraService { private File mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "test1.jpg");; 

ImageSaver, , .

Runnable. MainActivity:

  private static class ImageSaver implements Runnable { private final File mFile; ImageSaver(Image image, File file) { mImage = image; mFile = file; } @Override public void run() { ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); FileOutputStream output = null; try { output = new FileOutputStream(mFile); output.write(bytes); } catch (IOException e) { e.printStackTrace(); } finally { mImage.close(); if (null != output) { try { output.close(); } catch (IOException e) { e.printStackTrace(); } } } } } 

, OnImageAvailableListener :

 mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile)); 

WAIT! OH, SHIT!!

mBackgroundHandler??? .

— ? , BackgroundHandler BackgroundThread, . Activity :

  private HandlerThread mBackgroundThread; private Handler mBackgroundHandler = null; private void startBackgroundThread() { mBackgroundThread = new HandlerThread("CameraBackground"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); } private void stopBackgroundThread() { mBackgroundThread.quitSafely(); try { mBackgroundThread.join(); mBackgroundThread = null; mBackgroundHandler = null; } catch (InterruptedException e) { e.printStackTrace(); } } 

, BackgroundThread :

  public void onPause() { super.onPause(); stopBackgroundThread(); } @Override public void onResume() { super.onResume(); startBackgroundThread(); } 

mBackgroundHandler, , handler , , null.

, , . . onPause() onResume(). - .

. . , .



- , .

 package com.example.camera; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.ImageFormat; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.TotalCaptureResult; import android.media.Image; import android.media.ImageReader; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.Button; import android.widget.Toast; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; public class MainActivity extends AppCompatActivity { public static final String LOG_TAG = "myLogs"; CameraService[] myCameras = null; private CameraManager mCameraManager = null; private final int CAMERA1 = 0; private final int CAMERA2 = 1; private Button mButtonOpenCamera1 = null; private Button mButtonOpenCamera2 = null; private Button mButtonToMakeShot = null; private TextureView mImageView = null; private HandlerThread mBackgroundThread; private Handler mBackgroundHandler = null; private void startBackgroundThread() { mBackgroundThread = new HandlerThread("CameraBackground"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); } private void stopBackgroundThread() { mBackgroundThread.quitSafely(); try { mBackgroundThread.join(); mBackgroundThread = null; mBackgroundHandler = null; } catch (InterruptedException e) { e.printStackTrace(); } } @RequiresApi(api = Build.VERSION_CODES.M) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(LOG_TAG, " "); if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) ) { requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE},1); } mButtonOpenCamera1 = findViewById(R.id.button1); mButtonOpenCamera2 = findViewById(R.id.button2); mButtonToMakeShot =findViewById(R.id.button3); mImageView = findViewById(R.id.textureView); mButtonOpenCamera1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA2].isOpen()) {myCameras[CAMERA2].closeCamera();} if (myCameras[CAMERA1] != null) { if (!myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].openCamera(); } } }); mButtonOpenCamera2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA1].isOpen()) {myCameras[CAMERA1].closeCamera();} if (myCameras[CAMERA2] != null) { if (!myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].openCamera(); } } }); mButtonToMakeShot.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].makePhoto(); if (myCameras[CAMERA2].isOpen()) myCameras[CAMERA2].makePhoto(); } }); mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try{ //      myCameras = new CameraService[mCameraManager.getCameraIdList().length]; for (String cameraID : mCameraManager.getCameraIdList()) { Log.i(LOG_TAG, "cameraID: "+cameraID); int id = Integer.parseInt(cameraID); //     myCameras[id] = new CameraService(mCameraManager,cameraID); } } catch(CameraAccessException e){ Log.e(LOG_TAG, e.getMessage()); e.printStackTrace(); } } public class CameraService { private File mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "test1.jpg");; private String mCameraID; private CameraDevice mCameraDevice = null; private CameraCaptureSession mCaptureSession; private ImageReader mImageReader; public CameraService(CameraManager cameraManager, String cameraID) { mCameraManager = cameraManager; mCameraID = cameraID; } public void makePhoto (){ try { // This is the CaptureRequest.Builder that we use to take a picture. final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(mImageReader.getSurface()); CameraCaptureSession.CaptureCallback CaptureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { } }; mCaptureSession.stopRepeating(); mCaptureSession.abortCaptures(); mCaptureSession.capture(captureBuilder.build(), CaptureCallback, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile)); } }; private CameraDevice.StateCallback mCameraCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCameraDevice = camera; Log.i(LOG_TAG, "Open camera with id:"+mCameraDevice.getId()); createCameraPreviewSession(); } @Override public void onDisconnected(CameraDevice camera) { mCameraDevice.close(); Log.i(LOG_TAG, "disconnect camera with id:"+mCameraDevice.getId()); mCameraDevice = null; } @Override public void onError(CameraDevice camera, int error) { Log.i(LOG_TAG, "error! camera id:"+camera.getId()+" error:"+error); } }; private void createCameraPreviewSession() { mImageReader = ImageReader.newInstance(1920,1080, ImageFormat.JPEG,1); mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, null); SurfaceTexture texture = mImageView.getSurfaceTexture(); texture.setDefaultBufferSize(1920,1080); Surface surface = new Surface(texture); try { final CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(surface); mCameraDevice.createCaptureSession(Arrays.asList(surface,mImageReader.getSurface()), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { mCaptureSession = session; try { mCaptureSession.setRepeatingRequest(builder.build(),null,mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession session) { }}, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } public boolean isOpen() { if (mCameraDevice == null) { return false; } else { return true; } } public void openCamera() { try { if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { mCameraManager.openCamera(mCameraID,mCameraCallback,mBackgroundHandler); } } catch (CameraAccessException e) { Log.i(LOG_TAG,e.getMessage()); } } public void closeCamera() { if (mCameraDevice != null) { mCameraDevice.close(); mCameraDevice = null; } } } @Override public void onPause() { if(myCameras[CAMERA1].isOpen()){myCameras[CAMERA1].closeCamera();} if(myCameras[CAMERA2].isOpen()){myCameras[CAMERA2].closeCamera();} stopBackgroundThread(); super.onPause(); } @Override public void onResume() { super.onResume(); startBackgroundThread(); } private static class ImageSaver implements Runnable { /** * The JPEG image */ private final Image mImage; /** * The file we save the image into. */ private final File mFile; ImageSaver(Image image, File file) { mImage = image; mFile = file; } @Override public void run() { ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); FileOutputStream output = null; try { output = new FileOutputStream(mFile); output.write(bytes); } catch (IOException e) { e.printStackTrace(); } finally { mImage.close(); if (null != output) { try { output.close(); } catch (IOException e) { e.printStackTrace(); } } } } } } 

Source: https://habr.com/ru/post/ar468083/


All Articles