/* ******************************************************************************* * 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 #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); } }