对于从Wireshark抓取的rtsp/tcp数据文件,要想提取出里面的h264码流数据,貌似Wireshark并未提供相关功能选项。无赖之下只有自己动手写一个吧。

     下面是在linux上用 tcpdump -i enp3s0 -c 7000 src 192.168.8.0 -w /home/guoke/test.cap 抓取的test.cap文件在Wireshark中的显示界面截图。

从test.cap文件中提取h264数据的关键是,需要像剥洋葱一样,读取各个网络协议头部。剥离完了后,剩下的就是需要的h264视频数据。大致步骤如下:

1. 读取 cap格式文件 的头部

typedef struct _TCPDUMP_CAP_FILE_HEADER_
{
    int magic; //4bytes [D4 C3 B2 A1] = 0xA1B2C3D4
    int version_major;//2bytes [02 00] = 2
    int version_minor;//2bytes [04 00] = 4
    int time_zone;//4bytes 时区
    int timestamp_accuracy;//4bytes 时间戳精度
    int max_capture_size_per_packet;//4bytes 每个包的抓包的最大值(单位:字节) [00 00 04 00] = 0x40000 = 262144;
    int data_link_layer_type;//4bytes 数据链路层类型 [01 00 00 00] = 1 = LINKTYPE_ETHERNET; https://www.tcpdump.org/linktypes.html
}TCPDUMP_CAP_FILE_HEADER;

2. 读取 以太网帧(Ethernet Frame)(物理层的数据帧)的头部

typedef struct _ETHERNET_FRAME_
{
    unsigned long long timestamp; //8bytes 帧时间戳
    char timestampStr[80]; //帧时间戳 Arrival Time: Nov  1, 2019 15:35:59.430461000 中国标准时间
    int frame_length; //4bytes 帧数据大小(不包含头部本身16字节,单位:字节)一般为 0x05EA = 1514 bytes
    int capture_length; //4bytes 捕获的帧数据大小
}ETHERNET_FRAME;

3. 读取 数据链路层以太网帧 的头部(以太网协议版本II)

typedef struct _ETHERNET_II_HEADER_
{
    char destination_address[50]; //6bytes 目的MAC:厂名_序号(网卡地址) Address: HuaweiTe_70:5c:3c (08:4f:0a:70:5c:3c)
    char source_address[50]; //6bytes 源MAC:厂名_序号(网卡地址) Address: Hangzhou_68:5c:9c (48:7a:da:68:5c:9c)
    int type; //2bytes 帧内封装的上层协议类型(IP=0x0800,ARP=0806,RARP=0835 [TCP-IP详解卷1:协议 图2-1 16页])Type: IPv4 (0x0800)
}ETHERNET_II_HEADER;

4. 读取 互联网层IP包 的头部 [TCP-IP详解卷1:协议 图3-1 24页]

   注意:在此处将只挑选含有rtsp server ip的以太帧数据,其他的以太帧全被过滤掉(即不进行接下来的分析步骤)

typedef struct _INTERNET_PROTOCOL_V4_HEADER_
{
    int version; //4bit 版本 Version: 4
    int ip_header_length; //4bit IP包头部长度 Header Length: 20 bytes (5)
    int differentiated_services_field; //8bit 差分服务字段 Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT)
    int total_length; //16bit IP包的总长度 Total Length: 52
    int identification; //16bit 标志字段 Identification: 0x0000 (0)
    int flags_reserved_1bit; //1bit 标志字段 0 = Reserved bit: Not set
    int flags_do_not_fragment_set_1bit; //1bit 标志字段 .1.. .... .... .... = Don't fragment: Set
    int flags_more_fragments_1bit; //1bit 标志字段 ..0. .... .... .... = More fragments: Not set
    int flags_fragments_offset_13bits; //13bit 标志字段 分段偏移量(将一个IP包分段后传输时,本段的标识)...0 0000 0000 0000 = Fragment offset: 0
    int time_to_live; //8bit 生存期TTL Time to live: 62
    int protocol; //8bit 此包内封装的上层协议 Protocol: TCP (6); UDP(17) https://tools.ietf.org/html/rfc1700 Page7
    int header_checksum; //16bit 头部数据的校验和 Header checksum: 0x326e [validation disabled]
    int source_ip_addr; //32bit 源IP地址 Source: 192.168.8.0
    int destination_ip_addr; //32bit 目的IP地址 Destination: 192.168.8.1
}INTERNET_PROTOCOL_V4_HEADER;

