在上一篇文章《(一)Audio子系统之AudioRecord.getMinBufferSize》中已经介绍了AudioRecord如何获取最小缓冲区大小,接下来,继续分析AudioRecorder方法中的new AudioRecorder的实现,本文基于Android5.1,Android4.4请戳这里

函数原型:

public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,int bufferSizeInBytes) throws IllegalArgumentException

  作用:

    创建AudioRecord对象

  参数:

    audioSource:录制源,这里设置MediaRecorder.AudioSource.MIC,其他请见MediaRecorder.AudioSource录制源定义,比如MediaRecorder.AudioSource.FM_TUNER等;

    sampleRateInHz:默认采样率,单位Hz,这里设置为44100,44100Hz是当前唯一能保证在所有设备上工作的采样率;

    channelConfig: 描述音频通道设置,这里设置为AudioFormat.CHANNEL_CONFIGURATION_MONO,CHANNEL_CONFIGURATION_MONO保证能在所有设备上工作;

    audioFormat:音频数据保证支持此格式,这里设置为AudioFormat.ENCODING_16BIT;

    bufferSizeInBytes:在录制过程中,音频数据写入缓冲区的总数(字节),即getMinVufferSize()获取到的值。

  异常:

    当参数设置不正确或不支持的参数时,将会抛出IllegalArgumentException

接下来进入系统分析具体实现

frameworks/base/media/java/android/media/AudioRecord.java

    public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
int bufferSizeInBytes)
throws IllegalArgumentException {
this((new AudioAttributes.Builder())
.setInternalCapturePreset(audioSource)
.build(),
(new AudioFormat.Builder())
.setChannelMask(getChannelMaskFromLegacyConfig(channelConfig,//0x10
true/*allow legacy configurations*/))
.setEncoding(audioFormat)
.setSampleRate(sampleRateInHz)
.build(),
bufferSizeInBytes,
AudioManager.AUDIO_SESSION_ID_GENERATE);
}

调用相应的方法,检查参数的合法性,然后对参数进行保存等操作,然后调用自己的构造函数this()

    public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
int sessionId) throws IllegalArgumentException {
mRecordingState = RECORDSTATE_STOPPED; if (attributes == null) {
throw new IllegalArgumentException("Illegal null AudioAttributes");
}
if (format == null) {
throw new IllegalArgumentException("Illegal null AudioFormat");
} // remember which looper is associated with the AudioRecord instanciation
if ((mInitializationLooper = Looper.myLooper()) == null) {
mInitializationLooper = Looper.getMainLooper();
} // is this AudioRecord using REMOTE_SUBMIX at full volume?
if (attributes.getCapturePreset() == MediaRecorder.AudioSource.REMOTE_SUBMIX) {
final AudioAttributes.Builder filteredAttr = new AudioAttributes.Builder();
final Iterator<String> tagsIter = attributes.getTags().iterator();
while (tagsIter.hasNext()) {
final String tag = tagsIter.next();
if (tag.equalsIgnoreCase(SUBMIX_FIXED_VOLUME)) {
mIsSubmixFullVolume = true;
Log.v(TAG, "Will record from REMOTE_SUBMIX at full fixed volume");
} else { // SUBMIX_FIXED_VOLUME: is not to be propagated to the native layers
filteredAttr.addTag(tag);
}
}
filteredAttr.setInternalCapturePreset(attributes.getCapturePreset());
mAudioAttributes = filteredAttr.build();
} else {
mAudioAttributes = attributes;
} int rate = 0;
if ((format.getPropertySetMask()
& AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE) != 0)
{
rate = format.getSampleRate();
} else {
rate = AudioSystem.getPrimaryOutputSamplingRate();
if (rate <= 0) {
rate = 44100;
}
} int encoding = AudioFormat.ENCODING_DEFAULT;
if ((format.getPropertySetMask() & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_ENCODING) != 0)
{
encoding = format.getEncoding();
} audioParamCheck(attributes.getCapturePreset(), rate, encoding); mChannelCount = AudioFormat.channelCountFromInChannelMask(format.getChannelMask());
mChannelMask = getChannelMaskFromLegacyConfig(format.getChannelMask(), false); audioBuffSizeCheck(bufferSizeInBytes); int[] session = new int[1];
session[0] = sessionId;
//TODO: update native initialization when information about hardware init failure
// due to capture device already open is available.
int initResult = native_setup( new WeakReference<AudioRecord>(this),
mAudioAttributes, mSampleRate, mChannelMask, mAudioFormat, mNativeBufferSizeInBytes,
session);
if (initResult != SUCCESS) {
loge("Error code "+initResult+" when initializing native AudioRecord object.");
return; // with mState == STATE_UNINITIALIZED
} mSessionId = session[0]; mState = STATE_INITIALIZED;
}

在这个函数中,主要做了如下工作

1.标记mRecordingState为stoped状态;

2.获取一个MainLooper;

3.判断录音源是否是REMOTE_SUBMIX,有兴趣的童鞋可以深入研究;

4.重新获取rate与format参数,这里会根据AUDIO_FORMAT_HAS_PROPERTY_X来判断从哪里获取参数,而在之前的构造函数中,设置参数的时候已经标记了该标志位,所以这两个参数还是我们设置的;

5.调用audioParamCheck对参数再一次进行检查合法性;

6.获取声道数以及声道掩码,单声道掩码为0x10,双声道掩码为0x0c;

7.调用audioBuffSizeCheck检查最小缓冲区大小是否合法;

8.调用native_setup的native函数,注意这里传过去的参数包括:指向自己的指针,录制源,rate,声道掩码,format,minBuffSize,session[];

9.标记mRecordingState为inited状态;

注:关于SessionId
             一个Session就是一个会话,每个会话都有一个独一无二的Id来标识。该Id的最终管理在AudioFlinger中。
             一个会话可以被多个AudioTrack对象和MediaPlayer共用。
             共用一个Session的AudioTrack和MediaPlayer共享相同的AudioEffect(音效)。

我们只分析native_setup函数

frameworks/base/core/jni/android_media_AudioRecord.cpp

static jint
android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this,
jobject jaa, jint sampleRateInHertz, jint channelMask,
// Java channel masks map directly to the native definition
jint audioFormat, jint buffSizeInBytes, jintArray jSession)
{
if (jaa == 0) {
ALOGE("Error creating AudioRecord: invalid audio attributes");
return (jint) AUDIO_JAVA_ERROR;
} if (!audio_is_input_channel(channelMask)) {
ALOGE("Error creating AudioRecord: channel mask %#x is not valid.", channelMask);
return (jint) AUDIORECORD_ERROR_SETUP_INVALIDCHANNELMASK;
}
uint32_t channelCount = audio_channel_count_from_in_mask(channelMask); // compare the format against the Java constants
audio_format_t format = audioFormatToNative(audioFormat);
if (format == AUDIO_FORMAT_INVALID) {
ALOGE("Error creating AudioRecord: unsupported audio format %d.", audioFormat);
return (jint) AUDIORECORD_ERROR_SETUP_INVALIDFORMAT;
} size_t bytesPerSample = audio_bytes_per_sample(format); if (buffSizeInBytes == 0) {
ALOGE("Error creating AudioRecord: frameCount is 0.");
return (jint) AUDIORECORD_ERROR_SETUP_ZEROFRAMECOUNT;
}
size_t frameSize = channelCount * bytesPerSample;
size_t frameCount = buffSizeInBytes / frameSize; jclass clazz = env->GetObjectClass(thiz);
if (clazz == NULL) {
ALOGE("Can't find %s when setting up callback.", kClassPathName);
return (jint) AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED;
} if (jSession == NULL) {
ALOGE("Error creating AudioRecord: invalid session ID pointer");
return (jint) AUDIO_JAVA_ERROR;
} jint* nSession = (jint *) env->GetPrimitiveArrayCritical(jSession, NULL);
if (nSession == NULL) {
ALOGE("Error creating AudioRecord: Error retrieving session id pointer");
return (jint) AUDIO_JAVA_ERROR;
}
int sessionId = nSession[0];
env->ReleasePrimitiveArrayCritical(jSession, nSession, 0);
nSession = NULL; // create an uninitialized AudioRecord object
sp<AudioRecord> lpRecorder = new AudioRecord(); audio_attributes_t *paa = NULL;
// read the AudioAttributes values
paa = (audio_attributes_t *) calloc(1, sizeof(audio_attributes_t));
const jstring jtags =
(jstring) env->GetObjectField(jaa, javaAudioAttrFields.fieldFormattedTags);
const char* tags = env->GetStringUTFChars(jtags, NULL);
// copying array size -1, char array for tags was calloc'd, no need to NULL-terminate it
strncpy(paa->tags, tags, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1);
env->ReleaseStringUTFChars(jtags, tags);
paa->source = (audio_source_t) env->GetIntField(jaa, javaAudioAttrFields.fieldRecSource);
paa->flags = (audio_flags_mask_t)env->GetIntField(jaa, javaAudioAttrFields.fieldFlags);
ALOGV("AudioRecord_setup for source=%d tags=%s flags=%08x", paa->source, paa->tags, paa->flags); audio_input_flags_t flags = AUDIO_INPUT_FLAG_NONE;
if (paa->flags & AUDIO_FLAG_HW_HOTWORD) {
flags = AUDIO_INPUT_FLAG_HW_HOTWORD;
}
// create the callback information:
// this data will be passed with every AudioRecord callback
audiorecord_callback_cookie *lpCallbackData = new audiorecord_callback_cookie;
lpCallbackData->audioRecord_class = (jclass)env->NewGlobalRef(clazz);
// we use a weak reference so the AudioRecord object can be garbage collected.
lpCallbackData->audioRecord_ref = env->NewGlobalRef(weak_this);
lpCallbackData->busy = false; const status_t status = lpRecorder->set(paa->source,
sampleRateInHertz,
format, // word length, PCM
channelMask,
frameCount,
recorderCallback,// callback_t
lpCallbackData,// void* user
0, // notificationFrames,
true, // threadCanCallJava
sessionId,
AudioRecord::TRANSFER_DEFAULT,
flags,
paa); if (status != NO_ERROR) {
ALOGE("Error creating AudioRecord instance: initialization check failed with status %d.",
status);
goto native_init_failure;
}
nSession = (jint *) env->GetPrimitiveArrayCritical(jSession, NULL);
if (nSession == NULL) {
ALOGE("Error creating AudioRecord: Error retrieving session id pointer");
goto native_init_failure;
}
// read the audio session ID back from AudioRecord in case a new session was created during set()
nSession[0] = lpRecorder->getSessionId();
env->ReleasePrimitiveArrayCritical(jSession, nSession, 0);
nSession = NULL; { // scope for the lock
Mutex::Autolock l(sLock);
sAudioRecordCallBackCookies.add(lpCallbackData);
}
// save our newly created C++ AudioRecord in the "nativeRecorderInJavaObj" field
// of the Java object
setAudioRecord(env, thiz, lpRecorder); // save our newly created callback information in the "nativeCallbackCookie" field
// of the Java object (in mNativeCallbackCookie) so we can free the memory in finalize()
env->SetLongField(thiz, javaAudioRecordFields.nativeCallbackCookie, (jlong)lpCallbackData); return (jint) AUDIO_JAVA_SUCCESS; // failure:
native_init_failure:
env->DeleteGlobalRef(lpCallbackData->audioRecord_class);
env->DeleteGlobalRef(lpCallbackData->audioRecord_ref);
delete lpCallbackData;
env->SetLongField(thiz, javaAudioRecordFields.nativeCallbackCookie, 0); return (jint) AUDIORECORD_ERROR_SETUP_NATIVEINITFAILED;
}

