Android端的短视频开发,我们该如何快速实现移动端短视频功能?_android 实现抖音拍摄视频-程序员宅基地

技术标签: java  音视频  android  开发语言  

当下抖音非常火热,是不是也很心动做一个类似的app吗?

一.短视频内容生产

优质短视频内容的产生依赖于短视频的采集和特效编辑,这就要求在进行抖音APP开发时,用到基础的美颜、混音、滤镜、变速、图片视频混剪、字幕等功能,在这些功能基础上,进行预处理,结合OpenGL、AI、AR技术,产生很多有趣的动态贴纸玩法,使得短视频内容更具创意。

img

视频录制的大致实现流程是先由 Camera 、 AudioRecord 进行最原始的相机画面以及声音的采集,然后将采集的数据进行滤镜、降噪等前处理,处理完成后由 MediaCodec 进行硬件编码,最后采用 MediaMuxer 生成最终的 MP4 文件。

二.短视频处理播放

视频的处理和播放主要是视频的清晰度、观看流畅度方面的体验。在这方面来讲,可以采用“窄带高清”技术,在节省码率的同时能够提供更加清晰的观看体验,经过测试,同等视频质量下最高可以节省20-40%带宽。除了带宽之外,短视频内容的存储和CDN优化也尤为重要,通常我们需要上传到云存储服务器的内容是短视频内容和封面内容。

而CDN优化带给短视频平台的则是进一步的短视频首次载入和循环播放方面的体验。比如针对首播慢的问题,像阿里云播放器支持QUIC协议,基于CDN的调度,可以使短视频首次播放秒开的成功率达到98%,此外在循环播放时还可以边播放边缓存,用户反复观看某一短视频时就不用耗费流量了。

三.录制视频的方式

在Android系统当中,如果需要一台Android设备来获取到一个MP4这样的视频文件的话,主流的方式一共与三种:MediaRecorder、MediaCodec+MediaMuxer、FFmpeg。

MediaRecorder:是Android系统直接提供给我们的录制类,用于录制音频和视频的一个类,简单方便,不需要理会中间录制过程,结束录制后可以直接得到音频文件进行播放,录制的音频文件是经过压缩的,需要设置编码器,录制的音频文件可以用系统自带的播放器播放。

优点:大部分以及集成,直接调用相关接口即可,代码量小,简单稳定;

缺点:无法实时处理音频;输出的音频格式不是很多。

MediaCodec+MediaMuxer: MediaCodec 与 MediaMuxer结合使用同样能够实现录制的功能。MediaCodec是Android提供的编解码类,MediaMuxer则是复用类(生成视频文件)。从易用性的角度上来说肯定不如MediaRecorder,但是允许我们进行更加灵活的操作,比如需要给录制的视频添加水印等各种效果。

优点: 与MediaRecorder一样低功耗速度快,并且更加灵活

缺点: 支持的格式有限,兼容性问题

FFmpeg: FFmpeg(Fast forword mpeg,音视频转换器)是一个开源免费跨平台的视频和音频流方案,它提供了录制/音视频编解码、转换以及流化音视频的完整解决方案。主要的作用在于对多媒体数据进行解协议、解封装、解码以及转码等操作

优点:格式支持非常的强,十分的灵活,功能强大,兼容性好;

缺点:C语言些的音视频编解码程序,使用起来不是很方便。

img

虽然从数据看来FFmpeg是最好的,但是我们得首先排除这种,因为他的易用性是最差的;其次,MediaRecorder也是需要排除的,所以在这里我比较推荐MediaCodec+MediaMuxer这种方式。

四.编码器参数

码率:数据传输时单位时间传送的数据位数,kbps:千位每秒。码率和质量成正比,也和文件体积成正比。码率超过一定数值,对图像的质量没有多大的影响。

帧数:每秒显示多少个画面,fps

关键帧间隔:在H.264编码中,编码后输出的压缩图像数据有多种,可以简单的分为关键帧和非关键帧。关键帧能够进行独立解码,看成是一个图像经过压缩的产物。而非关键帧包含了与其他帧的“差异”信息,也可以称呼为“参考帧”,它的解码需要参考关键帧才能够解码出一个图像。非关键帧拥有更高的压缩率。

五、MediaCodec+MediaMuxer的使用

MediaMuxer和MediaCodec这两个类,它们的参考文http://developer.android.com/reference/android/media/MediaMuxer.html和http://developer.android.com/reference/android/media/MediaCodec.html,里边有使用的框架。这个组合可以实现很多功能,比如音视频文件的编辑(结合MediaExtractor),用OpenGL绘制Surface并生成mp4文件,屏幕录像以及类似Camera app里的录像功能(虽然这个用MediaRecorder更合适)等。

