/* * 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 "SimplePlayer" #include #include "SimplePlayer.h" #include #include #include #include #include #include #include #include #include #include #include namespace android { SimplePlayer::SimplePlayer() : mState(UNINITIALIZED), mDoMoreStuffGeneration(0), mStartTimeRealUs(-1ll) { } SimplePlayer::~SimplePlayer() { } // static status_t PostAndAwaitResponse( const sp &msg, sp *response) { status_t err = msg->postAndAwaitResponse(response); if (err != OK) { return err; } if (!(*response)->findInt32("err", &err)) { err = OK; } return err; } status_t SimplePlayer::setDataSource(const char *path) { sp msg = new AMessage(kWhatSetDataSource, id()); msg->setString("path", path); sp response; return PostAndAwaitResponse(msg, &response); } status_t SimplePlayer::setSurface(const sp &bufferProducer) { sp msg = new AMessage(kWhatSetSurface, id()); sp surface; if (bufferProducer != NULL) { surface = new Surface(bufferProducer); } msg->setObject( "native-window", new NativeWindowWrapper(surface)); sp response; return PostAndAwaitResponse(msg, &response); } status_t SimplePlayer::prepare() { sp msg = new AMessage(kWhatPrepare, id()); sp response; return PostAndAwaitResponse(msg, &response); } status_t SimplePlayer::start() { sp msg = new AMessage(kWhatStart, id()); sp response; return PostAndAwaitResponse(msg, &response); } status_t SimplePlayer::stop() { sp msg = new AMessage(kWhatStop, id()); sp response; return PostAndAwaitResponse(msg, &response); } status_t SimplePlayer::reset() { sp msg = new AMessage(kWhatReset, id()); sp response; return PostAndAwaitResponse(msg, &response); } void SimplePlayer::onMessageReceived(const sp &msg) { switch (msg->what()) { case kWhatSetDataSource: { status_t err; if (mState != UNINITIALIZED) { err = INVALID_OPERATION; } else { CHECK(msg->findString("path", &mPath)); mState = UNPREPARED; } uint32_t replyID; CHECK(msg->senderAwaitsResponse(&replyID)); sp response = new AMessage; response->setInt32("err", err); response->postReply(replyID); break; } case kWhatSetSurface: { status_t err; if (mState != UNPREPARED) { err = INVALID_OPERATION; } else { sp obj; CHECK(msg->findObject("native-window", &obj)); mNativeWindow = static_cast(obj.get()); err = OK; } uint32_t replyID; CHECK(msg->senderAwaitsResponse(&replyID)); sp response = new AMessage; response->setInt32("err", err); response->postReply(replyID); break; } case kWhatPrepare: { status_t err; if (mState != UNPREPARED) { err = INVALID_OPERATION; } else { err = onPrepare(); if (err == OK) { mState = STOPPED; } } uint32_t replyID; CHECK(msg->senderAwaitsResponse(&replyID)); sp response = new AMessage; response->setInt32("err", err); response->postReply(replyID); break; } case kWhatStart: { status_t err = OK; if (mState == UNPREPARED) { err = onPrepare(); if (err == OK) { mState = STOPPED; } } if (err == OK) { if (mState != STOPPED) { err = INVALID_OPERATION; } else { err = onStart(); if (err == OK) { mState = STARTED; } } } uint32_t replyID; CHECK(msg->senderAwaitsResponse(&replyID)); sp response = new AMessage; response->setInt32("err", err); response->postReply(replyID); break; } case kWhatStop: { status_t err; if (mState != STARTED) { err = INVALID_OPERATION; } else { err = onStop(); if (err == OK) { mState = STOPPED; } } uint32_t replyID; CHECK(msg->senderAwaitsResponse(&replyID)); sp response = new AMessage; response->setInt32("err", err); response->postReply(replyID); break; } case kWhatReset: { status_t err = OK; if (mState == STARTED) { CHECK_EQ(onStop(), (status_t)OK); mState = STOPPED; } if (mState == STOPPED) { err = onReset(); mState = UNINITIALIZED; } uint32_t replyID; CHECK(msg->senderAwaitsResponse(&replyID)); sp response = new AMessage; response->setInt32("err", err); response->postReply(replyID); break; } case kWhatDoMoreStuff: { int32_t generation; CHECK(msg->findInt32("generation", &generation)); if (generation != mDoMoreStuffGeneration) { break; } status_t err = onDoMoreStuff(); if (err == OK) { msg->post(10000ll); } break; } default: TRESPASS(); } } status_t SimplePlayer::onPrepare() { CHECK_EQ(mState, UNPREPARED); mExtractor = new NuMediaExtractor; status_t err = mExtractor->setDataSource( NULL /* httpService */, mPath.c_str()); if (err != OK) { mExtractor.clear(); return err; } if (mCodecLooper == NULL) { mCodecLooper = new ALooper; mCodecLooper->start(); } bool haveAudio = false; bool haveVideo = false; for (size_t i = 0; i < mExtractor->countTracks(); ++i) { sp format; status_t err = mExtractor->getTrackFormat(i, &format); CHECK_EQ(err, (status_t)OK); AString mime; CHECK(format->findString("mime", &mime)); bool isVideo = !strncasecmp(mime.c_str(), "video/", 6); if (!haveAudio && !strncasecmp(mime.c_str(), "audio/", 6)) { haveAudio = true; } else if (!haveVideo && isVideo) { haveVideo = true; } else { continue; } err = mExtractor->selectTrack(i); CHECK_EQ(err, (status_t)OK); CodecState *state = &mStateByTrackIndex.editValueAt( mStateByTrackIndex.add(i, CodecState())); state->mNumFramesWritten = 0; state->mCodec = MediaCodec::CreateByType( mCodecLooper, mime.c_str(), false /* encoder */); CHECK(state->mCodec != NULL); err = state->mCodec->configure( format, isVideo ? mNativeWindow->getSurfaceTextureClient() : NULL, NULL /* crypto */, 0 /* flags */); CHECK_EQ(err, (status_t)OK); size_t j = 0; sp buffer; while (format->findBuffer(StringPrintf("csd-%d", j).c_str(), &buffer)) { state->mCSD.push_back(buffer); ++j; } } for (size_t i = 0; i < mStateByTrackIndex.size(); ++i) { CodecState *state = &mStateByTrackIndex.editValueAt(i); status_t err = state->mCodec->start(); CHECK_EQ(err, (status_t)OK); err = state->mCodec->getInputBuffers(&state->mBuffers[0]); CHECK_EQ(err, (status_t)OK); err = state->mCodec->getOutputBuffers(&state->mBuffers[1]); CHECK_EQ(err, (status_t)OK); for (size_t j = 0; j < state->mCSD.size(); ++j) { const sp &srcBuffer = state->mCSD.itemAt(j); size_t index; err = state->mCodec->dequeueInputBuffer(&index, -1ll); CHECK_EQ(err, (status_t)OK); const sp &dstBuffer = state->mBuffers[0].itemAt(index); CHECK_LE(srcBuffer->size(), dstBuffer->capacity()); dstBuffer->setRange(0, srcBuffer->size()); memcpy(dstBuffer->data(), srcBuffer->data(), srcBuffer->size()); err = state->mCodec->queueInputBuffer( index, 0, dstBuffer->size(), 0ll, MediaCodec::BUFFER_FLAG_CODECCONFIG); CHECK_EQ(err, (status_t)OK); } } return OK; } status_t SimplePlayer::onStart() { CHECK_EQ(mState, STOPPED); mStartTimeRealUs = -1ll; sp msg = new AMessage(kWhatDoMoreStuff, id()); msg->setInt32("generation", ++mDoMoreStuffGeneration); msg->post(); return OK; } status_t SimplePlayer::onStop() { CHECK_EQ(mState, STARTED); ++mDoMoreStuffGeneration; return OK; } status_t SimplePlayer::onReset() { CHECK_EQ(mState, STOPPED); for (size_t i = 0; i < mStateByTrackIndex.size(); ++i) { CodecState *state = &mStateByTrackIndex.editValueAt(i); CHECK_EQ(state->mCodec->release(), (status_t)OK); } mStartTimeRealUs = -1ll; mStateByTrackIndex.clear(); mCodecLooper.clear(); mExtractor.clear(); mNativeWindow.clear(); mPath.clear(); return OK; } status_t SimplePlayer::onDoMoreStuff() { ALOGV("onDoMoreStuff"); for (size_t i = 0; i < mStateByTrackIndex.size(); ++i) { CodecState *state = &mStateByTrackIndex.editValueAt(i); status_t err; do { size_t index; err = state->mCodec->dequeueInputBuffer(&index); if (err == OK) { ALOGV("dequeued input buffer on track %d", mStateByTrackIndex.keyAt(i)); state->mAvailInputBufferIndices.push_back(index); } else { ALOGV("dequeueInputBuffer on track %d returned %d", mStateByTrackIndex.keyAt(i), err); } } while (err == OK); do { BufferInfo info; err = state->mCodec->dequeueOutputBuffer( &info.mIndex, &info.mOffset, &info.mSize, &info.mPresentationTimeUs, &info.mFlags); if (err == OK) { ALOGV("dequeued output buffer on track %d", mStateByTrackIndex.keyAt(i)); state->mAvailOutputBufferInfos.push_back(info); } else if (err == INFO_FORMAT_CHANGED) { err = onOutputFormatChanged(mStateByTrackIndex.keyAt(i), state); CHECK_EQ(err, (status_t)OK); } else if (err == INFO_OUTPUT_BUFFERS_CHANGED) { err = state->mCodec->getOutputBuffers(&state->mBuffers[1]); CHECK_EQ(err, (status_t)OK); } else { ALOGV("dequeueOutputBuffer on track %d returned %d", mStateByTrackIndex.keyAt(i), err); } } while (err == OK || err == INFO_FORMAT_CHANGED || err == INFO_OUTPUT_BUFFERS_CHANGED); } for (;;) { size_t trackIndex; status_t err = mExtractor->getSampleTrackIndex(&trackIndex); if (err != OK) { ALOGI("encountered input EOS."); break; } else { CodecState *state = &mStateByTrackIndex.editValueFor(trackIndex); if (state->mAvailInputBufferIndices.empty()) { break; } size_t index = *state->mAvailInputBufferIndices.begin(); state->mAvailInputBufferIndices.erase( state->mAvailInputBufferIndices.begin()); const sp &dstBuffer = state->mBuffers[0].itemAt(index); err = mExtractor->readSampleData(dstBuffer); CHECK_EQ(err, (status_t)OK); int64_t timeUs; CHECK_EQ(mExtractor->getSampleTime(&timeUs), (status_t)OK); err = state->mCodec->queueInputBuffer( index, dstBuffer->offset(), dstBuffer->size(), timeUs, 0); CHECK_EQ(err, (status_t)OK); ALOGV("enqueued input data on track %d", trackIndex); err = mExtractor->advance(); CHECK_EQ(err, (status_t)OK); } } int64_t nowUs = ALooper::GetNowUs(); if (mStartTimeRealUs < 0ll) { mStartTimeRealUs = nowUs + 1000000ll; } for (size_t i = 0; i < mStateByTrackIndex.size(); ++i) { CodecState *state = &mStateByTrackIndex.editValueAt(i); while (!state->mAvailOutputBufferInfos.empty()) { BufferInfo *info = &*state->mAvailOutputBufferInfos.begin(); int64_t whenRealUs = info->mPresentationTimeUs + mStartTimeRealUs; int64_t lateByUs = nowUs - whenRealUs; if (lateByUs > -10000ll) { bool release = true; if (lateByUs > 30000ll) { ALOGI("track %d buffer late by %lld us, dropping.", mStateByTrackIndex.keyAt(i), lateByUs); state->mCodec->releaseOutputBuffer(info->mIndex); } else { if (state->mAudioTrack != NULL) { const sp &srcBuffer = state->mBuffers[1].itemAt(info->mIndex); renderAudio(state, info, srcBuffer); if (info->mSize > 0) { release = false; } } if (release) { state->mCodec->renderOutputBufferAndRelease( info->mIndex); } } if (release) { state->mAvailOutputBufferInfos.erase( state->mAvailOutputBufferInfos.begin()); info = NULL; } else { break; } } else { ALOGV("track %d buffer early by %lld us.", mStateByTrackIndex.keyAt(i), -lateByUs); break; } } } return OK; } status_t SimplePlayer::onOutputFormatChanged( size_t trackIndex, CodecState *state) { sp format; status_t err = state->mCodec->getOutputFormat(&format); if (err != OK) { return err; } AString mime; CHECK(format->findString("mime", &mime)); if (!strncasecmp(mime.c_str(), "audio/", 6)) { int32_t channelCount; int32_t sampleRate; CHECK(format->findInt32("channel-count", &channelCount)); CHECK(format->findInt32("sample-rate", &sampleRate)); state->mAudioTrack = new AudioTrack( AUDIO_STREAM_MUSIC, sampleRate, AUDIO_FORMAT_PCM_16_BIT, audio_channel_out_mask_from_count(channelCount), 0); state->mNumFramesWritten = 0; } return OK; } void SimplePlayer::renderAudio( CodecState *state, BufferInfo *info, const sp &buffer) { CHECK(state->mAudioTrack != NULL); if (state->mAudioTrack->stopped()) { state->mAudioTrack->start(); } uint32_t numFramesPlayed; CHECK_EQ(state->mAudioTrack->getPosition(&numFramesPlayed), (status_t)OK); uint32_t numFramesAvailableToWrite = state->mAudioTrack->frameCount() - (state->mNumFramesWritten - numFramesPlayed); size_t numBytesAvailableToWrite = numFramesAvailableToWrite * state->mAudioTrack->frameSize(); size_t copy = info->mSize; if (copy > numBytesAvailableToWrite) { copy = numBytesAvailableToWrite; } if (copy == 0) { return; } int64_t startTimeUs = ALooper::GetNowUs(); ssize_t nbytes = state->mAudioTrack->write( buffer->base() + info->mOffset, copy); CHECK_EQ(nbytes, (ssize_t)copy); int64_t delayUs = ALooper::GetNowUs() - startTimeUs; uint32_t numFramesWritten = nbytes / state->mAudioTrack->frameSize(); if (delayUs > 2000ll) { ALOGW("AudioTrack::write took %lld us, numFramesAvailableToWrite=%u, " "numFramesWritten=%u", delayUs, numFramesAvailableToWrite, numFramesWritten); } info->mOffset += nbytes; info->mSize -= nbytes; state->mNumFramesWritten += numFramesWritten; } } // namespace android