ffmpeg之视频的编码手动添加SPS以及PPS

Posted 蓝天巨人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ffmpeg之视频的编码手动添加SPS以及PPS相关的知识,希望对你有一定的参考价值。

#include "./rtmp_source.hpp"
#include "iostream"
using namespace std;
#define V_WIDTH 640
#define V_HEIGHT 480

static AVFormatContext *open_device()

    int ret = 0;
    char errors[1024] = 0;

    // 创建输出的缓冲区
    AVFormatContext *fmt_ctx = NULL;
    AVDictionary *options = NULL;
    char *device_name = "/dev/video0";
    // 注设备信息
    avdevice_register_all();
    AVInputFormat *ifromat = av_find_input_format("video4linux2");
    av_dict_set(&options, "video_size", "640x480", 0);
    av_dict_set(&options, "framerate", "30", 0);
    av_dict_set(&options, "pixel_format", "nv12", 0);

    if ((ret = avformat_open_input(&fmt_ctx, device_name, ifromat, &options)) < 0)
    
        av_strerror(ret, errors, 1024);
        fprintf(stderr, "failede to open video device[%d]%s", ret, errors);
        return NULL;
    
    return fmt_ctx;


// 打开编码器
static void open_coder(int width, int height, AVCodecContext **enc_ctx)

    int ret = 0;
    char errors[1024];
    AVCodec *codec = NULL;
    // 找到对应的编码器
    codec = avcodec_find_encoder_by_name("libx264");
    if (!codec)
    
        fprintf(stderr, "codec libx264 not foundtion!\\n");
        exit(-1);
    
    // 申请一个AVcodec并且将其绑定到的AVCodecContext中
    *enc_ctx = avcodec_alloc_context3(codec);
    if (!*enc_ctx)
    
        av_strerror(ret, errors, 1024);
        fprintf(stderr, "Failed to open audio device, [%d]%s\\n", ret, errors);
        exit(-1);
    
    // 设置sps pps 相关信息
    (*enc_ctx)->profile = FF_PROFILE_H264_HIGH_444;
    (*enc_ctx)->level = 5.0;
    (*enc_ctx)->width = V_WIDTH;
    (*enc_ctx)->height = V_HEIGHT;
    (*enc_ctx)->gop_size = 12;
    (*enc_ctx)->keyint_min = 5;
    (*enc_ctx)->max_b_frames = 3;
    (*enc_ctx)->has_b_frames = 1;
    // 设置参考帧的数量
    (*enc_ctx)->refs = 3;
    // 设置输入视频的格式
    (*enc_ctx)->pix_fmt = AV_PIX_FMT_YUV420P;
    // 设置对用的码率
    (*enc_ctx)->bit_rate = 600000;
    // 设置帧率
    (*enc_ctx)->time_base = AVRational1, 25;
    (*enc_ctx)->framerate = AVRational25, 1;

    // 打开对应的编码器数量
    ret = avcodec_open2(*enc_ctx, codec, NULL);
    if (ret < 0)
    

        av_strerror(ret, errors, 1024);
        fprintf(stderr, "Failed to open audio device, [%d]%s\\n", ret, errors);
        exit(-1);
    


// 打开对应的frame
static AVFrame *creat_frame(int width, int height)

    int ret = 0;
    char errors[1024];
    AVFrame *frame = NULL;
    if (!frame)
    
        av_strerror(ret, errors, 1024);
        fprintf(stderr, "Failed to open audio device, [%d]%s\\n", ret, errors);
        goto _errors;
    
    frame->width = width;
    frame->height = height;
    frame->format = AV_PIX_FMT_YUV420P;
    // 分配一个缓冲区
    ret = av_frame_get_buffer(frame, 32);
    if (ret < 0)
    
        av_strerror(ret, errors, 1024);
        fprintf(stderr, "Failed to open audio device, [%d]%s\\n", ret, errors);
        goto _errors;
    
    return frame;
