commit 24c50947d2cc16cd07d285240b8c3acf0b43f8ee Author: protsenkovi Date: Wed Feb 28 21:43:04 2024 +0400 first commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e45979e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +FROM condaforge/mambaforge + +ENV TZ=Europe/Samara +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +ARG USER +ARG GROUP +ARG UID +ARG GID + +RUN groupadd --gid ${GID} ${GROUP} +RUN useradd --shell /bin/bash --uid ${UID} --gid ${GID} -G sudo --create-home ${USER} +RUN mkdir /wd +RUN chown ${USER}:${GROUP} /wd + +# SYSTEM CONFIGURATION +#RUN apt-get update && apt-get install ffmpeg libsm6 libxext6 -y + +USER ${USER} + +# USER CONFIGURATION +RUN pip install opencv-python-headless aiohttp aiortc + +ENV SHELL=/bin/bash +SHELL ["/bin/bash", "--login", "-i", "-c"] +WORKDIR /wd diff --git a/README.md b/README.md new file mode 100644 index 0000000..24a17c4 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Пример работы webrtc из python diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..e276c02 --- /dev/null +++ b/build.sh @@ -0,0 +1,8 @@ +#!/bin/bash +CURDIRNAME=${PWD##*/} + +docker build . -t ${USER}_${CURDIRNAME} \ + --build-arg USER=${USER} \ + --build-arg GROUP=${USER} \ + --build-arg UID=$(id -u ${USER}) \ + --build-arg GID=$(id -g ${USER}) diff --git a/certs/cert.pem b/certs/cert.pem new file mode 100644 index 0000000..fc4b5e9 --- /dev/null +++ b/certs/cert.pem @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFvTCCA6WgAwIBAgIUH5r7oChsDwr2GnJ4pNhBA9Z6S28wDQYJKoZIhvcNAQEL +BQAwbjELMAkGA1UEBhMCUlUxFTATBgNVBAgMDFNhbWFyYVJlZ2lvbjEPMA0GA1UE +BwwGU2FtYXJhMQ8wDQYDVQQKDAZNeUhvbWUxEjAQBgNVBAsMCVNhbWFyYURlcDES +MBAGA1UEAwwJbXlob21lLnJ1MB4XDTIzMDIyNjE1MTAyNVoXDTI0MDIyNjE1MTAy +NVowbjELMAkGA1UEBhMCUlUxFTATBgNVBAgMDFNhbWFyYVJlZ2lvbjEPMA0GA1UE +BwwGU2FtYXJhMQ8wDQYDVQQKDAZNeUhvbWUxEjAQBgNVBAsMCVNhbWFyYURlcDES +MBAGA1UEAwwJbXlob21lLnJ1MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC +AgEAw0CCeRE48FhUIuifEZ8o/akLpmYHqJRCcV6XLflXQjQ7uQn3tTplrW2Mgy4j +W+hqvjr4yAJQVL7TefNO+MPjEBMea+PEHDiesNZzP3QtT5b0AtQvU7VawNGALb6r +4M9gMgg2RD73069b93PfMFZulsVM+Yt9Tpdl3cGPeWyLkWrKoQ2dc+FwGEjgvcli +BHJvSLblVy4+BnaiuJsfSwlFBP8RD2/gjdpf/GUN/rmwO/RmYRuF/RZTSlUYrmID +sKczBmvP27/yFxKICR/BsNSBru8n56zrAU5BW4kCxOcxqnBnYaPfd+tGJyEIYHeH +9N8qcRoLh1qL5zGEFwRB0D3Ja49Ws1+qetII+/I/CEZ0Rf4t097ZDMNP4qKQCm2A +kwt0w23ao8L4nNAX4LhuW/3gwIV6sbwNxcR0UI4q2fsIyzd128RWs53PNugtD61x +VAYfrwHzpytRxmn9UzFGecvKUxQt618ISGSFXmEFvF9mZpx7RHqD8LMyp1tVRNXI +lVJ9QEferCd6UeHFGgkj3S8IGnsGmXO6zxcmoK3+fTvY7S5lFj3CC+2UdOdAlxEd +jlMj/8rMf6eHE4EYVGD46MDglxihdcAixeyrIy8MfvxkuNLSTdB7qehXeSwKM6pQ +PbbublvYOFli46c1JzMGKROKL7PnWS49Fdna290av8Bdnx0CAwEAAaNTMFEwHQYD +VR0OBBYEFDTjvQ1M7pinWct8Nrrd95Yuf5CFMB8GA1UdIwQYMBaAFDTjvQ1M7pin +Wct8Nrrd95Yuf5CFMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB +AJokgctOQmKdxVKPHT4ZqoOS1ISBuwRNF2WYpjSRAxfSojmmeFJtZp7BUHE+kbsk ++drnwAFLxo32BXbDBCUL79VUc6e8etH0JHtPxim4cBKR2TlulYF2NNVF10SMOK5A +9qvl73UfOK0AfXxXaGbZ82W3ZIjmjlnpPY1syLnAoa1iygP1RKAlY272ApJ8gmEv +QetoNaRN09IShX3GcHUNB7X59CuOn9cLe1DWEASu8rb8+yc4pLob8jg2rhkSZ2tu +gzk2O7TgrSVz+2Na0d/w7G3WupWjF3omxvc6J0aQDCAly5NnDwGql/91eedlQtI+ +Fd8SObkU83xxlKgi8TjC/pFoMM3SL78C+/vOoVRLgKFriCwXfeKmkabeGWUkdDXI +YV4Y8oWQILEwAUS13A5RXbZfjmXyyEYKHeDCfsonbRCxGDA70Rs6rpQFcGEh+fDk +gKb0j2+BknH7PHfzvntO9R7kzJaNzmFe/WDRq92A6VheznG33LBK9TaKGAFFpqdR +ryTOi7X/z9tsYMGdKxyuLV1v+dJRz9GDeEi98XlhM3IoEX9M1druOl94iaL6iZnK +SC6u9ybIeMpKqBVxqSfOrQIySzOie75fukAssNtGwbLgABl6qBUk4tjRie99VGEJ +Ug+O8W+0hud0ZEJ/cbsOGTaR9FWLTDJuvykkwOqvgObZ +-----END CERTIFICATE----- \ No newline at end of file diff --git a/certs/key.pem b/certs/key.pem new file mode 100644 index 0000000..7c6dbdd --- /dev/null +++ b/certs/key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDDQIJ5ETjwWFQi +6J8Rnyj9qQumZgeolEJxXpct+VdCNDu5Cfe1OmWtbYyDLiNb6Gq+OvjIAlBUvtN5 +8074w+MQEx5r48QcOJ6w1nM/dC1PlvQC1C9TtVrA0YAtvqvgz2AyCDZEPvfTr1v3 +c98wVm6WxUz5i31Ol2XdwY95bIuRasqhDZ1z4XAYSOC9yWIEcm9ItuVXLj4GdqK4 +mx9LCUUE/xEPb+CN2l/8ZQ3+ubA79GZhG4X9FlNKVRiuYgOwpzMGa8/bv/IXEogJ +H8Gw1IGu7yfnrOsBTkFbiQLE5zGqcGdho99360YnIQhgd4f03ypxGguHWovnMYQX +BEHQPclrj1azX6p60gj78j8IRnRF/i3T3tkMw0/iopAKbYCTC3TDbdqjwvic0Bfg +uG5b/eDAhXqxvA3FxHRQjirZ+wjLN3XbxFaznc826C0PrXFUBh+vAfOnK1HGaf1T +MUZ5y8pTFC3rXwhIZIVeYQW8X2ZmnHtEeoPwszKnW1VE1ciVUn1AR96sJ3pR4cUa +CSPdLwgaewaZc7rPFyagrf59O9jtLmUWPcIL7ZR050CXER2OUyP/ysx/p4cTgRhU +YPjowOCXGKF1wCLF7KsjLwx+/GS40tJN0Hup6Fd5LAozqlA9tu5uW9g4WWLjpzUn +MwYpE4ovs+dZLj0V2drb3Rq/wF2fHQIDAQABAoICAF9re1fnTshGonec74ARJFsy +7fqYCk4chowZChDxte8oz7cSY/gxXddvnifNuIIGB4SIFWTJnLCNwZPrkECKmsEU +Vt2rJgAUu5j4dMIh6LLotr9bKvXhv0RUHiw87oqjTgOld7/KdhsBrBQ2kHBzDQCp +04Bw5wAthR7mpVNV9K9QXU5v9O7Y0YtMsmiNrlU2B6SlnVcJxk4qVEObq6NkGFLk +xFWi+z7X5ejXCgvee34RZqc2aDjTXEkLZVBznhIK5CjTL3XZTFpyOAEdf+8HK4lu +XzYdj4vHzrEJOE06H0+INz3EdEWKuXIN9qYORGcrWPaFH/U6Dv0hecg10GQQjgoi +iD20VyuBWew01Zaf4EizFh8Cmb73Wju3Of+CA0IheZO6Wq+5463v/UpRUgzBgYyg +JZl3dO7cSrZZBMpyIXXa+HWao57wyRW20R0kS4IV7MFB/9ouQzfKpj6k/6hnhAmD +cVrfLW/IP2iJK0UFTwm0qNVkiWVF/gWpg7pnS5A2NylfbSANFox0d53qBro7NnXZ +lXimfbtePj7bLB27NRTO6Fbj8vfLZXXOK/FORnALeaWOQ0hEPgQh/vSKncRai8aW +pWgmUz7wqJrv17CQS27PybqrEfIgVNIiyNbLOhLLP8wclB5wD9ZLIjLnQjQPQBpq +uwqrEJ375m6Nfy04WyUBAoIBAQD83swuQLlHC30i1Qmi2qbZwprTlzYjPXS2G8F8 +61d22URKWzZbVxTmJsfXcc9A1AQuzIK7HBE7kDilU9YXMV3/aClYb+lMeYOcVxku +J7AAF98w4p2+XJAxik+5M4LMr7bxNIeq2ZIAxqQfmId4FQvFcLAS84JEfsBn9jN5 +ufopJpgdKtq49sgjpLGsG7f/OOCuzKU1G8O8lWoFvrx4yvoc+oYwoW8CiL8h2TS8 +fP1w2tf79hLKS3VlU/VtgkPiz4VrurYHSm+q2zIq0naRUjAQ7zJC8pbsu5j+V8SD +e8ilyLqlLpgCvfSs+AHcbq9VBe7gfmthO16sE7hFj2SpJzYRAoIBAQDFqycBKQu+ +7u+Fgyk2ztm4VLKguGj8A5HhaQJIEXmRgipJE9taHI+GnGKFgvm8Enk/a0DV7DAA +OuBYJDMvbJOEm6XteW7wPEtrf1na4d9aKH5k1RZ8OrPbCu8sQACQD8Ne1bjfgImE ++G5JEcf4Y/A/aOF6Z8fMlAdfvtMeLOtlpItGf4QbC8pdssndbSSQU9ZVGqK3XE3K +tlMBTKFnHMjNFTcJ8oLn7zafDi+zfYGN1YlSx4m9HV576guu7c/eCJ6OhymFBfNu +rDMx7Poge1BNDpoXfWURnTLkrlxykJCFN4wpP51tdEDVh/GxGHz2NeiPdSm7RJe2 +1rCW08AwoJxNAoIBAEhWhBqHMXl9c+LPBt6rpieNYDU/gKE8J3MhJYzS1kaNiNes +mDQxCS190pVzXVKyVC//GTblpJhhfZLuFMS1vqod5hYCjb7u1BOZZv6pI9QjABo/ ++dhHKojBhGT6s2RSsAb4fAcIDphiOvk+7SIRAzkML5J8TrvBdtFGwRsFdObov4M4 +izF2h3KK3rjZhR1h7ASVTn2O38PHCnlyQwBbMImxsUmgJN2YuPS56jgxmV77e+X8 +UwvD4mWGSyN0rG7p5sUWuJQFW0SJSCSv6HNSC6YMh1hXhY1bbTz1ZqYmdxPWBG7U +kW/XX3NLz1x73XR5KgjWoWlDTqXzTje3+ZfaO8ECggEBAI4/PpBtM1CxrZiM7MnO +TYuDGBDk5FgHUaG+6a9nM+7slvWD3qSYTQj33UZrHMClIq/qxPutPlXMCMolMth7 +8CTLxbqBWr5zBWtUeBs11H+TCHITjlzT+b41viw/2qfRUC0c7C+a1lvkU6ktrJQp +hyh6l1h4+qVUGYJjVpYuiS/aG4geF7lG4NhQ40f/VQKv8lvIETSWrjykvFMBDF2h +rzc2fycfZ+j0koAfu2AVf3fMJUh2474+NlJB+SpnZOFJnqC+z7g0shwAu86/1Lgv +RXhOe8FPsb3dPFSozUp8kiPr92dvqiCsOkPv0pQ5JSQhYzxpeiCTmgIvuWCw/WuB +CTkCggEAZrsI8NrW5fRf04IuNDXVOrXILbv5fmpciZhvpepdmfKv17pwzQeCEtx0 +YPR5RflxOCwt+nmTmWAviBUc4XC5U06j44DhEJXI3N9+wv0XU3MBSN328CKapB1s +BgNxWUroblpJWnbKBYiS2GHuFN0hrw1KFkBU1C58yUc+yADSmjCM3B84ZHL89jtB +V3c2hWZ2fmPXgRSySYdgjPo3Yx8azZNbSFV9f4gL2xkcMjDEAaySIEMIBpmhYLxO +oN1cdutV/E/FYQvLilNm1I2jWOalzOY3h9Y/m1Z03P88CqwE23Qeqpyimf58dgj3 +yOT1wNOhn28zKzKVpiJEtke7p8F2Dg== +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/demo.mp4 b/demo.mp4 new file mode 100644 index 0000000..dfea8c1 Binary files /dev/null and b/demo.mp4 differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..d3c4141 --- /dev/null +++ b/index.html @@ -0,0 +1,108 @@ + + + + + WebRTC demo + + + + +
+
+ +
+
+ diff --git a/main.py b/main.py new file mode 100644 index 0000000..00233e9 --- /dev/null +++ b/main.py @@ -0,0 +1,105 @@ +import time +from av import VideoFrame +from aiortc import VideoStreamTrack +import numpy as np +from aiohttp import web +import argparse +import asyncio +import ssl +import os +from aiortc import RTCPeerConnection, RTCSessionDescription +import cv2 +import json +import time + + +ROOT = "./" + +class WebRTCVideoSink(VideoStreamTrack): + def __init__(self, vr): + """ vr - opencv is videocapture. """ + super().__init__() # don't forget this! + self.counter = 0 + self.vr = vr + + async def recv(self): +# time.sleep(1/40) + ok, frame = self.vr.read() + pts, time_base = await self.next_timestamp() + if not ok: + return None + else: + frame = VideoFrame.from_ndarray(frame, format="bgr24") + frame.pts = pts + frame.time_base = time_base + self.counter += 1 + return frame + +pcs_rgb = dict() + +async def offer_video(request): + params = await request.json() + pc = RTCPeerConnection() + pcs_rgb[request.remote] = pc + reader = cv2.VideoCapture("demo.mp4") + video_track = WebRTCVideoSink(reader) + pc.addTrack(video_track) + offer = await pc.createOffer() + print("Offer:", offer) + await pc.setLocalDescription(offer) + return web.Response( + content_type="application/json", + text=json.dumps( + {"sdp": pc.localDescription.sdp, "type": pc.localDescription.type} + ), + ) + +async def answer_video(request): + params = await request.json() + answer = RTCSessionDescription(sdp=params["sdp"], type=params["type"]) + print("Answer:", answer) + pc = pcs_rgb.get(request.remote, None) + if not pc is None: + await pc.setRemoteDescription(answer) + return web.Response(text="Ok") + +async def index(request): + content = open(os.path.join(ROOT, "index.html"), "r").read() + return web.Response(content_type="text/html", text=content) + +async def on_shutdown(app): + # close peer connections + coros = [pc.close() for remote, pc in pcs_rgb.items()] + await asyncio.gather(*coros) + pcs_rgb.clear() + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="WebRTC audio / video / data-channels demo" + ) + parser.add_argument("--cert-file", help="SSL certificate file (for HTTPS)", default="./certs/cert.pem") + parser.add_argument("--key-file", help="SSL key file (for HTTPS)", default="./certs/key.pem") + parser.add_argument( + "--host", default="0.0.0.0", help="Host for HTTP server (default: 0.0.0.0)" + ) + parser.add_argument( + "--port", type=int, default=24000, help="Port for HTTP server (default: 8081)" + ) + parser.add_argument("--verbose", "-v", action="count") + args = parser.parse_args() + + # run event loop + loop = asyncio.get_event_loop() + + app = web.Application() + app.on_shutdown.append(on_shutdown) + app.router.add_get("/", index) + app.router.add_post("/offer_video", offer_video) + app.router.add_post("/answer_video", answer_video) + + ssl_context = ssl.SSLContext() + ssl_context.load_cert_chain(args.cert_file, args.key_file) + + web.run_app( + app, access_log=None, host=args.host, port=args.port, ssl_context=ssl_context + ) diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..5b27a78 --- /dev/null +++ b/start.sh @@ -0,0 +1,11 @@ +#!/bin/bash +CURDIRNAME=${PWD##*/} + +docker run \ + -it \ + --rm \ + -v $(pwd):/wd \ + --net host \ + --name ${USER}_${CURDIRNAME} \ + ${USER}_${CURDIRNAME} \ + python main.py