Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docs/CONFIGURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -674,15 +674,16 @@ This plugin does not, by itself, stream audio to any online services. Because i

| Key | Required | Default Value | Type | Description |
| --------- | :------: | ------------- | -------------------- | ------------------------------------------------------------ |
| address | ✓ | | string | IP address to send this audio stream to. Use "127.0.0.1" to send to the same computer that trunk-recorder is running on. |
| port | ✓ | | number | UDP or TCP port that this stream will send audio to. |
| url | | | string | URL specifying the protocol, host, and port for this stream, e.g. `udp://hostname.tld:8600` or `tcp://192.168.1.10:9000`. When set, `address` and `port` are not required, and `useTCP` is ignored — the protocol is determined by the URL scheme. Supports both hostnames and literal IP addresses. |
| address | | | string | **Deprecated.** Use `url` instead. IP address or hostname to send this audio stream to. |
| port | | | number | **Deprecated.** Use `url` instead. UDP or TCP port that this stream will send audio to. |
| TGID | ✓ | | number | Audio from this Talkgroup ID will be sent on this stream. Set to 0 to stream all recorded talkgroups. |
| sendJSON | | false | **true** / **false** | When set to true, JSON metadata will be prepended to the audio data each time a packet is sent. JSON fields are talkgroup, patched_talkgroups, src, src_tag, freq, audio_sample_rate, short_name, event (set to "audio"). The length of the JSON metadata is prepended to the metadata in long integer format (4 bytes, little endian). If this is set to **true**, the sendTGID field will be ignored. |
| sendCallStart | | false | **true** / **false** | Only used if sendJSON is set to **true**. When set to true, a JSON message will be sent at the start of each call that includes the following JSON fields: talkgroup, talkgroup_tag, patched_talkgroups, patched_talkgroup_tags, src, src_tag, freq, short_name, event (set to "call_start"). The length of the JSON metadata is prepended to the metadata in long integer format (4 bytes, little endian).
| sendCallEnd | | false | **true** / **false** | Only used if sendJSON is set to **true**. When set to true, a JSON message will be sent at the end of each call that includes the following JSON fields: talkgroup, patched_talkgroups, freq, short_name, event (set to "call_end"). The length of the JSON metadata is prepended to the metadata in long integer format (4 bytes, little endian).
| sendTGID | | false | **true** / **false** | Deprecated. Recommend using sendJSON for metadata instead. If sendJSON is set to true, this setting will be ignored. When set to true, the TGID will be prepended in long integer format (4 bytes, little endian) to the audio data each time a packet is sent. |
| shortName | | | string | shortName of the System that audio should be streamed for. This should match the shortName of a system that is defined in the main section of the config file. When omitted, all Systems will be streamed to the address and port configured. If TGIDs from Systems overlap, JSON metadata should be used to prevent interleaved audio for talkgroups from different Systems with the same TGID.
| useTCP | | false | **true** / **false** | When set to true, TCP will be used instead of UDP.
| useTCP | | false | **true** / **false** | **Deprecated.** Use `url` instead. When set to true, TCP will be used instead of UDP. Ignored if `url` is set — the protocol is determined by the URL scheme instead.

###### Plugin Object Example #1:
This example will stream audio from talkgroup 58914 on system "CountyTrunked" to the local machine on UDP port 9123.
Expand Down
7 changes: 4 additions & 3 deletions docs/Plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,13 @@ This plugin does not, by itself, stream audio to any online services. Because i

| Key | Required | Default Value | Type | Description |
| --------- | :------: | ------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| address | ✓ | | string | IP address to send this audio stream to. Use "127.0.0.1" to send to the same computer that trunk-recorder is running on. |
| port | ✓ | | number | UDP or TCP port that this stream will send audio to. |
| url | | | string | URL specifying the protocol, host, and port for this stream, e.g. `udp://hostname.tld:8600` or `tcp://192.168.1.10:9000`. When set, `address` and `port` are not required, and `useTCP` is ignored — the protocol is determined by the URL scheme. Supports both hostnames and literal IP addresses. |
| address | | | string | **Deprecated.** Use `url` instead. IP address or hostname to send this audio stream to. |
| port | | | number | **Deprecated.** Use `url` instead. UDP or TCP port that this stream will send audio to. |
| TGID | ✓ | | number | Audio from this Talkgroup ID will be sent on this stream. Set to 0 to stream all recorded talkgroups. |
| sendTGID | | false | **true** / **false** | When set to true, the TGID will be prepended in long integer format (4 bytes, little endian) to the audio data each time a packet is sent. |
| shortName | | | string | shortName of the System that audio should be streamed for. This should match the shortName of a system that is defined in the main section of the config file. When omitted, all Systems will be streamed to the address and port configured. If TGIDs from Systems overlap, each system must be sent to a different port to prevent interleaved audio for talkgroups from different Systems with the same TGID. |
| useTCP | | false | **true** / **false** | When set to true, TCP will be used instead of UDP. |
| useTCP | | false | **true** / **false** | **Deprecated.** Use `url` instead. When set to true, TCP will be used instead of UDP. Ignored if `url` is set — the protocol is determined by the URL scheme instead. |

