阅读更多

0顶
0踩

移动开发
【编者按】Android 视频相关的开发,大概一直是整个 android 生态、以及 Android API 中,最为分裂以及兼容性问题最为突出的一部分,本文从视频编码器的选择和如何对摄像头输出的 YUV 帧进行快速预处理两方面,从实践角度解析笔者曾趟过 Android 视频编码的那些坑,希望对广大读者有所助益。

Google 针对摄像头以及视频编码相关的 API,控制力一直非常差,导致不同厂商对这两个 API 的实现有不少差异,而且从 API 的设计来看,一直以来优化也相当有限,甚至有人认为这是“Android 上最难用的 API 之一”。

以微信为例,在 Android 设备录制一个 540P 的 MP4 文件,大体上遵循以下流程:

图1 Android 视频流编码流程图

从摄像头输出的 YUV 帧经过预处理之后,送入编码器,获得编码好的 H264 视频流。

上面只是针对视频流的编码,另外还需要对音频流单独录制,最后再将视频流和音频流合成最终视频。

这篇文章主要会对视频流的编码中两个常见问题进行分析:
  • 视频编码器的选择:硬编 or 软编?
  • 如何对摄像头输出的 YUV 帧进行快速预处理:镜像、缩放、旋转?
视频编码器的选择

对于录制视频的需求,不少 App 都需要对每一帧数据进行单独处理,因此很少会直接用到 MediaRecorder 来录取视频,一般来说,会有两个选择:
  • MediaCodec
  • FFMpeg+x264/openh264
下面我们逐个进行解析。

MediaCodec

MediaCodec 是 API 16 之后 Google 推出的用于音视频编解码的一套偏底层的 API,可以直接利用硬件加速进行视频的编解码。调用的时候需要先初始化 MediaCodec 作为视频的编码器,然后只需要不停传入原始的 YUV 数据进入编码器就可以直接输出编码好的 H.264 流,整个 API 设计模型同时包含了输入端和输出端的两条队列。

因此,作为编码器,输入端队列存放的是原始 YUV 数据,输出端队列输出的是编码好的 H.264 流,作为解码器则对应相反。在调用的时候,MediaCodec 提供了同步和异步两种调用方式,但是异步使用 Callback 的方式是在 API 21 之后才加入的,以同步调用为例,一般来说调用方式大概是这样(摘自官方例子):
MediaCodec codec = MediaCodec.createByCodecName(name);
 codec.configure(format, …);
 MediaFormat outputFormat = codec.getOutputFormat(); // option B
 codec.start();
 for (;;) {
   int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
   if (inputBufferId >= 0) {
     ByteBuffer inputBuffer = codec.getInputBuffer(…);
     // fill inputBuffer with valid data
     …
     codec.queueInputBuffer(inputBufferId, …);
   }
   int outputBufferId = codec.dequeueOutputBuffer(…);
   if (outputBufferId >= 0) {
     ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
     MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
     // bufferFormat is identical to outputFormat
     // outputBuffer is ready to be processed or rendered.
     …
     codec.releaseOutputBuffer(outputBufferId, …);
   } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
     // Subsequent data will conform to new format.
     // Can ignore if using getOutputFormat(outputBufferId)
     outputFormat = codec.getOutputFormat(); // option B
   }
 }
 codec.stop();
 codec.release();

简单解释一下,通过 getInputBuffers 获取输入队列,然后调用 dequeueInputBuffer 获取输入队列空闲数组下标,注意 dequeueOutputBuffer 会有几个特殊的返回值表示当前编解码状态的变化,然后再通过 queueInputBuffer 把原始 YUV 数据送入编码器,而在输出队列端同样通过 getOutputBuffers 和 dequeueOutputBuffer 获取输出的 H.264 流,处理完输出数据之后,需要通过 releaseOutputBuffer 把输出 buffer 还给系统,重新放到输出队列中。

关于 MediaCodec 更复杂的使用例子,可以参照 CTS 测试里面的使用方式:EncodeDecodeTest.java

从上面例子来看 MediaCodec 的确是非常原始的 API,由于 MediaCodec 底层直接调用了手机平台硬件的编解码能力,所以速度非常快,但是因为 Google 对整个 Android 硬件生态的掌控力非常弱,所以这个 API 有很多问题:

颜色格式问题

MediaCodec 在初始化的时候,configure 过程中需要传入一个 MediaFormat 对象,当作为编码器使用的时候,我们一般需要在 MediaFormat 中指定视频的宽高、帧率、码率、I 帧间隔等基本信息。除此之外,还有一个重要的信息就是,指定编码器接受的 YUV 帧的颜色格式,这是由于 YUV 根据其采样比例,UV 分量的排列顺序有很多种不同的颜色格式,而对于 Android 的摄像头在 onPreviewFrame 输出的 YUV 帧格式,没有配置任何参数的情况下,基本上都是 NV21 格式,但 Google 对 MediaCodec 的 API 在设计和规范的时候,显得很不厚道,过于贴近 Android 的 HAL 层了,导致了 NV21 格式并不是所有机器的 MediaCodec 都支持这种格式作为编码器的输入格式。 因此,在初始化 MediaCodec 的时候,我们需要通过 codecInfo.getCapabilitiesForType 来查询机器上的 MediaCodec 实现具体支持哪些 YUV 格式作为输入格式。一般来说,起码在 4.4+ 的系统上,这两种格式在大部分机器上都有支持:
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar

两种格式分别是 YUV420P 和 NV21,如果机器上只支持 YUV420P 格式,则需要先将摄像头输出的 NV21 格式先转换成 YUV420P,才能送入编码器进行编码,否则最终出来的视频就会花屏,或者颜色出现错乱。

这个算是一个不大不小的坑,基本上用 MediaCodec 进行视频编码都会遇上这个问题。

编码器支持特性相当有限

如果使用 MediaCodec 来编码 H.264 视频流,对于 H.264 格式来说,会有一些针对压缩率以及码率相关的视频质量设置,典型的诸如 Profile(baseline, main, hight)、Profile Level、Bitrate mode(CBR、CQ、VBR),合理配置这些参数可以让我们在同等的码率下,获得更高的压缩率,从而提升视频的质量,Android 也提供了对应的 API 进行设置,可以设置到 MediaFormat 中:
MediaFormat.KEY_BITRATE_MODE
MediaFormat.KEY_PROFILE
MediaFormat.KEY_LEVEL

但问题是,对于 Profile、Level、Bitrate mode 这些设置,在大部分手机上都是不支持的,即使是设置了最终也不会生效,例如设置了 Profile 为 high,最后出来的视频依然还会是 Baseline。

这个问题,在 7.0 以下的机器几乎是必现的,其中一个可能的原因是,Android 在源码层级 hardcode 了 Profile 的的设置:
// XXX
if (h264type.eProfile != OMX_VIDEO_AVCProfileBaseline) {
 ALOGW("Use baseline profile instead of %d for AVC recording",
     h264type.eProfile);
 h264type.eProfile = OMX_VIDEO_AVCProfileBaseline;
    }

Android 直到 7.0 之后才取消了这段地方的 Hardcode。
if (h264type.eProfile == OMX_VIDEO_AVCProfileBaseline) {
        ....
    } else if (h264type.eProfile == OMX_VIDEO_AVCProfileMain ||
            h264type.eProfile == OMX_VIDEO_AVCProfileHigh) {
        .....
    }

这个问题可以说间接导致了 MediaCodec 编码出来的视频质量偏低,同等码率下,难以获得跟软编码甚至 iOS 那样的视频质量。

16 位对齐要求

前面说到,MediaCodec 这个 API 在设计的时候,过于贴近 HAL 层,这在很多 SoC 的实现上,是直接把传入 MediaCodec 的 buffer,在不经过任何前置处理的情况下就直接送入了 Soc 中。而在编码 H264 视频流的时候,由于 H264 的编码块大小一般是 16x16,于是在一开始设置视频宽高的时候,如果设置了一个没有对齐 16 的大小,例如 960x540,在某些 CPU 上,最终编码出来的视频就会直接花屏。

很明显这还是因为厂商在实现这个 API 的时候,对传入的数据缺少校验以及前置处理导致的。目前来看,华为、三星的 SoC 出现这个问题会比较频繁,其他厂商的一些早期 Soc 也有这种问题,一般来说解决方法还是在设置视频宽高的时候,统一设置成对齐 16 位就好了。

FFMpeg+x264/openh264

