Loading media/libstagefright/Android.mk +2 −1 Original line number Original line Diff line number Diff line Loading @@ -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 \ Loading media/libstagefright/AwesomePlayer.cpp +14 −2 Original line number Original line Diff line number Diff line Loading @@ -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 Loading Loading @@ -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; } } Loading Loading @@ -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. Loading Loading @@ -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; Loading media/libstagefright/httplive/Android.mk +2 −1 Original line number Original line Diff line number Diff line Loading @@ -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 Loading media/libstagefright/httplive/LiveSource.cpp +262 −39 Original line number Original line Diff line number Diff line Loading @@ -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 { Loading @@ -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; Loading @@ -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 { Loading @@ -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(); Loading Loading @@ -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; } } Loading Loading @@ -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; } } Loading @@ -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)); Loading @@ -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; } } Loading Loading @@ -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( Loading @@ -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; Loading Loading @@ -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; } } Loading media/libstagefright/httplive/M3UParser.cpp +56 −0 Original line number Original line Diff line number Diff line Loading @@ -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")) { Loading Loading @@ -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 Loading
media/libstagefright/Android.mk +2 −1 Original line number Original line Diff line number Diff line Loading @@ -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 \ Loading
media/libstagefright/AwesomePlayer.cpp +14 −2 Original line number Original line Diff line number Diff line Loading @@ -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 Loading Loading @@ -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; } } Loading Loading @@ -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. Loading Loading @@ -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; Loading
media/libstagefright/httplive/Android.mk +2 −1 Original line number Original line Diff line number Diff line Loading @@ -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 Loading
media/libstagefright/httplive/LiveSource.cpp +262 −39 Original line number Original line Diff line number Diff line Loading @@ -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 { Loading @@ -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; Loading @@ -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 { Loading @@ -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(); Loading Loading @@ -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; } } Loading Loading @@ -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; } } Loading @@ -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)); Loading @@ -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; } } Loading Loading @@ -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( Loading @@ -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; Loading Loading @@ -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; } } Loading
media/libstagefright/httplive/M3UParser.cpp +56 −0 Original line number Original line Diff line number Diff line Loading @@ -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")) { Loading Loading @@ -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