Merge "File writer has a designated writer thread now" into gingerbread

This commit is contained in:
James Dong
2010-08-03 10:35:55 -07:00
committed by Android (Google) Code Review
2 changed files with 256 additions and 49 deletions

View File

@ -75,6 +75,7 @@ private:
uint32_t mInterleaveDurationUs; uint32_t mInterleaveDurationUs;
int32_t mTimeScale; int32_t mTimeScale;
int64_t mStartTimestampUs; int64_t mStartTimestampUs;
Mutex mLock; Mutex mLock;
List<Track *> mTracks; List<Track *> mTracks;
@ -87,6 +88,46 @@ private:
size_t numTracks(); size_t numTracks();
int64_t estimateMoovBoxSize(int32_t bitRate); int64_t estimateMoovBoxSize(int32_t bitRate);
struct Chunk {
Track *mTrack; // Owner
int64_t mTimeStampUs; // Timestamp of the 1st sample
List<MediaBuffer *> mSamples; // Sample data
// Convenient constructor
Chunk(Track *track, int64_t timeUs, List<MediaBuffer *> samples)
: mTrack(track), mTimeStampUs(timeUs), mSamples(samples) {
}
};
struct ChunkInfo {
Track *mTrack; // Owner
List<Chunk> mChunks; // Remaining chunks to be written
};
bool mIsFirstChunk;
volatile bool mDone; // Writer thread is done?
pthread_t mThread; // Thread id for the writer
List<ChunkInfo> mChunkInfos; // Chunk infos
Condition mChunkReadyCondition; // Signal that chunks are available
// Writer thread handling
status_t startWriterThread();
void stopWriterThread();
static void *ThreadWrapper(void *me);
void threadFunc();
// Buffer a single chunk to be written out later.
void bufferChunk(const Chunk& chunk);
// Write all buffered chunks from all tracks
void writeChunks();
// Write a chunk if there is one
status_t writeOneChunk();
// Write the first chunk from the given ChunkInfo.
void writeFirstChunk(ChunkInfo* info);
void lock(); void lock();
void unlock(); void unlock();

View File

