diff --git a/CHANGES.md b/CHANGES.md index 92975bf3..182fe64d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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: ### diff --git a/conf/generic.conf b/conf/generic.conf index d9f7b355..631824dc 100644 --- a/conf/generic.conf +++ b/conf/generic.conf @@ -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% \ No newline at end of file +%C% diff --git a/man/direwolf.1 b/man/direwolf.1 index 93f786dc..aee9ee2b 100644 --- a/man/direwolf.1 +++ b/man/direwolf.1 @@ -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 " diff --git a/src/aprs_tt.c b/src/aprs_tt.c index 7b125759..f8c8edb3 100644 --- a/src/aprs_tt.c +++ b/src/aprs_tt.c @@ -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; diff --git a/src/atest.c b/src/atest.c index c5f4ec50..ae24e8bd 100644 --- a/src/atest.c +++ b/src/atest.c @@ -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); /* diff --git a/src/audio.c b/src/audio.c index 82dec22a..737247ad 100644 --- a/src/audio.c +++ b/src/audio.c @@ -75,6 +75,7 @@ #include #include #include +#include #include @@ -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 (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); + } + } 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; + } + switch (adev[a].g_audio_out_type) { + case AUDIO_OUT_TYPE_STDOUT: + adev[a].outbuf_size_in_bytes = 1024; + break; + + 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); + 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); - } + 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"); + 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); - } + 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) { + int res; + unsigned char *ptr; + int len; + + 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; + int k; + unsigned char *psound; + int retries = 10; + snd_pcm_status_t *status; - assert (adev[a].audio_out_handle != NULL); + 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) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Audio output data underrun.\n"); + if (k == -EPIPE) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Audio output data underrun.\n"); - /* No problemo. Recover and go around again. */ + /* 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)); + 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? */ + /* 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. */ + 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); - 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); - } - } + /* 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"); + text_color_set(DW_COLOR_ERROR); + dw_printf ("Audio write error retry count exceeded.\n"); - adev[a].outbuf_len = 0; - return (-1); + adev[a].outbuf_len = 0; + 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"); - adev[a].outbuf_len = 0; - return (-1); - } - if (k < len) { - /* presumably full but didn't block. */ - usleep (10000); - } - ptr += k; - len -= k; - } + 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 (0); + adev[a].outbuf_len = 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 */ diff --git a/src/audio.h b/src/audio.h index cb5ca94e..dc8444e4 100644 --- a/src/audio.h +++ b/src/audio.h @@ -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 { diff --git a/src/audio_win.c b/src/audio_win.c index 85a1548b..adba65ee 100644 --- a/src/audio_win.c +++ b/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 ; nadev[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 ; nadev[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; aadev[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; aaaadev[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; + } + + /* Should never be full at this point. */ + + assert (p->dwBufferLength >= 0); + assert (p->dwBufferLength < (DWORD)(A->outbuf_size)); + + p->lpData[p->dwBufferLength++] = c; + + if (p->dwBufferLength == (DWORD)(A->outbuf_size)) { + return (audio_flush(a)); + } + break; - 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. */ + case AUDIO_OUT_TYPE_SDR_UDP: + case AUDIO_OUT_TYPE_STDOUT: - assert (p->dwBufferLength >= 0); - assert (p->dwBufferLength < (DWORD)(A->outbuf_size)); + A->stream_out_data[A->stream_out_next++] = c; - p->lpData[p->dwBufferLength++] = c; + assert(A->stream_out_next > 0); + assert(A->stream_out_next <= SDR_UDP_BUF_MAXLEN); - if (p->dwBufferLength == (DWORD)(A->outbuf_size)) { - return (audio_flush(a)); + 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); diff --git a/src/ax25_pad2.c b/src/ax25_pad2.c index 347df4b1..8b57f5bc 100644 --- a/src/ax25_pad2.c +++ b/src/ax25_pad2.c @@ -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++) { diff --git a/src/cm108.c b/src/cm108.c index ff3ff792..7ea1626c 100644 --- a/src/cm108.c +++ b/src/cm108.c @@ -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) { diff --git a/src/decode_aprs.c b/src/decode_aprs.c index d96274bc..5e328960 100644 --- a/src/decode_aprs.c +++ b/src/decode_aprs.c @@ -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) diff --git a/src/digipeater.c b/src/digipeater.c index 4daf1890..e3849820 100644 --- a/src/digipeater.c +++ b/src/digipeater.c @@ -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. diff --git a/src/direwolf.c b/src/direwolf.c index 4f3c3030..d397db31 100644 --- a/src/direwolf.c +++ b/src/direwolf.c @@ -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 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 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"); diff --git a/src/textcolor.h b/src/textcolor.h index 4e38c83e..123430e5 100644 --- a/src/textcolor.h +++ b/src/textcolor.h @@ -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 diff --git a/src/tt_text.c b/src/tt_text.c index 112adfe5..e13ac96f 100644 --- a/src/tt_text.c +++ b/src/tt_text.c @@ -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"); diff --git a/src/xid.c b/src/xid.c index 14e67e8d..f1f7981d 100644 --- a/src/xid.c +++ b/src/xid.c @@ -653,6 +653,7 @@ int main (int argc, char *argv[]) { unsigned char info[40]; // Currently max of 27 but things can change. char desc[150]; // I've seen 109. + text_color_init (1, 0); /* parse example. */