You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

159 lines
8.2 KiB

/* AAudio I/O без дополнительной PCM-очереди. Kotlin сериализует операции одного handle.
* write передаёт по аппаратному периоду: blocking pacing AAudio держит заданный запас,
* NON_BLOCKING способен заполнить всю ёмкость MMAP независимо от рабочего bufferSize. */
#include <aaudio/AAudio.h>
#include <jni.h>
#include <dlfcn.h>
#include <stdint.h>
#include <time.h>
#include "debug_config.h"
#include "mem.h"
#include "call/call_audio.h"
struct call_output {
AAudioStream* stream;
int burst;
int error;
};
static int64_t monotonic_ns(void) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
return (int64_t)now.tv_sec * 1000000000 + now.tv_nsec;
}
static void output_error(const char* operation, int result) {
DEBUG_WARN(DEBUG_CATEGORY_CALL, "AAudio %s result=%d reason=%s", operation, result, AAudio_convertResultToText(result));
}
JNIEXPORT jlong JNICALL Java_com_utun_chat_data_CallOutputNative_open(JNIEnv* env, jobject self, jint device) {
(void)env; (void)self;
/* Речевые атрибуты появились в API 28; libutun загружается также на API 26. */
void (*set_usage)(AAudioStreamBuilder*, aaudio_usage_t) = dlsym(RTLD_DEFAULT, "AAudioStreamBuilder_setUsage");
void (*set_content)(AAudioStreamBuilder*, aaudio_content_type_t) = dlsym(RTLD_DEFAULT, "AAudioStreamBuilder_setContentType");
if (!set_usage || !set_content) {
DEBUG_INFO(DEBUG_CATEGORY_CALL, "AAudio unavailable: voice attributes missing"); return 0;
}
AAudioStreamBuilder* builder = NULL;
int result = AAudio_createStreamBuilder(&builder);
if (result < 0) { output_error("builder", result); return 0; }
AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_OUTPUT);
AAudioStreamBuilder_setSampleRate(builder, 48000);
AAudioStreamBuilder_setChannelCount(builder, 1);
AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_I16);
AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
AAudioStreamBuilder_setSharingMode(builder, AAUDIO_SHARING_MODE_EXCLUSIVE);
if (device > 0) AAudioStreamBuilder_setDeviceId(builder, device);
set_usage(builder, AAUDIO_USAGE_VOICE_COMMUNICATION);
set_content(builder, AAUDIO_CONTENT_TYPE_SPEECH);
AAudioStream* stream = NULL;
result = AAudioStreamBuilder_openStream(builder, &stream);
int deleted = AAudioStreamBuilder_delete(builder);
if (deleted < 0) output_error("builder delete", deleted);
if (result < 0) { output_error("open", result); return 0; }
int burst = AAudioStream_getFramesPerBurst(stream);
int performance = AAudioStream_getPerformanceMode(stream);
bool (*is_mmap)(AAudioStream*) = dlsym(RTLD_DEFAULT, "AAudioStream_isMMapUsed");
int accepted = performance == AAUDIO_PERFORMANCE_MODE_LOW_LATENCY && burst > 0 && burst <= 480 &&
AAudioStream_getSampleRate(stream) == 48000 && AAudioStream_getChannelCount(stream) == 1 &&
AAudioStream_getFormat(stream) == AAUDIO_FORMAT_PCM_I16 &&
(device <= 0 || AAudioStream_getDeviceId(stream) == device);
DEBUG_INFO(DEBUG_CATEGORY_CALL, "AAudio candidate accepted=%d device=%d requested=%d rate=%d channels=%d "
"performance=%d sharing=%d mmap=%d burst=%d capacity=%d", accepted, AAudioStream_getDeviceId(stream), device,
AAudioStream_getSampleRate(stream), AAudioStream_getChannelCount(stream), performance,
AAudioStream_getSharingMode(stream), is_mmap ? is_mmap(stream) : -1, burst, AAudioStream_getBufferCapacityInFrames(stream));
if (accepted) {
result = AAudioStream_setBufferSizeInFrames(stream, 960);
if (result <= 0) { output_error("buffer", result); accepted = 0; }
}
if (accepted) {
result = AAudioStream_requestStart(stream);
if (result < 0) { output_error("start", result); accepted = 0; }
}
if (!accepted) {
result = AAudioStream_close(stream);
if (result < 0) output_error("close rejected", result);
return 0;
}
struct call_output* output = u_calloc(1, sizeof(*output));
if (!output) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "AAudio allocation failed");
result = AAudioStream_close(stream);
if (result < 0) output_error("close allocation failure", result);
return 0;
}
output->stream = stream; output->burst = burst;
return (jlong)(intptr_t)output;
}
JNIEXPORT jint JNICALL Java_com_utun_chat_data_CallOutputNative_write(
JNIEnv* env, jobject self, jlong handle, jshortArray input, jint offset, jint count) {
(void)self;
struct call_output* output = (struct call_output*)(intptr_t)handle;
if (!output || !input || offset < 0 || count <= 0 || count > 960 || offset > (*env)->GetArrayLength(env, input) - count) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "AAudio invalid write offset=%d count=%d", offset, count); return AAUDIO_ERROR_ILLEGAL_ARGUMENT;
}
if (output->error) return output->error;
int16_t pcm[960];
(*env)->GetShortArrayRegion(env, input, offset, count, pcm);
if ((*env)->ExceptionCheck(env)) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "AAudio JNI PCM copy failed"); return AAUDIO_ERROR_INTERNAL; }
int accepted = 0;
int64_t deadline = monotonic_ns() + 100000000;
while (accepted < count) {
int64_t timeout = deadline - monotonic_ns();
if (timeout <= 0) break;
int n = count - accepted < output->burst ? count - accepted : output->burst;
int result = AAudioStream_write(output->stream, pcm + accepted, n, timeout);
if (result < 0) { output_error("write", result); output->error = result; return accepted ? accepted : result; }
if (!result) break;
accepted += result;
}
return accepted;
}
JNIEXPORT jlong JNICALL Java_com_utun_chat_data_CallOutputNative_head(JNIEnv* env, jobject self, jlong handle) {
(void)env; (void)self;
struct call_output* output = (struct call_output*)(intptr_t)handle;
if (!output) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "AAudio head without output"); return -1; }
return AAudioStream_getFramesRead(output->stream);
}
/* head, timestamp frame/ns, underruns, buffer, capacity, device, performance, error, written. */
JNIEXPORT void JNICALL Java_com_utun_chat_data_CallOutputNative_sample(JNIEnv* env, jobject self, jlong handle, jlongArray out) {
(void)self;
struct call_output* output = (struct call_output*)(intptr_t)handle;
if (!output || !out || (*env)->GetArrayLength(env, out) != 10) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "AAudio invalid sample"); return;
}
AAudioStream* s = output->stream;
int64_t frame = -1, ns = -1;
int result = AAudioStream_getTimestamp(s, CLOCK_MONOTONIC, &frame, &ns);
if (result < 0) { frame = -1; ns = -1; }
jlong values[10] = {AAudioStream_getFramesRead(s), frame, ns, AAudioStream_getXRunCount(s),
AAudioStream_getBufferSizeInFrames(s), AAudioStream_getBufferCapacityInFrames(s), AAudioStream_getDeviceId(s),
AAudioStream_getPerformanceMode(s), output->error, AAudioStream_getFramesWritten(s)};
(*env)->SetLongArrayRegion(env, out, 0, 10, values);
}
JNIEXPORT jint JNICALL Java_com_utun_chat_data_CallOutputNative_buffer(JNIEnv* env, jobject self, jlong handle, jint frames) {
(void)env; (void)self;
struct call_output* output = (struct call_output*)(intptr_t)handle;
if (!output || frames < 960) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "AAudio invalid buffer=%d", frames); return -1; }
int result = AAudioStream_setBufferSizeInFrames(output->stream, frames);
if (result < 0) output_error("buffer change", result);
return result;
}
JNIEXPORT void JNICALL Java_com_utun_chat_data_CallOutputNative_close(JNIEnv* env, jobject self, jlong handle) {
(void)env; (void)self;
struct call_output* output = (struct call_output*)(intptr_t)handle;
if (!output) return;
int result = AAudioStream_close(output->stream);
if (result < 0) output_error("close", result);
u_free(output);
}
JNIEXPORT void JNICALL Java_com_utun_chat_data_CallOutputNative_suspendClock(JNIEnv* env, jobject self, jlong id, jlong epoch) {
(void)env; (void)self;
call_audio_suspend_device(id, epoch);
}