因为需要从海康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;
}

  

ps流提取H264并解码播放的更多相关文章

  1. RTP协议全解析(H264码流和PS流)

    转自:http://blog.csdn.net/chen495810242/article/details/39207305 写在前面:RTP的解析,网上找了很多资料,但是都不全,所以我力图整理出一个 ...

  2. (转)RTP协议全解(H264码流和PS流)

    写在前面:RTP的解析,网上找了很多资料,但是都不全,所以我力图整理出一个比较全面的解析, 其中借鉴了很多文章,我都列在了文章最后,在此表示感谢. 互联网的发展离不开大家的无私奉献,我决定从我做起,希 ...

  3. RTP协议全解(H264码流和PS流)

    写在前面:RTP的解析,网上找了很多资料,但是都不全,所以我力图整理出一个比较全面的解析, 其中借鉴了很多文章,我都列在了文章最后,在此表示感谢. 互联网的发展离不开大家的无私奉献,我决定从我做起,希 ...

  4. FFmpeg开发笔记(九):ffmpeg解码rtsp流并使用SDL同步播放

    前言   ffmpeg播放rtsp网络流和摄像头流.   Demo   使用ffmpeg播放局域网rtsp1080p海康摄像头:延迟0.2s,存在马赛克     使用ffmpeg播放网络rtsp文件流 ...

  5. (转)从海康7816的ps流里获取数据h264数据

    海康7816使用ps流来封装h.264数据,这里使用的解码器无法识别ps流,因此需要将h264数据从ps流里提取出来 对于ps流的规定可以参考13818-1文档 这里从7816里获取到一些数据取样 0 ...

  6. [转]【流媒體】H264—MP4格式及在MP4文件中提取H264的SPS、PPS及码流

    [流媒體]H264—MP4格式及在MP4文件中提取H264的SPS.PPS及码流 SkySeraph Apr 1st 2012  Email:skyseraph00@163.com 一.MP4格式基本 ...

  7. RTP协议全解析(H264码流和PS流)(转)

    源: RTP协议全解析(H264码流和PS流)

  8. PS流的格式和解析总结

    对于PS流,最近因为工作需要,所以MPEG2中的PS流格式和解包过程进行了学习. 首先我们需要知道PS包流格式是怎么样的: (来自http://blog.csdn.net/chen495810242/ ...

  9. PS 流格式解析(转)

    对于PS流,最近因为工作需要,所以MPEG2中的PS流格式和解包过程进行了学习. 首先我们需要知道PS包流格式是怎么样的: 针对H264 做如下PS 封装:每个IDR NALU 前一般都会包含SPS. ...

随机推荐

  1. Perl引用入门

    在perl中只有3种基本的数据结构:标量.数组.hash.变量可以是数值,可以是字符串. 这三种基本数据结构的数据存储方式如下: 但是,仅仅由这3种基本结构,就可以构造出更复杂的数据结构,例如hash ...

  2. 基于SpringMVC+Spring+MyBatis实现秒杀系统【数据库接口】

    前言 该篇教程主要关注MyBatis实现底层的接口,把MyBatis交给Spring来托管.数据库连接池用的c3p0.数据库用的MySQL.主要有2个大类:秒杀商品的查询.秒杀明细的插入. 准备工作 ...

  3. C#之WebApi权限认证_学习笔记1

    自己并不懂,在此先记录下来,留待以后学习... 正文 前言:最近,讨论到数据库安全的问题,于是就引出了WebApi服务没有加任何验证的问题.也就是说,任何人只要知道了接口的url,都能够模拟http请 ...

  4. 【转】探讨:ASP.NET技术的学习顺序问题

    摘要:很多人对于ASP.NET的入门和学习顺序比较迷茫,今天让我们一起来跟随作者的思路学习探讨ASP.NET的学习顺序问题,希望有所帮助. 如果你已经有较多的面向对象开发经验,跳过以下这两步: 第一步 ...

  5. [转]Ble蓝牙的使用手册

    本文转自:https://blog.csdn.net/dodan/article/details/52060446 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.cs ...

  6. JPA、Hibernate、Spring data jpa之间的关系,终于明白了

    什么么是JPA? 全称Java Persistence API,可以通过注解或者XML描述[对象-关系表]之间的映射关系,并将实体对象持久化到数据库中. 为我们提供了: 1)ORM映射元数据:JPA支 ...

  7. [android] activity横竖屏切换的生命周期

    模拟器横竖屏切换,ctrl+f11 界面activity会销毁,重新打开创建 第一种做法: 定死就是横屏 在清单文件,<activity/>节点部分,添加属性,设置屏幕朝向 android ...

  8. ajax实现跨域访问

    ajax跨域访问是一个老生畅谈的问题啦,网上解决方法很多,discuz用的p3p协议,有兴趣的朋友可以了解下,比较常用的是JSONP方法,貌似目前这种方法只支持GET方式,不如POST方式安全. 即使 ...

  9. linux中make的用法

    一.linux中make的用法 目的:       基本掌握了make 的用法,能在Linux系统上编程.环境:       Linux系统准备:       准备三个文件:file1.c, file ...

  10. Spring Cloud Feign 使用方法与性能优化

    1. feign自定义Configuration和root 容器有效隔离. 用@Configuration注解 不能在主@ComponentScan (or @SpringBootApplicatio ...