它们一个是生成视频,一个生成音频,这里把它们结合一下,同时生成音频和视频。基本框架和流程如下:

img

首先是录音线程,主要参考HWEncoderExperiments。通过AudioRecord类接收来自麦克风的采样数据,然后丢给Encoder准备编码:

AudioRecord audio_recorder; 
audio_recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 
 SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, buffer_size); 
// ... 
audio_recorder.startRecording(); 
while (is_recording) {
     
 byte[] this_buffer = new byte[frame_buffer_size]; 
 read_result = audio_recorder.read(this_buffer, 0, frame_buffer_size); // read audio raw data 
 // … 
 presentationTimeStamp = System.nanoTime() / 1000; 
 audioEncoder.offerAudioEncoder(this_buffer.clone(), presentationTimeStamp); // feed to audio encoder 

} 

这里也可以设置AudioRecord的回调(通过setRecordPositionUpdateListener())来触发音频数据的读取。offerAudioEncoder()里主要是把audio采样数据送入音频MediaCodec的InputBuffer进行编码:

ByteBuffer[] inputBuffers = mAudioEncoder.getInputBuffers(); 
int inputBufferIndex = mAudioEncoder.dequeueInputBuffer(-1); 
if (inputBufferIndex >= 0) {
     
 ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; 
 inputBuffer.clear(); 
 inputBuffer.put(this_buffer); 
 ... 
 mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, this_buffer.length, presentationTimeStamp, 0); 
} 

下面,参考Grafika-SoftInputSurfaceActivity,并加入音频处理。主循环大体分四部分:

try {
     
 // Part 1 
 prepareEncoder(outputFile); 
 ... 
 // Part 2 
 for (int i = 0; i < NUM_FRAMES; i++) {
     
 generateFrame(i); 
 drainVideoEncoder(false); 
 drainAudioEncoder(false); 
 } 
 // Part 3 
 ... 
 drainVideoEncoder(true); 
 drainAudioEncoder(true); 
} catch (IOException ioe) {
     
 throw new RuntimeException(ioe); 
} finally {
     
 // Part 4 
 releaseEncoder(); 
} 

第1部分是准备工作,除了video的MediaCodec,这里还初始化了audio的MediaCodec:

MediaFormat audioFormat = new MediaFormat(); 
audioFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100); 
audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); 
... 
mAudioEncoder = MediaCodec.createEncoderByType(AUDIO_MIME_TYPE); 
mAudioEncoder.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 
mAudioEncoder.start(); 

第2部分进入主循环,app在Surface上直接绘图,由于这个Surface是从MediaCodec中用createInputSurface()申请来的,所以画完后不用显式用queueInputBuffer()交给Encoder。drainVideoEncoder()和drainAudioEncoder()分别将编码好的音视频从buffer中拿出来(通过dequeueOutputBuffer()),然后交由MediaMuxer进行混合(通过writeSampleData())。注意音视频通过PTS(Presentation time stamp,决定了某一帧的音视频数据何时显示或播放)来同步,音频的time stamp需在AudioRecord从MIC采集到数据时获取并放到相应的bufferInfo中,视频由于是在Surface上画,因此直接用dequeueOutputBuffer()出来的bufferInfo中的就行,最后将编码好的数据送去MediaMuxer进行多路混合。

注意这里Muxer要等把audio track和video track都加入了再开始。MediaCodec在一开始调用dequeueOutputBuffer()时会返回一次INFO_OUTPUT_FORMAT_CHANGED消息。我们只需在这里获取该MediaCodec的format,并注册到MediaMuxer里。接着判断当前audio track和video track是否都已就绪,如果是的话就启动Muxer。

总结来说,drainVideoEncoder()的主逻辑大致如下,drainAudioEncoder也是类似的,只是把video的MediaCodec换成audio的MediaCodec即可。

while(true) {
     
 int encoderStatus = mVideoEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC); 
 if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
     
 ... 
 } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
     
 encoderOutputBuffers = mVideoEncoder.getOutputBuffers(); 
 } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
     
 MediaFormat newFormat = mAudioEncoder.getOutputFormat(); 
 mAudioTrackIndex = mMuxer.addTrack(newFormat); 
 mNumTracksAdded++; 
 if (mNumTracksAdded == TOTAL_NUM_TRACKS) {
     
 mMuxer.start(); 
 } 
 } else if (encoderStatus < 0) {
     
 ... 
 } else {
     
 ByteBuffer encodedData = encoderOutputBuffers[encoderStatus]; 
 ... 
 if (mBufferInfo.size != 0) {
     
 mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo); 
 } 
 mVideoEncoder.releaseOutputBuffer(encoderStatus, false); 
 if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
     
 break; 
 } 
 } 

} 

