给h264帧增加start code和sps/pps

简介: 给h264帧增加start code和sps/pps

从音视频文件中读取数据,抽取其中的h264视频数据,并保存在文件中,如果想要此文件被播放器正常解码播放,还需要添加在每个帧之前添加start code,在每个关键帧前添加sps/pps。

播放器需要知道一帧的开始位置和起始位置。

start code是一个四个字节的标识码,一般是0x 00 00 00 01,如果这帧有关键帧可能是三个字节,比如0x 00 00 01用于区分关键帧,此时后面还会跟几个字节的sps和pps数据。

pps和sps中含有视频的参数,比如宽高等,如果是以文件存储,可以只含一次就可以。当视频格参数改变时,在改变之前再添加一个pps和sps即可。

但如果要网络传输的话,考虑到丢帧,因此在每个关键帧前都需要加入sps和pps。

因为sps和pps只有几个字节,并不会增加网络负载,因此不管是文件还是实时流,一般都会在每个关键帧前加入sps和pps。

从文件中分离视频数据并单独保存在一个文件中。当从文件中读取好一个h264 packet时,在写入文件前,在每帧前加入start code,在关键帧前加入sps和pps,代码如下。

推荐一些音视频免费讲解,笔者听完了,nice!

#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>
#ifndef AV_WB32
#   define AV_WB32(p, val) do {                 \
        uint32_t d = (val);                     \
        ((uint8_t*)(p))[3] = (d);               \
        ((uint8_t*)(p))[2] = (d)>>8;            \
        ((uint8_t*)(p))[1] = (d)>>16;           \
        ((uint8_t*)(p))[0] = (d)>>24;           \
    } while(0)
#endif
#ifndef AV_RB16
#   define AV_RB16(x)                           \
    ((((const uint8_t*)(x))[0] << 8) |          \
      ((const uint8_t*)(x))[1])
