/* * Copyright (C) 2014 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 "WebmFrameThread" #include "WebmConstants.h" #include "WebmFrameThread.h" #include #include #include #include using namespace webm; namespace android { void *WebmFrameThread::wrap(void *arg) { WebmFrameThread *worker = reinterpret_cast(arg); worker->run(); return NULL; } status_t WebmFrameThread::start() { pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_create(&mThread, &attr, WebmFrameThread::wrap, this); pthread_attr_destroy(&attr); return OK; } status_t WebmFrameThread::stop() { void *status; pthread_join(mThread, &status); return (status_t)(intptr_t)status; } //================================================================================================= WebmFrameSourceThread::WebmFrameSourceThread( int type, LinkedBlockingQueue >& sink) : mType(type), mSink(sink) { } //================================================================================================= WebmFrameSinkThread::WebmFrameSinkThread( const int& fd, const uint64_t& off, sp videoThread, sp audioThread, List >& cues) : mFd(fd), mSegmentDataStart(off), mVideoFrames(videoThread->mSink), mAudioFrames(audioThread->mSink), mCues(cues), mDone(true) { } WebmFrameSinkThread::WebmFrameSinkThread( const int& fd, const uint64_t& off, LinkedBlockingQueue >& videoSource, LinkedBlockingQueue >& audioSource, List >& cues) : mFd(fd), mSegmentDataStart(off), mVideoFrames(videoSource), mAudioFrames(audioSource), mCues(cues), mDone(true) { } // Initializes a webm cluster with its starting timecode. // // frames: // sequence of input audio/video frames received from the source. // // clusterTimecodeL: // the starting timecode of the cluster; this is the timecode of the first // frame since frames are ordered by timestamp. // // children: // list to hold child elements in a webm cluster (start timecode and // simple blocks). // // static void WebmFrameSinkThread::initCluster( List >& frames, uint64_t& clusterTimecodeL, List >& children) { CHECK(!frames.empty() && children.empty()); const sp f = *(frames.begin()); clusterTimecodeL = f->mAbsTimecode; WebmUnsigned *clusterTimecode = new WebmUnsigned(kMkvTimecode, clusterTimecodeL); children.clear(); children.push_back(clusterTimecode); } void WebmFrameSinkThread::writeCluster(List >& children) { // children must contain at least one simpleblock and its timecode CHECK_GE(children.size(), 2); uint64_t size; sp cluster = new WebmMaster(kMkvCluster, children); cluster->write(mFd, size); children.clear(); } // Write out (possibly multiple) webm cluster(s) from frames split on video key frames. // // last: // current flush is triggered by EOS instead of a second outstanding video key frame. void WebmFrameSinkThread::flushFrames(List >& frames, bool last) { if (frames.empty()) { return; } uint64_t clusterTimecodeL; List > children; initCluster(frames, clusterTimecodeL, children); uint64_t cueTime = clusterTimecodeL; off_t fpos = ::lseek(mFd, 0, SEEK_CUR); size_t n = frames.size(); if (!last) { // If we are not flushing the last sequence of outstanding frames, flushFrames // must have been called right after we have pushed a second outstanding video key // frame (the last frame), which belongs to the next cluster; also hold back on // flushing the second to last frame before we check its type. A audio frame // should precede the aforementioned video key frame in the next sequence, a video // frame should be the last frame in the current (to-be-flushed) sequence. CHECK_GE(n, 2); n -= 2; } for (size_t i = 0; i < n; i++) { const sp f = *(frames.begin()); if (f->mType == kVideoType && f->mKey) { cueTime = f->mAbsTimecode; } if (f->mAbsTimecode - clusterTimecodeL > INT16_MAX) { writeCluster(children); initCluster(frames, clusterTimecodeL, children); } frames.erase(frames.begin()); children.push_back(f->SimpleBlock(clusterTimecodeL)); } // equivalent to last==false if (!frames.empty()) { // decide whether to write out the second to last frame. const sp secondLastFrame = *(frames.begin()); if (secondLastFrame->mType == kVideoType) { frames.erase(frames.begin()); children.push_back(secondLastFrame->SimpleBlock(clusterTimecodeL)); } } writeCluster(children); sp cuePoint = WebmElement::CuePointEntry(cueTime, 1, fpos - mSegmentDataStart); mCues.push_back(cuePoint); } status_t WebmFrameSinkThread::start() { mDone = false; return WebmFrameThread::start(); } status_t WebmFrameSinkThread::stop() { mDone = true; mVideoFrames.push(WebmFrame::EOS); mAudioFrames.push(WebmFrame::EOS); return WebmFrameThread::stop(); } void WebmFrameSinkThread::run() { int numVideoKeyFrames = 0; List > outstandingFrames; while (!mDone) { ALOGV("wait v frame"); const sp videoFrame = mVideoFrames.peek(); ALOGV("v frame: %p", videoFrame.get()); ALOGV("wait a frame"); const sp audioFrame = mAudioFrames.peek(); ALOGV("a frame: %p", audioFrame.get()); if (videoFrame->mEos && audioFrame->mEos) { break; } if (*audioFrame < *videoFrame) { ALOGV("take a frame"); mAudioFrames.take(); outstandingFrames.push_back(audioFrame); } else { ALOGV("take v frame"); mVideoFrames.take(); outstandingFrames.push_back(videoFrame); if (videoFrame->mKey) numVideoKeyFrames++; } if (numVideoKeyFrames == 2) { flushFrames(outstandingFrames, /* last = */ false); numVideoKeyFrames--; } } ALOGV("flushing last cluster (size %zu)", outstandingFrames.size()); flushFrames(outstandingFrames, /* last = */ true); mDone = true; } //================================================================================================= static const int64_t kInitialDelayTimeUs = 700000LL; void WebmFrameMediaSourceThread::clearFlags() { mDone = false; mPaused = false; mResumed = false; mStarted = false; mReachedEOS = false; } WebmFrameMediaSourceThread::WebmFrameMediaSourceThread( const sp& source, int type, LinkedBlockingQueue >& sink, uint64_t timeCodeScale, int64_t startTimeRealUs, int32_t startTimeOffsetMs, int numTracks, bool realTimeRecording) : WebmFrameSourceThread(type, sink), mSource(source), mTimeCodeScale(timeCodeScale), mTrackDurationUs(0) { clearFlags(); mStartTimeUs = startTimeRealUs; if (realTimeRecording && numTracks > 1) { /* * Copied from MPEG4Writer * * This extra delay of accepting incoming audio/video signals * helps to align a/v start time at the beginning of a recording * session, and it also helps eliminate the "recording" sound for * camcorder applications. * * If client does not set the start time offset, we fall back to * use the default initial delay value. */ int64_t startTimeOffsetUs = startTimeOffsetMs * 1000LL; if (startTimeOffsetUs < 0) { // Start time offset was not set startTimeOffsetUs = kInitialDelayTimeUs; } mStartTimeUs += startTimeOffsetUs; ALOGI("Start time offset: %" PRId64 " us", startTimeOffsetUs); } } status_t WebmFrameMediaSourceThread::start() { sp meta = new MetaData; meta->setInt64(kKeyTime, mStartTimeUs); status_t err = mSource->start(meta.get()); if (err != OK) { mDone = true; mReachedEOS = true; return err; } else { mStarted = true; return WebmFrameThread::start(); } } status_t WebmFrameMediaSourceThread::resume() { if (!mDone && mPaused) { mPaused = false; mResumed = true; } return OK; } status_t WebmFrameMediaSourceThread::pause() { if (mStarted) { mPaused = true; } return OK; } status_t WebmFrameMediaSourceThread::stop() { if (mStarted) { mStarted = false; mDone = true; mSource->stop(); return WebmFrameThread::stop(); } return OK; } void WebmFrameMediaSourceThread::run() { int32_t count = 0; int64_t timestampUs = 0xdeadbeef; int64_t lastTimestampUs = 0; // Previous sample time stamp int64_t lastDurationUs = 0; // Previous sample duration int64_t previousPausedDurationUs = 0; const uint64_t kUninitialized = 0xffffffffffffffffL; mStartTimeUs = kUninitialized; status_t err = OK; MediaBuffer *buffer; while (!mDone && (err = mSource->read(&buffer, NULL)) == OK) { if (buffer->range_length() == 0) { buffer->release(); buffer = NULL; continue; } sp md = buffer->meta_data(); CHECK(md->findInt64(kKeyTime, ×tampUs)); if (mStartTimeUs == kUninitialized) { mStartTimeUs = timestampUs; } timestampUs -= mStartTimeUs; if (mPaused && !mResumed) { lastDurationUs = timestampUs - lastTimestampUs; lastTimestampUs = timestampUs; buffer->release(); buffer = NULL; continue; } ++count; // adjust time-stamps after pause/resume if (mResumed) { int64_t durExcludingEarlierPausesUs = timestampUs - previousPausedDurationUs; CHECK_GE(durExcludingEarlierPausesUs, 0ll); int64_t pausedDurationUs = durExcludingEarlierPausesUs - mTrackDurationUs; CHECK_GE(pausedDurationUs, lastDurationUs); previousPausedDurationUs += pausedDurationUs - lastDurationUs; mResumed = false; } timestampUs -= previousPausedDurationUs; CHECK_GE(timestampUs, 0ll); int32_t isSync = false; md->findInt32(kKeyIsSyncFrame, &isSync); const sp f = new WebmFrame( mType, isSync, timestampUs * 1000 / mTimeCodeScale, buffer); mSink.push(f); ALOGV( "%s %s frame at %" PRId64 " size %zu\n", mType == kVideoType ? "video" : "audio", isSync ? "I" : "P", timestampUs * 1000 / mTimeCodeScale, buffer->range_length()); buffer->release(); buffer = NULL; if (timestampUs > mTrackDurationUs) { mTrackDurationUs = timestampUs; } lastDurationUs = timestampUs - lastTimestampUs; lastTimestampUs = timestampUs; } mTrackDurationUs += lastDurationUs; mSink.push(WebmFrame::EOS); } }