am 949f7d90: Merge "Work to support switching transport streams mid-stream and signalling discontinuities to the decoder." into gingerbread
Merge commit '949f7d9066e09768e570686a5695aaba4a1dafd0' into gingerbread-plus-aosp * commit '949f7d9066e09768e570686a5695aaba4a1dafd0': Work to support switching transport streams mid-stream and signalling discontinuities to the decoder.
This commit is contained in:
@@ -39,6 +39,7 @@ enum {
|
|||||||
|
|
||||||
// Not technically an error.
|
// Not technically an error.
|
||||||
INFO_FORMAT_CHANGED = MEDIA_ERROR_BASE - 12,
|
INFO_FORMAT_CHANGED = MEDIA_ERROR_BASE - 12,
|
||||||
|
INFO_DISCONTINUITY = MEDIA_ERROR_BASE - 13,
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace android
|
} // namespace android
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ struct OMXCodec : public MediaSource,
|
|||||||
public MediaBufferObserver {
|
public MediaBufferObserver {
|
||||||
enum CreationFlags {
|
enum CreationFlags {
|
||||||
kPreferSoftwareCodecs = 1,
|
kPreferSoftwareCodecs = 1,
|
||||||
|
kIgnoreCodecSpecificData = 2
|
||||||
};
|
};
|
||||||
static sp<MediaSource> Create(
|
static sp<MediaSource> Create(
|
||||||
const sp<IOMX> &omx,
|
const sp<IOMX> &omx,
|
||||||
@@ -248,7 +249,7 @@ private:
|
|||||||
|
|
||||||
void dumpPortStatus(OMX_U32 portIndex);
|
void dumpPortStatus(OMX_U32 portIndex);
|
||||||
|
|
||||||
status_t configureCodec(const sp<MetaData> &meta);
|
status_t configureCodec(const sp<MetaData> &meta, uint32_t flags);
|
||||||
|
|
||||||
static uint32_t getComponentQuirks(
|
static uint32_t getComponentQuirks(
|
||||||
const char *componentName, bool isEncoder);
|
const char *componentName, bool isEncoder);
|
||||||
|
|||||||
@@ -561,6 +561,39 @@ void AwesomePlayer::onBufferingUpdate() {
|
|||||||
postBufferingEvent_l();
|
postBufferingEvent_l();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AwesomePlayer::partial_reset_l() {
|
||||||
|
// Only reset the video renderer and shut down the video decoder.
|
||||||
|
// Then instantiate a new video decoder and resume video playback.
|
||||||
|
|
||||||
|
mVideoRenderer.clear();
|
||||||
|
|
||||||
|
if (mLastVideoBuffer) {
|
||||||
|
mLastVideoBuffer->release();
|
||||||
|
mLastVideoBuffer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mVideoBuffer) {
|
||||||
|
mVideoBuffer->release();
|
||||||
|
mVideoBuffer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
mVideoSource->stop();
|
||||||
|
|
||||||
|
// The following hack is necessary to ensure that the OMX
|
||||||
|
// component is completely released by the time we may try
|
||||||
|
// to instantiate it again.
|
||||||
|
wp<MediaSource> tmp = mVideoSource;
|
||||||
|
mVideoSource.clear();
|
||||||
|
while (tmp.promote() != NULL) {
|
||||||
|
usleep(1000);
|
||||||
|
}
|
||||||
|
IPCThreadState::self()->flushCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(OK, initVideoDecoder(OMXCodec::kIgnoreCodecSpecificData));
|
||||||
|
}
|
||||||
|
|
||||||
void AwesomePlayer::onStreamDone() {
|
void AwesomePlayer::onStreamDone() {
|
||||||
// Posted whenever any stream finishes playing.
|
// Posted whenever any stream finishes playing.
|
||||||
|
|
||||||
@@ -570,7 +603,21 @@ void AwesomePlayer::onStreamDone() {
|
|||||||
}
|
}
|
||||||
mStreamDoneEventPending = false;
|
mStreamDoneEventPending = false;
|
||||||
|
|
||||||
if (mStreamDoneStatus != ERROR_END_OF_STREAM) {
|
if (mStreamDoneStatus == INFO_DISCONTINUITY) {
|
||||||
|
// This special status is returned because an http live stream's
|
||||||
|
// video stream switched to a different bandwidth at this point
|
||||||
|
// and future data may have been encoded using different parameters.
|
||||||
|
// This requires us to shutdown the video decoder and reinstantiate
|
||||||
|
// a fresh one.
|
||||||
|
|
||||||
|
LOGV("INFO_DISCONTINUITY");
|
||||||
|
|
||||||
|
CHECK(mVideoSource != NULL);
|
||||||
|
|
||||||
|
partial_reset_l();
|
||||||
|
postVideoEvent_l();
|
||||||
|
return;
|
||||||
|
} else if (mStreamDoneStatus != ERROR_END_OF_STREAM) {
|
||||||
LOGV("MEDIA_ERROR %d", mStreamDoneStatus);
|
LOGV("MEDIA_ERROR %d", mStreamDoneStatus);
|
||||||
|
|
||||||
notifyListener_l(
|
notifyListener_l(
|
||||||
@@ -939,8 +986,7 @@ void AwesomePlayer::setVideoSource(sp<MediaSource> source) {
|
|||||||
mVideoTrack = source;
|
mVideoTrack = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
status_t AwesomePlayer::initVideoDecoder() {
|
status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {
|
||||||
uint32_t flags = 0;
|
|
||||||
mVideoSource = OMXCodec::Create(
|
mVideoSource = OMXCodec::Create(
|
||||||
mClient.interface(), mVideoTrack->getFormat(),
|
mClient.interface(), mVideoTrack->getFormat(),
|
||||||
false, // createEncoder
|
false, // createEncoder
|
||||||
|
|||||||
@@ -504,7 +504,7 @@ sp<MediaSource> OMXCodec::Create(
|
|||||||
|
|
||||||
observer->setCodec(codec);
|
observer->setCodec(codec);
|
||||||
|
|
||||||
err = codec->configureCodec(meta);
|
err = codec->configureCodec(meta, flags);
|
||||||
|
|
||||||
if (err == OK) {
|
if (err == OK) {
|
||||||
return codec;
|
return codec;
|
||||||
@@ -517,7 +517,8 @@ sp<MediaSource> OMXCodec::Create(
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
status_t OMXCodec::configureCodec(const sp<MetaData> &meta) {
|
status_t OMXCodec::configureCodec(const sp<MetaData> &meta, uint32_t flags) {
|
||||||
|
if (!(flags & kIgnoreCodecSpecificData)) {
|
||||||
uint32_t type;
|
uint32_t type;
|
||||||
const void *data;
|
const void *data;
|
||||||
size_t size;
|
size_t size;
|
||||||
@@ -606,6 +607,7 @@ status_t OMXCodec::configureCodec(const sp<MetaData> &meta) {
|
|||||||
return ERROR_UNSUPPORTED;
|
return ERROR_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int32_t bitRate = 0;
|
int32_t bitRate = 0;
|
||||||
if (mIsEncoder) {
|
if (mIsEncoder) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//#define LOG_NDEBUG 0
|
||||||
#define LOG_TAG "LiveSource"
|
#define LOG_TAG "LiveSource"
|
||||||
#include <utils/Log.h>
|
#include <utils/Log.h>
|
||||||
|
|
||||||
@@ -22,18 +23,21 @@
|
|||||||
#include "include/NuHTTPDataSource.h"
|
#include "include/NuHTTPDataSource.h"
|
||||||
|
|
||||||
#include <media/stagefright/foundation/ABuffer.h>
|
#include <media/stagefright/foundation/ABuffer.h>
|
||||||
|
#include <media/stagefright/FileSource.h>
|
||||||
#include <media/stagefright/MediaDebug.h>
|
#include <media/stagefright/MediaDebug.h>
|
||||||
|
|
||||||
namespace android {
|
namespace android {
|
||||||
|
|
||||||
LiveSource::LiveSource(const char *url)
|
LiveSource::LiveSource(const char *url)
|
||||||
: mURL(url),
|
: mMasterURL(url),
|
||||||
mInitCheck(NO_INIT),
|
mInitCheck(NO_INIT),
|
||||||
mPlaylistIndex(0),
|
mPlaylistIndex(0),
|
||||||
mLastFetchTimeUs(-1),
|
mLastFetchTimeUs(-1),
|
||||||
mSource(new NuHTTPDataSource),
|
mSource(new NuHTTPDataSource),
|
||||||
mSourceSize(0),
|
mSourceSize(0),
|
||||||
mOffsetBias(0) {
|
mOffsetBias(0),
|
||||||
|
mSignalDiscontinuity(false),
|
||||||
|
mPrevBandwidthIndex(-1) {
|
||||||
if (switchToNext()) {
|
if (switchToNext()) {
|
||||||
mInitCheck = OK;
|
mInitCheck = OK;
|
||||||
}
|
}
|
||||||
@@ -46,10 +50,113 @@ status_t LiveSource::initCheck() const {
|
|||||||
return mInitCheck;
|
return mInitCheck;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LiveSource::loadPlaylist() {
|
// static
|
||||||
|
int LiveSource::SortByBandwidth(const BandwidthItem *a, const BandwidthItem *b) {
|
||||||
|
if (a->mBandwidth < b->mBandwidth) {
|
||||||
|
return -1;
|
||||||
|
} else if (a->mBandwidth == b->mBandwidth) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double uniformRand() {
|
||||||
|
return (double)rand() / RAND_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LiveSource::loadPlaylist(bool fetchMaster) {
|
||||||
|
mSignalDiscontinuity = false;
|
||||||
|
|
||||||
mPlaylist.clear();
|
mPlaylist.clear();
|
||||||
mPlaylistIndex = 0;
|
mPlaylistIndex = 0;
|
||||||
|
|
||||||
|
if (fetchMaster) {
|
||||||
|
mPrevBandwidthIndex = -1;
|
||||||
|
|
||||||
|
sp<ABuffer> buffer;
|
||||||
|
status_t err = fetchM3U(mMasterURL.c_str(), &buffer);
|
||||||
|
|
||||||
|
if (err != OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mPlaylist = new M3UParser(
|
||||||
|
mMasterURL.c_str(), buffer->data(), buffer->size());
|
||||||
|
|
||||||
|
if (mPlaylist->initCheck() != OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mPlaylist->isVariantPlaylist()) {
|
||||||
|
for (size_t i = 0; i < mPlaylist->size(); ++i) {
|
||||||
|
BandwidthItem item;
|
||||||
|
|
||||||
|
sp<AMessage> meta;
|
||||||
|
mPlaylist->itemAt(i, &item.mURI, &meta);
|
||||||
|
|
||||||
|
unsigned long bandwidth;
|
||||||
|
CHECK(meta->findInt32("bandwidth", (int32_t *)&item.mBandwidth));
|
||||||
|
|
||||||
|
mBandwidthItems.push(item);
|
||||||
|
}
|
||||||
|
mPlaylist.clear();
|
||||||
|
|
||||||
|
// fall through
|
||||||
|
if (mBandwidthItems.size() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mBandwidthItems.sort(SortByBandwidth);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < mBandwidthItems.size(); ++i) {
|
||||||
|
const BandwidthItem &item = mBandwidthItems.itemAt(i);
|
||||||
|
LOGV("item #%d: %s", i, item.mURI.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mBandwidthItems.size() > 0) {
|
||||||
|
#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 = 0; // Lowest bandwidth stream
|
||||||
|
#endif
|
||||||
|
|
||||||
|
mURL = mBandwidthItems.editItemAt(index).mURI;
|
||||||
|
|
||||||
|
if (mPrevBandwidthIndex >= 0 && (size_t)mPrevBandwidthIndex != index) {
|
||||||
|
// If we switched streams because of bandwidth changes,
|
||||||
|
// we'll signal this discontinuity by inserting a
|
||||||
|
// special transport stream packet into the stream.
|
||||||
|
mSignalDiscontinuity = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
mPrevBandwidthIndex = index;
|
||||||
|
} else {
|
||||||
|
mURL = mMasterURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mPlaylist == NULL) {
|
||||||
sp<ABuffer> buffer;
|
sp<ABuffer> buffer;
|
||||||
status_t err = fetchM3U(mURL.c_str(), &buffer);
|
status_t err = fetchM3U(mURL.c_str(), &buffer);
|
||||||
|
|
||||||
@@ -63,6 +170,11 @@ bool LiveSource::loadPlaylist() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mPlaylist->isVariantPlaylist()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!mPlaylist->meta()->findInt32(
|
if (!mPlaylist->meta()->findInt32(
|
||||||
"media-sequence", &mFirstItemSequenceNumber)) {
|
"media-sequence", &mFirstItemSequenceNumber)) {
|
||||||
mFirstItemSequenceNumber = 0;
|
mFirstItemSequenceNumber = 0;
|
||||||
@@ -79,6 +191,8 @@ static int64_t getNowUs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool LiveSource::switchToNext() {
|
bool LiveSource::switchToNext() {
|
||||||
|
mSignalDiscontinuity = false;
|
||||||
|
|
||||||
mOffsetBias += mSourceSize;
|
mOffsetBias += mSourceSize;
|
||||||
mSourceSize = 0;
|
mSourceSize = 0;
|
||||||
|
|
||||||
@@ -87,7 +201,7 @@ bool LiveSource::switchToNext() {
|
|||||||
int32_t nextSequenceNumber =
|
int32_t nextSequenceNumber =
|
||||||
mPlaylistIndex + mFirstItemSequenceNumber;
|
mPlaylistIndex + mFirstItemSequenceNumber;
|
||||||
|
|
||||||
if (!loadPlaylist()) {
|
if (!loadPlaylist(mLastFetchTimeUs < 0)) {
|
||||||
LOGE("failed to reload playlist");
|
LOGE("failed to reload playlist");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -111,35 +225,62 @@ bool LiveSource::switchToNext() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AString uri;
|
AString uri;
|
||||||
CHECK(mPlaylist->itemAt(mPlaylistIndex, &uri));
|
sp<AMessage> itemMeta;
|
||||||
LOGI("switching to %s", uri.c_str());
|
CHECK(mPlaylist->itemAt(mPlaylistIndex, &uri, &itemMeta));
|
||||||
|
LOGV("switching to %s", uri.c_str());
|
||||||
|
|
||||||
if (mSource->connect(uri.c_str()) != OK
|
if (mSource->connect(uri.c_str()) != OK
|
||||||
|| mSource->getSize(&mSourceSize) != OK) {
|
|| mSource->getSize(&mSourceSize) != OK) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int32_t val;
|
||||||
|
if (itemMeta->findInt32("discontinuity", &val) && val != 0) {
|
||||||
|
mSignalDiscontinuity = true;
|
||||||
|
}
|
||||||
|
|
||||||
mPlaylistIndex++;
|
mPlaylistIndex++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const ssize_t kHeaderSize = 188;
|
||||||
|
|
||||||
ssize_t LiveSource::readAt(off_t offset, void *data, size_t size) {
|
ssize_t LiveSource::readAt(off_t offset, void *data, size_t size) {
|
||||||
CHECK(offset >= mOffsetBias);
|
CHECK(offset >= mOffsetBias);
|
||||||
offset -= mOffsetBias;
|
offset -= mOffsetBias;
|
||||||
|
|
||||||
if (offset >= mSourceSize) {
|
off_t delta = mSignalDiscontinuity ? kHeaderSize : 0;
|
||||||
CHECK_EQ(offset, mSourceSize);
|
|
||||||
|
|
||||||
offset -= mSourceSize;
|
if (offset >= mSourceSize + delta) {
|
||||||
|
CHECK_EQ(offset, mSourceSize + delta);
|
||||||
|
|
||||||
|
offset -= mSourceSize + delta;
|
||||||
if (!switchToNext()) {
|
if (!switchToNext()) {
|
||||||
return ERROR_END_OF_STREAM;
|
return ERROR_END_OF_STREAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mSignalDiscontinuity) {
|
||||||
|
LOGV("switchToNext changed streams");
|
||||||
|
} else {
|
||||||
|
LOGV("switchToNext stayed within the same stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
mOffsetBias += delta;
|
||||||
|
|
||||||
|
delta = mSignalDiscontinuity ? kHeaderSize : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset < delta) {
|
||||||
|
size_t avail = delta - offset;
|
||||||
|
memset(data, 0, avail);
|
||||||
|
return avail;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t numRead = 0;
|
size_t numRead = 0;
|
||||||
while (numRead < size) {
|
while (numRead < size) {
|
||||||
ssize_t n = mSource->readAt(
|
ssize_t n = mSource->readAt(
|
||||||
offset + numRead, (uint8_t *)data + numRead, size - numRead);
|
offset + numRead - delta,
|
||||||
|
(uint8_t *)data + numRead, size - numRead);
|
||||||
|
|
||||||
if (n <= 0) {
|
if (n <= 0) {
|
||||||
break;
|
break;
|
||||||
@@ -154,14 +295,24 @@ ssize_t LiveSource::readAt(off_t offset, void *data, size_t size) {
|
|||||||
status_t LiveSource::fetchM3U(const char *url, sp<ABuffer> *out) {
|
status_t LiveSource::fetchM3U(const char *url, sp<ABuffer> *out) {
|
||||||
*out = NULL;
|
*out = NULL;
|
||||||
|
|
||||||
|
sp<DataSource> source;
|
||||||
|
|
||||||
|
if (!strncasecmp(url, "file://", 7)) {
|
||||||
|
source = new FileSource(url + 7);
|
||||||
|
} else {
|
||||||
|
CHECK(!strncasecmp(url, "http://", 7));
|
||||||
|
|
||||||
status_t err = mSource->connect(url);
|
status_t err = mSource->connect(url);
|
||||||
|
|
||||||
if (err != OK) {
|
if (err != OK) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
source = mSource;
|
||||||
|
}
|
||||||
|
|
||||||
off_t size;
|
off_t size;
|
||||||
err = mSource->getSize(&size);
|
status_t err = source->getSize(&size);
|
||||||
|
|
||||||
if (err != OK) {
|
if (err != OK) {
|
||||||
return err;
|
return err;
|
||||||
@@ -170,7 +321,7 @@ status_t LiveSource::fetchM3U(const char *url, sp<ABuffer> *out) {
|
|||||||
sp<ABuffer> buffer = new ABuffer(size);
|
sp<ABuffer> buffer = new ABuffer(size);
|
||||||
size_t offset = 0;
|
size_t offset = 0;
|
||||||
while (offset < (size_t)size) {
|
while (offset < (size_t)size) {
|
||||||
ssize_t n = mSource->readAt(
|
ssize_t n = source->readAt(
|
||||||
offset, buffer->data() + offset, size - offset);
|
offset, buffer->data() + offset, size - offset);
|
||||||
|
|
||||||
if (n <= 0) {
|
if (n <= 0) {
|
||||||
|
|||||||
@@ -74,7 +74,8 @@ bool M3UParser::itemAt(size_t index, AString *uri, sp<AMessage> *meta) {
|
|||||||
static bool MakeURL(const char *baseURL, const char *url, AString *out) {
|
static bool MakeURL(const char *baseURL, const char *url, AString *out) {
|
||||||
out->clear();
|
out->clear();
|
||||||
|
|
||||||
if (strncasecmp("http://", baseURL, 7)) {
|
if (strncasecmp("http://", baseURL, 7)
|
||||||
|
&& strncasecmp("file://", baseURL, 7)) {
|
||||||
// Base URL must be absolute
|
// Base URL must be absolute
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -128,7 +129,12 @@ status_t M3UParser::parse(const void *_data, size_t size) {
|
|||||||
line.setTo(&data[offset], offsetLF - offset);
|
line.setTo(&data[offset], offsetLF - offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGI("#%s#", line.c_str());
|
// LOGI("#%s#", line.c_str());
|
||||||
|
|
||||||
|
if (line.empty()) {
|
||||||
|
offset = offsetLF + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (lineNo == 0 && line == "#EXTM3U") {
|
if (lineNo == 0 && line == "#EXTM3U") {
|
||||||
mIsExtM3U = true;
|
mIsExtM3U = true;
|
||||||
@@ -152,11 +158,20 @@ status_t M3UParser::parse(const void *_data, size_t size) {
|
|||||||
return ERROR_MALFORMED;
|
return ERROR_MALFORMED;
|
||||||
}
|
}
|
||||||
err = parseMetaData(line, &itemMeta, "duration");
|
err = parseMetaData(line, &itemMeta, "duration");
|
||||||
|
} else if (line.startsWith("#EXT-X-DISCONTINUITY")) {
|
||||||
|
if (mIsVariantPlaylist) {
|
||||||
|
return ERROR_MALFORMED;
|
||||||
|
}
|
||||||
|
if (itemMeta == NULL) {
|
||||||
|
itemMeta = new AMessage;
|
||||||
|
}
|
||||||
|
itemMeta->setInt32("discontinuity", true);
|
||||||
} else if (line.startsWith("#EXT-X-STREAM-INF")) {
|
} else if (line.startsWith("#EXT-X-STREAM-INF")) {
|
||||||
if (mMeta != NULL) {
|
if (mMeta != NULL) {
|
||||||
return ERROR_MALFORMED;
|
return ERROR_MALFORMED;
|
||||||
}
|
}
|
||||||
mIsVariantPlaylist = true;
|
mIsVariantPlaylist = true;
|
||||||
|
err = parseStreamInf(line, &itemMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err != OK) {
|
if (err != OK) {
|
||||||
@@ -214,6 +229,61 @@ status_t M3UParser::parseMetaData(
|
|||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
status_t M3UParser::parseStreamInf(
|
||||||
|
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());
|
||||||
|
|
||||||
|
if (!strcasecmp("bandwidth", key.c_str())) {
|
||||||
|
const char *s = val.c_str();
|
||||||
|
char *end;
|
||||||
|
unsigned long x = strtoul(s, &end, 10);
|
||||||
|
|
||||||
|
if (end == s || *end != '\0') {
|
||||||
|
// malformed
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (meta->get() == NULL) {
|
||||||
|
*meta = new AMessage;
|
||||||
|
}
|
||||||
|
(*meta)->setInt32("bandwidth", x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ private:
|
|||||||
status_t setDataSource_l(const sp<DataSource> &dataSource);
|
status_t setDataSource_l(const sp<DataSource> &dataSource);
|
||||||
status_t setDataSource_l(const sp<MediaExtractor> &extractor);
|
status_t setDataSource_l(const sp<MediaExtractor> &extractor);
|
||||||
void reset_l();
|
void reset_l();
|
||||||
|
void partial_reset_l();
|
||||||
status_t seekTo_l(int64_t timeUs);
|
status_t seekTo_l(int64_t timeUs);
|
||||||
status_t pause_l(bool at_eos = false);
|
status_t pause_l(bool at_eos = false);
|
||||||
void initRenderer_l();
|
void initRenderer_l();
|
||||||
@@ -231,7 +232,7 @@ private:
|
|||||||
status_t initAudioDecoder();
|
status_t initAudioDecoder();
|
||||||
|
|
||||||
void setVideoSource(sp<MediaSource> source);
|
void setVideoSource(sp<MediaSource> source);
|
||||||
status_t initVideoDecoder();
|
status_t initVideoDecoder(uint32_t flags = 0);
|
||||||
|
|
||||||
void onStreamDone();
|
void onStreamDone();
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,13 @@ protected:
|
|||||||
virtual ~LiveSource();
|
virtual ~LiveSource();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct BandwidthItem {
|
||||||
|
AString mURI;
|
||||||
|
unsigned long mBandwidth;
|
||||||
|
};
|
||||||
|
Vector<BandwidthItem> mBandwidthItems;
|
||||||
|
|
||||||
|
AString mMasterURL;
|
||||||
AString mURL;
|
AString mURL;
|
||||||
status_t mInitCheck;
|
status_t mInitCheck;
|
||||||
|
|
||||||
@@ -56,10 +63,15 @@ private:
|
|||||||
off_t mSourceSize;
|
off_t mSourceSize;
|
||||||
off_t mOffsetBias;
|
off_t mOffsetBias;
|
||||||
|
|
||||||
|
bool mSignalDiscontinuity;
|
||||||
|
ssize_t mPrevBandwidthIndex;
|
||||||
|
|
||||||
status_t fetchM3U(const char *url, sp<ABuffer> *buffer);
|
status_t fetchM3U(const char *url, sp<ABuffer> *buffer);
|
||||||
|
|
||||||
|
static int SortByBandwidth(const BandwidthItem *a, const BandwidthItem *b);
|
||||||
|
|
||||||
bool switchToNext();
|
bool switchToNext();
|
||||||
bool loadPlaylist();
|
bool loadPlaylist(bool fetchMaster);
|
||||||
|
|
||||||
DISALLOW_EVIL_CONSTRUCTORS(LiveSource);
|
DISALLOW_EVIL_CONSTRUCTORS(LiveSource);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ private:
|
|||||||
static status_t parseMetaData(
|
static status_t parseMetaData(
|
||||||
const AString &line, sp<AMessage> *meta, const char *key);
|
const AString &line, sp<AMessage> *meta, const char *key);
|
||||||
|
|
||||||
|
static status_t parseStreamInf(
|
||||||
|
const AString &line, sp<AMessage> *meta);
|
||||||
|
|
||||||
static status_t ParseInt32(const char *s, int32_t *x);
|
static status_t ParseInt32(const char *s, int32_t *x);
|
||||||
|
|
||||||
DISALLOW_EVIL_CONSTRUCTORS(M3UParser);
|
DISALLOW_EVIL_CONSTRUCTORS(M3UParser);
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ struct ATSParser::Program : public RefBase {
|
|||||||
unsigned pid, unsigned payload_unit_start_indicator,
|
unsigned pid, unsigned payload_unit_start_indicator,
|
||||||
ABitReader *br);
|
ABitReader *br);
|
||||||
|
|
||||||
|
void signalDiscontinuity();
|
||||||
|
|
||||||
sp<MediaSource> getSource(SourceType type);
|
sp<MediaSource> getSource(SourceType type);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -67,6 +69,8 @@ struct ATSParser::Stream : public RefBase {
|
|||||||
unsigned payload_unit_start_indicator,
|
unsigned payload_unit_start_indicator,
|
||||||
ABitReader *br);
|
ABitReader *br);
|
||||||
|
|
||||||
|
void signalDiscontinuity();
|
||||||
|
|
||||||
sp<MediaSource> getSource(SourceType type);
|
sp<MediaSource> getSource(SourceType type);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -124,6 +128,12 @@ bool ATSParser::Program::parsePID(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ATSParser::Program::signalDiscontinuity() {
|
||||||
|
for (size_t i = 0; i < mStreams.size(); ++i) {
|
||||||
|
mStreams.editValueAt(i)->signalDiscontinuity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ATSParser::Program::parseProgramMap(ABitReader *br) {
|
void ATSParser::Program::parseProgramMap(ABitReader *br) {
|
||||||
unsigned table_id = br->getBits(8);
|
unsigned table_id = br->getBits(8);
|
||||||
LOGV(" table_id = %u", table_id);
|
LOGV(" table_id = %u", table_id);
|
||||||
@@ -271,6 +281,19 @@ void ATSParser::Stream::parse(
|
|||||||
mBuffer->setRange(0, mBuffer->size() + payloadSizeBits / 8);
|
mBuffer->setRange(0, mBuffer->size() + payloadSizeBits / 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ATSParser::Stream::signalDiscontinuity() {
|
||||||
|
LOGV("Stream discontinuity");
|
||||||
|
mPayloadStarted = false;
|
||||||
|
mBuffer->setRange(0, 0);
|
||||||
|
|
||||||
|
mQueue.clear();
|
||||||
|
|
||||||
|
if (mStreamType == 0x1b && mSource != NULL) {
|
||||||
|
// Don't signal discontinuities on audio streams.
|
||||||
|
mSource->queueDiscontinuity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ATSParser::Stream::parsePES(ABitReader *br) {
|
void ATSParser::Stream::parsePES(ABitReader *br) {
|
||||||
unsigned packet_startcode_prefix = br->getBits(24);
|
unsigned packet_startcode_prefix = br->getBits(24);
|
||||||
|
|
||||||
@@ -459,7 +482,10 @@ void ATSParser::Stream::onPayloadData(
|
|||||||
mSource = new AnotherPacketSource(meta);
|
mSource = new AnotherPacketSource(meta);
|
||||||
mSource->queueAccessUnit(accessUnit);
|
mSource->queueAccessUnit(accessUnit);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (mQueue.getFormat() != NULL) {
|
||||||
|
// After a discontinuity we invalidate the queue's format
|
||||||
|
// and won't enqueue any access units to the source until
|
||||||
|
// the queue has reestablished the new format.
|
||||||
mSource->queueAccessUnit(accessUnit);
|
mSource->queueAccessUnit(accessUnit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -489,6 +515,12 @@ void ATSParser::feedTSPacket(const void *data, size_t size) {
|
|||||||
parseTS(&br);
|
parseTS(&br);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ATSParser::signalDiscontinuity() {
|
||||||
|
for (size_t i = 0; i < mPrograms.size(); ++i) {
|
||||||
|
mPrograms.editItemAt(i)->signalDiscontinuity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ATSParser::parseProgramAssociationTable(ABitReader *br) {
|
void ATSParser::parseProgramAssociationTable(ABitReader *br) {
|
||||||
unsigned table_id = br->getBits(8);
|
unsigned table_id = br->getBits(8);
|
||||||
LOGV(" table_id = %u", table_id);
|
LOGV(" table_id = %u", table_id);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ struct ATSParser : public RefBase {
|
|||||||
ATSParser();
|
ATSParser();
|
||||||
|
|
||||||
void feedTSPacket(const void *data, size_t size);
|
void feedTSPacket(const void *data, size_t size);
|
||||||
|
void signalDiscontinuity();
|
||||||
|
|
||||||
enum SourceType {
|
enum SourceType {
|
||||||
AVC_VIDEO,
|
AVC_VIDEO,
|
||||||
|
|||||||
@@ -59,7 +59,13 @@ status_t AnotherPacketSource::read(
|
|||||||
|
|
||||||
if (!mBuffers.empty()) {
|
if (!mBuffers.empty()) {
|
||||||
const sp<ABuffer> buffer = *mBuffers.begin();
|
const sp<ABuffer> buffer = *mBuffers.begin();
|
||||||
|
mBuffers.erase(mBuffers.begin());
|
||||||
|
|
||||||
|
int32_t discontinuity;
|
||||||
|
if (buffer->meta()->findInt32("discontinuity", &discontinuity)
|
||||||
|
&& discontinuity) {
|
||||||
|
return INFO_DISCONTINUITY;
|
||||||
|
} else {
|
||||||
uint64_t timeUs;
|
uint64_t timeUs;
|
||||||
CHECK(buffer->meta()->findInt64(
|
CHECK(buffer->meta()->findInt64(
|
||||||
"time", (int64_t *)&timeUs));
|
"time", (int64_t *)&timeUs));
|
||||||
@@ -71,10 +77,9 @@ status_t AnotherPacketSource::read(
|
|||||||
|
|
||||||
memcpy(mediaBuffer->data(), buffer->data(), buffer->size());
|
memcpy(mediaBuffer->data(), buffer->data(), buffer->size());
|
||||||
*out = mediaBuffer;
|
*out = mediaBuffer;
|
||||||
|
|
||||||
mBuffers.erase(mBuffers.begin());
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return mEOSResult;
|
return mEOSResult;
|
||||||
}
|
}
|
||||||
@@ -91,6 +96,15 @@ void AnotherPacketSource::queueAccessUnit(const sp<ABuffer> &buffer) {
|
|||||||
mCondition.signal();
|
mCondition.signal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AnotherPacketSource::queueDiscontinuity() {
|
||||||
|
sp<ABuffer> buffer = new ABuffer(0);
|
||||||
|
buffer->meta()->setInt32("discontinuity", true);
|
||||||
|
|
||||||
|
Mutex::Autolock autoLock(mLock);
|
||||||
|
mBuffers.push_back(buffer);
|
||||||
|
mCondition.signal();
|
||||||
|
}
|
||||||
|
|
||||||
void AnotherPacketSource::signalEOS(status_t result) {
|
void AnotherPacketSource::signalEOS(status_t result) {
|
||||||
CHECK(result != OK);
|
CHECK(result != OK);
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ struct AnotherPacketSource : public MediaSource {
|
|||||||
bool hasBufferAvailable(status_t *finalResult);
|
bool hasBufferAvailable(status_t *finalResult);
|
||||||
|
|
||||||
void queueAccessUnit(const sp<ABuffer> &buffer);
|
void queueAccessUnit(const sp<ABuffer> &buffer);
|
||||||
|
void queueDiscontinuity();
|
||||||
void signalEOS(status_t result);
|
void signalEOS(status_t result);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -115,6 +115,11 @@ static status_t getNextNALUnit(
|
|||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ElementaryStreamQueue::clear() {
|
||||||
|
mBuffer->setRange(0, 0);
|
||||||
|
mFormat.clear();
|
||||||
|
}
|
||||||
|
|
||||||
status_t ElementaryStreamQueue::appendData(
|
status_t ElementaryStreamQueue::appendData(
|
||||||
const void *data, size_t size, int64_t timeUs) {
|
const void *data, size_t size, int64_t timeUs) {
|
||||||
if (mBuffer == NULL || mBuffer->size() == 0) {
|
if (mBuffer == NULL || mBuffer->size() == 0) {
|
||||||
@@ -147,7 +152,7 @@ status_t ElementaryStreamQueue::appendData(
|
|||||||
if (mBuffer == NULL || neededSize > mBuffer->capacity()) {
|
if (mBuffer == NULL || neededSize > mBuffer->capacity()) {
|
||||||
neededSize = (neededSize + 65535) & ~65535;
|
neededSize = (neededSize + 65535) & ~65535;
|
||||||
|
|
||||||
LOGI("resizing buffer to size %d", neededSize);
|
LOGV("resizing buffer to size %d", neededSize);
|
||||||
|
|
||||||
sp<ABuffer> buffer = new ABuffer(neededSize);
|
sp<ABuffer> buffer = new ABuffer(neededSize);
|
||||||
if (mBuffer != NULL) {
|
if (mBuffer != NULL) {
|
||||||
@@ -498,6 +503,8 @@ sp<MetaData> ElementaryStreamQueue::MakeAVCCodecSpecificData(
|
|||||||
meta->setInt32(kKeyWidth, width);
|
meta->setInt32(kKeyWidth, width);
|
||||||
meta->setInt32(kKeyHeight, height);
|
meta->setInt32(kKeyHeight, height);
|
||||||
|
|
||||||
|
LOGI("found AVC codec config (%d x %d)", width, height);
|
||||||
|
|
||||||
return meta;
|
return meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ struct ElementaryStreamQueue {
|
|||||||
ElementaryStreamQueue(Mode mode);
|
ElementaryStreamQueue(Mode mode);
|
||||||
|
|
||||||
status_t appendData(const void *data, size_t size, int64_t timeUs);
|
status_t appendData(const void *data, size_t size, int64_t timeUs);
|
||||||
|
void clear();
|
||||||
|
|
||||||
sp<ABuffer> dequeueAccessUnit();
|
sp<ABuffer> dequeueAccessUnit();
|
||||||
|
|
||||||
|
|||||||
@@ -165,18 +165,26 @@ void MPEG2TSExtractor::init() {
|
|||||||
LOGI("haveAudio=%d, haveVideo=%d", haveAudio, haveVideo);
|
LOGI("haveAudio=%d, haveVideo=%d", haveAudio, haveVideo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isDiscontinuity(const uint8_t *data, ssize_t size) {
|
||||||
|
return size == 188 && data[0] == 0x00;
|
||||||
|
}
|
||||||
|
|
||||||
status_t MPEG2TSExtractor::feedMore() {
|
status_t MPEG2TSExtractor::feedMore() {
|
||||||
Mutex::Autolock autoLock(mLock);
|
Mutex::Autolock autoLock(mLock);
|
||||||
|
|
||||||
uint8_t packet[kTSPacketSize];
|
uint8_t packet[kTSPacketSize];
|
||||||
ssize_t n = mDataSource->readAt(mOffset, packet, kTSPacketSize);
|
ssize_t n = mDataSource->readAt(mOffset, packet, kTSPacketSize);
|
||||||
|
|
||||||
if (n < (ssize_t)kTSPacketSize) {
|
if (isDiscontinuity(packet, n)) {
|
||||||
|
LOGI("XXX discontinuity detected");
|
||||||
|
mParser->signalDiscontinuity();
|
||||||
|
} else if (n < (ssize_t)kTSPacketSize) {
|
||||||
return (n < 0) ? (status_t)n : ERROR_END_OF_STREAM;
|
return (n < 0) ? (status_t)n : ERROR_END_OF_STREAM;
|
||||||
|
} else {
|
||||||
|
mParser->feedTSPacket(packet, kTSPacketSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
mOffset += kTSPacketSize;
|
mOffset += n;
|
||||||
mParser->feedTSPacket(packet, kTSPacketSize);
|
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user