ffmepg H264 NALU分析

对于视频⽂件来说,视频由单张图⽚帧所组成,⽐如每秒25帧,但是图⽚帧的像素块之间存在
相似性,因此视频帧图像可以进⾏图像压缩;H264采⽤了16*16的分块⼤⼩对,视频帧图像
进⾏相似⽐较和压缩编码。如下图所示:

H264中的I帧、P帧和B帧 

H26使⽤帧内压缩和帧间压缩的⽅式提⾼编码压缩率;H264采⽤了独特的I帧、P帧和B帧策略
来实现,连续帧之间的压缩;

H264除了实现了对视频的压缩处理之外,为了⽅便⽹络传输,提供了对应的视频编码和分⽚
策略;类似于⽹络数据封装成IP帧,在H264中将其称为组(GOP, group of pictures)、⽚
(slice)、宏块(Macroblock)这些⼀起组成了H264的码流分层结构;H264将其组织成为
序列(GOP)、图⽚(pictrue)、⽚(Slice)、宏块(Macroblock)、⼦块(subblock)五个层次 

GOP (图像组)主要⽤作形容⼀个IDR帧 到下⼀个IDR帧之间的间隔了多少个帧。

H264将视频分为连续的帧进⾏传输,在连续的帧之间使⽤I帧、P帧和B帧。同时对于帧内⽽
⾔,将图像分块为⽚、宏块和字块进⾏分⽚传输;通过这个过程实现对视频⽂件的压缩包装。
IDR( Instantaneous Decoding Refresh,即时解码刷新 ) 

⼀个序列的第⼀个图像叫做 IDR 图像(⽴即刷新图像),IDR 图像都是 I 帧图像。
I和IDR帧都使⽤帧内预测。I帧不⽤参考任何帧,但是之后的P帧和B帧是有可能参考这个I帧之
前的帧的。IDR就不允许这样。⽐如(解码的顺序):
IDR1 P4 B2 B3 P7 B5 B6  I10 B8 B9 P13 B11 B12 P16 B14 B15 这⾥的B8可以跨过I10去参考P7
原始图像: IDR1 B2 B3 P4  B5 B6 P7 B8 B9  I10
IDR1 P4 B2 B3 P7 B5 B6 IDR8 P11 B9 B10 P14 B11 B12 这⾥的B9就只能参照IDR8和P11,不可以
参考IDR8前⾯的帧
其核⼼作⽤是,是为了解码的重同步,当解码器解码到 IDR 图像时,⽴即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始⼀个新的序列。这样,如果前⼀
个序列出现重⼤错误,在这⾥可以获得重新同步的机会。IDR图像之后的图像永远不会使⽤
IDR之前的图像的数据来解码。

 NALU

 

 

 

这儿我们只关注5,6,7,8点

FFmepg提取H264的格式

#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>


static char err_buff[128]={0};
static char* av_get_err(int errnum){
    av_strerror(errnum,err_buff,128);
    return err_buff;
}

/*
AvCodecContext->extradata[]中为nalu长度
*   codec_extradata:
*   1, 64, 0, 1f, ff, e1, [0, 18], 67, 64, 0, 1f, ac, c8, 60, 78, 1b, 7e,
*   78, 40, 0, 0, fa, 40, 0, 3a, 98, 3, c6, c, 66, 80,
*   1, [0, 5],68, e9, 78, bc, b0, 0,
*/

