android sdk build-tools_从零开始仿写一个抖音App——视频编辑SDK开发(一)

本文首发于微信公众号——世界上有意思的事,搬运转载请注明出处,否则将追究版权责任。交流qq群:859640274。

大家好久不见,又有一个多月没有发文章了。不知道还有哪些读者记得我的 从零开始仿写抖音App 的系列文章,这个系列的文章已经很久没有更新了,最后一篇文章是我开始开发 视频编辑SDK 时写的。当时踏入到了一个新的领域里,未知的东西太多了,导致接下来的大半年都没有更新相关的文章。 但是别以为我已经放弃了,今天对于我来说是一个值得纪念的日子,2019年10月28日 我终于将 视频编辑SDK 的最简版本给完成了,我将这个 视频编辑SDK 命名为 WSVideoEditor,接下来的一段时间里我计划更新 4 篇解析该 SDK 的相关文章,WsVideoEditor:https://github.com/TheGodsThemselves/WsVideoEditor 中的代码我也会随着文章同步更新。当 SDK 解析完毕之后 从零开始仿写一个抖音App 系列文章将会踏出最关键的一步。公众号后台发送:视频编辑SDK开发(一),可以获得 PDF 文件。

本文分为以下章节,读者可按需阅读:

  • 1.项目介绍

  • 2.SDK功能介绍

  • 3.SDK架构以及运行机制介绍

  • 4.VideoDecodeService解析

一、项目介绍

本章我将介绍 WsVideoEditor:https://github.com/TheGodsThemselves/WsVideoEditor 项目的基本结构、组织方式以及运行方式。需要大家把项目 clone 下来跟着我一步步来做。

1.基本结构

68f94654e38c8db095ba54f7823d1510.png

我们看着图1,一个个来讲:

  • 1.android:顾名思义,这个目录下是一个 Android 项目,去掉 .gradle、build、.idea 等等 ignore 的文件,我们主要关注下面这几个文件夹。

  6d0016199ebd4143c5ab3ba69fe90c98.png  

    • 1.ffmpeg-cpp:如图2,这个文件夹中有 FFMPEG 的头文件与 .so 文件,我们需要将这个库集成到我们的 SDK 中,我们的 编辑SDK 需要有解码视频的能力,解码分为硬解和软解,ffmpeg 就是用于软解的最强开源库。至于如何得到这些东西,我之前写过一篇 FFMPE食用指南:https://juejin.im/post/5c0d3c366fb9a049ea38c6b4 有兴趣的读者可以看看。

    • 2.protobuf-cpp:这个文件夹与 ffmpeg-cpp 类似,里面有 Protobuf For Cpp 的头文件与 .a 文件,因为我们 Native 与 Android/iOS/Linux 的通信方式使用的是 Protobuf,所以我们也需要将 Cpp 层的 Protobuf 集成到我们的 SDK 中。

  5ca9372834bfa8542e9d20fc94c45ace.png

    • 3.wsvideoeditor-sdk:如图3,这个文件夹是一个 Android Library 项目,我们的 编辑SDK 在 Android 端会以一个独立的 jar 包形式存在。这个目录下的东西比较多,例如 src 目录下是 Java 层的一些封装代码。jni 目录下是一些使用了 Android Native Api 的 Cpp 代码。更详细的解析,会在后面几章。

    • 4.wsvideoeditor-test:这个文件夹则是一个 Android Application 项目,主要是用于编写一些测试 编辑 SDK 的代码。

a40294235923f39a02c747089c1e4ec4.png

  • 2.buildtools:如图4,这个目录下主要存放一些工具脚本,例如目前 build_proto.sh 用于生成 Java 与 Cpp 层的 Protobuf 代码。

  • 3.ios、linux:因为我给 编辑SDK 的定义是一个跨平台的视频编辑SDK,所以未来的想法是 iOS 和 Linux 端也能接入我们的 编辑SDK,目前这两个目录里还啥也没有:-D。 

c1d9b1718cbafcc5cb09976436c03274.png

  • 4.sharedcpp:如图5,这个目录里面主要存放与平台无关的 Cpp 代码,因为我们要做的是一个跨平台的视频编辑 SDK,所以尽量多的将与平台无关的代码进行共用,是一个明智的选择。可以看见里面的 prebuilt_protobuf 目录下就有我们使用 build_proto.sh 生成的 Cpp 文件,这些文件就是可以共用的。 

  • 5.sharedproto:这里存放着我们定义的 Protobuf 文件。

  • 6.thirdparty:这里存放着一些包含源码的与平台无关的三方库,例如 libyuv。

  • 7.CMakeLists.txt:这个文件主要是为了让 Clion 能够识别我们这个整个项目。

