mirror of https://github.com/wb2osz/direwolf.git
Merge 6c62d30416
into ab834f338b
This commit is contained in:
commit
e5c01c82f7
|
@ -33,6 +33,10 @@
|
|||
>
|
||||
> Add: "FX25TX 1" (or 16 or 32 or 64)
|
||||
|
||||
- stdout is now supported for audio output via piping to other utilities. To support this, all non-audio output must be redirected to stderr using the new -O option on the command line.
|
||||
|
||||
- udp audio output is also now supported. Use udp:destination:port style output device in the configuration file.
|
||||
|
||||
|
||||
|
||||
### Bugs Fixed: ###
|
||||
|
|
|
@ -132,10 +132,15 @@
|
|||
%W%# "stdin" is not an audio device. Don't use this unless you
|
||||
%W%# understand what this means. Read the User Guide.
|
||||
%W%# You can also specify "UDP:" and an optional port for input.
|
||||
%W%# Something different must be specified for output.
|
||||
%W%# "-" or "stdout" can be used to pipe audio out to another application.
|
||||
%W%# The -O option must be specified on the command line to support this.
|
||||
%W%# For UDP output, specify the destination IP address/hostname and port number
|
||||
%W%# using "UDP:destination:port" syntax
|
||||
%W%
|
||||
%W%# ADEVICE stdin 0
|
||||
%W%# ADEVICE UDP:7355 0
|
||||
%W%# ADEVICE UDP:7355 UDP:localhost:7356
|
||||
%W%# ADEVICE stdin stdout
|
||||
%W%
|
||||
%W%# The position in the list can change when devices (e.g. USB) are added and removed.
|
||||
%W%# You can also specify devices by using part of the name.
|
||||
|
@ -158,10 +163,15 @@
|
|||
%L%# "stdin" is not an audio device. Don't use this unless you
|
||||
%L%# understand what this means. Read the User Guide.
|
||||
%L%# You can also specify "UDP:" and an optional port for input.
|
||||
%L%# Something different must be specified for output.
|
||||
%L%# "-" or "stdout" can be used to pipe audio out to another application.
|
||||
%L%# The -O option must be specified on the command line to support this.
|
||||
%L%# For UDP output, specify the destination IP address/hostname and port number
|
||||
%L%# using "UDP:destination:port" syntax
|
||||
%L%
|
||||
%L%# ADEVICE stdin plughw:1,0
|
||||
%L%# ADEVICE UDP:7355 default
|
||||
%L%# ADEVICE UDP:7355 UDP:localhost:7356
|
||||
%L%# ADEVICE stdin stdout
|
||||
%L%
|
||||
%R% ---------- Mac ----------
|
||||
%R%
|
||||
|
@ -183,9 +193,14 @@
|
|||
%M%# "stdin" is not an audio device. Don't use this unless you
|
||||
%M%# understand what this means. Read the User Guide.
|
||||
%M%# You can also specify "UDP:" and an optional port for input.
|
||||
%M%# Something different must be specified for output.
|
||||
%M%# "-" or "stdout" can be used to pipe audio out to another application.
|
||||
%M%# The -O option must be specified on the command line to support this.
|
||||
%M%# For UDP output, specify the destination IP address/hostname and port number
|
||||
%M%# using "UDP:destination:port" syntax
|
||||
%M%
|
||||
%M%# ADEVICE UDP:7355 default
|
||||
%M%# ADEVICE UDP:7355 UDP:localhost:7356
|
||||
%M%# ADEVICE stdin stdout
|
||||
%M%#
|
||||
%C%
|
||||
%C%#
|
||||
|
@ -607,4 +622,4 @@
|
|||
%C%#TTERR NO_CALL SPEECH No call or object name.
|
||||
%C%#TTERR SATSQ SPEECH Satellite square must be 4 digits.
|
||||
%C%#TTERR SUFFIX_NO_CALL SPEECH Send full call before using suffix.
|
||||
%C%
|
||||
%C%
|
||||
|
|
|
@ -155,6 +155,9 @@ x = Silence FX.25 information.
|
|||
.BI "-t " "n"
|
||||
Text colors. 0=disabled. 1=default. 2,3,4,... alternatives. Use 9 to test compatibility with your terminal.
|
||||
|
||||
.TP
|
||||
.BI "-O "
|
||||
Redirects all printed output to stderr so stdout can be used as audio device.
|
||||
|
||||
.TP
|
||||
.B "-p "
|
||||
|
|
|
@ -2078,6 +2078,7 @@ static void check_result (void)
|
|||
|
||||
int main (int argc, char *argv[])
|
||||
{
|
||||
text_color_init (1, 0);
|
||||
aprs_tt_init (NULL, 0);
|
||||
|
||||
error_count = 0;
|
||||
|
|
|
@ -217,7 +217,7 @@ int main (int argc, char *argv[])
|
|||
}
|
||||
#endif
|
||||
|
||||
text_color_init(1);
|
||||
text_color_init(1, 0);
|
||||
text_color_set(DW_COLOR_INFO);
|
||||
|
||||
/*
|
||||
|
|
478
src/audio.c
478
src/audio.c
|
@ -75,6 +75,7 @@
|
|||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
|
||||
|
||||
|
@ -129,8 +130,11 @@ static struct adev_s {
|
|||
int outbuf_len;
|
||||
|
||||
enum audio_in_type_e g_audio_in_type;
|
||||
enum audio_out_type_e g_audio_out_type;
|
||||
|
||||
int udp_sock; /* UDP socket for receiving data */
|
||||
int udp_in_sock; /* UDP socket for receiving data */
|
||||
int udp_out_sock; /* UDP socket for sending data */
|
||||
struct sockaddr_storage udp_dest_addr; /* Destination address for UDP socket sending */
|
||||
|
||||
} adev[MAX_ADEVS];
|
||||
|
||||
|
@ -222,8 +226,8 @@ int audio_open (struct audio_s *pa)
|
|||
#endif
|
||||
int chan;
|
||||
int a;
|
||||
char audio_in_name[30];
|
||||
char audio_out_name[30];
|
||||
char audio_in_name[80];
|
||||
char audio_out_name[80];
|
||||
|
||||
|
||||
save_audio_config_p = pa;
|
||||
|
@ -238,7 +242,7 @@ int audio_open (struct audio_s *pa)
|
|||
#else
|
||||
adev[a].oss_audio_device_fd = -1;
|
||||
#endif
|
||||
adev[a].udp_sock = -1;
|
||||
adev[a].udp_in_sock = -1;
|
||||
}
|
||||
|
||||
|
||||
|
@ -412,7 +416,7 @@ int audio_open (struct audio_s *pa)
|
|||
//int data_size = 0;
|
||||
|
||||
//Create UDP Socket
|
||||
if ((adev[a].udp_sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) {
|
||||
if ((adev[a].udp_in_sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Couldn't create socket, errno %d\n", errno);
|
||||
return -1;
|
||||
|
@ -424,7 +428,7 @@ int audio_open (struct audio_s *pa)
|
|||
si_me.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
|
||||
//Bind to the socket
|
||||
if (bind(adev[a].udp_sock, (const struct sockaddr *) &si_me, sizeof(si_me))==-1) {
|
||||
if (bind(adev[a].udp_in_sock, (const struct sockaddr *) &si_me, sizeof(si_me))==-1) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Couldn't bind socket, errno %d\n", errno);
|
||||
return -1;
|
||||
|
@ -453,52 +457,134 @@ int audio_open (struct audio_s *pa)
|
|||
}
|
||||
|
||||
/*
|
||||
* Output device. Only "soundcard" is supported at this time.
|
||||
* Output device. Soundcard, stdout, and UDP are supported at this time.
|
||||
*/
|
||||
|
||||
#if USE_ALSA
|
||||
err = snd_pcm_open (&(adev[a].audio_out_handle), audio_out_name, SND_PCM_STREAM_PLAYBACK, 0);
|
||||
|
||||
if (err < 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Could not open audio device %s for output\n%s\n",
|
||||
audio_out_name, snd_strerror(err));
|
||||
if (err == -EBUSY) {
|
||||
dw_printf ("This means that some other application is using that device.\n");
|
||||
dw_printf ("The solution is to identify that other application and stop it.\n");
|
||||
if (strcasecmp(pa->adev[a].adevice_out, "stdout") == 0 || strcmp(pa->adev[a].adevice_out, "-") == 0) {
|
||||
adev[a].g_audio_out_type = AUDIO_OUT_TYPE_STDOUT;
|
||||
if (!dw_printf_redirected()) {
|
||||
text_color_set (DW_COLOR_ERROR);
|
||||
dw_printf ("stdout must only be used with the -O option\n");
|
||||
return (-1);
|
||||
}
|
||||
return (-1);
|
||||
} else if (strncasecmp(pa->adev[a].adevice_out, "udp:", 4) == 0) {
|
||||
adev[a].g_audio_out_type = AUDIO_OUT_TYPE_SDR_UDP;
|
||||
/* User must supply address and port */
|
||||
if (strcasecmp(pa->adev[a].adevice_out,"udp:") == 0 ||
|
||||
strlen(pa->adev[a].adevice_out) < 7 ||
|
||||
strstr(pa->adev[a].adevice_out+5, ":") == 0) {
|
||||
text_color_set (DW_COLOR_ERROR);
|
||||
dw_printf ("Destination address and port must be supplied for UDP output\n");
|
||||
return (-1);
|
||||
}
|
||||
} else {
|
||||
adev[a].g_audio_out_type = AUDIO_OUT_TYPE_SOUNDCARD;
|
||||
}
|
||||
|
||||
adev[a].outbuf_size_in_bytes = set_alsa_params (a, adev[a].audio_out_handle, pa, audio_out_name, "output");
|
||||
switch (adev[a].g_audio_out_type) {
|
||||
case AUDIO_OUT_TYPE_STDOUT:
|
||||
adev[a].outbuf_size_in_bytes = 1024;
|
||||
break;
|
||||
|
||||
if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) {
|
||||
return (-1);
|
||||
}
|
||||
case AUDIO_OUT_TYPE_SOUNDCARD:
|
||||
#if USE_ALSA
|
||||
err = snd_pcm_open (&(adev[a].audio_out_handle), audio_out_name, SND_PCM_STREAM_PLAYBACK, 0);
|
||||
|
||||
if (err < 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Could not open audio device %s for output\n%s\n",
|
||||
audio_out_name, snd_strerror(err));
|
||||
if (err == -EBUSY) {
|
||||
dw_printf ("This means that some other application is using that device.\n");
|
||||
dw_printf ("The solution is to identify that other application and stop it.\n");
|
||||
}
|
||||
return (-1);
|
||||
}
|
||||
|
||||
adev[a].outbuf_size_in_bytes = set_alsa_params (a, adev[a].audio_out_handle, pa, audio_out_name, "output");
|
||||
|
||||
if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) {
|
||||
return (-1);
|
||||
}
|
||||
|
||||
#elif USE_SNDIO
|
||||
adev[a].sndio_out_handle = sio_open (audio_out_name, SIO_PLAY, 0);
|
||||
if (adev[a].sndio_out_handle == NULL) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Could not open audio device %s for output\n",
|
||||
audio_out_name);
|
||||
return (-1);
|
||||
}
|
||||
adev[a].sndio_out_handle = sio_open (audio_out_name, SIO_PLAY, 0);
|
||||
if (adev[a].sndio_out_handle == NULL) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Could not open audio device %s for output\n",
|
||||
audio_out_name);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
adev[a].outbuf_size_in_bytes = set_sndio_params (a, adev[a].sndio_out_handle, pa, audio_out_name, "output");
|
||||
adev[a].outbuf_size_in_bytes = set_sndio_params (a, adev[a].sndio_out_handle, pa, audio_out_name, "output");
|
||||
|
||||
if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) {
|
||||
return (-1);
|
||||
}
|
||||
if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) {
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if (!sio_start (adev[a].sndio_out_handle)) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Could not start audio device %s for output\n",
|
||||
audio_out_name);
|
||||
return (-1);
|
||||
}
|
||||
if (!sio_start (adev[a].sndio_out_handle)) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Could not start audio device %s for output\n",
|
||||
audio_out_name);
|
||||
return (-1);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
|
||||
case AUDIO_OUT_TYPE_SDR_UDP:;
|
||||
|
||||
struct addrinfo ai_out;
|
||||
struct addrinfo *ai_res;
|
||||
char udp_outhost[256];
|
||||
char *udp_outport;
|
||||
int res;
|
||||
|
||||
// Initialize structure for addrinfo restrictions
|
||||
memset((char *) &ai_out, 0, sizeof(ai_out));
|
||||
ai_out.ai_socktype = SOCK_DGRAM;
|
||||
ai_out.ai_protocol = IPPROTO_UDP;
|
||||
|
||||
// Separate out the host and port strings
|
||||
strncpy(udp_outhost, pa->adev[a].adevice_out+4, 255);
|
||||
udp_outhost[255] = 0;
|
||||
udp_outport = strstr(udp_outhost,":");
|
||||
*udp_outport++ = 0;
|
||||
|
||||
if (strlen(udp_outport) == 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("UDP output destination port must be supplied\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get the sockaddr to represent the host/port provided
|
||||
res = getaddrinfo(udp_outhost, udp_outport, &ai_out, &ai_res);
|
||||
if (res != 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Error parsing/resolving UDP output address\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// IPv4 and IPv6 structs are different sizes
|
||||
if (ai_res->ai_family == AF_INET6) {
|
||||
res = sizeof(struct sockaddr_in6);
|
||||
} else {
|
||||
res = sizeof(struct sockaddr_in);
|
||||
}
|
||||
|
||||
//Create UDP Socket for the right address family
|
||||
if ((adev[a].udp_out_sock=socket(ai_res->ai_family, SOCK_DGRAM, IPPROTO_UDP))==-1) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Couldn't create socket, errno %d\n", errno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Save sockaddr needed later to send the data and set buffer size
|
||||
memcpy(&adev[a].udp_dest_addr, ai_res->ai_addr, res);
|
||||
adev[a].outbuf_size_in_bytes = SDR_UDP_BUF_MAXLEN;
|
||||
freeaddrinfo(ai_res);
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
/*
|
||||
* Finally allocate buffer for each direction.
|
||||
*/
|
||||
|
@ -1205,8 +1291,8 @@ int audio_get (int a)
|
|||
while (adev[a].inbuf_next >= adev[a].inbuf_len) {
|
||||
int res;
|
||||
|
||||
assert (adev[a].udp_sock > 0);
|
||||
res = recv(adev[a].udp_sock, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes, 0);
|
||||
assert (adev[a].udp_in_sock > 0);
|
||||
res = recv(adev[a].udp_in_sock, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes, 0);
|
||||
if (res < 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Can't read from udp socket, res=%d", res);
|
||||
|
@ -1333,13 +1419,37 @@ int audio_put (int a, int c)
|
|||
|
||||
int audio_flush (int a)
|
||||
{
|
||||
#if USE_ALSA
|
||||
int k;
|
||||
unsigned char *psound;
|
||||
int retries = 10;
|
||||
snd_pcm_status_t *status;
|
||||
int res;
|
||||
unsigned char *ptr;
|
||||
int len;
|
||||
|
||||
assert (adev[a].audio_out_handle != NULL);
|
||||
switch (adev[a].g_audio_out_type) {
|
||||
case AUDIO_OUT_TYPE_STDOUT:;
|
||||
|
||||
ptr = adev[a].outbuf_ptr;
|
||||
len = adev[a].outbuf_len;
|
||||
|
||||
while (len > 0) {
|
||||
res = write(STDOUT_FILENO, ptr, (size_t) len);
|
||||
if (res <= 0) {
|
||||
text_color_set(DW_COLOR_INFO);
|
||||
dw_printf ("\nError writing to stdout. Exiting.\n");
|
||||
exit (0);
|
||||
}
|
||||
ptr += res;
|
||||
len -= res;
|
||||
}
|
||||
adev[a].outbuf_len = 0;
|
||||
return 0;
|
||||
|
||||
case AUDIO_OUT_TYPE_SOUNDCARD:;
|
||||
#if USE_ALSA
|
||||
int k;
|
||||
unsigned char *psound;
|
||||
int retries = 10;
|
||||
snd_pcm_status_t *status;
|
||||
|
||||
assert (adev[a].audio_out_handle != NULL);
|
||||
|
||||
|
||||
/*
|
||||
|
@ -1352,159 +1462,186 @@ int audio_flush (int a)
|
|||
*/
|
||||
|
||||
|
||||
snd_pcm_status_alloca(&status);
|
||||
snd_pcm_status_alloca(&status);
|
||||
|
||||
k = snd_pcm_status (adev[a].audio_out_handle, status);
|
||||
if (k != 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Audio output get status error.\n%s\n", snd_strerror(k));
|
||||
}
|
||||
k = snd_pcm_status (adev[a].audio_out_handle, status);
|
||||
if (k != 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Audio output get status error.\n%s\n", snd_strerror(k));
|
||||
}
|
||||
|
||||
if ((k = snd_pcm_status_get_state(status)) != SND_PCM_STATE_RUNNING) {
|
||||
if ((k = snd_pcm_status_get_state(status)) != SND_PCM_STATE_RUNNING) {
|
||||
|
||||
//text_color_set(DW_COLOR_DEBUG);
|
||||
//dw_printf ("Audio output state = %d. Try to start.\n", k);
|
||||
//text_color_set(DW_COLOR_DEBUG);
|
||||
//dw_printf ("Audio output state = %d. Try to start.\n", k);
|
||||
|
||||
k = snd_pcm_prepare (adev[a].audio_out_handle);
|
||||
k = snd_pcm_prepare (adev[a].audio_out_handle);
|
||||
|
||||
if (k != 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Audio output start error.\n%s\n", snd_strerror(k));
|
||||
}
|
||||
}
|
||||
if (k != 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Audio output start error.\n%s\n", snd_strerror(k));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
psound = adev[a].outbuf_ptr;
|
||||
psound = adev[a].outbuf_ptr;
|
||||
|
||||
while (retries-- > 0) {
|
||||
while (retries-- > 0) {
|
||||
|
||||
k = snd_pcm_writei (adev[a].audio_out_handle, psound, adev[a].outbuf_len / adev[a].bytes_per_frame);
|
||||
k = snd_pcm_writei (adev[a].audio_out_handle, psound, adev[a].outbuf_len / adev[a].bytes_per_frame);
|
||||
#if DEBUGx
|
||||
text_color_set(DW_COLOR_DEBUG);
|
||||
dw_printf ("audio_flush(): snd_pcm_writei %d frames returns %d\n",
|
||||
adev[a].outbuf_len / adev[a].bytes_per_frame, k);
|
||||
fflush (stdout);
|
||||
text_color_set(DW_COLOR_DEBUG);
|
||||
dw_printf ("audio_flush(): snd_pcm_writei %d frames returns %d\n",
|
||||
adev[a].outbuf_len / adev[a].bytes_per_frame, k);
|
||||
fflush (stdout);
|
||||
#endif
|
||||
if (k == -EPIPE) {
|
||||
if (k == -EPIPE) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Audio output data underrun.\n");
|
||||
|
||||
/* No problemo. Recover and go around again. */
|
||||
|
||||
snd_pcm_recover (adev[a].audio_out_handle, k, 1);
|
||||
}
|
||||
else if (k == -ESTRPIPE) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Driver suspended, recovering\n");
|
||||
snd_pcm_recover(adev[a].audio_out_handle, k, 1);
|
||||
}
|
||||
else if (k == -EBADFD) {
|
||||
k = snd_pcm_prepare (adev[a].audio_out_handle);
|
||||
if(k < 0) {
|
||||
dw_printf ("Error preparing after bad state: %s\n", snd_strerror(k));
|
||||
}
|
||||
}
|
||||
else if (k < 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Audio write error: %s\n", snd_strerror(k));
|
||||
|
||||
/* Some other error condition. */
|
||||
/* Try again. What do we have to lose? */
|
||||
|
||||
k = snd_pcm_prepare (adev[a].audio_out_handle);
|
||||
if(k < 0) {
|
||||
dw_printf ("Error preparing after error: %s\n", snd_strerror(k));
|
||||
}
|
||||
}
|
||||
else if (k != adev[a].outbuf_len / adev[a].bytes_per_frame) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Audio write took %d frames rather than %d.\n",
|
||||
k, adev[a].outbuf_len / adev[a].bytes_per_frame);
|
||||
|
||||
/* Go around again with the rest of it. */
|
||||
|
||||
psound += k * adev[a].bytes_per_frame;
|
||||
adev[a].outbuf_len -= k * adev[a].bytes_per_frame;
|
||||
}
|
||||
else {
|
||||
/* Success! */
|
||||
adev[a].outbuf_len = 0;
|
||||
return (0);
|
||||
}
|
||||
}
|
||||
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Audio output data underrun.\n");
|
||||
dw_printf ("Audio write error retry count exceeded.\n");
|
||||
|
||||
/* No problemo. Recover and go around again. */
|
||||
|
||||
snd_pcm_recover (adev[a].audio_out_handle, k, 1);
|
||||
}
|
||||
else if (k == -ESTRPIPE) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Driver suspended, recovering\n");
|
||||
snd_pcm_recover(adev[a].audio_out_handle, k, 1);
|
||||
}
|
||||
else if (k == -EBADFD) {
|
||||
k = snd_pcm_prepare (adev[a].audio_out_handle);
|
||||
if(k < 0) {
|
||||
dw_printf ("Error preparing after bad state: %s\n", snd_strerror(k));
|
||||
}
|
||||
}
|
||||
else if (k < 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Audio write error: %s\n", snd_strerror(k));
|
||||
|
||||
/* Some other error condition. */
|
||||
/* Try again. What do we have to lose? */
|
||||
|
||||
k = snd_pcm_prepare (adev[a].audio_out_handle);
|
||||
if(k < 0) {
|
||||
dw_printf ("Error preparing after error: %s\n", snd_strerror(k));
|
||||
}
|
||||
}
|
||||
else if (k != adev[a].outbuf_len / adev[a].bytes_per_frame) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Audio write took %d frames rather than %d.\n",
|
||||
k, adev[a].outbuf_len / adev[a].bytes_per_frame);
|
||||
|
||||
/* Go around again with the rest of it. */
|
||||
|
||||
psound += k * adev[a].bytes_per_frame;
|
||||
adev[a].outbuf_len -= k * adev[a].bytes_per_frame;
|
||||
}
|
||||
else {
|
||||
/* Success! */
|
||||
adev[a].outbuf_len = 0;
|
||||
return (0);
|
||||
}
|
||||
}
|
||||
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Audio write error retry count exceeded.\n");
|
||||
|
||||
adev[a].outbuf_len = 0;
|
||||
return (-1);
|
||||
return (-1);
|
||||
|
||||
#elif USE_SNDIO
|
||||
|
||||
int k;
|
||||
unsigned char *ptr;
|
||||
int len;
|
||||
int k;
|
||||
unsigned char *ptr;
|
||||
int len;
|
||||
|
||||
ptr = adev[a].outbuf_ptr;
|
||||
len = adev[a].outbuf_len;
|
||||
ptr = adev[a].outbuf_ptr;
|
||||
len = adev[a].outbuf_len;
|
||||
|
||||
while (len > 0) {
|
||||
assert (adev[a].sndio_out_handle != NULL);
|
||||
if (poll_sndio (adev[a].sndio_out_handle, POLLOUT) < 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
perror("Can't write to audio device");
|
||||
adev[a].outbuf_len = 0;
|
||||
return (-1);
|
||||
}
|
||||
while (len > 0) {
|
||||
assert (adev[a].sndio_out_handle != NULL);
|
||||
if (poll_sndio (adev[a].sndio_out_handle, POLLOUT) < 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
perror("Can't write to audio device");
|
||||
adev[a].outbuf_len = 0;
|
||||
return (-1);
|
||||
}
|
||||
|
||||
k = sio_write (adev[a].sndio_out_handle, ptr, len);
|
||||
k = sio_write (adev[a].sndio_out_handle, ptr, len);
|
||||
#if DEBUGx
|
||||
text_color_set(DW_COLOR_DEBUG);
|
||||
dw_printf ("audio_flush(): write %d returns %d\n", len, k);
|
||||
fflush (stdout);
|
||||
text_color_set(DW_COLOR_DEBUG);
|
||||
dw_printf ("audio_flush(): write %d returns %d\n", len, k);
|
||||
fflush (stdout);
|
||||
#endif
|
||||
ptr += k;
|
||||
len -= k;
|
||||
}
|
||||
ptr += k;
|
||||
len -= k;
|
||||
}
|
||||
|
||||
adev[a].outbuf_len = 0;
|
||||
return (0);
|
||||
adev[a].outbuf_len = 0;
|
||||
return (0);
|
||||
|
||||
#else /* OSS */
|
||||
|
||||
int k;
|
||||
unsigned char *ptr;
|
||||
int len;
|
||||
int k;
|
||||
unsigned char *ptr;
|
||||
int len;
|
||||
|
||||
ptr = adev[a].outbuf_ptr;
|
||||
len = adev[a].outbuf_len;
|
||||
ptr = adev[a].outbuf_ptr;
|
||||
len = adev[a].outbuf_len;
|
||||
|
||||
while (len > 0) {
|
||||
assert (adev[a].oss_audio_device_fd > 0);
|
||||
k = write (adev[a].oss_audio_device_fd, ptr, len);
|
||||
while (len > 0) {
|
||||
assert (adev[a].oss_audio_device_fd > 0);
|
||||
k = write (adev[a].oss_audio_device_fd, ptr, len);
|
||||
#if DEBUGx
|
||||
text_color_set(DW_COLOR_DEBUG);
|
||||
dw_printf ("audio_flush(): write %d returns %d\n", len, k);
|
||||
fflush (stdout);
|
||||
text_color_set(DW_COLOR_DEBUG);
|
||||
dw_printf ("audio_flush(): write %d returns %d\n", len, k);
|
||||
fflush (stdout);
|
||||
#endif
|
||||
if (k < 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
perror("Can't write to audio device");
|
||||
if (k < 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
perror("Can't write to audio device");
|
||||
adev[a].outbuf_len = 0;
|
||||
return (-1);
|
||||
}
|
||||
if (k < len) {
|
||||
/* presumably full but didn't block. */
|
||||
usleep (10000);
|
||||
}
|
||||
ptr += k;
|
||||
len -= k;
|
||||
}
|
||||
|
||||
adev[a].outbuf_len = 0;
|
||||
return (-1);
|
||||
}
|
||||
if (k < len) {
|
||||
/* presumably full but didn't block. */
|
||||
usleep (10000);
|
||||
}
|
||||
ptr += k;
|
||||
len -= k;
|
||||
}
|
||||
|
||||
adev[a].outbuf_len = 0;
|
||||
return (0);
|
||||
return (0);
|
||||
#endif
|
||||
|
||||
case AUDIO_OUT_TYPE_SDR_UDP:;
|
||||
|
||||
ptr = adev[a].outbuf_ptr;
|
||||
len = adev[a].outbuf_len;
|
||||
|
||||
while (len > 0) {
|
||||
|
||||
assert (adev[a].udp_out_sock > 0);
|
||||
|
||||
res = sendto(adev[a].udp_out_sock, adev[a].outbuf_ptr, len, 0, (struct sockaddr *)&adev[a].udp_dest_addr, sizeof(struct sockaddr_storage));
|
||||
|
||||
if (res <= 0) {
|
||||
text_color_set(DW_COLOR_INFO);
|
||||
dw_printf ("\nError %d writing to UDP socket. Exiting.\n", errno);
|
||||
exit (0);
|
||||
}
|
||||
|
||||
ptr += res;
|
||||
len -= res;
|
||||
|
||||
}
|
||||
|
||||
adev[a].outbuf_len = 0;
|
||||
return 0;
|
||||
|
||||
}
|
||||
return (0);
|
||||
} /* end audio_flush */
|
||||
|
||||
|
||||
|
@ -1546,9 +1683,12 @@ int audio_flush (int a)
|
|||
|
||||
void audio_wait (int a)
|
||||
{
|
||||
|
||||
audio_flush (a);
|
||||
|
||||
if (adev[a].g_audio_out_type != AUDIO_OUT_TYPE_SOUNDCARD) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if USE_ALSA
|
||||
|
||||
/* For playback, this should wait for all pending frames */
|
||||
|
|
|
@ -43,6 +43,11 @@ enum audio_in_type_e {
|
|||
AUDIO_IN_TYPE_SDR_UDP,
|
||||
AUDIO_IN_TYPE_STDIN };
|
||||
|
||||
enum audio_out_type_e {
|
||||
AUDIO_OUT_TYPE_SOUNDCARD,
|
||||
AUDIO_OUT_TYPE_SDR_UDP,
|
||||
AUDIO_OUT_TYPE_STDOUT };
|
||||
|
||||
/* For option to try fixing frames with bad CRC. */
|
||||
|
||||
typedef enum retry_e {
|
||||
|
|
369
src/audio_win.c
369
src/audio_win.c
|
@ -133,8 +133,8 @@ static int calcbufsize(int rate, int chans, int bits)
|
|||
|
||||
static struct adev_s {
|
||||
|
||||
enum audio_in_type_e g_audio_in_type;
|
||||
|
||||
enum audio_in_type_e g_audio_in_type;
|
||||
enum audio_out_type_e g_audio_out_type;
|
||||
/*
|
||||
* UDP socket for receiving audio stream.
|
||||
* Buffer, length, and pointer for UDP or stdin.
|
||||
|
@ -146,6 +146,14 @@ static struct adev_s {
|
|||
int stream_len;
|
||||
int stream_next;
|
||||
|
||||
/*
|
||||
* UDP socket for transmitting audio stream.
|
||||
* Buffer and index for stdout or UDP.
|
||||
*/
|
||||
SOCKET udp_out_sock;
|
||||
char stream_out_data[SDR_UDP_BUF_MAXLEN];
|
||||
int stream_out_next;
|
||||
struct sockaddr_storage udp_dest_addr;
|
||||
|
||||
/* For sound output. */
|
||||
/* out_wavehdr.dwUser is used to keep track of output buffer state. */
|
||||
|
@ -286,6 +294,7 @@ int audio_open (struct audio_s *pa)
|
|||
|
||||
|
||||
A->udp_sock = INVALID_SOCKET;
|
||||
A->udp_out_sock = INVALID_SOCKET;
|
||||
|
||||
in_dev_no[a] = WAVE_MAPPER; /* = ((UINT)-1) in mmsystem.h */
|
||||
out_dev_no[a] = WAVE_MAPPER;
|
||||
|
@ -343,28 +352,50 @@ int audio_open (struct audio_s *pa)
|
|||
|
||||
/*
|
||||
* Select output device.
|
||||
* Only soundcard at this point.
|
||||
* Purhaps we'd like to add UDP for an SDR transmitter.
|
||||
* Soundcard, UDP, and stdout supported.
|
||||
*/
|
||||
if (strlen(pa->adev[a].adevice_out) == 1 && isdigit(pa->adev[a].adevice_out[0])) {
|
||||
out_dev_no[a] = atoi(pa->adev[a].adevice_out);
|
||||
}
|
||||
else if (strlen(pa->adev[a].adevice_out) == 2 && isdigit(pa->adev[a].adevice_out[0]) && isdigit(pa->adev[a].adevice_out[1])) {
|
||||
out_dev_no[a] = atoi(pa->adev[a].adevice_out);
|
||||
}
|
||||
if (strcasecmp(pa->adev[a].adevice_out, "stdout") == 0 || strcmp(pa->adev[a].adevice_out, "-") == 0) {
|
||||
A->g_audio_out_type = AUDIO_OUT_TYPE_STDOUT;
|
||||
if (!dw_printf_redirected()) {
|
||||
text_color_set (DW_COLOR_ERROR);
|
||||
dw_printf ("stdout must only be used with the -O option\n");
|
||||
return (-1);
|
||||
}
|
||||
/* Change - to stdout for readability. */
|
||||
strlcpy (pa->adev[a].adevice_out, "stdout", sizeof(pa->adev[a].adevice_out));
|
||||
} else if (strncasecmp(pa->adev[a].adevice_out, "udp:", 4) == 0) {
|
||||
A->g_audio_out_type = AUDIO_OUT_TYPE_SDR_UDP;
|
||||
// User must supply address and port
|
||||
if (strcasecmp(pa->adev[a].adevice_out, "udp:") == 0 ||
|
||||
strlen(pa->adev[a].adevice_out) < 7 ||
|
||||
strstr(pa->adev[a].adevice_out+5, ":") == 0) {
|
||||
text_color_set (DW_COLOR_ERROR);
|
||||
dw_printf ("Destination address and port must be supplied for UDP output\n");
|
||||
return (-1);
|
||||
}
|
||||
} else {
|
||||
A->g_audio_out_type = AUDIO_OUT_TYPE_SOUNDCARD;
|
||||
|
||||
if ((UINT)(out_dev_no[a]) == WAVE_MAPPER && strlen(pa->adev[a].adevice_out) >= 1) {
|
||||
num_devices = waveOutGetNumDevs();
|
||||
for (n=0 ; n<num_devices && (UINT)(out_dev_no[a]) == WAVE_MAPPER ; n++) {
|
||||
if ( ! waveOutGetDevCaps(n, &woc, sizeof(WAVEOUTCAPS))) {
|
||||
if (strstr(woc.szPname, pa->adev[a].adevice_out) != NULL) {
|
||||
out_dev_no[a] = n;
|
||||
if (strlen(pa->adev[a].adevice_out) == 1 && isdigit(pa->adev[a].adevice_out[0])) {
|
||||
out_dev_no[a] = atoi(pa->adev[a].adevice_out);
|
||||
}
|
||||
else if (strlen(pa->adev[a].adevice_out) == 2 && isdigit(pa->adev[a].adevice_out[0]) && isdigit(pa->adev[a].adevice_out[1])) {
|
||||
out_dev_no[a] = atoi(pa->adev[a].adevice_out);
|
||||
}
|
||||
|
||||
if ((UINT)(out_dev_no[a]) == WAVE_MAPPER && strlen(pa->adev[a].adevice_out) >= 1) {
|
||||
num_devices = waveOutGetNumDevs();
|
||||
for (n=0 ; n<num_devices && (UINT)(out_dev_no[a]) == WAVE_MAPPER ; n++) {
|
||||
if ( ! waveOutGetDevCaps(n, &woc, sizeof(WAVEOUTCAPS))) {
|
||||
if (strstr(woc.szPname, pa->adev[a].adevice_out) != NULL) {
|
||||
out_dev_no[a] = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((UINT)(out_dev_no[a]) == WAVE_MAPPER) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("\"%s\" doesn't match any of the output devices.\n", pa->adev[a].adevice_out);
|
||||
if ((UINT)(out_dev_no[a]) == WAVE_MAPPER) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("\"%s\" doesn't match any of the output devices.\n", pa->adev[a].adevice_out);
|
||||
}
|
||||
}
|
||||
}
|
||||
} /* if defined */
|
||||
|
@ -424,7 +455,7 @@ int audio_open (struct audio_s *pa)
|
|||
|
||||
struct adev_s *A = &(adev[a]);
|
||||
|
||||
/* Display stdin or udp:port if appropriate. */
|
||||
/* Display stdin or udp:port if appropriate. */
|
||||
|
||||
if (A->g_audio_in_type != AUDIO_IN_TYPE_SOUNDCARD) {
|
||||
|
||||
|
@ -498,6 +529,36 @@ int audio_open (struct audio_s *pa)
|
|||
}
|
||||
}
|
||||
|
||||
// Add UDP or stdout to end of device list if used.
|
||||
|
||||
for (a=0; a<MAX_ADEVS; a++) {
|
||||
if (pa->adev[a].defined) {
|
||||
|
||||
struct adev_s *A = &(adev[a]);
|
||||
|
||||
/* Display stdout or udp:port if appropriate. */
|
||||
|
||||
if (A->g_audio_out_type != AUDIO_OUT_TYPE_SOUNDCARD) {
|
||||
|
||||
int aaa;
|
||||
for (aaa=0; aaa<MAX_ADEVS; aaa++) {
|
||||
if (pa->adev[aaa].defined) {
|
||||
dw_printf (" %c", a == aaa ? '*' : ' ');
|
||||
|
||||
}
|
||||
}
|
||||
dw_printf (" %s ", pa->adev[a].adevice_out); /* should be UDP:nnnn or stdout */
|
||||
|
||||
if (pa->adev[a].num_channels == 2) {
|
||||
dw_printf (" (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1);
|
||||
}
|
||||
else {
|
||||
dw_printf (" (channel %d)", ADEVFIRSTCHAN(a));
|
||||
}
|
||||
dw_printf ("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Open for each audio device input/output pair.
|
||||
|
@ -523,32 +584,112 @@ int audio_open (struct audio_s *pa)
|
|||
|
||||
/*
|
||||
* Open the audio output device.
|
||||
* Soundcard is only possibility at this time.
|
||||
* Soundcard and stdout are only possibility at this time.
|
||||
*/
|
||||
|
||||
err = waveOutOpen (&(A->audio_out_handle), out_dev_no[a], &wf, (DWORD_PTR)out_callback, a, CALLBACK_FUNCTION);
|
||||
if (err != MMSYSERR_NOERROR) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Could not open audio device for output.\n");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
switch (A->g_audio_out_type) {
|
||||
|
||||
case AUDIO_OUT_TYPE_SOUNDCARD:
|
||||
|
||||
err = waveOutOpen (&(A->audio_out_handle), out_dev_no[a], &wf, (DWORD_PTR)out_callback, a, CALLBACK_FUNCTION);
|
||||
if (err != MMSYSERR_NOERROR) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Could not open audio device for output.\n");
|
||||
return (-1);
|
||||
}
|
||||
break;
|
||||
/*
|
||||
* Set up the output buffers.
|
||||
* We use dwUser to indicate it is available for filling.
|
||||
*/
|
||||
|
||||
memset ((void*)(A->out_wavehdr), 0, sizeof(A->out_wavehdr));
|
||||
memset ((void*)(A->out_wavehdr), 0, sizeof(A->out_wavehdr));
|
||||
|
||||
for (n = 0; n < NUM_OUT_BUF; n++) {
|
||||
A->out_wavehdr[n].lpData = malloc(A->outbuf_size);
|
||||
A->out_wavehdr[n].dwUser = DWU_FILLING;
|
||||
A->out_wavehdr[n].dwBufferLength = 0;
|
||||
}
|
||||
A->out_current = 0;
|
||||
for (n = 0; n < NUM_OUT_BUF; n++) {
|
||||
A->out_wavehdr[n].lpData = malloc(A->outbuf_size);
|
||||
A->out_wavehdr[n].dwUser = DWU_FILLING;
|
||||
A->out_wavehdr[n].dwBufferLength = 0;
|
||||
}
|
||||
A->out_current = 0;
|
||||
|
||||
case AUDIO_OUT_TYPE_SDR_UDP:;
|
||||
|
||||
WSADATA wsadata;
|
||||
struct addrinfo ai_out;
|
||||
struct addrinfo *ai_res;
|
||||
char udp_outhost[256];
|
||||
char *udp_outport;
|
||||
int err, res;
|
||||
|
||||
err = WSAStartup (MAKEWORD(2,2), &wsadata);
|
||||
if (err != 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf("WSAStartup failed: %d\n", err);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf("Could not find a usable version of Winsock.dll\n");
|
||||
WSACleanup();
|
||||
return (-1);
|
||||
}
|
||||
|
||||
memset((char *) &ai_out, 0, sizeof(ai_out));
|
||||
ai_out.ai_socktype = SOCK_DGRAM;
|
||||
ai_out.ai_protocol = IPPROTO_UDP;
|
||||
|
||||
strncpy(udp_outhost, pa->adev[a].adevice_out + 4, 255);
|
||||
udp_outhost[255] = 0;
|
||||
udp_outport = strstr(udp_outhost, ":");
|
||||
*udp_outport++ = 0;
|
||||
|
||||
if (strlen(udp_outport) == 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf("UDP output destination port must be supplied\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
err = getaddrinfo(udp_outhost, udp_outport, &ai_out, &ai_res);
|
||||
if (err != 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf("Error parsing/resolving UDP output address\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ai_res->ai_family == AF_INET6) {
|
||||
res = sizeof(struct sockaddr_in6);
|
||||
} else {
|
||||
res = sizeof(struct sockaddr_in);
|
||||
}
|
||||
|
||||
// Create UDP Socket
|
||||
|
||||
A->udp_out_sock = socket(ai_res->ai_family, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (A->udp_out_sock == INVALID_SOCKET) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Couldn't create socket, errno %d\n", WSAGetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(&A->udp_dest_addr, ai_res->ai_addr, res);
|
||||
A->stream_out_next = 0;
|
||||
|
||||
break;
|
||||
|
||||
case AUDIO_OUT_TYPE_STDOUT:
|
||||
|
||||
setmode (STDOUT_FILENO, _O_BINARY);
|
||||
A->stream_out_next= 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf ("Internal error, invalid audio_out_type\n");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Open audio input device.
|
||||
* More possibilities here: soundcard, UDP port, stdin.
|
||||
|
@ -942,49 +1083,69 @@ int audio_put (int a, int c)
|
|||
|
||||
struct adev_s *A;
|
||||
A = &(adev[a]);
|
||||
|
||||
|
||||
switch (A->g_audio_out_type) {
|
||||
|
||||
case AUDIO_OUT_TYPE_SOUNDCARD:
|
||||
|
||||
/*
|
||||
* Wait if no buffers are available.
|
||||
* Don't use p yet because compiler might might consider dwFlags a loop invariant.
|
||||
*/
|
||||
|
||||
int timeout = 10;
|
||||
while ( A->out_wavehdr[A->out_current].dwUser == DWU_PLAYING) {
|
||||
SLEEP_MS (ONE_BUF_TIME);
|
||||
timeout--;
|
||||
if (timeout <= 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
int timeout = 10;
|
||||
while ( A->out_wavehdr[A->out_current].dwUser == DWU_PLAYING) {
|
||||
SLEEP_MS (ONE_BUF_TIME);
|
||||
timeout--;
|
||||
if (timeout <= 0) {
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
|
||||
// TODO: open issues 78 & 165. How can we avoid/improve this?
|
||||
|
||||
dw_printf ("Audio output failure waiting for buffer.\n");
|
||||
dw_printf ("This can occur when we are producing audio output for\n");
|
||||
dw_printf ("transmit and the operating system doesn't provide buffer\n");
|
||||
dw_printf ("space after waiting and retrying many times.\n");
|
||||
//dw_printf ("In recent years, this has been reported only when running the\n");
|
||||
//dw_printf ("Windows version with VMWare on a Macintosh.\n");
|
||||
ptt_term ();
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
dw_printf ("Audio output failure waiting for buffer.\n");
|
||||
dw_printf ("This can occur when we are producing audio output for\n");
|
||||
dw_printf ("transmit and the operating system doesn't provide buffer\n");
|
||||
dw_printf ("space after waiting and retrying many times.\n");
|
||||
//dw_printf ("In recent years, this has been reported only when running the\n");
|
||||
//dw_printf ("Windows version with VMWare on a Macintosh.\n");
|
||||
ptt_term ();
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
|
||||
p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current]));
|
||||
p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current]));
|
||||
|
||||
if (p->dwUser == DWU_DONE) {
|
||||
waveOutUnprepareHeader (A->audio_out_handle, p, sizeof(WAVEHDR));
|
||||
p->dwBufferLength = 0;
|
||||
p->dwUser = DWU_FILLING;
|
||||
}
|
||||
if (p->dwUser == DWU_DONE) {
|
||||
waveOutUnprepareHeader (A->audio_out_handle, p, sizeof(WAVEHDR));
|
||||
p->dwBufferLength = 0;
|
||||
p->dwUser = DWU_FILLING;
|
||||
}
|
||||
|
||||
/* Should never be full at this point. */
|
||||
/* Should never be full at this point. */
|
||||
|
||||
assert (p->dwBufferLength >= 0);
|
||||
assert (p->dwBufferLength < (DWORD)(A->outbuf_size));
|
||||
assert (p->dwBufferLength >= 0);
|
||||
assert (p->dwBufferLength < (DWORD)(A->outbuf_size));
|
||||
|
||||
p->lpData[p->dwBufferLength++] = c;
|
||||
p->lpData[p->dwBufferLength++] = c;
|
||||
|
||||
if (p->dwBufferLength == (DWORD)(A->outbuf_size)) {
|
||||
return (audio_flush(a));
|
||||
if (p->dwBufferLength == (DWORD)(A->outbuf_size)) {
|
||||
return (audio_flush(a));
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case AUDIO_OUT_TYPE_SDR_UDP:
|
||||
case AUDIO_OUT_TYPE_STDOUT:
|
||||
|
||||
A->stream_out_data[A->stream_out_next++] = c;
|
||||
|
||||
assert(A->stream_out_next > 0);
|
||||
assert(A->stream_out_next <= SDR_UDP_BUF_MAXLEN);
|
||||
|
||||
if (A->stream_out_next == SDR_UDP_BUF_MAXLEN) {
|
||||
return (audio_flush(a));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return (0);
|
||||
|
@ -1013,29 +1174,77 @@ int audio_flush (int a)
|
|||
WAVEHDR *p;
|
||||
MMRESULT e;
|
||||
struct adev_s *A;
|
||||
int res;
|
||||
char *ptr;
|
||||
unsigned int len;
|
||||
|
||||
|
||||
A = &(adev[a]);
|
||||
|
||||
p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current]));
|
||||
|
||||
if (p->dwUser == DWU_FILLING && p->dwBufferLength > 0) {
|
||||
switch (A->g_audio_out_type) {
|
||||
case AUDIO_OUT_TYPE_SOUNDCARD:
|
||||
p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current]));
|
||||
|
||||
p->dwUser = DWU_PLAYING;
|
||||
if (p->dwUser == DWU_FILLING && p->dwBufferLength > 0) {
|
||||
|
||||
waveOutPrepareHeader(A->audio_out_handle, p, sizeof(WAVEHDR));
|
||||
p->dwUser = DWU_PLAYING;
|
||||
|
||||
e = waveOutWrite(A->audio_out_handle, p, sizeof(WAVEHDR));
|
||||
if (e != MMSYSERR_NOERROR) {
|
||||
text_color_set (DW_COLOR_ERROR);
|
||||
dw_printf ("audio out write error %d\n", e);
|
||||
waveOutPrepareHeader(A->audio_out_handle, p, sizeof(WAVEHDR));
|
||||
|
||||
/* I don't expect this to ever happen but if it */
|
||||
/* does, make the buffer available for filling. */
|
||||
e = waveOutWrite(A->audio_out_handle, p, sizeof(WAVEHDR));
|
||||
if (e != MMSYSERR_NOERROR) {
|
||||
text_color_set (DW_COLOR_ERROR);
|
||||
dw_printf ("audio out write error %d\n", e);
|
||||
|
||||
/* I don't expect this to ever happen but if it */
|
||||
/* does, make the buffer available for filling. */
|
||||
|
||||
p->dwUser = DWU_DONE;
|
||||
return (-1);
|
||||
}
|
||||
A->out_current = (A->out_current + 1) % NUM_OUT_BUF;
|
||||
}
|
||||
break;
|
||||
|
||||
case AUDIO_OUT_TYPE_STDOUT:
|
||||
|
||||
ptr = A->stream_out_data;
|
||||
len = A->stream_out_next;
|
||||
|
||||
while (len > 0) {
|
||||
res = write(STDOUT_FILENO, ptr, len);
|
||||
if (res < 0) {
|
||||
text_color_set (DW_COLOR_ERROR);
|
||||
dw_printf ("stdout audio write error %d\n", res);
|
||||
return (-1);
|
||||
}
|
||||
ptr += res;
|
||||
len -= res;
|
||||
}
|
||||
|
||||
A->stream_out_next = 0;
|
||||
break;
|
||||
|
||||
case AUDIO_OUT_TYPE_SDR_UDP:
|
||||
|
||||
ptr = A->stream_out_data;
|
||||
len = A->stream_out_next;
|
||||
|
||||
while (len > 0) {
|
||||
res = sendto(A->udp_out_sock, ptr, len, 0, (struct sockaddr *)&A->udp_dest_addr, sizeof(struct sockaddr_storage));
|
||||
if (res < 0) {
|
||||
text_color_set (DW_COLOR_ERROR);
|
||||
dw_printf ("Error %d writing to UDP socket.\n", res);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
ptr += res;
|
||||
len -= res;
|
||||
}
|
||||
|
||||
A->stream_out_next = 0;
|
||||
break;
|
||||
|
||||
p->dwUser = DWU_DONE;
|
||||
return (-1);
|
||||
}
|
||||
A->out_current = (A->out_current + 1) % NUM_OUT_BUF;
|
||||
}
|
||||
return (0);
|
||||
|
||||
|
|
|
@ -792,6 +792,8 @@ int main ()
|
|||
strcpy (addrs[1], "WB2OSZ-15");
|
||||
num_addr = 2;
|
||||
|
||||
text_color_init (1, 0);
|
||||
|
||||
/* U frame */
|
||||
|
||||
for (ftype = frame_type_U_SABME; ftype <= frame_type_U_TEST; ftype++) {
|
||||
|
|
|
@ -113,7 +113,7 @@
|
|||
|
||||
int main (void)
|
||||
{
|
||||
text_color_init (0); // Turn off text color.
|
||||
text_color_init (0, 0); // Turn off text color.
|
||||
#if defined(__OpenBSD__) || defined(__FreeBSD__)
|
||||
dw_printf ("CM108 PTT support is not available for this operating system.\n");
|
||||
#else
|
||||
|
@ -340,7 +340,7 @@ int main (int argc, char **argv)
|
|||
int num_things;
|
||||
int i;
|
||||
|
||||
text_color_init (0); // Turn off text color.
|
||||
text_color_init (0, 0); // Turn off text color.
|
||||
text_color_set(DW_COLOR_INFO);
|
||||
|
||||
if (argc >=2) {
|
||||
|
|
|
@ -5330,7 +5330,7 @@ int main (int argc, char *argv[])
|
|||
}
|
||||
|
||||
// If you don't like the text colors, use 0 instead of 1 here.
|
||||
text_color_init(1);
|
||||
text_color_init(1, 0);
|
||||
text_color_set(DW_COLOR_INFO);
|
||||
|
||||
while (fgets(stuff, sizeof(stuff), stdin) != NULL)
|
||||
|
|
|
@ -765,6 +765,7 @@ int main (int argc, char *argv[])
|
|||
strlcpy(mycall, "WB2OSZ-9", sizeof(mycall));
|
||||
|
||||
dedupe_init (4);
|
||||
text_color_init (1, 0);
|
||||
|
||||
/*
|
||||
* Compile the patterns.
|
||||
|
|
|
@ -242,6 +242,8 @@ int main (int argc, char *argv[])
|
|||
char x_opt_mode = ' '; /* "-x N" option for transmitting calibration tones. */
|
||||
int x_opt_chan = 0; /* Split into 2 parts. Mode e.g. m, a, and optional channel. */
|
||||
|
||||
int O_opt = 0; /* Redirect text io to stderr for use with stdout audio */
|
||||
|
||||
strlcpy(l_opt_logdir, "", sizeof(l_opt_logdir));
|
||||
strlcpy(L_opt_logfile, "", sizeof(L_opt_logfile));
|
||||
strlcpy(P_opt, "", sizeof(P_opt));
|
||||
|
@ -270,23 +272,28 @@ int main (int argc, char *argv[])
|
|||
#endif
|
||||
|
||||
/*
|
||||
* Pre-scan the command line options for the text color option.
|
||||
* We need to set this before any text output.
|
||||
* Pre-scan the command line options for the text color and stdout redirect options.
|
||||
* We need to set these before any text output.
|
||||
* Default will be no colors if stdout is not a terminal (i.e. piped into
|
||||
* something else such as "tee") but command line can override this.
|
||||
*/
|
||||
for (j=1; j<argc; j++) {
|
||||
if (strcmp(argv[j], "-O") == 0) {
|
||||
O_opt = 1;
|
||||
}
|
||||
}
|
||||
|
||||
#if __WIN32__
|
||||
t_opt = _isatty(_fileno(stdout)) > 0;
|
||||
t_opt = _isatty(_fileno(O_opt ? stderr : stdout)) > 0;
|
||||
#else
|
||||
t_opt = isatty(fileno(stdout));
|
||||
t_opt = isatty(fileno(O_opt ? stderr : stdout));
|
||||
#endif
|
||||
/* 1 = normal, 0 = no text colors. */
|
||||
/* 2, 3, ... alternate escape sequences for different terminals. */
|
||||
|
||||
// FIXME: consider case of no space between t and number.
|
||||
|
||||
for (j=1; j<argc-1; j++) {
|
||||
for (j=1; j<argc; j++) {
|
||||
if (strcmp(argv[j], "-t") == 0) {
|
||||
t_opt = atoi (argv[j+1]);
|
||||
//dw_printf ("DEBUG: text color option = %d.\n", t_opt);
|
||||
|
@ -299,7 +306,7 @@ int main (int argc, char *argv[])
|
|||
// Might want to print OS version here. For Windows, see:
|
||||
// https://msdn.microsoft.com/en-us/library/ms724451(v=VS.85).aspx
|
||||
|
||||
text_color_init(t_opt);
|
||||
text_color_init(t_opt, O_opt);
|
||||
text_color_set(DW_COLOR_INFO);
|
||||
dw_printf ("Dire Wolf version %d.%d (%s) BETA TEST 7\n", MAJOR_VERSION, MINOR_VERSION, __DATE__);
|
||||
//dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "G", __DATE__);
|
||||
|
@ -421,7 +428,7 @@ int main (int argc, char *argv[])
|
|||
|
||||
/* ':' following option character means arg is required. */
|
||||
|
||||
c = getopt_long(argc, argv, "hP:B:gjJD:U:c:px:r:b:n:d:q:t:ul:L:Sa:E:T:e:X:AI:i:",
|
||||
c = getopt_long(argc, argv, "hP:B:gjJD:U:c:px:r:b:n:d:q:t:ul:L:Sa:E:T:e:X:AI:i:O",
|
||||
long_options, &option_index);
|
||||
if (c == -1)
|
||||
break;
|
||||
|
@ -738,6 +745,10 @@ int main (int argc, char *argv[])
|
|||
A_opt_ais_to_obj = 1;
|
||||
break;
|
||||
|
||||
case 'O': /* Was handled earlier. -O Redirects output to stderr. */
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
|
||||
/* Should not be here. */
|
||||
|
|
|
@ -532,6 +532,7 @@ int main ()
|
|||
my_audio_config.chan_medium[c] = MEDIUM_RADIO;
|
||||
my_audio_config.achan[c].dtmf_decode = DTMF_DECODE_ON;
|
||||
|
||||
text_color_init (1, 0);
|
||||
dtmf_init(&my_audio_config, 50);
|
||||
|
||||
text_color_set(DW_COLOR_INFO);
|
||||
|
|
|
@ -853,6 +853,7 @@ int main (int argc, char *argv[])
|
|||
char result[100];
|
||||
int errors = 0;
|
||||
|
||||
text_color_init (1, 0);
|
||||
|
||||
/*********** Position ***********/
|
||||
|
||||
|
|
|
@ -73,6 +73,7 @@ static int fx25_test_count = 0;
|
|||
|
||||
int main ()
|
||||
{
|
||||
text_color_init(1, 0);
|
||||
fx25_init(3);
|
||||
|
||||
for (int i = CTAG_MIN; i <= CTAG_MAX; i++) {
|
||||
|
@ -480,4 +481,4 @@ static int my_unstuff (int chan, int subchan, int slice, unsigned char * restric
|
|||
|
||||
} // my_unstuff
|
||||
|
||||
// end fx25_rec.c
|
||||
// end fx25_rec.c
|
||||
|
|
|
@ -55,6 +55,7 @@ static unsigned char preload[] = {
|
|||
|
||||
int main ()
|
||||
{
|
||||
text_color_init(1, 0);
|
||||
text_color_set(DW_COLOR_ERROR);
|
||||
dw_printf("fxsend - FX.25 unit test.\n");
|
||||
dw_printf("This generates 11 files named fx01.dat, fx02.dat, ..., fx0b.dat\n");
|
||||
|
@ -333,4 +334,4 @@ static int stuff_it (unsigned char *in, int ilen, unsigned char *out, int osize)
|
|||
|
||||
} // end stuff_it
|
||||
|
||||
// end fx25_send.c
|
||||
// end fx25_send.c
|
||||
|
|
|
@ -265,6 +265,8 @@ int main(int argc, char **argv)
|
|||
|
||||
strlcpy (output_file, "", sizeof(output_file));
|
||||
|
||||
text_color_init (1, 0);
|
||||
|
||||
/*
|
||||
* Parse the command line options.
|
||||
*/
|
||||
|
|
|
@ -53,7 +53,7 @@ static void decode_bitstream(void);
|
|||
int main ()
|
||||
{
|
||||
int enable_color = 1;
|
||||
text_color_init (enable_color);
|
||||
text_color_init (enable_color, 0);
|
||||
|
||||
int enable_debug_out = 0;
|
||||
il2p_init(enable_debug_out);
|
||||
|
@ -974,4 +974,4 @@ alevel_t demod_get_audio_level (int chan, int subchan)
|
|||
return (alevel);
|
||||
}
|
||||
|
||||
// end il2p_test.c
|
||||
// end il2p_test.c
|
||||
|
|
|
@ -179,7 +179,7 @@ static void trim (char *stuff)
|
|||
|
||||
int main (int argc, char *argv[])
|
||||
{
|
||||
text_color_init (0); // Turn off text color.
|
||||
text_color_init (0, 0); // Turn off text color.
|
||||
// It could interfere with trying to pipe stdout to some other application.
|
||||
|
||||
#if __WIN32__
|
||||
|
|
|
@ -899,6 +899,8 @@ int main (int argc, char *argv[])
|
|||
double dlat, dlon;
|
||||
double d, b;
|
||||
|
||||
text_color_init (1, 0);
|
||||
|
||||
/* Latitude to APRS format. */
|
||||
|
||||
latitude_to_str (45.25, 0, result);
|
||||
|
@ -1086,4 +1088,4 @@ int main (int argc, char *argv[])
|
|||
#endif
|
||||
|
||||
|
||||
/* end latlong.c */
|
||||
/* end latlong.c */
|
||||
|
|
|
@ -1527,6 +1527,7 @@ static void pftest (int test_num, char *filter, char *packet, int expected);
|
|||
int main ()
|
||||
{
|
||||
|
||||
text_color_init (1, 0);
|
||||
dw_printf ("Quick test for packet filtering.\n");
|
||||
dw_printf ("Some error messages are normal. Look at the final success/fail message.\n");
|
||||
|
||||
|
|
|
@ -1069,6 +1069,7 @@ int main ( )
|
|||
strlcpy (comment, "", sizeof(comment));
|
||||
|
||||
|
||||
text_color_init(1, 0);
|
||||
text_color_set(DW_COLOR_INFO);
|
||||
dw_printf ("Unit test for telemetry decoding functions...\n");
|
||||
|
||||
|
|
|
@ -171,14 +171,19 @@ static const char clear_eos[] = "\e[0J";
|
|||
*/
|
||||
|
||||
static int g_enable_color = 1;
|
||||
static FILE *g_dw_printf_dest = 0;
|
||||
|
||||
|
||||
void text_color_init (int enable_color)
|
||||
void text_color_init (int enable_color, int redirect_output)
|
||||
{
|
||||
|
||||
if (redirect_output != 0) {
|
||||
g_dw_printf_dest = stderr;
|
||||
} else {
|
||||
g_dw_printf_dest = stdout;
|
||||
}
|
||||
|
||||
#if __WIN32__
|
||||
|
||||
g_enable_color = enable_color;
|
||||
|
||||
if (g_enable_color != 0) {
|
||||
|
||||
|
@ -189,7 +194,12 @@ void text_color_init (int enable_color)
|
|||
COORD coord;
|
||||
DWORD nwritten;
|
||||
|
||||
h = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
if (redirect_output != 0) {
|
||||
h = GetStdHandle(STD_ERROR_HANDLE);
|
||||
} else {
|
||||
h = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
}
|
||||
|
||||
if (h != NULL && h != INVALID_HANDLE_VALUE) {
|
||||
|
||||
GetConsoleScreenBufferInfo (h, &csbi);
|
||||
|
@ -208,18 +218,18 @@ void text_color_init (int enable_color)
|
|||
if (enable_color < 0 || enable_color > MAX_T) {
|
||||
int t;
|
||||
for (t = 0; t <= MAX_T; t++) {
|
||||
text_color_init (t);
|
||||
printf ("-t %d", t);
|
||||
if (t) printf (" [white background] ");
|
||||
printf ("\n");
|
||||
printf ("%sBlack ", t_black[t]);
|
||||
printf ("%sRed ", t_red[t]);
|
||||
printf ("%sGreen ", t_green[t]);
|
||||
printf ("%sDark-Green ", t_dark_green[t]);
|
||||
printf ("%sYellow ", t_yellow[t]);
|
||||
printf ("%sBlue ", t_blue[t]);
|
||||
printf ("%sMagenta ", t_magenta[t]);
|
||||
printf ("%sCyan \n", t_cyan[t]);
|
||||
text_color_init (t, redirect_output);
|
||||
fprintf (g_dw_printf_dest,"-t %d", t);
|
||||
if (t) fprintf (g_dw_printf_dest, " [white background] ");
|
||||
fprintf (g_dw_printf_dest,"\n");
|
||||
fprintf (g_dw_printf_dest,"%sBlack ", t_black[t]);
|
||||
fprintf (g_dw_printf_dest,"%sRed ", t_red[t]);
|
||||
fprintf (g_dw_printf_dest,"%sGreen ", t_green[t]);
|
||||
fprintf (g_dw_printf_dest,"%sDark-Green ", t_dark_green[t]);
|
||||
fprintf (g_dw_printf_dest,"%sYellow ", t_yellow[t]);
|
||||
fprintf (g_dw_printf_dest,"%sBlue ", t_blue[t]);
|
||||
fprintf (g_dw_printf_dest, "%sMagenta ", t_magenta[t]);
|
||||
fprintf (g_dw_printf_dest, "%sCyan \n", t_cyan[t]);
|
||||
}
|
||||
exit (EXIT_SUCCESS);
|
||||
}
|
||||
|
@ -232,9 +242,9 @@ void text_color_init (int enable_color)
|
|||
if (t < 0) t = 0;
|
||||
if (t > MAX_T) t = MAX_T;
|
||||
|
||||
printf ("%s", t_background_white[t]);
|
||||
printf ("%s", clear_eos);
|
||||
printf ("%s", t_black[t]);
|
||||
fprintf (g_dw_printf_dest, "%s", t_background_white[t]);
|
||||
fprintf (g_dw_printf_dest, "%s", clear_eos);
|
||||
fprintf (g_dw_printf_dest, "%s", t_black[t]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -285,7 +295,11 @@ void text_color_set ( enum dw_color_e c )
|
|||
break;
|
||||
}
|
||||
|
||||
h = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
if (dw_printf_redirected()) {
|
||||
h = GetStdHandle(STD_ERROR_HANDLE);
|
||||
} else {
|
||||
h = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
}
|
||||
|
||||
if (h != NULL && h != INVALID_HANDLE_VALUE) {
|
||||
SetConsoleTextAttribute (h, attr);
|
||||
|
@ -310,30 +324,30 @@ void text_color_set ( enum dw_color_e c )
|
|||
|
||||
default:
|
||||
case DW_COLOR_INFO:
|
||||
printf ("%s", t_black[t]);
|
||||
fprintf (g_dw_printf_dest, "%s", t_black[t]);
|
||||
break;
|
||||
|
||||
case DW_COLOR_ERROR:
|
||||
printf ("%s", t_red[t]);
|
||||
fprintf (g_dw_printf_dest, "%s", t_red[t]);
|
||||
break;
|
||||
|
||||
case DW_COLOR_REC:
|
||||
// Bright green is very difficult to read against a while background.
|
||||
// Let's use dark green instead. release 1.6.
|
||||
//printf ("%s", t_green[t]);
|
||||
printf ("%s", t_dark_green[t]);
|
||||
fprintf (g_dw_printf_dest, "%s", t_dark_green[t]);
|
||||
break;
|
||||
|
||||
case DW_COLOR_DECODED:
|
||||
printf ("%s", t_blue[t]);
|
||||
fprintf (g_dw_printf_dest, "%s", t_blue[t]);
|
||||
break;
|
||||
|
||||
case DW_COLOR_XMIT:
|
||||
printf ("%s", t_magenta[t]);
|
||||
fprintf (g_dw_printf_dest, "%s", t_magenta[t]);
|
||||
break;
|
||||
|
||||
case DW_COLOR_DEBUG:
|
||||
printf ("%s", t_dark_green[t]);
|
||||
fprintf (g_dw_printf_dest, "%s", t_dark_green[t]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -377,17 +391,21 @@ int dw_printf (const char *fmt, ...)
|
|||
|
||||
// TODO: other possible destinations...
|
||||
|
||||
fputs (buffer, stdout);
|
||||
fputs (buffer, g_dw_printf_dest);
|
||||
fflush (g_dw_printf_dest);
|
||||
return (len);
|
||||
}
|
||||
|
||||
|
||||
int dw_printf_redirected ()
|
||||
{
|
||||
return g_dw_printf_dest != stdout;
|
||||
}
|
||||
|
||||
#if TESTC
|
||||
main ()
|
||||
{
|
||||
printf ("Initial condition\n");
|
||||
text_color_init (1);
|
||||
text_color_init (1, 0);
|
||||
printf ("After text_color_init\n");
|
||||
text_color_set(DW_COLOR_INFO); printf ("Info\n");
|
||||
text_color_set(DW_COLOR_ERROR); printf ("Error\n");
|
||||
|
|
|
@ -22,7 +22,7 @@ enum dw_color_e { DW_COLOR_INFO, /* black */
|
|||
typedef enum dw_color_e dw_color_t;
|
||||
|
||||
|
||||
void text_color_init (int enable_color);
|
||||
void text_color_init (int enable_color, int redirect_output);
|
||||
void text_color_set (dw_color_t c);
|
||||
void text_color_term (void);
|
||||
|
||||
|
@ -55,4 +55,6 @@ int dw_printf (const char *fmt, ...)
|
|||
__attribute__((format(printf,1,2))); /* gnu C lib. */
|
||||
#endif
|
||||
|
||||
int dw_printf_redirected ();
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1782,6 +1782,7 @@ static void test_tt2text (char *buttons, char *expect_mp, char *expect_2k, char
|
|||
int main (int argc, char *argv[])
|
||||
{
|
||||
|
||||
text_color_init (1, 0);
|
||||
text_color_set (DW_COLOR_INFO);
|
||||
dw_printf ("Test conversions between normal text and DTMF representation.\n");
|
||||
dw_printf ("Some error messages are normal. Just look for number of errors at end.\n");
|
||||
|
|
Loading…
Reference in New Issue