现在来写下s5pv210的h264解码,这一章有些部分我理解的不是很透彻,只能写个大概了。希望看到的人能给出些意见,有些地方写错的还望指正出来!

解码过程与编码过程类似,编码过程是先初始化编码器,然后从编码器输出buf中读出h264文件头数据,写入输出文件,然后开始不断地将一帧帧NV12格式的图像写入到编码器的输入buf,启动编码,从编码器输出buf中将h264视频数据写入到输出文件。解码是首先打开一个h264格式的文件作为输入文件,从这个文件中先读出文件头数据,写入到解码器的输入buf中,再初始化解码器,之后就是不断地将H264格式输入文件中的一段段NALU数据写入到解码器的输入buf,启动解码,从解码器输出buf中读取NV12格式的数据,然后转换成YUV420p格式写入到输出文件中。

上面一段中所提到的H264文件头数据其实是一段包含SPS(序列参数集)、PPS(图像参数集)的数据,里面的参数用来配置解码器的初始化。与编码过程中读取一帧帧NV12格式的图像数据不同,因为NV12格式每一帧长度是一样的。而H264格式文件中每一段NALU的长度不是固定的,这就需要在读取文件中做判断。下面给出一个h264格式文件的前160个字节(文件用Hex模式查看)。

00 00 00 01 67 64 00 28 ac d3 05 07 e4 00 00 00
01 68 ea 40 6f 2c 00 00 00 01 65 b8 40 57 8a b4
03 0e 39 4a 43 8f 20 fb db 09 bb ae 57 d1 94 e4
20 8c e7 8b 44 b0 03 1c 72 59 78 bf 57 a6 f1 f8
9f 33 ce 4a 5c b4 e1 be 52 03 3d 0b 64 74 37 a7
57 42 8e a1 39 75 03 d6 68 a3 2f e0 a3 0b 26 e3
a1 74 5a e5 b6 34 85 e6 10 c9 82 0f 53 12 47 cc
c8 0f 28 1d 9e 26 7c ac ed 4b e4 00 ea 64 ca 8a
3b 2c 4f f4 05 84 8d cd 6f 96 02 d1 92 be 0b dc
1f e5 5a 35 ea ed 87 a9 1b 7f ca 3c b3 53 a1 89

里面有几个特殊的字段“00 00 00 01”,这个即是h264格式文件中每一段NALU数据中各个数据单元的头部,这些数据单元可以是SPS、PPS、SEI等,具体如下。

enum H264NALTYPE{ H264NT_NAL = 0, H264NT_SLICE,        //1 非IDR图像的编码条带 H264NT_SLICE_DPA,    //2 编码条带数据分割块AH264NT_SLICE_DPB,    //3 编码条带数据分割块BH264NT_SLICE_DPC,    //4 编码条带数据分割块CH264NT_SLICE_IDR,    //5 IDR图像的编码条带H264NT_SEI,          //6 增强信息H264NT_SPS,          //7 序列参数集H264NT_PPS,          //8 图像参数集
}; 

区分这些数据单元,可以取“00 00 00 01”字段后一字节的数据,与0x1f相&获得。比如上面第一个数据单元:

00 00 00 01 67 64 00 28 ac d3 05 07 e4

说明这个是一段SPS(67&1f = 7)。既然解码是是以一段NALU数据为单位的,那么如何区分一段NALU中有几个数据单元呢?这是根据数据单元的类型定义的。其中SEI、SPS与PPS如果相邻则放在一段NALU数据中,给编码器做初始化用。SLICE和SLICE_IDR分别属于单独的NALU数据段,但SLICE_IDR为关键帧,SLICE为P帧,P帧为单向预测编码或帧内预测编码,依赖于关键帧。也即是说,解码是,在P帧的前面一般至少要有一帧关键帧发给解码器,否则不能正常解码图像信息。

接下来既可以说下这个h264格式的文件怎么读取了。首先是读取文件的头部,从SPS/PPS/SEI数据单元开始读,遇到SLICE/SLICE_IDR数据单元时停止,将读到的数据写入到解码器的输入buf中,然后初始化解码器。之后开始不断读取一段段NALU数据(可以是SPS/PPS/SE连续数据单元+SLICE/SLICE_IDR数据单元,也可以是一个SLICE数据单元,或者是一个SLICE_IDR数据单元)。