在这个函数中主要工作如下:

1.判断声道掩码是否合法,然后通过掩码计算出声道数;

2.由于最小缓冲区大小是采样帧数量*每个采样帧大小得出,每个采样帧大小为所有声道数所占的字节数,从而求出采样帧数量frameCount;

3.进行一系列的JNI处理录音源,以及把AudioRecord.java的指针绑定到lpCallbackData回调数据中,这样就能把数据通过回调的方式通知到上层;

4.调用AudioRecord的set函数,这里注意下flags,他的类型为audio_input_flags_t,定义在system\core\include\system\audio.h中,作为音频输入的标志,这里设置为AUDIO_INPUT_FLAG_NONE

typedef enum {
AUDIO_INPUT_FLAG_NONE = 0x0, // no attributes
AUDIO_INPUT_FLAG_FAST = 0x1, // prefer an input that supports "fast tracks"
AUDIO_INPUT_FLAG_HW_HOTWORD = 0x2, // prefer an input that captures from hw hotword source
} audio_input_flags_t;

5.把lpRecorder对象以及lpCallbackData回调保存到javaAudioRecordFields的相应字段中。

这里分析lpRecorder->set函数

frameworks\av\media\libmedia\AudioRecord.cpp

status_t AudioRecord::set(
audio_source_t inputSource,
uint32_t sampleRate,
audio_format_t format,
audio_channel_mask_t channelMask,
size_t frameCount,
callback_t cbf,
void* user,
uint32_t notificationFrames,
bool threadCanCallJava,
int sessionId,
transfer_type transferType,
audio_input_flags_t flags,
const audio_attributes_t* pAttributes)
{
switch (transferType) {
case TRANSFER_DEFAULT:
if (cbf == NULL || threadCanCallJava) {
transferType = TRANSFER_SYNC;
} else {
transferType = TRANSFER_CALLBACK;
}
break;
case TRANSFER_CALLBACK:
if (cbf == NULL) {
ALOGE("Transfer type TRANSFER_CALLBACK but cbf == NULL");
return BAD_VALUE;
}
break;
case TRANSFER_OBTAIN:
case TRANSFER_SYNC:
break;
default:
ALOGE("Invalid transfer type %d", transferType);
return BAD_VALUE;
}
mTransfer = transferType; AutoMutex lock(mLock); // invariant that mAudioRecord != 0 is true only after set() returns successfully
if (mAudioRecord != 0) {
ALOGE("Track already in use");
return INVALID_OPERATION;
} if (pAttributes == NULL) {
memset(&mAttributes, 0, sizeof(audio_attributes_t));
mAttributes.source = inputSource;
} else {
// stream type shouldn't be looked at, this track has audio attributes
memcpy(&mAttributes, pAttributes, sizeof(audio_attributes_t));
ALOGV("Building AudioRecord with attributes: source=%d flags=0x%x tags=[%s]",
mAttributes.source, mAttributes.flags, mAttributes.tags);
} if (sampleRate == 0) {
ALOGE("Invalid sample rate %u", sampleRate);
return BAD_VALUE;
}
mSampleRate = sampleRate; // these below should probably come from the audioFlinger too...
if (format == AUDIO_FORMAT_DEFAULT) {
format = AUDIO_FORMAT_PCM_16_BIT;
} // validate parameters
if (!audio_is_valid_format(format)) {
ALOGE("Invalid format %#x", format);
return BAD_VALUE;
}
// Temporary restriction: AudioFlinger currently supports 16-bit PCM only
if (format != AUDIO_FORMAT_PCM_16_BIT) {
ALOGE("Format %#x is not supported", format);
return BAD_VALUE;
}
mFormat = format; if (!audio_is_input_channel(channelMask)) {
ALOGE("Invalid channel mask %#x", channelMask);
return BAD_VALUE;
}
mChannelMask = channelMask;
uint32_t channelCount = audio_channel_count_from_in_mask(channelMask);
mChannelCount = channelCount; if (audio_is_linear_pcm(format)) {
mFrameSize = channelCount * audio_bytes_per_sample(format);
} else {
mFrameSize = sizeof(uint8_t);
} // mFrameCount is initialized in openRecord_l
mReqFrameCount = frameCount; mNotificationFramesReq = notificationFrames;
// mNotificationFramesAct is initialized in openRecord_l if (sessionId == AUDIO_SESSION_ALLOCATE) {
mSessionId = AudioSystem::newAudioUniqueId();
} else {
mSessionId = sessionId;
}
ALOGV("set(): mSessionId %d", mSessionId); mFlags = flags;
mCbf = cbf; if (cbf != NULL) {
mAudioRecordThread = new AudioRecordThread(*this, threadCanCallJava);
mAudioRecordThread->run("AudioRecord", ANDROID_PRIORITY_AUDIO);
} // create the IAudioRecord
status_t status = openRecord_l(0 /*epoch*/); if (status != NO_ERROR) {
if (mAudioRecordThread != 0) {
mAudioRecordThread->requestExit(); // see comment in AudioRecord.h
mAudioRecordThread->requestExitAndWait();
mAudioRecordThread.clear();
}
return status;
} mStatus = NO_ERROR;
mActive = false;
mUserData = user;
// TODO: add audio hardware input latency here
mLatency = (1000*mFrameCount) / sampleRate;
mMarkerPosition = 0;
mMarkerReached = false;
mNewPosition = 0;
mUpdatePeriod = 0;
AudioSystem::acquireAudioSessionId(mSessionId, -1);
mSequence = 1;
mObservedSequence = mSequence;
mInOverrun = false; return NO_ERROR;
}

在这个函数中主要工作如下:

1.在JNI中传递过来的参数:transferType为TRANSFER_DEFAULT,cbf!=null,threadCanCallJava=true,所以mTransfer设置为TRANSFER_SYNC,他是决定如何从AudioRecord传输数据方式,后面会用到;

2.保存相关的参数,如录制源mAttributes.source,采样率mSampleRate,采样精度mFormat,声道掩码mChannelMask,声道数mChannelCount,采样帧大小mFrameSize,采样帧数量mReqFrameCount,通知帧计数mNotificationFramesReq,mSessionId在这里更新了,音频输入标志mFlags还是之前的AUDIO_INPUT_FLAG_NONE

3.当cbf数据回调函数不为null时,开启一个录音线程AudioRecordThread;

4.调用openRecord_l(0)创建IAudioRecord对象;

5.如果建立失败,就销毁录音线程AudioRecordThread,否则更新参数;

这里继续分析如何创建IAudioRecord对象

