当前位置:   article > 正文

字节跳动nlp算法工程师面试问题及答案,从零开始仿写一个抖音App(1),Android高级

字节跳动nlp算法工程师面试问题及答案,从零开始仿写一个抖音App(1),Android高级

上一节讲解了 编辑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 中的代码也会随着讲解的进行而不断更新。最终形成一个可用的 编辑SDK

1.API讲解

-----代码块1----- VideoDecodeService.java
private 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.stringBuildertimes 是用来记录测试数据的就不说了
  • 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.cc
JNIEXPORT void JNICALL
Java_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 上。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

尾声

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

进阶学习视频

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算**

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/419760
推荐阅读
相关标签
  

闽ICP备14008679号