2.如何运行项目

  • 1.git clone https://github.com/TheGodsThemselves/WsVideoEditor.git

  • 2.NDK 环境需要准备好

  • 3.用 Android Studio 打开 WsVideoEditor/android 目录

  • 4.在手机中准备 /sdcard/test.mp4 视频文件

  • 5.运行 wsvideoeditor-test 项目

二、SDK功能介绍

这一章我们来介绍一下 编辑SDK 目前有的以及未来会有的功能。编辑SDK 的最终形态会和抖音的视频编辑功能接近,有其他想法的读者也可以在评论区留言或者提 issue。

1.目前有的功能

  • 1.开始播放

  • 2.暂停播放

  • 3.视频音量调整

  • 4.单段视频播放

  • 5.多段视频播放

  • 6.视频 Seek

  • 7.视频边缘模糊填充

2.规划中的功能

  • 1.视频类:

    • 1.按时间轴添加额外的声音

    • 2.按时间轴添加滤镜

    • 3.按时间轴添加静态贴纸、动态贴纸

    • 4.多段视频间转场

  • 2.图片类:

    • 1.添加声音

    • 2.多张图片间的转场

    • 3.照片电影

  • 3.工具类:

    • 1.视频缩略图截取

    • 2.视频元数据读取

  • 4.编码类:

    • 1.导出不同格式的视频

    • 2.更改视频的分辨率、帧率

    • 3.视频转 gif

  • 5.技术类:

    • 1.多进程编解码视频

    • 2.多进程播放视频

    • 3.多进程视频缩略图截取

三、SDK架构以及运行机制介绍

这一章我来介绍一下目前 编辑SDK 的整体架构以及运行机制。

1.编辑SDK架构

c129f1610ad17411496e9af7a52aa665.png

图6是 编辑SDK 的架构图,这一节我会照着这张图来介绍。

(1).基础API

先从底部看起,底部是整个 SDK 依赖的底层 API 库。

  • 1.FFMPEG:前面简单介绍过,是一个开源的视频库,在我们的项目中主要用于软编解码

  • 2.MediaCodec:是 Android 中的硬编解码 API,相应的 iOS 也有自己的硬编解码方式。

  • 3.OpenGL:是一个开源的图形库,Android 和 iOS 中都有内置 OpenGL ES 作为默认图形库。在我们的项目中主要用于将视频解码后的视频帧绘制到屏幕上去。当然也可以对这些图像做一些效果的变化,例如滤镜、视频/图片转场等等。

  • 4.Libyuv:是 Google 开源的实现各种 YUV 与 RGB 之间相互转换、旋转、缩放的库。

  • 5.Protobuf:是 Google 开源的一种平台无关、语言无关、可扩展且轻便高效的序列化数据结构的协议。在我们的项目中主要用于 Cpp 与 Java、OC、Dart 之间的数据通信。

(2).SDK主体

