Android Camera2在textureView中的预览和拍照

Camera2预览和拍照
  • 1、Camera2相机模型
  • 2、Camera2的重要类
  • 3、Camera2调用流程
  • 4、Camera2调用实现
    • 1)定义TextureView作为预览界面
    • 2)设置相机参数
    • 3)开启相机
    • 4)开启相机预览
    • 5)实现PreviewCallback
    • 6)拍照

1、Camera2相机模型

解释上诉示意图,假如想要同时拍摄两张不同尺寸的图片,并且在拍摄过程中闪光灯必须亮起来。整个拍摄流程如下:

  1. 创建一个用于从 Pipeline 获取图片的 CaptureRequest。
  2. 修改 CaptureRequest 的闪光灯配置,让闪光灯在拍照过程中亮起来。
  3. 创建两个不同尺寸的 Surface 用于接收图片数据,并且将它们添加到 CaptureRequest 中。
  4. 发送配置好的 CaptureRequest 到 Pipeline 中等待它返回拍照结果。

一个新的 CaptureRequest 会被放入一个被称作 Pending Request Queue 的队列中等待被执行,当 In-Flight Capture Queue 队列空闲的时候就会从 Pending Request Queue 获取若干个待处理的 CaptureRequest,并且根据每一个 CaptureRequest 的配置进行 Capture 操作。最后我们从不同尺寸的 Surface 中获取图片数据并且还会得到一个包含了很多与本次拍照相关的信息的 CaptureResult,流程结束。

2、Camera2的重要类

CameraManager: 负责查询和建立相机连接的系统服务。
1)将相机信息封装到 CameraCharacteristics 中,并提获取 CameraCharacteristics 实例的方式。
2)根据指定的相机 ID 连接相机设备。
3)提供将闪光灯设置成手电筒模式的快捷方式。
CameraCharacteristics
CameraCharacteristics 是一个只读的相机信息提供者,其内部携带大量的相机信息,包括代表相机朝向的 LENS_FACING;判断闪光灯是否可用的 FLASH_INFO_AVAILABLE;获取所有可用 AE 模式的 CONTROL_AE_AVAILABLE_MODES 等等。
CameraDevice
CameraDevice 代表当前连接的相机设备,职责有四个:
1)根据指定的参数创建 CameraCaptureSession。
2)根据指定的模板创建 CaptureRequest。
3)关闭相机设备。
4)监听相机设备的状态,例如断开连接、开启成功和开启失败等。
CameraCaptureSession
CameraCaptureSession 实际上就是配置了目标 Surface 的 Pipeline 实例,我们在使用相机功能之前必须先创建 CameraCaptureSession 实例。一个 CameraDevice 一次只能开启一个 CameraCaptureSession,绝大部分的相机操作都是通过向 CameraCaptureSession 提交一个 Capture 请求实现的,例如拍照、连拍、设置闪光灯模式、触摸对焦、显示预览画面等等。
CaptureRequest
CaptureRequest 是向 CameraCaptureSession 提交 Capture 请求时的信息载体,其内部包括了本次 Capture 的参数配置和接收图像数据的 Surface。CaptureRequest 可以配置的信息非常多,包括图像格式、图像分辨率、传感器控制、闪光灯控制、3A 控制等等,可以说绝大部分的相机参数都是通过 CaptureRequest 配置的。值得注意的是每一个 CaptureRequest 表示一帧画面的操作,这意味着你可以精确控制每一帧的 Capture 操作。
CaptureResult
CaptureResult 是每一次 Capture 操作的结果,里面包括了很多状态信息,包括闪光灯状态、对焦状态、时间戳等等。例如你可以在拍照完成的时候,通过 CaptureResult 获取本次拍照时的对焦状态和时间戳。需要注意的是,CaptureResult 并不包含任何图像数据,前面我们在介绍 Surface 的时候说了,图像数据都是从 Surface 获取的。
Surface
Surface 是一块用于填充图像数据的内存空间,例如你可以使用 SurfaceView 的 Surface 接收每一帧预览数据用于显示预览画面,也可以使用 ImageReader 的 Surface 接收 JPEG 或 YUV 数据。每一个 Surface 都可以有自己的尺寸和数据格式,你可以从 CameraCharacteristics 获取某一个数据格式支持的尺寸列表。

3、Camera2调用流程

