diff --git a/docs/CONFIGURE.md b/docs/CONFIGURE.md index e24b08d77..fe168e738 100644 --- a/docs/CONFIGURE.md +++ b/docs/CONFIGURE.md @@ -331,13 +331,13 @@ 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 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 streams uncompressed audio (16 bit Int, 8 or 16 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. **NOTE 1: In order for this plugin to work, the audioStreaming option in the Global Configs section (see above) must be set to true.** -**NOTE 2: trunk-recorder passes analog audio to this plugin at 16 kHz sample rate and digital audio at 8 kHz sample rate. Since the audio data being streamed doesn't contain the sample rate, analog and digital audio should be configured to be sent to different ports to receivers that are matched to the same sample rate.** +**NOTE 2: trunk-recorder passes analog audio to this plugin at 16 kHz sample rate and digital audio at 8 kHz sample rate. JSON metadata (if enabled) will contain the sample rate of the audio being sent.** | Key | Required | Default Value | Type | Description | | ------- | :------: | ------------- | ------ | ------------------------------------------------------------ | @@ -350,8 +350,17 @@ 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. | -| 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. +<<<<<<< HEAD +<<<<<<< HEAD +| 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, freq, audoi_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, src, freq, audoi_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. +>>>>>>> 0e42072 (Update CONFIGURE.md) +======= +| 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, freq, audoi_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. +>>>>>>> dcd66be (Update CONFIGURE.md) +| 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. ###### Plugin Object Example #1: @@ -364,7 +373,7 @@ This example will stream audio from talkgroup 58914 on system "CountyTrunked" to "TGID":58914, "address":"127.0.0.1", "port":9123, - "sendTGID":false, + "sendJSON":false, "shortName":"CountyTrunked"} } ``` @@ -379,19 +388,19 @@ This example will stream audio from talkgroup 58914 from System CountyTrunked to "TGID":58914, "address":"127.0.0.1", "port":9123, - "sendTGID":false, + "sendJSON":false, "shortName":"CountyTrunked"}, {"TGID":58916, "address":"127.0.0.1", "port":9124, - "sendTGID":false, + "sendJSON":false, "shortName":"StateTrunked"} ]} } ``` ###### Plugin Object Example #3: -This example will stream audio from talkgroups 58914 and 58916 from all Systems to the local machine on the same UDP port 9123. It will prepend the TGID to the audio data in each UDP packet so that the receiving program can differentiate the two audio streams (the receiver may decide to only play one depending on priority, mix the two streams, play one left and one right, etc.) +This example will stream audio from talkgroups 58914 and 58916 from all Systems to the local machine on the same UDP port 9123. It will prepend the TGID and other JSON metadata to the audio data in each UDP packet so that the receiving program can differentiate the two audio streams (the receiver may decide to only play one depending on priority, mix the two streams, play one left and one right, etc.) ```yaml { "name":"simplestream", @@ -400,16 +409,16 @@ This example will stream audio from talkgroups 58914 and 58916 from all Systems "TGID":58914, "address":"127.0.0.1", "port":9123, - "sendTGID":true}, + "sendJSON":true}, {"TGID":58916, "address":"127.0.0.1", "port":9123, - "sendTGID":true} + "sendJSON":true} ]} } ``` ###### Plugin Object Example #4: -This example will stream audio from all talkgroups being recorded on System CountyTrunked to the local machine on UDP port 9123. It will prepend the TGID to the audio data in each UDP packet so that the receiving program can decide which ones to play or otherwise handle) +This example will stream audio from all talkgroups being recorded on System CountyTrunked to the local machine on UDP port 9123. It will prepend the TGID and other JSON metadata to the audio data in each UDP packet so that the receiving program can decide which ones to play or otherwise handle) ```yaml { "name":"simplestream", @@ -418,7 +427,7 @@ This example will stream audio from all talkgroups being recorded on System Coun "TGID":0, "address":"127.0.0.1", "port":9123, - "sendTGID":true, + "sendJSON":true, "shortName":"CountyTrunked"} } ``` @@ -438,7 +447,7 @@ The matching simplestream config to send audio from talkgroup 58918 to TCP port "TGID":58918, "address":"127.0.0.1", "port":9125, - "sendTGID":false, + "sendJSON":false, "shortName":"CountyTrunked", "useTCP":true} } diff --git a/plugins/simplestream/example_audio_player.py b/plugins/simplestream/example_audio_player.py index 19ff38326..cd7c6404f 100644 --- a/plugins/simplestream/example_audio_player.py +++ b/plugins/simplestream/example_audio_player.py @@ -2,11 +2,14 @@ import struct import pyaudio import time +import json TGID_in_stream = False #When set to True, we expect a 4 byte long int with the TGID prior to the audio in each packet +JSON_in_stream = False #When set to True, we expect a 4 byte long int with the length of the JSON metadata followed by the JSON metadata prior to the audio TGID_to_play = 58917 #When TGID_in_stream is set to True, we'll only play audio if the received TGID matches this value UDP_PORT = 9123 #UDP port to listen on -AUDIO_OUTPUT_DEVICE_INDEX = 2 #Audio device to play received audio on +AUDIO_OUTPUT_DEVICE_INDEX = 4 #Audio device to play received audio on +DEFAULT_AUDIO_SAMPLE_RATE = 8000 # Set up a UDP server UDPSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) @@ -16,28 +19,56 @@ UDPSock.bind(listen_addr) p = pyaudio.PyAudio() -chunk = int(3456/2) -FORMAT = pyaudio.paInt16 -CHANNELS = 1 -RATE = 8000 -stream = p.open(format = FORMAT, - channels = CHANNELS, - rate = RATE, +print(p.get_host_api_info_by_index(0)) + +def create_audio_stream(rate=DEFAULT_AUDIO_SAMPLE_RATE): + stream = p.open(format = pyaudio.paInt16, + channels = 1, + rate = rate, input = False, output = True, - frames_per_buffer = chunk, + frames_per_buffer = int(3456/2), output_device_index = AUDIO_OUTPUT_DEVICE_INDEX,) + return stream + +streams = {} +if TGID_to_play != 0: + streams[TGID_to_play] = create_audio_stream() while True: try: data,addr = UDPSock.recvfrom(2048*2) - if TGID_in_stream: + audio_start_byte = 0 + playit = False + this_audio_sample_rate = DEFAULT_AUDIO_SAMPLE_RATE + if JSON_in_stream: + json_length = int.from_bytes(data[0:4],"little") + audio_start_byte = json_length + 4 + json_bytes = data[4:json_length+4] + json_string = json_bytes.decode('utf8') + # Load the JSON to a Python list & dump it back out as formatted JSON + json_data = json.loads(json_string) + tgid = json_data['talkgroup'] + this_audio_sample_rate = json_data['audio_sample_rate'] + if tgid == TGID_to_play or TGID_to_play == 0: + playit = True + #print(json.dumps(json_data, indent=4, sort_keys=True)) + elif TGID_in_stream: tgid = int.from_bytes(data[0:4],"little") print(tgid," ",len(data)) if (tgid == TGID_to_play or TGID_to_play == 0): - stream.write(data[4:]) + playit = True + audio_start_byte = 4 else: - stream.write(data) - print(data) + tgid = TGID_to_play + playit = True + if playit == True: + #stream.write(data[4:]) + if tgid not in streams: + streams[tgid] = create_audio_stream(rate=this_audio_sample_rate) + streams[tgid].write(data[audio_start_byte:]) + print(json.dumps(json_data, indent=4, sort_keys=True)) + #print(data) + print("received",len(data),"bytes and writing",len(data)-audio_start_byte,"audio bytes") except socket.timeout: pass diff --git a/plugins/simplestream/simplestream.cc b/plugins/simplestream/simplestream.cc index 25246915d..d970a90b1 100644 --- a/plugins/simplestream/simplestream.cc +++ b/plugins/simplestream/simplestream.cc @@ -27,6 +27,7 @@ struct stream_t { ip::udp::endpoint remote_endpoint; ip::tcp::socket *tcp_socket; bool sendTGID = false; + bool sendJSON = false; bool tcp = false; }; @@ -49,6 +50,7 @@ class Simple_Stream : public Plugin_Api { stream.port = element["port"]; 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.tcp = element.value("useTCP",false); stream.short_name = element.value("shortName", ""); BOOST_LOG_TRIVIAL(info) << "simplestreamer will stream audio from TGID " < buf1 = { - buffer(&TGID,4), - buffer(samples, sampleCount*2) + json json_object; + std::string json_string; + std::vector send_buffer; + if (stream.sendJSON==true){ + //create JSON metadata + json_object = { + {"src", call->get_current_source_id()}, + {"talkgroup", TGID}, + {"patched_talkgroups",patched_talkgroups}, + {"freq", call->get_freq()}, + {"short_name", call->get_short_name()}, + {"audio_sample_rate",recorder->get_wav_hz()}, }; - if(stream.tcp==true){ - stream.tcp_socket->send(buf1); - } - else{ - my_socket.send_to(buf1, stream.remote_endpoint, 0, error); - } + json_string = json_object.dump(); + uint32_t json_length = json_string.length(); //determine length in bytes + //BOOST_LOG_TRIVIAL(debug) << "json_length is " <send(send_buffer); } else{ - //just send the audio data - 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); - } + my_socket.send_to(send_buffer, stream.remote_endpoint, 0, error); } } }