Files
android/proprietary/wvm/WVMExtractorImpl.cpp
James Dong 79e250e999 If number of audio channels returned is 0, report an error
related-to-bug: 6500580

Change-Id: I55cab0bde30841c31f671d03f99c59d337fea564
2012-05-15 17:54:59 -07:00

555 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),
mHaveMetaData(false),
mUseAdaptiveStreaming(false),
mIsLiveStream(false),
mSession(NULL),
mSetupStatus(OK),
mUIDIsSet(false),
mCryptoPluginMode(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, this);
}
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) {
ALOGW("SocketInfoCallback: missing context!");
return;
} else if (!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;
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);
}
}
mAudioSource = new WVMMediaSource(mSession, WV_EsSelector_Audio, audioMetaData,
mIsLiveStream, mCryptoPluginMode);
mVideoSource = new WVMMediaSource(mSession, WV_EsSelector_Video, videoMetaData,
mIsLiveStream, mCryptoPluginMode);
// 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/wvm");
mHaveMetaData = true;
mInfoListener->configureHeartbeat();
if (mCryptoPluginMode) {
// 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);
mCryptoPluginMode = cryptoPluginMode;
}
bool WVMExtractorImpl::getCryptoPluginMode() const
{
//ALOGD("WVMExtractorImpl::getCryptoPluginMode - %d", mCryptoPluginMode);
return mCryptoPluginMode;
}
void WVMExtractorImpl::setUID(uid_t uid)
{
//ALOGD("WVMExtractorImpl::setUID(%d)", (uint32_t)uid);
mUID = uid;
mUIDIsSet = true;
}
} // namespace android