ffmpeg 写入和读取sei,用于计算推流精确延迟

由于我的程序,只针对个人需求,所以输入文件仅限于通过ffmpeg转码出来的视频。

ffmpeg默认转出来的sei的tpye是5, 且读取packet的data时发现只在第一个关键帧前面有sei.


 


#include 

#define __STDC_CONSTANT_MACROS

#ifdef _WIN32
//Windows
extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/mathematics.h"
#include "libavutil/time.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include 
#include 
#include 
#ifdef __cplusplus
};
#endif
#endif


void PrintBuffer(void* pBuff, unsigned int nLen)
{
	if (NULL == pBuff || 0 == nLen)
	{
		return;
	}

	const int nBytePerLine = 16;
	unsigned char* p = (unsigned char*)pBuff;

	char szHex[49] = { 0 };

	printf("-----------------begin-------------------\n");
	unsigned int i = 0;
	for (i = 0; i < nLen; ++i)
	{
		int idx = 3 * (i % nBytePerLine);
		if (0 == idx)
		{
			memset(szHex, 0, sizeof(szHex));
		}
#ifdef WIN32
		sprintf_s(&szHex[idx], 4, "%02x ", p[i]);// buff长度要多传入1个字节
#else
		snprintf(&szHex[idx], 4, "%02x ", p[i]); // buff长度要多传入1个字节
#endif

		// 以16个字节为一行,进行打印
		if (0 == ((i + 1) % nBytePerLine))
		{
			printf("%s\n", szHex);
		}
	}

	// 打印最后一行未满16个字节的内容
	if (0 != (nLen % nBytePerLine))
	{
		printf("%s\n", szHex);
	}

	printf("------------------end-------------------\n");
}

//大端转小端
uint32_t reversebytes(uint32_t value) {
	return (value & 0x000000FFU) << 24 | (value & 0x0000FF00U) << 8 |
		(value & 0x00FF0000U) >> 8 | (value & 0xFF000000U) >> 24;
}
 

void put_sei_time(uint8_t** sei_data){
	uint8_t* pdata = *sei_data;
	time_t t = time(NULL);
	tm *tp = localtime(&t);
	// 北京时间
	printf("\n\nPut %d/%d/%d  %d:%d:%d\n", tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec);
	*(pdata + 7) = tp->tm_hour;
	*(pdata + 8) = tp->tm_min;
	*(pdata + 9) = tp->tm_sec;
}

int main2(int argc, char* argv[])
{
	AVOutputFormat *ofmt = NULL;

	AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
	AVPacket pkt;
	const char *in_filename, *out_filename;
	int ret, i;
	int videoindex = -1;
	int frame_index = 0;
	int64_t start_time = 0;

	in_filename = "tcd-g2.flv";//输入URL(Input file URL)


	out_filename = "tcd-g2-sei.flv";
	av_register_all();

	avformat_network_init();

	if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
		printf("Could not open input file.");
		goto end;
	}
	if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
		printf("Failed to retrieve input stream information");
		goto end;
	}

	for (i = 0; inb_streams; i++)
	if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
		videoindex = i;
		break;
	}

	av_dump_format(ifmt_ctx, 0, in_filename, 0);

	avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", out_filename); //RTMP

	if (!ofmt_ctx) {
		printf("Could not create output context\n");
		ret = AVERROR_UNKNOWN;
		goto end;
	}
	ofmt = ofmt_ctx->oformat;
	for (i = 0; i < ifmt_ctx->nb_streams; i++) {

		AVStream *in_stream = ifmt_ctx->streams[i];
		AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
		if (!out_stream) {
			printf("Failed allocating output stream\n");
			ret = AVERROR_UNKNOWN;
			goto end;
		}

		ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
		if (ret < 0) {
			printf("Failed to copy context from input to output stream codec context\n");
			goto end;
		}
		out_stream->codec->codec_tag = 0;
		if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
			out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
	}

	av_dump_format(ofmt_ctx, 0, out_filename, 1);

	if (!(ofmt->flags & AVFMT_NOFILE)) {
		ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
		if (ret < 0) {
			printf("Could not open output URL '%s'", out_filename);
			goto end;
		}
	}

	ret = avformat_write_header(ofmt_ctx, NULL);
	if (ret < 0) {
		printf("Error occurred when opening output URL\n");
		goto end;
	}

	start_time = av_gettime();

	uint8_t* sei_data = (uint8_t*)malloc(4 + 250);
	memset(sei_data, 0, 254);


	while (1) {
		AVStream *in_stream, *out_stream;

		ret = av_read_frame(ifmt_ctx, &pkt);
		if (ret < 0)
			break;

		if (pkt.stream_index == videoindex){
			frame_index++;
			if (frame_index>750){
				av_free_packet(&pkt);
				break;
			}
			if (pkt.flags & AV_PKT_FLAG_KEY) // is keyframe
			{
				
				//Pkt.size是纯data的size+4字节起始码
				printf("frame_index:%d  pktsize:%d\n",frame_index,pkt.size);
				
				if (*(sei_data + 3) == 0){
					*sei_data = 0;
					*(sei_data + 1) = 0;
					*(sei_data + 2) = 0;
					*(sei_data + 3) = 250;
					*(sei_data + 3) = 250;

					*(sei_data + 4) = 6;
					*(sei_data + 5) = 0x64;
					*(sei_data + 6) = 250 - 4;
					*(sei_data + 253) = 0x80;
					PrintBuffer(sei_data, 254);
				}
				
				
				if ((pkt.data[4] & 0x1F) == 6){//判断是否是SEI
				
					uint32_t sei_nal_size = 0;
					memcpy(&sei_nal_size, pkt.data, 4);
					sei_nal_size = reversebytes(sei_nal_size);
					printf("sei_nal_size:%d\n",sei_nal_size);
					uint8_t* tmpdata = (uint8_t*)malloc(254 + pkt.size - 4 - sei_nal_size);
					put_sei_time(&sei_data);
					memcpy(tmpdata, sei_data, 254);
					memcpy(tmpdata + 254, pkt.data + 4 + sei_nal_size, pkt.size - 4 - sei_nal_size);
					pkt.data = tmpdata;
					pkt.size = 254 + pkt.size - 4 - sei_nal_size;
					 
				}
				else{
					uint8_t* tmpdata = (uint8_t*)malloc(254 + pkt.size);
					put_sei_time(&sei_data);
					memcpy(tmpdata, sei_data, 254);
					memcpy(tmpdata + 254, pkt.data, pkt.size);
					pkt.data = tmpdata;
					pkt.size = 254 + pkt.size;
				}

				
				PrintBuffer(pkt.data, 300);
				printf("%d\n",pkt.size);

			}
			AVRational time_base = ifmt_ctx->streams[videoindex]->time_base;
			AVRational time_base_q = { 1, AV_TIME_BASE };
			int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
			int64_t now_time = av_gettime() - start_time;
			if (pts_time > now_time)
				av_usleep(pts_time - now_time);

		}
		else{
			if (frame_index == 0){
				av_free_packet(&pkt);
				continue;
			}
		}

		in_stream = ifmt_ctx->streams[pkt.stream_index];
		out_stream = ofmt_ctx->streams[pkt.stream_index];

		pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
		pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
		pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
		pkt.pos = -1;

		ret = av_interleaved_write_frame(ofmt_ctx, &pkt);

		if (ret < 0) {
			printf("Error muxing packet\n");
			break;
		}

		av_free_packet(&pkt);

	}

	av_write_trailer(ofmt_ctx);