接着我们再看图片中的主体部分,因为目前只有 Android 端的实现,所以主体部分的上层实现我使用 Android 来代替。

  • 1.Android层架构

    • 1.WSMediaPlayerView:继承于 TextureView,所以其可以提供一个具有 Open GL 环境的线程。对 Surface 家族不了解的同学可以看看这两篇文章:Android绘制机制以及Surface家族源码全解析:https://juejin.im/post/5c666a9ff265da2d8b632bf9、相机/OpenGL/视频/Flutter和SurfaceView:https://juejin.im/post/5d9fd2415188256bf067e23d#heading-5)

    • 2.WSMediaPlayer:这个是一个代理了 Native 的 NativeWSMediaPlayer 的 Java 类。该类具有一个播放器应该有的各种 API,例如 play、pause、seek 等等。其实很多 Android 中的系统类都是以这种形式存在的,例如 Bitmap、Surface、Canvas 等等。说到底 Java 只是 Android 系统方便开发者开发 App 的上层语言,系统中大部分的功能最终都会走到 Native 中去,所以读者需要习惯这种代码逻辑

    • 3.AudioPlayer:这个类是基于 Android 中的 AudioTrack 封装的能够播放音频帧的 Java 类。我们在 Native 层也有一个 AudioPlayer。这里与 WSMediaPlayer 相反 Native 层的 AudioPlayer 是一个空壳,Java 层的 AudioPlayer 反向代理了 Native 层的 AudioPlayer,因为在这里 Java 层的 AudioPlayer 才是真正播放音频的东西。

  • 2.Native层架构:这里我们自底向上来剖析,Native 层的架构

    • 1.AudioDecodeService:它负责使用 FFMPEG/MediaCodec,来从视频/音频中解码出某个时间点的音频帧,并且存储在一个音频帧队列中。最终被外部取出音频帧交给音频播放器播放。

    • 2.VideoDecodeService:它和 AudioDecodeService 类似,是使用 FFMPEG/MediaCodec 来从视频中解码出某个时间点的视频帧并且存储在一个视频帧队列中。最终被外部取出视频帧交给 OpenGL 绘制到屏幕上。

    • 3.VideoFramePool:它负责响应外部的 seek 事件,然后使用 FFMPEG/MediaCodec 来从视频中解码出当前时间点的视频帧,然后存储到一个 LruCache 中同时返回 seek 时间点的视频帧。

    • 4.AudioPlayer:前面说过,这个是 Java 层的 AudioPlayer 代理类,主要用于播放 AudioDecodeService 解码出来音频帧。

    • 5.FrameRenderer:这个东西是一个渲染器,在视频播放时用于渲染 VideoDecodeService 不断解码出的视频帧,在视频 seek 的时用于向 VideoDecoderPool 发送 seek 请求,然后渲染返回的视频帧。

    • 6.NativeWSMediaPlayer:用于同步 AudioPlayer 和 FrameRenderer 的音视频播放。即我们一般认为的视频播放器实体,被 Java 层的 WSMediaPlayer 代理着。

2.编辑SDK运行机制

d9af9a07459c7832a39902a2f6431b58.png

上一节讲解了 编辑SDK 的架构,这一节在来基于图7讲讲 编辑SDK 的运行机制。

  • 1.经过上一节的介绍,我们都知道了 WSMediaPlayerView 是整个 编辑SDK 的顶级类。所以我们由 WSMediaPlayerView 入手,先看图片最上面。

    • 1.可以看见 WSMediaPlayerView 中会维护一个 30ms的定时循环,这个循环中会不断的调用 draw frame 来驱动 WSMediaPlayer/NativeWSMediaPlayer 进行视频/音频的播放。

    • 2.与此同时,最左边的用户会通过 play、pause、seek 等 API 来更新 NativeWSMediaPlayer 的状态。

    • 3.需要注意的是,WSMediaPlayerView 的定时循环不会被用户的 play、pause、seek 等操作所中断的。

  • 2.再来看看图片左边,这是 WSMediaPlayer 的内部播放机制。要点为 三个循环,两个播放,我们还是自底向上解析。

    • 1.VideoDecodeService:它内部维护了一个可阻塞循环与一个先进先出队列——BlockingQueue,当我们开始播放视频或者 seek 视频到某个时间点的时候,VideoDecodeService 会记录这个开始的时间点,然后不断的解码当前时间点之后的每一帧,每解码出一帧便把这一帧放入 BlockingQueue 中。当队列中的元素达到最大值时,当前的循环就会被阻塞,直到外部将 BlockingQueue 中的 Top 帧消费了,那么循环又会被启动继续解码。需要注意的是:VideoDecodeService 只在视频播放的时候提供视频帧,因为在这个情况下 BlockingQueue 中的视频帧的顺序就是视频真正播放的顺序。

    • 2.VideoFramePool:它内部维护了一个可阻塞请求循环与一个LruCachePool。一般情况下 VideoFramePool 的循环是处于阻塞状态的。当外部 seek 视频的时候,循环会接收到一个请求并开始处理这个请求,如果 LruCachePool 中有 Cache 被命中了,那么就直接返回 Cache,否则将会立即从视频中解码出这个请求中时间点的视频帧存到 LruCachePool 中然后再返回。需要注意的是:VideoFramePool 只在视频 seek 的时候提供视频帧,因为我们的 seek 操作是随机的,所以在这个情况下 VideoDecodeService 无法使用。

    • 3.AudioDecodeService:它与 VideoDecodeService 类似,也维护了一个可阻塞循环先进先出队列,内部的其他行为也类似,只是将视频帧换成了音频帧。

    • 4.FrameRenderer

      • 1.当视频 seek 的时候,其会从 VideoFramePool 中取出 seek 时刻的视频帧绘制它。

      • 2.当视频处于 playing 状态时,它的 drawFrame 方法就会不断被 WSMediaPlayerView 通过定时循环调用并从 VideoDecodeService 中取出当前帧通过 Open GL 绘制它。

    • 5.AudioPlayer:当视频处于 playing 状态时,它也会不断被 WSMediaPlayerView 通过定时循环驱动着从 AudioDecodeService 中取出当前的音频帧,然后通过反向代理将音频帧交给 Java 层的 AudioPlayer 进行播放。

