An uninitialized return code was causing intermittent playback failure during Widevine HLS live playback. This change supports vendors doing Widevine integration on their devices from the ICS code base. It has been confirmed to resolve the HLS streaming issue by 3 vendors. Change-Id: Ib5bd1aa92f577a0e759e11cb154359686943a903 related-to-bug: 6277231
475 lines
14 KiB
C++
475 lines
14 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 "WVMExtractorImpl"
|
|
#include <utils/Log.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, length);
|
|
|
|
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);
|
|
}
|
|
|
|
WVMExtractorImpl::WVMExtractorImpl(sp<DataSource> dataSource)
|
|
: mFileMetaData(new MetaData()),
|
|
mDataSource(dataSource),
|
|
mHaveMetaData(false),
|
|
mUseAdaptiveStreaming(false),
|
|
mIsLiveStream(false),
|
|
mSession(NULL),
|
|
mSetupStatus(OK)
|
|
{
|
|
dataSource->getDrmInfo(sDecryptHandle, &sDrmManagerClient);
|
|
|
|
//LOGD("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()
|
|
{
|
|
//LOGD("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) {
|
|
WVCallbacks callbacks = {NULL, NULL, NULL, NULL, NULL, NULL, WVMMediaSource::DecryptCallback};
|
|
result = WV_Initialize(&callbacks);
|
|
} else {
|
|
result = WV_Initialize(NULL);
|
|
}
|
|
#else
|
|
result = WV_Initialize(NULL);
|
|
#endif
|
|
|
|
if (result != WV_Status_OK) {
|
|
LOGE("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);
|
|
} 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) {
|
|
LOGE("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();
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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:
|
|
LOGE("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:
|
|
LOGE("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) {
|
|
LOGE("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) {
|
|
LOGE("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 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) {
|
|
LOGE("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)
|
|
LOGD("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, ¤tTrack);
|
|
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;
|
|
LOGI("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)
|
|
{
|
|
//LOGD("WVMExtractorImpl::setAdaptiveStreamingMode(%d)", adaptive);
|
|
mUseAdaptiveStreaming = adaptive;
|
|
}
|
|
|
|
bool WVMExtractorImpl::getAdaptiveStreamingMode() const
|
|
{
|
|
//LOGD("WVMExtractorImpl::getAdaptiveStreamingMode - %d", mUseAdaptiveStreaming);
|
|
return mUseAdaptiveStreaming;
|
|
}
|
|
|
|
} // namespace android
|
|
|