diff --git a/op25/gr-op25_repeater/lib/op25_audio.cc b/op25/gr-op25_repeater/lib/op25_audio.cc new file mode 100644 index 0000000..6397bca --- /dev/null +++ b/op25/gr-op25_repeater/lib/op25_audio.cc @@ -0,0 +1,242 @@ +/* -*- c++ -*- */ +/* + * Copyright 2017 Graham J Norbury, gnorbury@bondcar.com + * from op25_audio; rewrite Nov 2017 Copyright 2017 Max H. Parke KA1RBI + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "op25_audio.h" + +// convert hostname to ip address +static int hostname_to_ip(const char *hostname , char *ip) +{ + int sockfd; + struct addrinfo hints, *servinfo, *p; + struct sockaddr_in *h; + int rv; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; // use AF_INET6 to force IPv6 + hints.ai_socktype = SOCK_DGRAM; + + if ( (rv = getaddrinfo( hostname , NULL , &hints , &servinfo)) != 0) + { + fprintf(stderr, "op25_audio::hostname_to_ip() getaddrinfo: %s\n", gai_strerror(rv)); + return -1; + } + + // loop through all the results and connect to the first we can + for(p = servinfo; p != NULL; p = p->ai_next) + { + h = (struct sockaddr_in *) p->ai_addr; + if (h->sin_addr.s_addr != 0) + { + strcpy(ip , inet_ntoa( h->sin_addr ) ); + break; + } + } + + freeaddrinfo(servinfo); // all done with this structure + return 0; + +} + +// constructor +op25_audio::op25_audio(const char* udp_host, int port, int debug) : + d_udp_enabled(false), + d_debug(debug), + d_write_port(port), + d_audio_port(port), + d_write_sock(0), + d_file_enabled(false) +{ + char ip[20]; + if (hostname_to_ip(udp_host, ip) == 0) + { + strncpy(d_udp_host, ip, sizeof(d_udp_host)); + d_udp_host[sizeof(d_udp_host)-1] = 0; + if ( port ) + open_socket(); + } +} + +// destructor +op25_audio::~op25_audio() +{ + if (d_file_enabled) + close(d_write_sock); + close_socket(); +} + +// constructor +op25_audio::op25_audio(const char* destination, int debug) : + d_udp_enabled(false), + d_debug(debug), + d_write_port(0), + d_audio_port(0), + d_write_sock(0), + d_file_enabled(false) +{ + static const int DEFAULT_UDP_PORT = 23456; + static const char P_UDP[] = "udp://"; + static const char P_FILE[] = "file://"; + int port = DEFAULT_UDP_PORT; + + if (memcmp(destination, P_UDP, strlen(P_UDP)) == 0) { + const char * p1 = destination+strlen(P_UDP); + strncpy(d_udp_host, p1, sizeof(d_udp_host)); + d_udp_host[sizeof(d_udp_host)-1] = 0; + char * pc = index(d_udp_host, ':'); + if (pc) { + sscanf(pc+1, "%d", &port); + *pc = 0; + } + d_write_port = d_audio_port = port; + open_socket(); + } else if (memcmp(destination, P_FILE, strlen(P_FILE)) == 0) { + const char * filename = destination+strlen(P_FILE); + size_t l = strlen(filename); + if (l > 4 && (strcmp(&filename[l-4], ".wav") == 0 || strcmp(&filename[l-4], ".WAV") == 0)) { + fprintf (stderr, "Warning! Output file %s will be written, but in raw form ***without*** a WAV file header!\n", filename); + } + d_write_sock = open(filename, O_WRONLY | O_CREAT, 0644); + if (d_write_sock < 0) { + fprintf(stderr, "op25_audio::open file %s: error: %d (%s)\n", filename, errno, strerror(errno)); + d_write_sock = 0; + return; + } + d_file_enabled = true; + } +} +// open socket and set up data structures +void op25_audio::open_socket() +{ + memset (&d_sock_addr, 0, sizeof(d_sock_addr)); + + // open handle to socket + d_write_sock = socket(PF_INET, SOCK_DGRAM, 17); // UDP socket + if ( d_write_sock < 0 ) + { + fprintf(stderr, "op25_audio::open_socket(): error: %d\n", errno); + d_write_sock = 0; + return; + } + + // set up data structure for generic udp host/port + if ( !inet_aton(d_udp_host, &d_sock_addr.sin_addr) ) + { + fprintf(stderr, "op25_audio::open_socket(): inet_aton: bad IP address\n"); + close(d_write_sock); + d_write_sock = 0; + return; + } + d_sock_addr.sin_family = AF_INET; + + fprintf(stderr, "op25_audio::open_socket(): enabled udp host(%s), wireshark(%d), audio(%d)\n", d_udp_host, d_write_port, d_audio_port); + d_udp_enabled = true; +} + +// close socket +void op25_audio::close_socket() +{ + if (!d_udp_enabled) + return; + close(d_write_sock); + d_write_sock = 0; + d_udp_enabled = false; +} + +ssize_t op25_audio::do_send(const void * buf, size_t len, int port, bool is_ctrl ) const { + ssize_t rc = 0; + struct sockaddr_in tmp_sockaddr; + if (len <= 0) + return 0; + if (d_udp_enabled) { + memcpy(&tmp_sockaddr, &d_sock_addr, sizeof(struct sockaddr)); + tmp_sockaddr.sin_port = htons(port); + rc = sendto(d_write_sock, buf, len, 0, (struct sockaddr *)&tmp_sockaddr, sizeof(struct sockaddr_in)); + if (rc == -1) + { + fprintf(stderr, "op25_audio::do_send(length %lu): error(%d): %s\n", len, errno, strerror(errno)); + rc = 0; + } + } else if (d_file_enabled && !is_ctrl) { + size_t amt_written = 0; + for (;;) { + rc = write(d_write_sock, amt_written + (char*)buf, len - amt_written); + if (rc < 0) { + fprintf(stderr, "op25_audio::write(length %lu): error(%d): %s\n", len, errno, strerror(errno)); + rc = 0; + } else if (rc == 0) { + fprintf(stderr, "op25_audio::write(length %lu): error, write rc zero\n", len); + } else { + amt_written += rc; + } + if (rc <= 0 || amt_written >= len) + break; + } /* end of for() */ + rc = amt_written; + } + return rc; +} + +// send generic data to destination +ssize_t op25_audio::send_to(const void *buf, size_t len) const +{ + return do_send(buf, len, d_write_port, false); +} + +// send audio data to destination +ssize_t op25_audio::send_audio(const void *buf, size_t len) const +{ + return do_send(buf, len, d_audio_port, false); +} + +// send audio data on specifed channel to destination +ssize_t op25_audio::send_audio_channel(const void *buf, size_t len, ssize_t slot_id) const +{ + return do_send(buf, len, d_audio_port + slot_id*2, false); +} + +// send flag to audio destination +ssize_t op25_audio::send_audio_flag_channel(const udpFlagEnumType udp_flag, ssize_t slot_id) const +{ + char audio_flag[2]; + // 16 bit little endian encoding + audio_flag[0] = (udp_flag & 0x00ff); + audio_flag[1] = ((udp_flag & 0xff00) >> 8); + return do_send(audio_flag, 2, d_audio_port+slot_id, true); +} + +ssize_t op25_audio::send_audio_flag(const op25_audio::udpFlagEnumType udp_flag) const +{ + return send_audio_flag_channel(udp_flag, 0); +} diff --git a/op25/gr-op25_repeater/lib/op25_audio.h b/op25/gr-op25_repeater/lib/op25_audio.h new file mode 100644 index 0000000..dc715a0 --- /dev/null +++ b/op25/gr-op25_repeater/lib/op25_audio.h @@ -0,0 +1,70 @@ +/* -*- c++ -*- */ +/* + * Copyright 2017 Graham J Norbury, gnorbury@bondcar.com + * from op25_audio; rewrite Nov 2017 Copyright 2017 Max H. Parke KA1RBI + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef INCLUDED_OP25_AUDIO_H +#define INCLUDED_OP25_AUDIO_H + +#include +#include +#include +#include + +class op25_audio +{ +public: + enum udpFlagEnumType + { + DRAIN = 0x0000, // play queued pcm frames + DROP = 0x0001 // discard queued pcm frames + }; + +private: + bool d_udp_enabled; + int d_debug; + int d_write_port; + int d_audio_port; + char d_udp_host[64]; + int d_write_sock; + bool d_file_enabled; + struct sockaddr_in d_sock_addr; + + void open_socket(); + void close_socket(); + ssize_t do_send(const void * bufp, size_t len, int port, bool is_ctrl) const; + +public: + op25_audio(const char* udp_host, int port, int debug); + op25_audio(const char* destination, int debug); + ~op25_audio(); + + inline bool enabled() const { return d_udp_enabled; } + + ssize_t send_to(const void *buf, size_t len) const; + + ssize_t send_audio(const void *buf, size_t len) const; + ssize_t send_audio_flag(const udpFlagEnumType udp_flag) const; + + ssize_t send_audio_channel(const void *buf, size_t len, ssize_t slot_id) const; + ssize_t send_audio_flag_channel(const udpFlagEnumType udp_flag, ssize_t slot_id) const; + +}; // class op25_audio + +#endif /* INCLUDED_OP25_AUDIO_H */