ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

海康、国标PS流数据解析

2021-03-19 11:31:41  阅读:1136  来源: 互联网

标签:PS char pkt buffer length 海康 国标 pHandle NextPack


    最近做的一个项目,需要对接海康摄像头传过来的视频数据,经过分析知道传过来的是PS封装后的数据,不能直接播放,需要把PS流解析成h264裸流。经过研究有两种方法:1.直接扔给ffmpeg解析,这种方法需要用AVIOContext自定义输入;2.自己解析PS流,这种方法比较直接,只要熟悉PS解析的流程就可以做。我两种方法都实现了,刚开始是用ffmpeg,后来发现因为需要开线程,异步读写,复杂性较高,最主要是因为出现一个内存一直上涨的问题一直没解决,然后想着能不能自己解析PS,于是网上找了很多资料,终于也实现了,自己解析PS就是比较简单,不用开线程,不用异步处理,只要调用一个算法就能得出解析结果。这里记录下两种方法的实现,希望对后来者有所帮助。

 一、采用ffmpeg解析PS流

首先需要用AVIOContext自定义输入,我封装为一个H264Reader类,如下:

#pragma once
#include <stdio.h>
#include <thread>
extern "C" {
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
}
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avcodec.lib")
#include "rexkit\BlockingRingBuffer.h"
#include "FacdNetMediaServer.h"


class H264Reader
{
public:
	H264Reader();

	int Run(DevStreamHandle* aStreamHandle);
	virtual ~H264Reader();
	void Stop() {
		this->running = false;
	}
private:

	AVFormatContext *ic = NULL;
	AVIOContext *avio = NULL;
	unsigned char *avio_ctx_buffer = NULL;

public:
	bool running = true;
	DevStreamHandle* streamHandle;

};

#include "H264Reader.h"
#include <iostream>
#include "liblogger\liblogger.h"
using namespace std;

static void XSleep(int ms)
{
	//c++ 11
	chrono::milliseconds du(ms);
	this_thread::sleep_for(du);
}

H264Reader::H264Reader()
{
	
}

static int read_packet(void *opaque, uint8_t *buf, int buf_size) {
	H264Reader *h264 = (H264Reader*)opaque;
	DevStreamHandle *handle = h264->streamHandle;
	En_BlockingRingBuffer_FetchResult ret =handle->ringBuff->Fetch(buf, buf_size); //读取环形缓冲区的数据
	
	if (ret == RBFR_OK)
		return buf_size;
	else {
		return 0;
	}
	
}

int H264Reader::Run(DevStreamHandle* stream_handle)
{
	streamHandle = stream_handle;
	int isopen = -1;
	size_t avio_ctx_buffer_size = 1024*32;// 
	ic = avformat_alloc_context();
	avio_ctx_buffer = (unsigned char *)av_malloc(avio_ctx_buffer_size);
	printf("H264Reader::Run av_malloc()\n");
	avio = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, this, read_packet, NULL, NULL);
	ic->pb = avio;
	isopen = avformat_open_input(&ic, NULL, NULL, NULL);
	if (isopen < 0) {
		LogError("Could not open input----->>>\n");
		return -1;
	}
	AVPacket *pkt = av_packet_alloc();
	while (running)
	{
		int re = av_read_frame(ic, pkt);
		if (re != 0){
			av_packet_unref(pkt);
			printf("~~~~~~~~~av_read_frame()= %d\n", re);
			continue;
		}
		g_MainMediaServer.PostDataToRtsp((char*)pkt->data, pkt->size, stream_handle);
		av_packet_unref(pkt);
	}
	if (avio) {
		av_freep(&avio->buffer);
		av_freep(&avio);
	}
	return 0;
}

H264Reader::~H264Reader()
{
	//av_free(avio_ctx_buffer);
}

 

