linux-c语言 基于本地TCP制作服务端发送视频流方案、客户端接受流方案
注意只适合本地tcp,走局域网的再见哈
局域网单包发送61440个字节会拆包,read时多为几千个字节,而本地没有那么多限制read几万个字节是没问题的
TCP服务器源码(按帧为单位发送)
、、具体看代码中的解释
if(connfd>=0){//成功连接TCO客户端
pthread_mutex_lock(&m);
unsigned char sendBuf[200*1024];//用于存放帧数据的buffer
unsigned char *p = (unsigned char*)sendBuf;
int size1,size2,size3,ret;
if(cmd==JV_RECORD){//录像流
RemotePlayHandle_t* frameReaded = (RemotePlayHandle_t*)frame;
RecordPlay grpc_frame={0};//用于发送录像流的结构体
//赋值
memcpy(grpc_frame.fileName,frameReaded->fileName,sizeof(grpc_frame.fileName));
//文件名
grpc_frame.chnid = channel;
//通道号
memcpy(grpc_frame.nextfname,frameReaded->nextfname,sizeof(grpc_frame.nextfname));
grpc_frame.secInFile = frameReaded->secInFile;
memcpy(&grpc_frame.mediaInfo,&frameReaded->mediaInfo,sizeof(grpc_frame.mediaInfo));
//文件信息
grpc_frame.frametype = frameReaded->frameReaded.frameType;
//帧的类型
grpc_frame.buffer = frameReaded->frameReaded.data;
//帧的数据
grpc_frame.len = frameReaded->frameReaded.len;
//帧数据的长度
grpc_frame.timestap = frameReaded->frameReaded.timestap;
//帧的时间戳
grpc_frame.status = frameReaded->status;
grpc_frame.speed = frameReaded->speed;
//回放的倍数
grpc_frame.bOnlyIFrame = frameReaded->bOnlyIFrame;
grpc_frame.firstFraMsTime = frameReaded->firstFraMsTime;
grpc_frame.cntFile = frameReaded->cntFile;
grpc_frame.curFile = frameReaded->curFile;
//拷贝数据
Packet_Head head={0};//包头结构体
size1 = sizeof(RecordPlay);//存放录像流信息结构体
size2 = grpc_frame.len;//帧数据的长度
size3 = sizeof(Packet_Head)+size1+size2;//该帧的总字节
head.cmd =cmd;
head.frame_type = grpc_frame.frametype;
head.size = size1 + size2;
memcpy(p,&head,sizeof(Packet_Head));//拷贝包头到单帧包中
memcpy(p+sizeof(Packet_Head), &grpc_frame, size1);//拷贝录像信息结构体到单帧包中
memcpy(p+sizeof(Packet_Head)+size1, grpc_frame.buffer, size2);//拷贝帧数据
}else if(cmd==JV_STREAM){//实时流
JVFrame_t * frameReaded = (JVFrame_t *)frame;
StramFrame_t grpc_frame={0};
//赋值
grpc_frame.buffer=frameReaded->buffer;
grpc_frame.ch=channel;
grpc_frame.frametype = type;
grpc_frame.len=frameReaded->len;
grpc_frame.stream_id = sub_stream;
//子主码流标记
grpc_frame.timestamp = frameReaded->timestamp;
//拷贝数据
Packet_Head head={0};
size1 = sizeof(StramFrame_t);
size2 = grpc_frame.len;
size3 = sizeof(Packet_Head)+size1+size2;
head.cmd =cmd;
head.frame_type = grpc_frame.frametype;
head.size = size1 + size2;
memcpy(p,&head,sizeof(Packet_Head));
//*((JU32*)p) = CMD_HEAD+size1 + size2 ;
//*((JU8*)(p+PACKET_HEAD)) = cmd;
memcpy(p+sizeof(Packet_Head), &grpc_frame, size1);
memcpy(p+sizeof(Packet_Head)+size1, grpc_frame.buffer, size2);
printf("[lxr_test_grpc][func=%s][LINE=%d][size1=%d][size2=%d][size3=%d]\n",\
__func__,__LINE__,size1,size2,size3);
}
//开始发帧
if(size3>61440){//TCP一包最大可以发65535字节,当I帧数据比较大时需要分包
int i=0;
while(size3>61440){
ret=write(connfd,p+i,61440);
i+=61440;
size3-=61440;
}
ret=write(connfd,p+i,size3);
}else{
ret=write(connfd,p,sizeof(Packet_Head)+size1+size2);
}
pthread_mutex_unlock(&m);
}
TCP客户端源码(接受一帧为单位)(解决粘包和拆包问题)
、、具体看代码中的解释
while(1)
{
ret = read(sockfd,recBuf,61440);
Packet_Head head = {0};
memcpy(&head,recBuf,sizeof(Packet_Head));//先把包头读出来
unsigned int lenTotal = head.size + PACKET_HEAD;
if(ret>lenTotal){//沾包
unsigned int readlen=lenTotal,total=ret;
//写入数据
write_stream(recBuf,lenTotal,fp,head.cmd);
unsigned char * recBuf_offset = recBuf;
while (readlen!=ret)//判断拆包是否完毕
{
//偏移到下一包的首地址
recBuf_offset+=lenTotal;
memcpy(&head,recBuf_offset,sizeof(Packet_Head));
lenTotal = head.size + PACKET_HEAD;
//lenTotal = *((unsigned int*)recBuf_offset)+PACKET_HEAD;
//判断是否为I帧分包
if(head.frame_type==JV_FRAME_TYPE_I){
unsigned int iframe_read=ret-readlen;
while (iframe_read<lenTotal)//说明粘包中不包含完整I帧需要取包
{
ret = read(sockfd,recBuf_offset+iframe_read,61440);
iframe_read+=ret;
}
write_stream(recBuf_offset,lenTotal,fp,head.cmd);
readlen+=lenTotal;
}else{//P帧包
write_stream(recBuf_offset,lenTotal,fp,head.cmd);
readlen+=lenTotal;
}
}
}
else if(head.frame_type==JV_FRAME_TYPE_I){//判断是否为i帧包
unsigned int iframe_read=ret;
while(iframe_read<lenTotal){//I帧分包发送
ret = read(sockfd,recBuf+iframe_read,61440);
iframe_read+=ret;
}
if(lenTotal!=iframe_read){//出现粘包
unsigned int not_readlen = iframe_read-lenTotal;
unsigned int readlen=0;
unsigned char * recBuf_offset=recBuf;
write_stream(recBuf,lenTotal,fp,head.cmd);//先把完整I帧写入
while (not_readlen!=readlen)
{
//偏移到下一P帧的首地址
recBuf_offset+=lenTotal;
memcpy(&head,recBuf_offset,sizeof(Packet_Head));
lenTotal = head.size + PACKET_HEAD;
//lenTotal = *((unsigned int*)recBuf_offset)+4;
write_stream(recBuf_offset,lenTotal,fp,head.cmd);
readlen+=lenTotal;
}
}else if(lenTotal==iframe_read){//单包i帧
write_stream(recBuf,lenTotal,fp,head.cmd);
}
}else{//P帧包,p帧包也有可能出现分包,需要继续read,后面再优化
write_stream(recBuf,lenTotal,fp,head.cmd);
}
}
void write_stream(unsigned char *recBuf,unsigned int lenTotal,FILE * fp,int cmd){
//把头部去掉
unsigned char *head=(unsigned char *)recBuf+PACKET_HEAD;
switch (cmd)
{
case JV_RECORD:{//回放流
//给回放结构体填充
RecordPlay * recFrm=(RecordPlay *)(head);
if(recFrm)
{
//给回放流数据填充
recFrm->buffer = (unsigned char*)recFrm + sizeof(RecordPlay);
//check判断len的总长度
if(lenTotal-PACKET_HEAD != recFrm->len + sizeof(RecordPlay))
{
printf("bad length:chn=%d,lenTotal=%d,frmLen=%d\n", recFrm->chnid, lenTotal, recFrm->len);
}
}
//写入文件
write_input_file(cmd,fp,recFrm->buffer,recFrm->len,recFrm->frametype);
break;
}
case JV_STREAM:{//实时流
StramFrame_t * recFrm=(StramFrame_t *)(head);
if(recFrm)
{
recFrm->buffer = (unsigned char*)recFrm + sizeof(StramFrame_t);
if(lenTotal-PACKET_HEAD != recFrm->len + sizeof(StramFrame_t))
{
printf("bad length:chn=%d,lenTotal=%d,frmLen=%d\n", recFrm->ch, lenTotal, recFrm->len);
}
}
write_input_file(cmd,fp,recFrm->buffer,recFrm->len,recFrm->frametype);
break;
}
default:
break;
}
}