除了使用 MediaCodec 进行编码之外,另外一种比较流行的方案就是使用 FFmpeg + x264/OpenH264 进行软编码,FFmpeg 适用于一些视频帧的预处理。这里主要是使用 x264/OpenH264 作为视频的编码器。

x264 基本上被认为是当今市面上最快的商用视频编码器,而且基本上所有 H264 的特性都支持,通过合理配置各种参数还是能够得到较好的压缩率和编码速度的,限于篇幅,这里不再阐述 H.264 的参数配置。

OpenH264 则是由思科开源的另外一个 H264 编码器,项目在 2013 年开源,对比起 x264 来说略显年轻,不过由于思科支付买了 H.264 的年度专利费,所以对于外部用户来说,相当于可以直接免费使用了。另外,firefox 直接内置了 OpenH264,作为其在 WebRTC 中的视频编解码器使用。

但对比起 x264,OpenH264 在 H264 高级特性的支持比较差:
  • Profile 只支持到 baseline,level 5.2;
  • 多线程编码只支持 slice based,不支持 frame based 的多线程编码。
从编码效率上来看,OpenH264 的速度也并不会比 x264 快,不过其最大的好处,还是能够直接免费使用。

软硬编对比

从上面的分析来看,硬编的好处主要在于速度快,而且系统自带,不需要引入外部的库,但是特性支持有限,而且硬编的压缩率一般偏低。对于软编码来说,虽然速度较慢,但是压缩率比较高,而且支持的 H264 特性也会比硬编码多很多,相对来说比较可控。就可用性而言,在 4.4+的系统上,MediaCodec 的可用性是能够基本保证的,但是不同等级机器的编码器能力会有不少差别,建议可以根据机器的配置,选择不同的编码器配置。

YUV 帧的预处理

根据最开始给出的流程,在送入编码器之前,我们需要先对摄像头输出的 YUV 帧进行一些前置处理。

缩放

如果设置了 Camera 的预览大小为 1080P,在 onPreviewFrame 中输出的 YUV 帧直接就是 1920x1080 的大小,如果需要编码跟这个大小不一样的视频,我们就需要在录制的过程中,实时的对 YUV 帧进行缩放。

以微信为例,摄像头预览 1080P 的数据,需要编码 960x540 大小的视频。

最为常见的做法是使用 FFmpeg 的 swsscale 函数进行直接缩放,效果/性能比较好的一般是选择 SWSFAST_BILINEAR 算法:
mScaleYuvCtxPtr = sws_getContext(
                   srcWidth,
                   srcHeight,
                   AV_PIX_FMT_NV21,
                   dstWidth,
                   dstHeight,
                   AV_PIX_FMT_NV21,
                   SWS_FAST_BILINEAR, NULL, NULL, NULL);
sws_scale(mScaleYuvCtxPtr,
                    (const uint8_t* const *) srcAvPicture->data,
                    srcAvPicture->linesize, 0, srcHeight,
                    dstAvPicture->data, dstAvPicture->linesize);

在 Nexus 6P 上,直接使用 FFmpeg 来进行缩放的时间基本上都需要 40ms+,对于我们需要录制 30fps 的来说,每帧处理时间最多就 30ms,如果光是缩放就消耗了如此多的时间,基本上录制出来的视频只能在 15fps 上下了。

很明显,直接使用 FFmpeg 进行缩放实在太慢了,不得不说 swsscale 在 FFmpeg 里面不适用。经对比了几种业界常用的算法之后,我们最后考虑使用快速缩放的算法,如图 3 所示。

我们选择一种叫做局部均值的算法,前后两行四个临近点算出最终图片的四个像素点,对于源图片的每行像素,我们可以使用 Neon 直接实现,以缩放 Y 分量为例:
const uint8* src_next = src_ptr + src_stride;
    asm volatile (
      "1:                                          \n"    
        "vld4.8     {d0, d1, d2, d3}, [%0]!        \n"
        "vld4.8     {d4, d5, d6, d7}, [%1]!        \n"
        "subs       %3, %3, #16                    \n"  // 16 processed per loop
        "vrhadd.u8   d0, d0, d1                    \n"
        "vrhadd.u8   d4, d4, d5                    \n"
        "vrhadd.u8   d0, d0, d4                    \n"
        "vrhadd.u8   d2, d2, d3                    \n"
        "vrhadd.u8   d6, d6, d7                    \n"
        "vrhadd.u8   d2, d2, d6                    \n"
        "vst2.8     {d0, d2}, [%2]!                    \n"  // store odd pixels
        "bgt        1b                             \n"
      : "+r"(src_ptr),          // %0
        "+r"(src_next),         // %1
        "+r"(dst),              // %2
        "+r"(dst_width)         // %3
      :
      : "q0", "q1", "q2", "q3"              // Clobber List
);

