Android Audio代码分析15 - testPlaybackHeadPositionAfterFlush
2011-10-19 14:22
549 查看
上次看到的testPlaybackHeadPositionIncrease函数中,先play了一会,然后获取position。
今天看个复杂点的,先play,然后stop,之后在flush,此时再获取position会是什么情况呢?
*****************************************源码*************************************************
**********************************************************************************************
源码路径:
frameworks\base\media\tests\mediaframeworktest\src\com\android\mediaframeworktest\functional\MediaAudioTrackTest.java
#######################说明################################
###########################################################
&&&&&&&&&&&&&&&&&&&&&&&总结&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
stop的时候:
若AudioTrack不是active的,在将其audio_track_cblk_t中的user, server等清0。
若是direct output,则将其close。
flush的时候:
会将audio_track_cblk_t中的user, server等清0。
因此,flush 之后再get position的话,肯定是0.
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
今天看个复杂点的,先play,然后stop,之后在flush,此时再获取position会是什么情况呢?
*****************************************源码*************************************************
//Test case 3: getPlaybackHeadPosition() is 0 after flush(); @LargeTest public void testPlaybackHeadPositionAfterFlush() throws Exception { // constants for test final String TEST_NAME = "testPlaybackHeadPositionAfterFlush"; final int TEST_SR = 22050; final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO; final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; final int TEST_MODE = AudioTrack.MODE_STREAM; final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; //-------- initialization -------------- int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, minBuffSize, TEST_MODE); byte data[] = new byte[minBuffSize/2]; //-------- test -------------- assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); track.write(data, 0, data.length); track.write(data, 0, data.length); track.play(); Thread.sleep(100); track.stop(); track.flush(); log(TEST_NAME, "position ="+ track.getPlaybackHeadPosition()); assertTrue(TEST_NAME, track.getPlaybackHeadPosition() == 0); //-------- tear down -------------- track.release(); }
**********************************************************************************************
源码路径:
frameworks\base\media\tests\mediaframeworktest\src\com\android\mediaframeworktest\functional\MediaAudioTrackTest.java
#######################说明################################
//Test case 3: getPlaybackHeadPosition() is 0 after flush(); @LargeTest public void testPlaybackHeadPositionAfterFlush() throws Exception { // constants for test final String TEST_NAME = "testPlaybackHeadPositionAfterFlush"; final int TEST_SR = 22050; final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO; final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; final int TEST_MODE = AudioTrack.MODE_STREAM; final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; //-------- initialization -------------- int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, minBuffSize, TEST_MODE); byte data[] = new byte[minBuffSize/2]; //-------- test -------------- assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); track.write(data, 0, data.length); track.write(data, 0, data.length); track.play(); Thread.sleep(100); track.stop(); // ++++++++++++++++++++++++++++stop++++++++++++++++++++++++++++++++++++ /** * Stops playing the audio data. * @throws IllegalStateException */ public void stop() throws IllegalStateException { if (mState != STATE_INITIALIZED) { throw(new IllegalStateException("stop() called on uninitialized AudioTrack.")); } // stop playing synchronized(mPlayStateLock) { native_stop(); // ++++++++++++++++++++++++++++++android_media_AudioTrack_stop++++++++++++++++++++++++++++++++++ static void android_media_AudioTrack_stop(JNIEnv *env, jobject thiz) { AudioTrack *lpTrack = (AudioTrack *)env->GetIntField( thiz, javaAudioTrackFields.nativeTrackInJavaObj); if (lpTrack == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for stop()"); return; } lpTrack->stop(); // +++++++++++++++++++++++++++++++AudioTrack::stop+++++++++++++++++++++++++++++++++ void AudioTrack::stop() { // mAudioTrackThread在函数AudioTrack::set中被赋值。 // mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava); sp<AudioTrackThread> t = mAudioTrackThread; LOGV("stop %p", this); if (t != 0) { t->mLock.lock(); } if (android_atomic_and(~1, &mActive) == 1) { mCblk->cv.signal(); // mAudioTrack在函数AudioTrack::createTrack中被赋值,其最终指向的其实是一个TrackHandle对象 mAudioTrack->stop(); // ++++++++++++++++++++++++++++++AudioFlinger::TrackHandle::stop++++++++++++++++++++++++++++++++++ void AudioFlinger::TrackHandle::stop() { mTrack->stop(); // +++++++++++++++++++++++++++++AudioFlinger::PlaybackThread::Track::stop+++++++++++++++++++++++++++++++++++ void AudioFlinger::PlaybackThread::Track::stop() { LOGV("stop(%d), calling thread %d", mName, IPCThreadState::self()->getCallingPid()); // 在Track的构造函数中赋值,指向创建该Track对象的PlaybackThread sp<ThreadBase> thread = mThread.promote(); if (thread != 0) { Mutex::Autolock _l(thread->mLock); int state = mState; if (mState > STOPPED) { // ++++++++++++++++++++++++++++++++track_state++++++++++++++++++++++++++++++++ enum track_state { IDLE, TERMINATED, STOPPED, RESUMING, ACTIVE, PAUSING, PAUSED }; // --------------------------------track_state-------------------------------- mState = STOPPED; // If the track is not active (PAUSED and buffers full), flush buffers PlaybackThread *playbackThread = (PlaybackThread *)thread.get(); if (playbackThread->mActiveTracks.indexOf(this) < 0) { reset(); // ++++++++++++++++++++++++++++++AudioFlinger::PlaybackThread::Track::reset++++++++++++++++++++++++++++++++++ void AudioFlinger::PlaybackThread::Track::reset() { // Do not reset twice to avoid discarding data written just after a flush and before // the audioflinger thread detects the track is stopped. if (!mResetDone) { TrackBase::reset(); // ++++++++++++++++++++++++++++++AudioFlinger::ThreadBase::TrackBase::reset++++++++++++++++++++++++++++++++++ void AudioFlinger::ThreadBase::TrackBase::reset() { audio_track_cblk_t* cblk = this->cblk(); cblk->user = 0; cblk->server = 0; cblk->userBase = 0; cblk->serverBase = 0; mFlags &= (uint32_t)(~SYSTEM_FLAGS_MASK); LOGV("TrackBase::reset"); } // ------------------------------AudioFlinger::ThreadBase::TrackBase::reset---------------------------------- // Force underrun condition to avoid false underrun callback until first data is // written to buffer mCblk->flags |= CBLK_UNDERRUN_ON; mCblk->flags &= ~CBLK_FORCEREADY_MSK; mFillingUpStatus = FS_FILLING; mResetDone = true; } } // ------------------------------AudioFlinger::PlaybackThread::Track::reset---------------------------------- } LOGV("(> STOPPED) => STOPPED (%d) on thread %p", mName, playbackThread); } if (!isOutputTrack() && (state == ACTIVE || state == RESUMING)) { thread->mLock.unlock(); AudioSystem::stopOutput(thread->id(), (AudioSystem::stream_type)mStreamType, mSessionId); // ++++++++++++++++++++++++++++++AudioSystem::stopOutput++++++++++++++++++++++++++++++++++ status_t AudioSystem::stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream, int session) { const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service(); if (aps == 0) return PERMISSION_DENIED; return aps->stopOutput(output, stream, session); // +++++++++++++++++++++++++++++++++AudioPolicyService::stopOutput+++++++++++++++++++++++++++++++ status_t AudioPolicyService::stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream, int session) { if (mpPolicyManager == NULL) { return NO_INIT; } LOGV("stopOutput() tid %d", gettid()); Mutex::Autolock _l(mLock); return mpPolicyManager->stopOutput(output, stream, session); // ++++++++++++++++++++++++++++++AudioPolicyManagerBase::stopOutput++++++++++++++++++++++++++++++++++ status_t AudioPolicyManagerBase::stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream, int session) { LOGV("stopOutput() output %d, stream %d, session %d", output, stream, session); ssize_t index = mOutputs.indexOfKey(output); if (index < 0) { LOGW("stopOutput() unknow output %d", output); return BAD_VALUE; } AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index); routing_strategy strategy = getStrategy((AudioSystem::stream_type)stream); // handle special case for sonification while in call if (isInCall()) { handleIncallSonification(stream, false, false); } if (outputDesc->mRefCount[stream] > 0) { // decrement usage count of this stream on the output outputDesc->changeRefCount(stream, -1); // ++++++++++++++++++++++++++++AudioPolicyManagerBase::AudioOutputDescriptor::changeRefCount++++++++++++++++++++++++++++++++++++ void AudioPolicyManagerBase::AudioOutputDescriptor::changeRefCount(AudioSystem::stream_type stream, int delta) { // forward usage count change to attached outputs if (isDuplicated()) { mOutput1->changeRefCount(stream, delta); mOutput2->changeRefCount(stream, delta); } if ((delta + (int)mRefCount[stream]) < 0) { LOGW("changeRefCount() invalid delta %d for stream %d, refCount %d", delta, stream, mRefCount[stream]); mRefCount[stream] = 0; return; } // 函数AudioPolicyManagerBase::AudioOutputDescriptor::refCount会使用mRefCount mRefCount[stream] += delta; // +++++++++++++++++++++++++++++AudioPolicyManagerBase::AudioOutputDescriptor::refCount+++++++++++++++++++++++++++++++++++ uint32_t AudioPolicyManagerBase::AudioOutputDescriptor::refCount() { uint32_t refcount = 0; for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) { refcount += mRefCount[i]; } return refcount; } // 函数AudioPolicyManagerBase::releaseOutput中有调用函数AudioPolicyManagerBase::AudioOutputDescriptor::refCount。 // +++++++++++++++++++++++++++++AudioPolicyManagerBase::releaseOutput+++++++++++++++++++++++++++++++++++ void AudioPolicyManagerBase::releaseOutput(audio_io_handle_t output) { LOGV("releaseOutput() %d", output); ssize_t index = mOutputs.indexOfKey(output); if (index < 0) { LOGW("releaseOutput() releasing unknown output %d", output); return; } #ifdef AUDIO_POLICY_TEST int testIndex = testOutputIndex(output); if (testIndex != 0) { AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index); if (outputDesc->refCount() == 0) { mpClientInterface->closeOutput(output); delete mOutputs.valueAt(index); mOutputs.removeItem(output); mTestOutputs[testIndex] = 0; } return; } #endif //AUDIO_POLICY_TEST // 如果没定义AUDIO_POLICY_TEST,只对设置了OUTPUT_FLAG_DIRECT标志的output做delete处理 // 原因,其实在以前我们看AudioPolicyManagerBase::getOutput函数时已经看到了, // 在get output的时候,只有需要一个direct output的时候,才会调用函数AudioPolicyService::openOutput函数来打开一个output // 否则,只是将成员变量(在构造函数中打开的output)返回 // 当然,我们没有考虑AUDIO_POLICY_TEST时的情况 if (mOutputs.valueAt(index)->mFlags & AudioSystem::OUTPUT_FLAG_DIRECT) { // ++++++++++++++++++++++++++++++output_flags++++++++++++++++++++++++++++++++++ // request to open a direct output with getOutput() (by opposition to sharing an output with other AudioTracks) enum output_flags { OUTPUT_FLAG_INDIRECT = 0x0, OUTPUT_FLAG_DIRECT = 0x1 }; // ------------------------------output_flags---------------------------------- mpClientInterface->closeOutput(output); // +++++++++++++++++++++++++++++AudioPolicyService::closeOutput+++++++++++++++++++++++++++++++++++ status_t AudioPolicyService::closeOutput(audio_io_handle_t output) { sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); if (af == 0) return PERMISSION_DENIED; return af->closeOutput(output); // +++++++++++++++++++++++++++AudioFlinger::closeOutput++++++++++++++++++++++++++++++++++++ status_t AudioFlinger::closeOutput(int output) { // keep strong reference on the playback thread so that // it is not destroyed while exit() is executed sp <PlaybackThread> thread; { Mutex::Autolock _l(mLock); thread = checkPlaybackThread_l(output); if (thread == NULL) { return BAD_VALUE; } LOGV("closeOutput() %d", output); // 如果thread 为mixer // 删除所有DUPLICATING thread中包含的该thread if (thread->type() == PlaybackThread::MIXER) { for (size_t i = 0; i < mPlaybackThreads.size(); i++) { if (mPlaybackThreads.valueAt(i)->type() == PlaybackThread::DUPLICATING) { DuplicatingThread *dupThread = (DuplicatingThread *)mPlaybackThreads.valueAt(i).get(); dupThread->removeOutputTrack((MixerThread *)thread.get()); } } } void *param2 = 0; audioConfigChanged_l(AudioSystem::OUTPUT_CLOSED, output, param2); mPlaybackThreads.removeItem(output); } thread->exit(); // ++++++++++++++++++++++++++++AudioFlinger::ThreadBase::exit++++++++++++++++++++++++++++++++++++ void AudioFlinger::ThreadBase::exit() { // keep a strong ref on ourself so that we wont get // destroyed in the middle of requestExitAndWait() sp <ThreadBase> strongMe = this; LOGV("ThreadBase::exit"); { AutoMutex lock(&mLock); mExiting = true; requestExit(); // +++++++++++++++++++++++++++++Thread::requestExit+++++++++++++++++++++++++++++++++++ void Thread::requestExit() { // threadloop函数中会判断该成员变量,以判断是否要结束线程 mExitPending = true; } // -----------------------------Thread::requestExit---------------------------------- mWaitWorkCV.signal(); } requestExitAndWait(); // +++++++++++++++++++++++++++++Thread::requestExitAndWait+++++++++++++++++++++++++++++++++++ status_t Thread::requestExitAndWait() { if (mThread == getThreadId()) { LOGW( "Thread (this=%p): don't call waitForExit() from this " "Thread object's thread. It's a guaranteed deadlock!", this); return WOULD_BLOCK; } requestExit(); Mutex::Autolock _l(mLock); while (mRunning == true) { mThreadExitedCondition.wait(mLock); } mExitPending = false; return mStatus; } // -----------------------------Thread::requestExitAndWait----------------------------------- } // ----------------------------AudioFlinger::ThreadBase::exit------------------------------------ if (thread->type() != PlaybackThread::DUPLICATING) { // output是在函数AudioFlinger::openOutput中调用函数mAudioHardware->openOutputStream打开的。 mAudioHardware->closeOutputStream(thread->getOutput()); // +++++++++++++++++++++++++++AudioHardwareALSA::closeOutputStream+++++++++++++++++++++++++++++++++++++ void AudioHardwareALSA::closeOutputStream(AudioStreamOut* out) { AutoMutex lock(mLock); delete out; } // ---------------------------AudioHardwareALSA::closeOutputStream------------------------------------- } return NO_ERROR; } // ----------------------------AudioFlinger::closeOutput------------------------------------ } // -----------------------------AudioPolicyService::closeOutput----------------------------------- delete mOutputs.valueAt(index); mOutputs.removeItem(output); } } // -----------------------------AudioPolicyManagerBase::releaseOutput----------------------------------- // -----------------------------AudioPolicyManagerBase::AudioOutputDescriptor::refCount----------------------------------- LOGV("changeRefCount() stream %d, count %d", stream, mRefCount[stream]); } // ----------------------------AudioPolicyManagerBase::AudioOutputDescriptor::changeRefCount------------------------------------ // store time at which the last music track was stopped - see computeVolume() if (stream == AudioSystem::MUSIC) { mMusicStopTime = systemTime(); } setOutputDevice(output, getNewDevice(output)); #ifdef WITH_A2DP if (mA2dpOutput != 0 && !a2dpUsedForSonification() && strategy == STRATEGY_SONIFICATION) { setStrategyMute(STRATEGY_MEDIA, false, mA2dpOutput, mOutputs.valueFor(mHardwareOutput)->mLatency*2); } #endif if (output != mHardwareOutput) { setOutputDevice(mHardwareOutput, getNewDevice(mHardwareOutput), true); } return NO_ERROR; } else { LOGW("stopOutput() refcount is already 0 for output %d", output); return INVALID_OPERATION; } } // ------------------------------AudioPolicyManagerBase::stopOutput---------------------------------- } // ---------------------------------AudioPolicyService::stopOutput------------------------------- } // ------------------------------AudioSystem::stopOutput---------------------------------- thread->mLock.lock(); } } } // -----------------------------AudioFlinger::PlaybackThread::Track::stop----------------------------------- } // ------------------------------AudioFlinger::TrackHandle::stop---------------------------------- // Cancel loops (If we are in the middle of a loop, playback // would not stop until loopCount reaches 0). setLoop(0, 0, 0); // +++++++++++++++++++++++++++++AudioTrack::setLoop+++++++++++++++++++++++++++++++++++ status_t AudioTrack::setLoop(uint32_t loopStart, uint32_t loopEnd, int loopCount) { audio_track_cblk_t* cblk = mCblk; Mutex::Autolock _l(cblk->lock); if (loopCount == 0) { cblk->loopStart = ULLONG_MAX; cblk->loopEnd = ULLONG_MAX; cblk->loopCount = 0; mLoopCount = 0; return NO_ERROR; } if (loopStart >= loopEnd || loopEnd - loopStart > cblk->frameCount) { LOGE("setLoop invalid value: loopStart %d, loopEnd %d, loopCount %d, framecount %d, user %lld", loopStart, loopEnd, loopCount, cblk->frameCount, cblk->user); return BAD_VALUE; } if ((mSharedBuffer != 0) && (loopEnd > cblk->frameCount)) { LOGE("setLoop invalid value: loop markers beyond data: loopStart %d, loopEnd %d, framecount %d", loopStart, loopEnd, cblk->frameCount); return BAD_VALUE; } cblk->loopStart = loopStart; cblk->loopEnd = loopEnd; cblk->loopCount = loopCount; mLoopCount = loopCount; return NO_ERROR; } // -----------------------------AudioTrack::setLoop----------------------------------- // the playback head position will reset to 0, so if a marker is set, we need // to activate it again mMarkerReached = false; // Force flush if a shared buffer is used otherwise audioflinger // will not stop before end of buffer is reached. if (mSharedBuffer != 0) { flush(); // ++++++++++++++++++++++++++++AudioTrack::flush++++++++++++++++++++++++++++++++++++ void AudioTrack::flush() { LOGV("flush"); // clear playback marker and periodic update counter mMarkerPosition = 0; mMarkerReached = false; mUpdatePeriod = 0; if (!mActive) { mAudioTrack->flush(); // ++++++++++++++++++++++++++++++AudioFlinger::TrackHandle::flush++++++++++++++++++++++++++++++++++ void AudioFlinger::TrackHandle::flush() { mTrack->flush(); // ++++++++++++++++++++++++++++++AudioFlinger::PlaybackThread::Track::flush++++++++++++++++++++++++++++++++++ void AudioFlinger::PlaybackThread::Track::flush() { LOGV("flush(%d)", mName); sp<ThreadBase> thread = mThread.promote(); if (thread != 0) { Mutex::Autolock _l(thread->mLock); if (mState != STOPPED && mState != PAUSED && mState != PAUSING) { return; } // No point remaining in PAUSED state after a flush => go to // STOPPED state mState = STOPPED; mCblk->lock.lock(); // NOTE: reset() will reset cblk->user and cblk->server with // the risk that at the same time, the AudioMixer is trying to read // data. In this case, getNextBuffer() would return a NULL pointer // as audio buffer => the AudioMixer code MUST always test that pointer // returned by getNextBuffer() is not NULL! // reset函数在stop函数中已经看过 reset(); mCblk->lock.unlock(); } } // ------------------------------AudioFlinger::PlaybackThread::Track::flush---------------------------------- } // ------------------------------AudioFlinger::TrackHandle::flush---------------------------------- // Release AudioTrack callback thread in case it was waiting for new buffers // in AudioTrack::obtainBuffer() mCblk->cv.signal(); } } // ----------------------------AudioTrack::flush------------------------------------ } if (t != 0) { t->requestExit(); } else { setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_NORMAL); } } if (t != 0) { t->mLock.unlock(); } } // -------------------------------AudioTrack::stop--------------------------------- } // ------------------------------android_media_AudioTrack_stop---------------------------------- mPlayState = PLAYSTATE_STOPPED; } } // ----------------------------stop------------------------------------ track.flush(); // ++++++++++++++++++++++++++++flush++++++++++++++++++++++++++++++++++++ /** * Flushes the audio data currently queued for playback. */ public void flush() { if (mState == STATE_INITIALIZED) { // flush the data in native layer native_flush(); // ++++++++++++++++++++++++++++android_media_AudioTrack_flush++++++++++++++++++++++++++++++++++++ static void android_media_AudioTrack_flush(JNIEnv *env, jobject thiz) { AudioTrack *lpTrack = (AudioTrack *)env->GetIntField( thiz, javaAudioTrackFields.nativeTrackInJavaObj); if (lpTrack == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for flush()"); return; } lpTrack->flush(); // +++++++++++++++++++++++++++++AudioTrack::flush+++++++++++++++++++++++++++++++++++ void AudioTrack::flush() { LOGV("flush"); // clear playback marker and periodic update counter mMarkerPosition = 0; mMarkerReached = false; mUpdatePeriod = 0; if (!mActive) { mAudioTrack->flush(); // ++++++++++++++++++++++++++++AudioFlinger::TrackHandle::flush++++++++++++++++++++++++++++++++++++ void AudioFlinger::TrackHandle::flush() { // 函数AudioFlinger::PlaybackThread::Track::flush在上面已经看过 mTrack->flush(); } // ----------------------------AudioFlinger::TrackHandle::flush------------------------------------ // Release AudioTrack callback thread in case it was waiting for new buffers // in AudioTrack::obtainBuffer() mCblk->cv.signal(); } } // -----------------------------AudioTrack::flush----------------------------------- } // ----------------------------android_media_AudioTrack_flush------------------------------------ } } // ----------------------------flush------------------------------------ log(TEST_NAME, "position ="+ track.getPlaybackHeadPosition()); assertTrue(TEST_NAME, track.getPlaybackHeadPosition() == 0); //-------- tear down -------------- track.release(); }
###########################################################
&&&&&&&&&&&&&&&&&&&&&&&总结&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
stop的时候:
若AudioTrack不是active的,在将其audio_track_cblk_t中的user, server等清0。
若是direct output,则将其close。
flush的时候:
会将audio_track_cblk_t中的user, server等清0。
因此,flush 之后再get position的话,肯定是0.
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
相关文章推荐
- Android Audio代码分析15 - testPlaybackHeadPositionAfterFlush
- Android Audio代码分析14 - testPlaybackHeadPositionIncrease
- Android Audio代码分析14 - testPlaybackHeadPositionIncrease
- Android Audio代码分析13 - AudioTrack::getPosition函数
- Android Audio代码分析13 - AudioTrack::getPosition函数
- Android Audio代码分析9 - AudioTrack::write函数
- Android Audio代码分析21 - 创建AudioEffect对象
- Android Audio代码分析1 - AudioTrack使用示例
- Android Audio代码分析21 - 创建AudioEffect对象
- Android Audio代码分析24 - AudioEffect::setEnabled函数
- Android Audio代码分析26 - Audio Strategy
- Android Audio代码分析3 - 创建AudioTrack对象
- Android Audio代码分析4 - AudioSystem::getOutputSamplingRate
- Android Audio 代码分析- Audio Strategy
- Android Audio代码分析24 - AudioEffect::setEnabled函数
- Android Audio代码分析26 - Audio Strategy
- Android Audio代码分析8 - AudioHardwareALSA::openOutputStream函数
- Android Audio代码分析17 - setvolume函数
- Android Audio代码分析11 - AudioStreamOutALSA::write函数
- Android Audio代码分析8 - AudioHardwareALSA::openOutputStream函数