status_t AudioRecord::openRecord_l(size_t epoch)
{
status_t status;
const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();
if (audioFlinger == 0) {
ALOGE("Could not get audioflinger");
return NO_INIT;
} // Fast tracks must be at the primary _output_ [sic] sampling rate,
// because there is currently no concept of a primary input sampling rate
uint32_t afSampleRate = AudioSystem::getPrimaryOutputSamplingRate();
if (afSampleRate == 0) {
ALOGW("getPrimaryOutputSamplingRate failed");
} // Client can only express a preference for FAST. Server will perform additional tests.
if ((mFlags & AUDIO_INPUT_FLAG_FAST) && !(
// use case: callback transfer mode
(mTransfer == TRANSFER_CALLBACK) &&
// matching sample rate
(mSampleRate == afSampleRate))) {
ALOGW("AUDIO_INPUT_FLAG_FAST denied by client");
// once denied, do not request again if IAudioRecord is re-created
mFlags = (audio_input_flags_t) (mFlags & ~AUDIO_INPUT_FLAG_FAST);
} IAudioFlinger::track_flags_t trackFlags = IAudioFlinger::TRACK_DEFAULT; pid_t tid = -1;
if (mFlags & AUDIO_INPUT_FLAG_FAST) {
trackFlags |= IAudioFlinger::TRACK_FAST;
if (mAudioRecordThread != 0) {
tid = mAudioRecordThread->getTid();
}
} audio_io_handle_t input;
status = AudioSystem::getInputForAttr(&mAttributes, &input, (audio_session_t)mSessionId,
mSampleRate, mFormat, mChannelMask, mFlags); if (status != NO_ERROR) {
ALOGE("Could not get audio input for record source %d, sample rate %u, format %#x, "
"channel mask %#x, session %d, flags %#x",
mAttributes.source, mSampleRate, mFormat, mChannelMask, mSessionId, mFlags);
return BAD_VALUE;
}
{
// Now that we have a reference to an I/O handle and have not yet handed it off to AudioFlinger,
// we must release it ourselves if anything goes wrong. size_t frameCount = mReqFrameCount;
size_t temp = frameCount; // temp may be replaced by a revised value of frameCount,
// but we will still need the original value also
int originalSessionId = mSessionId; // The notification frame count is the period between callbacks, as suggested by the server.
size_t notificationFrames = mNotificationFramesReq; sp<IMemory> iMem; // for cblk
sp<IMemory> bufferMem; //return recordHandle = new RecordHandle(recordTrack);
//class RecordHandle : public android::BnAudioRecord
sp<IAudioRecord> record = audioFlinger->openRecord(input,
mSampleRate, mFormat,
mChannelMask,
&temp,
&trackFlags,
tid,
&mSessionId,
&notificationFrames,
iMem,
bufferMem,
&status);
ALOGE_IF(originalSessionId != AUDIO_SESSION_ALLOCATE && mSessionId != originalSessionId,
"session ID changed from %d to %d", originalSessionId, mSessionId); if (status != NO_ERROR) {
ALOGE("AudioFlinger could not create record track, status: %d", status);
goto release;
}
ALOG_ASSERT(record != 0); // AudioFlinger now owns the reference to the I/O handle,
// so we are no longer responsible for releasing it. if (iMem == 0) {
ALOGE("Could not get control block");
return NO_INIT;
}
void *iMemPointer = iMem->pointer();
if (iMemPointer == NULL) {
ALOGE("Could not get control block pointer");
return NO_INIT;
}
audio_track_cblk_t* cblk = static_cast<audio_track_cblk_t*>(iMemPointer); // Starting address of buffers in shared memory.
// The buffers are either immediately after the control block,
// or in a separate area at discretion of server.
void *buffers;
if (bufferMem == 0) {
buffers = cblk + 1;
} else {
buffers = bufferMem->pointer();
if (buffers == NULL) {
ALOGE("Could not get buffer pointer");
return NO_INIT;
}
} // invariant that mAudioRecord != 0 is true only after set() returns successfully
if (mAudioRecord != 0) {
mAudioRecord->asBinder()->unlinkToDeath(mDeathNotifier, this);
mDeathNotifier.clear();
}
mAudioRecord = record;
mCblkMemory = iMem;
mBufferMemory = bufferMem;
IPCThreadState::self()->flushCommands(); mCblk = cblk;
// note that temp is the (possibly revised) value of frameCount
if (temp < frameCount || (frameCount == 0 && temp == 0)) {
ALOGW("Requested frameCount %zu but received frameCount %zu", frameCount, temp);
}
frameCount = temp; mAwaitBoost = false;
if (mFlags & AUDIO_INPUT_FLAG_FAST) {
if (trackFlags & IAudioFlinger::TRACK_FAST) {
ALOGV("AUDIO_INPUT_FLAG_FAST successful; frameCount %zu", frameCount);
mAwaitBoost = true;
} else {
ALOGV("AUDIO_INPUT_FLAG_FAST denied by server; frameCount %zu", frameCount);
// once denied, do not request again if IAudioRecord is re-created
mFlags = (audio_input_flags_t) (mFlags & ~AUDIO_INPUT_FLAG_FAST);
}
} // Make sure that application is notified with sufficient margin before overrun
if (notificationFrames == 0 || notificationFrames > frameCount) {
ALOGW("Received notificationFrames %zu for frameCount %zu", notificationFrames, frameCount);
}
mNotificationFramesAct = notificationFrames; // We retain a copy of the I/O handle, but don't own the reference
mInput = input;
mRefreshRemaining = true; mFrameCount = frameCount;
// If IAudioRecord is re-created, don't let the requested frameCount
// decrease. This can confuse clients that cache frameCount().
if (frameCount > mReqFrameCount) {
mReqFrameCount = frameCount;
} // update proxy
mProxy = new AudioRecordClientProxy(cblk, buffers, mFrameCount, mFrameSize);
mProxy->setEpoch(epoch);
mProxy->setMinimum(mNotificationFramesAct); mDeathNotifier = new DeathNotifier(this);
mAudioRecord->asBinder()->linkToDeath(mDeathNotifier, this); return NO_ERROR;
} release:
AudioSystem::releaseInput(input, (audio_session_t)mSessionId);
if (status == NO_ERROR) {
status = NO_INIT;
}
return status;
}

在这个函数中主要工作如下:

1.获取IAudioFlinger对象,其通过binder和AudioFlinger通信,所以也就是相当于直接调用到AudioFlinger服务中了;

2.判断音频输入标志,是否需要清除AUDIO_INPUT_FLAG_FAST标志位,这里不需要,一直是AUDIO_INPUT_FLAG_NONE;

3.调用AudioSystem::getInputForAttr获取输入流的句柄input;

4.调用audioFlinger->openRecord创建IAudioRecord对象;

5.通过IMemory共享内存,获取录音数据;

6.更新AudioRecordClientProxy客户端代理的录音数据;

下面主要分析第3、4点:

首先看下AudioRecord.cpp::openRecord_l(0)的第3步.获取输入流的句柄input

frameworks\av\media\libmedia\AudioSystem.cpp

status_t AudioSystem::getInputForAttr(const audio_attributes_t *attr,
audio_io_handle_t *input,
audio_session_t session,
uint32_t samplingRate,
audio_format_t format,
audio_channel_mask_t channelMask,
audio_input_flags_t flags)
{
const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
if (aps == 0) return NO_INIT;
return aps->getInputForAttr(attr, input, session, samplingRate, format, channelMask, flags);
}

获取AudioPolicy的服务,继续调用AudioPolicyService的函数

frameworks\av\services\audiopolicy\AudioPolicyInterfaceImpl.cpp

status_t AudioPolicyService::getInputForAttr(const audio_attributes_t *attr,
audio_io_handle_t *input,
audio_session_t session,
uint32_t samplingRate,
audio_format_t format,
audio_channel_mask_t channelMask,
audio_input_flags_t flags)
{
if (mAudioPolicyManager == NULL) {
return NO_INIT;
} // already checked by client, but double-check in case the client wrapper is bypassed
if (attr->source >= AUDIO_SOURCE_CNT && attr->source != AUDIO_SOURCE_HOTWORD &&
attr->source != AUDIO_SOURCE_FM_TUNER) {
return BAD_VALUE;
} if (((attr->source == AUDIO_SOURCE_HOTWORD) && !captureHotwordAllowed()) ||
((attr->source == AUDIO_SOURCE_FM_TUNER) && !captureFmTunerAllowed())) {
return BAD_VALUE;
}
sp<AudioPolicyEffects>audioPolicyEffects;
status_t status;
AudioPolicyInterface::input_type_t inputType;
{
Mutex::Autolock _l(mLock);
// the audio_in_acoustics_t parameter is ignored by get_input()
status = mAudioPolicyManager->getInputForAttr(attr, input, session,
samplingRate, format, channelMask,
flags, &inputType);
audioPolicyEffects = mAudioPolicyEffects; if (status == NO_ERROR) {
// enforce permission (if any) required for each type of input
switch (inputType) {
case AudioPolicyInterface::API_INPUT_LEGACY:
break;
case AudioPolicyInterface::API_INPUT_MIX_CAPTURE:
if (!captureAudioOutputAllowed()) {
ALOGE("getInputForAttr() permission denied: capture not allowed");
status = PERMISSION_DENIED;
}
break;
case AudioPolicyInterface::API_INPUT_MIX_EXT_POLICY_REROUTE:
if (!modifyAudioRoutingAllowed()) {
ALOGE("getInputForAttr() permission denied: modify audio routing not allowed");
status = PERMISSION_DENIED;
}
break;
case AudioPolicyInterface::API_INPUT_INVALID:
default:
LOG_ALWAYS_FATAL("getInputForAttr() encountered an invalid input type %d",
(int)inputType);
}
} if (status != NO_ERROR) {
if (status == PERMISSION_DENIED) {
mAudioPolicyManager->releaseInput(*input, session);
}
return status;
}
} if (audioPolicyEffects != 0) {
// create audio pre processors according to input source
status_t status = audioPolicyEffects->addInputEffects(*input, attr->source, session);
if (status != NO_ERROR && status != ALREADY_EXISTS) {
ALOGW("Failed to add effects on input %d", *input);
}
}
return NO_ERROR;
}

在这个函数中主要的工作如下:

1.对source为HOTWORD或FM_TUNER的录音源,判断是否具有相应的录音权限(根据应用进程号);

2.继续调用AudioPolicyManager的方法获取input以及inputType;

3.检查应用是否具有该inputType的录音权限;

4.判断是否需要添加音效(audioPolicyEffects),需要则使用audioPolicyEffects->addInputEffects添加音效;

继续分析第2步

frameworks\av\services\audiopolicy\AudioPolicyManager.cpp

