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 = AVRational{1, 25};
    (*enc_ctx)->framerate = AVRational{25, 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;
}

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SPS(Sequence Parameter Set)和PPS(Picture Parameter Set)是H.264视频编码标准中的两个重要参数集,包含了视频编码的一些基本参数信息,比如视频的分辨率、码率、率、GOP大小等等。 在使用ffmpeg进行视频推流时,如果服务器报告“没有SPSPPS”错误,通常是因为推送的视频流中没有包含SPSPPS参数集。这种情况下,需要在代码中手动添加SPSPPS参数集。 具体的做法是: 1. 在代码中获取到视频编码器的AVCodecContext结构体。 2. 从AVCodecContext中获取到AVCodecParameters结构体。 3. 从AVCodecParameters中获取到SPSPPS参数集的数据。 4. 将SPSPPS数据分别打包成NALU单元并发送给服务器。 以下是参考代码: ``` AVCodecContext* codec_ctx = ...;//获取到编码器的AVCodecContext结构体 AVCodecParameters* codec_params = codec_ctx->codecpar;//获取到编码器的AVCodecParameters结构体 //获取SPSPPS数据 uint8_t* sps_data = codec_params->extradata + 4; uint32_t sps_size = (codec_params->extradata[0] << 8) | codec_params->extradata[1]; uint8_t* pps_data = sps_data + sps_size + 1; uint32_t pps_size = ((pps_data[0] << 8) | pps_data[1]) & 0x00FFFFFF; //打包SPS数据 uint8_t* sps_nalu = new uint8_t[4 + sps_size]; sps_nalu[0] = 0x00; sps_nalu[1] = 0x00; sps_nalu[2] = 0x00; sps_nalu[3] = 0x01; memcpy(sps_nalu + 4, sps_data, sps_size); //打包PPS数据 uint8_t* pps_nalu = new uint8_t[4 + pps_size]; pps_nalu[0] = 0x00; pps_nalu[1] = 0x00; pps_nalu[2] = 0x00; pps_nalu[3] = 0x01; memcpy(pps_nalu + 4, pps_data, pps_size); //发送SPSPPS数据 send_to_server(sps_nalu, 4 + sps_size); send_to_server(pps_nalu, 4 + pps_size); delete[] sps_nalu; delete[] pps_nalu; ``` 其中,send_to_server函数用于将数据发送给服务器,具体实现可以根据自己的需求进行编
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值