android camera(6)---camera2 拍照流程
android camera2 拍照流程
正文
camera2 API 的加入是从AndroidV5.0(21)开始的,因此我们使用Camera2应该是在Android 5.0(含5.0)之后。同时,对于Android6.0我们需要有动态权限的管理。这两点应该是使用Camera2使用前的最基本认知。Android 5.0对拍照API进行了全新的设计,新增了全新设计的Camera v2 API,这些API不仅大幅提高了Android系统拍照的功能,还能支持RAW照片输出,甚至允许程序调整相机的对焦模式、曝光模式、快门等。下面不做过多介绍了,直接开撸了。
Camera2包架构示意图
我们先来看看 camera2包架构示意图(不用一下子理解,只需要有个整体印象):
这里引用了管道的概念将安卓设备和摄像头之间联通起来,系统向摄像头发送 Capture 请求,而摄像头会返回 CameraMetadata。这一切建立在一个叫作 CameraCaptureSession 的会话中。
下面是 camera2包中的主要类:
CameraManager:摄像头管理器。这是一个全新的系统管理器,专门用于检测系统摄像头、打开系统摄像头。另外,调用CameraManager的getCameraCharacteristics(String cameraId)方法即可获取指定摄像头的相关特性。
CameraCharacteristics:摄像头特性。该对象通过CameraManager来获取,用于描述特定摄像头所支持的各种特性。类似与原来的CameraInfo 。
CameraDevice:代表系统摄像头。该类的功能类似于早期的Camera类。而每个 CameraDevice 自己会负责建立 CameraCaptureSession 以及建立 CaptureRequest。
CameraCaptureSession:这是一个非常重要的API,当程序需要预览、拍照时,都需要先通过该类的实例创建Session。而且不管预览还是拍照,也都是由该对象的方法进行控制的,其中控制预览的方法为setRepeatingRequest();控制拍照的方法为capture()。
为了监听CameraCaptureSession的创建过程,以及监听CameraCaptureSession的拍照过程,Camera v2 API为CameraCaptureSession提供了StateCallback、CaptureCallback等内部类。
CameraRequest和CameraRequest.Builder:当程序调用setRepeatingRequest()方法进行预览时,或调用capture()方法进行拍照时,都需要传入CameraRequest参数。CameraRequest代表了一次捕获请求,用于描述捕获图片的各种参数设置,比如对焦模式、曝光模式……总之,程序需要对照片所做的各种控制,都通过CameraRequest参数进行设置。CameraRequest.Builder则负责生成CameraRequest对象。
相机预览与拍照流程
如果你看不太懂流程图,没关系,待会儿我们通过代码就可以更好的理解了。首先,Google官方推荐的Camera2控制拍照的步骤大致如下。
1.用CameraManager的openCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler)方法打开指定摄像头。该方法的第一个参数代表要打开的摄像头ID;第二个参数用于监听摄像头的状态;第三个参数代表执行callback的Handler,如果程序希望直接在当前线程中执行callback,则可将handler参数设为null。
2.当摄像头被打开之后会回调接口mStateCallback.onOpened,程序即可获取CameraDevice —— 即根据摄像头ID获取了指定摄像头设备,然后调用CameraDevice的createCaptureSession(List outputs, CameraCaptureSession. StateCallback callback,Handler handler)方法来创建CameraCaptureSession。该方法的第一个参数是一个List集合,封装了所有需要从该摄像头获取图片的Surface,第二个参数用于监听CameraCaptureSession的创建过程;第三个参数代表执行callback的Handler,如果程序希望直接在当前线程中执行callback,则可将handler参数设为null。
- 3.不管预览还是拍照,程序都调用CameraDevice的createCaptureRequest(int templateType)方法创建CaptureRequest.Builder,该方法支持TEMPLATE_PREVIEW(预览)、TEMPLATE_RECORD(拍摄视频)、TEMPLATE_STILL_CAPTURE(拍照)等参数。
- 4.通过第3步所调用方法返回的CaptureRequest.Builder设置拍照的各种参数,比如对焦模式、曝光模式等。
- 5.调用CaptureRequest.Builder的build()方法即可得到CaptureRequest对象,接下来程序可通过CameraCaptureSession的setRepeatingRequest()方法开始预览,或调用capture()方法拍照。
相机的预览与拍照流程我们基本了解了。 - 6预览时,是将mSurfaceHolder.getSurface()作为目标,使用setRepeatingRequest()方法,
显示拍照结果时,是将mImageReader.getSurface()作为目标,使用capture()方法。
然后这里还有一个大招:Google官方Camera2拍照的demo的地址:点击跳转github
首先是我们的layout代码
首先权限不能忘:
uses-permission android:name=”android.permission.CAMERA” /
uses-feature android:name=”android.hardware.camera” /
uses-feature android:name=”android.hardware.camera.autofocus” /
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><TextureViewandroid:id="@+id/texture"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_weight="1"/><LinearLayoutandroid:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"><Buttonandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:id="@+id/cancelButton"android:text="取消"android:visibility="invisible"/><Buttonandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:id="@+id/captureButton"android:text="拍照"android:visibility="visible"/><Buttonandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:id="@+id/saveButton"android:text="保存"android:visibility="invisible"/></LinearLayout>
</LinearLayout>
下面是我的代码
public class customCarmeraActivity extends AppCompatActivity {private static final int REQUEST_CAMERA_PERMISSION = 1;private static final int STATE_PREVIEW = 1;private static final int STATE_WAITING_PRECAPTURE = 2;private int mState = STATE_PREVIEW;private static final SparseIntArray ORIENTATIONS = new SparseIntArray();///为了使照片竖直显示static {ORIENTATIONS.append(Surface.ROTATION_0, 90);ORIENTATIONS.append(Surface.ROTATION_90, 0);ORIENTATIONS.append(Surface.ROTATION_180, 270);ORIENTATIONS.append(Surface.ROTATION_270, 180);}private CameraManager mCameraManagerm;private CameraDevice mCameraDevice;private String mCameraId;private HandlerThread mBackgroundThread;private Handler mBackgroundHandler;private TextureView mTextureView;private ImageReader mImageReader;private File mFile;private CaptureRequest.Builder mPreviewBuilder;private CaptureRequest mPreviewRequest;private CameraCaptureSession mCaptureSession;private Semaphore mCameraOpenCloseLock = new Semaphore(1);/*** Starts a background thread and its {@link Handler}.*/private void startBackgroundThread() {mBackgroundThread = new HandlerThread("CameraBackground");mBackgroundThread.start();mBackgroundHandler = new Handler(mBackgroundThread.getLooper());}/*** Stops the background thread and its {@link Handler}.*/private void stopBackgroundThread() {mBackgroundThread.quitSafely();try {mBackgroundThread.join();mBackgroundThread = null;mBackgroundHandler = null;} catch (InterruptedException e) {e.printStackTrace();}}/**** @TextureView.SurfaceTextureListener:public static interface* @onSurfaceTextureAvailable:Invoked when a TextureView's SurfaceTexture is ready for use.* @onSurfaceTextureSizeChanged:Invoked when the SurfaceTexture's buffers size changed.* @onSurfaceTextureDestroyed:Invoked when the specified SurfaceTexture is about to be destroyed.* @onSurfaceTextureUpdated:Invoked when the specified SurfaceTexture is updated through updateTexImage().*/private final TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {openCamera(width, height);}@Overridepublic void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {//configureTransform(width, height);}@Overridepublic boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {return true;}@Overridepublic void onSurfaceTextureUpdated(SurfaceTexture texture) {}};/*** Shows a {@link Toast} on the UI thread.** @param text The message to show*/private void showToast(final String text) {runOnUiThread(new Runnable() {@Overridepublic void run() {Toast.makeText(customCarmeraActivity.this, text, Toast.LENGTH_SHORT).show();}});}/*** Capture a still picture. This method should be called when we get a response in* {@link #mCaptureCallback} from both {@link #lockFocus()}.*/private void captureStillPicture() {try {if (null == mCameraDevice) {return;}// 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());//拍照时,是将mImageReader.getSurface()作为目标// Use the same AE and AF modes as the preview.captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);// Orientationint rotation = getWindowManager().getDefaultDisplay().getRotation();captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));CameraCaptureSession.CaptureCallback CaptureCallback= new CameraCaptureSession.CaptureCallback() {@Overridepublic void onCaptureCompleted(@NonNull CameraCaptureSession session,@NonNull CaptureRequest request,@NonNull TotalCaptureResult result) {showToast("Saved: " + mFile);//Log.d("customCarmeraActivity", mFile.toString());unlockFocus();//恢复预览}};mCaptureSession.stopRepeating();mCaptureSession.abortCaptures();mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);} catch (CameraAccessException e) {e.printStackTrace();}}private CameraCaptureSession.CaptureCallback mCaptureCallback =new CameraCaptureSession.CaptureCallback() {@Overridepublic void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,TotalCaptureResult result) {//Log.d("linc","mSessionCaptureCallback, onCaptureCompleted");mCaptureSession = session;checkState(result);}@Overridepublic void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,CaptureResult partialResult) {Log.d("linc","mSessionCaptureCallback, onCaptureProgressed");mCaptureSession = session;checkState(partialResult);}private void checkState(CaptureResult result) {switch (mState) {case STATE_PREVIEW:// We have nothing to do when the camera preview is working normally.break;case STATE_WAITING_PRECAPTURE:Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);if (afState == null) {captureStillPicture();} else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {// CONTROL_AE_STATE can be null on some devicesInteger aeState = result.get(CaptureResult.CONTROL_AE_STATE);if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {//mState = STATE_PICTURE_TAKEN;captureStillPicture();} else {//runPrecaptureSequence();//视频拍摄}}break;}}};private void createCameraPreviewSession() {try {SurfaceTexture texture = mTextureView.getSurfaceTexture();//assert(texture != null);// We configure the size of default buffer to be the size of camera preview we want.texture.setDefaultBufferSize(mTextureView.getWidth(), mTextureView.getHeight());// This is the output Surface we need to start preview.Surface surface = new Surface(texture);mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);mPreviewBuilder.addTarget(surface);//预览时,是将Surface()作为目标mState = STATE_PREVIEW;mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {// The camera is already closedif (null == mCameraDevice) {return;}mCaptureSession = cameraCaptureSession;try {mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);mPreviewBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);mPreviewRequest = mPreviewBuilder.build();mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();Log.e("linc","set preview builder failed."+e.getMessage());}}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {Toast.makeText(customCarmeraActivity.this, "Camera configuration Failed", Toast.LENGTH_SHORT).show();}},mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();}}/**** @CameraDevice.StateCallback:public static abstract class* @onOpened:This method is called when the camera is opened. We start camera preview here.* @onDisconnected:The method called when a camera device is no longer available for use.* @onError:The method called when a camera device has encountered a serious error.*/private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(CameraDevice cameraDevice) {mCameraOpenCloseLock.release();mCameraDevice = cameraDevice;createCameraPreviewSession();}@Overridepublic void onDisconnected(CameraDevice cameraDevice) {mCameraOpenCloseLock.release();cameraDevice.close();mCameraDevice = null;}@Overridepublic void onError(CameraDevice cameraDevice, int error) {mCameraOpenCloseLock.release();cameraDevice.close();mCameraDevice = null;}};private void getCameraId() {try {//Return the list of currently connected camera devices by identifier, including cameras that may be in use by other camera API clientsfor (String cameraId : mCameraManagerm.getCameraIdList()) {//Query the capabilities of a camera deviceCameraCharacteristics characteristics = mCameraManagerm.getCameraCharacteristics(cameraId);if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {continue;}mCameraId = cameraId;return;}} catch (CameraAccessException e) {e.printStackTrace();}}/*** Saves a JPEG {@link Image} into the specified {@link File}.*/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;}@Overridepublic 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();}}}}}private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {//showToast("onImageAvailable: " );mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));}};//TODO 执行完上面的请求权限后,系统会弹出提示框让用户选择是否允许改权限。选择的结果可以在回到接口中得知:@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {if (requestCode == REQUEST_CAMERA_PERMISSION) {if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {//用户允许改权限,0表示允许,-1表示拒绝 PERMISSION_GRANTED = 0, PERMISSION_DENIED = -1//permission was granted, yay! Do the contacts-related task you need to do.//这里进行授权被允许的处理} else {//permission denied, boo! Disable the functionality that depends on this permission.//这里进行权限被拒绝的处理}} else {super.onRequestPermissionsResult(requestCode, permissions, grantResults);}}/*** Opens the camera specified by {@link Camera2BasicFragment#mCameraId}.*/private void openCamera(int width, int height) {//检查相机服务的访问权限if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {//Toast.makeText(this,"Lacking privileges to access camera service, please request permission first",Toast.LENGTH_SHORT).show();Log.e("customCarmeraActivity.openCamera","Lacking privileges to access camera service, please request permission first");ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);//API21后,向用户请求相机使用权限,然后执行onRequestPermissionsResult回调return;}getCameraId();//assert(mCameraId != null);mImageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG,/*maxImages*/7);mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);try {if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {throw new RuntimeException("Time out waiting to lock camera opening.");}mCameraManagerm.openCamera(mCameraId, mStateCallback, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();} catch (InterruptedException e) {throw new RuntimeException("Interrupted while trying to lock camera opening.", e);}}/*** Closes the current {@link CameraDevice}.*/private void closeCamera() {try {mCameraOpenCloseLock.acquire();if (null != mCaptureSession) {mCaptureSession.close();mCaptureSession = null;}if (null != mCameraDevice) {mCameraDevice.close();mCameraDevice = null;}if (null != mImageReader) {mImageReader.close();mImageReader = null;}} catch (InterruptedException e) {throw new RuntimeException("Interrupted while trying to lock camera closing.", e);} finally {mCameraOpenCloseLock.release();}}/*** Unlock the focus. This method should be called when still image capture sequence is* finished.*/private void unlockFocus() {try {// Reset the auto-focus triggermPreviewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);mPreviewBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);/* mCaptureSession.capture(mPreviewBuilder.build(), mCaptureCallback,mBackgroundHandler);*/// After this, the camera will go back to the normal state of preview.mState = STATE_PREVIEW;mCaptureSession.setRepeatingRequest(mPreviewBuilder.build(), mCaptureCallback, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();}}/*** Lock the focus as the first step for a still image capture.*/private void lockFocus() {try {// This is how to tell the camera to lock focus.mPreviewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,CameraMetadata.CONTROL_AF_TRIGGER_START);// Tell #mCaptureCallback to wait for the lock.mState = STATE_WAITING_PRECAPTURE;mCaptureSession.capture(mPreviewBuilder.build(), mCaptureCallback,mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.custom_carmera_layout);mCameraManagerm = (CameraManager)getSystemService(Context.CAMERA_SERVICE);mTextureView = (TextureView)findViewById(R.id.textureview);mFile = new File(getExternalFilesDir(null), "pic.jpg");Button tackPictureBtn = (Button)findViewById(R.id.takePictureButton);tackPictureBtn.setOnClickListener(new View.OnClickListener(){@Overridepublic void onClick(View view) {//Log.i("linc", "take picture");lockFocus();}});}@Overrideprotected void onResume() {super.onResume();startBackgroundThread();if(mTextureView.isAvailable()) {openCamera(mTextureView.getWidth(), mTextureView.getHeight());} else{mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);}}@Overrideprotected void onPause() {super.onPause();closeCamera();stopBackgroundThread();}
}
https://blog.csdn.net/zhangzheng_1986/article/details/78710655?locationNum=9&fps=1
android camera(6)---camera2 拍照流程相关推荐
- Android手机拍照程序开发,android Camera开发-手机拍照流程
android 拍照API流程 1. 在布局文件中添加一个 surfaceView (摄影平面) 2.根据 SurfaceView 获得 Holder (固定器) 3.给固定器设置 SurfaceH ...
- Android camera相机开发拍照功能
在Android 5.0(SDK 21)中,Google使用Camera2替代了Camera接口.Camera2在接口和架构上做了巨大的变动,但是基于众所周知的原因,我们还必须基于 Android 4 ...
- Android Camera、Camera2详解
前言 Android5.0之前使用android.hardware包下的Camera类进行拍照.录视频等功能.5.0以后,新增了android.hardware.camera2包,利用新的机制.新的类 ...
- Android Camera 打开预览流程分析(一)--打开camera的SDK流程
Android系统应用场景中,Camera的使用场景变得越来越重要,在手机端不管是牌照美颜,还是拍小视频上传小视频平台.在其他领域,如车载,倒车视频,360全景影像也同样会用到Camera接口.那我们 ...
- Android Camera API/Camera2 API 相机预览及滤镜、贴纸等处理
Android Lollipop 增加了Camera2 API,并将原来的Camera API标记为废弃了.相对原来的Camera API来说,Camera2是重新定义的相机 API,也重构了相机 A ...
- Android 10.0 Camera2 拍照功能默认选前摄像头
1.概述 在10.0的系统产品开发中,对于app调用系统api来打开摄像头拍照的功能也是常有的功能,而拍照一般是默认打开后置摄像头拍照的,由于 客户的产品特殊要求,需要打开前置摄像头拍照功能,所以需要 ...
- Android如何使用Camera2拍照【简易快速上手篇】
因为大部分的需求,并没有那么复杂,只需要简单的拍照功能,对于这种简单的需求,本文可以很好的满足.本文,是对Camera2做一个极简易的封装,以及去掉其它不重要的API,帮助你在几分钟内,使用Camer ...
- android camera噪点,拍照时总是有很多噪点怎么办?方法很简单但你还真不知道
原标题:拍照时总是有很多噪点怎么办?方法很简单但你还真不知道 摄影时很多小伙伴对颗粒感格外偏爱,总觉得这种质感能让照片更耐看.可悲的是,有些人盲目沉迷这种效果,噪点让人像都成麻子脸了还洋洋自得. 颗粒 ...
- Android 10.0 Camera2 静音时拍照去掉快门声音
1.概述 在10.0的系统产品开发中,对于Camera2相机的产品定制化中,发现在Camera2中发现一个问题 当媒体音量静音时,点击拍照还是有拍照声音,产品对这个不满意,所以要修改这个问题,所以针对 ...
最新文章
- 简谈-Python一些常用的爬虫技巧
- Linux Named 进程启动、停止脚本
- hashmap应用场景_Java初学者进阶系列:HashMap的容量与性能
- 关于ResultSet can not re-read row data for column 1 解决方法
- 使用Chronicle Wire将YAML连接到文件或网络
- 四种数据库特性对比(Redis/Mysql/SQLite/MongoDB)
- 从零基础入门Tensorflow2.0 ----七、33 数据padding,模型构建,训练
- Performing User-Managed Database-18.4、Restoring Datafiles and Archived Redo Logs
- 开源同步文件软件对比
- 好看的字体—方正粗倩
- 北京航天大学考研计算机科学与技术分数线,北京航空航天大学计算机科学与技术考研...
- 研发工程师L2_编程题
- android studio 显示view树_Android手势分发和嵌套滚动机制
- U大师U盘启动盘克隆制作工具
- 临床预测模型之综合判别改善指数IDI计算
- 为什么有的域名需要加WWW才能访问?
- python学习笔记之序列,内含列表和元组的常用方法
- 三年级下册计算机全册教案,小学三年级下册信息技术教案三篇
- 全球与中国沸石吸附剂市场深度研究分析报告
- sourceinsight使用技巧