下面看h264格式文件读取的代码。这个函数返回读取一段NALU数据的长度,数据会拷贝到buf指针处,当header为1是是读取文件头信息,为0时时正常读取一段NALU数据。

int read_one_frame(FILE *fp, uint8_t **buf, int header)
{static int end_of_file = 0;int ustart, uend;int cstart, cend;int found;uint8_t nal_unit_type;// 一、从文件中读取一段数据到fbuf缓冲区中,读取的长度是缓冲区最大长度的一半
    // fstart==fend : empty// we keep fstart<=fend. whenever fend goes beyond fbufsz, we move the data back to [0 ...)int rsz;if(!end_of_file && fend-fstart<fbufsz/2) { // fbuf is less than half fullif (fstart>fbufsz/2) {    // move back to [0 ...)memcpy(fbuf,fbuf+fstart, fend-fstart);fend-=fstart;fstart=0;}// fill up to half: fbufsz/2-fend+fstartrsz = fread(fbuf+fend, 1, fbufsz/2-fend+fstart, fp);if(rsz<(int)(fbufsz/2-fend+fstart)) { // end of fileprintf("We have read all data from the input file\n");end_of_file = 1;}if(rsz>0)fend += rsz;}if(fend>fbufsz) {fprintf(stderr,"Opps: this should never happen!\n");return -1;}
    // 二、读取文件头数据// now either fbuf is half full or it is end of fileif(header) { // find header// find the first SPS,PPS,SEI headerfound = 0;cstart = cend = -1;while (find_nal_unit(fbuf+fstart, fend-fstart, &ustart, &uend)>0) {nal_unit_type = fbuf[fstart+ustart] & 0x1f;if(nal_unit_type==(uint8_t)6 || nal_unit_type==(uint8_t)7 || nal_unit_type==(uint8_t)8) {// SEI, SPS or PPSif(!found){found = 1;cstart = fstart+ustart-3; // the start of first SPS, PPS or SEI, fbuf[cstart]: 00 00 01if(cstart>0 && !fbuf[cstart-1])cstart--;}}else {if(found) {cend = fstart+ustart-3; // the end of header before the following picture slice NAL. fbuf[cend]: 00 00 01if (!fbuf[cend-1]) { // the following picture slice has a long start code 00 00 00 01cend--;}break;}}fstart+=uend; // now fbuf[fstart] is the first byte of start code of next NAL}if(cstart<0 || cend<0) {fprintf(stderr,"Error: cannot find a NAL header.\n");buf = NULL;if(!end_of_file)fprintf(stderr,"You should consider increase fbufsz. Current fbufsz=%d.\n",fbufsz);return -1;}fstart = cend;// now fbuf[cstart,cend) should contain the first SPS,PPS,SEI headerprintf("Header: cstart=%x, cend=%x, length=%d\n",cstart,cend,cend-cstart);*buf=fbuf+cstart;return cend-cstart;}   // 三、读取一段NALU数据cstart = cend = -1;found = 0;while (find_nal_unit(fbuf+fstart, fend-fstart, &ustart, &uend)>0) {nal_unit_type = fbuf[fstart+ustart] & 0x1f;if(nal_unit_type==(uint8_t)6 || nal_unit_type==(uint8_t)7 || nal_unit_type==(uint8_t)8) {// SEI, SPS or PPSif(!found){found = 1;cstart = fstart+ustart-3; // the start of first SPS, PPS or SEI, fbuf[cstart]: 00 00 01if(cstart>0 && !fbuf[cstart-1])cstart--;}}else if(nal_unit_type==(uint8_t)1 || nal_unit_type==(uint8_t)5) { // IDR or non-IDRif(!found) { // no headercstart = fstart+ustart-3;if(cstart>0 && !fbuf[cstart-1])cstart--;}cend = fstart+uend;break;}fstart+=uend; // now fbuf[fstart] is the first byte of start code of next NAL}if(cstart<0 || cend<0) {//printf("No more NALs. Exiting\n");buf = NULL;if(!end_of_file)fprintf(stderr,"You should consider increase fbufsz. Current fbufsz=%d.\n",fbufsz);return -1;}fstart = cend;*buf=fbuf+cstart;return cend - cstart;
}