status_t AudioPolicyManager::getInputForAttr(const audio_attributes_t *attr,
audio_io_handle_t *input,
audio_session_t session,
uint32_t samplingRate,
audio_format_t format,
audio_channel_mask_t channelMask,
audio_input_flags_t flags,
input_type_t *inputType)
{
*input = AUDIO_IO_HANDLE_NONE;
*inputType = API_INPUT_INVALID;
audio_devices_t device;
// handle legacy remote submix case where the address was not always specified
String8 address = String8("");
bool isSoundTrigger = false;
audio_source_t inputSource = attr->source;
audio_source_t halInputSource;
AudioMix *policyMix = NULL; if (inputSource == AUDIO_SOURCE_DEFAULT) {
inputSource = AUDIO_SOURCE_MIC;
}
halInputSource = inputSource; if (inputSource == AUDIO_SOURCE_REMOTE_SUBMIX &&
strncmp(attr->tags, "addr=", strlen("addr=")) == 0) { device = AUDIO_DEVICE_IN_REMOTE_SUBMIX;
address = String8(attr->tags + strlen("addr="));
ssize_t index = mPolicyMixes.indexOfKey(address);
if (index < 0) {
ALOGW("getInputForAttr() no policy for address %s", address.string());
return BAD_VALUE;
}
if (mPolicyMixes[index]->mMix.mMixType != MIX_TYPE_PLAYERS) {
ALOGW("getInputForAttr() bad policy mix type for address %s", address.string());
return BAD_VALUE;
}
policyMix = &mPolicyMixes[index]->mMix;
*inputType = API_INPUT_MIX_EXT_POLICY_REROUTE;
} else {
device = getDeviceAndMixForInputSource(inputSource, &policyMix);
if (device == AUDIO_DEVICE_NONE) {
ALOGW("getInputForAttr() could not find device for source %d", inputSource);
return BAD_VALUE;
}
if (policyMix != NULL) {
address = policyMix->mRegistrationId;
if (policyMix->mMixType == MIX_TYPE_RECORDERS) {
// there is an external policy, but this input is attached to a mix of recorders,
// meaning it receives audio injected into the framework, so the recorder doesn't
// know about it and is therefore considered "legacy"
*inputType = API_INPUT_LEGACY;
} else {
// recording a mix of players defined by an external policy, we're rerouting for
// an external policy
*inputType = API_INPUT_MIX_EXT_POLICY_REROUTE;
}
} else if (audio_is_remote_submix_device(device)) {
address = String8("0");
*inputType = API_INPUT_MIX_CAPTURE;
} else {
*inputType = API_INPUT_LEGACY;
}
// adapt channel selection to input source
switch (inputSource) {
case AUDIO_SOURCE_VOICE_UPLINK:
channelMask = AUDIO_CHANNEL_IN_VOICE_UPLINK;
break;
case AUDIO_SOURCE_VOICE_DOWNLINK:
channelMask = AUDIO_CHANNEL_IN_VOICE_DNLINK;
break;
case AUDIO_SOURCE_VOICE_CALL:
channelMask = AUDIO_CHANNEL_IN_VOICE_UPLINK | AUDIO_CHANNEL_IN_VOICE_DNLINK;
break;
default:
break;
}
if (inputSource == AUDIO_SOURCE_HOTWORD) {
ssize_t index = mSoundTriggerSessions.indexOfKey(session);
if (index >= 0) {
*input = mSoundTriggerSessions.valueFor(session);
isSoundTrigger = true;
flags = (audio_input_flags_t)(flags | AUDIO_INPUT_FLAG_HW_HOTWORD);
ALOGV("SoundTrigger capture on session %d input %d", session, *input);
} else {
halInputSource = AUDIO_SOURCE_VOICE_RECOGNITION;
}
}
} sp<IOProfile> profile = getInputProfile(device, address,
samplingRate, format, channelMask,
flags);
if (profile == 0) {
PLOGV("profile == 0");
//retry without flags
audio_input_flags_t log_flags = flags;
flags = AUDIO_INPUT_FLAG_NONE;
profile = getInputProfile(device, address,
samplingRate, format, channelMask,
flags);
if (profile == 0) {
ALOGW("getInputForAttr() could not find profile for device 0x%X, samplingRate %u,"
"format %#x, channelMask 0x%X, flags %#x",
device, samplingRate, format, channelMask, log_flags);
return BAD_VALUE;
}
} if (profile->mModule->mHandle == 0) {
PLOGV("getInputForAttr(): HW module %s not opened", profile->mModule->mName);
ALOGE("getInputForAttr(): HW module %s not opened", profile->mModule->mName);
return NO_INIT;
} audio_config_t config = AUDIO_CONFIG_INITIALIZER;
config.sample_rate = samplingRate;
config.channel_mask = channelMask;
config.format = format; status_t status = mpClientInterface->openInput(profile->mModule->mHandle,
input,
&config,
&device,
address,
halInputSource,
flags);
// only accept input with the exact requested set of parameters
if (status != NO_ERROR || *input == AUDIO_IO_HANDLE_NONE ||
(samplingRate != config.sample_rate) ||
(format != config.format) ||
(channelMask != config.channel_mask)) {
ALOGW("getInputForAttr() failed opening input: samplingRate %d, format %d, channelMask %x",
samplingRate, format, channelMask);
if (*input != AUDIO_IO_HANDLE_NONE) {
mpClientInterface->closeInput(*input);
}
return BAD_VALUE;
} sp<AudioInputDescriptor> inputDesc = new AudioInputDescriptor(profile);
inputDesc->mInputSource = inputSource;
inputDesc->mRefCount = 0;
inputDesc->mOpenRefCount = 1;
inputDesc->mSamplingRate = samplingRate;
inputDesc->mFormat = format;
inputDesc->mChannelMask = channelMask;
inputDesc->mDevice = device;
inputDesc->mSessions.add(session);
inputDesc->mIsSoundTrigger = isSoundTrigger;
inputDesc->mPolicyMix = policyMix; ALOGV("getInputForAttr() returns input type = %d", inputType); addInput(*input, inputDesc);
mpClientInterface->onAudioPortListUpdate();
return NO_ERROR;
}

在这个函数中主要工作如下:

1.调用getDeviceAndMixForInputSource函数获取policyMix设备以及对应的audio_device_t设备类型(device),device定义在system\core\include\system\audio.h中,这里使用了内置的MIC,所以device为AUDIO_DEVICE_IN_BUILTIN_MIC,另外如果还需要新增一种音频设备的话,需要在这里增加;

enum {
AUDIO_DEVICE_NONE = 0x0,
/* reserved bits */
AUDIO_DEVICE_BIT_IN = 0x80000000,
AUDIO_DEVICE_BIT_DEFAULT = 0x40000000,
/* output devices */
AUDIO_DEVICE_OUT_EARPIECE = 0x1,
AUDIO_DEVICE_OUT_SPEAKER = 0x2,
AUDIO_DEVICE_OUT_WIRED_HEADSET = 0x4,
...
/* input devices */
AUDIO_DEVICE_IN_COMMUNICATION = AUDIO_DEVICE_BIT_IN | 0x1,
AUDIO_DEVICE_IN_AMBIENT = AUDIO_DEVICE_BIT_IN | 0x2,
AUDIO_DEVICE_IN_BUILTIN_MIC = AUDIO_DEVICE_BIT_IN | 0x4,
...
AUDIO_DEVICE_IN_ALL = (AUDIO_DEVICE_IN_COMMUNICATION |
AUDIO_DEVICE_IN_AMBIENT |
AUDIO_DEVICE_IN_BUILTIN_MIC |
AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET |
AUDIO_DEVICE_IN_WIRED_HEADSET |
AUDIO_DEVICE_IN_HDMI |
AUDIO_DEVICE_IN_TELEPHONY_RX |
AUDIO_DEVICE_IN_BACK_MIC |
AUDIO_DEVICE_IN_REMOTE_SUBMIX |
AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET |
AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET |
AUDIO_DEVICE_IN_USB_ACCESSORY |
AUDIO_DEVICE_IN_USB_DEVICE |
AUDIO_DEVICE_IN_FM_TUNER |
AUDIO_DEVICE_IN_TV_TUNER |
AUDIO_DEVICE_IN_LINE |
AUDIO_DEVICE_IN_SPDIF |
AUDIO_DEVICE_IN_BLUETOOTH_A2DP |
AUDIO_DEVICE_IN_LOOPBACK |
AUDIO_DEVICE_IN_AF |
AUDIO_DEVICE_IN_DEFAULT),
AUDIO_DEVICE_IN_ALL_SCO = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,
AUDIO_DEVICE_IN_ALL_USB = (AUDIO_DEVICE_IN_USB_ACCESSORY |
AUDIO_DEVICE_IN_USB_DEVICE),
}; typedef uint32_t audio_devices_t;

2.获取inputType的类型

    typedef enum {
API_INPUT_INVALID = -1,
API_INPUT_LEGACY = 0,// e.g. audio recording from a microphone
API_INPUT_MIX_CAPTURE,// used for "remote submix", capture of the media to play it remotely
API_INPUT_MIX_EXT_POLICY_REROUTE,// used for platform audio rerouting, where mixes are
// handled by external and dynamically installed
// policies which reroute audio mixes
} input_type_t;

3.更新channelMask,适配声道到输入源;

4.调用getInputProfile,根据传进来的采样率/精度/掩码等参数与获得的设备支持的Input Profile比较,返回一个与设备Profile匹配的IOProfile对象,IOProfile是用来描述输出或输入流的能力,策略管理器使用它来确定输出或输入是否适合于给定的用例,相应地打开/关闭它,以及连接/断开音频轨道;

5.如果获取失败的话,则使用AUDIO_INPUT_FLAG_NONE再次获取一遍,如果依然失败,则return一个bad news;

6.继续调用mpClientInterface->openInput建立起输入流;

