ffmpeg从MP4抽取H.264视频数据

看此文章之前,建议先看一看:https://blog.csdn.net/weixin_42462202/article/details/88182605

使用ffmpeg读取H264并不能直接得到NALU单元,必须从读取出来的AVPacket与AVFormatContext->streams[video_index]->codec->extradata提取出来。

输出H.264格式,我们需要将NALU提取出来,然后组织成下面的数据结构。
在这里插入图片描述

提取流程
1、打开MP4文件,循环读取

2、从AVPacket获取IDR(nalu type==5),从AVFormatContext->streams[video_index]->codec->extradata获取SPS与PPS。

3、添加start code,给SPS与PPS前加上00 00 00 01,IDR加上00 00 01

4、按照上图的数据结构写入文件中

5、继续提取NALU,添加start code(00 00 01),写入文件中

几个注意的点
1、SPS与PPS存在于AVFormatContext1、SPS与PPS存在于AVFormatContext->streams[video_index]->codec->extradata

2、其他的NALU存在于读取出来的AVPacket中

3、AVPacket->data前四个字节表示当前NALU的大小,根据这一条件可以获取NALU

4、AVFormatContext->streams[video_index]->codec->extradata + 5,之后两个字节表示SPS的个数,

SPS的数据结束后的两个字节表示PPS的个数

源码

#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
 
/* 获取SPS与PPS */
static int h264_extradata_to_annexb(const unsigned char *pCodecExtraData, const int codecExtraDataSize, 
                                AVPacket *pOutExtradata, int padding)
{
    const unsigned char *pExtraData = NULL; /* 前四个字节没用 */
    int len = 0;
    int spsUnitNum, ppsUnitNum;
    int unitSize, totolSize = 0;
    unsigned char startCode[] = {0, 0, 0, 1};
    unsigned char *pOut = NULL;
    int err;
 
    pExtraData = pCodecExtraData+4;
    len = (*pExtraData++ & 0x3) + 1;
 
    /* 获取SPS */
    spsUnitNum = (*pExtraData++ & 0x1f); /* SPS数量 */
    while(spsUnitNum--)
    {
        unitSize = (pExtraData[0]<<8 | pExtraData[1]); /* 两个字节表示这个unit的长度 */
        pExtraData += 2;
        totolSize += unitSize + sizeof(startCode);
        printf("unitSize:%d\n", unitSize);
 
        if(totolSize > INT_MAX - padding) 
        {
            av_log(NULL, AV_LOG_ERROR,
                   "Too big extradata size, corrupted stream or invalid MP4/AVCC bitstream\n");
            av_free(pOut);
            return AVERROR(EINVAL);
        }
 
        if(pExtraData + unitSize > pCodecExtraData + codecExtraDataSize) 
        {
            av_log(NULL, AV_LOG_ERROR, "Packet header is not contained in global extradata, "
                   "corrupted stream or invalid MP4/AVCC bitstream\n");
            av_free(pOut);
            return AVERROR(EINVAL);
        }
 
        if((err = av_reallocp(&pOut, totolSize + padding)) < 0)
            return err;
        
        
        memcpy(pOut+totolSize-unitSize-sizeof(startCode), startCode, sizeof(startCode));
        memcpy(pOut+totolSize-unitSize, pExtraData, unitSize);
 
        pExtraData += unitSize;
    }
 
    /* 获取PPS */
    ppsUnitNum = (*pExtraData++ & 0x1f); /* PPS数量 */
    while(ppsUnitNum--) 
    {
        unitSize = (pExtraData[0]<<8 | pExtraData[1]); /* 两个字节表示这个unit的长度 */
        pExtraData += 2;
        totolSize += unitSize + sizeof(startCode);
        printf("unitSize:%d\n", unitSize);
 
        if(totolSize > INT_MAX - padding) 
        {
            av_log(NULL, AV_LOG_ERROR,
                   "Too big extradata size, corrupted stream or invalid MP4/AVCC bitstream\n");
            av_free(pOut);
            return AVERROR(EINVAL);
        }
 
        if(pExtraData + unitSize > pCodecExtraData + codecExtraDataSize) 
        {
            av_log(NULL, AV_LOG_ERROR, "Packet header is not contained in global extradata, "
                   "corrupted stream or invalid MP4/AVCC bitstream\n");
            av_free(pOut);
            return AVERROR(EINVAL);
        }
 
        if((err = av_reallocp(&pOut, totolSize + padding)) < 0)
            return err;
        
        
        memcpy(pOut+totolSize-unitSize-sizeof(startCode), startCode, sizeof(startCode));
        memcpy(pOut+totolSize-unitSize, pExtraData, unitSize);
 
        pExtraData += unitSize;
    }
 
    pOutExtradata->data = pOut;
    pOutExtradata->size = totolSize;
 
    return len;
}
 
/* 将数据复制并且增加start      code */
static int alloc_and_copy(AVPacket *pOutPkt, const uint8_t *spspps, uint32_t spsppsSize,
                          const uint8_t *pIn, uint32_t inSize)
{
    int err;
    int startCodeLen = 3; /* start code长度 */
 
    /* 给pOutPkt->data分配内存 */
    err = av_grow_packet(pOutPkt, spsppsSize + inSize + startCodeLen);
    if (err < 0)
        return err;
    
    if (spspps)
    {
        memcpy(pOutPkt->data , spspps, spsppsSize); /* 拷贝SPS与PPS(前面分离的时候已经加了startcode(00 00 00 01)) */
    }
    
    /* 将真正的原始数据写入packet中 */
    (pOutPkt->data + spsppsSize)[0] = 0;
    (pOutPkt->data + spsppsSize)[1] = 0;
    (pOutPkt->data + spsppsSize)[2] = 1;
    memcpy(pOutPkt->data + spsppsSize + startCodeLen , pIn, inSize);
 
    return 0;
}
 
