diff options
Diffstat (limited to 'media/libmediaplayerservice')
44 files changed, 5571 insertions, 1188 deletions
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk index a645f13..2cf5710 100644 --- a/media/libmediaplayerservice/Android.mk +++ b/media/libmediaplayerservice/Android.mk @@ -22,12 +22,14 @@ LOCAL_SRC_FILES:= \ StagefrightPlayer.cpp \ StagefrightRecorder.cpp \ TestPlayerStub.cpp \ + VideoFrameScheduler.cpp \ LOCAL_SHARED_LIBRARIES := \ libbinder \ libcamera_client \ libcrypto \ libcutils \ + libdrmframework \ liblog \ libdl \ libgui \ @@ -46,10 +48,10 @@ LOCAL_STATIC_LIBRARIES := \ libstagefright_rtsp \ LOCAL_C_INCLUDES := \ - $(call include-path-for, graphics corecg) \ $(TOP)/frameworks/av/media/libstagefright/include \ $(TOP)/frameworks/av/media/libstagefright/rtsp \ $(TOP)/frameworks/av/media/libstagefright/wifi-display \ + $(TOP)/frameworks/av/media/libstagefright/webm \ $(TOP)/frameworks/native/include/media/openmax \ $(TOP)/external/tremolo/Tremolo \ diff --git a/media/libmediaplayerservice/Drm.cpp b/media/libmediaplayerservice/Drm.cpp index eebcb79..d222316 100644 --- a/media/libmediaplayerservice/Drm.cpp +++ b/media/libmediaplayerservice/Drm.cpp @@ -28,9 +28,21 @@ #include <media/stagefright/foundation/AString.h> #include <media/stagefright/foundation/hexdump.h> #include <media/stagefright/MediaErrors.h> +#include <binder/IServiceManager.h> +#include <binder/IPCThreadState.h> namespace android { +static bool checkPermission(const char* permissionString) { +#ifndef HAVE_ANDROID_OS + return true; +#endif + if (getpid() == IPCThreadState::self()->getCallingPid()) return true; + bool ok = checkCallingPermission(String16(permissionString)); + if (!ok) ALOGE("Request requires %s", permissionString); + return ok; +} + KeyedVector<Vector<uint8_t>, String8> Drm::mUUIDToLibraryPathMap; KeyedVector<String8, wp<SharedLibrary> > Drm::mLibraryPathToOpenLibraryMap; Mutex Drm::mMapLock; @@ -373,7 +385,8 @@ status_t Drm::queryKeyStatus(Vector<uint8_t> const &sessionId, return mPlugin->queryKeyStatus(sessionId, infoMap); } -status_t Drm::getProvisionRequest(Vector<uint8_t> &request, String8 &defaultUrl) { +status_t Drm::getProvisionRequest(String8 const &certType, String8 const &certAuthority, + Vector<uint8_t> &request, String8 &defaultUrl) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { @@ -384,10 +397,13 @@ status_t Drm::getProvisionRequest(Vector<uint8_t> &request, String8 &defaultUrl) return -EINVAL; } - return mPlugin->getProvisionRequest(request, defaultUrl); + return mPlugin->getProvisionRequest(certType, certAuthority, + request, defaultUrl); } -status_t Drm::provideProvisionResponse(Vector<uint8_t> const &response) { +status_t Drm::provideProvisionResponse(Vector<uint8_t> const &response, + Vector<uint8_t> &certificate, + Vector<uint8_t> &wrappedKey) { Mutex::Autolock autoLock(mLock); if (mInitCheck != OK) { @@ -398,9 +414,26 @@ status_t Drm::provideProvisionResponse(Vector<uint8_t> const &response) { return -EINVAL; } - return mPlugin->provideProvisionResponse(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); @@ -589,6 +622,28 @@ status_t Drm::verify(Vector<uint8_t> const &sessionId, return mPlugin->verify(sessionId, keyId, message, signature, match); } +status_t Drm::signRSA(Vector<uint8_t> const &sessionId, + String8 const &algorithm, + Vector<uint8_t> const &message, + Vector<uint8_t> const &wrappedKey, + Vector<uint8_t> &signature) { + Mutex::Autolock autoLock(mLock); + + if (mInitCheck != OK) { + return mInitCheck; + } + + if (mPlugin == NULL) { + return -EINVAL; + } + + if (!checkPermission("android.permission.ACCESS_DRM_CERTIFICATES")) { + return -EPERM; + } + + return mPlugin->signRSA(sessionId, algorithm, message, wrappedKey, signature); +} + void Drm::binderDied(const wp<IBinder> &the_late_who) { delete mPlugin; diff --git a/media/libmediaplayerservice/Drm.h b/media/libmediaplayerservice/Drm.h index 119fd50..9e23e2e 100644 --- a/media/libmediaplayerservice/Drm.h +++ b/media/libmediaplayerservice/Drm.h @@ -66,10 +66,16 @@ struct Drm : public BnDrm, virtual status_t queryKeyStatus(Vector<uint8_t> const &sessionId, KeyedVector<String8, String8> &infoMap) const; - virtual status_t getProvisionRequest(Vector<uint8_t> &request, + virtual status_t getProvisionRequest(String8 const &certType, + String8 const &certAuthority, + Vector<uint8_t> &request, String8 &defaulUrl); - virtual status_t provideProvisionResponse(Vector<uint8_t> const &response); + virtual status_t provideProvisionResponse(Vector<uint8_t> const &response, + Vector<uint8_t> &certificate, + Vector<uint8_t> &wrappedKey); + + virtual status_t unprovisionDevice(); virtual status_t getSecureStops(List<Vector<uint8_t> > &secureStops); @@ -111,6 +117,12 @@ struct Drm : public BnDrm, Vector<uint8_t> const &signature, bool &match); + virtual status_t signRSA(Vector<uint8_t> const &sessionId, + String8 const &algorithm, + Vector<uint8_t> const &message, + Vector<uint8_t> const &wrappedKey, + Vector<uint8_t> &signature); + virtual status_t setListener(const sp<IDrmClient>& listener); virtual void sendEvent(DrmPlugin::EventType eventType, int extra, diff --git a/media/libmediaplayerservice/HDCP.cpp b/media/libmediaplayerservice/HDCP.cpp index c2ac1a3..afe3936 100644 --- a/media/libmediaplayerservice/HDCP.cpp +++ b/media/libmediaplayerservice/HDCP.cpp @@ -107,11 +107,7 @@ uint32_t HDCP::getCaps() { return NO_INIT; } - // TO-DO: - // Only support HDCP_CAPS_ENCRYPT (byte-array to byte-array) for now. - // use mHDCPModule->getCaps() when the HDCP libraries get updated. - //return mHDCPModule->getCaps(); - return HDCPModule::HDCP_CAPS_ENCRYPT; + return mHDCPModule->getCaps(); } status_t HDCP::encrypt( diff --git a/media/libmediaplayerservice/MediaPlayerFactory.cpp b/media/libmediaplayerservice/MediaPlayerFactory.cpp index 09e6976..3e0fc0d 100644 --- a/media/libmediaplayerservice/MediaPlayerFactory.cpp +++ b/media/libmediaplayerservice/MediaPlayerFactory.cpp @@ -60,14 +60,20 @@ status_t MediaPlayerFactory::registerFactory_l(IFactory* factory, return OK; } -player_type MediaPlayerFactory::getDefaultPlayerType() { +static player_type getDefaultPlayerType() { char value[PROPERTY_VALUE_MAX]; - if (property_get("media.stagefright.use-nuplayer", value, NULL) + if (property_get("media.stagefright.use-awesome", value, NULL) && (!strcmp("1", value) || !strcasecmp("true", value))) { - return NU_PLAYER; + return STAGEFRIGHT_PLAYER; } - return STAGEFRIGHT_PLAYER; + // TODO: remove this EXPERIMENTAL developer settings property + if (property_get("persist.sys.media.use-awesome", value, NULL) + && !strcasecmp("true", value)) { + return STAGEFRIGHT_PLAYER; + } + + return NU_PLAYER; } status_t MediaPlayerFactory::registerFactory(IFactory* factory, @@ -175,16 +181,19 @@ class StagefrightPlayerFactory : int64_t offset, int64_t /*length*/, float /*curScore*/) { - char buf[20]; - lseek(fd, offset, SEEK_SET); - read(fd, buf, sizeof(buf)); - lseek(fd, offset, SEEK_SET); - - long ident = *((long*)buf); - - // Ogg vorbis? - if (ident == 0x5367674f) // 'OggS' - return 1.0; + if (getDefaultPlayerType() + == STAGEFRIGHT_PLAYER) { + char buf[20]; + lseek(fd, offset, SEEK_SET); + read(fd, buf, sizeof(buf)); + lseek(fd, offset, SEEK_SET); + + uint32_t ident = *((uint32_t*)buf); + + // Ogg vorbis? + if (ident == 0x5367674f) // 'OggS' + return 1.0; + } return 0.0; } diff --git a/media/libmediaplayerservice/MediaPlayerFactory.h b/media/libmediaplayerservice/MediaPlayerFactory.h index 5ddde19..55ff918 100644 --- a/media/libmediaplayerservice/MediaPlayerFactory.h +++ b/media/libmediaplayerservice/MediaPlayerFactory.h @@ -71,7 +71,6 @@ class MediaPlayerFactory { static status_t registerFactory_l(IFactory* factory, player_type type); - static player_type getDefaultPlayerType(); static Mutex sLock; static tFactoryMap sFactoryMap; diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp index a392b76..8eb1269 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -34,6 +34,7 @@ #include <utils/misc.h> +#include <binder/IBatteryStats.h> #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> #include <binder/MemoryHeapBase.h> @@ -42,8 +43,10 @@ #include <utils/Errors.h> // for status_t #include <utils/String8.h> #include <utils/SystemClock.h> +#include <utils/Timers.h> #include <utils/Vector.h> +#include <media/IMediaHTTPService.h> #include <media/IRemoteDisplay.h> #include <media/IRemoteDisplayClient.h> #include <media/MediaPlayerInterface.h> @@ -52,6 +55,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> @@ -185,6 +189,63 @@ bool findMetadata(const Metadata::Filter& filter, const int32_t val) } // anonymous namespace +namespace { +using android::Parcel; +using android::String16; + +// marshalling tag indicating flattened utf16 tags +// keep in sync with frameworks/base/media/java/android/media/AudioAttributes.java +const int32_t kAudioAttributesMarshallTagFlattenTags = 1; + +// Audio attributes format in a parcel: +// +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | usage | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | content_type | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | flags | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | kAudioAttributesMarshallTagFlattenTags | // ignore tags if not found +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | flattened tags in UTF16 | +// | ... | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// @param p Parcel that contains audio attributes. +// @param[out] attributes On exit points to an initialized audio_attributes_t structure +// @param[out] status On exit contains the status code to be returned. +void unmarshallAudioAttributes(const Parcel& parcel, audio_attributes_t *attributes) +{ + attributes->usage = (audio_usage_t) parcel.readInt32(); + attributes->content_type = (audio_content_type_t) parcel.readInt32(); + attributes->source = (audio_source_t) parcel.readInt32(); + attributes->flags = (audio_flags_mask_t) parcel.readInt32(); + const bool hasFlattenedTag = (parcel.readInt32() == kAudioAttributesMarshallTagFlattenTags); + if (hasFlattenedTag) { + // the tags are UTF16, convert to UTF8 + String16 tags = parcel.readString16(); + ssize_t realTagSize = utf16_to_utf8_length(tags.string(), tags.size()); + if (realTagSize <= 0) { + strcpy(attributes->tags, ""); + } else { + // copy the flattened string into the attributes as the destination for the conversion: + // copying array size -1, array for tags was calloc'd, no need to NULL-terminate it + size_t tagSize = realTagSize > AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1 ? + AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1 : realTagSize; + utf16_to_utf8(tags.string(), tagSize, attributes->tags); + } + } else { + ALOGE("unmarshallAudioAttributes() received unflattened tags, ignoring tag values"); + strcpy(attributes->tags, ""); + } +} +} // anonymous namespace + + namespace android { static bool checkPermission(const char* permissionString) { @@ -220,6 +281,20 @@ MediaPlayerService::MediaPlayerService() // speaker is on by default mBatteryAudio.deviceOn[SPEAKER] = 1; + // reset battery stats + // if the mediaserver has crashed, battery stats could be left + // in bad state, reset the state upon service start. + const sp<IServiceManager> sm(defaultServiceManager()); + if (sm != NULL) { + const String16 name("batterystats"); + sp<IBatteryStats> batteryStats = + interface_cast<IBatteryStats>(sm->getService(name)); + if (batteryStats != NULL) { + batteryStats->noteResetVideo(); + batteryStats->noteResetAudio(); + } + } + MediaPlayerFactory::registerBuiltinFactories(); } @@ -275,6 +350,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); @@ -306,12 +385,7 @@ sp<IRemoteDisplay> MediaPlayerService::listenForRemoteDisplay( return new RemoteDisplay(client, iface.string()); } -status_t MediaPlayerService::updateProxyConfig( - const char *host, int32_t port, const char *exclusionList) { - return HTTPBase::UpdateProxyConfig(host, port, exclusionList); -} - -status_t MediaPlayerService::AudioCache::dump(int fd, const Vector<String16>& args) const +status_t MediaPlayerService::AudioCache::dump(int fd, const Vector<String16>& /*args*/) const { const size_t SIZE = 256; char buffer[SIZE]; @@ -512,6 +586,7 @@ MediaPlayerService::Client::Client( mAudioSessionId = audioSessionId; mUID = uid; mRetransmitEndpointValid = false; + mAudioAttributes = NULL; #if CALLBACK_ANTAGONIZER ALOGD("create Antagonizer"); @@ -526,6 +601,9 @@ MediaPlayerService::Client::~Client() wp<Client> client(this); disconnect(); mService->removeClient(client); + if (mAudioAttributes != NULL) { + free(mAudioAttributes); + } } void MediaPlayerService::Client::disconnect() @@ -590,7 +668,8 @@ sp<MediaPlayerBase> MediaPlayerService::Client::setDataSource_pre( } if (!p->hardwareOutput()) { - mAudioOutput = new AudioOutput(mAudioSessionId, IPCThreadState::self()->getCallingUid()); + mAudioOutput = new AudioOutput(mAudioSessionId, IPCThreadState::self()->getCallingUid(), + mPid, mAudioAttributes); static_cast<MediaPlayerInterface*>(p.get())->setAudioSink(mAudioOutput); } @@ -622,7 +701,9 @@ void MediaPlayerService::Client::setDataSource_post( } status_t MediaPlayerService::Client::setDataSource( - const char *url, const KeyedVector<String8, String8> *headers) + const sp<IMediaHTTPService> &httpService, + const char *url, + const KeyedVector<String8, String8> *headers) { ALOGV("setDataSource(%s)", url); if (url == NULL) @@ -657,7 +738,7 @@ status_t MediaPlayerService::Client::setDataSource( return NO_INIT; } - setDataSource_post(p, p->setDataSource(url, headers)); + setDataSource_post(p, p->setDataSource(httpService, url, headers)); return mStatus; } } @@ -674,8 +755,8 @@ status_t MediaPlayerService::Client::setDataSource(int fd, int64_t offset, int64 ALOGV("st_dev = %llu", sb.st_dev); ALOGV("st_mode = %u", sb.st_mode); - ALOGV("st_uid = %lu", sb.st_uid); - ALOGV("st_gid = %lu", sb.st_gid); + ALOGV("st_uid = %lu", static_cast<unsigned long>(sb.st_uid)); + ALOGV("st_gid = %lu", static_cast<unsigned long>(sb.st_gid)); ALOGV("st_size = %llu", sb.st_size); if (offset >= sb.st_size) { @@ -804,7 +885,7 @@ status_t MediaPlayerService::Client::setMetadataFilter(const Parcel& filter) } status_t MediaPlayerService::Client::getMetadata( - bool update_only, bool apply_filter, Parcel *reply) + bool update_only, bool /*apply_filter*/, Parcel *reply) { sp<MediaPlayerBase> player = getPlayer(); if (player == 0) return UNKNOWN_ERROR; @@ -969,6 +1050,22 @@ status_t MediaPlayerService::Client::setAudioStreamType(audio_stream_type_t type return NO_ERROR; } +status_t MediaPlayerService::Client::setAudioAttributes_l(const Parcel &parcel) +{ + if (mAudioAttributes != NULL) { free(mAudioAttributes); } + mAudioAttributes = (audio_attributes_t *) calloc(1, sizeof(audio_attributes_t)); + unmarshallAudioAttributes(parcel, mAudioAttributes); + + ALOGV("setAudioAttributes_l() usage=%d content=%d flags=0x%x tags=%s", + mAudioAttributes->usage, mAudioAttributes->content_type, mAudioAttributes->flags, + mAudioAttributes->tags); + + if (mAudioOutput != 0) { + mAudioOutput->setAudioAttributes(mAudioAttributes); + } + return NO_ERROR; +} + status_t MediaPlayerService::Client::setLooping(int loop) { ALOGV("[%d] setLooping(%d)", mConnId, loop); @@ -1017,9 +1114,17 @@ status_t MediaPlayerService::Client::attachAuxEffect(int effectId) status_t MediaPlayerService::Client::setParameter(int key, const Parcel &request) { ALOGV("[%d] setParameter(%d)", mConnId, key); - sp<MediaPlayerBase> p = getPlayer(); - if (p == 0) return UNKNOWN_ERROR; - return p->setParameter(key, request); + switch (key) { + case KEY_PARAMETER_AUDIO_ATTRIBUTES: + { + Mutex::Autolock l(mLock); + return setAudioAttributes_l(request); + } + default: + sp<MediaPlayerBase> p = getPlayer(); + if (p == 0) { return UNKNOWN_ERROR; } + return p->setParameter(key, request); + } } status_t MediaPlayerService::Client::getParameter(int key, Parcel *reply) { @@ -1176,9 +1281,14 @@ int Antagonizer::callbackThread(void* user) } #endif -status_t MediaPlayerService::decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, - audio_format_t* pFormat, - const sp<IMemoryHeap>& heap, size_t *pSize) +status_t MediaPlayerService::decode( + const sp<IMediaHTTPService> &httpService, + const char* url, + uint32_t *pSampleRate, + int* pNumChannels, + audio_format_t* pFormat, + const sp<IMemoryHeap>& heap, + size_t *pSize) { ALOGV("decode(%s)", url); sp<MediaPlayerBase> player; @@ -1206,7 +1316,7 @@ status_t MediaPlayerService::decode(const char* url, uint32_t *pSampleRate, int* static_cast<MediaPlayerInterface*>(player.get())->setAudioSink(cache); // set data source - if (player->setDataSource(url) != NO_ERROR) goto Exit; + if (player->setDataSource(httpService, url) != NO_ERROR) goto Exit; ALOGV("prepare"); player->prepareAsync(); @@ -1296,13 +1406,15 @@ Exit: #undef LOG_TAG #define LOG_TAG "AudioSink" -MediaPlayerService::AudioOutput::AudioOutput(int sessionId, int uid) +MediaPlayerService::AudioOutput::AudioOutput(int sessionId, int uid, int pid, + const audio_attributes_t* attr) : mCallback(NULL), mCallbackCookie(NULL), mCallbackData(NULL), mBytesWritten(0), mSessionId(sessionId), mUid(uid), + mPid(pid), mFlags(AUDIO_OUTPUT_FLAG_NONE) { ALOGV("AudioOutput(%d)", sessionId); mStreamType = AUDIO_STREAM_MUSIC; @@ -1314,6 +1426,7 @@ MediaPlayerService::AudioOutput::AudioOutput(int sessionId, int uid) mAuxEffectId = 0; mSendLevel = 0.0; setMinBufferCount(); + mAttributes = attr; } MediaPlayerService::AudioOutput::~AudioOutput() @@ -1384,6 +1497,12 @@ status_t MediaPlayerService::AudioOutput::getPosition(uint32_t *position) const return mTrack->getPosition(position); } +status_t MediaPlayerService::AudioOutput::getTimestamp(AudioTimestamp &ts) const +{ + if (mTrack == 0) return NO_INIT; + return mTrack->getTimestamp(ts); +} + status_t MediaPlayerService::AudioOutput::getFramesWritten(uint32_t *frameswritten) const { if (mTrack == 0) return NO_INIT; @@ -1403,6 +1522,10 @@ String8 MediaPlayerService::AudioOutput::getParameters(const String8& keys) return mTrack->getParameters(keys); } +void MediaPlayerService::AudioOutput::setAudioAttributes(const audio_attributes_t * attributes) { + mAttributes = attributes; +} + void MediaPlayerService::AudioOutput::deleteRecycledTrack() { ALOGV("deleteRecycledTrack"); @@ -1450,7 +1573,7 @@ status_t MediaPlayerService::AudioOutput::open( format, bufferCount, mSessionId, flags); uint32_t afSampleRate; size_t afFrameCount; - uint32_t frameCount; + size_t frameCount; // offloading is only supported in callback mode for now. // offloadInfo must be present if offload flag is set @@ -1551,7 +1674,9 @@ status_t MediaPlayerService::AudioOutput::open( mSessionId, AudioTrack::TRANSFER_CALLBACK, offloadInfo, - mUid); + mUid, + mPid, + mAttributes); } else { t = new AudioTrack( mStreamType, @@ -1566,13 +1691,19 @@ status_t MediaPlayerService::AudioOutput::open( mSessionId, AudioTrack::TRANSFER_DEFAULT, NULL, // offload info - mUid); + mUid, + mPid, + mAttributes); } if ((t == 0) || (t->initCheck() != NO_ERROR)) { ALOGE("Unable to create audio track"); delete newcbd; return NO_INIT; + } else { + // successful AudioTrack initialization implies a legacy stream type was generated + // from the audio attributes + mStreamType = t->streamType(); } } @@ -1672,12 +1803,14 @@ void MediaPlayerService::AudioOutput::switchToNextOutput() { ssize_t MediaPlayerService::AudioOutput::write(const void* buffer, size_t size) { - LOG_FATAL_IF(mCallback != NULL, "Don't call write if supplying a callback."); + LOG_ALWAYS_FATAL_IF(mCallback != NULL, "Don't call write if supplying a callback."); //ALOGV("write(%p, %u)", buffer, size); if (mTrack != 0) { ssize_t ret = mTrack->write(buffer, size); - mBytesWritten += ret; + if (ret >= 0) { + mBytesWritten += ret; + } return ret; } return NO_INIT; @@ -1777,7 +1910,8 @@ void MediaPlayerService::AudioOutput::CallbackWrapper( me, buffer->raw, buffer->size, me->mCallbackCookie, CB_EVENT_FILL_BUFFER); - if (actualSize == 0 && buffer->size > 0 && me->mNextOutput == NULL) { + if ((me->mFlags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) == 0 && + actualSize == 0 && buffer->size > 0 && me->mNextOutput == NULL) { // We've reached EOS but the audio track is not stopped yet, // keep playing silence. @@ -1823,7 +1957,7 @@ uint32_t MediaPlayerService::AudioOutput::getSampleRate() const #define LOG_TAG "AudioCache" MediaPlayerService::AudioCache::AudioCache(const sp<IMemoryHeap>& heap) : mHeap(heap), mChannelCount(0), mFrameCount(1024), mSampleRate(0), mSize(0), - mError(NO_ERROR), mCommandComplete(false) + mFrameSize(1), mError(NO_ERROR), mCommandComplete(false) { } @@ -1840,14 +1974,23 @@ float MediaPlayerService::AudioCache::msecsPerFrame() const status_t MediaPlayerService::AudioCache::getPosition(uint32_t *position) const { if (position == 0) return BAD_VALUE; - *position = mSize; + *position = mSize / mFrameSize; + return NO_ERROR; +} + +status_t MediaPlayerService::AudioCache::getTimestamp(AudioTimestamp &ts) const +{ + ts.mPosition = mSize / mFrameSize; + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + ts.mTime.tv_sec = now / 1000000000LL; + ts.mTime.tv_nsec = now - (1000000000LL * ts.mTime.tv_sec); return NO_ERROR; } status_t MediaPlayerService::AudioCache::getFramesWritten(uint32_t *written) const { if (written == 0) return BAD_VALUE; - *written = mSize; + *written = mSize / mFrameSize; return NO_ERROR; } @@ -1909,6 +2052,8 @@ bool CallbackThread::threadLoop() { if (actualSize > 0) { sink->write(mBuffer, actualSize); + // Could return false on sink->write() error or short count. + // Not necessarily appropriate but would work for AudioCache behavior. } return true; @@ -1919,8 +2064,8 @@ bool CallbackThread::threadLoop() { status_t MediaPlayerService::AudioCache::open( uint32_t sampleRate, int channelCount, audio_channel_mask_t channelMask, audio_format_t format, int bufferCount, - AudioCallback cb, void *cookie, audio_output_flags_t flags, - const audio_offload_info_t *offloadInfo) + AudioCallback cb, void *cookie, audio_output_flags_t /*flags*/, + const audio_offload_info_t* /*offloadInfo*/) { ALOGV("open(%u, %d, 0x%x, %d, %d)", sampleRate, channelCount, channelMask, format, bufferCount); if (mHeap->getHeapID() < 0) { @@ -1931,6 +2076,9 @@ status_t MediaPlayerService::AudioCache::open( mChannelCount = (uint16_t)channelCount; mFormat = format; mMsecsPerFrame = 1.e3 / (float) sampleRate; + mFrameSize = audio_is_linear_pcm(mFormat) + ? mChannelCount * audio_bytes_per_sample(mFormat) : 1; + mFrameCount = mHeap->getSize() / mFrameSize; if (cb != NULL) { mCallbackThread = new CallbackThread(this, cb, cookie); @@ -1960,12 +2108,26 @@ ssize_t MediaPlayerService::AudioCache::write(const void* buffer, size_t size) if (p == NULL) return NO_INIT; p += mSize; ALOGV("memcpy(%p, %p, %u)", p, buffer, size); - if (mSize + size > mHeap->getSize()) { + + bool overflow = mSize + size > mHeap->getSize(); + if (overflow) { ALOGE("Heap size overflow! req size: %d, max size: %d", (mSize + size), mHeap->getSize()); size = mHeap->getSize() - mSize; } + size -= size % mFrameSize; // consume only integral amounts of frame size memcpy(p, buffer, size); mSize += size; + + if (overflow) { + // Signal heap filled here (last frame may be truncated). + // After this point, no more data should be written as the + // heap is filled and the AudioCache should be effectively + // immutable with respect to future writes. + // + // It is thus safe for another thread to read the AudioCache. + mCommandComplete = true; + mSignal.signal(); + } return size; } @@ -1987,7 +2149,7 @@ status_t MediaPlayerService::AudioCache::wait() } void MediaPlayerService::AudioCache::notify( - void* cookie, int msg, int ext1, int ext2, const Parcel *obj) + void* cookie, int msg, int ext1, int ext2, const Parcel* /*obj*/) { ALOGV("notify(%p, %d, %d, %d)", cookie, msg, ext1, ext2); AudioCache* p = static_cast<AudioCache*>(cookie); diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h index 9c084e1..3b96e88 100644 --- a/media/libmediaplayerservice/MediaPlayerService.h +++ b/media/libmediaplayerservice/MediaPlayerService.h @@ -72,7 +72,8 @@ class MediaPlayerService : public BnMediaPlayerService class CallbackData; public: - AudioOutput(int sessionId, int uid); + AudioOutput(int sessionId, int uid, int pid, + const audio_attributes_t * attr); virtual ~AudioOutput(); virtual bool ready() const { return mTrack != 0; } @@ -84,6 +85,7 @@ class MediaPlayerService : public BnMediaPlayerService virtual uint32_t latency() const; virtual float msecsPerFrame() const; virtual status_t getPosition(uint32_t *position) const; + virtual status_t getTimestamp(AudioTimestamp &ts) const; virtual status_t getFramesWritten(uint32_t *frameswritten) const; virtual int getSessionId() const; virtual uint32_t getSampleRate() const; @@ -104,6 +106,7 @@ class MediaPlayerService : public BnMediaPlayerService void setAudioStreamType(audio_stream_type_t streamType) { mStreamType = streamType; } virtual audio_stream_type_t getAudioStreamType() const { return mStreamType; } + void setAudioAttributes(const audio_attributes_t * attributes); void setVolume(float left, float right); virtual status_t setPlaybackRatePermille(int32_t ratePermille); @@ -133,6 +136,7 @@ class MediaPlayerService : public BnMediaPlayerService CallbackData * mCallbackData; uint64_t mBytesWritten; audio_stream_type_t mStreamType; + const audio_attributes_t *mAttributes; float mLeftVolume; float mRightVolume; int32_t mPlaybackRatePermille; @@ -140,6 +144,7 @@ class MediaPlayerService : public BnMediaPlayerService float mMsecsPerFrame; int mSessionId; int mUid; + int mPid; float mSendLevel; int mAuxEffectId; static bool mIsOnEmulator; @@ -190,10 +195,11 @@ class MediaPlayerService : public BnMediaPlayerService virtual ssize_t bufferSize() const { return frameSize() * mFrameCount; } virtual ssize_t frameCount() const { return mFrameCount; } virtual ssize_t channelCount() const { return (ssize_t)mChannelCount; } - virtual ssize_t frameSize() const { return ssize_t(mChannelCount * ((mFormat == AUDIO_FORMAT_PCM_16_BIT)?sizeof(int16_t):sizeof(u_int8_t))); } + virtual ssize_t frameSize() const { return (ssize_t)mFrameSize; } virtual uint32_t latency() const; virtual float msecsPerFrame() const; virtual status_t getPosition(uint32_t *position) const; + virtual status_t getTimestamp(AudioTimestamp &ts) const; virtual status_t getFramesWritten(uint32_t *frameswritten) const; virtual int getSessionId() const; virtual uint32_t getSampleRate() const; @@ -211,12 +217,12 @@ class MediaPlayerService : public BnMediaPlayerService virtual void flush() {} virtual void pause() {} virtual void close() {} - void setAudioStreamType(audio_stream_type_t streamType) {} + void setAudioStreamType(audio_stream_type_t streamType __unused) {} // stream type is not used for AudioCache virtual audio_stream_type_t getAudioStreamType() const { return AUDIO_STREAM_DEFAULT; } - void setVolume(float left, float right) {} - virtual status_t setPlaybackRatePermille(int32_t ratePermille) { return INVALID_OPERATION; } + void setVolume(float left __unused, float right __unused) {} + virtual status_t setPlaybackRatePermille(int32_t ratePermille __unused) { return INVALID_OPERATION; } uint32_t sampleRate() const { return mSampleRate; } audio_format_t format() const { return mFormat; } size_t size() const { return mSize; } @@ -240,6 +246,7 @@ class MediaPlayerService : public BnMediaPlayerService ssize_t mFrameCount; uint32_t mSampleRate; uint32_t mSize; + size_t mFrameSize; int mError; bool mCommandComplete; @@ -256,13 +263,20 @@ public: virtual sp<IMediaPlayer> create(const sp<IMediaPlayerClient>& client, int audioSessionId); - virtual status_t decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, - audio_format_t* pFormat, - const sp<IMemoryHeap>& heap, size_t *pSize); + virtual status_t decode( + const sp<IMediaHTTPService> &httpService, + const char* url, + uint32_t *pSampleRate, + int* pNumChannels, + audio_format_t* pFormat, + const sp<IMemoryHeap>& heap, + size_t *pSize); + virtual status_t decode(int fd, int64_t offset, int64_t length, 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(); @@ -272,9 +286,6 @@ public: const String8& iface); virtual status_t dump(int fd, const Vector<String16>& args); - virtual status_t updateProxyConfig( - const char *host, int32_t port, const char *exclusionList); - void removeClient(wp<Client> client); // For battery usage tracking purpose @@ -356,6 +367,7 @@ private: sp<MediaPlayerBase> createPlayer(player_type playerType); virtual status_t setDataSource( + const sp<IMediaHTTPService> &httpService, const char *url, const KeyedVector<String8, String8> *headers); @@ -405,6 +417,8 @@ private: // Disconnect from the currently connected ANativeWindow. void disconnectNativeWindow(); + status_t setAudioAttributes_l(const Parcel &request); + mutable Mutex mLock; sp<MediaPlayerBase> mPlayer; sp<MediaPlayerService> mService; @@ -415,6 +429,7 @@ private: bool mLoop; int32_t mConnId; int mAudioSessionId; + audio_attributes_t * mAudioAttributes; uid_t mUID; sp<ANativeWindow> mConnectedWindow; sp<IBinder> mConnectedWindowBinder; diff --git a/media/libmediaplayerservice/MediaRecorderClient.cpp b/media/libmediaplayerservice/MediaRecorderClient.cpp index a9820e0..194abbb 100644 --- a/media/libmediaplayerservice/MediaRecorderClient.cpp +++ b/media/libmediaplayerservice/MediaRecorderClient.cpp @@ -95,7 +95,8 @@ status_t MediaRecorderClient::setPreviewSurface(const sp<IGraphicBufferProducer> status_t MediaRecorderClient::setVideoSource(int vs) { ALOGV("setVideoSource(%d)", vs); - if (!checkPermission(cameraPermission)) { + // Check camera permission for sources other than SURFACE + if (vs != VIDEO_SOURCE_SURFACE && !checkPermission(cameraPermission)) { return PERMISSION_DENIED; } Mutex::Autolock lock(mLock); diff --git a/media/libmediaplayerservice/MetadataRetrieverClient.cpp b/media/libmediaplayerservice/MetadataRetrieverClient.cpp index 063eb94..fa28451 100644 --- a/media/libmediaplayerservice/MetadataRetrieverClient.cpp +++ b/media/libmediaplayerservice/MetadataRetrieverClient.cpp @@ -31,6 +31,7 @@ #include <binder/MemoryHeapBase.h> #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> +#include <media/IMediaHTTPService.h> #include <media/MediaMetadataRetrieverInterface.h> #include <media/MediaPlayerInterface.h> #include <private/media/VideoFrame.h> @@ -106,7 +107,9 @@ static sp<MediaMetadataRetrieverBase> createRetriever(player_type playerType) } status_t MetadataRetrieverClient::setDataSource( - const char *url, const KeyedVector<String8, String8> *headers) + const sp<IMediaHTTPService> &httpService, + const char *url, + const KeyedVector<String8, String8> *headers) { ALOGV("setDataSource(%s)", url); Mutex::Autolock lock(mLock); @@ -127,7 +130,7 @@ status_t MetadataRetrieverClient::setDataSource( ALOGV("player type = %d", playerType); sp<MediaMetadataRetrieverBase> p = createRetriever(playerType); if (p == NULL) return NO_INIT; - status_t ret = p->setDataSource(url, headers); + status_t ret = p->setDataSource(httpService, url, headers); if (ret == NO_ERROR) mRetriever = p; return ret; } @@ -144,8 +147,8 @@ status_t MetadataRetrieverClient::setDataSource(int fd, int64_t offset, int64_t } ALOGV("st_dev = %llu", sb.st_dev); ALOGV("st_mode = %u", sb.st_mode); - ALOGV("st_uid = %lu", sb.st_uid); - ALOGV("st_gid = %lu", sb.st_gid); + ALOGV("st_uid = %lu", static_cast<unsigned long>(sb.st_uid)); + ALOGV("st_gid = %lu", static_cast<unsigned long>(sb.st_gid)); ALOGV("st_size = %llu", sb.st_size); if (offset >= sb.st_size) { diff --git a/media/libmediaplayerservice/MetadataRetrieverClient.h b/media/libmediaplayerservice/MetadataRetrieverClient.h index f08f933..9d3fbe9 100644 --- a/media/libmediaplayerservice/MetadataRetrieverClient.h +++ b/media/libmediaplayerservice/MetadataRetrieverClient.h @@ -30,6 +30,7 @@ namespace android { +struct IMediaHTTPService; class IMediaPlayerService; class MemoryDealer; @@ -43,7 +44,9 @@ public: virtual void disconnect(); virtual status_t setDataSource( - const char *url, const KeyedVector<String8, String8> *headers); + const sp<IMediaHTTPService> &httpService, + const char *url, + const KeyedVector<String8, String8> *headers); virtual status_t setDataSource(int fd, int64_t offset, int64_t length); virtual sp<IMemory> getFrameAtTime(int64_t timeUs, int option); diff --git a/media/libmediaplayerservice/MidiFile.cpp b/media/libmediaplayerservice/MidiFile.cpp index 0a6aa90..749ef96 100644 --- a/media/libmediaplayerservice/MidiFile.cpp +++ b/media/libmediaplayerservice/MidiFile.cpp @@ -114,7 +114,9 @@ MidiFile::~MidiFile() { } status_t MidiFile::setDataSource( - const char* path, const KeyedVector<String8, String8> *) { + const sp<IMediaHTTPService> & /*httpService*/, + const char* path, + const KeyedVector<String8, String8> *) { ALOGV("MidiFile::setDataSource url=%s", path); Mutex::Autolock lock(mMutex); diff --git a/media/libmediaplayerservice/MidiFile.h b/media/libmediaplayerservice/MidiFile.h index 16792ab..82e4e88 100644 --- a/media/libmediaplayerservice/MidiFile.h +++ b/media/libmediaplayerservice/MidiFile.h @@ -32,7 +32,9 @@ public: virtual status_t initCheck(); virtual status_t setDataSource( - const char* path, const KeyedVector<String8, String8> *headers); + const sp<IMediaHTTPService> &httpService, + const char* path, + const KeyedVector<String8, String8> *headers); virtual status_t setDataSource(int fd, int64_t offset, int64_t length); virtual status_t setVideoSurfaceTexture( diff --git a/media/libmediaplayerservice/MidiMetadataRetriever.cpp b/media/libmediaplayerservice/MidiMetadataRetriever.cpp index 465209f..f3cf6ef 100644 --- a/media/libmediaplayerservice/MidiMetadataRetriever.cpp +++ b/media/libmediaplayerservice/MidiMetadataRetriever.cpp @@ -22,6 +22,8 @@ #include "MidiMetadataRetriever.h" #include <media/mediametadataretriever.h> +#include <media/IMediaHTTPService.h> + namespace android { static status_t ERROR_NOT_OPEN = -1; @@ -36,7 +38,9 @@ void MidiMetadataRetriever::clearMetadataValues() } status_t MidiMetadataRetriever::setDataSource( - const char *url, const KeyedVector<String8, String8> *headers) + const sp<IMediaHTTPService> &httpService, + const char *url, + const KeyedVector<String8, String8> *headers) { ALOGV("setDataSource: %s", url? url: "NULL pointer"); Mutex::Autolock lock(mLock); @@ -44,7 +48,7 @@ status_t MidiMetadataRetriever::setDataSource( if (mMidiPlayer == 0) { mMidiPlayer = new MidiFile(); } - return mMidiPlayer->setDataSource(url, headers); + return mMidiPlayer->setDataSource(httpService, url, headers); } status_t MidiMetadataRetriever::setDataSource(int fd, int64_t offset, int64_t length) diff --git a/media/libmediaplayerservice/MidiMetadataRetriever.h b/media/libmediaplayerservice/MidiMetadataRetriever.h index 4cee42d..b8214ee 100644 --- a/media/libmediaplayerservice/MidiMetadataRetriever.h +++ b/media/libmediaplayerservice/MidiMetadataRetriever.h @@ -32,7 +32,9 @@ public: ~MidiMetadataRetriever() {} virtual status_t setDataSource( - const char *url, const KeyedVector<String8, String8> *headers); + const sp<IMediaHTTPService> &httpService, + const char *url, + const KeyedVector<String8, String8> *headers); virtual status_t setDataSource(int fd, int64_t offset, int64_t length); virtual const char* extractMetadata(int keyCode); diff --git a/media/libmediaplayerservice/StagefrightPlayer.cpp b/media/libmediaplayerservice/StagefrightPlayer.cpp index 42b7766..b37aee3 100644 --- a/media/libmediaplayerservice/StagefrightPlayer.cpp +++ b/media/libmediaplayerservice/StagefrightPlayer.cpp @@ -54,8 +54,10 @@ status_t StagefrightPlayer::setUID(uid_t uid) { } status_t StagefrightPlayer::setDataSource( - const char *url, const KeyedVector<String8, String8> *headers) { - return mPlayer->setDataSource(url, headers); + const sp<IMediaHTTPService> &httpService, + const char *url, + const KeyedVector<String8, String8> *headers) { + return mPlayer->setDataSource(httpService, url, headers); } // Warning: The filedescriptor passed into this method will only be valid until diff --git a/media/libmediaplayerservice/StagefrightPlayer.h b/media/libmediaplayerservice/StagefrightPlayer.h index 600945e..e6c30ff 100644 --- a/media/libmediaplayerservice/StagefrightPlayer.h +++ b/media/libmediaplayerservice/StagefrightPlayer.h @@ -34,7 +34,9 @@ public: virtual status_t setUID(uid_t uid); virtual status_t setDataSource( - const char *url, const KeyedVector<String8, String8> *headers); + const sp<IMediaHTTPService> &httpService, + const char *url, + const KeyedVector<String8, String8> *headers); virtual status_t setDataSource(int fd, int64_t offset, int64_t length); diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp index 4da74e1..b904aa8 100644 --- a/media/libmediaplayerservice/StagefrightRecorder.cpp +++ b/media/libmediaplayerservice/StagefrightRecorder.cpp @@ -19,14 +19,17 @@ #include <inttypes.h> #include <utils/Log.h> +#include "WebmWriter.h" #include "StagefrightRecorder.h" #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> #include <media/IMediaPlayerService.h> -#include <media/openmax/OMX_Audio.h> +#include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/ALooper.h> #include <media/stagefright/AudioSource.h> #include <media/stagefright/AMRWriter.h> #include <media/stagefright/AACWriter.h> @@ -36,13 +39,12 @@ #include <media/stagefright/MPEG4Writer.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MetaData.h> +#include <media/stagefright/MediaCodecSource.h> #include <media/stagefright/OMXClient.h> #include <media/stagefright/OMXCodec.h> -#include <media/stagefright/SurfaceMediaSource.h> #include <media/MediaProfiles.h> #include <camera/ICamera.h> #include <camera/CameraParameters.h> -#include <gui/Surface.h> #include <utils/Errors.h> #include <sys/types.h> @@ -72,8 +74,7 @@ StagefrightRecorder::StagefrightRecorder() mAudioSource(AUDIO_SOURCE_CNT), mVideoSource(VIDEO_SOURCE_LIST_END), mCaptureTimeLapse(false), - mStarted(false), - mSurfaceMediaSource(NULL) { + mStarted(false) { ALOGV("Constructor"); reset(); @@ -82,10 +83,19 @@ StagefrightRecorder::StagefrightRecorder() StagefrightRecorder::~StagefrightRecorder() { ALOGV("Destructor"); stop(); + + if (mLooper != NULL) { + mLooper->stop(); + } } status_t StagefrightRecorder::init() { ALOGV("init"); + + mLooper = new ALooper; + mLooper->setName("recorder_looper"); + mLooper->start(); + return OK; } @@ -94,7 +104,7 @@ status_t StagefrightRecorder::init() { // while encoding GL Frames sp<IGraphicBufferProducer> StagefrightRecorder::querySurfaceMediaSource() const { ALOGV("Get SurfaceMediaSource"); - return mSurfaceMediaSource->getBufferQueue(); + return mGraphicBufferProducer; } status_t StagefrightRecorder::setAudioSource(audio_source_t as) { @@ -173,11 +183,7 @@ status_t StagefrightRecorder::setVideoEncoder(video_encoder ve) { return BAD_VALUE; } - if (ve == VIDEO_ENCODER_DEFAULT) { - mVideoEncoder = VIDEO_ENCODER_H263; - } else { - mVideoEncoder = ve; - } + mVideoEncoder = ve; return OK; } @@ -234,7 +240,7 @@ status_t StagefrightRecorder::setPreviewSurface(const sp<IGraphicBufferProducer> return OK; } -status_t StagefrightRecorder::setOutputFile(const char *path) { +status_t StagefrightRecorder::setOutputFile(const char * /* path */) { ALOGE("setOutputFile(const char*) must not be called"); // We don't actually support this at all, as the media_server process // no longer has permissions to create files. @@ -681,10 +687,10 @@ status_t StagefrightRecorder::setParameter( return setParamTimeLapseEnable(timeLapseEnable); } } else if (key == "time-between-time-lapse-frame-capture") { - int64_t timeBetweenTimeLapseFrameCaptureMs; - if (safe_strtoi64(value.string(), &timeBetweenTimeLapseFrameCaptureMs)) { + int64_t timeBetweenTimeLapseFrameCaptureUs; + if (safe_strtoi64(value.string(), &timeBetweenTimeLapseFrameCaptureUs)) { return setParamTimeBetweenTimeLapseFrameCapture( - 1000LL * timeBetweenTimeLapseFrameCaptureMs); + timeBetweenTimeLapseFrameCaptureUs); } } else { ALOGE("setParameter: failed to find key %s", key.string()); @@ -739,19 +745,15 @@ status_t StagefrightRecorder::setClientName(const String16& clientName) { return OK; } -status_t StagefrightRecorder::prepare() { - return OK; -} - -status_t StagefrightRecorder::start() { - CHECK_GE(mOutputFd, 0); +status_t StagefrightRecorder::prepareInternal() { + ALOGV("prepare"); + if (mOutputFd < 0) { + ALOGE("Output file descriptor is invalid"); + return INVALID_OPERATION; + } // Get UID here for permission checking mClientUid = IPCThreadState::self()->getCallingUid(); - if (mWriter != NULL) { - ALOGE("File writer is not avaialble"); - return UNKNOWN_ERROR; - } status_t status = OK; @@ -759,31 +761,103 @@ status_t StagefrightRecorder::start() { case OUTPUT_FORMAT_DEFAULT: case OUTPUT_FORMAT_THREE_GPP: case OUTPUT_FORMAT_MPEG_4: - status = startMPEG4Recording(); + case OUTPUT_FORMAT_WEBM: + status = setupMPEG4orWEBMRecording(); break; case OUTPUT_FORMAT_AMR_NB: case OUTPUT_FORMAT_AMR_WB: - status = startAMRRecording(); + status = setupAMRRecording(); break; case OUTPUT_FORMAT_AAC_ADIF: case OUTPUT_FORMAT_AAC_ADTS: - status = startAACRecording(); + status = setupAACRecording(); break; case OUTPUT_FORMAT_RTP_AVP: - status = startRTPRecording(); + status = setupRTPRecording(); + break; + + case OUTPUT_FORMAT_MPEG2TS: + status = setupMPEG2TSRecording(); + break; + + default: + ALOGE("Unsupported output file format: %d", mOutputFormat); + status = UNKNOWN_ERROR; + break; + } + + return status; +} + +status_t StagefrightRecorder::prepare() { + if (mVideoSource == VIDEO_SOURCE_SURFACE) { + return prepareInternal(); + } + return OK; +} + +status_t StagefrightRecorder::start() { + ALOGV("start"); + if (mOutputFd < 0) { + ALOGE("Output file descriptor is invalid"); + return INVALID_OPERATION; + } + + status_t status = OK; + + if (mVideoSource != VIDEO_SOURCE_SURFACE) { + status = prepareInternal(); + if (status != OK) { + return status; + } + } + + if (mWriter == NULL) { + ALOGE("File writer is not avaialble"); + return UNKNOWN_ERROR; + } + + switch (mOutputFormat) { + case OUTPUT_FORMAT_DEFAULT: + case OUTPUT_FORMAT_THREE_GPP: + case OUTPUT_FORMAT_MPEG_4: + case OUTPUT_FORMAT_WEBM: + { + bool isMPEG4 = true; + if (mOutputFormat == OUTPUT_FORMAT_WEBM) { + isMPEG4 = false; + } + sp<MetaData> meta = new MetaData; + setupMPEG4orWEBMMetaData(&meta); + status = mWriter->start(meta.get()); break; + } + case OUTPUT_FORMAT_AMR_NB: + case OUTPUT_FORMAT_AMR_WB: + case OUTPUT_FORMAT_AAC_ADIF: + case OUTPUT_FORMAT_AAC_ADTS: + case OUTPUT_FORMAT_RTP_AVP: case OUTPUT_FORMAT_MPEG2TS: - status = startMPEG2TSRecording(); + { + status = mWriter->start(); break; + } default: + { ALOGE("Unsupported output file format: %d", mOutputFormat); status = UNKNOWN_ERROR; break; + } + } + + if (status != OK) { + mWriter.clear(); + mWriter = NULL; } if ((status == OK) && (!mStarted)) { @@ -817,58 +891,58 @@ sp<MediaSource> StagefrightRecorder::createAudioSource() { return NULL; } - sp<MetaData> encMeta = new MetaData; + sp<AMessage> format = new AMessage; const char *mime; switch (mAudioEncoder) { case AUDIO_ENCODER_AMR_NB: case AUDIO_ENCODER_DEFAULT: - mime = MEDIA_MIMETYPE_AUDIO_AMR_NB; + format->setString("mime", MEDIA_MIMETYPE_AUDIO_AMR_NB); break; case AUDIO_ENCODER_AMR_WB: - mime = MEDIA_MIMETYPE_AUDIO_AMR_WB; + format->setString("mime", MEDIA_MIMETYPE_AUDIO_AMR_WB); break; case AUDIO_ENCODER_AAC: - mime = MEDIA_MIMETYPE_AUDIO_AAC; - encMeta->setInt32(kKeyAACProfile, OMX_AUDIO_AACObjectLC); + format->setString("mime", MEDIA_MIMETYPE_AUDIO_AAC); + format->setInt32("aac-profile", OMX_AUDIO_AACObjectLC); break; case AUDIO_ENCODER_HE_AAC: - mime = MEDIA_MIMETYPE_AUDIO_AAC; - encMeta->setInt32(kKeyAACProfile, OMX_AUDIO_AACObjectHE); + format->setString("mime", MEDIA_MIMETYPE_AUDIO_AAC); + format->setInt32("aac-profile", OMX_AUDIO_AACObjectHE); break; case AUDIO_ENCODER_AAC_ELD: - mime = MEDIA_MIMETYPE_AUDIO_AAC; - encMeta->setInt32(kKeyAACProfile, OMX_AUDIO_AACObjectELD); + format->setString("mime", MEDIA_MIMETYPE_AUDIO_AAC); + format->setInt32("aac-profile", OMX_AUDIO_AACObjectELD); break; default: ALOGE("Unknown audio encoder: %d", mAudioEncoder); return NULL; } - encMeta->setCString(kKeyMIMEType, mime); int32_t maxInputSize; CHECK(audioSource->getFormat()->findInt32( kKeyMaxInputSize, &maxInputSize)); - encMeta->setInt32(kKeyMaxInputSize, maxInputSize); - encMeta->setInt32(kKeyChannelCount, mAudioChannels); - encMeta->setInt32(kKeySampleRate, mSampleRate); - encMeta->setInt32(kKeyBitRate, mAudioBitRate); + format->setInt32("max-input-size", maxInputSize); + format->setInt32("channel-count", mAudioChannels); + format->setInt32("sample-rate", mSampleRate); + format->setInt32("bitrate", mAudioBitRate); if (mAudioTimeScale > 0) { - encMeta->setInt32(kKeyTimeScale, mAudioTimeScale); + format->setInt32("time-scale", mAudioTimeScale); } - OMXClient client; - CHECK_EQ(client.connect(), (status_t)OK); sp<MediaSource> audioEncoder = - OMXCodec::Create(client.interface(), encMeta, - true /* createEncoder */, audioSource); + MediaCodecSource::Create(mLooper, format, audioSource); mAudioSourceNode = audioSource; + if (audioEncoder == NULL) { + ALOGE("Failed to create audio encoder"); + } + return audioEncoder; } -status_t StagefrightRecorder::startAACRecording() { +status_t StagefrightRecorder::setupAACRecording() { // FIXME: // Add support for OUTPUT_FORMAT_AAC_ADIF CHECK_EQ(mOutputFormat, OUTPUT_FORMAT_AAC_ADTS); @@ -879,16 +953,10 @@ status_t StagefrightRecorder::startAACRecording() { CHECK(mAudioSource != AUDIO_SOURCE_CNT); mWriter = new AACWriter(mOutputFd); - status_t status = startRawAudioRecording(); - if (status != OK) { - mWriter.clear(); - mWriter = NULL; - } - - return status; + return setupRawAudioRecording(); } -status_t StagefrightRecorder::startAMRRecording() { +status_t StagefrightRecorder::setupAMRRecording() { CHECK(mOutputFormat == OUTPUT_FORMAT_AMR_NB || mOutputFormat == OUTPUT_FORMAT_AMR_WB); @@ -908,15 +976,10 @@ status_t StagefrightRecorder::startAMRRecording() { } mWriter = new AMRWriter(mOutputFd); - status_t status = startRawAudioRecording(); - if (status != OK) { - mWriter.clear(); - mWriter = NULL; - } - return status; + return setupRawAudioRecording(); } -status_t StagefrightRecorder::startRawAudioRecording() { +status_t StagefrightRecorder::setupRawAudioRecording() { if (mAudioSource >= AUDIO_SOURCE_CNT) { ALOGE("Invalid audio source: %d", mAudioSource); return BAD_VALUE; @@ -942,12 +1005,11 @@ status_t StagefrightRecorder::startRawAudioRecording() { mWriter->setMaxFileSize(mMaxFileSizeBytes); } mWriter->setListener(mListener); - mWriter->start(); return OK; } -status_t StagefrightRecorder::startRTPRecording() { +status_t StagefrightRecorder::setupRTPRecording() { CHECK_EQ(mOutputFormat, OUTPUT_FORMAT_RTP_AVP); if ((mAudioSource != AUDIO_SOURCE_CNT @@ -967,6 +1029,7 @@ status_t StagefrightRecorder::startRTPRecording() { if (mAudioSource != AUDIO_SOURCE_CNT) { source = createAudioSource(); } else { + setDefaultVideoEncoderIfNecessary(); sp<MediaSource> mediaSource; status_t err = setupMediaSource(&mediaSource); @@ -974,7 +1037,7 @@ status_t StagefrightRecorder::startRTPRecording() { return err; } - err = setupVideoEncoder(mediaSource, mVideoBitRate, &source); + err = setupVideoEncoder(mediaSource, &source); if (err != OK) { return err; } @@ -984,10 +1047,10 @@ status_t StagefrightRecorder::startRTPRecording() { mWriter->addSource(source); mWriter->setListener(mListener); - return mWriter->start(); + return OK; } -status_t StagefrightRecorder::startMPEG2TSRecording() { +status_t StagefrightRecorder::setupMPEG2TSRecording() { CHECK_EQ(mOutputFormat, OUTPUT_FORMAT_MPEG2TS); sp<MediaWriter> writer = new MPEG2TSWriter(mOutputFd); @@ -1008,6 +1071,7 @@ status_t StagefrightRecorder::startMPEG2TSRecording() { if (mVideoSource < VIDEO_SOURCE_LIST_END) { if (mVideoEncoder != VIDEO_ENCODER_H264) { + ALOGE("MPEG2TS recording only supports H.264 encoding!"); return ERROR_UNSUPPORTED; } @@ -1018,7 +1082,7 @@ status_t StagefrightRecorder::startMPEG2TSRecording() { } sp<MediaSource> encoder; - err = setupVideoEncoder(mediaSource, mVideoBitRate, &encoder); + err = setupVideoEncoder(mediaSource, &encoder); if (err != OK) { return err; @@ -1037,11 +1101,17 @@ status_t StagefrightRecorder::startMPEG2TSRecording() { mWriter = writer; - return mWriter->start(); + return OK; } void StagefrightRecorder::clipVideoFrameRate() { ALOGV("clipVideoFrameRate: encoder %d", mVideoEncoder); + if (mFrameRate == -1) { + mFrameRate = mEncoderProfiles->getCamcorderProfileParamByName( + "vid.fps", mCameraId, CAMCORDER_QUALITY_LOW); + ALOGW("Using default video fps %d", mFrameRate); + } + int minFrameRate = mEncoderProfiles->getVideoEncoderParamByName( "enc.vid.fps.min", mVideoEncoder); int maxFrameRate = mEncoderProfiles->getVideoEncoderParamByName( @@ -1177,6 +1247,27 @@ void StagefrightRecorder::setDefaultProfileIfNecessary() { } } +void StagefrightRecorder::setDefaultVideoEncoderIfNecessary() { + if (mVideoEncoder == VIDEO_ENCODER_DEFAULT) { + if (mOutputFormat == OUTPUT_FORMAT_WEBM) { + // default to VP8 for WEBM recording + mVideoEncoder = VIDEO_ENCODER_VP8; + } else { + // pick the default encoder for CAMCORDER_QUALITY_LOW + int videoCodec = mEncoderProfiles->getCamcorderProfileParamByName( + "vid.codec", mCameraId, CAMCORDER_QUALITY_LOW); + + if (videoCodec > VIDEO_ENCODER_DEFAULT && + videoCodec < VIDEO_ENCODER_LIST_END) { + mVideoEncoder = (video_encoder)videoCodec; + } else { + // default to H.264 if camcorder profile not available + mVideoEncoder = VIDEO_ENCODER_H264; + } + } + } +} + status_t StagefrightRecorder::checkAudioEncoderCapabilities() { clipAudioBitRate(); clipAudioSampleRate(); @@ -1278,49 +1369,14 @@ status_t StagefrightRecorder::setupMediaSource( return err; } *mediaSource = cameraSource; - } else if (mVideoSource == VIDEO_SOURCE_GRALLOC_BUFFER) { - // If using GRAlloc buffers, setup surfacemediasource. - // Later a handle to that will be passed - // to the client side when queried - status_t err = setupSurfaceMediaSource(); - if (err != OK) { - return err; - } - *mediaSource = mSurfaceMediaSource; + } else if (mVideoSource == VIDEO_SOURCE_SURFACE) { + *mediaSource = NULL; } else { return INVALID_OPERATION; } return OK; } -// setupSurfaceMediaSource creates a source with the given -// width and height and framerate. -// TODO: This could go in a static function inside SurfaceMediaSource -// similar to that in CameraSource -status_t StagefrightRecorder::setupSurfaceMediaSource() { - status_t err = OK; - mSurfaceMediaSource = new SurfaceMediaSource(mVideoWidth, mVideoHeight); - if (mSurfaceMediaSource == NULL) { - return NO_INIT; - } - - if (mFrameRate == -1) { - int32_t frameRate = 0; - CHECK (mSurfaceMediaSource->getFormat()->findInt32( - kKeyFrameRate, &frameRate)); - ALOGI("Frame rate is not explicitly set. Use the current frame " - "rate (%d fps)", frameRate); - mFrameRate = frameRate; - } else { - err = mSurfaceMediaSource->setFrameRate(mFrameRate); - } - CHECK(mFrameRate != -1); - - mIsMetaDataStoredInVideoBuffers = - mSurfaceMediaSource->isMetaDataStoredInVideoBuffers(); - return err; -} - status_t StagefrightRecorder::setupCameraSource( sp<CameraSource> *cameraSource) { status_t err = OK; @@ -1384,25 +1440,26 @@ status_t StagefrightRecorder::setupCameraSource( status_t StagefrightRecorder::setupVideoEncoder( sp<MediaSource> cameraSource, - int32_t videoBitRate, sp<MediaSource> *source) { source->clear(); - sp<MetaData> enc_meta = new MetaData; - enc_meta->setInt32(kKeyBitRate, videoBitRate); - enc_meta->setInt32(kKeyFrameRate, mFrameRate); + sp<AMessage> format = new AMessage(); switch (mVideoEncoder) { case VIDEO_ENCODER_H263: - enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263); + format->setString("mime", MEDIA_MIMETYPE_VIDEO_H263); break; case VIDEO_ENCODER_MPEG_4_SP: - enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4); + format->setString("mime", MEDIA_MIMETYPE_VIDEO_MPEG4); break; case VIDEO_ENCODER_H264: - enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC); + format->setString("mime", MEDIA_MIMETYPE_VIDEO_AVC); + break; + + case VIDEO_ENCODER_VP8: + format->setString("mime", MEDIA_MIMETYPE_VIDEO_VP8); break; default: @@ -1410,59 +1467,80 @@ status_t StagefrightRecorder::setupVideoEncoder( break; } - sp<MetaData> meta = cameraSource->getFormat(); + if (cameraSource != NULL) { + sp<MetaData> meta = cameraSource->getFormat(); + + int32_t width, height, stride, sliceHeight, colorFormat; + CHECK(meta->findInt32(kKeyWidth, &width)); + CHECK(meta->findInt32(kKeyHeight, &height)); + CHECK(meta->findInt32(kKeyStride, &stride)); + CHECK(meta->findInt32(kKeySliceHeight, &sliceHeight)); + CHECK(meta->findInt32(kKeyColorFormat, &colorFormat)); - int32_t width, height, stride, sliceHeight, colorFormat; - CHECK(meta->findInt32(kKeyWidth, &width)); - CHECK(meta->findInt32(kKeyHeight, &height)); - CHECK(meta->findInt32(kKeyStride, &stride)); - CHECK(meta->findInt32(kKeySliceHeight, &sliceHeight)); - CHECK(meta->findInt32(kKeyColorFormat, &colorFormat)); + format->setInt32("width", width); + format->setInt32("height", height); + format->setInt32("stride", stride); + format->setInt32("slice-height", sliceHeight); + format->setInt32("color-format", colorFormat); + } else { + format->setInt32("width", mVideoWidth); + format->setInt32("height", mVideoHeight); + format->setInt32("stride", mVideoWidth); + format->setInt32("slice-height", mVideoWidth); + format->setInt32("color-format", OMX_COLOR_FormatAndroidOpaque); + + // set up time lapse/slow motion for surface source + if (mCaptureTimeLapse) { + if (mTimeBetweenTimeLapseFrameCaptureUs <= 0) { + ALOGE("Invalid mTimeBetweenTimeLapseFrameCaptureUs value: %lld", + mTimeBetweenTimeLapseFrameCaptureUs); + return BAD_VALUE; + } + format->setInt64("time-lapse", + mTimeBetweenTimeLapseFrameCaptureUs); + } + } + + format->setInt32("bitrate", mVideoBitRate); + format->setInt32("frame-rate", mFrameRate); + format->setInt32("i-frame-interval", mIFramesIntervalSec); - enc_meta->setInt32(kKeyWidth, width); - enc_meta->setInt32(kKeyHeight, height); - enc_meta->setInt32(kKeyIFramesInterval, mIFramesIntervalSec); - enc_meta->setInt32(kKeyStride, stride); - enc_meta->setInt32(kKeySliceHeight, sliceHeight); - enc_meta->setInt32(kKeyColorFormat, colorFormat); if (mVideoTimeScale > 0) { - enc_meta->setInt32(kKeyTimeScale, mVideoTimeScale); + format->setInt32("time-scale", mVideoTimeScale); } if (mVideoEncoderProfile != -1) { - enc_meta->setInt32(kKeyVideoProfile, mVideoEncoderProfile); + format->setInt32("profile", mVideoEncoderProfile); } if (mVideoEncoderLevel != -1) { - enc_meta->setInt32(kKeyVideoLevel, mVideoEncoderLevel); + format->setInt32("level", mVideoEncoderLevel); } - OMXClient client; - CHECK_EQ(client.connect(), (status_t)OK); - - uint32_t encoder_flags = 0; + uint32_t flags = 0; if (mIsMetaDataStoredInVideoBuffers) { - encoder_flags |= OMXCodec::kStoreMetaDataInVideoBuffers; + flags |= MediaCodecSource::FLAG_USE_METADATA_INPUT; } - // Do not wait for all the input buffers to become available. - // This give timelapse video recording faster response in - // receiving output from video encoder component. - if (mCaptureTimeLapse) { - encoder_flags |= OMXCodec::kOnlySubmitOneInputBufferAtOneTime; + if (cameraSource == NULL) { + flags |= MediaCodecSource::FLAG_USE_SURFACE_INPUT; } - sp<MediaSource> encoder = OMXCodec::Create( - client.interface(), enc_meta, - true /* createEncoder */, cameraSource, - NULL, encoder_flags); + sp<MediaCodecSource> encoder = + MediaCodecSource::Create(mLooper, format, cameraSource, flags); if (encoder == NULL) { - ALOGW("Failed to create the encoder"); + ALOGE("Failed to create video encoder"); // When the encoder fails to be created, we need // release the camera source due to the camera's lock // and unlock mechanism. - cameraSource->stop(); + if (cameraSource != NULL) { + cameraSource->stop(); + } return UNKNOWN_ERROR; } + if (cameraSource == NULL) { + mGraphicBufferProducer = encoder->getGraphicBufferProducer(); + } + *source = encoder; return OK; @@ -1496,18 +1574,20 @@ status_t StagefrightRecorder::setupAudioEncoder(const sp<MediaWriter>& writer) { return OK; } -status_t StagefrightRecorder::setupMPEG4Recording( - int outputFd, - int32_t videoWidth, int32_t videoHeight, - int32_t videoBitRate, - int32_t *totalBitRate, - sp<MediaWriter> *mediaWriter) { - mediaWriter->clear(); - *totalBitRate = 0; +status_t StagefrightRecorder::setupMPEG4orWEBMRecording() { + mWriter.clear(); + mTotalBitRate = 0; + status_t err = OK; - sp<MediaWriter> writer = new MPEG4Writer(outputFd); + sp<MediaWriter> writer; + if (mOutputFormat == OUTPUT_FORMAT_WEBM) { + writer = new WebmWriter(mOutputFd); + } else { + writer = new MPEG4Writer(mOutputFd); + } if (mVideoSource < VIDEO_SOURCE_LIST_END) { + setDefaultVideoEncoderIfNecessary(); sp<MediaSource> mediaSource; err = setupMediaSource(&mediaSource); @@ -1516,31 +1596,34 @@ status_t StagefrightRecorder::setupMPEG4Recording( } sp<MediaSource> encoder; - err = setupVideoEncoder(mediaSource, videoBitRate, &encoder); + err = setupVideoEncoder(mediaSource, &encoder); if (err != OK) { return err; } writer->addSource(encoder); - *totalBitRate += videoBitRate; - } - - // Audio source is added at the end if it exists. - // This help make sure that the "recoding" sound is suppressed for - // camcorder applications in the recorded files. - if (!mCaptureTimeLapse && (mAudioSource != AUDIO_SOURCE_CNT)) { - err = setupAudioEncoder(writer); - if (err != OK) return err; - *totalBitRate += mAudioBitRate; - } + mTotalBitRate += mVideoBitRate; + } + + if (mOutputFormat != OUTPUT_FORMAT_WEBM) { + // Audio source is added at the end if it exists. + // This help make sure that the "recoding" sound is suppressed for + // camcorder applications in the recorded files. + // TODO Audio source is currently unsupported for webm output; vorbis encoder needed. + if (!mCaptureTimeLapse && (mAudioSource != AUDIO_SOURCE_CNT)) { + err = setupAudioEncoder(writer); + if (err != OK) return err; + mTotalBitRate += mAudioBitRate; + } - if (mInterleaveDurationUs > 0) { - reinterpret_cast<MPEG4Writer *>(writer.get())-> - setInterleaveDuration(mInterleaveDurationUs); - } - if (mLongitudex10000 > -3600000 && mLatitudex10000 > -3600000) { - reinterpret_cast<MPEG4Writer *>(writer.get())-> - setGeoData(mLatitudex10000, mLongitudex10000); + if (mInterleaveDurationUs > 0) { + reinterpret_cast<MPEG4Writer *>(writer.get())-> + setInterleaveDuration(mInterleaveDurationUs); + } + if (mLongitudex10000 > -3600000 && mLatitudex10000 > -3600000) { + reinterpret_cast<MPEG4Writer *>(writer.get())-> + setGeoData(mLatitudex10000, mLongitudex10000); + } } if (mMaxFileDurationUs != 0) { writer->setMaxFileDuration(mMaxFileDurationUs); @@ -1548,54 +1631,39 @@ status_t StagefrightRecorder::setupMPEG4Recording( if (mMaxFileSizeBytes != 0) { writer->setMaxFileSize(mMaxFileSizeBytes); } - - mStartTimeOffsetMs = mEncoderProfiles->getStartTimeOffsetMs(mCameraId); + if (mVideoSource == VIDEO_SOURCE_DEFAULT + || mVideoSource == VIDEO_SOURCE_CAMERA) { + mStartTimeOffsetMs = mEncoderProfiles->getStartTimeOffsetMs(mCameraId); + } else if (mVideoSource == VIDEO_SOURCE_SURFACE) { + // surface source doesn't need large initial delay + mStartTimeOffsetMs = 200; + } if (mStartTimeOffsetMs > 0) { - reinterpret_cast<MPEG4Writer *>(writer.get())-> - setStartTimeOffsetMs(mStartTimeOffsetMs); + writer->setStartTimeOffsetMs(mStartTimeOffsetMs); } writer->setListener(mListener); - *mediaWriter = writer; + mWriter = writer; return OK; } -void StagefrightRecorder::setupMPEG4MetaData(int64_t startTimeUs, int32_t totalBitRate, - sp<MetaData> *meta) { +void StagefrightRecorder::setupMPEG4orWEBMMetaData(sp<MetaData> *meta) { + int64_t startTimeUs = systemTime() / 1000; (*meta)->setInt64(kKeyTime, startTimeUs); (*meta)->setInt32(kKeyFileType, mOutputFormat); - (*meta)->setInt32(kKeyBitRate, totalBitRate); - (*meta)->setInt32(kKey64BitFileOffset, mUse64BitFileOffset); + (*meta)->setInt32(kKeyBitRate, mTotalBitRate); if (mMovieTimeScale > 0) { (*meta)->setInt32(kKeyTimeScale, mMovieTimeScale); } - if (mTrackEveryTimeDurationUs > 0) { - (*meta)->setInt64(kKeyTrackTimeStatus, mTrackEveryTimeDurationUs); - } - if (mRotationDegrees != 0) { - (*meta)->setInt32(kKeyRotation, mRotationDegrees); - } -} - -status_t StagefrightRecorder::startMPEG4Recording() { - int32_t totalBitRate; - status_t err = setupMPEG4Recording( - mOutputFd, mVideoWidth, mVideoHeight, - mVideoBitRate, &totalBitRate, &mWriter); - if (err != OK) { - return err; - } - - int64_t startTimeUs = systemTime() / 1000; - sp<MetaData> meta = new MetaData; - setupMPEG4MetaData(startTimeUs, totalBitRate, &meta); - - err = mWriter->start(meta.get()); - if (err != OK) { - return err; + if (mOutputFormat != OUTPUT_FORMAT_WEBM) { + (*meta)->setInt32(kKey64BitFileOffset, mUse64BitFileOffset); + if (mTrackEveryTimeDurationUs > 0) { + (*meta)->setInt64(kKeyTrackTimeStatus, mTrackEveryTimeDurationUs); + } + if (mRotationDegrees != 0) { + (*meta)->setInt32(kKeyRotation, mRotationDegrees); + } } - - return OK; } status_t StagefrightRecorder::pause() { @@ -1637,6 +1705,8 @@ status_t StagefrightRecorder::stop() { mWriter.clear(); } + mGraphicBufferProducer.clear(); + if (mOutputFd >= 0) { ::close(mOutputFd); mOutputFd = -1; @@ -1656,7 +1726,6 @@ status_t StagefrightRecorder::stop() { addBatteryData(params); } - return err; } @@ -1678,7 +1747,7 @@ status_t StagefrightRecorder::reset() { // Default parameters mOutputFormat = OUTPUT_FORMAT_THREE_GPP; mAudioEncoder = AUDIO_ENCODER_AMR_NB; - mVideoEncoder = VIDEO_ENCODER_H263; + mVideoEncoder = VIDEO_ENCODER_DEFAULT; mVideoWidth = 176; mVideoHeight = 144; mFrameRate = -1; @@ -1708,6 +1777,7 @@ status_t StagefrightRecorder::reset() { mRotationDegrees = 0; mLatitudex10000 = -3600000; mLongitudex10000 = -3600000; + mTotalBitRate = 0; mOutputFd = -1; diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h index 31f09e0..54c38d3 100644 --- a/media/libmediaplayerservice/StagefrightRecorder.h +++ b/media/libmediaplayerservice/StagefrightRecorder.h @@ -37,6 +37,7 @@ struct AudioSource; class MediaProfiles; class IGraphicBufferProducer; class SurfaceMediaSource; +class ALooper; struct StagefrightRecorder : public MediaRecorderBase { StagefrightRecorder(); @@ -106,6 +107,7 @@ private: int32_t mLatitudex10000; int32_t mLongitudex10000; int32_t mStartTimeOffsetMs; + int32_t mTotalBitRate; bool mCaptureTimeLapse; int64_t mTimeBetweenTimeLapseFrameCaptureUs; @@ -122,22 +124,17 @@ private: // An <IGraphicBufferProducer> pointer // will be sent to the client side using which the // frame buffers will be queued and dequeued - sp<SurfaceMediaSource> mSurfaceMediaSource; - - status_t setupMPEG4Recording( - int outputFd, - int32_t videoWidth, int32_t videoHeight, - int32_t videoBitRate, - int32_t *totalBitRate, - sp<MediaWriter> *mediaWriter); - void setupMPEG4MetaData(int64_t startTimeUs, int32_t totalBitRate, - sp<MetaData> *meta); - status_t startMPEG4Recording(); - status_t startAMRRecording(); - status_t startAACRecording(); - status_t startRawAudioRecording(); - status_t startRTPRecording(); - status_t startMPEG2TSRecording(); + sp<IGraphicBufferProducer> mGraphicBufferProducer; + sp<ALooper> mLooper; + + status_t prepareInternal(); + status_t setupMPEG4orWEBMRecording(); + void setupMPEG4orWEBMMetaData(sp<MetaData> *meta); + status_t setupAMRRecording(); + status_t setupAACRecording(); + status_t setupRawAudioRecording(); + status_t setupRTPRecording(); + status_t setupMPEG2TSRecording(); sp<MediaSource> createAudioSource(); status_t checkVideoEncoderCapabilities( bool *supportsCameraSourceMetaDataMode); @@ -147,14 +144,8 @@ private: // depending on the videosource type status_t setupMediaSource(sp<MediaSource> *mediaSource); status_t setupCameraSource(sp<CameraSource> *cameraSource); - // setup the surfacemediasource for the encoder - status_t setupSurfaceMediaSource(); - status_t setupAudioEncoder(const sp<MediaWriter>& writer); - status_t setupVideoEncoder( - sp<MediaSource> cameraSource, - int32_t videoBitRate, - sp<MediaSource> *source); + status_t setupVideoEncoder(sp<MediaSource> cameraSource, sp<MediaSource> *source); // Encoding parameter handling utilities status_t setParameter(const String8 &key, const String8 &value); @@ -187,6 +178,7 @@ private: void clipAudioSampleRate(); void clipNumberOfAudioChannels(); void setDefaultProfileIfNecessary(); + void setDefaultVideoEncoderIfNecessary(); StagefrightRecorder(const StagefrightRecorder &); diff --git a/media/libmediaplayerservice/TestPlayerStub.cpp b/media/libmediaplayerservice/TestPlayerStub.cpp index 5d9728a..5795773 100644 --- a/media/libmediaplayerservice/TestPlayerStub.cpp +++ b/media/libmediaplayerservice/TestPlayerStub.cpp @@ -113,7 +113,9 @@ status_t TestPlayerStub::parseUrl() // Create the test player. // Call setDataSource on the test player with the url in param. status_t TestPlayerStub::setDataSource( - const char *url, const KeyedVector<String8, String8> *headers) { + const sp<IMediaHTTPService> &httpService, + const char *url, + const KeyedVector<String8, String8> *headers) { if (!isTestUrl(url) || NULL != mHandle) { return INVALID_OPERATION; } @@ -162,7 +164,7 @@ status_t TestPlayerStub::setDataSource( } mPlayer = (*mNewPlayer)(); - return mPlayer->setDataSource(mContentUrl, headers); + return mPlayer->setDataSource(httpService, mContentUrl, headers); } // Internal cleanup. diff --git a/media/libmediaplayerservice/TestPlayerStub.h b/media/libmediaplayerservice/TestPlayerStub.h index a3802eb..55bf2c8 100644 --- a/media/libmediaplayerservice/TestPlayerStub.h +++ b/media/libmediaplayerservice/TestPlayerStub.h @@ -66,7 +66,9 @@ class TestPlayerStub : public MediaPlayerInterface { // @param url Should be a test url. See class comment. virtual status_t setDataSource( - const char* url, const KeyedVector<String8, String8> *headers); + const sp<IMediaHTTPService> &httpService, + const char* url, + const KeyedVector<String8, String8> *headers); // Test player for a file descriptor source is not supported. virtual status_t setDataSource(int, int64_t, int64_t) { diff --git a/media/libmediaplayerservice/VideoFrameScheduler.cpp b/media/libmediaplayerservice/VideoFrameScheduler.cpp new file mode 100644 index 0000000..4251c4e --- /dev/null +++ b/media/libmediaplayerservice/VideoFrameScheduler.cpp @@ -0,0 +1,499 @@ +/* + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "VideoFrameScheduler" +#include <utils/Log.h> +#define ATRACE_TAG ATRACE_TAG_VIDEO +#include <utils/Trace.h> + +#include <sys/time.h> + +#include <binder/IServiceManager.h> +#include <gui/ISurfaceComposer.h> +#include <ui/DisplayStatInfo.h> + +#include <media/stagefright/foundation/ADebug.h> + +#include "VideoFrameScheduler.h" + +namespace android { + +static const nsecs_t kNanosIn1s = 1000000000; + +template<class T> +inline static const T divRound(const T &nom, const T &den) { + if ((nom >= 0) ^ (den >= 0)) { + return (nom - den / 2) / den; + } else { + return (nom + den / 2) / den; + } +} + +template<class T> +inline static T abs(const T &a) { + return a < 0 ? -a : a; +} + +template<class T> +inline static const T &min(const T &a, const T &b) { + return a < b ? a : b; +} + +template<class T> +inline static const T &max(const T &a, const T &b) { + return a > b ? a : b; +} + +template<class T> +inline static T periodicError(const T &val, const T &period) { + T err = abs(val) % period; + return (err < (period / 2)) ? err : (period - err); +} + +template<class T> +static int compare(const T *lhs, const T *rhs) { + if (*lhs < *rhs) { + return -1; + } else if (*lhs > *rhs) { + return 1; + } else { + return 0; + } +} + +/* ======================================================================= */ +/* PLL */ +/* ======================================================================= */ + +static const size_t kMinSamplesToStartPrime = 3; +static const size_t kMinSamplesToStopPrime = VideoFrameScheduler::kHistorySize; +static const size_t kMinSamplesToEstimatePeriod = 3; +static const size_t kMaxSamplesToEstimatePeriod = VideoFrameScheduler::kHistorySize; + +static const size_t kPrecision = 12; +static const size_t kErrorThreshold = (1 << (kPrecision * 2)) / 10; +static const int64_t kMultiplesThresholdDiv = 4; // 25% +static const int64_t kReFitThresholdDiv = 100; // 1% +static const nsecs_t kMaxAllowedFrameSkip = kNanosIn1s; // 1 sec +static const nsecs_t kMinPeriod = kNanosIn1s / 120; // 120Hz +static const nsecs_t kRefitRefreshPeriod = 10 * kNanosIn1s; // 10 sec + +VideoFrameScheduler::PLL::PLL() + : mPeriod(-1), + mPhase(0), + mPrimed(false), + mSamplesUsedForPriming(0), + mLastTime(-1), + mNumSamples(0) { +} + +void VideoFrameScheduler::PLL::reset(float fps) { + //test(); + + mSamplesUsedForPriming = 0; + mLastTime = -1; + + // set up or reset video PLL + if (fps <= 0.f) { + mPeriod = -1; + mPrimed = false; + } else { + ALOGV("reset at %.1f fps", fps); + mPeriod = (nsecs_t)(1e9 / fps + 0.5); + mPrimed = true; + } + + restart(); +} + +// reset PLL but keep previous period estimate +void VideoFrameScheduler::PLL::restart() { + mNumSamples = 0; + mPhase = -1; +} + +#if 0 + +void VideoFrameScheduler::PLL::test() { + nsecs_t period = kNanosIn1s / 60; + mTimes[0] = 0; + mTimes[1] = period; + mTimes[2] = period * 3; + mTimes[3] = period * 4; + mTimes[4] = period * 7; + mTimes[5] = period * 8; + mTimes[6] = period * 10; + mTimes[7] = period * 12; + mNumSamples = 8; + int64_t a, b, err; + fit(0, period * 12 / 7, 8, &a, &b, &err); + // a = 0.8(5)+ + // b = -0.14097(2)+ + // err = 0.2750578(703)+ + ALOGD("a=%lld (%.6f), b=%lld (%.6f), err=%lld (%.6f)", + (long long)a, (a / (float)(1 << kPrecision)), + (long long)b, (b / (float)(1 << kPrecision)), + (long long)err, (err / (float)(1 << (kPrecision * 2)))); +} + +#endif + +void VideoFrameScheduler::PLL::fit( + nsecs_t phase, nsecs_t period, size_t numSamplesToUse, + int64_t *a, int64_t *b, int64_t *err) { + if (numSamplesToUse > mNumSamples) { + numSamplesToUse = mNumSamples; + } + + int64_t sumX = 0; + int64_t sumXX = 0; + int64_t sumXY = 0; + int64_t sumYY = 0; + int64_t sumY = 0; + + int64_t x = 0; // x usually is in [0..numSamplesToUse) + nsecs_t lastTime; + for (size_t i = 0; i < numSamplesToUse; i++) { + size_t ix = (mNumSamples - numSamplesToUse + i) % kHistorySize; + nsecs_t time = mTimes[ix]; + if (i > 0) { + x += divRound(time - lastTime, period); + } + // y is usually in [-numSamplesToUse..numSamplesToUse+kRefitRefreshPeriod/kMinPeriod) << kPrecision + // ideally in [0..numSamplesToUse), but shifted by -numSamplesToUse during + // priming, and possibly shifted by up to kRefitRefreshPeriod/kMinPeriod + // while we are not refitting. + int64_t y = divRound(time - phase, period >> kPrecision); + sumX += x; + sumY += y; + sumXX += x * x; + sumXY += x * y; + sumYY += y * y; + lastTime = time; + } + + int64_t div = numSamplesToUse * sumXX - sumX * sumX; + int64_t a_nom = numSamplesToUse * sumXY - sumX * sumY; + int64_t b_nom = sumXX * sumY - sumX * sumXY; + *a = divRound(a_nom, div); + *b = divRound(b_nom, div); + // don't use a and b directly as the rounding error is significant + *err = sumYY - divRound(a_nom * sumXY + b_nom * sumY, div); + ALOGV("fitting[%zu] a=%lld (%.6f), b=%lld (%.6f), err=%lld (%.6f)", + numSamplesToUse, + (long long)*a, (*a / (float)(1 << kPrecision)), + (long long)*b, (*b / (float)(1 << kPrecision)), + (long long)*err, (*err / (float)(1 << (kPrecision * 2)))); +} + +void VideoFrameScheduler::PLL::prime(size_t numSamplesToUse) { + if (numSamplesToUse > mNumSamples) { + numSamplesToUse = mNumSamples; + } + CHECK(numSamplesToUse >= 3); // must have at least 3 samples + + // estimate video framerate from deltas between timestamps, and + // 2nd order deltas + Vector<nsecs_t> deltas; + nsecs_t lastTime, firstTime; + for (size_t i = 0; i < numSamplesToUse; ++i) { + size_t index = (mNumSamples - numSamplesToUse + i) % kHistorySize; + nsecs_t time = mTimes[index]; + if (i > 0) { + if (time - lastTime > kMinPeriod) { + //ALOGV("delta: %lld", (long long)(time - lastTime)); + deltas.push(time - lastTime); + } + } else { + firstTime = time; + } + lastTime = time; + } + deltas.sort(compare<nsecs_t>); + size_t numDeltas = deltas.size(); + if (numDeltas > 1) { + nsecs_t deltaMinLimit = min(deltas[0] / kMultiplesThresholdDiv, kMinPeriod); + nsecs_t deltaMaxLimit = deltas[numDeltas / 2] * kMultiplesThresholdDiv; + for (size_t i = numDeltas / 2 + 1; i < numDeltas; ++i) { + if (deltas[i] > deltaMaxLimit) { + deltas.resize(i); + numDeltas = i; + break; + } + } + for (size_t i = 1; i < numDeltas; ++i) { + nsecs_t delta2nd = deltas[i] - deltas[i - 1]; + if (delta2nd >= deltaMinLimit) { + //ALOGV("delta2: %lld", (long long)(delta2nd)); + deltas.push(delta2nd); + } + } + } + + // use the one that yields the best match + int64_t bestScore; + for (size_t i = 0; i < deltas.size(); ++i) { + nsecs_t delta = deltas[i]; + int64_t score = 0; +#if 1 + // simplest score: number of deltas that are near multiples + size_t matches = 0; + for (size_t j = 0; j < deltas.size(); ++j) { + nsecs_t err = periodicError(deltas[j], delta); + if (err < delta / kMultiplesThresholdDiv) { + ++matches; + } + } + score = matches; +#if 0 + // could be weighed by the (1 - normalized error) + if (numSamplesToUse >= kMinSamplesToEstimatePeriod) { + int64_t a, b, err; + fit(firstTime, delta, numSamplesToUse, &a, &b, &err); + err = (1 << (2 * kPrecision)) - err; + score *= max(0, err); + } +#endif +#else + // or use the error as a negative score + if (numSamplesToUse >= kMinSamplesToEstimatePeriod) { + int64_t a, b, err; + fit(firstTime, delta, numSamplesToUse, &a, &b, &err); + score = -delta * err; + } +#endif + if (i == 0 || score > bestScore) { + bestScore = score; + mPeriod = delta; + mPhase = firstTime; + } + } + ALOGV("priming[%zu] phase:%lld period:%lld", numSamplesToUse, mPhase, mPeriod); +} + +nsecs_t VideoFrameScheduler::PLL::addSample(nsecs_t time) { + if (mLastTime >= 0 + // if time goes backward, or we skipped rendering + && (time > mLastTime + kMaxAllowedFrameSkip || time < mLastTime)) { + restart(); + } + + mLastTime = time; + mTimes[mNumSamples % kHistorySize] = time; + ++mNumSamples; + + bool doFit = time > mRefitAt; + if ((mPeriod <= 0 || !mPrimed) && mNumSamples >= kMinSamplesToStartPrime) { + prime(kMinSamplesToStopPrime); + ++mSamplesUsedForPriming; + doFit = true; + } + if (mPeriod > 0 && mNumSamples >= kMinSamplesToEstimatePeriod) { + if (mPhase < 0) { + // initialize phase to the current render time + mPhase = time; + doFit = true; + } else if (!doFit) { + int64_t err = periodicError(time - mPhase, mPeriod); + doFit = err > mPeriod / kReFitThresholdDiv; + } + + if (doFit) { + int64_t a, b, err; + mRefitAt = time + kRefitRefreshPeriod; + fit(mPhase, mPeriod, kMaxSamplesToEstimatePeriod, &a, &b, &err); + mPhase += (mPeriod * b) >> kPrecision; + mPeriod = (mPeriod * a) >> kPrecision; + ALOGV("new phase:%lld period:%lld", (long long)mPhase, (long long)mPeriod); + + if (err < kErrorThreshold) { + if (!mPrimed && mSamplesUsedForPriming >= kMinSamplesToStopPrime) { + mPrimed = true; + } + } else { + mPrimed = false; + mSamplesUsedForPriming = 0; + } + } + } + return mPeriod; +} + +/* ======================================================================= */ +/* Frame Scheduler */ +/* ======================================================================= */ + +static const nsecs_t kDefaultVsyncPeriod = kNanosIn1s / 60; // 60Hz +static const nsecs_t kVsyncRefreshPeriod = kNanosIn1s; // 1 sec + +VideoFrameScheduler::VideoFrameScheduler() + : mVsyncTime(0), + mVsyncPeriod(0), + mVsyncRefreshAt(0), + mLastVsyncTime(-1), + mTimeCorrection(0) { +} + +void VideoFrameScheduler::updateVsync() { + mVsyncRefreshAt = systemTime(SYSTEM_TIME_MONOTONIC) + kVsyncRefreshPeriod; + mVsyncPeriod = 0; + mVsyncTime = 0; + + // TODO: schedule frames for the destination surface + // For now, surface flinger only schedules frames on the primary display + if (mComposer == NULL) { + String16 name("SurfaceFlinger"); + sp<IServiceManager> sm = defaultServiceManager(); + mComposer = interface_cast<ISurfaceComposer>(sm->checkService(name)); + } + if (mComposer != NULL) { + DisplayStatInfo stats; + status_t res = mComposer->getDisplayStats(NULL /* display */, &stats); + if (res == OK) { + ALOGV("vsync time:%lld period:%lld", + (long long)stats.vsyncTime, (long long)stats.vsyncPeriod); + mVsyncTime = stats.vsyncTime; + mVsyncPeriod = stats.vsyncPeriod; + } else { + ALOGW("getDisplayStats returned %d", res); + } + } else { + ALOGW("could not get surface mComposer service"); + } +} + +void VideoFrameScheduler::init(float videoFps) { + updateVsync(); + + mLastVsyncTime = -1; + mTimeCorrection = 0; + + mPll.reset(videoFps); +} + +void VideoFrameScheduler::restart() { + mLastVsyncTime = -1; + mTimeCorrection = 0; + + mPll.restart(); +} + +nsecs_t VideoFrameScheduler::getVsyncPeriod() { + if (mVsyncPeriod > 0) { + return mVsyncPeriod; + } + return kDefaultVsyncPeriod; +} + +nsecs_t VideoFrameScheduler::schedule(nsecs_t renderTime) { + nsecs_t origRenderTime = renderTime; + + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + if (now >= mVsyncRefreshAt) { + updateVsync(); + } + + // without VSYNC info, there is nothing to do + if (mVsyncPeriod == 0) { + ALOGV("no vsync: render=%lld", (long long)renderTime); + return renderTime; + } + + // ensure vsync time is well before (corrected) render time + if (mVsyncTime > renderTime - 4 * mVsyncPeriod) { + mVsyncTime -= + ((mVsyncTime - renderTime) / mVsyncPeriod + 5) * mVsyncPeriod; + } + + // Video presentation takes place at the VSYNC _after_ renderTime. Adjust renderTime + // so this effectively becomes a rounding operation (to the _closest_ VSYNC.) + renderTime -= mVsyncPeriod / 2; + + const nsecs_t videoPeriod = mPll.addSample(origRenderTime); + if (videoPeriod > 0) { + // Smooth out rendering + size_t N = 12; + nsecs_t fiveSixthDev = + abs(((videoPeriod * 5 + mVsyncPeriod) % (mVsyncPeriod * 6)) - mVsyncPeriod) + / (mVsyncPeriod / 100); + // use 20 samples if we are doing 5:6 ratio +- 1% (e.g. playing 50Hz on 60Hz) + if (fiveSixthDev < 12) { /* 12% / 6 = 2% */ + N = 20; + } + + nsecs_t offset = 0; + nsecs_t edgeRemainder = 0; + for (size_t i = 1; i <= N; i++) { + offset += + (renderTime + mTimeCorrection + videoPeriod * i - mVsyncTime) % mVsyncPeriod; + edgeRemainder += (videoPeriod * i) % mVsyncPeriod; + } + mTimeCorrection += mVsyncPeriod / 2 - offset / N; + renderTime += mTimeCorrection; + nsecs_t correctionLimit = mVsyncPeriod * 3 / 5; + edgeRemainder = abs(edgeRemainder / N - mVsyncPeriod / 2); + if (edgeRemainder <= mVsyncPeriod / 3) { + correctionLimit /= 2; + } + + // estimate how many VSYNCs a frame will spend on the display + nsecs_t nextVsyncTime = + renderTime + mVsyncPeriod - ((renderTime - mVsyncTime) % mVsyncPeriod); + if (mLastVsyncTime >= 0) { + size_t minVsyncsPerFrame = videoPeriod / mVsyncPeriod; + size_t vsyncsForLastFrame = divRound(nextVsyncTime - mLastVsyncTime, mVsyncPeriod); + bool vsyncsPerFrameAreNearlyConstant = + periodicError(videoPeriod, mVsyncPeriod) / (mVsyncPeriod / 20) == 0; + + if (mTimeCorrection > correctionLimit && + (vsyncsPerFrameAreNearlyConstant || vsyncsForLastFrame > minVsyncsPerFrame)) { + // remove a VSYNC + mTimeCorrection -= mVsyncPeriod / 2; + renderTime -= mVsyncPeriod / 2; + nextVsyncTime -= mVsyncPeriod; + --vsyncsForLastFrame; + } else if (mTimeCorrection < -correctionLimit && + (vsyncsPerFrameAreNearlyConstant || vsyncsForLastFrame == minVsyncsPerFrame)) { + // add a VSYNC + mTimeCorrection += mVsyncPeriod / 2; + renderTime += mVsyncPeriod / 2; + nextVsyncTime += mVsyncPeriod; + ++vsyncsForLastFrame; + } + ATRACE_INT("FRAME_VSYNCS", vsyncsForLastFrame); + } + mLastVsyncTime = nextVsyncTime; + } + + // align rendertime to the center between VSYNC edges + renderTime -= (renderTime - mVsyncTime) % mVsyncPeriod; + renderTime += mVsyncPeriod / 2; + ALOGV("adjusting render: %lld => %lld", (long long)origRenderTime, (long long)renderTime); + ATRACE_INT("FRAME_FLIP_IN(ms)", (renderTime - now) / 1000000); + return renderTime; +} + +void VideoFrameScheduler::release() { + mComposer.clear(); +} + +VideoFrameScheduler::~VideoFrameScheduler() { + release(); +} + +} // namespace android + diff --git a/media/libmediaplayerservice/VideoFrameScheduler.h b/media/libmediaplayerservice/VideoFrameScheduler.h new file mode 100644 index 0000000..19f0787 --- /dev/null +++ b/media/libmediaplayerservice/VideoFrameScheduler.h @@ -0,0 +1,98 @@ +/* + * 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. + */ + +#ifndef VIDEO_FRAME_SCHEDULER_H_ +#define VIDEO_FRAME_SCHEDULER_H_ + +#include <utils/RefBase.h> +#include <utils/Timers.h> + +#include <media/stagefright/foundation/ABase.h> + +namespace android { + +struct ISurfaceComposer; + +struct VideoFrameScheduler : public RefBase { + VideoFrameScheduler(); + + // (re)initialize scheduler + void init(float videoFps = -1); + // use in case of video render-time discontinuity, e.g. seek + void restart(); + // get adjusted nanotime for a video frame render at renderTime + nsecs_t schedule(nsecs_t renderTime); + + // returns the vsync period for the main display + nsecs_t getVsyncPeriod(); + + void release(); + + static const size_t kHistorySize = 8; + +protected: + virtual ~VideoFrameScheduler(); + +private: + struct PLL { + PLL(); + + // reset PLL to new PLL + void reset(float fps = -1); + // keep current estimate, but restart phase + void restart(); + // returns period + nsecs_t addSample(nsecs_t time); + + private: + nsecs_t mPeriod; + nsecs_t mPhase; + + bool mPrimed; // have an estimate for the period + size_t mSamplesUsedForPriming; + + nsecs_t mLastTime; // last input time + nsecs_t mRefitAt; // next input time to fit at + + size_t mNumSamples; // can go past kHistorySize + nsecs_t mTimes[kHistorySize]; + + void test(); + void fit(nsecs_t phase, nsecs_t period, size_t numSamples, + int64_t *a, int64_t *b, int64_t *err); + void prime(size_t numSamples); + }; + + void updateVsync(); + + nsecs_t mVsyncTime; // vsync timing from display + nsecs_t mVsyncPeriod; + nsecs_t mVsyncRefreshAt; // next time to refresh timing info + + nsecs_t mLastVsyncTime; // estimated vsync time for last frame + nsecs_t mTimeCorrection; // running adjustment + + PLL mPll; // PLL for video frame rate based on render time + + sp<ISurfaceComposer> mComposer; + + DISALLOW_EVIL_CONSTRUCTORS(VideoFrameScheduler); +}; + +} // namespace android + +#endif // VIDEO_FRAME_SCHEDULER_H_ + diff --git a/media/libmediaplayerservice/nuplayer/Android.mk b/media/libmediaplayerservice/nuplayer/Android.mk index f946c1c..676c0a6 100644 --- a/media/libmediaplayerservice/nuplayer/Android.mk +++ b/media/libmediaplayerservice/nuplayer/Android.mk @@ -6,18 +6,20 @@ LOCAL_SRC_FILES:= \ HTTPLiveSource.cpp \ NuPlayer.cpp \ NuPlayerDecoder.cpp \ + NuPlayerDecoderPassThrough.cpp \ NuPlayerDriver.cpp \ NuPlayerRenderer.cpp \ NuPlayerStreamListener.cpp \ RTSPSource.cpp \ StreamingSource.cpp \ - mp4/MP4Source.cpp \ LOCAL_C_INCLUDES := \ $(TOP)/frameworks/av/media/libstagefright/httplive \ $(TOP)/frameworks/av/media/libstagefright/include \ $(TOP)/frameworks/av/media/libstagefright/mpeg2ts \ $(TOP)/frameworks/av/media/libstagefright/rtsp \ + $(TOP)/frameworks/av/media/libstagefright/timedtext \ + $(TOP)/frameworks/av/media/libmediaplayerservice \ $(TOP)/frameworks/native/include/media/openmax LOCAL_MODULE:= libstagefright_nuplayer diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp index b04e7a6..bd75034 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> @@ -28,57 +32,155 @@ #include <media/stagefright/MediaExtractor.h> #include <media/stagefright/MediaSource.h> #include <media/stagefright/MetaData.h> +#include <media/stagefright/Utils.h> +#include "../../libstagefright/include/DRMExtractor.h" +#include "../../libstagefright/include/NuCachedSource2.h" +#include "../../libstagefright/include/WVMExtractor.h" namespace android { NuPlayer::GenericSource::GenericSource( const sp<AMessage> ¬ify, - const char *url, - const KeyedVector<String8, String8> *headers, bool uidValid, uid_t uid) : Source(notify), + mFetchSubtitleDataGeneration(0), + mFetchTimedTextDataGeneration(0), mDurationUs(0ll), - mAudioIsVorbis(false) { + mAudioIsVorbis(false), + mIsWidevine(false), + mUIDValid(uidValid), + mUID(uid), + mDrmManagerClient(NULL), + mMetaDataSize(-1ll), + mBitrate(-1ll), + mPollBufferingGeneration(0), + mPendingReadBufferTypes(0) { + resetDataSource(); DataSource::RegisterDefaultSniffers(); +} + +void NuPlayer::GenericSource::resetDataSource() { + mAudioTimeUs = 0; + mVideoTimeUs = 0; + mHTTPService.clear(); + mUri.clear(); + mUriHeaders.clear(); + mFd = -1; + mOffset = 0; + mLength = 0; + setDrmPlaybackStatusIfNeeded(Playback::STOP, 0); + mDecryptHandle = NULL; + mDrmManagerClient = NULL; + mStarted = false; +} - sp<DataSource> dataSource = - DataSource::CreateFromURI(url, headers); - CHECK(dataSource != NULL); +status_t NuPlayer::GenericSource::setDataSource( + const sp<IMediaHTTPService> &httpService, + const char *url, + const KeyedVector<String8, String8> *headers) { + resetDataSource(); + + mHTTPService = httpService; + mUri = url; - initFromDataSource(dataSource); + 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), - mDurationUs(0ll), - mAudioIsVorbis(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; +} + +sp<MetaData> NuPlayer::GenericSource::getFileFormatMeta() const { + return mFileMeta; } -void NuPlayer::GenericSource::initFromDataSource( - const sp<DataSource> &dataSource) { - sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource); +status_t NuPlayer::GenericSource::initFromDataSource() { + sp<MediaExtractor> extractor; + + CHECK(mDataSource != NULL); - CHECK(extractor != NULL); + if (mIsWidevine) { + String8 mimeType; + float confidence; + sp<AMessage> dummy; + bool success; + + success = SniffWVM(mDataSource, &mimeType, &confidence, &dummy); + if (!success + || strcasecmp( + mimeType.string(), MEDIA_MIMETYPE_CONTAINER_WVM)) { + ALOGE("unsupported widevine mime: %s", mimeType.string()); + return UNKNOWN_ERROR; + } + + mWVMExtractor = new WVMExtractor(mDataSource); + mWVMExtractor->setAdaptiveStreamingMode(true); + if (mUIDValid) { + mWVMExtractor->setUID(mUID); + } + extractor = mWVMExtractor; + } else { + extractor = MediaExtractor::Create(mDataSource, + mSniffedMIME.empty() ? NULL: mSniffedMIME.c_str()); + } + + if (extractor == NULL) { + return UNKNOWN_ERROR; + } + + if (extractor->getDrmFlag()) { + checkDrmStatus(mDataSource); + } + + mFileMeta = extractor->getMetaData(); + if (mFileMeta != NULL) { + int64_t duration; + if (mFileMeta->findInt64(kKeyDuration, &duration)) { + mDurationUs = duration; + } + } + + int32_t totalBitrate = 0; + + size_t numtracks = extractor->countTracks(); + if (numtracks == 0) { + return UNKNOWN_ERROR; + } + + for (size_t i = 0; i < numtracks; ++i) { + sp<MediaSource> track = extractor->getTrack(i); - for (size_t i = 0; i < extractor->countTracks(); ++i) { sp<MetaData> meta = extractor->getTrackMetaData(i); const char *mime; CHECK(meta->findCString(kKeyMIMEType, &mime)); - sp<MediaSource> track; - + // Do the string compare immediately with "mime", + // we can't assume "mime" would stay valid after another + // extractor operation, some extractors might modify meta + // during getTrack() and make it invalid. if (!strncasecmp(mime, "audio/", 6)) { if (mAudioTrack.mSource == NULL) { - mAudioTrack.mSource = track = extractor->getTrack(i); + mAudioTrack.mIndex = i; + mAudioTrack.mSource = track; + mAudioTrack.mPackets = + new AnotherPacketSource(mAudioTrack.mSource->getFormat()); if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) { mAudioIsVorbis = true; @@ -88,37 +190,162 @@ void NuPlayer::GenericSource::initFromDataSource( } } else if (!strncasecmp(mime, "video/", 6)) { if (mVideoTrack.mSource == NULL) { - mVideoTrack.mSource = track = extractor->getTrack(i); + mVideoTrack.mIndex = i; + mVideoTrack.mSource = track; + mVideoTrack.mPackets = + new AnotherPacketSource(mVideoTrack.mSource->getFormat()); + + // check if the source requires secure buffers + int32_t secure; + if (meta->findInt32(kKeyRequiresSecureBuffers, &secure) + && secure) { + mIsWidevine = true; + if (mUIDValid) { + extractor->setUID(mUID); + } + } } } if (track != NULL) { + mSources.push(track); int64_t durationUs; if (meta->findInt64(kKeyDuration, &durationUs)) { if (durationUs > mDurationUs) { mDurationUs = durationUs; } } + + int32_t bitrate; + if (totalBitrate >= 0 && meta->findInt32(kKeyBitRate, &bitrate)) { + totalBitrate += bitrate; + } else { + totalBitrate = -1; + } + } + } + + mBitrate = totalBitrate; + + return OK; +} + +void NuPlayer::GenericSource::checkDrmStatus(const sp<DataSource>& dataSource) { + dataSource->getDrmInfo(mDecryptHandle, &mDrmManagerClient); + if (mDecryptHandle != NULL) { + CHECK(mDrmManagerClient); + if (RightsStatus::RIGHTS_VALID != mDecryptHandle->status) { + sp<AMessage> msg = dupNotify(); + msg->setInt32("what", kWhatDrmNoLicense); + msg->post(); } } } +int64_t NuPlayer::GenericSource::getLastReadPosition() { + if (mAudioTrack.mSource != NULL) { + return mAudioTimeUs; + } else if (mVideoTrack.mSource != NULL) { + return mVideoTimeUs; + } else { + return 0; + } +} + +status_t NuPlayer::GenericSource::setBuffers( + bool audio, Vector<MediaBuffer *> &buffers) { + if (mIsWidevine && !audio) { + return mVideoTrack.mSource->setBuffers(buffers); + } + return INVALID_OPERATION; +} + NuPlayer::GenericSource::~GenericSource() { + if (mLooper != NULL) { + mLooper->unregisterHandler(id()); + mLooper->stop(); + } } void NuPlayer::GenericSource::prepareAsync() { - if (mVideoTrack.mSource != NULL) { - sp<MetaData> meta = mVideoTrack.mSource->getFormat(); + if (mLooper == NULL) { + mLooper = new ALooper; + mLooper->setName("generic"); + mLooper->start(); + + mLooper->registerHandler(this); + } - int32_t width, height; - CHECK(meta->findInt32(kKeyWidth, &width)); - CHECK(meta->findInt32(kKeyHeight, &height)); + sp<AMessage> msg = new AMessage(kWhatPrepareAsync, id()); + msg->post(); +} + +void NuPlayer::GenericSource::onPrepareAsync() { + // delayed data source creation + if (mDataSource == NULL) { + if (!mUri.empty()) { + mIsWidevine = !strncasecmp(mUri.c_str(), "widevine://", 11); + + mDataSource = DataSource::CreateFromURI( + mHTTPService, mUri.c_str(), &mUriHeaders, &mContentType); + } else { + // set to false first, if the extractor + // comes back as secure, set it to true then. + mIsWidevine = false; + + mDataSource = new FileSource(mFd, mOffset, mLength); + } + + if (mDataSource == NULL) { + ALOGE("Failed to create data source!"); + notifyPreparedAndCleanup(UNKNOWN_ERROR); + return; + } + + if (mDataSource->flags() & DataSource::kIsCachingDataSource) { + mCachedSource = static_cast<NuCachedSource2 *>(mDataSource.get()); + } - notifyVideoSizeChanged(width, height); + if (mIsWidevine || mCachedSource != NULL) { + schedulePollBuffering(); + } + } + + // check initial caching status + status_t err = prefillCacheIfNecessary(); + if (err != OK) { + if (err == -EAGAIN) { + (new AMessage(kWhatPrepareAsync, id()))->post(200000); + } else { + ALOGE("Failed to prefill data cache!"); + notifyPreparedAndCleanup(UNKNOWN_ERROR); + } + return; + } + + // init extrator from data source + err = initFromDataSource(); + + if (err != OK) { + ALOGE("Failed to init from data source!"); + notifyPreparedAndCleanup(err); + return; + } + + if (mVideoTrack.mSource != NULL) { + sp<MetaData> meta = doGetFormatMeta(false /* audio */); + sp<AMessage> msg = new AMessage; + err = convertMetaDataToMessage(meta, &msg); + if(err != OK) { + notifyPreparedAndCleanup(err); + return; + } + notifyVideoSizeChanged(msg); } notifyFlagsChanged( - FLAG_CAN_PAUSE + (mIsWidevine ? FLAG_SECURE : 0) + | FLAG_CAN_PAUSE | FLAG_CAN_SEEK_BACKWARD | FLAG_CAN_SEEK_FORWARD | FLAG_CAN_SEEK); @@ -126,33 +353,431 @@ void NuPlayer::GenericSource::prepareAsync() { notifyPrepared(); } +void NuPlayer::GenericSource::notifyPreparedAndCleanup(status_t err) { + if (err != OK) { + mMetaDataSize = -1ll; + mContentType = ""; + mSniffedMIME = ""; + mDataSource.clear(); + mCachedSource.clear(); + + cancelPollBuffering(); + } + notifyPrepared(err); +} + +status_t NuPlayer::GenericSource::prefillCacheIfNecessary() { + CHECK(mDataSource != NULL); + + if (mCachedSource == NULL) { + // no prefill if the data source is not cached + return OK; + } + + // 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. + if (!strncasecmp(mContentType.string(), "audio/", 6)) { + return OK; + } + + // 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; + static const size_t kDefaultMetaSize = 200000; + + status_t finalStatus; + + size_t cachedDataRemaining = + mCachedSource->approxDataRemaining(&finalStatus); + + if (finalStatus != OK || (mMetaDataSize >= 0 + && (off64_t)cachedDataRemaining >= mMetaDataSize)) { + ALOGV("stop caching, status %d, " + "metaDataSize %lld, cachedDataRemaining %zu", + finalStatus, mMetaDataSize, cachedDataRemaining); + return OK; + } + + ALOGV("now cached %zu bytes of data", cachedDataRemaining); + + if (mMetaDataSize < 0 + && cachedDataRemaining >= kMinBytesForSniffing) { + String8 tmp; + float confidence; + sp<AMessage> meta; + if (!mCachedSource->sniff(&tmp, &confidence, &meta)) { + return UNKNOWN_ERROR; + } + + // 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() + mSniffedMIME = tmp.string(); + + if (meta == NULL + || !meta->findInt64("meta-data-size", + reinterpret_cast<int64_t*>(&mMetaDataSize))) { + mMetaDataSize = kDefaultMetaSize; + } + + if (mMetaDataSize < 0ll) { + ALOGE("invalid metaDataSize = %lld bytes", mMetaDataSize); + return UNKNOWN_ERROR; + } + } + + return -EAGAIN; +} + void NuPlayer::GenericSource::start() { ALOGI("start"); if (mAudioTrack.mSource != NULL) { CHECK_EQ(mAudioTrack.mSource->start(), (status_t)OK); - mAudioTrack.mPackets = - new AnotherPacketSource(mAudioTrack.mSource->getFormat()); - - readBuffer(true /* audio */); + postReadBuffer(MEDIA_TRACK_TYPE_AUDIO); } if (mVideoTrack.mSource != NULL) { CHECK_EQ(mVideoTrack.mSource->start(), (status_t)OK); - mVideoTrack.mPackets = - new AnotherPacketSource(mVideoTrack.mSource->getFormat()); + postReadBuffer(MEDIA_TRACK_TYPE_VIDEO); + } - readBuffer(false /* audio */); + setDrmPlaybackStatusIfNeeded(Playback::START, getLastReadPosition() / 1000); + mStarted = true; +} + +void NuPlayer::GenericSource::stop() { + // nothing to do, just account for DRM playback status + setDrmPlaybackStatusIfNeeded(Playback::STOP, 0); + mStarted = false; +} + +void NuPlayer::GenericSource::pause() { + // nothing to do, just account for DRM playback status + setDrmPlaybackStatusIfNeeded(Playback::PAUSE, 0); + mStarted = false; +} + +void NuPlayer::GenericSource::resume() { + // nothing to do, just account for DRM playback status + setDrmPlaybackStatusIfNeeded(Playback::START, getLastReadPosition() / 1000); + mStarted = true; +} + +void NuPlayer::GenericSource::disconnect() { + if (mDataSource != NULL) { + // disconnect data source + if (mDataSource->flags() & DataSource::kIsCachingDataSource) { + static_cast<NuCachedSource2 *>(mDataSource.get())->disconnect(); + } + } +} + +void NuPlayer::GenericSource::setDrmPlaybackStatusIfNeeded(int playbackStatus, int64_t position) { + if (mDecryptHandle != NULL) { + mDrmManagerClient->setPlaybackStatus(mDecryptHandle, playbackStatus, position); } + mSubtitleTrack.mPackets = new AnotherPacketSource(NULL); + mTimedTextTrack.mPackets = new AnotherPacketSource(NULL); } status_t NuPlayer::GenericSource::feedMoreTSData() { return OK; } +void NuPlayer::GenericSource::schedulePollBuffering() { + sp<AMessage> msg = new AMessage(kWhatPollBuffering, id()); + msg->setInt32("generation", mPollBufferingGeneration); + msg->post(1000000ll); +} + +void NuPlayer::GenericSource::cancelPollBuffering() { + ++mPollBufferingGeneration; +} + +void NuPlayer::GenericSource::notifyBufferingUpdate(int percentage) { + sp<AMessage> msg = dupNotify(); + msg->setInt32("what", kWhatBufferingUpdate); + msg->setInt32("percentage", percentage); + msg->post(); +} + +void NuPlayer::GenericSource::onPollBuffering() { + status_t finalStatus = UNKNOWN_ERROR; + int64_t cachedDurationUs = 0ll; + + if (mCachedSource != NULL) { + size_t cachedDataRemaining = + mCachedSource->approxDataRemaining(&finalStatus); + + if (finalStatus == OK) { + off64_t size; + int64_t bitrate = 0ll; + if (mDurationUs > 0 && mCachedSource->getSize(&size) == OK) { + bitrate = size * 8000000ll / mDurationUs; + } else if (mBitrate > 0) { + bitrate = mBitrate; + } + if (bitrate > 0) { + cachedDurationUs = cachedDataRemaining * 8000000ll / bitrate; + } + } + } else if (mWVMExtractor != NULL) { + cachedDurationUs + = mWVMExtractor->getCachedDurationUs(&finalStatus); + } + + if (finalStatus == ERROR_END_OF_STREAM) { + notifyBufferingUpdate(100); + cancelPollBuffering(); + return; + } else if (cachedDurationUs > 0ll && mDurationUs > 0ll) { + int percentage = 100.0 * cachedDurationUs / mDurationUs; + if (percentage > 100) { + percentage = 100; + } + + notifyBufferingUpdate(percentage); + } + + schedulePollBuffering(); +} + + +void NuPlayer::GenericSource::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatPrepareAsync: + { + onPrepareAsync(); + break; + } + case kWhatFetchSubtitleData: + { + 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: + { + sendTextData(kWhatSubtitleData, MEDIA_TRACK_TYPE_SUBTITLE, + mFetchSubtitleDataGeneration, mSubtitleTrack.mPackets, msg); + break; + } + + case kWhatSendTimedTextData: + { + sendTextData(kWhatTimedTextData, MEDIA_TRACK_TYPE_TIMEDTEXT, + mFetchTimedTextDataGeneration, mTimedTextTrack.mPackets, msg); + break; + } + + case kWhatChangeAVSource: + { + int32_t trackIndex; + CHECK(msg->findInt32("trackIndex", &trackIndex)); + const sp<MediaSource> source = mSources.itemAt(trackIndex); + + Track* track; + const char *mime; + media_track_type trackType, counterpartType; + sp<MetaData> meta = source->getFormat(); + meta->findCString(kKeyMIMEType, &mime); + if (!strncasecmp(mime, "audio/", 6)) { + track = &mAudioTrack; + trackType = MEDIA_TRACK_TYPE_AUDIO; + counterpartType = MEDIA_TRACK_TYPE_VIDEO;; + } else { + CHECK(!strncasecmp(mime, "video/", 6)); + track = &mVideoTrack; + trackType = MEDIA_TRACK_TYPE_VIDEO; + counterpartType = MEDIA_TRACK_TYPE_AUDIO;; + } + + + if (track->mSource != NULL) { + track->mSource->stop(); + } + track->mSource = source; + track->mSource->start(); + track->mIndex = trackIndex; + + status_t avail; + if (!track->mPackets->hasBufferAvailable(&avail)) { + // sync from other source + TRESPASS(); + break; + } + + int64_t timeUs, actualTimeUs; + const bool formatChange = true; + sp<AMessage> latestMeta = track->mPackets->getLatestEnqueuedMeta(); + CHECK(latestMeta != NULL && latestMeta->findInt64("timeUs", &timeUs)); + readBuffer(trackType, timeUs, &actualTimeUs, formatChange); + readBuffer(counterpartType, -1, NULL, formatChange); + ALOGV("timeUs %lld actualTimeUs %lld", timeUs, actualTimeUs); + + break; + } + case kWhatPollBuffering: + { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + if (generation == mPollBufferingGeneration) { + onPollBuffering(); + } + break; + } + + case kWhatGetFormat: + { + onGetFormatMeta(msg); + break; + } + + case kWhatGetSelectedTrack: + { + onGetSelectedTrack(msg); + break; + } + + case kWhatSelectTrack: + { + onSelectTrack(msg); + break; + } + + case kWhatSeek: + { + onSeek(msg); + break; + } + + case kWhatReadBuffer: + { + onReadBuffer(msg); + break; + } + + default: + Source::onMessageReceived(msg); + break; + } +} + +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<AMessage> msg = new AMessage(kWhatGetFormat, id()); + msg->setInt32("audio", audio); + + sp<AMessage> response; + void *format; + status_t err = msg->postAndAwaitResponse(&response); + if (err == OK && response != NULL) { + CHECK(response->findPointer("format", &format)); + return (MetaData *)format; + } else { + return NULL; + } +} + +void NuPlayer::GenericSource::onGetFormatMeta(sp<AMessage> msg) const { + int32_t audio; + CHECK(msg->findInt32("audio", &audio)); + + sp<AMessage> response = new AMessage; + sp<MetaData> format = doGetFormatMeta(audio); + response->setPointer("format", format.get()); + + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + response->postReply(replyID); +} + +sp<MetaData> NuPlayer::GenericSource::doGetFormatMeta(bool audio) const { sp<MediaSource> source = audio ? mAudioTrack.mSource : mVideoTrack.mSource; if (source == NULL) { @@ -170,14 +795,53 @@ status_t NuPlayer::GenericSource::dequeueAccessUnit( return -EWOULDBLOCK; } + if (mIsWidevine && !audio) { + // try to read a buffer as we may not have been able to the last time + postReadBuffer(MEDIA_TRACK_TYPE_VIDEO); + } + status_t finalResult; if (!track->mPackets->hasBufferAvailable(&finalResult)) { - return finalResult == OK ? -EWOULDBLOCK : finalResult; + return (finalResult == OK ? -EWOULDBLOCK : finalResult); } status_t result = track->mPackets->dequeueAccessUnit(accessUnit); - readBuffer(audio, -1ll); + if (!track->mPackets->hasBufferAvailable(&finalResult)) { + postReadBuffer(audio? MEDIA_TRACK_TYPE_AUDIO : MEDIA_TRACK_TYPE_VIDEO); + } + + if (result != OK) { + 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.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; } @@ -187,25 +851,371 @@ status_t NuPlayer::GenericSource::getDuration(int64_t *durationUs) { return OK; } +size_t NuPlayer::GenericSource::getTrackCount() const { + return mSources.size(); +} + +sp<AMessage> NuPlayer::GenericSource::getTrackInfo(size_t trackIndex) const { + size_t trackCount = mSources.size(); + if (trackIndex >= trackCount) { + return NULL; + } + + sp<AMessage> format = new AMessage(); + sp<MetaData> meta = mSources.itemAt(trackIndex)->getFormat(); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + int32_t trackType; + if (!strncasecmp(mime, "video/", 6)) { + trackType = MEDIA_TRACK_TYPE_VIDEO; + } else if (!strncasecmp(mime, "audio/", 6)) { + trackType = MEDIA_TRACK_TYPE_AUDIO; + } else if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) { + trackType = MEDIA_TRACK_TYPE_TIMEDTEXT; + } else { + trackType = MEDIA_TRACK_TYPE_UNKNOWN; + } + format->setInt32("type", trackType); + + const char *lang; + if (!meta->findCString(kKeyMediaLanguage, &lang)) { + lang = "und"; + } + format->setString("language", lang); + + if (trackType == MEDIA_TRACK_TYPE_SUBTITLE) { + format->setString("mime", mime); + + int32_t isAutoselect = 1, isDefault = 0, isForced = 0; + meta->findInt32(kKeyTrackIsAutoselect, &isAutoselect); + meta->findInt32(kKeyTrackIsDefault, &isDefault); + meta->findInt32(kKeyTrackIsForced, &isForced); + + format->setInt32("auto", !!isAutoselect); + format->setInt32("default", !!isDefault); + format->setInt32("forced", !!isForced); + } + + return format; +} + +ssize_t NuPlayer::GenericSource::getSelectedTrack(media_track_type type) const { + sp<AMessage> msg = new AMessage(kWhatGetSelectedTrack, id()); + msg->setInt32("type", type); + + sp<AMessage> response; + int32_t index; + status_t err = msg->postAndAwaitResponse(&response); + if (err == OK && response != NULL) { + CHECK(response->findInt32("index", &index)); + return index; + } else { + return -1; + } +} + +void NuPlayer::GenericSource::onGetSelectedTrack(sp<AMessage> msg) const { + int32_t tmpType; + CHECK(msg->findInt32("type", &tmpType)); + media_track_type type = (media_track_type)tmpType; + + sp<AMessage> response = new AMessage; + ssize_t index = doGetSelectedTrack(type); + response->setInt32("index", index); + + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + response->postReply(replyID); +} + +ssize_t NuPlayer::GenericSource::doGetSelectedTrack(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("%s track: %zu", select ? "select" : "deselect", trackIndex); + sp<AMessage> msg = new AMessage(kWhatSelectTrack, id()); + msg->setInt32("trackIndex", trackIndex); + msg->setInt32("select", select); + + sp<AMessage> response; + status_t err = msg->postAndAwaitResponse(&response); + if (err == OK && response != NULL) { + CHECK(response->findInt32("err", &err)); + } + + return err; +} + +void NuPlayer::GenericSource::onSelectTrack(sp<AMessage> msg) { + int32_t trackIndex, select; + CHECK(msg->findInt32("trackIndex", &trackIndex)); + CHECK(msg->findInt32("select", &select)); + + sp<AMessage> response = new AMessage; + status_t err = doSelectTrack(trackIndex, select); + response->setInt32("err", err); + + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + response->postReply(replyID); +} + +status_t NuPlayer::GenericSource::doSelectTrack(size_t trackIndex, bool select) { + if (trackIndex >= mSources.size()) { + return BAD_INDEX; + } + + if (!select) { + 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; + } + track->mSource->stop(); + track->mSource = NULL; + track->mPackets->clear(); + return OK; + } + + const sp<MediaSource> source = mSources.itemAt(trackIndex); + sp<MetaData> meta = source->getFormat(); + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + if (!strncasecmp(mime, "text/", 5)) { + bool isSubtitle = strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP); + Track *track = isSubtitle ? &mSubtitleTrack : &mTimedTextTrack; + if (track->mSource != NULL && track->mIndex == trackIndex) { + return OK; + } + track->mIndex = trackIndex; + if (track->mSource != NULL) { + track->mSource->stop(); + } + track->mSource = mSources.itemAt(trackIndex); + track->mSource->start(); + if (track->mPackets == NULL) { + track->mPackets = new AnotherPacketSource(track->mSource->getFormat()); + } else { + track->mPackets->clear(); + track->mPackets->setFormat(track->mSource->getFormat()); + + } + + if (isSubtitle) { + mFetchSubtitleDataGeneration++; + } else { + mFetchTimedTextDataGeneration++; + } + + return OK; + } else if (!strncasecmp(mime, "audio/", 6) || !strncasecmp(mime, "video/", 6)) { + bool audio = !strncasecmp(mime, "audio/", 6); + Track *track = audio ? &mAudioTrack : &mVideoTrack; + if (track->mSource != NULL && track->mIndex == trackIndex) { + return OK; + } + + sp<AMessage> msg = new AMessage(kWhatChangeAVSource, id()); + msg->setInt32("trackIndex", trackIndex); + msg->post(); + return OK; + } + + return INVALID_OPERATION; +} + status_t NuPlayer::GenericSource::seekTo(int64_t seekTimeUs) { + sp<AMessage> msg = new AMessage(kWhatSeek, id()); + msg->setInt64("seekTimeUs", seekTimeUs); + + sp<AMessage> response; + status_t err = msg->postAndAwaitResponse(&response); + if (err == OK && response != NULL) { + CHECK(response->findInt32("err", &err)); + } + + return err; +} + +void NuPlayer::GenericSource::onSeek(sp<AMessage> msg) { + int64_t seekTimeUs; + CHECK(msg->findInt64("seekTimeUs", &seekTimeUs)); + + sp<AMessage> response = new AMessage; + status_t err = doSeek(seekTimeUs); + response->setInt32("err", err); + + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + response->postReply(replyID); +} + +status_t NuPlayer::GenericSource::doSeek(int64_t seekTimeUs) { if (mVideoTrack.mSource != NULL) { int64_t actualTimeUs; - readBuffer(false /* audio */, seekTimeUs, &actualTimeUs); + readBuffer(MEDIA_TRACK_TYPE_VIDEO, seekTimeUs, &actualTimeUs); seekTimeUs = actualTimeUs; } if (mAudioTrack.mSource != NULL) { - readBuffer(true /* audio */, seekTimeUs); + readBuffer(MEDIA_TRACK_TYPE_AUDIO, seekTimeUs); } + setDrmPlaybackStatusIfNeeded(Playback::START, seekTimeUs / 1000); + if (!mStarted) { + setDrmPlaybackStatusIfNeeded(Playback::PAUSE, 0); + } return OK; } +sp<ABuffer> NuPlayer::GenericSource::mediaBufferToABuffer( + MediaBuffer* mb, + media_track_type trackType, + int64_t *actualTimeUs) { + bool audio = trackType == MEDIA_TRACK_TYPE_AUDIO; + size_t outLength = mb->range_length(); + + if (audio && mAudioIsVorbis) { + outLength += sizeof(int32_t); + } + + sp<ABuffer> ab; + if (mIsWidevine && !audio) { + // data is already provided in the buffer + ab = new ABuffer(NULL, mb->range_length()); + mb->add_ref(); + ab->setMediaBufferBase(mb); + } else { + ab = new ABuffer(outLength); + memcpy(ab->data(), + (const uint8_t *)mb->data() + mb->range_offset(), + mb->range_length()); + } + + if (audio && mAudioIsVorbis) { + int32_t numPageSamples; + if (!mb->meta_data()->findInt32(kKeyValidSamples, &numPageSamples)) { + numPageSamples = -1; + } + + uint8_t* abEnd = ab->data() + mb->range_length(); + memcpy(abEnd, &numPageSamples, sizeof(numPageSamples)); + } + + sp<AMessage> meta = ab->meta(); + + int64_t timeUs; + CHECK(mb->meta_data()->findInt64(kKeyTime, &timeUs)); + 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); + } + + if (trackType == MEDIA_TRACK_TYPE_SUBTITLE) { + meta->setInt32("trackIndex", mSubtitleTrack.mIndex); + } + + if (actualTimeUs) { + *actualTimeUs = timeUs; + } + + mb->release(); + mb = NULL; + + return ab; +} + +void NuPlayer::GenericSource::postReadBuffer(media_track_type trackType) { + Mutex::Autolock _l(mReadBufferLock); + + if ((mPendingReadBufferTypes & (1 << trackType)) == 0) { + mPendingReadBufferTypes |= (1 << trackType); + sp<AMessage> msg = new AMessage(kWhatReadBuffer, id()); + msg->setInt32("trackType", trackType); + msg->post(); + } +} + +void NuPlayer::GenericSource::onReadBuffer(sp<AMessage> msg) { + int32_t tmpType; + CHECK(msg->findInt32("trackType", &tmpType)); + media_track_type trackType = (media_track_type)tmpType; + { + // only protect the variable change, as readBuffer may + // take considerable time. This may result in one extra + // read being processed, but that is benign. + Mutex::Autolock _l(mReadBufferLock); + mPendingReadBufferTypes &= ~(1 << trackType); + } + readBuffer(trackType); +} + void NuPlayer::GenericSource::readBuffer( - bool audio, int64_t seekTimeUs, int64_t *actualTimeUs) { - Track *track = audio ? &mAudioTrack : &mVideoTrack; - CHECK(track->mSource != NULL); + media_track_type trackType, int64_t seekTimeUs, int64_t *actualTimeUs, bool formatChange) { + Track *track; + size_t maxBuffers = 1; + switch (trackType) { + case MEDIA_TRACK_TYPE_VIDEO: + track = &mVideoTrack; + break; + case MEDIA_TRACK_TYPE_AUDIO: + track = &mAudioTrack; + maxBuffers = 64; + break; + case MEDIA_TRACK_TYPE_SUBTITLE: + track = &mSubtitleTrack; + break; + case MEDIA_TRACK_TYPE_TIMEDTEXT: + track = &mTimedTextTrack; + break; + default: + TRESPASS(); + } + + if (track->mSource == NULL) { + return; + } if (actualTimeUs) { *actualTimeUs = seekTimeUs; @@ -216,64 +1226,56 @@ void NuPlayer::GenericSource::readBuffer( bool seeking = false; if (seekTimeUs >= 0) { - options.setSeekTo(seekTimeUs); + options.setSeekTo(seekTimeUs, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); seeking = true; } - for (;;) { + if (mIsWidevine && trackType != MEDIA_TRACK_TYPE_AUDIO) { + options.setNonBlocking(); + } + + for (size_t numBuffers = 0; numBuffers < maxBuffers; ) { MediaBuffer *mbuf; status_t err = track->mSource->read(&mbuf, &options); options.clearSeekTo(); if (err == OK) { - size_t outLength = mbuf->range_length(); - - if (audio && mAudioIsVorbis) { - outLength += sizeof(int32_t); - } - - sp<ABuffer> buffer = new ABuffer(outLength); - - memcpy(buffer->data(), - (const uint8_t *)mbuf->data() + mbuf->range_offset(), - mbuf->range_length()); - - if (audio && mAudioIsVorbis) { - int32_t numPageSamples; - if (!mbuf->meta_data()->findInt32( - kKeyValidSamples, &numPageSamples)) { - numPageSamples = -1; - } - - memcpy(buffer->data() + mbuf->range_length(), - &numPageSamples, - sizeof(numPageSamples)); - } - int64_t timeUs; CHECK(mbuf->meta_data()->findInt64(kKeyTime, &timeUs)); - - buffer->meta()->setInt64("timeUs", timeUs); - - if (actualTimeUs) { - *actualTimeUs = timeUs; + if (trackType == MEDIA_TRACK_TYPE_AUDIO) { + mAudioTimeUs = timeUs; + } else if (trackType == MEDIA_TRACK_TYPE_VIDEO) { + mVideoTimeUs = timeUs; } - mbuf->release(); - mbuf = NULL; - - if (seeking) { - track->mPackets->queueDiscontinuity( - ATSParser::DISCONTINUITY_SEEK, NULL); + // 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_AUDIO + || trackType == MEDIA_TRACK_TYPE_VIDEO)) { + ATSParser::DiscontinuityType type = formatChange + ? (seeking + ? ATSParser::DISCONTINUITY_FORMATCHANGE + : ATSParser::DISCONTINUITY_NONE) + : ATSParser::DISCONTINUITY_SEEK; + track->mPackets->queueDiscontinuity( type, NULL, true /* discard */); } + sp<ABuffer> buffer = mediaBufferToABuffer(mbuf, trackType, actualTimeUs); track->mPackets->queueAccessUnit(buffer); + formatChange = false; + seeking = false; + ++numBuffers; + } else if (err == WOULD_BLOCK) { break; } else if (err == INFO_FORMAT_CHANGED) { #if 0 track->mPackets->queueDiscontinuity( - ATSParser::DISCONTINUITY_FORMATCHANGE, NULL); + ATSParser::DISCONTINUITY_FORMATCHANGE, + NULL, + false /* discard */); #endif } else { track->mPackets->signalEOS(err); diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.h b/media/libmediaplayerservice/nuplayer/GenericSource.h index 2da680c..24bb6af 100644 --- a/media/libmediaplayerservice/nuplayer/GenericSource.h +++ b/media/libmediaplayerservice/nuplayer/GenericSource.h @@ -23,58 +23,172 @@ #include "ATSParser.h" +#include <media/mediaplayer.h> + namespace android { +class DecryptHandle; +class DrmManagerClient; struct AnotherPacketSource; struct ARTSPController; struct DataSource; +struct IMediaHTTPService; struct MediaSource; +class MediaBuffer; +struct NuCachedSource2; +struct WVMExtractor; 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 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(); virtual void start(); + virtual void stop(); + virtual void pause(); + virtual void resume(); + + virtual void disconnect(); virtual status_t feedMoreTSData(); + virtual sp<MetaData> getFileFormatMeta() const; + virtual status_t dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit); 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); + virtual status_t setBuffers(bool audio, Vector<MediaBuffer *> &buffers); + protected: virtual ~GenericSource(); + virtual void onMessageReceived(const sp<AMessage> &msg); + virtual sp<MetaData> getFormatMeta(bool audio); private: + enum { + kWhatPrepareAsync, + kWhatFetchSubtitleData, + kWhatFetchTimedTextData, + kWhatSendSubtitleData, + kWhatSendTimedTextData, + kWhatChangeAVSource, + kWhatPollBuffering, + kWhatGetFormat, + kWhatGetSelectedTrack, + kWhatSelectTrack, + kWhatSeek, + kWhatReadBuffer, + }; + + Vector<sp<MediaSource> > mSources; + struct Track { + size_t mIndex; sp<MediaSource> mSource; sp<AnotherPacketSource> mPackets; }; Track mAudioTrack; + int64_t mAudioTimeUs; Track mVideoTrack; + int64_t mVideoTimeUs; + Track mSubtitleTrack; + Track mTimedTextTrack; + int32_t mFetchSubtitleDataGeneration; + int32_t mFetchTimedTextDataGeneration; int64_t mDurationUs; bool mAudioIsVorbis; - - void initFromDataSource(const sp<DataSource> &dataSource); - + bool mIsWidevine; + bool mUIDValid; + uid_t mUID; + sp<IMediaHTTPService> mHTTPService; + AString mUri; + KeyedVector<String8, String8> mUriHeaders; + int mFd; + int64_t mOffset; + int64_t mLength; + + sp<DataSource> mDataSource; + sp<NuCachedSource2> mCachedSource; + sp<WVMExtractor> mWVMExtractor; + sp<MetaData> mFileMeta; + DrmManagerClient *mDrmManagerClient; + sp<DecryptHandle> mDecryptHandle; + bool mStarted; + String8 mContentType; + AString mSniffedMIME; + off64_t mMetaDataSize; + int64_t mBitrate; + int32_t mPollBufferingGeneration; + uint32_t mPendingReadBufferTypes; + mutable Mutex mReadBufferLock; + + sp<ALooper> mLooper; + + void resetDataSource(); + + status_t initFromDataSource(); + void checkDrmStatus(const sp<DataSource>& dataSource); + int64_t getLastReadPosition(); + void setDrmPlaybackStatusIfNeeded(int playbackStatus, int64_t position); + + status_t prefillCacheIfNecessary(); + + void notifyPreparedAndCleanup(status_t err); + + void onGetFormatMeta(sp<AMessage> msg) const; + sp<MetaData> doGetFormatMeta(bool audio) const; + + void onGetSelectedTrack(sp<AMessage> msg) const; + ssize_t doGetSelectedTrack(media_track_type type) const; + + void onSelectTrack(sp<AMessage> msg); + status_t doSelectTrack(size_t trackIndex, bool select); + + void onSeek(sp<AMessage> msg); + status_t doSeek(int64_t seekTimeUs); + + void onPrepareAsync(); + + 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, + media_track_type trackType, + int64_t *actualTimeUs = NULL); + + void postReadBuffer(media_track_type trackType); + void onReadBuffer(sp<AMessage> msg); void readBuffer( - bool audio, - int64_t seekTimeUs = -1ll, int64_t *actualTimeUs = NULL); + media_track_type trackType, + int64_t seekTimeUs = -1ll, int64_t *actualTimeUs = NULL, bool formatChange = false); + + void schedulePollBuffering(); + void cancelPollBuffering(); + void onPollBuffering(); + void notifyBufferingUpdate(int percentage); DISALLOW_EVIL_CONSTRUCTORS(GenericSource); }; diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp index 510dcc9..a003c81 100644 --- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp +++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp @@ -24,6 +24,7 @@ #include "LiveDataSource.h" #include "LiveSession.h" +#include <media/IMediaHTTPService.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AMessage.h> @@ -34,13 +35,12 @@ namespace android { NuPlayer::HTTPLiveSource::HTTPLiveSource( const sp<AMessage> ¬ify, + const sp<IMediaHTTPService> &httpService, const char *url, - const KeyedVector<String8, String8> *headers, - bool uidValid, uid_t uid) + const KeyedVector<String8, String8> *headers) : Source(notify), + mHTTPService(httpService), mURL(url), - mUIDValid(uidValid), - mUID(uid), mFlags(0), mFinalResult(OK), mOffset(0), @@ -62,25 +62,31 @@ NuPlayer::HTTPLiveSource::HTTPLiveSource( NuPlayer::HTTPLiveSource::~HTTPLiveSource() { if (mLiveSession != NULL) { mLiveSession->disconnect(); - mLiveSession.clear(); + mLiveLooper->unregisterHandler(mLiveSession->id()); + mLiveLooper->unregisterHandler(id()); mLiveLooper->stop(); + + mLiveSession.clear(); mLiveLooper.clear(); } } void NuPlayer::HTTPLiveSource::prepareAsync() { - mLiveLooper = new ALooper; - mLiveLooper->setName("http live"); - mLiveLooper->start(); + if (mLiveLooper == NULL) { + mLiveLooper = new ALooper; + mLiveLooper->setName("http live"); + mLiveLooper->start(); + + mLiveLooper->registerHandler(this); + } sp<AMessage> notify = new AMessage(kWhatSessionNotify, id()); mLiveSession = new LiveSession( notify, (mFlags & kFlagIncognito) ? LiveSession::kFlagIncognito : 0, - mUIDValid, - mUID); + mHTTPService); mLiveLooper->registerHandler(mLiveSession); @@ -121,8 +127,12 @@ status_t NuPlayer::HTTPLiveSource::getDuration(int64_t *durationUs) { return mLiveSession->getDuration(durationUs); } -status_t NuPlayer::HTTPLiveSource::getTrackInfo(Parcel *reply) const { - return mLiveSession->getTrackInfo(reply); +size_t NuPlayer::HTTPLiveSource::getTrackCount() const { + return mLiveSession->getTrackCount(); +} + +sp<AMessage> NuPlayer::HTTPLiveSource::getTrackInfo(size_t trackIndex) const { + return mLiveSession->getTrackInfo(trackIndex); } status_t NuPlayer::HTTPLiveSource::selectTrack(size_t trackIndex, bool select) { @@ -207,9 +217,9 @@ void NuPlayer::HTTPLiveSource::onSessionNotify(const sp<AMessage> &msg) { int32_t height; if (format != NULL && format->findInt32("width", &width) && format->findInt32("height", &height)) { - notifyVideoSizeChanged(width, height); + notifyVideoSizeChanged(format); } else { - notifyVideoSizeChanged(0, 0); + notifyVideoSizeChanged(); } uint32_t flags = FLAG_CAN_PAUSE; diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h index bcc3f8b..6b5f6af 100644 --- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h +++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h @@ -28,10 +28,9 @@ struct LiveSession; struct NuPlayer::HTTPLiveSource : public NuPlayer::Source { HTTPLiveSource( const sp<AMessage> ¬ify, + const sp<IMediaHTTPService> &httpService, const char *url, - const KeyedVector<String8, String8> *headers, - bool uidValid = false, - uid_t uid = 0); + const KeyedVector<String8, String8> *headers); virtual void prepareAsync(); virtual void start(); @@ -41,7 +40,8 @@ struct NuPlayer::HTTPLiveSource : public NuPlayer::Source { virtual status_t feedMoreTSData(); virtual status_t getDuration(int64_t *durationUs); - virtual status_t getTrackInfo(Parcel *reply) const; + virtual size_t getTrackCount() const; + virtual sp<AMessage> getTrackInfo(size_t trackIndex) const; virtual status_t selectTrack(size_t trackIndex, bool select); virtual status_t seekTo(int64_t seekTimeUs); @@ -61,10 +61,9 @@ private: kWhatFetchSubtitleData, }; + sp<IMediaHTTPService> mHTTPService; AString mURL; KeyedVector<String8, String8> mExtraHeaders; - bool mUIDValid; - uid_t mUID; uint32_t mFlags; status_t mFinalResult; off64_t mOffset; diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp index 25d55a3..dad480d 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp @@ -22,24 +22,22 @@ #include "HTTPLiveSource.h" #include "NuPlayerDecoder.h" +#include "NuPlayerDecoderPassThrough.h" #include "NuPlayerDriver.h" #include "NuPlayerRenderer.h" #include "NuPlayerSource.h" #include "RTSPSource.h" #include "StreamingSource.h" #include "GenericSource.h" -#include "mp4/MP4Source.h" +#include "TextDescriptions.h" #include "ATSParser.h" -#include "SoftwareRenderer.h" - -#include <cutils/properties.h> // for property_get #include <media/stagefright/foundation/hexdump.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AMessage.h> -#include <media/stagefright/ACodec.h> +#include <media/stagefright/MediaBuffer.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MediaErrors.h> #include <media/stagefright/MetaData.h> @@ -52,6 +50,10 @@ namespace android { +// TODO optimize buffer size for power consumption +// The offload read buffer size is 32 KB but 24 KB uses less power. +const size_t NuPlayer::kAggregateBufferSizeBytes = 24 * 1024; + struct NuPlayer::Action : public RefBase { Action() {} @@ -147,13 +149,18 @@ private: NuPlayer::NuPlayer() : mUIDValid(false), mSourceFlags(0), + mCurrentPositionUs(0), mVideoIsAVC(false), - mNeedsSwRenderer(false), + mOffloadAudio(false), + mCurrentOffloadInfo(AUDIO_INFO_INITIALIZER), + mAudioDecoderGeneration(0), + mVideoDecoderGeneration(0), mAudioEOS(false), mVideoEOS(false), mScanSourcesPending(false), mScanSourcesGeneration(0), mPollDurationGeneration(0), + mTimedTextGeneration(0), mTimeDiscontinuityPending(false), mFlushingAudio(NONE), mFlushingVideo(NONE), @@ -183,14 +190,7 @@ void NuPlayer::setDataSourceAsync(const sp<IStreamSource> &source) { sp<AMessage> notify = new AMessage(kWhatSourceNotify, id()); - char prop[PROPERTY_VALUE_MAX]; - if (property_get("media.stagefright.use-mp4source", prop, NULL) - && (!strcmp(prop, "1") || !strcasecmp(prop, "true"))) { - msg->setObject("source", new MP4Source(notify, source)); - } else { - msg->setObject("source", new StreamingSource(notify, source)); - } - + msg->setObject("source", new StreamingSource(notify, source)); msg->post(); } @@ -212,7 +212,10 @@ static bool IsHTTPLiveURL(const char *url) { } void NuPlayer::setDataSourceAsync( - const char *url, const KeyedVector<String8, String8> *headers) { + const sp<IMediaHTTPService> &httpService, + const char *url, + const KeyedVector<String8, String8> *headers) { + sp<AMessage> msg = new AMessage(kWhatSetDataSource, id()); size_t len = strlen(url); @@ -220,18 +223,31 @@ void NuPlayer::setDataSourceAsync( sp<Source> source; if (IsHTTPLiveURL(url)) { - source = new HTTPLiveSource(notify, url, headers, mUIDValid, mUID); + source = new HTTPLiveSource(notify, httpService, url, headers); } else if (!strncasecmp(url, "rtsp://", 7)) { - source = new RTSPSource(notify, url, headers, mUIDValid, mUID); + source = new RTSPSource( + notify, httpService, url, headers, mUIDValid, mUID); } else if ((!strncasecmp(url, "http://", 7) || !strncasecmp(url, "https://", 8)) && ((len >= 4 && !strcasecmp(".sdp", &url[len - 4])) || strstr(url, ".sdp?"))) { - source = new RTSPSource(notify, url, headers, mUIDValid, mUID, true); + source = new RTSPSource( + notify, httpService, url, headers, mUIDValid, mUID, true); } else { - source = new GenericSource(notify, url, headers, mUIDValid, mUID); + 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 +257,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(); } @@ -260,7 +285,7 @@ void NuPlayer::setVideoSurfaceTextureAsync( msg->setObject( "native-window", new NativeWindowWrapper( - new Surface(bufferProducer))); + new Surface(bufferProducer, true /* controlledByApp */))); } msg->post(); @@ -285,6 +310,16 @@ void NuPlayer::resume() { } void NuPlayer::resetAsync() { + if (mSource != NULL) { + // During a reset, the data source might be unresponsive already, we need to + // disconnect explicitly so that reads exit promptly. + // We can't queue the disconnect request to the looper, as it might be + // queued behind a stuck read and never gets processed. + // Doing a disconnect outside the looper to allows the pending reads to exit + // (either successfully or with error). + mSource->disconnect(); + } + (new AMessage(kWhatReset, id()))->post(); } @@ -314,6 +349,34 @@ bool NuPlayer::IsFlushingState(FlushStatus state, bool *needShutdown) { } } +void NuPlayer::writeTrackInfo( + Parcel* reply, const sp<AMessage> format) const { + int32_t trackType; + CHECK(format->findInt32("type", &trackType)); + + AString lang; + CHECK(format->findString("language", &lang)); + + reply->writeInt32(2); // write something non-zero + reply->writeInt32(trackType); + reply->writeString16(String16(lang.c_str())); + + if (trackType == MEDIA_TRACK_TYPE_SUBTITLE) { + AString mime; + CHECK(format->findString("mime", &mime)); + + int32_t isAuto, isDefault, isForced; + CHECK(format->findInt32("auto", &isAuto)); + CHECK(format->findInt32("default", &isDefault)); + CHECK(format->findInt32("forced", &isForced)); + + reply->writeString16(String16(mime.c_str())); + reply->writeInt32(isAuto); + reply->writeInt32(isDefault); + reply->writeInt32(isForced); + } +} + void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { switch (msg->what()) { case kWhatSetDataSource: @@ -322,17 +385,19 @@ 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()); + } else { + err = UNKNOWN_ERROR; + } CHECK(mDriver != NULL); sp<NuPlayerDriver> driver = mDriver.promote(); if (driver != NULL) { - driver->notifySetDataSourceCompleted(OK); + driver->notifySetDataSourceCompleted(err); } break; } @@ -348,16 +413,58 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { uint32_t replyID; CHECK(msg->senderAwaitsResponse(&replyID)); + Parcel* reply; + CHECK(msg->findPointer("reply", (void**)&reply)); + + size_t inbandTracks = 0; + if (mSource != NULL) { + inbandTracks = mSource->getTrackCount(); + } + + size_t ccTracks = 0; + if (mCCDecoder != NULL) { + ccTracks = mCCDecoder->getTrackCount(); + } + + // total track count + reply->writeInt32(inbandTracks + ccTracks); + + // write inband tracks + for (size_t i = 0; i < inbandTracks; ++i) { + writeTrackInfo(reply, mSource->getTrackInfo(i)); + } + + // write CC track + for (size_t i = 0; i < ccTracks; ++i) { + writeTrackInfo(reply, mCCDecoder->getTrackInfo(i)); + } + + sp<AMessage> response = new AMessage; + response->postReply(replyID); + break; + } + + case kWhatGetSelectedTrack: + { status_t err = INVALID_OPERATION; if (mSource != NULL) { + err = OK; + + int32_t type32; + CHECK(msg->findInt32("type", (int32_t*)&type32)); + media_track_type type = (media_track_type)type32; + ssize_t selectedTrack = mSource->getSelectedTrack(type); + Parcel* reply; CHECK(msg->findPointer("reply", (void**)&reply)); - err = mSource->getTrackInfo(reply); + reply->writeInt32(selectedTrack); } sp<AMessage> response = new AMessage; response->setInt32("err", err); + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); response->postReply(replyID); break; } @@ -367,13 +474,40 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { uint32_t replyID; CHECK(msg->senderAwaitsResponse(&replyID)); + size_t trackIndex; + int32_t select; + CHECK(msg->findSize("trackIndex", &trackIndex)); + CHECK(msg->findInt32("select", &select)); + status_t err = INVALID_OPERATION; + + size_t inbandTracks = 0; if (mSource != NULL) { - size_t trackIndex; - int32_t select; - CHECK(msg->findSize("trackIndex", &trackIndex)); - CHECK(msg->findInt32("select", &select)); + inbandTracks = mSource->getTrackCount(); + } + size_t ccTracks = 0; + if (mCCDecoder != NULL) { + ccTracks = mCCDecoder->getTrackCount(); + } + + if (trackIndex < inbandTracks) { err = mSource->selectTrack(trackIndex, select); + + if (!select && err == OK) { + int32_t type; + sp<AMessage> info = mSource->getTrackInfo(trackIndex); + if (info != NULL + && info->findInt32("type", &type) + && type == MEDIA_TRACK_TYPE_TIMEDTEXT) { + ++mTimedTextGeneration; + } + } + } else { + trackIndex -= inbandTracks; + + if (trackIndex < ccTracks) { + err = mCCDecoder->selectTrack(trackIndex, select); + } } sp<AMessage> response = new AMessage; @@ -421,6 +555,14 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { static_cast<NativeWindowWrapper *>(obj.get()))); if (obj != NULL) { + if (mStarted && mVideoDecoder != NULL) { + // Issue a seek to refresh the video screen only if started otherwise + // the extractor may not yet be started and will assert. + // If the video decoder is not set (perhaps audio only in this case) + // do not perform a seek as it is not needed. + mDeferredActions.push_back(new SeekAction(mCurrentPositionUs)); + } + // If there is a new surface texture, instantiate decoders // again if possible. mDeferredActions.push_back( @@ -447,7 +589,7 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { ALOGV("kWhatStart"); mVideoIsAVC = false; - mNeedsSwRenderer = false; + mOffloadAudio = false; mAudioEOS = false; mVideoEOS = false; mSkipRenderingAudioUntilMediaTimeUs = -1; @@ -457,6 +599,17 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { mNumFramesDropped = 0; mStarted = true; + /* instantiate decoders now for secure playback */ + if (mSourceFlags & Source::FLAG_SECURE) { + if (mNativeWindow != NULL) { + instantiateDecoder(false, &mVideoDecoder); + } + + if (mAudioSink != NULL) { + instantiateDecoder(true, &mAudioDecoder); + } + } + mSource->start(); uint32_t flags = 0; @@ -465,12 +618,37 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { flags |= Renderer::FLAG_REAL_TIME; } + sp<MetaData> audioMeta = mSource->getFormatMeta(true /* audio */); + audio_stream_type_t streamType = AUDIO_STREAM_MUSIC; + if (mAudioSink != NULL) { + streamType = mAudioSink->getAudioStreamType(); + } + + sp<AMessage> videoFormat = mSource->getFormat(false /* audio */); + + mOffloadAudio = + canOffloadStream(audioMeta, (videoFormat != NULL), + true /* is_streaming */, streamType); + if (mOffloadAudio) { + flags |= Renderer::FLAG_OFFLOAD_AUDIO; + } + mRenderer = new Renderer( mAudioSink, new AMessage(kWhatRendererNotify, id()), flags); - looper()->registerHandler(mRenderer); + mRendererLooper = new ALooper; + mRendererLooper->setName("NuPlayerRenderer"); + mRendererLooper->start(false, false, ANDROID_PRIORITY_AUDIO); + mRendererLooper->registerHandler(mRenderer); + + sp<MetaData> meta = getFileMeta(); + int32_t rate; + if (meta != NULL + && meta->findInt32(kKeyFrameRate, &rate) && rate > 0) { + mRenderer->setVideoFrameRate(rate); + } postScanSources(); break; @@ -493,11 +671,18 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { bool mHadAnySourcesBefore = (mAudioDecoder != NULL) || (mVideoDecoder != NULL); + // initialize video before audio because successful initialization of + // video may change deep buffer mode of audio. if (mNativeWindow != NULL) { instantiateDecoder(false, &mVideoDecoder); } if (mAudioSink != NULL) { + if (mOffloadAudio) { + // open audio sink early under offload mode. + sp<AMessage> format = mSource->getFormat(true /*audio*/); + openAudioSink(format, true /*offloadOnly*/); + } instantiateDecoder(true, &mAudioDecoder); } @@ -538,24 +723,40 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { { bool audio = msg->what() == kWhatAudioNotify; - sp<AMessage> codecRequest; - CHECK(msg->findMessage("codec-request", &codecRequest)); + int32_t currentDecoderGeneration = + (audio? mAudioDecoderGeneration : mVideoDecoderGeneration); + int32_t requesterGeneration = currentDecoderGeneration - 1; + CHECK(msg->findInt32("generation", &requesterGeneration)); + + if (requesterGeneration != currentDecoderGeneration) { + ALOGV("got message from old %s decoder, generation(%d:%d)", + audio ? "audio" : "video", requesterGeneration, + currentDecoderGeneration); + sp<AMessage> reply; + if (!(msg->findMessage("reply", &reply))) { + return; + } + + reply->setInt32("err", INFO_DISCONTINUITY); + reply->post(); + return; + } int32_t what; - CHECK(codecRequest->findInt32("what", &what)); + CHECK(msg->findInt32("what", &what)); - if (what == ACodec::kWhatFillThisBuffer) { + if (what == Decoder::kWhatFillThisBuffer) { status_t err = feedDecoderInputData( - audio, codecRequest); + audio, msg); if (err == -EWOULDBLOCK) { if (mSource->feedMoreTSData() == OK) { - msg->post(10000ll); + msg->post(10 * 1000ll); } } - } else if (what == ACodec::kWhatEOS) { + } else if (what == Decoder::kWhatEOS) { int32_t err; - CHECK(codecRequest->findInt32("err", &err)); + CHECK(msg->findInt32("err", &err)); if (err == ERROR_END_OF_STREAM) { ALOGV("got %s decoder EOS", audio ? "audio" : "video"); @@ -566,7 +767,7 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { } mRenderer->queueEOS(audio, err); - } else if (what == ACodec::kWhatFlushCompleted) { + } else if (what == Decoder::kWhatFlushCompleted) { bool needShutdown; if (audio) { @@ -585,7 +786,7 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { ALOGV("initiating %s decoder shutdown", audio ? "audio" : "video"); - (audio ? mAudioDecoder : mVideoDecoder)->initiateShutdown(); + getDecoder(audio)->initiateShutdown(); if (audio) { mFlushingAudio = SHUTTING_DOWN_DECODER; @@ -595,111 +796,20 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { } finishFlushIfPossible(); - } else if (what == ACodec::kWhatOutputFormatChanged) { - if (audio) { - int32_t numChannels; - CHECK(codecRequest->findInt32( - "channel-count", &numChannels)); - - int32_t sampleRate; - CHECK(codecRequest->findInt32("sample-rate", &sampleRate)); - - ALOGV("Audio output format changed to %d Hz, %d channels", - sampleRate, numChannels); - - mAudioSink->close(); - - audio_output_flags_t flags; - int64_t durationUs; - // FIXME: we should handle the case where the video decoder - // is created after we receive the format change indication. - // Current code will just make that we select deep buffer - // with video which should not be a problem as it should - // not prevent from keeping A/V sync. - if (mVideoDecoder == NULL && - mSource->getDuration(&durationUs) == OK && - durationUs - > AUDIO_SINK_MIN_DEEP_BUFFER_DURATION_US) { - flags = AUDIO_OUTPUT_FLAG_DEEP_BUFFER; - } else { - flags = AUDIO_OUTPUT_FLAG_NONE; - } - - int32_t channelMask; - if (!codecRequest->findInt32("channel-mask", &channelMask)) { - channelMask = CHANNEL_MASK_USE_CHANNEL_ORDER; - } + } else if (what == Decoder::kWhatOutputFormatChanged) { + sp<AMessage> format; + CHECK(msg->findMessage("format", &format)); - CHECK_EQ(mAudioSink->open( - sampleRate, - numChannels, - (audio_channel_mask_t)channelMask, - AUDIO_FORMAT_PCM_16_BIT, - 8 /* bufferCount */, - NULL, - NULL, - flags), - (status_t)OK); - mAudioSink->start(); - - mRenderer->signalAudioSinkChanged(); + if (audio) { + openAudioSink(format, false /*offloadOnly*/); } else { // video + sp<AMessage> inputFormat = + mSource->getFormat(false /* audio */); - int32_t width, height; - CHECK(codecRequest->findInt32("width", &width)); - CHECK(codecRequest->findInt32("height", &height)); - - int32_t cropLeft, cropTop, cropRight, cropBottom; - CHECK(codecRequest->findRect( - "crop", - &cropLeft, &cropTop, &cropRight, &cropBottom)); - - int32_t displayWidth = cropRight - cropLeft + 1; - int32_t displayHeight = cropBottom - cropTop + 1; - - ALOGV("Video output format changed to %d x %d " - "(crop: %d x %d @ (%d, %d))", - width, height, - displayWidth, - displayHeight, - cropLeft, cropTop); - - sp<AMessage> videoInputFormat = - mSource->getFormat(false /* audio */); - - // Take into account sample aspect ratio if necessary: - int32_t sarWidth, sarHeight; - if (videoInputFormat->findInt32("sar-width", &sarWidth) - && videoInputFormat->findInt32( - "sar-height", &sarHeight)) { - ALOGV("Sample aspect ratio %d : %d", - sarWidth, sarHeight); - - displayWidth = (displayWidth * sarWidth) / sarHeight; - - ALOGV("display dimensions %d x %d", - displayWidth, displayHeight); - } - - notifyListener( - MEDIA_SET_VIDEO_SIZE, displayWidth, displayHeight); - - if (mNeedsSwRenderer && mNativeWindow != NULL) { - int32_t colorFormat; - CHECK(codecRequest->findInt32("color-format", &colorFormat)); - - sp<MetaData> meta = new MetaData; - meta->setInt32(kKeyWidth, width); - meta->setInt32(kKeyHeight, height); - meta->setRect(kKeyCropRect, cropLeft, cropTop, cropRight, cropBottom); - meta->setInt32(kKeyColorFormat, colorFormat); - - mRenderer->setSoftRenderer( - new SoftwareRenderer(mNativeWindow->getNativeWindow(), meta)); - } + updateVideoSize(inputFormat, format); } - } else if (what == ACodec::kWhatShutdownCompleted) { + } else if (what == Decoder::kWhatShutdownCompleted) { ALOGV("%s shutdown completed", audio ? "audio" : "video"); if (audio) { mAudioDecoder.clear(); @@ -714,22 +824,27 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { } finishFlushIfPossible(); - } else if (what == ACodec::kWhatError) { + } else if (what == Decoder::kWhatError) { ALOGE("Received error from %s decoder, aborting playback.", audio ? "audio" : "video"); - mRenderer->queueEOS(audio, UNKNOWN_ERROR); - } else if (what == ACodec::kWhatDrainThisBuffer) { - renderBuffer(audio, codecRequest); - } else if (what == ACodec::kWhatComponentAllocated) { - if (!audio) { - AString name; - CHECK(codecRequest->findString("componentName", &name)); - mNeedsSwRenderer = name.startsWith("OMX.google."); + status_t err; + if (!msg->findInt32("err", &err)) { + err = UNKNOWN_ERROR; } - } else if (what != ACodec::kWhatComponentConfigured - && what != ACodec::kWhatBuffersAllocated) { - ALOGV("Unhandled codec notification %d '%c%c%c%c'.", + mRenderer->queueEOS(audio, err); + if (audio && mFlushingAudio != NONE) { + mAudioDecoder.clear(); + mFlushingAudio = SHUT_DOWN; + } else if (!audio && mFlushingVideo != NONE){ + mVideoDecoder.clear(); + mFlushingVideo = SHUT_DOWN; + } + finishFlushIfPossible(); + } else if (what == Decoder::kWhatDrainThisBuffer) { + renderBuffer(audio, msg); + } else { + ALOGV("Unhandled decoder notification %d '%c%c%c%c'.", what, what >> 24, (what >> 16) & 0xff, @@ -775,6 +890,7 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { } else if (what == Renderer::kWhatPosition) { int64_t positionUs; CHECK(msg->findInt64("positionUs", &positionUs)); + mCurrentPositionUs = positionUs; CHECK(msg->findInt64("videoLateByUs", &mVideoLateByUs)); @@ -797,6 +913,21 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { } else if (what == Renderer::kWhatMediaRenderingStart) { ALOGV("media rendering started"); notifyListener(MEDIA_STARTED, 0, 0); + } else if (what == Renderer::kWhatAudioOffloadTearDown) { + ALOGV("Tear down audio offload, fall back to s/w path"); + int64_t positionUs; + CHECK(msg->findInt64("positionUs", &positionUs)); + closeAudioSink(); + mAudioDecoder.clear(); + mRenderer->flush(true /* audio */); + if (mVideoDecoder != NULL) { + mRenderer->flush(false /* audio */); + } + mRenderer->signalDisableOffloadAudio(); + mOffloadAudio = false; + + performSeek(positionUs); + instantiateDecoder(true /* audio */, &mAudioDecoder); } break; } @@ -859,6 +990,12 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { break; } + case kWhatClosedCaptionNotify: + { + onClosedCaptionNotify(msg); + break; + } + default: TRESPASS(); break; @@ -866,26 +1003,31 @@ void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { } void NuPlayer::finishFlushIfPossible() { - if (mFlushingAudio != FLUSHED && mFlushingAudio != SHUT_DOWN) { + if (mFlushingAudio != NONE && mFlushingAudio != FLUSHED + && mFlushingAudio != SHUT_DOWN) { return; } - if (mFlushingVideo != FLUSHED && mFlushingVideo != SHUT_DOWN) { + if (mFlushingVideo != NONE && mFlushingVideo != FLUSHED + && mFlushingVideo != SHUT_DOWN) { return; } ALOGV("both audio and video are flushed now."); + mPendingAudioAccessUnit.clear(); + mAggregateBuffer.clear(); + if (mTimeDiscontinuityPending) { mRenderer->signalTimeDiscontinuity(); mTimeDiscontinuityPending = false; } - if (mAudioDecoder != NULL) { + if (mAudioDecoder != NULL && mFlushingAudio == FLUSHED) { mAudioDecoder->signalResume(); } - if (mVideoDecoder != NULL) { + if (mVideoDecoder != NULL && mFlushingVideo == FLUSHED) { mVideoDecoder->signalResume(); } @@ -907,6 +1049,149 @@ void NuPlayer::postScanSources() { mScanSourcesPending = true; } +void NuPlayer::openAudioSink(const sp<AMessage> &format, bool offloadOnly) { + ALOGV("openAudioSink: offloadOnly(%d) mOffloadAudio(%d)", + offloadOnly, mOffloadAudio); + bool audioSinkChanged = false; + + int32_t numChannels; + CHECK(format->findInt32("channel-count", &numChannels)); + + int32_t channelMask; + if (!format->findInt32("channel-mask", &channelMask)) { + // signal to the AudioSink to derive the mask from count. + channelMask = CHANNEL_MASK_USE_CHANNEL_ORDER; + } + + int32_t sampleRate; + CHECK(format->findInt32("sample-rate", &sampleRate)); + + uint32_t flags; + int64_t durationUs; + // FIXME: we should handle the case where the video decoder + // is created after we receive the format change indication. + // Current code will just make that we select deep buffer + // with video which should not be a problem as it should + // not prevent from keeping A/V sync. + if (mVideoDecoder == NULL && + mSource->getDuration(&durationUs) == OK && + durationUs + > AUDIO_SINK_MIN_DEEP_BUFFER_DURATION_US) { + flags = AUDIO_OUTPUT_FLAG_DEEP_BUFFER; + } else { + flags = AUDIO_OUTPUT_FLAG_NONE; + } + + if (mOffloadAudio) { + audio_format_t audioFormat = AUDIO_FORMAT_PCM_16_BIT; + AString mime; + CHECK(format->findString("mime", &mime)); + status_t err = mapMimeToAudioFormat(audioFormat, mime.c_str()); + + if (err != OK) { + ALOGE("Couldn't map mime \"%s\" to a valid " + "audio_format", mime.c_str()); + mOffloadAudio = false; + } else { + ALOGV("Mime \"%s\" mapped to audio_format 0x%x", + mime.c_str(), audioFormat); + + int avgBitRate = -1; + format->findInt32("bit-rate", &avgBitRate); + + int32_t aacProfile = -1; + if (audioFormat == AUDIO_FORMAT_AAC + && format->findInt32("aac-profile", &aacProfile)) { + // Redefine AAC format as per aac profile + mapAACProfileToAudioFormat( + audioFormat, + aacProfile); + } + + audio_offload_info_t offloadInfo = AUDIO_INFO_INITIALIZER; + offloadInfo.duration_us = -1; + format->findInt64( + "durationUs", &offloadInfo.duration_us); + offloadInfo.sample_rate = sampleRate; + offloadInfo.channel_mask = channelMask; + offloadInfo.format = audioFormat; + offloadInfo.stream_type = AUDIO_STREAM_MUSIC; + offloadInfo.bit_rate = avgBitRate; + offloadInfo.has_video = (mVideoDecoder != NULL); + offloadInfo.is_streaming = true; + + if (memcmp(&mCurrentOffloadInfo, &offloadInfo, sizeof(offloadInfo)) == 0) { + ALOGV("openAudioSink: no change in offload mode"); + return; // no change from previous configuration, everything ok. + } + ALOGV("openAudioSink: try to open AudioSink in offload mode"); + flags |= AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD; + flags &= ~AUDIO_OUTPUT_FLAG_DEEP_BUFFER; + audioSinkChanged = true; + mAudioSink->close(); + err = mAudioSink->open( + sampleRate, + numChannels, + (audio_channel_mask_t)channelMask, + audioFormat, + 8 /* bufferCount */, + &NuPlayer::Renderer::AudioSinkCallback, + mRenderer.get(), + (audio_output_flags_t)flags, + &offloadInfo); + + if (err == OK) { + // If the playback is offloaded to h/w, we pass + // the HAL some metadata information. + // We don't want to do this for PCM because it + // will be going through the AudioFlinger mixer + // before reaching the hardware. + sp<MetaData> audioMeta = + mSource->getFormatMeta(true /* audio */); + sendMetaDataToHal(mAudioSink, audioMeta); + mCurrentOffloadInfo = offloadInfo; + err = mAudioSink->start(); + ALOGV_IF(err == OK, "openAudioSink: offload succeeded"); + } + if (err != OK) { + // Clean up, fall back to non offload mode. + mAudioSink->close(); + mRenderer->signalDisableOffloadAudio(); + mOffloadAudio = false; + mCurrentOffloadInfo = AUDIO_INFO_INITIALIZER; + ALOGV("openAudioSink: offload failed"); + } + } + } + if (!offloadOnly && !mOffloadAudio) { + flags &= ~AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD; + ALOGV("openAudioSink: open AudioSink in NON-offload mode"); + + audioSinkChanged = true; + mAudioSink->close(); + mCurrentOffloadInfo = AUDIO_INFO_INITIALIZER; + CHECK_EQ(mAudioSink->open( + sampleRate, + numChannels, + (audio_channel_mask_t)channelMask, + AUDIO_FORMAT_PCM_16_BIT, + 8 /* bufferCount */, + NULL, + NULL, + (audio_output_flags_t)flags), + (status_t)OK); + mAudioSink->start(); + } + if (audioSinkChanged) { + mRenderer->signalAudioSinkChanged(); + } +} + +void NuPlayer::closeAudioSink() { + mAudioSink->close(); + mCurrentOffloadInfo = AUDIO_INFO_INITIALIZER; +} + status_t NuPlayer::instantiateDecoder(bool audio, sp<Decoder> *decoder) { if (*decoder != NULL) { return OK; @@ -922,18 +1207,57 @@ status_t NuPlayer::instantiateDecoder(bool audio, sp<Decoder> *decoder) { AString mime; CHECK(format->findString("mime", &mime)); mVideoIsAVC = !strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime.c_str()); - } - sp<AMessage> notify = - new AMessage(audio ? kWhatAudioNotify : kWhatVideoNotify, - id()); + sp<AMessage> ccNotify = new AMessage(kWhatClosedCaptionNotify, id()); + mCCDecoder = new CCDecoder(ccNotify); + + if (mSourceFlags & Source::FLAG_SECURE) { + format->setInt32("secure", true); + } + } - *decoder = audio ? new Decoder(notify) : - new Decoder(notify, mNativeWindow); - looper()->registerHandler(*decoder); + if (audio) { + sp<AMessage> notify = new AMessage(kWhatAudioNotify, id()); + ++mAudioDecoderGeneration; + notify->setInt32("generation", mAudioDecoderGeneration); + + if (mOffloadAudio) { + *decoder = new DecoderPassThrough(notify); + } else { + *decoder = new Decoder(notify); + } + } else { + sp<AMessage> notify = new AMessage(kWhatVideoNotify, id()); + ++mVideoDecoderGeneration; + notify->setInt32("generation", mVideoDecoderGeneration); + *decoder = new Decoder(notify, mNativeWindow); + } + (*decoder)->init(); (*decoder)->configure(format); + // allocate buffers to decrypt widevine source buffers + if (!audio && (mSourceFlags & Source::FLAG_SECURE)) { + Vector<sp<ABuffer> > inputBufs; + CHECK_EQ((*decoder)->getInputBuffers(&inputBufs), (status_t)OK); + + Vector<MediaBuffer *> mediaBufs; + for (size_t i = 0; i < inputBufs.size(); i++) { + const sp<ABuffer> &buffer = inputBufs[i]; + MediaBuffer *mbuf = new MediaBuffer(buffer->data(), buffer->size()); + mediaBufs.push(mbuf); + } + + status_t err = mSource->setBuffers(audio, mediaBufs); + if (err != OK) { + for (size_t i = 0; i < mediaBufs.size(); ++i) { + mediaBufs[i]->release(); + } + mediaBufs.clear(); + ALOGE("Secure source didn't support secure mediaBufs."); + return err; + } + } return OK; } @@ -941,8 +1265,9 @@ status_t NuPlayer::feedDecoderInputData(bool audio, const sp<AMessage> &msg) { sp<AMessage> reply; CHECK(msg->findMessage("reply", &reply)); - if ((audio && IsFlushingState(mFlushingAudio)) - || (!audio && IsFlushingState(mFlushingVideo))) { + if ((audio && mFlushingAudio != NONE) + || (!audio && mFlushingVideo != NONE) + || mSource == NULL) { reply->setInt32("err", INFO_DISCONTINUITY); reply->post(); return OK; @@ -950,14 +1275,37 @@ status_t NuPlayer::feedDecoderInputData(bool audio, const sp<AMessage> &msg) { sp<ABuffer> accessUnit; + // Aggregate smaller buffers into a larger buffer. + // The goal is to reduce power consumption. + // Note this will not work if the decoder requires one frame per buffer. + bool doBufferAggregation = (audio && mOffloadAudio); + bool needMoreData = false; + bool dropAccessUnit; do { - status_t err = mSource->dequeueAccessUnit(audio, &accessUnit); + status_t err; + // Did we save an accessUnit earlier because of a discontinuity? + if (audio && (mPendingAudioAccessUnit != NULL)) { + accessUnit = mPendingAudioAccessUnit; + mPendingAudioAccessUnit.clear(); + err = mPendingAudioErr; + ALOGV("feedDecoderInputData() use mPendingAudioAccessUnit"); + } else { + err = mSource->dequeueAccessUnit(audio, &accessUnit); + } if (err == -EWOULDBLOCK) { return err; } else if (err != OK) { if (err == INFO_DISCONTINUITY) { + if (doBufferAggregation && (mAggregateBuffer != NULL)) { + // We already have some data so save this for later. + mPendingAudioErr = err; + mPendingAudioAccessUnit = accessUnit; + accessUnit.clear(); + ALOGD("feedDecoderInputData() save discontinuity for later"); + break; + } int32_t type; CHECK(accessUnit->meta()->findInt32("discontinuity", &type)); @@ -1002,34 +1350,44 @@ status_t NuPlayer::feedDecoderInputData(bool audio, const sp<AMessage> &msg) { mTimeDiscontinuityPending = mTimeDiscontinuityPending || timeChange; - if (formatChange || timeChange) { - if (mFlushingAudio == NONE && mFlushingVideo == NONE) { - // And we'll resume scanning sources once we're done - // flushing. - mDeferredActions.push_front( - new SimpleAction( - &NuPlayer::performScanSources)); - } + bool seamlessFormatChange = false; + sp<AMessage> newFormat = mSource->getFormat(audio); + if (formatChange) { + seamlessFormatChange = + getDecoder(audio)->supportsSeamlessFormatChange(newFormat); + // treat seamless format change separately + formatChange = !seamlessFormatChange; + } + bool shutdownOrFlush = formatChange || timeChange; + + // We want to queue up scan-sources only once per discontinuity. + // We control this by doing it only if neither audio nor video are + // flushing or shutting down. (After handling 1st discontinuity, one + // of the flushing states will not be NONE.) + // No need to scan sources if this discontinuity does not result + // in a flush or shutdown, as the flushing state will stay NONE. + if (mFlushingAudio == NONE && mFlushingVideo == NONE && + shutdownOrFlush) { + // And we'll resume scanning sources once we're done + // flushing. + mDeferredActions.push_front( + new SimpleAction( + &NuPlayer::performScanSources)); + } - sp<AMessage> newFormat = mSource->getFormat(audio); - sp<Decoder> &decoder = audio ? mAudioDecoder : mVideoDecoder; - if (formatChange && !decoder->supportsSeamlessFormatChange(newFormat)) { - flushDecoder(audio, /* needShutdown = */ true); - } else { - flushDecoder(audio, /* needShutdown = */ false); - err = OK; - } + if (formatChange /* not seamless */) { + // must change decoder + flushDecoder(audio, /* needShutdown = */ true); + } else if (timeChange) { + // need to flush + flushDecoder(audio, /* needShutdown = */ false, newFormat); + err = OK; + } else if (seamlessFormatChange) { + // reuse existing decoder and don't flush + updateDecoderFormatWithoutFlush(audio, newFormat); + err = OK; } else { // This stream is unaffected by the discontinuity - - if (audio) { - mFlushingAudio = FLUSHED; - } else { - mFlushingVideo = FLUSHED; - } - - finishFlushIfPossible(); - return -EWOULDBLOCK; } } @@ -1045,13 +1403,58 @@ status_t NuPlayer::feedDecoderInputData(bool audio, const sp<AMessage> &msg) { dropAccessUnit = false; if (!audio + && !(mSourceFlags & Source::FLAG_SECURE) && mVideoLateByUs > 100000ll && mVideoIsAVC && !IsAVCReferenceFrame(accessUnit)) { dropAccessUnit = true; ++mNumFramesDropped; } - } while (dropAccessUnit); + + size_t smallSize = accessUnit->size(); + needMoreData = false; + if (doBufferAggregation && (mAggregateBuffer == NULL) + // Don't bother if only room for a few small buffers. + && (smallSize < (kAggregateBufferSizeBytes / 3))) { + // Create a larger buffer for combining smaller buffers from the extractor. + mAggregateBuffer = new ABuffer(kAggregateBufferSizeBytes); + mAggregateBuffer->setRange(0, 0); // start empty + } + + if (doBufferAggregation && (mAggregateBuffer != NULL)) { + int64_t timeUs; + int64_t dummy; + bool smallTimestampValid = accessUnit->meta()->findInt64("timeUs", &timeUs); + bool bigTimestampValid = mAggregateBuffer->meta()->findInt64("timeUs", &dummy); + // Will the smaller buffer fit? + size_t bigSize = mAggregateBuffer->size(); + size_t roomLeft = mAggregateBuffer->capacity() - bigSize; + // Should we save this small buffer for the next big buffer? + // If the first small buffer did not have a timestamp then save + // any buffer that does have a timestamp until the next big buffer. + if ((smallSize > roomLeft) + || (!bigTimestampValid && (bigSize > 0) && smallTimestampValid)) { + mPendingAudioErr = err; + mPendingAudioAccessUnit = accessUnit; + accessUnit.clear(); + } else { + // Grab time from first small buffer if available. + if ((bigSize == 0) && smallTimestampValid) { + mAggregateBuffer->meta()->setInt64("timeUs", timeUs); + } + // Append small buffer to the bigger buffer. + memcpy(mAggregateBuffer->base() + bigSize, accessUnit->data(), smallSize); + bigSize += smallSize; + mAggregateBuffer->setRange(0, bigSize); + + // Keep looping until we run out of room in the mAggregateBuffer. + needMoreData = true; + + ALOGV("feedDecoderInputData() smallSize = %zu, bigSize = %zu, capacity = %zu", + smallSize, bigSize, mAggregateBuffer->capacity()); + } + } + } while (dropAccessUnit || needMoreData); // ALOGV("returned a valid buffer of %s data", audio ? "audio" : "video"); @@ -1063,7 +1466,19 @@ status_t NuPlayer::feedDecoderInputData(bool audio, const sp<AMessage> &msg) { mediaTimeUs / 1E6); #endif - reply->setBuffer("buffer", accessUnit); + if (!audio) { + mCCDecoder->decode(accessUnit); + } + + if (doBufferAggregation && (mAggregateBuffer != NULL)) { + ALOGV("feedDecoderInputData() reply with aggregated buffer, %zu", + mAggregateBuffer->size()); + reply->setBuffer("buffer", mAggregateBuffer); + mAggregateBuffer.clear(); + } else { + reply->setBuffer("buffer", accessUnit); + } + reply->post(); return OK; @@ -1075,7 +1490,8 @@ void NuPlayer::renderBuffer(bool audio, const sp<AMessage> &msg) { sp<AMessage> reply; CHECK(msg->findMessage("reply", &reply)); - if (IsFlushingState(audio ? mFlushingAudio : mFlushingVideo)) { + if ((audio && mFlushingAudio != NONE) + || (!audio && mFlushingVideo != NONE)) { // We're currently attempting to flush the decoder, in order // to complete this, the decoder wants all its buffers back, // so we don't want any output buffers it sent us (from before @@ -1091,14 +1507,15 @@ void NuPlayer::renderBuffer(bool audio, const sp<AMessage> &msg) { sp<ABuffer> buffer; CHECK(msg->findBuffer("buffer", &buffer)); + int64_t mediaTimeUs; + CHECK(buffer->meta()->findInt64("timeUs", &mediaTimeUs)); + int64_t &skipUntilMediaTimeUs = audio ? mSkipRenderingAudioUntilMediaTimeUs : mSkipRenderingVideoUntilMediaTimeUs; if (skipUntilMediaTimeUs >= 0) { - int64_t mediaTimeUs; - CHECK(buffer->meta()->findInt64("timeUs", &mediaTimeUs)); if (mediaTimeUs < skipUntilMediaTimeUs) { ALOGV("dropping %s buffer at time %lld as requested.", @@ -1112,9 +1529,79 @@ void NuPlayer::renderBuffer(bool audio, const sp<AMessage> &msg) { skipUntilMediaTimeUs = -1; } + if (!audio && mCCDecoder->isSelected()) { + mCCDecoder->display(mediaTimeUs); + } + mRenderer->queueBuffer(audio, buffer, reply); } +void NuPlayer::updateVideoSize( + const sp<AMessage> &inputFormat, + const sp<AMessage> &outputFormat) { + if (inputFormat == NULL) { + ALOGW("Unknown video size, reporting 0x0!"); + notifyListener(MEDIA_SET_VIDEO_SIZE, 0, 0); + return; + } + + int32_t displayWidth, displayHeight; + int32_t cropLeft, cropTop, cropRight, cropBottom; + + if (outputFormat != NULL) { + int32_t width, height; + CHECK(outputFormat->findInt32("width", &width)); + CHECK(outputFormat->findInt32("height", &height)); + + int32_t cropLeft, cropTop, cropRight, cropBottom; + CHECK(outputFormat->findRect( + "crop", + &cropLeft, &cropTop, &cropRight, &cropBottom)); + + displayWidth = cropRight - cropLeft + 1; + displayHeight = cropBottom - cropTop + 1; + + ALOGV("Video output format changed to %d x %d " + "(crop: %d x %d @ (%d, %d))", + width, height, + displayWidth, + displayHeight, + cropLeft, cropTop); + } else { + CHECK(inputFormat->findInt32("width", &displayWidth)); + CHECK(inputFormat->findInt32("height", &displayHeight)); + + ALOGV("Video input format %d x %d", displayWidth, displayHeight); + } + + // Take into account sample aspect ratio if necessary: + int32_t sarWidth, sarHeight; + if (inputFormat->findInt32("sar-width", &sarWidth) + && inputFormat->findInt32("sar-height", &sarHeight)) { + ALOGV("Sample aspect ratio %d : %d", sarWidth, sarHeight); + + displayWidth = (displayWidth * sarWidth) / sarHeight; + + ALOGV("display dimensions %d x %d", displayWidth, displayHeight); + } + + int32_t rotationDegrees; + if (!inputFormat->findInt32("rotation-degrees", &rotationDegrees)) { + rotationDegrees = 0; + } + + if (rotationDegrees == 90 || rotationDegrees == 270) { + int32_t tmp = displayWidth; + displayWidth = displayHeight; + displayHeight = tmp; + } + + notifyListener( + MEDIA_SET_VIDEO_SIZE, + displayWidth, + displayHeight); +} + void NuPlayer::notifyListener(int msg, int ext1, int ext2, const Parcel *in) { if (mDriver == NULL) { return; @@ -1129,63 +1616,70 @@ void NuPlayer::notifyListener(int msg, int ext1, int ext2, const Parcel *in) { driver->notifyListener(msg, ext1, ext2, in); } -void NuPlayer::flushDecoder(bool audio, bool needShutdown) { +void NuPlayer::flushDecoder( + bool audio, bool needShutdown, const sp<AMessage> &newFormat) { ALOGV("[%s] flushDecoder needShutdown=%d", audio ? "audio" : "video", needShutdown); - if ((audio && mAudioDecoder == NULL) || (!audio && mVideoDecoder == NULL)) { + const sp<Decoder> &decoder = getDecoder(audio); + if (decoder == NULL) { ALOGI("flushDecoder %s without decoder present", audio ? "audio" : "video"); + return; } // Make sure we don't continue to scan sources until we finish flushing. ++mScanSourcesGeneration; mScanSourcesPending = false; - (audio ? mAudioDecoder : mVideoDecoder)->signalFlush(); + decoder->signalFlush(newFormat); mRenderer->flush(audio); FlushStatus newStatus = needShutdown ? FLUSHING_DECODER_SHUTDOWN : FLUSHING_DECODER; if (audio) { - CHECK(mFlushingAudio == NONE - || mFlushingAudio == AWAITING_DISCONTINUITY); - + ALOGE_IF(mFlushingAudio != NONE, + "audio flushDecoder() is called in state %d", mFlushingAudio); mFlushingAudio = newStatus; - - if (mFlushingVideo == NONE) { - mFlushingVideo = (mVideoDecoder != NULL) - ? AWAITING_DISCONTINUITY - : FLUSHED; - } } else { - CHECK(mFlushingVideo == NONE - || mFlushingVideo == AWAITING_DISCONTINUITY); - + ALOGE_IF(mFlushingVideo != NONE, + "video flushDecoder() is called in state %d", mFlushingVideo); mFlushingVideo = newStatus; - if (mFlushingAudio == NONE) { - mFlushingAudio = (mAudioDecoder != NULL) - ? AWAITING_DISCONTINUITY - : FLUSHED; + if (mCCDecoder != NULL) { + mCCDecoder->flush(); } } } -sp<AMessage> NuPlayer::Source::getFormat(bool audio) { - sp<MetaData> meta = getFormatMeta(audio); +void NuPlayer::updateDecoderFormatWithoutFlush( + bool audio, const sp<AMessage> &format) { + ALOGV("[%s] updateDecoderFormatWithoutFlush", audio ? "audio" : "video"); - if (meta == NULL) { - return NULL; + const sp<Decoder> &decoder = getDecoder(audio); + if (decoder == NULL) { + ALOGI("updateDecoderFormatWithoutFlush %s without decoder present", + audio ? "audio" : "video"); + return; } - sp<AMessage> msg = new AMessage; + decoder->signalUpdateFormat(format); +} - if(convertMetaDataToMessage(meta, &msg) == OK) { - return msg; - } - return NULL; +void NuPlayer::queueDecoderShutdown( + bool audio, bool video, const sp<AMessage> &reply) { + ALOGI("queueDecoderShutdown audio=%d, video=%d", audio, video); + + mDeferredActions.push_back( + new ShutdownDecoderAction(audio, video)); + + mDeferredActions.push_back( + new SimpleAction(&NuPlayer::performScanSources)); + + mDeferredActions.push_back(new PostMessageAction(reply)); + + processDeferredActions(); } status_t NuPlayer::setVideoScalingMode(int32_t mode) { @@ -1211,6 +1705,19 @@ status_t NuPlayer::getTrackInfo(Parcel* reply) const { return err; } +status_t NuPlayer::getSelectedTrack(int32_t type, Parcel* reply) const { + sp<AMessage> msg = new AMessage(kWhatGetSelectedTrack, id()); + msg->setPointer("reply", reply); + msg->setInt32("type", type); + + sp<AMessage> response; + status_t err = msg->postAndAwaitResponse(&response); + if (err == OK && response != NULL) { + CHECK(response->findInt32("err", &err)); + } + return err; +} + status_t NuPlayer::selectTrack(size_t trackIndex, bool select) { sp<AMessage> msg = new AMessage(kWhatSelectTrack, id()); msg->setSize("trackIndex", trackIndex); @@ -1219,9 +1726,21 @@ status_t NuPlayer::selectTrack(size_t trackIndex, bool select) { sp<AMessage> response; status_t err = msg->postAndAwaitResponse(&response); + if (err != OK) { + return err; + } + + if (!response->findInt32("err", &err)) { + err = OK; + } + return err; } +sp<MetaData> NuPlayer::getFileMeta() { + return mSource->getFileFormatMeta(); +} + void NuPlayer::schedulePollDuration() { sp<AMessage> msg = new AMessage(kWhatPollDuration, id()); msg->setInt32("generation", mPollDurationGeneration); @@ -1238,18 +1757,6 @@ void NuPlayer::processDeferredActions() { // an intermediate state, i.e. one more more decoders are currently // flushing or shutting down. - if (mRenderer != NULL) { - // There's an edge case where the renderer owns all output - // buffers and is paused, therefore the decoder will not read - // more input data and will never encounter the matching - // discontinuity. To avoid this, we resume the renderer. - - if (mFlushingAudio == AWAITING_DISCONTINUITY - || mFlushingVideo == AWAITING_DISCONTINUITY) { - mRenderer->resume(); - } - } - if (mFlushingAudio != NONE || mFlushingVideo != NONE) { // We're currently flushing, postpone the reset until that's // completed. @@ -1272,7 +1779,16 @@ void NuPlayer::performSeek(int64_t seekTimeUs) { seekTimeUs, seekTimeUs / 1E6); + if (mSource == NULL) { + // This happens when reset occurs right before the loop mode + // asynchronously seeks to the start of the stream. + LOG_ALWAYS_FATAL_IF(mAudioDecoder != NULL || mVideoDecoder != NULL, + "mSource is NULL and decoders not NULL audio(%p) video(%p)", + mAudioDecoder.get(), mVideoDecoder.get()); + return; + } mSource->seekTo(seekTimeUs); + ++mTimedTextGeneration; if (mDriver != NULL) { sp<NuPlayerDriver> driver = mDriver.promote(); @@ -1313,14 +1829,6 @@ void NuPlayer::performDecoderShutdown(bool audio, bool video) { mTimeDiscontinuityPending = true; - if (mFlushingAudio == NONE && (!audio || mAudioDecoder == NULL)) { - mFlushingAudio = FLUSHED; - } - - if (mFlushingVideo == NONE && (!video || mVideoDecoder == NULL)) { - mFlushingVideo = FLUSHED; - } - if (audio && mAudioDecoder != NULL) { flushDecoder(true /* audio */, true /* needShutdown */); } @@ -1341,13 +1849,21 @@ void NuPlayer::performReset() { ++mScanSourcesGeneration; mScanSourcesPending = false; + ++mAudioDecoderGeneration; + ++mVideoDecoderGeneration; + + if (mRendererLooper != NULL) { + if (mRenderer != NULL) { + mRendererLooper->unregisterHandler(mRenderer->id()); + } + mRendererLooper->stop(); + mRendererLooper.clear(); + } mRenderer.clear(); if (mSource != NULL) { mSource->stop(); - looper()->unregisterHandler(mSource->id()); - mSource.clear(); } @@ -1408,16 +1924,15 @@ void NuPlayer::onSourceNotify(const sp<AMessage> &msg) { sp<NuPlayerDriver> driver = mDriver.promote(); if (driver != NULL) { - driver->notifyPrepareCompleted(err); - } - - int64_t durationUs; - if (mDriver != NULL && mSource->getDuration(&durationUs) == OK) { - sp<NuPlayerDriver> driver = mDriver.promote(); - if (driver != NULL) { + // notify duration first, so that it's definitely set when + // the app received the "prepare complete" callback. + int64_t durationUs; + if (mSource->getDuration(&durationUs) == OK) { driver->notifyDuration(durationUs); } + driver->notifyPrepareCompleted(err); } + break; } @@ -1446,11 +1961,19 @@ void NuPlayer::onSourceNotify(const sp<AMessage> &msg) { case Source::kWhatVideoSizeChanged: { - int32_t width, height; - CHECK(msg->findInt32("width", &width)); - CHECK(msg->findInt32("height", &height)); + sp<AMessage> format; + CHECK(msg->findMessage("format", &format)); + + updateVideoSize(format); + break; + } + + case Source::kWhatBufferingUpdate: + { + int32_t percentage; + CHECK(msg->findInt32("percentage", &percentage)); - notifyListener(MEDIA_SET_VIDEO_SIZE, width, height); + notifyListener(MEDIA_BUFFERING_UPDATE, percentage, 0); break; } @@ -1471,21 +1994,40 @@ void NuPlayer::onSourceNotify(const sp<AMessage> &msg) { sp<ABuffer> buffer; CHECK(msg->findBuffer("buffer", &buffer)); - int32_t trackIndex; - int64_t timeUs, durationUs; - CHECK(buffer->meta()->findInt32("trackIndex", &trackIndex)); - CHECK(buffer->meta()->findInt64("timeUs", &timeUs)); - CHECK(buffer->meta()->findInt64("durationUs", &durationUs)); + sendSubtitleData(buffer, 0 /* baseIndex */); + break; + } - Parcel in; - in.writeInt32(trackIndex); - in.writeInt64(timeUs); - in.writeInt64(durationUs); - in.writeInt32(buffer->size()); - in.writeInt32(buffer->size()); - in.write(buffer->data(), buffer->size()); + case Source::kWhatTimedTextData: + { + int32_t generation; + if (msg->findInt32("generation", &generation) + && generation != mTimedTextGeneration) { + break; + } + + sp<ABuffer> buffer; + CHECK(msg->findBuffer("buffer", &buffer)); + + sp<NuPlayerDriver> driver = mDriver.promote(); + if (driver == NULL) { + break; + } + + int posMs; + int64_t timeUs, posUs; + driver->getCurrentPosition(&posMs); + posUs = posMs * 1000; + CHECK(buffer->meta()->findInt64("timeUs", &timeUs)); - notifyListener(MEDIA_SUBTITLE_DATA, 0, 0, &in); + if (posUs < timeUs) { + if (!msg->findInt32("generation", &generation)) { + msg->setInt32("generation", mTimedTextGeneration); + } + msg->post(timeUs - posUs); + } else { + sendTimedTextData(buffer); + } break; } @@ -1502,13 +2044,112 @@ void NuPlayer::onSourceNotify(const sp<AMessage> &msg) { break; } + case Source::kWhatDrmNoLicense: + { + notifyListener(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, ERROR_DRM_NO_LICENSE); + break; + } + default: TRESPASS(); } } +void NuPlayer::onClosedCaptionNotify(const sp<AMessage> &msg) { + int32_t what; + CHECK(msg->findInt32("what", &what)); + + switch (what) { + case NuPlayer::CCDecoder::kWhatClosedCaptionData: + { + sp<ABuffer> buffer; + CHECK(msg->findBuffer("buffer", &buffer)); + + size_t inbandTracks = 0; + if (mSource != NULL) { + inbandTracks = mSource->getTrackCount(); + } + + sendSubtitleData(buffer, inbandTracks); + break; + } + + case NuPlayer::CCDecoder::kWhatTrackAdded: + { + notifyListener(MEDIA_INFO, MEDIA_INFO_METADATA_UPDATE, 0); + + break; + } + + default: + TRESPASS(); + } + + +} + +void NuPlayer::sendSubtitleData(const sp<ABuffer> &buffer, int32_t baseIndex) { + int32_t trackIndex; + int64_t timeUs, durationUs; + CHECK(buffer->meta()->findInt32("trackIndex", &trackIndex)); + CHECK(buffer->meta()->findInt64("timeUs", &timeUs)); + CHECK(buffer->meta()->findInt64("durationUs", &durationUs)); + + Parcel in; + in.writeInt32(trackIndex + baseIndex); + in.writeInt64(timeUs); + in.writeInt64(durationUs); + in.writeInt32(buffer->size()); + in.writeInt32(buffer->size()); + in.write(buffer->data(), buffer->size()); + + notifyListener(MEDIA_SUBTITLE_DATA, 0, 0, &in); +} + +void NuPlayer::sendTimedTextData(const sp<ABuffer> &buffer) { + const void *data; + size_t size = 0; + int64_t timeUs; + int32_t flag = TextDescriptions::LOCAL_DESCRIPTIONS; + + AString mime; + CHECK(buffer->meta()->findString("mime", &mime)); + CHECK(strcasecmp(mime.c_str(), MEDIA_MIMETYPE_TEXT_3GPP) == 0); + + data = buffer->data(); + size = buffer->size(); + + Parcel parcel; + if (size > 0) { + CHECK(buffer->meta()->findInt64("timeUs", &timeUs)); + flag |= TextDescriptions::IN_BAND_TEXT_3GPP; + TextDescriptions::getParcelOfDescriptions( + (const uint8_t *)data, size, flag, timeUs / 1000, &parcel); + } + + if ((parcel.dataSize() > 0)) { + notifyListener(MEDIA_TIMED_TEXT, 0, 0, &parcel); + } else { // send an empty timed text + notifyListener(MEDIA_TIMED_TEXT, 0, 0); + } +} //////////////////////////////////////////////////////////////////////////////// +sp<AMessage> NuPlayer::Source::getFormat(bool audio) { + sp<MetaData> meta = getFormatMeta(audio); + + if (meta == NULL) { + return NULL; + } + + sp<AMessage> msg = new AMessage; + + if(convertMetaDataToMessage(meta, &msg) == OK) { + return msg; + } + return NULL; +} + void NuPlayer::Source::notifyFlagsChanged(uint32_t flags) { sp<AMessage> notify = dupNotify(); notify->setInt32("what", kWhatFlagsChanged); @@ -1516,11 +2157,10 @@ void NuPlayer::Source::notifyFlagsChanged(uint32_t flags) { notify->post(); } -void NuPlayer::Source::notifyVideoSizeChanged(int32_t width, int32_t height) { +void NuPlayer::Source::notifyVideoSizeChanged(const sp<AMessage> &format) { sp<AMessage> notify = dupNotify(); notify->setInt32("what", kWhatVideoSizeChanged); - notify->setInt32("width", width); - notify->setInt32("height", height); + notify->setMessage("format", format); notify->post(); } @@ -1531,23 +2171,8 @@ void NuPlayer::Source::notifyPrepared(status_t err) { notify->post(); } -void NuPlayer::Source::onMessageReceived(const sp<AMessage> &msg) { +void NuPlayer::Source::onMessageReceived(const sp<AMessage> & /* msg */) { TRESPASS(); } -void NuPlayer::queueDecoderShutdown( - bool audio, bool video, const sp<AMessage> &reply) { - ALOGI("queueDecoderShutdown audio=%d, video=%d", audio, video); - - mDeferredActions.push_back( - new ShutdownDecoderAction(audio, video)); - - mDeferredActions.push_back( - new SimpleAction(&NuPlayer::performScanSources)); - - mDeferredActions.push_back(new PostMessageAction(reply)); - - processDeferredActions(); -} - } // namespace android diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h index 590e1f2..7197e5f 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayer.h +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h @@ -24,7 +24,8 @@ namespace android { -struct ACodec; +struct ABuffer; +struct AMessage; struct MetaData; struct NuPlayerDriver; @@ -38,7 +39,9 @@ struct NuPlayer : public AHandler { void setDataSourceAsync(const sp<IStreamSource> &source); void setDataSourceAsync( - const char *url, const KeyedVector<String8, String8> *headers); + const sp<IMediaHTTPService> &httpService, + const char *url, + const KeyedVector<String8, String8> *headers); void setDataSourceAsync(int fd, int64_t offset, int64_t length); @@ -61,8 +64,13 @@ struct NuPlayer : public AHandler { status_t setVideoScalingMode(int32_t mode); status_t getTrackInfo(Parcel* reply) const; + status_t getSelectedTrack(int32_t type, Parcel* reply) const; status_t selectTrack(size_t trackIndex, bool select); + sp<MetaData> getFileMeta(); + + static const size_t kAggregateBufferSizeBytes; + protected: virtual ~NuPlayer(); @@ -74,6 +82,8 @@ public: private: struct Decoder; + struct DecoderPassThrough; + struct CCDecoder; struct GenericSource; struct HTTPLiveSource; struct Renderer; @@ -96,6 +106,7 @@ private: kWhatScanSources = 'scan', kWhatVideoNotify = 'vidN', kWhatAudioNotify = 'audN', + kWhatClosedCaptionNotify = 'capN', kWhatRendererNotify = 'renN', kWhatReset = 'rset', kWhatSeek = 'seek', @@ -104,6 +115,7 @@ private: kWhatPollDuration = 'polD', kWhatSourceNotify = 'srcN', kWhatGetTrackInfo = 'gTrI', + kWhatGetSelectedTrack = 'gSel', kWhatSelectTrack = 'selT', }; @@ -113,12 +125,18 @@ private: sp<Source> mSource; uint32_t mSourceFlags; sp<NativeWindowWrapper> mNativeWindow; + int64_t mCurrentPositionUs; sp<MediaPlayerBase::AudioSink> mAudioSink; sp<Decoder> mVideoDecoder; bool mVideoIsAVC; - bool mNeedsSwRenderer; + bool mOffloadAudio; + audio_offload_info_t mCurrentOffloadInfo; sp<Decoder> mAudioDecoder; + sp<CCDecoder> mCCDecoder; sp<Renderer> mRenderer; + sp<ALooper> mRendererLooper; + int32_t mAudioDecoderGeneration; + int32_t mVideoDecoderGeneration; List<sp<Action> > mDeferredActions; @@ -129,10 +147,10 @@ private: int32_t mScanSourcesGeneration; int32_t mPollDurationGeneration; + int32_t mTimedTextGeneration; enum FlushStatus { NONE, - AWAITING_DISCONTINUITY, FLUSHING_DECODER, FLUSHING_DECODER_SHUTDOWN, SHUTTING_DOWN_DECODER, @@ -144,6 +162,12 @@ private: // notion of time has changed. bool mTimeDiscontinuityPending; + // Used by feedDecoderInputData to aggregate small buffers into + // one large buffer. + sp<ABuffer> mPendingAudioAccessUnit; + status_t mPendingAudioErr; + sp<ABuffer> mAggregateBuffer; + FlushStatus mFlushingAudio; FlushStatus mFlushingVideo; @@ -157,8 +181,19 @@ private: bool mStarted; + inline const sp<Decoder> &getDecoder(bool audio) { + return audio ? mAudioDecoder : mVideoDecoder; + } + + void openAudioSink(const sp<AMessage> &format, bool offloadOnly); + void closeAudioSink(); + status_t instantiateDecoder(bool audio, sp<Decoder> *decoder); + void updateVideoSize( + const sp<AMessage> &inputFormat, + const sp<AMessage> &outputFormat = NULL); + status_t feedDecoderInputData(bool audio, const sp<AMessage> &msg); void renderBuffer(bool audio, const sp<AMessage> &msg); @@ -166,7 +201,9 @@ private: void finishFlushIfPossible(); - void flushDecoder(bool audio, bool needShutdown); + void flushDecoder( + bool audio, bool needShutdown, const sp<AMessage> &newFormat = NULL); + void updateDecoderFormatWithoutFlush(bool audio, const sp<AMessage> &format); static bool IsFlushingState(FlushStatus state, bool *needShutdown = NULL); @@ -185,10 +222,16 @@ private: void performSetSurface(const sp<NativeWindowWrapper> &wrapper); void onSourceNotify(const sp<AMessage> &msg); + void onClosedCaptionNotify(const sp<AMessage> &msg); void queueDecoderShutdown( bool audio, bool video, const sp<AMessage> &reply); + void sendSubtitleData(const sp<ABuffer> &buffer, int32_t baseIndex); + void sendTimedTextData(const sp<ABuffer> &buffer); + + void writeTrackInfo(Parcel* reply, const sp<AMessage> format) const; + DISALLOW_EVIL_CONSTRUCTORS(NuPlayer); }; diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp index 2423fd5..cdb860c 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp @@ -17,14 +17,19 @@ //#define LOG_NDEBUG 0 #define LOG_TAG "NuPlayerDecoder" #include <utils/Log.h> +#include <inttypes.h> #include "NuPlayerDecoder.h" +#include <media/ICrypto.h> +#include <media/stagefright/foundation/ABitReader.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AMessage.h> -#include <media/stagefright/ACodec.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaCodec.h> #include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> namespace android { @@ -32,120 +37,619 @@ NuPlayer::Decoder::Decoder( const sp<AMessage> ¬ify, const sp<NativeWindowWrapper> &nativeWindow) : mNotify(notify), - mNativeWindow(nativeWindow) { + mNativeWindow(nativeWindow), + mBufferGeneration(0), + mPaused(true), + mComponentName("decoder") { + // Every decoder has its own looper because MediaCodec operations + // are blocking, but NuPlayer needs asynchronous operations. + mDecoderLooper = new ALooper; + mDecoderLooper->setName("NPDecoder"); + mDecoderLooper->start(false, false, ANDROID_PRIORITY_AUDIO); + + mCodecLooper = new ALooper; + mCodecLooper->setName("NPDecoder-CL"); + mCodecLooper->start(false, false, ANDROID_PRIORITY_AUDIO); } NuPlayer::Decoder::~Decoder() { } -void NuPlayer::Decoder::configure(const sp<AMessage> &format) { +static +status_t PostAndAwaitResponse( + const sp<AMessage> &msg, sp<AMessage> *response) { + status_t err = msg->postAndAwaitResponse(response); + + if (err != OK) { + return err; + } + + if (!(*response)->findInt32("err", &err)) { + err = OK; + } + + return err; +} + +void NuPlayer::Decoder::rememberCodecSpecificData(const sp<AMessage> &format) { + mCSDsForCurrentFormat.clear(); + for (int32_t i = 0; ; ++i) { + AString tag = "csd-"; + tag.append(i); + sp<ABuffer> buffer; + if (!format->findBuffer(tag.c_str(), &buffer)) { + break; + } + mCSDsForCurrentFormat.push(buffer); + } +} + +void NuPlayer::Decoder::onConfigure(const sp<AMessage> &format) { CHECK(mCodec == NULL); + ++mBufferGeneration; + AString mime; CHECK(format->findString("mime", &mime)); - sp<AMessage> notifyMsg = - new AMessage(kWhatCodecNotify, id()); + sp<Surface> surface = NULL; + if (mNativeWindow != NULL) { + surface = mNativeWindow->getSurfaceTextureClient(); + } - mCSDIndex = 0; - for (size_t i = 0;; ++i) { - sp<ABuffer> csd; - if (!format->findBuffer(StringPrintf("csd-%d", i).c_str(), &csd)) { - break; - } + mComponentName = mime; + mComponentName.append(" decoder"); + ALOGV("[%s] onConfigure (surface=%p)", mComponentName.c_str(), surface.get()); - mCSD.push(csd); + mCodec = MediaCodec::CreateByType(mCodecLooper, mime.c_str(), false /* encoder */); + int32_t secure = 0; + if (format->findInt32("secure", &secure) && secure != 0) { + if (mCodec != NULL) { + mCodec->getName(&mComponentName); + mComponentName.append(".secure"); + mCodec->release(); + ALOGI("[%s] creating", mComponentName.c_str()); + mCodec = MediaCodec::CreateByComponentName( + mCodecLooper, mComponentName.c_str()); + } + } + if (mCodec == NULL) { + ALOGE("Failed to create %s%s decoder", + (secure ? "secure " : ""), mime.c_str()); + handleError(UNKNOWN_ERROR); + return; } + mCodec->getName(&mComponentName); + + status_t err; if (mNativeWindow != NULL) { - format->setObject("native-window", mNativeWindow); + // disconnect from surface as MediaCodec will reconnect + err = native_window_api_disconnect( + surface.get(), NATIVE_WINDOW_API_MEDIA); + // We treat this as a warning, as this is a preparatory step. + // Codec will try to connect to the surface, which is where + // any error signaling will occur. + ALOGW_IF(err != OK, "failed to disconnect from surface: %d", err); + } + err = mCodec->configure( + format, surface, NULL /* crypto */, 0 /* flags */); + if (err != OK) { + ALOGE("Failed to configure %s decoder (err=%d)", mComponentName.c_str(), err); + handleError(err); + return; } + rememberCodecSpecificData(format); - // Current video decoders do not return from OMX_FillThisBuffer - // quickly, violating the OpenMAX specs, until that is remedied - // we need to invest in an extra looper to free the main event - // queue. - bool needDedicatedLooper = !strncasecmp(mime.c_str(), "video/", 6); + // the following should work in configured state + CHECK_EQ((status_t)OK, mCodec->getOutputFormat(&mOutputFormat)); + CHECK_EQ((status_t)OK, mCodec->getInputFormat(&mInputFormat)); - mFormat = format; - mCodec = new ACodec; + err = mCodec->start(); + if (err != OK) { + ALOGE("Failed to start %s decoder (err=%d)", mComponentName.c_str(), err); + handleError(err); + return; + } - if (needDedicatedLooper && mCodecLooper == NULL) { - mCodecLooper = new ALooper; - mCodecLooper->setName("NuPlayerDecoder"); - mCodecLooper->start(false, false, ANDROID_PRIORITY_AUDIO); + // the following should work after start + CHECK_EQ((status_t)OK, mCodec->getInputBuffers(&mInputBuffers)); + releaseAndResetMediaBuffers(); + CHECK_EQ((status_t)OK, mCodec->getOutputBuffers(&mOutputBuffers)); + ALOGV("[%s] got %zu input and %zu output buffers", + mComponentName.c_str(), + mInputBuffers.size(), + mOutputBuffers.size()); + + requestCodecNotification(); + mPaused = false; +} + +void NuPlayer::Decoder::releaseAndResetMediaBuffers() { + for (size_t i = 0; i < mMediaBuffers.size(); i++) { + if (mMediaBuffers[i] != NULL) { + mMediaBuffers[i]->release(); + mMediaBuffers.editItemAt(i) = NULL; + } + } + mMediaBuffers.resize(mInputBuffers.size()); + for (size_t i = 0; i < mMediaBuffers.size(); i++) { + mMediaBuffers.editItemAt(i) = NULL; } + mInputBufferIsDequeued.clear(); + mInputBufferIsDequeued.resize(mInputBuffers.size()); + for (size_t i = 0; i < mInputBufferIsDequeued.size(); i++) { + mInputBufferIsDequeued.editItemAt(i) = false; + } +} - (needDedicatedLooper ? mCodecLooper : looper())->registerHandler(mCodec); +void NuPlayer::Decoder::requestCodecNotification() { + if (mCodec != NULL) { + sp<AMessage> reply = new AMessage(kWhatCodecNotify, id()); + reply->setInt32("generation", mBufferGeneration); + mCodec->requestActivityNotification(reply); + } +} - mCodec->setNotificationMessage(notifyMsg); - mCodec->initiateSetup(format); +bool NuPlayer::Decoder::isStaleReply(const sp<AMessage> &msg) { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + return generation != mBufferGeneration; } -void NuPlayer::Decoder::onMessageReceived(const sp<AMessage> &msg) { - switch (msg->what()) { - case kWhatCodecNotify: - { - int32_t what; - CHECK(msg->findInt32("what", &what)); +void NuPlayer::Decoder::init() { + mDecoderLooper->registerHandler(this); +} - if (what == ACodec::kWhatFillThisBuffer) { - onFillThisBuffer(msg); - } else { - sp<AMessage> notify = mNotify->dup(); - notify->setMessage("codec-request", msg); - notify->post(); +void NuPlayer::Decoder::configure(const sp<AMessage> &format) { + sp<AMessage> msg = new AMessage(kWhatConfigure, id()); + msg->setMessage("format", format); + msg->post(); +} + +void NuPlayer::Decoder::signalUpdateFormat(const sp<AMessage> &format) { + sp<AMessage> msg = new AMessage(kWhatUpdateFormat, id()); + msg->setMessage("format", format); + msg->post(); +} + +status_t NuPlayer::Decoder::getInputBuffers(Vector<sp<ABuffer> > *buffers) const { + sp<AMessage> msg = new AMessage(kWhatGetInputBuffers, id()); + msg->setPointer("buffers", buffers); + + sp<AMessage> response; + return PostAndAwaitResponse(msg, &response); +} + +void NuPlayer::Decoder::handleError(int32_t err) +{ + mCodec->release(); + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatError); + notify->setInt32("err", err); + notify->post(); +} + +bool NuPlayer::Decoder::handleAnInputBuffer() { + size_t bufferIx = -1; + status_t res = mCodec->dequeueInputBuffer(&bufferIx); + ALOGV("[%s] dequeued input: %d", + mComponentName.c_str(), res == OK ? (int)bufferIx : res); + if (res != OK) { + if (res != -EAGAIN) { + handleError(res); + } + return false; + } + + CHECK_LT(bufferIx, mInputBuffers.size()); + + if (mMediaBuffers[bufferIx] != NULL) { + mMediaBuffers[bufferIx]->release(); + mMediaBuffers.editItemAt(bufferIx) = NULL; + } + mInputBufferIsDequeued.editItemAt(bufferIx) = true; + + sp<AMessage> reply = new AMessage(kWhatInputBufferFilled, id()); + reply->setSize("buffer-ix", bufferIx); + reply->setInt32("generation", mBufferGeneration); + + if (!mCSDsToSubmit.isEmpty()) { + sp<ABuffer> buffer = mCSDsToSubmit.itemAt(0); + ALOGI("[%s] resubmitting CSD", mComponentName.c_str()); + reply->setBuffer("buffer", buffer); + mCSDsToSubmit.removeAt(0); + reply->post(); + return true; + } + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatFillThisBuffer); + notify->setBuffer("buffer", mInputBuffers[bufferIx]); + notify->setMessage("reply", reply); + notify->post(); + return true; +} + +void android::NuPlayer::Decoder::onInputBufferFilled(const sp<AMessage> &msg) { + size_t bufferIx; + CHECK(msg->findSize("buffer-ix", &bufferIx)); + CHECK_LT(bufferIx, mInputBuffers.size()); + sp<ABuffer> codecBuffer = mInputBuffers[bufferIx]; + + sp<ABuffer> buffer; + bool hasBuffer = msg->findBuffer("buffer", &buffer); + + // handle widevine classic source - that fills an arbitrary input buffer + MediaBuffer *mediaBuffer = NULL; + if (hasBuffer) { + mediaBuffer = (MediaBuffer *)(buffer->getMediaBufferBase()); + if (mediaBuffer != NULL) { + // likely filled another buffer than we requested: adjust buffer index + size_t ix; + for (ix = 0; ix < mInputBuffers.size(); ix++) { + const sp<ABuffer> &buf = mInputBuffers[ix]; + if (buf->data() == mediaBuffer->data()) { + // all input buffers are dequeued on start, hence the check + CHECK(mInputBufferIsDequeued[ix]); + ALOGV("[%s] received MediaBuffer for #%zu instead of #%zu", + mComponentName.c_str(), ix, bufferIx); + + // TRICKY: need buffer for the metadata, so instead, set + // codecBuffer to the same (though incorrect) buffer to + // avoid a memcpy into the codecBuffer + codecBuffer = buffer; + codecBuffer->setRange( + mediaBuffer->range_offset(), + mediaBuffer->range_length()); + bufferIx = ix; + break; + } } - break; + CHECK(ix < mInputBuffers.size()); } + } - default: - TRESPASS(); - break; + mInputBufferIsDequeued.editItemAt(bufferIx) = false; + + if (buffer == NULL /* includes !hasBuffer */) { + int32_t streamErr = ERROR_END_OF_STREAM; + CHECK(msg->findInt32("err", &streamErr) || !hasBuffer); + + if (streamErr == OK) { + /* buffers are returned to hold on to */ + return; + } + + // attempt to queue EOS + status_t err = mCodec->queueInputBuffer( + bufferIx, + 0, + 0, + 0, + MediaCodec::BUFFER_FLAG_EOS); + if (streamErr == ERROR_END_OF_STREAM && err != OK) { + streamErr = err; + // err will not be ERROR_END_OF_STREAM + } + + if (streamErr != ERROR_END_OF_STREAM) { + handleError(streamErr); + } + } else { + int64_t timeUs = 0; + uint32_t flags = 0; + CHECK(buffer->meta()->findInt64("timeUs", &timeUs)); + + int32_t eos, csd; + // we do not expect SYNCFRAME for decoder + if (buffer->meta()->findInt32("eos", &eos) && eos) { + flags |= MediaCodec::BUFFER_FLAG_EOS; + } else if (buffer->meta()->findInt32("csd", &csd) && csd) { + flags |= MediaCodec::BUFFER_FLAG_CODECCONFIG; + } + + // copy into codec buffer + if (buffer != codecBuffer) { + CHECK_LE(buffer->size(), codecBuffer->capacity()); + codecBuffer->setRange(0, buffer->size()); + memcpy(codecBuffer->data(), buffer->data(), buffer->size()); + } + + status_t err = mCodec->queueInputBuffer( + bufferIx, + codecBuffer->offset(), + codecBuffer->size(), + timeUs, + flags); + if (err != OK) { + ALOGE("Failed to queue input buffer for %s (err=%d)", + mComponentName.c_str(), err); + handleError(err); + } + + if (mediaBuffer != NULL) { + CHECK(mMediaBuffers[bufferIx] == NULL); + mMediaBuffers.editItemAt(bufferIx) = mediaBuffer; + } } } -void NuPlayer::Decoder::onFillThisBuffer(const sp<AMessage> &msg) { - sp<AMessage> reply; - CHECK(msg->findMessage("reply", &reply)); +bool NuPlayer::Decoder::handleAnOutputBuffer() { + size_t bufferIx = -1; + size_t offset; + size_t size; + int64_t timeUs; + uint32_t flags; + status_t res = mCodec->dequeueOutputBuffer( + &bufferIx, &offset, &size, &timeUs, &flags); -#if 0 - sp<ABuffer> outBuffer; - CHECK(msg->findBuffer("buffer", &outBuffer)); -#else - sp<ABuffer> outBuffer; -#endif + if (res != OK) { + ALOGV("[%s] dequeued output: %d", mComponentName.c_str(), res); + } else { + ALOGV("[%s] dequeued output: %d (time=%lld flags=%" PRIu32 ")", + mComponentName.c_str(), (int)bufferIx, timeUs, flags); + } - if (mCSDIndex < mCSD.size()) { - outBuffer = mCSD.editItemAt(mCSDIndex++); - outBuffer->meta()->setInt64("timeUs", 0); + if (res == INFO_OUTPUT_BUFFERS_CHANGED) { + res = mCodec->getOutputBuffers(&mOutputBuffers); + if (res != OK) { + ALOGE("Failed to get output buffers for %s after INFO event (err=%d)", + mComponentName.c_str(), res); + handleError(res); + return false; + } + // NuPlayer ignores this + return true; + } else if (res == INFO_FORMAT_CHANGED) { + sp<AMessage> format = new AMessage(); + res = mCodec->getOutputFormat(&format); + if (res != OK) { + ALOGE("Failed to get output format for %s after INFO event (err=%d)", + mComponentName.c_str(), res); + handleError(res); + return false; + } - reply->setBuffer("buffer", outBuffer); - reply->post(); - return; + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatOutputFormatChanged); + notify->setMessage("format", format); + notify->post(); + return true; + } else if (res == INFO_DISCONTINUITY) { + // nothing to do + return true; + } else if (res != OK) { + if (res != -EAGAIN) { + handleError(res); + } + return false; } + CHECK_LT(bufferIx, mOutputBuffers.size()); + sp<ABuffer> buffer = mOutputBuffers[bufferIx]; + buffer->setRange(offset, size); + buffer->meta()->clear(); + buffer->meta()->setInt64("timeUs", timeUs); + if (flags & MediaCodec::BUFFER_FLAG_EOS) { + buffer->meta()->setInt32("eos", true); + } + // we do not expect CODECCONFIG or SYNCFRAME for decoder + + sp<AMessage> reply = new AMessage(kWhatRenderBuffer, id()); + reply->setSize("buffer-ix", bufferIx); + reply->setInt32("generation", mBufferGeneration); + sp<AMessage> notify = mNotify->dup(); - notify->setMessage("codec-request", msg); + notify->setInt32("what", kWhatDrainThisBuffer); + notify->setBuffer("buffer", buffer); + notify->setMessage("reply", reply); notify->post(); + + // FIXME: This should be handled after rendering is complete, + // but Renderer needs it now + if (flags & MediaCodec::BUFFER_FLAG_EOS) { + ALOGV("queueing eos [%s]", mComponentName.c_str()); + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatEOS); + notify->setInt32("err", ERROR_END_OF_STREAM); + notify->post(); + } + return true; } -void NuPlayer::Decoder::signalFlush() { - if (mCodec != NULL) { - mCodec->signalFlush(); +void NuPlayer::Decoder::onRenderBuffer(const sp<AMessage> &msg) { + status_t err; + int32_t render; + size_t bufferIx; + CHECK(msg->findSize("buffer-ix", &bufferIx)); + if (msg->findInt32("render", &render) && render) { + int64_t timestampNs; + CHECK(msg->findInt64("timestampNs", ×tampNs)); + err = mCodec->renderOutputBufferAndRelease(bufferIx, timestampNs); + } else { + err = mCodec->releaseOutputBuffer(bufferIx); + } + if (err != OK) { + ALOGE("failed to release output buffer for %s (err=%d)", + mComponentName.c_str(), err); + handleError(err); } } -void NuPlayer::Decoder::signalResume() { +void NuPlayer::Decoder::onFlush() { + status_t err = OK; if (mCodec != NULL) { - mCodec->signalResume(); + err = mCodec->flush(); + mCSDsToSubmit = mCSDsForCurrentFormat; // copy operator + ++mBufferGeneration; + } + + if (err != OK) { + ALOGE("failed to flush %s (err=%d)", mComponentName.c_str(), err); + handleError(err); + return; } + + releaseAndResetMediaBuffers(); + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatFlushCompleted); + notify->post(); + mPaused = true; } -void NuPlayer::Decoder::initiateShutdown() { +void NuPlayer::Decoder::onResume() { + mPaused = false; +} + +void NuPlayer::Decoder::onShutdown() { + status_t err = OK; if (mCodec != NULL) { - mCodec->initiateShutdown(); + err = mCodec->release(); + mCodec = NULL; + ++mBufferGeneration; + + if (mNativeWindow != NULL) { + // reconnect to surface as MediaCodec disconnected from it + status_t error = + native_window_api_connect( + mNativeWindow->getNativeWindow().get(), + NATIVE_WINDOW_API_MEDIA); + ALOGW_IF(error != NO_ERROR, + "[%s] failed to connect to native window, error=%d", + mComponentName.c_str(), error); + } + mComponentName = "decoder"; + } + + releaseAndResetMediaBuffers(); + + if (err != OK) { + ALOGE("failed to release %s (err=%d)", mComponentName.c_str(), err); + handleError(err); + return; + } + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatShutdownCompleted); + notify->post(); + mPaused = true; +} + +void NuPlayer::Decoder::onMessageReceived(const sp<AMessage> &msg) { + ALOGV("[%s] onMessage: %s", mComponentName.c_str(), msg->debugString().c_str()); + + switch (msg->what()) { + case kWhatConfigure: + { + sp<AMessage> format; + CHECK(msg->findMessage("format", &format)); + onConfigure(format); + break; + } + + case kWhatUpdateFormat: + { + sp<AMessage> format; + CHECK(msg->findMessage("format", &format)); + rememberCodecSpecificData(format); + break; + } + + case kWhatGetInputBuffers: + { + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + Vector<sp<ABuffer> > *dstBuffers; + CHECK(msg->findPointer("buffers", (void **)&dstBuffers)); + + dstBuffers->clear(); + for (size_t i = 0; i < mInputBuffers.size(); i++) { + dstBuffers->push(mInputBuffers[i]); + } + + (new AMessage)->postReply(replyID); + break; + } + + case kWhatCodecNotify: + { + if (!isStaleReply(msg)) { + if (!mPaused) { + while (handleAnInputBuffer()) { + } + } + + while (handleAnOutputBuffer()) { + } + } + + requestCodecNotification(); + break; + } + + case kWhatInputBufferFilled: + { + if (!isStaleReply(msg)) { + onInputBufferFilled(msg); + } + + break; + } + + case kWhatRenderBuffer: + { + if (!isStaleReply(msg)) { + onRenderBuffer(msg); + } + break; + } + + case kWhatFlush: + { + sp<AMessage> format; + if (msg->findMessage("new-format", &format)) { + rememberCodecSpecificData(format); + } + onFlush(); + break; + } + + case kWhatResume: + { + onResume(); + break; + } + + case kWhatShutdown: + { + onShutdown(); + break; + } + + default: + TRESPASS(); + break; + } +} + +void NuPlayer::Decoder::signalFlush(const sp<AMessage> &format) { + sp<AMessage> msg = new AMessage(kWhatFlush, id()); + if (format != NULL) { + msg->setMessage("new-format", format); } + msg->post(); +} + +void NuPlayer::Decoder::signalResume() { + (new AMessage(kWhatResume, id()))->post(); +} + +void NuPlayer::Decoder::initiateShutdown() { + (new AMessage(kWhatShutdown, id()))->post(); } bool NuPlayer::Decoder::supportsSeamlessAudioFormatChange(const sp<AMessage> &targetFormat) const { @@ -163,14 +667,16 @@ bool NuPlayer::Decoder::supportsSeamlessAudioFormatChange(const sp<AMessage> &ta const char * keys[] = { "channel-count", "sample-rate", "is-adts" }; for (unsigned int i = 0; i < sizeof(keys) / sizeof(keys[0]); i++) { int32_t oldVal, newVal; - if (!mFormat->findInt32(keys[i], &oldVal) || !targetFormat->findInt32(keys[i], &newVal) - || oldVal != newVal) { + if (!mOutputFormat->findInt32(keys[i], &oldVal) || + !targetFormat->findInt32(keys[i], &newVal) || + oldVal != newVal) { return false; } } sp<ABuffer> oldBuf, newBuf; - if (mFormat->findBuffer("csd-0", &oldBuf) && targetFormat->findBuffer("csd-0", &newBuf)) { + if (mOutputFormat->findBuffer("csd-0", &oldBuf) && + targetFormat->findBuffer("csd-0", &newBuf)) { if (oldBuf->size() != newBuf->size()) { return false; } @@ -181,7 +687,7 @@ bool NuPlayer::Decoder::supportsSeamlessAudioFormatChange(const sp<AMessage> &ta } bool NuPlayer::Decoder::supportsSeamlessFormatChange(const sp<AMessage> &targetFormat) const { - if (mFormat == NULL) { + if (mOutputFormat == NULL) { return false; } @@ -190,7 +696,7 @@ bool NuPlayer::Decoder::supportsSeamlessFormatChange(const sp<AMessage> &targetF } AString oldMime, newMime; - if (!mFormat->findString("mime", &oldMime) + if (!mOutputFormat->findString("mime", &oldMime) || !targetFormat->findString("mime", &newMime) || !(oldMime == newMime)) { return false; @@ -201,12 +707,343 @@ bool NuPlayer::Decoder::supportsSeamlessFormatChange(const sp<AMessage> &targetF if (audio) { seamless = supportsSeamlessAudioFormatChange(targetFormat); } else { - seamless = mCodec != NULL && mCodec->isConfiguredForAdaptivePlayback(); + int32_t isAdaptive; + seamless = (mCodec != NULL && + mInputFormat->findInt32("adaptive-playback", &isAdaptive) && + isAdaptive); } ALOGV("%s seamless support for %s", seamless ? "yes" : "no", oldMime.c_str()); return seamless; } +struct CCData { + CCData(uint8_t type, uint8_t data1, uint8_t data2) + : mType(type), mData1(data1), mData2(data2) { + } + bool getChannel(size_t *channel) const { + if (mData1 >= 0x10 && mData1 <= 0x1f) { + *channel = (mData1 >= 0x18 ? 1 : 0) + (mType ? 2 : 0); + return true; + } + return false; + } + + uint8_t mType; + uint8_t mData1; + uint8_t mData2; +}; + +static bool isNullPad(CCData *cc) { + return cc->mData1 < 0x10 && cc->mData2 < 0x10; +} + +static void dumpBytePair(const sp<ABuffer> &ccBuf) { + size_t offset = 0; + AString out; + + while (offset < ccBuf->size()) { + char tmp[128]; + + CCData *cc = (CCData *) (ccBuf->data() + offset); + + if (isNullPad(cc)) { + // 1 null pad or XDS metadata, ignore + offset += sizeof(CCData); + continue; + } + + if (cc->mData1 >= 0x20 && cc->mData1 <= 0x7f) { + // 2 basic chars + sprintf(tmp, "[%d]Basic: %c %c", cc->mType, cc->mData1, cc->mData2); + } else if ((cc->mData1 == 0x11 || cc->mData1 == 0x19) + && cc->mData2 >= 0x30 && cc->mData2 <= 0x3f) { + // 1 special char + sprintf(tmp, "[%d]Special: %02x %02x", cc->mType, cc->mData1, cc->mData2); + } else if ((cc->mData1 == 0x12 || cc->mData1 == 0x1A) + && cc->mData2 >= 0x20 && cc->mData2 <= 0x3f){ + // 1 Spanish/French char + sprintf(tmp, "[%d]Spanish: %02x %02x", cc->mType, cc->mData1, cc->mData2); + } else if ((cc->mData1 == 0x13 || cc->mData1 == 0x1B) + && cc->mData2 >= 0x20 && cc->mData2 <= 0x3f){ + // 1 Portuguese/German/Danish char + sprintf(tmp, "[%d]German: %02x %02x", cc->mType, cc->mData1, cc->mData2); + } else if ((cc->mData1 == 0x11 || cc->mData1 == 0x19) + && cc->mData2 >= 0x20 && cc->mData2 <= 0x2f){ + // Mid-Row Codes (Table 69) + sprintf(tmp, "[%d]Mid-row: %02x %02x", cc->mType, cc->mData1, cc->mData2); + } else if (((cc->mData1 == 0x14 || cc->mData1 == 0x1c) + && cc->mData2 >= 0x20 && cc->mData2 <= 0x2f) + || + ((cc->mData1 == 0x17 || cc->mData1 == 0x1f) + && cc->mData2 >= 0x21 && cc->mData2 <= 0x23)){ + // Misc Control Codes (Table 70) + sprintf(tmp, "[%d]Ctrl: %02x %02x", cc->mType, cc->mData1, cc->mData2); + } else if ((cc->mData1 & 0x70) == 0x10 + && (cc->mData2 & 0x40) == 0x40 + && ((cc->mData1 & 0x07) || !(cc->mData2 & 0x20)) ) { + // Preamble Address Codes (Table 71) + sprintf(tmp, "[%d]PAC: %02x %02x", cc->mType, cc->mData1, cc->mData2); + } else { + sprintf(tmp, "[%d]Invalid: %02x %02x", cc->mType, cc->mData1, cc->mData2); + } + + if (out.size() > 0) { + out.append(", "); + } + + out.append(tmp); + + offset += sizeof(CCData); + } + + ALOGI("%s", out.c_str()); +} + +NuPlayer::CCDecoder::CCDecoder(const sp<AMessage> ¬ify) + : mNotify(notify), + mCurrentChannel(0), + mSelectedTrack(-1) { + for (size_t i = 0; i < sizeof(mTrackIndices)/sizeof(mTrackIndices[0]); ++i) { + mTrackIndices[i] = -1; + } +} + +size_t NuPlayer::CCDecoder::getTrackCount() const { + return mFoundChannels.size(); +} + +sp<AMessage> NuPlayer::CCDecoder::getTrackInfo(size_t index) const { + if (!isTrackValid(index)) { + return NULL; + } + + sp<AMessage> format = new AMessage(); + + format->setInt32("type", MEDIA_TRACK_TYPE_SUBTITLE); + format->setString("language", "und"); + format->setString("mime", MEDIA_MIMETYPE_TEXT_CEA_608); + //CC1, field 0 channel 0 + bool isDefaultAuto = (mFoundChannels[index] == 0); + format->setInt32("auto", isDefaultAuto); + format->setInt32("default", isDefaultAuto); + format->setInt32("forced", 0); + + return format; +} + +status_t NuPlayer::CCDecoder::selectTrack(size_t index, bool select) { + if (!isTrackValid(index)) { + return BAD_VALUE; + } + + if (select) { + if (mSelectedTrack == (ssize_t)index) { + ALOGE("track %zu already selected", index); + return BAD_VALUE; + } + ALOGV("selected track %zu", index); + mSelectedTrack = index; + } else { + if (mSelectedTrack != (ssize_t)index) { + ALOGE("track %zu is not selected", index); + return BAD_VALUE; + } + ALOGV("unselected track %zu", index); + mSelectedTrack = -1; + } + + return OK; +} + +bool NuPlayer::CCDecoder::isSelected() const { + return mSelectedTrack >= 0 && mSelectedTrack < (int32_t) getTrackCount(); +} + +bool NuPlayer::CCDecoder::isTrackValid(size_t index) const { + return index < getTrackCount(); +} + +int32_t NuPlayer::CCDecoder::getTrackIndex(size_t channel) const { + if (channel < sizeof(mTrackIndices)/sizeof(mTrackIndices[0])) { + return mTrackIndices[channel]; + } + return -1; +} + +// returns true if a new CC track is found +bool NuPlayer::CCDecoder::extractFromSEI(const sp<ABuffer> &accessUnit) { + int64_t timeUs; + CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs)); + + sp<ABuffer> sei; + if (!accessUnit->meta()->findBuffer("sei", &sei) || sei == NULL) { + return false; + } + + bool trackAdded = false; + + NALBitReader br(sei->data() + 1, sei->size() - 1); + // sei_message() + while (br.atLeastNumBitsLeft(16)) { // at least 16-bit for sei_message() + uint32_t payload_type = 0; + size_t payload_size = 0; + uint8_t last_byte; + + do { + last_byte = br.getBits(8); + payload_type += last_byte; + } while (last_byte == 0xFF); + + do { + last_byte = br.getBits(8); + payload_size += last_byte; + } while (last_byte == 0xFF); + + // sei_payload() + if (payload_type == 4) { + // user_data_registered_itu_t_t35() + + // ATSC A/72: 6.4.2 + uint8_t itu_t_t35_country_code = br.getBits(8); + uint16_t itu_t_t35_provider_code = br.getBits(16); + uint32_t user_identifier = br.getBits(32); + uint8_t user_data_type_code = br.getBits(8); + + payload_size -= 1 + 2 + 4 + 1; + + if (itu_t_t35_country_code == 0xB5 + && itu_t_t35_provider_code == 0x0031 + && user_identifier == 'GA94' + && user_data_type_code == 0x3) { + // MPEG_cc_data() + // ATSC A/53 Part 4: 6.2.3.1 + br.skipBits(1); //process_em_data_flag + bool process_cc_data_flag = br.getBits(1); + br.skipBits(1); //additional_data_flag + size_t cc_count = br.getBits(5); + br.skipBits(8); // em_data; + payload_size -= 2; + + if (process_cc_data_flag) { + AString out; + + sp<ABuffer> ccBuf = new ABuffer(cc_count * sizeof(CCData)); + ccBuf->setRange(0, 0); + + for (size_t i = 0; i < cc_count; i++) { + uint8_t marker = br.getBits(5); + CHECK_EQ(marker, 0x1f); + + bool cc_valid = br.getBits(1); + uint8_t cc_type = br.getBits(2); + // remove odd parity bit + uint8_t cc_data_1 = br.getBits(8) & 0x7f; + uint8_t cc_data_2 = br.getBits(8) & 0x7f; + + if (cc_valid + && (cc_type == 0 || cc_type == 1)) { + CCData cc(cc_type, cc_data_1, cc_data_2); + if (!isNullPad(&cc)) { + size_t channel; + if (cc.getChannel(&channel) && getTrackIndex(channel) < 0) { + mTrackIndices[channel] = mFoundChannels.size(); + mFoundChannels.push_back(channel); + trackAdded = true; + } + memcpy(ccBuf->data() + ccBuf->size(), + (void *)&cc, sizeof(cc)); + ccBuf->setRange(0, ccBuf->size() + sizeof(CCData)); + } + } + } + payload_size -= cc_count * 3; + + mCCMap.add(timeUs, ccBuf); + break; + } + } else { + ALOGV("Malformed SEI payload type 4"); + } + } else { + ALOGV("Unsupported SEI payload type %d", payload_type); + } + + // skipping remaining bits of this payload + br.skipBits(payload_size * 8); + } + + return trackAdded; +} + +sp<ABuffer> NuPlayer::CCDecoder::filterCCBuf( + const sp<ABuffer> &ccBuf, size_t index) { + sp<ABuffer> filteredCCBuf = new ABuffer(ccBuf->size()); + filteredCCBuf->setRange(0, 0); + + size_t cc_count = ccBuf->size() / sizeof(CCData); + const CCData* cc_data = (const CCData*)ccBuf->data(); + for (size_t i = 0; i < cc_count; ++i) { + size_t channel; + if (cc_data[i].getChannel(&channel)) { + mCurrentChannel = channel; + } + if (mCurrentChannel == mFoundChannels[index]) { + memcpy(filteredCCBuf->data() + filteredCCBuf->size(), + (void *)&cc_data[i], sizeof(CCData)); + filteredCCBuf->setRange(0, filteredCCBuf->size() + sizeof(CCData)); + } + } + + return filteredCCBuf; +} + +void NuPlayer::CCDecoder::decode(const sp<ABuffer> &accessUnit) { + if (extractFromSEI(accessUnit)) { + ALOGI("Found CEA-608 track"); + sp<AMessage> msg = mNotify->dup(); + msg->setInt32("what", kWhatTrackAdded); + msg->post(); + } + // TODO: extract CC from other sources +} + +void NuPlayer::CCDecoder::display(int64_t timeUs) { + if (!isTrackValid(mSelectedTrack)) { + ALOGE("Could not find current track(index=%d)", mSelectedTrack); + return; + } + + ssize_t index = mCCMap.indexOfKey(timeUs); + if (index < 0) { + ALOGV("cc for timestamp %" PRId64 " not found", timeUs); + return; + } + + sp<ABuffer> ccBuf = filterCCBuf(mCCMap.valueAt(index), mSelectedTrack); + + if (ccBuf->size() > 0) { +#if 0 + dumpBytePair(ccBuf); +#endif + + ccBuf->meta()->setInt32("trackIndex", mSelectedTrack); + ccBuf->meta()->setInt64("timeUs", timeUs); + ccBuf->meta()->setInt64("durationUs", 0ll); + + sp<AMessage> msg = mNotify->dup(); + msg->setInt32("what", kWhatClosedCaptionData); + msg->setBuffer("buffer", ccBuf); + msg->post(); + } + + // remove all entries before timeUs + mCCMap.removeItemsAt(0, index + 1); +} + +void NuPlayer::CCDecoder::flush() { + mCCMap.clear(); +} + } // namespace android diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h index 78ea74a..cc1bdff 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h @@ -25,20 +25,36 @@ namespace android { struct ABuffer; +struct MediaCodec; +struct MediaBuffer; struct NuPlayer::Decoder : public AHandler { Decoder(const sp<AMessage> ¬ify, const sp<NativeWindowWrapper> &nativeWindow = NULL); - void configure(const sp<AMessage> &format); + virtual void configure(const sp<AMessage> &format); + virtual void init(); - void signalFlush(); - void signalResume(); - void initiateShutdown(); + status_t getInputBuffers(Vector<sp<ABuffer> > *dstBuffers) const; + virtual void signalFlush(const sp<AMessage> &format = NULL); + virtual void signalUpdateFormat(const sp<AMessage> &format); + virtual void signalResume(); + virtual void initiateShutdown(); - bool supportsSeamlessFormatChange(const sp<AMessage> &to) const; + virtual bool supportsSeamlessFormatChange(const sp<AMessage> &to) const; + + enum { + kWhatFillThisBuffer = 'flTB', + kWhatDrainThisBuffer = 'drTB', + kWhatOutputFormatChanged = 'fmtC', + kWhatFlushCompleted = 'flsC', + kWhatShutdownCompleted = 'shDC', + kWhatEOS = 'eos ', + kWhatError = 'err ', + }; protected: + virtual ~Decoder(); virtual void onMessageReceived(const sp<AMessage> &msg); @@ -46,25 +62,86 @@ protected: private: enum { kWhatCodecNotify = 'cdcN', + kWhatConfigure = 'conf', + kWhatGetInputBuffers = 'gInB', + kWhatInputBufferFilled = 'inpF', + kWhatRenderBuffer = 'rndr', + kWhatFlush = 'flus', + kWhatShutdown = 'shuD', + kWhatUpdateFormat = 'uFmt', }; sp<AMessage> mNotify; sp<NativeWindowWrapper> mNativeWindow; - sp<AMessage> mFormat; - sp<ACodec> mCodec; + sp<AMessage> mInputFormat; + sp<AMessage> mOutputFormat; + sp<MediaCodec> mCodec; sp<ALooper> mCodecLooper; + sp<ALooper> mDecoderLooper; + + Vector<sp<ABuffer> > mInputBuffers; + Vector<sp<ABuffer> > mOutputBuffers; + Vector<sp<ABuffer> > mCSDsForCurrentFormat; + Vector<sp<ABuffer> > mCSDsToSubmit; + Vector<bool> mInputBufferIsDequeued; + Vector<MediaBuffer *> mMediaBuffers; + + void handleError(int32_t err); + bool handleAnInputBuffer(); + bool handleAnOutputBuffer(); + + void releaseAndResetMediaBuffers(); + void requestCodecNotification(); + bool isStaleReply(const sp<AMessage> &msg); + + void onConfigure(const sp<AMessage> &format); + void onFlush(); + void onResume(); + void onInputBufferFilled(const sp<AMessage> &msg); + void onRenderBuffer(const sp<AMessage> &msg); + void onShutdown(); + + int32_t mBufferGeneration; + bool mPaused; + AString mComponentName; - Vector<sp<ABuffer> > mCSD; - size_t mCSDIndex; + bool supportsSeamlessAudioFormatChange(const sp<AMessage> &targetFormat) const; + void rememberCodecSpecificData(const sp<AMessage> &format); + + DISALLOW_EVIL_CONSTRUCTORS(Decoder); +}; - sp<AMessage> makeFormat(const sp<MetaData> &meta); +struct NuPlayer::CCDecoder : public RefBase { + enum { + kWhatClosedCaptionData, + kWhatTrackAdded, + }; - void onFillThisBuffer(const sp<AMessage> &msg); + CCDecoder(const sp<AMessage> ¬ify); - bool supportsSeamlessAudioFormatChange(const sp<AMessage> &targetFormat) const; + size_t getTrackCount() const; + sp<AMessage> getTrackInfo(size_t index) const; + status_t selectTrack(size_t index, bool select); + bool isSelected() const; + void decode(const sp<ABuffer> &accessUnit); + void display(int64_t timeUs); + void flush(); - DISALLOW_EVIL_CONSTRUCTORS(Decoder); +private: + sp<AMessage> mNotify; + KeyedVector<int64_t, sp<ABuffer> > mCCMap; + size_t mCurrentChannel; + int32_t mSelectedTrack; + int32_t mTrackIndices[4]; + Vector<size_t> mFoundChannels; + + bool isTrackValid(size_t index) const; + int32_t getTrackIndex(size_t channel) const; + bool extractFromSEI(const sp<ABuffer> &accessUnit); + sp<ABuffer> filterCCBuf(const sp<ABuffer> &ccBuf, size_t index); + + DISALLOW_EVIL_CONSTRUCTORS(CCDecoder); }; } // namespace android diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoderPassThrough.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDecoderPassThrough.cpp new file mode 100644 index 0000000..f7aacdd --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoderPassThrough.cpp @@ -0,0 +1,266 @@ +/* + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "NuPlayerDecoderPassThrough" +#include <utils/Log.h> +#include <inttypes.h> + +#include "NuPlayerDecoderPassThrough.h" + +#include <media/ICrypto.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> + +namespace android { + +static const size_t kMaxCachedBytes = 200000; +// The buffers will contain a bit less than kAggregateBufferSizeBytes. +// So we can start off with just enough buffers to keep the cache full. +static const size_t kMaxPendingBuffers = 1 + (kMaxCachedBytes / NuPlayer::kAggregateBufferSizeBytes); + +NuPlayer::DecoderPassThrough::DecoderPassThrough( + const sp<AMessage> ¬ify) + : Decoder(notify), + mNotify(notify), + mBufferGeneration(0), + mReachedEOS(true), + mPendingBuffersToFill(0), + mPendingBuffersToDrain(0), + mCachedBytes(0), + mComponentName("pass through decoder") { + mDecoderLooper = new ALooper; + mDecoderLooper->setName("NuPlayerDecoderPassThrough"); + mDecoderLooper->start(false, false, ANDROID_PRIORITY_AUDIO); +} + +NuPlayer::DecoderPassThrough::~DecoderPassThrough() { +} + +void NuPlayer::DecoderPassThrough::configure(const sp<AMessage> &format) { + sp<AMessage> msg = new AMessage(kWhatConfigure, id()); + msg->setMessage("format", format); + msg->post(); +} + +void NuPlayer::DecoderPassThrough::init() { + mDecoderLooper->registerHandler(this); +} + +void NuPlayer::DecoderPassThrough::signalFlush() { + (new AMessage(kWhatFlush, id()))->post(); +} + +void NuPlayer::DecoderPassThrough::signalResume() { + (new AMessage(kWhatResume, id()))->post(); +} + +void NuPlayer::DecoderPassThrough::initiateShutdown() { + (new AMessage(kWhatShutdown, id()))->post(); +} + +bool NuPlayer::DecoderPassThrough::supportsSeamlessFormatChange( + const sp<AMessage> & /* targetFormat */) const { + return true; +} + +void NuPlayer::DecoderPassThrough::onConfigure(const sp<AMessage> &format) { + ALOGV("[%s] onConfigure", mComponentName.c_str()); + mCachedBytes = 0; + mPendingBuffersToFill = 0; + mPendingBuffersToDrain = 0; + mReachedEOS = false; + ++mBufferGeneration; + + requestMaxBuffers(); + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatOutputFormatChanged); + notify->setMessage("format", format); + notify->post(); +} + +bool NuPlayer::DecoderPassThrough::isStaleReply(const sp<AMessage> &msg) { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + return generation != mBufferGeneration; +} + +bool NuPlayer::DecoderPassThrough::requestABuffer() { + if (mCachedBytes >= kMaxCachedBytes) { + ALOGV("[%s] mCachedBytes = %zu", + mComponentName.c_str(), mCachedBytes); + return false; + } + if (mReachedEOS) { + ALOGV("[%s] reached EOS", mComponentName.c_str()); + return false; + } + + sp<AMessage> reply = new AMessage(kWhatInputBufferFilled, id()); + reply->setInt32("generation", mBufferGeneration); + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatFillThisBuffer); + notify->setMessage("reply", reply); + notify->post(); + mPendingBuffersToFill++; + ALOGV("requestABuffer: #ToFill = %zu, #ToDrain = %zu", mPendingBuffersToFill, + mPendingBuffersToDrain); + + return true; +} + +void android::NuPlayer::DecoderPassThrough::onInputBufferFilled( + const sp<AMessage> &msg) { + --mPendingBuffersToFill; + if (mReachedEOS) { + return; + } + + sp<ABuffer> buffer; + msg->findBuffer("buffer", &buffer); + if (buffer == NULL) { + mReachedEOS = true; + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatEOS); + notify->setInt32("err", ERROR_END_OF_STREAM); + notify->post(); + return; + } + + mCachedBytes += buffer->size(); + + sp<AMessage> reply = new AMessage(kWhatBufferConsumed, id()); + reply->setInt32("generation", mBufferGeneration); + reply->setInt32("size", buffer->size()); + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatDrainThisBuffer); + notify->setBuffer("buffer", buffer); + notify->setMessage("reply", reply); + notify->post(); + ++mPendingBuffersToDrain; + ALOGV("onInputBufferFilled: #ToFill = %zu, #ToDrain = %zu, cachedBytes = %zu", + mPendingBuffersToFill, mPendingBuffersToDrain, mCachedBytes); +} + +void NuPlayer::DecoderPassThrough::onBufferConsumed(int32_t size) { + --mPendingBuffersToDrain; + mCachedBytes -= size; + ALOGV("onBufferConsumed: #ToFill = %zu, #ToDrain = %zu, cachedBytes = %zu", + mPendingBuffersToFill, mPendingBuffersToDrain, mCachedBytes); + requestABuffer(); +} + +void NuPlayer::DecoderPassThrough::onFlush() { + ++mBufferGeneration; + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatFlushCompleted); + notify->post(); + mPendingBuffersToFill = 0; + mPendingBuffersToDrain = 0; + mCachedBytes = 0; + mReachedEOS = false; +} + +void NuPlayer::DecoderPassThrough::requestMaxBuffers() { + for (size_t i = 0; i < kMaxPendingBuffers; i++) { + if (!requestABuffer()) { + break; + } + } +} + +void NuPlayer::DecoderPassThrough::onShutdown() { + ++mBufferGeneration; + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatShutdownCompleted); + notify->post(); + mReachedEOS = true; +} + +void NuPlayer::DecoderPassThrough::onMessageReceived(const sp<AMessage> &msg) { + ALOGV("[%s] onMessage: %s", mComponentName.c_str(), + msg->debugString().c_str()); + + switch (msg->what()) { + case kWhatConfigure: + { + sp<AMessage> format; + CHECK(msg->findMessage("format", &format)); + onConfigure(format); + break; + } + + case kWhatRequestABuffer: + { + if (!isStaleReply(msg)) { + requestABuffer(); + } + + break; + } + + case kWhatInputBufferFilled: + { + if (!isStaleReply(msg)) { + onInputBufferFilled(msg); + } + break; + } + + case kWhatBufferConsumed: + { + if (!isStaleReply(msg)) { + int32_t size; + CHECK(msg->findInt32("size", &size)); + onBufferConsumed(size); + } + break; + } + + case kWhatFlush: + { + onFlush(); + break; + } + + case kWhatResume: + { + requestMaxBuffers(); + break; + } + + case kWhatShutdown: + { + onShutdown(); + break; + } + + default: + TRESPASS(); + break; + } +} + +} // namespace android diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoderPassThrough.h b/media/libmediaplayerservice/nuplayer/NuPlayerDecoderPassThrough.h new file mode 100644 index 0000000..fb20257 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoderPassThrough.h @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#ifndef NUPLAYER_DECODER_PASS_THROUGH_H_ + +#define NUPLAYER_DECODER_PASS_THROUGH_H_ + +#include "NuPlayer.h" + +#include "NuPlayerDecoder.h" + +namespace android { + +struct NuPlayer::DecoderPassThrough : public Decoder { + DecoderPassThrough(const sp<AMessage> ¬ify); + + virtual void configure(const sp<AMessage> &format); + virtual void init(); + + virtual void signalFlush(); + virtual void signalResume(); + virtual void initiateShutdown(); + + bool supportsSeamlessFormatChange(const sp<AMessage> &to) const; + +protected: + + virtual ~DecoderPassThrough(); + + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + enum { + kWhatRequestABuffer = 'reqB', + kWhatConfigure = 'conf', + kWhatInputBufferFilled = 'inpF', + kWhatBufferConsumed = 'bufC', + kWhatFlush = 'flus', + kWhatShutdown = 'shuD', + }; + + sp<AMessage> mNotify; + sp<ALooper> mDecoderLooper; + + /** Returns true if a buffer was requested. + * Returns false if at EOS or cache already full. + */ + bool requestABuffer(); + bool isStaleReply(const sp<AMessage> &msg); + + void onConfigure(const sp<AMessage> &format); + void onFlush(); + void onInputBufferFilled(const sp<AMessage> &msg); + void onBufferConsumed(int32_t size); + void requestMaxBuffers(); + void onShutdown(); + + int32_t mBufferGeneration; + bool mReachedEOS; + // TODO mPendingBuffersToFill and mPendingBuffersToDrain are only for + // debugging. They can be removed when the power investigation is done. + size_t mPendingBuffersToFill; + size_t mPendingBuffersToDrain; + size_t mCachedBytes; + AString mComponentName; + + DISALLOW_EVIL_CONSTRUCTORS(DecoderPassThrough); +}; + +} // namespace android + +#endif // NUPLAYER_DECODER_PASS_THROUGH_H_ diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp index b9651a1..7ec9876 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp @@ -37,11 +37,15 @@ NuPlayerDriver::NuPlayerDriver() mSetSurfaceInProgress(false), mDurationUs(-1), mPositionUs(-1), + mNotifyTimeRealUs(-1), + mPauseStartedTimeUs(-1), mNumFramesTotal(0), mNumFramesDropped(0), mLooper(new ALooper), mPlayerFlags(0), mAtEOS(false), + mLooping(false), + mAutoLoop(false), mStartupSeekTimeUs(-1) { mLooper->setName("NuPlayerDriver Looper"); @@ -71,16 +75,19 @@ status_t NuPlayerDriver::setUID(uid_t uid) { } status_t NuPlayerDriver::setDataSource( - const char *url, const KeyedVector<String8, String8> *headers) { + const sp<IMediaHTTPService> &httpService, + const char *url, + const KeyedVector<String8, String8> *headers) { Mutex::Autolock autoLock(mLock); + ALOGV("setDataSource: url=%s", url); if (mState != STATE_IDLE) { return INVALID_OPERATION; } mState = STATE_SET_DATASOURCE_PENDING; - mPlayer->setDataSourceAsync(url, headers); + mPlayer->setDataSourceAsync(httpService, url, headers); while (mState == STATE_SET_DATASOURCE_PENDING) { mCondition.wait(mLock); @@ -92,6 +99,7 @@ status_t NuPlayerDriver::setDataSource( status_t NuPlayerDriver::setDataSource(int fd, int64_t offset, int64_t length) { Mutex::Autolock autoLock(mLock); + ALOGV("setDataSource: fd=%d", fd); if (mState != STATE_IDLE) { return INVALID_OPERATION; } @@ -110,6 +118,7 @@ status_t NuPlayerDriver::setDataSource(int fd, int64_t offset, int64_t length) { status_t NuPlayerDriver::setDataSource(const sp<IStreamSource> &source) { Mutex::Autolock autoLock(mLock); + ALOGV("setDataSource: stream source"); if (mState != STATE_IDLE) { return INVALID_OPERATION; } @@ -172,6 +181,16 @@ status_t NuPlayerDriver::prepare_l() { mCondition.wait(mLock); } return (mState == STATE_PREPARED) ? OK : UNKNOWN_ERROR; + case STATE_STOPPED: + // this is really just paused. handle as seek to start + mAtEOS = false; + mState = STATE_STOPPED_AND_PREPARING; + mIsAsyncPrepare = false; + mPlayer->seekToAsync(0); + while (mState == STATE_STOPPED_AND_PREPARING) { + mCondition.wait(mLock); + } + return (mState == STATE_STOPPED_AND_PREPARED) ? OK : UNKNOWN_ERROR; default: return INVALID_OPERATION; }; @@ -186,6 +205,13 @@ status_t NuPlayerDriver::prepareAsync() { mIsAsyncPrepare = true; mPlayer->prepareAsync(); return OK; + case STATE_STOPPED: + // this is really just paused. handle as seek to start + mAtEOS = false; + mState = STATE_STOPPED_AND_PREPARING; + mIsAsyncPrepare = true; + mPlayer->seekToAsync(0); + return OK; default: return INVALID_OPERATION; }; @@ -215,7 +241,7 @@ status_t NuPlayerDriver::start() { if (mStartupSeekTimeUs >= 0) { if (mStartupSeekTimeUs == 0) { - notifySeekComplete(); + notifySeekComplete_l(); } else { mPlayer->seekToAsync(mStartupSeekTimeUs); } @@ -226,11 +252,34 @@ status_t NuPlayerDriver::start() { } case STATE_RUNNING: + { + if (mAtEOS) { + mPlayer->seekToAsync(0); + mAtEOS = false; + mPositionUs = -1; + } break; + } case STATE_PAUSED: + case STATE_STOPPED_AND_PREPARED: { - mPlayer->resume(); + if (mAtEOS) { + mPlayer->seekToAsync(0); + mAtEOS = false; + mPlayer->resume(); + mPositionUs = -1; + } else { + mPlayer->resume(); + if (mNotifyTimeRealUs != -1) { + // Pause time must be set if here by setPauseStartedTimeIfNeeded(). + //CHECK(mPauseStartedTimeUs != -1); + + // if no seek occurs, adjust our notify time so that getCurrentPosition() + // is continuous if read immediately after calling start(). + mNotifyTimeRealUs += ALooper::GetNowUs() - mPauseStartedTimeUs; + } + } break; } @@ -239,12 +288,37 @@ status_t NuPlayerDriver::start() { } mState = STATE_RUNNING; + mPauseStartedTimeUs = -1; return OK; } status_t NuPlayerDriver::stop() { - return pause(); + Mutex::Autolock autoLock(mLock); + + switch (mState) { + case STATE_RUNNING: + mPlayer->pause(); + // fall through + + case STATE_PAUSED: + mState = STATE_STOPPED; + notifyListener_l(MEDIA_STOPPED); + break; + + case STATE_PREPARED: + case STATE_STOPPED: + case STATE_STOPPED_AND_PREPARING: + case STATE_STOPPED_AND_PREPARED: + mState = STATE_STOPPED; + break; + + default: + return INVALID_OPERATION; + } + setPauseStartedTimeIfNeeded(); + + return OK; } status_t NuPlayerDriver::pause() { @@ -256,7 +330,9 @@ status_t NuPlayerDriver::pause() { return OK; case STATE_RUNNING: - notifyListener(MEDIA_PAUSED); + setPauseStartedTimeIfNeeded(); + mState = STATE_PAUSED; + notifyListener_l(MEDIA_PAUSED); mPlayer->pause(); break; @@ -264,8 +340,6 @@ status_t NuPlayerDriver::pause() { return INVALID_OPERATION; } - mState = STATE_PAUSED; - return OK; } @@ -282,6 +356,10 @@ status_t NuPlayerDriver::seekTo(int msec) { case STATE_PREPARED: { mStartupSeekTimeUs = seekTimeUs; + // pretend that the seek completed. It will actually happen when starting playback. + // TODO: actually perform the seek here, so the player is ready to go at the new + // location + notifySeekComplete_l(); break; } @@ -290,7 +368,7 @@ status_t NuPlayerDriver::seekTo(int msec) { { mAtEOS = false; // seeks can take a while, so we essentially paused - notifyListener(MEDIA_PAUSED); + notifyListener_l(MEDIA_PAUSED); mPlayer->seekToAsync(seekTimeUs); break; } @@ -299,6 +377,8 @@ status_t NuPlayerDriver::seekTo(int msec) { return INVALID_OPERATION; } + mPositionUs = seekTimeUs; + mNotifyTimeRealUs = -1; return OK; } @@ -306,11 +386,36 @@ status_t NuPlayerDriver::getCurrentPosition(int *msec) { Mutex::Autolock autoLock(mLock); if (mPositionUs < 0) { + // mPositionUs is the media time. + // It is negative under these cases + // (1) == -1 after reset, or very first playback, no stream notification yet. + // (2) == -1 start after end of stream, no stream notification yet. + // (3) == large negative # after ~292,471 years of continuous playback. + + //CHECK_EQ(mPositionUs, -1); *msec = 0; + } else if (mNotifyTimeRealUs == -1) { + // A seek has occurred just occurred, no stream notification yet. + // mPositionUs (>= 0) is the new media position. + *msec = mPositionUs / 1000; } else { - *msec = (mPositionUs + 500ll) / 1000; + // mPosition must be valid (i.e. >= 0) by the first check above. + // We're either playing or have pause time set: mPauseStartedTimeUs is >= 0 + //LOG_ALWAYS_FATAL_IF( + // !isPlaying() && mPauseStartedTimeUs < 0, + // "Player in non-playing mState(%d) and mPauseStartedTimeUs(%lld) < 0", + // mState, (long long)mPauseStartedTimeUs); + ALOG_ASSERT(mNotifyTimeRealUs >= 0); + int64_t nowUs = + (isPlaying() ? ALooper::GetNowUs() : mPauseStartedTimeUs); + *msec = (mPositionUs + nowUs - mNotifyTimeRealUs + 500ll) / 1000; + // It is possible for *msec to be negative if the media position is > 596 hours. + // but we turn on this checking in NDEBUG == 0 mode. + ALOG_ASSERT(*msec >= 0); + ALOGV("getCurrentPosition nowUs(%lld)", (long long)nowUs); } - + ALOGV("getCurrentPosition returning(%d) mPositionUs(%lld) mNotifyRealTimeUs(%lld)", + *msec, (long long)mPositionUs, (long long)mNotifyTimeRealUs); return OK; } @@ -341,7 +446,7 @@ status_t NuPlayerDriver::reset() { { CHECK(mIsAsyncPrepare); - notifyListener(MEDIA_PREPARED); + notifyListener_l(MEDIA_PREPARED); break; } @@ -349,7 +454,9 @@ status_t NuPlayerDriver::reset() { break; } - notifyListener(MEDIA_STOPPED); + if (mState != STATE_STOPPED) { + notifyListener_l(MEDIA_STOPPED); + } mState = STATE_RESET_IN_PROGRESS; mPlayer->resetAsync(); @@ -361,12 +468,14 @@ status_t NuPlayerDriver::reset() { mDurationUs = -1; mPositionUs = -1; mStartupSeekTimeUs = -1; + mLooping = false; return OK; } -status_t NuPlayerDriver::setLooping(int /* loop */) { - return INVALID_OPERATION; +status_t NuPlayerDriver::setLooping(int loop) { + mLooping = loop != 0; + return OK; } player_type NuPlayerDriver::playerType() { @@ -410,6 +519,12 @@ status_t NuPlayerDriver::invoke(const Parcel &request, Parcel *reply) { return mPlayer->selectTrack(trackIndex, false /* select */); } + case INVOKE_ID_GET_SELECTED_TRACK: + { + int32_t type = request.readInt32(); + return mPlayer->getSelectedTrack(type, reply); + } + default: { return INVALID_OPERATION; @@ -419,6 +534,7 @@ status_t NuPlayerDriver::invoke(const Parcel &request, Parcel *reply) { void NuPlayerDriver::setAudioSink(const sp<AudioSink> &audioSink) { mPlayer->setAudioSink(audioSink); + mAudioSink = audioSink; } status_t NuPlayerDriver::setParameter( @@ -481,11 +597,32 @@ void NuPlayerDriver::notifyDuration(int64_t durationUs) { void NuPlayerDriver::notifyPosition(int64_t positionUs) { Mutex::Autolock autoLock(mLock); - mPositionUs = positionUs; + if (isPlaying()) { + mPositionUs = positionUs; + mNotifyTimeRealUs = ALooper::GetNowUs(); + } } void NuPlayerDriver::notifySeekComplete() { - notifyListener(MEDIA_SEEK_COMPLETE); + Mutex::Autolock autoLock(mLock); + notifySeekComplete_l(); +} + +void NuPlayerDriver::notifySeekComplete_l() { + bool wasSeeking = true; + if (mState == STATE_STOPPED_AND_PREPARING) { + wasSeeking = false; + mState = STATE_STOPPED_AND_PREPARED; + mCondition.broadcast(); + if (!mIsAsyncPrepare) { + // if we are preparing synchronously, no need to notify listener + return; + } + } else if (mState == STATE_STOPPED) { + // no need to notify listener + return; + } + notifyListener_l(wasSeeking ? MEDIA_SEEK_COMPLETE : MEDIA_PREPARED); } void NuPlayerDriver::notifyFrameStats( @@ -517,11 +654,42 @@ status_t NuPlayerDriver::dump( void NuPlayerDriver::notifyListener( int msg, int ext1, int ext2, const Parcel *in) { - if (msg == MEDIA_PLAYBACK_COMPLETE || msg == MEDIA_ERROR) { - mAtEOS = true; + Mutex::Autolock autoLock(mLock); + notifyListener_l(msg, ext1, ext2, in); +} + +void NuPlayerDriver::notifyListener_l( + int msg, int ext1, int ext2, const Parcel *in) { + switch (msg) { + case MEDIA_PLAYBACK_COMPLETE: + { + if (mState != STATE_RESET_IN_PROGRESS) { + if (mLooping || (mAutoLoop + && (mAudioSink == NULL || mAudioSink->realtime()))) { + mPlayer->seekToAsync(0); + break; + } + + mPlayer->pause(); + mState = STATE_PAUSED; + } + // fall through + } + + case MEDIA_ERROR: + { + mAtEOS = true; + setPauseStartedTimeIfNeeded(); + break; + } + + default: + break; } + mLock.unlock(); sendEvent(msg, ext1, ext2, in); + mLock.lock(); } void NuPlayerDriver::notifySetDataSourceCompleted(status_t err) { @@ -550,15 +718,24 @@ void NuPlayerDriver::notifyPrepareCompleted(status_t err) { mAsyncResult = err; if (err == OK) { + // update state before notifying client, so that if client calls back into NuPlayerDriver + // in response, NuPlayerDriver has the right state + mState = STATE_PREPARED; if (mIsAsyncPrepare) { - notifyListener(MEDIA_PREPARED); + notifyListener_l(MEDIA_PREPARED); } - mState = STATE_PREPARED; } else { + mState = STATE_UNPREPARED; if (mIsAsyncPrepare) { - notifyListener(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err); + notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err); } - mState = STATE_UNPREPARED; + } + + sp<MetaData> meta = mPlayer->getFileMeta(); + int32_t loop; + if (meta != NULL + && meta->findInt32(kKeyAutoLoop, &loop) && loop != 0) { + mAutoLoop = true; } mCondition.broadcast(); @@ -570,4 +747,10 @@ void NuPlayerDriver::notifyFlagsChanged(uint32_t flags) { mPlayerFlags = flags; } +void NuPlayerDriver::setPauseStartedTimeIfNeeded() { + if (mPauseStartedTimeUs == -1) { + mPauseStartedTimeUs = ALooper::GetNowUs(); + } +} + } // namespace android diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h index 99f72a6..f2bd431 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h @@ -31,7 +31,9 @@ struct NuPlayerDriver : public MediaPlayerInterface { virtual status_t setUID(uid_t uid); virtual status_t setDataSource( - const char *url, const KeyedVector<String8, String8> *headers); + const sp<IMediaHTTPService> &httpService, + const char *url, + const KeyedVector<String8, String8> *headers); virtual status_t setDataSource(int fd, int64_t offset, int64_t length); @@ -68,6 +70,7 @@ struct NuPlayerDriver : public MediaPlayerInterface { void notifyDuration(int64_t durationUs); void notifyPosition(int64_t positionUs); void notifySeekComplete(); + void notifySeekComplete_l(); void notifyFrameStats(int64_t numFramesTotal, int64_t numFramesDropped); void notifyListener(int msg, int ext1 = 0, int ext2 = 0, const Parcel *in = NULL); void notifyFlagsChanged(uint32_t flags); @@ -85,6 +88,9 @@ private: STATE_RUNNING, STATE_PAUSED, STATE_RESET_IN_PROGRESS, + STATE_STOPPED, // equivalent to PAUSED + STATE_STOPPED_AND_PREPARING, // equivalent to PAUSED, but seeking + STATE_STOPPED_AND_PREPARED, // equivalent to PAUSED, but seek complete }; mutable Mutex mLock; @@ -100,19 +106,26 @@ private: bool mSetSurfaceInProgress; int64_t mDurationUs; int64_t mPositionUs; + int64_t mNotifyTimeRealUs; + int64_t mPauseStartedTimeUs; int64_t mNumFramesTotal; int64_t mNumFramesDropped; // <<< sp<ALooper> mLooper; sp<NuPlayer> mPlayer; + sp<AudioSink> mAudioSink; uint32_t mPlayerFlags; bool mAtEOS; + bool mLooping; + bool mAutoLoop; int64_t mStartupSeekTimeUs; status_t prepare_l(); + void notifyListener_l(int msg, int ext1 = 0, int ext2 = 0, const Parcel *in = NULL); + void setPauseStartedTimeIfNeeded(); DISALLOW_EVIL_CONSTRUCTORS(NuPlayerDriver); }; diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp index bf5271e..a8c8818 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp +++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp @@ -20,11 +20,15 @@ #include "NuPlayerRenderer.h" -#include "SoftwareRenderer.h" - #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> + +#include <VideoFrameScheduler.h> + +#include <inttypes.h> namespace android { @@ -36,7 +40,6 @@ NuPlayer::Renderer::Renderer( const sp<AMessage> ¬ify, uint32_t flags) : mAudioSink(sink), - mSoftRenderer(NULL), mNotify(notify), mFlags(flags), mNumFramesWritten(0), @@ -44,6 +47,7 @@ NuPlayer::Renderer::Renderer( mDrainVideoQueuePending(false), mAudioQueueGeneration(0), mVideoQueueGeneration(0), + mFirstAnchorTimeMediaUs(-1), mAnchorTimeMediaUs(-1), mAnchorTimeRealUs(-1), mFlushingAudio(false), @@ -52,6 +56,7 @@ NuPlayer::Renderer::Renderer( mHasVideo(false), mSyncQueues(false), mPaused(false), + mVideoSampleReceived(false), mVideoRenderingStarted(false), mVideoRenderingStartGeneration(0), mAudioRenderingStartGeneration(0), @@ -60,12 +65,11 @@ NuPlayer::Renderer::Renderer( } NuPlayer::Renderer::~Renderer() { - delete mSoftRenderer; -} - -void NuPlayer::Renderer::setSoftRenderer(SoftwareRenderer *softRenderer) { - delete mSoftRenderer; - mSoftRenderer = softRenderer; + if (offloadingAudio()) { + mAudioSink->stop(); + mAudioSink->flush(); + mAudioSink->close(); + } } void NuPlayer::Renderer::queueBuffer( @@ -92,10 +96,14 @@ void NuPlayer::Renderer::flush(bool audio) { { Mutex::Autolock autoLock(mFlushLock); if (audio) { - CHECK(!mFlushingAudio); + if (mFlushingAudio) { + return; + } mFlushingAudio = true; } else { - CHECK(!mFlushingVideo); + if (mFlushingVideo) { + return; + } mFlushingVideo = true; } } @@ -106,13 +114,23 @@ void NuPlayer::Renderer::flush(bool audio) { } void NuPlayer::Renderer::signalTimeDiscontinuity() { + Mutex::Autolock autoLock(mLock); // CHECK(mAudioQueue.empty()); // CHECK(mVideoQueue.empty()); + mFirstAnchorTimeMediaUs = -1; mAnchorTimeMediaUs = -1; mAnchorTimeRealUs = -1; mSyncQueues = false; } +void NuPlayer::Renderer::signalAudioSinkChanged() { + (new AMessage(kWhatAudioSinkChanged, id()))->post(); +} + +void NuPlayer::Renderer::signalDisableOffloadAudio() { + (new AMessage(kWhatDisableOffloadAudio, id()))->post(); +} + void NuPlayer::Renderer::pause() { (new AMessage(kWhatPause, id()))->post(); } @@ -121,8 +139,20 @@ void NuPlayer::Renderer::resume() { (new AMessage(kWhatResume, id()))->post(); } +void NuPlayer::Renderer::setVideoFrameRate(float fps) { + sp<AMessage> msg = new AMessage(kWhatSetVideoFrameRate, id()); + msg->setFloat("frame-rate", fps); + msg->post(); +} + void NuPlayer::Renderer::onMessageReceived(const sp<AMessage> &msg) { switch (msg->what()) { + case kWhatStopAudioSink: + { + mAudioSink->stop(); + break; + } + case kWhatDrainAudioQueue: { int32_t generation; @@ -149,7 +179,10 @@ void NuPlayer::Renderer::onMessageReceived(const sp<AMessage> &msg) { // Let's give it more data after about half that time // has elapsed. - postDrainAudioQueue(delayUs / 2); + // kWhatDrainAudioQueue is used for non-offloading mode, + // and mLock is used only for offloading mode. Therefore, + // no need to acquire mLock here. + postDrainAudioQueue_l(delayUs / 2); } break; } @@ -194,6 +227,12 @@ void NuPlayer::Renderer::onMessageReceived(const sp<AMessage> &msg) { break; } + case kWhatDisableOffloadAudio: + { + onDisableOffloadAudio(); + break; + } + case kWhatPause: { onPause(); @@ -206,14 +245,29 @@ void NuPlayer::Renderer::onMessageReceived(const sp<AMessage> &msg) { break; } + case kWhatSetVideoFrameRate: + { + float fps; + CHECK(msg->findFloat("frame-rate", &fps)); + onSetVideoFrameRate(fps); + break; + } + + case kWhatAudioOffloadTearDown: + { + onAudioOffloadTearDown(); + break; + } + default: TRESPASS(); break; } } -void NuPlayer::Renderer::postDrainAudioQueue(int64_t delayUs) { - if (mDrainAudioQueuePending || mSyncQueues || mPaused) { +void NuPlayer::Renderer::postDrainAudioQueue_l(int64_t delayUs) { + if (mDrainAudioQueuePending || mSyncQueues || mPaused + || offloadingAudio()) { return; } @@ -227,10 +281,6 @@ void NuPlayer::Renderer::postDrainAudioQueue(int64_t delayUs) { msg->post(delayUs); } -void NuPlayer::Renderer::signalAudioSinkChanged() { - (new AMessage(kWhatAudioSinkChanged, id()))->post(); -} - void NuPlayer::Renderer::prepareForMediaRenderingStart() { mAudioRenderingStartGeneration = mAudioQueueGeneration; mVideoRenderingStartGeneration = mVideoQueueGeneration; @@ -248,6 +298,103 @@ void NuPlayer::Renderer::notifyIfMediaRenderingStarted() { } } +// static +size_t NuPlayer::Renderer::AudioSinkCallback( + MediaPlayerBase::AudioSink * /* audioSink */, + void *buffer, + size_t size, + void *cookie, + MediaPlayerBase::AudioSink::cb_event_t event) { + NuPlayer::Renderer *me = (NuPlayer::Renderer *)cookie; + + switch (event) { + case MediaPlayerBase::AudioSink::CB_EVENT_FILL_BUFFER: + { + return me->fillAudioBuffer(buffer, size); + break; + } + + case MediaPlayerBase::AudioSink::CB_EVENT_STREAM_END: + { + me->notifyEOS(true /* audio */, ERROR_END_OF_STREAM); + break; + } + + case MediaPlayerBase::AudioSink::CB_EVENT_TEAR_DOWN: + { + me->notifyAudioOffloadTearDown(); + break; + } + } + + return 0; +} + +size_t NuPlayer::Renderer::fillAudioBuffer(void *buffer, size_t size) { + Mutex::Autolock autoLock(mLock); + + if (!offloadingAudio() || mPaused) { + return 0; + } + + bool hasEOS = false; + + size_t sizeCopied = 0; + bool firstEntry = true; + while (sizeCopied < size && !mAudioQueue.empty()) { + QueueEntry *entry = &*mAudioQueue.begin(); + + if (entry->mBuffer == NULL) { // EOS + hasEOS = true; + mAudioQueue.erase(mAudioQueue.begin()); + entry = NULL; + break; + } + + if (firstEntry && entry->mOffset == 0) { + firstEntry = false; + int64_t mediaTimeUs; + CHECK(entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs)); + ALOGV("rendering audio at media time %.2f secs", mediaTimeUs / 1E6); + if (mFirstAnchorTimeMediaUs == -1) { + mFirstAnchorTimeMediaUs = mediaTimeUs; + } + + int64_t nowUs = ALooper::GetNowUs(); + mAnchorTimeMediaUs = + mFirstAnchorTimeMediaUs + getPlayedOutAudioDurationUs(nowUs); + mAnchorTimeRealUs = nowUs; + + notifyPosition(); + } + + size_t copy = entry->mBuffer->size() - entry->mOffset; + size_t sizeRemaining = size - sizeCopied; + if (copy > sizeRemaining) { + copy = sizeRemaining; + } + + memcpy((char *)buffer + sizeCopied, + entry->mBuffer->data() + entry->mOffset, + copy); + + entry->mOffset += copy; + if (entry->mOffset == entry->mBuffer->size()) { + entry->mNotifyConsumed->post(); + mAudioQueue.erase(mAudioQueue.begin()); + entry = NULL; + } + sizeCopied += copy; + notifyIfMediaRenderingStarted(); + } + + if (hasEOS) { + (new AMessage(kWhatStopAudioSink, id()))->post(); + } + + return sizeCopied; +} + bool NuPlayer::Renderer::onDrainAudioQueue() { uint32_t numFramesPlayed; if (mAudioSink->getPosition(&numFramesPlayed) != OK) { @@ -274,8 +421,11 @@ bool NuPlayer::Renderer::onDrainAudioQueue() { if (entry->mBuffer == NULL) { // EOS - - notifyEOS(true /* audio */, entry->mFinalResult); + int64_t postEOSDelayUs = 0; + if (mAudioSink->needsTrailingPadding()) { + postEOSDelayUs = getPendingAudioPlayoutDurationUs(ALooper::GetNowUs()); + } + notifyEOS(true /* audio */, entry->mFinalResult, postEOSDelayUs); mAudioQueue.erase(mAudioQueue.begin()); entry = NULL; @@ -285,26 +435,16 @@ bool NuPlayer::Renderer::onDrainAudioQueue() { if (entry->mOffset == 0) { int64_t mediaTimeUs; CHECK(entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs)); - ALOGV("rendering audio at media time %.2f secs", mediaTimeUs / 1E6); - + if (mFirstAnchorTimeMediaUs == -1) { + mFirstAnchorTimeMediaUs = mediaTimeUs; + } mAnchorTimeMediaUs = mediaTimeUs; - uint32_t numFramesPlayed; - CHECK_EQ(mAudioSink->getPosition(&numFramesPlayed), (status_t)OK); + int64_t nowUs = ALooper::GetNowUs(); + mAnchorTimeRealUs = nowUs + getPendingAudioPlayoutDurationUs(nowUs); - uint32_t numFramesPendingPlayout = - mNumFramesWritten - numFramesPlayed; - - int64_t realTimeOffsetUs = - (mAudioSink->latency() / 2 /* XXX */ - + numFramesPendingPlayout - * mAudioSink->msecsPerFrame()) * 1000ll; - - // ALOGI("realTimeOffsetUs = %lld us", realTimeOffsetUs); - - mAnchorTimeRealUs = - ALooper::GetNowUs() + realTimeOffsetUs; + notifyPosition(); } size_t copy = entry->mBuffer->size() - entry->mOffset; @@ -312,11 +452,13 @@ bool NuPlayer::Renderer::onDrainAudioQueue() { copy = numBytesAvailableToWrite; } - CHECK_EQ(mAudioSink->write( - entry->mBuffer->data() + entry->mOffset, copy), - (ssize_t)copy); + ssize_t written = mAudioSink->write(entry->mBuffer->data() + entry->mOffset, copy); + if (written < 0) { + // An error in AudioSink write is fatal here. + LOG_ALWAYS_FATAL("AudioSink write error(%zd) when writing %zu bytes", written, copy); + } - entry->mOffset += copy; + entry->mOffset += written; if (entry->mOffset == entry->mBuffer->size()) { entry->mNotifyConsumed->post(); mAudioQueue.erase(mAudioQueue.begin()); @@ -324,20 +466,46 @@ bool NuPlayer::Renderer::onDrainAudioQueue() { entry = NULL; } - numBytesAvailableToWrite -= copy; - size_t copiedFrames = copy / mAudioSink->frameSize(); + numBytesAvailableToWrite -= written; + size_t copiedFrames = written / mAudioSink->frameSize(); mNumFramesWritten += copiedFrames; notifyIfMediaRenderingStarted(); - } - - notifyPosition(); + if (written != (ssize_t)copy) { + // A short count was received from AudioSink::write() + // + // AudioSink write should block until exactly the number of bytes are delivered. + // But it may return with a short count (without an error) when: + // + // 1) Size to be copied is not a multiple of the frame size. We consider this fatal. + // 2) AudioSink is an AudioCache for data retrieval, and the AudioCache is exceeded. + + // (Case 1) + // Must be a multiple of the frame size. If it is not a multiple of a frame size, it + // needs to fail, as we should not carry over fractional frames between calls. + CHECK_EQ(copy % mAudioSink->frameSize(), 0); + + // (Case 2) + // Return early to the caller. + // Beware of calling immediately again as this may busy-loop if you are not careful. + ALOGW("AudioSink write short frame count %zd < %zu", written, copy); + break; + } + } return !mAudioQueue.empty(); } +int64_t NuPlayer::Renderer::getPendingAudioPlayoutDurationUs(int64_t nowUs) { + int64_t writtenAudioDurationUs = + mNumFramesWritten * 1000LL * mAudioSink->msecsPerFrame(); + return writtenAudioDurationUs - getPlayedOutAudioDurationUs(nowUs); +} + void NuPlayer::Renderer::postDrainVideoQueue() { - if (mDrainVideoQueuePending || mSyncQueues || mPaused) { + if (mDrainVideoQueuePending + || mSyncQueues + || (mPaused && mVideoSampleReceived)) { return; } @@ -350,36 +518,48 @@ void NuPlayer::Renderer::postDrainVideoQueue() { sp<AMessage> msg = new AMessage(kWhatDrainVideoQueue, id()); msg->setInt32("generation", mVideoQueueGeneration); - int64_t delayUs; - if (entry.mBuffer == NULL) { // EOS doesn't carry a timestamp. - delayUs = 0; - } else if (mFlags & FLAG_REAL_TIME) { + msg->post(); + mDrainVideoQueuePending = true; + return; + } + + int64_t delayUs; + int64_t nowUs = ALooper::GetNowUs(); + int64_t realTimeUs; + if (mFlags & FLAG_REAL_TIME) { int64_t mediaTimeUs; CHECK(entry.mBuffer->meta()->findInt64("timeUs", &mediaTimeUs)); - - delayUs = mediaTimeUs - ALooper::GetNowUs(); + realTimeUs = mediaTimeUs; } else { int64_t mediaTimeUs; CHECK(entry.mBuffer->meta()->findInt64("timeUs", &mediaTimeUs)); + if (mFirstAnchorTimeMediaUs == -1 && !mHasAudio) { + mFirstAnchorTimeMediaUs = mediaTimeUs; + } if (mAnchorTimeMediaUs < 0) { - delayUs = 0; - if (!mHasAudio) { mAnchorTimeMediaUs = mediaTimeUs; - mAnchorTimeRealUs = ALooper::GetNowUs(); + mAnchorTimeRealUs = nowUs; + notifyPosition(); } + realTimeUs = nowUs; } else { - int64_t realTimeUs = + realTimeUs = (mediaTimeUs - mAnchorTimeMediaUs) + mAnchorTimeRealUs; - - delayUs = realTimeUs - ALooper::GetNowUs(); } } - msg->post(delayUs); + realTimeUs = mVideoScheduler->schedule(realTimeUs * 1000) / 1000; + int64_t twoVsyncsUs = 2 * (mVideoScheduler->getVsyncPeriod() / 1000); + + delayUs = realTimeUs - nowUs; + + ALOGW_IF(delayUs > 500000, "unusually high delayUs: %" PRId64, delayUs); + // post 2 display refreshes before rendering is due + msg->post(delayUs > twoVsyncsUs ? delayUs - twoVsyncsUs : 0); mDrainVideoQueuePending = true; } @@ -400,8 +580,6 @@ void NuPlayer::Renderer::onDrainVideoQueue() { entry = NULL; mVideoLateByUs = 0ll; - - notifyPosition(); return; } @@ -415,34 +593,39 @@ void NuPlayer::Renderer::onDrainVideoQueue() { realTimeUs = mediaTimeUs - mAnchorTimeMediaUs + mAnchorTimeRealUs; } - mVideoLateByUs = ALooper::GetNowUs() - realTimeUs; - bool tooLate = (mVideoLateByUs > 40000); + bool tooLate = false; - if (tooLate) { - ALOGV("video late by %lld us (%.2f secs)", - mVideoLateByUs, mVideoLateByUs / 1E6); - } else { - ALOGV("rendering video at media time %.2f secs", - (mFlags & FLAG_REAL_TIME ? realTimeUs : - (realTimeUs + mAnchorTimeMediaUs - mAnchorTimeRealUs)) / 1E6); - if (mSoftRenderer != NULL) { - mSoftRenderer->render(entry->mBuffer->data(), entry->mBuffer->size(), NULL); + if (!mPaused) { + mVideoLateByUs = ALooper::GetNowUs() - realTimeUs; + tooLate = (mVideoLateByUs > 40000); + + if (tooLate) { + ALOGV("video late by %lld us (%.2f secs)", + mVideoLateByUs, mVideoLateByUs / 1E6); + } else { + ALOGV("rendering video at media time %.2f secs", + (mFlags & FLAG_REAL_TIME ? realTimeUs : + (realTimeUs + mAnchorTimeMediaUs - mAnchorTimeRealUs)) / 1E6); } + } else { + mVideoLateByUs = 0ll; } + entry->mNotifyConsumed->setInt64("timestampNs", realTimeUs * 1000ll); entry->mNotifyConsumed->setInt32("render", !tooLate); entry->mNotifyConsumed->post(); mVideoQueue.erase(mVideoQueue.begin()); entry = NULL; - if (!mVideoRenderingStarted) { - mVideoRenderingStarted = true; - notifyVideoRenderingStart(); - } - - notifyIfMediaRenderingStarted(); + mVideoSampleReceived = true; - notifyPosition(); + if (!mPaused) { + if (!mVideoRenderingStarted) { + mVideoRenderingStarted = true; + notifyVideoRenderingStart(); + } + notifyIfMediaRenderingStarted(); + } } void NuPlayer::Renderer::notifyVideoRenderingStart() { @@ -451,12 +634,16 @@ void NuPlayer::Renderer::notifyVideoRenderingStart() { notify->post(); } -void NuPlayer::Renderer::notifyEOS(bool audio, status_t finalResult) { +void NuPlayer::Renderer::notifyEOS(bool audio, status_t finalResult, int64_t delayUs) { sp<AMessage> notify = mNotify->dup(); notify->setInt32("what", kWhatEOS); notify->setInt32("audio", static_cast<int32_t>(audio)); notify->setInt32("finalResult", finalResult); - notify->post(); + notify->post(delayUs); +} + +void NuPlayer::Renderer::notifyAudioOffloadTearDown() { + (new AMessage(kWhatAudioOffloadTearDown, id()))->post(); } void NuPlayer::Renderer::onQueueBuffer(const sp<AMessage> &msg) { @@ -467,6 +654,10 @@ void NuPlayer::Renderer::onQueueBuffer(const sp<AMessage> &msg) { mHasAudio = true; } else { mHasVideo = true; + if (mVideoScheduler == NULL) { + mVideoScheduler = new VideoFrameScheduler(); + mVideoScheduler->init(); + } } if (dropBufferWhileFlushing(audio, msg)) { @@ -486,13 +677,15 @@ void NuPlayer::Renderer::onQueueBuffer(const sp<AMessage> &msg) { entry.mFinalResult = OK; if (audio) { + Mutex::Autolock autoLock(mLock); mAudioQueue.push_back(entry); - postDrainAudioQueue(); + postDrainAudioQueue_l(); } else { mVideoQueue.push_back(entry); postDrainVideoQueue(); } + Mutex::Autolock autoLock(mLock); if (!mSyncQueues || mAudioQueue.empty() || mVideoQueue.empty()) { return; } @@ -502,7 +695,7 @@ void NuPlayer::Renderer::onQueueBuffer(const sp<AMessage> &msg) { if (firstAudioBuffer == NULL || firstVideoBuffer == NULL) { // EOS signalled on either queue. - syncQueuesDone(); + syncQueuesDone_l(); return; } @@ -526,10 +719,10 @@ void NuPlayer::Renderer::onQueueBuffer(const sp<AMessage> &msg) { return; } - syncQueuesDone(); + syncQueuesDone_l(); } -void NuPlayer::Renderer::syncQueuesDone() { +void NuPlayer::Renderer::syncQueuesDone_l() { if (!mSyncQueues) { return; } @@ -537,7 +730,7 @@ void NuPlayer::Renderer::syncQueuesDone() { mSyncQueues = false; if (!mAudioQueue.empty()) { - postDrainAudioQueue(); + postDrainAudioQueue_l(); } if (!mVideoQueue.empty()) { @@ -561,14 +754,16 @@ void NuPlayer::Renderer::onQueueEOS(const sp<AMessage> &msg) { entry.mFinalResult = finalResult; if (audio) { + Mutex::Autolock autoLock(mLock); if (mAudioQueue.empty() && mSyncQueues) { - syncQueuesDone(); + syncQueuesDone_l(); } mAudioQueue.push_back(entry); - postDrainAudioQueue(); + postDrainAudioQueue_l(); } else { if (mVideoQueue.empty() && mSyncQueues) { - syncQueuesDone(); + Mutex::Autolock autoLock(mLock); + syncQueuesDone_l(); } mVideoQueue.push_back(entry); postDrainVideoQueue(); @@ -579,6 +774,15 @@ void NuPlayer::Renderer::onFlush(const sp<AMessage> &msg) { int32_t audio; CHECK(msg->findInt32("audio", &audio)); + { + Mutex::Autolock autoLock(mFlushLock); + if (audio) { + mFlushingAudio = false; + } else { + mFlushingVideo = false; + } + } + // If we're currently syncing the queues, i.e. dropping audio while // aligning the first audio/video buffer times and only one of the // two queues has data, we may starve that queue by not requesting @@ -587,31 +791,46 @@ void NuPlayer::Renderer::onFlush(const sp<AMessage> &msg) { // corresponding discontinuity on the other queue. // Therefore we'll stop syncing the queues if at least one of them // is flushed. - syncQueuesDone(); + { + Mutex::Autolock autoLock(mLock); + syncQueuesDone_l(); + } ALOGV("flushing %s", audio ? "audio" : "video"); if (audio) { - flushQueue(&mAudioQueue); + { + Mutex::Autolock autoLock(mLock); + flushQueue(&mAudioQueue); - Mutex::Autolock autoLock(mFlushLock); - mFlushingAudio = false; + ++mAudioQueueGeneration; + prepareForMediaRenderingStart(); + + if (offloadingAudio()) { + mFirstAnchorTimeMediaUs = -1; + } + } mDrainAudioQueuePending = false; - ++mAudioQueueGeneration; - prepareForMediaRenderingStart(); + if (offloadingAudio()) { + mAudioSink->pause(); + mAudioSink->flush(); + mAudioSink->start(); + } } else { flushQueue(&mVideoQueue); - Mutex::Autolock autoLock(mFlushLock); - mFlushingVideo = false; - mDrainVideoQueuePending = false; ++mVideoQueueGeneration; + if (mVideoScheduler != NULL) { + mVideoScheduler->restart(); + } + prepareForMediaRenderingStart(); } + mVideoSampleReceived = false; notifyFlushComplete(audio); } @@ -661,6 +880,9 @@ bool NuPlayer::Renderer::dropBufferWhileFlushing( } void NuPlayer::Renderer::onAudioSinkChanged() { + if (offloadingAudio()) { + return; + } CHECK(!mDrainAudioQueuePending); mNumFramesWritten = 0; uint32_t written; @@ -669,10 +891,18 @@ void NuPlayer::Renderer::onAudioSinkChanged() { } } +void NuPlayer::Renderer::onDisableOffloadAudio() { + Mutex::Autolock autoLock(mLock); + mFlags &= ~FLAG_OFFLOAD_AUDIO; + ++mAudioQueueGeneration; +} + void NuPlayer::Renderer::notifyPosition() { - if (mAnchorTimeRealUs < 0 || mAnchorTimeMediaUs < 0) { - return; - } + // notifyPosition() must be called only after setting mAnchorTimeRealUs + // and mAnchorTimeMediaUs, and must not be paused as it extrapolates position. + //CHECK_GE(mAnchorTimeRealUs, 0); + //CHECK_GE(mAnchorTimeMediaUs, 0); + //CHECK(!mPaused || !mHasAudio); // video-only does display in paused mode. int64_t nowUs = ALooper::GetNowUs(); @@ -684,6 +914,18 @@ void NuPlayer::Renderer::notifyPosition() { int64_t positionUs = (nowUs - mAnchorTimeRealUs) + mAnchorTimeMediaUs; + //ALOGD("notifyPosition: positionUs(%lld) nowUs(%lld) mAnchorTimeRealUs(%lld)" + // " mAnchorTimeMediaUs(%lld) mFirstAnchorTimeMediaUs(%lld)", + // (long long)positionUs, (long long)nowUs, (long long)mAnchorTimeRealUs, + // (long long)mAnchorTimeMediaUs, (long long)mFirstAnchorTimeMediaUs); + + // Due to adding the latency to mAnchorTimeRealUs in onDrainAudioQueue(), + // positionUs may be less than the first media time. This is avoided + // here to prevent potential retrograde motion of the position bar + // when starting up after a seek. + if (positionUs < mFirstAnchorTimeMediaUs) { + positionUs = mFirstAnchorTimeMediaUs; + } sp<AMessage> notify = mNotify->dup(); notify->setInt32("what", kWhatPosition); notify->setInt64("positionUs", positionUs); @@ -692,15 +934,20 @@ void NuPlayer::Renderer::notifyPosition() { } void NuPlayer::Renderer::onPause() { - CHECK(!mPaused); + if (mPaused) { + ALOGW("Renderer::onPause() called while already paused!"); + return; + } + { + Mutex::Autolock autoLock(mLock); + ++mAudioQueueGeneration; + ++mVideoQueueGeneration; + prepareForMediaRenderingStart(); + mPaused = true; + } mDrainAudioQueuePending = false; - ++mAudioQueueGeneration; - mDrainVideoQueuePending = false; - ++mVideoQueueGeneration; - - prepareForMediaRenderingStart(); if (mHasAudio) { mAudioSink->pause(); @@ -708,8 +955,6 @@ void NuPlayer::Renderer::onPause() { ALOGV("now paused audio queue has %d entries, video has %d entries", mAudioQueue.size(), mVideoQueue.size()); - - mPaused = true; } void NuPlayer::Renderer::onResume() { @@ -721,10 +966,11 @@ void NuPlayer::Renderer::onResume() { mAudioSink->start(); } + Mutex::Autolock autoLock(mLock); mPaused = false; if (!mAudioQueue.empty()) { - postDrainAudioQueue(); + postDrainAudioQueue_l(); } if (!mVideoQueue.empty()) { @@ -732,5 +978,96 @@ void NuPlayer::Renderer::onResume() { } } +void NuPlayer::Renderer::onSetVideoFrameRate(float fps) { + if (mVideoScheduler == NULL) { + mVideoScheduler = new VideoFrameScheduler(); + } + mVideoScheduler->init(fps); +} + +// TODO: Remove unnecessary calls to getPlayedOutAudioDurationUs() +// as it acquires locks and may query the audio driver. +// +// Some calls are not needed since notifyPosition() doesn't always deliver a message. +// Some calls could conceivably retrieve extrapolated data instead of +// accessing getTimestamp() or getPosition() every time a data buffer with +// a media time is received. +// +int64_t NuPlayer::Renderer::getPlayedOutAudioDurationUs(int64_t nowUs) { + uint32_t numFramesPlayed; + int64_t numFramesPlayedAt; + AudioTimestamp ts; + static const int64_t kStaleTimestamp100ms = 100000; + + status_t res = mAudioSink->getTimestamp(ts); + if (res == OK) { // case 1: mixing audio tracks and offloaded tracks. + numFramesPlayed = ts.mPosition; + numFramesPlayedAt = + ts.mTime.tv_sec * 1000000LL + ts.mTime.tv_nsec / 1000; + const int64_t timestampAge = nowUs - numFramesPlayedAt; + if (timestampAge > kStaleTimestamp100ms) { + // This is an audio FIXME. + // getTimestamp returns a timestamp which may come from audio mixing threads. + // After pausing, the MixerThread may go idle, thus the mTime estimate may + // become stale. Assuming that the MixerThread runs 20ms, with FastMixer at 5ms, + // the max latency should be about 25ms with an average around 12ms (to be verified). + // For safety we use 100ms. + ALOGW("getTimestamp: returned stale timestamp nowUs(%lld) numFramesPlayedAt(%lld)", + (long long)nowUs, (long long)numFramesPlayedAt); + numFramesPlayedAt = nowUs - kStaleTimestamp100ms; + } + //ALOGD("getTimestamp: OK %d %lld", numFramesPlayed, (long long)numFramesPlayedAt); + } else if (res == WOULD_BLOCK) { // case 2: transitory state on start of a new track + numFramesPlayed = 0; + numFramesPlayedAt = nowUs; + //ALOGD("getTimestamp: WOULD_BLOCK %d %lld", + // numFramesPlayed, (long long)numFramesPlayedAt); + } else { // case 3: transitory at new track or audio fast tracks. + res = mAudioSink->getPosition(&numFramesPlayed); + CHECK_EQ(res, (status_t)OK); + numFramesPlayedAt = nowUs; + numFramesPlayedAt += 1000LL * mAudioSink->latency() / 2; /* XXX */ + //ALOGD("getPosition: %d %lld", numFramesPlayed, numFramesPlayedAt); + } + + // TODO: remove the (int32_t) casting below as it may overflow at 12.4 hours. + //CHECK_EQ(numFramesPlayed & (1 << 31), 0); // can't be negative until 12.4 hrs, test + int64_t durationUs = (int32_t)numFramesPlayed * 1000LL * mAudioSink->msecsPerFrame() + + nowUs - numFramesPlayedAt; + if (durationUs < 0) { + // Occurs when numFramesPlayed position is very small and the following: + // (1) In case 1, the time nowUs is computed before getTimestamp() is called and + // numFramesPlayedAt is greater than nowUs by time more than numFramesPlayed. + // (2) In case 3, using getPosition and adding mAudioSink->latency() to + // numFramesPlayedAt, by a time amount greater than numFramesPlayed. + // + // Both of these are transitory conditions. + ALOGW("getPlayedOutAudioDurationUs: negative timestamp %lld set to zero", (long long)durationUs); + durationUs = 0; + } + ALOGV("getPlayedOutAudioDurationUs(%lld) nowUs(%lld) frames(%u) framesAt(%lld)", + (long long)durationUs, (long long)nowUs, numFramesPlayed, (long long)numFramesPlayedAt); + return durationUs; +} + +void NuPlayer::Renderer::onAudioOffloadTearDown() { + int64_t firstAudioTimeUs; + { + Mutex::Autolock autoLock(mLock); + firstAudioTimeUs = mFirstAnchorTimeMediaUs; + } + + int64_t currentPositionUs = + firstAudioTimeUs + getPlayedOutAudioDurationUs(ALooper::GetNowUs()); + + mAudioSink->stop(); + mAudioSink->flush(); + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatAudioOffloadTearDown); + notify->setInt64("positionUs", currentPositionUs); + notify->post(); +} + } // namespace android diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h index 9124e03..e28071f 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h +++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h @@ -23,16 +23,22 @@ namespace android { struct ABuffer; -class SoftwareRenderer; +struct VideoFrameScheduler; struct NuPlayer::Renderer : public AHandler { enum Flags { FLAG_REAL_TIME = 1, + FLAG_OFFLOAD_AUDIO = 2, }; Renderer(const sp<MediaPlayerBase::AudioSink> &sink, const sp<AMessage> ¬ify, uint32_t flags = 0); + static size_t AudioSinkCallback( + MediaPlayerBase::AudioSink *audioSink, + void *data, size_t size, void *me, + MediaPlayerBase::AudioSink::cb_event_t event); + void queueBuffer( bool audio, const sp<ABuffer> &buffer, @@ -46,19 +52,22 @@ struct NuPlayer::Renderer : public AHandler { void signalAudioSinkChanged(); + void signalDisableOffloadAudio(); + void pause(); void resume(); + void setVideoFrameRate(float fps); + enum { kWhatEOS = 'eos ', kWhatFlushComplete = 'fluC', kWhatPosition = 'posi', kWhatVideoRenderingStart = 'vdrd', kWhatMediaRenderingStart = 'mdrd', + kWhatAudioOffloadTearDown = 'aOTD', }; - void setSoftRenderer(SoftwareRenderer *softRenderer); - protected: virtual ~Renderer(); @@ -66,14 +75,17 @@ protected: private: enum { - kWhatDrainAudioQueue = 'draA', - kWhatDrainVideoQueue = 'draV', - kWhatQueueBuffer = 'queB', - kWhatQueueEOS = 'qEOS', - kWhatFlush = 'flus', - kWhatAudioSinkChanged = 'auSC', - kWhatPause = 'paus', - kWhatResume = 'resm', + kWhatDrainAudioQueue = 'draA', + kWhatDrainVideoQueue = 'draV', + kWhatQueueBuffer = 'queB', + kWhatQueueEOS = 'qEOS', + kWhatFlush = 'flus', + kWhatAudioSinkChanged = 'auSC', + kWhatPause = 'paus', + kWhatResume = 'resm', + kWhatStopAudioSink = 'stpA', + kWhatDisableOffloadAudio = 'noOA', + kWhatSetVideoFrameRate = 'sVFR', }; struct QueueEntry { @@ -86,18 +98,20 @@ private: static const int64_t kMinPositionUpdateDelayUs; sp<MediaPlayerBase::AudioSink> mAudioSink; - SoftwareRenderer *mSoftRenderer; sp<AMessage> mNotify; + Mutex mLock; uint32_t mFlags; List<QueueEntry> mAudioQueue; List<QueueEntry> mVideoQueue; uint32_t mNumFramesWritten; + sp<VideoFrameScheduler> mVideoScheduler; bool mDrainAudioQueuePending; bool mDrainVideoQueuePending; int32_t mAudioQueueGeneration; int32_t mVideoQueueGeneration; + int64_t mFirstAnchorTimeMediaUs; int64_t mAnchorTimeMediaUs; int64_t mAnchorTimeRealUs; @@ -110,6 +124,7 @@ private: bool mSyncQueues; bool mPaused; + bool mVideoSampleReceived; bool mVideoRenderingStarted; int32_t mVideoRenderingStartGeneration; int32_t mAudioRenderingStartGeneration; @@ -117,8 +132,12 @@ private: int64_t mLastPositionUpdateUs; int64_t mVideoLateByUs; + size_t fillAudioBuffer(void *buffer, size_t size); + bool onDrainAudioQueue(); - void postDrainAudioQueue(int64_t delayUs = 0); + int64_t getPendingAudioPlayoutDurationUs(int64_t nowUs); + int64_t getPlayedOutAudioDurationUs(int64_t nowUs); + void postDrainAudioQueue_l(int64_t delayUs = 0); void onDrainVideoQueue(); void postDrainVideoQueue(); @@ -130,18 +149,24 @@ private: void onQueueEOS(const sp<AMessage> &msg); void onFlush(const sp<AMessage> &msg); void onAudioSinkChanged(); + void onDisableOffloadAudio(); void onPause(); void onResume(); + void onSetVideoFrameRate(float fps); + void onAudioOffloadTearDown(); - void notifyEOS(bool audio, status_t finalResult); + void notifyEOS(bool audio, status_t finalResult, int64_t delayUs = 0); void notifyFlushComplete(bool audio); void notifyPosition(); void notifyVideoLateBy(int64_t lateByUs); void notifyVideoRenderingStart(); + void notifyAudioOffloadTearDown(); void flushQueue(List<QueueEntry> *queue); bool dropBufferWhileFlushing(bool audio, const sp<AMessage> &msg); - void syncQueuesDone(); + void syncQueuesDone_l(); + + bool offloadingAudio() const { return (mFlags & FLAG_OFFLOAD_AUDIO) != 0; } DISALLOW_EVIL_CONSTRUCTORS(Renderer); }; diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h index 11279fc..2f06c31 100644 --- a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h +++ b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h @@ -21,11 +21,15 @@ #include "NuPlayer.h" #include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/MetaData.h> +#include <media/mediaplayer.h> +#include <utils/Vector.h> namespace android { struct ABuffer; struct MetaData; +struct MediaBuffer; struct NuPlayer::Source : public AHandler { enum Flags { @@ -34,16 +38,20 @@ struct NuPlayer::Source : public AHandler { FLAG_CAN_SEEK_FORWARD = 4, // the "10 sec forward button" FLAG_CAN_SEEK = 8, // the "seek bar" FLAG_DYNAMIC_DURATION = 16, + FLAG_SECURE = 32, }; enum { kWhatPrepared, kWhatFlagsChanged, kWhatVideoSizeChanged, + kWhatBufferingUpdate, kWhatBufferingStart, kWhatBufferingEnd, kWhatSubtitleData, + kWhatTimedTextData, kWhatQueueDecoderShutdown, + kWhatDrmNoLicense, }; // The provides message is used to notify the player about various @@ -59,11 +67,16 @@ struct NuPlayer::Source : public AHandler { virtual void pause() {} virtual void resume() {} + // Explicitly disconnect the underling data source + virtual void disconnect() {} + // Returns OK iff more data was available, // an error or ERROR_END_OF_STREAM if not. virtual status_t feedMoreTSData() = 0; virtual sp<AMessage> getFormat(bool audio); + virtual sp<MetaData> getFormatMeta(bool /* audio */) { return NULL; } + virtual sp<MetaData> getFileFormatMeta() const { return NULL; } virtual status_t dequeueAccessUnit( bool audio, sp<ABuffer> *accessUnit) = 0; @@ -72,7 +85,15 @@ struct NuPlayer::Source : public AHandler { return INVALID_OPERATION; } - virtual status_t getTrackInfo(Parcel* /* reply */) const { + virtual size_t getTrackCount() const { + return 0; + } + + virtual sp<AMessage> getTrackInfo(size_t /* trackIndex */) const { + return NULL; + } + + virtual ssize_t getSelectedTrack(media_track_type /* type */) const { return INVALID_OPERATION; } @@ -84,6 +105,10 @@ struct NuPlayer::Source : public AHandler { return INVALID_OPERATION; } + virtual status_t setBuffers(bool /* audio */, Vector<MediaBuffer *> &/* buffers */) { + return INVALID_OPERATION; + } + virtual bool isRealTime() const { return false; } @@ -93,12 +118,10 @@ protected: virtual void onMessageReceived(const sp<AMessage> &msg); - virtual sp<MetaData> getFormatMeta(bool /* audio */) { return NULL; } - sp<AMessage> dupNotify() const { return mNotify->dup(); } void notifyFlagsChanged(uint32_t flags); - void notifyVideoSizeChanged(int32_t width, int32_t height); + void notifyVideoSizeChanged(const sp<AMessage> &format = NULL); void notifyPrepared(status_t err = OK); private: diff --git a/media/libmediaplayerservice/nuplayer/RTSPSource.cpp b/media/libmediaplayerservice/nuplayer/RTSPSource.cpp index 18cf6d1..ffacb8f 100644 --- a/media/libmediaplayerservice/nuplayer/RTSPSource.cpp +++ b/media/libmediaplayerservice/nuplayer/RTSPSource.cpp @@ -24,6 +24,7 @@ #include "MyHandler.h" #include "SDPLoader.h" +#include <media/IMediaHTTPService.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MetaData.h> @@ -33,12 +34,14 @@ const int64_t kNearEOSTimeoutUs = 2000000ll; // 2 secs NuPlayer::RTSPSource::RTSPSource( const sp<AMessage> ¬ify, + const sp<IMediaHTTPService> &httpService, const char *url, const KeyedVector<String8, String8> *headers, bool uidValid, uid_t uid, bool isSDP) : Source(notify), + mHTTPService(httpService), mURL(url), mUIDValid(uidValid), mUID(uid), @@ -67,6 +70,7 @@ NuPlayer::RTSPSource::RTSPSource( NuPlayer::RTSPSource::~RTSPSource() { if (mLooper != NULL) { + mLooper->unregisterHandler(id()); mLooper->stop(); } } @@ -77,14 +81,13 @@ void NuPlayer::RTSPSource::prepareAsync() { mLooper->setName("rtsp"); mLooper->start(); - mReflector = new AHandlerReflector<RTSPSource>(this); - mLooper->registerHandler(mReflector); + mLooper->registerHandler(this); } CHECK(mHandler == NULL); CHECK(mSDPLoader == NULL); - sp<AMessage> notify = new AMessage(kWhatNotify, mReflector->id()); + sp<AMessage> notify = new AMessage(kWhatNotify, id()); CHECK_EQ(mState, (int)DISCONNECTED); mState = CONNECTING; @@ -92,7 +95,7 @@ void NuPlayer::RTSPSource::prepareAsync() { if (mIsSDP) { mSDPLoader = new SDPLoader(notify, (mFlags & kFlagIncognito) ? SDPLoader::kFlagIncognito : 0, - mUIDValid, mUID); + mHTTPService); mSDPLoader->load( mURL.c_str(), mExtraHeaders.isEmpty() ? NULL : &mExtraHeaders); @@ -115,7 +118,7 @@ void NuPlayer::RTSPSource::stop() { if (mLooper == NULL) { return; } - sp<AMessage> msg = new AMessage(kWhatDisconnect, mReflector->id()); + sp<AMessage> msg = new AMessage(kWhatDisconnect, id()); sp<AMessage> dummy; msg->postAndAwaitResponse(&dummy); @@ -302,7 +305,7 @@ status_t NuPlayer::RTSPSource::getDuration(int64_t *durationUs) { } status_t NuPlayer::RTSPSource::seekTo(int64_t seekTimeUs) { - sp<AMessage> msg = new AMessage(kWhatPerformSeek, mReflector->id()); + sp<AMessage> msg = new AMessage(kWhatPerformSeek, id()); msg->setInt32("generation", ++mSeekGeneration); msg->setInt64("timeUs", seekTimeUs); msg->post(200000ll); @@ -353,7 +356,7 @@ void NuPlayer::RTSPSource::onMessageReceived(const sp<AMessage> &msg) { { onConnected(); - notifyVideoSizeChanged(0, 0); + notifyVideoSizeChanged(); uint32_t flags = 0; @@ -502,7 +505,10 @@ void NuPlayer::RTSPSource::onMessageReceived(const sp<AMessage> &msg) { TrackInfo *info = &mTracks.editItemAt(trackIndex); sp<AnotherPacketSource> source = info->mSource; if (source != NULL) { - source->queueDiscontinuity(ATSParser::DISCONTINUITY_SEEK, NULL); + source->queueDiscontinuity( + ATSParser::DISCONTINUITY_SEEK, + NULL, + true /* discard */); } break; @@ -607,7 +613,7 @@ void NuPlayer::RTSPSource::onSDPLoaded(const sp<AMessage> &msg) { ALOGE("Unable to find url in SDP"); err = UNKNOWN_ERROR; } else { - sp<AMessage> notify = new AMessage(kWhatNotify, mReflector->id()); + sp<AMessage> notify = new AMessage(kWhatNotify, id()); mHandler = new MyHandler(rtspUri.c_str(), notify, mUIDValid, mUID); mLooper->registerHandler(mHandler); diff --git a/media/libmediaplayerservice/nuplayer/RTSPSource.h b/media/libmediaplayerservice/nuplayer/RTSPSource.h index 8cf34a0..f1cae53 100644 --- a/media/libmediaplayerservice/nuplayer/RTSPSource.h +++ b/media/libmediaplayerservice/nuplayer/RTSPSource.h @@ -22,8 +22,6 @@ #include "ATSParser.h" -#include <media/stagefright/foundation/AHandlerReflector.h> - namespace android { struct ALooper; @@ -34,6 +32,7 @@ struct SDPLoader; struct NuPlayer::RTSPSource : public NuPlayer::Source { RTSPSource( const sp<AMessage> ¬ify, + const sp<IMediaHTTPService> &httpService, const char *url, const KeyedVector<String8, String8> *headers, bool uidValid = false, @@ -88,6 +87,7 @@ private: bool mNPTMappingValid; }; + sp<IMediaHTTPService> mHTTPService; AString mURL; KeyedVector<String8, String8> mExtraHeaders; bool mUIDValid; @@ -100,7 +100,6 @@ private: bool mBuffering; sp<ALooper> mLooper; - sp<AHandlerReflector<RTSPSource> > mReflector; sp<MyHandler> mHandler; sp<SDPLoader> mSDPLoader; diff --git a/media/libmediaplayerservice/nuplayer/StreamingSource.cpp b/media/libmediaplayerservice/nuplayer/StreamingSource.cpp index 28f0d50..2e9a29a 100644 --- a/media/libmediaplayerservice/nuplayer/StreamingSource.cpp +++ b/media/libmediaplayerservice/nuplayer/StreamingSource.cpp @@ -44,7 +44,7 @@ NuPlayer::StreamingSource::~StreamingSource() { } void NuPlayer::StreamingSource::prepareAsync() { - notifyVideoSizeChanged(0, 0); + notifyVideoSizeChanged(); notifyFlagsChanged(0); notifyPrepared(); } diff --git a/media/libmediaplayerservice/nuplayer/mp4/MP4Source.cpp b/media/libmediaplayerservice/nuplayer/mp4/MP4Source.cpp deleted file mode 100644 index 2aae4dd..0000000 --- a/media/libmediaplayerservice/nuplayer/mp4/MP4Source.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2012 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 "MP4Source.h" - -#include "FragmentedMP4Parser.h" -#include "../NuPlayerStreamListener.h" - -#include <media/IStreamSource.h> -#include <media/stagefright/foundation/AMessage.h> -#include <media/stagefright/MediaErrors.h> -#include <media/stagefright/MetaData.h> - -namespace android { - -struct StreamSource : public FragmentedMP4Parser::Source { - StreamSource(const sp<IStreamSource> &source) - : mListener(new NuPlayer::NuPlayerStreamListener(source, 0)), - mPosition(0) { - mListener->start(); - } - - virtual ssize_t readAt(off64_t offset, void *data, size_t size) { - if (offset < mPosition) { - return -EPIPE; - } - - while (offset > mPosition) { - char buffer[1024]; - off64_t skipBytes = offset - mPosition; - if (skipBytes > sizeof(buffer)) { - skipBytes = sizeof(buffer); - } - - sp<AMessage> extra; - ssize_t n; - for (;;) { - n = mListener->read(buffer, skipBytes, &extra); - - if (n == -EWOULDBLOCK) { - usleep(10000); - continue; - } - - break; - } - - ALOGV("skipped %ld bytes at offset %lld", n, mPosition); - - if (n < 0) { - return n; - } - - mPosition += n; - } - - sp<AMessage> extra; - size_t total = 0; - while (total < size) { - ssize_t n = mListener->read( - (uint8_t *)data + total, size - total, &extra); - - if (n == -EWOULDBLOCK) { - usleep(10000); - continue; - } else if (n == 0) { - break; - } else if (n < 0) { - mPosition += total; - return n; - } - - total += n; - } - - ALOGV("read %ld bytes at offset %lld", total, mPosition); - - mPosition += total; - - return total; - } - - bool isSeekable() { - return false; - } - -private: - sp<NuPlayer::NuPlayerStreamListener> mListener; - off64_t mPosition; - - DISALLOW_EVIL_CONSTRUCTORS(StreamSource); -}; - -MP4Source::MP4Source( - const sp<AMessage> ¬ify, const sp<IStreamSource> &source) - : Source(notify), - mSource(source), - mLooper(new ALooper), - mParser(new FragmentedMP4Parser), - mEOS(false) { - mLooper->registerHandler(mParser); -} - -MP4Source::~MP4Source() { -} - -void MP4Source::prepareAsync() { - notifyVideoSizeChanged(0, 0); - notifyFlagsChanged(0); - notifyPrepared(); -} - -void MP4Source::start() { - mLooper->start(false /* runOnCallingThread */); - mParser->start(new StreamSource(mSource)); -} - -status_t MP4Source::feedMoreTSData() { - return mEOS ? ERROR_END_OF_STREAM : (status_t)OK; -} - -sp<AMessage> MP4Source::getFormat(bool audio) { - return mParser->getFormat(audio); -} - -status_t MP4Source::dequeueAccessUnit( - bool audio, sp<ABuffer> *accessUnit) { - return mParser->dequeueAccessUnit(audio, accessUnit); -} - -} // namespace android diff --git a/media/libmediaplayerservice/nuplayer/mp4/MP4Source.h b/media/libmediaplayerservice/nuplayer/mp4/MP4Source.h deleted file mode 100644 index a6ef622..0000000 --- a/media/libmediaplayerservice/nuplayer/mp4/MP4Source.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef MP4_SOURCE_H -#define MP4_SOURCE_H - -#include "NuPlayerSource.h" - -namespace android { - -struct FragmentedMP4Parser; - -struct MP4Source : public NuPlayer::Source { - MP4Source(const sp<AMessage> ¬ify, const sp<IStreamSource> &source); - - virtual void prepareAsync(); - virtual void start(); - - virtual status_t feedMoreTSData(); - - virtual sp<AMessage> getFormat(bool audio); - - virtual status_t dequeueAccessUnit( - bool audio, sp<ABuffer> *accessUnit); - -protected: - virtual ~MP4Source(); - -private: - sp<IStreamSource> mSource; - sp<ALooper> mLooper; - sp<FragmentedMP4Parser> mParser; - bool mEOS; - - DISALLOW_EVIL_CONSTRUCTORS(MP4Source); -}; - -} // namespace android - -#endif // MP4_SOURCE_H |