己所不欲勿施于人
-
Android Camera系列(一):SurfaceView+Camera
-
Android Camera系列(二):TextureView+Camera
-
Android Camera系列(三):GLSurfaceView+Camera
-
Android Camera系列(四):TextureView+OpenGL ES+Camera
-
Android Camera系列(五):Camera2
-
Android Camera系列(六):MediaCodec视频编码上-编码YUV
-
Android Camera系列(七):MediaCodec视频编码中-OpenGL ES多线程渲染
-
Android Camera系列(八):MediaCodec视频编码下-OpenGL ES离屏渲染
本系列主要讲述Android开发中Camera的相关操作、预览方式、视频录制等。项目结构简单、代码耦合性低,旨在帮助大家能从中有所收获(方便copy :) ),对于个人来说也是一个总结的好机会。
引言
前面我们已经花费了大量的章节来讲解Camera配合各种View用来预览、拍照等,接下来我们来讲解如何进行视频录制。Android中要进行录制视频有很多种方式,如:使用系统相机录制、MediaRecorder+Camera自定义录制等。如果上面的方式能够满足你的需求,那本章你可以划走了。
假如有这么一种需求,我们要获取Camera的帧数据流YUV进行实时检测识别,同时我又要将检测到的数据进行保存,那么MediaCodec就很合适,MediaCodec(硬编解码器)主要对帧数据进行编解码处理。实际的例子就好比抖音拍摄视频,人实时美颜或贴图后将视频保存。
除了可以使用MediaCodec对数据流进行编解码,还有ffmpeg同样也可以。
- MediaCodec属于硬解码,处理数据使用GPU效率高,功能受硬件限制兼容性略低。
- FFmpeg属于软解码,处理数据使用CPU效率低,功能强大兼容性好。当然该方式不在我们本章讨论范围
MediaCodec介绍
MediaCodec是Android平台上的一个多媒体编解码器,它可以用于对音频和视频进行编解码。通过MediaCodec,开发者可以直接访问底层的编解码器,实现更高效的音视频处理。同时,MediaCodec也支持硬件加速,可以利用设备的硬件资源来提高编解码的性能。MediaCodec主要应用于以下几个方面:
- 音视频编解码:MediaCodec可以对音频和视频进行硬件加速的编解码处理,可以实现高效的音视频处理和播放。
- 多媒体格式支持:支持常见的音视频格式,包括H.264、AAC、MP3等,可以进行解码和编码操作。
- 硬件加速:利用设备的硬件加速功能,可以提高音视频处理的效率和性能。
- 实时处理:支持实时的音视频处理,适用于实时通信、直播等场景。
- 自定义处理:可以通过MediaCodec进行自定义的音视频处理,如滤镜、特效等操作。
MediaCodec在Android平台上提供了强大的音视频编解码功能,可以用于多媒体应用的开发和优化。
硬编码流程
- 使用
MediaCodec
,将yuv数据编码获得h264数据 MediaMuxer
用来封装h264获得一个mp4文件
MediaCodec使用
1. 创建MediaCodec
通过静态方法创建:createDecoderByType
、createEncoderByType
、createByCodecName
使用 createDecoderByType
、createEncoderByType
两个,创建用于解码和编码的实体,传入参数为编解码器的mime type名称。createByCodecName
需要传入具体编解码器的名称。
官方建议:最好使用MediaCodecList.findEncoderForFormat和createByCodecName来确保生成的编解码器可以处理给定的格式。
mime可用的类型:
* "video/x-vnd.on2.vp8" - VP8 video (i.e. video in .webm)
* "video/x-vnd.on2.vp9" - VP9 video (i.e. video in .webm)
* "video/avc" - H.264/AVC video
* "video/hevc" - H.265/HEVC video
* "video/mp4v-es" - MPEG4 video
* "video/3gpp" - H.263 video
* "audio/3gpp" - AMR narrowband audio
* "audio/amr-wb" - AMR wideband audio
* "audio/mpeg" - MPEG1/2 audio layer III
* "audio/mp4a-latm" - AAC audio (note, this is raw AAC packets, not packaged in LATM!)
* "audio/vorbis" - vorbis audio
* "audio/g711-alaw" - G.711 alaw audio
* "audio/g711-mlaw" - G.711 ulaw audio
最常用的是 : “video/avc” - H.264/AVC video,现在网络上流传的短视频的视轨格式基本上都是H264。
private static final String MIME_TYPE = "video/avc";
// 创建MediaCodec
MediaCodec mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
2. 配置MediaCodec
实例化MediaCodec后,我们需要配置一些参数如:视频录制的宽高、码率、帧率等信息。配置使用的类是MediaFormat
调用MediaCodec
的configure
方法:
public void configure(
MediaFormat format,
Surface surface,
MediaCrypto crypto,
int flags
);
- MediaFormat format:输入数据的格式(解码器)或输出数据的所需格式(编码器)。
- Surface surface:指定surface,一般用于解码器,设置后解码的内容会被渲染到所指定的surface上。无需要则传null
- MediaCrypto crypto:指定一个crypto对象,用于对媒体数据进行安全解密。对于非安全的编解码器,传null。
- int flags:当组件是编码器时,flags指定为常量CONFIGURE_FLAG_ENCODE。
后面三个参数基本是固定的,基本不需要变化。
第一个参数MediaFormat是需要我们深入去了解下,解码的情况下,该参数大多通过MediaExtractor从视频中获取,这里先不讨论。编码的情况实体需要由我们来进行创建,常用的方法是它的静态方法:createVideoFormat
、createAudioFormat
。
以视频为例:
MediaFormat format = MediaFormat.createVideoFormat(MIME,width, height);
- MIME:第一步中的mine_type
- width,height:视频的分辨率/高宽
MediaFormat 必须设置的参数:YUV编码格式、比特率、帧率、关键帧间隔,不设置会报异常。
// 设置编码yuv格式
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
// 设置比特率
format.setInteger(MediaFormat.KEY_BIT_RATE, 1 * 1024 * 1024);
// 设置帧率
format.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
// 设置关键帧间隔,单位:秒
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
需要特別说明的是MediaFormat.KEY_COLOR_FORMAT :该属性用于指明video编码器的颜色格式,具体选择哪种颜色格式与输入的视频数据源颜色格式有关。
比如:Camera预览采集的图像流通常为NV21或YV12,那么编码器需要指定相应的颜色格式,否则编码得到的数据可能会出现花屏、叠影、颜色失真等现象。
MediaCodecInfo.CodecCapabilities.存储了编码器所有支持的颜色格式,常见颜色格式映射如下:
// 最常用的
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar, // 19, I420,测试通过
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar, // 21, NV12,测试通过
// 其他不常用
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar, // 20, I420,测试通过
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar, // 39, NV12, 测试通过
YUV各种格式还不熟悉的,请看下面的说明:
- YUV420p: I420、YV12
- YUV420sp: NV12、NV21
同样,对于一个6*4的图像,这四种像素格式的存储方式如下:
Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
U U U U U U V V V V V V U V U V U V V U V U V U
V V V V V V U U U U U U U V U V U V V U V U V U
- I420 - - YV12 - - NV12 - - NV21 -
虽然MediaCodec支持了多种YUV编码格式,而且代码中也有对应的值,但是正如我们引言中说的MediaCodec兼容性不好。支持哪种硬编码格式需要根据硬件来定,这也是大多数初入MediaCodec的人来说最头疼的地方。
3. 选择合适的编码器
MediaCodec都知道是硬编解码器,其实不然。他也是支持软编解码的,软编解码由Google实现,硬编解码由厂商自定义。这也就是兼容性的根源,因为我们不知道厂商是否真的支持硬编解码。我们只能自己查找合适的编解码器。而每种编解码器支持编码的YUV格式又不尽相同,我们是优先选择编码器还是优先选择YUV格式这是我们需要抉择的。
看过大部分的博文讲解都是优先选择编码器,而一旦我们选择的编码器后,就只能使用编码器中支持的YUV格式进行编码,如果发现YUV格式不是我们想要的,那么就会编码失败。而我们这里优先根据YUV格式来选择编码器,这么做的理由是有更好的兼容性,但是可能会牺牲一部分性能。
public class MediaVideoBufferEncoder extends MediaEncoder implements IVideoEncoder {
private static final String MIME_TYPE = "video/avc";
protected int mColorFormat;
...
/**
* select the first codec that match a specific MIME type
*
* @param mimeType
* @return null if no codec matched
*/
@SuppressWarnings("deprecation")
protected final MediaCodecInfo selectVideoCodec(final String mimeType) {
if (DEBUG) Log.v(TAG, "selectVideoCodec:");
// get the list of available codecs
final int numCodecs = MediaCodecList.getCodecCount();
// 硬编码器列表
List<MediaCodecInfo> hardwareCodecInfoList = new ArrayList<>();
// 软编码器列表
List<MediaCodecInfo> softwareCodecInfoList = new ArrayList<>();
for (int i = 0; i < numCodecs; i++) {
final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
if (!codecInfo.isEncoder()) {
// skipp decoder
continue;
}
/**
* 硬件编解码器
* OMX.qcom.video.encoder.heic
* OMX.qcom.video.decoder.avc
* OMX.qcom.video.decoder.avc.secure
* OMX.qcom.video.decoder.mpeg2
* OMX.google.gsm.decoder
* OMX.qti.video.decoder.h263sw
* c2.qti.avc.decoder
*
* 软件编解码器,通常以OMX.google或者c2.android开头
* OMX.google.h264.encoder
* c2.android.aac.decoder
* c2.android.aac.decoder
* c2.android.aac.encoder
* c2.android.aac.encoder
* c2.android.amrnb.decoder
* c2.android.amrnb.decoder
*/
// select first codec that match a specific MIME type and color format
final String[] types = codecInfo.getSupportedTypes();
for (int j = 0; j < types.length; j++) {