Hendrik Langer
4 years ago
6 changed files with 187 additions and 1 deletions
@ -0,0 +1,150 @@ |
|||||
|
import os |
||||
|
import sys |
||||
|
|
||||
|
import gi |
||||
|
gi.require_version('Gst', '1.0') |
||||
|
from gi.repository import Gst |
||||
|
gi.require_version('GstWebRTC', '1.0') |
||||
|
from gi.repository import GstWebRTC |
||||
|
gi.require_version('GstSdp', '1.0') |
||||
|
from gi.repository import GstSdp |
||||
|
|
||||
|
import json |
||||
|
|
||||
|
PIPELINE_DESC = ''' |
||||
|
webrtcbin name=sendrecv bundle-policy=max-bundle stun-server=stun://stun.l.google.com:19302 |
||||
|
videotestsrc is-live=true pattern=ball ! videoconvert ! queue ! vp8enc deadline=1 ! rtpvp8pay ! |
||||
|
queue ! application/x-rtp,media=video,encoding-name=VP8,payload=97 ! sendrecv. |
||||
|
audiotestsrc is-live=true wave=red-noise ! audioconvert ! audioresample ! queue ! opusenc !rtpopuspay ! |
||||
|
queue ! application/x-rtp,media=audio,encoding-name=OPUS,payload=96 ! sendrecv. |
||||
|
''' |
||||
|
|
||||
|
class WebRTCCamera: |
||||
|
def __init__(self, id_, peer_id, server=None): |
||||
|
self.id_ = id_ |
||||
|
self.conn = None |
||||
|
self.pipe = None |
||||
|
self.webrtc = None |
||||
|
self.socketio = None |
||||
|
self.peer_id = peer_id |
||||
|
self.room = 'default' |
||||
|
self.sid = 'gstwebrtc1000' |
||||
|
self.server = server or 'wss://localhost:5000/webrtc' |
||||
|
self.connected = False |
||||
|
|
||||
|
Gst.init(None) |
||||
|
if not check_plugins(): |
||||
|
sys.exit(1) |
||||
|
|
||||
|
def connect(self, socketio, room, sid): |
||||
|
self.socketio = socketio |
||||
|
self.room = room |
||||
|
self.sid = sid |
||||
|
self.start_pipeline() |
||||
|
self.connected = True |
||||
|
|
||||
|
def disconnect(self): |
||||
|
self.connected = False |
||||
|
self.close_pipeline() |
||||
|
self.socketio = None |
||||
|
|
||||
|
def start_pipeline(self): |
||||
|
self.pipe = Gst.parse_launch(PIPELINE_DESC) |
||||
|
self.webrtc = self.pipe.get_by_name('sendrecv') |
||||
|
self.webrtc.connect('on-negotiation-needed', self.on_negotiation_needed) |
||||
|
self.webrtc.connect('on-ice-candidate', self.send_ice_candidate_message) |
||||
|
self.webrtc.connect('pad-added', self.on_incoming_stream) |
||||
|
self.pipe.set_state(Gst.State.PLAYING) |
||||
|
|
||||
|
def close_pipeline(self): |
||||
|
self.pipe.set_state(Gst.State.NULL) |
||||
|
self.pipe = None |
||||
|
self.webrtc = None |
||||
|
|
||||
|
def handle_sdp_answer(self, sdp): |
||||
|
print ('Received answer:\n%s' % sdp) |
||||
|
res, sdpmsg = GstSdp.SDPMessage.new() |
||||
|
GstSdp.sdp_message_parse_buffer(bytes(sdp.encode()), sdpmsg) |
||||
|
answer = GstWebRTC.WebRTCSessionDescription.new(GstWebRTC.WebRTCSDPType.ANSWER, sdpmsg) |
||||
|
promise = Gst.Promise.new() |
||||
|
self.webrtc.emit('set-remote-description', answer, promise) |
||||
|
promise.interrupt() |
||||
|
|
||||
|
def handle_ice(self, ice): |
||||
|
candidate = ice['candidate'] |
||||
|
sdpmlineindex = ice['sdpMLineIndex'] |
||||
|
self.webrtc.emit('add-ice-candidate', sdpmlineindex, candidate) |
||||
|
|
||||
|
def on_negotiation_needed(self, element): |
||||
|
promise = Gst.Promise.new_with_change_func(self.on_offer_created, element, None) |
||||
|
element.emit('create-offer', None, promise) |
||||
|
|
||||
|
def send_ice_candidate_message(self, _, mlineindex, candidate): |
||||
|
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): |
||||
|
if pad.direction != Gst.PadDirection.SRC: |
||||
|
return |
||||
|
|
||||
|
decodebin = Gst.ElementFactory.make('decodebin') |
||||
|
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): |
||||
|
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, _, __): |
||||
|
promise.wait() |
||||
|
reply = promise.get_reply() |
||||
|
offer = reply['offer'] |
||||
|
promise = Gst.Promise.new() |
||||
|
self.webrtc.emit('set-local-description', offer, promise) |
||||
|
promise.interrupt() |
||||
|
text = offer.sdp.as_text() |
||||
|
print ('Sending offer:\n%s' % text) |
||||
|
msg = json.dumps({'type': 'offer', 'sdp': text}) |
||||
|
self.socketio.emit('data', type='offer', data=msg, room=self.room, namespace='/webrtc', skip_sid=self.sid) |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
def check_plugins(): |
||||
|
needed = ["opus", "vpx", "nice", "webrtc", "dtls", "srtp", "rtp", |
||||
|
"rtpmanager", "videotestsrc", "audiotestsrc"] |
||||
|
missing = list(filter(lambda p: Gst.Registry.get().find_plugin(p) is None, needed)) |
||||
|
if len(missing): |
||||
|
print('Missing gstreamer plugins:', missing) |
||||
|
return False |
||||
|
return True |
Loading…
Reference in new issue