Autonomy Software C++ 24.5.1
Welcome to the Autonomy Software repository of the Mars Rover Design Team (MRDT) at Missouri University of Science and Technology (Missouri S&T)! API reference contains the source code and other resources for the development of the autonomy software for our Mars rover. The Autonomy Software project aims to compete in the University Rover Challenge (URC) by demonstrating advanced autonomous capabilities and robust navigation algorithms.
Loading...
Searching...
No Matches
WebRTC Class Reference

This class is used to establish a connection with the RoveSoSimulator and retrieve video streams from it. More...

#include <WebRTC.h>

Collaboration diagram for WebRTC:

Public Member Functions

 WebRTC (const std::string &szSignallingServerURL, const std::string &szStreamerID)
 Construct a new Web RTC::WebRTC object.
 
 ~WebRTC ()
 Destroy the Web RTC::WebRTC object.
 
void CloseConnection ()
 Close the WebRTC connection.
 
void SetOnFrameReceivedCallback (std::function< void(cv::Mat &)> fnOnFrameReceivedCallback, const AVPixelFormat eOutputPixelFormat=AV_PIX_FMT_BGR24)
 Set the callback function for when a new frame is received.
 
bool GetIsConnected () const
 Get the connection status of the WebRTC object.
 

Private Member Functions

bool ConnectToSignallingServer (const std::string &szSignallingServerURL)
 Connected to the Unreal Engine 5 hosted Signalling Server for WebRTC negotiation.
 
bool InitializeH264Decoder ()
 Initialize the H264 decoder. Creates the AVCodecContext, AVFrame, and AVPacket.
 
bool DecodeH264BytesToCVMat (const std::vector< uint8_t > &vH264EncodedBytes, cv::Mat &cvDecodedFrame, const AVPixelFormat eOutputPixelFormat)
 Decodes H264 encoded bytes to a cv::Mat using FFmpeg.
 
bool RequestKeyFrame ()
 Requests a key frame from the given video track. This is useful for when the video track is out of sync or has lost frames.
 
bool SendCommandToStreamer (const std::string &szCommand)
 This method sends a command to the streamer via the data channel. The command is a JSON string that is sent as a binary message. The PixelStreaming plugin handles the command very weirdly, so we have to sort of encode the command in a specific way. This handles that encoding.
 

Private Attributes

std::string m_szSignallingServerURL
 
std::string m_szStreamerID
 
std::shared_ptr< rtc::WebSocket > m_pWebSocket
 
std::shared_ptr< rtc::PeerConnection > m_pPeerConnection
 
std::shared_ptr< rtc::DataChannel > m_pDataChannel
 
std::shared_ptr< rtc::Track > m_pVideoTrack1
 
std::shared_ptr< rtc::H264RtpDepacketizer > m_pTrack1H264DepacketizationHandler
 
std::shared_ptr< rtc::RtcpReceivingSession > m_pTrack1RtcpReceivingSession
 
std::chrono::system_clock::time_point m_tmLastKeyFrameRequestTime
 
AVCodecContext * m_pAVCodecContext
 
AVFrame * m_pFrame
 
AVPacket * m_pPacket
 
SwsContext * m_pSWSContext
 
AVPixelFormat m_eOutputPixelFormat
 
std::shared_mutex m_muDecoderMutex
 
cv::Mat m_cvFrame
 
std::function< void(cv::Mat &)> m_fnOnFrameReceivedCallback
 

Detailed Description

This class is used to establish a connection with the RoveSoSimulator and retrieve video streams from it.

Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2024-11-30

Constructor & Destructor Documentation

◆ WebRTC()

WebRTC::WebRTC ( const std::string &  szSignallingServerURL,
const std::string &  szStreamerID 
)

Construct a new Web RTC::WebRTC object.

Parameters
szSignallingServerURL- The URL of the signalling server.
szStreamerID- The ID of the streamer.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2024-12-02
30{
31 // Submit logger message.
32 LOG_INFO(logging::g_qSharedLogger, "WebRTC camera constructing instance. Target URL: {}, StreamerID: {}", szSignallingServerURL, szStreamerID);
33
34 // Set member variables.
35 m_szSignallingServerURL = szSignallingServerURL;
36 m_szStreamerID = szStreamerID;
37 m_tmLastKeyFrameRequestTime = std::chrono::system_clock::now();
38
39 // Setup the FFMPEG H264 decoder.
40 if (this->InitializeH264Decoder())
41 {
42 LOG_INFO(logging::g_qSharedLogger, "WebRTC camera {} H264 Decoder initialized successfully.", m_szStreamerID);
43 }
44 else
45 {
46 LOG_ERROR(logging::g_qSharedLogger, "WebRTC camera {} Failed to initialize H264 Decoder!", m_szStreamerID);
47 }
48
49 // Enable logging from the WebRTC LibDataChannel library for debugging.
50 // rtc::InitLogger(rtc::LogLevel::Verbose);
51
52 // Construct the WebRTC peer connection and data channel for receiving data from the simulator.
53 rtc::WebSocket::Configuration rtcWebSocketConfig;
54 rtc::Configuration rtcPeerConnectionConfig;
55 rtcPeerConnectionConfig.forceMediaTransport = true;
56 rtcPeerConnectionConfig.maxMessageSize = 100000000;
57 m_pWebSocket = std::make_shared<rtc::WebSocket>(rtcWebSocketConfig);
58 m_pPeerConnection = std::make_shared<rtc::PeerConnection>(rtcPeerConnectionConfig);
59 m_pDataChannel = m_pPeerConnection->createDataChannel("webrtc-datachannel");
60
61 LOG_INFO(logging::g_qSharedLogger, "WebRTC camera {} PeerConnection and DataChannel objects created.", m_szStreamerID);
62
63 // Attempt to connect to the signalling server.
64 this->ConnectToSignallingServer(szSignallingServerURL);
65}
bool InitializeH264Decoder()
Initialize the H264 decoder. Creates the AVCodecContext, AVFrame, and AVPacket.
Definition WebRTC.cpp:684
bool ConnectToSignallingServer(const std::string &szSignallingServerURL)
Connected to the Unreal Engine 5 hosted Signalling Server for WebRTC negotiation.
Definition WebRTC.cpp:195
Here is the call graph for this function:

