/* ******************************************************************************* * 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 #define MAX_CONNECT_NUM (5) #define DEFAULT_PID (0xffff) // Flag for message broker (main loop). static int is_terminate_ = 0; // Adapted from CoreAudioTypes.h enum { kTestFormatFlag_16BitSourceData = 1, kTestFormatFlag_20BitSourceData = 2, kTestFormatFlag_24BitSourceData = 3, kTestFormatFlag_32BitSourceData = 4 }; typedef struct { //! connect pid pid_t connect_pid; //! Flag to indicate whether we need to encode data or not. unsigned int do_encoding; } connect_info_t; connect_info_t g_atconnect_info[MAX_CONNECT_NUM]; typedef struct { // Flag to indicate whether we need to send configuration about each encoder or not. bool send_conf; // The handles for SynRingBuf. srb_handle_t* srb_handle; // The buffers for SynRingBuf. srb_buffer_t srb_buf; unsigned int enc_type; // FourCC of encoder. unsigned int seq_num; pthread_mutex_t data_mutex; pthread_cond_t data_cond; STATUS process_status; ATK_AUDIOCAP_CONFIG_T *p_audiocap_config; ATK_AUDIOCAP_HANDLE_T **p_cap_handle; AudioFormatDescription inputFormat; AudioFormatDescription outputFormat; // encoder handle void *enc_handle; } user_data_t; //#define DUMP_TIMESTAMP //#define DUMP_PCM_DATA #ifdef DUMP_PCM_DATA static int audio_fd_ = -1; static int open_pcm_file(/* int is_interleaved, unsigned int channels */) { const char *filename = "alacaenc_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(int /*is_interleaved*/, unsigned int /*channels*/, 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 pid_default(int dwStart) { unsigned int i; for (i = 0; i < MAX_CONNECT_NUM; ++i) { if (dwStart) { //! set default pid and start encoding if (!g_atconnect_info[i].connect_pid) { g_atconnect_info[i].connect_pid = DEFAULT_PID; ++g_atconnect_info[i].do_encoding; break; } else if (g_atconnect_info[i].connect_pid == DEFAULT_PID){ ++g_atconnect_info[i].do_encoding; break; } else { printf("[%s] Error: Something wrong \n", __func__); break; } } else { //! set default pid and stop encoding if (g_atconnect_info[i].connect_pid == DEFAULT_PID) { --g_atconnect_info[i].do_encoding; if (!g_atconnect_info[i].do_encoding) { g_atconnect_info[i].connect_pid = 0; } break; } } } if(i == MAX_CONNECT_NUM){ printf("[%s] Error: AENC Connect number to MAX\n",__func__); return 1; } return 0; } static int pid_start (pid_t new_pid) { unsigned int i = 0; for (i = 0; i < MAX_CONNECT_NUM; i++) { if (g_atconnect_info[i].connect_pid == 0 && !g_atconnect_info[i].do_encoding) { g_atconnect_info[i].connect_pid = new_pid; g_atconnect_info[i].do_encoding++; break; } else if (g_atconnect_info[i].connect_pid == new_pid) { g_atconnect_info[i].do_encoding++; break; } } if(i == MAX_CONNECT_NUM){ printf("[%s] Error: AENC Connect number to MAX\n",__func__); return 1; } return 0; } static int pid_stop(pid_t new_pid) { for (size_t i = 0; i < MAX_CONNECT_NUM; i++) { if (g_atconnect_info[i].connect_pid != 0 && g_atconnect_info[i].connect_pid == new_pid && g_atconnect_info[i].do_encoding) { g_atconnect_info[i].do_encoding--; if (!g_atconnect_info[i].do_encoding) g_atconnect_info[i].connect_pid = 0; break; } } return 0; } static int pid_do_encoding(void) { for (size_t i = 0; i < MAX_CONNECT_NUM; i++) { if (g_atconnect_info[i].connect_pid != 0 && g_atconnect_info[i].do_encoding) return 1; } return 0; } static void audiocap_callback(const ATK_AUDIO_NOTIFY_DATA_INFO_T *audio_info, void* user_data) { //Input pcm data should be interleaved user_data_t *temp_data = (user_data_t*) user_data; #ifdef DUMP_TIMESTAMP printf("time = %lu.%lu\n", audio_info->tDataTimestamp.tv_sec, audio_info->tDataTimestamp.tv_usec); #endif // DUMP_TIMESTAMP #ifdef DUMP_PCM_DATA write_pcm_data_to_file(audio_info->bIsInterleaved, audio_info->dwChannels, audio_info->ppbyAudioBufs, audio_info->dwDataBytes); #endif pthread_mutex_lock(&(temp_data->data_mutex)); ALACEncoder* theEncoder = (ALACEncoder*) temp_data->enc_handle; if (temp_data->send_conf) { // Send configuration of encoder. printf("send conf .............\n"); if(FOURCC_ALAC == temp_data->enc_type) { unsigned int *values = (unsigned int*) temp_data->srb_buf.buffer; uint32_t theMagicCookieSize = theEncoder->GetMagicCookieSize(temp_data->outputFormat.mChannelsPerFrame); uint8_t* theMagicCookie = (uint8_t *)calloc(theMagicCookieSize, 1); theEncoder->GetMagicCookie(theMagicCookie, &theMagicCookieSize); values[0] = FOURCC_CONF; values[1] = 16 + theMagicCookieSize; values[2] = FOURCC_ALAC; values[3] = temp_data->outputFormat.mSampleRate; values[4] = temp_data->outputFormat.mChannelsPerFrame; values[5] = theMagicCookieSize; memcpy(values + 6, theMagicCookie, theMagicCookieSize); free(theMagicCookie); } SRB_SendGetWriterBuff(temp_data->srb_handle, &temp_data->srb_buf); temp_data->send_conf = false; } if (temp_data->process_status == STOP) { pthread_cond_signal(&(temp_data->data_cond)); pthread_mutex_unlock(&(temp_data->data_mutex)); return; } pthread_mutex_unlock(&(temp_data->data_mutex)); if (pid_do_encoding()) { if (FOURCC_ALAC == temp_data->enc_type) { int32_t encode_data_bytes = audio_info->dwDataBytes; int32_t status = -1; status = theEncoder->Encode(temp_data->inputFormat, temp_data->outputFormat, audio_info->ppbyAudioBufs[0], temp_data->srb_buf.buffer + MAX_AUDIO_DATA_HEADER_SIZE, &encode_data_bytes); if (0 == status && encode_data_bytes > 0) { unsigned int* buf_ptr = (unsigned int*) temp_data->srb_buf.buffer; buf_ptr[0] = temp_data->enc_type; buf_ptr[1] = audio_info->tDataTimestamp.tv_sec; buf_ptr[2] = audio_info->tDataTimestamp.tv_usec; buf_ptr[3] = encode_data_bytes; buf_ptr[4] = temp_data->seq_num; SRB_SendGetWriterBuff(temp_data->srb_handle, &temp_data->srb_buf); } else { fprintf(stderr, "[%s, %s]: Encode error !!! status(%d), encode_data_bytes(%d)\n", __FILE__, __func__, status, encode_data_bytes); } ++(temp_data->seq_num); } } } static void msg_callback(MsgContext* msg, void* user_data) { user_data_t *temp_data = (user_data_t*) user_data; if (!strncasecmp(msg->pszHost, "encoder", 7)) { if (!strcasecmp(msg->pszCmd, "start")) { if (msg->dwDataSize) { pid_t *ptNew_pid = (pid_t *)msg->pbyData; pid_start(*ptNew_pid); } else { pid_default(1); } printf("start .............. \n"); } else if (!strcasecmp(msg->pszCmd, "stop")) { if (msg->dwDataSize) { pid_t *ptNew_pid = (pid_t *)msg->pbyData; pid_stop(*ptNew_pid); } else { pid_default(0); } printf("stop .............. \n"); } else if (!strcasecmp(msg->pszCmd, "forceCI")) { printf("forceCI ..............\n"); temp_data->send_conf = true; } } else if (!strcasecmp(msg->pszHost, SR_MODULE_NAME)) { if (!strcasecmp(msg->pszCmd, SUSPEND_CMD)) { pthread_mutex_lock(&(temp_data->data_mutex)); temp_data->process_status = STOP; pthread_cond_wait(&(temp_data->data_cond), &(temp_data->data_mutex)); pthread_mutex_unlock(&(temp_data->data_mutex)); ATK_AudioCap_Release(*(temp_data->p_cap_handle)); MsgBroker_SuspendAckMsg(); } else if(!strcasecmp(msg->pszCmd, RESUME_CMD)) { *(temp_data->p_cap_handle) = ATK_AudioCap_Init(temp_data->p_audiocap_config); pthread_mutex_lock(&(temp_data->data_mutex)); temp_data->process_status = START; pthread_mutex_unlock(&(temp_data->data_mutex)); } } if (msg->bHasResponse) msg->dwDataSize = 0; } static void exit_process() { is_terminate_ = 1; } static void sig_kill(int signo) { fprintf(stderr, "[%s,%s] Receive SIGNAL %d!!!\n", __FILE__, __func__, signo); switch(signo) { case SIGTERM: case SIGINT: exit_process(); break; default: break; } } static void print_usage(const char *ap_name) { fprintf(stderr, "Usage:\n" " %s [-b bit_depth] [-c channels] [-d PCM_device_name] [-f command_FIFO_path] [-h] [-i input_type] [-r sample_rate] [-D]\n" "Options:\n" " -b Bit depth for audio (Default: 16 -> S16_LE).\n" " -c Channel number (Default: 2).\n" " -d PCM device name for ALSA (Default: hw:0,0).\n" " -f The path of command FIFO (Default: /tmp/aenc/c0/command.fifo).\n" " -h This help.\n" " -i Input type of audio (0: MicIn, 1: LineIn. Default: 1 -> LineIn).\n" " -r Sample rate for audio (Default: 8000).\n" " -D Run as Daemon.\n" , ap_name); } int main(int argc, char **argv) { int opt; bool is_daemon = false; // Default setting. int input_type = 1; //0: MicIn, 1: LineIn int bit_depth = 16; std::string srb_name = "aenc_srb_1"; std::string pcm_name = "hw:0,0"; std::string cmd_fifo_path = CMD_FIFO_PATH; ATK_AUDIOCAP_CONFIG_T audiocap_config; ATK_AUDIOCAP_HANDLE_T *cap_handle = NULL; memset(&audiocap_config, 0, sizeof(ATK_AUDIOCAP_CONFIG_T)); audiocap_config.szPcmName = pcm_name.c_str(); audiocap_config.bIsInterleaved = 1; audiocap_config.eFormat = SND_PCM_FORMAT_S16_LE; audiocap_config.dwChannelsCount = 2; audiocap_config.dwSampleRate = 8000; audiocap_config.dwPeriodsPerBuffer = 8; audiocap_config.dwPeriodSizeInFrames = PERIOD_SIZE_IN_FRAMES; audiocap_config.bUseSimpleConfig = 0; while ((opt = getopt(argc, argv, "b:c:d:f:Dh:i:r:")) != -1) { switch(opt) { case 'b': bit_depth = atoi(optarg); break; case 'c': audiocap_config.dwChannelsCount = atoi(optarg); break; case 'd': pcm_name = optarg; audiocap_config.szPcmName = pcm_name.c_str(); break; case 'f': cmd_fifo_path = optarg; break; case 'h': print_usage(argv[0]); exit(EXIT_FAILURE); case 'i': input_type = (atoi(optarg) != 0)? 1:0; break; case 'r': audiocap_config.dwSampleRate = atoi(optarg); break; case 'D': is_daemon = true; break; default: print_usage(argv[0]); exit(EXIT_FAILURE); } } //audiocap_config.dwPeriodSizeInFrames *= (bit_depth >> 4); switch (bit_depth) { case 16: audiocap_config.eFormat = SND_PCM_FORMAT_S16_LE; break; case 24: audiocap_config.eFormat = SND_PCM_FORMAT_S24_LE; break; case 32: audiocap_config.eFormat = SND_PCM_FORMAT_S32_LE; break; default: print_usage(argv[0]); exit(EXIT_FAILURE); } signal(SIGTERM, sig_kill); signal(SIGINT, sig_kill); if (is_daemon) { daemon(1,1); } if (input_type == 1) { ATK_Audio_InputSelection(kTKAudioLineIn); //set audio volume to 90 ATK_Audio_SetCaptureVolume(90); } else { ATK_Audio_InputSelection(kTKAudioMicIn); //set audio volume to 90 ATK_Audio_SetCaptureVolume(90); } // Callbacks for audio capture and the private user data for callback. user_data_t user_data; memset(&user_data, 0, sizeof(user_data_t)); user_data.send_conf = true; pthread_mutex_init(&(user_data.data_mutex), NULL); pthread_cond_init(&(user_data.data_cond), NULL); user_data.process_status = START; user_data.p_audiocap_config = &audiocap_config; user_data.p_cap_handle = &cap_handle; audiocap_config.pfnCallback = audiocap_callback; audiocap_config.pUserData = (void*) (&user_data); // TODO: init ALAC encoder { ALACEncoder* theEncoder = new ALACEncoder; memset(&user_data.inputFormat, 0, sizeof(AudioFormatDescription)); memset(&user_data.outputFormat, 0, sizeof(AudioFormatDescription)); // setup input format user_data.inputFormat.mFormatID = kALACFormatLinearPCM; user_data.inputFormat.mChannelsPerFrame = audiocap_config.dwChannelsCount; user_data.inputFormat.mSampleRate = audiocap_config.dwSampleRate; user_data.inputFormat.mBitsPerChannel = bit_depth; user_data.inputFormat.mFormatFlags = kALACFormatFlagIsSignedInteger | kALACFormatFlagIsPacked; // always little endian user_data.inputFormat.mBytesPerPacket = user_data.inputFormat.mBytesPerFrame = (user_data.inputFormat.mBitsPerChannel >> 3) * user_data.inputFormat.mChannelsPerFrame; user_data.inputFormat.mFramesPerPacket = 1; user_data.inputFormat.mReserved = 0; // setup output format user_data.outputFormat.mFormatID = kALACFormatAppleLossless; user_data.outputFormat.mSampleRate = audiocap_config.dwSampleRate; switch(bit_depth) { case 16: user_data.outputFormat.mFormatFlags = kTestFormatFlag_16BitSourceData; break; case 24: user_data.outputFormat.mFormatFlags = kTestFormatFlag_24BitSourceData; break; case 32: user_data.outputFormat.mFormatFlags = kTestFormatFlag_32BitSourceData; break; default: return -1; break; } user_data.outputFormat.mFramesPerPacket = kALACDefaultFramesPerPacket; user_data.outputFormat.mChannelsPerFrame = audiocap_config.dwChannelsCount; user_data.outputFormat.mBytesPerPacket = user_data.outputFormat.mBytesPerFrame = user_data.outputFormat.mBitsPerChannel = user_data.outputFormat.mReserved = 0; theEncoder->SetFrameSize(user_data.outputFormat.mFramesPerPacket); theEncoder->InitializeEncoder(user_data.outputFormat); user_data.enc_type = FOURCC_ALAC; user_data.enc_handle = (void *) theEncoder; } //SynRingBuffer user_data.srb_handle = SRB_InitWriter(srb_name.c_str(), MAX_RING_BUF_SIZE, 4); memset(&user_data.srb_buf, 0, sizeof(srb_buffer_t)); // Get first buffer from SyncRingBuf. SRB_SendGetWriterBuff(user_data.srb_handle, &user_data.srb_buf); // Initialize the audio capture. cap_handle = ATK_AudioCap_Init(&audiocap_config); if(cap_handle == NULL) { fprintf(stderr, "[%s,%s] Can't initialize the audio capture.\n", __FILE__, __func__); goto main_end; } #ifdef DUMP_PCM_DATA if(open_pcm_file() < 0) { goto main_end; } #endif MsgBroker_RegisterMsg(cmd_fifo_path.c_str()); // Enter the main message loop. MsgBroker_Run(cmd_fifo_path.c_str(), msg_callback, &user_data, &is_terminate_); MsgBroker_UnRegisterMsg(); main_end: #ifdef DUMP_PCM_DATA close_pcm_file(); #endif ATK_AudioCap_Release(cap_handle); delete (ALACEncoder*)user_data.enc_handle; SRB_Release(user_data.srb_handle); pthread_mutex_destroy(&(user_data.data_mutex)); pthread_cond_destroy(&(user_data.data_cond)); return 0; }