Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 51716185 authored by Eric Laurent's avatar Eric Laurent
Browse files

audio flinger: fix offload track underrun

Fix offload track underrun detection causing early disabling
of offloaded audio tracks.

Optimize sleep time in case of underrun to avoid spinning while
waiting for new data.

Re-enable offload when streaming

Bug: 26668110

Change-Id: I874728c69647ac05e1effb84c48700a6d0ac7435
parent db52e5e3
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -1819,7 +1819,8 @@ sp<AudioFlinger::PlaybackThread> AudioFlinger::openOutput_l(audio_module_handle_

        PlaybackThread *thread;
        if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
            thread = new OffloadThread(this, outputStream, *output, devices, mSystemReady);
            thread = new OffloadThread(this, outputStream, *output, devices, mSystemReady,
                                       config->offload_info.bit_rate);
            ALOGV("openOutput_l() created offload output: ID %d thread %p", *output, thread);
        } else if ((flags & AUDIO_OUTPUT_FLAG_DIRECT)
                || !isValidPcmSinkFormat(config->format)
+94 −15
Original line number Diff line number Diff line
@@ -108,6 +108,13 @@ static const int8_t kMaxTrackStartupRetries = 50;
// direct outputs can be a scarce resource in audio hardware and should
// be released as quickly as possible.
static const int8_t kMaxTrackRetriesDirect = 2;
// retry count before removing active track in case of underrun on offloaded thread:
// we need to make sure that AudioTrack client has enough time to send large buffers
//FIXME may be more appropriate if expressed in time units. Need to revise how underrun is handled
// for offloaded tracks
static const int8_t kMaxTrackRetriesOffload = 10;
static const int8_t kMaxTrackStartupRetriesOffload = 100;


// don't warn about blocked writes or record buffer overflows more often than this
static const nsecs_t kWarningThrottleNs = seconds(5);
@@ -136,6 +143,14 @@ static const uint32_t kMinNormalCaptureBufferSizeMs = 12;
// Offloaded output thread standby delay: allows track transition without going to standby
static const nsecs_t kOffloadStandbyDelayNs = seconds(1);

// Direct output thread minimum sleep time in idle or active(underrun) state
static const nsecs_t kDirectMinSleepTimeUs = 10000;

// Offloaded output bit rate in bits per second when unknown.
// Used for sleep time calculation, so use a high default bitrate to be conservative on sleep time.
static const uint32_t kOffloadDefaultBitRateBps = 1500000;


// Whether to use fast mixer
static const enum {
    FastMixer_Never,    // never initialize or use: for debugging only
@@ -1553,7 +1568,8 @@ AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinge
                                             audio_io_handle_t id,
                                             audio_devices_t device,
                                             type_t type,
                                             bool systemReady)
                                             bool systemReady,
                                             uint32_t bitRate)
    :   ThreadBase(audioFlinger, id, device, AUDIO_DEVICE_NONE, type, systemReady),
        mNormalFrameCount(0), mSinkBuffer(NULL),
        mMixerBufferEnabled(AudioFlinger::kEnableExtendedPrecision),
@@ -1616,6 +1632,13 @@ AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinge
        mStreamTypes[stream].volume = mAudioFlinger->streamVolume_l(stream);
        mStreamTypes[stream].mute = mAudioFlinger->streamMute_l(stream);
    }

    if (audio_has_proportional_frames(mFormat)) {
        mBufferDurationUs = (uint32_t)((mNormalFrameCount * 1000000LL) / mSampleRate);
    } else {
        bitRate = bitRate != 0 ? bitRate : kOffloadDefaultBitRateBps;
        mBufferDurationUs = (uint32_t)((mBufferSize * 8 * 1000000LL) / bitRate);
    }
}

