请添加图片描述
Android AAudio详解
一、AAudio简介
AAudio 是在 Android O 版本中引入的全新 Android C API。此 API 专为需要低延迟的高性能音频应用而设计。应用通过读取数据并将数据写入流来与 AAudio 进行通信。
AAudio的mmap模式就是直接将内核alsa 驱动使用的一块内存映射到用户态,这样应用可以将数据直达驱动,节省了中途的拷贝操作,而且读写是基于NOIRQ的,也就是不需要没有用中断机制,用单独的一个计时器驱动读写数据。流程图如下:
音频流具有共享模式:
-
AAUDIO_SHARING_MODE_EXCLUSIVE(独占模式):表示该流独占一个音频设备。如果该音频设备已经在使用中,那么该流可能无法对其进行独占访问。独占流得延时较短,但连接断开的可能性也较大,如果不再需要独占流,应尽快予以关闭,以便其他应用访问该设备。独占流可以最大限度缩短延迟时间。
-
AAUDIO_SHARING_MODE_SHARED:允许AAudio混合音频,也就是可能和其他流公用同一个设备,AAudio会将分配给同一设备的所有共享流混合。
默认情况下,共享模式为 SHARED
。
音频流具有性能模式:
每个 AAudioStream 都具有性能模式,而这对应用行为的影响很大。共有三种模式:
-
AAUDIO_PERFORMANCE_MODE_NONE 是默认模式。这种模式使用在延迟时间与节能之间取得平衡的基本流。
-
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY 使用较小的缓冲区和经优化的数据路径,以减少延迟时间。
-
AAUDIO_PERFORMANCE_MODE_POWER_SAVING 使用较大的内部缓冲区,以及以延迟时间为代价换取节能优势的数据路径。
二、创建音频流
AAudio 库遵循构建器设计模式,并提供 AAudioStreamBuilder。使用与流参数对应的构建器函数,在构建器中设置音频流配置。配置 AAudioStreamBuilder 之后,用其创建流:
AAUDIO_API aaudio_result_t AAudioStreamBuilder_openStream(AAudioStreamBuilder* builder,
AAudioStream** streamPtr)
{
AudioStream *audioStream = nullptr;
aaudio_stream_id_t id = 0;
ALOGI("%s() called ----------------------------------------", __func__);
AudioStreamBuilder *streamBuilder = COMMON_GET_FROM_BUILDER_OR_RETURN(streamPtr);
aaudio_result_t result = streamBuilder->build(&audioStream);
if (result == AAUDIO_OK) {
// 注册audiostream, 主要是针对播放,这样就可以被系统音量统一控制。
audioStream->registerPlayerBase();
*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;
}
主要是2件事,一个是负责构建AudioStream,一个是负责注册,先看下构建过程。
aaudio_result_t AudioStreamBuilder::build(AudioStream** streamPtr) {
...
std::vector<AudioMMapPolicyInfo> policyInfos;
aaudio_policy_t mmapPolicy = AudioGlobal_getMMapPolicy();
if(android::AudioSystem::getMmapPolicyInfo(
AudioMMapPolicyType::DEFAULT, &policyInfos) == NO_ERROR) {
audio_policy_t systemMmapPolicy = AAudio_getAAudioPolicy(policyInfos);
if(mmapPolicy == AAUDIO_POLICY_ALWAYS && systemMmapPolicy == AAUDIO_POLICY_ALWAYS)
return AAUDIO_SERVICE_NO_SERVICE;
if(mmapPolicy == AAUDIO_UNSPECIFIED && systemMmapPolicy == AAUDIO_POLICY_NEVER)
mmapPolicy = systemMmapPolicy;
} else {
if(mmapPolicy == AAUDIO_POLICY_NEVER)
return AAUDIO_SERVICE_NO_SERVICE;
mmapPolicy = AAUDIO_POLICY_NEVER
}
int32_t mapExclusivePolicy = AAUDIO_UNSPECIFIED;
if(android::AudioSystem::getMmapPolicyInfo(
AudioMMapPolicyType::DEFAULT, &policyInfos) == NO_ERROR) {
mmapExclusivePolicy = AAudio_getAAudioPolicy(policyInfos);
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)) {
sharingMode = AAUDIO_SHARING_MODE_SHARED;
setSharingMode(sharingMode);
}
bool allowMMap = mmapPolicy != AAUDIO_POLICY_NEVER;
bool allowLegacy = mmapPolicy != AAUDIO_POLICY_ALWAYS;
// 非低延时不支持mmap
if (getPerformanceMode() != AAUDIO_PERFORMANCE_MODE_LOW_LATENCY) {
allowMMap = false;
}
// SessionID and Effects are only supported in Legacy mode.
if (getSessionId() != AAUDIO_SESSION_ID_NONE) {
allowMMap = false;
}
if (!allowMMap && !allowLegacy) {
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); // Camera和通话场景,设置隐私标记
}
} 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) {
result = builder_createStream(getDirection(), sharingMode,
false, audioStream);
if (result == AAUDIO_OK) {
result = audioStream->open(*this);
}
}
}
if (result == AAUDIO_OK) {
audioStream->logOpen();
*streamPtr = startUsingStream(audioStream);
} // else audioStream will go out of scope and be deleted
}
return result;
}
这儿主要是使用builder_createStream 创建AAudioSream,一个是执行AAudioStream的open方法:
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;
}
如果是使用非Mmap,那么走的就是AudioStreamTrack,实际上走的是AudioTrack。而AAudioStream的open 方法就是创建AudioRecord对象,并且注册设置参数,这时候就会在AudioFlinger中也创建一个对应的AudioRecord对象,并分配对应的缓存。
aaudio_result_t AudioStreamInternal::open(const AudioStreamBuilder &builder) {
aaudio_result_t result = AAUDIO_OK;
int32_t framesPerBurst;
int32_t framesPerHardwareBurst;
AAudioStreamRequest request;
AAudioStreamConfiguration configurationOutput;
// Copy requested parameters to the stream.
result = AudioStream::open(builder);
const int32_t burstMinMicros = AAudioProperty_getHardwareBurstMinMicros();
int32_t burstMicros = 0;
// We have to do volume scaling. So we prefer FLOAT format.
if (getFormat() == AUDIO_FORMAT_DEFAULT) {
setFormat(AUDIO_FORMAT_PCM_FLOAT);
}
// Request FLOAT for the shared mixer.
request.getConfiguration().setFormat(AUDIO_FORMAT_PCM_FLOAT);
// Build the request to send to the server.
request.setUserId(getuid());
request.setProcessId(getpid());
request.setSharingModeMatchRequired(isSharingModeMatchRequired());
request.setInService(isInService());
request.getConfiguration().setDeviceId(getDeviceId());
request.getConfiguration().setSampleRate(getSampleRate());
request.getConfiguration().setSamplesPerFrame(getSamplesPerFrame());
request.getConfiguration().setDirection(getDirection());
request.getConfiguration().setSharingMode(getSharingMode());
request.getConfiguration().setUsage(getUsage());
request.getConfiguration().setContentType(getContentType());
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); // 1: 打开流
if (mServiceStreamHandle < 0
&& request.getConfiguration().getSamplesPerFrame() == 1 // 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().setSamplesPerFrame(2); // 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.
mMetricsId = std::string(AMEDIAMETRICS_KEY_PREFIX_AUDIO_STREAM)
+ std::to_string(mServiceStreamHandle);
result = configurationOutput.validate();
if (result != AAUDIO_OK) {
goto error;
}
// Save results of the open.
if (getSamplesPerFrame() == AAUDIO_UNSPECIFIED) {
setSamplesPerFrame(configurationOutput.getSamplesPerFrame());
}
mDeviceChannelCount = configurationOutput.getSamplesPerFrame();
setSampleRate(configurationOutput.getSampleRate());
setDeviceId(configurationOutput.getDeviceId());
setSessionId(configurationOutput.getSessionId());
setSharingMode(configurationOutput.getSharingMode());
setUsage(configurationOutput.getUsage());
setContentType(configurationOutput.getContentType());
setInputPreset(configurationOutput.getInputPreset());
// Save device format so we can do format conversion and volume scaling together.
setDeviceFormat(configurationOutput.getFormat());
result = mServiceInterface.getStreamDescription(mServiceStreamHandle, mEndPointParcelable); // 2. 获取共享内存
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;
}
if((result != configureDataInfomation(builder.getFramesPerDataCallback())) != AAUDIO_OK) {
goto error;
}
setState(AAUDIO_STREAM_STATE_OPEN);
return result;
error:
releaseCloseFinal();
return result;
}
在这儿就可以看到将业务设置的参数全部发给AAudioservice,由AAudioservice来处理。 接下来看下AAudioservice 打开mmap流的过程。
Status AAudioService::openStream(const StreamRequest &_request, StreamParameters* _paramsOut,
int32_t *_aidl_return) {
static_assert(std::is_same_v<aaudio_result_t, std::decay_t<typeof(*_aidl_return)>>);
// Create wrapper objects for simple usage of the parcelables.
const AAudioStreamRequest request(_request);
AAudioStreamConfiguration paramsOut;
// A lock in is used to order the opening of endpoints when an
// EXCLUSIVE endpoint is stolen. We want the order to be:
// 1) Thread A opens exclusive MMAP endpoint
// 2) Thread B wants to open an exclusive MMAP endpoint so it steals the one from A
// under this lock.
// 3) Thread B opens a shared MMAP endpoint.
// 4) Thread A can then get the lock and also open a shared stream.
// Without the lock. Thread A might sneak in and reallocate an exclusive stream
// before B can open the shared stream.
std::unique_lock<std::recursive_mutex> lock(mOpenLock);
aaudio_result_t result = AAUDIO_OK;
sp<AAudioServiceStreamBase> serviceStream;
const AAudioStreamConfiguration &configurationInput = request.getConstantConfiguration();
bool sharingModeMatchRequired = request.isSharingModeMatchRequired();
aaudio_sharing_mode_t sharingMode = configurationInput.getSharingMode();
// Enforce limit on client processes.
AttributionSourceState attributionSource = request.getAttributionSource();
pid_t pid = IPCThreadState::self()->getCallingPid();
attributionSource.pid = VALUE_OR_RETURN_ILLEGAL_ARG_STATUS(
legacy2aidl_pid_t_int32_t(pid));
attributionSource.uid = VALUE_OR_RETURN_ILLEGAL_ARG_STATUS(
legacy2aidl_uid_t_int32_t(IPCThreadState::self()->getCallingUid()));
attributionSource.token = sp<BBinder>::make();
if (attributionSource.pid != mAudioClient.attributionSource.pid) {
int32_t count = AAudioClientTracker::getInstance().getStreamCount(pid);
if (count >= MAX_STREAMS_PER_PROCESS) {
ALOGE("openStream(): exceeded max streams per process %d >= %d",
count, MAX_STREAMS_PER_PROCESS);
AIDL_RETURN(AAUDIO_ERROR_UNAVAILABLE);
}
}
if (sharingMode != AAUDIO_SHARING_MODE_EXCLUSIVE && sharingMode != AAUDIO_SHARING_MODE_SHARED) {
ALOGE("openStream(): unrecognized sharing mode = %d", sharingMode);
AIDL_RETURN(AAUDIO_ERROR_ILLEGAL_ARGUMENT);
}
if (sharingMode == AAUDIO_SHARING_MODE_EXCLUSIVE
&& AAudioClientTracker::getInstance().isExclusiveEnabled(pid)) {
// only trust audioserver for in service indication
bool inService = false;
if (isCallerInService()) {
inService = request.isInService();
}
serviceStream = new AAudioServiceStreamMMAP(*this, inService);
result = serviceStream->open(request);
if (result != AAUDIO_OK) {
// Clear it so we can possibly fall back to using a shared stream.
ALOGW("openStream(), could not open in EXCLUSIVE mode");
serviceStream.clear();
}
}
// Try SHARED if SHARED requested or if EXCLUSIVE failed.
if (sharingMode == AAUDIO_SHARING_MODE_SHARED) {
serviceStream = new AAudioServiceStreamShared(*this);
result = serviceStream->open(request);
} else if (serviceStream.get() == nullptr && !sharingModeMatchRequired) {
aaudio::AAudioStreamRequest modifiedRequest = request;
// Overwrite the original EXCLUSIVE mode with SHARED.
modifiedRequest.getConfiguration().setSharingMode(AAUDIO_SHARING_MODE_SHARED);
serviceStream = new AAudioServiceStreamShared(*this);
result = serviceStream->open(modifiedRequest);
}
if (result != AAUDIO_OK) {
serviceStream.clear();
AIDL_RETURN(result);
} else {
aaudio_handle_t handle = mStreamTracker.addStreamForHandle(serviceStream.get());
serviceStream->setHandle(handle);
AAudioClientTracker::getInstance().registerClientStream(pid, serviceStream);
paramsOut.copyFrom(*serviceStream);
*_paramsOut = std::move(paramsOut).parcelable();
// Log open in MediaMetrics after we have the handle because we need the handle to
// create the metrics ID.
serviceStream->logOpen(handle);
ALOGV("%s(): return handle = 0x%08X", __func__, handle);
AIDL_RETURN(handle);
}
}
继续看下AAudioServiceStreamMMAP的流程
aaudio_result_t AAudioServiceStreamMMAP::open(const aaudio::AAudioStreamRequest &request) {
sp<AAudioServiceStreamMMAP> keep(this);
if (request.getConstantConfiguration().getSharingMode() != AAUDIO_SHARING_MODE_EXCLUSIVE) {
ALOGE("%s() sharingMode mismatch %d", __func__,
request.getConstantConfiguration().getSharingMode());
return AAUDIO_ERROR_INTERNAL;
}
aaudio_result_t result = AAudioServiceStreamBase::open(request);
if (result != AAUDIO_OK) {
return result;
}
sp<AAudioServiceEndpoint> endpoint = mServiceEndpointWeak.promote();
if (endpoint == nullptr) {
ALOGE("%s() has no endpoint", __func__);
return AAUDIO_ERROR_INVALID_STATE;
}
result = endpoint->registerStream(keep);
if (result != AAUDIO_OK) {
return result;
}
setState(AAUDIO_STREAM_STATE_OPEN);
return AAUDIO_OK;
}
使用了base的open,继续看下
aaudio_result_t AAudioServiceStreamBase::open(const aaudio::AAudioStreamRequest &request) {
AAudioEndpointManager &mEndpointManager = AAudioEndpointManager::getInstance();
aaudio_result_t result = AAUDIO_OK;
mMmaplient.attributionSource = request.getAttributionSource();
mMmaplient.clientUid = request.getUserId();
mMmapClient.clientPid = request.getProcessId();
// Limit scope of lock to avoid recursive lock in close().
{
std::lock_guard<std::mutex> lock(mUpMessageQueueLock);
if (mUpMessageQueue != nullptr) {
ALOGE("%s() called twice", __func__);
return AAUDIO_ERROR_INVALID_STATE;
}
mUpMessageQueue = new SharedRingBuffer(); // 分配共享内存,这个内存是支持进程间共享的
result = mUpMessageQueue->allocate(sizeof(AAudioServiceMessage),
QUEUE_UP_CAPACITY_COMMANDS);
if (result != AAUDIO_OK) {
goto error;
}
// This is not protected by a lock because the stream cannot be
// referenced until the service returns a handle to the client.
// So only one thread can open a stream.
mServiceEndpoint = mEndpointManager.openEndpoint(mAudioService,
request);
if (mServiceEndpoint == nullptr) {
result = AAUDIO_ERROR_UNAVAILABLE;
goto error;
}
// Save a weak pointer that we will use to access the endpoint.
mServiceEndpointWeak = mServiceEndpoint;
mFramesPerBurst = mServiceEndpoint->getFramesPerBurst();
copyFrom(*mServiceEndpoint);
}
return result;
error:
close();
return result;
}
这儿调用的是openEndpoint:
sp<AAudioServiceEndpoint> AAudioEndpointManager::openEndpoint(AAudioService &audioService,
const aaudio::AAudioStreamRequest &request) {
if (request.getConstantConfiguration().getSharingMode() == AAUDIO_SHARING_MODE_EXCLUSIVE) {
sp<AAudioServiceEndpoint> endpointToSteal;
sp<AAudioServiceEndpoint> foundEndpoint =
openExclusiveEndpoint(audioService, request, endpointToSteal);
if (endpointToSteal.get()) {
endpointToSteal->releaseRegisteredStreams(); // free the MMAP resource
}
return foundEndpoint;
} else {
return openSharedEndpoint(audioService, request);
}
}
继续看下openExclusiveEndpoint
sp<AAudioServiceEndpoint> AAudioEndpointManager::openExclusiveEndpoint(
AAudioService &aaudioService,
const aaudio::AAudioStreamRequest &request,
sp<AAudioServiceEndpoint> &endpointToSteal) {
std::lock_guard<std::mutex> lock(mExclusiveLock);
const AAudioStreamConfiguration &configuration = request.getConstantConfiguration();
// Try to find an existing endpoint.
sp<AAudioServiceEndpoint> endpoint = findExclusiveEndpoint_l(configuration); // 从cache中找对应的endPoint
// If we find an existing one then this one cannot be exclusive.
if (endpoint.get() != nullptr) {
if (kStealingEnabled
&& !endpoint->isForSharing() // not currently SHARED
&& !request.isSharingModeMatchRequired()) { // app did not request a shared stream
ALOGD("%s() endpoint in EXCLUSIVE use. Steal it!", __func__);
mExclusiveStolenCount++;
// Prevent this process from getting another EXCLUSIVE stream.
// This will prevent two clients from colliding after a DISCONNECTION
// when they both try to open an exclusive stream at the same time.
// That can result in a stream getting disconnected between the OPEN
// and START calls. This will help preserve app compatibility.
// An app can avoid having this happen by closing their streams when
// the app is paused.
AAudioClientTracker::getInstance().setExclusiveEnabled(request.getProcessId(), false);
endpointToSteal = endpoint; // return it to caller
}
return nullptr;
} else {
sp<AAudioServiceEndpointMMAP> endpointMMap = new AAudioServiceEndpointMMAP(aaudioService);
ALOGV("%s(), no match so try to open MMAP %p for dev %d",
__func__, endpointMMap.get(), configuration.getDeviceId());
endpoint = endpointMMap;
aaudio_result_t result = endpoint->open(request);
if (result != AAUDIO_OK) {
endpoint.clear();
} else {
mExclusiveStreams.push_back(endpointMMap);
mExclusiveOpenCount++;
}
}
if (endpoint.get() != nullptr) {
// Increment the reference count under this lock.
endpoint->setOpenCount(endpoint->getOpenCount() + 1);
endpoint->setForSharing(request.isSharingModeMatchRequired());
}
return endpoint;
}
这时候创建了一个AAudioServiceEndpointMMAP,然后调用了open,继续往下看:
aaudio_result_t AAudioServiceEndpointMMAP::open(const aaudio::AAudioStreamRequest &request) {
aaudio_result_t result = AAUDIO_OK;
mAudioDataWrapper = std::make_unique<SharedMemoryWrapper>();
copyFrom(request.getConstantConfiguration());
mRequestedDeviceId = getDeviceId();
mMmapClient.attributionSource = request.getAttributionSource();
// TODO b/182392769: use attribution source util
mMmapClient.attributionSource.uid = VALUE_OR_FATAL(
legacy2aidl_uid_t_int32_t(IPCThreadState::self()->getCallingUid()));
mMmapClient.attributionSource.pid = VALUE_OR_FATAL(
legacy2aidl_pid_t_int32_t(IPCThreadState::self()->getCallingPid()));
audio_format_t audioFormat = getFormat();
std::set<audio_format_t> formatsTried;
while(true){
if(formatsTried.find(audioFormat) != formatsTried.end())
break;
formatsTried.insert(audioFormat);
audio_format_t nextFormatToTry = AUDIO_FORMAT_DEFAULT;
result = openWithFormat(audioFormat, &nextFormatToTry);
if(result != AAUDIO_ERROR_UNAVAILABLE)
break;
nextFormatToTry = getNextFormatToTry(audioFormat, nextFormatToTry);
audioFormat = nextFormatToTry;
}
return result;
}
接着看openWithFormat
aaudio_result_t AAudioServiceEndpointMMAP::openWithFormat(audio_format_t audioFormat,
audio_format_t* nextFormatToTry) {
aaudio_result_t result = AAUDIO_OK;
audio_config_base_t config;
audio_port_handle_t deviceId;
const audio_attributes_t attributes = getAudioAttributesFrom(this);
deviceId = mRequestedDeviceId;
// Fill in config
config.format = audioFormat;
int32_t aaudioSampleRate = getSampleRate();
if (aaudioSampleRate == AAUDIO_UNSPECIFIED) {
aaudioSampleRate = AAUDIO_SAMPLE_RATE_DEFAULT;
}
config.sample_rate = aaudioSampleRate;
const aaudio_direction_t direction = getDirection();
config.channel_mask = AAudio_getChannelMaskForOpen(
getChannelMask(), getSamplesPerFrame(), direction == AAUDIO_DIRECTION_INPUT);
if (direction == AAUDIO_DIRECTION_OUTPUT) {
mHardwareTimeOffsetNanos = OUTPUT_ESTIMATED_HARDWARE_OFFSET_NANOS; // frames at DAC later
} else if (direction == AAUDIO_DIRECTION_INPUT) {
mHardwareTimeOffsetNanos = INPUT_ESTIMATED_HARDWARE_OFFSET_NANOS; // frames at ADC earlier
} else {
ALOGE("%s() invalid direction = %d", __func__, direction);
return AAUDIO_ERROR_ILLEGAL_ARGUMENT;
}
const MmapStreamInterface::stream_direction_t streamDirection =
(direction == AAUDIO_DIRECTION_OUTPUT)
? MmapStreamInterface::DIRECTION_OUTPUT
: MmapStreamInterface::DIRECTION_INPUT;
const aaudio_session_id_t requestedSessionId = getSessionId();
const audio_session_t sessionId = AAudioConvert_aaudioToAndroidSessionId(requestedSessionId);
// Open HAL stream. Set mMmapStream
ALOGD("%s trying to open MMAP stream with format=%#x, "
"sample_rate=%u, channel_mask=%#x, device=%d",
__func__, config.format, config.sample_rate,
config.channel_mask, deviceId);
status_t status = MmapStreamInterface::openMmapStream(streamDirection,
&attributes,
&config,
mMmapClient,
&deviceId,
&sessionId,
this, // callback
mMmapStream,
&mPortHandle);
ALOGD("%s() mMapClient.attributionSource = %s => portHandle = %d\n",
__func__, mMmapClient.attributionSource.toString().c_str(), mPortHandle);
if (status != OK) {
// This can happen if the resource is busy or the config does
// not match the hardware.
ALOGD("%s() - openMmapStream() returned status %d", __func__, status);
*nextFormatToTry = config.format != audioFormat? config.format : nextFormatToTry;
return AAUDIO_ERROR_UNAVAILABLE;
}
if (deviceId == AAUDIO_UNSPECIFIED) {
ALOGW("%s() - openMmapStream() failed to set deviceId", __func__);
}
setDeviceId(deviceId);
if (sessionId == AUDIO_SESSION_ALLOCATE) {
ALOGW("%s() - openMmapStream() failed to set sessionId", __func__);
}
const aaudio_session_id_t actualSessionId =
(requestedSessionId == AAUDIO_SESSION_ID_NONE)
? AAUDIO_SESSION_ID_NONE
: (aaudio_session_id_t) sessionId;
setSessionId(actualSessionId);
ALOGD("%s(format = 0x%X) deviceId = %d, sessionId = %d",
__func__, audioFormat, getDeviceId(), getSessionId());
// Create MMAP/NOIRQ buffer.
result = createMmapBuffer(&mAudioDataFileDescriptor);
if (result != AAUDIO_OK) {
goto error;
}
// Get information about the stream and pass it back to the caller.
setChannelMask(AAudioConvert_androidToAAudioChannelMask(
config.channel_mask, getDirection() == AAUDIO_DIRECTION_INPUT,
AAudio_isChannelIndexMask(config.channel_mask)));
setFormat(config.format);
setSampleRate(config.sample_rate);
// If the position is not updated while the timestamp is updated for more than a certain amount,
// the timestamp reported from the HAL may not be accurate. Here, a timestamp grace period is
// set as 5 burst size. We may want to update this value if there is any report from OEMs saying
// that is too short.
static constexpr int kTimestampGraceBurstCount = 5;
mTimestampGracePeriodMs = ((int64_t) kTimestampGraceBurstCount * mFramesPerBurst
* AAUDIO_MILLIS_PER_SECOND) / getSampleRate();
ALOGD("%s() got rate = %d, channels = %d channelMask = %#x, deviceId = %d, capacity = %d\n",
__func__, getSampleRate(), getSamplesPerFrame(), getChannelMask(),
deviceId, getBufferCapacity());
ALOGD("%s() got format = 0x%X = %s, frame size = %d, burst size = %d",
__func__, getFormat(), audio_format_to_string(getFormat()),
calculateBytesPerFrame(), mFramesPerBurst);
return result;
error:
close();
// restore original requests
setDeviceId(mRequestedDeviceId);
setSessionId(requestedSessionId);
return result;
}
这儿会创建流和共享buffer,看下openMmapStream:
status_t AudioFlinger::openMmapStream(MmapStreamInterface::stream_direction_t direction,
const audio_attributes_t *attr,
audio_config_base_t *config,
const AudioClient& client,
audio_port_handle_t *deviceId,
audio_session_t *sessionId,
const sp<MmapStreamCallback>& callback,
sp<MmapStreamInterface>& interface,
audio_port_handle_t *handle)
{
status_t ret = initCheck();
if (ret != NO_ERROR) {
return ret;
}
audio_session_t actualSessionId = *sessionId;
if (actualSessionId == AUDIO_SESSION_ALLOCATE) {
actualSessionId = (audio_session_t) newAudioUniqueId(AUDIO_UNIQUE_ID_USE_SESSION);
}
audio_stream_type_t streamType = AUDIO_STREAM_DEFAULT;
audio_io_handle_t io = AUDIO_IO_HANDLE_NONE;
audio_port_handle_t portId = AUDIO_PORT_HANDLE_NONE;
audio_attributes_t localAttr = *attr;
if (direction == MmapStreamInterface::DIRECTION_OUTPUT) {
audio_config_t fullConfig = AUDIO_CONFIG_INITIALIZER;
fullConfig.sample_rate = config->sample_rate;
fullConfig.channel_mask = config->channel_mask;
fullConfig.format = config->format;
std::vector<audio_io_handle_t> secondaryOutputs;
ret = AudioSystem::getOutputForAttr(&localAttr, &io,
actualSessionId,
&streamType, client.clientPid, client.clientUid,
&fullConfig,
(audio_output_flags_t)(AUDIO_OUTPUT_FLAG_MMAP_NOIRQ |
AUDIO_OUTPUT_FLAG_DIRECT),
deviceId, &portId, &secondaryOutputs);
ALOGW_IF(!secondaryOutputs.empty(),
"%s does not support secondary outputs, ignoring them", __func__);
} else {
ret = AudioSystem::getInputForAttr(&localAttr, &io,
RECORD_RIID_INVALID,
actualSessionId,
client.clientPid,
client.clientUid,
client.packageName,
config,
AUDIO_INPUT_FLAG_MMAP_NOIRQ, deviceId, &portId);
}
if (ret != NO_ERROR) {
return ret;
}
// at this stage, a MmapThread was created when openOutput() or openInput() was called by
// audio policy manager and we can retrieve it
sp<MmapThread> thread = mMmapThreads.valueFor(io);
if (thread != 0) {
interface = new MmapThreadHandle(thread);
thread->configure(&localAttr, streamType, actualSessionId, callback, *deviceId, portId);
*handle = portId;
*sessionId = actualSessionId;
config->sample_rate = thread->sampleRate();
config->channel_mask = thread->channelMask();
config->format = thread->format();
} else {
if (direction == MmapStreamInterface::DIRECTION_OUTPUT) {
AudioSystem::releaseOutput(portId);
} else {
AudioSystem::releaseInput(portId);
}
ret = NO_INIT;
}
ALOGV("%s done status %d portId %d", __FUNCTION__, ret, portId);
return ret;
}
这时候就可以通过MmapThread和Hal层读写数据了。这儿还返回了一个interface,就是MmapThreadHandle对象,用来共享内存的。接下来调用interface的createMmapBuffer来创建共享内存:
status_t AudioFlinger::MmapThreadHandle::createMmapBuffer(int32_t minSizeFrames,
struct audio_mmap_buffer_info *info)
{
return mThread->createMmapBuffer(minSizeFrames, info);
}
status_t AudioFlinger::MmapThread::createMmapBuffer(int32_t minSizeFrames,
struct audio_mmap_buffer_info *info)
{
if (mHalStream == 0) {
return NO_INIT;
}
mStandby = true;
acquireWakeLock();
return mHalStream->createMmapBuffer(minSizeFrames, info);
}
这时候就走到了Hal层创建共享内存了。 这时候就完成流的创建了。
三、播放音频流
接下来继续看下如何启动,入口是AAudioStream_requestStart:
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;
}
这时候就是直接调用AudioStream的systemStart 方法:
aaudio_result_t AudioStream::systemStart() {
std::lock_guard<std::mutex> lock(mStreamLock);
if (collidesWithCallback()) {
ALOGE("%s cannot be called from a callback!", __func__);
return AAUDIO_ERROR_INVALID_STATE;
}
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->start();
}
return result;
}
接下来看下Mmap的实现:
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;
}
if (isDisconnected()) {
ALOGD("requestStart() but DISCONNECTED");
return AAUDIO_ERROR_DISCONNECTED;
}
aaudio_stream_state_t originalState = getState();
setState(AAUDIO_STREAM_STATE_STARTING);
// Clear any stale timestamps from the previous run.
drainTimestampsFromService();
aaudio_result_t result = mServiceInterface.startStream(mServiceStreamHandle); // 请求启动
if (result == AAUDIO_ERROR_STANDBY) {
result = exitStandby_l();
if(result == AAUDIO_OK)
result = mServiceInterface.startStream(mServiceStreamHandle);
}
if (result != AAUDIO_OK) {
ALOGD("%s() INVALID_HANDLE, stream was probably stolen", __func__);
// Stealing was added in R. Coerce result to improve backward compatibility.
result = AAUDIO_ERROR_DISCONNECTED;
setDisconnected();
}
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(periodNanos, aaudio_callback_thread_proc, this); // 如果是异步形式,就创建一个线程
}
if (result != AAUDIO_OK) {
setState(originalState);
}
return result;
}
请求启动比较复杂,先看下异步线程:
aaudio_result_t AudioStream::createThread(int64_t periodNanoseconds,
aaudio_audio_thread_proc_t threadProc,
void* threadArg)
{
if (mHasThread) {
ALOGE("createThread() - mHasThread already true");
return AAUDIO_ERROR_INVALID_STATE;
}
if (threadProc == nullptr) {
return AAUDIO_ERROR_NULL;
}
// Pass input parameters to the background thread.
mThreadProc = threadProc;
mThreadArg = threadArg;
setPeriodNanoseconds(periodNanoseconds);
int err = pthread_create(&mThread, nullptr, AudioStream_internalThreadProc, this);
if (err != 0) {
android::status_t status = -errno;
ALOGE("createThread() - pthread_create() failed, %d", status);
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);
mHasThread = true;
return AAUDIO_OK;
}
}
这儿没啥逻辑,就是创建了一个线程,看下aaudio_callback_thread_proc是什么:
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;
}
}
这儿就是回调调用方,继续再看看:
// Read data from the stream and pass it to the callback for processing.
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 = systemStopFromCallback();
break;
}
}
ALOGD("callbackLoop() exiting, result = %d, isActive() = %d",
result, (int) isActive());
return NULL;
}
再看下maybeCallDataCallback:
aaudio_data_callback_result_t AudioStream::maybeCallDataCallback(void *audioData,
int32_t numFrames) {
aaudio_data_callback_result_t result = AAUDIO_CALLBACK_RESULT_STOP;
AAudioStream_dataCallback dataCallback = getDataCallbackProc();
if (dataCallback != nullptr) {
// Store thread ID of caller to detect stop() and close() calls from callback.
pid_t expected = CALLBACK_THREAD_NONE;
if (mDataCallbackThread.compare_exchange_strong(expected, gettid())) {
result = (*dataCallback)(
(AAudioStream *) this,
getDataCallbackUserData(),
audioData,
numFrames);
mDataCallbackThread.store(CALLBACK_THREAD_NONE);
} else {
ALOGW("%s() data callback already running!", __func__);
}
}
return result;
}
这儿的dataCallback 就是应用方注册进来的函数指针。
先继续看看startStream,实现到了AAudioService里:
aaudio_result_t AAudioService::startStream(aaudio_handle_t streamHandle) {
sp<AAudioServiceStreamBase> serviceStream = convertHandleToServiceStream(streamHandle);
if (serviceStream.get() == nullptr) {
ALOGW("%s(), invalid streamHandle = 0x%0x", __func__, streamHandle);
return AAUDIO_ERROR_INVALID_HANDLE;
}
return serviceStream->start();
}
继续跟下start:
aaudio_result_t AAudioServiceStreamBase::start() {
std::lock_guard<std::mutex> lock(mLock);
const int64_t beginNs = AudioClock::getNanoseconds();
aaudio_result_t result = AAUDIO_OK;
if (auto state = getState();
state == AAUDIO_STREAM_STATE_CLOSED || state == AAUDIO_STREAM_STATE_DISCONNECTED) {
ALOGW("%s() already CLOSED, returns INVALID_STATE, handle = %d",
__func__, getHandle());
return AAUDIO_ERROR_INVALID_STATE;
}
mediametrics::Defer defer([&] {
mediametrics::LogItem(mMetricsId)
.set(AMEDIAMETRICS_PROP_EVENT, AMEDIAMETRICS_PROP_EVENT_VALUE_START)
.set(AMEDIAMETRICS_PROP_EXECUTIONTIMENS, (int64_t)(AudioClock::getNanoseconds() - beginNs))
.set(AMEDIAMETRICS_PROP_STATE, AudioGlobal_convertStreamStateToText(getState()))
.set(AMEDIAMETRICS_PROP_STATUS, (int32_t)result)
.record(); });
if (isRunning()) {
return result;
}
setFlowing(false);
setSuspended(false);
// Start with fresh presentation timestamps.
mAtomicStreamTimestamp.clear();
mClientHandle = AUDIO_PORT_HANDLE_NONE;
result = startDevice();
if (result != AAUDIO_OK) goto error;
// This should happen at the end of the start.
sendServiceEvent(AAUDIO_SERVICE_EVENT_STARTED);
setState(AAUDIO_STREAM_STATE_STARTED);
mThreadEnabled.store(true);
result = mTimestampThread.start(this);
if (result != AAUDIO_OK) goto error;
return result;
error:
disconnect_l();
return result;
}
调用了startDevice:
aaudio_result_t AAudioServiceStreamBase::startDevice() {
mClientHandle = AUDIO_PORT_HANDLE_NONE;
sp<AAudioServiceEndpoint> endpoint = mServiceEndpointWeak.promote();
if (endpoint == nullptr) {
ALOGE("%s() has no endpoint", __func__);
return AAUDIO_ERROR_INVALID_STATE;
}
return endpoint->startStream(this, &mClientHandle);
}
继续跟下:
aaudio_result_t AAudioServiceEndpointMMAP::startStream(sp<AAudioServiceStreamBase> stream,
audio_port_handle_t *clientHandle __unused) {
// Start the client on behalf of the AAudio service.
// Use the port handle that was provided by openMmapStream().
audio_port_handle_t tempHandle = mPortHandle;
audio_attributes_t attr = {};
if (stream != nullptr) {
attr = getAudioAttributesFrom(stream.get());
}
aaudio_result_t result = startClient(
mMmapClient, stream == nullptr ? nullptr : &attr, &tempHandle);
// When AudioFlinger is passed a valid port handle then it should not change it.
LOG_ALWAYS_FATAL_IF(tempHandle != mPortHandle,
"%s() port handle not expected to change from %d to %d",
__func__, mPortHandle, tempHandle);
ALOGV("%s() mPortHandle = %d", __func__, mPortHandle);
return result;
}
调用了startClient:
aaudio_result_t AAudioServiceEndpointMMAP::startClient(const android::AudioClient& client, const audio_attributes_t *attr, audio_port_handle_t *clientHandle) {
if (mMmapStream == nullptr) return AAUDIO_ERROR_NULL;
status_t status = mMmapStream->start(client, attr, clientHandle);
return AAudioConvert_androidToAAudioResult(status);
}
mMmapStream 就是之前从AuidoFlinger中拿到的共享内存对象MmapThreadHandle,继续看下start:
status_t AudioFlinger::MmapThreadHandle::start(const AudioClient& client,
const audio_attributes_t *attr, audio_port_handle_t *handle)
{
return mThread->start(client, attr, handle);
}
调用的是MmapThread的start:
status_t AudioFlinger::MmapThread::start(const AudioClient& client,
const audio_attributes_t *attr,
audio_port_handle_t *handle)
{
ALOGV("%s clientUid %d mStandby %d mPortId %d *handle %d", __FUNCTION__,
client.clientUid, mStandby, mPortId, *handle);
if (mHalStream == 0) {
return NO_INIT;
}
status_t ret;
if (*handle == mPortId) {
// for the first track, reuse portId and session allocated when the stream was opened
return exitStandby();
}
audio_port_handle_t portId = AUDIO_PORT_HANDLE_NONE;
audio_io_handle_t io = mId;
if (isOutput()) {
audio_config_t config = AUDIO_CONFIG_INITIALIZER;
config.sample_rate = mSampleRate;
config.channel_mask = mChannelMask;
config.format = mFormat;
audio_stream_type_t stream = streamType();
audio_output_flags_t flags =
(audio_output_flags_t)(AUDIO_OUTPUT_FLAG_MMAP_NOIRQ | AUDIO_OUTPUT_FLAG_DIRECT);
audio_port_handle_t deviceId = mDeviceId;
std::vector<audio_io_handle_t> secondaryOutputs;
ret = AudioSystem::getOutputForAttr(&mAttr, &io,
mSessionId,
&stream,
client.clientPid,
client.clientUid,
&config,
flags,
&deviceId,
&portId,
&secondaryOutputs);
ALOGD_IF(!secondaryOutputs.empty(),
"MmapThread::start does not support secondary outputs, ignoring them");
} else {
audio_config_base_t config;
config.sample_rate = mSampleRate;
config.channel_mask = mChannelMask;
config.format = mFormat;
audio_port_handle_t deviceId = mDeviceId;
ret = AudioSystem::getInputForAttr(&mAttr, &io,
RECORD_RIID_INVALID,
mSessionId,
client.clientPid,
client.clientUid,
client.packageName,
&config,
AUDIO_INPUT_FLAG_MMAP_NOIRQ,
&deviceId,
&portId);
}
// APM should not chose a different input or output stream for the same set of attributes
// and audo configuration
if (ret != NO_ERROR || io != mId) {
ALOGE("%s: error getting output or input from APM (error %d, io %d expected io %d)",
__FUNCTION__, ret, io, mId);
return BAD_VALUE;
}
if (isOutput()) {
ret = AudioSystem::startOutput(portId);
} else {
ret = AudioSystem::startInput(portId);
}
Mutex::Autolock _l(mLock);
// abort if start is rejected by audio policy manager
if (ret != NO_ERROR) {
ALOGE("%s: error start rejected by AudioPolicyManager = %d", __FUNCTION__, ret);
if (!mActiveTracks.isEmpty()) {
mLock.unlock();
if (isOutput()) {
AudioSystem::releaseOutput(portId);
} else {
AudioSystem::releaseInput(portId);
}
mLock.lock();
} else {
mHalStream->stop();
}
return PERMISSION_DENIED;
}
// Given that MmapThread::mAttr is mutable, should a MmapTrack have attributes ?
sp<MmapTrack> track = new MmapTrack(this, attr == nullptr ? mAttr : *attr, mSampleRate, mFormat,
mChannelMask, mSessionId, isOutput(), client.clientUid,
client.clientPid, IPCThreadState::self()->getCallingPid(),
portId);
if (isOutput()) {
// force volume update when a new track is added
mHalVolFloat = -1.0f;
} else if (!track->isSilenced_l()) {
for (const sp<MmapTrack> &t : mActiveTracks) {
if (t->isSilenced_l() && t->uid() != client.clientUid)
t->invalidate();
}
}
mActiveTracks.add(track);
sp<EffectChain> chain = getEffectChain_l(mSessionId);
if (chain != 0) {
chain->setStrategy(AudioSystem::getStrategyForStream(streamType()));
chain->incTrackCnt();
chain->incActiveTrackCnt();
}
track->logBeginInterval(patchSinksToString(&mPatch)); // log to MediaMetrics
*handle = portId;
broadcast_l();
ALOGV("%s DONE handle %d stream %p", __FUNCTION__, *handle, mHalStream.get());
return NO_ERROR;
}