gf_ai_box/include/modules/audiotk/audio_playback_mmap.c
2026-04-12 17:47:54 +08:00

808 lines
26 KiB
C

/*
*******************************************************************************
* Copyright (c) 2010-2022 VATICS(KNERON) Inc. All rights reserved.
*
* +-----------------------------------------------------------------+
* | THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY ONLY BE USED |
* | AND COPIED IN ACCORDANCE WITH THE TERMS AND CONDITIONS OF SUCH |
* | A LICENSE AND WITH THE INCLUSION OF THE THIS COPY RIGHT NOTICE. |
* | THIS SOFTWARE OR ANY OTHER COPIES OF THIS SOFTWARE MAY NOT BE |
* | PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY OTHER PERSON. THE |
* | OWNERSHIP AND TITLE OF THIS SOFTWARE IS NOT TRANSFERRED. |
* | |
* | THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT |
* | ANY PRIOR NOTICE AND SHOULD NOT BE CONSTRUED AS A COMMITMENT BY |
* | VATICS(KNERON) INC. |
* +-----------------------------------------------------------------+
*
*******************************************************************************
*/
#include <audio_playback_mmap.h>
#define DEBUG_AUDIO_LOG
typedef int (*SILENCE_FUNC)(ATK_AUDIOPLAY_HANDLE_T *handle, unsigned char **bufs, size_t offset, size_t bytes, int channel);
struct ATK_AUDIOPLAY_HANDLE_T
{
snd_pcm_t *play_handle_; /**< ALSA PCM handle. */
int is_interleaved_; /**< Interleaved mode. */
unsigned int channels_; /**< Channels. */
unsigned int bytes_per_sample_; /**< The total bytes of one sample. */
unsigned int bytes_per_frame_; /**< The total bytes of one audio frame. */
unsigned int one_channel_period_bytes_; /**< The total size of one channel of one period. */
unsigned int chunk_bytes_; /**< The total size of one period. */
unsigned long period_size_; /**< Distance between interrupts is # frames. */
#ifdef DEBUG_AUDIO_LOG
snd_output_t *log_out_; /**< Output object for ALSA. */
#endif
snd_pcm_format_t format_; /**< Sample format. */
int need_start_pcm_; /**< Flag to control whether we need to start PCM. */
int is_initialized_; /**< Flag to indicate this object is initialized or not. */
SILENCE_FUNC silence_func_; /**< Function pointer for setting silence. */
snd_pcm_uframes_t mmap_offset_; /**< mmap area offset in area steps (==frames). */
snd_pcm_uframes_t mmap_frames_; /**< mmap area portion size in frames. */
const snd_pcm_channel_area_t *mmap_areas; /**< mmap areas. */
unsigned int is_drop_pending_frames_before_stop_; /**0: Stop a PCM preserving pending frames. non-zero: Stop a PCM dropping pending frames.*/
};
/**
* @brief Function to handle I/O error.
*
* @param[in] handle The handle of audio playback device (tk).
* @return 0: Successful, otherwise: Failed.
*/
static int handle_io_err(ATK_AUDIOPLAY_HANDLE_T *handle)
{
snd_pcm_status_t *status = NULL;
int ret = 0;
snd_pcm_status_alloca(&status);
if ((ret = snd_pcm_status(handle->play_handle_, status)) < 0)
{
fprintf(stderr, "[%s, %s]: Can't get status information. %s\n", __FILE__, __func__, snd_strerror(ret));
return -1;
}
if(snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN)
{
fprintf(stderr, "[%s, %s]: Underrun!!!!!!!!!.\n", __FILE__, __func__);
#ifdef DEBUG_AUDIO_LOG
fprintf(stderr, "============ Status start ============\n");
snd_pcm_status_dump(status, handle->log_out_);
fprintf(stderr, "============ Status end ============\n");
#endif
if((ret = snd_pcm_prepare(handle->play_handle_)) < 0)
{
fprintf(stderr, "[%s, %s]: Prepare error. %s\n", __FILE__, __func__, snd_strerror(ret));
return -1;
}
// ok, data should be accepted again.
return 0;
}
if(snd_pcm_status_get_state(status) == SND_PCM_STATE_DRAINING)
{
#ifdef DEBUG_AUDIO_LOG
fprintf(stderr, "============ Status(DRAINING) start ============\n");
snd_pcm_status_dump(status, handle->log_out_);
fprintf(stderr, "============ Status(DRAINING) end ============\n");
#endif
fprintf(stderr, "[%s, %s]: capture stream format change? attempting recover...\n", __FILE__, __func__);
if((ret = snd_pcm_prepare(handle->play_handle_)) < 0)
{
fprintf(stderr, "[%s, %s]: (DRAINING) Prepare error. %s\n", __FILE__, __func__, snd_strerror(ret));
return -1;
}
return 0;
}
#ifdef DEBUG_AUDIO_LOG
fprintf(stderr, "============ Status(R/W) start ============\n");
snd_pcm_status_dump(status, handle->log_out_);
fprintf(stderr, "============ Status(R/W) end ============\n");
#endif
fprintf(stderr, "[%s, %s]: read/write error, state = %s\n", __FILE__, __func__, snd_pcm_state_name(snd_pcm_status_get_state(status)));
return -1;
}
/**
* @brief Function to handle I/O suspend.
*
* @param[in] handle The handle of audio playback device (tk).
* @return 0: Successful, -1: Failed, -2: State machine is not running.
*/
static int handle_io_suspend(ATK_AUDIOPLAY_HANDLE_T *handle)
{
int ret;
#ifdef DEBUG_AUDIO_LOG
fprintf(stderr, "[%s, %s]: Suspended. Trying resume.\n", __FILE__, __func__);
#endif
while( (ret = snd_pcm_resume(handle->play_handle_)) == -EAGAIN )
{
// wait until suspend flag is released
sleep(1);
if(!handle->is_initialized_)
{
return -2;
}
}
if(ret < 0)
{
fprintf(stderr, "[%s, %s]: Failed. Restarting stream.\n", __FILE__, __func__);
if((ret = snd_pcm_prepare(handle->play_handle_)) < 0)
{
fprintf(stderr, "[%s, %s]: Suspend: prepare error: %s.\n", __FILE__, __func__, snd_strerror(ret));
return -1;
}
}
fprintf(stderr, "[%s, %s]: Resume from suspend.\n", __FILE__, __func__);
return 0;
}
/**
* @brief Function to handle some error code of ALSA.
*
* @param[in] handle The handle of audio playback device (tk).
* @param[in] err_code The error code from ALSA API.
* @return 0: Successful, negative: Failed.
*/
static int handle_err(ATK_AUDIOPLAY_HANDLE_T *handle, int err_code)
{
if(err_code == -EPIPE)
{
return handle_io_err(handle);
}
else if(err_code == -ESTRPIPE)
{
return handle_io_suspend(handle);
}
else if(err_code < 0)
{
fprintf(stderr, "[%s, %s]: Can't handle error. %s\n", __FILE__, __func__, snd_strerror(err_code));
return -1;
}
return 0;
}
/**
* @brief Function to set the parameters for ALSA.
*
* @param[in] handle The handle of audio playback device (tk).
* @param[in] config The audio playback device's configuration.
* @return 0: Successful, otherwise: Failed.
*/
static int set_params(ATK_AUDIOPLAY_HANDLE_T *handle, ATK_AUDIOPLAY_CONFIG_T *config)
{
int err = 0;
int ret = 0;
unsigned int rate = 0;
snd_pcm_hw_params_t *params = NULL;
snd_pcm_sw_params_t *swparams = NULL;
snd_pcm_uframes_t buffer_size = 0;
snd_pcm_uframes_t boundary = 0;
int bits_per_sample = 0;
unsigned int bits_per_frame = 0;
unsigned int period_time = 0;
unsigned int buffer_time = 0;
if((handle == NULL) || (config == NULL))
{
return -1;
}
// Allocate memory for hardware parameters.
// This temporary space is automatically freed when the
// function that called alloca() returns to its caller.
snd_pcm_hw_params_alloca(&params);
// Get current hardware configurations.
err = snd_pcm_hw_params_any(handle->play_handle_, params);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Can't get hardware configurations. %s\n", __FILE__, __func__, snd_strerror(err));
return -1;
}
#ifdef DEBUG_AUDIO_LOG
fprintf(stdout, "\n======== Current hardware configurations start ========\n");
snd_pcm_dump_hw_setup(handle->play_handle_, handle->log_out_);
fprintf(stdout, "======== Current hardware configurations end ========\n\n");
#endif
// Set access type.
if(config->bIsInterleaved)
{
err = snd_pcm_hw_params_set_access(handle->play_handle_, params, SND_PCM_ACCESS_MMAP_INTERLEAVED);
}
else
{
err = snd_pcm_hw_params_set_access(handle->play_handle_, params, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
}
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Can't set access type. %s\n", __FILE__, __func__, snd_strerror(err));
ret = -1;
goto set_params_end;
}
// Set format.
err = snd_pcm_hw_params_set_format(handle->play_handle_, params, config->eFormat);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Can't set format. %s\n", __FILE__, __func__, snd_strerror(err));
ret = -1;
goto set_params_end;
}
handle->format_ = config->eFormat;
// Set channels count.
err = snd_pcm_hw_params_set_channels(handle->play_handle_, params, config->dwChannelsCount);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Can't set channels count. %s\n", __FILE__, __func__, snd_strerror(err));
ret = -1;
goto set_params_end;
}
// Set sample rate.
rate = config->dwSampleRate;
err = snd_pcm_hw_params_set_rate_near(handle->play_handle_, params, &(config->dwSampleRate), 0);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Can't set sample rate. %s\n", __FILE__, __func__, snd_strerror(err));
ret = -1;
goto set_params_end;
}
if ((float)(rate) * 1.05 < config->dwSampleRate || (float)(rate) * 0.95 > config->dwSampleRate)
{
fprintf(stderr, "[%s, %s]: Warning: rate is not accurate (requested = %uHz, got = %uHz)\n", __FILE__, __func__, rate, config->dwSampleRate);
}
if(!(config->bUseSimpleConfig))
{
// Set periods per buffer.
err = snd_pcm_hw_params_set_periods(handle->play_handle_, params, config->dwPeriodsPerBuffer, 0);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Can't set periods per buffer. %s\n", __FILE__, __func__, snd_strerror(err));
ret = -1;
goto set_params_end;
}
// Distance between interrupts is # frames.
err = snd_pcm_hw_params_set_period_size_near(handle->play_handle_, params, &(config->dwPeriodSizeInFrames), 0);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Can't set period size. %s\n", __FILE__, __func__, snd_strerror(err));
ret = -1;
goto set_params_end;
}
}
else
{
err = snd_pcm_hw_params_get_buffer_time_max(params, &buffer_time, 0);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Can't get max buffer time. %s\n", __FILE__, __func__, snd_strerror(err));
ret = -1;
goto set_params_end;
}
if(buffer_time > 500000)
buffer_time = 500000;
period_time = buffer_time / 4;
err = snd_pcm_hw_params_set_period_time_near(handle->play_handle_, params, &period_time, 0);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Can't set period time. %s\n", __FILE__, __func__, snd_strerror(err));
ret = -1;
goto set_params_end;
}
err = snd_pcm_hw_params_set_buffer_time_near(handle->play_handle_, params, &buffer_time, 0);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Can't set period time. %s\n", __FILE__, __func__, snd_strerror(err));
ret = -1;
goto set_params_end;
}
}
// Install hardware parameters.
err = snd_pcm_hw_params(handle->play_handle_, params);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Unable to install hardware parameters. %s\n", __FILE__, __func__, snd_strerror(err));
ret = -1;
goto set_params_end;
}
snd_pcm_hw_params_get_period_size(params, &(handle->period_size_), 0);
snd_pcm_hw_params_get_buffer_size(params, &buffer_size);
if(handle->period_size_ == buffer_size)
{
fprintf(stderr, "[%s, %s]: Can't use period equal to buffer size (%lu == %lu).\n", __FILE__, __func__, handle->period_size_, buffer_size);
ret = -1;
goto set_params_end;
}
// Allocate memory for software parameters.
// This temporary space is automatically freed when the
// function that called alloca() returns to its caller.
snd_pcm_sw_params_alloca(&swparams);
// Get current software configurations.
snd_pcm_sw_params_current(handle->play_handle_, swparams);
#ifdef DEBUG_AUDIO_LOG
fprintf(stdout, "\n======== Current software configurations start ========\n");
snd_pcm_dump_sw_setup(handle->play_handle_, handle->log_out_);
fprintf(stdout, "======== Current software configurations end ========\n\n");
#endif
// Minimum available frames to consider PCM ready.
err = snd_pcm_sw_params_set_avail_min(handle->play_handle_, swparams, handle->period_size_);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Unable to set minimum available frames. %s\n", __FILE__, __func__, snd_strerror(err));
ret = -1;
goto set_params_end;
}
// Set start threshold in frames. PCM is automatically started when playback frames available to PCM are >= threshold.
err = snd_pcm_sw_params_set_start_threshold(handle->play_handle_, swparams, buffer_size);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Unable to set start threshold. %s\n", __FILE__, __func__, snd_strerror(err));
ret = -1;
goto set_params_end;
}
// Set stop threshold in frames. PCM is automatically stopped in SND_PCM_STATE_XRUN state
// when available frames is >= threshold. If the stop threshold is equal to boundary
// (also software parameter - sw_param) then automatic stop will be disabled (thus device
// will do the endless loop in the ring buffer).
err = snd_pcm_sw_params_set_stop_threshold(handle->play_handle_, swparams, buffer_size);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Unable to set stop threshold. %s\n", __FILE__, __func__, snd_strerror(err));
ret = -1;
goto set_params_end;
}
// The special case is when silence size value is equal or greater than boundary.
// The unused portion of the ring buffer (initial written samples are untouched)
// is filled with silence at start. Later, only just processed sample area is
// filled with silence. Note: silence_threshold must be set to zero.
err = snd_pcm_sw_params_get_boundary(swparams, &boundary);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Unable to get boundary. %s\n", __FILE__, __func__, snd_strerror(err));
ret = -1;
goto set_params_end;
}
err = snd_pcm_sw_params_set_silence_size(handle->play_handle_, swparams, boundary);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Unable to set silence size. %s\n", __FILE__, __func__, snd_strerror(err));
ret = -1;
goto set_params_end;
}
err = snd_pcm_sw_params_set_silence_threshold(handle->play_handle_, swparams, 0);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Unable to set silence size. %s\n", __FILE__, __func__, snd_strerror(err));
ret = -1;
goto set_params_end;
}
// Install software parameters.
err = snd_pcm_sw_params(handle->play_handle_, swparams);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Unable to set software parameters. %s\n", __FILE__, __func__, snd_strerror(err));
ret = -1;
goto set_params_end;
}
// Allocate audio buffer.
bits_per_sample = snd_pcm_format_physical_width(config->eFormat);
if(bits_per_sample < 0)
{
fprintf(stderr, "[%s, %s]: Unable to get bits per sample. %s\n", __FILE__, __func__, snd_strerror(err));
ret = -1;
goto set_params_end;
}
handle->bytes_per_sample_ = bits_per_sample >> 3;
bits_per_frame = bits_per_sample * config->dwChannelsCount;
handle->bytes_per_frame_ = bits_per_frame >> 3;
handle->chunk_bytes_ = (handle->period_size_ * bits_per_frame) >> 3;
handle->one_channel_period_bytes_ = (handle->period_size_ * bits_per_sample) >> 3;
set_params_end:
#ifdef DEBUG_AUDIO_LOG
fprintf(stdout, "\n======== New configurations start ========\n");
fprintf(stdout, "======== Hardware ========\n");
snd_pcm_dump_hw_setup(handle->play_handle_, handle->log_out_);
fprintf(stdout, "======== Software ========\n");
snd_pcm_dump_sw_setup(handle->play_handle_, handle->log_out_);
fprintf(stdout, "======== New configurations end ========\n\n");
#endif
return ret;
}
/**
* @brief Function to set silence data in the buffer within one period size.
*
* @param[in] handle The handle of audio playback device (tk).
* @param[in] buf The buffers those need to be set. If it is interleaved mode,
* all data are in the buf[0]. If it is non-interleaved mode, all data of each
* channel are in the buf[channel_index].
* @param[in] offset The offset for the start point which need to be set silence (bytes).
* @param[in] bytes The size for the portion of the buffer which need to be set silence.
* @param[in] channel This parameter is used for non-interleaved mode to specify which
* channel we want to set. If it is invalid, the data of all channel will be set.
*
* @return -1: Failed. 0: Success.
*/
static int set_silence_within_period_interleaved(ATK_AUDIOPLAY_HANDLE_T *handle, unsigned char **bufs, size_t offset, size_t bytes, int channel __attribute__((__unused__)))
{
size_t n_frames = offset / handle->bytes_per_frame_;
size_t n_silence_frames = bytes / handle->bytes_per_frame_;
if(n_frames < handle->period_size_)
{
if((n_frames + n_silence_frames) > handle->period_size_)
{
n_silence_frames = handle->period_size_ - n_frames;
}
int err = snd_pcm_format_set_silence(handle->format_, bufs[0] + n_frames * handle->bytes_per_frame_, n_silence_frames * handle->channels_);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Set silence error: %s\n", __FILE__, __func__, snd_strerror(err));
return -1;
}
}
return 0;
}
/**
* @brief Function to set silence data in the buffer within one period size.
*
* @param[in] handle The handle of audio playback device (tk).
* @param[in] buf The buffers those need to be set. If it is interleaved mode,
* all data are in the buf[0]. If it is non-interleaved mode, all data of each
* channel are in the buf[channel_index].
* @param[in] offset The offset for the start point which need to be set silence (bytes).
* @param[in] bytes The size for the portion of the buffer which need to be set silence.
* @param[in] channel This parameter is used for non-interleaved mode to specify which
* channel we want to set. If it is invalid, the data of all channel will be set.
*
* @return -1: Failed. 0: Success.
*/
static int set_silence_within_period_non_interleaved(ATK_AUDIOPLAY_HANDLE_T *handle, unsigned char **bufs, size_t offset, size_t bytes, int channel)
{
size_t n_frames = offset / handle->bytes_per_sample_;
size_t n_silence_frames = bytes / handle->bytes_per_frame_;
if(n_frames < handle->period_size_)
{
size_t temp_offset = n_frames * handle->bytes_per_sample_;
if((n_frames + n_silence_frames) > handle->period_size_)
{
n_silence_frames = handle->period_size_ - n_frames;
}
int err = 0;
if((channel >= 0) && ((unsigned int)channel < handle->channels_))
{
err = snd_pcm_format_set_silence(handle->format_, bufs[channel] + temp_offset, n_silence_frames);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Set silence error: %s\n", __FILE__, __func__, snd_strerror(err));
return -1;
}
}
else
{
for(unsigned int i = 0; i < handle->channels_; ++i)
{
err = snd_pcm_format_set_silence(handle->format_, bufs[i] + temp_offset, n_silence_frames);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Set silence error: %s\n", __FILE__, __func__, snd_strerror(err));
return -1;
}
}
}
}
return 0;
}
ATK_AUDIOPLAY_HANDLE_T* ATK_AudioPlay_Init(ATK_AUDIOPLAY_CONFIG_T* ptConfig)
{
int err = 0;
ATK_AUDIOPLAY_HANDLE_T *handle = NULL;
if(ptConfig == NULL)
{
goto init_err;
}
// Allocate memory for handler.
handle = (ATK_AUDIOPLAY_HANDLE_T*) calloc(1, sizeof(ATK_AUDIOPLAY_HANDLE_T));
if(handle == NULL)
{
fprintf(stderr, "[%s, %s]: Can't allocate memory.\n", __FILE__, __func__);
goto init_err;
}
// Store some information in the handle.
handle->is_interleaved_ = ptConfig->bIsInterleaved;
handle->channels_ = ptConfig->dwChannelsCount;
handle->need_start_pcm_ = 1;
handle->is_initialized_ = 1;
handle->silence_func_ = (ptConfig->bIsInterleaved) ? set_silence_within_period_interleaved : set_silence_within_period_non_interleaved;
// Open device. We use block mode.
err = snd_pcm_open(&(handle->play_handle_), ptConfig->szPcmName, SND_PCM_STREAM_PLAYBACK, 0);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Can't open device. %s\n", __FILE__, __func__, snd_strerror(err));
goto init_err;
}
#ifdef DEBUG_AUDIO_LOG
snd_output_stdio_attach(&(handle->log_out_), stdout, 0);
#endif
if(set_params(handle, ptConfig) < 0)
{
goto init_err;
}
// Prepare PCM for use.
err = snd_pcm_prepare(handle->play_handle_);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Can't prepare PCM for use. %s\n", __FILE__, __func__, snd_strerror(err));
goto init_err;
}
handle->is_drop_pending_frames_before_stop_ = ptConfig->bDropFramesBeforeStop;
return handle;
init_err:
ATK_AudioPlay_Release(handle);
return NULL;
}
size_t ATK_AudioPlay_GetPeriodFramesSize(ATK_AUDIOPLAY_HANDLE_T *ptHandle)
{
if(ptHandle)
{
return (ptHandle->is_interleaved_) ? (ptHandle->chunk_bytes_) : (ptHandle->one_channel_period_bytes_);
}
return 0;
}
size_t ATK_AudioPlay_GetOneFrameSize(ATK_AUDIOPLAY_HANDLE_T *ptHandle)
{
if(ptHandle)
{
return (ptHandle->is_interleaved_) ? (ptHandle->bytes_per_frame_) : (ptHandle->bytes_per_sample_);
}
return 0;
}
/**
* @brief Function to play audo frames in the current mmap buffer and get next mmap buffer.
*
* @param[in] handle The handle of audio playback device (tk).
* @param[in, out] bufs The data those need to be played. If it is interleaved mode,
* all data are in the buf[0]. If it is non-interleaved mode, all data of each
* channel are in the buf[channel_index]. If we call this function first time, the bufs[0] should be zero.
* @param[out] frames mmap area portion size in frames. This is the number of frames you must write to the mmap buffer.
* @param[in] is_write_period_mode Flag to indicate whether we force to write the complete period data.
*
* @return -1: Failed. 0: Success.
*/
static int atk_audioplay_play_frames(ATK_AUDIOPLAY_HANDLE_T *handle, unsigned char **bufs, unsigned int *frames, unsigned int is_write_period_mode)
{
snd_pcm_sframes_t avail = 0, commitres = 0;
snd_pcm_state_t state;
int err = 0;
unsigned int i = 0;
//if((bufs[0] != NULL) && (handle->mmap_frames_ != 0))
if(handle->mmap_frames_ != 0)
{
// Has completed the access to mmap area.
commitres = snd_pcm_mmap_commit(handle->play_handle_, handle->mmap_offset_, handle->mmap_frames_);
if (commitres < 0 || (snd_pcm_uframes_t)commitres != handle->mmap_frames_)
{
if ((err = handle_err(handle, commitres >= 0 ? -EPIPE : commitres)) < 0)
{
fprintf(stderr, "[%s, %s]: MMAP commit error: %s\n", __FILE__, __func__, snd_strerror(err));
return -1;
}
handle->need_start_pcm_ = 1;
}
}
*frames = 0;
while(handle->is_initialized_)
{
state = snd_pcm_state(handle->play_handle_);
if(state == SND_PCM_STATE_XRUN)
{
if((err = handle_io_err(handle)) < 0)
{
fprintf(stderr, "[%s, %s]: XRUN recovery failed: %s\n", __FILE__, __func__, snd_strerror(err));
return -1;
}
handle->need_start_pcm_ = 1;
}
else if(state == SND_PCM_STATE_SUSPENDED)
{
if((err = handle_io_suspend(handle)) < 0)
{
fprintf(stderr, "[%s, %s]: SUSPEND recovery failed: %s\n", __FILE__, __func__, snd_strerror(err));
return -1;
}
}
avail = snd_pcm_avail_update(handle->play_handle_);
if(avail < 0)
{
if((err = handle_err(handle, avail)) < 0)
{
fprintf(stderr, "[%s, %s]: Avail update failed: %s\n", __FILE__, __func__, snd_strerror(err));
return -1;
}
handle->need_start_pcm_ = 1;
continue;
}
if((unsigned int)avail < handle->period_size_)
{
if(handle->need_start_pcm_)
{
handle->need_start_pcm_ = 0;
if((err = snd_pcm_start(handle->play_handle_)) < 0)
{
fprintf(stderr, "[%s, %s]: Start error: %s\n", __FILE__, __func__, snd_strerror(err));
return -1;
}
}
else
{
// snd_pcm_wait() function contains embedded poll waiting implementation.
// Wait for the data is ready, timeout is four seconds.
if((err = snd_pcm_wait(handle->play_handle_, 4000)) < 0)
{
if((err = handle_err(handle, err)) < 0)
{
fprintf(stderr, "[%s, %s]: snd_pcm_wait error: %s\n", __FILE__, __func__, snd_strerror(err));
return -1;
}
handle->need_start_pcm_ = 1;
}
}
continue;
}
handle->mmap_frames_ = handle->period_size_;
// Request to access a portion of mmap area.
if((err = snd_pcm_mmap_begin(handle->play_handle_, &(handle->mmap_areas), &(handle->mmap_offset_), &(handle->mmap_frames_))) < 0)
{
if ((err = handle_err(handle, err)) < 0)
{
fprintf(stderr, "[%s, %s]: MMAP begin error: %s\n", __FILE__, __func__, snd_strerror(err));
return -1;
}
handle->need_start_pcm_ = 1;
}
if(is_write_period_mode && (handle->mmap_frames_ != handle->period_size_))
{
snd_pcm_mmap_commit(handle->play_handle_, handle->mmap_offset_, handle->mmap_frames_);
fprintf(stderr, "[%s, %s]: MMAP error.\n", __FILE__, __func__);
return -1;
}
*frames = handle->mmap_frames_;
// Get the start address in the mmap area for each channel.
for(i = 0; i < handle->channels_; ++i)
{
bufs[i] = ((unsigned char*)(handle->mmap_areas[i].addr)) + ((handle->mmap_areas[i].first + handle->mmap_offset_ * handle->mmap_areas[i].step) >> 3);
}
break;
}
return 0;
}
int ATK_AudioPlay_PlayPeriodFrames(ATK_AUDIOPLAY_HANDLE_T *ptHandle, unsigned char **ppbyBufs)
{
unsigned int frames;
return atk_audioplay_play_frames(ptHandle, ppbyBufs, &frames, 1);
}
int ATK_AudioPlay_PlayFrames(ATK_AUDIOPLAY_HANDLE_T *ptHandle, unsigned char **ppbyBufs, unsigned int *pdwFrames)
{
return atk_audioplay_play_frames(ptHandle, ppbyBufs, pdwFrames, 0);
}
int ATK_AudioPlay_SetSilenceWithinPeriod(ATK_AUDIOPLAY_HANDLE_T *ptHandle, unsigned char **ppbyBufs, size_t dwOffset, size_t dwBytes, int iChannel)
{
return ((ptHandle) ? (ptHandle->silence_func_(ptHandle, ppbyBufs, dwOffset, dwBytes, iChannel)) : -1);
}
void ATK_AudioPlay_Release(ATK_AUDIOPLAY_HANDLE_T *ptHandle)
{
if(ptHandle != NULL)
{
ptHandle->is_initialized_ = 0;
if(ptHandle->mmap_frames_ != 0)
{
// Because we fill the silence automatically in the ring buffer, we don't silence the last buffer here.
// Complete the access to the last buffer
snd_pcm_mmap_commit(ptHandle->play_handle_, ptHandle->mmap_offset_, ptHandle->mmap_frames_);
ptHandle->mmap_frames_ = 0;
}
// Close the sound device.
if(ptHandle->play_handle_ != NULL)
{
if(ptHandle->is_drop_pending_frames_before_stop_)
{
snd_pcm_drop(ptHandle->play_handle_);
}
else
{
snd_pcm_drain(ptHandle->play_handle_);
}
snd_pcm_close(ptHandle->play_handle_);
}
#ifdef DEBUG_AUDIO_LOG
snd_output_close(ptHandle->log_out_);
#endif
snd_config_update_free_global();
free(ptHandle);
}
}