Break reading and enqueueing of audio buffers into two steps so that errors in enqueueing can be distinguished for errors in reading. Errors in enqueueing signal a downstream decoder error. Add logic to gracefully recover from downstream decoder errors.

This commit is contained in:
Gregory Nutt 2014-08-05 10:04:24 -06:00
parent 2e5f4f160b
commit 0975ad77aa

View File

@ -49,6 +49,8 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
@ -481,19 +483,16 @@ static int nxplayer_mediasearch(FAR struct nxplayer_s *pPlayer,
#endif #endif
/**************************************************************************** /****************************************************************************
* Name: nxplayer_enqueuebuffer * Name: nxplayer_readbuffer
* *
* Reads the next block of data from the media file into the specified * Read the next block of data from the media file into the specified
* buffer and enqueues it to the audio device. * buffer.
* *
****************************************************************************/ ****************************************************************************/
static int nxplayer_enqueuebuffer(FAR struct nxplayer_s *pPlayer, static int nxplayer_readbuffer(FAR struct nxplayer_s *pPlayer,
FAR struct ap_buffer_s* apb) FAR struct ap_buffer_s* apb)
{ {
struct audio_buf_desc_s bufdesc;
int ret;
/* Validate the file is still open. It will be closed automatically when /* Validate the file is still open. It will be closed automatically when
* we encounter the end of file (or, perhaps, a read error that we cannot * we encounter the end of file (or, perhaps, a read error that we cannot
* handle. * handle.
@ -542,22 +541,42 @@ static int nxplayer_enqueuebuffer(FAR struct nxplayer_s *pPlayer,
{ {
DEBUGASSERT(errcode > 0); DEBUGASSERT(errcode > 0);
auddbg("ERROR: fread failed: %d\n", errcode); auddbg("ERROR: fread failed: %d\n", errcode);
UNUSED(errcode);
} }
#endif #endif
} }
/* We get here either on a successful read or a read error. /* Return OK to indicate that the buffer should be passed through to the
* audio device. This does not necessarily indicate that data was read
* correctly.
*/
return OK;
}
/****************************************************************************
* Name: nxplayer_enqueuebuffer
* *
* We will have a zero length buffer (with the AUDIO_APB_FINAL set) if a * Description:
* read error occurs or in the event that the file was an exact multiple * Enqueue the audio buffer in the downstream device. Normally we are
* of the nmaxbytes size of the audio buffer. In that latter case, we * called with a buffer of data to be enqueued in the audio stream.
* have an end of file with no bytes read.
* *
* These infrequency zero length buffers have to be passed through because * Be we may also receive an empty length buffer (with only the
* AUDIO_APB_FINAL set) in the event of certin read error occurs or in the
* event that the file was an exact multiple of the nmaxbytes size of the
* audio buffer. In that latter case, we have an end of file with no bytes
* read.
*
* These infrequent zero length buffers have to be passed through because
* the include the AUDIO_APB_FINAL flag that is needed to terminate the * the include the AUDIO_APB_FINAL flag that is needed to terminate the
* audio stream. * audio stream.
*/ *
****************************************************************************/
static int nxplayer_enqueuebuffer(FAR struct nxplayer_s *pPlayer,
FAR struct ap_buffer_s* apb)
{
struct audio_buf_desc_s bufdesc;
int ret;
/* Now enqueue the buffer with the audio device. If the number of /* Now enqueue the buffer with the audio device. If the number of
* bytes in the file happens to be an exact multiple of the audio * bytes in the file happens to be an exact multiple of the audio
@ -604,13 +623,17 @@ static void *nxplayer_playthread(pthread_addr_t pvarg)
struct audio_msg_s msg; struct audio_msg_s msg;
struct audio_buf_desc_s buf_desc; struct audio_buf_desc_s buf_desc;
ssize_t size; ssize_t size;
uint8_t running = true; bool running = true;
uint8_t streaming = true; bool streaming = true;
bool failed = false;
#ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS #ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS
struct ap_buffer_info_s buf_info; struct ap_buffer_info_s buf_info;
FAR struct ap_buffer_s** pBuffers; FAR struct ap_buffer_s** pBuffers;
#else #else
FAR struct ap_buffer_s* pBuffers[CONFIG_AUDIO_NUM_BUFFERS]; FAR struct ap_buffer_s* pBuffers[CONFIG_AUDIO_NUM_BUFFERS];
#endif
#ifdef CONFIG_DEBUG
int outstanding = 0;
#endif #endif
int prio; int prio;
int x; int x;
@ -692,26 +715,72 @@ static void *nxplayer_playthread(pthread_addr_t pvarg)
for (x = 0; x < CONFIG_AUDIO_NUM_BUFFERS; x++) for (x = 0; x < CONFIG_AUDIO_NUM_BUFFERS; x++)
#endif #endif
{ {
/* Enqueue next buffer */ /* Read the next buffer of data */
ret = nxplayer_enqueuebuffer(pPlayer, pBuffers[x]); ret = nxplayer_readbuffer(pPlayer, pBuffers[x]);
if (ret != OK) if (ret != OK)
{ {
/* Error encoding initial buffers or file is small */ /* nxplayer_readbuffer will return an error if there is no further
* data to be read from the file. This can happen normally if the
* file is very small (less than will fit in
* CONFIG_AUDIO_NUM_BUFFERS) or if an error occurs trying to read
* from the file.
*/
if (x == 0)
{
running = false;
}
else
{
/* We are no longer streaming data from the file */ /* We are no longer streaming data from the file */
streaming = false; streaming = false;
if (x == 0)
{
/* No buffers read? Should never really happen. Even in the
* case of a read failure, one empty buffer containing the
* AUDIO_APB_FINAL indication will be returned.
*/
running = false;
}
} }
/* Enqueue buffer by sending it to the audio driver */
else
{
ret = nxplayer_enqueuebuffer(pPlayer, pBuffers[x]);
if (ret != OK)
{
/* Failed to enqueue the buffer. The driver is not happy with
* the buffer. Perhaps a decoder has detected something that it
* does not like in the stream and has stopped streaming. This
* would happen normally if we send a file in the incorrect format
* to an audio decoder.
*
* We must stop streaming as gracefully as possible. Close the
* file so that no further data is read.
*/
fclose(pPlayer->fileFd);
pPlayer->fileFd = NULL;
/* We are no longer streaming data from the file. Be we will
* need to wait for any outstanding buffers to be recovered. We
* also still expect the audio driver to send a AUDIO_MSG_COMPLETE
* message after all queued buffers have been returned.
*/
streaming = false;
failed = true;
break; break;
} }
#ifdef CONFIG_DEBUG
else
{
/* The audio driver has one more buffer */
outstanding++;
}
#endif
}
} }
audvdbg("%d buffers queued, running=%d streaming=%d\n", audvdbg("%d buffers queued, running=%d streaming=%d\n",
@ -719,6 +788,8 @@ static void *nxplayer_playthread(pthread_addr_t pvarg)
/* Start the audio device */ /* Start the audio device */
if (running && !failed)
{
#ifdef CONFIG_AUDIO_MULTI_SESSION #ifdef CONFIG_AUDIO_MULTI_SESSION
ret = ioctl(pPlayer->devFd, AUDIOIOC_START, ret = ioctl(pPlayer->devFd, AUDIOIOC_START,
(unsigned long) pPlayer->session); (unsigned long) pPlayer->session);
@ -728,11 +799,17 @@ static void *nxplayer_playthread(pthread_addr_t pvarg)
if (ret < 0) if (ret < 0)
{ {
/* Error starting the audio stream! */ /* Error starting the audio stream! We need to continue running
* in order to recover the audio buffers that have already been
* queued.
*/
running = false; failed = true;
}
} }
if (running && !failed)
{
/* Indicate we are playing a file */ /* Indicate we are playing a file */
pPlayer->state = NXPLAYER_STATE_PLAYING; pPlayer->state = NXPLAYER_STATE_PLAYING;
@ -753,12 +830,23 @@ static void *nxplayer_playthread(pthread_addr_t pvarg)
nxplayer_setbass(pPlayer, pPlayer->bass); nxplayer_setbass(pPlayer, pPlayer->bass);
nxplayer_settreble(pPlayer, pPlayer->treble); nxplayer_settreble(pPlayer, pPlayer->treble);
#endif #endif
}
/* Loop until we specifically break. running == true means that we are /* Loop until we specifically break. running == true means that we are
* still looping waiting for the playback to complete. All of the file * still looping waiting for the playback to complete. All of the file
* data may have been sent (if streaming == false), but the playback is * data may have been sent (if streaming == false), but the playback is
* not complete until we get the AUDIO_MSG_COMPLETE (or AUDIO_MSG_STOP) * not complete until we get the AUDIO_MSG_COMPLETE (or AUDIO_MSG_STOP)
* message * message
*
* The normal protocol for streaming errors detected by the audio driver
* is as follows:
*
* (1) The audio driver will indicated the error by returning a negated
* error value when the next buffer is enqueued. The upper level
* then knows that this buffer was not queue.
* (2) The audio driver must return all queued buffers using the
* AUDIO_MSG_DEQUEUE message, and
* (3) Terminate playing by sending the AUDIO_MSG_COMPLETE message.
*/ */
audvdbg("%s\n", running ? "Playing..." : "Not runnning"); audvdbg("%s\n", running ? "Playing..." : "Not runnning");
@ -787,6 +875,15 @@ static void *nxplayer_playthread(pthread_addr_t pvarg)
/* An audio buffer is being dequeued by the driver */ /* An audio buffer is being dequeued by the driver */
case AUDIO_MSG_DEQUEUE: case AUDIO_MSG_DEQUEUE:
#ifdef CONFIG_DEBUG
/* Make sure that we believe that the audio driver has at
* least one buffer.
*/
DEBUGASSERT(msg.u.pPtr && outstanding > 0);
outstanding--;
#endif
/* Read data from the file directly into this buffer and /* Read data from the file directly into this buffer and
* re-enqueue it. streaming == true means that we have * re-enqueue it. streaming == true means that we have
* not yet hit the end-of-file. * not yet hit the end-of-file.
@ -794,7 +891,9 @@ static void *nxplayer_playthread(pthread_addr_t pvarg)
if (streaming) if (streaming)
{ {
ret = nxplayer_enqueuebuffer(pPlayer, msg.u.pPtr); /* Read the next buffer of data */
ret = nxplayer_readbuffer(pPlayer, msg.u.pPtr);
if (ret != OK) if (ret != OK)
{ {
/* Out of data. Stay in the loop until the device sends /* Out of data. Stay in the loop until the device sends
@ -804,6 +903,41 @@ static void *nxplayer_playthread(pthread_addr_t pvarg)
streaming = false; streaming = false;
} }
/* Enqueue buffer by sending it to the audio driver */
else
{
ret = nxplayer_enqueuebuffer(pPlayer, msg.u.pPtr);
if (ret != OK)
{
/* There is some issue from the audio driver.
* Perhaps a problem in the file format?
*
* We must stop streaming as gracefully as possible.
* Close the file so that no further data is read.
*/
fclose(pPlayer->fileFd);
pPlayer->fileFd = NULL;
/* Stop streaming and wait for buffers to be
* returned and to receive the AUDIO_MSG_COMPLETE
* indication.
*/
streaming = false;
failed = true;
}
#ifdef CONFIG_DEBUG
else
{
/* The audio driver has one more buffer */
outstanding++;
}
#endif
}
} }
break; break;
@ -812,7 +946,7 @@ static void *nxplayer_playthread(pthread_addr_t pvarg)
case AUDIO_MSG_STOP: case AUDIO_MSG_STOP:
/* Send a stop message to the device */ /* Send a stop message to the device */
audvdbg("Stopping!\n"); audvdbg("Stopping! outstanding=%d\n", outstanding);
#ifdef CONFIG_AUDIO_MULTI_SESSION #ifdef CONFIG_AUDIO_MULTI_SESSION
ioctl(pPlayer->devFd, AUDIOIOC_STOP, ioctl(pPlayer->devFd, AUDIOIOC_STOP,
@ -820,14 +954,19 @@ static void *nxplayer_playthread(pthread_addr_t pvarg)
#else #else
ioctl(pPlayer->devFd, AUDIOIOC_STOP, 0); ioctl(pPlayer->devFd, AUDIOIOC_STOP, 0);
#endif #endif
/* Stay in the running loop (without sending more data).
* we will need to recover our audio buffers. We will
* loop until AUDIO_MSG_COMPLETE is received.
*/
streaming = false; streaming = false;
running = false;
break; break;
/* Message indicating the playback is complete */ /* Message indicating the playback is complete */
case AUDIO_MSG_COMPLETE: case AUDIO_MSG_COMPLETE:
audvdbg("Play complete\n"); audvdbg("Play complete. outstanding=%d\n", outstanding);
DEBUGASSERT(outstanding == 0);
running = false; running = false;
break; break;
@ -897,6 +1036,7 @@ err_out:
fclose(pPlayer->fileFd); /* Close the file */ fclose(pPlayer->fileFd); /* Close the file */
pPlayer->fileFd = NULL; /* Clear out the FD */ pPlayer->fileFd = NULL; /* Clear out the FD */
} }
close(pPlayer->devFd); /* Close the device */ close(pPlayer->devFd); /* Close the device */
pPlayer->devFd = -1; /* Mark device as closed */ pPlayer->devFd = -1; /* Mark device as closed */
mq_close(pPlayer->mq); /* Close the message queue */ mq_close(pPlayer->mq); /* Close the message queue */