API使用的大致流程:
1、通过context.getSystemService(Context.CAMERA_SERVICE) 获取CameraManager。
2、调用CameraManager .open()方法在回调中得到CameraDevice。
3、通过CameraDevice.createCaptureSession() 在回调中获取CameraCaptureSession。
4、构建CaptureRequest, 有三种模式可选 预览/拍照/录像。
5、通过 CameraCaptureSession发送CaptureRequest, capture表示只发一次请求, setRepeatingRequest表示不断发送请求。
6、拍照数据可以在ImageReader.OnImageAvailableListener回调中获取, CaptureCallback中则可获取拍照实际的参数和Camera当前状态。

从相机设备获取一个或者多个image,首先必须创建一个CameraCaptureSession并输出到一个或多个目标Surface上。每个Surface必须预先设置合适的预览尺寸,这个尺寸必须是Camera支持的尺寸。目标Surface可以被一系列的类所持有,比如SurfaceView,SurfacTexture,MediaCodec,MediaRecorder,Allocation和ImageReader。因此,Camera的输出可以分发到多个Surface上面。
通常,相机预览图像可以被发送到SurfaceView或者TextureView上面,像拍照的时候去单独获取某一帧或者特效相机类的App获得要处理的RAW数据流可以通过ImageReader来获取JPEG格式或者YUV格式的图像数据。比如要用RenderScript, OpenGL ES或者直接在native处理的数据就推荐使用YUV_420_888数据格式来承载。

4、Camera2调用实现

1)定义TextureView作为预览界面

在布局文件加入TextureView控件,然后实现监听事件。

textureView = (TextureView) findViewById(R.id.textureView);

然后在OnResume()方法设置监听SurfaceTexture的事件。

textureView.setSurfaceTextureListener(textureListener);

当SurfaceTexture准备好会回调SurfaceTextureListener的onSurfaceTextureAvailable()方法。

TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {//当SurefaceTexture可用的时候,设置相机参数并打开相机setupCamera(width, height);openCamera();}

2)设置相机参数

为了更好地预览,我们根据TextureView的尺寸设置预览尺寸,Camera2中使用CameraManager来管理摄像头。

private void setupCamera(int width, int height) {//获取摄像头的管理者CameraManagerCameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);try {//遍历所有摄像头for (String cameraId: manager.getCameraIdList()) {CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);//默认打开后置摄像头if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT)continue;//获取StreamConfigurationMap,它是管理摄像头支持的所有输出格式和尺寸StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);//根据TextureView的尺寸设置预览尺寸mPreviewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height);mCameraId = cameraId;break;}} catch (CameraAccessException e) {e.printStackTrace();}
}

3)开启相机

Camera2中打开相机也需要通过CameraManager类。

private void openCamera() {//获取摄像头的管理者CameraManagerCameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);//检查权限try {if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {return;}//打开相机,第一个参数指示打开哪个摄像头,第二个参数stateCallback为相机的状态回调接口,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行manager.openCamera(mCameraId, stateCallback, null);} catch (CameraAccessException e) {e.printStackTrace();}
}

实现StateCallback 接口,当相机打开后会回调onOpened方法,在这个方法里面开启预览

private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(CameraDevice camera) {mCameraDevice = camera;//开启预览startPreview();}
}

4)开启相机预览

使用TextureView显示相机预览数据,Camera2的预览和拍照数据都是使用CameraCaptureSession会话来请求的。

private void startPreview() {SurfaceTexture mSurfaceTexture = mTextureView.getSurfaceTexture();//设置TextureView的缓冲区大小mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());//获取Surface显示预览数据Surface mSurface = new Surface(mSurfaceTexture);try {//创建CaptureRequestBuilder,TEMPLATE_PREVIEW比表示预览请求mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);//设置Surface作为预览数据的显示界面mCaptureRequestBuilder.addTarget(mSurface);//创建相机捕获会话,第一个参数是捕获数据的输出Surface列表,第二个参数是CameraCaptureSession的状态回调接口,当它创建好后会回调onConfigured方法,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行mCameraDevice.createCaptureSession(Arrays.asList(mSurface), new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(CameraCaptureSession session) {try {//创建捕获请求mCaptureRequest = mCaptureRequestBuilder.build();mPreviewSession = session;//设置反复捕获数据的请求,这样预览界面就会一直有数据显示mPreviewSession.setRepeatingRequest(mCaptureRequest, mSessionCaptureCallback, null);} catch (CameraAccessException e) {e.printStackTrace();}}@Overridepublic void onConfigureFailed(CameraCaptureSession session) {}}, null);} catch (CameraAccessException e) {e.printStackTrace();}
}

5)实现PreviewCallback

获取预览帧数据,使用ImageReader间接实现,首先创建一个ImageReader,并监听它的事件。

