AAudio es una nueva API de Android C introducida en Android O. Está diseñado principalmente para aplicaciones de audio de alto rendimiento que requieren baja latencia. Las aplicaciones se comunican con AAudio leyendo o escribiendo datos directamente en la transmisión, pero solo contiene capacidades básicas de entrada y salida de audio. El documento oficial de Android AAudio ofrece una buena introducción a la API de AAudio y las ideas de diseño. Aquí se presenta un vistazo a la implementación de AAudio. El siguiente análisis de código se basa en android-12.1.0_r27.
AAudio mueve datos de audio entre aplicaciones y entrada y salida de audio en el dispositivo Android. La aplicación pasa la transmisión de audio y lee datos de la transmisión de audio para lograr esta transferencia de datos de audio. La implementación de AAudio gira en torno a la transferencia de datos de audio y la transmisión de audio.
Hay dos modos de trabajo principales para la transferencia de datos de AAudio. Uno es el modo MMap. En este momento, la biblioteca de AAudio del cliente media.aaudio
obtiene una memoria compartida del servicio y transmite datos al dispositivo a través de esta memoria compartida; el otro es el llamado modo tradicional.En este momento, la interfaz del cliente AAudio se basa en la implementación tradicional android::AudioTrack
, utilizando los mismos componentes que la implementación android::AudioRecord
de Java . Lo que realmente puede lograr las ventajas de rendimiento y latencia descritas en la documentación de AAudio es el modo de trabajo de la memoria compartida basada en mmap y la transferencia de datos del dispositivo.AudioTrack
AudioRecord
La implementación de la biblioteca AAudio incluye principalmente los siguientes módulos, y su estructura de directorio de código fuente también está organizada según módulos:
- Interfaces API, que implementan directamente la interfaz AAudio C llamada en la aplicación, y el código relevante se encuentra en
libaaudio/src/core
el directorio; - Legacy, una secuencia de audio implementada basada en la interfaz tradicional
android::AudioTrack
, el código relevante se encuentra en el directorio;android::AudioRecord
libaaudio/src/legacy
- El enlace, el enlace del cliente, se utiliza principalmente para
media.aaudio
comunicarse con servicios, comomedia.aaudio
enviar solicitudes o recibirmedia.aaudio
notificaciones de eventos de los servicios.El código relevante se encuentra enlibaaudio/src/binding
el directorio; - fifo se utiliza principalmente para
media.aaudio
la transferencia de datos con servicios o dispositivos. AAudio obtiene el fd vinculado a la memoria compartida a través del módulo de enlace. Este fd necesita ser mapeado nuevamente en el proceso actual. Las clases relacionadas del módulo fifo se utilizan para compartir el memoria a través de estos mmapsmedia.aaudio
Para la transferencia de datos servicios o dispositivos, el código correspondiente se encuentra enlibaaudio/src/fifo
el directorio; - Diagrama de flujo, cuando se usa AAudio para reproducir datos, se realizará un procesamiento simple en los datos, como la conversión del número de canal, etc. El módulo de diagrama de flujo se usa para completar estos procesos y el código relevante se encuentra en el directorio
libaaudio/src/flowgraph
; - cliente, una secuencia de audio implementada en base a módulos como vinculación, fifo y diagrama de flujo. El código relevante se encuentra en
libaaudio/src/client
el directorio; - La utilidad, algunos programas de utilidad y los códigos relacionados se encuentran en
libaaudio/src/utility
el directorio.
Desde la perspectiva del módulo, la estructura de implementación de la biblioteca AAudio se muestra a continuación:
El análisis de la implementación de Android AAudio aquí se centra en la transmisión de audio basada en la memoria compartida mmap para transferir datos de audio.
Abrir secuencia de audio
Los usuarios de la interfaz AAudio abren una transmisión de audio AAudioStreamBuilder_openStream()
según la configuración de la transmisión de audio en la interfaz . AAudioStreamBuilder
La definición de esta función (ubicada en frameworks/av/media/libaaudio/src/flowgraph/AudioProcessorBase.h ) es la siguiente:
AAUDIO_API aaudio_result_t AAudioStreamBuilder_openStream(AAudioStreamBuilder* builder,
AAudioStream** streamPtr)
{
AudioStream *audioStream = nullptr;
aaudio_stream_id_t id = 0;
// Please leave these logs because they are very helpful when debugging.
ALOGI("%s() called ----------------------------------------", __func__);
AudioStreamBuilder *streamBuilder = COMMON_GET_FROM_BUILDER_OR_RETURN(streamPtr);
aaudio_result_t result = streamBuilder->build(&audioStream);
if (result == AAUDIO_OK) {
*streamPtr = (AAudioStream*) audioStream;
id = audioStream->getId();
} else {
*streamPtr = nullptr;
}
ALOGI("%s() returns %d = %s for s#%u ----------------",
__func__, result, AAudio_convertResultToText(result), id);
return result;
}
AAudio en realidad AudioStreamBuilder::build()
abre la transmisión de audio, lo que crea e inicializa el objeto de transmisión de audio AudioStream
y lo devuelve a la persona que llama. Por cierto, y que se ve en el archivo de encabezado de la interfaz AAudio aaudio/AAudio.h no son estructuras reales. Estas dos estructuras probablemente no sean diferentes de y las estructuras reales son y .struct AAudioStreamStruct
struct AAudioStreamBuilderStruct
void
aaudio::AudioStream
aaudio::AudioStreamBuilder
AudioStreamBuilder::build(AudioStream** streamPtr)
La definición de la función (ubicada en frameworks/av/media/libaaudio/src/core/AudioStreamBuilder.cpp ) es la siguiente:
static aaudio_result_t builder_createStream(aaudio_direction_t direction,
aaudio_sharing_mode_t sharingMode,
bool tryMMap,
android::sp<AudioStream> &stream) {
aaudio_result_t result = AAUDIO_OK;
switch (direction) {
case AAUDIO_DIRECTION_INPUT:
if (tryMMap) {
stream = new AudioStreamInternalCapture(AAudioBinderClient::getInstance(),
false);
} else {
stream = new AudioStreamRecord();
}
break;
case AAUDIO_DIRECTION_OUTPUT:
if (tryMMap) {
stream = new AudioStreamInternalPlay(AAudioBinderClient::getInstance(),
false);
} else {
stream = new AudioStreamTrack();
}
break;
default:
ALOGE("%s() bad direction = %d", __func__, direction);
result = AAUDIO_ERROR_ILLEGAL_ARGUMENT;
}
return result;
}
// Try to open using MMAP path if that is allowed.
// Fall back to Legacy path if MMAP not available.
// Exact behavior is controlled by MMapPolicy.
aaudio_result_t AudioStreamBuilder::build(AudioStream** streamPtr) {
if (streamPtr == nullptr) {
ALOGE("%s() streamPtr is null", __func__);
return AAUDIO_ERROR_NULL;
}
*streamPtr = nullptr;
logParameters();
aaudio_result_t result = validate();
if (result != AAUDIO_OK) {
return result;
}
// The API setting is the highest priority.
aaudio_policy_t mmapPolicy = AudioGlobal_getMMapPolicy();
// If not specified then get from a system property.
if (mmapPolicy == AAUDIO_UNSPECIFIED) {
mmapPolicy = AAudioProperty_getMMapPolicy();
}
// If still not specified then use the default.
if (mmapPolicy == AAUDIO_UNSPECIFIED) {
mmapPolicy = AAUDIO_MMAP_POLICY_DEFAULT;
}
int32_t mapExclusivePolicy = AAudioProperty_getMMapExclusivePolicy();
if (mapExclusivePolicy == AAUDIO_UNSPECIFIED) {
mapExclusivePolicy = AAUDIO_MMAP_EXCLUSIVE_POLICY_DEFAULT;
}
aaudio_sharing_mode_t sharingMode = getSharingMode();
if ((sharingMode == AAUDIO_SHARING_MODE_EXCLUSIVE)
&& (mapExclusivePolicy == AAUDIO_POLICY_NEVER)) {
ALOGD("%s() EXCLUSIVE sharing mode not supported. Use SHARED.", __func__);
sharingMode = AAUDIO_SHARING_MODE_SHARED;
setSharingMode(sharingMode);
}
bool allowMMap = mmapPolicy != AAUDIO_POLICY_NEVER;
bool allowLegacy = mmapPolicy != AAUDIO_POLICY_ALWAYS;
// TODO Support other performance settings in MMAP mode.
// Disable MMAP if low latency not requested.
if (getPerformanceMode() != AAUDIO_PERFORMANCE_MODE_LOW_LATENCY) {
ALOGD("%s() MMAP not used because AAUDIO_PERFORMANCE_MODE_LOW_LATENCY not requested.",
__func__);
allowMMap = false;
}
// SessionID and Effects are only supported in Legacy mode.
if (getSessionId() != AAUDIO_SESSION_ID_NONE) {
ALOGD("%s() MMAP not used because sessionId specified.", __func__);
allowMMap = false;
}
if (!allowMMap && !allowLegacy) {
ALOGE("%s() no backend available: neither MMAP nor legacy path are allowed", __func__);
return AAUDIO_ERROR_ILLEGAL_ARGUMENT;
}
setPrivacySensitive(false);
if (mPrivacySensitiveReq == PRIVACY_SENSITIVE_DEFAULT) {
// When not explicitly requested, set privacy sensitive mode according to input preset:
// communication and camcorder captures are considered privacy sensitive by default.
aaudio_input_preset_t preset = getInputPreset();
if (preset == AAUDIO_INPUT_PRESET_CAMCORDER
|| preset == AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION) {
setPrivacySensitive(true);
}
} else if (mPrivacySensitiveReq == PRIVACY_SENSITIVE_ENABLED) {
setPrivacySensitive(true);
}
android::sp<AudioStream> audioStream;
result = builder_createStream(getDirection(), sharingMode, allowMMap, audioStream);
if (result == AAUDIO_OK) {
// Open the stream using the parameters from the builder.
result = audioStream->open(*this);
if (result != AAUDIO_OK) {
bool isMMap = audioStream->isMMap();
if (isMMap && allowLegacy) {
ALOGV("%s() MMAP stream did not open so try Legacy path", __func__);
// If MMAP stream failed to open then TRY using a legacy stream.
result = builder_createStream(getDirection(), sharingMode,
false, audioStream);
if (result == AAUDIO_OK) {
result = audioStream->open(*this);
}
}
}
if (result == AAUDIO_OK) {
audioStream->registerPlayerBase();
audioStream->logOpenActual();
*streamPtr = startUsingStream(audioStream);
} // else audioStream will go out of scope and be deleted
}
return result;
}
La política MMap es uno de los parámetros más importantes al crear y abrir una transmisión de audio. AudioStreamBuilder::build()
El proceso principal para abrir una transmisión de audio es el siguiente:
- Para obtener la política MMap, primero obtengala de la configuración de API, si no está configurada, obtengala de las propiedades del sistema;
- Obtenga la política exclusiva de MMap de las propiedades del sistema;
- Corrija el modo de compartir de acuerdo con la política exclusiva de MMap y el modo de compartir establecido;
- Compruebe el modo de rendimiento y el ID de sesión. El ID de sesión y los efectos de sonido solo se admiten en el modo tradicional. Si el modo de rendimiento no es el modo de baja latencia, utilice el modo tradicional;
- Cree una transmisión de audio a través de
builder_createStream()
la función, que creará diferentes tipos de objetos de transmisión de audio según el modo MMap y los tipos de entrada y salida; - Ejecute
open()
la operación del objeto de transmisión de audio y abra la transmisión de audio; - Si falla una solicitud para abrir una transmisión de audio en modo MMap, se intenta crear y abrir una transmisión de audio en modo heredado.
La implementación de la interfaz de AAudio es relativamente robusta: cuando el modo de baja latencia no puede funcionar correctamente por diversas razones, volverá al modo tradicional.
La estructura jerárquica de los objetos de flujo de audio en AAudio es la siguiente:
No es difícil ver que AudioStreamInternalPlay
y AudioStreamInternalCapture
son, respectivamente, los tipos de flujo de audio de reproducción y de flujo de audio de colección en el modo MMap, AudioStreamTrack
y AudioStreamRecord
son respectivamente los tipos de flujo de audio de reproducción y de flujo de audio de colección en el modo tradicional.
AAudio proporciona interfaces para configurar y obtener políticas MMap. Las definiciones de estas interfaces (ubicadas en frameworks/av/media/libaaudio/src/core/AudioGlobal.cpp ) son las siguientes:
static aaudio_policy_t g_MMapPolicy = AAUDIO_UNSPECIFIED;
aaudio_policy_t AudioGlobal_getMMapPolicy() {
return g_MMapPolicy;
}
aaudio_result_t AudioGlobal_setMMapPolicy(aaudio_policy_t policy) {
aaudio_result_t result = AAUDIO_OK;
switch(policy) {
case AAUDIO_UNSPECIFIED:
case AAUDIO_POLICY_NEVER:
case AAUDIO_POLICY_AUTO:
case AAUDIO_POLICY_ALWAYS:
g_MMapPolicy = policy;
break;
default:
result = AAUDIO_ERROR_ILLEGAL_ARGUMENT;
break;
}
return result;
}
La propiedad del sistema que define la política MMap es aaudio.mmap_policy
y la propiedad del sistema que define la política exclusiva de MMap es aaudio.mmap_exclusive_policy
. Puede ver esto en el archivo frameworks/av/media/libaaudio/src/utility/AAudioUtilities.h :
int32_t AAudioProperty_getMMapPolicy();
#define AAUDIO_PROP_MMAP_POLICY "aaudio.mmap_policy"
/**
* Read system property.
* @return AAUDIO_UNSPECIFIED, AAUDIO_POLICY_NEVER or AAUDIO_POLICY_AUTO or AAUDIO_POLICY_ALWAYS
*/
int32_t AAudioProperty_getMMapExclusivePolicy();
#define AAUDIO_PROP_MMAP_EXCLUSIVE_POLICY "aaudio.mmap_exclusive_policy"
El método para obtener las propiedades del sistema (ubicado en frameworks/av/media/libaaudio/src/utility/AAudioUtilities.cpp ) es el siguiente:
static int32_t AAudioProperty_getMMapProperty(const char *propName,
int32_t defaultValue,
const char * caller) {
int32_t prop = property_get_int32(propName, defaultValue);
switch (prop) {
case AAUDIO_UNSPECIFIED:
case AAUDIO_POLICY_NEVER:
case AAUDIO_POLICY_ALWAYS:
case AAUDIO_POLICY_AUTO:
break;
default:
ALOGE("%s: invalid = %d", caller, prop);
prop = defaultValue;
break;
}
return prop;
}
int32_t AAudioProperty_getMMapPolicy() {
return AAudioProperty_getMMapProperty(AAUDIO_PROP_MMAP_POLICY,
AAUDIO_UNSPECIFIED, __func__);
}
int32_t AAudioProperty_getMMapExclusivePolicy() {
return AAudioProperty_getMMapProperty(AAUDIO_PROP_MMAP_EXCLUSIVE_POLICY,
AAUDIO_UNSPECIFIED, __func__);
}
Esta propiedad del sistema se puede definir en el archivo mk. Por ejemplo, para permitir que la versión AAOS del simulador inicie el media.aaudio
servicio, debe device/generic/car/emulator/audio/car_emulator_audio.mk
agregar las siguientes líneas al archivo:
PRODUCT_PROPERTY_OVERRIDES += aaudio.mmap_policy=2
Aunque en la implementación de la biblioteca AAudio, la política MMap establecida por la API tiene la máxima prioridad, esta configuración en el proceso del cliente no puede afectar media.aaudio
si se inicia el servicio, pero sí aaudio.mmap_policy
la configuración de las propiedades del sistema.
Los objetos de diferentes tipos de flujo de audio open()
tienen diferentes operaciones. A continuación se muestra AudioStreamInternalPlay::open()
la definición de la función (ubicada en frameworks/av/media/libaaudio/src/client/AudioStreamInternalPlay.cpp ):
aaudio_result_t AudioStreamInternalPlay::open(const AudioStreamBuilder &builder) {
aaudio_result_t result = AudioStreamInternal::open(builder);
if (result == AAUDIO_OK) {
result = mFlowGraph.configure(getFormat(),
getSamplesPerFrame(),
getDeviceFormat(),
getDeviceChannelCount());
if (result != AAUDIO_OK) {
safeReleaseClose();
}
// Sample rate is constrained to common values by now and should not overflow.
int32_t numFrames = kRampMSec * getSampleRate() / AAUDIO_MILLIS_PER_SECOND;
mFlowGraph.setRampLengthInFrames(numFrames);
}
return result;
}
AudioStreamInternalPlay::open()
Ejecute AudioStreamInternal::open(builder)
y configure la canalización de procesamiento de datos para procesar datos de reproducción de audio AAudioFlowGraph
.
AudioStreamInternal::open(builder)
La definición de la función (ubicada en frameworks/av/media/libaaudio/src/client/AudioStreamInternal.cpp ) es la siguiente:
aaudio_result_t AudioStreamInternal::open(const AudioStreamBuilder &builder) {
aaudio_result_t result = AAUDIO_OK;
int32_t framesPerBurst;
int32_t framesPerHardwareBurst;
AAudioStreamRequest request;
AAudioStreamConfiguration configurationOutput;
if (getState() != AAUDIO_STREAM_STATE_UNINITIALIZED) {
ALOGE("%s - already open! state = %d", __func__, getState());
return AAUDIO_ERROR_INVALID_STATE;
}
// Copy requested parameters to the stream.
result = AudioStream::open(builder);
if (result < 0) {
return result;
}
const int32_t burstMinMicros = AAudioProperty_getHardwareBurstMinMicros();
int32_t burstMicros = 0;
const audio_format_t requestedFormat = getFormat();
// We have to do volume scaling. So we prefer FLOAT format.
if (requestedFormat == AUDIO_FORMAT_DEFAULT) {
setFormat(AUDIO_FORMAT_PCM_FLOAT);
}
// Request FLOAT for the shared mixer or the device.
request.getConfiguration().setFormat(AUDIO_FORMAT_PCM_FLOAT);
// TODO b/182392769: use attribution source util
AttributionSourceState attributionSource;
attributionSource.uid = VALUE_OR_FATAL(android::legacy2aidl_uid_t_int32_t(getuid()));
attributionSource.pid = VALUE_OR_FATAL(android::legacy2aidl_pid_t_int32_t(getpid()));
attributionSource.packageName = builder.getOpPackageName();
attributionSource.attributionTag = builder.getAttributionTag();
attributionSource.token = sp<android::BBinder>::make();
// Build the request to send to the server.
request.setAttributionSource(attributionSource);
request.setSharingModeMatchRequired(isSharingModeMatchRequired());
request.setInService(isInService());
request.getConfiguration().setDeviceId(getDeviceId());
request.getConfiguration().setSampleRate(getSampleRate());
request.getConfiguration().setDirection(getDirection());
request.getConfiguration().setSharingMode(getSharingMode());
request.getConfiguration().setChannelMask(getChannelMask());
request.getConfiguration().setUsage(getUsage());
request.getConfiguration().setContentType(getContentType());
request.getConfiguration().setSpatializationBehavior(getSpatializationBehavior());
request.getConfiguration().setIsContentSpatialized(isContentSpatialized());
request.getConfiguration().setInputPreset(getInputPreset());
request.getConfiguration().setPrivacySensitive(isPrivacySensitive());
request.getConfiguration().setBufferCapacity(builder.getBufferCapacity());
mDeviceChannelCount = getSamplesPerFrame(); // Assume it will be the same. Update if not.
mServiceStreamHandle = mServiceInterface.openStream(request, configurationOutput);
if (mServiceStreamHandle < 0
&& (request.getConfiguration().getSamplesPerFrame() == 1
|| request.getConfiguration().getChannelMask() == AAUDIO_CHANNEL_MONO)
&& getDirection() == AAUDIO_DIRECTION_OUTPUT
&& !isInService()) {
// if that failed then try switching from mono to stereo if OUTPUT.
// Only do this in the client. Otherwise we end up with a mono mixer in the service
// that writes to a stereo MMAP stream.
ALOGD("%s() - openStream() returned %d, try switching from MONO to STEREO",
__func__, mServiceStreamHandle);
request.getConfiguration().setChannelMask(AAUDIO_CHANNEL_STEREO);
mServiceStreamHandle = mServiceInterface.openStream(request, configurationOutput);
}
if (mServiceStreamHandle < 0) {
return mServiceStreamHandle;
}
// This must match the key generated in oboeservice/AAudioServiceStreamBase.cpp
// so the client can have permission to log.
if (!mInService) {
// No need to log if it is from service side.
mMetricsId = std::string(AMEDIAMETRICS_KEY_PREFIX_AUDIO_STREAM)
+ std::to_string(mServiceStreamHandle);
}
android::mediametrics::LogItem(mMetricsId)
.set(AMEDIAMETRICS_PROP_PERFORMANCEMODE,
AudioGlobal_convertPerformanceModeToText(builder.getPerformanceMode()))
.set(AMEDIAMETRICS_PROP_SHARINGMODE,
AudioGlobal_convertSharingModeToText(builder.getSharingMode()))
.set(AMEDIAMETRICS_PROP_ENCODINGCLIENT,
android::toString(requestedFormat).c_str()).record();
result = configurationOutput.validate();
if (result != AAUDIO_OK) {
goto error;
}
// Save results of the open.
if (getChannelMask() == AAUDIO_UNSPECIFIED) {
setChannelMask(configurationOutput.getChannelMask());
}
mDeviceChannelCount = configurationOutput.getSamplesPerFrame();
setSampleRate(configurationOutput.getSampleRate());
setDeviceId(configurationOutput.getDeviceId());
setSessionId(configurationOutput.getSessionId());
setSharingMode(configurationOutput.getSharingMode());
setUsage(configurationOutput.getUsage());
setContentType(configurationOutput.getContentType());
setSpatializationBehavior(configurationOutput.getSpatializationBehavior());
setIsContentSpatialized(configurationOutput.isContentSpatialized());
setInputPreset(configurationOutput.getInputPreset());
// Save device format so we can do format conversion and volume scaling together.
setDeviceFormat(configurationOutput.getFormat());
result = mServiceInterface.getStreamDescription(mServiceStreamHandle, mEndPointParcelable);
if (result != AAUDIO_OK) {
goto error;
}
// Resolve parcelable into a descriptor.
result = mEndPointParcelable.resolve(&mEndpointDescriptor);
if (result != AAUDIO_OK) {
goto error;
}
// Configure endpoint based on descriptor.
mAudioEndpoint = std::make_unique<AudioEndpoint>();
result = mAudioEndpoint->configure(&mEndpointDescriptor, getDirection());
if (result != AAUDIO_OK) {
goto error;
}
framesPerHardwareBurst = mEndpointDescriptor.dataQueueDescriptor.framesPerBurst;
// Scale up the burst size to meet the minimum equivalent in microseconds.
// This is to avoid waking the CPU too often when the HW burst is very small
// or at high sample rates.
framesPerBurst = framesPerHardwareBurst;
do {
if (burstMicros > 0) { // skip first loop
framesPerBurst *= 2;
}
burstMicros = framesPerBurst * static_cast<int64_t>(1000000) / getSampleRate();
} while (burstMicros < burstMinMicros);
ALOGD("%s() original HW burst = %d, minMicros = %d => SW burst = %d\n",
__func__, framesPerHardwareBurst, burstMinMicros, framesPerBurst);
// Validate final burst size.
if (framesPerBurst < MIN_FRAMES_PER_BURST || framesPerBurst > MAX_FRAMES_PER_BURST) {
ALOGE("%s - framesPerBurst out of range = %d", __func__, framesPerBurst);
result = AAUDIO_ERROR_OUT_OF_RANGE;
goto error;
}
setFramesPerBurst(framesPerBurst); // only save good value
mBufferCapacityInFrames = mEndpointDescriptor.dataQueueDescriptor.capacityInFrames;
if (mBufferCapacityInFrames < getFramesPerBurst()
|| mBufferCapacityInFrames > MAX_BUFFER_CAPACITY_IN_FRAMES) {
ALOGE("%s - bufferCapacity out of range = %d", __func__, mBufferCapacityInFrames);
result = AAUDIO_ERROR_OUT_OF_RANGE;
goto error;
}
mClockModel.setSampleRate(getSampleRate());
mClockModel.setFramesPerBurst(framesPerHardwareBurst);
if (isDataCallbackSet()) {
mCallbackFrames = builder.getFramesPerDataCallback();
if (mCallbackFrames > getBufferCapacity() / 2) {
ALOGW("%s - framesPerCallback too big = %d, capacity = %d",
__func__, mCallbackFrames, getBufferCapacity());
result = AAUDIO_ERROR_OUT_OF_RANGE;
goto error;
} else if (mCallbackFrames < 0) {
ALOGW("%s - framesPerCallback negative", __func__);
result = AAUDIO_ERROR_OUT_OF_RANGE;
goto error;
}
if (mCallbackFrames == AAUDIO_UNSPECIFIED) {
mCallbackFrames = getFramesPerBurst();
}
const int32_t callbackBufferSize = mCallbackFrames * getBytesPerFrame();
mCallbackBuffer = std::make_unique<uint8_t[]>(callbackBufferSize);
}
// For debugging and analyzing the distribution of MMAP timestamps.
// For OUTPUT, use a NEGATIVE offset to move the CPU writes further BEFORE the HW reads.
// For INPUT, use a POSITIVE offset to move the CPU reads further AFTER the HW writes.
// You can use this offset to reduce glitching.
// You can also use this offset to force glitching. By iterating over multiple
// values you can reveal the distribution of the hardware timing jitter.
if (mAudioEndpoint->isFreeRunning()) { // MMAP?
int32_t offsetMicros = (getDirection() == AAUDIO_DIRECTION_OUTPUT)
? AAudioProperty_getOutputMMapOffsetMicros()
: AAudioProperty_getInputMMapOffsetMicros();
// This log is used to debug some tricky glitch issues. Please leave.
ALOGD_IF(offsetMicros, "%s() - %s mmap offset = %d micros",
__func__,
(getDirection() == AAUDIO_DIRECTION_OUTPUT) ? "output" : "input",
offsetMicros);
mTimeOffsetNanos = offsetMicros * AAUDIO_NANOS_PER_MICROSECOND;
}
setBufferSize(mBufferCapacityInFrames / 2); // Default buffer size to match Q
setState(AAUDIO_STREAM_STATE_OPEN);
return result;
error:
safeReleaseClose();
return result;
}
AudioStreamInternal::open(builder)
El proceso de ejecución de la función es aproximadamente el siguiente:
- La
AudioStream::open(builder)
función de ejecución copia principalmenteAudioStreamBuilder
la configuración de la transmisión de audio realizada por el usuario, como la frecuencia de muestreo, el número de canal, el formato de datos, el modo de compartir y el modo de rendimiento, etc. Para algunos elementos de configuración que el usuario no ha configurado, se establecerán los valores predeterminados apropiados. ser establecido; - Construya el objeto de solicitud de flujo AAudio, es decir,
AAudioStreamRequest
objeto; - Solicite al servicio que abra la transmisión de audio a través de la interfaz del servicio AAudio
media.aaudio
. El valor de retorno de la operación de abrir la transmisión de audio a través de la interfaz del servicio AAudio incluye dos partes. Una es el valor de retorno de la operación, que es elmedia.aaudio
identificador del servidor -Un lado del flujo de audio se abre para el servicio y el otro se utiliza como parámetro de salida.ElAAudioStreamConfiguration
objeto, que contiene principalmente la información de configuración real del flujo de audio; - Al solicitar al servicio que abra el stream a través de la interfaz del servicio AAudio
media.aaudio
falla, si el número de canales de stream solicitados es mono, intentará abrirlo nuevamente con una configuración de stream de audio estéreo de dos canales; - Establezca el estado de la transmisión de audio actual de acuerdo con la información de configuración de la transmisión de audio real devuelta al abrir la transmisión de audio;
- Obtenga la descripción de la transmisión de audio a través de la interfaz del servicio AAudio, pase el identificador de la transmisión de audio del servidor y obtenga la
AudioEndpointParcelable
descripción de la transmisión de audio de un tipo de objeto. La información relacionada con la memoria compartida requerida para que el cliente AAudio se comunique con el servicio no es pasa a través del objetomedia.aaudio
al abrir la secuencia, se devuelve aquí a través del objeto;AAudioStreamConfiguration
AudioEndpointParcelable
AudioEndpointParcelable
La información de la memoria compartida relacionada con la comunicación del objeto se convierte enEndpointDescriptor
una descripción en el formulario;- Cree y configure
AudioEndpoint
el objeto, que se utiliza principalmente paramedia.aaudio
intercambiar comandos y datos de audio con el servicio; - De acuerdo con la configuración de las propiedades del sistema, es decir,
aaudio.hw_burst_min_usec
la duración mínima de cada transmisión de datos, corrija la configuración de la cantidad mínima de datos de cada transmisión de datos; - Verifique la capacidad de cuadros de audio del búfer;
- Establecer el modelo de reloj;
- Devolución de llamada de datos de configuración;
- Establezca el tamaño del búfer y establezca el estado de la transmisión de audio para abrir.
Como se puede ver en AudioStreamInternal::open(builder)
la función, la implementación del flujo de audio de la comunicación de memoria compartida basada en mmap con dispositivos y media.aaudio
servicios tiene una estructura de nivel de clase como se muestra a continuación:
Aunque el directorio de código relacionado con la implementación de la interfaz API se denomina core
, en la biblioteca AAudio, el flujo de audio es sin duda el centro de toda la implementación.
vinculante
El módulo de enlace se utiliza principalmente para media.aaudio
comunicarse con servicios. AAudio define la interfaz del servicio AAudio AAudioServiceInterface
. AudioStreamInternal::open(builder)
Al abrir la transmisión de audio, solicite media.aaudio
al servicio que abra la transmisión de audio a través de esta interfaz y obtenga la descripción de la transmisión. La definición de esta interfaz (ubicada en frameworks/av/media/libaaudio/src/binding/ AAudioServiceInterface.h ) es el siguiente:
class AAudioServiceInterface {
public:
AAudioServiceInterface() {};
virtual ~AAudioServiceInterface() = default;
virtual void registerClient(const android::sp<IAAudioClient>& client) = 0;
/**
* @param request info needed to create the stream
* @param configuration contains information about the created stream
* @return handle to the stream or a negative error
*/
virtual aaudio_handle_t openStream(const AAudioStreamRequest &request,
AAudioStreamConfiguration &configuration) = 0;
virtual aaudio_result_t closeStream(aaudio_handle_t streamHandle) = 0;
/* Get an immutable description of the in-memory queues
* used to communicate with the underlying HAL or Service.
*/
virtual aaudio_result_t getStreamDescription(aaudio_handle_t streamHandle,
AudioEndpointParcelable &parcelable) = 0;
/**
* Start the flow of data.
*/
virtual aaudio_result_t startStream(aaudio_handle_t streamHandle) = 0;
/**
* Stop the flow of data such that start() can resume without loss of data.
*/
virtual aaudio_result_t pauseStream(aaudio_handle_t streamHandle) = 0;
/**
* Stop the flow of data after data currently inthe buffer has played.
*/
virtual aaudio_result_t stopStream(aaudio_handle_t streamHandle) = 0;
/**
* Discard any data held by the underlying HAL or Service.
*/
virtual aaudio_result_t flushStream(aaudio_handle_t streamHandle) = 0;
/**
* Manage the specified thread as a low latency audio thread.
*/
virtual aaudio_result_t registerAudioThread(aaudio_handle_t streamHandle,
pid_t clientThreadId,
int64_t periodNanoseconds) = 0;
virtual aaudio_result_t unregisterAudioThread(aaudio_handle_t streamHandle,
pid_t clientThreadId) = 0;
virtual aaudio_result_t startClient(aaudio_handle_t streamHandle,
const android::AudioClient& client,
const audio_attributes_t *attr,
audio_port_handle_t *clientHandle) = 0;
virtual aaudio_result_t stopClient(aaudio_handle_t streamHandle,
audio_port_handle_t clientHandle) = 0;
};
} /* namespace aaudio */
AAudioBinderAdapter
Encapsule media.aaudio
el objeto proxy del cliente del servicio e implemente AAudioServiceInterface
la interfaz basada en el objeto proxy del cliente. AAudioBinderAdapter
La implementación (ubicada en frameworks/av/media/libaaudio/src/binding/AAudioBinderAdapter.cpp ) es la siguiente:
namespace aaudio {
using android::aidl_utils::statusTFromBinderStatus;
using android::binder::Status;
AAudioBinderAdapter::AAudioBinderAdapter(IAAudioService* delegate)
: mDelegate(delegate) {}
void AAudioBinderAdapter::registerClient(const android::sp<IAAudioClient>& client) {
mDelegate->registerClient(client);
}
aaudio_handle_t AAudioBinderAdapter::openStream(const AAudioStreamRequest& request,
AAudioStreamConfiguration& config) {
aaudio_handle_t result;
StreamParameters params;
Status status = mDelegate->openStream(request.parcelable(),
¶ms,
&result);
if (!status.isOk()) {
result = AAudioConvert_androidToAAudioResult(statusTFromBinderStatus(status));
}
config = params;
return result;
}
aaudio_result_t AAudioBinderAdapter::closeStream(aaudio_handle_t streamHandle) {
aaudio_result_t result;
Status status = mDelegate->closeStream(streamHandle, &result);
if (!status.isOk()) {
result = AAudioConvert_androidToAAudioResult(statusTFromBinderStatus(status));
}
return result;
}
aaudio_result_t AAudioBinderAdapter::getStreamDescription(aaudio_handle_t streamHandle,
AudioEndpointParcelable& endpointOut) {
aaudio_result_t result;
Endpoint endpoint;
Status status = mDelegate->getStreamDescription(streamHandle,
&endpoint,
&result);
if (!status.isOk()) {
result = AAudioConvert_androidToAAudioResult(statusTFromBinderStatus(status));
}
endpointOut = std::move(endpoint);
return result;
}
aaudio_result_t AAudioBinderAdapter::startStream(aaudio_handle_t streamHandle) {
aaudio_result_t result;
Status status = mDelegate->startStream(streamHandle, &result);
if (!status.isOk()) {
result = AAudioConvert_androidToAAudioResult(statusTFromBinderStatus(status));
}
return result;
}
aaudio_result_t AAudioBinderAdapter::pauseStream(aaudio_handle_t streamHandle) {
aaudio_result_t result;
Status status = mDelegate->pauseStream(streamHandle, &result);
if (!status.isOk()) {
result = AAudioConvert_androidToAAudioResult(statusTFromBinderStatus(status));
}
return result;
}
aaudio_result_t AAudioBinderAdapter::stopStream(aaudio_handle_t streamHandle) {
aaudio_result_t result;
Status status = mDelegate->stopStream(streamHandle, &result);
if (!status.isOk()) {
result = AAudioConvert_androidToAAudioResult(statusTFromBinderStatus(status));
}
return result;
}
aaudio_result_t AAudioBinderAdapter::flushStream(aaudio_handle_t streamHandle) {
aaudio_result_t result;
Status status = mDelegate->flushStream(streamHandle, &result);
if (!status.isOk()) {
result = AAudioConvert_androidToAAudioResult(statusTFromBinderStatus(status));
}
return result;
}
aaudio_result_t AAudioBinderAdapter::registerAudioThread(aaudio_handle_t streamHandle,
pid_t clientThreadId,
int64_t periodNanoseconds) {
aaudio_result_t result;
Status status = mDelegate->registerAudioThread(streamHandle, clientThreadId, periodNanoseconds, &result);
if (!status.isOk()) {
result = AAudioConvert_androidToAAudioResult(statusTFromBinderStatus(status));
}
return result;
}
aaudio_result_t AAudioBinderAdapter::unregisterAudioThread(aaudio_handle_t streamHandle,
pid_t clientThreadId) {
aaudio_result_t result;
Status status = mDelegate->unregisterAudioThread(streamHandle, clientThreadId, &result);
if (!status.isOk()) {
result = AAudioConvert_androidToAAudioResult(statusTFromBinderStatus(status));
}
return result;
}
} // namespace aaudio
AAudioBinderAdapter
Básicamente lo que hace es transferir la operación al media.aaudio
objeto proxy del cliente del servicio y convertir el valor de retorno de forma adecuada.
AAudioClient
El servicio utiliza la interfaz de carpeta media.aaudio
para notificar al cliente sobre algunos eventos e AAudioBinderClient::AAudioClient
implementar BnAAudioClient
la interfaz. AAudioBinderClient::AAudioClient
Después de recibir la notificación del evento, el evento se transmitirá AAudioBinderClient
.
AAudioBinderClient::Adapter
Herencia AAudioBinderAdapter
, que AAudioBinderAdapter
agrega la lógica de cerrar sesión en la notificación de muerte cuando se destruye.
AAudioBinderClient
Es el núcleo del módulo de enlace, mantiene el media.aaudio
objeto proxy del cliente, el objeto, etc. del servicio AAudioClient
, AAudioBinderAdapter
y el módulo del cliente accede media.aaudio
al servicio a través de él.
AAudioBinderClient
La implementación de la función utilizada para obtener AAudioServiceInterface
el objeto de interfaz getAAudioService()
(ubicado en frameworks/av/media/libaaudio/src/binding/AAudioBinderClient.cpp ) es la siguiente:
// TODO Share code with other service clients.
// Helper function to get access to the "AAudioService" service.
// This code was modeled after frameworks/av/media/libaudioclient/AudioSystem.cpp
std::shared_ptr<AAudioServiceInterface> AAudioBinderClient::getAAudioService() {
std::shared_ptr<AAudioServiceInterface> result;
sp<IAAudioService> aaudioService;
bool needToRegister = false;
{
Mutex::Autolock _l(mServiceLock);
if (mAdapter == nullptr) {
sp<IBinder> binder;
sp<IServiceManager> sm = defaultServiceManager();
// Try several times to get the service.
int retries = 4;
do {
binder = sm->getService(String16(AAUDIO_SERVICE_NAME)); // This will wait a while.
if (binder.get() != nullptr) {
break;
}
} while (retries-- > 0);
if (binder.get() != nullptr) {
// Ask for notification if the service dies.
status_t status = binder->linkToDeath(mAAudioClient);
// TODO review what we should do if this fails
if (status != NO_ERROR) {
ALOGE("%s() - linkToDeath() returned %d", __func__, status);
}
aaudioService = interface_cast<IAAudioService>(binder);
mAdapter.reset(new Adapter(aaudioService, mAAudioClient));
needToRegister = true;
// Make sure callbacks can be received by mAAudioClient
ProcessState::self()->startThreadPool();
} else {
ALOGE("AAudioBinderClient could not connect to %s", AAUDIO_SERVICE_NAME);
}
}
result = mAdapter;
}
// Do this outside the mutex lock.
if (needToRegister && aaudioService.get() != nullptr) { // new client?
aaudioService->registerClient(mAAudioClient);
}
return result;
}
AAudioBinderClient::getAAudioService()
media.aaudio
La función obtiene el objeto proxy del cliente del servicio del administrador de servicios y crea AAudioServiceInterface
un objeto de interfaz basado en el objeto, y luego registra la notificación de muerte y AAudioClient
.
Al AAudioBinderClient
recibir media.aaudio
una notificación de suspensión del servicio, destruirá los recursos que mantiene, como media.aaudio
el objeto proxy del cliente del servicio, etc. Esto se hace a través de dropAAudioService()
la función (ubicada en frameworks/av/media/libaaudio/src/binding/AAudioBinderClient. cpp ):
void AAudioBinderClient::dropAAudioService() {
Mutex::Autolock _l(mServiceLock);
mAdapter.reset();
}
El intercambio de datos con media.aaudio
servicios es la parte central del módulo de enlace. El intercambio de datos específicos ocurre principalmente en las siguientes operaciones:
- Al abrir la transmisión de audio, envíe
AAudioStreamRequest
una solicitud representada por un objeto, recibaAAudioStreamConfiguration
una respuesta que represente la información de configuración de la transmisión representada por un objeto y el identificador de la transmisión de audio del lado del servidor; - Obtenga la descripción del flujo de audio, envíe el identificador del flujo de audio del servidor y reciba
AudioEndpointParcelable
la información de descripción del flujo de audio representada por, principalmente lamedia.aaudio
información relacionada con la memoria compartida utilizada para la comunicación posterior con el servicio; - Lectura y escritura de datos de audio.
Los objetos de datos utilizados para representar solicitudes y respuestas al abrir una transmisión de audio tienen esta estructura:
android::content::AttributionSourceState
El objeto contiene principalmente información del proceso actual, como nombre del paquete, ID de usuario, ID de proceso, etc.; el AAudioStreamConfiguration
objeto contiene principalmente información de configuración de la transmisión de audio, como ID del dispositivo, frecuencia de muestreo, número de canales, contenido. tipo, modo de rendimiento, etc. Los datos que se pueden pasar a través de procesos a través de Binder IPC son solo Parcelable
objetos que admiten mecanismos de serialización. AAudioStreamConfiguration
Los datos del objeto StreamParameters
se pasan a través del objeto AIDL, AAudioStreamConfiguration
lo que proporciona parcelable()
operaciones para crear StreamParameters
objetos según el estado del objeto actual y admite StreamParameters
la creación de objetos basados en AAudioStreamConfiguration
objetos. Los datos del objeto se pasan AAudioStreamRequest
a través de objetos AIDL .StreamRequest
El objeto de datos relacionado con la descripción del flujo de audio tiene la siguiente estructura:
Los objetos en el cuadro rectangular amarillo en la figura son objetos AIDL, que se utilizan directamente para el intercambio de datos de Binder IPC.
La figura anterior SharedMemoryParcelable
se utiliza para representar un espacio de memoria compartida vinculado a un determinado descriptor de archivo, como un mmap en un dispositivo de audio. Puede dividirse en múltiples áreas diferentes. Esta memoria se puede compartir usando Binder o entre procesos. La definición de esta clase (ubicada en frameworks/av/media/libaaudio/src/binding/SharedMemoryParcelable.h ) es la siguiente:
class SharedMemoryParcelable {
public:
SharedMemoryParcelable() = default;
// Ctor from a parcelable representation.
// Since the parcelable object owns a unique FD, move semantics are provided to avoid the need
// to dupe.
explicit SharedMemoryParcelable(android::media::SharedFileRegion&& parcelable);
/**
* Make a dup() of the fd and store it for later use.
*
* @param fd
* @param sizeInBytes
*/
void setup(const android::base::unique_fd& fd, int32_t sizeInBytes);
// mmap() shared memory
aaudio_result_t resolve(int32_t offsetInBytes, int32_t sizeInBytes, void **regionAddressPtr);
// munmap() any mapped memory
aaudio_result_t close();
int32_t getSizeInBytes();
void dump();
// Extract a parcelable representation of this object.
// Since we own a unique FD, move semantics are provided to avoid the need to dupe.
android::media::SharedFileRegion parcelable() &&;
// Copy this instance. Duplicates the underlying FD.
SharedMemoryParcelable dup() const;
private:
#define MMAP_UNRESOLVED_ADDRESS reinterpret_cast<uint8_t*>(MAP_FAILED)
android::base::unique_fd mFd;
int64_t mSizeInBytes = 0;
int64_t mOffsetInBytes = 0;
uint8_t *mResolvedAddress = MMAP_UNRESOLVED_ADDRESS;
aaudio_result_t resolveSharedMemory(const android::base::unique_fd& fd);
aaudio_result_t validate() const;
};
} /* namespace aaudio */
SharedMemoryParcelable
Contiene la memoria compartida y su descriptor de archivo asociado, el tamaño del bloque de memoria, el desplazamiento del bloque de memoria y la dirección del bloque de memoria en el proceso actual. En términos generales, la descripción del archivo es el índice de la tabla de descriptores de archivos del proceso. Solo es válido para el proceso actual que tiene el archivo abierto. Es un recurso que pertenece al proceso. Al pasar el descriptor de archivos, es necesario se creará en la tabla de descriptores de archivos del proceso de destino. El elemento correspondiente apunta al archivo original y el mecanismo Binder de Android proporciona la capacidad de pasar descriptores de archivos entre procesos. El descriptor de archivo que se ve aquí puede entenderse como algo cuyo valor real puede ser diferente del descriptor de archivo original en otro proceso, pero apunta al mismo archivo real.
SharedMemoryParcelable
Cree a partir del objeto AIDL utilizado directamente para la comunicación Binder IPC SharedFileRegion
, o cree el objeto basándose en el objeto actual (ubicado en frameworks/av/media/libaaudio/src/binding/SharedMemoryParcelable.cpp ) de la siguiente manera:
SharedMemoryParcelable::SharedMemoryParcelable(SharedFileRegion&& parcelable) {
mFd = parcelable.fd.release();
mSizeInBytes = parcelable.size;
mOffsetInBytes = parcelable.offset;
}
SharedFileRegion SharedMemoryParcelable::parcelable() && {
SharedFileRegion result;
result.fd.reset(std::move(mFd));
result.size = mSizeInBytes;
result.offset = mOffsetInBytes;
return result;
}
SharedMemoryParcelable::resolve()
La función se utiliza para analizar y obtener una determinada área de memoria compartida en el bloque de memoria compartida. Realizará el mapeo de memoria cuando sea necesario. La definición de esta función (ubicada en frameworks/av/media/libaaudio/src/binding/SharedMemoryParcelable.cpp ) es el siguiente:
aaudio_result_t SharedMemoryParcelable::resolveSharedMemory(const unique_fd& fd) {
mResolvedAddress = (uint8_t *) mmap(0, mSizeInBytes, PROT_READ | PROT_WRITE,
MAP_SHARED, fd.get(), 0);
if (mResolvedAddress == MMAP_UNRESOLVED_ADDRESS) {
ALOGE("mmap() failed for fd = %d, nBytes = %" PRId64 ", errno = %s",
fd.get(), mSizeInBytes, strerror(errno));
return AAUDIO_ERROR_INTERNAL;
}
return AAUDIO_OK;
}
aaudio_result_t SharedMemoryParcelable::resolve(int32_t offsetInBytes, int32_t sizeInBytes,
void **regionAddressPtr) {
if (offsetInBytes < 0) {
ALOGE("illegal offsetInBytes = %d", offsetInBytes);
return AAUDIO_ERROR_OUT_OF_RANGE;
} else if ((offsetInBytes + sizeInBytes) > mSizeInBytes) {
ALOGE("out of range, offsetInBytes = %d, "
"sizeInBytes = %d, mSizeInBytes = %" PRId64,
offsetInBytes, sizeInBytes, mSizeInBytes);
return AAUDIO_ERROR_OUT_OF_RANGE;
}
aaudio_result_t result = AAUDIO_OK;
if (mResolvedAddress == MMAP_UNRESOLVED_ADDRESS) {
if (mFd.get() != -1) {
result = resolveSharedMemory(mFd);
} else {
ALOGE("has no file descriptor for shared memory.");
result = AAUDIO_ERROR_INTERNAL;
}
}
if (result == AAUDIO_OK && mResolvedAddress != MMAP_UNRESOLVED_ADDRESS) {
*regionAddressPtr = mResolvedAddress + offsetInBytes;
ALOGV("mResolvedAddress = %p", mResolvedAddress);
ALOGV("offset by %d, *regionAddressPtr = %p", offsetInBytes, *regionAddressPtr);
}
return result;
}
SharedRegionParcelable
Describe SharedMemoryParcelable
una región de memoria compartida en el bloque de memoria compartida descrito por. La definición de esta clase (ubicada en frameworks/av/media/libaaudio/src/binding/SharedRegionParcelable.h ) es la siguiente:
namespace aaudio {
class SharedRegionParcelable {
public:
SharedRegionParcelable() = default;
// Construct based on a parcelable representation.
explicit SharedRegionParcelable(const SharedRegion& parcelable);
void setup(int32_t sharedMemoryIndex, int32_t offsetInBytes, int32_t sizeInBytes);
aaudio_result_t resolve(SharedMemoryParcelable *memoryParcels, void **regionAddressPtr);
bool isFileDescriptorSafe(SharedMemoryParcelable *memoryParcels);
void dump();
// Extract a parcelable representation of this object.
SharedRegion parcelable() const;
private:
int32_t mSharedMemoryIndex = -1;
int32_t mOffsetInBytes = 0;
int32_t mSizeInBytes = 0;
aaudio_result_t validate() const;
};
} /* namespace aaudio */
Como se puede ver en SharedMemoryParcelable::validate()
la función, el rango de tamaño del bloque de memoria compartida es (0 kB, 256 kB):
aaudio_result_t SharedMemoryParcelable::validate() const {
if (mSizeInBytes < 0 || mSizeInBytes >= MAX_MMAP_SIZE_BYTES) {
ALOGE("invalid mSizeInBytes = %" PRId64, mSizeInBytes);
return AAUDIO_ERROR_OUT_OF_RANGE;
}
if (mOffsetInBytes != 0) {
ALOGE("invalid mOffsetInBytes = %" PRId64, mOffsetInBytes);
return AAUDIO_ERROR_OUT_OF_RANGE;
}
return AAUDIO_OK;
}
SharedRegionParcelable
Contiene SharedMemoryParcelable
información que apunta a un área de memoria compartida en un bloque de memoria compartida en una matriz. Al ejecutar, SharedRegionParcelable::resolve()
obtenga la dirección de esta área de memoria compartida en el proceso actual:
aaudio_result_t SharedRegionParcelable::resolve(SharedMemoryParcelable *memoryParcels,
void **regionAddressPtr) {
if (mSizeInBytes == 0) {
*regionAddressPtr = nullptr;
return AAUDIO_OK;
}
if (mSharedMemoryIndex < 0) {
ALOGE("invalid mSharedMemoryIndex = %d", mSharedMemoryIndex);
return AAUDIO_ERROR_INTERNAL;
}
SharedMemoryParcelable *memoryParcel = &memoryParcels[mSharedMemoryIndex];
return memoryParcel->resolve(mOffsetInBytes, mSizeInBytes, regionAddressPtr);
}
El sistema operativo proporciona la infraestructura de memoria compartida subyacente, pero para comunicarse verdaderamente a través de la memoria compartida entre dos procesos, se requiere más diseño de protocolo, como por ejemplo cómo sincronizar el acceso al área de memoria compartida entre los dos procesos. RingBufferParcelable
Encapsula varias áreas de memoria compartida e implementa una ruta de datos para intercambiar grandes bloques de datos a través de la memoria compartida. RingBufferParcelable
La definición de la clase (ubicada en frameworks/av/media/libaaudio/src/binding/RingBufferParcelable.h ) es la siguiente:
namespace aaudio {
class RingBufferParcelable {
public:
RingBufferParcelable() = default;
// Construct based on a parcelable representation.
explicit RingBufferParcelable(const RingBuffer& parcelable);
// TODO This assumes that all three use the same SharedMemoryParcelable
void setupMemory(int32_t sharedMemoryIndex,
int32_t dataMemoryOffset,
int32_t dataSizeInBytes,
int32_t readCounterOffset,
int32_t writeCounterOffset,
int32_t counterSizeBytes);
void setupMemory(int32_t sharedMemoryIndex,
int32_t dataMemoryOffset,
int32_t dataSizeInBytes);
int32_t getBytesPerFrame();
void setBytesPerFrame(int32_t bytesPerFrame);
int32_t getFramesPerBurst();
void setFramesPerBurst(int32_t framesPerBurst);
int32_t getCapacityInFrames();
void setCapacityInFrames(int32_t capacityInFrames);
bool isFileDescriptorSafe(SharedMemoryParcelable *memoryParcels);
aaudio_result_t resolve(SharedMemoryParcelable *memoryParcels, RingBufferDescriptor *descriptor);
void dump();
// Extract a parcelable representation of this object.
RingBuffer parcelable() const;
private:
SharedRegionParcelable mReadCounterParcelable;
SharedRegionParcelable mWriteCounterParcelable;
SharedRegionParcelable mDataParcelable;
int32_t mBytesPerFrame = 0; // index is in frames
int32_t mFramesPerBurst = 0; // for ISOCHRONOUS queues
int32_t mCapacityInFrames = 0; // zero if unused
RingbufferFlags mFlags = RingbufferFlags::NONE;
aaudio_result_t validate() const;
};
} /* namespace aaudio */
Específicamente, RingBufferParcelable
se encapsulan tres áreas de memoria compartida, que se utilizan para contadores de lectura, contadores de escritura y transferencia de datos de audio, respectivamente. El método para crear y crear objetos AIDL RingBufferParcelable
basados en objetos AIDL (ubicados en frameworks/av/media/libaaudio/src/binding/RingBufferParcelable.cpp ) es el siguiente:RingBuffer
RingBuffer
RingBufferParcelable::RingBufferParcelable(const RingBuffer& parcelable)
: mReadCounterParcelable(std::move(parcelable.readCounterParcelable)),
mWriteCounterParcelable(std::move(parcelable.writeCounterParcelable)),
mDataParcelable(std::move(parcelable.dataParcelable)),
mBytesPerFrame(parcelable.bytesPerFrame),
mFramesPerBurst(parcelable.framesPerBurst),
mCapacityInFrames(parcelable.capacityInFrames),
mFlags(static_cast<RingbufferFlags>(parcelable.flags)) {
static_assert(sizeof(mFlags) == sizeof(parcelable.flags));
}
RingBuffer RingBufferParcelable::parcelable() const {
RingBuffer result;
result.readCounterParcelable = std::move(mReadCounterParcelable).parcelable();
result.writeCounterParcelable = std::move(mWriteCounterParcelable).parcelable();
result.dataParcelable = std::move(mDataParcelable).parcelable();
result.bytesPerFrame = mBytesPerFrame;
result.framesPerBurst = mFramesPerBurst;
result.capacityInFrames = mCapacityInFrames;
static_assert(sizeof(mFlags) == sizeof(result.flags));
result.flags = static_cast<int32_t>(mFlags);
return result;
}
RingBufferParcelable::setupMemory()
La función configura tres áreas de memoria en la misma memoria:
void RingBufferParcelable::setupMemory(int32_t sharedMemoryIndex,
int32_t dataMemoryOffset,
int32_t dataSizeInBytes,
int32_t readCounterOffset,
int32_t writeCounterOffset,
int32_t counterSizeBytes) {
mReadCounterParcelable.setup(sharedMemoryIndex, readCounterOffset, counterSizeBytes);
mWriteCounterParcelable.setup(sharedMemoryIndex, writeCounterOffset, counterSizeBytes);
mDataParcelable.setup(sharedMemoryIndex, dataMemoryOffset, dataSizeInBytes);
}
void RingBufferParcelable::setupMemory(int32_t sharedMemoryIndex,
int32_t dataMemoryOffset,
int32_t dataSizeInBytes) {
mReadCounterParcelable.setup(sharedMemoryIndex, 0, 0);
mWriteCounterParcelable.setup(sharedMemoryIndex, 0, 0);
mDataParcelable.setup(sharedMemoryIndex, dataMemoryOffset, dataSizeInBytes);
}
RingBufferParcelable::resolve()
La función se utiliza para obtener las direcciones de tres áreas de memoria:
aaudio_result_t RingBufferParcelable::resolve(SharedMemoryParcelable *memoryParcels, RingBufferDescriptor *descriptor) {
aaudio_result_t result;
result = mReadCounterParcelable.resolve(memoryParcels,
(void **) &descriptor->readCounterAddress);
if (result != AAUDIO_OK) {
return result;
}
result = mWriteCounterParcelable.resolve(memoryParcels,
(void **) &descriptor->writeCounterAddress);
if (result != AAUDIO_OK) {
return result;
}
result = mDataParcelable.resolve(memoryParcels, (void **) &descriptor->dataAddress);
if (result != AAUDIO_OK) {
return result;
}
descriptor->bytesPerFrame = mBytesPerFrame;
descriptor->framesPerBurst = mFramesPerBurst;
descriptor->capacityInFrames = mCapacityInFrames;
descriptor->flags = mFlags;
return AAUDIO_OK;
}
Un flujo de audio necesita media.aaudio
mantener múltiples rutas de datos basadas en la memoria compartida AudioEndpointParcelable
con el servicio y mantener información relevante en un flujo de audio media.aaudio
que se comunica con el servicio a través de la memoria compartida, incluida SharedMemoryParcelable
información sobre múltiples bloques de memoria compartida y rutas de datos para diferentes propósitos RingBufferParcelable
. . AudioEndpointParcelable
La definición de la clase (ubicada en frameworks/av/media/libaaudio/src/binding/AudioEndpointParcelable.h ) es la siguiente:
namespace aaudio {
/**
* Container for information about the message queues plus
* general stream information needed by AAudio clients.
* It contains no addresses, just sizes, offsets and file descriptors for
* shared memory that can be passed through Binder.
*/
class AudioEndpointParcelable {
public:
AudioEndpointParcelable() = default;
// Ctor/assignment from a parcelable representation.
// Since the parcelable object owns unique FDs (for shared memory blocks), move semantics are
// provided to avoid the need to dupe.
AudioEndpointParcelable(Endpoint&& parcelable);
AudioEndpointParcelable& operator=(Endpoint&& parcelable);
/**
* Add the file descriptor to the table.
* @return index in table or negative error
*/
int32_t addFileDescriptor(const android::base::unique_fd& fd, int32_t sizeInBytes);
aaudio_result_t resolve(EndpointDescriptor *descriptor);
aaudio_result_t close();
void dump();
// Extract a parcelable representation of this object.
// Since our shared memory objects own a unique FD, move semantics are provided to avoid the
// need to dupe.
Endpoint parcelable()&&;
public: // TODO add getters
// Set capacityInFrames to zero if Queue is unused.
RingBufferParcelable mUpMessageQueueParcelable; // server to client
RingBufferParcelable mDownMessageQueueParcelable; // to server
RingBufferParcelable mUpDataQueueParcelable; // eg. record, could share same queue
RingBufferParcelable mDownDataQueueParcelable; // eg. playback
private:
aaudio_result_t validate() const;
int32_t mNumSharedMemories = 0;
SharedMemoryParcelable mSharedMemories[MAX_SHARED_MEMORIES];
};
} /* namespace aaudio */
AudioEndpointParcelable
La información del objeto proviene del objeto AIDL Endpoint
. AudioEndpointParcelable::resolve()
La función analiza la información de la memoria compartida, establece la asignación de memoria para la memoria compartida en el proceso actual, etc.:
aaudio_result_t AudioEndpointParcelable::resolve(EndpointDescriptor *descriptor) {
aaudio_result_t result = mUpMessageQueueParcelable.resolve(mSharedMemories,
&descriptor->upMessageQueueDescriptor);
if (result != AAUDIO_OK) return result;
result = mDownMessageQueueParcelable.resolve(mSharedMemories,
&descriptor->downMessageQueueDescriptor);
if (result != AAUDIO_OK) return result;
result = mDownDataQueueParcelable.resolve(mSharedMemories,
&descriptor->dataQueueDescriptor);
return result;
}
AudioEndpointParcelable
, RingBufferParcelable
, SharedRegionParcelable
etc. SharedMemoryParcelable
no se utilizan directamente para media.aaudio
la comunicación con el servicio. AudioEndpointParcelable::resolve()
La información relacionada con la memoria compartida para la resolución de funciones es EndpointDescriptor
mantenida por . EndpointDescriptor
La definición (ubicada en frameworks/av/media/libaaudio/src/binding/AAudioServiceDefinitions.h ) es la siguiente:
// This must be a fixed width so it can be in shared memory.
enum RingbufferFlags : uint32_t {
NONE = 0,
RATE_ISOCHRONOUS = 0x0001,
RATE_ASYNCHRONOUS = 0x0002,
COHERENCY_DMA = 0x0004,
COHERENCY_ACQUIRE_RELEASE = 0x0008,
COHERENCY_AUTO = 0x0010,
};
// This is not passed through Binder.
// Client side code will convert Binder data and fill this descriptor.
typedef struct RingBufferDescriptor_s {
uint8_t* dataAddress; // offset from read or write block
int64_t* writeCounterAddress;
int64_t* readCounterAddress;
int32_t bytesPerFrame; // index is in frames
int32_t framesPerBurst; // for ISOCHRONOUS queues
int32_t capacityInFrames; // zero if unused
RingbufferFlags flags;
} RingBufferDescriptor;
// This is not passed through Binder.
// Client side code will convert Binder data and fill this descriptor.
typedef struct EndpointDescriptor_s {
// Set capacityInFrames to zero if Queue is unused.
RingBufferDescriptor upMessageQueueDescriptor; // server to client
RingBufferDescriptor downMessageQueueDescriptor; // client to server
RingBufferDescriptor dataQueueDescriptor; // playback or capture
} EndpointDescriptor;
Una transmisión y media.aaudio
un servicio de audio en realidad tienen tres canales de datos, que se utilizan para enviar mensajes, recibir mensajes y transmitir datos de audio.
quinceañera
No es difícil ver que no es muy conveniente comunicarse directamente EndpointDescriptor
con media.aaudio
el servicio. El módulo FIFO encapsula bloques de memoria compartida utilizados para la comunicación entre procesos y proporciona operaciones más convenientes. La relación estructural de cada clase en el módulo quince es la siguiente:
FifoBuffer
Representa un canal de datos, FifoControllerBase
que se utiliza para mantener FifoBuffer
contadores de lectura y contadores de escritura. FifoBufferIndirect
Implementado para bloques de memoria compartida basados en la comunicación entre procesos FifoBuffer
e FifoBufferAllocated
implementado en función de la memoria asignada en el montón FifoBuffer
.
La transmisión de audio de AAudio AudioEndpoint
mantiene estos canales de datos. AudioEndpoint::configure()
La función (ubicada en frameworks/av/media/libaaudio/src/client/AudioEndpoint.cpp ) EndpointDescriptor
crea FifoBuffer
un objeto basado en:
// TODO Consider moving to a method in RingBufferDescriptor
static aaudio_result_t AudioEndpoint_validateQueueDescriptor(const char *type,
const RingBufferDescriptor *descriptor) {
if (descriptor == nullptr) {
ALOGE("AudioEndpoint_validateQueueDescriptor() NULL descriptor");
return AAUDIO_ERROR_NULL;
}
if (descriptor->capacityInFrames < 1
|| descriptor->capacityInFrames > RIDICULOUSLY_LARGE_BUFFER_CAPACITY) {
ALOGE("AudioEndpoint_validateQueueDescriptor() bad capacityInFrames = %d",
descriptor->capacityInFrames);
return AAUDIO_ERROR_OUT_OF_RANGE;
}
// Reject extreme values to catch bugs and prevent numeric overflows.
if (descriptor->bytesPerFrame < 1
|| descriptor->bytesPerFrame > RIDICULOUSLY_LARGE_FRAME_SIZE) {
ALOGE("AudioEndpoint_validateQueueDescriptor() bad bytesPerFrame = %d",
descriptor->bytesPerFrame);
return AAUDIO_ERROR_OUT_OF_RANGE;
}
if (descriptor->dataAddress == nullptr) {
ALOGE("AudioEndpoint_validateQueueDescriptor() NULL dataAddress");
return AAUDIO_ERROR_NULL;
}
ALOGV("AudioEndpoint_validateQueueDescriptor %s, dataAddress at %p ====================",
type,
descriptor->dataAddress);
ALOGV("AudioEndpoint_validateQueueDescriptor readCounter at %p, writeCounter at %p",
descriptor->readCounterAddress,
descriptor->writeCounterAddress);
// Try to READ from the data area.
// This code will crash if the mmap failed.
uint8_t value = descriptor->dataAddress[0];
ALOGV("AudioEndpoint_validateQueueDescriptor() dataAddress[0] = %d, then try to write",
(int) value);
// Try to WRITE to the data area.
descriptor->dataAddress[0] = value * 3;
ALOGV("AudioEndpoint_validateQueueDescriptor() wrote successfully");
if (descriptor->readCounterAddress) {
fifo_counter_t counter = *descriptor->readCounterAddress;
ALOGV("AudioEndpoint_validateQueueDescriptor() *readCounterAddress = %d, now write",
(int) counter);
*descriptor->readCounterAddress = counter;
ALOGV("AudioEndpoint_validateQueueDescriptor() wrote readCounterAddress successfully");
}
if (descriptor->writeCounterAddress) {
fifo_counter_t counter = *descriptor->writeCounterAddress;
ALOGV("AudioEndpoint_validateQueueDescriptor() *writeCounterAddress = %d, now write",
(int) counter);
*descriptor->writeCounterAddress = counter;
ALOGV("AudioEndpoint_validateQueueDescriptor() wrote writeCounterAddress successfully");
}
return AAUDIO_OK;
}
aaudio_result_t AudioEndpoint_validateDescriptor(const EndpointDescriptor *pEndpointDescriptor) {
aaudio_result_t result = AudioEndpoint_validateQueueDescriptor("messages",
&pEndpointDescriptor->upMessageQueueDescriptor);
if (result == AAUDIO_OK) {
result = AudioEndpoint_validateQueueDescriptor("data",
&pEndpointDescriptor->dataQueueDescriptor);
}
return result;
}
aaudio_result_t AudioEndpoint::configure(const EndpointDescriptor *pEndpointDescriptor,
aaudio_direction_t direction)
{
aaudio_result_t result = AudioEndpoint_validateDescriptor(pEndpointDescriptor);
if (result != AAUDIO_OK) {
return result;
}
// ============================ up message queue =============================
const RingBufferDescriptor *descriptor = &pEndpointDescriptor->upMessageQueueDescriptor;
if(descriptor->bytesPerFrame != sizeof(AAudioServiceMessage)) {
ALOGE("configure() bytesPerFrame != sizeof(AAudioServiceMessage) = %d",
descriptor->bytesPerFrame);
return AAUDIO_ERROR_INTERNAL;
}
if(descriptor->readCounterAddress == nullptr || descriptor->writeCounterAddress == nullptr) {
ALOGE("configure() NULL counter address");
return AAUDIO_ERROR_NULL;
}
// Prevent memory leak and reuse.
if(mUpCommandQueue != nullptr || mDataQueue != nullptr) {
ALOGE("configure() endpoint already used");
return AAUDIO_ERROR_INTERNAL;
}
mUpCommandQueue = std::make_unique<FifoBufferIndirect>(
descriptor->bytesPerFrame,
descriptor->capacityInFrames,
descriptor->readCounterAddress,
descriptor->writeCounterAddress,
descriptor->dataAddress
);
// ============================ data queue =============================
descriptor = &pEndpointDescriptor->dataQueueDescriptor;
ALOGV("configure() data framesPerBurst = %d", descriptor->framesPerBurst);
ALOGV("configure() data readCounterAddress = %p",
descriptor->readCounterAddress);
// An example of free running is when the other side is read or written by hardware DMA
// or a DSP. It does not update its counter so we have to update it.
int64_t *remoteCounter = (direction == AAUDIO_DIRECTION_OUTPUT)
? descriptor->readCounterAddress // read by other side
: descriptor->writeCounterAddress; // written by other side
mFreeRunning = (remoteCounter == nullptr);
ALOGV("configure() mFreeRunning = %d", mFreeRunning ? 1 : 0);
int64_t *readCounterAddress = (descriptor->readCounterAddress == nullptr)
? &mDataReadCounter
: descriptor->readCounterAddress;
int64_t *writeCounterAddress = (descriptor->writeCounterAddress == nullptr)
? &mDataWriteCounter
: descriptor->writeCounterAddress;
// Clear buffer to avoid an initial glitch on some devices.
size_t bufferSizeBytes = descriptor->capacityInFrames * descriptor->bytesPerFrame;
memset(descriptor->dataAddress, 0, bufferSizeBytes);
mDataQueue = std::make_unique<FifoBufferIndirect>(
descriptor->bytesPerFrame,
descriptor->capacityInFrames,
readCounterAddress,
writeCounterAddress,
descriptor->dataAddress
);
uint32_t threshold = descriptor->capacityInFrames / 2;
mDataQueue->setThreshold(threshold);
return result;
}
Cada flujo de audio en realidad utiliza dos canales de datos, uno se usa para que media.aaudio
el servicio envíe mensajes al cliente AAudio; el otro se usa para la transmisión de datos de audio.
AudioEndpoint::configure()
El proceso de ejecución es aproximadamente el siguiente:
- Verifique el objeto pasado
EndpointDescriptor
; - Cree un canal de datos para que
media.aaudio
el servicio envíe mensajes al cliente AAudioFifoBufferIndirect
, el formato del mensaje seráAAudioServiceMessage
; - Creado para el canal de datos utilizado para la transferencia de datos de audio
FifoBufferIndirect
.
FifoBuffer
Proporciona principalmente operaciones de lectura y escritura en el canal de datos. El proceso general de operaciones de lectura y escritura es obtener primero FifoBuffer
el paquete del área de memoria disponible y luego leer y escribir el área de memoria disponible. El ajuste del área de memoria disponible se struct WrappingBuffer
describe en y la definición de esta estructura (ubicada en frameworks/av/media/libaaudio/src/fifo/FifoBuffer.h ) es la siguiente:
struct WrappingBuffer {
enum {
SIZE = 2
};
void *data[SIZE];
int32_t numFrames[SIZE];
};
Dado que el bloque de memoria del canal de datos es un búfer en anillo, el área de memoria disponible puede abarcar el límite del búfer, por lo que las áreas de memoria en la cola y el cabezal del búfer se describen por separado. getFullDataAvailable(WrappingBuffer *wrappingBuffer)
Se utiliza para obtener un área de datos legibles y getEmptyRoomAvailable(WrappingBuffer *wrappingBuffer)
un área de datos grabables. Las definiciones de estas dos funciones (ubicadas en frameworks/av/media/libaaudio/src/fifo/FifoBuffer.cpp ) son las siguientes:
void FifoBuffer::fillWrappingBuffer(WrappingBuffer *wrappingBuffer,
int32_t framesAvailable,
int32_t startIndex) {
wrappingBuffer->data[1] = nullptr;
wrappingBuffer->numFrames[1] = 0;
uint8_t *storage = getStorage();
if (framesAvailable > 0) {
fifo_frames_t capacity = mFifo->getCapacity();
uint8_t *source = &storage[convertFramesToBytes(startIndex)];
// Does the available data cross the end of the FIFO?
if ((startIndex + framesAvailable) > capacity) {
wrappingBuffer->data[0] = source;
fifo_frames_t firstFrames = capacity - startIndex;
wrappingBuffer->numFrames[0] = firstFrames;
wrappingBuffer->data[1] = &storage[0];
wrappingBuffer->numFrames[1] = framesAvailable - firstFrames;
} else {
wrappingBuffer->data[0] = source;
wrappingBuffer->numFrames[0] = framesAvailable;
}
} else {
wrappingBuffer->data[0] = nullptr;
wrappingBuffer->numFrames[0] = 0;
}
}
fifo_frames_t FifoBuffer::getFullDataAvailable(WrappingBuffer *wrappingBuffer) {
// The FIFO might be overfull so clip to capacity.
fifo_frames_t framesAvailable = std::min(mFifo->getFullFramesAvailable(),
mFifo->getCapacity());
fifo_frames_t startIndex = mFifo->getReadIndex();
fillWrappingBuffer(wrappingBuffer, framesAvailable, startIndex);
return framesAvailable;
}
fifo_frames_t FifoBuffer::getEmptyRoomAvailable(WrappingBuffer *wrappingBuffer) {
// The FIFO might have underrun so clip to capacity.
fifo_frames_t framesAvailable = std::min(mFifo->getEmptyFramesAvailable(),
mFifo->getCapacity());
fifo_frames_t startIndex = mFifo->getWriteIndex();
fillWrappingBuffer(wrappingBuffer, framesAvailable, startIndex);
return framesAvailable;
}
FifoBuffer
Los usuarios de pueden obtener los buffers disponibles a través getFullDataAvailable(WrappingBuffer *wrappingBuffer)
de y , y luego pasar datos o mensajes a través de los buffers obtenidos, o pueden usar directamente las operaciones y Las definiciones de estas dos funciones (ubicadas en frameworks/av/media/libaaudio/src/fifo /FifoBuffer.cpp ) de la siguiente manera:getEmptyRoomAvailable(WrappingBuffer *wrappingBuffer)
FifoBuffer
read()
write()
fifo_frames_t FifoBuffer::read(void *buffer, fifo_frames_t numFrames) {
WrappingBuffer wrappingBuffer;
uint8_t *destination = (uint8_t *) buffer;
fifo_frames_t framesLeft = numFrames;
getFullDataAvailable(&wrappingBuffer);
// Read data in one or two parts.
int partIndex = 0;
while (framesLeft > 0 && partIndex < WrappingBuffer::SIZE) {
fifo_frames_t framesToRead = framesLeft;
fifo_frames_t framesAvailable = wrappingBuffer.numFrames[partIndex];
if (framesAvailable > 0) {
if (framesToRead > framesAvailable) {
framesToRead = framesAvailable;
}
int32_t numBytes = convertFramesToBytes(framesToRead);
memcpy(destination, wrappingBuffer.data[partIndex], numBytes);
destination += numBytes;
framesLeft -= framesToRead;
} else {
break;
}
partIndex++;
}
fifo_frames_t framesRead = numFrames - framesLeft;
mFifo->advanceReadIndex(framesRead);
return framesRead;
}
fifo_frames_t FifoBuffer::write(const void *buffer, fifo_frames_t numFrames) {
WrappingBuffer wrappingBuffer;
uint8_t *source = (uint8_t *) buffer;
fifo_frames_t framesLeft = numFrames;
getEmptyRoomAvailable(&wrappingBuffer);
// Read data in one or two parts.
int partIndex = 0;
while (framesLeft > 0 && partIndex < WrappingBuffer::SIZE) {
fifo_frames_t framesToWrite = framesLeft;
fifo_frames_t framesAvailable = wrappingBuffer.numFrames[partIndex];
if (framesAvailable > 0) {
if (framesToWrite > framesAvailable) {
framesToWrite = framesAvailable;
}
int32_t numBytes = convertFramesToBytes(framesToWrite);
memcpy(wrappingBuffer.data[partIndex], source, numBytes);
source += numBytes;
framesLeft -= framesToWrite;
} else {
break;
}
partIndex++;
}
fifo_frames_t framesWritten = numFrames - framesLeft;
mFifo->advanceWriteIndex(framesWritten);
return framesWritten;
}
FifoBuffer
El proceso general de las operaciones read()
y write()
es el siguiente:
- Obtenga el buffer disponible a través de
getFullDataAvailable(WrappingBuffer *wrappingBuffer)
o ;getEmptyRoomAvailable(WrappingBuffer *wrappingBuffer)
- Leer algunos datos;
- Actualizar el contador de lectura o el contador de escritura.
Tanto el contador de lectura como el contador de escritura aumentan monótonamente. La cantidad de datos legibles en el buffer es el contador de escritura menos el contador de lectura. La cantidad de datos grabables es el umbral menos la cantidad de datos legibles. El puntero de lectura y el puntero de escritura pasar por el contador de lectura y escritura El contador se obtiene dividiendo el resto por la capacidad del búfer, como se muestra en el siguiente código (ubicado en frameworks/av/media/libaaudio/src/fifo/FifoControllerBase.cpp ):
FifoControllerBase::FifoControllerBase(fifo_frames_t capacity, fifo_frames_t threshold)
: mCapacity(capacity)
, mThreshold(threshold)
{
}
FifoControllerBase::~FifoControllerBase() {
}
fifo_frames_t FifoControllerBase::getFullFramesAvailable() {
fifo_frames_t temp = 0;
__builtin_sub_overflow(getWriteCounter(), getReadCounter(), &temp);
return temp;
}
fifo_frames_t FifoControllerBase::getReadIndex() {
// % works with non-power of two sizes
return (fifo_frames_t) ((uint64_t)getReadCounter() % mCapacity);
}
void FifoControllerBase::advanceReadIndex(fifo_frames_t numFrames) {
fifo_counter_t temp = 0;
__builtin_add_overflow(getReadCounter(), numFrames, &temp);
setReadCounter(temp);
}
fifo_frames_t FifoControllerBase::getEmptyFramesAvailable() {
return (int32_t)(mThreshold - getFullFramesAvailable());
}
fifo_frames_t FifoControllerBase::getWriteIndex() {
// % works with non-power of two sizes
return (fifo_frames_t) ((uint64_t)getWriteCounter() % mCapacity);
}
void FifoControllerBase::advanceWriteIndex(fifo_frames_t numFrames) {
fifo_counter_t temp = 0;
__builtin_add_overflow(getWriteCounter(), numFrames, &temp);
setWriteCounter(temp);
}
void FifoControllerBase::setThreshold(fifo_frames_t threshold) {
if (threshold > mCapacity) {
threshold = mCapacity;
} else if (threshold < 0) {
threshold = 0;
}
mThreshold = threshold;
}
FifoBuffer
Los usuarios de no pueden operarlo directamente FifoControllerBase
, y FifoBuffer
las operaciones de lectura y escritura están estrictamente controladas por él, por lo que la lectura y la escritura no cruzarán el límite.
Procesamiento de datos en AAudio
El objetivo principal de AAudio es la recopilación y reproducción de audio de alto rendimiento, pero también puede realizar un procesamiento simple de los datos que se van a reproducir. Específicamente, puede recibir datos de muestra de audio en los formatos de float
, int16_t
, int 24
y int32_t
y convertirlos en datos de muestra de audio en estos formatos para su entrega. Entre la recepción de datos y su transmisión, puede realizar aumentos de volumen, limitación del rango de muestra de audio y procesamiento mono a multicanal de los datos. El procesamiento de datos entre la recepción de los datos y su transferencia solo se float
realiza para los datos en el formato. AudioSource
El componente utilizado para recibir los datos convierte los datos en cualquier formato a float
los datos en el formato. El componente utilizado para pasar los datos Convierta AudioSink
los float
datos en el formato al formato de datos deseado.
La estructura de las clases relacionadas con el procesamiento de datos en AAudio es la siguiente:
AAudio está definido AudioProcessorBase
para representar componentes del nodo de procesamiento de datos de audio. La definición de esta clase (ubicada en frameworks/av/media/libaaudio/src/flowgraph/AudioProcessorBase.h ) es la siguiente:
class AudioProcessorBase {
public:
virtual ~AudioProcessorBase() = default;
/**
* Perform custom function.
*
* @param framePosition index of first frame to be processed
* @param numFrames maximum number of frames requested for processing
* @return number of frames actually processed
*/
virtual int32_t onProcess(int64_t framePosition, int32_t numFrames) = 0;
/**
* If the framePosition is at or after the last frame position then call onProcess().
* This prevents infinite recursion in case of cyclic graphs.
* It also prevents nodes upstream from a branch from being executed twice.
*
* @param framePosition
* @param numFrames
* @return
*/
int32_t pullData(int64_t framePosition, int32_t numFrames);
protected:
int64_t mLastFramePosition = -1; // Start at -1 so that the first pull works.
private:
int32_t mFramesValid = 0; // num valid frames in the block
};
AudioProcessorBase
La onProcess()
función realiza operaciones de procesamiento de datos personalizadas pullData()
para impulsar AudioProcessorBase
el procesamiento de datos. AudioProcessorBase
La implementación es la siguiente:
int32_t AudioProcessorBase::pullData(int64_t framePosition, int32_t numFrames) {
if (framePosition > mLastFramePosition) {
mLastFramePosition = framePosition;
mFramesValid = onProcess(framePosition, numFrames);
}
return mFramesValid;
}
pullData()
El método se llama solo cuando el parámetro entrante framePosition
es posterior al último cuadro de audio procesado onProcess()
, lo que evita la recursividad infinita cuando el gráfico de procesamiento de datos de audio forma un bucle. También evita que los nodos anteriores a la rama se ejecuten dos veces.
AAudio define AudioPort
conectores para permitir que los datos AudioProcessorBase
fluyan entre diferentes módulos. Las definiciones de estas clases (ubicadas en frameworks/av/media/libaaudio/src/flowgraph/AudioProcessorBase.h ) son las siguientes:
class AudioPort {
public:
AudioPort(AudioProcessorBase &parent, int32_t samplesPerFrame)
: mParent(parent)
, mSamplesPerFrame(samplesPerFrame) {
}
// Ports are often declared public. So let's make them non-copyable.
AudioPort(const AudioPort&) = delete;
AudioPort& operator=(const AudioPort&) = delete;
int32_t getSamplesPerFrame() const {
return mSamplesPerFrame;
}
protected:
AudioProcessorBase &mParent;
private:
const int32_t mSamplesPerFrame = 1;
};
/***************************************************************************/
/**
* This port contains a float type buffer.
* The size is framesPerBlock * samplesPerFrame).
*/
class AudioFloatBlockPort : public AudioPort {
public:
AudioFloatBlockPort(AudioProcessorBase &mParent,
int32_t samplesPerFrame,
int32_t framesPerBlock = kDefaultBlockSize
);
virtual ~AudioFloatBlockPort();
int32_t getFramesPerBlock() const {
return mFramesPerBlock;
}
protected:
/**
* @return buffer internal to the port or from a connected port
*/
virtual float *getBlock() {
return mSampleBlock;
}
private:
const int32_t mFramesPerBlock = 1;
float *mSampleBlock = nullptr; // allocated in constructor
};
/***************************************************************************/
/**
* The results of a module are stored in the buffer of the output ports.
*/
class AudioFloatOutputPort : public AudioFloatBlockPort {
public:
AudioFloatOutputPort(AudioProcessorBase &parent, int32_t samplesPerFrame)
: AudioFloatBlockPort(parent, samplesPerFrame) {
}
virtual ~AudioFloatOutputPort() = default;
using AudioFloatBlockPort::getBlock;
/**
* Call the parent module's onProcess() method.
* That may pull data from its inputs and recursively
* process the entire graph.
* @return number of frames actually pulled
*/
int32_t pullData(int64_t framePosition, int32_t numFrames);
/**
* Connect to the input of another module.
* An input port can only have one connection.
* An output port can have multiple connections.
* If you connect a second output port to an input port
* then it overwrites the previous connection.
*
* This not thread safe. Do not modify the graph topology form another thread while running.
*/
void connect(AudioFloatInputPort *port);
/**
* Disconnect from the input of another module.
* This not thread safe.
*/
void disconnect(AudioFloatInputPort *port);
};
/***************************************************************************/
class AudioFloatInputPort : public AudioFloatBlockPort {
public:
AudioFloatInputPort(AudioProcessorBase &parent, int32_t samplesPerFrame)
: AudioFloatBlockPort(parent, samplesPerFrame) {
}
virtual ~AudioFloatInputPort() = default;
/**
* If connected to an output port then this will return
* that output ports buffers.
* If not connected then it returns the input ports own buffer
* which can be loaded using setValue().
*/
float *getBlock() override;
/**
* Pull data from any output port that is connected.
*/
int32_t pullData(int64_t framePosition, int32_t numFrames);
/**
* Write every value of the float buffer.
* This value will be ignored if an output port is connected
* to this port.
*/
void setValue(float value) {
int numFloats = kDefaultBlockSize * getSamplesPerFrame();
float *buffer = getBlock();
for (int i = 0; i < numFloats; i++) {
*buffer++ = value;
}
}
/**
* Connect to the output of another module.
* An input port can only have one connection.
* An output port can have multiple connections.
* This not thread safe.
*/
void connect(AudioFloatOutputPort *port) {
assert(getSamplesPerFrame() == port->getSamplesPerFrame());
mConnected = port;
}
void disconnect(AudioFloatOutputPort *port) {
assert(mConnected == port);
(void) port;
mConnected = nullptr;
}
void disconnect() {
mConnected = nullptr;
}
private:
AudioFloatOutputPort *mConnected = nullptr;
};
AudioFloatBlockPort
Mantiene principalmente un búfer para la transferencia de datos. AudioProcessorBase
El trabajo de conectar diferentes módulos lo realiza principalmente AudioFloatOutputPort
y AudioFloatInputPort
, que agrega operaciones como pullData()
, connect()
y . disconnect()
La implementación de estos AudioPort
(ubicada en frameworks/av/media/libaaudio/src/flowgraph/AudioProcessorBase.cpp ) es la siguiente:
/***************************************************************************/
AudioFloatBlockPort::AudioFloatBlockPort(AudioProcessorBase &parent,
int32_t samplesPerFrame,
int32_t framesPerBlock)
: AudioPort(parent, samplesPerFrame)
, mFramesPerBlock(framesPerBlock)
, mSampleBlock(NULL) {
int32_t numFloats = framesPerBlock * getSamplesPerFrame();
mSampleBlock = new float[numFloats]{0.0f};
}
AudioFloatBlockPort::~AudioFloatBlockPort() {
delete[] mSampleBlock;
}
/***************************************************************************/
int32_t AudioFloatOutputPort::pullData(int64_t framePosition, int32_t numFrames) {
numFrames = std::min(getFramesPerBlock(), numFrames);
return mParent.pullData(framePosition, numFrames);
}
// These need to be in the .cpp file because of forward cross references.
void AudioFloatOutputPort::connect(AudioFloatInputPort *port) {
port->connect(this);
}
void AudioFloatOutputPort::disconnect(AudioFloatInputPort *port) {
port->disconnect(this);
}
/***************************************************************************/
int32_t AudioFloatInputPort::pullData(int64_t framePosition, int32_t numFrames) {
return (mConnected == NULL)
? std::min(getFramesPerBlock(), numFrames)
: mConnected->pullData(framePosition, numFrames);
}
float *AudioFloatInputPort::getBlock() {
if (mConnected == NULL) {
return AudioFloatBlockPort::getBlock(); // loaded using setValue()
} else {
return mConnected->getBlock();
}
}
AudioPort
La forma en que funciona el diseño es esta:
AudioProcessorBase
El módulo contieneAudioFloatOutputPort
objetos y la interfazAudioFloatOutputPort
depullData()
se utiliza para impulsar la tubería de procesamiento de datos de audio para procesar datos de audio, es decir, llamarAudioProcessorBase
ypullData()
luego realizarAudioProcessorBase
la operación de datos deonProcess()
EnAudioProcessorBase
laonProcess()
implementación de la operación de datos de,AudioProcessorBase
se obtienen los datos a procesar desde otro lugar y se coloca después del procesamientoAudioFloatOutputPort
... en el búfer;AudioProcessorBase
Los módulos contienenAudioFloatInputPort
objetos que se utilizan paraAudioProcessorBase
obtener datos de otros módulos después de que hayan sido procesados. No es necesario queAudioFloatInputPort
el objeto contenga una referencia al módulo que lo contiene ;AudioProcessorBase
AudioFloatOutputPort
Las operaciones yAudioFloatInputPort
deconnect()
y se utilizan principalmente para conectar el módulodisconnect()
anterior con el siguiente módulo .AudioProcessorBase
AudioFloatInputPort
AudioProcessorBase
AudioFloatOutputPort
El proceso de procesamiento de datos de audio compuesto AudioProcessorBase
por y AudioFloatOutputPort
será como se muestra a continuación:AudioFloatOutputPort
AudioProcessorBase
La transferencia de datos entre diferentes módulos se completa mediante AudioFloatOutputPort
y La transferencia de datos entre y dentro AudioFloatOutputPort
del mismo módulo se completa principalmente mediante la interfaz de operación de datos de .AudioProcessorBase
AudioFloatOutputPort
AudioFloatOutputPort
AudioProcessorBase
onProcess()
AAudio se define para representar el componente de nodo que AudioSource
procesa datos de entrada, es decir, convierte un determinado tipo de datos de audio en datos de tipo. La definición de esta clase (ubicada en frameworks/av/media/libaaudio/src/flowgraph/AudioProcessorBase.h ) es como sigue:float
class AudioSource : public AudioProcessorBase {
public:
explicit AudioSource(int32_t channelCount)
: output(*this, channelCount) {
}
virtual ~AudioSource() = default;
AudioFloatOutputPort output;
void setData(const void *data, int32_t numFrames) {
mData = data;
mSizeInFrames = numFrames;
mFrameIndex = 0;
}
protected:
const void *mData = nullptr;
int32_t mSizeInFrames = 0; // number of frames in mData
int32_t mFrameIndex = 0; // index of next frame to be processed
};
AudioSource
Proporciona una interfaz con el mundo exterior para proporcionar datos de origen para el canal de procesamiento de datos de audio y pasa los datos convertidos a los módulos posteriores del canal de procesamiento de datos de audio setData()
a través de él .AudioFloatOutputPort
AudioProcessorBase
AAudio se define para representar el componente de nodo que AudioSink
procesa datos de salida, es decir, convierte datos de tipo en un determinado tipo de datos de audio. La definición de esta clase (ubicada en frameworks/av/media/libaaudio/src/flowgraph/AudioProcessorBase.h ) es como sigue:float
class AudioSink : public AudioProcessorBase {
public:
explicit AudioSink(int32_t channelCount)
: input(*this, channelCount) {
}
virtual ~AudioSink() = default;
AudioFloatInputPort input;
/**
* Do nothing. The work happens in the read() method.
*
* @param framePosition index of first frame to be processed
* @param numFrames
* @return number of frames actually processed
*/
int32_t onProcess(int64_t framePosition, int32_t numFrames) override {
(void) framePosition;
(void) numFrames;
return 0;
};
virtual int32_t read(void *data, int32_t numFrames) = 0;
protected:
int32_t pull(int32_t numFrames);
private:
int64_t mFramePosition = 0;
};
AudioSink
La onProcess()
función no es operativa, agrega una nueva read()
operación para completar la operación de procesamiento de datos final en la canalización de procesamiento de datos de audio y obtener los datos procesados. AudioSink
El diseño actual de se onProcess()
implementará como una operación no operativa, lo que rompe la semántica de las pullData()
interfaces heredadas onProcess()
. Para AudioSink
el diseño de , existen otras opciones:
(1). No es necesario agregar read()
una operación;
(2). Además de un AudioFloatInputPort
objeto, se incluye AudioSink
otro objeto; (3). Todo el proceso de procesamiento de datos de audio está controlado por el objeto ; (4). El original La interfaz se completa El trabajo se divide en dos partes: la parte de procesamiento de datos se coloca en la interfaz, los datos procesados se guardan en el búfer de y la parte de adquisición de datos se completa directamente a través del objeto.AudioFloatOutputPort
AudioFloatOutputPort
pullData()
read()
onProcess()
AudioFloatOutputPort
AudioFloatOutputPort
Sin embargo, el diseño actual puede evitar una copia de datos. Este es probablemente un ejemplo de los esfuerzos realizados por la versatilidad o el compromiso con el rendimiento. A continuación se muestran algunos ejemplos más de diferentes tipos AudioProcessorBase
de módulos. SourceI16
Se utiliza para int16_t
convertir datos de origen de tipo en float
datos de tipo AudioSource
. La definición de esta clase (ubicada en frameworks/av/media/libaaudio/src/flowgraph/SourceI16.h ) es la siguiente:
namespace flowgraph {
class SourceI16 : public AudioSource {
public:
explicit SourceI16(int32_t channelCount);
int32_t onProcess(int64_t framePosition, int32_t numFrames) override;
};
} /* namespace flowgraph */
La implementación de esta clase (ubicada en frameworks/av/media/libaaudio/src/flowgraph/SourceI16.cpp ) es la siguiente:
using namespace flowgraph;
SourceI16::SourceI16(int32_t channelCount)
: AudioSource(channelCount) {
}
int32_t SourceI16::onProcess(int64_t framePosition, int32_t numFrames) {
float *floatData = output.getBlock();
int32_t channelCount = output.getSamplesPerFrame();
int32_t framesLeft = mSizeInFrames - mFrameIndex;
int32_t framesToProcess = std::min(numFrames, framesLeft);
int32_t numSamples = framesToProcess * channelCount;
const int16_t *shortBase = static_cast<const int16_t *>(mData);
const int16_t *shortData = &shortBase[mFrameIndex * channelCount];
#ifdef __ANDROID__
memcpy_to_float_from_i16(floatData, shortData, numSamples);
#else
for (int i = 0; i < numSamples; i++) {
*floatData++ = *shortData++ * (1.0f / 32768);
}
#endif
mFrameIndex += framesToProcess;
return framesToProcess;
}
MonoToMultiConverter
Ubicado en el medio del proceso de procesamiento de datos de audio, se utiliza para convertir datos monocanal en datos multicanal. La definición de esta clase (ubicada en frameworks/av/media/libaaudio/src/flowgraph/MonoToMultiConverter.h ) es como sigue:
namespace flowgraph {
class MonoToMultiConverter : public AudioProcessorBase {
public:
explicit MonoToMultiConverter(int32_t channelCount);
virtual ~MonoToMultiConverter();
int32_t onProcess(int64_t framePosition, int32_t numFrames) override;
AudioFloatInputPort input;
AudioFloatOutputPort output;
};
} /* namespace flowgraph */
La implementación de esta clase (ubicada en frameworks/av/media/libaaudio/src/flowgraph/MonoToMultiConverter.cpp ) es la siguiente:
using namespace flowgraph;
MonoToMultiConverter::MonoToMultiConverter(int32_t channelCount)
: input(*this, 1)
, output(*this, channelCount) {
}
MonoToMultiConverter::~MonoToMultiConverter() { }
int32_t MonoToMultiConverter::onProcess(int64_t framePosition, int32_t numFrames) {
int32_t framesToProcess = input.pullData(framePosition, numFrames);
const float *inputBuffer = input.getBlock();
float *outputBuffer = output.getBlock();
int32_t channelCount = output.getSamplesPerFrame();
// TODO maybe move to audio_util as audio_mono_to_multi()
for (int i = 0; i < framesToProcess; i++) {
// read one, write many
float sample = *inputBuffer++;
for (int channel = 0; channel < channelCount; channel++) {
*outputBuffer++ = sample;
}
}
return framesToProcess;
}
SinkI16
Convierte float
datos de tipo en int16_t
datos de tipo AudioSink
. La definición de esta clase (ubicada en frameworks/av/media/libaaudio/src/flowgraph/SinkI16.h ) es la siguiente:
namespace flowgraph {
class SinkI16 : public AudioSink {
public:
explicit SinkI16(int32_t channelCount);
int32_t read(void *data, int32_t numFrames) override;
};
} /* namespace flowgraph */
La implementación de esta clase (ubicada en frameworks/av/media/libaaudio/src/flowgraph/SinkI16.cpp ) es la siguiente:
using namespace flowgraph;
SinkI16::SinkI16(int32_t channelCount)
: AudioSink(channelCount) {}
int32_t SinkI16::read(void *data, int32_t numFrames) {
int16_t *shortData = (int16_t *) data;
const int32_t channelCount = input.getSamplesPerFrame();
int32_t framesLeft = numFrames;
while (framesLeft > 0) {
// Run the graph and pull data through the input port.
int32_t framesRead = pull(framesLeft);
if (framesRead <= 0) {
break;
}
const float *signal = input.getBlock();
int32_t numSamples = framesRead * channelCount;
#ifdef __ANDROID__
memcpy_to_i16_from_float(shortData, signal, numSamples);
shortData += numSamples;
signal += numSamples;
#else
for (int i = 0; i < numSamples; i++) {
int32_t n = (int32_t) (*signal++ * 32768.0f);
*shortData++ = std::min(INT16_MAX, std::max(INT16_MIN, n)); // clip
}
#endif
framesLeft -= framesRead;
}
return numFrames - framesLeft;
}
La canalización de procesamiento de datos de audio de AAudio es AAudioFlowGraph
mantenida por. La definición de esta clase (ubicada en frameworks/av/media/libaaudio/src/client/AAudioFlowGraph.h ) es la siguiente:
class AAudioFlowGraph {
public:
/** Connect several modules together to convert from source to sink.
* This should only be called once for each instance.
*
* @param sourceFormat
* @param sourceChannelCount
* @param sinkFormat
* @param sinkChannelCount
* @return
*/
aaudio_result_t configure(audio_format_t sourceFormat,
int32_t sourceChannelCount,
audio_format_t sinkFormat,
int32_t sinkChannelCount);
void process(const void *source, void *destination, int32_t numFrames);
/**
* @param volume between 0.0 and 1.0
*/
void setTargetVolume(float volume);
void setRampLengthInFrames(int32_t numFrames);
private:
std::unique_ptr<flowgraph::AudioSource> mSource;
std::unique_ptr<flowgraph::RampLinear> mVolumeRamp;
std::unique_ptr<flowgraph::ClipToRange> mClipper;
std::unique_ptr<flowgraph::MonoToMultiConverter> mChannelConverter;
std::unique_ptr<flowgraph::AudioSink> mSink;
};
AAudioFlowGraph
Cree una canalización de procesamiento de datos de audio durante la configuración y proporcione process()
operaciones para procesar datos a través de la canalización de procesamiento de datos de audio:
using namespace flowgraph;
aaudio_result_t AAudioFlowGraph::configure(audio_format_t sourceFormat,
int32_t sourceChannelCount,
audio_format_t sinkFormat,
int32_t sinkChannelCount) {
AudioFloatOutputPort *lastOutput = nullptr;
// TODO change back to ALOGD
ALOGI("%s() source format = 0x%08x, channels = %d, sink format = 0x%08x, channels = %d",
__func__, sourceFormat, sourceChannelCount, sinkFormat, sinkChannelCount);
switch (sourceFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
mSource = std::make_unique<SourceFloat>(sourceChannelCount);
break;
case AUDIO_FORMAT_PCM_16_BIT:
mSource = std::make_unique<SourceI16>(sourceChannelCount);
break;
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
mSource = std::make_unique<SourceI24>(sourceChannelCount);
break;
case AUDIO_FORMAT_PCM_32_BIT:
mSource = std::make_unique<SourceI32>(sourceChannelCount);
break;
default:
ALOGE("%s() Unsupported source format = %d", __func__, sourceFormat);
return AAUDIO_ERROR_UNIMPLEMENTED;
}
lastOutput = &mSource->output;
// Apply volume as a ramp to avoid pops.
mVolumeRamp = std::make_unique<RampLinear>(sourceChannelCount);
lastOutput->connect(&mVolumeRamp->input);
lastOutput = &mVolumeRamp->output;
// For a pure float graph, there is chance that the data range may be very large.
// So we should clip to a reasonable value that allows a little headroom.
if (sourceFormat == AUDIO_FORMAT_PCM_FLOAT && sinkFormat == AUDIO_FORMAT_PCM_FLOAT) {
mClipper = std::make_unique<ClipToRange>(sourceChannelCount);
lastOutput->connect(&mClipper->input);
lastOutput = &mClipper->output;
}
// Expand the number of channels if required.
if (sourceChannelCount == 1 && sinkChannelCount > 1) {
mChannelConverter = std::make_unique<MonoToMultiConverter>(sinkChannelCount);
lastOutput->connect(&mChannelConverter->input);
lastOutput = &mChannelConverter->output;
} else if (sourceChannelCount != sinkChannelCount) {
ALOGE("%s() Channel reduction not supported.", __func__);
return AAUDIO_ERROR_UNIMPLEMENTED;
}
switch (sinkFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
mSink = std::make_unique<SinkFloat>(sinkChannelCount);
break;
case AUDIO_FORMAT_PCM_16_BIT:
mSink = std::make_unique<SinkI16>(sinkChannelCount);
break;
case AUDIO_FORMAT_PCM_24_BIT_PACKED:
mSink = std::make_unique<SinkI24>(sinkChannelCount);
break;
case AUDIO_FORMAT_PCM_32_BIT:
mSink = std::make_unique<SinkI32>(sinkChannelCount);
break;
default:
ALOGE("%s() Unsupported sink format = %d", __func__, sinkFormat);
return AAUDIO_ERROR_UNIMPLEMENTED;
}
lastOutput->connect(&mSink->input);
return AAUDIO_OK;
}
void AAudioFlowGraph::process(const void *source, void *destination, int32_t numFrames) {
mSource->setData(source, numFrames);
mSink->read(destination, numFrames);
}
/**
* @param volume between 0.0 and 1.0
*/
void AAudioFlowGraph::setTargetVolume(float volume) {
mVolumeRamp->setTarget(volume);
}
void AAudioFlowGraph::setRampLengthInFrames(int32_t numFrames) {
mVolumeRamp->setLengthInFrames(numFrames);
}
De esta manera, el proceso de procesamiento de datos de audio de AAudio eventualmente se verá así:
Implementación de transmisión de audio
La mayoría de los recursos necesarios para que se ejecute la transmisión de audio se asignan cuando se abre la transmisión de audio. La transmisión de audio realmente se ejecutará cuando la aplicación solicite iniciar la transmisión de audio. La implementación de la interfaz de la solicitud AAudio para iniciar la transmisión de audio (ubicada en frameworks/av/media/libaaudio/src/core/AAudioAudio.cpp ) es la siguiente:
AAUDIO_API aaudio_result_t AAudioStream_requestStart(AAudioStream* stream)
{
AudioStream *audioStream = convertAAudioStreamToAudioStream(stream);
aaudio_stream_id_t id = audioStream->getId();
ALOGD("%s(s#%u) called --------------", __func__, id);
aaudio_result_t result = audioStream->systemStart();
ALOGD("%s(s#%u) returned %d ---------", __func__, id, result);
return result;
}
Esta función llama AudioStream::systemStart()
a la función, que está definida (ubicada en frameworks/av/media/libaaudio/src/core/AudioStream.cpp ) de la siguiente manera:
aaudio_result_t AudioStream::systemStart() {
if (collidesWithCallback()) {
ALOGE("%s cannot be called from a callback!", __func__);
return AAUDIO_ERROR_INVALID_STATE;
}
std::lock_guard<std::mutex> lock(mStreamLock);
switch (getState()) {
// Is this a good time to start?
case AAUDIO_STREAM_STATE_OPEN:
case AAUDIO_STREAM_STATE_PAUSING:
case AAUDIO_STREAM_STATE_PAUSED:
case AAUDIO_STREAM_STATE_STOPPING:
case AAUDIO_STREAM_STATE_STOPPED:
case AAUDIO_STREAM_STATE_FLUSHING:
case AAUDIO_STREAM_STATE_FLUSHED:
break; // Proceed with starting.
// Already started?
case AAUDIO_STREAM_STATE_STARTING:
case AAUDIO_STREAM_STATE_STARTED:
ALOGW("%s() stream was already started, state = %s", __func__,
AudioGlobal_convertStreamStateToText(getState()));
return AAUDIO_ERROR_INVALID_STATE;
// Don't start when the stream is dead!
case AAUDIO_STREAM_STATE_DISCONNECTED:
case AAUDIO_STREAM_STATE_CLOSING:
case AAUDIO_STREAM_STATE_CLOSED:
default:
ALOGW("%s() stream is dead, state = %s", __func__,
AudioGlobal_convertStreamStateToText(getState()));
return AAUDIO_ERROR_INVALID_STATE;
}
aaudio_result_t result = requestStart_l();
if (result == AAUDIO_OK) {
// We only call this for logging in "dumpsys audio". So ignore return code.
(void) mPlayerBase->startWithStatus(getDeviceId());
}
return result;
}
AudioStream::systemStart()
La función verifica el estado actual de la transmisión de audio; cuando el estado permite el inicio, solicita la transmisión de audio específica para realizar la acción de inicio, es decir requestStart_l()
. AudioStreamInternal::requestStart_l()
La definición de la función (ubicada en frameworks/av/media/libaaudio/src/client/AudioStreamInternal.cpp ) es la siguiente:
aaudio_result_t AudioStreamInternal::requestStart_l()
{
int64_t startTime;
if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
ALOGD("requestStart() mServiceStreamHandle invalid");
return AAUDIO_ERROR_INVALID_STATE;
}
if (isActive()) {
ALOGD("requestStart() already active");
return AAUDIO_ERROR_INVALID_STATE;
}
aaudio_stream_state_t originalState = getState();
if (originalState == AAUDIO_STREAM_STATE_DISCONNECTED) {
ALOGD("requestStart() but DISCONNECTED");
return AAUDIO_ERROR_DISCONNECTED;
}
setState(AAUDIO_STREAM_STATE_STARTING);
// Clear any stale timestamps from the previous run.
drainTimestampsFromService();
prepareBuffersForStart(); // tell subclasses to get ready
aaudio_result_t result = mServiceInterface.startStream(mServiceStreamHandle);
if (result == AAUDIO_ERROR_INVALID_HANDLE) {
ALOGD("%s() INVALID_HANDLE, stream was probably stolen", __func__);
// Stealing was added in R. Coerce result to improve backward compatibility.
result = AAUDIO_ERROR_DISCONNECTED;
setState(AAUDIO_STREAM_STATE_DISCONNECTED);
}
startTime = AudioClock::getNanoseconds();
mClockModel.start(startTime);
mNeedCatchUp.request(); // Ask data processing code to catch up when first timestamp received.
// Start data callback thread.
if (result == AAUDIO_OK && isDataCallbackSet()) {
// Launch the callback loop thread.
int64_t periodNanos = mCallbackFrames
* AAUDIO_NANOS_PER_SECOND
/ getSampleRate();
mCallbackEnabled.store(true);
result = createThread_l(periodNanos, aaudio_callback_thread_proc, this);
}
if (result != AAUDIO_OK) {
setState(originalState);
}
return result;
}
. . . . . .
aaudio_result_t AudioStreamInternal::drainTimestampsFromService() {
aaudio_result_t result = AAUDIO_OK;
while (result == AAUDIO_OK) {
AAudioServiceMessage message;
if (!mAudioEndpoint) {
break;
}
if (mAudioEndpoint->readUpCommand(&message) != 1) {
break; // no command this time, no problem
}
switch (message.what) {
// ignore most messages
case AAudioServiceMessage::code::TIMESTAMP_SERVICE:
case AAudioServiceMessage::code::TIMESTAMP_HARDWARE:
break;
case AAudioServiceMessage::code::EVENT:
result = onEventFromServer(&message);
break;
default:
ALOGE("%s - unrecognized message.what = %d", __func__, (int) message.what);
result = AAUDIO_ERROR_INTERNAL;
break;
}
}
return result;
}
El proceso de ejecución de esta función es el siguiente:
- Verifique el estado actual de la transmisión de audio y establezca el estado actual en
AAUDIO_STREAM_STATE_STARTING
; - Borrar los mensajes enviados por el servidor;
- Solicita a la subclase de flujo de audio que se prepare para el inicio y prepare el búfer;
- Solicite al servicio que inicie la transmisión de audio a través de la interfaz del servicio AAudio
media.aaudio
; - Establezca la hora actual como la hora de inicio del modelo de reloj sincronizado para facilitar la predicción de la posición del flujo de audio en un momento determinado;
- Hay dos tipos de transmisión de datos para transmisiones de audio: modo push y modo pull. Uno es que la aplicación toma sincrónicamente los datos de audio y los envía a la transmisión de audio a través
read()
de la operación / proporcionada por la transmisión de audio; el otro es configurar una devolución de llamada de datos parawrite()
la secuencia de audio. Cuando AAudio inicia un hilo para la secuencia de audio, primero completa la transferencia de datos con la aplicación a través de devoluciones de llamada y luego escribe o lee los datos en la secuencia de datos. Cuando se establece una devolución de llamada de datos, se calcula el ciclo de ejecución de la devolución de llamada y se solicita que se inicie un hilo.
Las tareas realizadas por el hilo iniciado para la transmisión de audio (ubicada en frameworks/av/media/libaaudio/src/client/AudioStreamInternal.cpp ) son las siguientes:
static void *aaudio_callback_thread_proc(void *context)
{
AudioStreamInternal *stream = (AudioStreamInternal *)context;
//LOGD("oboe_callback_thread, stream = %p", stream);
if (stream != NULL) {
return stream->callbackLoop();
} else {
return NULL;
}
}
El bucle de devolución de llamada para reproducir la transmisión de audio (ubicado en frameworks/av/media/libaaudio/src/client/AudioStreamInternalPlay.cpp ) es el siguiente:
void *AudioStreamInternalPlay::callbackLoop() {
ALOGD("%s() entering >>>>>>>>>>>>>>>", __func__);
aaudio_result_t result = AAUDIO_OK;
aaudio_data_callback_result_t callbackResult = AAUDIO_CALLBACK_RESULT_CONTINUE;
if (!isDataCallbackSet()) return NULL;
int64_t timeoutNanos = calculateReasonableTimeout(mCallbackFrames);
// result might be a frame count
while (mCallbackEnabled.load() && isActive() && (result >= 0)) {
// Call application using the AAudio callback interface.
callbackResult = maybeCallDataCallback(mCallbackBuffer.get(), mCallbackFrames);
if (callbackResult == AAUDIO_CALLBACK_RESULT_CONTINUE) {
// Write audio data to stream. This is a BLOCKING WRITE!
result = write(mCallbackBuffer.get(), mCallbackFrames, timeoutNanos);
if ((result != mCallbackFrames)) {
if (result >= 0) {
// Only wrote some of the frames requested. Must have timed out.
result = AAUDIO_ERROR_TIMEOUT;
}
maybeCallErrorCallback(result);
break;
}
} else if (callbackResult == AAUDIO_CALLBACK_RESULT_STOP) {
ALOGD("%s(): callback returned AAUDIO_CALLBACK_RESULT_STOP", __func__);
result = systemStopInternal();
break;
}
}
ALOGD("%s() exiting, result = %d, isActive() = %d <<<<<<<<<<<<<<",
__func__, result, (int) isActive());
return NULL;
}
Solicita continuamente datos de audio de la aplicación y los datos de audio obtenidos se guardan en un búfer y luego se escriben en la transmisión de audio.
El bucle de devolución de llamada para recopilar transmisiones de audio (ubicado en frameworks/av/media/libaaudio/src/client/AudioStreamInternalCapture.cpp ) es el siguiente:
void *AudioStreamInternalCapture::callbackLoop() {
aaudio_result_t result = AAUDIO_OK;
aaudio_data_callback_result_t callbackResult = AAUDIO_CALLBACK_RESULT_CONTINUE;
if (!isDataCallbackSet()) return NULL;
// result might be a frame count
while (mCallbackEnabled.load() && isActive() && (result >= 0)) {
// Read audio data from stream.
int64_t timeoutNanos = calculateReasonableTimeout(mCallbackFrames);
// This is a BLOCKING READ!
result = read(mCallbackBuffer.get(), mCallbackFrames, timeoutNanos);
if ((result != mCallbackFrames)) {
ALOGE("callbackLoop: read() returned %d", result);
if (result >= 0) {
// Only read some of the frames requested. Must have timed out.
result = AAUDIO_ERROR_TIMEOUT;
}
maybeCallErrorCallback(result);
break;
}
// Call application using the AAudio callback interface.
callbackResult = maybeCallDataCallback(mCallbackBuffer.get(), mCallbackFrames);
if (callbackResult == AAUDIO_CALLBACK_RESULT_STOP) {
ALOGD("%s(): callback returned AAUDIO_CALLBACK_RESULT_STOP", __func__);
result = systemStopInternal();
break;
}
}
ALOGD("callbackLoop() exiting, result = %d, isActive() = %d",
result, (int) isActive());
return NULL;
}
Lee continuamente datos de la transmisión de audio y luego los pasa a la aplicación a través de la función de devolución de llamada.
AudioStream
El proceso de creación de un hilo (ubicado en frameworks/av/media/libaaudio/src/core/AudioStream.cpp ) es el siguiente:
void* AudioStream::wrapUserThread() {
void* procResult = nullptr;
mThreadRegistrationResult = registerThread();
if (mThreadRegistrationResult == AAUDIO_OK) {
// Run callback loop. This may take a very long time.
procResult = mThreadProc(mThreadArg);
mThreadRegistrationResult = unregisterThread();
}
return procResult;
}
// This is the entry point for the new thread created by createThread_l().
// It converts the 'C' function call to a C++ method call.
static void* AudioStream_internalThreadProc(void* threadArg) {
AudioStream *audioStream = (AudioStream *) threadArg;
// Prevent the stream from being deleted while being used.
// This is just for extra safety. It is probably not needed because
// this callback should be joined before the stream is closed.
android::sp<AudioStream> protectedStream(audioStream);
// Balance the incStrong() in createThread_l().
protectedStream->decStrong(nullptr);
return protectedStream->wrapUserThread();
}
// This is not exposed in the API.
// But it is still used internally to implement callbacks for MMAP mode.
aaudio_result_t AudioStream::createThread_l(int64_t periodNanoseconds,
aaudio_audio_thread_proc_t threadProc,
void* threadArg)
{
if (mHasThread) {
ALOGD("%s() - previous thread was not joined, join now to be safe", __func__);
joinThread_l(nullptr);
}
if (threadProc == nullptr) {
return AAUDIO_ERROR_NULL;
}
// Pass input parameters to the background thread.
mThreadProc = threadProc;
mThreadArg = threadArg;
setPeriodNanoseconds(periodNanoseconds);
mHasThread = true;
// Prevent this object from getting deleted before the thread has a chance to create
// its strong pointer. Assume the thread will call decStrong().
this->incStrong(nullptr);
int err = pthread_create(&mThread, nullptr, AudioStream_internalThreadProc, this);
if (err != 0) {
android::status_t status = -errno;
ALOGE("%s() - pthread_create() failed, %d", __func__, status);
this->decStrong(nullptr); // Because the thread won't do it.
mHasThread = false;
return AAudioConvert_androidToAAudioResult(status);
} else {
// TODO Use AAudioThread or maybe AndroidThread
// Name the thread with an increasing index, "AAudio_#", for debugging.
static std::atomic<uint32_t> nextThreadIndex{1};
char name[16]; // max length for a pthread_name
uint32_t index = nextThreadIndex++;
// Wrap the index so that we do not hit the 16 char limit
// and to avoid hard-to-read large numbers.
index = index % 100000; // arbitrary
snprintf(name, sizeof(name), "AAudio_%u", index);
err = pthread_setname_np(mThread, name);
ALOGW_IF((err != 0), "Could not set name of AAudio thread. err = %d", err);
return AAUDIO_OK;
}
}
AudioStream
Creando pthread_create()
un hilo, realizando la tarea AudioStream_internalThreadProc
y luego configurando el nombre del hilo. Después de que el hilo recién creado comienza a ejecutarse, primero registra el hilo actual y luego ejecuta las tareas realizadas cuando se creó el hilo, que es el bucle de devolución de llamada de la transmisión de audio AAudio. Al registrar el hilo actual se enviará información del hilo al media.aaudio
servicio (ubicado en frameworks/av/media/libaaudio/src/client/AudioStreamInternal.cpp ):
aaudio_result_t AudioStreamInternal::registerThread() {
if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
ALOGW("%s() mServiceStreamHandle invalid", __func__);
return AAUDIO_ERROR_INVALID_STATE;
}
return mServiceInterface.registerAudioThread(mServiceStreamHandle,
gettid(),
getPeriodNanoseconds());
}
Aunque AudioStream
la duración del período se pasa al solicitar la creación de subprocesos, la ejecución periódica no se AudioStream
maneja en .
En el modo sincrónico de transferencia de datos de flujo de audio, la aplicación escribe datos en el flujo de audio AAudio para reproducir los datos de audio. La implementación de la interfaz para escribir datos de audio en la transmisión de audio (ubicada en frameworks/av/media/libaaudio/src/core/AAudioAudio.cpp ) es la siguiente:
AAUDIO_API aaudio_result_t AAudioStream_write(AAudioStream* stream,
const void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds)
{
AudioStream *audioStream = convertAAudioStreamToAudioStream(stream);
if (buffer == nullptr) {
return AAUDIO_ERROR_NULL;
}
// Don't allow writes when playing with a callback.
if (audioStream->isDataCallbackActive()) {
// A developer requested this warning because it would have saved lots of debugging.
ALOGW("%s() - Cannot write to a callback stream when running.", __func__);
return AAUDIO_ERROR_INVALID_STATE;
}
if (numFrames < 0) {
return AAUDIO_ERROR_ILLEGAL_ARGUMENT;
} else if (numFrames == 0) {
return 0;
}
aaudio_result_t result = audioStream->write(buffer, numFrames, timeoutNanoseconds);
return result;
}
Esta función verifica los parámetros y si la transmisión de audio está funcionando en modo síncrono, y luego write()
escribe los datos de audio mediante la operación del objeto de transmisión de audio, la misma operación utilizada en el bucle de devolución de llamada del modo asíncrono. write()
La operación se implementa mediante una secuencia de audio específica. Para la secuencia de reproducción de audio, AudioStreamInternalPlay::write()
la función se define (ubicada en frameworks/av/media/libaaudio/src/client/AudioStreamInternalPlay.cpp ) de la siguiente manera:
aaudio_result_t AudioStreamInternalPlay::write(const void *buffer, int32_t numFrames,
int64_t timeoutNanoseconds) {
return processData((void *)buffer, numFrames, timeoutNanoseconds);
}
AudioStreamInternalPlay::write()
Ejecutando AudioStreamInternal::processData()
el bucle de procesamiento de datos y realizando la espera necesaria (ubicada en frameworks/av/media/libaaudio/src/client/AudioStreamInternal.cpp ) hasta que se hayan procesado todos los datos o haya expirado el tiempo máximo de espera:
aaudio_result_t AudioStreamInternal::processData(void *buffer, int32_t numFrames,
int64_t timeoutNanoseconds)
{
const char * traceName = "aaProc";
const char * fifoName = "aaRdy";
ATRACE_BEGIN(traceName);
if (ATRACE_ENABLED()) {
int32_t fullFrames = mAudioEndpoint->getFullFramesAvailable();
ATRACE_INT(fifoName, fullFrames);
}
aaudio_result_t result = AAUDIO_OK;
int32_t loopCount = 0;
uint8_t* audioData = (uint8_t*)buffer;
int64_t currentTimeNanos = AudioClock::getNanoseconds();
const int64_t entryTimeNanos = currentTimeNanos;
const int64_t deadlineNanos = currentTimeNanos + timeoutNanoseconds;
int32_t framesLeft = numFrames;
// Loop until all the data has been processed or until a timeout occurs.
while (framesLeft > 0) {
// The call to processDataNow() will not block. It will just process as much as it can.
int64_t wakeTimeNanos = 0;
aaudio_result_t framesProcessed = processDataNow(audioData, framesLeft,
currentTimeNanos, &wakeTimeNanos);
if (framesProcessed < 0) {
result = framesProcessed;
break;
}
framesLeft -= (int32_t) framesProcessed;
audioData += framesProcessed * getBytesPerFrame();
// Should we block?
if (timeoutNanoseconds == 0) {
break; // don't block
} else if (wakeTimeNanos != 0) {
if (!mAudioEndpoint->isFreeRunning()) {
// If there is software on the other end of the FIFO then it may get delayed.
// So wake up just a little after we expect it to be ready.
wakeTimeNanos += mWakeupDelayNanos;
}
currentTimeNanos = AudioClock::getNanoseconds();
int64_t earliestWakeTime = currentTimeNanos + mMinimumSleepNanos;
// Guarantee a minimum sleep time.
if (wakeTimeNanos < earliestWakeTime) {
wakeTimeNanos = earliestWakeTime;
}
if (wakeTimeNanos > deadlineNanos) {
// If we time out, just return the framesWritten so far.
// TODO remove after we fix the deadline bug
ALOGW("processData(): entered at %lld nanos, currently %lld",
(long long) entryTimeNanos, (long long) currentTimeNanos);
ALOGW("processData(): TIMEOUT after %lld nanos",
(long long) timeoutNanoseconds);
ALOGW("processData(): wakeTime = %lld, deadline = %lld nanos",
(long long) wakeTimeNanos, (long long) deadlineNanos);
ALOGW("processData(): past deadline by %d micros",
(int)((wakeTimeNanos - deadlineNanos) / AAUDIO_NANOS_PER_MICROSECOND));
mClockModel.dump();
mAudioEndpoint->dump();
break;
}
if (ATRACE_ENABLED()) {
int32_t fullFrames = mAudioEndpoint->getFullFramesAvailable();
ATRACE_INT(fifoName, fullFrames);
int64_t sleepForNanos = wakeTimeNanos - currentTimeNanos;
ATRACE_INT("aaSlpNs", (int32_t)sleepForNanos);
}
AudioClock::sleepUntilNanoTime(wakeTimeNanos);
currentTimeNanos = AudioClock::getNanoseconds();
}
}
if (ATRACE_ENABLED()) {
int32_t fullFrames = mAudioEndpoint->getFullFramesAvailable();
ATRACE_INT(fifoName, fullFrames);
}
// return error or framesProcessed
(void) loopCount;
ATRACE_END();
return (result < 0) ? result : numFrames - framesLeft;
}
AudioStreamInternal::processData()
El bucle de procesamiento de datos procesa datos a través de sus subclases processDataNow()
. Para reproducir transmisiones de audio, processDataNow()
realiza la acción de escribir datos y para recopilar transmisiones de audio, processDataNow()
realiza la acción de leer datos. processDataNow()
La ejecución no se bloquea, simplemente procesa la mayor cantidad de datos posible.
La definición de función para reproducir transmisiones de audio AudioStreamInternalPlay::processDataNow()
(ubicada en frameworks/av/media/libaaudio/src/client/AudioStreamInternalPlay.cpp ) es la siguiente:
aaudio_result_t AudioStreamInternalPlay::processDataNow(void *buffer, int32_t numFrames,
int64_t currentNanoTime, int64_t *wakeTimePtr) {
aaudio_result_t result = processCommands();
if (result != AAUDIO_OK) {
return result;
}
const char *traceName = "aaWrNow";
ATRACE_BEGIN(traceName);
if (mClockModel.isStarting()) {
// Still haven't got any timestamps from server.
// Keep waiting until we get some valid timestamps then start writing to the
// current buffer position.
ALOGV("%s() wait for valid timestamps", __func__);
// Sleep very briefly and hope we get a timestamp soon.
*wakeTimePtr = currentNanoTime + (2000 * AAUDIO_NANOS_PER_MICROSECOND);
ATRACE_END();
return 0;
}
// If we have gotten this far then we have at least one timestamp from server.
// If a DMA channel or DSP is reading the other end then we have to update the readCounter.
if (mAudioEndpoint->isFreeRunning()) {
// Update data queue based on the timing model.
int64_t estimatedReadCounter = mClockModel.convertTimeToPosition(currentNanoTime);
// ALOGD("AudioStreamInternal::processDataNow() - estimatedReadCounter = %d", (int)estimatedReadCounter);
mAudioEndpoint->setDataReadCounter(estimatedReadCounter);
}
if (mNeedCatchUp.isRequested()) {
// Catch an MMAP pointer that is already advancing.
// This will avoid initial underruns caused by a slow cold start.
// We add a one burst margin in case the DSP advances before we can write the data.
// This can help prevent the beginning of the stream from being skipped.
advanceClientToMatchServerPosition(getFramesPerBurst());
mNeedCatchUp.acknowledge();
}
// If the read index passed the write index then consider it an underrun.
// For shared streams, the xRunCount is passed up from the service.
if (mAudioEndpoint->isFreeRunning() && mAudioEndpoint->getFullFramesAvailable() < 0) {
mXRunCount++;
if (ATRACE_ENABLED()) {
ATRACE_INT("aaUnderRuns", mXRunCount);
}
}
// Write some data to the buffer.
//ALOGD("AudioStreamInternal::processDataNow() - writeNowWithConversion(%d)", numFrames);
int32_t framesWritten = writeNowWithConversion(buffer, numFrames);
//ALOGD("AudioStreamInternal::processDataNow() - tried to write %d frames, wrote %d",
// numFrames, framesWritten);
if (ATRACE_ENABLED()) {
ATRACE_INT("aaWrote", framesWritten);
}
// Sleep if there is too much data in the buffer.
// Calculate an ideal time to wake up.
if (wakeTimePtr != nullptr
&& (mAudioEndpoint->getFullFramesAvailable() >= getBufferSize())) {
// By default wake up a few milliseconds from now. // TODO review
int64_t wakeTime = currentNanoTime + (1 * AAUDIO_NANOS_PER_MILLISECOND);
aaudio_stream_state_t state = getState();
//ALOGD("AudioStreamInternal::processDataNow() - wakeTime based on %s",
// AAudio_convertStreamStateToText(state));
switch (state) {
case AAUDIO_STREAM_STATE_OPEN:
case AAUDIO_STREAM_STATE_STARTING:
if (framesWritten != 0) {
// Don't wait to write more data. Just prime the buffer.
wakeTime = currentNanoTime;
}
break;
case AAUDIO_STREAM_STATE_STARTED:
{
// Sleep until the readCounter catches up and we only have
// the getBufferSize() frames of data sitting in the buffer.
int64_t nextReadPosition = mAudioEndpoint->getDataWriteCounter() - getBufferSize();
wakeTime = mClockModel.convertPositionToTime(nextReadPosition);
}
break;
default:
break;
}
*wakeTimePtr = wakeTime;
}
ATRACE_END();
return framesWritten;
}
Esta función primero llama para processCommands()
procesar media.aaudio
el comando enviado por el servicio, luego ejecuta principalmente writeNowWithConversion()
la escritura de datos de audio y luego calcula el tiempo de espera y regresa. AudioStreamInternalPlay::writeNowWithConversion()
La función toma una parte de la memoria del quince que presentamos anteriormente, procesa los datos de audio, escribe los datos de audio procesados en el área de memoria y mueve el contador de escritura:
aaudio_result_t AudioStreamInternalPlay::writeNowWithConversion(const void *buffer,
int32_t numFrames) {
WrappingBuffer wrappingBuffer;
uint8_t *byteBuffer = (uint8_t *) buffer;
int32_t framesLeft = numFrames;
mAudioEndpoint->getEmptyFramesAvailable(&wrappingBuffer);
// Write data in one or two parts.
int partIndex = 0;
while (framesLeft > 0 && partIndex < WrappingBuffer::SIZE) {
int32_t framesToWrite = framesLeft;
int32_t framesAvailable = wrappingBuffer.numFrames[partIndex];
if (framesAvailable > 0) {
if (framesToWrite > framesAvailable) {
framesToWrite = framesAvailable;
}
int32_t numBytes = getBytesPerFrame() * framesToWrite;
mFlowGraph.process((void *)byteBuffer,
wrappingBuffer.data[partIndex],
framesToWrite);
byteBuffer += numBytes;
framesLeft -= framesToWrite;
} else {
break;
}
partIndex++;
}
int32_t framesWritten = numFrames - framesLeft;
mAudioEndpoint->advanceWriteIndex(framesWritten);
return framesWritten;
}
En el modo sincrónico de transferencia de datos de flujo de audio, la aplicación lee datos del flujo de audio de AAudio para obtener los datos de audio recopilados. La implementación de la interfaz para leer datos de la transmisión de audio (ubicada en frameworks/av/media/libaaudio/src/core/AAudioAudio.cpp ) es la siguiente:
AAUDIO_API aaudio_result_t AAudioStream_read(AAudioStream* stream,
void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds)
{
AudioStream *audioStream = convertAAudioStreamToAudioStream(stream);
if (buffer == nullptr) {
return AAUDIO_ERROR_NULL;
}
if (numFrames < 0) {
return AAUDIO_ERROR_ILLEGAL_ARGUMENT;
} else if (numFrames == 0) {
return 0;
}
aaudio_result_t result = audioStream->read(buffer, numFrames, timeoutNanoseconds);
return result;
}
Esta función verifica los parámetros y luego read()
lee los datos de audio mediante la operación del objeto de flujo de audio, la misma operación utilizada en el bucle de devolución de llamada en modo asíncrono. read()
La operación se implementa mediante una secuencia de audio específica. Para la secuencia de recopilación de audio, AudioStreamInternalCapture::read()
la función se define (ubicada en frameworks/av/media/libaaudio/src/client/AudioStreamInternalCapture.cpp ) de la siguiente manera:
aaudio_result_t AudioStreamInternalCapture::read(void *buffer, int32_t numFrames,
int64_t timeoutNanoseconds)
{
return processData(buffer, numFrames, timeoutNanoseconds);
}
Al igual que AudioStreamInternalPlay::write()
aquí, el ciclo de procesamiento de datos también se ejecuta AudioStreamInternal::processData()
y se realiza la espera necesaria hasta que se procesen todos los datos o se alcance el tiempo máximo de espera. AudioStreamInternalCapture
La processDataNow()
definición (ubicada en frameworks/av/media/libaaudio/src/client/AudioStreamInternalCapture.cpp ) es la siguiente:
// Read as much data as we can without blocking.
aaudio_result_t AudioStreamInternalCapture::processDataNow(void *buffer, int32_t numFrames,
int64_t currentNanoTime, int64_t *wakeTimePtr) {
aaudio_result_t result = processCommands();
if (result != AAUDIO_OK) {
return result;
}
const char *traceName = "aaRdNow";
ATRACE_BEGIN(traceName);
if (mClockModel.isStarting()) {
// Still haven't got any timestamps from server.
// Keep waiting until we get some valid timestamps then start writing to the
// current buffer position.
ALOGD("processDataNow() wait for valid timestamps");
// Sleep very briefly and hope we get a timestamp soon.
*wakeTimePtr = currentNanoTime + (2000 * AAUDIO_NANOS_PER_MICROSECOND);
ATRACE_END();
return 0;
}
// If we have gotten this far then we have at least one timestamp from server.
if (mAudioEndpoint->isFreeRunning()) {
//ALOGD("AudioStreamInternalCapture::processDataNow() - update remote counter");
// Update data queue based on the timing model.
// Jitter in the DSP can cause late writes to the FIFO.
// This might be caused by resampling.
// We want to read the FIFO after the latest possible time
// that the DSP could have written the data.
int64_t estimatedRemoteCounter = mClockModel.convertLatestTimeToPosition(currentNanoTime);
// TODO refactor, maybe use setRemoteCounter()
mAudioEndpoint->setDataWriteCounter(estimatedRemoteCounter);
}
// This code assumes that we have already received valid timestamps.
if (mNeedCatchUp.isRequested()) {
// Catch an MMAP pointer that is already advancing.
// This will avoid initial underruns caused by a slow cold start.
advanceClientToMatchServerPosition();
mNeedCatchUp.acknowledge();
}
// If the capture buffer is full beyond capacity then consider it an overrun.
// For shared streams, the xRunCount is passed up from the service.
if (mAudioEndpoint->isFreeRunning()
&& mAudioEndpoint->getFullFramesAvailable() > mAudioEndpoint->getBufferCapacityInFrames()) {
mXRunCount++;
if (ATRACE_ENABLED()) {
ATRACE_INT("aaOverRuns", mXRunCount);
}
}
// Read some data from the buffer.
//ALOGD("AudioStreamInternalCapture::processDataNow() - readNowWithConversion(%d)", numFrames);
int32_t framesProcessed = readNowWithConversion(buffer, numFrames);
//ALOGD("AudioStreamInternalCapture::processDataNow() - tried to read %d frames, read %d",
// numFrames, framesProcessed);
if (ATRACE_ENABLED()) {
ATRACE_INT("aaRead", framesProcessed);
}
// Calculate an ideal time to wake up.
if (wakeTimePtr != nullptr && framesProcessed >= 0) {
// By default wake up a few milliseconds from now. // TODO review
int64_t wakeTime = currentNanoTime + (1 * AAUDIO_NANOS_PER_MILLISECOND);
aaudio_stream_state_t state = getState();
//ALOGD("AudioStreamInternalCapture::processDataNow() - wakeTime based on %s",
// AAudio_convertStreamStateToText(state));
switch (state) {
case AAUDIO_STREAM_STATE_OPEN:
case AAUDIO_STREAM_STATE_STARTING:
break;
case AAUDIO_STREAM_STATE_STARTED:
{
// When do we expect the next write burst to occur?
// Calculate frame position based off of the readCounter because
// the writeCounter might have just advanced in the background,
// causing us to sleep until a later burst.
int64_t nextPosition = mAudioEndpoint->getDataReadCounter() + getFramesPerBurst();
wakeTime = mClockModel.convertPositionToLatestTime(nextPosition);
}
break;
default:
break;
}
*wakeTimePtr = wakeTime;
}
ATRACE_END();
return framesProcessed;
}
Esta función también llama primero al comando processCommands()
de procesamiento media.aaudio
enviado por el servicio, y luego ejecuta principalmente readNowWithConversion()
la lectura de datos de audio, luego calcula el tiempo de espera y regresa. AudioStreamInternalCapture::readNowWithConversion()
La función toma una parte de la memoria del quince que presentamos anteriormente, copia los datos de audio recopilados y mueve el contador de lectura:
aaudio_result_t AudioStreamInternalCapture::readNowWithConversion(void *buffer,
int32_t numFrames) {
// ALOGD("readNowWithConversion(%p, %d)",
// buffer, numFrames);
WrappingBuffer wrappingBuffer;
uint8_t *destination = (uint8_t *) buffer;
int32_t framesLeft = numFrames;
mAudioEndpoint->getFullFramesAvailable(&wrappingBuffer);
// Read data in one or two parts.
for (int partIndex = 0; framesLeft > 0 && partIndex < WrappingBuffer::SIZE; partIndex++) {
int32_t framesToProcess = framesLeft;
const int32_t framesAvailable = wrappingBuffer.numFrames[partIndex];
if (framesAvailable <= 0) break;
if (framesToProcess > framesAvailable) {
framesToProcess = framesAvailable;
}
const int32_t numBytes = getBytesPerFrame() * framesToProcess;
const int32_t numSamples = framesToProcess * getSamplesPerFrame();
const audio_format_t sourceFormat = getDeviceFormat();
const audio_format_t destinationFormat = getFormat();
memcpy_by_audio_format(destination, destinationFormat,
wrappingBuffer.data[partIndex], sourceFormat, numSamples);
destination += numBytes;
framesLeft -= framesToProcess;
}
int32_t framesProcessed = numFrames - framesLeft;
mAudioEndpoint->advanceReadIndex(framesProcessed);
//ALOGD("readNowWithConversion() returns %d", framesProcessed);
return framesProcessed;
}
Las operaciones clave de la transmisión de audio se implementan más o menos así.
Hecho.