算法能计算出个图片的亮度,但实际根本不可能计算出当时的环境光;
创新互联建站是一家集网站建设,锦州企业网站建设,锦州品牌网站建设,网站定制,锦州网站建设报价,网络营销,网络优化,锦州网站推广为一体的创新建站企业,帮助传统企业提升企业形象加强企业竞争力。可充分满足这一群体相比中小企业更为丰富、高端、多元的互联网需求。同时我们时刻保持专业、时尚、前沿,时刻以成就客户成长自我,坚持不断学习、思考、沉淀、净化自己,让我们为更多的企业打造出实用型网站。
比如你在环境光亮度 10000单位的时候 拍摄一个反光率只有0.01%的黑色物体 ,那么亮度只有1单位,你计算这个照片的亮度,只会得到“亮度=1” 这个结果
在另外一个照度为100单位的场景 拍摄反光率100%的镜子, 计算出的亮度 则是“亮度=100”
下面主要介绍拍照流程的底层实现。
当指定了Camera的预览类,并开始预览之后,就可以通过takePicture()方法进行拍照了。它将以异步的方式从Camera中获取图像,具有多个回调类作为参数,并且都可以为null,下面分别介绍这些参数的意义:
• shutter:在按下快门的时候回调,这里可以播放一段声音。
• raw:从Camera获取到未经处理的图像。
• postview:从Camera获取一个快速预览的图片,不是所有设备都支持。
• jpeg:从Camera获取到一个经过压缩的jpeg图片。
虽然raw、postview、jpeg都是Camera.PictureCallback回调,但是一般我们只需要获取jpeg,其他传null即可,Camera.PictureCallback里需要实现一个方法onPictureTaken(byte[] data,Camera camera),data及为图像数据。值得注意的是,一般taskPicture()方法拍照完成之后,SurfaceView都会停留在拍照的瞬间,需要重新调用startPreview()才会继续预览。
如果直接使用taskPicture()进行拍照的话,Camera是不会进行自动对焦的,这里需要使用Camera.autoFocus()方法进行对焦,它传递一个Camera.AutoFocusCallback参数,用于自动对焦完成后回调,一般会在它对焦完成在进行taskPicture()拍照。
首先拍照的流程直接从Camera.java的takePicture开始分析。
可以看出,在方法中对各种回调的值进行了赋值,继续看底层对调函数的处理。
在应用层注册回调。
packages\apps\SnapdragonCamera\src\com\android\camera\AndroidCameraManagerImpl.java
在应用层实现回调。
这里就是真正存储数据的地方了,在android系统有四个地方可以存储共同数据区,
ContentProvider,sharedpreference、file、sqlite这几种方式,这里利用的是file方式。
然后调用到JNI层相应方法。
根据之前分析的binder机制,Camera.cpp - ICamera.cpp - CameraClient.cpp(server端)
此处的takepicture是在CameraHardwareInterface.h定义的方法。
frameworks/av/services/camera/libcameraservice/device1/CameraHardwareInterface.h
在CameraClient.cpp开始调用到HAL层中进行处理,接下来主要分析在
hardware/qcom/camera/QCamera2/HAL/QCamera2HWI.cpp得具体实现。
在此方法中去更新状态机的状态。
hardware/qcom/camera/QCamera2/HAL/QCameraStateMachine.cpp
首先调用回QCamera2HWI.cpp的prepareHardwareForSnapshot方法。
接着调用到mm_camera_interface.c的mm_camera_intf_prepare_snapshot方法。
hardware/qcom/camera/QCamera2/stack/mm-camera-interface/src/mm_camera_interface.c
接着调用mm_camera.c的mm_camera_prepare_snapshot方法,去与V4L2通信,准备拍照。
hardware/qcom/camera/QCamera2/stack/mm-camera-interface/src/mm_camera.c
当底层对拍照准备完成之后,会调用到QCamera2HWICallbacks.cpp里,处理metadata
数据的方法metadata_stream_cb_routine中。
hardware/qcom/camera/QCamera2/HAL/QCamera2HWICallbacks.cpp
之后返回到QCamera2HWI.cpp类里的take_picture方法,继续下面的操作。
此处又返回到QCamera2HWI.cpp类里的takePicture()方法中。
首先先停止并删除了preview的channel,然后调用addCaptureChannel()方法。
可以看出,在此方法中创建并初始化了channel,并且添加mediadata,postview,snapshot数据流到channel中。
返回到hardware/qcom/camera/QCamera2/HAL/QCamera2HWI.cpp的takePicture()方法中,继续调用到开启capture的channel。
channel的start方法在之前的preview流程中具体介绍过,在此不做分析。
之后又调用了QCameraChannel.cpp的startAdvancedCapture方法,然后是mm-camera-interface.c的process_advanced_capture方法,然后是mm-camera.c的mm_camera_channel_advanced_capture方法,这一系列方法设置了当前管道是拍照模式。
通过mm-jpeg-interface.c处理数据流,并且生成jpeg文件,然后在QCamera2HWI.cpp中处理从mm-jpeg-interface.c发出的jpeg相关事件。
此处更改了状态机的状态。
然后调用回QCamera2HWI.cpp的processJpegNotify方法
此处调用的是QCameraPostProc.cpp的processJpegEvt方法。
hardware/qcom/camera/QCamera2/HAL/QCameraPostProc.cpp
接着调用sendDataNotify方法。
可以看出此处在给回调的对象装填数据,并且发出通知notifyCallback回调。并且,类型为CAMERA_MSG_COMPRESSED_IMAGE。
之后的流程与preview的流程相似,将数据向上层抛送,通过JNI返回到java层的回调函数中。
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;public class Zip {
// 压缩
public static void zip(String zipFileName, String inputFile)
throws Exception {
File f = new File(inputFile);
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(
zipFileName));
zip(out, f, null);
System.out.println("zip done");
out.close();
} private static void zip(ZipOutputStream out, File f, String base)
throws Exception {
System.out.println("zipping " + f.getAbsolutePath());
if (f != null f.isDirectory()) {
File[] fc = f.listFiles();
if (base != null)
out.putNextEntry(new ZipEntry(base + "/"));
base = base == null ? "" : base + "/";
for (int i = 0; i fc.length; i++) {
zip(out, fc[i], base + fc[i].getName());
}
} else {
out.putNextEntry(new ZipEntry(base));
FileInputStream in = new FileInputStream(f);
int b;
while ((b = in.read()) != -1)
out.write(b);
in.close();
}
} // 解压
public static void unzip(String zipFileName, String outputDirectory)
throws Exception {
ZipInputStream in = new ZipInputStream(new FileInputStream(zipFileName));
ZipEntry z;
while ((z = in.getNextEntry()) != null) {
String name = z.getName();
if (z.isDirectory()) {
name = name.substring(0, name.length() - 1);
File f = new File(outputDirectory + File.separator + name);
f.mkdir();
System.out.println("MD " + outputDirectory + File.separator
+ name);
} else {
System.out.println("unziping " + z.getName());
File f = new File(outputDirectory + File.separator + name);
f.createNewFile();
FileOutputStream out = new FileOutputStream(f);
int b;
while ((b = in.read()) != -1)
out.write(b);
out.close();
}
}
in.close();
} public void deleteFolder(File dir) {
File filelist[] = dir.listFiles();
int listlen = filelist.length;
for (int i = 0; i listlen; i++) {
if (filelist[i].isDirectory()) {
deleteFolder(filelist[i]);
} else {
filelist[i].delete();
}
}
dir.delete();// 删除当前目录
} public static void main(String[] args) {
try {
// TestZip t = new TestZip();
// t.zip("c:\\test.zip","c:\\test");
// t.unzip("c:\\test.zip","c:\\test2");
} catch (Exception e) {
e.printStackTrace(System.out);
}
}
}
parse方法就是把一个字符串类型的日期,转换成真正意义上日期格式的日期。比如“2013-01-01”,人眼一下就能看出来是个日期,但是对于java来说只是一个字符串,必须要转换成为Date类型java才知道这是个日期。