summaryrefslogtreecommitdiffstats
path: root/media/libmediaplayerservice
diff options
context:
space:
mode:
authorAnatol Pomozov <anatol.pomozov@gmail.com>2012-03-28 09:12:55 -0700
committerAnatol Pomozov <anatol.pomozov@gmail.com>2012-03-28 12:02:47 -0700
commitb0b2b4d890cf3bfb274797a759642b4e733343d7 (patch)
tree12ad21cbad346f02d542aa4d672ffd76407d58a9 /media/libmediaplayerservice
parent51f8eec23a2bcc2cc190373cdd1195972d9b8804 (diff)
parent5a5491c17d74bd2c80cf451c6ddbba22d5d5f08a (diff)
downloadframeworks_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')
-rw-r--r--media/libmediaplayerservice/Android.mk53
-rw-r--r--media/libmediaplayerservice/Crypto.cpp65
-rw-r--r--media/libmediaplayerservice/Crypto.h57
-rw-r--r--media/libmediaplayerservice/MediaPlayerService.cpp2168
-rw-r--r--media/libmediaplayerservice/MediaPlayerService.h430
-rw-r--r--media/libmediaplayerservice/MediaRecorderClient.cpp336
-rw-r--r--media/libmediaplayerservice/MediaRecorderClient.h76
-rw-r--r--media/libmediaplayerservice/MetadataRetrieverClient.cpp254
-rw-r--r--media/libmediaplayerservice/MetadataRetrieverClient.h73
-rw-r--r--media/libmediaplayerservice/MidiFile.cpp552
-rw-r--r--media/libmediaplayerservice/MidiFile.h113
-rw-r--r--media/libmediaplayerservice/MidiMetadataRetriever.cpp92
-rw-r--r--media/libmediaplayerservice/MidiMetadataRetriever.h51
-rw-r--r--media/libmediaplayerservice/StagefrightPlayer.cpp220
-rw-r--r--media/libmediaplayerservice/StagefrightPlayer.h76
-rw-r--r--media/libmediaplayerservice/StagefrightRecorder.cpp1755
-rw-r--r--media/libmediaplayerservice/StagefrightRecorder.h194
-rw-r--r--media/libmediaplayerservice/TestPlayerStub.cpp196
-rw-r--r--media/libmediaplayerservice/TestPlayerStub.h127
-rw-r--r--media/libmediaplayerservice/nuplayer/Android.mk27
-rw-r--r--media/libmediaplayerservice/nuplayer/GenericSource.cpp265
-rw-r--r--media/libmediaplayerservice/nuplayer/GenericSource.h79
-rw-r--r--media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp189
-rw-r--r--media/libmediaplayerservice/nuplayer/HTTPLiveSource.h72
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayer.cpp905
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayer.h152
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp299
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerDecoder.h67
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp337
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerDriver.h107
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp658
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerRenderer.h133
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerSource.h64
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.cpp164
-rw-r--r--media/libmediaplayerservice/nuplayer/NuPlayerStreamListener.h74
-rw-r--r--media/libmediaplayerservice/nuplayer/RTSPSource.cpp387
-rw-r--r--media/libmediaplayerservice/nuplayer/RTSPSource.h115
-rw-r--r--media/libmediaplayerservice/nuplayer/StreamingSource.cpp145
-rw-r--r--media/libmediaplayerservice/nuplayer/StreamingSource.h53
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(), &degrees)) {
+ 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 &params) {
+ 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> &notify,
+ 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> &notify,
+ 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> &notify)
+ : 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> &notifyConsumed) {
+ 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", &notifyConsumed));
+
+ 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", &notifyConsumed)) {
+ 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> &notify);
+
+ void queueBuffer(
+ bool audio,
+ const sp<ABuffer> &buffer,
+ const sp<AMessage> &notifyConsumed);
+
+ 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_