end:
	avformat_close_input(&ifmt_ctx);

	if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
		avio_close(ofmt_ctx->pb);
	avformat_free_context(ofmt_ctx);
	if (ret < 0 && ret != AVERROR_EOF) {
		printf("Error occurred.\n");
		return -1;
	}
	printf("end called\n");
	return getchar();
}

int main(int argc, char* argv[])
{
	AVFormatContext *ifmt_ctx = NULL;
	AVPacket pkt;
	const char *in_filename;
	int ret, i;
	int videoindex = -1;
	int frame_index = 0;
	int64_t start_time = 0;

	in_filename = "tcd-g2-sei.flv";
	av_register_all();

	avformat_network_init();

	if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
		printf("Could not open input file.");
		goto end;
	}
	if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
		printf("Failed to retrieve input stream information");
		goto end;
	}

	for (i = 0; inb_streams; i++)
	if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
		videoindex = i;
		break;
	}

	av_dump_format(ifmt_ctx, 0, in_filename, 0);


	start_time = av_gettime();
	while (1) {
		AVStream *in_stream;

		ret = av_read_frame(ifmt_ctx, &pkt);
		if (ret < 0){
			printf("read finished!\n");
			break;
		}
			

		if (pkt.stream_index == videoindex){
			frame_index++;
			 
			if (pkt.flags & AV_PKT_FLAG_KEY) // is keyframe
			{
				time_t t = time(NULL);
				tm *tp = localtime(&t);

				// 北京时间
				printf("\n\nCurrent %d/%d/%d  %d:%d:%d\n", tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec);

				//Pkt.size是纯data的size+4字节起始码
				printf("frame_index:%d  pktsize:%d\n", frame_index, pkt.size);
				
				//PrintBuffer(pkt.data, 64);

				uint8_t* pdata = pkt.data;
				 
				PrintBuffer(pkt.data, 64);
				printf("Get %d:%d:%d\n", *(pdata + 7), *(pdata + 8), *(pdata + 9));
				printf("Calculate Delay : %d\n", (tp->tm_min*60+ tp->tm_sec)-(*(pdata + 8) * 60 + *(pdata + 9)));

			}
			AVRational time_base = ifmt_ctx->streams[videoindex]->time_base;
			AVRational time_base_q = { 1, AV_TIME_BASE };
			int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
			int64_t now_time = av_gettime() - start_time;
			if (pts_time > now_time)
				av_usleep(pts_time - now_time);

		}
		 
		av_free_packet(&pkt);

	}

	 
end:
	avformat_close_input(&ifmt_ctx);
	 
	if (ret < 0 && ret != AVERROR_EOF) {
		printf("Error occurred.\n");
		return -1;
	}
	return getchar();
}

我的需求很简单,要写到sei中的数据很少,所以payloadsize只用一个字节表示,即理论上最多254个字节的sei数据。

上面程序中固定使用sei的payloadtype是0x64 (100),

payloadsize是0xf6  (246)

每个sei的payload data前三个字节,分别写入了北京时间的时,分,秒。

通过推流端在sei写入实时北京时间,然后另一端程序去读sei,对比当前北京时间,则计算出精准延迟。

你可能感兴趣的:(ffmpeg)