7.根据IOProfile对象构造AudioInputDescriptor,并绑定到input流中,最后更新AudioPortList;

这里我们着重分析下第1,6步

首先看下AudioPolicyManager.cpp::getInputForAttr()的第1步.获取policyMix设备以及对应的audio_device_t设备类型(device)

audio_devices_t AudioPolicyManager::getDeviceAndMixForInputSource(audio_source_t inputSource,
AudioMix **policyMix)
{
audio_devices_t availableDeviceTypes = mAvailableInputDevices.types() &
~AUDIO_DEVICE_BIT_IN; for (size_t i = 0; i < mPolicyMixes.size(); i++) {
if (mPolicyMixes[i]->mMix.mMixType != MIX_TYPE_RECORDERS) {
continue;
}
for (size_t j = 0; j < mPolicyMixes[i]->mMix.mCriteria.size(); j++) {
if ((RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET == mPolicyMixes[i]->mMix.mCriteria[j].mRule &&
mPolicyMixes[i]->mMix.mCriteria[j].mAttr.mSource == inputSource) ||
(RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET == mPolicyMixes[i]->mMix.mCriteria[j].mRule &&
mPolicyMixes[i]->mMix.mCriteria[j].mAttr.mSource != inputSource)) {
if (availableDeviceTypes & AUDIO_DEVICE_IN_REMOTE_SUBMIX) {
if (policyMix != NULL) {
*policyMix = &mPolicyMixes[i]->mMix;
}
return AUDIO_DEVICE_IN_REMOTE_SUBMIX;
}
break;
}
}
} return getDeviceForInputSource(inputSource);
} audio_devices_t AudioPolicyManager::getDeviceForInputSource(audio_source_t inputSource)
{
uint32_t device = AUDIO_DEVICE_NONE;
audio_devices_t availableDeviceTypes = mAvailableInputDevices.types() &
~AUDIO_DEVICE_BIT_IN; switch (inputSource) {
case AUDIO_SOURCE_VOICE_UPLINK:
if (availableDeviceTypes & AUDIO_DEVICE_IN_VOICE_CALL) {
device = AUDIO_DEVICE_IN_VOICE_CALL;
break;
}
break; case AUDIO_SOURCE_DEFAULT:
case AUDIO_SOURCE_MIC:
if (availableDeviceTypes & AUDIO_DEVICE_IN_BLUETOOTH_A2DP) {
device = AUDIO_DEVICE_IN_BLUETOOTH_A2DP;
} else if ((mForceUse[AUDIO_POLICY_FORCE_FOR_RECORD] == AUDIO_POLICY_FORCE_BT_SCO) &&
(availableDeviceTypes & AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET)) {
device = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET;
} else if (availableDeviceTypes & AUDIO_DEVICE_IN_WIRED_HEADSET) {
device = AUDIO_DEVICE_IN_WIRED_HEADSET;
} else if (availableDeviceTypes & AUDIO_DEVICE_IN_USB_DEVICE) {
device = AUDIO_DEVICE_IN_USB_DEVICE;
} else if (availableDeviceTypes & AUDIO_DEVICE_IN_BUILTIN_MIC) {
device = AUDIO_DEVICE_IN_BUILTIN_MIC;
}
break; case AUDIO_SOURCE_VOICE_COMMUNICATION:
// Allow only use of devices on primary input if in call and HAL does not support routing
// to voice call path.
if ((mPhoneState == AUDIO_MODE_IN_CALL) &&
(mAvailableOutputDevices.types() & AUDIO_DEVICE_OUT_TELEPHONY_TX) == 0) {
availableDeviceTypes = availablePrimaryInputDevices() & ~AUDIO_DEVICE_BIT_IN;
} switch (mForceUse[AUDIO_POLICY_FORCE_FOR_COMMUNICATION]) {
case AUDIO_POLICY_FORCE_BT_SCO:
// if SCO device is requested but no SCO device is available, fall back to default case
if (availableDeviceTypes & AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET) {
device = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET;
break;
}
// FALL THROUGH default: // FORCE_NONE
if (availableDeviceTypes & AUDIO_DEVICE_IN_WIRED_HEADSET) {
device = AUDIO_DEVICE_IN_WIRED_HEADSET;
} else if (availableDeviceTypes & AUDIO_DEVICE_IN_USB_DEVICE) {
device = AUDIO_DEVICE_IN_USB_DEVICE;
} else if (availableDeviceTypes & AUDIO_DEVICE_IN_BUILTIN_MIC) {
device = AUDIO_DEVICE_IN_BUILTIN_MIC;
}
break; case AUDIO_POLICY_FORCE_SPEAKER:
if (availableDeviceTypes & AUDIO_DEVICE_IN_BACK_MIC) {
device = AUDIO_DEVICE_IN_BACK_MIC;
} else if (availableDeviceTypes & AUDIO_DEVICE_IN_BUILTIN_MIC) {
device = AUDIO_DEVICE_IN_BUILTIN_MIC;
}
break;
}
break; case AUDIO_SOURCE_VOICE_RECOGNITION:
case AUDIO_SOURCE_HOTWORD:
if (mForceUse[AUDIO_POLICY_FORCE_FOR_RECORD] == AUDIO_POLICY_FORCE_BT_SCO &&
availableDeviceTypes & AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET) {
device = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET;
} else if (availableDeviceTypes & AUDIO_DEVICE_IN_WIRED_HEADSET) {
device = AUDIO_DEVICE_IN_WIRED_HEADSET;
} else if (availableDeviceTypes & AUDIO_DEVICE_IN_USB_DEVICE) {
device = AUDIO_DEVICE_IN_USB_DEVICE;
} else if (availableDeviceTypes & AUDIO_DEVICE_IN_BUILTIN_MIC) {
device = AUDIO_DEVICE_IN_BUILTIN_MIC;
}
break;
case AUDIO_SOURCE_CAMCORDER:
if (availableDeviceTypes & AUDIO_DEVICE_IN_BACK_MIC) {
device = AUDIO_DEVICE_IN_BACK_MIC;
} else if (availableDeviceTypes & AUDIO_DEVICE_IN_BUILTIN_MIC) {
device = AUDIO_DEVICE_IN_BUILTIN_MIC;
}
break;
case AUDIO_SOURCE_VOICE_DOWNLINK:
case AUDIO_SOURCE_VOICE_CALL:
if (availableDeviceTypes & AUDIO_DEVICE_IN_VOICE_CALL) {
device = AUDIO_DEVICE_IN_VOICE_CALL;
}
break;
case AUDIO_SOURCE_REMOTE_SUBMIX:
if (availableDeviceTypes & AUDIO_DEVICE_IN_REMOTE_SUBMIX) {
device = AUDIO_DEVICE_IN_REMOTE_SUBMIX;
}
break;
case AUDIO_SOURCE_FM_TUNER:
if (availableDeviceTypes & AUDIO_DEVICE_IN_FM_TUNER) {
device = AUDIO_DEVICE_IN_FM_TUNER;
}
break;
default:
ALOGW("getDeviceForInputSource() invalid input source %d", inputSource);
break;
}
ALOGV("getDeviceForInputSource()input source %d, device %08x", inputSource, device);
return device;
}

这里就是通过InputSource去获取相应的policyMix与audio_device_t设备类型了,从这里也可以看出Android系统上对Audio设备的分类有多少种了。

然后再看下AudioPolicyManager.cpp::getInputForAttr()的第6步.mpClientInterface->openInput如何建立起输入流

frameworks\av\services\audiopolicy\AudioPolicyClientImpl.cpp

status_t AudioPolicyService::AudioPolicyClient::openInput(audio_module_handle_t module,
audio_io_handle_t *input,
audio_config_t *config,
audio_devices_t *device,
const String8& address,
audio_source_t source,
audio_input_flags_t flags)
{
sp<IAudioFlinger> af = AudioSystem::get_audio_flinger();
if (af == 0) {
ALOGW("%s: could not get AudioFlinger", __func__);
return PERMISSION_DENIED;
} return af->openInput(module, input, config, device, address, source, flags);
}

这里就调用到了AF端的openInput函数了

frameworks\av\services\audioflinger\AudioFlinger.cpp