四、VideoDecodeService解析

上一章大概的讲了讲整个 编辑SDK 的整体架构和运行机制,但其实整个 编辑SDK 内部的每一个部分的细节都非常多,所以这一章我会先讲解 VideoDecodeService 的内部细节。其他各个部分则放在后面几篇文章中讲解。与此同时,WsVideoEditor https://github.com/TheGodsThemselves/WsVideoEditor 中的代码也会随着讲解的进行而不断更新。最终形成一个可用的 编辑SDK

1.API讲解

-----代码块1----- VideoDecodeService.javaprivate native long newNative(int bufferCapacity);private native void releaseNative(long nativeAddress);private native void setProjectNative(long nativeAddress, double startTime, byte[] projectData);private native void startNative(long nativeAddress);private native String getRenderFrameNative(long nativeAddress, double renderTime);private native void updateProjectNative(long nativeAddress, byte[] projectData);private native void seekNative(long nativeAddress, double seekTime);private native void stopNative(long nativeAddress);private native boolean endedNative(long nativeAddress);private native boolean stoppedNative(long nativeAddress);private native int getBufferedFrameCountNative(long nativeAddress);

如代码块1所示,我们先来讲讲 VideoDecodeService 的 API

  • 1.newNative:由前面几章的讲解我们知道,VideoDecoderService 内部有一个先进先出的阻塞队列,这个方法的入参 bufferCapacity 就是用于设置这个阻塞队列的长度。这个方法调用之后 Native 层会创建一个与 Java 层同名的 VideoDecodeService.cpp 对象。然后返回一个 long 表示这个 Cpp 对象的地址。我们会将其记录在 Java 层,后续要调用其他方法时需要通过这个地址找到相应的对象。

  • 2.releaseNative:因为 Cpp 没有垃圾回收机制,所以 Cpp 对象都是需要手动释放的,所以这个方法就是用于释放 VideoDecodeService.cpp 对象。

  • 3.setProjectNative:因为 Protobuf 是高效的跨平台通信协议,所以 Java 与 Cpp 层的通信方式使用的就是 Protobuf,我们可以看 ws_video_editor_sdk.proto 这个文件,里面定义的 EditorProject 就是两端一起使用的数据结构。这个方法的入参 nativeAddress 就是我们在 1 中获取到的对象地址。入参 startTime 表示起始的解码点,单位是秒。入参 projectData 就是 EditorProject 序列化之后的字节流。

  • 4.startNative:这个方法表示开始解码。

  • 5.getRenderFrameNative:这个方法表示获取 renderTime 这一时刻的帧数据,目前返回到 Java 层的是一个 String,在 Cpp 层后续我们主要就是使用这个方法获取到的帧数据使用 OpenGL 绘制到屏幕上。 

  • 6.updateProjectNative:这个方法和 setProjectNative 类似,用于更新 EditorProject。

  • 7.seekNative:我们在看视频的时候,将进度条拖动到某一时刻的操作被称为 seek,在 VideoDecodeService 中的体现就是这个方法,这个方法会将当前的解码时间点设置为 seekTime

  • 8.stopNative:这个方法表示暂停解码。

  • 9.endedNative:返回一个 boolean 表示视频的解码点是否到达了视频的结尾。

  • 10.stoppedNative:返回了一个 boolean 表示当前是否暂停了解码。

  • 11.getBufferedFrameCountNative:返回一个 int,表示当前阻塞队列中有多少个帧,最大不会超过我们在 1 中设置的 bufferCapacity。 

2.代码分析

