Added SDP output
This commit is contained in:
parent
f0674b9efb
commit
dd2382e858
9 changed files with 383 additions and 84 deletions
|
@ -562,6 +562,7 @@ makeOutput(CMAF cmaf http)#LTS
|
||||||
makeOutput(EBML ebml)
|
makeOutput(EBML ebml)
|
||||||
makeOutput(RTSP rtsp)#LTS
|
makeOutput(RTSP rtsp)#LTS
|
||||||
makeOutput(WAV wav)#LTS
|
makeOutput(WAV wav)#LTS
|
||||||
|
makeOutput(SDP sdp http)
|
||||||
|
|
||||||
add_executable(MistProcFFMPEG
|
add_executable(MistProcFFMPEG
|
||||||
${BINARY_DIR}/mist/.headers
|
${BINARY_DIR}/mist/.headers
|
||||||
|
|
91
lib/sdp.cpp
91
lib/sdp.cpp
|
@ -7,6 +7,7 @@
|
||||||
#include "sdp.h"
|
#include "sdp.h"
|
||||||
#include "url.h"
|
#include "url.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
//Dynamic types we hardcode:
|
//Dynamic types we hardcode:
|
||||||
//96 = AAC
|
//96 = AAC
|
||||||
|
@ -58,7 +59,8 @@ namespace SDP{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the SDP contents for sending out a particular given DTSC::Track.
|
/// Gets the SDP contents for sending out a particular given DTSC::Track.
|
||||||
std::string mediaDescription(const DTSC::Meta *meta, size_t tid){
|
/// \param port UDP port we are sending data to. Defaults to 0
|
||||||
|
std::string mediaDescription(const DTSC::Meta *meta, size_t tid, uint64_t port){
|
||||||
const DTSC::Meta &M = *meta;
|
const DTSC::Meta &M = *meta;
|
||||||
std::stringstream mediaDesc;
|
std::stringstream mediaDesc;
|
||||||
|
|
||||||
|
@ -68,7 +70,7 @@ namespace SDP{
|
||||||
if (codec == "H264"){
|
if (codec == "H264"){
|
||||||
MP4::AVCC avccbox;
|
MP4::AVCC avccbox;
|
||||||
avccbox.setPayload(init);
|
avccbox.setPayload(init);
|
||||||
mediaDesc << "m=video 0 RTP/AVP 97\r\n"
|
mediaDesc << "m=video " << port << " RTP/AVP 97\r\n"
|
||||||
"a=rtpmap:97 H264/90000\r\n"
|
"a=rtpmap:97 H264/90000\r\n"
|
||||||
"a=cliprect:0,0,"
|
"a=cliprect:0,0,"
|
||||||
<< M.getHeight(tid) << "," << M.getWidth(tid) << "\r\na=framesize:97 "
|
<< M.getHeight(tid) << "," << M.getWidth(tid) << "\r\na=framesize:97 "
|
||||||
|
@ -94,7 +96,7 @@ namespace SDP{
|
||||||
<< ((double)M.getFpks(tid)) / 1000.0 << "\r\na=control:track" << tid << "\r\n";
|
<< ((double)M.getFpks(tid)) / 1000.0 << "\r\na=control:track" << tid << "\r\n";
|
||||||
}else if (codec == "HEVC"){
|
}else if (codec == "HEVC"){
|
||||||
h265::initData iData(init);
|
h265::initData iData(init);
|
||||||
mediaDesc << "m=video 0 RTP/AVP 104\r\n"
|
mediaDesc << "m=video " << port << " RTP/AVP 104\r\n"
|
||||||
"a=rtpmap:104 H265/90000\r\n"
|
"a=rtpmap:104 H265/90000\r\n"
|
||||||
"a=cliprect:0,0,"
|
"a=cliprect:0,0,"
|
||||||
<< M.getHeight(tid) << "," << M.getWidth(tid) << "\r\na=framesize:104 "
|
<< M.getHeight(tid) << "," << M.getWidth(tid) << "\r\na=framesize:104 "
|
||||||
|
@ -126,7 +128,7 @@ namespace SDP{
|
||||||
mediaDesc << "\r\na=framerate:" << ((double)M.getFpks(tid)) / 1000.0 << "\r\na=control:track"
|
mediaDesc << "\r\na=framerate:" << ((double)M.getFpks(tid)) / 1000.0 << "\r\na=control:track"
|
||||||
<< tid << "\r\n";
|
<< tid << "\r\n";
|
||||||
}else if (codec == "VP8"){
|
}else if (codec == "VP8"){
|
||||||
mediaDesc << "m=video 0 RTP/AVP 98\r\n"
|
mediaDesc << "m=video " << port << " RTP/AVP 98\r\n"
|
||||||
"a=rtpmap:98 VP8/90000\r\n"
|
"a=rtpmap:98 VP8/90000\r\n"
|
||||||
"a=cliprect:0,0,"
|
"a=cliprect:0,0,"
|
||||||
<< M.getHeight(tid) << "," << M.getWidth(tid) << "\r\na=framesize:98 "
|
<< M.getHeight(tid) << "," << M.getWidth(tid) << "\r\na=framesize:98 "
|
||||||
|
@ -134,7 +136,7 @@ namespace SDP{
|
||||||
<< "a=framerate:" << ((double)M.getFpks(tid)) / 1000.0 << "\r\n"
|
<< "a=framerate:" << ((double)M.getFpks(tid)) / 1000.0 << "\r\n"
|
||||||
<< "a=control:track" << tid << "\r\n";
|
<< "a=control:track" << tid << "\r\n";
|
||||||
}else if (codec == "VP9"){
|
}else if (codec == "VP9"){
|
||||||
mediaDesc << "m=video 0 RTP/AVP 99\r\n"
|
mediaDesc << "m=video " << port << " RTP/AVP 99\r\n"
|
||||||
"a=rtpmap:99 VP8/90000\r\n"
|
"a=rtpmap:99 VP8/90000\r\n"
|
||||||
"a=cliprect:0,0,"
|
"a=cliprect:0,0,"
|
||||||
<< M.getHeight(tid) << "," << M.getWidth(tid) << "\r\na=framesize:99 "
|
<< M.getHeight(tid) << "," << M.getWidth(tid) << "\r\na=framesize:99 "
|
||||||
|
@ -142,13 +144,13 @@ namespace SDP{
|
||||||
<< "a=framerate:" << ((double)M.getFpks(tid)) / 1000.0 << "\r\n"
|
<< "a=framerate:" << ((double)M.getFpks(tid)) / 1000.0 << "\r\n"
|
||||||
<< "a=control:track" << tid << "\r\n";
|
<< "a=control:track" << tid << "\r\n";
|
||||||
}else if (codec == "MPEG2"){
|
}else if (codec == "MPEG2"){
|
||||||
mediaDesc << "m=video 0 RTP/AVP 32\r\n"
|
mediaDesc << "m=video " << port << " RTP/AVP 32\r\n"
|
||||||
"a=cliprect:0,0,"
|
"a=cliprect:0,0,"
|
||||||
<< M.getHeight(tid) << "," << M.getWidth(tid) << "\r\na=framesize:32 " << M.getWidth(tid)
|
<< M.getHeight(tid) << "," << M.getWidth(tid) << "\r\na=framesize:32 " << M.getWidth(tid)
|
||||||
<< '-' << M.getHeight(tid) << "\r\na=framerate:" << ((double)M.getFpks(tid)) / 1000.0
|
<< '-' << M.getHeight(tid) << "\r\na=framerate:" << ((double)M.getFpks(tid)) / 1000.0
|
||||||
<< "\r\na=control:track" << tid << "\r\n";
|
<< "\r\na=control:track" << tid << "\r\n";
|
||||||
}else if (codec == "AAC"){
|
}else if (codec == "AAC"){
|
||||||
mediaDesc << "m=audio 0 RTP/AVP 96"
|
mediaDesc << "m=audio " << port << " RTP/AVP 96"
|
||||||
<< "\r\n"
|
<< "\r\n"
|
||||||
"a=rtpmap:96 mpeg4-generic/"
|
"a=rtpmap:96 mpeg4-generic/"
|
||||||
<< M.getRate(tid) << "/" << M.getChannels(tid)
|
<< M.getRate(tid) << "/" << M.getChannels(tid)
|
||||||
|
@ -162,53 +164,53 @@ namespace SDP{
|
||||||
"a=control:track"
|
"a=control:track"
|
||||||
<< tid << "\r\n";
|
<< tid << "\r\n";
|
||||||
}else if (codec == "MP3" || codec == "MP2"){
|
}else if (codec == "MP3" || codec == "MP2"){
|
||||||
mediaDesc << "m=" << M.getType(tid) << " 0 RTP/AVP 14"
|
mediaDesc << "m=" << M.getType(tid) << " " << port << " RTP/AVP 14"
|
||||||
<< "\r\n"
|
<< "\r\n"
|
||||||
"a=rtpmap:14 MPA/90000/"
|
"a=rtpmap:14 MPA/90000/"
|
||||||
<< M.getChannels(tid) << "\r\n"
|
<< M.getChannels(tid) << "\r\n"
|
||||||
<< "a=control:track" << tid << "\r\n";
|
<< "a=control:track" << tid << "\r\n";
|
||||||
}else if (codec == "AC3"){
|
}else if (codec == "AC3"){
|
||||||
mediaDesc << "m=audio 0 RTP/AVP 100"
|
mediaDesc << "m=audio " << port << " RTP/AVP 100"
|
||||||
<< "\r\n"
|
<< "\r\n"
|
||||||
"a=rtpmap:100 AC3/"
|
"a=rtpmap:100 AC3/"
|
||||||
<< M.getRate(tid) << "/" << M.getChannels(tid) << "\r\n"
|
<< M.getRate(tid) << "/" << M.getChannels(tid) << "\r\n"
|
||||||
<< "a=control:track" << tid << "\r\n";
|
<< "a=control:track" << tid << "\r\n";
|
||||||
}else if (codec == "ALAW"){
|
}else if (codec == "ALAW"){
|
||||||
if (M.getChannels(tid) == 1 && M.getRate(tid) == 8000){
|
if (M.getChannels(tid) == 1 && M.getRate(tid) == 8000){
|
||||||
mediaDesc << "m=audio 0 RTP/AVP 8"
|
mediaDesc << "m=audio " << port << " RTP/AVP 8"
|
||||||
<< "\r\n";
|
<< "\r\n";
|
||||||
}else{
|
}else{
|
||||||
mediaDesc << "m=audio 0 RTP/AVP 101"
|
mediaDesc << "m=audio " << port << " RTP/AVP 101"
|
||||||
<< "\r\n";
|
<< "\r\n";
|
||||||
mediaDesc << "a=rtpmap:101 PCMA/" << M.getRate(tid) << "/" << M.getChannels(tid) << "\r\n";
|
mediaDesc << "a=rtpmap:101 PCMA/" << M.getRate(tid) << "/" << M.getChannels(tid) << "\r\n";
|
||||||
}
|
}
|
||||||
mediaDesc << "a=control:track" << tid << "\r\n";
|
mediaDesc << "a=control:track" << tid << "\r\n";
|
||||||
}else if (codec == "ULAW"){
|
}else if (codec == "ULAW"){
|
||||||
if (M.getChannels(tid) == 1 && M.getRate(tid) == 8000){
|
if (M.getChannels(tid) == 1 && M.getRate(tid) == 8000){
|
||||||
mediaDesc << "m=audio 0 RTP/AVP 0"
|
mediaDesc << "m=audio " << port << " RTP/AVP 0"
|
||||||
<< "\r\n";
|
<< "\r\n";
|
||||||
}else{
|
}else{
|
||||||
mediaDesc << "m=audio 0 RTP/AVP 105"
|
mediaDesc << "m=audio " << port << " RTP/AVP 105"
|
||||||
<< "\r\n";
|
<< "\r\n";
|
||||||
mediaDesc << "a=rtpmap:105 PCMU/" << M.getRate(tid) << "/" << M.getChannels(tid) << "\r\n";
|
mediaDesc << "a=rtpmap:105 PCMU/" << M.getRate(tid) << "/" << M.getChannels(tid) << "\r\n";
|
||||||
}
|
}
|
||||||
mediaDesc << "a=control:track" << tid << "\r\n";
|
mediaDesc << "a=control:track" << tid << "\r\n";
|
||||||
}else if (codec == "PCM"){
|
}else if (codec == "PCM"){
|
||||||
if (M.getSize(tid) == 16 && M.getChannels(tid) == 2 && M.getRate(tid) == 44100){
|
if (M.getSize(tid) == 16 && M.getChannels(tid) == 2 && M.getRate(tid) == 44100){
|
||||||
mediaDesc << "m=audio 0 RTP/AVP 10"
|
mediaDesc << "m=audio " << port << " RTP/AVP 10"
|
||||||
<< "\r\n";
|
<< "\r\n";
|
||||||
}else if (M.getSize(tid) == 16 && M.getChannels(tid) == 1 && M.getRate(tid) == 44100){
|
}else if (M.getSize(tid) == 16 && M.getChannels(tid) == 1 && M.getRate(tid) == 44100){
|
||||||
mediaDesc << "m=audio 0 RTP/AVP 11"
|
mediaDesc << "m=audio " << port << " RTP/AVP 11"
|
||||||
<< "\r\n";
|
<< "\r\n";
|
||||||
}else{
|
}else{
|
||||||
mediaDesc << "m=audio 0 RTP/AVP 103"
|
mediaDesc << "m=audio " << port << " RTP/AVP 103"
|
||||||
<< "\r\n";
|
<< "\r\n";
|
||||||
mediaDesc << "a=rtpmap:103 L" << M.getSize(tid) << "/" << M.getRate(tid) << "/"
|
mediaDesc << "a=rtpmap:103 L" << M.getSize(tid) << "/" << M.getRate(tid) << "/"
|
||||||
<< M.getChannels(tid) << "\r\n";
|
<< M.getChannels(tid) << "\r\n";
|
||||||
}
|
}
|
||||||
mediaDesc << "a=control:track" << tid << "\r\n";
|
mediaDesc << "a=control:track" << tid << "\r\n";
|
||||||
}else if (codec == "opus"){
|
}else if (codec == "opus"){
|
||||||
mediaDesc << "m=audio 0 RTP/AVP 102"
|
mediaDesc << "m=audio " << port << " RTP/AVP 102"
|
||||||
<< "\r\n"
|
<< "\r\n"
|
||||||
"a=rtpmap:102 opus/"
|
"a=rtpmap:102 opus/"
|
||||||
<< M.getRate(tid) << "/" << M.getChannels(tid) << "\r\n"
|
<< M.getRate(tid) << "/" << M.getChannels(tid) << "\r\n"
|
||||||
|
@ -243,13 +245,43 @@ namespace SDP{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the TCP/UDP connection details from a given transport string.
|
/// Prepares data and RTP control UDP sockets for multicast delivery
|
||||||
/// Sets the transportString member to the current transport string on success.
|
/// \param dest: IP address to where we want to push RTP packets to
|
||||||
/// \param host The host connecting to us.
|
/// \param port: port which will receive DATA. port+1 will receive RTCP
|
||||||
/// \source The source identifier.
|
/// \return True if we have bound the correct ports, False if we need to abort
|
||||||
/// \return True if successful, false otherwise.
|
bool Track::prepareSockets(const std::string dest, uint32_t port){
|
||||||
bool Track::parseTransport(const std::string &transport, const std::string &host,
|
HIGH_MSG("Preparing sockets for pushing towards '%s' port '%" PRIu32 "'", dest.c_str(), port);
|
||||||
const std::string &source, const DTSC::Meta *M, size_t tid){
|
int sendbuff = 4 * 1024 * 1024;
|
||||||
|
setsockopt(data.getSock(), SOL_SOCKET, SO_SNDBUF, &sendbuff, sizeof(sendbuff));
|
||||||
|
setsockopt(rtcp.getSock(), SOL_SOCKET, SO_SNDBUF, &sendbuff, sizeof(sendbuff));
|
||||||
|
data.setSocketFamily(AF_UNSPEC);
|
||||||
|
rtcp.setSocketFamily(AF_UNSPEC);
|
||||||
|
data.SetDestination(dest, 1337);
|
||||||
|
rtcp.SetDestination(dest, 1337);
|
||||||
|
|
||||||
|
// If no explicit port was set, we will bind to anything that works for us
|
||||||
|
// We only need to bind the RTCP UDP port, since we are not receiving data
|
||||||
|
if (!port){
|
||||||
|
int retries = 0;
|
||||||
|
while (!portB && retries < 10){
|
||||||
|
portB = rtcp.bind(0);
|
||||||
|
}
|
||||||
|
portA = portB-1;
|
||||||
|
}else{
|
||||||
|
portA = port;
|
||||||
|
portB = rtcp.bind(portA + 1);
|
||||||
|
}
|
||||||
|
data.SetDestination(dest, portA);
|
||||||
|
rtcp.SetDestination(dest, portB);
|
||||||
|
|
||||||
|
if(portB != portA + 1 || !portA || !portB){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Determines the codec of the current track
|
||||||
|
bool Track::setPackCodec(const DTSC::Meta *M, size_t tid){
|
||||||
std::string codec = M->getCodec(tid);
|
std::string codec = M->getCodec(tid);
|
||||||
if (codec == "H264"){
|
if (codec == "H264"){
|
||||||
pack = RTP::Packet(97, 1, 0, mySSRC);
|
pack = RTP::Packet(97, 1, 0, mySSRC);
|
||||||
|
@ -293,6 +325,17 @@ namespace SDP{
|
||||||
ERROR_MSG("Unsupported codec %s for RTSP on track %zu", codec.c_str(), tid);
|
ERROR_MSG("Unsupported codec %s for RTSP on track %zu", codec.c_str(), tid);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the TCP/UDP connection details from a given transport string.
|
||||||
|
/// Sets the transportString member to the current transport string on success.
|
||||||
|
/// \param host The host connecting to us.
|
||||||
|
/// \source The source identifier.
|
||||||
|
/// \return True if successful, false otherwise.
|
||||||
|
bool Track::parseTransport(const std::string &transport, const std::string &host,
|
||||||
|
const std::string &source, const DTSC::Meta *M, size_t tid){
|
||||||
|
if (!setPackCodec(M, tid)){return false;}
|
||||||
if (transport.find("TCP") != std::string::npos){
|
if (transport.find("TCP") != std::string::npos){
|
||||||
std::string chanE = transport.substr(transport.find("interleaved=") + 12,
|
std::string chanE = transport.substr(transport.find("interleaved=") + 12,
|
||||||
(transport.size() - transport.rfind('-') - 1)); // extract channel ID
|
(transport.size() - transport.rfind('-') - 1)); // extract channel ID
|
||||||
|
|
|
@ -19,6 +19,8 @@ namespace SDP{
|
||||||
bool parseTransport(const std::string &transport, const std::string &host,
|
bool parseTransport(const std::string &transport, const std::string &host,
|
||||||
const std::string &source, const DTSC::Meta *M, size_t tid);
|
const std::string &source, const DTSC::Meta *M, size_t tid);
|
||||||
std::string rtpInfo(const DTSC::Meta &M, size_t tid, const std::string &source, uint64_t currentTime);
|
std::string rtpInfo(const DTSC::Meta &M, size_t tid, const std::string &source, uint64_t currentTime);
|
||||||
|
bool prepareSockets(const std::string dest, uint32_t port);
|
||||||
|
bool setPackCodec(const DTSC::Meta *M, size_t tid);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Socket::UDPConnection data;
|
Socket::UDPConnection data;
|
||||||
|
@ -60,5 +62,5 @@ namespace SDP{
|
||||||
std::map<uint64_t, Track> tracks; ///< List of selected tracks with SDP-specific session data.
|
std::map<uint64_t, Track> tracks; ///< List of selected tracks with SDP-specific session data.
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string mediaDescription(const DTSC::Meta *M, size_t tid);
|
std::string mediaDescription(const DTSC::Meta *M, size_t tid, uint64_t port = 0);
|
||||||
}// namespace SDP
|
}// namespace SDP
|
||||||
|
|
|
@ -1692,6 +1692,12 @@ Socket::UDPConnection::~UDPConnection(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sets socket family type (to IPV4 or IPV6) (AF_INET=2, AF_INET6=10)
|
||||||
|
void Socket::UDPConnection::setSocketFamily(int AF_TYPE){\
|
||||||
|
INFO_MSG("Switching UDP socket from %s to %s", addrFam(family), addrFam(AF_TYPE));
|
||||||
|
family = AF_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
/// Stores the properties of the receiving end of this UDP socket.
|
/// Stores the properties of the receiving end of this UDP socket.
|
||||||
/// This will be the receiving end for all SendNow calls.
|
/// This will be the receiving end for all SendNow calls.
|
||||||
void Socket::UDPConnection::SetDestination(std::string destIp, uint32_t port){
|
void Socket::UDPConnection::SetDestination(std::string destIp, uint32_t port){
|
||||||
|
|
|
@ -223,5 +223,6 @@ namespace Socket{
|
||||||
void SendNow(const std::string &data);
|
void SendNow(const std::string &data);
|
||||||
void SendNow(const char *data);
|
void SendNow(const char *data);
|
||||||
void SendNow(const char *data, size_t len);
|
void SendNow(const char *data, size_t len);
|
||||||
|
void setSocketFamily(int AF_TYPE);
|
||||||
};
|
};
|
||||||
}// namespace Socket
|
}// namespace Socket
|
||||||
|
|
|
@ -172,41 +172,14 @@ namespace Mist{
|
||||||
if (reqUrl.size() && lastAnnounce + 5 < Util::bootSecs()){
|
if (reqUrl.size() && lastAnnounce + 5 < Util::bootSecs()){
|
||||||
INFO_MSG("Sending announce");
|
INFO_MSG("Sending announce");
|
||||||
lastAnnounce = Util::bootSecs();
|
lastAnnounce = Util::bootSecs();
|
||||||
std::stringstream transportString;
|
std::string transportString = generateSDP(reqUrl);
|
||||||
transportString.precision(3);
|
|
||||||
transportString << std::fixed
|
|
||||||
<< "v=0\r\n"
|
|
||||||
"o=- "
|
|
||||||
<< Util::getMS()
|
|
||||||
<< " 1 IN IP4 127.0.0.1\r\n"
|
|
||||||
"s="
|
|
||||||
<< streamName
|
|
||||||
<< "\r\n"
|
|
||||||
"c=IN IP4 0.0.0.0\r\n"
|
|
||||||
"i="
|
|
||||||
<< streamName
|
|
||||||
<< "\r\n"
|
|
||||||
"u="
|
|
||||||
<< reqUrl
|
|
||||||
<< "\r\n"
|
|
||||||
"t=0 0\r\n"
|
|
||||||
"a=tool:" APPIDENT "\r\n"
|
|
||||||
"a=type:broadcast\r\n"
|
|
||||||
"a=control:*\r\n"
|
|
||||||
<< "a=range:npt=" << ((double)startTime()) / 1000.0 << "-"
|
|
||||||
<< ((double)endTime()) / 1000.0 << "\r\n";
|
|
||||||
|
|
||||||
std::set<size_t> validTracks = M.getValidTracks();
|
|
||||||
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
|
|
||||||
transportString << SDP::mediaDescription(&M, *it);
|
|
||||||
}
|
|
||||||
HTTP_S.Clean();
|
HTTP_S.Clean();
|
||||||
HTTP_S.SetHeader("Content-Type", "application/sdp");
|
HTTP_S.SetHeader("Content-Type", "application/sdp");
|
||||||
HTTP_S.SetHeader("Content-Base", reqUrl);
|
HTTP_S.SetHeader("Content-Base", reqUrl);
|
||||||
HTTP_S.method = "ANNOUNCE";
|
HTTP_S.method = "ANNOUNCE";
|
||||||
HTTP_S.url = reqUrl;
|
HTTP_S.url = reqUrl;
|
||||||
HTTP_S.protocol = "RTSP/1.0";
|
HTTP_S.protocol = "RTSP/1.0";
|
||||||
HTTP_S.SendRequest(myConn, transportString.str());
|
HTTP_S.SendRequest(myConn, transportString);
|
||||||
HTTP_R.Clean();
|
HTTP_R.Clean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -320,38 +293,11 @@ namespace Mist{
|
||||||
reqUrl = HTTP::URL(HTTP_R.url).link(streamName).getProxyUrl();
|
reqUrl = HTTP::URL(HTTP_R.url).link(streamName).getProxyUrl();
|
||||||
initialize();
|
initialize();
|
||||||
userSelect.clear();
|
userSelect.clear();
|
||||||
std::stringstream transportString;
|
std::string transportString = generateSDP(reqUrl);
|
||||||
transportString.precision(3);
|
HIGH_MSG("Reply: %s", transportString.c_str());
|
||||||
transportString << std::fixed
|
|
||||||
<< "v=0\r\n"
|
|
||||||
"o=- "
|
|
||||||
<< Util::getMS()
|
|
||||||
<< " 1 IN IP4 127.0.0.1\r\n"
|
|
||||||
"s="
|
|
||||||
<< streamName
|
|
||||||
<< "\r\n"
|
|
||||||
"c=IN IP4 0.0.0.0\r\n"
|
|
||||||
"i="
|
|
||||||
<< streamName
|
|
||||||
<< "\r\n"
|
|
||||||
"u="
|
|
||||||
<< reqUrl
|
|
||||||
<< "\r\n"
|
|
||||||
"t=0 0\r\n"
|
|
||||||
"a=tool:" APPIDENT "\r\n"
|
|
||||||
"a=type:broadcast\r\n"
|
|
||||||
"a=control:*\r\n"
|
|
||||||
<< "a=range:npt=" << ((double)startTime()) / 1000.0 << "-"
|
|
||||||
<< ((double)endTime()) / 1000.0 << "\r\n";
|
|
||||||
|
|
||||||
std::set<size_t> validTracks = Util::wouldSelect(M, targetParams, capa, UA);
|
|
||||||
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
|
|
||||||
transportString << SDP::mediaDescription(&M, *it);
|
|
||||||
}
|
|
||||||
HIGH_MSG("Reply: %s", transportString.str().c_str());
|
|
||||||
HTTP_S.SetHeader("Content-Base", reqUrl);
|
HTTP_S.SetHeader("Content-Base", reqUrl);
|
||||||
HTTP_S.SetHeader("Content-Type", "application/sdp");
|
HTTP_S.SetHeader("Content-Type", "application/sdp");
|
||||||
HTTP_S.SetBody(transportString.str());
|
HTTP_S.SetBody(transportString);
|
||||||
HTTP_S.SendResponse("200", "OK", myConn);
|
HTTP_S.SendResponse("200", "OK", myConn);
|
||||||
HTTP_R.Clean();
|
HTTP_R.Clean();
|
||||||
continue;
|
continue;
|
||||||
|
@ -479,6 +425,35 @@ namespace Mist{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \param reqUrl: URI to additional session information
|
||||||
|
std::string OutRTSP::generateSDP(std::string reqUrl){
|
||||||
|
std::stringstream transportString;
|
||||||
|
transportString.precision(3);
|
||||||
|
// Add session description & time description
|
||||||
|
transportString << std::fixed <<
|
||||||
|
"v=0\r\n"
|
||||||
|
"o=- " << Util::getMS() << " 1 IN IP4 127.0.0.1\r\n"
|
||||||
|
"s=" << streamName << "\r\n"
|
||||||
|
"c=IN IP4 0.0.0.0\r\n"
|
||||||
|
"i=" << streamName << "\r\n"
|
||||||
|
"u=" << reqUrl << "\r\n"
|
||||||
|
"t=0 0\r\n"
|
||||||
|
"a=tool:" APPIDENT "\r\n"
|
||||||
|
"a=type:broadcast\r\n"
|
||||||
|
"a=control:*\r\n";
|
||||||
|
if (M.getLive()){
|
||||||
|
transportString << "a=range:npt=" << ((double)startTime()) / 1000.0 << "-\r\n";
|
||||||
|
}else{
|
||||||
|
transportString << "a=range:npt=" << ((double)startTime()) / 1000.0 << "-" << ((double)endTime()) / 1000.0 << "\r\n";
|
||||||
|
}
|
||||||
|
// Add Media description
|
||||||
|
std::set<size_t> validTracks = M.getValidTracks();
|
||||||
|
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
|
||||||
|
transportString << SDP::mediaDescription(&M, *it);
|
||||||
|
}
|
||||||
|
return transportString.str();
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempts to parse TCP RTP packets at the beginning of the header.
|
/// Attempts to parse TCP RTP packets at the beginning of the header.
|
||||||
/// Returns whether it is safe to attempt to read HTTP/RTSP packets (true) or not (false).
|
/// Returns whether it is safe to attempt to read HTTP/RTSP packets (true) or not (false).
|
||||||
bool OutRTSP::handleTCP(){
|
bool OutRTSP::handleTCP(){
|
||||||
|
|
|
@ -30,6 +30,7 @@ namespace Mist{
|
||||||
int64_t packetOffset;
|
int64_t packetOffset;
|
||||||
bool expectTCP;
|
bool expectTCP;
|
||||||
bool checkPort;
|
bool checkPort;
|
||||||
|
std::string generateSDP(std::string reqUrl);
|
||||||
bool handleTCP();
|
bool handleTCP();
|
||||||
void handleUDP();
|
void handleUDP();
|
||||||
};
|
};
|
||||||
|
|
236
src/output/output_sdp.cpp
Normal file
236
src/output/output_sdp.cpp
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
#include "output_sdp.h"
|
||||||
|
#include "lib/defines.h"
|
||||||
|
#include "mist/defines.h"
|
||||||
|
#include "src/output/output.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
|
namespace Mist{
|
||||||
|
Socket::Connection *mainConn = 0;
|
||||||
|
|
||||||
|
OutSDP::OutSDP(Socket::Connection &conn) : HTTPOutput(conn){
|
||||||
|
mainConn = &conn;
|
||||||
|
exitOnNoRTCP = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Function used to send RTP packets over UDP
|
||||||
|
///\param socket A UDP Connection pointer, sent as a void*, to keep portability.
|
||||||
|
///\param data The RTP Packet that needs to be sent
|
||||||
|
///\param len The size of data
|
||||||
|
///\param channel Not used here, but is kept for compatibility with sendTCP
|
||||||
|
void sendUDP(void *socket, const char *data, size_t len, uint8_t){
|
||||||
|
((Socket::UDPConnection *)socket)->SendNow(data, len);
|
||||||
|
if (mainConn){mainConn->addUp(len);}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Initializes the SDP state
|
||||||
|
/// \param port: Each track will have a data and RTCP port.
|
||||||
|
/// These are consecutive and start at startPort
|
||||||
|
/// \param targetIP: (Multicast supported) IP address we are pushing the stream towards
|
||||||
|
void OutSDP::initTracks(uint32_t & port, std::string targetIP){
|
||||||
|
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); ++it){
|
||||||
|
SDP::Track newTrk;
|
||||||
|
newTrk.setPackCodec(&M, it->first);
|
||||||
|
if (!newTrk.prepareSockets(targetIP, port)){
|
||||||
|
FAIL_MSG("Could not bind required UDP sockets. Aborting push...");
|
||||||
|
parseData = false;
|
||||||
|
wantRequest = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If a starting port was set, increment by 2 for the next track
|
||||||
|
if (port){
|
||||||
|
port+=2;
|
||||||
|
}
|
||||||
|
sdpState.tracks.insert(std::pair<size_t,SDP::Track>(it->first, newTrk));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutSDP::sendHeader(){
|
||||||
|
if (isRecording()){
|
||||||
|
std::string targetIP;
|
||||||
|
if (targetParams["targetIP"].size()){
|
||||||
|
targetIP = targetParams["targetIP"];
|
||||||
|
} else {
|
||||||
|
targetIP = "234.234.234.234";
|
||||||
|
}
|
||||||
|
// If not set, default to random ports we can bind
|
||||||
|
uint32_t port = 0;
|
||||||
|
if (targetParams["startPort"].size()){
|
||||||
|
port = atoll(targetParams["startPort"].c_str());
|
||||||
|
}
|
||||||
|
// Add meta tracks to sdpState
|
||||||
|
initTracks(port, targetIP);
|
||||||
|
myConn.SendNow(generateSDP(targetIP, streamName));
|
||||||
|
INFO_MSG("Pushing %zu tracks towards target address '%s'.", sdpState.tracks.size(), targetIP.c_str());
|
||||||
|
}
|
||||||
|
sentHeader = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Generates an SDP file which clients can use to connect to the stream
|
||||||
|
/// initTracks should be called before this function is called, to make sure we have the right ports
|
||||||
|
/// \param targetAddress: the (multicast supported) target, where we will push the stream towards
|
||||||
|
/// \param streamName: the name of the stream we are serving
|
||||||
|
std::string OutSDP::generateSDP(std::string targetAddress, std::string streamName){
|
||||||
|
prevRTCP = Util::bootSecs();
|
||||||
|
std::stringstream sdpAsString;
|
||||||
|
sdpAsString.precision(3);
|
||||||
|
// Add session description & time description
|
||||||
|
sdpAsString << std::fixed <<
|
||||||
|
"v=0\r\n"
|
||||||
|
"o=- " << Util::getMS() << " 1 IN IP4 " << targetAddress << "\r\n"
|
||||||
|
"s=" << streamName << "\r\n"
|
||||||
|
"i=" << streamName << "\r\n"
|
||||||
|
"c=IN IP4 " << targetAddress << "\r\n"
|
||||||
|
"t=0 0\r\n"
|
||||||
|
"a=tool:" APPIDENT "\r\n"
|
||||||
|
"a=recvonly\r\n"
|
||||||
|
"a=type:broadcast\r\n";
|
||||||
|
// Add Media description
|
||||||
|
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); ++it){
|
||||||
|
DONTEVEN_MSG("Track '%zu' should be pushing data towards UDP port '%" PRIu32 "'", it->first, sdpState.tracks[it->first].portA);
|
||||||
|
sdpAsString << SDP::mediaDescription(&M, it->first, sdpState.tracks.at(it->first).portA);
|
||||||
|
}
|
||||||
|
return sdpAsString.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutSDP::init(Util::Config *cfg){
|
||||||
|
HTTPOutput::init(cfg);
|
||||||
|
capa["name"] = "SDP";
|
||||||
|
capa["friendly"] = "RTP streams using SDP";
|
||||||
|
capa["desc"] = "Make streams available as a RTP stream, using the SDP";
|
||||||
|
capa["url_rel"] = "/$.sdp";
|
||||||
|
capa["url_match"] = "/$.sdp";
|
||||||
|
capa["codecs"][0u][0u].append("+H264");
|
||||||
|
capa["codecs"][0u][1u].append("+HEVC");
|
||||||
|
capa["codecs"][0u][2u].append("+MPEG2");
|
||||||
|
capa["codecs"][0u][3u].append("+VP8");
|
||||||
|
capa["codecs"][0u][4u].append("+VP9");
|
||||||
|
capa["codecs"][0u][5u].append("+AAC");
|
||||||
|
capa["codecs"][0u][6u].append("+MP3");
|
||||||
|
capa["codecs"][0u][7u].append("+AC3");
|
||||||
|
capa["codecs"][0u][8u].append("+ALAW");
|
||||||
|
capa["codecs"][0u][9u].append("+ULAW");
|
||||||
|
capa["codecs"][0u][10u].append("+PCM");
|
||||||
|
capa["codecs"][0u][11u].append("+opus");
|
||||||
|
capa["codecs"][0u][12u].append("+MP2");
|
||||||
|
|
||||||
|
capa["methods"][0u]["handler"] = "http";
|
||||||
|
capa["methods"][0u]["type"] = "html5/application/sdp";
|
||||||
|
capa["methods"][0u]["url_rel"] = "/$.sdp";
|
||||||
|
capa["methods"][0u]["priority"] = 11;
|
||||||
|
|
||||||
|
capa["push_urls"].append("/*.sdp");
|
||||||
|
|
||||||
|
JSON::Value opt;
|
||||||
|
opt["arg"] = "string";
|
||||||
|
opt["default"] = "";
|
||||||
|
opt["arg_num"] = 1;
|
||||||
|
opt["help"] = "Target filename to store SDP file as, or - for stdout.";
|
||||||
|
cfg->addOption("target", opt);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
OutSDP::~OutSDP(){
|
||||||
|
// Remove written SDP file, if it was set as the output target
|
||||||
|
if (isRecording()){
|
||||||
|
HTTP::URL target(config->getString("target"));
|
||||||
|
if(target.getExt() == "sdp"){
|
||||||
|
INFO_MSG("Removing .SDP file located at '%s'", target.getFilePath().c_str());
|
||||||
|
std::remove(target.getFilePath().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutSDP::sendNext(){
|
||||||
|
char *dataPointer = 0;
|
||||||
|
size_t dataLen = 0;
|
||||||
|
thisPacket.getString("data", dataPointer, dataLen);
|
||||||
|
uint64_t timestamp = thisPacket.getTime();
|
||||||
|
|
||||||
|
void *socket = 0;
|
||||||
|
void (*callBack)(void *, const char *, size_t, uint8_t) = 0;
|
||||||
|
|
||||||
|
// Get data socket and send RTCP
|
||||||
|
if (sdpState.tracks[thisIdx].channel == -1){
|
||||||
|
socket = &sdpState.tracks[thisIdx].data;
|
||||||
|
callBack = sendUDP;
|
||||||
|
if (Util::bootSecs() != sdpState.tracks[thisIdx].rtcpSent){
|
||||||
|
sdpState.tracks[thisIdx].pack.setTimestamp(timestamp * SDP::getMultiplier(&M, thisIdx));
|
||||||
|
sdpState.tracks[thisIdx].rtcpSent = Util::bootSecs();
|
||||||
|
sdpState.tracks[thisIdx].pack.sendRTCP_SR(&sdpState.tracks[thisIdx].rtcp, sendUDP);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
FAIL_MSG("RTP SDP output does not support TCP. No data will be sent to the target address");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint64_t offset = thisPacket.getInt("offset");
|
||||||
|
sdpState.tracks[thisIdx].pack.setTimestamp((timestamp + offset) * SDP::getMultiplier(&M, thisIdx));
|
||||||
|
sdpState.tracks[thisIdx].pack.sendData(socket, callBack, dataPointer, dataLen,
|
||||||
|
sdpState.tracks[thisIdx].channel, meta.getCodec(thisIdx));
|
||||||
|
|
||||||
|
// Update last RTCP received variable
|
||||||
|
if (exitOnNoRTCP){
|
||||||
|
checkForRTCP(thisIdx);
|
||||||
|
// Exit if no RTCP packets received for 30 seconds
|
||||||
|
if (Util::bootSecs() - prevRTCP > 30){
|
||||||
|
parseData = false;
|
||||||
|
wantRequest = true;
|
||||||
|
WARN_MSG("Shutting down because we have not received RTCP receiver reports for 30 seconds.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads RTCP packets sent back by viewers
|
||||||
|
void OutSDP::checkForRTCP(uint64_t thisIdx){
|
||||||
|
Socket::UDPConnection &socket = sdpState.tracks[thisIdx].rtcp;
|
||||||
|
while (socket.Receive()){
|
||||||
|
prevRTCP = Util::bootSecs();
|
||||||
|
INFO_MSG("received RTCP packet '%u'", prevRTCP);
|
||||||
|
if (myConn){
|
||||||
|
myConn.addDown(sdpState.tracks[thisIdx].rtcp.data.size());
|
||||||
|
};
|
||||||
|
RTP::Packet pack(sdpState.tracks[thisIdx].rtcp.data, sdpState.tracks[thisIdx].rtcp.data.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutSDP::onHTTP(){
|
||||||
|
std::string targetIP;
|
||||||
|
uint32_t port;
|
||||||
|
|
||||||
|
// Init latest RTCP packet time on current time
|
||||||
|
prevRTCP = Util::bootSecs();
|
||||||
|
if (myConn.getHost().size()){
|
||||||
|
targetIP = myConn.getHost();
|
||||||
|
} else {
|
||||||
|
targetIP = "234.234.234.234";
|
||||||
|
}
|
||||||
|
// If not set, default to random ports we can bind
|
||||||
|
if (targetParams["startPort"].size()){
|
||||||
|
port = atoll(targetParams["startPort"].c_str());
|
||||||
|
} else {
|
||||||
|
port = 0;
|
||||||
|
}
|
||||||
|
// When we start an RTP stream via a HTTP request, close it when we no longer receive RTCP packets
|
||||||
|
exitOnNoRTCP = true;
|
||||||
|
|
||||||
|
// Add meta tracks to sdpState
|
||||||
|
initTracks(port, targetIP);
|
||||||
|
INFO_MSG("Pushing %zu tracks towards target address '%s')", sdpState.tracks.size(), targetIP.c_str());
|
||||||
|
|
||||||
|
// Send SDP file back
|
||||||
|
H.setCORSHeaders();
|
||||||
|
H.SetBody(generateSDP(targetIP, streamName));
|
||||||
|
H.SendResponse("200", "OK", myConn);
|
||||||
|
H.CleanPreserveHeaders();
|
||||||
|
// No more requests needed. Keep alive until we are no longer receiving RTCP packets
|
||||||
|
parseData = true;
|
||||||
|
wantRequest = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disconnects the user
|
||||||
|
bool OutSDP::onFinish(){
|
||||||
|
INFO_MSG("Shutting down...");
|
||||||
|
if (myConn){myConn.close();}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}// namespace Mist
|
34
src/output/output_sdp.h
Normal file
34
src/output/output_sdp.h
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#include "output_http.h"
|
||||||
|
#include <mist/h264.h>
|
||||||
|
#include <mist/http_parser.h>
|
||||||
|
#include <mist/rtp.h>
|
||||||
|
#include <mist/sdp.h>
|
||||||
|
#include <mist/socket.h>
|
||||||
|
#include <mist/url.h>
|
||||||
|
|
||||||
|
namespace Mist{
|
||||||
|
class OutSDP : public HTTPOutput{
|
||||||
|
public:
|
||||||
|
OutSDP(Socket::Connection &conn);
|
||||||
|
~OutSDP();
|
||||||
|
static void init(Util::Config *cfg);
|
||||||
|
void onHTTP();
|
||||||
|
void sendNext();
|
||||||
|
void sendHeader();
|
||||||
|
bool onFinish();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void initTracks(uint32_t & port, std::string targetIP);
|
||||||
|
void checkForRTCP(uint64_t thisIdx);
|
||||||
|
std::string generateSDP(std::string targetAddress, std::string streamName);
|
||||||
|
SDP::State sdpState;
|
||||||
|
uint32_t prevRTCP;
|
||||||
|
bool exitOnNoRTCP;
|
||||||
|
bool isFileTarget(){
|
||||||
|
INFO_MSG("Checking file target! %s", isRecording()?"yes":"no");
|
||||||
|
return isRecording();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}// namespace Mist
|
||||||
|
|
||||||
|
typedef Mist::OutSDP mistOut;
|
Loading…
Add table
Reference in a new issue