◆ ~WebRTC()

WebRTC::~WebRTC ( )

Destroy the Web RTC::WebRTC object.

Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2024-12-02
75{
76 LOG_INFO(logging::g_qSharedLogger, "WebRTC camera {} destructor called. Cleaning up...", m_szStreamerID);
77 this->CloseConnection();
78
79 // Free the codec context.
80 if (m_pSWSContext)
81 {
82 sws_freeContext(m_pSWSContext);
83 }
84 if (m_pFrame)
85 {
86 av_frame_free(&m_pFrame);
87 }
88 if (m_pPacket)
89 {
90 av_packet_free(&m_pPacket);
91 }
92 if (m_pAVCodecContext)
93 {
94 avcodec_free_context(&m_pAVCodecContext);
95 }
96
97 // Set dangling pointers to nullptr.
98 m_pAVCodecContext = nullptr;
99 m_pFrame = nullptr;
100 m_pPacket = nullptr;
101 m_pSWSContext = nullptr;
102 LOG_INFO(logging::g_qSharedLogger, "WebRTC camera {} Cleanup complete.", m_szStreamerID);
103}
void CloseConnection()
Close the WebRTC connection.
Definition WebRTC.cpp:112
Here is the call graph for this function:

Member Function Documentation

◆ CloseConnection()

void WebRTC::CloseConnection ( )

Close the WebRTC connection.

Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-05-03
113{
114 LOG_INFO(logging::g_qSharedLogger, "WebRTC camera {} closing connections...", m_szStreamerID);
115 // Close the WebRTC connections.
116 if (m_pVideoTrack1)
117 {
118 m_pVideoTrack1->resetCallbacks();
119 m_pVideoTrack1->close();
120 }
121 if (m_pDataChannel)
122 {
123 m_pDataChannel->resetCallbacks();
124 m_pDataChannel->close();
125 }
126 if (m_pPeerConnection)
127 {
128 m_pPeerConnection->resetCallbacks();
129 m_pPeerConnection->close();
130 }
131 if (m_pWebSocket)
132 {
133 m_pWebSocket->resetCallbacks();
134 m_pWebSocket->close();
135 }
136
137 // Wait for all connections to close.
138 while ((m_pVideoTrack1 && !m_pVideoTrack1->isClosed()) || (m_pDataChannel && !m_pDataChannel->isClosed()) || (m_pWebSocket && !m_pWebSocket->isClosed()))
139 {
140 std::this_thread::sleep_for(std::chrono::milliseconds(100));
141 }
142 LOG_INFO(logging::g_qSharedLogger, "WebRTC camera {} Connections closed.", m_szStreamerID);
143}
Here is the caller graph for this function:

◆ SetOnFrameReceivedCallback()

void WebRTC::SetOnFrameReceivedCallback ( std::function< void(cv::Mat &)>  fnOnFrameReceivedCallback,
const AVPixelFormat  eOutputPixelFormat = AV_PIX_FMT_BGR24 
)

Set the callback function for when a new frame is received.

Parameters
fnOnFrameReceivedCallback- The callback function to set.
eOutputPixelFormat- The output pixel format to use.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2024-12-02
155{
156 // Set the callback function.
157 m_fnOnFrameReceivedCallback = fnOnFrameReceivedCallback;
158 // Set the output pixel format.
159 m_eOutputPixelFormat = eOutputPixelFormat;
160 LOG_INFO(logging::g_qSharedLogger, "WebRTC camera {} frame received callback set.", m_szStreamerID);
161}

◆ GetIsConnected()

bool WebRTC::GetIsConnected ( ) const

Get the connection status of the WebRTC object.

Returns
true - The WebRTC object is connected.
false - The WebRTC object is not connected.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2024-12-26
173{
174 // Check if the datachannel shared pointer is valid.
175 if (m_pWebSocket != nullptr)
176 {
177 // Check if the datachannel is open.
178 return m_pWebSocket->isOpen();
179 }
180
181 // Return false if the datachannel is not valid.
182 return false;
183}
Here is the caller graph for this function:

◆ ConnectToSignallingServer()

bool WebRTC::ConnectToSignallingServer ( const std::string &  szSignallingServerURL)
private

Connected to the Unreal Engine 5 hosted Signalling Server for WebRTC negotiation.