这一小节中,我使用一个完整的例子来分析 VideoDecodeService 的源码

  • 1.例子在 TestActivity 中,我们运行项目会看见界面上有三个 Button 和两个 TextView。

  • 2.我们在 initButton 中进行了下面这些操作

    • 1.初始化了 ui。

    • 2.创建了一个 VideoDecodeService.java 类,内部就是调用我们上一节说的 newNative方法。这个方法最终会进入到 video_decode_service.h 中调用 VideoDecodeService.cpp 的构造方法,构造方法则会创建一个 BlockingQueue.cpp 对象 decoded_unit_queue_,这就是我们一直说的 先进先出阻塞队列

    • 3.构建了一个 EditorProject.java,里面传了一个需要解码的视频路径 /sdcard/test.mp4

  • 3.我们点击 START 按钮

    • 1.stringBuilder 和 times 是用来记录测试数据的就不说了

    • 2.这里然后调用了 setProject 方法,经过一系列调用链后会通过 jni 进入到代码块3

      • 1.将 buffer 反序列化成 EditorProject.cpp 对象。

      • 2.address 强转 VideoDecodeService.cpp 对象。

      • 3.使用 LoadProject 方法解析出一些数据,例如视频的帧率、宽高等等。有兴趣的读者可以跟进入看看。

      • 4.调用 SetProject 给 VideoDecodeService.cpp 设置 EditorProject.cpp。

    • 3.调用 start 最终也是到代码块3中,调用 Start 方法。我们继续进入 Start 方法中,发现其中是启动了一个线程然后调用 VideoDecodeService::DecodeThreadMain,这个方法内部则是一个 while 循环,每当使用 FFMPEG 解码出一个视频帧的时候就会将这一帧放到 decoded_unit_queue_ 中。当外部没有消费者时,decoded_unit_queue_ 的帧数量将会很快达到阈值(我们设置的是10),此时这个线程就会被阻塞。直到外部消费后,帧数量减少了,本线程将会继续开始解码视频帧,如此往复。

-----代码块3----- com_whensunset_wsvideoeditorsdk_inner_VideoDecoderService.ccJNIEXPORT void JNICALLJava_com_whensunset_wsvideoeditorsdk_inner_VideoDecodeService_setProjectNative(JNIEnv *env, jobject, jlong address, jdouble render_pos, jbyteArray buffer) {VideoDecodeService *native_decode_service = reinterpret_cast<VideoDecodeService *>(address);model::EditorProject project;jbyte *buffer_elements = env->GetByteArrayElements(buffer, 0);project.ParseFromArray(buffer_elements, env->GetArrayLength(buffer));env->ReleaseByteArrayElements(buffer, buffer_elements, 0);LoadProject(&project);native_decode_service->SetProject(project, render_pos);}JNIEXPORT void JNICALL Java_com_whensunset_wsvideoeditorsdk_inner_VideoDecodeService_startNative(JNIEnv *, jobject, jlong address) {VideoDecodeService *native_decode_service = reinterpret_cast<VideoDecodeService *>(address);native_decode_service->Start();}
  • 4.继续看代码块2,可以看见我启动了一个 Java 层的无限循环线程,每隔 30ms 会 sleep 一下。每次循环我则会调用 getRenderFrame 方法来从 VideoDecodeService 中消费一个视频帧。然后把帧的信息打印到 TextView 上面。其实这里的代码可以类比为视频的播放,VideoDecodeService 不断地在后台线程进行解码按顺序将视频帧放入到队列中,本线程则不断的从队列中取出一帧进行消费,就像视频帧被渲染到屏幕上一样。

  • 5.最下面还有一个 Java 层的无限循环线程,会不断的读取 VideoDecodeService 的其他信息打印到 TextView 上。

  • 6.这一节只是简单的介绍 VideoDecodeService 的运行思路,其实代码里还有很多实现细节,这些细节解析就只能交给读者了,毕竟我比较懒:-D

五、尾巴

终于从零开发仿写一个抖音APP这一系列文章又重新开始更新了,今年以来文章的发表间隔长了很多,写文章的时间也少了很多。但是为了那么多支持、关注我的读者我也不能就这样放弃更新。立一个 flag,今后每个月都要更新一篇文章,希望大家能够多多支持,感谢!!

不贩卖焦虑,也不标题党。分享一些这个世界上有意思的事情。题材包括且不限于:科幻、科学、科技、互联网、程序员、计算机编程。下面是我的微信公众号:世界上有意思的事,干货多多等你来看。b8eaef2eb93c43bae30fc7aa8fcb7895.png

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

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

相关文章

爱默生E系列服务器机柜托盘,艾默生通信电源PS48300-3B/1800 一体化室内机柜

