├── 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: ', 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 | --------------------------------------------------------------------------------