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