###### Plugin Object Example #1:
This example will stream audio from talkgroup 58914 on system "CountyTrunked" to the local machine on UDP port 9123.
Expand Down
98 changes: 83 additions & 15 deletions plugins/simplestream/simplestream.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ using namespace boost::asio;

typedef struct plugin_t plugin_t;
typedef struct stream_t stream_t;
std::vector<stream_t> streams;
io_service my_tcp_io_service;
long max_tcp_index = 0;

struct plugin_t {
Config* config;
Expand All @@ -35,8 +32,11 @@ struct stream_t {
class Simple_Stream : public Plugin_Api {
typedef boost::asio::io_service io_service;
io_service my_io_service;
io_service my_tcp_io_service;
ip::udp::endpoint remote_endpoint;
ip::udp::socket my_socket{my_io_service};
std::vector<stream_t> streams;
long max_tcp_index = 0;
public:

Simple_Stream(){
Expand All @@ -47,16 +47,52 @@ class Simple_Stream : public Plugin_Api {
for (json element : config_data["streams"]) {
stream_t stream;
stream.TGID = element["TGID"];
stream.address = element["address"];
stream.port = element["port"];
stream.remote_endpoint = ip::udp::endpoint(ip::address::from_string(stream.address), stream.port);

if (element.contains("url")) {
// Parse url field: udp://hostname:port or tcp://hostname:port
std::string url = element["url"];
if (url.substr(0, 6) == "udp://") {
stream.tcp = false;
url = url.substr(6);
} else if (url.substr(0, 6) == "tcp://") {
stream.tcp = true;
url = url.substr(6);
} else {
BOOST_LOG_TRIVIAL(error) << "SimpleStream: invalid URL scheme in \"" << element["url"] << "\", expected udp:// or tcp://";
continue;
}
size_t colon = url.rfind(':');
if (colon == std::string::npos) {
BOOST_LOG_TRIVIAL(error) << "SimpleStream: missing port in URL \"" << element["url"].get<std::string>() << "\"";
continue;
}
stream.address = url.substr(0, colon);
stream.port = static_cast<uint32_t>(std::stoul(url.substr(colon + 1)));
} else {
BOOST_LOG_TRIVIAL(warning) << "SimpleStream: address/port/useTCP are deprecated, please use the url field instead (e.g. \"url\": \"udp://hostname:port\")";
stream.address = element["address"];
stream.port = element["port"];
stream.tcp = element.value("useTCP", false);
}

if (!stream.tcp) {
ip::udp::resolver udp_resolver(my_io_service);
ip::udp::resolver::query udp_query(stream.address, std::to_string(stream.port));
boost::system::error_code ec;
ip::udp::resolver::iterator iter = udp_resolver.resolve(udp_query, ec);
if (ec) {
BOOST_LOG_TRIVIAL(error) << "SimpleStream: failed to resolve UDP address " << stream.address << ": " << ec.message();
continue;
}
stream.remote_endpoint = iter->endpoint();
}

stream.sendTGID = element.value("sendTGID",false);
stream.sendJSON = element.value("sendJSON",false);
stream.sendCallStart = element.value("sendCallStart",false);
stream.sendCallEnd = element.value("sendCallEnd",false);
stream.tcp = element.value("useTCP",false);
stream.short_name = element.value("shortName", "");
BOOST_LOG_TRIVIAL(info) << "simplestreamer will stream audio from TGID " <<stream.TGID << " on System " <<stream.short_name << " to " << stream.address <<" on port " << stream.port << " tcp is "<<stream.tcp;
BOOST_LOG_TRIVIAL(info) << "SimpleStream will stream audio from TGID " <<stream.TGID << " on System " <<stream.short_name << " to " << stream.address <<" on port " << stream.port << " tcp is "<<stream.tcp;
streams.push_back(stream);
}
return 0;
Expand Down Expand Up @@ -93,7 +129,7 @@ class Simple_Stream : public Plugin_Api {
int recorder_id = local_recorder.get_num();
long wav_hz = local_recorder.get_wav_hz();
boost::system::error_code error;
BOOST_FOREACH (auto stream, streams){
BOOST_FOREACH (const auto &stream, streams){
if (0==stream.short_name.compare(call_short_name) || (0==stream.short_name.compare(""))){ //Check if shortName matches or is not specified
if (patched_talkgroups.size() == 0){
patched_talkgroups.push_back(call_tgid); //call_info.talkgroup may be negative - we cast stream.TGID to signed for comparison
Expand Down Expand Up @@ -129,10 +165,17 @@ class Simple_Stream : public Plugin_Api {
}
send_buffer.push_back(buffer(samples, sampleCount*2));
if(stream.tcp == true){
stream.tcp_socket->send(send_buffer);
try {
stream.tcp_socket->send(send_buffer);
} catch (const boost::system::system_error &e) {
BOOST_LOG_TRIVIAL(error) << "SimpleStream TCP send failed: " << e.what();
}
}
else{
my_socket.send_to(send_buffer, stream.remote_endpoint, 0, error);
if (error) {
BOOST_LOG_TRIVIAL(warning) << "SimpleStream UDP send failed: " << error.message();
}
}
}
}
Expand Down Expand Up @@ -165,7 +208,7 @@ class Simple_Stream : public Plugin_Api {
}
}

BOOST_FOREACH (auto stream, streams){
BOOST_FOREACH (const auto &stream, streams){
if (stream.sendJSON == true && stream.sendCallStart == true){
if (0==stream.short_name.compare(call_short_name) || (0==stream.short_name.compare(""))){ //Check if shortName matches or is not specified
if (patched_talkgroups.size() == 0){
Expand Down Expand Up @@ -201,10 +244,17 @@ class Simple_Stream : public Plugin_Api {
send_buffer.push_back(buffer(json_string)); //prepend json data
}
if(stream.tcp == true){
stream.tcp_socket->send(send_buffer);
try {
stream.tcp_socket->send(send_buffer);
} catch (const boost::system::system_error &e) {
BOOST_LOG_TRIVIAL(error) << "SimpleStream TCP send failed: " << e.what();
}
}
else{
my_socket.send_to(send_buffer, stream.remote_endpoint, 0, error);
if (error) {
BOOST_LOG_TRIVIAL(warning) << "SimpleStream UDP send failed: " << error.message();
}
}
}
}
Expand All @@ -216,7 +266,7 @@ class Simple_Stream : public Plugin_Api {

int call_end(Call_Data_t call_info) {
boost::system::error_code error;
BOOST_FOREACH (auto stream, streams){
BOOST_FOREACH (const auto &stream, streams){
if (stream.sendJSON == true && stream.sendCallEnd == true){
if (0==stream.short_name.compare(call_info.short_name) || (0==stream.short_name.compare(""))){ //Check if shortName matches or is not specified
std::vector<long> patched_talkgroups;
Expand Down Expand Up @@ -249,10 +299,17 @@ class Simple_Stream : public Plugin_Api {
send_buffer.push_back(buffer(json_string)); //prepend json data
}
if(stream.tcp == true){
stream.tcp_socket->send(send_buffer);
try {
stream.tcp_socket->send(send_buffer);
} catch (const boost::system::system_error &e) {
BOOST_LOG_TRIVIAL(error) << "SimpleStream TCP send failed: " << e.what();
}
}
else{
my_socket.send_to(send_buffer, stream.remote_endpoint, 0, error);
if (error) {
BOOST_LOG_TRIVIAL(warning) << "SimpleStream UDP send failed: " << error.message();
}
}
}
}
Expand All @@ -267,7 +324,18 @@ class Simple_Stream : public Plugin_Api {
if (stream.tcp == true){
ip::tcp::socket *my_tcp_socket = new ip::tcp::socket{my_tcp_io_service};
stream.tcp_socket = my_tcp_socket;
stream.tcp_socket->connect(ip::tcp::endpoint( boost::asio::ip::address::from_string(stream.address), stream.port ));
ip::tcp::resolver tcp_resolver(my_tcp_io_service);
ip::tcp::resolver::query tcp_query(stream.address, std::to_string(stream.port));
boost::system::error_code ec;
ip::tcp::resolver::iterator iter = tcp_resolver.resolve(tcp_query, ec);
if (ec) {
BOOST_LOG_TRIVIAL(error) << "SimpleStream: failed to resolve TCP address " << stream.address << ": " << ec.message();
continue;
}
stream.tcp_socket->connect(iter->endpoint(), ec);
if (ec) {
BOOST_LOG_TRIVIAL(error) << "SimpleStream: TCP connect failed to " << stream.address << ":" << stream.port << ": " << ec.message();
}
}
}
my_socket.open(ip::udp::v4());
Expand Down
Loading