PS48300-3B/1800电源系统PS48300-3B/1800电源系统是艾默生网络能源集多年开发和网上运行经验&#xff0c;采用 DSP控制技术&#xff0c;为满足3G网络需求而设计的高可靠、高功率密度、高性能、全数 字化通信电源系统。根据交流配电和机柜高度。一、特点 1、休眠节能专利技术&am…

功能Java示例 第8部分–更多纯函数

这是第8部分&#xff0c;该系列的最后一部分称为“ Functional Java by Example”。 我在本系列的每个部分中发展的示例是某种“提要处理程序”&#xff0c;用于处理文档。 在上一期文章中&#xff0c;我们已经使用Vavr库看到了一些模式匹配&#xff0c;并且还将故障也视为数据…

tensorflow 小于_坐姿不对,屏幕就变模糊!教你用TensorFlow做一款“隐形背背佳”...

大数据文摘出品作为一个上班族&#xff0c;每天坐在电脑前那么久&#xff0c;难免出现腰酸背痛的情况&#xff0c;时间长了甚至脊柱都歪曲变形了&#xff0c;这可不行&#xff01;一定要克制住自己的坐姿。这里有款“隐形背背佳”&#xff0c;要不要了解一下&#xff1f;一位名…

python里随机抽取样本_概率分布和抽样分布基础知识及Python实现

本文主要介绍推论统计中的概率分布和抽样分布,本文结构如下: 一、概率分布 随机变量:在一定条件下,某件事情可能发生或者不发生,这个事件就叫随机事件。例如抛硬币哪面朝上。随机变量X就是用来量化随机事件的函数,是将随机事件每一个可能出现的结果映射到数值的一个函数。…

php 创建目录_使用SMB绕过PHP远程文件包含限制

译文声明本文是翻译文章&#xff0c;文章原作者mannulinux&#xff0c;文章来源&#xff1a;mannulinux.org 原文地址&#xff1a;http://www.mannulinux.org/2019/05/exploiting-rfi-in-php-bypass-remote-url-inclusion-restriction.html译文仅供参考&#xff0c;具体内容表达…

eas系统服务器地址,EAS7.0EAS7.5服务端及其客户端标准配置介绍

金蝶EAS是基于JavaEE的大型企业应用。目前服务端支持以下主流软硬件环境。适用版本&#xff1a;EAS7.0 EAS703 EAS7.51.金蝶EAS服务端支持的软硬件环境注&#xff1a;标志为部署支持的环境&#xff0c;可以正常安装配置&#xff0c;但是正式运行前需联系金蝶技术支持。2.客户端…

Java的Kafka:构建安全,可扩展的消息传递应用程序

使用Okta的身份管理平台轻松部署您的应用程序 使用Okta的API在几分钟之内即可对任何应用程序中的用户进行身份验证&#xff0c;管理和保护。 今天尝试Okta。 当今的用户希望可以通过其计算机&#xff0c;手机&#xff0c;平板电脑或任何其他设备访问您的应用程序&#xff01; …

background复合属性顺序_CSS有哪些好用的字体属性?

本文创建于2020年9月&#xff0c;以下为正文&#xff1a;CSS中有哪些字体属性呢&#xff1f;font&#xff1a;复合属性。设置或检索对象中的文本特征。font-style&#xff1a;设置或检索对象中的字体样式。用于定义字体的风格&#xff0c;如&#xff1a;斜体&#xff08;italic…

转译和编译_10个有趣又能编译为JavaScript的语言,你用过哪些?

点击上方“IT平头哥联盟”&#xff0c;选择“置顶或者星标”你的关注意义重大&#xff01;来源&#xff1a;https://www.sitepoint.com/现代应用相比普通的网页有不同的要求。但是浏览器是一个有着一套(大部分)固定可用的技术的平台&#xff0c;JavaScript依然是web应用的核心语…

OPC服务器如何采集设备的信息,如何将OPC服务器采集的设备数据转为MQTT方式,实现云端发布或订阅...

通过与PLC、智能仪表等硬件设备&#xff0c;以及OPC服务器、数据库等软件建立通讯&#xff0c;进行实时数据采集监控&#xff0c;然后将相关数据转换为数据库、OPC&#xff0c;以及MQTT、HTTP等多种服务接口&#xff0c;实现各类自动化与信息化系统软件灵活对接。MQTT智能网关案…

微信小程序摄像头监控_微信必备黑科技小程序!

