图片 3

Android端的短视频开发,我们该如何快速实现移动端短视频功能?

Posted by

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

转载:

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

 

图片 1

Android中MediaMuxer和MediaCodec用例
在Android的多媒体类中,MediaMuxer和MediaCodec算
是比较年轻的,它们是JB 4.1和JB
4.3才引入的。前者用于将音频和视频进行混合生成多媒体文件。缺点是目前只能支持一个audio
track和一个video
track,而且仅支持mp4输出。不过既然是新生事物,相信之后的版本应该会有大的改进。MediaCodec用于将音视频进行压缩编码,它有个比较牛
X的地方是可以对Surface内容进行编码,如KK
4.4中屏幕录像功能就是用它实现的。

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

注意它们和其它一些多媒体相关类的关系和区别:MediaExtractor用于音视频分路,和MediaMuxer正好是反过程。
MediaFormat用于描述多媒体数据的格式。MediaRecorder用于录像+压缩编码,生成编码好的文件如mp4,
3gpp,视频主要是用于录制Camera
preview。MediaPlayer用于播放压缩编码后的音视频文件。AudioRecord用于录制PCM数据。AudioTrack用于播放
PCM数据。PCM即原始音频采样数据,可以用如vlc播放器播放。当然了,通道采样率之类的要自己设,因为原始采样数据是没有文件头的,如:
vlc –demux=rawaud
–rawaud-channels 2 –rawaud-samplerate 44100 audio.pcm

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

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

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

这里以一个很无聊的功能为例,就是在一个Surface上画图编码生成视频,同时用MIC录音编码生成音频,然后将音视频混合生成mp4文件。程序
本身没什么用,但是示例了MediaMuxer和MediaCodec的基本用法。本程序主要是基于两个测试程序:一个是Grafika中的
SoftInputSurfaceActivity和HWEncoderExperiments。它们一个是生成视频,一个生成音频,这里把它们结合一
下,同时生成音频和视频。基本框架和流程如下:

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

图片 2

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

首先是录音线程,主要参考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进行编码:

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

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);
}

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

下面,参考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();
}

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

第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();

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

第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进行多路混合。

图片 3

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

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

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

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

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;        
        }
    }

}

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

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

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

最后几点注意:
1.
在AndroidManifest.xml里加上录音权限,否则创建AudioRecord对象时铁定失败:
 <uses-permission
android:name=”android.permission.RECORD_AUDIO”/>
2.
音视频通过PTS同步,两个的单位要一致。

相关文章

Leave a Reply

电子邮件地址不会被公开。 必填项已用*标注