直接从NVENC编码器进行RTMP实时流

时间:2018-11-12 20:33:17

标签: ffmpeg mpeg-dash libav libx264

我正在尝试创建一个实时RTMP流,其中包含使用NVIDIA OptiX生成的动画。该流将由nginx + rtmp模块接收,并以MPEG-DASH格式广播。如果先将视频保存到.flv文件,然后使用ffmpeg发送该视频,而无需使用以下命令进行任何重新格式化,则dash.js播放器的完整链接将正常工作:

ffmpeg -re -i my_video.flv -c:v copy -f flv rtmp://x.x.x.x:1935/dash/test

但是我想直接从代码中流式传输。而且与此有关,我失败了……Nginx记录错误“破折号:收到无效的avcc(2:没有这样的文件或目录)”。然后,它似乎正确接收了流(分段正在滚动,其中有破折号清单),但是该流无法在浏览器中播放。

我只能看到直接流和文件流之间的清单差异。缺少直接流中表示形式的编解码器属性:从文件流传输时得到的是codecs =“ avcc1.000000”而不是“ avc1.640028”。

我的代码打开流:

av_register_all();
AVOutputFormat* fmt = av_guess_format("flv",
file_name, nullptr);
fmt->video_codec = AV_CODEC_ID_H264;

AVFormatContext* _oc;
avformat_alloc_output_context2(&_oc, fmt, nullptr, "rtmp://x.x.x.x:1935/dash/test");

AVStream* _vs = avformat_new_stream(_oc, nullptr);
_vs->id = 0;
_vs->time_base = AVRational { 1, 25 };
_vs->avg_frame_rate = AVRational{ 25, 1 };

AVCodecParameters *vpar = _vs->codecpar;
vpar->codec_id = fmt->video_codec;
vpar->codec_type = AVMEDIA_TYPE_VIDEO;
vpar->format = AV_PIX_FMT_YUV420P;
vpar->profile = FF_PROFILE_H264_HIGH;
vpar->level = _level;
vpar->width = _width;
vpar->height = _height;
vpar->bit_rate = _avg_bitrate;

avio_open(&_oc->pb, _oc->filename, AVIO_FLAG_WRITE);
avformat_write_header(_oc, nullptr);

我从NVENC编码器设置中获得的宽度,高度,比特率,电平和配置文件。我也做错误检查,这里省略。然后我有一个循环,用NVENC快速编写每个编码的数据包,IDR帧等。循环体为:

auto & pkt_data = _packets[i];
AVPacket pkt = { 0 };
av_init_packet(&pkt);
pkt.pts = av_rescale_q(_n_frames++, AVRational{ 1, 25 }, _vs->time_base);
pkt.duration = av_rescale_q(1, AVRational{ 1, 25 }, _vs->time_base);
pkt.dts = pkt.pts;
pkt.stream_index = _vs->index;
pkt.data = pkt_data.data();
pkt.size = (int)pkt_data.size();

if (!memcmp(pkt_data.data(), "\x00\x00\x00\x01\x67", 5))
{
    pkt.flags |= AV_PKT_FLAG_KEY;
}

av_write_frame(_oc, &pkt);

显然ffmpeg正在某处编写avcc代码...我不知道在何处添加此代码,以便RTMP服务器可以识别它。还是我想念其他东西?

人们非常感谢任何提示!

1 个答案:

答案 0 :(得分:2)

感谢Gyan的评论,我得以解决此问题。在wrapper中的AV_CODEC_FLAG_GLOBAL_HEADER标志之后,可以看到如何添加全局标头,在我的情况下这是缺少的。您可以直接使用NVENC API函数nvEncGetSequenceParams,但是由于我无论如何都在使用SDK,因此它会更干净一些。

所以我不得不将标题附加到AVCodecParameters::extradata

std::vector<uint8_t> payload;
_encoder->GetSequenceParams(payload);
vpar->extradata_size = payload.size();
vpar->extradata = (uint8_t*)av_mallocz(payload.size() + AV_INPUT_BUFFER_PADDING_SIZE);
memcpy(vpar->extradata, payload.data(), payload.size());

_encoder是我来自SDK的NvEncoder的实例。

wrapper在做相同的事情,但是使用了不推荐使用的结构AVCodecContext