admin管理员组文章数量:1530842
Android Camera模块(一)
一、Android Camera架构
1.1 整体架构
架构参考Android Camera简单整理(一)-Camera Android架构(基于Q)
google 官方图:
从上图得知,Android手机中Camera软件主要有大体上有4层:
1.应用层: 应用开发者调用AOSP提供的接口即可,AOSP的接口即Android提供的相机应用的通用接口,这些接口将通过Binder与Framework层的相机服务进行操作与数据传递;
2.Framework层: 位于 frameworks/av/services/camera/libcameraservice/CameraService.cpp ,相机Framework服务是承上启下的作用,上与应用交互,下与HAL曾交互。
3.Hal层: 硬件抽象层,Android 定义好了Framework服务与HAL层通信的协议及接口,HAL层如何实现有各个Vendor自己实现,如Qcom的老架构mm-Camera,新架构Camx架构,Mtk的P之后的Hal3架构.
4.Driver层: 驱动层,数据由硬件到驱动层处理,驱动层接收HAL层数据以及传递Sensor数据给到HAL层,这里当然是各个Sensor芯片不同驱动也不同.
说到这为什么要分这么多层,大体上还是为了分清界限,升级方便, AndroidO Treble架构分析.
Android要适应各个手机组装厂商的不同配置,不同sensor,不管怎么换芯片,从Framework及之上都不需要改变,App也不需要改变就可以在各种配置机器上顺利运行,HAL层对上的接口也由Android定义死,各个平台厂商只需要按照接口实现适合自己平台的HAL层即可.
1.2 Android Camera工作大体流程
绿色框中是应用开发者需要做的操作,蓝色为AOSP提供的API,黄色为Native Framework Service,紫色为HAL层Service.
描述一下步骤:
1 App一般在MainActivity中使用SurfaceView或者SurfaceTexture+TextureView或者GLSurfaceView等控件作为显示预览界面的控件,共同点都是包含了一个单独的Surface作为取相机数据的容器.
2 在MainActivity onCreate的时候调用API 去通知Framework Native Service CameraServer去connect HAL继而打开Camera硬件sensor.
3 openCamera成功会有回调从CameraServer通知到App,在onOpenedCamera或类似回调中去调用类似startPreview的操作.此时会创建CameraCaptureSession,创建过程中会向CameraServer调用ConfigureStream的操作,ConfigureStream的参数中包含了第一步中空间中的Surface的引用,相当于App将Surface容器给到了CameraServer,CameraServer包装了下该Surface容器为stream,通过HIDL传递给HAL,继而HAL也做configureStream操作
4 ConfigureStream成功后CameraServer会给App回调通知ConfigStream成功,接下来App便会调用AOSP setRepeatingRequest接口给到CameraServer,在Camera3Device初始化时起来了一个死循环线程 RequestThread来接收Request。
5 CameraServer将request交到Hal层去处理,得到HAL处理结果后取出该Request的处理Result中的Buffer填到App给到的容器中,SetRepeatingRequest为了预览,则交给Preview的Surface容器,如果是Capture Request则将收到的Buffer交给ImageReader的Surface容器.
6 Surface本质上是BufferQueue的使用者和封装者,当CameraServer中App设置来的Surface容器被填满了BufferQueue机制将会通知到应用,此时App中控件取出各自容器中的内容消费掉,Preview控件中的Surface中的内容将通过View提供到SurfaceFlinger中进行合成最终显示出来,即预览;而ImageReader中的Surface被App取出保存成图片文件消费掉.
比较简单的一张图:
二、Camera APP的启动流程
我们如果打开Android原生相机会呈现以下界面(由于没有Android12的设备所以使用Android模拟器来操作):
接着我们寻找上面这个界面是那个Activity呈现的,直接dump当前resumed的actvitiy:
emulator64_x86_64:/ $ dumpsys activity |grep mResumed
mResumedActivity: ActivityRecord{f078923 u0 com.android.camera2/com.android.camera.CameraLauncher t73}
可以清楚的看到当前的Activity是CameraLauncher在代码库种查找这个类发现在
/packages/apps/Camera2/AndroidManifest.xml
83 <activity-alias
84 android:name="com.android.camera.CameraLauncher"
85 android:label="@string/app_name"
86 android:exported="true"
87 android:targetActivity="com.android.camera.CameraActivity">
88 <intent-filter>
89 <action android:name="android.intent.action.MAIN" />
90
91 <category android:name="android.intent.category.DEFAULT" />
92 <category android:name="android.intent.category.LAUNCHER" />
93 </intent-filter>
94 </activity-alias>
可以看到CameraLauncher是CameraActivity的别名,所以我们在打开Camera相机时,其实就是启动了CameraActivity。
接着我们看CameraActivity是如何打开相机并预览的:
/packages/apps/Camera2/src/com/android/camera/CameraActivity.java
177 public class CameraActivity extends QuickActivity
178 implements AppController, CameraAgent.CameraOpenCallback,
179 ShareActionProvider.OnShareTargetSelectedListener {
首先CameraActivity继承自QuickActivity,我们先看一下QuickActivity的实现:
107 @Override
108 protected final void onCreate(Bundle bundle) {
109 mExecutionStartNanoTime = SystemClock.elapsedRealtimeNanos();
110 logLifecycle("onCreate", true);
111 mStartupOnCreate = true;
112 super.onCreate(bundle);
113 mMainHandler = new Handler(getMainLooper());
114 onCreateTasks(bundle);
115 logLifecycle("onCreate", false);
116 }
...
200 }
可以看到QuickActivity主要就是将Activity的生命周期再次封装了一下:其中各种生命周期对应关系如下:
QuickActivity | CameraActivity |
---|---|
onCreate | onCreateTasks |
onStart | onStartTasks |
onResume | onResumeTasks |
onPause | onPauseTasks |
onStop | onStopTasks |
onNewIntent | onNewIntentTasks |
所以CameraActivity创建时会调用到onCreateTasks:
2.1 onCreateTasks
/packages/apps/Camera2/src/com/android/camera/CameraActivity.java
1430 @Override
1431 public void onCreateTasks(Bundle state) {
//1.初始化一些Manager并且检查权限
1465 mActiveCameraDeviceTracker = ActiveCameraDeviceTracker.instance();
1466 try {
1467 mOneCameraOpener = OneCameraModule.provideOneCameraOpener(
1468 mFeatureConfig,
1469 mAppContext,
1470 mActiveCameraDeviceTracker,
1471 ResolutionUtil.getDisplayMetrics(this));
1472 mOneCameraManager = OneCameraModule.provideOneCameraManager();
1473 } catch (OneCameraException e) {
1474 // Log error and continue start process while showing error dialog..
1475 Log.e(TAG, "Creating camera manager failed.", e);
1476 mFatalErrorHandler.onGenericCameraAccessFailure();
1477 }
1478 profile.mark("OneCameraManager.get");
1464
1465 mActiveCameraDeviceTracker = ActiveCameraDeviceTracker.instance();
1479 //2.初始化CameraController
1480 try {
1481 mCameraController = new CameraController(mAppContext, this, mMainHandler,
1482 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1483 CameraAgentFactory.CameraApi.API_1),
1484 CameraAgentFactory.getAndroidCameraAgent(mAppContext,
1485 CameraAgentFactory.CameraApi.AUTO),
1486 mActiveCameraDeviceTracker);
1487 mCameraController.setCameraExceptionHandler(
1488 new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler));
1489 } catch (AssertionError e) {
1490 Log.e(TAG, "Creating camera controller failed.", e);
1491 mFatalErrorHandler.onGenericCameraAccessFailure();
1492 }
1493 //3.初始化ModuleManager并设置module的创建规则
1494 // TODO: Try to move all the resources allocation to happen as soon as
1495 // possible so we can call module.init() at the earliest time.
1496 mModuleManager = new ModuleManagerImpl();
1497
1498 ModulesInfo.setupModules(mAppContext, mModuleManager, mFeatureConfig);
1499
1500 AppUpgrader appUpgrader = new AppUpgrader(this);
1501 appUpgrader.upgrade(mSettingsManager);
1502
...
1528 //4.设置视图
1529 profile.mark();
1530 setContentView(R.layout.activity_main);
1531 profile.mark("setContentView()");
...
//5.设置工作模式
1625 setModuleFromModeIndex(getModeIndex());
1627 profile.mark();
1628 mCameraAppUI.prepareModuleUI();
1629 profile.mark("Init Current Module UI");
//6.初始化模块
1630 mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
1631 profile.mark("Init CurrentModule");
1687 }
可以看到在Camera的onCreate种主要做了以下几件事:
1.一些基本的初始化
2.初始化CameraController和ModuleManager,并设置module的创建规则。
3.设置视图和工作模式。
4.初始化photo module
这一步我们主要看一下ModuleManager干了什么事,首先初始化了一个ModuleManager,接着调用了 ModulesInfo.setupModules来设置模块的生成规则。
/packages/apps/Camera2/src/com/android/camera/module/ModulesInfo.java
47 public static void setupModules(Context context, ModuleManager moduleManager,
48 OneCameraFeatureConfig config) {
49 Resources res = context.getResources();
50 int photoModuleId = context.getResources().getInteger(R.integer.camera_mode_photo);
51 registerPhotoModule(moduleManager, photoModuleId, SettingsScopeNamespaces.PHOTO,
52 config.isUsingCaptureModule());
53 moduleManager.setDefaultModuleIndex(photoModuleId);
54 registerVideoModule(moduleManager, res.getInteger(R.integer.camera_mode_video),
55 SettingsScopeNamespaces.VIDEO);
56 if (PhotoSphereHelper.hasLightCycleCapture(context)) {
57 registerWideAngleModule(moduleManager, res.getInteger(R.integer.camera_mode_panorama),
58 SettingsScopeNamespaces.PANORAMA);
59 registerPhotoSphereModule(moduleManager,
60 res.getInteger(R.integer.camera_mode_photosphere),
61 SettingsScopeNamespaces.PANORAMA);
62 }
63 if (RefocusHelper.hasRefocusCapture(context)) {
64 registerRefocusModule(moduleManager, res.getInteger(R.integer.camera_mode_refocus),
65 SettingsScopeNamespaces.REFOCUS);
66 }
67 if (GcamHelper.hasGcamAsSeparateModule(config)) {
68 registerGcamModule(moduleManager, res.getInteger(R.integer.camera_mode_gcam),
69 SettingsScopeNamespaces.PHOTO,
70 config.getHdrPlusSupportLevel(OneCamera.Facing.BACK));
71 }
72 int imageCaptureIntentModuleId = res.getInteger(R.integer.camera_mode_capture_intent);
73 registerCaptureIntentModule(moduleManager, imageCaptureIntentModuleId,
74 SettingsScopeNamespaces.PHOTO, config.isUsingCaptureModule());
75 }
可以看到setupModules注册了很多模块,主要有PhotoModule,VideoModule和CaptureIntentModule,另外几个则是有条件限制:WideAngleModule,PhotoSphereModule,RefocusModule,GcamModule。由于我们一进app就是拍照,所以我们以PhotoModule为例:
/packages/apps/Camera2/src/com/android/camera/module/ModulesInfo.java
77 private static void registerPhotoModule(ModuleManager moduleManager, final int moduleId,
78 final String namespace, final boolean enableCaptureModule) {
79 moduleManager.registerModule(new ModuleManager.ModuleAgent() {
80
81 @Override
82 public int getModuleId() {
83 return moduleId;
84 }
85
86 @Override
87 public boolean requestAppForCamera() {
88 // The PhotoModule requests the old app camere, while the new
89 // capture module is using OneCamera. At some point we'll
90 // refactor all modules to use OneCamera, then the new module
91 // doesn't have to manage it itself.
92 return !enableCaptureModule;
93 }
94
95 @Override
96 public String getScopeNamespace() {
97 return namespace;
98 }
99
100 @Override
101 public ModuleController createModule(AppController app, Intent intent) {
102 Log.v(TAG, "EnableCaptureModule = " + enableCaptureModule);
103 return enableCaptureModule ? new CaptureModule(app) : new PhotoModule(app);
104 }
105 });
106 }
这里只是将ModuleAgent注册给了moduleManager还并未真正创建module创建module要到设置工作模式时进行。这里有两个点后面会用到
1.注册的PhotoModule的photoModuleId为0
2.注册PhotoModule时传入enableCaptureModule默认为true
接着调用setModuleFromModeIndex(getModeIndex());设置工作模式:getModeIndex默认返回的就是photoIndex 也就是0
/packages/apps/Camera2/src/com/android/camera/CameraActivity.java
2562 private void setModuleFromModeIndex(int modeIndex) {
2563 ModuleManagerImpl.ModuleAgent agent = mModuleManager.getModuleAgent(modeIndex);
2564 if (agent == null) {
2565 return;
2566 }
2567 if (!agent.requestAppForCamera()) {
2568 mCameraController.closeCamera(true);
2569 }
2570 mCurrentModeIndex = agent.getModuleId();
//将CameraActivity作为AppControle传入module
2571 mCurrentModule = (CameraModule) agent.createModule(this, getIntent());
2572 }
在setModuleFromModeIndex会通过agent.createModule创建一个module,在这里时创建了一个PhotoModule又因为EnableCaptureModule为true,所以最终mCurrentModule是CaptureModule。接着会调用 mCurrentModule.init去初始化模块。主要就是初始化一些线程并创建一个handler处理请求,并且获得了mOneCameraOpener和 mOneCameraManager,接着生命周期走到onResumed时CameraActivity走到
2.2 onResumeTask
/packages/apps/Camera2/src/com/android/camera/CameraActivity.java
1894 public void onResumeTasks() {
1895 mPaused = false;
1896 checkPermissions();
1897 if (!mHasCriticalPermissions) {
1898 Log.v(TAG, "onResume: Missing critical permissions.");
1899 finish();
1900 return;
1901 }
//1.这里由于我们时正常启动camera app所以都是走到里面的mFirstRunDialog.showIfNecessary
1902 if (!isSecureCamera() && !isCaptureIntent()) {
1903 // Show the dialog if necessary. The rest resume logic will be invoked
1904 // at the onFirstRunStateReady() callback.
1905 try {
1906 mFirstRunDialog.showIfNecessary();
1907 } catch (AssertionError e) {
1908 Log.e(TAG, "Creating camera controller failed.", e);
1909 mFatalErrorHandler.onGenericCameraAccessFailure();
1910 }
1911 } else {
1912 // In secure mode from lockscreen, we go straight to camera and will
1913 // show first run dialog next time user enters launcher.
1914 Log.v(TAG, "in secure mode, skipping first run dialog check");
1915 resume();
1916 }
1917 }
这里的 mFirstRunDialog.showIfNecessary();主要就是显示两个权限请求框如下:
这两个图片都确认后会走到FirstRunDialog的onFirstRunStateReady
/packages/apps/Camera2/src/com/android/camera/CameraActivity.java
1661 mFirstRunDialog = new FirstRunDialog(this,
1662 this /* as context */,
1663 mResolutionSetting,
1664 mSettingsManager,
1665 mOneCameraManager,
1666 new FirstRunDialog.FirstRunDialogListener() {
1667 @Override
1668 public void onFirstRunStateReady() {
1669 // Run normal resume tasks.
1670 resume();
1671 }
1672
1673 @Override
1674 public void onFirstRunDialogCancelled() {
1675 // App isn't functional until users finish first run dialog.
1676 // We need to finish here since users hit back button during
1677 // first run dialog (b/19593942).
1678 finish();
1679 }
1680
1681 @Override
1682 public void onCameraAccessException() {
1683 mFatalErrorHandler.onGenericCameraAccessFailure();
1684 }
1685 });
即走到resume()
/packages/apps/Camera2/src/com/android/camera/CameraActivity.java
2002 private void resume() {
...
2072
2073 profile.mark();
2074 mCurrentModule.resume();
2075 UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
2076 NavigationChange.InteractionCause.BUTTON);
2077 setSwipingEnabled(true);
2078 profile.mark("mCurrentModule.resume");
2079 ...
2153 }
这里我们只关注mCurrentModule的resume
/packages/apps/Camera2/src/com/android/camera/CaptureModule.java
674 @Override
675 public void resume() {
....
705 // This means we are resuming with an existing preview texture. This
706 // means we will never get the onSurfaceTextureAvailable call. So we
707 // have to open the camera and start the preview here.
708 SurfaceTexture texture = getPreviewSurfaceTexture();
709
710 guard.mark();
711 if (texture != null) {
//在这里会初始化SurfaceTexture并打开相机开始预览
712 initSurfaceTextureConsumer();
713 guard.mark("initSurfaceTextureConsumer");
714 }
715
716 mSoundPlayer.loadSound(R.raw.timer_final_second);
717 mSoundPlayer.loadSound(R.raw.timer_increment);
718
719 guard.mark();
720 mHeadingSensor.activate();
721 guard.stop("mHeadingSensor.activate()");
722 }
633 private void initSurfaceTextureConsumer() {
634 synchronized (mSurfaceTextureLock) {
635 if (mPreviewSurfaceTexture != null) {
636 mPreviewSurfaceTexture.setDefaultBufferSize(
637 mAppController.getCameraAppUI().getSurfaceWidth(),
638 mAppController.getCameraAppUI().getSurfaceHeight());
639 }
640 }
641 reopenCamera();
642 }
644 private void reopenCamera() {
645 if (mPaused) {
646 return;
647 }
//使用线程池执行openCameraAndStartPreview
648 AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
649 @Override
650 public void run() {
651 closeCamera();
652 if(!mAppController.isPaused()) {
653 openCameraAndStartPreview();
654 }
655 }
656 });
657 }
658
最终打开相机并预览是通过线程池执行了openCameraAndStartPreview
/packages/apps/Camera2/src/com/android/camera/CaptureModule.java
1315 private void openCameraAndStartPreview() {
1371 //1.调用mOneCameraOpener.open去打开相机
1372 mOneCameraOpener.open(cameraId, captureSetting, mCameraHandler, mainThread,
1373 imageRotationCalculator, mBurstController, mSoundPlayer,
1374 new OpenCallback() {
1375 @Override
1376 public void onFailure() {
1377 Log.e(TAG, "Could not open camera.");
1378 // Sometimes the failure happens due to the controller
1379 // being in paused state but mCamera is already
1380 // initialized. In these cases we just need to close the
1381 // camera device without showing the error dialog.
1382 // Application will properly reopen the camera on the next
1383 // resume operation (b/21025113).
1384 boolean isControllerPaused = mAppController.isPaused();
1385 if (mCamera != null) {
1386 mCamera.close();
1387 }
1388 mCamera = null;
1389 mCameraOpenCloseLock.release();
1390 if (!isControllerPaused) {
1391 mAppController.getFatalErrorHandler().onCameraOpenFailure();
1392 }
1393 }
1394
1395 @Override
1396 public void onCameraClosed() {
1397 mCamera = null;
1398 mCameraOpenCloseLock.release();
1399 }
1400
1401 @Override
1402 public void onCameraOpened(@Nonnull final OneCamera camera) {
1403 Log.d(TAG, "onCameraOpened: " + camera);
1404 mCamera = camera;
1405
1406 // A race condition exists where the camera may be in the process
1407 // of opening (blocked), but the activity gets destroyed. If the
1408 // preview is initialized or callbacks are invoked on a destroyed
1409 // activity, bad things can happen.
1410 if (mAppController.isPaused()) {
1411 onFailure();
1412 return;
1413 }
1414
1415 // When camera is opened, the zoom is implicitly reset to 1.0f
1416 mZoomValue = 1.0f;
1417
1418 updatePreviewBufferDimension();
1419
1420 // If the surface texture is not destroyed, it may have
1421 // the last frame lingering. We need to hold off setting
1422 // transform until preview is started.
1423 updatePreviewBufferSize();
1424 mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED;
1425 Log.d(TAG, "starting preview ...");
1426
1427 // TODO: make mFocusController final and remove null
1428 // check.
1429 if (mFocusController != null) {
1430 camera.setFocusDistanceListener(mFocusController);
1431 }
1432
1433 mMainThread.execute(new Runnable() {
1434 @Override
1435 public void run() {
1436 mAppController.getCameraAppUI().onChangeCamera();
1437 mAppController.getButtonManager().enableCameraButton();
1438 }
1439 });
1440 //2.开始预览将SurfaceTexture传给Camera用来显示预览的画面
1441 // TODO: Consider rolling these two calls into one.
1442 camera.startPreview(new Surface(getPreviewSurfaceTexture()),
1443 new CaptureReadyCallback() {
1444 @Override
1445 public void onSetupFailed() {
1446 // We must release this lock here,
1447 // before posting to the main handler
1448 // since we may be blocked in pause(),
1449 // getting ready to close the camera.
1450 mCameraOpenCloseLock.release();
1451 Log.e(TAG, "Could not set up preview.");
1452 mMainThread.execute(new Runnable() {
1453 @Override
1454 public void run() {
1455 if (mCamera == null) {
1456 Log.d(TAG, "Camera closed, aborting.");
1457 return;
1458 }
1459 mCamera.close();
1460 mCamera = null;
1461 // TODO: Show an error message
1462 // and exit.
1463 }
1464 });
1465 }
1466
1467 @Override
1468 public void onReadyForCapture() {
1469 // We must release this lock here,
1470 // before posting to the main handler
1471 // since we may be blocked in pause(),
1472 // getting ready to close the camera.
1473 mCameraOpenCloseLock.release();
1474 mMainThread.execute(new Runnable() {
1475 @Override
1476 public void run() {
1477 Log.d(TAG, "Ready for capture.");
1478 if (mCamera == null) {
1479 Log.d(TAG, "Camera closed, aborting.");
1480 return;
1481 }
1482 onPreviewStarted();
1483 // May be overridden by
1484 // subsequent call to
1485 // onReadyStateChanged().
1486 onReadyStateChanged(true);
1487 mCamera.setReadyStateChangedListener(
1488 CaptureModule.this);
1489 // Enable zooming after preview
1490 // has started.
1491 mUI.initializeZoom(mCamera.getMaxZoom());
1492 mCamera.setFocusStateListener(CaptureModule.this);
1493 }
1494 });
1495 }
1496 });
1497 }
1498 }, mAppController.getFatalErrorHandler());
1499 guard.stop("mOneCameraOpener.open()");
1500 }
这里的mOneCameraOpener是在module在init时从appcontroler中获取到的:
/packages/apps/Camera2/src/com/android/camera/CaptureModule.java
400 @Override
401 public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
402 Profile guard = mProfiler.create("CaptureModule.init").start();
403 Log.d(TAG, "init UseAutotransformUiLayout = " + USE_AUTOTRANSFORM_UI_LAYOUT);
404 HandlerThread thread = new HandlerThread("CaptureModule.mCameraHandler");
405 thread.start();
406 mCameraHandler = new Handler(thread.getLooper());
//从mAppController获取mOneCameraOpener
407 mOneCameraOpener = mAppController.getCameraOpener();
408
409 try {
410 mOneCameraManager = OneCameraModule.provideOneCameraManager();
411 } catch (OneCameraException e) {
412 Log.e(TAG, "Unable to provide a OneCameraManager. ", e);
413 }
...
443 }
而mAppController则是在创建module时将CameraActivity传到module中的
2.3 Camera Open
上一节说到调用mOneCameraOpener.open去打开相机,并且mOneCameraOpener也是CameraActivity在onCreateTask后调用 OneCameraModule.provideOneCameraOpener初始化的,
/packages/apps/Camera2/src/com/android/camera/one/OneCameraModule.java
43 public static OneCameraOpener provideOneCameraOpener(
44 OneCameraFeatureConfig featureConfig,
45 Context context,
46 ActiveCameraDeviceTracker activeCameraDeviceTracker,
47 DisplayMetrics displayMetrics) throws OneCameraException {
48 Optional<OneCameraOpener> manager = Camera2OneCameraOpenerImpl.create(
49 featureConfig, context, activeCameraDeviceTracker, displayMetrics);
50 if (!manager.isPresent()) {
51 manager = LegacyOneCameraOpenerImpl.create();
52 }
53 if (!manager.isPresent()) {
54 throw new OneCameraException("No camera manager is available.");
55 }
56 return manager.get();
57 }
在CameraActivity的onCreateTask中创建的mOneCameraOpener是Camera2OneCameraOpenerImpl,最终打开相机调用的是Camera2OneCameraOpenerImpl的open函数:
/packages/apps/Camera2/src/com/android/camera/one/v2/Camera2OneCameraOpenerImpl.java
102 @Override
103 public void open(
104 final CameraId cameraKey,
105 final OneCameraCaptureSetting captureSetting,
106 final Handler handler,
107 final MainThread mainThread,
108 final ImageRotationCalculator imageRotationCalculator,
109 final BurstFacade burstController,
110 final SoundPlayer soundPlayer,
111 final OpenCallback openCallback,
112 final FatalErrorHandler fatalErrorHandler) {
113 try {
114 Log.i(TAG, "Opening Camera: " + cameraKey);
115
116 mActiveCameraDeviceTracker.onCameraOpening(cameraKey);
117 //最终通过mCameraManager的openCamera调用到CameraServer去打开相机
118 mCameraManager.openCamera(cameraKey.getValue(), new CameraDevice.StateCallback() {
119 // We may get multiple calls to StateCallback, but only the
120 // first callback indicates the status of the camera-opening
121 // operation. For example, we may receive onOpened() and later
122 // onClosed(), but only the first should be relayed to
123 // openCallback.
124 private boolean isFirstCallback = true;
125
126 @Override
127 public void onDisconnected(CameraDevice device) {
128 if (isFirstCallback) {
129 isFirstCallback = false;
130 // If the camera is disconnected before it is opened
131 // then we must call close.
132 device.close();
133 openCallback.onCameraClosed();
134 }
135 }
136
137 @Override
138 public void onClosed(CameraDevice device) {
139 if (isFirstCallback) {
140 isFirstCallback = false;
141 openCallback.onCameraClosed();
142 }
143 }
144
145 @Override
146 public void onError(CameraDevice device, int error) {
147 if (isFirstCallback) {
148 isFirstCallback = false;
149 device.close();
150 openCallback.onFailure();
151 } else {
152 // Ensures we handle the case where an error occurs
153 // after the camera has been opened.
154 fatalErrorHandler.onGenericCameraAccessFailure();
155 }
156 }
157 //相机打开后回传的CameraDevice
158 @Override
159 public void onOpened(CameraDevice device) {
160 if (isFirstCallback) {
161 isFirstCallback = false;
162 try {
163 CameraCharacteristics characteristics = mCameraManager
164 .getCameraCharacteristics(device.getId());
165 // TODO: Set boolean based on whether HDR+ is
166 // enabled.
//包装成OneCamera回调给了CaptureModule的onCameraOpened
167 OneCamera oneCamera = OneCameraCreator.create(
168 device,
169 characteristics,
170 mFeatureConfig,
171 captureSetting,
172 mDisplayMetrics,
173 mContext,
174 mainThread,
175 imageRotationCalculator,
176 burstController,
177 soundPlayer, fatalErrorHandler);
178
179 if (oneCamera != null) {
180 openCallback.onCameraOpened(oneCamera);
181 } else {
182 Log.d(TAG, "Could not construct a OneCamera object!");
183 openCallback.onFailure();
184 }
185 } catch (CameraAccessException e) {
186 Log.d(TAG, "Could not get camera characteristics", e);
187 openCallback.onFailure();
188 } catch (OneCameraAccessException e) {
189 Log.d(TAG, "Could not create OneCamera", e);
190 openCallback.onFailure();
191 }
192 }
193 }
194 }, handler);
195 } catch (CameraAccessException ex) {
196 Log.e(TAG, "Could not open camera. " + ex.getMessage());
197 handler.post(new Runnable() {
198 @Override
199 public void run() {
200 openCallback.onFailure();
201 }
202 });
203 } catch (UnsupportedOperationException ex) {
204 Log.e(TAG, "Could not open camera. " + ex.getMessage());
205 handler.post(new Runnable() {
206 @Override
207 public void run() {
208 openCallback.onFailure();
209 }
210 });
211 } catch (SecurityException ex) {
212 fatalErrorHandler.onCameraDisabledFailure();
213 }
214 }
打开相机后,在CaptureModule传入的接口OpenCallback的onOpenCamera中开启预览
2.4 onCameraOpened
/packages/apps/Camera2/src/com/android/camera/CaptureModule.java
1401 @Override
1402 public void onCameraOpened(@Nonnull final OneCamera camera) {
1403 Log.d(TAG, "onCameraOpened: " + camera);
1404 mCamera = camera;
1405
1406 // A race condition exists where the camera may be in the process
1407 // of opening (blocked), but the activity gets destroyed. If the
1408 // preview is initialized or callbacks are invoked on a destroyed
1409 // activity, bad things can happen.
1410 if (mAppController.isPaused()) {
1411 onFailure();
1412 return;
1413 }
1414
1415 // When camera is opened, the zoom is implicitly reset to 1.0f
1416 mZoomValue = 1.0f;
1417
1418 updatePreviewBufferDimension();
1419
1420 // If the surface texture is not destroyed, it may have
1421 // the last frame lingering. We need to hold off setting
1422 // transform until preview is started.
1423 updatePreviewBufferSize();
1424 mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED;
1425 Log.d(TAG, "starting preview ...");
1426
1427 // TODO: make mFocusController final and remove null
1428 // check.
1429 if (mFocusController != null) {
1430 camera.setFocusDistanceListener(mFocusController);
1431 }
1432
1433 mMainThread.execute(new Runnable() {
1434 @Override
1435 public void run() {
1436 mAppController.getCameraAppUI().onChangeCamera();
1437 mAppController.getButtonManager().enableCameraButton();
1438 }
1439 });
1440 //2.开始预览将SurfaceTexture传给Camera用来显示预览的画面
1441 // TODO: Consider rolling these two calls into one.
1442 camera.startPreview(new Surface(getPreviewSurfaceTexture()),
1443 new CaptureReadyCallback() {
1444 @Override
1445 public void onSetupFailed() {
1446 // We must release this lock here,
1447 // before posting to the main handler
1448 // since we may be blocked in pause(),
1449 // getting ready to close the camera.
1450 mCameraOpenCloseLock.release();
1451 Log.e(TAG, "Could not set up preview.");
1452 mMainThread.execute(new Runnable() {
1453 @Override
1454 public void run() {
1455 if (mCamera == null) {
1456 Log.d(TAG, "Camera closed, aborting.");
1457 return;
1458 }
1459 mCamera.close();
1460 mCamera = null;
1461 // TODO: Show an error message
1462 // and exit.
1463 }
1464 });
1465 }
1466
在相机打开后会调用startPreview把CameraUI的SurfaceTexture包装成一个Surface发给camera做预览。
2.5小结
总结来看CameraApp在启动后主要就是干了两件事,打开相机,预览相机。其中打开相机主要是通过Camera2OneCameraOpenerImpl调用CameraManager.openCamera,接收到相机的引用后调用oneCamera的startPreview开启预览。后面我们具体来看这两步是如何实现的。
三、拍照流程
通过Android Device Monitor的Hierarchy View可以了解到一个Activity的层级,比如CameraApp启动后的层级如下:
从中可以轻易找到拍照键的id是shutter_button。接着我们在代码库中搜索整个id可以知道他是在CameraAppUI中初始化的。而且ShutterButton是继承于ImageView,当图片被点击时会回到到onShutterButtonClick函数。
/packages/apps/Camera2/src/com/android/camera/app/CameraAppUI.java
1313 public void prepareModuleUI() {
...
1337 //拍照按键
1338 mShutterButton = (ShutterButton) mCameraRootView.findViewById(R.id.shutter_button);
1339 addShutterListener(mController.getCurrentModuleController());
1340 addShutterListener(mModeOptionsOverlay);
1341 addShutterListener(this);
...
1385 }
1926 public void addShutterListener(ShutterButton.OnShutterButtonListener listener) {
1927 mShutterButton.addOnShutterButtonListener(listener);
1928 }
接着addShutterListener主要就是向ShutterButton注册响应函数,其中CameraAppUI中的onShutterButtonClick内部什么也没做,那么拍照的操作被哪里响应了呢?从上面代码中可以看到还另外添加了一个listener,mController.getCurrentModuleController(),之前有提过mController就是CameraActivity。所以getCurrentModuleController调用到了CameraActivity中:
/packages/apps/Camera2/src/com/android/camera/CameraActivity.java
1072 @Override
1073 public ModuleController getCurrentModuleController() {
1074 return mCurrentModule;
1075 }
这边返回的是mCurrentModule,之前也说过mCurrentModule 当前的情况下是CaptureModule,所以当拍照按钮被按下时会调用到CaptureModule的onShutterButtonClick:
/packages/apps/Camera2/src/com/android/camera/CaptureModule.java
486 @Override
487 public void onShutterButtonClick() {
488 if (mCamera == null) {
489 return;
490 }
491 //1.延迟拍照
492 int countDownDuration = mSettingsManager
493 .getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION);
494 if (countDownDuration > 0) {
495 // Start count down.
496 mAppController.getCameraAppUI().transitionToCancel();
497 mAppController.getCameraAppUI().hideModeOptions();
498 mUI.setCountdownFinishedListener(this);
499 mUI.startCountdown(countDownDuration);
500 // Will take picture later via listener callback.
501 } else {
//2.立即拍照
502 takePictureNow();
503 }
504 }
如果是延迟拍照的话当倒计时完成就会调用到CaptureModule的onCountDownFinished,最终也会调用到takePictureNow。
/packages/apps/Camera2/src/com/android/camera/CaptureModule.java
530 private void takePictureNow() {
531 if (mCamera == null) {
532 Log.i(TAG, "Not taking picture since Camera is closed.");
533 return;
534 }
535 //1.创建并启动CaptureSession
536 CaptureSession session = createAndStartCaptureSession();
537 int orientation = mAppController.getOrientationManager().getDeviceOrientation()
538 .getDegrees();
539 //2.组装PhotoCaptureParameters
540 // TODO: This should really not use getExternalCacheDir and instead use
541 // the SessionStorage API. Need to sync with gcam if that's OK.
542 PhotoCaptureParameters params = new PhotoCaptureParameters(
543 session.getTitle(), orientation, session.getLocation(),
544 mContext.getExternalCacheDir(), this, mPictureSaverCallback,
545 mHeadingSensor.getCurrentHeading(), mZoomValue, 0);
546 decorateSessionAtCaptureTime(session);
//调用Camera的takePicture进行拍照
547 mCamera.takePicture(params, session);
548 }
四 、录像流程
录像流程和拍照的流程其实比较相似,只不过是当录像时CameraActivity的mCurrentModule是VideoModule,当录像按钮按下时则会调用到VideoModule的onShutterButtonClick,如下:
/packages/apps/Camera2/src/com/android/camera/VideoModule.java
689 @Override
690 public void onShutterButtonClick() {
691 if (mSwitchingCamera) {
692 return;
693 }
//如果点击录像按钮时 当前的状态是正在录像,那么就走关闭录像的流程
694 boolean stop = mMediaRecorderRecording;
695
696 if (stop) {
697 // CameraAppUI mishandles mode option enable/disable
698 // for video, override that
699 mAppController.getCameraAppUI().enableModeOptions();
700 onStopVideoRecording();
701 } else {
702 // CameraAppUI mishandles mode option enable/disable
703 // for video, override that
//录像流程走这里
704 mAppController.getCameraAppUI().disableModeOptions();
705 startVideoRecording();
706 }
707 mAppController.setShutterEnabled(false);
708 if (mCameraSettings != null) {
709 mFocusManager.onShutterUp(mCameraSettings.getCurrentFocusMode());
710 }
711
712 // Keep the shutter button disabled when in video capture intent
713 // mode and recording is stopped. It'll be re-enabled when
714 // re-take button is clicked.
715 if (!(mIsVideoCaptureIntent && stop)) {
716 mHandler.sendEmptyMessageDelayed(MSG_ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
717 }
718 }
从上面的代码可以知道当点击录像按钮时,如果当前没有录像那么就调用startVideoRecording开启录像,如果当前正在录像那么就调用onStopVideoRecording停止录像。
4.1 startVideoRecording
/packages/apps/Camera2/src/com/android/camera/VideoModule.java
43 private void startVideoRecording() {
1344 Log.i(TAG, "startVideoRecording: " + Thread.currentThread());
1345 mUI.cancelAnimations();
1346 mUI.setSwipingEnabled(false);
1347 mUI.hidePassiveFocusIndicator();
1348 mAppController.getCameraAppUI().hideCaptureIndicator();
1349 mAppController.getCameraAppUI().setShouldSuppressCaptureIndicator(true);
1350 //1.更新存储信息
1351 mActivity.updateStorageSpaceAndHint(new CameraActivity.OnStorageUpdateDoneListener() {
1352 @Override
1353 public void onStorageUpdateDone(long bytes) {
1354 if (bytes <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
1355 Log.w(TAG, "Storage issue, ignore the start request");
1356 } else {
1357 if (mCameraDevice == null) {
1358 Log.v(TAG, "in storage callback after camera closed");
1359 return;
1360 }
1361 if (mPaused == true) {
1362 Log.v(TAG, "in storage callback after module paused");
1363 return;
1364 }
1365
1366 // Monkey is so fast so it could trigger startVideoRecording twice. To prevent
1367 // app crash (b/17313985), do nothing here for the second storage-checking
1368 // callback because recording is already started.
1369 if (mMediaRecorderRecording) {
1370 Log.v(TAG, "in storage callback after recording started");
1371 return;
1372 }
1373
1374 mCurrentVideoUri = null;
1375 //2.初始化MediaRecoder
1376 initializeRecorder();
1377 if (mMediaRecorder == null) {
1378 Log.e(TAG, "Fail to initialize media recorder");
1379 return;
1380 }
1381
1382 try {
//3.调用mMediaRecorder.start 开始录像
1383 mMediaRecorder.start(); // Recording is now started
1384 } catch (RuntimeException e) {
1385 Log.e(TAG, "Could not start media recorder. ", e);
1386 mAppController.getFatalErrorHandler().onGenericCameraAccessFailure();
1387 releaseMediaRecorder();
1388 // If start fails, frameworks will not lock the camera for us.
1389 mCameraDevice.lock();
1390 return;
1391 }
...
1422
1423 setFocusParameters();
1424 //更新录像的时间
1425 updateRecordingTime();
1426 mActivity.enableKeepScreenOn(true);
1427 }
1428 }
1429 });
1430 }
在startVideoRecording中会首先做一些UI的切换,接着查询存储空间的剩余量,如果剩余空间过小或者camera没打开,那么就无视录像的请求。否则就调用
initializeRecorder初始化MeidaRecoder,接着调用MediaRecorder.start开启录像。
4.2 initializeRecorder
1087 // Prepares media recorder.
1088 private void initializeRecorder() {
1089 Log.i(TAG, "initializeRecorder: " + Thread.currentThread());
1090 // If the mCameraDevice is null, then this activity is going to finish
1091 if (mCameraDevice == null) {
1092 Log.w(TAG, "null camera proxy, not recording");
1093 return;
1094 }
1095 Intent intent = mActivity.getIntent();
1096 Bundle myExtras = intent.getExtras();
1097
1098 long requestedSizeLimit = 0;
1099 closeVideoFileDescriptor();
1100 mCurrentVideoUriFromMediaSaved = false;
//如果是camera app外部的请求
1101 if (mIsVideoCaptureIntent && myExtras != null) {
1102 Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
1103 if (saveUri != null) {
1104 try {
1105 mVideoFileDescriptor =
1106 mContentResolver.openFileDescriptor(saveUri, "rw");
1107 mCurrentVideoUri = saveUri;
1108 } catch (java.io.FileNotFoundException ex) {
1109 // invalid uri
1110 Log.e(TAG, ex.toString());
1111 }
1112 }
1113 requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
1114 } else {
//camera app内部的请求
1115 generateVideoValues();
1116 Uri videoTable = MediaStore.Video.Media.getContentUri(
1117 MediaStore.VOLUME_EXTERNAL_PRIMARY);
1118 Uri videoUri = mContentResolver.insert(videoTable, mCurrentVideoValues);
1119
1120 try {
1121 mVideoFileDescriptor =
1122 mContentResolver.openFileDescriptor(videoUri, "rw");
1123 mCurrentVideoUri = videoUri;
1124 } catch (java.io.FileNotFoundException ex) {
1125 // invalid uri
1126 mContentResolver.delete(videoUri, null, null);
1127 Log.e(TAG, ex.toString());
1128 }
1129 }
1130 //1.初始化MediaRecorder
1131 mMediaRecorder = new MediaRecorder();
1132 // Unlock the camera object before passing it to media recorder.
1133 mCameraDevice.unlock();
1134 // We rely here on the fact that the unlock call above is synchronous
1135 // and blocks until it occurs in the handler thread. Thereby ensuring
1136 // that we are up to date with handler requests, and if this proxy had
1137 // ever been released by a prior command, it would be null.
1138 Camera camera = mCameraDevice.getCamera();
1139 // If the camera device is null, the camera proxy is stale and recording
1140 // should be ignored.
1141 if (camera == null) {
1142 Log.w(TAG, "null camera within proxy, not recording");
1143 return;
1144 }
1145 //2.设置MediaRecorder
1146 mMediaRecorder.setCamera(camera);
1147 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
1148 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
1149 mMediaRecorder.setProfile(mProfile);
1150 mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
1151 mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
1152
1153 setRecordLocation();
1154
1155 // Set output file using video Uri.
1156 if (mVideoFileDescriptor != null) {
1157 mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
1158 } else {
1159 releaseMediaRecorder();
1160 throw new RuntimeException("No valid video file descriptor");
1161 }
1162
1163 // Set maximum file size.
1164 long maxFileSize = mActivity.getStorageSpaceBytes() - Storage.LOW_STORAGE_THRESHOLD_BYTES;
1165 if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
1166 maxFileSize = requestedSizeLimit;
1167 }
1168
1169 try {
1170 mMediaRecorder.setMaxFileSize(maxFileSize);
1171 } catch (RuntimeException exception) {
1172 // We are going to ignore failure of setMaxFileSize here, as
1173 // a) The composer selected may simply not support it, or
1174 // b) The underlying media framework may not handle 64-bit range
1175 // on the size restriction.
1176 }
1177
1178 int sensorOrientation =
1179 mActivity.getCameraProvider().getCharacteristics(mCameraId).getSensorOrientation();
1180 int deviceOrientation =
1181 mAppController.getOrientationManager().getDeviceOrientation().getDegrees();
1182 int rotation = CameraUtil.getImageRotation(
1183 sensorOrientation, deviceOrientation, isCameraFrontFacing());
1184 mMediaRecorder.setOrientationHint(rotation);
1185
1186 try {
//调用MediaRecorder的prepare进入准备状态
1187 mMediaRecorder.prepare();
1188 } catch (IOException e) {
1189 Log.e(TAG, "prepare failed", e);
1190 releaseMediaRecorder();
1191 throw new RuntimeException(e);
1192 }
1193
1194 mMediaRecorder.setOnErrorListener(this);
1195 mMediaRecorder.setOnInfoListener(this);
1196 }
initializeRecorder主要做了四件事:
1.新建MediaRecorder对象
2.对MeidaRecoder做一些设置,比如设置camera,音视频的资源类型,视频的大小(一帧的宽高),输出文件的路径等等。
3.调用MeidaRecoder.prepare 进入准备状态
4.设置两个回调监听
4.3 onStopVideoRecording
/packages/apps/Camera2/src/com/android/camera/VideoModule.java
654 private void onStopVideoRecording() {
655 mAppController.getCameraAppUI().setSwipeEnabled(true);
656 boolean recordFail = stopVideoRecording();
657 if (mIsVideoCaptureIntent) {
658 if (mQuickCapture) {
659 doReturnToCaller(!recordFail);
660 } else if (!recordFail) {
661 showCaptureResult();
662 }
663 } else if (!recordFail){
664 // Start capture animation.
665 if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
666 // The capture animation is disabled on ICS because we use SurfaceView
667 // for preview during recording. When the recording is done, we switch
668 // back to use SurfaceTexture for preview and we need to stop then start
669 // the preview. This will cause the preview flicker since the preview
670 // will not be continuous for a short period of time.
671 mAppController.startFlashAnimation(false);
672 }
673 }
674 }
1458 private boolean stopVideoRecording() {
1459 // Do nothing if camera device is still capturing photo. Monkey test can trigger app crashes
1460 // (b/17313985) without this check. Crash could also be reproduced by continuously tapping
1461 // on shutter button and preview with two fingers.
1462 if (mSnapshotInProgress) {
1463 Log.v(TAG, "Skip stopVideoRecording since snapshot in progress");
1464 return true;
1465 }
1466 Log.v(TAG, "stopVideoRecording");
1467
1468 // Re-enable sound as early as possible to avoid interfering with stop
1469 // recording sound.
1470 restoreRingerMode();
1471
1472 mUI.setSwipingEnabled(true);
1473 mUI.showPassiveFocusIndicator();
1474 mAppController.getCameraAppUI().setShouldSuppressCaptureIndicator(false);
1475
1476 boolean fail = false;
1477 if (mMediaRecorderRecording) {
1478 boolean shouldAddToMediaStoreNow = false;
1479
1480 try {
//1.设置回调为null并调用MediaRecorder的stop函数来停止录像
1481 mMediaRecorder.setOnErrorListener(null);
1482 mMediaRecorder.setOnInfoListener(null);
1483 mMediaRecorder.stop();
1484 shouldAddToMediaStoreNow = true;
1485 } catch (RuntimeException e) {
1486 Log.e(TAG, "stop fail", e);
1487 fail = true;
1488 }
1489 mMediaRecorderRecording = false;
1490 mActivity.unlockOrientation();
1491
1492 // If the activity is paused, this means activity is interrupted
1493 // during recording. Release the camera as soon as possible because
1494 // face unlock or other applications may need to use the camera.
1495 if (mPaused) {
1496 // b/16300704: Monkey is fast so it could pause the module while recording.
1497 // stopPreview should definitely be called before switching off.
1498 stopPreview();
1499 closeCamera();
1500 }
1501
1502 mUI.showRecordingUI(false);
1503 // The orientation was fixed during video recording. Now make it
1504 // reflect the device orientation as video recording is stopped.
1505 mUI.setOrientationIndicator(0, true);
1506 mActivity.enableKeepScreenOn(false);
1507 if (shouldAddToMediaStoreNow && !fail) {
1508 if (mIsVideoCaptureIntent) {
1509 // if no file save is needed, we can show the post capture UI now
1510 showCaptureResult();
1511 } else {
//把生成的录像文件记录插入到MediaProvider的数据库中
1512 saveVideo();
1513 }
1514 }
1515 }
1516 // release media recorder
1517 releaseMediaRecorder();
1518
1519 mAppController.getCameraAppUI().showModeOptions();
1520 mAppController.getCameraAppUI().animateBottomBarToFullSize(mShutterIconId);
1521 if (!mPaused && mCameraDevice != null) {
1522 setFocusParameters();
1523 mCameraDevice.lock();
1524 if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
1525 stopPreview();
1526 // Switch back to use SurfaceTexture for preview.
1527 startPreview();
1528 }
1529 // Update the parameters here because the parameters might have been altered
1530 // by MediaRecorder.
1531 mCameraSettings = mCameraDevice.getSettings();
1532 }
1533
1534 // Check this in advance of each shot so we don't add to shutter
1535 // latency. It's true that someone else could write to the SD card
1536 // in the mean time and fill it, but that could have happened
1537 // between the shutter press and saving the file too.
1538 mActivity.updateStorageSpaceAndHint(null);
1539
1540 return fail;
1541 }
停止录像最终则是调用到了MediaRecoder的stop函数,接着保存记录到数据库中。
关于MeidaRecoder的具体代码可以参考Android Camera模块(三)
版权声明:本文标题:Android Camera模块(一) 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/xitong/1725889613a1047404.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论