1/* 2 * Copyright 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17//#define LOG_NDEBUG 0 18#define LOG_TAG "HTTPDownloader" 19#include <utils/Log.h> 20 21#include "HTTPDownloader.h" 22#include "M3UParser.h" 23 24#include <media/IMediaHTTPConnection.h> 25#include <media/IMediaHTTPService.h> 26#include <media/stagefright/foundation/ABuffer.h> 27#include <media/stagefright/foundation/ADebug.h> 28#include <media/stagefright/MediaHTTP.h> 29#include <media/stagefright/DataSource.h> 30#include <media/stagefright/FileSource.h> 31#include <openssl/aes.h> 32#include <openssl/md5.h> 33#include <utils/Mutex.h> 34#include <inttypes.h> 35 36namespace android { 37 38HTTPDownloader::HTTPDownloader( 39 const sp<IMediaHTTPService> &httpService, 40 const KeyedVector<String8, String8> &headers) : 41 mHTTPDataSource(new MediaHTTP(httpService->makeHTTPConnection())), 42 mExtraHeaders(headers), 43 mDisconnecting(false) { 44} 45 46void HTTPDownloader::reconnect() { 47 AutoMutex _l(mLock); 48 mDisconnecting = false; 49} 50 51void HTTPDownloader::disconnect() { 52 { 53 AutoMutex _l(mLock); 54 mDisconnecting = true; 55 } 56 mHTTPDataSource->disconnect(); 57} 58 59bool HTTPDownloader::isDisconnecting() { 60 AutoMutex _l(mLock); 61 return mDisconnecting; 62} 63 64/* 65 * Illustration of parameters: 66 * 67 * 0 `range_offset` 68 * +------------+-------------------------------------------------------+--+--+ 69 * | | | next block to fetch | | | 70 * | | `source` handle => `out` buffer | | | | 71 * | `url` file |<--------- buffer size --------->|<--- `block_size` -->| | | 72 * | |<----------- `range_length` / buffer capacity ----------->| | 73 * |<------------------------------ file_size ------------------------------->| 74 * 75 * Special parameter values: 76 * - range_length == -1 means entire file 77 * - block_size == 0 means entire range 78 * 79 */ 80ssize_t HTTPDownloader::fetchBlock( 81 const char *url, sp<ABuffer> *out, 82 int64_t range_offset, int64_t range_length, 83 uint32_t block_size, /* download block size */ 84 String8 *actualUrl, 85 bool reconnect /* force connect HTTP when resuing source */) { 86 if (isDisconnecting()) { 87 return ERROR_NOT_CONNECTED; 88 } 89 90 off64_t size; 91 92 if (reconnect) { 93 if (!strncasecmp(url, "file://", 7)) { 94 mDataSource = new FileSource(url + 7); 95 } else if (strncasecmp(url, "http://", 7) 96 && strncasecmp(url, "https://", 8)) { 97 return ERROR_UNSUPPORTED; 98 } else { 99 KeyedVector<String8, String8> headers = mExtraHeaders; 100 if (range_offset > 0 || range_length >= 0) { 101 headers.add( 102 String8("Range"), 103 String8( 104 AStringPrintf( 105 "bytes=%lld-%s", 106 range_offset, 107 range_length < 0 108 ? "" : AStringPrintf("%lld", 109 range_offset + range_length - 1).c_str()).c_str())); 110 } 111 112 status_t err = mHTTPDataSource->connect(url, &headers); 113 114 if (isDisconnecting()) { 115 return ERROR_NOT_CONNECTED; 116 } 117 118 if (err != OK) { 119 return err; 120 } 121 122 mDataSource = mHTTPDataSource; 123 } 124 } 125 126 status_t getSizeErr = mDataSource->getSize(&size); 127 128 if (isDisconnecting()) { 129 return ERROR_NOT_CONNECTED; 130 } 131 132 if (getSizeErr != OK) { 133 size = 65536; 134 } 135 136 sp<ABuffer> buffer = *out != NULL ? *out : new ABuffer(size); 137 if (*out == NULL) { 138 buffer->setRange(0, 0); 139 } 140 141 ssize_t bytesRead = 0; 142 // adjust range_length if only reading partial block 143 if (block_size > 0 && (range_length == -1 || (int64_t)(buffer->size() + block_size) < range_length)) { 144 range_length = buffer->size() + block_size; 145 } 146 for (;;) { 147 // Only resize when we don't know the size. 148 size_t bufferRemaining = buffer->capacity() - buffer->size(); 149 if (bufferRemaining == 0 && getSizeErr != OK) { 150 size_t bufferIncrement = buffer->size() / 2; 151 if (bufferIncrement < 32768) { 152 bufferIncrement = 32768; 153 } 154 bufferRemaining = bufferIncrement; 155 156 ALOGV("increasing download buffer to %zu bytes", 157 buffer->size() + bufferRemaining); 158 159 sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining); 160 memcpy(copy->data(), buffer->data(), buffer->size()); 161 copy->setRange(0, buffer->size()); 162 163 buffer = copy; 164 } 165 166 size_t maxBytesToRead = bufferRemaining; 167 if (range_length >= 0) { 168 int64_t bytesLeftInRange = range_length - buffer->size(); 169 if (bytesLeftInRange < 0) { 170 ALOGE("range_length %" PRId64 " wrapped around", range_length); 171 return ERROR_OUT_OF_RANGE; 172 } else if (bytesLeftInRange < (int64_t)maxBytesToRead) { 173 maxBytesToRead = bytesLeftInRange; 174 175 if (bytesLeftInRange == 0) { 176 break; 177 } 178 } 179 } 180 181 // The DataSource is responsible for informing us of error (n < 0) or eof (n == 0) 182 // to help us break out of the loop. 183 ssize_t n = mDataSource->readAt( 184 buffer->size(), buffer->data() + buffer->size(), 185 maxBytesToRead); 186 187 if (isDisconnecting()) { 188 return ERROR_NOT_CONNECTED; 189 } 190 191 if (n < 0) { 192 return n; 193 } 194 195 if (n == 0) { 196 break; 197 } 198 199 buffer->setRange(0, buffer->size() + (size_t)n); 200 bytesRead += n; 201 } 202 203 *out = buffer; 204 if (actualUrl != NULL) { 205 *actualUrl = mDataSource->getUri(); 206 if (actualUrl->isEmpty()) { 207 *actualUrl = url; 208 } 209 } 210 211 return bytesRead; 212} 213 214ssize_t HTTPDownloader::fetchFile( 215 const char *url, sp<ABuffer> *out, String8 *actualUrl) { 216 ssize_t err = fetchBlock(url, out, 0, -1, 0, actualUrl, true /* reconnect */); 217 218 // close off the connection after use 219 mHTTPDataSource->disconnect(); 220 221 return err; 222} 223 224sp<M3UParser> HTTPDownloader::fetchPlaylist( 225 const char *url, uint8_t *curPlaylistHash, bool *unchanged) { 226 ALOGV("fetchPlaylist '%s'", url); 227 228 *unchanged = false; 229 230 sp<ABuffer> buffer; 231 String8 actualUrl; 232 ssize_t err = fetchFile(url, &buffer, &actualUrl); 233 234 // close off the connection after use 235 mHTTPDataSource->disconnect(); 236 237 if (err <= 0) { 238 return NULL; 239 } 240 241 // MD5 functionality is not available on the simulator, treat all 242 // playlists as changed. 243 244#if defined(__ANDROID__) 245 uint8_t hash[16]; 246 247 MD5_CTX m; 248 MD5_Init(&m); 249 MD5_Update(&m, buffer->data(), buffer->size()); 250 251 MD5_Final(hash, &m); 252 253 if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) { 254 // playlist unchanged 255 *unchanged = true; 256 257 return NULL; 258 } 259 260 if (curPlaylistHash != NULL) { 261 memcpy(curPlaylistHash, hash, sizeof(hash)); 262 } 263#endif 264 265 sp<M3UParser> playlist = 266 new M3UParser(actualUrl.string(), buffer->data(), buffer->size()); 267 268 if (playlist->initCheck() != OK) { 269 ALOGE("failed to parse .m3u8 playlist"); 270 271 return NULL; 272 } 273 274 return playlist; 275} 276 277} // namespace android 278