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

728 lines
21 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_capture_mmap.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#ifndef USE_WALL_CLOCK
#include <time.h>
#endif
#define DEBUG_AUDIO_LOG
struct ATK_AUDIOCAP_HANDLE_T
{
snd_pcm_t *cap_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. */
pthread_t cap_thread_; /**< Thread for capturing audio data. */
int is_running_; /**< Flag to indicate the state machine is running. */
#ifdef DEBUG_AUDIO_LOG
snd_output_t *log_out_; /**< Output object for ALSA. */
#endif
ATK_AUDIO_CAP_DATA_FUNC callback_; /**< Callback funcion for receiving audio data. */
void* user_data_; /**< User's private data can be passed into the callback funcion. */
};
/**
* @brief Function to set the parameters for ALSA.
*
* @param[in] handle The handle of audio capture device (tk).
* @param[in] config The audio capture device's configuration.
* @return 0: Successful, otherwise: Failed.
*/
static int set_params(ATK_AUDIOCAP_HANDLE_T *handle, ATK_AUDIOCAP_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;
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->cap_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->cap_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->cap_handle_, params, SND_PCM_ACCESS_MMAP_INTERLEAVED);
}
else
{
err = snd_pcm_hw_params_set_access(handle->cap_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->cap_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;
}
// Set channels count.
err = snd_pcm_hw_params_set_channels(handle->cap_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->cap_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->cap_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->cap_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->cap_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->cap_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->cap_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->cap_handle_, swparams);
#ifdef DEBUG_AUDIO_LOG
fprintf(stdout, "\n======== Current software configurations start ========\n");
snd_pcm_dump_sw_setup(handle->cap_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->cap_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 requested capture frames are >= threshold.
err = snd_pcm_sw_params_set_start_threshold(handle->cap_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->cap_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;
}
// Install software parameters.
err = snd_pcm_sw_params(handle->cap_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;
}
// Calculate some information.
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->cap_handle_, handle->log_out_);
fprintf(stdout, "======== Software ========\n");
snd_pcm_dump_sw_setup(handle->cap_handle_, handle->log_out_);
fprintf(stdout, "======== New configurations end ========\n\n");
#endif
return ret;
}
/**
* @brief Function to handle I/O error.
*
* @param[in] handle The handle of audio capture device (tk).
* @return 0: Successful, negative: Failed.
*/
static int handle_io_err(ATK_AUDIOCAP_HANDLE_T *handle)
{
snd_pcm_status_t *status = NULL;
int ret = 0;
snd_pcm_status_alloca(&status);
if ((ret = snd_pcm_status(handle->cap_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]: Overrun!!!!!!!!!.\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->cap_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->cap_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 capture device (tk).
* @return 0: Successful, -1: Failed, -2: State machine is not running.
*/
static int handle_io_suspend(ATK_AUDIOCAP_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->cap_handle_)) == -EAGAIN )
{
// wait until suspend flag is released
sleep(1);
if(!handle->is_running_)
{
return -2;
}
}
if(ret < 0)
{
fprintf(stderr, "[%s, %s]: Failed. Restarting stream.\n", __FILE__, __func__);
if((ret = snd_pcm_prepare(handle->cap_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 capture device (tk).
* @param[in] err_code The error code from ALSA API.
* @return 0: Successful, negative: Failed.
*/
static int handle_err(ATK_AUDIOCAP_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 Main loop for capturing audio data.
*
* @param[in] data The handle of audio capture device (tk).
* @return NULL.
*/
static void *audio_capture_loop(void *data)
{
ATK_AUDIOCAP_HANDLE_T *handle = (ATK_AUDIOCAP_HANDLE_T*) data;
const snd_pcm_channel_area_t *mmap_areas = NULL;
snd_pcm_uframes_t offset = 0, frames = 0;
snd_pcm_sframes_t avail = 0, commitres = 0;
snd_pcm_state_t state;
int err = 0, first = 1;
unsigned int i = 0;
#ifndef USE_WALL_CLOCK
struct timespec temp_time;
#endif
// Initialize the notification data.
ATK_AUDIO_NOTIFY_DATA_INFO_T audio_notify_data_info;
memset(&audio_notify_data_info, 0, sizeof(ATK_AUDIO_NOTIFY_DATA_INFO_T));
audio_notify_data_info.bIsInterleaved = handle->is_interleaved_;
audio_notify_data_info.dwChannels = handle->channels_;
if(handle->is_interleaved_)
{
audio_notify_data_info.dwDataBytes = handle->chunk_bytes_;
audio_notify_data_info.dwOneFrameBytes = handle->bytes_per_frame_;
}
else
{
audio_notify_data_info.dwDataBytes = handle->one_channel_period_bytes_;
audio_notify_data_info.dwOneFrameBytes = handle->bytes_per_sample_;
}
// Main loop.
while(handle->is_running_)
{
state = snd_pcm_state(handle->cap_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 NULL;
}
first = 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 NULL;
}
}
// Get the number of frames ready to read.
avail = snd_pcm_avail_update(handle->cap_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 NULL;
}
first = 1;
continue;
}
if ((unsigned int)avail < handle->period_size_)
{
if (first)
{
first = 0;
if((err = snd_pcm_start(handle->cap_handle_)) < 0)
{
fprintf(stderr, "[%s, %s]: Start error: %s\n", __FILE__, __func__, snd_strerror(err));
return NULL;
}
}
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->cap_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 NULL;
}
first = 1;
}
}
continue;
}
frames = handle->period_size_;
// Request to access a portion of mmap area.
if((err = snd_pcm_mmap_begin(handle->cap_handle_, &mmap_areas, &offset, &frames)) < 0)
{
if ((err = handle_err(handle, err)) < 0)
{
fprintf(stderr, "[%s, %s]: MMAP begin error: %s\n", __FILE__, __func__, snd_strerror(err));
return NULL;
}
first = 1;
}
if(frames != handle->period_size_)
{
snd_pcm_mmap_commit(handle->cap_handle_, offset, frames);
fprintf(stderr, "[%s, %s]: MMAP error.\n", __FILE__, __func__);
return NULL;
}
// Update timestamp.
#ifndef USE_WALL_CLOCK
if(clock_gettime(CLOCK_MONOTONIC_RAW, &temp_time) == 0)
{
audio_notify_data_info.tDataTimestamp.tv_sec = temp_time.tv_sec;
audio_notify_data_info.tDataTimestamp.tv_usec = temp_time.tv_nsec / 1000;
}
else
{
fprintf(stderr, "[%s, %s]: Unable to get monotonic clock : %s\n", __FILE__, __func__, strerror(errno));
}
#else
if(gettimeofday(&(audio_notify_data_info.tDataTimestamp), NULL) < 0)
{
fprintf(stderr, "[%s, %s]: Unable to get wall clock : %s\n", __FILE__, __func__, strerror(errno));
}
#endif
// Get the start address in the mmap area for each channel.
for(i = 0; i < handle->channels_; ++i)
{
audio_notify_data_info.ppbyAudioBufs[i] = ((unsigned char*)mmap_areas[i].addr) + ((mmap_areas[i].first + offset * mmap_areas[i].step) >> 3);
}
// Call the callback function to handle audio data.
handle->callback_(&audio_notify_data_info, handle->user_data_);
// Has completed the access to mmap area.
commitres = snd_pcm_mmap_commit(handle->cap_handle_, offset, frames);
if (commitres < 0 || (snd_pcm_uframes_t)commitres != 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 NULL;
}
first = 1;
}
}
return NULL;
}
/**
* @brief Function to start capture.
*
* @param[in] handle The handle of audio capture device (tk).
* @return 0: Successful, otherwise: Failed.
*/
static int start_capture(ATK_AUDIOCAP_HANDLE_T *handle)
{
if((handle != NULL) && !(handle->is_running_))
{
handle->is_running_ = 1;
int rc = pthread_create(&handle->cap_thread_, NULL, &audio_capture_loop, handle);
if(rc)
{
fprintf(stderr, "[%s, %s]: ERROR: return code from pthread_create() is %d\n", __FILE__, __func__, rc);
handle->is_running_ = 0;
return -1;
}
}
return 0;
}
/**
* @brief Function to stop capture.
*
* @param[in] handle The handle of audio capture device (tk).
* @return 0: Successful, otherwise: Failed.
*/
static int stop_capture(ATK_AUDIOCAP_HANDLE_T *handle)
{
if((handle != NULL) && (handle->is_running_))
{
handle->is_running_ = 0;
void* ret_val = NULL;
int rc = pthread_join(handle->cap_thread_, &ret_val);
if(rc)
{
fprintf(stderr, "[%s, %s]: ERROR: return code from pthread_join() is %d\n", __FILE__, __func__, rc);
return -1;
}
}
return 0;
}
ATK_AUDIOCAP_HANDLE_T* ATK_AudioCap_Init(ATK_AUDIOCAP_CONFIG_T* ptConfig)
{
int err = 0;
ATK_AUDIOCAP_HANDLE_T *handle = NULL;
if(ptConfig == NULL)
{
fprintf(stderr, "[%s, %s]: Initial configuration is NULL.\n", __FILE__, __func__);
goto init_err;
}
// Allocate memory for handler.
handle = (ATK_AUDIOCAP_HANDLE_T*) calloc(1, sizeof(ATK_AUDIOCAP_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.
if(ptConfig->pfnCallback == NULL)
{
fprintf(stderr, "[%s, %s]: Callback function is NULL.\n", __FILE__, __func__);
goto init_err;
}
handle->callback_ = ptConfig->pfnCallback;
handle->user_data_ = ptConfig->pUserData;
handle->is_interleaved_ = ptConfig->bIsInterleaved;
handle->channels_ = ptConfig->dwChannelsCount;
// Open device and we use block mode.
err = snd_pcm_open(&(handle->cap_handle_), ptConfig->szPcmName, SND_PCM_STREAM_CAPTURE, 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
// We set the third parameter to zero because we don't want to close the stdout when we call snd_output_close function.
// Otherwise, we will be blocked in snd_output_close function.
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->cap_handle_);
if(err < 0)
{
fprintf(stderr, "[%s, %s]: Can't prepare PCM for use. %s\n", __FILE__, __func__, snd_strerror(err));
goto init_err;
}
if(start_capture(handle) < 0)
{
goto init_err;
}
return handle;
init_err:
ATK_AudioCap_Release(handle);
return NULL;
}
void ATK_AudioCap_Release(ATK_AUDIOCAP_HANDLE_T *ptHandle)
{
if(ptHandle != NULL)
{
// Wait thread.
stop_capture(ptHandle);
// Close the sound device.
if(ptHandle->cap_handle_ != NULL)
{
snd_pcm_close(ptHandle->cap_handle_);
ptHandle->cap_handle_ = NULL;
}
#ifdef DEBUG_AUDIO_LOG
if(ptHandle->log_out_)
{
snd_output_close(ptHandle->log_out_);
}
#endif
snd_config_update_free_global();
free(ptHandle);
}
}