@ -52,6 +52,11 @@ public:
int64_t getDurationUs() const; int64_t getDurationUs() const;
int64_t getEstimatedTrackSizeBytes() const; int64_t getEstimatedTrackSizeBytes() const;
void writeTrackHeader(int32_t trackID, bool use32BitOffset = true); void writeTrackHeader(int32_t trackID, bool use32BitOffset = true);
void bufferChunk(int64_t timestampUs);
bool isAvc() const { return mIsAvc; }
bool isAudio() const { return mIsAudio; }
bool isMPEG4() const { return mIsMPEG4; }
void addChunkOffset(off_t offset) { mChunkOffsets.push_back(offset); }
private: private:
MPEG4Writer *mOwner; MPEG4Writer *mOwner;
@ -60,8 +65,12 @@ private:
volatile bool mDone; volatile bool mDone;
volatile bool mPaused; volatile bool mPaused;
volatile bool mResumed; volatile bool mResumed;
bool mIsAvc;
bool mIsAudio;
bool mIsMPEG4;
int64_t mMaxTimeStampUs; int64_t mMaxTimeStampUs;
int64_t mEstimatedTrackSizeBytes; int64_t mEstimatedTrackSizeBytes;
int64_t mMaxWriteTimeUs;
int32_t mTimeScale; int32_t mTimeScale;
pthread_t mThread; pthread_t mThread;
@ -117,7 +126,6 @@ private:
status_t makeAVCCodecSpecificData( status_t makeAVCCodecSpecificData(
const uint8_t *data, size_t size); const uint8_t *data, size_t size);
void writeOneChunk(bool isAvc);
// Track authoring progress status // Track authoring progress status
void trackProgressStatus(int64_t timeUs, status_t err = OK); void trackProgressStatus(int64_t timeUs, status_t err = OK);
@ -320,10 +328,17 @@ status_t MPEG4Writer::start(MetaData *param) {
} else { } else {
write("\x00\x00\x00\x01mdat????????", 16); write("\x00\x00\x00\x01mdat????????", 16);
} }
status_t err = startTracks(param);
status_t err = startWriterThread();
if (err != OK) { if (err != OK) {
return err; return err;
} }
err = startTracks(param);
if (err != OK) {
return err;
}
mStarted = true; mStarted = true;
return OK; return OK;
} }
@ -339,6 +354,20 @@ void MPEG4Writer::pause() {
} }
} }
void MPEG4Writer::stopWriterThread() {
LOGV("stopWriterThread");
{
Mutex::Autolock autolock(mLock);
mDone = true;
mChunkReadyCondition.signal();
}
void *dummy;
pthread_join(mThread, &dummy);
}
void MPEG4Writer::stop() { void MPEG4Writer::stop() {
if (mFile == NULL) { if (mFile == NULL) {
return; return;
@ -355,6 +384,7 @@ void MPEG4Writer::stop() {
} }
} }
stopWriterThread();
// Fix up the size of the 'mdat' chunk. // Fix up the size of the 'mdat' chunk.
if (mUse32BitOffset) { if (mUse32BitOffset) {
@ -693,6 +723,14 @@ MPEG4Writer::Track::Track(
if (!mMeta->findInt32(kKeyTimeScale, &mTimeScale)) { if (!mMeta->findInt32(kKeyTimeScale, &mTimeScale)) {
mTimeScale = 1000; mTimeScale = 1000;
} }
const char *mime;
mMeta->findCString(kKeyMIMEType, &mime);
mIsAvc = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC);
mIsAudio = !strncasecmp(mime, "audio/", 6);
mIsMPEG4 = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4) ||
!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC);
CHECK(mTimeScale > 0); CHECK(mTimeScale > 0);
} }
@ -751,6 +789,148 @@ void MPEG4Writer::Track::initTrackingProgressStatus(MetaData *params) {
} }
} }
// static
void *MPEG4Writer::ThreadWrapper(void *me) {
LOGV("ThreadWrapper: %p", me);
MPEG4Writer *writer = static_cast<MPEG4Writer *>(me);
writer->threadFunc();
return NULL;
}
void MPEG4Writer::bufferChunk(const Chunk& chunk) {
LOGV("bufferChunk: %p", chunk.mTrack);
Mutex::Autolock autolock(mLock);
CHECK_EQ(mDone, false);
for (List<ChunkInfo>::iterator it = mChunkInfos.begin();
it != mChunkInfos.end(); ++it) {
if (chunk.mTrack == it->mTrack) { // Found owner
it->mChunks.push_back(chunk);
mChunkReadyCondition.signal();
return;
}
}
CHECK("Received a chunk for a unknown track" == 0);
}
void MPEG4Writer::writeFirstChunk(ChunkInfo* info) {
LOGV("writeFirstChunk: %p", info->mTrack);
List<Chunk>::iterator chunkIt = info->mChunks.begin();
for (List<MediaBuffer *>::iterator it = chunkIt->mSamples.begin();
it != chunkIt->mSamples.end(); ++it) {
off_t offset = info->mTrack->isAvc()
? addLengthPrefixedSample_l(*it)
: addSample_l(*it);
if (it == chunkIt->mSamples.begin()) {
info->mTrack->addChunkOffset(offset);
}
}
// Done with the current chunk.
// Release all the samples in this chunk.
while (!chunkIt->mSamples.empty()) {
List<MediaBuffer *>::iterator it = chunkIt->mSamples.begin();
(*it)->release();
(*it) = NULL;
chunkIt->mSamples.erase(it);
}
chunkIt->mSamples.clear();
info->mChunks.erase(chunkIt);
}
void MPEG4Writer::writeChunks() {
LOGV("writeChunks");
size_t outstandingChunks = 0;
while (!mChunkInfos.empty()) {
List<ChunkInfo>::iterator it = mChunkInfos.begin();
while (!it->mChunks.empty()) {
CHECK_EQ(OK, writeOneChunk());
++outstandingChunks;
}
it->mTrack = NULL;
mChunkInfos.erase(it);
}
mChunkInfos.clear();
LOGD("%d chunks are written in the last batch", outstandingChunks);
}
status_t MPEG4Writer::writeOneChunk() {
LOGV("writeOneChunk");
// Find the smallest timestamp, and write that chunk out
// XXX: What if some track is just too slow?
int64_t minTimestampUs = 0x7FFFFFFFFFFFFFFFLL;
Track *track = NULL;
for (List<ChunkInfo>::iterator it = mChunkInfos.begin();
it != mChunkInfos.end(); ++it) {
if (!it->mChunks.empty()) {
List<Chunk>::iterator chunkIt = it->mChunks.begin();
if (chunkIt->mTimeStampUs < minTimestampUs) {
minTimestampUs = chunkIt->mTimeStampUs;
track = it->mTrack;
}
}
}
if (track == NULL) {
LOGV("Nothing to be written after all");
return OK;
}
if (mIsFirstChunk) {
mIsFirstChunk = false;
}
for (List<ChunkInfo>::iterator it = mChunkInfos.begin();
it != mChunkInfos.end(); ++it) {
if (it->mTrack == track) {
writeFirstChunk(&(*it));
}
}
return OK;
}
void MPEG4Writer::threadFunc() {
LOGV("threadFunc");
while (!mDone) {
{
Mutex::Autolock autolock(mLock);
mChunkReadyCondition.wait(mLock);
CHECK_EQ(writeOneChunk(), OK);
}
}
{
// Write ALL samples
Mutex::Autolock autolock(mLock);
writeChunks();
}
}
status_t MPEG4Writer::startWriterThread() {
LOGV("startWriterThread");
mDone = false;
mIsFirstChunk = true;
for (List<Track *>::iterator it = mTracks.begin();
it != mTracks.end(); ++it) {
ChunkInfo info;
info.mTrack = *it;
mChunkInfos.push_back(info);
}
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&mThread, &attr, ThreadWrapper, this);
pthread_attr_destroy(&attr);
return OK;
}
status_t MPEG4Writer::Track::start(MetaData *params) { status_t MPEG4Writer::Track::start(MetaData *params) {
if (!mDone && mPaused) { if (!mDone && mPaused) {
mPaused = false; mPaused = false;
@ -926,13 +1106,6 @@ static bool collectStatisticalData() {
} }
void MPEG4Writer::Track::threadEntry() { void MPEG4Writer::Track::threadEntry() {
sp<MetaData> meta = mSource->getFormat();
const char *mime;
meta->findCString(kKeyMIMEType, &mime);
bool is_mpeg4 = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4) ||
!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC);
bool is_avc = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC);
bool is_audio = !strncasecmp(mime, "audio/", 6);
int32_t count = 0; int32_t count = 0;
const int64_t interleaveDurationUs = mOwner->interleaveDuration(); const int64_t interleaveDurationUs = mOwner->interleaveDuration();
int64_t chunkTimestampUs = 0; int64_t chunkTimestampUs = 0;
@ -943,10 +1116,12 @@ void MPEG4Writer::Track::threadEntry() {
int32_t sampleCount = 1; // Sample count in the current stts table entry int32_t sampleCount = 1; // Sample count in the current stts table entry
uint32_t previousSampleSize = 0; // Size of the previous sample uint32_t previousSampleSize = 0; // Size of the previous sample
int64_t previousPausedDurationUs = 0; int64_t previousPausedDurationUs = 0;
int64_t timestampUs;
sp<MetaData> meta_data; sp<MetaData> meta_data;
bool collectStats = collectStatisticalData(); bool collectStats = collectStatisticalData();
mNumSamples = 0; mNumSamples = 0;
mMaxWriteTimeUs = 0;
status_t err = OK; status_t err = OK;
MediaBuffer *buffer; MediaBuffer *buffer;
while (!mDone && (err = mSource->read(&buffer)) == OK) { while (!mDone && (err = mSource->read(&buffer)) == OK) {
@ -973,13 +1148,13 @@ void MPEG4Writer::Track::threadEntry() {
&& isCodecConfig) { && isCodecConfig) {
CHECK(!mGotAllCodecSpecificData); CHECK(!mGotAllCodecSpecificData);
if (is_avc) { if (mIsAvc) {
status_t err = makeAVCCodecSpecificData( status_t err = makeAVCCodecSpecificData(
(const uint8_t *)buffer->data() (const uint8_t *)buffer->data()
+ buffer->range_offset(), + buffer->range_offset(),
buffer->range_length()); buffer->range_length());
CHECK_EQ(OK, err); CHECK_EQ(OK, err);
} else if (is_mpeg4) { } else if (mIsMPEG4) {
mCodecSpecificDataSize = buffer->range_length(); mCodecSpecificDataSize = buffer->range_length();
mCodecSpecificData = malloc(mCodecSpecificDataSize); mCodecSpecificData = malloc(mCodecSpecificDataSize);
memcpy(mCodecSpecificData, memcpy(mCodecSpecificData,
@ -994,7 +1169,7 @@ void MPEG4Writer::Track::threadEntry() {
mGotAllCodecSpecificData = true; mGotAllCodecSpecificData = true;
continue; continue;
} else if (!mGotAllCodecSpecificData && } else if (!mGotAllCodecSpecificData &&
count == 1 && is_mpeg4 && mCodecSpecificData == NULL) { count == 1 && mIsMPEG4 && mCodecSpecificData == NULL) {
// The TI mpeg4 encoder does not properly set the // The TI mpeg4 encoder does not properly set the
// codec-specific-data flag. // codec-specific-data flag.
@ -1034,7 +1209,7 @@ void MPEG4Writer::Track::threadEntry() {
} }
mGotAllCodecSpecificData = true; mGotAllCodecSpecificData = true;
} else if (!mGotAllCodecSpecificData && is_avc && count < 3) { } else if (!mGotAllCodecSpecificData && mIsAvc && count < 3) {
// The TI video encoder does not flag codec specific data // The TI video encoder does not flag codec specific data
// as such and also splits up SPS and PPS across two buffers. // as such and also splits up SPS and PPS across two buffers.
@ -1090,10 +1265,10 @@ void MPEG4Writer::Track::threadEntry() {
buffer->release(); buffer->release();
buffer = NULL; buffer = NULL;
if (is_avc) StripStartcode(copy); if (mIsAvc) StripStartcode(copy);
size_t sampleSize; size_t sampleSize;
sampleSize = is_avc sampleSize = mIsAvc
#if USE_NALLEN_FOUR #if USE_NALLEN_FOUR
? copy->range_length() + 4 ? copy->range_length() + 4
#else #else
@ -1116,7 +1291,6 @@ void MPEG4Writer::Track::threadEntry() {
int32_t isSync = false; int32_t isSync = false;
meta_data->findInt32(kKeyIsSyncFrame, &isSync); meta_data->findInt32(kKeyIsSyncFrame, &isSync);
int64_t timestampUs;
CHECK(meta_data->findInt64(kKeyTime, &timestampUs)); CHECK(meta_data->findInt64(kKeyTime, &timestampUs));
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -1168,7 +1342,7 @@ void MPEG4Writer::Track::threadEntry() {
trackProgressStatus(timestampUs); trackProgressStatus(timestampUs);
} }
if (mOwner->numTracks() == 1) { if (mOwner->numTracks() == 1) {
off_t offset = is_avc? mOwner->addLengthPrefixedSample_l(copy) off_t offset = mIsAvc? mOwner->addLengthPrefixedSample_l(copy)
: mOwner->addSample_l(copy); : mOwner->addSample_l(copy);
if (mChunkOffsets.empty()) { if (mChunkOffsets.empty()) {
mChunkOffsets.push_back(offset); mChunkOffsets.push_back(offset);
@ -1182,7 +1356,7 @@ void MPEG4Writer::Track::threadEntry() {
if (interleaveDurationUs == 0) { if (interleaveDurationUs == 0) {
StscTableEntry stscEntry(++nChunks, 1, 1); StscTableEntry stscEntry(++nChunks, 1, 1);
mStscTableEntries.push_back(stscEntry); mStscTableEntries.push_back(stscEntry);
writeOneChunk(is_avc); bufferChunk(timestampUs);
} else { } else {
if (chunkTimestampUs == 0) { if (chunkTimestampUs == 0) {
chunkTimestampUs = timestampUs; chunkTimestampUs = timestampUs;
@ -1199,7 +1373,7 @@ void MPEG4Writer::Track::threadEntry() {
mChunkSamples.size(), 1); mChunkSamples.size(), 1);
mStscTableEntries.push_back(stscEntry); mStscTableEntries.push_back(stscEntry);
} }
writeOneChunk(is_avc); bufferChunk(timestampUs);
chunkTimestampUs = timestampUs; chunkTimestampUs = timestampUs;
} }
} }
@ -1220,7 +1394,7 @@ void MPEG4Writer::Track::threadEntry() {
++nChunks; ++nChunks;
StscTableEntry stscEntry(nChunks, mChunkSamples.size(), 1); StscTableEntry stscEntry(nChunks, mChunkSamples.size(), 1);
mStscTableEntries.push_back(stscEntry); mStscTableEntries.push_back(stscEntry);
writeOneChunk(is_avc); bufferChunk(timestampUs);
} }
// We don't really know how long the last frame lasts, since // We don't really know how long the last frame lasts, since
@ -1234,10 +1408,10 @@ void MPEG4Writer::Track::threadEntry() {
SttsTableEntry sttsEntry(sampleCount, lastDurationUs); SttsTableEntry sttsEntry(sampleCount, lastDurationUs);
mSttsTableEntries.push_back(sttsEntry); mSttsTableEntries.push_back(sttsEntry);
mReachedEOS = true; mReachedEOS = true;
LOGI("Received total/0-length (%d/%d) buffers and encoded %d frames - %s", LOGI("Received total/0-length (%d/%d) buffers and encoded %d frames. Max write time: %lld us - %s",
count, nZeroLengthFrames, mNumSamples, is_audio? "audio": "video"); count, nZeroLengthFrames, mNumSamples, mMaxWriteTimeUs, mIsAudio? "audio": "video");
logStatisticalData(is_audio); logStatisticalData(mIsAudio);
} }
void MPEG4Writer::Track::trackProgressStatus(int64_t timeUs, status_t err) { void MPEG4Writer::Track::trackProgressStatus(int64_t timeUs, status_t err) {
@ -1380,24 +1554,17 @@ void MPEG4Writer::Track::logStatisticalData(bool isAudio) {
} }
} }
void MPEG4Writer::Track::writeOneChunk(bool isAvc) { void MPEG4Writer::Track::bufferChunk(int64_t timestampUs) {
mOwner->lock(); LOGV("bufferChunk");
for (List<MediaBuffer *>::iterator it = mChunkSamples.begin();
it != mChunkSamples.end(); ++it) { int64_t startTimeUs = systemTime() / 1000;
off_t offset = isAvc? mOwner->addLengthPrefixedSample_l(*it) Chunk chunk(this, timestampUs, mChunkSamples);
: mOwner->addSample_l(*it); mOwner->bufferChunk(chunk);
if (it == mChunkSamples.begin()) {
mChunkOffsets.push_back(offset);
}
}
mOwner->unlock();
while (!mChunkSamples.empty()) {
List<MediaBuffer *>::iterator it = mChunkSamples.begin();
(*it)->release();
(*it) = NULL;
mChunkSamples.erase(it);
}
mChunkSamples.clear(); mChunkSamples.clear();
int64_t endTimeUs = systemTime() / 1000;
if (mMaxWriteTimeUs < endTimeUs - startTimeUs) {
mMaxWriteTimeUs = endTimeUs - startTimeUs;
}
} }
int64_t MPEG4Writer::Track::getDurationUs() const { int64_t MPEG4Writer::Track::getDurationUs() const {
@ -1414,9 +1581,8 @@ void MPEG4Writer::Track::writeTrackHeader(
bool success = mMeta->findCString(kKeyMIMEType, &mime); bool success = mMeta->findCString(kKeyMIMEType, &mime);
CHECK(success); CHECK(success);
bool is_audio = !strncasecmp(mime, "audio/", 6);
LOGV("%s track time scale: %d", LOGV("%s track time scale: %d",
is_audio? "Audio": "Video", mTimeScale); mIsAudio? "Audio": "Video", mTimeScale);
time_t now = time(NULL); time_t now = time(NULL);
@ -1440,7 +1606,7 @@ void MPEG4Writer::Track::writeTrackHeader(
mOwner->writeInt32(0); // reserved mOwner->writeInt32(0); // reserved
mOwner->writeInt16(0); // layer mOwner->writeInt16(0); // layer
mOwner->writeInt16(0); // alternate group mOwner->writeInt16(0); // alternate group
mOwner->writeInt16(is_audio ? 0x100 : 0); // volume mOwner->writeInt16(mIsAudio ? 0x100 : 0); // volume
mOwner->writeInt16(0); // reserved mOwner->writeInt16(0); // reserved
mOwner->writeInt32(0x10000); // matrix mOwner->writeInt32(0x10000); // matrix
@ -1453,7 +1619,7 @@ void MPEG4Writer::Track::writeTrackHeader(
mOwner->writeInt32(0); mOwner->writeInt32(0);
mOwner->writeInt32(0x40000000); mOwner->writeInt32(0x40000000);
if (is_audio) { if (mIsAudio) {
mOwner->writeInt32(0); mOwner->writeInt32(0);
mOwner->writeInt32(0); mOwner->writeInt32(0);
} else { } else {
@ -1511,16 +1677,16 @@ void MPEG4Writer::Track::writeTrackHeader(
mOwner->beginBox("hdlr"); mOwner->beginBox("hdlr");
mOwner->writeInt32(0); // version=0, flags=0 mOwner->writeInt32(0); // version=0, flags=0
mOwner->writeInt32(0); // component type: should be mhlr mOwner->writeInt32(0); // component type: should be mhlr
mOwner->writeFourcc(is_audio ? "soun" : "vide"); // component subtype mOwner->writeFourcc(mIsAudio ? "soun" : "vide"); // component subtype
mOwner->writeInt32(0); // reserved mOwner->writeInt32(0); // reserved
mOwner->writeInt32(0); // reserved mOwner->writeInt32(0); // reserved
mOwner->writeInt32(0); // reserved mOwner->writeInt32(0); // reserved
// Removing "r" for the name string just makes the string 4 byte aligned // Removing "r" for the name string just makes the string 4 byte aligned
mOwner->writeCString(is_audio ? "SoundHandle": "VideoHandle"); // name mOwner->writeCString(mIsAudio ? "SoundHandle": "VideoHandle"); // name
mOwner->endBox(); mOwner->endBox();
mOwner->beginBox("minf"); mOwner->beginBox("minf");
if (is_audio) { if (mIsAudio) {
mOwner->beginBox("smhd"); mOwner->beginBox("smhd");
mOwner->writeInt32(0); // version=0, flags=0 mOwner->writeInt32(0); // version=0, flags=0
mOwner->writeInt16(0); // balance mOwner->writeInt16(0); // balance
@ -1553,7 +1719,7 @@ void MPEG4Writer::Track::writeTrackHeader(
mOwner->beginBox("stsd"); mOwner->beginBox("stsd");
mOwner->writeInt32(0); // version=0, flags=0 mOwner->writeInt32(0); // version=0, flags=0
mOwner->writeInt32(1); // entry count mOwner->writeInt32(1); // entry count
if (is_audio) { if (mIsAudio) {
const char *fourcc = NULL; const char *fourcc = NULL;
if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AMR_NB, mime)) { if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AMR_NB, mime)) {
fourcc = "samr"; fourcc = "samr";
@ -1735,7 +1901,7 @@ void MPEG4Writer::Track::writeTrackHeader(
} }
mOwner->endBox(); // stts mOwner->endBox(); // stts
if (!is_audio) { if (!mIsAudio) {
mOwner->beginBox("stss"); mOwner->beginBox("stss");
mOwner->writeInt32(0); // version=0, flags=0 mOwner->writeInt32(0); // version=0, flags=0
mOwner->writeInt32(mStssTableEntries.size()); // number of sync frames mOwner->writeInt32(mStssTableEntries.size()); // number of sync frames