第3部分是结束录制,发送EOS信息,这样在drainVideoEncoder()和drainAudioEncoder中就可以根据EOS退出内循环。第4部分为清理工作。把audio和video的MediaCodec,MediaCodec用的Surface及MediaMuxer对象释放。

最后几点注意: 1. 在AndroidManifest.xml里加上录音权限,否则创建AudioRecord对象时铁定失败: 2. 音视频通过PTS同步,两个的单位要一致。 3. MediaMuxer的使用要按照Constructor -> addTrack -> start -> writeSampleData -> stop 的顺序。如果既有音频又有视频,在stop前两个都要writeSampleData()过。

总结

以上就是抖音类APP的部分内容,其中的步骤和过程是我亲自实践过的,按照上述的过程应该都可以正常运行,写这一篇文章花了很多时间,希望所有看了这篇文章的朋友们都能够有一定的收获。

此外关于更多音视频的学习资料可以扫描下方二维码免费领取资料!

《Android音视频精编源码解析》

第一章 WebRTC Native 源码导读

  • 安卓相机采集实现分析
  • 安卓预览实现分析
  • 安卓视频硬编码实现分析 VideoCRE 与内存抖动优化
  • 安卓 P2P 连接过程和DataChannel 使用 视频数据 native 层之旅
  • 混音 P2P 连接过程完全解析 API 概览
  • RTP H.264 封包与解包

在这里插入图片描述

第二章 X264源码解读

  • 概述 x264命令行工具
  • 编码器主干部分-2 x264_
  • slice_write() 滤波(Filter)部分
  • 宏块分析(Analysis)部分-帧内宏块(Intra)

在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_47933729/article/details/131659692

智能推荐

使用Docker在windows上安装IBM MQ_win系统中,可视化工具连接其他服务上的ibmmq-程序员宅基地

文章浏览阅读859次,点赞18次,收藏8次。Python编程学习,学习内容包含:语法、正则、文件、 网络、多线程等常用库,推荐《Python核心编程》,不要看完;在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定的编程能力。恭喜你,如果学到这里,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web 渗透、安全服务、安全分析等岗位;③漏洞扫描、漏洞利用、原理,利用方法、工具(MSF)、绕过IDS和反病毒侦察。

软考—系统集成管理工程师备考经验_信息系统管理工程师真题百度云-程序员宅基地

文章浏览阅读362次,点赞6次,收藏8次。关于软考--系统集成管理工程师的学习总结和教训_信息系统管理工程师真题百度云

淘宝/天猫自定义API操作 API接口,custom-自定义API操作-程序员宅基地

文章浏览阅读887次,点赞24次,收藏23次。淘宝/天猫平台本身并不直接提供“自定义API操作”的官方API接口。API接口通常是由平台方定义和提供的,用于开发者与平台进行数据交互。然而,淘宝/天猫开放平台允许商家和开发者通过其提供的官方API进行一系列的操作,这些API覆盖了商品管理、订单处理、用户信息、物流查询等多个方面。您可以利用淘宝/天猫开放平台提供的官方API,通过组合多个API调用,来实现您自定义的业务逻辑。这可能需要一定的编程能力和对平台API的深入理解。

Ubuntu 16.04简易安装Nginx-rtmp-module_libnginx-mod-rtmp_1.22.0-1ubuntu3_amd64.deb-程序员宅基地

文章浏览阅读3.7k次,点赞2次,收藏5次。Ubuntu 16.04简易安装Nginx-rtmp-modulelibnginx-mod-rtmp是18.04上自带的,可以通过apt-get install libnginx-mod-rtmp进行安装,在16.04上如果想要安装,直接下载libnginx-mod-rtmp_1.14.0-0+xenial1_amd64.deb安装的话会被告知nginx版本过低,依赖有问题,需要16.04自带的..._libnginx-mod-rtmp_1.22.0-1ubuntu3_amd64.deb

mysql 字符 1024个字符限制 cast转为varchar 不限制字符长度 最大字符长度 group_concat长度限制_mysql 改变输出字符串最大长度-程序员宅基地

