From da9deca7bab75f39a236d04b9e43d9da833ce4a0 Mon Sep 17 00:00:00 2001 From: Marco Nelissen Date: Wed, 21 Mar 2012 13:36:07 -0700 Subject: [PATCH] Support gapless playback for mp3 and m4a Gapless playback for appropriately tagged mp3 and m4a files. Currently this is implemented in OMXCodec, which most players use, but should be easy to support in other players as well by using the SkipCutBuffer utility class. Change-Id: I748c669adc1cfbe5ee9a7dea2fad945d48882551 --- include/media/stagefright/OMXCodec.h | 3 + include/media/stagefright/SkipCutBuffer.h | 58 ++++++++ media/libstagefright/Android.mk | 1 + media/libstagefright/OMXCodec.cpp | 40 ++++++ media/libstagefright/SkipCutBuffer.cpp | 130 ++++++++++++++++++ .../libstagefright/codecs/mp3dec/SoftMP3.cpp | 20 ++- media/libstagefright/codecs/mp3dec/SoftMP3.h | 6 +- 7 files changed, 252 insertions(+), 6 deletions(-) create mode 100644 include/media/stagefright/SkipCutBuffer.h create mode 100755 media/libstagefright/SkipCutBuffer.cpp diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h index 392ea87bc033d..7c612ba7c842b 100644 --- a/include/media/stagefright/OMXCodec.h +++ b/include/media/stagefright/OMXCodec.h @@ -30,6 +30,7 @@ struct MediaCodecList; class MemoryDealer; struct OMXCodecObserver; struct CodecProfileLevel; +class SkipCutBuffer; struct OMXCodec : public MediaSource, public MediaBufferObserver { @@ -201,6 +202,7 @@ private: ReadOptions::SeekMode mSeekMode; int64_t mTargetTimeUs; bool mOutputPortSettingsChangedPending; + SkipCutBuffer *mSkipCutBuffer; MediaBuffer *mLeftOverBuffer; @@ -378,6 +380,7 @@ status_t QueryCodecs( const char *mimeType, bool queryDecoders, Vector *results); + } // namespace android #endif // OMX_CODEC_H_ diff --git a/include/media/stagefright/SkipCutBuffer.h b/include/media/stagefright/SkipCutBuffer.h new file mode 100644 index 0000000000000..5c7cd47bf0616 --- /dev/null +++ b/include/media/stagefright/SkipCutBuffer.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#ifndef SKIP_CUT_BUFFER_H_ + +#define SKIP_CUT_BUFFER_H_ + +#include + +namespace android { + +/** + * utility class to cut the start and end off a stream of data in MediaBuffers + * + */ +class SkipCutBuffer { + public: + // 'skip' is the number of bytes to skip from the beginning + // 'cut' is the number of bytes to cut from the end + // 'output_size' is the size in bytes of the MediaBuffers that will be used + SkipCutBuffer(int32_t skip, int32_t cut, int32_t output_size); + virtual ~SkipCutBuffer(); + + // Submit one MediaBuffer for skipping and cutting. This may consume all or + // some of the data in the buffer, or it may add data to it. + // After this, the caller should continue processing the buffer as usual. + void submit(MediaBuffer *buffer); + void clear(); + size_t size(); // how many bytes are currently stored in the buffer + + private: + void write(const char *src, size_t num); + size_t read(char *dst, size_t num); + int32_t mFrontPadding; + int32_t mBackPadding; + int32_t mWriteHead; + int32_t mReadHead; + int32_t mCapacity; + char* mCutBuffer; + DISALLOW_EVIL_CONSTRUCTORS(SkipCutBuffer); +}; + +} // namespace android + +#endif // OMX_CODEC_H_ diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index 77714f3e94b33..7d7bd7d6646ca 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -42,6 +42,7 @@ LOCAL_SRC_FILES:= \ OggExtractor.cpp \ SampleIterator.cpp \ SampleTable.cpp \ + SkipCutBuffer.cpp \ StagefrightMediaScanner.cpp \ StagefrightMetadataRetriever.cpp \ SurfaceMediaSource.cpp \ diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index d5e6bec78a2e8..8b6e9d58db8bb 100755 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -1303,6 +1304,7 @@ OMXCodec::OMXCodec( mSeekMode(ReadOptions::SEEK_CLOSEST_SYNC), mTargetTimeUs(-1), mOutputPortSettingsChangedPending(false), + mSkipCutBuffer(NULL), mLeftOverBuffer(NULL), mPaused(false), mNativeWindow( @@ -1413,6 +1415,9 @@ OMXCodec::~OMXCodec() { free(mMIME); mMIME = NULL; + + delete mSkipCutBuffer; + mSkipCutBuffer = NULL; } status_t OMXCodec::init() { @@ -1573,6 +1578,34 @@ status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) { portIndex == kPortIndexInput ? "input" : "output"); } + if (portIndex == kPortIndexOutput) { + + sp meta = mSource->getFormat(); + int32_t delay = 0; + if (!meta->findInt32(kKeyEncoderDelay, &delay)) { + delay = 0; + } + int32_t padding = 0; + if (!meta->findInt32(kKeyEncoderPadding, &padding)) { + padding = 0; + } + int32_t numchannels = 0; + if (delay + padding) { + if (meta->findInt32(kKeyChannelCount, &numchannels)) { + size_t frameSize = numchannels * sizeof(int16_t); + if (mSkipCutBuffer) { + size_t prevbuffersize = mSkipCutBuffer->size(); + if (prevbuffersize != 0) { + ALOGW("Replacing SkipCutBuffer holding %d bytes", prevbuffersize); + } + delete mSkipCutBuffer; + } + mSkipCutBuffer = new SkipCutBuffer(delay * frameSize, padding * frameSize, + def.nBufferSize); + } + } + } + // dumpPortStatus(portIndex); if (portIndex == kPortIndexInput && (mFlags & kUseSecureInputBuffers)) { @@ -2490,6 +2523,10 @@ void OMXCodec::onCmdComplete(OMX_COMMANDTYPE cmd, OMX_U32 data) { CHECK_EQ(countBuffersWeOwn(mPortBuffers[portIndex]), mPortBuffers[portIndex].size()); + if (mSkipCutBuffer && mPortStatus[kPortIndexOutput] == ENABLED) { + mSkipCutBuffer->clear(); + } + if (mState == RECONFIGURING) { CHECK_EQ(portIndex, (OMX_U32)kPortIndexOutput); @@ -3800,6 +3837,9 @@ status_t OMXCodec::read( info->mStatus = OWNED_BY_CLIENT; info->mMediaBuffer->add_ref(); + if (mSkipCutBuffer) { + mSkipCutBuffer->submit(info->mMediaBuffer); + } *buffer = info->mMediaBuffer; return OK; diff --git a/media/libstagefright/SkipCutBuffer.cpp b/media/libstagefright/SkipCutBuffer.cpp new file mode 100755 index 0000000000000..6d331b0d0946d --- /dev/null +++ b/media/libstagefright/SkipCutBuffer.cpp @@ -0,0 +1,130 @@ +/* + * 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_NDEBUG 0 +#define LOG_TAG "SkipCutBuffer" +#include + +#include +#include +#include + +namespace android { + +SkipCutBuffer::SkipCutBuffer(int32_t skip, int32_t cut, int32_t output_size) { + mFrontPadding = skip; + mBackPadding = cut; + mWriteHead = 0; + mReadHead = 0; + mCapacity = cut + output_size; + mCutBuffer = new char[mCapacity]; + ALOGV("skipcutbuffer %d %d %d", skip, cut, mCapacity); +} + +SkipCutBuffer::~SkipCutBuffer() { + delete[] mCutBuffer; +} + +void SkipCutBuffer::submit(MediaBuffer *buffer) { + int32_t offset = buffer->range_offset(); + int32_t buflen = buffer->range_length(); + + // drop the initial data from the buffer if needed + if (mFrontPadding > 0) { + // still data left to drop + int32_t to_drop = (buflen < mFrontPadding) ? buflen : mFrontPadding; + offset += to_drop; + buflen -= to_drop; + buffer->set_range(offset, buflen); + mFrontPadding -= to_drop; + } + + + // append data to cutbuffer + char *src = ((char*) buffer->data()) + offset; + write(src, buflen); + + + // the mediabuffer is now empty. Fill it from cutbuffer, always leaving + // at least mBackPadding bytes in the cutbuffer + char *dst = (char*) buffer->data(); + size_t copied = read(dst, buffer->size()); + buffer->set_range(0, copied); +} + +void SkipCutBuffer::clear() { + mWriteHead = mReadHead = 0; +} + +void SkipCutBuffer::write(const char *src, size_t num) { + int32_t sizeused = (mWriteHead - mReadHead); + if (sizeused < 0) sizeused += mCapacity; + + // everything must fit + CHECK_GE((mCapacity - size_t(sizeused)), num); + + size_t copyfirst = (mCapacity - mWriteHead); + if (copyfirst > num) copyfirst = num; + if (copyfirst) { + memcpy(mCutBuffer + mWriteHead, src, copyfirst); + num -= copyfirst; + src += copyfirst; + mWriteHead += copyfirst; + CHECK_LE(mWriteHead, mCapacity); + if (mWriteHead == mCapacity) mWriteHead = 0; + if (num) { + memcpy(mCutBuffer, src, num); + mWriteHead += num; + } + } +} + +size_t SkipCutBuffer::read(char *dst, size_t num) { + int32_t available = (mWriteHead - mReadHead); + if (available < 0) available += mCapacity; + + available -= mBackPadding; + if (available <=0) { + return 0; + } + if (available < num) { + num = available; + } + + size_t copyfirst = (mCapacity - mReadHead); + if (copyfirst > num) copyfirst = num; + if (copyfirst) { + memcpy(dst, mCutBuffer + mReadHead, copyfirst); + num -= copyfirst; + dst += copyfirst; + mReadHead += copyfirst; + CHECK_LE(mReadHead, mCapacity); + if (mReadHead == mCapacity) mReadHead = 0; + if (num) { + memcpy(dst, mCutBuffer, num); + mReadHead += num; + } + } + return available; +} + +size_t SkipCutBuffer::size() { + int32_t available = (mWriteHead - mReadHead); + if (available < 0) available += mCapacity; + return available; +} + +} // namespace android diff --git a/media/libstagefright/codecs/mp3dec/SoftMP3.cpp b/media/libstagefright/codecs/mp3dec/SoftMP3.cpp index ad5529538b094..92009eeb76ff3 100644 --- a/media/libstagefright/codecs/mp3dec/SoftMP3.cpp +++ b/media/libstagefright/codecs/mp3dec/SoftMP3.cpp @@ -115,6 +115,7 @@ void SoftMP3::initDecoder() { mDecoderBuf = malloc(memRequirements); pvmp3_InitDecoder(mConfig, mDecoderBuf); + mIsFirst = true; } OMX_ERRORTYPE SoftMP3::internalGetParameter( @@ -190,7 +191,10 @@ void SoftMP3::onQueueFilled(OMX_U32 portIndex) { inInfo->mOwnedByUs = false; notifyEmptyBufferDone(inHeader); - outHeader->nFilledLen = 0; + // pad the end of the stream with 529 samples, since that many samples + // were trimmed off the beginning when decoding started + outHeader->nFilledLen = kPVMP3DecoderDelay * mNumChannels * sizeof(int16_t); + memset(outHeader->pBuffer, 0, outHeader->nFilledLen); outHeader->nFlags = OMX_BUFFERFLAG_EOS; outQueue.erase(outQueue.begin()); @@ -251,8 +255,17 @@ void SoftMP3::onQueueFilled(OMX_U32 portIndex) { return; } - outHeader->nOffset = 0; - outHeader->nFilledLen = mConfig->outputFrameSize * sizeof(int16_t); + if (mIsFirst) { + mIsFirst = false; + // The decoder delay is 529 samples, so trim that many samples off + // the start of the first output buffer. This essentially makes this + // decoder have zero delay, which the rest of the pipeline assumes. + outHeader->nOffset = kPVMP3DecoderDelay * mNumChannels * sizeof(int16_t); + outHeader->nFilledLen = mConfig->outputFrameSize * sizeof(int16_t) - outHeader->nOffset; + } else { + outHeader->nOffset = 0; + outHeader->nFilledLen = mConfig->outputFrameSize * sizeof(int16_t); + } outHeader->nTimeStamp = mAnchorTimeUs @@ -288,6 +301,7 @@ void SoftMP3::onPortFlushCompleted(OMX_U32 portIndex) { // Make sure that the next buffer output does not still // depend on fragments from the last one decoded. pvmp3_InitDecoder(mConfig, mDecoderBuf); + mIsFirst = true; } } diff --git a/media/libstagefright/codecs/mp3dec/SoftMP3.h b/media/libstagefright/codecs/mp3dec/SoftMP3.h index 70d0682a100bc..3a05466475f4d 100644 --- a/media/libstagefright/codecs/mp3dec/SoftMP3.h +++ b/media/libstagefright/codecs/mp3dec/SoftMP3.h @@ -46,7 +46,8 @@ protected: private: enum { kNumBuffers = 4, - kOutputBufferSize = 4608 * 2 + kOutputBufferSize = 4608 * 2, + kPVMP3DecoderDelay = 529 // frames }; tPVMP3DecoderExternal *mConfig; @@ -57,8 +58,7 @@ private: int32_t mNumChannels; int32_t mSamplingRate; - bool mConfigured; - + bool mIsFirst; bool mSignalledError; enum {