{"id":1428,"date":"2024-10-16T15:29:28","date_gmt":"2024-10-16T07:29:28","guid":{"rendered":"https:\/\/www.fanyamin.com\/wordpress\/?p=1428"},"modified":"2024-10-16T18:10:40","modified_gmt":"2024-10-16T10:10:40","slug":"push-camera-to-web","status":"publish","type":"post","link":"https:\/\/www.fanyamin.com\/wordpress\/?p=1428","title":{"rendered":"push camera to web"},"content":{"rendered":"<p>To exchange data over a datachannel using libdatachannel in C++, we\u2019ll need to set up a signaling server (in Python) to exchange the necessary WebRTC connection information (SDP and ICE candidates), and a STUN\/TURN server using coturn for NAT traversal.<\/p>\n<h2>components:<\/h2>\n<ol>\n<li>C++ Client and Server using libdatachannel<\/li>\n<\/ol>\n<ul>\n<li>We\u2019ll use libdatachannel to establish WebRTC peer-to-peer connections.<\/li>\n<li>Data will be exchanged via the WebRTC DataChannel.<\/li>\n<\/ul>\n<ol start=\"2\">\n<li>Python Signaling Server<\/li>\n<\/ol>\n<ul>\n<li>This server will handle the signaling process, exchanging WebRTC SDP offers\/answers and ICE candidates between the peers.<\/li>\n<li>For simplicity, we can use WebSockets for signaling.<\/li>\n<\/ul>\n<ol start=\"3\">\n<li>STUN\/TURN Server using coturn<\/li>\n<\/ol>\n<ul>\n<li>We\u2019ll set up coturn as a STUN\/TURN server to assist with NAT traversal.<\/li>\n<\/ul>\n<h2>Implementation steps<\/h2>\n<h3>Step 1: STUN\/TURN Server (coturn)<\/h3>\n<p>Install coturn using the package manager and configure it to act as a STUN\/TURN server.<\/p>\n<pre><code class=\"language-shell\">sudo apt-get install coturn<\/code><\/pre>\n<p>Configure \/etc\/turnserver.conf with:<\/p>\n<pre><code>listening-port=3478\nfingerprint\nlt-cred-mech\nuse-auth-secret\nstatic-auth-secret=your-secret-key\nrealm=www.fanyamin.com\nserver-name=www.fanyamin.com<\/code><\/pre>\n<p>Start coturn:<\/p>\n<pre><code class=\"language-shell\">sudo turnserver -v<\/code><\/pre>\n<h3>Step 2: Python Signaling Server<\/h3>\n<ul>\n<li>install library<\/li>\n<\/ul>\n<pre><code class=\"language-shell\">pip install websockets\n<\/code><\/pre>\n<p>Create a simple WebSocket signaling server in Python using websockets:<\/p>\n<pre><code class=\"language-python\">import asyncio\nimport websockets\nimport json\n\nclients = {}\n\nasync def signaling(websocket, path):\n    async for message in websocket:\n        data = json.loads(message)\n        peer_id = data[&#039;peer_id&#039;]\n        if data[&#039;type&#039;] == &#039;offer&#039; or data[&#039;type&#039;] == &#039;answer&#039;:\n            recipient = clients.get(data[&#039;to&#039;], None)\n            if recipient:\n                await recipient.send(json.dumps(data))\n        elif data[&#039;type&#039;] == &#039;register&#039;:\n            clients[peer_id] = websocket\n\nasync def main():\n    async with websockets.serve(signaling, &quot;0.0.0.0&quot;, 8765):\n        await asyncio.Future()\n\nasyncio.run(main())<\/code><\/pre>\n<p>This WebSocket server will exchange SDP and ICE candidates between peers.<\/p>\n<h3>Step 3: C++ Client and Server using libdatachannel<\/h3>\n<p>You\u2019ll need to install libdatachannel (you can use Conan if you prefer).<\/p>\n<pre><code class=\"language-cpp\">#include &lt;rtc\/rtc.hpp&gt;\n#include &lt;iostream&gt;\n\nvoid setup_peer(rtc::PeerConnection &amp;pc, const std::string &amp;role) {\n    \/\/ DataChannel for sending messages\n    auto dc = pc.createDataChannel(&quot;data&quot;);\n\n    dc-&gt;onOpen([role]() {\n        std::cout &lt;&lt; role &lt;&lt; &quot; DataChannel open!&quot; &lt;&lt; std::endl;\n    });\n\n    dc-&gt;onMessage([](std::variant&lt;rtc::binary, std::string&gt; message) {\n        if (std::holds_alternative&lt;std::string&gt;(message)) {\n            std::cout &lt;&lt; &quot;Received message: &quot; &lt;&lt; std::get&lt;std::string&gt;(message) &lt;&lt; std::endl;\n        }\n    });\n}\n\nint main() {\n    \/\/ Configure STUN server\n    rtc::Configuration config;\n    config.iceServers.emplace_back(&quot;stun:your-stun-server.com&quot;);\n\n    rtc::PeerConnection pc(config);\n\n    setup_peer(pc, &quot;Client&quot;);\n\n    \/\/ Exchange SDP via signaling server\n    pc.onLocalDescription([](rtc::Description desc) {\n        std::cout &lt;&lt; &quot;Local SDP: &quot; &lt;&lt; std::string(desc) &lt;&lt; std::endl;\n        \/\/ Send SDP to signaling server (use WebSocket)\n    });\n\n    pc.onGatheringStateChange([](rtc::PeerConnection::GatheringState state) {\n        if (state == rtc::PeerConnection::GatheringState::Complete) {\n            std::cout &lt;&lt; &quot;ICE gathering complete.&quot; &lt;&lt; std::endl;\n        }\n    });\n\n    \/\/ Connect to signaling server and exchange SDPs and ICE candidates\n    \/\/ Implement the signaling logic using WebSockets.\n\n    return 0;\n}<\/code><\/pre>\n<p>Workflow<\/p>\n<ol>\n<li>Client and Server both connect to the Python signaling server via WebSockets.<\/li>\n<li>Client sends an SDP offer through the signaling server.<\/li>\n<li>Server responds with an SDP answer.<\/li>\n<li>Both peers exchange ICE candidates until a connection is established.<\/li>\n<li>Data is exchanged over the DataChannel.<\/li>\n<\/ol>\n<h2>Example<\/h2>\n<ol>\n<li>Use gstreamer to push video <\/li>\n<\/ol>\n<pre><code>$ gst-launch-1.0 v4l2src device=\/dev\/video0 \\\n! video\/x-raw,width=640,height=480 \\\n! videoconvert ! queue \\\n! x264enc tune=zerolatency bitrate=1000 key-int-max=30 \\\n! video\/x-h264, profile=constrained-baseline \\\n! rtph264pay pt=96 mtu=1200 \\\n! udpsink host=127.0.0.1 port=6000<\/code><\/pre>\n<ul>\n<li>v4l2src \u4ece\u6444\u50cf\u5934\u6355\u83b7\u89c6\u9891\u6d41, \u5206\u8fa8\u7387\u4e3a 640*480<\/li>\n<li>x264enc \u5c06\u89c6\u9891\u6d41\u7f16\u7801\u4e3a h264 codec<\/li>\n<li>rtph264pay \u5c06\u89c6\u9891\u6d41\u6253\u5305\u4e3a RTP packet<\/li>\n<li>udpsink \u5c06 rtp packet \u4ee5 udp \u5305\u53d1\u9001\u51fa\u53bb<\/li>\n<\/ul>\n<ol start=\"2\">\n<li>Use cpp\/web client to receive the video<\/li>\n<\/ol>\n<pre><code class=\"language-html\">\n&lt;!DOCTYPE html&gt;\n&lt;html lang=&quot;en&quot;&gt;\n&lt;head&gt;\n    &lt;meta charset=&quot;UTF-8&quot;&gt;\n    &lt;title&gt;libdatachannel media sender example&lt;\/title&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n\n&lt;p&gt;Please enter the offer provided to you by the sender application: &lt;\/p&gt;\n&lt;textarea cols=&quot;80&quot; rows=&quot;25&quot;&gt;&lt;\/textarea&gt;\n&lt;button&gt;Submit&lt;\/button&gt;\n\n&lt;video id=&quot;video-element&quot; muted&gt;&lt;\/video&gt;\n\n&lt;script&gt;\n    document.querySelector(&#039;button&#039;).addEventListener(&#039;click&#039;,  async () =&gt; {\n        const offer = JSON.parse(document.querySelector(&#039;textarea&#039;).value);\n        const pc = new RTCPeerConnection({\n            \/\/ Recommended for libdatachannel\n            bundlePolicy: &#039;max-bundle&#039;,\n        });\n\n        pc.onicegatheringstatechange = (state) =&gt; {\n            if (pc.iceGatheringState === &#039;complete&#039;) {\n                \/\/ We only want to provide an answer once all of our candidates have been added to the SDP.\n                const answer = pc.localDescription;\n                document.querySelector(&#039;textarea&#039;).value = JSON.stringify({&quot;type&quot;: answer.type, sdp: answer.sdp});\n                document.querySelector(&#039;p&#039;).value = &#039;Please paste the answer in the sender application.&#039;;\n                alert(&#039;Please paste the answer in the sender application.&#039;);\n            }\n        }\n\n        pc.ontrack = (evt) =&gt; {\n            const videoElement = document.getElementById(&#039;video-element&#039;);\n            videoElement.srcObject = evt.streams[0];\n            videoElement.play();\n        };\n\n        await pc.setRemoteDescription(offer);\n\n        const answer = await pc.createAnswer();\n        await pc.setLocalDescription(answer);\n    })\n&lt;\/script&gt;\n\n&lt;\/body&gt;\n&lt;\/html&gt;<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>To exchange data over a datachannel using libdatachannel in C++, we\u2019ll need to set up a signaling server (in Python) to exchange the necessary WebRTC connection information (SDP and ICE candidates), and a STUN\/TURN server using coturn for NAT traversal. components: C++ Client and Server using libdatachannel We\u2019ll use libdatachannel to establish WebRTC peer-to-peer connections. [&hellip;] <a class=\"read-more\" href=\"https:\/\/www.fanyamin.com\/wordpress\/?p=1428\" title=\"Permanent Link to: push camera to web\">&rarr;Read&nbsp;more<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-1428","post","type-post","status-publish","format-standard","hentry","category-5"],"_links":{"self":[{"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/1428"}],"collection":[{"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1428"}],"version-history":[{"count":4,"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/1428\/revisions"}],"predecessor-version":[{"id":1435,"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/1428\/revisions\/1435"}],"wp:attachment":[{"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1428"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1428"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.fanyamin.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1428"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}