Skip to content

Commit da9deca

Browse files
committed
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
1 parent 222dfc7 commit da9deca

File tree

7 files changed

+252
-6
lines changed

7 files changed

+252
-6
lines changed

include/media/stagefright/OMXCodec.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ struct MediaCodecList;
3030
class MemoryDealer;
3131
struct OMXCodecObserver;
3232
struct CodecProfileLevel;
33+
class SkipCutBuffer;
3334

3435
struct OMXCodec : public MediaSource,
3536
public MediaBufferObserver {
@@ -201,6 +202,7 @@ struct OMXCodec : public MediaSource,
201202
ReadOptions::SeekMode mSeekMode;
202203
int64_t mTargetTimeUs;
203204
bool mOutputPortSettingsChangedPending;
205+
SkipCutBuffer *mSkipCutBuffer;
204206

205207
MediaBuffer *mLeftOverBuffer;
206208

@@ -378,6 +380,7 @@ status_t QueryCodecs(
378380
const char *mimeType, bool queryDecoders,
379381
Vector<CodecCapabilities> *results);
380382

383+
381384
} // namespace android
382385

383386
#endif // OMX_CODEC_H_
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (C) 2012 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef SKIP_CUT_BUFFER_H_
18+
19+
#define SKIP_CUT_BUFFER_H_
20+
21+
#include <media/stagefright/MediaBuffer.h>
22+
23+
namespace android {
24+
25+
/**
26+
* utility class to cut the start and end off a stream of data in MediaBuffers
27+
*
28+
*/
29+
class SkipCutBuffer {
30+
public:
31+
// 'skip' is the number of bytes to skip from the beginning
32+
// 'cut' is the number of bytes to cut from the end
33+
// 'output_size' is the size in bytes of the MediaBuffers that will be used
34+
SkipCutBuffer(int32_t skip, int32_t cut, int32_t output_size);
35+
virtual ~SkipCutBuffer();
36+
37+
// Submit one MediaBuffer for skipping and cutting. This may consume all or
38+
// some of the data in the buffer, or it may add data to it.
39+
// After this, the caller should continue processing the buffer as usual.
40+
void submit(MediaBuffer *buffer);
41+
void clear();
42+
size_t size(); // how many bytes are currently stored in the buffer
43+
44+
private:
45+
void write(const char *src, size_t num);
46+
size_t read(char *dst, size_t num);
47+
int32_t mFrontPadding;
48+
int32_t mBackPadding;
49+
int32_t mWriteHead;
50+
int32_t mReadHead;
51+
int32_t mCapacity;
52+
char* mCutBuffer;
53+
DISALLOW_EVIL_CONSTRUCTORS(SkipCutBuffer);
54+
};
55+
56+
} // namespace android
57+
58+
#endif // OMX_CODEC_H_

media/libstagefright/Android.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ LOCAL_SRC_FILES:= \
4242
OggExtractor.cpp \
4343
SampleIterator.cpp \
4444
SampleTable.cpp \
45+
SkipCutBuffer.cpp \
4546
StagefrightMediaScanner.cpp \
4647
StagefrightMetadataRetriever.cpp \
4748
SurfaceMediaSource.cpp \

media/libstagefright/OMXCodec.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include <media/stagefright/MetaData.h>
3939
#include <media/stagefright/OMXCodec.h>
4040
#include <media/stagefright/Utils.h>
41+
#include <media/stagefright/SkipCutBuffer.h>
4142
#include <utils/Vector.h>
4243

