Merge "Work to support switching transport streams mid-stream and signalling discontinuities to the decoder." into gingerbread
This commit is contained in:
committed by
Android (Google) Code Review
commit
949f7d9066
@@ -39,6 +39,7 @@ enum {
|
||||
|
||||
// Not technically an error.
|
||||
INFO_FORMAT_CHANGED = MEDIA_ERROR_BASE - 12,
|
||||
INFO_DISCONTINUITY = MEDIA_ERROR_BASE - 13,
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
|
||||
@@ -32,7 +32,8 @@ struct CodecProfileLevel;
|
||||
struct OMXCodec : public MediaSource,
|
||||
public MediaBufferObserver {
|
||||
enum CreationFlags {
|
||||
kPreferSoftwareCodecs = 1,
|
||||
kPreferSoftwareCodecs = 1,
|
||||
kIgnoreCodecSpecificData = 2
|
||||
};
|
||||
static sp<MediaSource> Create(
|
||||
const sp<IOMX> &omx,
|
||||
@@ -248,7 +249,7 @@ private:
|
||||
|
||||
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(
|
||||
const char *componentName, bool isEncoder);
|
||||
|
||||
@@ -561,6 +561,39 @@ void AwesomePlayer::onBufferingUpdate() {
|
||||
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() {
|
||||
// Posted whenever any stream finishes playing.
|
||||
|
||||
@@ -570,7 +603,21 @@ void AwesomePlayer::onStreamDone() {
|
||||
}
|
||||
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);
|
||||
|
||||
notifyListener_l(
|
||||
@@ -939,8 +986,7 @@ void AwesomePlayer::setVideoSource(sp<MediaSource> source) {
|
||||
mVideoTrack = source;
|
||||
}
|
||||
|
||||
status_t AwesomePlayer::initVideoDecoder() {
|
||||
uint32_t flags = 0;
|
||||
status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {
|
||||
mVideoSource = OMXCodec::Create(
|
||||
mClient.interface(), mVideoTrack->getFormat(),
|
||||
false, // createEncoder
|
||||
|
||||
@@ -504,7 +504,7 @@ sp<MediaSource> OMXCodec::Create(
|
||||
|
||||
observer->setCodec(codec);
|
||||
|
||||
err = codec->configureCodec(meta);
|
||||
err = codec->configureCodec(meta, flags);
|
||||
|
||||
if (err == OK) {
|
||||
return codec;
|
||||
@@ -517,93 +517,95 @@ sp<MediaSource> OMXCodec::Create(
|
||||
return NULL;
|
||||
}
|
||||
|
||||
status_t OMXCodec::configureCodec(const sp<MetaData> &meta) {
|
||||
uint32_t type;
|
||||
const void *data;
|
||||
size_t size;
|
||||
if (meta->findData(kKeyESDS, &type, &data, &size)) {
|
||||
ESDS esds((const char *)data, size);
|
||||
CHECK_EQ(esds.InitCheck(), OK);
|
||||
status_t OMXCodec::configureCodec(const sp<MetaData> &meta, uint32_t flags) {
|
||||
if (!(flags & kIgnoreCodecSpecificData)) {
|
||||
uint32_t type;
|
||||
const void *data;
|
||||
size_t size;
|
||||
if (meta->findData(kKeyESDS, &type, &data, &size)) {
|
||||
ESDS esds((const char *)data, size);
|
||||
CHECK_EQ(esds.InitCheck(), OK);
|
||||
|
||||
const void *codec_specific_data;
|
||||
size_t codec_specific_data_size;
|
||||
esds.getCodecSpecificInfo(
|
||||
&codec_specific_data, &codec_specific_data_size);
|
||||
const void *codec_specific_data;
|
||||
size_t codec_specific_data_size;
|
||||
esds.getCodecSpecificInfo(
|
||||
&codec_specific_data, &codec_specific_data_size);
|
||||
|
||||
addCodecSpecificData(
|
||||
codec_specific_data, codec_specific_data_size);
|
||||
} else if (meta->findData(kKeyAVCC, &type, &data, &size)) {
|
||||
// Parse the AVCDecoderConfigurationRecord
|
||||
addCodecSpecificData(
|
||||
codec_specific_data, codec_specific_data_size);
|
||||
} else if (meta->findData(kKeyAVCC, &type, &data, &size)) {
|
||||
// Parse the AVCDecoderConfigurationRecord
|
||||
|
||||
const uint8_t *ptr = (const uint8_t *)data;
|
||||
const uint8_t *ptr = (const uint8_t *)data;
|
||||
|
||||
CHECK(size >= 7);
|
||||
CHECK_EQ(ptr[0], 1); // configurationVersion == 1
|
||||
uint8_t profile = ptr[1];
|
||||
uint8_t level = ptr[3];
|
||||
CHECK(size >= 7);
|
||||
CHECK_EQ(ptr[0], 1); // configurationVersion == 1
|
||||
uint8_t profile = ptr[1];
|
||||
uint8_t level = ptr[3];
|
||||
|
||||
// There is decodable content out there that fails the following
|
||||
// assertion, let's be lenient for now...
|
||||
// CHECK((ptr[4] >> 2) == 0x3f); // reserved
|
||||
// There is decodable content out there that fails the following
|
||||
// assertion, let's be lenient for now...
|
||||
// CHECK((ptr[4] >> 2) == 0x3f); // reserved
|
||||
|
||||
size_t lengthSize = 1 + (ptr[4] & 3);
|
||||
size_t lengthSize = 1 + (ptr[4] & 3);
|
||||
|
||||
// commented out check below as H264_QVGA_500_NO_AUDIO.3gp
|
||||
// violates it...
|
||||
// CHECK((ptr[5] >> 5) == 7); // reserved
|
||||
// commented out check below as H264_QVGA_500_NO_AUDIO.3gp
|
||||
// violates it...
|
||||
// CHECK((ptr[5] >> 5) == 7); // reserved
|
||||
|
||||
size_t numSeqParameterSets = ptr[5] & 31;
|
||||
size_t numSeqParameterSets = ptr[5] & 31;
|
||||
|
||||
ptr += 6;
|
||||
size -= 6;
|
||||
ptr += 6;
|
||||
size -= 6;
|
||||
|
||||
for (size_t i = 0; i < numSeqParameterSets; ++i) {
|
||||
CHECK(size >= 2);
|
||||
size_t length = U16_AT(ptr);
|
||||
for (size_t i = 0; i < numSeqParameterSets; ++i) {
|
||||
CHECK(size >= 2);
|
||||
size_t length = U16_AT(ptr);
|
||||
|
||||
ptr += 2;
|
||||
size -= 2;
|
||||
ptr += 2;
|
||||
size -= 2;
|
||||
|
||||
CHECK(size >= length);
|
||||
CHECK(size >= length);
|
||||
|
||||
addCodecSpecificData(ptr, length);
|
||||
addCodecSpecificData(ptr, length);
|
||||
|
||||
ptr += length;
|
||||
size -= length;
|
||||
}
|
||||
ptr += length;
|
||||
size -= length;
|
||||
}
|
||||
|
||||
CHECK(size >= 1);
|
||||
size_t numPictureParameterSets = *ptr;
|
||||
++ptr;
|
||||
--size;
|
||||
CHECK(size >= 1);
|
||||
size_t numPictureParameterSets = *ptr;
|
||||
++ptr;
|
||||
--size;
|
||||
|
||||
for (size_t i = 0; i < numPictureParameterSets; ++i) {
|
||||
CHECK(size >= 2);
|
||||
size_t length = U16_AT(ptr);
|
||||
for (size_t i = 0; i < numPictureParameterSets; ++i) {
|
||||
CHECK(size >= 2);
|
||||
size_t length = U16_AT(ptr);
|
||||
|
||||
ptr += 2;
|
||||
size -= 2;
|
||||
ptr += 2;
|
||||
size -= 2;
|
||||
|
||||
CHECK(size >= length);
|
||||
CHECK(size >= length);
|
||||
|
||||
addCodecSpecificData(ptr, length);
|
||||
addCodecSpecificData(ptr, length);
|
||||
|
||||
ptr += length;
|
||||
size -= length;
|
||||
}
|
||||
ptr += length;
|
||||
size -= length;
|
||||
}
|
||||
|
||||
CODEC_LOGV(
|
||||
"AVC profile = %d (%s), level = %d",
|
||||
(int)profile, AVCProfileToString(profile), level);
|
||||
CODEC_LOGV(
|
||||
"AVC profile = %d (%s), level = %d",
|
||||
(int)profile, AVCProfileToString(profile), level);
|
||||
|
||||
if (!strcmp(mComponentName, "OMX.TI.Video.Decoder")
|
||||
&& (profile != kAVCProfileBaseline || level > 30)) {
|
||||
// This stream exceeds the decoder's capabilities. The decoder
|
||||
// does not handle this gracefully and would clobber the heap
|
||||
// and wreak havoc instead...
|
||||
if (!strcmp(mComponentName, "OMX.TI.Video.Decoder")
|
||||
&& (profile != kAVCProfileBaseline || level > 30)) {
|
||||
// This stream exceeds the decoder's capabilities. The decoder
|
||||
// does not handle this gracefully and would clobber the heap
|
||||
// and wreak havoc instead...
|
||||
|
||||
LOGE("Profile and/or level exceed the decoder's capabilities.");
|
||||
return ERROR_UNSUPPORTED;
|
||||
LOGE("Profile and/or level exceed the decoder's capabilities.");
|
||||
return ERROR_UNSUPPORTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//#define LOG_NDEBUG 0
|
||||
#define LOG_TAG "LiveSource"
|
||||
#include <utils/Log.h>
|
||||
|
||||
@@ -22,18 +23,21 @@
|
||||
#include "include/NuHTTPDataSource.h"
|
||||
|
||||
#include <media/stagefright/foundation/ABuffer.h>
|
||||
#include <media/stagefright/FileSource.h>
|
||||
#include <media/stagefright/MediaDebug.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
LiveSource::LiveSource(const char *url)
|
||||
: mURL(url),
|
||||
: mMasterURL(url),
|
||||
mInitCheck(NO_INIT),
|
||||
mPlaylistIndex(0),
|
||||
mLastFetchTimeUs(-1),
|
||||
mSource(new NuHTTPDataSource),
|
||||
mSourceSize(0),
|
||||
mOffsetBias(0) {
|
||||
mOffsetBias(0),
|
||||
mSignalDiscontinuity(false),
|
||||
mPrevBandwidthIndex(-1) {
|
||||
if (switchToNext()) {
|
||||
mInitCheck = OK;
|
||||
}
|
||||
@@ -46,21 +50,129 @@ status_t LiveSource::initCheck() const {
|
||||
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();
|
||||
mPlaylistIndex = 0;
|
||||
|
||||
sp<ABuffer> buffer;
|
||||
status_t err = fetchM3U(mURL.c_str(), &buffer);
|
||||
if (fetchMaster) {
|
||||
mPrevBandwidthIndex = -1;
|
||||
|
||||
if (err != OK) {
|
||||
return false;
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mPlaylist = new M3UParser(mURL.c_str(), buffer->data(), buffer->size());
|
||||
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
|
||||
|
||||
if (mPlaylist->initCheck() != OK) {
|
||||
return false;
|
||||
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;
|
||||
status_t err = fetchM3U(mURL.c_str(), &buffer);
|
||||
|
||||
if (err != OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mPlaylist = new M3UParser(mURL.c_str(), buffer->data(), buffer->size());
|
||||
|
||||
if (mPlaylist->initCheck() != OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mPlaylist->isVariantPlaylist()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mPlaylist->meta()->findInt32(
|
||||
@@ -79,6 +191,8 @@ static int64_t getNowUs() {
|
||||
}
|
||||
|
||||
bool LiveSource::switchToNext() {
|
||||
mSignalDiscontinuity = false;
|
||||
|
||||
mOffsetBias += mSourceSize;
|
||||
mSourceSize = 0;
|
||||
|
||||
@@ -87,7 +201,7 @@ bool LiveSource::switchToNext() {
|
||||
int32_t nextSequenceNumber =
|
||||
mPlaylistIndex + mFirstItemSequenceNumber;
|
||||
|
||||
if (!loadPlaylist()) {
|
||||
if (!loadPlaylist(mLastFetchTimeUs < 0)) {
|
||||
LOGE("failed to reload playlist");
|
||||
return false;
|
||||
}
|
||||
@@ -111,35 +225,62 @@ bool LiveSource::switchToNext() {
|
||||
}
|
||||
|
||||
AString uri;
|
||||
CHECK(mPlaylist->itemAt(mPlaylistIndex, &uri));
|
||||
LOGI("switching to %s", uri.c_str());
|
||||
sp<AMessage> itemMeta;
|
||||
CHECK(mPlaylist->itemAt(mPlaylistIndex, &uri, &itemMeta));
|
||||
LOGV("switching to %s", uri.c_str());
|
||||
|
||||
if (mSource->connect(uri.c_str()) != OK
|
||||
|| mSource->getSize(&mSourceSize) != OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t val;
|
||||
if (itemMeta->findInt32("discontinuity", &val) && val != 0) {
|
||||
mSignalDiscontinuity = true;
|
||||
}
|
||||
|
||||
mPlaylistIndex++;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const ssize_t kHeaderSize = 188;
|
||||
|
||||
ssize_t LiveSource::readAt(off_t offset, void *data, size_t size) {
|
||||
CHECK(offset >= mOffsetBias);
|
||||
offset -= mOffsetBias;
|
||||
|
||||
if (offset >= mSourceSize) {
|
||||
CHECK_EQ(offset, mSourceSize);
|
||||
off_t delta = mSignalDiscontinuity ? kHeaderSize : 0;
|
||||
|
||||
offset -= mSourceSize;
|
||||
if (offset >= mSourceSize + delta) {
|
||||
CHECK_EQ(offset, mSourceSize + delta);
|
||||
|
||||
offset -= mSourceSize + delta;
|
||||
if (!switchToNext()) {
|
||||
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;
|
||||
while (numRead < size) {
|
||||
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) {
|
||||
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) {
|
||||
*out = NULL;
|
||||
|
||||
status_t err = mSource->connect(url);
|
||||
sp<DataSource> source;
|
||||
|
||||
if (err != OK) {
|
||||
return err;
|
||||
if (!strncasecmp(url, "file://", 7)) {
|
||||
source = new FileSource(url + 7);
|
||||
} else {
|
||||
CHECK(!strncasecmp(url, "http://", 7));
|
||||
|
||||
status_t err = mSource->connect(url);
|
||||
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
source = mSource;
|
||||
}
|
||||
|
||||
off_t size;
|
||||
err = mSource->getSize(&size);
|
||||
status_t err = source->getSize(&size);
|
||||
|
||||
if (err != OK) {
|
||||
return err;
|
||||
@@ -170,7 +321,7 @@ status_t LiveSource::fetchM3U(const char *url, sp<ABuffer> *out) {
|
||||
sp<ABuffer> buffer = new ABuffer(size);
|
||||
size_t offset = 0;
|
||||
while (offset < (size_t)size) {
|
||||
ssize_t n = mSource->readAt(
|
||||
ssize_t n = source->readAt(
|
||||
offset, buffer->data() + offset, size - offset);
|
||||
|
||||
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) {
|
||||
out->clear();
|
||||
|
||||
if (strncasecmp("http://", baseURL, 7)) {
|
||||
if (strncasecmp("http://", baseURL, 7)
|
||||
&& strncasecmp("file://", baseURL, 7)) {
|
||||
// Base URL must be absolute
|
||||
return false;
|
||||
}
|
||||
@@ -128,7 +129,12 @@ status_t M3UParser::parse(const void *_data, size_t size) {
|
||||
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") {
|
||||
mIsExtM3U = true;
|
||||
@@ -152,11 +158,20 @@ status_t M3UParser::parse(const void *_data, size_t size) {
|
||||
return ERROR_MALFORMED;
|
||||
}
|
||||
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")) {
|
||||
if (mMeta != NULL) {
|
||||
return ERROR_MALFORMED;
|
||||
}
|
||||
mIsVariantPlaylist = true;
|
||||
err = parseStreamInf(line, &itemMeta);
|
||||
}
|
||||
|
||||
if (err != OK) {
|
||||
@@ -214,6 +229,61 @@ status_t M3UParser::parseMetaData(
|
||||
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
|
||||
status_t M3UParser::ParseInt32(const char *s, int32_t *x) {
|
||||
char *end;
|
||||
|
||||
@@ -220,6 +220,7 @@ private:
|
||||
status_t setDataSource_l(const sp<DataSource> &dataSource);
|
||||
status_t setDataSource_l(const sp<MediaExtractor> &extractor);
|
||||
void reset_l();
|
||||
void partial_reset_l();
|
||||
status_t seekTo_l(int64_t timeUs);
|
||||
status_t pause_l(bool at_eos = false);
|
||||
void initRenderer_l();
|
||||
@@ -231,7 +232,7 @@ private:
|
||||
status_t initAudioDecoder();
|
||||
|
||||
void setVideoSource(sp<MediaSource> source);
|
||||
status_t initVideoDecoder();
|
||||
status_t initVideoDecoder(uint32_t flags = 0);
|
||||
|
||||
void onStreamDone();
|
||||
|
||||
|
||||
@@ -44,6 +44,13 @@ protected:
|
||||
virtual ~LiveSource();
|
||||
|
||||
private:
|
||||
struct BandwidthItem {
|
||||
AString mURI;
|
||||
unsigned long mBandwidth;
|
||||
};
|
||||
Vector<BandwidthItem> mBandwidthItems;
|
||||
|
||||
AString mMasterURL;
|
||||
AString mURL;
|
||||
status_t mInitCheck;
|
||||
|
||||
@@ -56,10 +63,15 @@ private:
|
||||
off_t mSourceSize;
|
||||
off_t mOffsetBias;
|
||||
|
||||
bool mSignalDiscontinuity;
|
||||
ssize_t mPrevBandwidthIndex;
|
||||
|
||||
status_t fetchM3U(const char *url, sp<ABuffer> *buffer);
|
||||
|
||||
static int SortByBandwidth(const BandwidthItem *a, const BandwidthItem *b);
|
||||
|
||||
bool switchToNext();
|
||||
bool loadPlaylist();
|
||||
bool loadPlaylist(bool fetchMaster);
|
||||
|
||||
DISALLOW_EVIL_CONSTRUCTORS(LiveSource);
|
||||
};
|
||||
|
||||
@@ -61,6 +61,9 @@ private:
|
||||
static status_t parseMetaData(
|
||||
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);
|
||||
|
||||
DISALLOW_EVIL_CONSTRUCTORS(M3UParser);
|
||||
|
||||
@@ -49,6 +49,8 @@ struct ATSParser::Program : public RefBase {
|
||||
unsigned pid, unsigned payload_unit_start_indicator,
|
||||
ABitReader *br);
|
||||
|
||||
void signalDiscontinuity();
|
||||
|
||||
sp<MediaSource> getSource(SourceType type);
|
||||
|
||||
private:
|
||||
@@ -67,6 +69,8 @@ struct ATSParser::Stream : public RefBase {
|
||||
unsigned payload_unit_start_indicator,
|
||||
ABitReader *br);
|
||||
|
||||
void signalDiscontinuity();
|
||||
|
||||
sp<MediaSource> getSource(SourceType type);
|
||||
|
||||
protected:
|
||||
@@ -124,6 +128,12 @@ bool ATSParser::Program::parsePID(
|
||||
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) {
|
||||
unsigned table_id = br->getBits(8);
|
||||
LOGV(" table_id = %u", table_id);
|
||||
@@ -271,6 +281,19 @@ void ATSParser::Stream::parse(
|
||||
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) {
|
||||
unsigned packet_startcode_prefix = br->getBits(24);
|
||||
|
||||
@@ -459,7 +482,10 @@ void ATSParser::Stream::onPayloadData(
|
||||
mSource = new AnotherPacketSource(meta);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -489,6 +515,12 @@ void ATSParser::feedTSPacket(const void *data, size_t size) {
|
||||
parseTS(&br);
|
||||
}
|
||||
|
||||
void ATSParser::signalDiscontinuity() {
|
||||
for (size_t i = 0; i < mPrograms.size(); ++i) {
|
||||
mPrograms.editItemAt(i)->signalDiscontinuity();
|
||||
}
|
||||
}
|
||||
|
||||
void ATSParser::parseProgramAssociationTable(ABitReader *br) {
|
||||
unsigned table_id = br->getBits(8);
|
||||
LOGV(" table_id = %u", table_id);
|
||||
|
||||
@@ -33,6 +33,7 @@ struct ATSParser : public RefBase {
|
||||
ATSParser();
|
||||
|
||||
void feedTSPacket(const void *data, size_t size);
|
||||
void signalDiscontinuity();
|
||||
|
||||
enum SourceType {
|
||||
AVC_VIDEO,
|
||||
|
||||
@@ -59,21 +59,26 @@ status_t AnotherPacketSource::read(
|
||||
|
||||
if (!mBuffers.empty()) {
|
||||
const sp<ABuffer> buffer = *mBuffers.begin();
|
||||
|
||||
uint64_t timeUs;
|
||||
CHECK(buffer->meta()->findInt64(
|
||||
"time", (int64_t *)&timeUs));
|
||||
|
||||
MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size());
|
||||
mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs);
|
||||
|
||||
// hexdump(buffer->data(), buffer->size());
|
||||
|
||||
memcpy(mediaBuffer->data(), buffer->data(), buffer->size());
|
||||
*out = mediaBuffer;
|
||||
|
||||
mBuffers.erase(mBuffers.begin());
|
||||
return OK;
|
||||
|
||||
int32_t discontinuity;
|
||||
if (buffer->meta()->findInt32("discontinuity", &discontinuity)
|
||||
&& discontinuity) {
|
||||
return INFO_DISCONTINUITY;
|
||||
} else {
|
||||
uint64_t timeUs;
|
||||
CHECK(buffer->meta()->findInt64(
|
||||
"time", (int64_t *)&timeUs));
|
||||
|
||||
MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size());
|
||||
mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs);
|
||||
|
||||
// hexdump(buffer->data(), buffer->size());
|
||||
|
||||
memcpy(mediaBuffer->data(), buffer->data(), buffer->size());
|
||||
*out = mediaBuffer;
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
|
||||
return mEOSResult;
|
||||
@@ -91,6 +96,15 @@ void AnotherPacketSource::queueAccessUnit(const sp<ABuffer> &buffer) {
|
||||
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) {
|
||||
CHECK(result != OK);
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ struct AnotherPacketSource : public MediaSource {
|
||||
bool hasBufferAvailable(status_t *finalResult);
|
||||
|
||||
void queueAccessUnit(const sp<ABuffer> &buffer);
|
||||
void queueDiscontinuity();
|
||||
void signalEOS(status_t result);
|
||||
|
||||
protected:
|
||||
|
||||
@@ -115,6 +115,11 @@ static status_t getNextNALUnit(
|
||||
return OK;
|
||||
}
|
||||
|
||||
void ElementaryStreamQueue::clear() {
|
||||
mBuffer->setRange(0, 0);
|
||||
mFormat.clear();
|
||||
}
|
||||
|
||||
status_t ElementaryStreamQueue::appendData(
|
||||
const void *data, size_t size, int64_t timeUs) {
|
||||
if (mBuffer == NULL || mBuffer->size() == 0) {
|
||||
@@ -147,7 +152,7 @@ status_t ElementaryStreamQueue::appendData(
|
||||
if (mBuffer == NULL || neededSize > mBuffer->capacity()) {
|
||||
neededSize = (neededSize + 65535) & ~65535;
|
||||
|
||||
LOGI("resizing buffer to size %d", neededSize);
|
||||
LOGV("resizing buffer to size %d", neededSize);
|
||||
|
||||
sp<ABuffer> buffer = new ABuffer(neededSize);
|
||||
if (mBuffer != NULL) {
|
||||
@@ -498,6 +503,8 @@ sp<MetaData> ElementaryStreamQueue::MakeAVCCodecSpecificData(
|
||||
meta->setInt32(kKeyWidth, width);
|
||||
meta->setInt32(kKeyHeight, height);
|
||||
|
||||
LOGI("found AVC codec config (%d x %d)", width, height);
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ struct ElementaryStreamQueue {
|
||||
ElementaryStreamQueue(Mode mode);
|
||||
|
||||
status_t appendData(const void *data, size_t size, int64_t timeUs);
|
||||
void clear();
|
||||
|
||||
sp<ABuffer> dequeueAccessUnit();
|
||||
|
||||
|
||||
@@ -165,18 +165,26 @@ void MPEG2TSExtractor::init() {
|
||||
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() {
|
||||
Mutex::Autolock autoLock(mLock);
|
||||
|
||||
uint8_t 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;
|
||||
} else {
|
||||
mParser->feedTSPacket(packet, kTSPacketSize);
|
||||
}
|
||||
|
||||
mOffset += kTSPacketSize;
|
||||
mParser->feedTSPacket(packet, kTSPacketSize);
|
||||
mOffset += n;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user