欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

从Wireshark/tcpdump文件中提取rtsp over tcp的H264数据

程序员文章站 2022-07-14 17:35:34
...

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

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

从Wireshark/tcpdump文件中提取rtsp over tcp的H264数据

从Wireshark/tcpdump文件中提取rtsp over tcp的H264数据

从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;

从Wireshark/tcpdump文件中提取rtsp over tcp的H264数据

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