2 changed files with 101 additions and 3 deletions
@ -0,0 +1,91 @@ |
|||||
|
import cv2 |
||||
|
import gi |
||||
|
import threading |
||||
|
import numpy as np |
||||
|
|
||||
|
gi.require_version('Gst', '1.0') |
||||
|
gi.require_version('GstRtspServer', '1.0') |
||||
|
from gi.repository import Gst, GstRtspServer, GLib |
||||
|
|
||||
|
|
||||
|
class VideoStream(GstRtspServer.RTSPMediaFactory): |
||||
|
def __init__(self, fps=30, width=640, height=480): |
||||
|
super(VideoStream, self).__init__() |
||||
|
self.fps = fps |
||||
|
self.width = width |
||||
|
self.height = height |
||||
|
self.frame = np.zeros((height, width, 3), dtype=np.uint8) # Black frame initially |
||||
|
self.lock = threading.Lock() |
||||
|
|
||||
|
def update_frame(self, frame): |
||||
|
"""Externally updates the current frame.""" |
||||
|
with self.lock: |
||||
|
self.frame = cv2.resize(frame, (self.width, self.height)) # Resize if necessary |
||||
|
|
||||
|
def on_need_data(self, src, length): |
||||
|
"""Provides frames when the pipeline requests data.""" |
||||
|
with self.lock: |
||||
|
frame_rgb = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB) # Convert BGR to RGB |
||||
|
data = frame_rgb.tobytes() # Convert to bytes |
||||
|
|
||||
|
buf = Gst.Buffer.new_allocate(None, len(data), None) |
||||
|
buf.fill(0, data) |
||||
|
buf.duration = Gst.SECOND // self.fps |
||||
|
buf.pts = buf.dts = Gst.CLOCK_TIME_NONE |
||||
|
src.emit("push-buffer", buf) |
||||
|
|
||||
|
def do_create_element(self, url): |
||||
|
"""Creates the GStreamer pipeline for RTSP streaming.""" |
||||
|
pipeline_str = ( |
||||
|
"appsrc name=source is-live=true format=GST_FORMAT_TIME " |
||||
|
"caps=video/x-raw,format=RGB,width={},height={},framerate={}/1 " |
||||
|
"! videoconvert ! video/x-raw,format=I420 " |
||||
|
"! x264enc tune=zerolatency bitrate=500 speed-preset=ultrafast " |
||||
|
"! rtph264pay config-interval=1 name=pay0 pt=96" |
||||
|
).format(self.width, self.height, self.fps) |
||||
|
|
||||
|
pipeline = Gst.parse_launch(pipeline_str) |
||||
|
src = pipeline.get_by_name("source") |
||||
|
src.connect("need-data", self.on_need_data) |
||||
|
return pipeline |
||||
|
|
||||
|
|
||||
|
class RTSPServer: |
||||
|
def __init__(self, ip="0.0.0.0", port=8554, mount_point="/stream"): |
||||
|
Gst.init(None) |
||||
|
self.server = GstRtspServer.RTSPServer() |
||||
|
self.server.set_address(ip) |
||||
|
self.server.set_service(str(port)) |
||||
|
|
||||
|
self.video_stream = VideoStream() |
||||
|
self.server.get_mount_points().add_factory(mount_point, self.video_stream) |
||||
|
self.server.attach(None) |
||||
|
|
||||
|
def start(self): |
||||
|
print(f"RTSP Server running at rtsp://{self.server.get_address()}:{self.server.get_service()}/stream") |
||||
|
loop = GLib.MainLoop() |
||||
|
loop.run() |
||||
|
|
||||
|
def update_frame(self, frame): |
||||
|
"""Externally updates the current frame for streaming.""" |
||||
|
self.video_stream.update_frame(frame) |
||||
|
|
||||
|
# Global server instance |
||||
|
rtsp_server = RTSPServer("192.168.0.196",41231) |
||||
|
|
||||
|
def run_server(): |
||||
|
rtsp_server.start() |
||||
|
|
||||
|
def stream_webcam(): |
||||
|
cap = cv2.VideoCapture("/home/mht/Downloads/bcd2890d71caaf0e095b95c9b525973f61186656-360p.mp4") # Open webcam |
||||
|
while cap.isOpened(): |
||||
|
ret, frame = cap.read() |
||||
|
if ret: |
||||
|
rtsp_server.update_frame(frame) # Send frame to RTSP server |
||||
|
|
||||
|
if __name__ == "__main__": |
||||
|
# Start RTSP server in a separate thread |
||||
|
threading.Thread(target=run_server, daemon=True).start() |
||||
|
|
||||
|
# Stream webcam frames |
||||
|
stream_webcam() |
Write
Preview
Loading…
Cancel
Save
Reference in new issue