التقاط صورة من الكاميرا بدون معاينة
-
24-09-2019 - |
سؤال
أنا أكتب تطبيق Android 1.5 الذي يبدأ بعد التمهيد مباشرة. هذا ال Service
ويجب أن تلتقط صورة بدون معاينة. سيقوم هذا التطبيق بتسجيل كثافة الضوء في بعض المناطق مهما كانت. تمكنت من التقاط صورة لكن الصورة كانت سوداء.
بعد البحث لفترة طويلة ، صادفت خيط الأخطاء حول هذا الموضوع. إذا لم تقم بإنشاء معاينة ، فستكون الصورة سوداء لأن كاميرا Android تحتاج إلى معاينة لإعداد التعرض والتركيز. لقد أنشأت أ SurfaceView
والمستمع ، لكن onSurfaceCreated()
الحدث لا يتم إطلاق النار.
أعتقد أن السبب هو أن السطح لا يتم إنشاؤه بصريًا. لقد رأيت أيضًا بعض الأمثلة على الاتصال بالكاميرا بشكل ثابت MediaStore.CAPTURE_OR_SOMETHING
الذي يلتقط صورة ويحفظ في المجلد المطلوب مع سطرين من التعليمات البرمجية ، لكن الأمر لا يلتقط صورة أيضًا.
هل أحتاج إلى استخدام IPC و bindService()
لاستدعاء هذه الوظيفة؟ أم أن هناك طريقة بديلة لتحقيق ذلك؟
المحلول
من الغريب حقًا أن الكاميرا على منصة Android لا يمكنها دفق الفيديو حتى تعطى سطح معاينة صالح. يبدو أن المهندسين المعماريين للمنصة لم يفكروا في تطبيقات بث الفيديو الثالثة على الإطلاق. حتى بالنسبة لحالة الواقع المعزز ، يمكن تقديم الصورة كنوع من الاستبدال البصري ، وليس تيار الكاميرا في الوقت الحقيقي.
على أي حال ، يمكنك ببساطة تغيير حجم سطح المعاينة إلى 1x1 بكسل ووضعه في مكان ما في زاوية القطعة (العنصر البصري). يرجى الانتباه - تغيير حجم سطح المعاينة ، وليس حجم إطار الكاميرا.
بالطبع لا تقضي هذه الخدعة على تدفق البيانات غير المرغوب فيها (للمعاينة) التي تستهلك بعض موارد النظام والبطارية.
نصائح أخرى
لقد وجدت الإجابة على هذا في مستندات كاميرا Android.
ملاحظة: من الممكن الاستخدام
MediaRecorder
دون إنشاء معاينة الكاميرا أولاً وتخطي الخطوات القليلة الأولى من هذه العملية. ومع ذلك ، نظرًا لأن المستخدمين يفضلون عادة رؤية معاينة قبل بدء تسجيل ، فإن هذه العملية لم تتم مناقشتها هنا.
يمكنك العثور على تعليمات خطوة بخطوة على الرابط أعلاه. بعد التعليمات ، سيذكر الاقتباس الذي قدمته أعلاه.
في الواقع ، من الممكن ، ولكن عليك أن تزييف المعاينة باستخدام SurfaceView الوهمية
SurfaceView view = new SurfaceView(this);
c.setPreviewDisplay(view.getHolder());
c.startPreview();
c.takePicture(shutterCallback, rawPictureCallback, jpegPictureCallback);
تحديث 9/21/11: يبدو أن هذا لا يعمل لكل جهاز Android.
التقاط الصورة
احصل على هذا العمل أولاً قبل محاولة إخفاء المعاينة.
- قم بإعداد المعاينة بشكل صحيح
- إستخدم
SurfaceView
(توافق ما قبل أندرويد -4.0) أوSurfaceTexture
(Android 4+ ، يمكن أن يكون شفافًا) - قم بتعيينه وتهيئته قبل التقاط الصورة
- انتظر
SurfaceView
'سSurfaceHolder
(عبرgetHolder()
) للتقريرsurfaceCreated()
أو الTextureView
للإبلاغonSurfaceTextureAvailable
لهSurfaceTextureListener
قبل إعداد وتهيئة المعاينة.
- إستخدم
- تأكد من أن المعاينة مرئية:
- أضفه إلى
WindowManager
- تأكد من أن حجم التصميم الخاص به لا يقل عن 1 × 1 بكسل (قد ترغب في البدء من خلال صنعه
MATCH_PARENT
xMATCH_PARENT
للاختبار) - تأكد من ذلك
View.VISIBLE
(الذي يبدو أنه الافتراضي إذا لم تحدده) - تأكد من استخدامك
FLAG_HARDWARE_ACCELERATED
في الLayoutParams
إذا كانTextureView
.
- أضفه إلى
- يستخدم
takePicture
رد اتصال JPEG لأن الوثائق تقول إن عمليات الاسترجاعات الأخرى غير مدعومة على جميع الأجهزة
استكشاف الأخطاء وإصلاحها
- لو
surfaceCreated
/onSurfaceTextureAvailable
لا يتم استدعاؤه ،SurfaceView
/TextureView
ربما لا يتم عرضها. - لو
takePicture
يفشل ، أولا تأكد من أن المعاينة تعمل بشكل صحيح. يمكنك إزالة الخاص بكtakePicture
اتصل واترك المعاينة تعمل لمعرفة ما إذا كان يتم عرضه على الشاشة. - إذا كانت الصورة أغمق مما ينبغي ، فقد تحتاج إلى التأخير لمدة ثانية قبل الاتصال
takePicture
بحيث يكون للكاميرا وقت لضبط تعرضها بمجرد بدء المعاينة.
إخفاء المعاينة
جعل المعاينة
View
حجم 1 × 1 لتقليل وضوحه (أو حاول 8x16 لمزيد من الموثوقية)new WindowManager.LayoutParams(1, 1, /*...*/)
انقل المعاينة خارج المركز لتقليل قابلية الملاحظة:
new WindowManager.LayoutParams(width, height, Integer.MIN_VALUE, Integer.MIN_VALUE, /*...*/)
اجعل المعاينة شفافة (تعمل فقط
TextureView
)WindowManager.LayoutParams params = new WindowManager.LayoutParams( width, height, /*...*/ PixelFormat.TRANSPARENT); params.alpha = 0;
مثال على العمل (تم اختباره على Sony Xperia M ، Android 4.3)
/** Takes a single photo on service start. */
public class PhotoTakingService extends Service {
@Override
public void onCreate() {
super.onCreate();
takePhoto(this);
}
@SuppressWarnings("deprecation")
private static void takePhoto(final Context context) {
final SurfaceView preview = new SurfaceView(context);
SurfaceHolder holder = preview.getHolder();
// deprecated setting, but required on Android versions prior to 3.0
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(new Callback() {
@Override
//The preview must happen at or after this point or takePicture fails
public void surfaceCreated(SurfaceHolder holder) {
showMessage("Surface created");
Camera camera = null;
try {
camera = Camera.open();
showMessage("Opened camera");
try {
camera.setPreviewDisplay(holder);
} catch (IOException e) {
throw new RuntimeException(e);
}
camera.startPreview();
showMessage("Started preview");
camera.takePicture(null, null, new PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
showMessage("Took picture");
camera.release();
}
});
} catch (Exception e) {
if (camera != null)
camera.release();
throw new RuntimeException(e);
}
}
@Override public void surfaceDestroyed(SurfaceHolder holder) {}
@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
});
WindowManager wm = (WindowManager)context
.getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
1, 1, //Must be at least 1x1
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
0,
//Don't know if this is a safe default
PixelFormat.UNKNOWN);
//Don't set the preview visibility to GONE or INVISIBLE
wm.addView(preview, params);
}
private static void showMessage(String message) {
Log.i("Camera", message);
}
@Override public IBinder onBind(Intent intent) { return null; }
}
على Android 4.0 وما فوق (مستوى API> = 14) ، يمكنك استخدام lectureview لمعاينة دفق الكاميرا وجعله غير مرئي حتى لا تظهره للمستخدم. إليك الطريقة:
قم أولاً بإنشاء فئة لتنفيذ surfacetextureListener التي ستحصل على عمليات الاسترجاعات Create/Update لسطح المعاينة. تأخذ هذه الفئة أيضًا كائن كاميرا كمدخل ، بحيث يمكنه استدعاء وظيفة StartPreview للكاميرا بمجرد إنشاء السطح:
public class CamPreview extends TextureView implements SurfaceTextureListener {
private Camera mCamera;
public CamPreview(Context context, Camera camera) {
super(context);
mCamera = camera;
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
Camera.Size previewSize = mCamera.getParameters().getPreviewSize();
setLayoutParams(new FrameLayout.LayoutParams(
previewSize.width, previewSize.height, Gravity.CENTER));
try{
mCamera.setPreviewTexture(surface);
} catch (IOException t) {}
mCamera.startPreview();
this.setVisibility(INVISIBLE); // Make the surface invisible as soon as it is created
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
// Put code here to handle texture size change if you want to
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
// Update your view here!
}
}
ستحتاج أيضًا إلى تنفيذ فئة رد الاتصال لمعالجة بيانات المعاينة:
public class CamCallback implements Camera.PreviewCallback{
public void onPreviewFrame(byte[] data, Camera camera){
// Process the camera data here
}
}
استخدم فصول Campreview و CamCallback أعلاه لإعداد الكاميرا في OnCreate () أو وظيفة بدء التشغيل المماثلة:
// Setup the camera and the preview object
Camera mCamera = Camera.open(0);
CamPreview camPreview = new CamPreview(Context,mCamera);
camPreview.setSurfaceTextureListener(camPreview);
// Connect the preview object to a FrameLayout in your UI
// You'll have to create a FrameLayout object in your UI to place this preview in
FrameLayout preview = (FrameLayout) findViewById(R.id.cameraView);
preview.addView(camPreview);
// Attach a callback for preview
CamCallback camCallback = new CamCallback();
mCamera.setPreviewCallback(camCallback);
هناك طريقة للقيام بذلك لكنها صعبة إلى حد ما. ما ينبغي القيام به ، هو إرفاق حامل السطح بمدير النافذة من الخدمة
WindowManager wm = (WindowManager) mCtx.getSystemService(Context.WINDOW_SERVICE);
params = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSLUCENT);
wm.addView(surfaceview, params);
ثم تعيين
surfaceview.setZOrderOnTop(true);
mHolder.setFormat(PixelFormat.TRANSPARENT);
حيث يكون Mholder هو الحامل الذي تحصل عليه من عرض السطح.
وبهذه الطريقة ، يمكنك اللعب مع ألفا SurfaceView ، وجعلها شفافة تمامًا ، لكن الكاميرا ستظل تحصل على إطارات.
هكذا أفعل ذلك. أتمنى أن يساعد :)
قمنا بحل هذه المشكلة باستخدام SurfaceView الوهمية (لم يضاف إلى واجهة المستخدم الرسومية الفعلية) في الإصدارات أدناه 3.0 (أو دعنا نقول 4.0 كخدمة كاميرا على جهاز لوحي لا معنى لها حقًا). في الإصدارات> = 4.0 ، عمل هذا في المحاكي فقط ؛ (استخدام surfacetexture (و setSurfAcetExture ()) بدلاً من SurfaceView (و setSurfaceView ()) يعمل هنا على الأقل على Nexus S.
أعتقد أن هذا هو حقًا عيب في إطار Android.
في "مثال العمل من قبل سام" (شكرا لك سام ...)
إذا في Istruction "WM.Addview (معاينة ، params) ؛"
الحصول على استثناء "غير قادر على إضافة Window Android.view.viewroot - تم رفض إذن من نوع النافذة"
حل باستخدام هذا الإذن في AndroidManifest:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
يمكنك تجربة رمز العمل هذا ، هذه الخدمة انقر فوق الصورة الأمامية ، إذا كنت تريد التقاط صورة الكاميرا ، ثم Uncomment Backcamera في الكود والتعليق الأمامي.
ملاحظة:- اسمح للكاميرا وتخزين إذن بالتطبيق و HRACTER STARTER من النشاط أو في أي مكان.
public class MyService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
CapturePhoto();
}
private void CapturePhoto() {
Log.d("kkkk","Preparing to take photo");
Camera camera = null;
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
int frontCamera = 1;
//int backCamera=0;
Camera.getCameraInfo(frontCamera, cameraInfo);
try {
camera = Camera.open(frontCamera);
} catch (RuntimeException e) {
Log.d("kkkk","Camera not available: " + 1);
camera = null;
//e.printStackTrace();
}
try {
if (null == camera) {
Log.d("kkkk","Could not get camera instance");
} else {
Log.d("kkkk","Got the camera, creating the dummy surface texture");
try {
camera.setPreviewTexture(new SurfaceTexture(0));
camera.startPreview();
} catch (Exception e) {
Log.d("kkkk","Could not set the surface preview texture");
e.printStackTrace();
}
camera.takePicture(null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
File pictureFileDir=new File("/sdcard/CaptureByService");
if (!pictureFileDir.exists() && !pictureFileDir.mkdirs()) {
pictureFileDir.mkdirs();
}
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyymmddhhmmss");
String date = dateFormat.format(new Date());
String photoFile = "ServiceClickedPic_" + "_" + date + ".jpg";
String filename = pictureFileDir.getPath() + File.separator + photoFile;
File mainPicture = new File(filename);
try {
FileOutputStream fos = new FileOutputStream(mainPicture);
fos.write(data);
fos.close();
Log.d("kkkk","image saved");
} catch (Exception error) {
Log.d("kkkk","Image could not be saved");
}
camera.release();
}
});
}
} catch (Exception e) {
camera.release();
}
}
}