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

Commit efbb6195 authored by Chong Zhang's avatar Chong Zhang
Browse files

NuPlayer: pause playback when buffering is low

also fix buffering percentage report (should be the buffered position)

bug: 18730095
Change-Id: I11e7ca4ba9e772a1ae76861ca1ff1725b62f65ae
parent 77f877f8
Loading
Loading
Loading
Loading
+169 −20
Original line number Diff line number Diff line
@@ -40,6 +40,11 @@

namespace android {

static int64_t kLowWaterMarkUs = 2000000ll;  // 2secs
static int64_t kHighWaterMarkUs = 5000000ll;  // 5secs
static const ssize_t kLowWaterMarkBytes = 40000;
static const ssize_t kHighWaterMarkBytes = 200000;

NuPlayer::GenericSource::GenericSource(
        const sp<AMessage> &notify,
        bool uidValid,
@@ -55,6 +60,7 @@ NuPlayer::GenericSource::GenericSource(
      mAudioIsVorbis(false),
      mIsWidevine(false),
      mIsSecure(false),
      mIsStreaming(false),
      mUIDValid(uidValid),
      mUID(uid),
      mFd(-1),
@@ -62,7 +68,9 @@ NuPlayer::GenericSource::GenericSource(
      mMetaDataSize(-1ll),
      mBitrate(-1ll),
      mPollBufferingGeneration(0),
      mPendingReadBufferTypes(0) {
      mPendingReadBufferTypes(0),
      mBuffering(false),
      mPrepareBuffering(false) {
    resetDataSource();
    DataSource::RegisterDefaultSniffers();
}
@@ -254,6 +262,20 @@ status_t NuPlayer::GenericSource::initFromDataSource() {
        }
    }

    // Start the selected A/V tracks now before we start buffering.
    // Widevine sources might re-initialize crypto when starting, if we delay
    // this to start(), all data buffered during prepare would be wasted.
    // (We don't actually start reading until start().)
    if (mAudioTrack.mSource != NULL && mAudioTrack.mSource->start() != OK) {
        ALOGE("failed to start audio track!");
        return UNKNOWN_ERROR;
    }

    if (mVideoTrack.mSource != NULL && mVideoTrack.mSource->start() != OK) {
        ALOGE("failed to start video track!");
        return UNKNOWN_ERROR;
    }

    mBitrate = totalBitrate;

    return OK;
@@ -352,9 +374,13 @@ void NuPlayer::GenericSource::onPrepareAsync() {
            mCachedSource = static_cast<NuCachedSource2 *>(mDataSource.get());
        }

        if (mIsWidevine || mCachedSource != NULL) {
            schedulePollBuffering();
        }
        // For widevine or other cached streaming cases, we need to wait for
        // enough buffering before reporting prepared.
        // Note that even when URL doesn't start with widevine://, mIsWidevine
        // could still be set to true later, if the streaming or file source
        // is sniffed to be widevine. We don't want to buffer for file source
        // in that case, so must check the flag now.
        mIsStreaming = (mIsWidevine || mCachedSource != NULL);
    }

    // check initial caching status
@@ -397,8 +423,15 @@ void NuPlayer::GenericSource::onPrepareAsync() {
            | FLAG_CAN_SEEK_FORWARD
            | FLAG_CAN_SEEK);

    if (mIsStreaming) {
        mPrepareBuffering = true;

        ensureCacheIsFetching();
        restartPollBuffering();
    } else {
        notifyPrepared();
    }
}

void NuPlayer::GenericSource::notifyPreparedAndCleanup(status_t err) {
    if (err != OK) {
@@ -489,19 +522,17 @@ void NuPlayer::GenericSource::start() {

    mStopRead = false;
    if (mAudioTrack.mSource != NULL) {
        CHECK_EQ(mAudioTrack.mSource->start(), (status_t)OK);

        postReadBuffer(MEDIA_TRACK_TYPE_AUDIO);
    }

    if (mVideoTrack.mSource != NULL) {
        CHECK_EQ(mVideoTrack.mSource->start(), (status_t)OK);

        postReadBuffer(MEDIA_TRACK_TYPE_VIDEO);
    }

    setDrmPlaybackStatusIfNeeded(Playback::START, getLastReadPosition() / 1000);
    mStarted = true;

    (new AMessage(kWhatStart, id()))->post();
}

void NuPlayer::GenericSource::stop() {
@@ -526,6 +557,8 @@ void NuPlayer::GenericSource::resume() {
    // nothing to do, just account for DRM playback status
    setDrmPlaybackStatusIfNeeded(Playback::START, getLastReadPosition() / 1000);
    mStarted = true;

    (new AMessage(kWhatResume, id()))->post();
}

void NuPlayer::GenericSource::disconnect() {
@@ -558,22 +591,98 @@ void NuPlayer::GenericSource::schedulePollBuffering() {
}

void NuPlayer::GenericSource::cancelPollBuffering() {
    mBuffering = false;
    ++mPollBufferingGeneration;
}

void NuPlayer::GenericSource::restartPollBuffering() {
    if (mIsStreaming) {
        cancelPollBuffering();
        onPollBuffering();
    }
}

void NuPlayer::GenericSource::notifyBufferingUpdate(int percentage) {
    ALOGV("notifyBufferingUpdate: buffering %d%%", percentage);

    sp<AMessage> msg = dupNotify();
    msg->setInt32("what", kWhatBufferingUpdate);
    msg->setInt32("percentage", percentage);
    msg->post();
}

void NuPlayer::GenericSource::startBufferingIfNecessary() {
    ALOGV("startBufferingIfNecessary: mPrepareBuffering=%d, mBuffering=%d",
            mPrepareBuffering, mBuffering);

    if (mPrepareBuffering) {
        return;
    }

    if (!mBuffering) {
        mBuffering = true;

        ensureCacheIsFetching();
        sendCacheStats();

        sp<AMessage> notify = dupNotify();
        notify->setInt32("what", kWhatPauseOnBufferingStart);
        notify->post();
    }
}

void NuPlayer::GenericSource::stopBufferingIfNecessary() {
    ALOGV("stopBufferingIfNecessary: mPrepareBuffering=%d, mBuffering=%d",
            mPrepareBuffering, mBuffering);

    if (mPrepareBuffering) {
        mPrepareBuffering = false;
        notifyPrepared();
        return;
    }

    if (mBuffering) {
        mBuffering = false;

        sendCacheStats();

        sp<AMessage> notify = dupNotify();
        notify->setInt32("what", kWhatResumeOnBufferingEnd);
        notify->post();
    }
}

void NuPlayer::GenericSource::sendCacheStats() {
    int32_t kbps = 0;
    status_t err = UNKNOWN_ERROR;

    if (mCachedSource != NULL) {
        err = mCachedSource->getEstimatedBandwidthKbps(&kbps);
    } else if (mWVMExtractor != NULL) {
        err = mWVMExtractor->getEstimatedBandwidthKbps(&kbps);
    }

    if (err == OK) {
        sp<AMessage> notify = dupNotify();
        notify->setInt32("what", kWhatCacheStats);
        notify->setInt32("bandwidth", kbps);
        notify->post();
    }
}

void NuPlayer::GenericSource::ensureCacheIsFetching() {
    if (mCachedSource != NULL) {
        mCachedSource->resumeFetchingIfNecessary();
    }
}

void NuPlayer::GenericSource::onPollBuffering() {
    status_t finalStatus = UNKNOWN_ERROR;
    int64_t cachedDurationUs = 0ll;
    int64_t cachedDurationUs = -1ll;
    ssize_t cachedDataRemaining = -1;

    if (mCachedSource != NULL) {
        size_t cachedDataRemaining =
        cachedDataRemaining =
                mCachedSource->approxDataRemaining(&finalStatus);

        if (finalStatus == OK) {
@@ -593,12 +702,19 @@ void NuPlayer::GenericSource::onPollBuffering() {
            = mWVMExtractor->getCachedDurationUs(&finalStatus);
    }

    if (finalStatus != OK) {
        ALOGV("onPollBuffering: EOS (finalStatus = %d)", finalStatus);

        if (finalStatus == ERROR_END_OF_STREAM) {
            notifyBufferingUpdate(100);
        cancelPollBuffering();
        }

        stopBufferingIfNecessary();
        return;
    } else if (cachedDurationUs > 0ll && mDurationUs > 0ll) {
        int percentage = 100.0 * cachedDurationUs / mDurationUs;
    } else if (cachedDurationUs >= 0ll) {
        if (mDurationUs > 0ll) {
            int64_t cachedPosUs = getLastReadPosition() + cachedDurationUs;
            int percentage = 100.0 * cachedPosUs / mDurationUs;
            if (percentage > 100) {
                percentage = 100;
            }
@@ -606,9 +722,27 @@ void NuPlayer::GenericSource::onPollBuffering() {
            notifyBufferingUpdate(percentage);
        }

    schedulePollBuffering();
        ALOGV("onPollBuffering: cachedDurationUs %.1f sec",
                cachedDurationUs / 1000000.0f);

        if (cachedDurationUs < kLowWaterMarkUs) {
            startBufferingIfNecessary();
        } else if (cachedDurationUs > kHighWaterMarkUs) {
            stopBufferingIfNecessary();
        }
    } else if (cachedDataRemaining >= 0) {
        ALOGV("onPollBuffering: cachedDataRemaining %d bytes",
                cachedDataRemaining);

        if (cachedDataRemaining < kLowWaterMarkBytes) {
            startBufferingIfNecessary();
        } else if (cachedDataRemaining > kHighWaterMarkBytes) {
            stopBufferingIfNecessary();
        }
    }

    schedulePollBuffering();
}

void NuPlayer::GenericSource::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
@@ -688,6 +822,14 @@ void NuPlayer::GenericSource::onMessageReceived(const sp<AMessage> &msg) {

          break;
      }

      case kWhatStart:
      case kWhatResume:
      {
          restartPollBuffering();
          break;
      }

      case kWhatPollBuffering:
      {
          int32_t generation;
@@ -1201,6 +1343,13 @@ status_t NuPlayer::GenericSource::doSeek(int64_t seekTimeUs) {
    if (!mStarted) {
        setDrmPlaybackStatusIfNeeded(Playback::PAUSE, 0);
    }

    // If currently buffering, post kWhatBufferingEnd first, so that
    // NuPlayer resumes. Otherwise, if cache hits high watermark
    // before new polling happens, no one will resume the playback.
    stopBufferingIfNecessary();
    restartPollBuffering();

    return OK;
}

+11 −2
Original line number Diff line number Diff line
@@ -94,16 +94,17 @@ private:
        kWhatSeek,
        kWhatReadBuffer,
        kWhatStopWidevine,
        kWhatStart,
        kWhatResume,
    };

    Vector<sp<MediaSource> > mSources;

    struct Track {
        size_t mIndex;
        sp<MediaSource> mSource;
        sp<AnotherPacketSource> mPackets;
    };

    Vector<sp<MediaSource> > mSources;
    Track mAudioTrack;
    int64_t mAudioTimeUs;
    int64_t mAudioLastDequeueTimeUs;
@@ -119,6 +120,7 @@ private:
    bool mAudioIsVorbis;
    bool mIsWidevine;
    bool mIsSecure;
    bool mIsStreaming;
    bool mUIDValid;
    uid_t mUID;
    sp<IMediaHTTPService> mHTTPService;
@@ -143,6 +145,8 @@ private:
    int64_t mBitrate;
    int32_t mPollBufferingGeneration;
    uint32_t mPendingReadBufferTypes;
    bool mBuffering;
    bool mPrepareBuffering;
    mutable Mutex mReadBufferLock;

    sp<ALooper> mLooper;
@@ -194,8 +198,13 @@ private:

    void schedulePollBuffering();
    void cancelPollBuffering();
    void restartPollBuffering();
    void onPollBuffering();
    void notifyBufferingUpdate(int percentage);
    void startBufferingIfNecessary();
    void stopBufferingIfNecessary();
    void sendCacheStats();
    void ensureCacheIsFetching();

    DISALLOW_EVIL_CONSTRUCTORS(GenericSource);
};
+58 −11
Original line number Diff line number Diff line
@@ -180,7 +180,9 @@ NuPlayer::NuPlayer()
      mFlushingVideo(NONE),
      mResumePending(false),
      mVideoScalingMode(NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW),
      mStarted(false) {
      mStarted(false),
      mPaused(false),
      mPausedByClient(false) {
    clearFlushComplete();
}

@@ -598,6 +600,7 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
            } else {
                onStart();
            }
            mPausedByClient = false;
            break;
        }

@@ -956,16 +959,8 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {

        case kWhatPause:
        {
            if (mSource != NULL) {
                mSource->pause();
            } else {
                ALOGW("pause called when source is gone or not set");
            }
            if (mRenderer != NULL) {
                mRenderer->pause();
            } else {
                ALOGW("pause called when renderer is gone or not set");
            }
            onPause();
            mPausedByClient = true;
            break;
        }

@@ -988,6 +983,10 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
}

void NuPlayer::onResume() {
    if (!mPaused) {
        return;
    }
    mPaused = false;
    if (mSource != NULL) {
        mSource->resume();
    } else {
@@ -1072,6 +1071,23 @@ void NuPlayer::onStart() {
    postScanSources();
}

void NuPlayer::onPause() {
    if (mPaused) {
        return;
    }
    mPaused = true;
    if (mSource != NULL) {
        mSource->pause();
    } else {
        ALOGW("pause called when source is gone or not set");
    }
    if (mRenderer != NULL) {
        mRenderer->pause();
    } else {
        ALOGW("pause called when renderer is gone or not set");
    }
}

bool NuPlayer::audioDecoderStillNeeded() {
    // Audio decoder is no longer needed if it's in shut/shutting down status.
    return ((mFlushingAudio != SHUT_DOWN) && (mFlushingAudio != SHUTTING_DOWN_DECODER));
@@ -1709,18 +1725,49 @@ void NuPlayer::onSourceNotify(const sp<AMessage> &msg) {
            break;
        }

        case Source::kWhatPauseOnBufferingStart:
        {
            // ignore if not playing
            if (mStarted && !mPausedByClient) {
                ALOGI("buffer low, pausing...");

                onPause();
            }
            // fall-thru
        }

        case Source::kWhatBufferingStart:
        {
            notifyListener(MEDIA_INFO, MEDIA_INFO_BUFFERING_START, 0);
            break;
        }

        case Source::kWhatResumeOnBufferingEnd:
        {
            // ignore if not playing
            if (mStarted && !mPausedByClient) {
                ALOGI("buffer ready, resuming...");

                onResume();
            }
            // fall-thru
        }

        case Source::kWhatBufferingEnd:
        {
            notifyListener(MEDIA_INFO, MEDIA_INFO_BUFFERING_END, 0);
            break;
        }

        case Source::kWhatCacheStats:
        {
            int32_t kbps;
            CHECK(msg->findInt32("bandwidth", &kbps));

            notifyListener(MEDIA_INFO, MEDIA_INFO_NETWORK_BANDWIDTH, kbps);
            break;
        }

        case Source::kWhatSubtitleData:
        {
            sp<ABuffer> buffer;
+9 −0
Original line number Diff line number Diff line
@@ -177,6 +177,14 @@ private:

    bool mStarted;

    // Actual pause state, either as requested by client or due to buffering.
    bool mPaused;

    // Pause state as requested by client. Note that if mPausedByClient is
    // true, mPaused is always true; if mPausedByClient is false, mPaused could
    // still become true, when we pause internally due to buffering.
    bool mPausedByClient;

    inline const sp<DecoderBase> &getDecoder(bool audio) {
        return audio ? mAudioDecoder : mVideoDecoder;
    }
@@ -204,6 +212,7 @@ private:

    void onStart();
    void onResume();
    void onPause();

    bool audioDecoderStillNeeded();

+3 −0
Original line number Diff line number Diff line
@@ -49,6 +49,9 @@ struct NuPlayer::Source : public AHandler {
        kWhatBufferingUpdate,
        kWhatBufferingStart,
        kWhatBufferingEnd,
        kWhatPauseOnBufferingStart,
        kWhatResumeOnBufferingEnd,
        kWhatCacheStats,
        kWhatSubtitleData,
        kWhatTimedTextData,
        kWhatQueueDecoderShutdown,
Loading