Add stdout option for audio output

This commit is contained in:
ars-ka0s 2023-01-17 01:25:08 -06:00
parent 58652df5b9
commit f1fcb8cd95
2 changed files with 205 additions and 162 deletions

View File

@ -129,6 +129,7 @@ static struct adev_s {
int outbuf_len; int outbuf_len;
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;
int udp_sock; /* UDP socket for receiving data */ int udp_sock; /* UDP socket for receiving data */
@ -453,52 +454,63 @@ int audio_open (struct audio_s *pa)
} }
/* /*
* Output device. Only "soundcard" is supported at this time. * Output device. Only "soundcard" and "stdout" 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;
} 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 #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) { if (err < 0) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not open audio device %s for output\n%s\n", dw_printf ("Could not open audio device %s for output\n%s\n",
audio_out_name, snd_strerror(err)); audio_out_name, snd_strerror(err));
if (err == -EBUSY) { if (err == -EBUSY) {
dw_printf ("This means that some other application is using that device.\n"); 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"); dw_printf ("The solution is to identify that other application and stop it.\n");
} }
return (-1); 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) { if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) {
return (-1); return (-1);
} }
#elif USE_SNDIO #elif USE_SNDIO
adev[a].sndio_out_handle = sio_open (audio_out_name, SIO_PLAY, 0); adev[a].sndio_out_handle = sio_open (audio_out_name, SIO_PLAY, 0);
if (adev[a].sndio_out_handle == NULL) { if (adev[a].sndio_out_handle == NULL) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not open audio device %s for output\n", dw_printf ("Could not open audio device %s for output\n",
audio_out_name); audio_out_name);
return (-1); 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) { if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) {
return (-1); return (-1);
} }
if (!sio_start (adev[a].sndio_out_handle)) { if (!sio_start (adev[a].sndio_out_handle)) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not start audio device %s for output\n", dw_printf ("Could not start audio device %s for output\n",
audio_out_name); audio_out_name);
return (-1); return (-1);
} }
#endif #endif
}
/* /*
* Finally allocate buffer for each direction. * Finally allocate buffer for each direction.
*/ */
@ -1333,13 +1345,36 @@ int audio_put (int a, int c)
int audio_flush (int a) int audio_flush (int a)
{ {
#if USE_ALSA switch (adev[a].g_audio_out_type) {
int k; case AUDIO_OUT_TYPE_STDOUT:;
unsigned char *psound; int res;
int retries = 10; unsigned char *ptr;
snd_pcm_status_t *status; int len;
assert (adev[a].audio_out_handle != NULL); 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 +1387,160 @@ 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); k = snd_pcm_status (adev[a].audio_out_handle, status);
if (k != 0) { if (k != 0) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio output get status error.\n%s\n", snd_strerror(k)); 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); //text_color_set(DW_COLOR_DEBUG);
//dw_printf ("Audio output state = %d. Try to start.\n", k); //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) { if (k != 0) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio output start error.\n%s\n", snd_strerror(k)); 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 #if DEBUGx
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_flush(): snd_pcm_writei %d frames returns %d\n", dw_printf ("audio_flush(): snd_pcm_writei %d frames returns %d\n",
adev[a].outbuf_len / adev[a].bytes_per_frame, k); adev[a].outbuf_len / adev[a].bytes_per_frame, k);
fflush (stdout); fflush (stdout);
#endif #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); 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; adev[a].outbuf_len = 0;
return (0); return (-1);
}
}
text_color_set(DW_COLOR_ERROR);
dw_printf ("Audio write error retry count exceeded.\n");
adev[a].outbuf_len = 0;
return (-1);
#elif USE_SNDIO #elif USE_SNDIO
int k; int k;
unsigned char *ptr; unsigned char *ptr;
int len; int len;
ptr = adev[a].outbuf_ptr; ptr = adev[a].outbuf_ptr;
len = adev[a].outbuf_len; len = adev[a].outbuf_len;
while (len > 0) { while (len > 0) {
assert (adev[a].sndio_out_handle != NULL); assert (adev[a].sndio_out_handle != NULL);
if (poll_sndio (adev[a].sndio_out_handle, POLLOUT) < 0) { if (poll_sndio (adev[a].sndio_out_handle, POLLOUT) < 0) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
perror("Can't write to audio device"); perror("Can't write to audio device");
adev[a].outbuf_len = 0; adev[a].outbuf_len = 0;
return (-1); return (-1);
} }
k = sio_write (adev[a].sndio_out_handle, ptr, len); k = sio_write (adev[a].sndio_out_handle, ptr, len);
#if DEBUGx #if DEBUGx
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_flush(): write %d returns %d\n", len, k); dw_printf ("audio_flush(): write %d returns %d\n", len, k);
fflush (stdout); fflush (stdout);
#endif #endif
ptr += k; ptr += k;
len -= k; len -= k;
} }
adev[a].outbuf_len = 0; adev[a].outbuf_len = 0;
return (0); return (0);
#else /* OSS */ #else /* OSS */
int k; int k;
unsigned char *ptr; unsigned char *ptr;
int len; int len;
ptr = adev[a].outbuf_ptr; ptr = adev[a].outbuf_ptr;
len = adev[a].outbuf_len; len = adev[a].outbuf_len;
while (len > 0) { while (len > 0) {
assert (adev[a].oss_audio_device_fd > 0); assert (adev[a].oss_audio_device_fd > 0);
k = write (adev[a].oss_audio_device_fd, ptr, len); k = write (adev[a].oss_audio_device_fd, ptr, len);
#if DEBUGx #if DEBUGx
text_color_set(DW_COLOR_DEBUG); text_color_set(DW_COLOR_DEBUG);
dw_printf ("audio_flush(): write %d returns %d\n", len, k); dw_printf ("audio_flush(): write %d returns %d\n", len, k);
fflush (stdout); fflush (stdout);
#endif #endif
if (k < 0) { if (k < 0) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
perror("Can't write to audio device"); 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; adev[a].outbuf_len = 0;
return (-1); return (0);
}
if (k < len) {
/* presumably full but didn't block. */
usleep (10000);
}
ptr += k;
len -= k;
}
adev[a].outbuf_len = 0;
return (0);
#endif #endif
}
return (0);
} /* end audio_flush */ } /* end audio_flush */
@ -1546,9 +1582,12 @@ int audio_flush (int a)
void audio_wait (int a) void audio_wait (int a)
{ {
audio_flush (a); audio_flush (a);
if (adev[a].g_audio_out_type == AUDIO_OUT_TYPE_STDOUT) {
return;
}
#if USE_ALSA #if USE_ALSA
/* For playback, this should wait for all pending frames */ /* For playback, this should wait for all pending frames */

View File

@ -43,6 +43,10 @@ enum audio_in_type_e {
AUDIO_IN_TYPE_SDR_UDP, AUDIO_IN_TYPE_SDR_UDP,
AUDIO_IN_TYPE_STDIN }; AUDIO_IN_TYPE_STDIN };
enum audio_out_type_e {
AUDIO_OUT_TYPE_SOUNDCARD,
AUDIO_OUT_TYPE_STDOUT };
/* For option to try fixing frames with bad CRC. */ /* For option to try fixing frames with bad CRC. */
typedef enum retry_e { typedef enum retry_e {