status_t AudioFlinger::openInput(audio_module_handle_t module,
audio_io_handle_t *input,
audio_config_t *config,
audio_devices_t *device,
const String8& address,
audio_source_t source,
audio_input_flags_t flags)
{
Mutex::Autolock _l(mLock); if (*device == AUDIO_DEVICE_NONE) {
return BAD_VALUE;
} sp<RecordThread> thread = openInput_l(module, input, config, *device, address, source, flags); if (thread != 0) {
// notify client processes of the new input creation
thread->audioConfigChanged(AudioSystem::INPUT_OPENED);
return NO_ERROR;
}
return NO_INIT;
} sp<AudioFlinger::RecordThread> AudioFlinger::openInput_l(audio_module_handle_t module,
audio_io_handle_t *input,
audio_config_t *config,
audio_devices_t device,
const String8& address,
audio_source_t source,
audio_input_flags_t flags)
{
AudioHwDevice *inHwDev = findSuitableHwDev_l(module, device);
if (inHwDev == NULL) {
*input = AUDIO_IO_HANDLE_NONE;
return 0;
} if (*input == AUDIO_IO_HANDLE_NONE) {
*input = nextUniqueId();
} audio_config_t halconfig = *config;
audio_hw_device_t *inHwHal = inHwDev->hwDevice();
audio_stream_in_t *inStream = NULL;
//获取inStream对象
status_t status = inHwHal->open_input_stream(inHwHal, *input, device, &halconfig,
&inStream, flags, address.string(), source); // If the input could not be opened with the requested parameters and we can handle the
// conversion internally, try to open again with the proposed parameters. The AudioFlinger can
// resample the input and do mono to stereo or stereo to mono conversions on 16 bit PCM inputs.
if (status == BAD_VALUE &&
config->format == halconfig.format && halconfig.format == AUDIO_FORMAT_PCM_16_BIT &&
(halconfig.sample_rate <= 2 * config->sample_rate) &&
(audio_channel_count_from_in_mask(halconfig.channel_mask) <= FCC_2) &&
(audio_channel_count_from_in_mask(config->channel_mask) <= FCC_2)) {
// FIXME describe the change proposed by HAL (save old values so we can log them here)
ALOGV("openInput_l() reopening with proposed sampling rate and channel mask");
inStream = NULL;
status = inHwHal->open_input_stream(inHwHal, *input, device, &halconfig,
&inStream, flags, address.string(), source);
// FIXME log this new status; HAL should not propose any further changes
} if (status == NO_ERROR && inStream != NULL) { #ifdef TEE_SINK
// Try to re-use most recently used Pipe to archive a copy of input for dumpsys,
// or (re-)create if current Pipe is idle and does not match the new format
sp<NBAIO_Sink> teeSink;
enum {
TEE_SINK_NO, // don't copy input
TEE_SINK_NEW, // copy input using a new pipe
TEE_SINK_OLD, // copy input using an existing pipe
} kind;
NBAIO_Format format = Format_from_SR_C(halconfig.sample_rate,
audio_channel_count_from_in_mask(halconfig.channel_mask), halconfig.format);
if (!mTeeSinkInputEnabled) {
kind = TEE_SINK_NO;
} else if (!Format_isValid(format)) {
kind = TEE_SINK_NO;
} else if (mRecordTeeSink == 0) {
kind = TEE_SINK_NEW;
} else if (mRecordTeeSink->getStrongCount() != 1) {
kind = TEE_SINK_NO;
} else if (Format_isEqual(format, mRecordTeeSink->format())) {
kind = TEE_SINK_OLD;
} else {
kind = TEE_SINK_NEW;
}
switch (kind) {
case TEE_SINK_NEW: {
Pipe *pipe = new Pipe(mTeeSinkInputFrames, format);
size_t numCounterOffers = 0;
const NBAIO_Format offers[1] = {format};
ssize_t index = pipe->negotiate(offers, 1, NULL, numCounterOffers);
ALOG_ASSERT(index == 0);
PipeReader *pipeReader = new PipeReader(*pipe);
numCounterOffers = 0;
index = pipeReader->negotiate(offers, 1, NULL, numCounterOffers);
ALOG_ASSERT(index == 0);
mRecordTeeSink = pipe;
mRecordTeeSource = pipeReader;
teeSink = pipe;
}
break;
case TEE_SINK_OLD:
teeSink = mRecordTeeSink;
break;
case TEE_SINK_NO:
default:
break;
}
#endif
AudioStreamIn *inputStream = new AudioStreamIn(inHwDev, inStream);
// Start record thread
// RecordThread requires both input and output device indication to forward to audio
// pre processing modules
sp<RecordThread> thread = new RecordThread(this,
inputStream,
*input,
primaryOutputDevice_l(),
device
#ifdef TEE_SINK
, teeSink
#endif
);
mRecordThreads.add(*input, thread);
ALOGV("openInput_l() created record thread: ID %d thread %p", *input, thread.get());
return thread;
} *input = AUDIO_IO_HANDLE_NONE;
return 0;
}

在这个函数中主要工作如下:

1.findSuitableHwDev_l中通过IOProfile中的module.handle与audio_device_t设备类型找到Hw模块;

2.调用HAL层inHwHal->open_input_stream打开输入流;

3.如果失败了,再继续调用一次;

4.根据inHwDev与inStream创建AudioStreamIn对象,如此,就建立起了一个输入流了,AudioStreamIn定义在frameworks\av\services\audioflinger\AudioFlinger.h;

5.创建一个RecordThread线程,并把该线程加入到mRecordThreads线程中,这个线程是在AudioRecord.cpp::set()函数中创建的;

这里我们着重分析第2、5步:

首先看下AudioFlinger.cpp::openInput()的第2步:打开输入流

hardware\aw\audio\tulip\audio_hw.c

static int adev_open_input_stream(struct audio_hw_device *dev,
audio_io_handle_t handle,
audio_devices_t devices,
struct audio_config *config,
struct audio_stream_in **stream_in)
{
struct sunxi_audio_device *ladev = (struct sunxi_audio_device *)dev;
struct sunxi_stream_in *in;
int ret;
int channel_count = popcount(config->channel_mask); *stream_in = NULL; if (check_input_parameters(config->sample_rate, config->format, channel_count) != 0)
return -EINVAL; in = (struct sunxi_stream_in *)calloc(1, sizeof(struct sunxi_stream_in));
if (!in)
return -ENOMEM; in->stream.common.get_sample_rate = in_get_sample_rate;
in->stream.common.set_sample_rate = in_set_sample_rate;
in->stream.common.get_buffer_size = in_get_buffer_size;
in->stream.common.get_channels = in_get_channels;
in->stream.common.get_format = in_get_format;
in->stream.common.set_format = in_set_format;
in->stream.common.standby = in_standby;
in->stream.common.dump = in_dump;
in->stream.common.set_parameters = in_set_parameters;
in->stream.common.get_parameters = in_get_parameters;
in->stream.common.add_audio_effect = in_add_audio_effect;
in->stream.common.remove_audio_effect = in_remove_audio_effect;
in->stream.set_gain = in_set_gain;
in->stream.read = in_read;
in->stream.get_input_frames_lost = in_get_input_frames_lost; in->requested_rate = config->sample_rate; // default config
memcpy(&in->config, &pcm_config_mm_in, sizeof(pcm_config_mm_in));
in->config.channels = channel_count;
//in->config.in_init_channels = channel_count; in->buffer = malloc(in->config.period_size *
audio_stream_frame_size(&in->stream.common) * 8); if (!in->buffer) {
ret = -ENOMEM;
goto err;
}
memset(in->buffer, 0, in->config.period_size *
audio_stream_frame_size(&in->stream.common) * 8); //mute ladev->af_capture_flag = false;
//devices = AUDIO_DEVICE_IN_WIFI_DISPLAY;//for test if (devices == AUDIO_DEVICE_IN_AF) {
ALOGV("to malloc PcmManagerBuffer: Buffer_size: %d", AF_BUFFER_SIZE);
ladev->PcmManager.BufStart= (unsigned char *)malloc(AF_BUFFER_SIZE); if(!ladev->PcmManager.BufStart) {
ret = -ENOMEM;
goto err;
} ladev->PcmManager.BufExist = true;
ladev->PcmManager.BufTotalLen = AF_BUFFER_SIZE;
ladev->PcmManager.BufWritPtr = ladev->PcmManager.BufStart;
ladev->PcmManager.BufReadPtr = ladev->PcmManager.BufStart;
ladev->PcmManager.BufValideLen = ladev->PcmManager.BufTotalLen;
ladev->PcmManager.DataLen = 0;
ladev->PcmManager.SampleRate = config->sample_rate;
ladev->PcmManager.Channel = 2;
ladev->af_capture_flag = true; ladev->PcmManager.dev = (struct sunxi_audio_device *)ladev;
} in->dev = ladev;
in->standby = 1;
in->device = devices & ~AUDIO_DEVICE_BIT_IN; *stream_in = &in->stream;
return 0; err:
if (in->resampler)
release_resampler(in->resampler); free(in);
return ret;
}

在这个函数中主要工作如下:

1.检查rate,format,channel参数是否支持;

2.给sunxi_stream_in输入流对象分配内存空间;

3.绑定相应参数的获取/设置方法;

4.为输入流创建buff空间:in->config.period_size *audio_stream_frame_size(&in->stream.common) * 8;

5.如果是AUDIO_DEVICE_IN_AF类型的设备的话,则对PcmManager做相应处理;

这个输入流对象会绑定到AF中的AudioStreamIn对象中,所以到这里,输入流对象就已经完全创建好了。

然后继续分析AudioFlinger.cpp::openInput()的第5步:创建RecordThread线程:

frameworks\av\services\audioflinger\Threads.cpp

