-
I would like to have the browser create an offer with a request for video, and the server to respond by creating an answer. This is like the opposite of the official example here: https://github.com/paullouisageneau/libdatachannel/tree/master/examples/media-sender. I'd greatly appreciate any guidance in getting it to work properly! I'm not using trickle ice, and I want the browser to only send the offer once all of its ice candidates have been found. And similarly on the server, I only print out the copy/paste answer once its ice candidates are found. I'm using the same gstreamer demo pipeline, and can confirm that the original example works (when the server sends the offer). Procedure:
Notes:
/**
* libdatachannel media sender example
* Copyright (c) 2020 Staz Modrzynski
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "rtc/rtc.hpp"
#include <cstddef>
#include <fstream>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <utility>
#include <nlohmann/json.hpp>
#ifdef _WIN32
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#else
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
typedef int SOCKET;
#endif
using nlohmann::json;
const int BUFFER_SIZE = 2048;
int main() {
try {
rtc::InitLogger(rtc::LogLevel::Debug);
auto pc = std::make_shared<rtc::PeerConnection>();
pc->onStateChange(
[](rtc::PeerConnection::State state) { std::cout << "State: " << state << std::endl; });
pc->onGatheringStateChange([pc](rtc::PeerConnection::GatheringState state) {
std::cout << "Gathering State: " << state << std::endl;
if (state == rtc::PeerConnection::GatheringState::Complete) {
auto description = pc->localDescription();
json answer = {
{"type", description->typeString()},
{"sdp", std::string(description.value())}
};
std::cout << answer << std::endl;
}
});
SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(6000);
if (bind(sock, reinterpret_cast<const sockaddr *>(&addr), sizeof(addr)) < 0)
throw std::runtime_error("Failed to bind UDP socket on 127.0.0.1:6000");
int rcvBufSize = 212992;
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<const char *>(&rcvBufSize),
sizeof(rcvBufSize));
const rtc::SSRC ssrc = 42;
rtc::Description::Video media("video", rtc::Description::Direction::SendOnly);
media.addH264Codec(96); // Must match the payload type of the external h264 RTP stream
media.addSSRC(ssrc, "video-send");
auto track = pc->addTrack(media);
std::ifstream sdp("offer.json");
json j = json::parse(sdp);
rtc::Description offer(j["sdp"].get<std::string>(), j["type"].get<std::string>());
pc->setRemoteDescription(offer);
// Receive from UDP
char buffer[BUFFER_SIZE];
int len;
while ((len = recv(sock, buffer, BUFFER_SIZE, 0)) >= 0) {
if (len < sizeof(rtc::RtpHeader) || !track->isOpen())
continue;
auto rtp = reinterpret_cast<rtc::RtpHeader *>(buffer);
rtp->setSsrc(ssrc);
track->send(reinterpret_cast<const std::byte*>(buffer), len);
}
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << std::endl;
}
} <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>libdatachannel media sender example</title>
</head>
<body>
<p>Please enter the offer provided to you by the sender application: </p>
<textarea cols="80" rows="25"></textarea>
<button>Submit</button>
<video id="video-element" muted></video>
<script type="module">
const pc = new RTCPeerConnection();
pc.addEventListener('icegatheringstatechange', () => {
console.log(`ICE gathering state changed: ${pc.iceGatheringState}`)
})
pc.addEventListener('iceconnectionstatechange ', () => {
console.log(`ICE connection state change: ${pc.iceConnectionState}`)
})
pc.addEventListener('connectionstatechange', () => {
console.log(`Connection state change: ${pc.connectionState}`)
})
pc.addEventListener('icecandidate', (event) => {
console.log(`Ice candidate: ${JSON.stringify(event.candidate?.toJSON())}`)
})
pc.onicegatheringstatechange = (state) => {
if (pc.iceGatheringState === 'complete') {
document.querySelector('textarea').value = JSON.stringify(pc.localDescription?.toJSON());
}
}
pc.ontrack = (evt) => {
console.log('track received: ', evt);
const videoElement = document.getElementById('video-element');
videoElement.srcObject = evt.streams[0];
videoElement.play();
};
const offer = await pc.createOffer({
offerToReceiveVideo: true,
});
await pc.setLocalDescription(offer);
document.querySelector('button').addEventListener('click', async () => {
const answer = JSON.parse(document.querySelector('textarea').value);
await pc.setRemoteDescription(answer);
})
</script>
</body>
</html> Sample offer SDP
Sample answer SDP
|
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
Solved based on the rough guidelines provided here: #1064 (comment) The solution that worked for me was something like this: rtc::SSRC ssrc = 42;
std::string targetCodec = "H264";
int payloadType = -1;
// For POC, define track in outer scope
std::shared_ptr<rtc::Track> track;
pc->onTrack([](const std::shared_ptr<rtc::Track>& t) {
track = t;
rtc::Description::Media description = track->description();
description.addSSRC(ssrc, "video-send");
// This stuff might be superfluous/unoptimal, but it was helpful for my understanding
// Removes all payload types except for the target one, e.g. 39 for H264
std::vector<int> payloadTypesToRemove;
for (int p : description.payloadTypes()) {
rtc::Description::Media::RtpMap* map = description.rtpMap(p);
if (payloadType == -1 && map->format == targetCodec) {
payloadType = p;
} else {
payloadTypesToRemove.push_back(p);
}
}
for (int p : payloadTypesToRemove) {
description.removeRtpMap(p);
}
track->setDescription(description);
})
// ...
while ((len = recv(sock, buffer, BUFFER_SIZE, 0)) >= 0) {
if (len < sizeof(rtc::RtpHeader) || !track->isOpen())
continue;
auto rtp = reinterpret_cast<rtc::RtpHeader *>(buffer);
rtp->setSsrc(ssrc);
rtp->setPayloadType(payloadType);
track->send(reinterpret_cast<const std::byte*>(buffer), len);
} |
Beta Was this translation helpful? Give feedback.
Solved based on the rough guidelines provided here: #1064 (comment)
The solution that worked for me was something like this: