summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
Diffstat (limited to 'media')
-rw-r--r--media/libmedia/AudioEffect.cpp2
-rw-r--r--media/libmedia/AudioParameter.cpp2
-rw-r--r--media/libmedia/AudioPolicy.cpp2
-rw-r--r--media/libmedia/AudioRecord.cpp55
-rw-r--r--media/libmedia/AudioSystem.cpp2
-rw-r--r--media/libmedia/AudioTrack.cpp38
-rw-r--r--media/libmedia/AudioTrackShared.cpp1
-rw-r--r--media/libmedia/IAudioFlinger.cpp2
-rw-r--r--media/libmedia/IAudioFlingerClient.cpp2
-rw-r--r--media/libmedia/IAudioPolicyService.cpp2
-rw-r--r--media/libmedia/IAudioPolicyServiceClient.cpp2
-rw-r--r--media/libmedia/IAudioRecord.cpp2
-rw-r--r--media/libmedia/IAudioTrack.cpp2
-rw-r--r--media/libmedia/IDrmClient.cpp2
-rw-r--r--media/libmedia/IEffect.cpp2
-rw-r--r--media/libmedia/IEffectClient.cpp2
-rw-r--r--media/libmedia/IMediaCodecList.cpp2
-rw-r--r--media/libmedia/IMediaDeathNotifier.cpp2
-rw-r--r--media/libmedia/IMediaHTTPConnection.cpp3
-rw-r--r--media/libmedia/IMediaHTTPService.cpp3
-rw-r--r--media/libmedia/IMediaLogService.cpp2
-rw-r--r--media/libmedia/IMediaMetadataRetriever.cpp2
-rw-r--r--media/libmedia/IMediaPlayer.cpp2
-rw-r--r--media/libmedia/IMediaPlayerClient.cpp2
-rw-r--r--media/libmedia/IMediaPlayerService.cpp2
-rw-r--r--media/libmedia/IMediaRecorder.cpp2
-rw-r--r--media/libmedia/IMediaRecorderClient.cpp2
-rw-r--r--media/libmedia/IRemoteDisplay.cpp2
-rw-r--r--media/libmedia/IRemoteDisplayClient.cpp2
-rw-r--r--media/libmedia/StringArray.cpp2
-rw-r--r--media/libmedia/Visualizer.cpp2
-rw-r--r--media/libmedia/mediametadataretriever.cpp2
-rw-r--r--media/libmedia/mediaplayer.cpp2
-rw-r--r--media/libmedia/mediarecorder.cpp2
-rw-r--r--media/libmediaplayerservice/DrmSessionManager.cpp33
-rw-r--r--media/libmediaplayerservice/ProcessInfoInterface.h33
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp12
-rw-r--r--media/libmediaplayerservice/tests/DrmSessionManager_test.cpp2
-rw-r--r--media/libstagefright/ACodec.cpp10
-rw-r--r--media/libstagefright/Android.mk1
-rw-r--r--media/libstagefright/OMXCodec.cpp10
-rw-r--r--media/libstagefright/ProcessInfo.cpp53
-rw-r--r--media/libstagefright/codecs/g711/dec/SoftG711.cpp7
-rw-r--r--media/libstagefright/codecs/g711/dec/SoftG711.h3
-rw-r--r--media/libstagefright/data/media_codecs_google_audio.xml4
-rw-r--r--media/libstagefright/httplive/LiveSession.cpp614
-rw-r--r--media/libstagefright/httplive/LiveSession.h54
-rw-r--r--media/libstagefright/httplive/M3UParser.cpp6
-rw-r--r--media/libstagefright/httplive/M3UParser.h1
-rw-r--r--media/libstagefright/httplive/PlaylistFetcher.cpp537
-rw-r--r--media/libstagefright/httplive/PlaylistFetcher.h33
-rw-r--r--media/libstagefright/mpeg2ts/ATSParser.h3
-rw-r--r--media/libstagefright/mpeg2ts/AnotherPacketSource.cpp28
-rw-r--r--media/libstagefright/mpeg2ts/AnotherPacketSource.h4
-rw-r--r--media/mediaserver/Android.mk6
-rw-r--r--media/mediaserver/main_mediaserver.cpp2
56 files changed, 1057 insertions, 557 deletions
diff --git a/media/libmedia/AudioEffect.cpp b/media/libmedia/AudioEffect.cpp
index af103c1..7d8222f 100644
--- a/media/libmedia/AudioEffect.cpp
+++ b/media/libmedia/AudioEffect.cpp
@@ -486,4 +486,4 @@ status_t AudioEffect::guidToString(const effect_uuid_t *guid, char *str, size_t
}
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/AudioParameter.cpp b/media/libmedia/AudioParameter.cpp
index 33dbf0b..8c8cf45 100644
--- a/media/libmedia/AudioParameter.cpp
+++ b/media/libmedia/AudioParameter.cpp
@@ -180,4 +180,4 @@ status_t AudioParameter::getAt(size_t index, String8& key, String8& value)
}
}
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/AudioPolicy.cpp b/media/libmedia/AudioPolicy.cpp
index d2d0971..c7dafcb 100644
--- a/media/libmedia/AudioPolicy.cpp
+++ b/media/libmedia/AudioPolicy.cpp
@@ -112,4 +112,4 @@ status_t AudioMix::writeToParcel(Parcel *parcel) const
return NO_ERROR;
}
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp
index 07ca14f..1a65ee8 100644
--- a/media/libmedia/AudioRecord.cpp
+++ b/media/libmedia/AudioRecord.cpp
@@ -112,7 +112,9 @@ AudioRecord::~AudioRecord()
mCblkMemory.clear();
mBufferMemory.clear();
IPCThreadState::self()->flushCommands();
- AudioSystem::releaseAudioSessionId(mSessionId, -1);
+ ALOGV("~AudioRecord, releasing session id %d",
+ mSessionId);
+ AudioSystem::releaseAudioSessionId(mSessionId, -1 /*pid*/);
}
}
@@ -286,7 +288,6 @@ status_t AudioRecord::start(AudioSystem::sync_event_t event, int triggerSession)
status_t status = NO_ERROR;
if (!(flags & CBLK_INVALID)) {
- ALOGV("mAudioRecord->start()");
status = mAudioRecord->start(event, triggerSession);
if (status == DEAD_OBJECT) {
flags |= CBLK_INVALID;
@@ -352,6 +353,10 @@ status_t AudioRecord::setMarkerPosition(uint32_t marker)
mMarkerPosition = marker;
mMarkerReached = false;
+ sp<AudioRecordThread> t = mAudioRecordThread;
+ if (t != 0) {
+ t->wake();
+ }
return NO_ERROR;
}
@@ -378,6 +383,10 @@ status_t AudioRecord::setPositionUpdatePeriod(uint32_t updatePeriod)
mNewPosition = mProxy->getPosition() + updatePeriod;
mUpdatePeriod = updatePeriod;
+ sp<AudioRecordThread> t = mAudioRecordThread;
+ if (t != 0) {
+ t->wake();
+ }
return NO_ERROR;
}
@@ -408,7 +417,7 @@ status_t AudioRecord::getPosition(uint32_t *position) const
uint32_t AudioRecord::getInputFramesLost() const
{
// no need to check mActive, because if inactive this will return 0, which is what we want
- return AudioSystem::getInputFramesLost(getInput());
+ return AudioSystem::getInputFramesLost(getInputPrivate());
}
// -------------------------------------------------------------------------
@@ -416,7 +425,6 @@ uint32_t AudioRecord::getInputFramesLost() const
// must be called with mLock held
status_t AudioRecord::openRecord_l(size_t epoch)
{
- status_t status;
const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();
if (audioFlinger == 0) {
ALOGE("Could not get audioflinger");
@@ -436,7 +444,8 @@ status_t AudioRecord::openRecord_l(size_t epoch)
(mTransfer == TRANSFER_CALLBACK) &&
// matching sample rate
(mSampleRate == afSampleRate))) {
- ALOGW("AUDIO_INPUT_FLAG_FAST denied by client");
+ ALOGW("AUDIO_INPUT_FLAG_FAST denied by client; transfer %d, track %u Hz, primary %u Hz",
+ mTransfer, mSampleRate, afSampleRate);
// once denied, do not request again if IAudioRecord is re-created
mFlags = (audio_input_flags_t) (mFlags & ~AUDIO_INPUT_FLAG_FAST);
}
@@ -452,7 +461,8 @@ status_t AudioRecord::openRecord_l(size_t epoch)
}
audio_io_handle_t input;
- status = AudioSystem::getInputForAttr(&mAttributes, &input, (audio_session_t)mSessionId,
+ status_t status = AudioSystem::getInputForAttr(&mAttributes, &input,
+ (audio_session_t)mSessionId,
mSampleRate, mFormat, mChannelMask, mFlags);
if (status != NO_ERROR) {
@@ -684,9 +694,9 @@ status_t AudioRecord::obtainBuffer(Buffer* audioBuffer, const struct timespec *r
return status;
}
-void AudioRecord::releaseBuffer(Buffer* audioBuffer)
+void AudioRecord::releaseBuffer(const Buffer* audioBuffer)
{
- // all TRANSFER_* are valid
+ // FIXME add error checking on mode, by adding an internal version
size_t stepCount = audioBuffer->size / mFrameSize;
if (stepCount == 0) {
@@ -704,7 +714,7 @@ void AudioRecord::releaseBuffer(Buffer* audioBuffer)
// the server does not automatically disable recorder on overrun, so no need to restart
}
-audio_io_handle_t AudioRecord::getInput() const
+audio_io_handle_t AudioRecord::getInputPrivate() const
{
AutoMutex lock(mLock);
return mInput;
@@ -863,8 +873,11 @@ nsecs_t AudioRecord::processAudioBuffer()
if (!markerReached && position < markerPosition) {
minFrames = markerPosition - position;
}
- if (updatePeriod > 0 && updatePeriod < minFrames) {
- minFrames = updatePeriod;
+ if (updatePeriod > 0) {
+ uint32_t remaining = newPosition - position;
+ if (remaining < minFrames) {
+ minFrames = remaining;
+ }
}
// If > 0, poll periodically to recover from a stuck server. A good value is 2.
@@ -990,14 +1003,13 @@ status_t AudioRecord::restoreRecord_l(const char *from)
{
ALOGW("dead IAudioRecord, creating a new one from %s()", from);
++mSequence;
- status_t result;
// if the new IAudioRecord is created, openRecord_l() will modify the
// following member variables: mAudioRecord, mCblkMemory, mCblk, mBufferMemory.
// It will also delete the strong references on previous IAudioRecord and IMemory
size_t position = mProxy->getPosition();
mNewPosition = position + mUpdatePeriod;
- result = openRecord_l(position);
+ status_t result = openRecord_l(position);
if (result == NO_ERROR) {
if (mActive) {
// callback thread or sync event hasn't changed
@@ -1069,8 +1081,8 @@ bool AudioRecord::AudioRecordThread::threadLoop()
case NS_NEVER:
return false;
case NS_WHENEVER:
- // FIXME increase poll interval, or make event-driven
- ns = 1000000000LL;
+ // Event driven: call wake() when callback notifications conditions change.
+ ns = INT64_MAX;
// fall through
default:
LOG_ALWAYS_FATAL_IF(ns < 0, "processAudioBuffer() returned %" PRId64, ns);
@@ -1103,6 +1115,17 @@ void AudioRecord::AudioRecordThread::resume()
}
}
+void AudioRecord::AudioRecordThread::wake()
+{
+ AutoMutex _l(mMyLock);
+ if (!mPaused && mPausedInt && mPausedNs > 0) {
+ // audio record is active and internally paused with timeout.
+ mIgnoreNextPausedInt = true;
+ mPausedInt = false;
+ mMyCond.signal();
+ }
+}
+
void AudioRecord::AudioRecordThread::pauseInternal(nsecs_t ns)
{
AutoMutex _l(mMyLock);
@@ -1112,4 +1135,4 @@ void AudioRecord::AudioRecordThread::pauseInternal(nsecs_t ns)
// -------------------------------------------------------------------------
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/AudioSystem.cpp b/media/libmedia/AudioSystem.cpp
index f5a5712..c6b34a7 100644
--- a/media/libmedia/AudioSystem.cpp
+++ b/media/libmedia/AudioSystem.cpp
@@ -1003,4 +1003,4 @@ void AudioSystem::AudioPolicyServiceClient::onAudioPatchListUpdate()
}
}
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index c775e7b..8fd5278 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -203,8 +203,8 @@ AudioTrack::~AudioTrack()
mCblkMemory.clear();
mSharedBuffer.clear();
IPCThreadState::self()->flushCommands();
- ALOGV("~AudioTrack, releasing session id from %d on behalf of %d",
- IPCThreadState::self()->getCallingPid(), mClientPid);
+ ALOGV("~AudioTrack, releasing session id %d from %d on behalf of %d",
+ mSessionId, IPCThreadState::self()->getCallingPid(), mClientPid);
AudioSystem::releaseAudioSessionId(mSessionId, mClientPid);
}
}
@@ -229,9 +229,9 @@ status_t AudioTrack::set(
const audio_attributes_t* pAttributes)
{
ALOGV("set(): streamType %d, sampleRate %u, format %#x, channelMask %#x, frameCount %zu, "
- "flags #%x, notificationFrames %u, sessionId %d, transferType %d",
+ "flags #%x, notificationFrames %u, sessionId %d, transferType %d, uid %d, pid %d",
streamType, sampleRate, format, channelMask, frameCount, flags, notificationFrames,
- sessionId, transferType);
+ sessionId, transferType, uid, pid);
switch (transferType) {
case TRANSFER_DEFAULT:
@@ -964,9 +964,9 @@ status_t AudioTrack::createTrack_l()
if (status != NO_ERROR || output == AUDIO_IO_HANDLE_NONE) {
- ALOGE("Could not get audio output for stream type %d, usage %d, sample rate %u, format %#x,"
+ ALOGE("Could not get audio output for session %d, stream type %d, usage %d, sample rate %u, format %#x,"
" channel mask %#x, flags %#x",
- streamType, mAttributes.usage, mSampleRate, mFormat, mChannelMask, mFlags);
+ mSessionId, streamType, mAttributes.usage, mSampleRate, mFormat, mChannelMask, mFlags);
return BAD_VALUE;
}
{
@@ -981,6 +981,7 @@ status_t AudioTrack::createTrack_l()
ALOGE("getLatency(%d) failed status %d", output, status);
goto release;
}
+ ALOGV("createTrack_l() output %d afLatency %u", output, afLatency);
size_t afFrameCount;
status = AudioSystem::getFrameCount(output, &afFrameCount);
@@ -1010,11 +1011,11 @@ status_t AudioTrack::createTrack_l()
(mTransfer == TRANSFER_OBTAIN)) &&
// matching sample rate
(mSampleRate == afSampleRate))) {
- ALOGW("AUDIO_OUTPUT_FLAG_FAST denied by client");
+ ALOGW("AUDIO_OUTPUT_FLAG_FAST denied by client; transfer %d, track %u Hz, output %u Hz",
+ mTransfer, mSampleRate, afSampleRate);
// once denied, do not request again if IAudioTrack is re-created
mFlags = (audio_output_flags_t) (mFlags & ~AUDIO_OUTPUT_FLAG_FAST);
}
- ALOGV("createTrack_l() output %d afLatency %d", output, afLatency);
// The client's AudioTrack buffer is divided into n parts for purpose of wakeup by server, where
// n = 1 fast track with single buffering; nBuffering is ignored
@@ -1090,6 +1091,7 @@ status_t AudioTrack::createTrack_l()
size_t temp = frameCount; // temp may be replaced by a revised value of frameCount,
// but we will still need the original value also
+ int originalSessionId = mSessionId;
sp<IAudioTrack> track = audioFlinger->createTrack(streamType,
mSampleRate,
mFormat,
@@ -1102,6 +1104,8 @@ status_t AudioTrack::createTrack_l()
&mSessionId,
mClientUid,
&status);
+ ALOGE_IF(originalSessionId != AUDIO_SESSION_ALLOCATE && mSessionId != originalSessionId,
+ "session ID changed from %d to %d", originalSessionId, mSessionId);
if (status != NO_ERROR) {
ALOGE("AudioFlinger could not create track, status: %d", status);
@@ -1194,9 +1198,13 @@ status_t AudioTrack::createTrack_l()
// address space. AudioFlinger::TrackBase::mBuffer is for the server address space.
void* buffers;
if (mSharedBuffer == 0) {
- buffers = (char*)cblk + sizeof(audio_track_cblk_t);
+ buffers = cblk + 1;
} else {
buffers = mSharedBuffer->pointer();
+ if (buffers == NULL) {
+ ALOGE("Could not get buffer pointer");
+ return NO_INIT;
+ }
}
mAudioTrack->attachAuxEffect(mAuxEffectId);
@@ -1415,8 +1423,7 @@ ssize_t AudioTrack::write(const void* buffer, size_t userSize, bool blocking)
return ssize_t(err);
}
- size_t toWrite;
- toWrite = audioBuffer.size;
+ size_t toWrite = audioBuffer.size;
memcpy(audioBuffer.i8, buffer, toWrite);
buffer = ((const char *) buffer) + toWrite;
userSize -= toWrite;
@@ -1784,7 +1791,7 @@ nsecs_t AudioTrack::processAudioBuffer()
return WAIT_PERIOD_MS * 1000000LL;
}
- size_t releasedFrames = audioBuffer.size / mFrameSize;
+ size_t releasedFrames = writtenSize / mFrameSize;
audioBuffer.frameCount = releasedFrames;
mRemainingFrames -= releasedFrames;
if (misalignment >= releasedFrames) {
@@ -1829,7 +1836,6 @@ status_t AudioTrack::restoreTrack_l(const char *from)
ALOGW("dead IAudioTrack, %s, creating a new one from %s()",
isOffloadedOrDirect_l() ? "Offloaded or Direct" : "PCM", from);
++mSequence;
- status_t result;
// refresh the audio configuration cache in this process to make sure we get new
// output parameters and new IAudioFlinger in createTrack_l()
@@ -1851,13 +1857,13 @@ status_t AudioTrack::restoreTrack_l(const char *from)
// following member variables: mAudioTrack, mCblkMemory and mCblk.
// It will also delete the strong references on previous IAudioTrack and IMemory.
// If a new IAudioTrack cannot be created, the previous (dead) instance will be left intact.
- result = createTrack_l();
+ status_t result = createTrack_l();
// take the frames that will be lost by track recreation into account in saved position
// For streaming tracks, this is the amount we obtained from the user/client
// (not the number actually consumed at the server - those are already lost).
(void) updateAndGetPosition_l();
- if (mStaticProxy != 0) {
+ if (mStaticProxy == 0) {
mPosition = mReleased;
}
@@ -2185,4 +2191,4 @@ void AudioTrack::AudioTrackThread::pauseInternal(nsecs_t ns)
mPausedNs = ns;
}
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/AudioTrackShared.cpp b/media/libmedia/AudioTrackShared.cpp
index 08241e2..6d5f1af 100644
--- a/media/libmedia/AudioTrackShared.cpp
+++ b/media/libmedia/AudioTrackShared.cpp
@@ -423,7 +423,6 @@ status_t AudioTrackClientProxy::waitStreamEndDone(const struct timespec *request
goto end;
}
// check for obtainBuffer interrupted by client
- // check for obtainBuffer interrupted by client
if (flags & CBLK_INTERRUPT) {
ALOGV("waitStreamEndDone() interrupted by client");
status = -EINTR;
diff --git a/media/libmedia/IAudioFlinger.cpp b/media/libmedia/IAudioFlinger.cpp
index 8e3b633..6f038ea 100644
--- a/media/libmedia/IAudioFlinger.cpp
+++ b/media/libmedia/IAudioFlinger.cpp
@@ -1369,4 +1369,4 @@ status_t BnAudioFlinger::onTransact(
// ----------------------------------------------------------------------------
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IAudioFlingerClient.cpp b/media/libmedia/IAudioFlingerClient.cpp
index 1c299f7..641e6c1 100644
--- a/media/libmedia/IAudioFlingerClient.cpp
+++ b/media/libmedia/IAudioFlingerClient.cpp
@@ -99,4 +99,4 @@ status_t BnAudioFlingerClient::onTransact(
// ----------------------------------------------------------------------------
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IAudioPolicyService.cpp b/media/libmedia/IAudioPolicyService.cpp
index f2ff27b..39374d8 100644
--- a/media/libmedia/IAudioPolicyService.cpp
+++ b/media/libmedia/IAudioPolicyService.cpp
@@ -1228,4 +1228,4 @@ status_t BnAudioPolicyService::onTransact(
// ----------------------------------------------------------------------------
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IAudioPolicyServiceClient.cpp b/media/libmedia/IAudioPolicyServiceClient.cpp
index e802277..7c65878 100644
--- a/media/libmedia/IAudioPolicyServiceClient.cpp
+++ b/media/libmedia/IAudioPolicyServiceClient.cpp
@@ -80,4 +80,4 @@ status_t BnAudioPolicyServiceClient::onTransact(
// ----------------------------------------------------------------------------
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IAudioRecord.cpp b/media/libmedia/IAudioRecord.cpp
index 8a4a383..9d80753 100644
--- a/media/libmedia/IAudioRecord.cpp
+++ b/media/libmedia/IAudioRecord.cpp
@@ -91,4 +91,4 @@ status_t BnAudioRecord::onTransact(
}
}
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IAudioTrack.cpp b/media/libmedia/IAudioTrack.cpp
index df209fd..651cb61 100644
--- a/media/libmedia/IAudioTrack.cpp
+++ b/media/libmedia/IAudioTrack.cpp
@@ -292,4 +292,4 @@ status_t BnAudioTrack::onTransact(
}
}
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IDrmClient.cpp b/media/libmedia/IDrmClient.cpp
index f50715e..490c6ed 100644
--- a/media/libmedia/IDrmClient.cpp
+++ b/media/libmedia/IDrmClient.cpp
@@ -78,4 +78,4 @@ status_t BnDrmClient::onTransact(
}
}
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IEffect.cpp b/media/libmedia/IEffect.cpp
index c2fff78..eb4b098 100644
--- a/media/libmedia/IEffect.cpp
+++ b/media/libmedia/IEffect.cpp
@@ -201,4 +201,4 @@ status_t BnEffect::onTransact(
// ----------------------------------------------------------------------------
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IEffectClient.cpp b/media/libmedia/IEffectClient.cpp
index aef4371..1322e72 100644
--- a/media/libmedia/IEffectClient.cpp
+++ b/media/libmedia/IEffectClient.cpp
@@ -141,4 +141,4 @@ status_t BnEffectClient::onTransact(
// ----------------------------------------------------------------------------
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IMediaCodecList.cpp b/media/libmedia/IMediaCodecList.cpp
index bf7c5ca..80020db 100644
--- a/media/libmedia/IMediaCodecList.cpp
+++ b/media/libmedia/IMediaCodecList.cpp
@@ -160,4 +160,4 @@ status_t BnMediaCodecList::onTransact(
// ----------------------------------------------------------------------------
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IMediaDeathNotifier.cpp b/media/libmedia/IMediaDeathNotifier.cpp
index 38e9ca0..d4360ea 100644
--- a/media/libmedia/IMediaDeathNotifier.cpp
+++ b/media/libmedia/IMediaDeathNotifier.cpp
@@ -108,4 +108,4 @@ IMediaDeathNotifier::DeathNotifier::~DeathNotifier()
}
}
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IMediaHTTPConnection.cpp b/media/libmedia/IMediaHTTPConnection.cpp
index 7e26ee6..2ff7658 100644
--- a/media/libmedia/IMediaHTTPConnection.cpp
+++ b/media/libmedia/IMediaHTTPConnection.cpp
@@ -178,5 +178,4 @@ private:
IMPLEMENT_META_INTERFACE(
MediaHTTPConnection, "android.media.IMediaHTTPConnection");
-} // namespace android
-
+} // namespace android
diff --git a/media/libmedia/IMediaHTTPService.cpp b/media/libmedia/IMediaHTTPService.cpp
index 1260582..f30d0f3 100644
--- a/media/libmedia/IMediaHTTPService.cpp
+++ b/media/libmedia/IMediaHTTPService.cpp
@@ -54,5 +54,4 @@ struct BpMediaHTTPService : public BpInterface<IMediaHTTPService> {
IMPLEMENT_META_INTERFACE(
MediaHTTPService, "android.media.IMediaHTTPService");
-} // namespace android
-
+} // namespace android
diff --git a/media/libmedia/IMediaLogService.cpp b/media/libmedia/IMediaLogService.cpp
index a4af7b7..230749e 100644
--- a/media/libmedia/IMediaLogService.cpp
+++ b/media/libmedia/IMediaLogService.cpp
@@ -91,4 +91,4 @@ status_t BnMediaLogService::onTransact(
// ----------------------------------------------------------------------------
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IMediaMetadataRetriever.cpp b/media/libmedia/IMediaMetadataRetriever.cpp
index aa2665a..551cffe 100644
--- a/media/libmedia/IMediaMetadataRetriever.cpp
+++ b/media/libmedia/IMediaMetadataRetriever.cpp
@@ -297,4 +297,4 @@ status_t BnMediaMetadataRetriever::onTransact(
// ----------------------------------------------------------------------------
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IMediaPlayer.cpp b/media/libmedia/IMediaPlayer.cpp
index dcd5670..ce3009a 100644
--- a/media/libmedia/IMediaPlayer.cpp
+++ b/media/libmedia/IMediaPlayer.cpp
@@ -574,4 +574,4 @@ status_t BnMediaPlayer::onTransact(
// ----------------------------------------------------------------------------
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IMediaPlayerClient.cpp b/media/libmedia/IMediaPlayerClient.cpp
index a670c96..d608386 100644
--- a/media/libmedia/IMediaPlayerClient.cpp
+++ b/media/libmedia/IMediaPlayerClient.cpp
@@ -75,4 +75,4 @@ status_t BnMediaPlayerClient::onTransact(
}
}
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IMediaPlayerService.cpp b/media/libmedia/IMediaPlayerService.cpp
index feea267..aa7b2e1 100644
--- a/media/libmedia/IMediaPlayerService.cpp
+++ b/media/libmedia/IMediaPlayerService.cpp
@@ -234,4 +234,4 @@ status_t BnMediaPlayerService::onTransact(
// ----------------------------------------------------------------------------
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IMediaRecorder.cpp b/media/libmedia/IMediaRecorder.cpp
index 9181f86..8ca256c 100644
--- a/media/libmedia/IMediaRecorder.cpp
+++ b/media/libmedia/IMediaRecorder.cpp
@@ -463,4 +463,4 @@ status_t BnMediaRecorder::onTransact(
// ----------------------------------------------------------------------------
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IMediaRecorderClient.cpp b/media/libmedia/IMediaRecorderClient.cpp
index e7907e3..6795d23 100644
--- a/media/libmedia/IMediaRecorderClient.cpp
+++ b/media/libmedia/IMediaRecorderClient.cpp
@@ -67,4 +67,4 @@ status_t BnMediaRecorderClient::onTransact(
}
}
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IRemoteDisplay.cpp b/media/libmedia/IRemoteDisplay.cpp
index 1e15434..869d11a 100644
--- a/media/libmedia/IRemoteDisplay.cpp
+++ b/media/libmedia/IRemoteDisplay.cpp
@@ -91,4 +91,4 @@ status_t BnRemoteDisplay::onTransact(
}
}
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/IRemoteDisplayClient.cpp b/media/libmedia/IRemoteDisplayClient.cpp
index 9d63bc9..bedeb6c 100644
--- a/media/libmedia/IRemoteDisplayClient.cpp
+++ b/media/libmedia/IRemoteDisplayClient.cpp
@@ -101,4 +101,4 @@ status_t BnRemoteDisplayClient::onTransact(
}
}
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/StringArray.cpp b/media/libmedia/StringArray.cpp
index 477e3fd..b2e5907 100644
--- a/media/libmedia/StringArray.cpp
+++ b/media/libmedia/StringArray.cpp
@@ -110,4 +110,4 @@ void StringArray::setEntry(int idx, const char* str) {
}
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/Visualizer.cpp b/media/libmedia/Visualizer.cpp
index f91e3e4..9d69b6a 100644
--- a/media/libmedia/Visualizer.cpp
+++ b/media/libmedia/Visualizer.cpp
@@ -429,4 +429,4 @@ bool Visualizer::CaptureThread::threadLoop()
return false;
}
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/mediametadataretriever.cpp b/media/libmedia/mediametadataretriever.cpp
index 8e8a1ed..873808a 100644
--- a/media/libmedia/mediametadataretriever.cpp
+++ b/media/libmedia/mediametadataretriever.cpp
@@ -176,4 +176,4 @@ MediaMetadataRetriever::DeathNotifier::~DeathNotifier()
}
}
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp
index d1d51cc..5dd8c02 100644
--- a/media/libmedia/mediaplayer.cpp
+++ b/media/libmedia/mediaplayer.cpp
@@ -877,4 +877,4 @@ status_t MediaPlayer::setNextMediaPlayer(const sp<MediaPlayer>& next) {
return mPlayer->setNextPlayer(next == NULL ? NULL : next->mPlayer);
}
-}; // namespace android
+} // namespace android
diff --git a/media/libmedia/mediarecorder.cpp b/media/libmedia/mediarecorder.cpp
index 973e156..a2d6e53 100644
--- a/media/libmedia/mediarecorder.cpp
+++ b/media/libmedia/mediarecorder.cpp
@@ -680,4 +680,4 @@ void MediaRecorder::died()
notify(MEDIA_RECORDER_EVENT_ERROR, MEDIA_ERROR_SERVER_DIED, 0);
}
-}; // namespace android
+} // namespace android
diff --git a/media/libmediaplayerservice/DrmSessionManager.cpp b/media/libmediaplayerservice/DrmSessionManager.cpp
index 6e17eb1..641f881 100644
--- a/media/libmediaplayerservice/DrmSessionManager.cpp
+++ b/media/libmediaplayerservice/DrmSessionManager.cpp
@@ -21,10 +21,10 @@
#include "DrmSessionManager.h"
#include "DrmSessionClientInterface.h"
-#include "ProcessInfoInterface.h"
#include <binder/IPCThreadState.h>
#include <binder/IProcessInfoService.h>
#include <binder/IServiceManager.h>
+#include <media/stagefright/ProcessInfo.h>
#include <unistd.h>
#include <utils/String8.h>
@@ -38,37 +38,6 @@ static String8 GetSessionIdString(const Vector<uint8_t> &sessionId) {
return sessionIdStr;
}
-struct ProcessInfo : public ProcessInfoInterface {
- ProcessInfo() {}
-
- virtual bool getPriority(int pid, int* priority) {
- sp<IBinder> binder = defaultServiceManager()->getService(String16("processinfo"));
- sp<IProcessInfoService> service = interface_cast<IProcessInfoService>(binder);
-
- size_t length = 1;
- int32_t states;
- status_t err = service->getProcessStatesFromPids(length, &pid, &states);
- if (err != OK) {
- ALOGE("getProcessStatesFromPids failed");
- return false;
- }
- ALOGV("pid %d states %d", pid, states);
- if (states < 0) {
- return false;
- }
-
- // Use process state as the priority. Lower the value, higher the priority.
- *priority = states;
- return true;
- }
-
-protected:
- virtual ~ProcessInfo() {}
-
-private:
- DISALLOW_EVIL_CONSTRUCTORS(ProcessInfo);
-};
-
bool isEqualSessionId(const Vector<uint8_t> &sessionId1, const Vector<uint8_t> &sessionId2) {
if (sessionId1.size() != sessionId2.size()) {
return false;
diff --git a/media/libmediaplayerservice/ProcessInfoInterface.h b/media/libmediaplayerservice/ProcessInfoInterface.h
deleted file mode 100644
index 222f92d..0000000
--- a/media/libmediaplayerservice/ProcessInfoInterface.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-#ifndef PROCESS_INFO_INTERFACE_H_
-#define PROCESS_INFO_INTERFACE_H_
-
-#include <utils/RefBase.h>
-
-namespace android {
-
-struct ProcessInfoInterface : public RefBase {
- virtual bool getPriority(int pid, int* priority) = 0;
-
-protected:
- virtual ~ProcessInfoInterface() {}
-};
-
-} // namespace android
-
-#endif // PROCESS_INFO_INTERFACE_H_
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
index 4bccfa8..a2ec51c 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
@@ -566,12 +566,22 @@ size_t NuPlayer::Renderer::fillAudioBuffer(void *buffer, size_t size) {
}
bool NuPlayer::Renderer::onDrainAudioQueue() {
-#if 0
+ // TODO: This call to getPosition checks if AudioTrack has been created
+ // in AudioSink before draining audio. If AudioTrack doesn't exist, then
+ // CHECKs on getPosition will fail.
+ // We still need to figure out why AudioTrack is not created when
+ // this function is called. One possible reason could be leftover
+ // audio. Another possible place is to check whether decoder
+ // has received INFO_FORMAT_CHANGED as the first buffer since
+ // AudioSink is opened there, and possible interactions with flush
+ // immediately after start. Investigate error message
+ // "vorbis_dsp_synthesis returned -135", along with RTSP.
uint32_t numFramesPlayed;
if (mAudioSink->getPosition(&numFramesPlayed) != OK) {
return false;
}
+#if 0
ssize_t numFramesAvailableToWrite =
mAudioSink->frameCount() - (mNumFramesWritten - numFramesPlayed);
diff --git a/media/libmediaplayerservice/tests/DrmSessionManager_test.cpp b/media/libmediaplayerservice/tests/DrmSessionManager_test.cpp
index 27b482b..d3e760b 100644
--- a/media/libmediaplayerservice/tests/DrmSessionManager_test.cpp
+++ b/media/libmediaplayerservice/tests/DrmSessionManager_test.cpp
@@ -23,8 +23,8 @@
#include "Drm.h"
#include "DrmSessionClientInterface.h"
#include "DrmSessionManager.h"
-#include "ProcessInfoInterface.h"
#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/ProcessInfoInterface.h>
namespace android {
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index 31e10ce..97f3e20 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -1591,7 +1591,11 @@ status_t ACodec::configureCodec(
if (!msg->findInt32("channel-count", &numChannels)) {
err = INVALID_OPERATION;
} else {
- err = setupG711Codec(encoder, numChannels);
+ int32_t sampleRate;
+ if (!msg->findInt32("sample-rate", &sampleRate)) {
+ sampleRate = 8000;
+ }
+ err = setupG711Codec(encoder, sampleRate, numChannels);
}
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)) {
int32_t numChannels, sampleRate, compressionLevel = -1;
@@ -2066,11 +2070,11 @@ status_t ACodec::setupAMRCodec(bool encoder, bool isWAMR, int32_t bitrate) {
1 /* numChannels */);
}
-status_t ACodec::setupG711Codec(bool encoder, int32_t numChannels) {
+status_t ACodec::setupG711Codec(bool encoder, int32_t sampleRate, int32_t numChannels) {
CHECK(!encoder); // XXX TODO
return setupRawAudioFormat(
- kPortIndexInput, 8000 /* sampleRate */, numChannels);
+ kPortIndexInput, sampleRate, numChannels);
}
status_t ACodec::setupFlacCodec(
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 38f2e34..177293d 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -47,6 +47,7 @@ LOCAL_SRC_FILES:= \
OMXClient.cpp \
OMXCodec.cpp \
OggExtractor.cpp \
+ ProcessInfo.cpp \
SampleIterator.cpp \
SampleTable.cpp \
SkipCutBuffer.cpp \
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index ea19ab2..4d30069 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -629,10 +629,14 @@ status_t OMXCodec::configureCodec(const sp<MetaData> &meta) {
// These are PCM-like formats with a fixed sample rate but
// a variable number of channels.
+ int32_t sampleRate;
int32_t numChannels;
CHECK(meta->findInt32(kKeyChannelCount, &numChannels));
+ if (!meta->findInt32(kKeySampleRate, &sampleRate)) {
+ sampleRate = 8000;
+ }
- setG711Format(numChannels);
+ setG711Format(sampleRate, numChannels);
} else if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_RAW, mMIME)) {
CHECK(!mIsEncoder);
@@ -3616,9 +3620,9 @@ status_t OMXCodec::setAC3Format(int32_t numChannels, int32_t sampleRate) {
sizeof(def));
}
-void OMXCodec::setG711Format(int32_t numChannels) {
+void OMXCodec::setG711Format(int32_t sampleRate, int32_t numChannels) {
CHECK(!mIsEncoder);
- setRawAudioFormat(kPortIndexInput, 8000, numChannels);
+ setRawAudioFormat(kPortIndexInput, sampleRate, numChannels);
}
void OMXCodec::setImageOutputFormat(
diff --git a/media/libstagefright/ProcessInfo.cpp b/media/libstagefright/ProcessInfo.cpp
new file mode 100644
index 0000000..b4172b3
--- /dev/null
+++ b/media/libstagefright/ProcessInfo.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 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 "ProcessInfo"
+#include <utils/Log.h>
+
+#include <media/stagefright/ProcessInfo.h>
+
+#include <binder/IProcessInfoService.h>
+#include <binder/IServiceManager.h>
+
+namespace android {
+
+ProcessInfo::ProcessInfo() {}
+
+bool ProcessInfo::getPriority(int pid, int* priority) {
+ sp<IBinder> binder = defaultServiceManager()->getService(String16("processinfo"));
+ sp<IProcessInfoService> service = interface_cast<IProcessInfoService>(binder);
+
+ size_t length = 1;
+ int32_t states;
+ status_t err = service->getProcessStatesFromPids(length, &pid, &states);
+ if (err != OK) {
+ ALOGE("getProcessStatesFromPids failed");
+ return false;
+ }
+ ALOGV("pid %d states %d", pid, states);
+ if (states < 0) {
+ return false;
+ }
+
+ // Use process state as the priority. Lower the value, higher the priority.
+ *priority = states;
+ return true;
+}
+
+ProcessInfo::~ProcessInfo() {}
+
+} // namespace android
diff --git a/media/libstagefright/codecs/g711/dec/SoftG711.cpp b/media/libstagefright/codecs/g711/dec/SoftG711.cpp
index 3a69095..015515e 100644
--- a/media/libstagefright/codecs/g711/dec/SoftG711.cpp
+++ b/media/libstagefright/codecs/g711/dec/SoftG711.cpp
@@ -41,8 +41,9 @@ SoftG711::SoftG711(
OMX_COMPONENTTYPE **component)
: SimpleSoftOMXComponent(name, callbacks, appData, component),
mIsMLaw(true),
+ mSignalledError(false),
mNumChannels(1),
- mSignalledError(false) {
+ mSamplingRate(8000) {
if (!strcmp(name, "OMX.google.g711.alaw.decoder")) {
mIsMLaw = false;
} else {
@@ -129,7 +130,7 @@ OMX_ERRORTYPE SoftG711::internalGetParameter(
pcmParams->eChannelMapping[1] = OMX_AUDIO_ChannelRF;
pcmParams->nChannels = mNumChannels;
- pcmParams->nSamplingRate = 8000;
+ pcmParams->nSamplingRate = mSamplingRate;
return OMX_ErrorNone;
}
@@ -159,6 +160,8 @@ OMX_ERRORTYPE SoftG711::internalSetParameter(
mNumChannels = pcmParams->nChannels;
}
+ mSamplingRate = pcmParams->nSamplingRate;
+
return OMX_ErrorNone;
}
diff --git a/media/libstagefright/codecs/g711/dec/SoftG711.h b/media/libstagefright/codecs/g711/dec/SoftG711.h
index bff0c68..16b6340 100644
--- a/media/libstagefright/codecs/g711/dec/SoftG711.h
+++ b/media/libstagefright/codecs/g711/dec/SoftG711.h
@@ -46,8 +46,9 @@ private:
};
bool mIsMLaw;
- OMX_U32 mNumChannels;
bool mSignalledError;
+ OMX_U32 mNumChannels;
+ int32_t mSamplingRate;
void initPorts();
diff --git a/media/libstagefright/data/media_codecs_google_audio.xml b/media/libstagefright/data/media_codecs_google_audio.xml
index a06684b..b957b0c 100644
--- a/media/libstagefright/data/media_codecs_google_audio.xml
+++ b/media/libstagefright/data/media_codecs_google_audio.xml
@@ -38,12 +38,12 @@
</MediaCodec>
<MediaCodec name="OMX.google.g711.alaw.decoder" type="audio/g711-alaw">
<Limit name="channel-count" max="1" />
- <Limit name="sample-rate" ranges="8000" />
+ <Limit name="sample-rate" ranges="8000-48000" />
<Limit name="bitrate" range="64000" />
</MediaCodec>
<MediaCodec name="OMX.google.g711.mlaw.decoder" type="audio/g711-mlaw">
<Limit name="channel-count" max="1" />
- <Limit name="sample-rate" ranges="8000" />
+ <Limit name="sample-rate" ranges="8000-48000" />
<Limit name="bitrate" range="64000" />
</MediaCodec>
<MediaCodec name="OMX.google.vorbis.decoder" type="audio/vorbis">
diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp
index a8f60a8..738f8b6 100644
--- a/media/libstagefright/httplive/LiveSession.cpp
+++ b/media/libstagefright/httplive/LiveSession.cpp
@@ -50,13 +50,75 @@
namespace android {
// static
-// Number of recently-read bytes to use for bandwidth estimation
-const size_t LiveSession::kBandwidthHistoryBytes = 200 * 1024;
// High water mark to start up switch or report prepared)
const int64_t LiveSession::kHighWaterMark = 8000000ll;
const int64_t LiveSession::kMidWaterMark = 5000000ll;
const int64_t LiveSession::kLowWaterMark = 3000000ll;
+struct LiveSession::BandwidthEstimator : public RefBase {
+ BandwidthEstimator();
+
+ void addBandwidthMeasurement(size_t numBytes, int64_t delayUs);
+ bool estimateBandwidth(int32_t *bandwidth);
+
+private:
+ // Bandwidth estimation parameters
+ static const int32_t kMaxBandwidthHistoryItems = 20;
+ static const int64_t kMaxBandwidthHistoryWindowUs = 3000000ll; // 3 sec
+
+ struct BandwidthEntry {
+ int64_t mDelayUs;
+ size_t mNumBytes;
+ };
+
+ Mutex mLock;
+ List<BandwidthEntry> mBandwidthHistory;
+ int64_t mTotalTransferTimeUs;
+ size_t mTotalTransferBytes;
+
+ DISALLOW_EVIL_CONSTRUCTORS(BandwidthEstimator);
+};
+
+LiveSession::BandwidthEstimator::BandwidthEstimator() :
+ mTotalTransferTimeUs(0),
+ mTotalTransferBytes(0) {
+}
+
+void LiveSession::BandwidthEstimator::addBandwidthMeasurement(
+ size_t numBytes, int64_t delayUs) {
+ AutoMutex autoLock(mLock);
+
+ BandwidthEntry entry;
+ entry.mDelayUs = delayUs;
+ entry.mNumBytes = numBytes;
+ mTotalTransferTimeUs += delayUs;
+ mTotalTransferBytes += numBytes;
+ mBandwidthHistory.push_back(entry);
+
+ // trim old samples, keeping at least kMaxBandwidthHistoryItems samples,
+ // and total transfer time at least kMaxBandwidthHistoryWindowUs.
+ while (mBandwidthHistory.size() > kMaxBandwidthHistoryItems) {
+ List<BandwidthEntry>::iterator it = mBandwidthHistory.begin();
+ if (mTotalTransferTimeUs - it->mDelayUs < kMaxBandwidthHistoryWindowUs) {
+ break;
+ }
+ mTotalTransferTimeUs -= it->mDelayUs;
+ mTotalTransferBytes -= it->mNumBytes;
+ mBandwidthHistory.erase(mBandwidthHistory.begin());
+ }
+}
+
+bool LiveSession::BandwidthEstimator::estimateBandwidth(int32_t *bandwidthBps) {
+ AutoMutex autoLock(mLock);
+
+ if (mBandwidthHistory.size() < 2) {
+ return false;
+ }
+
+ *bandwidthBps = ((double)mTotalTransferBytes * 8E6 / mTotalTransferTimeUs);
+ return true;
+}
+
LiveSession::LiveSession(
const sp<AMessage> &notify, uint32_t flags,
const sp<IMediaHTTPService> &httpService)
@@ -66,18 +128,17 @@ LiveSession::LiveSession(
mInPreparationPhase(true),
mHTTPDataSource(new MediaHTTP(mHTTPService->makeHTTPConnection())),
mCurBandwidthIndex(-1),
+ mLastBandwidthBps(-1ll),
+ mBandwidthEstimator(new BandwidthEstimator()),
mStreamMask(0),
mNewStreamMask(0),
mSwapMask(0),
- mCheckBandwidthGeneration(0),
mSwitchGeneration(0),
mSubtitleGeneration(0),
mLastDequeuedTimeUs(0ll),
mRealTimeBaseUs(0ll),
mReconfigurationInProgress(false),
mSwitchInProgress(false),
- mDisconnectReplyID(0),
- mSeekReplyID(0),
mFirstTimeUsValid(false),
mFirstTimeUs(0),
mLastSeekTimeUs(0),
@@ -90,15 +151,7 @@ LiveSession::LiveSession(
for (size_t i = 0; i < kMaxStreams; ++i) {
mPacketSources.add(indexToType(i), new AnotherPacketSource(NULL /* meta */));
mPacketSources2.add(indexToType(i), new AnotherPacketSource(NULL /* meta */));
- mBuffering[i] = false;
- }
-
- size_t numHistoryItems = kBandwidthHistoryBytes /
- PlaylistFetcher::kDownloadBlockSize + 1;
- if (numHistoryItems < 5) {
- numHistoryItems = 5;
}
- mHTTPDataSource->setBandwidthHistorySize(numHistoryItems);
}
LiveSession::~LiveSession() {
@@ -107,24 +160,6 @@ LiveSession::~LiveSession() {
}
}
-sp<ABuffer> LiveSession::createFormatChangeBuffer(bool swap) {
- ABuffer *discontinuity = new ABuffer(0);
- discontinuity->meta()->setInt32("discontinuity", ATSParser::DISCONTINUITY_FORMATCHANGE);
- discontinuity->meta()->setInt32("swapPacketSource", swap);
- discontinuity->meta()->setInt32("switchGeneration", mSwitchGeneration);
- discontinuity->meta()->setInt64("timeUs", -1);
- return discontinuity;
-}
-
-void LiveSession::swapPacketSource(StreamType stream) {
- sp<AnotherPacketSource> &aps = mPacketSources.editValueFor(stream);
- sp<AnotherPacketSource> &aps2 = mPacketSources2.editValueFor(stream);
- sp<AnotherPacketSource> tmp = aps;
- aps = aps2;
- aps2 = tmp;
- aps2->clear();
-}
-
status_t LiveSession::dequeueAccessUnit(
StreamType stream, sp<ABuffer> *accessUnit) {
if (!(mStreamMask & stream)) {
@@ -137,58 +172,22 @@ status_t LiveSession::dequeueAccessUnit(
sp<AnotherPacketSource> packetSource = mPacketSources.valueFor(stream);
ssize_t idx = typeToIndex(stream);
- if (!packetSource->hasBufferAvailable(&finalResult)) {
+ // Do not let client pull data if we don't have data packets yet.
+ // We might only have a format discontinuity queued without data.
+ // When NuPlayerDecoder dequeues the format discontinuity, it will
+ // immediately try to getFormat. If we return NULL, NuPlayerDecoder
+ // thinks it can do seamless change, so will not shutdown decoder.
+ // When the actual format arrives, it can't handle it and get stuck.
+ if (!packetSource->hasDataBufferAvailable(&finalResult)) {
if (finalResult == OK) {
- mBuffering[idx] = true;
return -EAGAIN;
} else {
return finalResult;
}
}
- int32_t targetDuration = 0;
- sp<AMessage> meta = packetSource->getLatestEnqueuedMeta();
- if (meta != NULL) {
- meta->findInt32("targetDuration", &targetDuration);
- }
-
- int64_t targetDurationUs = targetDuration * 1000000ll;
- if (targetDurationUs == 0 ||
- targetDurationUs > PlaylistFetcher::kMinBufferedDurationUs) {
- // Fetchers limit buffering to
- // min(3 * targetDuration, kMinBufferedDurationUs)
- targetDurationUs = PlaylistFetcher::kMinBufferedDurationUs;
- }
-
- if (mBuffering[idx]) {
- if (mSwitchInProgress
- || packetSource->isFinished(0)
- || packetSource->hasBufferAvailable(&finalResult)) {
- mBuffering[idx] = false;
- }
- }
-
- if (mBuffering[idx]) {
- return -EAGAIN;
- }
-
- // wait for counterpart
- sp<AnotherPacketSource> otherSource;
- uint32_t mask = mNewStreamMask & mStreamMask;
- uint32_t fetchersMask = 0;
- for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
- uint32_t fetcherMask = mFetcherInfos.valueAt(i).mFetcher->getStreamTypeMask();
- fetchersMask |= fetcherMask;
- }
- mask &= fetchersMask;
- if (stream == STREAMTYPE_AUDIO && (mask & STREAMTYPE_VIDEO)) {
- otherSource = mPacketSources.valueFor(STREAMTYPE_VIDEO);
- } else if (stream == STREAMTYPE_VIDEO && (mask & STREAMTYPE_AUDIO)) {
- otherSource = mPacketSources.valueFor(STREAMTYPE_AUDIO);
- }
- if (otherSource != NULL && !otherSource->hasBufferAvailable(&finalResult)) {
- return finalResult == OK ? -EAGAIN : finalResult;
- }
+ // Let the client dequeue as long as we have buffers available
+ // Do not make pause/resume decisions here.
status_t err = packetSource->dequeueAccessUnit(accessUnit);
@@ -227,41 +226,25 @@ status_t LiveSession::dequeueAccessUnit(
type,
extra == NULL ? "NULL" : extra->debugString().c_str());
- int32_t swap;
- if ((*accessUnit)->meta()->findInt32("swapPacketSource", &swap) && swap) {
- int32_t switchGeneration;
- CHECK((*accessUnit)->meta()->findInt32("switchGeneration", &switchGeneration));
- {
- Mutex::Autolock lock(mSwapMutex);
- if (switchGeneration == mSwitchGeneration) {
- swapPacketSource(stream);
- sp<AMessage> msg = new AMessage(kWhatSwapped, this);
- msg->setInt32("stream", stream);
- msg->setInt32("switchGeneration", switchGeneration);
- msg->post();
- }
- }
+ size_t seq = strm.mCurDiscontinuitySeq;
+ int64_t offsetTimeUs;
+ if (mDiscontinuityOffsetTimesUs.indexOfKey(seq) >= 0) {
+ offsetTimeUs = mDiscontinuityOffsetTimesUs.valueFor(seq);
} else {
- size_t seq = strm.mCurDiscontinuitySeq;
- int64_t offsetTimeUs;
- if (mDiscontinuityOffsetTimesUs.indexOfKey(seq) >= 0) {
- offsetTimeUs = mDiscontinuityOffsetTimesUs.valueFor(seq);
- } else {
- offsetTimeUs = 0;
- }
-
- seq += 1;
- if (mDiscontinuityAbsStartTimesUs.indexOfKey(strm.mCurDiscontinuitySeq) >= 0) {
- int64_t firstTimeUs;
- firstTimeUs = mDiscontinuityAbsStartTimesUs.valueFor(strm.mCurDiscontinuitySeq);
- offsetTimeUs += strm.mLastDequeuedTimeUs - firstTimeUs;
- offsetTimeUs += strm.mLastSampleDurationUs;
- } else {
- offsetTimeUs += strm.mLastSampleDurationUs;
- }
+ offsetTimeUs = 0;
+ }
- mDiscontinuityOffsetTimesUs.add(seq, offsetTimeUs);
+ seq += 1;
+ if (mDiscontinuityAbsStartTimesUs.indexOfKey(strm.mCurDiscontinuitySeq) >= 0) {
+ int64_t firstTimeUs;
+ firstTimeUs = mDiscontinuityAbsStartTimesUs.valueFor(strm.mCurDiscontinuitySeq);
+ offsetTimeUs += strm.mLastDequeuedTimeUs - firstTimeUs;
+ offsetTimeUs += strm.mLastSampleDurationUs;
+ } else {
+ offsetTimeUs += strm.mLastSampleDurationUs;
}
+
+ mDiscontinuityOffsetTimesUs.add(seq, offsetTimeUs);
} else if (err == OK) {
if (stream == STREAMTYPE_AUDIO || stream == STREAMTYPE_VIDEO) {
@@ -322,7 +305,6 @@ status_t LiveSession::dequeueAccessUnit(
}
status_t LiveSession::getStreamFormat(StreamType stream, sp<AMessage> *format) {
- // No swapPacketSource race condition; called from the same thread as dequeueAccessUnit.
if (!(mStreamMask & stream)) {
return UNKNOWN_ERROR;
}
@@ -338,6 +320,10 @@ status_t LiveSession::getStreamFormat(StreamType stream, sp<AMessage> *format) {
return convertMetaDataToMessage(meta, format);
}
+sp<HTTPBase> LiveSession::getHTTPDataSource() {
+ return new MediaHTTP(mHTTPService->makeHTTPConnection());
+}
+
void LiveSession::connectAsync(
const char *url, const KeyedVector<String8, String8> *headers) {
sp<AMessage> msg = new AMessage(kWhatConnect, this);
@@ -417,21 +403,27 @@ void LiveSession::onMessageReceived(const sp<AMessage> &msg) {
case PlaylistFetcher::kWhatPaused:
case PlaylistFetcher::kWhatStopped:
{
+ AString uri;
+ CHECK(msg->findString("uri", &uri));
+ ssize_t index = mFetcherInfos.indexOfKey(uri);
+ if (index < 0) {
+ // ignore msgs from fetchers that's already gone
+ break;
+ }
+
if (what == PlaylistFetcher::kWhatStopped) {
- AString uri;
- CHECK(msg->findString("uri", &uri));
- ssize_t index = mFetcherInfos.indexOfKey(uri);
- if (index < 0) {
- // ignore duplicated kWhatStopped messages.
- break;
- }
+ tryToFinishBandwidthSwitch(uri);
mFetcherLooper->unregisterHandler(
mFetcherInfos[index].mFetcher->id());
mFetcherInfos.removeItemsAt(index);
-
- if (mSwitchInProgress) {
- tryToFinishBandwidthSwitch();
+ } else if (what == PlaylistFetcher::kWhatPaused) {
+ int32_t seekMode;
+ CHECK(msg->findInt32("seekMode", &seekMode));
+ for (size_t i = 0; i < kMaxStreams; ++i) {
+ if (mStreams[i].mUri == uri) {
+ mStreams[i].mSeekMode = (SeekMode) seekMode;
+ }
}
}
@@ -513,6 +505,13 @@ void LiveSession::onMessageReceived(const sp<AMessage> &msg) {
break;
}
+ AString uri;
+ CHECK(msg->findString("uri", &uri));
+ ssize_t index = mFetcherInfos.indexOfKey(uri);
+ if (index >= 0) {
+ mFetcherInfos.editValueAt(index).mToBeResumed = true;
+ }
+
// Resume fetcher for the original variant; the resumed fetcher should
// continue until the timestamps found in msg, which is stored by the
// new fetcher to indicate where the new variant has started buffering.
@@ -556,12 +555,6 @@ void LiveSession::onMessageReceived(const sp<AMessage> &msg) {
break;
}
- case kWhatSwapped:
- {
- onSwapped(msg);
- break;
- }
-
case kWhatPollBuffering:
{
int32_t generation;
@@ -700,11 +693,10 @@ void LiveSession::onConnect(const sp<AMessage> &msg) {
}
void LiveSession::finishDisconnect() {
+ ALOGV("finishDisconnect");
+
// No reconfiguration is currently pending, make sure none will trigger
// during disconnection either.
-
- // Protect mPacketSources from a swapPacketSource race condition through disconnect.
- // (finishDisconnect, onFinishDisconnect2)
cancelBandwidthSwitch();
// cancel buffer polling
@@ -737,7 +729,7 @@ void LiveSession::onFinishDisconnect2() {
response->setInt32("err", OK);
response->postReply(mDisconnectReplyID);
- mDisconnectReplyID = 0;
+ mDisconnectReplyID.clear();
}
sp<PlaylistFetcher> LiveSession::addFetcher(const char *uri) {
@@ -754,8 +746,8 @@ sp<PlaylistFetcher> LiveSession::addFetcher(const char *uri) {
FetcherInfo info;
info.mFetcher = new PlaylistFetcher(notify, this, uri, mSubtitleGeneration);
info.mDurationUs = -1ll;
- info.mIsPrepared = false;
info.mToBeRemoved = false;
+ info.mToBeResumed = false;
mFetcherLooper->registerHandler(info.mFetcher);
mFetcherInfos.add(uri, info);
@@ -784,14 +776,15 @@ ssize_t LiveSession::fetchFile(
int64_t range_offset, int64_t range_length,
uint32_t block_size, /* download block size */
sp<DataSource> *source, /* to return and reuse source */
- String8 *actualUrl) {
+ String8 *actualUrl,
+ bool forceConnectHTTP /* force connect HTTP when resuing source */) {
off64_t size;
sp<DataSource> temp_source;
if (source == NULL) {
source = &temp_source;
}
- if (*source == NULL) {
+ if (*source == NULL || forceConnectHTTP) {
if (!strncasecmp(url, "file://", 7)) {
*source = new FileSource(url + 7);
} else if (strncasecmp(url, "http://", 7)
@@ -810,13 +803,18 @@ ssize_t LiveSession::fetchFile(
? "" : AStringPrintf("%lld",
range_offset + range_length - 1).c_str()).c_str()));
}
- status_t err = mHTTPDataSource->connect(url, &headers);
+
+ HTTPBase* httpDataSource =
+ (*source == NULL) ? mHTTPDataSource.get() : (HTTPBase*)source->get();
+ status_t err = httpDataSource->connect(url, &headers);
if (err != OK) {
return err;
}
- *source = mHTTPDataSource;
+ if (*source == NULL) {
+ *source = mHTTPDataSource;
+ }
}
}
@@ -952,8 +950,66 @@ static double uniformRand() {
}
#endif
-size_t LiveSession::getBandwidthIndex() {
- if (mBandwidthItems.size() == 0) {
+float LiveSession::getAbortThreshold(
+ ssize_t currentBWIndex, ssize_t targetBWIndex) const {
+ float abortThreshold = -1.0f;
+ if (currentBWIndex > 0 && targetBWIndex < currentBWIndex) {
+ /*
+ If we're switching down, we need to decide whether to
+
+ 1) finish last segment of high-bandwidth variant, or
+ 2) abort last segment of high-bandwidth variant, and fetch an
+ overlapping portion from low-bandwidth variant.
+
+ Here we try to maximize the amount of buffer left when the
+ switch point is met. Given the following parameters:
+
+ B: our current buffering level in seconds
+ T: target duration in seconds
+ X: sample duration in seconds remain to fetch in last segment
+ bw0: bandwidth of old variant (as specified in playlist)
+ bw1: bandwidth of new variant (as specified in playlist)
+ bw: measured bandwidth available
+
+ If we choose 1), when switch happens at the end of current
+ segment, our buffering will be
+ B + X - X * bw0 / bw
+
+ If we choose 2), when switch happens where we aborted current
+ segment, our buffering will be
+ B - (T - X) * bw1 / bw
+
+ We should only choose 1) if
+ X/T < bw1 / (bw1 + bw0 - bw)
+ */
+
+ CHECK(mLastBandwidthBps >= 0);
+ abortThreshold =
+ (float)mBandwidthItems.itemAt(targetBWIndex).mBandwidth
+ / ((float)mBandwidthItems.itemAt(targetBWIndex).mBandwidth
+ + (float)mBandwidthItems.itemAt(currentBWIndex).mBandwidth
+ - (float)mLastBandwidthBps * 0.7f);
+ if (abortThreshold < 0.0f) {
+ abortThreshold = -1.0f; // do not abort
+ }
+ ALOGV("Switching Down: bps %ld => %ld, measured %d, abort ratio %.2f",
+ mBandwidthItems.itemAt(currentBWIndex).mBandwidth,
+ mBandwidthItems.itemAt(targetBWIndex).mBandwidth,
+ mLastBandwidthBps,
+ abortThreshold);
+ }
+ return abortThreshold;
+}
+
+void LiveSession::addBandwidthMeasurement(size_t numBytes, int64_t delayUs) {
+ mBandwidthEstimator->addBandwidthMeasurement(numBytes, delayUs);
+}
+
+size_t LiveSession::getBandwidthIndex(int32_t bandwidthBps) {
+ if (mBandwidthItems.size() < 2) {
+ // shouldn't be here if we only have 1 bandwidth, check
+ // logic to get rid of redundant bandwidth polling
+ ALOGW("getBandwidthIndex() called for single bandwidth playlist!");
return 0;
}
@@ -971,15 +1027,6 @@ size_t LiveSession::getBandwidthIndex() {
}
if (index < 0) {
- int32_t bandwidthBps;
- if (mHTTPDataSource != NULL
- && mHTTPDataSource->estimateBandwidth(&bandwidthBps)) {
- ALOGV("bandwidth estimated at %.2f kbps", bandwidthBps / 1024.0f);
- } else {
- ALOGV("no bandwidth estimate.");
- return 0; // Pick the lowest bandwidth stream by default.
- }
-
char value[PROPERTY_VALUE_MAX];
if (property_get("media.httplive.max-bw", value, NULL)) {
char *end;
@@ -996,15 +1043,9 @@ size_t LiveSession::getBandwidthIndex() {
index = mBandwidthItems.size() - 1;
while (index > 0) {
- // consider only 80% of the available bandwidth, but if we are switching up,
- // be even more conservative (70%) to avoid overestimating and immediately
- // switching back.
- size_t adjustedBandwidthBps = bandwidthBps;
- if (index > mCurBandwidthIndex) {
- adjustedBandwidthBps = adjustedBandwidthBps * 7 / 10;
- } else {
- adjustedBandwidthBps = adjustedBandwidthBps * 8 / 10;
- }
+ // be conservative (70%) to avoid overestimating and immediately
+ // switching down again.
+ size_t adjustedBandwidthBps = bandwidthBps * 7 / 10;
if (mBandwidthItems.itemAt(index).mBandwidth <= adjustedBandwidthBps) {
break;
}
@@ -1167,8 +1208,6 @@ void LiveSession::changeConfiguration(
CHECK(!mReconfigurationInProgress);
mReconfigurationInProgress = true;
- mCurBandwidthIndex = bandwidthIndex;
-
ALOGV("changeConfiguration => timeUs:%" PRId64 " us, bwIndex:%zu, pickTrack:%d",
timeUs, bandwidthIndex, pickTrack);
@@ -1192,28 +1231,41 @@ void LiveSession::changeConfiguration(
bool discardFetcher = true;
- // If we're seeking all current fetchers are discarded.
if (timeUs < 0ll) {
// delay fetcher removal if not picking tracks
discardFetcher = pickTrack;
+ }
- for (size_t j = 0; j < kMaxStreams; ++j) {
- StreamType type = indexToType(j);
- if ((streamMask & type) && uri == URIs[j]) {
- resumeMask |= type;
- streamMask &= ~type;
- discardFetcher = false;
- }
+ for (size_t j = 0; j < kMaxStreams; ++j) {
+ StreamType type = indexToType(j);
+ if ((streamMask & type) && uri == URIs[j]) {
+ resumeMask |= type;
+ streamMask &= ~type;
+ discardFetcher = false;
}
}
if (discardFetcher) {
mFetcherInfos.valueAt(i).mFetcher->stopAsync();
} else {
- mFetcherInfos.valueAt(i).mFetcher->pauseAsync();
+ float threshold = -1.0f; // always finish fetching by default
+ if (timeUs >= 0ll) {
+ // seeking, no need to finish fetching
+ threshold = 0.0f;
+ } else if (!pickTrack) {
+ // adapting, abort if remaining of current segment is over threshold
+ threshold = getAbortThreshold(
+ mCurBandwidthIndex, bandwidthIndex);
+ }
+
+ ALOGV("Pausing with threshold %.3f", threshold);
+
+ mFetcherInfos.valueAt(i).mFetcher->pauseAsync(threshold);
}
}
+ mCurBandwidthIndex = bandwidthIndex;
+
sp<AMessage> msg;
if (timeUs < 0ll) {
// skip onChangeConfiguration2 (decoder destruction) if not seeking.
@@ -1274,11 +1326,11 @@ void LiveSession::onChangeConfiguration2(const sp<AMessage> &msg) {
mDiscontinuityOffsetTimesUs.clear();
mDiscontinuityAbsStartTimesUs.clear();
- if (mSeekReplyID != 0) {
+ if (mSeekReplyID != NULL) {
CHECK(mSeekReply != NULL);
mSeekReply->setInt32("err", OK);
mSeekReply->postReply(mSeekReplyID);
- mSeekReplyID = 0;
+ mSeekReplyID.clear();
mSeekReply.clear();
}
}
@@ -1287,9 +1339,6 @@ void LiveSession::onChangeConfiguration2(const sp<AMessage> &msg) {
CHECK(msg->findInt32("streamMask", (int32_t *)&streamMask));
CHECK(msg->findInt32("resumeMask", (int32_t *)&resumeMask));
- // currently onChangeConfiguration2 is only called for seeking;
- // remove the following CHECK if using it else where.
- CHECK_EQ(resumeMask, 0);
streamMask |= resumeMask;
AString URIs[kMaxStreams];
@@ -1301,17 +1350,25 @@ void LiveSession::onChangeConfiguration2(const sp<AMessage> &msg) {
}
}
- // Determine which decoders to shutdown on the player side,
- // a decoder has to be shutdown if either
- // 1) its streamtype was active before but now longer isn't.
- // or
- // 2) its streamtype was already active and still is but the URI
- // has changed.
uint32_t changedMask = 0;
for (size_t i = 0; i < kMaxStreams && i != kSubtitleIndex; ++i) {
- if (((mStreamMask & streamMask & indexToType(i))
- && !(URIs[i] == mStreams[i].mUri))
- || (mStreamMask & ~streamMask & indexToType(i))) {
+ // stream URI could change even if onChangeConfiguration2 is only
+ // used for seek. Seek could happen during a bw switch, in this
+ // case bw switch will be cancelled, but the seekTo position will
+ // fetch from the new URI.
+ if ((mStreamMask & streamMask & indexToType(i))
+ && !mStreams[i].mUri.empty()
+ && !(URIs[i] == mStreams[i].mUri)) {
+ ALOGV("stream %zu changed: oldURI %s, newURI %s", i,
+ mStreams[i].mUri.c_str(), URIs[i].c_str());
+ sp<AnotherPacketSource> source = mPacketSources.valueFor(indexToType(i));
+ source->queueDiscontinuity(
+ ATSParser::DISCONTINUITY_FORMATCHANGE, NULL, true);
+ }
+ // Determine which decoders to shutdown on the player side,
+ // a decoder has to be shutdown if its streamtype was active
+ // before but now longer isn't.
+ if ((mStreamMask & ~streamMask & indexToType(i))) {
changedMask |= indexToType(i);
}
}
@@ -1394,14 +1451,13 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
if (sources[kAudioIndex] != NULL || sources[kVideoIndex] != NULL
|| sources[kSubtitleIndex] != NULL) {
info.mFetcher->startAsync(
- sources[kAudioIndex], sources[kVideoIndex], sources[kSubtitleIndex]);
+ sources[kAudioIndex], sources[kVideoIndex], sources[kSubtitleIndex], timeUs);
} else {
info.mToBeRemoved = true;
}
}
// streamMask now only contains the types that need a new fetcher created.
-
if (streamMask != 0) {
ALOGV("creating new fetchers for mask 0x%08x", streamMask);
}
@@ -1422,6 +1478,7 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
int64_t startTimeUs = -1;
int64_t segmentStartTimeUs = -1ll;
int32_t discontinuitySeq = -1;
+ SeekMode seekMode = kSeekModeExactPosition;
sp<AnotherPacketSource> sources[kMaxStreams];
if (i == kSubtitleIndex) {
@@ -1441,6 +1498,16 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
sp<AMessage> meta;
if (pickTrack) {
// selecting
+
+ // FIXME:
+ // This should only apply to the track that's being picked, we
+ // need a bitmask to indicate that.
+ //
+ // It's possible that selectTrack() gets called during a bandwidth
+ // switch, and we needed to fetch a new variant. The new fetcher
+ // should start from where old fetcher left off, not where decoder
+ // is dequeueing at.
+
meta = sources[j]->getLatestDequeuedMeta();
} else {
// adapting
@@ -1474,17 +1541,25 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
break;
}
- ALOGV("stream[%d]: queue format change", j);
+ ALOGV("stream[%zu]: queue format change", j);
sources[j]->queueDiscontinuity(
- ATSParser::DISCONTINUITY_FORMATCHANGE, NULL, true);
+ ATSParser::DISCONTINUITY_FORMAT_ONLY, NULL, true);
} else {
// adapting, queue discontinuities after resume
sources[j] = mPacketSources2.valueFor(indexToType(j));
sources[j]->clear();
uint32_t extraStreams = mNewStreamMask & (~mStreamMask);
if (extraStreams & indexToType(j)) {
- sources[j]->queueAccessUnit(createFormatChangeBuffer(/*swap*/ false));
+ sources[j]->queueDiscontinuity(
+ ATSParser::DISCONTINUITY_FORMAT_ONLY, NULL, true);
+ }
+ // the new fetcher might be providing streams that used to be
+ // provided by two different fetchers, if one of the fetcher
+ // paused in the middle while the other somehow paused in next
+ // seg, we have to start from next seg.
+ if (seekMode < mStreams[j].mSeekMode) {
+ seekMode = mStreams[j].mSeekMode;
}
}
}
@@ -1500,7 +1575,7 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
startTimeUs < 0 ? mLastSeekTimeUs : startTimeUs,
segmentStartTimeUs,
discontinuitySeq,
- switching);
+ seekMode);
}
// All fetchers have now been started, the configuration change
@@ -1514,30 +1589,62 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) {
mStreamMask = mNewStreamMask;
}
- if (mDisconnectReplyID != 0) {
+ if (mDisconnectReplyID != NULL) {
finishDisconnect();
}
}
-void LiveSession::onSwapped(const sp<AMessage> &msg) {
- int32_t switchGeneration;
- CHECK(msg->findInt32("switchGeneration", &switchGeneration));
- if (switchGeneration != mSwitchGeneration) {
+void LiveSession::swapPacketSource(StreamType stream) {
+ ALOGV("swapPacketSource: stream = %d", stream);
+
+ // transfer packets from source2 to source
+ sp<AnotherPacketSource> &aps = mPacketSources.editValueFor(stream);
+ sp<AnotherPacketSource> &aps2 = mPacketSources2.editValueFor(stream);
+
+ // queue discontinuity in mPacketSource
+ aps->queueDiscontinuity(ATSParser::DISCONTINUITY_FORMAT_ONLY, NULL, false);
+
+ // queue packets in mPacketSource2 to mPacketSource
+ status_t finalResult = OK;
+ sp<ABuffer> accessUnit;
+ while (aps2->hasBufferAvailable(&finalResult) && finalResult == OK &&
+ OK == aps2->dequeueAccessUnit(&accessUnit)) {
+ aps->queueAccessUnit(accessUnit);
+ }
+ aps2->clear();
+}
+
+void LiveSession::tryToFinishBandwidthSwitch(const AString &uri) {
+ if (!mSwitchInProgress) {
return;
}
- int32_t stream;
- CHECK(msg->findInt32("stream", &stream));
+ ssize_t index = mFetcherInfos.indexOfKey(uri);
+ if (index < 0 || !mFetcherInfos[index].mToBeRemoved) {
+ return;
+ }
- ssize_t idx = typeToIndex(stream);
- CHECK(idx >= 0);
- if ((mNewStreamMask & stream) && mStreams[idx].mNewUri.empty()) {
- ALOGW("swapping stream type %d %s to empty stream", stream, mStreams[idx].mUri.c_str());
+ // Swap packet source of streams provided by old variant
+ for (size_t idx = 0; idx < kMaxStreams; idx++) {
+ if (uri == mStreams[idx].mUri) {
+ StreamType stream = indexToType(idx);
+
+ swapPacketSource(stream);
+
+ if ((mNewStreamMask & stream) && mStreams[idx].mNewUri.empty()) {
+ ALOGW("swapping stream type %d %s to empty stream",
+ stream, mStreams[idx].mUri.c_str());
+ }
+ mStreams[idx].mUri = mStreams[idx].mNewUri;
+ mStreams[idx].mNewUri.clear();
+
+ mSwapMask &= ~stream;
+ }
}
- mStreams[idx].mUri = mStreams[idx].mNewUri;
- mStreams[idx].mNewUri.clear();
- mSwapMask &= ~stream;
+ mFetcherInfos.editValueAt(index).mToBeRemoved = false;
+
+ ALOGV("tryToFinishBandwidthSwitch: mSwapMask=%x", mSwapMask);
if (mSwapMask != 0) {
return;
}
@@ -1545,21 +1652,50 @@ void LiveSession::onSwapped(const sp<AMessage> &msg) {
// Check if new variant contains extra streams.
uint32_t extraStreams = mNewStreamMask & (~mStreamMask);
while (extraStreams) {
- StreamType extraStream = (StreamType) (extraStreams & ~(extraStreams - 1));
- swapPacketSource(extraStream);
- extraStreams &= ~extraStream;
+ StreamType stream = (StreamType) (extraStreams & ~(extraStreams - 1));
+ extraStreams &= ~stream;
- idx = typeToIndex(extraStream);
+ swapPacketSource(stream);
+
+ ssize_t idx = typeToIndex(stream);
CHECK(idx >= 0);
if (mStreams[idx].mNewUri.empty()) {
ALOGW("swapping extra stream type %d %s to empty stream",
- extraStream, mStreams[idx].mUri.c_str());
+ stream, mStreams[idx].mUri.c_str());
}
mStreams[idx].mUri = mStreams[idx].mNewUri;
mStreams[idx].mNewUri.clear();
}
- tryToFinishBandwidthSwitch();
+ // Restart new fetcher (it was paused after the first 47k block)
+ // and let it fetch into mPacketSources (not mPacketSources2)
+ for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
+ FetcherInfo &info = mFetcherInfos.editValueAt(i);
+ if (info.mToBeResumed) {
+ const AString &uri = mFetcherInfos.keyAt(i);
+ sp<AnotherPacketSource> sources[kMaxStreams];
+ for (size_t j = 0; j < kMaxStreams; ++j) {
+ if (uri == mStreams[j].mUri) {
+ sources[j] = mPacketSources.valueFor(indexToType(j));
+ }
+ }
+ if (sources[kAudioIndex] != NULL
+ || sources[kVideoIndex] != NULL
+ || sources[kSubtitleIndex] != NULL) {
+ ALOGV("resuming fetcher %s", uri.c_str());
+ info.mFetcher->startAsync(
+ sources[kAudioIndex],
+ sources[kVideoIndex],
+ sources[kSubtitleIndex]);
+ }
+ info.mToBeResumed = false;
+ }
+ }
+
+ mStreamMask = mNewStreamMask;
+ mSwitchInProgress = false;
+
+ ALOGI("#### Finished Bandwidth Switch");
}
void LiveSession::schedulePollBuffering() {
@@ -1574,9 +1710,9 @@ void LiveSession::cancelPollBuffering() {
void LiveSession::onPollBuffering() {
ALOGV("onPollBuffering: mSwitchInProgress %d, mReconfigurationInProgress %d, "
- "mInPreparationPhase %d, mStreamMask 0x%x",
+ "mInPreparationPhase %d, mCurBandwidthIndex %zd, mStreamMask 0x%x",
mSwitchInProgress, mReconfigurationInProgress,
- mInPreparationPhase, mStreamMask);
+ mInPreparationPhase, mCurBandwidthIndex, mStreamMask);
bool low, mid, high;
if (checkBuffering(low, mid, high)) {
@@ -1585,38 +1721,17 @@ void LiveSession::onPollBuffering() {
}
// don't switch before we report prepared
- if (!mInPreparationPhase && (low || high)) {
- switchBandwidthIfNeeded(high);
+ if (!mInPreparationPhase) {
+ switchBandwidthIfNeeded(high, !mid);
}
}
schedulePollBuffering();
}
-// Mark switch done when:
-// 1. all old buffers are swapped out
-void LiveSession::tryToFinishBandwidthSwitch() {
- if (!mSwitchInProgress) {
- return;
- }
-
- bool needToRemoveFetchers = false;
- for (size_t i = 0; i < mFetcherInfos.size(); ++i) {
- if (mFetcherInfos.valueAt(i).mToBeRemoved) {
- needToRemoveFetchers = true;
- break;
- }
- }
-
- if (!needToRemoveFetchers && mSwapMask == 0) {
- ALOGI("mSwitchInProgress = false");
- mStreamMask = mNewStreamMask;
- mSwitchInProgress = false;
- }
-}
-
void LiveSession::cancelBandwidthSwitch() {
- Mutex::Autolock lock(mSwapMutex);
+ ALOGV("cancelBandwidthSwitch: mSwitchGen(%d)++", mSwitchGeneration);
+
mSwitchGeneration++;
mSwitchInProgress = false;
mSwapMask = 0;
@@ -1679,7 +1794,7 @@ bool LiveSession::checkBuffering(bool &low, bool &mid, bool &high) {
++activeCount;
int64_t bufferedDurationUs =
mPacketSources[i]->getEstimatedDurationUs();
- ALOGV("source[%d]: buffered %lld us", i, bufferedDurationUs);
+ ALOGV("source[%zu]: buffered %lld us", i, (long long)bufferedDurationUs);
if (bufferedDurationUs < kLowWaterMark) {
++lowCount;
break;
@@ -1701,11 +1816,36 @@ bool LiveSession::checkBuffering(bool &low, bool &mid, bool &high) {
return false;
}
-void LiveSession::switchBandwidthIfNeeded(bool canSwitchUp) {
- ssize_t bandwidthIndex = getBandwidthIndex();
+void LiveSession::switchBandwidthIfNeeded(bool bufferHigh, bool bufferLow) {
+ // no need to check bandwidth if we only have 1 bandwidth settings
+ if (mBandwidthItems.size() < 2) {
+ return;
+ }
+
+ int32_t bandwidthBps;
+ if (mBandwidthEstimator->estimateBandwidth(&bandwidthBps)) {
+ ALOGV("bandwidth estimated at %.2f kbps", bandwidthBps / 1024.0f);
+ mLastBandwidthBps = bandwidthBps;
+ } else {
+ ALOGV("no bandwidth estimate.");
+ return;
+ }
+
+ int32_t curBandwidth = mBandwidthItems.itemAt(mCurBandwidthIndex).mBandwidth;
+ bool bandwidthLow = bandwidthBps < (int32_t)curBandwidth * 8 / 10;
+ bool bandwidthHigh = bandwidthBps > (int32_t)curBandwidth * 12 / 10;
+
+ if ((bufferHigh && bandwidthHigh) || (bufferLow && bandwidthLow)) {
+ ssize_t bandwidthIndex = getBandwidthIndex(bandwidthBps);
+
+ if (bandwidthIndex == mCurBandwidthIndex
+ || (bufferHigh && bandwidthIndex < mCurBandwidthIndex)
+ || (bufferLow && bandwidthIndex > mCurBandwidthIndex)) {
+ return;
+ }
- if ((canSwitchUp && bandwidthIndex > mCurBandwidthIndex)
- || (!canSwitchUp && bandwidthIndex < mCurBandwidthIndex)) {
+ ALOGI("#### Starting Bandwidth Switch: %zd => %zd",
+ mCurBandwidthIndex, bandwidthIndex);
changeConfiguration(-1, bandwidthIndex, false);
}
}
diff --git a/media/libstagefright/httplive/LiveSession.h b/media/libstagefright/httplive/LiveSession.h
index 3b0a9a4..cbf988e 100644
--- a/media/libstagefright/httplive/LiveSession.h
+++ b/media/libstagefright/httplive/LiveSession.h
@@ -54,6 +54,12 @@ struct LiveSession : public AHandler {
STREAMTYPE_SUBTITLES = 1 << kSubtitleIndex,
};
+ enum SeekMode {
+ kSeekModeExactPosition = 0, // used for seeking
+ kSeekModeNextSample = 1, // used for seamless switching
+ kSeekModeNextSegment = 2, // used for seamless switching
+ };
+
LiveSession(
const sp<AMessage> &notify,
uint32_t flags,
@@ -63,6 +69,8 @@ struct LiveSession : public AHandler {
status_t getStreamFormat(StreamType stream, sp<AMessage> *format);
+ sp<HTTPBase> getHTTPDataSource();
+
void connectAsync(
const char *url,
const KeyedVector<String8, String8> *headers = NULL);
@@ -88,11 +96,6 @@ struct LiveSession : public AHandler {
kWhatPreparationFailed,
};
- // create a format-change discontinuity
- //
- // swap:
- // whether is format-change discontinuity should trigger a buffer swap
- sp<ABuffer> createFormatChangeBuffer(bool swap = true);
protected:
virtual ~LiveSession();
@@ -106,20 +109,18 @@ private:
kWhatDisconnect = 'disc',
kWhatSeek = 'seek',
kWhatFetcherNotify = 'notf',
- kWhatCheckBandwidth = 'bndw',
kWhatChangeConfiguration = 'chC0',
kWhatChangeConfiguration2 = 'chC2',
kWhatChangeConfiguration3 = 'chC3',
kWhatFinishDisconnect2 = 'fin2',
- kWhatSwapped = 'swap',
kWhatPollBuffering = 'poll',
};
- static const size_t kBandwidthHistoryBytes;
static const int64_t kHighWaterMark;
static const int64_t kMidWaterMark;
static const int64_t kLowWaterMark;
+ struct BandwidthEstimator;
struct BandwidthItem {
size_t mPlaylistIndex;
unsigned long mBandwidth;
@@ -128,23 +129,22 @@ private:
struct FetcherInfo {
sp<PlaylistFetcher> mFetcher;
int64_t mDurationUs;
- bool mIsPrepared;
bool mToBeRemoved;
+ bool mToBeResumed;
};
struct StreamItem {
const char *mType;
AString mUri, mNewUri;
+ SeekMode mSeekMode;
size_t mCurDiscontinuitySeq;
int64_t mLastDequeuedTimeUs;
int64_t mLastSampleDurationUs;
StreamItem()
- : mType(""),
- mCurDiscontinuitySeq(0),
- mLastDequeuedTimeUs(0),
- mLastSampleDurationUs(0) {}
+ : StreamItem("") {}
StreamItem(const char *type)
: mType(type),
+ mSeekMode(kSeekModeExactPosition),
mCurDiscontinuitySeq(0),
mLastDequeuedTimeUs(0),
mLastSampleDurationUs(0) {}
@@ -170,6 +170,8 @@ private:
Vector<BandwidthItem> mBandwidthItems;
ssize_t mCurBandwidthIndex;
+ int32_t mLastBandwidthBps;
+ sp<BandwidthEstimator> mBandwidthEstimator;
sp<M3UParser> mPlaylist;
@@ -190,12 +192,6 @@ private:
// A second set of packet sources that buffer content for the variant we're switching to.
KeyedVector<StreamType, sp<AnotherPacketSource> > mPacketSources2;
- // A mutex used to serialize two sets of events:
- // * the swapping of packet sources in dequeueAccessUnit on the player thread, AND
- // * a forced bandwidth switch termination in cancelSwitch on the live looper.
- Mutex mSwapMutex;
-
- int32_t mCheckBandwidthGeneration;
int32_t mSwitchGeneration;
int32_t mSubtitleGeneration;
@@ -244,12 +240,17 @@ private:
uint32_t block_size = 0,
/* reuse DataSource if doing partial fetch */
sp<DataSource> *source = NULL,
- String8 *actualUrl = NULL);
+ String8 *actualUrl = NULL,
+ /* force connect http even when resuing DataSource */
+ bool forceConnectHTTP = false);
sp<M3UParser> fetchPlaylist(
const char *url, uint8_t *curPlaylistHash, bool *unchanged);
- size_t getBandwidthIndex();
+ float getAbortThreshold(
+ ssize_t currentBWIndex, ssize_t targetBWIndex) const;
+ void addBandwidthMeasurement(size_t numBytes, int64_t delayUs);
+ size_t getBandwidthIndex(int32_t bandwidthBps);
int64_t latestMediaSegmentStartTimeUs();
static int SortByBandwidth(const BandwidthItem *, const BandwidthItem *);
@@ -261,26 +262,21 @@ private:
void onChangeConfiguration(const sp<AMessage> &msg);
void onChangeConfiguration2(const sp<AMessage> &msg);
void onChangeConfiguration3(const sp<AMessage> &msg);
- void onSwapped(const sp<AMessage> &msg);
- void tryToFinishBandwidthSwitch();
+ void swapPacketSource(StreamType stream);
+ void tryToFinishBandwidthSwitch(const AString &uri);
- // cancelBandwidthSwitch is atomic wrt swapPacketSource; call it to prevent packet sources
- // from being swapped out on stale discontinuities while manipulating
- // mPacketSources/mPacketSources2.
void cancelBandwidthSwitch();
void schedulePollBuffering();
void cancelPollBuffering();
void onPollBuffering();
bool checkBuffering(bool &low, bool &mid, bool &high);
- void switchBandwidthIfNeeded(bool canSwitchUp);
+ void switchBandwidthIfNeeded(bool bufferHigh, bool bufferLow);
void finishDisconnect();
void postPrepared(status_t err);
- void swapPacketSource(StreamType stream);
-
DISALLOW_EVIL_CONSTRUCTORS(LiveSession);
};
diff --git a/media/libstagefright/httplive/M3UParser.cpp b/media/libstagefright/httplive/M3UParser.cpp
index 997b694..3c5d7cf 100644
--- a/media/libstagefright/httplive/M3UParser.cpp
+++ b/media/libstagefright/httplive/M3UParser.cpp
@@ -251,6 +251,7 @@ M3UParser::M3UParser(
mIsComplete(false),
mIsEvent(false),
mDiscontinuitySeq(0),
+ mDiscontinuityCount(0),
mSelectedIndex(-1) {
mInitCheck = parse(data, size);
}
@@ -582,6 +583,7 @@ status_t M3UParser::parse(const void *_data, size_t size) {
itemMeta = new AMessage;
}
itemMeta->setInt32("discontinuity", true);
+ ++mDiscontinuityCount;
} else if (line.startsWith("#EXT-X-STREAM-INF")) {
if (mMeta != NULL) {
return ERROR_MALFORMED;
@@ -609,6 +611,9 @@ status_t M3UParser::parse(const void *_data, size_t size) {
} else if (line.startsWith("#EXT-X-MEDIA")) {
err = parseMedia(line);
} else if (line.startsWith("#EXT-X-DISCONTINUITY-SEQUENCE")) {
+ if (mIsVariantPlaylist) {
+ return ERROR_MALFORMED;
+ }
size_t seq;
err = parseDiscontinuitySequence(line, &seq);
if (err == OK) {
@@ -628,6 +633,7 @@ status_t M3UParser::parse(const void *_data, size_t size) {
|| !itemMeta->findInt64("durationUs", &durationUs)) {
return ERROR_MALFORMED;
}
+ itemMeta->setInt32("discontinuity-sequence", mDiscontinuitySeq + mDiscontinuityCount);
}
mItems.push();
diff --git a/media/libstagefright/httplive/M3UParser.h b/media/libstagefright/httplive/M3UParser.h
index 1cad060..d475683 100644
--- a/media/libstagefright/httplive/M3UParser.h
+++ b/media/libstagefright/httplive/M3UParser.h
@@ -70,6 +70,7 @@ private:
bool mIsComplete;
bool mIsEvent;
size_t mDiscontinuitySeq;
+ int32_t mDiscontinuityCount;
sp<AMessage> mMeta;
Vector<Item> mItems;
diff --git a/media/libstagefright/httplive/PlaylistFetcher.cpp b/media/libstagefright/httplive/PlaylistFetcher.cpp
index 3710686..68f0357 100644
--- a/media/libstagefright/httplive/PlaylistFetcher.cpp
+++ b/media/libstagefright/httplive/PlaylistFetcher.cpp
@@ -54,13 +54,98 @@ const int64_t PlaylistFetcher::kFetcherResumeThreshold = 100000ll;
const int32_t PlaylistFetcher::kDownloadBlockSize = 47 * 1024;
const int32_t PlaylistFetcher::kNumSkipFrames = 5;
+struct PlaylistFetcher::DownloadState : public RefBase {
+ DownloadState();
+ void resetState();
+ bool hasSavedState() const;
+ void restoreState(
+ AString &uri,
+ sp<AMessage> &itemMeta,
+ sp<ABuffer> &buffer,
+ sp<ABuffer> &tsBuffer,
+ int32_t &firstSeqNumberInPlaylist,
+ int32_t &lastSeqNumberInPlaylist);
+ void saveState(
+ AString &uri,
+ sp<AMessage> &itemMeta,
+ sp<ABuffer> &buffer,
+ sp<ABuffer> &tsBuffer,
+ int32_t &firstSeqNumberInPlaylist,
+ int32_t &lastSeqNumberInPlaylist);
+
+private:
+ bool mHasSavedState;
+ AString mUri;
+ sp<AMessage> mItemMeta;
+ sp<ABuffer> mBuffer;
+ sp<ABuffer> mTsBuffer;
+ int32_t mFirstSeqNumberInPlaylist;
+ int32_t mLastSeqNumberInPlaylist;
+};
+
+PlaylistFetcher::DownloadState::DownloadState() {
+ resetState();
+}
+
+bool PlaylistFetcher::DownloadState::hasSavedState() const {
+ return mHasSavedState;
+}
+
+void PlaylistFetcher::DownloadState::resetState() {
+ mHasSavedState = false;
+
+ mUri.clear();
+ mItemMeta = NULL;
+ mBuffer = NULL;
+ mTsBuffer = NULL;
+ mFirstSeqNumberInPlaylist = 0;
+ mLastSeqNumberInPlaylist = 0;
+}
+
+void PlaylistFetcher::DownloadState::restoreState(
+ AString &uri,
+ sp<AMessage> &itemMeta,
+ sp<ABuffer> &buffer,
+ sp<ABuffer> &tsBuffer,
+ int32_t &firstSeqNumberInPlaylist,
+ int32_t &lastSeqNumberInPlaylist) {
+ if (!mHasSavedState) {
+ return;
+ }
+
+ uri = mUri;
+ itemMeta = mItemMeta;
+ buffer = mBuffer;
+ tsBuffer = mTsBuffer;
+ firstSeqNumberInPlaylist = mFirstSeqNumberInPlaylist;
+ lastSeqNumberInPlaylist = mLastSeqNumberInPlaylist;
+
+ resetState();
+}
+
+void PlaylistFetcher::DownloadState::saveState(
+ AString &uri,
+ sp<AMessage> &itemMeta,
+ sp<ABuffer> &buffer,
+ sp<ABuffer> &tsBuffer,
+ int32_t &firstSeqNumberInPlaylist,
+ int32_t &lastSeqNumberInPlaylist) {
+ mHasSavedState = true;
+
+ mUri = uri;
+ mItemMeta = itemMeta;
+ mBuffer = buffer;
+ mTsBuffer = tsBuffer;
+ mFirstSeqNumberInPlaylist = firstSeqNumberInPlaylist;
+ mLastSeqNumberInPlaylist = lastSeqNumberInPlaylist;
+}
+
PlaylistFetcher::PlaylistFetcher(
const sp<AMessage> &notify,
const sp<LiveSession> &session,
const char *uri,
int32_t subtitleGeneration)
: mNotify(notify),
- mStartTimeUsNotify(notify->dup()),
mSession(session),
mURI(uri),
mStreamTypeMask(0),
@@ -72,18 +157,21 @@ PlaylistFetcher::PlaylistFetcher(
mSeqNumber(-1),
mNumRetries(0),
mStartup(true),
- mAdaptive(false),
+ mSeekMode(LiveSession::kSeekModeExactPosition),
mPrepared(false),
+ mTimeChangeSignaled(false),
mNextPTSTimeUs(-1ll),
mMonitorQueueGeneration(0),
mSubtitleGeneration(subtitleGeneration),
+ mLastDiscontinuitySeq(-1ll),
mRefreshState(INITIAL_MINIMUM_RELOAD_DELAY),
mFirstPTSValid(false),
- mAbsoluteTimeAnchorUs(0ll),
- mVideoBuffer(new AnotherPacketSource(NULL)) {
+ mFirstTimeUs(-1ll),
+ mVideoBuffer(new AnotherPacketSource(NULL)),
+ mThresholdRatio(-1.0f),
+ mDownloadState(new DownloadState()) {
memset(mPlaylistHash, 0, sizeof(mPlaylistHash));
- mStartTimeUsNotify->setInt32("what", kWhatStartedAt);
- mStartTimeUsNotify->setInt32("streamMask", 0);
+ mHTTPDataSource = mSession->getHTTPDataSource();
}
PlaylistFetcher::~PlaylistFetcher() {
@@ -335,6 +423,14 @@ void PlaylistFetcher::cancelMonitorQueue() {
++mMonitorQueueGeneration;
}
+void PlaylistFetcher::setStoppingThreshold(float thresholdRatio) {
+ AutoMutex _l(mThresholdLock);
+ if (mStreamTypeMask == LiveSession::STREAMTYPE_SUBTITLES) {
+ return;
+ }
+ mThresholdRatio = thresholdRatio;
+}
+
void PlaylistFetcher::startAsync(
const sp<AnotherPacketSource> &audioSource,
const sp<AnotherPacketSource> &videoSource,
@@ -342,7 +438,7 @@ void PlaylistFetcher::startAsync(
int64_t startTimeUs,
int64_t segmentStartTimeUs,
int32_t startDiscontinuitySeq,
- bool adaptive) {
+ LiveSession::SeekMode seekMode) {
sp<AMessage> msg = new AMessage(kWhatStart, this);
uint32_t streamTypeMask = 0ul;
@@ -366,15 +462,20 @@ void PlaylistFetcher::startAsync(
msg->setInt64("startTimeUs", startTimeUs);
msg->setInt64("segmentStartTimeUs", segmentStartTimeUs);
msg->setInt32("startDiscontinuitySeq", startDiscontinuitySeq);
- msg->setInt32("adaptive", adaptive);
+ msg->setInt32("seekMode", seekMode);
msg->post();
}
-void PlaylistFetcher::pauseAsync() {
+void PlaylistFetcher::pauseAsync(float thresholdRatio) {
+ if (thresholdRatio >= 0.0f) {
+ setStoppingThreshold(thresholdRatio);
+ }
(new AMessage(kWhatPause, this))->post();
}
void PlaylistFetcher::stopAsync(bool clear) {
+ setStoppingThreshold(0.0f);
+
sp<AMessage> msg = new AMessage(kWhatStop, this);
msg->setInt32("clear", clear);
msg->post();
@@ -405,6 +506,10 @@ void PlaylistFetcher::onMessageReceived(const sp<AMessage> &msg) {
sp<AMessage> notify = mNotify->dup();
notify->setInt32("what", kWhatPaused);
+ notify->setInt32("seekMode",
+ mDownloadState->hasSavedState()
+ ? LiveSession::kSeekModeNextSample
+ : LiveSession::kSeekModeNextSegment);
notify->post();
break;
}
@@ -451,6 +556,11 @@ void PlaylistFetcher::onMessageReceived(const sp<AMessage> &msg) {
status_t PlaylistFetcher::onStart(const sp<AMessage> &msg) {
mPacketSources.clear();
+ mStopParams.clear();
+ mStartTimeUsNotify = mNotify->dup();
+ mStartTimeUsNotify->setInt32("what", kWhatStartedAt);
+ mStartTimeUsNotify->setInt32("streamMask", 0);
+ mStartTimeUsNotify->setString("uri", mURI);
uint32_t streamTypeMask;
CHECK(msg->findInt32("streamTypeMask", (int32_t *)&streamTypeMask));
@@ -458,11 +568,11 @@ status_t PlaylistFetcher::onStart(const sp<AMessage> &msg) {
int64_t startTimeUs;
int64_t segmentStartTimeUs;
int32_t startDiscontinuitySeq;
- int32_t adaptive;
+ int32_t seekMode;
CHECK(msg->findInt64("startTimeUs", &startTimeUs));
CHECK(msg->findInt64("segmentStartTimeUs", &segmentStartTimeUs));
CHECK(msg->findInt32("startDiscontinuitySeq", &startDiscontinuitySeq));
- CHECK(msg->findInt32("adaptive", &adaptive));
+ CHECK(msg->findInt32("seekMode", &seekMode));
if (streamTypeMask & LiveSession::STREAMTYPE_AUDIO) {
void *ptr;
@@ -496,12 +606,19 @@ status_t PlaylistFetcher::onStart(const sp<AMessage> &msg) {
mSegmentStartTimeUs = segmentStartTimeUs;
mDiscontinuitySeq = startDiscontinuitySeq;
+ mRefreshState = INITIAL_MINIMUM_RELOAD_DELAY;
+ mSeekMode = (LiveSession::SeekMode) seekMode;
+
if (startTimeUs >= 0) {
mStartTimeUs = startTimeUs;
+ mFirstPTSValid = false;
mSeqNumber = -1;
mStartup = true;
mPrepared = false;
- mAdaptive = adaptive;
+ mIDRFound = false;
+ mTimeChangeSignaled = false;
+ mVideoBuffer->clear();
+ mDownloadState->resetState();
}
postMonitorQueue();
@@ -511,6 +628,9 @@ status_t PlaylistFetcher::onStart(const sp<AMessage> &msg) {
void PlaylistFetcher::onPause() {
cancelMonitorQueue();
+ mLastDiscontinuitySeq = mDiscontinuitySeq;
+
+ setStoppingThreshold(-1.0f);
}
void PlaylistFetcher::onStop(const sp<AMessage> &msg) {
@@ -525,8 +645,11 @@ void PlaylistFetcher::onStop(const sp<AMessage> &msg) {
}
}
+ mDownloadState->resetState();
mPacketSources.clear();
mStreamTypeMask = 0;
+
+ setStoppingThreshold(-1.0f);
}
// Resume until we have reached the boundary timestamps listed in `msg`; when
@@ -578,9 +701,6 @@ status_t PlaylistFetcher::onResumeUntil(const sp<AMessage> &msg) {
// Don't resume if all streams are within a resume threshold
if (stopCount == mPacketSources.size()) {
- for (size_t i = 0; i < mPacketSources.size(); i++) {
- mPacketSources.valueAt(i)->queueAccessUnit(mSession->createFormatChangeBuffer());
- }
stopAsync(/* clear = */ false);
return OK;
}
@@ -624,27 +744,23 @@ void PlaylistFetcher::onMonitorQueue() {
targetDurationUs = targetDurationSecs * 1000000ll;
}
- // buffer at least 3 times the target duration, or up to 10 seconds
- int64_t durationToBufferUs = targetDurationUs * 3;
- if (durationToBufferUs > kMinBufferedDurationUs) {
- durationToBufferUs = kMinBufferedDurationUs;
- }
-
int64_t bufferedDurationUs = 0ll;
- status_t finalResult = NOT_ENOUGH_DATA;
+ status_t finalResult = OK;
if (mStreamTypeMask == LiveSession::STREAMTYPE_SUBTITLES) {
sp<AnotherPacketSource> packetSource =
mPacketSources.valueFor(LiveSession::STREAMTYPE_SUBTITLES);
bufferedDurationUs =
packetSource->getBufferedDurationUs(&finalResult);
- finalResult = OK;
} else {
- // Use max stream duration to prevent us from waiting on a non-existent stream;
- // when we cannot make out from the manifest what streams are included in a playlist
- // we might assume extra streams.
+ // Use min stream duration, but ignore streams that never have any packet
+ // enqueued to prevent us from waiting on a non-existent stream;
+ // when we cannot make out from the manifest what streams are included in
+ // a playlist we might assume extra streams.
+ bufferedDurationUs = -1ll;
for (size_t i = 0; i < mPacketSources.size(); ++i) {
- if ((mStreamTypeMask & mPacketSources.keyAt(i)) == 0) {
+ if ((mStreamTypeMask & mPacketSources.keyAt(i)) == 0
+ || mPacketSources[i]->getLatestEnqueuedMeta() == NULL) {
continue;
}
@@ -652,24 +768,19 @@ void PlaylistFetcher::onMonitorQueue() {
mPacketSources.valueAt(i)->getBufferedDurationUs(&finalResult);
ALOGV("buffered %" PRId64 " for stream %d",
bufferedStreamDurationUs, mPacketSources.keyAt(i));
- if (bufferedStreamDurationUs > bufferedDurationUs) {
+ if (bufferedDurationUs == -1ll
+ || bufferedStreamDurationUs < bufferedDurationUs) {
bufferedDurationUs = bufferedStreamDurationUs;
}
}
- }
- downloadMore = (bufferedDurationUs < durationToBufferUs);
-
- // signal start if buffered up at least the target size
- if (!mPrepared && bufferedDurationUs > targetDurationUs && downloadMore) {
- mPrepared = true;
-
- ALOGV("prepared, buffered=%" PRId64 " > %" PRId64 "",
- bufferedDurationUs, targetDurationUs);
+ if (bufferedDurationUs == -1ll) {
+ bufferedDurationUs = 0ll;
+ }
}
- if (finalResult == OK && downloadMore) {
+ if (finalResult == OK && bufferedDurationUs < kMinBufferedDurationUs) {
ALOGV("monitoring, buffered=%" PRId64 " < %" PRId64 "",
- bufferedDurationUs, durationToBufferUs);
+ bufferedDurationUs, kMinBufferedDurationUs);
// delay the next download slightly; hopefully this gives other concurrent fetchers
// a better chance to run.
// onDownloadNext();
@@ -677,13 +788,16 @@ void PlaylistFetcher::onMonitorQueue() {
msg->setInt32("generation", mMonitorQueueGeneration);
msg->post(1000l);
} else {
- // Nothing to do yet, try again in a second.
- int64_t delayUs = mPrepared ? kMaxMonitorDelayUs : targetDurationUs / 2;
+ // We'd like to maintain buffering above durationToBufferUs, so try
+ // again when buffer just about to go below durationToBufferUs
+ // (or after targetDurationUs / 2, whichever is smaller).
+ int64_t delayUs = bufferedDurationUs - kMinBufferedDurationUs + 1000000ll;
+ if (delayUs > targetDurationUs / 2) {
+ delayUs = targetDurationUs / 2;
+ }
ALOGV("pausing for %" PRId64 ", buffered=%" PRId64 " > %" PRId64 "",
- delayUs, bufferedDurationUs, durationToBufferUs);
- // :TRICKY: need to enforce minimum delay because the delay to
- // refresh the playlist will become 0
- postMonitorQueue(delayUs, mPrepared ? targetDurationUs * 2 : 0);
+ delayUs, bufferedDurationUs, kMinBufferedDurationUs);
+ postMonitorQueue(delayUs);
}
}
@@ -724,10 +838,74 @@ bool PlaylistFetcher::bufferStartsWithTsSyncByte(const sp<ABuffer>& buffer) {
return buffer->size() > 0 && buffer->data()[0] == 0x47;
}
-void PlaylistFetcher::onDownloadNext() {
+bool PlaylistFetcher::shouldPauseDownload(bool startFound) {
+ if (mStreamTypeMask == LiveSession::STREAMTYPE_SUBTITLES) {
+ // doesn't apply to subtitles
+ return false;
+ }
+
+ // If we're switching, save state and pause after start point is found
+ if (mSeekMode != LiveSession::kSeekModeExactPosition && startFound) {
+ return true;
+ }
+
+ // Calculate threshold to abort current download
+ int32_t targetDurationSecs;
+ CHECK(mPlaylist->meta()->findInt32("target-duration", &targetDurationSecs));
+ int64_t targetDurationUs = targetDurationSecs * 1000000ll;
+ int64_t thresholdUs = -1;
+ {
+ AutoMutex _l(mThresholdLock);
+ thresholdUs = (mThresholdRatio < 0.0f) ?
+ -1ll : mThresholdRatio * targetDurationUs;
+ }
+
+ if (thresholdUs < 0) {
+ // never abort
+ return false;
+ } else if (thresholdUs == 0) {
+ // immediately abort
+ return true;
+ }
+
+ // now we have a positive thresholdUs, abort if remaining
+ // portion to download is over that threshold.
+ if (mSegmentFirstPTS < 0) {
+ // this means we haven't even find the first access unit,
+ // abort now as we must be very far away from the end.
+ return true;
+ }
+ int64_t lastEnqueueUs = mSegmentFirstPTS;
+ for (size_t i = 0; i < mPacketSources.size(); ++i) {
+ if ((mStreamTypeMask & mPacketSources.keyAt(i)) == 0) {
+ continue;
+ }
+ sp<AMessage> meta = mPacketSources[i]->getLatestEnqueuedMeta();
+ int32_t type;
+ if (meta == NULL || meta->findInt32("discontinuity", &type)) {
+ continue;
+ }
+ int64_t tmpUs;
+ CHECK(meta->findInt64("timeUs", &tmpUs));
+ if (tmpUs > lastEnqueueUs) {
+ lastEnqueueUs = tmpUs;
+ }
+ }
+ lastEnqueueUs -= mSegmentFirstPTS;
+ if (targetDurationUs - lastEnqueueUs > thresholdUs) {
+ return true;
+ }
+ return false;
+}
+
+bool PlaylistFetcher::initDownloadState(
+ AString &uri,
+ sp<AMessage> &itemMeta,
+ int32_t &firstSeqNumberInPlaylist,
+ int32_t &lastSeqNumberInPlaylist) {
status_t err = refreshPlaylist();
- int32_t firstSeqNumberInPlaylist = 0;
- int32_t lastSeqNumberInPlaylist = 0;
+ firstSeqNumberInPlaylist = 0;
+ lastSeqNumberInPlaylist = 0;
bool discontinuity = false;
if (mPlaylist != NULL) {
@@ -743,6 +921,8 @@ void PlaylistFetcher::onDownloadNext() {
}
}
+ mSegmentFirstPTS = -1ll;
+
if (mPlaylist != NULL && mSeqNumber < 0) {
CHECK_GE(mStartTimeUs, 0ll);
@@ -770,7 +950,7 @@ void PlaylistFetcher::onDownloadNext() {
// timestamps coming from the media container) is used to determine the position
// inside a segments.
mSeqNumber = getSeqNumberForTime(mSegmentStartTimeUs);
- if (mAdaptive) {
+ if (mSeekMode == LiveSession::kSeekModeNextSegment) {
// avoid double fetch/decode
mSeqNumber += 1;
}
@@ -820,12 +1000,12 @@ void PlaylistFetcher::onDownloadNext() {
mSeqNumber, firstSeqNumberInPlaylist,
lastSeqNumberInPlaylist, delayUs, mNumRetries);
postMonitorQueue(delayUs);
- return;
+ return false;
}
if (err != OK) {
notifyError(err);
- return;
+ return false;
}
// we've missed the boat, let's start 3 segments prior to the latest sequence
@@ -840,12 +1020,8 @@ void PlaylistFetcher::onDownloadNext() {
// but since the segments we are supposed to fetch have already rolled off
// the playlist, i.e. we have already missed the boat, we inevitably have to
// skip.
- for (size_t i = 0; i < mPacketSources.size(); i++) {
- sp<ABuffer> formatChange = mSession->createFormatChangeBuffer();
- mPacketSources.valueAt(i)->queueAccessUnit(formatChange);
- }
stopAsync(/* clear = */ false);
- return;
+ return false;
}
mSeqNumber = lastSeqNumberInPlaylist - 3;
if (mSeqNumber < firstSeqNumberInPlaylist) {
@@ -861,39 +1037,34 @@ void PlaylistFetcher::onDownloadNext() {
firstSeqNumberInPlaylist + (int32_t)mPlaylist->size() - 1);
notifyError(ERROR_END_OF_STREAM);
- return;
+ return false;
}
}
mNumRetries = 0;
- AString uri;
- sp<AMessage> itemMeta;
CHECK(mPlaylist->itemAt(
mSeqNumber - firstSeqNumberInPlaylist,
&uri,
&itemMeta));
+ CHECK(itemMeta->findInt32("discontinuity-sequence", &mDiscontinuitySeq));
+
int32_t val;
if (itemMeta->findInt32("discontinuity", &val) && val != 0) {
- mDiscontinuitySeq++;
+ discontinuity = true;
+ } else if (mLastDiscontinuitySeq >= 0
+ && mDiscontinuitySeq != mLastDiscontinuitySeq) {
+ // Seek jumped to a new discontinuity sequence. We need to signal
+ // a format change to decoder. Decoder needs to shutdown and be
+ // created again if seamless format change is unsupported.
+ ALOGV("saw discontinuity: mStartup %d, mLastDiscontinuitySeq %d, "
+ "mDiscontinuitySeq %d, mStartTimeUs %lld",
+ mStartup, mLastDiscontinuitySeq, mDiscontinuitySeq, (long long)mStartTimeUs);
discontinuity = true;
}
+ mLastDiscontinuitySeq = -1;
- int64_t range_offset, range_length;
- if (!itemMeta->findInt64("range-offset", &range_offset)
- || !itemMeta->findInt64("range-length", &range_length)) {
- range_offset = 0;
- range_length = -1;
- }
-
- ALOGV("fetching segment %d from (%d .. %d)",
- mSeqNumber, firstSeqNumberInPlaylist, lastSeqNumberInPlaylist);
-
- ALOGV("fetching '%s'", uri.c_str());
-
- sp<DataSource> source;
- sp<ABuffer> buffer, tsBuffer;
// decrypt a junk buffer to prefetch key; since a session uses only one http connection,
// this avoids interleaved connections to the key and segment file.
{
@@ -903,16 +1074,117 @@ void PlaylistFetcher::onDownloadNext() {
true /* first */);
if (err != OK) {
notifyError(err);
+ return false;
+ }
+ }
+
+ if ((mStartup && !mTimeChangeSignaled) || discontinuity) {
+ // We need to signal a time discontinuity to ATSParser on the
+ // first segment after start, or on a discontinuity segment.
+ // Setting mNextPTSTimeUs informs extractAndQueueAccessUnitsXX()
+ // to send the time discontinuity.
+ if (mPlaylist->isComplete() || mPlaylist->isEvent()) {
+ // If this was a live event this made no sense since
+ // we don't have access to all the segment before the current
+ // one.
+ mNextPTSTimeUs = getSegmentStartTimeUs(mSeqNumber);
+ }
+
+ // Setting mTimeChangeSignaled to true, so that if start time
+ // searching goes into 2nd segment (without a discontinuity),
+ // we don't reset time again. It causes corruption when pending
+ // data in ATSParser is cleared.
+ mTimeChangeSignaled = true;
+ }
+
+ if (discontinuity) {
+ ALOGI("queueing discontinuity (explicit=%d)", discontinuity);
+
+ // Signal a format discontinuity to ATSParser to clear partial data
+ // from previous streams. Not doing this causes bitstream corruption.
+ mTSParser->signalDiscontinuity(
+ ATSParser::DISCONTINUITY_FORMATCHANGE, NULL /* extra */);
+
+ queueDiscontinuity(
+ ATSParser::DISCONTINUITY_FORMATCHANGE,
+ NULL /* extra */);
+
+ if (mStartup && mStartTimeUsRelative && mFirstPTSValid) {
+ // This means we guessed mStartTimeUs to be in the previous
+ // segment (likely very close to the end), but either video or
+ // audio has not found start by the end of that segment.
+ //
+ // If this new segment is not a discontinuity, keep searching.
+ //
+ // If this new segment even got a discontinuity marker, just
+ // set mStartTimeUs=0, and take all samples from now on.
+ mStartTimeUs = 0;
+ mFirstPTSValid = false;
+ }
+ }
+
+ ALOGV("fetching segment %d from (%d .. %d)",
+ mSeqNumber, firstSeqNumberInPlaylist, lastSeqNumberInPlaylist);
+ return true;
+}
+
+void PlaylistFetcher::onDownloadNext() {
+ AString uri;
+ sp<AMessage> itemMeta;
+ sp<ABuffer> buffer;
+ sp<ABuffer> tsBuffer;
+ int32_t firstSeqNumberInPlaylist = 0;
+ int32_t lastSeqNumberInPlaylist = 0;
+ bool connectHTTP = true;
+
+ if (mDownloadState->hasSavedState()) {
+ mDownloadState->restoreState(
+ uri,
+ itemMeta,
+ buffer,
+ tsBuffer,
+ firstSeqNumberInPlaylist,
+ lastSeqNumberInPlaylist);
+ connectHTTP = false;
+ ALOGV("resuming: '%s'", uri.c_str());
+ } else {
+ if (!initDownloadState(
+ uri,
+ itemMeta,
+ firstSeqNumberInPlaylist,
+ lastSeqNumberInPlaylist)) {
return;
}
+ ALOGV("fetching: '%s'", uri.c_str());
+ }
+
+ int64_t range_offset, range_length;
+ if (!itemMeta->findInt64("range-offset", &range_offset)
+ || !itemMeta->findInt64("range-length", &range_length)) {
+ range_offset = 0;
+ range_length = -1;
}
// block-wise download
- bool startup = mStartup;
ssize_t bytesRead;
do {
+ sp<DataSource> source = mHTTPDataSource;
+
+ int64_t startUs = ALooper::GetNowUs();
bytesRead = mSession->fetchFile(
- uri.c_str(), &buffer, range_offset, range_length, kDownloadBlockSize, &source);
+ uri.c_str(), &buffer, range_offset, range_length, kDownloadBlockSize,
+ &source, NULL, connectHTTP);
+
+ // add sample for bandwidth estimation (excluding subtitles)
+ if (bytesRead > 0
+ && (mStreamTypeMask
+ & (LiveSession::STREAMTYPE_AUDIO
+ | LiveSession::STREAMTYPE_VIDEO))) {
+ int64_t delayUs = ALooper::GetNowUs() - startUs;
+ mSession->addBandwidthMeasurement(bytesRead, delayUs);
+ }
+
+ connectHTTP = false;
if (bytesRead < 0) {
status_t err = bytesRead;
@@ -938,28 +1210,7 @@ void PlaylistFetcher::onDownloadNext() {
return;
}
- if (startup || discontinuity) {
- // Signal discontinuity.
-
- if (mPlaylist->isComplete() || mPlaylist->isEvent()) {
- // If this was a live event this made no sense since
- // we don't have access to all the segment before the current
- // one.
- mNextPTSTimeUs = getSegmentStartTimeUs(mSeqNumber);
- }
-
- if (discontinuity) {
- ALOGI("queueing discontinuity (explicit=%d)", discontinuity);
-
- queueDiscontinuity(
- ATSParser::DISCONTINUITY_FORMATCHANGE,
- NULL /* extra */);
-
- discontinuity = false;
- }
-
- startup = false;
- }
+ bool startUp = mStartup; // save current start up state
err = OK;
if (bufferStartsWithTsSyncByte(buffer)) {
@@ -973,7 +1224,6 @@ void PlaylistFetcher::onDownloadNext() {
tsBuffer->setRange(tsOff, tsSize);
}
tsBuffer->setRange(tsBuffer->offset(), tsBuffer->size() + bytesRead);
-
err = extractAndQueueAccessUnitsFromTs(tsBuffer);
}
@@ -993,8 +1243,17 @@ void PlaylistFetcher::onDownloadNext() {
} else if (err != OK) {
notifyError(err);
return;
+ } else if (bytesRead != 0 &&
+ shouldPauseDownload(mStartup != startUp /* startFound */)) {
+ mDownloadState->saveState(
+ uri,
+ itemMeta,
+ buffer,
+ tsBuffer,
+ firstSeqNumberInPlaylist,
+ lastSeqNumberInPlaylist);
+ return;
}
-
} while (bytesRead != 0);
if (bufferStartsWithTsSyncByte(buffer)) {
@@ -1031,7 +1290,7 @@ void PlaylistFetcher::onDownloadNext() {
return;
}
- err = OK;
+ status_t err = OK;
if (tsBuffer != NULL) {
AString method;
CHECK(buffer->meta()->findString("cipher-method", &method));
@@ -1064,11 +1323,11 @@ void PlaylistFetcher::onDownloadNext() {
}
++mSeqNumber;
-
postMonitorQueue();
}
-int32_t PlaylistFetcher::getSeqNumberWithAnchorTime(int64_t anchorTimeUs) const {
+int32_t PlaylistFetcher::getSeqNumberWithAnchorTime(
+ int64_t anchorTimeUs, int64_t targetDurationUs) const {
int32_t firstSeqNumberInPlaylist, lastSeqNumberInPlaylist;
if (mPlaylist->meta() == NULL
|| !mPlaylist->meta()->findInt32("media-sequence", &firstSeqNumberInPlaylist)) {
@@ -1077,7 +1336,8 @@ int32_t PlaylistFetcher::getSeqNumberWithAnchorTime(int64_t anchorTimeUs) const
lastSeqNumberInPlaylist = firstSeqNumberInPlaylist + mPlaylist->size() - 1;
int32_t index = mSeqNumber - firstSeqNumberInPlaylist - 1;
- while (index >= 0 && anchorTimeUs > mStartTimeUs) {
+ // adjust anchorTimeUs to within 1x targetDurationUs from mStartTimeUs
+ while (index >= 0 && anchorTimeUs - mStartTimeUs > targetDurationUs) {
sp<AMessage> itemMeta;
CHECK(mPlaylist->itemAt(index, NULL /* uri */, &itemMeta));
@@ -1197,9 +1457,7 @@ status_t PlaylistFetcher::extractAndQueueAccessUnitsFromTs(const sp<ABuffer> &bu
mTSParser->signalDiscontinuity(
ATSParser::DISCONTINUITY_TIME, extra);
- mAbsoluteTimeAnchorUs = mNextPTSTimeUs;
mNextPTSTimeUs = -1ll;
- mFirstPTSValid = false;
}
size_t offset = 0;
@@ -1252,14 +1510,22 @@ status_t PlaylistFetcher::extractAndQueueAccessUnitsFromTs(const sp<ABuffer> &bu
continue;
}
- int64_t timeUs;
+ const char *mime;
+ sp<MetaData> format = source->getFormat();
+ bool isAvc = format != NULL && format->findCString(kKeyMIMEType, &mime)
+ && !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC);
+
sp<ABuffer> accessUnit;
status_t finalResult;
while (source->hasBufferAvailable(&finalResult)
&& source->dequeueAccessUnit(&accessUnit) == OK) {
+ int64_t timeUs;
CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+ if (mSegmentFirstPTS < 0ll) {
+ mSegmentFirstPTS = timeUs;
+ }
if (mStartup) {
if (!mFirstPTSValid) {
mFirstTimeUs = timeUs;
@@ -1272,30 +1538,30 @@ status_t PlaylistFetcher::extractAndQueueAccessUnitsFromTs(const sp<ABuffer> &bu
}
}
- if (timeUs < mStartTimeUs) {
- // buffer up to the closest preceding IDR frame
- ALOGV("timeUs %" PRId64 " us < mStartTimeUs %" PRId64 " us",
- timeUs, mStartTimeUs);
- const char *mime;
- sp<MetaData> format = source->getFormat();
- bool isAvc = false;
- if (format != NULL && format->findCString(kKeyMIMEType, &mime)
- && !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) {
- isAvc = true;
- }
- if (isAvc && IsIDR(accessUnit)) {
- mVideoBuffer->clear();
- }
+ bool seeking = mSeekMode == LiveSession::kSeekModeExactPosition;
+ bool startTimeReached =
+ seeking ? (timeUs >= mStartTimeUs)
+ : (timeUs > mStartTimeUs);
+
+ if (!startTimeReached || (isAvc && !mIDRFound)) {
+ // buffer up to the closest preceding IDR frame in the next segement,
+ // or the closest succeeding IDR frame after the exact position
if (isAvc) {
- mVideoBuffer->queueAccessUnit(accessUnit);
+ if (IsIDR(accessUnit) && (seeking || startTimeReached)) {
+ mVideoBuffer->clear();
+ mIDRFound = true;
+ }
+ if (mIDRFound && seeking && !startTimeReached) {
+ mVideoBuffer->queueAccessUnit(accessUnit);
+ }
+ }
+ if (!startTimeReached || (isAvc && !mIDRFound)) {
+ continue;
}
-
- continue;
}
}
- CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
- if (mStartTimeUsNotify != NULL && timeUs > mStartTimeUs) {
+ if (mStartTimeUsNotify != NULL) {
int32_t firstSeqNumberInPlaylist;
if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32(
"media-sequence", &firstSeqNumberInPlaylist)) {
@@ -1328,7 +1594,8 @@ status_t PlaylistFetcher::extractAndQueueAccessUnitsFromTs(const sp<ABuffer> &bu
// live stream; re-adjust based on the actual timestamp extracted from the
// media segment; if we didn't move backward after the re-adjustment
// (newSeqNumber), start at least 1 segment prior.
- int32_t newSeqNumber = getSeqNumberWithAnchorTime(timeUs);
+ int32_t newSeqNumber = getSeqNumberWithAnchorTime(
+ timeUs, targetDurationUs);
if (newSeqNumber >= mSeqNumber) {
--mSeqNumber;
} else {
@@ -1336,6 +1603,7 @@ status_t PlaylistFetcher::extractAndQueueAccessUnitsFromTs(const sp<ABuffer> &bu
}
mStartTimeUsNotify = mNotify->dup();
mStartTimeUsNotify->setInt32("what", kWhatStartedAt);
+ mIDRFound = false;
return -EAGAIN;
}
@@ -1354,7 +1622,11 @@ status_t PlaylistFetcher::extractAndQueueAccessUnitsFromTs(const sp<ABuffer> &bu
if (streamMask == mStreamTypeMask) {
mStartup = false;
- mStartTimeUsNotify->post();
+ // only need to post if we're switching and searching for a
+ // start point in next segment, or next IDR
+ if (mSeekMode != LiveSession::kSeekModeExactPosition) {
+ mStartTimeUsNotify->post();
+ }
mStartTimeUsNotify.clear();
}
}
@@ -1369,7 +1641,6 @@ status_t PlaylistFetcher::extractAndQueueAccessUnitsFromTs(const sp<ABuffer> &bu
|| !mStopParams->findInt64(key, &stopTimeUs)
|| (discontinuitySeq == mDiscontinuitySeq
&& timeUs >= stopTimeUs)) {
- packetSource->queueAccessUnit(mSession->createFormatChangeBuffer());
mStreamTypeMask &= ~stream;
mPacketSources.removeItemsAt(i);
break;
@@ -1464,8 +1735,6 @@ status_t PlaylistFetcher::extractAndQueueAccessUnits(
}
if (mNextPTSTimeUs >= 0ll) {
- mFirstPTSValid = false;
- mAbsoluteTimeAnchorUs = mNextPTSTimeUs;
mNextPTSTimeUs = -1ll;
}
@@ -1566,7 +1835,7 @@ status_t PlaylistFetcher::extractAndQueueAccessUnits(
CHECK(packetSource->getFormat()->findInt32(kKeySampleRate, &sampleRate));
int64_t timeUs = (PTS * 100ll) / 9ll;
- if (!mFirstPTSValid) {
+ if (mStartup && !mFirstPTSValid) {
mFirstPTSValid = true;
mFirstTimeUs = timeUs;
}
@@ -1621,7 +1890,8 @@ status_t PlaylistFetcher::extractAndQueueAccessUnits(
// Duplicated logic from how we handle .ts playlists.
if (mStartup && mSegmentStartTimeUs >= 0
&& timeUs - mStartTimeUs > targetDurationUs) {
- int32_t newSeqNumber = getSeqNumberWithAnchorTime(timeUs);
+ int32_t newSeqNumber = getSeqNumberWithAnchorTime(
+ timeUs, targetDurationUs);
if (newSeqNumber >= mSeqNumber) {
--mSeqNumber;
} else {
@@ -1647,7 +1917,6 @@ status_t PlaylistFetcher::extractAndQueueAccessUnits(
|| discontinuitySeq > mDiscontinuitySeq
|| !mStopParams->findInt64("timeUsAudio", &stopTimeUs)
|| (discontinuitySeq == mDiscontinuitySeq && unitTimeUs >= stopTimeUs)) {
- packetSource->queueAccessUnit(mSession->createFormatChangeBuffer());
mStreamTypeMask = 0;
mPacketSources.clear();
return ERROR_OUT_OF_RANGE;
diff --git a/media/libstagefright/httplive/PlaylistFetcher.h b/media/libstagefright/httplive/PlaylistFetcher.h
index 2f11949..8d34cbc 100644
--- a/media/libstagefright/httplive/PlaylistFetcher.h
+++ b/media/libstagefright/httplive/PlaylistFetcher.h
@@ -65,9 +65,9 @@ struct PlaylistFetcher : public AHandler {
int64_t segmentStartTimeUs = -1ll, // starting position within playlist
// startTimeUs!=segmentStartTimeUs only when playlist is live
int32_t startDiscontinuitySeq = 0,
- bool adaptive = false);
+ LiveSession::SeekMode seekMode = LiveSession::kSeekModeExactPosition);
- void pauseAsync();
+ void pauseAsync(float thresholdRatio);
void stopAsync(bool clear = true);
@@ -95,6 +95,8 @@ private:
kWhatDownloadNext = 'dlnx',
};
+ struct DownloadState;
+
static const int64_t kMaxMonitorDelayUs;
static const int32_t kNumSkipFrames;
@@ -105,6 +107,7 @@ private:
sp<AMessage> mNotify;
sp<AMessage> mStartTimeUsNotify;
+ sp<HTTPBase> mHTTPDataSource;
sp<LiveSession> mSession;
AString mURI;
@@ -116,7 +119,7 @@ private:
// adapting or switching tracks.
int64_t mSegmentStartTimeUs;
- ssize_t mDiscontinuitySeq;
+ int32_t mDiscontinuitySeq;
bool mStartTimeUsRelative;
sp<AMessage> mStopParams; // message containing the latest timestamps we should fetch.
@@ -130,13 +133,17 @@ private:
int32_t mSeqNumber;
int32_t mNumRetries;
bool mStartup;
- bool mAdaptive;
+ bool mIDRFound;
+ int32_t mSeekMode;
bool mPrepared;
+ bool mTimeChangeSignaled;
int64_t mNextPTSTimeUs;
int32_t mMonitorQueueGeneration;
const int32_t mSubtitleGeneration;
+ int32_t mLastDiscontinuitySeq;
+
enum RefreshState {
INITIAL_MINIMUM_RELOAD_DELAY,
FIRST_UNCHANGED_RELOAD_ATTEMPT,
@@ -150,9 +157,8 @@ private:
sp<ATSParser> mTSParser;
bool mFirstPTSValid;
- uint64_t mFirstPTS;
int64_t mFirstTimeUs;
- int64_t mAbsoluteTimeAnchorUs;
+ int64_t mSegmentFirstPTS;
sp<AnotherPacketSource> mVideoBuffer;
// Stores the initialization vector to decrypt the next block of cipher text, which can
@@ -160,6 +166,11 @@ private:
// the last block of cipher text (cipher-block chaining).
unsigned char mAESInitVec[16];
+ Mutex mThresholdLock;
+ float mThresholdRatio;
+
+ sp<DownloadState> mDownloadState;
+
// Set first to true if decrypting the first segment of a playlist segment. When
// first is true, reset the initialization vector based on the available
// information in the manifest; otherwise, use the initialization vector as
@@ -175,6 +186,8 @@ private:
void postMonitorQueue(int64_t delayUs = 0, int64_t minDelayUs = 0);
void cancelMonitorQueue();
+ void setStoppingThreshold(float thresholdRatio);
+ bool shouldPauseDownload(bool startFound);
int64_t delayUsToRefreshPlaylist() const;
status_t refreshPlaylist();
@@ -188,6 +201,11 @@ private:
void onStop(const sp<AMessage> &msg);
void onMonitorQueue();
void onDownloadNext();
+ bool initDownloadState(
+ AString &uri,
+ sp<AMessage> &itemMeta,
+ int32_t &firstSeqNumberInPlaylist,
+ int32_t &lastSeqNumberInPlaylist);
// Resume a fetcher to continue until the stopping point stored in msg.
status_t onResumeUntil(const sp<AMessage> &msg);
@@ -206,7 +224,8 @@ private:
void queueDiscontinuity(
ATSParser::DiscontinuityType type, const sp<AMessage> &extra);
- int32_t getSeqNumberWithAnchorTime(int64_t anchorTimeUs) const;
+ int32_t getSeqNumberWithAnchorTime(
+ int64_t anchorTimeUs, int64_t targetDurationUs) const;
int32_t getSeqNumberForDiscontinuity(size_t discontinuitySeq) const;
int32_t getSeqNumberForTime(int64_t timeUs) const;
diff --git a/media/libstagefright/mpeg2ts/ATSParser.h b/media/libstagefright/mpeg2ts/ATSParser.h
index 75d76dc..5c50747 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.h
+++ b/media/libstagefright/mpeg2ts/ATSParser.h
@@ -46,6 +46,9 @@ struct ATSParser : public RefBase {
DISCONTINUITY_AUDIO_FORMAT
| DISCONTINUITY_VIDEO_FORMAT
| DISCONTINUITY_TIME,
+ DISCONTINUITY_FORMAT_ONLY =
+ DISCONTINUITY_AUDIO_FORMAT
+ | DISCONTINUITY_VIDEO_FORMAT,
};
enum Flags {
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
index bb05417..9f42217 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
@@ -48,7 +48,10 @@ AnotherPacketSource::AnotherPacketSource(const sp<MetaData> &meta)
}
void AnotherPacketSource::setFormat(const sp<MetaData> &meta) {
- CHECK(mFormat == NULL);
+ if (mFormat != NULL) {
+ // Only allowed to be set once. Requires explicit clear to reset.
+ return;
+ }
mIsAudio = false;
mIsVideo = false;
@@ -94,7 +97,8 @@ sp<MetaData> AnotherPacketSource::getFormat() {
if (!buffer->meta()->findInt32("discontinuity", &discontinuity)) {
sp<RefBase> object;
if (buffer->meta()->findObject("format", &object)) {
- return mFormat = static_cast<MetaData*>(object.get());
+ setFormat(static_cast<MetaData*>(object.get()));
+ return mFormat;
}
}
@@ -129,7 +133,7 @@ status_t AnotherPacketSource::dequeueAccessUnit(sp<ABuffer> *buffer) {
sp<RefBase> object;
if ((*buffer)->meta()->findObject("format", &object)) {
- mFormat = static_cast<MetaData*>(object.get());
+ setFormat(static_cast<MetaData*>(object.get()));
}
return OK;
@@ -164,7 +168,7 @@ status_t AnotherPacketSource::read(
sp<RefBase> object;
if (buffer->meta()->findObject("format", &object)) {
- mFormat = static_cast<MetaData*>(object.get());
+ setFormat(static_cast<MetaData*>(object.get()));
}
int64_t timeUs;
@@ -294,6 +298,7 @@ void AnotherPacketSource::signalEOS(status_t result) {
bool AnotherPacketSource::hasBufferAvailable(status_t *finalResult) {
Mutex::Autolock autoLock(mLock);
+ *finalResult = OK;
if (!mBuffers.empty()) {
return true;
}
@@ -302,6 +307,21 @@ bool AnotherPacketSource::hasBufferAvailable(status_t *finalResult) {
return false;
}
+bool AnotherPacketSource::hasDataBufferAvailable(status_t *finalResult) {
+ Mutex::Autolock autoLock(mLock);
+ *finalResult = OK;
+ List<sp<ABuffer> >::iterator it;
+ for (it = mBuffers.begin(); it != mBuffers.end(); it++) {
+ int32_t discontinuity;
+ if (!(*it)->meta()->findInt32("discontinuity", &discontinuity)) {
+ return true;
+ }
+ }
+
+ *finalResult = mEOSResult;
+ return false;
+}
+
int64_t AnotherPacketSource::getBufferedDurationUs(status_t *finalResult) {
Mutex::Autolock autoLock(mLock);
return getBufferedDurationUs_l(finalResult);
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.h b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
index 809a858..d4fde7c 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.h
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
@@ -43,8 +43,12 @@ struct AnotherPacketSource : public MediaSource {
void clear();
+ // Returns true if we have any packets including discontinuities
bool hasBufferAvailable(status_t *finalResult);
+ // Returns true if we have packets that's not discontinuities
+ bool hasDataBufferAvailable(status_t *finalResult);
+
// Returns the difference between the last and the first queued
// presentation timestamps since the last discontinuity (if any).
int64_t getBufferedDurationUs(status_t *finalResult);
diff --git a/media/mediaserver/Android.mk b/media/mediaserver/Android.mk
index f1b84ad..0ad0bf3 100644
--- a/media/mediaserver/Android.mk
+++ b/media/mediaserver/Android.mk
@@ -26,7 +26,8 @@ LOCAL_SHARED_LIBRARIES := \
libutils \
liblog \
libbinder \
- libsoundtriggerservice
+ libsoundtriggerservice \
+ libradioservice
LOCAL_STATIC_LIBRARIES := \
libregistermsext
@@ -38,7 +39,8 @@ LOCAL_C_INCLUDES := \
frameworks/av/services/audiopolicy \
frameworks/av/services/camera/libcameraservice \
$(call include-path-for, audio-utils) \
- frameworks/av/services/soundtrigger
+ frameworks/av/services/soundtrigger \
+ frameworks/av/services/radio
LOCAL_MODULE:= mediaserver
LOCAL_32_BIT_ONLY := true
diff --git a/media/mediaserver/main_mediaserver.cpp b/media/mediaserver/main_mediaserver.cpp
index 263dd32..99572f8 100644
--- a/media/mediaserver/main_mediaserver.cpp
+++ b/media/mediaserver/main_mediaserver.cpp
@@ -35,6 +35,7 @@
#include "MediaPlayerService.h"
#include "service/AudioPolicyService.h"
#include "SoundTriggerHwService.h"
+#include "RadioService.h"
using namespace android;
@@ -130,6 +131,7 @@ int main(int argc __unused, char** argv)
CameraService::instantiate();
AudioPolicyService::instantiate();
SoundTriggerHwService::instantiate();
+ RadioService::instantiate();
registerExtensions();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();