Parameters
szSignallingServerURL- The full URL of the signalling server. Should be in the format of "ws://<IP>:<PORT>"
Returns
true - Successfully connected to the signalling server.
false - Failed to connect to the signalling server.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2024-11-11
196{
197 LOG_INFO(logging::g_qSharedLogger, "WebRTC camera {} opening WebSocket to {}...", m_szStreamerID, szSignallingServerURL);
198 // Connect to the signalling server via a websocket to handle WebRTC negotiation and signalling.
199 m_pWebSocket->open(szSignallingServerURL);
200
202 // Set some callbacks on important events for the websocket.
204
205 // WebSocket has been opened.
206 m_pWebSocket->onOpen(
207 [this]()
208 {
209 // Submit logger message.
210 LOG_DEBUG(logging::g_qSharedLogger, "WebRTC camera {} WebSocket OPEN. Connected to {}. Sending 'listStreamers'...", m_szStreamerID, m_szSignallingServerURL);
211
212 // Request the streamer list from the server. This also kicks off the negotiation process.
213 nlohmann::json jsnStreamList;
214 jsnStreamList["type"] = "listStreamers";
215 m_pWebSocket->send(jsnStreamList.dump());
216 });
217
218 // WebSocket has been closed.
219 m_pWebSocket->onClosed(
220 [this]()
221 {
222 // Submit logger message.
223 LOG_INFO(logging::g_qSharedLogger, "WebRTC camera {} WebSocket CLOSED. Disconnected from signalling server.", m_szStreamerID);
224 });
225
226 // Handling signalling server messages. (offer/answer/ICE candidate)
227 m_pWebSocket->onMessage(
228 [this](std::variant<rtc::binary, rtc::string> rtcMessage)
229 {
230 try
231 {
232 // Create instance variables.
233 nlohmann::json jsnMessage;
234
235 // Check if data is of type rtc::string.
236 if (std::holds_alternative<rtc::string>(rtcMessage))
237 {
238 // Retrieve the string message
239 std::string szMessage = std::get<rtc::string>(rtcMessage);
240
241 // Parse the JSON message from the signaling server.
242 jsnMessage = nlohmann::json::parse(szMessage);
243 LOG_DEBUG(logging::g_qSharedLogger, "WebRTC camera {} WS Message (String): {}", m_szStreamerID, szMessage);
244 }
245 else if (std::holds_alternative<rtc::binary>(rtcMessage))
246 {
247 // Retrieve the binary message.
248 rtc::binary rtcBinaryData = std::get<rtc::binary>(rtcMessage);
249 // Print length of binary data.
250 LOG_DEBUG(logging::g_qSharedLogger, "WebRTC camera {} WS Message (Binary) of length: {}", m_szStreamerID, rtcBinaryData.size());
251
252 // Convert the binary data to a string.
253 std::string szBinaryDataStr(reinterpret_cast<const char*>(rtcBinaryData.data()), rtcBinaryData.size());
254 // Print the binary data as a string.
255 LOG_DEBUG(logging::g_qSharedLogger, "WebRTC camera {} WS Binary Content: {}", m_szStreamerID, szBinaryDataStr);
256 // Parse the binary data as JSON.
257 jsnMessage = nlohmann::json::parse(szBinaryDataStr);
258 }
259 else
260 {
261 LOG_ERROR(logging::g_qSharedLogger, "WebRTC camera {} WS Received unknown message type.", m_szStreamerID);
262 }
263
264 // Check if the message contains a type.
265 if (jsnMessage.contains("type"))
266 {
267 std::string szType = jsnMessage["type"];
268 // If the message from the server is a config message, do nothing.
269 if (szType == "config")
270 {
271 // Submit logger message.
272 LOG_DEBUG(logging::g_qSharedLogger, "WebRTC camera {} Received 'config': {}", m_szStreamerID, jsnMessage.dump());
273 }
274 // If the message from the server is an offer, set the remote description offer.
275 else if (szType == "offer")
276 {
277 // Get the SDP offer and set it as the remote description.
278 std::string sdp = jsnMessage["sdp"];
279 m_pPeerConnection->setRemoteDescription(rtc::Description(sdp, "offer"));
280 LOG_DEBUG(logging::g_qSharedLogger,
281 "WebRTC camera {} Received 'offer'. SDP Length: {}. Setting Remote Description...",
282 m_szStreamerID,
283 sdp.length());
284
285 // Trigger answer creation.
286 m_pPeerConnection->setLocalDescription();
287 LOG_DEBUG(logging::g_qSharedLogger, "WebRTC camera {} Triggered setLocalDescription() to generate answer.", m_szStreamerID);
288 }
289 // If the message from the server is an answer, set the remote description answer.
290 else if (szType == "answer")
291 {
292 // Get the SDP answer and set it as the remote description.
293 std::string sdp = jsnMessage["sdp"];
294 m_pPeerConnection->setRemoteDescription(rtc::Description(sdp, "answer"));
295 LOG_DEBUG(logging::g_qSharedLogger, "WebRTC camera {} Received 'answer'. Setting Remote Description.", m_szStreamerID);
296 }
297 // If the message from the server is advertising an ICE candidate, add it to the peer connection.
298 else if (szType == "iceCandidate")
299 {
300 // Handle ICE candidate
301 nlohmann::json jsnCandidate = jsnMessage["candidate"];
302 std::string szCandidateStr = jsnCandidate["candidate"];
303
304 rtc::Candidate rtcCandidate = rtc::Candidate(szCandidateStr);
305 m_pPeerConnection->addRemoteCandidate(rtcCandidate);
306 LOG_DEBUG(logging::g_qSharedLogger, "WebRTC camera {} Received 'iceCandidate'. Added: {}", m_szStreamerID, szCandidateStr);
307 }
308 else if (szType == "streamerList")
309 {
310 // Print the streamer list.
311 LOG_DEBUG(logging::g_qSharedLogger, "WebRTC camera {} Received 'streamerList': {}", m_szStreamerID, jsnMessage.dump());
312
313 // Check that the streamer ID given by the user is in the streamer list.
314 if (jsnMessage.contains("ids"))
315 {
316 std::vector<std::string> streamerList = jsnMessage["ids"].get<std::vector<std::string>>();
317 if (std::find(streamerList.begin(), streamerList.end(), m_szStreamerID) != streamerList.end())
318 {
319 // Send what stream we want to the server.
320 nlohmann::json jsnStream;
321 jsnStream["type"] = "subscribe";
322 jsnStream["streamerId"] = m_szStreamerID;
323 m_pWebSocket->send(jsnStream.dump());
324 // Submit logger message.
325 LOG_DEBUG(logging::g_qSharedLogger, "WebRTC camera {} Streamer ID {} found! Sending 'subscribe'...", m_szStreamerID, m_szStreamerID);
326 }
327 else
328 {
329 LOG_ERROR(logging::g_qSharedLogger, "WebRTC camera {} Streamer ID {} NOT found in streamer list!", m_szStreamerID, m_szStreamerID);
330 }
331 }
332 else
333 {
334 LOG_ERROR(logging::g_qSharedLogger, "WebRTC camera {} Streamer list does not contain 'ids' field!", m_szStreamerID);
335 }
336 }
337 else
338 {
339 LOG_ERROR(logging::g_qSharedLogger, "WebRTC camera {} Unknown message type received: {}", m_szStreamerID, szType);
340 }
341 }
342 }
343 catch (const std::exception& e)
344 {
345 // Submit logger message.
346 LOG_ERROR(logging::g_qSharedLogger, "WebRTC camera {} Exception during Negotiation: {}", m_szStreamerID, e.what());
347 }
348 });
349
350 m_pWebSocket->onError(
351 [this](const std::string& szError)
352 {
353 // Submit logger message.
354 LOG_ERROR(logging::g_qSharedLogger, "WebRTC camera {} WebSocket Error: {}", m_szStreamerID, szError);
355 });
356
358 // Set some callbacks on important events for the peer connection.
360
361 m_pPeerConnection->onLocalDescription(
362 [this](rtc::Description rtcDescription)
363 {
364 LOG_DEBUG(logging::g_qSharedLogger, "WebRTC camera {} Generated Local Description (Type: {}).", m_szStreamerID, rtcDescription.typeString());
365
366 // Check the type of the description.
367 if (rtcDescription.typeString() == "offer")
368 {
369 LOG_DEBUG(logging::g_qSharedLogger, "WebRTC camera {} Ignoring 'offer' type in onLocalDescription.", m_szStreamerID);
370 return;
371 }
372
373 // First lets send some preconfig stuff.
374 nlohmann::json jsnConfigMessage;
375 jsnConfigMessage["type"] = "layerPreference";
376 jsnConfigMessage["spatialLayer"] = 0;
377 jsnConfigMessage["temporalLayer"] = 0;
378 jsnConfigMessage["playerId"] = "";
379 m_pWebSocket->send(jsnConfigMessage.dump());
380 LOG_DEBUG(logging::g_qSharedLogger, "WebRTC Sent 'layerPreference'.");
381
382 // Send the local description to the signalling server
383 nlohmann::json jsnMessage;
384 jsnMessage["type"] = rtcDescription.typeString();
385 // This next bit is specific to the Unreal Engine 5 Signalling Server, we must append the min/max bitrate to the message.
386 jsnMessage["minBitrateBps"] = 0;
387 jsnMessage["maxBitrateBps"] = 0;
388 // Here's our actual SDP.
389 std::string szSDP = rtcDescription.generateSdp();
390 // Munger the SDP to add the bitrate.
391 std::string szMungedSDP =
392 std::regex_replace(szSDP, std::regex("(a=fmtp:\\d+ level-asymmetry-allowed=.*)\r\n"), "$1;x-google-start-bitrate=10000;x-google-max-bitrate=100000\r\n");
393 jsnMessage["sdp"] = szMungedSDP;
394 // Send the message.
395 m_pWebSocket->send(jsnMessage.dump());
396
397 // Submit logger message.
398 LOG_INFO(logging::g_qSharedLogger, "WebRTC camera {} Sent Local Description to Server (Munged SDP).", m_szStreamerID);
399 });
400
401 m_pPeerConnection->onTrack(
402 [this](std::shared_ptr<rtc::Track> rtcTrack)
403 {
404 // Submit logger message.
405 rtc::Description::Media rtcMediaDescription = rtcTrack->description();
406 // Get some information about the track.
407 std::string szMediaType = rtcMediaDescription.type();
408
409 LOG_DEBUG(logging::g_qSharedLogger, "WebRTC camera {} onTrack triggered. Media Type: {}", m_szStreamerID, szMediaType);
410
411 // Check if the track is a video track.
412 if (szMediaType != "video")
413 {
414 LOG_DEBUG(logging::g_qSharedLogger, "WebRTC camera {} Ignoring non-video track.", m_szStreamerID);
415 return;
416 }
417
418 // Set member variable to the video track.
419 m_pVideoTrack1 = rtcTrack;
420
421 // Create a H264 depacketization handler and rtcp receiving session.
422 m_pTrack1H264DepacketizationHandler = std::make_shared<rtc::H264RtpDepacketizer>(rtc::NalUnit::Separator::LongStartSequence);
423 m_pTrack1RtcpReceivingSession = std::make_shared<rtc::RtcpReceivingSession>();
424 m_pTrack1H264DepacketizationHandler->addToChain(m_pTrack1RtcpReceivingSession);
425 m_pVideoTrack1->setMediaHandler(m_pTrack1H264DepacketizationHandler);
426
427 LOG_INFO(logging::g_qSharedLogger, "WebRTC camera {} Video Track Handler Configured. Waiting for frames...", m_szStreamerID);
428
429 // Set the onMessage callback for the video track.
430 m_pVideoTrack1->onFrame(
431 [this](rtc::binary rtcBinaryMessage, rtc::FrameInfo rtcFrameInfo)
432 {
433 // CRITICAL FIX: Ignore empty packets to prevent flushing the decoder.
434 if (rtcBinaryMessage.empty())
435 {
436 return;
437 }
438
439 // Prepare buffer for H.264 bytes.
440 std::vector<uint8_t> vH264EncodedBytes;
441 // Reserve space + FFmpeg Padding safety buffer.
442 vH264EncodedBytes.reserve(rtcBinaryMessage.size() + 16 + AV_INPUT_BUFFER_PADDING_SIZE);
443
444 if (rtcFrameInfo.payloadType == 96)
445 {
446 // Standard H264 Packet (Already Depacketized by LibDataChannel)
447 // It usually includes the Start Code (00 00 00 01) because of the Handler config.
448
449 const uint8_t* pData = reinterpret_cast<const uint8_t*>(rtcBinaryMessage.data());
450 vH264EncodedBytes.insert(vH264EncodedBytes.end(), pData, pData + rtcBinaryMessage.size());
451 }
452 else if (rtcFrameInfo.payloadType == 97)
453 {
454 // RTX (Retransmission) Packet
455 // Structure: [OSN (2 bytes)] [Original RTP Payload]
456 // This packet bypassed the Depacketizer, so it is "Raw".
457 // To decode it, we must strip the OSN and manually add the Start Code.
458
459 if (rtcBinaryMessage.size() <= 2)
460 return; // Too small to contain data.
461
462 // Start code. (Long Start Sequence: 00 00 00 01)
463 vH264EncodedBytes.push_back(0);
464 vH264EncodedBytes.push_back(0);
465 vH264EncodedBytes.push_back(0);
466 vH264EncodedBytes.push_back(1);
467
468 // Original payload. (Skip first 2 bytes of RTX header)
469 const uint8_t* pData = reinterpret_cast<const uint8_t*>(rtcBinaryMessage.data());
470 vH264EncodedBytes.insert(vH264EncodedBytes.end(), pData + 2, pData + rtcBinaryMessage.size());
471 }
472 else
473 {
474 // Unknown payload type.
475 return;
476 }
477
478 // Zero-initialize padding bytes. (required by FFmpeg safety)
479 vH264EncodedBytes.insert(vH264EncodedBytes.end(), AV_INPUT_BUFFER_PADDING_SIZE, 0);
480
481 // Decode.
482 std::unique_lock<std::shared_mutex> lkDecoderLock(m_muDecoderMutex);
483 bool bDecoded = this->DecodeH264BytesToCVMat(vH264EncodedBytes, m_cvFrame, m_eOutputPixelFormat);
484
485 if (bDecoded && m_fnOnFrameReceivedCallback)
486 {
487 m_fnOnFrameReceivedCallback(m_cvFrame);
488 }
489 lkDecoderLock.unlock();
490 });
491 });
492
493 m_pPeerConnection->onGatheringStateChange(
494 [this](rtc::PeerConnection::GatheringState eGatheringState)
495 {
496 // Switch to translate the state to a string.
497 switch (eGatheringState)
498 {
499 case rtc::PeerConnection::GatheringState::Complete:
500 LOG_DEBUG(logging::g_qSharedLogger, "Camera {} PeerConnection ICE gathering state changed to: Complete", m_szStreamerID);
501 break;
502 case rtc::PeerConnection::GatheringState::InProgress:
503 LOG_DEBUG(logging::g_qSharedLogger, "Camera {} PeerConnection ICE gathering state changed to: InProgress", m_szStreamerID);
504 break;
505 case rtc::PeerConnection::GatheringState::New:
506 LOG_DEBUG(logging::g_qSharedLogger, "Camera {} PeerConnection ICE gathering state changed to: New", m_szStreamerID);
507 break;
508 default: LOG_DEBUG(logging::g_qSharedLogger, "Camera {} Peer connection ICE gathering state changed to: Unknown", m_szStreamerID); break;
509 }
510 });
511
512 m_pPeerConnection->onIceStateChange(
513 [this](rtc::PeerConnection::IceState eIceState)
514 {
515 // Switch to translate the state to a string.
516 switch (eIceState)
517 {
518 case rtc::PeerConnection::IceState::Checking:
519 LOG_INFO(logging::g_qSharedLogger, "Camera {} PeerConnection ICE state changed to: Checking", m_szStreamerID);
520 break;
521 case rtc::PeerConnection::IceState::Closed:
522 LOG_INFO(logging::g_qSharedLogger, "Camera {} PeerConnection ICE state changed to: Closed", m_szStreamerID);
523 break;
524 case rtc::PeerConnection::IceState::Completed:
525 LOG_INFO(logging::g_qSharedLogger, "Camera {} PeerConnection ICE state changed to: Completed", m_szStreamerID);
526 break;
527 case rtc::PeerConnection::IceState::Connected:
528 LOG_INFO(logging::g_qSharedLogger, "Camera {} PeerConnection ICE state changed to: Connected", m_szStreamerID);
529 break;
530 case rtc::PeerConnection::IceState::Disconnected:
531 LOG_INFO(logging::g_qSharedLogger, "Camera {} PeerConnection ICE state changed to: Disconnected", m_szStreamerID);
532 break;
533 case rtc::PeerConnection::IceState::Failed:
534 LOG_INFO(logging::g_qSharedLogger, "Camera {} PeerConnection ICE state changed to: Failed", m_szStreamerID);
535 break;
536 case rtc::PeerConnection::IceState::New: LOG_INFO(logging::g_qSharedLogger, "Camera {} PeerConnection ICE state changed to: New", m_szStreamerID); break;
537 default: LOG_INFO(logging::g_qSharedLogger, "Camera {} Peer connection ICE state changed to: Unknown", m_szStreamerID); break;
538 }
539 });
540 m_pPeerConnection->onSignalingStateChange(
541 [this](rtc::PeerConnection::SignalingState eSignalingState)
542 {
543 // Switch to translate the state to a string.
544 switch (eSignalingState)
545 {
546 case rtc::PeerConnection::SignalingState::HaveLocalOffer:
547 LOG_INFO(logging::g_qSharedLogger, "Camera {} PeerConnection signaling state changed to: HaveLocalOffer", m_szStreamerID);
548 break;
549 case rtc::PeerConnection::SignalingState::HaveLocalPranswer:
550 LOG_INFO(logging::g_qSharedLogger, "Camera {} PeerConnection signaling state changed to: HaveLocalPranswer", m_szStreamerID);
551 break;
552 case rtc::PeerConnection::SignalingState::HaveRemoteOffer:
553 LOG_INFO(logging::g_qSharedLogger, "Camera {} PeerConnection signaling state changed to: HaveRemoteOffer", m_szStreamerID);
554 break;
555 case rtc::PeerConnection::SignalingState::HaveRemotePranswer:
556 LOG_INFO(logging::g_qSharedLogger, "Camera {} PeerConnection signaling state changed to: HaveRemotePrAnswer", m_szStreamerID);
557 break;
558 case rtc::PeerConnection::SignalingState::Stable:
559 LOG_INFO(logging::g_qSharedLogger, "Camera {} PeerConnection signaling state changed to: Stable", m_szStreamerID);
560 break;
561 default: LOG_INFO(logging::g_qSharedLogger, "Camera {} Peer connection signaling state changed to: Unknown", m_szStreamerID); break;
562 }
563 });
564
565 m_pPeerConnection->onStateChange(
566 [this](rtc::PeerConnection::State eState)
567 {
568 // Switch to translate the state to a string.
569 switch (eState)
570 {
571 case rtc::PeerConnection::State::Closed: LOG_INFO(logging::g_qSharedLogger, "Camera {} Peer connection state changed to: Closed", m_szStreamerID); break;
572 case rtc::PeerConnection::State::Connected:
573 LOG_INFO(logging::g_qSharedLogger, "Camera {} Peer connection state changed to: Connected", m_szStreamerID);
574 break;
575 case rtc::PeerConnection::State::Connecting:
576 LOG_INFO(logging::g_qSharedLogger, "Camera {} Peer connection state changed to: Connecting", m_szStreamerID);
577 break;
578 case rtc::PeerConnection::State::Disconnected:
579 LOG_INFO(logging::g_qSharedLogger, "Camera {} Peer connection state changed to: Disconnected", m_szStreamerID);
580 break;
581 case rtc::PeerConnection::State::Failed: LOG_INFO(logging::g_qSharedLogger, "Camera {} Peer connection state changed to: Failed", m_szStreamerID); break;
582 case rtc::PeerConnection::State::New: LOG_INFO(logging::g_qSharedLogger, "Camera {} Peer connection state changed to: New", m_szStreamerID); break;
583 default: LOG_INFO(logging::g_qSharedLogger, "Camera {} Peer connection state changed to: Unknown", m_szStreamerID); break;
584 }
585 });
586
588 // Set some callbacks on important events for the data channel.
590
591 m_pDataChannel->onOpen(
592 [this]()
593 {
594 // Submit logger message.
595 LOG_INFO(logging::g_qSharedLogger, "Camera {} WebRTC Data channel OPENED.", m_szStreamerID);
596
597 // Request quality control of the stream.
598 m_pDataChannel->send(std::string(1, static_cast<char>(1)));
599
600 // --------------------------------------------------------
601 // SEND INITIAL ENCODER CONFIGURATION
602 // --------------------------------------------------------
603 LOG_INFO(logging::g_qSharedLogger, "Camera {} WebRTC Sending Encoder Configuration to Simulator...", m_szStreamerID);
604
605 // Set the QP factor. (0 = Lossless/Max Quality).
606 this->SendCommandToStreamer("{\"Encoder.MaxQP\":" + std::to_string(constants::SIM_WEBRTC_QP) + "}");
607
608 // Set bitrate limits.
609 this->SendCommandToStreamer(R"({"WebRTC.MinBitrate":100000})");
610 this->SendCommandToStreamer(R"({"WebRTC.MaxBitrate":100000000})");
611
612 // Set FPS.
613 this->SendCommandToStreamer(R"({"WebRTC.Fps":60})");
614 this->SendCommandToStreamer(R"({"WebRTC.MaxFps":60})");
615
616 // Target Bitrate. (-1 = Use Max/Unlimited)
617 this->SendCommandToStreamer(R"({"Encoder.TargetBitrate":-1})");
618 });
619
620 m_pDataChannel->onMessage(
621 [this](std::variant<rtc::binary, rtc::string> rtcMessage)
622 {
623 try
624 {
625 // Create instance variables.
626 nlohmann::json jsnMessage;
627
628 // Check if data is of type rtc::string.
629 if (std::holds_alternative<rtc::string>(rtcMessage))
630 {
631 // Retrieve the string message
632 std::string szMessage = std::get<rtc::string>(rtcMessage);
633
634 // Parse the JSON message from the signaling server.
635 jsnMessage = nlohmann::json::parse(szMessage);
636 LOG_DEBUG(logging::g_qSharedLogger, "Camera {} DATA_CHANNEL Received message from peer: {}", m_szStreamerID, szMessage);
637 }
638 else if (std::holds_alternative<rtc::binary>(rtcMessage))
639 {
640 // Retrieve the binary message.
641 rtc::binary rtcBinaryData = std::get<rtc::binary>(rtcMessage);
642
643 // Convert the binary data to a string, ignoring non-printable characters.
644 std::string szBinaryDataStr;
645 for (auto byte : rtcBinaryData)
646 {
647 if (std::isprint(static_cast<unsigned char>(byte)))
648 {
649 szBinaryDataStr += static_cast<char>(byte);
650 }
651 }
652
653 // Print the binary data as a string.
654 LOG_DEBUG(logging::g_qSharedLogger,
655 "Camera {} DATA_CHANNEL Received binary data ({} bytes): {}",
656 m_szStreamerID,
657 rtcBinaryData.size(),
658 szBinaryDataStr);
659 }
660 else
661 {
662 LOG_ERROR(logging::g_qSharedLogger, "Camera {} Received unknown message type from peer", m_szStreamerID);
663 }
664 }
665 catch (const std::exception& e)
666 {
667 // Submit logger message.
668 LOG_ERROR(logging::g_qSharedLogger, "Camera {} Error occurred while negotiating with the datachannel: {}", m_szStreamerID, e.what());
669 }
670 });
671
672 return true;
673}
bool SendCommandToStreamer(const std::string &szCommand)
This method sends a command to the streamer via the data channel. The command is a JSON string that i...
Definition WebRTC.cpp:898
bool DecodeH264BytesToCVMat(const std::vector< uint8_t > &vH264EncodedBytes, cv::Mat &cvDecodedFrame, const AVPixelFormat eOutputPixelFormat)
Decodes H264 encoded bytes to a cv::Mat using FFmpeg.
Definition WebRTC.cpp:746
::uint8_t uint8_t
Here is the call graph for this function:
Here is the caller graph for this function:

◆ InitializeH264Decoder()

bool WebRTC::InitializeH264Decoder ( )
private

Initialize the H264 decoder. Creates the AVCodecContext, AVFrame, and AVPacket.

Returns
true - Successfully initialized the H264 decoder.
false - Failed to initialize the H264 decoder.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2024-12-27
685{
686 // Configure logging level from FFMPEG library.
687 av_log_set_level(AV_LOG_DEBUG);
688
689 // Find the H264 decoder
690 const AVCodec* avCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
691 if (!avCodec)
692 {
693 LOG_ERROR(logging::g_qSharedLogger, "H264 codec not found!");
694 return false;
695 }
696 // Create codec context
697 m_pAVCodecContext = avcodec_alloc_context3(avCodec);
698 if (!m_pAVCodecContext)
699 {
700 LOG_ERROR(logging::g_qSharedLogger, "Failed to allocate codec context!");
701 return false;
702 }
703 // Set codec context options.
704 m_pAVCodecContext->flags |= AV_CODEC_FLAG2_FAST;
705 m_pAVCodecContext->err_recognition = AV_EF_COMPLIANT | AV_EF_CAREFUL;
706 m_pAVCodecContext->rc_buffer_size = 50 * 1024 * 1024; // 50 MB buffer size.
707 av_opt_set_int(m_pAVCodecContext, "refcounted_frames", 1, 0);
708 av_opt_set_int(m_pAVCodecContext, "error_concealment", FF_EC_GUESS_MVS | FF_EC_DEBLOCK, 0);
709 av_opt_set_int(m_pAVCodecContext, "threads", 4, 0);
710
711 // Open the codec
712 if (avcodec_open2(m_pAVCodecContext, avCodec, nullptr) < 0)
713 {
714 LOG_ERROR(logging::g_qSharedLogger, "Failed to open codec!");
715 return false;
716 }
717
718 // Allocate the AVPacket.
719 m_pPacket = av_packet_alloc();
720 // Allocate the AVFrame.
721 m_pFrame = av_frame_alloc();
722 if (!m_pPacket || !m_pFrame)
723 {
724 LOG_ERROR(logging::g_qSharedLogger, "Failed to allocate packet or frame!");
725 return false;
726 }
727
728 // Set the SWSScale context to nullptr.
729 m_pSWSContext = nullptr;
730
731 return true;
732}
Here is the caller graph for this function:

