440 lines
13 KiB
Plaintext
440 lines
13 KiB
Plaintext
|
|
/***************************************************************************************
|
||
|
|
*
|
||
|
|
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||
|
|
*
|
||
|
|
* By downloading, copying, installing or using the software you agree to this license.
|
||
|
|
* If you do not agree to this license, do not download, install,
|
||
|
|
* copy or use the software.
|
||
|
|
*
|
||
|
|
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
|
||
|
|
*
|
||
|
|
* Redistribution and use in binary forms, with or without modification, are permitted.
|
||
|
|
*
|
||
|
|
* Unless required by applicable law or agreed to in writing, software distributed
|
||
|
|
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||
|
|
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
|
||
|
|
* language governing permissions and limitations under the License.
|
||
|
|
*
|
||
|
|
****************************************************************************************/
|
||
|
|
|
||
|
|
#include "sys_inc.h"
|
||
|
|
#include "audio_play_avf.h"
|
||
|
|
#include <CoreAudio/CoreAudio.h>
|
||
|
|
#include <AudioToolbox/AudioToolbox.h>
|
||
|
|
#include <AudioUnit/AudioUnit.h>
|
||
|
|
|
||
|
|
/***************************************************************************************/
|
||
|
|
|
||
|
|
typedef struct
|
||
|
|
{
|
||
|
|
BOOL play_flag;
|
||
|
|
pthread_t play_thread;
|
||
|
|
AudioQueueRef audio_queue;
|
||
|
|
int num_audio_buffers;
|
||
|
|
AudioQueueBufferRef * audio_buffer;
|
||
|
|
void * buffer;
|
||
|
|
uint32 buffer_offset;
|
||
|
|
uint32 buffer_size;
|
||
|
|
AudioStreamBasicDescription strdesc;
|
||
|
|
AudioDeviceID device_id;
|
||
|
|
int samplerate;
|
||
|
|
uint16 channels;
|
||
|
|
uint16 samples;
|
||
|
|
int samplefmt;
|
||
|
|
avf_audio_play_callback callback;
|
||
|
|
void * userdata;
|
||
|
|
void * mutex_cb;
|
||
|
|
} AVFAudioPlayContext;
|
||
|
|
|
||
|
|
/***************************************************************************************/
|
||
|
|
|
||
|
|
BOOL avf_audio_play_prepare_device(AVFAudioPlayContext * context)
|
||
|
|
{
|
||
|
|
AudioDeviceID devid;
|
||
|
|
OSStatus result = noErr;
|
||
|
|
uint32 size = 0;
|
||
|
|
uint32 alive = 0;
|
||
|
|
pid_t pid = 0;
|
||
|
|
|
||
|
|
AudioObjectPropertyAddress addr = {
|
||
|
|
0,
|
||
|
|
kAudioObjectPropertyScopeGlobal,
|
||
|
|
kAudioObjectPropertyElementMain
|
||
|
|
};
|
||
|
|
|
||
|
|
size = sizeof(AudioDeviceID);
|
||
|
|
addr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
|
||
|
|
result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
|
||
|
|
0, NULL, &size, &devid);
|
||
|
|
if (result != noErr)
|
||
|
|
{
|
||
|
|
log_print(HT_LOG_ERR, "%s, AudioObjectGetPropertyData failed\r\n", __FUNCTION__);
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
addr.mSelector = kAudioDevicePropertyDeviceIsAlive;
|
||
|
|
addr.mScope = kAudioDevicePropertyScopeOutput;
|
||
|
|
|
||
|
|
size = sizeof (alive);
|
||
|
|
result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive);
|
||
|
|
if (result != noErr)
|
||
|
|
{
|
||
|
|
log_print(HT_LOG_ERR, "%s, AudioObjectGetPropertyData failed\r\n", __FUNCTION__);
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!alive)
|
||
|
|
{
|
||
|
|
log_print(HT_LOG_ERR, "%s, requested device exists, but isn't alive\r\n", __FUNCTION__);
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
addr.mSelector = kAudioDevicePropertyHogMode;
|
||
|
|
size = sizeof (pid);
|
||
|
|
result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &pid);
|
||
|
|
|
||
|
|
/* some devices don't support this property, so errors are fine here. */
|
||
|
|
if ((result == noErr) && (pid != -1))
|
||
|
|
{
|
||
|
|
log_print(HT_LOG_ERR, "%s, requested device is being hogged\r\n", __FUNCTION__);
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
context->device_id = devid;
|
||
|
|
|
||
|
|
return TRUE;
|
||
|
|
}
|
||
|
|
|
||
|
|
int avf_audio_play_assign_device(AVFAudioPlayContext * context)
|
||
|
|
{
|
||
|
|
const AudioObjectPropertyAddress prop = {
|
||
|
|
kAudioDevicePropertyDeviceUID,
|
||
|
|
kAudioDevicePropertyScopeOutput,
|
||
|
|
kAudioObjectPropertyElementMain
|
||
|
|
};
|
||
|
|
|
||
|
|
OSStatus result;
|
||
|
|
CFStringRef devuid;
|
||
|
|
uint32 devuidsize = sizeof (devuid);
|
||
|
|
|
||
|
|
result = AudioObjectGetPropertyData(context->device_id, &prop, 0, NULL, &devuidsize, &devuid);
|
||
|
|
if (result != noErr)
|
||
|
|
{
|
||
|
|
log_print(HT_LOG_ERR, "%s, AudioObjectGetPropertyData failed\r\n", __FUNCTION__);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
result = AudioQueueSetProperty(context->audio_queue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize);
|
||
|
|
if (result != noErr)
|
||
|
|
{
|
||
|
|
log_print(HT_LOG_ERR, "%s, AudioQueueSetProperty failed\r\n", __FUNCTION__);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
void avf_audio_play_cb(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
|
||
|
|
{
|
||
|
|
AVFAudioPlayContext * context = (AVFAudioPlayContext *) inUserData;
|
||
|
|
|
||
|
|
uint32 remaining = inBuffer->mAudioDataBytesCapacity;
|
||
|
|
uint8 *ptr = (uint8 *) inBuffer->mAudioData;
|
||
|
|
|
||
|
|
while (remaining > 0)
|
||
|
|
{
|
||
|
|
uint32 len;
|
||
|
|
|
||
|
|
if (context->buffer_offset >= context->buffer_size)
|
||
|
|
{
|
||
|
|
sys_os_mutex_enter(context->mutex_cb);
|
||
|
|
if (context->callback)
|
||
|
|
{
|
||
|
|
context->callback(context->buffer, context->buffer_size, context->userdata);
|
||
|
|
}
|
||
|
|
sys_os_mutex_leave(context->mutex_cb);
|
||
|
|
context->buffer_offset = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
len = context->buffer_size - context->buffer_offset;
|
||
|
|
if (len > remaining)
|
||
|
|
{
|
||
|
|
len = remaining;
|
||
|
|
}
|
||
|
|
memcpy(ptr, (char *)context->buffer + context->buffer_offset, len);
|
||
|
|
ptr = ptr + len;
|
||
|
|
remaining -= len;
|
||
|
|
context->buffer_offset += len;
|
||
|
|
}
|
||
|
|
|
||
|
|
AudioQueueEnqueueBuffer(context->audio_queue, inBuffer, 0, NULL);
|
||
|
|
|
||
|
|
inBuffer->mAudioDataByteSize = inBuffer->mAudioDataBytesCapacity;
|
||
|
|
}
|
||
|
|
|
||
|
|
int avf_audio_play_prepare_queue(AVFAudioPlayContext * context)
|
||
|
|
{
|
||
|
|
int i;
|
||
|
|
OSStatus result;
|
||
|
|
const AudioStreamBasicDescription *strdesc = &context->strdesc;
|
||
|
|
|
||
|
|
result = AudioQueueNewOutput(strdesc, avf_audio_play_cb, context, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &context->audio_queue);
|
||
|
|
if (result != noErr)
|
||
|
|
{
|
||
|
|
log_print(HT_LOG_ERR, "%s, AudioQueueNewOutput failed\r\n", __FUNCTION__);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!avf_audio_play_assign_device(context))
|
||
|
|
{
|
||
|
|
log_print(HT_LOG_ERR, "%s, avf_audio_play_assign_device failed\r\n", __FUNCTION__);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
AudioChannelLayout layout;
|
||
|
|
memset(&layout, 0, sizeof(layout));
|
||
|
|
|
||
|
|
layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
|
||
|
|
|
||
|
|
result = AudioQueueSetProperty(context->audio_queue, kAudioQueueProperty_ChannelLayout, &layout, sizeof(layout));
|
||
|
|
if (result != noErr)
|
||
|
|
{
|
||
|
|
log_print(HT_LOG_ERR, "%s, AudioQueueSetProperty failed\r\n", __FUNCTION__);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
uint32 size = strdesc->mBitsPerChannel / 8;
|
||
|
|
size *= context->channels;
|
||
|
|
size *= context->samples;
|
||
|
|
|
||
|
|
/* Allocate a sample buffer */
|
||
|
|
context->buffer_size = size;
|
||
|
|
context->buffer_offset = context->buffer_size;
|
||
|
|
|
||
|
|
context->buffer = malloc(context->buffer_size);
|
||
|
|
if (context->buffer == NULL)
|
||
|
|
{
|
||
|
|
log_print(HT_LOG_ERR, "%s, malloc failed\r\n", __FUNCTION__);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Make sure we can feed the device a minimum amount of time */
|
||
|
|
double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0;
|
||
|
|
|
||
|
|
const double msecs = (context->samples / ((double) context->samplerate)) * 1000.0;
|
||
|
|
int num_audio_buffers = 2;
|
||
|
|
if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS)
|
||
|
|
{
|
||
|
|
/* use more buffers if we have a VERY small sample set. */
|
||
|
|
num_audio_buffers = ((int)ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2);
|
||
|
|
}
|
||
|
|
|
||
|
|
context->num_audio_buffers = num_audio_buffers;
|
||
|
|
context->audio_buffer = (AudioQueueBufferRef *) calloc(1, sizeof(AudioQueueBufferRef) * num_audio_buffers);
|
||
|
|
if (context->audio_buffer == NULL)
|
||
|
|
{
|
||
|
|
log_print(HT_LOG_ERR, "%s, calloc failed\r\n", __FUNCTION__);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (i = 0; i < num_audio_buffers; i++)
|
||
|
|
{
|
||
|
|
result = AudioQueueAllocateBuffer(context->audio_queue, size, &context->audio_buffer[i]);
|
||
|
|
if (result != noErr)
|
||
|
|
{
|
||
|
|
log_print(HT_LOG_ERR, "%s, AudioQueueAllocateBuffer failed\r\n", __FUNCTION__);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
memset(context->audio_buffer[i]->mAudioData, 0, context->audio_buffer[i]->mAudioDataBytesCapacity);
|
||
|
|
context->audio_buffer[i]->mAudioDataByteSize = context->audio_buffer[i]->mAudioDataBytesCapacity;
|
||
|
|
|
||
|
|
result = AudioQueueEnqueueBuffer(context->audio_queue, context->audio_buffer[i], 0, NULL);
|
||
|
|
if (result != noErr)
|
||
|
|
{
|
||
|
|
log_print(HT_LOG_ERR, "%s, AudioQueueEnqueueBuffer failed\r\n", __FUNCTION__);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
result = AudioQueueStart(context->audio_queue, NULL);
|
||
|
|
if (result != noErr)
|
||
|
|
{
|
||
|
|
log_print(HT_LOG_ERR, "%s, AudioQueueStart failed\r\n", __FUNCTION__);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* We're running! */
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
void * avf_audio_play_thread(void * argv)
|
||
|
|
{
|
||
|
|
AVFAudioPlayContext * context = (AVFAudioPlayContext *) argv;
|
||
|
|
|
||
|
|
const int rc = avf_audio_play_prepare_queue(context);
|
||
|
|
if (!rc)
|
||
|
|
{
|
||
|
|
log_print(HT_LOG_ERR, "%s, avf_audio_play_prepare_queue failed\r\n", __FUNCTION__);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
while (context->play_flag)
|
||
|
|
{
|
||
|
|
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
context->play_thread = 0;
|
||
|
|
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
void * avf_audio_play_init(int samplerate, int channels)
|
||
|
|
{
|
||
|
|
AVFAudioPlayContext * context = (AVFAudioPlayContext *)malloc(sizeof(AVFAudioPlayContext));
|
||
|
|
if (NULL == context)
|
||
|
|
{
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
memset(context, 0, sizeof(AVFAudioPlayContext));
|
||
|
|
|
||
|
|
context->samplerate = samplerate;
|
||
|
|
context->channels = channels;
|
||
|
|
context->samples = 1024;
|
||
|
|
context->samplefmt = 1; // AV_SAMPLE_FMT_S16
|
||
|
|
|
||
|
|
AudioStreamBasicDescription *strdesc = &context->strdesc;
|
||
|
|
|
||
|
|
memset(strdesc, 0, sizeof(AudioStreamBasicDescription));
|
||
|
|
|
||
|
|
strdesc->mFormatID = kAudioFormatLinearPCM;
|
||
|
|
strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked;
|
||
|
|
strdesc->mChannelsPerFrame = channels;
|
||
|
|
strdesc->mSampleRate = samplerate;
|
||
|
|
strdesc->mFramesPerPacket = 1;
|
||
|
|
strdesc->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
|
||
|
|
strdesc->mBitsPerChannel = 16;
|
||
|
|
strdesc->mBytesPerFrame = strdesc->mChannelsPerFrame * strdesc->mBitsPerChannel / 8;
|
||
|
|
strdesc->mBytesPerPacket = strdesc->mBytesPerFrame * strdesc->mFramesPerPacket;
|
||
|
|
|
||
|
|
if (!avf_audio_play_prepare_device(context))
|
||
|
|
{
|
||
|
|
log_print(HT_LOG_ERR, "%s, avf_prepare_device failed!\r\n", __FUNCTION__);
|
||
|
|
goto fail;
|
||
|
|
}
|
||
|
|
|
||
|
|
context->play_flag = TRUE;
|
||
|
|
context->play_thread = sys_os_create_thread((void *)avf_audio_play_thread, (void *)context);
|
||
|
|
if (!context->play_thread)
|
||
|
|
{
|
||
|
|
goto fail;
|
||
|
|
}
|
||
|
|
|
||
|
|
context->mutex_cb = sys_os_create_mutex();
|
||
|
|
|
||
|
|
return context;
|
||
|
|
|
||
|
|
fail:
|
||
|
|
|
||
|
|
avf_audio_play_uninit((void *)context);
|
||
|
|
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
void avf_audio_play_uninit(void * ctx)
|
||
|
|
{
|
||
|
|
if (NULL == ctx)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
AVFAudioPlayContext * context = (AVFAudioPlayContext *)ctx;
|
||
|
|
|
||
|
|
if (context->audio_queue)
|
||
|
|
{
|
||
|
|
AudioQueueDispose(context->audio_queue, 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
context->play_flag = FALSE;
|
||
|
|
|
||
|
|
while (context->play_thread)
|
||
|
|
{
|
||
|
|
usleep(200*1000);
|
||
|
|
}
|
||
|
|
|
||
|
|
free(context->audio_buffer);
|
||
|
|
free(context->buffer);
|
||
|
|
|
||
|
|
sys_os_destroy_sig_mutex(context->mutex_cb);
|
||
|
|
|
||
|
|
free(context);
|
||
|
|
}
|
||
|
|
|
||
|
|
void avf_audio_play_set_callback(void * ctx, avf_audio_play_callback cb, void * userdata)
|
||
|
|
{
|
||
|
|
if (NULL == ctx)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
AVFAudioPlayContext * context = (AVFAudioPlayContext *)ctx;
|
||
|
|
|
||
|
|
sys_os_mutex_enter(context->mutex_cb);
|
||
|
|
context->callback = cb;
|
||
|
|
context->userdata = userdata;
|
||
|
|
sys_os_mutex_leave(context->mutex_cb);
|
||
|
|
}
|
||
|
|
|
||
|
|
BOOL avf_audio_play_set_volume(void * ctx, double volume)
|
||
|
|
{
|
||
|
|
if (NULL == ctx)
|
||
|
|
{
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
AVFAudioPlayContext * context = (AVFAudioPlayContext *)ctx;
|
||
|
|
|
||
|
|
if (!context->audio_queue)
|
||
|
|
{
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
OSStatus result = AudioQueueSetParameter(context->audio_queue, kAudioQueueParam_Volume, volume);
|
||
|
|
if (result != noErr)
|
||
|
|
{
|
||
|
|
log_print(HT_LOG_ERR, "%s, AudioQueueSetParameter failed\r\n", __FUNCTION__);
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
return TRUE;
|
||
|
|
}
|
||
|
|
|
||
|
|
double avf_audio_play_get_volume(void * ctx)
|
||
|
|
{
|
||
|
|
if (NULL == ctx)
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
double volume = 0;
|
||
|
|
AVFAudioPlayContext * context = (AVFAudioPlayContext *)ctx;
|
||
|
|
|
||
|
|
if (!context->audio_queue)
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
OSStatus result = AudioQueueGetParameter(context->audio_queue, kAudioQueueParam_Volume, (float *)&volume);
|
||
|
|
if (result != noErr)
|
||
|
|
{
|
||
|
|
log_print(HT_LOG_ERR, "%s, AudioQueueGetParameter failed\r\n", __FUNCTION__);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return volume;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|