private void setupImageReader() {//前三个参数分别是需要的尺寸和格式,最后一个参数代表每次最多获取几帧数据,本例的2代表ImageReader中最多可以获取两帧图像流mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),ImageFormat.JPEG, 2);//监听ImageReader的事件,当有图像流数据可用时会回调onImageAvailable方法,它的参数就是预览帧数据,可以对这帧数据进行处理mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {Image image = reader.acquireLatestImage();//我们可以将这帧数据转成字节数组,类似于Camera1的PreviewCallback回调的预览帧数据ByteBuffer buffer = image.getPlanes()[0].getBuffer();byte[] data = new byte[buffer.remaining()];buffer.get(data);image.close();}}, null);
}

注意:一定要调用reader.acquireLatestImage()和close()方法,否则画面就会卡住

在开启预览之前,设置ImageReader为输出Surface。

setupImageReader();
//获取ImageReader的Surface
Surface imageReaderSurface = mImageReader.getSurface();
//CaptureRequest添加imageReaderSurface,不加的话就会导致ImageReader的onImageAvailable()方法不会回调
mCaptureRequestBuilder.addTarget(imageReaderSurface);
//创建CaptureSession时加上imageReaderSurface,如下,这样预览数据就会同时输出到previewSurface和imageReaderSurface了
mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, imageReaderSurface), new CameraCaptureSession.StateCallback() {
}

关闭相机时别忘了关闭ImageReader。

6)拍照

Camera2拍照也是通过ImageReader来实现的。
首先先做些准备工作,设置拍照参数,如方向、尺寸等

private static final SparseIntArray ORIENTATION = new SparseIntArray();static {//将不同的Surface旋转常量与相应的角度值进行映射ORIENTATION.append(Surface.ROTATION_0, 90); //无旋转,设备屏幕处于纵向(竖直)方向。ORIENTATION.append(Surface.ROTATION_90, 0); //顺时针旋转90度,设备屏幕处于横向(水平)方向,宽度大于高度。ORIENTATION.append(Surface.ROTATION_180, 270); //顺时针旋转180度,设备屏幕处于纵向(竖直)方向。ORIENTATION.append(Surface.ROTATION_270, 180); //顺时针旋转270度,设备屏幕处于横向(水平)方向,宽度大于高度。}

 设置拍照尺寸,可以跟预览尺寸一起设置,然后ImageReader初始化使用此尺寸

mCaptureSize = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new Comparator<Size>() {@Overridepublic int compare(Size lhs, Size rhs) {return Long.signum(lhs.getWidth() * lhs.getHeight() - rhs.getHeight() * rhs.getWidth());}
});

创建保存图片的线程

public static class imageSaver implements Runnable {private Image mImage;public imageSaver(Image image) {mImage = image;}@Overridepublic void run() {ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();byte[] data = new byte[buffer.remaining()];buffer.get(data);mImageFile = new File(Environment.getExternalStorageDirectory() + "/DCIM/myPicture.jpg");FileOutputStream fos = null;try {fos = new FileOutputStream(mImageFile);fos.write(data, 0 ,data.length);} catch (IOException e) {e.printStackTrace();} finally {mImageFile = null;if (fos != null) {try {fos.close();fos = null;} catch (IOException e) {e.printStackTrace();}}}}}

然后当ImageReader有数据时,通过此线程保存图片