_errors:
    if (frame)
    
        av_frame_free(&frame);
    
    return NULL;


static void encode(AVCodecContext *enc_ctx,
                   AVFrame *frame,
                   AVPacket *newpacket,
                   FILE *outfile)

    int ret = 0;
    char errors[1024];
    if (!frame)
    
        printf("send frame to encoder ,pts=%lld", (long long)frame->pts);
    
    // 将原始数据给的编码器进行编码
    ret = avcodec_send_frame(enc_ctx, frame);
    if (ret < 0)
    
        av_strerror(ret, errors, 1024);
        fprintf(stderr, "Failed to open audio device, [%d]%s\\n", ret, errors);
        exit(-1);
    
    // 从编码器获取编码号的输入
    while (ret >= 0)
    
        ret = avcodec_receive_packet(enc_ctx, newpacket);

        if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
        
            return;
        
        else if (ret < 0)
        
            printf("Error,failed to encode!\\n");
            exit(1);
        
        fwrite(newpacket->data, 1, newpacket->size, outfile);
        av_packet_unref(newpacket);
    


static void rev_video()

    int ret = 0;
    int base = 0;
    char errors[1024];
    AVPacket pkt;
    AVFormatContext *fmt_ctx = NULL;
    AVCodecContext *enc_ctx = NULL;

    av_log_set_level(AV_LOG_ERROR);

    char *in_file = "/home/zhao/learn/ffmpeg_learn/source/sample_vide.yuv";
    char *out_file = "/home/zhao/learn/ffmpeg_learn/source/sample_vide.h264";
    FILE *yuv_out_file = fopen(in_file, "wb+");
    FILE *h264_out_file = fopen(out_file, "wb+");

    // 建立上下文信息
    fmt_ctx = open_device();
    //打开编码器上下文
    open_coder(V_WIDTH, V_HEIGHT, &enc_ctx);
    // 创建frame缓冲区
    AVFrame *frame = creat_frame(V_WIDTH, V_HEIGHT);
    // 创建编码后的输出的packet
    AVPacket *newpkt = av_packet_alloc();
    if (!newpkt)
    
        av_strerror(ret, errors, 1024);
        fprintf(stderr, "Failed to open audio device, [%d]%s\\n", ret, errors);
        goto _errors;
    
    while ((ret = av_read_frame(fmt_ctx, &pkt)) == 0)
    
        int i = 0;
        av_log(NULL, AV_LOG_INFO, "packet size is %d\\n", pkt.size);
        // nv12 YYYYYYYYUVVU
        // YUV420 YYYYYYYYUUVV
        memcpy(frame->data[0], pkt.data, 307200);
        for (i = 0; i < 307200; i++)
        
            frame->data[1][i] = pkt.data[307200 + 2 * i];
            frame->data[2][i] = pkt.data[307200 + 2 * i + 1];
        
        fwrite(frame->data[0], 1, 307200, yuv_out_file);
        fwrite(frame->data[1], 1, 307200 / 4, yuv_out_file);
        fwrite(frame->data[2], 1, 307200 / 4, yuv_out_file);
        frame->pts = base++;
        encode(enc_ctx, frame, newpkt, h264_out_file);
        av_packet_unref(&pkt);
    
    encode(enc_ctx, NULL, newpkt, h264_out_file);

_errors:
    if (yuv_out_file)
    
        fclose(yuv_out_file);
    
    if (fmt_ctx)
    
        avformat_close_input(&fmt_ctx);
    
    av_log(NULL, AV_LOG_INFO, "finished ");
    return;