上面使用的 Neon 指令每次只能读取和存储 8 或者 16 位的数据,对于多出来的数据,只需要用同样的算法改成用 C 语言实现即可。

在使用上述的算法优化之后,进行每帧缩放,在 Nexus 6P 上,只需要不到 5ms 就能完成了,而对于缩放质量来说,FFmpeg 的 SWSFASTBILINEAR 算法和上述算法缩放出来的图片进行对比,峰值信噪比(psnr)在大部分场景下大概在 38-40 左右,质量也足够好。

旋转

在 Android 机器上,由于摄像头安装角度不同,onPreviewFrame 出来的 YUV 帧一般都是旋转了 90 度或者 270 度,如果最终视频是要竖拍的,那一般来说需要把 YUV 帧进行旋转。

对于旋转的算法,如果是纯 C 实现的代码,一般来说是个 O(n2 )复杂度的算法,如果是旋转 960x540 的 YUV 帧数据,在 Nexus 6P 上,每帧旋转也需要 30ms+,这显然也是不能接受的。

在这里我们换个思路,能不能不对 YUV 帧进行旋转?显当然是可以的。

事实上在 MP4 文件格式的头部,我们可以指定一个旋转矩阵,具体来说是在 moov.trak.tkhd box 里面指定,视频播放器在播放视频的时候,会读取这里的矩阵信息,从而决定视频本身的旋转角度、位移、缩放等,具体可以参考苹果的文档

通过 FFmpeg,我们可以很轻松的给合成之后的 mp4 文件打上这个旋转角度:
char rotateStr[1024];
sprintf(rotateStr, "%d", rotate);
av_dict_set(&out_stream->metadata, "rotate", rotateStr, 0);

于是可以在录制的时候省下一大笔旋转的开销。

镜像

在使用前置摄像头拍摄的时候,如果不对 YUV 帧进行处理,那么直接拍出来的视频是会镜像翻转的,这里原理就跟照镜子一样,从前置摄像头方向拿出来的 YUV 帧刚好是反的,但有些时候拍出来的镜像视频可能不合我们的需求,因此这个时候我们就需要对 YUV 帧进行镜像翻转。

但由于摄像头安装角度一般是 90 度或者 270 度,所以实际上原生的 YUV 帧是水平翻转过来的,因此做镜像翻转的时候,只需要刚好以中间为中轴,分别上下交换每行数据即可,注意 Y 跟 UV 要分开处理,这种算法用 Neon 实现相当简单:
asm volatile (
      "1:                                          \n"
        "vld4.8     {d0, d1, d2, d3}, [%2]!        \n"  // load 32 from src
        "vld4.8     {d4, d5, d6, d7}, [%3]!        \n"  // load 32 from dst
        "subs       %4, %4, #32                    \n"  // 32 processed per loop
        "vst4.8     {d0, d1, d2, d3}, [%1]!        \n"  // store 32 to dst
        "vst4.8     {d4, d5, d6, d7}, [%0]!        \n"  // store 32 to src
        "bgt        1b                             \n"
      : "+r"(src),   // %0
        "+r"(dst),   // %1
        "+r"(srcdata), // %2
        "+r"(dstdata), // %3
        "+r"(count)  // %4  // Output registers
      :                     // Input registers
      : "cc", "memory", "q0", "q1", "q2", "q3"  // Clobber List
    );

同样,剩余的数据用纯 C 代码实现就好了, 在 Nexus 6P 上,这种镜像翻转一帧 1080x1920 YUV 数据大概只要不到 5ms。

在编码好 H.264 视频流之后,最终处理就是把音频流跟视频流合流然后包装到 mp4 文件,这部分我们可以通过系统的 MediaMuxer、mp4v2,或者 FFmpeg 来实现,这部分比较简单,在这里就不再阐述了。