static int h264Mp4ToAnnexb(AVFormatContext *pAVFormatContext, AVPacket *pAvPkt, FILE *pFd)
{
    unsigned char *pData = pAvPkt->data; /* 帧数据 */
    unsigned char *pEnd = NULL;
    int dataSize = pAvPkt->size; /* pAvPkt->data的数据量 */
    int curSize = 0;
    int naluSize = 0; 
    int i;
    unsigned char nalHeader, nalType;
    AVPacket spsppsPkt;
    AVPacket *pOutPkt;
    int ret;
    int len;
 
    pOutPkt = av_packet_alloc();
    pOutPkt->data = NULL;
    pOutPkt->size = 0;
    spsppsPkt.data = NULL;
    spsppsPkt.size = 0;
 
    pEnd = pData + dataSize;
 
    while(curSize < dataSize)
    {
        if(pEnd-pData < 4)
            goto fail;
 
        /* 前四个字节表示当前NALU的大小 */
        for(i = 0; i < 4; i++)
        {
            naluSize <<= 8;
            naluSize |= pData[i];
        }
 
        pData += 4;
 
        if(naluSize > (pEnd-pData+1) || naluSize <= 0)
        {
            goto fail;
        }
        
        nalHeader = *pData;
        nalType = nalHeader&0x1F;
        if(nalType == 5)
        {
            /* 得到SPS与PPS(存在与codec->extradata中) */
            h264_extradata_to_annexb(pAVFormatContext->streams[pAvPkt->stream_index]->codec->extradata,
                                    pAVFormatContext->streams[pAvPkt->stream_index]->codec->extradata_size,
                                    &spsppsPkt, AV_INPUT_BUFFER_PADDING_SIZE);
            /* 添加start code */
            ret = alloc_and_copy(pOutPkt, spsppsPkt.data, spsppsPkt.size, pData, naluSize);
            if(ret < 0)
                goto fail;
        }
        else
        {
            /* 添加start code */
            ret = alloc_and_copy(pOutPkt, NULL, 0, pData, naluSize);
            if(ret < 0)
                goto fail;
        }
 
        /* 将处理好的数据写入文件中 */
        len = fwrite(pOutPkt->data, 1, pOutPkt->size, pFd);
        if(len != pOutPkt->size)
        {
            av_log(NULL, AV_LOG_DEBUG, "fwrite warning(%d, %d)!\n", len, pOutPkt->size);
        }
 
        /* 将数据从缓冲区写入磁盘 */
        fflush(pFd);
 
        curSize += (naluSize+4);
        pData += naluSize; /* 处理下一个NALU */
    }
    
fail:
    av_packet_free(&pOutPkt);
    if(spsppsPkt.data)
    {
        free(spsppsPkt.data);
        spsppsPkt.data = NULL;
    }
        
    return 0;
}
 
static int parseH264FromMp4(char *pSrc, char *pDst)
{
    AVFormatContext *pAVFormatContext = NULL;
    AVInputFormat avInputFormat;
    AVPacket avPkt;
    FILE *pFd = NULL;
    int videoStreamIndex; /* 视频流对应的index */
    int ret;
    int len;
 
    av_log_set_level(AV_LOG_DEBUG); /* 设置日志打印级别 */
    av_register_all(); /* 对ffmpeg的初始化 */
 
    /* 打开video文件 */
    ret = avformat_open_input(&pAVFormatContext, pSrc, NULL, NULL);
    if(ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "avformat_open_input:%s\n", av_err2str(ret));
        return -1;
    }
 
    /* 打印video文件信息 */
    av_dump_format(pAVFormatContext, 0, pSrc, 0);
 
    /* 找到最好的一路视频流 */
    videoStreamIndex = av_find_best_stream(pAVFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if(videoStreamIndex < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "av_find_best_stream error!\n");
        avformat_close_input(&pAVFormatContext);
    }
 
    /* 初始化一个数据包 */
    av_init_packet(&avPkt);
    /* 读取到的数据存储在这里 */
    avPkt.data = NULL;
    avPkt.size = 0;
 
    /* 打开输出文件 */
    pFd = fopen(pDst, "wb");
    if(!pFd)
    {
        av_log(NULL, AV_LOG_ERROR, "fopen error!\n");
        avformat_close_input(&pAVFormatContext);
    }
 
    /* 开始读取数据包 */
    while(av_read_frame(pAVFormatContext, &avPkt) >= 0)
    {
        if(avPkt.stream_index == videoStreamIndex) /* 当前数据包属于视频流 */
        {
            /* 处理每一个数据包 */
            h264Mp4ToAnnexb(pAVFormatContext, &avPkt, pFd);
        }
 
        av_packet_unref(&avPkt);
    }
 
    /* 关闭video文件 */
    avformat_close_input(&pAVFormatContext);
 
    if(pFd)
        fclose(pFd);
 
    return 0;
}
 
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        av_log(NULL, AV_LOG_INFO, "arg error\n");
        return -1;
    }
 
    /* argv[1]为输入MP4文件,argv[2]为输出的h264文件 */
    parseH264FromMp4(argv[1], argv[2]);
 
    return 0;
}
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值