//使用前面获取的拍照尺寸
mImageReader = ImageReader.newInstance(mCaptureSize.getWidth(), mCaptureSize.getHeight(),ImageFormat.JPEG, 2);
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {//执行图像保存子线程mCameraHandler.post(new imageSaver(reader.acquireNextImage()));}}, mCameraHandler);

然后开启预览创建CaptureSession时把ImageReader添加进去

mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() { 
}

还需要响应点击拍照事件,我们设置点击拍照按钮调用capture()方法,capture()方法即实现拍照

private void capture() {try {//首先我们创建请求拍照的CaptureRequestfinal CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);//获取屏幕方向int rotation = getWindowManager().getDefaultDisplay().getRotation();//设置CaptureRequest输出到mImageReadermCaptureBuilder.addTarget(mImageReader.getSurface());//设置拍照方向,ORIENTATION.get(rotation)根据设备屏幕的旋转状态获取相应的角度值。mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));//这个回调接口用于拍照结束时重启预览,因为拍照会导致预览停止CameraCaptureSession.CaptureCallback mImageSavedCallback = new CameraCaptureSession.CaptureCallback() {@Overridepublic void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {Toast.makeText(getApplicationContext(), "Image Saved!", Toast.LENGTH_SHORT).show();//重启预览restartPreview();}};//停止预览mCameraCaptureSession.stopRepeating();//开始拍照,然后回调上面的接口重启预览,因为mCaptureBuilder设置ImageReader作为target,所以会自动回调ImageReader的onImageAvailable()方法保存图片mCameraCaptureSession.capture(mCaptureBuilder.build(), mImageSavedCallback, null);} catch (CameraAccessException e) {e.printStackTrace();}}

重启预览的方法很简单了

private void restartPreview() {try {//执行setRepeatingRequest方法就行了,注意mCaptureRequest是之前开启预览设置的请求mCameraCaptureSession.setRepeatingRequest(mCaptureRequest, null, mCameraHandler);} catch (CameraAccessException e) {e.printStackTrace();}}


原文链接:https://blog.csdn.net/Jwfaikeyan/article/details/131740288

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/450734.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

图像及视频的基本操作

文章目录 一、认识计算机中的图像二、图像数据的读取三、数据读取-视频四、图像的其他操作 一、认识计算机中的图像 一张彩色图片是由很多个像素点组合而成的&#xff0c;而一个像素点是由R G B三个通道组成。RGB代表红色&#xff08;Red&#xff09;、绿色&#xff08;Green&a…

【远程监控新体验】OpenObserve结合内网穿透无公网IP远程访问全攻略

文章目录 前言1. 安装Docker2. Docker镜像源添加方法3. 创建并启动OpenObserve容器4. 本地访问测试5. 公网访问本地部署的OpenObserve5.1 内网穿透工具安装5.2 创建公网地址6. 配置固定公网地址前言 本文主要介绍如何在Linux系统使用Docker快速本地化部署OpenObserve云原生可观…

MacOS RocketMQ安装

MacOS RocketMQ安装 文章目录 MacOS RocketMQ安装一、下载二、安装修改JVM参数启动关闭测试关闭测试测试收发消息运行自带的生产者测试类运行自带的消费者测试类参考博客&#xff1a;https://blog.csdn.net/zhiyikeji/article/details/140911649 一、下载 打开官网&#xff0c;…

并发-线程

1, 线程 线程(thread)也是并发的一种形式&#xff0c;线程是比进程更小的活动单位&#xff0c;一个进程中可以有多个线程&#xff0c;线程是进程内部的一个执行分支。 一个进程刚开始时只有一个线程(称之为主线程)&#xff0c;后续的代码中可以创建新的线程&#xff0c;可以指…

git提交到github个人记录

windows下git下载 1.进入git官网https://git-scm.com/downloads/win 一直默认选项即可 2.在settings中SSH and GPG keys中Add SSH key 3.选择git cmd git使用 1.配置用户名&#xff0c;和邮箱 git config --global user.email "youexample.com" git config --g…

aws(学习笔记第六课) AWS的虚拟私有,共有子网以及ACL,定义公网碉堡主机子网以及varnish反向代理

aws(学习笔记第六课) AWS的虚拟私有&#xff0c;共有子网以及ACL&#xff0c;定义公网碉堡主机子网以及varnish反向代理 学习内容&#xff1a; AWS的虚拟私有&#xff0c;共有子网以及ACL定义公网碉堡主机子网&#xff0c;私有子网和共有子网以及varnish反向代理 1. AWS的虚拟…

深入理解WPF中的命令机制

Windows Presentation Foundation&#xff08;WPF&#xff09;是微软推出的一种用于构建桌面客户端应用程序的技术。它被认为是现代Windows应用程序的基础&#xff0c;具有强大的图形和媒体处理能力。在WPF中&#xff0c;“命令”是一个重要的概念&#xff0c;它为应用程序开发…

如何在算家云搭建Video-Infinity(视频生成)

一、模型介绍 Video-Infinity是一个先进的视频生成模型&#xff0c;使用多个 GPU 快速生成长视频&#xff0c;无需额外训练。它能够基于用户提供的文本或图片提示&#xff0c;创造出高质量、多样化的视频内容。 二、模型搭建流程 1.大模型 Video-Infinity 一键使用 基础环境…

Nest.js 实战 (十四):如何获取客户端真实 IP

问题解析 在 Nest.js 应用中&#xff0c;当你试图通过 request.ip 获取客户端的 IP 地址时&#xff0c;如果总是返回 ::1 或者 ::ffff:127.0.0.1&#xff0c;这通常意味着请求来自本地主机。 因为在前后端分离应用中&#xff0c;前端请求后端服务一般的做法都是通过代理&…

SQL进阶技巧:如何删除第N次连续出现NULL值所存在的行?

目录 0 场景描述 1 数据准备 2 问题分析 问题拓展:如何删除第2次、第3次、第N次连续出现NULL值所在的行? 3 小结 0 场景描述 有下面的场景: 我们希望删除某id中连续存在NULL值的所有行,但是保留第一次出现不为NULL值的以下所有存在NULL值的行。具体如下图所示: 如…

Leetcode 判断子序列

通过双指针来判断字符串s是否是字符串t的子序列。 算法思想&#xff1a; 双指针法&#xff1a; 我们使用两个指针i和j分别遍历字符串s和t。初始时&#xff0c;i指向s的第一个字符&#xff0c;j指向t的第一个字符。 匹配字符&#xff1a; 每次比较s[i]和t[j]&#xff1a; 如果…

大数据治理-数据质量管理

目录 一、定义数据质量 1.1 数据质量的定义 1.2 数据质量的重要性 二、常见的数据质量问题 2.1 数据不准确 2.2 数据不完整 2.3 数据不一致 2.4 数据不及时 2.5 数据无效 2.6 数据重复 三、数据清洗与转换 3.1 数据清洗 3.1.1 数据审计 3.1.2 数据验证 3.1.3 数…

uniapp小程序自定义聚合点

注&#xff1a; 1.默认的聚合点可以点击自动展示子级点位&#xff0c;但是自定义的聚合点在ios上无法触发markerClusterClick的监听&#xff0c;至今未解决&#xff0c;不知啥原因 2.ios和安卓展示的点位样式还有有差别 源码附上 <template><view class"marke…

Linux - 环境变量 | 命令行参数 | 进程基础

文章目录 一、了解冯诺依曼体系结构1、概念2、对数据层面3、实例二、操作系统1、概念2、设计OS的目的3、定位4、操作系统怎么管理&#xff1f; 三、进程1、概念2、怎么管理进程3、描述进程-PCB4、描述进程怎么运行&#xff08;粗略&#xff09;5、进程属性6、创建子进程7、创建…

PDF文件为什么不能编辑是?是啥原因导致的,有何解决方法

PDF文件格式广泛应用于工作中&#xff0c;但有时候我们可能遇到无法编辑PDF文件的情况。这可能导致工作效率降低&#xff0c;特别是在需要修改文件内容时显得尤为棘手。遇到PDF不能编辑时&#xff0c;可以看看是否以下3个原因导致的。 一、文件受保护 有些PDF文件可能被设置了…

ChatGPT 现已登陆 Windows 平台

今天&#xff0c;OpenAI 宣布其人工智能聊天机器人平台 ChatGPT 已开始预览专用 Windows 应用程序。OpenAI 表示&#xff0c;该应用目前仅适用于 ChatGPT Plus、Team、Enterprise 和 Edu 用户&#xff0c;是一个早期版本&#xff0c;将在今年晚些时候推出"完整体验"。…

[每周一更]-(第119期):“BP”大揭秘:生物学与金融学中的微小单位竟有如此大不同!

最近&#xff08;2024.09.29&#xff09;央行要把存量房贷在LPR&#xff08;贷款市场报价利率&#xff09;基础上&#xff0c;降低30BP&#xff0c;刚好基因行业内&#xff0c;也有bp的概念&#xff0c;通过发音无法区分&#xff0c;以下就讲解下生物学的bp和金融学的BP的概念的…

汽车零部件行业CRM应用数字化解决方案解析

1.行业背景与挑战分析 近年来&#xff0c;随着国家对新能源汽车行业的大力支持&#xff0c;国内汽车产业不仅在国内市场实现了弯道超车&#xff0c;而且新能源汽车的海外出口也开拓了新的市场&#xff0c;为自主品牌的新能源战略贡献了新的增长点&#xff1b;这一迅猛发展的趋…

最新版快递小程序源码 独立版快递系统 附教程

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 懂得都懂&#xff0c;现在电商平台退换货量大&#xff0c;快递需求量大&#xff0c;对接物流一个单子4块到6块之间 其中间是例如润 其余的 就不说了吧 互站上买的源码 分享一下 还有…

如何查看默认网关地址:详细步骤

在日常的网络配置与故障排查中&#xff0c;了解并正确查看默认网关地址是一项基础且至关重要的技能。默认网关是连接本地网络与外部网络&#xff08;如互联网&#xff09;的关键节点&#xff0c;它扮演着数据包转发的重要角色。无论是家庭网络、办公室网络还是更复杂的网络环境…