/* ******************************************************************************* * 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 #include #include #include #include #include #ifndef USE_WALL_CLOCK #include #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(¶ms); // 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); } }