#endif
static int alloc_and_copy(AVPacket *out,
                          const uint8_t *sps_pps, uint32_t sps_pps_size,
                          const uint8_t *in, uint32_t in_size)
{
    uint32_t offset         = out->size;
    uint8_t nal_header_size = offset ? 3 : 4;
    int err;
    err = av_grow_packet(out, sps_pps_size + in_size + nal_header_size);
    if (err < 0)
        return err;
    if (sps_pps)
        memcpy(out->data + offset, sps_pps, sps_pps_size);
    memcpy(out->data + sps_pps_size + nal_header_size + offset, in, in_size);
    if (!offset) {
        AV_WB32(out->data + sps_pps_size, 1);
    } else {
        (out->data + offset + sps_pps_size)[0] =
        (out->data + offset + sps_pps_size)[1] = 0;
        (out->data + offset + sps_pps_size)[2] = 1;
    }
    return 0;
}
int h264_extradata_to_annexb(const uint8_t *codec_extradata, const int codec_extradata_size, AVPacket *out_extradata, int padding)
{
    uint16_t unit_size;
    uint64_t total_size                 = 0;
    uint8_t *out                        = NULL, unit_nb, sps_done = 0,
             sps_seen                   = 0, pps_seen = 0, sps_offset = 0, pps_offset = 0;
    const uint8_t *extradata            = codec_extradata + 4;
    static const uint8_t nalu_header[4] = { 0, 0, 0, 1 };
    int length_size = (*extradata++ & 0x3) + 1; // retrieve length coded size, 用于指示表示编码数据长度所需字节数
    sps_offset = pps_offset = -1;
    /* retrieve sps and pps unit(s) */
    unit_nb = *extradata++ & 0x1f; /* number of sps unit(s) */
    if (!unit_nb) {
        goto pps;
    }else {
        sps_offset = 0;
        sps_seen = 1;
    }
    while (unit_nb--) {
        int err;
        unit_size   = AV_RB16(extradata);
        total_size += unit_size + 4;
        if (total_size > INT_MAX - padding) {
            av_log(NULL, AV_LOG_ERROR,
                   "Too big extradata size, corrupted stream or invalid MP4/AVCC bitstream\n");
            av_free(out);
            return AVERROR(EINVAL);
        }
        if (extradata + 2 + unit_size > codec_extradata + codec_extradata_size) {
            av_log(NULL, AV_LOG_ERROR, "Packet header is not contained in global extradata, "
                   "corrupted stream or invalid MP4/AVCC bitstream\n");
            av_free(out);
            return AVERROR(EINVAL);
        }
        if ((err = av_reallocp(&out, total_size + padding)) < 0)
            return err;
        memcpy(out + total_size - unit_size - 4, nalu_header, 4);
        memcpy(out + total_size - unit_size, extradata + 2, unit_size);
        extradata += 2 + unit_size;
pps:
        if (!unit_nb && !sps_done++) {
            unit_nb = *extradata++; /* number of pps unit(s) */
            if (unit_nb) {
                pps_offset = total_size;
                pps_seen = 1;
            }
        }
    }
    if (out)
        memset(out + total_size, 0, padding);
    if (!sps_seen)
        av_log(NULL, AV_LOG_WARNING,
               "Warning: SPS NALU missing or invalid. "
               "The resulting stream may not play.\n");
    if (!pps_seen)
        av_log(NULL, AV_LOG_WARNING,
               "Warning: PPS NALU missing or invalid. "
               "The resulting stream may not play.\n");
    out_extradata->data      = out;
    out_extradata->size      = total_size;
    return length_size;
}
int h264_mp4toannexb(AVFormatContext *fmt_ctx, AVPacket *in, FILE *dst_fd)
{
    AVPacket *out = NULL;
    AVPacket spspps_pkt;
    int len;
    uint8_t unit_type;
    int32_t nal_size;
    uint32_t cumul_size    = 0;
    const uint8_t *buf;
    const uint8_t *buf_end;
    int            buf_size;
    int ret = 0, i;
    out = av_packet_alloc();
    buf      = in->data;
    buf_size = in->size;
    buf_end  = in->data + in->size;
    do {
        ret= AVERROR(EINVAL);
        if (buf + 4 /*s->length_size*/ > buf_end)
            goto fail;
        for (nal_size = 0, i = 0; i<4/*s->length_size*/; i++)
            nal_size = (nal_size << 8) | buf[i];
        buf += 4; /*s->length_size;*/
        unit_type = *buf & 0x1f;
        if (nal_size > buf_end - buf || nal_size < 0)
            goto fail;
        /*
        if (unit_type == 7)
            s->idr_sps_seen = s->new_idr = 1;
        else if (unit_type == 8) {
            s->idr_pps_seen = s->new_idr = 1;
            */
            /* if SPS has not been seen yet, prepend the AVCC one to PPS */
            /*
            if (!s->idr_sps_seen) {
                if (s->sps_offset == -1)
                    av_log(ctx, AV_LOG_WARNING, "SPS not present in the stream, nor in AVCC, stream may be unreadable\n");
                else {
                    if ((ret = alloc_and_copy(out,
                                         ctx->par_out->extradata + s->sps_offset,
                                         s->pps_offset != -1 ? s->pps_offset : ctx->par_out->extradata_size - s->sps_offset,
                                         buf, nal_size)) < 0)
                        goto fail;
                    s->idr_sps_seen = 1;
                    goto next_nal;
                }
            }
        }
        */
        /* if this is a new IDR picture following an IDR picture, reset the idr flag.
         * Just check first_mb_in_slice to be 0 as this is the simplest solution.
         * This could be checking idr_pic_id instead, but would complexify the parsing. */
        /*
        if (!s->new_idr && unit_type == 5 && (buf[1] & 0x80))
            s->new_idr = 1;
        */
        /* prepend only to the first type 5 NAL unit of an IDR picture, if no sps/pps are already present */
        if (/*s->new_idr && */unit_type == 5 /*&& !s->idr_sps_seen && !s->idr_pps_seen*/) {
            h264_extradata_to_annexb( fmt_ctx->streams[in->stream_index]->codec->extradata,
                                      fmt_ctx->streams[in->stream_index]->codec->extradata_size,
                                      &spspps_pkt,
                                      AV_INPUT_BUFFER_PADDING_SIZE);
            if ((ret=alloc_and_copy(out,
                               spspps_pkt.data, spspps_pkt.size,
                               buf, nal_size)) < 0)
                goto fail;
            /*s->new_idr = 0;*/
        /* if only SPS has been seen, also insert PPS */
        }
        /*else if (s->new_idr && unit_type == 5 && s->idr_sps_seen && !s->idr_pps_seen) {
            if (s->pps_offset == -1) {
                av_log(ctx, AV_LOG_WARNING, "PPS not present in the stream, nor in AVCC, stream may be unreadable\n");
                if ((ret = alloc_and_copy(out, NULL, 0, buf, nal_size)) < 0)
                    goto fail;
            } else if ((ret = alloc_and_copy(out,
                                        ctx->par_out->extradata + s->pps_offset, ctx->par_out->extradata_size - s->pps_offset,
                                        buf, nal_size)) < 0)
                goto fail;
        }*/ else {
            if ((ret=alloc_and_copy(out, NULL, 0, buf, nal_size)) < 0)
                goto fail;
            /*
            if (!s->new_idr && unit_type == 1) {
                s->new_idr = 1;
                s->idr_sps_seen = 0;
                s->idr_pps_seen = 0;
            }
            */
        }
        len = fwrite( out->data, 1, out->size, dst_fd);
        if(len != out->size){
            av_log(NULL, AV_LOG_DEBUG, "warning, length of writed data isn't equal pkt.size(%d, %d)\n",
                    len,
                    out->size);
        }
        fflush(dst_fd);
next_nal:
        buf        += nal_size;
        cumul_size += nal_size + 4;//s->length_size;
    } while (cumul_size < buf_size);
    /*
    ret = av_packet_copy_props(out, in);
    if (ret < 0)
        goto fail;
    */
fail:
    av_packet_free(&out);
    return ret;
}
int main(int argc, char *argv[])
{
    int err_code;
    char errors[1024];
    char *src_filename = NULL;
    char *dst_filename = NULL;
    FILE *dst_fd = NULL;
    int video_stream_index = -1;
    //AVFormatContext *ofmt_ctx = NULL;
    //AVOutputFormat *output_fmt = NULL;
    //AVStream *out_stream = NULL;
    AVFormatContext *fmt_ctx = NULL;
    AVPacket pkt;
    //AVFrame *frame = NULL;
    av_log_set_level(AV_LOG_DEBUG);
    if(argc < 3){
        av_log(NULL, AV_LOG_DEBUG, "the count of parameters should be more than three!\n");
        return -1;
    }
    src_filename = argv[1];
    dst_filename = argv[2];
    if(src_filename == NULL || dst_filename == NULL){
        av_log(NULL, AV_LOG_ERROR, "src or dts file is null, plz check them!\n");
        return -1;
    }
    /*register all formats and codec*/
    av_register_all();
    dst_fd = fopen(dst_filename, "wb");
    if (!dst_fd) {
        av_log(NULL, AV_LOG_DEBUG, "Could not open destination file %s\n", dst_filename);
        return -1;
    }
    /*open input media file, and allocate format context*/
    if((err_code = avformat_open_input(&fmt_ctx, src_filename, NULL, NULL)) < 0){
        av_strerror(err_code, errors, 1024);
        av_log(NULL, AV_LOG_DEBUG, "Could not open source file: %s, %d(%s)\n",
               src_filename,
               err_code,
               errors);
        return -1;
    }
    /*dump input information*/
    av_dump_format(fmt_ctx, 0, src_filename, 0);
    /*initialize packet*/
    av_init_packet(&pkt);
    pkt.data = NULL;
    pkt.size = 0;
    /*find best video stream*/
    video_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if(video_stream_index < 0){
        av_log(NULL, AV_LOG_DEBUG, "Could not find %s stream in input file %s\n",
               av_get_media_type_string(AVMEDIA_TYPE_VIDEO),
               src_filename);
        return AVERROR(EINVAL);
    }
    /*
    if (avformat_write_header(ofmt_ctx, NULL) < 0) {
        av_log(NULL, AV_LOG_DEBUG, "Error occurred when opening output file");
        exit(1);
    }
    */
    /*read frames from media file*/
    while(av_read_frame(fmt_ctx, &pkt) >=0 ){
        if(pkt.stream_index == video_stream_index){
            /*
            pkt.stream_index = 0;
            av_write_frame(ofmt_ctx, &pkt);
            av_free_packet(&pkt);
            */
            h264_mp4toannexb(fmt_ctx, &pkt, dst_fd);
        }
        //release pkt->data
        av_packet_unref(&pkt);
    }
    //av_write_trailer(ofmt_ctx);
    /*close input media file*/
    avformat_close_input(&fmt_ctx);
    if(dst_fd) {
        fclose(dst_fd);
    }
    //avio_close(ofmt_ctx->pb);
    return 0;
}