AudioFlinger::RecordThread::RecordThread(const sp<AudioFlinger>& audioFlinger,
AudioStreamIn *input,
audio_io_handle_t id,
audio_devices_t outDevice,
audio_devices_t inDevice
#ifdef TEE_SINK
, const sp<NBAIO_Sink>& teeSink
#endif
) :
ThreadBase(audioFlinger, id, outDevice, inDevice, RECORD),
mInput(input), mActiveTracksGen(0), mRsmpInBuffer(NULL),
// mRsmpInFrames and mRsmpInFramesP2 are set by readInputParameters_l()
mRsmpInRear(0)
#ifdef TEE_SINK
, mTeeSink(teeSink)
#endif
, mReadOnlyHeap(new MemoryDealer(kRecordThreadReadOnlyHeapSize,
"RecordThreadRO", MemoryHeapBase::READ_ONLY))
// mFastCapture below
, mFastCaptureFutex(0)
// mInputSource
// mPipeSink
// mPipeSource
, mPipeFramesP2(0)
// mPipeMemory
// mFastCaptureNBLogWriter
, mFastTrackAvail(false)
{
snprintf(mName, kNameLength, "AudioIn_%X", id);
mNBLogWriter = audioFlinger->newWriter_l(kLogSize, mName); readInputParameters_l();
// create an NBAIO source for the HAL input stream, and negotiate
mInputSource = new AudioStreamInSource(input->stream);
size_t numCounterOffers = 0;
const NBAIO_Format offers[1] = {Format_from_SR_C(mSampleRate, mChannelCount, mFormat)};
ssize_t index = mInputSource->negotiate(offers, 1, NULL, numCounterOffers);
ALOG_ASSERT(index == 0); // initialize fast capture depending on configuration
bool initFastCapture;
switch (kUseFastCapture) {
case FastCapture_Never:
initFastCapture = false;
break;
case FastCapture_Always:
initFastCapture = true;
break;
case FastCapture_Static:
uint32_t primaryOutputSampleRate;
{
AutoMutex _l(audioFlinger->mHardwareLock);
primaryOutputSampleRate = audioFlinger->mPrimaryOutputSampleRate;
}
initFastCapture =
// either capture sample rate is same as (a reasonable) primary output sample rate
(((primaryOutputSampleRate == 44100 || primaryOutputSampleRate == 48000) &&
(mSampleRate == primaryOutputSampleRate)) ||
// or primary output sample rate is unknown, and capture sample rate is reasonable
((primaryOutputSampleRate == 0) &&
((mSampleRate == 44100 || mSampleRate == 48000)))) &&
// and the buffer size is < 12 ms
(mFrameCount * 1000) / mSampleRate < 12;
break;
// case FastCapture_Dynamic:
} if (initFastCapture) {
// create a Pipe for FastMixer to write to, and for us and fast tracks to read from
NBAIO_Format format = mInputSource->format();
size_t pipeFramesP2 = roundup(mSampleRate / 25); // double-buffering of 20 ms each
size_t pipeSize = pipeFramesP2 * Format_frameSize(format);
void *pipeBuffer;
const sp<MemoryDealer> roHeap(readOnlyHeap());
sp<IMemory> pipeMemory;
if ((roHeap == 0) ||
(pipeMemory = roHeap->allocate(pipeSize)) == 0 ||
(pipeBuffer = pipeMemory->pointer()) == NULL) {
ALOGE("not enough memory for pipe buffer size=%zu", pipeSize);
goto failed;
}
// pipe will be shared directly with fast clients, so clear to avoid leaking old information
memset(pipeBuffer, 0, pipeSize);
Pipe *pipe = new Pipe(pipeFramesP2, format, pipeBuffer);
const NBAIO_Format offers[1] = {format};
size_t numCounterOffers = 0;
ssize_t index = pipe->negotiate(offers, 1, NULL, numCounterOffers);
ALOG_ASSERT(index == 0);
mPipeSink = pipe;
PipeReader *pipeReader = new PipeReader(*pipe);
numCounterOffers = 0;
index = pipeReader->negotiate(offers, 1, NULL, numCounterOffers);
ALOG_ASSERT(index == 0);
mPipeSource = pipeReader;
mPipeFramesP2 = pipeFramesP2;
mPipeMemory = pipeMemory; // create fast capture
mFastCapture = new FastCapture();
FastCaptureStateQueue *sq = mFastCapture->sq();
#ifdef STATE_QUEUE_DUMP
// FIXME
#endif
FastCaptureState *state = sq->begin();
state->mCblk = NULL;
state->mInputSource = mInputSource.get();
state->mInputSourceGen++;
state->mPipeSink = pipe;
state->mPipeSinkGen++;
state->mFrameCount = mFrameCount;
state->mCommand = FastCaptureState::COLD_IDLE;
// already done in constructor initialization list
//mFastCaptureFutex = 0;
state->mColdFutexAddr = &mFastCaptureFutex;
state->mColdGen++;
state->mDumpState = &mFastCaptureDumpState;
#ifdef TEE_SINK
// FIXME
#endif
mFastCaptureNBLogWriter = audioFlinger->newWriter_l(kFastCaptureLogSize, "FastCapture");
state->mNBLogWriter = mFastCaptureNBLogWriter.get();
sq->end();
sq->push(FastCaptureStateQueue::BLOCK_UNTIL_PUSHED); // start the fast capture
mFastCapture->run("FastCapture", ANDROID_PRIORITY_URGENT_AUDIO);
pid_t tid = mFastCapture->getTid();
int err = requestPriority(getpid_cached, tid, kPriorityFastMixer);
if (err != 0) {
ALOGW("Policy SCHED_FIFO priority %d is unavailable for pid %d tid %d; error %d",
kPriorityFastCapture, getpid_cached, tid, err);
} #ifdef AUDIO_WATCHDOG
// FIXME
#endif mFastTrackAvail = true;
}
failed: ; // FIXME mNormalSource
}

在这个线程中主要工作如下:

1.调用readInputParameters_l函数把录音参数读取到线程空间中;

2.创建AudioStreamInSource对象,作为线程中间中的输入流,其实现是在frameworks\av\media\libnbaio\AudioStreamInSource.cpp;

所以可以猜到,后续在启动录音时,RecordThread中将会通过AudioStreamInSource对象进行获取数据,实时更新共享内存中的数据。

再回到AudioRecord.cpp::openRecord_l(0)的第4步.创建IAudioRecord对象

sp<IAudioRecord> AudioFlinger::openRecord(
audio_io_handle_t input,
uint32_t sampleRate,
audio_format_t format,
audio_channel_mask_t channelMask,
size_t *frameCount,
IAudioFlinger::track_flags_t *flags,
pid_t tid,
int *sessionId,
size_t *notificationFrames,
sp<IMemory>& cblk,
sp<IMemory>& buffers,
status_t *status)
{
sp<RecordThread::RecordTrack> recordTrack;
sp<RecordHandle> recordHandle;
sp<Client> client;
status_t lStatus;
int lSessionId; cblk.clear();
buffers.clear(); // check calling permissions
if (!recordingAllowed()) {
ALOGE("openRecord() permission denied: recording not allowed");
lStatus = PERMISSION_DENIED;
goto Exit;
} // further sample rate checks are performed by createRecordTrack_l()
if (sampleRate == 0) {
ALOGE("openRecord() invalid sample rate %u", sampleRate);
lStatus = BAD_VALUE;
goto Exit;
} // we don't yet support anything other than 16-bit PCM
if (!(audio_is_valid_format(format) &&
audio_is_linear_pcm(format) && format == AUDIO_FORMAT_PCM_16_BIT)) {
ALOGE("openRecord() invalid format %#x", format);
lStatus = BAD_VALUE;
goto Exit;
} // further channel mask checks are performed by createRecordTrack_l()
if (!audio_is_input_channel(channelMask)) {
ALOGE("openRecord() invalid channel mask %#x", channelMask);
lStatus = BAD_VALUE;
goto Exit;
} {
Mutex::Autolock _l(mLock);
RecordThread *thread = checkRecordThread_l(input);
if (thread == NULL) {
ALOGE("openRecord() checkRecordThread_l failed");
lStatus = BAD_VALUE;
goto Exit;
} pid_t pid = IPCThreadState::self()->getCallingPid();
client = registerPid(pid); if (sessionId != NULL && *sessionId != AUDIO_SESSION_ALLOCATE) {
lSessionId = *sessionId;
} else {
// if no audio session id is provided, create one here
lSessionId = nextUniqueId();
if (sessionId != NULL) {
*sessionId = lSessionId;
}
} // TODO: the uid should be passed in as a parameter to openRecord
recordTrack = thread->createRecordTrack_l(client, sampleRate, format, channelMask,
frameCount, lSessionId, notificationFrames,
IPCThreadState::self()->getCallingUid(),
flags, tid, &lStatus);
LOG_ALWAYS_FATAL_IF((lStatus == NO_ERROR) && (recordTrack == 0)); if (lStatus == NO_ERROR) {
// Check if one effect chain was awaiting for an AudioRecord to be created on this
// session and move it to this thread.
sp<EffectChain> chain = getOrphanEffectChain_l((audio_session_t)lSessionId);
if (chain != 0) {
Mutex::Autolock _l(thread->mLock);
thread->addEffectChain_l(chain);
}
}
} if (lStatus != NO_ERROR) {
// remove local strong reference to Client before deleting the RecordTrack so that the
// Client destructor is called by the TrackBase destructor with mClientLock held
// Don't hold mClientLock when releasing the reference on the track as the
// destructor will acquire it.
{
Mutex::Autolock _cl(mClientLock);
client.clear();
}
recordTrack.clear();
goto Exit;
} cblk = recordTrack->getCblk();
buffers = recordTrack->getBuffers(); // return handle to client
recordHandle = new RecordHandle(recordTrack); Exit:
*status = lStatus;
return recordHandle;
}

在这个函数中主要工作如下:

1.调用recordingAllowed检查录音权限;

2.判断参数是否非法;

3.调用checkRecordThread_l函数,根据input从AudioRecordThread线程中获取该input的RecordThread,在前面的分析中可以得知,这个RecordThread是在AudioFlinger.cpp::openInput函数中创建并添加到AudioRecordThread中的;

4.调用createRecordTrack_l方法创建一个RecordTrack对象,RecordThread::RecordTrack对象的作用是管理RecordThread中的音频数据;

5.通过SessionId获取是否存在effect chain,若有,则加到RecordThread中;

6.通过RecordTrack获取cblk以及buffers,他们就是CblkMemory以及BufferMemory;

7.根据recordTrack,创建RecordHandle对象,实现位置:frameworks\av\services\audioflinger\Tracks.cpp,也就完成了IAudioRecord对象的创建了,也就是说IAudioRecord的方法是在Tracks.cpp中实现的;

这里再看下第2步:createRecordTrack_l函数

