关于ngx_rtmp_hls_append_sps_pps造成的hls: error appenging SPS/PPS NALs错误

关于ngx_rtmp_hls_append_sps_pps造成的hls: error appenging SPS/PPS NALs错误

一giao我哩giao。

今天在调试同rtmp配合nginx推流的时候,发现推live可以推成功,结果在推hls时出现以上错误,经过一顿猛虎操作,发现nginx错误日志报出了一下图片错误:
在这里插入图片描述

然后根据这个狗东西回到源码看看是什么原因造成的,看下面图片,看到是先因为在ngx_rtmp_hls_copy拷贝时缺少了5个字节的原因造成的那个狗东西。*in是类似一种节点,里面包含当前缓冲区和指向下一个缓冲区的next指针。
上面即(*in)->next时已经没有数据了,所以读取失败,最终导致hls: error appenging SPS/PPS NALs错误。
在这里插入图片描述
在这里插入图片描述

然后就开始分析分析…,为什么live都可以播放,而hls说我缺少sps/pps部分字节呢?而且我的h264裸流是包含sps/pps数据的,因为我保存下来验证多次了。所以在这里也非常的头疼。。。。

然后接着就去ffmpeg的源码中尝试寻找答案,首先肯定在写头中寻找,因为它里面有保存着关于sps/pps内容的extredata和其大小。然后跟踪该写头源码函数,发现当我们在初始化输出流的上下文AVCodecContext设置是AV_CODEC_ID_H264而不是AV_CODEC_ID_AAC格式时,ffmpeg会自动调用ff_isom_write_avcc帮我们转成AV_CODEC_ID_AAC(应该是为了在nginx在传输)。
在这里插入图片描述

结果重点来了:
跳到ff_isom_write_avcc函数中将AV_CODEC_ID_H264转化时,发现len如果小于等于6就不帮你转以至于无法转成AV_CODEC_ID_AAC在nginx传输,所以就报出上面的狗东西。
继续往下看。
在这里插入图片描述

看回上上张图片,这个len就是我们par->extradata_size,也就是用于保存sps/pps的数据,他在写头发送首包的时候被发送,看到这里已经明白了把,终于顺藤摸瓜找到源头了。

然后我立马回到初始化的函数当中看,虽然也是赋值了,但是却被后面的语句给覆盖了。。。。。。真是我日xxx。
部分代码如下(因为保密所以拷贝网上其它代码作示例):

//初始化部分
//codec_ctx->time_base = AVRational{1, 25};
//codec_ctx->bit_rate = 1400 * 1000;
stream->codecpar->time_base = AVRational{
    
    1, 25};//codecpar是新版本的解码上下文,因为旧版的后续将被分离不再使用。所以现在要慢慢习惯要新版本的。
stream->codecpar->bit_rate = 1400 * 1000;
//记住初始化一定要添加extradata数据和extradata_size大小。具体怎么做百度有的。

//结果一拷贝的时候由于我是直接给codecpar赋值而没有给codec_ctx赋值,结果就被覆盖掉了。
avcodec_parameters_from_context(stream->codecpar, codec_ctx);

所以改过来后,应该是先给旧版的AVCodecContext赋值,在赋值给新版的codecpar。

//初始化部分
codec_ctx->time_base = AVRational{
    
    1, 25};
codec_ctx->bit_rate = 1400 * 1000;
//stream->codecpar->time_base = AVRational{1, 25};//codecpar是新版本的解码上下文,因为旧版的后续将被分离不再使用。所以现在要慢慢习惯要新版本的。
//stream->codecpar->bit_rate = 1400 * 1000;
//记住初始化一定要添加extradata数据和extradata_size大小。具体怎么做百度有的。

//结果一拷贝的时候由于我是直接给codecpar赋值而没有给codec_ctx赋值,结果就被覆盖掉了。
avcodec_parameters_from_context(stream->codecpar, codec_ctx);

但是还没完,还有一点也非常重要,就是ffmpeg打开网络流和拷贝值到新版的顺序。必须要在打开网络流后才能拷贝值到新版中。即完整的写法不是上面两个,而是这个第三个写法:

//1 各种初始化,这里只写两个。
codec_ctx->time_base = AVRational{
    
    1, 25};//应该根据帧率设置
codec_ctx->bit_rate = 1400 * 1000;
//记住初始化一定要添加extradata数据和extradata_size大小。具体怎么做百度有的。

//2
avcodec_open2(codec_ctx, codec, nullptr);

//3 将AVCodecContext的成员复制到AVCodecParameters结构体。前后两行不能调换顺序
avcodec_parameters_from_context(stream->codecpar, codec_ctx);

总结:
实际上上面的错误改法步骤就是:
1)添加sps/pps数据和大小。extradata,extradata_size。
2)打开网络流和拷贝值到新版codecpar(编码时)的顺序。

这就是本篇文章的核心内容。

注意!!!:以上是编码时候写法,因为编码是由我们自定义初始化的,旧版的AVCodecContext可以认为是没有内容的,所以我们在编码时需要给旧版赋值在拷贝到新版中。两个结构最好都要有值,因为好像只有一个有我试过好像也不行。
而解码时:由于我们编码的时候已经对新版的codecpar赋过值了,所以应该以新版的值为基准,将新版的值拷贝给旧版。和上面的函数不一样,该函数为:

avcodec_parameters_to_context(codec_ctx, stream->codecpar);

具体对新版AVCodecParameters的使用可以看一下的文章:
用AVCodecParameters代替AVCodecContext

猜你喜欢

转载自blog.csdn.net/weixin_44517656/article/details/109259180
PPS