使用H264Reader类需要开启一个线程,在线程里调用H264Reader的Run()函数。使用AVIOContext,需要定义一个读回调函数,上述代码 avio = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, this, read_packet, NULL, NULL);这行就是把自定义回调函数read_packet绑定到AVIOContext,其中第四个参数是用户数据,这里传入this,可以在回调里面获取这个用户数据。在回调里面给ffmpeg写入PS流,ffmpeg内部会不断调用这个回调,然后在循环里面不断调用 av_read_frame(ic, pkt),返回的AVPacket变量pkt就是已经被ffmpeg解析后的h264裸流。

使用AVIOContext自定义输入的难点在于需要设计一个缓冲区,写入线程需要向缓冲区写入不定大小的数据,而读取线程需要向缓冲区读取不定大小的数据,因为写入和读取的大小都不固定所以不能定义一个固定大小的缓冲区,这里我采用一个环形阻塞缓冲链表BlockingRingBuffer。

在写入线程需要调用BlockingRingBuffer的Cat()函数写入PS流:


// 取到PS流,存储在ringBuff
int  FacdNetMediaServer::cbPreStreamMedia(HSTREAM hStream, const StreamMediaFrame *cFrame, DWORD dwUserData)
{
	DevStreamHandle *pHandle = (DevStreamHandle*)dwUserData;
	if (pHandle != NULL && pHandle->ringBuff != NULL){
		pHandle->ringBuff->Cat((const unsigned char*)cFrame->cFrameBuffer.pBuffer, cFrame->cFrameBuffer.dwBufLen);
	}
	return 0;
}

 在AVIOContext的读回调里面调用BlockingRingBuffer的Fecth()函数读取PS流:

static int read_packet(void *opaque, uint8_t *buf, int buf_size) {
	H264Reader *h264 = (H264Reader*)opaque;
	DevStreamHandle *handle = h264->streamHandle;
	En_BlockingRingBuffer_FetchResult ret =handle->ringBuff->Fetch(buf, buf_size); //
	
	if (ret == RBFR_OK)
		return buf_size;
	else {
		return 0;
	}
}

二,采用自己解析PS流的方法

这个方法就简单多了,主要思想是两个PS流头部之间的包拼接成一个数据包扔给PS解析函数处理,得出的就是一个h264帧,PS头以0x000001ba标识,所以拼接方法如下:


