/* * 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 #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 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) { return new WVMExtractorImpl(dataSource); } WVMExtractorImpl::WVMExtractorImpl(sp 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 = WV_Initialize(NULL); 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); mIsLiveStream = (mDataSource->getUri().getPathExtension().find(".m3u8") == 0); } 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; } mInfoListener->setSession(mSession); } WV_SetWarningToErrorMS(5000); } WVMExtractorImpl::~WVMExtractorImpl() { } // // 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 audioMetaData = new MetaData(); sp 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 = readAVCCMetaData(videoMetaData); if (status != OK) return 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::readAVCCMetaData(sp videoMetaData) { 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 video data. But we can't use it yet, so just request 0 bytes. // (void)WV_GetEsData(mSession, WV_EsSelector_Video, buf, bufSize, bytesRead, auStart, dts, pts, sync); result = WV_Info_GetCodecConfig(mSession, WV_CodecConfigType_AVCC, 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 AVCC returned error %d\n", result); return ERROR_IO; } #if 0 char *filename = "/data/wvm/avcc"; FILE *f = fopen(filename, "w"); if (!f) LOGD("Failed to open %s", filename); else { fwrite(config, size, 1, f); fclose(f); } #endif videoMetaData->setData(kKeyAVCC, kTypeAVCC, config, size); return OK; } status_t WVMExtractorImpl::readESDSMetaData(sp 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 WVMExtractorImpl::getTrack(size_t index) { status_t err; if ((err = readMetaData()) != OK) { return NULL; } sp result; switch(index) { case 0: result = mVideoSource; break; case 1: result = mAudioSource; break; default: break; } return result; } sp WVMExtractorImpl::getTrackMetaData(size_t index, uint32_t flags) { status_t err; if ((err = readMetaData()) != OK) { return NULL; } sp 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 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; uint64_t durationUs = 0; WVStatus status = WV_Info_GetAdaptiveBitrates(mSession, encodedRates, kMaxEncodedRates, &ratesReturned, ¤tTrack); if (status == WV_Status_OK) { if (currentTrack == kMaxEncodedRates || ratesReturned == 0) { *finalStatus = ERROR_IO; } else { unsigned long long bytesBuffered; status = WV_Info_BytesBuffered(mSession, &bytesBuffered); if (status == WV_Status_OK) { *finalStatus = OK; durationUs = 8000000LL * bytesBuffered / encodedRates[currentTrack]; } else if (status == WV_Status_End_Of_Media) { *finalStatus = ERROR_END_OF_STREAM; } // Log the current adaptive rate every 5 seconds // FIXME: need to see if there is a way to send this info to the app 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]); } } } else { *finalStatus = ERROR_IO; } // 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