From 2dd4bdd715f586d4d30cf90cc6fc2bbfbce60fe0 Mon Sep 17 00:00:00 2001 From: Glenn Kasten Date: Wed, 29 Aug 2012 11:10:32 -0700 Subject: Move libnbaio out of AudioFlinger libnbaio is now a separate shared library from AudioFlinger, rather than a static library used only by AudioFlinger. AudioBufferProvider interface is now also independent of AudioFlinger, moved to include/media/ Change-Id: I9bb62ffbc38d42a38b0af76e66da5e9ab1e0e21b --- media/libnbaio/Android.mk | 32 +++ media/libnbaio/AudioBufferProviderSource.cpp | 145 +++++++++++++ media/libnbaio/AudioStreamInSource.cpp | 83 ++++++++ media/libnbaio/AudioStreamOutSink.cpp | 82 ++++++++ media/libnbaio/LibsndfileSink.cpp | 50 +++++ media/libnbaio/LibsndfileSource.cpp | 80 +++++++ media/libnbaio/MonoPipe.cpp | 299 +++++++++++++++++++++++++++ media/libnbaio/MonoPipeReader.cpp | 89 ++++++++ media/libnbaio/NBAIO.cpp | 190 +++++++++++++++++ media/libnbaio/Pipe.cpp | 70 +++++++ media/libnbaio/PipeReader.cpp | 95 +++++++++ media/libnbaio/SourceAudioBufferProvider.cpp | 104 ++++++++++ media/libnbaio/roundup.c | 32 +++ 13 files changed, 1351 insertions(+) create mode 100644 media/libnbaio/Android.mk create mode 100644 media/libnbaio/AudioBufferProviderSource.cpp create mode 100644 media/libnbaio/AudioStreamInSource.cpp create mode 100644 media/libnbaio/AudioStreamOutSink.cpp create mode 100644 media/libnbaio/LibsndfileSink.cpp create mode 100644 media/libnbaio/LibsndfileSource.cpp create mode 100644 media/libnbaio/MonoPipe.cpp create mode 100644 media/libnbaio/MonoPipeReader.cpp create mode 100644 media/libnbaio/NBAIO.cpp create mode 100644 media/libnbaio/Pipe.cpp create mode 100644 media/libnbaio/PipeReader.cpp create mode 100644 media/libnbaio/SourceAudioBufferProvider.cpp create mode 100644 media/libnbaio/roundup.c (limited to 'media/libnbaio') diff --git a/media/libnbaio/Android.mk b/media/libnbaio/Android.mk new file mode 100644 index 0000000..757272f --- /dev/null +++ b/media/libnbaio/Android.mk @@ -0,0 +1,32 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + AudioBufferProviderSource.cpp \ + AudioStreamOutSink.cpp \ + AudioStreamInSource.cpp \ + NBAIO.cpp \ + MonoPipe.cpp \ + MonoPipeReader.cpp \ + Pipe.cpp \ + PipeReader.cpp \ + roundup.c \ + SourceAudioBufferProvider.cpp + +# libsndfile license is incompatible; uncomment to use for local debug only +#LOCAL_SRC_FILES += LibsndfileSink.cpp LibsndfileSource.cpp +#LOCAL_C_INCLUDES += path/to/libsndfile/src +#LOCAL_STATIC_LIBRARIES += libsndfile + +# uncomment for systrace +# LOCAL_CFLAGS += -DATRACE_TAG=ATRACE_TAG_AUDIO + +LOCAL_MODULE := libnbaio + +LOCAL_SHARED_LIBRARIES := \ + libcommon_time_client \ + libcutils \ + libutils + +include $(BUILD_SHARED_LIBRARY) diff --git a/media/libnbaio/AudioBufferProviderSource.cpp b/media/libnbaio/AudioBufferProviderSource.cpp new file mode 100644 index 0000000..74a6fdb --- /dev/null +++ b/media/libnbaio/AudioBufferProviderSource.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "AudioBufferProviderSource" +//#define LOG_NDEBUG 0 + +#include +#include +#include + +namespace android { + +AudioBufferProviderSource::AudioBufferProviderSource(AudioBufferProvider *provider, + NBAIO_Format format) : + NBAIO_Source(format), mProvider(provider), mConsumed(0) +{ + ALOG_ASSERT(provider != NULL); + ALOG_ASSERT(format != Format_Invalid); +} + +AudioBufferProviderSource::~AudioBufferProviderSource() +{ + if (mBuffer.raw != NULL) { + mProvider->releaseBuffer(&mBuffer); + } +} + +ssize_t AudioBufferProviderSource::availableToRead() +{ + if (CC_UNLIKELY(!mNegotiated)) { + return NEGOTIATE; + } + return mBuffer.raw != NULL ? mBuffer.frameCount - mConsumed : 0; +} + +ssize_t AudioBufferProviderSource::read(void *buffer, + size_t count, + int64_t readPTS) +{ + if (CC_UNLIKELY(!mNegotiated)) { + return NEGOTIATE; + } + if (CC_UNLIKELY(mBuffer.raw == NULL)) { + mBuffer.frameCount = count; + status_t status = mProvider->getNextBuffer(&mBuffer, readPTS); + if (status != OK) { + return status == NOT_ENOUGH_DATA ? (ssize_t) WOULD_BLOCK : (ssize_t) status; + } + ALOG_ASSERT(mBuffer.raw != NULL); + // mConsumed is 0 either from constructor or after releaseBuffer() + } + size_t available = mBuffer.frameCount - mConsumed; + if (CC_UNLIKELY(count > available)) { + count = available; + } + // count could be zero, either because count was zero on entry or + // available is zero, but both are unlikely so don't check for that + memcpy(buffer, (char *) mBuffer.raw + (mConsumed << mBitShift), count << mBitShift); + if (CC_UNLIKELY((mConsumed += count) >= mBuffer.frameCount)) { + mProvider->releaseBuffer(&mBuffer); + mBuffer.raw = NULL; + mConsumed = 0; + } + mFramesRead += count; + // For better responsiveness with large values of count, + // return a short count rather than continuing with next buffer. + // This gives the caller a chance to interpolate other actions. + return count; +} + +ssize_t AudioBufferProviderSource::readVia(readVia_t via, size_t total, void *user, + int64_t readPTS, size_t block) +{ + if (CC_UNLIKELY(!mNegotiated)) { + return NEGOTIATE; + } + if (CC_UNLIKELY(block == 0)) { + block = ~0; + } + for (size_t accumulator = 0; ; ) { + ALOG_ASSERT(accumulator <= total); + size_t count = total - accumulator; + if (CC_UNLIKELY(count == 0)) { + return accumulator; + } + if (CC_LIKELY(count > block)) { + count = block; + } + // 1 <= count <= block + if (CC_UNLIKELY(mBuffer.raw == NULL)) { + mBuffer.frameCount = count; + status_t status = mProvider->getNextBuffer(&mBuffer, readPTS); + if (CC_LIKELY(status == OK)) { + ALOG_ASSERT(mBuffer.raw != NULL && mBuffer.frameCount <= count); + // mConsumed is 0 either from constructor or after releaseBuffer() + continue; + } + // FIXME simplify logic - does the initial count and block checks again for no reason; + // don't you just want to fall through to the size_t available line? + if (CC_LIKELY(status == NOT_ENOUGH_DATA)) { + status = WOULD_BLOCK; + } + return accumulator > 0 ? accumulator : (ssize_t) status; + } + size_t available = mBuffer.frameCount - mConsumed; + if (CC_UNLIKELY(count > available)) { + count = available; + } + if (CC_LIKELY(count > 0)) { + char* readTgt = (char *) mBuffer.raw + (mConsumed << mBitShift); + ssize_t ret = via(user, readTgt, count, readPTS); + if (CC_UNLIKELY(ret <= 0)) { + if (CC_LIKELY(accumulator > 0)) { + return accumulator; + } + return ret; + } + ALOG_ASSERT((size_t) ret <= count); + mFramesRead += ret; + accumulator += ret; + if (CC_LIKELY((mConsumed += ret) < mBuffer.frameCount)) { + continue; + } + } + mProvider->releaseBuffer(&mBuffer); + mBuffer.raw = NULL; + mConsumed = 0; + // don't get next buffer until we really need it + } +} + +} // namespace android diff --git a/media/libnbaio/AudioStreamInSource.cpp b/media/libnbaio/AudioStreamInSource.cpp new file mode 100644 index 0000000..05273f6 --- /dev/null +++ b/media/libnbaio/AudioStreamInSource.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "AudioStreamInSource" +//#define LOG_NDEBUG 0 + +#include +#include +#include + +namespace android { + +AudioStreamInSource::AudioStreamInSource(audio_stream_in *stream) : + NBAIO_Source(), + mStream(stream), + mStreamBufferSizeBytes(0), + mFramesOverrun(0), + mOverruns(0) +{ + ALOG_ASSERT(stream != NULL); +} + +AudioStreamInSource::~AudioStreamInSource() +{ +} + +ssize_t AudioStreamInSource::negotiate(const NBAIO_Format offers[], size_t numOffers, + NBAIO_Format counterOffers[], size_t& numCounterOffers) +{ + if (mFormat == Format_Invalid) { + mStreamBufferSizeBytes = mStream->common.get_buffer_size(&mStream->common); + audio_format_t streamFormat = mStream->common.get_format(&mStream->common); + if (streamFormat == AUDIO_FORMAT_PCM_16_BIT) { + uint32_t sampleRate = mStream->common.get_sample_rate(&mStream->common); + audio_channel_mask_t channelMask = + (audio_channel_mask_t) mStream->common.get_channels(&mStream->common); + mFormat = Format_from_SR_C(sampleRate, popcount(channelMask)); + mBitShift = Format_frameBitShift(mFormat); + } + } + return NBAIO_Source::negotiate(offers, numOffers, counterOffers, numCounterOffers); +} + +size_t AudioStreamInSource::framesOverrun() +{ + uint32_t framesOverrun = mStream->get_input_frames_lost(mStream); + if (framesOverrun > 0) { + mFramesOverrun += framesOverrun; + // FIXME only increment for contiguous ranges + ++mOverruns; + } + return mFramesOverrun; +} + +ssize_t AudioStreamInSource::read(void *buffer, size_t count) +{ + if (CC_UNLIKELY(mFormat == Format_Invalid)) { + return NEGOTIATE; + } + ssize_t bytesRead = mStream->read(mStream, buffer, count << mBitShift); + if (bytesRead > 0) { + size_t framesRead = bytesRead >> mBitShift; + mFramesRead += framesRead; + return framesRead; + } else { + return bytesRead; + } +} + +} // namespace android diff --git a/media/libnbaio/AudioStreamOutSink.cpp b/media/libnbaio/AudioStreamOutSink.cpp new file mode 100644 index 0000000..6f525e5 --- /dev/null +++ b/media/libnbaio/AudioStreamOutSink.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "AudioStreamOutSink" +//#define LOG_NDEBUG 0 + +#include +#include + +namespace android { + +AudioStreamOutSink::AudioStreamOutSink(audio_stream_out *stream) : + NBAIO_Sink(), + mStream(stream), + mStreamBufferSizeBytes(0) +{ + ALOG_ASSERT(stream != NULL); +} + +AudioStreamOutSink::~AudioStreamOutSink() +{ +} + +ssize_t AudioStreamOutSink::negotiate(const NBAIO_Format offers[], size_t numOffers, + NBAIO_Format counterOffers[], size_t& numCounterOffers) +{ + if (mFormat == Format_Invalid) { + mStreamBufferSizeBytes = mStream->common.get_buffer_size(&mStream->common); + audio_format_t streamFormat = mStream->common.get_format(&mStream->common); + if (streamFormat == AUDIO_FORMAT_PCM_16_BIT) { + uint32_t sampleRate = mStream->common.get_sample_rate(&mStream->common); + audio_channel_mask_t channelMask = + (audio_channel_mask_t) mStream->common.get_channels(&mStream->common); + mFormat = Format_from_SR_C(sampleRate, popcount(channelMask)); + mBitShift = Format_frameBitShift(mFormat); + } + } + return NBAIO_Sink::negotiate(offers, numOffers, counterOffers, numCounterOffers); +} + +ssize_t AudioStreamOutSink::write(const void *buffer, size_t count) +{ + if (!mNegotiated) { + return NEGOTIATE; + } + ALOG_ASSERT(mFormat != Format_Invalid); + ssize_t ret = mStream->write(mStream, buffer, count << mBitShift); + if (ret > 0) { + ret >>= mBitShift; + mFramesWritten += ret; + } else { + // FIXME verify HAL implementations are returning the correct error codes e.g. WOULD_BLOCK + } + return ret; +} + +status_t AudioStreamOutSink::getNextWriteTimestamp(int64_t *timestamp) { + ALOG_ASSERT(timestamp != NULL); + + if (NULL == mStream) + return INVALID_OPERATION; + + if (NULL == mStream->get_next_write_timestamp) + return INVALID_OPERATION; + + return mStream->get_next_write_timestamp(mStream, timestamp); +} + +} // namespace android diff --git a/media/libnbaio/LibsndfileSink.cpp b/media/libnbaio/LibsndfileSink.cpp new file mode 100644 index 0000000..77debc0 --- /dev/null +++ b/media/libnbaio/LibsndfileSink.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "LibsndfileSink" +//#define LOG_NDEBUG 0 + +#include +#include +#include + +namespace android { + +LibsndfileSink::LibsndfileSink(SNDFILE *sndfile, const SF_INFO &sfinfo) : + NBAIO_Sink(Format_from_SR_C(sfinfo.samplerate, sfinfo.channels)), + mSndfile(sndfile) +{ +} + +LibsndfileSink::~LibsndfileSink() +{ + // do not close mSndfile; we don't own it +} + +ssize_t LibsndfileSink::write(const void *buffer, size_t count) +{ + if (!mNegotiated) { + return (ssize_t) NEGOTIATE; + } + if (mSndfile == NULL) { + return (ssize_t) NO_INIT; + } + sf_count_t actual = sf_writef_short(mSndfile, (short *) buffer, (sf_count_t) count); + mFramesWritten += actual; + return actual; +} + +} // namespace android diff --git a/media/libnbaio/LibsndfileSource.cpp b/media/libnbaio/LibsndfileSource.cpp new file mode 100644 index 0000000..98610e0 --- /dev/null +++ b/media/libnbaio/LibsndfileSource.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "LibsndfileSource" +//#define LOG_NDEBUG 0 + +#include +#include +#include + +namespace android { + +LibsndfileSource::LibsndfileSource(SNDFILE *sndfile, const SF_INFO &sfinfo, bool loop) : + NBAIO_Source(Format_from_SR_C(sfinfo.samplerate, sfinfo.channels)), + mSndfile(sndfile), + mEstimatedFramesUntilEOF(sfinfo.frames), + mLooping(loop && sfinfo.seekable), + mReadAnyFramesThisLoopCycle(false) +{ +} + +LibsndfileSource::~LibsndfileSource() +{ + // do not close mSndfile; we don't own it +} + +ssize_t LibsndfileSource::availableToRead() +{ + // after we reach the presumed EOF, report infinity just in case there's actually more + return !mLooping && mEstimatedFramesUntilEOF > 0 ? mEstimatedFramesUntilEOF : SSIZE_MAX; +} + +ssize_t LibsndfileSource::read(void *buffer, size_t count) +{ + if (!mNegotiated) { + return (ssize_t) NEGOTIATE; + } + if (mSndfile == NULL) { + return (ssize_t) NO_INIT; + } + sf_count_t actual = sf_readf_short(mSndfile, (short *) buffer, (sf_count_t) count); + // Detect EOF by zero frames read, not by mFramesUntilEOF as it could be inaccurate + if (actual == 0) { + if (mLooping) { + if (mReadAnyFramesThisLoopCycle) { + (void) sf_seek(mSndfile, (sf_count_t) 0, SEEK_SET); + mReadAnyFramesThisLoopCycle = false; + } else { + // We didn't read any frames during the current loop cycle, so disable + // further looping to prevent the caller from busy waiting at read(). + // This is especially important when looping an empty file. + mLooping = false; + } + } + } else { + mFramesRead += actual; + if (actual >= mEstimatedFramesUntilEOF) { + mEstimatedFramesUntilEOF = 0; + } else { + mEstimatedFramesUntilEOF -= actual; + } + mReadAnyFramesThisLoopCycle = true; + } + return actual; +} + +} // namespace android diff --git a/media/libnbaio/MonoPipe.cpp b/media/libnbaio/MonoPipe.cpp new file mode 100644 index 0000000..c426efb --- /dev/null +++ b/media/libnbaio/MonoPipe.cpp @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "MonoPipe" +//#define LOG_NDEBUG 0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace android { + +MonoPipe::MonoPipe(size_t reqFrames, NBAIO_Format format, bool writeCanBlock) : + NBAIO_Sink(format), + mUpdateSeq(0), + mReqFrames(reqFrames), + mMaxFrames(roundup(reqFrames)), + mBuffer(malloc(mMaxFrames * Format_frameSize(format))), + mFront(0), + mRear(0), + mWriteTsValid(false), + // mWriteTs + mSetpoint((reqFrames * 11) / 16), + mWriteCanBlock(writeCanBlock) +{ + CCHelper tmpHelper; + status_t res; + uint64_t N, D; + + mNextRdPTS = AudioBufferProvider::kInvalidPTS; + + mSamplesToLocalTime.a_zero = 0; + mSamplesToLocalTime.b_zero = 0; + mSamplesToLocalTime.a_to_b_numer = 0; + mSamplesToLocalTime.a_to_b_denom = 0; + + D = Format_sampleRate(format); + if (OK != (res = tmpHelper.getLocalFreq(&N))) { + ALOGE("Failed to fetch local time frequency when constructing a" + " MonoPipe (res = %d). getNextWriteTimestamp calls will be" + " non-functional", res); + return; + } + + LinearTransform::reduce(&N, &D); + static const uint64_t kSignedHiBitsMask = ~(0x7FFFFFFFull); + static const uint64_t kUnsignedHiBitsMask = ~(0xFFFFFFFFull); + if ((N & kSignedHiBitsMask) || (D & kUnsignedHiBitsMask)) { + ALOGE("Cannot reduce sample rate to local clock frequency ratio to fit" + " in a 32/32 bit rational. (max reduction is 0x%016llx/0x%016llx" + "). getNextWriteTimestamp calls will be non-functional", N, D); + return; + } + + mSamplesToLocalTime.a_to_b_numer = static_cast(N); + mSamplesToLocalTime.a_to_b_denom = static_cast(D); +} + +MonoPipe::~MonoPipe() +{ + free(mBuffer); +} + +ssize_t MonoPipe::availableToWrite() const +{ + if (CC_UNLIKELY(!mNegotiated)) { + return NEGOTIATE; + } + // uses mMaxFrames not mReqFrames, so allows "over-filling" the pipe beyond requested limit + ssize_t ret = mMaxFrames - (mRear - android_atomic_acquire_load(&mFront)); + ALOG_ASSERT((0 <= ret) && (ret <= mMaxFrames)); + return ret; +} + +ssize_t MonoPipe::write(const void *buffer, size_t count) +{ + if (CC_UNLIKELY(!mNegotiated)) { + return NEGOTIATE; + } + size_t totalFramesWritten = 0; + while (count > 0) { + // can't return a negative value, as we already checked for !mNegotiated + size_t avail = availableToWrite(); + size_t written = avail; + if (CC_LIKELY(written > count)) { + written = count; + } + size_t rear = mRear & (mMaxFrames - 1); + size_t part1 = mMaxFrames - rear; + if (part1 > written) { + part1 = written; + } + if (CC_LIKELY(part1 > 0)) { + memcpy((char *) mBuffer + (rear << mBitShift), buffer, part1 << mBitShift); + if (CC_UNLIKELY(rear + part1 == mMaxFrames)) { + size_t part2 = written - part1; + if (CC_LIKELY(part2 > 0)) { + memcpy(mBuffer, (char *) buffer + (part1 << mBitShift), part2 << mBitShift); + } + } + android_atomic_release_store(written + mRear, &mRear); + totalFramesWritten += written; + } + if (!mWriteCanBlock) { + break; + } + count -= written; + buffer = (char *) buffer + (written << mBitShift); + // Simulate blocking I/O by sleeping at different rates, depending on a throttle. + // The throttle tries to keep the mean pipe depth near the setpoint, with a slight jitter. + uint32_t ns; + if (written > 0) { + size_t filled = (mMaxFrames - avail) + written; + // FIXME cache these values to avoid re-computation + if (filled <= mSetpoint / 2) { + // pipe is (nearly) empty, fill quickly + ns = written * ( 500000000 / Format_sampleRate(mFormat)); + } else if (filled <= (mSetpoint * 3) / 4) { + // pipe is below setpoint, fill at slightly faster rate + ns = written * ( 750000000 / Format_sampleRate(mFormat)); + } else if (filled <= (mSetpoint * 5) / 4) { + // pipe is at setpoint, fill at nominal rate + ns = written * (1000000000 / Format_sampleRate(mFormat)); + } else if (filled <= (mSetpoint * 3) / 2) { + // pipe is above setpoint, fill at slightly slower rate + ns = written * (1150000000 / Format_sampleRate(mFormat)); + } else if (filled <= (mSetpoint * 7) / 4) { + // pipe is overflowing, fill slowly + ns = written * (1350000000 / Format_sampleRate(mFormat)); + } else { + // pipe is severely overflowing + ns = written * (1750000000 / Format_sampleRate(mFormat)); + } + } else { + ns = count * (1350000000 / Format_sampleRate(mFormat)); + } + if (ns > 999999999) { + ns = 999999999; + } + struct timespec nowTs; + bool nowTsValid = !clock_gettime(CLOCK_MONOTONIC, &nowTs); + // deduct the elapsed time since previous write() completed + if (nowTsValid && mWriteTsValid) { + time_t sec = nowTs.tv_sec - mWriteTs.tv_sec; + long nsec = nowTs.tv_nsec - mWriteTs.tv_nsec; + if (nsec < 0) { + --sec; + nsec += 1000000000; + } + if (sec == 0) { + if ((long) ns > nsec) { + ns -= nsec; + } else { + ns = 0; + } + } + } + if (ns > 0) { + const struct timespec req = {0, ns}; + nanosleep(&req, NULL); + } + // record the time that this write() completed + if (nowTsValid) { + mWriteTs = nowTs; + if ((mWriteTs.tv_nsec += ns) >= 1000000000) { + mWriteTs.tv_nsec -= 1000000000; + ++mWriteTs.tv_sec; + } + } + mWriteTsValid = nowTsValid; + } + mFramesWritten += totalFramesWritten; + return totalFramesWritten; +} + +void MonoPipe::setAvgFrames(size_t setpoint) +{ + mSetpoint = setpoint; +} + +status_t MonoPipe::getNextWriteTimestamp(int64_t *timestamp) +{ + int32_t front; + + ALOG_ASSERT(NULL != timestamp); + + if (0 == mSamplesToLocalTime.a_to_b_denom) + return UNKNOWN_ERROR; + + observeFrontAndNRPTS(&front, timestamp); + + if (AudioBufferProvider::kInvalidPTS != *timestamp) { + // If we have a valid read-pointer and next read timestamp pair, then + // use the current value of the write pointer to figure out how many + // frames are in the buffer, and offset the timestamp by that amt. Then + // next time we write to the MonoPipe, the data will hit the speakers at + // the next read timestamp plus the current amount of data in the + // MonoPipe. + size_t pendingFrames = (mRear - front) & (mMaxFrames - 1); + *timestamp = offsetTimestampByAudioFrames(*timestamp, pendingFrames); + } + + return OK; +} + +void MonoPipe::updateFrontAndNRPTS(int32_t newFront, int64_t newNextRdPTS) +{ + // Set the MSB of the update sequence number to indicate that there is a + // multi-variable update in progress. Use an atomic store with an "acquire" + // barrier to make sure that the next operations cannot be re-ordered and + // take place before the change to mUpdateSeq is commited.. + int32_t tmp = mUpdateSeq | 0x80000000; + android_atomic_acquire_store(tmp, &mUpdateSeq); + + // Update mFront and mNextRdPTS + mFront = newFront; + mNextRdPTS = newNextRdPTS; + + // We are finished with the update. Compute the next sequnce number (which + // should be the old sequence number, plus one, and with the MSB cleared) + // and then store it in mUpdateSeq using an atomic store with a "release" + // barrier so our update operations cannot be re-ordered past the update of + // the sequence number. + tmp = (tmp + 1) & 0x7FFFFFFF; + android_atomic_release_store(tmp, &mUpdateSeq); +} + +void MonoPipe::observeFrontAndNRPTS(int32_t *outFront, int64_t *outNextRdPTS) +{ + // Perform an atomic observation of mFront and mNextRdPTS. Basically, + // atomically observe the sequence number, then observer the variables, then + // atomically observe the sequence number again. If the two observations of + // the sequence number match, and the update-in-progress bit was not set, + // then we know we have a successful atomic observation. Otherwise, we loop + // around and try again. + // + // Note, it is very important that the observer be a lower priority thread + // than the updater. If the updater is lower than the observer, or they are + // the same priority and running with SCHED_FIFO (implying that quantum + // based premption is disabled) then we run the risk of deadlock. + int32_t seqOne, seqTwo; + + do { + seqOne = android_atomic_acquire_load(&mUpdateSeq); + *outFront = mFront; + *outNextRdPTS = mNextRdPTS; + seqTwo = android_atomic_release_load(&mUpdateSeq); + } while ((seqOne != seqTwo) || (seqOne & 0x80000000)); +} + +int64_t MonoPipe::offsetTimestampByAudioFrames(int64_t ts, size_t audFrames) +{ + if (0 == mSamplesToLocalTime.a_to_b_denom) + return AudioBufferProvider::kInvalidPTS; + + if (ts == AudioBufferProvider::kInvalidPTS) + return AudioBufferProvider::kInvalidPTS; + + int64_t frame_lt_duration; + if (!mSamplesToLocalTime.doForwardTransform(audFrames, + &frame_lt_duration)) { + // This should never fail, but if there is a bug which is causing it + // to fail, this message would probably end up flooding the logs + // because the conversion would probably fail forever. Log the + // error, but then zero out the ratio in the linear transform so + // that we don't try to do any conversions from now on. This + // MonoPipe's getNextWriteTimestamp is now broken for good. + ALOGE("Overflow when attempting to convert %d audio frames to" + " duration in local time. getNextWriteTimestamp will fail from" + " now on.", audFrames); + mSamplesToLocalTime.a_to_b_numer = 0; + mSamplesToLocalTime.a_to_b_denom = 0; + return AudioBufferProvider::kInvalidPTS; + } + + return ts + frame_lt_duration; +} + +} // namespace android diff --git a/media/libnbaio/MonoPipeReader.cpp b/media/libnbaio/MonoPipeReader.cpp new file mode 100644 index 0000000..394f6ac --- /dev/null +++ b/media/libnbaio/MonoPipeReader.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "MonoPipeReader" +//#define LOG_NDEBUG 0 + +#include +#include +#include + +namespace android { + +MonoPipeReader::MonoPipeReader(MonoPipe* pipe) : + NBAIO_Source(pipe->mFormat), + mPipe(pipe) +{ +} + +MonoPipeReader::~MonoPipeReader() +{ +} + +ssize_t MonoPipeReader::availableToRead() +{ + if (CC_UNLIKELY(!mNegotiated)) { + return NEGOTIATE; + } + ssize_t ret = android_atomic_acquire_load(&mPipe->mRear) - mPipe->mFront; + ALOG_ASSERT((0 <= ret) && (ret <= mMaxFrames)); + return ret; +} + +ssize_t MonoPipeReader::read(void *buffer, size_t count, int64_t readPTS) +{ + // Compute the "next read PTS" and cache it. Callers of read pass a read + // PTS indicating the local time for which they are requesting data along + // with a count (which is the number of audio frames they are going to + // ultimately pass to the next stage of the pipeline). Offsetting readPTS + // by the duration of count will give us the readPTS which will be passed to + // us next time, assuming they system continues to operate in steady state + // with no discontinuities. We stash this value so it can be used by the + // MonoPipe writer to imlement getNextWriteTimestamp. + int64_t nextReadPTS; + nextReadPTS = mPipe->offsetTimestampByAudioFrames(readPTS, count); + + // count == 0 is unlikely and not worth checking for explicitly; will be handled automatically + ssize_t red = availableToRead(); + if (CC_UNLIKELY(red <= 0)) { + // Uh-oh, looks like we are underflowing. Update the next read PTS and + // get out. + mPipe->updateFrontAndNRPTS(mPipe->mFront, nextReadPTS); + return red; + } + if (CC_LIKELY((size_t) red > count)) { + red = count; + } + size_t front = mPipe->mFront & (mPipe->mMaxFrames - 1); + size_t part1 = mPipe->mMaxFrames - front; + if (part1 > (size_t) red) { + part1 = red; + } + if (CC_LIKELY(part1 > 0)) { + memcpy(buffer, (char *) mPipe->mBuffer + (front << mBitShift), part1 << mBitShift); + if (CC_UNLIKELY(front + part1 == mPipe->mMaxFrames)) { + size_t part2 = red - part1; + if (CC_LIKELY(part2 > 0)) { + memcpy((char *) buffer + (part1 << mBitShift), mPipe->mBuffer, part2 << mBitShift); + } + } + mPipe->updateFrontAndNRPTS(red + mPipe->mFront, nextReadPTS); + mFramesRead += red; + } + return red; +} + +} // namespace android diff --git a/media/libnbaio/NBAIO.cpp b/media/libnbaio/NBAIO.cpp new file mode 100644 index 0000000..00d2017 --- /dev/null +++ b/media/libnbaio/NBAIO.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "NBAIO" +//#define LOG_NDEBUG 0 + +#include +#include + +namespace android { + +size_t Format_frameSize(NBAIO_Format format) +{ + switch (format) { + case Format_SR44_1_C2_I16: + case Format_SR48_C2_I16: + return 2 * sizeof(short); + case Format_SR44_1_C1_I16: + case Format_SR48_C1_I16: + return 1 * sizeof(short); + case Format_Invalid: + default: + return 0; + } +} + +size_t Format_frameBitShift(NBAIO_Format format) +{ + switch (format) { + case Format_SR44_1_C2_I16: + case Format_SR48_C2_I16: + return 2; // 1 << 2 == 2 * sizeof(short) + case Format_SR44_1_C1_I16: + case Format_SR48_C1_I16: + return 1; // 1 << 1 == 1 * sizeof(short) + case Format_Invalid: + default: + return 0; + } +} + +unsigned Format_sampleRate(NBAIO_Format format) +{ + switch (format) { + case Format_SR44_1_C1_I16: + case Format_SR44_1_C2_I16: + return 44100; + case Format_SR48_C1_I16: + case Format_SR48_C2_I16: + return 48000; + case Format_Invalid: + default: + return 0; + } +} + +unsigned Format_channelCount(NBAIO_Format format) +{ + switch (format) { + case Format_SR44_1_C1_I16: + case Format_SR48_C1_I16: + return 1; + case Format_SR44_1_C2_I16: + case Format_SR48_C2_I16: + return 2; + case Format_Invalid: + default: + return 0; + } +} + +NBAIO_Format Format_from_SR_C(unsigned sampleRate, unsigned channelCount) +{ + if (sampleRate == 44100 && channelCount == 2) return Format_SR44_1_C2_I16; + if (sampleRate == 48000 && channelCount == 2) return Format_SR48_C2_I16; + if (sampleRate == 44100 && channelCount == 1) return Format_SR44_1_C1_I16; + if (sampleRate == 48000 && channelCount == 1) return Format_SR48_C1_I16; + return Format_Invalid; +} + +// This is a default implementation; it is expected that subclasses will optimize this. +ssize_t NBAIO_Sink::writeVia(writeVia_t via, size_t total, void *user, size_t block) +{ + if (!mNegotiated) { + return (ssize_t) NEGOTIATE; + } + static const size_t maxBlock = 32; + size_t frameSize = Format_frameSize(mFormat); + ALOG_ASSERT(frameSize > 0 && frameSize <= 8); + // double guarantees alignment for stack similar to what malloc() gives for heap + if (block == 0 || block > maxBlock) { + block = maxBlock; + } + double buffer[((frameSize * block) + sizeof(double) - 1) / sizeof(double)]; + size_t accumulator = 0; + while (accumulator < total) { + size_t count = total - accumulator; + if (count > block) { + count = block; + } + ssize_t ret = via(user, buffer, count); + if (ret > 0) { + ALOG_ASSERT((size_t) ret <= count); + size_t maxRet = ret; + ret = write(buffer, maxRet); + if (ret > 0) { + ALOG_ASSERT((size_t) ret <= maxRet); + accumulator += ret; + continue; + } + } + return accumulator > 0 ? accumulator : ret; + } + return accumulator; +} + +// This is a default implementation; it is expected that subclasses will optimize this. +ssize_t NBAIO_Source::readVia(readVia_t via, size_t total, void *user, + int64_t readPTS, size_t block) +{ + if (!mNegotiated) { + return (ssize_t) NEGOTIATE; + } + static const size_t maxBlock = 32; + size_t frameSize = Format_frameSize(mFormat); + ALOG_ASSERT(frameSize > 0 && frameSize <= 8); + // double guarantees alignment for stack similar to what malloc() gives for heap + if (block == 0 || block > maxBlock) { + block = maxBlock; + } + double buffer[((frameSize * block) + sizeof(double) - 1) / sizeof(double)]; + size_t accumulator = 0; + while (accumulator < total) { + size_t count = total - accumulator; + if (count > block) { + count = block; + } + ssize_t ret = read(buffer, count, readPTS); + if (ret > 0) { + ALOG_ASSERT((size_t) ret <= count); + size_t maxRet = ret; + ret = via(user, buffer, maxRet, readPTS); + if (ret > 0) { + ALOG_ASSERT((size_t) ret <= maxRet); + accumulator += ret; + continue; + } + } + return accumulator > 0 ? accumulator : ret; + } + return accumulator; +} + +// Default implementation that only accepts my mFormat +ssize_t NBAIO_Port::negotiate(const NBAIO_Format offers[], size_t numOffers, + NBAIO_Format counterOffers[], size_t& numCounterOffers) +{ + ALOGV("negotiate offers=%p numOffers=%u countersOffers=%p numCounterOffers=%u", + offers, numOffers, counterOffers, numCounterOffers); + if (mFormat != Format_Invalid) { + for (size_t i = 0; i < numOffers; ++i) { + if (offers[i] == mFormat) { + mNegotiated = true; + return i; + } + } + if (numCounterOffers > 0) { + counterOffers[0] = mFormat; + } + numCounterOffers = 1; + } else { + numCounterOffers = 0; + } + return (ssize_t) NEGOTIATE; +} + +} // namespace android diff --git a/media/libnbaio/Pipe.cpp b/media/libnbaio/Pipe.cpp new file mode 100644 index 0000000..1c21f9c --- /dev/null +++ b/media/libnbaio/Pipe.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "Pipe" +//#define LOG_NDEBUG 0 + +#include +#include +#include +#include +#include + +namespace android { + +Pipe::Pipe(size_t maxFrames, NBAIO_Format format) : + NBAIO_Sink(format), + mMaxFrames(roundup(maxFrames)), + mBuffer(malloc(mMaxFrames * Format_frameSize(format))), + mRear(0), + mReaders(0) +{ +} + +Pipe::~Pipe() +{ + ALOG_ASSERT(android_atomic_acquire_load(&mReaders) == 0); + free(mBuffer); +} + +ssize_t Pipe::write(const void *buffer, size_t count) +{ + // count == 0 is unlikely and not worth checking for + if (CC_UNLIKELY(!mNegotiated)) { + return NEGOTIATE; + } + // write() is not multi-thread safe w.r.t. itself, so no mutex or atomic op needed to read mRear + size_t rear = mRear & (mMaxFrames - 1); + size_t written = mMaxFrames - rear; + if (CC_LIKELY(written > count)) { + written = count; + } + memcpy((char *) mBuffer + (rear << mBitShift), buffer, written << mBitShift); + if (CC_UNLIKELY(rear + written == mMaxFrames)) { + if (CC_UNLIKELY((count -= written) > rear)) { + count = rear; + } + if (CC_LIKELY(count > 0)) { + memcpy(mBuffer, (char *) buffer + (written << mBitShift), count << mBitShift); + written += count; + } + } + android_atomic_release_store(written + mRear, &mRear); + mFramesWritten += written; + return written; +} + +} // namespace android diff --git a/media/libnbaio/PipeReader.cpp b/media/libnbaio/PipeReader.cpp new file mode 100644 index 0000000..d786b84 --- /dev/null +++ b/media/libnbaio/PipeReader.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "PipeReader" +//#define LOG_NDEBUG 0 + +#include +#include +#include + +namespace android { + +PipeReader::PipeReader(Pipe& pipe) : + NBAIO_Source(pipe.mFormat), + mPipe(pipe), + // any data already in the pipe is not visible to this PipeReader + mFront(android_atomic_acquire_load(&pipe.mRear)), + mFramesOverrun(0), + mOverruns(0) +{ + android_atomic_inc(&pipe.mReaders); +} + +PipeReader::~PipeReader() +{ + int32_t readers = android_atomic_dec(&mPipe.mReaders); + ALOG_ASSERT(readers > 0); +} + +ssize_t PipeReader::availableToRead() +{ + if (CC_UNLIKELY(!mNegotiated)) { + return NEGOTIATE; + } + int32_t rear = android_atomic_acquire_load(&mPipe.mRear); + // read() is not multi-thread safe w.r.t. itself, so no mutex or atomic op needed to read mFront + size_t avail = rear - mFront; + if (CC_UNLIKELY(avail > mPipe.mMaxFrames)) { + // Discard 1/16 of the most recent data in pipe to avoid another overrun immediately + int32_t oldFront = mFront; + mFront = rear - mPipe.mMaxFrames + (mPipe.mMaxFrames >> 4); + mFramesOverrun += (size_t) (mFront - oldFront); + ++mOverruns; + return OVERRUN; + } + return avail; +} + +ssize_t PipeReader::read(void *buffer, size_t count, int64_t readPTS) +{ + ssize_t avail = availableToRead(); + if (CC_UNLIKELY(avail <= 0)) { + return avail; + } + // An overrun can occur from here on and be silently ignored, + // but it will be caught at next read() + if (CC_LIKELY(count > (size_t) avail)) { + count = avail; + } + size_t front = mFront & (mPipe.mMaxFrames - 1); + size_t red = mPipe.mMaxFrames - front; + if (CC_LIKELY(red > count)) { + red = count; + } + // In particular, an overrun during the memcpy will result in reading corrupt data + memcpy(buffer, (char *) mPipe.mBuffer + (front << mBitShift), red << mBitShift); + // We could re-read the rear pointer here to detect the corruption, but why bother? + if (CC_UNLIKELY(front + red == mPipe.mMaxFrames)) { + if (CC_UNLIKELY((count -= red) > front)) { + count = front; + } + if (CC_LIKELY(count > 0)) { + memcpy((char *) buffer + (red << mBitShift), mPipe.mBuffer, count << mBitShift); + red += count; + } + } + mFront += red; + mFramesRead += red; + return red; +} + +} // namespace android diff --git a/media/libnbaio/SourceAudioBufferProvider.cpp b/media/libnbaio/SourceAudioBufferProvider.cpp new file mode 100644 index 0000000..d11a86c --- /dev/null +++ b/media/libnbaio/SourceAudioBufferProvider.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "SourceAudioBufferProvider" +//#define LOG_NDEBUG 0 + +#include +#include + +namespace android { + +SourceAudioBufferProvider::SourceAudioBufferProvider(const sp& source) : + mSource(source), + // mFrameBitShiftFormat below + mAllocated(NULL), mSize(0), mOffset(0), mRemaining(0), mGetCount(0) +{ + ALOG_ASSERT(source != 0); + + // negotiate with source + NBAIO_Format counterOffers[1]; + size_t numCounterOffers = 1; + ssize_t index = source->negotiate(NULL, 0, counterOffers, numCounterOffers); + ALOG_ASSERT(index == (ssize_t) NEGOTIATE && numCounterOffers > 0); + numCounterOffers = 0; + index = source->negotiate(counterOffers, 1, NULL, numCounterOffers); + ALOG_ASSERT(index == 0); + mFrameBitShift = Format_frameBitShift(source->format()); +} + +SourceAudioBufferProvider::~SourceAudioBufferProvider() +{ + free(mAllocated); +} + +status_t SourceAudioBufferProvider::getNextBuffer(Buffer *buffer, int64_t pts) +{ + ALOG_ASSERT(buffer != NULL && buffer->frameCount > 0 && mGetCount == 0); + // any leftover data available? + if (mRemaining > 0) { + ALOG_ASSERT(mOffset + mRemaining <= mSize); + if (mRemaining < buffer->frameCount) { + buffer->frameCount = mRemaining; + } + buffer->raw = (char *) mAllocated + (mOffset << mFrameBitShift); + mGetCount = buffer->frameCount; + return OK; + } + // do we need to reallocate? + if (buffer->frameCount > mSize) { + free(mAllocated); + mAllocated = malloc(buffer->frameCount << mFrameBitShift); + mSize = buffer->frameCount; + } + // read from source + ssize_t actual = mSource->read(mAllocated, buffer->frameCount, pts); + if (actual > 0) { + ALOG_ASSERT((size_t) actual <= buffer->frameCount); + mOffset = 0; + mRemaining = actual; + buffer->raw = mAllocated; + buffer->frameCount = actual; + mGetCount = actual; + return OK; + } + buffer->raw = NULL; + buffer->frameCount = 0; + mGetCount = 0; + return NOT_ENOUGH_DATA; +} + +void SourceAudioBufferProvider::releaseBuffer(Buffer *buffer) +{ + ALOG_ASSERT((buffer != NULL) && + (buffer->raw == (char *) mAllocated + (mOffset << mFrameBitShift)) && + (buffer->frameCount <= mGetCount) && + (mGetCount <= mRemaining) && + (mOffset + mRemaining <= mSize)); + mOffset += buffer->frameCount; + mRemaining -= buffer->frameCount; + buffer->raw = NULL; + buffer->frameCount = 0; + mGetCount = 0; +} + +size_t SourceAudioBufferProvider::framesReady() const +{ + ssize_t avail = mSource->availableToRead(); + return avail < 0 ? 0 : (size_t) avail; +} + +} // namespace android diff --git a/media/libnbaio/roundup.c b/media/libnbaio/roundup.c new file mode 100644 index 0000000..1d552d1 --- /dev/null +++ b/media/libnbaio/roundup.c @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +unsigned roundup(unsigned v) +{ + // __builtin_clz is undefined for zero input + if (v == 0) { + v = 1; + } + int lz = __builtin_clz((int) v); + unsigned rounded = ((unsigned) 0x80000000) >> lz; + // 0x800000001 and higher are actually rounded _down_ to prevent overflow + if (v > rounded && lz > 0) { + rounded <<= 1; + } + return rounded; +} -- cgit v1.1