4344
#include <OMX_Audio.h>
@@ -1303,6 +1304,7 @@ OMXCodec::OMXCodec(
13031304
mSeekMode(ReadOptions::SEEK_CLOSEST_SYNC),
13041305
mTargetTimeUs(-1),
13051306
mOutputPortSettingsChangedPending(false),
1307+
mSkipCutBuffer(NULL),
13061308
mLeftOverBuffer(NULL),
13071309
mPaused(false),
13081310
mNativeWindow(
@@ -1413,6 +1415,9 @@ OMXCodec::~OMXCodec() {
14131415

14141416
free(mMIME);
14151417
mMIME = NULL;
1418+
1419+
delete mSkipCutBuffer;
1420+
mSkipCutBuffer = NULL;
14161421
}
14171422

14181423
status_t OMXCodec::init() {
@@ -1573,6 +1578,34 @@ status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) {
15731578
portIndex == kPortIndexInput ? "input" : "output");
15741579
}
15751580

1581+
if (portIndex == kPortIndexOutput) {
1582+
1583+
sp<MetaData> meta = mSource->getFormat();
1584+
int32_t delay = 0;
1585+
if (!meta->findInt32(kKeyEncoderDelay, &delay)) {
1586+
delay = 0;
1587+
}
1588+
int32_t padding = 0;
1589+
if (!meta->findInt32(kKeyEncoderPadding, &padding)) {
1590+
padding = 0;
1591+
}
1592+
int32_t numchannels = 0;
1593+
if (delay + padding) {
1594+
if (meta->findInt32(kKeyChannelCount, &numchannels)) {
1595+
size_t frameSize = numchannels * sizeof(int16_t);
1596+
if (mSkipCutBuffer) {
1597+
size_t prevbuffersize = mSkipCutBuffer->size();
1598+
if (prevbuffersize != 0) {
1599+
ALOGW("Replacing SkipCutBuffer holding %d bytes", prevbuffersize);
1600+
}
1601+
delete mSkipCutBuffer;
1602+
}
1603+
mSkipCutBuffer = new SkipCutBuffer(delay * frameSize, padding * frameSize,
1604+
def.nBufferSize);
1605+
}
1606+
}
1607+
}
1608+
15761609
// dumpPortStatus(portIndex);
15771610

15781611
if (portIndex == kPortIndexInput && (mFlags & kUseSecureInputBuffers)) {
@@ -2490,6 +2523,10 @@ void OMXCodec::onCmdComplete(OMX_COMMANDTYPE cmd, OMX_U32 data) {
24902523
CHECK_EQ(countBuffersWeOwn(mPortBuffers[portIndex]),
24912524
mPortBuffers[portIndex].size());
24922525

2526+
if (mSkipCutBuffer && mPortStatus[kPortIndexOutput] == ENABLED) {
2527+
mSkipCutBuffer->clear();
2528+
}
2529+
24932530
if (mState == RECONFIGURING) {
24942531
CHECK_EQ(portIndex, (OMX_U32)kPortIndexOutput);
24952532

@@ -3800,6 +3837,9 @@ status_t OMXCodec::read(
38003837
info->mStatus = OWNED_BY_CLIENT;
38013838

38023839
info->mMediaBuffer->add_ref();
3840+
if (mSkipCutBuffer) {
3841+
mSkipCutBuffer->submit(info->mMediaBuffer);
3842+
}
38033843
*buffer = info->mMediaBuffer;
38043844

38053845
return OK;
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright (C) 2012 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
//#define LOG_NDEBUG 0
18+
#define LOG_TAG "SkipCutBuffer"
19+
#include <utils/Log.h>
20+
21+
#include <media/stagefright/foundation/ADebug.h>
22+
#include <media/stagefright/MediaBuffer.h>
23+
#include <media/stagefright/SkipCutBuffer.h>
24+
25+
namespace android {
26+
27+
SkipCutBuffer::SkipCutBuffer(int32_t skip, int32_t cut, int32_t output_size) {
28+
mFrontPadding = skip;
29+
mBackPadding = cut;
30+
mWriteHead = 0;
31+
mReadHead = 0;
32+
mCapacity = cut + output_size;
33+
mCutBuffer = new char[mCapacity];
34+
ALOGV("skipcutbuffer %d %d %d", skip, cut, mCapacity);
35+
}
36+
37+
SkipCutBuffer::~SkipCutBuffer() {
38+
delete[] mCutBuffer;
39+
}
40+
41+
void SkipCutBuffer::submit(MediaBuffer *buffer) {
42+
int32_t offset = buffer->range_offset();
43+
int32_t buflen = buffer->range_length();
44+
45+
// drop the initial data from the buffer if needed
46+
if (mFrontPadding > 0) {
47+
// still data left to drop
48+
int32_t to_drop = (buflen < mFrontPadding) ? buflen : mFrontPadding;
49+
offset += to_drop;
50+
buflen -= to_drop;
51+
buffer->set_range(offset, buflen);
52+
mFrontPadding -= to_drop;
53+
}
54+
55+
56+
// append data to cutbuffer
57+
char *src = ((char*) buffer->data()) + offset;
58+
write(src, buflen);
59+
60+
61+
// the mediabuffer is now empty. Fill it from cutbuffer, always leaving
62+
// at least mBackPadding bytes in the cutbuffer
63+
char *dst = (char*) buffer->data();
64+
size_t copied = read(dst, buffer->size());
65+
buffer->set_range(0, copied);
66+
}
67+
68+
void SkipCutBuffer::clear() {
69+
mWriteHead = mReadHead = 0;
70+
}
71+
72+
void SkipCutBuffer::write(const char *src, size_t num) {
73+
int32_t sizeused = (mWriteHead - mReadHead);
74+
if (sizeused < 0) sizeused += mCapacity;
75+
76+
// everything must fit
77+
CHECK_GE((mCapacity - size_t(sizeused)), num);
78+
79+
size_t copyfirst = (mCapacity - mWriteHead);
80+
if (copyfirst > num) copyfirst = num;
81+
if (copyfirst) {
82+
memcpy(mCutBuffer + mWriteHead, src, copyfirst);
83+
num -= copyfirst;
84+
src += copyfirst;
85+
mWriteHead += copyfirst;
86+
CHECK_LE(mWriteHead, mCapacity);
87+
if (mWriteHead == mCapacity) mWriteHead = 0;
88+
if (num) {
89+
memcpy(mCutBuffer, src, num);
90+
mWriteHead += num;
91+
}
92+
}
93+
}
94+
95+
size_t SkipCutBuffer::read(char *dst, size_t num) {
96+
int32_t available = (mWriteHead - mReadHead);
97+
if (available < 0) available += mCapacity;
98+
99+
available -= mBackPadding;
100+
if (available <=0) {
101+
return 0;
102+
}
103+
if (available < num) {
104+
num = available;
105+
}
106+
107+
size_t copyfirst = (mCapacity - mReadHead);
108+
if (copyfirst > num) copyfirst = num;
109+
if (copyfirst) {
110+
memcpy(dst, mCutBuffer + mReadHead, copyfirst);
111+
num -= copyfirst;
112+
dst += copyfirst;
113+
mReadHead += copyfirst;
114+
CHECK_LE(mReadHead, mCapacity);
115+
if (mReadHead == mCapacity) mReadHead = 0;
116+
if (num) {
117+
memcpy(dst, mCutBuffer, num);
118+
mReadHead += num;
119+
}
120+
}
121+
return available;
122+
}
123+
124+
size_t SkipCutBuffer::size() {
125+
int32_t available = (mWriteHead - mReadHead);
126+
if (available < 0) available += mCapacity;
127+
return available;
128+
}
129+
130+
} // namespace android

media/libstagefright/codecs/mp3dec/SoftMP3.cpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ void SoftMP3::initDecoder() {
115115
mDecoderBuf = malloc(memRequirements);
116116

117117
pvmp3_InitDecoder(mConfig, mDecoderBuf);
118+
mIsFirst = true;
118119
}
119120

120121
OMX_ERRORTYPE SoftMP3::internalGetParameter(
@@ -190,7 +191,10 @@ void SoftMP3::onQueueFilled(OMX_U32 portIndex) {
190191
inInfo->mOwnedByUs = false;
191192
notifyEmptyBufferDone(inHeader);
192193

193-
outHeader->nFilledLen = 0;
194+
// pad the end of the stream with 529 samples, since that many samples
195+
// were trimmed off the beginning when decoding started
196+
outHeader->nFilledLen = kPVMP3DecoderDelay * mNumChannels * sizeof(int16_t);
197+
memset(outHeader->pBuffer, 0, outHeader->nFilledLen);
194198
outHeader->nFlags = OMX_BUFFERFLAG_EOS;
195199

196200
outQueue.erase(outQueue.begin());
@@ -251,8 +255,17 @@ void SoftMP3::onQueueFilled(OMX_U32 portIndex) {
251255
return;
252256
}
253257

254-
outHeader->nOffset = 0;
255-
outHeader->nFilledLen = mConfig->outputFrameSize * sizeof(int16_t);
258+
if (mIsFirst) {
259+
mIsFirst = false;
260+
// The decoder delay is 529 samples, so trim that many samples off
261+
// the start of the first output buffer. This essentially makes this
262+
// decoder have zero delay, which the rest of the pipeline assumes.
263+
outHeader->nOffset = kPVMP3DecoderDelay * mNumChannels * sizeof(int16_t);
264+
outHeader->nFilledLen = mConfig->outputFrameSize * sizeof(int16_t) - outHeader->nOffset;
265+
} else {
266+
outHeader->nOffset = 0;
267+
outHeader->nFilledLen = mConfig->outputFrameSize * sizeof(int16_t);
268+
}
256269

257270
outHeader->nTimeStamp =
258271
mAnchorTimeUs
@@ -288,6 +301,7 @@ void SoftMP3::onPortFlushCompleted(OMX_U32 portIndex) {
288301
// Make sure that the next buffer output does not still
289302
// depend on fragments from the last one decoded.
290303
pvmp3_InitDecoder(mConfig, mDecoderBuf);
304+
mIsFirst = true;
291305
}
292306
}
293307

media/libstagefright/codecs/mp3dec/SoftMP3.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ struct SoftMP3 : public SimpleSoftOMXComponent {
4646
private:
4747
enum {
4848
kNumBuffers = 4,
49-
kOutputBufferSize = 4608 * 2
49+
kOutputBufferSize = 4608 * 2,
50+
kPVMP3DecoderDelay = 529 // frames
5051
};
5152

5253
tPVMP3DecoderExternal *mConfig;
@@ -57,8 +58,7 @@ struct SoftMP3 : public SimpleSoftOMXComponent {
5758
int32_t mNumChannels;
5859
int32_t mSamplingRate;
5960

60-
bool mConfigured;
61-
61+
bool mIsFirst;
6262
bool mSignalledError;
6363

6464
enum {

0 commit comments

Comments
 (0)