◆ DecodeH264BytesToCVMat()

bool WebRTC::DecodeH264BytesToCVMat ( const std::vector< uint8_t > &  vH264EncodedBytes,
cv::Mat cvDecodedFrame,
const AVPixelFormat  eOutputPixelFormat 
)
private

Decodes H264 encoded bytes to a cv::Mat using FFmpeg.

Parameters
vH264EncodedBytes- The H264 encoded bytes.
cvDecodedFrame- The decoded frame.
eOutputPixelFormat- The output pixel format.
Returns
true - Frame was successfully decoded.
false - Frame was not successfully decoded.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2024-11-16
747{
748 // Safety check
749 if (vH264EncodedBytes.empty())
750 return false;
751
752 // Use the actual data size, excluding the padding we added in onFrame.
753 size_t nDataSize = vH264EncodedBytes.size() - AV_INPUT_BUFFER_PADDING_SIZE;
754
755 // Initialize packet data.
756 m_pPacket->data = const_cast<uint8_t*>(vH264EncodedBytes.data());
757 m_pPacket->size = static_cast<int>(nDataSize); // Tell FFmpeg the real size.
758
759 // Send the packet to the decoder.
760 int nReturnCode = avcodec_send_packet(m_pAVCodecContext, m_pPacket);
761 if (nReturnCode < 0)
762 {
763 // Get the error message.
764 char aErrorBuffer[AV_ERROR_MAX_STRING_SIZE];
765 av_strerror(nReturnCode, aErrorBuffer, AV_ERROR_MAX_STRING_SIZE);
766 // Submit logger message.
767 LOG_WARNING(logging::g_qSharedLogger, "WebRTC camera {} FFMPEG send_packet failed. Error: {} {}", m_szStreamerID, nReturnCode, aErrorBuffer);
768 // Request a new keyframe from the video track.
769 this->RequestKeyFrame();
770
771 return false;
772 }
773
774 // Receive decoded frames in a loop
775 while (true)
776 {
777 nReturnCode = avcodec_receive_frame(m_pAVCodecContext, m_pFrame);
778 if (nReturnCode == AVERROR(EAGAIN) || nReturnCode == AVERROR_EOF)
779 {
780 // No more frames available in stream.
781 break;
782 }
783 else if (nReturnCode < 0)
784 {
785 // Get the error message.
786 char aErrorBuffer[AV_ERROR_MAX_STRING_SIZE];
787 av_strerror(nReturnCode, aErrorBuffer, AV_ERROR_MAX_STRING_SIZE);
788 // Submit logger message.
789 LOG_WARNING(logging::g_qSharedLogger, "Failed to receive frame from decoder! Error code: {} {}", nReturnCode, aErrorBuffer);
790 // Request a new keyframe from the video track.
791 this->RequestKeyFrame();
792
793 return false;
794 }
795
796 // Check if the user want to keep the YUV420P data un-altered.
797 if (eOutputPixelFormat == AV_PIX_FMT_YUV420P)
798 {
799 // The frame received from the FFMPEG H264 decoder is already in YUV420P format.
800 // We want to keep the raw YUV420P byte data un-altered, but store that data in a RGB 3 channel Mat.
801 // Absolutely no colorspace conversion or the binary data will be corrupted.
802
803 // Extract the Y, U, and V planes.
804 cv::Mat cvYPlane(m_pFrame->height, m_pFrame->width, CV_8UC1, m_pFrame->data[0]);
805 cv::Mat cvUPlane(m_pFrame->height / 2, m_pFrame->width / 2, CV_8UC1, m_pFrame->data[1]);
806 cv::Mat cvVPlane(m_pFrame->height / 2, m_pFrame->width / 2, CV_8UC1, m_pFrame->data[2]);
807 // Upsample the U and V planes to match the Y plane.
808 cv::Mat cvUPlaneUpsampled, cvVPlaneUpsampled;
809 cv::resize(cvUPlane, cvUPlaneUpsampled, cv::Size(m_pFrame->width, m_pFrame->height), 0, 0, cv::INTER_NEAREST);
810 cv::resize(cvVPlane, cvVPlaneUpsampled, cv::Size(m_pFrame->width, m_pFrame->height), 0, 0, cv::INTER_NEAREST);
811 // Merge the Y, U, and V planes into a single 3 channel Mat.
812 std::vector<cv::Mat> vYUVPlanes = {cvYPlane, cvUPlaneUpsampled, cvVPlaneUpsampled};
813 cv::merge(vYUVPlanes, cvDecodedFrame);
814 }
815 else
816 {
817 // Convert the decoded frame to cv::Mat using sws_scale.
818 if (m_pSWSContext == nullptr)
819 {
820 LOG_DEBUG(logging::g_qSharedLogger, "WebRTC camera {} Initializing SwsContext...", m_szStreamerID);
821 m_pSWSContext = sws_getContext(m_pFrame->width,
822 m_pFrame->height,
823 static_cast<AVPixelFormat>(m_pFrame->format),
824 m_pFrame->width,
825 m_pFrame->height,
826 eOutputPixelFormat,
827 SWS_FAST_BILINEAR,
828 nullptr,
829 nullptr,
830 nullptr);
831 if (m_pSWSContext == nullptr)
832 {
833 // Submit logger message.
834 LOG_WARNING(logging::g_qSharedLogger, "Failed to initialize SwsContext!");
835 // Request a new keyframe from the video track.
836 this->RequestKeyFrame();
837
838 return false;
839 }
840 }
841
842 // Create new mat for the decoded frame.
843 cvDecodedFrame.create(m_pFrame->height, m_pFrame->width, CV_8UC3);
844 std::array<uint8_t*, 4> aDest = {cvDecodedFrame.data, nullptr, nullptr, nullptr};
845 std::array<int, 4> aDestLinesize = {static_cast<int>(cvDecodedFrame.step[0]), 0, 0, 0};
846
847 // Convert the frame to the output pixel format.
848 sws_scale(m_pSWSContext, m_pFrame->data, m_pFrame->linesize, 0, m_pFrame->height, aDest.data(), aDestLinesize.data());
849 }
850 }
851
852 return true;
853}
bool RequestKeyFrame()
Requests a key frame from the given video track. This is useful for when the video track is out of sy...
Definition WebRTC.cpp:865
uchar * data
void create(int rows, int cols, int type)
MatStep step
void merge(const Mat *mv, size_t count, OutputArray dst)
void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR)
INTER_NEAREST
Here is the call graph for this function:
Here is the caller graph for this function:

