root / src / gst / engine / remoteConfig.cpp @ 0b392805
History | View | Annotate | Download (10.1 kB)
| 1 |
/* remoteConfig.cpp
|
|---|---|
| 2 |
* Copyright (C) 2008-2009 Société des arts technologiques (SAT) |
| 3 |
* http://www.sat.qc.ca |
| 4 |
* All rights reserved. |
| 5 |
* |
| 6 |
* This file is part of [propulse]ART. |
| 7 |
* |
| 8 |
* [propulse]ART is free software: you can redistribute it and/or modify |
| 9 |
* it under the terms of the GNU General Public License as published by |
| 10 |
* the Free Software Foundation, either version 3 of the License, or |
| 11 |
* (at your option) any later version. |
| 12 |
* |
| 13 |
* [propulse]ART is distributed in the hope that it will be useful, |
| 14 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 |
* GNU General Public License for more details. |
| 17 |
* |
| 18 |
* You should have received a copy of the GNU General Public License |
| 19 |
* along with [propulse]ART. If not, see <http://www.gnu.org/licenses/>. |
| 20 |
* |
| 21 |
*/ |
| 22 |
|
| 23 |
#include "util.h" |
| 24 |
|
| 25 |
#include <algorithm> |
| 26 |
#include <boost/assign.hpp> |
| 27 |
#include <gst/gst.h> |
| 28 |
#include "pipeline.h" |
| 29 |
#include "remoteConfig.h" |
| 30 |
#include "tcp/asio.h" |
| 31 |
#include "mapMsg.h" |
| 32 |
#include "codec.h" |
| 33 |
|
| 34 |
const int RemoteConfig::PORT_MIN = 1024; |
| 35 |
const int RemoteConfig::PORT_MAX = 65000; |
| 36 |
|
| 37 |
|
| 38 |
std::set<int> RemoteConfig::usedPorts_;
|
| 39 |
|
| 40 |
RemoteConfig::RemoteConfig(MapMsg &msg, int msgId__) :
|
| 41 |
codec_(msg["codec"]), remoteHost_(msg["address"]), port_(msg["port"]), msgId_(msgId__) |
| 42 |
{}
|
| 43 |
|
| 44 |
|
| 45 |
// Can't be called from destructor, must be called by this object's owner/client,
|
| 46 |
// as sometime this object is copied and we don't want the ports destroyed prematurely
|
| 47 |
void RemoteConfig::cleanupPorts() const |
| 48 |
{
|
| 49 |
usedPorts_.erase(capsPort()); |
| 50 |
usedPorts_.erase(rtcpSecondPort()); |
| 51 |
usedPorts_.erase(rtcpFirstPort()); |
| 52 |
usedPorts_.erase(port()); |
| 53 |
} |
| 54 |
|
| 55 |
|
| 56 |
/// Make sure there are no port clashes (at least as far as this process can tell)
|
| 57 |
void RemoteConfig::checkPorts() const |
| 58 |
{
|
| 59 |
if (port_ < PORT_MIN || port_ > PORT_MAX)
|
| 60 |
THROW_ERROR("Invalid port " << port_ << ", must be in range [" |
| 61 |
<< PORT_MIN << "," << PORT_MAX << "]"); |
| 62 |
|
| 63 |
if (usedPorts_.find(port()) != usedPorts_.end())
|
| 64 |
THROW_ERROR("Invalid port " << port() << ", already in use"); |
| 65 |
|
| 66 |
if (usedPorts_.find(rtcpFirstPort()) != usedPorts_.end())
|
| 67 |
THROW_ERROR("Invalid port " << port() << ", its rtcp port " << rtcpFirstPort() << " is already in use"); |
| 68 |
|
| 69 |
if (usedPorts_.find(rtcpSecondPort()) != usedPorts_.end())
|
| 70 |
THROW_ERROR("Invalid port " << port() << ", its rtcp port " << rtcpSecondPort() << " is already in use"); |
| 71 |
|
| 72 |
if (usedPorts_.find(capsPort()) != usedPorts_.end())
|
| 73 |
THROW_ERROR("Invalid port " << port() << ", its caps port " << capsPort() << " is already in use"); |
| 74 |
|
| 75 |
// add our ports now that we know they're available
|
| 76 |
usedPorts_.insert(port()); |
| 77 |
usedPorts_.insert(rtcpFirstPort()); |
| 78 |
usedPorts_.insert(rtcpSecondPort()); |
| 79 |
usedPorts_.insert(capsPort()); |
| 80 |
} |
| 81 |
|
| 82 |
|
| 83 |
SenderConfig::SenderConfig(Pipeline &pipeline, MapMsg &msg, int msgId__) :
|
| 84 |
RemoteConfig(msg, msgId__), |
| 85 |
BusMsgHandler(pipeline), |
| 86 |
message_(""),
|
| 87 |
capsOutOfBand_(false) // this will be determined later |
| 88 |
{}
|
| 89 |
|
| 90 |
|
| 91 |
VideoEncoder * SenderConfig::createVideoEncoder(Pipeline &pipeline, MapMsg &settings) const
|
| 92 |
{
|
| 93 |
if (codec_.empty())
|
| 94 |
THROW_ERROR("Can't make encoder without codec being specified.");
|
| 95 |
|
| 96 |
if (codec_ == "h264") |
| 97 |
return new H264Encoder(pipeline, settings); |
| 98 |
else if (codec_ == "h263") |
| 99 |
return new H263Encoder(pipeline, settings); // set caps from here? |
| 100 |
else if (codec_ == "mpeg4") |
| 101 |
return new Mpeg4Encoder(pipeline, settings); |
| 102 |
else if (codec_ == "theora") |
| 103 |
return new TheoraEncoder(pipeline, settings); |
| 104 |
else
|
| 105 |
{
|
| 106 |
THROW_ERROR(codec_ << " is an invalid codec!");
|
| 107 |
return 0; |
| 108 |
} |
| 109 |
LOG_DEBUG("Video encoder " << codec_ << " built"); |
| 110 |
} |
| 111 |
|
| 112 |
|
| 113 |
Encoder * SenderConfig::createAudioEncoder(Pipeline &pipeline) const
|
| 114 |
{
|
| 115 |
if (codec_.empty())
|
| 116 |
THROW_ERROR("Can't make encoder without codec being specified.");
|
| 117 |
|
| 118 |
if (codec_ == "vorbis") |
| 119 |
return new VorbisEncoder(pipeline); |
| 120 |
else if (codec_ == "raw") |
| 121 |
return new RawEncoder(pipeline); |
| 122 |
else if (codec_ == "mp3") |
| 123 |
return new LameEncoder(pipeline); |
| 124 |
else
|
| 125 |
{
|
| 126 |
THROW_ERROR(codec_ << " is an invalid codec!");
|
| 127 |
return 0; |
| 128 |
} |
| 129 |
LOG_DEBUG("Audio encoder " << codec_ << " built"); |
| 130 |
} |
| 131 |
|
| 132 |
|
| 133 |
gboolean SenderConfig::sendMessage(gpointer data) |
| 134 |
{
|
| 135 |
const SenderConfig *context = static_cast<const SenderConfig*>(data); |
| 136 |
LOG_DEBUG("\n\n\nSending tcp msg for host "
|
| 137 |
<< context->remoteHost_ << " on port " << context->capsPort()
|
| 138 |
<< " with id " << context->msgId_);
|
| 139 |
|
| 140 |
/// FIXME: everytime a receiver starts, it should ask sender for caps, then the sender can
|
| 141 |
/// send them.
|
| 142 |
if (asio::tcpSendBuffer(context->remoteHost_, context->capsPort(), context->msgId_, context->message_))
|
| 143 |
LOG_INFO("Caps sent successfully");
|
| 144 |
return TRUE; // try again later, in case we have a new receiver |
| 145 |
} |
| 146 |
|
| 147 |
|
| 148 |
/**
|
| 149 |
* The new caps message is posted on the bus by the src pad of our udpsink, |
| 150 |
* received by this audiosender, and sent to our other host if needed. */ |
| 151 |
bool SenderConfig::handleBusMsg(GstMessage *msg)
|
| 152 |
{
|
| 153 |
const GstStructure *s = gst_message_get_structure(msg);
|
| 154 |
if (s != NULL and gst_structure_has_name(s, "caps-changed")) |
| 155 |
{
|
| 156 |
// this is our msg
|
| 157 |
const gchar *newCapsStr = gst_structure_get_string(s, "caps"); |
| 158 |
tassert(newCapsStr); |
| 159 |
std::string str(newCapsStr);
|
| 160 |
|
| 161 |
GstStructure *structure = gst_caps_get_structure(gst_caps_from_string(str.c_str()), 0);
|
| 162 |
const GValue *encodingStr = gst_structure_get_value(structure, "encoding-name"); |
| 163 |
std::string encodingName(g_value_get_string(encodingStr));
|
| 164 |
|
| 165 |
if (!capsMatchCodec(encodingName, codec()))
|
| 166 |
return false; // not our caps, ignore it |
| 167 |
else if (capsOutOfBand_) |
| 168 |
{
|
| 169 |
LOG_DEBUG("Sending caps for codec " << codec());
|
| 170 |
|
| 171 |
message_ = std::string(newCapsStr);
|
| 172 |
enum {MESSAGE_SEND_TIMEOUT = 1000}; // send caps once every second |
| 173 |
g_timeout_add(MESSAGE_SEND_TIMEOUT, static_cast<GSourceFunc>(SenderConfig::sendMessage),
|
| 174 |
static_cast<gpointer>(this)); |
| 175 |
return true; |
| 176 |
} |
| 177 |
else
|
| 178 |
return true; // was our caps, but we don't need to send caps for it |
| 179 |
} |
| 180 |
|
| 181 |
return false; // this wasn't our msg, someone else should handle it |
| 182 |
} |
| 183 |
|
| 184 |
|
| 185 |
bool ReceiverConfig::isSupportedCodec(const std::string &codec) |
| 186 |
{
|
| 187 |
using namespace boost::assign; |
| 188 |
using std::string; |
| 189 |
|
| 190 |
static const std::vector<string> CODECS = |
| 191 |
list_of<string>("mpeg4")("theora")("vorbis")("raw")("h264")("h263")("mp3"); |
| 192 |
|
| 193 |
bool result = std::find(CODECS.begin(), CODECS.end(), codec) != CODECS.end();
|
| 194 |
return result;
|
| 195 |
} |
| 196 |
|
| 197 |
|
| 198 |
ReceiverConfig::ReceiverConfig(MapMsg &msg, |
| 199 |
const std::string &caps__, |
| 200 |
int msgId__) :
|
| 201 |
RemoteConfig(msg, msgId__), |
| 202 |
multicastInterface_(msg["multicast-interface"]), caps_(caps__),
|
| 203 |
capsOutOfBand_(msg["negotiate-caps"] or caps_ == ""), |
| 204 |
jitterbufferControlEnabled_(msg["enable-controls"])
|
| 205 |
{
|
| 206 |
if (capsOutOfBand_) // couldn't find caps, need them from other host or we've explicitly been told to send caps |
| 207 |
{
|
| 208 |
if (isSupportedCodec(codec_)) // this would fail later but we want to make sure we don't wait with a bogus codec |
| 209 |
{
|
| 210 |
LOG_INFO("Waiting for " << codec_ << " caps from other host"); |
| 211 |
receiveCaps(); // wait for new caps from sender
|
| 212 |
} |
| 213 |
else
|
| 214 |
THROW_ERROR("Codec " << codec_ << " is not supported"); |
| 215 |
} |
| 216 |
} |
| 217 |
|
| 218 |
VideoDecoder * ReceiverConfig::createVideoDecoder(Pipeline &pipeline, bool doDeinterlace) const |
| 219 |
{
|
| 220 |
if (codec_.empty())
|
| 221 |
THROW_ERROR("Can't make decoder without codec being specified.");
|
| 222 |
|
| 223 |
if (codec_ == "h264") |
| 224 |
return new H264Decoder(pipeline, doDeinterlace); |
| 225 |
else if (codec_ == "h263") |
| 226 |
return new H263Decoder(pipeline, doDeinterlace); |
| 227 |
else if (codec_ == "mpeg4") |
| 228 |
return new Mpeg4Decoder(pipeline, doDeinterlace); |
| 229 |
else if (codec_ == "theora") |
| 230 |
return new TheoraDecoder(pipeline, doDeinterlace); |
| 231 |
else
|
| 232 |
{
|
| 233 |
THROW_ERROR(codec_ << " is an invalid codec!");
|
| 234 |
return 0; |
| 235 |
} |
| 236 |
LOG_DEBUG("Video decoder " << codec_ << " built"); |
| 237 |
} |
| 238 |
|
| 239 |
|
| 240 |
Decoder * ReceiverConfig::createAudioDecoder(Pipeline &pipeline) const
|
| 241 |
{
|
| 242 |
if (codec_.empty())
|
| 243 |
THROW_ERROR("Can't make decoder without codec being specified.");
|
| 244 |
|
| 245 |
if (codec_ == "vorbis") |
| 246 |
return new VorbisDecoder(pipeline); |
| 247 |
else if (codec_ == "raw") |
| 248 |
return new RawDecoder(pipeline); |
| 249 |
else if (codec_ == "mp3") |
| 250 |
return new MadDecoder(pipeline); |
| 251 |
else
|
| 252 |
{
|
| 253 |
THROW_ERROR(codec_ << " is an invalid codec!");
|
| 254 |
return 0; |
| 255 |
} |
| 256 |
LOG_DEBUG("Audio decoder " << codec_ << " built"); |
| 257 |
} |
| 258 |
|
| 259 |
|
| 260 |
/// compares internal codec names and RTP-header codec names
|
| 261 |
bool RemoteConfig::capsMatchCodec(const std::string &encodingName, const std::string &codec) |
| 262 |
{
|
| 263 |
return (encodingName == "VORBIS" and codec == "vorbis") |
| 264 |
or (encodingName == "L16" and codec == "raw") |
| 265 |
or (encodingName == "MPA" and codec == "mp3") |
| 266 |
or (encodingName == "MP4V-ES" and codec == "mpeg4") |
| 267 |
or (encodingName == "H264" and codec == "h264") |
| 268 |
or (encodingName == "H263-1998" and codec == "h263") |
| 269 |
or (encodingName == "THEORA" and codec == "theora"); |
| 270 |
} |
| 271 |
|
| 272 |
/// This function makes sure that the caps set on this receiver by a sender, match the codec
|
| 273 |
/// that it expects. If it fails, it is probably due to a mismatch of codecs between sender and
|
| 274 |
/// receiver, or a change in the caps specification for a given codec from gstreamer.
|
| 275 |
// TODO: maybe the whole receiver should be created based on this info, at least up to and including
|
| 276 |
// the decoder?
|
| 277 |
bool ReceiverConfig::capsMatchCodec() const |
| 278 |
{
|
| 279 |
GstStructure *structure = gst_caps_get_structure(gst_caps_from_string(caps_.c_str()), 0);
|
| 280 |
const GValue *str = gst_structure_get_value(structure, "encoding-name"); |
| 281 |
std::string encodingName(g_value_get_string(str));
|
| 282 |
|
| 283 |
return RemoteConfig::capsMatchCodec(encodingName, codec_);
|
| 284 |
} |
| 285 |
|
| 286 |
|
| 287 |
void ReceiverConfig::receiveCaps()
|
| 288 |
{
|
| 289 |
int id;
|
| 290 |
// this blocks
|
| 291 |
std::string msg(asio::tcpGetBuffer(capsPort(), id));
|
| 292 |
//tassert(id == msgId_);
|
| 293 |
caps_ = msg; |
| 294 |
LOG_DEBUG("Received caps " << caps_);
|
| 295 |
} |
| 296 |
|
