diff options
31 files changed, 996 insertions, 471 deletions
diff --git a/include/media/IResourceManagerClient.h b/include/media/IResourceManagerClient.h index 3587aea..aa0cd88 100644 --- a/include/media/IResourceManagerClient.h +++ b/include/media/IResourceManagerClient.h @@ -18,6 +18,7 @@ #define ANDROID_IRESOURCEMANAGERCLIENT_H #include <utils/RefBase.h> +#include <utils/String8.h> #include <binder/IInterface.h> #include <binder/Parcel.h> @@ -29,6 +30,7 @@ public: DECLARE_META_INTERFACE(ResourceManagerClient); virtual bool reclaimResource() = 0; + virtual String8 getName() = 0; }; // ---------------------------------------------------------------------------- diff --git a/include/media/stagefright/MediaSync.h b/include/media/stagefright/MediaSync.h index 8bb8c7f..8ad74a4 100644 --- a/include/media/stagefright/MediaSync.h +++ b/include/media/stagefright/MediaSync.h @@ -113,6 +113,9 @@ public: // MediaClock::getMediaTime() and MediaClock::getRealTimeFor(). sp<const MediaClock> getMediaClock(); + // Get the play time for pending audio frames in audio sink. + status_t getPlayTimeForPendingAudioFrames(int64_t *outTimeUs); + protected: virtual void onMessageReceived(const sp<AMessage> &msg); diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h index 8bdebf6..ca80123 100644 --- a/include/media/stagefright/MetaData.h +++ b/include/media/stagefright/MetaData.h @@ -176,6 +176,9 @@ enum { kKeyTrackIsDefault = 'dflt', // bool (int32_t) // Similar to MediaFormat.KEY_IS_FORCED_SUBTITLE but pertains to av tracks as well. kKeyTrackIsForced = 'frcd', // bool (int32_t) + + // H264 supplemental enhancement information offsets/sizes + kKeySEI = 'sei ', // raw data }; enum { diff --git a/media/libmedia/IResourceManagerClient.cpp b/media/libmedia/IResourceManagerClient.cpp index 6fa56fc..b3f56e8 100644 --- a/media/libmedia/IResourceManagerClient.cpp +++ b/media/libmedia/IResourceManagerClient.cpp @@ -25,6 +25,7 @@ namespace android { enum { RECLAIM_RESOURCE = IBinder::FIRST_CALL_TRANSACTION, + GET_NAME, }; class BpResourceManagerClient: public BpInterface<IResourceManagerClient> @@ -46,6 +47,19 @@ public: } return ret; } + + virtual String8 getName() { + Parcel data, reply; + data.writeInterfaceToken(IResourceManagerClient::getInterfaceDescriptor()); + + String8 ret; + status_t status = remote()->transact(GET_NAME, data, &reply); + if (status == NO_ERROR) { + ret = reply.readString8(); + } + return ret; + } + }; IMPLEMENT_META_INTERFACE(ResourceManagerClient, "android.media.IResourceManagerClient"); @@ -62,6 +76,12 @@ status_t BnResourceManagerClient::onTransact( reply->writeInt32(ret); return NO_ERROR; } break; + case GET_NAME: { + CHECK_INTERFACE(IResourceManagerClient, data, reply); + String8 ret = getName(); + reply->writeString8(ret); + return NO_ERROR; + } break; default: return BBinder::onTransact(code, data, reply, flags); } diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp index fb21c73..8a0b060 100644 --- a/media/libmediaplayerservice/StagefrightRecorder.cpp +++ b/media/libmediaplayerservice/StagefrightRecorder.cpp @@ -953,6 +953,7 @@ sp<MediaSource> StagefrightRecorder::createAudioSource() { if (mAudioTimeScale > 0) { format->setInt32("time-scale", mAudioTimeScale); } + format->setInt32("priority", 0 /* realtime */); sp<MediaSource> audioEncoder = MediaCodecSource::Create(mLooper, format, audioSource); @@ -1543,6 +1544,11 @@ status_t StagefrightRecorder::setupVideoEncoder( format->setInt32("level", mVideoEncoderLevel); } + format->setInt32("priority", 0 /* realtime */); + if (mCaptureTimeLapse) { + format->setFloat("operating-rate", mCaptureFps); + } + uint32_t flags = 0; if (mIsMetaDataStoredInVideoBuffers) { flags |= MediaCodecSource::FLAG_USE_METADATA_INPUT; diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp index b7a88e7..7eaa0e0 100644 --- a/media/libmediaplayerservice/nuplayer/GenericSource.cpp +++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp @@ -1400,6 +1400,14 @@ sp<ABuffer> NuPlayer::GenericSource::mediaBufferToABuffer( meta->setInt32("trackIndex", mSubtitleTrack.mIndex); } + uint32_t dataType; // unused + const void *seiData; + size_t seiLength; + if (mb->meta_data()->findData(kKeySEI, &dataType, &seiData, &seiLength)) { + sp<ABuffer> sei = ABuffer::CreateAsCopy(seiData, seiLength);; + meta->setBuffer("sei", sei); + } + if (actualTimeUs) { *actualTimeUs = timeUs; } diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp index 65e80c3..acc9ef5 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp @@ -658,7 +658,7 @@ status_t NuPlayer::Decoder::fetchInputData(sp<AMessage> &reply) { #if 0 int64_t mediaTimeUs; CHECK(accessUnit->meta()->findInt64("timeUs", &mediaTimeUs)); - ALOGV("[%s] feeding input buffer at media time %" PRId64, + ALOGV("[%s] feeding input buffer at media time %.3f", mIsAudio ? "audio" : "video", mediaTimeUs / 1E6); #endif diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp index 93864e4..7065a6e 100644 --- a/media/libstagefright/MediaCodec.cpp +++ b/media/libstagefright/MediaCodec.cpp @@ -79,6 +79,21 @@ struct ResourceManagerClient : public BnResourceManagerClient { return (err == OK); } + virtual String8 getName() { + String8 ret; + sp<MediaCodec> codec = mMediaCodec.promote(); + if (codec == NULL) { + // codec is already gone. + return ret; + } + + AString name; + if (codec->getName(&name) == OK) { + ret.setTo(name.c_str()); + } + return ret; + } + protected: virtual ~ResourceManagerClient() {} @@ -139,12 +154,7 @@ sp<IBatteryStats> MediaCodec::BatteryNotifier::getBatteryService_l() { return NULL; } mDeathNotifier = new DeathNotifier(); - if (IInterface::asBinder(mBatteryStatService)-> - linkToDeath(mDeathNotifier) != OK) { - mBatteryStatService.clear(); - mDeathNotifier.clear(); - return NULL; - } + IInterface::asBinder(mBatteryStatService)->linkToDeath(mDeathNotifier); // notify start now if media already started if (mVideoRefCount > 0) { mBatteryStatService->noteStartVideo(AID_MEDIA); @@ -179,8 +189,9 @@ void MediaCodec::BatteryNotifier::noteStopVideo() { return; } - mVideoRefCount--; sp<IBatteryStats> batteryService = getBatteryService_l(); + + mVideoRefCount--; if (mVideoRefCount == 0 && batteryService != NULL) { batteryService->noteStopVideo(AID_MEDIA); } @@ -202,8 +213,9 @@ void MediaCodec::BatteryNotifier::noteStopAudio() { return; } - mAudioRefCount--; sp<IBatteryStats> batteryService = getBatteryService_l(); + + mAudioRefCount--; if (mAudioRefCount == 0 && batteryService != NULL) { batteryService->noteStopAudio(AID_MEDIA); } diff --git a/media/libstagefright/MediaSync.cpp b/media/libstagefright/MediaSync.cpp index ec956c4..8030a36 100644 --- a/media/libstagefright/MediaSync.cpp +++ b/media/libstagefright/MediaSync.cpp @@ -179,6 +179,42 @@ sp<const MediaClock> MediaSync::getMediaClock() { return mMediaClock; } +status_t MediaSync::getPlayTimeForPendingAudioFrames(int64_t *outTimeUs) { + Mutex::Autolock lock(mMutex); + // User should check the playback rate if it doesn't want to receive a + // huge number for play time. + if (mPlaybackRate == 0.0f) { + *outTimeUs = INT64_MAX; + return OK; + } + + uint32_t numFramesPlayed = 0; + if (mAudioTrack != NULL) { + status_t res = mAudioTrack->getPosition(&numFramesPlayed); + if (res != OK) { + return res; + } + } + + int64_t numPendingFrames = mNumFramesWritten - numFramesPlayed; + if (numPendingFrames < 0) { + numPendingFrames = 0; + ALOGW("getPlayTimeForPendingAudioFrames: pending frame count is negative."); + } + double timeUs = numPendingFrames * 1000000.0 + / (mNativeSampleRateInHz * (double)mPlaybackRate); + if (timeUs > (double)INT64_MAX) { + // Overflow. + *outTimeUs = INT64_MAX; + ALOGW("getPlayTimeForPendingAudioFrames: play time for pending audio frames " + "is too high, possibly due to super low playback rate(%f)", mPlaybackRate); + } else { + *outTimeUs = (int64_t)timeUs; + } + + return OK; +} + status_t MediaSync::updateQueuedAudioData( size_t sizeInBytes, int64_t presentationTimeUs) { if (sizeInBytes == 0) { @@ -195,13 +231,14 @@ status_t MediaSync::updateQueuedAudioData( int64_t numFrames = sizeInBytes / mAudioTrack->frameSize(); int64_t maxMediaTimeUs = presentationTimeUs + getDurationIfPlayedAtNativeSampleRate_l(numFrames); - mNumFramesWritten += numFrames; int64_t nowUs = ALooper::GetNowUs(); - int64_t nowMediaUs = maxMediaTimeUs + int64_t nowMediaUs = presentationTimeUs - getDurationIfPlayedAtNativeSampleRate_l(mNumFramesWritten) + getPlayedOutAudioDurationMedia_l(nowUs); + mNumFramesWritten += numFrames; + int64_t oldRealTime = -1; if (mNextBufferItemMediaUs != -1) { oldRealTime = getRealTime(mNextBufferItemMediaUs, nowUs); @@ -212,12 +249,13 @@ status_t MediaSync::updateQueuedAudioData( if (oldRealTime != -1) { int64_t newRealTime = getRealTime(mNextBufferItemMediaUs, nowUs); - if (newRealTime < oldRealTime) { - mNextBufferItemMediaUs = -1; - onDrainVideo_l(); + if (newRealTime >= oldRealTime) { + return OK; } } + mNextBufferItemMediaUs = -1; + onDrainVideo_l(); return OK; } @@ -316,12 +354,12 @@ void MediaSync::onDrainVideo_l() { return; } - int64_t nowUs = ALooper::GetNowUs(); - while (!mBufferItems.empty()) { + int64_t nowUs = ALooper::GetNowUs(); BufferItem *bufferItem = &*mBufferItems.begin(); int64_t itemMediaUs = bufferItem->mTimestamp / 1000; int64_t itemRealUs = getRealTime(itemMediaUs, nowUs); + if (itemRealUs <= nowUs) { if (mHasAudio) { if (nowUs - itemRealUs <= kMaxAllowedVideoLateTimeUs) { @@ -341,15 +379,13 @@ void MediaSync::onDrainVideo_l() { } mBufferItems.erase(mBufferItems.begin()); - - if (mBufferItems.empty()) { - mNextBufferItemMediaUs = -1; - } + mNextBufferItemMediaUs = -1; } else { if (mNextBufferItemMediaUs == -1 - || mNextBufferItemMediaUs != itemMediaUs) { + || mNextBufferItemMediaUs > itemMediaUs) { sp<AMessage> msg = new AMessage(kWhatDrainVideo, this); msg->post(itemRealUs - nowUs); + mNextBufferItemMediaUs = itemMediaUs; } break; } @@ -395,7 +431,9 @@ void MediaSync::onFrameAvailableFromInput() { } mBufferItems.push_back(bufferItem); - onDrainVideo_l(); + if (mBufferItems.size() == 1) { + onDrainVideo_l(); + } } void MediaSync::renderOneBufferItem_l( const BufferItem &bufferItem) { @@ -499,6 +537,20 @@ void MediaSync::onMessageReceived(const sp<AMessage> &msg) { case kWhatDrainVideo: { Mutex::Autolock lock(mMutex); + if (mNextBufferItemMediaUs != -1) { + int64_t nowUs = ALooper::GetNowUs(); + int64_t itemRealUs = getRealTime(mNextBufferItemMediaUs, nowUs); + + // The message could arrive earlier than expected due to + // various reasons, e.g., media clock has been changed because + // of new anchor time or playback rate. In such cases, the + // message needs to be re-posted. + if (itemRealUs > nowUs) { + msg->post(itemRealUs - nowUs); + break; + } + } + onDrainVideo_l(); break; } diff --git a/media/libstagefright/httplive/Android.mk b/media/libstagefright/httplive/Android.mk index 2639deb..fc85835 100644 --- a/media/libstagefright/httplive/Android.mk +++ b/media/libstagefright/httplive/Android.mk @@ -3,6 +3,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ + HTTPDownloader.cpp \ LiveDataSource.cpp \ LiveSession.cpp \ M3UParser.cpp \ diff --git a/media/libstagefright/httplive/HTTPDownloader.cpp b/media/libstagefright/httplive/HTTPDownloader.cpp new file mode 100644 index 0000000..3b44bae --- /dev/null +++ b/media/libstagefright/httplive/HTTPDownloader.cpp @@ -0,0 +1,273 @@ +/* + * Copyright 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 "HTTPDownloader" +#include <utils/Log.h> + +#include "HTTPDownloader.h" +#include "M3UParser.h" + +#include <media/IMediaHTTPConnection.h> +#include <media/IMediaHTTPService.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/MediaHTTP.h> +#include <media/stagefright/DataSource.h> +#include <media/stagefright/FileSource.h> +#include <openssl/aes.h> +#include <openssl/md5.h> +#include <utils/Mutex.h> + +namespace android { + +HTTPDownloader::HTTPDownloader( + const sp<IMediaHTTPService> &httpService, + const KeyedVector<String8, String8> &headers) : + mHTTPDataSource(new MediaHTTP(httpService->makeHTTPConnection())), + mExtraHeaders(headers), + mDisconnecting(false) { +} + +void HTTPDownloader::reconnect() { + AutoMutex _l(mLock); + mDisconnecting = false; +} + +void HTTPDownloader::disconnect() { + { + AutoMutex _l(mLock); + mDisconnecting = true; + } + mHTTPDataSource->disconnect(); +} + +bool HTTPDownloader::isDisconnecting() { + AutoMutex _l(mLock); + return mDisconnecting; +} + +/* + * Illustration of parameters: + * + * 0 `range_offset` + * +------------+-------------------------------------------------------+--+--+ + * | | | next block to fetch | | | + * | | `source` handle => `out` buffer | | | | + * | `url` file |<--------- buffer size --------->|<--- `block_size` -->| | | + * | |<----------- `range_length` / buffer capacity ----------->| | + * |<------------------------------ file_size ------------------------------->| + * + * Special parameter values: + * - range_length == -1 means entire file + * - block_size == 0 means entire range + * + */ +ssize_t HTTPDownloader::fetchBlock( + const char *url, sp<ABuffer> *out, + int64_t range_offset, int64_t range_length, + uint32_t block_size, /* download block size */ + String8 *actualUrl, + bool reconnect /* force connect HTTP when resuing source */) { + if (isDisconnecting()) { + return ERROR_NOT_CONNECTED; + } + + off64_t size; + + if (reconnect) { + if (!strncasecmp(url, "file://", 7)) { + mDataSource = new FileSource(url + 7); + } else if (strncasecmp(url, "http://", 7) + && strncasecmp(url, "https://", 8)) { + return ERROR_UNSUPPORTED; + } else { + KeyedVector<String8, String8> headers = mExtraHeaders; + if (range_offset > 0 || range_length >= 0) { + headers.add( + String8("Range"), + String8( + AStringPrintf( + "bytes=%lld-%s", + range_offset, + range_length < 0 + ? "" : AStringPrintf("%lld", + range_offset + range_length - 1).c_str()).c_str())); + } + + status_t err = mHTTPDataSource->connect(url, &headers); + + if (isDisconnecting()) { + return ERROR_NOT_CONNECTED; + } + + if (err != OK) { + return err; + } + + mDataSource = mHTTPDataSource; + } + } + + status_t getSizeErr = mDataSource->getSize(&size); + + if (isDisconnecting()) { + return ERROR_NOT_CONNECTED; + } + + if (getSizeErr != OK) { + size = 65536; + } + + sp<ABuffer> buffer = *out != NULL ? *out : new ABuffer(size); + if (*out == NULL) { + buffer->setRange(0, 0); + } + + ssize_t bytesRead = 0; + // adjust range_length if only reading partial block + if (block_size > 0 && (range_length == -1 || (int64_t)(buffer->size() + block_size) < range_length)) { + range_length = buffer->size() + block_size; + } + for (;;) { + // Only resize when we don't know the size. + size_t bufferRemaining = buffer->capacity() - buffer->size(); + if (bufferRemaining == 0 && getSizeErr != OK) { + size_t bufferIncrement = buffer->size() / 2; + if (bufferIncrement < 32768) { + bufferIncrement = 32768; + } + bufferRemaining = bufferIncrement; + + ALOGV("increasing download buffer to %zu bytes", + buffer->size() + bufferRemaining); + + sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining); + memcpy(copy->data(), buffer->data(), buffer->size()); + copy->setRange(0, buffer->size()); + + buffer = copy; + } + + size_t maxBytesToRead = bufferRemaining; + if (range_length >= 0) { + int64_t bytesLeftInRange = range_length - buffer->size(); + if (bytesLeftInRange < (int64_t)maxBytesToRead) { + maxBytesToRead = bytesLeftInRange; + + if (bytesLeftInRange == 0) { + break; + } + } + } + + // The DataSource is responsible for informing us of error (n < 0) or eof (n == 0) + // to help us break out of the loop. + ssize_t n = mDataSource->readAt( + buffer->size(), buffer->data() + buffer->size(), + maxBytesToRead); + + if (isDisconnecting()) { + return ERROR_NOT_CONNECTED; + } + + if (n < 0) { + return n; + } + + if (n == 0) { + break; + } + + buffer->setRange(0, buffer->size() + (size_t)n); + bytesRead += n; + } + + *out = buffer; + if (actualUrl != NULL) { + *actualUrl = mDataSource->getUri(); + if (actualUrl->isEmpty()) { + *actualUrl = url; + } + } + + return bytesRead; +} + +ssize_t HTTPDownloader::fetchFile( + const char *url, sp<ABuffer> *out, String8 *actualUrl) { + ssize_t err = fetchBlock(url, out, 0, -1, 0, actualUrl, true /* reconnect */); + + // close off the connection after use + mHTTPDataSource->disconnect(); + + return err; +} + +sp<M3UParser> HTTPDownloader::fetchPlaylist( + const char *url, uint8_t *curPlaylistHash, bool *unchanged) { + ALOGV("fetchPlaylist '%s'", url); + + *unchanged = false; + + sp<ABuffer> buffer; + String8 actualUrl; + ssize_t err = fetchFile(url, &buffer, &actualUrl); + + // close off the connection after use + mHTTPDataSource->disconnect(); + + if (err <= 0) { + return NULL; + } + + // MD5 functionality is not available on the simulator, treat all + // playlists as changed. + +#if defined(HAVE_ANDROID_OS) + uint8_t hash[16]; + + MD5_CTX m; + MD5_Init(&m); + MD5_Update(&m, buffer->data(), buffer->size()); + + MD5_Final(hash, &m); + + if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) { + // playlist unchanged + *unchanged = true; + + return NULL; + } + + if (curPlaylistHash != NULL) { + memcpy(curPlaylistHash, hash, sizeof(hash)); + } +#endif + + sp<M3UParser> playlist = + new M3UParser(actualUrl.string(), buffer->data(), buffer->size()); + + if (playlist->initCheck() != OK) { + ALOGE("failed to parse .m3u8 playlist"); + + return NULL; + } + + return playlist; +} + +} // namespace android diff --git a/media/libstagefright/httplive/HTTPDownloader.h b/media/libstagefright/httplive/HTTPDownloader.h new file mode 100644 index 0000000..1db4a48 --- /dev/null +++ b/media/libstagefright/httplive/HTTPDownloader.h @@ -0,0 +1,86 @@ +/* + * Copyright 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 HTTP_DOWNLOADER_H_ + +#define HTTP_DOWNLOADER_H_ + +#include <media/stagefright/foundation/ADebug.h> +#include <utils/KeyedVector.h> +#include <utils/Mutex.h> +#include <utils/RefBase.h> + +namespace android { + +struct ABuffer; +class DataSource; +struct HTTPBase; +struct IMediaHTTPService; +struct M3UParser; + +struct HTTPDownloader : public RefBase { + HTTPDownloader( + const sp<IMediaHTTPService> &httpService, + const KeyedVector<String8, String8> &headers); + + void reconnect(); + void disconnect(); + bool isDisconnecting(); + // If given a non-zero block_size (default 0), it is used to cap the number of + // bytes read in from the DataSource. If given a non-NULL buffer, new content + // is read into the end. + // + // The DataSource we read from is responsible for signaling error or EOF to help us + // break out of the read loop. The DataSource can be returned to the caller, so + // that the caller can reuse it for subsequent fetches (within the initially + // requested range). + // + // For reused HTTP sources, the caller must download a file sequentially without + // any overlaps or gaps to prevent reconnection. + ssize_t fetchBlock( + const char *url, + sp<ABuffer> *out, + int64_t range_offset, /* open file at range_offset */ + int64_t range_length, /* open file for range_length (-1: entire file) */ + uint32_t block_size, /* download block size (0: entire range) */ + String8 *actualUrl, /* returns actual URL */ + bool reconnect /* force connect http */ + ); + + // simplified version to fetch a single file + ssize_t fetchFile( + const char *url, + sp<ABuffer> *out, + String8 *actualUrl = NULL); + + // fetch a playlist file + sp<M3UParser> fetchPlaylist( + const char *url, uint8_t *curPlaylistHash, bool *unchanged); + +private: + sp<HTTPBase> mHTTPDataSource; + sp<DataSource> mDataSource; + KeyedVector<String8, String8> mExtraHeaders; + + Mutex mLock; + bool mDisconnecting; + + DISALLOW_EVIL_CONSTRUCTORS(HTTPDownloader); +}; + +} // namespace android + +#endif // HTTP_DOWNLOADER_H_ diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp index 9ac764c..d8c38e7 100644 --- a/media/libstagefright/httplive/LiveSession.cpp +++ b/media/libstagefright/httplive/LiveSession.cpp @@ -19,25 +19,18 @@ #include <utils/Log.h> #include "LiveSession.h" - +#include "HTTPDownloader.h" #include "M3UParser.h" #include "PlaylistFetcher.h" -#include "include/HTTPBase.h" #include "mpeg2ts/AnotherPacketSource.h" #include <cutils/properties.h> -#include <media/IMediaHTTPConnection.h> #include <media/IMediaHTTPService.h> -#include <media/stagefright/foundation/hexdump.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AMessage.h> #include <media/stagefright/foundation/AUtils.h> -#include <media/stagefright/DataSource.h> -#include <media/stagefright/FileSource.h> -#include <media/stagefright/MediaErrors.h> -#include <media/stagefright/MediaHTTP.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MetaData.h> #include <media/stagefright/Utils.h> @@ -46,8 +39,6 @@ #include <ctype.h> #include <inttypes.h> -#include <openssl/aes.h> -#include <openssl/md5.h> namespace android { @@ -257,7 +248,6 @@ LiveSession::LiveSession( mInPreparationPhase(true), mPollBufferingGeneration(0), mPrevBufferPercentage(-1), - mHTTPDataSource(new MediaHTTP(mHTTPService->makeHTTPConnection())), mCurBandwidthIndex(-1), mOrigBandwidthIndex(-1), mLastBandwidthBps(-1ll), @@ -317,7 +307,7 @@ status_t LiveSession::dequeueAccessUnit( ssize_t streamIdx = typeToIndex(stream); if (streamIdx < 0) { - return INVALID_VALUE; + return BAD_VALUE; } const char *streamStr = getNameForStream(stream); // Do not let client pull data if we don't have data packets yet. @@ -465,8 +455,8 @@ status_t LiveSession::getStreamFormat(StreamType stream, sp<AMessage> *format) { return convertMetaDataToMessage(meta, format); } -sp<HTTPBase> LiveSession::getHTTPDataSource() { - return new MediaHTTP(mHTTPService->makeHTTPConnection()); +sp<HTTPDownloader> LiveSession::getHTTPDownloader() { + return new HTTPDownloader(mHTTPService, mExtraHeaders); } void LiveSession::connectAsync( @@ -838,6 +828,12 @@ void LiveSession::onMessageReceived(const sp<AMessage> &msg) { break; } + case PlaylistFetcher::kWhatPlaylistFetched: + { + onMasterPlaylistFetched(msg); + break; + } + case PlaylistFetcher::kWhatMetadataDetected: { if (!mHasMetadata) { @@ -874,12 +870,6 @@ void LiveSession::onMessageReceived(const sp<AMessage> &msg) { break; } - case kWhatFinishDisconnect2: - { - onFinishDisconnect2(); - break; - } - case kWhatPollBuffering: { int32_t generation; @@ -931,8 +921,10 @@ ssize_t LiveSession::typeToIndex(int32_t type) { } void LiveSession::onConnect(const sp<AMessage> &msg) { - AString url; - CHECK(msg->findString("url", &url)); + CHECK(msg->findString("url", &mMasterURL)); + + // TODO currently we don't know if we are coming here from incognito mode + ALOGI("onConnect %s", uriDebugString(mMasterURL).c_str()); KeyedVector<String8, String8> *headers = NULL; if (!msg->findPointer("headers", (void **)&headers)) { @@ -944,21 +936,6 @@ void LiveSession::onConnect(const sp<AMessage> &msg) { headers = NULL; } - // TODO currently we don't know if we are coming here from incognito mode - ALOGI("onConnect %s", uriDebugString(url).c_str()); - - mMasterURL = url; - - bool dummy; - mPlaylist = fetchPlaylist(url.c_str(), NULL /* curPlaylistHash */, &dummy); - - if (mPlaylist == NULL) { - ALOGE("unable to fetch master playlist %s.", uriDebugString(url).c_str()); - - postPrepared(ERROR_IO); - return; - } - // create looper for fetchers if (mFetcherLooper == NULL) { mFetcherLooper = new ALooper(); @@ -967,6 +944,31 @@ void LiveSession::onConnect(const sp<AMessage> &msg) { mFetcherLooper->start(false, false); } + // create fetcher to fetch the master playlist + addFetcher(mMasterURL.c_str())->fetchPlaylistAsync(); +} + +void LiveSession::onMasterPlaylistFetched(const sp<AMessage> &msg) { + AString uri; + CHECK(msg->findString("uri", &uri)); + ssize_t index = mFetcherInfos.indexOfKey(uri); + if (index < 0) { + ALOGW("fetcher for master playlist is gone."); + return; + } + + // no longer useful, remove + mFetcherLooper->unregisterHandler(mFetcherInfos[index].mFetcher->id()); + mFetcherInfos.removeItemsAt(index); + + CHECK(msg->findObject("playlist", (sp<RefBase> *)&mPlaylist)); + if (mPlaylist == NULL) { + ALOGE("unable to fetch master playlist %s.", + uriDebugString(mMasterURL).c_str()); + + postPrepared(ERROR_IO); + return; + } // We trust the content provider to make a reasonable choice of preferred // initial bandwidth by listing it first in the variant playlist. // At startup we really don't have a good estimate on the available @@ -1050,22 +1052,26 @@ void LiveSession::finishDisconnect() { // cancel buffer polling cancelPollBuffering(); + // TRICKY: don't wait for all fetcher to be stopped when disconnecting + // + // Some fetchers might be stuck in connect/getSize at this point. These + // operations will eventually timeout (as we have a timeout set in + // MediaHTTPConnection), but we don't want to block the main UI thread + // until then. Here we just need to make sure we clear all references + // to the fetchers, so that when they finally exit from the blocking + // operation, they can be destructed. + // + // There is one very tricky point though. For this scheme to work, the + // fecther must hold a reference to LiveSession, so that LiveSession is + // destroyed after fetcher. Otherwise LiveSession would get stuck in its + // own destructor when it waits for mFetcherLooper to stop, which still + // blocks main UI thread. for (size_t i = 0; i < mFetcherInfos.size(); ++i) { mFetcherInfos.valueAt(i).mFetcher->stopAsync(); + mFetcherLooper->unregisterHandler( + mFetcherInfos.valueAt(i).mFetcher->id()); } - - sp<AMessage> msg = new AMessage(kWhatFinishDisconnect2, this); - - mContinuationCounter = mFetcherInfos.size(); - mContinuation = msg; - - if (mContinuationCounter == 0) { - msg->post(); - } -} - -void LiveSession::onFinishDisconnect2() { - mContinuation.clear(); + mFetcherInfos.clear(); mPacketSources.valueFor(STREAMTYPE_AUDIO)->signalEOS(ERROR_END_OF_STREAM); mPacketSources.valueFor(STREAMTYPE_VIDEO)->signalEOS(ERROR_END_OF_STREAM); @@ -1104,198 +1110,6 @@ sp<PlaylistFetcher> LiveSession::addFetcher(const char *uri) { return info.mFetcher; } -/* - * Illustration of parameters: - * - * 0 `range_offset` - * +------------+-------------------------------------------------------+--+--+ - * | | | next block to fetch | | | - * | | `source` handle => `out` buffer | | | | - * | `url` file |<--------- buffer size --------->|<--- `block_size` -->| | | - * | |<----------- `range_length` / buffer capacity ----------->| | - * |<------------------------------ file_size ------------------------------->| - * - * Special parameter values: - * - range_length == -1 means entire file - * - block_size == 0 means entire range - * - */ -ssize_t LiveSession::fetchFile( - const char *url, sp<ABuffer> *out, - 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, - bool forceConnectHTTP /* force connect HTTP when resuing source */) { - off64_t size; - sp<DataSource> temp_source; - if (source == NULL) { - source = &temp_source; - } - - if (*source == NULL || forceConnectHTTP) { - if (!strncasecmp(url, "file://", 7)) { - *source = new FileSource(url + 7); - } else if (strncasecmp(url, "http://", 7) - && strncasecmp(url, "https://", 8)) { - return ERROR_UNSUPPORTED; - } else { - KeyedVector<String8, String8> headers = mExtraHeaders; - if (range_offset > 0 || range_length >= 0) { - headers.add( - String8("Range"), - String8( - AStringPrintf( - "bytes=%lld-%s", - range_offset, - range_length < 0 - ? "" : AStringPrintf("%lld", - range_offset + range_length - 1).c_str()).c_str())); - } - - HTTPBase* httpDataSource = - (*source == NULL) ? mHTTPDataSource.get() : (HTTPBase*)source->get(); - status_t err = httpDataSource->connect(url, &headers); - - if (err != OK) { - return err; - } - - if (*source == NULL) { - *source = mHTTPDataSource; - } - } - } - - status_t getSizeErr = (*source)->getSize(&size); - if (getSizeErr != OK) { - size = 65536; - } - - sp<ABuffer> buffer = *out != NULL ? *out : new ABuffer(size); - if (*out == NULL) { - buffer->setRange(0, 0); - } - - ssize_t bytesRead = 0; - // adjust range_length if only reading partial block - if (block_size > 0 && (range_length == -1 || (int64_t)(buffer->size() + block_size) < range_length)) { - range_length = buffer->size() + block_size; - } - for (;;) { - // Only resize when we don't know the size. - size_t bufferRemaining = buffer->capacity() - buffer->size(); - if (bufferRemaining == 0 && getSizeErr != OK) { - size_t bufferIncrement = buffer->size() / 2; - if (bufferIncrement < 32768) { - bufferIncrement = 32768; - } - bufferRemaining = bufferIncrement; - - ALOGV("increasing download buffer to %zu bytes", - buffer->size() + bufferRemaining); - - sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining); - memcpy(copy->data(), buffer->data(), buffer->size()); - copy->setRange(0, buffer->size()); - - buffer = copy; - } - - size_t maxBytesToRead = bufferRemaining; - if (range_length >= 0) { - int64_t bytesLeftInRange = range_length - buffer->size(); - if (bytesLeftInRange < (int64_t)maxBytesToRead) { - maxBytesToRead = bytesLeftInRange; - - if (bytesLeftInRange == 0) { - break; - } - } - } - - // The DataSource is responsible for informing us of error (n < 0) or eof (n == 0) - // to help us break out of the loop. - ssize_t n = (*source)->readAt( - buffer->size(), buffer->data() + buffer->size(), - maxBytesToRead); - - if (n < 0) { - return n; - } - - if (n == 0) { - break; - } - - buffer->setRange(0, buffer->size() + (size_t)n); - bytesRead += n; - } - - *out = buffer; - if (actualUrl != NULL) { - *actualUrl = (*source)->getUri(); - if (actualUrl->isEmpty()) { - *actualUrl = url; - } - } - - return bytesRead; -} - -sp<M3UParser> LiveSession::fetchPlaylist( - const char *url, uint8_t *curPlaylistHash, bool *unchanged) { - ALOGV("fetchPlaylist '%s'", url); - - *unchanged = false; - - sp<ABuffer> buffer; - String8 actualUrl; - ssize_t err = fetchFile(url, &buffer, 0, -1, 0, NULL, &actualUrl); - - // close off the connection after use - mHTTPDataSource->disconnect(); - - if (err <= 0) { - return NULL; - } - - // MD5 functionality is not available on the simulator, treat all - // playlists as changed. - -#if defined(HAVE_ANDROID_OS) - uint8_t hash[16]; - - MD5_CTX m; - MD5_Init(&m); - MD5_Update(&m, buffer->data(), buffer->size()); - - MD5_Final(hash, &m); - - if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) { - // playlist unchanged - *unchanged = true; - - return NULL; - } - - if (curPlaylistHash != NULL) { - memcpy(curPlaylistHash, hash, sizeof(hash)); - } -#endif - - sp<M3UParser> playlist = - new M3UParser(actualUrl.string(), buffer->data(), buffer->size()); - - if (playlist->initCheck() != OK) { - ALOGE("failed to parse .m3u8 playlist"); - - return NULL; - } - - return playlist; -} - #if 0 static double uniformRand() { return (double)rand() / RAND_MAX; @@ -1690,9 +1504,11 @@ void LiveSession::changeConfiguration( fetcher->stopAsync(); } else { float threshold = -1.0f; // always finish fetching by default + bool disconnect = false; if (timeUs >= 0ll) { // seeking, no need to finish fetching threshold = 0.0f; + disconnect = true; } else if (delayRemoval) { // adapting, abort if remaining of current segment is over threshold threshold = getAbortThreshold( @@ -1701,7 +1517,7 @@ void LiveSession::changeConfiguration( ALOGV("pausing fetcher-%d, threshold=%.2f", fetcher->getFetcherID(), threshold); - fetcher->pauseAsync(threshold); + fetcher->pauseAsync(threshold, disconnect); } } diff --git a/media/libstagefright/httplive/LiveSession.h b/media/libstagefright/httplive/LiveSession.h index 4e7ccac..21be413 100644 --- a/media/libstagefright/httplive/LiveSession.h +++ b/media/libstagefright/httplive/LiveSession.h @@ -37,6 +37,7 @@ struct LiveDataSource; struct M3UParser; struct PlaylistFetcher; struct HLSTime; +struct HTTPDownloader; struct LiveSession : public AHandler { enum Flags { @@ -76,7 +77,7 @@ struct LiveSession : public AHandler { status_t getStreamFormat(StreamType stream, sp<AMessage> *format); - sp<HTTPBase> getHTTPDataSource(); + sp<HTTPDownloader> getHTTPDownloader(); void connectAsync( const char *url, @@ -127,7 +128,6 @@ private: kWhatChangeConfiguration = 'chC0', kWhatChangeConfiguration2 = 'chC2', kWhatChangeConfiguration3 = 'chC3', - kWhatFinishDisconnect2 = 'fin2', kWhatPollBuffering = 'poll', }; @@ -191,7 +191,6 @@ private: int32_t mPollBufferingGeneration; int32_t mPrevBufferPercentage; - sp<HTTPBase> mHTTPDataSource; KeyedVector<String8, String8> mExtraHeaders; AString mMasterURL; @@ -253,34 +252,8 @@ private: sp<PlaylistFetcher> addFetcher(const char *uri); void onConnect(const sp<AMessage> &msg); + void onMasterPlaylistFetched(const sp<AMessage> &msg); void onSeek(const sp<AMessage> &msg); - void onFinishDisconnect2(); - - // If given a non-zero block_size (default 0), it is used to cap the number of - // bytes read in from the DataSource. If given a non-NULL buffer, new content - // is read into the end. - // - // The DataSource we read from is responsible for signaling error or EOF to help us - // break out of the read loop. The DataSource can be returned to the caller, so - // that the caller can reuse it for subsequent fetches (within the initially - // requested range). - // - // For reused HTTP sources, the caller must download a file sequentially without - // any overlaps or gaps to prevent reconnection. - ssize_t fetchFile( - const char *url, sp<ABuffer> *out, - /* request/open a file starting at range_offset for range_length bytes */ - int64_t range_offset = 0, int64_t range_length = -1, - /* download block size */ - uint32_t block_size = 0, - /* reuse DataSource if doing partial fetch */ - sp<DataSource> *source = 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); bool UriIsSameAsIndex( const AString &uri, int32_t index, bool newUri); sp<AnotherPacketSource> getPacketSourceForStreamIndex(size_t trackIndex, bool newUri); diff --git a/media/libstagefright/httplive/PlaylistFetcher.cpp b/media/libstagefright/httplive/PlaylistFetcher.cpp index 8350c1b..53087b6 100644 --- a/media/libstagefright/httplive/PlaylistFetcher.cpp +++ b/media/libstagefright/httplive/PlaylistFetcher.cpp @@ -20,23 +20,16 @@ #include <utils/misc.h> #include "PlaylistFetcher.h" - -#include "LiveDataSource.h" +#include "HTTPDownloader.h" #include "LiveSession.h" #include "M3UParser.h" - #include "include/avc_utils.h" -#include "include/HTTPBase.h" #include "include/ID3.h" #include "mpeg2ts/AnotherPacketSource.h" -#include <media/IStreamSource.h> #include <media/stagefright/foundation/ABitReader.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> -#include <media/stagefright/foundation/AUtils.h> -#include <media/stagefright/foundation/hexdump.h> -#include <media/stagefright/FileSource.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MetaData.h> #include <media/stagefright/Utils.h> @@ -44,7 +37,6 @@ #include <ctype.h> #include <inttypes.h> #include <openssl/aes.h> -#include <openssl/md5.h> #define FLOGV(fmt, ...) ALOGV("[fetcher-%d] " fmt, mFetcherID, ##__VA_ARGS__) #define FSLOGV(stream, fmt, ...) ALOGV("[fetcher-%d] [%s] " fmt, mFetcherID, \ @@ -179,7 +171,7 @@ PlaylistFetcher::PlaylistFetcher( mDownloadState(new DownloadState()), mHasMetadata(false) { memset(mPlaylistHash, 0, sizeof(mPlaylistHash)); - mHTTPDataSource = mSession->getHTTPDataSource(); + mHTTPDownloader = mSession->getHTTPDownloader(); } PlaylistFetcher::~PlaylistFetcher() { @@ -338,9 +330,11 @@ status_t PlaylistFetcher::decryptBuffer( if (index >= 0) { key = mAESKeyForURI.valueAt(index); } else { - ssize_t err = mSession->fetchFile(keyURI.c_str(), &key); + ssize_t err = mHTTPDownloader->fetchFile(keyURI.c_str(), &key); - if (err < 0) { + if (err == ERROR_NOT_CONNECTED) { + return ERROR_NOT_CONNECTED; + } else if (err < 0) { ALOGE("failed to fetch cipher key from '%s'.", keyURI.c_str()); return ERROR_IO; } else if (key->size() != 16) { @@ -448,12 +442,32 @@ void PlaylistFetcher::cancelMonitorQueue() { ++mMonitorQueueGeneration; } -void PlaylistFetcher::setStoppingThreshold(float thresholdRatio) { - AutoMutex _l(mThresholdLock); - if (mStreamTypeMask == LiveSession::STREAMTYPE_SUBTITLES) { - return; +void PlaylistFetcher::setStoppingThreshold(float thresholdRatio, bool disconnect) { + { + AutoMutex _l(mThresholdLock); + mThresholdRatio = thresholdRatio; + } + if (disconnect) { + mHTTPDownloader->disconnect(); + } +} + +void PlaylistFetcher::resetStoppingThreshold(bool disconnect) { + { + AutoMutex _l(mThresholdLock); + mThresholdRatio = -1.0f; + } + if (disconnect) { + mHTTPDownloader->disconnect(); + } else { + // allow reconnect + mHTTPDownloader->reconnect(); } - mThresholdRatio = thresholdRatio; +} + +float PlaylistFetcher::getStoppingThreshold() { + AutoMutex _l(mThresholdLock); + return mThresholdRatio; } void PlaylistFetcher::startAsync( @@ -497,15 +511,15 @@ void PlaylistFetcher::startAsync( msg->post(); } -void PlaylistFetcher::pauseAsync(float thresholdRatio) { - if (thresholdRatio >= 0.0f) { - setStoppingThreshold(thresholdRatio); - } +void PlaylistFetcher::pauseAsync( + float thresholdRatio, bool disconnect) { + setStoppingThreshold(thresholdRatio, disconnect); + (new AMessage(kWhatPause, this))->post(); } void PlaylistFetcher::stopAsync(bool clear) { - setStoppingThreshold(0.0f); + setStoppingThreshold(0.0f, true /* disconncect */); sp<AMessage> msg = new AMessage(kWhatStop, this); msg->setInt32("clear", clear); @@ -520,6 +534,10 @@ void PlaylistFetcher::resumeUntilAsync(const sp<AMessage> ¶ms) { msg->post(); } +void PlaylistFetcher::fetchPlaylistAsync() { + (new AMessage(kWhatFetchPlaylist, this))->post(); +} + void PlaylistFetcher::onMessageReceived(const sp<AMessage> &msg) { switch (msg->what()) { case kWhatStart: @@ -557,6 +575,19 @@ void PlaylistFetcher::onMessageReceived(const sp<AMessage> &msg) { break; } + case kWhatFetchPlaylist: + { + bool unchanged; + sp<M3UParser> playlist = mHTTPDownloader->fetchPlaylist( + mURI.c_str(), NULL /* curPlaylistHash */, &unchanged); + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatPlaylistFetched); + notify->setObject("playlist", playlist); + notify->post(); + break; + } + case kWhatMonitorQueue: case kWhatDownloadNext: { @@ -676,7 +707,7 @@ void PlaylistFetcher::onPause() { cancelMonitorQueue(); mLastDiscontinuitySeq = mDiscontinuitySeq; - setStoppingThreshold(-1.0f); + resetStoppingThreshold(false /* disconnect */); } void PlaylistFetcher::onStop(const sp<AMessage> &msg) { @@ -691,14 +722,11 @@ void PlaylistFetcher::onStop(const sp<AMessage> &msg) { } } - // close off the connection after use - mHTTPDataSource->disconnect(); - mDownloadState->resetState(); mPacketSources.clear(); mStreamTypeMask = 0; - setStoppingThreshold(-1.0f); + resetStoppingThreshold(true /* disconnect */); } // Resume until we have reached the boundary timestamps listed in `msg`; when @@ -815,7 +843,7 @@ void PlaylistFetcher::onMonitorQueue() { status_t PlaylistFetcher::refreshPlaylist() { if (delayUsToRefreshPlaylist() <= 0) { bool unchanged; - sp<M3UParser> playlist = mSession->fetchPlaylist( + sp<M3UParser> playlist = mHTTPDownloader->fetchPlaylist( mURI.c_str(), mPlaylistHash, &unchanged); if (playlist == NULL) { @@ -864,18 +892,12 @@ bool PlaylistFetcher::shouldPauseDownload() { } // Calculate threshold to abort current download - int64_t targetDurationUs = mPlaylist->getTargetDuration(); - int64_t thresholdUs = -1; - { - AutoMutex _l(mThresholdLock); - thresholdUs = (mThresholdRatio < 0.0f) ? - -1ll : mThresholdRatio * targetDurationUs; - } + float thresholdRatio = getStoppingThreshold(); - if (thresholdUs < 0) { + if (thresholdRatio < 0.0f) { // never abort return false; - } else if (thresholdUs == 0) { + } else if (thresholdRatio == 0.0f) { // immediately abort return true; } @@ -905,6 +927,9 @@ bool PlaylistFetcher::shouldPauseDownload() { } lastEnqueueUs -= mSegmentFirstPTS; + int64_t targetDurationUs = mPlaylist->getTargetDuration(); + int64_t thresholdUs = thresholdRatio * targetDurationUs; + FLOGV("%spausing now, thresholdUs %lld, remaining %lld", targetDurationUs - lastEnqueueUs > thresholdUs ? "" : "not ", (long long)thresholdUs, @@ -1101,7 +1126,9 @@ bool PlaylistFetcher::initDownloadState( junk->setRange(0, 16); status_t err = decryptBuffer(mSeqNumber - firstSeqNumberInPlaylist, junk, true /* first */); - if (err != OK) { + if (err == ERROR_NOT_CONNECTED) { + return false; + } else if (err != OK) { notifyError(err); return false; } @@ -1202,12 +1229,21 @@ void PlaylistFetcher::onDownloadNext() { bool shouldPause = false; ssize_t bytesRead; do { - sp<DataSource> source = mHTTPDataSource; - int64_t startUs = ALooper::GetNowUs(); - bytesRead = mSession->fetchFile( + bytesRead = mHTTPDownloader->fetchBlock( uri.c_str(), &buffer, range_offset, range_length, kDownloadBlockSize, - &source, NULL, connectHTTP); + NULL /* actualURL */, connectHTTP); + int64_t delayUs = ALooper::GetNowUs() - startUs; + + if (bytesRead == ERROR_NOT_CONNECTED) { + return; + } + if (bytesRead < 0) { + status_t err = bytesRead; + ALOGE("failed to fetch .ts segment at url '%s'", uri.c_str()); + notifyError(err); + return; + } // add sample for bandwidth estimation, excluding samples from subtitles (as // its too small), or during startup/resumeUntil (when we could have more than @@ -1216,9 +1252,7 @@ void PlaylistFetcher::onDownloadNext() { && (mStreamTypeMask & (LiveSession::STREAMTYPE_AUDIO | LiveSession::STREAMTYPE_VIDEO))) { - int64_t delayUs = ALooper::GetNowUs() - startUs; mSession->addBandwidthMeasurement(bytesRead, delayUs); - if (delayUs > 2000000ll) { FLOGV("bytesRead %zd took %.2f seconds - abnormal bandwidth dip", bytesRead, (double)delayUs / 1.0e6); @@ -1227,13 +1261,6 @@ void PlaylistFetcher::onDownloadNext() { connectHTTP = false; - if (bytesRead < 0) { - status_t err = bytesRead; - ALOGE("failed to fetch .ts segment at url '%s'", uri.c_str()); - notifyError(err); - return; - } - CHECK(buffer != NULL); size_t size = buffer->size(); diff --git a/media/libstagefright/httplive/PlaylistFetcher.h b/media/libstagefright/httplive/PlaylistFetcher.h index 1f5e9b0..c8ca457 100644 --- a/media/libstagefright/httplive/PlaylistFetcher.h +++ b/media/libstagefright/httplive/PlaylistFetcher.h @@ -49,6 +49,7 @@ struct PlaylistFetcher : public AHandler { kWhatPreparationFailed, kWhatStartedAt, kWhatStopReached, + kWhatPlaylistFetched, kWhatMetadataDetected, }; @@ -61,8 +62,6 @@ struct PlaylistFetcher : public AHandler { int32_t getFetcherID() const; - sp<DataSource> getDataSource(); - void startAsync( const sp<AnotherPacketSource> &audioSource, const sp<AnotherPacketSource> &videoSource, @@ -74,12 +73,14 @@ struct PlaylistFetcher : public AHandler { int32_t startDiscontinuitySeq = -1, LiveSession::SeekMode seekMode = LiveSession::kSeekModeExactPosition); - void pauseAsync(float thresholdRatio); + void pauseAsync(float thresholdRatio, bool disconnect); void stopAsync(bool clear = true); void resumeUntilAsync(const sp<AMessage> ¶ms); + void fetchPlaylistAsync(); + uint32_t getStreamTypeMask() const { return mStreamTypeMask; } @@ -100,6 +101,7 @@ private: kWhatMonitorQueue = 'moni', kWhatResumeUntil = 'rsme', kWhatDownloadNext = 'dlnx', + kWhatFetchPlaylist = 'flst' }; struct DownloadState; @@ -114,7 +116,7 @@ private: sp<AMessage> mNotify; sp<AMessage> mStartTimeUsNotify; - sp<HTTPBase> mHTTPDataSource; + sp<HTTPDownloader> mHTTPDownloader; sp<LiveSession> mSession; AString mURI; @@ -197,7 +199,9 @@ private: void postMonitorQueue(int64_t delayUs = 0, int64_t minDelayUs = 0); void cancelMonitorQueue(); - void setStoppingThreshold(float thresholdRatio); + void setStoppingThreshold(float thresholdRatio, bool disconnect); + void resetStoppingThreshold(bool disconnect); + float getStoppingThreshold(); bool shouldPauseDownload(); int64_t delayUsToRefreshPlaylist() const; diff --git a/media/libstagefright/matroska/MatroskaExtractor.cpp b/media/libstagefright/matroska/MatroskaExtractor.cpp index 0712bf0..ddca437 100644 --- a/media/libstagefright/matroska/MatroskaExtractor.cpp +++ b/media/libstagefright/matroska/MatroskaExtractor.cpp @@ -941,7 +941,10 @@ void MatroskaExtractor::addTracks() { const mkvparser::VideoTrack *vtrack = static_cast<const mkvparser::VideoTrack *>(track); - if (!strcmp("V_MPEG4/ISO/AVC", codecID)) { + if (codecID == NULL) { + ALOGW("unknown codecID is not supported."); + continue; + } else if (!strcmp("V_MPEG4/ISO/AVC", codecID)) { meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC); meta->setData(kKeyAVCC, 0, codecPrivate, codecPrivateSize); } else if (!strcmp("V_MPEG4/ISO/ASP", codecID)) { diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp index f74b859..0878a1b 100644 --- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp +++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp @@ -211,6 +211,11 @@ status_t AnotherPacketSource::read( mediaBuffer->meta_data()->setInt32(kKeyIsSyncFrame, isSync); } + sp<ABuffer> sei; + if (buffer->meta()->findBuffer("sei", &sei) && sei != NULL) { + mediaBuffer->meta_data()->setData(kKeySEI, 0, sei->data(), sei->size()); + } + *out = mediaBuffer; return OK; } diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h index c7d9161..e1ddcbc 100644 --- a/services/audioflinger/AudioFlinger.h +++ b/services/audioflinger/AudioFlinger.h @@ -73,18 +73,18 @@ class AudioMixer; class AudioBuffer; class AudioResampler; class FastMixer; +class PassthruBufferProvider; class ServerProxy; // ---------------------------------------------------------------------------- -// AudioFlinger has a hard-coded upper limit of 2 channels for capture and playback. -// There is support for > 2 channel tracks down-mixed to 2 channel output via a down-mix effect. -// Adding full support for > 2 channel capture or playback would require more than simply changing -// this #define. There is an independent hard-coded upper limit in AudioMixer; -// removing that AudioMixer limit would be necessary but insufficient to support > 2 channels. -// The macro FCC_2 highlights some (but not all) places where there is are 2-channel assumptions. +// The macro FCC_2 highlights some (but not all) places where there are are 2-channel assumptions. +// This is typically due to legacy implementation of stereo input or output. // Search also for "2", "left", "right", "[0]", "[1]", ">> 16", "<< 16", etc. #define FCC_2 2 // FCC_2 = Fixed Channel Count 2 +// The macro FCC_8 highlights places where there are 8-channel assumptions. +// This is typically due to audio mixer and resampler limitations. +#define FCC_8 8 // FCC_8 = Fixed Channel Count 8 static const nsecs_t kDefaultStandbyTimeInNsecs = seconds(3); diff --git a/services/audioflinger/AudioMixer.cpp b/services/audioflinger/AudioMixer.cpp index 18ce1d0..7040af4 100644 --- a/services/audioflinger/AudioMixer.cpp +++ b/services/audioflinger/AudioMixer.cpp @@ -317,7 +317,7 @@ status_t AudioMixer::track_t::prepareForDownmix() // discard the previous downmixer if there was one unprepareForDownmix(); - // Only remix (upmix or downmix) if the track and mixer/device channel masks + // MONO_HACK Only remix (upmix or downmix) if the track and mixer/device channel masks // are not the same and not handled internally, as mono -> stereo currently is. if (channelMask == mMixerChannelMask || (channelMask == AUDIO_CHANNEL_OUT_MONO @@ -920,7 +920,8 @@ void AudioMixer::process__validate(state_t* state, int64_t pts) } else { if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_1){ t.hook = getTrackHook( - t.mMixerChannelCount == 2 // TODO: MONO_HACK. + (t.mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO // TODO: MONO_HACK + && t.channelMask == AUDIO_CHANNEL_OUT_MONO) ? TRACKTYPE_NORESAMPLEMONO : TRACKTYPE_NORESAMPLE, t.mMixerChannelCount, t.mMixerInFormat, t.mMixerFormat); diff --git a/services/audioflinger/FastCapture.cpp b/services/audioflinger/FastCapture.cpp index 9e7e8a4..79ac12b 100644 --- a/services/audioflinger/FastCapture.cpp +++ b/services/audioflinger/FastCapture.cpp @@ -105,7 +105,7 @@ void FastCapture::onStateChange() mFormat = mInputSource->format(); mSampleRate = Format_sampleRate(mFormat); unsigned channelCount = Format_channelCount(mFormat); - ALOG_ASSERT(channelCount == 1 || channelCount == 2); + ALOG_ASSERT(channelCount >= 1 && channelCount <= FCC_8); } dumpState->mSampleRate = mSampleRate; eitherChanged = true; diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp index 4eaeda3..4039564 100644 --- a/services/audioflinger/Threads.cpp +++ b/services/audioflinger/Threads.cpp @@ -56,6 +56,7 @@ #include "AudioFlinger.h" #include "AudioMixer.h" +#include "BufferProviders.h" #include "FastMixer.h" #include "FastCapture.h" #include "ServiceUtilities.h" @@ -94,6 +95,10 @@ static inline T min(const T& a, const T& b) return a < b ? a : b; } +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) +#endif + namespace android { // retry counts for buffer fill timeout @@ -5324,7 +5329,7 @@ AudioFlinger::RecordThread::~RecordThread() } mAudioFlinger->unregisterWriter(mFastCaptureNBLogWriter); mAudioFlinger->unregisterWriter(mNBLogWriter); - delete[] mRsmpInBuffer; + free(mRsmpInBuffer); } void AudioFlinger::RecordThread::onFirstRef() @@ -5557,7 +5562,7 @@ reacquire_wakelock: // If an NBAIO source is present, use it to read the normal capture's data if (mPipeSource != 0) { size_t framesToRead = mBufferSize / mFrameSize; - framesRead = mPipeSource->read(&mRsmpInBuffer[rear * mChannelCount], + framesRead = mPipeSource->read((uint8_t*)mRsmpInBuffer + rear * mFrameSize, framesToRead, AudioBufferProvider::kInvalidPTS); if (framesRead == 0) { // since pipe is non-blocking, simulate blocking input @@ -5566,7 +5571,7 @@ reacquire_wakelock: // otherwise use the HAL / AudioStreamIn directly } else { ssize_t bytesRead = mInput->stream->read(mInput->stream, - &mRsmpInBuffer[rear * mChannelCount], mBufferSize); + (uint8_t*)mRsmpInBuffer + rear * mFrameSize, mBufferSize); if (bytesRead < 0) { framesRead = bytesRead; } else { @@ -5586,13 +5591,13 @@ reacquire_wakelock: ALOG_ASSERT(framesRead > 0); if (mTeeSink != 0) { - (void) mTeeSink->write(&mRsmpInBuffer[rear * mChannelCount], framesRead); + (void) mTeeSink->write((uint8_t*)mRsmpInBuffer + rear * mFrameSize, framesRead); } // If destination is non-contiguous, we now correct for reading past end of buffer. { size_t part1 = mRsmpInFramesP2 - rear; if ((size_t) framesRead > part1) { - memcpy(mRsmpInBuffer, &mRsmpInBuffer[mRsmpInFramesP2 * mChannelCount], + memcpy(mRsmpInBuffer, (uint8_t*)mRsmpInBuffer + mRsmpInFramesP2 * mFrameSize, (framesRead - part1) * mFrameSize); } } @@ -6210,7 +6215,7 @@ status_t AudioFlinger::RecordThread::ResamplerBufferProvider::getNextBuffer( return NOT_ENOUGH_DATA; } - buffer->raw = recordThread->mRsmpInBuffer + front * recordThread->mChannelCount; + buffer->raw = (uint8_t*)recordThread->mRsmpInBuffer + front * recordThread->mFrameSize; buffer->frameCount = part1; mRsmpInUnrel = part1; return NO_ERROR; @@ -6246,7 +6251,11 @@ AudioFlinger::RecordThread::RecordBufferConverter::RecordBufferConverter( // mDstChannelCount // mDstFrameSize mBuf(NULL), mBufFrames(0), mBufFrameSize(0), - mResampler(NULL), mRsmpOutBuffer(NULL), mRsmpOutFrameCount(0) + mResampler(NULL), + mIsLegacyDownmix(false), + mIsLegacyUpmix(false), + mRequiresFloat(false), + mInputConverterProvider(NULL) { (void)updateParameters(srcChannelMask, srcFormat, srcSampleRate, dstChannelMask, dstFormat, dstSampleRate); @@ -6255,13 +6264,18 @@ AudioFlinger::RecordThread::RecordBufferConverter::RecordBufferConverter( AudioFlinger::RecordThread::RecordBufferConverter::~RecordBufferConverter() { free(mBuf); delete mResampler; - free(mRsmpOutBuffer); + delete mInputConverterProvider; } size_t AudioFlinger::RecordThread::RecordBufferConverter::convert(void *dst, AudioBufferProvider *provider, size_t frames) { - if (mSrcSampleRate == mDstSampleRate) { + if (mInputConverterProvider != NULL) { + mInputConverterProvider->setBufferProvider(provider); + provider = mInputConverterProvider; + } + + if (mResampler == NULL) { ALOGVV("NO RESAMPLING sampleRate:%u mSrcFormat:%#x mDstFormat:%#x", mSrcSampleRate, mSrcFormat, mDstFormat); @@ -6273,8 +6287,8 @@ size_t AudioFlinger::RecordThread::RecordBufferConverter::convert(void *dst, frames -= i; // cannot fill request. break; } - // convert to destination buffer - convert(dst, buffer.raw, buffer.frameCount); + // format convert to destination buffer + convertNoResampler(dst, buffer.raw, buffer.frameCount); dst = (int8_t*)dst + buffer.frameCount * mDstFrameSize; i -= buffer.frameCount; @@ -6284,20 +6298,17 @@ size_t AudioFlinger::RecordThread::RecordBufferConverter::convert(void *dst, ALOGVV("RESAMPLING mSrcSampleRate:%u mDstSampleRate:%u mSrcFormat:%#x mDstFormat:%#x", mSrcSampleRate, mDstSampleRate, mSrcFormat, mDstFormat); - // reallocate mRsmpOutBuffer as needed; we will grow but never shrink - if (mRsmpOutFrameCount < frames) { - // FIXME why does each track need it's own mRsmpOutBuffer? can't they share? - free(mRsmpOutBuffer); - // resampler always outputs stereo (FOR NOW) - (void)posix_memalign(&mRsmpOutBuffer, 32, frames * FCC_2 * sizeof(int32_t) /*Q4.27*/); - mRsmpOutFrameCount = frames; - } + // reallocate buffer if needed + if (mBufFrameSize != 0 && mBufFrames < frames) { + free(mBuf); + mBufFrames = frames; + (void)posix_memalign(&mBuf, 32, mBufFrames * mBufFrameSize); + } // resampler accumulates, but we only have one source track - memset(mRsmpOutBuffer, 0, frames * FCC_2 * sizeof(int32_t)); - frames = mResampler->resample((int32_t*)mRsmpOutBuffer, frames, provider); - - // convert to destination buffer - convert(dst, mRsmpOutBuffer, frames); + memset(mBuf, 0, frames * mBufFrameSize); + frames = mResampler->resample((int32_t*)mBuf, frames, provider); + // format convert to destination buffer + convertResampler(dst, mBuf, frames); } return frames; } @@ -6341,74 +6352,132 @@ status_t AudioFlinger::RecordThread::RecordBufferConverter::updateParameters( mDstChannelCount = audio_channel_count_from_in_mask(dstChannelMask); mDstFrameSize = mDstChannelCount * audio_bytes_per_sample(mDstFormat); - // do we need a format buffer? - if (mSrcFormat != mDstFormat && mDstChannelCount != mSrcChannelCount) { + // do we need to resample? + delete mResampler; + mResampler = NULL; + if (mSrcSampleRate != mDstSampleRate) { + mResampler = AudioResampler::create(AUDIO_FORMAT_PCM_FLOAT, + mSrcChannelCount, mDstSampleRate); + mResampler->setSampleRate(mSrcSampleRate); + mResampler->setVolume(AudioMixer::UNITY_GAIN_FLOAT, AudioMixer::UNITY_GAIN_FLOAT); + } + + // are we running legacy channel conversion modes? + mIsLegacyDownmix = (mSrcChannelMask == AUDIO_CHANNEL_IN_STEREO + || mSrcChannelMask == AUDIO_CHANNEL_IN_FRONT_BACK) + && mDstChannelMask == AUDIO_CHANNEL_IN_MONO; + mIsLegacyUpmix = mSrcChannelMask == AUDIO_CHANNEL_IN_MONO + && (mDstChannelMask == AUDIO_CHANNEL_IN_STEREO + || mDstChannelMask == AUDIO_CHANNEL_IN_FRONT_BACK); + + // do we need to process in float? + mRequiresFloat = mResampler != NULL || mIsLegacyDownmix || mIsLegacyUpmix; + + // do we need a staging buffer to convert for destination (we can still optimize this)? + // we use mBufFrameSize > 0 to indicate both frame size as well as buffer necessity + if (mResampler != NULL) { + mBufFrameSize = max(mSrcChannelCount, FCC_2) + * audio_bytes_per_sample(AUDIO_FORMAT_PCM_FLOAT); + } else if ((mIsLegacyUpmix || mIsLegacyDownmix) && mDstFormat != AUDIO_FORMAT_PCM_FLOAT) { + mBufFrameSize = mDstChannelCount * audio_bytes_per_sample(AUDIO_FORMAT_PCM_FLOAT); + } else if (mSrcChannelMask != mDstChannelMask && mDstFormat != mSrcFormat) { mBufFrameSize = mDstChannelCount * audio_bytes_per_sample(mSrcFormat); } else { mBufFrameSize = 0; } mBufFrames = 0; // force the buffer to be resized. - // do we need to resample? - if (mSrcSampleRate != mDstSampleRate) { - if (mResampler != NULL) { - delete mResampler; - } - mResampler = AudioResampler::create(AUDIO_FORMAT_PCM_16_BIT, - mSrcChannelCount, mDstSampleRate); // may seem confusing... - mResampler->setSampleRate(mSrcSampleRate); - mResampler->setVolume(AudioMixer::UNITY_GAIN_FLOAT, AudioMixer::UNITY_GAIN_FLOAT); + // do we need an input converter buffer provider to give us float? + delete mInputConverterProvider; + mInputConverterProvider = NULL; + if (mRequiresFloat && mSrcFormat != AUDIO_FORMAT_PCM_FLOAT) { + mInputConverterProvider = new ReformatBufferProvider( + audio_channel_count_from_in_mask(mSrcChannelMask), + mSrcFormat, + AUDIO_FORMAT_PCM_FLOAT, + 256 /* provider buffer frame count */); + } + + // do we need a remixer to do channel mask conversion + if (!mIsLegacyDownmix && !mIsLegacyUpmix && mSrcChannelMask != mDstChannelMask) { + (void) memcpy_by_index_array_initialization_from_channel_mask( + mIdxAry, ARRAY_SIZE(mIdxAry), mDstChannelMask, mSrcChannelMask); } return NO_ERROR; } -void AudioFlinger::RecordThread::RecordBufferConverter::convert( - void *dst, /*const*/ void *src, size_t frames) +void AudioFlinger::RecordThread::RecordBufferConverter::convertNoResampler( + void *dst, const void *src, size_t frames) { - // check if a memcpy will do - if (mResampler == NULL - && mSrcChannelCount == mDstChannelCount - && mSrcFormat == mDstFormat) { - memcpy(dst, src, - frames * mDstChannelCount * audio_bytes_per_sample(mDstFormat)); - return; - } - // reallocate buffer if needed + // src is native type unless there is legacy upmix or downmix, whereupon it is float. if (mBufFrameSize != 0 && mBufFrames < frames) { free(mBuf); mBufFrames = frames; (void)posix_memalign(&mBuf, 32, mBufFrames * mBufFrameSize); } - // do processing - if (mResampler != NULL) { - // src channel count is always >= 2. + // do we need to do legacy upmix and downmix? + if (mIsLegacyUpmix || mIsLegacyDownmix) { void *dstBuf = mBuf != NULL ? mBuf : dst; - // ditherAndClamp() works as long as all buffers returned by - // activeTrack->getNextBuffer() are 32 bit aligned which should be always true. - if (mDstChannelCount == 1) { - // the resampler always outputs stereo samples. - // FIXME: this rewrites back into src - ditherAndClamp((int32_t *)src, (const int32_t *)src, frames); - downmix_to_mono_i16_from_stereo_i16((int16_t *)dstBuf, - (const int16_t *)src, frames); - } else { - ditherAndClamp((int32_t *)dstBuf, (const int32_t *)src, frames); + if (mIsLegacyUpmix) { + upmix_to_stereo_float_from_mono_float((float *)dstBuf, + (const float *)src, frames); + } else /*mIsLegacyDownmix */ { + downmix_to_mono_float_from_stereo_float((float *)dstBuf, + (const float *)src, frames); } - } else if (mSrcChannelCount != mDstChannelCount) { + if (mBuf != NULL) { + memcpy_by_audio_format(dst, mDstFormat, mBuf, AUDIO_FORMAT_PCM_FLOAT, + frames * mDstChannelCount); + } + return; + } + // do we need to do channel mask conversion? + if (mSrcChannelMask != mDstChannelMask) { void *dstBuf = mBuf != NULL ? mBuf : dst; + memcpy_by_index_array(dstBuf, mDstChannelCount, + src, mSrcChannelCount, mIdxAry, audio_bytes_per_sample(mSrcFormat), frames); + if (dstBuf == dst) { + return; // format is the same + } + } + // convert to destination buffer + const void *convertBuf = mBuf != NULL ? mBuf : src; + memcpy_by_audio_format(dst, mDstFormat, convertBuf, mSrcFormat, + frames * mDstChannelCount); +} + +void AudioFlinger::RecordThread::RecordBufferConverter::convertResampler( + void *dst, /*not-a-const*/ void *src, size_t frames) +{ + // src buffer format is ALWAYS float when entering this routine + if (mIsLegacyUpmix) { + ; // mono to stereo already handled by resampler + } else if (mIsLegacyDownmix + || (mSrcChannelMask == mDstChannelMask && mSrcChannelCount == 1)) { + // the resampler outputs stereo for mono input channel (a feature?) + // must convert to mono + downmix_to_mono_float_from_stereo_float((float *)src, + (const float *)src, frames); + } else if (mSrcChannelMask != mDstChannelMask) { + // convert to mono channel again for channel mask conversion (could be skipped + // with further optimization). if (mSrcChannelCount == 1) { - upmix_to_stereo_i16_from_mono_i16((int16_t *)dstBuf, (const int16_t *)src, - frames); - } else { - downmix_to_mono_i16_from_stereo_i16((int16_t *)dstBuf, - (const int16_t *)src, frames); + downmix_to_mono_float_from_stereo_float((float *)src, + (const float *)src, frames); } + // convert to destination format (in place, OK as float is larger than other types) + if (mDstFormat != AUDIO_FORMAT_PCM_FLOAT) { + memcpy_by_audio_format(src, mDstFormat, src, AUDIO_FORMAT_PCM_FLOAT, + frames * mSrcChannelCount); + } + // channel convert and save to dst + memcpy_by_index_array(dst, mDstChannelCount, + src, mSrcChannelCount, mIdxAry, audio_bytes_per_sample(mDstFormat), frames); + return; } - if (mSrcFormat != mDstFormat) { - void *srcBuf = mBuf != NULL ? mBuf : src; - memcpy_by_audio_format(dst, mDstFormat, srcBuf, mSrcFormat, - frames * mDstChannelCount); - } + // convert to destination format and save to dst + memcpy_by_audio_format(dst, mDstFormat, src, AUDIO_FORMAT_PCM_FLOAT, + frames * mDstChannelCount); } bool AudioFlinger::RecordThread::checkForNewParameter_l(const String8& keyValuePair, @@ -6421,6 +6490,10 @@ bool AudioFlinger::RecordThread::checkForNewParameter_l(const String8& keyValueP audio_format_t reqFormat = mFormat; uint32_t samplingRate = mSampleRate; audio_channel_mask_t channelMask = audio_channel_in_mask_from_count(mChannelCount); + // possible that we are > 2 channels, use channel index mask + if (channelMask == AUDIO_CHANNEL_INVALID && mChannelCount <= FCC_8) { + audio_channel_mask_for_index_assignment_from_count(mChannelCount); + } AudioParameter param = AudioParameter(keyValuePair); int value; @@ -6441,7 +6514,8 @@ bool AudioFlinger::RecordThread::checkForNewParameter_l(const String8& keyValueP } if (param.getInt(String8(AudioParameter::keyChannels), value) == NO_ERROR) { audio_channel_mask_t mask = (audio_channel_mask_t) value; - if (mask != AUDIO_CHANNEL_IN_MONO && mask != AUDIO_CHANNEL_IN_STEREO) { + if (!audio_is_input_channel(mask) || + audio_channel_count_from_in_mask(mask) > FCC_8) { status = BAD_VALUE; } else { channelMask = mask; @@ -6566,10 +6640,13 @@ void AudioFlinger::RecordThread::readInputParameters_l() mSampleRate = mInput->stream->common.get_sample_rate(&mInput->stream->common); mChannelMask = mInput->stream->common.get_channels(&mInput->stream->common); mChannelCount = audio_channel_count_from_in_mask(mChannelMask); + if (mChannelCount > FCC_8) { + ALOGE("HAL channel count %d > %d", mChannelCount, FCC_8); + } mHALFormat = mInput->stream->common.get_format(&mInput->stream->common); mFormat = mHALFormat; - if (mFormat != AUDIO_FORMAT_PCM_16_BIT) { - ALOGE("HAL format %#x not supported; must be AUDIO_FORMAT_PCM_16_BIT", mFormat); + if (!audio_is_linear_pcm(mFormat)) { + ALOGE("HAL format %#x is not linear pcm", mFormat); } mFrameSize = audio_stream_in_frame_size(mInput->stream); mBufferSize = mInput->stream->common.get_buffer_size(&mInput->stream->common); @@ -6584,7 +6661,7 @@ void AudioFlinger::RecordThread::readInputParameters_l() // Note this is independent of the maximum downsampling ratio permitted for capture. mRsmpInFrames = mFrameCount * 7; mRsmpInFramesP2 = roundup(mRsmpInFrames); - delete[] mRsmpInBuffer; + free(mRsmpInBuffer); // TODO optimize audio capture buffer sizes ... // Here we calculate the size of the sliding buffer used as a source @@ -6594,7 +6671,7 @@ void AudioFlinger::RecordThread::readInputParameters_l() // The current value is higher than necessary. However it should not add to latency. // Over-allocate beyond mRsmpInFramesP2 to permit a HAL read past end of buffer - mRsmpInBuffer = new int16_t[(mRsmpInFramesP2 + mFrameCount - 1) * mChannelCount]; + (void)posix_memalign(&mRsmpInBuffer, 32, (mRsmpInFramesP2 + mFrameCount - 1) * mFrameSize); // AudioRecord mSampleRate and mChannelCount are constant due to AudioRecord API constraints. // But if thread's mSampleRate or mChannelCount changes, how will that affect active tracks? diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h index 27bc56b..b7c1ed1 100644 --- a/services/audioflinger/Threads.h +++ b/services/audioflinger/Threads.h @@ -1130,8 +1130,11 @@ public: } private: - // internal convert function for format and channel mask. - void convert(void *dst, /*const*/ void *src, size_t frames); + // format conversion when not using resampler + void convertNoResampler(void *dst, const void *src, size_t frames); + + // format conversion when using resampler; modifies src in-place + void convertResampler(void *dst, /*not-a-const*/ void *src, size_t frames); // user provided information audio_channel_mask_t mSrcChannelMask; @@ -1153,10 +1156,12 @@ public: // resampler info AudioResampler *mResampler; - // interleaved stereo pairs of fixed-point Q4.27 or float depending on resampler - void *mRsmpOutBuffer; - // current allocated frame count for the above, which may be larger than needed - size_t mRsmpOutFrameCount; + + bool mIsLegacyDownmix; // legacy stereo to mono conversion needed + bool mIsLegacyUpmix; // legacy mono to stereo conversion needed + bool mRequiresFloat; // data processing requires float (e.g. resampler) + PassthruBufferProvider *mInputConverterProvider; // converts input to float + int8_t mIdxAry[sizeof(uint32_t) * 8]; // used for channel mask conversion }; #include "RecordTracks.h" @@ -1267,7 +1272,7 @@ private: Condition mStartStopCond; // resampler converts input at HAL Hz to output at AudioRecord client Hz - int16_t *mRsmpInBuffer; // see new[] for details on the size + void *mRsmpInBuffer; // size_t mRsmpInFrames; // size of resampler input in frames size_t mRsmpInFramesP2;// size rounded up to a power-of-2 diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioPort.h b/services/audiopolicy/common/managerdefinitions/include/AudioPort.h index 1c2c27e..82e2c43 100644 --- a/services/audiopolicy/common/managerdefinitions/include/AudioPort.h +++ b/services/audiopolicy/common/managerdefinitions/include/AudioPort.h @@ -75,10 +75,8 @@ public: audio_format_t pickFormat() const; static const audio_format_t sPcmFormatCompareTable[]; - static int compareFormatsGoodToBad( - const audio_format_t *format1, const audio_format_t *format2) { - // compareFormats sorts from bad to good, we reverse it here - return compareFormats(*format2, *format1); + static int compareFormats(const audio_format_t *format1, const audio_format_t *format2) { + return compareFormats(*format1, *format2); } static int compareFormats(audio_format_t format1, audio_format_t format2); diff --git a/services/audiopolicy/common/managerdefinitions/include/ConfigParsingUtils.h b/services/audiopolicy/common/managerdefinitions/include/ConfigParsingUtils.h index f8c4d08..0b08430 100644 --- a/services/audiopolicy/common/managerdefinitions/include/ConfigParsingUtils.h +++ b/services/audiopolicy/common/managerdefinitions/include/ConfigParsingUtils.h @@ -38,7 +38,8 @@ struct StringToEnum { uint32_t value; }; -#define STRING_TO_ENUM(string) { #string, string } +// TODO: move to a separate file. Should be in sync with audio.h. +#define STRING_TO_ENUM(string) { #string, (uint32_t)string } // uint32_t cast removes warning #define NAME_TO_ENUM(name, value) { name, value } #ifndef ARRAY_SIZE #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) @@ -204,6 +205,17 @@ const StringToEnum sInChannelsNameToEnumTable[] = { STRING_TO_ENUM(AUDIO_CHANNEL_IN_FRONT_BACK), }; +const StringToEnum sIndexChannelsNameToEnumTable[] = { + STRING_TO_ENUM(AUDIO_CHANNEL_INDEX_MASK_1), + STRING_TO_ENUM(AUDIO_CHANNEL_INDEX_MASK_2), + STRING_TO_ENUM(AUDIO_CHANNEL_INDEX_MASK_3), + STRING_TO_ENUM(AUDIO_CHANNEL_INDEX_MASK_4), + STRING_TO_ENUM(AUDIO_CHANNEL_INDEX_MASK_5), + STRING_TO_ENUM(AUDIO_CHANNEL_INDEX_MASK_6), + STRING_TO_ENUM(AUDIO_CHANNEL_INDEX_MASK_7), + STRING_TO_ENUM(AUDIO_CHANNEL_INDEX_MASK_8), +}; + const StringToEnum sGainModeNameToEnumTable[] = { STRING_TO_ENUM(AUDIO_GAIN_MODE_JOINT), STRING_TO_ENUM(AUDIO_GAIN_MODE_CHANNELS), diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioPort.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioPort.cpp index f3978ec..64f883a 100644 --- a/services/audiopolicy/common/managerdefinitions/src/AudioPort.cpp +++ b/services/audiopolicy/common/managerdefinitions/src/AudioPort.cpp @@ -146,7 +146,7 @@ void AudioPort::importAudioPort(const sp<AudioPort> port) { break; } } - if (!hasFormat) { // never import a channel mask twice + if (!hasFormat) { // never import a format twice mFormats.add(format); } } @@ -216,7 +216,12 @@ void AudioPort::loadFormats(char *name) } str = strtok(NULL, "|"); } - mFormats.sort(compareFormatsGoodToBad); + // we sort from worst to best, so that AUDIO_FORMAT_DEFAULT is always the first entry. + // TODO: compareFormats could be a lambda to convert between pointer-to-format to format: + // [](const audio_format_t *format1, const audio_format_t *format2) { + // return compareFormats(*format1, *format2); + // } + mFormats.sort(compareFormats); } void AudioPort::loadInChannels(char *name) @@ -235,8 +240,14 @@ void AudioPort::loadInChannels(char *name) (audio_channel_mask_t)ConfigParsingUtils::stringToEnum(sInChannelsNameToEnumTable, ARRAY_SIZE(sInChannelsNameToEnumTable), str); + if (channelMask == 0) { // if not found, check the channel index table + channelMask = (audio_channel_mask_t) + ConfigParsingUtils::stringToEnum(sIndexChannelsNameToEnumTable, + ARRAY_SIZE(sIndexChannelsNameToEnumTable), + str); + } if (channelMask != 0) { - ALOGV("loadInChannels() adding channelMask %04x", channelMask); + ALOGV("loadInChannels() adding channelMask %#x", channelMask); mChannelMasks.add(channelMask); } str = strtok(NULL, "|"); @@ -441,30 +452,87 @@ status_t AudioPort::checkCompatibleChannelMask(audio_channel_mask_t channelMask, } const bool isRecordThread = mType == AUDIO_PORT_TYPE_MIX && mRole == AUDIO_PORT_ROLE_SINK; + const bool isIndex = audio_channel_mask_get_representation(channelMask) + == AUDIO_CHANNEL_REPRESENTATION_INDEX; + int bestMatch = 0; for (size_t i = 0; i < mChannelMasks.size(); i ++) { - // FIXME Does not handle multi-channel automatic conversions yet audio_channel_mask_t supported = mChannelMasks[i]; if (supported == channelMask) { + // Exact matches always taken. if (updatedChannelMask != NULL) { *updatedChannelMask = channelMask; } return NO_ERROR; } - if (isRecordThread) { - // This uses hard-coded knowledge that AudioFlinger can silently down-mix and up-mix. - // FIXME Abstract this out to a table. - if (((supported == AUDIO_CHANNEL_IN_FRONT_BACK || supported == AUDIO_CHANNEL_IN_STEREO) - && channelMask == AUDIO_CHANNEL_IN_MONO) || - (supported == AUDIO_CHANNEL_IN_MONO && (channelMask == AUDIO_CHANNEL_IN_FRONT_BACK - || channelMask == AUDIO_CHANNEL_IN_STEREO))) { + + // AUDIO_CHANNEL_NONE (value: 0) is used for dynamic channel support + if (isRecordThread && supported != AUDIO_CHANNEL_NONE) { + // Approximate (best) match: + // The match score measures how well the supported channel mask matches the + // desired mask, where increasing-is-better. + // + // TODO: Some tweaks may be needed. + // Should be a static function of the data processing library. + // + // In priority: + // match score = 1000 if legacy channel conversion equivalent (always prefer this) + // OR + // match score += 100 if the channel mask representations match + // match score += number of channels matched. + // + // If there are no matched channels, the mask may still be accepted + // but the playback or record will be silent. + const bool isSupportedIndex = (audio_channel_mask_get_representation(supported) + == AUDIO_CHANNEL_REPRESENTATION_INDEX); + int match; + if (isIndex && isSupportedIndex) { + // index equivalence + match = 100 + __builtin_popcount( + audio_channel_mask_get_bits(channelMask) + & audio_channel_mask_get_bits(supported)); + } else if (isIndex && !isSupportedIndex) { + const uint32_t equivalentBits = + (1 << audio_channel_count_from_in_mask(supported)) - 1 ; + match = __builtin_popcount( + audio_channel_mask_get_bits(channelMask) & equivalentBits); + } else if (!isIndex && isSupportedIndex) { + const uint32_t equivalentBits = + (1 << audio_channel_count_from_in_mask(channelMask)) - 1; + match = __builtin_popcount( + equivalentBits & audio_channel_mask_get_bits(supported)); + } else { + // positional equivalence + match = 100 + __builtin_popcount( + audio_channel_mask_get_bits(channelMask) + & audio_channel_mask_get_bits(supported)); + switch (supported) { + case AUDIO_CHANNEL_IN_FRONT_BACK: + case AUDIO_CHANNEL_IN_STEREO: + if (channelMask == AUDIO_CHANNEL_IN_MONO) { + match = 1000; + } + break; + case AUDIO_CHANNEL_IN_MONO: + if (channelMask == AUDIO_CHANNEL_IN_FRONT_BACK + || channelMask == AUDIO_CHANNEL_IN_STEREO) { + match = 1000; + } + break; + default: + break; + } + } + if (match > bestMatch) { + bestMatch = match; if (updatedChannelMask != NULL) { *updatedChannelMask = supported; + } else { + return NO_ERROR; // any match will do in this case. } - return NO_ERROR; } } } - return BAD_VALUE; + return bestMatch > 0 ? NO_ERROR : BAD_VALUE; } status_t AudioPort::checkExactFormat(audio_format_t format) const @@ -495,11 +563,13 @@ status_t AudioPort::checkCompatibleFormat(audio_format_t format, audio_format_t mType == AUDIO_PORT_TYPE_MIX && mRole == AUDIO_PORT_ROLE_SINK && audio_is_linear_pcm(format); - for (size_t i = 0; i < mFormats.size(); ++i) { + // iterate from best format to worst format (reverse order) + for (ssize_t i = mFormats.size() - 1; i >= 0 ; --i) { if (mFormats[i] == format || - (checkInexact && audio_is_linear_pcm(mFormats[i]))) { - // for inexact checks we take the first linear pcm format since - // mFormats is sorted from best PCM format to worst PCM format. + (checkInexact + && mFormats[i] != AUDIO_FORMAT_DEFAULT + && audio_is_linear_pcm(mFormats[i]))) { + // for inexact checks we take the first linear pcm format due to sorting. if (updatedFormat != NULL) { *updatedFormat = mFormats[i]; } @@ -726,10 +796,15 @@ void AudioPort::dump(int fd, int spaces) const const char *formatStr = ConfigParsingUtils::enumToString(sFormatNameToEnumTable, ARRAY_SIZE(sFormatNameToEnumTable), mFormats[i]); - if (i == 0 && strcmp(formatStr, "") == 0) { + const bool isEmptyStr = formatStr[0] == 0; + if (i == 0 && isEmptyStr) { snprintf(buffer, SIZE, "Dynamic"); } else { - snprintf(buffer, SIZE, "%s", formatStr); + if (isEmptyStr) { + snprintf(buffer, SIZE, "%#x", mFormats[i]); + } else { + snprintf(buffer, SIZE, "%s", formatStr); + } } result.append(buffer); result.append(i == (mFormats.size() - 1) ? "" : ", "); diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp index 01f2b61..ba3fcaf 100644 --- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp +++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp @@ -1420,6 +1420,13 @@ status_t AudioPolicyManager::startInput(audio_io_handle_t input, } if (inputDesc->mRefCount == 0) { + // if input maps to a dynamic policy with an activity listener, notify of state change + if ((inputDesc->mPolicyMix != NULL) + && ((inputDesc->mPolicyMix->mFlags & MIX_FLAG_NOTIFY_ACTIVITY) != 0)) { + mpClientInterface->onDynamicPolicyMixStateUpdate(inputDesc->mPolicyMix->mRegistrationId, + MIX_STATE_MIXING); + } + if (mInputs.activeInputsCount() == 0) { SoundTrigger::setCaptureState(true); } @@ -1473,6 +1480,12 @@ status_t AudioPolicyManager::stopInput(audio_io_handle_t input, inputDesc->mRefCount--; if (inputDesc->mRefCount == 0) { + // if input maps to a dynamic policy with an activity listener, notify of state change + if ((inputDesc->mPolicyMix != NULL) + && ((inputDesc->mPolicyMix->mFlags & MIX_FLAG_NOTIFY_ACTIVITY) != 0)) { + mpClientInterface->onDynamicPolicyMixStateUpdate(inputDesc->mPolicyMix->mRegistrationId, + MIX_STATE_IDLE); + } // automatically disable the remote submix output when input is stopped if not // used by a policy mix of type MIX_TYPE_RECORDERS diff --git a/services/mediaresourcemanager/ResourceManagerService.cpp b/services/mediaresourcemanager/ResourceManagerService.cpp index 75a69ed..17aac4e 100644 --- a/services/mediaresourcemanager/ResourceManagerService.cpp +++ b/services/mediaresourcemanager/ResourceManagerService.cpp @@ -88,6 +88,51 @@ static ResourceInfo& getResourceInfoForEdit( return infos.editItemAt(infos.size() - 1); } +status_t ResourceManagerService::dump(int fd, const Vector<String16>& args) { + Mutex::Autolock lock(mLock); + + String8 result; + const size_t SIZE = 256; + char buffer[SIZE]; + + snprintf(buffer, SIZE, "ResourceManagerService: %p\n", this); + result.append(buffer); + result.append(" Policies:\n"); + snprintf(buffer, SIZE, " SupportsMultipleSecureCodecs: %d\n", mSupportsMultipleSecureCodecs); + result.append(buffer); + snprintf(buffer, SIZE, " SupportsSecureWithNonSecureCodec: %d\n", mSupportsSecureWithNonSecureCodec); + result.append(buffer); + + snprintf(buffer, SIZE, " Processes:\n"); + result.append(buffer); + for (size_t i = 0; i < mMap.size(); ++i) { + snprintf(buffer, SIZE, " Pid: %d\n", mMap.keyAt(i)); + result.append(buffer); + + const ResourceInfos &infos = mMap.valueAt(i); + for (size_t j = 0; j < infos.size(); ++j) { + snprintf(buffer, SIZE, " Client:\n"); + result.append(buffer); + snprintf(buffer, SIZE, " Id: %lld\n", (long long)infos[j].clientId); + result.append(buffer); + + snprintf(buffer, SIZE, " Name: %s\n", infos[j].client->getName().string()); + result.append(buffer); + + Vector<MediaResource> resources = infos[j].resources; + snprintf(buffer, SIZE, " Resources:\n"); + result.append(buffer); + for (size_t k = 0; k < resources.size(); ++k) { + snprintf(buffer, SIZE, " %s\n", resources[k].toString().string()); + result.append(buffer); + } + } + } + + write(fd, result.string(), result.size()); + return OK; +} + ResourceManagerService::ResourceManagerService() : mProcessInfo(new ProcessInfo()), mSupportsMultipleSecureCodecs(true), diff --git a/services/mediaresourcemanager/ResourceManagerService.h b/services/mediaresourcemanager/ResourceManagerService.h index 2ed9bf8..0c3d694 100644 --- a/services/mediaresourcemanager/ResourceManagerService.h +++ b/services/mediaresourcemanager/ResourceManagerService.h @@ -48,6 +48,8 @@ class ResourceManagerService public: static char const *getServiceName() { return "media.resource_manager"; } + virtual status_t dump(int fd, const Vector<String16>& args); + ResourceManagerService(); ResourceManagerService(sp<ProcessInfoInterface> processInfo); diff --git a/services/mediaresourcemanager/test/ResourceManagerService_test.cpp b/services/mediaresourcemanager/test/ResourceManagerService_test.cpp index 48d1395..bccc7fa 100644 --- a/services/mediaresourcemanager/test/ResourceManagerService_test.cpp +++ b/services/mediaresourcemanager/test/ResourceManagerService_test.cpp @@ -55,6 +55,10 @@ struct TestClient : public BnResourceManagerClient { return true; } + virtual String8 getName() { + return String8("test_client"); + } + bool reclaimed() const { return mReclaimed; } diff --git a/services/radio/RadioService.cpp b/services/radio/RadioService.cpp index a6c2bdf..cd0f5f3 100644 --- a/services/radio/RadioService.cpp +++ b/services/radio/RadioService.cpp @@ -146,7 +146,7 @@ status_t RadioService::attach(radio_handle_t handle, radio = module->addClient(client, config, withAudio); if (radio == 0) { - NO_INIT; + return NO_INIT; } return NO_ERROR; } @@ -500,13 +500,12 @@ sp<RadioService::ModuleClient> RadioService::Module::addClient(const sp<IRadioCl if (audio) { notifyDeviceConnection(true, ""); } + ALOGV("addClient() DONE moduleClient %p", moduleClient.get()); } else { + ALOGW("%s open_tuner failed with error %d", __FUNCTION__, ret); moduleClient.clear(); } - - ALOGV("addClient() DONE moduleClient %p", moduleClient.get()); - return moduleClient; } |