diff options
Diffstat (limited to 'media')
35 files changed, 2042 insertions, 667 deletions
diff --git a/media/libmedia/Android.mk b/media/libmedia/Android.mk index cee26d9..3be0651 100644 --- a/media/libmedia/Android.mk +++ b/media/libmedia/Android.mk @@ -25,6 +25,7 @@ LOCAL_SRC_FILES:= \ AudioRecord.cpp \ AudioSystem.cpp \ mediaplayer.cpp \ + IMediaCodecList.cpp \ IMediaHTTPConnection.cpp \ IMediaHTTPService.cpp \ IMediaLogService.cpp \ @@ -36,6 +37,7 @@ LOCAL_SRC_FILES:= \ IRemoteDisplay.cpp \ IRemoteDisplayClient.cpp \ IStreamSource.cpp \ + MediaCodecInfo.cpp \ Metadata.cpp \ mediarecorder.cpp \ IMediaMetadataRetriever.cpp \ @@ -74,6 +76,7 @@ LOCAL_MODULE:= libmedia LOCAL_C_INCLUDES := \ $(TOP)/frameworks/native/include/media/openmax \ + $(TOP)/frameworks/av/media/libstagefright \ external/icu/icu4c/source/common \ external/icu/icu4c/source/i18n \ $(call include-path-for, audio-effects) \ diff --git a/media/libmedia/AudioSystem.cpp b/media/libmedia/AudioSystem.cpp index 365a594..3486d21 100644 --- a/media/libmedia/AudioSystem.cpp +++ b/media/libmedia/AudioSystem.cpp @@ -450,6 +450,13 @@ void AudioSystem::releaseAudioSessionId(int audioSession, pid_t pid) } } +audio_hw_sync_t AudioSystem::getAudioHwSyncForSession(audio_session_t sessionId) +{ + const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger(); + if (af == 0) return AUDIO_HW_SYNC_INVALID; + return af->getAudioHwSyncForSession(sessionId); +} + // --------------------------------------------------------------------------- void AudioSystem::AudioFlingerClient::binderDied(const wp<IBinder>& who __unused) @@ -913,6 +920,21 @@ void AudioSystem::setAudioPortCallback(sp<AudioPortCallback> callBack) gAudioPortCallback = callBack; } +status_t AudioSystem::acquireSoundTriggerSession(audio_session_t *session, + audio_io_handle_t *ioHandle, + audio_devices_t *device) +{ + const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service(); + if (aps == 0) return PERMISSION_DENIED; + return aps->acquireSoundTriggerSession(session, ioHandle, device); +} + +status_t AudioSystem::releaseSoundTriggerSession(audio_session_t session) +{ + const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service(); + if (aps == 0) return PERMISSION_DENIED; + return aps->releaseSoundTriggerSession(session); +} // --------------------------------------------------------------------------- void AudioSystem::AudioPolicyServiceClient::binderDied(const wp<IBinder>& who __unused) diff --git a/media/libmedia/IAudioFlinger.cpp b/media/libmedia/IAudioFlinger.cpp index 5331fce..346a192 100644 --- a/media/libmedia/IAudioFlinger.cpp +++ b/media/libmedia/IAudioFlinger.cpp @@ -79,7 +79,8 @@ enum { CREATE_AUDIO_PATCH, RELEASE_AUDIO_PATCH, LIST_AUDIO_PATCHES, - SET_AUDIO_PORT_CONFIG + SET_AUDIO_PORT_CONFIG, + GET_AUDIO_HW_SYNC }; class BpAudioFlinger : public BpInterface<IAudioFlinger> @@ -883,6 +884,17 @@ public: } return status; } + virtual audio_hw_sync_t getAudioHwSyncForSession(audio_session_t sessionId) + { + Parcel data, reply; + data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor()); + data.writeInt32(sessionId); + status_t status = remote()->transact(GET_AUDIO_HW_SYNC, data, &reply); + if (status != NO_ERROR) { + return AUDIO_HW_SYNC_INVALID; + } + return (audio_hw_sync_t)reply.readInt32(); + } }; IMPLEMENT_META_INTERFACE(AudioFlinger, "android.media.IAudioFlinger"); @@ -1345,6 +1357,11 @@ status_t BnAudioFlinger::onTransact( reply->writeInt32(status); return NO_ERROR; } break; + case GET_AUDIO_HW_SYNC: { + CHECK_INTERFACE(IAudioFlinger, data, reply); + reply->writeInt32(getAudioHwSyncForSession((audio_session_t)data.readInt32())); + return NO_ERROR; + } break; default: return BBinder::onTransact(code, data, reply, flags); } diff --git a/media/libmedia/IAudioPolicyService.cpp b/media/libmedia/IAudioPolicyService.cpp index 1593b17..b57f747 100644 --- a/media/libmedia/IAudioPolicyService.cpp +++ b/media/libmedia/IAudioPolicyService.cpp @@ -65,7 +65,9 @@ enum { LIST_AUDIO_PATCHES, SET_AUDIO_PORT_CONFIG, REGISTER_CLIENT, - GET_OUTPUT_FOR_ATTR + GET_OUTPUT_FOR_ATTR, + ACQUIRE_SOUNDTRIGGER_SESSION, + RELEASE_SOUNDTRIGGER_SESSION }; class BpAudioPolicyService : public BpInterface<IAudioPolicyService> @@ -563,6 +565,7 @@ public: } return status; } + virtual void registerClient(const sp<IAudioPolicyServiceClient>& client) { Parcel data, reply; @@ -570,6 +573,40 @@ public: data.writeStrongBinder(client->asBinder()); remote()->transact(REGISTER_CLIENT, data, &reply); } + + virtual status_t acquireSoundTriggerSession(audio_session_t *session, + audio_io_handle_t *ioHandle, + audio_devices_t *device) + { + if (session == NULL || ioHandle == NULL || device == NULL) { + return BAD_VALUE; + } + Parcel data, reply; + data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor()); + status_t status = remote()->transact(ACQUIRE_SOUNDTRIGGER_SESSION, data, &reply); + if (status != NO_ERROR) { + return status; + } + status = (status_t)reply.readInt32(); + if (status == NO_ERROR) { + *session = (audio_session_t)reply.readInt32(); + *ioHandle = (audio_io_handle_t)reply.readInt32(); + *device = (audio_devices_t)reply.readInt32(); + } + return status; + } + + virtual status_t releaseSoundTriggerSession(audio_session_t session) + { + Parcel data, reply; + data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor()); + data.writeInt32(session); + status_t status = remote()->transact(RELEASE_SOUNDTRIGGER_SESSION, data, &reply); + if (status != NO_ERROR) { + return status; + } + return (status_t)reply.readInt32(); + } }; IMPLEMENT_META_INTERFACE(AudioPolicyService, "android.media.IAudioPolicyService"); @@ -984,6 +1021,7 @@ status_t BnAudioPolicyService::onTransact( reply->writeInt32(status); return NO_ERROR; } + case REGISTER_CLIENT: { CHECK_INTERFACE(IAudioPolicyService, data, reply); sp<IAudioPolicyServiceClient> client = interface_cast<IAudioPolicyServiceClient>( @@ -992,6 +1030,33 @@ status_t BnAudioPolicyService::onTransact( return NO_ERROR; } break; + case ACQUIRE_SOUNDTRIGGER_SESSION: { + CHECK_INTERFACE(IAudioPolicyService, data, reply); + sp<IAudioPolicyServiceClient> client = interface_cast<IAudioPolicyServiceClient>( + data.readStrongBinder()); + audio_session_t session; + audio_io_handle_t ioHandle; + audio_devices_t device; + status_t status = acquireSoundTriggerSession(&session, &ioHandle, &device); + reply->writeInt32(status); + if (status == NO_ERROR) { + reply->writeInt32(session); + reply->writeInt32(ioHandle); + reply->writeInt32(device); + } + return NO_ERROR; + } break; + + case RELEASE_SOUNDTRIGGER_SESSION: { + CHECK_INTERFACE(IAudioPolicyService, data, reply); + sp<IAudioPolicyServiceClient> client = interface_cast<IAudioPolicyServiceClient>( + data.readStrongBinder()); + audio_session_t session = (audio_session_t)data.readInt32(); + status_t status = releaseSoundTriggerSession(session); + reply->writeInt32(status); + return NO_ERROR; + } break; + default: return BBinder::onTransact(code, data, reply, flags); } diff --git a/media/libmedia/IDrm.cpp b/media/libmedia/IDrm.cpp index f1a6a9f..1904839 100644 --- a/media/libmedia/IDrm.cpp +++ b/media/libmedia/IDrm.cpp @@ -53,7 +53,8 @@ enum { SIGN, SIGN_RSA, VERIFY, - SET_LISTENER + SET_LISTENER, + UNPROVISION_DEVICE }; struct BpDrm : public BpInterface<IDrm> { @@ -229,6 +230,15 @@ struct BpDrm : public BpInterface<IDrm> { return reply.readInt32(); } + virtual status_t unprovisionDevice() { + Parcel data, reply; + data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); + + remote()->transact(UNPROVISION_DEVICE, data, &reply); + + return reply.readInt32(); + } + virtual status_t getSecureStops(List<Vector<uint8_t> > &secureStops) { Parcel data, reply; data.writeInterfaceToken(IDrm::getInterfaceDescriptor()); @@ -619,6 +629,14 @@ status_t BnDrm::onTransact( return OK; } + case UNPROVISION_DEVICE: + { + CHECK_INTERFACE(IDrm, data, reply); + status_t result = unprovisionDevice(); + reply->writeInt32(result); + return OK; + } + case GET_SECURE_STOPS: { CHECK_INTERFACE(IDrm, data, reply); diff --git a/media/libmedia/IMediaCodecList.cpp b/media/libmedia/IMediaCodecList.cpp new file mode 100644 index 0000000..bf7c5ca --- /dev/null +++ b/media/libmedia/IMediaCodecList.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2014, 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. + */ + +#include <stdint.h> +#include <sys/types.h> + +#include <binder/Parcel.h> +#include <media/stagefright/MediaCodecList.h> +#include <media/IMediaCodecList.h> +#include <media/MediaCodecInfo.h> + +#include <utils/Errors.h> // for status_t + +namespace android { + +enum { + CREATE = IBinder::FIRST_CALL_TRANSACTION, + COUNT_CODECS, + GET_CODEC_INFO, + FIND_CODEC_BY_TYPE, + FIND_CODEC_BY_NAME, +}; + +class BpMediaCodecList: public BpInterface<IMediaCodecList> +{ +public: + BpMediaCodecList(const sp<IBinder>& impl) + : BpInterface<IMediaCodecList>(impl) + { + } + + virtual size_t countCodecs() const + { + Parcel data, reply; + data.writeInterfaceToken(IMediaCodecList::getInterfaceDescriptor()); + remote()->transact(COUNT_CODECS, data, &reply); + return static_cast<size_t>(reply.readInt32()); + } + + virtual sp<MediaCodecInfo> getCodecInfo(size_t index) const + { + Parcel data, reply; + data.writeInterfaceToken(IMediaCodecList::getInterfaceDescriptor()); + data.writeInt32(index); + remote()->transact(GET_CODEC_INFO, data, &reply); + status_t err = reply.readInt32(); + if (err == OK) { + return MediaCodecInfo::FromParcel(reply); + } else { + return NULL; + } + } + + virtual ssize_t findCodecByType( + const char *type, bool encoder, size_t startIndex = 0) const + { + if (startIndex > INT32_MAX) { + return NAME_NOT_FOUND; + } + + Parcel data, reply; + data.writeInterfaceToken(IMediaCodecList::getInterfaceDescriptor()); + data.writeCString(type); + data.writeInt32(encoder); + data.writeInt32(startIndex); + remote()->transact(FIND_CODEC_BY_TYPE, data, &reply); + return static_cast<ssize_t>(reply.readInt32()); + } + + virtual ssize_t findCodecByName(const char *name) const + { + Parcel data, reply; + data.writeInterfaceToken(IMediaCodecList::getInterfaceDescriptor()); + data.writeCString(name); + remote()->transact(FIND_CODEC_BY_NAME, data, &reply); + return static_cast<ssize_t>(reply.readInt32()); + } +}; + +IMPLEMENT_META_INTERFACE(MediaCodecList, "android.media.IMediaCodecList"); + +// ---------------------------------------------------------------------- + +status_t BnMediaCodecList::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + switch (code) { + case COUNT_CODECS: + { + CHECK_INTERFACE(IMediaCodecList, data, reply); + size_t count = countCodecs(); + if (count > INT32_MAX) { + count = INT32_MAX; + } + reply->writeInt32(count); + return NO_ERROR; + } + break; + + case GET_CODEC_INFO: + { + CHECK_INTERFACE(IMediaCodecList, data, reply); + size_t index = static_cast<size_t>(data.readInt32()); + const sp<MediaCodecInfo> info = getCodecInfo(index); + if (info != NULL) { + reply->writeInt32(OK); + info->writeToParcel(reply); + } else { + reply->writeInt32(-ERANGE); + } + return NO_ERROR; + } + break; + + case FIND_CODEC_BY_TYPE: + { + CHECK_INTERFACE(IMediaCodecList, data, reply); + const char *type = data.readCString(); + bool isEncoder = static_cast<bool>(data.readInt32()); + size_t startIndex = static_cast<size_t>(data.readInt32()); + ssize_t index = findCodecByType(type, isEncoder, startIndex); + if (index > INT32_MAX || index < 0) { + index = NAME_NOT_FOUND; + } + reply->writeInt32(index); + return NO_ERROR; + } + break; + + case FIND_CODEC_BY_NAME: + { + CHECK_INTERFACE(IMediaCodecList, data, reply); + const char *name = data.readCString(); + ssize_t index = findCodecByName(name); + if (index > INT32_MAX || index < 0) { + index = NAME_NOT_FOUND; + } + reply->writeInt32(index); + return NO_ERROR; + } + break; + + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +// ---------------------------------------------------------------------------- + +}; // namespace android diff --git a/media/libmedia/IMediaPlayerService.cpp b/media/libmedia/IMediaPlayerService.cpp index d116b14..2e02d17 100644 --- a/media/libmedia/IMediaPlayerService.cpp +++ b/media/libmedia/IMediaPlayerService.cpp @@ -23,6 +23,7 @@ #include <media/ICrypto.h> #include <media/IDrm.h> #include <media/IHDCP.h> +#include <media/IMediaCodecList.h> #include <media/IMediaHTTPService.h> #include <media/IMediaPlayerService.h> #include <media/IMediaRecorder.h> @@ -49,6 +50,7 @@ enum { ADD_BATTERY_DATA, PULL_BATTERY_DATA, LISTEN_FOR_REMOTE_DISPLAY, + GET_CODEC_LIST, }; class BpMediaPlayerService: public BpInterface<IMediaPlayerService> @@ -191,6 +193,13 @@ public: remote()->transact(LISTEN_FOR_REMOTE_DISPLAY, data, &reply); return interface_cast<IRemoteDisplay>(reply.readStrongBinder()); } + + virtual sp<IMediaCodecList> getCodecList() const { + Parcel data, reply; + data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor()); + remote()->transact(GET_CODEC_LIST, data, &reply); + return interface_cast<IMediaCodecList>(reply.readStrongBinder()); + } }; IMPLEMENT_META_INTERFACE(MediaPlayerService, "android.media.IMediaPlayerService"); @@ -318,6 +327,12 @@ status_t BnMediaPlayerService::onTransact( reply->writeStrongBinder(display->asBinder()); return NO_ERROR; } break; + case GET_CODEC_LIST: { + CHECK_INTERFACE(IMediaPlayerService, data, reply); + sp<IMediaCodecList> mcl = getCodecList(); + reply->writeStrongBinder(mcl->asBinder()); + return NO_ERROR; + } break; default: return BBinder::onTransact(code, data, reply, flags); } diff --git a/media/libmedia/MediaCodecInfo.cpp b/media/libmedia/MediaCodecInfo.cpp new file mode 100644 index 0000000..7900eae --- /dev/null +++ b/media/libmedia/MediaCodecInfo.cpp @@ -0,0 +1,252 @@ +/* + * Copyright 2014, 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 "MediaCodecInfo" +#include <utils/Log.h> + +#include <media/IOMX.h> + +#include <media/MediaCodecInfo.h> + +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <binder/Parcel.h> + +#include <media/stagefright/OMXCodec.h> + +namespace android { + +void MediaCodecInfo::Capabilities::getSupportedProfileLevels( + Vector<ProfileLevel> *profileLevels) const { + profileLevels->clear(); + profileLevels->appendVector(mProfileLevels); +} + +void MediaCodecInfo::Capabilities::getSupportedColorFormats( + Vector<uint32_t> *colorFormats) const { + colorFormats->clear(); + colorFormats->appendVector(mColorFormats); +} + +uint32_t MediaCodecInfo::Capabilities::getFlags() const { + return mFlags; +} + +const sp<AMessage> &MediaCodecInfo::Capabilities::getDetails() const { + return mDetails; +} + +MediaCodecInfo::Capabilities::Capabilities() + : mFlags(0) { + mDetails = new AMessage; +} + +// static +sp<MediaCodecInfo::Capabilities> MediaCodecInfo::Capabilities::FromParcel( + const Parcel &parcel) { + sp<MediaCodecInfo::Capabilities> caps = new Capabilities(); + size_t size = static_cast<size_t>(parcel.readInt32()); + for (size_t i = 0; i < size; i++) { + ProfileLevel profileLevel; + profileLevel.mProfile = static_cast<uint32_t>(parcel.readInt32()); + profileLevel.mLevel = static_cast<uint32_t>(parcel.readInt32()); + if (caps != NULL) { + caps->mProfileLevels.push_back(profileLevel); + } + } + size = static_cast<size_t>(parcel.readInt32()); + for (size_t i = 0; i < size; i++) { + uint32_t color = static_cast<uint32_t>(parcel.readInt32()); + if (caps != NULL) { + caps->mColorFormats.push_back(color); + } + } + uint32_t flags = static_cast<uint32_t>(parcel.readInt32()); + sp<AMessage> details = AMessage::FromParcel(parcel); + if (caps != NULL) { + caps->mFlags = flags; + caps->mDetails = details; + } + return caps; +} + +status_t MediaCodecInfo::Capabilities::writeToParcel(Parcel *parcel) const { + CHECK_LE(mProfileLevels.size(), INT32_MAX); + parcel->writeInt32(mProfileLevels.size()); + for (size_t i = 0; i < mProfileLevels.size(); i++) { + parcel->writeInt32(mProfileLevels.itemAt(i).mProfile); + parcel->writeInt32(mProfileLevels.itemAt(i).mLevel); + } + CHECK_LE(mColorFormats.size(), INT32_MAX); + parcel->writeInt32(mColorFormats.size()); + for (size_t i = 0; i < mColorFormats.size(); i++) { + parcel->writeInt32(mColorFormats.itemAt(i)); + } + parcel->writeInt32(mFlags); + mDetails->writeToParcel(parcel); + return OK; +} + +bool MediaCodecInfo::isEncoder() const { + return mIsEncoder; +} + +bool MediaCodecInfo::hasQuirk(const char *name) const { + for (size_t ix = 0; ix < mQuirks.size(); ix++) { + if (mQuirks.itemAt(ix).equalsIgnoreCase(name)) { + return true; + } + } + return false; +} + +void MediaCodecInfo::getSupportedMimes(Vector<AString> *mimes) const { + mimes->clear(); + for (size_t ix = 0; ix < mCaps.size(); ix++) { + mimes->push_back(mCaps.keyAt(ix)); + } +} + +const sp<MediaCodecInfo::Capabilities> & +MediaCodecInfo::getCapabilitiesFor(const char *mime) const { + ssize_t ix = getCapabilityIndex(mime); + if (ix >= 0) { + return mCaps.valueAt(ix); + } + return NULL; +} + +const char *MediaCodecInfo::getCodecName() const { + return mName.c_str(); +} + +// static +sp<MediaCodecInfo> MediaCodecInfo::FromParcel(const Parcel &parcel) { + AString name = AString::FromParcel(parcel); + bool isEncoder = static_cast<bool>(parcel.readInt32()); + sp<MediaCodecInfo> info = new MediaCodecInfo(name, isEncoder, NULL); + size_t size = static_cast<size_t>(parcel.readInt32()); + for (size_t i = 0; i < size; i++) { + AString quirk = AString::FromParcel(parcel); + if (info != NULL) { + info->mQuirks.push_back(quirk); + } + } + size = static_cast<size_t>(parcel.readInt32()); + for (size_t i = 0; i < size; i++) { + AString mime = AString::FromParcel(parcel); + sp<Capabilities> caps = Capabilities::FromParcel(parcel); + if (info != NULL) { + info->mCaps.add(mime, caps); + } + } + return info; +} + +status_t MediaCodecInfo::writeToParcel(Parcel *parcel) const { + mName.writeToParcel(parcel); + parcel->writeInt32(mIsEncoder); + parcel->writeInt32(mQuirks.size()); + for (size_t i = 0; i < mQuirks.size(); i++) { + mQuirks.itemAt(i).writeToParcel(parcel); + } + parcel->writeInt32(mCaps.size()); + for (size_t i = 0; i < mCaps.size(); i++) { + mCaps.keyAt(i).writeToParcel(parcel); + mCaps.valueAt(i)->writeToParcel(parcel); + } + return OK; +} + +ssize_t MediaCodecInfo::getCapabilityIndex(const char *mime) const { + for (size_t ix = 0; ix < mCaps.size(); ix++) { + if (mCaps.keyAt(ix).equalsIgnoreCase(mime)) { + return ix; + } + } + return -1; +} + +MediaCodecInfo::MediaCodecInfo(AString name, bool encoder, const char *mime) + : mName(name), + mIsEncoder(encoder), + mHasSoleMime(false) { + if (mime != NULL) { + addMime(mime); + mHasSoleMime = true; + } +} + +status_t MediaCodecInfo::addMime(const char *mime) { + if (mHasSoleMime) { + ALOGE("Codec '%s' already had its type specified", mName.c_str()); + return -EINVAL; + } + ssize_t ix = getCapabilityIndex(mime); + if (ix >= 0) { + mCurrentCaps = mCaps.valueAt(ix); + } else { + mCurrentCaps = new Capabilities(); + mCaps.add(AString(mime), mCurrentCaps); + } + return OK; +} + +status_t MediaCodecInfo::initializeCapabilities(const CodecCapabilities &caps) { + mCurrentCaps->mProfileLevels.clear(); + mCurrentCaps->mColorFormats.clear(); + + for (size_t i = 0; i < caps.mProfileLevels.size(); ++i) { + const CodecProfileLevel &src = caps.mProfileLevels.itemAt(i); + + ProfileLevel profileLevel; + profileLevel.mProfile = src.mProfile; + profileLevel.mLevel = src.mLevel; + mCurrentCaps->mProfileLevels.push_back(profileLevel); + } + + for (size_t i = 0; i < caps.mColorFormats.size(); ++i) { + mCurrentCaps->mColorFormats.push_back(caps.mColorFormats.itemAt(i)); + } + + mCurrentCaps->mFlags = caps.mFlags; + mCurrentCaps->mDetails = new AMessage; + + return OK; +} + +void MediaCodecInfo::addQuirk(const char *name) { + if (!hasQuirk(name)) { + mQuirks.push(name); + } +} + +void MediaCodecInfo::complete() { + mCurrentCaps = NULL; +} + +void MediaCodecInfo::addDetail(const AString &key, const AString &value) { + mCurrentCaps->mDetails->setString(key.c_str(), value.c_str()); +} + +void MediaCodecInfo::addFeature(const AString &key, int32_t value) { + AString tag = "feature-"; + tag.append(key); + mCurrentCaps->mDetails->setInt32(tag.c_str(), value); +} + +} // namespace android diff --git a/media/libmediaplayerservice/Drm.cpp b/media/libmediaplayerservice/Drm.cpp index d50037f..d222316 100644 --- a/media/libmediaplayerservice/Drm.cpp +++ b/media/libmediaplayerservice/Drm.cpp @@ -417,6 +417,23 @@ status_t Drm::provideProvisionResponse(Vector<uint8_t> const &response, return mPlugin->provideProvisionResponse(response, certificate, wrappedKey); } +status_t Drm::unprovisionDevice() { + Mutex::Autolock autoLock(mLock); + + if (mInitCheck != OK) { + return mInitCheck; + } + + if (mPlugin == NULL) { + return -EINVAL; + } + + if (!checkPermission("android.permission.REMOVE_DRM_CERTIFICATES")) { + return -EPERM; + } + + return mPlugin->unprovisionDevice(); +} status_t Drm::getSecureStops(List<Vector<uint8_t> > &secureStops) { Mutex::Autolock autoLock(mLock); diff --git a/media/libmediaplayerservice/Drm.h b/media/libmediaplayerservice/Drm.h index 3d4b0fc..9e23e2e 100644 --- a/media/libmediaplayerservice/Drm.h +++ b/media/libmediaplayerservice/Drm.h @@ -75,6 +75,8 @@ struct Drm : public BnDrm, Vector<uint8_t> &certificate, Vector<uint8_t> &wrappedKey); + virtual status_t unprovisionDevice(); + virtual status_t getSecureStops(List<Vector<uint8_t> > &secureStops); virtual status_t releaseSecureStops(Vector<uint8_t> const &ssRelease); diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp index 735344c..a706987 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -54,6 +54,7 @@ #include <media/Metadata.h> #include <media/AudioTrack.h> #include <media/MemoryLeakTrackUtil.h> +#include <media/stagefright/MediaCodecList.h> #include <media/stagefright/MediaErrors.h> #include <media/stagefright/AudioPlayer.h> #include <media/stagefright/foundation/ADebug.h> @@ -345,6 +346,10 @@ sp<IMediaPlayer> MediaPlayerService::create(const sp<IMediaPlayerClient>& client return c; } +sp<IMediaCodecList> MediaPlayerService::getCodecList() const { + return MediaCodecList::getLocalInstance(); +} + sp<IOMX> MediaPlayerService::getOMX() { Mutex::Autolock autoLock(mLock); diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h index 2eca6a0..406e3f6 100644 --- a/media/libmediaplayerservice/MediaPlayerService.h +++ b/media/libmediaplayerservice/MediaPlayerService.h @@ -273,6 +273,7 @@ public: uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat, const sp<IMemoryHeap>& heap, size_t *pSize); + virtual sp<IMediaCodecList> getCodecList() const; virtual sp<IOMX> getOMX(); virtual sp<ICrypto> makeCrypto(); virtual sp<IDrm> makeDrm(); diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp index 2f5b0f1..1616448 100644 --- a/media/libmediaplayerservice/nuplayer/GenericSource.cpp +++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp @@ -14,10 +14,14 @@ * limitations under the License. */ +//#define LOG_NDEBUG 0 +#define LOG_TAG "GenericSource" + #include "GenericSource.h" #include "AnotherPacketSource.h" +#include <media/IMediaHTTPService.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AMessage.h> @@ -34,45 +38,63 @@ namespace android { NuPlayer::GenericSource::GenericSource( const sp<AMessage> ¬ify, - const sp<IMediaHTTPService> &httpService, - const char *url, - const KeyedVector<String8, String8> *headers, - bool isWidevine, bool uidValid, uid_t uid) : Source(notify), mFetchSubtitleDataGeneration(0), + mFetchTimedTextDataGeneration(0), mDurationUs(0ll), mAudioIsVorbis(false), - mIsWidevine(isWidevine), + mIsWidevine(false), mUIDValid(uidValid), mUID(uid) { + resetDataSource(); DataSource::RegisterDefaultSniffers(); +} - sp<DataSource> dataSource = - DataSource::CreateFromURI(httpService, url, headers); - CHECK(dataSource != NULL); +void NuPlayer::GenericSource::resetDataSource() { + mHTTPService.clear(); + mUri.clear(); + mUriHeaders.clear(); + mFd = -1; + mOffset = 0; + mLength = 0; +} + +status_t NuPlayer::GenericSource::setDataSource( + const sp<IMediaHTTPService> &httpService, + const char *url, + const KeyedVector<String8, String8> *headers) { + resetDataSource(); - initFromDataSource(dataSource); + mHTTPService = httpService; + mUri = url; + + if (headers) { + mUriHeaders = *headers; + } + + // delay data source creation to prepareAsync() to avoid blocking + // the calling thread in setDataSource for any significant time. + return OK; } -NuPlayer::GenericSource::GenericSource( - const sp<AMessage> ¬ify, - int fd, int64_t offset, int64_t length) - : Source(notify), - mFetchSubtitleDataGeneration(0), - mDurationUs(0ll), - mAudioIsVorbis(false), - mIsWidevine(false) { - DataSource::RegisterDefaultSniffers(); +status_t NuPlayer::GenericSource::setDataSource( + int fd, int64_t offset, int64_t length) { + resetDataSource(); - sp<DataSource> dataSource = new FileSource(dup(fd), offset, length); + mFd = dup(fd); + mOffset = offset; + mLength = length; - initFromDataSource(dataSource); + // delay data source creation to prepareAsync() to avoid blocking + // the calling thread in setDataSource for any significant time. + return OK; } -void NuPlayer::GenericSource::initFromDataSource( - const sp<DataSource> &dataSource) { +status_t NuPlayer::GenericSource::initFromDataSource( + const sp<DataSource> &dataSource, + const char* mime) { sp<MediaExtractor> extractor; if (mIsWidevine) { @@ -86,7 +108,7 @@ void NuPlayer::GenericSource::initFromDataSource( || strcasecmp( mimeType.string(), MEDIA_MIMETYPE_CONTAINER_WVM)) { ALOGE("unsupported widevine mime: %s", mimeType.string()); - return; + return UNKNOWN_ERROR; } sp<WVMExtractor> wvmExtractor = new WVMExtractor(dataSource); @@ -96,10 +118,12 @@ void NuPlayer::GenericSource::initFromDataSource( } extractor = wvmExtractor; } else { - extractor = MediaExtractor::Create(dataSource); + extractor = MediaExtractor::Create(dataSource, mime); } - CHECK(extractor != NULL); + if (extractor == NULL) { + return UNKNOWN_ERROR; + } sp<MetaData> fileMeta = extractor->getMetaData(); if (fileMeta != NULL) { @@ -132,6 +156,16 @@ void NuPlayer::GenericSource::initFromDataSource( if (mVideoTrack.mSource == NULL) { mVideoTrack.mIndex = i; mVideoTrack.mSource = track; + + // check if the source requires secure buffers + int32_t secure; + if (meta->findInt32(kKeyRequiresSecureBuffers, &secure) + && secure) { + mIsWidevine = true; + if (mUIDValid) { + extractor->setUID(mUID); + } + } } } @@ -145,9 +179,12 @@ void NuPlayer::GenericSource::initFromDataSource( } } } + + return OK; } -status_t NuPlayer::GenericSource::setBuffers(bool audio, Vector<MediaBuffer *> &buffers) { +status_t NuPlayer::GenericSource::setBuffers( + bool audio, Vector<MediaBuffer *> &buffers) { if (mIsWidevine && !audio) { return mVideoTrack.mSource->setBuffers(buffers); } @@ -158,6 +195,38 @@ NuPlayer::GenericSource::~GenericSource() { } void NuPlayer::GenericSource::prepareAsync() { + // delayed data source creation + AString sniffedMIME; + sp<DataSource> dataSource; + + if (!mUri.empty()) { + mIsWidevine = !strncasecmp(mUri.c_str(), "widevine://", 11); + + dataSource = DataSource::CreateFromURI( + mHTTPService, mUri.c_str(), &mUriHeaders, &sniffedMIME); + } else { + // set to false first, if the extractor + // comes back as secure, set it to true then. + mIsWidevine = false; + + dataSource = new FileSource(mFd, mOffset, mLength); + } + + if (dataSource == NULL) { + ALOGE("Failed to create data source!"); + notifyPrepared(UNKNOWN_ERROR); + return; + } + + status_t err = initFromDataSource( + dataSource, sniffedMIME.empty() ? NULL : sniffedMIME.c_str()); + + if (err != OK) { + ALOGE("Failed to init from data source!"); + notifyPrepared(err); + return; + } + if (mVideoTrack.mSource != NULL) { sp<MetaData> meta = mVideoTrack.mSource->getFormat(); @@ -206,66 +275,29 @@ void NuPlayer::GenericSource::onMessageReceived(const sp<AMessage> &msg) { switch (msg->what()) { case kWhatFetchSubtitleData: { - int32_t generation; - CHECK(msg->findInt32("generation", &generation)); - if (generation != mFetchSubtitleDataGeneration) { - // stale - break; - } - - int32_t avail; - if (mSubtitleTrack.mPackets->hasBufferAvailable(&avail)) { - break; - } - - int64_t timeUs; - CHECK(msg->findInt64("timeUs", &timeUs)); - - int64_t subTimeUs; - readBuffer(MEDIA_TRACK_TYPE_SUBTITLE, timeUs, &subTimeUs); - - const int64_t oneSecUs = 1000000ll; - const int64_t delayUs = subTimeUs - timeUs - oneSecUs; - sp<AMessage> msg2 = new AMessage(kWhatSendSubtitleData, id()); - msg2->setInt32("generation", generation); - msg2->post(delayUs < 0 ? 0 : delayUs); - ALOGV("kWhatFetchSubtitleData generation %d, delayUs %lld", - mFetchSubtitleDataGeneration, delayUs); + fetchTextData(kWhatSendSubtitleData, MEDIA_TRACK_TYPE_SUBTITLE, + mFetchSubtitleDataGeneration, mSubtitleTrack.mPackets, msg); + break; + } + case kWhatFetchTimedTextData: + { + fetchTextData(kWhatSendTimedTextData, MEDIA_TRACK_TYPE_TIMEDTEXT, + mFetchTimedTextDataGeneration, mTimedTextTrack.mPackets, msg); break; } case kWhatSendSubtitleData: { - int32_t generation; - CHECK(msg->findInt32("generation", &generation)); - if (generation != mFetchSubtitleDataGeneration) { - // stale - break; - } - - int64_t subTimeUs; - if (mSubtitleTrack.mPackets->nextBufferTime(&subTimeUs) != OK) { - break; - } - - int64_t nextSubTimeUs; - readBuffer(MEDIA_TRACK_TYPE_SUBTITLE, -1, &nextSubTimeUs); - - sp<ABuffer> buffer; - status_t dequeueStatus = mSubtitleTrack.mPackets->dequeueAccessUnit(&buffer); - if (dequeueStatus != OK) { - ALOGE("kWhatSendSubtitleData dequeueAccessUnit: %d", dequeueStatus); - } else { - sp<AMessage> notify = dupNotify(); - notify->setInt32("what", kWhatSubtitleData); - notify->setBuffer("buffer", buffer); - notify->post(); - - const int64_t delayUs = nextSubTimeUs - subTimeUs; - msg->post(delayUs < 0 ? 0 : delayUs); - } + sendTextData(kWhatSubtitleData, MEDIA_TRACK_TYPE_SUBTITLE, + mFetchSubtitleDataGeneration, mSubtitleTrack.mPackets, msg); + break; + } + case kWhatSendTimedTextData: + { + sendTextData(kWhatTimedTextData, MEDIA_TRACK_TYPE_TIMEDTEXT, + mFetchTimedTextDataGeneration, mTimedTextTrack.mPackets, msg); break; } @@ -308,7 +340,7 @@ void NuPlayer::GenericSource::onMessageReceived(const sp<AMessage> &msg) { int64_t timeUs, actualTimeUs; const bool formatChange = true; - sp<AMessage> latestMeta = track->mPackets->getLatestMeta(); + sp<AMessage> latestMeta = track->mPackets->getLatestEnqueuedMeta(); CHECK(latestMeta != NULL && latestMeta->findInt64("timeUs", &timeUs)); readBuffer(trackType, timeUs, &actualTimeUs, formatChange); readBuffer(counterpartType, -1, NULL, formatChange); @@ -323,6 +355,74 @@ void NuPlayer::GenericSource::onMessageReceived(const sp<AMessage> &msg) { } } +void NuPlayer::GenericSource::fetchTextData( + uint32_t sendWhat, + media_track_type type, + int32_t curGen, + sp<AnotherPacketSource> packets, + sp<AMessage> msg) { + int32_t msgGeneration; + CHECK(msg->findInt32("generation", &msgGeneration)); + if (msgGeneration != curGen) { + // stale + return; + } + + int32_t avail; + if (packets->hasBufferAvailable(&avail)) { + return; + } + + int64_t timeUs; + CHECK(msg->findInt64("timeUs", &timeUs)); + + int64_t subTimeUs; + readBuffer(type, timeUs, &subTimeUs); + + int64_t delayUs = subTimeUs - timeUs; + if (msg->what() == kWhatFetchSubtitleData) { + const int64_t oneSecUs = 1000000ll; + delayUs -= oneSecUs; + } + sp<AMessage> msg2 = new AMessage(sendWhat, id()); + msg2->setInt32("generation", msgGeneration); + msg2->post(delayUs < 0 ? 0 : delayUs); +} + +void NuPlayer::GenericSource::sendTextData( + uint32_t what, + media_track_type type, + int32_t curGen, + sp<AnotherPacketSource> packets, + sp<AMessage> msg) { + int32_t msgGeneration; + CHECK(msg->findInt32("generation", &msgGeneration)); + if (msgGeneration != curGen) { + // stale + return; + } + + int64_t subTimeUs; + if (packets->nextBufferTime(&subTimeUs) != OK) { + return; + } + + int64_t nextSubTimeUs; + readBuffer(type, -1, &nextSubTimeUs); + + sp<ABuffer> buffer; + status_t dequeueStatus = packets->dequeueAccessUnit(&buffer); + if (dequeueStatus == OK) { + sp<AMessage> notify = dupNotify(); + notify->setInt32("what", what); + notify->setBuffer("buffer", buffer); + notify->post(); + + const int64_t delayUs = nextSubTimeUs - subTimeUs; + msg->post(delayUs < 0 ? 0 : delayUs); + } +} + sp<MetaData> NuPlayer::GenericSource::getFormatMeta(bool audio) { sp<MediaSource> source = audio ? mAudioTrack.mSource : mVideoTrack.mSource; @@ -357,27 +457,49 @@ status_t NuPlayer::GenericSource::dequeueAccessUnit( readBuffer(audio? MEDIA_TRACK_TYPE_AUDIO : MEDIA_TRACK_TYPE_VIDEO, -1ll); } - if (mSubtitleTrack.mSource == NULL) { + if (mSubtitleTrack.mSource == NULL && mTimedTextTrack.mSource == NULL) { return result; } - CHECK(mSubtitleTrack.mPackets != NULL); + if (mSubtitleTrack.mSource != NULL) { + CHECK(mSubtitleTrack.mPackets != NULL); + } + if (mTimedTextTrack.mSource != NULL) { + CHECK(mTimedTextTrack.mPackets != NULL); + } + if (result != OK) { - mSubtitleTrack.mPackets->clear(); - mFetchSubtitleDataGeneration++; + if (mSubtitleTrack.mSource != NULL) { + mSubtitleTrack.mPackets->clear(); + mFetchSubtitleDataGeneration++; + } + if (mTimedTextTrack.mSource != NULL) { + mTimedTextTrack.mPackets->clear(); + mFetchTimedTextDataGeneration++; + } return result; } int64_t timeUs; status_t eosResult; // ignored CHECK((*accessUnit)->meta()->findInt64("timeUs", &timeUs)); - if (!mSubtitleTrack.mPackets->hasBufferAvailable(&eosResult)) { + + if (mSubtitleTrack.mSource != NULL + && !mSubtitleTrack.mPackets->hasBufferAvailable(&eosResult)) { sp<AMessage> msg = new AMessage(kWhatFetchSubtitleData, id()); msg->setInt64("timeUs", timeUs); msg->setInt32("generation", mFetchSubtitleDataGeneration); msg->post(); } + if (mTimedTextTrack.mSource != NULL + && !mTimedTextTrack.mPackets->hasBufferAvailable(&eosResult)) { + sp<AMessage> msg = new AMessage(kWhatFetchTimedTextData, id()); + msg->setInt64("timeUs", timeUs); + msg->setInt32("generation", mFetchTimedTextDataGeneration); + msg->post(); + } + return result; } @@ -436,20 +558,53 @@ sp<AMessage> NuPlayer::GenericSource::getTrackInfo(size_t trackIndex) const { return format; } +ssize_t NuPlayer::GenericSource::getSelectedTrack(media_track_type type) const { + const Track *track = NULL; + switch (type) { + case MEDIA_TRACK_TYPE_VIDEO: + track = &mVideoTrack; + break; + case MEDIA_TRACK_TYPE_AUDIO: + track = &mAudioTrack; + break; + case MEDIA_TRACK_TYPE_TIMEDTEXT: + track = &mTimedTextTrack; + break; + case MEDIA_TRACK_TYPE_SUBTITLE: + track = &mSubtitleTrack; + break; + default: + break; + } + + if (track != NULL && track->mSource != NULL) { + return track->mIndex; + } + + return -1; +} + status_t NuPlayer::GenericSource::selectTrack(size_t trackIndex, bool select) { - ALOGV("selectTrack: %zu", trackIndex); + ALOGV("%s track: %zu", select ? "select" : "deselect", trackIndex); if (trackIndex >= mSources.size()) { return BAD_INDEX; } if (!select) { - if (mSubtitleTrack.mSource == NULL || trackIndex != mSubtitleTrack.mIndex) { + Track* track = NULL; + if (mSubtitleTrack.mSource != NULL && trackIndex == mSubtitleTrack.mIndex) { + track = &mSubtitleTrack; + mFetchSubtitleDataGeneration++; + } else if (mTimedTextTrack.mSource != NULL && trackIndex == mTimedTextTrack.mIndex) { + track = &mTimedTextTrack; + mFetchTimedTextDataGeneration++; + } + if (track == NULL) { return INVALID_OPERATION; } - mSubtitleTrack.mSource->stop(); - mSubtitleTrack.mSource = NULL; - mSubtitleTrack.mPackets->clear(); - mFetchSubtitleDataGeneration++; + track->mSource->stop(); + track->mSource = NULL; + track->mPackets->clear(); return OK; } @@ -458,22 +613,31 @@ status_t NuPlayer::GenericSource::selectTrack(size_t trackIndex, bool select) { const char *mime; CHECK(meta->findCString(kKeyMIMEType, &mime)); if (!strncasecmp(mime, "text/", 5)) { - if (mSubtitleTrack.mSource != NULL && mSubtitleTrack.mIndex == trackIndex) { + bool isSubtitle = strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP); + Track *track = isSubtitle ? &mSubtitleTrack : &mTimedTextTrack; + if (track->mSource != NULL && track->mIndex == trackIndex) { return OK; } - mSubtitleTrack.mIndex = trackIndex; - if (mSubtitleTrack.mSource != NULL) { - mSubtitleTrack.mSource->stop(); + track->mIndex = trackIndex; + if (track->mSource != NULL) { + track->mSource->stop(); } - mSubtitleTrack.mSource = mSources.itemAt(trackIndex); - mSubtitleTrack.mSource->start(); - if (mSubtitleTrack.mPackets == NULL) { - mSubtitleTrack.mPackets = new AnotherPacketSource(mSubtitleTrack.mSource->getFormat()); + track->mSource = mSources.itemAt(trackIndex); + track->mSource->start(); + if (track->mPackets == NULL) { + track->mPackets = new AnotherPacketSource(track->mSource->getFormat()); } else { - mSubtitleTrack.mPackets->clear(); + track->mPackets->clear(); + track->mPackets->setFormat(track->mSource->getFormat()); } - mFetchSubtitleDataGeneration++; + + if (isSubtitle) { + mFetchSubtitleDataGeneration++; + } else { + mFetchTimedTextDataGeneration++; + } + return OK; } else if (!strncasecmp(mime, "audio/", 6) || !strncasecmp(mime, "video/", 6)) { bool audio = !strncasecmp(mime, "audio/", 6); @@ -540,12 +704,19 @@ sp<ABuffer> NuPlayer::GenericSource::mediaBufferToABuffer( memcpy(abEnd, &numPageSamples, sizeof(numPageSamples)); } + sp<AMessage> meta = ab->meta(); + int64_t timeUs; CHECK(mb->meta_data()->findInt64(kKeyTime, &timeUs)); - - sp<AMessage> meta = ab->meta(); meta->setInt64("timeUs", timeUs); + if (trackType == MEDIA_TRACK_TYPE_TIMEDTEXT) { + const char *mime; + CHECK(mTimedTextTrack.mSource != NULL + && mTimedTextTrack.mSource->getFormat()->findCString(kKeyMIMEType, &mime)); + meta->setString("mime", mime); + } + int64_t durationUs; if (mb->meta_data()->findInt64(kKeyDuration, &durationUs)) { meta->setInt64("durationUs", durationUs); @@ -578,6 +749,9 @@ void NuPlayer::GenericSource::readBuffer( case MEDIA_TRACK_TYPE_SUBTITLE: track = &mSubtitleTrack; break; + case MEDIA_TRACK_TYPE_TIMEDTEXT: + track = &mTimedTextTrack; + break; default: TRESPASS(); } @@ -613,7 +787,9 @@ void NuPlayer::GenericSource::readBuffer( // formatChange && seeking: track whose source is changed during selection // formatChange && !seeking: track whose source is not changed during selection // !formatChange: normal seek - if ((seeking || formatChange) && trackType != MEDIA_TRACK_TYPE_SUBTITLE) { + if ((seeking || formatChange) + && (trackType == MEDIA_TRACK_TYPE_AUDIO + || trackType == MEDIA_TRACK_TYPE_VIDEO)) { ATSParser::DiscontinuityType type = formatChange ? (seeking ? ATSParser::DISCONTINUITY_FORMATCHANGE diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.h b/media/libmediaplayerservice/nuplayer/GenericSource.h index 4e25d55..44d690e 100644 --- a/media/libmediaplayerservice/nuplayer/GenericSource.h +++ b/media/libmediaplayerservice/nuplayer/GenericSource.h @@ -30,22 +30,19 @@ namespace android { struct AnotherPacketSource; struct ARTSPController; struct DataSource; +struct IMediaHTTPService; struct MediaSource; class MediaBuffer; struct NuPlayer::GenericSource : public NuPlayer::Source { - GenericSource( - const sp<AMessage> ¬ify, + GenericSource(const sp<AMessage> ¬ify, bool uidValid, uid_t uid); + + status_t setDataSource( const sp<IMediaHTTPService> &httpService, const char *url, - const KeyedVector<String8, String8> *headers, - bool isWidevine = false, - bool uidValid = false, - uid_t uid = 0); + const KeyedVector<String8, String8> *headers); - GenericSource( - const sp<AMessage> ¬ify, - int fd, int64_t offset, int64_t length); + status_t setDataSource(int fd, int64_t offset, int64_t length); virtual void prepareAsync(); @@ -58,6 +55,7 @@ struct NuPlayer::GenericSource : public NuPlayer::Source { virtual status_t getDuration(int64_t *durationUs); virtual size_t getTrackCount() const; virtual sp<AMessage> getTrackInfo(size_t trackIndex) const; + virtual ssize_t getSelectedTrack(media_track_type type) const; virtual status_t selectTrack(size_t trackIndex, bool select); virtual status_t seekTo(int64_t seekTimeUs); @@ -73,7 +71,9 @@ protected: private: enum { kWhatFetchSubtitleData, + kWhatFetchTimedTextData, kWhatSendSubtitleData, + kWhatSendTimedTextData, kWhatChangeAVSource, }; @@ -88,15 +88,35 @@ private: Track mAudioTrack; Track mVideoTrack; Track mSubtitleTrack; + Track mTimedTextTrack; int32_t mFetchSubtitleDataGeneration; + int32_t mFetchTimedTextDataGeneration; int64_t mDurationUs; bool mAudioIsVorbis; bool mIsWidevine; bool mUIDValid; uid_t mUID; - - void initFromDataSource(const sp<DataSource> &dataSource); + sp<IMediaHTTPService> mHTTPService; + AString mUri; + KeyedVector<String8, String8> mUriHeaders; + int mFd; + int64_t mOffset; + int64_t mLength; + + void resetDataSource(); + + status_t initFromDataSource( + const sp<DataSource> &dataSource, + const char *mime); + + void fetchTextData( + uint32_t what, media_track_type type, + int32_t curGen, sp<AnotherPacketSource> packets, sp<AMessage> msg); + + void sendTextData( + uint32_t what, media_track_type type, + int32_t curGen, sp<AnotherPacketSource> packets, sp<AMessage> msg); sp<ABuffer> mediaBufferToABuffer( MediaBuffer *mbuf, diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp index 58d0138..fe115c6 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp @@ -207,6 +207,7 @@ void NuPlayer::setDataSourceAsync( const sp<IMediaHTTPService> &httpService, const char *url, const KeyedVector<String8, String8> *headers) { + sp<AMessage> msg = new AMessage(kWhatSetDataSource, id()); size_t len = strlen(url); @@ -224,14 +225,21 @@ void NuPlayer::setDataSourceAsync( || strstr(url, ".sdp?"))) { source = new RTSPSource( notify, httpService, url, headers, mUIDValid, mUID, true); - } else if ((!strncasecmp(url, "widevine://", 11))) { - source = new GenericSource(notify, httpService, url, headers, - true /* isWidevine */, mUIDValid, mUID); - mSourceFlags |= Source::FLAG_SECURE; } else { - source = new GenericSource(notify, httpService, url, headers); - } + sp<GenericSource> genericSource = + new GenericSource(notify, mUIDValid, mUID); + // Don't set FLAG_SECURE on mSourceFlags here for widevine. + // The correct flags will be updated in Source::kWhatFlagsChanged + // handler when GenericSource is prepared. + + status_t err = genericSource->setDataSource(httpService, url, headers); + if (err == OK) { + source = genericSource; + } else { + ALOGE("Failed to set data source!"); + } + } msg->setObject("source", source); msg->post(); } @@ -241,7 +249,16 @@ void NuPlayer::setDataSourceAsync(int fd, int64_t offset, int64_t length) { sp<AMessage> notify = new AMessage(kWhatSourceNotify, id()); - sp<Source> source = new GenericSource(notify, fd, offset, length); + sp<GenericSource> source = + new GenericSource(notify, mUIDValid, mUID); + + status_t err = source->setDataSource(fd, offset, length); + + if (err != OK) { + ALOGE("Failed to set data source!"); + source = NULL; + } + msg->setObject("source", source); msg->post(); } @@ -350,17 +367,20 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { CHECK(mSource == NULL); + status_t err = OK; sp<RefBase> obj; CHECK(msg->findObject("source", &obj)); - - mSource = static_cast<Source *>(obj.get()); - - looper()->registerHandler(mSource); + if (obj != NULL) { + mSource = static_cast<Source *>(obj.get()); + looper()->registerHandler(mSource); + } else { + err = UNKNOWN_ERROR; + } CHECK(mDriver != NULL); sp<NuPlayerDriver> driver = mDriver.promote(); if (driver != NULL) { - driver->notifySetDataSourceCompleted(OK); + driver->notifySetDataSourceCompleted(err); } break; } @@ -749,6 +769,15 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { ALOGV("Mime \"%s\" mapped to audio_format 0x%x", mime.c_str(), audioFormat); + int32_t aacProfile = -1; + if (audioFormat == AUDIO_FORMAT_AAC + && format->findInt32("aac-profile", &aacProfile)) { + // Redefine AAC format as per aac profile + mapAACProfileToAudioFormat( + audioFormat, + aacProfile); + } + flags |= AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD; offloadInfo.duration_us = -1; diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp index 1808d07..503ce81 100644 --- a/media/libstagefright/ACodec.cpp +++ b/media/libstagefright/ACodec.cpp @@ -51,6 +51,48 @@ namespace android { +// OMX errors are directly mapped into status_t range if +// there is no corresponding MediaError status code. +// Use the statusFromOMXError(int32_t omxError) function. +// +// Currently this is a direct map. +// See frameworks/native/include/media/openmax/OMX_Core.h +// +// Vendor OMX errors from 0x90000000 - 0x9000FFFF +// Extension OMX errors from 0x8F000000 - 0x90000000 +// Standard OMX errors from 0x80001000 - 0x80001024 (0x80001024 current) +// + +// returns true if err is a recognized OMX error code. +// as OMX error is OMX_S32, this is an int32_t type +static inline bool isOMXError(int32_t err) { + return (ERROR_CODEC_MIN <= err && err <= ERROR_CODEC_MAX); +} + +// converts an OMX error to a status_t +static inline status_t statusFromOMXError(int32_t omxError) { + switch (omxError) { + case OMX_ErrorInvalidComponentName: + case OMX_ErrorComponentNotFound: + return NAME_NOT_FOUND; // can trigger illegal argument error for provided names. + default: + return isOMXError(omxError) ? omxError : 0; // no translation required + } +} + +// checks and converts status_t to a non-side-effect status_t +static inline status_t makeNoSideEffectStatus(status_t err) { + switch (err) { + // the following errors have side effects and may come + // from other code modules. Remap for safety reasons. + case INVALID_OPERATION: + case DEAD_OBJECT: + return UNKNOWN_ERROR; + default: + return err; + } +} + template<class T> static void InitOMXParams(T *params) { params->nSize = sizeof(T); @@ -1848,6 +1890,17 @@ status_t ACodec::setVideoPortFormatType( return err; } + // substitute back flexible color format to codec supported format + OMX_U32 flexibleEquivalent; + if (compressionFormat == OMX_VIDEO_CodingUnused && + isFlexibleColorFormat( + mOMX, mNode, format.eColorFormat, &flexibleEquivalent) && + colorFormat == flexibleEquivalent) { + ALOGI("[%s] using color format %#x in place of %#x", + mComponentName.c_str(), format.eColorFormat, colorFormat); + colorFormat = format.eColorFormat; + } + // The following assertion is violated by TI's video decoder. // CHECK_EQ(format.nIndex, index); @@ -2824,7 +2877,7 @@ void ACodec::processDeferredMessages() { } // static -void ACodec::describeDefaultColorFormat(DescribeColorFormatParams ¶ms) { +bool ACodec::describeDefaultColorFormat(DescribeColorFormatParams ¶ms) { MediaImage &image = params.sMediaImage; memset(&image, 0, sizeof(image)); @@ -2836,7 +2889,7 @@ void ACodec::describeDefaultColorFormat(DescribeColorFormatParams ¶ms) { if (params.nStride == 0 || params.nSliceHeight == 0) { ALOGW("cannot describe color format 0x%x = %d with stride=%u and sliceHeight=%u", fmt, fmt, params.nStride, params.nSliceHeight); - return; + return false; } image.mWidth = params.nFrameWidth; @@ -2848,7 +2901,7 @@ void ACodec::describeDefaultColorFormat(DescribeColorFormatParams ¶ms) { fmt != OMX_COLOR_FormatYUV420SemiPlanar && fmt != OMX_COLOR_FormatYUV420PackedSemiPlanar) { ALOGW("do not know color format 0x%x = %d", fmt, fmt); - return; + return false; } // set-up YUV format @@ -2898,6 +2951,67 @@ void ACodec::describeDefaultColorFormat(DescribeColorFormatParams ¶ms) { default: TRESPASS(); } + return true; +} + +// static +bool ACodec::describeColorFormat( + const sp<IOMX> &omx, IOMX::node_id node, + DescribeColorFormatParams &describeParams) +{ + OMX_INDEXTYPE describeColorFormatIndex; + if (omx->getExtensionIndex( + node, "OMX.google.android.index.describeColorFormat", + &describeColorFormatIndex) != OK || + omx->getParameter( + node, describeColorFormatIndex, + &describeParams, sizeof(describeParams)) != OK) { + return describeDefaultColorFormat(describeParams); + } + return describeParams.sMediaImage.mType != + MediaImage::MEDIA_IMAGE_TYPE_UNKNOWN; +} + +// static +bool ACodec::isFlexibleColorFormat( + const sp<IOMX> &omx, IOMX::node_id node, + uint32_t colorFormat, OMX_U32 *flexibleEquivalent) { + DescribeColorFormatParams describeParams; + InitOMXParams(&describeParams); + describeParams.eColorFormat = (OMX_COLOR_FORMATTYPE)colorFormat; + // reasonable dummy values + describeParams.nFrameWidth = 128; + describeParams.nFrameHeight = 128; + describeParams.nStride = 128; + describeParams.nSliceHeight = 128; + + CHECK(flexibleEquivalent != NULL); + + if (!describeColorFormat(omx, node, describeParams)) { + return false; + } + + const MediaImage &img = describeParams.sMediaImage; + if (img.mType == MediaImage::MEDIA_IMAGE_TYPE_YUV) { + if (img.mNumPlanes != 3 || + img.mPlane[img.Y].mHorizSubsampling != 1 || + img.mPlane[img.Y].mVertSubsampling != 1) { + return false; + } + + // YUV 420 + if (img.mPlane[img.U].mHorizSubsampling == 2 + && img.mPlane[img.U].mVertSubsampling == 2 + && img.mPlane[img.V].mHorizSubsampling == 2 + && img.mPlane[img.V].mVertSubsampling == 2) { + // possible flexible YUV420 format + if (img.mBitDepth <= 8) { + *flexibleEquivalent = OMX_COLOR_FormatYUV420Flexible; + return true; + } + } + } + return false; } status_t ACodec::getPortFormat(OMX_U32 portIndex, sp<AMessage> ¬ify) { @@ -2927,7 +3041,6 @@ status_t ACodec::getPortFormat(OMX_U32 portIndex, sp<AMessage> ¬ify) { notify->setInt32("slice-height", videoDef->nSliceHeight); notify->setInt32("color-format", videoDef->eColorFormat); - DescribeColorFormatParams describeParams; InitOMXParams(&describeParams); describeParams.eColorFormat = videoDef->eColorFormat; @@ -2936,17 +3049,7 @@ status_t ACodec::getPortFormat(OMX_U32 portIndex, sp<AMessage> ¬ify) { describeParams.nStride = videoDef->nStride; describeParams.nSliceHeight = videoDef->nSliceHeight; - OMX_INDEXTYPE describeColorFormatIndex; - if (mOMX->getExtensionIndex( - mNode, "OMX.google.android.index.describeColorFormat", - &describeColorFormatIndex) || - mOMX->getParameter( - mNode, describeColorFormatIndex, - &describeParams, sizeof(describeParams))) { - describeDefaultColorFormat(describeParams); - } - - if (describeParams.sMediaImage.mType != MediaImage::MEDIA_IMAGE_TYPE_UNKNOWN) { + if (describeColorFormat(mOMX, mNode, describeParams)) { notify->setBuffer( "image-data", ABuffer::CreateAsCopy( @@ -3268,8 +3371,18 @@ void ACodec::sendFormatChange(const sp<AMessage> &reply) { void ACodec::signalError(OMX_ERRORTYPE error, status_t internalError) { sp<AMessage> notify = mNotify->dup(); notify->setInt32("what", CodecBase::kWhatError); - notify->setInt32("omx-error", error); + ALOGE("signalError(omxError %#x, internalError %d)", error, internalError); + + if (internalError == UNKNOWN_ERROR) { // find better error code + const status_t omxStatus = statusFromOMXError(error); + if (omxStatus != 0) { + internalError = omxStatus; + } else { + ALOGW("Invalid OMX error %#x", error); + } + } notify->setInt32("err", internalError); + notify->setInt32("actionCode", ACTION_CODE_FATAL); // could translate from OMX error. notify->post(); } @@ -3493,6 +3606,7 @@ bool ACodec::BaseState::onMessageReceived(const sp<AMessage> &msg) { case ACodec::kWhatCreateInputSurface: case ACodec::kWhatSignalEndOfInputStream: { + // This may result in an app illegal state exception. ALOGE("Message 0x%x was not handled", msg->what()); mCodec->signalError(OMX_ErrorUndefined, INVALID_OPERATION); return true; @@ -3500,6 +3614,7 @@ bool ACodec::BaseState::onMessageReceived(const sp<AMessage> &msg) { case ACodec::kWhatOMXDied: { + // This will result in kFlagSawMediaServerDie handling in MediaCodec. ALOGE("OMX/mediaserver died, signalling error!"); mCodec->signalError(OMX_ErrorResourcesLost, DEAD_OBJECT); break; @@ -3598,7 +3713,13 @@ bool ACodec::BaseState::onOMXEvent( ALOGE("[%s] ERROR(0x%08lx)", mCodec->mComponentName.c_str(), data1); - mCodec->signalError((OMX_ERRORTYPE)data1); + // verify OMX component sends back an error we expect. + OMX_ERRORTYPE omxError = (OMX_ERRORTYPE)data1; + if (!isOMXError(omxError)) { + ALOGW("Invalid OMX error %#x", omxError); + omxError = OMX_ErrorUndefined; + } + mCodec->signalError(omxError); return true; } @@ -4041,7 +4162,7 @@ void ACodec::BaseState::onOutputBufferDrained(const sp<AMessage> &msg) { info->mGraphicBuffer.get(), -1)) == OK) { info->mStatus = BufferInfo::OWNED_BY_NATIVE_WINDOW; } else { - mCodec->signalError(OMX_ErrorUndefined, err); + mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err)); info->mStatus = BufferInfo::OWNED_BY_US; } } else { @@ -4413,7 +4534,7 @@ bool ACodec::LoadedState::onConfigureComponent( ALOGE("[%s] configureCodec returning error %d", mCodec->mComponentName.c_str(), err); - mCodec->signalError(OMX_ErrorUndefined, err); + mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err)); return false; } @@ -4560,7 +4681,7 @@ void ACodec::LoadedToIdleState::stateEntered() { "(error 0x%08x)", err); - mCodec->signalError(OMX_ErrorUndefined, err); + mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err)); mCodec->changeState(mCodec->mLoadedState); } @@ -5088,7 +5209,7 @@ bool ACodec::OutputPortSettingsChangedState::onOMXEvent( "port reconfiguration (error 0x%08x)", err); - mCodec->signalError(OMX_ErrorUndefined, err); + mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err)); // This is technically not correct, but appears to be // the only way to free the component instance. diff --git a/media/libstagefright/AudioSource.cpp b/media/libstagefright/AudioSource.cpp index a67fabe..804f131 100644 --- a/media/libstagefright/AudioSource.cpp +++ b/media/libstagefright/AudioSource.cpp @@ -155,12 +155,12 @@ status_t AudioSource::reset() { } mStarted = false; + mFrameAvailableCondition.signal(); + mRecord->stop(); waitOutstandingEncodingFrames_l(); releaseQueuedFrames_l(); - mFrameAvailableCondition.signal(); - return OK; } diff --git a/media/libstagefright/DataSource.cpp b/media/libstagefright/DataSource.cpp index 6e0f37a..908cdca 100644 --- a/media/libstagefright/DataSource.cpp +++ b/media/libstagefright/DataSource.cpp @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +//#define LOG_NDEBUG 0 +#define LOG_TAG "DataSource" #include "include/AMRExtractor.h" @@ -33,6 +35,7 @@ #include <media/IMediaHTTPConnection.h> #include <media/IMediaHTTPService.h> +#include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AMessage.h> #include <media/stagefright/DataSource.h> #include <media/stagefright/DataURISource.h> @@ -182,7 +185,12 @@ void DataSource::RegisterDefaultSniffers() { sp<DataSource> DataSource::CreateFromURI( const sp<IMediaHTTPService> &httpService, const char *uri, - const KeyedVector<String8, String8> *headers) { + const KeyedVector<String8, String8> *headers, + AString *sniffedMIME) { + if (sniffedMIME != NULL) { + *sniffedMIME = ""; + } + bool isWidevine = !strncasecmp("widevine://", uri, 11); sp<DataSource> source; @@ -202,6 +210,7 @@ sp<DataSource> DataSource::CreateFromURI( } if (httpSource->connect(uri, headers) != OK) { + ALOGE("Failed to connect http source!"); return NULL; } @@ -214,9 +223,76 @@ sp<DataSource> DataSource::CreateFromURI( ©, &cacheConfig, &disconnectAtHighwatermark); } - source = new NuCachedSource2( + sp<NuCachedSource2> cachedSource = new NuCachedSource2( httpSource, cacheConfig.isEmpty() ? NULL : cacheConfig.string()); + + String8 contentType = httpSource->getMIMEType(); + + if (strncasecmp(contentType.string(), "audio/", 6)) { + // We're not doing this for streams that appear to be audio-only + // streams to ensure that even low bandwidth streams start + // playing back fairly instantly. + + // We're going to prefill the cache before trying to instantiate + // the extractor below, as the latter is an operation that otherwise + // could block on the datasource for a significant amount of time. + // During that time we'd be unable to abort the preparation phase + // without this prefill. + + // Initially make sure we have at least 192 KB for the sniff + // to complete without blocking. + static const size_t kMinBytesForSniffing = 192 * 1024; + + off64_t metaDataSize = -1ll; + for (;;) { + status_t finalStatus; + size_t cachedDataRemaining = + cachedSource->approxDataRemaining(&finalStatus); + + if (finalStatus != OK || (metaDataSize >= 0 + && (off64_t)cachedDataRemaining >= metaDataSize)) { + ALOGV("stop caching, status %d, " + "metaDataSize %lld, cachedDataRemaining %zu", + finalStatus, metaDataSize, cachedDataRemaining); + break; + } + + ALOGV("now cached %zu bytes of data", cachedDataRemaining); + + if (metaDataSize < 0 + && cachedDataRemaining >= kMinBytesForSniffing) { + String8 tmp; + float confidence; + sp<AMessage> meta; + if (!cachedSource->sniff(&tmp, &confidence, &meta)) { + return NULL; + } + + // We successfully identified the file's extractor to + // be, remember this mime type so we don't have to + // sniff it again when we call MediaExtractor::Create() + if (sniffedMIME != NULL) { + *sniffedMIME = tmp.string(); + } + + if (meta == NULL + || !meta->findInt64("meta-data-size", + reinterpret_cast<int64_t*>(&metaDataSize))) { + metaDataSize = kDefaultMetaSize; + } + + if (metaDataSize < 0ll) { + ALOGE("invalid metaDataSize = %lld bytes", metaDataSize); + return NULL; + } + } + + usleep(200000); + } + } + + source = cachedSource; } else { // We do not want that prefetching, caching, datasource wrapper // in the widevine:// case. diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp index 7c02959..42691b9 100644 --- a/media/libstagefright/MediaCodec.cpp +++ b/media/libstagefright/MediaCodec.cpp @@ -113,24 +113,26 @@ void MediaCodec::BatteryNotifier::noteStopAudio() { } // static sp<MediaCodec> MediaCodec::CreateByType( - const sp<ALooper> &looper, const char *mime, bool encoder) { + const sp<ALooper> &looper, const char *mime, bool encoder, status_t *err) { sp<MediaCodec> codec = new MediaCodec(looper); - if (codec->init(mime, true /* nameIsType */, encoder) != OK) { - return NULL; - } - return codec; + const status_t ret = codec->init(mime, true /* nameIsType */, encoder); + if (err != NULL) { + *err = ret; + } + return ret == OK ? codec : NULL; // NULL deallocates codec. } // static sp<MediaCodec> MediaCodec::CreateByComponentName( - const sp<ALooper> &looper, const char *name) { + const sp<ALooper> &looper, const char *name, status_t *err) { sp<MediaCodec> codec = new MediaCodec(looper); - if (codec->init(name, false /* nameIsType */, false /* encoder */) != OK) { - return NULL; - } - return codec; + const status_t ret = codec->init(name, false /* nameIsType */, false /* encoder */); + if (err != NULL) { + *err = ret; + } + return ret == OK ? codec : NULL; // NULL deallocates codec. } MediaCodec::MediaCodec(const sp<ALooper> &looper) @@ -139,6 +141,7 @@ MediaCodec::MediaCodec(const sp<ALooper> &looper) mCodec(NULL), mReplyID(0), mFlags(0), + mStickyError(OK), mSoftRenderer(NULL), mBatteryStatNotified(false), mIsVideo(false), @@ -195,16 +198,16 @@ status_t MediaCodec::init(const char *name, bool nameIsType, bool encoder) { if (tmp.endsWith(".secure")) { tmp.erase(tmp.size() - 7, 7); } - const MediaCodecList *mcl = MediaCodecList::getInstance(); + const sp<IMediaCodecList> mcl = MediaCodecList::getInstance(); ssize_t codecIdx = mcl->findCodecByName(tmp.c_str()); if (codecIdx >= 0) { - Vector<AString> types; - if (mcl->getSupportedTypes(codecIdx, &types) == OK) { - for (size_t i = 0; i < types.size(); i++) { - if (types[i].startsWith("video/")) { - needDedicatedLooper = true; - break; - } + const sp<MediaCodecInfo> info = mcl->getCodecInfo(codecIdx); + Vector<AString> mimes; + info->getSupportedMimes(&mimes); + for (size_t i = 0; i < mimes.size(); i++) { + if (mimes[i].startsWith("video/")) { + needDedicatedLooper = true; + break; } } } @@ -330,6 +333,7 @@ status_t MediaCodec::reset() { mLooper->unregisterHandler(id()); mFlags = 0; // clear all flags + mStickyError = OK; // reset state not reset by setState(UNINITIALIZED) mReplyID = 0; @@ -620,10 +624,12 @@ void MediaCodec::cancelPendingDequeueOperations() { bool MediaCodec::handleDequeueInputBuffer(uint32_t replyID, bool newRequest) { if (!isExecuting() || (mFlags & kFlagIsAsync) - || (mFlags & kFlagStickyError) || (newRequest && (mFlags & kFlagDequeueInputPending))) { PostReplyWithError(replyID, INVALID_OPERATION); return true; + } else if (mFlags & kFlagStickyError) { + PostReplyWithError(replyID, getStickyError()); + return true; } ssize_t index = dequeuePortBuffer(kPortIndexInput); @@ -644,9 +650,10 @@ bool MediaCodec::handleDequeueOutputBuffer(uint32_t replyID, bool newRequest) { sp<AMessage> response = new AMessage; if (!isExecuting() || (mFlags & kFlagIsAsync) - || (mFlags & kFlagStickyError) || (newRequest && (mFlags & kFlagDequeueOutputPending))) { response->setInt32("err", INVALID_OPERATION); + } else if (mFlags & kFlagStickyError) { + response->setInt32("err", getStickyError()); } else if (mFlags & kFlagOutputBuffersChanged) { response->setInt32("err", INFO_OUTPUT_BUFFERS_CHANGED); mFlags &= ~kFlagOutputBuffersChanged; @@ -705,16 +712,12 @@ void MediaCodec::onMessageReceived(const sp<AMessage> &msg) { switch (what) { case CodecBase::kWhatError: { - int32_t omxError, internalError; - CHECK(msg->findInt32("omx-error", &omxError)); - CHECK(msg->findInt32("err", &internalError)); - - ALOGE("Codec reported an error. " - "(omx error 0x%08x, internalError %d)", - omxError, internalError); + int32_t err, actionCode; + CHECK(msg->findInt32("err", &err)); + CHECK(msg->findInt32("actionCode", &actionCode)); - if (omxError == OMX_ErrorResourcesLost - && internalError == DEAD_OBJECT) { + ALOGE("Codec reported err %#x, actionCode %d", err, actionCode); + if (err == DEAD_OBJECT) { mFlags |= kFlagSawMediaServerDie; } @@ -774,15 +777,24 @@ void MediaCodec::onMessageReceived(const sp<AMessage> &msg) { { sendErrorReponse = false; - mFlags |= kFlagStickyError; + setStickyError(err); postActivityNotificationIfPossible(); cancelPendingDequeueOperations(); if (mFlags & kFlagIsAsync) { - onError(omxError, 0); + onError(err, actionCode); + } + switch (actionCode) { + case ACTION_CODE_TRANSIENT: + break; + case ACTION_CODE_RECOVERABLE: + setState(INITIALIZED); + break; + default: + setState(UNINITIALIZED); + break; } - setState(UNINITIALIZED); break; } @@ -790,19 +802,32 @@ void MediaCodec::onMessageReceived(const sp<AMessage> &msg) { { sendErrorReponse = false; - mFlags |= kFlagStickyError; + setStickyError(err); postActivityNotificationIfPossible(); + // actionCode in an uninitialized state is always fatal. + if (mState == UNINITIALIZED) { + actionCode = ACTION_CODE_FATAL; + } if (mFlags & kFlagIsAsync) { - onError(omxError, 0); + onError(err, actionCode); + } + switch (actionCode) { + case ACTION_CODE_TRANSIENT: + break; + case ACTION_CODE_RECOVERABLE: + setState(INITIALIZED); + break; + default: + setState(UNINITIALIZED); + break; } - setState(UNINITIALIZED); break; } } if (sendErrorReponse) { - PostReplyWithError(mReplyID, UNKNOWN_ERROR); + PostReplyWithError(mReplyID, err); } break; } @@ -1009,7 +1034,7 @@ void MediaCodec::onMessageReceived(const sp<AMessage> &msg) { ALOGE("queueCSDInputBuffer failed w/ error %d", err); - mFlags |= kFlagStickyError; + setStickyError(err); postActivityNotificationIfPossible(); cancelPendingDequeueOperations(); @@ -1401,9 +1426,12 @@ void MediaCodec::onMessageReceived(const sp<AMessage> &msg) { uint32_t replyID; CHECK(msg->senderAwaitsResponse(&replyID)); - if (!isExecuting() || (mFlags & kFlagStickyError)) { + if (!isExecuting()) { PostReplyWithError(replyID, INVALID_OPERATION); break; + } else if (mFlags & kFlagStickyError) { + PostReplyWithError(replyID, getStickyError()); + break; } status_t err = onQueueInputBuffer(msg); @@ -1472,9 +1500,12 @@ void MediaCodec::onMessageReceived(const sp<AMessage> &msg) { uint32_t replyID; CHECK(msg->senderAwaitsResponse(&replyID)); - if (!isExecuting() || (mFlags & kFlagStickyError)) { + if (!isExecuting()) { PostReplyWithError(replyID, INVALID_OPERATION); break; + } else if (mFlags & kFlagStickyError) { + PostReplyWithError(replyID, getStickyError()); + break; } status_t err = onReleaseOutputBuffer(msg); @@ -1488,9 +1519,12 @@ void MediaCodec::onMessageReceived(const sp<AMessage> &msg) { uint32_t replyID; CHECK(msg->senderAwaitsResponse(&replyID)); - if (!isExecuting() || (mFlags & kFlagStickyError)) { + if (!isExecuting()) { PostReplyWithError(replyID, INVALID_OPERATION); break; + } else if (mFlags & kFlagStickyError) { + PostReplyWithError(replyID, getStickyError()); + break; } mReplyID = replyID; @@ -1503,10 +1537,12 @@ void MediaCodec::onMessageReceived(const sp<AMessage> &msg) { uint32_t replyID; CHECK(msg->senderAwaitsResponse(&replyID)); - if (!isExecuting() || (mFlags & kFlagIsAsync) - || (mFlags & kFlagStickyError)) { + if (!isExecuting() || (mFlags & kFlagIsAsync)) { PostReplyWithError(replyID, INVALID_OPERATION); break; + } else if (mFlags & kFlagStickyError) { + PostReplyWithError(replyID, getStickyError()); + break; } int32_t portIndex; @@ -1535,9 +1571,12 @@ void MediaCodec::onMessageReceived(const sp<AMessage> &msg) { uint32_t replyID; CHECK(msg->senderAwaitsResponse(&replyID)); - if (!isExecuting() || (mFlags & kFlagStickyError)) { + if (!isExecuting()) { PostReplyWithError(replyID, INVALID_OPERATION); break; + } else if (mFlags & kFlagStickyError) { + PostReplyWithError(replyID, getStickyError()); + break; } mReplyID = replyID; @@ -1561,10 +1600,12 @@ void MediaCodec::onMessageReceived(const sp<AMessage> &msg) { if ((mState != CONFIGURED && mState != STARTING && mState != STARTED && mState != FLUSHING && mState != FLUSHED) - || (mFlags & kFlagStickyError) || format == NULL) { PostReplyWithError(replyID, INVALID_OPERATION); break; + } else if (mFlags & kFlagStickyError) { + PostReplyWithError(replyID, getStickyError()); + break; } sp<AMessage> response = new AMessage; @@ -1687,6 +1728,7 @@ void MediaCodec::setState(State newState) { mFlags &= ~kFlagIsEncoder; mFlags &= ~kFlagGatherCodecSpecificData; mFlags &= ~kFlagIsAsync; + mStickyError = OK; mActivityNotify.clear(); mCallback.clear(); diff --git a/media/libstagefright/MediaCodecList.cpp b/media/libstagefright/MediaCodecList.cpp index d021533..7f8b7f5 100644 --- a/media/libstagefright/MediaCodecList.cpp +++ b/media/libstagefright/MediaCodecList.cpp @@ -18,13 +18,19 @@ #define LOG_TAG "MediaCodecList" #include <utils/Log.h> -#include <media/stagefright/MediaCodecList.h> +#include <binder/IServiceManager.h> + +#include <media/IMediaCodecList.h> +#include <media/IMediaPlayerService.h> +#include <media/MediaCodecInfo.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/MediaCodecList.h> #include <media/stagefright/MediaErrors.h> #include <media/stagefright/OMXClient.h> #include <media/stagefright/OMXCodec.h> + #include <utils/threads.h> #include <libexpat/expat.h> @@ -33,18 +39,47 @@ namespace android { static Mutex sInitMutex; +static MediaCodecList *gCodecList = NULL; + // static -MediaCodecList *MediaCodecList::sCodecList; +sp<IMediaCodecList> MediaCodecList::sCodecList; // static -const MediaCodecList *MediaCodecList::getInstance() { +sp<IMediaCodecList> MediaCodecList::getLocalInstance() { Mutex::Autolock autoLock(sInitMutex); - if (sCodecList == NULL) { - sCodecList = new MediaCodecList; + if (gCodecList == NULL) { + gCodecList = new MediaCodecList; + if (gCodecList->initCheck() == OK) { + sCodecList = gCodecList; + } } - return sCodecList->initCheck() == OK ? sCodecList : NULL; + return sCodecList; +} + +static Mutex sRemoteInitMutex; + +sp<IMediaCodecList> MediaCodecList::sRemoteList; + +// static +sp<IMediaCodecList> MediaCodecList::getInstance() { + Mutex::Autolock _l(sRemoteInitMutex); + if (sRemoteList == NULL) { + sp<IBinder> binder = + defaultServiceManager()->getService(String16("media.player")); + sp<IMediaPlayerService> service = + interface_cast<IMediaPlayerService>(binder); + if (service.get() != NULL) { + sRemoteList = service->getCodecList(); + } + + if (sRemoteList == NULL) { + // if failed to get remote list, create local list + sRemoteList = getLocalInstance(); + } + } + return sRemoteList; } MediaCodecList::MediaCodecList() @@ -59,37 +94,69 @@ void MediaCodecList::parseTopLevelXMLFile(const char *codecs_xml) { mHrefBase = AString(codecs_xml, href_base_end - codecs_xml + 1); } - mInitCheck = OK; + mInitCheck = OK; // keeping this here for safety mCurrentSection = SECTION_TOPLEVEL; mDepth = 0; + OMXClient client; + mInitCheck = client.connect(); + if (mInitCheck != OK) { + return; + } + mOMX = client.interface(); parseXMLFile(codecs_xml); + mOMX.clear(); if (mInitCheck != OK) { mCodecInfos.clear(); - mCodecQuirks.clear(); return; } for (size_t i = mCodecInfos.size(); i-- > 0;) { - CodecInfo *info = &mCodecInfos.editItemAt(i); + const MediaCodecInfo &info = *mCodecInfos.itemAt(i).get(); - if (info->mTypes == 0) { + if (info.mCaps.size() == 0) { // No types supported by this component??? ALOGW("Component %s does not support any type of media?", - info->mName.c_str()); + info.mName.c_str()); mCodecInfos.removeAt(i); #if LOG_NDEBUG == 0 } else { - for (size_t type_ix = 0; type_ix < mTypes.size(); ++type_ix) { - uint32_t typeMask = 1ul << mTypes.valueAt(type_ix); - if (info->mTypes & typeMask) { - AString mime = mTypes.keyAt(type_ix); - uint32_t bit = mTypes.valueAt(type_ix); - - ALOGV("%s codec info for %s: %s", info->mName.c_str(), mime.c_str(), - info->mCaps.editValueFor(bit)->debugString().c_str()); + for (size_t type_ix = 0; type_ix < info.mCaps.size(); ++type_ix) { + AString mime = info.mCaps.keyAt(type_ix); + const sp<MediaCodecInfo::Capabilities> &caps = info.mCaps.valueAt(type_ix); + + ALOGV("%s codec info for %s: %s", info.mName.c_str(), mime.c_str(), + caps->getDetails()->debugString().c_str()); + ALOGV(" flags=%d", caps->getFlags()); + { + Vector<uint32_t> colorFormats; + caps->getSupportedColorFormats(&colorFormats); + AString nice; + for (size_t ix = 0; ix < colorFormats.size(); ix++) { + if (ix > 0) { + nice.append(", "); + } + nice.append(colorFormats.itemAt(ix)); + } + ALOGV(" colors=[%s]", nice.c_str()); + } + { + Vector<MediaCodecInfo::ProfileLevel> profileLevels; + caps->getSupportedProfileLevels(&profileLevels); + AString nice; + for (size_t ix = 0; ix < profileLevels.size(); ix++) { + if (ix > 0) { + nice.append(", "); + } + const MediaCodecInfo::ProfileLevel &pl = + profileLevels.itemAt(ix); + nice.append(pl.mProfile); + nice.append("/"); + nice.append(pl.mLevel); + } + ALOGV(" levels=[%s]", nice.c_str()); } } #endif @@ -294,9 +361,8 @@ void MediaCodecList::startElementHandler( case SECTION_DECODER_TYPE: case SECTION_ENCODER_TYPE: { - CodecInfo *info = &mCodecInfos.editItemAt(mCodecInfos.size() - 1); // ignore limits and features specified outside of type - bool outside = !inType && info->mSoleType == 0; + bool outside = !inType && !mCurrentInfo->mHasSoleMime; if (outside && (!strcmp(name, "Limit") || !strcmp(name, "Feature"))) { ALOGW("ignoring %s specified outside of a Type", name); } else if (!strcmp(name, "Limit")) { @@ -344,8 +410,7 @@ void MediaCodecList::endElementHandler(const char *name) { (mCurrentSection == SECTION_DECODER_TYPE ? SECTION_DECODER : SECTION_ENCODER); - CodecInfo *info = &mCodecInfos.editItemAt(mCodecInfos.size() - 1); - info->mCurrentCaps = NULL; + mCurrentInfo->complete(); } break; } @@ -354,9 +419,8 @@ void MediaCodecList::endElementHandler(const char *name) { { if (!strcmp(name, "MediaCodec")) { mCurrentSection = SECTION_DECODERS; - - CodecInfo *info = &mCodecInfos.editItemAt(mCodecInfos.size() - 1); - info->mCurrentCaps = NULL; + mCurrentInfo->complete(); + mCurrentInfo = NULL; } break; } @@ -365,9 +429,8 @@ void MediaCodecList::endElementHandler(const char *name) { { if (!strcmp(name, "MediaCodec")) { mCurrentSection = SECTION_ENCODERS; - - CodecInfo *info = &mCodecInfos.editItemAt(mCodecInfos.size() - 1); - info->mCurrentCaps = NULL; + mCurrentInfo->complete();; + mCurrentInfo = NULL; } break; } @@ -418,28 +481,27 @@ status_t MediaCodecList::addMediaCodecFromAttributes( return -EINVAL; } - addMediaCodec(encoder, name, type); - - return OK; + mCurrentInfo = new MediaCodecInfo(name, encoder, type); + mCodecInfos.push_back(mCurrentInfo); + return initializeCapabilities(type); } -void MediaCodecList::addMediaCodec( - bool encoder, const char *name, const char *type) { - mCodecInfos.push(); - CodecInfo *info = &mCodecInfos.editItemAt(mCodecInfos.size() - 1); - info->mName = name; - info->mIsEncoder = encoder; - info->mSoleType = 0; - info->mTypes = 0; - info->mQuirks = 0; - info->mCurrentCaps = NULL; - - if (type != NULL) { - addType(type); - // if type was specified in attributes, we do not allow - // subsequent types - info->mSoleType = info->mTypes; +status_t MediaCodecList::initializeCapabilities(const char *type) { + ALOGV("initializeCapabilities %s:%s", + mCurrentInfo->mName.c_str(), type); + + CodecCapabilities caps; + status_t err = QueryCodec( + mOMX, + mCurrentInfo->mName.c_str(), + type, + mCurrentInfo->mIsEncoder, + &caps); + if (err != OK) { + return err; } + + return mCurrentInfo->initializeCapabilities(caps); } status_t MediaCodecList::addQuirk(const char **attrs) { @@ -464,36 +526,13 @@ status_t MediaCodecList::addQuirk(const char **attrs) { return -EINVAL; } - uint32_t bit; - ssize_t index = mCodecQuirks.indexOfKey(name); - if (index < 0) { - bit = mCodecQuirks.size(); - - if (bit == 32) { - ALOGW("Too many distinct quirk names in configuration."); - return OK; - } - - mCodecQuirks.add(name, bit); - } else { - bit = mCodecQuirks.valueAt(index); - } - - CodecInfo *info = &mCodecInfos.editItemAt(mCodecInfos.size() - 1); - info->mQuirks |= 1ul << bit; - + mCurrentInfo->addQuirk(name); return OK; } status_t MediaCodecList::addTypeFromAttributes(const char **attrs) { const char *name = NULL; - CodecInfo *info = &mCodecInfos.editItemAt(mCodecInfos.size() - 1); - if (info->mSoleType != 0) { - ALOGE("Codec '%s' already had its type specified", info->mName.c_str()); - return -EINVAL; - } - size_t i = 0; while (attrs[i] != NULL) { if (!strcmp(attrs[i], "name")) { @@ -513,54 +552,47 @@ status_t MediaCodecList::addTypeFromAttributes(const char **attrs) { return -EINVAL; } - addType(name); - - return OK; -} - -void MediaCodecList::addType(const char *name) { - uint32_t bit; - ssize_t index = mTypes.indexOfKey(name); - if (index < 0) { - bit = mTypes.size(); - - if (bit == 32) { - ALOGW("Too many distinct type names in configuration."); - return; - } - - mTypes.add(name, bit); - } else { - bit = mTypes.valueAt(index); + status_t ret = mCurrentInfo->addMime(name); + if (ret == OK) { + ret = initializeCapabilities(name); } - - CodecInfo *info = &mCodecInfos.editItemAt(mCodecInfos.size() - 1); - info->mTypes |= 1ul << bit; - if (info->mCaps.indexOfKey(bit) < 0) { - AMessage *msg = new AMessage(); - info->mCaps.add(bit, msg); - } - info->mCurrentCaps = info->mCaps.editValueFor(bit); + return ret; } +// legacy method for non-advanced codecs ssize_t MediaCodecList::findCodecByType( const char *type, bool encoder, size_t startIndex) const { - ssize_t typeIndex = mTypes.indexOfKey(type); + static const char *advancedFeatures[] = { + "feature-secure-playback", + "feature-tunneled-playback", + }; - if (typeIndex < 0) { - return -ENOENT; - } + size_t numCodecs = mCodecInfos.size(); + for (; startIndex < numCodecs; ++startIndex) { + const MediaCodecInfo &info = *mCodecInfos.itemAt(startIndex).get(); - uint32_t typeMask = 1ul << mTypes.valueAt(typeIndex); + if (info.isEncoder() != encoder) { + continue; + } + sp<MediaCodecInfo::Capabilities> capabilities = info.getCapabilitiesFor(type); + if (capabilities == NULL) { + continue; + } + const sp<AMessage> &details = capabilities->getDetails(); - while (startIndex < mCodecInfos.size()) { - const CodecInfo &info = mCodecInfos.itemAt(startIndex); + int32_t required; + bool isAdvanced = false; + for (size_t ix = 0; ix < ARRAY_SIZE(advancedFeatures); ix++) { + if (details->findInt32(advancedFeatures[ix], &required) && + required != 0) { + isAdvanced = true; + break; + } + } - if (info.mIsEncoder == encoder && (info.mTypes & typeMask)) { + if (!isAdvanced) { return startIndex; } - - ++startIndex; } return -ENOENT; @@ -616,12 +648,11 @@ status_t MediaCodecList::addLimit(const char **attrs) { return -EINVAL; } - CodecInfo *info = &mCodecInfos.editItemAt(mCodecInfos.size() - 1); - // size, blocks, bitrate, frame-rate, blocks-per-second, aspect-ratio: range // quality: range + default + [scale] // complexity: range + default bool found; + if (name == "aspect-ratio" || name == "bitrate" || name == "block-count" || name == "blocks-per-second" || name == "complexity" || name == "frame-rate" || name == "quality" || name == "size") { @@ -672,16 +703,16 @@ status_t MediaCodecList::addLimit(const char **attrs) { name = in_; } if (name == "quality") { - info->mCurrentCaps->setString("quality-scale", scale); + mCurrentInfo->addDetail("quality-scale", scale); } if (name == "quality" || name == "complexity") { AString tag = name; tag.append("-default"); - info->mCurrentCaps->setString(tag.c_str(), def); + mCurrentInfo->addDetail(tag, def); } AString tag = name; tag.append("-range"); - info->mCurrentCaps->setString(tag.c_str(), range); + mCurrentInfo->addDetail(tag, range); } else { AString max, value, ranges; if (msg->contains("default")) { @@ -708,13 +739,13 @@ status_t MediaCodecList::addLimit(const char **attrs) { if (max.size()) { AString tag = "max-"; tag.append(name); - info->mCurrentCaps->setString(tag.c_str(), max); + mCurrentInfo->addDetail(tag, max); } else if (value.size()) { - info->mCurrentCaps->setString(name.c_str(), value); + mCurrentInfo->addDetail(name, value); } else if (ranges.size()) { AString tag = name; tag.append("-ranges"); - info->mCurrentCaps->setString(tag.c_str(), ranges); + mCurrentInfo->addDetail(tag, ranges); } else { ALOGW("Ignoring unrecognized limit '%s'", name.c_str()); } @@ -769,16 +800,13 @@ status_t MediaCodecList::addFeature(const char **attrs) { return -EINVAL; } - CodecInfo *info = &mCodecInfos.editItemAt(mCodecInfos.size() - 1); - AString tag = "feature-"; - tag.append(name); - info->mCurrentCaps->setInt32(tag.c_str(), (required == 1) || (optional == 0)); + mCurrentInfo->addFeature(name, (required == 1) || (optional == 0)); return OK; } ssize_t MediaCodecList::findCodecByName(const char *name) const { for (size_t i = 0; i < mCodecInfos.size(); ++i) { - const CodecInfo &info = mCodecInfos.itemAt(i); + const MediaCodecInfo &info = *mCodecInfos.itemAt(i).get(); if (info.mName == name) { return i; @@ -792,121 +820,4 @@ size_t MediaCodecList::countCodecs() const { return mCodecInfos.size(); } -const char *MediaCodecList::getCodecName(size_t index) const { - if (index >= mCodecInfos.size()) { - return NULL; - } - - const CodecInfo &info = mCodecInfos.itemAt(index); - return info.mName.c_str(); -} - -bool MediaCodecList::isEncoder(size_t index) const { - if (index >= mCodecInfos.size()) { - return false; - } - - const CodecInfo &info = mCodecInfos.itemAt(index); - return info.mIsEncoder; -} - -bool MediaCodecList::codecHasQuirk( - size_t index, const char *quirkName) const { - if (index >= mCodecInfos.size()) { - return false; - } - - const CodecInfo &info = mCodecInfos.itemAt(index); - - if (info.mQuirks != 0) { - ssize_t index = mCodecQuirks.indexOfKey(quirkName); - if (index >= 0 && info.mQuirks & (1ul << mCodecQuirks.valueAt(index))) { - return true; - } - } - - return false; -} - -status_t MediaCodecList::getSupportedTypes( - size_t index, Vector<AString> *types) const { - types->clear(); - - if (index >= mCodecInfos.size()) { - return -ERANGE; - } - - const CodecInfo &info = mCodecInfos.itemAt(index); - - for (size_t i = 0; i < mTypes.size(); ++i) { - uint32_t typeMask = 1ul << mTypes.valueAt(i); - - if (info.mTypes & typeMask) { - types->push(mTypes.keyAt(i)); - } - } - - return OK; -} - -status_t MediaCodecList::getCodecCapabilities( - size_t index, const char *type, - Vector<ProfileLevel> *profileLevels, - Vector<uint32_t> *colorFormats, - uint32_t *flags, - sp<AMessage> *capabilities) const { - profileLevels->clear(); - colorFormats->clear(); - - if (index >= mCodecInfos.size()) { - return -ERANGE; - } - - const CodecInfo &info = mCodecInfos.itemAt(index); - - ssize_t typeIndex = mTypes.indexOfKey(type); - if (typeIndex < 0) { - return -EINVAL; - } - // essentially doing valueFor without the CHECK abort - typeIndex = mTypes.valueAt(typeIndex); - - OMXClient client; - status_t err = client.connect(); - if (err != OK) { - return err; - } - - CodecCapabilities caps; - err = QueryCodec( - client.interface(), - info.mName.c_str(), type, info.mIsEncoder, &caps); - - if (err != OK) { - return err; - } - - for (size_t i = 0; i < caps.mProfileLevels.size(); ++i) { - const CodecProfileLevel &src = caps.mProfileLevels.itemAt(i); - - ProfileLevel profileLevel; - profileLevel.mProfile = src.mProfile; - profileLevel.mLevel = src.mLevel; - profileLevels->push(profileLevel); - } - - for (size_t i = 0; i < caps.mColorFormats.size(); ++i) { - colorFormats->push(caps.mColorFormats.itemAt(i)); - } - - *flags = caps.mFlags; - - // TODO this check will be removed once JNI side is merged - if (capabilities != NULL) { - *capabilities = info.mCaps.valueFor(typeIndex); - } - - return OK; -} - } // namespace android diff --git a/media/libstagefright/MediaCodecSource.cpp b/media/libstagefright/MediaCodecSource.cpp index 9868ecf..1a80dcc 100644 --- a/media/libstagefright/MediaCodecSource.cpp +++ b/media/libstagefright/MediaCodecSource.cpp @@ -54,7 +54,7 @@ struct MediaCodecSource::Puller : public AHandler { Puller(const sp<MediaSource> &source); status_t start(const sp<MetaData> &meta, const sp<AMessage> ¬ify); - void stopAsync(); + void stop(); void pause(); void resume(); @@ -139,8 +139,17 @@ status_t MediaCodecSource::Puller::start(const sp<MetaData> &meta, return postSynchronouslyAndReturnError(msg); } -void MediaCodecSource::Puller::stopAsync() { - ALOGV("puller (%s) stopAsync", mIsAudio ? "audio" : "video"); +void MediaCodecSource::Puller::stop() { + // Stop source from caller's thread instead of puller's looper. + // mSource->stop() is thread-safe, doing it outside the puller's + // looper allows us to at least stop if source gets stuck. + // If source gets stuck in read(), the looper would never + // be able to process the stop(), which could lead to ANR. + + ALOGV("source (%s) stopping", mIsAudio ? "audio" : "video"); + mSource->stop(); + ALOGV("source (%s) stopped", mIsAudio ? "audio" : "video"); + (new AMessage(kWhatStop, id()))->post(); } @@ -194,9 +203,6 @@ void MediaCodecSource::Puller::onMessageReceived(const sp<AMessage> &msg) { case kWhatStop: { - ALOGV("source (%s) stopping", mIsAudio ? "audio" : "video"); - mSource->stop(); - ALOGV("source (%s) stopped", mIsAudio ? "audio" : "video"); ++mPullGeneration; handleEOS(); @@ -283,7 +289,21 @@ status_t MediaCodecSource::start(MetaData* params) { status_t MediaCodecSource::stop() { sp<AMessage> msg = new AMessage(kWhatStop, mReflector->id()); - return postSynchronouslyAndReturnError(msg); + status_t err = postSynchronouslyAndReturnError(msg); + + // mPuller->stop() needs to be done outside MediaCodecSource's looper, + // as it contains a synchronous call to stop the underlying MediaSource, + // which often waits for all outstanding MediaBuffers to return, but + // MediaBuffers are only returned when MediaCodecSource looper gets + // to process them. + + if (mPuller != NULL) { + ALOGI("puller (%s) stopping", mIsVideo ? "video" : "audio"); + mPuller->stop(); + ALOGI("puller (%s) stopped", mIsVideo ? "video" : "audio"); + } + + return err; } status_t MediaCodecSource::pause() { @@ -301,10 +321,10 @@ status_t MediaCodecSource::read( Mutex::Autolock autolock(mOutputBufferLock); *buffer = NULL; - while (mOutputBufferQueue.size() == 0 && !mEncodedReachedEOS) { + while (mOutputBufferQueue.size() == 0 && !mEncoderReachedEOS) { mOutputBufferCond.wait(mOutputBufferLock); } - if (!mEncodedReachedEOS) { + if (!mEncoderReachedEOS) { *buffer = *mOutputBufferQueue.begin(); mOutputBufferQueue.erase(mOutputBufferQueue.begin()); return OK; @@ -330,9 +350,8 @@ MediaCodecSource::MediaCodecSource( mStarted(false), mStopping(false), mDoMoreWorkPending(false), - mPullerReachedEOS(false), mFirstSampleTimeUs(-1ll), - mEncodedReachedEOS(false), + mEncoderReachedEOS(false), mErrorCode(OK) { CHECK(mLooper != NULL); @@ -434,7 +453,7 @@ status_t MediaCodecSource::initEncoder() { return err; } - mEncodedReachedEOS = false; + mEncoderReachedEOS = false; mErrorCode = OK; return OK; @@ -465,10 +484,6 @@ void MediaCodecSource::releaseEncoder() { mEncoderOutputBuffers.clear(); } -bool MediaCodecSource::reachedEOS() { - return mEncodedReachedEOS && ((mPuller == NULL) || mPullerReachedEOS); -} - status_t MediaCodecSource::postSynchronouslyAndReturnError( const sp<AMessage> &msg) { sp<AMessage> response; @@ -486,8 +501,8 @@ status_t MediaCodecSource::postSynchronouslyAndReturnError( } void MediaCodecSource::signalEOS(status_t err) { - if (!mEncodedReachedEOS) { - ALOGI("encoder (%s) reached EOS", mIsVideo ? "video" : "audio"); + if (!mEncoderReachedEOS) { + ALOGV("encoder (%s) reached EOS", mIsVideo ? "video" : "audio"); { Mutex::Autolock autoLock(mOutputBufferLock); // release all unread media buffers @@ -496,16 +511,15 @@ void MediaCodecSource::signalEOS(status_t err) { (*it)->release(); } mOutputBufferQueue.clear(); - mEncodedReachedEOS = true; + mEncoderReachedEOS = true; mErrorCode = err; mOutputBufferCond.signal(); } releaseEncoder(); } - if (mStopping && reachedEOS()) { - ALOGI("MediaCodecSource (%s) fully stopped", - mIsVideo ? "video" : "audio"); + if (mStopping && mEncoderReachedEOS) { + ALOGI("encoder (%s) stopped", mIsVideo ? "video" : "audio"); // posting reply to everyone that's waiting List<uint32_t>::iterator it; for (it = mStopReplyIDQueue.begin(); @@ -755,7 +769,6 @@ status_t MediaCodecSource::onStart(MetaData *params) { kWhatPullerNotify, mReflector->id()); err = mPuller->start(params, notify); if (err != OK) { - mPullerReachedEOS = true; return err; } } @@ -774,9 +787,9 @@ void MediaCodecSource::onMessageReceived(const sp<AMessage> &msg) { CHECK(msg->findPointer("accessUnit", (void**)&mbuf)); if (mbuf == NULL) { - ALOGI("puller (%s) reached EOS", + ALOGV("puller (%s) reached EOS", mIsVideo ? "video" : "audio"); - mPullerReachedEOS = true; + signalEOS(); } if (mEncoder == NULL) { @@ -785,9 +798,8 @@ void MediaCodecSource::onMessageReceived(const sp<AMessage> &msg) { if (mbuf != NULL) { mbuf->release(); - } else { - signalEOS(); } + break; } @@ -833,14 +845,14 @@ void MediaCodecSource::onMessageReceived(const sp<AMessage> &msg) { } case kWhatStop: { - ALOGI("MediaCodecSource (%s) stopping", mIsVideo ? "video" : "audio"); + ALOGI("encoder (%s) stopping", mIsVideo ? "video" : "audio"); uint32_t replyID; CHECK(msg->senderAwaitsResponse(&replyID)); - if (reachedEOS()) { + if (mEncoderReachedEOS) { // if we already reached EOS, reply and return now - ALOGI("MediaCodecSource (%s) already stopped", + ALOGI("encoder (%s) already stopped", mIsVideo ? "video" : "audio"); (new AMessage)->postReply(replyID); break; @@ -860,8 +872,6 @@ void MediaCodecSource::onMessageReceived(const sp<AMessage> &msg) { if (mFlags & FLAG_USE_SURFACE_INPUT) { mEncoder->signalEndOfInputStream(); } else { - CHECK(mPuller != NULL); - mPuller->stopAsync(); signalEOS(); } break; diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index 354712c..3d1d40e 100644 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -35,6 +35,7 @@ #include <HardwareAPI.h> #include <media/stagefright/foundation/ADebug.h> #include <media/IMediaPlayerService.h> +#include <media/stagefright/ACodec.h> #include <media/stagefright/MediaBuffer.h> #include <media/stagefright/MediaBufferGroup.h> #include <media/stagefright/MediaDefs.h> @@ -197,7 +198,7 @@ void OMXCodec::findMatchingCodecs( Vector<CodecNameAndQuirks> *matchingCodecs) { matchingCodecs->clear(); - const MediaCodecList *list = MediaCodecList::getInstance(); + const sp<IMediaCodecList> list = MediaCodecList::getInstance(); if (list == NULL) { return; } @@ -213,7 +214,9 @@ void OMXCodec::findMatchingCodecs( index = matchIndex + 1; - const char *componentName = list->getCodecName(matchIndex); + const sp<MediaCodecInfo> info = list->getCodecInfo(matchIndex); + CHECK(info != NULL); + const char *componentName = info->getCodecName(); // If a specific codec is requested, skip the non-matching ones. if (matchComponentName && strcmp(componentName, matchComponentName)) { @@ -231,7 +234,7 @@ void OMXCodec::findMatchingCodecs( ssize_t index = matchingCodecs->add(); CodecNameAndQuirks *entry = &matchingCodecs->editItemAt(index); entry->mName = String8(componentName); - entry->mQuirks = getComponentQuirks(list, matchIndex); + entry->mQuirks = getComponentQuirks(info); ALOGV("matching '%s' quirks 0x%08x", entry->mName.string(), entry->mQuirks); @@ -245,18 +248,15 @@ void OMXCodec::findMatchingCodecs( // static uint32_t OMXCodec::getComponentQuirks( - const MediaCodecList *list, size_t index) { + const sp<MediaCodecInfo> &info) { uint32_t quirks = 0; - if (list->codecHasQuirk( - index, "requires-allocate-on-input-ports")) { + if (info->hasQuirk("requires-allocate-on-input-ports")) { quirks |= kRequiresAllocateBufferOnInputPorts; } - if (list->codecHasQuirk( - index, "requires-allocate-on-output-ports")) { + if (info->hasQuirk("requires-allocate-on-output-ports")) { quirks |= kRequiresAllocateBufferOnOutputPorts; } - if (list->codecHasQuirk( - index, "output-buffers-are-unreadable")) { + if (info->hasQuirk("output-buffers-are-unreadable")) { quirks |= kOutputBuffersAreUnreadable; } @@ -265,8 +265,7 @@ uint32_t OMXCodec::getComponentQuirks( // static bool OMXCodec::findCodecQuirks(const char *componentName, uint32_t *quirks) { - const MediaCodecList *list = MediaCodecList::getInstance(); - + const sp<IMediaCodecList> list = MediaCodecList::getInstance(); if (list == NULL) { return false; } @@ -277,7 +276,9 @@ bool OMXCodec::findCodecQuirks(const char *componentName, uint32_t *quirks) { return false; } - *quirks = getComponentQuirks(list, index); + const sp<MediaCodecInfo> info = list->getCodecInfo(index); + CHECK(info != NULL); + *quirks = getComponentQuirks(info); return true; } @@ -1551,7 +1552,7 @@ OMXCodec::~OMXCodec() { status_t err = mOMX->freeNode(mNode); CHECK_EQ(err, (status_t)OK); - mNode = NULL; + mNode = 0; setState(DEAD); clearCodecSpecificData(); @@ -4746,6 +4747,8 @@ status_t QueryCodec( } // Color format query + // return colors in the order reported by the OMX component + // prefix "flexible" standard ones with the flexible equivalent OMX_VIDEO_PARAM_PORTFORMATTYPE portFormat; InitOMXParams(&portFormat); portFormat.nPortIndex = !isEncoder ? 1 : 0; @@ -4756,6 +4759,21 @@ status_t QueryCodec( if (err != OK) { break; } + + OMX_U32 flexibleEquivalent; + if (ACodec::isFlexibleColorFormat( + omx, node, portFormat.eColorFormat, &flexibleEquivalent)) { + bool marked = false; + for (size_t i = 0; i < caps->mColorFormats.size(); i++) { + if (caps->mColorFormats.itemAt(i) == flexibleEquivalent) { + marked = true; + break; + } + } + if (!marked) { + caps->mColorFormats.push(flexibleEquivalent); + } + } caps->mColorFormats.push(portFormat.eColorFormat); } diff --git a/media/libstagefright/Utils.cpp b/media/libstagefright/Utils.cpp index 587e264..5f1d1c6 100644 --- a/media/libstagefright/Utils.cpp +++ b/media/libstagefright/Utils.cpp @@ -135,6 +135,11 @@ status_t convertMetaDataToMessage( if (meta->findInt32(kKeyIsADTS, &isADTS)) { msg->setInt32("is-adts", true); } + + int32_t aacProfile = -1; + if (meta->findInt32(kKeyAACAOT, &aacProfile)) { + msg->setInt32("aac-profile", aacProfile); + } } int32_t maxInputSize; diff --git a/media/libstagefright/foundation/AString.cpp b/media/libstagefright/foundation/AString.cpp index 894f65c..9835ca3 100644 --- a/media/libstagefright/foundation/AString.cpp +++ b/media/libstagefright/foundation/AString.cpp @@ -20,6 +20,7 @@ #include <stdlib.h> #include <string.h> +#include <binder/Parcel.h> #include <utils/String8.h> #include "ADebug.h" #include "AString.h" @@ -306,6 +307,14 @@ int AString::compare(const AString &other) const { return strcmp(mData, other.mData); } +int AString::compareIgnoreCase(const AString &other) const { + return strcasecmp(mData, other.mData); +} + +bool AString::equalsIgnoreCase(const AString &other) const { + return compareIgnoreCase(other) == 0; +} + void AString::tolower() { makeMutable(); @@ -342,6 +351,21 @@ bool AString::endsWithIgnoreCase(const char *suffix) const { return !strcasecmp(mData + mSize - suffixLen, suffix); } +// static +AString AString::FromParcel(const Parcel &parcel) { + size_t size = static_cast<size_t>(parcel.readInt32()); + return AString(static_cast<const char *>(parcel.readInplace(size)), size); +} + +status_t AString::writeToParcel(Parcel *parcel) const { + CHECK_LE(mSize, static_cast<size_t>(INT32_MAX)); + status_t err = parcel->writeInt32(mSize); + if (err == OK) { + err = parcel->write(mData, mSize); + } + return err; +} + AString StringPrintf(const char *format, ...) { va_list ap; va_start(ap, format); diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp index 10cdde2..8667a6b 100644 --- a/media/libstagefright/httplive/LiveSession.cpp +++ b/media/libstagefright/httplive/LiveSession.cpp @@ -57,7 +57,7 @@ LiveSession::LiveSession( mHTTPService(httpService), mInPreparationPhase(true), mHTTPDataSource(new MediaHTTP(mHTTPService->makeHTTPConnection())), - mPrevBandwidthIndex(-1), + mCurBandwidthIndex(-1), mStreamMask(0), mNewStreamMask(0), mSwapMask(0), @@ -68,13 +68,17 @@ LiveSession::LiveSession( mReconfigurationInProgress(false), mSwitchInProgress(false), mDisconnectReplyID(0), - mSeekReplyID(0) { + mSeekReplyID(0), + mFirstTimeUsValid(false), + mFirstTimeUs(0), + mLastSeekTimeUs(0) { mStreams[kAudioIndex] = StreamItem("audio"); mStreams[kVideoIndex] = StreamItem("video"); mStreams[kSubtitleIndex] = StreamItem("subtitles"); for (size_t i = 0; i < kMaxStreams; ++i) { + mDiscontinuities.add(indexToType(i), new AnotherPacketSource(NULL /* meta */)); mPacketSources.add(indexToType(i), new AnotherPacketSource(NULL /* meta */)); mPacketSources2.add(indexToType(i), new AnotherPacketSource(NULL /* meta */)); } @@ -109,31 +113,65 @@ status_t LiveSession::dequeueAccessUnit( return -EWOULDBLOCK; } + status_t finalResult; + sp<AnotherPacketSource> discontinuityQueue = mDiscontinuities.valueFor(stream); + if (discontinuityQueue->hasBufferAvailable(&finalResult)) { + discontinuityQueue->dequeueAccessUnit(accessUnit); + // seeking, track switching + sp<AMessage> extra; + int64_t timeUs; + if ((*accessUnit)->meta()->findMessage("extra", &extra) + && extra != NULL + && extra->findInt64("timeUs", &timeUs)) { + // seeking only + mLastSeekTimeUs = timeUs; + mDiscontinuityOffsetTimesUs.clear(); + mDiscontinuityAbsStartTimesUs.clear(); + } + return INFO_DISCONTINUITY; + } + sp<AnotherPacketSource> packetSource = mPacketSources.valueFor(stream); - status_t finalResult; if (!packetSource->hasBufferAvailable(&finalResult)) { return finalResult == OK ? -EAGAIN : finalResult; } + // wait for counterpart + sp<AnotherPacketSource> otherSource; + if (stream == STREAMTYPE_AUDIO && (mStreamMask & STREAMTYPE_VIDEO)) { + otherSource = mPacketSources.valueFor(STREAMTYPE_VIDEO); + } else if (stream == STREAMTYPE_VIDEO && (mStreamMask & STREAMTYPE_AUDIO)) { + otherSource = mPacketSources.valueFor(STREAMTYPE_AUDIO); + } + if (otherSource != NULL && !otherSource->hasBufferAvailable(&finalResult)) { + return finalResult == OK ? -EAGAIN : finalResult; + } + status_t err = packetSource->dequeueAccessUnit(accessUnit); + size_t streamIdx; const char *streamStr; switch (stream) { case STREAMTYPE_AUDIO: + streamIdx = kAudioIndex; streamStr = "audio"; break; case STREAMTYPE_VIDEO: + streamIdx = kVideoIndex; streamStr = "video"; break; case STREAMTYPE_SUBTITLES: + streamIdx = kSubtitleIndex; streamStr = "subs"; break; default: TRESPASS(); } + StreamItem& strm = mStreams[streamIdx]; if (err == INFO_DISCONTINUITY) { + // adaptive streaming, discontinuities in the playlist int32_t type; CHECK((*accessUnit)->meta()->findInt32("discontinuity", &type)); @@ -148,10 +186,7 @@ status_t LiveSession::dequeueAccessUnit( extra == NULL ? "NULL" : extra->debugString().c_str()); int32_t swap; - if (type == ATSParser::DISCONTINUITY_FORMATCHANGE - && (*accessUnit)->meta()->findInt32("swapPacketSource", &swap) - && swap) { - + if ((*accessUnit)->meta()->findInt32("swapPacketSource", &swap) && swap) { int32_t switchGeneration; CHECK((*accessUnit)->meta()->findInt32("switchGeneration", &switchGeneration)); { @@ -164,13 +199,67 @@ status_t LiveSession::dequeueAccessUnit( msg->post(); } } + } 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; + } + + mDiscontinuityOffsetTimesUs.add(seq, offsetTimeUs); } } else if (err == OK) { + if (stream == STREAMTYPE_AUDIO || stream == STREAMTYPE_VIDEO) { int64_t timeUs; + int32_t discontinuitySeq = 0; CHECK((*accessUnit)->meta()->findInt64("timeUs", &timeUs)); - ALOGV("[%s] read buffer at time %" PRId64 " us", streamStr, timeUs); + (*accessUnit)->meta()->findInt32("discontinuitySeq", &discontinuitySeq); + strm.mCurDiscontinuitySeq = discontinuitySeq; + + int32_t discard = 0; + int64_t firstTimeUs; + if (mDiscontinuityAbsStartTimesUs.indexOfKey(strm.mCurDiscontinuitySeq) >= 0) { + int64_t durUs; // approximate sample duration + if (timeUs > strm.mLastDequeuedTimeUs) { + durUs = timeUs - strm.mLastDequeuedTimeUs; + } else { + durUs = strm.mLastDequeuedTimeUs - timeUs; + } + strm.mLastSampleDurationUs = durUs; + firstTimeUs = mDiscontinuityAbsStartTimesUs.valueFor(strm.mCurDiscontinuitySeq); + } else if ((*accessUnit)->meta()->findInt32("discard", &discard) && discard) { + firstTimeUs = timeUs; + } else { + mDiscontinuityAbsStartTimesUs.add(strm.mCurDiscontinuitySeq, timeUs); + firstTimeUs = timeUs; + } + + strm.mLastDequeuedTimeUs = timeUs; + if (timeUs >= firstTimeUs) { + timeUs -= firstTimeUs; + } else { + timeUs = 0; + } + timeUs += mLastSeekTimeUs; + if (mDiscontinuityOffsetTimesUs.indexOfKey(discontinuitySeq) >= 0) { + timeUs += mDiscontinuityOffsetTimesUs.valueFor(discontinuitySeq); + } + ALOGV("[%s] read buffer at time %" PRId64 " us", streamStr, timeUs); + (*accessUnit)->meta()->setInt64("timeUs", timeUs); mLastDequeuedTimeUs = timeUs; mRealTimeBaseUs = ALooper::GetNowUs() - timeUs; } else if (stream == STREAMTYPE_SUBTITLES) { @@ -289,7 +378,9 @@ void LiveSession::onMessageReceived(const sp<AMessage> &msg) { break; } - tryToFinishBandwidthSwitch(); + if (mSwitchInProgress) { + tryToFinishBandwidthSwitch(); + } } if (mContinuation != NULL) { @@ -538,8 +629,9 @@ void LiveSession::onConnect(const sp<AMessage> &msg) { mBandwidthItems.push(item); } + mPlaylist->pickRandomMediaItems(); changeConfiguration( - 0ll /* timeUs */, initialBandwidthIndex, true /* pickTrack */); + 0ll /* timeUs */, initialBandwidthIndex, false /* pickTrack */); } void LiveSession::finishDisconnect() { @@ -847,20 +939,20 @@ size_t LiveSession::getBandwidthIndex() { // to lowest) const size_t kMinIndex = 0; - static ssize_t mPrevBandwidthIndex = -1; + static ssize_t mCurBandwidthIndex = -1; size_t index; - if (mPrevBandwidthIndex < 0) { + if (mCurBandwidthIndex < 0) { index = kMinIndex; } else if (uniformRand() < 0.5) { - index = (size_t)mPrevBandwidthIndex; + index = (size_t)mCurBandwidthIndex; } else { - index = mPrevBandwidthIndex + 1; + index = mCurBandwidthIndex + 1; if (index == mBandwidthItems.size()) { index = kMinIndex; } } - mPrevBandwidthIndex = index; + mCurBandwidthIndex = index; #elif 0 // Pick the highest bandwidth stream below or equal to 1.2 Mbit/sec @@ -937,7 +1029,10 @@ sp<AMessage> LiveSession::getTrackInfo(size_t trackIndex) const { status_t LiveSession::selectTrack(size_t index, bool select) { status_t err = mPlaylist->selectTrack(index, select); if (err == OK) { - (new AMessage(kWhatChangeConfiguration, id()))->post(); + sp<AMessage> msg = new AMessage(kWhatChangeConfiguration, id()); + msg->setInt32("bandwidthIndex", mCurBandwidthIndex); + msg->setInt32("pickTrack", select); + msg->post(); } return err; } @@ -964,15 +1059,11 @@ void LiveSession::changeConfiguration( CHECK(!mReconfigurationInProgress); mReconfigurationInProgress = true; - mPrevBandwidthIndex = bandwidthIndex; + mCurBandwidthIndex = bandwidthIndex; ALOGV("changeConfiguration => timeUs:%" PRId64 " us, bwIndex:%zu, pickTrack:%d", timeUs, bandwidthIndex, pickTrack); - if (pickTrack) { - mPlaylist->pickRandomMediaItems(); - } - CHECK_LT(bandwidthIndex, mBandwidthItems.size()); const BandwidthItem &item = mBandwidthItems.itemAt(bandwidthIndex); @@ -995,14 +1086,15 @@ void LiveSession::changeConfiguration( // If we're seeking all current fetchers are discarded. if (timeUs < 0ll) { - // delay fetcher removal - discardFetcher = false; + // 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; } } } @@ -1016,16 +1108,17 @@ void LiveSession::changeConfiguration( sp<AMessage> msg; if (timeUs < 0ll) { - // skip onChangeConfiguration2 (decoder destruction) if switching. + // skip onChangeConfiguration2 (decoder destruction) if not seeking. msg = new AMessage(kWhatChangeConfiguration3, id()); } else { msg = new AMessage(kWhatChangeConfiguration2, id()); } msg->setInt32("streamMask", streamMask); msg->setInt32("resumeMask", resumeMask); + msg->setInt32("pickTrack", pickTrack); msg->setInt64("timeUs", timeUs); for (size_t i = 0; i < kMaxStreams; ++i) { - if (streamMask & indexToType(i)) { + if ((streamMask | resumeMask) & indexToType(i)) { msg->setString(mStreams[i].uriKey().c_str(), URIs[i].c_str()); } } @@ -1049,7 +1142,10 @@ void LiveSession::changeConfiguration( void LiveSession::onChangeConfiguration(const sp<AMessage> &msg) { if (!mReconfigurationInProgress) { - changeConfiguration(-1ll /* timeUs */, getBandwidthIndex()); + int32_t pickTrack = 0, bandwidthIndex = mCurBandwidthIndex; + msg->findInt32("pickTrack", &pickTrack); + msg->findInt32("bandwidthIndex", &bandwidthIndex); + changeConfiguration(-1ll /* timeUs */, bandwidthIndex, pickTrack); } else { msg->post(1000000ll); // retry in 1 sec } @@ -1060,8 +1156,14 @@ void LiveSession::onChangeConfiguration2(const sp<AMessage> &msg) { // All fetchers are either suspended or have been removed now. - uint32_t streamMask; + uint32_t streamMask, resumeMask; 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]; for (size_t i = 0; i < kMaxStreams; ++i) { @@ -1125,16 +1227,21 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) { } int64_t timeUs; + int32_t pickTrack; bool switching = false; CHECK(msg->findInt64("timeUs", &timeUs)); + CHECK(msg->findInt32("pickTrack", &pickTrack)); if (timeUs < 0ll) { - timeUs = mLastDequeuedTimeUs; - switching = true; + if (!pickTrack) { + switching = true; + } + mRealTimeBaseUs = ALooper::GetNowUs() - mLastDequeuedTimeUs; + } else { + mRealTimeBaseUs = ALooper::GetNowUs() - timeUs; } - mRealTimeBaseUs = ALooper::GetNowUs() - timeUs; - mNewStreamMask = streamMask; + mNewStreamMask = streamMask | resumeMask; // Of all existing fetchers: // * Resume fetchers that are still needed and assign them original packet sources. @@ -1147,6 +1254,16 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) { for (size_t j = 0; j < kMaxStreams; ++j) { if ((resumeMask & indexToType(j)) && uri == mStreams[j].mUri) { sources[j] = mPacketSources.valueFor(indexToType(j)); + + if (j != kSubtitleIndex) { + ALOGV("queueing dummy discontinuity for stream type %d", indexToType(j)); + sp<AnotherPacketSource> discontinuityQueue; + discontinuityQueue = mDiscontinuities.valueFor(indexToType(j)); + discontinuityQueue->queueDiscontinuity( + ATSParser::DISCONTINUITY_NONE, + NULL, + true); + } } } @@ -1180,7 +1297,9 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) { CHECK(fetcher != NULL); int32_t latestSeq = -1; - int64_t latestTimeUs = 0ll; + int64_t startTimeUs = -1; + int64_t segmentStartTimeUs = -1ll; + int32_t discontinuitySeq = -1; sp<AnotherPacketSource> sources[kMaxStreams]; // TRICKY: looping from i as earlier streams are already removed from streamMask @@ -1188,29 +1307,65 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) { if ((streamMask & indexToType(j)) && uri == mStreams[j].mUri) { sources[j] = mPacketSources.valueFor(indexToType(j)); - if (!switching) { + if (timeUs >= 0) { sources[j]->clear(); + startTimeUs = timeUs; + + sp<AnotherPacketSource> discontinuityQueue; + sp<AMessage> extra = new AMessage; + extra->setInt64("timeUs", timeUs); + discontinuityQueue = mDiscontinuities.valueFor(indexToType(j)); + discontinuityQueue->queueDiscontinuity( + ATSParser::DISCONTINUITY_SEEK, extra, true); } else { - int32_t type, seq; - int64_t srcTimeUs; - sp<AMessage> meta = sources[j]->getLatestMeta(); + int32_t type; + int64_t srcSegmentStartTimeUs; + sp<AMessage> meta; + if (pickTrack) { + // selecting + meta = sources[j]->getLatestDequeuedMeta(); + } else { + // adapting + meta = sources[j]->getLatestEnqueuedMeta(); + } if (meta != NULL && !meta->findInt32("discontinuity", &type)) { - CHECK(meta->findInt32("seq", &seq)); - if (seq > latestSeq) { - latestSeq = seq; + int64_t tmpUs; + CHECK(meta->findInt64("timeUs", &tmpUs)); + if (startTimeUs < 0 || tmpUs < startTimeUs) { + startTimeUs = tmpUs; + } + + CHECK(meta->findInt64("segmentStartTimeUs", &tmpUs)); + if (segmentStartTimeUs < 0 || tmpUs < segmentStartTimeUs) { + segmentStartTimeUs = tmpUs; } - CHECK(meta->findInt64("timeUs", &srcTimeUs)); - if (srcTimeUs > latestTimeUs) { - latestTimeUs = srcTimeUs; + + int32_t seq; + CHECK(meta->findInt32("discontinuitySeq", &seq)); + if (discontinuitySeq < 0 || seq < discontinuitySeq) { + discontinuitySeq = seq; } } - sources[j] = mPacketSources2.valueFor(indexToType(j)); - sources[j]->clear(); - uint32_t extraStreams = mNewStreamMask & (~mStreamMask); - if (extraStreams & indexToType(j)) { - sources[j]->queueAccessUnit(createFormatChangeBuffer(/* swap = */ false)); + if (pickTrack) { + // selecting track, queue discontinuities before content + sources[j]->clear(); + if (j == kSubtitleIndex) { + break; + } + sp<AnotherPacketSource> discontinuityQueue; + discontinuityQueue = mDiscontinuities.valueFor(indexToType(j)); + discontinuityQueue->queueDiscontinuity( + ATSParser::DISCONTINUITY_FORMATCHANGE, 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)); + } } } @@ -1222,9 +1377,10 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) { sources[kAudioIndex], sources[kVideoIndex], sources[kSubtitleIndex], - timeUs, - latestTimeUs /* min start time(us) */, - latestSeq >= 0 ? latestSeq + 1 : -1 /* starting sequence number hint */ ); + startTimeUs < 0 ? mLastSeekTimeUs : startTimeUs, + segmentStartTimeUs, + discontinuitySeq, + switching); } // All fetchers have now been started, the configuration change @@ -1236,6 +1392,7 @@ void LiveSession::onChangeConfiguration3(const sp<AMessage> &msg) { mReconfigurationInProgress = false; if (switching) { mSwitchInProgress = true; + mSwapMask = streamMask; } else { mStreamMask = mNewStreamMask; } @@ -1254,8 +1411,8 @@ void LiveSession::onSwapped(const sp<AMessage> &msg) { int32_t stream; CHECK(msg->findInt32("stream", &stream)); - mSwapMask |= stream; - if (mSwapMask != mStreamMask) { + mSwapMask &= ~stream; + if (mSwapMask != 0) { return; } @@ -1271,9 +1428,12 @@ void LiveSession::onSwapped(const sp<AMessage> &msg) { } // Mark switch done when: -// 1. all old buffers are swapped out, AND -// 2. all old fetchers are removed. +// 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) { @@ -1281,10 +1441,11 @@ void LiveSession::tryToFinishBandwidthSwitch() { break; } } - if (!needToRemoveFetchers && mSwapMask == mStreamMask) { + + if (!needToRemoveFetchers && mSwapMask == 0) { + ALOGI("mSwitchInProgress = false"); mStreamMask = mNewStreamMask; mSwitchInProgress = false; - mSwapMask = 0; } } @@ -1310,13 +1471,13 @@ bool LiveSession::canSwitchBandwidthTo(size_t bandwidthIndex) { return false; } - if (mPrevBandwidthIndex < 0) { + if (mCurBandwidthIndex < 0) { return true; } - if (bandwidthIndex == (size_t)mPrevBandwidthIndex) { + if (bandwidthIndex == (size_t)mCurBandwidthIndex) { return false; - } else if (bandwidthIndex > (size_t)mPrevBandwidthIndex) { + } else if (bandwidthIndex > (size_t)mCurBandwidthIndex) { return canSwitchUp(); } else { return true; diff --git a/media/libstagefright/httplive/LiveSession.h b/media/libstagefright/httplive/LiveSession.h index ed3818f..5423f0f 100644 --- a/media/libstagefright/httplive/LiveSession.h +++ b/media/libstagefright/httplive/LiveSession.h @@ -125,8 +125,19 @@ private: struct StreamItem { const char *mType; AString mUri; - StreamItem() : mType("") {} - StreamItem(const char *type) : mType(type) {} + size_t mCurDiscontinuitySeq; + int64_t mLastDequeuedTimeUs; + int64_t mLastSampleDurationUs; + StreamItem() + : mType(""), + mCurDiscontinuitySeq(0), + mLastDequeuedTimeUs(0), + mLastSampleDurationUs(0) {} + StreamItem(const char *type) + : mType(type), + mCurDiscontinuitySeq(0), + mLastDequeuedTimeUs(0), + mLastSampleDurationUs(0) {} AString uriKey() { AString key(mType); key.append("URI"); @@ -147,7 +158,7 @@ private: AString mMasterURL; Vector<BandwidthItem> mBandwidthItems; - ssize_t mPrevBandwidthIndex; + ssize_t mCurBandwidthIndex; sp<M3UParser> mPlaylist; @@ -163,6 +174,7 @@ private: // we use this to track reconfiguration progress. uint32_t mSwapMask; + KeyedVector<StreamType, sp<AnotherPacketSource> > mDiscontinuities; KeyedVector<StreamType, sp<AnotherPacketSource> > mPacketSources; // A second set of packet sources that buffer content for the variant we're switching to. KeyedVector<StreamType, sp<AnotherPacketSource> > mPacketSources2; @@ -187,6 +199,12 @@ private: uint32_t mDisconnectReplyID; uint32_t mSeekReplyID; + bool mFirstTimeUsValid; + int64_t mFirstTimeUs; + int64_t mLastSeekTimeUs; + KeyedVector<size_t, int64_t> mDiscontinuityAbsStartTimesUs; + KeyedVector<size_t, int64_t> mDiscontinuityOffsetTimesUs; + sp<PlaylistFetcher> addFetcher(const char *uri); void onConnect(const sp<AMessage> &msg); diff --git a/media/libstagefright/httplive/M3UParser.cpp b/media/libstagefright/httplive/M3UParser.cpp index efd852c..1651dee 100644 --- a/media/libstagefright/httplive/M3UParser.cpp +++ b/media/libstagefright/httplive/M3UParser.cpp @@ -157,8 +157,8 @@ void M3UParser::MediaGroup::pickRandomMediaItems() { } status_t M3UParser::MediaGroup::selectTrack(size_t index, bool select) { - if (mType != TYPE_SUBS) { - ALOGE("only select subtitile tracks for now!"); + if (mType != TYPE_SUBS && mType != TYPE_AUDIO) { + ALOGE("only select subtitile/audio tracks for now!"); return INVALID_OPERATION; } @@ -246,6 +246,7 @@ M3UParser::M3UParser( mIsVariantPlaylist(false), mIsComplete(false), mIsEvent(false), + mDiscontinuitySeq(0), mSelectedIndex(-1) { mInitCheck = parse(data, size); } @@ -273,6 +274,10 @@ bool M3UParser::isEvent() const { return mIsEvent; } +size_t M3UParser::getDiscontinuitySeq() const { + return mDiscontinuitySeq; +} + sp<AMessage> M3UParser::meta() { return mMeta; } @@ -567,6 +572,12 @@ 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")) { + size_t seq; + err = parseDiscontinuitySequence(line, &seq); + if (err == OK) { + mDiscontinuitySeq = seq; + } } if (err != OK) { @@ -1110,6 +1121,30 @@ status_t M3UParser::parseMedia(const AString &line) { } // static +status_t M3UParser::parseDiscontinuitySequence(const AString &line, size_t *seq) { + ssize_t colonPos = line.find(":"); + + if (colonPos < 0) { + return ERROR_MALFORMED; + } + + int32_t x; + status_t err = ParseInt32(line.c_str() + colonPos + 1, &x); + if (err != OK) { + return err; + } + + if (x < 0) { + return ERROR_MALFORMED; + } + + if (seq) { + *seq = x; + } + return OK; +} + +// static status_t M3UParser::ParseInt32(const char *s, int32_t *x) { char *end; long lval = strtol(s, &end, 10); diff --git a/media/libstagefright/httplive/M3UParser.h b/media/libstagefright/httplive/M3UParser.h index fe9fb9d..d588afe 100644 --- a/media/libstagefright/httplive/M3UParser.h +++ b/media/libstagefright/httplive/M3UParser.h @@ -34,6 +34,7 @@ struct M3UParser : public RefBase { bool isVariantPlaylist() const; bool isComplete() const; bool isEvent() const; + size_t getDiscontinuitySeq() const; sp<AMessage> meta(); @@ -66,6 +67,7 @@ private: bool mIsVariantPlaylist; bool mIsComplete; bool mIsEvent; + size_t mDiscontinuitySeq; sp<AMessage> mMeta; Vector<Item> mItems; @@ -94,6 +96,8 @@ private: status_t parseMedia(const AString &line); + static status_t parseDiscontinuitySequence(const AString &line, size_t *seq); + static status_t ParseInt32(const char *s, int32_t *x); static status_t ParseDouble(const char *s, double *x); diff --git a/media/libstagefright/httplive/PlaylistFetcher.cpp b/media/libstagefright/httplive/PlaylistFetcher.cpp index 10437c9..80cb2d0 100644 --- a/media/libstagefright/httplive/PlaylistFetcher.cpp +++ b/media/libstagefright/httplive/PlaylistFetcher.cpp @@ -49,7 +49,7 @@ namespace android { // static const int64_t PlaylistFetcher::kMinBufferedDurationUs = 10000000ll; const int64_t PlaylistFetcher::kMaxMonitorDelayUs = 3000000ll; -const int32_t PlaylistFetcher::kDownloadBlockSize = 192; +const int32_t PlaylistFetcher::kDownloadBlockSize = 2048; const int32_t PlaylistFetcher::kNumSkipFrames = 10; PlaylistFetcher::PlaylistFetcher( @@ -62,19 +62,21 @@ PlaylistFetcher::PlaylistFetcher( mURI(uri), mStreamTypeMask(0), mStartTimeUs(-1ll), - mMinStartTimeUs(0ll), - mStopParams(NULL), + mSegmentStartTimeUs(-1ll), + mDiscontinuitySeq(-1ll), + mStartTimeUsRelative(false), mLastPlaylistFetchTimeUs(-1ll), mSeqNumber(-1), mNumRetries(0), mStartup(true), + mAdaptive(false), mPrepared(false), - mSkipToFirstIDRAfterConnect(false), mNextPTSTimeUs(-1ll), mMonitorQueueGeneration(0), mRefreshState(INITIAL_MINIMUM_RELOAD_DELAY), mFirstPTSValid(false), - mAbsoluteTimeAnchorUs(0ll) { + mAbsoluteTimeAnchorUs(0ll), + mVideoBuffer(new AnotherPacketSource(NULL)) { memset(mPlaylistHash, 0, sizeof(mPlaylistHash)); mStartTimeUsNotify->setInt32("what", kWhatStartedAt); mStartTimeUsNotify->setInt32("streamMask", 0); @@ -335,8 +337,9 @@ void PlaylistFetcher::startAsync( const sp<AnotherPacketSource> &videoSource, const sp<AnotherPacketSource> &subtitleSource, int64_t startTimeUs, - int64_t minStartTimeUs, - int32_t startSeqNumberHint) { + int64_t segmentStartTimeUs, + int32_t startDiscontinuitySeq, + bool adaptive) { sp<AMessage> msg = new AMessage(kWhatStart, id()); uint32_t streamTypeMask = 0ul; @@ -358,8 +361,9 @@ void PlaylistFetcher::startAsync( msg->setInt32("streamTypeMask", streamTypeMask); msg->setInt64("startTimeUs", startTimeUs); - msg->setInt64("minStartTimeUs", minStartTimeUs); - msg->setInt32("startSeqNumberHint", startSeqNumberHint); + msg->setInt64("segmentStartTimeUs", segmentStartTimeUs); + msg->setInt32("startDiscontinuitySeq", startDiscontinuitySeq); + msg->setInt32("adaptive", adaptive); msg->post(); } @@ -367,9 +371,9 @@ void PlaylistFetcher::pauseAsync() { (new AMessage(kWhatPause, id()))->post(); } -void PlaylistFetcher::stopAsync(bool selfTriggered) { +void PlaylistFetcher::stopAsync(bool clear) { sp<AMessage> msg = new AMessage(kWhatStop, id()); - msg->setInt32("selfTriggered", selfTriggered); + msg->setInt32("clear", clear); msg->post(); } @@ -449,10 +453,13 @@ status_t PlaylistFetcher::onStart(const sp<AMessage> &msg) { CHECK(msg->findInt32("streamTypeMask", (int32_t *)&streamTypeMask)); int64_t startTimeUs; - int32_t startSeqNumberHint; + int64_t segmentStartTimeUs; + int32_t startDiscontinuitySeq; + int32_t adaptive; CHECK(msg->findInt64("startTimeUs", &startTimeUs)); - CHECK(msg->findInt64("minStartTimeUs", (int64_t *) &mMinStartTimeUs)); - CHECK(msg->findInt32("startSeqNumberHint", &startSeqNumberHint)); + CHECK(msg->findInt64("segmentStartTimeUs", &segmentStartTimeUs)); + CHECK(msg->findInt32("startDiscontinuitySeq", &startDiscontinuitySeq)); + CHECK(msg->findInt32("adaptive", &adaptive)); if (streamTypeMask & LiveSession::STREAMTYPE_AUDIO) { void *ptr; @@ -482,16 +489,16 @@ status_t PlaylistFetcher::onStart(const sp<AMessage> &msg) { } mStreamTypeMask = streamTypeMask; + mStartTimeUs = startTimeUs; + mSegmentStartTimeUs = segmentStartTimeUs; + mDiscontinuitySeq = startDiscontinuitySeq; if (mStartTimeUs >= 0ll) { mSeqNumber = -1; mStartup = true; mPrepared = false; - } - - if (startSeqNumberHint >= 0) { - mSeqNumber = startSeqNumberHint; + mAdaptive = adaptive; } postMonitorQueue(); @@ -506,11 +513,9 @@ void PlaylistFetcher::onPause() { void PlaylistFetcher::onStop(const sp<AMessage> &msg) { cancelMonitorQueue(); - int32_t selfTriggered; - CHECK(msg->findInt32("selfTriggered", &selfTriggered)); - if (!selfTriggered) { - // Self triggered stops only happen during switching, in which case we do not want - // to clear the discontinuities queued at the end of packet sources. + int32_t clear; + CHECK(msg->findInt32("clear", &clear)); + if (clear) { for (size_t i = 0; i < mPacketSources.size(); i++) { sp<AnotherPacketSource> packetSource = mPacketSources.valueAt(i); packetSource->clear(); @@ -552,15 +557,16 @@ status_t PlaylistFetcher::onResumeUntil(const sp<AMessage> &msg) { } // Don't resume if we would stop within a resume threshold. + int32_t discontinuitySeq; int64_t latestTimeUs = 0, stopTimeUs = 0; - sp<AMessage> latestMeta = packetSource->getLatestMeta(); + sp<AMessage> latestMeta = packetSource->getLatestDequeuedMeta(); if (latestMeta != NULL - && (latestMeta->findInt64("timeUs", &latestTimeUs) - && params->findInt64(stopKey, &stopTimeUs))) { - int64_t diffUs = stopTimeUs - latestTimeUs; - if (diffUs < resumeThreshold(latestMeta)) { - stop = true; - } + && latestMeta->findInt32("discontinuitySeq", &discontinuitySeq) + && discontinuitySeq == mDiscontinuitySeq + && latestMeta->findInt64("timeUs", &latestTimeUs) + && params->findInt64(stopKey, &stopTimeUs) + && stopTimeUs - latestTimeUs < resumeThreshold(latestMeta)) { + stop = true; } } @@ -568,7 +574,7 @@ status_t PlaylistFetcher::onResumeUntil(const sp<AMessage> &msg) { for (size_t i = 0; i < mPacketSources.size(); i++) { mPacketSources.valueAt(i)->queueAccessUnit(mSession->createFormatChangeBuffer()); } - stopAsync(/* selfTriggered = */ true); + stopAsync(/* clear = */ false); return OK; } @@ -737,26 +743,47 @@ void PlaylistFetcher::onDownloadNext() { mSeqNumber = lastSeqNumberInPlaylist; } + if (mDiscontinuitySeq < 0) { + mDiscontinuitySeq = mPlaylist->getDiscontinuitySeq(); + } + if (mSeqNumber < 0) { CHECK_GE(mStartTimeUs, 0ll); - if (mPlaylist->isComplete() || mPlaylist->isEvent()) { - mSeqNumber = getSeqNumberForTime(mStartTimeUs); + if (mSegmentStartTimeUs < 0) { + if (!mPlaylist->isComplete() && !mPlaylist->isEvent()) { + // If this is a live session, start 3 segments from the end on connect + mSeqNumber = lastSeqNumberInPlaylist - 3; + } else { + mSeqNumber = getSeqNumberForTime(mStartTimeUs); + mStartTimeUs -= getSegmentStartTimeUs(mSeqNumber); + } + mStartTimeUsRelative = true; ALOGV("Initial sequence number for time %" PRId64 " is %d from (%d .. %d)", mStartTimeUs, mSeqNumber, firstSeqNumberInPlaylist, lastSeqNumberInPlaylist); } else { - // If this is a live session, start 3 segments from the end. - mSeqNumber = lastSeqNumberInPlaylist - 3; + mSeqNumber = getSeqNumberForTime(mSegmentStartTimeUs); + if (mAdaptive) { + // avoid double fetch/decode + mSeqNumber += 1; + } + ssize_t minSeq = getSeqNumberForDiscontinuity(mDiscontinuitySeq); + if (mSeqNumber < minSeq) { + mSeqNumber = minSeq; + } + if (mSeqNumber < firstSeqNumberInPlaylist) { mSeqNumber = firstSeqNumberInPlaylist; } + + if (mSeqNumber > lastSeqNumberInPlaylist) { + mSeqNumber = lastSeqNumberInPlaylist; + } ALOGV("Initial sequence number for live event %d from (%d .. %d)", mSeqNumber, firstSeqNumberInPlaylist, lastSeqNumberInPlaylist); } - - mStartTimeUs = -1ll; } if (mSeqNumber < firstSeqNumberInPlaylist @@ -819,6 +846,7 @@ void PlaylistFetcher::onDownloadNext() { int32_t val; if (itemMeta->findInt32("discontinuity", &val) && val != 0) { + mDiscontinuitySeq++; discontinuity = true; } @@ -850,6 +878,7 @@ void PlaylistFetcher::onDownloadNext() { } // block-wise download + bool startup = mStartup; ssize_t bytesRead; do { bytesRead = mSession->fetchFile( @@ -879,7 +908,7 @@ void PlaylistFetcher::onDownloadNext() { return; } - if (mStartup || discontinuity) { + if (startup || discontinuity) { // Signal discontinuity. if (mPlaylist->isComplete() || mPlaylist->isEvent()) { @@ -898,6 +927,8 @@ void PlaylistFetcher::onDownloadNext() { discontinuity = false; } + + startup = false; } err = OK; @@ -917,24 +948,19 @@ void PlaylistFetcher::onDownloadNext() { } if (err == -EAGAIN) { - // bad starting sequence number hint + // starting sequence number too low mTSParser.clear(); postMonitorQueue(); return; - } - - if (err == ERROR_OUT_OF_RANGE) { + } else if (err == ERROR_OUT_OF_RANGE) { // reached stopping point - stopAsync(/* selfTriggered = */ true); + stopAsync(/* clear = */ false); return; - } - - if (err != OK) { + } else if (err != OK) { notifyError(err); return; } - mStartup = false; } while (bytesRead != 0); if (bufferStartsWithTsSyncByte(buffer)) { @@ -994,11 +1020,44 @@ void PlaylistFetcher::onDownloadNext() { return; } + mStartup = false; ++mSeqNumber; postMonitorQueue(); } +int32_t PlaylistFetcher::getSeqNumberForDiscontinuity(size_t discontinuitySeq) const { + int32_t firstSeqNumberInPlaylist; + if (mPlaylist->meta() == NULL + || !mPlaylist->meta()->findInt32("media-sequence", &firstSeqNumberInPlaylist)) { + firstSeqNumberInPlaylist = 0; + } + + size_t curDiscontinuitySeq = mPlaylist->getDiscontinuitySeq(); + if (discontinuitySeq < curDiscontinuitySeq) { + return firstSeqNumberInPlaylist <= 0 ? 0 : (firstSeqNumberInPlaylist - 1); + } + + size_t index = 0; + while (index < mPlaylist->size()) { + sp<AMessage> itemMeta; + CHECK(mPlaylist->itemAt( index, NULL /* uri */, &itemMeta)); + + int64_t discontinuity; + if (itemMeta->findInt64("discontinuity", &discontinuity)) { + curDiscontinuitySeq++; + } + + if (curDiscontinuitySeq == discontinuitySeq) { + return firstSeqNumberInPlaylist + index; + } + + ++index; + } + + return firstSeqNumberInPlaylist + mPlaylist->size(); +} + int32_t PlaylistFetcher::getSeqNumberForTime(int64_t timeUs) const { int32_t firstSeqNumberInPlaylist; if (mPlaylist->meta() == NULL || !mPlaylist->meta()->findInt32( @@ -1031,6 +1090,23 @@ int32_t PlaylistFetcher::getSeqNumberForTime(int64_t timeUs) const { return firstSeqNumberInPlaylist + index; } +const sp<ABuffer> &PlaylistFetcher::setAccessUnitProperties( + const sp<ABuffer> &accessUnit, const sp<AnotherPacketSource> &source, bool discard) { + sp<MetaData> format = source->getFormat(); + if (format != NULL) { + // for simplicity, store a reference to the format in each unit + accessUnit->meta()->setObject("format", format); + } + + if (discard) { + accessUnit->meta()->setInt32("discard", discard); + } + + accessUnit->meta()->setInt32("discontinuitySeq", mDiscontinuitySeq); + accessUnit->meta()->setInt64("segmentStartTimeUs", getSegmentStartTimeUs(mSeqNumber)); + return accessUnit; +} + status_t PlaylistFetcher::extractAndQueueAccessUnitsFromTs(const sp<ABuffer> &buffer) { if (mTSParser == NULL) { // Use TS_TIMESTAMPS_ARE_ABSOLUTE so pts carry over between fetchers. @@ -1046,7 +1122,9 @@ status_t PlaylistFetcher::extractAndQueueAccessUnitsFromTs(const sp<ABuffer> &bu mTSParser->signalDiscontinuity( ATSParser::DISCONTINUITY_SEEK, extra); + mAbsoluteTimeAnchorUs = mNextPTSTimeUs; mNextPTSTimeUs = -1ll; + mFirstPTSValid = false; } size_t offset = 0; @@ -1099,46 +1177,30 @@ status_t PlaylistFetcher::extractAndQueueAccessUnitsFromTs(const sp<ABuffer> &bu continue; } - if (stream == LiveSession::STREAMTYPE_VIDEO && mVideoMime.empty()) { - const char *mime; - if (source->getFormat()->findCString(kKeyMIMEType, &mime)) { - mVideoMime.setTo(mime); - if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) { - mSkipToFirstIDRAfterConnect = true; - } - } - } - int64_t timeUs; sp<ABuffer> accessUnit; status_t finalResult; while (source->hasBufferAvailable(&finalResult) && source->dequeueAccessUnit(&accessUnit) == OK) { - if (stream == LiveSession::STREAMTYPE_VIDEO && mSkipToFirstIDRAfterConnect) { - if (!IsIDR(accessUnit)) { - continue; - } else { - mSkipToFirstIDRAfterConnect = false; - } - } - CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs)); - if (mMinStartTimeUs > 0) { - if (timeUs < mMinStartTimeUs) { - // TODO untested path - // try a later ts - int32_t targetDuration; - mPlaylist->meta()->findInt32("target-duration", &targetDuration); - int32_t incr = (mMinStartTimeUs - timeUs) / 1000000 / targetDuration; - if (incr == 0) { - // increment mSeqNumber by at least one - incr = 1; + + if (mStartup) { + if (!mFirstPTSValid) { + mFirstTimeUs = timeUs; + mFirstPTSValid = true; + } + if (mStartTimeUsRelative) { + timeUs -= mFirstTimeUs; + if (timeUs < 0) { + timeUs = 0; + } + } else if (mAdaptive && timeUs > mStartTimeUs) { + int32_t seq; + if (mStartTimeUsNotify != NULL + && !mStartTimeUsNotify->findInt32("discontinuitySeq", &seq)) { + mStartTimeUsNotify->setInt32("discontinuitySeq", mDiscontinuitySeq); } - mSeqNumber += incr; - err = -EAGAIN; - break; - } else { int64_t startTimeUs; if (mStartTimeUsNotify != NULL && !mStartTimeUsNotify->findInt64(key, &startTimeUs)) { @@ -1155,12 +1217,51 @@ status_t PlaylistFetcher::extractAndQueueAccessUnitsFromTs(const sp<ABuffer> &bu } } } + + if (timeUs < mStartTimeUs) { + if (mAdaptive) { + int32_t targetDuration; + mPlaylist->meta()->findInt32("target-duration", &targetDuration); + int32_t incr = (mStartTimeUs - timeUs) / 1000000 / targetDuration; + if (incr == 0) { + // increment mSeqNumber by at least one + incr = 1; + } + mSeqNumber += incr; + err = -EAGAIN; + break; + } else { + // 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(); + } + if (isAvc) { + mVideoBuffer->queueAccessUnit(accessUnit); + } + + continue; + } + } } if (mStopParams != NULL) { // Queue discontinuity in original stream. + int32_t discontinuitySeq; int64_t stopTimeUs; - if (!mStopParams->findInt64(key, &stopTimeUs) || timeUs >= stopTimeUs) { + if (!mStopParams->findInt32("discontinuitySeq", &discontinuitySeq) + || discontinuitySeq > mDiscontinuitySeq + || !mStopParams->findInt64(key, &stopTimeUs) + || (discontinuitySeq == mDiscontinuitySeq + && timeUs >= stopTimeUs)) { packetSource->queueAccessUnit(mSession->createFormatChangeBuffer()); mStreamTypeMask &= ~stream; mPacketSources.removeItemsAt(i); @@ -1169,15 +1270,18 @@ status_t PlaylistFetcher::extractAndQueueAccessUnitsFromTs(const sp<ABuffer> &bu } // Note that we do NOT dequeue any discontinuities except for format change. - - // for simplicity, store a reference to the format in each unit - sp<MetaData> format = source->getFormat(); - if (format != NULL) { - accessUnit->meta()->setObject("format", format); + if (stream == LiveSession::STREAMTYPE_VIDEO) { + const bool discard = true; + status_t status; + while (mVideoBuffer->hasBufferAvailable(&status)) { + sp<ABuffer> videoBuffer; + mVideoBuffer->dequeueAccessUnit(&videoBuffer); + setAccessUnitProperties(videoBuffer, source, discard); + packetSource->queueAccessUnit(videoBuffer); + } } - // Stash the sequence number so we can hint future playlist where to start at. - accessUnit->meta()->setInt32("seq", mSeqNumber); + setAccessUnitProperties(accessUnit, source); packetSource->queueAccessUnit(accessUnit); } @@ -1244,7 +1348,8 @@ status_t PlaylistFetcher::extractAndQueueAccessUnits( CHECK(itemMeta->findInt64("durationUs", &durationUs)); buffer->meta()->setInt64("timeUs", getSegmentStartTimeUs(mSeqNumber)); buffer->meta()->setInt64("durationUs", durationUs); - buffer->meta()->setInt32("seq", mSeqNumber); + buffer->meta()->setInt64("segmentStartTimeUs", getSegmentStartTimeUs(mSeqNumber)); + buffer->meta()->setInt32("discontinuitySeq", mDiscontinuitySeq); packetSource->queueAccessUnit(buffer); return OK; @@ -1310,14 +1415,6 @@ status_t PlaylistFetcher::extractAndQueueAccessUnits( firstID3Tag = false; } - if (!mFirstPTSValid) { - mFirstPTSValid = true; - mFirstPTS = PTS; - } - PTS -= mFirstPTS; - - int64_t timeUs = (PTS * 100ll) / 9ll + mAbsoluteTimeAnchorUs; - if (mStreamTypeMask != LiveSession::STREAMTYPE_AUDIO) { ALOGW("This stream only contains audio data!"); @@ -1360,6 +1457,12 @@ status_t PlaylistFetcher::extractAndQueueAccessUnits( int32_t sampleRate; CHECK(packetSource->getFormat()->findInt32(kKeySampleRate, &sampleRate)); + int64_t timeUs = (PTS * 100ll) / 9ll; + if (!mFirstPTSValid) { + mFirstPTSValid = true; + mFirstTimeUs = timeUs; + } + size_t offset = 0; while (offset < buffer->size()) { const uint8_t *adtsHeader = buffer->data() + offset; @@ -1384,19 +1487,32 @@ status_t PlaylistFetcher::extractAndQueueAccessUnits( CHECK_LE(offset + aac_frame_length, buffer->size()); - sp<ABuffer> unit = new ABuffer(aac_frame_length); - memcpy(unit->data(), adtsHeader, aac_frame_length); - int64_t unitTimeUs = timeUs + numSamples * 1000000ll / sampleRate; - unit->meta()->setInt64("timeUs", unitTimeUs); + offset += aac_frame_length; // Each AAC frame encodes 1024 samples. numSamples += 1024; - unit->meta()->setInt32("seq", mSeqNumber); - packetSource->queueAccessUnit(unit); + if (mStartup) { + int64_t startTimeUs = unitTimeUs; + if (mStartTimeUsRelative) { + startTimeUs -= mFirstTimeUs; + if (startTimeUs < 0) { + startTimeUs = 0; + } + } + if (startTimeUs < mStartTimeUs) { + continue; + } + } - offset += aac_frame_length; + sp<ABuffer> unit = new ABuffer(aac_frame_length); + memcpy(unit->data(), adtsHeader, aac_frame_length); + + unit->meta()->setInt64("timeUs", unitTimeUs); + unit->meta()->setInt64("segmentStartTimeUs", getSegmentStartTimeUs(mSeqNumber)); + unit->meta()->setInt32("discontinuitySeq", mDiscontinuitySeq); + packetSource->queueAccessUnit(unit); } return OK; diff --git a/media/libstagefright/httplive/PlaylistFetcher.h b/media/libstagefright/httplive/PlaylistFetcher.h index e4fdbff..daefb26 100644 --- a/media/libstagefright/httplive/PlaylistFetcher.h +++ b/media/libstagefright/httplive/PlaylistFetcher.h @@ -57,13 +57,15 @@ struct PlaylistFetcher : public AHandler { const sp<AnotherPacketSource> &audioSource, const sp<AnotherPacketSource> &videoSource, const sp<AnotherPacketSource> &subtitleSource, - int64_t startTimeUs = -1ll, - int64_t minStartTimeUs = 0ll /* start after this timestamp */, - int32_t startSeqNumberHint = -1 /* try starting at this sequence number */); + int64_t startTimeUs = -1ll, // starting timestamps + int64_t segmentStartTimeUs = -1ll, // starting position within playlist + // startTimeUs!=segmentStartTimeUs only when playlist is live + int32_t startDiscontinuitySeq = 0, + bool adaptive = false); void pauseAsync(); - void stopAsync(bool selfTriggered = false); + void stopAsync(bool clear = true); void resumeUntilAsync(const sp<AMessage> ¶ms); @@ -99,11 +101,12 @@ private: sp<LiveSession> mSession; AString mURI; - AString mVideoMime; uint32_t mStreamTypeMask; int64_t mStartTimeUs; - int64_t mMinStartTimeUs; // start fetching no earlier than this value + int64_t mSegmentStartTimeUs; + ssize_t mDiscontinuitySeq; + bool mStartTimeUsRelative; sp<AMessage> mStopParams; // message containing the latest timestamps we should fetch. KeyedVector<LiveSession::StreamType, sp<AnotherPacketSource> > @@ -116,8 +119,8 @@ private: int32_t mSeqNumber; int32_t mNumRetries; bool mStartup; + bool mAdaptive; bool mPrepared; - bool mSkipToFirstIDRAfterConnect; int64_t mNextPTSTimeUs; int32_t mMonitorQueueGeneration; @@ -136,7 +139,9 @@ private: bool mFirstPTSValid; uint64_t mFirstPTS; + int64_t mFirstTimeUs; int64_t mAbsoluteTimeAnchorUs; + sp<AnotherPacketSource> mVideoBuffer; // Stores the initialization vector to decrypt the next block of cipher text, which can // either be derived from the sequence number, read from the manifest, or copied from @@ -175,6 +180,10 @@ private: // Resume a fetcher to continue until the stopping point stored in msg. status_t onResumeUntil(const sp<AMessage> &msg); + const sp<ABuffer> &setAccessUnitProperties( + const sp<ABuffer> &accessUnit, + const sp<AnotherPacketSource> &source, + bool discard = false); status_t extractAndQueueAccessUnitsFromTs(const sp<ABuffer> &buffer); status_t extractAndQueueAccessUnits( @@ -185,6 +194,8 @@ private: void queueDiscontinuity( ATSParser::DiscontinuityType type, const sp<AMessage> &extra); + int32_t getSeqNumberWithAnchorTime(int64_t anchorTimeUs) const; + int32_t getSeqNumberForDiscontinuity(size_t discontinuitySeq) const; int32_t getSeqNumberForTime(int64_t timeUs) const; void updateDuration(); diff --git a/media/libstagefright/include/SoftVideoDecoderOMXComponent.h b/media/libstagefright/include/SoftVideoDecoderOMXComponent.h index d050fa6..7f200dd 100644 --- a/media/libstagefright/include/SoftVideoDecoderOMXComponent.h +++ b/media/libstagefright/include/SoftVideoDecoderOMXComponent.h @@ -27,8 +27,6 @@ #include <utils/threads.h> #include <utils/Vector.h> -#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a))) - namespace android { struct SoftVideoDecoderOMXComponent : public SimpleSoftOMXComponent { diff --git a/media/libstagefright/include/WVMExtractor.h b/media/libstagefright/include/WVMExtractor.h index 8e62946..ab7e8b8 100644 --- a/media/libstagefright/include/WVMExtractor.h +++ b/media/libstagefright/include/WVMExtractor.h @@ -49,6 +49,7 @@ public: virtual sp<MediaSource> getTrack(size_t index); virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags); virtual sp<MetaData> getMetaData(); + virtual void setUID(uid_t uid); // Return the amount of data cached from the current // playback positiion (in us). @@ -74,8 +75,6 @@ public: // codec. void setCryptoPluginMode(bool cryptoPluginMode); - void setUID(uid_t uid); - static bool getVendorLibHandle(); status_t getError(); diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp index eda6387..6d8866a 100644 --- a/media/libstagefright/mpeg2ts/ATSParser.cpp +++ b/media/libstagefright/mpeg2ts/ATSParser.cpp @@ -894,6 +894,12 @@ void ATSParser::Stream::onPayloadData( ALOGV("Stream PID 0x%08x of type 0x%02x now has data.", mElementaryPID, mStreamType); + const char *mime; + if (meta->findCString(kKeyMIMEType, &mime) + && !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC) + && !IsIDR(accessUnit)) { + continue; + } mSource = new AnotherPacketSource(meta); mSource->queueAccessUnit(accessUnit); } diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp index 72c9dae..010063f 100644 --- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp +++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp @@ -14,6 +14,9 @@ * limitations under the License. */ +//#define LOG_NDEBUG 0 +#define LOG_TAG "AnotherPacketSource" + #include "AnotherPacketSource.h" #include <media/stagefright/foundation/ABuffer.h> @@ -38,7 +41,8 @@ AnotherPacketSource::AnotherPacketSource(const sp<MetaData> &meta) mFormat(NULL), mLastQueuedTimeUs(0), mEOSResult(OK), - mLatestEnqueuedMeta(NULL) { + mLatestEnqueuedMeta(NULL), + mLatestDequeuedMeta(NULL) { setFormat(meta); } @@ -92,7 +96,7 @@ sp<MetaData> AnotherPacketSource::getFormat() { sp<RefBase> object; if (buffer->meta()->findObject("format", &object)) { - return static_cast<MetaData*>(object.get()); + return mFormat = static_cast<MetaData*>(object.get()); } ++it; @@ -121,6 +125,8 @@ status_t AnotherPacketSource::dequeueAccessUnit(sp<ABuffer> *buffer) { return INFO_DISCONTINUITY; } + mLatestDequeuedMeta = (*buffer)->meta()->dup(); + sp<RefBase> object; if ((*buffer)->meta()->findObject("format", &object)) { mFormat = static_cast<MetaData*>(object.get()); @@ -142,8 +148,10 @@ status_t AnotherPacketSource::read( } if (!mBuffers.empty()) { + const sp<ABuffer> buffer = *mBuffers.begin(); mBuffers.erase(mBuffers.begin()); + mLatestDequeuedMeta = buffer->meta()->dup(); int32_t discontinuity; if (buffer->meta()->findInt32("discontinuity", &discontinuity)) { @@ -202,7 +210,7 @@ void AnotherPacketSource::queueAccessUnit(const sp<ABuffer> &buffer) { mBuffers.push_back(buffer); mCondition.signal(); - if (!mLatestEnqueuedMeta.get()) { + if (mLatestEnqueuedMeta == NULL) { mLatestEnqueuedMeta = buffer->meta(); } else { int64_t latestTimeUs = 0; @@ -341,9 +349,14 @@ bool AnotherPacketSource::isFinished(int64_t duration) const { return (mEOSResult != OK); } -sp<AMessage> AnotherPacketSource::getLatestMeta() { +sp<AMessage> AnotherPacketSource::getLatestEnqueuedMeta() { Mutex::Autolock autoLock(mLock); return mLatestEnqueuedMeta; } +sp<AMessage> AnotherPacketSource::getLatestDequeuedMeta() { + Mutex::Autolock autoLock(mLock); + return mLatestDequeuedMeta; +} + } // namespace android diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.h b/media/libstagefright/mpeg2ts/AnotherPacketSource.h index f38f9dc..0c717d7 100644 --- a/media/libstagefright/mpeg2ts/AnotherPacketSource.h +++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.h @@ -64,7 +64,8 @@ struct AnotherPacketSource : public MediaSource { bool isFinished(int64_t duration) const; - sp<AMessage> getLatestMeta(); + sp<AMessage> getLatestEnqueuedMeta(); + sp<AMessage> getLatestDequeuedMeta(); protected: virtual ~AnotherPacketSource(); @@ -80,6 +81,7 @@ private: List<sp<ABuffer> > mBuffers; status_t mEOSResult; sp<AMessage> mLatestEnqueuedMeta; + sp<AMessage> mLatestDequeuedMeta; bool wasFormatChange(int32_t discontinuityType) const; |