|
|
51CTO旗下网站
|
|
移动端

刷抖音看美腿中毒后,我决定做一款抖音App

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

作者:十年开发来源:51CTO博客|2019-06-21 09:55

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

短视频内容生产

优质短视频内容的产生依赖于短视频的采集和特效编辑,这就要求在进行抖音 App 开发时,用到基础的美颜、混音、滤镜、变速、图片视频混剪、字幕等功能。

在这些功能基础上,进行预处理,结合 OpenGL、AI、AR 技术,产生很多有趣的动态贴纸玩法,使得短视频内容更具创意。

视频录制的大致实现流程是先由 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语言些的音视频编解码程序,使用起来不是很方便。

虽然从数据看来 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 更合适)等。

它们一个是生成视频,一个生成音频,这里把它们结合一下,同时生成音频和视频。

基本框架和流程如下:

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

  1. AudioRecord audio_recorder;  
  2. audio_recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,  
  3.  SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, buffer_size);  
  4. // ...  
  5. audio_recorder.startRecording();  
  6. while (is_recording) {  
  7.  byte[] this_buffer = new byte[frame_buffer_size];  
  8.  read_result = audio_recorder.read(this_buffer, 0, frame_buffer_size); // read audio raw data  
  9.  // …  
  10.  presentationTimeStamp = System.nanoTime() / 1000;  
  11.  audioEncoder.offerAudioEncoder(this_buffer.clone(), presentationTimeStamp); // feed to audio encoder  
  12.  
  13. }  

这里也可以设置 AudioRecord 的回调(通过 setRecordPositionUpdateListener())来触发音频数据的读取。

offerAudioEncoder() 里主要是把 Audio 采样数据送入音频 MediaCodec 的 InputBuffer 进行编码:

  1. ByteBuffer[] inputBuffers = mAudioEncoder.getInputBuffers();  
  2. int inputBufferIndex = mAudioEncoder.dequeueInputBuffer(-1);  
  3. if (inputBufferIndex >= 0) {  
  4.  ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];  
  5.  inputBuffer.clear();  
  6.  inputBuffer.put(this_buffer);  
  7.  ...  
  8.  mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, this_buffer.length, presentationTimeStamp, 0);  
  9. }  

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

  1. try {  
  2.  // Part 1  
  3.  prepareEncoder(outputFile);  
  4.  ...  
  5.  // Part 2  
  6.  for (int i = 0; i < NUM_FRAMES; i++) {  
  7.  generateFrame(i);  
  8.  drainVideoEncoder(false);  
  9.  drainAudioEncoder(false);  
  10.  }  
  11.  // Part 3  
  12.  ...  
  13.  drainVideoEncoder(true);  
  14.  drainAudioEncoder(true);  
  15. } catch (IOException ioe) {  
  16.  throw new RuntimeException(ioe);  
  17. } finally {  
  18.  // Part 4  
  19.  releaseEncoder();  
  20. }  

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

  1. MediaFormat audioFormat = new MediaFormat();  
  2. audioFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);  
  3. audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);  
  4. ...  
  5. mAudioEncoder = MediaCodec.createEncoderByType(AUDIO_MIME_TYPE);  
  6. mAudioEncoder.configure(audioFormat, nullnull, MediaCodec.CONFIGURE_FLAG_ENCODE);  
  7. 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 即可:

  1. while(true) {  
  2.  int encoderStatus = mVideoEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);  
  3.  if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {  
  4.  ...  
  5.  } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {  
  6.  encoderOutputBuffers = mVideoEncoder.getOutputBuffers();  
  7.  } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {  
  8.  MediaFormat newFormat = mAudioEncoder.getOutputFormat();  
  9.  mAudioTrackIndex = mMuxer.addTrack(newFormat);  
  10.  mNumTracksAdded++;  
  11.  if (mNumTracksAdded == TOTAL_NUM_TRACKS) {  
  12.  mMuxer.start();  
  13.  }  
  14.  } else if (encoderStatus < 0) {  
  15.  ...  
  16.  } else {  
  17.  ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];  
  18.  ...  
  19.  if (mBufferInfo.size != 0) {  
  20.  mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo);  
  21.  }  
  22.  mVideoEncoder.releaseOutputBuffer(encoderStatus, false);  
  23.  if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {  
  24.  break;  
  25.  }  
  26.  }  
  27.  
  28. }  

第 3 部分是结束录制,发送 EOS 信息,这样在 drainVideoEncoder() 和 drainAudioEncoder 中就可以根据 EOS 退出内循环。

第 4 部分为清理工作。把 audio 和 video 的 MediaCodec,MediaCodec 用的 Surface 及 MediaMuxer 对象释放。

***几点注意:

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

总结

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

【编辑推荐】

  1. 一篇文章了解H5打开APP的诸多方案
  2. 全新重构,uni-app实现微信端性能翻倍
  3. ACM 国际大学生程序设计竞赛决赛在即 快手APP全程直播
  4. 5G到来,App的未来,是JavaScript,Flutter还是Native ?
  5. GitHub五万星中文资源:命令行技巧大合集,新老司机各取所需
【责任编辑:武晓燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

订阅专栏+更多

这就是5G

这就是5G

5G那些事儿
共15章 | armmay

115人订阅学习

16招轻松掌握PPT技巧

16招轻松掌握PPT技巧

GET职场加薪技能
共16章 | 晒书包

371人订阅学习

20个局域网建设改造案例

20个局域网建设改造案例

网络搭建技巧
共20章 | 捷哥CCIE

758人订阅学习

读 书 +更多

Scrum敏捷项目管理

本书详细描述如何在复杂技术项目中使用Scrum,并结合真实的Scrum案例及专家洞识,在简明及高度概括的理论之上更侧重于实践,并不断强调Scru...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO官微