↑点击上方蓝字关注极物推荐欢迎分享到朋友圈-不花冤枉钱&#xff0c;历史见真谛对于喜欢购物的朋友们&#xff0c;这绝对是一把利器。只需把商品链接复制粘贴到上面&#xff0c;就可以轻松的查阅到该商品的历史价格&#xff0c;轻松直观的了解商品过去的价格波动&#xff0c;同…

无线打印 airprint 服务器,如何让 Windows 的共享打印机支持 AirPrint

对很多办公室环境来说&#xff0c;局域网共享打印机往往都是刚需&#xff0c;而如果局域网内的设备大多为 Windows 设备&#xff0c;通过 网络共享 的方式就可以轻松将所连接的 USB 打印机共享到局域网。但对同一个局域网内的 Apple 设备而言&#xff0c;因为很多打印机的 Mac …

硒测试中所需的功能和示例

所需功能是用于声明一组基本要求&#xff08;例如浏览器&#xff0c;操作系统&#xff0c;浏览器版本等的组合&#xff09;的类&#xff0c;以对Web应用程序执行自动跨浏览器测试。 当我们尝试通过Selenium自动化测试来自动化测试脚本时&#xff0c;我们需要考虑这些组合&…

边沿触发是什么意思_集基耦合双稳电路,集成化单稳电路,数字逻辑电路,门电路,触发器...

1).箝位器能把脉冲电压维持在某个数值上而使波形保持不变的电路称为箝位器。它也是整形电路的一种。例如电视信号在传输过程中会造成失真&#xff0c;为了使脉冲波形恢复原样&#xff0c;接收机里就要用箝位电路把波形顶部箝制在某个固定电平上。下图中反相器输出端上就有一个箝…

鲸鱼优化算法_盘点 35 个 Java 代码优化细节

代码优化&#xff0c;一个很重要的课题。可能有些人觉得没用&#xff0c;一些细小的地方有什么好修改的&#xff0c;改与不改对于代码的运行效率有什么影响呢&#xff1f;这个问题我是这么考虑的&#xff0c;就像大海里面的鲸鱼一样&#xff0c;它吃一条小虾米有用吗&#xff1…

powerbi导入地图_Power BI系列教程之powerBI功能介绍及使用导引(一)

作为从事数据分析相关工作的小伙伴&#xff0c;我们总会遇到一些工作是每周、每月甚至每天都是重复做的工作&#xff0c;这个时候我们有没有一种结构化的方法来处理这一些数据呢&#xff1f;答案是肯定的&#xff0c;微软为了满足广大职场数据分析人员的需求&#xff0c;开发了…

模糊搜索怎么实现_干货 | 你真的了解自己是怎么搜索的吗_01

「干货」栏目的开启是为了帮大家科普学习关于品牌出海、数字营销与跨境电商行业相关的知识&#xff0c;以及我们在业内会遇到的情况和一些规避的方法。第一讲搜索者我们整个搜索营销活动都是围绕搜索者展开的我们的目的是把搜索者转化为我们的销售机会所以&#xff0c;研究搜索…

linux shc shell脚本_详解shell脚本加密解密软件—gzese和shc

概述以我个人的需求为例&#xff0c;有时写一个脚本需要传密码&#xff0c;如果直接把密码写在脚本里会存在安全问题&#xff0c;一般是把密码写在脚本里&#xff0c;作为参数传给脚本&#xff0c;而保存密码的脚本&#xff0c;使用某种手段加密&#xff0c;令其不可读但是可执…

linux 查看主板sn_如何在 Linux 上查找硬件规格

在 Linux 系统上有许多工具可用于查找硬件规格。-- Sk(作者)在 Linux 系统上有许多工具可用于查找硬件规格。在这里&#xff0c;我列出了四种最常用的工具&#xff0c;可以获取 Linux 系统的几乎所有硬件(和软件)细节。好在是这些工具在某些 Linux 发行版上默认预装。我在 Ubun…

laravel 分词搜索匹配度_【地名地址】面向智慧城市的高精度地名地址匹配方法...

点击上方蓝字关注我们↑↑↑↑原 文 摘 要针对智慧城市建设中各种业务数据对地名地址匹配准确度和效率不高的问题&#xff0c;本文提出一种面向智慧城市的高精度地名地址匹配方法。该方法在基于中文分词的地名地址匹配技术框架下&#xff0c;综合利用精细化地名地址库构建、地名…