Merge "aaudio: fix SHARED MMAP mode in server plus other bugs" into oc-dev
diff --git a/include/media/omx/1.0/WGraphicBufferSource.h b/include/media/omx/1.0/WGraphicBufferSource.h
index 0ca5f44..397e576 100644
--- a/include/media/omx/1.0/WGraphicBufferSource.h
+++ b/include/media/omx/1.0/WGraphicBufferSource.h
@@ -67,14 +67,11 @@
struct LWGraphicBufferSource : public BnGraphicBufferSource {
sp<TGraphicBufferSource> mBase;
LWGraphicBufferSource(sp<TGraphicBufferSource> const& base);
- BnStatus configure(
- const sp<IOMXNode>& omxNode, int32_t dataSpace) override;
+ BnStatus configure(const sp<IOMXNode>& omxNode, int32_t dataSpace) override;
BnStatus setSuspend(bool suspend, int64_t timeUs) override;
- BnStatus setRepeatPreviousFrameDelayUs(
- int64_t repeatAfterUs) override;
+ BnStatus setRepeatPreviousFrameDelayUs(int64_t repeatAfterUs) override;
BnStatus setMaxFps(float maxFps) override;
- BnStatus setTimeLapseConfig(
- int64_t timePerFrameUs, int64_t timePerCaptureUs) override;
+ BnStatus setTimeLapseConfig(double fps, double captureFps) override;
BnStatus setStartTimeUs(int64_t startTimeUs) override;
BnStatus setStopTimeUs(int64_t stopTimeUs) override;
BnStatus setColorAspects(int32_t aspects) override;
diff --git a/media/libmedia/aidl/android/IGraphicBufferSource.aidl b/media/libmedia/aidl/android/IGraphicBufferSource.aidl
index 325c631..f3c7abc 100644
--- a/media/libmedia/aidl/android/IGraphicBufferSource.aidl
+++ b/media/libmedia/aidl/android/IGraphicBufferSource.aidl
@@ -28,10 +28,10 @@
void setSuspend(boolean suspend, long suspendTimeUs);
void setRepeatPreviousFrameDelayUs(long repeatAfterUs);
void setMaxFps(float maxFps);
- void setTimeLapseConfig(long timePerFrameUs, long timePerCaptureUs);
+ void setTimeLapseConfig(double fps, double captureFps);
void setStartTimeUs(long startTimeUs);
void setStopTimeUs(long stopTimeUs);
void setColorAspects(int aspects);
void setTimeOffsetUs(long timeOffsetsUs);
void signalEndOfInputStream();
-}
\ No newline at end of file
+}
diff --git a/media/libmedia/omx/1.0/WGraphicBufferSource.cpp b/media/libmedia/omx/1.0/WGraphicBufferSource.cpp
index b4e2975..4c543fa 100644
--- a/media/libmedia/omx/1.0/WGraphicBufferSource.cpp
+++ b/media/libmedia/omx/1.0/WGraphicBufferSource.cpp
@@ -53,9 +53,8 @@
}
BnStatus LWGraphicBufferSource::setTimeLapseConfig(
- int64_t timePerFrameUs, int64_t timePerCaptureUs) {
- return toBinderStatus(mBase->setTimeLapseConfig(
- timePerFrameUs, timePerCaptureUs));
+ double fps, double captureFps) {
+ return toBinderStatus(mBase->setTimeLapseConfig(fps, captureFps));
}
BnStatus LWGraphicBufferSource::setStartTimeUs(
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index 3b7c61b..e1d762f 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -163,7 +163,7 @@
// TBD mTrackEveryTimeDurationUs = 0;
mAnalyticsItem->setInt32(kRecorderCaptureFpsEnable, mCaptureFpsEnable);
mAnalyticsItem->setDouble(kRecorderCaptureFps, mCaptureFps);
- // TBD mTimeBetweenCaptureUs = -1;
+ // TBD mCaptureFps = -1.0;
// TBD mCameraSourceTimeLapse = NULL;
// TBD mMetaDataStoredInVideoBuffers = kMetadataBufferTypeInvalid;
// TBD mEncoderProfiles = MediaProfiles::getInstance();
@@ -709,18 +709,11 @@
status_t StagefrightRecorder::setParamCaptureFps(double fps) {
ALOGV("setParamCaptureFps: %.2f", fps);
- // FPS value is from Java layer where double follows IEEE-754, which is not
- // necessarily true for double here.
- constexpr double kIeee754Epsilon = 2.220446049250313e-16;
- constexpr double kEpsilon = std::max(std::numeric_limits<double>::epsilon(), kIeee754Epsilon);
- // Not allowing fps less than 1 frame / day minus epsilon.
- if (fps < 1.0 / 86400 - kEpsilon) {
- ALOGE("fps (%lf) is out of range (>= 1 frame / day)", fps);
+ if (!(fps >= 1.0 / 86400)) {
+ ALOGE("FPS is too small");
return BAD_VALUE;
}
-
mCaptureFps = fps;
- mTimeBetweenCaptureUs = std::llround(1e6 / fps);
return OK;
}
@@ -1574,16 +1567,15 @@
videoSize.width = mVideoWidth;
videoSize.height = mVideoHeight;
if (mCaptureFpsEnable) {
- if (mTimeBetweenCaptureUs < 0) {
- ALOGE("Invalid mTimeBetweenTimeLapseFrameCaptureUs value: %lld",
- (long long)mTimeBetweenCaptureUs);
+ if (!(mCaptureFps > 0.)) {
+ ALOGE("Invalid mCaptureFps value: %lf", mCaptureFps);
return BAD_VALUE;
}
mCameraSourceTimeLapse = CameraSourceTimeLapse::CreateFromCamera(
mCamera, mCameraProxy, mCameraId, mClientName, mClientUid, mClientPid,
videoSize, mFrameRate, mPreviewSurface,
- mTimeBetweenCaptureUs);
+ std::llround(1e6 / mCaptureFps));
*cameraSource = mCameraSourceTimeLapse;
} else {
*cameraSource = CameraSource::CreateFromCamera(
@@ -1679,12 +1671,11 @@
// set up time lapse/slow motion for surface source
if (mCaptureFpsEnable) {
- if (mTimeBetweenCaptureUs <= 0) {
- ALOGE("Invalid mTimeBetweenCaptureUs value: %lld",
- (long long)mTimeBetweenCaptureUs);
+ if (!(mCaptureFps > 0.)) {
+ ALOGE("Invalid mCaptureFps value: %lf", mCaptureFps);
return BAD_VALUE;
}
- format->setInt64("time-lapse", mTimeBetweenCaptureUs);
+ format->setDouble("time-lapse-fps", mCaptureFps);
}
}
@@ -2075,8 +2066,7 @@
mMaxFileSizeBytes = 0;
mTrackEveryTimeDurationUs = 0;
mCaptureFpsEnable = false;
- mCaptureFps = 0.0;
- mTimeBetweenCaptureUs = -1;
+ mCaptureFps = -1.0;
mCameraSourceTimeLapse = NULL;
mMetaDataStoredInVideoBuffers = kMetadataBufferTypeInvalid;
mEncoderProfiles = MediaProfiles::getInstance();
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index 63b9571..8b91541 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -546,8 +546,8 @@
mRepeatFrameDelayUs(-1ll),
mMaxPtsGapUs(-1ll),
mMaxFps(-1),
- mTimePerFrameUs(-1ll),
- mTimePerCaptureUs(-1ll),
+ mFps(-1.0),
+ mCaptureFps(-1.0),
mCreateInputBuffersSuspended(false),
mLatency(0),
mTunneled(false),
@@ -1802,8 +1802,8 @@
mMaxFps = -1;
}
- if (!msg->findInt64("time-lapse", &mTimePerCaptureUs)) {
- mTimePerCaptureUs = -1ll;
+ if (!msg->findDouble("time-lapse-fps", &mCaptureFps)) {
+ mCaptureFps = -1.0;
}
if (!msg->findInt32(
@@ -3739,17 +3739,18 @@
def.nBufferSize = (video_def->nStride * video_def->nSliceHeight * 3) / 2;
- float frameRate;
- if (!msg->findFloat("frame-rate", &frameRate)) {
+ float framerate;
+ if (!msg->findFloat("frame-rate", &framerate)) {
int32_t tmp;
if (!msg->findInt32("frame-rate", &tmp)) {
return INVALID_OPERATION;
}
- frameRate = (float)tmp;
- mTimePerFrameUs = (int64_t) (1000000.0f / frameRate);
+ mFps = (double)tmp;
+ } else {
+ mFps = (double)framerate;
}
- video_def->xFramerate = (OMX_U32)(frameRate * 65536.0f);
+ video_def->xFramerate = (OMX_U32)(mFps * 65536);
video_def->eCompressionFormat = OMX_VIDEO_CodingUnused;
// this is redundant as it was already set up in setVideoPortFormatType
// FIXME for now skip this only for flexible YUV formats
@@ -6597,11 +6598,10 @@
}
}
- if (mCodec->mTimePerCaptureUs > 0ll
- && mCodec->mTimePerFrameUs > 0ll) {
+ if (mCodec->mCaptureFps > 0. && mCodec->mFps > 0.) {
err = statusFromBinderStatus(
mCodec->mGraphicBufferSource->setTimeLapseConfig(
- mCodec->mTimePerFrameUs, mCodec->mTimePerCaptureUs));
+ mCodec->mFps, mCodec->mCaptureFps));
if (err != OK) {
ALOGE("[%s] Unable to configure time lapse (err %d)",
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 61b8f9d..372b11a 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -78,6 +78,7 @@
libaudioutils \
libbinder \
libcamera_client \
+ libcrypto \
libcutils \
libdl \
libdrmframework \
diff --git a/media/libstagefright/httplive/PlaylistFetcher.cpp b/media/libstagefright/httplive/PlaylistFetcher.cpp
index bbcea51..00cf142 100644
--- a/media/libstagefright/httplive/PlaylistFetcher.cpp
+++ b/media/libstagefright/httplive/PlaylistFetcher.cpp
@@ -26,6 +26,7 @@
#include "include/avc_utils.h"
#include "include/ID3.h"
#include "mpeg2ts/AnotherPacketSource.h"
+#include "mpeg2ts/HlsSampleDecryptor.h"
#include <media/stagefright/foundation/ABitReader.h>
#include <media/stagefright/foundation/ABuffer.h>
@@ -36,7 +37,6 @@
#include <ctype.h>
#include <inttypes.h>
-#include <openssl/aes.h>
#define FLOGV(fmt, ...) ALOGV("[fetcher-%d] " fmt, mFetcherID, ##__VA_ARGS__)
#define FSLOGV(stream, fmt, ...) ALOGV("[fetcher-%d] [%s] " fmt, mFetcherID, \
@@ -167,11 +167,15 @@
mFirstPTSValid(false),
mFirstTimeUs(-1ll),
mVideoBuffer(new AnotherPacketSource(NULL)),
+ mSampleAesKeyItemChanged(false),
mThresholdRatio(-1.0f),
mDownloadState(new DownloadState()),
mHasMetadata(false) {
memset(mPlaylistHash, 0, sizeof(mPlaylistHash));
mHTTPDownloader = mSession->getHTTPDownloader();
+
+ memset(mKeyData, 0, sizeof(mKeyData));
+ memset(mAESInitVec, 0, sizeof(mAESInitVec));
}
PlaylistFetcher::~PlaylistFetcher() {
@@ -306,6 +310,15 @@
}
}
+ // TODO: Revise this when we add support for KEYFORMAT
+ // If method has changed (e.g., -> NONE); sufficient to check at the segment boundary
+ if (mSampleAesKeyItem != NULL && first && found && method != "SAMPLE-AES") {
+ ALOGI("decryptBuffer: resetting mSampleAesKeyItem(%p) with method %s",
+ mSampleAesKeyItem.get(), method.c_str());
+ mSampleAesKeyItem = NULL;
+ mSampleAesKeyItemChanged = true;
+ }
+
if (!found) {
method = "NONE";
}
@@ -313,6 +326,8 @@
if (method == "NONE") {
return OK;
+ } else if (method == "SAMPLE-AES") {
+ ALOGV("decryptBuffer: Non-Widevine SAMPLE-AES is supported now.");
} else if (!(method == "AES-128")) {
ALOGE("Unsupported cipher method '%s'", method.c_str());
return ERROR_UNSUPPORTED;
@@ -345,6 +360,79 @@
mAESKeyForURI.add(keyURI, key);
}
+ if (first) {
+ // If decrypting the first block in a file, read the iv from the manifest
+ // or derive the iv from the file's sequence number.
+
+ unsigned char AESInitVec[AES_BLOCK_SIZE];
+ AString iv;
+ if (itemMeta->findString("cipher-iv", &iv)) {
+ if ((!iv.startsWith("0x") && !iv.startsWith("0X"))
+ || iv.size() > 16 * 2 + 2) {
+ ALOGE("malformed cipher IV '%s'.", iv.c_str());
+ return ERROR_MALFORMED;
+ }
+
+ while (iv.size() < 16 * 2 + 2) {
+ iv.insert("0", 1, 2);
+ }
+
+ memset(AESInitVec, 0, sizeof(AESInitVec));
+ for (size_t i = 0; i < 16; ++i) {
+ char c1 = tolower(iv.c_str()[2 + 2 * i]);
+ char c2 = tolower(iv.c_str()[3 + 2 * i]);
+ if (!isxdigit(c1) || !isxdigit(c2)) {
+ ALOGE("malformed cipher IV '%s'.", iv.c_str());
+ return ERROR_MALFORMED;
+ }
+ uint8_t nibble1 = isdigit(c1) ? c1 - '0' : c1 - 'a' + 10;
+ uint8_t nibble2 = isdigit(c2) ? c2 - '0' : c2 - 'a' + 10;
+
+ AESInitVec[i] = nibble1 << 4 | nibble2;
+ }
+ } else {
+ memset(AESInitVec, 0, sizeof(AESInitVec));
+ AESInitVec[15] = mSeqNumber & 0xff;
+ AESInitVec[14] = (mSeqNumber >> 8) & 0xff;
+ AESInitVec[13] = (mSeqNumber >> 16) & 0xff;
+ AESInitVec[12] = (mSeqNumber >> 24) & 0xff;
+ }
+
+ bool newKey = memcmp(mKeyData, key->data(), AES_BLOCK_SIZE) != 0;
+ bool newInitVec = memcmp(mAESInitVec, AESInitVec, AES_BLOCK_SIZE) != 0;
+ bool newSampleAesKeyItem = newKey || newInitVec;
+ ALOGV("decryptBuffer: SAMPLE-AES newKeyItem %d/%d (Key %d initVec %d)",
+ mSampleAesKeyItemChanged, newSampleAesKeyItem, newKey, newInitVec);
+
+ if (newSampleAesKeyItem) {
+ memcpy(mKeyData, key->data(), AES_BLOCK_SIZE);
+ memcpy(mAESInitVec, AESInitVec, AES_BLOCK_SIZE);
+
+ if (method == "SAMPLE-AES") {
+ mSampleAesKeyItemChanged = true;
+
+ sp<ABuffer> keyDataBuffer = ABuffer::CreateAsCopy(mKeyData, sizeof(mKeyData));
+ sp<ABuffer> initVecBuffer = ABuffer::CreateAsCopy(mAESInitVec, sizeof(mAESInitVec));
+
+ // always allocating a new one rather than updating the old message
+ // lower layer might still have a reference to the old message
+ mSampleAesKeyItem = new AMessage();
+ mSampleAesKeyItem->setBuffer("keyData", keyDataBuffer);
+ mSampleAesKeyItem->setBuffer("initVec", initVecBuffer);
+
+ ALOGV("decryptBuffer: New SampleAesKeyItem: Key: %s IV: %s",
+ HlsSampleDecryptor::aesBlockToStr(mKeyData).c_str(),
+ HlsSampleDecryptor::aesBlockToStr(mAESInitVec).c_str());
+ } // SAMPLE-AES
+ } // newSampleAesKeyItem
+ } // first
+
+ if (method == "SAMPLE-AES") {
+ ALOGV("decryptBuffer: skipping full-seg decrypt for SAMPLE-AES");
+ return OK;
+ }
+
+
AES_KEY aes_key;
if (AES_set_decrypt_key(key->data(), 128, &aes_key) != 0) {
ALOGE("failed to set AES decryption key.");
@@ -361,44 +449,6 @@
return ERROR_MALFORMED;
}
- if (first) {
- // If decrypting the first block in a file, read the iv from the manifest
- // or derive the iv from the file's sequence number.
-
- AString iv;
- if (itemMeta->findString("cipher-iv", &iv)) {
- if ((!iv.startsWith("0x") && !iv.startsWith("0X"))
- || iv.size() > 16 * 2 + 2) {
- ALOGE("malformed cipher IV '%s'.", iv.c_str());
- return ERROR_MALFORMED;
- }
-
- while (iv.size() < 16 * 2 + 2) {
- iv.insert("0", 1, 2);
- }
-
- memset(mAESInitVec, 0, sizeof(mAESInitVec));
- for (size_t i = 0; i < 16; ++i) {
- char c1 = tolower(iv.c_str()[2 + 2 * i]);
- char c2 = tolower(iv.c_str()[3 + 2 * i]);
- if (!isxdigit(c1) || !isxdigit(c2)) {
- ALOGE("malformed cipher IV '%s'.", iv.c_str());
- return ERROR_MALFORMED;
- }
- uint8_t nibble1 = isdigit(c1) ? c1 - '0' : c1 - 'a' + 10;
- uint8_t nibble2 = isdigit(c2) ? c2 - '0' : c2 - 'a' + 10;
-
- mAESInitVec[i] = nibble1 << 4 | nibble2;
- }
- } else {
- memset(mAESInitVec, 0, sizeof(mAESInitVec));
- mAESInitVec[15] = mSeqNumber & 0xff;
- mAESInitVec[14] = (mSeqNumber >> 8) & 0xff;
- mAESInitVec[13] = (mSeqNumber >> 16) & 0xff;
- mAESInitVec[12] = (mSeqNumber >> 24) & 0xff;
- }
- }
-
AES_cbc_encrypt(
buffer->data(), buffer->data(), buffer->size(),
&aes_key, mAESInitVec, AES_DECRYPT);
@@ -409,7 +459,7 @@
status_t PlaylistFetcher::checkDecryptPadding(const sp<ABuffer> &buffer) {
AString method;
CHECK(buffer->meta()->findString("cipher-method", &method));
- if (method == "NONE") {
+ if (method == "NONE" || method == "SAMPLE-AES") {
return OK;
}
@@ -1656,6 +1706,11 @@
mNextPTSTimeUs = -1ll;
}
+ if (mSampleAesKeyItemChanged) {
+ mTSParser->signalNewSampleAesKey(mSampleAesKeyItem);
+ mSampleAesKeyItemChanged = false;
+ }
+
size_t offset = 0;
while (offset + 188 <= buffer->size()) {
status_t err = mTSParser->feedTSPacket(buffer->data() + offset, 188);
@@ -2038,10 +2093,24 @@
}
}
+ sp<HlsSampleDecryptor> sampleDecryptor = NULL;
+ if (mSampleAesKeyItem != NULL) {
+ ALOGV("extractAndQueueAccessUnits[%d] SampleAesKeyItem: Key: %s IV: %s",
+ mSeqNumber,
+ HlsSampleDecryptor::aesBlockToStr(mKeyData).c_str(),
+ HlsSampleDecryptor::aesBlockToStr(mAESInitVec).c_str());
+
+ sampleDecryptor = new HlsSampleDecryptor(mSampleAesKeyItem);
+ }
+
+ int frameId = 0;
+
size_t offset = 0;
while (offset < buffer->size()) {
const uint8_t *adtsHeader = buffer->data() + offset;
CHECK_LT(offset + 5, buffer->size());
+ // non-const pointer for decryption if needed
+ uint8_t *adtsFrame = buffer->data() + offset;
unsigned aac_frame_length =
((adtsHeader[3] & 3) << 11)
@@ -2099,6 +2168,18 @@
}
}
+ if (sampleDecryptor != NULL) {
+ bool protection_absent = (adtsHeader[1] & 0x1);
+ size_t headerSize = protection_absent ? 7 : 9;
+ if (frameId == 0) {
+ ALOGV("extractAndQueueAAC[%d] protection_absent %d (%02x) headerSize %zu",
+ mSeqNumber, protection_absent, adtsHeader[1], headerSize);
+ }
+
+ sampleDecryptor->processAAC(headerSize, adtsFrame, aac_frame_length);
+ }
+ frameId++;
+
sp<ABuffer> unit = new ABuffer(aac_frame_length);
memcpy(unit->data(), adtsHeader, aac_frame_length);
diff --git a/media/libstagefright/httplive/PlaylistFetcher.h b/media/libstagefright/httplive/PlaylistFetcher.h
index ee7d3a1..d7db54a 100644
--- a/media/libstagefright/httplive/PlaylistFetcher.h
+++ b/media/libstagefright/httplive/PlaylistFetcher.h
@@ -19,6 +19,7 @@
#define PLAYLIST_FETCHER_H_
#include <media/stagefright/foundation/AHandler.h>
+#include <openssl/aes.h>
#include "mpeg2ts/ATSParser.h"
#include "LiveSession.h"
@@ -175,7 +176,10 @@
// Stores the initialization vector to decrypt the next block of cipher text, which can
// either be derived from the sequence number, read from the manifest, or copied from
// the last block of cipher text (cipher-block chaining).
- unsigned char mAESInitVec[16];
+ unsigned char mAESInitVec[AES_BLOCK_SIZE];
+ unsigned char mKeyData[AES_BLOCK_SIZE];
+ bool mSampleAesKeyItemChanged;
+ sp<AMessage> mSampleAesKeyItem;
Mutex mThresholdLock;
float mThresholdRatio;
diff --git a/media/libstagefright/include/ACodec.h b/media/libstagefright/include/ACodec.h
index 6c1a5c6..06ee0e8 100644
--- a/media/libstagefright/include/ACodec.h
+++ b/media/libstagefright/include/ACodec.h
@@ -293,8 +293,8 @@
int64_t mRepeatFrameDelayUs;
int64_t mMaxPtsGapUs;
float mMaxFps;
- int64_t mTimePerFrameUs;
- int64_t mTimePerCaptureUs;
+ double mFps;
+ double mCaptureFps;
bool mCreateInputBuffersSuspended;
uint32_t mLatency;
diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp
index 8099edb..31edb21 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.cpp
+++ b/media/libstagefright/mpeg2ts/ATSParser.cpp
@@ -105,6 +105,8 @@
void updateCasSessions();
+ void signalNewSampleAesKey(const sp<AMessage> &keyItem);
+
private:
struct StreamInfo {
unsigned mType;
@@ -119,6 +121,7 @@
bool mFirstPTSValid;
uint64_t mFirstPTS;
int64_t mLastRecoveredPTS;
+ sp<AMessage> mSampleAesKeyItem;
status_t parseProgramMap(ABitReader *br);
int64_t recoverPTS(uint64_t PTS_33bit);
@@ -168,6 +171,8 @@
bool isVideo() const;
bool isMeta() const;
+ void signalNewSampleAesKey(const sp<AMessage> &keyItem);
+
protected:
virtual ~Stream();
@@ -194,6 +199,8 @@
ElementaryStreamQueue *mQueue;
bool mScrambled;
+ bool mSampleEncrypted;
+ sp<AMessage> mSampleAesKeyItem;
sp<IMemory> mMem;
sp<MemoryDealer> mDealer;
sp<ABuffer> mDescrambledBuffer;
@@ -586,6 +593,10 @@
sp<Stream> stream = new Stream(
this, info.mPID, info.mType, PCR_PID, info.mCASystemId);
+ if (mSampleAesKeyItem != NULL) {
+ stream->signalNewSampleAesKey(mSampleAesKeyItem);
+ }
+
isAddingScrambledStream |= info.mCASystemId >= 0;
mStreams.add(info.mPID, stream);
}
@@ -710,22 +721,32 @@
mPrevPTS(0),
mQueue(NULL),
mScrambled(CA_system_ID >= 0) {
- ALOGV("new stream PID 0x%02x, type 0x%02x, scrambled %d",
- elementaryPID, streamType, mScrambled);
- uint32_t flags = (isVideo() && mScrambled) ?
- ElementaryStreamQueue::kFlag_ScrambledData : 0;
+ mSampleEncrypted =
+ mStreamType == STREAMTYPE_H264_ENCRYPTED ||
+ mStreamType == STREAMTYPE_AAC_ENCRYPTED ||
+ mStreamType == STREAMTYPE_AC3_ENCRYPTED;
+
+ ALOGV("new stream PID 0x%02x, type 0x%02x, scrambled %d, SampleEncrypted: %d",
+ elementaryPID, streamType, mScrambled, mSampleEncrypted);
+
+ uint32_t flags =
+ (isVideo() && mScrambled) ? ElementaryStreamQueue::kFlag_ScrambledData :
+ (mSampleEncrypted) ? ElementaryStreamQueue::kFlag_SampleEncryptedData :
+ 0;
ElementaryStreamQueue::Mode mode = ElementaryStreamQueue::INVALID;
switch (mStreamType) {
case STREAMTYPE_H264:
+ case STREAMTYPE_H264_ENCRYPTED:
mode = ElementaryStreamQueue::H264;
flags |= (mProgram->parserFlags() & ALIGNED_VIDEO_DATA) ?
ElementaryStreamQueue::kFlag_AlignedData : 0;
break;
case STREAMTYPE_MPEG2_AUDIO_ADTS:
+ case STREAMTYPE_AAC_ENCRYPTED:
mode = ElementaryStreamQueue::AAC;
break;
@@ -745,6 +766,7 @@
case STREAMTYPE_LPCM_AC3:
case STREAMTYPE_AC3:
+ case STREAMTYPE_AC3_ENCRYPTED:
mode = ElementaryStreamQueue::AC3;
break;
@@ -761,6 +783,10 @@
mQueue = new ElementaryStreamQueue(mode, flags);
if (mQueue != NULL) {
+ if (mSampleAesKeyItem != NULL) {
+ mQueue->signalNewSampleAesKey(mSampleAesKeyItem);
+ }
+
ensureBufferCapacity(kInitialStreamBufferSize);
if (mScrambled && (isAudio() || isVideo())) {
@@ -913,6 +939,7 @@
bool ATSParser::Stream::isVideo() const {
switch (mStreamType) {
case STREAMTYPE_H264:
+ case STREAMTYPE_H264_ENCRYPTED:
case STREAMTYPE_MPEG1_VIDEO:
case STREAMTYPE_MPEG2_VIDEO:
case STREAMTYPE_MPEG4_VIDEO:
@@ -930,6 +957,8 @@
case STREAMTYPE_MPEG2_AUDIO_ADTS:
case STREAMTYPE_LPCM_AC3:
case STREAMTYPE_AC3:
+ case STREAMTYPE_AAC_ENCRYPTED:
+ case STREAMTYPE_AC3_ENCRYPTED:
return true;
default:
@@ -1454,7 +1483,7 @@
mPrevPTS = PTS;
#endif
- ALOGV("onPayloadData mStreamType=0x%02x", mStreamType);
+ ALOGV("onPayloadData mStreamType=0x%02x size: %zu", mStreamType, size);
int64_t timeUs = 0ll; // no presentation timestamp available.
if (PTS_DTS_flags == 2 || PTS_DTS_flags == 3) {
@@ -1492,6 +1521,8 @@
}
mSource = new AnotherPacketSource(meta);
mSource->queueAccessUnit(accessUnit);
+ ALOGV("onPayloadData: created AnotherPacketSource PID 0x%08x of type 0x%02x",
+ mElementaryPID, mStreamType);
}
} else if (mQueue->getFormat() != NULL) {
// After a discontinuity we invalidate the queue's format
@@ -1730,6 +1761,9 @@
if (!found) {
mPrograms.push(
new Program(this, program_number, programMapPID, mLastRecoveredPTS));
+ if (mSampleAesKeyItem != NULL) {
+ mPrograms.top()->signalNewSampleAesKey(mSampleAesKeyItem);
+ }
}
if (mPSISections.indexOfKey(programMapPID) < 0) {
@@ -2228,4 +2262,40 @@
ALOGV("crc: %08x\n", crc);
return (crc == 0);
}
+
+// SAMPLE_AES key handling
+// TODO: Merge these to their respective class after Widevine-HLS
+void ATSParser::signalNewSampleAesKey(const sp<AMessage> &keyItem) {
+ ALOGD("signalNewSampleAesKey: %p", keyItem.get());
+
+ mSampleAesKeyItem = keyItem;
+
+ // a NULL key item will propagate to existing ElementaryStreamQueues
+ for (size_t i = 0; i < mPrograms.size(); ++i) {
+ mPrograms[i]->signalNewSampleAesKey(keyItem);
+ }
+}
+
+void ATSParser::Program::signalNewSampleAesKey(const sp<AMessage> &keyItem) {
+ ALOGD("Program::signalNewSampleAesKey: %p", keyItem.get());
+
+ mSampleAesKeyItem = keyItem;
+
+ // a NULL key item will propagate to existing ElementaryStreamQueues
+ for (size_t i = 0; i < mStreams.size(); ++i) {
+ mStreams[i]->signalNewSampleAesKey(keyItem);
+ }
+}
+
+void ATSParser::Stream::signalNewSampleAesKey(const sp<AMessage> &keyItem) {
+ ALOGD("Stream::signalNewSampleAesKey: 0x%04x size = %zu keyItem: %p",
+ mElementaryPID, mBuffer->size(), keyItem.get());
+
+ // a NULL key item will propagate to existing ElementaryStreamQueues
+ mSampleAesKeyItem = keyItem;
+
+ flush(NULL);
+ mQueue->signalNewSampleAesKey(keyItem);
+}
+
} // namespace android
diff --git a/media/libstagefright/mpeg2ts/ATSParser.h b/media/libstagefright/mpeg2ts/ATSParser.h
index 4a88713..374e011 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.h
+++ b/media/libstagefright/mpeg2ts/ATSParser.h
@@ -131,6 +131,8 @@
int64_t getFirstPTSTimeUs();
+ void signalNewSampleAesKey(const sp<AMessage> &keyItem);
+
enum {
// From ISO/IEC 13818-1: 2000 (E), Table 2-29
STREAMTYPE_RESERVED = 0x00,
@@ -149,6 +151,11 @@
// Stream type 0x83 is non-standard,
// it could be LPCM or TrueHD AC3
STREAMTYPE_LPCM_AC3 = 0x83,
+
+ //Sample Encrypted types
+ STREAMTYPE_H264_ENCRYPTED = 0xDB,
+ STREAMTYPE_AAC_ENCRYPTED = 0xCF,
+ STREAMTYPE_AC3_ENCRYPTED = 0xC1,
};
protected:
@@ -181,6 +188,8 @@
size_t mNumTSPacketsParsed;
+ sp<AMessage> mSampleAesKeyItem;
+
void parseProgramAssociationTable(ABitReader *br);
void parseProgramMap(ABitReader *br);
// Parse PES packet where br is pointing to. If the PES contains a sync
diff --git a/media/libstagefright/mpeg2ts/Android.mk b/media/libstagefright/mpeg2ts/Android.mk
index 5140e66..20acfe7 100644
--- a/media/libstagefright/mpeg2ts/Android.mk
+++ b/media/libstagefright/mpeg2ts/Android.mk
@@ -7,6 +7,7 @@
ATSParser.cpp \
CasManager.cpp \
ESQueue.cpp \
+ HlsSampleDecryptor.cpp \
MPEG2PSExtractor.cpp \
MPEG2TSExtractor.cpp \
@@ -18,7 +19,9 @@
LOCAL_SANITIZE := unsigned-integer-overflow signed-integer-overflow cfi
LOCAL_SANITIZE_DIAG := cfi
-LOCAL_SHARED_LIBRARIES := libmedia
+LOCAL_SHARED_LIBRARIES := \
+ libcrypto \
+ libmedia \
LOCAL_MODULE:= libstagefright_mpeg2ts
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index ae7ec77..f1b44ae 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.cpp
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -42,7 +42,15 @@
: mMode(mode),
mFlags(flags),
mEOSReached(false),
- mCASystemId(0) {
+ mCASystemId(0),
+ mAUIndex(0) {
+
+ ALOGV("ElementaryStreamQueue(%p) mode %x flags %x isScrambled %d isSampleEncrypted %d",
+ this, mode, flags, isScrambled(), isSampleEncrypted());
+
+ // Create the decryptor anyway since we don't know the use-case unless key is provided
+ // Won't decrypt if key info not available (e.g., scanner/extractor just parsing ts files)
+ mSampleDecryptor = isSampleEncrypted() ? new HlsSampleDecryptor : NULL;
}
sp<MetaData> ElementaryStreamQueue::getFormat() {
@@ -659,6 +667,9 @@
unsigned syncStartPos = 0; // in bytes
unsigned payloadSize = 0;
sp<MetaData> format = new MetaData;
+
+ ALOGV("dequeueAccessUnit_AC3[%d]: mBuffer %p(%zu)", mAUIndex, mBuffer->data(), mBuffer->size());
+
while (true) {
if (syncStartPos + 2 >= mBuffer->size()) {
return NULL;
@@ -671,6 +682,10 @@
if (payloadSize > 0) {
break;
}
+
+ ALOGV("dequeueAccessUnit_AC3[%d]: syncStartPos %u payloadSize %u",
+ mAUIndex, syncStartPos, payloadSize);
+
++syncStartPos;
}
@@ -683,14 +698,22 @@
mFormat = format;
}
- sp<ABuffer> accessUnit = new ABuffer(syncStartPos + payloadSize);
- memcpy(accessUnit->data(), mBuffer->data(), syncStartPos + payloadSize);
int64_t timeUs = fetchTimestamp(syncStartPos + payloadSize);
if (timeUs < 0ll) {
ALOGE("negative timeUs");
return NULL;
}
+
+ // Not decrypting if key info not available (e.g., scanner/extractor parsing ts files)
+ if (mSampleDecryptor != NULL) {
+ mSampleDecryptor->processAC3(mBuffer->data() + syncStartPos, payloadSize);
+ }
+ mAUIndex++;
+
+ sp<ABuffer> accessUnit = new ABuffer(syncStartPos + payloadSize);
+ memcpy(accessUnit->data(), mBuffer->data(), syncStartPos + payloadSize);
+
accessUnit->meta()->setInt64("timeUs", timeUs);
accessUnit->meta()->setInt32("isSync", 1);
@@ -791,6 +814,17 @@
return NULL;
}
+ ALOGV("dequeueAccessUnit_AAC[%d]: mBuffer %zu info.mLength %zu",
+ mAUIndex, mBuffer->size(), info.mLength);
+
+ struct ADTSPosition {
+ size_t offset;
+ size_t headerSize;
+ size_t length;
+ };
+
+ Vector<ADTSPosition> frames;
+
// The idea here is consume all AAC frames starting at offsets before
// info.mLength so we can assign a meaningful timestamp without
// having to interpolate.
@@ -811,7 +845,7 @@
return NULL;
}
bits.skipBits(3); // ID, layer
- bool protection_absent __unused = bits.getBits(1) != 0;
+ bool protection_absent = bits.getBits(1) != 0;
if (mFormat == NULL) {
unsigned profile = bits.getBits(2);
@@ -873,11 +907,36 @@
return NULL;
}
- size_t headerSize __unused = protection_absent ? 7 : 9;
+ size_t headerSize = protection_absent ? 7 : 9;
+
+ // tracking the frame positions first then decrypt only if an accessUnit to be generated
+ if (mSampleDecryptor != NULL) {
+ ADTSPosition frame = {
+ .offset = offset,
+ .headerSize = headerSize,
+ .length = aac_frame_length
+ };
+
+ frames.push(frame);
+ }
offset += aac_frame_length;
}
+ // Decrypting only if the loop didn't exit early and an accessUnit is about to be generated
+ // Not decrypting if key info not available (e.g., scanner/extractor parsing ts files)
+ if (mSampleDecryptor != NULL) {
+ for (size_t frameId = 0; frameId < frames.size(); frameId++) {
+ const ADTSPosition &frame = frames.itemAt(frameId);
+
+ mSampleDecryptor->processAAC(frame.headerSize,
+ mBuffer->data() + frame.offset, frame.length);
+// ALOGV("dequeueAccessUnitAAC[%zu]: while offset %zu headerSize %zu frame_len %zu",
+// frameId, frame.offset, frame.headerSize, frame.length);
+ }
+ }
+ mAUIndex++;
+
int64_t timeUs = fetchTimestamp(offset);
sp<ABuffer> accessUnit = new ABuffer(offset);
@@ -970,6 +1029,9 @@
size_t nalSize;
bool foundSlice = false;
bool foundIDR = false;
+
+ ALOGV("dequeueAccessUnit_H264[%d] %p/%zu", mAUIndex, data, size);
+
while ((err = getNextNALUnit(&data, &size, &nalStart, &nalSize)) == OK) {
if (nalSize == 0) continue;
@@ -981,6 +1043,7 @@
foundIDR = true;
}
if (foundSlice) {
+ //TODO: Shouldn't this have been called with nalSize-1?
ABitReader br(nalStart + 1, nalSize);
unsigned first_mb_in_slice = parseUE(&br);
@@ -1021,6 +1084,7 @@
size_t dstOffset = 0;
size_t seiIndex = 0;
+ size_t shrunkBytes = 0;
for (size_t i = 0; i < nals.size(); ++i) {
const NALPosition &pos = nals.itemAt(i);
@@ -1047,11 +1111,30 @@
memcpy(accessUnit->data() + dstOffset, "\x00\x00\x00\x01", 4);
- memcpy(accessUnit->data() + dstOffset + 4,
- mBuffer->data() + pos.nalOffset,
- pos.nalSize);
+ if (mSampleDecryptor != NULL && (nalType == 1 || nalType == 5)) {
+ uint8_t *nalData = mBuffer->data() + pos.nalOffset;
+ size_t newSize = mSampleDecryptor->processNal(nalData, pos.nalSize);
+ // Note: the data can shrink due to unescaping
+ memcpy(accessUnit->data() + dstOffset + 4,
+ nalData,
+ newSize);
+ dstOffset += newSize + 4;
- dstOffset += pos.nalSize + 4;
+ size_t thisShrunkBytes = pos.nalSize - newSize;
+ //ALOGV("dequeueAccessUnitH264[%d]: nalType: %d -> %zu (%zu)",
+ // nalType, (int)pos.nalSize, newSize, thisShrunkBytes);
+
+ shrunkBytes += thisShrunkBytes;
+ }
+ else {
+ memcpy(accessUnit->data() + dstOffset + 4,
+ mBuffer->data() + pos.nalOffset,
+ pos.nalSize);
+
+ dstOffset += pos.nalSize + 4;
+ //ALOGV("dequeueAccessUnitH264 [%d] %d @%d",
+ // nalType, (int)pos.nalSize, (int)pos.nalOffset);
+ }
}
#if !LOG_NDEBUG
@@ -1082,6 +1165,18 @@
mFormat = MakeAVCCodecSpecificData(accessUnit);
}
+ if (mSampleDecryptor != NULL && shrunkBytes > 0) {
+ size_t adjustedSize = accessUnit->size() - shrunkBytes;
+ ALOGV("dequeueAccessUnitH264[%d]: AU size adjusted %zu -> %zu",
+ mAUIndex, accessUnit->size(), adjustedSize);
+ accessUnit->setRange(0, adjustedSize);
+ }
+
+ ALOGV("dequeueAccessUnitH264[%d]: AU %p(%zu) dstOffset:%zu, nals:%zu, totalSize:%zu ",
+ mAUIndex, accessUnit->data(), accessUnit->size(),
+ dstOffset, nals.size(), totalSize);
+ mAUIndex++;
+
return accessUnit;
}
@@ -1612,4 +1707,15 @@
return accessUnit;
}
+void ElementaryStreamQueue::signalNewSampleAesKey(const sp<AMessage> &keyItem) {
+ if (mSampleDecryptor == NULL) {
+ ALOGE("signalNewSampleAesKey: Stream %x is not encrypted; keyItem: %p",
+ mMode, keyItem.get());
+ return;
+ }
+
+ mSampleDecryptor->signalNewSampleAesKey(keyItem);
+}
+
+
} // namespace android
diff --git a/media/libstagefright/mpeg2ts/ESQueue.h b/media/libstagefright/mpeg2ts/ESQueue.h
index 11e1af7..ffcb502 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.h
+++ b/media/libstagefright/mpeg2ts/ESQueue.h
@@ -19,11 +19,14 @@
#define ES_QUEUE_H_
#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AMessage.h>
#include <utils/Errors.h>
#include <utils/List.h>
#include <utils/RefBase.h>
#include <vector>
+#include "HlsSampleDecryptor.h"
+
namespace android {
struct ABuffer;
@@ -46,6 +49,7 @@
// Data appended to the queue is always at access unit boundaries.
kFlag_AlignedData = 1,
kFlag_ScrambledData = 2,
+ kFlag_SampleEncryptedData = 4,
};
explicit ElementaryStreamQueue(Mode mode, uint32_t flags = 0);
@@ -69,6 +73,8 @@
void setCasInfo(int32_t systemId, const std::vector<uint8_t> &sessionId);
+ void signalNewSampleAesKey(const sp<AMessage> &keyItem);
+
private:
struct RangeInfo {
int64_t mTimestampUs;
@@ -100,6 +106,13 @@
sp<MetaData> mFormat;
+ sp<HlsSampleDecryptor> mSampleDecryptor;
+ int mAUIndex;
+
+ bool isSampleEncrypted() const {
+ return (mFlags & kFlag_SampleEncryptedData) != 0;
+ }
+
sp<ABuffer> dequeueAccessUnitH264();
sp<ABuffer> dequeueAccessUnitAAC();
sp<ABuffer> dequeueAccessUnitAC3();
diff --git a/media/libstagefright/mpeg2ts/HlsSampleDecryptor.cpp b/media/libstagefright/mpeg2ts/HlsSampleDecryptor.cpp
new file mode 100644
index 0000000..e32f676
--- /dev/null
+++ b/media/libstagefright/mpeg2ts/HlsSampleDecryptor.cpp
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2017 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_NDEBUG 0
+#define LOG_TAG "HlsSampleDecryptor"
+
+#include "HlsSampleDecryptor.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/Utils.h>
+
+
+namespace android {
+
+HlsSampleDecryptor::HlsSampleDecryptor()
+ : mValidKeyInfo(false) {
+}
+
+HlsSampleDecryptor::HlsSampleDecryptor(const sp<AMessage> &sampleAesKeyItem)
+ : mValidKeyInfo(false) {
+
+ signalNewSampleAesKey(sampleAesKeyItem);
+}
+
+void HlsSampleDecryptor::signalNewSampleAesKey(const sp<AMessage> &sampleAesKeyItem) {
+
+ if (sampleAesKeyItem == NULL) {
+ mValidKeyInfo = false;
+ ALOGW("signalNewSampleAesKey: sampleAesKeyItem is NULL");
+ return;
+ }
+
+ sp<ABuffer> keyDataBuffer, initVecBuffer;
+ sampleAesKeyItem->findBuffer("keyData", &keyDataBuffer);
+ sampleAesKeyItem->findBuffer("initVec", &initVecBuffer);
+
+ if (keyDataBuffer != NULL && keyDataBuffer->size() == AES_BLOCK_SIZE &&
+ initVecBuffer != NULL && initVecBuffer->size() == AES_BLOCK_SIZE) {
+
+ ALOGV("signalNewSampleAesKey: Key: %s IV: %s",
+ aesBlockToStr(keyDataBuffer->data()).c_str(),
+ aesBlockToStr(initVecBuffer->data()).c_str());
+
+ uint8_t KeyData[AES_BLOCK_SIZE];
+ memcpy(KeyData, keyDataBuffer->data(), AES_BLOCK_SIZE);
+ memcpy(mAESInitVec, initVecBuffer->data(), AES_BLOCK_SIZE);
+
+ mValidKeyInfo = (AES_set_decrypt_key(KeyData, 8*AES_BLOCK_SIZE/*128*/, &mAesKey) == 0);
+ if (!mValidKeyInfo) {
+ ALOGE("signalNewSampleAesKey: failed to set AES decryption key.");
+ }
+
+ } else {
+ // Media scanner might try extract/parse the TS files without knowing the key.
+ // Otherwise, shouldn't get here (unless an invalid playlist has swaped SAMPLE-AES with
+ // NONE method while still sample-encrypted stream is parsed).
+
+ mValidKeyInfo = false;
+ ALOGE("signalNewSampleAesKey Can't decrypt; keyDataBuffer: %p(%zu) initVecBuffer: %p(%zu)",
+ keyDataBuffer.get(), (keyDataBuffer.get() == NULL)? -1 : keyDataBuffer->size(),
+ initVecBuffer.get(), (initVecBuffer.get() == NULL)? -1 : initVecBuffer->size());
+ }
+}
+
+size_t HlsSampleDecryptor::processNal(uint8_t *nalData, size_t nalSize) {
+
+ unsigned nalType = nalData[0] & 0x1f;
+ if (!mValidKeyInfo) {
+ ALOGV("processNal[%d]: (%p)/%zu Skipping due to invalid key", nalType, nalData, nalSize);
+ return nalSize;
+ }
+
+ bool isEncrypted = (nalSize > VIDEO_CLEAR_LEAD + AES_BLOCK_SIZE);
+ ALOGV("processNal[%d]: (%p)/%zu isEncrypted: %d", nalType, nalData, nalSize, isEncrypted);
+
+ if (isEncrypted) {
+ // Encrypted NALUs have extra start code emulation prevention that must be
+ // stripped out before we can decrypt it.
+ size_t newSize = unescapeStream(nalData, nalSize);
+
+ ALOGV("processNal:unescapeStream[%d]: %zu -> %zu", nalType, nalSize, newSize);
+ nalSize = newSize;
+
+ //Encrypted_nal_unit () {
+ // nal_unit_type_byte // 1 byte
+ // unencrypted_leader // 31 bytes
+ // while (bytes_remaining() > 0) {
+ // if (bytes_remaining() > 16) {
+ // encrypted_block // 16 bytes
+ // }
+ // unencrypted_block // MIN(144, bytes_remaining()) bytes
+ // }
+ //}
+
+ size_t offset = VIDEO_CLEAR_LEAD;
+ size_t remainingBytes = nalSize - VIDEO_CLEAR_LEAD;
+
+ // a copy of initVec as decryptBlock updates it
+ unsigned char AESInitVec[AES_BLOCK_SIZE];
+ memcpy(AESInitVec, mAESInitVec, AES_BLOCK_SIZE);
+
+ while (remainingBytes > 0) {
+ // encrypted_block: protected block uses 10% skip encryption
+ if (remainingBytes > AES_BLOCK_SIZE) {
+ uint8_t *encrypted = nalData + offset;
+ status_t ret = decryptBlock(encrypted, AES_BLOCK_SIZE, AESInitVec);
+ if (ret != OK) {
+ ALOGE("processNal failed with %d", ret);
+ return nalSize; // revisit this
+ }
+
+ offset += AES_BLOCK_SIZE;
+ remainingBytes -= AES_BLOCK_SIZE;
+ }
+
+ // unencrypted_block
+ size_t clearBytes = std::min(remainingBytes, (size_t)(9 * AES_BLOCK_SIZE));
+
+ offset += clearBytes;
+ remainingBytes -= clearBytes;
+ } // while
+
+ } else { // isEncrypted == false
+ ALOGV("processNal[%d]: Unencrypted NALU (%p)/%zu", nalType, nalData, nalSize);
+ }
+
+ return nalSize;
+}
+
+void HlsSampleDecryptor::processAAC(size_t adtsHdrSize, uint8_t *data, size_t size) {
+
+ if (!mValidKeyInfo) {
+ ALOGV("processAAC: (%p)/%zu Skipping due to invalid key", data, size);
+ return;
+ }
+
+ // ADTS header is included in the size
+ size_t offset = adtsHdrSize;
+ size_t remainingBytes = size - adtsHdrSize;
+
+ bool isEncrypted = (remainingBytes >= AUDIO_CLEAR_LEAD + AES_BLOCK_SIZE);
+ ALOGV("processAAC: header: %zu data: %p(%zu) isEncrypted: %d",
+ adtsHdrSize, data, size, isEncrypted);
+
+ //Encrypted_AAC_Frame () {
+ // ADTS_Header // 7 or 9 bytes
+ // unencrypted_leader // 16 bytes
+ // while (bytes_remaining() >= 16) {
+ // encrypted_block // 16 bytes
+ // }
+ // unencrypted_trailer // 0-15 bytes
+ //}
+
+ // with lead bytes
+ if (remainingBytes >= AUDIO_CLEAR_LEAD) {
+ offset += AUDIO_CLEAR_LEAD;
+ remainingBytes -= AUDIO_CLEAR_LEAD;
+
+ // encrypted_block
+ if (remainingBytes >= AES_BLOCK_SIZE) {
+
+ size_t encryptedBytes = (remainingBytes / AES_BLOCK_SIZE) * AES_BLOCK_SIZE;
+ unsigned char AESInitVec[AES_BLOCK_SIZE];
+ memcpy(AESInitVec, mAESInitVec, AES_BLOCK_SIZE);
+
+ // decrypting all blocks at once
+ uint8_t *encrypted = data + offset;
+ status_t ret = decryptBlock(encrypted, encryptedBytes, AESInitVec);
+ if (ret != OK) {
+ ALOGE("processAAC: decryptBlock failed with %d", ret);
+ return;
+ }
+
+ offset += encryptedBytes;
+ remainingBytes -= encryptedBytes;
+ } // encrypted
+
+ // unencrypted_trailer
+ size_t clearBytes = remainingBytes;
+ if (clearBytes > 0) {
+ CHECK(clearBytes < AES_BLOCK_SIZE);
+ }
+
+ } else { // without lead bytes
+ ALOGV("processAAC: Unencrypted frame (without lead bytes) size %zu = %zu (hdr) + %zu (rem)",
+ size, adtsHdrSize, remainingBytes);
+ }
+
+}
+
+void HlsSampleDecryptor::processAC3(uint8_t *data, size_t size) {
+
+ if (!mValidKeyInfo) {
+ ALOGV("processAC3: (%p)/%zu Skipping due to invalid key", data, size);
+ return;
+ }
+
+ bool isEncrypted = (size >= AUDIO_CLEAR_LEAD + AES_BLOCK_SIZE);
+ ALOGV("processAC3 %p(%zu) isEncrypted: %d", data, size, isEncrypted);
+
+ //Encrypted_AC3_Frame () {
+ // unencrypted_leader // 16 bytes
+ // while (bytes_remaining() >= 16) {
+ // encrypted_block // 16 bytes
+ // }
+ // unencrypted_trailer // 0-15 bytes
+ //}
+
+ if (size >= AUDIO_CLEAR_LEAD) {
+ // unencrypted_leader
+ size_t offset = AUDIO_CLEAR_LEAD;
+ size_t remainingBytes = size - AUDIO_CLEAR_LEAD;
+
+ if (remainingBytes >= AES_BLOCK_SIZE) {
+
+ size_t encryptedBytes = (remainingBytes / AES_BLOCK_SIZE) * AES_BLOCK_SIZE;
+
+ // encrypted_block
+ unsigned char AESInitVec[AES_BLOCK_SIZE];
+ memcpy(AESInitVec, mAESInitVec, AES_BLOCK_SIZE);
+
+ // decrypting all blocks at once
+ uint8_t *encrypted = data + offset;
+ status_t ret = decryptBlock(encrypted, encryptedBytes, AESInitVec);
+ if (ret != OK) {
+ ALOGE("processAC3: decryptBlock failed with %d", ret);
+ return;
+ }
+
+ offset += encryptedBytes;
+ remainingBytes -= encryptedBytes;
+ } // encrypted
+
+ // unencrypted_trailer
+ size_t clearBytes = remainingBytes;
+ if (clearBytes > 0) {
+ CHECK(clearBytes < AES_BLOCK_SIZE);
+ }
+
+ } else {
+ ALOGV("processAC3: Unencrypted frame (without lead bytes) size %zu", size);
+ }
+}
+
+// Unescapes data replacing occurrences of [0, 0, 3] with [0, 0] and returns the new size
+size_t HlsSampleDecryptor::unescapeStream(uint8_t *data, size_t limit) const {
+ Vector<size_t> scratchEscapePositions;
+ size_t position = 0;
+
+ while (position < limit) {
+ position = findNextUnescapeIndex(data, position, limit);
+ if (position < limit) {
+ scratchEscapePositions.add(position);
+ position += 3;
+ }
+ }
+
+ size_t scratchEscapeCount = scratchEscapePositions.size();
+ size_t escapedPosition = 0; // The position being read from.
+ size_t unescapedPosition = 0; // The position being written to.
+ for (size_t i = 0; i < scratchEscapeCount; i++) {
+ size_t nextEscapePosition = scratchEscapePositions[i];
+ //TODO: add 2 and get rid of the later = 0 assignments
+ size_t copyLength = nextEscapePosition - escapedPosition;
+ memmove(data+unescapedPosition, data+escapedPosition, copyLength);
+ unescapedPosition += copyLength;
+ data[unescapedPosition++] = 0;
+ data[unescapedPosition++] = 0;
+ escapedPosition += copyLength + 3;
+ }
+
+ size_t unescapedLength = limit - scratchEscapeCount;
+ size_t remainingLength = unescapedLength - unescapedPosition;
+ memmove(data+unescapedPosition, data+escapedPosition, remainingLength);
+
+ return unescapedLength;
+}
+
+size_t HlsSampleDecryptor::findNextUnescapeIndex(uint8_t *data, size_t offset, size_t limit) const {
+ for (size_t i = offset; i < limit - 2; i++) {
+ //TODO: speed
+ if (data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x03) {
+ return i;
+ }
+ }
+ return limit;
+}
+
+status_t HlsSampleDecryptor::decryptBlock(uint8_t *buffer, size_t size,
+ uint8_t AESInitVec[AES_BLOCK_SIZE]) {
+ if (size == 0) {
+ return OK;
+ }
+
+ if ((size % AES_BLOCK_SIZE) != 0) {
+ ALOGE("decryptBlock: size (%zu) not a multiple of block size", size);
+ return ERROR_MALFORMED;
+ }
+
+ ALOGV("decryptBlock: %p (%zu)", buffer, size);
+
+ AES_cbc_encrypt(buffer, buffer, size, &mAesKey, AESInitVec, AES_DECRYPT);
+
+ return OK;
+}
+
+AString HlsSampleDecryptor::aesBlockToStr(uint8_t block[AES_BLOCK_SIZE]) {
+ AString result;
+
+ if (block == NULL) {
+ result = AString("null");
+ } else {
+ result = AStringPrintf("0x%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
+ block[0], block[1], block[2], block[3], block[4], block[5], block[6], block[7],
+ block[8], block[9], block[10], block[11], block[12], block[13], block[14], block[15]);
+ }
+
+ return result;
+}
+
+
+} // namespace android
diff --git a/media/libstagefright/mpeg2ts/HlsSampleDecryptor.h b/media/libstagefright/mpeg2ts/HlsSampleDecryptor.h
new file mode 100644
index 0000000..2c76620
--- /dev/null
+++ b/media/libstagefright/mpeg2ts/HlsSampleDecryptor.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef SAMPLE_AES_PROCESSOR_H_
+
+#define SAMPLE_AES_PROCESSOR_H_
+
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/AString.h>
+
+#include <openssl/aes.h>
+
+#include <utils/Errors.h>
+#include <utils/List.h>
+#include <utils/RefBase.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+struct HlsSampleDecryptor : RefBase {
+
+ HlsSampleDecryptor();
+ explicit HlsSampleDecryptor(const sp<AMessage> &sampleAesKeyItem);
+
+ void signalNewSampleAesKey(const sp<AMessage> &sampleAesKeyItem);
+
+ size_t processNal(uint8_t *nalData, size_t nalSize);
+ void processAAC(size_t adtsHdrSize, uint8_t *data, size_t size);
+ void processAC3(uint8_t *data, size_t size);
+
+ static AString aesBlockToStr(uint8_t block[AES_BLOCK_SIZE]);
+
+private:
+ size_t unescapeStream(uint8_t *data, size_t limit) const;
+ size_t findNextUnescapeIndex(uint8_t *data, size_t offset, size_t limit) const;
+ status_t decryptBlock(uint8_t *buffer, size_t size, uint8_t AESInitVec[AES_BLOCK_SIZE]);
+
+ static const int VIDEO_CLEAR_LEAD = 32;
+ static const int AUDIO_CLEAR_LEAD = 16;
+
+ AES_KEY mAesKey;
+ uint8_t mAESInitVec[AES_BLOCK_SIZE];
+ bool mValidKeyInfo;
+
+ DISALLOW_EVIL_CONSTRUCTORS(HlsSampleDecryptor);
+};
+
+} // namespace android
+
+#endif // SAMPLE_AES_PROCESSOR_H_
diff --git a/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp b/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
index ebca879..e876306 100644
--- a/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
+++ b/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
@@ -192,8 +192,8 @@
}
Return<Status> TWGraphicBufferSource::setTimeLapseConfig(
- int64_t timePerFrameUs, int64_t timePerCaptureUs) {
- return toStatus(mBase->setTimeLapseConfig(timePerFrameUs, timePerCaptureUs));
+ double fps, double captureFps) {
+ return toStatus(mBase->setTimeLapseConfig(fps, captureFps));
}
Return<Status> TWGraphicBufferSource::setStartTimeUs(int64_t startTimeUs) {
diff --git a/media/libstagefright/omx/1.0/WGraphicBufferSource.h b/media/libstagefright/omx/1.0/WGraphicBufferSource.h
index 8f822b8..4549c97 100644
--- a/media/libstagefright/omx/1.0/WGraphicBufferSource.h
+++ b/media/libstagefright/omx/1.0/WGraphicBufferSource.h
@@ -78,8 +78,7 @@
Return<Status> setSuspend(bool suspend, int64_t timeUs) override;
Return<Status> setRepeatPreviousFrameDelayUs(int64_t repeatAfterUs) override;
Return<Status> setMaxFps(float maxFps) override;
- Return<Status> setTimeLapseConfig(
- int64_t timePerFrameUs, int64_t timePerCaptureUs) override;
+ Return<Status> setTimeLapseConfig(double fps, double captureFps) override;
Return<Status> setStartTimeUs(int64_t startTimeUs) override;
Return<Status> setStopTimeUs(int64_t stopTimeUs) override;
Return<void> getStopTimeOffsetUs(getStopTimeOffsetUs_cb _hidl_cb) override;
diff --git a/media/libstagefright/omx/BWGraphicBufferSource.cpp b/media/libstagefright/omx/BWGraphicBufferSource.cpp
index 4e0f6dd..f2a454f 100644
--- a/media/libstagefright/omx/BWGraphicBufferSource.cpp
+++ b/media/libstagefright/omx/BWGraphicBufferSource.cpp
@@ -145,9 +145,9 @@
}
::android::binder::Status BWGraphicBufferSource::setTimeLapseConfig(
- int64_t timePerFrameUs, int64_t timePerCaptureUs) {
+ double fps, double captureFps) {
return Status::fromStatusT(mBase->setTimeLapseConfig(
- timePerFrameUs, timePerCaptureUs));
+ fps, captureFps));
}
::android::binder::Status BWGraphicBufferSource::setStartTimeUs(
diff --git a/media/libstagefright/omx/BWGraphicBufferSource.h b/media/libstagefright/omx/BWGraphicBufferSource.h
index f1ce2af..43763c2 100644
--- a/media/libstagefright/omx/BWGraphicBufferSource.h
+++ b/media/libstagefright/omx/BWGraphicBufferSource.h
@@ -50,7 +50,7 @@
int64_t repeatAfterUs) override;
Status setMaxFps(float maxFps) override;
Status setTimeLapseConfig(
- int64_t timePerFrameUs, int64_t timePerCaptureUs) override;
+ double fps, double captureFps) override;
Status setStartTimeUs(int64_t startTimeUs) override;
Status setStopTimeUs(int64_t stopTimeUs) override;
Status setColorAspects(int32_t aspects) override;
diff --git a/media/libstagefright/omx/GraphicBufferSource.cpp b/media/libstagefright/omx/GraphicBufferSource.cpp
index 5257b50..0521460 100644
--- a/media/libstagefright/omx/GraphicBufferSource.cpp
+++ b/media/libstagefright/omx/GraphicBufferSource.cpp
@@ -42,6 +42,7 @@
#include <functional>
#include <memory>
+#include <cmath>
namespace android {
@@ -270,8 +271,11 @@
mRepeatLastFrameGeneration(0),
mOutstandingFrameRepeatCount(0),
mFrameRepeatBlockedOnCodecBuffer(false),
- mTimePerCaptureUs(-1ll),
- mTimePerFrameUs(-1ll),
+ mFps(-1.0),
+ mCaptureFps(-1.0),
+ mBaseCaptureUs(-1ll),
+ mBaseFrameUs(-1ll),
+ mFrameCount(0),
mPrevCaptureUs(-1ll),
mPrevFrameUs(-1ll),
mInputBufferTimeOffsetUs(0ll) {
@@ -713,26 +717,31 @@
int64_t timeUs = bufferTimeNs / 1000;
timeUs += mInputBufferTimeOffsetUs;
- if (mTimePerCaptureUs > 0ll
- && (mTimePerCaptureUs > 2 * mTimePerFrameUs
- || mTimePerFrameUs > 2 * mTimePerCaptureUs)) {
+ if (mCaptureFps > 0.
+ && (mFps > 2 * mCaptureFps
+ || mCaptureFps > 2 * mFps)) {
// Time lapse or slow motion mode
if (mPrevCaptureUs < 0ll) {
// first capture
- mPrevCaptureUs = timeUs;
+ mPrevCaptureUs = mBaseCaptureUs = timeUs;
// adjust the first sample timestamp.
- mPrevFrameUs = (timeUs * mTimePerFrameUs) / mTimePerCaptureUs;
+ mPrevFrameUs = mBaseFrameUs =
+ std::llround((timeUs * mCaptureFps) / mFps);
+ mFrameCount = 0;
} else {
// snap to nearest capture point
- int64_t nFrames = (timeUs + mTimePerCaptureUs / 2 - mPrevCaptureUs)
- / mTimePerCaptureUs;
+ int64_t nFrames = std::llround(
+ (timeUs - mPrevCaptureUs) * mCaptureFps);
if (nFrames <= 0) {
// skip this frame as it's too close to previous capture
ALOGV("skipping frame, timeUs %lld", static_cast<long long>(timeUs));
return false;
}
- mPrevCaptureUs += mTimePerCaptureUs * nFrames;
- mPrevFrameUs += mTimePerFrameUs * nFrames;
+ mFrameCount += nFrames;
+ mPrevCaptureUs = mBaseCaptureUs + std::llround(
+ mFrameCount / mCaptureFps);
+ mPrevFrameUs = mBaseFrameUs + std::llround(
+ mFrameCount / mFps);
}
ALOGV("timeUs %lld, captureUs %lld, frameUs %lld",
@@ -1054,10 +1063,13 @@
mOutstandingFrameRepeatCount = 0;
mLatestBuffer.mBuffer.reset();
mFrameRepeatBlockedOnCodecBuffer = false;
- mTimePerCaptureUs = -1ll;
- mTimePerFrameUs = -1ll;
+ mFps = -1.0;
+ mCaptureFps = -1.0;
+ mBaseCaptureUs = -1ll;
+ mBaseFrameUs = -1ll;
mPrevCaptureUs = -1ll;
mPrevFrameUs = -1ll;
+ mFrameCount = 0;
mInputBufferTimeOffsetUs = 0;
mStopTimeUs = -1;
mActionQueue.clear();
@@ -1202,18 +1214,18 @@
return OK;
}
-status_t GraphicBufferSource::setTimeLapseConfig(int64_t timePerFrameUs, int64_t timePerCaptureUs) {
- ALOGV("setTimeLapseConfig: timePerFrameUs=%lld, timePerCaptureUs=%lld",
- (long long)timePerFrameUs, (long long)timePerCaptureUs);
+status_t GraphicBufferSource::setTimeLapseConfig(double fps, double captureFps) {
+ ALOGV("setTimeLapseConfig: fps=%lg, captureFps=%lg",
+ fps, captureFps);
Mutex::Autolock autoLock(mMutex);
- if (mExecuting || timePerFrameUs <= 0ll || timePerCaptureUs <= 0ll) {
+ if (mExecuting || !(fps > 0) || !(captureFps > 0)) {
return INVALID_OPERATION;
}
- mTimePerFrameUs = timePerFrameUs;
- mTimePerCaptureUs = timePerCaptureUs;
+ mFps = fps;
+ mCaptureFps = captureFps;
return OK;
}
diff --git a/media/libstagefright/omx/GraphicBufferSource.h b/media/libstagefright/omx/GraphicBufferSource.h
index 635cfd6..3df1aa1 100644
--- a/media/libstagefright/omx/GraphicBufferSource.h
+++ b/media/libstagefright/omx/GraphicBufferSource.h
@@ -162,7 +162,7 @@
// Sets the time lapse (or slow motion) parameters.
// When set, the sample's timestamp will be modified to playback framerate,
// and capture timestamp will be modified to capture rate.
- status_t setTimeLapseConfig(int64_t timePerFrameUs, int64_t timePerCaptureUs);
+ status_t setTimeLapseConfig(double fps, double captureFps);
// Sets the start time us (in system time), samples before which should
// be dropped and not submitted to encoder
@@ -417,27 +417,39 @@
// Time lapse / slow motion configuration
// --------------------------------------
- // desired time interval between captured frames (capture interval) - value <= 0 if undefined
- int64_t mTimePerCaptureUs;
+ // desired frame rate for encoding - value <= 0 if undefined
+ double mFps;
- // desired time interval between encoded frames (media time interval) - value <= 0 if undefined
- int64_t mTimePerFrameUs;
+ // desired frame rate for capture - value <= 0 if undefined
+ double mCaptureFps;
- // Time lapse mode is enabled if capture interval is defined and it is more than twice the
- // media time interval (if defined). In this mode frames that come in between the capture
- // interval are dropped and the media timestamp is adjusted to have exactly the desired
- // media time interval.
+ // Time lapse mode is enabled if the capture frame rate is defined and it is
+ // smaller than half the encoding frame rate (if defined). In this mode,
+ // frames that come in between the capture interval (the reciprocal of the
+ // capture frame rate) are dropped and the encoding timestamp is adjusted to
+ // match the desired encoding frame rate.
//
- // Slow motion mode is enabled if both media and capture intervals are defined and the media
- // time interval is more than twice the capture interval. In this mode frames that come in
- // between the capture interval are dropped (though there isn't expected to be any, but there
- // could eventually be a frame drop if the actual capture interval is smaller than the
- // configured capture interval). The media timestamp is adjusted to have exactly the desired
- // media time interval.
+ // Slow motion mode is enabled if both encoding and capture frame rates are
+ // defined and the encoding frame rate is less than half the capture frame
+ // rate. In this mode, the source is expected to produce frames with an even
+ // timestamp interval (after rounding) with the configured capture fps. The
+ // first source timestamp is used as the source base time. Afterwards, the
+ // timestamp of each source frame is snapped to the nearest expected capture
+ // timestamp and scaled to match the configured encoding frame rate.
// These modes must be enabled before using this source.
- // adjusted capture timestamp for previous frame
+ // adjusted capture timestamp of the base frame
+ int64_t mBaseCaptureUs;
+
+ // adjusted encoding timestamp of the base frame
+ int64_t mBaseFrameUs;
+
+ // number of frames from the base time
+ int64_t mFrameCount;
+
+ // adjusted capture timestamp for previous frame (negative if there were
+ // none)
int64_t mPrevCaptureUs;
// adjusted media timestamp for previous frame (negative if there were none)
diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
index eaffad8..8b76a97 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.h
+++ b/services/camera/libcameraservice/device3/Camera3Device.h
@@ -294,9 +294,7 @@
size_t result = 1;
result = 31 * result + buf->numFds;
- result = 31 * result + buf->numInts;
- int length = buf->numFds + buf->numInts;
- for (int i = 0; i < length; i++) {
+ for (int i = 0; i < buf->numFds; i++) {
result = 31 * result + buf->data[i];
}
return result;
@@ -305,9 +303,8 @@
struct BufferComparator {
bool operator()(const buffer_handle_t& buf1, const buffer_handle_t& buf2) const {
- if (buf1->numFds == buf2->numFds && buf1->numInts == buf2->numInts) {
- int length = buf1->numFds + buf1->numInts;
- for (int i = 0; i < length; i++) {
+ if (buf1->numFds == buf2->numFds) {
+ for (int i = 0; i < buf1->numFds; i++) {
if (buf1->data[i] != buf2->data[i]) {
return false;
}