//ffmpeg -i 2018.mp4 -codec copy -bsf:h264_mp4toannexb -f h264 tmp.h264
//ffmpeg 从mp4上提取H264的nalu h
int main(int argc, char **argv)
{
    AVFormatContext *ifmt_ctx=NULL;
    int videoIndex=-1;
    AVPacket *pkt=NULL;
    int ret=-1;
    int file_end=0;//判斷是都讀取結束
    if(argc<3){
        printf("error could not alllcate context\n.");
         // avformat_close_input(&ifmt_ctx);
        return -1;
    }
    FILE *outfp=fopen(argv[2],"wb");
    printf("in:%s out:%s",argv[1],argv[2]);
// 分解解复用器的内存,使用avformat_close_input 释放
    ifmt_ctx=avformat_alloc_context();
    if(!ifmt_ctx){
        printf("error could not allocate context.\n");
        return -1;

    }
    ret=avformat_open_input(&ifmt_ctx,argv[1],NULL,NULL);
    if(ret!=0){
        printf("error avformat_open_input:%s\n",av_get_err(ret));
        return -1;
    }
    ret=avformat_find_stream_info(ifmt_ctx,NULL);
    if(ret<0){
        printf("error avformat_find_stream_info :%s\n",av_get_err(ret));
          avformat_close_input(&ifmt_ctx);
        return -1;
    }
    //查找出哪個m码流是video/audio/subtitles

    videoIndex=-1;
    videoIndex=av_find_best_stream(ifmt_ctx,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0);
    if(videoIndex==-1){
        printf("Did not find a video stream.\n");
        avformat_close_input(&ifmt_ctx);
        return -1;
    }

    //分配数据包
    pkt=av_packet_alloc();
    //
    av_init_packet(pkt);
    //获取相应的比特流过滤器
    //FLV/MP4/MKV中,h264需要h264_mp4toannexb 处理,添加SPS和PPS等信息
    //FLV 封装时,可以把多个NALU放在VIDEO TAG中,结构中4B NALU长度+NALU1+4B
    const AVBitStreamFilter *bsFilter=av_bsf_get_by_name("h264_mp4toannexb");
    //初始化过滤器上下文
    AVBSFContext *bsf_ctx=NULL;
    av_bsf_alloc(bsFilter,&bsf_ctx);
    // 添加解码器属性
    avcodec_parameters_copy(bsf_ctx->par_in,ifmt_ctx->streams[videoIndex]->codecpar);
    av_bsf_init(bsf_ctx);
    file_end=0;
    while(0==file_end){
        if(ret=av_read_frame(ifmt_ctx,pkt)<0){
            file_end=1;
            printf("read file end:ret:%d\n,",ret);

        }
        if(ret==0&&pkt->stream_index==videoIndex)
        {
#if 0
            int input_size=pkt->size;
            int out_pkt_count=0;
            if(av_bsf_send_packet(bsf_ctx,pkt)!=0){
                av_packet_unref(pkt);//你不用了酒吧资源释放掉
                continue;
            }
            av_packet_unref(pkt);//释放资源
            while(av_bsf_receive_packet(bsf_ctx,pkt)==0){
                out_pkt_count++;
                //printf
                size_t size=fwrite(pkt->data,1,pkt->size,outfp);
                if(size!=pkt->size){
                    printf("fwrite failed->write:%u\n",size,pkt->size);
                }
                av_packet_unref(pkt);
            }
            if(out_pkt_count>=2){
                printf("cur_pkt(size:%d) only get 1 out pkt:%u",input_size,out_pkt_count);
            }
#else
            size_t size=fwrite(pkt->data,1,pkt->size,outfp);
            if(size!=pkt->size){
                printf("fwrite failed->write:%u,pkt->size:%u\n",size,pkt->size);
            }
            av_packet_unref(pkt);
#endif

        }
        else{
            if(ret==0){
                av_packet_unref(pkt);// 释放内存
            }
        }
    }
    if(outfp)
        fclose(outfp);
    if(bsf_ctx){

        av_bsf_free(&bsf_ctx);
    }

    if(pkt)

    {
        av_packet_free(&pkt);
    }

    if(ifmt_ctx){
        avformat_close_input(&ifmt_ctx);
    }

    printf("finish\n");

    return 0;
}
 

 

源码下载地址:

GitHub - weixiaoq20/ffmepg: ffmepg学习   里面ffmepg文件下的07-03-extract-h264这个目录,运行的环境是Qt Creator 4.5.1

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值