gf_ai_box/include/modules/apps/alacaenc/alac_aenc_mmap.cpp
2026-04-12 17:47:54 +08:00

588 lines
16 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 <string>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <audiotk/audio_capture_mmap.h>
#include <audiotk/audio_vol_ctrl.h>
#include <MsgBroker/msg_broker.h>
#include <SyncRingBuffer/sync_ring_buffer.h>
#include <alac/ALACAudioTypes.h>
#include <alac/ALACEncoder.h>
#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;
}