函数有点长,不过总体上分为三部分。第一部分是从文件中读入数据到fbuf缓冲区,并使缓冲区数据保持一半空间存有数据。第二部分是读取文件头数据,find_nal_unit()函数为读取一个数据单元,即两个“00 00 00 01”字段之间的数据,然后判断数据单元类型,当为SPS(7),PPS(8),SEI(6)时则继续读,直到遇到其它类型数据单元时,将fbuf中前面几个数据单元的起始地址赋给buf,然后返回前面几个数据单元(不包含其它数据类型)的长度,即完成了文件头数据的读取。

当header不等于1时,会执行第三部分程序,读取一段NALU数据。可以看到第三部分程序,先是用find_nal_unit()函数读取一个数据单元,接着判断单元类型,是SPS(7),PPS(8),SEI(6)时则继续读,读到SLICE/SLICE_IDR数据单元时停止,将这端NALU数据的起始地址赋给buf,然后返回NALU数据段(包含一个SLICE/SLICE_IDR数据单元)的长度。

好了,知道文件怎么读取了,接下来解码就简单多了。首先是解码器初始化的代码。

    unsigned int buf_type = CACHE;void *openHandle;SSBSIP_MFC_ERROR_CODE err;SSBSIP_MFC_DEC_OUTPUT_INFO oinfo;FILE *fpi, *fpo;                  // input and output files// 打开输入输出文件char *ifile=DEFAULT_INPUT_FILE, *ofile=DEFAULT_OUTPUT_FILE;if(!(fpi = fopen(ifile,"rb"))) {fprintf(stderr,"Error: open input file %s.\n",ifile);return 1;}if(!(fpo = fopen(ofile,"wb"))) {fprintf(stderr,"Error: open output file %s.\n",ofile);goto clr_fpi;}printf("Input file: %s. Output file: %s.\n", ifile,ofile);//初始化文件读入bufif(init_frame_parser()<0) {fprintf(stderr,"Error: init frame parser\n");goto clr_fpo;}// find the first SPS,PPS,SEI header -> 读取h264文件头到frmbuf中int frmlen;uint8_t * frmbuf;if((frmlen=read_one_frame(fpi,&frmbuf,1))<=0) {fprintf(stderr,"Error: cannot find header\n");goto clr_parser;}// 打开解码器openHandle = SsbSipMfcDecOpen(&buf_type);if(!openHandle) {fprintf(stderr,"Error: SsbSipMfcDecOpen.\n");goto clr_parser;}printf("SsbSipMfcDecOpen succeeded.\n");// 获得解码器输入buf地址->virInBufvoid * phyInBuf;void * virInBuf;virInBuf = SsbSipMfcDecGetInBuf(openHandle, &phyInBuf, MAX_DECODER_INPUT_BUFFER_SIZE);if(!virInBuf) {fprintf(stderr,"Error: SsbSipMfcDecGetInBuf.\n");goto clr_mfc;}printf("SsbSipMfcDecGetInBuf succeeded.\n");// 将文件头数据拷贝到解码器输入bufmemcpy(virInBuf,frmbuf,frmlen);// 初始化解码器err = SsbSipMfcDecInit(openHandle, H264_DEC, frmlen);if(err<0) {fprintf(stderr,"Error: SsbSipMfcDecInit. Code %d\n",err);goto clr_mfc;}printf("SsbSipMfcDecInit succeeded..\n");

