Skip to content

Commit

Permalink
Simplestream improvements (#966)
Browse files Browse the repository at this point in the history
* simplestream pass by reference fixes, add call start and end events, metadata improvements

* Update CONFIGURE.md

Update simplestream plugin documentation

* Update CONFIGURE.md

* simplestream bug fix

* copy call info at start of function to mitigate changes while looping
  • Loading branch information
aaknitt authored Aug 6, 2024
1 parent 2d838f7 commit f2b86d8
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 13 deletions.
7 changes: 6 additions & 1 deletion docs/CONFIGURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,9 @@ This plugin does not, by itself, stream audio to any online services. Because i
| 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. |
| 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, src, freq, audio_sample_rate, and short_name. 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.
| 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.
Expand Down Expand Up @@ -456,6 +458,9 @@ The matching simplestream config to send audio from talkgroup 58918 to TCP port
"useTCP":true}
}
```
#### Example - Sending Audio to FFMPEG for compression
Here's an FFMPEG command that takes PCM audio from simplestream via UDP, cleans it up, and outputs ogg/opus to stdout. Note that this will only work if sendTGID and sendJSON are both set to false and only a single talkgroup is fed to ffmpeg over the UDP port, as ffmpeg cannot interpret any metadata.
`ffmpeg -loglevel warning -f s16le -ar 16000 -ac 1 -i udp://localhost:9125 -af:a adeclick -f:a ogg -c:a libopus -frame_duration:a 20 -vbr:a on -b:a 48000 -application:a voip pipe:1`

## talkgroupsFile

Expand Down
139 changes: 127 additions & 12 deletions plugins/simplestream/simplestream.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ std::vector<stream_t> streams;
io_service my_tcp_io_service;
long max_tcp_index = 0;


struct plugin_t {
Config* config;
};
Expand All @@ -28,6 +27,8 @@ struct stream_t {
ip::tcp::socket *tcp_socket;
bool sendTGID = false;
bool sendJSON = false;
bool sendCallStart = false;
bool sendCallEnd = false;
bool tcp = false;
};

