2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 //#define LOG_NDEBUG 0
18 #define LOG_TAG "LiveSource"
19 #include <utils/Log.h>
21 #include "include/LiveSource.h"
22 #include "include/M3UParser.h"
23 #include "include/NuHTTPDataSource.h"
25 #include <media/stagefright/foundation/ABuffer.h>
26 #include <media/stagefright/FileSource.h>
27 #include <media/stagefright/MediaDebug.h>
31 LiveSource::LiveSource(const char *url)
37 mSource(new NuHTTPDataSource),
40 mSignalDiscontinuity(false),
41 mPrevBandwidthIndex(-1) {
45 determineSeekability();
49 LiveSource::~LiveSource() {
52 status_t LiveSource::initCheck() const {
57 int LiveSource::SortByBandwidth(const BandwidthItem *a, const BandwidthItem *b) {
58 if (a->mBandwidth < b->mBandwidth) {
60 } else if (a->mBandwidth == b->mBandwidth) {
67 static double uniformRand() {
68 return (double)rand() / RAND_MAX;
71 bool LiveSource::loadPlaylist(bool fetchMaster) {
72 mSignalDiscontinuity = false;
78 mPrevBandwidthIndex = -1;
81 status_t err = fetchM3U(mMasterURL.c_str(), &buffer);
87 mPlaylist = new M3UParser(
88 mMasterURL.c_str(), buffer->data(), buffer->size());
90 if (mPlaylist->initCheck() != OK) {
94 if (mPlaylist->isVariantPlaylist()) {
95 for (size_t i = 0; i < mPlaylist->size(); ++i) {
99 mPlaylist->itemAt(i, &item.mURI, &meta);
101 unsigned long bandwidth;
102 CHECK(meta->findInt32("bandwidth", (int32_t *)&item.mBandwidth));
104 mBandwidthItems.push(item);
109 if (mBandwidthItems.size() == 0) {
113 mBandwidthItems.sort(SortByBandwidth);
115 for (size_t i = 0; i < mBandwidthItems.size(); ++i) {
116 const BandwidthItem &item = mBandwidthItems.itemAt(i);
117 LOGV("item #%d: %s", i, item.mURI.c_str());
122 if (mBandwidthItems.size() > 0) {
124 // Change bandwidth at random()
125 size_t index = uniformRand() * mBandwidthItems.size();
127 // There's a 50% chance to stay on the current bandwidth and
128 // a 50% chance to switch to the next higher bandwidth (wrapping around
131 if (uniformRand() < 0.5) {
132 index = mPrevBandwidthIndex < 0 ? 0 : (size_t)mPrevBandwidthIndex;
134 if (mPrevBandwidthIndex < 0) {
137 index = mPrevBandwidthIndex + 1;
138 if (index == mBandwidthItems.size()) {
144 // Stay on the lowest bandwidth available.
145 size_t index = mBandwidthItems.size() - 1; // Highest bandwidth stream
148 mURL = mBandwidthItems.editItemAt(index).mURI;
150 if (mPrevBandwidthIndex >= 0 && (size_t)mPrevBandwidthIndex != index) {
151 // If we switched streams because of bandwidth changes,
152 // we'll signal this discontinuity by inserting a
153 // special transport stream packet into the stream.
154 mSignalDiscontinuity = true;
157 mPrevBandwidthIndex = index;
162 if (mPlaylist == NULL) {
164 status_t err = fetchM3U(mURL.c_str(), &buffer);
170 mPlaylist = new M3UParser(mURL.c_str(), buffer->data(), buffer->size());
172 if (mPlaylist->initCheck() != OK) {
176 if (mPlaylist->isVariantPlaylist()) {
181 if (!mPlaylist->meta()->findInt32(
182 "media-sequence", &mFirstItemSequenceNumber)) {
183 mFirstItemSequenceNumber = 0;
189 static int64_t getNowUs() {
191 gettimeofday(&tv, NULL);
193 return (int64_t)tv.tv_usec + tv.tv_sec * 1000000ll;
196 bool LiveSource::switchToNext() {
197 mSignalDiscontinuity = false;
199 mOffsetBias += mSourceSize;
202 if (mLastFetchTimeUs < 0 || getNowUs() >= mLastFetchTimeUs + 15000000ll
203 || mPlaylistIndex == mPlaylist->size()) {
204 int32_t nextSequenceNumber =
205 mPlaylistIndex + mFirstItemSequenceNumber;
207 if (!loadPlaylist(mLastFetchTimeUs < 0)) {
208 LOGE("failed to reload playlist");
212 if (mLastFetchTimeUs < 0) {
215 if (nextSequenceNumber < mFirstItemSequenceNumber
216 || nextSequenceNumber
217 >= mFirstItemSequenceNumber + (int32_t)mPlaylist->size()) {
218 LOGE("Cannot find sequence number %d in new playlist",
224 mPlaylistIndex = nextSequenceNumber - mFirstItemSequenceNumber;
227 mLastFetchTimeUs = getNowUs();
231 sp<AMessage> itemMeta;
232 CHECK(mPlaylist->itemAt(mPlaylistIndex, &uri, &itemMeta));
233 LOGV("switching to %s", uri.c_str());
235 if (mSource->connect(uri.c_str()) != OK
236 || mSource->getSize(&mSourceSize) != OK) {
241 if (itemMeta->findInt32("discontinuity", &val) && val != 0) {
242 mSignalDiscontinuity = true;
249 static const ssize_t kHeaderSize = 188;
251 ssize_t LiveSource::readAt(off_t offset, void *data, size_t size) {
252 CHECK(offset >= mOffsetBias);
253 offset -= mOffsetBias;
255 off_t delta = mSignalDiscontinuity ? kHeaderSize : 0;
257 if (offset >= mSourceSize + delta) {
258 CHECK_EQ(offset, mSourceSize + delta);
260 offset -= mSourceSize + delta;
261 if (!switchToNext()) {
262 return ERROR_END_OF_STREAM;
265 if (mSignalDiscontinuity) {
266 LOGV("switchToNext changed streams");
268 LOGV("switchToNext stayed within the same stream");
271 mOffsetBias += delta;
273 delta = mSignalDiscontinuity ? kHeaderSize : 0;
276 if (offset < delta) {
277 size_t avail = delta - offset;
278 memset(data, 0, avail);
283 while (numRead < size) {
284 ssize_t n = mSource->readAt(
285 offset + numRead - delta,
286 (uint8_t *)data + numRead, size - numRead);
298 status_t LiveSource::fetchM3U(const char *url, sp<ABuffer> *out) {
301 sp<DataSource> source;
303 if (!strncasecmp(url, "file://", 7)) {
304 source = new FileSource(url + 7);
306 CHECK(!strncasecmp(url, "http://", 7));
308 status_t err = mSource->connect(url);
318 status_t err = source->getSize(&size);
324 sp<ABuffer> buffer = new ABuffer(size);
325 buffer->setRange(0, 0);
328 size_t bufferRemaining = buffer->capacity() - buffer->size();
330 if (bufferRemaining == 0) {
331 bufferRemaining = 32768;
333 LOGV("increasing download buffer to %d bytes",
334 buffer->size() + bufferRemaining);
336 sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining);
337 memcpy(copy->data(), buffer->data(), buffer->size());
338 copy->setRange(0, buffer->size());
343 ssize_t n = source->readAt(
344 buffer->size(), buffer->data() + buffer->size(),
355 buffer->setRange(0, buffer->size() + (size_t)n);
363 bool LiveSource::seekTo(int64_t seekTimeUs) {
364 LOGV("seek to %lld us", seekTimeUs);
366 if (!mPlaylist->isComplete()) {
370 int32_t targetDuration;
371 if (!mPlaylist->meta()->findInt32("target-duration", &targetDuration)) {
375 int64_t seekTimeSecs = (seekTimeUs + 500000ll) / 1000000ll;
377 int64_t index = seekTimeSecs / targetDuration;
379 if (index < 0 || index >= mPlaylist->size()) {
383 size_t newPlaylistIndex = mFirstItemSequenceNumber + index;
385 if (newPlaylistIndex == mPlaylistIndex) {
389 mPlaylistIndex = newPlaylistIndex;
394 LOGV("seeking to index %lld", index);
399 bool LiveSource::getDuration(int64_t *durationUs) const {
400 if (mDurationUs >= 0) {
401 *durationUs = mDurationUs;
409 bool LiveSource::isSeekable() const {
410 return mDurationUs >= 0;
413 void LiveSource::determineSeekability() {
416 if (!mPlaylist->isComplete()) {
420 int32_t targetDuration;
421 if (!mPlaylist->meta()->findInt32("target-duration", &targetDuration)) {
425 mDurationUs = targetDuration * 1000000ll * mPlaylist->size();
428 } // namespace android