mirror of https://github.com/stenzek/duckstation
dep/cubeb: Minimize and update to 54217bc
parent
7cc52bba23
commit
1b948aab62
@ -1,4 +0,0 @@
|
||||
@PACKAGE_INIT@
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/cubebTargets.cmake")
|
||||
check_required_components(cubeb)
|
||||
@ -1,14 +0,0 @@
|
||||
SET(CMAKE_SYSTEM_NAME Windows)
|
||||
|
||||
set(COMPILER_PREFIX "i686-w64-mingw32")
|
||||
|
||||
find_program(CMAKE_RC_COMPILER NAMES ${COMPILER_PREFIX}-windres)
|
||||
find_program(CMAKE_C_COMPILER NAMES ${COMPILER_PREFIX}-gcc-posix)
|
||||
find_program(CMAKE_CXX_COMPILER NAMES ${COMPILER_PREFIX}-g++-posix)
|
||||
|
||||
SET(CMAKE_FIND_ROOT_PATH /usr/${COMPILER_PREFIX})
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
{
|
||||
snd_config_update-malloc
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
...
|
||||
fun:snd_config_update_r
|
||||
}
|
||||
{
|
||||
snd1_dlobj_cache_get-malloc
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
...
|
||||
fun:snd1_dlobj_cache_get
|
||||
}
|
||||
{
|
||||
parse_defs-malloc
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
...
|
||||
fun:parse_defs
|
||||
}
|
||||
{
|
||||
parse_defs-calloc
|
||||
Memcheck:Leak
|
||||
fun:calloc
|
||||
...
|
||||
fun:parse_defs
|
||||
}
|
||||
{
|
||||
pa_client_conf_from_x11-malloc
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
...
|
||||
fun:pa_client_conf_from_x11
|
||||
}
|
||||
|
||||
@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 <stdint.h>
|
||||
|
||||
/*
|
||||
* The following definitions are copied from the android sources. Only the
|
||||
* relevant enum member and values needed are copied.
|
||||
*/
|
||||
|
||||
/*
|
||||
* From
|
||||
* https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/utils/Errors.h
|
||||
*/
|
||||
typedef int32_t status_t;
|
||||
|
||||
/*
|
||||
* From
|
||||
* https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioTrack.h
|
||||
*/
|
||||
struct Buffer {
|
||||
uint32_t flags;
|
||||
int channelCount;
|
||||
int format;
|
||||
size_t frameCount;
|
||||
size_t size;
|
||||
union {
|
||||
void * raw;
|
||||
short * i16;
|
||||
int8_t * i8;
|
||||
};
|
||||
};
|
||||
|
||||
enum event_type {
|
||||
EVENT_MORE_DATA = 0,
|
||||
EVENT_UNDERRUN = 1,
|
||||
EVENT_LOOP_END = 2,
|
||||
EVENT_MARKER = 3,
|
||||
EVENT_NEW_POS = 4,
|
||||
EVENT_BUFFER_END = 5
|
||||
};
|
||||
|
||||
/**
|
||||
* From
|
||||
* https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioSystem.h
|
||||
* and
|
||||
* https://android.googlesource.com/platform/system/core/+/android-4.2.2_r1/include/system/audio.h
|
||||
*/
|
||||
|
||||
#define AUDIO_STREAM_TYPE_MUSIC 3
|
||||
|
||||
enum {
|
||||
AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS = 0x1,
|
||||
AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS = 0x2,
|
||||
AUDIO_CHANNEL_OUT_MONO_ICS = AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS,
|
||||
AUDIO_CHANNEL_OUT_STEREO_ICS =
|
||||
(AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS | AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS)
|
||||
} AudioTrack_ChannelMapping_ICS;
|
||||
|
||||
enum {
|
||||
AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy = 0x4,
|
||||
AUDIO_CHANNEL_OUT_FRONT_RIGHT_Legacy = 0x8,
|
||||
AUDIO_CHANNEL_OUT_MONO_Legacy = AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy,
|
||||
AUDIO_CHANNEL_OUT_STEREO_Legacy = (AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy |
|
||||
AUDIO_CHANNEL_OUT_FRONT_RIGHT_Legacy)
|
||||
} AudioTrack_ChannelMapping_Legacy;
|
||||
|
||||
typedef enum {
|
||||
AUDIO_FORMAT_PCM = 0x00000000,
|
||||
AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1,
|
||||
AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM | AUDIO_FORMAT_PCM_SUB_16_BIT),
|
||||
} AudioTrack_SampleType;
|
||||
@ -1,76 +0,0 @@
|
||||
#ifndef _CUBEB_OUTPUT_LATENCY_H_
|
||||
#define _CUBEB_OUTPUT_LATENCY_H_
|
||||
|
||||
#include "../cubeb-jni.h"
|
||||
#include "cubeb_media_library.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
struct output_latency_function {
|
||||
media_lib * from_lib;
|
||||
cubeb_jni * from_jni;
|
||||
int version;
|
||||
};
|
||||
|
||||
typedef struct output_latency_function output_latency_function;
|
||||
|
||||
const int ANDROID_JELLY_BEAN_MR1_4_2 = 17;
|
||||
|
||||
output_latency_function *
|
||||
cubeb_output_latency_load_method(int version)
|
||||
{
|
||||
output_latency_function * ol = NULL;
|
||||
ol = calloc(1, sizeof(output_latency_function));
|
||||
|
||||
ol->version = version;
|
||||
|
||||
if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
|
||||
ol->from_jni = cubeb_jni_init();
|
||||
return ol;
|
||||
}
|
||||
|
||||
ol->from_lib = cubeb_load_media_library();
|
||||
return ol;
|
||||
}
|
||||
|
||||
bool
|
||||
cubeb_output_latency_method_is_loaded(output_latency_function * ol)
|
||||
{
|
||||
assert(ol);
|
||||
if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
|
||||
return !!ol->from_jni;
|
||||
}
|
||||
|
||||
return !!ol->from_lib;
|
||||
}
|
||||
|
||||
void
|
||||
cubeb_output_latency_unload_method(output_latency_function * ol)
|
||||
{
|
||||
if (!ol) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_jni) {
|
||||
cubeb_jni_destroy(ol->from_jni);
|
||||
}
|
||||
|
||||
if (ol->version <= ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_lib) {
|
||||
cubeb_close_media_library(ol->from_lib);
|
||||
}
|
||||
|
||||
free(ol);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
cubeb_get_output_latency(output_latency_function * ol)
|
||||
{
|
||||
assert(cubeb_output_latency_method_is_loaded(ol));
|
||||
|
||||
if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
|
||||
return cubeb_get_output_latency_from_jni(ol->from_jni);
|
||||
}
|
||||
|
||||
return cubeb_get_output_latency_from_media_library(ol->from_lib);
|
||||
}
|
||||
|
||||
#endif // _CUBEB_OUTPUT_LATENCY_H_
|
||||
@ -1,64 +0,0 @@
|
||||
#ifndef _CUBEB_MEDIA_LIBRARY_H_
|
||||
#define _CUBEB_MEDIA_LIBRARY_H_
|
||||
|
||||
struct media_lib {
|
||||
void * libmedia;
|
||||
int32_t (*get_output_latency)(uint32_t * latency, int stream_type);
|
||||
};
|
||||
|
||||
typedef struct media_lib media_lib;
|
||||
|
||||
media_lib *
|
||||
cubeb_load_media_library()
|
||||
{
|
||||
media_lib ml = {0};
|
||||
ml.libmedia = dlopen("libmedia.so", RTLD_LAZY);
|
||||
if (!ml.libmedia) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get the latency, in ms, from AudioFlinger. First, try the most recent
|
||||
// signature. status_t AudioSystem::getOutputLatency(uint32_t* latency,
|
||||
// audio_stream_type_t streamType)
|
||||
ml.get_output_latency = dlsym(
|
||||
ml.libmedia,
|
||||
"_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t");
|
||||
if (!ml.get_output_latency) {
|
||||
// In case of failure, try the signature from legacy version.
|
||||
// status_t AudioSystem::getOutputLatency(uint32_t* latency, int streamType)
|
||||
ml.get_output_latency =
|
||||
dlsym(ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji");
|
||||
if (!ml.get_output_latency) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
media_lib * rv = NULL;
|
||||
rv = calloc(1, sizeof(media_lib));
|
||||
assert(rv);
|
||||
*rv = ml;
|
||||
return rv;
|
||||
}
|
||||
|
||||
void
|
||||
cubeb_close_media_library(media_lib * ml)
|
||||
{
|
||||
dlclose(ml->libmedia);
|
||||
ml->libmedia = NULL;
|
||||
ml->get_output_latency = NULL;
|
||||
free(ml);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
cubeb_get_output_latency_from_media_library(media_lib * ml)
|
||||
{
|
||||
uint32_t latency = 0;
|
||||
const int audio_stream_type_music = 3;
|
||||
int32_t r = ml->get_output_latency(&latency, audio_stream_type_music);
|
||||
if (r) {
|
||||
return 0;
|
||||
}
|
||||
return latency;
|
||||
}
|
||||
|
||||
#endif // _CUBEB_MEDIA_LIBRARY_H_
|
||||
@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file is similar to the file "OpenSLES_AndroidConfiguration.h" found in
|
||||
* the Android NDK, but removes the #ifdef __cplusplus defines, so we can keep
|
||||
* using a C compiler in cubeb.
|
||||
*/
|
||||
|
||||
#ifndef OPENSL_ES_ANDROIDCONFIGURATION_H_
|
||||
#define OPENSL_ES_ANDROIDCONFIGURATION_H_
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Android AudioRecorder configuration */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
/** Audio recording preset */
|
||||
/** Audio recording preset key */
|
||||
#define SL_ANDROID_KEY_RECORDING_PRESET \
|
||||
((const SLchar *)"androidRecordingPreset")
|
||||
/** Audio recording preset values */
|
||||
/** preset "none" cannot be set, it is used to indicate the current settings
|
||||
* do not match any of the presets. */
|
||||
#define SL_ANDROID_RECORDING_PRESET_NONE ((SLuint32)0x00000000)
|
||||
/** generic recording configuration on the platform */
|
||||
#define SL_ANDROID_RECORDING_PRESET_GENERIC ((SLuint32)0x00000001)
|
||||
/** uses the microphone audio source with the same orientation as the camera
|
||||
* if available, the main device microphone otherwise */
|
||||
#define SL_ANDROID_RECORDING_PRESET_CAMCORDER ((SLuint32)0x00000002)
|
||||
/** uses the main microphone tuned for voice recognition */
|
||||
#define SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION ((SLuint32)0x00000003)
|
||||
/** uses the main microphone tuned for audio communications */
|
||||
#define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32)0x00000004)
|
||||
/** uses the main microphone unprocessed */
|
||||
#define SL_ANDROID_RECORDING_PRESET_UNPROCESSED ((SLuint32)0x00000005)
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Android AudioPlayer configuration */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
/** Audio playback stream type */
|
||||
/** Audio playback stream type key */
|
||||
#define SL_ANDROID_KEY_STREAM_TYPE ((const SLchar *)"androidPlaybackStreamType")
|
||||
|
||||
/** Audio playback stream type values */
|
||||
/* same as android.media.AudioManager.STREAM_VOICE_CALL */
|
||||
#define SL_ANDROID_STREAM_VOICE ((SLint32)0x00000000)
|
||||
/* same as android.media.AudioManager.STREAM_SYSTEM */
|
||||
#define SL_ANDROID_STREAM_SYSTEM ((SLint32)0x00000001)
|
||||
/* same as android.media.AudioManager.STREAM_RING */
|
||||
#define SL_ANDROID_STREAM_RING ((SLint32)0x00000002)
|
||||
/* same as android.media.AudioManager.STREAM_MUSIC */
|
||||
#define SL_ANDROID_STREAM_MEDIA ((SLint32)0x00000003)
|
||||
/* same as android.media.AudioManager.STREAM_ALARM */
|
||||
#define SL_ANDROID_STREAM_ALARM ((SLint32)0x00000004)
|
||||
/* same as android.media.AudioManager.STREAM_NOTIFICATION */
|
||||
#define SL_ANDROID_STREAM_NOTIFICATION ((SLint32)0x00000005)
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Android AudioPlayer and AudioRecorder configuration */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
/** Audio Performance mode.
|
||||
* Performance mode tells the framework how to configure the audio path
|
||||
* for a player or recorder according to application performance and
|
||||
* functional requirements.
|
||||
* It affects the output or input latency based on acceptable tradeoffs on
|
||||
* battery drain and use of pre or post processing effects.
|
||||
* Performance mode should be set before realizing the object and should be
|
||||
* read after realizing the object to check if the requested mode could be
|
||||
* granted or not.
|
||||
*/
|
||||
/** Audio Performance mode key */
|
||||
#define SL_ANDROID_KEY_PERFORMANCE_MODE \
|
||||
((const SLchar *)"androidPerformanceMode")
|
||||
|
||||
/** Audio performance values */
|
||||
/* No specific performance requirement. Allows HW and SW pre/post
|
||||
* processing. */
|
||||
#define SL_ANDROID_PERFORMANCE_NONE ((SLuint32)0x00000000)
|
||||
/* Priority given to latency. No HW or software pre/post processing.
|
||||
* This is the default if no performance mode is specified. */
|
||||
#define SL_ANDROID_PERFORMANCE_LATENCY ((SLuint32)0x00000001)
|
||||
/* Priority given to latency while still allowing HW pre and post
|
||||
* processing. */
|
||||
#define SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS ((SLuint32)0x00000002)
|
||||
/* Priority given to power saving if latency is not a concern.
|
||||
* Allows HW and SW pre/post processing. */
|
||||
#define SL_ANDROID_PERFORMANCE_POWER_SAVING ((SLuint32)0x00000003)
|
||||
|
||||
#endif /* OPENSL_ES_ANDROIDCONFIGURATION_H_ */
|
||||
@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2016 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#ifndef _CUBEB_SLES_H_
|
||||
#define _CUBEB_SLES_H_
|
||||
#include <SLES/OpenSLES.h>
|
||||
|
||||
static SLresult
|
||||
cubeb_get_sles_engine(SLObjectItf * pEngine, SLuint32 numOptions,
|
||||
const SLEngineOption * pEngineOptions,
|
||||
SLuint32 numInterfaces,
|
||||
const SLInterfaceID * pInterfaceIds,
|
||||
const SLboolean * pInterfaceRequired)
|
||||
{
|
||||
return slCreateEngine(pEngine, numOptions, pEngineOptions, numInterfaces,
|
||||
pInterfaceIds, pInterfaceRequired);
|
||||
}
|
||||
|
||||
static void
|
||||
cubeb_destroy_sles_engine(SLObjectItf * self)
|
||||
{
|
||||
if (*self != NULL) {
|
||||
(**self)->Destroy(*self);
|
||||
*self = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static SLresult
|
||||
cubeb_realize_sles_engine(SLObjectItf self)
|
||||
{
|
||||
return (*self)->Realize(self, SL_BOOLEAN_FALSE);
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,17 +0,0 @@
|
||||
#ifndef CUBEB_ANDROID_H
|
||||
#define CUBEB_ANDROID_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
// If the latency requested is above this threshold, this stream is considered
|
||||
// intended for playback (vs. real-time). Tell Android it should favor saving
|
||||
// power over performance or latency.
|
||||
// This is around 100ms at 44100 or 48000
|
||||
const uint16_t POWERSAVE_LATENCY_FRAMES_THRESHOLD = 4000;
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // CUBEB_ANDROID_H
|
||||
@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2016 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#ifndef CUBEB_ARRAY_QUEUE_H
|
||||
#define CUBEB_ARRAY_QUEUE_H
|
||||
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
void ** buf;
|
||||
size_t num;
|
||||
size_t writePos;
|
||||
size_t readPos;
|
||||
pthread_mutex_t mutex;
|
||||
} array_queue;
|
||||
|
||||
array_queue *
|
||||
array_queue_create(size_t num)
|
||||
{
|
||||
assert(num != 0);
|
||||
array_queue * new_queue = (array_queue *)calloc(1, sizeof(array_queue));
|
||||
new_queue->buf = (void **)calloc(1, sizeof(void *) * num);
|
||||
new_queue->readPos = 0;
|
||||
new_queue->writePos = 0;
|
||||
new_queue->num = num;
|
||||
|
||||
pthread_mutex_init(&new_queue->mutex, NULL);
|
||||
|
||||
return new_queue;
|
||||
}
|
||||
|
||||
void
|
||||
array_queue_destroy(array_queue * aq)
|
||||
{
|
||||
assert(aq);
|
||||
|
||||
free(aq->buf);
|
||||
pthread_mutex_destroy(&aq->mutex);
|
||||
free(aq);
|
||||
}
|
||||
|
||||
int
|
||||
array_queue_push(array_queue * aq, void * item)
|
||||
{
|
||||
assert(item);
|
||||
|
||||
pthread_mutex_lock(&aq->mutex);
|
||||
int ret = -1;
|
||||
if (aq->buf[aq->writePos % aq->num] == NULL) {
|
||||
aq->buf[aq->writePos % aq->num] = item;
|
||||
aq->writePos = (aq->writePos + 1) % aq->num;
|
||||
ret = 0;
|
||||
}
|
||||
// else queue is full
|
||||
pthread_mutex_unlock(&aq->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void *
|
||||
array_queue_pop(array_queue * aq)
|
||||
{
|
||||
pthread_mutex_lock(&aq->mutex);
|
||||
void * value = aq->buf[aq->readPos % aq->num];
|
||||
if (value) {
|
||||
aq->buf[aq->readPos % aq->num] = NULL;
|
||||
aq->readPos = (aq->readPos + 1) % aq->num;
|
||||
}
|
||||
pthread_mutex_unlock(&aq->mutex);
|
||||
return value;
|
||||
}
|
||||
|
||||
size_t
|
||||
array_queue_get_size(array_queue * aq)
|
||||
{
|
||||
pthread_mutex_lock(&aq->mutex);
|
||||
ssize_t r = aq->writePos - aq->readPos;
|
||||
if (r < 0) {
|
||||
r = aq->num + r;
|
||||
assert(r >= 0);
|
||||
}
|
||||
pthread_mutex_unlock(&aq->mutex);
|
||||
return (size_t)r;
|
||||
}
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // CUBE_ARRAY_QUEUE_H
|
||||
@ -1,472 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2013 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
#define NDEBUG
|
||||
#endif
|
||||
#include <android/log.h>
|
||||
#include <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "android/audiotrack_definitions.h"
|
||||
#include "cubeb-internal.h"
|
||||
#include "cubeb/cubeb.h"
|
||||
|
||||
#ifndef ALOG
|
||||
#if defined(DEBUG) || defined(FORCE_ALOG)
|
||||
#define ALOG(args...) \
|
||||
__android_log_print(ANDROID_LOG_INFO, "Gecko - Cubeb", ##args)
|
||||
#else
|
||||
#define ALOG(args...)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* A lot of bytes for safety. It should be possible to bring this down a bit. */
|
||||
#define SIZE_AUDIOTRACK_INSTANCE 256
|
||||
|
||||
/**
|
||||
* call dlsym to get the symbol |mangled_name|, handle the error and store the
|
||||
* pointer in |pointer|. Because depending on Android version, we want different
|
||||
* symbols, not finding a symbol is not an error. */
|
||||
#define DLSYM_DLERROR(mangled_name, pointer, lib) \
|
||||
do { \
|
||||
pointer = dlsym(lib, mangled_name); \
|
||||
if (!pointer) { \
|
||||
ALOG("error while loading %stm: %stm\n", mangled_name, dlerror()); \
|
||||
} else { \
|
||||
ALOG("%stm: OK", mangled_name); \
|
||||
} \
|
||||
} while (0);
|
||||
|
||||
static struct cubeb_ops const audiotrack_ops;
|
||||
void
|
||||
audiotrack_destroy(cubeb * context);
|
||||
void
|
||||
audiotrack_stream_destroy(cubeb_stream * stream);
|
||||
|
||||
struct AudioTrack {
|
||||
/* only available on ICS and later. The second int paramter is in fact of type
|
||||
* audio_stream_type_t. */
|
||||
/* static */ status_t (*get_min_frame_count)(int * frame_count,
|
||||
int stream_type, uint32_t rate);
|
||||
/* if we have a recent ctor, but can't find the above symbol, we
|
||||
* can get the minimum frame count with this signature, and we are
|
||||
* running gingerbread. */
|
||||
/* static */ status_t (*get_min_frame_count_gingerbread)(int * frame_count,
|
||||
int stream_type,
|
||||
uint32_t rate);
|
||||
void * (*ctor)(void * instance, int, unsigned int, int, int, int,
|
||||
unsigned int, void (*)(int, void *, void *), void *, int, int);
|
||||
void * (*dtor)(void * instance);
|
||||
void (*start)(void * instance);
|
||||
void (*pause)(void * instance);
|
||||
uint32_t (*latency)(void * instance);
|
||||
status_t (*check)(void * instance);
|
||||
status_t (*get_position)(void * instance, uint32_t * position);
|
||||
/* static */ int (*get_output_samplingrate)(int * samplerate, int stream);
|
||||
status_t (*set_marker_position)(void * instance, unsigned int);
|
||||
status_t (*set_volume)(void * instance, float left, float right);
|
||||
};
|
||||
|
||||
struct cubeb {
|
||||
struct cubeb_ops const * ops;
|
||||
void * library;
|
||||
struct AudioTrack klass;
|
||||
};
|
||||
|
||||
struct cubeb_stream {
|
||||
/* Note: Must match cubeb_stream layout in cubeb.c. */
|
||||
cubeb * context;
|
||||
void * user_ptr;
|
||||
/**/
|
||||
cubeb_stream_params params;
|
||||
cubeb_data_callback data_callback;
|
||||
cubeb_state_callback state_callback;
|
||||
void * instance;
|
||||
/* Number of frames that have been passed to the AudioTrack callback */
|
||||
long unsigned written;
|
||||
int draining;
|
||||
};
|
||||
|
||||
static void
|
||||
audiotrack_refill(int event, void * user, void * info)
|
||||
{
|
||||
cubeb_stream * stream = user;
|
||||
switch (event) {
|
||||
case EVENT_MORE_DATA: {
|
||||
long got = 0;
|
||||
struct Buffer * b = (struct Buffer *)info;
|
||||
|
||||
if (stream->draining) {
|
||||
return;
|
||||
}
|
||||
|
||||
got = stream->data_callback(stream, stream->user_ptr, NULL, b->raw,
|
||||
b->frameCount);
|
||||
|
||||
stream->written += got;
|
||||
|
||||
if (got != (long)b->frameCount) {
|
||||
stream->draining = 1;
|
||||
/* set a marker so we are notified when the are done draining, that is,
|
||||
* when every frame has been played by android. */
|
||||
stream->context->klass.set_marker_position(stream->instance,
|
||||
stream->written);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case EVENT_UNDERRUN:
|
||||
ALOG("underrun in cubeb backend.");
|
||||
break;
|
||||
case EVENT_LOOP_END:
|
||||
assert(0 && "We don't support the loop feature of audiotrack.");
|
||||
break;
|
||||
case EVENT_MARKER:
|
||||
assert(stream->draining);
|
||||
stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED);
|
||||
break;
|
||||
case EVENT_NEW_POS:
|
||||
assert(
|
||||
0 &&
|
||||
"We don't support the setPositionUpdatePeriod feature of audiotrack.");
|
||||
break;
|
||||
case EVENT_BUFFER_END:
|
||||
assert(0 && "Should not happen.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* We are running on gingerbread if we found the gingerbread signature for
|
||||
* getMinFrameCount */
|
||||
static int
|
||||
audiotrack_version_is_gingerbread(cubeb * ctx)
|
||||
{
|
||||
return ctx->klass.get_min_frame_count_gingerbread != NULL;
|
||||
}
|
||||
|
||||
int
|
||||
audiotrack_get_min_frame_count(cubeb * ctx, cubeb_stream_params * params,
|
||||
int * min_frame_count)
|
||||
{
|
||||
status_t status;
|
||||
/* Recent Android have a getMinFrameCount method. */
|
||||
if (!audiotrack_version_is_gingerbread(ctx)) {
|
||||
status = ctx->klass.get_min_frame_count(
|
||||
min_frame_count, AUDIO_STREAM_TYPE_MUSIC, params->rate);
|
||||
} else {
|
||||
status = ctx->klass.get_min_frame_count_gingerbread(
|
||||
min_frame_count, AUDIO_STREAM_TYPE_MUSIC, params->rate);
|
||||
}
|
||||
if (status != 0) {
|
||||
ALOG("error getting the min frame count");
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
int
|
||||
audiotrack_init(cubeb ** context, char const * context_name)
|
||||
{
|
||||
cubeb * ctx;
|
||||
struct AudioTrack * c;
|
||||
|
||||
assert(context);
|
||||
*context = NULL;
|
||||
|
||||
ctx = calloc(1, sizeof(*ctx));
|
||||
assert(ctx);
|
||||
|
||||
/* If we use an absolute path here ("/system/lib/libmedia.so"), and on Android
|
||||
* 2.2, the dlopen succeeds, all the dlsym succeed, but a segfault happens on
|
||||
* the first call to a dlsym'ed function. Somehow this does not happen when
|
||||
* using only the name of the library. */
|
||||
ctx->library = dlopen("libmedia.so", RTLD_LAZY);
|
||||
if (!ctx->library) {
|
||||
ALOG("dlopen error: %s.", dlerror());
|
||||
free(ctx);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
/* Recent Android first, then Gingerbread. */
|
||||
DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_ii",
|
||||
ctx->klass.ctor, ctx->library);
|
||||
DLSYM_DLERROR("_ZN7android10AudioTrackD1Ev", ctx->klass.dtor, ctx->library);
|
||||
|
||||
DLSYM_DLERROR("_ZNK7android10AudioTrack7latencyEv", ctx->klass.latency,
|
||||
ctx->library);
|
||||
DLSYM_DLERROR("_ZNK7android10AudioTrack9initCheckEv", ctx->klass.check,
|
||||
ctx->library);
|
||||
|
||||
DLSYM_DLERROR("_ZN7android11AudioSystem21getOutputSamplingRateEPii",
|
||||
ctx->klass.get_output_samplingrate, ctx->library);
|
||||
|
||||
/* |getMinFrameCount| is available on gingerbread and ICS with different
|
||||
* signatures. */
|
||||
DLSYM_DLERROR(
|
||||
"_ZN7android10AudioTrack16getMinFrameCountEPi19audio_stream_type_tj",
|
||||
ctx->klass.get_min_frame_count, ctx->library);
|
||||
if (!ctx->klass.get_min_frame_count) {
|
||||
DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPiij",
|
||||
ctx->klass.get_min_frame_count_gingerbread, ctx->library);
|
||||
}
|
||||
|
||||
DLSYM_DLERROR("_ZN7android10AudioTrack5startEv", ctx->klass.start,
|
||||
ctx->library);
|
||||
DLSYM_DLERROR("_ZN7android10AudioTrack5pauseEv", ctx->klass.pause,
|
||||
ctx->library);
|
||||
DLSYM_DLERROR("_ZN7android10AudioTrack11getPositionEPj",
|
||||
ctx->klass.get_position, ctx->library);
|
||||
DLSYM_DLERROR("_ZN7android10AudioTrack17setMarkerPositionEj",
|
||||
ctx->klass.set_marker_position, ctx->library);
|
||||
DLSYM_DLERROR("_ZN7android10AudioTrack9setVolumeEff", ctx->klass.set_volume,
|
||||
ctx->library);
|
||||
|
||||
/* check that we have a combination of symbol that makes sense */
|
||||
c = &ctx->klass;
|
||||
if (!(c->ctor && c->dtor && c->latency && c->check &&
|
||||
/* at least one way to get the minimum frame count to request. */
|
||||
(c->get_min_frame_count || c->get_min_frame_count_gingerbread) &&
|
||||
c->start && c->pause && c->get_position && c->set_marker_position)) {
|
||||
ALOG("Could not find all the symbols we need.");
|
||||
audiotrack_destroy(ctx);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
ctx->ops = &audiotrack_ops;
|
||||
|
||||
*context = ctx;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
char const *
|
||||
audiotrack_get_backend_id(cubeb * context)
|
||||
{
|
||||
return "audiotrack";
|
||||
}
|
||||
|
||||
static int
|
||||
audiotrack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
|
||||
{
|
||||
assert(ctx && max_channels);
|
||||
|
||||
/* The android mixer handles up to two channels, see
|
||||
http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67
|
||||
*/
|
||||
*max_channels = 2;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
audiotrack_get_min_latency(cubeb * ctx, cubeb_stream_params params,
|
||||
uint32_t * latency_ms)
|
||||
{
|
||||
/* We always use the lowest latency possible when using this backend (see
|
||||
* audiotrack_stream_init), so this value is not going to be used. */
|
||||
int r;
|
||||
|
||||
r = audiotrack_get_min_frame_count(ctx, ¶ms, (int *)latency_ms);
|
||||
if (r != CUBEB_OK) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
audiotrack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
|
||||
{
|
||||
status_t r;
|
||||
|
||||
r = ctx->klass.get_output_samplingrate((int32_t *)rate, 3 /* MUSIC */);
|
||||
|
||||
return r == 0 ? CUBEB_OK : CUBEB_ERROR;
|
||||
}
|
||||
|
||||
void
|
||||
audiotrack_destroy(cubeb * context)
|
||||
{
|
||||
assert(context);
|
||||
|
||||
dlclose(context->library);
|
||||
|
||||
free(context);
|
||||
}
|
||||
|
||||
int
|
||||
audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream,
|
||||
char const * stream_name, cubeb_devid input_device,
|
||||
cubeb_stream_params * input_stream_params,
|
||||
cubeb_devid output_device,
|
||||
cubeb_stream_params * output_stream_params,
|
||||
unsigned int latency, cubeb_data_callback data_callback,
|
||||
cubeb_state_callback state_callback, void * user_ptr)
|
||||
{
|
||||
cubeb_stream * stm;
|
||||
int32_t channels;
|
||||
uint32_t min_frame_count;
|
||||
|
||||
assert(ctx && stream);
|
||||
|
||||
assert(!input_stream_params && "not supported");
|
||||
if (input_device || output_device) {
|
||||
/* Device selection not yet implemented. */
|
||||
return CUBEB_ERROR_DEVICE_UNAVAILABLE;
|
||||
}
|
||||
|
||||
if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE ||
|
||||
output_stream_params->format == CUBEB_SAMPLE_FLOAT32BE) {
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
if (audiotrack_get_min_frame_count(ctx, output_stream_params,
|
||||
(int *)&min_frame_count)) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
stm = calloc(1, sizeof(*stm));
|
||||
assert(stm);
|
||||
|
||||
stm->context = ctx;
|
||||
stm->data_callback = data_callback;
|
||||
stm->state_callback = state_callback;
|
||||
stm->user_ptr = user_ptr;
|
||||
stm->params = *output_stream_params;
|
||||
|
||||
stm->instance = calloc(SIZE_AUDIOTRACK_INSTANCE, 1);
|
||||
(*(uint32_t *)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) =
|
||||
0xbaadbaad;
|
||||
assert(stm->instance && "cubeb: EOM");
|
||||
|
||||
/* gingerbread uses old channel layout enum */
|
||||
if (audiotrack_version_is_gingerbread(ctx)) {
|
||||
channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_Legacy
|
||||
: AUDIO_CHANNEL_OUT_MONO_Legacy;
|
||||
} else {
|
||||
channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_ICS
|
||||
: AUDIO_CHANNEL_OUT_MONO_ICS;
|
||||
}
|
||||
|
||||
ctx->klass.ctor(stm->instance, AUDIO_STREAM_TYPE_MUSIC, stm->params.rate,
|
||||
AUDIO_FORMAT_PCM_16_BIT, channels, min_frame_count, 0,
|
||||
audiotrack_refill, stm, 0, 0);
|
||||
|
||||
assert((*(uint32_t *)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE -
|
||||
4)) == 0xbaadbaad);
|
||||
|
||||
if (ctx->klass.check(stm->instance)) {
|
||||
ALOG("stream not initialized properly.");
|
||||
audiotrack_stream_destroy(stm);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
*stream = stm;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
void
|
||||
audiotrack_stream_destroy(cubeb_stream * stream)
|
||||
{
|
||||
assert(stream->context);
|
||||
|
||||
stream->context->klass.dtor(stream->instance);
|
||||
|
||||
free(stream->instance);
|
||||
stream->instance = NULL;
|
||||
free(stream);
|
||||
}
|
||||
|
||||
int
|
||||
audiotrack_stream_start(cubeb_stream * stream)
|
||||
{
|
||||
assert(stream->instance);
|
||||
|
||||
stream->context->klass.start(stream->instance);
|
||||
stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED);
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
int
|
||||
audiotrack_stream_stop(cubeb_stream * stream)
|
||||
{
|
||||
assert(stream->instance);
|
||||
|
||||
stream->context->klass.pause(stream->instance);
|
||||
stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED);
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
int
|
||||
audiotrack_stream_get_position(cubeb_stream * stream, uint64_t * position)
|
||||
{
|
||||
uint32_t p;
|
||||
|
||||
assert(stream->instance && position);
|
||||
stream->context->klass.get_position(stream->instance, &p);
|
||||
*position = p;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
int
|
||||
audiotrack_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
|
||||
{
|
||||
assert(stream->instance && latency);
|
||||
|
||||
/* Android returns the latency in ms, we want it in frames. */
|
||||
*latency = stream->context->klass.latency(stream->instance);
|
||||
/* with rate <= 96000, we won't overflow until 44.739 seconds of latency */
|
||||
*latency = (*latency * stream->params.rate) / 1000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
audiotrack_stream_set_volume(cubeb_stream * stream, float volume)
|
||||
{
|
||||
status_t status;
|
||||
|
||||
status = stream->context->klass.set_volume(stream->instance, volume, volume);
|
||||
|
||||
if (status) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static struct cubeb_ops const audiotrack_ops = {
|
||||
.init = audiotrack_init,
|
||||
.get_backend_id = audiotrack_get_backend_id,
|
||||
.get_max_channel_count = audiotrack_get_max_channel_count,
|
||||
.get_min_latency = audiotrack_get_min_latency,
|
||||
.get_preferred_sample_rate = audiotrack_get_preferred_sample_rate,
|
||||
.enumerate_devices = NULL,
|
||||
.device_collection_destroy = NULL,
|
||||
.destroy = audiotrack_destroy,
|
||||
.stream_init = audiotrack_stream_init,
|
||||
.stream_destroy = audiotrack_stream_destroy,
|
||||
.stream_start = audiotrack_stream_start,
|
||||
.stream_stop = audiotrack_stream_stop,
|
||||
.stream_get_position = audiotrack_stream_get_position,
|
||||
.stream_get_latency = audiotrack_stream_get_latency,
|
||||
.stream_get_input_latency = NULL,
|
||||
.stream_set_volume = audiotrack_stream_set_volume,
|
||||
.stream_set_name = NULL,
|
||||
.stream_get_current_device = NULL,
|
||||
.stream_device_destroy = NULL,
|
||||
.stream_register_device_changed_callback = NULL,
|
||||
.register_device_collection_changed = NULL};
|
||||
@ -1,369 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2015 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/fmutex.h>
|
||||
|
||||
#include <kai.h>
|
||||
|
||||
#include "cubeb-internal.h"
|
||||
#include "cubeb/cubeb.h"
|
||||
|
||||
/* We don't support more than 2 channels in KAI */
|
||||
#define MAX_CHANNELS 2
|
||||
|
||||
#define NBUFS 2
|
||||
#define FRAME_SIZE 2048
|
||||
|
||||
struct cubeb_stream_item {
|
||||
cubeb_stream * stream;
|
||||
};
|
||||
|
||||
static struct cubeb_ops const kai_ops;
|
||||
|
||||
struct cubeb {
|
||||
struct cubeb_ops const * ops;
|
||||
};
|
||||
|
||||
struct cubeb_stream {
|
||||
/* Note: Must match cubeb_stream layout in cubeb.c. */
|
||||
cubeb * context;
|
||||
void * user_ptr;
|
||||
/**/
|
||||
cubeb_stream_params params;
|
||||
cubeb_data_callback data_callback;
|
||||
cubeb_state_callback state_callback;
|
||||
|
||||
HKAI hkai;
|
||||
KAISPEC spec;
|
||||
uint64_t total_frames;
|
||||
float soft_volume;
|
||||
_fmutex mutex;
|
||||
float float_buffer[FRAME_SIZE * MAX_CHANNELS];
|
||||
};
|
||||
|
||||
static inline long
|
||||
frames_to_bytes(long frames, cubeb_stream_params params)
|
||||
{
|
||||
return frames * 2 * params.channels; /* 2 bytes per frame */
|
||||
}
|
||||
|
||||
static inline long
|
||||
bytes_to_frames(long bytes, cubeb_stream_params params)
|
||||
{
|
||||
return bytes / 2 / params.channels; /* 2 bytes per frame */
|
||||
}
|
||||
|
||||
static void
|
||||
kai_destroy(cubeb * ctx);
|
||||
|
||||
/*static*/ int
|
||||
kai_init(cubeb ** context, char const * context_name)
|
||||
{
|
||||
cubeb * ctx;
|
||||
|
||||
XASSERT(context);
|
||||
*context = NULL;
|
||||
|
||||
if (kaiInit(KAIM_AUTO))
|
||||
return CUBEB_ERROR;
|
||||
|
||||
ctx = calloc(1, sizeof(*ctx));
|
||||
XASSERT(ctx);
|
||||
|
||||
ctx->ops = &kai_ops;
|
||||
|
||||
*context = ctx;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static char const *
|
||||
kai_get_backend_id(cubeb * ctx)
|
||||
{
|
||||
return "kai";
|
||||
}
|
||||
|
||||
static void
|
||||
kai_destroy(cubeb * ctx)
|
||||
{
|
||||
kaiDone();
|
||||
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
static void
|
||||
float_to_s16ne(int16_t * dst, float * src, size_t n)
|
||||
{
|
||||
long l;
|
||||
|
||||
while (n--) {
|
||||
l = lrintf(*src++ * 0x8000);
|
||||
if (l > 32767)
|
||||
l = 32767;
|
||||
if (l < -32768)
|
||||
l = -32768;
|
||||
*dst++ = (int16_t)l;
|
||||
}
|
||||
}
|
||||
|
||||
static ULONG APIENTRY
|
||||
kai_callback(PVOID cbdata, PVOID buffer, ULONG len)
|
||||
{
|
||||
cubeb_stream * stm = cbdata;
|
||||
void * p;
|
||||
long wanted_frames;
|
||||
long frames;
|
||||
float soft_volume;
|
||||
int elements = len / sizeof(int16_t);
|
||||
|
||||
p = stm->params.format == CUBEB_SAMPLE_FLOAT32NE ? stm->float_buffer : buffer;
|
||||
|
||||
wanted_frames = bytes_to_frames(len, stm->params);
|
||||
frames = stm->data_callback(stm, stm->user_ptr, NULL, p, wanted_frames);
|
||||
|
||||
_fmutex_request(&stm->mutex, 0);
|
||||
stm->total_frames += frames;
|
||||
soft_volume = stm->soft_volume;
|
||||
_fmutex_release(&stm->mutex);
|
||||
|
||||
if (frames < wanted_frames)
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
|
||||
|
||||
if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE)
|
||||
float_to_s16ne(buffer, p, elements);
|
||||
|
||||
if (soft_volume != -1.0f) {
|
||||
int16_t * b = buffer;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < elements; i++)
|
||||
*b++ *= soft_volume;
|
||||
}
|
||||
|
||||
return frames_to_bytes(frames, stm->params);
|
||||
}
|
||||
|
||||
static void
|
||||
kai_stream_destroy(cubeb_stream * stm);
|
||||
|
||||
static int
|
||||
kai_stream_init(cubeb * context, cubeb_stream ** stream,
|
||||
char const * stream_name, cubeb_devid input_device,
|
||||
cubeb_stream_params * input_stream_params,
|
||||
cubeb_devid output_device,
|
||||
cubeb_stream_params * output_stream_params,
|
||||
unsigned int latency, cubeb_data_callback data_callback,
|
||||
cubeb_state_callback state_callback, void * user_ptr)
|
||||
{
|
||||
cubeb_stream * stm;
|
||||
KAISPEC wanted_spec;
|
||||
|
||||
XASSERT(!input_stream_params && "not supported.");
|
||||
if (input_device || output_device) {
|
||||
/* Device selection not yet implemented. */
|
||||
return CUBEB_ERROR_DEVICE_UNAVAILABLE;
|
||||
}
|
||||
|
||||
if (!output_stream_params)
|
||||
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||
|
||||
// Loopback is unsupported
|
||||
if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
|
||||
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (output_stream_params->channels < 1 ||
|
||||
output_stream_params->channels > MAX_CHANNELS)
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
|
||||
XASSERT(context);
|
||||
XASSERT(stream);
|
||||
|
||||
*stream = NULL;
|
||||
|
||||
stm = calloc(1, sizeof(*stm));
|
||||
XASSERT(stm);
|
||||
|
||||
stm->context = context;
|
||||
stm->params = *output_stream_params;
|
||||
stm->data_callback = data_callback;
|
||||
stm->state_callback = state_callback;
|
||||
stm->user_ptr = user_ptr;
|
||||
stm->soft_volume = -1.0f;
|
||||
|
||||
if (_fmutex_create(&stm->mutex, 0)) {
|
||||
free(stm);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
wanted_spec.usDeviceIndex = 0;
|
||||
wanted_spec.ulType = KAIT_PLAY;
|
||||
wanted_spec.ulBitsPerSample = BPS_16;
|
||||
wanted_spec.ulSamplingRate = stm->params.rate;
|
||||
wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM;
|
||||
wanted_spec.ulChannels = stm->params.channels;
|
||||
wanted_spec.ulNumBuffers = NBUFS;
|
||||
wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, stm->params);
|
||||
wanted_spec.fShareable = TRUE;
|
||||
wanted_spec.pfnCallBack = kai_callback;
|
||||
wanted_spec.pCallBackData = stm;
|
||||
|
||||
if (kaiOpen(&wanted_spec, &stm->spec, &stm->hkai)) {
|
||||
_fmutex_close(&stm->mutex);
|
||||
free(stm);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
*stream = stm;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static void
|
||||
kai_stream_destroy(cubeb_stream * stm)
|
||||
{
|
||||
kaiClose(stm->hkai);
|
||||
_fmutex_close(&stm->mutex);
|
||||
free(stm);
|
||||
}
|
||||
|
||||
static int
|
||||
kai_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
|
||||
{
|
||||
XASSERT(ctx && max_channels);
|
||||
|
||||
*max_channels = MAX_CHANNELS;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
kai_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency)
|
||||
{
|
||||
/* We have at least two buffers. One is being played, the other one is being
|
||||
filled. So there is as much latency as one buffer. */
|
||||
*latency = FRAME_SIZE;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
kai_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
|
||||
{
|
||||
cubeb_stream_params params;
|
||||
KAISPEC wanted_spec;
|
||||
KAISPEC spec;
|
||||
HKAI hkai;
|
||||
|
||||
params.format = CUBEB_SAMPLE_S16NE;
|
||||
params.rate = 48000;
|
||||
params.channels = 2;
|
||||
|
||||
wanted_spec.usDeviceIndex = 0;
|
||||
wanted_spec.ulType = KAIT_PLAY;
|
||||
wanted_spec.ulBitsPerSample = BPS_16;
|
||||
wanted_spec.ulSamplingRate = params.rate;
|
||||
wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM;
|
||||
wanted_spec.ulChannels = params.channels;
|
||||
wanted_spec.ulNumBuffers = NBUFS;
|
||||
wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, params);
|
||||
wanted_spec.fShareable = TRUE;
|
||||
wanted_spec.pfnCallBack = kai_callback;
|
||||
wanted_spec.pCallBackData = NULL;
|
||||
|
||||
/* Test 48KHz */
|
||||
if (kaiOpen(&wanted_spec, &spec, &hkai)) {
|
||||
/* Not supported. Fall back to 44.1KHz */
|
||||
params.rate = 44100;
|
||||
} else {
|
||||
/* Supported. Use 48KHz */
|
||||
kaiClose(hkai);
|
||||
}
|
||||
|
||||
*rate = params.rate;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
kai_stream_start(cubeb_stream * stm)
|
||||
{
|
||||
if (kaiPlay(stm->hkai))
|
||||
return CUBEB_ERROR;
|
||||
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
kai_stream_stop(cubeb_stream * stm)
|
||||
{
|
||||
if (kaiStop(stm->hkai))
|
||||
return CUBEB_ERROR;
|
||||
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
kai_stream_get_position(cubeb_stream * stm, uint64_t * position)
|
||||
{
|
||||
_fmutex_request(&stm->mutex, 0);
|
||||
*position = stm->total_frames;
|
||||
_fmutex_release(&stm->mutex);
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
kai_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
|
||||
{
|
||||
/* Out of buffers, one is being played, the others are being filled.
|
||||
So there is as much latency as total buffers - 1. */
|
||||
*latency = bytes_to_frames(stm->spec.ulBufferSize, stm->params) *
|
||||
(stm->spec.ulNumBuffers - 1);
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
kai_stream_set_volume(cubeb_stream * stm, float volume)
|
||||
{
|
||||
_fmutex_request(&stm->mutex, 0);
|
||||
stm->soft_volume = volume;
|
||||
_fmutex_release(&stm->mutex);
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static struct cubeb_ops const kai_ops = {
|
||||
/*.init =*/kai_init,
|
||||
/*.get_backend_id =*/kai_get_backend_id,
|
||||
/*.get_max_channel_count=*/kai_get_max_channel_count,
|
||||
/*.get_min_latency=*/kai_get_min_latency,
|
||||
/*.get_preferred_sample_rate =*/kai_get_preferred_sample_rate,
|
||||
/*.get_preferred_channel_layout =*/NULL,
|
||||
/*.enumerate_devices =*/NULL,
|
||||
/*.device_collection_destroy =*/NULL,
|
||||
/*.destroy =*/kai_destroy,
|
||||
/*.stream_init =*/kai_stream_init,
|
||||
/*.stream_destroy =*/kai_stream_destroy,
|
||||
/*.stream_start =*/kai_stream_start,
|
||||
/*.stream_stop =*/kai_stream_stop,
|
||||
/*.stream_get_position =*/kai_stream_get_position,
|
||||
/*.stream_get_latency = */ kai_stream_get_latency,
|
||||
/*.stream_get_input_latency = */ NULL,
|
||||
/*.stream_set_volume =*/kai_stream_set_volume,
|
||||
/*.stream_set_name =*/NULL,
|
||||
/*.stream_get_current_device =*/NULL,
|
||||
/*.stream_device_destroy =*/NULL,
|
||||
/*.stream_register_device_changed_callback=*/NULL,
|
||||
/*.register_device_collection_changed=*/NULL};
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,733 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2019-2020 Nia Alarie <nia@NetBSD.org>
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
#include "cubeb-internal.h"
|
||||
#include "cubeb/cubeb.h"
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/audioio.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Default to 4 + 1 for the default device. */
|
||||
#ifndef SUN_DEVICE_COUNT
|
||||
#define SUN_DEVICE_COUNT (5)
|
||||
#endif
|
||||
|
||||
/* Supported well by most hardware. */
|
||||
#ifndef SUN_PREFER_RATE
|
||||
#define SUN_PREFER_RATE (48000)
|
||||
#endif
|
||||
|
||||
/* Standard acceptable minimum. */
|
||||
#ifndef SUN_LATENCY_MS
|
||||
#define SUN_LATENCY_MS (40)
|
||||
#endif
|
||||
|
||||
#ifndef SUN_DEFAULT_DEVICE
|
||||
#define SUN_DEFAULT_DEVICE "/dev/audio"
|
||||
#endif
|
||||
|
||||
#ifndef SUN_BUFFER_FRAMES
|
||||
#define SUN_BUFFER_FRAMES (32)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Supported on NetBSD regardless of hardware.
|
||||
*/
|
||||
|
||||
#ifndef SUN_MAX_CHANNELS
|
||||
#ifdef __NetBSD__
|
||||
#define SUN_MAX_CHANNELS (12)
|
||||
#else
|
||||
#define SUN_MAX_CHANNELS (2)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef SUN_MIN_RATE
|
||||
#define SUN_MIN_RATE (1000)
|
||||
#endif
|
||||
|
||||
#ifndef SUN_MAX_RATE
|
||||
#define SUN_MAX_RATE (192000)
|
||||
#endif
|
||||
|
||||
static struct cubeb_ops const sun_ops;
|
||||
|
||||
struct cubeb {
|
||||
struct cubeb_ops const * ops;
|
||||
};
|
||||
|
||||
struct sun_stream {
|
||||
char name[32];
|
||||
int fd;
|
||||
void * buf;
|
||||
struct audio_info info;
|
||||
unsigned frame_size; /* precision in bytes * channels */
|
||||
bool floating;
|
||||
};
|
||||
|
||||
struct cubeb_stream {
|
||||
struct cubeb * context;
|
||||
void * user_ptr;
|
||||
pthread_t thread;
|
||||
pthread_mutex_t mutex; /* protects running, volume, frames_written */
|
||||
bool running;
|
||||
float volume;
|
||||
struct sun_stream play;
|
||||
struct sun_stream record;
|
||||
cubeb_data_callback data_cb;
|
||||
cubeb_state_callback state_cb;
|
||||
uint64_t frames_written;
|
||||
uint64_t blocks_written;
|
||||
};
|
||||
|
||||
int
|
||||
sun_init(cubeb ** context, char const * context_name)
|
||||
{
|
||||
cubeb * c;
|
||||
|
||||
(void)context_name;
|
||||
if ((c = calloc(1, sizeof(cubeb))) == NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
c->ops = &sun_ops;
|
||||
*context = c;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static void
|
||||
sun_destroy(cubeb * context)
|
||||
{
|
||||
free(context);
|
||||
}
|
||||
|
||||
static char const *
|
||||
sun_get_backend_id(cubeb * context)
|
||||
{
|
||||
return "sun";
|
||||
}
|
||||
|
||||
static int
|
||||
sun_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
|
||||
{
|
||||
(void)context;
|
||||
|
||||
*rate = SUN_PREFER_RATE;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_get_max_channel_count(cubeb * context, uint32_t * max_channels)
|
||||
{
|
||||
(void)context;
|
||||
|
||||
*max_channels = SUN_MAX_CHANNELS;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_get_min_latency(cubeb * context, cubeb_stream_params params,
|
||||
uint32_t * latency_frames)
|
||||
{
|
||||
(void)context;
|
||||
|
||||
*latency_frames = SUN_LATENCY_MS * params.rate / 1000;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_get_hwinfo(const char * device, struct audio_info * format, int * props,
|
||||
struct audio_device * dev)
|
||||
{
|
||||
int fd = -1;
|
||||
|
||||
if ((fd = open(device, O_RDONLY)) == -1) {
|
||||
goto error;
|
||||
}
|
||||
#ifdef AUDIO_GETFORMAT
|
||||
if (ioctl(fd, AUDIO_GETFORMAT, format) != 0) {
|
||||
goto error;
|
||||
}
|
||||
#endif
|
||||
#ifdef AUDIO_GETPROPS
|
||||
if (ioctl(fd, AUDIO_GETPROPS, props) != 0) {
|
||||
goto error;
|
||||
}
|
||||
#endif
|
||||
if (ioctl(fd, AUDIO_GETDEV, dev) != 0) {
|
||||
goto error;
|
||||
}
|
||||
close(fd);
|
||||
return CUBEB_OK;
|
||||
error:
|
||||
if (fd != -1) {
|
||||
close(fd);
|
||||
}
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* XXX: PR kern/54264
|
||||
*/
|
||||
static int
|
||||
sun_prinfo_verify_sanity(struct audio_prinfo * prinfo)
|
||||
{
|
||||
return prinfo->precision >= 8 && prinfo->precision <= 32 &&
|
||||
prinfo->channels >= 1 && prinfo->channels < SUN_MAX_CHANNELS &&
|
||||
prinfo->sample_rate < SUN_MAX_RATE &&
|
||||
prinfo->sample_rate > SUN_MIN_RATE;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_enumerate_devices(cubeb * context, cubeb_device_type type,
|
||||
cubeb_device_collection * collection)
|
||||
{
|
||||
unsigned i;
|
||||
cubeb_device_info device = {0};
|
||||
char dev[16] = SUN_DEFAULT_DEVICE;
|
||||
char dev_friendly[64];
|
||||
struct audio_info hwfmt;
|
||||
struct audio_device hwname;
|
||||
struct audio_prinfo * prinfo = NULL;
|
||||
int hwprops;
|
||||
|
||||
collection->device = calloc(SUN_DEVICE_COUNT, sizeof(cubeb_device_info));
|
||||
if (collection->device == NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
collection->count = 0;
|
||||
|
||||
for (i = 0; i < SUN_DEVICE_COUNT; ++i) {
|
||||
if (i > 0) {
|
||||
(void)snprintf(dev, sizeof(dev), "/dev/audio%u", i - 1);
|
||||
}
|
||||
if (sun_get_hwinfo(dev, &hwfmt, &hwprops, &hwname) != CUBEB_OK) {
|
||||
continue;
|
||||
}
|
||||
#ifdef AUDIO_GETPROPS
|
||||
device.type = 0;
|
||||
if ((hwprops & AUDIO_PROP_CAPTURE) != 0 &&
|
||||
sun_prinfo_verify_sanity(&hwfmt.record)) {
|
||||
/* the device supports recording, probably */
|
||||
device.type |= CUBEB_DEVICE_TYPE_INPUT;
|
||||
}
|
||||
if ((hwprops & AUDIO_PROP_PLAYBACK) != 0 &&
|
||||
sun_prinfo_verify_sanity(&hwfmt.play)) {
|
||||
/* the device supports playback, probably */
|
||||
device.type |= CUBEB_DEVICE_TYPE_OUTPUT;
|
||||
}
|
||||
switch (device.type) {
|
||||
case 0:
|
||||
/* device doesn't do input or output, aliens probably involved */
|
||||
continue;
|
||||
case CUBEB_DEVICE_TYPE_INPUT:
|
||||
if ((type & CUBEB_DEVICE_TYPE_INPUT) == 0) {
|
||||
/* this device is input only, not scanning for those, skip it */
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case CUBEB_DEVICE_TYPE_OUTPUT:
|
||||
if ((type & CUBEB_DEVICE_TYPE_OUTPUT) == 0) {
|
||||
/* this device is output only, not scanning for those, skip it */
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ((type & CUBEB_DEVICE_TYPE_INPUT) != 0) {
|
||||
prinfo = &hwfmt.record;
|
||||
}
|
||||
if ((type & CUBEB_DEVICE_TYPE_OUTPUT) != 0) {
|
||||
prinfo = &hwfmt.play;
|
||||
}
|
||||
#endif
|
||||
if (i > 0) {
|
||||
(void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (%d)",
|
||||
hwname.name, hwname.version, hwname.config, i - 1);
|
||||
} else {
|
||||
(void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (default)",
|
||||
hwname.name, hwname.version, hwname.config);
|
||||
}
|
||||
device.devid = (void *)(uintptr_t)i;
|
||||
device.device_id = strdup(dev);
|
||||
device.friendly_name = strdup(dev_friendly);
|
||||
device.group_id = strdup(dev);
|
||||
device.vendor_name = strdup(hwname.name);
|
||||
device.type = type;
|
||||
device.state = CUBEB_DEVICE_STATE_ENABLED;
|
||||
device.preferred =
|
||||
(i == 0) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
|
||||
#ifdef AUDIO_GETFORMAT
|
||||
device.max_channels = prinfo->channels;
|
||||
device.default_rate = prinfo->sample_rate;
|
||||
#else
|
||||
device.max_channels = 2;
|
||||
device.default_rate = SUN_PREFER_RATE;
|
||||
#endif
|
||||
device.default_format = CUBEB_DEVICE_FMT_S16NE;
|
||||
device.format = CUBEB_DEVICE_FMT_S16NE;
|
||||
device.min_rate = SUN_MIN_RATE;
|
||||
device.max_rate = SUN_MAX_RATE;
|
||||
device.latency_lo = SUN_LATENCY_MS * SUN_MIN_RATE / 1000;
|
||||
device.latency_hi = SUN_LATENCY_MS * SUN_MAX_RATE / 1000;
|
||||
collection->device[collection->count++] = device;
|
||||
}
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_device_collection_destroy(cubeb * context,
|
||||
cubeb_device_collection * collection)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < collection->count; ++i) {
|
||||
free((char *)collection->device[i].device_id);
|
||||
free((char *)collection->device[i].friendly_name);
|
||||
free((char *)collection->device[i].group_id);
|
||||
free((char *)collection->device[i].vendor_name);
|
||||
}
|
||||
free(collection->device);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
|
||||
struct audio_info * info, struct audio_prinfo * prinfo)
|
||||
{
|
||||
prinfo->channels = params->channels;
|
||||
prinfo->sample_rate = params->rate;
|
||||
#ifdef AUDIO_ENCODING_SLINEAR_LE
|
||||
switch (params->format) {
|
||||
case CUBEB_SAMPLE_S16LE:
|
||||
prinfo->encoding = AUDIO_ENCODING_SLINEAR_LE;
|
||||
prinfo->precision = 16;
|
||||
break;
|
||||
case CUBEB_SAMPLE_S16BE:
|
||||
prinfo->encoding = AUDIO_ENCODING_SLINEAR_BE;
|
||||
prinfo->precision = 16;
|
||||
break;
|
||||
case CUBEB_SAMPLE_FLOAT32NE:
|
||||
prinfo->encoding = AUDIO_ENCODING_SLINEAR;
|
||||
prinfo->precision = 32;
|
||||
break;
|
||||
default:
|
||||
LOG("Unsupported format");
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
#else
|
||||
switch (params->format) {
|
||||
case CUBEB_SAMPLE_S16NE:
|
||||
prinfo->encoding = AUDIO_ENCODING_LINEAR;
|
||||
prinfo->precision = 16;
|
||||
break;
|
||||
case CUBEB_SAMPLE_FLOAT32NE:
|
||||
prinfo->encoding = AUDIO_ENCODING_LINEAR;
|
||||
prinfo->precision = 32;
|
||||
break;
|
||||
default:
|
||||
LOG("Unsupported format");
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
#endif
|
||||
if (ioctl(fd, AUDIO_SETINFO, info) == -1) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
if (ioctl(fd, AUDIO_GETINFO, info) == -1) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_stop(cubeb_stream * s)
|
||||
{
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
if (s->running) {
|
||||
s->running = false;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
pthread_join(s->thread, NULL);
|
||||
} else {
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
}
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static void
|
||||
sun_stream_destroy(cubeb_stream * s)
|
||||
{
|
||||
sun_stream_stop(s);
|
||||
pthread_mutex_destroy(&s->mutex);
|
||||
if (s->play.fd != -1) {
|
||||
close(s->play.fd);
|
||||
}
|
||||
if (s->record.fd != -1) {
|
||||
close(s->record.fd);
|
||||
}
|
||||
free(s->play.buf);
|
||||
free(s->record.buf);
|
||||
free(s);
|
||||
}
|
||||
|
||||
static void
|
||||
sun_float_to_linear32(void * buf, unsigned sample_count, float vol)
|
||||
{
|
||||
float * in = buf;
|
||||
int32_t * out = buf;
|
||||
int32_t * tail = out + sample_count;
|
||||
|
||||
while (out < tail) {
|
||||
float f = *(in++) * vol;
|
||||
if (f < -1.0)
|
||||
f = -1.0;
|
||||
else if (f > 1.0)
|
||||
f = 1.0;
|
||||
*(out++) = f * (float)INT32_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sun_linear32_to_float(void * buf, unsigned sample_count)
|
||||
{
|
||||
int32_t * in = buf;
|
||||
float * out = buf;
|
||||
float * tail = out + sample_count;
|
||||
|
||||
while (out < tail) {
|
||||
*(out++) = (1.0 / 0x80000000) * *(in++);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sun_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
|
||||
{
|
||||
unsigned i;
|
||||
int32_t multiplier = vol * 0x8000;
|
||||
|
||||
for (i = 0; i < sample_count; ++i) {
|
||||
buf[i] = (buf[i] * multiplier) >> 15;
|
||||
}
|
||||
}
|
||||
|
||||
static void *
|
||||
sun_io_routine(void * arg)
|
||||
{
|
||||
cubeb_stream * s = arg;
|
||||
cubeb_state state = CUBEB_STATE_STARTED;
|
||||
size_t to_read = 0;
|
||||
long to_write = 0;
|
||||
size_t write_ofs = 0;
|
||||
size_t read_ofs = 0;
|
||||
int drain = 0;
|
||||
|
||||
s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED);
|
||||
while (state != CUBEB_STATE_ERROR) {
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
if (!s->running) {
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
state = CUBEB_STATE_STOPPED;
|
||||
break;
|
||||
}
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
if (s->record.fd != -1 && s->record.floating) {
|
||||
sun_linear32_to_float(s->record.buf,
|
||||
s->record.info.record.channels * SUN_BUFFER_FRAMES);
|
||||
}
|
||||
to_write = s->data_cb(s, s->user_ptr, s->record.buf, s->play.buf,
|
||||
SUN_BUFFER_FRAMES);
|
||||
if (to_write == CUBEB_ERROR) {
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
if (s->play.fd != -1) {
|
||||
float vol;
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
vol = s->volume;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
|
||||
if (s->play.floating) {
|
||||
sun_float_to_linear32(s->play.buf,
|
||||
s->play.info.play.channels * to_write, vol);
|
||||
} else {
|
||||
sun_linear16_set_vol(s->play.buf, s->play.info.play.channels * to_write,
|
||||
vol);
|
||||
}
|
||||
}
|
||||
if (to_write < SUN_BUFFER_FRAMES) {
|
||||
drain = 1;
|
||||
}
|
||||
to_write = s->play.fd != -1 ? to_write : 0;
|
||||
to_read = s->record.fd != -1 ? SUN_BUFFER_FRAMES : 0;
|
||||
write_ofs = 0;
|
||||
read_ofs = 0;
|
||||
while (to_write > 0 || to_read > 0) {
|
||||
size_t bytes;
|
||||
ssize_t n, frames;
|
||||
|
||||
if (to_write > 0) {
|
||||
bytes = to_write * s->play.frame_size;
|
||||
if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, bytes)) <
|
||||
0) {
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
frames = n / s->play.frame_size;
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
s->frames_written += frames;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
to_write -= frames;
|
||||
write_ofs += n;
|
||||
}
|
||||
if (to_read > 0) {
|
||||
bytes = to_read * s->record.frame_size;
|
||||
if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs,
|
||||
bytes)) < 0) {
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
frames = n / s->record.frame_size;
|
||||
to_read -= frames;
|
||||
read_ofs += n;
|
||||
}
|
||||
}
|
||||
if (drain && state != CUBEB_STATE_ERROR) {
|
||||
state = CUBEB_STATE_DRAINED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
s->state_cb(s, s->user_ptr, state);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_init(cubeb * context, cubeb_stream ** stream,
|
||||
char const * stream_name, cubeb_devid input_device,
|
||||
cubeb_stream_params * input_stream_params,
|
||||
cubeb_devid output_device,
|
||||
cubeb_stream_params * output_stream_params,
|
||||
unsigned latency_frames, cubeb_data_callback data_callback,
|
||||
cubeb_state_callback state_callback, void * user_ptr)
|
||||
{
|
||||
int ret = CUBEB_OK;
|
||||
cubeb_stream * s = NULL;
|
||||
|
||||
(void)stream_name;
|
||||
(void)latency_frames;
|
||||
if ((s = calloc(1, sizeof(cubeb_stream))) == NULL) {
|
||||
ret = CUBEB_ERROR;
|
||||
goto error;
|
||||
}
|
||||
s->record.fd = -1;
|
||||
s->play.fd = -1;
|
||||
if (input_device != 0) {
|
||||
snprintf(s->record.name, sizeof(s->record.name), "/dev/audio%zu",
|
||||
(uintptr_t)input_device - 1);
|
||||
} else {
|
||||
snprintf(s->record.name, sizeof(s->record.name), "%s", SUN_DEFAULT_DEVICE);
|
||||
}
|
||||
if (output_device != 0) {
|
||||
snprintf(s->play.name, sizeof(s->play.name), "/dev/audio%zu",
|
||||
(uintptr_t)output_device - 1);
|
||||
} else {
|
||||
snprintf(s->play.name, sizeof(s->play.name), "%s", SUN_DEFAULT_DEVICE);
|
||||
}
|
||||
if (input_stream_params != NULL) {
|
||||
if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
|
||||
LOG("Loopback not supported");
|
||||
ret = CUBEB_ERROR_NOT_SUPPORTED;
|
||||
goto error;
|
||||
}
|
||||
if (s->record.fd == -1) {
|
||||
if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) {
|
||||
LOG("Audio device could not be opened as read-only");
|
||||
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
AUDIO_INITINFO(&s->record.info);
|
||||
#ifdef AUMODE_RECORD
|
||||
s->record.info.mode = AUMODE_RECORD;
|
||||
#endif
|
||||
if ((ret = sun_copy_params(s->record.fd, s, input_stream_params,
|
||||
&s->record.info, &s->record.info.record)) !=
|
||||
CUBEB_OK) {
|
||||
LOG("Setting record params failed");
|
||||
goto error;
|
||||
}
|
||||
s->record.floating =
|
||||
(input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
|
||||
}
|
||||
if (output_stream_params != NULL) {
|
||||
if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
|
||||
LOG("Loopback not supported");
|
||||
ret = CUBEB_ERROR_NOT_SUPPORTED;
|
||||
goto error;
|
||||
}
|
||||
if (s->play.fd == -1) {
|
||||
if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
|
||||
LOG("Audio device could not be opened as write-only");
|
||||
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
AUDIO_INITINFO(&s->play.info);
|
||||
#ifdef AUMODE_PLAY
|
||||
s->play.info.mode = AUMODE_PLAY;
|
||||
#endif
|
||||
if ((ret = sun_copy_params(s->play.fd, s, output_stream_params,
|
||||
&s->play.info, &s->play.info.play)) !=
|
||||
CUBEB_OK) {
|
||||
LOG("Setting play params failed");
|
||||
goto error;
|
||||
}
|
||||
s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
|
||||
}
|
||||
s->context = context;
|
||||
s->volume = 1.0;
|
||||
s->state_cb = state_callback;
|
||||
s->data_cb = data_callback;
|
||||
s->user_ptr = user_ptr;
|
||||
if (pthread_mutex_init(&s->mutex, NULL) != 0) {
|
||||
LOG("Failed to create mutex");
|
||||
goto error;
|
||||
}
|
||||
s->play.frame_size =
|
||||
s->play.info.play.channels * (s->play.info.play.precision / 8);
|
||||
if (s->play.fd != -1 &&
|
||||
(s->play.buf = calloc(SUN_BUFFER_FRAMES, s->play.frame_size)) == NULL) {
|
||||
ret = CUBEB_ERROR;
|
||||
goto error;
|
||||
}
|
||||
s->record.frame_size =
|
||||
s->record.info.record.channels * (s->record.info.record.precision / 8);
|
||||
if (s->record.fd != -1 &&
|
||||
(s->record.buf = calloc(SUN_BUFFER_FRAMES, s->record.frame_size)) ==
|
||||
NULL) {
|
||||
ret = CUBEB_ERROR;
|
||||
goto error;
|
||||
}
|
||||
*stream = s;
|
||||
return CUBEB_OK;
|
||||
error:
|
||||
if (s != NULL) {
|
||||
sun_stream_destroy(s);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_start(cubeb_stream * s)
|
||||
{
|
||||
s->running = true;
|
||||
if (pthread_create(&s->thread, NULL, sun_io_routine, s) != 0) {
|
||||
LOG("Couldn't create thread");
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_get_position(cubeb_stream * s, uint64_t * position)
|
||||
{
|
||||
#ifdef AUDIO_GETOOFFS
|
||||
struct audio_offset offset;
|
||||
|
||||
if (ioctl(s->play.fd, AUDIO_GETOOFFS, &offset) == -1) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
s->blocks_written += offset.deltablks;
|
||||
*position = (s->blocks_written * s->play.info.blocksize) / s->play.frame_size;
|
||||
return CUBEB_OK;
|
||||
#else
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
*position = s->frames_written;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
return CUBEB_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_get_latency(cubeb_stream * s, uint32_t * latency)
|
||||
{
|
||||
#ifdef AUDIO_GETBUFINFO
|
||||
struct audio_info info;
|
||||
|
||||
if (ioctl(s->play.fd, AUDIO_GETBUFINFO, &info) == -1) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
*latency = (info.play.seek + info.blocksize) / s->play.frame_size;
|
||||
return CUBEB_OK;
|
||||
#else
|
||||
cubeb_stream_params params;
|
||||
|
||||
params.rate = s->play.info.play.sample_rate;
|
||||
|
||||
return sun_get_min_latency(NULL, params, latency);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_set_volume(cubeb_stream * stream, float volume)
|
||||
{
|
||||
pthread_mutex_lock(&stream->mutex);
|
||||
stream->volume = volume;
|
||||
pthread_mutex_unlock(&stream->mutex);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
|
||||
{
|
||||
*device = calloc(1, sizeof(cubeb_device));
|
||||
if (*device == NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
(*device)->input_name =
|
||||
stream->record.fd != -1 ? strdup(stream->record.name) : NULL;
|
||||
(*device)->output_name =
|
||||
stream->play.fd != -1 ? strdup(stream->play.name) : NULL;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
|
||||
{
|
||||
(void)stream;
|
||||
free(device->input_name);
|
||||
free(device->output_name);
|
||||
free(device);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static struct cubeb_ops const sun_ops = {
|
||||
.init = sun_init,
|
||||
.get_backend_id = sun_get_backend_id,
|
||||
.get_max_channel_count = sun_get_max_channel_count,
|
||||
.get_min_latency = sun_get_min_latency,
|
||||
.get_preferred_sample_rate = sun_get_preferred_sample_rate,
|
||||
.enumerate_devices = sun_enumerate_devices,
|
||||
.device_collection_destroy = sun_device_collection_destroy,
|
||||
.destroy = sun_destroy,
|
||||
.stream_init = sun_stream_init,
|
||||
.stream_destroy = sun_stream_destroy,
|
||||
.stream_start = sun_stream_start,
|
||||
.stream_stop = sun_stream_stop,
|
||||
.stream_get_position = sun_stream_get_position,
|
||||
.stream_get_latency = sun_stream_get_latency,
|
||||
.stream_get_input_latency = NULL,
|
||||
.stream_set_volume = sun_stream_set_volume,
|
||||
.stream_set_name = NULL,
|
||||
.stream_get_current_device = sun_get_current_device,
|
||||
.stream_device_destroy = sun_stream_device_destroy,
|
||||
.stream_register_device_changed_callback = NULL,
|
||||
.register_device_collection_changed = NULL};
|
||||
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright © 2022 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adapted and ported to C++ from https://crates.io/crates/triple_buffer
|
||||
*/
|
||||
|
||||
#ifndef CUBEB_TRIPLE_BUFFER
|
||||
#define CUBEB_TRIPLE_BUFFER
|
||||
|
||||
#include <atomic>
|
||||
|
||||
// Single producer / single consumer wait-free triple buffering
|
||||
// implementation, for when a producer wants to publish data to a consumer
|
||||
// without blocking, but when a queue is wastefull, because it's OK for the
|
||||
// consumer to miss data updates.
|
||||
template <typename T> class triple_buffer {
|
||||
public:
|
||||
// Write a new value into the triple buffer. Returns true if a value was
|
||||
// overwritten.
|
||||
// Producer-side only.
|
||||
bool write(T & input)
|
||||
{
|
||||
storage[input_idx] = input;
|
||||
return publish();
|
||||
}
|
||||
// Get the latest value from the triple buffer.
|
||||
// Consumer-side only.
|
||||
T & read()
|
||||
{
|
||||
update();
|
||||
return storage[output_idx];
|
||||
}
|
||||
// Returns true if a new value has been published by the consumer without
|
||||
// having been consumed yet.
|
||||
// Consumer-side only.
|
||||
bool updated()
|
||||
{
|
||||
return (shared_state.load(std::memory_order_relaxed) & BACK_DIRTY_BIT) != 0;
|
||||
}
|
||||
|
||||
private:
|
||||
// Publish a value to the consumer. Returns true if the data was overwritten
|
||||
// without having been read.
|
||||
bool publish()
|
||||
{
|
||||
auto former_back_idx = shared_state.exchange(input_idx | BACK_DIRTY_BIT,
|
||||
std::memory_order_acq_rel);
|
||||
input_idx = former_back_idx & BACK_INDEX_MASK;
|
||||
return (former_back_idx & BACK_DIRTY_BIT) != 0;
|
||||
}
|
||||
// Get a new value from the producer, if a new value has been produced.
|
||||
bool update()
|
||||
{
|
||||
bool was_updated = updated();
|
||||
if (was_updated) {
|
||||
auto former_back_idx =
|
||||
shared_state.exchange(output_idx, std::memory_order_acq_rel);
|
||||
output_idx = former_back_idx & BACK_INDEX_MASK;
|
||||
}
|
||||
return was_updated;
|
||||
}
|
||||
T storage[3];
|
||||
// Mask used to extract back-buffer index
|
||||
const uint8_t BACK_INDEX_MASK = 0b11;
|
||||
// Bit set by producer to signal updates
|
||||
const uint8_t BACK_DIRTY_BIT = 0b100;
|
||||
// Shared state: a dirty bit, and an index.
|
||||
std::atomic<uint8_t> shared_state = {0};
|
||||
// Output index, private to the consumer.
|
||||
uint8_t output_idx = 1;
|
||||
// Input index, private to the producer.
|
||||
uint8_t input_idx = 2;
|
||||
};
|
||||
|
||||
#endif // CUBEB_TRIPLE_BUFFER
|
||||
Loading…
Reference in New Issue