Expand All @@ -51,6 +52,8 @@ class Simple_Stream : public Plugin_Api {
stream.remote_endpoint = ip::udp::endpoint(ip::address::from_string(stream.address), stream.port);
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;
Expand All @@ -60,15 +63,24 @@ class Simple_Stream : public Plugin_Api {
}

int audio_stream(Call *call, Recorder *recorder, int16_t *samples, int sampleCount){
int recorder_id = recorder->get_num();
//Call local_call = *call;
System *call_system = call->get_system();
uint32_t call_tgid = call->get_talkgroup();
uint32_t call_src = call->get_current_source_id();
uint32_t call_freq = call->get_freq();
std::string call_short_name = call->get_short_name();
std::string call_src_tag = call_system->find_unit_tag(call_src);
std::vector<unsigned long> patched_talkgroups = call_system->get_talkgroup_patch(call_tgid);

Recorder local_recorder = *recorder;
int recorder_id = local_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());
BOOST_FOREACH (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->get_talkgroup());
patched_talkgroups.push_back(call_tgid);
}
BOOST_FOREACH (auto& TGID, patched_talkgroups){
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_LOG_TRIVIAL(debug) << "got " <<sampleCount <<" samples - " <<sampleCount*2<<" bytes from recorder "<<recorder_id<<" for TGID "<<TGID;
json json_object;
Expand All @@ -77,12 +89,14 @@ class Simple_Stream : public Plugin_Api {
if (stream.sendJSON==true){
//create JSON metadata
json_object = {
{"src", call->get_current_source_id()},
{"src", call_src},
{"src_tag",call_src_tag},
{"talkgroup", TGID},
{"patched_talkgroups",patched_talkgroups},
{"freq", call->get_freq()},
{"short_name", call->get_short_name()},
{"audio_sample_rate",recorder->get_wav_hz()},
{"freq", call_freq},
{"short_name", call_short_name},
{"audio_sample_rate",local_recorder.get_wav_hz()},
{"event","audio"},
};
json_string = json_object.dump();
uint32_t json_length = json_string.length(); //determine length in bytes
Expand All @@ -107,7 +121,108 @@ class Simple_Stream : public Plugin_Api {
}
return 0;
}


int call_start(Call *call){
boost::system::error_code error;
System *call_system = call->get_system();
uint32_t call_tgid = call->get_talkgroup();
uint32_t call_src = call->get_current_source_id();
uint32_t call_freq = call->get_freq();
std::string call_short_name = call->get_short_name();
std::string call_src_tag = call_system->find_unit_tag(call_src);
std::string call_tgid_tag = call->get_talkgroup_tag();
std::vector<unsigned long> patched_talkgroups = call_system->get_talkgroup_patch(call_tgid);

BOOST_FOREACH (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){
patched_talkgroups.push_back(call_tgid);
}
std::vector<std::string> patched_talkgroup_tags;
BOOST_FOREACH (auto TGID, patched_talkgroups){
Talkgroup* this_tg = call_system->find_talkgroup(TGID);
if (this_tg != nullptr) {
patched_talkgroup_tags.push_back(this_tg->alpha_tag);
}
if ((TGID==stream.TGID || stream.TGID==0)){ //setting TGID to 0 in the config file will stream everything
json json_object;
std::string json_string;
std::vector<boost::asio::const_buffer> send_buffer;
if (stream.sendJSON==true){
//create JSON metadata
json_object = {
{"src", call_src},
{"src_tag", call_src_tag},
{"talkgroup", call_tgid},
{"talkgroup_tag",call_tgid_tag},
{"patched_talkgroups",patched_talkgroups},
{"patched_talkgroup_tags",patched_talkgroup_tags},
{"freq", call_freq},
{"short_name", call_short_name},
{"event","call_start"},
};
json_string = json_object.dump();
uint32_t json_length = json_string.length(); //determine length in bytes
send_buffer.push_back(buffer(&json_length,4)); //prepend length of the json data
send_buffer.push_back(buffer(json_string)); //prepend json data
}
if(stream.tcp == true){
stream.tcp_socket->send(send_buffer);
}
else{
my_socket.send_to(send_buffer, stream.remote_endpoint, 0, error);
}
}
}
}
}
}
return 0;
}

int call_end(Call_Data_t call_info) {
boost::system::error_code error;
BOOST_FOREACH (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<unsigned long> patched_talkgroups = call_info.patched_talkgroups;
if (patched_talkgroups.size() == 0){
patched_talkgroups.push_back(call_info.talkgroup);
}
BOOST_FOREACH (auto TGID, patched_talkgroups){
if ((TGID==stream.TGID || stream.TGID==0)){ //setting TGID to 0 in the config file will stream everything
json json_object;
std::string json_string;
std::vector<boost::asio::const_buffer> send_buffer;
if (stream.sendJSON==true){
//create JSON metadata
json_object = {
{"talkgroup", call_info.talkgroup},
{"patched_talkgroups",patched_talkgroups},
{"freq", call_info.freq},
{"short_name", call_info.short_name},
{"event","call_end"},
};
json_string = json_object.dump();
uint32_t json_length = json_string.length(); //determine length in bytes
send_buffer.push_back(buffer(&json_length,4)); //prepend length of the json data
send_buffer.push_back(buffer(json_string)); //prepend json data
}
if(stream.tcp == true){
stream.tcp_socket->send(send_buffer);
}
else{
my_socket.send_to(send_buffer, stream.remote_endpoint, 0, error);
}
}
}
}
}
}
return 0;
}

int start(){
BOOST_FOREACH (auto& stream, streams){
if (stream.tcp == true){
Expand Down

0 comments on commit f2b86d8

Please sign in to comment.