int  FacdNetMediaServer::cbPreStreamMedia(HSTREAM hStream, const StreamMediaFrame *cFrame, DWORD dwUserData)
{
	DevStreamHandle *pHandle = (DevStreamHandle*)dwUserData;
	time_t t;
	t = time(NULL);
	pHandle->pusher_time = time(&t);
	
	if (pHandle != NULL && pHandle->cam != NULL){
		char *h264Frame;
		int h264Len;
		char* buffer = (char*)cFrame->cFrameBuffer.pBuffer;
		int len = cFrame->cFrameBuffer.dwBufLen;
		
		//printf("%02x %02x %02x %02x\n", buffer[0], buffer[1], buffer[2], buffer[3]);
		//查找ps头 0x000001BA
		if (buffer[0] == '\x00' && buffer[1] == '\x00' && buffer[2] == '\x01' && buffer[3] == '\xba')
		{
			if (!pHandle->isfirst)
			{
				//此包为ps新的一帧,每次到这里都先处理存储好的前一帧
				GetH246FromPs(pHandle->h264_pkt, pHandle->pkt_len, &h264Frame, &h264Len);
				//海康流特殊处理部分:分界符数据(nal_unit_type=9)或补充增强信息单元(nal_unit_type=6),如果直接送入解码器,有可能会出现问题,直接舍弃.00 00 01 bd和 00 00 01 c0为私有标志和音频数据舍弃
				if (h264Frame[0] >> 5 == '\x06' || h264Frame[0] >> 5 == '\x09' || h264Frame[0] >> 5 == '\x0a' || h264Frame[0] >> 5 == '\x0b' || h264Frame[0] >> 5 == '\x0c')
				{
				}
				else {//这就是获取的含有标准正常H264数据的帧 我们开始进行处理,将该帧存入Deque,该deque只负责存储所有的一帧帧的数据
					bool ret = pHandle->cam->deliverFrame(h264Frame, h264Len);
					if (ret) {
						time_t t1;
						t1 = time(NULL);
						pHandle->puller_time = time(&t1);
					}
				}
			}
			//各个变量初始化,开始拼接下一个帧(可能收到的N个包才能组成一个帧,所以这里有一个拼接操作,主要是一个帧的头部指针一直memcpy,把内存向后叠加)
			pHandle->pkt_len = 0;
			memset(pHandle->h264_pkt, 0, MAX_FRAME_SIZE);
			memcpy(pHandle->h264_pkt + pHandle->pkt_len, buffer, len);
			pHandle->pkt_len += len;
			pHandle->isfirst = false;
		}
		else {//当然如果开头不是0x000001BA,默认为一个帧的中间部分,我们将这部分内存顺着帧的开头向后存储
			if (!pHandle->isfirst)
			{
				//排除音频和私有数据
				if (buffer[0] == '\x00' && buffer[1] == '\x00' && buffer[2] == '\x01' && (buffer[3] == '\xc0' || buffer[3] == '\xbd'))
				{
					return 0;
				}
				//这是正常的帧数据
				if (pHandle->pkt_len + len > MAX_FRAME_SIZE) {
					LogError("帧大小超过5MB");
					return -1;
				}
				memcpy(pHandle->h264_pkt + pHandle->pkt_len, buffer, len);
				pHandle->pkt_len += len;
			}
		}
		
	}
	return 0;
}

 GetH246FromPs()就是解析PS流的接口,定义如下:

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#pragma pack(1)

union littel_endian_size
{
	unsigned short int	length;
	unsigned char		byte[2];
};

struct pack_start_code
{
	unsigned char start_code[3];
	unsigned char stream_id[1];
};

struct program_stream_pack_header
{
	pack_start_code PackStart;// 4
	unsigned char Buf[9];
	unsigned char stuffinglen;
};

struct program_stream_map
{
	pack_start_code PackStart;
	littel_endian_size PackLength;//we mast do exchange
								  //program_stream_info_length
								  //info
								  //elementary_stream_map_length
								  //elem
};

struct program_stream_e
{
	pack_start_code		PackStart;
	littel_endian_size	PackLength;//we mast do exchange
	char				PackInfo1[2];
	unsigned char		stuffing_length;
};

#pragma pack()

int inline ProgramStreamPackHeader(char* Pack, int length, char **NextPack, int *leftlength)
{
	//printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);
	//通过 00 00 01 ba头的第14个字节的最后3位来确定头部填充了多少字节
	program_stream_pack_header *PsHead = (program_stream_pack_header *)Pack;
	unsigned char pack_stuffing_length = PsHead->stuffinglen & '\x07';

	*leftlength = length - sizeof(program_stream_pack_header) - pack_stuffing_length;//减去头和填充的字节
	*NextPack = Pack + sizeof(program_stream_pack_header) + pack_stuffing_length;

	if (*leftlength<4) return 0;

	//printf("[%s]2 %x %x %x %x\n", __FUNCTION__, (*NextPack)[0], (*NextPack)[1], (*NextPack)[2], (*NextPack)[3]);

	return *leftlength;
}

inline int ProgramStreamMap(char* Pack, int length, char **NextPack, int *leftlength, char **PayloadData, int *PayloadDataLen)
{
	//printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);

	program_stream_map* PSMPack = (program_stream_map*)Pack;

	//no payload
	*PayloadData = 0;
	*PayloadDataLen = 0;

	if (length < sizeof(program_stream_map)) return 0;

	littel_endian_size psm_length;
	psm_length.byte[0] = PSMPack->PackLength.byte[1];
	psm_length.byte[1] = PSMPack->PackLength.byte[0];

	*leftlength = length - psm_length.length - sizeof(program_stream_map);

	//printf("[%s]leftlength %d\n", __FUNCTION__, *leftlength);

	if (*leftlength <= 0) return 0;

	*NextPack = Pack + psm_length.length + sizeof(program_stream_map);

	return *leftlength;
}