int main(int argc, char *argv[])

    rev_video();
    return 0;

        在h264流中,有两种NALU极其的重要,序列参数集Sequence Paramater Set,SPS)和图像参数集(Picture ParamaterSet,PPS

        SPS中的信息至关重要,记录了编码的prfile、level、图像宽高等,如果其中的数据丢失或出现错误,那么解码过程很可能会失败。每一帧编码后数据所依赖的参数保存于PPS中

        一般情况SPS和PPSNAL Unit通常位于整个码流的起始位置。封装文件一般进保存一次,位于文件头部,sps/sps再整个解码过程中复用,不发生变化。然而对于实时流,通常是从流中间开始解码,因此需要在每个I帧前添加SPS和PPS;如果编码器在编码过程中改变了码流参数(如分辨率),需要重新调整SPS和PPS数据。

        ffmpeg中SPSPPS数据从输入流中解析时,位于AVFormatContext->streams[video_index]->codecpar->extradata中。编码时,sps/pps数据存放于编码器上下文AVCodecContext->extradata中,但是该对象通常是空指针,还需要进行额外设置。

通常我们编码保存裸流时,仅第一个I帧前有SPS/PPS数据(见后续代码示例分析)。但是,如果需要做实时流传输,必须要在每一个I帧前添加SPS和PPS。其中SPS和PPS分别是14/5个字节

#include "./rtmp_source.hpp"
#include "iostream"
#include "signal.h"

using namespace std;
#define V_WIDTH 640
#define V_HEIGHT 480
bool bRuning = true;
void sig_handlr(int sig_num)

    bRuning = false;

int main(int argc, char const *argv[])

    signal(SIGINT, sig_handlr);

    int ret = 0;
    char errors[1024] = 0;
    avdevice_register_all();

    AVDictionary *options = NULL;
    AVFormatContext *fmt_ctx = NULL;
    char *device_name = "/dev/video0";
    AVInputFormat *ifromat = av_find_input_format("video4linux2");
    av_dict_set(&options, "video_size", "640x480", 0);
    av_dict_set(&options, "framerate", "25", 0);
    av_dict_set(&options, "pixel_format", "nv12", 0);

    if ((ret = avformat_open_input(&fmt_ctx, device_name, ifromat, &options)) < 0)
    
        av_strerror(ret, errors, 1024);
        fprintf(stderr, "failede to open video device[%d]%s", ret, errors);
        return ret;
    
    if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0)
    
        av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\\n");
        return ret;
    
    av_dump_format(fmt_ctx, 0, device_name, 0);

    if ((av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, NULL)) < 0)
    
        av_strerror(ret, errors, 1024);
        fprintf(stderr, "failede to open video device[%d]%s", ret, errors);
        avformat_close_input(&fmt_ctx);
        return ret;
    
    int video_stream_index = ret;
    // 初始化解码器
    AVCodecParameters *codepar = fmt_ctx->streams[video_stream_index]->codecpar;
    AVCodec *video_codec = avcodec_find_encoder(codepar->codec_id);
    if (!video_codec)
    
        av_log(NULL, AV_LOG_ERROR, "Can't find decoer\\n");
        return -1;
    
    // 视频编码器的上下文信息
    AVCodecContext *video_decoder_ctx = avcodec_alloc_context3(video_codec);
    if (!video_decoder_ctx)
    
        av_log(NULL, AV_LOG_ERROR, "Could not allocate a decoding context\\n");
        avformat_close_input(&fmt_ctx);
        return AVERROR(ENOMEM);
    
    // 配置解码器上下文
    if ((ret = avcodec_parameters_to_context(video_decoder_ctx, codepar)) < 0)
    
        avformat_close_input(&fmt_ctx);
        avcodec_free_context(&video_decoder_ctx);
        return ret;
    
    // 打开解码器
    if ((ret = avcodec_open2(video_decoder_ctx, video_codec, NULL)) < 0)
    
        avformat_close_input(&fmt_ctx);
        avcodec_free_context(&video_decoder_ctx);
        return ret;
    
    // 编码器的数量
    AVCodec *enc = avcodec_find_encoder_by_name("libx264");
    AVCodecContext *enc_ctx = avcodec_alloc_context3(enc);
    if (!enc_ctx)
    
        av_log(NULL, AV_LOG_ERROR, "Could not allocate a decoding context\\n");
        return AVERROR(ENOMEM);
    
    enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P;

    enc_ctx->width = 640;
    enc_ctx->height = 480;

    int fps = 25; // 降低便于查看
    enc_ctx->framerate = fps, 1;
    enc_ctx->time_base = 1, fps;
    enc_ctx->gop_size = fps;

    enc_ctx->bit_rate = 2000000; // 2M

    // 解码并保存到文件
    uint32_t frameCnt = 0;

    AVPacket *pkt = av_packet_alloc(); // 分配一个AVPactet对象,用于管理其缓冲区
    AVFrame *frame = av_frame_alloc(); // 分配一个AVFrame对象,用于管理其缓冲区

    AVPacket *enc_pkt = av_packet_alloc();

    FILE *f264 = fopen("out.h264", "wb");

    while (bRuning)
    

        ret = av_read_frame(fmt_ctx, pkt); // 循环从输入获取一帧压缩编码数据,分配pkt缓冲区
        if (ret < 0)
        
            break;
        

        // 仅处理视频码流
        if (pkt->stream_index != video_stream_index)
            continue;

        ret = avcodec_send_packet(video_decoder_ctx, pkt); // 送一帧到解码器

        while (ret >= 0)
        
            ret = avcodec_receive_frame(video_decoder_ctx, frame); // 尝试获取解码数据,分配frame缓冲区
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            
                break;
            
            else if (ret < 0)
            
                av_log(NULL, AV_LOG_ERROR, "Error while sending a packet to the decoder\\n");
                goto end;
            

            // 解码的视频数据处理

            printf("\\rSucceed to decode frame %d\\n", frameCnt++);

            // 编码
            ret = avcodec_send_frame(enc_ctx, frame); // 送一帧到解码器
            while (ret >= 0)
            
                ret = avcodec_receive_packet(enc_ctx, enc_pkt);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                
                    break;
                
                else if (ret < 0)
                
                    av_log(NULL, AV_LOG_ERROR, "Error while sending a packet to the decoder\\n");
                    goto end;
                
                // 判断当前数据包的前四个字节判断类型,
                if ((enc_pkt->data[4] & 0x1f) == 5)
                
                    fwrite(enc_ctx->extradata, 1, enc_ctx->extradata_size, f264);
                

                printf("\\rSucceed to encode frame %d\\n", frameCnt);
                fwrite(enc_pkt->data, 1, enc_pkt->size, f264);

                av_packet_unref(enc_pkt);
            

            av_frame_unref(frame); // 释放frame缓冲区数据
        

        av_packet_unref(pkt); // 释放pkt缓冲区数据
    

