468 lines
13 KiB
C++
468 lines
13 KiB
C++
/*
|
|
* Copyright (C) 2011 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_TAG "WVMMediaSource"
|
|
#include <utils/Log.h>
|
|
|
|
#include "WVMMediaSource.h"
|
|
#include "WVMFileSource.h"
|
|
#include "WVMExtractorImpl.h"
|
|
#include "WVMErrorCodes.h"
|
|
#include "media/stagefright/MediaErrors.h"
|
|
#include "media/stagefright/MediaDefs.h"
|
|
#include "media/stagefright/MediaDebug.h"
|
|
#include "AndroidHooks.h"
|
|
|
|
namespace android {
|
|
|
|
static void _cb(int code)
|
|
{
|
|
WVMMediaSource::sLastError = (status_t)code;
|
|
}
|
|
|
|
status_t WVMMediaSource::sLastError = NO_ERROR;
|
|
|
|
WVMMediaSource::WVMMediaSource(WVSession *session, WVEsSelector esSelector,
|
|
const sp<MetaData> &metaData, bool isLive)
|
|
: mSession(session),
|
|
mESSelector(esSelector),
|
|
mTrackMetaData(metaData),
|
|
mStarted(false),
|
|
mIsLiveStream(isLive),
|
|
mNewSegment(false),
|
|
mGroup(NULL),
|
|
mDts(0),
|
|
mPts(0)
|
|
{
|
|
_ah010(_cb);
|
|
#ifdef REQUIRE_SECURE_BUFFERS
|
|
if (esSelector == WV_EsSelector_Video) {
|
|
OEMCrypto_Initialize();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Since the WVMExtractor lifetime is short, we delegate ownership of some resources
|
|
// to the media source, which cleans them up after when the media source is destroyed
|
|
|
|
void WVMMediaSource::delegateFileSource(sp<WVMFileSource> fileSource)
|
|
{
|
|
mFileSource = fileSource;
|
|
}
|
|
|
|
void WVMMediaSource::delegateDataSource(sp<DataSource> dataSource)
|
|
{
|
|
mDataSource = dataSource;
|
|
}
|
|
|
|
void WVMMediaSource::allocBufferGroup()
|
|
{
|
|
if (mGroup)
|
|
delete mGroup;
|
|
|
|
mGroup = new MediaBufferGroup;
|
|
|
|
size_t size;
|
|
if (mESSelector == WV_EsSelector_Video)
|
|
size = 256 * 1024;
|
|
else
|
|
size = 64 * 1024;
|
|
|
|
mGroup->add_buffer(new MediaBuffer(size));
|
|
}
|
|
|
|
|
|
status_t WVMMediaSource::setBuffers(const Vector<MediaBuffer *> &buffers) {
|
|
#ifdef REQUIRE_SECURE_BUFFERS
|
|
if (!mIsLiveStream) {
|
|
LOGI("Using codec-supplied buffers");
|
|
|
|
delete mGroup;
|
|
mGroup = new MediaBufferGroup;
|
|
for (size_t i = 0; i < buffers.size(); ++i) {
|
|
mGroup->add_buffer(buffers.itemAt(i));
|
|
}
|
|
return OK;
|
|
} else {
|
|
return ERROR_UNSUPPORTED;
|
|
}
|
|
#else
|
|
return ERROR_UNSUPPORTED;
|
|
#endif
|
|
}
|
|
|
|
|
|
status_t WVMMediaSource::start(MetaData *)
|
|
{
|
|
//LOGD("WVMMediaSource::start()");
|
|
Mutex::Autolock autoLock(mLock);
|
|
|
|
CHECK(!mStarted);
|
|
|
|
|
|
mNewSegment = true;
|
|
mStarted = true;
|
|
mLogOnce = true;
|
|
|
|
// Let video stream control play/pause
|
|
if (mESSelector == WV_EsSelector_Video) {
|
|
float speed;
|
|
WVStatus result = WV_Play(mSession, 1.0, &speed, "now-");
|
|
if (result != WV_Status_OK) {
|
|
LOGE("WV_Play returned status %d in WVMMediaSource::start\n", result);
|
|
return ERROR_IO;
|
|
}
|
|
|
|
#ifndef REQUIRE_SECURE_BUFFERS
|
|
allocBufferGroup();
|
|
#else
|
|
if (mIsLiveStream) {
|
|
allocBufferGroup();
|
|
}
|
|
#endif
|
|
} else {
|
|
// audio
|
|
allocBufferGroup();
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
status_t WVMMediaSource::stop()
|
|
{
|
|
//LOGD("WVMMediaSource::stop()");
|
|
Mutex::Autolock autoLock(mLock);
|
|
|
|
CHECK(mStarted);
|
|
|
|
status_t status = OK;
|
|
|
|
// Let video stream control play/pause
|
|
if (mESSelector == WV_EsSelector_Video) {
|
|
WVStatus result = WV_Pause(mSession, "now");
|
|
if (result != WV_Status_OK) {
|
|
LOGE("WV_Pause returned status %d in WVMMediaSource::stop\n", result);
|
|
status = ERROR_IO;
|
|
}
|
|
}
|
|
|
|
delete mGroup;
|
|
mGroup = NULL;
|
|
|
|
mStarted = false;
|
|
|
|
return status;
|
|
}
|
|
|
|
sp<MetaData> WVMMediaSource::getFormat()
|
|
{
|
|
Mutex::Autolock autoLock(mLock);
|
|
|
|
#ifdef REQUIRE_SECURE_BUFFERS
|
|
if (!mIsLiveStream && (mESSelector == WV_EsSelector_Video)) {
|
|
mTrackMetaData->setInt32(kKeyRequiresSecureBuffers, true);
|
|
}
|
|
#endif
|
|
|
|
return mTrackMetaData;
|
|
}
|
|
|
|
std::string usecToNPT(int64_t time)
|
|
{
|
|
unsigned hours = (unsigned)(time / (60LL * 60 * 1000000));
|
|
time -= (int64_t)hours * 60 * 60 * 1000000;
|
|
unsigned mins = (unsigned)(time / (60 * 1000000));
|
|
time -= (int64_t)mins * 60 * 1000000;
|
|
float secs = (float)time / 1000000;
|
|
char buf[32];
|
|
sprintf(buf, "%d:%d:%f", hours, mins, secs);
|
|
return std::string(buf);
|
|
}
|
|
|
|
status_t WVMMediaSource::read(MediaBuffer **buffer, const ReadOptions *options)
|
|
{
|
|
Mutex::Autolock autoLock(mLock);
|
|
|
|
CHECK(mStarted);
|
|
|
|
*buffer = NULL;
|
|
bool seekNextSync = false;
|
|
|
|
#if 0
|
|
// The sync bits aren't working right yet on live streams, so need to disable this
|
|
// for now.
|
|
if (mIsLiveStream && mNewSegment && (mESSelector == WV_EsSelector_Video)) {
|
|
seekNextSync = true;
|
|
mNewSegment = false;
|
|
}
|
|
#endif
|
|
|
|
int64_t seekTimeUs;
|
|
|
|
int retryLimit = 500; // Limit on number of retries before timeout, 10ms per retry
|
|
|
|
ReadOptions::SeekMode mode;
|
|
if (options && options->getSeekTo(&seekTimeUs, &mode)) {
|
|
|
|
// When doing a seek, use a longer timeout since we need to set up a new connection
|
|
retryLimit = 1500;
|
|
|
|
//LOGD("%s seek mode=%d, seek time=%lld lateby=%lld",
|
|
// (mESSelector == WV_EsSelector_Video) ? "video" : "audio",
|
|
// mode, seekTimeUs, options->getLateBy());
|
|
if (mode == ReadOptions::SEEK_NEXT_SYNC) {
|
|
// Handle seek next sync by dropping frames on this track that are
|
|
// prior to the specified time.
|
|
seekNextSync = true;
|
|
} else {
|
|
// Let video stream control seek
|
|
if (mESSelector == WV_EsSelector_Video) {
|
|
float scaleUsed;
|
|
std::string when = usecToNPT(seekTimeUs) + std::string("-");
|
|
WVStatus result = WV_Play(mSession, 1.0, &scaleUsed, when );
|
|
if (result != WV_Status_OK) {
|
|
LOGE("WV_Play returned status %d in WVMMediaSource::read\n", result);
|
|
return ERROR_IO;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MediaBuffer *mediaBuf;
|
|
|
|
status_t err = mGroup->acquire_buffer(&mediaBuf);
|
|
|
|
if (err != OK) {
|
|
CHECK_EQ(mediaBuf, NULL);
|
|
return err;
|
|
}
|
|
|
|
#ifdef REQUIRE_SECURE_BUFFERS
|
|
sDecryptContext[mESSelector].Initialize(mediaBuf);
|
|
#endif
|
|
|
|
size_t bytesRead;
|
|
bool auStart;
|
|
size_t offset = 0;
|
|
int64_t keyTime;
|
|
|
|
bool syncFrame;
|
|
int retryCount = 0;
|
|
|
|
// Pull full access units. Since we aren't sure how big they might be,
|
|
// start with initial buffer size, then allocate a larger buffer if we
|
|
// get a number of bytes equal to the full buffer size and go back
|
|
// for the rest. Only loop in this case, usually it's one pass through.
|
|
|
|
while (true) {
|
|
size_t size = mediaBuf->size() - offset;
|
|
|
|
WVStatus result = WV_GetEsData(mSession, mESSelector, (uint8_t *)mediaBuf->data() + offset,
|
|
size, bytesRead, auStart, mDts, mPts, syncFrame);
|
|
|
|
if (result != WV_Status_OK &&
|
|
result != WV_Status_Warning_Need_Key &&
|
|
result != WV_Status_Warning_Download_Stalled) {
|
|
|
|
status_t status;
|
|
|
|
switch(result) {
|
|
case WV_Status_End_Of_Media:
|
|
status = ERROR_END_OF_STREAM;
|
|
break;
|
|
case WV_Status_Terminate_Requested:
|
|
status = ERROR_HEARTBEAT_TERMINATE_REQUESTED;
|
|
break;
|
|
case YT_HEARTBEAT_NO_ACTIVE_PURCHASE_AGREEMENT:
|
|
status = ERROR_HEARTBEAT_NO_ACTIVE_PURCHASE_AGREEMENT;
|
|
break;
|
|
case YT_HEARTBEAT_CONCURRENT_PLAYBACK:
|
|
status = ERROR_HEARTBEAT_CONCURRENT_PLAYBACK;
|
|
break;
|
|
case YT_HEARTBEAT_UNUSUAL_ACTIVITY:
|
|
status = ERROR_HEARTBEAT_UNUSUAL_ACTIVITY;
|
|
break;
|
|
case YT_HEARTBEAT_STREAMING_UNAVAILABLE:
|
|
status = ERROR_HEARTBEAT_STREAMING_UNAVAILABLE;
|
|
break;
|
|
case YT_HEARTBEAT_CANNOT_ACTIVATE_RENTAL:
|
|
status = ERROR_HEARTBEAT_CANNOT_ACTIVATE_RENTAL;
|
|
break;
|
|
default:
|
|
if (mLogOnce) {
|
|
LOGE("WV_GetEsData returned ERROR %d in WVMMediaSource::read\n", result);
|
|
mLogOnce = false;
|
|
}
|
|
status = ERROR_IO;
|
|
break;
|
|
}
|
|
|
|
mediaBuf->release();
|
|
return status;
|
|
}
|
|
|
|
if (sLastError != NO_ERROR) {
|
|
mediaBuf->release();
|
|
status_t status = sLastError;
|
|
sLastError = NO_ERROR;
|
|
return status;
|
|
}
|
|
|
|
if (bytesRead == 0) {
|
|
if (retryCount++ >= retryLimit) {
|
|
// If no data received within the retry limit, return ERROR_IO
|
|
// This prevents the player from becoming unresponsive
|
|
mediaBuf->release();
|
|
return ERROR_IO;
|
|
} else {
|
|
// Didn't get anything, sleep a bit so we don't hog the CPU then try again
|
|
usleep(10000);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
#define PCR_HZ 90000
|
|
keyTime = (int64_t)mPts * 1000000 / PCR_HZ;
|
|
|
|
if (seekNextSync && ((keyTime < seekTimeUs) || !syncFrame)) {
|
|
// drop frames up to next sync if requested
|
|
usleep(10000);
|
|
#ifdef REQUIRE_SECURE_BUFFERS
|
|
sDecryptContext[mESSelector].Initialize(mediaBuf);
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
if (offset + bytesRead < mediaBuf->size())
|
|
break;
|
|
|
|
#ifdef REQUIRE_SECURE_BUFFERS
|
|
LOGD("buffer overflow");
|
|
mediaBuf->release();
|
|
return ERROR_IO;
|
|
#endif
|
|
|
|
//LOGD("Resizing...");
|
|
|
|
// This buffer is too small, allocate a larger buffer twice the size
|
|
// and copy the data from the current buffer into the first part of
|
|
// the new buffer, then set offset to where the next read should go.
|
|
|
|
MediaBuffer *newBuffer = new MediaBuffer(mediaBuf->size() * 2);
|
|
newBuffer->add_ref();
|
|
|
|
memcpy(newBuffer->data(), mediaBuf->data(), mediaBuf->size());
|
|
offset = mediaBuf->size();
|
|
|
|
mGroup->add_buffer(newBuffer);
|
|
|
|
mediaBuf->release();
|
|
mediaBuf = newBuffer;
|
|
}
|
|
|
|
mediaBuf->meta_data()->clear();
|
|
mediaBuf->meta_data()->setInt64(kKeyTime, keyTime);
|
|
|
|
mediaBuf->set_range(0, bytesRead + offset);
|
|
|
|
#if 0
|
|
// debug code - log packets to files
|
|
char filename[32];
|
|
static int acounter = 0, vcounter = 0;
|
|
if (mESSelector == WV_EsSelector_Video)
|
|
sprintf(filename, "/data/wvm/v%d", vcounter++);
|
|
else
|
|
sprintf(filename, "/data/wvm/a%d", acounter++);
|
|
|
|
FILE *f = fopen(filename, "w");
|
|
if (!f)
|
|
LOGE("WVMFileSource: can't open %s", filename);
|
|
else {
|
|
fwrite(mediaBuf->data(), bytesRead + offset, 1, f);
|
|
fclose(f);
|
|
}
|
|
LOGD("WVMMediaSource::read writing (%d bytes to %s)", bytesRead + offset, filename);
|
|
#endif
|
|
|
|
#if 0
|
|
LOGD("[%p] %s packet length=%d kKeyTime=%lld %s\n", mediaBuf,
|
|
(mESSelector == WV_EsSelector_Video ? "video" : "audio"),
|
|
bytesRead + offset, keyTime, syncFrame ? "sync" : "");
|
|
#endif
|
|
|
|
*buffer = mediaBuf;
|
|
|
|
return OK;
|
|
}
|
|
|
|
#ifdef REQUIRE_SECURE_BUFFERS
|
|
|
|
WVMMediaSource::DecryptContext WVMMediaSource::sDecryptContext[2] = {};
|
|
|
|
void WVMMediaSource::DecryptCallback(WVEsSelector esType, void* input, void* output,
|
|
size_t length, int key)
|
|
{
|
|
//LOGD("DecryptCallback(type=%d, in=%p, out=%p, len=%d, key=%d\n",
|
|
// (int)esType, input, output, length, key);
|
|
DecryptContext &context = sDecryptContext[esType];
|
|
OEMCrypto_UINT32 copied = length;
|
|
OEMCryptoResult result;
|
|
unsigned char *iv = NULL;
|
|
|
|
if (key)
|
|
iv = context.mIV;
|
|
|
|
if (esType == WV_EsSelector_Video) {
|
|
result = OEMCrypto_DecryptVideo(iv, (OEMCrypto_UINT8 *)input, length,
|
|
(OEMCrypto_UINT32)(char *)context.mMediaBuf->data(),
|
|
context.mOffset, &copied);
|
|
} else {
|
|
result = OEMCrypto_DecryptAudio(iv, (OEMCrypto_UINT8 *)input, length,
|
|
(OEMCrypto_UINT8 *)context.mMediaBuf->data() + context.mOffset,
|
|
&copied);
|
|
}
|
|
|
|
if (result != OEMCrypto_SUCCESS) {
|
|
LOGD("OEMCrypto decrypt failure: %d", result);
|
|
}
|
|
|
|
context.mOffset += copied;
|
|
}
|
|
#endif
|
|
|
|
|
|
WVMMediaSource::~WVMMediaSource()
|
|
{
|
|
//LOGD("WVMMediaSource::~WVMMediaSource()");
|
|
|
|
if (mStarted) {
|
|
stop();
|
|
}
|
|
|
|
if (mESSelector == WV_EsSelector_Video) {
|
|
if (mSession != NULL) {
|
|
WV_Teardown(mSession);
|
|
#ifdef REQUIRE_SECURE_BUFFERS
|
|
OEMCrypto_Terminate();
|
|
#endif
|
|
}
|
|
WVMExtractorImpl::cleanup();
|
|
}
|
|
}
|
|
|
|
} // namespace android
|