/*******************************************************}
{                                                       }
{       File: demuxer.cpp                               }
{       Created by Tsviatko Jongov                      }
{       http://tsviatko.jongov.com                      }
{       Date : 25.01.2007                               }
{                                                       }
{       CMpeg2Demux class.                              }
{                                                       }
{*******************************************************/
#include "assert.h"
#include <stdio.h>
#include "demuxer.h"
#include "Pack_Header_Def.h"
#include "time.h"

#include <chrono>

#ifdef _WIN32
#include "Winsock2.h"
#pragma comment(lib, "ws2_32.lib")
#endif
#ifdef __linux__
#include "arpa/inet.h"
#endif

using namespace std;


static /*_inline*/ unsigned int asm_swap32(unsigned int x)
{
	return ntohl(x);
}

static /*_inline*/ unsigned short asm_swap16(unsigned short x) 
{
	return ntohs(x);
}

CMpeg2Demux::CMpeg2Demux()
: m_streamType(STREAM_TYPE_UNKNOWN)
,m_userdata(NULL)
,m_userdata2(NULL)
,m_lastiskeyfram(false)
{
	try
	{
		fBuffer = new CBuffer(1024 * 1024); 
		if (fBuffer == NULL)
		{
		}
		m_lastpts = 0;
		fReceiveFunction = 0;
		fReceiveFunction2 = 0;

		m_psvideo= new CBuffer(1024*50);
		m_esvideo= new CBuffer(1024*50);
		m_psaudio= new CBuffer(1024*50);
		m_esaudio= new CBuffer(1024*50);
	}
	catch (...)
	{
	}
};

CMpeg2Demux::~CMpeg2Demux()
{
	try
	{
		if (fBuffer != NULL)
		{	
			delete fBuffer;
			fBuffer = NULL;
		}

		if (NULL != m_psvideo)
		{
			delete m_psvideo;
			m_psvideo = NULL;
		}
		if (NULL != m_esvideo)
		{
			delete m_esvideo;
			m_esvideo = NULL;
		}
		if (NULL != m_psaudio)
		{
			delete m_psaudio;
			m_psaudio = NULL;
		}
		if (NULL != m_esaudio)
		{
			delete m_esaudio;
			m_esaudio = NULL;
		}
	}
	catch (...)
	{		
	}
};

int CMpeg2Demux::AddData(void * Data, int Size/*, DWORD pts*/)
{
	try
	{
		if ((Data == NULL) || (Size <= 0))
		{		
			return (-1);
		}

		/*m_timeStamp = pts;*/
		fBuffer->add((char *)Data, Size);
		if (!Demultiplex())
		{	
			return (-1);
		}

		return (0);	
	}
	catch (...)
	{			
		return (-1);
	}
}

void CMpeg2Demux::SetReceiveFunction(ReceiveFunction * func, void* userdata)
{	 
	try
	{	
		fReceiveFunction = func;
		m_userdata       = userdata;
	}
	catch (...)
	{		
	}
}

void CMpeg2Demux::SetReceiveFunction2(ReceiveFunction2 * func2, void* userdata2)
{
	fReceiveFunction2 = func2;
	m_userdata2       = userdata2;
}

uint64_t  GetLocalTimeOfMicroSecond()
{
	chrono::time_point<chrono::system_clock, chrono::microseconds> tpMicro
		= chrono::time_point_cast<chrono::microseconds>(chrono::system_clock::now());

	return tpMicro.time_since_epoch().count();
}