end:
    // 关闭输入
    avformat_close_input(&fmt_ctx);

    av_packet_free(&pkt);
    av_frame_free(&frame);

    av_dict_free(&options);

    fclose(f264);

    return 0;

ffmpeg 添加 sps pps

参考技术A 分离某些封装格式(例如MP4/FLV/MKV等)中的H.264的时候,需要首先写入SPS和PPS,否则会导致分离出来的数据没有SPS、PPS而无法播放。H.264码流的SPS和PPS信息存储在AVCodecContext结构体的extradata中。需要使用ffmpeg中名称为“h264_mp4toannexb”的bitstream filter处理。
原有的API已被弃用,新的API如下:

Query

Setup

Usage

Cleanup

ps: FFmpeg给出的例子中并未while循环调用av_bsf_receive_packet,也未对其flush。

https://blogs.gentoo.org/lu_zero/2016/03/21/bitstream-filtering/

以上是关于ffmpeg之视频的编码手动添加SPS以及PPS的主要内容,如果未能解决你的问题,请参考以下文章

SPS、PPS

使用ffmpeg命令推送rtsp流,不包含SPS和PPS帧

SPS & PPS in H.264 详解

FFmpeg解封装h264 ---- 提取SPS PPS

(原)关于获取ffmpeg解析rtsp流sdp中带有sps,pps的情况

视频知识点(20)- H264码流如何在SPS中获取宽高信息?