当前位置: 首页 > article >正文

超分服务的分量保存

分量说明

    分量的概念主要是对于显卡解码,编码和网络传输而言,显卡可以同时进行几个线程,多个显卡可以分布式计算,对分量进行AI识别,比如我们有cuda的显卡,cuda的核心量可以分给不同的分片视频,第一步先将视频减小,第二部分割视频。对于小视频片而言,不同的智能盒子也可以接收网络传输来进行接收数据,进行并行识别服务。这就是我说的分量服务的概念。

采样

    在超分服务中,上采样和下采样是两个重要的操作,分别用于增加和减少图像的分辨率。我们在下采样后存储文件和传输,可以显著减少存储的量,同时减少网络的传输量,而接收端收到以后再进行上采样放大,同时进行AI 分析

超分服务说明

    实现超分服务,将实时视频能够缩小并且传输和保存,同时需要将文件分成片,同时保证每个文件的最后一帧和下一个文件的第一帧相同,还需要解决两个问题
文件切片是问了能够分布式传输出去,同时本地如果有多个显卡,可以同时进行文件的搜索,比如再多个文件中同时启动AI服务,搜索同一个人脸。

    采用rtsp,rtmp服务器接收,rtmp服务器在接收文件时保存为flv文件,为了不污染任何代码,不采用开源的各种服务,使用c++20 去写rtmp和rtsp服务器,这个花了两天时间,协议没啥问题,结果反而时卡在了文件保存上面,下面具体说几个问题,主要体现在时间戳上面。

    以下为flv文件保存的要素,首先时flv 头部,在头部中查找0x17 和 0x00 0x01
*/
//0x17 key 00(AVCPacketType ) 00 表示 是AVCDecoderConfigurationRecord
//0x17 key 01(AVCPacketType ) 01 表示 raw data ->nalu
//0x27 not key 01 01 表示 raw data ->nalu

//FLV head 9 bytes
//4 bytes previous tag
//tag data —>11 bytes head -> 5 bytes video head -> nalu data
//4 bytes previous tag
//tag data
熟悉flv文件格式的人一看就懂,无需多言。

1 关键帧问题
2 时间戳问题

关键帧问题

    必须保证一个文件第一帧一定为关键帧,所以在分割视频的时候必须能够拿到关键帧的时候才能分割,为了能够保证未丢失文件,上一个文件的最后一帧为下一个文件的第一帧,否则会有很多依靠关键帧解码的p帧b帧无法解码,变成比较难受的绿色,也有可能为绿加黑。

分量保存的时间戳

看下图,
在这里插入图片描述

    显然除了第一个文件是正确的,但除了第一个分量文件,其他文件第一帧的时间戳是不对的,这是因为AVC sequence header 总是零,vlc播放的时候计算的时间就不正确了,那么就有两个方法:

1 是修改 sequence header的时间戳,
2 是修改每一帧时间戳,

    这里有一个问题要说明,就是整体直播出去的时候关键帧的时间戳肯定是对的,为了让文件比较正常,采取修改每一帧时间戳。

    总结一下flv头部, 11 个字节头部后,如果是视频,加5个字节的扩展,后面就是nalu数据,如果是音频,加2个字节的扩展,视频5个字节里面第一个就是判别是关键帧和非关键帧的紧要,这里简单一点先用0x17 0x27来判别,注意实际上不是这样,只有h264才是这个值,先找定时间戳,假定我们从协议里面获取的时间戳是正确的,看下面的代码


//11 个字节头部
static void pack_tag_header(uint8_t *buf, uint8_t type, uint32_t data_size, uint32_t timestamp) {
	//8 audio ; 9 video ; 18 script
	//8 is the most 
	if (type == 8 || type == 9 || type == 18)
	{
		*buf++ = type;//one bytes
		buf = write_be_ui24(buf, data_size); //three bytes
		buf = write_be_ui24(buf, timestamp & 0xffffff);//three bytes
		*buf++ = timestamp >> 24; //one bytes
		buf = write_be_ui24(buf, 0); //three bytes
	}
}

//flv header length is 11
//type 8:audio,  9:video,  18:script meta
static void pack_tag(uint8_t* header, ptr_s_memory mem, uint8_t type, uint32_t timestamp) {

	pack_tag_header(header, type, (uint32_t)mem->v_len, timestamp);
	uint8_t* p = mem->v_data_r + mem->v_len;
	//last write the frame length ,it must include the header length
	write_be_ui32(p, FLV_TAG_HEADER_LEN + (uint32_t)mem->v_len);
}

1 2 3 4 5 6 7 8 9 10 11
09 xx xx xx 00 7c 79 00 00 00 00
11 个flv字节头部里面有四个字节包含了时间戳,第5个字节到第8,也是我们自己的代码写入的,我们要做的就是重写时间戳,但是不能修改传入的tag数组,这是外面传输出去要用的

0 和 1 之间无缝衔接,同时每个文件的时间长度和时间戳都保证正确
在这里插入图片描述

在这里插入图片描述
开头和结尾衔接
在这里插入图片描述
相邻两个文件开头和结尾为同一帧

code

主要就是需要重新改写时间戳,直接看代码

#pragma once
#include <stdint.h>
#include <stdio.h>
#include <string>