参考文献
  • 雷霄骅(leixiaohua1020)的专栏 ,大名鼎鼎雷神的博客,里面有非常多关于音视频编码/FFmpeg 相关的学习资料,入门必备。也祈愿他能够在天堂安息吧。
  • Android MediaCodec stuff,包含了一些 MediaCodec 使用的示例代码,初次使用可以参考下这里。
  • Coding for NEON,一个系列教程,讲述了一些常用 Neon 指令使用方法。上面在介绍缩放的时候使用到了 Neon,事实上大部分音视频处理过程都会使用到,以 yuv 帧处理为例,缩放,旋转,镜像翻转都可以使用 Neon 来做优化。
  • libyuv,Google 开源的一个 YUV 处理库,上面只针对 1080p->540p 视频帧缩放的算法,而对于通用的压缩处理,可以直接使用这里的实现,对比起 FFmpeg 的速度快上不少。
引用
作者:周俊杰,微信 Android 客户端开发工程师,常年维护音视频相关模块的开发以及各类兼容性问题的处理,负责微信支付相关需求的开发。
责编:唐门教 主(tangxy@csdn.net)
声明:本文为 CSDN《程序员》原创文章,未经许可,请勿转载,如需转载,请留言。

  • 大小: 65.7 KB
  • 大小: 67.6 KB
  • 大小: 30.6 KB
