Loading media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp +1 −1 Original line number Diff line number Diff line Loading @@ -658,7 +658,7 @@ status_t NuPlayer::Decoder::fetchInputData(sp<AMessage> &reply) { #if 0 int64_t mediaTimeUs; CHECK(accessUnit->meta()->findInt64("timeUs", &mediaTimeUs)); ALOGV("[%s] feeding input buffer at media time %" PRId64, ALOGV("[%s] feeding input buffer at media time %.3f", mIsAudio ? "audio" : "video", mediaTimeUs / 1E6); #endif Loading media/libstagefright/httplive/Android.mk +1 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ HTTPDownloader.cpp \ LiveDataSource.cpp \ LiveSession.cpp \ M3UParser.cpp \ Loading media/libstagefright/httplive/HTTPDownloader.cpp 0 → 100644 +273 −0 Original line number Diff line number Diff line /* * Copyright 2015 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 "HTTPDownloader" #include <utils/Log.h> #include "HTTPDownloader.h" #include "M3UParser.h" #include <media/IMediaHTTPConnection.h> #include <media/IMediaHTTPService.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/MediaHTTP.h> #include <media/stagefright/DataSource.h> #include <media/stagefright/FileSource.h> #include <openssl/aes.h> #include <openssl/md5.h> #include <utils/Mutex.h> namespace android { HTTPDownloader::HTTPDownloader( const sp<IMediaHTTPService> &httpService, const KeyedVector<String8, String8> &headers) : mHTTPDataSource(new MediaHTTP(httpService->makeHTTPConnection())), mExtraHeaders(headers), mDisconnecting(false) { } void HTTPDownloader::reconnect() { AutoMutex _l(mLock); mDisconnecting = false; } void HTTPDownloader::disconnect() { { AutoMutex _l(mLock); mDisconnecting = true; } mHTTPDataSource->disconnect(); } bool HTTPDownloader::isDisconnecting() { AutoMutex _l(mLock); return mDisconnecting; } /* * Illustration of parameters: * * 0 `range_offset` * +------------+-------------------------------------------------------+--+--+ * | | | next block to fetch | | | * | | `source` handle => `out` buffer | | | | * | `url` file |<--------- buffer size --------->|<--- `block_size` -->| | | * | |<----------- `range_length` / buffer capacity ----------->| | * |<------------------------------ file_size ------------------------------->| * * Special parameter values: * - range_length == -1 means entire file * - block_size == 0 means entire range * */ ssize_t HTTPDownloader::fetchBlock( const char *url, sp<ABuffer> *out, int64_t range_offset, int64_t range_length, uint32_t block_size, /* download block size */ String8 *actualUrl, bool reconnect /* force connect HTTP when resuing source */) { if (isDisconnecting()) { return ERROR_NOT_CONNECTED; } off64_t size; if (reconnect) { if (!strncasecmp(url, "file://", 7)) { mDataSource = new FileSource(url + 7); } else if (strncasecmp(url, "http://", 7) && strncasecmp(url, "https://", 8)) { return ERROR_UNSUPPORTED; } else { KeyedVector<String8, String8> headers = mExtraHeaders; if (range_offset > 0 || range_length >= 0) { headers.add( String8("Range"), String8( AStringPrintf( "bytes=%lld-%s", range_offset, range_length < 0 ? "" : AStringPrintf("%lld", range_offset + range_length - 1).c_str()).c_str())); } status_t err = mHTTPDataSource->connect(url, &headers); if (isDisconnecting()) { return ERROR_NOT_CONNECTED; } if (err != OK) { return err; } mDataSource = mHTTPDataSource; } } status_t getSizeErr = mDataSource->getSize(&size); if (isDisconnecting()) { return ERROR_NOT_CONNECTED; } if (getSizeErr != OK) { size = 65536; } sp<ABuffer> buffer = *out != NULL ? *out : new ABuffer(size); if (*out == NULL) { buffer->setRange(0, 0); } ssize_t bytesRead = 0; // adjust range_length if only reading partial block if (block_size > 0 && (range_length == -1 || (int64_t)(buffer->size() + block_size) < range_length)) { range_length = buffer->size() + block_size; } for (;;) { // Only resize when we don't know the size. size_t bufferRemaining = buffer->capacity() - buffer->size(); if (bufferRemaining == 0 && getSizeErr != OK) { size_t bufferIncrement = buffer->size() / 2; if (bufferIncrement < 32768) { bufferIncrement = 32768; } bufferRemaining = bufferIncrement; ALOGV("increasing download buffer to %zu bytes", buffer->size() + bufferRemaining); sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining); memcpy(copy->data(), buffer->data(), buffer->size()); copy->setRange(0, buffer->size()); buffer = copy; } size_t maxBytesToRead = bufferRemaining; if (range_length >= 0) { int64_t bytesLeftInRange = range_length - buffer->size(); if (bytesLeftInRange < (int64_t)maxBytesToRead) { maxBytesToRead = bytesLeftInRange; if (bytesLeftInRange == 0) { break; } } } // The DataSource is responsible for informing us of error (n < 0) or eof (n == 0) // to help us break out of the loop. ssize_t n = mDataSource->readAt( buffer->size(), buffer->data() + buffer->size(), maxBytesToRead); if (isDisconnecting()) { return ERROR_NOT_CONNECTED; } if (n < 0) { return n; } if (n == 0) { break; } buffer->setRange(0, buffer->size() + (size_t)n); bytesRead += n; } *out = buffer; if (actualUrl != NULL) { *actualUrl = mDataSource->getUri(); if (actualUrl->isEmpty()) { *actualUrl = url; } } return bytesRead; } ssize_t HTTPDownloader::fetchFile( const char *url, sp<ABuffer> *out, String8 *actualUrl) { ssize_t err = fetchBlock(url, out, 0, -1, 0, actualUrl, true /* reconnect */); // close off the connection after use mHTTPDataSource->disconnect(); return err; } sp<M3UParser> HTTPDownloader::fetchPlaylist( const char *url, uint8_t *curPlaylistHash, bool *unchanged) { ALOGV("fetchPlaylist '%s'", url); *unchanged = false; sp<ABuffer> buffer; String8 actualUrl; ssize_t err = fetchFile(url, &buffer, &actualUrl); // close off the connection after use mHTTPDataSource->disconnect(); if (err <= 0) { return NULL; } // MD5 functionality is not available on the simulator, treat all // playlists as changed. #if defined(HAVE_ANDROID_OS) uint8_t hash[16]; MD5_CTX m; MD5_Init(&m); MD5_Update(&m, buffer->data(), buffer->size()); MD5_Final(hash, &m); if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) { // playlist unchanged *unchanged = true; return NULL; } if (curPlaylistHash != NULL) { memcpy(curPlaylistHash, hash, sizeof(hash)); } #endif sp<M3UParser> playlist = new M3UParser(actualUrl.string(), buffer->data(), buffer->size()); if (playlist->initCheck() != OK) { ALOGE("failed to parse .m3u8 playlist"); return NULL; } return playlist; } } // namespace android media/libstagefright/httplive/HTTPDownloader.h 0 → 100644 +86 −0 Original line number Diff line number Diff line /* * Copyright 2015 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 HTTP_DOWNLOADER_H_ #define HTTP_DOWNLOADER_H_ #include <media/stagefright/foundation/ADebug.h> #include <utils/KeyedVector.h> #include <utils/Mutex.h> #include <utils/RefBase.h> namespace android { struct ABuffer; class DataSource; struct HTTPBase; struct IMediaHTTPService; struct M3UParser; struct HTTPDownloader : public RefBase { HTTPDownloader( const sp<IMediaHTTPService> &httpService, const KeyedVector<String8, String8> &headers); void reconnect(); void disconnect(); bool isDisconnecting(); // If given a non-zero block_size (default 0), it is used to cap the number of // bytes read in from the DataSource. If given a non-NULL buffer, new content // is read into the end. // // The DataSource we read from is responsible for signaling error or EOF to help us // break out of the read loop. The DataSource can be returned to the caller, so // that the caller can reuse it for subsequent fetches (within the initially // requested range). // // For reused HTTP sources, the caller must download a file sequentially without // any overlaps or gaps to prevent reconnection. ssize_t fetchBlock( const char *url, sp<ABuffer> *out, int64_t range_offset, /* open file at range_offset */ int64_t range_length, /* open file for range_length (-1: entire file) */ uint32_t block_size, /* download block size (0: entire range) */ String8 *actualUrl, /* returns actual URL */ bool reconnect /* force connect http */ ); // simplified version to fetch a single file ssize_t fetchFile( const char *url, sp<ABuffer> *out, String8 *actualUrl = NULL); // fetch a playlist file sp<M3UParser> fetchPlaylist( const char *url, uint8_t *curPlaylistHash, bool *unchanged); private: sp<HTTPBase> mHTTPDataSource; sp<DataSource> mDataSource; KeyedVector<String8, String8> mExtraHeaders; Mutex mLock; bool mDisconnecting; DISALLOW_EVIL_CONSTRUCTORS(HTTPDownloader); }; } // namespace android #endif // HTTP_DOWNLOADER_H_ media/libstagefright/httplive/LiveSession.cpp +59 −243 Original line number Diff line number Diff line Loading @@ -19,25 +19,18 @@ #include <utils/Log.h> #include "LiveSession.h" #include "HTTPDownloader.h" #include "M3UParser.h" #include "PlaylistFetcher.h" #include "include/HTTPBase.h" #include "mpeg2ts/AnotherPacketSource.h" #include <cutils/properties.h> #include <media/IMediaHTTPConnection.h> #include <media/IMediaHTTPService.h> #include <media/stagefright/foundation/hexdump.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AMessage.h> #include <media/stagefright/foundation/AUtils.h> #include <media/stagefright/DataSource.h> #include <media/stagefright/FileSource.h> #include <media/stagefright/MediaErrors.h> #include <media/stagefright/MediaHTTP.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MetaData.h> #include <media/stagefright/Utils.h> Loading @@ -46,8 +39,6 @@ #include <ctype.h> #include <inttypes.h> #include <openssl/aes.h> #include <openssl/md5.h> namespace android { Loading Loading @@ -257,7 +248,6 @@ LiveSession::LiveSession( mInPreparationPhase(true), mPollBufferingGeneration(0), mPrevBufferPercentage(-1), mHTTPDataSource(new MediaHTTP(mHTTPService->makeHTTPConnection())), mCurBandwidthIndex(-1), mOrigBandwidthIndex(-1), mLastBandwidthBps(-1ll), Loading Loading @@ -317,7 +307,7 @@ status_t LiveSession::dequeueAccessUnit( ssize_t streamIdx = typeToIndex(stream); if (streamIdx < 0) { return INVALID_VALUE; return BAD_VALUE; } const char *streamStr = getNameForStream(stream); // Do not let client pull data if we don't have data packets yet. Loading Loading @@ -465,8 +455,8 @@ status_t LiveSession::getStreamFormat(StreamType stream, sp<AMessage> *format) { return convertMetaDataToMessage(meta, format); } sp<HTTPBase> LiveSession::getHTTPDataSource() { return new MediaHTTP(mHTTPService->makeHTTPConnection()); sp<HTTPDownloader> LiveSession::getHTTPDownloader() { return new HTTPDownloader(mHTTPService, mExtraHeaders); } void LiveSession::connectAsync( Loading Loading @@ -838,6 +828,12 @@ void LiveSession::onMessageReceived(const sp<AMessage> &msg) { break; } case PlaylistFetcher::kWhatPlaylistFetched: { onMasterPlaylistFetched(msg); break; } case PlaylistFetcher::kWhatMetadataDetected: { if (!mHasMetadata) { Loading Loading @@ -874,12 +870,6 @@ void LiveSession::onMessageReceived(const sp<AMessage> &msg) { break; } case kWhatFinishDisconnect2: { onFinishDisconnect2(); break; } case kWhatPollBuffering: { int32_t generation; Loading Loading @@ -931,8 +921,10 @@ ssize_t LiveSession::typeToIndex(int32_t type) { } void LiveSession::onConnect(const sp<AMessage> &msg) { AString url; CHECK(msg->findString("url", &url)); CHECK(msg->findString("url", &mMasterURL)); // TODO currently we don't know if we are coming here from incognito mode ALOGI("onConnect %s", uriDebugString(mMasterURL).c_str()); KeyedVector<String8, String8> *headers = NULL; if (!msg->findPointer("headers", (void **)&headers)) { Loading @@ -944,21 +936,6 @@ void LiveSession::onConnect(const sp<AMessage> &msg) { headers = NULL; } // TODO currently we don't know if we are coming here from incognito mode ALOGI("onConnect %s", uriDebugString(url).c_str()); mMasterURL = url; bool dummy; mPlaylist = fetchPlaylist(url.c_str(), NULL /* curPlaylistHash */, &dummy); if (mPlaylist == NULL) { ALOGE("unable to fetch master playlist %s.", uriDebugString(url).c_str()); postPrepared(ERROR_IO); return; } // create looper for fetchers if (mFetcherLooper == NULL) { mFetcherLooper = new ALooper(); Loading @@ -967,6 +944,31 @@ void LiveSession::onConnect(const sp<AMessage> &msg) { mFetcherLooper->start(false, false); } // create fetcher to fetch the master playlist addFetcher(mMasterURL.c_str())->fetchPlaylistAsync(); } void LiveSession::onMasterPlaylistFetched(const sp<AMessage> &msg) { AString uri; CHECK(msg->findString("uri", &uri)); ssize_t index = mFetcherInfos.indexOfKey(uri); if (index < 0) { ALOGW("fetcher for master playlist is gone."); return; } // no longer useful, remove mFetcherLooper->unregisterHandler(mFetcherInfos[index].mFetcher->id()); mFetcherInfos.removeItemsAt(index); CHECK(msg->findObject("playlist", (sp<RefBase> *)&mPlaylist)); if (mPlaylist == NULL) { ALOGE("unable to fetch master playlist %s.", uriDebugString(mMasterURL).c_str()); postPrepared(ERROR_IO); return; } // We trust the content provider to make a reasonable choice of preferred // initial bandwidth by listing it first in the variant playlist. // At startup we really don't have a good estimate on the available Loading Loading @@ -1050,22 +1052,26 @@ void LiveSession::finishDisconnect() { // cancel buffer polling cancelPollBuffering(); // TRICKY: don't wait for all fetcher to be stopped when disconnecting // // Some fetchers might be stuck in connect/getSize at this point. These // operations will eventually timeout (as we have a timeout set in // MediaHTTPConnection), but we don't want to block the main UI thread // until then. Here we just need to make sure we clear all references // to the fetchers, so that when they finally exit from the blocking // operation, they can be destructed. // // There is one very tricky point though. For this scheme to work, the // fecther must hold a reference to LiveSession, so that LiveSession is // destroyed after fetcher. Otherwise LiveSession would get stuck in its // own destructor when it waits for mFetcherLooper to stop, which still // blocks main UI thread. for (size_t i = 0; i < mFetcherInfos.size(); ++i) { mFetcherInfos.valueAt(i).mFetcher->stopAsync(); mFetcherLooper->unregisterHandler( mFetcherInfos.valueAt(i).mFetcher->id()); } sp<AMessage> msg = new AMessage(kWhatFinishDisconnect2, this); mContinuationCounter = mFetcherInfos.size(); mContinuation = msg; if (mContinuationCounter == 0) { msg->post(); } } void LiveSession::onFinishDisconnect2() { mContinuation.clear(); mFetcherInfos.clear(); mPacketSources.valueFor(STREAMTYPE_AUDIO)->signalEOS(ERROR_END_OF_STREAM); mPacketSources.valueFor(STREAMTYPE_VIDEO)->signalEOS(ERROR_END_OF_STREAM); Loading Loading @@ -1104,198 +1110,6 @@ sp<PlaylistFetcher> LiveSession::addFetcher(const char *uri) { return info.mFetcher; } /* * Illustration of parameters: * * 0 `range_offset` * +------------+-------------------------------------------------------+--+--+ * | | | next block to fetch | | | * | | `source` handle => `out` buffer | | | | * | `url` file |<--------- buffer size --------->|<--- `block_size` -->| | | * | |<----------- `range_length` / buffer capacity ----------->| | * |<------------------------------ file_size ------------------------------->| * * Special parameter values: * - range_length == -1 means entire file * - block_size == 0 means entire range * */ ssize_t LiveSession::fetchFile( const char *url, sp<ABuffer> *out, int64_t range_offset, int64_t range_length, uint32_t block_size, /* download block size */ sp<DataSource> *source, /* to return and reuse source */ String8 *actualUrl, bool forceConnectHTTP /* force connect HTTP when resuing source */) { off64_t size; sp<DataSource> temp_source; if (source == NULL) { source = &temp_source; } if (*source == NULL || forceConnectHTTP) { if (!strncasecmp(url, "file://", 7)) { *source = new FileSource(url + 7); } else if (strncasecmp(url, "http://", 7) && strncasecmp(url, "https://", 8)) { return ERROR_UNSUPPORTED; } else { KeyedVector<String8, String8> headers = mExtraHeaders; if (range_offset > 0 || range_length >= 0) { headers.add( String8("Range"), String8( AStringPrintf( "bytes=%lld-%s", range_offset, range_length < 0 ? "" : AStringPrintf("%lld", range_offset + range_length - 1).c_str()).c_str())); } HTTPBase* httpDataSource = (*source == NULL) ? mHTTPDataSource.get() : (HTTPBase*)source->get(); status_t err = httpDataSource->connect(url, &headers); if (err != OK) { return err; } if (*source == NULL) { *source = mHTTPDataSource; } } } status_t getSizeErr = (*source)->getSize(&size); if (getSizeErr != OK) { size = 65536; } sp<ABuffer> buffer = *out != NULL ? *out : new ABuffer(size); if (*out == NULL) { buffer->setRange(0, 0); } ssize_t bytesRead = 0; // adjust range_length if only reading partial block if (block_size > 0 && (range_length == -1 || (int64_t)(buffer->size() + block_size) < range_length)) { range_length = buffer->size() + block_size; } for (;;) { // Only resize when we don't know the size. size_t bufferRemaining = buffer->capacity() - buffer->size(); if (bufferRemaining == 0 && getSizeErr != OK) { size_t bufferIncrement = buffer->size() / 2; if (bufferIncrement < 32768) { bufferIncrement = 32768; } bufferRemaining = bufferIncrement; ALOGV("increasing download buffer to %zu bytes", buffer->size() + bufferRemaining); sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining); memcpy(copy->data(), buffer->data(), buffer->size()); copy->setRange(0, buffer->size()); buffer = copy; } size_t maxBytesToRead = bufferRemaining; if (range_length >= 0) { int64_t bytesLeftInRange = range_length - buffer->size(); if (bytesLeftInRange < (int64_t)maxBytesToRead) { maxBytesToRead = bytesLeftInRange; if (bytesLeftInRange == 0) { break; } } } // The DataSource is responsible for informing us of error (n < 0) or eof (n == 0) // to help us break out of the loop. ssize_t n = (*source)->readAt( buffer->size(), buffer->data() + buffer->size(), maxBytesToRead); if (n < 0) { return n; } if (n == 0) { break; } buffer->setRange(0, buffer->size() + (size_t)n); bytesRead += n; } *out = buffer; if (actualUrl != NULL) { *actualUrl = (*source)->getUri(); if (actualUrl->isEmpty()) { *actualUrl = url; } } return bytesRead; } sp<M3UParser> LiveSession::fetchPlaylist( const char *url, uint8_t *curPlaylistHash, bool *unchanged) { ALOGV("fetchPlaylist '%s'", url); *unchanged = false; sp<ABuffer> buffer; String8 actualUrl; ssize_t err = fetchFile(url, &buffer, 0, -1, 0, NULL, &actualUrl); // close off the connection after use mHTTPDataSource->disconnect(); if (err <= 0) { return NULL; } // MD5 functionality is not available on the simulator, treat all // playlists as changed. #if defined(HAVE_ANDROID_OS) uint8_t hash[16]; MD5_CTX m; MD5_Init(&m); MD5_Update(&m, buffer->data(), buffer->size()); MD5_Final(hash, &m); if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) { // playlist unchanged *unchanged = true; return NULL; } if (curPlaylistHash != NULL) { memcpy(curPlaylistHash, hash, sizeof(hash)); } #endif sp<M3UParser> playlist = new M3UParser(actualUrl.string(), buffer->data(), buffer->size()); if (playlist->initCheck() != OK) { ALOGE("failed to parse .m3u8 playlist"); return NULL; } return playlist; } #if 0 static double uniformRand() { return (double)rand() / RAND_MAX; Loading Loading @@ -1690,9 +1504,11 @@ void LiveSession::changeConfiguration( fetcher->stopAsync(); } else { float threshold = -1.0f; // always finish fetching by default bool disconnect = false; if (timeUs >= 0ll) { // seeking, no need to finish fetching threshold = 0.0f; disconnect = true; } else if (delayRemoval) { // adapting, abort if remaining of current segment is over threshold threshold = getAbortThreshold( Loading @@ -1701,7 +1517,7 @@ void LiveSession::changeConfiguration( ALOGV("pausing fetcher-%d, threshold=%.2f", fetcher->getFetcherID(), threshold); fetcher->pauseAsync(threshold); fetcher->pauseAsync(threshold, disconnect); } } Loading Loading
media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp +1 −1 Original line number Diff line number Diff line Loading @@ -658,7 +658,7 @@ status_t NuPlayer::Decoder::fetchInputData(sp<AMessage> &reply) { #if 0 int64_t mediaTimeUs; CHECK(accessUnit->meta()->findInt64("timeUs", &mediaTimeUs)); ALOGV("[%s] feeding input buffer at media time %" PRId64, ALOGV("[%s] feeding input buffer at media time %.3f", mIsAudio ? "audio" : "video", mediaTimeUs / 1E6); #endif Loading
media/libstagefright/httplive/Android.mk +1 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ HTTPDownloader.cpp \ LiveDataSource.cpp \ LiveSession.cpp \ M3UParser.cpp \ Loading
media/libstagefright/httplive/HTTPDownloader.cpp 0 → 100644 +273 −0 Original line number Diff line number Diff line /* * Copyright 2015 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 "HTTPDownloader" #include <utils/Log.h> #include "HTTPDownloader.h" #include "M3UParser.h" #include <media/IMediaHTTPConnection.h> #include <media/IMediaHTTPService.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/MediaHTTP.h> #include <media/stagefright/DataSource.h> #include <media/stagefright/FileSource.h> #include <openssl/aes.h> #include <openssl/md5.h> #include <utils/Mutex.h> namespace android { HTTPDownloader::HTTPDownloader( const sp<IMediaHTTPService> &httpService, const KeyedVector<String8, String8> &headers) : mHTTPDataSource(new MediaHTTP(httpService->makeHTTPConnection())), mExtraHeaders(headers), mDisconnecting(false) { } void HTTPDownloader::reconnect() { AutoMutex _l(mLock); mDisconnecting = false; } void HTTPDownloader::disconnect() { { AutoMutex _l(mLock); mDisconnecting = true; } mHTTPDataSource->disconnect(); } bool HTTPDownloader::isDisconnecting() { AutoMutex _l(mLock); return mDisconnecting; } /* * Illustration of parameters: * * 0 `range_offset` * +------------+-------------------------------------------------------+--+--+ * | | | next block to fetch | | | * | | `source` handle => `out` buffer | | | | * | `url` file |<--------- buffer size --------->|<--- `block_size` -->| | | * | |<----------- `range_length` / buffer capacity ----------->| | * |<------------------------------ file_size ------------------------------->| * * Special parameter values: * - range_length == -1 means entire file * - block_size == 0 means entire range * */ ssize_t HTTPDownloader::fetchBlock( const char *url, sp<ABuffer> *out, int64_t range_offset, int64_t range_length, uint32_t block_size, /* download block size */ String8 *actualUrl, bool reconnect /* force connect HTTP when resuing source */) { if (isDisconnecting()) { return ERROR_NOT_CONNECTED; } off64_t size; if (reconnect) { if (!strncasecmp(url, "file://", 7)) { mDataSource = new FileSource(url + 7); } else if (strncasecmp(url, "http://", 7) && strncasecmp(url, "https://", 8)) { return ERROR_UNSUPPORTED; } else { KeyedVector<String8, String8> headers = mExtraHeaders; if (range_offset > 0 || range_length >= 0) { headers.add( String8("Range"), String8( AStringPrintf( "bytes=%lld-%s", range_offset, range_length < 0 ? "" : AStringPrintf("%lld", range_offset + range_length - 1).c_str()).c_str())); } status_t err = mHTTPDataSource->connect(url, &headers); if (isDisconnecting()) { return ERROR_NOT_CONNECTED; } if (err != OK) { return err; } mDataSource = mHTTPDataSource; } } status_t getSizeErr = mDataSource->getSize(&size); if (isDisconnecting()) { return ERROR_NOT_CONNECTED; } if (getSizeErr != OK) { size = 65536; } sp<ABuffer> buffer = *out != NULL ? *out : new ABuffer(size); if (*out == NULL) { buffer->setRange(0, 0); } ssize_t bytesRead = 0; // adjust range_length if only reading partial block if (block_size > 0 && (range_length == -1 || (int64_t)(buffer->size() + block_size) < range_length)) { range_length = buffer->size() + block_size; } for (;;) { // Only resize when we don't know the size. size_t bufferRemaining = buffer->capacity() - buffer->size(); if (bufferRemaining == 0 && getSizeErr != OK) { size_t bufferIncrement = buffer->size() / 2; if (bufferIncrement < 32768) { bufferIncrement = 32768; } bufferRemaining = bufferIncrement; ALOGV("increasing download buffer to %zu bytes", buffer->size() + bufferRemaining); sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining); memcpy(copy->data(), buffer->data(), buffer->size()); copy->setRange(0, buffer->size()); buffer = copy; } size_t maxBytesToRead = bufferRemaining; if (range_length >= 0) { int64_t bytesLeftInRange = range_length - buffer->size(); if (bytesLeftInRange < (int64_t)maxBytesToRead) { maxBytesToRead = bytesLeftInRange; if (bytesLeftInRange == 0) { break; } } } // The DataSource is responsible for informing us of error (n < 0) or eof (n == 0) // to help us break out of the loop. ssize_t n = mDataSource->readAt( buffer->size(), buffer->data() + buffer->size(), maxBytesToRead); if (isDisconnecting()) { return ERROR_NOT_CONNECTED; } if (n < 0) { return n; } if (n == 0) { break; } buffer->setRange(0, buffer->size() + (size_t)n); bytesRead += n; } *out = buffer; if (actualUrl != NULL) { *actualUrl = mDataSource->getUri(); if (actualUrl->isEmpty()) { *actualUrl = url; } } return bytesRead; } ssize_t HTTPDownloader::fetchFile( const char *url, sp<ABuffer> *out, String8 *actualUrl) { ssize_t err = fetchBlock(url, out, 0, -1, 0, actualUrl, true /* reconnect */); // close off the connection after use mHTTPDataSource->disconnect(); return err; } sp<M3UParser> HTTPDownloader::fetchPlaylist( const char *url, uint8_t *curPlaylistHash, bool *unchanged) { ALOGV("fetchPlaylist '%s'", url); *unchanged = false; sp<ABuffer> buffer; String8 actualUrl; ssize_t err = fetchFile(url, &buffer, &actualUrl); // close off the connection after use mHTTPDataSource->disconnect(); if (err <= 0) { return NULL; } // MD5 functionality is not available on the simulator, treat all // playlists as changed. #if defined(HAVE_ANDROID_OS) uint8_t hash[16]; MD5_CTX m; MD5_Init(&m); MD5_Update(&m, buffer->data(), buffer->size()); MD5_Final(hash, &m); if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) { // playlist unchanged *unchanged = true; return NULL; } if (curPlaylistHash != NULL) { memcpy(curPlaylistHash, hash, sizeof(hash)); } #endif sp<M3UParser> playlist = new M3UParser(actualUrl.string(), buffer->data(), buffer->size()); if (playlist->initCheck() != OK) { ALOGE("failed to parse .m3u8 playlist"); return NULL; } return playlist; } } // namespace android
media/libstagefright/httplive/HTTPDownloader.h 0 → 100644 +86 −0 Original line number Diff line number Diff line /* * Copyright 2015 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 HTTP_DOWNLOADER_H_ #define HTTP_DOWNLOADER_H_ #include <media/stagefright/foundation/ADebug.h> #include <utils/KeyedVector.h> #include <utils/Mutex.h> #include <utils/RefBase.h> namespace android { struct ABuffer; class DataSource; struct HTTPBase; struct IMediaHTTPService; struct M3UParser; struct HTTPDownloader : public RefBase { HTTPDownloader( const sp<IMediaHTTPService> &httpService, const KeyedVector<String8, String8> &headers); void reconnect(); void disconnect(); bool isDisconnecting(); // If given a non-zero block_size (default 0), it is used to cap the number of // bytes read in from the DataSource. If given a non-NULL buffer, new content // is read into the end. // // The DataSource we read from is responsible for signaling error or EOF to help us // break out of the read loop. The DataSource can be returned to the caller, so // that the caller can reuse it for subsequent fetches (within the initially // requested range). // // For reused HTTP sources, the caller must download a file sequentially without // any overlaps or gaps to prevent reconnection. ssize_t fetchBlock( const char *url, sp<ABuffer> *out, int64_t range_offset, /* open file at range_offset */ int64_t range_length, /* open file for range_length (-1: entire file) */ uint32_t block_size, /* download block size (0: entire range) */ String8 *actualUrl, /* returns actual URL */ bool reconnect /* force connect http */ ); // simplified version to fetch a single file ssize_t fetchFile( const char *url, sp<ABuffer> *out, String8 *actualUrl = NULL); // fetch a playlist file sp<M3UParser> fetchPlaylist( const char *url, uint8_t *curPlaylistHash, bool *unchanged); private: sp<HTTPBase> mHTTPDataSource; sp<DataSource> mDataSource; KeyedVector<String8, String8> mExtraHeaders; Mutex mLock; bool mDisconnecting; DISALLOW_EVIL_CONSTRUCTORS(HTTPDownloader); }; } // namespace android #endif // HTTP_DOWNLOADER_H_
media/libstagefright/httplive/LiveSession.cpp +59 −243 Original line number Diff line number Diff line Loading @@ -19,25 +19,18 @@ #include <utils/Log.h> #include "LiveSession.h" #include "HTTPDownloader.h" #include "M3UParser.h" #include "PlaylistFetcher.h" #include "include/HTTPBase.h" #include "mpeg2ts/AnotherPacketSource.h" #include <cutils/properties.h> #include <media/IMediaHTTPConnection.h> #include <media/IMediaHTTPService.h> #include <media/stagefright/foundation/hexdump.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AMessage.h> #include <media/stagefright/foundation/AUtils.h> #include <media/stagefright/DataSource.h> #include <media/stagefright/FileSource.h> #include <media/stagefright/MediaErrors.h> #include <media/stagefright/MediaHTTP.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MetaData.h> #include <media/stagefright/Utils.h> Loading @@ -46,8 +39,6 @@ #include <ctype.h> #include <inttypes.h> #include <openssl/aes.h> #include <openssl/md5.h> namespace android { Loading Loading @@ -257,7 +248,6 @@ LiveSession::LiveSession( mInPreparationPhase(true), mPollBufferingGeneration(0), mPrevBufferPercentage(-1), mHTTPDataSource(new MediaHTTP(mHTTPService->makeHTTPConnection())), mCurBandwidthIndex(-1), mOrigBandwidthIndex(-1), mLastBandwidthBps(-1ll), Loading Loading @@ -317,7 +307,7 @@ status_t LiveSession::dequeueAccessUnit( ssize_t streamIdx = typeToIndex(stream); if (streamIdx < 0) { return INVALID_VALUE; return BAD_VALUE; } const char *streamStr = getNameForStream(stream); // Do not let client pull data if we don't have data packets yet. Loading Loading @@ -465,8 +455,8 @@ status_t LiveSession::getStreamFormat(StreamType stream, sp<AMessage> *format) { return convertMetaDataToMessage(meta, format); } sp<HTTPBase> LiveSession::getHTTPDataSource() { return new MediaHTTP(mHTTPService->makeHTTPConnection()); sp<HTTPDownloader> LiveSession::getHTTPDownloader() { return new HTTPDownloader(mHTTPService, mExtraHeaders); } void LiveSession::connectAsync( Loading Loading @@ -838,6 +828,12 @@ void LiveSession::onMessageReceived(const sp<AMessage> &msg) { break; } case PlaylistFetcher::kWhatPlaylistFetched: { onMasterPlaylistFetched(msg); break; } case PlaylistFetcher::kWhatMetadataDetected: { if (!mHasMetadata) { Loading Loading @@ -874,12 +870,6 @@ void LiveSession::onMessageReceived(const sp<AMessage> &msg) { break; } case kWhatFinishDisconnect2: { onFinishDisconnect2(); break; } case kWhatPollBuffering: { int32_t generation; Loading Loading @@ -931,8 +921,10 @@ ssize_t LiveSession::typeToIndex(int32_t type) { } void LiveSession::onConnect(const sp<AMessage> &msg) { AString url; CHECK(msg->findString("url", &url)); CHECK(msg->findString("url", &mMasterURL)); // TODO currently we don't know if we are coming here from incognito mode ALOGI("onConnect %s", uriDebugString(mMasterURL).c_str()); KeyedVector<String8, String8> *headers = NULL; if (!msg->findPointer("headers", (void **)&headers)) { Loading @@ -944,21 +936,6 @@ void LiveSession::onConnect(const sp<AMessage> &msg) { headers = NULL; } // TODO currently we don't know if we are coming here from incognito mode ALOGI("onConnect %s", uriDebugString(url).c_str()); mMasterURL = url; bool dummy; mPlaylist = fetchPlaylist(url.c_str(), NULL /* curPlaylistHash */, &dummy); if (mPlaylist == NULL) { ALOGE("unable to fetch master playlist %s.", uriDebugString(url).c_str()); postPrepared(ERROR_IO); return; } // create looper for fetchers if (mFetcherLooper == NULL) { mFetcherLooper = new ALooper(); Loading @@ -967,6 +944,31 @@ void LiveSession::onConnect(const sp<AMessage> &msg) { mFetcherLooper->start(false, false); } // create fetcher to fetch the master playlist addFetcher(mMasterURL.c_str())->fetchPlaylistAsync(); } void LiveSession::onMasterPlaylistFetched(const sp<AMessage> &msg) { AString uri; CHECK(msg->findString("uri", &uri)); ssize_t index = mFetcherInfos.indexOfKey(uri); if (index < 0) { ALOGW("fetcher for master playlist is gone."); return; } // no longer useful, remove mFetcherLooper->unregisterHandler(mFetcherInfos[index].mFetcher->id()); mFetcherInfos.removeItemsAt(index); CHECK(msg->findObject("playlist", (sp<RefBase> *)&mPlaylist)); if (mPlaylist == NULL) { ALOGE("unable to fetch master playlist %s.", uriDebugString(mMasterURL).c_str()); postPrepared(ERROR_IO); return; } // We trust the content provider to make a reasonable choice of preferred // initial bandwidth by listing it first in the variant playlist. // At startup we really don't have a good estimate on the available Loading Loading @@ -1050,22 +1052,26 @@ void LiveSession::finishDisconnect() { // cancel buffer polling cancelPollBuffering(); // TRICKY: don't wait for all fetcher to be stopped when disconnecting // // Some fetchers might be stuck in connect/getSize at this point. These // operations will eventually timeout (as we have a timeout set in // MediaHTTPConnection), but we don't want to block the main UI thread // until then. Here we just need to make sure we clear all references // to the fetchers, so that when they finally exit from the blocking // operation, they can be destructed. // // There is one very tricky point though. For this scheme to work, the // fecther must hold a reference to LiveSession, so that LiveSession is // destroyed after fetcher. Otherwise LiveSession would get stuck in its // own destructor when it waits for mFetcherLooper to stop, which still // blocks main UI thread. for (size_t i = 0; i < mFetcherInfos.size(); ++i) { mFetcherInfos.valueAt(i).mFetcher->stopAsync(); mFetcherLooper->unregisterHandler( mFetcherInfos.valueAt(i).mFetcher->id()); } sp<AMessage> msg = new AMessage(kWhatFinishDisconnect2, this); mContinuationCounter = mFetcherInfos.size(); mContinuation = msg; if (mContinuationCounter == 0) { msg->post(); } } void LiveSession::onFinishDisconnect2() { mContinuation.clear(); mFetcherInfos.clear(); mPacketSources.valueFor(STREAMTYPE_AUDIO)->signalEOS(ERROR_END_OF_STREAM); mPacketSources.valueFor(STREAMTYPE_VIDEO)->signalEOS(ERROR_END_OF_STREAM); Loading Loading @@ -1104,198 +1110,6 @@ sp<PlaylistFetcher> LiveSession::addFetcher(const char *uri) { return info.mFetcher; } /* * Illustration of parameters: * * 0 `range_offset` * +------------+-------------------------------------------------------+--+--+ * | | | next block to fetch | | | * | | `source` handle => `out` buffer | | | | * | `url` file |<--------- buffer size --------->|<--- `block_size` -->| | | * | |<----------- `range_length` / buffer capacity ----------->| | * |<------------------------------ file_size ------------------------------->| * * Special parameter values: * - range_length == -1 means entire file * - block_size == 0 means entire range * */ ssize_t LiveSession::fetchFile( const char *url, sp<ABuffer> *out, int64_t range_offset, int64_t range_length, uint32_t block_size, /* download block size */ sp<DataSource> *source, /* to return and reuse source */ String8 *actualUrl, bool forceConnectHTTP /* force connect HTTP when resuing source */) { off64_t size; sp<DataSource> temp_source; if (source == NULL) { source = &temp_source; } if (*source == NULL || forceConnectHTTP) { if (!strncasecmp(url, "file://", 7)) { *source = new FileSource(url + 7); } else if (strncasecmp(url, "http://", 7) && strncasecmp(url, "https://", 8)) { return ERROR_UNSUPPORTED; } else { KeyedVector<String8, String8> headers = mExtraHeaders; if (range_offset > 0 || range_length >= 0) { headers.add( String8("Range"), String8( AStringPrintf( "bytes=%lld-%s", range_offset, range_length < 0 ? "" : AStringPrintf("%lld", range_offset + range_length - 1).c_str()).c_str())); } HTTPBase* httpDataSource = (*source == NULL) ? mHTTPDataSource.get() : (HTTPBase*)source->get(); status_t err = httpDataSource->connect(url, &headers); if (err != OK) { return err; } if (*source == NULL) { *source = mHTTPDataSource; } } } status_t getSizeErr = (*source)->getSize(&size); if (getSizeErr != OK) { size = 65536; } sp<ABuffer> buffer = *out != NULL ? *out : new ABuffer(size); if (*out == NULL) { buffer->setRange(0, 0); } ssize_t bytesRead = 0; // adjust range_length if only reading partial block if (block_size > 0 && (range_length == -1 || (int64_t)(buffer->size() + block_size) < range_length)) { range_length = buffer->size() + block_size; } for (;;) { // Only resize when we don't know the size. size_t bufferRemaining = buffer->capacity() - buffer->size(); if (bufferRemaining == 0 && getSizeErr != OK) { size_t bufferIncrement = buffer->size() / 2; if (bufferIncrement < 32768) { bufferIncrement = 32768; } bufferRemaining = bufferIncrement; ALOGV("increasing download buffer to %zu bytes", buffer->size() + bufferRemaining); sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining); memcpy(copy->data(), buffer->data(), buffer->size()); copy->setRange(0, buffer->size()); buffer = copy; } size_t maxBytesToRead = bufferRemaining; if (range_length >= 0) { int64_t bytesLeftInRange = range_length - buffer->size(); if (bytesLeftInRange < (int64_t)maxBytesToRead) { maxBytesToRead = bytesLeftInRange; if (bytesLeftInRange == 0) { break; } } } // The DataSource is responsible for informing us of error (n < 0) or eof (n == 0) // to help us break out of the loop. ssize_t n = (*source)->readAt( buffer->size(), buffer->data() + buffer->size(), maxBytesToRead); if (n < 0) { return n; } if (n == 0) { break; } buffer->setRange(0, buffer->size() + (size_t)n); bytesRead += n; } *out = buffer; if (actualUrl != NULL) { *actualUrl = (*source)->getUri(); if (actualUrl->isEmpty()) { *actualUrl = url; } } return bytesRead; } sp<M3UParser> LiveSession::fetchPlaylist( const char *url, uint8_t *curPlaylistHash, bool *unchanged) { ALOGV("fetchPlaylist '%s'", url); *unchanged = false; sp<ABuffer> buffer; String8 actualUrl; ssize_t err = fetchFile(url, &buffer, 0, -1, 0, NULL, &actualUrl); // close off the connection after use mHTTPDataSource->disconnect(); if (err <= 0) { return NULL; } // MD5 functionality is not available on the simulator, treat all // playlists as changed. #if defined(HAVE_ANDROID_OS) uint8_t hash[16]; MD5_CTX m; MD5_Init(&m); MD5_Update(&m, buffer->data(), buffer->size()); MD5_Final(hash, &m); if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) { // playlist unchanged *unchanged = true; return NULL; } if (curPlaylistHash != NULL) { memcpy(curPlaylistHash, hash, sizeof(hash)); } #endif sp<M3UParser> playlist = new M3UParser(actualUrl.string(), buffer->data(), buffer->size()); if (playlist->initCheck() != OK) { ALOGE("failed to parse .m3u8 playlist"); return NULL; } return playlist; } #if 0 static double uniformRand() { return (double)rand() / RAND_MAX; Loading Loading @@ -1690,9 +1504,11 @@ void LiveSession::changeConfiguration( fetcher->stopAsync(); } else { float threshold = -1.0f; // always finish fetching by default bool disconnect = false; if (timeUs >= 0ll) { // seeking, no need to finish fetching threshold = 0.0f; disconnect = true; } else if (delayRemoval) { // adapting, abort if remaining of current segment is over threshold threshold = getAbortThreshold( Loading @@ -1701,7 +1517,7 @@ void LiveSession::changeConfiguration( ALOGV("pausing fetcher-%d, threshold=%.2f", fetcher->getFetcherID(), threshold); fetcher->pauseAsync(threshold); fetcher->pauseAsync(threshold, disconnect); } } Loading