int CMpeg2Demux::Demultiplex()
{
	try
	{
    	unsigned char * PDW {};
    	unsigned int Code {};
		int Processed = 0;
    	int StreamID {};
    	int PES_packet_length {};
    	int PES_header_data_length {};
    	int Val {};

		if (fBuffer->len() <= 0)
		{
			return (0);
		}
		 
		PDW = (unsigned char *)fBuffer->head();
		while (fBuffer->len() - Processed > 140)
		{
			if ((*(unsigned int *)PDW & 0x00FFFFFF) != 0x00010000)
			{
				PDW++;
				Processed++;
				continue;
			}

			Code = asm_swap32(*(unsigned int *)PDW);
			StreamID = (Code & 0xFF);
			if (PACK_START_CODE != Code)
			{
				unsigned short pespacklen = asm_swap16(*(unsigned short *)(PDW + 4)) + 6;
				if (fBuffer->len() - Processed < pespacklen + 6)
				{
					break;
				}
			}

			if (PACK_START_CODE == Code)//前一个pspack完或下一个pspack开始
			{
				if (fReceiveFunction != NULL && m_esvideo->len())//视频es
				{
					PS_HEADER_tag* pshead = (PS_HEADER_tag*)m_psvideo->head();
					uint64_t pts = 0;
					pshead->getSystem_clock_reference_base(pts);
					pts = pts/90;
					int64_t localPts = GetLocalTimeOfMicroSecond();
					fReceiveFunction(m_streamType, m_esvideo->head(), m_esvideo->len(), pts, localPts, m_lastiskeyfram, m_userdata);
				}
				m_esvideo->fFirst = m_esvideo->len();
				m_esvideo->compact();

				if (fReceiveFunction != NULL && m_esaudio->len())//音频es
				{
					int64_t localPts = GetLocalTimeOfMicroSecond();
					fReceiveFunction(0xC0, m_esaudio->head(), m_esaudio->len(), m_lastpts, localPts, 0, m_userdata);
				}
				m_esaudio->fFirst = m_esaudio->len();
				m_esaudio->compact();

				if (fReceiveFunction2 != NULL && m_psvideo->len())//视频ps
				{
					fReceiveFunction2(STREAM_TYPE_VIDEO,m_psvideo->head(),m_psvideo->len(),m_lastpts,m_lastiskeyfram,m_userdata2);
				}
				m_psvideo->fFirst = m_psvideo->len();
				m_psvideo->compact();

				if (fReceiveFunction2 != NULL && m_psaudio->len())//音频
				{
					fReceiveFunction2(STREAM_TYPE_AUDIO,m_psaudio->head(),m_psaudio->len(),m_lastpts,0,m_userdata2);
				}
				m_psaudio->fFirst = m_psaudio->len();
				m_psaudio->compact();

				PS_HEADER_tag* ppshead = (PS_HEADER_tag*)PDW;
				unsigned long psheadlen = sizeof(PS_HEADER_tag) + ppshead->pack_stuffing_length;
				if (fBuffer->len() - Processed < (int)psheadlen)
				{
					break;
				}

				//
				m_lastiskeyfram = false;
				if ((*(unsigned int *)(PDW+psheadlen)&0x00FFFFFF) != 0x00010000)
				{
				}
				else
				{
					m_psvideo->add((char*)PDW,psheadlen);//ps头写入视频数据中
					PDW += psheadlen;
					Processed += psheadlen;
					continue;
				}
			}
			else if (SYSTEM_START_CODE == Code)//system_header
			{
				unsigned short pespacklen = asm_swap16(*(unsigned short *)(PDW + 4)) + 6;
				if ((*(unsigned int *)(PDW+pespacklen)&0x00FFFFFF) != 0x00010000)
				{
					printf("SYSTEM_START_CODE: Warning in CMpeg2Demux::Demultiplex - ((*PDW & 0x00FFFFFF) != 0x00010000).\n");
				}
				else
				{
					m_psvideo->add((char*)PDW,pespacklen);//ps头写入视频数据中
					PDW += pespacklen;
					Processed +=pespacklen;
					m_lastiskeyfram = true;
					continue;
				}
			}
			else if (PROGRAM_STREAM_MAP == StreamID)//psm
			{
				unsigned short pespacklen = asm_swap16(*(unsigned short *)(PDW + 4)) + 6;
				if ((*(unsigned int *)(PDW+pespacklen)&0x00FFFFFF) != 0x00010000)
				{
				}
				else
				{
					unsigned short program_stream_info_length;
					//unsigned short elementary_stream_map_length;
					unsigned short* plen = (unsigned short*)(PDW + 8);
					program_stream_info_length = ntohs(*plen);
					int test = *(PDW + 8 + program_stream_info_length + 5);//192
					//assert(*(PDW+8+program_stream_info_length+5) == 0xe0);

					//if(*(PDW + 8 + program_stream_info_length + 5) != 0xe0)
					//{
					//	continue;
					//}
					//printf("0xe0=%d, 0x1b=%d\n", *(PDW + 8 + program_stream_info_length + 5), *(PDW + 8 + program_stream_info_length + 4));
					if (m_pserror > 5 && (*(PDW + 8 + program_stream_info_length + 5) != 0xe0 && *(PDW + 8 + program_stream_info_length + 5) != 0xc0))//判断c0是为了兼容枢纽引擎名叫博士的垃圾小厂家
					{
						printf("--------------------------------2 error:{%d}\n", m_pserror);
						return -1;
					}
					if (*(PDW + 8 + program_stream_info_length + 5) != 0xe0 && *(PDW + 8 + program_stream_info_length + 5) != 0xc0)
					{
						//printf("--------------------------------1\n");
						++m_pserror;
						continue;
					}
					m_lastiskeyfram = true;
					if (*(PDW + 8 + program_stream_info_length + 4) == 0x1b || *(PDW + 8 + program_stream_info_length + 4) == 0x03)
					{
						m_streamType = VIDEO_TYPE_H264; // H264
					}
					else if (*(PDW + 8 + program_stream_info_length + 4) == 0x24)
					{
						m_streamType = VIDEO_TYPE_H265; // H265
					}
					else if (*(PDW+8+program_stream_info_length+4) == 0x10)
					{
						m_streamType = VIDEO_TYPE_MPEG4; // MPEG4
					}
					else
					{
						m_streamType = STREAM_TYPE_UNKNOWN;
					}
					m_pserror = 0;
					m_psvideo->add((char*)PDW,pespacklen);//ps头写入视频数据中
					PDW += pespacklen;
					Processed +=pespacklen;
					continue;
				}
			}
			else if (0xBD == StreamID)//私有数据
			{
				unsigned short pespacklen = asm_swap16(*(unsigned short *)(PDW + 4)) + 6;

				if ((*(unsigned int *)(PDW+pespacklen)&0x00FFFFFF) != 0x00010000)
				{
				}
				else
				{
					m_psvideo->add((char*)PDW,pespacklen);//ps头写入视频数据中
					PDW += pespacklen;
					Processed +=pespacklen;
					continue;
				}
			}

			if ((Code >= SYSTEM_START_CODE_MIN) &&
				(Code <= SYSTEM_START_CODE_MAX) &&
				(Code != PACK_START_CODE) &&
				(Code != SYSTEM_START_CODE) &&
				(StreamID != PROGRAM_STREAM_MAP) &&
				(StreamID != PADDING_STREAM) &&
				(StreamID != PRIVATE_STREAM_2) &&
				(StreamID != ECM_STREAM) &&
				(StreamID != EMM_STREAM) &&
				(StreamID != PROGRAM_STREAM_DIRECTORY) &&
				(StreamID != DSM_CC_STREAM) &&
				(StreamID != ITU_T_STREAM_E))
			{
				PES_packet_length = asm_swap16(*(unsigned short *)(PDW + 4));
				PES_header_data_length = *(PDW + 8);

				if (PES_packet_length == 0)
					PES_packet_length = fBuffer->len() - Processed/* - 4*/;
				else
					PES_packet_length += 6;

				if (fBuffer->len() - Processed >= PES_packet_length/* + 4*/)
				{
					PES_HEADER_tag* peshead = (PES_HEADER_tag*)PDW;
					uint64_t pts = 0;
					if (peshead->PTS_DTS_flags&0x3)
					{
						PTS_tag* ptstag = (PTS_tag*)(PDW + 9);
						ptstag->getPTS(pts);
						m_lastpts = pts/90;
					}

					Val = 6 + 3 + PES_header_data_length;

					if (0xE0 == StreamID)
					{
						m_esvideo->add((char*)PDW + Val, PES_packet_length - Val);
						m_psvideo->add((char*)PDW, PES_packet_length);
					}
					else if (0xC0 == StreamID)
					{
						if (PES_packet_length + 6 - Val > 0)
						{
							m_esaudio->add((char*)PDW + Val,PES_packet_length - Val);
						}
						else
						{
							m_esaudio->add((char*)PDW + Val,PES_packet_length - 19);
						}
						m_psaudio->add((char*)PDW,PES_packet_length+6);
					}

					fBuffer->fFirst += Processed + PES_packet_length;
					fBuffer->compact();

					Processed = 0;
					PDW = (unsigned char *)fBuffer->head();
				}
				else
				{
					break;
				}
			}
			else {
				PDW++;
				Processed++;
			}
		};

		if (Processed)
		{
           fBuffer->fFirst += Processed;
		   fBuffer->compact();
		   Processed = 0;
		}
		return (true);
	}
	catch (...)
	{
		return (-1);
	}
}