上篇博客传送门:Android音视频学习笔记(一)
Camera视频预览
Camera是官方提供的相机相关的api,官方文档如下:
https://developer.android.com/reference/android/hardware/Camera
如果是api 21以上的版本,可以使用Camera2,官方文档如下:
https://developer.android.com/reference/android/hardware/camera2/package-summary.html
xml布局文件较为简单,这里不再贴出
CameraActivity相关代码如下:
1 | public class CameraActivity extends AppCompatActivity implements SurfaceHolder.Callback { |
在这个Activity中,我们使用了SurfaceView来显示Camera提供的预览数据
有兴趣的童鞋也可以使用TextureView来代替实现相应的功能
SurfaceView官方文档如下:
https://developer.android.com/reference/android/view/SurfaceView
TextureView官方文档如下:
https://developer.android.com/reference/android/view/TextureView
二者的区别可以参考下面的博客:
https://www.cnblogs.com/wytiger/p/5693569.html
https://www.jianshu.com/p/b9a1e66e95ea
简单来说,一般而言,如果我们不需要对视频容器的View进行一些动画或者平移翻转等变化的话,可以优先考虑使用SurfaceView(消耗更低内存,减少绘制延迟)
有几个地方值得我们稍微注意一下:
- Camera返回的数据可能是旋转过后的,一般我们需要使用Camera#setDisplayOrientation旋转90度摆正
- Camera预览时可能因为没有对焦而导致视频模糊,我们可以使用Camera#setFocusMode,并传入Camera#Parameters#FOCUS_MODE_CONTINUOUS_VIDEO获得一个自动对焦的效果
采集视频YUV数据
YUV(亮度、明度、对比度)是视频最原始的数据,跟音频的PCM类似
参考上面的代码,我们可以使用Camera#setPreviewCallback来设置回调,获取相机预览回调的YUV数据
值得注意的是,我们需要通过Camera#setPreviewFormat,把预览回调的数据格式设置为ImageFormat#NV21
NV21是YUV420-Semeplanar格式中的一种,相关的介绍可以参考下面的文章:
https://blog.csdn.net/jk198310/article/details/79084283
输出H264格式
获取到以NV21格式表示的YUV数据后,我们就可以把它们编码压缩成H264格式了
相关代码如下:
1 | /** |
这里我们通过MediaCodec创建硬件编码器,并传入一些关键的参数来进行H264编码
官方的MediaCodec文档如下:
https://developer.android.com/reference/android/media/MediaCodec
传入的关键参数如下:
- 视频长宽:这个我们可以通过Camera#Parameters#getPreferredPreviewSizeForVideo来获取推荐的视频录制大小(长宽自己随便填可能会导致编码失败)
- MediaFormat#KEY_COLOR_FORMAT:YUV格式,我们使用的是NV21,但这里只支持NV12,因此我们传入MediaCodecInfo#CodecCapabilities#COLOR_FormatYUV420SemiPlanar
- MediaFormat#KEY_BIT_RATE:码率,一般使用的是 长 x 宽 x 一个系数(这里取的5),码率越高视频越大,也越清晰
- MediaFormat#KEY_FRAME_RATE:帧率,这个我们可以通过Camera#Parameters#getSupportedPreviewFpsRange来获取,在这个例子中我们则是写死了30
- MediaFormat#KEY_I_FRAME_INTERVAL:每秒关键帧数,例子填的是每秒1帧关键帧
值得注意的是,在Android 6.0中,通过MediaCodec#configure来设置关键帧在某些机型上会出现失效的情况,进而影响视频的编码
这时我们可以通过MediaCodec#setParameters来设置相应的关键帧参数,具体代码如下:
1 | Bundle bundle = new Bundle(); |
另外值得注意的有以下几个点:
- MediaCodec硬件编码器仅支持NV12格式,因此我们需要把Camera回调的NV21格式转为NV12格式,主要是U分量与V分量的对调,相关代码可见H264Encoder#NV21ToNV12
- 正如上面所说Camera预览时需要旋转90度摆正,而Camera回调回来的YUV数据是没有旋转的,因此在编码前我们需要旋转YUV数据,相关代码可见H264Encoder#NV12Rotate90
- 不同的手机上硬件编码效率不一致,相差甚远,有的手机非常快(存放数据的阻塞队列几乎不留任何数据),而有的则会囤积200多条数据(估计这也是用FFMPEG进行软编码的原因?)