0
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 微信Android视频编码爬过的那些坑

    Google针对摄像头以及视频编码相关的API,控制力一直非常差,导致不同厂商对这两个 API的实现有不少差异,...文件,大体上遵循以下流程:图1Android视频流编码流程图从摄像头输出的YUV帧经过预处理之后,送入编码器,

  • 微信Android 视频编码爬过的那些坑

    Google 针对摄像头以及视频编码相关的 API,控制力一直非常差,导致不同厂商对这两个 API 的实现有不少差异,而且从 API 的设计来看,一直以来优化也相当有限,甚至有人认为这是“Android 上最难用的 API 之一”。...

  • 这些年,我爬过的 Android 坑 | 持续更新

    AndroidStudio 提示 Please select Android SDK 关于 Java 中字符与字节的编码关系认识 关于 emoji 编码的长度计算问题 <> 如何理解非主线程可以更新UI 谷歌在 viewRootImpl 中检查更新ui的线程 void checkThread() ...

  • 腾讯团队,微信中使用到的视频技术,音视频研究

     微信商业化是三层架构:最底层是微信的社交平台,它聚集了海量的用户,这是商业化的养分;第二层是开放公众平台,它连接所有的主体(服务和内容提供方),这是商业化的土壤;第三层是业务,包括游戏、支付业务、...

  • 微信公众号抓取 操作手机相关操作 ②

    # 点击退出登录 quit_logging_util = wait.until(EC.presence_of_element_located((By.XPATH, "/hierarchy/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget....

  • android音视频总结

    github上十二款最著名的Android播放器开源项目 视频播放(media palyer, video player): ijkplayer(c语言):https://github.com/Bilibili/ijkplayer Exoplayer:https://github.com/google/ (google开源) ...

  • 移动大脑-SpringMVc搭建RestFul后台服务(六)-微信支付(Android)

    我的视频课程(基础):《(NDK)FFmpeg...我的视频课程(编码直播推流):《Android视频编码和直播推流》   目录:  移动大脑-SpringMVc搭建RestFul后台服务(一)-环境搭建  移动大脑-SpringMVc搭建Res...

  • 音视频开发总结之二Android平台相关

    串联整个音视频录制流程,完成音视频的采集、编码、封包成 mp4 输出。 通过摄像头和麦克风获得实时的音视频数据; 播放流程: 获取流—>解码—>播放。 录制播放路程: 录制音频视频—>视频处理—>编码—...

  • 微信小程序

    当开发者允许微信索引时,微信会通过爬虫的形式,为小程序的页面内容建立索引。当用户的搜索词条触发该索引时,小程序的页面将可能展示在搜索结果中 5.3.1在小程序管理的后台配置是否允许被搜索到 打开小程序的管理...

  • 微信发送自定义语音

    很皮微信前景开发遇到的难题收集语音素材问题解决录制音频如何变声音频格式转换amr问题(ffmpeg&amr)音频方面的知识点音频文件的组成PCM&...

  • Python自动抢红包,超详细教程,再也不会错过微信红包了

    利用 AirtestIDE 创建一个项目时,设备类型选中 Android,就会在编码区生成一段初始化的代码。 from airtest.core.api import * auto_setup(__file__) from poco.drivers.android.uiautomation import ...

  • 视频教程-微信公众平台开发实战PHP版-微信开发

    微信公众平台开发实战PHP版 互联商通创始人/架构师, 在IT领域摸爬滚打2...

  • 美团外卖商家端视频探索之旅

    背景 美团外卖至今已迅猛发展了六年,随着外卖业务量级与日俱增,单一的文字...为此,商家端引入了视频功能,进行了一系列视频功能开发,核心功能包含视频处理(混音,滤镜,加水印,动画等)、视频拍摄、合成等,最...

  • 张小龙发布2018微信全新计划(内附演讲全文)

    欢迎大家来到微信公开课。 刚刚出现的是我打游戏的画面,被大家看到了,那个不是我最好的水平,因为有点紧张,我最高分曾打到6000多分。当然我是练习了很久了,并不是我比大家更厉害,而是我有很多时间去...

  • 高校学生选课系统项目源码资源

    项目名称: 高校学生选课系统 内容概要: 高校学生选课系统是为了方便高校学生进行选课管理而设计的系统。该系统提供了学生选课、查看课程信息、管理个人课程表等功能,同时也为教师提供了课程发布和管理功能,以及管理员对整个选课系统的管理功能。 适用人群: 学生: 高校本科生和研究生,用于选课、查看课程信息、管理个人课程表等。 教师: 高校教师,用于发布课程、管理课程信息和学生选课情况等。 管理员: 系统管理员,用于管理整个选课系统,包括用户管理、课程管理、权限管理等。 使用场景及目标: 学生选课场景: 学生登录系统后可以浏览课程列表,根据自己的专业和兴趣选择适合自己的课程,并进行选课操作。系统会实时更新学生的选课信息,并生成个人课程表。 教师发布课程场景: 教师登录系统后可以发布新的课程信息,包括课程名称、课程描述、上课时间、上课地点等。发布后的课程将出现在课程列表中供学生选择。 管理员管理场景: 管理员可以管理系统的用户信息,包括学生、教师和管理员账号的添加、删除和修改;管理课程信息,包括课程的添加、删除和修改;管理系统的权限控制,包括用户权限的分配和管理。 目标: 为高校学生提

  • TC-125 230V 50HZ 圆锯

    TC-125 230V 50HZ 圆锯

  • 影音娱乐北雨影音系统 v1.0.1-bymov101.rar

    北雨影音系统 v1.0.1_bymov101.rar 是一个计算机专业的 JSP 源码资料包,它为用户提供了一个强大而灵活的在线影音娱乐平台。该系统集成了多种功能,包括视频上传、播放、分享和评论等,旨在为用户提供一个全面而便捷的在线视频观看体验。首先,北雨影音系统具有强大的视频上传功能。用户可以轻松地将本地的视频文件上传到系统中,并与其他人分享。系统支持多种视频格式,包括常见的 MP4、AVI、FLV 等,确保用户能够方便地上传和观看各种类型的视频。其次,该系统提供了丰富的视频播放功能。用户可以选择不同的视频进行观看,并且可以调整视频的清晰度、音量等参数,以适应不同的观看需求。系统还支持自动播放下一个视频的功能,让用户可以连续观看多个视频,无需手动切换。此外,北雨影音系统还提供了一个社交互动的平台。用户可以在视频下方发表评论,与其他观众进行交流和讨论。这为用户之间的互动提供了便利,增加了观看视频的乐趣和参与感。最后,该系统还具备良好的用户体验和界面设计。界面简洁明了,操作直观易用,让用户可以快速上手并使用各项功能。同时,系统还提供了个性化的推荐功能,根据用户的观看历史和兴趣,为用户推荐

  • Tripp Trapp 儿童椅用户指南 STOKKE

    Tripp Trapp 儿童椅用户指南

  • node-v8.13.0-linux-armv6l.tar.gz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

Global site tag (gtag.js) - Google Analytics