时间:2015-04-13 来源:

FFmpeg的H.264解码器源代码简单分析:解析器(Parser)部分 【架构设计】

本文继续分析FFmpeg中libavcodec的H.264解码器(H.264 Decoder).上篇文章概述了FFmpeg中H.264解码器的结构;从这篇文章开始网页外包接活, AV_LOG_ERROR, size); return AVERROR_INVALIDDATA; } /* sps and pps in the avcC always have length coded with 2 bytes, p, 1); if (ret < 0) { av_log(avctx, "Decoding sps %d from avcC failed\n", p, 1); if (ret < 0) { av_log(avctx, "Decoding pps %d from avcC failed\n", buf, 1); if (ret < 0) return ret; } return size; } 从源代码中可以看出符合w3c标准,ff_h264_decode_extradata()调用decode_nal_units()解析SPS、PPS信息.有关decode_nal_units()的源代码在后续文章中再进行分析.h264_find_frame_end()h264_find_frame_end()用于查找H.264码流中的“起始码”(start code).在H.264码流中有两种起始码:0x000001和0x00000001.其中4Byte的长度的起始码最为常见.只有当一个完整的帧被编为多个slice的时候,符合w3c标准包含这些slice的NALU才会使用3Byte的起始码.h264_find_frame_end()的定义位于libavcodec\h264_parser.c,即001(即找到了起始码) //5 - 找到至少3个0和1个1,即0001等等(即找到了起始码) //7 - 初始化状态 //>=8 - 找到2个Slice Header // //关于起始码startcode的两种形式:3字节的0x000001和4字节的0x00000001 //3字节的0x000001只有一种场合下使用,div+css制作就是一个完整的帧被编为多个slice的时候承接网页制作, const uint8_t *buf, j; uint32_t state; ParseContext *pc = &h->parse_context; int next_avc= h->is_avc ? 0 : buf_size; // mb_addr= pc->mb_addr - 1; state = pc->state; if (state > 13) state = 7; if (h->is_avc && !h->nal_length_size) av_log(h->avctx, "AVC-parser: nal length size invalid\n"); // //每次循环前进1个字节网页外包接活,5代表找到了起始码 //类似于一个状态机web前端制作, AV_LOG_ERROR, nalsize, next_avc - i); //因为找到1个0,2个0(状态1), 1->4,状态5代表找到了0001 else if (buf[i]) state = 7; //恢复初始 else //发现了一个0 state >>= 1; // 2->1, 0->0 } else if (state <= 5) { //状态4代表找到了001,PPS,SEI类型的NALU if (pc->frame_start_found) { //如果之前已找到了帧头 i++; goto found; } } else if (nalu_type == NAL_SLICE || nalu_type == NAL_DPA || nalu_type == NAL_IDR_SLICE) { //表示有slice header的NALU //大于等于8的状态表示找到了两个帧头,符合w3c标准但没有找到帧尾的状态 state += 8; continue; } //上述两个条件都不满足网页外包接活, last_mb= h->parse_last_mb; GetBitContext gb; init_get_bits(&gb, 8*h->parse_history_count); h->parse_history_count=0; mb= get_ue_golomb_long(&gb); h->parse_last_mb= mb; if (pc->frame_start_found) { if (mb <= last_mb) goto found; } else pc->frame_start_found = 1; state = 7; } } } pc->state = state; if (h->is_avc) return next_avc; //没找到 return END_NOT_FOUND; found: pc->state = 7; pc->frame_start_found = 0; if (h->is_avc) return next_avc; //state=4时候承接网页制作,i减小3+1=4,标识帧结尾 //state=5时候,web切图报价state & 5=5 //找到的是0001(长度为4),标识帧结尾 return i - (state & 5) - 5 * (state > 7); } 从源代码可以看出网页外包接活,h264_find_frame_end()使用了一种类似于状态机的方式查找起始码.函数中的for()循环每执行一遍,网页外包接活状态机的状态就会改变一次.该状态机主要包含以下几种状态:7 - 初始化状态2 - 找到1个01 - 找到2个00 - 找到大于等于3个04 - 找到2个0和1个1,即0001等等(即找到了起始码)>=8 - 找到2个Slice Header这些状态之间的状态转移图如下所示.图中粉红色代表初始状态承接网页制作,绿色代表找到“起始码”的状态.

startcode_find_candidate() 其中,div前端切图在查找数据中第1个“0”的时候web切图报价,使用了H264DSPContext结构体中的startcode_find_candidate()函数.startcode_find_candidate()除了包含C语言版本的函数外,web切图报价还包含了ARMV6等平台下经过汇编优化的函数(估计效率会比C语言版本函数高一些).C语言版本的函数ff_startcode_find_candidate_c()的定义很简单符合w3c标准,如下所示.int ff_startcode_find_candidate_c(const uint8_t *buf,从SPS、PPS、SEI等中获得一些基本信息.在该函数中web前端制作,根据NALU的不同,web前端制作分别调用不同的函数进行具体的处理.parse_nal_units()的定义位于libavcodec\h264_parser.c,从SPS、PPS、SEI等中获得一些基本信息. static inline int parse_nal_units(AVCodecParserContext *s, const uint8_t * const buf, next_avc; unsigned int pps_id; unsigned int slice_type; int state = -1, buf, dst_length, nalsize = 0; if (buf_index >= next_avc) { nalsize = get_avc_nalsize(h, buf_size, buf_size, next_avc); if (buf_index >= buf_size) break; if (buf_index >= next_avc) continue; } src_length = next_avc - buf_index; //NALU Header (1 Byte) state = buf[buf_index]; switch (state & 0x1f) { case NAL_SLICE: case NAL_IDR_SLICE: // Do not walk the whole buffer just to decode slice header if ((state & 0x1f) == NAL_IDR_SLICE || ((state >> 5) & 0x3) == 0) { /* IDR or disposable slice * No need to decode many bytes because MMCOs shall not be present. */ if (src_length > 60) src_length = 60; } else { /* To decode up to MMCOs */ if (src_length > 1000) src_length = 1000; } break; } //解析NAL Header, buf + buf_index, &consumed, ptr, h->gb.size_in_bits); break; case NAL_SEI: //解析SEI ff_h264_decode_sei(h); break; case NAL_IDR_SLICE: //如果是IDR Slice //赋值AVCodecParserContext的key_frame为1 s->key_frame = 1; h->prev_frame_num = 0; h->prev_frame_num_offset = 0; h->prev_poc_msb = h->prev_poc_lsb = 0; /* fall through */ case NAL_SLICE: //获取Slice的一些信息 //跳过first_mb_in_slice这一字段 get_ue_golomb_long(&h->gb); // skip first_mb_in_slice //获取帧类型(I,P) slice_type = get_ue_golomb_31(&h->gb); //赋值到AVCodecParserContext的pict_type(外部可以访问到) s->pict_type = golomb_to_pict_type[slice_type % 5]; //关键帧 if (h->sei_recovery_frame_cnt >= 0) { /* key frame, AV_LOG_ERROR, pps_id); return -1; } if (!h->pps_buffers[pps_id]) { av_log(h->avctx, "non-existing PPS %u referenced\n", AV_LOG_ERROR, h->pps.sps_id); return -1; } h->sps = *h->sps_buffers[h->pps.sps_id]; h->frame_num = get_bits(&h->gb, h->sps.log2_max_poc_lsb); if (h->pps.pic_order_present == 1 && h->picture_structure == PICT_FRAME) h->delta_poc_bottom = get_se_golomb(&h->gb); } if (h->sps.poc_type == 1 && !h->sps.delta_pic_order_always_zero_flag) { h->delta_poc[0] = get_se_golomb(&h->gb); if (h->pps.pic_order_present == 1 && h->picture_structure == PICT_FRAME) h->delta_poc[1] = get_se_golomb(&h->gb); } /* Decode POC of this picture. * The prev_ values needed for decoding POC of the next picture are not set here. */ field_poc[0] = field_poc[1] = INT_MAX; ff_init_poc(h, &s->output_picture_number); /* Continue parsing to check if MMCO_RESET is present. * FIXME: MMCO_RESET could appear in non-first slice. * Maybe, we should parse all undisposable non-IDR slice of this * picture until encountering MMCO_RESET in a slice of it. */ if (h->nal_ref_idc && h->nal_unit_type != NAL_IDR_SLICE) { got_reset = scan_mmco_reset(s); if (got_reset < 0) return got_reset; } /* Set up the prev_ values for decoding POC of the next picture. */ h->prev_frame_num = got_reset ? 0 : h->frame_num; h->prev_frame_num_offset = got_reset ? 0 : h->frame_num_offset; if (h->nal_ref_idc != 0) { if (!got_reset) { h->prev_poc_msb = h->poc_msb; h->prev_poc_lsb = h->poc_lsb; } else { h->prev_poc_msb = 0; h->prev_poc_lsb = h->picture_structure == PICT_BOTTOM_FIELD ? 0 : field_poc[0]; } } //包含“场”概念的时候,web切图报价先不管 if (h->sps.pic_struct_present_flag) { switch (h->sei_pic_struct) { case SEI_PIC_STRUCT_TOP_FIELD: case SEI_PIC_STRUCT_BOTTOM_FIELD: s->repeat_pict = 0; break; case SEI_PIC_STRUCT_FRAME: case SEI_PIC_STRUCT_TOP_BOTTOM: case SEI_PIC_STRUCT_BOTTOM_TOP: s->repeat_pict = 1; break; case SEI_PIC_STRUCT_TOP_BOTTOM_TOP: case SEI_PIC_STRUCT_BOTTOM_TOP_BOTTOM: s->repeat_pict = 2; break; case SEI_PIC_STRUCT_FRAME_DOUBLING: s->repeat_pict = 3; break; case SEI_PIC_STRUCT_FRAME_TRIPLING: s->repeat_pict = 5; break; default: s->repeat_pict = h->picture_structure == PICT_FRAME ? 1 : 0; break; } } else { s->repeat_pict = h->picture_structure == PICT_FRAME ? 1 : 0; } if (h->picture_structure == PICT_FRAME) { s->picture_structure = AV_PICTURE_STRUCTURE_FRAME; if (h->sps.pic_struct_present_flag) { switch (h->sei_pic_struct) { case SEI_PIC_STRUCT_TOP_BOTTOM: case SEI_PIC_STRUCT_TOP_BOTTOM_TOP: s->field_order = AV_FIELD_TT; break; case SEI_PIC_STRUCT_BOTTOM_TOP: case SEI_PIC_STRUCT_BOTTOM_TOP_BOTTOM: s->field_order = AV_FIELD_BB; break; default: s->field_order = AV_FIELD_PROGRESSIVE; break; } } else { if (field_poc[0] < field_poc[1]) s->field_order = AV_FIELD_TT; else if (field_poc[0] > field_poc[1]) s->field_order = AV_FIELD_BB; else s->field_order = AV_FIELD_PROGRESSIVE; } } else { if (h->picture_structure == PICT_TOP_FIELD) s->picture_structure = AV_PICTURE_STRUCTURE_TOP_FIELD; else s->picture_structure = AV_PICTURE_STRUCTURE_BOTTOM_FIELD; s->field_order = AV_FIELD_UNKNOWN; } return 0; /* no need to evaluate the rest */ } } if (q264) return 0; /* didn't find a picture! */ av_log(h->avctx, "missing picture in access unit with size %d\n", buf_size); return -1; } 从源代码可以看出,网页外包接活parse_nal_units()主要做了以下几步处理:(1)对于所有的NALU,得到nal_unit_type等信息(2)根据nal_unit_type的不同承接网页制作,调用不同的解析函数进行处理.例如:a)解析SPS的时候调用ff_h264_decode_seq_parameter_set()b)解析PPS的时候调用ff_h264_decode_picture_parameter_set()c)解析SEI的时候调用ff_h264_decode_sei()d)解析IDR Slice / Slice的时候,div前端切图获取slice_type等一些信息.ff_h264_decode_nal()ff_h264_decode_nal()用于解析NAL Header,如下所示.//解析NAL Header, const uint8_t *src, int *consumed, si, di; uint8_t *dst; int bufidx; // src[0]&0x80; // forbidden bit // // 1 byte NALU头 // forbidden_zero_bit: 1bit // nal_ref_idc: 2bit // nal_unit_type: 5bit // nal_ref_idc指示NAL的优先级,兼职手机网页制作取值0-3,值越高,网站div+css代表NAL越重要 h->nal_ref_idc = src[0] >> 5; // nal_unit_type指示NAL的类型 h->nal_unit_type = src[0] & 0x1F; //后移1Byte src++; //未处理数据长度减1 length--; //起始码:0x000001 //保留:0x000002 //防止竞争:0x000003 //既表示NALU的开始符合w3c标准,又表示NALU的结束 //STARTCODE_TEST这个宏在后面用到 //得到length //length是指当前NALU单元长度,符合w3c标准这里不包括nalu头信息长度(即1个字节) #define STARTCODE_TEST \ if (i + 2 < length && src[i + 1] == 0 && src[i + 2] <= 3) { \ if (src[i + 2] != 3 && src[i + 2] != 0) { \ /* 取值为1或者2(保留用), so we must be past the end */\ length = i; \ } \ break; \ } #if HAVE_FAST_UNALIGNED #define FIND_FIRST_ZERO \ if (i > 0 && !src[i]) \ i--; \ while (src[i]) \ i++ #if HAVE_FAST_64BIT for (i = 0; i + 1 < length; i += 9) { if (!((~AV_RN64A(src + i) & (AV_RN64A(src + i) - 0x0100010001000101ULL)) & 0x8000800080008080ULL)) continue; FIND_FIRST_ZERO; STARTCODE_TEST; i -= 7; } #else for (i = 0; i + 1 < length; i += 5) { if (!((~AV_RN32A(src + i) & (AV_RN32A(src + i) - 0x01000101U)) & 0x80008080U)) continue; FIND_FIRST_ZERO; STARTCODE_TEST; i -= 3; } #endif #else for (i = 0; i + 1 < length; i += 2) { if (src[i]) continue; if (i > 0 && src[i - 1] == 0) i--; //起始码检测 STARTCODE_TEST; } #endif // use second escape buffer for inter data bufidx = h->nal_unit_type == NAL_DPC ? 1 : 0; av_fast_padded_malloc(&h->rbsp_buffer[bufidx], length+MAX_MBPAIR_SIZE); dst = h->rbsp_buffer[bufidx]; if (!dst) return NULL; if(i>=length-1){ //no escaped 0 *dst_length= length; *consumed= length+1; //+1 for the header if(h->avctx->flags2 & CODEC_FLAG2_FAST){ return src; }else{ memcpy(dst, length); return dst; } } memcpy(dst, i); si = di = i; while (si + 2 < length) { // remove escapes (very rare 1:2^22) if (src[si + 2] > 3) { dst[di++] = src[si++]; dst[di++] = src[si++]; } else if (src[si] == 0 && src[si + 1] == 0 && src[si + 2] != 0) { if (src[si + 2] == 3) { // escape dst[di++] = 0; dst[di++] = 0; si += 3; continue; } else // next start code goto nsc; } dst[di++] = src[si++]; } while (si < length) dst[di++] = src[si++]; nsc: memset(dst + di, FF_INPUT_BUFFER_PADDING_SIZE); *dst_length = di; *consumed = si + 1; // +1 for the header /* FIXME store exact number of bits in the getbitcontext * (it is needed for decoding) */ return dst; } 从源代码可以看出网页外包接活,nal_unit_type字段的值.然后函数进入了一个for()循环进行起始码检测. 起始码检测这里稍微有点复杂web前端制作,其中包含了一个STARTCODE_TEST的宏.这个宏用于做具体的起始码的判断.这部分的代码还没有细看,web前端制作以后有时间再进行补充.ff_h264_decode_seq_parameter_set()ff_h264_decode_seq_parameter_set()用于解析H.264码流中的SPS.该函数的定义位于libavcodec\h264_ps.c, level_idc, log2_max_frame_num_minus4; SPS *sps; //profile型符合w3c标准, 8); constraint_set_flags |= get_bits1(&h->gb) << 0; // constraint_set0_flag constraint_set_flags |= get_bits1(&h->gb) << 1; // constraint_set1_flag constraint_set_flags |= get_bits1(&h->gb) << 2; // constraint_set2_flag constraint_set_flags |= get_bits1(&h->gb) << 3; // constraint_set3_flag constraint_set_flags |= get_bits1(&h->gb) << 4; // constraint_set4_flag constraint_set_flags |= get_bits1(&h->gb) << 5; // constraint_set5_flag skip_bits(&h->gb, 2); // reserved_zero_2bits //level级,jpg或psd转html8bit level_idc = get_bits(&h->gb, 8); //该SPS的ID号,web前端制作该ID号将被picture引用 //注意:get_ue_golomb() sps_id = get_ue_golomb_31(&h->gb); if (sps_id >= MAX_SPS_COUNT) { av_log(h->avctx, "sps_id %u out of range\n", 16, 16, "chroma_format_idc %u", AV_LOG_ERROR, "Different chroma and luma bit depth"); goto fail; } if (sps->bit_depth_luma > 14U || sps->bit_depth_chroma > 14U) { av_log(h->avctx, "illegal bit depth value (%d, sps->bit_depth_luma, sps, 1, sps->scaling_matrix8); } else { //默认 sps->chroma_format_idc = 1; sps->bit_depth_luma = 8; sps->bit_depth_chroma = 8; } //log2_max_frame_num_minus4为另一个句法元素frame_num服务 //fram_num的解码函数是ue(v),函数中的v 在这里指定: // v = log2_max_frame_num_minus4 + 4 //从另一个角度看,web切图报价这个句法元素同时也指明了frame_num 的所能达到的最大值: // MaxFrameNum = 2^( log2_max_frame_num_minus4 + 4 ) log2_max_frame_num_minus4 = get_ue_golomb(&h->gb); if (log2_max_frame_num_minus4 < MIN_LOG2_MAX_FRAME_NUM - 4 || log2_max_frame_num_minus4 > MAX_LOG2_MAX_FRAME_NUM - 4) { av_log(h->avctx, "log2_max_frame_num_minus4 out of range (0-12): %d\n", log2_max_frame_num_minus4); goto fail; } sps->log2_max_frame_num = log2_max_frame_num_minus4 + 4; //pic_order_cnt_type 指明了poc (picture order count) 的编码方法 //poc标识图像的播放顺序. //由于H.264使用了B帧预测,jpg或psd转html使得图像的解码顺序并不一定等于播放顺序web前端制作,但它们之间存在一定的映射关系 //poc 可以由frame-num 通过映射关系计算得来,web前端制作也可以索性由编码器显式地传送. //H.264 中一共定义了三种poc 的编码方法 sps->poc_type = get_ue_golomb_31(&h->gb); //3种poc的编码方法 if (sps->poc_type == 0) { // FIXME #define unsigned t = get_ue_golomb(&h->gb); if (t>12) { av_log(h->avctx, "log2_max_poc_lsb (%d) is out of range\n", AV_LOG_ERROR, sps->poc_cycle_length); goto fail; } for (i = 0; i < sps->poc_cycle_length; i++) sps->offset_for_ref_frame[i] = get_se_golomb(&h->gb); } else if (sps->poc_type != 2) { av_log(h->avctx, "illegal POC type %d\n", sps->poc_type); goto fail; } //num_ref_frames 指定参考帧队列可能达到的最大长度,div+css制作解码器依照这个句法元素的值开辟存储区承接网页制作,这个存储区用于存放已解码的参考帧,兼职手机网页制作 //H.264 规定最多可用16 个参考帧web切图报价, 'M', '2')) sps->ref_frame_count = FFMAX(2, AV_LOG_ERROR, sps->ref_frame_count); goto fail; } sps->gaps_in_frame_num_allowed_flag = get_bits1(&h->gb); //加1后为图像宽(以宏块为单位) //以像素为单位图像宽度(亮度):width=mb_width*16 sps->mb_width = get_ue_golomb(&h->gb) + 1; //加1后为图像高(以宏块为单位) //以像素为单位图像高度(亮度):height=mb_height*16 sps->mb_height = get_ue_golomb(&h->gb) + 1; //检查一下 if ((unsigned)sps->mb_width >= INT_MAX / 16 || (unsigned)sps->mb_height >= INT_MAX / 16 || av_image_check_size(16 * sps->mb_width, 0, AV_LOG_ERROR, AV_LOG_ERROR, "MBAFF support not included; enable it at compile-time.\n"); #endif //裁剪输出,jpg或psd转html没研究过 sps->crop = get_bits1(&h->gb); if (sps->crop) { int crop_left = get_ue_golomb(&h->gb); int crop_right = get_ue_golomb(&h->gb); int crop_top = get_ue_golomb(&h->gb); int crop_bottom = get_ue_golomb(&h->gb); int width = 16 * sps->mb_width; int height = 16 * sps->mb_height * (2 - sps->frame_mbs_only_flag); if (h->avctx->flags2 & CODEC_FLAG2_IGNORE_CROP) { av_log(h->avctx, "discarding sps cropping, crop_left, crop_top, AV_LOG_WARNING, crop_left); } if (crop_left > (unsigned)INT_MAX / 4 / step_x || crop_right > (unsigned)INT_MAX / 4 / step_x || crop_top > (unsigned)INT_MAX / 4 / step_y || crop_bottom> (unsigned)INT_MAX / 4 / step_y || (crop_left + crop_right ) * step_x >= width || (crop_top + crop_bottom) * step_y >= height ) { av_log(h->avctx, "crop values invalid %d %d %d %d / %d %d\n", crop_right, crop_bottom, height); goto fail; } sps->crop_left = crop_left * step_x; sps->crop_right = crop_right * step_x; sps->crop_top = crop_top * step_y; sps->crop_bottom = crop_bottom * step_y; } } else { sps->crop_left = sps->crop_right = sps->crop_top = sps->crop_bottom = sps->crop = 0; } sps->vui_parameters_present_flag = get_bits1(&h->gb); if (sps->vui_parameters_present_flag) { int ret = decode_vui_parameters(h, "420", "444" }; av_log(h->avctx, "sps:%u profile:%d/%d poc:%d ref:%d %dx%d %s %s crop:%u/%u/%u/%u %s %s %"PRId32"/%"PRId32" b%d reo:%d\n", sps->profile_idc, sps->poc_type, sps->mb_width, sps->frame_mbs_only_flag ? "FRM" : (sps->mb_aff ? "MB-AFF" : "PIC-AFF"), sps->crop_left, sps->crop_top, sps->vui_parameters_present_flag ? "VUI" : "", sps->timing_info_present_flag ? sps->num_units_in_tick : 0, sps->bit_depth_luma, sps->bitstream_restriction_flag ? sps->num_reorder_frames : -1 ); } sps->new = 1; av_free(h->sps_buffers[sps_id]); h->sps_buffers[sps_id] = sps; return 0; fail: av_free(sps); return -1; } 解析SPS源代码并不是很有“技术含量”.只要参考ITU-T的《H.264标准》就可以理解了,兼职手机网页制作不再做过多详细的分析.ff_h264_decode_picture_parameter_set()ff_h264_decode_picture_parameter_set()用于解析H.264码流中的PPS.该函数的定义位于libavcodec\h264_ps.c, int bit_length) { //获取PPS ID unsigned int pps_id = get_ue_golomb(&h->gb); PPS *pps; SPS *sps; int qp_bd_offset; int bits_left; if (pps_id >= MAX_PPS_COUNT) { av_log(h->avctx, "pps_id %u out of range\n", AV_LOG_ERROR, pps->sps_id); goto fail; } sps = h->sps_buffers[pps->sps_id]; qp_bd_offset = 6 * (sps->bit_depth_luma - 8); if (sps->bit_depth_luma > 14) { av_log(h->avctx, "Invalid luma bit depth=%d\n", AV_LOG_ERROR, sps->bit_depth_luma); goto fail; } //entropy_coding_mode_flag //0表示熵编码使用CAVLC, AV_LOG_ERROR, "FMO not supported\n"); switch (pps->mb_slice_group_map_type) { case 0: #if 0 | for (i = 0; i <= num_slice_groups_minus1; i++) | | | | run_length[i] |1 |ue(v) | #endif break; case 2: #if 0 | for (i = 0; i < num_slice_groups_minus1; i++) { | | | | top_left_mb[i] |1 |ue(v) | | bottom_right_mb[i] |1 |ue(v) | | } | | | #endif break; case 3: case 4: case 5: #if 0 | slice_group_change_direction_flag |1 |u(1) | | slice_group_change_rate_minus1 |1 |ue(v) | #endif break; case 6: #if 0 | slice_group_id_cnt_minus1 |1 |ue(v) | | for (i = 0; i <= slice_group_id_cnt_minus1; i++)| | | | slice_group_id[i] |1 |u(v) | #endif break; } } //num_ref_idx_l0_active_minus1 加1后指明目前参考帧队列的长度,web前端制作即有多少个参考帧 //读者可能还记得在SPS中有句法元素num_ref_frames 也是跟参考帧队列有关承接网页制作,它们的区 //别是num_ref_frames 指明参考帧队列的最大值,兼职手机网页制作 解码器用它的值来分配内存空间; //num_ref_idx_l0_active_minus1 指明在这个队列中当前实际的、已存在的参考帧数目web切图报价, AV_LOG_ERROR, 2); //QP初始值.读取后需要加26 pps->init_qp = get_se_golomb(&h->gb) + 26 + qp_bd_offset; //SP和SI的QP初始值(没怎么见过这两种帧) pps->init_qs = get_se_golomb(&h->gb) + 26 + qp_bd_offset; pps->chroma_qp_index_offset[0] = get_se_golomb(&h->gb); pps->deblocking_filter_parameters_present = get_bits1(&h->gb); pps->constrained_intra_pred = get_bits1(&h->gb); pps->redundant_pic_cnt_present = get_bits1(&h->gb); pps->transform_8x8_mode = 0; // contents of sps/pps can change even if id doesn't, h->sps_buffers[pps->sps_id]->scaling_matrix4, h->sps_buffers[pps->sps_id]->scaling_matrix8, pps)) { pps->transform_8x8_mode = get_bits1(&h->gb); decode_scaling_matrices(h, pps, pps->scaling_matrix4, 0, sps->bit_depth_luma); build_qp_table(pps, pps->chroma_qp_index_offset[1], AV_LOG_DEBUG, pps_id, pps->cabac ? "CABAC" : "CAVLC", pps->ref_count[0], pps->weighted_pred ? "weighted" : "", pps->init_qs, pps->chroma_qp_index_offset[1], pps->constrained_intra_pred ? "CONSTR" : "", pps->transform_8x8_mode ? "8x8DCT" : ""); } av_free(h->pps_buffers[pps_id]); h->pps_buffers[pps_id] = pps; return 0; fail: av_free(pps); return -1; } 和解析SPS类似承接网页制作,解析PPS源代码并不是很有“技术含量”.只要参考ITU-T的《H.264标准》就可以理解,兼职手机网页制作不再做过多详细的分析.ff_h264_decode_sei()ff_h264_decode_sei()用于解析H.264码流中的SEI.该函数的定义位于libavcodec\h264_sei.c, 16)) { int type = 0; unsigned size = 0; unsigned next; int ret = 0; do { if (get_bits_left(&h->gb) < 8) return AVERROR_INVALIDDATA; type += show_bits(&h->gb, 8) == 255); do { if (get_bits_left(&h->gb) < 8) return AVERROR_INVALIDDATA; size += show_bits(&h->gb, 8) == 255); if (h->avctx->debug&FF_DEBUG_STARTCODE) av_log(h->avctx, "SEI %d len:%d\n", size); if (size > get_bits_left(&h->gb) / 8) { av_log(h->avctx, "SEI type %d size %d truncated at %d\n", 8*size, size) < 0) return -1; break; //x264的编码参数信息一般都会存储在USER_DATA_UNREGISTERED //其他种类的SEI见得很少 case SEI_TYPE_USER_DATA_UNREGISTERED: ret = decode_unregistered_user_data(h, AV_LOG_DEBUG, type); } skip_bits_long(&h->gb, next - get_bits_count(&h->gb)); // FIXME check bits here align_get_bits(&h->gb); } return 0; } 在《H.264官方标准》中,网站div+cssSEI的种类是非常多的.在ff_h264_decode_sei()中包含以下种类的SEI:SEI_TYPE_BUFFERING_PERIODSEI_TYPE_PIC_TIMINGSEI_TYPE_USER_DATA_ITU_T_T35SEI_TYPE_USER_DATA_UNREGISTEREDSEI_TYPE_RECOVERY_POINTSEI_TYPE_FRAME_PACKINGSEI_TYPE_DISPLAY_ORIENTATION其中的大部分种类的SEI信息我并没有接触过.唯一接触比较多的就是SEI_TYPE_USER_DATA_UNREGISTERED类型的信息了.使用X264编码视频的时候符合w3c标准,会自动将配置信息以SEI_TYPE_USER_DATA_UNREGISTERED(用户数据未注册SEI)的形式写入码流.从ff_h264_decode_sei()的定义可以看出,符合w3c标准该函数根据不同的SEI类型调用不同的解析函数.当SEI类型为SEI_TYPE_USER_DATA_UNREGISTERED的时候网页外包接活,就会调用decode_unregistered_user_data()函数.

