|
@ -1,6 +1,12 @@ |
|
|
import os |
|
|
import os |
|
|
import sys |
|
|
import sys |
|
|
|
|
|
|
|
|
|
|
|
from threading import Thread |
|
|
|
|
|
|
|
|
|
|
|
import socketio |
|
|
|
|
|
|
|
|
|
|
|
sio = socketio.Client() |
|
|
|
|
|
|
|
|
import gi |
|
|
import gi |
|
|
gi.require_version('Gst', '1.0') |
|
|
gi.require_version('Gst', '1.0') |
|
|
from gi.repository import Gst |
|
|
from gi.repository import Gst |
|
@ -19,41 +25,42 @@ webrtcbin name=sendrecv bundle-policy=max-bundle stun-server=stun://stun.l.googl |
|
|
queue ! application/x-rtp,media=audio,encoding-name=OPUS,payload=96 ! sendrecv. |
|
|
queue ! application/x-rtp,media=audio,encoding-name=OPUS,payload=96 ! sendrecv. |
|
|
''' |
|
|
''' |
|
|
|
|
|
|
|
|
class WebRTCCamera: |
|
|
class WebRTCCamera(Thread): |
|
|
def __init__(self, id_, peer_id, server=None): |
|
|
def __init__(self): |
|
|
self.id_ = id_ |
|
|
|
|
|
self.conn = None |
|
|
|
|
|
self.pipe = None |
|
|
self.pipe = None |
|
|
self.webrtc = None |
|
|
self.webrtc = None |
|
|
self.socketio = None |
|
|
self.server = 'ws://localhost:5000' |
|
|
self.peer_id = peer_id |
|
|
|
|
|
self.room = 'default' |
|
|
|
|
|
self.sid = 'gstwebrtc1000' |
|
|
|
|
|
self.server = server or 'wss://localhost:5000/webrtc' |
|
|
|
|
|
self.connected = False |
|
|
self.connected = False |
|
|
|
|
|
self.exitFlag = False |
|
|
|
|
|
|
|
|
|
|
|
Thread.__init__(self) |
|
|
|
|
|
|
|
|
Gst.init(None) |
|
|
Gst.init(None) |
|
|
if not check_plugins(): |
|
|
if not check_plugins(): |
|
|
sys.exit(1) |
|
|
sys.exit(1) |
|
|
|
|
|
|
|
|
def connect(self, socketio, room, sid): |
|
|
def run(self): |
|
|
self.socketio = socketio |
|
|
self.connect() |
|
|
self.room = room |
|
|
while not self.exitFlag: |
|
|
self.sid = sid |
|
|
sio.sleep(1) |
|
|
|
|
|
|
|
|
|
|
|
def connect(self): |
|
|
|
|
|
sio.connect(self.server, namespaces=['/webrtc'], transports=['websocket']) |
|
|
|
|
|
print('my sid is', sio.sid) |
|
|
|
|
|
sio.emit('message', 'starting camera', namespace='/webrtc') |
|
|
self.start_pipeline() |
|
|
self.start_pipeline() |
|
|
self.connected = True |
|
|
self.connected = True |
|
|
|
|
|
|
|
|
def disconnect(self): |
|
|
def disconnect(self): |
|
|
self.connected = False |
|
|
self.connected = False |
|
|
self.close_pipeline() |
|
|
self.close_pipeline() |
|
|
self.socketio = None |
|
|
sio.disconnect() |
|
|
|
|
|
|
|
|
def start_pipeline(self): |
|
|
def start_pipeline(self): # ok |
|
|
self.pipe = Gst.parse_launch(PIPELINE_DESC) |
|
|
self.pipe = Gst.parse_launch(PIPELINE_DESC) |
|
|
self.webrtc = self.pipe.get_by_name('sendrecv') |
|
|
self.webrtc = self.pipe.get_by_name('sendrecv') |
|
|
self.webrtc.connect('on-negotiation-needed', self.on_negotiation_needed) |
|
|
self.webrtc.connect('on-negotiation-needed', self.on_negotiation_needed) |
|
|
self.webrtc.connect('on-ice-candidate', self.send_ice_candidate_message) |
|
|
self.webrtc.connect('on-ice-candidate', self.on_ice_candidate) |
|
|
self.webrtc.connect('pad-added', self.on_incoming_stream) |
|
|
|
|
|
self.pipe.set_state(Gst.State.PLAYING) |
|
|
self.pipe.set_state(Gst.State.PLAYING) |
|
|
|
|
|
|
|
|
def close_pipeline(self): |
|
|
def close_pipeline(self): |
|
@ -61,9 +68,9 @@ class WebRTCCamera: |
|
|
self.pipe = None |
|
|
self.pipe = None |
|
|
self.webrtc = None |
|
|
self.webrtc = None |
|
|
|
|
|
|
|
|
def handle_sdp_answer(self, sdp): |
|
|
def handle_sdp_answer(self, sdp): # ok |
|
|
print("handle_sdp_answer") |
|
|
print("handle_sdp_answer") |
|
|
print ('Received answer:\n%s' % sdp) |
|
|
#print ('Received answer:\n%s' % sdp) |
|
|
res, sdpmsg = GstSdp.SDPMessage.new() |
|
|
res, sdpmsg = GstSdp.SDPMessage.new() |
|
|
GstSdp.sdp_message_parse_buffer(bytes(sdp.encode()), sdpmsg) |
|
|
GstSdp.sdp_message_parse_buffer(bytes(sdp.encode()), sdpmsg) |
|
|
answer = GstWebRTC.WebRTCSessionDescription.new(GstWebRTC.WebRTCSDPType.ANSWER, sdpmsg) |
|
|
answer = GstWebRTC.WebRTCSessionDescription.new(GstWebRTC.WebRTCSDPType.ANSWER, sdpmsg) |
|
@ -71,65 +78,23 @@ class WebRTCCamera: |
|
|
self.webrtc.emit('set-remote-description', answer, promise) |
|
|
self.webrtc.emit('set-remote-description', answer, promise) |
|
|
promise.interrupt() |
|
|
promise.interrupt() |
|
|
|
|
|
|
|
|
def handle_ice(self, ice): |
|
|
def handle_ice(self, ice): # ok |
|
|
print("handle_ice") |
|
|
print("handle_ice") |
|
|
candidate = ice['candidate'] |
|
|
candidate = ice['candidate'] |
|
|
sdpmlineindex = ice['sdpMLineIndex'] |
|
|
sdpmlineindex = ice['sdpMLineIndex'] |
|
|
self.webrtc.emit('add-ice-candidate', sdpmlineindex, candidate) |
|
|
self.webrtc.emit('add-ice-candidate', sdpmlineindex, candidate) |
|
|
|
|
|
|
|
|
def on_negotiation_needed(self, element): |
|
|
def on_negotiation_needed(self, receiver_entry): # ok |
|
|
print("on_negotiation_needed") |
|
|
print("on_negotiation_needed") |
|
|
promise = Gst.Promise.new_with_change_func(self.on_offer_created, element, None) |
|
|
promise = Gst.Promise.new_with_change_func(self.on_offer_created, receiver_entry, None) |
|
|
element.emit('create-offer', None, promise) |
|
|
receiver_entry.emit('create-offer', None, promise) |
|
|
|
|
|
|
|
|
def send_ice_candidate_message(self, _, mlineindex, candidate): |
|
|
|
|
|
print("send_ice_candidate_message") |
|
|
|
|
|
icemsg = json.dumps({'type': 'candidate', 'candidate': {'candidate': candidate, 'sdpMLineIndex': mlineindex}}) |
|
|
|
|
|
self.socketio.emit('data', type='candidate', data=icemsg, room=self.room, namespace='/webrtc', skip_sid=self.sid) |
|
|
|
|
|
|
|
|
|
|
|
def on_incoming_stream(self, _, pad): |
|
|
def on_ice_candidate(self, _, mlineindex, candidate): # ok |
|
|
print("on_incoming_stream") |
|
|
print("on_ice_candidate") |
|
|
if pad.direction != Gst.PadDirection.SRC: |
|
|
icemsg = json.dumps({'type': 'ice', 'data': {'candidate': candidate, 'sdpMLineIndex': mlineindex}}) |
|
|
return |
|
|
sio.emit('message', data=icemsg, namespace='/webrtc') |
|
|
|
|
|
|
|
|
decodebin = Gst.ElementFactory.make('decodebin') |
|
|
def on_offer_created(self, promise, _, __): # ok |
|
|
decodebin.connect('pad-added', self.on_incoming_decodebin_stream) |
|
|
|
|
|
self.pipe.add(decodebin) |
|
|
|
|
|
decodebin.sync_state_with_parent() |
|
|
|
|
|
self.webrtc.link(decodebin) |
|
|
|
|
|
|
|
|
|
|
|
def on_incoming_decodebin_stream(self, _, pad): |
|
|
|
|
|
print("on_incoming_decodebin_stream") |
|
|
|
|
|
if not pad.has_current_caps(): |
|
|
|
|
|
print (pad, 'has no caps, ignoring') |
|
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
caps = pad.get_current_caps() |
|
|
|
|
|
assert (len(caps)) |
|
|
|
|
|
s = caps[0] |
|
|
|
|
|
name = s.get_name() |
|
|
|
|
|
if name.startswith('video'): |
|
|
|
|
|
q = Gst.ElementFactory.make('queue') |
|
|
|
|
|
conv = Gst.ElementFactory.make('videoconvert') |
|
|
|
|
|
sink = Gst.ElementFactory.make('autovideosink') |
|
|
|
|
|
self.pipe.add(q, conv, sink) |
|
|
|
|
|
self.pipe.sync_children_states() |
|
|
|
|
|
pad.link(q.get_static_pad('sink')) |
|
|
|
|
|
q.link(conv) |
|
|
|
|
|
conv.link(sink) |
|
|
|
|
|
elif name.startswith('audio'): |
|
|
|
|
|
q = Gst.ElementFactory.make('queue') |
|
|
|
|
|
conv = Gst.ElementFactory.make('audioconvert') |
|
|
|
|
|
resample = Gst.ElementFactory.make('audioresample') |
|
|
|
|
|
sink = Gst.ElementFactory.make('autoaudiosink') |
|
|
|
|
|
self.pipe.add(q, conv, resample, sink) |
|
|
|
|
|
self.pipe.sync_children_states() |
|
|
|
|
|
pad.link(q.get_static_pad('sink')) |
|
|
|
|
|
q.link(conv) |
|
|
|
|
|
conv.link(resample) |
|
|
|
|
|
resample.link(sink) |
|
|
|
|
|
|
|
|
|
|
|
def on_offer_created(self, promise, _, __): |
|
|
|
|
|
print("on_offer_created") |
|
|
print("on_offer_created") |
|
|
promise.wait() |
|
|
promise.wait() |
|
|
reply = promise.get_reply() |
|
|
reply = promise.get_reply() |
|
@ -139,9 +104,63 @@ class WebRTCCamera: |
|
|
promise.interrupt() |
|
|
promise.interrupt() |
|
|
text = offer.sdp.as_text() |
|
|
text = offer.sdp.as_text() |
|
|
print ('Sending offer:\n%s' % text) |
|
|
print ('Sending offer:\n%s' % text) |
|
|
msg = json.dumps({'type': 'offer', 'sdp': text}) |
|
|
msg = json.dumps({'type': 'sdp', 'data': {'type': 'offer', 'sdp': text}}) |
|
|
self.socketio.emit('data', type='offer', data=msg, room=self.room, namespace='/webrtc', skip_sid=self.sid) |
|
|
sio.emit('message', data=msg, namespace='/webrtc') |
|
|
|
|
|
print("test") |
|
|
|
|
|
|
|
|
|
|
|
@sio.on('message', namespace='/webrtc') # ok |
|
|
|
|
|
def webrtc_message(data): |
|
|
|
|
|
from roberto import webrtccamera |
|
|
|
|
|
# print('Message from {}: {}'.format(sid, data)) |
|
|
|
|
|
if not data: |
|
|
|
|
|
print("data malformed") |
|
|
|
|
|
return |
|
|
|
|
|
if not 'type' in data: |
|
|
|
|
|
print("Received message without type field") |
|
|
|
|
|
return |
|
|
|
|
|
if not 'data' in data: |
|
|
|
|
|
print("Received message without data field") |
|
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
if data['type'] == 'sdp': |
|
|
|
|
|
if not 'type' in data['data']: |
|
|
|
|
|
print("Received SDP message without type field") |
|
|
|
|
|
return |
|
|
|
|
|
if data['data']['type'] == 'answer': |
|
|
|
|
|
if 'sdp' in data['data']: |
|
|
|
|
|
print("Received SDP:") |
|
|
|
|
|
print(data['data']['sdp']) |
|
|
|
|
|
webrtccamera.handle_sdp_answer(data['data']['sdp']) |
|
|
|
|
|
else: |
|
|
|
|
|
print("Received SDP message without SDP string") |
|
|
|
|
|
else: |
|
|
|
|
|
print("Expected SDP message type \"answer\", got \"%s\"" % data['data']['type']) |
|
|
|
|
|
|
|
|
|
|
|
elif data['type'] == 'ice': |
|
|
|
|
|
if not 'sdpMLineIndex' in data['data']: |
|
|
|
|
|
print("Received ICE message without mline index") |
|
|
|
|
|
return |
|
|
|
|
|
if 'candidate' in data['data']: |
|
|
|
|
|
print("Received ICE candidate with mline index %u" % data['data']['sdpMLineIndex']) |
|
|
|
|
|
print(data['data']['candidate']) |
|
|
|
|
|
webrtccamera.handle_ice(data['data']) |
|
|
|
|
|
else: |
|
|
|
|
|
print("Received ICE message without ICE candidate string") |
|
|
|
|
|
return |
|
|
|
|
|
else: |
|
|
|
|
|
print("Unknown message \"%s\", ignoring" % data['data']) |
|
|
|
|
|
|
|
|
|
|
|
@sio.event |
|
|
|
|
|
def connected(): |
|
|
|
|
|
print("I'm connected!") |
|
|
|
|
|
|
|
|
|
|
|
@sio.event |
|
|
|
|
|
def connect_error(): |
|
|
|
|
|
print("The connection failed!") |
|
|
|
|
|
|
|
|
|
|
|
@sio.event |
|
|
|
|
|
def disconnected(): |
|
|
|
|
|
print("I'm disconnected!") |
|
|
|
|
|
|
|
|
def check_plugins(): |
|
|
def check_plugins(): |
|
|
needed = ["opus", "vpx", "nice", "webrtc", "dtls", "srtp", "rtp", |
|
|
needed = ["opus", "vpx", "nice", "webrtc", "dtls", "srtp", "rtp", |
|
|