因為需要從海康ps流中提取H264數據並進行解碼播放,才有了這篇文章.因為是視頻編解碼領域的純入門新手,個別理解或者方法有誤,需要自行判斷,不過相關方法已經測試通過,對於
像我這樣的新手還是有一定的借鑒的.斷斷續續搞了很長一段時間,把相關經驗分享給各個新手.
---------------------------------------------------------------------------------------------------------
分為3個部分來說吧,僅供參考.
1. 接收並且解析RTP流部分
2. 解析ps流部分,包括解析海康部分私有格式
3. 將提取的標准H264流進行解碼播放
--------------------------------------------------------------------------------------------------------
接收並且解析RTP流:
當時想到了兩種方案:1.編寫udp-socket來接受流,然后自己解析rtp包. 2.使用jrtplib庫來接受流,自動解析rtp包. 由於第二種方案簡單易用,使用了第二種方案.實驗過程發現jrtplib會
出現丟包的情況,導致花屏的出現.具體丟包原因未查明.(jrtplib使用了最簡單的框架,摘自jrtplib的其中一個example,未精簡,未使用thread,效果還可以)
//該函數主要作用是使用jrtplib接收rtp流,並且解析出H264,將一幀幀的H264存入一個deque中
DWORD WINAPI GetSocketData(LPVOID lpparentet)
{
//////////////////////////////////////////////////////////////////////////jrtplib的相關初始化
WSADATA dat;
WSAStartup(MAKEWORD(2,2),&dat);
RTPSession session;
RTPSessionParams sessionparams;
sessionparams.SetOwnTimestampUnit(1.0/3600.0);
sessionparams.SetUsePollThread(true);
RTPUDPv4TransmissionParams transparams;
transparams.SetPortbase(6000);
session.SetMaximumPacketSize(sessionparams.GetMaximumPacketSize()+1000);
transparams.SetRTPReceiveBuffer(transparams.GetRTCPReceiveBuffer()*3);
transparams.SetRTCPReceiveBuffer(transparams.GetRTCPReceiveBuffer()*3);
int status = session.Create(sessionparams,&transparams);
if (status < 0)
{
std::cerr << RTPGetErrorString(status) << std::endl;
exit(-1);
}
uint8_t localip[]={127,0,0,1};
RTPIPv4Address addr(localip,9000);
status = session.AddDestination(addr);
if (status < 0)
{
std::cerr << RTPGetErrorString(status) << std::endl;
exit(-1);
}
session.SetDefaultPayloadType(96);
session.SetDefaultMark(false);
session.SetDefaultTimestampIncrement(160);
uint8_t silencebuffer[160];
for (int i = 0 ; i < 160 ; i++)
silencebuffer[i] = 128;
RTPTime delay(0.02);
RTPTime starttime = RTPTime::CurrentTime();
/////////////////////////////////////////////////////////////////////////收到rtp包之后進行數據處理
bool full=true;
bool done = false;
int i=0;
while (!done)
{
session.BeginDataAccess();
if (session.GotoFirstSource())
{
do
{
RTPPacket *packet;
while ((packet = session.GetNextPacket()) != 0)
{
//查找ps頭 0x000001BA
if (packet->GetPacketData()[12]==0x00 && packet->GetPacketData()[13]==0x00 && packet->GetPacketData()[14]==0x01 && packet->GetPacketData()[15]==0xba)
{
if (i!=0)
{
//此包為ps新的一幀,每次到這里都先處理存儲好的前一幀
int iPsLength=0;
GetH246FromPs(jimbak,jimlen,&returnps,&iPsLength); //從ps流中提取h264
//海康流特殊處理部分:分界符數據(nal_unit_type=9)或補充增強信息單元(nal_unit_type=6),如果直接送入解碼器,有可能會出現問題,直接舍棄.00 00 01 bd和 00 00 01 c0為私有標志和音頻數據舍棄
if (returnps[0]>>5==0x06 || returnps[0]>>5==0x09 ||returnps[0]>>5==0x0a || returnps[0]>>5==0x0b ||returnps[0]>>5==0x0c)
{
}
else //這就是獲取的含有標准正常H264數據的幀 我們開始進行處理,將該幀存入Deque,該deque只負責存儲所有的一幀幀的數據
{
char *h264buffer=new char[iPsLength];
memcpy(h264buffer,returnps,iPsLength);
BUFFERINFO bi;
bi.h264buf=h264buffer;
bi.lLength=iPsLength;
EnterCriticalSection(&g_cs);
gDeque.push_back(bi);
LeaveCriticalSection(&g_cs);
}
}
//各個變量初始化,開始拼接下一個幀(可能收到的N個包才能組成一個幀,所以這里有一個拼接操作,主要是一個幀的頭部指針一直memcpy,把內存向后疊加)
jimlen=0;
jim=jimbak;
memcpy(jim,(char *)packet->GetPacketData()+12,packet->GetPacketLength()-12);
jim+=(packet->GetPacketLength()-12);
jimlen+=packet->GetPacketLength()-12;
i++;
}
else //當然如果開頭不是0x000001BA,默認為一個幀的中間部分,我們將這部分內存順着幀的開頭向后存儲
{
if (i!=0)
{
//排除音頻和私有數據
if (packet->GetPacketData()[12]==0x00 && packet->GetPacketData()[13]==0x00 && packet->GetPacketData()[14]==0x01 && packet->GetPacketData()[15]==0xc0)
{
}
else if (packet->GetPacketData()[12]==0x00 && packet->GetPacketData()[13]==0x00 && packet->GetPacketData()[14]==0x01 && packet->GetPacketData()[15]==0xbd)
{
}
else //這是正常的幀數據,像貪吃蛇一樣,將它放在幀開頭的后邊
{
memcpy(jim,(char *)packet->GetPacketData()+12,packet->GetPacketLength()-12);
jim+=(packet->GetPacketLength()-12);
jimlen+=packet->GetPacketLength()-12;
}
}
}
session.DeletePacket(packet);
i++;
}
} while (session.GotoNextSource());
}
session.EndDataAccess();
RTPTime::Wait(delay);
}
delay = RTPTime(10.0);
session.BYEDestroy(delay,"Time's up",9);
WSACleanup();
}
---------------------------------------------------------------------------------------------
海康PS流解析,可參考http://blog.csdn.net/wwyyxx26/article/details/15224879#
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_pack_bb_header
{
unsigned char head[4];
unsigned char num1;
unsigned char num2;
};
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)
{
//cout <<WRITE_LOG(LOG_LEVEL_SUB_1, "%02x %02x %02x %02x",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;
//如果開頭含有bb 則去掉bb
if(*NextPack && (*NextPack)[0]=='\x00' && (*NextPack)[1]=='\x00' && (*NextPack)[2]=='\x01' && (*NextPack)[3]=='\xBB')
{
program_stream_pack_bb_header *pbbHeader=(program_stream_pack_bb_header *)(*NextPack);
unsigned char bbheaderlen=pbbHeader->num2;
(*NextPack) = (*NextPack) + sizeof(program_stream_pack_bb_header)+bbheaderlen;
*leftlength = length - sizeof(program_stream_pack_bb_header) - bbheaderlen;
int a=0;
a++;
}
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];
*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 = 0;
*h264Buffer = buffer;
*h264length = 0;
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)
break;
}
else
{
//printf("[%s]no konw %x %x %x %x\n", __FUNCTION__, NextPack[0], NextPack[1], NextPack[2], NextPack[3]);
break;
}
}
return *h264length;
}
H264解碼並播放
BOOL H264_Init_and_SDL()
{
avcodec_init();
av_register_all();
AVCodec *pCodec=avcodec_find_decoder(CODEC_ID_H264);
g_pCodecCtx=avcodec_alloc_context();
g_pCodecCtx->time_base.num = 1; //這兩行:一秒鍾25幀
g_pCodecCtx->time_base.den = 25;
g_pCodecCtx->bit_rate = 0; //初始化為0
g_pCodecCtx->frame_number = 1; //每包一個視頻幀
g_pCodecCtx->codec_type = CODEC_TYPE_VIDEO;
g_pCodecCtx->width = 1280; //這兩行:視頻的寬度和高度
g_pCodecCtx->height = 720;
if (avcodec_open(g_pCodecCtx,pCodec)>=0)
{
g_pavfFrame=avcodec_alloc_frame();
g_pYUVavfFrame=avcodec_alloc_frame();
}
//////////////////////////////////////////////////////////////////////////
SDL_putenv("SDL_VIDEO_WINDOW_POS=0,0");
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
printf( "Could not initialize SDL - %s\n", SDL_GetError());
return -1;
}
screen_w = g_pCodecCtx->width;
screen_h = g_pCodecCtx->height;
g_screen = SDL_SetVideoMode(screen_w, screen_h, 0,0);
if(!g_screen)
{
printf("SDL: could not set video mode - exiting:%s\n",SDL_GetError());
return FALSE;
}
g_bmp = SDL_CreateYUVOverlay(g_pCodecCtx->width, g_pCodecCtx->height,SDL_YV12_OVERLAY, g_screen);
rect.x = 0;
rect.y = 0;
rect.w = screen_w;
rect.h = screen_h;
//SDL End------------------------
img_convert_ctx = sws_getContext(g_pCodecCtx->width, g_pCodecCtx->height, g_pCodecCtx->pix_fmt, g_pCodecCtx->width, g_pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
return (BOOL)g_pavfFrame;
}
BOOL H264_Decode(const PBYTE pSrcData, const DWORD dwDataLen, PBYTE pDeData, int * pnWidth, int * pnHeight)
{
//pSrcData – 待解碼數據
//dwDataLen – 待解碼數據字節數
//pDeData – 用來返回解碼后的YUV數據
//pnWidth, pnHeight – 用來返回視頻的長度和寬度
BOOL n_Got=FALSE;
avcodec_decode_video(g_pCodecCtx,g_pavfFrame,(int *)&n_Got,(unsigned __int8*)pSrcData,dwDataLen);
if (n_Got)
{
*pnWidth=g_pCodecCtx->width;
*pnHeight=g_pCodecCtx->height;
//ASSERT(g_pCodecCtx->pix_fmt==PIX_FMT_YUV420P);
if (g_pCodecCtx->pix_fmt!=PIX_FMT_YUV420P)
{
return FALSE;
}
//轉為YUV
int ndatalen=0;
for (int i=0;i<3;i++)
{
int nShift=(i==0)?0:1;
PBYTE pYUVData=(PBYTE)g_pavfFrame->data[i];
for (int j=0;j<(g_pCodecCtx->height>>nShift);j++)
{
memcpy(&pDeData[ndatalen],pYUVData,(g_pCodecCtx->width >> nShift));
pYUVData+=g_pavfFrame->linesize[i];
ndatalen+=(g_pCodecCtx->width >> nShift);
}
}
//////////////////////////////////////////////////////////////////////////
SDL_LockYUVOverlay(g_bmp);
g_pYUVavfFrame->data[0]=g_bmp->pixels[0];
g_pYUVavfFrame->data[1]=g_bmp->pixels[2];
g_pYUVavfFrame->data[2]=g_bmp->pixels[1];
g_pYUVavfFrame->linesize[0]=g_bmp->pitches[0];
g_pYUVavfFrame->linesize[1]=g_bmp->pitches[2];
g_pYUVavfFrame->linesize[2]=g_bmp->pitches[1];
sws_scale(img_convert_ctx, g_pavfFrame->data, g_pavfFrame->linesize, 0, g_pCodecCtx->height, g_pYUVavfFrame->data, g_pYUVavfFrame->linesize);
SDL_UnlockYUVOverlay(g_bmp);
SDL_DisplayYUVOverlay(g_bmp, &rect);
//Delay 40ms
//SDL_Delay(40);
}
return n_Got;
}