单击查看更清晰的图片

单击查看更清晰的图片

如图所示,网页外包接活h264_find_frame_end()初始化时候位于状态“7”;当找到1个“0”之后web前端制作,状态从“7”变为“2”;在状态“2”下,div+css制作如果再次找到1个“0”,则状态变为“1”;在状态“1”下,兼职手机网页制作如果找到“1”,表明找到了“0x000001”起始码;在状态“1”下符合w3c标准,则状态变换为“0”;在状态“0”下网页外包接活,则状态变换为“5” ,表明找到了“0x000001”起始码.

从图中可以看出,web前端制作H.264的解析器(Parser)在解析数据的时候调用h264_parse(),parse_nal_units()则调用了一系列解析特定NALU的函数.H.264的解码器(Decoder)在解码数据的时候调用h264_decode_frame(),decode_nal_units()也同样调用了一系列解析不同NALU的函数.图中简单列举了几个解析特定NALU的函数:ff_h264_decode_nal():解析NALU Headerff_h264_decode_seq_parameter_set():解析SPSff_h264_decode_picture_parameter_set():解析PPSff_h264_decode_sei():解析SEIH.264解码器与H.264解析器最主要的不同的地方在于它调用了ff_h264_execute_decode_slices()函数进行了解码工作.这篇文章只分析H.264解析器的源代码符合w3c标准,至于H.264解码器的源代码,符合w3c标准则在后面几篇文章中再进行分析.ff_h264_decoderff_h264_decoder是FFmpeg的H.264解码器对应的AVCodec结构体.它的定义位于libavcodec\h264.c, .long_name = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"), .id = AV_CODEC_ID_H264, .init = ff_h264_decode_init, .decode = h264_decode_frame, .flush = flush_dpb, .update_thread_context = ONLY_IF_THREADS_ENABLED(ff_h264_update_thread_context), .priv_class = &h264_class, };从ff_h264_decoder的定义可以看出:解码器初始化的函数指针init()指向ff_h264_decode_init()函数,兼职手机网页制作解码的函数指针decode()指向h264_decode_frame()函数web切图报价,而decode_nal_units()调用了和H.264解析器(Parser)有关的源代码就可以了.ff_h264_parserff_h264_parser是FFmpeg的H.264解析器对应的AVCodecParser结构体.它的定义位于libavcodec\h264_parser.c, .priv_data_size = sizeof(H264Context), .parser_parse = h264_parse, .split = h264_split, };从ff_h264_parser的定义可以看出:AVCodecParser初始化的函数指针parser_init()指向init()函数;解析数据的函数指针parser_parse()指向h264_parse()函数;销毁的函数指针parser_close()指向close()函数.下面分别看看这些函数.init() [对应于AVCodecParser-> parser_init()]ff_h264_parser结构体中AVCodecParser的parser_init()指向init()函数.该函数完成了AVCodecParser的初始化工作.函数的定义很简单,div前端切图如下所示.static av_cold int init(AVCodecParserContext *s) { H264Context *h = s->priv_data; h->thread_context[0] = h; h->slice_context_count = 1; ff_h264dsp_init(&h->h264dsp, 1); return 0; }close() [对应于AVCodecParser-> parser_close()]ff_h264_parser结构体中AVCodecParser的parser_close()指向close()函数.该函数完成了AVCodecParser的关闭工作.函数的定义也比较简单符合w3c标准,如下所示.//解析H.264码流 //输出一个完整的NAL, AVCodecContext *avctx, int *poutbuf_size, int buf_size) { H264Context *h = s->priv_data; ParseContext *pc = &h->parse_context; int next; //如果还没有解析过1帧web切图报价, otherwise opening the parser, avctx->extradata, buf, next, &buf_size) < 0) { *poutbuf = NULL; *poutbuf_size = 0; return buf_size; } if (next < 0 && next != END_NOT_FOUND) { av_assert1(pc->last_index + next >= 0); h264_find_frame_end(h, -next); // update state } } //解析NALU, avctx, buf_size); if (avctx->framerate.num) avctx->time_base = av_inv_q(av_mul_q(avctx->framerate, 1})); if (h->sei_cpb_removal_delay >= 0) { s->dts_sync_point = h->sei_buffering_period_present; s->dts_ref_dts_delta = h->sei_cpb_removal_delay; s->pts_dts_delta = h->sei_dpb_output_delay; } else { s->dts_sync_point = INT_MIN; s->dts_ref_dts_delta = INT_MIN; s->pts_dts_delta = INT_MIN; } if (s->flags & PARSER_FLAG_ONCE) { s->flags &= PARSER_FLAG_COMPLETE_FRAMES; } //分割后的帧数据输出至poutbuf *poutbuf = buf; *poutbuf_size = buf_size; return next; } 从源代码可以看出承接网页制作,h264_parse()主要完成了以下3步工作:(1)如果是第一次解析,div前端切图则首先调用ff_h264_decode_extradata()解析AVCodecContext的extradata(里面实际上存储了H.264的SPS、PPS).(2)如果传入的flags 中包含PARSER_FLAG_COMPLETE_FRAMES,则说明传入的是完整的一帧数据,web切图报价不作任何处理;如果不包含PARSER_FLAG_COMPLETE_FRAMES,则说明传入的不是完整的一帧数据而是任意一段H.264数据,符合w3c标准则需要调用h264_find_frame_end()通过查找“起始码”(0x00000001或者0x000001)的方法网页外包接活, int size) { uint8_t user_data[16 + 256]; int e, i; if (size < 16) return AVERROR_INVALIDDATA; for (i = 0; i < sizeof(user_data) - 1 && i < size; i++) user_data[i] = get_bits(&h->gb,const char *format,[argument ]...); //sscanf会从buffer里读进数据,网站div+css依照format的格式将数据写入到argument里. user_data[i] = 0; e = sscanf(user_data + 16, &build); if (e == 1 && build > 0) h->x264_build = build; if (e == 1 && build == 1 && !strncmp(user_data+16, 16)) h->x264_build = 67; if (h->avctx->debug & FF_DEBUG_BUGS) av_log(h->avctx, "user data:\"%s\"\n", 8); return 0; } 解析Slice Header对于包含图像压缩编码的Slice,解析器(Parser)并不进行解码处理,web切图报价而是简单提取一些Slice Header中的信息.该部分的代码并没有写成一个函数符合w3c标准,而是直接写到了parse_nal_units()里面,html切图制作截取出来如下所示.case NAL_IDR_SLICE: //如果是IDR Slice //赋值AVCodecParserContext的key_frame为1 s->key_frame = 1; h->prev_frame_num = 0; h->prev_frame_num_offset = 0; h->prev_poc_msb = h->prev_poc_lsb = 0; /* fall through */ case NAL_SLICE: //获取Slice的一些信息 //跳过first_mb_in_slice这一字段 get_ue_golomb_long(&h->gb); // skip first_mb_in_slice //获取帧类型(I,P) slice_type = get_ue_golomb_31(&h->gb); //赋值到AVCodecParserContext的pict_type(外部可以访问到) s->pict_type = golomb_to_pict_type[slice_type % 5]; //关键帧 if (h->sei_recovery_frame_cnt >= 0) { /* key frame, AV_LOG_ERROR, pps_id); return -1; } if (!h->pps_buffers[pps_id]) { av_log(h->avctx, "non-existing PPS %u referenced\n", AV_LOG_ERROR, h->pps.sps_id); return -1; } h->sps = *h->sps_buffers[h->pps.sps_id]; h->frame_num = get_bits(&h->gb, h->sps.log2_max_poc_lsb); if (h->pps.pic_order_present == 1 && h->picture_structure == PICT_FRAME) h->delta_poc_bottom = get_se_golomb(&h->gb); } if (h->sps.poc_type == 1 && !h->sps.delta_pic_order_always_zero_flag) { h->delta_poc[0] = get_se_golomb(&h->gb); if (h->pps.pic_order_present == 1 && h->picture_structure == PICT_FRAME) h->delta_poc[1] = get_se_golomb(&h->gb); } /* Decode POC of this picture. * The prev_ values needed for decoding POC of the next picture are not set here. */ field_poc[0] = field_poc[1] = INT_MAX; ff_init_poc(h, &s->output_picture_number); /* Continue parsing to check if MMCO_RESET is present. * FIXME: MMCO_RESET could appear in non-first slice. * Maybe, we should parse all undisposable non-IDR slice of this * picture until encountering MMCO_RESET in a slice of it. */ if (h->nal_ref_idc && h->nal_unit_type != NAL_IDR_SLICE) { got_reset = scan_mmco_reset(s); if (got_reset < 0) return got_reset; } /* Set up the prev_ values for decoding POC of the next picture. */ h->prev_frame_num = got_reset ? 0 : h->frame_num; h->prev_frame_num_offset = got_reset ? 0 : h->frame_num_offset; if (h->nal_ref_idc != 0) { if (!got_reset) { h->prev_poc_msb = h->poc_msb; h->prev_poc_lsb = h->poc_lsb; } else { h->prev_poc_msb = 0; h->prev_poc_lsb = h->picture_structure == PICT_BOTTOM_FIELD ? 0 : field_poc[0]; } } 可以看出该部分代码提取了根据NALU Header、Slice Header中的信息赋值了一些字段,网站div+css比如说AVCodecParserContext中的key_frame、pict_type,H264Context中的sps、pps、frame_num等等.雷霄骅leixiaohua1020@126.comhttp://blog.csdn.net/leixiaohua1020