sp<AudioFlinger::RecordThread::RecordTrack> AudioFlinger::RecordThread::createRecordTrack_l(
const sp<AudioFlinger::Client>& client,
uint32_t sampleRate,
audio_format_t format,
audio_channel_mask_t channelMask,
size_t *pFrameCount,
int sessionId,
size_t *notificationFrames,
int uid,
IAudioFlinger::track_flags_t *flags,
pid_t tid,
status_t *status)
{
size_t frameCount = *pFrameCount;
sp<RecordTrack> track;
status_t lStatus; // client expresses a preference for FAST, but we get the final say
if (*flags & IAudioFlinger::TRACK_FAST) {
if (
// use case: callback handler
(tid != -1) &&
// frame count is not specified, or is exactly the pipe depth
((frameCount == 0) || (frameCount == mPipeFramesP2)) &&
// PCM data
audio_is_linear_pcm(format) &&
// native format
(format == mFormat) &&
// native channel mask
(channelMask == mChannelMask) &&
// native hardware sample rate
(sampleRate == mSampleRate) &&
// record thread has an associated fast capture
hasFastCapture() &&
// there are sufficient fast track slots available
mFastTrackAvail
) {
ALOGV("AUDIO_INPUT_FLAG_FAST accepted: frameCount=%u mFrameCount=%u",
frameCount, mFrameCount);
} else {
ALOGV("AUDIO_INPUT_FLAG_FAST denied: frameCount=%u mFrameCount=%u mPipeFramesP2=%u "
"format=%#x isLinear=%d channelMask=%#x sampleRate=%u mSampleRate=%u "
"hasFastCapture=%d tid=%d mFastTrackAvail=%d",
frameCount, mFrameCount, mPipeFramesP2,
format, audio_is_linear_pcm(format), channelMask, sampleRate, mSampleRate,
hasFastCapture(), tid, mFastTrackAvail);
*flags &= ~IAudioFlinger::TRACK_FAST;
}
} // compute track buffer size in frames, and suggest the notification frame count
if (*flags & IAudioFlinger::TRACK_FAST) {
// fast track: frame count is exactly the pipe depth
frameCount = mPipeFramesP2;
// ignore requested notificationFrames, and always notify exactly once every HAL buffer
*notificationFrames = mFrameCount;
} else {
// not fast track: max notification period is resampled equivalent of one HAL buffer time
// or 20 ms if there is a fast capture
// TODO This could be a roundupRatio inline, and const
size_t maxNotificationFrames = ((int64_t) (hasFastCapture() ? mSampleRate/50 : mFrameCount)
* sampleRate + mSampleRate - 1) / mSampleRate;
// minimum number of notification periods is at least kMinNotifications,
// and at least kMinMs rounded up to a whole notification period (minNotificationsByMs)
static const size_t kMinNotifications = 3;
static const uint32_t kMinMs = 30;
// TODO This could be a roundupRatio inline
const size_t minFramesByMs = (sampleRate * kMinMs + 1000 - 1) / 1000;
// TODO This could be a roundupRatio inline
const size_t minNotificationsByMs = (minFramesByMs + maxNotificationFrames - 1) /
maxNotificationFrames;
const size_t minFrameCount = maxNotificationFrames *
max(kMinNotifications, minNotificationsByMs);
frameCount = max(frameCount, minFrameCount);
if (*notificationFrames == 0 || *notificationFrames > maxNotificationFrames) {
*notificationFrames = maxNotificationFrames;
}
}
*pFrameCount = frameCount; lStatus = initCheck();
if (lStatus != NO_ERROR) {
ALOGE("createRecordTrack_l() audio driver not initialized");
goto Exit;
} { // scope for mLock
Mutex::Autolock _l(mLock); track = new RecordTrack(this, client, sampleRate,
format, channelMask, frameCount, NULL, sessionId, uid,
*flags, TrackBase::TYPE_DEFAULT); lStatus = track->initCheck();
if (lStatus != NO_ERROR) {
ALOGE("createRecordTrack_l() initCheck failed %d; no control block?", lStatus);
// track must be cleared from the caller as the caller has the AF lock
goto Exit;
}
mTracks.add(track); // disable AEC and NS if the device is a BT SCO headset supporting those pre processings
bool suspend = audio_is_bluetooth_sco_device(mInDevice) &&
mAudioFlinger->btNrecIsOff();
setEffectSuspended_l(FX_IID_AEC, suspend, sessionId);
setEffectSuspended_l(FX_IID_NS, suspend, sessionId); if ((*flags & IAudioFlinger::TRACK_FAST) && (tid != -1)) {
pid_t callingPid = IPCThreadState::self()->getCallingPid();
// we don't have CAP_SYS_NICE, nor do we want to have it as it's too powerful,
// so ask activity manager to do this on our behalf
sendPrioConfigEvent_l(callingPid, tid, kPriorityAudioApp);
}
} lStatus = NO_ERROR; Exit:
*status = lStatus;
return track;
}

这个函数重要的一点就是重新计算了frameCount大小,然后根据新的参数创建了RecordTrack对象,然后return。

总结:

当应用层new AudioRecord时,系统建立起了输入流,并创建了RecordThread线程,现在录音的准备工作已经完成,就等待应用层开启录音了。

由于作者内功有限,若文章中存在错误或不足的地方,还请给位大佬指出,不胜感激!

(二)Audio子系统之new AudioRecord()的更多相关文章

  1. (二)Audio子系统之new AudioRecord()(Android4.4)

    在上一篇文章<(一)Audio子系统之AudioRecord.getMinBufferSize>中已经介绍了AudioRecord如何获取最小缓冲区大小,接下来,继续分析AudioReco ...

  2. (三)Audio子系统之AudioRecord.startRecording

    在上一篇文章<(二)Audio子系统之new AudioRecord()>中已经介绍了Audio系统如何创建AudioRecord对象以及输入流,并创建了RecordThread线程,接下 ...

  3. (一)Audio子系统之AudioRecord.getMinBufferSize

    在文章<基于Allwinner的Audio子系统分析(Android-5.1)>中已经介绍了Audio的系统架构以及应用层调用的流程,接下来,继续分析AudioRecorder方法中的ge ...

  4. (四)Audio子系统之AudioRecord.read

      在上一篇文章<(三)Audio子系统之AudioRecord.startRecording>中已经介绍了AudioRecord如何开始录制音频,接下来,继续分析AudioRecord方 ...

  5. (五)Audio子系统之AudioRecord.stop

    在上一篇文章<(四)Audio子系统之AudioRecord.read>中已经介绍了AudioRecord如何获取音频数据,接下来,继续分析AudioRecord方法中的stop的实现 函 ...

  6. (六)Audio子系统之AudioRecord.release

      在上一篇文章<(五)Audio子系统之AudioRecord.stop>中已经介绍了AudioRecord如何暂停录制,接下来,继续分析AudioRecord方法中的release的实 ...

  7. 基于Allwinner的Audio子系统分析(Android-5.1)

    前言 一直想总结下Audio子系统的博客,但是各种原因(主要还是自己懒>_<),一直拖到现在才开始重新整理,期间看过H8(Android-4.4),T3(Android-4.4),A64( ...

  8. Android音视频之AudioRecord录音(一)

    在音视频开发中,录音当然是必不可少的.首先我们要学会单独的录音功能,当然这里说的录音是指用AudioRecord来录音,读取录音原始数据,读到的就是所谓的PCM数据.对于录音来说,最重要的几个参数要搞 ...

  9. audio元素和video元素在ios和andriod中无法自动播放

    原因: 因为各大浏览器都为了节省流量,做出了优化,在用户没有行为动作时(交互)不予许自动播放: /音频,写法一 <audio src="music/bg.mp3" autop ...

随机推荐

  1. java通过经纬度计算两个点的之间的距离的算法

    通过两个点的经纬度计算距离 从google maps的脚本里扒了段代码,没准啥时会用上.大家一块看看是怎么算的. private const double EARTH_RADIUS = 6378.13 ...

  2. curl:get,post 以及SoapClien访问webservice数据

    一.curl get模式 public function close_order(){ $url="http://192.168.2.50:7777/U8API.asmx?op=Insert ...

  3. RocketMQ服务器监控误区

    请不要监控10912端口 case: result: 监控10912端口会导致HAService异常,新起线程,吃掉内存,无限次监控虚拟机将宕机! 时间上是直接因果关系. 监控10911 和 9876 ...

  4. Android-Activity启动模式(launchMode)

    Activity启动模式是非常重要的一块内容,启动模式直接关系到用户的体验 和 性能的提升等 Activity启动模式分为四种: 如果不配置:launchMode,默认就是:standard 标准的 ...

  5. 使用python读写excel

    项目中要在excel要跨工作簿根据一列数据获取另一列的数据,而excel本身的函数vlookup一直不太好用,只能用程序进行处理了,最近刚接触了python,灵机一动使用Python进行处理,先将js ...

  6. Bug报告提交规范

    首先声明,bug的测试规范应该在公司的正式文档建立.本建议非正式文档,有些内容可能不正确,有些内容可能需要继续商榷,甚至有些内容同公司规范有冲突.如果发现问题,直接忽略本文相应内容.本帖本意仅就工作中 ...

  7. Solr相似度算法三:DRFSimilarity框架介绍

    地址:http://terrier.org/docs/v3.5/dfr_description.html The Divergence from Randomness (DFR) paradigm i ...

  8. django 的 ajax 方式上传图片

    页面代码: <html>     <!-- 引入相关的js文件,相对路径  -->     <script type="text/javascript" ...

  9. ES6摘抄

    1.函数可选参数function log(x, y = 'World') {} 只能作为尾参数使用,因为如果不是尾参数还是要输入的.2.函数参数默认值与解构赋值结合使用.(注意对象冒号解构等号)fun ...

  10. C#多边形求角——实例说

    前段时间有写过一个计算多边形角度的代码,这里给它整理整理,留给自己也送给萌新. 看左下图,这是一个多环的多边形,一个外环(内部为多边形内部区域),一个内环(外部为多边形内部区域),同时多边形中任意一个 ...