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存在于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;
}


 

在Android中使用FFmpeg提取MP4中的H264视频流,可以使用FFmpeg的C库,以下是基本的步骤: 1. 在 Android 项目中添加 FFmpeg C库文件和头文件。 2. 使用 FFmpeg 的 API 打开 MP4 文件并解析出 H264 视频流的信息,例如视频流的宽度、高度、码率、帧率等。 3. 使用 FFmpeg 的 API 打开视频流并解码每一帧视频,将解码后的视频帧保存到文件或渲染到 Android 的 SurfaceView 上。 4. 关闭 FFmpeg 解码器和输入文件。 下面是一个简单的示例代码,可以帮助你开始使用 FFmpeg 解码 H264 视频流: ``` #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> // FFmpeg 头文件 #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" int main(int argc, char* argv[]) { // 初始化 FFmpeg 库 av_register_all(); // 打开 MP4 文件 AVFormatContext *pFormatCtx = NULL; if (avformat_open_input(&pFormatCtx, "input.mp4", NULL, NULL) != 0) { printf("Error: could not open input file.\n"); return -1; } // 查找视频流信息 if (avformat_find_stream_info(pFormatCtx, NULL) < 0) { printf("Error: could not find stream information.\n"); return -1; } // 查找视频流索引 int videoStream = -1; for (int i = 0; i < pFormatCtx->nb_streams; i++) { if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoStream = i; break; } } if (videoStream == -1) { printf("Error: could not find video stream.\n"); return -1; } // 获取视频流解码器 AVCodecParameters *pCodecPar = pFormatCtx->streams[videoStream]->codecpar; AVCodec *pCodec = avcodec_find_decoder(pCodecPar->codec_id); if (pCodec == NULL) { printf("Error: unsupported codec.\n"); return -1; } // 打开视频流解码器 AVCodecContext *pCodecCtx = avcodec_alloc_context3(pCodec); if (avcodec_parameters_to_context(pCodecCtx, pCodecPar) < 0) { printf("Error: could not copy codec parameters to decoder context.\n"); return -1; } if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { printf("Error: could not open codec.\n"); return -1; } // 分配视频帧缓冲区 AVFrame *pFrame = av_frame_alloc(); AVFrame *pFrameRGB = av_frame_alloc(); if (pFrameRGB == NULL || pFrame == NULL) { printf("Error: could not allocate frame.\n"); return -1; } // 计算视频帧大小 int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height, 1); uint8_t *buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t)); // 初始化视频帧缓冲区 av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, buffer, AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height, 1); // 创建视频转换器 struct SwsContext *sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL); // 读取视频帧并解码 AVPacket packet; while (av_read_frame(pFormatCtx, &packet) >= 0) { if (packet.stream_index == videoStream) { avcodec_send_packet(pCodecCtx, &packet); while (avcodec_receive_frame(pCodecCtx, pFrame) == 0) { // 将视频帧转换为 RGB 格式 sws_scale(sws_ctx, (const uint8_t * const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize); // 处理 RGB 格式的视频帧,例如保存到文件或渲染到 SurfaceView 上 // ... // 释放视频帧资源 av_frame_unref(pFrame); } } av_packet_unref(&packet); } // 释放资源 av_free(buffer); av_frame_free(&pFrameRGB); av_frame_free(&pFrame); avcodec_close(pCodecCtx); avcodec_free_context(&pCodecCtx); avformat_close_input(&pFormatCtx); return 0; } ``` 这只是一个简单的示例代码,你需要根据自己的需求进行修改和扩展。同时,需要注意的是,FFmpeg 的使用需要遵循相关的协议和法律规定,不要用于非法用途。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值