5. 读取 传输层TCP数据段 的头部 [TCP-IP详解卷1:协议 图17-2 172页]

typedef struct _TCP_OPTIONS_
{
    int kind; //8bits TCP选项的类型
    int length; //8bits TCP选项的总长度(单位:byte,包含本字段长度)
    char data[32]; //只有当length大于2时,此字段才有效
}TCP_OPTIONS;

typedef struct _TRANSMISSION_CONTROL_PROTOCOL_HEADER_
{
    int source_port; //16bit 源端口号 Source Port: 554
    int destination_port; //16bit 目的端口号 Destination Port: 55014
    unsigned int sequence_number; //32bit 序列号 Sequence number: 0    (relative sequence number)
    unsigned int next_sequence_number; //32bit 下一个期望的序列号 = sequence_number + tcp_payload_length
    unsigned int acknowledgment_number; //32bit 确认序列号 Acknowledgment number: 1    (relative ack number)
    int tcp_header_length; //4bit 给出头部占32比特的数目。没有任何选项字段的TCP头部长度为20字节(5x32=160比特);最多可以有60字节的TCP头部。 1000 .... = Header Length: 32 bytes (8)
    int flags_reserved_3bit; //3bit 保留字段 000. .... .... = Reserved: Not set
    int flags_nonce_1bit; //1bit 保留字段 ....0 .... .... = Nonce: Not set
    int flags_congestion_window_reduced_1bit; //1bit 拥塞窗口减少 ...... 0... .... = Congestion Window Reduced (CWR): Not set
    int flags_ecn_echo_1bit; //1bit 显式拥塞通知Explicit Congestion Notification .... .0.. .... = ECN-Echo: Not set
    int flags_urgent_1bit; //1bit 紧急指针URG( urgent pointer)有效 .... ..0. .... = Urgent: Not set
    int flags_acknowledgment_1bit; //1bit 确认序号有效ACK .... ...1 .... = Acknowledgment: Set
    int flags_push_1bit; //1bit 接收方应该尽快将这个报文段交给应用层PSH .... .... 0... = Push: Not set
    int flags_reset_1bit; //1bit 重建连接RST .... .... .0.. = Reset: Not set
    int flags_syn_1bit; //1bit 同步序号用来发起一个连接SYN .... .... ..1. = Syn: Set
    int flags_fin_1bit; //1bit 发端完成发送任务FIN .... .... ...0 = Fin: Not set
    int window_size; //16bit 流量控制的窗口大小 Window size value: 29200
    int checksum; //16bit TCP数据段的校验和 Checksum: 0x7f9e [unverified]
    int urgent_pointer; //16bit 紧急指针 Urgent pointer: 0
    int tcp_options_size; //TCP可选项的数目,范围[1,40],等于0时tcp_options[40]字段无效
    TCP_OPTIONS tcp_options[40]; //TCP可选项
    unsigned char *tcp_payload; //TCP有效载荷
    int tcp_payload_length; //注意:一个TCP包的有效载荷可能被RTP人为的分成两个部分,原因是:单个RTP包的大小超出了TCP的最大载荷容量(即1460bytes),这时候超出的部分只有放入下一个TCP包
}TRANSMISSION_CONTROL_PROTOCOL_HEADER;

6. 读取 TCP payload 有效载荷数据中的 RTSP 数据,如果tcp_payload的第一个字符是 '$'=0x24,则表示是RTP包数据。否则是      RTSP的请求信令字符串,比如 "OPTIONS rtsp://192.168.8.0:554/h264/ch1/main/av_stream RTSP/1.0"

