我正在参加「掘金·启航计划」
本文主要介绍一些音视频方面的编解码知识,包括H264编解码的格式,Android MediaCodec编解码的信息。
1. H264码流分为两种格式:AnnexB和AVCC格式。
1.1 AnnexB格式:
[start code]NALU | [start code]NALU | [start code]NALU
每个帧前面都有0x00 00 00 01 或者0x00 00 01作为起始码。SPS和PPS作为一类NALU存储在码流中,
一般在码流的最前面。
1.2 AVCC格式:
([extradata]) | ([length]NALU) | ([length]NALU)
没有起始码,每帧最前面的4个字节表示帧长度,这种格式中的NALU一般没有SPS和PPS等参数信息,参数
信息存储在extradata中。在ffmpeg中解析Mp4文件后,sps和pps信息存储在avStream->codecpar->extradata
中。
1.3 sps和pps
在FFMPEG的extradata数组中包含了sps和pps的数据,数据由一些固定参数、sps和pps的相关数据计算得出,
第1字节:version (通常0x01)
第2字节:avc profile (值同第1个sps的第2字节,sps[1],如果是sps组成的NALU,那就是sps[4+1])
第3字节:avc compatibility (值同第1个sps的第3字节,sps[2]或者sps[4+2])
第4字节:avc level (值同第1个sps的第3字节,sps[3]huozhe sps[4+3])
//通常在AVCC格式的码流中每个NALU单元使用四个字节表示长度,所以这里一般表示为:0xFC | 3
第5字节前6位:保留全1
第5字节后2位:NALU Length 字段大小减1,通常这个值为3,即NAL码流中使用3+1=4字节表示NALU的长度
// 这里一般默认为1,使用0xE0 | 1表示
第6字节前3位:保留,全1
第6字节后5位:SPS NALU的个数,通常为1
// 采用两个字节,16位来表示sps的长度,可以用[7]=(spslen>>8)&0x00ff,[8]=spslen & 0x00ff来表示
第7字节、第8字节:SPS结构 [16位 SPS长度][SPS NALU data]
// 后面拼接sps的数据
sps data
// 后面接入pps的相关参数
// 表示pps的个数,一般为1,用[8+spslen+1]=0x01显示
第[8+spslen+1]字节:PPS的个数,通常为1
// 采用两个字节,16位表示pps的长度,[8+spslen+2]=(ppslen >>8)&0x00ff、[8+spslen+3] = ppslen & 0x00ff
第[8+spslen+2]、[8+spslen+3]字节:
PPS结构 [16位 PPS长度][PPS NALU data]
// 后面拼接pps的数据
pps data
1.4 H264的打包方式:
AVCC和Annex-B,MediaCodec就是使用Annex-B。
2. MediaCodec
通过MediaCodec提供的硬编解码能力,来实现音视频的处理。下面是MediaCodec编解码相关联的一些类信息,如MediaFormat、MediaExtractor等
2.1 MediaFormat
描述媒体数据格式的信息,分为音频和视频相关。
音频
// mime 音频格式
// sampleRate 采样率
// channelCount 通道数
public static final @NonNull MediaFormat createAudioFormat(
@NonNull String mime,
int sampleRate,
int channelCount) {
MediaFormat format = new MediaFormat();
format.setString(KEY_MIME, mime);
format.setInteger(KEY_SAMPLE_RATE, sampleRate);
format.setInteger(KEY_CHANNEL_COUNT, channelCount);
return format;
}
音频格式主要使用的是:
String MIMETYPE_AUDIO_AAC = "audio/mp4a-latm"
采样率一般使用44.1HZ,通道数分为单通道和双通道。
在配置音频相关的MediaFormat时,比特率是一定要配置的。比特率也可以叫做码率,指的是单位时间内的数据传输速率,单位以秒来计算。码率越高音频的质量就越高,但同时体积也会增大。
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, config.audioBitrate.toInt())
视频
// videoType
// 底层支持的宽高来设置
val format = MediaFormat.createVideoFormat(MIME_TYPE, config.width, config.height)
//指定编码器的颜色格式
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
//指定比特率
format.setInteger(MediaFormat.KEY_BIT_RATE, config.videoBitrate.toInt())
//指定帧率
format.setFloat(MediaFormat.KEY_FRAME_RATE, config.fps)
//指定关键帧的间隔时间,设置为2s
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2)
videoType一般来说常用的就两种
//H264
String MIMETYPE_VIDEO_AVC = "video/avc"
//H265
String MIMETYPE_VIDEO_HEVC = "video/hevc"
帧率指的是每秒钟包含多少张画面,单位为fps,如25fps。
2.2 MediaExtractor
从数据源提取编码后的媒体数据,即音视频解析,分离。 具体的使用:
// 创建一个MediaExtractor对象并设置数据源
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(...);
// 获取媒体资源中的轨道数,一般包括视轨、两个音轨、字幕轨
int numTracks = extractor.getTrackCount();
for (int i = 0; i < numTracks; ++i) {
// 根据下标获取对应的MediaFormat,内部包含媒体数据的信息
MediaFormat format = extractor.getTrackFormat(i);
//mime表示轨道的信息,比如video、audio
String mime = format.getString(MediaFormat.KEY_MIME);
//判断是需要的轨道,则选中该轨道后续解析
if (weAreInterestedInThisTrack) {
extractor.selectTrack(i);
}
}
//获取一个Buffer对象,一般是通过MediaCodec获取对应的队列中的Buffer
ByteBuffer inputBuffer = ByteBuffer.allocate(...)
//解析数据到Buffer中,大于0表示解析成功
while (extractor.readSampleData(inputBuffer, ...) >= 0) {
int trackIndex = extractor.getSampleTrackIndex();
//返回当前数据的显示时间,以微秒为单位
long presentationTimeUs = extractor.getSampleTime();
...
//移动到下一个解析点,读取下一帧数据v
extractor.advance();
}
//释放对象
extractor.release();
extractor = null;
2.3 编解码器特定的数据(Codec-specific Data)
一些格式比如AAC、H264等编解码器处理数据时,需要添加特定的数据作为缓冲区。比如H264需要sps和pps信息,AAC需要ADTS信息等。
val sps = byteArrayOf(0, 0, 0, 1, 103, 66, 0, 41, -115, -115, 64, 80, 30, -48, 15, 8, -124, 83, -128)
mediaFormat.setByteBuffer("csd-0", ByteBuffer.wrap(sps))
val pps = byteArrayOf(0, 0, 0, 1, 104, -54, 67, -56)
mediaFormat.setByteBuffer("csd-1",ByteBuffer.wrap(pps))
2.4 createInputSurface
在MediaCodec中通过createInputSurface
可以创建一个Surface作为输入源.