466 lines
13 KiB
C++
466 lines
13 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 <stdio.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <string>
|
|
#include <list>
|
|
#include <thread>
|
|
#include <mutex>
|
|
#include <condition_variable>
|
|
#include <memory>
|
|
|
|
#include <audiotk/audio_common.h>
|
|
#include <audiotk/audio_playback_mmap.h>
|
|
#include <MsgBroker/msg_broker.h>
|
|
#include <SyncRingBuffer/sync_ring_buffer.h>
|
|
|
|
#include <alac/ALACAudioTypes.h>
|
|
#include <alac/ALACDecoder.h>
|
|
#include <alac/ALACBitUtilities.h>
|
|
|
|
//#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;
|
|
}
|
|
|