├── GB28181b补充协议.pdf
├── GBT 28181-2011 安全防范视频监控联网系统信息传输、交换、控制技术要求.pdf
├── README.md
├── PTZ.py
├── h264.py
└── UDP_sip_copy.py
/GB28181b补充协议.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/10961020/GB28181/HEAD/GB28181b补充协议.pdf
--------------------------------------------------------------------------------
/GBT 28181-2011 安全防范视频监控联网系统信息传输、交换、控制技术要求.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/10961020/GB28181/HEAD/GBT 28181-2011 安全防范视频监控联网系统信息传输、交换、控制技术要求.pdf
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GB28181-python
2 | 公司需求研究了一下GB28181协议
3 | 对接的2011版国标 2016版国标向下兼容 没有什么大影响
4 | 使用python实现了此协议,没有具体验证过可以同时预览几路视频流,最多的一次是22路视频流同时预览
5 | 这个取决与网络的带宽以及部署服务器的内存大小
6 | 踩过很多坑,完善优化了很多次程序,目前这版至少后台启动两个月正常使用,生成的文件需定时清理否则服务器被写满会导致程序无法进行I/O操作而结束
7 | 本程序已100帧存储为一个文件 翻译成人话就是 大概一个视频文件大概能播三秒,想变长可以自行修改h264.py
8 | 对接国标需要提供视频方绑定三个信息:名称,IP,端口 此程序绑定的名称为 0000042001000001 如需修改,全选替换。
9 | 绑定的本地IP地址以文件的形成放在程序同一目录config.txt
10 | 内容为
11 | ip=192.168.1.1
12 | port=7890
13 |
14 | 这是我瞎写的 只是举个栗子
15 |
16 | 程序会定时获取设备信息 ,此程序获取设备信息是在当天晚上1点,之后每隔七天获取一次,保存成sb_sbdy.txt(从包里摘取主要用到的信息 格式为 名称 ID 经度 纬度 状态 摄像机类型) zong_sb_sbdy.txt(存储为接收的完成视频信息包 用于验证核实 此文件比较大)
17 |
18 | 预览的视频流根据文件 如需修改自己找 我每个方法名都注释此方法的用途
19 | 预览视频时读取的文件内容格式
20 | alives:123456432145,2143653235465743524
21 | deads:2143658754365,435676546346458,3254658857463524
22 |
23 | 就是这么个格式 中间的数字是设备ID 也是我瞎写的 如果关闭数据最后一个ID没截对 就在deads后边加回车 两秒钟扫描一次 读取一个文件删一个 不想删把删除注释就好,那样一个文件可以一直控制程序预览哪些ID,程序打开的过程中 删除预览视频文件可能会导致程序找不到文件错误,我这块没加异常处理 想比程序速度快吗 那你就删了试试看程序挂没挂
24 |
25 | PTZ文件实现对球机的控制
26 |
27 |
28 | 最后一句 python不适合做这个 只适合采坑,最好使用C++,java等其他语言实现此程序
29 | 视频花屏先抓网络包查看网关收到的码流是否正常 网关的正常说明视频发送过来的是好的 原因就只有python的效率较低,部分码流包被缓冲区丢弃最终导致花屏
30 | 本人能力较低 无法使用python在线解码H264并播放,故以缓兵之策 保存成文件
31 | ffmpeg库可以实现对码流的在线解码转发推流操作 目前本人找到python关于ffmpeg库的资料只能调用命令 无法调用ffmpeg库方法
32 |
33 |
34 | 写了这么多 各位帮忙点个star呗?感谢
35 |
36 | 参考博文
37 | ps流 h264解析
38 | https://blog.csdn.net/caixing_java/article/details/79154819
39 | https://blog.csdn.net/occupy8/article/details/39288035
40 | https://blog.csdn.net/machh/article/details/52165292
41 | ptz控制
42 | https://blog.csdn.net/zhangrui_fslib_org/article/details/52537781
43 |
--------------------------------------------------------------------------------
/PTZ.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/python
2 | # encoding: utf-8
3 | # author: zhangtong
4 | import socket
5 | import random
6 |
7 | with open('config.txt', 'r') as df:
8 | str2 = df.read()
9 | local_ip = str2[str2.find('ip=') + 3:str2.find('\n')]
10 | local_port = 7778
11 | sip_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
12 | sip_udp.bind((local_ip, int(local_port)))
13 |
14 | sb_id = '18062804121310234425'
15 |
16 |
17 | def ptz(i, control=0, height=144, width=144, speed=16):
18 | list1 = '\n'
19 | list1 += '\r\n'
20 | list1 += 'DeviceControl\r\n'
21 | list1 += '{}\r\n'.format(str(random.randint(100000, 999999))[1:])
22 | list1 += '{}\r\n'.format(i)
23 | list1 += 'a50f4d{:0>2}{:0>2}{:0>2}{:0>2}{:0>2}\r\n'.format(
24 | hex(control)[2:],
25 | hex(height)[2:],
26 | hex(width)[2:],
27 | hex(speed)[2:],
28 | hex((0xa5 + 0x0f + 0x4d + control + height + width + speed) % 0x100)[2:])
29 | list1 += '\r\n'
30 | list1 += '150\r\n'
31 | list1 += '0\r\n'
32 | list1 += '0\r\n'
33 | list1 += '0\r\n'
34 | list1 += '0\r\n'
35 | list1 += '\r\n'
36 | list1 += '\r\n'
37 |
38 | str_send = 'MESSAGE sip:{}@192.168.60.5:7100 SIP/2.0\n'.format(i)
39 | str_send += 'Via: SIP/2.0/UDP {}:{};rport;branch=z9hG4bK34202{}\n'.format(local_ip, local_port, str(random.randint(1000, 9999)))
40 | str_send += 'From: ;tag=500485{}\n'.format(local_ip, local_port, str(random.randint(1000, 9999)))
41 | str_send += 'To: \n'.format(i)
42 | str_send += 'Call-ID: {}\n'.format(i[12:]+str(random.randint(1000, 9999))[1:])
43 | str_send += 'CSeq: 20 MESSAGE\n'
44 | str_send += 'Content-Type: Application/MANSCDP+xml\n'
45 | str_send += 'Max-Forwards: 70\n'
46 | str_send += 'User-Agent: NCG V2.6.3.477777\n'
47 | str_send += 'Content-Length: {}\n\n'.format(len(list1))
48 | str_send += list1
49 | b4 = str_send.encode()
50 | sip_udp.sendto(b4, ('192.168.60.5', 7100))
51 |
52 | # ptz(sb_id, 5)
53 | # data = sip_udp.recvfrom(1500)
54 | # str_receive = data[0].decode('gbk')
55 | # print(str_receive)
56 | #
57 | # ptz(str_receive[(str_receive.find('To: 2:
66 | break
67 | ptz(sb_id, int(ptz_control))
68 | data = sip_udp.recvfrom(1500)
69 | str_receive = data[0].decode('gbk')
70 | print(str_receive)
71 |
72 | ptz(str_receive[(str_receive.find('To: 140000:
69 | with open('video/' + name_id + '/' + name_id + '_' + time_[name_id] + '.dat.tmp', 'a', encoding='latin1') as f:
70 | f.write(rtp_shipin[name_id].decode('latin1'))
71 | rtp_shipin[name_id] = b''
72 | shutil.move('video/' + name_id + '/' + name_id + '_' + time_[name_id] + '.dat.tmp',
73 | 'video/' + name_id + '/' + name_id + '_' + time_[name_id] + '.dat')
74 | time_[name_id] = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
75 |
76 |
77 | # TODO 提取RTP包中payload
78 | def recv_pkt(data, name_id):
79 | global sn
80 | global rtp_dict
81 | # global str111
82 | len_1 = len(data)
83 | bt = bitstring.BitArray(bytes=data)
84 | cc = bt[4:8].uint # 固定头部后面跟着的CSRC的数目
85 | sn_1 = bt[16:32].uint # 序列号
86 | lc = 12
87 | lc = parse_csrc(data, cc, lc)
88 | if bt[2]: # 如果该位置位,则该RTP包的尾部就包含附加的填充字节
89 | len_1 -= bt[-8:].uint
90 | if bt[3]: # 如果该位置位的话,RTP固定头部后面就跟有一个扩展头部
91 | lc = parse_ext_hdr(data[lc:], lc*8)
92 | # str111 += str(sn_1) + ' ' + str(len(data[lc:len_1])) + '\n'
93 | if sn[name_id] == -1:
94 | if bt[9:16].uint == 40:
95 | sn[name_id] = bt[16:32].uint + 1
96 | while rtp_dict[name_id].get(sn[name_id]):
97 | parse_frame(rtp_dict[name_id].get(sn[name_id]), name_id)
98 | rtp_dict[name_id].pop(sn[name_id])
99 | sn[name_id] += 1
100 | else:
101 | rtp_dict[name_id][sn_1] = data[lc:len_1]
102 | if len(rtp_dict[name_id]) == 10:
103 | lin_shi = sorted(rtp_dict[name_id].keys())
104 | for i in lin_shi:
105 | parse_frame(rtp_dict[name_id][i], name_id)
106 | rtp_dict[name_id].pop(i)
107 | sn[name_id] = i+1
108 |
109 | elif sn[name_id] == sn_1: # 按序号接包 皆大欢喜
110 | parse_frame(data[lc:len_1], name_id)
111 | sn[name_id] += 1
112 | while rtp_dict[name_id].get(sn[name_id]):
113 | parse_frame(rtp_dict[name_id].get(sn[name_id]), name_id)
114 | rtp_dict[name_id].pop(sn[name_id])
115 | sn[name_id] += 1
116 | else:
117 | rtp_dict[name_id][sn_1] = data[lc:len_1]
118 | if len(rtp_dict[name_id]) == 10:
119 | lin_shi = sorted(rtp_dict[name_id].keys())
120 | for i in lin_shi:
121 | parse_frame(rtp_dict[name_id][i], name_id)
122 | rtp_dict[name_id].pop(i)
123 | sn[name_id] = i+1
124 |
125 |
126 | def main(name_id, port, time_now, local_ip1, local_port1):
127 | global sn
128 | global ji_shu
129 | global time_
130 | global rtp_dict
131 | global rtp_shipin
132 | time_[name_id] = time_now
133 | sn[name_id] = -1
134 | ji_shu[name_id] = 0
135 | rtp_dict[name_id] = {}
136 | rtp_shipin[name_id] = b''
137 | with open('config.txt', 'r') as f:
138 | str2 = f.read()
139 | local_ip = str2[str2.find('ip=') + 3:str2.find('\n')]
140 | address = (local_ip, port)
141 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
142 | s.bind(address)
143 | s.settimeout(20)
144 | if not os.path.exists('video/'+name_id+'/'):
145 | os.makedirs('video/'+name_id+'/')
146 | with open('video/'+name_id+'/'+name_id+'_'+time_[name_id]+'.dat.tmp', 'w', encoding='latin1') as f:
147 | pass
148 | while True:
149 | try:
150 | data, addr = s.recvfrom(MAX_RTP_PKT_LEN)
151 | except socket.timeout:
152 | print("h264: ~Over {},端口: {} ,一共 {} 帧".format(name_id, port, ji_shu[name_id]))
153 | try:
154 | shutil.move('video/'+name_id+'/'+name_id+'_'+time_[name_id]+'.dat.tmp', 'video/'+name_id+'/'+name_id+'_'+time_[name_id]+'.dat')
155 | except FileNotFoundError:
156 | pass
157 | str_send = 'del:{}'.format(name_id)
158 | b4 = str_send.encode()
159 | s.sendto(b4, (local_ip1, int(local_port1)))
160 | # with open('1.txt', 'w')as f:
161 | # f.write(str111)
162 | break
163 | if len(data) == 2:
164 | if data.decode('latin1') == 'by':
165 | print("h264: Over {},端口: {} ,一共 {} 帧".format(name_id, port, ji_shu[name_id]))
166 | try:
167 | shutil.move('video/'+name_id+'/'+name_id+'_'+time_[name_id]+'.dat.tmp', 'video/'+name_id+'/'+name_id+'_'+time_[name_id]+'.dat')
168 | except FileNotFoundError:
169 | pass
170 | # with open('1.txt', 'w')as f:
171 | # f.write(str111)
172 | break
173 | recv_pkt(data, name_id)
174 | s.close()
175 | return
176 |
177 | if __name__ == '__main__':
178 | main(1, 6000, 20180202, 1, None)
179 |
--------------------------------------------------------------------------------
/UDP_sip_copy.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/python
2 | # encoding: utf-8
3 | # author: zhangtong
4 |
5 | """
6 | 海康视频转接平台
7 | """
8 |
9 | import time
10 | import os
11 | import itertools
12 | import random
13 | import socket
14 | import threading
15 | import multiprocessing
16 | # import pymysql
17 | from h264 import main
18 |
19 | ip_haikang = '' # 海康端口IP信息18062904141310236235
20 | device_dict = {
21 | # '52010000001310000467': [6000, 0, 0, 0, 0],
22 | # '52010000001310000466': [6000, 0, 0, 0, 0],
23 | # '52262274001322910598': [6000, 0, 0, 0, 0],
24 | # '18070511461310236681': [6000, 0, 0, 0],
25 | # '18062804101310239734': [6000, 0, 0, 0],
26 | # '18062804101310238708': [6000, 0, 0, 0],
27 | # '18062804101310239683': [6000, 0, 0, 0],
28 | # '18070511441310232617': [6000, 0, 0, 0]
29 | } # 设备信息 {'设备编码':端口号,尝试次数,Call-ID,tag,关闭视频流失败次数,未响应请求次数}
30 | device_dict_shiyong = {} # 正在预览的设备信息
31 | device_dict_now = {} # 请求发出去了,还没告诉我消息呢
32 | device_dict_over = {} # 当前预览过的设备信息
33 | local_ip = ''
34 | local_port = ''
35 | time_now = ''
36 | device_len = 5
37 | port = 32500
38 | sema = ''
39 |
40 |
41 | # TODO 登入
42 | def login_sip(s):
43 | global ip_haikang
44 | global device_dict
45 | global device_dict_now
46 | global device_dict_shiyong
47 | sema.acquire()
48 | while True:
49 | data, addr = s.recvfrom(1024)
50 | str_receive = data.decode('gbk')
51 | print(addr)
52 | if len(str_receive) > 30:
53 | ip_haikang = str_receive[(str_receive.find('From:') + 11):str_receive.find('>', str_receive.find('From:'))]
54 | keep_heart(s, addr, str_receive)
55 | data, addr = s.recvfrom(1024)
56 | str_receive = data.decode('latin1')
57 | if str_receive[(str_receive.find('CSeq: ') + 6):str_receive.find(
58 | '\n', str_receive.find('CSeq: '))-1] == '20 MESSAGE':
59 | keep_heart(s, addr, str_receive)
60 | device_dict.clear()
61 | device_dict_now.clear()
62 | device_dict_shiyong.clear()
63 | sema.release()
64 | return addr
65 |
66 |
67 | # TODO 保持心跳
68 | def keep_heart(s, addr_heart, str_receive):
69 | str_send = 'SIP/2.0 200 OK\n'
70 | str_send += 'To: ;tag=69113a2a\n'.format(ip_haikang)
71 | str_send += 'Contact: sip:{}\n'.format(ip_haikang)
72 | str_send += 'Content-Length: 0\n'
73 | str_send += 'CSeq: {}\n'.format(
74 | str_receive[(str_receive.find('CSeq:') + 6):str_receive.find('\n', str_receive.find('CSeq:'))])
75 | str_send += 'Call-ID: {}\n'.format(
76 | str_receive[(str_receive.find('Call-ID: ') + 9):str_receive.find('\n', str_receive.find('Call-ID: '))])
77 | str_send += 'From: ;tag={}\n'.format(
78 | ip_haikang, str_receive[(str_receive.find('tag=') + 4):str_receive.find('\n', str_receive.find('tag='))])
79 | str_send += 'Via: SIP/2.0/UDP {}:{};branch={}\n'.format(
80 | local_ip, local_port,
81 | str_receive[(str_receive.find('branch=') + 7):str_receive.find('\n', str_receive.find('branch='))])
82 | b4 = str_send.encode()
83 | s.sendto(b4, addr_heart)
84 |
85 |
86 | # TODO 获取设备信息请求
87 | def get_messages_receive(s, addr_get):
88 | list1 = '\n\nCatalog\n1{}\n{}\n\n'.format(str(random.randint(10000, 99999))[1:], ip_haikang[:ip_haikang.find('@')])
89 | str_send = 'MESSAGE sip:{} SIP/2.0\n'.format(ip_haikang)
90 | str_send += 'To: \n'.format(ip_haikang)
91 | str_send += 'Content-Length: {}\n'.format(len(list1))
92 | str_send += 'CSeq: 2 MESSAGE\n'
93 | str_send += 'Call-ID: 12495{}\n'.format(str(random.randint(10000, 99999))[1:])
94 | str_send += 'Via: SIP/2.0/UDP {}:{};rport;branch=z9hG4bK342026{}\n'.format(local_ip, local_port, str(random.randint(10000, 99999))[1:])
95 | str_send += 'From: ;tag=50048{}\n'.format(local_ip, local_port, str(random.randint(10000, 99999))[1:])
96 | str_send += 'Content-Type: Application/MANSCDP+xml\n'
97 | str_send += 'Max-Forwards: 70\n\n'
98 | str_send += list1
99 | b4 = str_send.encode()
100 | s.sendto(b4, addr_get)
101 |
102 |
103 | # TODO 设备信息数据解析
104 | def get_messages_send(str_receive):
105 | name = str_receive[(str_receive.find('')+6):str_receive.find('<', str_receive.find('')+6)]
106 | device_id = str_receive[(str_receive.find('', str_receive.find(''))+10):str_receive.find(
107 | '<', str_receive.find('', str_receive.find(''))+10)]
108 | longitude = str_receive[(str_receive.find('')+11):str_receive.find('<', str_receive.find('')+11)]
109 | latitude = str_receive[(str_receive.find('')+10):str_receive.find('<', str_receive.find('')+10)]
110 | status = str_receive[(str_receive.find('')+8):str_receive.find('<', str_receive.find('')+8)]
111 | PTZType = str_receive[(str_receive.find('')+9):str_receive.find('<', str_receive.find('')+9)]
112 | name = name.replace(' ', '')
113 | with open('zong_sb_sbdy.txt', 'a') as f:
114 | f.write(str_receive+'\r\n')
115 | with open('sb_sbdy.txt', 'a') as f:
116 | f.write(name+' '+device_id+' '+longitude+' '+latitude+' '+status+' '+PTZType+'\r\n')
117 |
118 |
119 | # TODO 发送ACK确认推流
120 | def get_video_receive2(s, addr_get, str_receive):
121 | global device_dict_shiyong
122 | global device_dict
123 | sb_id = str_receive[(str_receive.find('To:') + 9):str_receive.find('@', str_receive.find('To:'))]
124 | if sb_id in device_dict:
125 | str_send = 'ACK sip:{} SIP/2.0\n'.format(ip_haikang)
126 | str_send += 'To: \n'.format(
127 | str_receive[(str_receive.find('To:') + 9):str_receive.find('>', str_receive.find('To:'))])
128 | str_send += 'Content-Length: 0\n'
129 | str_send += 'Contact: \n'.format(local_ip, local_port)
130 | str_send += 'CSeq: {}\n'.format(
131 | str_receive[(str_receive.find('CSeq:') + 6):str_receive.find('\n', str_receive.find('CSeq:'))])
132 | str_send += 'Call-ID: {}\n'.format(
133 | str_receive[(str_receive.find('Call-ID: ') + 9):str_receive.find('\n', str_receive.find('Call-ID: '))])
134 | str_send += 'Via: SIP/2.0/UDP {}:{};branch={}\n'.format(
135 | local_ip, local_port,
136 | str_receive[(str_receive.find('branch=') + 7):str_receive.find('\n', str_receive.find('branch='))])
137 | str_send += 'From: ;tag={}\n'.format(local_ip, local_port, device_dict[sb_id][3])
138 | str_send += 'User-Agent: NCG V2.6.3.777777\n'
139 | str_send += 'Max-Forwards: 70\n'
140 | b4 = str_send.encode()
141 | s.sendto(b4, addr_get)
142 | device_dict[sb_id][1] = 0
143 | device_dict_shiyong[sb_id] = device_dict[sb_id].copy()
144 | device_dict[sb_id][1] = 100
145 | p1 = multiprocessing.Process(target=shi_pin_liu, args=(sb_id,))
146 | p1.start()
147 | # timer = threading.Timer(3600, get_videoclose_receive, (s, addr_get, sb_id))
148 | # timer.start()
149 |
150 |
151 | # TODO 发送ACK确认推流失败
152 | def get_video_receive3(s, addr_get, str_receive):
153 | global device_dict
154 | global device_dict_now
155 | sb_id = str_receive[(str_receive.find('To: \n'.format(
159 | str_receive[(str_receive.find('To:') + 9):str_receive.find('>', str_receive.find('To:'))])
160 | str_send += 'Content-Length: 0\n'
161 | str_send += 'CSeq: 20 ACK\n'
162 | str_send += 'Call-ID: {}\n'.format(
163 | str_receive[(str_receive.find('Call-ID: ') + 9):str_receive.find('\n', str_receive.find('Call-ID: '))])
164 | str_send += 'Via: SIP/2.0/UDP {}:{};branch={}\n'.format(
165 | local_ip, local_port,
166 | str_receive[(str_receive.find('branch=') + 7):str_receive.find('\n', str_receive.find('branch='))])
167 | str_send += 'From: ;tag={}\n'.format(local_ip, local_port, device_dict[sb_id][3])
168 | b4 = str_send.encode()
169 | s.sendto(b4, addr_get)
170 | try:
171 | del device_dict_now[sb_id]
172 | except KeyError:
173 | pass
174 | device_dict[sb_id][1] = int(device_dict[sb_id][1]) + 1
175 |
176 |
177 | # TODO 请求设备视频流
178 | def get_video_receive(s, addr_get):
179 | global port
180 | global device_dict
181 | global device_dict_now
182 | port_not_equal = 0
183 | print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + '一轮预览开始\n', device_dict)
184 | for i in list(device_dict.keys()): # 删除五次预览失败的id以及成功预览的id
185 | if device_dict[i][1] == 100:
186 | del device_dict[i]
187 | elif device_dict[i][1] >= device_len:
188 | print('删除 {} 该设备无法预览'.format(i))
189 | del device_dict[i]
190 | try:
191 | del device_dict_now[i]
192 | except KeyError:
193 | pass
194 | for i in list(device_dict_now.keys()): # 清理device_dict_now中错误的id
195 | if i in device_dict_shiyong:
196 | try:
197 | del device_dict_now[i]
198 | except KeyError:
199 | continue
200 | if i not in device_dict:
201 | try:
202 | del device_dict_now[i]
203 | except KeyError:
204 | pass
205 | for i in device_dict: # 五次请求之后该id预览请求还是未响应,默认删了它 重新预览
206 | if i in device_dict_now:
207 | device_dict[i][5] += 1
208 | if device_dict[i][5] >= 5:
209 | device_dict[i][5] = 0
210 | try:
211 | del device_dict_now[i]
212 | except KeyError:
213 | pass
214 | continue
215 | print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + '请求预览设备:', i)
216 | while not port_not_equal: # 保证使用的端口不重复
217 | port_not_equal = 1
218 | for j in device_dict_shiyong.values():
219 | if j[0] == port:
220 | port_not_equal = 0
221 | port += 2
222 | continue
223 | device_dict[i][0] = port
224 | device_dict[i][2] = i[12:]+str(random.randint(1000, 9999))[1:]
225 | device_dict[i][3] = str(500485)+str(random.randint(1000, 9999))[1:]
226 | list1 = 'v=0\r\n'
227 | list1 += 'o={} 0 0 IN IP4 {}\r\n'.format(i, local_ip)
228 | list1 += 's=Play\r\n'
229 | list1 += 'c=IN IP4 {}\r\n'.format(local_ip)
230 | list1 += 't=0 0\r\n'
231 | list1 += 'm=video {} RTP/AVP 96 97 98\r\n'.format(port)
232 | list1 += 'a=rtpmap:96 PS/90000\r\n'
233 | list1 += 'a=rtpmap:97 H264/90000\r\n'
234 | list1 += 'a=rtpmap:98 MPEG4/90000\r\n'
235 | list1 += 'a=recvonly\r\n'
236 | list1 += 'a=streamMode:MAIN\r\n'
237 | list1 += 'a=filesize:-1\r\n'
238 | list1 += 'y=0999999999\r\n'
239 |
240 | str_send = 'INVITE sip:{}{} SIP/2.0\n'.format(i, ip_haikang[ip_haikang.find('@'):])
241 | str_send += 'Via: SIP/2.0/UDP {}:{};rport;branch=z9hG4bK34202{}\n'.format(local_ip, local_port, str(random.randint(1000, 9999)))
242 | str_send += 'From: ;tag={}\n'.format(local_ip, local_port, device_dict[i][3])
243 | str_send += 'To: \n'.format(i, ip_haikang[ip_haikang.find('@'):])
244 | str_send += 'Call-ID: {}\n'.format(device_dict[i][2])
245 | str_send += 'CSeq: 20 INVITE\n'
246 | str_send += 'Contact: \n'.format(local_ip, local_port)
247 | str_send += 'Content-Type: Application/SDP\n'
248 | str_send += 'Max-Forwards: 70\n'
249 | str_send += 'User-Agent: NCG V2.6.3.477777\n'
250 | str_send += 'Subject: {}:{},0000042001000001:0\n'.format(i, port)
251 | str_send += 'Content-Length: {}\n\n'.format(len(list1))
252 | str_send += list1
253 | b4 = str_send.encode()
254 | s.sendto(b4, addr_get)
255 | port += 2
256 | if port == 33500:
257 | port = 32500
258 | device_dict_now[i] = device_dict[i].copy()
259 |
260 |
261 | # TODO 请求关闭某个设备视频流
262 | def get_videoclose_receive(s, addr_get, sb_id):
263 | if sb_id in device_dict_shiyong:
264 | print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + '请求关闭{}视频流'.format(sb_id))
265 | str_send = 'BYE sip:{} SIP/2.0\n'.format(ip_haikang)
266 | str_send += 'Via: SIP/2.0/UDP {}:{};rport;branch=z9hG4bK34202{}\n'.format(local_ip, local_port, str(random.randint(1000, 9999)))
267 | str_send += 'From: ;tag={}\n'.format(local_ip, local_port, device_dict_shiyong[sb_id][3])
268 | str_send += 'To: ;tag=3982398926\n'.format(sb_id, ip_haikang[ip_haikang.find('@'):])
269 | str_send += 'Call-ID: {}\n'.format(device_dict_shiyong[sb_id][2])
270 | str_send += 'CSeq: 21 BYE\n'
271 | str_send += 'Contact: \n'.format(local_ip, local_port)
272 | str_send += 'Max-Forwards: 70\n'
273 | str_send += 'User-Agent: NCG V2.6.3.477777\n'
274 | str_send += 'Content-Length: 0\n'
275 | b4 = str_send.encode()
276 | s.sendto(b4, addr_get)
277 |
278 |
279 | # TODO 监听消息
280 | def monitor_messages(s):
281 | global device_dict_shiyong
282 | while True:
283 | data, addr_messages = s.recvfrom(1500)
284 | str_receive = data.decode('gbk')
285 | sb_id = str_receive[(str_receive.find('To: ') + 6):str_receive.find('<', str_receive.find('') + 6)])
297 | # else:
298 | # print('>>>2', str_receive[
299 | # (str_receive.find('') + 6):str_receive.find('<', str_receive.find('') + 6)])
300 | else:
301 | print(str_receive)
302 | elif str_receive[(str_receive.find('CSeq: ') + 6):str_receive.find(
303 | '\n', str_receive.find('CSeq: '))-1] == '1 REGISTER':
304 | login_sip(s) # 注册
305 | elif str_receive[(str_receive.find('CSeq: ') + 6):str_receive.find(
306 | '\n', str_receive.find('CSeq: '))-1] == '20 INVITE':
307 | if str_receive.find('200 OK') != -1:
308 | print('视频流推流反馈成功 ', sb_id)
309 | if sb_id in device_dict_shiyong:
310 | continue
311 | get_video_receive2(s, addr_messages, str_receive) # 反馈发ACK
312 | elif str_receive.find('404 Not Found') != -1:
313 | print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + ' 404 Not Found', sb_id, ' ', str_receive[str_receive.find('')+11:str_receive.find('<', str_receive.find('')+11)])
314 | get_video_receive3(s, addr_messages, str_receive)
315 | elif str_receive.find('400 Bad Request') != -1:
316 | print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))+' 400 Bad Request', sb_id, ' ', str_receive[str_receive.find('')+11:str_receive.find('<', str_receive.find('')+11)])
317 | get_video_receive3(s, addr_messages, str_receive)
318 | elif str_receive[(str_receive.find('CSeq: ') + 6):str_receive.find(
319 | '\n', str_receive.find('CSeq: '))-1] == '21 BYE':
320 | if str_receive.find('200 OK') != -1:
321 | print('视频流推流结束 ', sb_id)
322 | str_send = 'by'
323 | b4 = str_send.encode('latin1')
324 | if sb_id in device_dict_shiyong:
325 | s.sendto(b4, (local_ip, int(device_dict_shiyong[sb_id][0])))
326 | device_dict_over[sb_id] = device_dict_shiyong[sb_id]
327 | del device_dict_shiyong[sb_id]
328 | else:
329 | print('视频流关闭失败'+sb_id)
330 | if sb_id in device_dict_shiyong:
331 | device_dict_shiyong[sb_id][4] += 1
332 | if device_dict_shiyong[sb_id][4] > 5:
333 | del device_dict_shiyong[sb_id]
334 | continue
335 | timer = threading.Timer(15, get_videoclose_receive, (s, addr_messages, sb_id))
336 | timer.start()
337 | elif str_receive[(str_receive.find('CSeq: ') + 6):str_receive.find(
338 | '\n', str_receive.find('CSeq: ')) - 1] == '20 NOTIFY':
339 | if str_receive.find('Catalog') != -1:
340 | keep_heart(s, addr_messages, str_receive)
341 | if str_receive.find('Status') != -1:
342 | get_messages_send(str_receive)
343 | print('1 ', str_receive[(str_receive.find('') + 6):str_receive.find('<', str_receive.find('') + 6)])
344 | elif str_receive[:4] == 'del:':
345 | try:
346 | del device_dict_shiyong[str_receive[4:]]
347 | except KeyError:
348 | pass
349 | # get_videoclose_receive(s, addr_messages, str_receive[4:])
350 |
351 |
352 | # TODO 视频预览模块
353 | def mutual_interface(s, addr_interface):
354 | global device_dict
355 | global time_now
356 | time.sleep(30)
357 | timer = threading.Timer(time.mktime(time.strptime(time.strftime('%Y%m%d', time.localtime(time.time())), "%Y%m%d"))+60*60*25-time.time(), device_info, (s, addr_interface))
358 | timer.start()
359 |
360 | while True:
361 | sema.acquire()
362 | sb_dat(s, addr_interface)
363 | # if not device_dict:
364 | # while device_dict_shiyong:
365 | # time.sleep(2)
366 | # print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
367 | # sb_dat(s, addr_interface)
368 | # device_dict_over.clear()
369 | # time.sleep(10)
370 | sema.release()
371 | time.sleep(2)
372 |
373 |
374 | # TODO 视频处理中心
375 | def shi_pin_liu(i):
376 | if i in device_dict_shiyong:
377 | main(i, device_dict_shiyong[i][0], time_now, local_ip, local_port)
378 | return
379 |
380 |
381 | # def sql_sb():
382 | # global device_dict
383 | # device_dict = {}
384 | # db = pymysql.connect(
385 | # host='1.1.1.1',
386 | # user='pro',
387 | # password='1',
388 | # database='n'
389 | # )
390 | # cur = db.cursor()
391 | # sql_select = "SELECT"
392 | # cur.execute(sql_select)
393 | #
394 | # data = cur.fetchall()
395 | # print('从库表中取出{}个隧道桥梁设备id'.format(len(data)))
396 | # db.commit()
397 | # cur.close()
398 | # db.close()
399 | # for i in data:
400 | # device_dict[i[0]] = [6000, 0, 0, 0]
401 |
402 |
403 | # TODO 更新设备状态一天一次
404 | def device_info(s, addr_interface):
405 | print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + '更新设备状态')
406 | timer = threading.Timer(time.mktime(time.strptime(time.strftime('%Y%m%d', time.localtime(time.time())), "%Y%m%d"))+60*60*24*7-time.time(), device_info, (s, addr_interface))
407 | timer.start()
408 | try:
409 | os.remove('zong_sb_sbdy.txt')
410 | os.remove('sb_sbdy.txt')
411 | except Exception:
412 | pass
413 | get_messages_receive(s, addr_interface)
414 |
415 |
416 | # TODO 读本地文件更新设备是否播放
417 | def sb_dat(s, addr_interface):
418 | global device_dict
419 | global time_now
420 | time_now = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
421 | device_dict_del = []
422 | dizhi = '/data/video/'
423 | lists = os.listdir(dizhi)
424 | for k in lists:
425 | if k.find('.tmp') != -1:
426 | continue
427 | with open(dizhi + k, 'r', encoding='gbk') as f:
428 | str1 = f.read()
429 | open_1 = str1[str1.find('alives:') + 7:str1.find('\n')].split(',')
430 | close_1 = str1[str1.find('deads:') + 6:].split(',')
431 | for i, j in itertools.zip_longest(open_1, close_1):
432 | if i and i not in device_dict_shiyong and i not in device_dict_now:
433 | if(i[:2] == '12' or i[:2] == '13' or i[0] == '5')and i not in device_dict:
434 | device_dict[i] = [6000, 0, 0, 0, 0, 0]
435 | if j and j in device_dict_shiyong:
436 | if(j[:2] == '12' or j[:2] == '13' or j[0] == '5')and j not in device_dict_del:
437 | device_dict_del.append(j)
438 | os.remove(dizhi + k)
439 | if device_dict or device_dict_del:
440 | print('----------------------------------')
441 | print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + '需要打开的视频设备: ', list(device_dict.keys()))
442 | print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + '需要关闭的视频设备: ', device_dict_del)
443 | get_video_receive(s, addr_interface)
444 | for i in device_dict_del:
445 | get_videoclose_receive(s, addr_interface, i)
446 | # time.sleep(1)
447 |
448 | if __name__ == '__main__':
449 | with open('config.txt', 'r') as df:
450 | str2 = df.read()
451 | local_ip = str2[str2.find('ip=') + 3:str2.find('\n')]
452 | local_port = str2[str2.find('port=') + 5:str2.find('\n', str2.find('port='))]
453 | sip_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
454 | sip_udp.bind((local_ip, int(local_port)))
455 | sema = threading.Semaphore(value=1)
456 | print('开始注册...')
457 | addr1 = login_sip(sip_udp)
458 | print('注册成功...')
459 | t1 = threading.Thread(target=monitor_messages, args=(sip_udp,))
460 | t2 = threading.Thread(target=mutual_interface, args=(sip_udp, addr1))
461 | t1.start()
462 | t2.start()
463 | t1.join()
464 | t2.join()
465 | sip_udp.close()
466 |
--------------------------------------------------------------------------------