Merge "Support gapless playback for mp3 and m4a"
This commit is contained in:
committed by
Android (Google) Code Review
commit
66331b9c99
@@ -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<CodecCapabilities> *results);
|
||||
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // OMX_CODEC_H_
|
||||
|
||||
58
include/media/stagefright/SkipCutBuffer.h
Normal file
58
include/media/stagefright/SkipCutBuffer.h
Normal file
@@ -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 <media/stagefright/MediaBuffer.h>
|
||||
|
||||
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_
|
||||
@@ -42,6 +42,7 @@ LOCAL_SRC_FILES:= \
|
||||
OggExtractor.cpp \
|
||||
SampleIterator.cpp \
|
||||
SampleTable.cpp \
|
||||
SkipCutBuffer.cpp \
|
||||
StagefrightMediaScanner.cpp \
|
||||
StagefrightMetadataRetriever.cpp \
|
||||
SurfaceMediaSource.cpp \
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <media/stagefright/MetaData.h>
|
||||
#include <media/stagefright/OMXCodec.h>
|
||||
#include <media/stagefright/Utils.h>
|
||||
#include <media/stagefright/SkipCutBuffer.h>
|
||||
#include <utils/Vector.h>
|
||||
|
||||
#include <OMX_Audio.h>
|
||||
@@ -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<MetaData> 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;
|
||||
|
||||
130
media/libstagefright/SkipCutBuffer.cpp
Executable file
130
media/libstagefright/SkipCutBuffer.cpp
Executable file
@@ -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 <utils/Log.h>
|
||||
|
||||
#include <media/stagefright/foundation/ADebug.h>
|
||||
#include <media/stagefright/MediaBuffer.h>
|
||||
#include <media/stagefright/SkipCutBuffer.h>
|
||||
|
||||
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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user