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;
                         }