diff --git a/docs/CONFIGURE.md b/docs/CONFIGURE.md index 1cfe06bae..2c7c56783 100644 --- a/docs/CONFIGURE.md +++ b/docs/CONFIGURE.md @@ -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. @@ -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 diff --git a/plugins/simplestream/simplestream.cc b/plugins/simplestream/simplestream.cc index d970a90b1..bbdcb25bd 100644 --- a/plugins/simplestream/simplestream.cc +++ b/plugins/simplestream/simplestream.cc @@ -13,7 +13,6 @@ std::vector streams; io_service my_tcp_io_service; long max_tcp_index = 0; - struct plugin_t { Config* config; }; @@ -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; }; @@ -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 " <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 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 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 " <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 @@ -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 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 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 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 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 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){