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和PPS的NAL Unit通常位于整个码流的起始位置。封装文件一般进保存一次,位于文件头部,sps/sps再整个解码过程中复用,不发生变化。然而对于实时流,通常是从流中间开始解码,因此需要在每个I帧前添加SPS和PPS;如果编码器在编码过程中改变了码流参数(如分辨率),需要重新调整SPS和PPS数据。
ffmpeg中SPS、PPS数据从输入流中解析时,位于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的主要内容,如果未能解决你的问题,请参考以下文章