You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
653 lines
18 KiB
653 lines
18 KiB
/*
|
|
* 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 <utils/Log.h>
|
|
|
|
#include "SimplePlayer.h"
|
|
|
|
#include <gui/Surface.h>
|
|
|
|
#include <media/AudioTrack.h>
|
|
#include <mediadrm/ICrypto.h>
|
|
#include <media/IMediaHTTPService.h>
|
|
#include <media/MediaCodecBuffer.h>
|
|
#include <media/stagefright/foundation/ABuffer.h>
|
|
#include <media/stagefright/foundation/ADebug.h>
|
|
#include <media/stagefright/foundation/AMessage.h>
|
|
#include <media/stagefright/MediaCodec.h>
|
|
#include <media/stagefright/MediaErrors.h>
|
|
#include <media/stagefright/NuMediaExtractor.h>
|
|
|
|
namespace android {
|
|
|
|
SimplePlayer::SimplePlayer()
|
|
: mState(UNINITIALIZED),
|
|
mDoMoreStuffGeneration(0),
|
|
mStartTimeRealUs(-1ll) {
|
|
}
|
|
|
|
SimplePlayer::~SimplePlayer() {
|
|
}
|
|
|
|
// static
|
|
status_t PostAndAwaitResponse(
|
|
const sp<AMessage> &msg, sp<AMessage> *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<AMessage> msg = new AMessage(kWhatSetDataSource, this);
|
|
msg->setString("path", path);
|
|
sp<AMessage> response;
|
|
return PostAndAwaitResponse(msg, &response);
|
|
}
|
|
|
|
status_t SimplePlayer::setSurface(const sp<IGraphicBufferProducer> &bufferProducer) {
|
|
sp<AMessage> msg = new AMessage(kWhatSetSurface, this);
|
|
|
|
sp<Surface> surface;
|
|
if (bufferProducer != NULL) {
|
|
surface = new Surface(bufferProducer);
|
|
}
|
|
|
|
msg->setObject("surface", surface);
|
|
|
|
sp<AMessage> response;
|
|
return PostAndAwaitResponse(msg, &response);
|
|
}
|
|
|
|
status_t SimplePlayer::prepare() {
|
|
sp<AMessage> msg = new AMessage(kWhatPrepare, this);
|
|
sp<AMessage> response;
|
|
return PostAndAwaitResponse(msg, &response);
|
|
}
|
|
|
|
status_t SimplePlayer::start() {
|
|
sp<AMessage> msg = new AMessage(kWhatStart, this);
|
|
sp<AMessage> response;
|
|
return PostAndAwaitResponse(msg, &response);
|
|
}
|
|
|
|
status_t SimplePlayer::stop() {
|
|
sp<AMessage> msg = new AMessage(kWhatStop, this);
|
|
sp<AMessage> response;
|
|
return PostAndAwaitResponse(msg, &response);
|
|
}
|
|
|
|
status_t SimplePlayer::reset() {
|
|
sp<AMessage> msg = new AMessage(kWhatReset, this);
|
|
sp<AMessage> response;
|
|
return PostAndAwaitResponse(msg, &response);
|
|
}
|
|
|
|
void SimplePlayer::onMessageReceived(const sp<AMessage> &msg) {
|
|
switch (msg->what()) {
|
|
case kWhatSetDataSource:
|
|
{
|
|
status_t err;
|
|
if (mState != UNINITIALIZED) {
|
|
err = INVALID_OPERATION;
|
|
} else {
|
|
CHECK(msg->findString("path", &mPath));
|
|
mState = UNPREPARED;
|
|
}
|
|
|
|
sp<AReplyToken> replyID;
|
|
CHECK(msg->senderAwaitsResponse(&replyID));
|
|
|
|
sp<AMessage> response = new AMessage;
|
|
response->setInt32("err", err);
|
|
response->postReply(replyID);
|
|
break;
|
|
}
|
|
|
|
case kWhatSetSurface:
|
|
{
|
|
status_t err;
|
|
if (mState != UNPREPARED) {
|
|
err = INVALID_OPERATION;
|
|
} else {
|
|
sp<RefBase> obj;
|
|
CHECK(msg->findObject("surface", &obj));
|
|
mSurface = static_cast<Surface *>(obj.get());
|
|
err = OK;
|
|
}
|
|
|
|
sp<AReplyToken> replyID;
|
|
CHECK(msg->senderAwaitsResponse(&replyID));
|
|
|
|
sp<AMessage> 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;
|
|
}
|
|
}
|
|
|
|
sp<AReplyToken> replyID;
|
|
CHECK(msg->senderAwaitsResponse(&replyID));
|
|
|
|
sp<AMessage> 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
sp<AReplyToken> replyID;
|
|
CHECK(msg->senderAwaitsResponse(&replyID));
|
|
|
|
sp<AMessage> 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;
|
|
}
|
|
}
|
|
|
|
sp<AReplyToken> replyID;
|
|
CHECK(msg->senderAwaitsResponse(&replyID));
|
|
|
|
sp<AMessage> 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;
|
|
}
|
|
|
|
sp<AReplyToken> replyID;
|
|
CHECK(msg->senderAwaitsResponse(&replyID));
|
|
|
|
sp<AMessage> 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<AMessage> 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 ? mSurface : NULL,
|
|
NULL /* crypto */,
|
|
0 /* flags */);
|
|
|
|
CHECK_EQ(err, (status_t)OK);
|
|
|
|
size_t j = 0;
|
|
sp<ABuffer> buffer;
|
|
while (format->findBuffer(AStringPrintf("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<ABuffer> &srcBuffer = state->mCSD.itemAt(j);
|
|
|
|
size_t index;
|
|
err = state->mCodec->dequeueInputBuffer(&index, -1ll);
|
|
CHECK_EQ(err, (status_t)OK);
|
|
|
|
const sp<MediaCodecBuffer> &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<AMessage> msg = new AMessage(kWhatDoMoreStuff, this);
|
|
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();
|
|
mSurface.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 %zu",
|
|
mStateByTrackIndex.keyAt(i));
|
|
|
|
state->mAvailInputBufferIndices.push_back(index);
|
|
} else {
|
|
ALOGV("dequeueInputBuffer on track %zu 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 %zu",
|
|
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 %zu 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<MediaCodecBuffer> &dstBuffer =
|
|
state->mBuffers[0].itemAt(index);
|
|
sp<ABuffer> abuffer = new ABuffer(dstBuffer->base(), dstBuffer->capacity());
|
|
|
|
err = mExtractor->readSampleData(abuffer);
|
|
CHECK_EQ(err, (status_t)OK);
|
|
dstBuffer->setRange(abuffer->offset(), abuffer->size());
|
|
|
|
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 %zu", 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 %zu buffer late by %lld us, dropping.",
|
|
mStateByTrackIndex.keyAt(i), (long long)lateByUs);
|
|
state->mCodec->releaseOutputBuffer(info->mIndex);
|
|
} else {
|
|
if (state->mAudioTrack != NULL) {
|
|
const sp<MediaCodecBuffer> &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 %zu buffer early by %lld us.",
|
|
mStateByTrackIndex.keyAt(i), (long long)-lateByUs);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
status_t SimplePlayer::onOutputFormatChanged(
|
|
size_t trackIndex __unused, CodecState *state) {
|
|
sp<AMessage> 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<MediaCodecBuffer> &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",
|
|
(long long)delayUs, numFramesAvailableToWrite, numFramesWritten);
|
|
}
|
|
|
|
info->mOffset += nbytes;
|
|
info->mSize -= nbytes;
|
|
|
|
state->mNumFramesWritten += numFramesWritten;
|
|
}
|
|
|
|
} // namespace android
|