AudioFlinger::PlaybackThread::~PlaybackThread()
@@ -2008,8 +2031,6 @@ status_t AudioFlinger::PlaybackThread::addTrack_l(const sp<Track>& track)
{
    status_t status = ALREADY_EXISTS;

    // set retry count for buffer fill
    track->mRetryCount = kMaxTrackStartupRetries;
    if (mActiveTracks.indexOf(track) < 0) {
        // the track is newly added, make sure it fills up all its
        // buffers before playing. This is to ensure the client will
@@ -2040,6 +2061,13 @@ status_t AudioFlinger::PlaybackThread::addTrack_l(const sp<Track>& track)
#endif
        }

        // set retry count for buffer fill
        if (track->isOffloaded()) {
            track->mRetryCount = kMaxTrackStartupRetriesOffload;
        } else {
            track->mRetryCount = kMaxTrackStartupRetries;
        }

        track->mFillingUpStatus = track->sharedBuffer() != 0 ? Track::FS_FILLED : Track::FS_FILLING;
        track->mResetDone = false;
        track->mPresentationCompleteFrames = 0;
@@ -2572,6 +2600,7 @@ ssize_t AudioFlinger::PlaybackThread::threadLoop_write()
        // FIXME We should have an implementation of timestamps for direct output threads.
        // They are used e.g for multichannel PCM playback over HDMI.
        bytesWritten = mOutput->write((char *)mSinkBuffer + offset, mBytesRemaining);

        if (mUseAsyncWrite &&
                ((bytesWritten < 0) || (bytesWritten == (ssize_t)mBytesRemaining))) {
            // do not wait for async callback in case of error of full write
@@ -3137,7 +3166,30 @@ bool AudioFlinger::PlaybackThread::threadLoop()

            } else {
                ATRACE_BEGIN("sleep");
                if ((mType == OFFLOAD) && !audio_has_proportional_frames(mFormat)) {
                    Mutex::Autolock _l(mLock);
                    if (!mSignalPending && !exitPending()) {
                        // Do not sleep more than one buffer duration since last write and not
                        // less than kDirectMinSleepTimeUs
                        // Wake up if a command is received
                        nsecs_t now = systemTime();
                        uint32_t deltaUs = (uint32_t)((now - mLastWriteTime) / 1000);
                        uint32_t timeoutUs = mSleepTimeUs;
                        if (timeoutUs + deltaUs > mBufferDurationUs) {
                            if (mBufferDurationUs > deltaUs) {
                                timeoutUs = mBufferDurationUs - deltaUs;
                                if (timeoutUs < kDirectMinSleepTimeUs) {
                                    timeoutUs = kDirectMinSleepTimeUs;
                                }
                            } else {
                                timeoutUs = kDirectMinSleepTimeUs;
                            }
                        }
                        mWaitWorkCV.waitRelative(mLock, microseconds((nsecs_t)timeoutUs));
                    }
                } else {
                    usleep(mSleepTimeUs);
                }
                ATRACE_END();
            }
        }
@@ -4583,16 +4635,17 @@ void AudioFlinger::MixerThread::cacheParameters_l()
// ----------------------------------------------------------------------------

AudioFlinger::DirectOutputThread::DirectOutputThread(const sp<AudioFlinger>& audioFlinger,
        AudioStreamOut* output, audio_io_handle_t id, audio_devices_t device, bool systemReady)
    :   PlaybackThread(audioFlinger, output, id, device, DIRECT, systemReady)
        AudioStreamOut* output, audio_io_handle_t id, audio_devices_t device, bool systemReady,
        uint32_t bitRate)
    :   PlaybackThread(audioFlinger, output, id, device, DIRECT, systemReady, bitRate)
        // mLeftVolFloat, mRightVolFloat
{
}

AudioFlinger::DirectOutputThread::DirectOutputThread(const sp<AudioFlinger>& audioFlinger,
        AudioStreamOut* output, audio_io_handle_t id, uint32_t device,
        ThreadBase::type_t type, bool systemReady)
    :   PlaybackThread(audioFlinger, output, id, device, type, systemReady)
        ThreadBase::type_t type, bool systemReady, uint32_t bitRate)
    :   PlaybackThread(audioFlinger, output, id, device, type, systemReady, bitRate)
        // mLeftVolFloat, mRightVolFloat
{
}
@@ -4871,7 +4924,10 @@ void AudioFlinger::DirectOutputThread::threadLoop_mix()
        buffer.frameCount = frameCount;
        status_t status = mActiveTrack->getNextBuffer(&buffer);
        if (status != NO_ERROR || buffer.raw == NULL) {
            // no need to pad with 0 for compressed audio
            if (audio_has_proportional_frames(mFormat)) {
                memset(curBuf, 0, frameCount * mFrameSize);
            }
            break;
        }
        memcpy(curBuf, buffer.raw, buffer.frameCount * mFrameSize);
