From a7f03353d5f172016f324e2a01f301cca6794152 Mon Sep 17 00:00:00 2001 From: Andy Hung Date: Sun, 31 May 2015 21:54:49 -0700 Subject: Compute sleep time when AudioTrack client callback returns no PCM data Callbacks can go into a sleep-wait cycle if the client/app is unable to deliver data. This can happen if the buffer is large, or if the client/app cannot keep the buffer filled, or upon a stream end condition. We improve the sleep time computation for AudioTrack PCM callbacks. This minimizes the number of callbacks to NuPlayerRenderer. Bug: 21198655 Change-Id: I4247798a6638def2f0d8f1b46f60323482065cb2 --- include/media/AudioTrack.h | 20 +++++++--- media/libmedia/AudioTrack.cpp | 89 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 93 insertions(+), 16 deletions(-) diff --git a/include/media/AudioTrack.h b/include/media/AudioTrack.h index 3efa74c..ec16933 100644 --- a/include/media/AudioTrack.h +++ b/include/media/AudioTrack.h @@ -43,22 +43,30 @@ public: */ enum event_type { EVENT_MORE_DATA = 0, // Request to write more data to buffer. + // This event only occurs for TRANSFER_CALLBACK. // If this event is delivered but the callback handler - // does not want to write more data, the handler must explicitly + // does not want to write more data, the handler must // ignore the event by setting frameCount to zero. - EVENT_UNDERRUN = 1, // Buffer underrun occurred. + // This might occur, for example, if the application is + // waiting for source data or is at the end of stream. + // + // For data filling, it is preferred that the callback + // does not block and instead returns a short count on + // the amount of data actually delivered + // (or 0, if no data is currently available). + EVENT_UNDERRUN = 1, // Buffer underrun occurred. This will not occur for + // static tracks. EVENT_LOOP_END = 2, // Sample loop end was reached; playback restarted from - // loop start if loop count was not 0. + // loop start if loop count was not 0 for a static track. EVENT_MARKER = 3, // Playback head is at the specified marker position // (See setMarkerPosition()). EVENT_NEW_POS = 4, // Playback head is at a new position // (See setPositionUpdatePeriod()). - EVENT_BUFFER_END = 5, // Playback head is at the end of the buffer. - // Not currently used by android.media.AudioTrack. + EVENT_BUFFER_END = 5, // Playback has completed for a static track. EVENT_NEW_IAUDIOTRACK = 6, // IAudioTrack was re-created, either due to re-routing and // voluntary invalidation by mediaserver, or mediaserver crash. EVENT_STREAM_END = 7, // Sent after all the buffers queued in AF and HW are played - // back (after stop is called) + // back (after stop is called) for an offloaded track. #if 0 // FIXME not yet implemented EVENT_NEW_TIMESTAMP = 8, // Delivered periodically and when there's a significant change // in the mapping from frame position to presentation time. diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp index 070baa1..e2889b1 100644 --- a/media/libmedia/AudioTrack.cpp +++ b/media/libmedia/AudioTrack.cpp @@ -38,11 +38,23 @@ static const int kMaxLoopCountNotifications = 32; namespace android { // --------------------------------------------------------------------------- +// TODO: Move to a separate .h + template -const T &min(const T &x, const T &y) { +static inline const T &min(const T &x, const T &y) { return x < y ? x : y; } +template +static inline const T &max(const T &x, const T &y) { + return x > y ? x : y; +} + +static inline nsecs_t framesToNanoseconds(ssize_t frames, uint32_t sampleRate, float speed) +{ + return ((double)frames * 1000000000) / ((double)sampleRate * speed); +} + static int64_t convertTimespecToUs(const struct timespec &tv) { return tv.tv_sec * 1000000ll + tv.tv_nsec / 1000; @@ -1759,7 +1771,7 @@ nsecs_t AudioTrack::processAudioBuffer() // Cache other fields that will be needed soon uint32_t sampleRate = mSampleRate; float speed = mPlaybackRate.mSpeed; - uint32_t notificationFrames = mNotificationFramesAct; + const uint32_t notificationFrames = mNotificationFramesAct; if (mRefreshRemaining) { mRefreshRemaining = false; mRemainingFrames = notificationFrames; @@ -1797,7 +1809,14 @@ nsecs_t AudioTrack::processAudioBuffer() mLock.unlock(); + // get anchor time to account for callbacks. + const nsecs_t timeBeforeCallbacks = systemTime(); + if (waitStreamEnd) { + // FIXME: Instead of blocking in proxy->waitStreamEndDone(), Callback thread + // should wait on proxy futex and handle CBLK_STREAM_END_DONE within this function + // (and make sure we don't callback for more data while we're stopping). + // This helps with position, marker notifications, and track invalidation. struct timespec timeout; timeout.tv_sec = WAIT_STREAM_END_TIMEOUT_SEC; timeout.tv_nsec = 0; @@ -1882,12 +1901,17 @@ nsecs_t AudioTrack::processAudioBuffer() minFrames = kPoll * notificationFrames; } + // This "fudge factor" avoids soaking CPU, and compensates for late progress by server + static const nsecs_t kWaitPeriodNs = WAIT_PERIOD_MS * 1000000LL; + const nsecs_t timeAfterCallbacks = systemTime(); + // Convert frame units to time units nsecs_t ns = NS_WHENEVER; if (minFrames != (uint32_t) ~0) { - // This "fudge factor" avoids soaking CPU, and compensates for late progress by server - static const nsecs_t kFudgeNs = 10000000LL; // 10 ms - ns = ((double)minFrames * 1000000000) / ((double)sampleRate * speed) + kFudgeNs; + ns = framesToNanoseconds(minFrames, sampleRate, speed) + kWaitPeriodNs; + ns -= (timeAfterCallbacks - timeBeforeCallbacks); // account for callback time + // TODO: Should we warn if the callback time is too long? + if (ns < 0) ns = 0; } // If not supplying data by EVENT_MORE_DATA, then we're done @@ -1895,6 +1919,13 @@ nsecs_t AudioTrack::processAudioBuffer() return ns; } + // EVENT_MORE_DATA callback handling. + // Timing for linear pcm audio data formats can be derived directly from the + // buffer fill level. + // Timing for compressed data is not directly available from the buffer fill level, + // rather indirectly from waiting for blocking mode callbacks or waiting for obtain() + // to return a certain fill level. + struct timespec timeout; const struct timespec *requested = &ClientProxy::kForever; if (ns != NS_WHENEVER) { @@ -1925,12 +1956,15 @@ nsecs_t AudioTrack::processAudioBuffer() return NS_NEVER; } - if (mRetryOnPartialBuffer && !isOffloaded()) { + if (mRetryOnPartialBuffer && audio_is_linear_pcm(mFormat)) { mRetryOnPartialBuffer = false; if (avail < mRemainingFrames) { - int64_t myns = ((double)(mRemainingFrames - avail) * 1100000000) - / ((double)sampleRate * speed); - if (ns < 0 || myns < ns) { + if (ns > 0) { // account for obtain time + const nsecs_t timeNow = systemTime(); + ns = max((nsecs_t)0, ns - (timeNow - timeAfterCallbacks)); + } + nsecs_t myns = framesToNanoseconds(mRemainingFrames - avail, sampleRate, speed); + if (ns < 0 /* NS_WHENEVER */ || myns < ns) { ns = myns; } return ns; @@ -1953,7 +1987,42 @@ nsecs_t AudioTrack::processAudioBuffer() // Keep this thread going to handle timed events and // still try to get more data in intervals of WAIT_PERIOD_MS // but don't just loop and block the CPU, so wait - return WAIT_PERIOD_MS * 1000000LL; + + // mCbf(EVENT_MORE_DATA, ...) might either + // (1) Block until it can fill the buffer, returning 0 size on EOS. + // (2) Block until it can fill the buffer, returning 0 data (silence) on EOS. + // (3) Return 0 size when no data is available, does not wait for more data. + // + // (1) and (2) occurs with AudioPlayer/AwesomePlayer; (3) occurs with NuPlayer. + // We try to compute the wait time to avoid a tight sleep-wait cycle, + // especially for case (3). + // + // The decision to support (1) and (2) affect the sizing of mRemainingFrames + // and this loop; whereas for case (3) we could simply check once with the full + // buffer size and skip the loop entirely. + + nsecs_t myns; + if (audio_is_linear_pcm(mFormat)) { + // time to wait based on buffer occupancy + const nsecs_t datans = mRemainingFrames <= avail ? 0 : + framesToNanoseconds(mRemainingFrames - avail, sampleRate, speed); + // audio flinger thread buffer size (TODO: adjust for fast tracks) + const nsecs_t afns = framesToNanoseconds(mAfFrameCount, mAfSampleRate, speed); + // add a half the AudioFlinger buffer time to avoid soaking CPU if datans is 0. + myns = datans + (afns / 2); + } else { + // FIXME: This could ping quite a bit if the buffer isn't full. + // Note that when mState is stopping we waitStreamEnd, so it never gets here. + myns = kWaitPeriodNs; + } + if (ns > 0) { // account for obtain and callback time + const nsecs_t timeNow = systemTime(); + ns = max((nsecs_t)0, ns - (timeNow - timeAfterCallbacks)); + } + if (ns < 0 /* NS_WHENEVER */ || myns < ns) { + ns = myns; + } + return ns; } size_t releasedFrames = writtenSize / mFrameSize; -- cgit v1.1