Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f9c2d338 authored by Andreas Huber's avatar Andreas Huber Committed by Android (Google) Code Review
Browse files

Merge "More HTTP live support, AES encryption etc."

parents d42c40fd e332a918
Loading
Loading
Loading
Loading
+2 −1
Original line number Original line Diff line number Diff line
@@ -68,7 +68,8 @@ LOCAL_SHARED_LIBRARIES := \
        libsurfaceflinger_client \
        libsurfaceflinger_client \
        libstagefright_yuv \
        libstagefright_yuv \
        libcamera_client \
        libcamera_client \
        libdrmframework
        libdrmframework  \
        libcrypto


LOCAL_STATIC_LIBRARIES := \
LOCAL_STATIC_LIBRARIES := \
        libstagefright_aacdec \
        libstagefright_aacdec \
+14 −2
Original line number Original line Diff line number Diff line
@@ -49,6 +49,8 @@


#include <media/stagefright/foundation/ALooper.h>
#include <media/stagefright/foundation/ALooper.h>


#define USE_SURFACE_ALLOC 1

namespace android {
namespace android {


static int64_t kLowWaterMarkUs = 2000000ll;  // 2secs
static int64_t kLowWaterMarkUs = 2000000ll;  // 2secs
@@ -294,6 +296,16 @@ status_t AwesomePlayer::setDataSource_l(


    mUri = uri;
    mUri = uri;


    if (!strncmp("http://", uri, 7)) {
        // Hack to support http live.

        size_t len = strlen(uri);
        if (!strcasecmp(&uri[len - 5], ".m3u8")) {
            mUri = "httplive://";
            mUri.append(&uri[7]);
        }
    }

    if (headers) {
    if (headers) {
        mUriHeaders = *headers;
        mUriHeaders = *headers;
    }
    }
@@ -873,7 +885,7 @@ void AwesomePlayer::initRenderer_l() {
        IPCThreadState::self()->flushCommands();
        IPCThreadState::self()->flushCommands();


        if (mSurface != NULL) {
        if (mSurface != NULL) {
            if (strncmp(component, "OMX.", 4) == 0) {
            if (USE_SURFACE_ALLOC && strncmp(component, "OMX.", 4) == 0) {
                // Hardware decoders avoid the CPU color conversion by decoding
                // Hardware decoders avoid the CPU color conversion by decoding
                // directly to ANativeBuffers, so we must use a renderer that
                // directly to ANativeBuffers, so we must use a renderer that
                // just pushes those buffers to the ANativeWindow.
                // just pushes those buffers to the ANativeWindow.
@@ -1143,7 +1155,7 @@ status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {
            mClient.interface(), mVideoTrack->getFormat(),
            mClient.interface(), mVideoTrack->getFormat(),
            false, // createEncoder
            false, // createEncoder
            mVideoTrack,
            mVideoTrack,
            NULL, flags, mSurface);
            NULL, flags, USE_SURFACE_ALLOC ? mSurface : NULL);


    if (mVideoSource != NULL) {
    if (mVideoSource != NULL) {
        int64_t durationUs;
        int64_t durationUs;
+2 −1
Original line number Original line Diff line number Diff line
@@ -9,7 +9,8 @@ LOCAL_SRC_FILES:= \
LOCAL_C_INCLUDES:= \
LOCAL_C_INCLUDES:= \
	$(JNI_H_INCLUDE) \
	$(JNI_H_INCLUDE) \
	$(TOP)/frameworks/base/include/media/stagefright/openmax \
	$(TOP)/frameworks/base/include/media/stagefright/openmax \
        $(TOP)/frameworks/base/media/libstagefright
        $(TOP)/frameworks/base/media/libstagefright \
        $(TOP)/external/openssl/include


LOCAL_MODULE:= libstagefright_httplive
LOCAL_MODULE:= libstagefright_httplive


+262 −39
Original line number Original line Diff line number Diff line
@@ -22,9 +22,14 @@
#include "include/M3UParser.h"
#include "include/M3UParser.h"
#include "include/NuHTTPDataSource.h"
#include "include/NuHTTPDataSource.h"


#include <cutils/properties.h>
#include <media/stagefright/foundation/hexdump.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/FileSource.h>
#include <media/stagefright/FileSource.h>
#include <media/stagefright/MediaDebug.h>

#include <ctype.h>
#include <openssl/aes.h>


namespace android {
namespace android {


@@ -38,7 +43,9 @@ LiveSource::LiveSource(const char *url)
      mSourceSize(0),
      mSourceSize(0),
      mOffsetBias(0),
      mOffsetBias(0),
      mSignalDiscontinuity(false),
      mSignalDiscontinuity(false),
      mPrevBandwidthIndex(-1) {
      mPrevBandwidthIndex(-1),
      mAESKey((AES_KEY *)malloc(sizeof(AES_KEY))),
      mStreamEncrypted(false) {
    if (switchToNext()) {
    if (switchToNext()) {
        mInitCheck = OK;
        mInitCheck = OK;


@@ -47,6 +54,8 @@ LiveSource::LiveSource(const char *url)
}
}


LiveSource::~LiveSource() {
LiveSource::~LiveSource() {
    free(mAESKey);
    mAESKey = NULL;
}
}


status_t LiveSource::initCheck() const {
status_t LiveSource::initCheck() const {
@@ -68,7 +77,77 @@ static double uniformRand() {
    return (double)rand() / RAND_MAX;
    return (double)rand() / RAND_MAX;
}
}


bool LiveSource::loadPlaylist(bool fetchMaster) {
size_t LiveSource::getBandwidthIndex() {
    if (mBandwidthItems.size() == 0) {
        return 0;
    }

#if 1
    int32_t bandwidthBps;
    if (mSource != NULL && mSource->estimateBandwidth(&bandwidthBps)) {
        LOGI("bandwidth estimated at %.2f kbps", bandwidthBps / 1024.0f);
    } else {
        LOGI("no bandwidth estimate.");
        return 0;  // Pick the lowest bandwidth stream by default.
    }

    char value[PROPERTY_VALUE_MAX];
    if (property_get("media.httplive.max-bw", value, NULL)) {
        char *end;
        long maxBw = strtoul(value, &end, 10);
        if (end > value && *end == '\0') {
            if (maxBw > 0 && bandwidthBps > maxBw) {
                LOGV("bandwidth capped to %ld bps", maxBw);
                bandwidthBps = maxBw;
            }
        }
    }

    // Consider only 80% of the available bandwidth usable.
    bandwidthBps = (bandwidthBps * 8) / 10;

    // Pick the highest bandwidth stream below or equal to estimated bandwidth.

    size_t index = mBandwidthItems.size() - 1;
    while (index > 0 && mBandwidthItems.itemAt(index).mBandwidth
                            > (size_t)bandwidthBps) {
        --index;
    }
#elif 0
    // Change bandwidth at random()
    size_t index = uniformRand() * mBandwidthItems.size();
#elif 0
    // There's a 50% chance to stay on the current bandwidth and
    // a 50% chance to switch to the next higher bandwidth (wrapping around
    // to lowest)
    const size_t kMinIndex = 0;

    size_t index;
    if (mPrevBandwidthIndex < 0) {
        index = kMinIndex;
    } else if (uniformRand() < 0.5) {
        index = (size_t)mPrevBandwidthIndex;
    } else {
        index = mPrevBandwidthIndex + 1;
        if (index == mBandwidthItems.size()) {
            index = kMinIndex;
        }
    }
#elif 0
    // Pick the highest bandwidth stream below or equal to 1.2 Mbit/sec

    size_t index = mBandwidthItems.size() - 1;
    while (index > 0 && mBandwidthItems.itemAt(index).mBandwidth > 1200000) {
        --index;
    }
#else
    size_t index = mBandwidthItems.size() - 1;  // Highest bandwidth stream
#endif

    return index;
}

bool LiveSource::loadPlaylist(bool fetchMaster, size_t bandwidthIndex) {
    mSignalDiscontinuity = false;
    mSignalDiscontinuity = false;


    mPlaylist.clear();
    mPlaylist.clear();
@@ -112,49 +191,35 @@ bool LiveSource::loadPlaylist(bool fetchMaster) {


            mBandwidthItems.sort(SortByBandwidth);
            mBandwidthItems.sort(SortByBandwidth);


#if 1  // XXX
            if (mBandwidthItems.size() > 1) {
                // Remove the lowest bandwidth stream, this is sometimes
                // an AAC program stream, which we don't support at this point.
                mBandwidthItems.removeItemsAt(0);
            }
#endif

            for (size_t i = 0; i < mBandwidthItems.size(); ++i) {
            for (size_t i = 0; i < mBandwidthItems.size(); ++i) {
                const BandwidthItem &item = mBandwidthItems.itemAt(i);
                const BandwidthItem &item = mBandwidthItems.itemAt(i);
                LOGV("item #%d: %s", i, item.mURI.c_str());
                LOGV("item #%d: %s", i, item.mURI.c_str());
            }
            }
        }
    }


    if (mBandwidthItems.size() > 0) {
            bandwidthIndex = getBandwidthIndex();
#if 0
        // Change bandwidth at random()
        size_t index = uniformRand() * mBandwidthItems.size();
#elif 0
        // There's a 50% chance to stay on the current bandwidth and
        // a 50% chance to switch to the next higher bandwidth (wrapping around
        // to lowest)
        size_t index;
        if (uniformRand() < 0.5) {
            index = mPrevBandwidthIndex < 0 ? 0 : (size_t)mPrevBandwidthIndex;
        } else {
            if (mPrevBandwidthIndex < 0) {
                index = 0;
            } else {
                index = mPrevBandwidthIndex + 1;
                if (index == mBandwidthItems.size()) {
                    index = 0;
        }
        }
    }
    }
        }
#else
        // Stay on the lowest bandwidth available.
        size_t index = mBandwidthItems.size() - 1;  // Highest bandwidth stream
#endif


        mURL = mBandwidthItems.editItemAt(index).mURI;
    if (mBandwidthItems.size() > 0) {
        mURL = mBandwidthItems.editItemAt(bandwidthIndex).mURI;


        if (mPrevBandwidthIndex >= 0 && (size_t)mPrevBandwidthIndex != index) {
        if (mPrevBandwidthIndex >= 0
                && (size_t)mPrevBandwidthIndex != bandwidthIndex) {
            // If we switched streams because of bandwidth changes,
            // If we switched streams because of bandwidth changes,
            // we'll signal this discontinuity by inserting a
            // we'll signal this discontinuity by inserting a
            // special transport stream packet into the stream.
            // special transport stream packet into the stream.
            mSignalDiscontinuity = true;
            mSignalDiscontinuity = true;
        }
        }


        mPrevBandwidthIndex = index;
        mPrevBandwidthIndex = bandwidthIndex;
    } else {
    } else {
        mURL = mMasterURL;
        mURL = mMasterURL;
    }
    }
@@ -199,12 +264,15 @@ bool LiveSource::switchToNext() {
    mOffsetBias += mSourceSize;
    mOffsetBias += mSourceSize;
    mSourceSize = 0;
    mSourceSize = 0;


    size_t bandwidthIndex = getBandwidthIndex();

    if (mLastFetchTimeUs < 0 || getNowUs() >= mLastFetchTimeUs + 15000000ll
    if (mLastFetchTimeUs < 0 || getNowUs() >= mLastFetchTimeUs + 15000000ll
        || mPlaylistIndex == mPlaylist->size()) {
        || mPlaylistIndex == mPlaylist->size()
        || (ssize_t)bandwidthIndex != mPrevBandwidthIndex) {
        int32_t nextSequenceNumber =
        int32_t nextSequenceNumber =
            mPlaylistIndex + mFirstItemSequenceNumber;
            mPlaylistIndex + mFirstItemSequenceNumber;


        if (!loadPlaylist(mLastFetchTimeUs < 0)) {
        if (!loadPlaylist(mLastFetchTimeUs < 0, bandwidthIndex)) {
            LOGE("failed to reload playlist");
            LOGE("failed to reload playlist");
            return false;
            return false;
        }
        }
@@ -227,6 +295,10 @@ bool LiveSource::switchToNext() {
        mLastFetchTimeUs = getNowUs();
        mLastFetchTimeUs = getNowUs();
    }
    }


    if (!setupCipher()) {
        return false;
    }

    AString uri;
    AString uri;
    sp<AMessage> itemMeta;
    sp<AMessage> itemMeta;
    CHECK(mPlaylist->itemAt(mPlaylistIndex, &uri, &itemMeta));
    CHECK(mPlaylist->itemAt(mPlaylistIndex, &uri, &itemMeta));
@@ -243,6 +315,121 @@ bool LiveSource::switchToNext() {
    }
    }


    mPlaylistIndex++;
    mPlaylistIndex++;

    return true;
}

bool LiveSource::setupCipher() {
    sp<AMessage> itemMeta;
    bool found = false;
    AString method;

    for (ssize_t i = mPlaylistIndex; i >= 0; --i) {
        AString uri;
        CHECK(mPlaylist->itemAt(i, &uri, &itemMeta));

        if (itemMeta->findString("cipher-method", &method)) {
            found = true;
            break;
        }
    }

    if (!found) {
        method = "NONE";
    }

    mStreamEncrypted = false;

    if (method == "AES-128") {
        AString keyURI;
        if (!itemMeta->findString("cipher-uri", &keyURI)) {
            LOGE("Missing key uri");
            return false;
        }

        if (keyURI.size() >= 2
                && keyURI.c_str()[0] == '"'
                && keyURI.c_str()[keyURI.size() - 1] == '"') {
            // Remove surrounding quotes.
            AString tmp(keyURI, 1, keyURI.size() - 2);
            keyURI = tmp;
        }

        ssize_t index = mAESKeyForURI.indexOfKey(keyURI);

        sp<ABuffer> key;
        if (index >= 0) {
            key = mAESKeyForURI.valueAt(index);
        } else {
            key = new ABuffer(16);

            sp<NuHTTPDataSource> keySource = new NuHTTPDataSource;
            status_t err = keySource->connect(keyURI.c_str());

            if (err == OK) {
                size_t offset = 0;
                while (offset < 16) {
                    ssize_t n = keySource->readAt(
                            offset, key->data() + offset, 16 - offset);
                    if (n <= 0) {
                        err = ERROR_IO;
                        break;
                    }

                    offset += n;
                }
            }

            if (err != OK) {
                LOGE("failed to fetch cipher key from '%s'.", keyURI.c_str());
                return false;
            }

            mAESKeyForURI.add(keyURI, key);
        }

        if (AES_set_decrypt_key(key->data(), 128, (AES_KEY *)mAESKey) != 0) {
            LOGE("failed to set AES decryption key.");
            return false;
        }

        AString iv;
        if (itemMeta->findString("cipher-iv", &iv)) {
            if ((!iv.startsWith("0x") && !iv.startsWith("0X"))
                    || iv.size() != 16 * 2 + 2) {
                LOGE("malformed cipher IV '%s'.", iv.c_str());
                return false;
            }

            memset(mAESIVec, 0, sizeof(mAESIVec));
            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)) {
                    LOGE("malformed cipher IV '%s'.", iv.c_str());
                    return false;
                }
                uint8_t nibble1 = isdigit(c1) ? c1 - '0' : c1 - 'a' + 10;
                uint8_t nibble2 = isdigit(c2) ? c2 - '0' : c2 - 'a' + 10;

                mAESIVec[i] = nibble1 << 4 | nibble2;
            }
        } else {
            size_t seqNum = mPlaylistIndex + mFirstItemSequenceNumber;

            memset(mAESIVec, 0, sizeof(mAESIVec));
            mAESIVec[15] = seqNum & 0xff;
            mAESIVec[14] = (seqNum >> 8) & 0xff;
            mAESIVec[13] = (seqNum >> 16) & 0xff;
            mAESIVec[12] = (seqNum >> 24) & 0xff;
        }

        mStreamEncrypted = true;
    } else if (!(method == "NONE")) {
        LOGE("Unsupported cipher method '%s'", method.c_str());
        return false;
    }

    return true;
    return true;
}
}


@@ -279,6 +466,7 @@ ssize_t LiveSource::readAt(off_t offset, void *data, size_t size) {
        return avail;
        return avail;
    }
    }


    bool done = false;
    size_t numRead = 0;
    size_t numRead = 0;
    while (numRead < size) {
    while (numRead < size) {
        ssize_t n = mSource->readAt(
        ssize_t n = mSource->readAt(
@@ -289,7 +477,44 @@ ssize_t LiveSource::readAt(off_t offset, void *data, size_t size) {
            break;
            break;
        }
        }


        if (mStreamEncrypted) {
            size_t nmod = n % 16;
            CHECK(nmod == 0);

            sp<ABuffer> tmp = new ABuffer(n);

            AES_cbc_encrypt((const unsigned char *)data + numRead,
                            tmp->data(),
                            n,
                            (const AES_KEY *)mAESKey,
                            mAESIVec,
                            AES_DECRYPT);

            if (mSourceSize == (off_t)(offset + numRead - delta + n)) {
                // check for padding at the end of the file.

                size_t pad = tmp->data()[n - 1];
                CHECK_GT(pad, 0u);
                CHECK_LE(pad, 16u);
                CHECK_GE((size_t)n, pad);
                for (size_t i = 0; i < pad; ++i) {
                    CHECK_EQ((unsigned)tmp->data()[n - 1 - i], pad);
                }

                n -= pad;
                mSourceSize -= pad;

                done = true;
            }

            memcpy((uint8_t *)data + numRead, tmp->data(), n);
        }

        numRead += n;
        numRead += n;

        if (done) {
            break;
        }
    }
    }


    return numRead;
    return numRead;
@@ -359,19 +584,17 @@ bool LiveSource::seekTo(int64_t seekTimeUs) {
        return false;
        return false;
    }
    }


    size_t newPlaylistIndex = mFirstItemSequenceNumber + index;
    if (index == mPlaylistIndex) {

    if (newPlaylistIndex == mPlaylistIndex) {
        return false;
        return false;
    }
    }


    mPlaylistIndex = newPlaylistIndex;
    mPlaylistIndex = index;

    LOGV("seeking to index %lld", index);


    switchToNext();
    switchToNext();
    mOffsetBias = 0;
    mOffsetBias = 0;


    LOGV("seeking to index %lld", index);

    return true;
    return true;
}
}


+56 −0
Original line number Original line Diff line number Diff line
@@ -158,6 +158,11 @@ status_t M3UParser::parse(const void *_data, size_t size) {
                    return ERROR_MALFORMED;
                    return ERROR_MALFORMED;
                }
                }
                err = parseMetaData(line, &mMeta, "media-sequence");
                err = parseMetaData(line, &mMeta, "media-sequence");
            } else if (line.startsWith("#EXT-X-KEY")) {
                if (mIsVariantPlaylist) {
                    return ERROR_MALFORMED;
                }
                err = parseCipherInfo(line, &itemMeta);
            } else if (line.startsWith("#EXT-X-ENDLIST")) {
            } else if (line.startsWith("#EXT-X-ENDLIST")) {
                mIsComplete = true;
                mIsComplete = true;
            } else if (line.startsWith("#EXTINF")) {
            } else if (line.startsWith("#EXTINF")) {
@@ -291,6 +296,57 @@ status_t M3UParser::parseStreamInf(
    return OK;
    return OK;
}
}


// static
status_t M3UParser::parseCipherInfo(
        const AString &line, sp<AMessage> *meta) {
    ssize_t colonPos = line.find(":");

    if (colonPos < 0) {
        return ERROR_MALFORMED;
    }

    size_t offset = colonPos + 1;

    while (offset < line.size()) {
        ssize_t end = line.find(",", offset);
        if (end < 0) {
            end = line.size();
        }

        AString attr(line, offset, end - offset);
        attr.trim();

        offset = end + 1;

        ssize_t equalPos = attr.find("=");
        if (equalPos < 0) {
            continue;
        }

        AString key(attr, 0, equalPos);
        key.trim();

        AString val(attr, equalPos + 1, attr.size() - equalPos - 1);
        val.trim();

        LOGV("key=%s value=%s", key.c_str(), val.c_str());

        key.tolower();

        if (key == "method" || key == "uri" || key == "iv") {
            if (meta->get() == NULL) {
                *meta = new AMessage;
            }

            key.insert(AString("cipher-"), 0);

            (*meta)->setString(key.c_str(), val.c_str(), val.size());
        }
    }

    return OK;
}

// static
// static
status_t M3UParser::ParseInt32(const char *s, int32_t *x) {
status_t M3UParser::ParseInt32(const char *s, int32_t *x) {
    char *end;
    char *end;
Loading