Files
android/proprietary/wvm/WVMExtractorImpl.cpp
Jeff Tinker e9c40a625c Fix native fault in WVMMediaSource::DecryptCallback
This change resolves a lifetime issue between the media extractor
and media sources.  The extractor was being passed as a context
object to a callout in the WV libs.  In some cases, a pointer to
the extractor would be delivered to the callout after the extractor
had been released.  This change assigns the responsibility of the
lifetime of the context object to the media source, to ensure that
a ref is always held on the context object during the lifetime of
the media source.

Change-Id: Ic7a57a1c8496a4798fe590ec356b8a19a4f69967
related-to-bug: 6502322
2012-05-16 19:08:25 -07:00

553 lines
16 KiB
C++

/*
* Copyright (C) 2011 Google, Inc. All Rights Reserved
*/
#define LOG_TAG "WVMExtractorImpl"
#include <utils/Log.h>
#include <cutils/qtaguid.h>
#include "WVMExtractorImpl.h"
#include "WVMMediaSource.h"
#include "WVMFileSource.h"
#include "WVMInfoListener.h"
#include "WVMLogging.h"
#include "WVStreamControlAPI.h"
#include "media/stagefright/MediaErrors.h"
#include "media/stagefright/MediaDefs.h"
#include "drm/DrmManagerClient.h"
#include "drm/DrmConstraints.h"
#include "drm/DrmInfoEvent.h"
#include "AndroidHooks.h"
#define AES_BLOCK_SIZE 16
using namespace android;
static sp<DecryptHandle> sDecryptHandle;
static DrmManagerClient *sDrmManagerClient;
static void _cb1(char *data, unsigned long size)
{
DrmBuffer buf(data, size);
if (sDrmManagerClient != NULL) {
sDrmManagerClient->initializeDecryptUnit(sDecryptHandle, 0, &buf);
}
}
static int _cb2(char *in, char *out, int length, char *iv)
{
int status = -1;
if (sDrmManagerClient != NULL) {
DrmBuffer encryptedDrmBuffer(in, length);
DrmBuffer ivBuffer(iv, (iv? AES_BLOCK_SIZE: 0));
DrmBuffer decryptedDrmBuffer(out, length);
DrmBuffer *decryptedDrmBufferPtr = &decryptedDrmBuffer;
char ivout[AES_BLOCK_SIZE];
if (in && length)
memcpy(ivout, in + length - AES_BLOCK_SIZE, AES_BLOCK_SIZE);
status = sDrmManagerClient->decrypt(sDecryptHandle, 0,
&encryptedDrmBuffer, &decryptedDrmBufferPtr,
&ivBuffer);
if (iv)
memcpy(iv, ivout, AES_BLOCK_SIZE);
}
return status;
}
namespace android {
// DLL entry - construct an extractor and return it
WVMLoadableExtractor *GetInstance(sp<DataSource> dataSource) {
return new WVMExtractorImpl(dataSource);
}
bool IsWidevineMedia(const sp<DataSource>& dataSource) {
char buffer[64 * 1024];
String8 uri = dataSource->getUri();
if (uri.getPathExtension() == ".m3u8" || uri.find(".m3u8?") != -1) {
// can't sniff live streams - check for .m3u8 file extension
return true;
}
ssize_t bytesRead = dataSource->readAt(0, buffer, sizeof(buffer));
if (bytesRead < (ssize_t)sizeof(buffer)) {
ALOGV("IsWidevineMedia - insufficient data: %d", (int)bytesRead);
return false;
}
setenv("WV_SILENT", "true", 1);
bool result = WV_IsWidevineMedia(buffer, sizeof(buffer));
return result;
}
WVMExtractorImpl::WVMExtractorImpl(sp<DataSource> dataSource)
: mFileMetaData(new MetaData()),
mDataSource(dataSource),
mClientContext(new ClientContext()),
mHaveMetaData(false),
mUseAdaptiveStreaming(false),
mIsLiveStream(false),
mSession(NULL),
mSetupStatus(OK)
{
dataSource->getDrmInfo(sDecryptHandle, &sDrmManagerClient);
//ALOGD("WVMExtractorImpl::WVMExtractorImpl: uniqueId = %d", sDrmManagerClient->mUniqueId);
_ah006(android_printbuf);
_ah002(_cb1);
_ah004(_cb2);
if (sDecryptHandle != NULL) {
if (sDecryptHandle->status != RightsStatus::RIGHTS_VALID) {
mSetupStatus = ERROR_DRM_NO_LICENSE;
}
} else
mSetupStatus = ERROR_DRM_NO_LICENSE;
// Set an info listener to handle messages from the drm plugin
mInfoListener = new WVMInfoListener();
sDrmManagerClient->setOnInfoListener(mInfoListener);
}
void WVMExtractorImpl::Initialize()
{
//ALOGD("WVMExtractorImpl::Initialize(%d)\n", getAdaptiveStreamingMode());
WVCredentials credentials;
WVStatus result;
if (mDataSource->getUri().size() > 0 && getAdaptiveStreamingMode()) {
mIsLiveStream = (mDataSource->getUri().getPathExtension().find(".m3u8") == 0);
}
#ifdef REQUIRE_SECURE_BUFFERS
if (!mIsLiveStream) {
//ALOGD("WVMExtractorImpl::Initialize setting DecryptCallback\n");
WVCallbacks callbacks;
callbacks.decrypt = WVMMediaSource::DecryptCallback;
callbacks.socketInfo = SocketInfoCallback;
result = WV_Initialize(&callbacks);
} else {
WVCallbacks callbacks;
callbacks.socketInfo = SocketInfoCallback;
result = WV_Initialize(&callbacks);
}
#else
WVCallbacks callbacks;
callbacks.socketInfo = SocketInfoCallback;
result = WV_Initialize(&callbacks);
#endif
if (result != WV_Status_OK) {
ALOGE("WV_Initialize returned status %d\n", result);
mSetupStatus = ERROR_IO;
} else {
// Enable for debugging HTTP messages
// WV_SetLogging(WV_Logging_HTTP);
if (mDataSource->getUri().size() > 0 && getAdaptiveStreamingMode()) {
// Use the URI - streaming case, only for widevine:// protocol
result = WV_Setup(mSession, mDataSource->getUri().string(),
"RAW/RAW/RAW;destination=getdata", credentials,
WV_OutputFormat_ES, kStreamCacheSize, mClientContext.get());
} else {
// No URI supplied or not adaptive, pull data from the stagefright data source.
mFileSource = new WVMFileSource(mDataSource);
result = WV_Setup(mSession, mFileSource.get(),
"RAW/RAW/RAW;destination=getdata", credentials,
WV_OutputFormat_ES, kStreamCacheSize, mClientContext.get());
}
if (result != WV_Status_OK) {
ALOGE("WV_Setup returned status %d in WVMMediaSource::start\n", result);
mSetupStatus = ERROR_IO;
WV_Teardown(mSession);
mSession = NULL;
} else {
mInfoListener->setSession(mSession);
}
}
WV_SetWarningToErrorMS(10000);
}
WVMExtractorImpl::~WVMExtractorImpl() {
}
// Release decrypt handle when media sources are destroyed
void WVMExtractorImpl::cleanup()
{
if (sDecryptHandle.get()) {
sDecryptHandle.clear();
}
}
void WVMExtractorImpl::SocketInfoCallback(int fd, int closing, void *context)
{
//ALOGD("WVMExtractorImpl::SocketInfoCallback(%d, %d, %p)", fd, closing, context);
ClientContext *obj = (ClientContext *)context;
if (!obj) {
// Not an error, there are some cases where this is expected
return;
} else if (!obj->haveUID()) {
ALOGW("SocketInfoCallback: UID not set!");
return;
}
if (!closing) {
uint32_t kTag = *(uint32_t *)"WVEX";
int res = qtaguid_tagSocket(fd, kTag, obj->getUID());
if (res != 0) {
ALOGE("Failed tagging socket %d for uid %d (My UID=%d)", fd, obj->getUID(), geteuid());
}
} else {
int res = qtaguid_untagSocket(fd);
if (res != 0) {
ALOGE("Failed untagging socket %d (My UID=%d)", fd, geteuid());
}
}
}
//
// Configure metadata for video and audio sources
//
status_t WVMExtractorImpl::readMetaData()
{
if (mHaveMetaData)
return OK;
Initialize();
if (mSetupStatus != OK)
return mSetupStatus;
// Get Video Configuration
WVVideoType videoType;
unsigned short videoStreamID;
unsigned short videoProfile;
unsigned short level;
unsigned short width, height;
float aspect, frameRate;
unsigned long videoBitRate;
WVStatus result = WV_Info_GetVideoConfiguration(mSession, &videoType, &videoStreamID,
&videoProfile, &level, &width, &height,
&aspect, &frameRate, &videoBitRate);
if (result != WV_Status_OK)
return ERROR_MALFORMED;
// Get Audio Configuration
WVAudioType audioType;
unsigned short audioStreamID;
unsigned short audioProfile;
unsigned short numChannels;
unsigned long sampleRate;
unsigned long audioBitRate;
result = WV_Info_GetAudioConfiguration(mSession, &audioType, &audioStreamID, &audioProfile,
&numChannels, &sampleRate, &audioBitRate);
if (result != WV_Status_OK)
return ERROR_MALFORMED;
if (numChannels == 0) {
ALOGD("numChannels is 0!");
return ERROR_MALFORMED;
}
std::string durationString = WV_Info_GetDuration(mSession, "sec");
if (durationString == "") {
// We won't have a duration for live streams, and Stagefright doesn't seem to
// have a way to represent that. Give a default duration of 1 hour for now.
if (mIsLiveStream)
durationString = "3600";
else
return ERROR_MALFORMED;
}
int64_t duration = (int64_t)(strtod(durationString.c_str(), NULL) * 1000000);
sp<MetaData> audioMetaData = new MetaData();
sp<MetaData> videoMetaData = new MetaData();
audioMetaData->setInt64(kKeyDuration, duration);
videoMetaData->setInt64(kKeyDuration, duration);
audioMetaData->setInt32(kKeyBitRate, audioBitRate);
videoMetaData->setInt32(kKeyBitRate, videoBitRate);
switch(videoType) {
case WV_VideoType_H264:
videoMetaData->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
break;
default:
ALOGE("Invalid WV video type %d, expected H264C\n", audioType);
break;
}
switch(audioType) {
case WV_AudioType_AAC:
audioMetaData->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC);
break;
default:
ALOGE("Invalid WV audio type %d, expected AAC\n", audioType);
break;
}
audioMetaData->setInt32(kKeyTrackID, audioStreamID);
videoMetaData->setInt32(kKeyTrackID, videoStreamID);
audioMetaData->setInt32(kKeyChannelCount, numChannels);
audioMetaData->setInt32(kKeySampleRate, sampleRate);
videoMetaData->setInt32(kKeyWidth, width);
videoMetaData->setInt32(kKeyHeight, height);
if (mIsLiveStream) {
float scaleUsed;
result = WV_Play(mSession, 1.0, &scaleUsed, "npt=now-");
if (result != WV_Status_OK) {
ALOGE("WV_Play for live stream setup failed: %d", result);
return ERROR_IO;
}
}
status_t status;
status = readESDSMetaData(audioMetaData);
if (status != OK)
return status;
if (mIsLiveStream) {
result = WV_Pause(mSession, "");
if (result != WV_Status_OK) {
ALOGE("WV_Pause for live stream setup failed: %d", result);
}
}
bool cryptoPluginMode = mClientContext->getCryptoPluginMode();
mAudioSource = new WVMMediaSource(mSession, WV_EsSelector_Audio, audioMetaData,
mIsLiveStream, cryptoPluginMode);
mVideoSource = new WVMMediaSource(mSession, WV_EsSelector_Video, videoMetaData,
mIsLiveStream, cryptoPluginMode);
// Since the WVExtractor goes away soon after this, we delegate ownership of some resources
// to the constructed media source
if (mFileSource.get())
mVideoSource->delegateFileSource(mFileSource);
mVideoSource->delegateDataSource(mDataSource);
mClientContext->setAudioSource(mAudioSource);
mClientContext->setVideoSource(mVideoSource);
mVideoSource->delegateClientContext(mClientContext);
mFileMetaData->setCString(kKeyMIMEType, "video/wvm");
mHaveMetaData = true;
mInfoListener->configureHeartbeat();
if (cryptoPluginMode) {
// In crypto plugin mode, need to trigger the drm plugin to begin
// license use on playback since the media player isn't involved.
sDrmManagerClient->setPlaybackStatus(sDecryptHandle, Playback::START, 0);
}
return OK;
}
status_t WVMExtractorImpl::readESDSMetaData(sp<MetaData> audioMetaData)
{
WVStatus result;
const unsigned char *config;
unsigned long size;
int limit = 500;
do {
size_t bytesRead;
bool auStart, sync;
unsigned long long dts, pts;
unsigned char buf[1];
size_t bufSize = 0;
//
// In order to get the codec config data, we need to have the WVMK
// pull some audio data. But we can't use it yet, so just request 0 bytes.
//
(void)WV_GetEsData(mSession, WV_EsSelector_Audio, buf, bufSize,
bytesRead, auStart, dts, pts, sync);
result = WV_Info_GetCodecConfig(mSession, WV_CodecConfigType_ESDS, config, size);
if (result != WV_Status_OK)
usleep(10000);
} while (result == WV_Status_Warning_Not_Available && limit-- > 0);
if (result != WV_Status_OK) {
ALOGE("WV_Info_GetCodecConfig ESDS returned error %d\n", result);
return ERROR_IO;
}
#if 0
char *filename = "/data/wvm/esds";
FILE *f = fopen(filename, "w");
if (!f)
ALOGD("Failed to open %s", filename);
else {
fwrite(config, size, 1, f);
fclose(f);
}
#endif
audioMetaData->setData(kKeyESDS, kTypeESDS, config, size);
return OK;
}
size_t WVMExtractorImpl::countTracks() {
status_t err;
if ((err = readMetaData()) != OK) {
return 0;
}
return 2; // 1 audio + 1 video
}
sp<MediaSource> WVMExtractorImpl::getTrack(size_t index)
{
status_t err;
if ((err = readMetaData()) != OK) {
return NULL;
}
sp<MediaSource> result;
switch(index) {
case 0:
result = mVideoSource;
break;
case 1:
result = mAudioSource;
break;
default:
break;
}
return result;
}
sp<MetaData> WVMExtractorImpl::getTrackMetaData(size_t index, uint32_t flags)
{
status_t err;
if ((err = readMetaData()) != OK) {
return NULL;
}
sp<MetaData> result;
switch(index) {
case 0:
if (mVideoSource != NULL)
result = mVideoSource->getFormat();
break;
case 1:
if (mAudioSource != NULL)
result = mAudioSource->getFormat();
break;
default:
break;
}
return result;
}
sp<MetaData> WVMExtractorImpl::getMetaData() {
status_t err;
if ((err = readMetaData()) != OK) {
return new MetaData;
}
return mFileMetaData;
}
int64_t WVMExtractorImpl::getCachedDurationUs(status_t *finalStatus) {
const size_t kMaxEncodedRates = 10;
unsigned long encodedRates[kMaxEncodedRates];
size_t ratesReturned, currentTrack;
WVStatus status = WV_Info_GetAdaptiveBitrates(mSession, encodedRates, kMaxEncodedRates,
&ratesReturned, &currentTrack);
if (status == WV_Status_OK) {
if (currentTrack != kMaxEncodedRates && ratesReturned != 0) {
// Log the current adaptive rate every 5 seconds
time_t now = time(0);
static time_t lastLogTime = now;
if (now > lastLogTime + 5) {
lastLogTime = now;
ALOGI("using adaptive track #%d, rate=%ld\n",
currentTrack, encodedRates[currentTrack]);
}
}
}
uint64_t durationUs = 0;
float secondsBuffered;
status = WV_Info_TimeBuffered(mSession, &secondsBuffered);
if (status == WV_Status_End_Of_Media) {
*finalStatus = ERROR_END_OF_STREAM;
} else if (status != WV_Status_OK) {
*finalStatus = ERROR_IO;
} else {
if (mIsLiveStream)
*finalStatus = ERROR_END_OF_STREAM;
else
*finalStatus = OK;
durationUs = (uint64_t)(secondsBuffered * 1000000LL);
}
// Scale the duration to account for Stagefright's conservative buffering.
// This provides much more responsive operation and due to the ability to
// adapt, we don't need to prebuffer so much data. Based on testing, 2x
// is a reasonable compromise between faster startup time and additional
// pauses after playback starts.
return durationUs * 2;
}
void WVMExtractorImpl::setAdaptiveStreamingMode(bool adaptive)
{
//ALOGD("WVMExtractorImpl::setAdaptiveStreamingMode(%d)", adaptive);
mUseAdaptiveStreaming = adaptive;
}
bool WVMExtractorImpl::getAdaptiveStreamingMode() const
{
//ALOGD("WVMExtractorImpl::getAdaptiveStreamingMode - %d", mUseAdaptiveStreaming);
return mUseAdaptiveStreaming;
}
void WVMExtractorImpl::setCryptoPluginMode(bool cryptoPluginMode)
{
//ALOGD("WVMExtractorImpl::setCryptoPluginMode(%d)", cryptoPluginMode);
mClientContext->setCryptoPluginMode(cryptoPluginMode);
}
void WVMExtractorImpl::setUID(uid_t uid)
{
//ALOGD("WVMExtractorImpl::setUID(%d)", (uint32_t)uid);
mClientContext->setUID(uid);
}
} // namespace android