typedef struct _RTSP_INTERLEAVED_FRAME_
{
    int magic;//1byte 0x24 => '$'
    int channel; //1byte 0-1
    int rtp_length; //2bytes rtp包发送时单个包的载荷总大小(即不包含 magic、channel、rtp_length 这3个字段所占的4字节),但实际上可能超过TCP最大的1460字节
    int rtp_real_read_bytes_in_single_tcp_packet; //在单个TCP包中实际上读到的字节数目,如果该值小于rtp_length,则剩下的字节需要在下一个TCP包中读取
    unsigned char * interleaved_frame_data;
    RTP_AND_RTCP_INFO rtp_and_rtcp;
}RTSP_INTERLEAVED_FRAME;

7. 读取 RTP数据包 的头部

typedef struct _RTP_HEADER_AND_PAYLOAD_
{
    int rtp_packet_total_size; //该rtp包的总大小(单位:字节)
    int channel; //rtsp通道,偶数为数据通道,比如0-表示视频通道,2-表示音频通道
    int version;//2bits 用来标志使用的RTP版本 10.. .... = Version: RFC 1889 Version (2)
    int padding;//1bit .如果为1,则该RTP包的尾部包含附加的填充字节 1. .... = Padding: True
    int extension;//1bit 如果为1,RTP头部后面有一个扩展头部 ...0 .... = Extension: False
    int contributing_source_identifiers_count;//4bits 头部后面跟着的CSRC的数目 .... 0000 = Contributing source identifiers count: 0
    int marker;//1bit 标记位(1代表一帧数据的结束) 0... .... = Marker: False
    int payload_type;//7bits RTP载荷类型 Payload type: DynamicRTP-Type-96 (96); 96是指h264编码
    unsigned int sequence_number;//16bits 序列号 Sequence number: 48657
    unsigned int timestamp;//32bits 该RTP包中数据的第一个字节的采样时刻 Timestamp: 706810978
    int synchronization_source_identifier;//32bits 同步源标识符(SSRC)指RTP包流的来源 Synchronization Source identifier: 0x45439e03 (1162059267)
    int rtp_header_extension_defined_by_profile;//32bits
    int rtp_header_extension_length;//32bits (长度不包含本身)
    int rtp_payload_size;//RTP包有效载荷大小
    unsigned char *rtp_payload;//RTP包有效载荷 Payload: 420101016000000300b0000003000003007ba003c08010e5...
    int padding_data;//(padding_count - 8)bits 附加的填充字节 Padding data: 0000
    int padding_count;//8bits 附加的填充字节数目(包含自身) Padding count: 3
    H264_DATA h264_data; //h264数据
}RTP_HEADER_AND_PAYLOAD;

8. 读取 RTP payload载荷中的h264视频数据 的头部

typedef struct _H264_DATA_
{
    char start_code[5]; // 00 00 00 01 67
    int start_code_length; // 0 or 5,如果为5,则表示一个h264包的开始,为0表示中间部分或结束部分
    int nal_unit_type_h264; //nal的类型(取值范围:1-23)
    int nal_unit_type_h264_rtp; //nal的rtp类型(1-23时表示的意思和h264的nal_unit_type定义一致)
    unsigned char *h264_sub_packet_data; //一个h264包可能被拆成多个RTP包碎片
    int h264_sub_packet_data_length; //h264碎片大小
}H264_DATA;

注意:RTP对H264打包时,有几种打包方式,比如H264 I 帧数据太大了,则需要分成多个RTP包,或者H264 P帧数据不大,将多个P帧数据打包成单个RTP包

Type   Packet    Type name                        Section
---------------------------------------------------------
0      undefined                                    -
1-23   NAL unit  Single NAL unit packet per H.264   5.6
24     STAP-A    Single-time aggregation packet     5.7.1
25     STAP-B    Single-time aggregation packet     5.7.1
26     MTAP16    Multi-time aggregation packet      5.7.2
27     MTAP24    Multi-time aggregation packet      5.7.2
28     FU-A      Fragmentation unit                 5.8
29     FU-B      Fragmentation unit                 5.8
30-31  undefined        -                           -

---------------------------------------------

工程代码的地址:https://github.com/jfu222/wireshark_rtsp_over_tcp

 

Logo

更多推荐