diff options
| author | Anatol Pomozov <anatol.pomozov@gmail.com> | 2012-03-28 09:12:55 -0700 |
|---|---|---|
| committer | Anatol Pomozov <anatol.pomozov@gmail.com> | 2012-03-28 12:02:47 -0700 |
| commit | b0b2b4d890cf3bfb274797a759642b4e733343d7 (patch) | |
| tree | 12ad21cbad346f02d542aa4d672ffd76407d58a9 /media/libmediaplayerservice | |
| parent | 51f8eec23a2bcc2cc190373cdd1195972d9b8804 (diff) | |
| parent | 5a5491c17d74bd2c80cf451c6ddbba22d5d5f08a (diff) | |
| download | frameworks_av-b0b2b4d890cf3bfb274797a759642b4e733343d7.zip frameworks_av-b0b2b4d890cf3bfb274797a759642b4e733343d7.tar.gz frameworks_av-b0b2b4d890cf3bfb274797a759642b4e733343d7.tar.bz2 | |
Merge media files with history from frameworks/base.git
Diffstat (limited to 'media/libmediaplayerservice')
39 files changed, 11180 insertions, 0 deletions
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk new file mode 100644 index 0000000..675c563 --- /dev/null +++ b/media/libmediaplayerservice/Android.mk @@ -0,0 +1,53 @@ +LOCAL_PATH:= $(call my-dir) + +# +# libmediaplayerservice +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + Crypto.cpp \ + MediaRecorderClient.cpp \ + MediaPlayerService.cpp \ + MetadataRetrieverClient.cpp \ + TestPlayerStub.cpp \ + MidiMetadataRetriever.cpp \ + MidiFile.cpp \ + StagefrightPlayer.cpp \ + StagefrightRecorder.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libutils \ + libbinder \ + libvorbisidec \ + libsonivox \ + libmedia \ + libmedia_native \ + libcamera_client \ + libandroid_runtime \ + libstagefright \ + libstagefright_omx \ + libstagefright_foundation \ + libgui \ + libdl \ + libaah_rtp + +LOCAL_STATIC_LIBRARIES := \ + libstagefright_nuplayer \ + libstagefright_rtsp \ + +LOCAL_C_INCLUDES := \ + $(call include-path-for, graphics corecg) \ + $(TOP)/frameworks/base/media/libstagefright/include \ + $(TOP)/frameworks/base/media/libstagefright/rtsp \ + $(TOP)/frameworks/native/include/media/openmax \ + $(TOP)/external/tremolo/Tremolo \ + +LOCAL_MODULE:= libmediaplayerservice + +include $(BUILD_SHARED_LIBRARY) + +include $(call all-makefiles-under,$(LOCAL_PATH)) + diff --git a/media/libmediaplayerservice/Crypto.cpp b/media/libmediaplayerservice/Crypto.cpp new file mode 100644 index 0000000..e02035f --- /dev/null +++ b/media/libmediaplayerservice/Crypto.cpp @@ -0,0 +1,65 @@ +/* + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "Crypto" +#include <utils/Log.h> + +#include "Crypto.h" + +#include <media/stagefright/MediaErrors.h> + +namespace android { + +Crypto::Crypto() { +} + +Crypto::~Crypto() { +} + +status_t Crypto::initialize() { + return ERROR_UNSUPPORTED; +} + +status_t Crypto::terminate() { + return ERROR_UNSUPPORTED; +} + +status_t Crypto::setEntitlementKey( + const void *key, size_t keyLength) { + return ERROR_UNSUPPORTED; +} + +status_t Crypto::setEntitlementControlMessage( + const void *msg, size_t msgLength) { + return ERROR_UNSUPPORTED; +} + +ssize_t Crypto::decryptVideo( + const void *iv, size_t ivLength, + const void *srcData, size_t srcDataSize, + void *dstData, size_t dstDataOffset) { + return ERROR_UNSUPPORTED; +} + +ssize_t Crypto::decryptAudio( + const void *iv, size_t ivLength, + const void *srcData, size_t srcDataSize, + void *dstData, size_t dstDataSize) { + return ERROR_UNSUPPORTED; +} + +} // namespace android diff --git a/media/libmediaplayerservice/Crypto.h b/media/libmediaplayerservice/Crypto.h new file mode 100644 index 0000000..9855496 --- /dev/null +++ b/media/libmediaplayerservice/Crypto.h @@ -0,0 +1,57 @@ +/* + * 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 CRYPTO_H_ + +#define CRYPTO_H_ + +#include <media/ICrypto.h> +#include <utils/threads.h> + +namespace android { + +struct Crypto : public BnCrypto { + Crypto(); + + virtual status_t initialize(); + virtual status_t terminate(); + + virtual status_t setEntitlementKey( + const void *key, size_t keyLength); + + virtual status_t setEntitlementControlMessage( + const void *msg, size_t msgLength); + + virtual ssize_t decryptVideo( + const void *iv, size_t ivLength, + const void *srcData, size_t srcDataSize, + void *dstData, size_t dstDataOffset); + + virtual ssize_t decryptAudio( + const void *iv, size_t ivLength, + const void *srcData, size_t srcDataSize, + void *dstData, size_t dstDataSize); + +protected: + virtual ~Crypto(); + +private: + DISALLOW_EVIL_CONSTRUCTORS(Crypto); +}; + +} // namespace android + +#endif // CRYPTO_H_ diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp new file mode 100644 index 0000000..123d07f --- /dev/null +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -0,0 +1,2168 @@ +/* +** +** Copyright 2008, 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. +*/ + +// Proxy for media player implementations + +//#define LOG_NDEBUG 0 +#define LOG_TAG "MediaPlayerService" +#include <utils/Log.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <dirent.h> +#include <unistd.h> + +#include <string.h> + +#include <cutils/atomic.h> +#include <cutils/properties.h> // for property_get + +#include <utils/misc.h> + +#include <android_runtime/ActivityManager.h> + +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <binder/MemoryHeapBase.h> +#include <binder/MemoryBase.h> +#include <gui/SurfaceTextureClient.h> +#include <utils/Errors.h> // for status_t +#include <utils/String8.h> +#include <utils/SystemClock.h> +#include <utils/Vector.h> +#include <cutils/properties.h> + +#include <media/MediaPlayerInterface.h> +#include <media/mediarecorder.h> +#include <media/MediaMetadataRetrieverInterface.h> +#include <media/Metadata.h> +#include <media/AudioTrack.h> +#include <media/MemoryLeakTrackUtil.h> +#include <media/stagefright/MediaErrors.h> + +#include <system/audio.h> + +#include <private/android_filesystem_config.h> + +#include "MediaRecorderClient.h" +#include "MediaPlayerService.h" +#include "MetadataRetrieverClient.h" + +#include "MidiFile.h" +#include "TestPlayerStub.h" +#include "StagefrightPlayer.h" +#include "nuplayer/NuPlayerDriver.h" + +#include <OMX.h> + +#include "Crypto.h" + +namespace android { +sp<MediaPlayerBase> createAAH_TXPlayer(); +sp<MediaPlayerBase> createAAH_RXPlayer(); +} + +namespace { +using android::media::Metadata; +using android::status_t; +using android::OK; +using android::BAD_VALUE; +using android::NOT_ENOUGH_DATA; +using android::Parcel; + +// Max number of entries in the filter. +const int kMaxFilterSize = 64; // I pulled that out of thin air. + +// FIXME: Move all the metadata related function in the Metadata.cpp + + +// Unmarshall a filter from a Parcel. +// Filter 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | number of entries (n) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | metadata type 1 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | metadata type 2 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// .... +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | metadata type n | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// @param p Parcel that should start with a filter. +// @param[out] filter On exit contains the list of metadata type to be +// filtered. +// @param[out] status On exit contains the status code to be returned. +// @return true if the parcel starts with a valid filter. +bool unmarshallFilter(const Parcel& p, + Metadata::Filter *filter, + status_t *status) +{ + int32_t val; + if (p.readInt32(&val) != OK) + { + ALOGE("Failed to read filter's length"); + *status = NOT_ENOUGH_DATA; + return false; + } + + if( val > kMaxFilterSize || val < 0) + { + ALOGE("Invalid filter len %d", val); + *status = BAD_VALUE; + return false; + } + + const size_t num = val; + + filter->clear(); + filter->setCapacity(num); + + size_t size = num * sizeof(Metadata::Type); + + + if (p.dataAvail() < size) + { + ALOGE("Filter too short expected %d but got %d", size, p.dataAvail()); + *status = NOT_ENOUGH_DATA; + return false; + } + + const Metadata::Type *data = + static_cast<const Metadata::Type*>(p.readInplace(size)); + + if (NULL == data) + { + ALOGE("Filter had no data"); + *status = BAD_VALUE; + return false; + } + + // TODO: The stl impl of vector would be more efficient here + // because it degenerates into a memcpy on pod types. Try to + // replace later or use stl::set. + for (size_t i = 0; i < num; ++i) + { + filter->add(*data); + ++data; + } + *status = OK; + return true; +} + +// @param filter Of metadata type. +// @param val To be searched. +// @return true if a match was found. +bool findMetadata(const Metadata::Filter& filter, const int32_t val) +{ + // Deal with empty and ANY right away + if (filter.isEmpty()) return false; + if (filter[0] == Metadata::kAny) return true; + + return filter.indexOf(val) >= 0; +} + +} // anonymous namespace + + +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; +} + +// TODO: Temp hack until we can register players +typedef struct { + const char *extension; + const player_type playertype; +} extmap; +extmap FILE_EXTS [] = { + {".mid", SONIVOX_PLAYER}, + {".midi", SONIVOX_PLAYER}, + {".smf", SONIVOX_PLAYER}, + {".xmf", SONIVOX_PLAYER}, + {".imy", SONIVOX_PLAYER}, + {".rtttl", SONIVOX_PLAYER}, + {".rtx", SONIVOX_PLAYER}, + {".ota", SONIVOX_PLAYER}, +}; + +// TODO: Find real cause of Audio/Video delay in PV framework and remove this workaround +/* static */ int MediaPlayerService::AudioOutput::mMinBufferCount = 4; +/* static */ bool MediaPlayerService::AudioOutput::mIsOnEmulator = false; + +void MediaPlayerService::instantiate() { + defaultServiceManager()->addService( + String16("media.player"), new MediaPlayerService()); +} + +MediaPlayerService::MediaPlayerService() +{ + ALOGV("MediaPlayerService created"); + mNextConnId = 1; + + mBatteryAudio.refCount = 0; + for (int i = 0; i < NUM_AUDIO_DEVICES; i++) { + mBatteryAudio.deviceOn[i] = 0; + mBatteryAudio.lastTime[i] = 0; + mBatteryAudio.totalTime[i] = 0; + } + // speaker is on by default + mBatteryAudio.deviceOn[SPEAKER] = 1; +} + +MediaPlayerService::~MediaPlayerService() +{ + ALOGV("MediaPlayerService destroyed"); +} + +sp<IMediaRecorder> MediaPlayerService::createMediaRecorder(pid_t pid) +{ + sp<MediaRecorderClient> recorder = new MediaRecorderClient(this, pid); + wp<MediaRecorderClient> w = recorder; + Mutex::Autolock lock(mLock); + mMediaRecorderClients.add(w); + ALOGV("Create new media recorder client from pid %d", pid); + return recorder; +} + +void MediaPlayerService::removeMediaRecorderClient(wp<MediaRecorderClient> client) +{ + Mutex::Autolock lock(mLock); + mMediaRecorderClients.remove(client); + ALOGV("Delete media recorder client"); +} + +sp<IMediaMetadataRetriever> MediaPlayerService::createMetadataRetriever(pid_t pid) +{ + sp<MetadataRetrieverClient> retriever = new MetadataRetrieverClient(pid); + ALOGV("Create new media retriever from pid %d", pid); + return retriever; +} + +sp<IMediaPlayer> MediaPlayerService::create(pid_t pid, const sp<IMediaPlayerClient>& client, + int audioSessionId) +{ + int32_t connId = android_atomic_inc(&mNextConnId); + + sp<Client> c = new Client( + this, pid, connId, client, audioSessionId, + IPCThreadState::self()->getCallingUid()); + + ALOGV("Create new client(%d) from pid %d, uid %d, ", connId, pid, + IPCThreadState::self()->getCallingUid()); + + wp<Client> w = c; + { + Mutex::Autolock lock(mLock); + mClients.add(w); + } + return c; +} + +sp<IOMX> MediaPlayerService::getOMX() { + Mutex::Autolock autoLock(mLock); + + if (mOMX.get() == NULL) { + mOMX = new OMX; + } + + return mOMX; +} + +sp<ICrypto> MediaPlayerService::makeCrypto() { + Mutex::Autolock autoLock(mLock); + + if (mCrypto == NULL) { + mCrypto = new Crypto; + } + + return mCrypto; +} + +status_t MediaPlayerService::AudioCache::dump(int fd, const Vector<String16>& args) const +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + result.append(" AudioCache\n"); + if (mHeap != 0) { + snprintf(buffer, 255, " heap base(%p), size(%d), flags(%d), device(%s)\n", + mHeap->getBase(), mHeap->getSize(), mHeap->getFlags(), mHeap->getDevice()); + result.append(buffer); + } + snprintf(buffer, 255, " msec per frame(%f), channel count(%d), format(%d), frame count(%ld)\n", + mMsecsPerFrame, mChannelCount, mFormat, mFrameCount); + result.append(buffer); + snprintf(buffer, 255, " sample rate(%d), size(%d), error(%d), command complete(%s)\n", + mSampleRate, mSize, mError, mCommandComplete?"true":"false"); + result.append(buffer); + ::write(fd, result.string(), result.size()); + return NO_ERROR; +} + +status_t MediaPlayerService::AudioOutput::dump(int fd, const Vector<String16>& args) const +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + result.append(" AudioOutput\n"); + snprintf(buffer, 255, " stream type(%d), left - right volume(%f, %f)\n", + mStreamType, mLeftVolume, mRightVolume); + result.append(buffer); + snprintf(buffer, 255, " msec per frame(%f), latency (%d)\n", + mMsecsPerFrame, (mTrack != 0) ? mTrack->latency() : -1); + result.append(buffer); + snprintf(buffer, 255, " aux effect id(%d), send level (%f)\n", + mAuxEffectId, mSendLevel); + result.append(buffer); + + ::write(fd, result.string(), result.size()); + if (mTrack != 0) { + mTrack->dump(fd, args); + } + return NO_ERROR; +} + +status_t MediaPlayerService::Client::dump(int fd, const Vector<String16>& args) const +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + result.append(" Client\n"); + snprintf(buffer, 255, " pid(%d), connId(%d), status(%d), looping(%s)\n", + mPid, mConnId, mStatus, mLoop?"true": "false"); + result.append(buffer); + write(fd, result.string(), result.size()); + if (mPlayer != NULL) { + mPlayer->dump(fd, args); + } + if (mAudioOutput != 0) { + mAudioOutput->dump(fd, args); + } + write(fd, "\n", 1); + return NO_ERROR; +} + +status_t MediaPlayerService::dump(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + if (checkCallingPermission(String16("android.permission.DUMP")) == false) { + snprintf(buffer, SIZE, "Permission Denial: " + "can't dump MediaPlayerService from pid=%d, uid=%d\n", + IPCThreadState::self()->getCallingPid(), + IPCThreadState::self()->getCallingUid()); + result.append(buffer); + } else { + Mutex::Autolock lock(mLock); + for (int i = 0, n = mClients.size(); i < n; ++i) { + sp<Client> c = mClients[i].promote(); + if (c != 0) c->dump(fd, args); + } + if (mMediaRecorderClients.size() == 0) { + result.append(" No media recorder client\n\n"); + } else { + for (int i = 0, n = mMediaRecorderClients.size(); i < n; ++i) { + sp<MediaRecorderClient> c = mMediaRecorderClients[i].promote(); + if (c != 0) { + snprintf(buffer, 255, " MediaRecorderClient pid(%d)\n", c->mPid); + result.append(buffer); + write(fd, result.string(), result.size()); + result = "\n"; + c->dump(fd, args); + } + } + } + + result.append(" Files opened and/or mapped:\n"); + snprintf(buffer, SIZE, "/proc/%d/maps", gettid()); + FILE *f = fopen(buffer, "r"); + if (f) { + while (!feof(f)) { + fgets(buffer, SIZE, f); + if (strstr(buffer, " /mnt/sdcard/") || + strstr(buffer, " /system/sounds/") || + strstr(buffer, " /data/") || + strstr(buffer, " /system/media/")) { + result.append(" "); + result.append(buffer); + } + } + fclose(f); + } else { + result.append("couldn't open "); + result.append(buffer); + result.append("\n"); + } + + snprintf(buffer, SIZE, "/proc/%d/fd", gettid()); + DIR *d = opendir(buffer); + if (d) { + struct dirent *ent; + while((ent = readdir(d)) != NULL) { + if (strcmp(ent->d_name,".") && strcmp(ent->d_name,"..")) { + snprintf(buffer, SIZE, "/proc/%d/fd/%s", gettid(), ent->d_name); + struct stat s; + if (lstat(buffer, &s) == 0) { + if ((s.st_mode & S_IFMT) == S_IFLNK) { + char linkto[256]; + int len = readlink(buffer, linkto, sizeof(linkto)); + if(len > 0) { + if(len > 255) { + linkto[252] = '.'; + linkto[253] = '.'; + linkto[254] = '.'; + linkto[255] = 0; + } else { + linkto[len] = 0; + } + if (strstr(linkto, "/mnt/sdcard/") == linkto || + strstr(linkto, "/system/sounds/") == linkto || + strstr(linkto, "/data/") == linkto || + strstr(linkto, "/system/media/") == linkto) { + result.append(" "); + result.append(buffer); + result.append(" -> "); + result.append(linkto); + result.append("\n"); + } + } + } else { + result.append(" unexpected type for "); + result.append(buffer); + result.append("\n"); + } + } + } + } + closedir(d); + } else { + result.append("couldn't open "); + result.append(buffer); + result.append("\n"); + } + + bool dumpMem = false; + for (size_t i = 0; i < args.size(); i++) { + if (args[i] == String16("-m")) { + dumpMem = true; + } + } + if (dumpMem) { + dumpMemoryAddresses(fd); + } + } + write(fd, result.string(), result.size()); + return NO_ERROR; +} + +void MediaPlayerService::removeClient(wp<Client> client) +{ + Mutex::Autolock lock(mLock); + mClients.remove(client); +} + +MediaPlayerService::Client::Client( + const sp<MediaPlayerService>& service, pid_t pid, + int32_t connId, const sp<IMediaPlayerClient>& client, + int audioSessionId, uid_t uid) +{ + ALOGV("Client(%d) constructor", connId); + mPid = pid; + mConnId = connId; + mService = service; + mClient = client; + mLoop = false; + mStatus = NO_INIT; + mAudioSessionId = audioSessionId; + mUID = uid; + mRetransmitEndpointValid = false; + +#if CALLBACK_ANTAGONIZER + ALOGD("create Antagonizer"); + mAntagonizer = new Antagonizer(notify, this); +#endif +} + +MediaPlayerService::Client::~Client() +{ + ALOGV("Client(%d) destructor pid = %d", mConnId, mPid); + mAudioOutput.clear(); + wp<Client> client(this); + disconnect(); + mService->removeClient(client); +} + +void MediaPlayerService::Client::disconnect() +{ + ALOGV("disconnect(%d) from pid %d", mConnId, mPid); + // grab local reference and clear main reference to prevent future + // access to object + sp<MediaPlayerBase> p; + { + Mutex::Autolock l(mLock); + p = mPlayer; + } + mClient.clear(); + + mPlayer.clear(); + + // clear the notification to prevent callbacks to dead client + // and reset the player. We assume the player will serialize + // access to itself if necessary. + if (p != 0) { + p->setNotifyCallback(0, 0); +#if CALLBACK_ANTAGONIZER + ALOGD("kill Antagonizer"); + mAntagonizer->kill(); +#endif + p->reset(); + } + + disconnectNativeWindow(); + + IPCThreadState::self()->flushCommands(); +} + +static player_type getDefaultPlayerType() { + char value[PROPERTY_VALUE_MAX]; + if (property_get("media.stagefright.use-nuplayer", value, NULL) + && (!strcmp("1", value) || !strcasecmp("true", value))) { + return NU_PLAYER; + } + + return STAGEFRIGHT_PLAYER; +} + +player_type getPlayerType(int fd, int64_t offset, int64_t length) +{ + 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 STAGEFRIGHT_PLAYER; + + // Some kind of MIDI? + EAS_DATA_HANDLE easdata; + if (EAS_Init(&easdata) == EAS_SUCCESS) { + EAS_FILE locator; + locator.path = NULL; + locator.fd = fd; + locator.offset = offset; + locator.length = length; + EAS_HANDLE eashandle; + if (EAS_OpenFile(easdata, &locator, &eashandle) == EAS_SUCCESS) { + EAS_CloseFile(easdata, eashandle); + EAS_Shutdown(easdata); + return SONIVOX_PLAYER; + } + EAS_Shutdown(easdata); + } + + return getDefaultPlayerType(); +} + +player_type getPlayerType(const char* url) +{ + if (TestPlayerStub::canBeUsed(url)) { + return TEST_PLAYER; + } + + if (!strncasecmp("http://", url, 7) + || !strncasecmp("https://", url, 8)) { + size_t len = strlen(url); + if (len >= 5 && !strcasecmp(".m3u8", &url[len - 5])) { + return NU_PLAYER; + } + + if (strstr(url,"m3u8")) { + return NU_PLAYER; + } + } + + if (!strncasecmp("rtsp://", url, 7)) { + return NU_PLAYER; + } + + if (!strncasecmp("aahRX://", url, 8)) { + return AAH_RX_PLAYER; + } + + // use MidiFile for MIDI extensions + int lenURL = strlen(url); + for (int i = 0; i < NELEM(FILE_EXTS); ++i) { + int len = strlen(FILE_EXTS[i].extension); + int start = lenURL - len; + if (start > 0) { + if (!strncasecmp(url + start, FILE_EXTS[i].extension, len)) { + return FILE_EXTS[i].playertype; + } + } + } + + return getDefaultPlayerType(); +} + +player_type MediaPlayerService::Client::getPlayerType(int fd, + int64_t offset, + int64_t length) +{ + // Until re-transmit functionality is added to the existing core android + // players, we use the special AAH TX player whenever we were configured + // for retransmission. + if (mRetransmitEndpointValid) { + return AAH_TX_PLAYER; + } + + return android::getPlayerType(fd, offset, length); +} + +player_type MediaPlayerService::Client::getPlayerType(const char* url) +{ + // Until re-transmit functionality is added to the existing core android + // players, we use the special AAH TX player whenever we were configured + // for retransmission. + if (mRetransmitEndpointValid) { + return AAH_TX_PLAYER; + } + + return android::getPlayerType(url); +} + +player_type MediaPlayerService::Client::getPlayerType( + const sp<IStreamSource> &source) { + // Until re-transmit functionality is added to the existing core android + // players, we use the special AAH TX player whenever we were configured + // for retransmission. + if (mRetransmitEndpointValid) { + return AAH_TX_PLAYER; + } + + return NU_PLAYER; +} + +static sp<MediaPlayerBase> createPlayer(player_type playerType, void* cookie, + notify_callback_f notifyFunc) +{ + sp<MediaPlayerBase> p; + switch (playerType) { + case SONIVOX_PLAYER: + ALOGV(" create MidiFile"); + p = new MidiFile(); + break; + case STAGEFRIGHT_PLAYER: + ALOGV(" create StagefrightPlayer"); + p = new StagefrightPlayer; + break; + case NU_PLAYER: + ALOGV(" create NuPlayer"); + p = new NuPlayerDriver; + break; + case TEST_PLAYER: + ALOGV("Create Test Player stub"); + p = new TestPlayerStub(); + break; + case AAH_RX_PLAYER: + ALOGV(" create A@H RX Player"); + p = createAAH_RXPlayer(); + break; + case AAH_TX_PLAYER: + ALOGV(" create A@H TX Player"); + p = createAAH_TXPlayer(); + break; + default: + ALOGE("Unknown player type: %d", playerType); + return NULL; + } + if (p != NULL) { + if (p->initCheck() == NO_ERROR) { + p->setNotifyCallback(cookie, notifyFunc); + } else { + p.clear(); + } + } + if (p == NULL) { + ALOGE("Failed to create player object"); + } + return p; +} + +sp<MediaPlayerBase> MediaPlayerService::Client::createPlayer(player_type playerType) +{ + // determine if we have the right player type + sp<MediaPlayerBase> p = mPlayer; + if ((p != NULL) && (p->playerType() != playerType)) { + ALOGV("delete player"); + p.clear(); + } + if (p == NULL) { + p = android::createPlayer(playerType, this, notify); + } + + if (p != NULL) { + p->setUID(mUID); + } + + return p; +} + +sp<MediaPlayerBase> MediaPlayerService::Client::setDataSource_pre( + player_type playerType) +{ + ALOGV("player type = %d", playerType); + + // create the right type of player + sp<MediaPlayerBase> p = createPlayer(playerType); + if (p == NULL) { + return p; + } + + if (!p->hardwareOutput()) { + mAudioOutput = new AudioOutput(mAudioSessionId); + static_cast<MediaPlayerInterface*>(p.get())->setAudioSink(mAudioOutput); + } + + return p; +} + +void MediaPlayerService::Client::setDataSource_post( + const sp<MediaPlayerBase>& p, + status_t status) +{ + ALOGV(" setDataSource"); + mStatus = status; + if (mStatus != OK) { + ALOGE(" error: %d", mStatus); + return; + } + + // Set the re-transmission endpoint if one was chosen. + if (mRetransmitEndpointValid) { + mStatus = p->setRetransmitEndpoint(&mRetransmitEndpoint); + if (mStatus != NO_ERROR) { + ALOGE("setRetransmitEndpoint error: %d", mStatus); + } + } + + if (mStatus == OK) { + mPlayer = p; + } +} + +status_t MediaPlayerService::Client::setDataSource( + const char *url, const KeyedVector<String8, String8> *headers) +{ + ALOGV("setDataSource(%s)", url); + if (url == NULL) + return UNKNOWN_ERROR; + + if ((strncmp(url, "http://", 7) == 0) || + (strncmp(url, "https://", 8) == 0) || + (strncmp(url, "rtsp://", 7) == 0)) { + if (!checkPermission("android.permission.INTERNET")) { + return PERMISSION_DENIED; + } + } + + if (strncmp(url, "content://", 10) == 0) { + // get a filedescriptor for the content Uri and + // pass it to the setDataSource(fd) method + + String16 url16(url); + int fd = android::openContentProviderFile(url16); + if (fd < 0) + { + ALOGE("Couldn't open fd for %s", url); + return UNKNOWN_ERROR; + } + setDataSource(fd, 0, 0x7fffffffffLL); // this sets mStatus + close(fd); + return mStatus; + } else { + player_type playerType = getPlayerType(url); + sp<MediaPlayerBase> p = setDataSource_pre(playerType); + if (p == NULL) { + return NO_INIT; + } + + setDataSource_post(p, p->setDataSource(url, headers)); + return mStatus; + } +} + +status_t MediaPlayerService::Client::setDataSource(int fd, int64_t offset, int64_t length) +{ + ALOGV("setDataSource fd=%d, offset=%lld, length=%lld", fd, offset, length); + struct stat sb; + int ret = fstat(fd, &sb); + if (ret != 0) { + ALOGE("fstat(%d) failed: %d, %s", fd, ret, strerror(errno)); + return UNKNOWN_ERROR; + } + + 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_size = %llu", sb.st_size); + + if (offset >= sb.st_size) { + ALOGE("offset error"); + ::close(fd); + return UNKNOWN_ERROR; + } + if (offset + length > sb.st_size) { + length = sb.st_size - offset; + ALOGV("calculated length = %lld", length); + } + + // Until re-transmit functionality is added to the existing core android + // players, we use the special AAH TX player whenever we were configured for + // retransmission. + player_type playerType = getPlayerType(fd, offset, length); + sp<MediaPlayerBase> p = setDataSource_pre(playerType); + if (p == NULL) { + return NO_INIT; + } + + // now set data source + setDataSource_post(p, p->setDataSource(fd, offset, length)); + return mStatus; +} + +status_t MediaPlayerService::Client::setDataSource( + const sp<IStreamSource> &source) { + // create the right type of player + // Until re-transmit functionality is added to the existing core android + // players, we use the special AAH TX player whenever we were configured for + // retransmission. + player_type playerType = getPlayerType(source); + sp<MediaPlayerBase> p = setDataSource_pre(playerType); + if (p == NULL) { + return NO_INIT; + } + + // now set data source + setDataSource_post(p, p->setDataSource(source)); + return mStatus; +} + +void MediaPlayerService::Client::disconnectNativeWindow() { + if (mConnectedWindow != NULL) { + status_t err = native_window_api_disconnect(mConnectedWindow.get(), + NATIVE_WINDOW_API_MEDIA); + + if (err != OK) { + ALOGW("native_window_api_disconnect returned an error: %s (%d)", + strerror(-err), err); + } + } + mConnectedWindow.clear(); +} + +status_t MediaPlayerService::Client::setVideoSurfaceTexture( + const sp<ISurfaceTexture>& surfaceTexture) +{ + ALOGV("[%d] setVideoSurfaceTexture(%p)", mConnId, surfaceTexture.get()); + sp<MediaPlayerBase> p = getPlayer(); + if (p == 0) return UNKNOWN_ERROR; + + sp<IBinder> binder(surfaceTexture == NULL ? NULL : + surfaceTexture->asBinder()); + if (mConnectedWindowBinder == binder) { + return OK; + } + + sp<ANativeWindow> anw; + if (surfaceTexture != NULL) { + anw = new SurfaceTextureClient(surfaceTexture); + status_t err = native_window_api_connect(anw.get(), + NATIVE_WINDOW_API_MEDIA); + + if (err != OK) { + ALOGE("setVideoSurfaceTexture failed: %d", err); + // Note that we must do the reset before disconnecting from the ANW. + // Otherwise queue/dequeue calls could be made on the disconnected + // ANW, which may result in errors. + reset(); + + disconnectNativeWindow(); + + return err; + } + } + + // Note that we must set the player's new SurfaceTexture before + // disconnecting the old one. Otherwise queue/dequeue calls could be made + // on the disconnected ANW, which may result in errors. + status_t err = p->setVideoSurfaceTexture(surfaceTexture); + + disconnectNativeWindow(); + + mConnectedWindow = anw; + + if (err == OK) { + mConnectedWindowBinder = binder; + } else { + disconnectNativeWindow(); + } + + return err; +} + +status_t MediaPlayerService::Client::invoke(const Parcel& request, + Parcel *reply) +{ + sp<MediaPlayerBase> p = getPlayer(); + if (p == NULL) return UNKNOWN_ERROR; + return p->invoke(request, reply); +} + +// This call doesn't need to access the native player. +status_t MediaPlayerService::Client::setMetadataFilter(const Parcel& filter) +{ + status_t status; + media::Metadata::Filter allow, drop; + + if (unmarshallFilter(filter, &allow, &status) && + unmarshallFilter(filter, &drop, &status)) { + Mutex::Autolock lock(mLock); + + mMetadataAllow = allow; + mMetadataDrop = drop; + } + return status; +} + +status_t MediaPlayerService::Client::getMetadata( + bool update_only, bool apply_filter, Parcel *reply) +{ + sp<MediaPlayerBase> player = getPlayer(); + if (player == 0) return UNKNOWN_ERROR; + + status_t status; + // Placeholder for the return code, updated by the caller. + reply->writeInt32(-1); + + media::Metadata::Filter ids; + + // We don't block notifications while we fetch the data. We clear + // mMetadataUpdated first so we don't lose notifications happening + // during the rest of this call. + { + Mutex::Autolock lock(mLock); + if (update_only) { + ids = mMetadataUpdated; + } + mMetadataUpdated.clear(); + } + + media::Metadata metadata(reply); + + metadata.appendHeader(); + status = player->getMetadata(ids, reply); + + if (status != OK) { + metadata.resetParcel(); + ALOGE("getMetadata failed %d", status); + return status; + } + + // FIXME: Implement filtering on the result. Not critical since + // filtering takes place on the update notifications already. This + // would be when all the metadata are fetch and a filter is set. + + // Everything is fine, update the metadata length. + metadata.updateLength(); + return OK; +} + +status_t MediaPlayerService::Client::prepareAsync() +{ + ALOGV("[%d] prepareAsync", mConnId); + sp<MediaPlayerBase> p = getPlayer(); + if (p == 0) return UNKNOWN_ERROR; + status_t ret = p->prepareAsync(); +#if CALLBACK_ANTAGONIZER + ALOGD("start Antagonizer"); + if (ret == NO_ERROR) mAntagonizer->start(); +#endif + return ret; +} + +status_t MediaPlayerService::Client::start() +{ + ALOGV("[%d] start", mConnId); + sp<MediaPlayerBase> p = getPlayer(); + if (p == 0) return UNKNOWN_ERROR; + p->setLooping(mLoop); + return p->start(); +} + +status_t MediaPlayerService::Client::stop() +{ + ALOGV("[%d] stop", mConnId); + sp<MediaPlayerBase> p = getPlayer(); + if (p == 0) return UNKNOWN_ERROR; + return p->stop(); +} + +status_t MediaPlayerService::Client::pause() +{ + ALOGV("[%d] pause", mConnId); + sp<MediaPlayerBase> p = getPlayer(); + if (p == 0) return UNKNOWN_ERROR; + return p->pause(); +} + +status_t MediaPlayerService::Client::isPlaying(bool* state) +{ + *state = false; + sp<MediaPlayerBase> p = getPlayer(); + if (p == 0) return UNKNOWN_ERROR; + *state = p->isPlaying(); + ALOGV("[%d] isPlaying: %d", mConnId, *state); + return NO_ERROR; +} + +status_t MediaPlayerService::Client::getCurrentPosition(int *msec) +{ + ALOGV("getCurrentPosition"); + sp<MediaPlayerBase> p = getPlayer(); + if (p == 0) return UNKNOWN_ERROR; + status_t ret = p->getCurrentPosition(msec); + if (ret == NO_ERROR) { + ALOGV("[%d] getCurrentPosition = %d", mConnId, *msec); + } else { + ALOGE("getCurrentPosition returned %d", ret); + } + return ret; +} + +status_t MediaPlayerService::Client::getDuration(int *msec) +{ + ALOGV("getDuration"); + sp<MediaPlayerBase> p = getPlayer(); + if (p == 0) return UNKNOWN_ERROR; + status_t ret = p->getDuration(msec); + if (ret == NO_ERROR) { + ALOGV("[%d] getDuration = %d", mConnId, *msec); + } else { + ALOGE("getDuration returned %d", ret); + } + return ret; +} + +status_t MediaPlayerService::Client::setNextPlayer(const sp<IMediaPlayer>& player) { + ALOGV("setNextPlayer"); + Mutex::Autolock l(mLock); + sp<Client> c = static_cast<Client*>(player.get()); + mNextClient = c; + if (mAudioOutput != NULL && c != NULL) { + mAudioOutput->setNextOutput(c->mAudioOutput); + } else { + ALOGE("no current audio output"); + } + return OK; +} + + +status_t MediaPlayerService::Client::seekTo(int msec) +{ + ALOGV("[%d] seekTo(%d)", mConnId, msec); + sp<MediaPlayerBase> p = getPlayer(); + if (p == 0) return UNKNOWN_ERROR; + return p->seekTo(msec); +} + +status_t MediaPlayerService::Client::reset() +{ + ALOGV("[%d] reset", mConnId); + mRetransmitEndpointValid = false; + sp<MediaPlayerBase> p = getPlayer(); + if (p == 0) return UNKNOWN_ERROR; + return p->reset(); +} + +status_t MediaPlayerService::Client::setAudioStreamType(audio_stream_type_t type) +{ + ALOGV("[%d] setAudioStreamType(%d)", mConnId, type); + // TODO: for hardware output, call player instead + Mutex::Autolock l(mLock); + if (mAudioOutput != 0) mAudioOutput->setAudioStreamType(type); + return NO_ERROR; +} + +status_t MediaPlayerService::Client::setLooping(int loop) +{ + ALOGV("[%d] setLooping(%d)", mConnId, loop); + mLoop = loop; + sp<MediaPlayerBase> p = getPlayer(); + if (p != 0) return p->setLooping(loop); + return NO_ERROR; +} + +status_t MediaPlayerService::Client::setVolume(float leftVolume, float rightVolume) +{ + ALOGV("[%d] setVolume(%f, %f)", mConnId, leftVolume, rightVolume); + + // for hardware output, call player instead + sp<MediaPlayerBase> p = getPlayer(); + { + Mutex::Autolock l(mLock); + if (p != 0 && p->hardwareOutput()) { + MediaPlayerHWInterface* hwp = + reinterpret_cast<MediaPlayerHWInterface*>(p.get()); + return hwp->setVolume(leftVolume, rightVolume); + } else { + if (mAudioOutput != 0) mAudioOutput->setVolume(leftVolume, rightVolume); + return NO_ERROR; + } + } + + return NO_ERROR; +} + +status_t MediaPlayerService::Client::setAuxEffectSendLevel(float level) +{ + ALOGV("[%d] setAuxEffectSendLevel(%f)", mConnId, level); + Mutex::Autolock l(mLock); + if (mAudioOutput != 0) return mAudioOutput->setAuxEffectSendLevel(level); + return NO_ERROR; +} + +status_t MediaPlayerService::Client::attachAuxEffect(int effectId) +{ + ALOGV("[%d] attachAuxEffect(%d)", mConnId, effectId); + Mutex::Autolock l(mLock); + if (mAudioOutput != 0) return mAudioOutput->attachAuxEffect(effectId); + return NO_ERROR; +} + +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); +} + +status_t MediaPlayerService::Client::getParameter(int key, Parcel *reply) { + ALOGV("[%d] getParameter(%d)", mConnId, key); + sp<MediaPlayerBase> p = getPlayer(); + if (p == 0) return UNKNOWN_ERROR; + return p->getParameter(key, reply); +} + +status_t MediaPlayerService::Client::setRetransmitEndpoint( + const struct sockaddr_in* endpoint) { + + if (NULL != endpoint) { + uint32_t a = ntohl(endpoint->sin_addr.s_addr); + uint16_t p = ntohs(endpoint->sin_port); + ALOGV("[%d] setRetransmitEndpoint(%u.%u.%u.%u:%hu)", mConnId, + (a >> 24), (a >> 16) & 0xFF, (a >> 8) & 0xFF, (a & 0xFF), p); + } else { + ALOGV("[%d] setRetransmitEndpoint = <none>", mConnId); + } + + sp<MediaPlayerBase> p = getPlayer(); + + // Right now, the only valid time to set a retransmit endpoint is before + // player selection has been made (since the presence or absence of a + // retransmit endpoint is going to determine which player is selected during + // setDataSource). + if (p != 0) return INVALID_OPERATION; + + if (NULL != endpoint) { + mRetransmitEndpoint = *endpoint; + mRetransmitEndpointValid = true; + } else { + mRetransmitEndpointValid = false; + } + + return NO_ERROR; +} + +void MediaPlayerService::Client::notify( + void* cookie, int msg, int ext1, int ext2, const Parcel *obj) +{ + Client* client = static_cast<Client*>(cookie); + + { + Mutex::Autolock l(client->mLock); + if (msg == MEDIA_PLAYBACK_COMPLETE && client->mNextClient != NULL) { + client->mAudioOutput->switchToNextOutput(); + client->mNextClient->start(); + client->mNextClient->mClient->notify(MEDIA_INFO, MEDIA_INFO_STARTED_AS_NEXT, 0, obj); + } + } + + if (MEDIA_INFO == msg && + MEDIA_INFO_METADATA_UPDATE == ext1) { + const media::Metadata::Type metadata_type = ext2; + + if(client->shouldDropMetadata(metadata_type)) { + return; + } + + // Update the list of metadata that have changed. getMetadata + // also access mMetadataUpdated and clears it. + client->addNewMetadataUpdate(metadata_type); + } + ALOGV("[%d] notify (%p, %d, %d, %d)", client->mConnId, cookie, msg, ext1, ext2); + client->mClient->notify(msg, ext1, ext2, obj); +} + + +bool MediaPlayerService::Client::shouldDropMetadata(media::Metadata::Type code) const +{ + Mutex::Autolock lock(mLock); + + if (findMetadata(mMetadataDrop, code)) { + return true; + } + + if (mMetadataAllow.isEmpty() || findMetadata(mMetadataAllow, code)) { + return false; + } else { + return true; + } +} + + +void MediaPlayerService::Client::addNewMetadataUpdate(media::Metadata::Type metadata_type) { + Mutex::Autolock lock(mLock); + if (mMetadataUpdated.indexOf(metadata_type) < 0) { + mMetadataUpdated.add(metadata_type); + } +} + +#if CALLBACK_ANTAGONIZER +const int Antagonizer::interval = 10000; // 10 msecs + +Antagonizer::Antagonizer(notify_callback_f cb, void* client) : + mExit(false), mActive(false), mClient(client), mCb(cb) +{ + createThread(callbackThread, this); +} + +void Antagonizer::kill() +{ + Mutex::Autolock _l(mLock); + mActive = false; + mExit = true; + mCondition.wait(mLock); +} + +int Antagonizer::callbackThread(void* user) +{ + ALOGD("Antagonizer started"); + Antagonizer* p = reinterpret_cast<Antagonizer*>(user); + while (!p->mExit) { + if (p->mActive) { + ALOGV("send event"); + p->mCb(p->mClient, 0, 0, 0); + } + usleep(interval); + } + Mutex::Autolock _l(p->mLock); + p->mCondition.signal(); + ALOGD("Antagonizer stopped"); + return 0; +} +#endif + +static size_t kDefaultHeapSize = 1024 * 1024; // 1MB + +sp<IMemory> MediaPlayerService::decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat) +{ + ALOGV("decode(%s)", url); + sp<MemoryBase> mem; + sp<MediaPlayerBase> player; + + // Protect our precious, precious DRMd ringtones by only allowing + // decoding of http, but not filesystem paths or content Uris. + // If the application wants to decode those, it should open a + // filedescriptor for them and use that. + if (url != NULL && strncmp(url, "http://", 7) != 0) { + ALOGD("Can't decode %s by path, use filedescriptor instead", url); + return mem; + } + + player_type playerType = getPlayerType(url); + ALOGV("player type = %d", playerType); + + // create the right type of player + sp<AudioCache> cache = new AudioCache(url); + player = android::createPlayer(playerType, cache.get(), cache->notify); + if (player == NULL) goto Exit; + if (player->hardwareOutput()) goto Exit; + + static_cast<MediaPlayerInterface*>(player.get())->setAudioSink(cache); + + // set data source + if (player->setDataSource(url) != NO_ERROR) goto Exit; + + ALOGV("prepare"); + player->prepareAsync(); + + ALOGV("wait for prepare"); + if (cache->wait() != NO_ERROR) goto Exit; + + ALOGV("start"); + player->start(); + + ALOGV("wait for playback complete"); + cache->wait(); + // in case of error, return what was successfully decoded. + if (cache->size() == 0) { + goto Exit; + } + + mem = new MemoryBase(cache->getHeap(), 0, cache->size()); + *pSampleRate = cache->sampleRate(); + *pNumChannels = cache->channelCount(); + *pFormat = cache->format(); + ALOGV("return memory @ %p, sampleRate=%u, channelCount = %d, format = %d", mem->pointer(), *pSampleRate, *pNumChannels, *pFormat); + +Exit: + if (player != 0) player->reset(); + return mem; +} + +sp<IMemory> MediaPlayerService::decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat) +{ + ALOGV("decode(%d, %lld, %lld)", fd, offset, length); + sp<MemoryBase> mem; + sp<MediaPlayerBase> player; + + player_type playerType = getPlayerType(fd, offset, length); + ALOGV("player type = %d", playerType); + + // create the right type of player + sp<AudioCache> cache = new AudioCache("decode_fd"); + player = android::createPlayer(playerType, cache.get(), cache->notify); + if (player == NULL) goto Exit; + if (player->hardwareOutput()) goto Exit; + + static_cast<MediaPlayerInterface*>(player.get())->setAudioSink(cache); + + // set data source + if (player->setDataSource(fd, offset, length) != NO_ERROR) goto Exit; + + ALOGV("prepare"); + player->prepareAsync(); + + ALOGV("wait for prepare"); + if (cache->wait() != NO_ERROR) goto Exit; + + ALOGV("start"); + player->start(); + + ALOGV("wait for playback complete"); + cache->wait(); + // in case of error, return what was successfully decoded. + if (cache->size() == 0) { + goto Exit; + } + + mem = new MemoryBase(cache->getHeap(), 0, cache->size()); + *pSampleRate = cache->sampleRate(); + *pNumChannels = cache->channelCount(); + *pFormat = cache->format(); + ALOGV("return memory @ %p, sampleRate=%u, channelCount = %d, format = %d", mem->pointer(), *pSampleRate, *pNumChannels, *pFormat); + +Exit: + if (player != 0) player->reset(); + ::close(fd); + return mem; +} + + +#undef LOG_TAG +#define LOG_TAG "AudioSink" +MediaPlayerService::AudioOutput::AudioOutput(int sessionId) + : mCallback(NULL), + mCallbackCookie(NULL), + mCallbackData(NULL), + mSessionId(sessionId) { + ALOGV("AudioOutput(%d)", sessionId); + mTrack = 0; + mRecycledTrack = 0; + mStreamType = AUDIO_STREAM_MUSIC; + mLeftVolume = 1.0; + mRightVolume = 1.0; + mPlaybackRatePermille = 1000; + mSampleRateHz = 0; + mMsecsPerFrame = 0; + mAuxEffectId = 0; + mSendLevel = 0.0; + setMinBufferCount(); +} + +MediaPlayerService::AudioOutput::~AudioOutput() +{ + close(); + delete mRecycledTrack; + delete mCallbackData; +} + +void MediaPlayerService::AudioOutput::setMinBufferCount() +{ + char value[PROPERTY_VALUE_MAX]; + if (property_get("ro.kernel.qemu", value, 0)) { + mIsOnEmulator = true; + mMinBufferCount = 12; // to prevent systematic buffer underrun for emulator + } +} + +bool MediaPlayerService::AudioOutput::isOnEmulator() +{ + setMinBufferCount(); + return mIsOnEmulator; +} + +int MediaPlayerService::AudioOutput::getMinBufferCount() +{ + setMinBufferCount(); + return mMinBufferCount; +} + +ssize_t MediaPlayerService::AudioOutput::bufferSize() const +{ + if (mTrack == 0) return NO_INIT; + return mTrack->frameCount() * frameSize(); +} + +ssize_t MediaPlayerService::AudioOutput::frameCount() const +{ + if (mTrack == 0) return NO_INIT; + return mTrack->frameCount(); +} + +ssize_t MediaPlayerService::AudioOutput::channelCount() const +{ + if (mTrack == 0) return NO_INIT; + return mTrack->channelCount(); +} + +ssize_t MediaPlayerService::AudioOutput::frameSize() const +{ + if (mTrack == 0) return NO_INIT; + return mTrack->frameSize(); +} + +uint32_t MediaPlayerService::AudioOutput::latency () const +{ + if (mTrack == 0) return 0; + return mTrack->latency(); +} + +float MediaPlayerService::AudioOutput::msecsPerFrame() const +{ + return mMsecsPerFrame; +} + +status_t MediaPlayerService::AudioOutput::getPosition(uint32_t *position) +{ + if (mTrack == 0) return NO_INIT; + return mTrack->getPosition(position); +} + +status_t MediaPlayerService::AudioOutput::open( + uint32_t sampleRate, int channelCount, audio_channel_mask_t channelMask, + audio_format_t format, int bufferCount, + AudioCallback cb, void *cookie) +{ + mCallback = cb; + mCallbackCookie = cookie; + + // Check argument "bufferCount" against the mininum buffer count + if (bufferCount < mMinBufferCount) { + ALOGD("bufferCount (%d) is too small and increased to %d", bufferCount, mMinBufferCount); + bufferCount = mMinBufferCount; + + } + ALOGV("open(%u, %d, 0x%x, %d, %d, %d)", sampleRate, channelCount, channelMask, + format, bufferCount, mSessionId); + int afSampleRate; + int afFrameCount; + int frameCount; + + if (AudioSystem::getOutputFrameCount(&afFrameCount, mStreamType) != NO_ERROR) { + return NO_INIT; + } + if (AudioSystem::getOutputSamplingRate(&afSampleRate, mStreamType) != NO_ERROR) { + return NO_INIT; + } + + frameCount = (sampleRate*afFrameCount*bufferCount)/afSampleRate; + + if (channelMask == CHANNEL_MASK_USE_CHANNEL_ORDER) { + channelMask = audio_channel_out_mask_from_count(channelCount); + if (0 == channelMask) { + ALOGE("open() error, can\'t derive mask for %d audio channels", channelCount); + return NO_INIT; + } + } + if (mRecycledTrack) { + // check if the existing track can be reused as-is, or if a new track needs to be created. + + bool reuse = true; + if ((mCallbackData == NULL && mCallback != NULL) || + (mCallbackData != NULL && mCallback == NULL)) { + // recycled track uses callbacks but the caller wants to use writes, or vice versa + ALOGV("can't chain callback and write"); + reuse = false; + } else if ((mRecycledTrack->getSampleRate() != sampleRate) || + (mRecycledTrack->channelCount() != channelCount) || + (mRecycledTrack->frameCount() != frameCount)) { + ALOGV("samplerate, channelcount or framecount differ"); + reuse = false; + } + if (reuse) { + ALOGV("chaining to next output"); + close(); + mTrack = mRecycledTrack; + mRecycledTrack = NULL; + if (mCallbackData != NULL) { + mCallbackData->setOutput(this); + } + return OK; + } + + // if we're not going to reuse the track, unblock and flush it + if (mCallbackData != NULL) { + mCallbackData->setOutput(NULL); + mCallbackData->endTrackSwitch(); + } + mRecycledTrack->flush(); + delete mRecycledTrack; + mRecycledTrack = NULL; + delete mCallbackData; + mCallbackData = NULL; + close(); + } + + AudioTrack *t; + if (mCallback != NULL) { + mCallbackData = new CallbackData(this); + t = new AudioTrack( + mStreamType, + sampleRate, + format, + channelMask, + frameCount, + AUDIO_POLICY_OUTPUT_FLAG_NONE, + CallbackWrapper, + mCallbackData, + 0, // notification frames + mSessionId); + } else { + t = new AudioTrack( + mStreamType, + sampleRate, + format, + channelMask, + frameCount, + AUDIO_POLICY_OUTPUT_FLAG_NONE, + NULL, + NULL, + 0, + mSessionId); + } + + if ((t == 0) || (t->initCheck() != NO_ERROR)) { + ALOGE("Unable to create audio track"); + delete t; + return NO_INIT; + } + + ALOGV("setVolume"); + t->setVolume(mLeftVolume, mRightVolume); + + mSampleRateHz = sampleRate; + mMsecsPerFrame = mPlaybackRatePermille / (float) sampleRate; + mTrack = t; + + status_t res = t->setSampleRate(mPlaybackRatePermille * mSampleRateHz / 1000); + if (res != NO_ERROR) { + return res; + } + t->setAuxEffectSendLevel(mSendLevel); + return t->attachAuxEffect(mAuxEffectId);; +} + +void MediaPlayerService::AudioOutput::start() +{ + ALOGV("start"); + if (mCallbackData != NULL) { + mCallbackData->endTrackSwitch(); + } + if (mTrack) { + mTrack->setVolume(mLeftVolume, mRightVolume); + mTrack->setAuxEffectSendLevel(mSendLevel); + mTrack->start(); + } +} + +void MediaPlayerService::AudioOutput::setNextOutput(const sp<AudioOutput>& nextOutput) { + mNextOutput = nextOutput; +} + + +void MediaPlayerService::AudioOutput::switchToNextOutput() { + ALOGV("switchToNextOutput"); + if (mNextOutput != NULL) { + if (mCallbackData != NULL) { + mCallbackData->beginTrackSwitch(); + } + delete mNextOutput->mCallbackData; + mNextOutput->mCallbackData = mCallbackData; + mCallbackData = NULL; + mNextOutput->mRecycledTrack = mTrack; + mTrack = NULL; + mNextOutput->mSampleRateHz = mSampleRateHz; + mNextOutput->mMsecsPerFrame = mMsecsPerFrame; + } +} + +ssize_t MediaPlayerService::AudioOutput::write(const void* buffer, size_t size) +{ + LOG_FATAL_IF(mCallback != NULL, "Don't call write if supplying a callback."); + + //ALOGV("write(%p, %u)", buffer, size); + if (mTrack) { + ssize_t ret = mTrack->write(buffer, size); + return ret; + } + return NO_INIT; +} + +void MediaPlayerService::AudioOutput::stop() +{ + ALOGV("stop"); + if (mTrack) mTrack->stop(); +} + +void MediaPlayerService::AudioOutput::flush() +{ + ALOGV("flush"); + if (mTrack) mTrack->flush(); +} + +void MediaPlayerService::AudioOutput::pause() +{ + ALOGV("pause"); + if (mTrack) mTrack->pause(); +} + +void MediaPlayerService::AudioOutput::close() +{ + ALOGV("close"); + delete mTrack; + mTrack = 0; +} + +void MediaPlayerService::AudioOutput::setVolume(float left, float right) +{ + ALOGV("setVolume(%f, %f)", left, right); + mLeftVolume = left; + mRightVolume = right; + if (mTrack) { + mTrack->setVolume(left, right); + } +} + +status_t MediaPlayerService::AudioOutput::setPlaybackRatePermille(int32_t ratePermille) +{ + ALOGV("setPlaybackRatePermille(%d)", ratePermille); + status_t res = NO_ERROR; + if (mTrack) { + res = mTrack->setSampleRate(ratePermille * mSampleRateHz / 1000); + } else { + res = NO_INIT; + } + mPlaybackRatePermille = ratePermille; + if (mSampleRateHz != 0) { + mMsecsPerFrame = mPlaybackRatePermille / (float) mSampleRateHz; + } + return res; +} + +status_t MediaPlayerService::AudioOutput::setAuxEffectSendLevel(float level) +{ + ALOGV("setAuxEffectSendLevel(%f)", level); + mSendLevel = level; + if (mTrack) { + return mTrack->setAuxEffectSendLevel(level); + } + return NO_ERROR; +} + +status_t MediaPlayerService::AudioOutput::attachAuxEffect(int effectId) +{ + ALOGV("attachAuxEffect(%d)", effectId); + mAuxEffectId = effectId; + if (mTrack) { + return mTrack->attachAuxEffect(effectId); + } + return NO_ERROR; +} + +// static +void MediaPlayerService::AudioOutput::CallbackWrapper( + int event, void *cookie, void *info) { + //ALOGV("callbackwrapper"); + if (event != AudioTrack::EVENT_MORE_DATA) { + return; + } + + CallbackData *data = (CallbackData*)cookie; + data->lock(); + AudioOutput *me = data->getOutput(); + AudioTrack::Buffer *buffer = (AudioTrack::Buffer *)info; + if (me == NULL) { + // no output set, likely because the track was scheduled to be reused + // by another player, but the format turned out to be incompatible. + data->unlock(); + buffer->size = 0; + return; + } + + size_t actualSize = (*me->mCallback)( + me, buffer->raw, buffer->size, me->mCallbackCookie); + + if (actualSize == 0 && buffer->size > 0 && me->mNextOutput == NULL) { + // We've reached EOS but the audio track is not stopped yet, + // keep playing silence. + + memset(buffer->raw, 0, buffer->size); + actualSize = buffer->size; + } + + buffer->size = actualSize; + data->unlock(); +} + +int MediaPlayerService::AudioOutput::getSessionId() +{ + return mSessionId; +} + +#undef LOG_TAG +#define LOG_TAG "AudioCache" +MediaPlayerService::AudioCache::AudioCache(const char* name) : + mChannelCount(0), mFrameCount(1024), mSampleRate(0), mSize(0), + mError(NO_ERROR), mCommandComplete(false) +{ + // create ashmem heap + mHeap = new MemoryHeapBase(kDefaultHeapSize, 0, name); +} + +uint32_t MediaPlayerService::AudioCache::latency () const +{ + return 0; +} + +float MediaPlayerService::AudioCache::msecsPerFrame() const +{ + return mMsecsPerFrame; +} + +status_t MediaPlayerService::AudioCache::getPosition(uint32_t *position) +{ + if (position == 0) return BAD_VALUE; + *position = mSize; + return NO_ERROR; +} + +//////////////////////////////////////////////////////////////////////////////// + +struct CallbackThread : public Thread { + CallbackThread(const wp<MediaPlayerBase::AudioSink> &sink, + MediaPlayerBase::AudioSink::AudioCallback cb, + void *cookie); + +protected: + virtual ~CallbackThread(); + + virtual bool threadLoop(); + +private: + wp<MediaPlayerBase::AudioSink> mSink; + MediaPlayerBase::AudioSink::AudioCallback mCallback; + void *mCookie; + void *mBuffer; + size_t mBufferSize; + + CallbackThread(const CallbackThread &); + CallbackThread &operator=(const CallbackThread &); +}; + +CallbackThread::CallbackThread( + const wp<MediaPlayerBase::AudioSink> &sink, + MediaPlayerBase::AudioSink::AudioCallback cb, + void *cookie) + : mSink(sink), + mCallback(cb), + mCookie(cookie), + mBuffer(NULL), + mBufferSize(0) { +} + +CallbackThread::~CallbackThread() { + if (mBuffer) { + free(mBuffer); + mBuffer = NULL; + } +} + +bool CallbackThread::threadLoop() { + sp<MediaPlayerBase::AudioSink> sink = mSink.promote(); + if (sink == NULL) { + return false; + } + + if (mBuffer == NULL) { + mBufferSize = sink->bufferSize(); + mBuffer = malloc(mBufferSize); + } + + size_t actualSize = + (*mCallback)(sink.get(), mBuffer, mBufferSize, mCookie); + + if (actualSize > 0) { + sink->write(mBuffer, actualSize); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +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) +{ + ALOGV("open(%u, %d, 0x%x, %d, %d)", sampleRate, channelCount, channelMask, format, bufferCount); + if (mHeap->getHeapID() < 0) { + return NO_INIT; + } + + mSampleRate = sampleRate; + mChannelCount = (uint16_t)channelCount; + mFormat = format; + mMsecsPerFrame = 1.e3 / (float) sampleRate; + + if (cb != NULL) { + mCallbackThread = new CallbackThread(this, cb, cookie); + } + return NO_ERROR; +} + +void MediaPlayerService::AudioCache::start() { + if (mCallbackThread != NULL) { + mCallbackThread->run("AudioCache callback"); + } +} + +void MediaPlayerService::AudioCache::stop() { + if (mCallbackThread != NULL) { + mCallbackThread->requestExitAndWait(); + } +} + +ssize_t MediaPlayerService::AudioCache::write(const void* buffer, size_t size) +{ + ALOGV("write(%p, %u)", buffer, size); + if ((buffer == 0) || (size == 0)) return size; + + uint8_t* p = static_cast<uint8_t*>(mHeap->getBase()); + if (p == NULL) return NO_INIT; + p += mSize; + ALOGV("memcpy(%p, %p, %u)", p, buffer, size); + if (mSize + size > mHeap->getSize()) { + ALOGE("Heap size overflow! req size: %d, max size: %d", (mSize + size), mHeap->getSize()); + size = mHeap->getSize() - mSize; + } + memcpy(p, buffer, size); + mSize += size; + return size; +} + +// call with lock held +status_t MediaPlayerService::AudioCache::wait() +{ + Mutex::Autolock lock(mLock); + while (!mCommandComplete) { + mSignal.wait(mLock); + } + mCommandComplete = false; + + if (mError == NO_ERROR) { + ALOGV("wait - success"); + } else { + ALOGV("wait - error"); + } + return mError; +} + +void MediaPlayerService::AudioCache::notify( + 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); + + // ignore buffering messages + switch (msg) + { + case MEDIA_ERROR: + ALOGE("Error %d, %d occurred", ext1, ext2); + p->mError = ext1; + break; + case MEDIA_PREPARED: + ALOGV("prepared"); + break; + case MEDIA_PLAYBACK_COMPLETE: + ALOGV("playback complete"); + break; + default: + ALOGV("ignored"); + return; + } + + // wake up thread + Mutex::Autolock lock(p->mLock); + p->mCommandComplete = true; + p->mSignal.signal(); +} + +int MediaPlayerService::AudioCache::getSessionId() +{ + return 0; +} + +void MediaPlayerService::addBatteryData(uint32_t params) +{ + Mutex::Autolock lock(mLock); + + int32_t time = systemTime() / 1000000L; + + // change audio output devices. This notification comes from AudioFlinger + if ((params & kBatteryDataSpeakerOn) + || (params & kBatteryDataOtherAudioDeviceOn)) { + + int deviceOn[NUM_AUDIO_DEVICES]; + for (int i = 0; i < NUM_AUDIO_DEVICES; i++) { + deviceOn[i] = 0; + } + + if ((params & kBatteryDataSpeakerOn) + && (params & kBatteryDataOtherAudioDeviceOn)) { + deviceOn[SPEAKER_AND_OTHER] = 1; + } else if (params & kBatteryDataSpeakerOn) { + deviceOn[SPEAKER] = 1; + } else { + deviceOn[OTHER_AUDIO_DEVICE] = 1; + } + + for (int i = 0; i < NUM_AUDIO_DEVICES; i++) { + if (mBatteryAudio.deviceOn[i] != deviceOn[i]){ + + if (mBatteryAudio.refCount > 0) { // if playing audio + if (!deviceOn[i]) { + mBatteryAudio.lastTime[i] += time; + mBatteryAudio.totalTime[i] += mBatteryAudio.lastTime[i]; + mBatteryAudio.lastTime[i] = 0; + } else { + mBatteryAudio.lastTime[i] = 0 - time; + } + } + + mBatteryAudio.deviceOn[i] = deviceOn[i]; + } + } + return; + } + + // an sudio stream is started + if (params & kBatteryDataAudioFlingerStart) { + // record the start time only if currently no other audio + // is being played + if (mBatteryAudio.refCount == 0) { + for (int i = 0; i < NUM_AUDIO_DEVICES; i++) { + if (mBatteryAudio.deviceOn[i]) { + mBatteryAudio.lastTime[i] -= time; + } + } + } + + mBatteryAudio.refCount ++; + return; + + } else if (params & kBatteryDataAudioFlingerStop) { + if (mBatteryAudio.refCount <= 0) { + ALOGW("Battery track warning: refCount is <= 0"); + return; + } + + // record the stop time only if currently this is the only + // audio being played + if (mBatteryAudio.refCount == 1) { + for (int i = 0; i < NUM_AUDIO_DEVICES; i++) { + if (mBatteryAudio.deviceOn[i]) { + mBatteryAudio.lastTime[i] += time; + mBatteryAudio.totalTime[i] += mBatteryAudio.lastTime[i]; + mBatteryAudio.lastTime[i] = 0; + } + } + } + + mBatteryAudio.refCount --; + return; + } + + int uid = IPCThreadState::self()->getCallingUid(); + if (uid == AID_MEDIA) { + return; + } + int index = mBatteryData.indexOfKey(uid); + + if (index < 0) { // create a new entry for this UID + BatteryUsageInfo info; + info.audioTotalTime = 0; + info.videoTotalTime = 0; + info.audioLastTime = 0; + info.videoLastTime = 0; + info.refCount = 0; + + if (mBatteryData.add(uid, info) == NO_MEMORY) { + ALOGE("Battery track error: no memory for new app"); + return; + } + } + + BatteryUsageInfo &info = mBatteryData.editValueFor(uid); + + if (params & kBatteryDataCodecStarted) { + if (params & kBatteryDataTrackAudio) { + info.audioLastTime -= time; + info.refCount ++; + } + if (params & kBatteryDataTrackVideo) { + info.videoLastTime -= time; + info.refCount ++; + } + } else { + if (info.refCount == 0) { + ALOGW("Battery track warning: refCount is already 0"); + return; + } else if (info.refCount < 0) { + ALOGE("Battery track error: refCount < 0"); + mBatteryData.removeItem(uid); + return; + } + + if (params & kBatteryDataTrackAudio) { + info.audioLastTime += time; + info.refCount --; + } + if (params & kBatteryDataTrackVideo) { + info.videoLastTime += time; + info.refCount --; + } + + // no stream is being played by this UID + if (info.refCount == 0) { + info.audioTotalTime += info.audioLastTime; + info.audioLastTime = 0; + info.videoTotalTime += info.videoLastTime; + info.videoLastTime = 0; + } + } +} + +status_t MediaPlayerService::pullBatteryData(Parcel* reply) { + Mutex::Autolock lock(mLock); + + // audio output devices usage + int32_t time = systemTime() / 1000000L; //in ms + int32_t totalTime; + + for (int i = 0; i < NUM_AUDIO_DEVICES; i++) { + totalTime = mBatteryAudio.totalTime[i]; + + if (mBatteryAudio.deviceOn[i] + && (mBatteryAudio.lastTime[i] != 0)) { + int32_t tmpTime = mBatteryAudio.lastTime[i] + time; + totalTime += tmpTime; + } + + reply->writeInt32(totalTime); + // reset the total time + mBatteryAudio.totalTime[i] = 0; + } + + // codec usage + BatteryUsageInfo info; + int size = mBatteryData.size(); + + reply->writeInt32(size); + int i = 0; + + while (i < size) { + info = mBatteryData.valueAt(i); + + reply->writeInt32(mBatteryData.keyAt(i)); //UID + reply->writeInt32(info.audioTotalTime); + reply->writeInt32(info.videoTotalTime); + + info.audioTotalTime = 0; + info.videoTotalTime = 0; + + // remove the UID entry where no stream is being played + if (info.refCount <= 0) { + mBatteryData.removeItemsAt(i); + size --; + i --; + } + i++; + } + return NO_ERROR; +} +} // namespace android diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h new file mode 100644 index 0000000..b08dd6c --- /dev/null +++ b/media/libmediaplayerservice/MediaPlayerService.h @@ -0,0 +1,430 @@ +/* +** +** Copyright 2008, 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 ANDROID_MEDIAPLAYERSERVICE_H +#define ANDROID_MEDIAPLAYERSERVICE_H + +#include <arpa/inet.h> + +#include <utils/Log.h> +#include <utils/threads.h> +#include <utils/List.h> +#include <utils/Errors.h> +#include <utils/KeyedVector.h> +#include <utils/String8.h> +#include <utils/Vector.h> + +#include <media/IMediaPlayerService.h> +#include <media/MediaPlayerInterface.h> +#include <media/Metadata.h> +#include <media/stagefright/foundation/ABase.h> + +#include <system/audio.h> + +namespace android { + +class AudioTrack; +class IMediaRecorder; +class IMediaMetadataRetriever; +class IOMX; +class MediaRecorderClient; + +#define CALLBACK_ANTAGONIZER 0 +#if CALLBACK_ANTAGONIZER +class Antagonizer { +public: + Antagonizer(notify_callback_f cb, void* client); + void start() { mActive = true; } + void stop() { mActive = false; } + void kill(); +private: + static const int interval; + Antagonizer(); + static int callbackThread(void* cookie); + Mutex mLock; + Condition mCondition; + bool mExit; + bool mActive; + void* mClient; + notify_callback_f mCb; +}; +#endif + +class MediaPlayerService : public BnMediaPlayerService +{ + class Client; + + class AudioOutput : public MediaPlayerBase::AudioSink + { + class CallbackData; + + public: + AudioOutput(int sessionId); + virtual ~AudioOutput(); + + virtual bool ready() const { return mTrack != NULL; } + virtual bool realtime() const { return true; } + virtual ssize_t bufferSize() const; + virtual ssize_t frameCount() const; + virtual ssize_t channelCount() const; + virtual ssize_t frameSize() const; + virtual uint32_t latency() const; + virtual float msecsPerFrame() const; + virtual status_t getPosition(uint32_t *position); + virtual int getSessionId(); + + virtual status_t open( + uint32_t sampleRate, int channelCount, audio_channel_mask_t channelMask, + audio_format_t format, int bufferCount, + AudioCallback cb, void *cookie); + + virtual void start(); + virtual ssize_t write(const void* buffer, size_t size); + virtual void stop(); + virtual void flush(); + virtual void pause(); + virtual void close(); + void setAudioStreamType(audio_stream_type_t streamType) { mStreamType = streamType; } + void setVolume(float left, float right); + virtual status_t setPlaybackRatePermille(int32_t ratePermille); + status_t setAuxEffectSendLevel(float level); + status_t attachAuxEffect(int effectId); + virtual status_t dump(int fd, const Vector<String16>& args) const; + + static bool isOnEmulator(); + static int getMinBufferCount(); + void setNextOutput(const sp<AudioOutput>& nextOutput); + void switchToNextOutput(); + virtual bool needsTrailingPadding() { return mNextOutput == NULL; } + + private: + static void setMinBufferCount(); + static void CallbackWrapper( + int event, void *me, void *info); + + AudioTrack* mTrack; + AudioTrack* mRecycledTrack; + sp<AudioOutput> mNextOutput; + AudioCallback mCallback; + void * mCallbackCookie; + CallbackData * mCallbackData; + audio_stream_type_t mStreamType; + float mLeftVolume; + float mRightVolume; + int32_t mPlaybackRatePermille; + uint32_t mSampleRateHz; // sample rate of the content, as set in open() + float mMsecsPerFrame; + int mSessionId; + float mSendLevel; + int mAuxEffectId; + static bool mIsOnEmulator; + static int mMinBufferCount; // 12 for emulator; otherwise 4 + + // CallbackData is what is passed to the AudioTrack as the "user" data. + // We need to be able to target this to a different Output on the fly, + // so we can't use the Output itself for this. + class CallbackData { + public: + CallbackData(AudioOutput *cookie) { + mData = cookie; + mSwitching = false; + } + AudioOutput * getOutput() { return mData;} + void setOutput(AudioOutput* newcookie) { mData = newcookie; } + // lock/unlock are used by the callback before accessing the payload of this object + void lock() { mLock.lock(); } + void unlock() { mLock.unlock(); } + // beginTrackSwitch/endTrackSwitch are used when this object is being handed over + // to the next sink. + void beginTrackSwitch() { mLock.lock(); mSwitching = true; } + void endTrackSwitch() { + if (mSwitching) { + mLock.unlock(); + } + mSwitching = false; + } + private: + AudioOutput * mData; + mutable Mutex mLock; + bool mSwitching; + DISALLOW_EVIL_CONSTRUCTORS(CallbackData); + }; + + }; // AudioOutput + + + class AudioCache : public MediaPlayerBase::AudioSink + { + public: + AudioCache(const char* name); + virtual ~AudioCache() {} + + virtual bool ready() const { return (mChannelCount > 0) && (mHeap->getHeapID() > 0); } + virtual bool realtime() const { return false; } + 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 uint32_t latency() const; + virtual float msecsPerFrame() const; + virtual status_t getPosition(uint32_t *position); + virtual int getSessionId(); + + virtual status_t open( + uint32_t sampleRate, int channelCount, audio_channel_mask_t channelMask, + audio_format_t format, int bufferCount = 1, + AudioCallback cb = NULL, void *cookie = NULL); + + virtual void start(); + virtual ssize_t write(const void* buffer, size_t size); + virtual void stop(); + virtual void flush() {} + virtual void pause() {} + virtual void close() {} + void setAudioStreamType(audio_stream_type_t streamType) {} + void setVolume(float left, float right) {} + virtual status_t setPlaybackRatePermille(int32_t ratePermille) { return INVALID_OPERATION; } + uint32_t sampleRate() const { return mSampleRate; } + audio_format_t format() const { return mFormat; } + size_t size() const { return mSize; } + status_t wait(); + + sp<IMemoryHeap> getHeap() const { return mHeap; } + + static void notify(void* cookie, int msg, + int ext1, int ext2, const Parcel *obj); + virtual status_t dump(int fd, const Vector<String16>& args) const; + + private: + AudioCache(); + + Mutex mLock; + Condition mSignal; + sp<MemoryHeapBase> mHeap; + float mMsecsPerFrame; + uint16_t mChannelCount; + audio_format_t mFormat; + ssize_t mFrameCount; + uint32_t mSampleRate; + uint32_t mSize; + int mError; + bool mCommandComplete; + + sp<Thread> mCallbackThread; + }; // AudioCache + +public: + static void instantiate(); + + // IMediaPlayerService interface + virtual sp<IMediaRecorder> createMediaRecorder(pid_t pid); + void removeMediaRecorderClient(wp<MediaRecorderClient> client); + virtual sp<IMediaMetadataRetriever> createMetadataRetriever(pid_t pid); + + virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, int audioSessionId); + + virtual sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat); + virtual sp<IMemory> decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat); + virtual sp<IOMX> getOMX(); + virtual sp<ICrypto> makeCrypto(); + + virtual status_t dump(int fd, const Vector<String16>& args); + + void removeClient(wp<Client> client); + + // For battery usage tracking purpose + struct BatteryUsageInfo { + // how many streams are being played by one UID + int refCount; + // a temp variable to store the duration(ms) of audio codecs + // when we start a audio codec, we minus the system time from audioLastTime + // when we pause it, we add the system time back to the audioLastTime + // so after the pause, audioLastTime = pause time - start time + // if multiple audio streams are played (or recorded), then audioLastTime + // = the total playing time of all the streams + int32_t audioLastTime; + // when all the audio streams are being paused, we assign audioLastTime to + // this variable, so this value could be provided to the battery app + // in the next pullBatteryData call + int32_t audioTotalTime; + + int32_t videoLastTime; + int32_t videoTotalTime; + }; + KeyedVector<int, BatteryUsageInfo> mBatteryData; + + enum { + SPEAKER, + OTHER_AUDIO_DEVICE, + SPEAKER_AND_OTHER, + NUM_AUDIO_DEVICES + }; + + struct BatteryAudioFlingerUsageInfo { + int refCount; // how many audio streams are being played + int deviceOn[NUM_AUDIO_DEVICES]; // whether the device is currently used + int32_t lastTime[NUM_AUDIO_DEVICES]; // in ms + // totalTime[]: total time of audio output devices usage + int32_t totalTime[NUM_AUDIO_DEVICES]; // in ms + }; + + // This varialble is used to record the usage of audio output device + // for battery app + BatteryAudioFlingerUsageInfo mBatteryAudio; + + // Collect info of the codec usage from media player and media recorder + virtual void addBatteryData(uint32_t params); + // API for the Battery app to pull the data of codecs usage + virtual status_t pullBatteryData(Parcel* reply); +private: + + class Client : public BnMediaPlayer { + + // IMediaPlayer interface + virtual void disconnect(); + virtual status_t setVideoSurfaceTexture( + const sp<ISurfaceTexture>& surfaceTexture); + virtual status_t prepareAsync(); + virtual status_t start(); + virtual status_t stop(); + virtual status_t pause(); + virtual status_t isPlaying(bool* state); + virtual status_t seekTo(int msec); + virtual status_t getCurrentPosition(int* msec); + virtual status_t getDuration(int* msec); + virtual status_t reset(); + virtual status_t setAudioStreamType(audio_stream_type_t type); + virtual status_t setLooping(int loop); + virtual status_t setVolume(float leftVolume, float rightVolume); + virtual status_t invoke(const Parcel& request, Parcel *reply); + virtual status_t setMetadataFilter(const Parcel& filter); + virtual status_t getMetadata(bool update_only, + bool apply_filter, + Parcel *reply); + virtual status_t setAuxEffectSendLevel(float level); + virtual status_t attachAuxEffect(int effectId); + virtual status_t setParameter(int key, const Parcel &request); + virtual status_t getParameter(int key, Parcel *reply); + virtual status_t setRetransmitEndpoint(const struct sockaddr_in* endpoint); + virtual status_t setNextPlayer(const sp<IMediaPlayer>& player); + + sp<MediaPlayerBase> createPlayer(player_type playerType); + + virtual status_t setDataSource( + const char *url, + const KeyedVector<String8, String8> *headers); + + virtual status_t setDataSource(int fd, int64_t offset, int64_t length); + + virtual status_t setDataSource(const sp<IStreamSource> &source); + + sp<MediaPlayerBase> setDataSource_pre(player_type playerType); + void setDataSource_post(const sp<MediaPlayerBase>& p, + status_t status); + + player_type getPlayerType(int fd, int64_t offset, int64_t length); + player_type getPlayerType(const char* url); + player_type getPlayerType(const sp<IStreamSource> &source); + + static void notify(void* cookie, int msg, + int ext1, int ext2, const Parcel *obj); + + pid_t pid() const { return mPid; } + virtual status_t dump(int fd, const Vector<String16>& args) const; + + int getAudioSessionId() { return mAudioSessionId; } + + private: + friend class MediaPlayerService; + Client( const sp<MediaPlayerService>& service, + pid_t pid, + int32_t connId, + const sp<IMediaPlayerClient>& client, + int audioSessionId, + uid_t uid); + Client(); + virtual ~Client(); + + void deletePlayer(); + + sp<MediaPlayerBase> getPlayer() const { Mutex::Autolock lock(mLock); return mPlayer; } + + + + // @param type Of the metadata to be tested. + // @return true if the metadata should be dropped according to + // the filters. + bool shouldDropMetadata(media::Metadata::Type type) const; + + // Add a new element to the set of metadata updated. Noop if + // the element exists already. + // @param type Of the metadata to be recorded. + void addNewMetadataUpdate(media::Metadata::Type type); + + // Disconnect from the currently connected ANativeWindow. + void disconnectNativeWindow(); + + mutable Mutex mLock; + sp<MediaPlayerBase> mPlayer; + sp<MediaPlayerService> mService; + sp<IMediaPlayerClient> mClient; + sp<AudioOutput> mAudioOutput; + pid_t mPid; + status_t mStatus; + bool mLoop; + int32_t mConnId; + int mAudioSessionId; + uid_t mUID; + sp<ANativeWindow> mConnectedWindow; + sp<IBinder> mConnectedWindowBinder; + struct sockaddr_in mRetransmitEndpoint; + bool mRetransmitEndpointValid; + sp<Client> mNextClient; + + // Metadata filters. + media::Metadata::Filter mMetadataAllow; // protected by mLock + media::Metadata::Filter mMetadataDrop; // protected by mLock + + // Metadata updated. For each MEDIA_INFO_METADATA_UPDATE + // notification we try to update mMetadataUpdated which is a + // set: no duplicate. + // getMetadata clears this set. + media::Metadata::Filter mMetadataUpdated; // protected by mLock + +#if CALLBACK_ANTAGONIZER + Antagonizer* mAntagonizer; +#endif + }; // Client + +// ---------------------------------------------------------------------------- + + MediaPlayerService(); + virtual ~MediaPlayerService(); + + mutable Mutex mLock; + SortedVector< wp<Client> > mClients; + SortedVector< wp<MediaRecorderClient> > mMediaRecorderClients; + int32_t mNextConnId; + sp<IOMX> mOMX; + sp<ICrypto> mCrypto; +}; + +// ---------------------------------------------------------------------------- + +}; // namespace android + +#endif // ANDROID_MEDIAPLAYERSERVICE_H diff --git a/media/libmediaplayerservice/MediaRecorderClient.cpp b/media/libmediaplayerservice/MediaRecorderClient.cpp new file mode 100644 index 0000000..beda945 --- /dev/null +++ b/media/libmediaplayerservice/MediaRecorderClient.cpp @@ -0,0 +1,336 @@ +/* + ** Copyright 2008, 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 "MediaRecorderService" +#include <utils/Log.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <unistd.h> +#include <string.h> +#include <cutils/atomic.h> +#include <cutils/properties.h> // for property_get +#include <android_runtime/ActivityManager.h> +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <binder/MemoryHeapBase.h> +#include <binder/MemoryBase.h> + +#include <utils/String16.h> + +#include <system/audio.h> + +#include "MediaRecorderClient.h" +#include "MediaPlayerService.h" + +#include "StagefrightRecorder.h" +#include <gui/ISurfaceTexture.h> + +namespace android { + +const char* cameraPermission = "android.permission.CAMERA"; +const char* recordAudioPermission = "android.permission.RECORD_AUDIO"; + +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; +} + + +sp<ISurfaceTexture> MediaRecorderClient::querySurfaceMediaSource() +{ + ALOGV("Query SurfaceMediaSource"); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NULL; + } + return mRecorder->querySurfaceMediaSource(); +} + + + +status_t MediaRecorderClient::setCamera(const sp<ICamera>& camera, + const sp<ICameraRecordingProxy>& proxy) +{ + ALOGV("setCamera"); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->setCamera(camera, proxy); +} + +status_t MediaRecorderClient::setPreviewSurface(const sp<Surface>& surface) +{ + ALOGV("setPreviewSurface"); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->setPreviewSurface(surface); +} + +status_t MediaRecorderClient::setVideoSource(int vs) +{ + ALOGV("setVideoSource(%d)", vs); + if (!checkPermission(cameraPermission)) { + return PERMISSION_DENIED; + } + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->setVideoSource((video_source)vs); +} + +status_t MediaRecorderClient::setAudioSource(int as) +{ + ALOGV("setAudioSource(%d)", as); + if (!checkPermission(recordAudioPermission)) { + return PERMISSION_DENIED; + } + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->setAudioSource((audio_source_t)as); +} + +status_t MediaRecorderClient::setOutputFormat(int of) +{ + ALOGV("setOutputFormat(%d)", of); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->setOutputFormat((output_format)of); +} + +status_t MediaRecorderClient::setVideoEncoder(int ve) +{ + ALOGV("setVideoEncoder(%d)", ve); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->setVideoEncoder((video_encoder)ve); +} + +status_t MediaRecorderClient::setAudioEncoder(int ae) +{ + ALOGV("setAudioEncoder(%d)", ae); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->setAudioEncoder((audio_encoder)ae); +} + +status_t MediaRecorderClient::setOutputFile(const char* path) +{ + ALOGV("setOutputFile(%s)", path); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->setOutputFile(path); +} + +status_t MediaRecorderClient::setOutputFile(int fd, int64_t offset, int64_t length) +{ + ALOGV("setOutputFile(%d, %lld, %lld)", fd, offset, length); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->setOutputFile(fd, offset, length); +} + +status_t MediaRecorderClient::setVideoSize(int width, int height) +{ + ALOGV("setVideoSize(%dx%d)", width, height); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->setVideoSize(width, height); +} + +status_t MediaRecorderClient::setVideoFrameRate(int frames_per_second) +{ + ALOGV("setVideoFrameRate(%d)", frames_per_second); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->setVideoFrameRate(frames_per_second); +} + +status_t MediaRecorderClient::setParameters(const String8& params) { + ALOGV("setParameters(%s)", params.string()); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->setParameters(params); +} + +status_t MediaRecorderClient::prepare() +{ + ALOGV("prepare"); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->prepare(); +} + + +status_t MediaRecorderClient::getMaxAmplitude(int* max) +{ + ALOGV("getMaxAmplitude"); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->getMaxAmplitude(max); +} + +status_t MediaRecorderClient::start() +{ + ALOGV("start"); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->start(); + +} + +status_t MediaRecorderClient::stop() +{ + ALOGV("stop"); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->stop(); +} + +status_t MediaRecorderClient::init() +{ + ALOGV("init"); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->init(); +} + +status_t MediaRecorderClient::close() +{ + ALOGV("close"); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->close(); +} + + +status_t MediaRecorderClient::reset() +{ + ALOGV("reset"); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->reset(); +} + +status_t MediaRecorderClient::release() +{ + ALOGV("release"); + Mutex::Autolock lock(mLock); + if (mRecorder != NULL) { + delete mRecorder; + mRecorder = NULL; + wp<MediaRecorderClient> client(this); + mMediaPlayerService->removeMediaRecorderClient(client); + } + return NO_ERROR; +} + +MediaRecorderClient::MediaRecorderClient(const sp<MediaPlayerService>& service, pid_t pid) +{ + ALOGV("Client constructor"); + mPid = pid; + mRecorder = new StagefrightRecorder; + mMediaPlayerService = service; +} + +MediaRecorderClient::~MediaRecorderClient() +{ + ALOGV("Client destructor"); + release(); +} + +status_t MediaRecorderClient::setListener(const sp<IMediaRecorderClient>& listener) +{ + ALOGV("setListener"); + Mutex::Autolock lock(mLock); + if (mRecorder == NULL) { + ALOGE("recorder is not initialized"); + return NO_INIT; + } + return mRecorder->setListener(listener); +} + +status_t MediaRecorderClient::dump(int fd, const Vector<String16>& args) const { + if (mRecorder != NULL) { + return mRecorder->dump(fd, args); + } + return OK; +} + +}; // namespace android diff --git a/media/libmediaplayerservice/MediaRecorderClient.h b/media/libmediaplayerservice/MediaRecorderClient.h new file mode 100644 index 0000000..c9ccf22 --- /dev/null +++ b/media/libmediaplayerservice/MediaRecorderClient.h @@ -0,0 +1,76 @@ +/* + ** + ** Copyright 2008, 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 ANDROID_MEDIARECORDERCLIENT_H +#define ANDROID_MEDIARECORDERCLIENT_H + +#include <media/IMediaRecorder.h> + +namespace android { + +class MediaRecorderBase; +class MediaPlayerService; +class ICameraRecordingProxy; +class ISurfaceTexture; + +class MediaRecorderClient : public BnMediaRecorder +{ +public: + virtual status_t setCamera(const sp<ICamera>& camera, + const sp<ICameraRecordingProxy>& proxy); + virtual status_t setPreviewSurface(const sp<Surface>& surface); + virtual status_t setVideoSource(int vs); + virtual status_t setAudioSource(int as); + virtual status_t setOutputFormat(int of); + virtual status_t setVideoEncoder(int ve); + virtual status_t setAudioEncoder(int ae); + virtual status_t setOutputFile(const char* path); + virtual status_t setOutputFile(int fd, int64_t offset, + int64_t length); + virtual status_t setVideoSize(int width, int height); + virtual status_t setVideoFrameRate(int frames_per_second); + virtual status_t setParameters(const String8& params); + virtual status_t setListener( + const sp<IMediaRecorderClient>& listener); + virtual status_t prepare(); + virtual status_t getMaxAmplitude(int* max); + virtual status_t start(); + virtual status_t stop(); + virtual status_t reset(); + virtual status_t init(); + virtual status_t close(); + virtual status_t release(); + virtual status_t dump(int fd, const Vector<String16>& args) const; + virtual sp<ISurfaceTexture> querySurfaceMediaSource(); + +private: + friend class MediaPlayerService; // for accessing private constructor + + MediaRecorderClient( + const sp<MediaPlayerService>& service, + pid_t pid); + virtual ~MediaRecorderClient(); + + pid_t mPid; + Mutex mLock; + MediaRecorderBase *mRecorder; + sp<MediaPlayerService> mMediaPlayerService; +}; + +}; // namespace android + +#endif // ANDROID_MEDIARECORDERCLIENT_H diff --git a/media/libmediaplayerservice/MetadataRetrieverClient.cpp b/media/libmediaplayerservice/MetadataRetrieverClient.cpp new file mode 100644 index 0000000..776d288 --- /dev/null +++ b/media/libmediaplayerservice/MetadataRetrieverClient.cpp @@ -0,0 +1,254 @@ +/* +** +** Copyright (C) 2008 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 "MetadataRetrieverClient" +#include <utils/Log.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <unistd.h> + +#include <string.h> +#include <cutils/atomic.h> +#include <cutils/properties.h> +#include <binder/MemoryBase.h> +#include <binder/MemoryHeapBase.h> +#include <android_runtime/ActivityManager.h> +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <media/MediaMetadataRetrieverInterface.h> +#include <media/MediaPlayerInterface.h> +#include <private/media/VideoFrame.h> +#include "MidiMetadataRetriever.h" +#include "MetadataRetrieverClient.h" +#include "StagefrightMetadataRetriever.h" + +namespace android { + +extern player_type getPlayerType(const char* url); +extern player_type getPlayerType(int fd, int64_t offset, int64_t length); + +MetadataRetrieverClient::MetadataRetrieverClient(pid_t pid) +{ + ALOGV("MetadataRetrieverClient constructor pid(%d)", pid); + mPid = pid; + mThumbnail = NULL; + mAlbumArt = NULL; + mRetriever = NULL; +} + +MetadataRetrieverClient::~MetadataRetrieverClient() +{ + ALOGV("MetadataRetrieverClient destructor"); + disconnect(); +} + +status_t MetadataRetrieverClient::dump(int fd, const Vector<String16>& args) const +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + result.append(" MetadataRetrieverClient\n"); + snprintf(buffer, 255, " pid(%d)\n", mPid); + result.append(buffer); + write(fd, result.string(), result.size()); + write(fd, "\n", 1); + return NO_ERROR; +} + +void MetadataRetrieverClient::disconnect() +{ + ALOGV("disconnect from pid %d", mPid); + Mutex::Autolock lock(mLock); + mRetriever.clear(); + mThumbnail.clear(); + mAlbumArt.clear(); + IPCThreadState::self()->flushCommands(); +} + +static sp<MediaMetadataRetrieverBase> createRetriever(player_type playerType) +{ + sp<MediaMetadataRetrieverBase> p; + switch (playerType) { + case STAGEFRIGHT_PLAYER: + case NU_PLAYER: + { + p = new StagefrightMetadataRetriever; + break; + } + case SONIVOX_PLAYER: + ALOGV("create midi metadata retriever"); + p = new MidiMetadataRetriever(); + break; + default: + // TODO: + // support for TEST_PLAYER + ALOGE("player type %d is not supported", playerType); + break; + } + if (p == NULL) { + ALOGE("failed to create a retriever object"); + } + return p; +} + +status_t MetadataRetrieverClient::setDataSource( + const char *url, const KeyedVector<String8, String8> *headers) +{ + ALOGV("setDataSource(%s)", url); + Mutex::Autolock lock(mLock); + if (url == NULL) { + return UNKNOWN_ERROR; + } + player_type playerType = getPlayerType(url); + ALOGV("player type = %d", playerType); + sp<MediaMetadataRetrieverBase> p = createRetriever(playerType); + if (p == NULL) return NO_INIT; + status_t ret = p->setDataSource(url, headers); + if (ret == NO_ERROR) mRetriever = p; + return ret; +} + +status_t MetadataRetrieverClient::setDataSource(int fd, int64_t offset, int64_t length) +{ + ALOGV("setDataSource fd=%d, offset=%lld, length=%lld", fd, offset, length); + Mutex::Autolock lock(mLock); + struct stat sb; + int ret = fstat(fd, &sb); + if (ret != 0) { + ALOGE("fstat(%d) failed: %d, %s", fd, ret, strerror(errno)); + return BAD_VALUE; + } + 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_size = %llu", sb.st_size); + + if (offset >= sb.st_size) { + ALOGE("offset (%lld) bigger than file size (%llu)", offset, sb.st_size); + ::close(fd); + return BAD_VALUE; + } + if (offset + length > sb.st_size) { + length = sb.st_size - offset; + ALOGV("calculated length = %lld", length); + } + + player_type playerType = getPlayerType(fd, offset, length); + ALOGV("player type = %d", playerType); + sp<MediaMetadataRetrieverBase> p = createRetriever(playerType); + if (p == NULL) { + ::close(fd); + return NO_INIT; + } + status_t status = p->setDataSource(fd, offset, length); + if (status == NO_ERROR) mRetriever = p; + ::close(fd); + return status; +} + +sp<IMemory> MetadataRetrieverClient::getFrameAtTime(int64_t timeUs, int option) +{ + ALOGV("getFrameAtTime: time(%lld us) option(%d)", timeUs, option); + Mutex::Autolock lock(mLock); + mThumbnail.clear(); + if (mRetriever == NULL) { + ALOGE("retriever is not initialized"); + return NULL; + } + VideoFrame *frame = mRetriever->getFrameAtTime(timeUs, option); + if (frame == NULL) { + ALOGE("failed to capture a video frame"); + return NULL; + } + size_t size = sizeof(VideoFrame) + frame->mSize; + sp<MemoryHeapBase> heap = new MemoryHeapBase(size, 0, "MetadataRetrieverClient"); + if (heap == NULL) { + ALOGE("failed to create MemoryDealer"); + delete frame; + return NULL; + } + mThumbnail = new MemoryBase(heap, 0, size); + if (mThumbnail == NULL) { + ALOGE("not enough memory for VideoFrame size=%u", size); + delete frame; + return NULL; + } + VideoFrame *frameCopy = static_cast<VideoFrame *>(mThumbnail->pointer()); + frameCopy->mWidth = frame->mWidth; + frameCopy->mHeight = frame->mHeight; + frameCopy->mDisplayWidth = frame->mDisplayWidth; + frameCopy->mDisplayHeight = frame->mDisplayHeight; + frameCopy->mSize = frame->mSize; + frameCopy->mRotationAngle = frame->mRotationAngle; + ALOGV("rotation: %d", frameCopy->mRotationAngle); + frameCopy->mData = (uint8_t *)frameCopy + sizeof(VideoFrame); + memcpy(frameCopy->mData, frame->mData, frame->mSize); + delete frame; // Fix memory leakage + return mThumbnail; +} + +sp<IMemory> MetadataRetrieverClient::extractAlbumArt() +{ + ALOGV("extractAlbumArt"); + Mutex::Autolock lock(mLock); + mAlbumArt.clear(); + if (mRetriever == NULL) { + ALOGE("retriever is not initialized"); + return NULL; + } + MediaAlbumArt *albumArt = mRetriever->extractAlbumArt(); + if (albumArt == NULL) { + ALOGE("failed to extract an album art"); + return NULL; + } + size_t size = sizeof(MediaAlbumArt) + albumArt->mSize; + sp<MemoryHeapBase> heap = new MemoryHeapBase(size, 0, "MetadataRetrieverClient"); + if (heap == NULL) { + ALOGE("failed to create MemoryDealer object"); + delete albumArt; + return NULL; + } + mAlbumArt = new MemoryBase(heap, 0, size); + if (mAlbumArt == NULL) { + ALOGE("not enough memory for MediaAlbumArt size=%u", size); + delete albumArt; + return NULL; + } + MediaAlbumArt *albumArtCopy = static_cast<MediaAlbumArt *>(mAlbumArt->pointer()); + albumArtCopy->mSize = albumArt->mSize; + albumArtCopy->mData = (uint8_t *)albumArtCopy + sizeof(MediaAlbumArt); + memcpy(albumArtCopy->mData, albumArt->mData, albumArt->mSize); + delete albumArt; // Fix memory leakage + return mAlbumArt; +} + +const char* MetadataRetrieverClient::extractMetadata(int keyCode) +{ + ALOGV("extractMetadata"); + Mutex::Autolock lock(mLock); + if (mRetriever == NULL) { + ALOGE("retriever is not initialized"); + return NULL; + } + return mRetriever->extractMetadata(keyCode); +} + +}; // namespace android diff --git a/media/libmediaplayerservice/MetadataRetrieverClient.h b/media/libmediaplayerservice/MetadataRetrieverClient.h new file mode 100644 index 0000000..f08f933 --- /dev/null +++ b/media/libmediaplayerservice/MetadataRetrieverClient.h @@ -0,0 +1,73 @@ +/* +** +** Copyright (C) 2008 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 ANDROID_MEDIAMETADATARETRIEVERSERVICE_H +#define ANDROID_MEDIAMETADATARETRIEVERSERVICE_H + +#include <utils/Log.h> +#include <utils/threads.h> +#include <utils/List.h> +#include <utils/Errors.h> +#include <utils/KeyedVector.h> +#include <binder/IMemory.h> + +#include <media/MediaMetadataRetrieverInterface.h> + + +namespace android { + +class IMediaPlayerService; +class MemoryDealer; + +class MetadataRetrieverClient : public BnMediaMetadataRetriever +{ +public: + MetadataRetrieverClient(const sp<IMediaPlayerService>& service, pid_t pid, int32_t connId); + + // Implements IMediaMetadataRetriever interface + // These methods are called in IMediaMetadataRetriever.cpp? + virtual void disconnect(); + + virtual status_t setDataSource( + 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); + virtual sp<IMemory> extractAlbumArt(); + virtual const char* extractMetadata(int keyCode); + + virtual status_t dump(int fd, const Vector<String16>& args) const; + +private: + friend class MediaPlayerService; + + explicit MetadataRetrieverClient(pid_t pid); + virtual ~MetadataRetrieverClient(); + + mutable Mutex mLock; + sp<MediaMetadataRetrieverBase> mRetriever; + pid_t mPid; + + // Keep the shared memory copy of album art and capture frame (for thumbnail) + sp<IMemory> mAlbumArt; + sp<IMemory> mThumbnail; +}; + +}; // namespace android + +#endif // ANDROID_MEDIAMETADATARETRIEVERSERVICE_H + diff --git a/media/libmediaplayerservice/MidiFile.cpp b/media/libmediaplayerservice/MidiFile.cpp new file mode 100644 index 0000000..8db5b9b --- /dev/null +++ b/media/libmediaplayerservice/MidiFile.cpp @@ -0,0 +1,552 @@ +/* MidiFile.cpp +** +** Copyright 2007, 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 "MidiFile" +#include "utils/Log.h" + +#include <stdio.h> +#include <assert.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <sched.h> +#include <utils/threads.h> +#include <libsonivox/eas_reverb.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <system/audio.h> + +#include "MidiFile.h" + +// ---------------------------------------------------------------------------- + +namespace android { + +// ---------------------------------------------------------------------------- + +// The midi engine buffers are a bit small (128 frames), so we batch them up +static const int NUM_BUFFERS = 4; + +// TODO: Determine appropriate return codes +static status_t ERROR_NOT_OPEN = -1; +static status_t ERROR_OPEN_FAILED = -2; +static status_t ERROR_EAS_FAILURE = -3; +static status_t ERROR_ALLOCATE_FAILED = -4; + +static const S_EAS_LIB_CONFIG* pLibConfig = NULL; + +MidiFile::MidiFile() : + mEasData(NULL), mEasHandle(NULL), mAudioBuffer(NULL), + mPlayTime(-1), mDuration(-1), mState(EAS_STATE_ERROR), + mStreamType(AUDIO_STREAM_MUSIC), mLoop(false), mExit(false), + mPaused(false), mRender(false), mTid(-1) +{ + ALOGV("constructor"); + + mFileLocator.path = NULL; + mFileLocator.fd = -1; + mFileLocator.offset = 0; + mFileLocator.length = 0; + + // get the library configuration and do sanity check + if (pLibConfig == NULL) + pLibConfig = EAS_Config(); + if ((pLibConfig == NULL) || (LIB_VERSION != pLibConfig->libVersion)) { + ALOGE("EAS library/header mismatch"); + goto Failed; + } + + // initialize EAS library + if (EAS_Init(&mEasData) != EAS_SUCCESS) { + ALOGE("EAS_Init failed"); + goto Failed; + } + + // select reverb preset and enable + EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET, EAS_PARAM_REVERB_CHAMBER); + EAS_SetParameter(mEasData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS, EAS_FALSE); + + // create playback thread + { + Mutex::Autolock l(mMutex); + mThread = new MidiFileThread(this); + mThread->run("midithread", ANDROID_PRIORITY_AUDIO); + mCondition.wait(mMutex); + ALOGV("thread started"); + } + + // indicate success + if (mTid > 0) { + ALOGV(" render thread(%d) started", mTid); + mState = EAS_STATE_READY; + } + +Failed: + return; +} + +status_t MidiFile::initCheck() +{ + if (mState == EAS_STATE_ERROR) return ERROR_EAS_FAILURE; + return NO_ERROR; +} + +MidiFile::~MidiFile() { + ALOGV("MidiFile destructor"); + release(); +} + +status_t MidiFile::setDataSource( + const char* path, const KeyedVector<String8, String8> *) { + ALOGV("MidiFile::setDataSource url=%s", path); + Mutex::Autolock lock(mMutex); + + // file still open? + if (mEasHandle) { + reset_nosync(); + } + + // open file and set paused state + mFileLocator.path = strdup(path); + mFileLocator.fd = -1; + mFileLocator.offset = 0; + mFileLocator.length = 0; + EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle); + if (result == EAS_SUCCESS) { + updateState(); + } + + if (result != EAS_SUCCESS) { + ALOGE("EAS_OpenFile failed: [%d]", (int)result); + mState = EAS_STATE_ERROR; + return ERROR_OPEN_FAILED; + } + + mState = EAS_STATE_OPEN; + mPlayTime = 0; + return NO_ERROR; +} + +status_t MidiFile::setDataSource(int fd, int64_t offset, int64_t length) +{ + ALOGV("MidiFile::setDataSource fd=%d", fd); + Mutex::Autolock lock(mMutex); + + // file still open? + if (mEasHandle) { + reset_nosync(); + } + + // open file and set paused state + mFileLocator.fd = dup(fd); + mFileLocator.offset = offset; + mFileLocator.length = length; + EAS_RESULT result = EAS_OpenFile(mEasData, &mFileLocator, &mEasHandle); + updateState(); + + if (result != EAS_SUCCESS) { + ALOGE("EAS_OpenFile failed: [%d]", (int)result); + mState = EAS_STATE_ERROR; + return ERROR_OPEN_FAILED; + } + + mState = EAS_STATE_OPEN; + mPlayTime = 0; + return NO_ERROR; +} + +status_t MidiFile::prepare() +{ + ALOGV("MidiFile::prepare"); + Mutex::Autolock lock(mMutex); + if (!mEasHandle) { + return ERROR_NOT_OPEN; + } + EAS_RESULT result; + if ((result = EAS_Prepare(mEasData, mEasHandle)) != EAS_SUCCESS) { + ALOGE("EAS_Prepare failed: [%ld]", result); + return ERROR_EAS_FAILURE; + } + updateState(); + return NO_ERROR; +} + +status_t MidiFile::prepareAsync() +{ + ALOGV("MidiFile::prepareAsync"); + status_t ret = prepare(); + + // don't hold lock during callback + if (ret == NO_ERROR) { + sendEvent(MEDIA_PREPARED); + } else { + sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, ret); + } + return ret; +} + +status_t MidiFile::start() +{ + ALOGV("MidiFile::start"); + Mutex::Autolock lock(mMutex); + if (!mEasHandle) { + return ERROR_NOT_OPEN; + } + + // resuming after pause? + if (mPaused) { + if (EAS_Resume(mEasData, mEasHandle) != EAS_SUCCESS) { + return ERROR_EAS_FAILURE; + } + mPaused = false; + updateState(); + } + + mRender = true; + + // wake up render thread + ALOGV(" wakeup render thread"); + mCondition.signal(); + return NO_ERROR; +} + +status_t MidiFile::stop() +{ + ALOGV("MidiFile::stop"); + Mutex::Autolock lock(mMutex); + if (!mEasHandle) { + return ERROR_NOT_OPEN; + } + if (!mPaused && (mState != EAS_STATE_STOPPED)) { + EAS_RESULT result = EAS_Pause(mEasData, mEasHandle); + if (result != EAS_SUCCESS) { + ALOGE("EAS_Pause returned error %ld", result); + return ERROR_EAS_FAILURE; + } + } + mPaused = false; + return NO_ERROR; +} + +status_t MidiFile::seekTo(int position) +{ + ALOGV("MidiFile::seekTo %d", position); + // hold lock during EAS calls + { + Mutex::Autolock lock(mMutex); + if (!mEasHandle) { + return ERROR_NOT_OPEN; + } + EAS_RESULT result; + if ((result = EAS_Locate(mEasData, mEasHandle, position, false)) + != EAS_SUCCESS) + { + ALOGE("EAS_Locate returned %ld", result); + return ERROR_EAS_FAILURE; + } + EAS_GetLocation(mEasData, mEasHandle, &mPlayTime); + } + sendEvent(MEDIA_SEEK_COMPLETE); + return NO_ERROR; +} + +status_t MidiFile::pause() +{ + ALOGV("MidiFile::pause"); + Mutex::Autolock lock(mMutex); + if (!mEasHandle) { + return ERROR_NOT_OPEN; + } + if ((mState == EAS_STATE_PAUSING) || (mState == EAS_STATE_PAUSED)) return NO_ERROR; + if (EAS_Pause(mEasData, mEasHandle) != EAS_SUCCESS) { + return ERROR_EAS_FAILURE; + } + mPaused = true; + return NO_ERROR; +} + +bool MidiFile::isPlaying() +{ + ALOGV("MidiFile::isPlaying, mState=%d", int(mState)); + if (!mEasHandle || mPaused) return false; + return (mState == EAS_STATE_PLAY); +} + +status_t MidiFile::getCurrentPosition(int* position) +{ + ALOGV("MidiFile::getCurrentPosition"); + if (!mEasHandle) { + ALOGE("getCurrentPosition(): file not open"); + return ERROR_NOT_OPEN; + } + if (mPlayTime < 0) { + ALOGE("getCurrentPosition(): mPlayTime = %ld", mPlayTime); + return ERROR_EAS_FAILURE; + } + *position = mPlayTime; + return NO_ERROR; +} + +status_t MidiFile::getDuration(int* duration) +{ + + ALOGV("MidiFile::getDuration"); + { + Mutex::Autolock lock(mMutex); + if (!mEasHandle) return ERROR_NOT_OPEN; + *duration = mDuration; + } + + // if no duration cached, get the duration + // don't need a lock here because we spin up a new engine + if (*duration < 0) { + EAS_I32 temp; + EAS_DATA_HANDLE easData = NULL; + EAS_HANDLE easHandle = NULL; + EAS_RESULT result = EAS_Init(&easData); + if (result == EAS_SUCCESS) { + result = EAS_OpenFile(easData, &mFileLocator, &easHandle); + } + if (result == EAS_SUCCESS) { + result = EAS_Prepare(easData, easHandle); + } + if (result == EAS_SUCCESS) { + result = EAS_ParseMetaData(easData, easHandle, &temp); + } + if (easHandle) { + EAS_CloseFile(easData, easHandle); + } + if (easData) { + EAS_Shutdown(easData); + } + + if (result != EAS_SUCCESS) { + return ERROR_EAS_FAILURE; + } + + // cache successful result + mDuration = *duration = int(temp); + } + + return NO_ERROR; +} + +status_t MidiFile::release() +{ + ALOGV("MidiFile::release"); + Mutex::Autolock l(mMutex); + reset_nosync(); + + // wait for render thread to exit + mExit = true; + mCondition.signal(); + + // wait for thread to exit + if (mAudioBuffer) { + mCondition.wait(mMutex); + } + + // release resources + if (mEasData) { + EAS_Shutdown(mEasData); + mEasData = NULL; + } + return NO_ERROR; +} + +status_t MidiFile::reset() +{ + ALOGV("MidiFile::reset"); + Mutex::Autolock lock(mMutex); + return reset_nosync(); +} + +// call only with mutex held +status_t MidiFile::reset_nosync() +{ + ALOGV("MidiFile::reset_nosync"); + // close file + if (mEasHandle) { + EAS_CloseFile(mEasData, mEasHandle); + mEasHandle = NULL; + } + if (mFileLocator.path) { + free((void*)mFileLocator.path); + mFileLocator.path = NULL; + } + if (mFileLocator.fd >= 0) { + close(mFileLocator.fd); + } + mFileLocator.fd = -1; + mFileLocator.offset = 0; + mFileLocator.length = 0; + + mPlayTime = -1; + mDuration = -1; + mLoop = false; + mPaused = false; + mRender = false; + return NO_ERROR; +} + +status_t MidiFile::setLooping(int loop) +{ + ALOGV("MidiFile::setLooping"); + Mutex::Autolock lock(mMutex); + if (!mEasHandle) { + return ERROR_NOT_OPEN; + } + loop = loop ? -1 : 0; + if (EAS_SetRepeat(mEasData, mEasHandle, loop) != EAS_SUCCESS) { + return ERROR_EAS_FAILURE; + } + return NO_ERROR; +} + +status_t MidiFile::createOutputTrack() { + if (mAudioSink->open(pLibConfig->sampleRate, pLibConfig->numChannels, + CHANNEL_MASK_USE_CHANNEL_ORDER, AUDIO_FORMAT_PCM_16_BIT, 2) != NO_ERROR) { + ALOGE("mAudioSink open failed"); + return ERROR_OPEN_FAILED; + } + return NO_ERROR; +} + +int MidiFile::render() { + EAS_RESULT result = EAS_FAILURE; + EAS_I32 count; + int temp; + bool audioStarted = false; + + ALOGV("MidiFile::render"); + + // allocate render buffer + mAudioBuffer = new EAS_PCM[pLibConfig->mixBufferSize * pLibConfig->numChannels * NUM_BUFFERS]; + if (!mAudioBuffer) { + ALOGE("mAudioBuffer allocate failed"); + goto threadExit; + } + + // signal main thread that we started + { + Mutex::Autolock l(mMutex); + mTid = gettid(); + ALOGV("render thread(%d) signal", mTid); + mCondition.signal(); + } + + while (1) { + mMutex.lock(); + + // nothing to render, wait for client thread to wake us up + while (!mRender && !mExit) + { + ALOGV("MidiFile::render - signal wait"); + mCondition.wait(mMutex); + ALOGV("MidiFile::render - signal rx'd"); + } + if (mExit) { + mMutex.unlock(); + break; + } + + // render midi data into the input buffer + //ALOGV("MidiFile::render - rendering audio"); + int num_output = 0; + EAS_PCM* p = mAudioBuffer; + for (int i = 0; i < NUM_BUFFERS; i++) { + result = EAS_Render(mEasData, p, pLibConfig->mixBufferSize, &count); + if (result != EAS_SUCCESS) { + ALOGE("EAS_Render returned %ld", result); + } + p += count * pLibConfig->numChannels; + num_output += count * pLibConfig->numChannels * sizeof(EAS_PCM); + } + + // update playback state and position + // ALOGV("MidiFile::render - updating state"); + EAS_GetLocation(mEasData, mEasHandle, &mPlayTime); + EAS_State(mEasData, mEasHandle, &mState); + mMutex.unlock(); + + // create audio output track if necessary + if (!mAudioSink->ready()) { + ALOGV("MidiFile::render - create output track"); + if (createOutputTrack() != NO_ERROR) + goto threadExit; + } + + // Write data to the audio hardware + // ALOGV("MidiFile::render - writing to audio output"); + if ((temp = mAudioSink->write(mAudioBuffer, num_output)) < 0) { + ALOGE("Error in writing:%d",temp); + return temp; + } + + // start audio output if necessary + if (!audioStarted) { + //ALOGV("MidiFile::render - starting audio"); + mAudioSink->start(); + audioStarted = true; + } + + // still playing? + if ((mState == EAS_STATE_STOPPED) || (mState == EAS_STATE_ERROR) || + (mState == EAS_STATE_PAUSED)) + { + switch(mState) { + case EAS_STATE_STOPPED: + { + ALOGV("MidiFile::render - stopped"); + sendEvent(MEDIA_PLAYBACK_COMPLETE); + break; + } + case EAS_STATE_ERROR: + { + ALOGE("MidiFile::render - error"); + sendEvent(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN); + break; + } + case EAS_STATE_PAUSED: + ALOGV("MidiFile::render - paused"); + break; + default: + break; + } + mAudioSink->stop(); + audioStarted = false; + mRender = false; + } + } + +threadExit: + mAudioSink.clear(); + if (mAudioBuffer) { + delete [] mAudioBuffer; + mAudioBuffer = NULL; + } + mMutex.lock(); + mTid = -1; + mCondition.signal(); + mMutex.unlock(); + return result; +} + +} // end namespace android diff --git a/media/libmediaplayerservice/MidiFile.h b/media/libmediaplayerservice/MidiFile.h new file mode 100644 index 0000000..f6f8f7b --- /dev/null +++ b/media/libmediaplayerservice/MidiFile.h @@ -0,0 +1,113 @@ +/* +** +** Copyright 2008, 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 ANDROID_MIDIFILE_H +#define ANDROID_MIDIFILE_H + +#include <media/MediaPlayerInterface.h> +#include <libsonivox/eas.h> + +namespace android { + +// Note that the name MidiFile is misleading; this actually represents a MIDI file player +class MidiFile : public MediaPlayerInterface { +public: + MidiFile(); + ~MidiFile(); + + virtual status_t initCheck(); + + virtual status_t setDataSource( + const char* path, const KeyedVector<String8, String8> *headers); + + virtual status_t setDataSource(int fd, int64_t offset, int64_t length); + virtual status_t setVideoSurfaceTexture( + const sp<ISurfaceTexture>& surfaceTexture) + { return UNKNOWN_ERROR; } + virtual status_t prepare(); + virtual status_t prepareAsync(); + virtual status_t start(); + virtual status_t stop(); + virtual status_t seekTo(int msec); + virtual status_t pause(); + virtual bool isPlaying(); + virtual status_t getCurrentPosition(int* msec); + virtual status_t getDuration(int* msec); + virtual status_t release(); + virtual status_t reset(); + virtual status_t setLooping(int loop); + virtual player_type playerType() { return SONIVOX_PLAYER; } + virtual status_t invoke(const Parcel& request, Parcel *reply) { + return INVALID_OPERATION; + } + virtual status_t setParameter(int key, const Parcel &request) { + return INVALID_OPERATION; + } + virtual status_t getParameter(int key, Parcel *reply) { + return INVALID_OPERATION; + } + + +private: + status_t createOutputTrack(); + status_t reset_nosync(); + int render(); + void updateState(){ EAS_State(mEasData, mEasHandle, &mState); } + + Mutex mMutex; + Condition mCondition; + EAS_DATA_HANDLE mEasData; + EAS_HANDLE mEasHandle; + EAS_PCM* mAudioBuffer; + EAS_I32 mPlayTime; + EAS_I32 mDuration; + EAS_STATE mState; + EAS_FILE mFileLocator; + audio_stream_type_t mStreamType; + bool mLoop; + volatile bool mExit; + bool mPaused; + volatile bool mRender; + pid_t mTid; + + class MidiFileThread : public Thread { + public: + MidiFileThread(MidiFile *midiPlayer) : mMidiFile(midiPlayer) { + } + + protected: + virtual ~MidiFileThread() {} + + private: + MidiFile *mMidiFile; + + bool threadLoop() { + int result; + result = mMidiFile->render(); + return false; + } + + MidiFileThread(const MidiFileThread &); + MidiFileThread &operator=(const MidiFileThread &); + }; + + sp<MidiFileThread> mThread; +}; + +}; // namespace android + +#endif // ANDROID_MIDIFILE_H diff --git a/media/libmediaplayerservice/MidiMetadataRetriever.cpp b/media/libmediaplayerservice/MidiMetadataRetriever.cpp new file mode 100644 index 0000000..465209f --- /dev/null +++ b/media/libmediaplayerservice/MidiMetadataRetriever.cpp @@ -0,0 +1,92 @@ +/* +** +** Copyright 2009, 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 "MidiMetadataRetriever" +#include <utils/Log.h> + +#include "MidiMetadataRetriever.h" +#include <media/mediametadataretriever.h> + +namespace android { + +static status_t ERROR_NOT_OPEN = -1; +static status_t ERROR_OPEN_FAILED = -2; +static status_t ERROR_EAS_FAILURE = -3; +static status_t ERROR_ALLOCATE_FAILED = -4; + +void MidiMetadataRetriever::clearMetadataValues() +{ + ALOGV("clearMetadataValues"); + mMetadataValues[0][0] = '\0'; +} + +status_t MidiMetadataRetriever::setDataSource( + const char *url, const KeyedVector<String8, String8> *headers) +{ + ALOGV("setDataSource: %s", url? url: "NULL pointer"); + Mutex::Autolock lock(mLock); + clearMetadataValues(); + if (mMidiPlayer == 0) { + mMidiPlayer = new MidiFile(); + } + return mMidiPlayer->setDataSource(url, headers); +} + +status_t MidiMetadataRetriever::setDataSource(int fd, int64_t offset, int64_t length) +{ + ALOGV("setDataSource: fd(%d), offset(%lld), and length(%lld)", fd, offset, length); + Mutex::Autolock lock(mLock); + clearMetadataValues(); + if (mMidiPlayer == 0) { + mMidiPlayer = new MidiFile(); + } + return mMidiPlayer->setDataSource(fd, offset, length);; +} + +const char* MidiMetadataRetriever::extractMetadata(int keyCode) +{ + ALOGV("extractMetdata: key(%d)", keyCode); + Mutex::Autolock lock(mLock); + if (mMidiPlayer == 0 || mMidiPlayer->initCheck() != NO_ERROR) { + ALOGE("Midi player is not initialized yet"); + return NULL; + } + switch (keyCode) { + case METADATA_KEY_DURATION: + { + if (mMetadataValues[0][0] == '\0') { + int duration = -1; + if (mMidiPlayer->getDuration(&duration) != NO_ERROR) { + ALOGE("failed to get duration"); + return NULL; + } + snprintf(mMetadataValues[0], MAX_METADATA_STRING_LENGTH, "%d", duration); + } + + ALOGV("duration: %s ms", mMetadataValues[0]); + return mMetadataValues[0]; + } + default: + ALOGE("Unsupported key code (%d)", keyCode); + return NULL; + } + return NULL; +} + +}; + diff --git a/media/libmediaplayerservice/MidiMetadataRetriever.h b/media/libmediaplayerservice/MidiMetadataRetriever.h new file mode 100644 index 0000000..4cee42d --- /dev/null +++ b/media/libmediaplayerservice/MidiMetadataRetriever.h @@ -0,0 +1,51 @@ +/* +** +** Copyright 2009, 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 ANDROID_MIDIMETADATARETRIEVER_H +#define ANDROID_MIDIMETADATARETRIEVER_H + +#include <utils/threads.h> +#include <utils/Errors.h> +#include <media/MediaMetadataRetrieverInterface.h> + +#include "MidiFile.h" + +namespace android { + +class MidiMetadataRetriever : public MediaMetadataRetrieverInterface { +public: + MidiMetadataRetriever() {} + ~MidiMetadataRetriever() {} + + virtual status_t setDataSource( + 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); + +private: + static const uint32_t MAX_METADATA_STRING_LENGTH = 128; + void clearMetadataValues(); + + Mutex mLock; + sp<MidiFile> mMidiPlayer; + char mMetadataValues[1][MAX_METADATA_STRING_LENGTH]; +}; + +}; // namespace android + +#endif // ANDROID_MIDIMETADATARETRIEVER_H diff --git a/media/libmediaplayerservice/StagefrightPlayer.cpp b/media/libmediaplayerservice/StagefrightPlayer.cpp new file mode 100644 index 0000000..619c149 --- /dev/null +++ b/media/libmediaplayerservice/StagefrightPlayer.cpp @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2009 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 "StagefrightPlayer" +#include <utils/Log.h> + +#include "StagefrightPlayer.h" + +#include "AwesomePlayer.h" + +#include <media/Metadata.h> +#include <media/stagefright/MediaExtractor.h> + +namespace android { + +StagefrightPlayer::StagefrightPlayer() + : mPlayer(new AwesomePlayer) { + ALOGV("StagefrightPlayer"); + + mPlayer->setListener(this); +} + +StagefrightPlayer::~StagefrightPlayer() { + ALOGV("~StagefrightPlayer"); + reset(); + + delete mPlayer; + mPlayer = NULL; +} + +status_t StagefrightPlayer::initCheck() { + ALOGV("initCheck"); + return OK; +} + +status_t StagefrightPlayer::setUID(uid_t uid) { + mPlayer->setUID(uid); + + return OK; +} + +status_t StagefrightPlayer::setDataSource( + const char *url, const KeyedVector<String8, String8> *headers) { + return mPlayer->setDataSource(url, headers); +} + +// Warning: The filedescriptor passed into this method will only be valid until +// the method returns, if you want to keep it, dup it! +status_t StagefrightPlayer::setDataSource(int fd, int64_t offset, int64_t length) { + ALOGV("setDataSource(%d, %lld, %lld)", fd, offset, length); + return mPlayer->setDataSource(dup(fd), offset, length); +} + +status_t StagefrightPlayer::setDataSource(const sp<IStreamSource> &source) { + return mPlayer->setDataSource(source); +} + +status_t StagefrightPlayer::setVideoSurfaceTexture( + const sp<ISurfaceTexture> &surfaceTexture) { + ALOGV("setVideoSurfaceTexture"); + + return mPlayer->setSurfaceTexture(surfaceTexture); +} + +status_t StagefrightPlayer::prepare() { + return mPlayer->prepare(); +} + +status_t StagefrightPlayer::prepareAsync() { + return mPlayer->prepareAsync(); +} + +status_t StagefrightPlayer::start() { + ALOGV("start"); + + return mPlayer->play(); +} + +status_t StagefrightPlayer::stop() { + ALOGV("stop"); + + return pause(); // what's the difference? +} + +status_t StagefrightPlayer::pause() { + ALOGV("pause"); + + return mPlayer->pause(); +} + +bool StagefrightPlayer::isPlaying() { + ALOGV("isPlaying"); + return mPlayer->isPlaying(); +} + +status_t StagefrightPlayer::seekTo(int msec) { + ALOGV("seekTo %.2f secs", msec / 1E3); + + status_t err = mPlayer->seekTo((int64_t)msec * 1000); + + return err; +} + +status_t StagefrightPlayer::getCurrentPosition(int *msec) { + ALOGV("getCurrentPosition"); + + int64_t positionUs; + status_t err = mPlayer->getPosition(&positionUs); + + if (err != OK) { + return err; + } + + *msec = (positionUs + 500) / 1000; + + return OK; +} + +status_t StagefrightPlayer::getDuration(int *msec) { + ALOGV("getDuration"); + + int64_t durationUs; + status_t err = mPlayer->getDuration(&durationUs); + + if (err != OK) { + *msec = 0; + return OK; + } + + *msec = (durationUs + 500) / 1000; + + return OK; +} + +status_t StagefrightPlayer::reset() { + ALOGV("reset"); + + mPlayer->reset(); + + return OK; +} + +status_t StagefrightPlayer::setLooping(int loop) { + ALOGV("setLooping"); + + return mPlayer->setLooping(loop); +} + +player_type StagefrightPlayer::playerType() { + ALOGV("playerType"); + return STAGEFRIGHT_PLAYER; +} + +status_t StagefrightPlayer::invoke(const Parcel &request, Parcel *reply) { + ALOGV("invoke()"); + return mPlayer->invoke(request, reply); +} + +void StagefrightPlayer::setAudioSink(const sp<AudioSink> &audioSink) { + MediaPlayerInterface::setAudioSink(audioSink); + + mPlayer->setAudioSink(audioSink); +} + +status_t StagefrightPlayer::setParameter(int key, const Parcel &request) { + ALOGV("setParameter(key=%d)", key); + return mPlayer->setParameter(key, request); +} + +status_t StagefrightPlayer::getParameter(int key, Parcel *reply) { + ALOGV("getParameter"); + return mPlayer->getParameter(key, reply); +} + +status_t StagefrightPlayer::getMetadata( + const media::Metadata::Filter& ids, Parcel *records) { + using media::Metadata; + + uint32_t flags = mPlayer->flags(); + + Metadata metadata(records); + + metadata.appendBool( + Metadata::kPauseAvailable, + flags & MediaExtractor::CAN_PAUSE); + + metadata.appendBool( + Metadata::kSeekBackwardAvailable, + flags & MediaExtractor::CAN_SEEK_BACKWARD); + + metadata.appendBool( + Metadata::kSeekForwardAvailable, + flags & MediaExtractor::CAN_SEEK_FORWARD); + + metadata.appendBool( + Metadata::kSeekAvailable, + flags & MediaExtractor::CAN_SEEK); + + return OK; +} + +status_t StagefrightPlayer::dump(int fd, const Vector<String16> &args) const { + return mPlayer->dump(fd, args); +} + +} // namespace android diff --git a/media/libmediaplayerservice/StagefrightPlayer.h b/media/libmediaplayerservice/StagefrightPlayer.h new file mode 100644 index 0000000..e89e18a --- /dev/null +++ b/media/libmediaplayerservice/StagefrightPlayer.h @@ -0,0 +1,76 @@ +/* +** +** Copyright 2009, 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 ANDROID_STAGEFRIGHTPLAYER_H +#define ANDROID_STAGEFRIGHTPLAYER_H + +#include <media/MediaPlayerInterface.h> + +namespace android { + +struct AwesomePlayer; + +class StagefrightPlayer : public MediaPlayerInterface { +public: + StagefrightPlayer(); + virtual ~StagefrightPlayer(); + + virtual status_t initCheck(); + + virtual status_t setUID(uid_t uid); + + virtual status_t setDataSource( + const char *url, const KeyedVector<String8, String8> *headers); + + virtual status_t setDataSource(int fd, int64_t offset, int64_t length); + + virtual status_t setDataSource(const sp<IStreamSource> &source); + + virtual status_t setVideoSurfaceTexture( + const sp<ISurfaceTexture> &surfaceTexture); + virtual status_t prepare(); + virtual status_t prepareAsync(); + virtual status_t start(); + virtual status_t stop(); + virtual status_t pause(); + virtual bool isPlaying(); + virtual status_t seekTo(int msec); + virtual status_t getCurrentPosition(int *msec); + virtual status_t getDuration(int *msec); + virtual status_t reset(); + virtual status_t setLooping(int loop); + virtual player_type playerType(); + virtual status_t invoke(const Parcel &request, Parcel *reply); + virtual void setAudioSink(const sp<AudioSink> &audioSink); + virtual status_t setParameter(int key, const Parcel &request); + virtual status_t getParameter(int key, Parcel *reply); + + virtual status_t getMetadata( + const media::Metadata::Filter& ids, Parcel *records); + + virtual status_t dump(int fd, const Vector<String16> &args) const; + +private: + AwesomePlayer *mPlayer; + + StagefrightPlayer(const StagefrightPlayer &); + StagefrightPlayer &operator=(const StagefrightPlayer &); +}; + +} // namespace android + +#endif // ANDROID_STAGEFRIGHTPLAYER_H diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp new file mode 100644 index 0000000..ca79657 --- /dev/null +++ b/media/libmediaplayerservice/StagefrightRecorder.cpp @@ -0,0 +1,1755 @@ +/* + * Copyright (C) 2009 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 "StagefrightRecorder" +#include <utils/Log.h> + +#include "StagefrightRecorder.h" + +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> + +#include <media/IMediaPlayerService.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/AudioSource.h> +#include <media/stagefright/AMRWriter.h> +#include <media/stagefright/AACWriter.h> +#include <media/stagefright/CameraSource.h> +#include <media/stagefright/CameraSourceTimeLapse.h> +#include <media/stagefright/MPEG2TSWriter.h> +#include <media/stagefright/MPEG4Writer.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MetaData.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> +#include <ctype.h> +#include <unistd.h> + +#include <system/audio.h> + +#include "ARTPWriter.h" + +namespace android { + +// To collect the encoder usage for the battery app +static void addBatteryData(uint32_t params) { + sp<IBinder> binder = + defaultServiceManager()->getService(String16("media.player")); + sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder); + CHECK(service.get() != NULL); + + service->addBatteryData(params); +} + + +StagefrightRecorder::StagefrightRecorder() + : mWriter(NULL), + mOutputFd(-1), + mAudioSource(AUDIO_SOURCE_CNT), + mVideoSource(VIDEO_SOURCE_LIST_END), + mStarted(false), mSurfaceMediaSource(NULL) { + + ALOGV("Constructor"); + reset(); +} + +StagefrightRecorder::~StagefrightRecorder() { + ALOGV("Destructor"); + stop(); +} + +status_t StagefrightRecorder::init() { + ALOGV("init"); + return OK; +} + +// The client side of mediaserver asks it to creat a SurfaceMediaSource +// and return a interface reference. The client side will use that +// while encoding GL Frames +sp<ISurfaceTexture> StagefrightRecorder::querySurfaceMediaSource() const { + ALOGV("Get SurfaceMediaSource"); + return mSurfaceMediaSource; +} + +status_t StagefrightRecorder::setAudioSource(audio_source_t as) { + ALOGV("setAudioSource: %d", as); + if (as < AUDIO_SOURCE_DEFAULT || + as >= AUDIO_SOURCE_CNT) { + ALOGE("Invalid audio source: %d", as); + return BAD_VALUE; + } + + if (as == AUDIO_SOURCE_DEFAULT) { + mAudioSource = AUDIO_SOURCE_MIC; + } else { + mAudioSource = as; + } + + return OK; +} + +status_t StagefrightRecorder::setVideoSource(video_source vs) { + ALOGV("setVideoSource: %d", vs); + if (vs < VIDEO_SOURCE_DEFAULT || + vs >= VIDEO_SOURCE_LIST_END) { + ALOGE("Invalid video source: %d", vs); + return BAD_VALUE; + } + + if (vs == VIDEO_SOURCE_DEFAULT) { + mVideoSource = VIDEO_SOURCE_CAMERA; + } else { + mVideoSource = vs; + } + + return OK; +} + +status_t StagefrightRecorder::setOutputFormat(output_format of) { + ALOGV("setOutputFormat: %d", of); + if (of < OUTPUT_FORMAT_DEFAULT || + of >= OUTPUT_FORMAT_LIST_END) { + ALOGE("Invalid output format: %d", of); + return BAD_VALUE; + } + + if (of == OUTPUT_FORMAT_DEFAULT) { + mOutputFormat = OUTPUT_FORMAT_THREE_GPP; + } else { + mOutputFormat = of; + } + + return OK; +} + +status_t StagefrightRecorder::setAudioEncoder(audio_encoder ae) { + ALOGV("setAudioEncoder: %d", ae); + if (ae < AUDIO_ENCODER_DEFAULT || + ae >= AUDIO_ENCODER_LIST_END) { + ALOGE("Invalid audio encoder: %d", ae); + return BAD_VALUE; + } + + if (ae == AUDIO_ENCODER_DEFAULT) { + mAudioEncoder = AUDIO_ENCODER_AMR_NB; + } else { + mAudioEncoder = ae; + } + + return OK; +} + +status_t StagefrightRecorder::setVideoEncoder(video_encoder ve) { + ALOGV("setVideoEncoder: %d", ve); + if (ve < VIDEO_ENCODER_DEFAULT || + ve >= VIDEO_ENCODER_LIST_END) { + ALOGE("Invalid video encoder: %d", ve); + return BAD_VALUE; + } + + if (ve == VIDEO_ENCODER_DEFAULT) { + mVideoEncoder = VIDEO_ENCODER_H263; + } else { + mVideoEncoder = ve; + } + + return OK; +} + +status_t StagefrightRecorder::setVideoSize(int width, int height) { + ALOGV("setVideoSize: %dx%d", width, height); + if (width <= 0 || height <= 0) { + ALOGE("Invalid video size: %dx%d", width, height); + return BAD_VALUE; + } + + // Additional check on the dimension will be performed later + mVideoWidth = width; + mVideoHeight = height; + + return OK; +} + +status_t StagefrightRecorder::setVideoFrameRate(int frames_per_second) { + ALOGV("setVideoFrameRate: %d", frames_per_second); + if ((frames_per_second <= 0 && frames_per_second != -1) || + frames_per_second > 120) { + ALOGE("Invalid video frame rate: %d", frames_per_second); + return BAD_VALUE; + } + + // Additional check on the frame rate will be performed later + mFrameRate = frames_per_second; + + return OK; +} + +status_t StagefrightRecorder::setCamera(const sp<ICamera> &camera, + const sp<ICameraRecordingProxy> &proxy) { + ALOGV("setCamera"); + if (camera == 0) { + ALOGE("camera is NULL"); + return BAD_VALUE; + } + if (proxy == 0) { + ALOGE("camera proxy is NULL"); + return BAD_VALUE; + } + + mCamera = camera; + mCameraProxy = proxy; + return OK; +} + +status_t StagefrightRecorder::setPreviewSurface(const sp<Surface> &surface) { + ALOGV("setPreviewSurface: %p", surface.get()); + mPreviewSurface = surface; + + return OK; +} + +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. + + return -EPERM; +} + +status_t StagefrightRecorder::setOutputFile(int fd, int64_t offset, int64_t length) { + ALOGV("setOutputFile: %d, %lld, %lld", fd, offset, length); + // These don't make any sense, do they? + CHECK_EQ(offset, 0ll); + CHECK_EQ(length, 0ll); + + if (fd < 0) { + ALOGE("Invalid file descriptor: %d", fd); + return -EBADF; + } + + if (mOutputFd >= 0) { + ::close(mOutputFd); + } + mOutputFd = dup(fd); + + return OK; +} + +// Attempt to parse an int64 literal optionally surrounded by whitespace, +// returns true on success, false otherwise. +static bool safe_strtoi64(const char *s, int64_t *val) { + char *end; + + // It is lame, but according to man page, we have to set errno to 0 + // before calling strtoll(). + errno = 0; + *val = strtoll(s, &end, 10); + + if (end == s || errno == ERANGE) { + return false; + } + + // Skip trailing whitespace + while (isspace(*end)) { + ++end; + } + + // For a successful return, the string must contain nothing but a valid + // int64 literal optionally surrounded by whitespace. + + return *end == '\0'; +} + +// Return true if the value is in [0, 0x007FFFFFFF] +static bool safe_strtoi32(const char *s, int32_t *val) { + int64_t temp; + if (safe_strtoi64(s, &temp)) { + if (temp >= 0 && temp <= 0x007FFFFFFF) { + *val = static_cast<int32_t>(temp); + return true; + } + } + return false; +} + +// Trim both leading and trailing whitespace from the given string. +static void TrimString(String8 *s) { + size_t num_bytes = s->bytes(); + const char *data = s->string(); + + size_t leading_space = 0; + while (leading_space < num_bytes && isspace(data[leading_space])) { + ++leading_space; + } + + size_t i = num_bytes; + while (i > leading_space && isspace(data[i - 1])) { + --i; + } + + s->setTo(String8(&data[leading_space], i - leading_space)); +} + +status_t StagefrightRecorder::setParamAudioSamplingRate(int32_t sampleRate) { + ALOGV("setParamAudioSamplingRate: %d", sampleRate); + if (sampleRate <= 0) { + ALOGE("Invalid audio sampling rate: %d", sampleRate); + return BAD_VALUE; + } + + // Additional check on the sample rate will be performed later. + mSampleRate = sampleRate; + return OK; +} + +status_t StagefrightRecorder::setParamAudioNumberOfChannels(int32_t channels) { + ALOGV("setParamAudioNumberOfChannels: %d", channels); + if (channels <= 0 || channels >= 3) { + ALOGE("Invalid number of audio channels: %d", channels); + return BAD_VALUE; + } + + // Additional check on the number of channels will be performed later. + mAudioChannels = channels; + return OK; +} + +status_t StagefrightRecorder::setParamAudioEncodingBitRate(int32_t bitRate) { + ALOGV("setParamAudioEncodingBitRate: %d", bitRate); + if (bitRate <= 0) { + ALOGE("Invalid audio encoding bit rate: %d", bitRate); + return BAD_VALUE; + } + + // The target bit rate may not be exactly the same as the requested. + // It depends on many factors, such as rate control, and the bit rate + // range that a specific encoder supports. The mismatch between the + // the target and requested bit rate will NOT be treated as an error. + mAudioBitRate = bitRate; + return OK; +} + +status_t StagefrightRecorder::setParamVideoEncodingBitRate(int32_t bitRate) { + ALOGV("setParamVideoEncodingBitRate: %d", bitRate); + if (bitRate <= 0) { + ALOGE("Invalid video encoding bit rate: %d", bitRate); + return BAD_VALUE; + } + + // The target bit rate may not be exactly the same as the requested. + // It depends on many factors, such as rate control, and the bit rate + // range that a specific encoder supports. The mismatch between the + // the target and requested bit rate will NOT be treated as an error. + mVideoBitRate = bitRate; + return OK; +} + +// Always rotate clockwise, and only support 0, 90, 180 and 270 for now. +status_t StagefrightRecorder::setParamVideoRotation(int32_t degrees) { + ALOGV("setParamVideoRotation: %d", degrees); + if (degrees < 0 || degrees % 90 != 0) { + ALOGE("Unsupported video rotation angle: %d", degrees); + return BAD_VALUE; + } + mRotationDegrees = degrees % 360; + return OK; +} + +status_t StagefrightRecorder::setParamMaxFileDurationUs(int64_t timeUs) { + ALOGV("setParamMaxFileDurationUs: %lld us", timeUs); + + // This is meant for backward compatibility for MediaRecorder.java + if (timeUs <= 0) { + ALOGW("Max file duration is not positive: %lld us. Disabling duration limit.", timeUs); + timeUs = 0; // Disable the duration limit for zero or negative values. + } else if (timeUs <= 100000LL) { // XXX: 100 milli-seconds + ALOGE("Max file duration is too short: %lld us", timeUs); + return BAD_VALUE; + } + + if (timeUs <= 15 * 1000000LL) { + ALOGW("Target duration (%lld us) too short to be respected", timeUs); + } + mMaxFileDurationUs = timeUs; + return OK; +} + +status_t StagefrightRecorder::setParamMaxFileSizeBytes(int64_t bytes) { + ALOGV("setParamMaxFileSizeBytes: %lld bytes", bytes); + + // This is meant for backward compatibility for MediaRecorder.java + if (bytes <= 0) { + ALOGW("Max file size is not positive: %lld bytes. " + "Disabling file size limit.", bytes); + bytes = 0; // Disable the file size limit for zero or negative values. + } else if (bytes <= 1024) { // XXX: 1 kB + ALOGE("Max file size is too small: %lld bytes", bytes); + return BAD_VALUE; + } + + if (bytes <= 100 * 1024) { + ALOGW("Target file size (%lld bytes) is too small to be respected", bytes); + } + + mMaxFileSizeBytes = bytes; + return OK; +} + +status_t StagefrightRecorder::setParamInterleaveDuration(int32_t durationUs) { + ALOGV("setParamInterleaveDuration: %d", durationUs); + if (durationUs <= 500000) { // 500 ms + // If interleave duration is too small, it is very inefficient to do + // interleaving since the metadata overhead will count for a significant + // portion of the saved contents + ALOGE("Audio/video interleave duration is too small: %d us", durationUs); + return BAD_VALUE; + } else if (durationUs >= 10000000) { // 10 seconds + // If interleaving duration is too large, it can cause the recording + // session to use too much memory since we have to save the output + // data before we write them out + ALOGE("Audio/video interleave duration is too large: %d us", durationUs); + return BAD_VALUE; + } + mInterleaveDurationUs = durationUs; + return OK; +} + +// If seconds < 0, only the first frame is I frame, and rest are all P frames +// If seconds == 0, all frames are encoded as I frames. No P frames +// If seconds > 0, it is the time spacing (seconds) between 2 neighboring I frames +status_t StagefrightRecorder::setParamVideoIFramesInterval(int32_t seconds) { + ALOGV("setParamVideoIFramesInterval: %d seconds", seconds); + mIFramesIntervalSec = seconds; + return OK; +} + +status_t StagefrightRecorder::setParam64BitFileOffset(bool use64Bit) { + ALOGV("setParam64BitFileOffset: %s", + use64Bit? "use 64 bit file offset": "use 32 bit file offset"); + mUse64BitFileOffset = use64Bit; + return OK; +} + +status_t StagefrightRecorder::setParamVideoCameraId(int32_t cameraId) { + ALOGV("setParamVideoCameraId: %d", cameraId); + if (cameraId < 0) { + return BAD_VALUE; + } + mCameraId = cameraId; + return OK; +} + +status_t StagefrightRecorder::setParamTrackTimeStatus(int64_t timeDurationUs) { + ALOGV("setParamTrackTimeStatus: %lld", timeDurationUs); + if (timeDurationUs < 20000) { // Infeasible if shorter than 20 ms? + ALOGE("Tracking time duration too short: %lld us", timeDurationUs); + return BAD_VALUE; + } + mTrackEveryTimeDurationUs = timeDurationUs; + return OK; +} + +status_t StagefrightRecorder::setParamVideoEncoderProfile(int32_t profile) { + ALOGV("setParamVideoEncoderProfile: %d", profile); + + // Additional check will be done later when we load the encoder. + // For now, we are accepting values defined in OpenMAX IL. + mVideoEncoderProfile = profile; + return OK; +} + +status_t StagefrightRecorder::setParamVideoEncoderLevel(int32_t level) { + ALOGV("setParamVideoEncoderLevel: %d", level); + + // Additional check will be done later when we load the encoder. + // For now, we are accepting values defined in OpenMAX IL. + mVideoEncoderLevel = level; + return OK; +} + +status_t StagefrightRecorder::setParamMovieTimeScale(int32_t timeScale) { + ALOGV("setParamMovieTimeScale: %d", timeScale); + + // The range is set to be the same as the audio's time scale range + // since audio's time scale has a wider range. + if (timeScale < 600 || timeScale > 96000) { + ALOGE("Time scale (%d) for movie is out of range [600, 96000]", timeScale); + return BAD_VALUE; + } + mMovieTimeScale = timeScale; + return OK; +} + +status_t StagefrightRecorder::setParamVideoTimeScale(int32_t timeScale) { + ALOGV("setParamVideoTimeScale: %d", timeScale); + + // 60000 is chosen to make sure that each video frame from a 60-fps + // video has 1000 ticks. + if (timeScale < 600 || timeScale > 60000) { + ALOGE("Time scale (%d) for video is out of range [600, 60000]", timeScale); + return BAD_VALUE; + } + mVideoTimeScale = timeScale; + return OK; +} + +status_t StagefrightRecorder::setParamAudioTimeScale(int32_t timeScale) { + ALOGV("setParamAudioTimeScale: %d", timeScale); + + // 96000 Hz is the highest sampling rate support in AAC. + if (timeScale < 600 || timeScale > 96000) { + ALOGE("Time scale (%d) for audio is out of range [600, 96000]", timeScale); + return BAD_VALUE; + } + mAudioTimeScale = timeScale; + return OK; +} + +status_t StagefrightRecorder::setParamTimeLapseEnable(int32_t timeLapseEnable) { + ALOGV("setParamTimeLapseEnable: %d", timeLapseEnable); + + if(timeLapseEnable == 0) { + mCaptureTimeLapse = false; + } else if (timeLapseEnable == 1) { + mCaptureTimeLapse = true; + } else { + return BAD_VALUE; + } + return OK; +} + +status_t StagefrightRecorder::setParamTimeBetweenTimeLapseFrameCapture(int64_t timeUs) { + ALOGV("setParamTimeBetweenTimeLapseFrameCapture: %lld us", timeUs); + + // Not allowing time more than a day + if (timeUs <= 0 || timeUs > 86400*1E6) { + ALOGE("Time between time lapse frame capture (%lld) is out of range [0, 1 Day]", timeUs); + return BAD_VALUE; + } + + mTimeBetweenTimeLapseFrameCaptureUs = timeUs; + return OK; +} + +status_t StagefrightRecorder::setParamGeoDataLongitude( + int64_t longitudex10000) { + + if (longitudex10000 > 1800000 || longitudex10000 < -1800000) { + return BAD_VALUE; + } + mLongitudex10000 = longitudex10000; + return OK; +} + +status_t StagefrightRecorder::setParamGeoDataLatitude( + int64_t latitudex10000) { + + if (latitudex10000 > 900000 || latitudex10000 < -900000) { + return BAD_VALUE; + } + mLatitudex10000 = latitudex10000; + return OK; +} + +status_t StagefrightRecorder::setParameter( + const String8 &key, const String8 &value) { + ALOGV("setParameter: key (%s) => value (%s)", key.string(), value.string()); + if (key == "max-duration") { + int64_t max_duration_ms; + if (safe_strtoi64(value.string(), &max_duration_ms)) { + return setParamMaxFileDurationUs(1000LL * max_duration_ms); + } + } else if (key == "max-filesize") { + int64_t max_filesize_bytes; + if (safe_strtoi64(value.string(), &max_filesize_bytes)) { + return setParamMaxFileSizeBytes(max_filesize_bytes); + } + } else if (key == "interleave-duration-us") { + int32_t durationUs; + if (safe_strtoi32(value.string(), &durationUs)) { + return setParamInterleaveDuration(durationUs); + } + } else if (key == "param-movie-time-scale") { + int32_t timeScale; + if (safe_strtoi32(value.string(), &timeScale)) { + return setParamMovieTimeScale(timeScale); + } + } else if (key == "param-use-64bit-offset") { + int32_t use64BitOffset; + if (safe_strtoi32(value.string(), &use64BitOffset)) { + return setParam64BitFileOffset(use64BitOffset != 0); + } + } else if (key == "param-geotag-longitude") { + int64_t longitudex10000; + if (safe_strtoi64(value.string(), &longitudex10000)) { + return setParamGeoDataLongitude(longitudex10000); + } + } else if (key == "param-geotag-latitude") { + int64_t latitudex10000; + if (safe_strtoi64(value.string(), &latitudex10000)) { + return setParamGeoDataLatitude(latitudex10000); + } + } else if (key == "param-track-time-status") { + int64_t timeDurationUs; + if (safe_strtoi64(value.string(), &timeDurationUs)) { + return setParamTrackTimeStatus(timeDurationUs); + } + } else if (key == "audio-param-sampling-rate") { + int32_t sampling_rate; + if (safe_strtoi32(value.string(), &sampling_rate)) { + return setParamAudioSamplingRate(sampling_rate); + } + } else if (key == "audio-param-number-of-channels") { + int32_t number_of_channels; + if (safe_strtoi32(value.string(), &number_of_channels)) { + return setParamAudioNumberOfChannels(number_of_channels); + } + } else if (key == "audio-param-encoding-bitrate") { + int32_t audio_bitrate; + if (safe_strtoi32(value.string(), &audio_bitrate)) { + return setParamAudioEncodingBitRate(audio_bitrate); + } + } else if (key == "audio-param-time-scale") { + int32_t timeScale; + if (safe_strtoi32(value.string(), &timeScale)) { + return setParamAudioTimeScale(timeScale); + } + } else if (key == "video-param-encoding-bitrate") { + int32_t video_bitrate; + if (safe_strtoi32(value.string(), &video_bitrate)) { + return setParamVideoEncodingBitRate(video_bitrate); + } + } else if (key == "video-param-rotation-angle-degrees") { + int32_t degrees; + if (safe_strtoi32(value.string(), °rees)) { + return setParamVideoRotation(degrees); + } + } else if (key == "video-param-i-frames-interval") { + int32_t seconds; + if (safe_strtoi32(value.string(), &seconds)) { + return setParamVideoIFramesInterval(seconds); + } + } else if (key == "video-param-encoder-profile") { + int32_t profile; + if (safe_strtoi32(value.string(), &profile)) { + return setParamVideoEncoderProfile(profile); + } + } else if (key == "video-param-encoder-level") { + int32_t level; + if (safe_strtoi32(value.string(), &level)) { + return setParamVideoEncoderLevel(level); + } + } else if (key == "video-param-camera-id") { + int32_t cameraId; + if (safe_strtoi32(value.string(), &cameraId)) { + return setParamVideoCameraId(cameraId); + } + } else if (key == "video-param-time-scale") { + int32_t timeScale; + if (safe_strtoi32(value.string(), &timeScale)) { + return setParamVideoTimeScale(timeScale); + } + } else if (key == "time-lapse-enable") { + int32_t timeLapseEnable; + if (safe_strtoi32(value.string(), &timeLapseEnable)) { + return setParamTimeLapseEnable(timeLapseEnable); + } + } else if (key == "time-between-time-lapse-frame-capture") { + int64_t timeBetweenTimeLapseFrameCaptureMs; + if (safe_strtoi64(value.string(), &timeBetweenTimeLapseFrameCaptureMs)) { + return setParamTimeBetweenTimeLapseFrameCapture( + 1000LL * timeBetweenTimeLapseFrameCaptureMs); + } + } else { + ALOGE("setParameter: failed to find key %s", key.string()); + } + return BAD_VALUE; +} + +status_t StagefrightRecorder::setParameters(const String8 ¶ms) { + ALOGV("setParameters: %s", params.string()); + const char *cparams = params.string(); + const char *key_start = cparams; + for (;;) { + const char *equal_pos = strchr(key_start, '='); + if (equal_pos == NULL) { + ALOGE("Parameters %s miss a value", cparams); + return BAD_VALUE; + } + String8 key(key_start, equal_pos - key_start); + TrimString(&key); + if (key.length() == 0) { + ALOGE("Parameters %s contains an empty key", cparams); + return BAD_VALUE; + } + const char *value_start = equal_pos + 1; + const char *semicolon_pos = strchr(value_start, ';'); + String8 value; + if (semicolon_pos == NULL) { + value.setTo(value_start); + } else { + value.setTo(value_start, semicolon_pos - value_start); + } + if (setParameter(key, value) != OK) { + return BAD_VALUE; + } + if (semicolon_pos == NULL) { + break; // Reaches the end + } + key_start = semicolon_pos + 1; + } + return OK; +} + +status_t StagefrightRecorder::setListener(const sp<IMediaRecorderClient> &listener) { + mListener = listener; + + return OK; +} + +status_t StagefrightRecorder::prepare() { + return OK; +} + +status_t StagefrightRecorder::start() { + CHECK_GE(mOutputFd, 0); + + if (mWriter != NULL) { + ALOGE("File writer is not avaialble"); + return UNKNOWN_ERROR; + } + + status_t status = OK; + + switch (mOutputFormat) { + case OUTPUT_FORMAT_DEFAULT: + case OUTPUT_FORMAT_THREE_GPP: + case OUTPUT_FORMAT_MPEG_4: + status = startMPEG4Recording(); + break; + + case OUTPUT_FORMAT_AMR_NB: + case OUTPUT_FORMAT_AMR_WB: + status = startAMRRecording(); + break; + + case OUTPUT_FORMAT_AAC_ADIF: + case OUTPUT_FORMAT_AAC_ADTS: + status = startAACRecording(); + break; + + case OUTPUT_FORMAT_RTP_AVP: + status = startRTPRecording(); + break; + + case OUTPUT_FORMAT_MPEG2TS: + status = startMPEG2TSRecording(); + break; + + default: + ALOGE("Unsupported output file format: %d", mOutputFormat); + status = UNKNOWN_ERROR; + break; + } + + if ((status == OK) && (!mStarted)) { + mStarted = true; + + uint32_t params = IMediaPlayerService::kBatteryDataCodecStarted; + if (mAudioSource != AUDIO_SOURCE_CNT) { + params |= IMediaPlayerService::kBatteryDataTrackAudio; + } + if (mVideoSource != VIDEO_SOURCE_LIST_END) { + params |= IMediaPlayerService::kBatteryDataTrackVideo; + } + + addBatteryData(params); + } + + return status; +} + +sp<MediaSource> StagefrightRecorder::createAudioSource() { + sp<AudioSource> audioSource = + new AudioSource( + mAudioSource, + mSampleRate, + mAudioChannels); + + status_t err = audioSource->initCheck(); + + if (err != OK) { + ALOGE("audio source is not initialized"); + return NULL; + } + + sp<MetaData> encMeta = new MetaData; + const char *mime; + switch (mAudioEncoder) { + case AUDIO_ENCODER_AMR_NB: + case AUDIO_ENCODER_DEFAULT: + mime = MEDIA_MIMETYPE_AUDIO_AMR_NB; + break; + case AUDIO_ENCODER_AMR_WB: + mime = MEDIA_MIMETYPE_AUDIO_AMR_WB; + break; + case AUDIO_ENCODER_AAC: + mime = MEDIA_MIMETYPE_AUDIO_AAC; + 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); + if (mAudioTimeScale > 0) { + encMeta->setInt32(kKeyTimeScale, mAudioTimeScale); + } + + OMXClient client; + CHECK_EQ(client.connect(), (status_t)OK); + + sp<MediaSource> audioEncoder = + OMXCodec::Create(client.interface(), encMeta, + true /* createEncoder */, audioSource); + mAudioSourceNode = audioSource; + + return audioEncoder; +} + +status_t StagefrightRecorder::startAACRecording() { + // FIXME: + // Add support for OUTPUT_FORMAT_AAC_ADIF + CHECK_EQ(mOutputFormat, OUTPUT_FORMAT_AAC_ADTS); + + CHECK_EQ(mAudioEncoder, AUDIO_ENCODER_AAC); + CHECK(mAudioSource != AUDIO_SOURCE_CNT); + + mWriter = new AACWriter(mOutputFd); + status_t status = startRawAudioRecording(); + if (status != OK) { + mWriter.clear(); + mWriter = NULL; + } + + return status; +} + +status_t StagefrightRecorder::startAMRRecording() { + CHECK(mOutputFormat == OUTPUT_FORMAT_AMR_NB || + mOutputFormat == OUTPUT_FORMAT_AMR_WB); + + if (mOutputFormat == OUTPUT_FORMAT_AMR_NB) { + if (mAudioEncoder != AUDIO_ENCODER_DEFAULT && + mAudioEncoder != AUDIO_ENCODER_AMR_NB) { + ALOGE("Invalid encoder %d used for AMRNB recording", + mAudioEncoder); + return BAD_VALUE; + } + } else { // mOutputFormat must be OUTPUT_FORMAT_AMR_WB + if (mAudioEncoder != AUDIO_ENCODER_AMR_WB) { + ALOGE("Invlaid encoder %d used for AMRWB recording", + mAudioEncoder); + return BAD_VALUE; + } + } + + mWriter = new AMRWriter(mOutputFd); + status_t status = startRawAudioRecording(); + if (status != OK) { + mWriter.clear(); + mWriter = NULL; + } + return status; +} + +status_t StagefrightRecorder::startRawAudioRecording() { + if (mAudioSource >= AUDIO_SOURCE_CNT) { + ALOGE("Invalid audio source: %d", mAudioSource); + return BAD_VALUE; + } + + status_t status = BAD_VALUE; + if (OK != (status = checkAudioEncoderCapabilities())) { + return status; + } + + sp<MediaSource> audioEncoder = createAudioSource(); + if (audioEncoder == NULL) { + return UNKNOWN_ERROR; + } + + CHECK(mWriter != 0); + mWriter->addSource(audioEncoder); + + if (mMaxFileDurationUs != 0) { + mWriter->setMaxFileDuration(mMaxFileDurationUs); + } + if (mMaxFileSizeBytes != 0) { + mWriter->setMaxFileSize(mMaxFileSizeBytes); + } + mWriter->setListener(mListener); + mWriter->start(); + + return OK; +} + +status_t StagefrightRecorder::startRTPRecording() { + CHECK_EQ(mOutputFormat, OUTPUT_FORMAT_RTP_AVP); + + if ((mAudioSource != AUDIO_SOURCE_CNT + && mVideoSource != VIDEO_SOURCE_LIST_END) + || (mAudioSource == AUDIO_SOURCE_CNT + && mVideoSource == VIDEO_SOURCE_LIST_END)) { + // Must have exactly one source. + return BAD_VALUE; + } + + if (mOutputFd < 0) { + return BAD_VALUE; + } + + sp<MediaSource> source; + + if (mAudioSource != AUDIO_SOURCE_CNT) { + source = createAudioSource(); + } else { + + sp<MediaSource> mediaSource; + status_t err = setupMediaSource(&mediaSource); + if (err != OK) { + return err; + } + + err = setupVideoEncoder(mediaSource, mVideoBitRate, &source); + if (err != OK) { + return err; + } + } + + mWriter = new ARTPWriter(mOutputFd); + mWriter->addSource(source); + mWriter->setListener(mListener); + + return mWriter->start(); +} + +status_t StagefrightRecorder::startMPEG2TSRecording() { + CHECK_EQ(mOutputFormat, OUTPUT_FORMAT_MPEG2TS); + + sp<MediaWriter> writer = new MPEG2TSWriter(mOutputFd); + + if (mAudioSource != AUDIO_SOURCE_CNT) { + if (mAudioEncoder != AUDIO_ENCODER_AAC) { + return ERROR_UNSUPPORTED; + } + + status_t err = setupAudioEncoder(writer); + + if (err != OK) { + return err; + } + } + + if (mVideoSource < VIDEO_SOURCE_LIST_END) { + if (mVideoEncoder != VIDEO_ENCODER_H264) { + return ERROR_UNSUPPORTED; + } + + sp<MediaSource> mediaSource; + status_t err = setupMediaSource(&mediaSource); + if (err != OK) { + return err; + } + + sp<MediaSource> encoder; + err = setupVideoEncoder(mediaSource, mVideoBitRate, &encoder); + + if (err != OK) { + return err; + } + + writer->addSource(encoder); + } + + if (mMaxFileDurationUs != 0) { + writer->setMaxFileDuration(mMaxFileDurationUs); + } + + if (mMaxFileSizeBytes != 0) { + writer->setMaxFileSize(mMaxFileSizeBytes); + } + + mWriter = writer; + + return mWriter->start(); +} + +void StagefrightRecorder::clipVideoFrameRate() { + ALOGV("clipVideoFrameRate: encoder %d", mVideoEncoder); + int minFrameRate = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.fps.min", mVideoEncoder); + int maxFrameRate = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.fps.max", mVideoEncoder); + if (mFrameRate < minFrameRate && mFrameRate != -1) { + ALOGW("Intended video encoding frame rate (%d fps) is too small" + " and will be set to (%d fps)", mFrameRate, minFrameRate); + mFrameRate = minFrameRate; + } else if (mFrameRate > maxFrameRate) { + ALOGW("Intended video encoding frame rate (%d fps) is too large" + " and will be set to (%d fps)", mFrameRate, maxFrameRate); + mFrameRate = maxFrameRate; + } +} + +void StagefrightRecorder::clipVideoBitRate() { + ALOGV("clipVideoBitRate: encoder %d", mVideoEncoder); + int minBitRate = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.bps.min", mVideoEncoder); + int maxBitRate = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.bps.max", mVideoEncoder); + if (mVideoBitRate < minBitRate) { + ALOGW("Intended video encoding bit rate (%d bps) is too small" + " and will be set to (%d bps)", mVideoBitRate, minBitRate); + mVideoBitRate = minBitRate; + } else if (mVideoBitRate > maxBitRate) { + ALOGW("Intended video encoding bit rate (%d bps) is too large" + " and will be set to (%d bps)", mVideoBitRate, maxBitRate); + mVideoBitRate = maxBitRate; + } +} + +void StagefrightRecorder::clipVideoFrameWidth() { + ALOGV("clipVideoFrameWidth: encoder %d", mVideoEncoder); + int minFrameWidth = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.width.min", mVideoEncoder); + int maxFrameWidth = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.width.max", mVideoEncoder); + if (mVideoWidth < minFrameWidth) { + ALOGW("Intended video encoding frame width (%d) is too small" + " and will be set to (%d)", mVideoWidth, minFrameWidth); + mVideoWidth = minFrameWidth; + } else if (mVideoWidth > maxFrameWidth) { + ALOGW("Intended video encoding frame width (%d) is too large" + " and will be set to (%d)", mVideoWidth, maxFrameWidth); + mVideoWidth = maxFrameWidth; + } +} + +status_t StagefrightRecorder::checkVideoEncoderCapabilities() { + if (!mCaptureTimeLapse) { + // Dont clip for time lapse capture as encoder will have enough + // time to encode because of slow capture rate of time lapse. + clipVideoBitRate(); + clipVideoFrameRate(); + clipVideoFrameWidth(); + clipVideoFrameHeight(); + setDefaultProfileIfNecessary(); + } + return OK; +} + +// Set to use AVC baseline profile if the encoding parameters matches +// CAMCORDER_QUALITY_LOW profile; this is for the sake of MMS service. +void StagefrightRecorder::setDefaultProfileIfNecessary() { + ALOGV("setDefaultProfileIfNecessary"); + + camcorder_quality quality = CAMCORDER_QUALITY_LOW; + + int64_t durationUs = mEncoderProfiles->getCamcorderProfileParamByName( + "duration", mCameraId, quality) * 1000000LL; + + int fileFormat = mEncoderProfiles->getCamcorderProfileParamByName( + "file.format", mCameraId, quality); + + int videoCodec = mEncoderProfiles->getCamcorderProfileParamByName( + "vid.codec", mCameraId, quality); + + int videoBitRate = mEncoderProfiles->getCamcorderProfileParamByName( + "vid.bps", mCameraId, quality); + + int videoFrameRate = mEncoderProfiles->getCamcorderProfileParamByName( + "vid.fps", mCameraId, quality); + + int videoFrameWidth = mEncoderProfiles->getCamcorderProfileParamByName( + "vid.width", mCameraId, quality); + + int videoFrameHeight = mEncoderProfiles->getCamcorderProfileParamByName( + "vid.height", mCameraId, quality); + + int audioCodec = mEncoderProfiles->getCamcorderProfileParamByName( + "aud.codec", mCameraId, quality); + + int audioBitRate = mEncoderProfiles->getCamcorderProfileParamByName( + "aud.bps", mCameraId, quality); + + int audioSampleRate = mEncoderProfiles->getCamcorderProfileParamByName( + "aud.hz", mCameraId, quality); + + int audioChannels = mEncoderProfiles->getCamcorderProfileParamByName( + "aud.ch", mCameraId, quality); + + if (durationUs == mMaxFileDurationUs && + fileFormat == mOutputFormat && + videoCodec == mVideoEncoder && + videoBitRate == mVideoBitRate && + videoFrameRate == mFrameRate && + videoFrameWidth == mVideoWidth && + videoFrameHeight == mVideoHeight && + audioCodec == mAudioEncoder && + audioBitRate == mAudioBitRate && + audioSampleRate == mSampleRate && + audioChannels == mAudioChannels) { + if (videoCodec == VIDEO_ENCODER_H264) { + ALOGI("Force to use AVC baseline profile"); + setParamVideoEncoderProfile(OMX_VIDEO_AVCProfileBaseline); + } + } +} + +status_t StagefrightRecorder::checkAudioEncoderCapabilities() { + clipAudioBitRate(); + clipAudioSampleRate(); + clipNumberOfAudioChannels(); + return OK; +} + +void StagefrightRecorder::clipAudioBitRate() { + ALOGV("clipAudioBitRate: encoder %d", mAudioEncoder); + + int minAudioBitRate = + mEncoderProfiles->getAudioEncoderParamByName( + "enc.aud.bps.min", mAudioEncoder); + if (mAudioBitRate < minAudioBitRate) { + ALOGW("Intended audio encoding bit rate (%d) is too small" + " and will be set to (%d)", mAudioBitRate, minAudioBitRate); + mAudioBitRate = minAudioBitRate; + } + + int maxAudioBitRate = + mEncoderProfiles->getAudioEncoderParamByName( + "enc.aud.bps.max", mAudioEncoder); + if (mAudioBitRate > maxAudioBitRate) { + ALOGW("Intended audio encoding bit rate (%d) is too large" + " and will be set to (%d)", mAudioBitRate, maxAudioBitRate); + mAudioBitRate = maxAudioBitRate; + } +} + +void StagefrightRecorder::clipAudioSampleRate() { + ALOGV("clipAudioSampleRate: encoder %d", mAudioEncoder); + + int minSampleRate = + mEncoderProfiles->getAudioEncoderParamByName( + "enc.aud.hz.min", mAudioEncoder); + if (mSampleRate < minSampleRate) { + ALOGW("Intended audio sample rate (%d) is too small" + " and will be set to (%d)", mSampleRate, minSampleRate); + mSampleRate = minSampleRate; + } + + int maxSampleRate = + mEncoderProfiles->getAudioEncoderParamByName( + "enc.aud.hz.max", mAudioEncoder); + if (mSampleRate > maxSampleRate) { + ALOGW("Intended audio sample rate (%d) is too large" + " and will be set to (%d)", mSampleRate, maxSampleRate); + mSampleRate = maxSampleRate; + } +} + +void StagefrightRecorder::clipNumberOfAudioChannels() { + ALOGV("clipNumberOfAudioChannels: encoder %d", mAudioEncoder); + + int minChannels = + mEncoderProfiles->getAudioEncoderParamByName( + "enc.aud.ch.min", mAudioEncoder); + if (mAudioChannels < minChannels) { + ALOGW("Intended number of audio channels (%d) is too small" + " and will be set to (%d)", mAudioChannels, minChannels); + mAudioChannels = minChannels; + } + + int maxChannels = + mEncoderProfiles->getAudioEncoderParamByName( + "enc.aud.ch.max", mAudioEncoder); + if (mAudioChannels > maxChannels) { + ALOGW("Intended number of audio channels (%d) is too large" + " and will be set to (%d)", mAudioChannels, maxChannels); + mAudioChannels = maxChannels; + } +} + +void StagefrightRecorder::clipVideoFrameHeight() { + ALOGV("clipVideoFrameHeight: encoder %d", mVideoEncoder); + int minFrameHeight = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.height.min", mVideoEncoder); + int maxFrameHeight = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.height.max", mVideoEncoder); + if (mVideoHeight < minFrameHeight) { + ALOGW("Intended video encoding frame height (%d) is too small" + " and will be set to (%d)", mVideoHeight, minFrameHeight); + mVideoHeight = minFrameHeight; + } else if (mVideoHeight > maxFrameHeight) { + ALOGW("Intended video encoding frame height (%d) is too large" + " and will be set to (%d)", mVideoHeight, maxFrameHeight); + mVideoHeight = maxFrameHeight; + } +} + +// Set up the appropriate MediaSource depending on the chosen option +status_t StagefrightRecorder::setupMediaSource( + sp<MediaSource> *mediaSource) { + if (mVideoSource == VIDEO_SOURCE_DEFAULT + || mVideoSource == VIDEO_SOURCE_CAMERA) { + sp<CameraSource> cameraSource; + status_t err = setupCameraSource(&cameraSource); + if (err != OK) { + 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 { + 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; + if ((err = checkVideoEncoderCapabilities()) != OK) { + return err; + } + Size videoSize; + videoSize.width = mVideoWidth; + videoSize.height = mVideoHeight; + if (mCaptureTimeLapse) { + if (mTimeBetweenTimeLapseFrameCaptureUs < 0) { + ALOGE("Invalid mTimeBetweenTimeLapseFrameCaptureUs value: %lld", + mTimeBetweenTimeLapseFrameCaptureUs); + return BAD_VALUE; + } + + mCameraSourceTimeLapse = CameraSourceTimeLapse::CreateFromCamera( + mCamera, mCameraProxy, mCameraId, + videoSize, mFrameRate, mPreviewSurface, + mTimeBetweenTimeLapseFrameCaptureUs); + *cameraSource = mCameraSourceTimeLapse; + } else { + *cameraSource = CameraSource::CreateFromCamera( + mCamera, mCameraProxy, mCameraId, videoSize, mFrameRate, + mPreviewSurface, true /*storeMetaDataInVideoBuffers*/); + } + mCamera.clear(); + mCameraProxy.clear(); + if (*cameraSource == NULL) { + return UNKNOWN_ERROR; + } + + if ((*cameraSource)->initCheck() != OK) { + (*cameraSource).clear(); + *cameraSource = NULL; + return NO_INIT; + } + + // When frame rate is not set, the actual frame rate will be set to + // the current frame rate being used. + if (mFrameRate == -1) { + int32_t frameRate = 0; + CHECK ((*cameraSource)->getFormat()->findInt32( + kKeyFrameRate, &frameRate)); + ALOGI("Frame rate is not explicitly set. Use the current frame " + "rate (%d fps)", frameRate); + mFrameRate = frameRate; + } + + CHECK(mFrameRate != -1); + + mIsMetaDataStoredInVideoBuffers = + (*cameraSource)->isMetaDataStoredInVideoBuffers(); + + return OK; +} + +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); + + switch (mVideoEncoder) { + case VIDEO_ENCODER_H263: + enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263); + break; + + case VIDEO_ENCODER_MPEG_4_SP: + enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4); + break; + + case VIDEO_ENCODER_H264: + enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC); + break; + + default: + CHECK(!"Should not be here, unsupported video encoding."); + break; + } + + 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)); + + 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); + } + if (mVideoEncoderProfile != -1) { + enc_meta->setInt32(kKeyVideoProfile, mVideoEncoderProfile); + } + if (mVideoEncoderLevel != -1) { + enc_meta->setInt32(kKeyVideoLevel, mVideoEncoderLevel); + } + + OMXClient client; + CHECK_EQ(client.connect(), (status_t)OK); + + uint32_t encoder_flags = 0; + if (mIsMetaDataStoredInVideoBuffers) { + encoder_flags |= OMXCodec::kHardwareCodecsOnly; + encoder_flags |= OMXCodec::kStoreMetaDataInVideoBuffers; + } + + // 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; + } + + sp<MediaSource> encoder = OMXCodec::Create( + client.interface(), enc_meta, + true /* createEncoder */, cameraSource, + NULL, encoder_flags); + if (encoder == NULL) { + ALOGW("Failed to create the 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(); + return UNKNOWN_ERROR; + } + + *source = encoder; + + return OK; +} + +status_t StagefrightRecorder::setupAudioEncoder(const sp<MediaWriter>& writer) { + status_t status = BAD_VALUE; + if (OK != (status = checkAudioEncoderCapabilities())) { + return status; + } + + switch(mAudioEncoder) { + case AUDIO_ENCODER_AMR_NB: + case AUDIO_ENCODER_AMR_WB: + case AUDIO_ENCODER_AAC: + break; + + default: + ALOGE("Unsupported audio encoder: %d", mAudioEncoder); + return UNKNOWN_ERROR; + } + + sp<MediaSource> audioEncoder = createAudioSource(); + if (audioEncoder == NULL) { + return UNKNOWN_ERROR; + } + + writer->addSource(audioEncoder); + 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 err = OK; + sp<MediaWriter> writer = new MPEG4Writer(outputFd); + + if (mVideoSource < VIDEO_SOURCE_LIST_END) { + + sp<MediaSource> mediaSource; + err = setupMediaSource(&mediaSource); + if (err != OK) { + return err; + } + + sp<MediaSource> encoder; + err = setupVideoEncoder(mediaSource, videoBitRate, &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; + } + + 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); + } + if (mMaxFileSizeBytes != 0) { + writer->setMaxFileSize(mMaxFileSizeBytes); + } + + mStartTimeOffsetMs = mEncoderProfiles->getStartTimeOffsetMs(mCameraId); + if (mStartTimeOffsetMs > 0) { + reinterpret_cast<MPEG4Writer *>(writer.get())-> + setStartTimeOffsetMs(mStartTimeOffsetMs); + } + + writer->setListener(mListener); + *mediaWriter = writer; + return OK; +} + +void StagefrightRecorder::setupMPEG4MetaData(int64_t startTimeUs, int32_t totalBitRate, + sp<MetaData> *meta) { + (*meta)->setInt64(kKeyTime, startTimeUs); + (*meta)->setInt32(kKeyFileType, mOutputFormat); + (*meta)->setInt32(kKeyBitRate, totalBitRate); + (*meta)->setInt32(kKey64BitFileOffset, mUse64BitFileOffset); + 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; + } + + return OK; +} + +status_t StagefrightRecorder::pause() { + ALOGV("pause"); + if (mWriter == NULL) { + return UNKNOWN_ERROR; + } + mWriter->pause(); + + if (mStarted) { + mStarted = false; + + uint32_t params = 0; + if (mAudioSource != AUDIO_SOURCE_CNT) { + params |= IMediaPlayerService::kBatteryDataTrackAudio; + } + if (mVideoSource != VIDEO_SOURCE_LIST_END) { + params |= IMediaPlayerService::kBatteryDataTrackVideo; + } + + addBatteryData(params); + } + + + return OK; +} + +status_t StagefrightRecorder::stop() { + ALOGV("stop"); + status_t err = OK; + + if (mCaptureTimeLapse && mCameraSourceTimeLapse != NULL) { + mCameraSourceTimeLapse->startQuickReadReturns(); + mCameraSourceTimeLapse = NULL; + } + + if (mWriter != NULL) { + err = mWriter->stop(); + mWriter.clear(); + } + + if (mOutputFd >= 0) { + ::close(mOutputFd); + mOutputFd = -1; + } + + if (mStarted) { + mStarted = false; + + uint32_t params = 0; + if (mAudioSource != AUDIO_SOURCE_CNT) { + params |= IMediaPlayerService::kBatteryDataTrackAudio; + } + if (mVideoSource != VIDEO_SOURCE_LIST_END) { + params |= IMediaPlayerService::kBatteryDataTrackVideo; + } + + addBatteryData(params); + } + + + return err; +} + +status_t StagefrightRecorder::close() { + ALOGV("close"); + stop(); + + return OK; +} + +status_t StagefrightRecorder::reset() { + ALOGV("reset"); + stop(); + + // No audio or video source by default + mAudioSource = AUDIO_SOURCE_CNT; + mVideoSource = VIDEO_SOURCE_LIST_END; + + // Default parameters + mOutputFormat = OUTPUT_FORMAT_THREE_GPP; + mAudioEncoder = AUDIO_ENCODER_AMR_NB; + mVideoEncoder = VIDEO_ENCODER_H263; + mVideoWidth = 176; + mVideoHeight = 144; + mFrameRate = -1; + mVideoBitRate = 192000; + mSampleRate = 8000; + mAudioChannels = 1; + mAudioBitRate = 12200; + mInterleaveDurationUs = 0; + mIFramesIntervalSec = 1; + mAudioSourceNode = 0; + mUse64BitFileOffset = false; + mMovieTimeScale = -1; + mAudioTimeScale = -1; + mVideoTimeScale = -1; + mCameraId = 0; + mStartTimeOffsetMs = -1; + mVideoEncoderProfile = -1; + mVideoEncoderLevel = -1; + mMaxFileDurationUs = 0; + mMaxFileSizeBytes = 0; + mTrackEveryTimeDurationUs = 0; + mCaptureTimeLapse = false; + mTimeBetweenTimeLapseFrameCaptureUs = -1; + mCameraSourceTimeLapse = NULL; + mIsMetaDataStoredInVideoBuffers = false; + mEncoderProfiles = MediaProfiles::getInstance(); + mRotationDegrees = 0; + mLatitudex10000 = -3600000; + mLongitudex10000 = -3600000; + + mOutputFd = -1; + + return OK; +} + +status_t StagefrightRecorder::getMaxAmplitude(int *max) { + ALOGV("getMaxAmplitude"); + + if (max == NULL) { + ALOGE("Null pointer argument"); + return BAD_VALUE; + } + + if (mAudioSourceNode != 0) { + *max = mAudioSourceNode->getMaxAmplitude(); + } else { + *max = 0; + } + + return OK; +} + +status_t StagefrightRecorder::dump( + int fd, const Vector<String16>& args) const { + ALOGV("dump"); + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + if (mWriter != 0) { + mWriter->dump(fd, args); + } else { + snprintf(buffer, SIZE, " No file writer\n"); + result.append(buffer); + } + snprintf(buffer, SIZE, " Recorder: %p\n", this); + snprintf(buffer, SIZE, " Output file (fd %d):\n", mOutputFd); + result.append(buffer); + snprintf(buffer, SIZE, " File format: %d\n", mOutputFormat); + result.append(buffer); + snprintf(buffer, SIZE, " Max file size (bytes): %lld\n", mMaxFileSizeBytes); + result.append(buffer); + snprintf(buffer, SIZE, " Max file duration (us): %lld\n", mMaxFileDurationUs); + result.append(buffer); + snprintf(buffer, SIZE, " File offset length (bits): %d\n", mUse64BitFileOffset? 64: 32); + result.append(buffer); + snprintf(buffer, SIZE, " Interleave duration (us): %d\n", mInterleaveDurationUs); + result.append(buffer); + snprintf(buffer, SIZE, " Progress notification: %lld us\n", mTrackEveryTimeDurationUs); + result.append(buffer); + snprintf(buffer, SIZE, " Audio\n"); + result.append(buffer); + snprintf(buffer, SIZE, " Source: %d\n", mAudioSource); + result.append(buffer); + snprintf(buffer, SIZE, " Encoder: %d\n", mAudioEncoder); + result.append(buffer); + snprintf(buffer, SIZE, " Bit rate (bps): %d\n", mAudioBitRate); + result.append(buffer); + snprintf(buffer, SIZE, " Sampling rate (hz): %d\n", mSampleRate); + result.append(buffer); + snprintf(buffer, SIZE, " Number of channels: %d\n", mAudioChannels); + result.append(buffer); + snprintf(buffer, SIZE, " Max amplitude: %d\n", mAudioSourceNode == 0? 0: mAudioSourceNode->getMaxAmplitude()); + result.append(buffer); + snprintf(buffer, SIZE, " Video\n"); + result.append(buffer); + snprintf(buffer, SIZE, " Source: %d\n", mVideoSource); + result.append(buffer); + snprintf(buffer, SIZE, " Camera Id: %d\n", mCameraId); + result.append(buffer); + snprintf(buffer, SIZE, " Start time offset (ms): %d\n", mStartTimeOffsetMs); + result.append(buffer); + snprintf(buffer, SIZE, " Encoder: %d\n", mVideoEncoder); + result.append(buffer); + snprintf(buffer, SIZE, " Encoder profile: %d\n", mVideoEncoderProfile); + result.append(buffer); + snprintf(buffer, SIZE, " Encoder level: %d\n", mVideoEncoderLevel); + result.append(buffer); + snprintf(buffer, SIZE, " I frames interval (s): %d\n", mIFramesIntervalSec); + result.append(buffer); + snprintf(buffer, SIZE, " Frame size (pixels): %dx%d\n", mVideoWidth, mVideoHeight); + result.append(buffer); + snprintf(buffer, SIZE, " Frame rate (fps): %d\n", mFrameRate); + result.append(buffer); + snprintf(buffer, SIZE, " Bit rate (bps): %d\n", mVideoBitRate); + result.append(buffer); + ::write(fd, result.string(), result.size()); + return OK; +} +} // namespace android diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h new file mode 100644 index 0000000..ec5ce7e --- /dev/null +++ b/media/libmediaplayerservice/StagefrightRecorder.h @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2009 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 STAGEFRIGHT_RECORDER_H_ + +#define STAGEFRIGHT_RECORDER_H_ + +#include <media/MediaRecorderBase.h> +#include <camera/CameraParameters.h> +#include <utils/String8.h> + +#include <system/audio.h> + +namespace android { + +class Camera; +class ICameraRecordingProxy; +class CameraSource; +class CameraSourceTimeLapse; +struct MediaSource; +struct MediaWriter; +class MetaData; +struct AudioSource; +class MediaProfiles; +class ISurfaceTexture; +class SurfaceMediaSource; + +struct StagefrightRecorder : public MediaRecorderBase { + StagefrightRecorder(); + virtual ~StagefrightRecorder(); + + virtual status_t init(); + virtual status_t setAudioSource(audio_source_t as); + virtual status_t setVideoSource(video_source vs); + virtual status_t setOutputFormat(output_format of); + virtual status_t setAudioEncoder(audio_encoder ae); + virtual status_t setVideoEncoder(video_encoder ve); + virtual status_t setVideoSize(int width, int height); + virtual status_t setVideoFrameRate(int frames_per_second); + virtual status_t setCamera(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy); + virtual status_t setPreviewSurface(const sp<Surface>& surface); + virtual status_t setOutputFile(const char *path); + virtual status_t setOutputFile(int fd, int64_t offset, int64_t length); + virtual status_t setParameters(const String8& params); + virtual status_t setListener(const sp<IMediaRecorderClient>& listener); + virtual status_t prepare(); + virtual status_t start(); + virtual status_t pause(); + virtual status_t stop(); + virtual status_t close(); + virtual status_t reset(); + virtual status_t getMaxAmplitude(int *max); + virtual status_t dump(int fd, const Vector<String16>& args) const; + // Querying a SurfaceMediaSourcer + virtual sp<ISurfaceTexture> querySurfaceMediaSource() const; + +private: + sp<ICamera> mCamera; + sp<ICameraRecordingProxy> mCameraProxy; + sp<Surface> mPreviewSurface; + sp<IMediaRecorderClient> mListener; + sp<MediaWriter> mWriter; + int mOutputFd; + sp<AudioSource> mAudioSourceNode; + + audio_source_t mAudioSource; + video_source mVideoSource; + output_format mOutputFormat; + audio_encoder mAudioEncoder; + video_encoder mVideoEncoder; + bool mUse64BitFileOffset; + int32_t mVideoWidth, mVideoHeight; + int32_t mFrameRate; + int32_t mVideoBitRate; + int32_t mAudioBitRate; + int32_t mAudioChannels; + int32_t mSampleRate; + int32_t mInterleaveDurationUs; + int32_t mIFramesIntervalSec; + int32_t mCameraId; + int32_t mVideoEncoderProfile; + int32_t mVideoEncoderLevel; + int32_t mMovieTimeScale; + int32_t mVideoTimeScale; + int32_t mAudioTimeScale; + int64_t mMaxFileSizeBytes; + int64_t mMaxFileDurationUs; + int64_t mTrackEveryTimeDurationUs; + int32_t mRotationDegrees; // Clockwise + int32_t mLatitudex10000; + int32_t mLongitudex10000; + int32_t mStartTimeOffsetMs; + + bool mCaptureTimeLapse; + int64_t mTimeBetweenTimeLapseFrameCaptureUs; + sp<CameraSourceTimeLapse> mCameraSourceTimeLapse; + + + String8 mParams; + + bool mIsMetaDataStoredInVideoBuffers; + MediaProfiles *mEncoderProfiles; + + bool mStarted; + // Needed when GLFrames are encoded. + // An <ISurfaceTexture> 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<MediaSource> createAudioSource(); + status_t checkVideoEncoderCapabilities(); + status_t checkAudioEncoderCapabilities(); + // Generic MediaSource set-up. Returns the appropriate + // source (CameraSource or SurfaceMediaSource) + // 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); + + // Encoding parameter handling utilities + status_t setParameter(const String8 &key, const String8 &value); + status_t setParamAudioEncodingBitRate(int32_t bitRate); + status_t setParamAudioNumberOfChannels(int32_t channles); + status_t setParamAudioSamplingRate(int32_t sampleRate); + status_t setParamAudioTimeScale(int32_t timeScale); + status_t setParamTimeLapseEnable(int32_t timeLapseEnable); + status_t setParamTimeBetweenTimeLapseFrameCapture(int64_t timeUs); + status_t setParamVideoEncodingBitRate(int32_t bitRate); + status_t setParamVideoIFramesInterval(int32_t seconds); + status_t setParamVideoEncoderProfile(int32_t profile); + status_t setParamVideoEncoderLevel(int32_t level); + status_t setParamVideoCameraId(int32_t cameraId); + status_t setParamVideoTimeScale(int32_t timeScale); + status_t setParamVideoRotation(int32_t degrees); + status_t setParamTrackTimeStatus(int64_t timeDurationUs); + status_t setParamInterleaveDuration(int32_t durationUs); + status_t setParam64BitFileOffset(bool use64BitFileOffset); + status_t setParamMaxFileDurationUs(int64_t timeUs); + status_t setParamMaxFileSizeBytes(int64_t bytes); + status_t setParamMovieTimeScale(int32_t timeScale); + status_t setParamGeoDataLongitude(int64_t longitudex10000); + status_t setParamGeoDataLatitude(int64_t latitudex10000); + void clipVideoBitRate(); + void clipVideoFrameRate(); + void clipVideoFrameWidth(); + void clipVideoFrameHeight(); + void clipAudioBitRate(); + void clipAudioSampleRate(); + void clipNumberOfAudioChannels(); + void setDefaultProfileIfNecessary(); + + + StagefrightRecorder(const StagefrightRecorder &); + StagefrightRecorder &operator=(const StagefrightRecorder &); +}; + +} // namespace android + +#endif // STAGEFRIGHT_RECORDER_H_ diff --git a/media/libmediaplayerservice/TestPlayerStub.cpp b/media/libmediaplayerservice/TestPlayerStub.cpp new file mode 100644 index 0000000..5d9728a --- /dev/null +++ b/media/libmediaplayerservice/TestPlayerStub.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2009 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 "TestPlayerStub" +#include "utils/Log.h" + +#include "TestPlayerStub.h" + +#include <dlfcn.h> // for dlopen/dlclose +#include <stdlib.h> +#include <string.h> +#include <cutils/properties.h> +#include <utils/Errors.h> // for status_t + +#include "media/MediaPlayerInterface.h" + + +namespace { +using android::status_t; +using android::MediaPlayerBase; + +const char *kTestUrlScheme = "test:"; +const char *kUrlParam = "url="; + +const char *kBuildTypePropName = "ro.build.type"; +const char *kEngBuild = "eng"; +const char *kTestBuild = "test"; + +// @return true if the current build is 'eng' or 'test'. +bool isTestBuild() +{ + char prop[PROPERTY_VALUE_MAX] = { '\0', }; + + property_get(kBuildTypePropName, prop, '\0'); + return strcmp(prop, kEngBuild) == 0 || strcmp(prop, kTestBuild) == 0; +} + +// @return true if the url scheme is 'test:' +bool isTestUrl(const char *url) +{ + return url && strncmp(url, kTestUrlScheme, strlen(kTestUrlScheme)) == 0; +} + +} // anonymous namespace + +namespace android { + +TestPlayerStub::TestPlayerStub() + :mUrl(NULL), mFilename(NULL), mContentUrl(NULL), + mHandle(NULL), mNewPlayer(NULL), mDeletePlayer(NULL), + mPlayer(NULL) { } + +TestPlayerStub::~TestPlayerStub() +{ + resetInternal(); +} + +status_t TestPlayerStub::initCheck() +{ + return isTestBuild() ? OK : INVALID_OPERATION; +} + +// Parse mUrl to get: +// * The library to be dlopened. +// * The url to be passed to the real setDataSource impl. +// +// mUrl is expected to be in following format: +// +// test:<name of the .so>?url=<url for setDataSource> +// +// The value of the url parameter is treated as a string (no +// unescaping of illegal charaters). +status_t TestPlayerStub::parseUrl() +{ + if (strlen(mUrl) < strlen(kTestUrlScheme)) { + resetInternal(); + return BAD_VALUE; + } + + char *i = mUrl + strlen(kTestUrlScheme); + + mFilename = i; + + while (*i != '\0' && *i != '?') { + ++i; + } + + if (*i == '\0' || strncmp(i + 1, kUrlParam, strlen(kUrlParam)) != 0) { + resetInternal(); + return BAD_VALUE; + } + *i = '\0'; // replace '?' to nul-terminate mFilename + + mContentUrl = i + 1 + strlen(kUrlParam); + return OK; +} + +// Load the dynamic library. +// 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) { + if (!isTestUrl(url) || NULL != mHandle) { + return INVALID_OPERATION; + } + + mUrl = strdup(url); + + status_t status = parseUrl(); + + if (OK != status) { + resetInternal(); + return status; + } + + ::dlerror(); // Clears any pending error. + + // Load the test player from the url. dlopen will fail if the lib + // is not there. dls are under /system/lib + // None of the entry points should be NULL. + mHandle = ::dlopen(mFilename, RTLD_NOW | RTLD_GLOBAL); + if (!mHandle) { + ALOGE("dlopen failed: %s", ::dlerror()); + resetInternal(); + return UNKNOWN_ERROR; + } + + // Load the 2 entry points to create and delete instances. + const char *err; + mNewPlayer = reinterpret_cast<NEW_PLAYER>(dlsym(mHandle, + "newPlayer")); + err = ::dlerror(); + if (err || mNewPlayer == NULL) { + // if err is NULL the string <null> is inserted in the logs => + // mNewPlayer was NULL. + ALOGE("dlsym for newPlayer failed %s", err); + resetInternal(); + return UNKNOWN_ERROR; + } + + mDeletePlayer = reinterpret_cast<DELETE_PLAYER>(dlsym(mHandle, + "deletePlayer")); + err = ::dlerror(); + if (err || mDeletePlayer == NULL) { + ALOGE("dlsym for deletePlayer failed %s", err); + resetInternal(); + return UNKNOWN_ERROR; + } + + mPlayer = (*mNewPlayer)(); + return mPlayer->setDataSource(mContentUrl, headers); +} + +// Internal cleanup. +status_t TestPlayerStub::resetInternal() +{ + if(mUrl) { + free(mUrl); + mUrl = NULL; + } + mFilename = NULL; + mContentUrl = NULL; + + if (mPlayer) { + ALOG_ASSERT(mDeletePlayer != NULL, "mDeletePlayer is null"); + (*mDeletePlayer)(mPlayer); + mPlayer = NULL; + } + + if (mHandle) { + ::dlclose(mHandle); + mHandle = NULL; + } + return OK; +} + +/* static */ bool TestPlayerStub::canBeUsed(const char *url) +{ + return isTestBuild() && isTestUrl(url); +} + +} // namespace android diff --git a/media/libmediaplayerservice/TestPlayerStub.h b/media/libmediaplayerservice/TestPlayerStub.h new file mode 100644 index 0000000..91ffa7d --- /dev/null +++ b/media/libmediaplayerservice/TestPlayerStub.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2009 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 ANDROID_FRAMEWORKS_BASE_MEDIA_LIBMEDIAPLAYERSERVICE_TESTPLAYERSTUB_H__ +#define ANDROID_FRAMEWORKS_BASE_MEDIA_LIBMEDIAPLAYERSERVICE_TESTPLAYERSTUB_H__ + +#include <media/MediaPlayerInterface.h> +#include <utils/Errors.h> + +namespace android { +class MediaPlayerBase; // in media/MediaPlayerInterface.h + +// Wrapper around a test media player that gets dynamically loaded. +// +// The URL passed to setDataSource has this format: +// +// test:<name of the .so>?url=<url for the real setDataSource impl.> +// +// e.g: +// test:invoke_test_media_player.so?url=http://youtube.com/ +// test:invoke_test_media_player.so?url=speedtest +// +// TestPlayerStub::setDataSource loads the library in the test url. 2 +// entry points with C linkage are expected. One to create the test +// player and one to destroy it. +// +// extern "C" android::MediaPlayerBase* newPlayer(); +// extern "C" android::status_t deletePlayer(android::MediaPlayerBase *p); +// +// Once the test player has been loaded, its setDataSource +// implementation is called with the value of the 'url' parameter. +// +// typical usage in a java test: +// ============================ +// +// MediaPlayer p = new MediaPlayer(); +// p.setDataSource("test:invoke_mock_media_player.so?url=http://youtube.com"); +// p.prepare(); +// ... +// p.release(); + +class TestPlayerStub : public MediaPlayerInterface { + public: + typedef MediaPlayerBase* (*NEW_PLAYER)(); + typedef status_t (*DELETE_PLAYER)(MediaPlayerBase *); + + TestPlayerStub(); + virtual ~TestPlayerStub(); + + // Called right after the constructor. Check if the current build + // allows test players. + virtual status_t initCheck(); + + // @param url Should be a test url. See class comment. + virtual status_t setDataSource( + 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) { + return INVALID_OPERATION; + } + + + // All the methods below wrap the mPlayer instance. + virtual status_t setVideoSurfaceTexture( + const android::sp<android::ISurfaceTexture>& st) { + return mPlayer->setVideoSurfaceTexture(st); + } + virtual status_t prepare() {return mPlayer->prepare();} + virtual status_t prepareAsync() {return mPlayer->prepareAsync();} + virtual status_t start() {return mPlayer->start();} + virtual status_t stop() {return mPlayer->stop();} + virtual status_t pause() {return mPlayer->pause();} + virtual bool isPlaying() {return mPlayer->isPlaying();} + virtual status_t seekTo(int msec) {return mPlayer->seekTo(msec);} + virtual status_t getCurrentPosition(int *p) { + return mPlayer->getCurrentPosition(p); + } + virtual status_t getDuration(int *d) {return mPlayer->getDuration(d);} + virtual status_t reset() {return mPlayer->reset();} + virtual status_t setLooping(int b) {return mPlayer->setLooping(b);} + virtual player_type playerType() {return mPlayer->playerType();} + virtual status_t invoke(const android::Parcel& in, android::Parcel *out) { + return mPlayer->invoke(in, out); + } + virtual status_t setParameter(int key, const Parcel &request) { + return mPlayer->setParameter(key, request); + } + virtual status_t getParameter(int key, Parcel *reply) { + return mPlayer->getParameter(key, reply); + } + + + // @return true if the current build is 'eng' or 'test' and the + // url's scheme is 'test:' + static bool canBeUsed(const char *url); + + private: + // Release the player, dlclose the library. + status_t resetInternal(); + status_t parseUrl(); + + char *mUrl; // test:foo.so?url=http://bar + char *mFilename; // foo.so + char *mContentUrl; // http://bar + void *mHandle; // returned by dlopen + NEW_PLAYER mNewPlayer; + DELETE_PLAYER mDeletePlayer; + MediaPlayerBase *mPlayer; // wrapped player +}; + +} // namespace android + +#endif diff --git a/media/libmediaplayerservice/nuplayer/Android.mk b/media/libmediaplayerservice/nuplayer/Android.mk new file mode 100644 index 0000000..73336ef --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/Android.mk @@ -0,0 +1,27 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + GenericSource.cpp \ + HTTPLiveSource.cpp \ + NuPlayer.cpp \ + NuPlayerDecoder.cpp \ + NuPlayerDriver.cpp \ + NuPlayerRenderer.cpp \ + NuPlayerStreamListener.cpp \ + RTSPSource.cpp \ + StreamingSource.cpp \ + +LOCAL_C_INCLUDES := \ + $(TOP)/frameworks/base/media/libstagefright/httplive \ + $(TOP)/frameworks/base/media/libstagefright/include \ + $(TOP)/frameworks/base/media/libstagefright/mpeg2ts \ + $(TOP)/frameworks/base/media/libstagefright/rtsp \ + $(TOP)/frameworks/native/include/media/openmax + +LOCAL_MODULE:= libstagefright_nuplayer + +LOCAL_MODULE_TAGS := eng + +include $(BUILD_STATIC_LIBRARY) + diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp new file mode 100644 index 0000000..99569c9 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp @@ -0,0 +1,265 @@ +/* + * 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 "GenericSource.h" + +#include "AnotherPacketSource.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/DataSource.h> +#include <media/stagefright/FileSource.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaExtractor.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> + +namespace android { + +NuPlayer::GenericSource::GenericSource( + const char *url, + const KeyedVector<String8, String8> *headers, + bool uidValid, + uid_t uid) + : mDurationUs(0ll), + mAudioIsVorbis(false) { + DataSource::RegisterDefaultSniffers(); + + sp<DataSource> dataSource = + DataSource::CreateFromURI(url, headers); + CHECK(dataSource != NULL); + + initFromDataSource(dataSource); +} + +NuPlayer::GenericSource::GenericSource( + int fd, int64_t offset, int64_t length) + : mDurationUs(0ll), + mAudioIsVorbis(false) { + DataSource::RegisterDefaultSniffers(); + + sp<DataSource> dataSource = new FileSource(dup(fd), offset, length); + + initFromDataSource(dataSource); +} + +void NuPlayer::GenericSource::initFromDataSource( + const sp<DataSource> &dataSource) { + sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource); + + CHECK(extractor != NULL); + + 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; + + if (!strncasecmp(mime, "audio/", 6)) { + if (mAudioTrack.mSource == NULL) { + mAudioTrack.mSource = track = extractor->getTrack(i); + + if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) { + mAudioIsVorbis = true; + } else { + mAudioIsVorbis = false; + } + } + } else if (!strncasecmp(mime, "video/", 6)) { + if (mVideoTrack.mSource == NULL) { + mVideoTrack.mSource = track = extractor->getTrack(i); + } + } + + if (track != NULL) { + int64_t durationUs; + if (meta->findInt64(kKeyDuration, &durationUs)) { + if (durationUs > mDurationUs) { + mDurationUs = durationUs; + } + } + } + } +} + +NuPlayer::GenericSource::~GenericSource() { +} + +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 */); + } + + if (mVideoTrack.mSource != NULL) { + CHECK_EQ(mVideoTrack.mSource->start(), (status_t)OK); + + mVideoTrack.mPackets = + new AnotherPacketSource(mVideoTrack.mSource->getFormat()); + + readBuffer(false /* audio */); + } +} + +status_t NuPlayer::GenericSource::feedMoreTSData() { + return OK; +} + +sp<MetaData> NuPlayer::GenericSource::getFormat(bool audio) { + sp<MediaSource> source = audio ? mAudioTrack.mSource : mVideoTrack.mSource; + + if (source == NULL) { + return NULL; + } + + return source->getFormat(); +} + +status_t NuPlayer::GenericSource::dequeueAccessUnit( + bool audio, sp<ABuffer> *accessUnit) { + Track *track = audio ? &mAudioTrack : &mVideoTrack; + + if (track->mSource == NULL) { + return -EWOULDBLOCK; + } + + status_t finalResult; + if (!track->mPackets->hasBufferAvailable(&finalResult)) { + return finalResult == OK ? -EWOULDBLOCK : finalResult; + } + + status_t result = track->mPackets->dequeueAccessUnit(accessUnit); + + readBuffer(audio, -1ll); + + return result; +} + +status_t NuPlayer::GenericSource::getDuration(int64_t *durationUs) { + *durationUs = mDurationUs; + return OK; +} + +status_t NuPlayer::GenericSource::seekTo(int64_t seekTimeUs) { + if (mVideoTrack.mSource != NULL) { + int64_t actualTimeUs; + readBuffer(false /* audio */, seekTimeUs, &actualTimeUs); + + seekTimeUs = actualTimeUs; + } + + if (mAudioTrack.mSource != NULL) { + readBuffer(true /* audio */, seekTimeUs); + } + + return OK; +} + +void NuPlayer::GenericSource::readBuffer( + bool audio, int64_t seekTimeUs, int64_t *actualTimeUs) { + Track *track = audio ? &mAudioTrack : &mVideoTrack; + CHECK(track->mSource != NULL); + + if (actualTimeUs) { + *actualTimeUs = seekTimeUs; + } + + MediaSource::ReadOptions options; + + bool seeking = false; + + if (seekTimeUs >= 0) { + options.setSeekTo(seekTimeUs); + seeking = true; + } + + for (;;) { + 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; + } + + mbuf->release(); + mbuf = NULL; + + if (seeking) { + track->mPackets->queueDiscontinuity( + ATSParser::DISCONTINUITY_SEEK, NULL); + } + + track->mPackets->queueAccessUnit(buffer); + break; + } else if (err == INFO_FORMAT_CHANGED) { +#if 0 + track->mPackets->queueDiscontinuity( + ATSParser::DISCONTINUITY_FORMATCHANGE, NULL); +#endif + } else { + track->mPackets->signalEOS(err); + break; + } + } +} + +bool NuPlayer::GenericSource::isSeekable() { + return true; +} + +} // namespace android diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.h b/media/libmediaplayerservice/nuplayer/GenericSource.h new file mode 100644 index 0000000..aaa5876 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/GenericSource.h @@ -0,0 +1,79 @@ +/* + * 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 GENERIC_SOURCE_H_ + +#define GENERIC_SOURCE_H_ + +#include "NuPlayer.h" +#include "NuPlayerSource.h" + +#include "ATSParser.h" + +namespace android { + +struct AnotherPacketSource; +struct ARTSPController; +struct DataSource; +struct MediaSource; + +struct NuPlayer::GenericSource : public NuPlayer::Source { + GenericSource( + const char *url, + const KeyedVector<String8, String8> *headers, + bool uidValid = false, + uid_t uid = 0); + + GenericSource(int fd, int64_t offset, int64_t length); + + virtual void start(); + + virtual status_t feedMoreTSData(); + + virtual sp<MetaData> getFormat(bool audio); + virtual status_t dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit); + + virtual status_t getDuration(int64_t *durationUs); + virtual status_t seekTo(int64_t seekTimeUs); + virtual bool isSeekable(); + +protected: + virtual ~GenericSource(); + +private: + struct Track { + sp<MediaSource> mSource; + sp<AnotherPacketSource> mPackets; + }; + + Track mAudioTrack; + Track mVideoTrack; + + int64_t mDurationUs; + bool mAudioIsVorbis; + + void initFromDataSource(const sp<DataSource> &dataSource); + + void readBuffer( + bool audio, + int64_t seekTimeUs = -1ll, int64_t *actualTimeUs = NULL); + + DISALLOW_EVIL_CONSTRUCTORS(GenericSource); +}; + +} // namespace android + +#endif // GENERIC_SOURCE_H_ diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp new file mode 100644 index 0000000..22b8847 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2010 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 "HTTPLiveSource" +#include <utils/Log.h> + +#include "HTTPLiveSource.h" + +#include "ATSParser.h" +#include "AnotherPacketSource.h" +#include "LiveDataSource.h" +#include "LiveSession.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> + +namespace android { + +NuPlayer::HTTPLiveSource::HTTPLiveSource( + const char *url, + const KeyedVector<String8, String8> *headers, + bool uidValid, uid_t uid) + : mURL(url), + mUIDValid(uidValid), + mUID(uid), + mFlags(0), + mFinalResult(OK), + mOffset(0) { + if (headers) { + mExtraHeaders = *headers; + + ssize_t index = + mExtraHeaders.indexOfKey(String8("x-hide-urls-from-log")); + + if (index >= 0) { + mFlags |= kFlagIncognito; + + mExtraHeaders.removeItemsAt(index); + } + } +} + +NuPlayer::HTTPLiveSource::~HTTPLiveSource() { + if (mLiveSession != NULL) { + mLiveSession->disconnect(); + mLiveLooper->stop(); + } +} + +void NuPlayer::HTTPLiveSource::start() { + mLiveLooper = new ALooper; + mLiveLooper->setName("http live"); + mLiveLooper->start(); + + mLiveSession = new LiveSession( + (mFlags & kFlagIncognito) ? LiveSession::kFlagIncognito : 0, + mUIDValid, mUID); + + mLiveLooper->registerHandler(mLiveSession); + + mLiveSession->connect( + mURL.c_str(), mExtraHeaders.isEmpty() ? NULL : &mExtraHeaders); + + mTSParser = new ATSParser; +} + +sp<MetaData> NuPlayer::HTTPLiveSource::getFormat(bool audio) { + ATSParser::SourceType type = + audio ? ATSParser::AUDIO : ATSParser::VIDEO; + + sp<AnotherPacketSource> source = + static_cast<AnotherPacketSource *>(mTSParser->getSource(type).get()); + + if (source == NULL) { + return NULL; + } + + return source->getFormat(); +} + +status_t NuPlayer::HTTPLiveSource::feedMoreTSData() { + if (mFinalResult != OK) { + return mFinalResult; + } + + sp<LiveDataSource> source = + static_cast<LiveDataSource *>(mLiveSession->getDataSource().get()); + + for (int32_t i = 0; i < 50; ++i) { + char buffer[188]; + ssize_t n = source->readAtNonBlocking(mOffset, buffer, sizeof(buffer)); + + if (n == -EWOULDBLOCK) { + break; + } else if (n < 0) { + if (n != ERROR_END_OF_STREAM) { + ALOGI("input data EOS reached, error %ld", n); + } else { + ALOGI("input data EOS reached."); + } + mTSParser->signalEOS(n); + mFinalResult = n; + break; + } else { + if (buffer[0] == 0x00) { + // XXX legacy + sp<AMessage> extra; + mTSParser->signalDiscontinuity( + buffer[1] == 0x00 + ? ATSParser::DISCONTINUITY_SEEK + : ATSParser::DISCONTINUITY_FORMATCHANGE, + extra); + } else { + status_t err = mTSParser->feedTSPacket(buffer, sizeof(buffer)); + + if (err != OK) { + ALOGE("TS Parser returned error %d", err); + mTSParser->signalEOS(err); + mFinalResult = err; + break; + } + } + + mOffset += n; + } + } + + return OK; +} + +status_t NuPlayer::HTTPLiveSource::dequeueAccessUnit( + bool audio, sp<ABuffer> *accessUnit) { + ATSParser::SourceType type = + audio ? ATSParser::AUDIO : ATSParser::VIDEO; + + sp<AnotherPacketSource> source = + static_cast<AnotherPacketSource *>(mTSParser->getSource(type).get()); + + if (source == NULL) { + return -EWOULDBLOCK; + } + + status_t finalResult; + if (!source->hasBufferAvailable(&finalResult)) { + return finalResult == OK ? -EWOULDBLOCK : finalResult; + } + + return source->dequeueAccessUnit(accessUnit); +} + +status_t NuPlayer::HTTPLiveSource::getDuration(int64_t *durationUs) { + return mLiveSession->getDuration(durationUs); +} + +status_t NuPlayer::HTTPLiveSource::seekTo(int64_t seekTimeUs) { + // We need to make sure we're not seeking until we have seen the very first + // PTS timestamp in the whole stream (from the beginning of the stream). + while (!mTSParser->PTSTimeDeltaEstablished() && feedMoreTSData() == OK) { + usleep(100000); + } + + mLiveSession->seekTo(seekTimeUs); + + return OK; +} + +bool NuPlayer::HTTPLiveSource::isSeekable() { + return mLiveSession->isSeekable(); +} + +} // namespace android + diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h new file mode 100644 index 0000000..f22af5b --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HTTP_LIVE_SOURCE_H_ + +#define HTTP_LIVE_SOURCE_H_ + +#include "NuPlayer.h" +#include "NuPlayerSource.h" + +namespace android { + +struct ATSParser; +struct LiveSession; + +struct NuPlayer::HTTPLiveSource : public NuPlayer::Source { + HTTPLiveSource( + const char *url, + const KeyedVector<String8, String8> *headers, + bool uidValid = false, + uid_t uid = 0); + + virtual void start(); + + virtual status_t feedMoreTSData(); + + virtual sp<MetaData> getFormat(bool audio); + virtual status_t dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit); + + virtual status_t getDuration(int64_t *durationUs); + virtual status_t seekTo(int64_t seekTimeUs); + virtual bool isSeekable(); + +protected: + virtual ~HTTPLiveSource(); + +private: + enum Flags { + // Don't log any URLs. + kFlagIncognito = 1, + }; + + AString mURL; + KeyedVector<String8, String8> mExtraHeaders; + bool mUIDValid; + uid_t mUID; + uint32_t mFlags; + status_t mFinalResult; + off64_t mOffset; + sp<ALooper> mLiveLooper; + sp<LiveSession> mLiveSession; + sp<ATSParser> mTSParser; + + DISALLOW_EVIL_CONSTRUCTORS(HTTPLiveSource); +}; + +} // namespace android + +#endif // HTTP_LIVE_SOURCE_H_ diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp new file mode 100644 index 0000000..544d501 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp @@ -0,0 +1,905 @@ +/* + * Copyright (C) 2010 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 "NuPlayer" +#include <utils/Log.h> + +#include "NuPlayer.h" + +#include "HTTPLiveSource.h" +#include "NuPlayerDecoder.h" +#include "NuPlayerDriver.h" +#include "NuPlayerRenderer.h" +#include "NuPlayerSource.h" +#include "RTSPSource.h" +#include "StreamingSource.h" +#include "GenericSource.h" + +#include "ATSParser.h" + +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/ACodec.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <gui/ISurfaceTexture.h> + +#include "avc_utils.h" + +namespace android { + +//////////////////////////////////////////////////////////////////////////////// + +NuPlayer::NuPlayer() + : mUIDValid(false), + mVideoIsAVC(false), + mAudioEOS(false), + mVideoEOS(false), + mScanSourcesPending(false), + mScanSourcesGeneration(0), + mTimeDiscontinuityPending(false), + mFlushingAudio(NONE), + mFlushingVideo(NONE), + mResetInProgress(false), + mResetPostponed(false), + mSkipRenderingAudioUntilMediaTimeUs(-1ll), + mSkipRenderingVideoUntilMediaTimeUs(-1ll), + mVideoLateByUs(0ll), + mNumFramesTotal(0ll), + mNumFramesDropped(0ll) { +} + +NuPlayer::~NuPlayer() { +} + +void NuPlayer::setUID(uid_t uid) { + mUIDValid = true; + mUID = uid; +} + +void NuPlayer::setDriver(const wp<NuPlayerDriver> &driver) { + mDriver = driver; +} + +void NuPlayer::setDataSource(const sp<IStreamSource> &source) { + sp<AMessage> msg = new AMessage(kWhatSetDataSource, id()); + + msg->setObject("source", new StreamingSource(source)); + msg->post(); +} + +static bool IsHTTPLiveURL(const char *url) { + if (!strncasecmp("http://", url, 7) + || !strncasecmp("https://", url, 8)) { + size_t len = strlen(url); + if (len >= 5 && !strcasecmp(".m3u8", &url[len - 5])) { + return true; + } + + if (strstr(url,"m3u8")) { + return true; + } + } + + return false; +} + +void NuPlayer::setDataSource( + const char *url, const KeyedVector<String8, String8> *headers) { + sp<AMessage> msg = new AMessage(kWhatSetDataSource, id()); + + sp<Source> source; + if (IsHTTPLiveURL(url)) { + source = new HTTPLiveSource(url, headers, mUIDValid, mUID); + } else if (!strncasecmp(url, "rtsp://", 7)) { + source = new RTSPSource(url, headers, mUIDValid, mUID); + } else { + source = new GenericSource(url, headers, mUIDValid, mUID); + } + + msg->setObject("source", source); + msg->post(); +} + +void NuPlayer::setDataSource(int fd, int64_t offset, int64_t length) { + sp<AMessage> msg = new AMessage(kWhatSetDataSource, id()); + + sp<Source> source = new GenericSource(fd, offset, length); + msg->setObject("source", source); + msg->post(); +} + +void NuPlayer::setVideoSurfaceTexture(const sp<ISurfaceTexture> &surfaceTexture) { + sp<AMessage> msg = new AMessage(kWhatSetVideoNativeWindow, id()); + sp<SurfaceTextureClient> surfaceTextureClient(surfaceTexture != NULL ? + new SurfaceTextureClient(surfaceTexture) : NULL); + msg->setObject("native-window", new NativeWindowWrapper(surfaceTextureClient)); + msg->post(); +} + +void NuPlayer::setAudioSink(const sp<MediaPlayerBase::AudioSink> &sink) { + sp<AMessage> msg = new AMessage(kWhatSetAudioSink, id()); + msg->setObject("sink", sink); + msg->post(); +} + +void NuPlayer::start() { + (new AMessage(kWhatStart, id()))->post(); +} + +void NuPlayer::pause() { + (new AMessage(kWhatPause, id()))->post(); +} + +void NuPlayer::resume() { + (new AMessage(kWhatResume, id()))->post(); +} + +void NuPlayer::resetAsync() { + (new AMessage(kWhatReset, id()))->post(); +} + +void NuPlayer::seekToAsync(int64_t seekTimeUs) { + sp<AMessage> msg = new AMessage(kWhatSeek, id()); + msg->setInt64("seekTimeUs", seekTimeUs); + msg->post(); +} + +// static +bool NuPlayer::IsFlushingState(FlushStatus state, bool *needShutdown) { + switch (state) { + case FLUSHING_DECODER: + if (needShutdown != NULL) { + *needShutdown = false; + } + return true; + + case FLUSHING_DECODER_SHUTDOWN: + if (needShutdown != NULL) { + *needShutdown = true; + } + return true; + + default: + return false; + } +} + +void NuPlayer::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatSetDataSource: + { + ALOGV("kWhatSetDataSource"); + + CHECK(mSource == NULL); + + sp<RefBase> obj; + CHECK(msg->findObject("source", &obj)); + + mSource = static_cast<Source *>(obj.get()); + break; + } + + case kWhatSetVideoNativeWindow: + { + ALOGV("kWhatSetVideoNativeWindow"); + + sp<RefBase> obj; + CHECK(msg->findObject("native-window", &obj)); + + mNativeWindow = static_cast<NativeWindowWrapper *>(obj.get()); + break; + } + + case kWhatSetAudioSink: + { + ALOGV("kWhatSetAudioSink"); + + sp<RefBase> obj; + CHECK(msg->findObject("sink", &obj)); + + mAudioSink = static_cast<MediaPlayerBase::AudioSink *>(obj.get()); + break; + } + + case kWhatStart: + { + ALOGV("kWhatStart"); + + mVideoIsAVC = false; + mAudioEOS = false; + mVideoEOS = false; + mSkipRenderingAudioUntilMediaTimeUs = -1; + mSkipRenderingVideoUntilMediaTimeUs = -1; + mVideoLateByUs = 0; + mNumFramesTotal = 0; + mNumFramesDropped = 0; + + mSource->start(); + + mRenderer = new Renderer( + mAudioSink, + new AMessage(kWhatRendererNotify, id())); + + looper()->registerHandler(mRenderer); + + postScanSources(); + break; + } + + case kWhatScanSources: + { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + if (generation != mScanSourcesGeneration) { + // Drop obsolete msg. + break; + } + + mScanSourcesPending = false; + + ALOGV("scanning sources haveAudio=%d, haveVideo=%d", + mAudioDecoder != NULL, mVideoDecoder != NULL); + + instantiateDecoder(false, &mVideoDecoder); + + if (mAudioSink != NULL) { + instantiateDecoder(true, &mAudioDecoder); + } + + status_t err; + if ((err = mSource->feedMoreTSData()) != OK) { + if (mAudioDecoder == NULL && mVideoDecoder == NULL) { + // We're not currently decoding anything (no audio or + // video tracks found) and we just ran out of input data. + + if (err == ERROR_END_OF_STREAM) { + notifyListener(MEDIA_PLAYBACK_COMPLETE, 0, 0); + } else { + notifyListener(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err); + } + } + break; + } + + if (mAudioDecoder == NULL || mVideoDecoder == NULL) { + msg->post(100000ll); + mScanSourcesPending = true; + } + break; + } + + case kWhatVideoNotify: + case kWhatAudioNotify: + { + bool audio = msg->what() == kWhatAudioNotify; + + sp<AMessage> codecRequest; + CHECK(msg->findMessage("codec-request", &codecRequest)); + + int32_t what; + CHECK(codecRequest->findInt32("what", &what)); + + if (what == ACodec::kWhatFillThisBuffer) { + status_t err = feedDecoderInputData( + audio, codecRequest); + + if (err == -EWOULDBLOCK) { + if (mSource->feedMoreTSData() == OK) { + msg->post(10000ll); + } + } + } else if (what == ACodec::kWhatEOS) { + int32_t err; + CHECK(codecRequest->findInt32("err", &err)); + + if (err == ERROR_END_OF_STREAM) { + ALOGV("got %s decoder EOS", audio ? "audio" : "video"); + } else { + ALOGV("got %s decoder EOS w/ error %d", + audio ? "audio" : "video", + err); + } + + mRenderer->queueEOS(audio, err); + } else if (what == ACodec::kWhatFlushCompleted) { + bool needShutdown; + + if (audio) { + CHECK(IsFlushingState(mFlushingAudio, &needShutdown)); + mFlushingAudio = FLUSHED; + } else { + CHECK(IsFlushingState(mFlushingVideo, &needShutdown)); + mFlushingVideo = FLUSHED; + + mVideoLateByUs = 0; + } + + ALOGV("decoder %s flush completed", audio ? "audio" : "video"); + + if (needShutdown) { + ALOGV("initiating %s decoder shutdown", + audio ? "audio" : "video"); + + (audio ? mAudioDecoder : mVideoDecoder)->initiateShutdown(); + + if (audio) { + mFlushingAudio = SHUTTING_DOWN_DECODER; + } else { + mFlushingVideo = SHUTTING_DOWN_DECODER; + } + } + + 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(); + CHECK_EQ(mAudioSink->open( + sampleRate, + numChannels, + CHANNEL_MASK_USE_CHANNEL_ORDER, + AUDIO_FORMAT_PCM_16_BIT, + 8 /* bufferCount */), + (status_t)OK); + mAudioSink->start(); + + mRenderer->signalAudioSinkChanged(); + } else { + // video + + 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)); + + ALOGV("Video output format changed to %d x %d " + "(crop: %d x %d @ (%d, %d))", + width, height, + (cropRight - cropLeft + 1), + (cropBottom - cropTop + 1), + cropLeft, cropTop); + + notifyListener( + MEDIA_SET_VIDEO_SIZE, + cropRight - cropLeft + 1, + cropBottom - cropTop + 1); + } + } else if (what == ACodec::kWhatShutdownCompleted) { + ALOGV("%s shutdown completed", audio ? "audio" : "video"); + if (audio) { + mAudioDecoder.clear(); + + CHECK_EQ((int)mFlushingAudio, (int)SHUTTING_DOWN_DECODER); + mFlushingAudio = SHUT_DOWN; + } else { + mVideoDecoder.clear(); + + CHECK_EQ((int)mFlushingVideo, (int)SHUTTING_DOWN_DECODER); + mFlushingVideo = SHUT_DOWN; + } + + finishFlushIfPossible(); + } else if (what == ACodec::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 { + ALOGV("Unhandled codec notification %d.", what); + } + + break; + } + + case kWhatRendererNotify: + { + int32_t what; + CHECK(msg->findInt32("what", &what)); + + if (what == Renderer::kWhatEOS) { + int32_t audio; + CHECK(msg->findInt32("audio", &audio)); + + int32_t finalResult; + CHECK(msg->findInt32("finalResult", &finalResult)); + + if (audio) { + mAudioEOS = true; + } else { + mVideoEOS = true; + } + + if (finalResult == ERROR_END_OF_STREAM) { + ALOGV("reached %s EOS", audio ? "audio" : "video"); + } else { + ALOGE("%s track encountered an error (%d)", + audio ? "audio" : "video", finalResult); + + notifyListener( + MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, finalResult); + } + + if ((mAudioEOS || mAudioDecoder == NULL) + && (mVideoEOS || mVideoDecoder == NULL)) { + notifyListener(MEDIA_PLAYBACK_COMPLETE, 0, 0); + } + } else if (what == Renderer::kWhatPosition) { + int64_t positionUs; + CHECK(msg->findInt64("positionUs", &positionUs)); + + CHECK(msg->findInt64("videoLateByUs", &mVideoLateByUs)); + + if (mDriver != NULL) { + sp<NuPlayerDriver> driver = mDriver.promote(); + if (driver != NULL) { + driver->notifyPosition(positionUs); + + driver->notifyFrameStats( + mNumFramesTotal, mNumFramesDropped); + } + } + } else if (what == Renderer::kWhatFlushComplete) { + CHECK_EQ(what, (int32_t)Renderer::kWhatFlushComplete); + + int32_t audio; + CHECK(msg->findInt32("audio", &audio)); + + ALOGV("renderer %s flush completed.", audio ? "audio" : "video"); + } + break; + } + + case kWhatMoreDataQueued: + { + break; + } + + case kWhatReset: + { + ALOGV("kWhatReset"); + + 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. + + ALOGV("postponing reset mFlushingAudio=%d, mFlushingVideo=%d", + mFlushingAudio, mFlushingVideo); + + mResetPostponed = true; + break; + } + + if (mAudioDecoder == NULL && mVideoDecoder == NULL) { + finishReset(); + break; + } + + mTimeDiscontinuityPending = true; + + if (mAudioDecoder != NULL) { + flushDecoder(true /* audio */, true /* needShutdown */); + } + + if (mVideoDecoder != NULL) { + flushDecoder(false /* audio */, true /* needShutdown */); + } + + mResetInProgress = true; + break; + } + + case kWhatSeek: + { + int64_t seekTimeUs; + CHECK(msg->findInt64("seekTimeUs", &seekTimeUs)); + + ALOGV("kWhatSeek seekTimeUs=%lld us (%.2f secs)", + seekTimeUs, seekTimeUs / 1E6); + + mSource->seekTo(seekTimeUs); + + if (mDriver != NULL) { + sp<NuPlayerDriver> driver = mDriver.promote(); + if (driver != NULL) { + driver->notifySeekComplete(); + } + } + + break; + } + + case kWhatPause: + { + CHECK(mRenderer != NULL); + mRenderer->pause(); + break; + } + + case kWhatResume: + { + CHECK(mRenderer != NULL); + mRenderer->resume(); + break; + } + + default: + TRESPASS(); + break; + } +} + +void NuPlayer::finishFlushIfPossible() { + if (mFlushingAudio != FLUSHED && mFlushingAudio != SHUT_DOWN) { + return; + } + + if (mFlushingVideo != FLUSHED && mFlushingVideo != SHUT_DOWN) { + return; + } + + ALOGV("both audio and video are flushed now."); + + if (mTimeDiscontinuityPending) { + mRenderer->signalTimeDiscontinuity(); + mTimeDiscontinuityPending = false; + } + + if (mAudioDecoder != NULL) { + mAudioDecoder->signalResume(); + } + + if (mVideoDecoder != NULL) { + mVideoDecoder->signalResume(); + } + + mFlushingAudio = NONE; + mFlushingVideo = NONE; + + if (mResetInProgress) { + ALOGV("reset completed"); + + mResetInProgress = false; + finishReset(); + } else if (mResetPostponed) { + (new AMessage(kWhatReset, id()))->post(); + mResetPostponed = false; + } else if (mAudioDecoder == NULL || mVideoDecoder == NULL) { + postScanSources(); + } +} + +void NuPlayer::finishReset() { + CHECK(mAudioDecoder == NULL); + CHECK(mVideoDecoder == NULL); + + ++mScanSourcesGeneration; + mScanSourcesPending = false; + + mRenderer.clear(); + + if (mSource != NULL) { + mSource->stop(); + mSource.clear(); + } + + if (mDriver != NULL) { + sp<NuPlayerDriver> driver = mDriver.promote(); + if (driver != NULL) { + driver->notifyResetComplete(); + } + } +} + +void NuPlayer::postScanSources() { + if (mScanSourcesPending) { + return; + } + + sp<AMessage> msg = new AMessage(kWhatScanSources, id()); + msg->setInt32("generation", mScanSourcesGeneration); + msg->post(); + + mScanSourcesPending = true; +} + +status_t NuPlayer::instantiateDecoder(bool audio, sp<Decoder> *decoder) { + if (*decoder != NULL) { + return OK; + } + + sp<MetaData> meta = mSource->getFormat(audio); + + if (meta == NULL) { + return -EWOULDBLOCK; + } + + if (!audio) { + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + mVideoIsAVC = !strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime); + } + + sp<AMessage> notify = + new AMessage(audio ? kWhatAudioNotify : kWhatVideoNotify, + id()); + + *decoder = audio ? new Decoder(notify) : + new Decoder(notify, mNativeWindow); + looper()->registerHandler(*decoder); + + (*decoder)->configure(meta); + + int64_t durationUs; + if (mDriver != NULL && mSource->getDuration(&durationUs) == OK) { + sp<NuPlayerDriver> driver = mDriver.promote(); + if (driver != NULL) { + driver->notifyDuration(durationUs); + } + } + + return OK; +} + +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))) { + reply->setInt32("err", INFO_DISCONTINUITY); + reply->post(); + return OK; + } + + sp<ABuffer> accessUnit; + + bool dropAccessUnit; + do { + status_t err = mSource->dequeueAccessUnit(audio, &accessUnit); + + if (err == -EWOULDBLOCK) { + return err; + } else if (err != OK) { + if (err == INFO_DISCONTINUITY) { + int32_t type; + CHECK(accessUnit->meta()->findInt32("discontinuity", &type)); + + bool formatChange = + (audio && + (type & ATSParser::DISCONTINUITY_AUDIO_FORMAT)) + || (!audio && + (type & ATSParser::DISCONTINUITY_VIDEO_FORMAT)); + + bool timeChange = (type & ATSParser::DISCONTINUITY_TIME) != 0; + + ALOGI("%s discontinuity (formatChange=%d, time=%d)", + audio ? "audio" : "video", formatChange, timeChange); + + if (audio) { + mSkipRenderingAudioUntilMediaTimeUs = -1; + } else { + mSkipRenderingVideoUntilMediaTimeUs = -1; + } + + if (timeChange) { + sp<AMessage> extra; + if (accessUnit->meta()->findMessage("extra", &extra) + && extra != NULL) { + int64_t resumeAtMediaTimeUs; + if (extra->findInt64( + "resume-at-mediatimeUs", &resumeAtMediaTimeUs)) { + ALOGI("suppressing rendering of %s until %lld us", + audio ? "audio" : "video", resumeAtMediaTimeUs); + + if (audio) { + mSkipRenderingAudioUntilMediaTimeUs = + resumeAtMediaTimeUs; + } else { + mSkipRenderingVideoUntilMediaTimeUs = + resumeAtMediaTimeUs; + } + } + } + } + + mTimeDiscontinuityPending = + mTimeDiscontinuityPending || timeChange; + + if (formatChange || timeChange) { + flushDecoder(audio, formatChange); + } else { + // This stream is unaffected by the discontinuity + + if (audio) { + mFlushingAudio = FLUSHED; + } else { + mFlushingVideo = FLUSHED; + } + + finishFlushIfPossible(); + + return -EWOULDBLOCK; + } + } + + reply->setInt32("err", err); + reply->post(); + return OK; + } + + if (!audio) { + ++mNumFramesTotal; + } + + dropAccessUnit = false; + if (!audio + && mVideoLateByUs > 100000ll + && mVideoIsAVC + && !IsAVCReferenceFrame(accessUnit)) { + dropAccessUnit = true; + ++mNumFramesDropped; + } + } while (dropAccessUnit); + + // ALOGV("returned a valid buffer of %s data", audio ? "audio" : "video"); + +#if 0 + int64_t mediaTimeUs; + CHECK(accessUnit->meta()->findInt64("timeUs", &mediaTimeUs)); + ALOGV("feeding %s input buffer at media time %.2f secs", + audio ? "audio" : "video", + mediaTimeUs / 1E6); +#endif + + reply->setBuffer("buffer", accessUnit); + reply->post(); + + return OK; +} + +void NuPlayer::renderBuffer(bool audio, const sp<AMessage> &msg) { + // ALOGV("renderBuffer %s", audio ? "audio" : "video"); + + sp<AMessage> reply; + CHECK(msg->findMessage("reply", &reply)); + + if (IsFlushingState(audio ? mFlushingAudio : mFlushingVideo)) { + // 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 + // we initiated the flush) to be stuck in the renderer's queue. + + ALOGV("we're still flushing the %s decoder, sending its output buffer" + " right back.", audio ? "audio" : "video"); + + reply->post(); + return; + } + + sp<ABuffer> buffer; + CHECK(msg->findBuffer("buffer", &buffer)); + + 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.", + audio ? "audio" : "video", + mediaTimeUs); + + reply->post(); + return; + } + + skipUntilMediaTimeUs = -1; + } + + mRenderer->queueBuffer(audio, buffer, reply); +} + +void NuPlayer::notifyListener(int msg, int ext1, int ext2) { + if (mDriver == NULL) { + return; + } + + sp<NuPlayerDriver> driver = mDriver.promote(); + + if (driver == NULL) { + return; + } + + driver->notifyListener(msg, ext1, ext2); +} + +void NuPlayer::flushDecoder(bool audio, bool needShutdown) { + if ((audio && mAudioDecoder == NULL) || (!audio && mVideoDecoder == NULL)) { + ALOGI("flushDecoder %s without decoder present", + audio ? "audio" : "video"); + } + + // Make sure we don't continue to scan sources until we finish flushing. + ++mScanSourcesGeneration; + mScanSourcesPending = false; + + (audio ? mAudioDecoder : mVideoDecoder)->signalFlush(); + mRenderer->flush(audio); + + FlushStatus newStatus = + needShutdown ? FLUSHING_DECODER_SHUTDOWN : FLUSHING_DECODER; + + if (audio) { + CHECK(mFlushingAudio == NONE + || mFlushingAudio == AWAITING_DISCONTINUITY); + + mFlushingAudio = newStatus; + + if (mFlushingVideo == NONE) { + mFlushingVideo = (mVideoDecoder != NULL) + ? AWAITING_DISCONTINUITY + : FLUSHED; + } + } else { + CHECK(mFlushingVideo == NONE + || mFlushingVideo == AWAITING_DISCONTINUITY); + + mFlushingVideo = newStatus; + + if (mFlushingAudio == NONE) { + mFlushingAudio = (mAudioDecoder != NULL) + ? AWAITING_DISCONTINUITY + : FLUSHED; + } + } +} + +} // namespace android diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h new file mode 100644 index 0000000..25766e0 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2010 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 NU_PLAYER_H_ + +#define NU_PLAYER_H_ + +#include <media/MediaPlayerInterface.h> +#include <media/stagefright/foundation/AHandler.h> +#include <media/stagefright/NativeWindowWrapper.h> + +namespace android { + +struct ACodec; +struct MetaData; +struct NuPlayerDriver; + +struct NuPlayer : public AHandler { + NuPlayer(); + + void setUID(uid_t uid); + + void setDriver(const wp<NuPlayerDriver> &driver); + + void setDataSource(const sp<IStreamSource> &source); + + void setDataSource( + const char *url, const KeyedVector<String8, String8> *headers); + + void setDataSource(int fd, int64_t offset, int64_t length); + + void setVideoSurfaceTexture(const sp<ISurfaceTexture> &surfaceTexture); + void setAudioSink(const sp<MediaPlayerBase::AudioSink> &sink); + void start(); + + void pause(); + void resume(); + + // Will notify the driver through "notifyResetComplete" once finished. + void resetAsync(); + + // Will notify the driver through "notifySeekComplete" once finished. + void seekToAsync(int64_t seekTimeUs); + +protected: + virtual ~NuPlayer(); + + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + struct Decoder; + struct GenericSource; + struct HTTPLiveSource; + struct NuPlayerStreamListener; + struct Renderer; + struct RTSPSource; + struct Source; + struct StreamingSource; + + enum { + kWhatSetDataSource = '=DaS', + kWhatSetVideoNativeWindow = '=NaW', + kWhatSetAudioSink = '=AuS', + kWhatMoreDataQueued = 'more', + kWhatStart = 'strt', + kWhatScanSources = 'scan', + kWhatVideoNotify = 'vidN', + kWhatAudioNotify = 'audN', + kWhatRendererNotify = 'renN', + kWhatReset = 'rset', + kWhatSeek = 'seek', + kWhatPause = 'paus', + kWhatResume = 'rsme', + }; + + wp<NuPlayerDriver> mDriver; + bool mUIDValid; + uid_t mUID; + sp<Source> mSource; + sp<NativeWindowWrapper> mNativeWindow; + sp<MediaPlayerBase::AudioSink> mAudioSink; + sp<Decoder> mVideoDecoder; + bool mVideoIsAVC; + sp<Decoder> mAudioDecoder; + sp<Renderer> mRenderer; + + bool mAudioEOS; + bool mVideoEOS; + + bool mScanSourcesPending; + int32_t mScanSourcesGeneration; + + enum FlushStatus { + NONE, + AWAITING_DISCONTINUITY, + FLUSHING_DECODER, + FLUSHING_DECODER_SHUTDOWN, + SHUTTING_DOWN_DECODER, + FLUSHED, + SHUT_DOWN, + }; + + // Once the current flush is complete this indicates whether the + // notion of time has changed. + bool mTimeDiscontinuityPending; + + FlushStatus mFlushingAudio; + FlushStatus mFlushingVideo; + bool mResetInProgress; + bool mResetPostponed; + + int64_t mSkipRenderingAudioUntilMediaTimeUs; + int64_t mSkipRenderingVideoUntilMediaTimeUs; + + int64_t mVideoLateByUs; + int64_t mNumFramesTotal, mNumFramesDropped; + + status_t instantiateDecoder(bool audio, sp<Decoder> *decoder); + + status_t feedDecoderInputData(bool audio, const sp<AMessage> &msg); + void renderBuffer(bool audio, const sp<AMessage> &msg); + + void notifyListener(int msg, int ext1, int ext2); + + void finishFlushIfPossible(); + + void flushDecoder(bool audio, bool needShutdown); + + static bool IsFlushingState(FlushStatus state, bool *needShutdown = NULL); + + void finishReset(); + void postScanSources(); + + DISALLOW_EVIL_CONSTRUCTORS(NuPlayer); +}; + +} // namespace android + +#endif // NU_PLAYER_H_ diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp new file mode 100644 index 0000000..5733229 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2010 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 "NuPlayerDecoder" +#include <utils/Log.h> + +#include "NuPlayerDecoder.h" + +#include "ESDS.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/MediaDefs.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/Utils.h> + +namespace android { + +NuPlayer::Decoder::Decoder( + const sp<AMessage> ¬ify, + const sp<NativeWindowWrapper> &nativeWindow) + : mNotify(notify), + mNativeWindow(nativeWindow) { +} + +NuPlayer::Decoder::~Decoder() { +} + +void NuPlayer::Decoder::configure(const sp<MetaData> &meta) { + CHECK(mCodec == NULL); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + sp<AMessage> notifyMsg = + new AMessage(kWhatCodecNotify, id()); + + sp<AMessage> format = makeFormat(meta); + + if (mNativeWindow != NULL) { + format->setObject("native-window", mNativeWindow); + } + + // 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, "video/", 6); + + mCodec = new ACodec; + + if (needDedicatedLooper && mCodecLooper == NULL) { + mCodecLooper = new ALooper; + mCodecLooper->setName("NuPlayerDecoder"); + mCodecLooper->start(false, false, ANDROID_PRIORITY_AUDIO); + } + + (needDedicatedLooper ? mCodecLooper : looper())->registerHandler(mCodec); + + mCodec->setNotificationMessage(notifyMsg); + mCodec->initiateSetup(format); +} + +void NuPlayer::Decoder::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatCodecNotify: + { + int32_t what; + CHECK(msg->findInt32("what", &what)); + + if (what == ACodec::kWhatFillThisBuffer) { + onFillThisBuffer(msg); + } else { + sp<AMessage> notify = mNotify->dup(); + notify->setMessage("codec-request", msg); + notify->post(); + } + break; + } + + default: + TRESPASS(); + break; + } +} + +sp<AMessage> NuPlayer::Decoder::makeFormat(const sp<MetaData> &meta) { + CHECK(mCSD.isEmpty()); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + sp<AMessage> msg = new AMessage; + msg->setString("mime", mime); + + if (!strncasecmp("video/", mime, 6)) { + int32_t width, height; + CHECK(meta->findInt32(kKeyWidth, &width)); + CHECK(meta->findInt32(kKeyHeight, &height)); + + msg->setInt32("width", width); + msg->setInt32("height", height); + } else { + CHECK(!strncasecmp("audio/", mime, 6)); + + int32_t numChannels, sampleRate; + CHECK(meta->findInt32(kKeyChannelCount, &numChannels)); + CHECK(meta->findInt32(kKeySampleRate, &sampleRate)); + + msg->setInt32("channel-count", numChannels); + msg->setInt32("sample-rate", sampleRate); + + int32_t isADTS; + if (meta->findInt32(kKeyIsADTS, &isADTS) && isADTS != 0) { + msg->setInt32("is-adts", true); + } + } + + int32_t maxInputSize; + if (meta->findInt32(kKeyMaxInputSize, &maxInputSize)) { + msg->setInt32("max-input-size", maxInputSize); + } + + mCSDIndex = 0; + + uint32_t type; + const void *data; + size_t size; + if (meta->findData(kKeyAVCC, &type, &data, &size)) { + // Parse the AVCDecoderConfigurationRecord + + const uint8_t *ptr = (const uint8_t *)data; + + CHECK(size >= 7); + CHECK_EQ((unsigned)ptr[0], 1u); // configurationVersion == 1 + uint8_t profile = ptr[1]; + uint8_t level = ptr[3]; + + // There is decodable content out there that fails the following + // assertion, let's be lenient for now... + // CHECK((ptr[4] >> 2) == 0x3f); // reserved + + size_t lengthSize = 1 + (ptr[4] & 3); + + // commented out check below as H264_QVGA_500_NO_AUDIO.3gp + // violates it... + // CHECK((ptr[5] >> 5) == 7); // reserved + + size_t numSeqParameterSets = ptr[5] & 31; + + ptr += 6; + size -= 6; + + sp<ABuffer> buffer = new ABuffer(1024); + buffer->setRange(0, 0); + + for (size_t i = 0; i < numSeqParameterSets; ++i) { + CHECK(size >= 2); + size_t length = U16_AT(ptr); + + ptr += 2; + size -= 2; + + CHECK(size >= length); + + memcpy(buffer->data() + buffer->size(), "\x00\x00\x00\x01", 4); + memcpy(buffer->data() + buffer->size() + 4, ptr, length); + buffer->setRange(0, buffer->size() + 4 + length); + + ptr += length; + size -= length; + } + + buffer->meta()->setInt32("csd", true); + mCSD.push(buffer); + + buffer = new ABuffer(1024); + buffer->setRange(0, 0); + + CHECK(size >= 1); + size_t numPictureParameterSets = *ptr; + ++ptr; + --size; + + for (size_t i = 0; i < numPictureParameterSets; ++i) { + CHECK(size >= 2); + size_t length = U16_AT(ptr); + + ptr += 2; + size -= 2; + + CHECK(size >= length); + + memcpy(buffer->data() + buffer->size(), "\x00\x00\x00\x01", 4); + memcpy(buffer->data() + buffer->size() + 4, ptr, length); + buffer->setRange(0, buffer->size() + 4 + length); + + ptr += length; + size -= length; + } + + buffer->meta()->setInt32("csd", true); + mCSD.push(buffer); + } else if (meta->findData(kKeyESDS, &type, &data, &size)) { + ESDS esds((const char *)data, size); + CHECK_EQ(esds.InitCheck(), (status_t)OK); + + const void *codec_specific_data; + size_t codec_specific_data_size; + esds.getCodecSpecificInfo( + &codec_specific_data, &codec_specific_data_size); + + sp<ABuffer> buffer = new ABuffer(codec_specific_data_size); + + memcpy(buffer->data(), codec_specific_data, + codec_specific_data_size); + + buffer->meta()->setInt32("csd", true); + mCSD.push(buffer); + } else if (meta->findData(kKeyVorbisInfo, &type, &data, &size)) { + sp<ABuffer> buffer = new ABuffer(size); + memcpy(buffer->data(), data, size); + + buffer->meta()->setInt32("csd", true); + mCSD.push(buffer); + + CHECK(meta->findData(kKeyVorbisBooks, &type, &data, &size)); + + buffer = new ABuffer(size); + memcpy(buffer->data(), data, size); + + buffer->meta()->setInt32("csd", true); + mCSD.push(buffer); + } + + return msg; +} + +void NuPlayer::Decoder::onFillThisBuffer(const sp<AMessage> &msg) { + sp<AMessage> reply; + CHECK(msg->findMessage("reply", &reply)); + +#if 0 + sp<ABuffer> outBuffer; + CHECK(msg->findBuffer("buffer", &outBuffer)); +#else + sp<ABuffer> outBuffer; +#endif + + if (mCSDIndex < mCSD.size()) { + outBuffer = mCSD.editItemAt(mCSDIndex++); + outBuffer->meta()->setInt64("timeUs", 0); + + reply->setBuffer("buffer", outBuffer); + reply->post(); + return; + } + + sp<AMessage> notify = mNotify->dup(); + notify->setMessage("codec-request", msg); + notify->post(); +} + +void NuPlayer::Decoder::signalFlush() { + if (mCodec != NULL) { + mCodec->signalFlush(); + } +} + +void NuPlayer::Decoder::signalResume() { + if (mCodec != NULL) { + mCodec->signalResume(); + } +} + +void NuPlayer::Decoder::initiateShutdown() { + if (mCodec != NULL) { + mCodec->initiateShutdown(); + } +} + +} // namespace android + diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h new file mode 100644 index 0000000..3ab1fcf --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2010 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_H_ + +#define NUPLAYER_DECODER_H_ + +#include "NuPlayer.h" + +#include <media/stagefright/foundation/AHandler.h> + +namespace android { + +struct ABuffer; + +struct NuPlayer::Decoder : public AHandler { + Decoder(const sp<AMessage> ¬ify, + const sp<NativeWindowWrapper> &nativeWindow = NULL); + + void configure(const sp<MetaData> &meta); + + void signalFlush(); + void signalResume(); + void initiateShutdown(); + +protected: + virtual ~Decoder(); + + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + enum { + kWhatCodecNotify = 'cdcN', + }; + + sp<AMessage> mNotify; + sp<NativeWindowWrapper> mNativeWindow; + + sp<ACodec> mCodec; + sp<ALooper> mCodecLooper; + + Vector<sp<ABuffer> > mCSD; + size_t mCSDIndex; + + sp<AMessage> makeFormat(const sp<MetaData> &meta); + + void onFillThisBuffer(const sp<AMessage> &msg); + + DISALLOW_EVIL_CONSTRUCTORS(Decoder); +}; + +} // namespace android + +#endif // NUPLAYER_DECODER_H_ diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp new file mode 100644 index 0000000..253bc2f --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2010 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 "NuPlayerDriver" +#include <utils/Log.h> + +#include "NuPlayerDriver.h" + +#include "NuPlayer.h" + +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/ALooper.h> + +namespace android { + +NuPlayerDriver::NuPlayerDriver() + : mResetInProgress(false), + mDurationUs(-1), + mPositionUs(-1), + mNumFramesTotal(0), + mNumFramesDropped(0), + mLooper(new ALooper), + mState(UNINITIALIZED), + mAtEOS(false), + mStartupSeekTimeUs(-1) { + mLooper->setName("NuPlayerDriver Looper"); + + mLooper->start( + false, /* runOnCallingThread */ + true, /* canCallJava */ + PRIORITY_AUDIO); + + mPlayer = new NuPlayer; + mLooper->registerHandler(mPlayer); + + mPlayer->setDriver(this); +} + +NuPlayerDriver::~NuPlayerDriver() { + mLooper->stop(); +} + +status_t NuPlayerDriver::initCheck() { + return OK; +} + +status_t NuPlayerDriver::setUID(uid_t uid) { + mPlayer->setUID(uid); + + return OK; +} + +status_t NuPlayerDriver::setDataSource( + const char *url, const KeyedVector<String8, String8> *headers) { + CHECK_EQ((int)mState, (int)UNINITIALIZED); + + mPlayer->setDataSource(url, headers); + + mState = STOPPED; + + return OK; +} + +status_t NuPlayerDriver::setDataSource(int fd, int64_t offset, int64_t length) { + CHECK_EQ((int)mState, (int)UNINITIALIZED); + + mPlayer->setDataSource(fd, offset, length); + + mState = STOPPED; + + return OK; +} + +status_t NuPlayerDriver::setDataSource(const sp<IStreamSource> &source) { + CHECK_EQ((int)mState, (int)UNINITIALIZED); + + mPlayer->setDataSource(source); + + mState = STOPPED; + + return OK; +} + +status_t NuPlayerDriver::setVideoSurfaceTexture( + const sp<ISurfaceTexture> &surfaceTexture) { + mPlayer->setVideoSurfaceTexture(surfaceTexture); + + return OK; +} + +status_t NuPlayerDriver::prepare() { + sendEvent(MEDIA_SET_VIDEO_SIZE, 320, 240); + return OK; +} + +status_t NuPlayerDriver::prepareAsync() { + status_t err = prepare(); + + notifyListener(MEDIA_PREPARED); + + return err; +} + +status_t NuPlayerDriver::start() { + switch (mState) { + case UNINITIALIZED: + return INVALID_OPERATION; + case STOPPED: + { + mAtEOS = false; + mPlayer->start(); + + if (mStartupSeekTimeUs >= 0) { + if (mStartupSeekTimeUs == 0) { + notifySeekComplete(); + } else { + mPlayer->seekToAsync(mStartupSeekTimeUs); + } + + mStartupSeekTimeUs = -1; + } + + break; + } + case PLAYING: + return OK; + default: + { + CHECK_EQ((int)mState, (int)PAUSED); + + mPlayer->resume(); + break; + } + } + + mState = PLAYING; + + return OK; +} + +status_t NuPlayerDriver::stop() { + return pause(); +} + +status_t NuPlayerDriver::pause() { + switch (mState) { + case UNINITIALIZED: + return INVALID_OPERATION; + case STOPPED: + return OK; + case PLAYING: + mPlayer->pause(); + break; + default: + { + CHECK_EQ((int)mState, (int)PAUSED); + return OK; + } + } + + mState = PAUSED; + + return OK; +} + +bool NuPlayerDriver::isPlaying() { + return mState == PLAYING && !mAtEOS; +} + +status_t NuPlayerDriver::seekTo(int msec) { + int64_t seekTimeUs = msec * 1000ll; + + switch (mState) { + case UNINITIALIZED: + return INVALID_OPERATION; + case STOPPED: + { + mStartupSeekTimeUs = seekTimeUs; + break; + } + case PLAYING: + case PAUSED: + { + mAtEOS = false; + mPlayer->seekToAsync(seekTimeUs); + break; + } + + default: + TRESPASS(); + break; + } + + return OK; +} + +status_t NuPlayerDriver::getCurrentPosition(int *msec) { + Mutex::Autolock autoLock(mLock); + + if (mPositionUs < 0) { + *msec = 0; + } else { + *msec = (mPositionUs + 500ll) / 1000; + } + + return OK; +} + +status_t NuPlayerDriver::getDuration(int *msec) { + Mutex::Autolock autoLock(mLock); + + if (mDurationUs < 0) { + *msec = 0; + } else { + *msec = (mDurationUs + 500ll) / 1000; + } + + return OK; +} + +status_t NuPlayerDriver::reset() { + Mutex::Autolock autoLock(mLock); + mResetInProgress = true; + + mPlayer->resetAsync(); + + while (mResetInProgress) { + mCondition.wait(mLock); + } + + mDurationUs = -1; + mPositionUs = -1; + mState = UNINITIALIZED; + mStartupSeekTimeUs = -1; + + return OK; +} + +status_t NuPlayerDriver::setLooping(int loop) { + return INVALID_OPERATION; +} + +player_type NuPlayerDriver::playerType() { + return NU_PLAYER; +} + +status_t NuPlayerDriver::invoke(const Parcel &request, Parcel *reply) { + return INVALID_OPERATION; +} + +void NuPlayerDriver::setAudioSink(const sp<AudioSink> &audioSink) { + mPlayer->setAudioSink(audioSink); +} + +status_t NuPlayerDriver::setParameter(int key, const Parcel &request) { + return INVALID_OPERATION; +} + +status_t NuPlayerDriver::getParameter(int key, Parcel *reply) { + return INVALID_OPERATION; +} + +status_t NuPlayerDriver::getMetadata( + const media::Metadata::Filter& ids, Parcel *records) { + return INVALID_OPERATION; +} + +void NuPlayerDriver::notifyResetComplete() { + Mutex::Autolock autoLock(mLock); + CHECK(mResetInProgress); + mResetInProgress = false; + mCondition.broadcast(); +} + +void NuPlayerDriver::notifyDuration(int64_t durationUs) { + Mutex::Autolock autoLock(mLock); + mDurationUs = durationUs; +} + +void NuPlayerDriver::notifyPosition(int64_t positionUs) { + Mutex::Autolock autoLock(mLock); + mPositionUs = positionUs; +} + +void NuPlayerDriver::notifySeekComplete() { + notifyListener(MEDIA_SEEK_COMPLETE); +} + +void NuPlayerDriver::notifyFrameStats( + int64_t numFramesTotal, int64_t numFramesDropped) { + Mutex::Autolock autoLock(mLock); + mNumFramesTotal = numFramesTotal; + mNumFramesDropped = numFramesDropped; +} + +status_t NuPlayerDriver::dump(int fd, const Vector<String16> &args) const { + Mutex::Autolock autoLock(mLock); + + FILE *out = fdopen(dup(fd), "w"); + + fprintf(out, " NuPlayer\n"); + fprintf(out, " numFramesTotal(%lld), numFramesDropped(%lld), " + "percentageDropped(%.2f)\n", + mNumFramesTotal, + mNumFramesDropped, + mNumFramesTotal == 0 + ? 0.0 : (double)mNumFramesDropped / mNumFramesTotal); + + fclose(out); + out = NULL; + + return OK; +} + +void NuPlayerDriver::notifyListener(int msg, int ext1, int ext2) { + if (msg == MEDIA_PLAYBACK_COMPLETE || msg == MEDIA_ERROR) { + mAtEOS = true; + } + + sendEvent(msg, ext1, ext2); +} + +} // namespace android diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h new file mode 100644 index 0000000..4a0026c --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2010 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 <media/MediaPlayerInterface.h> + +#include <media/stagefright/foundation/ABase.h> + +namespace android { + +struct ALooper; +struct NuPlayer; + +struct NuPlayerDriver : public MediaPlayerInterface { + NuPlayerDriver(); + + virtual status_t initCheck(); + + virtual status_t setUID(uid_t uid); + + virtual status_t setDataSource( + const char *url, const KeyedVector<String8, String8> *headers); + + virtual status_t setDataSource(int fd, int64_t offset, int64_t length); + + virtual status_t setDataSource(const sp<IStreamSource> &source); + + virtual status_t setVideoSurfaceTexture( + const sp<ISurfaceTexture> &surfaceTexture); + virtual status_t prepare(); + virtual status_t prepareAsync(); + virtual status_t start(); + virtual status_t stop(); + virtual status_t pause(); + virtual bool isPlaying(); + virtual status_t seekTo(int msec); + virtual status_t getCurrentPosition(int *msec); + virtual status_t getDuration(int *msec); + virtual status_t reset(); + virtual status_t setLooping(int loop); + virtual player_type playerType(); + virtual status_t invoke(const Parcel &request, Parcel *reply); + virtual void setAudioSink(const sp<AudioSink> &audioSink); + virtual status_t setParameter(int key, const Parcel &request); + virtual status_t getParameter(int key, Parcel *reply); + + virtual status_t getMetadata( + const media::Metadata::Filter& ids, Parcel *records); + + virtual status_t dump(int fd, const Vector<String16> &args) const; + + void notifyResetComplete(); + void notifyDuration(int64_t durationUs); + void notifyPosition(int64_t positionUs); + void notifySeekComplete(); + void notifyFrameStats(int64_t numFramesTotal, int64_t numFramesDropped); + void notifyListener(int msg, int ext1 = 0, int ext2 = 0); + +protected: + virtual ~NuPlayerDriver(); + +private: + mutable Mutex mLock; + Condition mCondition; + + // The following are protected through "mLock" + // >>> + bool mResetInProgress; + int64_t mDurationUs; + int64_t mPositionUs; + int64_t mNumFramesTotal; + int64_t mNumFramesDropped; + // <<< + + sp<ALooper> mLooper; + sp<NuPlayer> mPlayer; + + enum State { + UNINITIALIZED, + STOPPED, + PLAYING, + PAUSED + }; + + State mState; + bool mAtEOS; + + int64_t mStartupSeekTimeUs; + + DISALLOW_EVIL_CONSTRUCTORS(NuPlayerDriver); +}; + +} // namespace android + + diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp new file mode 100644 index 0000000..ecbc428 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp @@ -0,0 +1,658 @@ +/* + * Copyright (C) 2010 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 "NuPlayerRenderer" +#include <utils/Log.h> + +#include "NuPlayerRenderer.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> + +namespace android { + +// static +const int64_t NuPlayer::Renderer::kMinPositionUpdateDelayUs = 100000ll; + +NuPlayer::Renderer::Renderer( + const sp<MediaPlayerBase::AudioSink> &sink, + const sp<AMessage> ¬ify) + : mAudioSink(sink), + mNotify(notify), + mNumFramesWritten(0), + mDrainAudioQueuePending(false), + mDrainVideoQueuePending(false), + mAudioQueueGeneration(0), + mVideoQueueGeneration(0), + mAnchorTimeMediaUs(-1), + mAnchorTimeRealUs(-1), + mFlushingAudio(false), + mFlushingVideo(false), + mHasAudio(false), + mHasVideo(false), + mSyncQueues(false), + mPaused(false), + mLastPositionUpdateUs(-1ll), + mVideoLateByUs(0ll) { +} + +NuPlayer::Renderer::~Renderer() { +} + +void NuPlayer::Renderer::queueBuffer( + bool audio, + const sp<ABuffer> &buffer, + const sp<AMessage> ¬ifyConsumed) { + sp<AMessage> msg = new AMessage(kWhatQueueBuffer, id()); + msg->setInt32("audio", static_cast<int32_t>(audio)); + msg->setBuffer("buffer", buffer); + msg->setMessage("notifyConsumed", notifyConsumed); + msg->post(); +} + +void NuPlayer::Renderer::queueEOS(bool audio, status_t finalResult) { + CHECK_NE(finalResult, (status_t)OK); + + sp<AMessage> msg = new AMessage(kWhatQueueEOS, id()); + msg->setInt32("audio", static_cast<int32_t>(audio)); + msg->setInt32("finalResult", finalResult); + msg->post(); +} + +void NuPlayer::Renderer::flush(bool audio) { + { + Mutex::Autolock autoLock(mFlushLock); + if (audio) { + CHECK(!mFlushingAudio); + mFlushingAudio = true; + } else { + CHECK(!mFlushingVideo); + mFlushingVideo = true; + } + } + + sp<AMessage> msg = new AMessage(kWhatFlush, id()); + msg->setInt32("audio", static_cast<int32_t>(audio)); + msg->post(); +} + +void NuPlayer::Renderer::signalTimeDiscontinuity() { + CHECK(mAudioQueue.empty()); + CHECK(mVideoQueue.empty()); + mAnchorTimeMediaUs = -1; + mAnchorTimeRealUs = -1; + mSyncQueues = mHasAudio && mHasVideo; +} + +void NuPlayer::Renderer::pause() { + (new AMessage(kWhatPause, id()))->post(); +} + +void NuPlayer::Renderer::resume() { + (new AMessage(kWhatResume, id()))->post(); +} + +void NuPlayer::Renderer::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatDrainAudioQueue: + { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + if (generation != mAudioQueueGeneration) { + break; + } + + mDrainAudioQueuePending = false; + + if (onDrainAudioQueue()) { + uint32_t numFramesPlayed; + CHECK_EQ(mAudioSink->getPosition(&numFramesPlayed), + (status_t)OK); + + uint32_t numFramesPendingPlayout = + mNumFramesWritten - numFramesPlayed; + + // This is how long the audio sink will have data to + // play back. + int64_t delayUs = + mAudioSink->msecsPerFrame() + * numFramesPendingPlayout * 1000ll; + + // Let's give it more data after about half that time + // has elapsed. + postDrainAudioQueue(delayUs / 2); + } + break; + } + + case kWhatDrainVideoQueue: + { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + if (generation != mVideoQueueGeneration) { + break; + } + + mDrainVideoQueuePending = false; + + onDrainVideoQueue(); + + postDrainVideoQueue(); + break; + } + + case kWhatQueueBuffer: + { + onQueueBuffer(msg); + break; + } + + case kWhatQueueEOS: + { + onQueueEOS(msg); + break; + } + + case kWhatFlush: + { + onFlush(msg); + break; + } + + case kWhatAudioSinkChanged: + { + onAudioSinkChanged(); + break; + } + + case kWhatPause: + { + onPause(); + break; + } + + case kWhatResume: + { + onResume(); + break; + } + + default: + TRESPASS(); + break; + } +} + +void NuPlayer::Renderer::postDrainAudioQueue(int64_t delayUs) { + if (mDrainAudioQueuePending || mSyncQueues || mPaused) { + return; + } + + if (mAudioQueue.empty()) { + return; + } + + mDrainAudioQueuePending = true; + sp<AMessage> msg = new AMessage(kWhatDrainAudioQueue, id()); + msg->setInt32("generation", mAudioQueueGeneration); + msg->post(delayUs); +} + +void NuPlayer::Renderer::signalAudioSinkChanged() { + (new AMessage(kWhatAudioSinkChanged, id()))->post(); +} + +bool NuPlayer::Renderer::onDrainAudioQueue() { + uint32_t numFramesPlayed; + if (mAudioSink->getPosition(&numFramesPlayed) != OK) { + return false; + } + + ssize_t numFramesAvailableToWrite = + mAudioSink->frameCount() - (mNumFramesWritten - numFramesPlayed); + +#if 0 + if (numFramesAvailableToWrite == mAudioSink->frameCount()) { + ALOGI("audio sink underrun"); + } else { + ALOGV("audio queue has %d frames left to play", + mAudioSink->frameCount() - numFramesAvailableToWrite); + } +#endif + + size_t numBytesAvailableToWrite = + numFramesAvailableToWrite * mAudioSink->frameSize(); + + while (numBytesAvailableToWrite > 0 && !mAudioQueue.empty()) { + QueueEntry *entry = &*mAudioQueue.begin(); + + if (entry->mBuffer == NULL) { + // EOS + + notifyEOS(true /* audio */, entry->mFinalResult); + + mAudioQueue.erase(mAudioQueue.begin()); + entry = NULL; + return false; + } + + if (entry->mOffset == 0) { + int64_t mediaTimeUs; + CHECK(entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs)); + + ALOGV("rendering audio at media time %.2f secs", mediaTimeUs / 1E6); + + mAnchorTimeMediaUs = mediaTimeUs; + + uint32_t numFramesPlayed; + CHECK_EQ(mAudioSink->getPosition(&numFramesPlayed), (status_t)OK); + + 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; + } + + size_t copy = entry->mBuffer->size() - entry->mOffset; + if (copy > numBytesAvailableToWrite) { + copy = numBytesAvailableToWrite; + } + + CHECK_EQ(mAudioSink->write( + entry->mBuffer->data() + entry->mOffset, copy), + (ssize_t)copy); + + entry->mOffset += copy; + if (entry->mOffset == entry->mBuffer->size()) { + entry->mNotifyConsumed->post(); + mAudioQueue.erase(mAudioQueue.begin()); + + entry = NULL; + } + + numBytesAvailableToWrite -= copy; + size_t copiedFrames = copy / mAudioSink->frameSize(); + mNumFramesWritten += copiedFrames; + } + + notifyPosition(); + + return !mAudioQueue.empty(); +} + +void NuPlayer::Renderer::postDrainVideoQueue() { + if (mDrainVideoQueuePending || mSyncQueues || mPaused) { + return; + } + + if (mVideoQueue.empty()) { + return; + } + + QueueEntry &entry = *mVideoQueue.begin(); + + 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 { + int64_t mediaTimeUs; + CHECK(entry.mBuffer->meta()->findInt64("timeUs", &mediaTimeUs)); + + if (mAnchorTimeMediaUs < 0) { + delayUs = 0; + + if (!mHasAudio) { + mAnchorTimeMediaUs = mediaTimeUs; + mAnchorTimeRealUs = ALooper::GetNowUs(); + } + } else { + int64_t realTimeUs = + (mediaTimeUs - mAnchorTimeMediaUs) + mAnchorTimeRealUs; + + delayUs = realTimeUs - ALooper::GetNowUs(); + } + } + + msg->post(delayUs); + + mDrainVideoQueuePending = true; +} + +void NuPlayer::Renderer::onDrainVideoQueue() { + if (mVideoQueue.empty()) { + return; + } + + QueueEntry *entry = &*mVideoQueue.begin(); + + if (entry->mBuffer == NULL) { + // EOS + + notifyEOS(false /* audio */, entry->mFinalResult); + + mVideoQueue.erase(mVideoQueue.begin()); + entry = NULL; + + mVideoLateByUs = 0ll; + + notifyPosition(); + return; + } + + int64_t mediaTimeUs; + CHECK(entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs)); + + int64_t realTimeUs = mediaTimeUs - mAnchorTimeMediaUs + mAnchorTimeRealUs; + mVideoLateByUs = ALooper::GetNowUs() - realTimeUs; + + bool 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", mediaTimeUs / 1E6); + } + + entry->mNotifyConsumed->setInt32("render", !tooLate); + entry->mNotifyConsumed->post(); + mVideoQueue.erase(mVideoQueue.begin()); + entry = NULL; + + notifyPosition(); +} + +void NuPlayer::Renderer::notifyEOS(bool audio, status_t finalResult) { + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatEOS); + notify->setInt32("audio", static_cast<int32_t>(audio)); + notify->setInt32("finalResult", finalResult); + notify->post(); +} + +void NuPlayer::Renderer::onQueueBuffer(const sp<AMessage> &msg) { + int32_t audio; + CHECK(msg->findInt32("audio", &audio)); + + if (audio) { + mHasAudio = true; + } else { + mHasVideo = true; + } + + if (dropBufferWhileFlushing(audio, msg)) { + return; + } + + sp<ABuffer> buffer; + CHECK(msg->findBuffer("buffer", &buffer)); + + sp<AMessage> notifyConsumed; + CHECK(msg->findMessage("notifyConsumed", ¬ifyConsumed)); + + QueueEntry entry; + entry.mBuffer = buffer; + entry.mNotifyConsumed = notifyConsumed; + entry.mOffset = 0; + entry.mFinalResult = OK; + + if (audio) { + mAudioQueue.push_back(entry); + postDrainAudioQueue(); + } else { + mVideoQueue.push_back(entry); + postDrainVideoQueue(); + } + + if (!mSyncQueues || mAudioQueue.empty() || mVideoQueue.empty()) { + return; + } + + sp<ABuffer> firstAudioBuffer = (*mAudioQueue.begin()).mBuffer; + sp<ABuffer> firstVideoBuffer = (*mVideoQueue.begin()).mBuffer; + + if (firstAudioBuffer == NULL || firstVideoBuffer == NULL) { + // EOS signalled on either queue. + syncQueuesDone(); + return; + } + + int64_t firstAudioTimeUs; + int64_t firstVideoTimeUs; + CHECK(firstAudioBuffer->meta() + ->findInt64("timeUs", &firstAudioTimeUs)); + CHECK(firstVideoBuffer->meta() + ->findInt64("timeUs", &firstVideoTimeUs)); + + int64_t diff = firstVideoTimeUs - firstAudioTimeUs; + + ALOGV("queueDiff = %.2f secs", diff / 1E6); + + if (diff > 100000ll) { + // Audio data starts More than 0.1 secs before video. + // Drop some audio. + + (*mAudioQueue.begin()).mNotifyConsumed->post(); + mAudioQueue.erase(mAudioQueue.begin()); + return; + } + + syncQueuesDone(); +} + +void NuPlayer::Renderer::syncQueuesDone() { + if (!mSyncQueues) { + return; + } + + mSyncQueues = false; + + if (!mAudioQueue.empty()) { + postDrainAudioQueue(); + } + + if (!mVideoQueue.empty()) { + postDrainVideoQueue(); + } +} + +void NuPlayer::Renderer::onQueueEOS(const sp<AMessage> &msg) { + int32_t audio; + CHECK(msg->findInt32("audio", &audio)); + + if (dropBufferWhileFlushing(audio, msg)) { + return; + } + + int32_t finalResult; + CHECK(msg->findInt32("finalResult", &finalResult)); + + QueueEntry entry; + entry.mOffset = 0; + entry.mFinalResult = finalResult; + + if (audio) { + mAudioQueue.push_back(entry); + postDrainAudioQueue(); + } else { + mVideoQueue.push_back(entry); + postDrainVideoQueue(); + } +} + +void NuPlayer::Renderer::onFlush(const sp<AMessage> &msg) { + int32_t audio; + CHECK(msg->findInt32("audio", &audio)); + + // 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 + // more buffers from the decoder. If the other source then encounters + // a discontinuity that leads to flushing, we'll never find the + // corresponding discontinuity on the other queue. + // Therefore we'll stop syncing the queues if at least one of them + // is flushed. + syncQueuesDone(); + + if (audio) { + flushQueue(&mAudioQueue); + + Mutex::Autolock autoLock(mFlushLock); + mFlushingAudio = false; + + mDrainAudioQueuePending = false; + ++mAudioQueueGeneration; + } else { + flushQueue(&mVideoQueue); + + Mutex::Autolock autoLock(mFlushLock); + mFlushingVideo = false; + + mDrainVideoQueuePending = false; + ++mVideoQueueGeneration; + } + + notifyFlushComplete(audio); +} + +void NuPlayer::Renderer::flushQueue(List<QueueEntry> *queue) { + while (!queue->empty()) { + QueueEntry *entry = &*queue->begin(); + + if (entry->mBuffer != NULL) { + entry->mNotifyConsumed->post(); + } + + queue->erase(queue->begin()); + entry = NULL; + } +} + +void NuPlayer::Renderer::notifyFlushComplete(bool audio) { + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatFlushComplete); + notify->setInt32("audio", static_cast<int32_t>(audio)); + notify->post(); +} + +bool NuPlayer::Renderer::dropBufferWhileFlushing( + bool audio, const sp<AMessage> &msg) { + bool flushing = false; + + { + Mutex::Autolock autoLock(mFlushLock); + if (audio) { + flushing = mFlushingAudio; + } else { + flushing = mFlushingVideo; + } + } + + if (!flushing) { + return false; + } + + sp<AMessage> notifyConsumed; + if (msg->findMessage("notifyConsumed", ¬ifyConsumed)) { + notifyConsumed->post(); + } + + return true; +} + +void NuPlayer::Renderer::onAudioSinkChanged() { + CHECK(!mDrainAudioQueuePending); + mNumFramesWritten = 0; +} + +void NuPlayer::Renderer::notifyPosition() { + if (mAnchorTimeRealUs < 0 || mAnchorTimeMediaUs < 0) { + return; + } + + int64_t nowUs = ALooper::GetNowUs(); + + if (mLastPositionUpdateUs >= 0 + && nowUs < mLastPositionUpdateUs + kMinPositionUpdateDelayUs) { + return; + } + mLastPositionUpdateUs = nowUs; + + int64_t positionUs = (nowUs - mAnchorTimeRealUs) + mAnchorTimeMediaUs; + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("what", kWhatPosition); + notify->setInt64("positionUs", positionUs); + notify->setInt64("videoLateByUs", mVideoLateByUs); + notify->post(); +} + +void NuPlayer::Renderer::onPause() { + CHECK(!mPaused); + + mDrainAudioQueuePending = false; + ++mAudioQueueGeneration; + + mDrainVideoQueuePending = false; + ++mVideoQueueGeneration; + + if (mHasAudio) { + mAudioSink->pause(); + } + + ALOGV("now paused audio queue has %d entries, video has %d entries", + mAudioQueue.size(), mVideoQueue.size()); + + mPaused = true; +} + +void NuPlayer::Renderer::onResume() { + if (!mPaused) { + return; + } + + if (mHasAudio) { + mAudioSink->start(); + } + + mPaused = false; + + if (!mAudioQueue.empty()) { + postDrainAudioQueue(); + } + + if (!mVideoQueue.empty()) { + postDrainVideoQueue(); + } +} + +} // namespace android + diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h new file mode 100644 index 0000000..268628b --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2010 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_RENDERER_H_ + +#define NUPLAYER_RENDERER_H_ + +#include "NuPlayer.h" + +namespace android { + +struct ABuffer; + +struct NuPlayer::Renderer : public AHandler { + Renderer(const sp<MediaPlayerBase::AudioSink> &sink, + const sp<AMessage> ¬ify); + + void queueBuffer( + bool audio, + const sp<ABuffer> &buffer, + const sp<AMessage> ¬ifyConsumed); + + void queueEOS(bool audio, status_t finalResult); + + void flush(bool audio); + + void signalTimeDiscontinuity(); + + void signalAudioSinkChanged(); + + void pause(); + void resume(); + + enum { + kWhatEOS = 'eos ', + kWhatFlushComplete = 'fluC', + kWhatPosition = 'posi', + }; + +protected: + virtual ~Renderer(); + + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + enum { + kWhatDrainAudioQueue = 'draA', + kWhatDrainVideoQueue = 'draV', + kWhatQueueBuffer = 'queB', + kWhatQueueEOS = 'qEOS', + kWhatFlush = 'flus', + kWhatAudioSinkChanged = 'auSC', + kWhatPause = 'paus', + kWhatResume = 'resm', + }; + + struct QueueEntry { + sp<ABuffer> mBuffer; + sp<AMessage> mNotifyConsumed; + size_t mOffset; + status_t mFinalResult; + }; + + static const int64_t kMinPositionUpdateDelayUs; + + sp<MediaPlayerBase::AudioSink> mAudioSink; + sp<AMessage> mNotify; + List<QueueEntry> mAudioQueue; + List<QueueEntry> mVideoQueue; + uint32_t mNumFramesWritten; + + bool mDrainAudioQueuePending; + bool mDrainVideoQueuePending; + int32_t mAudioQueueGeneration; + int32_t mVideoQueueGeneration; + + int64_t mAnchorTimeMediaUs; + int64_t mAnchorTimeRealUs; + + Mutex mFlushLock; // protects the following 2 member vars. + bool mFlushingAudio; + bool mFlushingVideo; + + bool mHasAudio; + bool mHasVideo; + bool mSyncQueues; + + bool mPaused; + + int64_t mLastPositionUpdateUs; + int64_t mVideoLateByUs; + + bool onDrainAudioQueue(); + void postDrainAudioQueue(int64_t delayUs = 0); + + void onDrainVideoQueue(); + void postDrainVideoQueue(); + + void onQueueBuffer(const sp<AMessage> &msg); + void onQueueEOS(const sp<AMessage> &msg); + void onFlush(const sp<AMessage> &msg); + void onAudioSinkChanged(); + void onPause(); + void onResume(); + + void notifyEOS(bool audio, status_t finalResult); + void notifyFlushComplete(bool audio); + void notifyPosition(); + void notifyVideoLateBy(int64_t lateByUs); + + void flushQueue(List<QueueEntry> *queue); + bool dropBufferWhileFlushing(bool audio, const sp<AMessage> &msg); + void syncQueuesDone(); + + DISALLOW_EVIL_CONSTRUCTORS(Renderer); +}; + +} // namespace android + +#endif // NUPLAYER_RENDERER_H_ diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerSource.h b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h new file mode 100644 index 0000000..531b29f --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerSource.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010 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_SOURCE_H_ + +#define NUPLAYER_SOURCE_H_ + +#include "NuPlayer.h" + +namespace android { + +struct ABuffer; + +struct NuPlayer::Source : public RefBase { + Source() {} + + virtual void start() = 0; + virtual void stop() {} + + // Returns OK iff more data was available, + // an error or ERROR_END_OF_STREAM if not. + virtual status_t feedMoreTSData() = 0; + + virtual sp<MetaData> getFormat(bool audio) = 0; + + virtual status_t dequeueAccessUnit( + bool audio, sp<ABuffer> *accessUnit) = 0; + + virtual status_t getDuration(int64_t *durationUs) { + return INVALID_OPERATION; + } + + virtual status_t seekTo(int64_t seekTimeUs) { + return INVALID_OPERATION; + } + + virtual bool isSeekable() { + return false; + } + +protected: + virtual ~Source() {} + +private: + DISALLOW_EVIL_CONSTRUCTORS(Source); +}; + +} // namespace android + +#endif // NUPLAYER_SOURCE_H_ + diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.cpp new file mode 100644 index 0000000..885ebe4 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2010 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 "NuPlayerStreamListener" +#include <utils/Log.h> + +#include "NuPlayerStreamListener.h" + +#include <binder/MemoryDealer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/MediaErrors.h> + +namespace android { + +NuPlayer::NuPlayerStreamListener::NuPlayerStreamListener( + const sp<IStreamSource> &source, + ALooper::handler_id id) + : mSource(source), + mTargetID(id), + mEOS(false), + mSendDataNotification(true) { + mSource->setListener(this); + + mMemoryDealer = new MemoryDealer(kNumBuffers * kBufferSize); + for (size_t i = 0; i < kNumBuffers; ++i) { + sp<IMemory> mem = mMemoryDealer->allocate(kBufferSize); + CHECK(mem != NULL); + + mBuffers.push(mem); + } + mSource->setBuffers(mBuffers); +} + +void NuPlayer::NuPlayerStreamListener::start() { + for (size_t i = 0; i < kNumBuffers; ++i) { + mSource->onBufferAvailable(i); + } +} + +void NuPlayer::NuPlayerStreamListener::queueBuffer(size_t index, size_t size) { + QueueEntry entry; + entry.mIsCommand = false; + entry.mIndex = index; + entry.mSize = size; + entry.mOffset = 0; + + Mutex::Autolock autoLock(mLock); + mQueue.push_back(entry); + + if (mSendDataNotification) { + mSendDataNotification = false; + + if (mTargetID != 0) { + (new AMessage(kWhatMoreDataQueued, mTargetID))->post(); + } + } +} + +void NuPlayer::NuPlayerStreamListener::issueCommand( + Command cmd, bool synchronous, const sp<AMessage> &extra) { + CHECK(!synchronous); + + QueueEntry entry; + entry.mIsCommand = true; + entry.mCommand = cmd; + entry.mExtra = extra; + + Mutex::Autolock autoLock(mLock); + mQueue.push_back(entry); + + if (mSendDataNotification) { + mSendDataNotification = false; + + if (mTargetID != 0) { + (new AMessage(kWhatMoreDataQueued, mTargetID))->post(); + } + } +} + +ssize_t NuPlayer::NuPlayerStreamListener::read( + void *data, size_t size, sp<AMessage> *extra) { + CHECK_GT(size, 0u); + + extra->clear(); + + Mutex::Autolock autoLock(mLock); + + if (mEOS) { + return 0; + } + + if (mQueue.empty()) { + mSendDataNotification = true; + + return -EWOULDBLOCK; + } + + QueueEntry *entry = &*mQueue.begin(); + + if (entry->mIsCommand) { + switch (entry->mCommand) { + case EOS: + { + mQueue.erase(mQueue.begin()); + entry = NULL; + + mEOS = true; + return 0; + } + + case DISCONTINUITY: + { + *extra = entry->mExtra; + + mQueue.erase(mQueue.begin()); + entry = NULL; + + return INFO_DISCONTINUITY; + } + + default: + TRESPASS(); + break; + } + } + + size_t copy = entry->mSize; + if (copy > size) { + copy = size; + } + + memcpy(data, + (const uint8_t *)mBuffers.editItemAt(entry->mIndex)->pointer() + + entry->mOffset, + copy); + + entry->mOffset += copy; + entry->mSize -= copy; + + if (entry->mSize == 0) { + mSource->onBufferAvailable(entry->mIndex); + mQueue.erase(mQueue.begin()); + entry = NULL; + } + + return copy; +} + +} // namespace android diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.h b/media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.h new file mode 100644 index 0000000..1874d80 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2010 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_STREAM_LISTENER_H_ + +#define NUPLAYER_STREAM_LISTENER_H_ + +#include "NuPlayer.h" + +#include <media/IStreamSource.h> + +namespace android { + +struct MemoryDealer; + +struct NuPlayer::NuPlayerStreamListener : public BnStreamListener { + NuPlayerStreamListener( + const sp<IStreamSource> &source, + ALooper::handler_id targetID); + + virtual void queueBuffer(size_t index, size_t size); + + virtual void issueCommand( + Command cmd, bool synchronous, const sp<AMessage> &extra); + + void start(); + ssize_t read(void *data, size_t size, sp<AMessage> *extra); + +private: + enum { + kNumBuffers = 8, + kBufferSize = 188 * 10 + }; + + struct QueueEntry { + bool mIsCommand; + + size_t mIndex; + size_t mSize; + size_t mOffset; + + Command mCommand; + sp<AMessage> mExtra; + }; + + Mutex mLock; + + sp<IStreamSource> mSource; + ALooper::handler_id mTargetID; + sp<MemoryDealer> mMemoryDealer; + Vector<sp<IMemory> > mBuffers; + List<QueueEntry> mQueue; + bool mEOS; + bool mSendDataNotification; + + DISALLOW_EVIL_CONSTRUCTORS(NuPlayerStreamListener); +}; + +} // namespace android + +#endif // NUPLAYER_STREAM_LISTENER_H_ diff --git a/media/libmediaplayerservice/nuplayer/RTSPSource.cpp b/media/libmediaplayerservice/nuplayer/RTSPSource.cpp new file mode 100644 index 0000000..4c65b65 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/RTSPSource.cpp @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2010 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 "RTSPSource" +#include <utils/Log.h> + +#include "RTSPSource.h" + +#include "AnotherPacketSource.h" +#include "MyHandler.h" + +#include <media/stagefright/MetaData.h> + +namespace android { + +NuPlayer::RTSPSource::RTSPSource( + const char *url, + const KeyedVector<String8, String8> *headers, + bool uidValid, + uid_t uid) + : mURL(url), + mUIDValid(uidValid), + mUID(uid), + mFlags(0), + mState(DISCONNECTED), + mFinalResult(OK), + mDisconnectReplyID(0), + mSeekGeneration(0) { + if (headers) { + mExtraHeaders = *headers; + + ssize_t index = + mExtraHeaders.indexOfKey(String8("x-hide-urls-from-log")); + + if (index >= 0) { + mFlags |= kFlagIncognito; + + mExtraHeaders.removeItemsAt(index); + } + } +} + +NuPlayer::RTSPSource::~RTSPSource() { + if (mLooper != NULL) { + mLooper->stop(); + } +} + +void NuPlayer::RTSPSource::start() { + if (mLooper == NULL) { + mLooper = new ALooper; + mLooper->setName("rtsp"); + mLooper->start(); + + mReflector = new AHandlerReflector<RTSPSource>(this); + mLooper->registerHandler(mReflector); + } + + CHECK(mHandler == NULL); + + sp<AMessage> notify = new AMessage(kWhatNotify, mReflector->id()); + + mHandler = new MyHandler(mURL.c_str(), notify, mUIDValid, mUID); + mLooper->registerHandler(mHandler); + + CHECK_EQ(mState, (int)DISCONNECTED); + mState = CONNECTING; + + mHandler->connect(); +} + +void NuPlayer::RTSPSource::stop() { + sp<AMessage> msg = new AMessage(kWhatDisconnect, mReflector->id()); + + sp<AMessage> dummy; + msg->postAndAwaitResponse(&dummy); +} + +status_t NuPlayer::RTSPSource::feedMoreTSData() { + return mFinalResult; +} + +sp<MetaData> NuPlayer::RTSPSource::getFormat(bool audio) { + sp<AnotherPacketSource> source = getSource(audio); + + if (source == NULL) { + return NULL; + } + + return source->getFormat(); +} + +status_t NuPlayer::RTSPSource::dequeueAccessUnit( + bool audio, sp<ABuffer> *accessUnit) { + sp<AnotherPacketSource> source = getSource(audio); + + if (source == NULL) { + return -EWOULDBLOCK; + } + + status_t finalResult; + if (!source->hasBufferAvailable(&finalResult)) { + return finalResult == OK ? -EWOULDBLOCK : finalResult; + } + + return source->dequeueAccessUnit(accessUnit); +} + +sp<AnotherPacketSource> NuPlayer::RTSPSource::getSource(bool audio) { + return audio ? mAudioTrack : mVideoTrack; +} + +status_t NuPlayer::RTSPSource::getDuration(int64_t *durationUs) { + *durationUs = 0ll; + + int64_t audioDurationUs; + if (mAudioTrack != NULL + && mAudioTrack->getFormat()->findInt64( + kKeyDuration, &audioDurationUs) + && audioDurationUs > *durationUs) { + *durationUs = audioDurationUs; + } + + int64_t videoDurationUs; + if (mVideoTrack != NULL + && mVideoTrack->getFormat()->findInt64( + kKeyDuration, &videoDurationUs) + && videoDurationUs > *durationUs) { + *durationUs = videoDurationUs; + } + + return OK; +} + +status_t NuPlayer::RTSPSource::seekTo(int64_t seekTimeUs) { + sp<AMessage> msg = new AMessage(kWhatPerformSeek, mReflector->id()); + msg->setInt32("generation", ++mSeekGeneration); + msg->setInt64("timeUs", seekTimeUs); + msg->post(200000ll); + + return OK; +} + +void NuPlayer::RTSPSource::performSeek(int64_t seekTimeUs) { + if (mState != CONNECTED) { + return; + } + + mState = SEEKING; + mHandler->seek(seekTimeUs); +} + +bool NuPlayer::RTSPSource::isSeekable() { + return true; +} + +void NuPlayer::RTSPSource::onMessageReceived(const sp<AMessage> &msg) { + if (msg->what() == kWhatDisconnect) { + uint32_t replyID; + CHECK(msg->senderAwaitsResponse(&replyID)); + + mDisconnectReplyID = replyID; + finishDisconnectIfPossible(); + return; + } else if (msg->what() == kWhatPerformSeek) { + int32_t generation; + CHECK(msg->findInt32("generation", &generation)); + + if (generation != mSeekGeneration) { + // obsolete. + return; + } + + int64_t seekTimeUs; + CHECK(msg->findInt64("timeUs", &seekTimeUs)); + + performSeek(seekTimeUs); + return; + } + + CHECK_EQ(msg->what(), (int)kWhatNotify); + + int32_t what; + CHECK(msg->findInt32("what", &what)); + + switch (what) { + case MyHandler::kWhatConnected: + onConnected(); + break; + + case MyHandler::kWhatDisconnected: + onDisconnected(msg); + break; + + case MyHandler::kWhatSeekDone: + { + mState = CONNECTED; + break; + } + + case MyHandler::kWhatAccessUnit: + { + size_t trackIndex; + CHECK(msg->findSize("trackIndex", &trackIndex)); + CHECK_LT(trackIndex, mTracks.size()); + + sp<ABuffer> accessUnit; + CHECK(msg->findBuffer("accessUnit", &accessUnit)); + + int32_t damaged; + if (accessUnit->meta()->findInt32("damaged", &damaged) + && damaged) { + ALOGI("dropping damaged access unit."); + break; + } + + TrackInfo *info = &mTracks.editItemAt(trackIndex); + + sp<AnotherPacketSource> source = info->mSource; + if (source != NULL) { + uint32_t rtpTime; + CHECK(accessUnit->meta()->findInt32("rtp-time", (int32_t *)&rtpTime)); + + if (!info->mNPTMappingValid) { + // This is a live stream, we didn't receive any normal + // playtime mapping. Assume the first packets correspond + // to time 0. + + ALOGV("This is a live stream, assuming time = 0"); + + info->mRTPTime = rtpTime; + info->mNormalPlaytimeUs = 0ll; + info->mNPTMappingValid = true; + } + + int64_t nptUs = + ((double)rtpTime - (double)info->mRTPTime) + / info->mTimeScale + * 1000000ll + + info->mNormalPlaytimeUs; + + accessUnit->meta()->setInt64("timeUs", nptUs); + + source->queueAccessUnit(accessUnit); + } + break; + } + + case MyHandler::kWhatEOS: + { + size_t trackIndex; + CHECK(msg->findSize("trackIndex", &trackIndex)); + CHECK_LT(trackIndex, mTracks.size()); + + int32_t finalResult; + CHECK(msg->findInt32("finalResult", &finalResult)); + CHECK_NE(finalResult, (status_t)OK); + + TrackInfo *info = &mTracks.editItemAt(trackIndex); + sp<AnotherPacketSource> source = info->mSource; + if (source != NULL) { + source->signalEOS(finalResult); + } + + break; + } + + case MyHandler::kWhatSeekDiscontinuity: + { + size_t trackIndex; + CHECK(msg->findSize("trackIndex", &trackIndex)); + CHECK_LT(trackIndex, mTracks.size()); + + TrackInfo *info = &mTracks.editItemAt(trackIndex); + sp<AnotherPacketSource> source = info->mSource; + if (source != NULL) { + source->queueDiscontinuity(ATSParser::DISCONTINUITY_SEEK, NULL); + } + + break; + } + + case MyHandler::kWhatNormalPlayTimeMapping: + { + size_t trackIndex; + CHECK(msg->findSize("trackIndex", &trackIndex)); + CHECK_LT(trackIndex, mTracks.size()); + + uint32_t rtpTime; + CHECK(msg->findInt32("rtpTime", (int32_t *)&rtpTime)); + + int64_t nptUs; + CHECK(msg->findInt64("nptUs", &nptUs)); + + TrackInfo *info = &mTracks.editItemAt(trackIndex); + info->mRTPTime = rtpTime; + info->mNormalPlaytimeUs = nptUs; + info->mNPTMappingValid = true; + break; + } + + default: + TRESPASS(); + } +} + +void NuPlayer::RTSPSource::onConnected() { + CHECK(mAudioTrack == NULL); + CHECK(mVideoTrack == NULL); + + size_t numTracks = mHandler->countTracks(); + for (size_t i = 0; i < numTracks; ++i) { + int32_t timeScale; + sp<MetaData> format = mHandler->getTrackFormat(i, &timeScale); + + const char *mime; + CHECK(format->findCString(kKeyMIMEType, &mime)); + + bool isAudio = !strncasecmp(mime, "audio/", 6); + bool isVideo = !strncasecmp(mime, "video/", 6); + + TrackInfo info; + info.mTimeScale = timeScale; + info.mRTPTime = 0; + info.mNormalPlaytimeUs = 0ll; + info.mNPTMappingValid = false; + + if ((isAudio && mAudioTrack == NULL) + || (isVideo && mVideoTrack == NULL)) { + sp<AnotherPacketSource> source = new AnotherPacketSource(format); + + if (isAudio) { + mAudioTrack = source; + } else { + mVideoTrack = source; + } + + info.mSource = source; + } + + mTracks.push(info); + } + + mState = CONNECTED; +} + +void NuPlayer::RTSPSource::onDisconnected(const sp<AMessage> &msg) { + status_t err; + CHECK(msg->findInt32("result", &err)); + CHECK_NE(err, (status_t)OK); + + mLooper->unregisterHandler(mHandler->id()); + mHandler.clear(); + + mState = DISCONNECTED; + mFinalResult = err; + + if (mDisconnectReplyID != 0) { + finishDisconnectIfPossible(); + } +} + +void NuPlayer::RTSPSource::finishDisconnectIfPossible() { + if (mState != DISCONNECTED) { + mHandler->disconnect(); + return; + } + + (new AMessage)->postReply(mDisconnectReplyID); + mDisconnectReplyID = 0; +} + +} // namespace android diff --git a/media/libmediaplayerservice/nuplayer/RTSPSource.h b/media/libmediaplayerservice/nuplayer/RTSPSource.h new file mode 100644 index 0000000..59d06ad --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/RTSPSource.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2010 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 RTSP_SOURCE_H_ + +#define RTSP_SOURCE_H_ + +#include "NuPlayerSource.h" + +#include <media/stagefright/foundation/AHandlerReflector.h> + +namespace android { + +struct ALooper; +struct AnotherPacketSource; +struct MyHandler; + +struct NuPlayer::RTSPSource : public NuPlayer::Source { + RTSPSource( + const char *url, + const KeyedVector<String8, String8> *headers, + bool uidValid = false, + uid_t uid = 0); + + virtual void start(); + virtual void stop(); + + virtual status_t feedMoreTSData(); + + virtual sp<MetaData> getFormat(bool audio); + virtual status_t dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit); + + virtual status_t getDuration(int64_t *durationUs); + virtual status_t seekTo(int64_t seekTimeUs); + virtual bool isSeekable(); + + void onMessageReceived(const sp<AMessage> &msg); + +protected: + virtual ~RTSPSource(); + +private: + enum { + kWhatNotify = 'noti', + kWhatDisconnect = 'disc', + kWhatPerformSeek = 'seek', + }; + + enum State { + DISCONNECTED, + CONNECTING, + CONNECTED, + SEEKING, + }; + + enum Flags { + // Don't log any URLs. + kFlagIncognito = 1, + }; + + struct TrackInfo { + sp<AnotherPacketSource> mSource; + + int32_t mTimeScale; + uint32_t mRTPTime; + int64_t mNormalPlaytimeUs; + bool mNPTMappingValid; + }; + + AString mURL; + KeyedVector<String8, String8> mExtraHeaders; + bool mUIDValid; + uid_t mUID; + uint32_t mFlags; + State mState; + status_t mFinalResult; + uint32_t mDisconnectReplyID; + + sp<ALooper> mLooper; + sp<AHandlerReflector<RTSPSource> > mReflector; + sp<MyHandler> mHandler; + + Vector<TrackInfo> mTracks; + sp<AnotherPacketSource> mAudioTrack; + sp<AnotherPacketSource> mVideoTrack; + + int32_t mSeekGeneration; + + sp<AnotherPacketSource> getSource(bool audio); + + void onConnected(); + void onDisconnected(const sp<AMessage> &msg); + void finishDisconnectIfPossible(); + + void performSeek(int64_t seekTimeUs); + + DISALLOW_EVIL_CONSTRUCTORS(RTSPSource); +}; + +} // namespace android + +#endif // RTSP_SOURCE_H_ diff --git a/media/libmediaplayerservice/nuplayer/StreamingSource.cpp b/media/libmediaplayerservice/nuplayer/StreamingSource.cpp new file mode 100644 index 0000000..7c9bc5e --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/StreamingSource.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2010 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 "StreamingSource" +#include <utils/Log.h> + +#include "StreamingSource.h" + +#include "ATSParser.h" +#include "AnotherPacketSource.h" +#include "NuPlayerStreamListener.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> + +namespace android { + +NuPlayer::StreamingSource::StreamingSource(const sp<IStreamSource> &source) + : mSource(source), + mFinalResult(OK) { +} + +NuPlayer::StreamingSource::~StreamingSource() { +} + +void NuPlayer::StreamingSource::start() { + mStreamListener = new NuPlayerStreamListener(mSource, 0); + mTSParser = new ATSParser(ATSParser::TS_TIMESTAMPS_ARE_ABSOLUTE); + + mStreamListener->start(); +} + +status_t NuPlayer::StreamingSource::feedMoreTSData() { + if (mFinalResult != OK) { + return mFinalResult; + } + + for (int32_t i = 0; i < 50; ++i) { + char buffer[188]; + sp<AMessage> extra; + ssize_t n = mStreamListener->read(buffer, sizeof(buffer), &extra); + + if (n == 0) { + ALOGI("input data EOS reached."); + mTSParser->signalEOS(ERROR_END_OF_STREAM); + mFinalResult = ERROR_END_OF_STREAM; + break; + } else if (n == INFO_DISCONTINUITY) { + int32_t type = ATSParser::DISCONTINUITY_SEEK; + + int32_t mask; + if (extra != NULL + && extra->findInt32( + IStreamListener::kKeyDiscontinuityMask, &mask)) { + if (mask == 0) { + ALOGE("Client specified an illegal discontinuity type."); + return ERROR_UNSUPPORTED; + } + + type = mask; + } + + mTSParser->signalDiscontinuity( + (ATSParser::DiscontinuityType)type, extra); + } else if (n < 0) { + CHECK_EQ(n, -EWOULDBLOCK); + break; + } else { + if (buffer[0] == 0x00) { + // XXX legacy + mTSParser->signalDiscontinuity( + buffer[1] == 0x00 + ? ATSParser::DISCONTINUITY_SEEK + : ATSParser::DISCONTINUITY_FORMATCHANGE, + extra); + } else { + status_t err = mTSParser->feedTSPacket(buffer, sizeof(buffer)); + + if (err != OK) { + ALOGE("TS Parser returned error %d", err); + + mTSParser->signalEOS(err); + mFinalResult = err; + break; + } + } + } + } + + return OK; +} + +sp<MetaData> NuPlayer::StreamingSource::getFormat(bool audio) { + ATSParser::SourceType type = + audio ? ATSParser::AUDIO : ATSParser::VIDEO; + + sp<AnotherPacketSource> source = + static_cast<AnotherPacketSource *>(mTSParser->getSource(type).get()); + + if (source == NULL) { + return NULL; + } + + return source->getFormat(); +} + +status_t NuPlayer::StreamingSource::dequeueAccessUnit( + bool audio, sp<ABuffer> *accessUnit) { + ATSParser::SourceType type = + audio ? ATSParser::AUDIO : ATSParser::VIDEO; + + sp<AnotherPacketSource> source = + static_cast<AnotherPacketSource *>(mTSParser->getSource(type).get()); + + if (source == NULL) { + return -EWOULDBLOCK; + } + + status_t finalResult; + if (!source->hasBufferAvailable(&finalResult)) { + return finalResult == OK ? -EWOULDBLOCK : finalResult; + } + + return source->dequeueAccessUnit(accessUnit); +} + +} // namespace android + diff --git a/media/libmediaplayerservice/nuplayer/StreamingSource.h b/media/libmediaplayerservice/nuplayer/StreamingSource.h new file mode 100644 index 0000000..ca00ef9 --- /dev/null +++ b/media/libmediaplayerservice/nuplayer/StreamingSource.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2010 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 STREAMING_SOURCE_H_ + +#define STREAMING_SOURCE_H_ + +#include "NuPlayer.h" +#include "NuPlayerSource.h" + +namespace android { + +struct ABuffer; +struct ATSParser; + +struct NuPlayer::StreamingSource : public NuPlayer::Source { + StreamingSource(const sp<IStreamSource> &source); + + virtual void start(); + + virtual status_t feedMoreTSData(); + + virtual sp<MetaData> getFormat(bool audio); + virtual status_t dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit); + +protected: + virtual ~StreamingSource(); + +private: + sp<IStreamSource> mSource; + status_t mFinalResult; + sp<NuPlayerStreamListener> mStreamListener; + sp<ATSParser> mTSParser; + + DISALLOW_EVIL_CONSTRUCTORS(StreamingSource); +}; + +} // namespace android + +#endif // STREAMING_SOURCE_H_ |
