要分析h264裸流中的数据,H264BSAnalyzer这个工具就不错,在这里推荐一下。
用H264BSAnalyzer读取我编码的一段h264视频,截图如下:
阅读或者修改就不太方便,依赖一些编解码库。昨天,我阅读webrtc的代码,发现chromium代码里有相关的部分,主要就两个文件,短小精悍,于是就把它摘下来的。官方的测试例子[2]。
我摘取的代码,下载地址[4],编译需要c++14。来段测试程序:
#include <iostream>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <string>
#include "range.h"
#include "h264_bit_reader.h"
#include "h264_parser.h"
#include "logging.h"
using namespace std;
using namespace media;
//https://cs.chromium.org/chromium/src/media/video/h264_parser_unittest.cc
https://zhuanlan.zhihu.com/p/27896239 sps pps
void print_sps_info(const H264SPS *sps,int len){
int height=sps->pic_height_in_map_units_minus1;
int width=sps->pic_width_in_mbs_minus1;
std::cout<<"h "<<(height+1)*16<<" w "<<(width+1)*16<<std::endl;
std::cout<<"ref "<<sps->max_num_ref_frames<<" "<<len<<std::endl;
}
static std::string convert_slice_string(int type){
switch(type%5){
case H264SliceHeader::kPSlice:{
return "PSlice";
}
case H264SliceHeader::kBSlice:{
return "BSlice";
}
case H264SliceHeader::kISlice:{
return "ISlice";
}
case H264SliceHeader::kSPSlice:{
return "SPSlice";
}
case H264SliceHeader::kSISlice:{
return "SISlice";
}
default:{
return "none";
}
}
}
void printf_slice_info(media::H264SliceHeader &slice,int len){
std::cout<<slice.frame_num<<" slice "<<convert_slice_string(slice.slice_type)<<" "<<len<<std::endl;
}
int main()
{
int fd=0;
if((fd=open("1280x720.h264",O_RDONLY,S_IRGRP))<0){
printf("open failed \n");
return 0;
}
struct stat file_stat;
if((fstat(fd,&file_stat))<0){
printf("fstat failed \n");
return 0;
}
void *start_fp;
if((start_fp=mmap(NULL,file_stat.st_size,PROT_READ,MAP_SHARED,fd,0))==MAP_FAILED){
printf("mmap failed \n");
return 0;
}
int length=file_stat.st_size;
std::cout<<"size "<<length<<std::endl;
H264Parser parser;
parser.SetStream((uint8_t*)start_fp,length);
int id;
int f=0;
int total_to_parse=5;
int parse_len=0;
while(f<total_to_parse){
media::H264SliceHeader shdr;
media::H264SEIMessage sei_msg;
H264NALU nalu;
H264Parser::Result res = parser.AdvanceToNextNALU(&nalu);
if(res==H264Parser::kOk){
switch (nalu.nal_unit_type){
case H264NALU::kIDRSlice:
case H264NALU::kNonIDRSlice:{
CHECK_EQ(parser.ParseSliceHeader(nalu, &shdr), H264Parser::kOk);
int len=length-parser.get_offset()-parse_len;
parse_len+=len;
printf_slice_info(shdr,len);
break;
}
case H264NALU::kSPS:{
CHECK_EQ(parser.ParseSPS(&id), H264Parser::kOk);
const H264SPS *sps=parser.GetSPS(id);
int len=length-parser.get_offset()-parse_len;
parse_len+=len;
print_sps_info(sps,len);
break;
}
case H264NALU::kPPS:{
CHECK_EQ(parser.ParsePPS(&id), H264Parser::kOk);
int len=length-parser.get_offset()-parse_len;
parse_len+=len;
break;
}
case H264NALU::kSEIMessage:{
int len=length-parser.get_offset()-parse_len;
parse_len+=len;
CHECK_EQ(parser.ParseSEI(&sei_msg), H264Parser::kOk);
break;
}
default:{
// Skip unsupported NALU.
DLOG(INFO)<< "Skipping unsupported NALU";
int len=length-parser.get_offset()-parse_len;
parse_len+=len;
break;
}
}
f++;
}else{
std::cout<<"h264 parser error"<<std::endl;
break;
}
}
close(fd);
return 0;
}
输出结果:
1 4 28 SPS SPS
2 4 8 PPS PPS
3 3 699 SEI SEI
4 3 27126 IDR ISlice
5 3 15373 IDR ISlice
6 4 11264 NonIDR PSlice
7 3 7752 NonIDR PSlice
8 4 10301 NonIDR PSlice
9 3 6717 NonIDR PSlice
10 4 9580 NonIDR PSlice
[8]也是从webrtc中摘取的代码,不仅可以解析H264也可以解析H265。
ref:
[1]H264BSAnalyzer
[2]h264_parser_unittest.cc
[3]h264bitstream
[4]h264-parser
[5]H264码流解析及NALU
[6]视音频数据处理入门:H.264视频码流解析
[7] 从零实现一个H.264码流解析器
[8] VideoCodecAnatomy