Skip to content

Commit

Permalink
Merge pull request #649 from aaknitt/sstcp
Browse files Browse the repository at this point in the history
Add TCP support to simplestream plugin
  • Loading branch information
robotastic authored Mar 20, 2022
2 parents 79b3773 + 6a9bcee commit bfebb6a
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 13 deletions.
33 changes: 29 additions & 4 deletions docs/CONFIGURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ This plugin makes it easy to connect Trunk Recorder with [Rdio Scanner](https://
**Name:** simplestream
**Library:** libsimplestream.so

This plugin streams uncompressed audio (16 bit Int, 8 kHz, mono) to UDP ports in real time as it is being recorded by trunk-recorder. It can be configured to stream audio from all talkgroups and systems being recorded or only specified talkgroups and systems. TGID information can be prepended to the audio data to allow the receiving program to take action based on the TGID. Audio from different Systems should be streamed to different UDP ports to prevent crosstalk and interleaved audio from talkgroups with the same TGID on different systems.
This plugin streams uncompressed audio (16 bit Int, 8 kHz, mono) to UDP or TCP ports in real time as it is being recorded by trunk-recorder. It can be configured to stream audio from all talkgroups and systems being recorded or only specified talkgroups and systems. TGID information can be prepended to the audio data to allow the receiving program to take action based on the TGID. Audio from different Systems should be streamed to different UDP/TCP ports to prevent crosstalk and interleaved audio from talkgroups with the same TGID on different systems.

This plugin does not, by itself, stream audio to any online services. Because it sends uncompressed PCM audio, it is not bandwidth efficient and is intended mostly to send audio to other programs running on the same computer as trunk-recorder or to other computers on the LAN. The programs receiving PCM audio from this plugin may play it on speakers, compress it and stream it to an online service, etc.

Expand All @@ -263,10 +263,11 @@ 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 port that this stream will send audio to. |
| port || | number | 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 | boolean | When set to true, the TGID will be prepended in long integer format (4 bytes, little endian) to the audio data each time a UDP 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 UDP port to prevent interleaved audio for talkgroups from different Systems with the same TGID.
| sendTGID | | false | boolean | 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 |boolean | When set to true, TCP will be used instead of UDP.

###### 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 Expand Up @@ -336,7 +337,31 @@ This example will stream audio from all talkgroups being recorded on System Coun
"shortName":"CountyTrunked"}
}
```
##### Example - Sending Audio to pulseaudio
pulseaudio is the default sound system on many Linux computers, including the Raspberry Pi. If configured to do so, pulseaudio can accept raw audio via TCP connection using the module-simple-protocol-tcp module. Each TCP connection will show up as a different "application" in the pavucontrol volume mixer.

An example command to set up pulseaudio to receive 8 kHz audio (digital audio) from simplestream on TCP port 9125:
```
pacmd load-module module-simple-protocol-tcp sink=1 playback=true port=9125 format=s16be rate=8000 channels=1
```
An example command to set up pulseaudio to receive 16 kHz audio (analog audio) from simplestream on TCP port 9125:
```
pacmd load-module module-simple-protocol-tcp sink=1 playback=true port=9125 format=s16be rate=16000 channels=1
```
The matching simplestream config to send audio from talkgroup 58918 to TCP port 9125 would then be something like this:
```yaml
{
"name":"simplestream",
"library":"libsimplestream.so",
"streams":[{
"TGID":58918,
"address":"127.0.0.1",
"port":9125,
"sendTGID":true,
"shortName":"CountyTrunked",
"useTCP":true}
}
```

## talkgroupsFile

Expand Down
47 changes: 38 additions & 9 deletions plugins/simplestream/simplestream.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,28 @@ 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;
};

struct stream_t {
unsigned long TGID;
long tcp_index;
std::string address;
std::string short_name;
long port;
ip::udp::endpoint remote_endpoint;
ip::tcp::socket *tcp_socket;
bool sendTGID = false;
bool tcp = false;
};

class Simple_Stream : public Plugin_Api {
typedef boost::asio::io_service io_service;

io_service my_io_service;
ip::udp::endpoint remote_endpoint;
ip::udp::socket my_socket{my_io_service};
Expand All @@ -43,15 +48,17 @@ class Simple_Stream : public Plugin_Api {
stream.port = node.second.get<long>("port");
stream.remote_endpoint = ip::udp::endpoint(ip::address::from_string(stream.address), stream.port);
stream.sendTGID = node.second.get<bool>("sendTGID",false);
stream.tcp = node.second.get<bool>("useTCP",false);
stream.short_name = node.second.get<std::string>("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;
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;
streams.push_back(stream);
}
return 0;
}

int audio_stream(Call *call, Recorder *recorder, int16_t *samples, int sampleCount){
int recorder_id = recorder->get_num();
boost::system::error_code error;
BOOST_FOREACH (auto& stream, streams){
if (0==stream.short_name.compare(call->get_system()->get_short_name()) || (0==stream.short_name.compare(""))){ //Check if shortName matches or is not specified
std::vector<unsigned long> patched_talkgroups = call->get_system()->get_talkgroup_patch(call->get_talkgroup());
Expand All @@ -60,19 +67,28 @@ class Simple_Stream : public Plugin_Api {
}
BOOST_FOREACH (auto& TGID, patched_talkgroups){
if ((TGID==stream.TGID || stream.TGID==0)){ //setting TGID to 0 in the config file will stream everything
boost::system::error_code err;
BOOST_LOG_TRIVIAL(debug) << "got " <<sampleCount <<" samples - " <<sampleCount*2<<" bytes from recorder "<<recorder_id<<" for TGID "<<TGID;
if (stream.sendTGID==true){
//prepend 4 byte long tgid to the audio data
boost::array<mutable_buffer, 2> buf1 = {
buffer(&TGID,4),
buffer(samples, sampleCount*2)
};
my_socket.send_to(buf1, stream.remote_endpoint, 0, err);
if(stream.tcp==true){
stream.tcp_socket->send(buf1);
}
else{
my_socket.send_to(buf1, stream.remote_endpoint, 0, error);
}
}
else{
//just send the audio data
my_socket.send_to(buffer(samples, sampleCount*2), stream.remote_endpoint, 0, err);
if(stream.tcp == true){
stream.tcp_socket->send(buffer(samples, sampleCount*2));
}
else{
my_socket.send_to(buffer(samples, sampleCount*2), stream.remote_endpoint, 0, error);
}
}
}
}
Expand All @@ -82,13 +98,26 @@ class Simple_Stream : public Plugin_Api {
}

int start(){
my_socket.open(ip::udp::v4());
return 0;
BOOST_FOREACH (auto& stream, streams){
if (stream.tcp == true){
io_service my_tcp_io_service;
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 ));
}
}
my_socket.open(ip::udp::v4());
return 0;
}

int stop(){
my_socket.close();
return 0;
BOOST_FOREACH (auto& stream, streams){
if (stream.tcp == true){
stream.tcp_socket->close();
}
}
my_socket.close();
return 0;
}

static boost::shared_ptr<Simple_Stream> create() {
Expand Down

0 comments on commit bfebb6a

Please sign in to comment.