程序首先打开了输入文件和输出文件,输出文件fpo 在解码部分才会使用。输入文件即fpi 就是H264格式文件了,程序首先通过调用read_one_frame(fpi,&frmbuf,1)) 函数读出文件头数据,然后将数据拷贝入解码器输入buf,最后初始化了解码器。
    解码器初始化完成后,接下来是正式的解码过程了。代码如下。

    // now start decodingstatus = MFC_GETOUTBUF_STATUS_NULL;read_cnt = 0;show_cnt = 0;do {if (status != MFC_GETOUTBUF_DISPLAY_ONLY) {// read one frameif((frmlen = read_one_frame(fpi,&frmbuf,0))<=0) {printf("No more NALs. Exiting\n");break;}else{printf("%d frames len %d!\n", ++read_cnt, frmlen);}memcpy(virInBuf, frmbuf, frmlen);}err = SsbSipMfcDecExe(openHandle, frmlen);if(err<0) {fprintf(stderr,"Error: SsbSipMfcDecExe. Code %d\n",err);break;}memset(&oinfo, 0, sizeof(oinfo));status = SsbSipMfcDecGetOutBuf(openHandle,&oinfo);if(status==MFC_GETOUTBUF_DISPLAY_DECODING || status==MFC_GETOUTBUF_DISPLAY_ONLY) {if(!ylin)ylin = (uint8_t *)malloc(oinfo.img_width*oinfo.img_height);if(!ylin) {fprintf(stderr,"Out of memory.\n");break;}// converted tiled to linear nv12 format - Y planecsc_tiled_to_linear(ylin, (uint8_t *)oinfo.YVirAddr, oinfo.img_width, oinfo.img_height);fwrite(ylin,1, oinfo.img_width*oinfo.img_height, fpo);if(!clin)clin = (uint8_t *)malloc(oinfo.img_width*oinfo.img_height/2);if(!clin) {fprintf(stderr,"Out of memory.\n");break;}p_U = (uint8_t *)clin;p_V = (uint8_t *)clin;p_V += ((oinfo.img_width * oinfo.img_height) >> 2);// converted tiled to linear uv format - C planecsc_tiled_to_linear_deinterleave(p_U, p_V, (uint8_t *)oinfo.CVirAddr, oinfo.img_width, oinfo.img_height/2);fwrite(clin,1,oinfo.img_width*oinfo.img_height/2,fpo);show_cnt++;}} while (1);printf("Decoding completed! Total number of decoded frames: %d.\nThe video has a dimension of: ", show_cnt);printf("img %dx%d, buf %dx%d\n",oinfo.img_width,oinfo.img_height, oinfo.buf_width,oinfo.buf_height);

解码过程与编码过程类似,首先read_one_frame(fpi,&frmbuf,0)) 函数读取一段NALU数据,然后用memcpy(virInBuf, frmbuf, frmlen) 函数将数据拷贝到解码器输入buf,接着调用SsbSipMfcDecExe(openHandle, frmlen) 函数来启动一次解码,最后用SsbSipMfcDecGetOutBuf(openHandle,&oinfo) 函数获取解码的输出数据,由于解码器输出的格式是NV12,而且是tiled类型的,这里需要进行格式转换。转换时先转换Y分量,然后转换UV分量。

    csc_tiled_to_linear(ylin, (uint8_t *)oinfo.YVirAddr, oinfo.img_width, oinfo.img_height);fwrite(ylin,1, oinfo.img_width*oinfo.img_height, fpo);csc_tiled_to_linear_deinterleave(p_U, p_V, (uint8_t *)oinfo.CVirAddr, oinfo.img_width, oinfo.img_height/2);             fwrite(clin,1,oinfo.img_width*oinfo.img_height/2,fpo);

这样就完成了写一帧解码后YUV格式图像到输出文件,这个文件可以用YUV 格式播放器打开,播放器下载地址为http://www.yuvplayer.com/。

要注意的是,测试这个程序是,所选的h264格式文件不要太大,因为解码后的yuv格式文件很大,所以编码h264格式文件时,尺寸要小于640*480,帧数小于200帧最好。其实是smart210板子上可用的存储空间太小了,不到180M,不够用啊!下面一章我会写一个解码后直接用液晶显示的,不存储就不会有这个问题了。顺便调整下编码参数,使编码后的图像足够清晰。

整个工程的代码我上传到了http://download.csdn.net/detail/westlor/9396310。

