808 lines
26 KiB
C
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(¶ms);
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|