@@ -4894,7 +4950,14 @@ void AudioFlinger::DirectOutputThread::threadLoop_sleepTime()
    }
    if (mSleepTimeUs == 0) {
        if (mMixerStatus == MIXER_TRACKS_ENABLED) {
            // For compressed offload, use faster sleep time when underruning until more than an
            // entire buffer was written to the audio HAL
            if (!audio_has_proportional_frames(mFormat) &&
                    (mType == OFFLOAD) && (mBytesWritten < mBufferSize)) {
                mSleepTimeUs = kDirectMinSleepTimeUs;
            } else {
                mSleepTimeUs = mActiveSleepTimeUs;
            }
        } else {
            mSleepTimeUs = mIdleSleepTimeUs;
        }
@@ -5008,7 +5071,7 @@ uint32_t AudioFlinger::DirectOutputThread::activeSleepTimeUs() const
    if (audio_has_proportional_frames(mFormat)) {
        time = PlaybackThread::activeSleepTimeUs();
    } else {
        time = 10000;
        time = kDirectMinSleepTimeUs;
    }
    return time;
}
@@ -5019,7 +5082,7 @@ uint32_t AudioFlinger::DirectOutputThread::idleSleepTimeUs() const
    if (audio_has_proportional_frames(mFormat)) {
        time = (uint32_t)(((mFrameCount * 1000) / mSampleRate) * 1000) / 2;
    } else {
        time = 10000;
        time = kDirectMinSleepTimeUs;
    }
    return time;
}
@@ -5030,7 +5093,7 @@ uint32_t AudioFlinger::DirectOutputThread::suspendSleepTimeUs() const
    if (audio_has_proportional_frames(mFormat)) {
        time = (uint32_t)(((mFrameCount * 1000) / mSampleRate) * 1000);
    } else {
        time = 10000;
        time = kDirectMinSleepTimeUs;
    }
    return time;
}
@@ -5162,8 +5225,9 @@ void AudioFlinger::AsyncCallbackThread::resetDraining()

// ----------------------------------------------------------------------------
AudioFlinger::OffloadThread::OffloadThread(const sp<AudioFlinger>& audioFlinger,
        AudioStreamOut* output, audio_io_handle_t id, uint32_t device, bool systemReady)
    :   DirectOutputThread(audioFlinger, output, id, device, OFFLOAD, systemReady),
        AudioStreamOut* output, audio_io_handle_t id, uint32_t device, bool systemReady,
        uint32_t bitRate)
    :   DirectOutputThread(audioFlinger, output, id, device, OFFLOAD, systemReady, bitRate),
        mPausedBytesRemaining(0)
{
    //FIXME: mStandby should be set to true by ThreadBase constructor
@@ -5244,6 +5308,7 @@ AudioFlinger::PlaybackThread::mixer_state AudioFlinger::OffloadThread::prepareTr
            }
            tracksToRemove->add(track);
        } else if (track->isFlushPending()) {
            track->mRetryCount = kMaxTrackRetriesOffload;
            track->flushAck();
            if (last) {
                mFlushPending = true;
@@ -5424,6 +5489,20 @@ void AudioFlinger::OffloadThread::flushHw_l()
    }
}

uint32_t AudioFlinger::OffloadThread::activeSleepTimeUs() const
{
    uint32_t time;
    if (audio_has_proportional_frames(mFormat)) {
        time = PlaybackThread::activeSleepTimeUs();
    } else {
        // sleep time is half the duration of an audio HAL buffer.
        // Note: This can be problematic in case of underrun with variable bit rate and
        // current rate is much less than initial rate.
        time = (uint32_t)max(kDirectMinSleepTimeUs, mBufferDurationUs / 2);
    }
    return time;
}