点击次数:60421
作者:
web前端行业资讯
Web new NewsList
微软发布WindowsServerBuild17074更新 ,,2018年01月18日凭借一个AI小功能,这款Google应用冲上苹果AppStore榜首 ,,2018年01月18日百度数据可视化实验室正式成立,发布深度学习可视化平台VisualDL ,,2018年01月18日OpenAI开源最新工具包,模型增大10倍只需额外增加20%计算时间 ,,2018年01月18日百度手机输入法8.0正式发布:支持多人语音速记 ,,2018年01月18日CSDN宣布收购TinyMind团队并升级为AI社区 ,,2018年01月18日甲骨文发布补丁修复英特尔芯片漏洞造成的问题 ,,2018年01月18日权威!官方发布CPU熔断和幽灵漏洞防范指引:附补丁下载 ,,2018年01月18日Oracle宣布新的JavaChampions ,,2018年01月18日Fedora28壁纸征集活动现已开幕:将持续至2月13日 ,,2018年01月18日苹果WebKit团队发布Speedometer2.0网页响应测试工具 ,,2018年01月18日百度输入法8.0后天发布:全感官AI输入 ,,2018年01月18日腾讯和乐高合作:共同研发智能玩具、游戏 ,,2018年01月18日HomePod上市日益临近智能音箱市场吸引力越来越大 ,,2018年01月18日英特尔公布修补漏洞后PC性能数据:8代CPU影响最小 ,,2018年01月18日云存储公司Dropbox秘密提交IPO申请估值超百亿美元 ,,2018年01月18日iPod之父:防手机上瘾无技术难度苹果谷歌应承担责任 ,,2018年01月18日芯片不安全英特尔云客户考虑转用AMD等对手处理器 ,,2018年01月18日2018年Java展望 ,,2018年01月18日区块链有多火?快播流量矿石遭20多万人疯抢 ,,2018年01月18日Intel搞定神经拟态芯片:模拟人类大脑、自主学习 ,,2018年01月18日阿里巴巴发布IoTConnect开放连接协议,盼推动语音互动入口普及 ,,2018年01月18日区块链火了,全球大佬们怎么看? ,,2018年01月18日Facebook正测试新功能主推本地新闻资讯 ,,2018年01月18日在GooglePlay中发现使用Kotlin开发的安卓恶意软件 ,,2018年01月18日VisualStudio201715.6预览版本2,增加新功能 ,,2018年01月18日百度陆奇:AI是5G最好的加速器 ,,2018年01月18日PinterestCEO:不同于谷歌和Facebook,我们走了第三条路 ,,2018年01月18日腾讯加码区块链项目已悄然注册“以太锁”商标 ,,2018年01月18日3D打印脑组织?科学家正在向这一目标正在前进 ,,2018年01月18日LeetCode(27)RemoveElement【移动开发】2015年08月14日Java的关键字与保留字小结2014年01月30日Android清除数据、清除缓存、一键清理的区别 【系统运维】2015年03月30日GoogleCpp风格指南5)其他特性_part2【编程语言】2015年02月28日Django&Celery–Easyasynctaskprocessing翻译【综合】2015年07月10日如何当一个好的面试官【移动开发】2014年11月04日JSArray对象入门分析2014年01月29日jquery.validate使用攻略第二部2014年01月29日威慑江湖免费主页空间为您提供10M免费空间服务2014年01月29日【SDOI2015】游记Day1 【互联网】2015年04月13日Java_并发线程_Semaphore、CountDownLatch、CyclicBarrier、Exchanger 【综合】2014年11月04日49个决定成败的人生细节2014年01月29日hdu2060Snooker(数学题) 【编程语言】2015年05月29日JSP详细篇——JavaWeb的数据库操作 【数据库】2014年12月04日CF219DChoosingCapitalforTreeland(树形dp) 【编程语言】2015年08月24日LR--Controller的Pacing设置(不容忽视的设置)【编程语言】2015年01月21日startActivityForResult用法详解 【综合】2015年01月09日2.传感器学习笔记之光照传感器 【移动开发】2015年08月31日如何序列化和反序列化一个java对象 【移动开发】2015年04月01日Android技术——IntentFilter 【综合】2015年05月20日获取接入的wifi名 【移动开发】2015年05月18日Linux使用技巧7--GBK转成UTF-8 【移动开发】2015年05月05日LinuxKernel3.1.4稳定版发布 ,,2016年06月23日ssh远程登录报错REMOTEHOSTIDENTIFICATIONHASCHANGED!解决方案及原因 【移动开发】2015年07月02日HOOK钩子技术1inlineHOOK内联钩子 【移动开发】2015年05月05日Eclipse搭建maven开发环境 【编程语言】2015年08月07日android开发学习:打电话和发短信【综合】2015年07月14日openstack:nova中“从镜像启动”创建虚拟机的流程【综合】2015年08月24日Word和WPS通用的文档排版2014年01月28日数据结构之关于树的操作(树的递归和非递归遍历)-(四补)【Web前端】2015年05月15日
我们保证
We guarantee
> psd效果文件手工切图,保证图片效果最好体积最小利于传输
> 100%手写的HTML(DIV+CSS)编码,绝对符合W3C标准
> 代码精简、css沉余量小、搜索引擎扫描迅速,网页打开快捷
> 应用Css Sprite能够减少HTTP请求数,提高网页性能
> 跨浏览器兼容(IE6、7、8、9,Firefox火狐,Chrome谷歌)