在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。
示例代码
这里先以AVDevice
打开摄像头,将原始的rawvideo编码的yuv数据解析后,进行h264编码直接保存,查看其sps和pps数据保存情况。
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
#include "libavformat/avformat.h"
#include "libavdevice/avdevice.h"
#ifdef __cplusplus
}
#endif
#include <signal.h>
bool bRuning = true;
void sig_handle(int sig_num)
{
bRuning = false;
}
int main()
{
signal(SIGINT, sig_handle);
int ret;
avdevice_register_all(); // 必须执行,否则av_find_input_format失败
const char* input_file = "/dev/video0";
AVDictionary *options = NULL;
av_dict_set(&options, "f", "v4l2", 0); // valid, for replacing AVInputFormat argument
//av_dict_set(&options, "input_format", "h264", 0); // valid
av_dict_set(&options, "pixel_format", "yuv420p", 0); // valid
//av_dict_set(&options, "input_format", "yuv420p", 0); // valid , 同上
av_dict_set(&options, "video_size", "1280x720", 0); // valid
av_dict_set(&options, "framerate", "25", 0); // valid
AVFormatContext* input_fmt_ctx = NULL; // 必须设置NULL
if((ret = avformat_open_input(&input_fmt_ctx, input_file, NULL, &options)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot open input file\n");
return ret;
}
// 分析流信息
if((ret = avformat_find_stream_info(input_fmt_ctx, NULL)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");
return ret;
}
// 打印信息
av_dump_format(input_fmt_ctx, 0, input_file, 0);
//---------------------- 解码部分 ----------------------//
int video_stream_index = -1;
AVCodec *video_codec;
AVCodecContext *video_decoder_ctx;
// 查找视频流
//if((ret = av_find_best_stream(input_fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &video_codec, -1)) < 0) {
if((ret = av_find_best_stream(input_fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, -1)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot find an video stream in the input file\n");
avformat_close_input(&input_fmt_ctx);
return ret;
}
video_stream_index = ret;
// // 解码器初始化
AVCodecParameters *codecpar = input_fmt_ctx->streams[video_stream_index]->codecpar;
video_codec = avcodec_find_decoder(codecpar->codec_id);
if(!video_codec) {
av_log(NULL, AV_LOG_ERROR, "Can't find decoder\n");
return -1;
}
video_decoder_ctx = avcodec_alloc_context3(video_codec);
if(!video_decoder_ctx) {
av_log(NULL, AV_LOG_ERROR, "C