#include "c_hub.h"
#include "util_flv_pack.h"
//flv 文件读写
class c_flv_writer
{
	FILE* v_fp = NULL;
	int64_t v_num = 0;
	uint32_t v_hash = 0;
	std::string v_deviceurl;
	uint32_t v_record_timestamp = 0;
public:
	ptr_s_memory v_head_video = nullptr;
	ptr_s_memory v_head_audio = nullptr;
	int v_frame_count = 2000;
	int v_frame_record = 0;
	int v_inited = 0;
	
protected:
	std::string GetFileName()
	{
		//判断v_deviceurl是否"/"结尾

		std::string name = v_deviceurl + std::to_string(v_hash);
		name +="_" + std::to_string(v_num);
		name += ".flv";
		v_num++;
		return name;
	}
public:
	void initStart(std::string deviceurl, uint32_t hash, ptr_s_memory v, ptr_s_memory a)
	{
		v_hash = hash;
		v_deviceurl = deviceurl;
		v_head_video = v;
		v_head_audio = a;
		v_inited = 1;
	}

	static void modify_timestamp(uint8_t* buf, uint32_t timestamp) {

		buf = buf + 4; // write_be_ui24(buf, data_size); //three bytes
		buf = write_be_ui24(buf, timestamp & 0xffffff);//three bytes
		*buf++ = timestamp >> 24; //one bytes
	}
	
	int writeStart(uint32_t ts)
	{
		if (v_fp == NULL)
		{
			v_frame_record = 0;
			std::string name = GetFileName();
			v_fp = fopen(name.c_str(), "wb+");
			if (v_fp == NULL)
				return -1;
			fwrite(FLV_HEADER_BUF_13, 13, 1, v_fp);
			//video head
			if (v_head_video != nullptr)
			{
				uint8_t* data_v = v_head_video->v_data_h; //flvhub->v_cache_hv->v_data_h;
				size_t len_v = v_head_video->v_len + 11 + 4;
				fwrite(data_v, len_v, 1, v_fp);
			}
			//audio head
			if (v_head_audio != nullptr)
			{
				uint8_t* data_a = v_head_audio->v_data_h;
				size_t len_a = v_head_audio->v_len + 11 + 4;
				fwrite(data_a, len_a, 1, v_fp);
			}
		}
		v_record_timestamp = ts;
		return 0;
	}
	

   void writeData(uint8_t* tag, int taglen, uint8_t* data, size_t len, uint32_t ts)
   {
	 if (v_fp == NULL)
		writeStart(ts);
	//遇到关键帧才能重新开始
	 if (v_fp != NULL /*&& v_frame_record < v_frame_count*/)
	 {
		uint8_t a = *data;
		uint8_t b = *(data + 1);
		if (v_frame_record > v_frame_count && ((a == 0x17) && (b == 0x01)))
		{
			//需要重复最后一帧放开
			uint8_t newtag[11];
			memcpy(newtag, tag, 11);
			uint32_t nowts = ts - v_record_timestamp;
			modify_timestamp(newtag, nowts);
			fwrite(newtag, taglen, 1, v_fp);
			fwrite(data, len, 1, v_fp);

			fclose(v_fp);
			v_fp = NULL;
			std::cout << "close the file now" << std::endl;
			writeStart(ts);
		}
		uint8_t newtag[11];
		memcpy(newtag, tag, 11);
		uint32_t nowts = ts - v_record_timestamp;
		modify_timestamp(newtag, nowts);
		fwrite(newtag, taglen, 1, v_fp);
		fwrite(data, len, 1, v_fp);
		v_frame_record++;

		std::cout << "write the number " << v_frame_record << std::endl;
	}
  }
};

调用

调用的时候放在音视频接收以后并且下采样结束的地方

if (flvhub->v_flv_w.v_inited == 0)
{
	flvhub->v_flv_w.initStart("./", hash, flvhub->v_cache_hv, flvhub->v_cache_ha);
	flvhub->v_flv_w.v_inited = 1;
}
flvhub->v_flv_w.writeData(tag,taglen, mem->v_data_r ,len, mem->v_ts);

其他编码

    由于rtmp协议已经加入enhanced 扩展,rtmp/flv已经有统一支持H265的国际版本,我后面会修改rtmp server,加入对h265的支持,那么这边存储flv 也必须进行修改,适应编码


http://www.kler.cn/a/328489.html

相关文章:

  • ubuntu20.04 更换清华源报错
  • 【AlphaFold3】开源本地的安装及使用
  • 帧中继原理与配置
  • 深度学习神经网络创新点方向
  • http响应码https的区别
  • 现代密码学|公钥密码体制 | RSA加密算法及其数学基础
  • Gateway和VirtualService
  • 代码随想录算法训练营day44
  • PostgreSQL 数据库语法学习:深入理解 `JOIN` 操作
  • 【AI基础】pytorch lightning 基础学习
  • 【JavaEE初阶】深入解析死锁的产生和避免以及内存不可见问题
  • 药品识别与分类系统源码分享
  • 【Transformer】长距离依赖
  • 微信小程序中的 `<block>` 元素:高效渲染与结构清晰的利器
  • 初识C语言(五)
  • 鸿蒙开发(NEXT/API 12)【硬件(传感器开发)】传感器服务
  • Unity 2D RPG Kit 学习笔记
  • 滚雪球学Oracle[8.1讲]:高级主题与未来趋势
  • vite 快速入门指南
  • Flask+微信小程序实现Login+Profile
  • python-ds:Python 中的数据结构库(适用于面试的数据结构和算法合集)
  • 眼镜识别数据集类别和数量已经在文档中说明,训练集和验证集共2200,g是眼镜,ng是没有眼镜。
  • 可视化图表与源代码显示配置项及页面的动态调整功能分析
  • 9、论文阅读:无监督的感知驱动深水下图像增强
  • Arduino UNO R3自学笔记6 之 Arduino引脚(IO)功能介绍
  • 电笔有用吗