Files
android/proprietary/wvm/WVMExtractorImpl.cpp
Jeff Tinker 462dc030f8 Upgrade of Widevine libraries from 4.5.0 to 6.0.0
Upload for Dave Parsons

6.0.0 Widevine libraries contain the features required
to support multiple audio tracks and subtitles.  This
is establishing the baseline for those features.

Change-Id: If12074bb2e4364c8107339e2f69453c40698e91f
related-to-bug: 5880566
2012-04-05 15:49:32 -07:00

521 lines
15 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;
}
bool result = WV_IsWidevineMedia(buffer, sizeof(buffer));
return result;
}
WVMExtractorImpl::WVMExtractorImpl(sp<DataSource> dataSource)
: mFileMetaData(new MetaData()),
mDataSource(dataSource),
mHaveMetaData(false),
mUseAdaptiveStreaming(false),
mIsLiveStream(false),
mSession(NULL),
mSetupStatus(OK),
mUIDIsSet(false)
{
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, this);
} 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);
}
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);
WVMExtractorImpl *obj = (WVMExtractorImpl *)context;
if (!obj || !obj->mUIDIsSet) {
ALOGW("SocketInfoCallback - UID not set!");
return;
}
if (!closing) {
uint32_t kTag = *(uint32_t *)"WVEX";
int res = qtaguid_tagSocket(fd, kTag, obj->mUID);
if (res != 0) {
ALOGE("Failed tagging socket %d for uid %d (My UID=%d)", fd, obj->mUID, 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;
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);
}
}
mAudioSource = new WVMMediaSource(mSession, WV_EsSelector_Audio, audioMetaData, mIsLiveStream);
mVideoSource = new WVMMediaSource(mSession, WV_EsSelector_Video, videoMetaData, mIsLiveStream);
// 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);
mFileMetaData->setCString(kKeyMIMEType, "video/mp4");
mHaveMetaData = true;
mInfoListener->configureHeartbeat();
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 auEnd, 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, dts, pts, sync, auEnd);
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 {
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::setUID(uid_t uid)
{
//ALOGD("WVMExtractorImpl::setUID(%d)", (uint32_t)uid);
mUID = uid;
mUIDIsSet = true;
}
} // namespace android