当RTSP客户端向RTSP服务端发送完PLAY命令后,RTSP服务端就会另外开启UDP端口(SDP协商定义的端口)发送RTP媒体流数据包。这些数据包之间会间隔一段时间(毫秒级)陆续被发送到RTSP客户端,此时RTSP客户端可以调用GetMediaData等接口获取媒体流数据。
一、uint8_t * RtspClient::GetMediaData(string media_type, uint8_t * buf, size_t * size, size_t max_size)
该函数的作用即获取媒体流数据,并将数据放入参数buf中,数据大小放入size中,media_type可以为字符串“audio”或“video”,max_size为buf的最大值。
该函数首先在MediaSessionMap中查询匹配media_type的媒体会话,然后选择调用GetVideoData或GetAudioData。
1 uint8_t * RtspClient::GetMediaData(string media_type, uint8_t * buf, size_t * size, size_t max_size) 2 { 3 MyRegex Regex; 4 map<string, MediaSession>::iterator it; 5 bool IgnoreCase = true; 6 if(!buf) return NULL; 7 if(!size) return NULL; 8 9 *size = 0; 10 11 for(it = MediaSessionMap->begin(); it != MediaSessionMap->end(); it++) { 12 if(Regex.Regex(it->first.c_str(), media_type.c_str(), IgnoreCase)) break; 13 } 14 15 if(it == MediaSessionMap->end()) { 16 fprintf(stderr, "%s: No such media session\n", __func__); 17 return NULL; 18 } 19 20 if(it->second.MediaType == "video") return GetVideoData(&(it->second), buf, size, max_size); 21 if(it->second.MediaType == "audio") return GetAudioData(&(it->second), buf, size, max_size); 22 return NULL; 23 }
二、uint8_t * RtspClient::GetVideoData(MediaSession * media_session, uint8_t * buf, size_t * size, size_t max_size, bool get_vps_sps_pps_periodly)
参数get_vps_sps_pps_periodly用来指示是否要周期性写入VPS、SPS和PPS(h264/h265中参数),默认为true。周期写入的目的是为了防止视频传输一开始将这些关键参数丢失,虽然会多耗费一点带宽,但是相对视频数据本身可谓九牛一毛,重要的是这样可以用来防止由于这些参数丢失所导致的诸如花屏之类的问题。
然后根据media_session中的编码类型,获取RTP音视频传输解析层中的特定类对象(NaluBaseType_H264Obj、NaluBaseType_H265Obj)来处理视频数据。
接着是循环获取视频帧的NALU单元,由于一个NALU单元常常会超过一个MTU(最大传输单元),所以NALU单元就会被分包,最后一个分包会带一个结束标志,如果获得了NALU单元的最后一个分包,则跳出该循环并从函数中返回。
1 uint8_t * RtspClient::GetVideoData(MediaSession * media_session, uint8_t * buf, size_t * size, size_t max_size, bool get_vps_sps_pps_periodly) 2 { 3 if(!media_session || !buf || !size) return NULL; 4 5 *size = 0; 6 7 const size_t GetSPS_PPS_Period = GET_SPS_PPS_PERIOD; // 30 times 8 if(true == get_vps_sps_pps_periodly) { 9 if(GetVideoDataCount >= GetSPS_PPS_Period) { 10 GetVideoDataCount = 0; 11 12 const size_t NALU_StartCodeSize = 4; 13 size_t SizeTmp = 0; 14 if(!GetVPSNalu(buf + (*size), &SizeTmp) || SizeTmp <= NALU_StartCodeSize) { 15 // fprintf(stderr, "\033[31mWARNING: No H264 VPS\033[0m\n"); 16 } else { 17 *size += SizeTmp; 18 } 19 if(!GetSPSNalu(buf + (*size), &SizeTmp) || SizeTmp <= NALU_StartCodeSize) { 20 fprintf(stderr, "\033[31mWARNING: No SPS\033[0m\n"); 21 } else { 22 *size += SizeTmp; 23 } 24 if(!GetPPSNalu(buf + (*size), &SizeTmp) || SizeTmp <= NALU_StartCodeSize) { 25 fprintf(stderr, "\033[31mWARNING: No PPS\033[0m\n"); 26 } else { 27 *size += SizeTmp; 28 } 29 return buf; 30 } else { 31 GetVideoDataCount++; 32 } 33 } 34 35 size_t SizeTmp = 0; 36 bool EndFlag = false; 37 NALUTypeBase * NALUTypeBaseTmp = NULL; 38 NALUTypeBase * NALUType; 39 40 int PM = media_session->Packetization; 41 if(!IS_PACKET_MODE_VALID(PM)) { 42 cerr << "WARNING:Invalid Packetization Mode" << endl; 43 return NULL; 44 } 45 if(media_session->EncodeType == "H264") { 46 NALUTypeBaseTmp = &NaluBaseType_H264Obj; 47 } else if (media_session->EncodeType == "H265") { 48 NALUTypeBaseTmp = &NaluBaseType_H265Obj; 49 } else { 50 // Unknown Nalu type 51 printf("Unsupported codec type: %s\n", media_session->EncodeType.c_str()); 52 return NULL; 53 } 54 55 do { 56 EndFlag = true; 57 if(!media_session->GetMediaData(VideoBuffer.Buf, &SizeTmp)) return NULL; 58 if(0 == SizeTmp) { 59 cerr << "No RTP data" << endl; 60 return NULL; 61 } 62 int NT; 63 NT = NALUTypeBaseTmp->ParseNALUHeader_Type(VideoBuffer.Buf); 64 NALUType = NALUTypeBaseTmp->GetNaluRtpType(PM, NT); 65 if(NULL == NALUType) { 66 printf("Unknown NALU Type: %s\n", media_session->EncodeType.c_str()); 67 return NULL; 68 } 69 70 if(SizeTmp > VideoBuffer.Size) { 71 cerr << "Error: RTP Packet too large(" << SizeTmp << " bytes > " << VideoBuffer.Size << "bytes)" << endl; 72 return NULL; 73 } 74 75 if(*size + SizeTmp > max_size) { 76 fprintf(stderr, "\033[31mWARNING: NALU truncated because larger than buffer: %u(NALU size) > %u(Buffer size)\033[0m\n", *size + SizeTmp, max_size); 77 return buf; 78 } 79 80 SizeTmp = NALUType->CopyData(buf + (*size), VideoBuffer.Buf, SizeTmp); 81 *size += SizeTmp; 82 EndFlag = NALUType->GetEndFlag(); 83 } while(!EndFlag); 84 85 return buf; 86 }
三、uint8_t * RtspClient::GetAudioData(MediaSession * media_session, uint8_t * buf, size_t * size, size_t max_size)
该函数和GetVideoData的思路一样:
先是获取RTP音视频传输解析层中的特定类对象(MPEG_AudioObj),然后再去获取音频数据流。
1 uint8_t * RtspClient::GetAudioData(MediaSession * media_session, uint8_t * buf, size_t * size, size_t max_size) 2 { 3 if(!media_session || !buf || !size) return NULL; 4 5 *size = 0; 6 7 size_t SizeTmp = 0; 8 MPEGTypeBase * MPEGType; 9 10 if(!media_session->GetMediaData(AudioBuffer.Buf, &SizeTmp)) return NULL; 11 if(0 == SizeTmp) { 12 cerr << "No RTP data" << endl; 13 return NULL; 14 } 15 16 MPEGType = &MPEG_AudioObj; 17 18 if(SizeTmp > AudioBuffer.Size) { 19 cerr << "Error: RTP Packet too large(" << SizeTmp << " bytes > " << AudioBuffer.Size << "bytes)" << endl; 20 return NULL; 21 } 22 23 if(*size + SizeTmp > max_size) { 24 fprintf(stderr, "\033[31mWARNING: NALU truncated because larger than buffer: %u(NALU size) > %u(Buffer size)\033[0m\n", *size + SizeTmp, max_size); 25 return buf; 26 } 27 28 SizeTmp = MPEGType->CopyData(buf + (*size), AudioBuffer.Buf, SizeTmp); 29 *size += SizeTmp; 30 31 return buf; 32 }