◆ RequestKeyFrame()

bool WebRTC::RequestKeyFrame ( )
private

Requests a key frame from the given video track. This is useful for when the video track is out of sync or has lost frames.

Returns
true - Key frame was successfully requested.
false - Key frame was not successfully requested.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2024-11-30
866{
867 // Check if the video track is valid.
868 if (!m_pVideoTrack1)
869 {
870 LOG_ERROR(logging::g_qSharedLogger, "Invalid video track!");
871 return false;
872 }
873
874 // Submit logger message.
875 LOG_DEBUG(logging::g_qSharedLogger, "Requested key frame from video track. Success?: {}", m_pVideoTrack1->requestKeyframe());
876
877 // Request a key frame from the video track.
878 return true;
879}
Here is the caller graph for this function:

◆ SendCommandToStreamer()

bool WebRTC::SendCommandToStreamer ( const std::string &  szCommand)
private

This method sends a command to the streamer via the data channel. The command is a JSON string that is sent as a binary message. The PixelStreaming plugin handles the command very weirdly, so we have to sort of encode the command in a specific way. This handles that encoding.

Parameters
szCommand- The command to send to the streamer.
Returns
true - Command was successfully sent.
false - Command was not successfully sent.
Note
This will only work for valid COMMANDS with ID of type 51. Check the PixelStreamingInfrastructure repo for more information. https://github.com/EpicGamesExt/PixelStreamingInfrastructure/blob/13ce022d3a09d315d4ca85c05b61a8d3fe92741c/Extras/JSStreamer/src/protocol.ts#L196
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-01-01
899{
900 if (!m_pDataChannel)
901 return false;
902 if (szCommand.empty())
903 return false;
904
905 // Command ID 51.
906 std::string szID(1, static_cast<char>(51));
907
908 // Calculate total payload size in BYTES. (UTF-16 = size * 2)
909 uint16_t u16Size = static_cast<uint16_t>(szCommand.size() * 2);
910
911 rtc::binary rtcBinaryMessage;
912 rtcBinaryMessage.push_back(static_cast<std::byte>(szID[0]));
913
914 // Send Size as 16-bit integer. (Little Endian)
915 rtcBinaryMessage.push_back(static_cast<std::byte>(u16Size & 0xFF)); // Low byte
916 rtcBinaryMessage.push_back(static_cast<std::byte>((u16Size >> 8) & 0xFF)); // High byte
917
918 // Send payload as UTF-16 Little Endian.
919 for (char cLetter : szCommand)
920 {
921 rtcBinaryMessage.push_back(static_cast<std::byte>(cLetter));
922 rtcBinaryMessage.push_back(static_cast<std::byte>(0));
923 }
924
925 LOG_DEBUG(logging::g_qSharedLogger, "WebRTC camera {} Sending Data Channel Command: {}", m_szStreamerID, szCommand);
926 return m_pDataChannel->send(rtcBinaryMessage);
927}
::uint16_t uint16_t
Here is the caller graph for this function:

The documentation for this class was generated from the following files: