前言
IP相机RTSP取H264码流,其定义了自定义SEI信息,具体信息为时间戳,但在调试时发现使用FFmpeg4.2+x264软解码或FFmpeg4.2+nvmpi硬解码时,解析出的时间戳信息与相机叠加在图像上的OSD时间不对应。通过分析源码等手段进行问题分析,解码过程中AVPacket和AVFrame存在队列处理?(待进一步研究),并通过请教其他人,发现FFmpeg4.4及以上版本中,AVFrame中AVFrameSideData附带对应解码出此Frame的AVPacket中的Unregistered SEI信息,因此在Jetson AGX Xavier(Jetpack4.4)搭建FFmpeg4.4+x264+nvmpi测试环境(环境搭建已另外一个帖子中记录),测试AVFrame-AVFrameSideData中Unregistered 信息是否与AVPacket中SEI自定义时间戳信息是否一致。
测试环境:FFmpeg4.4+x264+nvmpi测试环境(环境搭建见:FFmpeg4.4+x264+nvmpi测试环境搭建)
测试代码
#include <string>
#include <iostream>
#include <opencv2/opencv.hpp>
extern "C"
{
#include "libavutil/imgutils.h"
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
}
int main()
{
av_register_all();
avcodec_register_all();
avformat_network_init();
av_log_set_level(AV_LOG_QUIET);
AVFormatContext *fmt_ctx;
AVCodecContext *pCodecCtx;
AVCodec *pCodec;
AVStream *st;
AVPacket *pkt;
AVFrame *pFrame;
char *url = "xxx"; //rtsp取流地址
AVDictionary *opts = NULL;
av_dict_set(&opts, "rtsp_transport", "udp", 0);
av_dict_set(&opts, "stimeout", "1500000", 0);
av_dict_set(&opts, "max_delay", "300000", 0);
av_dict_set(&opts, "buffer_size", "2048000", 0);
fmt_ctx = NULL;
// 获取上下文
if (avformat_open_input(&fmt_ctx, url, NULL, &opts) != 0)
{
printf("Couldn't open input stream.\n");
return -1;
}
if (avformat_find_stream_info(fmt_ctx, NULL) < 0)
{
printf("Couldn't find stream information.\n");
return -1;
}
int videoindex = -1;
for (int i = 0; i < fmt_ctx->nb_streams; i++) // find video stream index
{
st = fmt_ctx->streams[i];
if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoindex = i;
}
}
if (-1 == videoindex)
{
printf("No H.264 video stream in the input file\n");
return -1;
}
// 获取打开解码器
pCodecCtx = avcodec_alloc_context3(NULL);
if (avcodec_parameters_to_context(pCodecCtx, fmt_ctx->streams[videoindex]->codecpar) < 0)
{
printf("avcodec_parameters_to_context failed\n");
return -1;
}
if (pCodecCtx->codec_id == AV_CODEC_ID_H264)
{
// pCodec = avcodec_find_decoder_by_name("h264_nvmpi");
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
}
if (pCodec == NULL)
{
printf("Codec not found.\n");
return -1;
}
pCodecCtx->flags |= AV_CODEC_FLAG_LOW_DELAY; //加上这行好像对解码器时间延迟没什么用???
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
{
printf("open codec failed!\n");
return -1;
}
// 取流/解码/叠图/显示
pFrame = av_frame_alloc();
pkt = (AVPacket *)av_malloc(sizeof(AVPacket));
while (av_read_frame(fmt_ctx, pkt) >= 0)
{
if (pkt->stream_index == videoindex)
{
int ret = 0;
int got_picture = 0;
ret = avcodec_send_packet(pCodecCtx, pkt);
if (ret < 0)
{
std::cout << "Error submitting a packet for decoding." << std::endl;
continue;
}
while (1)
{
ret = avcodec_receive_frame(pCodecCtx, pFrame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
else if (ret < 0)
{
std::cout << "Error during decoding." << std::endl;
return -1;
}
if (pFrame->nb_side_data > 0)
{
const AVFrameSideData *sei_data = av_frame_get_side_data(pFrame, AV_FRAME_DATA_SEI_UNREGISTERED);
unsigned long long tmp_timestamp = 0;
// SEI中自定义信息结构为UUID+Data,其中UUID占16字节,Data占8字节
memcpy(&tmp_timestamp, sei_data->data + 16, sizeof(tmp_timestamp));
}
}
// 省略时间戳叠图代码和图像显示
}
av_free_packet(pkt);
}
av_dict_free(&opts);
avformat_close_input(&fmt_ctx);
avcodec_close(pCodecCtx);
av_frame_free(&pFrame);
return 0;
}
结果
-
FFmpeg4.4+x264软解码
AVFrame中AVFrameSideData中可以解析出AV_FRAME_DATA_SEI_UNREGISTERED类型SEI信息,通过AVFrame时间戳与OSD时间比较,两者可以对应上 -
FFmpeg4.4+nvmpi硬解码
AVFrame中AVFrameSideData中无数据,应该与解码器实现有关,待进一步研究
如有不对之处,请大家指出。