inline int Pes(char* Pack, int length, char **NextPack, int *leftlength, char **PayloadData, int *PayloadDataLen)
{
	//printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);
	program_stream_e* PSEPack = (program_stream_e*)Pack;

	*PayloadData = 0;
	*PayloadDataLen = 0;

	if (length < sizeof(program_stream_e)) return 0;

	littel_endian_size pse_length;
	pse_length.byte[0] = PSEPack->PackLength.byte[1];
	pse_length.byte[1] = PSEPack->PackLength.byte[0];
	if (pse_length.length + 6 > length)
	{
		pse_length.length = length - 6;
		/*int PES_packet_length_fixed = length - 6;
		Pack[4] = (char)((PES_packet_length_fixed && 0xFF00) >> 8);
		Pack[5] = (char)(PES_packet_length_fixed & 0x00FF);*/
	}

	*PayloadDataLen = pse_length.length - 2 - 1 - PSEPack->stuffing_length;
	if (*PayloadDataLen>0)
		*PayloadData = Pack + sizeof(program_stream_e) + PSEPack->stuffing_length;

	*leftlength = length - pse_length.length - sizeof(pack_start_code) - sizeof(littel_endian_size);

	//printf("[%s]leftlength %d\n", __FUNCTION__, *leftlength);

	if (*leftlength <= 0) return 0;

	*NextPack = Pack + sizeof(pack_start_code) + sizeof(littel_endian_size) + pse_length.length;

	return *leftlength;
}

int inline GetH246FromPs(char* buffer, int length, char **h264Buffer, int *h264length)
{
	int leftlength = 0;
	char *NextPack = buffer;

	*h264Buffer = buffer;
	*h264length = 0;

	leftlength = length;

	if (ProgramStreamPackHeader(buffer, length, &NextPack, &leftlength) == 0)
		return 0;

	char *PayloadData = NULL;
	int PayloadDataLen = 0;

	while (leftlength >= sizeof(pack_start_code))
	{
		PayloadData = NULL;
		PayloadDataLen = 0;

		
		if (NextPack
			&& NextPack[0] == '\x00'
			&& NextPack[1] == '\x00'
			&& NextPack[2] == '\x01'
			&& NextPack[3] == '\xE0')
		{
			//接着就是流包,说明是非i帧
			if (Pes(NextPack, leftlength, &NextPack, &leftlength, &PayloadData, &PayloadDataLen))
			{
				if (PayloadDataLen)
				{
					memcpy(buffer, PayloadData, PayloadDataLen);
					buffer += PayloadDataLen;
					*h264length += PayloadDataLen;
				}
			}
			else
			{
				if (PayloadDataLen)
				{
					memcpy(buffer, PayloadData, PayloadDataLen);
					buffer += PayloadDataLen;
					*h264length += PayloadDataLen;
				}

				break;
			}
		}
		else if (NextPack
			&& NextPack[0] == '\x00'
			&& NextPack[1] == '\x00'
			&& NextPack[2] == '\x01'
			&& NextPack[3] == '\xBC')
		{
			if (ProgramStreamMap(NextPack, leftlength, &NextPack, &leftlength, &PayloadData, &PayloadDataLen) == 0)
				continue;
		}
		else
		{
			//printf("no konw %x %x %x %x\n", NextPack[0], NextPack[1], NextPack[2], NextPack[3]);
			break;
		}
	}


	return *h264length;
}

 

 

标签:PS,char,pkt,buffer,length,海康,国标,pHandle,NextPack
来源: https://blog.csdn.net/wdglhack/article/details/114999974

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有