webrtc快速入门——使用 WebRTC 拍摄静止的照片
文章目录
- 使用 getUserMedia() 拍摄静态照片
- HTML 标记
- JavaScript 代码
- 初始化
- startup() 函数
- 获取元素引用
- 获取流媒体
- 监听视频开始播放
- 处理按钮上的点击
- 包装 startup() 方法
- 清理照片框
- 从流中捕获帧
- 例子代码
- HTML代码
- CSS代码
- JavaScript代码
- 过滤器
- 使用特定设备
使用 getUserMedia() 拍摄静态照片
介绍了如何在 WebRTC 的支持下可以访问到电脑或者手机的摄像头并且使用它来拍摄照片。
本文介绍在支持 getUserMedia()
的计算机或手机上如何使用 navigator.mediaDevices.getUserMedia()
访问摄像机,并用其拍照。
HTML 标记
我们的 HTML 界面有两个主要的操作部分:流和捕获面板以及演示面板。它们俩都在它们自己的 <div>
中并排渲染,以便于添加样式和控制。
左边的面板包含两个组件:一个 <video>
元素,它将接收来自 navigator.mediaDevices.getUserMedia()
的流,以及用于用户点击以捕获视频帧的 <button>
。
<div class="camera"><video id="video">视频流目前不可用。</video><button id="startbutton">拍摄照片</button>
</div>
这很简单,当我们进入 JavaScript 代码时,我们将看到它们是如何紧密联系在一起的。
接下来,我们有一个 <canvas>
元素,捕获的帧被存储到其中,可能以某种方式进行操作,然后转换为输出图像文件。通过使用样式 display:none
将画布保持隐藏,以避免画面的混乱——用户不需要看到这个中间过程。
我们还有一个 <img>
元素,我们将在其中绘制图像——这是让用户看到的最终显示。
<canvas id="canvas"> </canvas>
<div class="output"><img id="photo" alt="捕获的图像会显示在这里。" />
</div>
这是所有相关的 HTML。其余的只是一些页面布局和提供一个返回页面链接的些许文本。
JavaScript 代码
现在来看看 JavaScript 代码。我们将把它分解成几个小的部分,使其更容易解释。
初始化
我们首先将整个脚本包装在匿名函数中,以避免使用全局变量,然后设置我们将要使用的各种变量。
(() => {const width = 320; const height = 0; const streaming = false;let video = null;let canvas = null;let photo = null;let startbutton = null;
这些变量分别是:
width
无论输入视频的尺寸如何,我们将把所得到的图像缩放到宽度为 320 像素。
height
给定流的 width
和宽高比,计算出图像的输出高度。
streaming
指示当前是否有活动的视频流正在运行。
video
这将是页面加载完成后对 <video>
元素的引用。
canvas
这将是页面加载完成后对 <canvas>
元素的引用。
photo
这将在页面加载完成后引用 <img>
元素。
startbutton
这将引用用于触发捕获的 <button>
元素。我们会在页面加载完成之后得到。
startup() 函数
当页面加载完成时,提供给 EventTarget.addEventListener
的 startup()
函数将会运行。此函数的作用是请求访问用户的网络摄像头,将用于输出的 <img>
初始化为默认状态,并建立从相机接收每帧视频所需的事件监听器,并在点击按钮捕获图像时作出反应。
获取元素引用
首先,我们参考我们需要访问的主要内容。
function startup() {video = document.getElementById('video');canvas = document.getElementById('canvas');photo = document.getElementById('photo');startbutton = document.getElementById('startbutton');
获取流媒体
接下来的任务是获取媒体流:
navigator.mediaDevices.getUserMedia({ video: true, audio: false }).then((stream) => {video.srcObject = stream;video.play();}).catch((err) => {console.error(`An error occurred: ${err}`);});
在这里,我们调用 MediaDevices.getUserMedia()
并请求视频流(无音频)。它返回一个 promise,我们给它附加成功和失败情况下的回调方法。
成功回调接收一个 stream 对象作为输入。它是新视频的 <video>
元素的源。
一旦流被链接到 <video>
元素,我们通过调用 HTMLMediaElement.play()
开始播放。
如果打开流失败,则调用失败回调函数。在没有连接兼容的相机,或者用户拒绝访问时,则会发生这种情况。
监听视频开始播放
在 <video>
上调用 HTMLMediaElement.play()
之后,在视频流开始流动之前,有一段(希望简短)的时间段过去了。为了避免在此之前一直阻塞,我们为 video
加上一个 canplay
事件的监听器,当视频播放实际开始时会触发该事件。那时,视频对象中的所有属性都已基于流的格式进行配置。
video.addEventListener("canplay",(ev) => {if (!streaming) {height = (video.videoHeight / video.videoWidth) * width;video.setAttribute("width", width);video.setAttribute("height", height);canvas.setAttribute("width", width);canvas.setAttribute("height", height);streaming = true;}},false,
);
这个回调什么都不做,除非它是第一次被调用;这是通过查看我们的 streaming
变量的值进行测试,这是第一次运行此方法时为 false
。
如果这是第一次运行,我们会根据视频的实际大小,video.videoWidth
和要渲染视频宽度的宽度(witdh
)之间的大小差异来设置视频的高度。
最后,通过在视频和画布上调用 Element.setAttribute()
来设置视频和画布的宽度(witdh
)和高度(height
),以使得两者相互匹配。最后,我们将 streaming
变量设置为 true
,以防止我们无意中再次运行此设置代码。
处理按钮上的点击
为了在每次用户点击 startbutton
时捕获静态照片,我们需要向按钮添加一个事件监听器,以便在发出 click
事件时被调用:
startbutton.addEventListener("click",(ev) => {takepicture();ev.preventDefault();},false,
);
这个方法很简单:它只是调用我们的 takepicture()
函数,在从流中捕获帧的部分中定义,然后在接收的事件上调用 Event.preventDefault()
,以防止点击被多次处理。
包装 startup() 方法
startup()
方法中只有两行代码:
这就是我们调用 clearphoto()
方法的地方,我们将在下面的清理照片框部分进行描述。
清理照片框
清理照片框包括创建一个图像,然后将其转换为可以显示最近捕获的帧的 <img>
元素使用的格式。该代码如下所示:
function clearphoto() {const context = canvas.getContext("2d");context.fillStyle = "#AAA";context.fillRect(0, 0, canvas.width, canvas.height);const data = canvas.toDataURL("image/png");photo.setAttribute("src", data);
}
我们首先得到对我们用于屏幕外渲染的隐藏的 <canvas>
元素的引用。接下来,我们将 fillStyle
设置为 #AAA
(相当浅的灰色),并通过调用 fillRect()
来填充整个画布。
最后在此功能中,我们将画布转换为 PNG 图像,并调用 photo.setAttribute()
以使我们捕获的静止框显示图像。
从流中捕获帧
最后一个定义的功能是整个练习的重点:takepicture()
函数,其捕获当前显示的视频帧的作业将其转换为 PNG 文件,并将其显示在捕获的帧框中。代码如下所示:
function takepicture() {const context = canvas.getContext("2d");if (width && height) {canvas.width = width;canvas.height = height;context.drawImage(video, 0, 0, width, height);const data = canvas.toDataURL("image/png");photo.setAttribute("src", data);} else {clearphoto();}
}
正如我们需要处理画布内容的情况一样,我们首先得到隐藏画布的 2D 绘图上下文
。
然后,如果宽度和高度都是非零(意味着至少有潜在有效的图像数据),我们将画布的宽度和高度设置为与捕获帧的宽度和高度相匹配,然后调用 drawImage()
将视频的当前帧绘制到上下文中,用帧图像填充整个画布。
一旦画布包含捕获的图像,我们通过调用它的 HTMLCanvasElement.toDataURL()
将它转换为 PNG 格式; 最后,我们调用 photo.setAttribute()
来使我们捕获的静态框显示图像。
如果没有可用的有效图像(即宽度和高度均为 0),则通过调用 clearphoto()
清除捕获帧框的内容。
例子代码
HTML代码
<div class="contentarea"><h1>MDN——navigator.mediaDevices.getUserMedia(): 静态照片拍摄演示</h1><p>此示例演示了如何使用内置的网络摄像头来获取媒体流,并从中获取图像,以使用该图像来创建一个PNG 图像。</p><div class="camera"><video id="video">视频流目前不可用。</video><button id="startbutton">拍摄照片</button></div><canvas id="canvas"> </canvas><div class="output"><img id="photo" alt="捕获的图像会显示在这里。" /></div><p>访问我们的文章:<ahref="https://developer.mozilla.org/zh-CN/docs/Web/API/WebRTC_API/Taking_still_photos">使用 getUserMedia() 拍摄静态照片</a>以详细了解此处使用的技术。</p>
</div>
CSS代码
#video {border: 1px solid black;box-shadow: 2px 2px 3px black;width: 320px;height: 240px;
}#photo {border: 1px solid black;box-shadow: 2px 2px 3px black;width: 320px;height: 240px;
}#canvas {display: none;
}.camera {width: 340px;display: inline-block;
}.output {width: 340px;display: inline-block;vertical-align: top;
}#startbutton {display: block;position: relative;margin-left: auto;margin-right: auto;bottom: 32px;background-color: rgba(0, 150, 0, 0.5);border: 1px solid rgba(255, 255, 255, 0.7);box-shadow: 0px 0px 1px 2px rgba(0, 0, 0, 0.2);font-size: 14px;font-family: "Lucida Grande", "Arial", sans-serif;color: rgba(255, 255, 255, 1);
}.contentarea {font-size: 16px;font-family: "Lucida Grande", "Arial", sans-serif;width: 760px;
}
JavaScript代码
(() => {// The width and height of the captured photo. We will set the// width to the value defined here, but the height will be// calculated based on the aspect ratio of the input stream.const width = 320; // We will scale the photo width to thislet height = 0; // This will be computed based on the input stream// |streaming| indicates whether or not we're currently streaming// video from the camera. Obviously, we start at false.let streaming = false;// The various HTML elements we need to configure or control. These// will be set by the startup() function.let video = null;let canvas = null;let photo = null;let startbutton = null;function showViewLiveResultButton() {if (window.self !== window.top) {// Ensure that if our document is in a frame, we get the user// to first open it in its own tab or window. Otherwise, it// won't be able to request permission for camera access.document.querySelector(".contentarea").remove();const button = document.createElement("button");button.textContent = "查看以上示例代码的实时演示";document.body.append(button);button.addEventListener("click", () => window.open(location.href));return true;}return false;}function startup() {if (showViewLiveResultButton()) {return;}video = document.getElementById("video");canvas = document.getElementById("canvas");photo = document.getElementById("photo");startbutton = document.getElementById("startbutton");navigator.mediaDevices.getUserMedia({ video: true, audio: false }).then((stream) => {video.srcObject = stream;video.play();}).catch((err) => {console.error(`An error occurred: ${err}`);});video.addEventListener("canplay",(ev) => {if (!streaming) {height = video.videoHeight / (video.videoWidth / width);// Firefox currently has a bug where the height can't be read from// the video, so we will make assumptions if this happens.if (isNaN(height)) {height = width / (4 / 3);}video.setAttribute("width", width);video.setAttribute("height", height);canvas.setAttribute("width", width);canvas.setAttribute("height", height);streaming = true;}},false,);startbutton.addEventListener("click",(ev) => {takepicture();ev.preventDefault();},false,);clearphoto();}// Fill the photo with an indication that none has been// captured.function clearphoto() {const context = canvas.getContext("2d");context.fillStyle = "#AAA";context.fillRect(0, 0, canvas.width, canvas.height);const data = canvas.toDataURL("image/png");photo.setAttribute("src", data);}// Capture a photo by fetching the current contents of the video// and drawing it into a canvas, then converting that to a PNG// format data URL. By drawing it on an offscreen canvas and then// drawing that to the screen, we can change its size and/or apply// other changes before drawing it.function takepicture() {const context = canvas.getContext("2d");if (width && height) {canvas.width = width;canvas.height = height;context.drawImage(video, 0, 0, width, height);const data = canvas.toDataURL("image/png");photo.setAttribute("src", data);} else {clearphoto();}}// Set up our event listener to run the startup process// once loading is complete.window.addEventListener("load", startup, false);
})();
过滤器
由于我们通过从 <video>
元素中抓取帧来捕获用户网络摄像头的图像,因此我们可以非常轻松地将过滤器和有趣的效果应用于视频。事实证明,使用 filter
属性应用于元素的任何 CSS 过滤器都会影响捕获的照片。这些过滤器可以从简单(使图像黑白)到复杂(高斯模糊和色调旋转)。
你可以使用例如 Firefox 开发者工具的样式编辑器来播放此效果;有关如何执行此操作的详细信息,请参阅编辑 CSS 过滤器。
使用特定设备
如果需要,你可以将允许的视频源限定为特定的设备或特定的一组设备。要做到这一点,请调用 MediaDevices.enumerateDevices
。若返回的 promise 兑现了一个 MediaDeviceInfo
对象(描述了可用的设备)数组,可以从中选取一个你想要允许的设备,并将对应的 deviceId
或 MediaTrackConstraints
对象的 deviceId
作为参数传入到 getUserMedia()
中。
相关文章:
webrtc快速入门——使用 WebRTC 拍摄静止的照片
文章目录 使用 getUserMedia() 拍摄静态照片HTML 标记JavaScript 代码初始化startup() 函数获取元素引用获取流媒体 监听视频开始播放处理按钮上的点击包装 startup() 方法 清理照片框从流中捕获帧 例子代码HTML代码CSS代码JavaScript代码 过滤器使用特定设备 使用 getUserMedi…...
预约按摩app软件开发定制足浴SPA上们服务小程序
同城按摩小程序是一种基于地理位置服务的小程序,它可以帮助用户快速找到附近的按摩师,并提供在线预约、评价、支付等功能。用户可以通过手机或者其他移动设备访问同城按摩小程序,实现足不出户就能预约到专业的按摩服务。 一、同城按摩小程序的…...
jenkins出错与恢复
如果你的jenkins出现了如下图所示问题(比如不能下载插件,无法保存任务等),这个时候就需要重新安装了。 一、卸载干净jenknis 要彻底卸载 Jenkins,您可以按照以下步骤进行操作: 1、停止 Jenkins 服务&…...
ssh免密登录的原理RSA非对称加密的理解
RSA非对称加密,是采用公钥加密私钥解密的原则。 举个例子SSH的免密登录 SSH免密登录是通过使用公钥加密技术实现的。以下是SSH免密登录的原理: 1. 生成密钥对:首先,在客户端上生成一对密钥,包括一个私钥和一个公钥。私…...
【监督学习】基于合取子句进化算法(CCEA)和析取范式进化算法(DNFEA)解决分类问题(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
力扣每日一题41:缺失的第一个正数
题目描述: 给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。 示例 1: 输入:nums [1,2,0] 输出:3示例 2: 输…...
OpenCV与mediapipe实践
1. 安装前准备 开发环境:vscode venv 设置vscode, 建立项目,如: t1/src, 用vscode打开,新建终端Terminal,这时可能会有错误产生,解决办法: 运行命令:Set-ExecutionPolicy -ExecutionPolicy …...
【css拾遗】粘性布局实现有滚动条的情况下,按钮固定在页面底部展示
效果: 滚动条滚动过程中,按钮的位置位于手机的底部 滚动条滚到底部时,按钮的位置正常 这个position:sticky真的好用,我原先的想法是利用滚动条滚动事件去控制,没想到css就可以解决 <template><view class…...
git 创建并配置 GitHub 连接密钥
前记: git svn sourcetree gitee github gitlab gitblit gitbucket gitolite gogs 版本控制 | 仓库管理 ---- 系列工程笔记. Platform:Windows 10 Git version:git version 2.32.0.windows.1 Function: git 创建并配置 GitHub…...
使用Premiere、PhotoShop和Audition做视频特效
今天接到一个做视频的任务,给一个精忠报国的视频,要求: ①去掉人声,就是将唱歌的人声去掉,只留下伴奏; ②截图视频中的横幅,做一个展开的效果,类似卷纸慢慢展开;…...
vueday01——动态参数
我们现在知道了 v-bind:的语法糖是: v-on:的语法糖是 我们现在来尝试一下,定义一个动态参数模拟点击事件按钮 <div :id"idValue" ref"myDiv">我是待测div{{ resultId }}</div> <button v-on:[eventName]"doSomething&…...
双向链表C语言版本
1、声明链表节点操作函数 linklist.h #ifndef LINKLIST_H__ #define LINKLIST_H__ #include <stdio.h> #include <stdlib.h> #include <stdbool.h>//#define TAIL_ADD #define HEAD_ADD typedef int LinkDataType; // 构造节点 struct LinkNode {LinkDataTy…...
visual studio安装时候修改共享组件、工具和SDK路径方法
安装了VsStudio后,如果自己修改了Shared路径,当卸载旧版本,需要安装新版本时发现,之前的Shared路径无法进行修改,这就很坑爹了,因为我运行flutter程序的时候,报错找不到windows sdk的位置,所以我…...
Motorola IPMC761 使用边缘TPU加速神经网络
Motorola IPMC761 使用边缘TPU加速神经网络 人工智能(AI)和机器学习(ML)正在塑造和推进复杂的自动化技术解决方案。将这些功能集成到硬件中,解决方案可以识别图像中的对象,分析和检测模式中的异常或找到关键短语。这些功能对于包括但不限于自动驾驶汽车…...
EM@直线的参数方程
文章目录 abstract直线参数方程从运动轨迹的角度从普通方程转换导参数方程向量法 参数方程间的转换从第3型转化为第2型方程组例 abstract 平面直线的参数方程的3种表示形式直线参数方程间的转换 直线参数方程 以下从不同角度推导直线参数方程分别记为第1,2,3形式参数方程 从…...
day08-注册功能、前端登录注册页面复制、前端登录功能、前端注册功能
1 注册功能 补充(开放文件夹内) 2 前端登录注册页面复制 4 前端注册功能 1 注册功能 # 分析前端:携带数据格式 {mobile:,code:,password}后端:-1 视图类---》注册方法-2 序列化类---》校验,保存(表中字段多,传的少---…...
rust: function
///file: nestd.rs ///ide: RustRover 233.8264.22 /// /// /// /***自定义函数*/ pub fn function() {println!("called my::nested::function()"); }#[allow(dead_code)] fn private_function() {println!("called my::nested::private_function()"); }/…...
零代码编程:用ChatGPT批量下载谷歌podcast上的播客音频
谷歌podcast有很多播客音频,如何批量下载到电脑呢? 以这个播客为例: https://podcasts.google.com/feed/aHR0cHM6Ly9oYWRhcnNoZW1lc2guY29tL2ZlZWQvcG9kY2FzdC8?saX&ved0CAkQlvsGahcKEwi4uauWsvKBAxUAAAAAHQAAAAAQAg 查看网页源代码&a…...
nginx.4——正向代理和反向代理(七层代理和四层代理)
1、正向代理反向代理 nginx当中有两种代理方式 七层代理(http协议) 四层代理(tcp/udp流量转发) 七层代理 七层代理:代理的是http的请求和响应。 客户端请求代理服务器,由代理服务器转发给客户端http请求。转发到内部服务器(可以单台&#…...
基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程(三)
更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio 演示地址:RuoYi-Nbcio后台管理系统 相应的后端也要做一些调整 1、启动流程修改如下: /*** 启动流程实例*/private R startProce…...
Spring-事务源码解析2
上一篇文章我们介绍了事务开启注解EnableTransactionManagement源码解析《Spring-事务源码解析1》 里面提到了2个关键组件,这里我们分析下Spring如何利用这2个组件来给Bean创建代理对象。 本篇文章我们看下当一个类里面包含了Transactional注解,Spring如…...
基于ssm008医院门诊挂号系统+jsp【附PPT|开题|任务书|万字文档(LW)和搭建文档】
主要功能 后台登录:4个角色 管理员: ①个人中心、修改密码、个人信息 ②药房管理、护士管理、医生管理、病人信息管理、科室信息管理、挂号管理、诊断信息管理、病例库管理、开药信息管理、药品信息管理、收费信息管理 药房: ①个人中心、修…...
【Linux常用命令11】Linux文件与权限详解
权限 r :读权限,用数字4表示 w :写权限,用数字2表示 x :执行权限,用数字1表示 常用权限 644:代表所有者拥有读、写权限,而所属组和其他人拥有只读权限。 755:代表所有…...
BAT026:删除当前目录指定文件夹以外的文件夹
引言:编写批处理程序,实现删除当前目录指定文件夹以外的文件夹。 一、新建Windows批处理文件 参考博客: CSDNhttps://mp.csdn.net/mp_blog/creation/editor/132137544 二、写入批处理代码 1.右键新建的批处理文件,点击【编辑】…...
Python浏览器自动化
如果你正在进行手机爬虫的工作,并且希望通过模拟浏览器行为来抓取数据,那么Pyppeteer将会是你的理想选择。Pyppeteer是一个强大的Python库,它可以让你控制浏览器进行自动化操作,如点击按钮、填写表单等,从而实现数据的…...
基于tornado BELLE 搭建本地的web 服务
我的github 将BELLE 封装成web 后端服务,采用tornado 框架 import timeimport torch import torch.nn as nnfrom gptq import * from modelutils import * from quant import *from transformers import AutoTokenizer import sys import json #import lightgbm a…...
信息系统漏洞与风险管理制度
1、总则 1.1、目的 为了进一步规范XXXXX单位信息系统风险管理活动,提升风险管理工作的可操纵性和适用性,使信息网络正常运行,防止网络攻击,保证业务的正常进行,依据XXXXX单位员的相关规范和标准规定,特制…...
Hadoop3教程(十七):MapReduce之ReduceJoin案例分析
文章目录 (113)ReduceJoin案例需求分析(114)ReduceJoin案例代码实操 - TableBean(115)ReduceJoin案例代码实操 - TableMapper(116)ReduceJoin案例代码实操 - Reducer及Driver参考文献…...
BAT026:删除当前目录及子目录下的空文件夹
引言:编写批处理程序,实现批量删除当前目录及子目录下的空文件夹。 一、新建Windows批处理文件 参考博客: CSDNhttps://mp.csdn.net/mp_blog/creation/editor/132137544 二、写入批处理代码 1.右键新建的批处理文件,点击【编辑…...
nodejs+vue网课学习平台
目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性:…...
四川省二级建造师报名入口官网/搜外网 seo教程
描述 Description 在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整…...
做网站做丝袜美女的能行吗/今天的新闻
与客户“调情” 作者 Jenni (Dow) Jepsen 译者 侯伯薇 在世界上,到处都有教人们如何调情的课程。某个德国的大学甚至要求他们的IT工程师参加调情的课程——并不是要吸引伙伴,而是要学习如何在工作中更有效地交流。乍听起来似乎有些“轻浮”,但…...
重庆做网站的程序员待遇/苏州关键词搜索排名
拿一个小点的项目来说,不用管什么开发框架,页面中掺和数据库访问代码加逻辑实现代码,直接搞定即可 大一点的项目一般采用三层开发框架,前台表示层,基本逻辑层,数据访问层,数据库的设计... …...
可以做推广的门户网站/线上销售渠道有哪几种
根据此视频入门即可 https://www.bilibili.com/video/av54488377?fromsearch&seid15888036743951343421 把Auto.js的“后台弹出界面” 的权限打开,不进入Auto.js也可以运行调试脚本。...
怎么介绍自己做的电影网站/seo网站推广
360手机N4S配置怎么样?360手机N4S值得购买吗?360手机N4S有几个版本?各版本有什么区别?下面脚本之家的小编就带来了360手机N4S各版本区别对比评测,一起来看看吧。外观设计360手机N4S是360手机N4的升级版,但是…...
大学城网站开发公司/廊坊seo
学习于: https://www.bilibili.com/video/av29268873/?p23 《鸟哥的linux私房菜》先让我们来看看进程的虚拟地址中的0~3g用户空间的存储:正文即代码段,初始化的数据段,未初始化的数据即bss段。bss段和初始化的数据段加在一起就是…...