四 H264解码输出yuv文件相关推荐

  1. Android解码输出yuv,Android OpenGLES2.0 直接导出YUV420数据

    Android OpenGLES2.0中提供的glReadPixels方法提供的格式只有RGB的几种格式,但是这并不妨碍我们导出YUV格式的数据,因为不管是RGBA还是YUV,都不是glReadPix ...

  2. 【数据压缩(五)】基于C++实现BMP序列转YUV文件

    基于C++实现BMP序列转YUV文件 一.实验目的 二.实验要求 三.实验原理 四.实验步骤 1.`main.cpp` 2.`bmp2yuv.h` 3.`bmp2yuv.cpp` (1)引头文件以及定 ...

  3. ffmpeg 解码视频(h264、mpeg2)输出yuv420p文件

    ffmpeg 解码视频(h264.mpeg2)输出yuv420p文件 播放yuv可以参考:ffplay -pixel_format yuv420p -video_size 768x320 -frame ...

  4. yuv编码成h264格式写成文件

    yuv编码成h264格式写成文件 (使用ffmpeg 编码yuv420p编码成h264格式) #include <stdio.h> #include <stdlib.h> #i ...

  5. [学习笔记]基于ffmpeg的视频解码,输出YUV图像到文件。

    虽然在音视频领域工作了一段时间,但是对于ffmpeg还是比较陌生,从这周开始入手学习. 拜读了雷霄骅的<最简单的基于FFMPEG+SDL的视频播放器 ver2>,受益匪浅,链接如下 htt ...

  6. ffmpeg 解码音频(aac、mp3)输出pcm文件

    ffmpeg 解码音频(aac.mp3)输出pcm文件 播放pcm可以参考: ffplay -ar 48000 -ac 2 -f f32le out.pcm main.c #include <s ...

  7. ffmpeg输出yuv的函数堆栈(h264)

    ffmpeg解码264码流输出yuv时的堆栈信息(FFMPEG2.5): main()  ffmpeg.c:3941 transcode() ffmpeg.c:3764 transcode_step( ...

  8. 【数据压缩(四)】c语言实现BMP序列转YUV文件并播放

    一.实验目的 1.理解图像文件的基本组成 2.掌握结构体作为复杂数据对象的用法.进一步熟悉由问题到程序的解决方案,并掌握编程细节:如内存分配.倒序读写.字节序.文件读写过程等 二.实验要求 (1)在图 ...

  9. 264 解码之 yuv

    博客访问量日渐减少,于是我决定丢一点技术东东上去,吸引爬虫光顾一下. 先谈谈 h.264 的编解码问题. 个人建议做视频.音频的孩子们,一定要抓住 RFC 和 standard ,然后多看开源编解码程 ...

  10. BMP文件转YUV文件_C语言实现

    一.最终实现的效果 BMP图像序列(本次实验共195张BMP图片)如下: 上述BMP图片经过程序转换后,生成YUV图像如下: 上述YUV文件经过YUVPlayer播放后,效果如下: 对于不同位数的图像 ...

最新文章

  1. 8个计算机视觉深度学习中常见的Bug
  2. R语言polygon函数绘制多边形实战
  3. TeaPot 用webgl画茶壶(3) 环境纹理和skybox
  4. 二叉树题目----2 检查两颗树是否相同 和 对称二叉树的判定
  5. 计算机控制技术实际PID控制,计算机控制技术数字PID.doc
  6. Alibaba Druid 源码阅读(四) 数据库连接池中连接获取探索
  7. mysql.sys用户权限_MySQL用户及权限小结
  8. 20179209《Linux内核原理与分析》第一周作业
  9. Python实现代码行数统计工具
  10. 基于Protostuff的通用序列化、反序列化功能实现
  11. 搭建sendmail邮件服务器
  12. [礼仪大赛/模特比赛策划方案]现场场景描述
  13. JDK安装和环境变量配置(超详细图文)
  14. 在RStudio里面部署Python
  15. G.652与G.655单模光纤分类及对比
  16. 突破与改变:酒店品牌的中高端之战
  17. windows 技巧篇-解除共享文件夹占用方法,解决共享文件被占用导致不可修改问题,查看共享文件被谁占用方法
  18. Python之ascii转中文
  19. Eureka(6)-- ClusterResolver 集群解析器
  20. Matlab中图像剪裁命令——imcrop()

热门文章

  1. android有道翻译api,有道智云自然翻译服务API
  2. FreeSSL.cn 创建免费 https 证书
  3. 小麦积分墙:如何更好的使用积分墙
  4. 用户画像 客户消费模型表
  5. ad采样频率_涨知识!模拟信号采样与AD转换详解
  6. linux ls命令ls /dev | grep sda 什么意思(以及grep连接符|怎么用)
  7. 加拿大 计算机学校排名2015,2015年加拿大大学综合排名
  8. 中兴新支点操作系统_中兴新支点操作系统
  9. 中兴新支点操作系统_中兴新支点系统预装测试
  10. 从管理的角度如何提高团队工作效率?