文章浏览阅读1.1k次。设置group_concat的最大长度然后再运行。_mysql 改变输出字符串最大长度

使用 Vite 和 Electron 进行开发 - 解决问题与经验分享_electron vite 原生模块处理-程序员宅基地

文章浏览阅读145次。Vite 是一个现代化的前端构建工具,它通过利用 ES 模块原生支持(ES Module)来提供快速的冷启动时间和热模块替换(HMR)能力。Electron 是一个流行的桌面应用程序开发框架,它允许使用 Web 技术(HTML、CSS 和 JavaScript)构建跨平台的桌面应用程序。结合使用 Vite 和 Electron 可以带来许多好处,例如快速的开发周期、模块化的架构和跨平台的能力。然而,在使用这两个工具时,可能会遇到一些挑战和问题。接下来,我们将讨论一些常见的问题,并提供相应的解决方案。_electron vite 原生模块处理

随便推点

[Flutter翻译]GSoC ‘21:为Flutter创建一个桌面样本_flutter 桌面模板(1)-程序员宅基地

文章浏览阅读815次,点赞16次,收藏29次。两个主要的东西是能够从现有的analysis_options.yaml文件中加载配置文件,以及在规则列表中搜索特定规则的能力。经过与他和组织管理员的讨论,我找到了一个可以工作的项目。经过与Brett和团队的讨论,我们决定建立一个桌面样本,同时也是一个工具,帮助开发者管理他们项目的lint规则。今年,在Flutter Engage上,Flutter的桌面支持的测试版快照被纳入了稳定频道。你可以为不同类型的项目创建不同的规则配置文件。不幸的是,由于导师的不到位,今年的。这个博客显示了我为我的项目所做的工作。

ARC/OC对象自动管理内存_arc oc-程序员宅基地

文章浏览阅读1w次。ARC是一个编译器特征,它提供了对OC对象自动管理内存。ARC让开发者专注于感兴趣的代码和对象的关系,而不用考虑对象的retain和release。转自hherima的博客原文:Transitioning to ARC Release Notes(苹果官方文档) ARC是一个编译器特征,它提供了对OC对象自动管理内存。ARC让开发者专注于感兴趣的代码和对象的关系_arc oc

JAVA设计模式(09):结构型-代理模式(Proxy)_pengzhile 是谁-程序员宅基地

文章浏览阅读5.8k次。代理模式是常用的结构型设计模式之一,当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口。根据代理模式的使用目的不同,代理模式又可以分为多种类型,例如保护代理、远程代理、虚拟代理、缓冲代理等,它们应用于不同的场合,满足用户的不同需求。1 代理模式概述近年来,代购已逐步成为电_pengzhile 是谁

C语言:结构体,枚举,联合_c语言使用枚举结构体实现计算器程序-程序员宅基地

文章浏览阅读347次。一.结构体类型创建结构体是一些值的集合,这些值称为成员变量。结构体的每一个成员可以是不同类型的变量。结构体的声明struct tag{ member-list;}variable-list;举例: 描述一个学生:struct Stu{ char name[20];//名字 int age;//年龄 char sex[5];//性别 cha..._c语言使用枚举结构体实现计算器程序

【图解UDS】UDS汽车诊断标准协议(ISO 14229)带你入门到精通_uds诊断协议-程序员宅基地

文章浏览阅读8.3w次,点赞285次,收藏1.8k次。【图解UDS】UDS汽车诊断标准协议(ISO 14229)带你入门到精通目录0 前言1 诊断的基本概念2 UDS诊断诊断协议2.1 诊断服务的概念2.2 诊断会话控制0x10服务2.3 会话访问0x27服务2.4 用于读/写的DID的0x22/0x2E服务2.5 故障存储相关的0x19和0x14服务..._uds诊断协议

Mysql数据库渗透及漏洞利用总结_利用mysql注入可以获取到phpmyadmin权限吗-程序员宅基地

文章浏览阅读3.9k次。Mysql数据库是目前世界上使用最为广泛的数据库之一,很多著名公司和站点都使用Mysql作为其数据库支撑,目前很多架构都以Mysql作为数据库管理系统,例如LAMP、和WAMP等,在针对网站渗透中,很多都是跟Mysql数据库有关,各种Mysql注入,Mysql提权,Mysql数据库root账号webshell获取等的,但没有一个对Mysql数据库渗透较为全面对总结,针对这种情况我们开展了研究,虽然..._利用mysql注入可以获取到phpmyadmin权限吗

推荐文章

热门文章

相关标签