/* ******************************************************************************* * 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 #include #include #include #include #include #include #include #include #include #include #include #include //#define DUMP_PCM_DATA #define CMD_PLAY_FIFO_PATH "/tmp/playback/c0/command.fifo" typedef struct { pthread_mutex_t data_mutex; pthread_cond_t data_cond; //pthread_mutex_t play_mutex; pthread_cond_t play_cond; STATUS process_status; ATK_AUDIOPLAY_HANDLE_T **p_playback_handle; ATK_AUDIOPLAY_CONFIG_T *p_config; } user_data_t; #ifdef DUMP_PCM_DATA static int audio_fd_ = -1; static int open_pcm_file() { const char *filename = "./playback_alac_debug_audio.pcm"; audio_fd_ = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); if (audio_fd_ == -1) { fprintf(stderr, "[%s, %s]: Open file error: %s\n", __FILE__, __func__, strerror(errno)); return -1; } return 0; } static void close_pcm_file() { if(audio_fd_ >= 0) close(audio_fd_); } static int write_pcm_data_to_file(unsigned char* const* audio_bufs, size_t data_bytes) { ssize_t ret = write(audio_fd_, audio_bufs[0], data_bytes); if (ret < 0) { fprintf(stderr, "[%s, %s]: Write error. %s\n", __FILE__, __func__, strerror(errno)); return -1; } else if ((size_t) ret != data_bytes) { fprintf(stderr, "[%s, %s]: Data loss ..............\n", __FILE__, __func__); return -1; } return 0; } #endif // DUMP_PCM_DATA static int is_terminate_ = 0; // The handle of ring buffer to receive audio data. static srb_handle_t* audio_srb_handle_ = NULL; static void exit_process() { is_terminate_ = 1; if(audio_srb_handle_) { // Notify the reader of ring buffer to exit. SRB_WakeupReader(audio_srb_handle_); } } static void sig_kill(int signo) { printf("[%s,%s] Receive SIGNAL %d!!!\n", __FILE__, __func__, signo); switch(signo) { case SIGTERM: case SIGINT: exit_process(); break; default: break; } } static void dump_trace(int /*signo*/) { printf(" ===== Segmentation fault. ===== \n"); exit(EXIT_FAILURE); } static void print_usage(const char *ap_name) { fprintf(stderr, "Usage:\n" " %s -d PCM_device_name -r sample_rate -c channels [-b bit_depth] [-R name] [-D]\n" "Options:\n" " -b Bit depth for audio (Default: 16 -> S16_LE).\n" " -d PCM device name for ALSA (ex: hw:0,0).\n" " -r Sample rate for audio.\n" " -c The number of audio channels.\n" " -R The name of ring buffer for receiving data.\n" " -D Run as Daemon.\n" " -h This help.\n" , ap_name); fprintf(stderr, "ex:\n" "%s -d \"hw:0,0\" -r 8000 -c 2\n" "%s -d \"hw:0,0\" -r 8000 -c 2 -B -R audio_backchannel_srb_1\n", ap_name, ap_name); } static void send_cmd(const char *cmd) { // Send the command to the audio encoder. MsgContext msg_context; pid_t connect_pid = getpid(); msg_context.bHasResponse = 0; msg_context.pszHost = "encoder"; msg_context.dwHostLen = strlen(msg_context.pszHost) + 1; msg_context.pszCmd = cmd; msg_context.dwCmdLen = strlen(msg_context.pszCmd) + 1; msg_context.dwDataSize = sizeof(pid_t); msg_context.pbyData = (unsigned char *) &connect_pid; MsgBroker_SendMsg(CMD_FIFO_PATH, &msg_context); } static void* play_interleaved_data(void* args) { unsigned int* values = NULL; unsigned char* temp_buf = NULL; user_data_t *temp_data = (user_data_t*) args; ATK_AUDIOPLAY_HANDLE_T *playback_handle = *(temp_data->p_playback_handle); #ifdef DUMP_PCM_DATA // Get total bytes of data in one period. size_t period_frame_size = ATK_AudioPlay_GetPeriodFramesSize(playback_handle); #endif // Audio output buffer. unsigned char *bufs[ATK_AUDIO_MAX_CHANNELS] = {NULL}; // Prepare the buffer pointer for the ring buffer. srb_buffer_t srb_buf; memset(&srb_buf, 0, sizeof(srb_buffer_t)); ALACDecoder* decoder = NULL; AudioFormatDescription outputFormat; uint8_t* theMagicCookie = NULL; uint32_t theMagicCookieSize = 0; // Tell the audio encoder to send the configuration data. send_cmd("forceCI"); outputFormat.mFormatID = kALACFormatLinearPCM; outputFormat.mSampleRate = temp_data->p_config->dwSampleRate; outputFormat.mBitsPerChannel = 16; switch (temp_data->p_config->eFormat) { case SND_PCM_FORMAT_S16_LE: outputFormat.mBitsPerChannel = 16; break; case SND_PCM_FORMAT_S24_LE: outputFormat.mBitsPerChannel = 24; break; case SND_PCM_FORMAT_S32_LE: outputFormat.mBitsPerChannel = 32; break; default: break; } outputFormat.mFramesPerPacket = 1; outputFormat.mChannelsPerFrame = temp_data->p_config->dwChannelsCount; outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame = outputFormat.mBitsPerChannel != 20 ? temp_data->p_config->dwChannelsCount * ((outputFormat.mBitsPerChannel) >> 3) : (int32_t)(temp_data->p_config->dwChannelsCount * 2.5 + .5); outputFormat.mFormatFlags = kALACFormatFlagsNativeEndian; outputFormat.mReserved = 0; while (!is_terminate_) { if (temp_data->process_status == STOP) { pthread_mutex_lock(&(temp_data->data_mutex)); pthread_cond_signal(&(temp_data->data_cond)); pthread_mutex_unlock(&(temp_data->data_mutex)); pthread_mutex_lock(&(temp_data->data_mutex)); pthread_cond_wait(&(temp_data->play_cond), &(temp_data->data_mutex)); pthread_mutex_unlock(&(temp_data->data_mutex)); } #ifdef DUMP_PCM_DATA if(bufs[0] != NULL) { write_pcm_data_to_file(bufs, period_frame_size); } #endif // Output the current buffer and get the next output buffer. if(ATK_AudioPlay_PlayPeriodFrames(playback_handle, bufs) < 0) { fprintf(stderr, "[%s,%s] Can't get the audio output buffer..\n", __FILE__, __func__); break; } // Get the buffer with audio data from the audio encoder. if(SRB_ReturnReceiveReaderBuff(audio_srb_handle_, &srb_buf) == 0) { // We check whether the data is audio data or not. values = (unsigned int*)srb_buf.buffer; if(values[0] != FOURCC_CONF) { // This is audio data. // Check whether the data is ALAC or not. if(values[0] == FOURCC_ALAC && decoder) { // This is the payload. BitBuffer input_bit_buffer; uint32_t data_bytes = values[3]; uint32_t frames = 0; temp_buf = srb_buf.buffer + MAX_AUDIO_DATA_HEADER_SIZE; BitBufferInit(&input_bit_buffer, (uint8_t*)temp_buf, data_bytes); decoder->Decode(&input_bit_buffer, bufs[0], 1, 2, &frames); //uint32_t numBytes = frames * outputFormat.mBytesPerFrame; //printf("ALAC decoded %u bytes, %u frames, mBytesPerFrame = %u\n", numBytes, numFrames, outputFormat.mBytesPerFrame); } } else { // This is configuration data. if(values[2] == FOURCC_ALAC) { temp_buf = srb_buf.buffer + MAX_AUDIO_DATA_HEADER_SIZE; theMagicCookieSize = values[5]; theMagicCookie = (uint8_t *) calloc(theMagicCookieSize, 1); memcpy(theMagicCookie, &values[6], theMagicCookieSize); if (decoder) delete decoder; decoder = new ALACDecoder; decoder->Init(theMagicCookie, theMagicCookieSize); free(theMagicCookie); } // Tell the audio encoder to start to encode data. send_cmd("start"); } } } // Return the last buffer to the ring buffer. SRB_ReturnReaderBuff(audio_srb_handle_, &srb_buf); // Tell the audio encoder to stop encoding data. send_cmd("stop"); if (decoder) delete decoder; return NULL; } static void msg_callback(MsgContext* msg, void* args) { user_data_t *temp_data = (user_data_t*) args; if (!strcasecmp(msg->pszHost, SR_MODULE_NAME)) { if (!strcasecmp(msg->pszCmd, SUSPEND_CMD)) { pthread_mutex_lock(&(temp_data->data_mutex)); SRB_WakeupReader(audio_srb_handle_); temp_data->process_status = STOP; pthread_cond_wait(&(temp_data->data_cond), &(temp_data->data_mutex)); pthread_mutex_unlock(&(temp_data->data_mutex)); if(*(temp_data->p_playback_handle)) ATK_AudioPlay_Release(*(temp_data->p_playback_handle)); MsgBroker_SuspendAckMsg(); }else if(!strcasecmp(msg->pszCmd, RESUME_CMD) ) { *(temp_data->p_playback_handle) = ATK_AudioPlay_Init(temp_data->p_config); pthread_mutex_lock(&(temp_data->data_mutex)); temp_data->process_status = START; pthread_cond_signal(&(temp_data->play_cond)); pthread_mutex_unlock(&(temp_data->data_mutex)); } } if (msg->bHasResponse) msg->dwDataSize = 0; } int main(int argc, char **argv) { int opt; bool is_daemon = false; int bit_depth = 16; pthread_t playback_tid = 0; ATK_AUDIOPLAY_HANDLE_T *playback_handle = NULL; ATK_AUDIOPLAY_CONFIG_T config; std::string pcm_name = "hw:0,0"; std::string ring_buf_name; // Default value of configuration. memset(&config, 0, sizeof(ATK_AUDIOPLAY_CONFIG_T)); config.szPcmName = pcm_name.c_str(); config.bIsInterleaved = 1; config.eFormat = SND_PCM_FORMAT_S16_LE; config.dwChannelsCount = 2; config.dwSampleRate = 8000; config.bUseSimpleConfig = 0; config.dwPeriodsPerBuffer = 8; config.dwPeriodSizeInFrames = PERIOD_SIZE_IN_FRAMES; while ((opt = getopt(argc, argv, "b:c:d:Dh:r:R:")) != -1) { switch(opt) { case 'b': bit_depth = atoi(optarg); break; case 'c': config.dwChannelsCount = atoi(optarg); break; case 'd': pcm_name = optarg; config.szPcmName = pcm_name.c_str(); break; case 'h': print_usage(argv[0]); exit(EXIT_FAILURE); case 'r': config.dwSampleRate = atoi(optarg); break; case 'D': is_daemon = true; break; case 'R': ring_buf_name = optarg; break; default: print_usage(argv[0]); exit(EXIT_FAILURE); } } switch (bit_depth) { case 16: config.eFormat = SND_PCM_FORMAT_S16_LE; break; case 24: config.eFormat = SND_PCM_FORMAT_S24_LE; break; case 32: config.eFormat = SND_PCM_FORMAT_S32_LE; break; default: print_usage(argv[0]); exit(EXIT_FAILURE); } signal(SIGTERM, sig_kill); signal(SIGINT, sig_kill); signal(SIGSEGV, dump_trace); if (is_daemon) { daemon(1,1); } user_data_t user_data; memset(&user_data, 0, sizeof(user_data_t)); pthread_mutex_init(&(user_data.data_mutex), NULL); pthread_cond_init(&(user_data.data_cond), NULL); pthread_cond_init(&(user_data.play_cond), NULL); user_data.process_status = START; user_data.p_playback_handle = &playback_handle; user_data.p_config = &config; // Initialize audio playback. playback_handle = ATK_AudioPlay_Init(&config); if(playback_handle == NULL) { fprintf(stderr, "[%s,%s] Can't initialize the audio playback.\n", __FILE__, __func__); goto main_end; } // Initialize the reader of ring buffer. if(ring_buf_name.empty()) { ring_buf_name = "aenc_srb_1"; } audio_srb_handle_ = SRB_InitReader(ring_buf_name.c_str()); if(audio_srb_handle_ == NULL) { fprintf(stderr, "[%s,%s] Can't initialize the reader of ring buffer.\n", __FILE__, __func__); goto main_end; } #ifdef DUMP_PCM_DATA if(open_pcm_file() < 0) { goto main_end; } #endif pthread_create(&playback_tid, NULL, play_interleaved_data, &user_data); MsgBroker_RegisterMsg(CMD_PLAY_FIFO_PATH); MsgBroker_Run(CMD_PLAY_FIFO_PATH, msg_callback, (void *)&user_data, (int *)&is_terminate_); MsgBroker_UnRegisterMsg(); main_end: pthread_join(playback_tid, NULL); #ifdef DUMP_PCM_DATA close_pcm_file(); #endif // Release audio playback. if (playback_handle) ATK_AudioPlay_Release(playback_handle); pthread_mutex_destroy(&(user_data.data_mutex)); pthread_cond_destroy(&(user_data.data_cond)); pthread_cond_destroy(&(user_data.play_cond)); // Release the reader of ring buffer. if(audio_srb_handle_) SRB_Release(audio_srb_handle_); return 0; }