建设网站的费用属于,做网站的软件page,上市企业网站设计,如何做一个网页系列文章目录
ExoPlayer架构详解与源码分析#xff08;1#xff09;——前言 ExoPlayer架构详解与源码分析#xff08;2#xff09;——Player ExoPlayer架构详解与源码分析#xff08;3#xff09;——Timeline ExoPlayer架构详解与源码分析#xff08;4#xff09;—…系列文章目录
ExoPlayer架构详解与源码分析1——前言 ExoPlayer架构详解与源码分析2——Player ExoPlayer架构详解与源码分析3——Timeline ExoPlayer架构详解与源码分析4——整体架构 ExoPlayer架构详解与源码分析5——MediaSource ExoPlayer架构详解与源码分析6——MediaPeriod ExoPlayer架构详解与源码分析7——SampleQueue ExoPlayer架构详解与源码分析8——Loader ExoPlayer架构详解与源码分析9——TsExtractor ExoPlayer架构详解与源码分析10——H264Reader 文章目录 系列文章目录前言H264结构H264ReaderSPS的解析PPS的解析SEI的解析Slice的解析 总结 前言
TsExtractor解封完TS数据后会根据payload中的视频类型使用指定Reader继续解析如果payload是H.264格式就会使用H264Reader来继续解析PES payload部分视频数据流。先上下ProgressiveMediaPeriod的万年老图 这部分已经可以和SampQueue关联起来了也就是说图中sampleData的地方就发生在H264Reader中。
H264结构
在看代码前老规矩先简单了解下H264的码流结构 H264都是由一个个的NAL基本单元组成的每个NAL由包含一个HEADER和一个DATA如下图 这些基本的NAL可能为多种类型如上图的SPSPPSSLICE这些类型就定义在NAL的Header之中Header的结构很简单就一个字节如下表
名称大小(b)说明forbidden_zero_bit1禁止位占用NAL头的第一个位当禁止位值为1时表示语法错误告诉接收方丢掉该单元否则为0nal_ref_idc2指示当前NALU的优先级或者说重要性数值越大表明越重要nal_unit_type5表示NALU的类型
那么nal_unit_type不同值对应什么类型呢看下表
nal_unit_typeNAL类型0未使用1不分区、非 IDR 图像的片2SLICE A 片分区 A3SLICE B 片分区 B4SLICE C 片分区 C5IDR 图像中的片6Supplemental Enhancement Information(SEI ) 补充增强信息单元7Sequence Paramater Set(SPS) 序列参数集8Picture Paramater Set(PPS) 图像参数集9Access Unit Delimiter(AUD) 分界符10End Of Seq 序列结束11End Of Stream 码流结束12Filler Data 填充13…23保留24…31未使用
下面看下几个重要的unitType结构 Sequence Paramater Set(SPS) 序列参数集 SPS结构比较复杂这里挑几个用到的 名称大小(b)说明profile_idc8本视频编码时遵循的profileprofile分为BaselineMainExtended等主要用来规定编码时是否采用某些特性比如说Baseline profile就规定了只能使用I、P slice进行编码关于profile的说明可以去查看标准的Annex A。constraint_set0_flag1强制使用Baseline profile进行编码constraint_set1_flag1强制使用Main profile进行编码constraint_set2_flag1强制使用Extended profile进行编码level_idc8本视频遵循的levellevel主要规定了每秒最多能处理多少个宏块最大的帧大小最大的解码缓存最大比特率等这些性能相关的东西如果是硬解码则比较容易出现由于视频level太高而不能解码的情况。seq_parameter_set_idue(v)本SPS的ID这个ID主要是给PPS用的separate_colour_plane_flag1separate_colour_plane_flag 等于 1 表示对 4:4:4 色度格式中的三个色彩分量分别进行编码。 如果 separate_colour_plane_flag 的值为 0则表示不对色彩成分进行单独编码separate_colour_plane_flag 等于 1 时主编码图像由三个独立的分量组成每个分量由一个颜色平面Y、Cb 或 Cr的编码采样组成每个采样使用单色编码语法。在这种情况下每个色彩平面都与特定的 color_plane_id 值相关联log2_max_frame_num_minus4ue(v)指定了变量 MaxFrameNum 的值值范围应为 0 至 12含 12 M a x F r a m e N u m 2 ( l o g 2 m a x f r a m e n u m m i n u s 4 4 ) MaxFrameNum 2^{(log2maxframenumminus4 4)} MaxFrameNum2(log2maxframenumminus44)pic_order_cnt_typeue(v)指定解码图片顺序计数的方法,pic_order_cnt_type 的值范围应为 0 至 2含 2pic_width_in_mbs_minus1ue(v)图片宽度pic_height_in_map_units_minus1ue(v)图片高度frame_mbs_only_flag1是否只进行帧编码vui_parameters_present_flag1SPS是否包含vui参数 video usability information在标准的Annex E中有描述主要包含了视频的比例调整overscan视频格式timing比特率等信息aspect_ratio_info_present_flag1等于 1 表示存在 aspect_ratio_idc等于 0 表示不存在 aspect_ratio_idcaspect_ratio_idc8指定样本的采样纵横比值。当 aspect_ratio_idc 表示 Extended_SAR扩展 SAR时采样纵横比用 sar_width : sar_height 表示当没有 aspect_ratio_idc 语法元素时aspect_ratio_idc 值为 0sar_width16表示样本纵横比的水平尺寸sar_height16表示样本纵横比的垂直尺寸单位与 sar_width 相同ue(v)、se(v)表示以哥伦布编码的一种变长压缩算法 Picture Paramater Set(PPS) 图像参数集 这里也挑几个用到的讲下 名称大小(b)说明pic_parameter_set_idue(v)当前PPS的ID供slice RBSP使用seq_parameter_set_idue(v)当前PPS所属的SPS的IDbottom_field_pic_order_in_frame_present_flag1用于POC计算请参考h.264的POC计算中的bottom_field_flag Supplemental Enhancement Information(SEI ) 补充增强信息单元 集成在音视频码流中用于在音视频内部传递消息可以保证信息与直播音视频数据的同步SEI并不是解码过程的必须项有可能对解码过程容错、纠错有帮助视频传输过程、解封装、解码环节都可能因为某种原因丢弃SEI 在视频内容的生成端、传输过程中都可以插入SEI 信息。插入的信息和其他视频内容一起经过传输链路到达了消费端。那么在SEI 中可以添加哪些信息呢传递编码器参数、传递视频版权信息、传递摄像头参数、当然也可以传输字幕信息后面我们会看到。 Slice 视频中的一帧图像可以理解成由一个或多个Slice组成每一个Slice总体来看都由两部分组成 Slice header包含着分片类型、分片中的宏块类型、分片帧的数量以及对应的帧的设置和参数等信息slice body中的宏块在进行解码时需依赖这些信息 来看下Header 的结构 名称大小(b)说明first_mb_in_sliceue(v)当前slice中包含的第一个宏块在整帧中的位置slice_typeue(v)当前slice的类型参照下表pic_parameter_set_idue(v)当前slice所依赖的pps的id范围 0 到 255colour_plane_id2当标识位separate_colour_plane_flag为true时colour_plane_id表示当前的颜色分量0、1、2分别表示Y、U、V分量frame_numue(v)表示当前帧序号数据长度参考上面的log2_max_frame_num_minus4field_pic_flag1场编码标识位。当该标识位为1时表示当前slice按照场进行编码该标识位为0时表示当前 slice按照帧进行编码bottom_field_flag1底场标识位。该标志位为1表示当前slice是某一帧的底场为0表示当前slice为某一帧的顶场idr_pic_idue(v)表示IDR帧的序号。某一个IDR帧所属的所有slice其idr_pic_id应保持一致。该值的取值范围为[0,65535]。pic_order_cnt_lsbue(v)表示当前帧序号的另一种计量方式delta_pic_order_cnt_bottomse(v)表示顶场与底场POC差值的计算方法不存在则默认为0delta_pic_order_cnt[0]se(v)指定编码帧顶部字段的图片顺序计数与预期图片顺序计数的差值delta_pic_order_cnt[1]se(v)指定图像顺序计数与编码帧底层字段的预期图像顺序计数的差值 slice_typeName of slice_type0P (P slice)1B (B slice)2I (I slice)3SP (SP slice)4SI (SI slice)5P (P slice)6B (B slice)7I (I slice)8SP (SP slice)9SI (SI slice) Slice body通常是一组连续的宏块结构参照上图这里就是最终存储像素数据的地方了。宏块中还包含了宏块类型、预测类型、Coded Block Pattern、Quantization Parameter、像素的亮度和色度数据集等等信息。具体结构这个里不是重点不展开。 一个视频由多个帧组成一帧由多个Slice片组成一个Slice由多个宏块组成一个宏块又由多个如4X4的YUV像素数据组成。
看完了这些SPS、PPS、SLICE他们之间关系是怎么样的呢 Slice里的pic_parameter_set_id指向了PPS里的pic_parameter_set_id而PPS里的seq_parameter_set_id又指向了SPS序列参数集里的seq_parameter_set_id这样一个SPS关联多个PPS而一个PPS又关联了多个Slice解码器解码Slice时就通过这些ID查询相关的PPS、SPS获取解码所需的必要信息。
在网络传输流的过程中编码器可能会将每个NAL单元放入到单个独立的网络传输块中如TS中可能一个包中之包含一个NAL解码器可以很容易的检测出NAL的分界然后依次取出NAL来解码但是实际可能一个包里会包含一个PES头这个头后面跟随了多个NAL单元这种情况该如何找到这些NAL单元的分界呢?
很简单给NAL前添加0x000001头3个字节某些情况下会要求NAL长度对齐不足的部分填充0所以H.264规定当检测到0x000000这3个字节的时候也表示当前NAL结束这样感觉已经可以解决分界问题了。
但是如果NAL内部数据出现0x000001或者0x000000字段怎么办呢解码器会误以为这里是新的NAL的开始导致数据解码出错于是H.264规定了另一个规则 emulation prevention在编码器编码完一个NAL时会再去检测当前NAL中是否包含上述2种字节序列如果检测出则在最后一个字节前插入一个新字节0x03,当解码器在NAL内部检测到有0x000003 字节序列时就会把0x03丢弃恢复数据。
如0x000001 最后一个字节添加0x03 变成0x00000301解码器丢弃后又变成0x000001。 源码里的ParsableNalUnitBitArray 和NalUnitUtil.unescapeStream方法就是用来丢弃0x03的。
H264Reader
了解了上面的知识基本就可以开始看代码实现了这部分最好联系上文ExoPlayer架构详解与源码分析7——SampleQueue一起看。
Overridepublic void consume(ParsableByteArray data) {assertTracksCreated();int offset data.getPosition();int limit data.limit();byte[] dataArray data.getData();// 将当前数据长度计入总长度此时总数据的尾部和当前数据的尾部就是对齐的totalBytesWritten data.bytesLeft();//到这里已经是解复用后的数据了将数据发给SampleQueueoutput.sampleData(data, data.bytesLeft());// 循环读取到NAL单元结束while (true) {//通过判断是否为0x000001 3字节确定NAL开始位置prefixFlags用于保存上一次循环里的3字节信息防止目标字节被循环分割int nalUnitOffset NalUnitUtil.findNalUnit(dataArray, offset, limit, prefixFlags);if (nalUnitOffset limit) {// 读取到最后一个字节循环结束nalUnitData(dataArray, offset, limit);return;}// 知道起始位置后获取第四个字节后5位就是NAL的类型int nalUnitType NalUnitUtil.getNalUnitType(dataArray, nalUnitOffset);//获取NAL开始位置到当前位置的偏移量当NAL单元开始位置在上一段数据中时这个值为负值int lengthToNalUnit nalUnitOffset - offset;if (lengthToNalUnit 0) {//将当前位置到下一个NAL开始位置的数据输入nalUnitData(dataArray, offset, nalUnitOffset);}//用当前数据的结束位置-相对于当前数据的NAL开始位置得到就是当前NAL开始位置到当前数据的结束距离int bytesWrittenPastPosition limit - nalUnitOffset;//由于当前的结束位置和整个的结束位置是对齐的用整个数据的长度减轻到结尾的距离就是这个NAL相对于整个数据的绝对位置long absolutePosition totalBytesWritten - bytesWrittenPastPosition;// 如果到下一个单元开始的长度为负那么我们向 NAL 缓冲区写入了过多字节。当通知NAL结束时丢弃多余的字节。endNalUnit(absolutePosition,bytesWrittenPastPosition,lengthToNalUnit 0 ? -lengthToNalUnit : 0,pesTimeUs);// 下个NAL单元开始startNalUnit(absolutePosition, nalUnitType, pesTimeUs);// 从NAL单元开始位置读取3个字节offset nalUnitOffset 3;}}//结束NAL单元private void endNalUnit(long position, int offset, int discardPadding, long pesTimeUs) {if (!hasOutputFormat || sampleReader.needsSpsPps()) {sps.endNalUnit(discardPadding);pps.endNalUnit(discardPadding);if (!hasOutputFormat) {//保证只执行一次if (sps.isCompleted() pps.isCompleted()) {//sps和pps都已经endNalUnitListbyte[] initializationData new ArrayList();initializationData.add(Arrays.copyOf(sps.nalData, sps.nalLength));initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength));//解析出SPS数据NalUnitUtil.SpsData spsData NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength);//解析出PPS数据NalUnitUtil.PpsData ppsData NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.nalLength);//构建codecs 参数最终用于 MediaCodec 的 configure确定解码器String codecs CodecSpecificDataUtil.buildAvcCodecString(spsData.profileIdc,spsData.constraintsFlagsAndReservedZero2Bits,spsData.levelIdc);//通过SPS和PPS构建Format输出给SampleQueueoutput.format(new Format.Builder().setId(formatId).setSampleMimeType(MimeTypes.VIDEO_H264).setCodecs(codecs).setWidth(spsData.width).setHeight(spsData.height).setPixelWidthHeightRatio(spsData.pixelWidthHeightRatio).setInitializationData(initializationData).build());hasOutputFormat true;sampleReader.putSps(spsData);sampleReader.putPps(ppsData);sps.reset();pps.reset();}} else if (sps.isCompleted()) {NalUnitUtil.SpsData spsData NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength);sampleReader.putSps(spsData);sps.reset();} else if (pps.isCompleted()) {NalUnitUtil.PpsData ppsData NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.nalLength);sampleReader.putPps(ppsData);pps.reset();}}if (sei.endNalUnit(discardPadding)) {//丢弃0x03字节int unescapedLength NalUnitUtil.unescapeStream(sei.nalData, sei.nalLength);seiWrapper.reset(sei.nalData, unescapedLength);seiWrapper.setPosition(4); // NAL prefix and nal_unit() header.//解析SEI解析这部分主演是防止SEI中包含字幕信息将SEI中的字幕轨道提取出来seiReader.consume(pesTimeUs, seiWrapper);}boolean sampleIsKeyFrame sampleReader.endNalUnit(position, offset, hasOutputFormat, randomAccessIndicator);if (sampleIsKeyFrame) {//这要么是 IDR 帧要么是自随机访问指示符以来的第一个 I 帧因此将其标记为关键帧。清除该标志以便后续的非 IDR I 帧不会被标记为关键帧直到我们看到另一个随机访问指示符。randomAccessIndicator false;}}//sampleReader.endNalUnitpublic boolean endNalUnit(long position, int offset, boolean hasOutputFormat, boolean randomAccessIndicator) {if (nalUnitType NalUnitUtil.NAL_UNIT_TYPE_AUD//遇到一个AUD就sample一次Metadata|| (detectAccessUnits sliceHeader.isFirstVclNalUnitOfPicture(previousSliceHeader))) {// If the NAL unit ending is the start of a new sample, output the previous one.if (hasOutputFormat readingSample) {//Fromat未解析出也就是SPS PPS未解析完成跳过//position为当前AUD结束位置nalUnitLength 就是AUD长度一般都是5int nalUnitLength (int) (position - nalUnitStartPosition);//这里的offset 为当前AUD结尾到sampleData结尾的距离//offset nalUnitLength后相当于AUD开始位置到sampleData结尾的距离相当于当前Metadata 结束位置到SampleData结尾的距离outputSample(offset nalUnitLength);}samplePosition nalUnitStartPosition;//标记当前Metadata 开始位置sampleTimeUs nalUnitTimeUs;//标记当前Metadata 开始时间sampleIsKeyframe false;readingSample true;//标记当前Metadata 开始}boolean treatIFrameAsKeyframe allowNonIdrKeyframes ? sliceHeader.isISlice() : randomAccessIndicator;sampleIsKeyframe |nalUnitType NalUnitUtil.NAL_UNIT_TYPE_IDR|| (treatIFrameAsKeyframe nalUnitType NalUnitUtil.NAL_UNIT_TYPE_NON_IDR);return sampleIsKeyframe;}private void outputSample(int offset) {if (sampleTimeUs C.TIME_UNSET) {return;}C.BufferFlags int flags sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;//计算当前Metadata 的有效长度从第一个AUD开始到下一个AUD的开始位置长度int size (int) (nalUnitStartPosition - samplePosition);//将Metadata Sample计算Metadata 在SampleData中起始位置时就可以用SampleData总长度-Metadata 的长度-Metadata 结束位置到SampleData结尾的距离output.sampleMetadata(sampleTimeUs, flags, size, offset, null);}SPS的解析
public static SpsData parseSpsNalUnitPayload(byte[] nalData, int nalOffset, int nalLimit) {ParsableNalUnitBitArray data new ParsableNalUnitBitArray(nalData, nalOffset, nalLimit);int profileIdc data.readBits(8);//获取profileIdc 主要用于Codec的构建int constraintsFlagsAndReservedZero2Bits data.readBits(8);//获取后面几个Flag主要用于Codec的构建int levelIdc data.readBits(8);//获取levelIdc 主要用于Codec的构建int seqParameterSetId data.readUnsignedExpGolombCodedInt();//获取SPS的IDint chromaFormatIdc 1; // Default is 4:2:0boolean separateColorPlaneFlag false;if (profileIdc 100|| profileIdc 110|| profileIdc 122|| profileIdc 244|| profileIdc 44|| profileIdc 83|| profileIdc 86|| profileIdc 118|| profileIdc 128|| profileIdc 138) {chromaFormatIdc data.readUnsignedExpGolombCodedInt();if (chromaFormatIdc 3) {separateColorPlaneFlag data.readBit();//获取separate_colour_plane_flag}data.readUnsignedExpGolombCodedInt(); // bit_depth_luma_minus8data.readUnsignedExpGolombCodedInt(); // bit_depth_chroma_minus8data.skipBit(); // qpprime_y_zero_transform_bypass_flagboolean seqScalingMatrixPresentFlag data.readBit();if (seqScalingMatrixPresentFlag) {int limit (chromaFormatIdc ! 3) ? 8 : 12;for (int i 0; i limit; i) {boolean seqScalingListPresentFlag data.readBit();if (seqScalingListPresentFlag) {skipScalingList(data, i 6 ? 16 : 64);}}}}int frameNumLength data.readUnsignedExpGolombCodedInt() 4; // log2_max_frame_num_minus4 4int picOrderCntType data.readUnsignedExpGolombCodedInt();//pic_order_cnt_typeint picOrderCntLsbLength 0;boolean deltaPicOrderAlwaysZeroFlag false;if (picOrderCntType 0) {// log2_max_pic_order_cnt_lsb_minus4 4picOrderCntLsbLength data.readUnsignedExpGolombCodedInt() 4;} else if (picOrderCntType 1) {deltaPicOrderAlwaysZeroFlag data.readBit(); // delta_pic_order_always_zero_flagdata.readSignedExpGolombCodedInt(); // offset_for_non_ref_picdata.readSignedExpGolombCodedInt(); // offset_for_top_to_bottom_fieldlong numRefFramesInPicOrderCntCycle data.readUnsignedExpGolombCodedInt();for (int i 0; i numRefFramesInPicOrderCntCycle; i) {data.readUnsignedExpGolombCodedInt(); // offset_for_ref_frame[i]}}int maxNumRefFrames data.readUnsignedExpGolombCodedInt(); // max_num_ref_framesdata.skipBit(); // gaps_in_frame_num_value_allowed_flagint picWidthInMbs data.readUnsignedExpGolombCodedInt() 1;//pic_width_in_mbs_minus1int picHeightInMapUnits data.readUnsignedExpGolombCodedInt() 1;//pic_height_in_map_units_minus1boolean frameMbsOnlyFlag data.readBit();//frame_mbs_only_flagint frameHeightInMbs (2 - (frameMbsOnlyFlag ? 1 : 0)) * picHeightInMapUnits;if (!frameMbsOnlyFlag) {data.skipBit(); // mb_adaptive_frame_field_flag}data.skipBit(); // direct_8x8_inference_flag//下面确定视频帧的高宽int frameWidth picWidthInMbs * 16;int frameHeight frameHeightInMbs * 16;boolean frameCroppingFlag data.readBit();if (frameCroppingFlag) {//获取裁剪后的高宽int frameCropLeftOffset data.readUnsignedExpGolombCodedInt();int frameCropRightOffset data.readUnsignedExpGolombCodedInt();int frameCropTopOffset data.readUnsignedExpGolombCodedInt();int frameCropBottomOffset data.readUnsignedExpGolombCodedInt();int cropUnitX;int cropUnitY;if (chromaFormatIdc 0) {cropUnitX 1;cropUnitY 2 - (frameMbsOnlyFlag ? 1 : 0);} else {int subWidthC (chromaFormatIdc 3) ? 1 : 2;int subHeightC (chromaFormatIdc 1) ? 2 : 1;cropUnitX subWidthC;cropUnitY subHeightC * (2 - (frameMbsOnlyFlag ? 1 : 0));}frameWidth - (frameCropLeftOffset frameCropRightOffset) * cropUnitX;frameHeight - (frameCropTopOffset frameCropBottomOffset) * cropUnitY;}C.ColorSpace int colorSpace Format.NO_VALUE;C.ColorRange int colorRange Format.NO_VALUE;C.ColorTransfer int colorTransfer Format.NO_VALUE;//确定宽高比float pixelWidthHeightRatio 1;boolean vuiParametersPresentFlag data.readBit();if (vuiParametersPresentFlag) {//vui_parameters_present_flag包含VUI数据boolean aspectRatioInfoPresentFlag data.readBit();if (aspectRatioInfoPresentFlag) {//aspect_ratio_info_present_flagint aspectRatioIdc data.readBits(8);//aspect_ratio_idcif (aspectRatioIdc NalUnitUtil.EXTENDED_SAR) {//自定义了宽高比int sarWidth data.readBits(16);//sar_widthint sarHeight data.readBits(16);//sar_heightif (sarWidth ! 0 sarHeight ! 0) {pixelWidthHeightRatio (float) sarWidth / sarHeight;}} else if (aspectRatioIdc NalUnitUtil.ASPECT_RATIO_IDC_VALUES.length) {//无自定义获取已定义的宽高比pixelWidthHeightRatio NalUnitUtil.ASPECT_RATIO_IDC_VALUES[aspectRatioIdc];} else {Log.w(TAG, Unexpected aspect_ratio_idc value: aspectRatioIdc);}}if (data.readBit()) { // overscan_info_present_flagdata.skipBit(); // overscan_appropriate_flag}if (data.readBit()) { // video_signal_type_present_flagdata.skipBits(3); // video_formatcolorRange data.readBit() ? C.COLOR_RANGE_FULL : C.COLOR_RANGE_LIMITED; // video_full_range_flagif (data.readBit()) { // colour_description_present_flagint colorPrimaries data.readBits(8); // colour_primariesint transferCharacteristics data.readBits(8); // transfer_characteristicsdata.skipBits(8); // matrix_coeffscolorSpace ColorInfo.isoColorPrimariesToColorSpace(colorPrimaries);colorTransfer ColorInfo.isoTransferCharacteristicsToColorTransfer(transferCharacteristics);}}}return new SpsData(profileIdc,constraintsFlagsAndReservedZero2Bits,levelIdc,seqParameterSetId,maxNumRefFrames,frameWidth,frameHeight,pixelWidthHeightRatio,separateColorPlaneFlag,frameMbsOnlyFlag,frameNumLength,picOrderCntType,picOrderCntLsbLength,deltaPicOrderAlwaysZeroFlag,colorSpace,colorRange,colorTransfer);}SPS这主要获取的SPS 的ID编码的profile帧宽高以及宽高比到这里基本可以确定解码器确定出视频的宽高等全局参数。
PPS的解析 public static PpsData parsePpsNalUnitPayload(byte[] nalData, int nalOffset, int nalLimit) {ParsableNalUnitBitArray data new ParsableNalUnitBitArray(nalData, nalOffset, nalLimit);int picParameterSetId data.readUnsignedExpGolombCodedInt();//pic_parameter_set_id PPS的IDint seqParameterSetId data.readUnsignedExpGolombCodedInt();//seq_parameter_set_id SPS的IDdata.skipBit(); // entropy_coding_mode_flagboolean bottomFieldPicOrderInFramePresentFlag data.readBit();//bottom_field_pic_order_in_frame_present_flagreturn new PpsData(picParameterSetId, seqParameterSetId, bottomFieldPicOrderInFramePresentFlag);}PPS的解析就简单多了主要或bottom_field_pic_order_in_frame_present_flag这一个值。
SEI的解析
public static void consume(long presentationTimeUs, ParsableByteArray seiBuffer, TrackOutput[] outputs) {while (seiBuffer.bytesLeft() 1 /* last byte will be rbsp_trailing_bits */) {int payloadType readNon255TerminatedValue(seiBuffer);//SEI的类型int payloadSize readNon255TerminatedValue(seiBuffer);//SEI大小int nextPayloadPosition seiBuffer.getPosition() payloadSize;// Process the payload.if (payloadSize -1 || payloadSize seiBuffer.bytesLeft()) {// This might occur if were trying to read an encrypted SEI NAL unit.Log.w(TAG, Skipping remainder of malformed SEI NAL unit.);nextPayloadPosition seiBuffer.limit();} else if (payloadType PAYLOAD_TYPE_CC payloadSize 8) {//字幕类型的数据int countryCode seiBuffer.readUnsignedByte();//获取国家int providerCode seiBuffer.readUnsignedShort();//获取地区int userIdentifier 0;if (providerCode PROVIDER_CODE_ATSC) {userIdentifier seiBuffer.readInt();}int userDataTypeCode seiBuffer.readUnsignedByte();if (providerCode PROVIDER_CODE_DIRECTV) {seiBuffer.skipBytes(1); // user_data_length.}boolean messageIsSupportedCeaCaption countryCode COUNTRY_CODE (providerCode PROVIDER_CODE_ATSC || providerCode PROVIDER_CODE_DIRECTV) userDataTypeCode USER_DATA_TYPE_CODE_MPEG_CC;if (providerCode PROVIDER_CODE_ATSC) {messageIsSupportedCeaCaption userIdentifier USER_DATA_IDENTIFIER_GA94;}if (messageIsSupportedCeaCaption) {//开始解析字幕consumeCcData(presentationTimeUs, seiBuffer, outputs);}}seiBuffer.setPosition(nextPayloadPosition);}}public static void consumeCcData(long presentationTimeUs, ParsableByteArray ccDataBuffer, TrackOutput[] outputs) {// First byte contains: reserved (1), process_cc_data_flag (1), zero_bit (1), cc_count (5).int firstByte ccDataBuffer.readUnsignedByte();boolean processCcDataFlag (firstByte 0x40) ! 0;if (!processCcDataFlag) {// No need to process.return;}int ccCount firstByte 0x1F;ccDataBuffer.skipBytes(1); // Ignore em_data// Each data packet consists of 24 bits: marker bits (5) cc_valid (1) cc_type (2)// cc_data_1 (8) cc_data_2 (8).int sampleLength ccCount * 3;int sampleStartPosition ccDataBuffer.getPosition();for (TrackOutput output : outputs) {ccDataBuffer.setPosition(sampleStartPosition);output.sampleData(ccDataBuffer, sampleLength);//发送数据到SamleQueueif (presentationTimeUs ! C.TIME_UNSET) {output.sampleMetadata(//字幕轨道sampleMetadatapresentationTimeUs,C.BUFFER_FLAG_KEY_FRAME,sampleLength,/* offset */ 0,/* cryptoData */ null);}}}可以看到这里解析SEI主要是为了获取其中的字幕信息如果没有字幕信息SEI可以直接忽略
Slice的解析
定义在SampleReader中。
public void appendToNalUnit(byte[] data, int offset, int limit) {if (!isFilling) {//数据还没有填充足够返回继续填充return;}int readLength limit - offset;if (buffer.length bufferLength readLength) {buffer Arrays.copyOf(buffer, (bufferLength readLength) * 2);}System.arraycopy(data, offset, buffer, bufferLength, readLength);bufferLength readLength;bitArray.reset(buffer, 0, bufferLength);if (!bitArray.canReadBits(8)) {return;}bitArray.skipBit(); // forbidden_zero_bitint nalRefIdc bitArray.readBits(2);//nal_ref_idc优先级bitArray.skipBits(5); // nal_unit_type// Read the slice header using the syntax defined in ITU-T Recommendation H.264 (2013)// subsection 7.3.3.if (!bitArray.canReadExpGolombCodedNum()) {return;}bitArray.readUnsignedExpGolombCodedInt(); // first_mb_in_sliceif (!bitArray.canReadExpGolombCodedNum()) {return;}int sliceType bitArray.readUnsignedExpGolombCodedInt();//slice_typeif (!detectAccessUnits) {// There are AUDs in the stream so the rest of the header can be ignored.isFilling false;sliceHeader.setSliceType(sliceType);return;}if (!bitArray.canReadExpGolombCodedNum()) {return;}int picParameterSetId bitArray.readUnsignedExpGolombCodedInt();//获取 PPS IDif (pps.indexOfKey(picParameterSetId) 0) {// We have not seen the PPS yet, so dont try to decode the slice header.isFilling false;return;}NalUnitUtil.PpsData ppsData pps.get(picParameterSetId);//首先通过当前Slice的PPS ID 获取到PPSNalUnitUtil.SpsData spsData sps.get(ppsData.seqParameterSetId);//再通过PPS的ID获取到SPS数据if (spsData.separateColorPlaneFlag) {//separate_colour_plane_flag为1说明存在colour_plane_idif (!bitArray.canReadBits(2)) {return;}bitArray.skipBits(2); // 跳过colour_plane_id}if (!bitArray.canReadBits(spsData.frameNumLength)) {return;}boolean fieldPicFlag false;boolean bottomFieldFlagPresent false;boolean bottomFieldFlag false;//通过SPS 获取到的帧序号长度读取帧序号int frameNum bitArray.readBits(spsData.frameNumLength);if (!spsData.frameMbsOnlyFlag) {//frame_mbs_only_flag 不只进行帧编码if (!bitArray.canReadBits(1)) {return;}fieldPicFlag bitArray.readBit();if (fieldPicFlag) {//field_pic_flag 还存在场编码if (!bitArray.canReadBits(1)) {return;}bottomFieldFlag bitArray.readBit();//bottom_field_flag 底场标识位bottomFieldFlagPresent true;}}boolean idrPicFlag nalUnitType NalUnitUtil.NAL_UNIT_TYPE_IDR;//IDR 类型的NALint idrPicId 0;if (idrPicFlag) {if (!bitArray.canReadExpGolombCodedNum()) {return;}idrPicId bitArray.readUnsignedExpGolombCodedInt();//idr_pic_id IDR帧的序号}int picOrderCntLsb 0;int deltaPicOrderCntBottom 0;int deltaPicOrderCnt0 0;int deltaPicOrderCnt1 0;if (spsData.picOrderCountType 0) {if (!bitArray.canReadBits(spsData.picOrderCntLsbLength)) {return;}picOrderCntLsb bitArray.readBits(spsData.picOrderCntLsbLength);//pic_order_cnt_lsbif (ppsData.bottomFieldPicOrderInFramePresentFlag !fieldPicFlag) {if (!bitArray.canReadExpGolombCodedNum()) {return;}deltaPicOrderCntBottom bitArray.readSignedExpGolombCodedInt();//delta_pic_order_cnt_bottom}} else if (spsData.picOrderCountType 1 !spsData.deltaPicOrderAlwaysZeroFlag) {if (!bitArray.canReadExpGolombCodedNum()) {return;}deltaPicOrderCnt0 bitArray.readSignedExpGolombCodedInt();//delta_pic_order_cnt[0]if (ppsData.bottomFieldPicOrderInFramePresentFlag !fieldPicFlag) {if (!bitArray.canReadExpGolombCodedNum()) {return;}deltaPicOrderCnt1 bitArray.readSignedExpGolombCodedInt();//delta_pic_order_cnt[1]}}sliceHeader.setAll(spsData,nalRefIdc,sliceType,frameNum,picParameterSetId,fieldPicFlag,bottomFieldFlagPresent,bottomFieldFlag,idrPicFlag,idrPicId,picOrderCntLsb,deltaPicOrderCntBottom,deltaPicOrderCnt0,deltaPicOrderCnt1);isFilling false;}
Slice解析主要是解析了Slice的Header用于判断是否为I帧以及判断当前NAL是否为图像的第一个VCL类的NAL这些数据主要用于没有AUD时确定SampleMetadata的时机。 下面我们来动态看下SampleMetadata基于流的时序关系图 对照上图可以看出H264Reader 一开始就会将所有数据Sample到SampleQueue接下来会查找第一个NAL开始位置如果读取到第一个AUD记录AUD开始位置为samplePosition作为这段SampleData的有效开始位置下面数据序列首先会将SPSPPS这2个索引的NAL放在前面等解码器获取了SPS和PPS基本上就能确定解码器的具体配置这个时候会调用SampleQueue的format方法将解码器格式输出用于解码器的初始化等。当读取到下一个AUD的时候将这个AUD的开始位置作为有效SampleData的结束位置通过有效结束位置absolutePosition-有效开始位置samplePosition获得当前有效数据长度size同时通过当前AUD的长度nalUnitLength下一个NAL头到数据段末尾的距离bytesWrittenPastPosition得到offset将size和offset传给SampleQueue的sampleMetadata方法sampleMetadata里通过数据总长度-size-offset确定当前SampleData有效数据的开始位置这样就记录了每个Sample的开始位置和长度当Rendere读取数据用于解码时就可以查询这个开始位置和长度读取有效的视频数据给解码器。 总结
到这里ProgressiveMediaPeriod的数据解析部分终于讲完那么这些解析的数据是如何加载的呢这就是ProgressiveMediaPeriod整体架构右半部分的最后一块拼图——DataSource这也是我们后面要讲的内容了。 版权声明 © 本文为CSDN作者山雨楼原创文章 转载请注明出处 原创不易觉得有用的话收藏转发点赞支持