image.png

相关文章
|
6月前
ffmpeg `AVCodecContext`的`frame_number`字段查看解码器是否正在产生输出帧
ffmpeg `AVCodecContext`的`frame_number`字段查看解码器是否正在产生输出帧
53 0
UE在Sequence输出时设置ID通道
UE在Sequence输出时设置ID通道
188 0
UE在Sequence输出时设置ID通道
UE4 使用Animation Data Modifiers修改动画片段
UE4 使用Animation Data Modifiers修改动画片段
121 0
UE4 使用Animation Data Modifiers修改动画片段
av_read_frame每次返回的视频和音频帧数
av_read_frame每次返回的视频和音频帧数
79 0
av_read_frame每次返回的视频和音频帧数
|
存储 编解码
srs报错:demux SPS/PPS : avc decode sequence header
srs报错:demux SPS/PPS : avc decode sequence header
248 0
srs报错:demux SPS/PPS : avc decode sequence header
|
网络协议 BI 调度
NR PRACH(五) type1 RA(4-step)基本过程
无线通信,最重要的前提是建立接收端和发射端之间的时间同步。
|
缓存 Go
Go --- for range会使通道中的缓存值被取出
Go --- for range会使通道中的缓存值被取出
Go --- for range会使通道中的缓存值被取出
|
缓存 网络协议 Java
TCP的Window Size和Scale参数对传输效率的影响
目前大多数互联网数据通信都是通过TCP协议进行的,了解其通信方式对提高通信效率,排查通信效率问题有很重要的意义。一. TCP的滑动窗口机制1. 概述TCP协议是可靠的通信协议,数据发送方发送给数据接收方的每一个包必须需要数据接收方返回对应的ACK,否则数据发送方就需要重传这个包。这个模式就有点像我和你面对面聊天,你一句我一句。但这种方式的缺点是效率比较低的。如果你说完一句话,我在处理其他事情,没有
6773 0
|
编解码 Android开发 数据格式
【Android RTMP】x264 编码器初始化及设置 ( 获取 x264 编码参数 | 编码规格 | 码率 | 帧率 | B帧个数 | 关键帧间隔 | 关键帧解码数据 SPS PPS )(二)
【Android RTMP】x264 编码器初始化及设置 ( 获取 x264 编码参数 | 编码规格 | 码率 | 帧率 | B帧个数 | 关键帧间隔 | 关键帧解码数据 SPS PPS )(二)
399 0
【Android RTMP】x264 编码器初始化及设置 ( 获取 x264 编码参数 | 编码规格 | 码率 | 帧率 | B帧个数 | 关键帧间隔 | 关键帧解码数据 SPS PPS )(二)
|
存储
单位换算】存储单位(bit Byte KB MB GB TB PB EB ZB YB BB)时间单位(ms μs ns ps)长度单位(dm cm mm μm nm pm fm am zm ym)
单位换算】存储单位(bit Byte KB MB GB TB PB EB ZB YB BB)时间单位(ms μs ns ps)长度单位(dm cm mm μm nm pm fm am zm ym)
596 0