// ----------------------------------------------------------------------------

AudioFlinger::DuplicatingThread::DuplicatingThread(const sp<AudioFlinger>& audioFlinger,
+11 −10
Original line number Diff line number Diff line
@@ -479,14 +479,9 @@ public:
        // suspend by audio policy manager is orthogonal to mixer state
    };

    // retry count before removing active track in case of underrun on offloaded thread:
    // we need to make sure that AudioTrack client has enough time to send large buffers
//FIXME may be more appropriate if expressed in time units. Need to revise how underrun is handled
    // for offloaded tracks
    static const int8_t kMaxTrackRetriesOffload = 20;

    PlaybackThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output,
                   audio_io_handle_t id, audio_devices_t device, type_t type, bool systemReady);
                   audio_io_handle_t id, audio_devices_t device, type_t type, bool systemReady,
                   uint32_t bitRate = 0);
    virtual             ~PlaybackThread();

                void        dump(int fd, const Vector<String16>& args);
@@ -841,6 +836,8 @@ protected:
                bool        mHwSupportsPause;
                bool        mHwPaused;
                bool        mFlushPending;
                uint32_t    mBufferDurationUs;      // estimated duration of an audio HAL buffer
                                                    // based on initial bit rate (offload only)
};

class MixerThread : public PlaybackThread {
@@ -931,7 +928,8 @@ class DirectOutputThread : public PlaybackThread {
public:

    DirectOutputThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output,
                       audio_io_handle_t id, audio_devices_t device, bool systemReady);
                       audio_io_handle_t id, audio_devices_t device, bool systemReady,
                       uint32_t bitRate = 0);
    virtual                 ~DirectOutputThread();

    // Thread virtuals
@@ -964,7 +962,7 @@ protected:

    DirectOutputThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output,
                        audio_io_handle_t id, uint32_t device, ThreadBase::type_t type,
                        bool systemReady);
                        bool systemReady, uint32_t bitRate = 0);
    void processVolume_l(Track *track, bool lastTrack);

    // prepareTracks_l() tells threadLoop_mix() the name of the single active track
@@ -980,7 +978,8 @@ class OffloadThread : public DirectOutputThread {
public:

    OffloadThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output,
                        audio_io_handle_t id, uint32_t device, bool systemReady);
                        audio_io_handle_t id, uint32_t device,
                        bool systemReady, uint32_t bitRate);
    virtual                 ~OffloadThread() {};
    virtual     void        flushHw_l();

@@ -989,6 +988,8 @@ protected:
    virtual     mixer_state prepareTracks_l(Vector< sp<Track> > *tracksToRemove);
    virtual     void        threadLoop_exit();

    virtual     uint32_t    activeSleepTimeUs() const;

    virtual     bool        waitingAsyncCallback();
    virtual     bool        waitingAsyncCallback_l();

+0 −5
Original line number Diff line number Diff line
@@ -792,11 +792,6 @@ void AudioFlinger::PlaybackThread::Track::flush()
                mState = ACTIVE;
            }

            if (mState == ACTIVE) {
                ALOGV("flush called in active state, resetting buffer time out retry count");
                mRetryCount = PlaybackThread::kMaxTrackRetriesOffload;
            }

            mFlushHwPending = true;
            mResumeToStopping = false;
        } else {
+0 −9
Original line number Diff line number Diff line
@@ -2197,15 +2197,6 @@ bool AudioPolicyManager::isOffloadSupported(const audio_offload_info_t& offloadI
        return false;
    }

    // Check if streaming is off, then only allow offload as of now.
    // This is a temporary work around until the root cause is fixed in offload
    // playback path.
    if (offloadInfo.is_streaming)
    {
        ALOGV("isOffloadSupported: is_streaming == true, returning false");
        return false;
    }

    //TODO: enable audio offloading with video when ready
    const bool allowOffloadWithVideo =
            property_get_bool("audio.offload.video", false /* default_value */);