diff options
Diffstat (limited to 'media/libstagefright/httplive/HTTPDownloader.cpp')
-rw-r--r-- | media/libstagefright/httplive/HTTPDownloader.cpp | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/media/libstagefright/httplive/HTTPDownloader.cpp b/media/libstagefright/httplive/HTTPDownloader.cpp new file mode 100644 index 0000000..3b44bae --- /dev/null +++ b/media/libstagefright/httplive/HTTPDownloader.cpp @@ -0,0 +1,273 @@ +/* + * 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 |