Merge "Camera: add buffer size to Stream@3.4"
diff --git a/camera/CaptureResult.cpp b/camera/CaptureResult.cpp
index e6c0d00..928a6bc 100644
--- a/camera/CaptureResult.cpp
+++ b/camera/CaptureResult.cpp
@@ -60,6 +60,39 @@
return OK;
}
+status_t PhysicalCaptureResultInfo::readFromParcel(const android::Parcel* parcel) {
+ status_t res;
+
+ mPhysicalCameraId.remove(mPhysicalCameraId.size());
+ mPhysicalCameraMetadata.clear();
+
+ if ((res = parcel->readString16(&mPhysicalCameraId)) != OK) {
+ ALOGE("%s: Failed to read camera id: %d", __FUNCTION__, res);
+ return res;
+ }
+
+ if ((res = mPhysicalCameraMetadata.readFromParcel(parcel)) != OK) {
+ ALOGE("%s: Failed to read metadata from parcel: %d", __FUNCTION__, res);
+ return res;
+ }
+ return OK;
+}
+
+status_t PhysicalCaptureResultInfo::writeToParcel(android::Parcel* parcel) const {
+ status_t res;
+ if ((res = parcel->writeString16(mPhysicalCameraId)) != OK) {
+ ALOGE("%s: Failed to write physical camera ID to parcel: %d",
+ __FUNCTION__, res);
+ return res;
+ }
+ if ((res = mPhysicalCameraMetadata.writeToParcel(parcel)) != OK) {
+ ALOGE("%s: Failed to write physical camera metadata to parcel: %d",
+ __FUNCTION__, res);
+ return res;
+ }
+ return OK;
+}
+
CaptureResult::CaptureResult() :
mMetadata(), mResultExtras() {
}
@@ -67,6 +100,7 @@
CaptureResult::CaptureResult(const CaptureResult &otherResult) {
mResultExtras = otherResult.mResultExtras;
mMetadata = otherResult.mMetadata;
+ mPhysicalMetadatas = otherResult.mPhysicalMetadatas;
}
status_t CaptureResult::readFromParcel(android::Parcel *parcel) {
@@ -79,6 +113,7 @@
}
mMetadata.clear();
+ mPhysicalMetadatas.clear();
status_t res = OK;
res = mMetadata.readFromParcel(parcel);
@@ -89,6 +124,34 @@
}
ALOGV("%s: Read metadata from parcel", __FUNCTION__);
+ int32_t physicalMetadataCount;
+ if ((res = parcel->readInt32(&physicalMetadataCount)) != OK) {
+ ALOGE("%s: Failed to read the physical metadata count from parcel: %d", __FUNCTION__, res);
+ return res;
+ }
+ if (physicalMetadataCount < 0) {
+ ALOGE("%s: Invalid physical metadata count from parcel: %d",
+ __FUNCTION__, physicalMetadataCount);
+ return BAD_VALUE;
+ }
+
+ for (int32_t i = 0; i < physicalMetadataCount; i++) {
+ String16 cameraId;
+ if ((res = parcel->readString16(&cameraId)) != OK) {
+ ALOGE("%s: Failed to read camera id: %d", __FUNCTION__, res);
+ return res;
+ }
+
+ CameraMetadata physicalMetadata;
+ if ((res = physicalMetadata.readFromParcel(parcel)) != OK) {
+ ALOGE("%s: Failed to read metadata from parcel: %d", __FUNCTION__, res);
+ return res;
+ }
+
+ mPhysicalMetadatas.emplace(mPhysicalMetadatas.end(), cameraId, physicalMetadata);
+ }
+ ALOGV("%s: Read physical metadata from parcel", __FUNCTION__);
+
res = mResultExtras.readFromParcel(parcel);
if (res != OK) {
ALOGE("%s: Failed to read result extras from parcel.",
@@ -118,6 +181,27 @@
}
ALOGV("%s: Wrote metadata to parcel", __FUNCTION__);
+ int32_t physicalMetadataCount = static_cast<int32_t>(mPhysicalMetadatas.size());
+ res = parcel->writeInt32(physicalMetadataCount);
+ if (res != OK) {
+ ALOGE("%s: Failed to write physical metadata count to parcel: %d",
+ __FUNCTION__, res);
+ return BAD_VALUE;
+ }
+ for (const auto& physicalMetadata : mPhysicalMetadatas) {
+ if ((res = parcel->writeString16(physicalMetadata.mPhysicalCameraId)) != OK) {
+ ALOGE("%s: Failed to write physical camera ID to parcel: %d",
+ __FUNCTION__, res);
+ return res;
+ }
+ if ((res = physicalMetadata.mPhysicalCameraMetadata.writeToParcel(parcel)) != OK) {
+ ALOGE("%s: Failed to write physical camera metadata to parcel: %d",
+ __FUNCTION__, res);
+ return res;
+ }
+ }
+ ALOGV("%s: Wrote physical camera metadata to parcel", __FUNCTION__);
+
res = mResultExtras.writeToParcel(parcel);
if (res != OK) {
ALOGE("%s: Failed to write result extras to parcel", __FUNCTION__);
diff --git a/camera/aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl b/camera/aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl
index 4db7f85..58b19a3 100644
--- a/camera/aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl
+++ b/camera/aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl
@@ -18,6 +18,7 @@
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
+import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
/** @hide */
interface ICameraDeviceCallbacks
@@ -36,7 +37,8 @@
oneway void onDeviceIdle();
oneway void onCaptureStarted(in CaptureResultExtras resultExtras, long timestamp);
oneway void onResultReceived(in CameraMetadataNative result,
- in CaptureResultExtras resultExtras);
+ in CaptureResultExtras resultExtras,
+ in PhysicalCaptureResultInfo[] physicalCaptureResultInfos);
oneway void onPrepared(int streamId);
/**
diff --git a/camera/aidl/android/hardware/camera2/impl/PhysicalCaptureResultInfo.aidl b/camera/aidl/android/hardware/camera2/impl/PhysicalCaptureResultInfo.aidl
new file mode 100644
index 0000000..78d9b7b
--- /dev/null
+++ b/camera/aidl/android/hardware/camera2/impl/PhysicalCaptureResultInfo.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+package android.hardware.camera2.impl;
+
+/** @hide */
+parcelable PhysicalCaptureResultInfo cpp_header "camera/CaptureResult.h";
diff --git a/camera/include/camera/CaptureResult.h b/camera/include/camera/CaptureResult.h
index 917d953..56fa178 100644
--- a/camera/include/camera/CaptureResult.h
+++ b/camera/include/camera/CaptureResult.h
@@ -91,14 +91,36 @@
virtual status_t readFromParcel(const android::Parcel* parcel) override;
virtual status_t writeToParcel(android::Parcel* parcel) const override;
};
+
+struct PhysicalCaptureResultInfo : public android::Parcelable {
+
+ PhysicalCaptureResultInfo()
+ : mPhysicalCameraId(),
+ mPhysicalCameraMetadata() {
+ }
+ PhysicalCaptureResultInfo(const String16& cameraId,
+ const CameraMetadata& cameraMetadata)
+ : mPhysicalCameraId(cameraId),
+ mPhysicalCameraMetadata(cameraMetadata) {
+ }
+
+ String16 mPhysicalCameraId;
+ CameraMetadata mPhysicalCameraMetadata;
+
+ virtual status_t readFromParcel(const android::Parcel* parcel) override;
+ virtual status_t writeToParcel(android::Parcel* parcel) const override;
+};
+
} // namespace impl
} // namespace camera2
} // namespace hardware
using hardware::camera2::impl::CaptureResultExtras;
+using hardware::camera2::impl::PhysicalCaptureResultInfo;
struct CaptureResult : public virtual LightRefBase<CaptureResult> {
CameraMetadata mMetadata;
+ std::vector<PhysicalCaptureResultInfo> mPhysicalMetadatas;
CaptureResultExtras mResultExtras;
CaptureResult();
diff --git a/camera/ndk/impl/ACameraDevice.cpp b/camera/ndk/impl/ACameraDevice.cpp
index ef1c61f..907debc 100644
--- a/camera/ndk/impl/ACameraDevice.cpp
+++ b/camera/ndk/impl/ACameraDevice.cpp
@@ -1406,7 +1406,9 @@
binder::Status
CameraDevice::ServiceCallback::onResultReceived(
const CameraMetadata& metadata,
- const CaptureResultExtras& resultExtras) {
+ const CaptureResultExtras& resultExtras,
+ const std::vector<PhysicalCaptureResultInfo>& physicalResultInfos) {
+ (void) physicalResultInfos;
binder::Status ret = binder::Status::ok();
sp<CameraDevice> dev = mDevice.promote();
diff --git a/camera/ndk/impl/ACameraDevice.h b/camera/ndk/impl/ACameraDevice.h
index 1db3dfb..1369148 100644
--- a/camera/ndk/impl/ACameraDevice.h
+++ b/camera/ndk/impl/ACameraDevice.h
@@ -74,7 +74,8 @@
binder::Status onCaptureStarted(const CaptureResultExtras& resultExtras,
int64_t timestamp) override;
binder::Status onResultReceived(const CameraMetadata& metadata,
- const CaptureResultExtras& resultExtras) override;
+ const CaptureResultExtras& resultExtras,
+ const std::vector<PhysicalCaptureResultInfo>& physicalResultInfos) override;
binder::Status onPrepared(int streamId) override;
binder::Status onRequestQueueEmpty() override;
binder::Status onRepeatingRequestError(int64_t lastFrameNumber,
diff --git a/camera/tests/CameraBinderTests.cpp b/camera/tests/CameraBinderTests.cpp
index 24c0c51..1de7013 100644
--- a/camera/tests/CameraBinderTests.cpp
+++ b/camera/tests/CameraBinderTests.cpp
@@ -198,9 +198,11 @@
virtual binder::Status onResultReceived(const CameraMetadata& metadata,
- const CaptureResultExtras& resultExtras) {
+ const CaptureResultExtras& resultExtras,
+ const std::vector<PhysicalCaptureResultInfo>& physicalResultInfos) {
(void) metadata;
(void) resultExtras;
+ (void) physicalResultInfos;
Mutex::Autolock l(mLock);
mLastStatus = SENT_RESULT;
mStatusesHit.push_back(mLastStatus);
diff --git a/cmds/screenrecord/screenrecord.cpp b/cmds/screenrecord/screenrecord.cpp
index f9e4639..46bd8f0 100644
--- a/cmds/screenrecord/screenrecord.cpp
+++ b/cmds/screenrecord/screenrecord.cpp
@@ -73,6 +73,7 @@
static enum {
FORMAT_MP4, FORMAT_H264, FORMAT_FRAMES, FORMAT_RAW_FRAMES
} gOutputFormat = FORMAT_MP4; // data format for output
+static AString gCodecName = ""; // codec name override
static bool gSizeSpecified = false; // was size explicitly requested?
static bool gWantInfoScreen = false; // do we want initial info screen?
static bool gWantFrameTime = false; // do we want times on each frame?
@@ -154,6 +155,7 @@
if (gVerbose) {
printf("Configuring recorder for %dx%d %s at %.2fMbps\n",
gVideoWidth, gVideoHeight, kMimeTypeAvc, gBitRate / 1000000.0);
+ fflush(stdout);
}
sp<AMessage> format = new AMessage;
@@ -169,11 +171,21 @@
looper->setName("screenrecord_looper");
looper->start();
ALOGV("Creating codec");
- sp<MediaCodec> codec = MediaCodec::CreateByType(looper, kMimeTypeAvc, true);
- if (codec == NULL) {
- fprintf(stderr, "ERROR: unable to create %s codec instance\n",
- kMimeTypeAvc);
- return UNKNOWN_ERROR;
+ sp<MediaCodec> codec;
+ if (gCodecName.empty()) {
+ codec = MediaCodec::CreateByType(looper, kMimeTypeAvc, true);
+ if (codec == NULL) {
+ fprintf(stderr, "ERROR: unable to create %s codec instance\n",
+ kMimeTypeAvc);
+ return UNKNOWN_ERROR;
+ }
+ } else {
+ codec = MediaCodec::CreateByComponentName(looper, gCodecName);
+ if (codec == NULL) {
+ fprintf(stderr, "ERROR: unable to create %s codec instance\n",
+ gCodecName.c_str());
+ return UNKNOWN_ERROR;
+ }
}
err = codec->configure(format, NULL, NULL,
@@ -275,9 +287,11 @@
if (gRotate) {
printf("Rotated content area is %ux%u at offset x=%d y=%d\n",
outHeight, outWidth, offY, offX);
+ fflush(stdout);
} else {
printf("Content area is %ux%u at offset x=%d y=%d\n",
outWidth, outHeight, offX, offY);
+ fflush(stdout);
}
}
@@ -346,6 +360,7 @@
if (systemTime(CLOCK_MONOTONIC) > endWhenNsec) {
if (gVerbose) {
printf("Time limit reached\n");
+ fflush(stdout);
}
break;
}
@@ -483,6 +498,7 @@
printf("Encoder stopping; recorded %u frames in %" PRId64 " seconds\n",
debugNumFrames, nanoseconds_to_seconds(
systemTime(CLOCK_MONOTONIC) - startWhenNsec));
+ fflush(stdout);
}
return NO_ERROR;
}
@@ -556,6 +572,7 @@
printf("Main display is %dx%d @%.2ffps (orientation=%u)\n",
mainDpyInfo.w, mainDpyInfo.h, mainDpyInfo.fps,
mainDpyInfo.orientation);
+ fflush(stdout);
}
bool rotated = isDeviceRotated(mainDpyInfo.orientation);
@@ -623,6 +640,7 @@
}
if (gVerbose) {
printf("Bugreport overlay created\n");
+ fflush(stdout);
}
} else {
// Use the encoder's input surface as the virtual display surface.
@@ -715,6 +733,7 @@
if (gVerbose) {
printf("Stopping encoder and muxer\n");
+ fflush(stdout);
}
}
@@ -761,6 +780,7 @@
printf(" %s", argv[i]);
}
putchar('\n');
+ fflush(stdout);
}
pid_t pid = fork();
@@ -898,6 +918,7 @@
{ "show-frame-time", no_argument, NULL, 'f' },
{ "rotate", no_argument, NULL, 'r' },
{ "output-format", required_argument, NULL, 'o' },
+ { "codec-name", required_argument, NULL, 'N' },
{ "monotonic-time", no_argument, NULL, 'm' },
{ NULL, 0, NULL, 0 }
};
@@ -978,6 +999,9 @@
return 2;
}
break;
+ case 'N':
+ gCodecName = optarg;
+ break;
case 'm':
gMonotonicTime = true;
break;
diff --git a/drm/libmediadrm/Android.bp b/drm/libmediadrm/Android.bp
index 2e8da4b..ea239c5 100644
--- a/drm/libmediadrm/Android.bp
+++ b/drm/libmediadrm/Android.bp
@@ -18,6 +18,7 @@
"PluginMetricsReporting.cpp",
"SharedLibrary.cpp",
"DrmHal.cpp",
+ "DrmMetrics.cpp",
"CryptoHal.cpp",
"protos/plugin_metrics.proto",
],
diff --git a/drm/libmediadrm/DrmHal.cpp b/drm/libmediadrm/DrmHal.cpp
index c4b197c..b5c1ddf 100644
--- a/drm/libmediadrm/DrmHal.cpp
+++ b/drm/libmediadrm/DrmHal.cpp
@@ -30,6 +30,7 @@
#include <media/DrmHal.h>
#include <media/DrmSessionClientInterface.h>
#include <media/DrmSessionManager.h>
+#include <media/EventMetric.h>
#include <media/PluginMetricsReporting.h>
#include <media/drm/DrmAPI.h>
#include <media/stagefright/foundation/ADebug.h>
@@ -38,12 +39,13 @@
#include <media/stagefright/MediaErrors.h>
using drm::V1_0::KeyedVector;
-using drm::V1_0::KeyRequestType;
using drm::V1_0::KeyStatusType;
using drm::V1_0::KeyType;
using drm::V1_0::KeyValue;
using drm::V1_1::HdcpLevel;;
using drm::V1_0::SecureStop;
+using drm::V1_1::SecureStopRelease;
+using drm::V1_0::SecureStopId;
using drm::V1_1::SecurityLevel;
using drm::V1_0::Status;
using ::android::hardware::hidl_array;
@@ -54,6 +56,14 @@
using ::android::hidl::manager::V1_0::IServiceManager;
using ::android::sp;
+namespace {
+
+// This constant corresponds to the PROPERTY_DEVICE_UNIQUE_ID constant
+// in the MediaDrm API.
+constexpr char kPropertyDeviceUniqueId[] = "deviceUniqueId";
+
+}
+
namespace android {
#define INIT_CHECK() {if (mInitCheck != OK) return mInitCheck;}
@@ -157,6 +167,15 @@
return secureStops;
}
+static List<Vector<uint8_t>> toSecureStopIds(const hidl_vec<SecureStopId>&
+ hSecureStopIds) {
+ List<Vector<uint8_t>> secureStopIds;
+ for (size_t i = 0; i < hSecureStopIds.size(); i++) {
+ secureStopIds.push_back(toVector(hSecureStopIds[i]));
+ }
+ return secureStopIds;
+}
+
static status_t toStatusT(Status status) {
switch (status) {
case Status::OK:
@@ -233,9 +252,9 @@
void DrmHal::closeOpenSessions() {
if (mPlugin != NULL) {
- for (size_t i = 0; i < mOpenSessions.size(); i++) {
- mPlugin->closeSession(toHidlVec(mOpenSessions[i]));
- DrmSessionManager::Instance()->removeSession(mOpenSessions[i]);
+ auto openSessions = mOpenSessions;
+ for (size_t i = 0; i < openSessions.size(); i++) {
+ closeSession(openSessions[i]);
}
}
mOpenSessions.clear();
@@ -320,6 +339,7 @@
Return<void> DrmHal::sendEvent(EventType hEventType,
const hidl_vec<uint8_t>& sessionId, const hidl_vec<uint8_t>& data) {
+ mMetrics.mEventCounter.Increment(hEventType);
mEventLock.lock();
sp<IDrmClient> listener = mListener;
@@ -410,12 +430,21 @@
break;
}
obj.writeInt32(type);
+ mMetrics.mKeyStatusChangeCounter.Increment(keyStatus.type);
}
obj.writeInt32(hasNewUsableKey);
Mutex::Autolock lock(mNotifyLock);
listener->notify(DrmPlugin::kDrmPluginEventKeysChange, 0, &obj);
+ } else {
+ // There's no listener. But we still want to count the key change
+ // events.
+ size_t nKeys = keyStatusList.size();
+ for (size_t i = 0; i < nKeys; i++) {
+ mMetrics.mKeyStatusChangeCounter.Increment(keyStatusList[i].type);
+ }
}
+
return Void();
}
@@ -518,6 +547,8 @@
mDrmSessionClient, sessionId);
mOpenSessions.push(sessionId);
}
+
+ mMetrics.mOpenSessionCounter.Increment(err);
return err;
}
@@ -537,8 +568,11 @@
}
}
reportMetrics();
- return toStatusT(status);
+ status_t response = toStatusT(status);
+ mMetrics.mCloseSessionCounter.Increment(response);
+ return response;
}
+ mMetrics.mCloseSessionCounter.Increment(DEAD_OBJECT);
return DEAD_OBJECT;
}
@@ -549,6 +583,7 @@
String8 &defaultUrl, DrmPlugin::KeyRequestType *keyRequestType) {
Mutex::Autolock autoLock(mLock);
INIT_CHECK();
+ EventTimer<status_t> keyRequestTimer(&mMetrics.mGetKeyRequestTiming);
DrmSessionManager::Instance()->useSession(sessionId);
@@ -560,6 +595,7 @@
} else if (keyType == DrmPlugin::kKeyType_Release) {
hKeyType = KeyType::RELEASE;
} else {
+ keyRequestTimer.SetAttribute(BAD_VALUE);
return BAD_VALUE;
}
@@ -567,23 +603,63 @@
status_t err = UNKNOWN_ERROR;
+ if (mPluginV1_1 != NULL) {
+ Return<void> hResult =
+ mPluginV1_1->getKeyRequest_1_1(
+ toHidlVec(sessionId), toHidlVec(initData),
+ toHidlString(mimeType), hKeyType, hOptionalParameters,
+ [&](Status status, const hidl_vec<uint8_t>& hRequest,
+ drm::V1_1::KeyRequestType hKeyRequestType,
+ const hidl_string& hDefaultUrl) {
+
+ if (status == Status::OK) {
+ request = toVector(hRequest);
+ defaultUrl = toString8(hDefaultUrl);
+
+ switch (hKeyRequestType) {
+ case drm::V1_1::KeyRequestType::INITIAL:
+ *keyRequestType = DrmPlugin::kKeyRequestType_Initial;
+ break;
+ case drm::V1_1::KeyRequestType::RENEWAL:
+ *keyRequestType = DrmPlugin::kKeyRequestType_Renewal;
+ break;
+ case drm::V1_1::KeyRequestType::RELEASE:
+ *keyRequestType = DrmPlugin::kKeyRequestType_Release;
+ break;
+ case drm::V1_1::KeyRequestType::NONE:
+ *keyRequestType = DrmPlugin::kKeyRequestType_None;
+ break;
+ case drm::V1_1::KeyRequestType::UPDATE:
+ *keyRequestType = DrmPlugin::kKeyRequestType_Update;
+ break;
+ default:
+ *keyRequestType = DrmPlugin::kKeyRequestType_Unknown;
+ break;
+ }
+ err = toStatusT(status);
+ }
+ });
+ return hResult.isOk() ? err : DEAD_OBJECT;
+ }
+
Return<void> hResult = mPlugin->getKeyRequest(toHidlVec(sessionId),
toHidlVec(initData), toHidlString(mimeType), hKeyType, hOptionalParameters,
[&](Status status, const hidl_vec<uint8_t>& hRequest,
- KeyRequestType hKeyRequestType, const hidl_string& hDefaultUrl) {
+ drm::V1_0::KeyRequestType hKeyRequestType,
+ const hidl_string& hDefaultUrl) {
if (status == Status::OK) {
request = toVector(hRequest);
defaultUrl = toString8(hDefaultUrl);
switch (hKeyRequestType) {
- case KeyRequestType::INITIAL:
+ case drm::V1_0::KeyRequestType::INITIAL:
*keyRequestType = DrmPlugin::kKeyRequestType_Initial;
break;
- case KeyRequestType::RENEWAL:
+ case drm::V1_0::KeyRequestType::RENEWAL:
*keyRequestType = DrmPlugin::kKeyRequestType_Renewal;
break;
- case KeyRequestType::RELEASE:
+ case drm::V1_0::KeyRequestType::RELEASE:
*keyRequestType = DrmPlugin::kKeyRequestType_Release;
break;
default:
@@ -594,12 +670,16 @@
}
});
- return hResult.isOk() ? err : DEAD_OBJECT;
+ err = hResult.isOk() ? err : DEAD_OBJECT;
+ keyRequestTimer.SetAttribute(err);
+ return err;
}
status_t DrmHal::provideKeyResponse(Vector<uint8_t> const &sessionId,
Vector<uint8_t> const &response, Vector<uint8_t> &keySetId) {
Mutex::Autolock autoLock(mLock);
+ EventTimer<status_t> keyResponseTimer(&mMetrics.mProvideKeyResponseTiming);
+
INIT_CHECK();
DrmSessionManager::Instance()->useSession(sessionId);
@@ -615,8 +695,9 @@
err = toStatusT(status);
}
);
-
- return hResult.isOk() ? err : DEAD_OBJECT;
+ err = hResult.isOk() ? err : DEAD_OBJECT;
+ keyResponseTimer.SetAttribute(err);
+ return err;
}
status_t DrmHal::removeKeys(Vector<uint8_t> const &keySetId) {
@@ -680,7 +761,9 @@
}
);
- return hResult.isOk() ? err : DEAD_OBJECT;
+ err = hResult.isOk() ? err : DEAD_OBJECT;
+ mMetrics.mGetProvisionRequestCounter.Increment(err);
+ return err;
}
status_t DrmHal::provideProvisionResponse(Vector<uint8_t> const &response,
@@ -701,7 +784,9 @@
}
);
- return hResult.isOk() ? err : DEAD_OBJECT;
+ err = hResult.isOk() ? err : DEAD_OBJECT;
+ mMetrics.mProvideProvisionResponseCounter.Increment(err);
+ return err;
}
status_t DrmHal::getSecureStops(List<Vector<uint8_t>> &secureStops) {
@@ -723,6 +808,32 @@
}
+status_t DrmHal::getSecureStopIds(List<Vector<uint8_t>> &secureStopIds) {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mInitCheck != OK) {
+ return mInitCheck;
+ }
+
+ if (mPluginV1_1 == NULL) {
+ return ERROR_DRM_CANNOT_HANDLE;
+ }
+
+ status_t err = UNKNOWN_ERROR;
+
+ Return<void> hResult = mPluginV1_1->getSecureStopIds(
+ [&](Status status, const hidl_vec<SecureStopId>& hSecureStopIds) {
+ if (status == Status::OK) {
+ secureStopIds = toSecureStopIds(hSecureStopIds);
+ }
+ err = toStatusT(status);
+ }
+ );
+
+ return hResult.isOk() ? err : DEAD_OBJECT;
+}
+
+
status_t DrmHal::getSecureStop(Vector<uint8_t> const &ssid, Vector<uint8_t> &secureStop) {
Mutex::Autolock autoLock(mLock);
INIT_CHECK();
@@ -745,13 +856,36 @@
Mutex::Autolock autoLock(mLock);
INIT_CHECK();
+ if (mPluginV1_1 != NULL) {
+ SecureStopRelease secureStopRelease;
+ secureStopRelease.opaqueData = toHidlVec(ssRelease);
+ return toStatusT(mPluginV1_1->releaseSecureStops(secureStopRelease));
+ }
+
return toStatusT(mPlugin->releaseSecureStop(toHidlVec(ssRelease)));
}
-status_t DrmHal::releaseAllSecureStops() {
+status_t DrmHal::removeSecureStop(Vector<uint8_t> const &ssid) {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mInitCheck != OK) {
+ return mInitCheck;
+ }
+
+ if (mPluginV1_1 == NULL) {
+ return ERROR_DRM_CANNOT_HANDLE;
+ }
+
+ return toStatusT(mPluginV1_1->removeSecureStop(toHidlVec(ssid)));
+}
+
+status_t DrmHal::removeAllSecureStops() {
Mutex::Autolock autoLock(mLock);
INIT_CHECK();
+ if (mPluginV1_1 != NULL) {
+ return toStatusT(mPluginV1_1->removeAllSecureStops());
+ }
return toStatusT(mPlugin->releaseAllSecureStops());
}
@@ -923,7 +1057,11 @@
}
);
- return hResult.isOk() ? err : DEAD_OBJECT;
+ err = hResult.isOk() ? err : DEAD_OBJECT;
+ if (name == kPropertyDeviceUniqueId) {
+ mMetrics.mGetDeviceUniqueIdCounter.Increment(err);
+ }
+ return err;
}
status_t DrmHal::setPropertyString(String8 const &name, String8 const &value ) const {
@@ -945,9 +1083,12 @@
return toStatusT(status);
}
-status_t DrmHal::getMetrics(MediaAnalyticsItem* metrics) {
- // TODO: Replace this with real metrics.
- metrics->setCString("/drm/mediadrm/dummymetric", "dummy");
+status_t DrmHal::getMetrics(MediaAnalyticsItem* item) {
+ if (item == nullptr) {
+ return UNEXPECTED_NULL;
+ }
+
+ mMetrics.Export(item);
return OK;
}
diff --git a/drm/libmediadrm/DrmMetrics.cpp b/drm/libmediadrm/DrmMetrics.cpp
new file mode 100644
index 0000000..258c4b0
--- /dev/null
+++ b/drm/libmediadrm/DrmMetrics.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2018 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 <android-base/macros.h>
+#include <media/DrmMetrics.h>
+
+using ::android::hardware::drm::V1_0::EventType;
+using ::android::hardware::drm::V1_0::KeyStatusType;
+
+namespace {
+
+template<typename T>
+std::string GetAttributeName(T type);
+
+template<>
+std::string GetAttributeName<KeyStatusType>(KeyStatusType type) {
+ static const char* type_names[] = {
+ "USABLE", "EXPIRED", "OUTPUT_NOT_ALLOWED",
+ "STATUS_PENDING", "INTERNAL_ERROR" };
+ if (((size_t) type) > arraysize(type_names)) {
+ return "UNKNOWN_TYPE";
+ }
+ return type_names[(size_t) type];
+}
+
+template<>
+std::string GetAttributeName<EventType>(EventType type) {
+ static const char* type_names[] = {
+ "PROVISION_REQUIRED", "KEY_NEEDED", "KEY_EXPIRED",
+ "VENDOR_DEFINED", "SESSION_RECLAIMED" };
+ if (((size_t) type) > arraysize(type_names)) {
+ return "UNKNOWN_TYPE";
+ }
+ return type_names[(size_t) type];
+}
+
+template<typename T>
+void ExportCounterMetric(const android::CounterMetric<T>& counter,
+ android::MediaAnalyticsItem* item) {
+ if (!item) {
+ ALOGE("item was unexpectedly null.");
+ return;
+ }
+ std::string success_count_name = counter.metric_name() + ".ok.count";
+ std::string error_count_name = counter.metric_name() + ".error.count";
+ counter.ExportValues(
+ [&] (const android::status_t status, const int64_t value) {
+ if (status == android::OK) {
+ item->setInt64(success_count_name.c_str(), value);
+ } else {
+ int64_t total_errors(0);
+ item->getInt64(error_count_name.c_str(), &total_errors);
+ item->setInt64(error_count_name.c_str(), total_errors + value);
+ // TODO: Add support for exporting the list of error values.
+ // This probably needs to be added to MediaAnalyticsItem.
+ }
+ });
+}
+
+template<typename T>
+void ExportCounterMetricWithAttributeNames(
+ const android::CounterMetric<T>& counter,
+ android::MediaAnalyticsItem* item) {
+ if (!item) {
+ ALOGE("item was unexpectedly null.");
+ return;
+ }
+ counter.ExportValues(
+ [&] (const T& attribute, const int64_t value) {
+ std::string name = counter.metric_name()
+ + "." + GetAttributeName(attribute) + ".count";
+ item->setInt64(name.c_str(), value);
+ });
+}
+
+template<typename T>
+void ExportEventMetric(const android::EventMetric<T>& event,
+ android::MediaAnalyticsItem* item) {
+ if (!item) {
+ ALOGE("item was unexpectedly null.");
+ return;
+ }
+ std::string success_count_name = event.metric_name() + ".ok.count";
+ std::string error_count_name = event.metric_name() + ".error.count";
+ std::string timing_name = event.metric_name() + ".ok.average_time_micros";
+ event.ExportValues(
+ [&] (const android::status_t& status,
+ const android::EventStatistics& value) {
+ if (status == android::OK) {
+ item->setInt64(success_count_name.c_str(), value.count);
+ item->setInt64(timing_name.c_str(), value.mean);
+ } else {
+ int64_t total_errors(0);
+ item->getInt64(error_count_name.c_str(), &total_errors);
+ item->setInt64(error_count_name.c_str(),
+ total_errors + value.count);
+ // TODO: Add support for exporting the list of error values.
+ // This probably needs to be added to MediaAnalyticsItem.
+ }
+ });
+}
+
+} // namespace anonymous
+
+namespace android {
+
+MediaDrmMetrics::MediaDrmMetrics()
+ : mOpenSessionCounter("drm.mediadrm.open_session", "status"),
+ mCloseSessionCounter("drm.mediadrm.close_session", "status"),
+ mGetKeyRequestTiming("drm.mediadrm.get_key_request", "status"),
+ mProvideKeyResponseTiming("drm.mediadrm.provide_key_response", "status"),
+ mGetProvisionRequestCounter(
+ "drm.mediadrm.get_provision_request", "status"),
+ mProvideProvisionResponseCounter(
+ "drm.mediadrm.provide_provision_response", "status"),
+ mKeyStatusChangeCounter(
+ "drm.mediadrm.key_status_change", "key_status_type"),
+ mEventCounter("drm.mediadrm.event", "event_type"),
+ mGetDeviceUniqueIdCounter(
+ "drm.mediadrm.get_device_unique_id", "status") {
+}
+
+void MediaDrmMetrics::Export(MediaAnalyticsItem* item) {
+ if (!item) {
+ ALOGE("item was unexpectedly null.");
+ return;
+ }
+ ExportCounterMetric(mOpenSessionCounter, item);
+ ExportCounterMetric(mCloseSessionCounter, item);
+ ExportEventMetric(mGetKeyRequestTiming, item);
+ ExportEventMetric(mProvideKeyResponseTiming, item);
+ ExportCounterMetric(mGetProvisionRequestCounter, item);
+ ExportCounterMetric(mProvideProvisionResponseCounter, item);
+ ExportCounterMetricWithAttributeNames(mKeyStatusChangeCounter, item);
+ ExportCounterMetricWithAttributeNames(mEventCounter, item);
+ ExportCounterMetric(mGetDeviceUniqueIdCounter, item);
+}
+
+} // namespace android
diff --git a/drm/libmediadrm/IDrm.cpp b/drm/libmediadrm/IDrm.cpp
index e7417cc..63a9562 100644
--- a/drm/libmediadrm/IDrm.cpp
+++ b/drm/libmediadrm/IDrm.cpp
@@ -56,11 +56,13 @@
VERIFY,
SET_LISTENER,
GET_SECURE_STOP,
- RELEASE_ALL_SECURE_STOPS,
+ REMOVE_ALL_SECURE_STOPS,
GET_HDCP_LEVELS,
GET_NUMBER_OF_SESSIONS,
GET_SECURITY_LEVEL,
SET_SECURITY_LEVEL,
+ REMOVE_SECURE_STOP,
+ GET_SECURE_STOP_IDS
};
struct BpDrm : public BpInterface<IDrm> {
@@ -302,6 +304,25 @@
return reply.readInt32();
}
+ virtual status_t getSecureStopIds(List<Vector<uint8_t> > &secureStopIds) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
+
+ status_t status = remote()->transact(GET_SECURE_STOP_IDS, data, &reply);
+ if (status != OK) {
+ return status;
+ }
+
+ secureStopIds.clear();
+ uint32_t count = reply.readInt32();
+ for (size_t i = 0; i < count; i++) {
+ Vector<uint8_t> secureStopId;
+ readVector(reply, secureStopId);
+ secureStopIds.push_back(secureStopId);
+ }
+ return reply.readInt32();
+ }
+
virtual status_t getSecureStop(Vector<uint8_t> const &ssid, Vector<uint8_t> &secureStop) {
Parcel data, reply;
data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
@@ -329,11 +350,24 @@
return reply.readInt32();
}
- virtual status_t releaseAllSecureStops() {
+ virtual status_t removeSecureStop(Vector<uint8_t> const &ssid) {
Parcel data, reply;
data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
- status_t status = remote()->transact(RELEASE_ALL_SECURE_STOPS, data, &reply);
+ writeVector(data, ssid);
+ status_t status = remote()->transact(REMOVE_SECURE_STOP, data, &reply);
+ if (status != OK) {
+ return status;
+ }
+
+ return reply.readInt32();
+ }
+
+ virtual status_t removeAllSecureStops() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
+
+ status_t status = remote()->transact(REMOVE_ALL_SECURE_STOPS, data, &reply);
if (status != OK) {
return status;
}
@@ -854,6 +888,24 @@
return OK;
}
+ case GET_SECURE_STOP_IDS:
+ {
+ CHECK_INTERFACE(IDrm, data, reply);
+ List<Vector<uint8_t> > secureStopIds;
+ status_t result = getSecureStopIds(secureStopIds);
+ size_t count = secureStopIds.size();
+ reply->writeInt32(count);
+ List<Vector<uint8_t> >::iterator iter = secureStopIds.begin();
+ while(iter != secureStopIds.end()) {
+ size_t size = iter->size();
+ reply->writeInt32(size);
+ reply->write(iter->array(), iter->size());
+ iter++;
+ }
+ reply->writeInt32(result);
+ return OK;
+ }
+
case GET_SECURE_STOP:
{
CHECK_INTERFACE(IDrm, data, reply);
@@ -874,10 +926,19 @@
return OK;
}
- case RELEASE_ALL_SECURE_STOPS:
+ case REMOVE_SECURE_STOP:
{
CHECK_INTERFACE(IDrm, data, reply);
- reply->writeInt32(releaseAllSecureStops());
+ Vector<uint8_t> ssid;
+ readVector(data, ssid);
+ reply->writeInt32(removeSecureStop(ssid));
+ return OK;
+ }
+
+ case REMOVE_ALL_SECURE_STOPS:
+ {
+ CHECK_INTERFACE(IDrm, data, reply);
+ reply->writeInt32(removeAllSecureStops());
return OK;
}
diff --git a/drm/libmediadrm/tests/Android.bp b/drm/libmediadrm/tests/Android.bp
new file mode 100644
index 0000000..fdc982d
--- /dev/null
+++ b/drm/libmediadrm/tests/Android.bp
@@ -0,0 +1,44 @@
+// Build definitions for unit tests.
+
+cc_test {
+ name: "CounterMetric_test",
+ srcs: ["CounterMetric_test.cpp"],
+ shared_libs: ["libmediadrm"],
+ include_dirs: ["frameworks/av/include/media"],
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+}
+
+cc_test {
+ name: "DrmMetrics_test",
+ srcs: ["DrmMetrics_test.cpp"],
+ shared_libs: [
+ "android.hardware.drm@1.0",
+ "liblog",
+ "libmediadrm",
+ "libmediametrics",
+ "libutils",
+ ],
+ include_dirs: ["frameworks/av/include/media"],
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+}
+
+cc_test {
+ name: "EventMetric_test",
+ srcs: ["EventMetric_test.cpp"],
+ shared_libs: [
+ "liblog",
+ "libmediadrm",
+ "libutils",
+ ],
+ include_dirs: ["frameworks/av/include/media"],
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+}
diff --git a/drm/libmediadrm/tests/CounterMetric_test.cpp b/drm/libmediadrm/tests/CounterMetric_test.cpp
new file mode 100644
index 0000000..6bca0da
--- /dev/null
+++ b/drm/libmediadrm/tests/CounterMetric_test.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2018 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 <gtest/gtest.h>
+
+#include "CounterMetric.h"
+
+namespace android {
+
+/**
+ * Unit tests for the CounterMetric class.
+ */
+class CounterMetricTest : public ::testing::Test {
+};
+
+TEST_F(CounterMetricTest, IntDataTypeEmpty) {
+ CounterMetric<int> metric("MyMetricName", "MetricAttributeName");
+
+ std::map<int, int64_t> values;
+
+ metric.ExportValues(
+ [&] (int attribute_value, int64_t value) {
+ values[attribute_value] = value;
+ });
+
+ EXPECT_TRUE(values.empty());
+}
+
+TEST_F(CounterMetricTest, IntDataType) {
+ CounterMetric<int> metric("MyMetricName", "MetricAttributeName");
+
+ std::map<int, int64_t> values;
+
+ metric.Increment(7);
+ metric.Increment(8);
+ metric.Increment(8);
+
+ metric.ExportValues(
+ [&] (int attribute_value, int64_t value) {
+ values[attribute_value] = value;
+ });
+
+ ASSERT_EQ(2u, values.size());
+ EXPECT_EQ(1, values[7]);
+ EXPECT_EQ(2, values[8]);
+}
+
+TEST_F(CounterMetricTest, StringDataType) {
+ CounterMetric<std::string> metric("MyMetricName", "MetricAttributeName");
+
+ std::map<std::string, int64_t> values;
+
+ metric.Increment("a");
+ metric.Increment("b");
+ metric.Increment("b");
+
+ metric.ExportValues(
+ [&] (std::string attribute_value, int64_t value) {
+ values[attribute_value] = value;
+ });
+
+ ASSERT_EQ(2u, values.size());
+ EXPECT_EQ(1, values["a"]);
+ EXPECT_EQ(2, values["b"]);
+}
+
+} // namespace android
diff --git a/drm/libmediadrm/tests/DrmMetrics_test.cpp b/drm/libmediadrm/tests/DrmMetrics_test.cpp
new file mode 100644
index 0000000..d1948b4
--- /dev/null
+++ b/drm/libmediadrm/tests/DrmMetrics_test.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2018 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 <gtest/gtest.h>
+
+#include "DrmMetrics.h"
+
+using ::android::hardware::drm::V1_0::EventType;
+using ::android::hardware::drm::V1_0::KeyStatusType;
+
+namespace android {
+
+/**
+ * Unit tests for the MediaDrmMetrics class.
+ */
+class MediaDrmMetricsTest : public ::testing::Test {
+};
+
+TEST_F(MediaDrmMetricsTest, EmptySuccess) {
+ MediaDrmMetrics metrics;
+ MediaAnalyticsItem item;
+
+ metrics.Export(&item);
+ EXPECT_EQ(0, item.count());
+}
+
+TEST_F(MediaDrmMetricsTest, AllValuesSuccessCounts) {
+ MediaDrmMetrics metrics;
+
+ metrics.mOpenSessionCounter.Increment(OK);
+ metrics.mCloseSessionCounter.Increment(OK);
+
+ {
+ EventTimer<status_t> get_key_request_timer(&metrics.mGetKeyRequestTiming);
+ EventTimer<status_t> provide_key_response_timer(
+ &metrics.mProvideKeyResponseTiming);
+ get_key_request_timer.SetAttribute(OK);
+ provide_key_response_timer.SetAttribute(OK);
+ }
+
+ metrics.mGetProvisionRequestCounter.Increment(OK);
+ metrics.mProvideProvisionResponseCounter.Increment(OK);
+ metrics.mGetDeviceUniqueIdCounter.Increment(OK);
+
+ metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::USABLE);
+ metrics.mEventCounter.Increment(EventType::PROVISION_REQUIRED);
+
+ MediaAnalyticsItem item;
+
+ metrics.Export(&item);
+ EXPECT_EQ(11, item.count());
+
+ // Verify the list of pairs of int64 metrics.
+ std::vector<std::pair<std::string, int64_t>> expected_values = {
+ { "drm.mediadrm.open_session.ok.count", 1 },
+ { "drm.mediadrm.close_session.ok.count", 1 },
+ { "drm.mediadrm.get_key_request.ok.count", 1 },
+ { "drm.mediadrm.provide_key_response.ok.count", 1 },
+ { "drm.mediadrm.get_provision_request.ok.count", 1 },
+ { "drm.mediadrm.provide_provision_response.ok.count", 1 },
+ { "drm.mediadrm.key_status_change.USABLE.count", 1 },
+ { "drm.mediadrm.event.PROVISION_REQUIRED.count", 1 },
+ { "drm.mediadrm.get_device_unique_id.ok.count", 1 }};
+ for (const auto& expected_pair : expected_values) {
+ int64_t value = -1;
+ EXPECT_TRUE(item.getInt64(expected_pair.first.c_str(), &value))
+ << "Failed to get " << expected_pair.first;
+ EXPECT_EQ(expected_pair.second, value)
+ << "Unexpected value for " << expected_pair.first;
+ }
+
+ // Validate timing values exist.
+ int64_t value = -1;
+ EXPECT_TRUE(
+ item.getInt64("drm.mediadrm.get_key_request.ok.average_time_micros",
+ &value));
+ EXPECT_GE(value, 0);
+
+ value = -1;
+ EXPECT_TRUE(
+ item.getInt64("drm.mediadrm.provide_key_response.ok.average_time_micros",
+ &value));
+ EXPECT_GE(value, 0);
+}
+
+TEST_F(MediaDrmMetricsTest, AllValuesFull) {
+ MediaDrmMetrics metrics;
+
+ metrics.mOpenSessionCounter.Increment(OK);
+ metrics.mOpenSessionCounter.Increment(UNEXPECTED_NULL);
+
+ metrics.mCloseSessionCounter.Increment(OK);
+ metrics.mCloseSessionCounter.Increment(UNEXPECTED_NULL);
+
+ for (status_t s : {OK, UNEXPECTED_NULL}) {
+ {
+ EventTimer<status_t> get_key_request_timer(&metrics.mGetKeyRequestTiming);
+ EventTimer<status_t> provide_key_response_timer(
+ &metrics.mProvideKeyResponseTiming);
+ get_key_request_timer.SetAttribute(s);
+ provide_key_response_timer.SetAttribute(s);
+ }
+ }
+
+ metrics.mGetProvisionRequestCounter.Increment(OK);
+ metrics.mGetProvisionRequestCounter.Increment(UNEXPECTED_NULL);
+ metrics.mProvideProvisionResponseCounter.Increment(OK);
+ metrics.mProvideProvisionResponseCounter.Increment(UNEXPECTED_NULL);
+ metrics.mGetDeviceUniqueIdCounter.Increment(OK);
+ metrics.mGetDeviceUniqueIdCounter.Increment(UNEXPECTED_NULL);
+
+ metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::USABLE);
+ metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::EXPIRED);
+ metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::OUTPUTNOTALLOWED);
+ metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::STATUSPENDING);
+ metrics.mKeyStatusChangeCounter.Increment(KeyStatusType::INTERNALERROR);
+ metrics.mEventCounter.Increment(EventType::PROVISION_REQUIRED);
+ metrics.mEventCounter.Increment(EventType::KEY_NEEDED);
+ metrics.mEventCounter.Increment(EventType::KEY_EXPIRED);
+ metrics.mEventCounter.Increment(EventType::VENDOR_DEFINED);
+ metrics.mEventCounter.Increment(EventType::SESSION_RECLAIMED);
+
+ MediaAnalyticsItem item;
+
+ metrics.Export(&item);
+ EXPECT_EQ(26, item.count());
+
+ // Verify the list of pairs of int64 metrics.
+ std::vector<std::pair<std::string, int64_t>> expected_values = {
+ { "drm.mediadrm.open_session.ok.count", 1 },
+ { "drm.mediadrm.close_session.ok.count", 1 },
+ { "drm.mediadrm.get_key_request.ok.count", 1 },
+ { "drm.mediadrm.provide_key_response.ok.count", 1 },
+ { "drm.mediadrm.get_provision_request.ok.count", 1 },
+ { "drm.mediadrm.provide_provision_response.ok.count", 1 },
+ { "drm.mediadrm.get_device_unique_id.ok.count", 1 },
+ { "drm.mediadrm.open_session.error.count", 1 },
+ { "drm.mediadrm.close_session.error.count", 1 },
+ { "drm.mediadrm.get_key_request.error.count", 1 },
+ { "drm.mediadrm.provide_key_response.error.count", 1 },
+ { "drm.mediadrm.get_provision_request.error.count", 1 },
+ { "drm.mediadrm.provide_provision_response.error.count", 1 },
+ { "drm.mediadrm.get_device_unique_id.error.count", 1 },
+ { "drm.mediadrm.key_status_change.USABLE.count", 1 },
+ { "drm.mediadrm.key_status_change.EXPIRED.count", 1 },
+ { "drm.mediadrm.key_status_change.OUTPUT_NOT_ALLOWED.count", 1 },
+ { "drm.mediadrm.key_status_change.STATUS_PENDING.count", 1 },
+ { "drm.mediadrm.key_status_change.INTERNAL_ERROR.count", 1 },
+ { "drm.mediadrm.event.PROVISION_REQUIRED.count", 1 },
+ { "drm.mediadrm.event.KEY_NEEDED.count", 1 },
+ { "drm.mediadrm.event.KEY_EXPIRED.count", 1 },
+ { "drm.mediadrm.event.VENDOR_DEFINED.count", 1 },
+ { "drm.mediadrm.event.SESSION_RECLAIMED.count", 1 }};
+ for (const auto& expected_pair : expected_values) {
+ int64_t value = -1;
+ EXPECT_TRUE(item.getInt64(expected_pair.first.c_str(), &value))
+ << "Failed to get " << expected_pair.first;
+ EXPECT_EQ(expected_pair.second, value)
+ << "Unexpected value for " << expected_pair.first;
+ }
+
+ // Validate timing values exist.
+ int64_t value = -1;
+ EXPECT_TRUE(
+ item.getInt64("drm.mediadrm.get_key_request.ok.average_time_micros",
+ &value));
+ EXPECT_GE(value, 0);
+
+ value = -1;
+ EXPECT_TRUE(
+ item.getInt64("drm.mediadrm.provide_key_response.ok.average_time_micros",
+ &value));
+ EXPECT_GE(value, 0);
+}
+
+} // namespace android
diff --git a/drm/libmediadrm/tests/EventMetric_test.cpp b/drm/libmediadrm/tests/EventMetric_test.cpp
new file mode 100644
index 0000000..eb6c4f6
--- /dev/null
+++ b/drm/libmediadrm/tests/EventMetric_test.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2018 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 <gtest/gtest.h>
+
+#include "EventMetric.h"
+
+namespace android {
+
+/**
+ * Unit tests for the EventMetric class.
+ */
+
+TEST(EventMetricTest, IntDataTypeEmpty) {
+ EventMetric<int> metric("MyMetricName", "MetricAttributeName");
+
+ std::map<int, EventStatistics> values;
+
+ metric.ExportValues(
+ [&] (int attribute_value, const EventStatistics& value) {
+ values[attribute_value] = value;
+ });
+
+ EXPECT_TRUE(values.empty());
+}
+
+TEST(EventMetricTest, IntDataType) {
+ EventMetric<int> metric("MyMetricName", "MetricAttributeName");
+
+ std::map<int, EventStatistics> values;
+
+ metric.Record(4, 7);
+ metric.Record(5, 8);
+ metric.Record(5, 8);
+ metric.Record(5, 8);
+ metric.Record(6, 8);
+ metric.Record(6, 8);
+ metric.Record(6, 8);
+
+ metric.ExportValues(
+ [&] (int attribute_value, const EventStatistics& value) {
+ values[attribute_value] = value;
+ });
+
+ ASSERT_EQ(2u, values.size());
+ EXPECT_EQ(4, values[7].min);
+ EXPECT_EQ(4, values[7].max);
+ EXPECT_EQ(4, values[7].mean);
+ EXPECT_EQ(1, values[7].count);
+
+ EXPECT_EQ(5, values[8].min);
+ EXPECT_EQ(6, values[8].max);
+ // This is an approximate value because of the technique we're using.
+ EXPECT_NEAR(5.5, values[8].mean, 0.2);
+ EXPECT_EQ(6, values[8].count);
+}
+
+TEST(EventMetricTest, StringDataType) {
+ EventMetric<std::string> metric("MyMetricName", "MetricAttributeName");
+
+ std::map<std::string, EventStatistics> values;
+
+ metric.Record(1, "a");
+ metric.Record(2, "b");
+ metric.Record(2, "b");
+ metric.Record(3, "b");
+ metric.Record(3, "b");
+
+ metric.ExportValues(
+ [&] (std::string attribute_value, const EventStatistics& value) {
+ values[attribute_value] = value;
+ });
+
+ ASSERT_EQ(2u, values.size());
+ EXPECT_EQ(1, values["a"].min);
+ EXPECT_EQ(1, values["a"].max);
+ EXPECT_EQ(1, values["a"].mean);
+ EXPECT_EQ(1, values["a"].count);
+
+ EXPECT_EQ(2, values["b"].min);
+ EXPECT_EQ(3, values["b"].max);
+ EXPECT_NEAR(2.5, values["b"].mean, 0.2);
+ EXPECT_EQ(4, values["b"].count);
+}
+
+// Helper class that allows us to mock the clock.
+template<typename AttributeType>
+class MockEventTimer : public EventTimer<AttributeType> {
+ public:
+ explicit MockEventTimer(nsecs_t time_delta_ns,
+ EventMetric<AttributeType>* metric)
+ : EventTimer<AttributeType>(metric) {
+ // Pretend the event started earlier.
+ this->start_time_ = systemTime() - time_delta_ns;
+ }
+};
+
+TEST(EventTimerTest, IntDataType) {
+ EventMetric<int> metric("MyMetricName", "MetricAttributeName");
+
+ for (int i = 0; i < 5; i++) {
+ {
+ // Add a mock time delta.
+ MockEventTimer<int> metric_timer(i * 1000000, &metric);
+ metric_timer.SetAttribute(i % 2);
+ }
+ }
+
+ std::map<int, EventStatistics> values;
+ metric.ExportValues(
+ [&] (int attribute_value, const EventStatistics& value) {
+ values[attribute_value] = value;
+ });
+
+ ASSERT_EQ(2u, values.size());
+ EXPECT_LT(values[0].min, values[0].max);
+ EXPECT_GE(4000, values[0].max);
+ EXPECT_GT(values[0].mean, values[0].min);
+ EXPECT_LE(values[0].mean, values[0].max);
+ EXPECT_EQ(3, values[0].count);
+
+ EXPECT_LT(values[1].min, values[1].max);
+ EXPECT_GE(3000, values[1].max);
+ EXPECT_GT(values[1].mean, values[1].min);
+ EXPECT_LE(values[1].mean, values[1].max);
+ EXPECT_EQ(2, values[1].count);
+}
+
+} // namespace android
diff --git a/include/media/CounterMetric.h b/include/media/CounterMetric.h
new file mode 120000
index 0000000..baba043
--- /dev/null
+++ b/include/media/CounterMetric.h
@@ -0,0 +1 @@
+../../media/libmedia/include/media/CounterMetric.h
\ No newline at end of file
diff --git a/include/media/DrmMetrics.h b/include/media/DrmMetrics.h
new file mode 120000
index 0000000..abc966b
--- /dev/null
+++ b/include/media/DrmMetrics.h
@@ -0,0 +1 @@
+../../media/libmedia/include/media/DrmMetrics.h
\ No newline at end of file
diff --git a/include/media/EventMetric.h b/include/media/EventMetric.h
new file mode 120000
index 0000000..5707d9a
--- /dev/null
+++ b/include/media/EventMetric.h
@@ -0,0 +1 @@
+../../media/libmedia/include/media/EventMetric.h
\ No newline at end of file
diff --git a/include/media/MediaSourceBase.h b/include/media/MediaSourceBase.h
new file mode 120000
index 0000000..fe227b1
--- /dev/null
+++ b/include/media/MediaSourceBase.h
@@ -0,0 +1 @@
+../../media/libmediaextractor/include/media/MediaSourceBase.h
\ No newline at end of file
diff --git a/include/media/MicrophoneInfo.h b/include/media/MicrophoneInfo.h
new file mode 100644
index 0000000..b0199d4
--- /dev/null
+++ b/include/media/MicrophoneInfo.h
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2018 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_MICROPHONE_INFO_H
+#define ANDROID_MICROPHONE_INFO_H
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <system/audio.h>
+#include <utils/String16.h>
+#include <utils/Vector.h>
+
+namespace android {
+namespace media {
+
+#define RETURN_IF_FAILED(calledOnce) \
+ { \
+ status_t returnStatus = calledOnce; \
+ if (returnStatus) { \
+ ALOGE("Failed at %s:%d (%s)", __FILE__, __LINE__, __func__); \
+ return returnStatus; \
+ } \
+ }
+
+class MicrophoneInfo : public Parcelable {
+public:
+ MicrophoneInfo() = default;
+ MicrophoneInfo(const MicrophoneInfo& microphoneInfo) = default;
+ MicrophoneInfo(audio_microphone_characteristic_t& characteristic) {
+ mDeviceId = String16(&characteristic.device_id[0]);
+ mPortId = characteristic.id;
+ mType = characteristic.type;
+ mAddress = String16(&characteristic.address[0]);
+ mDeviceLocation = characteristic.location;
+ mDeviceGroup = characteristic.group;
+ mIndexInTheGroup = characteristic.index_in_the_group;
+ mGeometricLocation.push_back(characteristic.geometric_location.x);
+ mGeometricLocation.push_back(characteristic.geometric_location.y);
+ mGeometricLocation.push_back(characteristic.geometric_location.z);
+ mOrientation.push_back(characteristic.orientation.x);
+ mOrientation.push_back(characteristic.orientation.y);
+ mOrientation.push_back(characteristic.orientation.z);
+ Vector<float> frequencies;
+ Vector<float> responses;
+ for (size_t i = 0; i < characteristic.num_frequency_responses; i++) {
+ frequencies.push_back(characteristic.frequency_responses[0][i]);
+ responses.push_back(characteristic.frequency_responses[1][i]);
+ }
+ mFrequencyResponses.push_back(frequencies);
+ mFrequencyResponses.push_back(responses);
+ for (size_t i = 0; i < AUDIO_CHANNEL_COUNT_MAX; i++) {
+ mChannelMapping.push_back(characteristic.channel_mapping[i]);
+ }
+ mSensitivity = characteristic.sensitivity;
+ mMaxSpl = characteristic.max_spl;
+ mMinSpl = characteristic.min_spl;
+ mDirectionality = characteristic.directionality;
+ }
+
+ virtual ~MicrophoneInfo() = default;
+
+ virtual status_t writeToParcel(Parcel* parcel) const {
+ RETURN_IF_FAILED(parcel->writeString16(mDeviceId));
+ RETURN_IF_FAILED(parcel->writeInt32(mPortId));
+ RETURN_IF_FAILED(parcel->writeUint32(mType));
+ RETURN_IF_FAILED(parcel->writeString16(mAddress));
+ RETURN_IF_FAILED(parcel->writeInt32(mDeviceLocation));
+ RETURN_IF_FAILED(parcel->writeInt32(mDeviceGroup));
+ RETURN_IF_FAILED(parcel->writeInt32(mIndexInTheGroup));
+ RETURN_IF_FAILED(writeFloatVector(parcel, mGeometricLocation));
+ RETURN_IF_FAILED(writeFloatVector(parcel, mOrientation));
+ if (mFrequencyResponses.size() != 2) {
+ return BAD_VALUE;
+ }
+ for (size_t i = 0; i < mFrequencyResponses.size(); i++) {
+ RETURN_IF_FAILED(parcel->writeInt32(mFrequencyResponses[i].size()));
+ RETURN_IF_FAILED(writeFloatVector(parcel, mFrequencyResponses[i]));
+ }
+ std::vector<int> channelMapping;
+ for (size_t i = 0; i < mChannelMapping.size(); ++i) {
+ channelMapping.push_back(mChannelMapping[i]);
+ }
+ RETURN_IF_FAILED(parcel->writeInt32Vector(channelMapping));
+ RETURN_IF_FAILED(parcel->writeFloat(mSensitivity));
+ RETURN_IF_FAILED(parcel->writeFloat(mMaxSpl));
+ RETURN_IF_FAILED(parcel->writeFloat(mMinSpl));
+ RETURN_IF_FAILED(parcel->writeInt32(mDirectionality));
+ return OK;
+ }
+
+ virtual status_t readFromParcel(const Parcel* parcel) {
+ RETURN_IF_FAILED(parcel->readString16(&mDeviceId));
+ RETURN_IF_FAILED(parcel->readInt32(&mPortId));
+ RETURN_IF_FAILED(parcel->readUint32(&mType));
+ RETURN_IF_FAILED(parcel->readString16(&mAddress));
+ RETURN_IF_FAILED(parcel->readInt32(&mDeviceLocation));
+ RETURN_IF_FAILED(parcel->readInt32(&mDeviceGroup));
+ RETURN_IF_FAILED(parcel->readInt32(&mIndexInTheGroup));
+ RETURN_IF_FAILED(readFloatVector(parcel, &mGeometricLocation, 3));
+ RETURN_IF_FAILED(readFloatVector(parcel, &mOrientation, 3));
+ int32_t frequenciesNum;
+ RETURN_IF_FAILED(parcel->readInt32(&frequenciesNum));
+ Vector<float> frequencies;
+ RETURN_IF_FAILED(readFloatVector(parcel, &frequencies, frequenciesNum));
+ int32_t responsesNum;
+ RETURN_IF_FAILED(parcel->readInt32(&responsesNum));
+ Vector<float> responses;
+ RETURN_IF_FAILED(readFloatVector(parcel, &responses, responsesNum));
+ if (frequencies.size() != responses.size()) {
+ return BAD_VALUE;
+ }
+ mFrequencyResponses.push_back(frequencies);
+ mFrequencyResponses.push_back(responses);
+ std::vector<int> channelMapping;
+ status_t result = parcel->readInt32Vector(&channelMapping);
+ if (result != OK) {
+ return result;
+ }
+ if (channelMapping.size() != AUDIO_CHANNEL_COUNT_MAX) {
+ return BAD_VALUE;
+ }
+ for (size_t i = 0; i < channelMapping.size(); i++) {
+ mChannelMapping.push_back(channelMapping[i]);
+ }
+ RETURN_IF_FAILED(parcel->readFloat(&mSensitivity));
+ RETURN_IF_FAILED(parcel->readFloat(&mMaxSpl));
+ RETURN_IF_FAILED(parcel->readFloat(&mMinSpl));
+ RETURN_IF_FAILED(parcel->readInt32(&mDirectionality));
+ return OK;
+ }
+
+ String16 getDeviceId() const {
+ return mDeviceId;
+ }
+
+ int getPortId() const {
+ return mPortId;
+ }
+
+ unsigned int getType() const {
+ return mType;
+ }
+
+ String16 getAddress() const {
+ return mAddress;
+ }
+
+ int getDeviceLocation() const {
+ return mDeviceLocation;
+ }
+
+ int getDeviceGroup() const {
+ return mDeviceGroup;
+ }
+
+ int getIndexInTheGroup() const {
+ return mIndexInTheGroup;
+ }
+
+ const Vector<float>& getGeometricLocation() const {
+ return mGeometricLocation;
+ }
+
+ const Vector<float>& getOrientation() const {
+ return mOrientation;
+ }
+
+ const Vector<Vector<float>>& getFrequencyResponses() const {
+ return mFrequencyResponses;
+ }
+
+ const Vector<int>& getChannelMapping() const {
+ return mChannelMapping;
+ }
+
+ float getSensitivity() const {
+ return mSensitivity;
+ }
+
+ float getMaxSpl() const {
+ return mMaxSpl;
+ }
+
+ float getMinSpl() const {
+ return mMinSpl;
+ }
+
+ int getDirectionality() const {
+ return mDirectionality;
+ }
+
+private:
+ status_t readFloatVector(
+ const Parcel* parcel, Vector<float> *vectorPtr, size_t defaultLength) {
+ std::unique_ptr<std::vector<float>> v;
+ status_t result = parcel->readFloatVector(&v);
+ if (result != OK) return result;
+ vectorPtr->clear();
+ if (v.get() != nullptr) {
+ for (const auto& iter : *v) {
+ vectorPtr->push_back(iter);
+ }
+ } else {
+ vectorPtr->resize(defaultLength);
+ }
+ return OK;
+ }
+ status_t writeFloatVector(Parcel* parcel, const Vector<float>& vector) const {
+ std::vector<float> v;
+ for (size_t i = 0; i < vector.size(); i++) {
+ v.push_back(vector[i]);
+ }
+ return parcel->writeFloatVector(v);
+ }
+
+ String16 mDeviceId;
+ int32_t mPortId;
+ uint32_t mType;
+ String16 mAddress;
+ int32_t mDeviceLocation;
+ int32_t mDeviceGroup;
+ int32_t mIndexInTheGroup;
+ Vector<float> mGeometricLocation;
+ Vector<float> mOrientation;
+ Vector<Vector<float>> mFrequencyResponses;
+ Vector<int> mChannelMapping;
+ float mSensitivity;
+ float mMaxSpl;
+ float mMinSpl;
+ int32_t mDirectionality;
+};
+
+} // namespace media
+} // namespace android
+
+#endif
diff --git a/media/extractors/aac/AACExtractor.cpp b/media/extractors/aac/AACExtractor.cpp
index dfb54e2..17d6aee 100644
--- a/media/extractors/aac/AACExtractor.cpp
+++ b/media/extractors/aac/AACExtractor.cpp
@@ -20,7 +20,7 @@
#include "AACExtractor.h"
#include <media/DataSource.h>
-#include <media/MediaSource.h>
+#include <media/MediaSourceBase.h>
#include <media/stagefright/foundation/avc_utils.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/AMessage.h>
@@ -33,7 +33,7 @@
namespace android {
-class AACSource : public MediaSource {
+class AACSource : public MediaSourceBase {
public:
AACSource(const sp<DataSource> &source,
const sp<MetaData> &meta,
@@ -207,7 +207,7 @@
return mInitCheck == OK ? 1 : 0;
}
-sp<MediaSource> AACExtractor::getTrack(size_t index) {
+MediaSourceBase *AACExtractor::getTrack(size_t index) {
if (mInitCheck != OK || index != 0) {
return NULL;
}
diff --git a/media/extractors/aac/AACExtractor.h b/media/extractors/aac/AACExtractor.h
index aede185..33fbba7 100644
--- a/media/extractors/aac/AACExtractor.h
+++ b/media/extractors/aac/AACExtractor.h
@@ -32,7 +32,7 @@
AACExtractor(const sp<DataSource> &source, const sp<AMessage> &meta);
virtual size_t countTracks();
- virtual sp<MediaSource> getTrack(size_t index);
+ virtual MediaSourceBase *getTrack(size_t index);
virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
virtual sp<MetaData> getMetaData();
diff --git a/media/extractors/amr/AMRExtractor.cpp b/media/extractors/amr/AMRExtractor.cpp
index b8967bd..10be50c 100644
--- a/media/extractors/amr/AMRExtractor.cpp
+++ b/media/extractors/amr/AMRExtractor.cpp
@@ -21,7 +21,7 @@
#include "AMRExtractor.h"
#include <media/DataSource.h>
-#include <media/MediaSource.h>
+#include <media/MediaSourceBase.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/MediaBufferGroup.h>
#include <media/stagefright/MediaDefs.h>
@@ -31,7 +31,7 @@
namespace android {
-class AMRSource : public MediaSource {
+class AMRSource : public MediaSourceBase {
public:
AMRSource(const sp<DataSource> &source,
const sp<MetaData> &meta,
@@ -186,7 +186,7 @@
return mInitCheck == OK ? 1 : 0;
}
-sp<MediaSource> AMRExtractor::getTrack(size_t index) {
+MediaSourceBase *AMRExtractor::getTrack(size_t index) {
if (mInitCheck != OK || index != 0) {
return NULL;
}
diff --git a/media/extractors/amr/AMRExtractor.h b/media/extractors/amr/AMRExtractor.h
index 79b22d6..56883e3 100644
--- a/media/extractors/amr/AMRExtractor.h
+++ b/media/extractors/amr/AMRExtractor.h
@@ -32,7 +32,7 @@
explicit AMRExtractor(const sp<DataSource> &source);
virtual size_t countTracks();
- virtual sp<MediaSource> getTrack(size_t index);
+ virtual MediaSourceBase *getTrack(size_t index);
virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
virtual sp<MetaData> getMetaData();
diff --git a/media/extractors/flac/FLACExtractor.cpp b/media/extractors/flac/FLACExtractor.cpp
index 0c88246..ba28e86 100644
--- a/media/extractors/flac/FLACExtractor.cpp
+++ b/media/extractors/flac/FLACExtractor.cpp
@@ -23,7 +23,7 @@
#include "FLAC/stream_decoder.h"
#include <media/DataSource.h>
-#include <media/MediaSource.h>
+#include <media/MediaSourceBase.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/base64.h>
@@ -161,7 +161,7 @@
class FLACParser;
-class FLACSource : public MediaSource {
+class FLACSource : public MediaSourceBase {
public:
FLACSource(
@@ -936,7 +936,7 @@
return mInitCheck == OK ? 1 : 0;
}
-sp<MediaSource> FLACExtractor::getTrack(size_t index)
+MediaSourceBase *FLACExtractor::getTrack(size_t index)
{
if (mInitCheck != OK || index > 0) {
return NULL;
diff --git a/media/extractors/flac/FLACExtractor.h b/media/extractors/flac/FLACExtractor.h
index 6907ceb..2e7ee3b 100644
--- a/media/extractors/flac/FLACExtractor.h
+++ b/media/extractors/flac/FLACExtractor.h
@@ -32,7 +32,7 @@
explicit FLACExtractor(const sp<DataSource> &source);
virtual size_t countTracks();
- virtual sp<MediaSource> getTrack(size_t index);
+ virtual MediaSourceBase *getTrack(size_t index);
virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
virtual sp<MetaData> getMetaData();
diff --git a/media/extractors/midi/MidiExtractor.cpp b/media/extractors/midi/MidiExtractor.cpp
index a8509fc..18b1d23 100644
--- a/media/extractors/midi/MidiExtractor.cpp
+++ b/media/extractors/midi/MidiExtractor.cpp
@@ -25,7 +25,7 @@
#include <media/stagefright/MediaBufferGroup.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaData.h>
-#include <media/MediaSource.h>
+#include <media/MediaSourceBase.h>
#include <libsonivox/eas_reverb.h>
namespace android {
@@ -33,7 +33,7 @@
// how many Sonivox output buffers to aggregate into one MediaBuffer
static const int NUM_COMBINE_BUFFERS = 4;
-class MidiSource : public MediaSource {
+class MidiSource : public MediaSourceBase {
public:
MidiSource(
@@ -282,7 +282,7 @@
return mInitCheck == OK ? 1 : 0;
}
-sp<MediaSource> MidiExtractor::getTrack(size_t index)
+MediaSourceBase *MidiExtractor::getTrack(size_t index)
{
if (mInitCheck != OK || index > 0) {
return NULL;
diff --git a/media/extractors/midi/MidiExtractor.h b/media/extractors/midi/MidiExtractor.h
index 0fae94a..87e4654 100644
--- a/media/extractors/midi/MidiExtractor.h
+++ b/media/extractors/midi/MidiExtractor.h
@@ -56,7 +56,7 @@
explicit MidiExtractor(const sp<DataSource> &source);
virtual size_t countTracks();
- virtual sp<MediaSource> getTrack(size_t index);
+ virtual MediaSourceBase *getTrack(size_t index);
virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
virtual sp<MetaData> getMetaData();
diff --git a/media/extractors/mkv/MatroskaExtractor.cpp b/media/extractors/mkv/MatroskaExtractor.cpp
index e199f03..7baccb7 100644
--- a/media/extractors/mkv/MatroskaExtractor.cpp
+++ b/media/extractors/mkv/MatroskaExtractor.cpp
@@ -22,7 +22,7 @@
#include "MatroskaExtractor.h"
#include <media/DataSource.h>
-#include <media/MediaSource.h>
+#include <media/MediaSourceBase.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AUtils.h>
#include <media/stagefright/foundation/ABuffer.h>
@@ -121,9 +121,8 @@
BlockIterator &operator=(const BlockIterator &);
};
-struct MatroskaSource : public MediaSource {
- MatroskaSource(
- const sp<MatroskaExtractor> &extractor, size_t index);
+struct MatroskaSource : public MediaSourceBase {
+ MatroskaSource(MatroskaExtractor *extractor, size_t index);
virtual status_t start(MetaData *params);
virtual status_t stop();
@@ -144,7 +143,7 @@
OTHER
};
- sp<MatroskaExtractor> mExtractor;
+ MatroskaExtractor *mExtractor;
size_t mTrackIndex;
Type mType;
bool mIsAudio;
@@ -211,12 +210,12 @@
}
MatroskaSource::MatroskaSource(
- const sp<MatroskaExtractor> &extractor, size_t index)
+ MatroskaExtractor *extractor, size_t index)
: mExtractor(extractor),
mTrackIndex(index),
mType(OTHER),
mIsAudio(false),
- mBlockIter(mExtractor.get(),
+ mBlockIter(mExtractor,
mExtractor->mTracks.itemAt(index).mTrackNum,
index),
mNALSizeLen(-1) {
@@ -928,7 +927,7 @@
return mTracks.size();
}
-sp<MediaSource> MatroskaExtractor::getTrack(size_t index) {
+MediaSourceBase *MatroskaExtractor::getTrack(size_t index) {
if (index >= mTracks.size()) {
return NULL;
}
diff --git a/media/extractors/mkv/MatroskaExtractor.h b/media/extractors/mkv/MatroskaExtractor.h
index 26f8d19..54419bf 100644
--- a/media/extractors/mkv/MatroskaExtractor.h
+++ b/media/extractors/mkv/MatroskaExtractor.h
@@ -38,7 +38,7 @@
virtual size_t countTracks();
- virtual sp<MediaSource> getTrack(size_t index);
+ virtual MediaSourceBase *getTrack(size_t index);
virtual sp<MetaData> getTrackMetaData(
size_t index, uint32_t flags);
diff --git a/media/extractors/mp3/MP3Extractor.cpp b/media/extractors/mp3/MP3Extractor.cpp
index f26ed25..7e27fd8 100644
--- a/media/extractors/mp3/MP3Extractor.cpp
+++ b/media/extractors/mp3/MP3Extractor.cpp
@@ -25,7 +25,7 @@
#include "XINGSeeker.h"
#include <media/DataSource.h>
-#include <media/MediaSource.h>
+#include <media/MediaSourceBase.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/avc_utils.h>
@@ -209,7 +209,7 @@
return valid;
}
-class MP3Source : public MediaSource {
+class MP3Source : public MediaSourceBase {
public:
MP3Source(
const sp<MetaData> &meta, const sp<DataSource> &source,
@@ -407,7 +407,7 @@
return mInitCheck != OK ? 0 : 1;
}
-sp<MediaSource> MP3Extractor::getTrack(size_t index) {
+MediaSourceBase *MP3Extractor::getTrack(size_t index) {
if (mInitCheck != OK || index != 0) {
return NULL;
}
diff --git a/media/extractors/mp3/MP3Extractor.h b/media/extractors/mp3/MP3Extractor.h
index f0ab6b0..3b3387d 100644
--- a/media/extractors/mp3/MP3Extractor.h
+++ b/media/extractors/mp3/MP3Extractor.h
@@ -34,7 +34,7 @@
MP3Extractor(const sp<DataSource> &source, const sp<AMessage> &meta);
virtual size_t countTracks();
- virtual sp<MediaSource> getTrack(size_t index);
+ virtual MediaSourceBase *getTrack(size_t index);
virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
virtual sp<MetaData> getMetaData();
diff --git a/media/extractors/mp4/Android.bp b/media/extractors/mp4/Android.bp
index fce8dd6..3fe2336 100644
--- a/media/extractors/mp4/Android.bp
+++ b/media/extractors/mp4/Android.bp
@@ -1,4 +1,5 @@
-cc_library_shared {
+cc_defaults {
+ name: "libmp4extractor_defaults",
srcs: [
"ItemTable.cpp",
@@ -23,17 +24,21 @@
"libstagefright_id3",
],
- name: "libmp4extractor",
- relative_install_path: "extractors",
-
- compile_multilib: "first",
-
cflags: [
"-Werror",
"-Wall",
"-fvisibility=hidden",
],
version_script: "exports.lds",
+ relative_install_path: "extractors",
+ compile_multilib: "first",
+}
+
+cc_library_shared {
+
+
+ name: "libmp4extractor",
+ defaults: ["libmp4extractor_defaults"],
sanitize: {
cfi: true,
@@ -47,3 +52,9 @@
},
}
+
+cc_library_static {
+ name: "libmp4extractor_fuzzing",
+
+ defaults: ["libmp4extractor_defaults"],
+}
diff --git a/media/extractors/mp4/MPEG4Extractor.cpp b/media/extractors/mp4/MPEG4Extractor.cpp
index 938bd5d..e88a464 100644
--- a/media/extractors/mp4/MPEG4Extractor.cpp
+++ b/media/extractors/mp4/MPEG4Extractor.cpp
@@ -31,7 +31,7 @@
#include "ItemTable.h"
#include "include/ESDS.h"
-#include <media/MediaSource.h>
+#include <media/MediaSourceBase.h>
#include <media/stagefright/foundation/ABitReader.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
@@ -65,11 +65,10 @@
kMaxAtomSize = 64 * 1024 * 1024,
};
-class MPEG4Source : public MediaSource {
+class MPEG4Source : public MediaSourceBase {
public:
// Caller retains ownership of both "dataSource" and "sampleTable".
- MPEG4Source(const sp<MPEG4Extractor> &owner,
- const sp<MetaData> &format,
+ MPEG4Source(const sp<MetaData> &format,
const sp<DataSource> &dataSource,
int32_t timeScale,
const sp<SampleTable> &sampleTable,
@@ -88,14 +87,11 @@
virtual bool supportNonblockingRead() { return true; }
virtual status_t fragmentedRead(MediaBuffer **buffer, const ReadOptions *options = NULL);
-protected:
virtual ~MPEG4Source();
private:
Mutex mLock;
- // keep the MPEG4Extractor around, since we're referencing its data
- sp<MPEG4Extractor> mOwner;
sp<MetaData> mFormat;
sp<DataSource> mDataSource;
int32_t mTimescale;
@@ -3412,7 +3408,7 @@
}
}
-sp<MediaSource> MPEG4Extractor::getTrack(size_t index) {
+MediaSourceBase *MPEG4Extractor::getTrack(size_t index) {
status_t err;
if ((err = readMetaData()) != OK) {
return NULL;
@@ -3488,10 +3484,11 @@
}
}
- sp<MPEG4Source> source = new MPEG4Source(this,
+ MPEG4Source *source = new MPEG4Source(
track->meta, mDataSource, track->timescale, track->sampleTable,
mSidxEntries, trex, mMoofOffset, itemTable);
if (source->init() != OK) {
+ delete source;
return NULL;
}
return source;
@@ -3884,7 +3881,6 @@
////////////////////////////////////////////////////////////////////////////////
MPEG4Source::MPEG4Source(
- const sp<MPEG4Extractor> &owner,
const sp<MetaData> &format,
const sp<DataSource> &dataSource,
int32_t timeScale,
@@ -3893,8 +3889,7 @@
const Trex *trex,
off64_t firstMoofOffset,
const sp<ItemTable> &itemTable)
- : mOwner(owner),
- mFormat(format),
+ : mFormat(format),
mDataSource(dataSource),
mTimescale(timeScale),
mSampleTable(sampleTable),
diff --git a/media/extractors/mp4/MPEG4Extractor.h b/media/extractors/mp4/MPEG4Extractor.h
index 8bfecaa..e947b87 100644
--- a/media/extractors/mp4/MPEG4Extractor.h
+++ b/media/extractors/mp4/MPEG4Extractor.h
@@ -22,6 +22,7 @@
#include <media/DataSource.h>
#include <media/MediaExtractor.h>
+#include <media/stagefright/foundation/AString.h>
#include <utils/List.h>
#include <utils/Vector.h>
#include <utils/String8.h>
@@ -55,7 +56,7 @@
explicit MPEG4Extractor(const sp<DataSource> &source, const char *mime = NULL);
virtual size_t countTracks();
- virtual sp<MediaSource> getTrack(size_t index);
+ virtual MediaSourceBase *getTrack(size_t index);
virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
virtual sp<MetaData> getMetaData();
diff --git a/media/extractors/mpeg2/MPEG2PSExtractor.cpp b/media/extractors/mpeg2/MPEG2PSExtractor.cpp
index c519caf..0978387 100644
--- a/media/extractors/mpeg2/MPEG2PSExtractor.cpp
+++ b/media/extractors/mpeg2/MPEG2PSExtractor.cpp
@@ -24,7 +24,7 @@
#include "mpeg2ts/ESQueue.h"
#include <media/DataSource.h>
-#include <media/MediaSource.h>
+#include <media/MediaSourceBase.h>
#include <media/stagefright/foundation/ABitReader.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
@@ -40,7 +40,7 @@
namespace android {
-struct MPEG2PSExtractor::Track : public MediaSource {
+struct MPEG2PSExtractor::Track : public MediaSourceBase, public RefBase {
Track(MPEG2PSExtractor *extractor,
unsigned stream_id, unsigned stream_type);
@@ -72,8 +72,8 @@
DISALLOW_EVIL_CONSTRUCTORS(Track);
};
-struct MPEG2PSExtractor::WrappedTrack : public MediaSource {
- WrappedTrack(const sp<MPEG2PSExtractor> &extractor, const sp<Track> &track);
+struct MPEG2PSExtractor::WrappedTrack : public MediaSourceBase {
+ WrappedTrack(MPEG2PSExtractor *extractor, const sp<Track> &track);
virtual status_t start(MetaData *params);
virtual status_t stop();
@@ -86,7 +86,7 @@
virtual ~WrappedTrack();
private:
- sp<MPEG2PSExtractor> mExtractor;
+ MPEG2PSExtractor *mExtractor;
sp<MPEG2PSExtractor::Track> mTrack;
DISALLOW_EVIL_CONSTRUCTORS(WrappedTrack);
@@ -125,7 +125,7 @@
return mTracks.size();
}
-sp<MediaSource> MPEG2PSExtractor::getTrack(size_t index) {
+MediaSourceBase *MPEG2PSExtractor::getTrack(size_t index) {
if (index >= mTracks.size()) {
return NULL;
}
@@ -723,7 +723,7 @@
////////////////////////////////////////////////////////////////////////////////
MPEG2PSExtractor::WrappedTrack::WrappedTrack(
- const sp<MPEG2PSExtractor> &extractor, const sp<Track> &track)
+ MPEG2PSExtractor *extractor, const sp<Track> &track)
: mExtractor(extractor),
mTrack(track) {
}
diff --git a/media/extractors/mpeg2/MPEG2PSExtractor.h b/media/extractors/mpeg2/MPEG2PSExtractor.h
index ab3ab05..f8a97ef 100644
--- a/media/extractors/mpeg2/MPEG2PSExtractor.h
+++ b/media/extractors/mpeg2/MPEG2PSExtractor.h
@@ -34,7 +34,7 @@
explicit MPEG2PSExtractor(const sp<DataSource> &source);
virtual size_t countTracks();
- virtual sp<MediaSource> getTrack(size_t index);
+ virtual MediaSourceBase *getTrack(size_t index);
virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
virtual sp<MetaData> getMetaData();
diff --git a/media/extractors/mpeg2/MPEG2TSExtractor.cpp b/media/extractors/mpeg2/MPEG2TSExtractor.cpp
index fef4d30..b24e8db 100644
--- a/media/extractors/mpeg2/MPEG2TSExtractor.cpp
+++ b/media/extractors/mpeg2/MPEG2TSExtractor.cpp
@@ -24,7 +24,7 @@
#include <media/DataSource.h>
#include <media/IStreamSource.h>
-#include <media/MediaSource.h>
+#include <media/MediaSourceBase.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/ALooper.h>
@@ -49,11 +49,12 @@
static const int kMaxDurationReadSize = 250000LL;
static const int kMaxDurationRetry = 6;
-struct MPEG2TSSource : public MediaSource {
+struct MPEG2TSSource : public MediaSourceBase {
MPEG2TSSource(
- const sp<MPEG2TSExtractor> &extractor,
+ MPEG2TSExtractor *extractor,
const sp<AnotherPacketSource> &impl,
bool doesSeek);
+ virtual ~MPEG2TSSource();
virtual status_t start(MetaData *params = NULL);
virtual status_t stop();
@@ -63,7 +64,7 @@
MediaBuffer **buffer, const ReadOptions *options = NULL);
private:
- sp<MPEG2TSExtractor> mExtractor;
+ MPEG2TSExtractor *mExtractor;
sp<AnotherPacketSource> mImpl;
// If there are both audio and video streams, only the video stream
@@ -74,7 +75,7 @@
};
MPEG2TSSource::MPEG2TSSource(
- const sp<MPEG2TSExtractor> &extractor,
+ MPEG2TSExtractor *extractor,
const sp<AnotherPacketSource> &impl,
bool doesSeek)
: mExtractor(extractor),
@@ -82,6 +83,9 @@
mDoesSeek(doesSeek) {
}
+MPEG2TSSource::~MPEG2TSSource() {
+}
+
status_t MPEG2TSSource::start(MetaData *params) {
return mImpl->start(params);
}
@@ -129,7 +133,7 @@
return mSourceImpls.size();
}
-sp<MediaSource> MPEG2TSExtractor::getTrack(size_t index) {
+MediaSourceBase *MPEG2TSExtractor::getTrack(size_t index) {
if (index >= mSourceImpls.size()) {
return NULL;
}
@@ -202,9 +206,7 @@
break;
}
if (!haveVideo) {
- sp<AnotherPacketSource> impl =
- (AnotherPacketSource *)mParser->getSource(
- ATSParser::VIDEO).get();
+ sp<AnotherPacketSource> impl = mParser->getSource(ATSParser::VIDEO);
if (impl != NULL) {
sp<MetaData> format = impl->getFormat();
@@ -220,9 +222,7 @@
}
if (!haveAudio) {
- sp<AnotherPacketSource> impl =
- (AnotherPacketSource *)mParser->getSource(
- ATSParser::AUDIO).get();
+ sp<AnotherPacketSource> impl = mParser->getSource(ATSParser::AUDIO);
if (impl != NULL) {
sp<MetaData> format = impl->getFormat();
@@ -261,10 +261,8 @@
off64_t size;
if (mDataSource->getSize(&size) == OK && (haveAudio || haveVideo)) {
sp<AnotherPacketSource> impl = haveVideo
- ? (AnotherPacketSource *)mParser->getSource(
- ATSParser::VIDEO).get()
- : (AnotherPacketSource *)mParser->getSource(
- ATSParser::AUDIO).get();
+ ? mParser->getSource(ATSParser::VIDEO)
+ : mParser->getSource(ATSParser::AUDIO);
size_t prevSyncSize = 1;
int64_t durationUs = -1;
List<int64_t> durations;
@@ -420,8 +418,7 @@
ev.reset();
int64_t firstTimeUs;
- sp<AnotherPacketSource> src =
- (AnotherPacketSource *)mParser->getSource(type).get();
+ sp<AnotherPacketSource> src = mParser->getSource(type);
if (src == NULL || src->nextBufferTime(&firstTimeUs) != OK) {
continue;
}
@@ -449,7 +446,7 @@
if (!allDurationsFound) {
allDurationsFound = true;
for (auto t: {ATSParser::VIDEO, ATSParser::AUDIO}) {
- sp<AnotherPacketSource> src = (AnotherPacketSource *)mParser->getSource(t).get();
+ sp<AnotherPacketSource> src = mParser->getSource(t);
if (src == NULL) {
continue;
}
@@ -473,7 +470,7 @@
}
status_t MPEG2TSExtractor::seek(int64_t seekTimeUs,
- const MediaSource::ReadOptions::SeekMode &seekMode) {
+ const MediaSourceBase::ReadOptions::SeekMode &seekMode) {
if (mSeekSyncPoints == NULL || mSeekSyncPoints->isEmpty()) {
ALOGW("No sync point to seek to.");
// ... and therefore we have nothing useful to do here.
@@ -494,18 +491,18 @@
}
switch (seekMode) {
- case MediaSource::ReadOptions::SEEK_NEXT_SYNC:
+ case MediaSourceBase::ReadOptions::SEEK_NEXT_SYNC:
if (index == mSeekSyncPoints->size()) {
ALOGW("Next sync not found; starting from the latest sync.");
--index;
}
break;
- case MediaSource::ReadOptions::SEEK_CLOSEST_SYNC:
- case MediaSource::ReadOptions::SEEK_CLOSEST:
+ case MediaSourceBase::ReadOptions::SEEK_CLOSEST_SYNC:
+ case MediaSourceBase::ReadOptions::SEEK_CLOSEST:
ALOGW("seekMode not supported: %d; falling back to PREVIOUS_SYNC",
seekMode);
// fall-through
- case MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC:
+ case MediaSourceBase::ReadOptions::SEEK_PREVIOUS_SYNC:
if (index == 0) {
ALOGW("Previous sync not found; starting from the earliest "
"sync.");
diff --git a/media/extractors/mpeg2/MPEG2TSExtractor.h b/media/extractors/mpeg2/MPEG2TSExtractor.h
index 55356bf..362f016 100644
--- a/media/extractors/mpeg2/MPEG2TSExtractor.h
+++ b/media/extractors/mpeg2/MPEG2TSExtractor.h
@@ -20,7 +20,7 @@
#include <media/stagefright/foundation/ABase.h>
#include <media/MediaExtractor.h>
-#include <media/MediaSource.h>
+#include <media/MediaSourceBase.h>
#include <utils/threads.h>
#include <utils/KeyedVector.h>
#include <utils/Vector.h>
@@ -40,7 +40,7 @@
explicit MPEG2TSExtractor(const sp<DataSource> &source);
virtual size_t countTracks();
- virtual sp<MediaSource> getTrack(size_t index);
+ virtual MediaSourceBase *getTrack(size_t index);
virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
virtual sp<MetaData> getMetaData();
diff --git a/media/extractors/ogg/OggExtractor.cpp b/media/extractors/ogg/OggExtractor.cpp
index f42a6a8..f62ec47 100644
--- a/media/extractors/ogg/OggExtractor.cpp
+++ b/media/extractors/ogg/OggExtractor.cpp
@@ -22,7 +22,7 @@
#include <cutils/properties.h>
#include <media/DataSource.h>
-#include <media/MediaSource.h>
+#include <media/MediaSourceBase.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/base64.h>
@@ -45,8 +45,8 @@
namespace android {
-struct OggSource : public MediaSource {
- explicit OggSource(const sp<OggExtractor> &extractor);
+struct OggSource : public MediaSourceBase {
+ explicit OggSource(OggExtractor *extractor);
virtual sp<MetaData> getFormat();
@@ -60,7 +60,7 @@
virtual ~OggSource();
private:
- sp<OggExtractor> mExtractor;
+ OggExtractor *mExtractor;
bool mStarted;
OggSource(const OggSource &);
@@ -224,7 +224,7 @@
////////////////////////////////////////////////////////////////////////////////
-OggSource::OggSource(const sp<OggExtractor> &extractor)
+OggSource::OggSource(OggExtractor *extractor)
: mExtractor(extractor),
mStarted(false) {
}
@@ -1348,7 +1348,7 @@
return mInitCheck != OK ? 0 : 1;
}
-sp<MediaSource> OggExtractor::getTrack(size_t index) {
+MediaSourceBase *OggExtractor::getTrack(size_t index) {
if (index >= 1) {
return NULL;
}
diff --git a/media/extractors/ogg/OggExtractor.h b/media/extractors/ogg/OggExtractor.h
index 0f7fe5f..126428c 100644
--- a/media/extractors/ogg/OggExtractor.h
+++ b/media/extractors/ogg/OggExtractor.h
@@ -34,7 +34,7 @@
explicit OggExtractor(const sp<DataSource> &source);
virtual size_t countTracks();
- virtual sp<MediaSource> getTrack(size_t index);
+ virtual MediaSourceBase *getTrack(size_t index);
virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
virtual sp<MetaData> getMetaData();
diff --git a/media/extractors/wav/WAVExtractor.cpp b/media/extractors/wav/WAVExtractor.cpp
index 6c5f893..cf22c66 100644
--- a/media/extractors/wav/WAVExtractor.cpp
+++ b/media/extractors/wav/WAVExtractor.cpp
@@ -22,7 +22,7 @@
#include <audio_utils/primitives.h>
#include <media/DataSource.h>
-#include <media/MediaSource.h>
+#include <media/MediaSourceBase.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/MediaBufferGroup.h>
#include <media/stagefright/MediaDefs.h>
@@ -55,7 +55,7 @@
return ptr[1] << 8 | ptr[0];
}
-struct WAVSource : public MediaSource {
+struct WAVSource : public MediaSourceBase {
WAVSource(
const sp<DataSource> &dataSource,
const sp<MetaData> &meta,
@@ -120,7 +120,7 @@
return mInitCheck == OK ? 1 : 0;
}
-sp<MediaSource> WAVExtractor::getTrack(size_t index) {
+MediaSourceBase *WAVExtractor::getTrack(size_t index) {
if (mInitCheck != OK || index > 0) {
return NULL;
}
@@ -564,8 +564,10 @@
return NULL;
}
- sp<MediaExtractor> extractor = new WAVExtractor(source);
- if (extractor->countTracks() == 0) {
+ MediaExtractor *extractor = new WAVExtractor(source);
+ int numTracks = extractor->countTracks();
+ delete extractor;
+ if (numTracks == 0) {
return NULL;
}
diff --git a/media/extractors/wav/WAVExtractor.h b/media/extractors/wav/WAVExtractor.h
index 98a2dfa..47c3c40 100644
--- a/media/extractors/wav/WAVExtractor.h
+++ b/media/extractors/wav/WAVExtractor.h
@@ -33,13 +33,12 @@
explicit WAVExtractor(const sp<DataSource> &source);
virtual size_t countTracks();
- virtual sp<MediaSource> getTrack(size_t index);
+ virtual MediaSourceBase *getTrack(size_t index);
virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
virtual sp<MetaData> getMetaData();
virtual const char * name() { return "WAVExtractor"; }
-protected:
virtual ~WAVExtractor();
private:
diff --git a/media/libaaudio/examples/loopback/src/loopback.cpp b/media/libaaudio/examples/loopback/src/loopback.cpp
index d23d907..f2254ce 100644
--- a/media/libaaudio/examples/loopback/src/loopback.cpp
+++ b/media/libaaudio/examples/loopback/src/loopback.cpp
@@ -160,16 +160,9 @@
static void usage() {
printf("Usage: aaudio_loopback [OPTION]...\n\n");
- printf(" -c{channels} number of output channels\n");
+ AAudioArgsParser::usage();
printf(" -C{channels} number of input channels\n");
printf(" -g{gain} recirculating loopback gain\n");
- printf(" -m{0|1|2|3} set MMAP policy\n");
- printf(" 0 = _UNSPECIFIED\n");
- printf(" 1 = _NEVER\n");
- printf(" 2 = _AUTO, also if -m is used with no number\n");
- printf(" 3 = _ALWAYS\n");
- printf(" -n{numBursts} buffer size, for example 2 for double buffered\n");
- printf(" -p{outPerf} set output AAUDIO_PERFORMANCE_MODE*\n");
printf(" -P{inPerf} set input AAUDIO_PERFORMANCE_MODE*\n");
printf(" n for _NONE\n");
printf(" l for _LATENCY\n");
@@ -178,8 +171,7 @@
printf(" m for sine magnitude\n");
printf(" e for echo latency (default)\n");
printf(" f for file latency, analyzes %s\n\n", FILENAME_ECHOS);
- printf(" -x use EXCLUSIVE mode for output\n");
- printf(" -X use EXCLUSIVE mode for input\n");
+ printf(" -X use EXCLUSIVE mode for input\n");
printf("Example: aaudio_loopback -n2 -pl -Pl -x\n");
}
diff --git a/media/libaaudio/examples/utils/AAudioArgsParser.h b/media/libaaudio/examples/utils/AAudioArgsParser.h
index 142b295..4fc5b9f 100644
--- a/media/libaaudio/examples/utils/AAudioArgsParser.h
+++ b/media/libaaudio/examples/utils/AAudioArgsParser.h
@@ -88,6 +88,30 @@
mPerformanceMode = performanceMode;
}
+ aaudio_usage_t getUsage() const {
+ return mUsage;
+ }
+
+ void setUsage(aaudio_usage_t usage) {
+ mUsage = usage;
+ }
+
+ aaudio_content_type_t getContentType() const {
+ return mContentType;
+ }
+
+ void setContentType(aaudio_content_type_t contentType) {
+ mContentType = contentType;
+ }
+
+ aaudio_input_preset_t getInputPreset() const {
+ return mInputPreset;
+ }
+
+ void setInputPreset(aaudio_input_preset_t inputPreset) {
+ mInputPreset = inputPreset;
+ }
+
int32_t getDeviceId() const {
return mDeviceId;
}
@@ -116,6 +140,9 @@
AAudioStreamBuilder_setDeviceId(builder, mDeviceId);
AAudioStreamBuilder_setSharingMode(builder, mSharingMode);
AAudioStreamBuilder_setPerformanceMode(builder, mPerformanceMode);
+ AAudioStreamBuilder_setUsage(builder, mUsage);
+ AAudioStreamBuilder_setContentType(builder, mContentType);
+ AAudioStreamBuilder_setInputPreset(builder, mInputPreset);
}
private:
@@ -128,6 +155,10 @@
aaudio_sharing_mode_t mSharingMode = AAUDIO_SHARING_MODE_SHARED;
aaudio_performance_mode_t mPerformanceMode = AAUDIO_PERFORMANCE_MODE_NONE;
+ aaudio_usage_t mUsage = AAUDIO_UNSPECIFIED;
+ aaudio_content_type_t mContentType = AAUDIO_UNSPECIFIED;
+ aaudio_input_preset_t mInputPreset = AAUDIO_UNSPECIFIED;
+
int32_t mNumberOfBursts = AAUDIO_UNSPECIFIED;
};
@@ -158,8 +189,8 @@
case 'd':
setDeviceId(atoi(&arg[2]));
break;
- case 's':
- mDurationSeconds = atoi(&arg[2]);
+ case 'i':
+ setInputPreset(atoi(&arg[2]));
break;
case 'm': {
aaudio_policy_t policy = AAUDIO_POLICY_AUTO;
@@ -177,9 +208,18 @@
case 'r':
setSampleRate(atoi(&arg[2]));
break;
+ case 's':
+ mDurationSeconds = atoi(&arg[2]);
+ break;
+ case 'u':
+ setUsage(atoi(&arg[2]));
+ break;
case 'x':
setSharingMode(AAUDIO_SHARING_MODE_EXCLUSIVE);
break;
+ case 'y':
+ setContentType(atoi(&arg[2]));
+ break;
default:
unrecognized = true;
break;
@@ -207,24 +247,28 @@
}
static void usage() {
- printf("-c{channels} -d{duration} -m -n{burstsPerBuffer} -p{perfMode} -r{rate} -x\n");
+ printf("-c{channels} -d{deviceId} -m{mmapPolicy} -n{burstsPerBuffer} -p{perfMode}");
+ printf(" -r{rate} -s{seconds} -x\n");
printf(" Default values are UNSPECIFIED unless otherwise stated.\n");
printf(" -b{bufferCapacity} frames\n");
printf(" -c{channels} for example 2 for stereo\n");
printf(" -d{deviceId} default is %d\n", AAUDIO_UNSPECIFIED);
- printf(" -s{duration} in seconds, default is %d\n", DEFAULT_DURATION_SECONDS);
+ printf(" -i{inputPreset} eg. 5 for AAUDIO_INPUT_PRESET_CAMCORDER\n");
printf(" -m{0|1|2|3} set MMAP policy\n");
- printf(" 0 = _UNSPECIFIED, default\n");
- printf(" 1 = _NEVER\n");
- printf(" 2 = _AUTO, also if -m is used with no number\n");
- printf(" 3 = _ALWAYS\n");
+ printf(" 0 = _UNSPECIFIED, use aaudio.mmap_policy system property, default\n");
+ printf(" 1 = _NEVER, never use MMAP\n");
+ printf(" 2 = _AUTO, use MMAP if available, default for -m with no number\n");
+ printf(" 3 = _ALWAYS, use MMAP or fail\n");
printf(" -n{numberOfBursts} for setBufferSize\n");
printf(" -p{performanceMode} set output AAUDIO_PERFORMANCE_MODE*, default NONE\n");
printf(" n for _NONE\n");
printf(" l for _LATENCY\n");
printf(" p for _POWER_SAVING;\n");
printf(" -r{sampleRate} for example 44100\n");
+ printf(" -s{duration} in seconds, default is %d\n", DEFAULT_DURATION_SECONDS);
+ printf(" -u{usage} eg. 14 for AAUDIO_USAGE_GAME\n");
printf(" -x to use EXCLUSIVE mode\n");
+ printf(" -y{contentType} eg. 1 for AAUDIO_CONTENT_TYPE_SPEECH\n");
}
static aaudio_performance_mode_t parsePerformanceMode(char c) {
@@ -287,6 +331,17 @@
printf(" PerformanceMode: requested = %d, actual = %d\n",
getPerformanceMode(), AAudioStream_getPerformanceMode(stream));
+
+ printf(" Usage: requested = %d, actual = %d\n",
+ getUsage(), AAudioStream_getUsage(stream));
+ printf(" ContentType: requested = %d, actual = %d\n",
+ getContentType(), AAudioStream_getContentType(stream));
+
+ if (AAudioStream_getDirection(stream) == AAUDIO_DIRECTION_INPUT) {
+ printf(" InputPreset: requested = %d, actual = %d\n",
+ getInputPreset(), AAudioStream_getInputPreset(stream));
+ }
+
printf(" Is MMAP used? %s\n", AAudioStream_isMMapUsed(stream)
? "yes" : "no");
diff --git a/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp b/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
index 5d41fd0..e167773 100644
--- a/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
+++ b/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
@@ -204,7 +204,7 @@
AAudioArgsParser::usage();
printf(" -l{count} loopCount start/stop, every other one is silent\n");
printf(" -t{msec} play a high pitched tone at the beginning\n");
- printf(" -u force periodic Underruns by sleeping in callback\n");
+ printf(" -f force periodic underruns by sleeping in callback\n");
}
int main(int argc, const char **argv)
@@ -234,7 +234,7 @@
case 't':
prefixToneMsec = atoi(&arg[2]);
break;
- case 'u':
+ case 'f':
forceUnderruns = true;
break;
default:
diff --git a/media/libaaudio/src/binding/AAudioServiceMessage.h b/media/libaaudio/src/binding/AAudioServiceMessage.h
index 9779f24..3981454 100644
--- a/media/libaaudio/src/binding/AAudioServiceMessage.h
+++ b/media/libaaudio/src/binding/AAudioServiceMessage.h
@@ -36,7 +36,6 @@
AAUDIO_SERVICE_EVENT_PAUSED,
AAUDIO_SERVICE_EVENT_STOPPED,
AAUDIO_SERVICE_EVENT_FLUSHED,
- AAUDIO_SERVICE_EVENT_CLOSED,
AAUDIO_SERVICE_EVENT_DISCONNECTED,
AAUDIO_SERVICE_EVENT_VOLUME,
AAUDIO_SERVICE_EVENT_XRUN
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp
index 4980e97..b611160 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -340,8 +340,13 @@
}
}
-aaudio_result_t AudioStreamInternal::requestStopInternal()
+aaudio_result_t AudioStreamInternal::requestStop()
{
+ aaudio_result_t result = stopCallback();
+ if (result != AAUDIO_OK) {
+ return result;
+ }
+
if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
ALOGE("requestStopInternal() mServiceStreamHandle invalid = 0x%08X",
mServiceStreamHandle);
@@ -355,16 +360,6 @@
return mServiceInterface.stopStream(mServiceStreamHandle);
}
-aaudio_result_t AudioStreamInternal::requestStop()
-{
- aaudio_result_t result = stopCallback();
- if (result != AAUDIO_OK) {
- return result;
- }
- result = requestStopInternal();
- return result;
-}
-
aaudio_result_t AudioStreamInternal::registerThread() {
if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
ALOGE("registerThread() mServiceStreamHandle invalid");
@@ -483,10 +478,6 @@
onFlushFromServer();
}
break;
- case AAUDIO_SERVICE_EVENT_CLOSED:
- ALOGD("%s - got AAUDIO_SERVICE_EVENT_CLOSED", __func__);
- setState(AAUDIO_STREAM_STATE_CLOSED);
- break;
case AAUDIO_SERVICE_EVENT_DISCONNECTED:
// Prevent hardware from looping on old data and making buzzing sounds.
if (getDirection() == AAUDIO_DIRECTION_OUTPUT) {
diff --git a/media/libaaudio/src/client/AudioStreamInternal.h b/media/libaaudio/src/client/AudioStreamInternal.h
index 117756d..0f54f8c 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.h
+++ b/media/libaaudio/src/client/AudioStreamInternal.h
@@ -121,8 +121,6 @@
aaudio_result_t processCommands();
- aaudio_result_t requestStopInternal();
-
aaudio_result_t stopCallback();
virtual void advanceClientToMatchServerPosition() = 0;
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
index 5de6a11..5660c1b 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
@@ -38,9 +38,12 @@
AudioStreamInternalPlay::~AudioStreamInternalPlay() {}
-
-aaudio_result_t AudioStreamInternalPlay::requestPauseInternal()
+aaudio_result_t AudioStreamInternalPlay::requestPause()
{
+ aaudio_result_t result = stopCallback();
+ if (result != AAUDIO_OK) {
+ return result;
+ }
if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
ALOGE("AudioStreamInternal::requestPauseInternal() mServiceStreamHandle invalid = 0x%08X",
mServiceStreamHandle);
@@ -53,16 +56,6 @@
return mServiceInterface.pauseStream(mServiceStreamHandle);
}
-aaudio_result_t AudioStreamInternalPlay::requestPause()
-{
- aaudio_result_t result = stopCallback();
- if (result != AAUDIO_OK) {
- return result;
- }
- result = requestPauseInternal();
- return result;
-}
-
aaudio_result_t AudioStreamInternalPlay::requestFlush() {
if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
ALOGE("AudioStreamInternal::requestFlush() mServiceStreamHandle invalid = 0x%08X",
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.h b/media/libaaudio/src/client/AudioStreamInternalPlay.h
index d5c1b1e..04e4a62 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.h
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.h
@@ -37,6 +37,16 @@
aaudio_result_t requestFlush() override;
+ bool isFlushSupported() const override {
+ // Only implement FLUSH for OUTPUT streams.
+ return true;
+ }
+
+ bool isPauseSupported() const override {
+ // Only implement PAUSE for OUTPUT streams.
+ return true;
+ }
+
aaudio_result_t write(const void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
@@ -52,8 +62,6 @@
protected:
- aaudio_result_t requestPauseInternal();
-
void advanceClientToMatchServerPosition() override;
void onFlushFromServer() override;
diff --git a/media/libaaudio/src/core/AAudioStreamParameters.cpp b/media/libaaudio/src/core/AAudioStreamParameters.cpp
index 9645ea8..d56701b 100644
--- a/media/libaaudio/src/core/AAudioStreamParameters.cpp
+++ b/media/libaaudio/src/core/AAudioStreamParameters.cpp
@@ -17,7 +17,7 @@
#define LOG_TAG "AAudioStreamParameters"
#include <utils/Log.h>
-#include <hardware/audio.h>
+#include <system/audio.h>
#include "AAudioStreamParameters.h"
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index 9a2405a..c4465fd 100644
--- a/media/libaaudio/src/core/AudioStream.cpp
+++ b/media/libaaudio/src/core/AudioStream.cpp
@@ -130,29 +130,106 @@
}
aaudio_result_t AudioStream::safePause() {
+ if (!isPauseSupported()) {
+ return AAUDIO_ERROR_UNIMPLEMENTED;
+ }
+
std::lock_guard<std::mutex> lock(mStreamLock);
if (collidesWithCallback()) {
ALOGE("%s cannot be called from a callback!", __func__);
return AAUDIO_ERROR_INVALID_STATE;
}
+
+ switch (getState()) {
+ // Proceed with pausing.
+ case AAUDIO_STREAM_STATE_STARTING:
+ case AAUDIO_STREAM_STATE_STARTED:
+ case AAUDIO_STREAM_STATE_DISCONNECTED:
+ break;
+
+ // Transition from one inactive state to another.
+ case AAUDIO_STREAM_STATE_OPEN:
+ case AAUDIO_STREAM_STATE_STOPPED:
+ case AAUDIO_STREAM_STATE_FLUSHED:
+ setState(AAUDIO_STREAM_STATE_PAUSED);
+ return AAUDIO_OK;
+
+ // Redundant?
+ case AAUDIO_STREAM_STATE_PAUSING:
+ case AAUDIO_STREAM_STATE_PAUSED:
+ return AAUDIO_OK;
+
+ // Don't interfere with transitional states or when closed.
+ case AAUDIO_STREAM_STATE_STOPPING:
+ case AAUDIO_STREAM_STATE_FLUSHING:
+ case AAUDIO_STREAM_STATE_CLOSING:
+ case AAUDIO_STREAM_STATE_CLOSED:
+ default:
+ ALOGW("safePause() stream not running, state = %s",
+ AAudio_convertStreamStateToText(getState()));
+ return AAUDIO_ERROR_INVALID_STATE;
+ }
+
return requestPause();
}
aaudio_result_t AudioStream::safeFlush() {
+ if (!isFlushSupported()) {
+ ALOGE("flush not supported for this stream");
+ return AAUDIO_ERROR_UNIMPLEMENTED;
+ }
+
std::lock_guard<std::mutex> lock(mStreamLock);
if (collidesWithCallback()) {
- ALOGE("%s cannot be called from a callback!", __func__);
+ ALOGE("stream cannot be flushed from a callback!");
return AAUDIO_ERROR_INVALID_STATE;
}
+
+ aaudio_result_t result = AAudio_isFlushAllowed(getState());
+ if (result != AAUDIO_OK) {
+ return result;
+ }
+
return requestFlush();
}
aaudio_result_t AudioStream::safeStop() {
std::lock_guard<std::mutex> lock(mStreamLock);
if (collidesWithCallback()) {
- ALOGE("%s cannot be called from a callback!", __func__);
+ ALOGE("stream cannot be stopped from a callback!");
return AAUDIO_ERROR_INVALID_STATE;
}
+
+ switch (getState()) {
+ // Proceed with stopping.
+ case AAUDIO_STREAM_STATE_STARTING:
+ case AAUDIO_STREAM_STATE_STARTED:
+ case AAUDIO_STREAM_STATE_DISCONNECTED:
+ break;
+
+ // Transition from one inactive state to another.
+ case AAUDIO_STREAM_STATE_OPEN:
+ case AAUDIO_STREAM_STATE_PAUSED:
+ case AAUDIO_STREAM_STATE_FLUSHED:
+ setState(AAUDIO_STREAM_STATE_STOPPED);
+ return AAUDIO_OK;
+
+ // Redundant?
+ case AAUDIO_STREAM_STATE_STOPPING:
+ case AAUDIO_STREAM_STATE_STOPPED:
+ return AAUDIO_OK;
+
+ // Don't interfere with transitional states or when closed.
+ case AAUDIO_STREAM_STATE_PAUSING:
+ case AAUDIO_STREAM_STATE_FLUSHING:
+ case AAUDIO_STREAM_STATE_CLOSING:
+ case AAUDIO_STREAM_STATE_CLOSED:
+ default:
+ ALOGW("requestStop() stream not running, state = %s",
+ AAudio_convertStreamStateToText(getState()));
+ return AAUDIO_ERROR_INVALID_STATE;
+ }
+
return requestStop();
}
@@ -238,6 +315,7 @@
if (err != 0) {
return AAudioConvert_androidToAAudioResult(-errno);
} else {
+ // TODO Use AAudioThread or maybe AndroidThread
// Name the thread with an increasing index, "AAudio_#", for debugging.
static std::atomic<uint32_t> nextThreadIndex{1};
char name[16]; // max length for a pthread_name
diff --git a/media/libaaudio/src/core/AudioStream.h b/media/libaaudio/src/core/AudioStream.h
index c59ee6c..c0db0f9 100644
--- a/media/libaaudio/src/core/AudioStream.h
+++ b/media/libaaudio/src/core/AudioStream.h
@@ -73,6 +73,24 @@
*/
virtual aaudio_result_t requestStart() = 0;
+ /**
+ * Check the state to see if Pause if currently legal.
+ *
+ * @param result pointer to return code
+ * @return true if OK to continue, if false then return result
+ */
+ bool checkPauseStateTransition(aaudio_result_t *result);
+
+ virtual bool isFlushSupported() const {
+ // Only implement FLUSH for OUTPUT streams.
+ return false;
+ }
+
+ virtual bool isPauseSupported() const {
+ // Only implement PAUSE for OUTPUT streams.
+ return false;
+ }
+
virtual aaudio_result_t requestPause()
{
// Only implement this for OUTPUT streams.
@@ -341,11 +359,13 @@
return mPlayerBase->getResult();
}
+ // Pass pause request through PlayerBase for tracking.
aaudio_result_t systemPause() {
mPlayerBase->pause();
return mPlayerBase->getResult();
}
+ // Pass stop request through PlayerBase for tracking.
aaudio_result_t systemStop() {
mPlayerBase->stop();
return mPlayerBase->getResult();
@@ -452,7 +472,14 @@
}
void setState(aaudio_stream_state_t state) {
- mState = state;
+ if (mState == AAUDIO_STREAM_STATE_CLOSED) {
+ ; // CLOSED is a final state
+ } else if (mState == AAUDIO_STREAM_STATE_DISCONNECTED
+ && state != AAUDIO_STREAM_STATE_CLOSED) {
+ ; // Once DISCONNECTED, we can only move to CLOSED state.
+ } else {
+ mState = state;
+ }
}
void setDeviceId(int32_t deviceId) {
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.cpp b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
index 4d1b187..ee069ee 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
@@ -287,13 +287,8 @@
if (mAudioTrack.get() == nullptr) {
ALOGE("requestPause() no AudioTrack");
return AAUDIO_ERROR_INVALID_STATE;
- } else if (getState() != AAUDIO_STREAM_STATE_STARTING
- && getState() != AAUDIO_STREAM_STATE_STARTED) {
- // TODO What about DISCONNECTED?
- ALOGE("requestPause(), called when state is %s",
- AAudio_convertStreamStateToText(getState()));
- return AAUDIO_ERROR_INVALID_STATE;
}
+
setState(AAUDIO_STREAM_STATE_PAUSING);
mAudioTrack->pause();
mCallbackEnabled.store(false);
@@ -308,10 +303,8 @@
if (mAudioTrack.get() == nullptr) {
ALOGE("requestFlush() no AudioTrack");
return AAUDIO_ERROR_INVALID_STATE;
- } else if (getState() != AAUDIO_STREAM_STATE_PAUSED) {
- ALOGE("requestFlush() not paused");
- return AAUDIO_ERROR_INVALID_STATE;
}
+
setState(AAUDIO_STREAM_STATE_FLUSHING);
incrementFramesRead(getFramesWritten() - getFramesRead());
mAudioTrack->flush();
@@ -325,6 +318,7 @@
ALOGE("requestStop() no AudioTrack");
return AAUDIO_ERROR_INVALID_STATE;
}
+
setState(AAUDIO_STREAM_STATE_STOPPING);
incrementFramesRead(getFramesWritten() - getFramesRead()); // TODO review
mTimestampPosition.set(getFramesWritten());
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.h b/media/libaaudio/src/legacy/AudioStreamTrack.h
index a871db4..68608de 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.h
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.h
@@ -48,6 +48,16 @@
aaudio_result_t requestFlush() override;
aaudio_result_t requestStop() override;
+ bool isFlushSupported() const override {
+ // Only implement FLUSH for OUTPUT streams.
+ return true;
+ }
+
+ bool isPauseSupported() const override {
+ // Only implement PAUSE for OUTPUT streams.
+ return true;
+ }
+
aaudio_result_t getTimestamp(clockid_t clockId,
int64_t *framePosition,
int64_t *timeNanoseconds) override;
diff --git a/media/libaaudio/src/utility/AAudioUtilities.cpp b/media/libaaudio/src/utility/AAudioUtilities.cpp
index 2a34016..adc4904 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.cpp
+++ b/media/libaaudio/src/utility/AAudioUtilities.cpp
@@ -441,3 +441,31 @@
}
return prop;
}
+
+aaudio_result_t AAudio_isFlushAllowed(aaudio_stream_state_t state) {
+ aaudio_result_t result = AAUDIO_OK;
+ switch (state) {
+// Proceed with flushing.
+ case AAUDIO_STREAM_STATE_OPEN:
+ case AAUDIO_STREAM_STATE_PAUSED:
+ case AAUDIO_STREAM_STATE_STOPPED:
+ case AAUDIO_STREAM_STATE_FLUSHED:
+ break;
+
+// Transition from one inactive state to another.
+ case AAUDIO_STREAM_STATE_STARTING:
+ case AAUDIO_STREAM_STATE_STARTED:
+ case AAUDIO_STREAM_STATE_STOPPING:
+ case AAUDIO_STREAM_STATE_PAUSING:
+ case AAUDIO_STREAM_STATE_FLUSHING:
+ case AAUDIO_STREAM_STATE_CLOSING:
+ case AAUDIO_STREAM_STATE_CLOSED:
+ case AAUDIO_STREAM_STATE_DISCONNECTED:
+ default:
+ ALOGE("can only flush stream when PAUSED, OPEN or STOPPED, state = %s",
+ AAudio_convertStreamStateToText(state));
+ result = AAUDIO_ERROR_INVALID_STATE;
+ break;
+ }
+ return result;
+}
diff --git a/media/libaaudio/src/utility/AAudioUtilities.h b/media/libaaudio/src/utility/AAudioUtilities.h
index 0c59f6d..3673c34 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.h
+++ b/media/libaaudio/src/utility/AAudioUtilities.h
@@ -23,7 +23,7 @@
#include <sys/types.h>
#include <utils/Errors.h>
-#include <hardware/audio.h>
+#include <system/audio.h>
#include "aaudio/AAudio.h"
@@ -268,6 +268,14 @@
*/
int32_t AAudioProperty_getHardwareBurstMinMicros();
+
+/**
+ * Is flush allowed for the given state?
+ * @param state
+ * @return AAUDIO_OK if allowed or an error
+ */
+aaudio_result_t AAudio_isFlushAllowed(aaudio_stream_state_t state);
+
/**
* Try a function f until it returns true.
*
diff --git a/media/libaaudio/tests/test_various.cpp b/media/libaaudio/tests/test_various.cpp
index dc19985..4b065c9 100644
--- a/media/libaaudio/tests/test_various.cpp
+++ b/media/libaaudio/tests/test_various.cpp
@@ -26,7 +26,6 @@
#include <gtest/gtest.h>
#include <unistd.h>
-
// Callback function that does nothing.
aaudio_data_callback_result_t NoopDataCallbackProc(
AAudioStream *stream,
@@ -45,73 +44,376 @@
constexpr int64_t NANOS_PER_MILLISECOND = 1000 * 1000;
-//int foo() { // To fix Android Studio formatting when editing.
-TEST(test_various, aaudio_stop_when_open) {
+enum FunctionToCall {
+ CALL_START, CALL_STOP, CALL_PAUSE, CALL_FLUSH
+};
+
+void checkStateTransition(aaudio_performance_mode_t perfMode,
+ aaudio_stream_state_t originalState,
+ FunctionToCall functionToCall,
+ aaudio_result_t expectedResult,
+ aaudio_stream_state_t expectedState) {
AAudioStreamBuilder *aaudioBuilder = nullptr;
AAudioStream *aaudioStream = nullptr;
-// Use an AAudioStreamBuilder to contain requested parameters.
+ // Use an AAudioStreamBuilder to contain requested parameters.
ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));
-// Request stream properties.
+ // Request stream properties.
AAudioStreamBuilder_setDataCallback(aaudioBuilder, NoopDataCallbackProc, nullptr);
- AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
+ AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, perfMode);
-// Create an AAudioStream using the Builder.
- EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
+ // Create an AAudioStream using the Builder.
+ ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
-
+ // Verify Open State
aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNKNOWN;
EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
AAUDIO_STREAM_STATE_UNKNOWN, &state,
1000 * NANOS_PER_MILLISECOND));
EXPECT_EQ(AAUDIO_STREAM_STATE_OPEN, state);
- EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));
+ // Put stream into desired state.
+ aaudio_stream_state_t inputState = AAUDIO_STREAM_STATE_UNINITIALIZED;
+ if (originalState != AAUDIO_STREAM_STATE_OPEN) {
- state = AAUDIO_STREAM_STATE_UNKNOWN;
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream));
+
+ if (originalState != AAUDIO_STREAM_STATE_STARTING) {
+
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
+ AAUDIO_STREAM_STATE_STARTING,
+ &state,
+ 1000 * NANOS_PER_MILLISECOND));
+ ASSERT_EQ(AAUDIO_STREAM_STATE_STARTED, state);
+
+ if (originalState == AAUDIO_STREAM_STATE_STOPPING) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));
+ } else if (originalState == AAUDIO_STREAM_STATE_STOPPED) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestStop(aaudioStream));
+ inputState = AAUDIO_STREAM_STATE_STOPPING;
+ } else if (originalState == AAUDIO_STREAM_STATE_PAUSING) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream));
+ } else if (originalState == AAUDIO_STREAM_STATE_PAUSED) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_requestPause(aaudioStream));
+ inputState = AAUDIO_STREAM_STATE_PAUSING;
+ }
+ }
+ }
+
+ // Wait until past transitional state.
+ if (inputState != AAUDIO_STREAM_STATE_UNINITIALIZED) {
+ ASSERT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
+ inputState,
+ &state,
+ 1000 * NANOS_PER_MILLISECOND));
+ ASSERT_EQ(originalState, state);
+ }
+
+ aaudio_stream_state_t transitionalState = originalState;
+ switch(functionToCall) {
+ case FunctionToCall::CALL_START:
+ EXPECT_EQ(expectedResult, AAudioStream_requestStart(aaudioStream));
+ transitionalState = AAUDIO_STREAM_STATE_STARTING;
+ break;
+ case FunctionToCall::CALL_STOP:
+ EXPECT_EQ(expectedResult, AAudioStream_requestStop(aaudioStream));
+ transitionalState = AAUDIO_STREAM_STATE_STOPPING;
+ break;
+ case FunctionToCall::CALL_PAUSE:
+ EXPECT_EQ(expectedResult, AAudioStream_requestPause(aaudioStream));
+ transitionalState = AAUDIO_STREAM_STATE_PAUSING;
+ break;
+ case FunctionToCall::CALL_FLUSH:
+ EXPECT_EQ(expectedResult, AAudioStream_requestFlush(aaudioStream));
+ transitionalState = AAUDIO_STREAM_STATE_FLUSHING;
+ break;
+ }
+
EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
- AAUDIO_STREAM_STATE_UNKNOWN, &state, 0));
- EXPECT_EQ(AAUDIO_STREAM_STATE_OPEN, state);
-
- AAudioStream_close(aaudioStream);
- AAudioStreamBuilder_delete(aaudioBuilder);
-}
-
-//int boo() { // To fix Android Studio formatting when editing.
-TEST(test_various, aaudio_flush_when_started) {
- AAudioStreamBuilder *aaudioBuilder = nullptr;
- AAudioStream *aaudioStream = nullptr;
-
-// Use an AAudioStreamBuilder to contain requested parameters.
- ASSERT_EQ(AAUDIO_OK, AAudio_createStreamBuilder(&aaudioBuilder));
-
-// Request stream properties.
- AAudioStreamBuilder_setDataCallback(aaudioBuilder, NoopDataCallbackProc, nullptr);
- AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
-
-// Create an AAudioStream using the Builder.
- EXPECT_EQ(AAUDIO_OK, AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream));
- EXPECT_EQ(AAUDIO_OK, AAudioStream_requestStart(aaudioStream));
-
- aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNKNOWN;
- EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
- AAUDIO_STREAM_STATE_STARTING, &state,
+ transitionalState,
+ &state,
1000 * NANOS_PER_MILLISECOND));
- EXPECT_EQ(AAUDIO_STREAM_STATE_STARTED, state);
-
- EXPECT_EQ(AAUDIO_ERROR_INVALID_STATE, AAudioStream_requestFlush(aaudioStream));
-
- state = AAUDIO_STREAM_STATE_UNKNOWN;
- EXPECT_EQ(AAUDIO_OK, AAudioStream_waitForStateChange(aaudioStream,
- AAUDIO_STREAM_STATE_UNKNOWN, &state, 0));
- EXPECT_EQ(AAUDIO_STREAM_STATE_STARTED, state);
+ // We should not change state when a function fails.
+ if (expectedResult != AAUDIO_OK) {
+ ASSERT_EQ(originalState, expectedState);
+ }
+ EXPECT_EQ(expectedState, state);
+ if (state != expectedState) {
+ printf("ERROR - expected %s, actual = %s\n",
+ AAudio_convertStreamStateToText(expectedState),
+ AAudio_convertStreamStateToText(state));
+ fflush(stdout);
+ }
AAudioStream_close(aaudioStream);
AAudioStreamBuilder_delete(aaudioBuilder);
}
-//int main() { // To fix Android Studio formatting when editing.
+// TODO Use parameterized tests instead of these individual specific tests.
+
+// OPEN =================================================================
+TEST(test_various, aaudio_state_lowlat_open_start) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_open_start) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_lowlat_open_stop) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_none_open_stop) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_lowlat_open_pause) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_none_open_pause) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_lowlat_open_flush) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+TEST(test_various, aaudio_state_none_open_flush) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_OPEN,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+
+// STARTED =================================================================
+TEST(test_various, aaudio_state_lowlat_started_start) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_START,
+ AAUDIO_ERROR_INVALID_STATE,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_started_start) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_START,
+ AAUDIO_ERROR_INVALID_STATE,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_lowlat_started_stop) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_none_started_stop) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_lowlat_started_pause) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_none_started_pause) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_lowlat_started_flush) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_ERROR_INVALID_STATE,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_started_flush) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STARTED,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_ERROR_INVALID_STATE,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+// STOPPED =================================================================
+TEST(test_various, aaudio_state_lowlat_stopped_start) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_stopped_start) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_lowlat_stopped_stop) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_none_stopped_stop) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_lowlat_stopped_pause) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_none_stopped_pause) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_lowlat_stopped_flush) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+TEST(test_various, aaudio_state_none_stopped_flush) {
+ checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_STOPPED,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+// PAUSED =================================================================
+TEST(test_various, aaudio_state_lowlat_paused_start) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_none_paused_start) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_START,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STARTED);
+}
+
+TEST(test_various, aaudio_state_lowlat_paused_stop) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_none_paused_stop) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_STOP,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_STOPPED);
+}
+
+TEST(test_various, aaudio_state_lowlat_paused_pause) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_none_paused_pause) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_PAUSE,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_PAUSED);
+}
+
+TEST(test_various, aaudio_state_lowlat_paused_flush) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+TEST(test_various, aaudio_state_none_paused_flush) {
+checkStateTransition(AAUDIO_PERFORMANCE_MODE_NONE,
+ AAUDIO_STREAM_STATE_PAUSED,
+ FunctionToCall::CALL_FLUSH,
+ AAUDIO_OK,
+ AAUDIO_STREAM_STATE_FLUSHED);
+}
+
+// ==========================================================================
TEST(test_various, aaudio_set_buffer_size) {
int32_t bufferCapacity;
@@ -174,7 +476,6 @@
AAudioStreamBuilder_delete(aaudioBuilder);
}
-
// ************************************************************
// Test to make sure that AAUDIO_CALLBACK_RESULT_STOP works.
diff --git a/media/libaudioclient/AudioRecord.cpp b/media/libaudioclient/AudioRecord.cpp
index bc294c5..3aebb8a 100644
--- a/media/libaudioclient/AudioRecord.cpp
+++ b/media/libaudioclient/AudioRecord.cpp
@@ -77,22 +77,47 @@
return rawbuffer;
}
+static std::string audioSourceString(audio_source_t value) {
+ std::string source;
+ if (SourceTypeConverter::toString(value, source)) {
+ return source;
+ }
+ char rawbuffer[16]; // room for "%d"
+ snprintf(rawbuffer, sizeof(rawbuffer), "%d", value);
+ return rawbuffer;
+}
+
void AudioRecord::MediaMetrics::gather(const AudioRecord *record)
{
// key for media statistics is defined in the header
// attrs for media statistics
static constexpr char kAudioRecordChannelCount[] = "android.media.audiorecord.channels";
- static constexpr char kAudioRecordFormat[] = "android.media.audiorecord.format";
+ static constexpr char kAudioRecordEncoding[] = "android.media.audiorecord.encoding";
static constexpr char kAudioRecordLatency[] = "android.media.audiorecord.latency";
static constexpr char kAudioRecordSampleRate[] = "android.media.audiorecord.samplerate";
+ static constexpr char kAudioRecordSource[] = "android.media.audiotrack.source";
// constructor guarantees mAnalyticsItem is valid
mAnalyticsItem->setInt32(kAudioRecordLatency, record->mLatency);
mAnalyticsItem->setInt32(kAudioRecordSampleRate, record->mSampleRate);
mAnalyticsItem->setInt32(kAudioRecordChannelCount, record->mChannelCount);
- mAnalyticsItem->setCString(kAudioRecordFormat,
+ mAnalyticsItem->setCString(kAudioRecordEncoding,
audioFormatTypeString(record->mFormat).c_str());
+ mAnalyticsItem->setCString(kAudioRecordSource,
+ audioSourceString(record->mAttributes.source).c_str());
+}
+
+// hand the user a snapshot of the metrics.
+status_t AudioRecord::getMetrics(MediaAnalyticsItem * &item)
+{
+ mMediaMetrics.gather(this);
+ MediaAnalyticsItem *tmp = mMediaMetrics.dup();
+ if (tmp == nullptr) {
+ return BAD_VALUE;
+ }
+ item = tmp;
+ return NO_ERROR;
}
AudioRecord::AudioRecord(const String16 &opPackageName)
diff --git a/media/libaudioclient/AudioSystem.cpp b/media/libaudioclient/AudioSystem.cpp
index 7969ed0..9dfb514 100644
--- a/media/libaudioclient/AudioSystem.cpp
+++ b/media/libaudioclient/AudioSystem.cpp
@@ -1059,11 +1059,11 @@
return af->getPrimaryOutputFrameCount();
}
-status_t AudioSystem::setLowRamDevice(bool isLowRamDevice)
+status_t AudioSystem::setLowRamDevice(bool isLowRamDevice, int64_t totalMemory)
{
const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();
if (af == 0) return PERMISSION_DENIED;
- return af->setLowRamDevice(isLowRamDevice);
+ return af->setLowRamDevice(isLowRamDevice, totalMemory);
}
void AudioSystem::clearAudioConfigCache()
@@ -1278,6 +1278,13 @@
return aps->getStreamVolumeDB(stream, index, device);
}
+status_t AudioSystem::getMicrophones(std::vector<media::MicrophoneInfo> *microphones)
+{
+ const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();
+ if (af == 0) return PERMISSION_DENIED;
+ return af->getMicrophones(microphones);
+}
+
// ---------------------------------------------------------------------------
int AudioSystem::AudioPolicyServiceClient::addAudioPortCallback(
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index a3c66fe..7dd3f29 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -189,22 +189,23 @@
static constexpr char kAudioTrackUsage[] = "android.media.audiotrack.usage";
static constexpr char kAudioTrackSampleRate[] = "android.media.audiotrack.samplerate";
static constexpr char kAudioTrackChannelMask[] = "android.media.audiotrack.channelmask";
-#if 0
- // XXX: disabled temporarily for b/72027185
static constexpr char kAudioTrackUnderrunFrames[] = "android.media.audiotrack.underrunframes";
-#endif
static constexpr char kAudioTrackStartupGlitch[] = "android.media.audiotrack.glitch.startup";
+ // only if we're in a good state...
+ // XXX: shall we gather alternative info if failing?
+ const status_t lstatus = track->initCheck();
+ if (lstatus != NO_ERROR) {
+ ALOGD("no metrics gathered, track status=%d", (int) lstatus);
+ return;
+ }
+
// constructor guarantees mAnalyticsItem is valid
-#if 0
- // XXX: disabled temporarily for b/72027185
- // must gather underrun info before cleaning mProxy information.
const int32_t underrunFrames = track->getUnderrunFrames();
if (underrunFrames != 0) {
mAnalyticsItem->setInt32(kAudioTrackUnderrunFrames, underrunFrames);
}
-#endif
if (track->mTimestampStartupGlitchReported) {
mAnalyticsItem->setInt32(kAudioTrackStartupGlitch, 1);
@@ -223,6 +224,17 @@
mAnalyticsItem->setInt64(kAudioTrackChannelMask, track->mChannelMask);
}
+// hand the user a snapshot of the metrics.
+status_t AudioTrack::getMetrics(MediaAnalyticsItem * &item)
+{
+ mMediaMetrics.gather(this);
+ MediaAnalyticsItem *tmp = mMediaMetrics.dup();
+ if (tmp == nullptr) {
+ return BAD_VALUE;
+ }
+ item = tmp;
+ return NO_ERROR;
+}
AudioTrack::AudioTrack()
: mStatus(NO_INIT),
diff --git a/media/libaudioclient/IAudioFlinger.cpp b/media/libaudioclient/IAudioFlinger.cpp
index 6507a5c..b6c98cc 100644
--- a/media/libaudioclient/IAudioFlinger.cpp
+++ b/media/libaudioclient/IAudioFlinger.cpp
@@ -85,6 +85,7 @@
GET_AUDIO_HW_SYNC_FOR_SESSION,
SYSTEM_READY,
FRAME_COUNT_HAL,
+ LIST_MICROPHONES,
};
#define MAX_ITEMS_PER_LIST 1024
@@ -707,14 +708,18 @@
return reply.readInt64();
}
- virtual status_t setLowRamDevice(bool isLowRamDevice)
+ virtual status_t setLowRamDevice(bool isLowRamDevice, int64_t totalMemory) override
{
Parcel data, reply;
- data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor());
- data.writeInt32((int) isLowRamDevice);
- remote()->transact(SET_LOW_RAM_DEVICE, data, &reply);
- return reply.readInt32();
+
+ static_assert(NO_ERROR == 0, "NO_ERROR must be 0");
+ return data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor())
+ ?: data.writeInt32((int) isLowRamDevice)
+ ?: data.writeInt64(totalMemory)
+ ?: remote()->transact(SET_LOW_RAM_DEVICE, data, &reply)
+ ?: reply.readInt32();
}
+
virtual status_t listAudioPorts(unsigned int *num_ports,
struct audio_port *ports)
{
@@ -838,6 +843,18 @@
}
return reply.readInt64();
}
+ virtual status_t getMicrophones(std::vector<media::MicrophoneInfo> *microphones)
+ {
+ Parcel data, reply;
+ data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor());
+ status_t status = remote()->transact(LIST_MICROPHONES, data, &reply);
+ if (status != NO_ERROR ||
+ (status = (status_t)reply.readInt32()) != NO_ERROR) {
+ return status;
+ }
+ status = reply.readParcelableVector(microphones);
+ return status;
+ }
};
IMPLEMENT_META_INTERFACE(AudioFlinger, "android.media.IAudioFlinger");
@@ -1281,8 +1298,13 @@
} break;
case SET_LOW_RAM_DEVICE: {
CHECK_INTERFACE(IAudioFlinger, data, reply);
- bool isLowRamDevice = data.readInt32() != 0;
- reply->writeInt32(setLowRamDevice(isLowRamDevice));
+ int32_t isLowRamDevice;
+ int64_t totalMemory;
+ const status_t status =
+ data.readInt32(&isLowRamDevice) ?:
+ data.readInt64(&totalMemory) ?:
+ setLowRamDevice(isLowRamDevice != 0, totalMemory);
+ (void)reply->writeInt32(status);
return NO_ERROR;
} break;
case LIST_AUDIO_PORTS: {
@@ -1398,6 +1420,16 @@
reply->writeInt64( frameCountHAL((audio_io_handle_t) data.readInt32()) );
return NO_ERROR;
} break;
+ case LIST_MICROPHONES: {
+ CHECK_INTERFACE(IAudioFlinger, data, reply);
+ std::vector<media::MicrophoneInfo> microphones;
+ status_t status = getMicrophones(µphones);
+ reply->writeInt32(status);
+ if (status == NO_ERROR) {
+ reply->writeParcelableVector(microphones);
+ }
+ return NO_ERROR;
+ }
default:
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/media/libaudioclient/aidl/android/media/MicrophoneInfo.aidl b/media/libaudioclient/aidl/android/media/MicrophoneInfo.aidl
new file mode 100644
index 0000000..d6e46cb
--- /dev/null
+++ b/media/libaudioclient/aidl/android/media/MicrophoneInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2018 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.
+ */
+
+package android.media;
+
+parcelable MicrophoneInfo cpp_header "media/MicrophoneInfo.h";
diff --git a/media/libaudioclient/include/media/AudioMixer.h b/media/libaudioclient/include/media/AudioMixer.h
index d4ce417..2e29316 100644
--- a/media/libaudioclient/include/media/AudioMixer.h
+++ b/media/libaudioclient/include/media/AudioMixer.h
@@ -18,8 +18,11 @@
#ifndef ANDROID_AUDIO_MIXER_H
#define ANDROID_AUDIO_MIXER_H
+#include <pthread.h>
+#include <sstream>
#include <stdint.h>
#include <sys/types.h>
+#include <unordered_map>
#include <media/AudioBufferProvider.h>
#include <media/AudioResampler.h>
@@ -43,20 +46,14 @@
class AudioMixer
{
public:
- AudioMixer(size_t frameCount, uint32_t sampleRate,
- uint32_t maxNumTracks = MAX_NUM_TRACKS);
+ // This mixer has a hard-coded upper limit of active track inputs;
+ // the value is arbitrary but should be less than TRACK0 to avoid confusion.
+ static constexpr int32_t MAX_NUM_TRACKS = 256;
- /*virtual*/ ~AudioMixer(); // non-virtual saves a v-table, restore if sub-classed
-
-
- // This mixer has a hard-coded upper limit of 32 active track inputs.
- // Adding support for > 32 tracks would require more than simply changing this value.
- static const uint32_t MAX_NUM_TRACKS = 32;
- // maximum number of channels supported by the mixer
-
+ // Do not change these unless underlying code changes.
// This mixer has a hard-coded upper limit of 8 channels for output.
- static const uint32_t MAX_NUM_CHANNELS = 8;
- static const uint32_t MAX_NUM_VOLUMES = 2; // stereo volume only
+ static constexpr uint32_t MAX_NUM_CHANNELS = FCC_8;
+ static constexpr uint32_t MAX_NUM_VOLUMES = FCC_2; // stereo volume only
// maximum number of channels supported for the content
static const uint32_t MAX_NUM_CHANNELS_TO_DOWNMIX = AUDIO_CHANNEL_COUNT_MAX;
@@ -108,6 +105,12 @@
// parameter 'value' is a pointer to the new playback rate.
};
+ AudioMixer(size_t frameCount, uint32_t sampleRate, int32_t maxNumTracks = MAX_NUM_TRACKS)
+ : mMaxNumTracks(maxNumTracks)
+ , mSampleRate(sampleRate)
+ , mFrameCount(frameCount) {
+ pthread_once(&sOnceControl, &sInitRoutine);
+ }
// For all APIs with "name": TRACK0 <= name < TRACK0 + MAX_NUM_TRACKS
@@ -127,27 +130,38 @@
void setParameter(int name, int target, int param, void *value);
void setBufferProvider(int name, AudioBufferProvider* bufferProvider);
- void process();
- uint32_t trackNames() const { return mTrackNames; }
+ void process() {
+ (this->*mHook)();
+ }
size_t getUnreleasedFrames(int name) const;
- static inline bool isValidPcmTrackFormat(audio_format_t format) {
- switch (format) {
- case AUDIO_FORMAT_PCM_8_BIT:
- case AUDIO_FORMAT_PCM_16_BIT:
- case AUDIO_FORMAT_PCM_24_BIT_PACKED:
- case AUDIO_FORMAT_PCM_32_BIT:
- case AUDIO_FORMAT_PCM_FLOAT:
- return true;
- default:
- return false;
+ std::string trackNames() const {
+ std::stringstream ss;
+ for (const auto &pair : mTracks) {
+ ss << pair.first << " ";
}
+ return ss.str();
+ }
+
+ void setNBLogWriter(NBLog::Writer *logWriter) {
+ mNBLogWriter = logWriter;
}
private:
+ /* For multi-format functions (calls template functions
+ * in AudioMixerOps.h). The template parameters are as follows:
+ *
+ * MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration)
+ * USEFLOATVOL (set to true if float volume is used)
+ * ADJUSTVOL (set to true if volume ramp parameters needs adjustment afterwards)
+ * TO: int32_t (Q4.27) or float
+ * TI: int32_t (Q4.27) or int16_t (Q0.15) or float
+ * TA: int32_t (Q4.27)
+ */
+
enum {
// FIXME this representation permits up to 8 channels
NEEDS_CHANNEL_COUNT__MASK = 0x00000007,
@@ -164,14 +178,67 @@
NEEDS_AUX = 0x00010000,
};
- struct state_t;
- struct track_t;
+ // hook types
+ enum {
+ PROCESSTYPE_NORESAMPLEONETRACK, // others set elsewhere
+ };
- typedef void (*hook_t)(track_t* t, int32_t* output, size_t numOutFrames, int32_t* temp,
- int32_t* aux);
- static const int BLOCKSIZE = 16; // 4 cache lines
+ enum {
+ TRACKTYPE_NOP,
+ TRACKTYPE_RESAMPLE,
+ TRACKTYPE_NORESAMPLE,
+ TRACKTYPE_NORESAMPLEMONO,
+ };
- struct track_t {
+ // process hook functionality
+ using process_hook_t = void(AudioMixer::*)();
+
+ struct Track;
+ using hook_t = void(Track::*)(int32_t* output, size_t numOutFrames, int32_t* temp, int32_t* aux);
+
+ struct Track {
+ Track()
+ : bufferProvider(nullptr)
+ {
+ // TODO: move additional initialization here.
+ }
+
+ ~Track()
+ {
+ // bufferProvider, mInputBufferProvider need not be deleted.
+ mResampler.reset(nullptr);
+ // Ensure the order of destruction of buffer providers as they
+ // release the upstream provider in the destructor.
+ mTimestretchBufferProvider.reset(nullptr);
+ mPostDownmixReformatBufferProvider.reset(nullptr);
+ mDownmixerBufferProvider.reset(nullptr);
+ mReformatBufferProvider.reset(nullptr);
+ }
+
+ bool needsRamp() { return (volumeInc[0] | volumeInc[1] | auxInc) != 0; }
+ bool setResampler(uint32_t trackSampleRate, uint32_t devSampleRate);
+ bool doesResample() const { return mResampler.get() != nullptr; }
+ void resetResampler() { if (mResampler.get() != nullptr) mResampler->reset(); }
+ void adjustVolumeRamp(bool aux, bool useFloat = false);
+ size_t getUnreleasedFrames() const { return mResampler.get() != nullptr ?
+ mResampler->getUnreleasedFrames() : 0; };
+
+ status_t prepareForDownmix();
+ void unprepareForDownmix();
+ status_t prepareForReformat();
+ void unprepareForReformat();
+ bool setPlaybackRate(const AudioPlaybackRate &playbackRate);
+ void reconfigureBufferProviders();
+
+ static hook_t getTrackHook(int trackType, uint32_t channelCount,
+ audio_format_t mixerInFormat, audio_format_t mixerOutFormat);
+
+ void track__nop(int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux);
+
+ template <int MIXTYPE, bool USEFLOATVOL, bool ADJUSTVOL,
+ typename TO, typename TI, typename TA>
+ void volumeMix(TO *out, size_t outFrames, const TI *in, TA *aux, bool ramp);
+
uint32_t needs;
// TODO: Eventually remove legacy integer volume settings
@@ -181,16 +248,11 @@
};
int32_t prevVolume[MAX_NUM_VOLUMES];
-
- // 16-byte boundary
-
int32_t volumeInc[MAX_NUM_VOLUMES];
int32_t auxInc;
int32_t prevAuxLevel;
-
- // 16-byte boundary
-
int16_t auxLevel; // 0 <= auxLevel <= MAX_GAIN_INT, but signed for mul performance
+
uint16_t frameCount;
uint8_t channelCount; // 1 or 2, redundant with (needs & NEEDS_CHANNEL_COUNT__MASK)
@@ -202,22 +264,16 @@
// for how the Track buffer provider is wrapped by another one when dowmixing is required
AudioBufferProvider* bufferProvider;
- // 16-byte boundary
-
mutable AudioBufferProvider::Buffer buffer; // 8 bytes
hook_t hook;
- const void* in; // current location in buffer
+ const void *mIn; // current location in buffer
- // 16-byte boundary
-
- AudioResampler* resampler;
+ std::unique_ptr<AudioResampler> mResampler;
uint32_t sampleRate;
int32_t* mainBuffer;
int32_t* auxBuffer;
- // 16-byte boundary
-
/* Buffer providers are constructed to translate the track input data as needed.
*
* TODO: perhaps make a single PlaybackConverterProvider class to move
@@ -228,17 +284,17 @@
* match either mMixerInFormat or mDownmixRequiresFormat, if the downmixer
* requires reformat. For example, it may convert floating point input to
* PCM_16_bit if that's required by the downmixer.
- * 3) downmixerBufferProvider: If not NULL, performs the channel remixing to match
+ * 3) mDownmixerBufferProvider: If not NULL, performs the channel remixing to match
* the number of channels required by the mixer sink.
* 4) mPostDownmixReformatBufferProvider: If not NULL, performs reformatting from
* the downmixer requirements to the mixer engine input requirements.
* 5) mTimestretchBufferProvider: Adds timestretching for playback rate
*/
AudioBufferProvider* mInputBufferProvider; // externally provided buffer provider.
- PassthruBufferProvider* mReformatBufferProvider; // provider wrapper for reformatting.
- PassthruBufferProvider* downmixerBufferProvider; // wrapper for channel conversion.
- PassthruBufferProvider* mPostDownmixReformatBufferProvider;
- PassthruBufferProvider* mTimestretchBufferProvider;
+ std::unique_ptr<PassthruBufferProvider> mReformatBufferProvider;
+ std::unique_ptr<PassthruBufferProvider> mDownmixerBufferProvider;
+ std::unique_ptr<PassthruBufferProvider> mPostDownmixReformatBufferProvider;
+ std::unique_ptr<PassthruBufferProvider> mTimestretchBufferProvider;
int32_t sessionId;
@@ -263,129 +319,94 @@
AudioPlaybackRate mPlaybackRate;
- bool needsRamp() { return (volumeInc[0] | volumeInc[1] | auxInc) != 0; }
- bool setResampler(uint32_t trackSampleRate, uint32_t devSampleRate);
- bool doesResample() const { return resampler != NULL; }
- void resetResampler() { if (resampler != NULL) resampler->reset(); }
- void adjustVolumeRamp(bool aux, bool useFloat = false);
- size_t getUnreleasedFrames() const { return resampler != NULL ?
- resampler->getUnreleasedFrames() : 0; };
+ private:
+ // hooks
+ void track__genericResample(int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux);
+ void track__16BitsStereo(int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux);
+ void track__16BitsMono(int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux);
- status_t prepareForDownmix();
- void unprepareForDownmix();
- status_t prepareForReformat();
- void unprepareForReformat();
- bool setPlaybackRate(const AudioPlaybackRate &playbackRate);
- void reconfigureBufferProviders();
+ void volumeRampStereo(int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux);
+ void volumeStereo(int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux);
+
+ // multi-format track hooks
+ template <int MIXTYPE, typename TO, typename TI, typename TA>
+ void track__Resample(TO* out, size_t frameCount, TO* temp __unused, TA* aux);
+ template <int MIXTYPE, typename TO, typename TI, typename TA>
+ void track__NoResample(TO* out, size_t frameCount, TO* temp __unused, TA* aux);
};
- typedef void (*process_hook_t)(state_t* state);
-
- // pad to 32-bytes to fill cache line
- struct state_t {
- uint32_t enabledTracks;
- uint32_t needsChanged;
- size_t frameCount;
- process_hook_t hook; // one of process__*, never NULL
- int32_t *outputTemp;
- int32_t *resampleTemp;
- NBLog::Writer* mNBLogWriter; // associated NBLog::Writer or &mDummyLog
- int32_t reserved[1];
- // FIXME allocate dynamically to save some memory when maxNumTracks < MAX_NUM_TRACKS
- track_t tracks[MAX_NUM_TRACKS] __attribute__((aligned(32)));
- };
-
- // bitmask of allocated track names, where bit 0 corresponds to TRACK0 etc.
- uint32_t mTrackNames;
-
- // bitmask of configured track names; ~0 if maxNumTracks == MAX_NUM_TRACKS,
- // but will have fewer bits set if maxNumTracks < MAX_NUM_TRACKS
- const uint32_t mConfiguredNames;
-
- const uint32_t mSampleRate;
-
- NBLog::Writer mDummyLogWriter;
-public:
- // Called by FastMixer to inform AudioMixer of it's associated NBLog::Writer.
- // FIXME It would be safer to use TLS for this, so we don't accidentally use wrong one.
- void setNBLogWriter(NBLog::Writer* log);
-private:
- state_t mState __attribute__((aligned(32)));
-
- // Call after changing either the enabled status of a track, or parameters of an enabled track.
- // OK to call more often than that, but unnecessary.
- void invalidateState(uint32_t mask);
+ // TODO: remove BLOCKSIZE unit of processing - it isn't needed anymore.
+ static constexpr int BLOCKSIZE = 16;
bool setChannelMasks(int name,
audio_channel_mask_t trackChannelMask, audio_channel_mask_t mixerChannelMask);
- static void track__genericResample(track_t* t, int32_t* out, size_t numFrames, int32_t* temp,
- int32_t* aux);
- static void track__nop(track_t* t, int32_t* out, size_t numFrames, int32_t* temp, int32_t* aux);
- static void track__16BitsStereo(track_t* t, int32_t* out, size_t numFrames, int32_t* temp,
- int32_t* aux);
- static void track__16BitsMono(track_t* t, int32_t* out, size_t numFrames, int32_t* temp,
- int32_t* aux);
- static void volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp,
- int32_t* aux);
- static void volumeStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp,
- int32_t* aux);
+ // Called when track info changes and a new process hook should be determined.
+ void invalidate() {
+ mHook = &AudioMixer::process__validate;
+ }
- static void process__validate(state_t* state);
- static void process__nop(state_t* state);
- static void process__genericNoResampling(state_t* state);
- static void process__genericResampling(state_t* state);
- static void process__OneTrack16BitsStereoNoResampling(state_t* state);
+ void process__validate();
+ void process__nop();
+ void process__genericNoResampling();
+ void process__genericResampling();
+ void process__oneTrack16BitsStereoNoResampling();
- static pthread_once_t sOnceControl;
- static void sInitRoutine();
-
- /* multi-format volume mixing function (calls template functions
- * in AudioMixerOps.h). The template parameters are as follows:
- *
- * MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration)
- * USEFLOATVOL (set to true if float volume is used)
- * ADJUSTVOL (set to true if volume ramp parameters needs adjustment afterwards)
- * TO: int32_t (Q4.27) or float
- * TI: int32_t (Q4.27) or int16_t (Q0.15) or float
- * TA: int32_t (Q4.27)
- */
- template <int MIXTYPE, bool USEFLOATVOL, bool ADJUSTVOL,
- typename TO, typename TI, typename TA>
- static void volumeMix(TO *out, size_t outFrames,
- const TI *in, TA *aux, bool ramp, AudioMixer::track_t *t);
-
- // multi-format process hooks
template <int MIXTYPE, typename TO, typename TI, typename TA>
- static void process_NoResampleOneTrack(state_t* state);
+ void process__noResampleOneTrack();
- // multi-format track hooks
- template <int MIXTYPE, typename TO, typename TI, typename TA>
- static void track__Resample(track_t* t, TO* out, size_t frameCount,
- TO* temp __unused, TA* aux);
- template <int MIXTYPE, typename TO, typename TI, typename TA>
- static void track__NoResample(track_t* t, TO* out, size_t frameCount,
- TO* temp __unused, TA* aux);
+ static process_hook_t getProcessHook(int processType, uint32_t channelCount,
+ audio_format_t mixerInFormat, audio_format_t mixerOutFormat);
static void convertMixerFormat(void *out, audio_format_t mixerOutFormat,
void *in, audio_format_t mixerInFormat, size_t sampleCount);
- // hook types
- enum {
- PROCESSTYPE_NORESAMPLEONETRACK,
- };
- enum {
- TRACKTYPE_NOP,
- TRACKTYPE_RESAMPLE,
- TRACKTYPE_NORESAMPLE,
- TRACKTYPE_NORESAMPLEMONO,
- };
+ static inline bool isValidPcmTrackFormat(audio_format_t format) {
+ switch (format) {
+ case AUDIO_FORMAT_PCM_8_BIT:
+ case AUDIO_FORMAT_PCM_16_BIT:
+ case AUDIO_FORMAT_PCM_24_BIT_PACKED:
+ case AUDIO_FORMAT_PCM_32_BIT:
+ case AUDIO_FORMAT_PCM_FLOAT:
+ return true;
+ default:
+ return false;
+ }
+ }
- // functions for determining the proper process and track hooks.
- static process_hook_t getProcessHook(int processType, uint32_t channelCount,
- audio_format_t mixerInFormat, audio_format_t mixerOutFormat);
- static hook_t getTrackHook(int trackType, uint32_t channelCount,
- audio_format_t mixerInFormat, audio_format_t mixerOutFormat);
+ static void sInitRoutine();
+
+ // initialization constants
+ const int mMaxNumTracks;
+ const uint32_t mSampleRate;
+ const size_t mFrameCount;
+
+ NBLog::Writer *mNBLogWriter = nullptr; // associated NBLog::Writer
+
+ process_hook_t mHook = &AudioMixer::process__nop; // one of process__*, never nullptr
+
+ // the size of the type (int32_t) should be the largest of all types supported
+ // by the mixer.
+ std::unique_ptr<int32_t[]> mOutputTemp;
+ std::unique_ptr<int32_t[]> mResampleTemp;
+
+ // fast lookup of previously deleted track names for reuse.
+ // the AudioMixer tries to return the smallest unused name -
+ // this is an arbitrary decision (actually any non-negative
+ // integer that isn't in mTracks could be used).
+ std::set<int /* name */> mUnusedNames; // set of unused track names (may be empty)
+
+ // track names grouped by main buffer, in no particular order of main buffer.
+ // however names for a particular main buffer are in order (by construction).
+ std::unordered_map<void * /* mainBuffer */, std::vector<int /* name */>> mGroups;
+
+ // track names that are enabled, in increasing order (by construction).
+ std::vector<int /* name */> mEnabled;
+
+ // track smart pointers, by name, in increasing order of name.
+ std::map<int /* name */, std::shared_ptr<Track>> mTracks;
+
+ static pthread_once_t sOnceControl; // initialized in constructor by first new
};
// ----------------------------------------------------------------------------
diff --git a/media/libaudioclient/include/media/AudioRecord.h b/media/libaudioclient/include/media/AudioRecord.h
index fea973a..caaefce 100644
--- a/media/libaudioclient/include/media/AudioRecord.h
+++ b/media/libaudioclient/include/media/AudioRecord.h
@@ -256,6 +256,11 @@
*/
uint32_t getNotificationPeriodInFrames() const { return mNotificationFramesAct; }
+ /*
+ * return metrics information for the current instance.
+ */
+ status_t getMetrics(MediaAnalyticsItem * &item);
+
/* After it's created the track is not active. Call start() to
* make it active. If set, the callback will start being called.
* If event is not AudioSystem::SYNC_EVENT_NONE, the capture start will be delayed until
@@ -703,6 +708,7 @@
}
}
void gather(const AudioRecord *record);
+ MediaAnalyticsItem *dup() { return mAnalyticsItem->dup(); }
private:
std::unique_ptr<MediaAnalyticsItem> mAnalyticsItem;
};
diff --git a/media/libaudioclient/include/media/AudioSystem.h b/media/libaudioclient/include/media/AudioSystem.h
index e452837..3c8e7bc 100644
--- a/media/libaudioclient/include/media/AudioSystem.h
+++ b/media/libaudioclient/include/media/AudioSystem.h
@@ -23,11 +23,13 @@
#include <media/AudioIoDescriptor.h>
#include <media/IAudioFlingerClient.h>
#include <media/IAudioPolicyServiceClient.h>
+#include <media/MicrophoneInfo.h>
#include <system/audio.h>
#include <system/audio_effect.h>
#include <system/audio_policy.h>
#include <utils/Errors.h>
#include <utils/Mutex.h>
+#include <vector>
namespace android {
@@ -285,7 +287,7 @@
static uint32_t getPrimaryOutputSamplingRate();
static size_t getPrimaryOutputFrameCount();
- static status_t setLowRamDevice(bool isLowRamDevice);
+ static status_t setLowRamDevice(bool isLowRamDevice, int64_t totalMemory);
// Check if hw offload is possible for given format, stream type, sample rate,
// bit rate, duration, video and streaming or offload property is enabled
@@ -340,6 +342,8 @@
static float getStreamVolumeDB(
audio_stream_type_t stream, int index, audio_devices_t device);
+ static status_t getMicrophones(std::vector<media::MicrophoneInfo> *microphones);
+
// ----------------------------------------------------------------------------
class AudioPortCallback : public RefBase
diff --git a/media/libaudioclient/include/media/AudioTrack.h b/media/libaudioclient/include/media/AudioTrack.h
index c146db9..8fbe980 100644
--- a/media/libaudioclient/include/media/AudioTrack.h
+++ b/media/libaudioclient/include/media/AudioTrack.h
@@ -386,6 +386,11 @@
/* Return the static buffer specified in constructor or set(), or 0 for streaming mode */
sp<IMemory> sharedBuffer() const { return mSharedBuffer; }
+ /*
+ * return metrics information for the current track.
+ */
+ status_t getMetrics(MediaAnalyticsItem * &item);
+
/* After it's created the track is not active. Call start() to
* make it active. If set, the callback will start being called.
* If the track was previously paused, volume is ramped up over the first mix buffer.
@@ -1198,6 +1203,7 @@
}
}
void gather(const AudioTrack *track);
+ MediaAnalyticsItem *dup() { return mAnalyticsItem->dup(); }
private:
std::unique_ptr<MediaAnalyticsItem> mAnalyticsItem;
};
diff --git a/media/libaudioclient/include/media/IAudioFlinger.h b/media/libaudioclient/include/media/IAudioFlinger.h
index 472a3da..e6bf72f 100644
--- a/media/libaudioclient/include/media/IAudioFlinger.h
+++ b/media/libaudioclient/include/media/IAudioFlinger.h
@@ -35,6 +35,8 @@
#include <media/IEffect.h>
#include <media/IEffectClient.h>
#include <utils/String8.h>
+#include <media/MicrophoneInfo.h>
+#include <vector>
#include "android/media/IAudioRecord.h"
@@ -454,8 +456,9 @@
// Intended for AudioService to inform AudioFlinger of device's low RAM attribute,
// and should be called at most once. For a definition of what "low RAM" means, see
- // android.app.ActivityManager.isLowRamDevice().
- virtual status_t setLowRamDevice(bool isLowRamDevice) = 0;
+ // android.app.ActivityManager.isLowRamDevice(). The totalMemory parameter
+ // is obtained from android.app.ActivityManager.MemoryInfo.totalMem.
+ virtual status_t setLowRamDevice(bool isLowRamDevice, int64_t totalMemory) = 0;
/* List available audio ports and their attributes */
virtual status_t listAudioPorts(unsigned int *num_ports,
@@ -485,6 +488,9 @@
// Returns the number of frames per audio HAL buffer.
virtual size_t frameCountHAL(audio_io_handle_t ioHandle) const = 0;
+
+ /* List available microphones and their characteristics */
+ virtual status_t getMicrophones(std::vector<media::MicrophoneInfo> *microphones) = 0;
};
diff --git a/media/libaudioprocessing/AudioMixer.cpp b/media/libaudioprocessing/AudioMixer.cpp
index 5fafb8a..f1daeb4 100644
--- a/media/libaudioprocessing/AudioMixer.cpp
+++ b/media/libaudioprocessing/AudioMixer.cpp
@@ -62,13 +62,6 @@
#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
#endif
-// TODO: Move these macro/inlines to a header file.
-template <typename T>
-static inline
-T max(const T& x, const T& y) {
- return x > y ? x : y;
-}
-
// Set kUseNewMixer to true to use the new mixer engine always. Otherwise the
// original code will be used for stereo sinks, the new mixer for multichannel.
static constexpr bool kUseNewMixer = true;
@@ -93,88 +86,41 @@
// ----------------------------------------------------------------------------
-template <typename T>
-T min(const T& a, const T& b)
-{
- return a < b ? a : b;
-}
-
-// ----------------------------------------------------------------------------
-
-// Ensure mConfiguredNames bitmask is initialized properly on all architectures.
-// The value of 1 << x is undefined in C when x >= 32.
-
-AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate, uint32_t maxNumTracks)
- : mTrackNames(0), mConfiguredNames((maxNumTracks >= 32 ? 0 : 1 << maxNumTracks) - 1),
- mSampleRate(sampleRate)
-{
- ALOG_ASSERT(maxNumTracks <= MAX_NUM_TRACKS, "maxNumTracks %u > MAX_NUM_TRACKS %u",
- maxNumTracks, MAX_NUM_TRACKS);
-
- // AudioMixer is not yet capable of more than 32 active track inputs
- ALOG_ASSERT(32 >= MAX_NUM_TRACKS, "bad MAX_NUM_TRACKS %d", MAX_NUM_TRACKS);
-
- pthread_once(&sOnceControl, &sInitRoutine);
-
- mState.enabledTracks= 0;
- mState.needsChanged = 0;
- mState.frameCount = frameCount;
- mState.hook = process__nop;
- mState.outputTemp = NULL;
- mState.resampleTemp = NULL;
- mState.mNBLogWriter = &mDummyLogWriter;
- // mState.reserved
-
- // FIXME Most of the following initialization is probably redundant since
- // tracks[i] should only be referenced if (mTrackNames & (1 << i)) != 0
- // and mTrackNames is initially 0. However, leave it here until that's verified.
- track_t* t = mState.tracks;
- for (unsigned i=0 ; i < MAX_NUM_TRACKS ; i++) {
- t->resampler = NULL;
- t->downmixerBufferProvider = NULL;
- t->mReformatBufferProvider = NULL;
- t->mTimestretchBufferProvider = NULL;
- t++;
- }
-
-}
-
-AudioMixer::~AudioMixer()
-{
- track_t* t = mState.tracks;
- for (unsigned i=0 ; i < MAX_NUM_TRACKS ; i++) {
- delete t->resampler;
- delete t->downmixerBufferProvider;
- delete t->mReformatBufferProvider;
- delete t->mTimestretchBufferProvider;
- t++;
- }
- delete [] mState.outputTemp;
- delete [] mState.resampleTemp;
-}
-
-void AudioMixer::setNBLogWriter(NBLog::Writer *logWriter)
-{
- mState.mNBLogWriter = logWriter;
-}
-
static inline audio_format_t selectMixerInFormat(audio_format_t inputFormat __unused) {
return kUseFloat && kUseNewMixer ? AUDIO_FORMAT_PCM_FLOAT : AUDIO_FORMAT_PCM_16_BIT;
}
-int AudioMixer::getTrackName(audio_channel_mask_t channelMask,
- audio_format_t format, int sessionId)
+int AudioMixer::getTrackName(
+ audio_channel_mask_t channelMask, audio_format_t format, int sessionId)
{
if (!isValidPcmTrackFormat(format)) {
ALOGE("AudioMixer::getTrackName invalid format (%#x)", format);
return -1;
}
- uint32_t names = (~mTrackNames) & mConfiguredNames;
- if (names != 0) {
- int n = __builtin_ctz(names);
- ALOGV("add track (%d)", n);
+ if (mTracks.size() >= (size_t)mMaxNumTracks) {
+ ALOGE("%s: out of track names (max = %d)", __func__, mMaxNumTracks);
+ return -1;
+ }
+
+ // get a new name for the track.
+ int name;
+ if (mUnusedNames.size() != 0) {
+ // reuse first name for deleted track.
+ auto it = mUnusedNames.begin();
+ name = *it;
+ (void)mUnusedNames.erase(it);
+ } else {
+ // we're fully populated, so create a new name.
+ name = mTracks.size();
+ }
+ ALOGV("add track (%d)", name);
+
+ auto t = std::make_shared<Track>();
+ mTracks[name] = t;
+
+ {
+ // TODO: move initialization to the Track constructor.
// assume default parameters for the track, except where noted below
- track_t* t = &mState.tracks[n];
t->needs = 0;
// Integer volume.
@@ -215,17 +161,12 @@
// no initialization needed
// t->buffer.frameCount
t->hook = NULL;
- t->in = NULL;
- t->resampler = NULL;
+ t->mIn = NULL;
t->sampleRate = mSampleRate;
// setParameter(name, TRACK, MAIN_BUFFER, mixBuffer) is required before enable(name)
t->mainBuffer = NULL;
t->auxBuffer = NULL;
t->mInputBufferProvider = NULL;
- t->mReformatBufferProvider = NULL;
- t->downmixerBufferProvider = NULL;
- t->mPostDownmixReformatBufferProvider = NULL;
- t->mTimestretchBufferProvider = NULL;
t->mMixerFormat = AUDIO_FORMAT_PCM_16_BIT;
t->mFormat = format;
t->mMixerInFormat = selectMixerInFormat(format);
@@ -243,91 +184,78 @@
// prepareForDownmix() may change mDownmixRequiresFormat
ALOGVV("mMixerFormat:%#x mMixerInFormat:%#x\n", t->mMixerFormat, t->mMixerInFormat);
t->prepareForReformat();
- mTrackNames |= 1 << n;
- return TRACK0 + n;
+ return TRACK0 + name;
}
- ALOGE("AudioMixer::getTrackName out of available tracks");
- return -1;
}
-void AudioMixer::invalidateState(uint32_t mask)
-{
- if (mask != 0) {
- mState.needsChanged |= mask;
- mState.hook = process__validate;
- }
- }
-
// Called when channel masks have changed for a track name
// TODO: Fix DownmixerBufferProvider not to (possibly) change mixer input format,
// which will simplify this logic.
bool AudioMixer::setChannelMasks(int name,
audio_channel_mask_t trackChannelMask, audio_channel_mask_t mixerChannelMask) {
- track_t &track = mState.tracks[name];
+ LOG_ALWAYS_FATAL_IF(mTracks.find(name) == mTracks.end(), "invalid name: %d", name);
+ const std::shared_ptr<Track> &track = mTracks[name];
- if (trackChannelMask == track.channelMask
- && mixerChannelMask == track.mMixerChannelMask) {
+ if (trackChannelMask == track->channelMask
+ && mixerChannelMask == track->mMixerChannelMask) {
return false; // no need to change
}
// always recompute for both channel masks even if only one has changed.
const uint32_t trackChannelCount = audio_channel_count_from_out_mask(trackChannelMask);
const uint32_t mixerChannelCount = audio_channel_count_from_out_mask(mixerChannelMask);
- const bool mixerChannelCountChanged = track.mMixerChannelCount != mixerChannelCount;
+ const bool mixerChannelCountChanged = track->mMixerChannelCount != mixerChannelCount;
ALOG_ASSERT((trackChannelCount <= MAX_NUM_CHANNELS_TO_DOWNMIX)
&& trackChannelCount
&& mixerChannelCount);
- track.channelMask = trackChannelMask;
- track.channelCount = trackChannelCount;
- track.mMixerChannelMask = mixerChannelMask;
- track.mMixerChannelCount = mixerChannelCount;
+ track->channelMask = trackChannelMask;
+ track->channelCount = trackChannelCount;
+ track->mMixerChannelMask = mixerChannelMask;
+ track->mMixerChannelCount = mixerChannelCount;
// channel masks have changed, does this track need a downmixer?
// update to try using our desired format (if we aren't already using it)
- const audio_format_t prevDownmixerFormat = track.mDownmixRequiresFormat;
- const status_t status = mState.tracks[name].prepareForDownmix();
+ const audio_format_t prevDownmixerFormat = track->mDownmixRequiresFormat;
+ const status_t status = track->prepareForDownmix();
ALOGE_IF(status != OK,
"prepareForDownmix error %d, track channel mask %#x, mixer channel mask %#x",
- status, track.channelMask, track.mMixerChannelMask);
+ status, track->channelMask, track->mMixerChannelMask);
- if (prevDownmixerFormat != track.mDownmixRequiresFormat) {
- track.prepareForReformat(); // because of downmixer, track format may change!
+ if (prevDownmixerFormat != track->mDownmixRequiresFormat) {
+ track->prepareForReformat(); // because of downmixer, track format may change!
}
- if (track.resampler && mixerChannelCountChanged) {
+ if (track->mResampler.get() != nullptr && mixerChannelCountChanged) {
// resampler channels may have changed.
- const uint32_t resetToSampleRate = track.sampleRate;
- delete track.resampler;
- track.resampler = NULL;
- track.sampleRate = mSampleRate; // without resampler, track rate is device sample rate.
+ const uint32_t resetToSampleRate = track->sampleRate;
+ track->mResampler.reset(nullptr);
+ track->sampleRate = mSampleRate; // without resampler, track rate is device sample rate.
// recreate the resampler with updated format, channels, saved sampleRate.
- track.setResampler(resetToSampleRate /*trackSampleRate*/, mSampleRate /*devSampleRate*/);
+ track->setResampler(resetToSampleRate /*trackSampleRate*/, mSampleRate /*devSampleRate*/);
}
return true;
}
-void AudioMixer::track_t::unprepareForDownmix() {
+void AudioMixer::Track::unprepareForDownmix() {
ALOGV("AudioMixer::unprepareForDownmix(%p)", this);
- if (mPostDownmixReformatBufferProvider != nullptr) {
+ if (mPostDownmixReformatBufferProvider.get() != nullptr) {
// release any buffers held by the mPostDownmixReformatBufferProvider
- // before deallocating the downmixerBufferProvider.
+ // before deallocating the mDownmixerBufferProvider.
mPostDownmixReformatBufferProvider->reset();
}
mDownmixRequiresFormat = AUDIO_FORMAT_INVALID;
- if (downmixerBufferProvider != NULL) {
+ if (mDownmixerBufferProvider.get() != nullptr) {
// this track had previously been configured with a downmixer, delete it
- ALOGV(" deleting old downmixer");
- delete downmixerBufferProvider;
- downmixerBufferProvider = NULL;
+ mDownmixerBufferProvider.reset(nullptr);
reconfigureBufferProviders();
} else {
ALOGV(" nothing to do, no downmixer to delete");
}
}
-status_t AudioMixer::track_t::prepareForDownmix()
+status_t AudioMixer::Track::prepareForDownmix()
{
ALOGV("AudioMixer::prepareForDownmix(%p) with mask 0x%x",
this, channelMask);
@@ -345,40 +273,35 @@
if (audio_channel_mask_get_representation(channelMask)
== AUDIO_CHANNEL_REPRESENTATION_POSITION
&& DownmixerBufferProvider::isMultichannelCapable()) {
- DownmixerBufferProvider* pDbp = new DownmixerBufferProvider(channelMask,
+ mDownmixerBufferProvider.reset(new DownmixerBufferProvider(channelMask,
mMixerChannelMask,
AUDIO_FORMAT_PCM_16_BIT /* TODO: use mMixerInFormat, now only PCM 16 */,
- sampleRate, sessionId, kCopyBufferFrameCount);
-
- if (pDbp->isValid()) { // if constructor completed properly
+ sampleRate, sessionId, kCopyBufferFrameCount));
+ if (static_cast<DownmixerBufferProvider *>(mDownmixerBufferProvider.get())->isValid()) {
mDownmixRequiresFormat = AUDIO_FORMAT_PCM_16_BIT; // PCM 16 bit required for downmix
- downmixerBufferProvider = pDbp;
reconfigureBufferProviders();
return NO_ERROR;
}
- delete pDbp;
+ // mDownmixerBufferProvider reset below.
}
// Effect downmixer does not accept the channel conversion. Let's use our remixer.
- RemixBufferProvider* pRbp = new RemixBufferProvider(channelMask,
- mMixerChannelMask, mMixerInFormat, kCopyBufferFrameCount);
+ mDownmixerBufferProvider.reset(new RemixBufferProvider(channelMask,
+ mMixerChannelMask, mMixerInFormat, kCopyBufferFrameCount));
// Remix always finds a conversion whereas Downmixer effect above may fail.
- downmixerBufferProvider = pRbp;
reconfigureBufferProviders();
return NO_ERROR;
}
-void AudioMixer::track_t::unprepareForReformat() {
+void AudioMixer::Track::unprepareForReformat() {
ALOGV("AudioMixer::unprepareForReformat(%p)", this);
bool requiresReconfigure = false;
- if (mReformatBufferProvider != NULL) {
- delete mReformatBufferProvider;
- mReformatBufferProvider = NULL;
+ if (mReformatBufferProvider.get() != nullptr) {
+ mReformatBufferProvider.reset(nullptr);
requiresReconfigure = true;
}
- if (mPostDownmixReformatBufferProvider != NULL) {
- delete mPostDownmixReformatBufferProvider;
- mPostDownmixReformatBufferProvider = NULL;
+ if (mPostDownmixReformatBufferProvider.get() != nullptr) {
+ mPostDownmixReformatBufferProvider.reset(nullptr);
requiresReconfigure = true;
}
if (requiresReconfigure) {
@@ -386,7 +309,7 @@
}
}
-status_t AudioMixer::track_t::prepareForReformat()
+status_t AudioMixer::Track::prepareForReformat()
{
ALOGV("AudioMixer::prepareForReformat(%p) with format %#x", this, mFormat);
// discard previous reformatters
@@ -396,19 +319,19 @@
? mDownmixRequiresFormat : mMixerInFormat;
bool requiresReconfigure = false;
if (mFormat != targetFormat) {
- mReformatBufferProvider = new ReformatBufferProvider(
+ mReformatBufferProvider.reset(new ReformatBufferProvider(
audio_channel_count_from_out_mask(channelMask),
mFormat,
targetFormat,
- kCopyBufferFrameCount);
+ kCopyBufferFrameCount));
requiresReconfigure = true;
}
if (targetFormat != mMixerInFormat) {
- mPostDownmixReformatBufferProvider = new ReformatBufferProvider(
+ mPostDownmixReformatBufferProvider.reset(new ReformatBufferProvider(
audio_channel_count_from_out_mask(mMixerChannelMask),
targetFormat,
mMixerInFormat,
- kCopyBufferFrameCount);
+ kCopyBufferFrameCount));
requiresReconfigure = true;
}
if (requiresReconfigure) {
@@ -417,74 +340,63 @@
return NO_ERROR;
}
-void AudioMixer::track_t::reconfigureBufferProviders()
+void AudioMixer::Track::reconfigureBufferProviders()
{
bufferProvider = mInputBufferProvider;
- if (mReformatBufferProvider) {
+ if (mReformatBufferProvider.get() != nullptr) {
mReformatBufferProvider->setBufferProvider(bufferProvider);
- bufferProvider = mReformatBufferProvider;
+ bufferProvider = mReformatBufferProvider.get();
}
- if (downmixerBufferProvider) {
- downmixerBufferProvider->setBufferProvider(bufferProvider);
- bufferProvider = downmixerBufferProvider;
+ if (mDownmixerBufferProvider.get() != nullptr) {
+ mDownmixerBufferProvider->setBufferProvider(bufferProvider);
+ bufferProvider = mDownmixerBufferProvider.get();
}
- if (mPostDownmixReformatBufferProvider) {
+ if (mPostDownmixReformatBufferProvider.get() != nullptr) {
mPostDownmixReformatBufferProvider->setBufferProvider(bufferProvider);
- bufferProvider = mPostDownmixReformatBufferProvider;
+ bufferProvider = mPostDownmixReformatBufferProvider.get();
}
- if (mTimestretchBufferProvider) {
+ if (mTimestretchBufferProvider.get() != nullptr) {
mTimestretchBufferProvider->setBufferProvider(bufferProvider);
- bufferProvider = mTimestretchBufferProvider;
+ bufferProvider = mTimestretchBufferProvider.get();
}
}
void AudioMixer::deleteTrackName(int name)
{
- ALOGV("AudioMixer::deleteTrackName(%d)", name);
name -= TRACK0;
- LOG_ALWAYS_FATAL_IF(name < 0 || name >= (int)MAX_NUM_TRACKS, "bad track name %d", name);
+ LOG_ALWAYS_FATAL_IF(mTracks.find(name) == mTracks.end(), "invalid name: %d", name);
ALOGV("deleteTrackName(%d)", name);
- track_t& track(mState.tracks[ name ]);
- if (track.enabled) {
- track.enabled = false;
- invalidateState(1<<name);
+
+ if (mTracks[name]->enabled) {
+ invalidate();
}
- // delete the resampler
- delete track.resampler;
- track.resampler = NULL;
- // delete the downmixer
- mState.tracks[name].unprepareForDownmix();
- // delete the reformatter
- mState.tracks[name].unprepareForReformat();
- // delete the timestretch provider
- delete track.mTimestretchBufferProvider;
- track.mTimestretchBufferProvider = NULL;
- mTrackNames &= ~(1<<name);
+ mTracks.erase(name); // deallocate track
+ mUnusedNames.emplace(name); // recycle name
}
void AudioMixer::enable(int name)
{
name -= TRACK0;
- ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name);
- track_t& track = mState.tracks[name];
+ LOG_ALWAYS_FATAL_IF(mTracks.find(name) == mTracks.end(), "invalid name: %d", name);
+ const std::shared_ptr<Track> &track = mTracks[name];
- if (!track.enabled) {
- track.enabled = true;
+ if (!track->enabled) {
+ track->enabled = true;
ALOGV("enable(%d)", name);
- invalidateState(1 << name);
+ invalidate();
}
}
void AudioMixer::disable(int name)
{
name -= TRACK0;
- ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name);
- track_t& track = mState.tracks[name];
+ LOG_ALWAYS_FATAL_IF(mTracks.find(name) == mTracks.end(), "invalid name: %d", name);
+ const std::shared_ptr<Track> &track = mTracks[name];
- if (track.enabled) {
- track.enabled = false;
+ if (track->enabled) {
+ track->enabled = false;
ALOGV("disable(%d)", name);
- invalidateState(1 << name);
+ invalidate();
}
}
@@ -562,7 +474,8 @@
ALOGD_IF(*pPrevVolume != *pSetVolume, "previous float ramp hasn't finished,"
" prev:%f set_to:%f", *pPrevVolume, *pSetVolume);
const float inc = (newVolume - *pPrevVolume) / ramp; // could be inf, nan, subnormal
- const float maxv = max(newVolume, *pPrevVolume); // could be inf, cannot be nan, subnormal
+ // could be inf, cannot be nan, subnormal
+ const float maxv = std::max(newVolume, *pPrevVolume);
if (isnormal(inc) // inc must be a normal number (no subnormals, infinite, nan)
&& maxv + inc != maxv) { // inc must make forward progress
@@ -616,8 +529,8 @@
void AudioMixer::setParameter(int name, int target, int param, void *value)
{
name -= TRACK0;
- ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name);
- track_t& track = mState.tracks[name];
+ LOG_ALWAYS_FATAL_IF(mTracks.find(name) == mTracks.end(), "invalid name: %d", name);
+ const std::shared_ptr<Track> &track = mTracks[name];
int valueInt = static_cast<int>(reinterpret_cast<uintptr_t>(value));
int32_t *valueBuf = reinterpret_cast<int32_t*>(value);
@@ -629,33 +542,33 @@
case CHANNEL_MASK: {
const audio_channel_mask_t trackChannelMask =
static_cast<audio_channel_mask_t>(valueInt);
- if (setChannelMasks(name, trackChannelMask, track.mMixerChannelMask)) {
+ if (setChannelMasks(name, trackChannelMask, track->mMixerChannelMask)) {
ALOGV("setParameter(TRACK, CHANNEL_MASK, %x)", trackChannelMask);
- invalidateState(1 << name);
+ invalidate();
}
} break;
case MAIN_BUFFER:
- if (track.mainBuffer != valueBuf) {
- track.mainBuffer = valueBuf;
+ if (track->mainBuffer != valueBuf) {
+ track->mainBuffer = valueBuf;
ALOGV("setParameter(TRACK, MAIN_BUFFER, %p)", valueBuf);
- invalidateState(1 << name);
+ invalidate();
}
break;
case AUX_BUFFER:
- if (track.auxBuffer != valueBuf) {
- track.auxBuffer = valueBuf;
+ if (track->auxBuffer != valueBuf) {
+ track->auxBuffer = valueBuf;
ALOGV("setParameter(TRACK, AUX_BUFFER, %p)", valueBuf);
- invalidateState(1 << name);
+ invalidate();
}
break;
case FORMAT: {
audio_format_t format = static_cast<audio_format_t>(valueInt);
- if (track.mFormat != format) {
+ if (track->mFormat != format) {
ALOG_ASSERT(audio_is_linear_pcm(format), "Invalid format %#x", format);
- track.mFormat = format;
+ track->mFormat = format;
ALOGV("setParameter(TRACK, FORMAT, %#x)", format);
- track.prepareForReformat();
- invalidateState(1 << name);
+ track->prepareForReformat();
+ invalidate();
}
} break;
// FIXME do we want to support setting the downmix type from AudioFlinger?
@@ -664,17 +577,17 @@
break */
case MIXER_FORMAT: {
audio_format_t format = static_cast<audio_format_t>(valueInt);
- if (track.mMixerFormat != format) {
- track.mMixerFormat = format;
+ if (track->mMixerFormat != format) {
+ track->mMixerFormat = format;
ALOGV("setParameter(TRACK, MIXER_FORMAT, %#x)", format);
}
} break;
case MIXER_CHANNEL_MASK: {
const audio_channel_mask_t mixerChannelMask =
static_cast<audio_channel_mask_t>(valueInt);
- if (setChannelMasks(name, track.channelMask, mixerChannelMask)) {
+ if (setChannelMasks(name, track->channelMask, mixerChannelMask)) {
ALOGV("setParameter(TRACK, MIXER_CHANNEL_MASK, %#x)", mixerChannelMask);
- invalidateState(1 << name);
+ invalidate();
}
} break;
default:
@@ -686,21 +599,20 @@
switch (param) {
case SAMPLE_RATE:
ALOG_ASSERT(valueInt > 0, "bad sample rate %d", valueInt);
- if (track.setResampler(uint32_t(valueInt), mSampleRate)) {
+ if (track->setResampler(uint32_t(valueInt), mSampleRate)) {
ALOGV("setParameter(RESAMPLE, SAMPLE_RATE, %u)",
uint32_t(valueInt));
- invalidateState(1 << name);
+ invalidate();
}
break;
case RESET:
- track.resetResampler();
- invalidateState(1 << name);
+ track->resetResampler();
+ invalidate();
break;
case REMOVE:
- delete track.resampler;
- track.resampler = NULL;
- track.sampleRate = mSampleRate;
- invalidateState(1 << name);
+ track->mResampler.reset(nullptr);
+ track->sampleRate = mSampleRate;
+ invalidate();
break;
default:
LOG_ALWAYS_FATAL("setParameter resample: bad param %d", param);
@@ -712,26 +624,28 @@
switch (param) {
case AUXLEVEL:
if (setVolumeRampVariables(*reinterpret_cast<float*>(value),
- target == RAMP_VOLUME ? mState.frameCount : 0,
- &track.auxLevel, &track.prevAuxLevel, &track.auxInc,
- &track.mAuxLevel, &track.mPrevAuxLevel, &track.mAuxInc)) {
+ target == RAMP_VOLUME ? mFrameCount : 0,
+ &track->auxLevel, &track->prevAuxLevel, &track->auxInc,
+ &track->mAuxLevel, &track->mPrevAuxLevel, &track->mAuxInc)) {
ALOGV("setParameter(%s, AUXLEVEL: %04x)",
- target == VOLUME ? "VOLUME" : "RAMP_VOLUME", track.auxLevel);
- invalidateState(1 << name);
+ target == VOLUME ? "VOLUME" : "RAMP_VOLUME", track->auxLevel);
+ invalidate();
}
break;
default:
if ((unsigned)param >= VOLUME0 && (unsigned)param < VOLUME0 + MAX_NUM_VOLUMES) {
if (setVolumeRampVariables(*reinterpret_cast<float*>(value),
- target == RAMP_VOLUME ? mState.frameCount : 0,
- &track.volume[param - VOLUME0], &track.prevVolume[param - VOLUME0],
- &track.volumeInc[param - VOLUME0],
- &track.mVolume[param - VOLUME0], &track.mPrevVolume[param - VOLUME0],
- &track.mVolumeInc[param - VOLUME0])) {
+ target == RAMP_VOLUME ? mFrameCount : 0,
+ &track->volume[param - VOLUME0],
+ &track->prevVolume[param - VOLUME0],
+ &track->volumeInc[param - VOLUME0],
+ &track->mVolume[param - VOLUME0],
+ &track->mPrevVolume[param - VOLUME0],
+ &track->mVolumeInc[param - VOLUME0])) {
ALOGV("setParameter(%s, VOLUME%d: %04x)",
target == VOLUME ? "VOLUME" : "RAMP_VOLUME", param - VOLUME0,
- track.volume[param - VOLUME0]);
- invalidateState(1 << name);
+ track->volume[param - VOLUME0]);
+ invalidate();
}
} else {
LOG_ALWAYS_FATAL("setParameter volume: bad param %d", param);
@@ -744,16 +658,16 @@
const AudioPlaybackRate *playbackRate =
reinterpret_cast<AudioPlaybackRate*>(value);
ALOGW_IF(!isAudioPlaybackRateValid(*playbackRate),
- "bad parameters speed %f, pitch %f",playbackRate->mSpeed,
- playbackRate->mPitch);
- if (track.setPlaybackRate(*playbackRate)) {
+ "bad parameters speed %f, pitch %f",
+ playbackRate->mSpeed, playbackRate->mPitch);
+ if (track->setPlaybackRate(*playbackRate)) {
ALOGV("setParameter(TIMESTRETCH, PLAYBACK_RATE, STRETCH_MODE, FALLBACK_MODE "
"%f %f %d %d",
playbackRate->mSpeed,
playbackRate->mPitch,
playbackRate->mStretchMode,
playbackRate->mFallbackMode);
- // invalidateState(1 << name);
+ // invalidate(); (should not require reconfigure)
}
} break;
default:
@@ -766,12 +680,12 @@
}
}
-bool AudioMixer::track_t::setResampler(uint32_t trackSampleRate, uint32_t devSampleRate)
+bool AudioMixer::Track::setResampler(uint32_t trackSampleRate, uint32_t devSampleRate)
{
- if (trackSampleRate != devSampleRate || resampler != NULL) {
+ if (trackSampleRate != devSampleRate || mResampler.get() != nullptr) {
if (sampleRate != trackSampleRate) {
sampleRate = trackSampleRate;
- if (resampler == NULL) {
+ if (mResampler.get() == nullptr) {
ALOGV("Creating resampler from track %d Hz to device %d Hz",
trackSampleRate, devSampleRate);
AudioResampler::src_quality quality;
@@ -787,15 +701,15 @@
// TODO: Remove MONO_HACK. Resampler sees #channels after the downmixer
// but if none exists, it is the channel count (1 for mono).
- const int resamplerChannelCount = downmixerBufferProvider != NULL
+ const int resamplerChannelCount = mDownmixerBufferProvider.get() != nullptr
? mMixerChannelCount : channelCount;
ALOGVV("Creating resampler:"
" format(%#x) channels(%d) devSampleRate(%u) quality(%d)\n",
mMixerInFormat, resamplerChannelCount, devSampleRate, quality);
- resampler = AudioResampler::create(
+ mResampler.reset(AudioResampler::create(
mMixerInFormat,
resamplerChannelCount,
- devSampleRate, quality);
+ devSampleRate, quality));
}
return true;
}
@@ -803,25 +717,25 @@
return false;
}
-bool AudioMixer::track_t::setPlaybackRate(const AudioPlaybackRate &playbackRate)
+bool AudioMixer::Track::setPlaybackRate(const AudioPlaybackRate &playbackRate)
{
- if ((mTimestretchBufferProvider == NULL &&
+ if ((mTimestretchBufferProvider.get() == nullptr &&
fabs(playbackRate.mSpeed - mPlaybackRate.mSpeed) < AUDIO_TIMESTRETCH_SPEED_MIN_DELTA &&
fabs(playbackRate.mPitch - mPlaybackRate.mPitch) < AUDIO_TIMESTRETCH_PITCH_MIN_DELTA) ||
isAudioPlaybackRateEqual(playbackRate, mPlaybackRate)) {
return false;
}
mPlaybackRate = playbackRate;
- if (mTimestretchBufferProvider == NULL) {
+ if (mTimestretchBufferProvider.get() == nullptr) {
// TODO: Remove MONO_HACK. Resampler sees #channels after the downmixer
// but if none exists, it is the channel count (1 for mono).
- const int timestretchChannelCount = downmixerBufferProvider != NULL
+ const int timestretchChannelCount = mDownmixerBufferProvider.get() != nullptr
? mMixerChannelCount : channelCount;
- mTimestretchBufferProvider = new TimestretchBufferProvider(timestretchChannelCount,
- mMixerInFormat, sampleRate, playbackRate);
+ mTimestretchBufferProvider.reset(new TimestretchBufferProvider(timestretchChannelCount,
+ mMixerInFormat, sampleRate, playbackRate));
reconfigureBufferProviders();
} else {
- static_cast<TimestretchBufferProvider*>(mTimestretchBufferProvider)
+ static_cast<TimestretchBufferProvider*>(mTimestretchBufferProvider.get())
->setPlaybackRate(playbackRate);
}
return true;
@@ -840,7 +754,7 @@
*
* There is a bit of duplicated code here, but it keeps backward compatibility.
*/
-inline void AudioMixer::track_t::adjustVolumeRamp(bool aux, bool useFloat)
+inline void AudioMixer::Track::adjustVolumeRamp(bool aux, bool useFloat)
{
if (useFloat) {
for (uint32_t i = 0; i < MAX_NUM_VOLUMES; i++) {
@@ -895,8 +809,9 @@
size_t AudioMixer::getUnreleasedFrames(int name) const
{
name -= TRACK0;
- if (uint32_t(name) < MAX_NUM_TRACKS) {
- return mState.tracks[name].getUnreleasedFrames();
+ const auto it = mTracks.find(name);
+ if (it != mTracks.end()) {
+ return it->second->getUnreleasedFrames();
}
return 0;
}
@@ -904,87 +819,63 @@
void AudioMixer::setBufferProvider(int name, AudioBufferProvider* bufferProvider)
{
name -= TRACK0;
- ALOG_ASSERT(uint32_t(name) < MAX_NUM_TRACKS, "bad track name %d", name);
+ const std::shared_ptr<Track> &track = mTracks[name];
- if (mState.tracks[name].mInputBufferProvider == bufferProvider) {
+ if (track->mInputBufferProvider == bufferProvider) {
return; // don't reset any buffer providers if identical.
}
- if (mState.tracks[name].mReformatBufferProvider != NULL) {
- mState.tracks[name].mReformatBufferProvider->reset();
- } else if (mState.tracks[name].downmixerBufferProvider != NULL) {
- mState.tracks[name].downmixerBufferProvider->reset();
- } else if (mState.tracks[name].mPostDownmixReformatBufferProvider != NULL) {
- mState.tracks[name].mPostDownmixReformatBufferProvider->reset();
- } else if (mState.tracks[name].mTimestretchBufferProvider != NULL) {
- mState.tracks[name].mTimestretchBufferProvider->reset();
+ if (track->mReformatBufferProvider.get() != nullptr) {
+ track->mReformatBufferProvider->reset();
+ } else if (track->mDownmixerBufferProvider != nullptr) {
+ track->mDownmixerBufferProvider->reset();
+ } else if (track->mPostDownmixReformatBufferProvider.get() != nullptr) {
+ track->mPostDownmixReformatBufferProvider->reset();
+ } else if (track->mTimestretchBufferProvider.get() != nullptr) {
+ track->mTimestretchBufferProvider->reset();
}
- mState.tracks[name].mInputBufferProvider = bufferProvider;
- mState.tracks[name].reconfigureBufferProviders();
+ track->mInputBufferProvider = bufferProvider;
+ track->reconfigureBufferProviders();
}
-
-void AudioMixer::process()
+void AudioMixer::process__validate()
{
- mState.hook(&mState);
-}
-
-
-void AudioMixer::process__validate(state_t* state)
-{
- ALOGW_IF(!state->needsChanged,
- "in process__validate() but nothing's invalid");
-
- uint32_t changed = state->needsChanged;
- state->needsChanged = 0; // clear the validation flag
-
- // recompute which tracks are enabled / disabled
- uint32_t enabled = 0;
- uint32_t disabled = 0;
- while (changed) {
- const int i = 31 - __builtin_clz(changed);
- const uint32_t mask = 1<<i;
- changed &= ~mask;
- track_t& t = state->tracks[i];
- (t.enabled ? enabled : disabled) |= mask;
- }
- state->enabledTracks &= ~disabled;
- state->enabledTracks |= enabled;
-
- // compute everything we need...
- int countActiveTracks = 0;
// TODO: fix all16BitsStereNoResample logic to
// either properly handle muted tracks (it should ignore them)
// or remove altogether as an obsolete optimization.
bool all16BitsStereoNoResample = true;
bool resampling = false;
bool volumeRamp = false;
- uint32_t en = state->enabledTracks;
- while (en) {
- const int i = 31 - __builtin_clz(en);
- en &= ~(1<<i);
- countActiveTracks++;
- track_t& t = state->tracks[i];
+ mEnabled.clear();
+ mGroups.clear();
+ for (const auto &pair : mTracks) {
+ const int name = pair.first;
+ const std::shared_ptr<Track> &t = pair.second;
+ if (!t->enabled) continue;
+
+ mEnabled.emplace_back(name); // we add to mEnabled in order of name.
+ mGroups[t->mainBuffer].emplace_back(name); // mGroups also in order of name.
+
uint32_t n = 0;
// FIXME can overflow (mask is only 3 bits)
- n |= NEEDS_CHANNEL_1 + t.channelCount - 1;
- if (t.doesResample()) {
+ n |= NEEDS_CHANNEL_1 + t->channelCount - 1;
+ if (t->doesResample()) {
n |= NEEDS_RESAMPLE;
}
- if (t.auxLevel != 0 && t.auxBuffer != NULL) {
+ if (t->auxLevel != 0 && t->auxBuffer != NULL) {
n |= NEEDS_AUX;
}
- if (t.volumeInc[0]|t.volumeInc[1]) {
+ if (t->volumeInc[0]|t->volumeInc[1]) {
volumeRamp = true;
- } else if (!t.doesResample() && t.volumeRL == 0) {
+ } else if (!t->doesResample() && t->volumeRL == 0) {
n |= NEEDS_MUTE;
}
- t.needs = n;
+ t->needs = n;
if (n & NEEDS_MUTE) {
- t.hook = track__nop;
+ t->hook = &Track::track__nop;
} else {
if (n & NEEDS_AUX) {
all16BitsStereoNoResample = false;
@@ -992,23 +883,23 @@
if (n & NEEDS_RESAMPLE) {
all16BitsStereoNoResample = false;
resampling = true;
- t.hook = getTrackHook(TRACKTYPE_RESAMPLE, t.mMixerChannelCount,
- t.mMixerInFormat, t.mMixerFormat);
+ t->hook = Track::getTrackHook(TRACKTYPE_RESAMPLE, t->mMixerChannelCount,
+ t->mMixerInFormat, t->mMixerFormat);
ALOGV_IF((n & NEEDS_CHANNEL_COUNT__MASK) > NEEDS_CHANNEL_2,
"Track %d needs downmix + resample", i);
} else {
if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_1){
- t.hook = getTrackHook(
- (t.mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO // TODO: MONO_HACK
- && t.channelMask == AUDIO_CHANNEL_OUT_MONO)
+ t->hook = Track::getTrackHook(
+ (t->mMixerChannelMask == AUDIO_CHANNEL_OUT_STEREO // TODO: MONO_HACK
+ && t->channelMask == AUDIO_CHANNEL_OUT_MONO)
? TRACKTYPE_NORESAMPLEMONO : TRACKTYPE_NORESAMPLE,
- t.mMixerChannelCount,
- t.mMixerInFormat, t.mMixerFormat);
+ t->mMixerChannelCount,
+ t->mMixerInFormat, t->mMixerFormat);
all16BitsStereoNoResample = false;
}
if ((n & NEEDS_CHANNEL_COUNT__MASK) >= NEEDS_CHANNEL_2){
- t.hook = getTrackHook(TRACKTYPE_NORESAMPLE, t.mMixerChannelCount,
- t.mMixerInFormat, t.mMixerFormat);
+ t->hook = Track::getTrackHook(TRACKTYPE_NORESAMPLE, t->mMixerChannelCount,
+ t->mMixerInFormat, t->mMixerFormat);
ALOGV_IF((n & NEEDS_CHANNEL_COUNT__MASK) > NEEDS_CHANNEL_2,
"Track %d needs downmix", i);
}
@@ -1017,137 +908,125 @@
}
// select the processing hooks
- state->hook = process__nop;
- if (countActiveTracks > 0) {
+ mHook = &AudioMixer::process__nop;
+ if (mEnabled.size() > 0) {
if (resampling) {
- if (!state->outputTemp) {
- state->outputTemp = new int32_t[MAX_NUM_CHANNELS * state->frameCount];
+ if (mOutputTemp.get() == nullptr) {
+ mOutputTemp.reset(new int32_t[MAX_NUM_CHANNELS * mFrameCount]);
}
- if (!state->resampleTemp) {
- state->resampleTemp = new int32_t[MAX_NUM_CHANNELS * state->frameCount];
+ if (mResampleTemp.get() == nullptr) {
+ mResampleTemp.reset(new int32_t[MAX_NUM_CHANNELS * mFrameCount]);
}
- state->hook = process__genericResampling;
+ mHook = &AudioMixer::process__genericResampling;
} else {
- if (state->outputTemp) {
- delete [] state->outputTemp;
- state->outputTemp = NULL;
- }
- if (state->resampleTemp) {
- delete [] state->resampleTemp;
- state->resampleTemp = NULL;
- }
- state->hook = process__genericNoResampling;
+ // we keep temp arrays around.
+ mHook = &AudioMixer::process__genericNoResampling;
if (all16BitsStereoNoResample && !volumeRamp) {
- if (countActiveTracks == 1) {
- const int i = 31 - __builtin_clz(state->enabledTracks);
- track_t& t = state->tracks[i];
- if ((t.needs & NEEDS_MUTE) == 0) {
+ if (mEnabled.size() == 1) {
+ const std::shared_ptr<Track> &t = mTracks[mEnabled[0]];
+ if ((t->needs & NEEDS_MUTE) == 0) {
// The check prevents a muted track from acquiring a process hook.
//
// This is dangerous if the track is MONO as that requires
// special case handling due to implicit channel duplication.
// Stereo or Multichannel should actually be fine here.
- state->hook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK,
- t.mMixerChannelCount, t.mMixerInFormat, t.mMixerFormat);
+ mHook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK,
+ t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat);
}
}
}
}
}
- ALOGV("mixer configuration change: %d activeTracks (%08x) "
+ ALOGV("mixer configuration change: %zu "
"all16BitsStereoNoResample=%d, resampling=%d, volumeRamp=%d",
- countActiveTracks, state->enabledTracks,
- all16BitsStereoNoResample, resampling, volumeRamp);
+ mEnabled.size(), all16BitsStereoNoResample, resampling, volumeRamp);
- state->hook(state);
+ process();
// Now that the volume ramp has been done, set optimal state and
// track hooks for subsequent mixer process
- if (countActiveTracks > 0) {
+ if (mEnabled.size() > 0) {
bool allMuted = true;
- uint32_t en = state->enabledTracks;
- while (en) {
- const int i = 31 - __builtin_clz(en);
- en &= ~(1<<i);
- track_t& t = state->tracks[i];
- if (!t.doesResample() && t.volumeRL == 0) {
- t.needs |= NEEDS_MUTE;
- t.hook = track__nop;
+
+ for (const int name : mEnabled) {
+ const std::shared_ptr<Track> &t = mTracks[name];
+ if (!t->doesResample() && t->volumeRL == 0) {
+ t->needs |= NEEDS_MUTE;
+ t->hook = &Track::track__nop;
} else {
allMuted = false;
}
}
if (allMuted) {
- state->hook = process__nop;
+ mHook = &AudioMixer::process__nop;
} else if (all16BitsStereoNoResample) {
- if (countActiveTracks == 1) {
- const int i = 31 - __builtin_clz(state->enabledTracks);
- track_t& t = state->tracks[i];
+ if (mEnabled.size() == 1) {
+ //const int i = 31 - __builtin_clz(enabledTracks);
+ const std::shared_ptr<Track> &t = mTracks[mEnabled[0]];
// Muted single tracks handled by allMuted above.
- state->hook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK,
- t.mMixerChannelCount, t.mMixerInFormat, t.mMixerFormat);
+ mHook = getProcessHook(PROCESSTYPE_NORESAMPLEONETRACK,
+ t->mMixerChannelCount, t->mMixerInFormat, t->mMixerFormat);
}
}
}
}
-
-void AudioMixer::track__genericResample(track_t* t, int32_t* out, size_t outFrameCount,
- int32_t* temp, int32_t* aux)
+void AudioMixer::Track::track__genericResample(
+ int32_t* out, size_t outFrameCount, int32_t* temp, int32_t* aux)
{
ALOGVV("track__genericResample\n");
- t->resampler->setSampleRate(t->sampleRate);
+ mResampler->setSampleRate(sampleRate);
// ramp gain - resample to temp buffer and scale/mix in 2nd step
if (aux != NULL) {
// always resample with unity gain when sending to auxiliary buffer to be able
// to apply send level after resampling
- t->resampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT);
- memset(temp, 0, outFrameCount * t->mMixerChannelCount * sizeof(int32_t));
- t->resampler->resample(temp, outFrameCount, t->bufferProvider);
- if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1]|t->auxInc)) {
- volumeRampStereo(t, out, outFrameCount, temp, aux);
+ mResampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT);
+ memset(temp, 0, outFrameCount * mMixerChannelCount * sizeof(int32_t));
+ mResampler->resample(temp, outFrameCount, bufferProvider);
+ if (CC_UNLIKELY(volumeInc[0]|volumeInc[1]|auxInc)) {
+ volumeRampStereo(out, outFrameCount, temp, aux);
} else {
- volumeStereo(t, out, outFrameCount, temp, aux);
+ volumeStereo(out, outFrameCount, temp, aux);
}
} else {
- if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1])) {
- t->resampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT);
+ if (CC_UNLIKELY(volumeInc[0]|volumeInc[1])) {
+ mResampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT);
memset(temp, 0, outFrameCount * MAX_NUM_CHANNELS * sizeof(int32_t));
- t->resampler->resample(temp, outFrameCount, t->bufferProvider);
- volumeRampStereo(t, out, outFrameCount, temp, aux);
+ mResampler->resample(temp, outFrameCount, bufferProvider);
+ volumeRampStereo(out, outFrameCount, temp, aux);
}
// constant gain
else {
- t->resampler->setVolume(t->mVolume[0], t->mVolume[1]);
- t->resampler->resample(out, outFrameCount, t->bufferProvider);
+ mResampler->setVolume(mVolume[0], mVolume[1]);
+ mResampler->resample(out, outFrameCount, bufferProvider);
}
}
}
-void AudioMixer::track__nop(track_t* t __unused, int32_t* out __unused,
+void AudioMixer::Track::track__nop(int32_t* out __unused,
size_t outFrameCount __unused, int32_t* temp __unused, int32_t* aux __unused)
{
}
-void AudioMixer::volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp,
- int32_t* aux)
+void AudioMixer::Track::volumeRampStereo(
+ int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux)
{
- int32_t vl = t->prevVolume[0];
- int32_t vr = t->prevVolume[1];
- const int32_t vlInc = t->volumeInc[0];
- const int32_t vrInc = t->volumeInc[1];
+ int32_t vl = prevVolume[0];
+ int32_t vr = prevVolume[1];
+ const int32_t vlInc = volumeInc[0];
+ const int32_t vrInc = volumeInc[1];
//ALOGD("[0] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d",
- // t, vlInc/65536.0f, vl/65536.0f, t->volume[0],
+ // t, vlInc/65536.0f, vl/65536.0f, volume[0],
// (vl + vlInc*frameCount)/65536.0f, frameCount);
// ramp volume
if (CC_UNLIKELY(aux != NULL)) {
- int32_t va = t->prevAuxLevel;
- const int32_t vaInc = t->auxInc;
+ int32_t va = prevAuxLevel;
+ const int32_t vaInc = auxInc;
int32_t l;
int32_t r;
@@ -1161,7 +1040,7 @@
vr += vrInc;
va += vaInc;
} while (--frameCount);
- t->prevAuxLevel = va;
+ prevAuxLevel = va;
} else {
do {
*out++ += (vl >> 16) * (*temp++ >> 12);
@@ -1170,19 +1049,19 @@
vr += vrInc;
} while (--frameCount);
}
- t->prevVolume[0] = vl;
- t->prevVolume[1] = vr;
- t->adjustVolumeRamp(aux != NULL);
+ prevVolume[0] = vl;
+ prevVolume[1] = vr;
+ adjustVolumeRamp(aux != NULL);
}
-void AudioMixer::volumeStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp,
- int32_t* aux)
+void AudioMixer::Track::volumeStereo(
+ int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux)
{
- const int16_t vl = t->volume[0];
- const int16_t vr = t->volume[1];
+ const int16_t vl = volume[0];
+ const int16_t vr = volume[1];
if (CC_UNLIKELY(aux != NULL)) {
- const int16_t va = t->auxLevel;
+ const int16_t va = auxLevel;
do {
int16_t l = (int16_t)(*temp++ >> 12);
int16_t r = (int16_t)(*temp++ >> 12);
@@ -1204,25 +1083,25 @@
}
}
-void AudioMixer::track__16BitsStereo(track_t* t, int32_t* out, size_t frameCount,
- int32_t* temp __unused, int32_t* aux)
+void AudioMixer::Track::track__16BitsStereo(
+ int32_t* out, size_t frameCount, int32_t* temp __unused, int32_t* aux)
{
ALOGVV("track__16BitsStereo\n");
- const int16_t *in = static_cast<const int16_t *>(t->in);
+ const int16_t *in = static_cast<const int16_t *>(mIn);
if (CC_UNLIKELY(aux != NULL)) {
int32_t l;
int32_t r;
// ramp gain
- if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1]|t->auxInc)) {
- int32_t vl = t->prevVolume[0];
- int32_t vr = t->prevVolume[1];
- int32_t va = t->prevAuxLevel;
- const int32_t vlInc = t->volumeInc[0];
- const int32_t vrInc = t->volumeInc[1];
- const int32_t vaInc = t->auxInc;
+ if (CC_UNLIKELY(volumeInc[0]|volumeInc[1]|auxInc)) {
+ int32_t vl = prevVolume[0];
+ int32_t vr = prevVolume[1];
+ int32_t va = prevAuxLevel;
+ const int32_t vlInc = volumeInc[0];
+ const int32_t vrInc = volumeInc[1];
+ const int32_t vaInc = auxInc;
// ALOGD("[1] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d",
- // t, vlInc/65536.0f, vl/65536.0f, t->volume[0],
+ // t, vlInc/65536.0f, vl/65536.0f, volume[0],
// (vl + vlInc*frameCount)/65536.0f, frameCount);
do {
@@ -1236,16 +1115,16 @@
va += vaInc;
} while (--frameCount);
- t->prevVolume[0] = vl;
- t->prevVolume[1] = vr;
- t->prevAuxLevel = va;
- t->adjustVolumeRamp(true);
+ prevVolume[0] = vl;
+ prevVolume[1] = vr;
+ prevAuxLevel = va;
+ adjustVolumeRamp(true);
}
// constant gain
else {
- const uint32_t vrl = t->volumeRL;
- const int16_t va = (int16_t)t->auxLevel;
+ const uint32_t vrl = volumeRL;
+ const int16_t va = (int16_t)auxLevel;
do {
uint32_t rl = *reinterpret_cast<const uint32_t *>(in);
int16_t a = (int16_t)(((int32_t)in[0] + in[1]) >> 1);
@@ -1259,14 +1138,14 @@
}
} else {
// ramp gain
- if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1])) {
- int32_t vl = t->prevVolume[0];
- int32_t vr = t->prevVolume[1];
- const int32_t vlInc = t->volumeInc[0];
- const int32_t vrInc = t->volumeInc[1];
+ if (CC_UNLIKELY(volumeInc[0]|volumeInc[1])) {
+ int32_t vl = prevVolume[0];
+ int32_t vr = prevVolume[1];
+ const int32_t vlInc = volumeInc[0];
+ const int32_t vrInc = volumeInc[1];
// ALOGD("[1] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d",
- // t, vlInc/65536.0f, vl/65536.0f, t->volume[0],
+ // t, vlInc/65536.0f, vl/65536.0f, volume[0],
// (vl + vlInc*frameCount)/65536.0f, frameCount);
do {
@@ -1276,14 +1155,14 @@
vr += vrInc;
} while (--frameCount);
- t->prevVolume[0] = vl;
- t->prevVolume[1] = vr;
- t->adjustVolumeRamp(false);
+ prevVolume[0] = vl;
+ prevVolume[1] = vr;
+ adjustVolumeRamp(false);
}
// constant gain
else {
- const uint32_t vrl = t->volumeRL;
+ const uint32_t vrl = volumeRL;
do {
uint32_t rl = *reinterpret_cast<const uint32_t *>(in);
in += 2;
@@ -1293,27 +1172,27 @@
} while (--frameCount);
}
}
- t->in = in;
+ mIn = in;
}
-void AudioMixer::track__16BitsMono(track_t* t, int32_t* out, size_t frameCount,
- int32_t* temp __unused, int32_t* aux)
+void AudioMixer::Track::track__16BitsMono(
+ int32_t* out, size_t frameCount, int32_t* temp __unused, int32_t* aux)
{
ALOGVV("track__16BitsMono\n");
- const int16_t *in = static_cast<int16_t const *>(t->in);
+ const int16_t *in = static_cast<int16_t const *>(mIn);
if (CC_UNLIKELY(aux != NULL)) {
// ramp gain
- if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1]|t->auxInc)) {
- int32_t vl = t->prevVolume[0];
- int32_t vr = t->prevVolume[1];
- int32_t va = t->prevAuxLevel;
- const int32_t vlInc = t->volumeInc[0];
- const int32_t vrInc = t->volumeInc[1];
- const int32_t vaInc = t->auxInc;
+ if (CC_UNLIKELY(volumeInc[0]|volumeInc[1]|auxInc)) {
+ int32_t vl = prevVolume[0];
+ int32_t vr = prevVolume[1];
+ int32_t va = prevAuxLevel;
+ const int32_t vlInc = volumeInc[0];
+ const int32_t vrInc = volumeInc[1];
+ const int32_t vaInc = auxInc;
// ALOGD("[2] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d",
- // t, vlInc/65536.0f, vl/65536.0f, t->volume[0],
+ // t, vlInc/65536.0f, vl/65536.0f, volume[0],
// (vl + vlInc*frameCount)/65536.0f, frameCount);
do {
@@ -1326,16 +1205,16 @@
va += vaInc;
} while (--frameCount);
- t->prevVolume[0] = vl;
- t->prevVolume[1] = vr;
- t->prevAuxLevel = va;
- t->adjustVolumeRamp(true);
+ prevVolume[0] = vl;
+ prevVolume[1] = vr;
+ prevAuxLevel = va;
+ adjustVolumeRamp(true);
}
// constant gain
else {
- const int16_t vl = t->volume[0];
- const int16_t vr = t->volume[1];
- const int16_t va = (int16_t)t->auxLevel;
+ const int16_t vl = volume[0];
+ const int16_t vr = volume[1];
+ const int16_t va = (int16_t)auxLevel;
do {
int16_t l = *in++;
out[0] = mulAdd(l, vl, out[0]);
@@ -1347,14 +1226,14 @@
}
} else {
// ramp gain
- if (CC_UNLIKELY(t->volumeInc[0]|t->volumeInc[1])) {
- int32_t vl = t->prevVolume[0];
- int32_t vr = t->prevVolume[1];
- const int32_t vlInc = t->volumeInc[0];
- const int32_t vrInc = t->volumeInc[1];
+ if (CC_UNLIKELY(volumeInc[0]|volumeInc[1])) {
+ int32_t vl = prevVolume[0];
+ int32_t vr = prevVolume[1];
+ const int32_t vlInc = volumeInc[0];
+ const int32_t vrInc = volumeInc[1];
// ALOGD("[2] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d",
- // t, vlInc/65536.0f, vl/65536.0f, t->volume[0],
+ // t, vlInc/65536.0f, vl/65536.0f, volume[0],
// (vl + vlInc*frameCount)/65536.0f, frameCount);
do {
@@ -1365,14 +1244,14 @@
vr += vrInc;
} while (--frameCount);
- t->prevVolume[0] = vl;
- t->prevVolume[1] = vr;
- t->adjustVolumeRamp(false);
+ prevVolume[0] = vl;
+ prevVolume[1] = vr;
+ adjustVolumeRamp(false);
}
// constant gain
else {
- const int16_t vl = t->volume[0];
- const int16_t vr = t->volume[1];
+ const int16_t vl = volume[0];
+ const int16_t vr = volume[1];
do {
int16_t l = *in++;
out[0] = mulAdd(l, vl, out[0]);
@@ -1381,274 +1260,214 @@
} while (--frameCount);
}
}
- t->in = in;
+ mIn = in;
}
// no-op case
-void AudioMixer::process__nop(state_t* state)
+void AudioMixer::process__nop()
{
ALOGVV("process__nop\n");
- uint32_t e0 = state->enabledTracks;
- while (e0) {
+
+ for (const auto &pair : mGroups) {
// process by group of tracks with same output buffer to
// avoid multiple memset() on same buffer
- uint32_t e1 = e0, e2 = e0;
- int i = 31 - __builtin_clz(e1);
- {
- track_t& t1 = state->tracks[i];
- e2 &= ~(1<<i);
- while (e2) {
- i = 31 - __builtin_clz(e2);
- e2 &= ~(1<<i);
- track_t& t2 = state->tracks[i];
- if (CC_UNLIKELY(t2.mainBuffer != t1.mainBuffer)) {
- e1 &= ~(1<<i);
- }
- }
- e0 &= ~(e1);
+ const auto &group = pair.second;
- memset(t1.mainBuffer, 0, state->frameCount * t1.mMixerChannelCount
- * audio_bytes_per_sample(t1.mMixerFormat));
- }
+ const std::shared_ptr<Track> &t = mTracks[group[0]];
+ memset(t->mainBuffer, 0,
+ mFrameCount * t->mMixerChannelCount
+ * audio_bytes_per_sample(t->mMixerFormat));
- while (e1) {
- i = 31 - __builtin_clz(e1);
- e1 &= ~(1<<i);
- {
- track_t& t3 = state->tracks[i];
- size_t outFrames = state->frameCount;
- while (outFrames) {
- t3.buffer.frameCount = outFrames;
- t3.bufferProvider->getNextBuffer(&t3.buffer);
- if (t3.buffer.raw == NULL) break;
- outFrames -= t3.buffer.frameCount;
- t3.bufferProvider->releaseBuffer(&t3.buffer);
- }
+ // now consume data
+ for (const int name : group) {
+ const std::shared_ptr<Track> &t = mTracks[name];
+ size_t outFrames = mFrameCount;
+ while (outFrames) {
+ t->buffer.frameCount = outFrames;
+ t->bufferProvider->getNextBuffer(&t->buffer);
+ if (t->buffer.raw == NULL) break;
+ outFrames -= t->buffer.frameCount;
+ t->bufferProvider->releaseBuffer(&t->buffer);
}
}
}
}
// generic code without resampling
-void AudioMixer::process__genericNoResampling(state_t* state)
+void AudioMixer::process__genericNoResampling()
{
ALOGVV("process__genericNoResampling\n");
int32_t outTemp[BLOCKSIZE * MAX_NUM_CHANNELS] __attribute__((aligned(32)));
- // acquire each track's buffer
- uint32_t enabledTracks = state->enabledTracks;
- uint32_t e0 = enabledTracks;
- while (e0) {
- const int i = 31 - __builtin_clz(e0);
- e0 &= ~(1<<i);
- track_t& t = state->tracks[i];
- t.buffer.frameCount = state->frameCount;
- t.bufferProvider->getNextBuffer(&t.buffer);
- t.frameCount = t.buffer.frameCount;
- t.in = t.buffer.raw;
- }
+ for (const auto &pair : mGroups) {
+ // process by group of tracks with same output main buffer to
+ // avoid multiple memset() on same buffer
+ const auto &group = pair.second;
- e0 = enabledTracks;
- while (e0) {
- // process by group of tracks with same output buffer to
- // optimize cache use
- uint32_t e1 = e0, e2 = e0;
- int j = 31 - __builtin_clz(e1);
- track_t& t1 = state->tracks[j];
- e2 &= ~(1<<j);
- while (e2) {
- j = 31 - __builtin_clz(e2);
- e2 &= ~(1<<j);
- track_t& t2 = state->tracks[j];
- if (CC_UNLIKELY(t2.mainBuffer != t1.mainBuffer)) {
- e1 &= ~(1<<j);
- }
+ // acquire buffer
+ for (const int name : group) {
+ const std::shared_ptr<Track> &t = mTracks[name];
+ t->buffer.frameCount = mFrameCount;
+ t->bufferProvider->getNextBuffer(&t->buffer);
+ t->frameCount = t->buffer.frameCount;
+ t->mIn = t->buffer.raw;
}
- e0 &= ~(e1);
- // this assumes output 16 bits stereo, no resampling
- int32_t *out = t1.mainBuffer;
+
+ int32_t *out = (int *)pair.first;
size_t numFrames = 0;
do {
- const size_t frameCount = min((size_t)BLOCKSIZE, state->frameCount - numFrames);
+ const size_t frameCount = std::min((size_t)BLOCKSIZE, mFrameCount - numFrames);
memset(outTemp, 0, sizeof(outTemp));
- e2 = e1;
- while (e2) {
- const int i = 31 - __builtin_clz(e2);
- e2 &= ~(1<<i);
- track_t& t = state->tracks[i];
- size_t outFrames = frameCount;
+ for (const int name : group) {
+ const std::shared_ptr<Track> &t = mTracks[name];
int32_t *aux = NULL;
- if (CC_UNLIKELY(t.needs & NEEDS_AUX)) {
- aux = t.auxBuffer + numFrames;
+ if (CC_UNLIKELY(t->needs & NEEDS_AUX)) {
+ aux = t->auxBuffer + numFrames;
}
- while (outFrames) {
- // t.in == NULL can happen if the track was flushed just after having
+ for (int outFrames = frameCount; outFrames > 0; ) {
+ // t->in == nullptr can happen if the track was flushed just after having
// been enabled for mixing.
- if (t.in == NULL) {
- enabledTracks &= ~(1<<i);
- e1 &= ~(1<<i);
+ if (t->mIn == nullptr) {
break;
}
- size_t inFrames = (t.frameCount > outFrames)?outFrames:t.frameCount;
+ size_t inFrames = (t->frameCount > outFrames)?outFrames:t->frameCount;
if (inFrames > 0) {
- t.hook(&t, outTemp + (frameCount - outFrames) * t.mMixerChannelCount,
- inFrames, state->resampleTemp, aux);
- t.frameCount -= inFrames;
+ (t.get()->*t->hook)(
+ outTemp + (frameCount - outFrames) * t->mMixerChannelCount,
+ inFrames, mResampleTemp.get() /* naked ptr */, aux);
+ t->frameCount -= inFrames;
outFrames -= inFrames;
if (CC_UNLIKELY(aux != NULL)) {
aux += inFrames;
}
}
- if (t.frameCount == 0 && outFrames) {
- t.bufferProvider->releaseBuffer(&t.buffer);
- t.buffer.frameCount = (state->frameCount - numFrames) -
+ if (t->frameCount == 0 && outFrames) {
+ t->bufferProvider->releaseBuffer(&t->buffer);
+ t->buffer.frameCount = (mFrameCount - numFrames) -
(frameCount - outFrames);
- t.bufferProvider->getNextBuffer(&t.buffer);
- t.in = t.buffer.raw;
- if (t.in == NULL) {
- enabledTracks &= ~(1<<i);
- e1 &= ~(1<<i);
+ t->bufferProvider->getNextBuffer(&t->buffer);
+ t->mIn = t->buffer.raw;
+ if (t->mIn == nullptr) {
break;
}
- t.frameCount = t.buffer.frameCount;
+ t->frameCount = t->buffer.frameCount;
}
}
}
- convertMixerFormat(out, t1.mMixerFormat, outTemp, t1.mMixerInFormat,
- frameCount * t1.mMixerChannelCount);
+ const std::shared_ptr<Track> &t1 = mTracks[group[0]];
+ convertMixerFormat(out, t1->mMixerFormat, outTemp, t1->mMixerInFormat,
+ frameCount * t1->mMixerChannelCount);
// TODO: fix ugly casting due to choice of out pointer type
out = reinterpret_cast<int32_t*>((uint8_t*)out
- + frameCount * t1.mMixerChannelCount
- * audio_bytes_per_sample(t1.mMixerFormat));
+ + frameCount * t1->mMixerChannelCount
+ * audio_bytes_per_sample(t1->mMixerFormat));
numFrames += frameCount;
- } while (numFrames < state->frameCount);
- }
+ } while (numFrames < mFrameCount);
- // release each track's buffer
- e0 = enabledTracks;
- while (e0) {
- const int i = 31 - __builtin_clz(e0);
- e0 &= ~(1<<i);
- track_t& t = state->tracks[i];
- t.bufferProvider->releaseBuffer(&t.buffer);
+ // release each track's buffer
+ for (const int name : group) {
+ const std::shared_ptr<Track> &t = mTracks[name];
+ t->bufferProvider->releaseBuffer(&t->buffer);
+ }
}
}
-
// generic code with resampling
-void AudioMixer::process__genericResampling(state_t* state)
+void AudioMixer::process__genericResampling()
{
ALOGVV("process__genericResampling\n");
- // this const just means that local variable outTemp doesn't change
- int32_t* const outTemp = state->outputTemp;
- size_t numFrames = state->frameCount;
+ int32_t * const outTemp = mOutputTemp.get(); // naked ptr
+ size_t numFrames = mFrameCount;
- uint32_t e0 = state->enabledTracks;
- while (e0) {
- // process by group of tracks with same output buffer
- // to optimize cache use
- uint32_t e1 = e0, e2 = e0;
- int j = 31 - __builtin_clz(e1);
- track_t& t1 = state->tracks[j];
- e2 &= ~(1<<j);
- while (e2) {
- j = 31 - __builtin_clz(e2);
- e2 &= ~(1<<j);
- track_t& t2 = state->tracks[j];
- if (CC_UNLIKELY(t2.mainBuffer != t1.mainBuffer)) {
- e1 &= ~(1<<j);
- }
- }
- e0 &= ~(e1);
- int32_t *out = t1.mainBuffer;
- memset(outTemp, 0, sizeof(*outTemp) * t1.mMixerChannelCount * state->frameCount);
- while (e1) {
- const int i = 31 - __builtin_clz(e1);
- e1 &= ~(1<<i);
- track_t& t = state->tracks[i];
+ for (const auto &pair : mGroups) {
+ const auto &group = pair.second;
+ const std::shared_ptr<Track> &t1 = mTracks[group[0]];
+
+ // clear temp buffer
+ memset(outTemp, 0, sizeof(*outTemp) * t1->mMixerChannelCount * mFrameCount);
+ for (const int name : group) {
+ const std::shared_ptr<Track> &t = mTracks[name];
int32_t *aux = NULL;
- if (CC_UNLIKELY(t.needs & NEEDS_AUX)) {
- aux = t.auxBuffer;
+ if (CC_UNLIKELY(t->needs & NEEDS_AUX)) {
+ aux = t->auxBuffer;
}
// this is a little goofy, on the resampling case we don't
// acquire/release the buffers because it's done by
// the resampler.
- if (t.needs & NEEDS_RESAMPLE) {
- t.hook(&t, outTemp, numFrames, state->resampleTemp, aux);
+ if (t->needs & NEEDS_RESAMPLE) {
+ (t.get()->*t->hook)(outTemp, numFrames, mResampleTemp.get() /* naked ptr */, aux);
} else {
size_t outFrames = 0;
while (outFrames < numFrames) {
- t.buffer.frameCount = numFrames - outFrames;
- t.bufferProvider->getNextBuffer(&t.buffer);
- t.in = t.buffer.raw;
- // t.in == NULL can happen if the track was flushed just after having
+ t->buffer.frameCount = numFrames - outFrames;
+ t->bufferProvider->getNextBuffer(&t->buffer);
+ t->mIn = t->buffer.raw;
+ // t->mIn == nullptr can happen if the track was flushed just after having
// been enabled for mixing.
- if (t.in == NULL) break;
+ if (t->mIn == nullptr) break;
if (CC_UNLIKELY(aux != NULL)) {
aux += outFrames;
}
- t.hook(&t, outTemp + outFrames * t.mMixerChannelCount, t.buffer.frameCount,
- state->resampleTemp, aux);
- outFrames += t.buffer.frameCount;
- t.bufferProvider->releaseBuffer(&t.buffer);
+ (t.get()->*t->hook)(
+ outTemp + outFrames * t->mMixerChannelCount, t->buffer.frameCount,
+ mResampleTemp.get() /* naked ptr */, aux);
+ outFrames += t->buffer.frameCount;
+ t->bufferProvider->releaseBuffer(&t->buffer);
}
}
}
- convertMixerFormat(out, t1.mMixerFormat,
- outTemp, t1.mMixerInFormat, numFrames * t1.mMixerChannelCount);
+ convertMixerFormat(t1->mainBuffer, t1->mMixerFormat,
+ outTemp, t1->mMixerInFormat, numFrames * t1->mMixerChannelCount);
}
}
// one track, 16 bits stereo without resampling is the most common case
-void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state)
+void AudioMixer::process__oneTrack16BitsStereoNoResampling()
{
- ALOGVV("process__OneTrack16BitsStereoNoResampling\n");
- // This method is only called when state->enabledTracks has exactly
- // one bit set. The asserts below would verify this, but are commented out
- // since the whole point of this method is to optimize performance.
- //ALOG_ASSERT(0 != state->enabledTracks, "no tracks enabled");
- const int i = 31 - __builtin_clz(state->enabledTracks);
- //ALOG_ASSERT((1 << i) == state->enabledTracks, "more than 1 track enabled");
- const track_t& t = state->tracks[i];
+ ALOGVV("process__oneTrack16BitsStereoNoResampling\n");
+ LOG_ALWAYS_FATAL_IF(mEnabled.size() != 0,
+ "%zu != 1 tracks enabled", mEnabled.size());
+ const int name = mEnabled[0];
+ const std::shared_ptr<Track> &t = mTracks[name];
- AudioBufferProvider::Buffer& b(t.buffer);
+ AudioBufferProvider::Buffer& b(t->buffer);
- int32_t* out = t.mainBuffer;
+ int32_t* out = t->mainBuffer;
float *fout = reinterpret_cast<float*>(out);
- size_t numFrames = state->frameCount;
+ size_t numFrames = mFrameCount;
- const int16_t vl = t.volume[0];
- const int16_t vr = t.volume[1];
- const uint32_t vrl = t.volumeRL;
+ const int16_t vl = t->volume[0];
+ const int16_t vr = t->volume[1];
+ const uint32_t vrl = t->volumeRL;
while (numFrames) {
b.frameCount = numFrames;
- t.bufferProvider->getNextBuffer(&b);
+ t->bufferProvider->getNextBuffer(&b);
const int16_t *in = b.i16;
// in == NULL can happen if the track was flushed just after having
// been enabled for mixing.
if (in == NULL || (((uintptr_t)in) & 3)) {
- if ( AUDIO_FORMAT_PCM_FLOAT == t.mMixerFormat ) {
+ if ( AUDIO_FORMAT_PCM_FLOAT == t->mMixerFormat ) {
memset((char*)fout, 0, numFrames
- * t.mMixerChannelCount * audio_bytes_per_sample(t.mMixerFormat));
+ * t->mMixerChannelCount * audio_bytes_per_sample(t->mMixerFormat));
} else {
memset((char*)out, 0, numFrames
- * t.mMixerChannelCount * audio_bytes_per_sample(t.mMixerFormat));
+ * t->mMixerChannelCount * audio_bytes_per_sample(t->mMixerFormat));
}
ALOGE_IF((((uintptr_t)in) & 3),
- "process__OneTrack16BitsStereoNoResampling: misaligned buffer"
+ "process__oneTrack16BitsStereoNoResampling: misaligned buffer"
" %p track %d, channels %d, needs %08x, volume %08x vfl %f vfr %f",
- in, i, t.channelCount, t.needs, vrl, t.mVolume[0], t.mVolume[1]);
+ in, name, t->channelCount, t->needs, vrl, t->mVolume[0], t->mVolume[1]);
return;
}
size_t outFrames = b.frameCount;
- switch (t.mMixerFormat) {
+ switch (t->mMixerFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
do {
uint32_t rl = *reinterpret_cast<const uint32_t *>(in);
@@ -1686,10 +1505,10 @@
}
break;
default:
- LOG_ALWAYS_FATAL("bad mixer format: %d", t.mMixerFormat);
+ LOG_ALWAYS_FATAL("bad mixer format: %d", t->mMixerFormat);
}
numFrames -= b.frameCount;
- t.bufferProvider->releaseBuffer(&b);
+ t->bufferProvider->releaseBuffer(&b);
}
}
@@ -1800,42 +1619,42 @@
*/
template <int MIXTYPE, bool USEFLOATVOL, bool ADJUSTVOL,
typename TO, typename TI, typename TA>
-void AudioMixer::volumeMix(TO *out, size_t outFrames,
- const TI *in, TA *aux, bool ramp, AudioMixer::track_t *t)
+void AudioMixer::Track::volumeMix(TO *out, size_t outFrames,
+ const TI *in, TA *aux, bool ramp)
{
if (USEFLOATVOL) {
if (ramp) {
- volumeRampMulti<MIXTYPE>(t->mMixerChannelCount, out, outFrames, in, aux,
- t->mPrevVolume, t->mVolumeInc,
+ volumeRampMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux,
+ mPrevVolume, mVolumeInc,
#ifdef FLOAT_AUX
- &t->mPrevAuxLevel, t->mAuxInc
+ &mPrevAuxLevel, mAuxInc
#else
- &t->prevAuxLevel, t->auxInc
+ &prevAuxLevel, auxInc
#endif
);
if (ADJUSTVOL) {
- t->adjustVolumeRamp(aux != NULL, true);
+ adjustVolumeRamp(aux != NULL, true);
}
} else {
- volumeMulti<MIXTYPE>(t->mMixerChannelCount, out, outFrames, in, aux,
- t->mVolume,
+ volumeMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux,
+ mVolume,
#ifdef FLOAT_AUX
- t->mAuxLevel
+ mAuxLevel
#else
- t->auxLevel
+ auxLevel
#endif
);
}
} else {
if (ramp) {
- volumeRampMulti<MIXTYPE>(t->mMixerChannelCount, out, outFrames, in, aux,
- t->prevVolume, t->volumeInc, &t->prevAuxLevel, t->auxInc);
+ volumeRampMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux,
+ prevVolume, volumeInc, &prevAuxLevel, auxInc);
if (ADJUSTVOL) {
- t->adjustVolumeRamp(aux != NULL);
+ adjustVolumeRamp(aux != NULL);
}
} else {
- volumeMulti<MIXTYPE>(t->mMixerChannelCount, out, outFrames, in, aux,
- t->volume, t->auxLevel);
+ volumeMulti<MIXTYPE>(mMixerChannelCount, out, outFrames, in, aux,
+ volume, auxLevel);
}
}
}
@@ -1850,19 +1669,18 @@
* TA: int32_t (Q4.27)
*/
template <int MIXTYPE, typename TO, typename TI, typename TA>
-void AudioMixer::process_NoResampleOneTrack(state_t* state)
+void AudioMixer::process__noResampleOneTrack()
{
- ALOGVV("process_NoResampleOneTrack\n");
- // CLZ is faster than CTZ on ARM, though really not sure if true after 31 - clz.
- const int i = 31 - __builtin_clz(state->enabledTracks);
- ALOG_ASSERT((1 << i) == state->enabledTracks, "more than 1 track enabled");
- track_t *t = &state->tracks[i];
+ ALOGVV("process__noResampleOneTrack\n");
+ LOG_ALWAYS_FATAL_IF(mEnabled.size() != 1,
+ "%zu != 1 tracks enabled", mEnabled.size());
+ const std::shared_ptr<Track> &t = mTracks[mEnabled[0]];
const uint32_t channels = t->mMixerChannelCount;
TO* out = reinterpret_cast<TO*>(t->mainBuffer);
TA* aux = reinterpret_cast<TA*>(t->auxBuffer);
const bool ramp = t->needsRamp();
- for (size_t numFrames = state->frameCount; numFrames; ) {
+ for (size_t numFrames = mFrameCount; numFrames > 0; ) {
AudioBufferProvider::Buffer& b(t->buffer);
// get input buffer
b.frameCount = numFrames;
@@ -1874,15 +1692,15 @@
if (in == NULL || (((uintptr_t)in) & 3)) {
memset(out, 0, numFrames
* channels * audio_bytes_per_sample(t->mMixerFormat));
- ALOGE_IF((((uintptr_t)in) & 3), "process_NoResampleOneTrack: bus error: "
+ ALOGE_IF((((uintptr_t)in) & 3), "process__noResampleOneTrack: bus error: "
"buffer %p track %p, channels %d, needs %#x",
- in, t, t->channelCount, t->needs);
+ in, &t, t->channelCount, t->needs);
return;
}
const size_t outFrames = b.frameCount;
- volumeMix<MIXTYPE, is_same<TI, float>::value /* USEFLOATVOL */, false /* ADJUSTVOL */> (
- out, outFrames, in, aux, ramp, t);
+ t->volumeMix<MIXTYPE, is_same<TI, float>::value /* USEFLOATVOL */, false /* ADJUSTVOL */> (
+ out, outFrames, in, aux, ramp);
out += outFrames * channels;
if (aux != NULL) {
@@ -1907,30 +1725,30 @@
* TA: int32_t (Q4.27) or float
*/
template <int MIXTYPE, typename TO, typename TI, typename TA>
-void AudioMixer::track__Resample(track_t* t, TO* out, size_t outFrameCount, TO* temp, TA* aux)
+void AudioMixer::Track::track__Resample(TO* out, size_t outFrameCount, TO* temp, TA* aux)
{
ALOGVV("track__Resample\n");
- t->resampler->setSampleRate(t->sampleRate);
- const bool ramp = t->needsRamp();
+ mResampler->setSampleRate(sampleRate);
+ const bool ramp = needsRamp();
if (ramp || aux != NULL) {
// if ramp: resample with unity gain to temp buffer and scale/mix in 2nd step.
// if aux != NULL: resample with unity gain to temp buffer then apply send level.
- t->resampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT);
- memset(temp, 0, outFrameCount * t->mMixerChannelCount * sizeof(TO));
- t->resampler->resample((int32_t*)temp, outFrameCount, t->bufferProvider);
+ mResampler->setVolume(UNITY_GAIN_FLOAT, UNITY_GAIN_FLOAT);
+ memset(temp, 0, outFrameCount * mMixerChannelCount * sizeof(TO));
+ mResampler->resample((int32_t*)temp, outFrameCount, bufferProvider);
volumeMix<MIXTYPE, is_same<TI, float>::value /* USEFLOATVOL */, true /* ADJUSTVOL */>(
- out, outFrameCount, temp, aux, ramp, t);
+ out, outFrameCount, temp, aux, ramp);
} else { // constant volume gain
- t->resampler->setVolume(t->mVolume[0], t->mVolume[1]);
- t->resampler->resample((int32_t*)out, outFrameCount, t->bufferProvider);
+ mResampler->setVolume(mVolume[0], mVolume[1]);
+ mResampler->resample((int32_t*)out, outFrameCount, bufferProvider);
}
}
/* This track hook is called to mix a track, when no resampling is required.
- * The input buffer should be present in t->in.
+ * The input buffer should be present in in.
*
* MIXTYPE (see AudioMixerOps.h MIXTYPE_* enumeration)
* TO: int32_t (Q4.27) or float
@@ -1938,25 +1756,25 @@
* TA: int32_t (Q4.27) or float
*/
template <int MIXTYPE, typename TO, typename TI, typename TA>
-void AudioMixer::track__NoResample(track_t* t, TO* out, size_t frameCount,
- TO* temp __unused, TA* aux)
+void AudioMixer::Track::track__NoResample(TO* out, size_t frameCount, TO* temp __unused, TA* aux)
{
ALOGVV("track__NoResample\n");
- const TI *in = static_cast<const TI *>(t->in);
+ const TI *in = static_cast<const TI *>(mIn);
volumeMix<MIXTYPE, is_same<TI, float>::value /* USEFLOATVOL */, true /* ADJUSTVOL */>(
- out, frameCount, in, aux, t->needsRamp(), t);
+ out, frameCount, in, aux, needsRamp());
// MIXTYPE_MONOEXPAND reads a single input channel and expands to NCHAN output channels.
// MIXTYPE_MULTI reads NCHAN input channels and places to NCHAN output channels.
- in += (MIXTYPE == MIXTYPE_MONOEXPAND) ? frameCount : frameCount * t->mMixerChannelCount;
- t->in = in;
+ in += (MIXTYPE == MIXTYPE_MONOEXPAND) ? frameCount : frameCount * mMixerChannelCount;
+ mIn = in;
}
/* The Mixer engine generates either int32_t (Q4_27) or float data.
* We use this function to convert the engine buffers
* to the desired mixer output format, either int16_t (Q.15) or float.
*/
+/* static */
void AudioMixer::convertMixerFormat(void *out, audio_format_t mixerOutFormat,
void *in, audio_format_t mixerInFormat, size_t sampleCount)
{
@@ -1995,19 +1813,20 @@
/* Returns the proper track hook to use for mixing the track into the output buffer.
*/
-AudioMixer::hook_t AudioMixer::getTrackHook(int trackType, uint32_t channelCount,
+/* static */
+AudioMixer::hook_t AudioMixer::Track::getTrackHook(int trackType, uint32_t channelCount,
audio_format_t mixerInFormat, audio_format_t mixerOutFormat __unused)
{
if (!kUseNewMixer && channelCount == FCC_2 && mixerInFormat == AUDIO_FORMAT_PCM_16_BIT) {
switch (trackType) {
case TRACKTYPE_NOP:
- return track__nop;
+ return &Track::track__nop;
case TRACKTYPE_RESAMPLE:
- return track__genericResample;
+ return &Track::track__genericResample;
case TRACKTYPE_NORESAMPLEMONO:
- return track__16BitsMono;
+ return &Track::track__16BitsMono;
case TRACKTYPE_NORESAMPLE:
- return track__16BitsStereo;
+ return &Track::track__16BitsStereo;
default:
LOG_ALWAYS_FATAL("bad trackType: %d", trackType);
break;
@@ -2016,14 +1835,14 @@
LOG_ALWAYS_FATAL_IF(channelCount > MAX_NUM_CHANNELS);
switch (trackType) {
case TRACKTYPE_NOP:
- return track__nop;
+ return &Track::track__nop;
case TRACKTYPE_RESAMPLE:
switch (mixerInFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
- return (AudioMixer::hook_t)track__Resample<
+ return (AudioMixer::hook_t) &Track::track__Resample<
MIXTYPE_MULTI, float /*TO*/, float /*TI*/, TYPE_AUX>;
case AUDIO_FORMAT_PCM_16_BIT:
- return (AudioMixer::hook_t)track__Resample<
+ return (AudioMixer::hook_t) &Track::track__Resample<
MIXTYPE_MULTI, int32_t /*TO*/, int16_t /*TI*/, TYPE_AUX>;
default:
LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
@@ -2033,10 +1852,10 @@
case TRACKTYPE_NORESAMPLEMONO:
switch (mixerInFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
- return (AudioMixer::hook_t)track__NoResample<
+ return (AudioMixer::hook_t) &Track::track__NoResample<
MIXTYPE_MONOEXPAND, float /*TO*/, float /*TI*/, TYPE_AUX>;
case AUDIO_FORMAT_PCM_16_BIT:
- return (AudioMixer::hook_t)track__NoResample<
+ return (AudioMixer::hook_t) &Track::track__NoResample<
MIXTYPE_MONOEXPAND, int32_t /*TO*/, int16_t /*TI*/, TYPE_AUX>;
default:
LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
@@ -2046,10 +1865,10 @@
case TRACKTYPE_NORESAMPLE:
switch (mixerInFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
- return (AudioMixer::hook_t)track__NoResample<
+ return (AudioMixer::hook_t) &Track::track__NoResample<
MIXTYPE_MULTI, float /*TO*/, float /*TI*/, TYPE_AUX>;
case AUDIO_FORMAT_PCM_16_BIT:
- return (AudioMixer::hook_t)track__NoResample<
+ return (AudioMixer::hook_t) &Track::track__NoResample<
MIXTYPE_MULTI, int32_t /*TO*/, int16_t /*TI*/, TYPE_AUX>;
default:
LOG_ALWAYS_FATAL("bad mixerInFormat: %#x", mixerInFormat);
@@ -2070,7 +1889,9 @@
* a stereo output track, the input track cannot be MONO. This should be
* prevented by the caller.
*/
-AudioMixer::process_hook_t AudioMixer::getProcessHook(int processType, uint32_t channelCount,
+/* static */
+AudioMixer::process_hook_t AudioMixer::getProcessHook(
+ int processType, uint32_t channelCount,
audio_format_t mixerInFormat, audio_format_t mixerOutFormat)
{
if (processType != PROCESSTYPE_NORESAMPLEONETRACK) { // Only NORESAMPLEONETRACK
@@ -2078,17 +1899,17 @@
return NULL;
}
if (!kUseNewMixer && channelCount == FCC_2 && mixerInFormat == AUDIO_FORMAT_PCM_16_BIT) {
- return process__OneTrack16BitsStereoNoResampling;
+ return &AudioMixer::process__oneTrack16BitsStereoNoResampling;
}
LOG_ALWAYS_FATAL_IF(channelCount > MAX_NUM_CHANNELS);
switch (mixerInFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
switch (mixerOutFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
- return process_NoResampleOneTrack<
+ return &AudioMixer::process__noResampleOneTrack<
MIXTYPE_MULTI_SAVEONLY, float /*TO*/, float /*TI*/, TYPE_AUX>;
case AUDIO_FORMAT_PCM_16_BIT:
- return process_NoResampleOneTrack<
+ return &AudioMixer::process__noResampleOneTrack<
MIXTYPE_MULTI_SAVEONLY, int16_t /*TO*/, float /*TI*/, TYPE_AUX>;
default:
LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat);
@@ -2098,10 +1919,10 @@
case AUDIO_FORMAT_PCM_16_BIT:
switch (mixerOutFormat) {
case AUDIO_FORMAT_PCM_FLOAT:
- return process_NoResampleOneTrack<
+ return &AudioMixer::process__noResampleOneTrack<
MIXTYPE_MULTI_SAVEONLY, float /*TO*/, int16_t /*TI*/, TYPE_AUX>;
case AUDIO_FORMAT_PCM_16_BIT:
- return process_NoResampleOneTrack<
+ return &AudioMixer::process__noResampleOneTrack<
MIXTYPE_MULTI_SAVEONLY, int16_t /*TO*/, int16_t /*TI*/, TYPE_AUX>;
default:
LOG_ALWAYS_FATAL("bad mixerOutFormat: %#x", mixerOutFormat);
diff --git a/media/libheif/HeifDecoderImpl.cpp b/media/libheif/HeifDecoderImpl.cpp
index a63a2df..2dfbdca 100644
--- a/media/libheif/HeifDecoderImpl.cpp
+++ b/media/libheif/HeifDecoderImpl.cpp
@@ -22,6 +22,7 @@
#include <stdio.h>
#include <binder/IMemory.h>
+#include <binder/MemoryDealer.h>
#include <drm/drm_framework_common.h>
#include <media/IDataSource.h>
#include <media/mediametadataretriever.h>
diff --git a/media/libmedia/MediaCodecBuffer.cpp b/media/libmedia/MediaCodecBuffer.cpp
index 59d6164..68ae3ea 100644
--- a/media/libmedia/MediaCodecBuffer.cpp
+++ b/media/libmedia/MediaCodecBuffer.cpp
@@ -21,15 +21,13 @@
#include <media/MediaCodecBuffer.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/AMessage.h>
-#include <media/stagefright/foundation/MediaBufferBase.h>
namespace android {
MediaCodecBuffer::MediaCodecBuffer(const sp<AMessage> &format, const sp<ABuffer> &buffer)
: mMeta(new AMessage),
mFormat(format),
- mBuffer(buffer),
- mMediaBufferBase(nullptr) {
+ mBuffer(buffer) {
}
// ABuffer-like interface
@@ -58,20 +56,6 @@
return OK;
}
-MediaBufferBase *MediaCodecBuffer::getMediaBufferBase() {
- if (mMediaBufferBase != NULL) {
- mMediaBufferBase->add_ref();
- }
- return mMediaBufferBase;
-}
-
-void MediaCodecBuffer::setMediaBufferBase(MediaBufferBase *mediaBuffer) {
- if (mMediaBufferBase != NULL) {
- mMediaBufferBase->release();
- }
- mMediaBufferBase = mediaBuffer;
-}
-
sp<AMessage> MediaCodecBuffer::meta() {
return mMeta;
}
diff --git a/media/libmedia/MediaPlayer2Factory.cpp b/media/libmedia/MediaPlayer2Factory.cpp
index df567ce..ac115f6 100644
--- a/media/libmedia/MediaPlayer2Factory.cpp
+++ b/media/libmedia/MediaPlayer2Factory.cpp
@@ -34,9 +34,17 @@
namespace android {
Mutex MediaPlayer2Factory::sLock;
-MediaPlayer2Factory::tFactoryMap MediaPlayer2Factory::sFactoryMap;
+MediaPlayer2Factory::tFactoryMap *MediaPlayer2Factory::sFactoryMap;
bool MediaPlayer2Factory::sInitComplete = false;
+// static
+bool MediaPlayer2Factory::ensureInit_l() {
+ if (sFactoryMap == NULL) {
+ sFactoryMap = new (std::nothrow) tFactoryMap();
+ }
+ return (sFactoryMap != NULL);
+}
+
status_t MediaPlayer2Factory::registerFactory_l(IFactory* factory,
player2_type type) {
if (NULL == factory) {
@@ -45,13 +53,17 @@
return BAD_VALUE;
}
- if (sFactoryMap.indexOfKey(type) >= 0) {
+ if (!ensureInit_l()) {
+ return NO_INIT;
+ }
+
+ if (sFactoryMap->indexOfKey(type) >= 0) {
ALOGE("Failed to register MediaPlayer2Factory of type %d, type is"
" already registered.", type);
return ALREADY_EXISTS;
}
- if (sFactoryMap.add(type, factory) < 0) {
+ if (sFactoryMap->add(type, factory) < 0) {
ALOGE("Failed to register MediaPlayer2Factory of type %d, failed to add"
" to map.", type);
return UNKNOWN_ERROR;
@@ -64,31 +76,24 @@
return PLAYER2_NU_PLAYER2;
}
-status_t MediaPlayer2Factory::registerFactory(IFactory* factory,
- player2_type type) {
- Mutex::Autolock lock_(&sLock);
- return registerFactory_l(factory, type);
-}
-
-void MediaPlayer2Factory::unregisterFactory(player2_type type) {
- Mutex::Autolock lock_(&sLock);
- sFactoryMap.removeItem(type);
-}
-
#define GET_PLAYER_TYPE_IMPL(a...) \
Mutex::Autolock lock_(&sLock); \
\
player2_type ret = PLAYER2_STAGEFRIGHT_PLAYER; \
float bestScore = 0.0; \
\
- for (size_t i = 0; i < sFactoryMap.size(); ++i) { \
+ if (!ensureInit_l()) { \
+ return ret; \
+ } \
\
- IFactory* v = sFactoryMap.valueAt(i); \
+ for (size_t i = 0; i < sFactoryMap->size(); ++i) { \
+ \
+ IFactory* v = sFactoryMap->valueAt(i); \
float thisScore; \
CHECK(v != NULL); \
thisScore = v->scoreFactory(a, bestScore); \
if (thisScore > bestScore) { \
- ret = sFactoryMap.keyAt(i); \
+ ret = sFactoryMap->keyAt(i); \
bestScore = thisScore; \
} \
} \
@@ -133,13 +138,17 @@
status_t init_result;
Mutex::Autolock lock_(&sLock);
- if (sFactoryMap.indexOfKey(playerType) < 0) {
+ if (!ensureInit_l()) {
+ return NULL;
+ }
+
+ if (sFactoryMap->indexOfKey(playerType) < 0) {
ALOGE("Failed to create player object of type %d, no registered"
" factory", playerType);
return p;
}
- factory = sFactoryMap.valueFor(playerType);
+ factory = sFactoryMap->valueFor(playerType);
CHECK(NULL != factory);
p = factory->createPlayer(pid);
diff --git a/media/libmedia/MediaPlayer2Factory.h b/media/libmedia/MediaPlayer2Factory.h
index 799b5f3..416d241 100644
--- a/media/libmedia/MediaPlayer2Factory.h
+++ b/media/libmedia/MediaPlayer2Factory.h
@@ -50,9 +50,6 @@
virtual sp<MediaPlayer2Base> createPlayer(pid_t pid) = 0;
};
- static status_t registerFactory(IFactory* factory,
- player2_type type);
- static void unregisterFactory(player2_type type);
static player2_type getPlayerType(const sp<MediaPlayer2Engine>& client,
const char* url);
static player2_type getPlayerType(const sp<MediaPlayer2Engine>& client,
@@ -76,11 +73,13 @@
MediaPlayer2Factory() { }
+ static bool ensureInit_l();
+
static status_t registerFactory_l(IFactory* factory,
player2_type type);
static Mutex sLock;
- static tFactoryMap sFactoryMap;
+ static tFactoryMap *sFactoryMap;
static bool sInitComplete;
DISALLOW_EVIL_CONSTRUCTORS(MediaPlayer2Factory);
diff --git a/media/libmedia/include/media/CounterMetric.h b/media/libmedia/include/media/CounterMetric.h
new file mode 100644
index 0000000..b53470d
--- /dev/null
+++ b/media/libmedia/include/media/CounterMetric.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2018 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_COUNTER_METRIC_H_
+#define ANDROID_COUNTER_METRIC_H_
+
+#include <functional>
+#include <map>
+#include <string>
+
+#include <media/MediaAnalyticsItem.h>
+#include <utils/Log.h>
+
+namespace android {
+
+
+// The CounterMetric class is used to hold counts of operations or events.
+// A CounterMetric can break down counts by a dimension specified by the
+// application. E.g. an application may want to track counts broken out by
+// error code or the size of some parameter.
+//
+// Example:
+//
+// CounterMetric<status_t> workCounter;
+// workCounter("workCounterName", "result_status");
+//
+// status_t err = DoWork();
+//
+// // Increments the number of times called with the given error code.
+// workCounter.Increment(err);
+//
+// std::map<int, int64_t> values;
+// metric.ExportValues(
+// [&] (int attribute_value, int64_t value) {
+// values[attribute_value] = value;
+// });
+//
+// // Do something with the exported stat.
+//
+template<typename AttributeType>
+class CounterMetric {
+ public:
+ // Instantiate the counter with the given metric name and
+ // attribute names. |attribute_names| must not be null.
+ CounterMetric(
+ const std::string& metric_name,
+ const std::string& attribute_name)
+ : metric_name_(metric_name),
+ attribute_name_(attribute_name) {}
+
+ // Increment the count of times the operation occurred with this
+ // combination of attributes.
+ void Increment(AttributeType attribute) {
+ if (values_.find(attribute) == values_.end()) {
+ values_[attribute] = 1;
+ } else {
+ values_[attribute] = values_[attribute] + 1;
+ }
+ };
+
+ // Export the metrics to the provided |function|. Each value for Attribute
+ // has a separate count. As such, |function| will be called once per value
+ // of Attribute.
+ void ExportValues(
+ std::function<void (const AttributeType&,
+ const int64_t count)> function) const {
+ for (auto it = values_.begin(); it != values_.end(); it++) {
+ function(it->first, it->second);
+ }
+ }
+
+ const std::string& metric_name() const { return metric_name_; };
+
+ private:
+ const std::string metric_name_;
+ const std::string attribute_name_;
+ std::map<AttributeType, int64_t> values_;
+};
+
+} // namespace android
+
+#endif // ANDROID_COUNTER_METRIC_H_
diff --git a/media/libmedia/include/media/DrmHal.h b/media/libmedia/include/media/DrmHal.h
index 1a0553e..ec3a9b3 100644
--- a/media/libmedia/include/media/DrmHal.h
+++ b/media/libmedia/include/media/DrmHal.h
@@ -23,6 +23,7 @@
#include <android/hardware/drm/1.0/IDrmPluginListener.h>
#include <android/hardware/drm/1.0/IDrmFactory.h>
+#include <media/DrmMetrics.h>
#include <media/IDrm.h>
#include <media/IDrmClient.h>
#include <media/MediaAnalyticsItem.h>
@@ -96,10 +97,12 @@
Vector<uint8_t> &wrappedKey);
virtual status_t getSecureStops(List<Vector<uint8_t>> &secureStops);
+ virtual status_t getSecureStopIds(List<Vector<uint8_t>> &secureStopIds);
virtual status_t getSecureStop(Vector<uint8_t> const &ssid, Vector<uint8_t> &secureStop);
virtual status_t releaseSecureStops(Vector<uint8_t> const &ssRelease);
- virtual status_t releaseAllSecureStops();
+ virtual status_t removeSecureStop(Vector<uint8_t> const &ssid);
+ virtual status_t removeAllSecureStops();
virtual status_t getHdcpLevels(DrmPlugin::HdcpLevel *connectedLevel,
DrmPlugin::HdcpLevel *maxLevel) const;
@@ -180,6 +183,9 @@
sp<IDrmPlugin> mPlugin;
sp<drm::V1_1::IDrmPlugin> mPluginV1_1;
+ // Mutable to allow modification within GetPropertyByteArray.
+ mutable MediaDrmMetrics mMetrics;
+
Vector<Vector<uint8_t>> mOpenSessions;
void closeOpenSessions();
diff --git a/media/libmedia/include/media/DrmMetrics.h b/media/libmedia/include/media/DrmMetrics.h
new file mode 100644
index 0000000..bb7509b
--- /dev/null
+++ b/media/libmedia/include/media/DrmMetrics.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2018 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 DRM_METRICS_H_
+#define DRM_METRICS_H_
+
+#include <map>
+
+#include <android/hardware/drm/1.0/types.h>
+#include <media/CounterMetric.h>
+#include <media/EventMetric.h>
+
+namespace android {
+
+/**
+ * This class contains the definition of metrics captured within MediaDrm.
+ * It also contains a method for exporting all of the metrics to a
+ * MediaAnalyticsItem instance.
+ */
+class MediaDrmMetrics {
+ public:
+ explicit MediaDrmMetrics();
+ // Count of openSession calls.
+ CounterMetric<status_t> mOpenSessionCounter;
+ // Count of closeSession calls.
+ CounterMetric<status_t> mCloseSessionCounter;
+ // Count and timing of getKeyRequest calls.
+ EventMetric<status_t> mGetKeyRequestTiming;
+ // Count and timing of provideKeyResponse calls.
+ EventMetric<status_t> mProvideKeyResponseTiming;
+ // Count of getProvisionRequest calls.
+ CounterMetric<status_t> mGetProvisionRequestCounter;
+ // Count of provideProvisionResponse calls.
+ CounterMetric<status_t> mProvideProvisionResponseCounter;
+
+ // Count of key status events broken out by status type.
+ CounterMetric<::android::hardware::drm::V1_0::KeyStatusType>
+ mKeyStatusChangeCounter;
+ // Count of events broken out by event type
+ CounterMetric<::android::hardware::drm::V1_0::EventType> mEventCounter;
+
+ // Count getPropertyByteArray calls to retrieve the device unique id.
+ CounterMetric<status_t> mGetDeviceUniqueIdCounter;
+
+ // TODO: Add session start and end time support. These are a special case.
+
+ // Export the metrics to a MediaAnalyticsItem.
+ void Export(MediaAnalyticsItem* item);
+};
+
+} // namespace android
+
+#endif // DRM_METRICS_H_
diff --git a/media/libmedia/include/media/EventMetric.h b/media/libmedia/include/media/EventMetric.h
new file mode 100644
index 0000000..dbb736a
--- /dev/null
+++ b/media/libmedia/include/media/EventMetric.h
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2018 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_EVENT_METRIC_H_
+#define ANDROID_EVENT_METRIC_H_
+
+#include <media/MediaAnalyticsItem.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+// This is a simple holder for the statistics recorded in EventMetric.
+struct EventStatistics {
+ // The count of times the event occurred.
+ int64_t count;
+
+ // The minimum and maximum values recorded in the Record method.
+ double min;
+ double max;
+
+ // The average (mean) of all values recorded.
+ double mean;
+ // The sum of squared devation. Variance can be calculated from
+ // this value.
+ // var = sum_squared_deviation / count;
+ double sum_squared_deviation;
+};
+
+// The EventMetric class is used to accumulate stats about an event over time.
+// A common use case is to track clock timings for a method call or operation.
+// An EventMetric can break down stats by a dimension specified by the
+// application. E.g. an application may want to track counts broken out by
+// error code or the size of some parameter.
+//
+// Example:
+//
+// struct C {
+// status_t DoWork() {
+// unsigned long start_time = now();
+// status_t result;
+//
+// // DO WORK and determine result;
+//
+// work_event_.Record(now() - start_time, result);
+//
+// return result;
+// }
+// EventMetric<status_t> work_event_;
+// };
+//
+// C c;
+// c.DoWork();
+//
+// std::map<int, int64_t> values;
+// metric.ExportValues(
+// [&] (int attribute_value, int64_t value) {
+// values[attribute_value] = value;
+// });
+// // Do something with the exported stat.
+//
+template<typename AttributeType>
+class EventMetric {
+ public:
+ // Instantiate the counter with the given metric name and
+ // attribute names. |attribute_names| must not be null.
+ EventMetric(
+ const std::string& metric_name,
+ const std::string& attribute_name)
+ : metric_name_(metric_name),
+ attribute_name_(attribute_name) {}
+
+ // Increment the count of times the operation occurred with this
+ // combination of attributes.
+ void Record(double value, AttributeType attribute) {
+ if (values_.find(attribute) != values_.end()) {
+ EventStatistics* stats = values_[attribute].get();
+ // Using method of provisional means.
+ double deviation = value - stats->mean;
+ stats->mean = stats->mean + (deviation / stats->count);
+ stats->sum_squared_deviation =
+ stats->sum_squared_deviation + (deviation * (value - stats->mean));
+ stats->count++;
+
+ stats->min = stats->min < value ? stats->min : value;
+ stats->max = stats->max > value ? stats->max : value;
+ } else {
+ std::unique_ptr<EventStatistics> stats =
+ std::make_unique<EventStatistics>();
+ stats->count = 1;
+ stats->min = value;
+ stats->max = value;
+ stats->mean = value;
+ stats->sum_squared_deviation = 0;
+ values_[attribute] = std::move(stats);
+ }
+ };
+
+ // Export the metrics to the provided |function|. Each value for Attribute
+ // has a separate set of stats. As such, |function| will be called once per
+ // value of Attribute.
+ void ExportValues(
+ std::function<void (const AttributeType&,
+ const EventStatistics&)> function) const {
+ for (auto it = values_.begin(); it != values_.end(); it++) {
+ function(it->first, *(it->second));
+ }
+ }
+
+ const std::string& metric_name() const { return metric_name_; };
+
+ private:
+ const std::string metric_name_;
+ const std::string attribute_name_;
+ std::map<AttributeType, std::unique_ptr<struct EventStatistics>> values_;
+};
+
+// The EventTimer is a supporting class for EventMetric instances that are used
+// to time methods. The EventTimer starts a timer when first in scope, and
+// records the timing when exiting scope.
+//
+// Example:
+//
+// EventMetric<int> my_metric;
+//
+// {
+// EventTimer<int> my_timer(&my_metric);
+// // Set the attribute to associate with this timing.
+// my_timer.SetAttribtue(42);
+//
+// // Do some work that you want to time.
+//
+// } // The EventTimer destructor will record the the timing in my_metric;
+//
+template<typename AttributeType>
+class EventTimer {
+ public:
+ explicit EventTimer(EventMetric<AttributeType>* metric)
+ :start_time_(systemTime()), metric_(metric) {
+ }
+
+ virtual ~EventTimer() {
+ if (metric_) {
+ metric_->Record(ns2us(systemTime() - start_time_), attribute_);
+ }
+ }
+
+ // Set the attribute to associate with this timing. E.g. this can be used to
+ // record the return code from the work that was timed.
+ void SetAttribute(const AttributeType& attribute) {
+ attribute_ = attribute;
+ }
+
+ protected:
+ // Visible for testing only.
+ nsecs_t start_time_;
+
+ private:
+ EventMetric<AttributeType>* metric_;
+ AttributeType attribute_;
+};
+
+} // namespace android
+
+#endif // ANDROID_EVENT_METRIC_H_
diff --git a/media/libmedia/include/media/IDrm.h b/media/libmedia/include/media/IDrm.h
index 9266f99..994cade 100644
--- a/media/libmedia/include/media/IDrm.h
+++ b/media/libmedia/include/media/IDrm.h
@@ -74,10 +74,12 @@
Vector<uint8_t> &wrappedKey) = 0;
virtual status_t getSecureStops(List<Vector<uint8_t> > &secureStops) = 0;
+ virtual status_t getSecureStopIds(List<Vector<uint8_t> > &secureStopIds) = 0;
virtual status_t getSecureStop(Vector<uint8_t> const &ssid, Vector<uint8_t> &secureStop) = 0;
virtual status_t releaseSecureStops(Vector<uint8_t> const &ssRelease) = 0;
- virtual status_t releaseAllSecureStops() = 0;
+ virtual status_t removeSecureStop(Vector<uint8_t> const &ssid) = 0;
+ virtual status_t removeAllSecureStops() = 0;
virtual status_t getHdcpLevels(DrmPlugin::HdcpLevel *connectedLevel,
DrmPlugin::HdcpLevel *maxLevel)
diff --git a/media/libmedia/include/media/MediaBufferHolder.h b/media/libmedia/include/media/MediaBufferHolder.h
new file mode 100644
index 0000000..e8e2c4b
--- /dev/null
+++ b/media/libmedia/include/media/MediaBufferHolder.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2018, 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 MEDIA_BUFFER_HOLDER_H_
+
+#define MEDIA_BUFFER_HOLDER_H_
+
+#include <media/stagefright/MediaBuffer.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct MediaBufferHolder : public RefBase {
+ MediaBufferHolder(MediaBuffer* buffer)
+ : mMediaBuffer(buffer) {
+ if (mMediaBuffer != nullptr) {
+ mMediaBuffer->add_ref();
+ }
+ }
+
+ virtual ~MediaBufferHolder() {
+ if (mMediaBuffer != nullptr) {
+ mMediaBuffer->release();
+ }
+ }
+
+ MediaBuffer* mediaBuffer() { return mMediaBuffer; }
+
+private:
+ MediaBuffer* const mMediaBuffer;
+};
+
+} // android
+
+#endif // MEDIA_BUFFER_HOLDER_H_
diff --git a/media/libmedia/include/media/MediaCodecBuffer.h b/media/libmedia/include/media/MediaCodecBuffer.h
index 501c00b..2c16fba 100644
--- a/media/libmedia/include/media/MediaCodecBuffer.h
+++ b/media/libmedia/include/media/MediaCodecBuffer.h
@@ -50,9 +50,6 @@
size_t offset() const;
// Default implementation calls ABuffer::setRange() and returns OK.
virtual status_t setRange(size_t offset, size_t size);
- // TODO: These can be removed if we finish replacing all MediaBuffer's.
- MediaBufferBase *getMediaBufferBase();
- void setMediaBufferBase(MediaBufferBase *mediaBuffer);
// TODO: Specify each field for meta/format.
sp<AMessage> meta();
@@ -66,7 +63,6 @@
const sp<AMessage> mMeta;
sp<AMessage> mFormat;
const sp<ABuffer> mBuffer;
- MediaBufferBase *mMediaBufferBase;
};
} // namespace android
diff --git a/media/libmedia/nuplayer2/Android.bp b/media/libmedia/nuplayer2/Android.bp
index d609ba0..700c840 100644
--- a/media/libmedia/nuplayer2/Android.bp
+++ b/media/libmedia/nuplayer2/Android.bp
@@ -22,7 +22,6 @@
],
include_dirs: [
- "frameworks/av/media/libmedia/include",
"frameworks/av/media/libstagefright",
"frameworks/av/media/libstagefright/httplive",
"frameworks/av/media/libstagefright/include",
@@ -54,6 +53,10 @@
"libpowermanager",
],
+ static_libs: [
+ "libmedia_helper",
+ ],
+
name: "libstagefright_nuplayer2",
tags: ["eng"],
diff --git a/media/libmedia/nuplayer2/GenericSource.cpp b/media/libmedia/nuplayer2/GenericSource.cpp
index c0b81fb..094af7e 100644
--- a/media/libmedia/nuplayer2/GenericSource.cpp
+++ b/media/libmedia/nuplayer2/GenericSource.cpp
@@ -24,7 +24,9 @@
#include <binder/IServiceManager.h>
#include <cutils/properties.h>
#include <media/DataSource.h>
+#include <media/MediaBufferHolder.h>
#include <media/IMediaExtractorService.h>
+#include <media/IMediaSource.h>
#include <media/MediaHTTPService.h>
#include <media/MediaExtractor.h>
#include <media/MediaSource.h>
@@ -1167,8 +1169,7 @@
// data is already provided in the buffer
ab = new ABuffer(NULL, mb->range_length());
- mb->add_ref();
- ab->setMediaBufferBase(mb);
+ ab->meta()->setObject("mediaBufferHolder", new MediaBufferHolder(mb));
// Modular DRM: Required b/c of the above add_ref.
// If ref>0, there must be an observer, or it'll crash at release().
diff --git a/media/libmedia/nuplayer2/GenericSource.h b/media/libmedia/nuplayer2/GenericSource.h
index 0666d27..5a71edb 100644
--- a/media/libmedia/nuplayer2/GenericSource.h
+++ b/media/libmedia/nuplayer2/GenericSource.h
@@ -33,6 +33,7 @@
struct ARTSPController;
class DataSource;
class IDataSource;
+class IMediaSource;
struct MediaHTTPService;
struct MediaSource;
class MediaBuffer;
diff --git a/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp b/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp
index 715d6fc..a436592 100644
--- a/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp
+++ b/media/libmedia/nuplayer2/NuPlayer2Decoder.cpp
@@ -28,6 +28,7 @@
#include "NuPlayer2Source.h"
#include <cutils/properties.h>
+#include <media/MediaBufferHolder.h>
#include <media/MediaCodecBuffer.h>
#include <media/NdkMediaCodec.h>
#include <media/NdkWrapper.h>
@@ -1081,16 +1082,17 @@
memcpy(codecBuffer->data(), buffer->data(), buffer->size());
} else { // No buffer->data()
//Modular DRM
- mediaBuf = (MediaBuffer*)buffer->getMediaBufferBase();
+ sp<RefBase> holder;
+ if (buffer->meta()->findObject("mediaBufferHolder", &holder)) {
+ mediaBuf = (holder != nullptr) ?
+ static_cast<MediaBufferHolder*>(holder.get())->mediaBuffer() : nullptr;
+ }
if (mediaBuf != NULL) {
codecBuffer->setRange(0, mediaBuf->size());
memcpy(codecBuffer->data(), mediaBuf->data(), mediaBuf->size());
sp<MetaData> meta_data = mediaBuf->meta_data();
cryptInfo = AMediaCodecCryptoInfoWrapper::Create(meta_data);
-
- // since getMediaBuffer() has incremented the refCount
- mediaBuf->release();
} else { // No mediaBuf
ALOGE("onInputBufferFetched: buffer->data()/mediaBuf are NULL for %p",
buffer.get());
diff --git a/media/libmedia/nuplayer2/NuPlayer2Driver.cpp b/media/libmedia/nuplayer2/NuPlayer2Driver.cpp
index 629a7eb..ccfcc47 100644
--- a/media/libmedia/nuplayer2/NuPlayer2Driver.cpp
+++ b/media/libmedia/nuplayer2/NuPlayer2Driver.cpp
@@ -77,7 +77,7 @@
};
// key for media statistics
-static const char *kKeyPlayer = "nuplayer2";
+static const char *kKeyPlayer = "nuplayer";
// attrs for media statistics
static const char *kPlayerVMime = "android.media.mediaplayer.video.mime";
static const char *kPlayerVCodec = "android.media.mediaplayer.video.codec";
@@ -666,7 +666,7 @@
// re-init in case we prepare() and start() again.
delete mAnalyticsItem ;
- mAnalyticsItem = new MediaAnalyticsItem("nuplayer");
+ mAnalyticsItem = new MediaAnalyticsItem(kKeyPlayer);
if (mAnalyticsItem) {
mAnalyticsItem->generateSessionID();
mAnalyticsItem->setUid(mClientUid);
diff --git a/media/libmediaextractor/Android.bp b/media/libmediaextractor/Android.bp
index 18573db..984d4f1 100644
--- a/media/libmediaextractor/Android.bp
+++ b/media/libmediaextractor/Android.bp
@@ -15,6 +15,7 @@
],
shared_libs: [
+ "libbinder",
"libstagefright_foundation",
"libutils",
"libcutils",
@@ -23,6 +24,9 @@
srcs: [
"DataSource.cpp",
+ "MediaBuffer.cpp",
+ "MediaBufferGroup.cpp",
+ "MediaSourceBase.cpp",
"MediaSource.cpp",
"MediaExtractor.cpp",
],
diff --git a/media/libstagefright/foundation/MediaBuffer.cpp b/media/libmediaextractor/MediaBuffer.cpp
similarity index 98%
rename from media/libstagefright/foundation/MediaBuffer.cpp
rename to media/libmediaextractor/MediaBuffer.cpp
index 95951dd..28fc760 100644
--- a/media/libstagefright/foundation/MediaBuffer.cpp
+++ b/media/libmediaextractor/MediaBuffer.cpp
@@ -26,8 +26,6 @@
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MetaData.h>
-#include <ui/GraphicBuffer.h>
-
namespace android {
/* static */
diff --git a/media/libstagefright/foundation/MediaBufferGroup.cpp b/media/libmediaextractor/MediaBufferGroup.cpp
similarity index 100%
rename from media/libstagefright/foundation/MediaBufferGroup.cpp
rename to media/libmediaextractor/MediaBufferGroup.cpp
diff --git a/media/libmediaextractor/MediaSource.cpp b/media/libmediaextractor/MediaSource.cpp
index a5d41f7..5bbd3d8 100644
--- a/media/libmediaextractor/MediaSource.cpp
+++ b/media/libmediaextractor/MediaSource.cpp
@@ -25,45 +25,4 @@
////////////////////////////////////////////////////////////////////////////////
-MediaSource::ReadOptions::ReadOptions() {
- reset();
-}
-
-void MediaSource::ReadOptions::reset() {
- mOptions = 0;
- mSeekTimeUs = 0;
- mNonBlocking = false;
-}
-
-void MediaSource::ReadOptions::setNonBlocking() {
- mNonBlocking = true;
-}
-
-void MediaSource::ReadOptions::clearNonBlocking() {
- mNonBlocking = false;
-}
-
-bool MediaSource::ReadOptions::getNonBlocking() const {
- return mNonBlocking;
-}
-
-void MediaSource::ReadOptions::setSeekTo(int64_t time_us, SeekMode mode) {
- mOptions |= kSeekTo_Option;
- mSeekTimeUs = time_us;
- mSeekMode = mode;
-}
-
-void MediaSource::ReadOptions::clearSeekTo() {
- mOptions &= ~kSeekTo_Option;
- mSeekTimeUs = 0;
- mSeekMode = SEEK_CLOSEST_SYNC;
-}
-
-bool MediaSource::ReadOptions::getSeekTo(
- int64_t *time_us, SeekMode *mode) const {
- *time_us = mSeekTimeUs;
- *mode = mSeekMode;
- return (mOptions & kSeekTo_Option) != 0;
-}
-
} // namespace android
diff --git a/media/libmediaextractor/MediaSourceBase.cpp b/media/libmediaextractor/MediaSourceBase.cpp
new file mode 100644
index 0000000..6d45c90
--- /dev/null
+++ b/media/libmediaextractor/MediaSourceBase.cpp
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+#include <media/MediaSourceBase.h>
+
+namespace android {
+
+MediaSourceBase::MediaSourceBase() {}
+
+MediaSourceBase::~MediaSourceBase() {}
+
+////////////////////////////////////////////////////////////////////////////////
+
+MediaSourceBase::ReadOptions::ReadOptions() {
+ reset();
+}
+
+void MediaSourceBase::ReadOptions::reset() {
+ mOptions = 0;
+ mSeekTimeUs = 0;
+ mNonBlocking = false;
+}
+
+void MediaSourceBase::ReadOptions::setNonBlocking() {
+ mNonBlocking = true;
+}
+
+void MediaSourceBase::ReadOptions::clearNonBlocking() {
+ mNonBlocking = false;
+}
+
+bool MediaSourceBase::ReadOptions::getNonBlocking() const {
+ return mNonBlocking;
+}
+
+void MediaSourceBase::ReadOptions::setSeekTo(int64_t time_us, SeekMode mode) {
+ mOptions |= kSeekTo_Option;
+ mSeekTimeUs = time_us;
+ mSeekMode = mode;
+}
+
+void MediaSourceBase::ReadOptions::clearSeekTo() {
+ mOptions &= ~kSeekTo_Option;
+ mSeekTimeUs = 0;
+ mSeekMode = SEEK_CLOSEST_SYNC;
+}
+
+bool MediaSourceBase::ReadOptions::getSeekTo(
+ int64_t *time_us, SeekMode *mode) const {
+ *time_us = mSeekTimeUs;
+ *mode = mSeekMode;
+ return (mOptions & kSeekTo_Option) != 0;
+}
+
+} // namespace android
diff --git a/media/libmediaextractor/include/media/DataSource.h b/media/libmediaextractor/include/media/DataSource.h
index 44f94a0..9925a21 100644
--- a/media/libmediaextractor/include/media/DataSource.h
+++ b/media/libmediaextractor/include/media/DataSource.h
@@ -19,23 +19,15 @@
#define DATA_SOURCE_H_
#include <sys/types.h>
-#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/MediaErrors.h>
#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/List.h>
#include <utils/RefBase.h>
#include <utils/threads.h>
#include <drm/DrmManagerClient.h>
namespace android {
-struct AMessage;
-struct AString;
-class IDataSource;
-struct IMediaHTTPService;
class String8;
-struct HTTPBase;
class DataSource : public RefBase {
public:
diff --git a/media/libmediaextractor/include/media/MediaExtractor.h b/media/libmediaextractor/include/media/MediaExtractor.h
index f197b5e..15a5d8c 100644
--- a/media/libmediaextractor/include/media/MediaExtractor.h
+++ b/media/libmediaextractor/include/media/MediaExtractor.h
@@ -27,19 +27,31 @@
namespace android {
class DataSource;
-class IMediaSource;
-class MediaExtractorFactory;
class MetaData;
-class Parcel;
class String8;
struct AMessage;
-struct MediaSource;
+struct MediaSourceBase;
typedef std::vector<uint8_t> HInterfaceToken;
-class MediaExtractor : public RefBase {
+
+class ExtractorAllocTracker {
public:
+ ExtractorAllocTracker() {
+ ALOGD("extractor allocated: %p", this);
+ }
+ virtual ~ExtractorAllocTracker() {
+ ALOGD("extractor freed: %p", this);
+ }
+};
+
+
+class MediaExtractor
+// : public ExtractorAllocTracker
+{
+public:
+ virtual ~MediaExtractor();
virtual size_t countTracks() = 0;
- virtual sp<MediaSource> getTrack(size_t index) = 0;
+ virtual MediaSourceBase *getTrack(size_t index) = 0;
enum GetTrackMetaDataFlags {
kIncludeExtensiveMetaData = 1
@@ -115,7 +127,6 @@
protected:
MediaExtractor();
- virtual ~MediaExtractor();
private:
MediaExtractor(const MediaExtractor &);
diff --git a/media/libmediaextractor/include/media/MediaSource.h b/media/libmediaextractor/include/media/MediaSource.h
index 25d691d..45070d6 100644
--- a/media/libmediaextractor/include/media/MediaSource.h
+++ b/media/libmediaextractor/include/media/MediaSource.h
@@ -20,113 +20,19 @@
#include <sys/types.h>
-#include <binder/IMemory.h>
-#include <binder/MemoryDealer.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MetaData.h>
#include <utils/RefBase.h>
-#include <utils/Vector.h>
+
+#include "media/MediaSourceBase.h"
namespace android {
class MediaBuffer;
-class IMediaSource;
-struct MediaSource : public virtual RefBase {
+struct MediaSource : public MediaSourceBase, public virtual RefBase {
MediaSource();
- // To be called before any other methods on this object, except
- // getFormat().
- virtual status_t start(MetaData *params = NULL) = 0;
-
- // Any blocking read call returns immediately with a result of NO_INIT.
- // It is an error to call any methods other than start after this call
- // returns. Any buffers the object may be holding onto at the time of
- // the stop() call are released.
- // Also, it is imperative that any buffers output by this object and
- // held onto by callers be released before a call to stop() !!!
- virtual status_t stop() = 0;
-
- // Returns the format of the data output by this media source.
- virtual sp<MetaData> getFormat() = 0;
-
- // Options that modify read() behaviour. The default is to
- // a) not request a seek
- // b) not be late, i.e. lateness_us = 0
- struct ReadOptions {
- enum SeekMode : int32_t {
- SEEK_PREVIOUS_SYNC,
- SEEK_NEXT_SYNC,
- SEEK_CLOSEST_SYNC,
- SEEK_CLOSEST,
- SEEK_FRAME_INDEX,
- };
-
- ReadOptions();
-
- // Reset everything back to defaults.
- void reset();
-
- void setSeekTo(int64_t time_us, SeekMode mode = SEEK_CLOSEST_SYNC);
- void clearSeekTo();
- bool getSeekTo(int64_t *time_us, SeekMode *mode) const;
-
- void setNonBlocking();
- void clearNonBlocking();
- bool getNonBlocking() const;
-
- // Used to clear all non-persistent options for multiple buffer reads.
- void clearNonPersistent() {
- clearSeekTo();
- }
-
- private:
- enum Options {
- kSeekTo_Option = 1,
- };
-
- uint32_t mOptions;
- int64_t mSeekTimeUs;
- SeekMode mSeekMode;
- bool mNonBlocking;
- } __attribute__((packed)); // sent through Binder
-
- // Returns a new buffer of data. Call blocks until a
- // buffer is available, an error is encountered of the end of the stream
- // is reached.
- // End of stream is signalled by a result of ERROR_END_OF_STREAM.
- // A result of INFO_FORMAT_CHANGED indicates that the format of this
- // MediaSource has changed mid-stream, the client can continue reading
- // but should be prepared for buffers of the new configuration.
- virtual status_t read(
- MediaBuffer **buffer, const ReadOptions *options = NULL) = 0;
-
- // Causes this source to suspend pulling data from its upstream source
- // until a subsequent read-with-seek. This is currently not supported
- // as such by any source. E.g. MediaCodecSource does not suspend its
- // upstream source, and instead discard upstream data while paused.
- virtual status_t pause() {
- return ERROR_UNSUPPORTED;
- }
-
- // The consumer of this media source requests the source stops sending
- // buffers with timestamp larger than or equal to stopTimeUs. stopTimeUs
- // must be in the same time base as the startTime passed in start(). If
- // source does not support this request, ERROR_UNSUPPORTED will be returned.
- // If stopTimeUs is invalid, BAD_VALUE will be returned. This could be
- // called at any time even before source starts and it could be called
- // multiple times. Setting stopTimeUs to be -1 will effectively cancel the stopTimeUs
- // set previously. If stopTimeUs is larger than or equal to last buffer's timestamp,
- // source will start to drop buffer when it gets a buffer with timestamp larger
- // than or equal to stopTimeUs. If stopTimeUs is smaller than or equal to last
- // buffer's timestamp, source will drop all the incoming buffers immediately.
- // After setting stopTimeUs, source may still stop sending buffers with timestamp
- // less than stopTimeUs if it is stopped by the consumer.
- virtual status_t setStopTimeUs(int64_t /* stopTimeUs */) {
- return ERROR_UNSUPPORTED;
- }
-
-protected:
virtual ~MediaSource();
private:
diff --git a/media/libmediaextractor/include/media/MediaSourceBase.h b/media/libmediaextractor/include/media/MediaSourceBase.h
new file mode 100644
index 0000000..77d4fc9
--- /dev/null
+++ b/media/libmediaextractor/include/media/MediaSourceBase.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2018 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 MEDIA_SOURCE_BASE_H_
+
+#define MEDIA_SOURCE_BASE_H_
+
+#include <sys/types.h>
+
+#include <binder/IMemory.h>
+#include <binder/MemoryDealer.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MetaData.h>
+#include <utils/RefBase.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+class MediaBuffer;
+
+class SourceBaseAllocTracker {
+public:
+ SourceBaseAllocTracker() {
+ ALOGD("sourcebase allocated: %p", this);
+ }
+ virtual ~SourceBaseAllocTracker() {
+ ALOGD("sourcebase freed: %p", this);
+ }
+};
+
+struct MediaSourceBase
+// : public SourceBaseAllocTracker
+{
+ MediaSourceBase();
+
+ // To be called before any other methods on this object, except
+ // getFormat().
+ virtual status_t start(MetaData *params = NULL) = 0;
+
+ // Any blocking read call returns immediately with a result of NO_INIT.
+ // It is an error to call any methods other than start after this call
+ // returns. Any buffers the object may be holding onto at the time of
+ // the stop() call are released.
+ // Also, it is imperative that any buffers output by this object and
+ // held onto by callers be released before a call to stop() !!!
+ virtual status_t stop() = 0;
+
+ // Returns the format of the data output by this media source.
+ virtual sp<MetaData> getFormat() = 0;
+
+ // Options that modify read() behaviour. The default is to
+ // a) not request a seek
+ // b) not be late, i.e. lateness_us = 0
+ struct ReadOptions {
+ enum SeekMode : int32_t {
+ SEEK_PREVIOUS_SYNC,
+ SEEK_NEXT_SYNC,
+ SEEK_CLOSEST_SYNC,
+ SEEK_CLOSEST,
+ SEEK_FRAME_INDEX,
+ };
+
+ ReadOptions();
+
+ // Reset everything back to defaults.
+ void reset();
+
+ void setSeekTo(int64_t time_us, SeekMode mode = SEEK_CLOSEST_SYNC);
+ void clearSeekTo();
+ bool getSeekTo(int64_t *time_us, SeekMode *mode) const;
+
+ void setNonBlocking();
+ void clearNonBlocking();
+ bool getNonBlocking() const;
+
+ // Used to clear all non-persistent options for multiple buffer reads.
+ void clearNonPersistent() {
+ clearSeekTo();
+ }
+
+ private:
+ enum Options {
+ kSeekTo_Option = 1,
+ };
+
+ uint32_t mOptions;
+ int64_t mSeekTimeUs;
+ SeekMode mSeekMode;
+ bool mNonBlocking;
+ } __attribute__((packed)); // sent through Binder
+
+ // Returns a new buffer of data. Call blocks until a
+ // buffer is available, an error is encountered of the end of the stream
+ // is reached.
+ // End of stream is signalled by a result of ERROR_END_OF_STREAM.
+ // A result of INFO_FORMAT_CHANGED indicates that the format of this
+ // MediaSource has changed mid-stream, the client can continue reading
+ // but should be prepared for buffers of the new configuration.
+ virtual status_t read(
+ MediaBuffer **buffer, const ReadOptions *options = NULL) = 0;
+
+ // Causes this source to suspend pulling data from its upstream source
+ // until a subsequent read-with-seek. This is currently not supported
+ // as such by any source. E.g. MediaCodecSource does not suspend its
+ // upstream source, and instead discard upstream data while paused.
+ virtual status_t pause() {
+ return ERROR_UNSUPPORTED;
+ }
+
+ // The consumer of this media source requests the source stops sending
+ // buffers with timestamp larger than or equal to stopTimeUs. stopTimeUs
+ // must be in the same time base as the startTime passed in start(). If
+ // source does not support this request, ERROR_UNSUPPORTED will be returned.
+ // If stopTimeUs is invalid, BAD_VALUE will be returned. This could be
+ // called at any time even before source starts and it could be called
+ // multiple times. Setting stopTimeUs to be -1 will effectively cancel the stopTimeUs
+ // set previously. If stopTimeUs is larger than or equal to last buffer's timestamp,
+ // source will start to drop buffer when it gets a buffer with timestamp larger
+ // than or equal to stopTimeUs. If stopTimeUs is smaller than or equal to last
+ // buffer's timestamp, source will drop all the incoming buffers immediately.
+ // After setting stopTimeUs, source may still stop sending buffers with timestamp
+ // less than stopTimeUs if it is stopped by the consumer.
+ virtual status_t setStopTimeUs(int64_t /* stopTimeUs */) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ virtual ~MediaSourceBase();
+
+private:
+ MediaSourceBase(const MediaSourceBase &);
+ MediaSourceBase &operator=(const MediaSourceBase &);
+};
+
+} // namespace android
+
+#endif // MEDIA_SOURCE_BASE_H_
diff --git a/media/libstagefright/include/media/stagefright/MediaBuffer.h b/media/libmediaextractor/include/media/stagefright/MediaBuffer.h
similarity index 97%
rename from media/libstagefright/include/media/stagefright/MediaBuffer.h
rename to media/libmediaextractor/include/media/stagefright/MediaBuffer.h
index 367a467..a8f8375 100644
--- a/media/libstagefright/include/media/stagefright/MediaBuffer.h
+++ b/media/libmediaextractor/include/media/stagefright/MediaBuffer.h
@@ -20,7 +20,6 @@
#include <atomic>
#include <list>
-#include <media/stagefright/foundation/MediaBufferBase.h>
#include <pthread.h>
@@ -31,7 +30,6 @@
namespace android {
struct ABuffer;
-class GraphicBuffer;
class MediaBuffer;
class MediaBufferObserver;
class MetaData;
@@ -48,7 +46,7 @@
MediaBufferObserver &operator=(const MediaBufferObserver &);
};
-class MediaBuffer : public MediaBufferBase {
+class MediaBuffer {
public:
// allocations larger than or equal to this will use shared memory.
static const size_t kSharedMemThreshold = 64 * 1024;
@@ -72,11 +70,11 @@
//
// If no MediaBufferGroup is set, the local reference count must be zero
// when called, whereupon the MediaBuffer is deleted.
- virtual void release();
+ void release();
// Increments the local reference count.
// Use only when MediaBufferGroup is set.
- virtual void add_ref();
+ void add_ref();
void *data() const;
size_t size() const;
diff --git a/media/libstagefright/include/media/stagefright/MediaBufferGroup.h b/media/libmediaextractor/include/media/stagefright/MediaBufferGroup.h
similarity index 100%
rename from media/libstagefright/include/media/stagefright/MediaBufferGroup.h
rename to media/libmediaextractor/include/media/stagefright/MediaBufferGroup.h
diff --git a/media/libmediametrics/MediaAnalyticsItem.cpp b/media/libmediametrics/MediaAnalyticsItem.cpp
index 423dfb8..2e7efad 100644
--- a/media/libmediametrics/MediaAnalyticsItem.cpp
+++ b/media/libmediametrics/MediaAnalyticsItem.cpp
@@ -722,7 +722,7 @@
std::string MediaAnalyticsItem::toString() {
- return toString(-1);
+ return toString(PROTO_LAST);
}
std::string MediaAnalyticsItem::toString(int version) {
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
index 33c3094..511f46f 100644
--- a/media/libmediaplayerservice/nuplayer/GenericSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
@@ -24,6 +24,7 @@
#include <binder/IServiceManager.h>
#include <cutils/properties.h>
#include <media/DataSource.h>
+#include <media/MediaBufferHolder.h>
#include <media/MediaExtractor.h>
#include <media/MediaSource.h>
#include <media/IMediaHTTPService.h>
@@ -1160,14 +1161,12 @@
// data is already provided in the buffer
ab = new ABuffer(NULL, mb->range_length());
- mb->add_ref();
- ab->setMediaBufferBase(mb);
+ ab->meta()->setObject("mediaBufferHolder", new MediaBufferHolder(mb));
// Modular DRM: Required b/c of the above add_ref.
// If ref>0, there must be an observer, or it'll crash at release().
// TODO: MediaBuffer might need to be revised to ease such need.
mb->setObserver(this);
- // setMediaBufferBase() interestingly doesn't increment the ref count on its own.
// Extra increment (since we want to keep mb alive and attached to ab beyond this function
// call. This is to counter the effect of mb->release() towards the end.
mb->add_ref();
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.h b/media/libmediaplayerservice/nuplayer/GenericSource.h
index 856f03b..2406665 100644
--- a/media/libmediaplayerservice/nuplayer/GenericSource.h
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.h
@@ -35,6 +35,7 @@
class IDataSource;
struct IMediaHTTPService;
struct MediaSource;
+class IMediaSource;
class MediaBuffer;
struct MediaClock;
struct NuCachedSource2;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
index 1b02adb..1aca96c 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
@@ -29,6 +29,7 @@
#include <cutils/properties.h>
#include <media/ICrypto.h>
+#include <media/MediaBufferHolder.h>
#include <media/MediaCodecBuffer.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
@@ -1061,16 +1062,17 @@
memcpy(codecBuffer->data(), buffer->data(), buffer->size());
} else { // No buffer->data()
//Modular DRM
- mediaBuf = (MediaBuffer*)buffer->getMediaBufferBase();
+ sp<RefBase> holder;
+ if (buffer->meta()->findObject("mediaBufferHolder", &holder)) {
+ mediaBuf = (holder != nullptr) ?
+ static_cast<MediaBufferHolder*>(holder.get())->mediaBuffer() : nullptr;
+ }
if (mediaBuf != NULL) {
codecBuffer->setRange(0, mediaBuf->size());
memcpy(codecBuffer->data(), mediaBuf->data(), mediaBuf->size());
sp<MetaData> meta_data = mediaBuf->meta_data();
cryptInfo = NuPlayerDrm::getSampleCryptoInfo(meta_data);
-
- // since getMediaBuffer() has incremented the refCount
- mediaBuf->release();
} else { // No mediaBuf
ALOGE("onInputBufferFetched: buffer->data()/mediaBuf are NULL for %p",
buffer.get());
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index 14ea2a8..541093a 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -43,6 +43,7 @@
#include <media/stagefright/PersistentSurface.h>
#include <media/stagefright/SurfaceUtils.h>
#include <media/hardware/HardwareAPI.h>
+#include <media/MediaBufferHolder.h>
#include <media/OMXBuffer.h>
#include <media/omx/1.0/WOmxNode.h>
@@ -125,6 +126,32 @@
}
}
+static OMX_VIDEO_CONTROLRATETYPE getVideoBitrateMode(const sp<AMessage> &msg) {
+ int32_t tmp;
+ if (msg->findInt32("bitrate-mode", &tmp)) {
+ // explicitly translate from MediaCodecInfo.EncoderCapabilities.
+ // BITRATE_MODE_* into OMX bitrate mode.
+ switch (tmp) {
+ //BITRATE_MODE_CQ
+ case 0: return OMX_Video_ControlRateConstantQuality;
+ //BITRATE_MODE_VBR
+ case 1: return OMX_Video_ControlRateVariable;
+ //BITRATE_MODE_CBR
+ case 2: return OMX_Video_ControlRateConstant;
+ default: break;
+ }
+ }
+ return OMX_Video_ControlRateVariable;
+}
+
+static bool findVideoBitrateControlInfo(const sp<AMessage> &msg,
+ OMX_VIDEO_CONTROLRATETYPE *mode, int32_t *bitrate, int32_t *quality) {
+ *mode = getVideoBitrateMode(msg);
+ bool isCQ = (*mode == OMX_Video_ControlRateConstantQuality);
+ return (!isCQ && msg->findInt32("bitrate", bitrate))
+ || (isCQ && msg->findInt32("quality", quality));
+}
+
struct MessageList : public RefBase {
MessageList() {
}
@@ -1685,6 +1712,7 @@
mConfigFormat = msg;
mIsEncoder = encoder;
+ mIsVideo = !strncasecmp(mime, "video/", 6);
mPortMode[kPortIndexInput] = IOMX::kPortModePresetByteBuffer;
mPortMode[kPortIndexOutput] = IOMX::kPortModePresetByteBuffer;
@@ -1695,19 +1723,26 @@
return err;
}
- int32_t bitRate = 0;
- // FLAC encoder doesn't need a bitrate, other encoders do
- if (encoder && strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)
- && !msg->findInt32("bitrate", &bitRate)) {
- return INVALID_OPERATION;
+ OMX_VIDEO_CONTROLRATETYPE bitrateMode;
+ int32_t bitrate = 0, quality;
+ // FLAC encoder or video encoder in constant quality mode doesn't need a
+ // bitrate, other encoders do.
+ if (encoder) {
+ if (mIsVideo && !findVideoBitrateControlInfo(
+ msg, &bitrateMode, &bitrate, &quality)) {
+ return INVALID_OPERATION;
+ } else if (!mIsVideo && strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)
+ && !msg->findInt32("bitrate", &bitrate)) {
+ return INVALID_OPERATION;
+ }
}
// propagate bitrate to the output so that the muxer has it
- if (encoder && msg->findInt32("bitrate", &bitRate)) {
+ if (encoder && msg->findInt32("bitrate", &bitrate)) {
// Technically ISO spec says that 'bitrate' should be 0 for VBR even though it is the
// average bitrate. We've been setting both bitrate and max-bitrate to this same value.
- outputFormat->setInt32("bitrate", bitRate);
- outputFormat->setInt32("max-bitrate", bitRate);
+ outputFormat->setInt32("bitrate", bitrate);
+ outputFormat->setInt32("max-bitrate", bitrate);
}
int32_t storeMeta;
@@ -1764,9 +1799,7 @@
// Only enable metadata mode on encoder output if encoder can prepend
// sps/pps to idr frames, since in metadata mode the bitstream is in an
// opaque handle, to which we don't have access.
- int32_t video = !strncasecmp(mime, "video/", 6);
- mIsVideo = video;
- if (encoder && video) {
+ if (encoder && mIsVideo) {
OMX_BOOL enable = (OMX_BOOL) (prependSPSPPS
&& msg->findInt32("android._store-metadata-in-buffers-output", &storeMeta)
&& storeMeta != 0);
@@ -1812,9 +1845,9 @@
// NOTE: we only use native window for video decoders
sp<RefBase> obj;
bool haveNativeWindow = msg->findObject("native-window", &obj)
- && obj != NULL && video && !encoder;
+ && obj != NULL && mIsVideo && !encoder;
mUsingNativeWindow = haveNativeWindow;
- if (video && !encoder) {
+ if (mIsVideo && !encoder) {
inputFormat->setInt32("adaptive-playback", false);
int32_t usageProtected;
@@ -1977,7 +2010,7 @@
(void)msg->findInt32("pcm-encoding", (int32_t*)&pcmEncoding);
// invalid encodings will default to PCM-16bit in setupRawAudioFormat.
- if (video) {
+ if (mIsVideo) {
// determine need for software renderer
bool usingSwRenderer = false;
if (haveNativeWindow && mComponentName.startsWith("OMX.google.")) {
@@ -2111,14 +2144,14 @@
}
err = setupAACCodec(
- encoder, numChannels, sampleRate, bitRate, aacProfile,
+ encoder, numChannels, sampleRate, bitrate, aacProfile,
isADTS != 0, sbrMode, maxOutputChannelCount, drc,
pcmLimiterEnable);
}
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)) {
- err = setupAMRCodec(encoder, false /* isWAMR */, bitRate);
+ err = setupAMRCodec(encoder, false /* isWAMR */, bitrate);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
- err = setupAMRCodec(encoder, true /* isWAMR */, bitRate);
+ err = setupAMRCodec(encoder, true /* isWAMR */, bitrate);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_G711_ALAW)
|| !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_G711_MLAW)) {
// These are PCM-like formats with a fixed sample rate but
@@ -2232,7 +2265,7 @@
rateFloat = (float)rateInt; // 16MHz (FLINTMAX) is OK for upper bound.
}
if (rateFloat > 0) {
- err = setOperatingRate(rateFloat, video);
+ err = setOperatingRate(rateFloat, mIsVideo);
err = OK; // ignore errors
}
@@ -2257,7 +2290,7 @@
}
// create data converters if needed
- if (!video && err == OK) {
+ if (!mIsVideo && err == OK) {
AudioEncoding codecPcmEncoding = kAudioEncodingPcm16bit;
if (encoder) {
(void)mInputFormat->findInt32("pcm-encoding", (int32_t*)&codecPcmEncoding);
@@ -3708,10 +3741,12 @@
return err;
}
- int32_t width, height, bitrate;
+ OMX_VIDEO_CONTROLRATETYPE bitrateMode;
+ int32_t width, height, bitrate = 0, quality;
if (!msg->findInt32("width", &width)
|| !msg->findInt32("height", &height)
- || !msg->findInt32("bitrate", &bitrate)) {
+ || !findVideoBitrateControlInfo(
+ msg, &bitrateMode, &bitrate, &quality)) {
return INVALID_OPERATION;
}
@@ -3964,15 +3999,6 @@
return ret > 0 ? ret - 1 : 0;
}
-static OMX_VIDEO_CONTROLRATETYPE getBitrateMode(const sp<AMessage> &msg) {
- int32_t tmp;
- if (!msg->findInt32("bitrate-mode", &tmp)) {
- return OMX_Video_ControlRateVariable;
- }
-
- return static_cast<OMX_VIDEO_CONTROLRATETYPE>(tmp);
-}
-
status_t ACodec::setupMPEG4EncoderParameters(const sp<AMessage> &msg) {
int32_t bitrate;
float iFrameInterval;
@@ -3981,7 +4007,7 @@
return INVALID_OPERATION;
}
- OMX_VIDEO_CONTROLRATETYPE bitrateMode = getBitrateMode(msg);
+ OMX_VIDEO_CONTROLRATETYPE bitrateMode = getVideoBitrateMode(msg);
float frameRate;
if (!msg->findFloat("frame-rate", &frameRate)) {
@@ -4046,7 +4072,7 @@
return err;
}
- err = configureBitrate(bitrate, bitrateMode);
+ err = configureBitrate(bitrateMode, bitrate);
if (err != OK) {
return err;
@@ -4063,7 +4089,7 @@
return INVALID_OPERATION;
}
- OMX_VIDEO_CONTROLRATETYPE bitrateMode = getBitrateMode(msg);
+ OMX_VIDEO_CONTROLRATETYPE bitrateMode = getVideoBitrateMode(msg);
float frameRate;
if (!msg->findFloat("frame-rate", &frameRate)) {
@@ -4123,7 +4149,7 @@
return err;
}
- err = configureBitrate(bitrate, bitrateMode);
+ err = configureBitrate(bitrateMode, bitrate);
if (err != OK) {
return err;
@@ -4193,7 +4219,7 @@
return INVALID_OPERATION;
}
- OMX_VIDEO_CONTROLRATETYPE bitrateMode = getBitrateMode(msg);
+ OMX_VIDEO_CONTROLRATETYPE bitrateMode = getVideoBitrateMode(msg);
float frameRate;
if (!msg->findFloat("frame-rate", &frameRate)) {
@@ -4349,18 +4375,20 @@
}
}
- return configureBitrate(bitrate, bitrateMode);
+ return configureBitrate(bitrateMode, bitrate);
}
status_t ACodec::setupHEVCEncoderParameters(const sp<AMessage> &msg) {
- int32_t bitrate;
float iFrameInterval;
- if (!msg->findInt32("bitrate", &bitrate)
- || !msg->findAsFloat("i-frame-interval", &iFrameInterval)) {
+ if (!msg->findAsFloat("i-frame-interval", &iFrameInterval)) {
return INVALID_OPERATION;
}
- OMX_VIDEO_CONTROLRATETYPE bitrateMode = getBitrateMode(msg);
+ OMX_VIDEO_CONTROLRATETYPE bitrateMode;
+ int32_t bitrate, quality;
+ if (!findVideoBitrateControlInfo(msg, &bitrateMode, &bitrate, &quality)) {
+ return INVALID_OPERATION;
+ }
float frameRate;
if (!msg->findFloat("frame-rate", &frameRate)) {
@@ -4406,7 +4434,7 @@
return err;
}
- return configureBitrate(bitrate, bitrateMode);
+ return configureBitrate(bitrateMode, bitrate, quality);
}
status_t ACodec::setupVPXEncoderParameters(const sp<AMessage> &msg, sp<AMessage> &outputFormat) {
@@ -4427,7 +4455,7 @@
}
msg->findAsFloat("i-frame-interval", &iFrameInterval);
- OMX_VIDEO_CONTROLRATETYPE bitrateMode = getBitrateMode(msg);
+ OMX_VIDEO_CONTROLRATETYPE bitrateMode = getVideoBitrateMode(msg);
float frameRate;
if (!msg->findFloat("frame-rate", &frameRate)) {
@@ -4504,7 +4532,7 @@
}
}
- return configureBitrate(bitrate, bitrateMode);
+ return configureBitrate(bitrateMode, bitrate);
}
status_t ACodec::verifySupportForProfileAndLevel(
@@ -4540,7 +4568,7 @@
}
status_t ACodec::configureBitrate(
- int32_t bitrate, OMX_VIDEO_CONTROLRATETYPE bitrateMode) {
+ OMX_VIDEO_CONTROLRATETYPE bitrateMode, int32_t bitrate, int32_t quality) {
OMX_VIDEO_PARAM_BITRATETYPE bitrateType;
InitOMXParams(&bitrateType);
bitrateType.nPortIndex = kPortIndexOutput;
@@ -4553,7 +4581,13 @@
}
bitrateType.eControlRate = bitrateMode;
- bitrateType.nTargetBitrate = bitrate;
+
+ // write it out explicitly even if it's a union
+ if (bitrateMode == OMX_Video_ControlRateConstantQuality) {
+ bitrateType.nQualityFactor = quality;
+ } else {
+ bitrateType.nTargetBitrate = bitrate;
+ }
return mOMXNode->setParameter(
OMX_IndexParamVideoBitrate, &bitrateType, sizeof(bitrateType));
@@ -5606,7 +5640,7 @@
// by this "MediaBuffer" object. Now that the OMX component has
// told us that it's done with the input buffer, we can decrement
// the mediaBuffer's reference count.
- info->mData->setMediaBufferBase(NULL);
+ info->mData->meta()->setObject("mediaBufferHolder", sp<MediaBufferHolder>(nullptr));
PortMode mode = getPortMode(kPortIndexInput);
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index 01f73a1..90f6ed7 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -105,6 +105,7 @@
"libmedia_helper",
"libstagefright_codec2",
"libstagefright_foundation",
+ "libstagefright_gbs",
"libstagefright_omx",
"libstagefright_omx_utils",
"libstagefright_xmlparser",
diff --git a/media/libstagefright/CCodec.cpp b/media/libstagefright/CCodec.cpp
index 0103abd..b058f37 100644
--- a/media/libstagefright/CCodec.cpp
+++ b/media/libstagefright/CCodec.cpp
@@ -23,7 +23,9 @@
#include <C2PlatformSupport.h>
#include <gui/Surface.h>
+#include <media/stagefright/BufferProducerWrapper.h>
#include <media/stagefright/CCodec.h>
+#include <media/stagefright/PersistentSurface.h>
#include "include/CCodecBufferChannel.h"
@@ -111,33 +113,35 @@
class CCodecListener : public C2Component::Listener {
public:
- CCodecListener(const std::shared_ptr<CCodecBufferChannel> &channel)
- : mChannel(channel) {
- }
+ explicit CCodecListener(const wp<CCodec> &codec) : mCodec(codec) {}
virtual void onWorkDone_nb(
std::weak_ptr<C2Component> component,
std::vector<std::unique_ptr<C2Work>> workItems) override {
- (void) component;
- mChannel->onWorkDone(std::move(workItems));
+ (void)component;
+ sp<CCodec> codec(mCodec.promote());
+ if (!codec) {
+ return;
+ }
+ codec->onWorkDone(workItems);
}
virtual void onTripped_nb(
std::weak_ptr<C2Component> component,
std::vector<std::shared_ptr<C2SettingResult>> settingResult) override {
// TODO
- (void) component;
- (void) settingResult;
+ (void)component;
+ (void)settingResult;
}
virtual void onError_nb(std::weak_ptr<C2Component> component, uint32_t errorCode) override {
// TODO
- (void) component;
- (void) errorCode;
+ (void)component;
+ (void)errorCode;
}
private:
- std::shared_ptr<CCodecBufferChannel> mChannel;
+ wp<CCodec> mCodec;
};
} // namespace
@@ -159,11 +163,11 @@
void CCodec::initiateAllocateComponent(const sp<AMessage> &msg) {
{
Mutexed<State>::Locked state(mState);
- if (state->mState != RELEASED) {
+ if (state->get() != RELEASED) {
mCallback->onError(INVALID_OPERATION, ACTION_CODE_FATAL);
return;
}
- state->mState = ALLOCATING;
+ state->set(ALLOCATING);
}
AString componentName;
@@ -178,14 +182,14 @@
void CCodec::allocate(const AString &componentName) {
// TODO: use C2ComponentStore to create component
- mListener.reset(new CCodecListener(mChannel));
+ mListener.reset(new CCodecListener(this));
std::shared_ptr<C2Component> comp;
c2_status_t err = GetCodec2PlatformComponentStore()->createComponent(
componentName.c_str(), &comp);
if (err != C2_OK) {
Mutexed<State>::Locked state(mState);
- state->mState = RELEASED;
+ state->set(RELEASED);
state.unlock();
mCallback->onError(err, ACTION_CODE_FATAL);
state.lock();
@@ -194,15 +198,15 @@
comp->setListener_vb(mListener, C2_MAY_BLOCK);
{
Mutexed<State>::Locked state(mState);
- if (state->mState != ALLOCATING) {
- state->mState = RELEASED;
+ if (state->get() != ALLOCATING) {
+ state->set(RELEASED);
state.unlock();
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
state.lock();
return;
}
- state->mState = ALLOCATED;
- state->mComp = comp;
+ state->set(ALLOCATED);
+ state->comp = comp;
}
mChannel->setComponent(comp);
mCallback->onComponentAllocated(comp->intf()->getName().c_str());
@@ -211,7 +215,7 @@
void CCodec::initiateConfigureComponent(const sp<AMessage> &format) {
{
Mutexed<State>::Locked state(mState);
- if (state->mState != ALLOCATED) {
+ if (state->get() != ALLOCATED) {
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
return;
}
@@ -252,6 +256,9 @@
inputFormat->setInt32("sample-rate", 44100);
outputFormat->setInt32("channel-count", 1);
outputFormat->setInt32("sample-rate", 44100);
+ } else {
+ outputFormat->setInt32("width", 1080);
+ outputFormat->setInt32("height", 1920);
}
} else {
inputFormat->setString("mime", mime);
@@ -272,32 +279,81 @@
{
Mutexed<Formats>::Locked formats(mFormats);
- formats->mInputFormat = inputFormat;
- formats->mOutputFormat = outputFormat;
+ formats->inputFormat = inputFormat;
+ formats->outputFormat = outputFormat;
}
mCallback->onComponentConfigured(inputFormat, outputFormat);
}
-
void CCodec::initiateCreateInputSurface() {
- // TODO
+ (new AMessage(kWhatCreateInputSurface, this))->post();
+}
+
+void CCodec::createInputSurface() {
+ sp<IGraphicBufferProducer> producer;
+ sp<GraphicBufferSource> source(new GraphicBufferSource);
+
+ status_t err = source->initCheck();
+ if (err != OK) {
+ ALOGE("Failed to initialize graphic buffer source: %d", err);
+ mCallback->onInputSurfaceCreationFailed(err);
+ return;
+ }
+ producer = source->getIGraphicBufferProducer();
+
+ err = setupInputSurface(source);
+ if (err != OK) {
+ ALOGE("Failed to set up input surface: %d", err);
+ mCallback->onInputSurfaceCreationFailed(err);
+ return;
+ }
+
+ sp<AMessage> inputFormat;
+ sp<AMessage> outputFormat;
+ {
+ Mutexed<Formats>::Locked formats(mFormats);
+ inputFormat = formats->inputFormat;
+ outputFormat = formats->outputFormat;
+ }
+ mCallback->onInputSurfaceCreated(
+ inputFormat,
+ outputFormat,
+ new BufferProducerWrapper(producer));
+}
+
+status_t CCodec::setupInputSurface(const sp<GraphicBufferSource> &source) {
+ status_t err = mChannel->setGraphicBufferSource(source);
+ if (err != OK) {
+ return err;
+ }
+
+ // TODO: configure |source| with other settings.
+ return OK;
}
void CCodec::initiateSetInputSurface(const sp<PersistentSurface> &surface) {
+ sp<AMessage> msg = new AMessage(kWhatSetInputSurface, this);
+ msg->setObject("surface", surface);
+ msg->post();
+}
+
+void CCodec::setInputSurface(const sp<PersistentSurface> &surface) {
// TODO
- (void) surface;
+ (void)surface;
+
+ mCallback->onInputSurfaceDeclined(ERROR_UNSUPPORTED);
}
void CCodec::initiateStart() {
{
Mutexed<State>::Locked state(mState);
- if (state->mState != ALLOCATED) {
+ if (state->get() != ALLOCATED) {
state.unlock();
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
state.lock();
return;
}
- state->mState = STARTING;
+ state->set(STARTING);
}
(new AMessage(kWhatStart, this))->post();
@@ -307,13 +363,13 @@
std::shared_ptr<C2Component> comp;
{
Mutexed<State>::Locked state(mState);
- if (state->mState != STARTING) {
+ if (state->get() != STARTING) {
state.unlock();
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
state.lock();
return;
}
- comp = state->mComp;
+ comp = state->comp;
}
c2_status_t err = comp->start();
if (err != C2_OK) {
@@ -325,20 +381,20 @@
sp<AMessage> outputFormat;
{
Mutexed<Formats>::Locked formats(mFormats);
- inputFormat = formats->mInputFormat;
- outputFormat = formats->mOutputFormat;
+ inputFormat = formats->inputFormat;
+ outputFormat = formats->outputFormat;
}
mChannel->start(inputFormat, outputFormat);
{
Mutexed<State>::Locked state(mState);
- if (state->mState != STARTING) {
+ if (state->get() != STARTING) {
state.unlock();
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
state.lock();
return;
}
- state->mState = RUNNING;
+ state->set(RUNNING);
}
mCallback->onStartCompleted();
}
@@ -354,17 +410,17 @@
void CCodec::initiateStop() {
{
Mutexed<State>::Locked state(mState);
- if (state->mState == ALLOCATED
- || state->mState == RELEASED
- || state->mState == STOPPING
- || state->mState == RELEASING) {
+ if (state->get() == ALLOCATED
+ || state->get() == RELEASED
+ || state->get() == STOPPING
+ || state->get() == RELEASING) {
// We're already stopped, released, or doing it right now.
state.unlock();
mCallback->onStopCompleted();
state.lock();
return;
}
- state->mState = STOPPING;
+ state->set(STOPPING);
}
(new AMessage(kWhatStop, this))->post();
@@ -374,19 +430,19 @@
std::shared_ptr<C2Component> comp;
{
Mutexed<State>::Locked state(mState);
- if (state->mState == RELEASING) {
+ if (state->get() == RELEASING) {
state.unlock();
// We're already stopped or release is in progress.
mCallback->onStopCompleted();
state.lock();
return;
- } else if (state->mState != STOPPING) {
+ } else if (state->get() != STOPPING) {
state.unlock();
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
state.lock();
return;
}
- comp = state->mComp;
+ comp = state->comp;
}
mChannel->stop();
status_t err = comp->stop();
@@ -397,8 +453,8 @@
{
Mutexed<State>::Locked state(mState);
- if (state->mState == STOPPING) {
- state->mState = ALLOCATED;
+ if (state->get() == STOPPING) {
+ state->set(ALLOCATED);
}
}
mCallback->onStopCompleted();
@@ -407,7 +463,7 @@
void CCodec::initiateRelease(bool sendCallback /* = true */) {
{
Mutexed<State>::Locked state(mState);
- if (state->mState == RELEASED || state->mState == RELEASING) {
+ if (state->get() == RELEASED || state->get() == RELEASING) {
// We're already released or doing it right now.
if (sendCallback) {
state.unlock();
@@ -416,8 +472,8 @@
}
return;
}
- if (state->mState == ALLOCATING) {
- state->mState = RELEASING;
+ if (state->get() == ALLOCATING) {
+ state->set(RELEASING);
// With the altered state allocate() would fail and clean up.
if (sendCallback) {
state.unlock();
@@ -426,7 +482,7 @@
}
return;
}
- state->mState = RELEASING;
+ state->set(RELEASING);
}
std::thread([this, sendCallback] { release(sendCallback); }).detach();
@@ -436,7 +492,7 @@
std::shared_ptr<C2Component> comp;
{
Mutexed<State>::Locked state(mState);
- if (state->mState == RELEASED) {
+ if (state->get() == RELEASED) {
if (sendCallback) {
state.unlock();
mCallback->onReleaseCompleted();
@@ -444,15 +500,15 @@
}
return;
}
- comp = state->mComp;
+ comp = state->comp;
}
mChannel->stop();
comp->release();
{
Mutexed<State>::Locked state(mState);
- state->mState = RELEASED;
- state->mComp.reset();
+ state->set(RELEASED);
+ state->comp.reset();
}
if (sendCallback) {
mCallback->onReleaseCompleted();
@@ -466,11 +522,11 @@
void CCodec::signalFlush() {
{
Mutexed<State>::Locked state(mState);
- if (state->mState != RUNNING) {
+ if (state->get() != RUNNING) {
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
return;
}
- state->mState = FLUSHING;
+ state->set(FLUSHING);
}
(new AMessage(kWhatFlush, this))->post();
@@ -480,13 +536,13 @@
std::shared_ptr<C2Component> comp;
{
Mutexed<State>::Locked state(mState);
- if (state->mState != FLUSHING) {
+ if (state->get() != FLUSHING) {
state.unlock();
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
state.lock();
return;
}
- comp = state->mComp;
+ comp = state->comp;
}
mChannel->stop();
@@ -502,7 +558,7 @@
{
Mutexed<State>::Locked state(mState);
- state->mState = FLUSHED;
+ state->set(FLUSHED);
}
mCallback->onFlushCompleted();
}
@@ -510,26 +566,26 @@
void CCodec::signalResume() {
{
Mutexed<State>::Locked state(mState);
- if (state->mState != FLUSHED) {
+ if (state->get() != FLUSHED) {
state.unlock();
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
state.lock();
return;
}
- state->mState = RESUMING;
+ state->set(RESUMING);
}
mChannel->start(nullptr, nullptr);
{
Mutexed<State>::Locked state(mState);
- if (state->mState != RESUMING) {
+ if (state->get() != RESUMING) {
state.unlock();
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
state.lock();
return;
}
- state->mState = RUNNING;
+ state->set(RUNNING);
}
}
@@ -545,6 +601,14 @@
// TODO
}
+void CCodec::onWorkDone(std::vector<std::unique_ptr<C2Work>> &workItems) {
+ Mutexed<std::list<std::unique_ptr<C2Work>>>::Locked queue(mWorkDoneQueue);
+ for (std::unique_ptr<C2Work> &item : workItems) {
+ queue->push_back(std::move(item));
+ }
+ (new AMessage(kWhatWorkDone, this))->post();
+}
+
void CCodec::onMessageReceived(const sp<AMessage> &msg) {
TimePoint now = std::chrono::steady_clock::now();
switch (msg->what()) {
@@ -582,6 +646,37 @@
flush();
break;
}
+ case kWhatCreateInputSurface: {
+ // Surface operations may be briefly blocking.
+ setDeadline(now + 100ms);
+ createInputSurface();
+ break;
+ }
+ case kWhatSetInputSurface: {
+ // Surface operations may be briefly blocking.
+ setDeadline(now + 100ms);
+ sp<RefBase> obj;
+ CHECK(msg->findObject("surface", &obj));
+ sp<PersistentSurface> surface(static_cast<PersistentSurface *>(obj.get()));
+ setInputSurface(surface);
+ break;
+ }
+ case kWhatWorkDone: {
+ std::unique_ptr<C2Work> work;
+ {
+ Mutexed<std::list<std::unique_ptr<C2Work>>>::Locked queue(mWorkDoneQueue);
+ if (queue->empty()) {
+ break;
+ }
+ work.swap(queue->front());
+ queue->pop_front();
+ if (!queue->empty()) {
+ (new AMessage(kWhatWorkDone, this))->post();
+ }
+ }
+ mChannel->onWorkDone(work);
+ break;
+ }
default: {
ALOGE("unrecognized message");
break;
diff --git a/media/libstagefright/CCodecBufferChannel.cpp b/media/libstagefright/CCodecBufferChannel.cpp
index eea9c78..2f4a7ea 100644
--- a/media/libstagefright/CCodecBufferChannel.cpp
+++ b/media/libstagefright/CCodecBufferChannel.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_NDEBUG 0
+//#define LOG_NDEBUG 0
#define LOG_TAG "CCodecBufferChannel"
#include <utils/Log.h>
@@ -48,10 +48,189 @@
using namespace hardware::cas::V1_0;
using namespace hardware::cas::native::V1_0;
+/**
+ * Base class for representation of buffers at one port.
+ */
+class CCodecBufferChannel::Buffers {
+public:
+ Buffers() = default;
+ virtual ~Buffers() = default;
+
+ /**
+ * Set format for MediaCodec-facing buffers.
+ */
+ void setFormat(const sp<AMessage> &format) { mFormat = format; }
+
+ /**
+ * Returns true if the buffers are operating under array mode.
+ */
+ virtual bool isArrayMode() const { return false; }
+
+ /**
+ * Fills the vector with MediaCodecBuffer's if in array mode; otherwise,
+ * no-op.
+ */
+ virtual void getArray(Vector<sp<MediaCodecBuffer>> *) const {}
+
+protected:
+ // Format to be used for creating MediaCodec-facing buffers.
+ sp<AMessage> mFormat;
+
+private:
+ DISALLOW_EVIL_CONSTRUCTORS(Buffers);
+};
+
+class CCodecBufferChannel::InputBuffers : public CCodecBufferChannel::Buffers {
+public:
+ InputBuffers() = default;
+ virtual ~InputBuffers() = default;
+
+ /**
+ * Set a block pool to obtain input memory blocks.
+ */
+ void setPool(const std::shared_ptr<C2BlockPool> &pool) { mPool = pool; }
+
+ /**
+ * Get a new MediaCodecBuffer for input and its corresponding index.
+ * Returns false if no new buffer can be obtained at the moment.
+ */
+ virtual bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) = 0;
+
+ /**
+ * Release the buffer obtained from requestNewBuffer() and get the
+ * associated C2Buffer object back. Returns empty shared_ptr if the
+ * buffer is not on file.
+ *
+ * XXX: this is a quick hack to be removed
+ */
+ virtual std::shared_ptr<C2Buffer> releaseBufferIndex(size_t /* index */) { return nullptr; }
+
+ /**
+ * Release the buffer obtained from requestNewBuffer() and get the
+ * associated C2Buffer object back. Returns empty shared_ptr if the
+ * buffer is not on file.
+ */
+ virtual std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) = 0;
+
+ /**
+ * Flush internal state. After this call, no index or buffer previously
+ * returned from requestNewBuffer() is valid.
+ */
+ virtual void flush() = 0;
+
+ /**
+ * Return array-backed version of input buffers. The returned object
+ * shall retain the internal state so that it will honor index and
+ * buffer from previous calls of requestNewBuffer().
+ */
+ virtual std::unique_ptr<InputBuffers> toArrayMode() = 0;
+
+protected:
+ // Pool to obtain blocks for input buffers.
+ std::shared_ptr<C2BlockPool> mPool;
+
+private:
+ DISALLOW_EVIL_CONSTRUCTORS(InputBuffers);
+};
+
+class CCodecBufferChannel::InputBufferClient {
+public:
+ explicit InputBufferClient(
+ const std::shared_ptr<CCodecBufferChannel> &channel) : mChannel(channel) {}
+ virtual ~InputBufferClient() = default;
+
+ virtual void onInputBufferAdded(size_t index, const sp<MediaCodecBuffer> &buffer) {
+ std::shared_ptr<CCodecBufferChannel> channel = mChannel.lock();
+ if (!channel) {
+ return;
+ }
+ channel->mCallback->onInputBufferAvailable(index, buffer);
+ }
+
+ virtual void onStart() {
+ // no-op
+ }
+
+ virtual void onStop() {
+ // no-op
+ }
+
+ virtual void onRelease() {
+ // no-op
+ }
+
+ virtual void onInputBufferAvailable(size_t index, const sp<MediaCodecBuffer> &buffer) {
+ std::shared_ptr<CCodecBufferChannel> channel = mChannel.lock();
+ if (!channel) {
+ return;
+ }
+ channel->mCallback->onInputBufferAvailable(index, buffer);
+ }
+
+protected:
+ InputBufferClient() = default;
+ std::weak_ptr<CCodecBufferChannel> mChannel;
+
+ DISALLOW_EVIL_CONSTRUCTORS(InputBufferClient);
+};
+
+class CCodecBufferChannel::OutputBuffers : public CCodecBufferChannel::Buffers {
+public:
+ OutputBuffers() = default;
+ virtual ~OutputBuffers() = default;
+
+ /**
+ * Register output C2Buffer from the component and obtain corresponding
+ * index and MediaCodecBuffer object. Returns false if registration
+ * fails.
+ */
+ virtual bool registerBuffer(
+ const std::shared_ptr<C2Buffer> &buffer,
+ size_t *index,
+ sp<MediaCodecBuffer> *codecBuffer) = 0;
+
+ /**
+ * Register codec specific data as a buffer to be consistent with
+ * MediaCodec behavior.
+ */
+ virtual bool registerCsd(
+ const C2StreamCsdInfo::output * /* csd */,
+ size_t * /* index */,
+ sp<MediaCodecBuffer> * /* codecBuffer */) {
+ return false;
+ }
+
+ /**
+ * Release the buffer obtained from registerBuffer() and get the
+ * associated C2Buffer object back. Returns empty shared_ptr if the
+ * buffer is not on file.
+ */
+ virtual std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) = 0;
+
+ /**
+ * Flush internal state. After this call, no index or buffer previously
+ * returned from registerBuffer() is valid.
+ */
+ virtual void flush(const std::list<std::unique_ptr<C2Work>> &flushedWork) = 0;
+
+ /**
+ * Return array-backed version of output buffers. The returned object
+ * shall retain the internal state so that it will honor index and
+ * buffer from previous calls of registerBuffer().
+ */
+ virtual std::unique_ptr<OutputBuffers> toArrayMode() = 0;
+
+private:
+ DISALLOW_EVIL_CONSTRUCTORS(OutputBuffers);
+};
+
namespace {
+constexpr int32_t kMaskI32 = ~0;
+
// TODO: get this info from component
const static size_t kMinBufferArraySize = 16;
+const static size_t kLinearBufferSize = 524288;
template <class T>
ssize_t findBufferSlot(
@@ -88,9 +267,14 @@
return Codec2Buffer::allocate(format, block);
}
-class LinearBuffer : public C2Buffer {
+class Buffer1D : public C2Buffer {
public:
- explicit LinearBuffer(C2ConstLinearBlock block) : C2Buffer({ block }) {}
+ explicit Buffer1D(C2ConstLinearBlock block) : C2Buffer({ block }) {}
+};
+
+class Buffer2D : public C2Buffer {
+public:
+ explicit Buffer2D(C2ConstGraphicBlock block) : C2Buffer({ block }) {}
};
class InputBuffersArray : public CCodecBufferChannel::InputBuffers {
@@ -99,36 +283,36 @@
void add(
size_t index,
- const sp<MediaCodecBuffer> &clientBuffer,
- const std::shared_ptr<C2Buffer> &compBuffer,
+ const sp<Codec2Buffer> &clientBuffer,
bool available) {
- if (mBufferArray.size() < index) {
+ if (mBufferArray.size() <= index) {
+ // TODO: make this more efficient
mBufferArray.resize(index + 1);
}
mBufferArray[index].clientBuffer = clientBuffer;
- mBufferArray[index].compBuffer = compBuffer;
mBufferArray[index].available = available;
}
- bool isArrayMode() final { return true; }
+ bool isArrayMode() const final { return true; }
std::unique_ptr<CCodecBufferChannel::InputBuffers> toArrayMode() final {
return nullptr;
}
- void getArray(Vector<sp<MediaCodecBuffer>> *array) final {
+ void getArray(Vector<sp<MediaCodecBuffer>> *array) const final {
array->clear();
- for (const auto &entry : mBufferArray) {
+ for (const Entry &entry : mBufferArray) {
array->push(entry.clientBuffer);
}
}
bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override {
for (size_t i = 0; i < mBufferArray.size(); ++i) {
- if (mBufferArray[i].available) {
+ if (mBufferArray[i].available && mBufferArray[i].compBuffer.expired()) {
mBufferArray[i].available = false;
*index = i;
*buffer = mBufferArray[i].clientBuffer;
+ (*buffer)->setRange(0, (*buffer)->capacity());
return true;
}
}
@@ -138,8 +322,10 @@
std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) override {
for (size_t i = 0; i < mBufferArray.size(); ++i) {
if (!mBufferArray[i].available && mBufferArray[i].clientBuffer == buffer) {
+ std::shared_ptr<C2Buffer> buffer = std::make_shared<Buffer1D>(mBufferArray[i].clientBuffer->share());
mBufferArray[i].available = true;
- return std::move(mBufferArray[i].compBuffer);
+ mBufferArray[i].compBuffer = buffer;
+ return buffer;
}
}
return nullptr;
@@ -148,14 +334,13 @@
void flush() override {
for (size_t i = 0; i < mBufferArray.size(); ++i) {
mBufferArray[i].available = true;
- mBufferArray[i].compBuffer.reset();
}
}
private:
struct Entry {
- sp<MediaCodecBuffer> clientBuffer;
- std::shared_ptr<C2Buffer> compBuffer;
+ sp<Codec2Buffer> clientBuffer;
+ std::weak_ptr<C2Buffer> compBuffer;
bool available;
};
@@ -177,7 +362,7 @@
// TODO: proper max input size and usage
// TODO: read usage from intf
C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
- sp<Codec2Buffer> newBuffer = allocateLinearBuffer(mPool, mFormat, 65536, usage);
+ sp<Codec2Buffer> newBuffer = allocateLinearBuffer(mPool, mFormat, kLinearBufferSize, usage);
if (newBuffer == nullptr) {
return false;
}
@@ -195,7 +380,7 @@
sp<Codec2Buffer> codecBuffer = it->promote();
// We got sp<> reference from the caller so this should never happen..
CHECK(codecBuffer != nullptr);
- return std::make_shared<LinearBuffer>(codecBuffer->share());
+ return std::make_shared<Buffer1D>(codecBuffer->share());
}
void flush() override {
@@ -203,8 +388,10 @@
std::unique_ptr<CCodecBufferChannel::InputBuffers> toArrayMode() final {
std::unique_ptr<InputBuffersArray> array(new InputBuffersArray);
+ array->setFormat(mFormat);
// TODO
const size_t size = std::max(kMinBufferArraySize, mBuffers.size());
+ mBuffers.resize(size);
for (size_t i = 0; i < size; ++i) {
sp<Codec2Buffer> clientBuffer = mBuffers[i].promote();
bool available = false;
@@ -212,13 +399,12 @@
// TODO: proper max input size
// TODO: read usage from intf
C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
- clientBuffer = allocateLinearBuffer(mPool, mFormat, 65536, usage);
+ clientBuffer = allocateLinearBuffer(mPool, mFormat, kLinearBufferSize, usage);
available = true;
}
array->add(
i,
clientBuffer,
- std::make_shared<LinearBuffer>(clientBuffer->share()),
available);
}
return std::move(array);
@@ -230,19 +416,30 @@
std::vector<wp<Codec2Buffer>> mBuffers;
};
-// TODO: stub
class GraphicInputBuffers : public CCodecBufferChannel::InputBuffers {
public:
- using CCodecBufferChannel::InputBuffers::InputBuffers;
+ GraphicInputBuffers() = default;
bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override {
- (void)index;
- (void)buffer;
- return false;
+ *buffer = nullptr;
+ for (size_t i = 0; i < mAvailable.size(); ++i) {
+ if (mAvailable[i]) {
+ *index = i;
+ mAvailable[i] = false;
+ return true;
+ }
+ }
+ *index = mAvailable.size();
+ mAvailable.push_back(false);
+ return true;
}
- std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) override {
- (void)buffer;
+ std::shared_ptr<C2Buffer> releaseBufferIndex(size_t index) override {
+ mAvailable[index] = true;
+ return nullptr;
+ }
+
+ std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &) override {
return nullptr;
}
@@ -252,6 +449,9 @@
std::unique_ptr<CCodecBufferChannel::InputBuffers> toArrayMode() final {
return nullptr;
}
+
+private:
+ std::vector<bool> mAvailable;
};
class OutputBuffersArray : public CCodecBufferChannel::OutputBuffers {
@@ -263,7 +463,8 @@
const sp<MediaCodecBuffer> &clientBuffer,
const std::shared_ptr<C2Buffer> &compBuffer,
bool available) {
- if (mBufferArray.size() < index) {
+ if (mBufferArray.size() <= index) {
+ // TODO: make this more efficient
mBufferArray.resize(index + 1);
}
mBufferArray[index].clientBuffer = clientBuffer;
@@ -271,7 +472,7 @@
mBufferArray[index].available = available;
}
- bool isArrayMode() final { return true; }
+ bool isArrayMode() const final { return true; }
std::unique_ptr<CCodecBufferChannel::OutputBuffers> toArrayMode() final {
return nullptr;
@@ -285,6 +486,7 @@
if (mBufferArray[i].available && copy(buffer, mBufferArray[i].clientBuffer)) {
*index = i;
*codecBuffer = mBufferArray[i].clientBuffer;
+ (*codecBuffer)->setFormat(mFormat);
mBufferArray[i].compBuffer = buffer;
mBufferArray[i].available = false;
return true;
@@ -299,10 +501,17 @@
sp<MediaCodecBuffer> *codecBuffer) final {
for (size_t i = 0; i < mBufferArray.size(); ++i) {
if (mBufferArray[i].available
- && mBufferArray[i].clientBuffer->capacity() <= csd->flexCount()) {
+ && mBufferArray[i].clientBuffer->capacity() >= csd->flexCount()) {
+ // TODO: proper format update
+ sp<ABuffer> csdBuffer = ABuffer::CreateAsCopy(csd->m.value, csd->flexCount());
+ mFormat = mFormat->dup();
+ mFormat->setBuffer("csd-0", csdBuffer);
+
memcpy(mBufferArray[i].clientBuffer->base(), csd->m.value, csd->flexCount());
+ mBufferArray[i].clientBuffer->setRange(0, csd->flexCount());
*index = i;
*codecBuffer = mBufferArray[i].clientBuffer;
+ (*codecBuffer)->setFormat(mFormat);
mBufferArray[i].available = false;
return true;
}
@@ -333,9 +542,9 @@
const std::shared_ptr<C2Buffer> &buffer,
const sp<MediaCodecBuffer> &clientBuffer) = 0;
- void getArray(Vector<sp<MediaCodecBuffer>> *array) final {
+ void getArray(Vector<sp<MediaCodecBuffer>> *array) const final {
array->clear();
- for (const auto &entry : mBufferArray) {
+ for (const Entry &entry : mBufferArray) {
array->push(entry.clientBuffer);
}
}
@@ -363,6 +572,7 @@
}
C2ReadView view = buffer->data().linearBlocks().front().map().get();
if (clientBuffer->capacity() < view.capacity()) {
+ ALOGV("view.capacity() = %u", view.capacity());
return false;
}
clientBuffer->setRange(0u, view.capacity());
@@ -425,9 +635,11 @@
if (ret < 0) {
return false;
}
- sp<MediaCodecBuffer> newBuffer = new MediaCodecBuffer(
- mFormat,
- ABuffer::CreateAsCopy(csd->m.value, csd->flexCount()));
+ // TODO: proper format update
+ sp<ABuffer> csdBuffer = ABuffer::CreateAsCopy(csd->m.value, csd->flexCount());
+ mFormat = mFormat->dup();
+ mFormat->setBuffer("csd-0", csdBuffer);
+ sp<MediaCodecBuffer> newBuffer = new MediaCodecBuffer(mFormat, csdBuffer);
mBuffers[ret] = { newBuffer, nullptr };
*index = ret;
*codecBuffer = newBuffer;
@@ -498,15 +710,17 @@
std::unique_ptr<CCodecBufferChannel::OutputBuffers> toArrayMode() override {
std::unique_ptr<OutputBuffersArray> array(new LinearOutputBuffersArray);
+ array->setFormat(mFormat);
const size_t size = std::max(kMinBufferArraySize, mBuffers.size());
+ mBuffers.resize(size);
for (size_t i = 0; i < size; ++i) {
sp<MediaCodecBuffer> clientBuffer = mBuffers[i].clientBuffer.promote();
std::shared_ptr<C2Buffer> compBuffer = mBuffers[i].bufferRef;
bool available = false;
if (clientBuffer == nullptr) {
// TODO: proper max input size
- clientBuffer = new MediaCodecBuffer(mFormat, new ABuffer(65536));
+ clientBuffer = new MediaCodecBuffer(mFormat, new ABuffer(kLinearBufferSize));
available = true;
compBuffer.reset();
}
@@ -526,8 +740,10 @@
std::unique_ptr<CCodecBufferChannel::OutputBuffers> toArrayMode() override {
std::unique_ptr<OutputBuffersArray> array(new GraphicOutputBuffersArray);
+ array->setFormat(mFormat);
const size_t size = std::max(kMinBufferArraySize, mBuffers.size());
+ mBuffers.resize(size);
for (size_t i = 0; i < size; ++i) {
sp<MediaCodecBuffer> clientBuffer = mBuffers[i].clientBuffer.promote();
std::shared_ptr<C2Buffer> compBuffer = mBuffers[i].bufferRef;
@@ -543,8 +759,163 @@
}
};
+class BufferQueueClient : public CCodecBufferChannel::InputBufferClient {
+public:
+ explicit BufferQueueClient(const sp<GraphicBufferSource> &source) : mSource(source) {}
+ virtual ~BufferQueueClient() = default;
+
+ void onInputBufferAdded(size_t index, const sp<MediaCodecBuffer> &buffer) override {
+ (void)buffer;
+ mSource->onInputBufferAdded(index & kMaskI32);
+ }
+
+ void onStart() override {
+ mSource->start();
+ }
+
+ void onStop() override {
+ mSource->stop();
+ }
+
+ void onRelease() override {
+ mSource->release();
+ }
+
+ void onInputBufferAvailable(size_t index, const sp<MediaCodecBuffer> &buffer) override {
+ ALOGV("onInputBufferEmptied index = %zu", index);
+ (void)buffer;
+ // TODO: can we really ignore fence here?
+ mSource->onInputBufferEmptied(index & kMaskI32, -1 /* fenceFd */);
+ }
+
+private:
+ sp<GraphicBufferSource> mSource;
+};
+
+class GraphicBlock : public C2GraphicBlock {
+ using C2GraphicBlock::C2GraphicBlock;
+ friend class ::android::CCodecBufferChannel;
+};
+
} // namespace
+class CCodecBufferChannel::C2ComponentWrapper : public ComponentWrapper {
+public:
+ explicit C2ComponentWrapper(
+ const std::shared_ptr<CCodecBufferChannel> &channel)
+ : mChannel(channel), mLastTimestamp(0) {}
+
+ virtual ~C2ComponentWrapper() {
+ for (const std::pair<int32_t, C2Handle *> &entry : mHandles) {
+ native_handle_delete(entry.second);
+ }
+ }
+
+ status_t submitBuffer(
+ int32_t bufferId, const sp<GraphicBuffer> &buffer,
+ int64_t timestamp, int fenceFd) override {
+ ALOGV("submitBuffer bufferId = %d", bufferId);
+ // TODO: Use fd to construct fence
+ (void)fenceFd;
+
+ std::shared_ptr<CCodecBufferChannel> channel = mChannel.lock();
+ if (!channel) {
+ return NO_INIT;
+ }
+
+ std::shared_ptr<C2Allocator> allocator = mAllocator.lock();
+ if (!allocator) {
+ c2_status_t err = GetCodec2PlatformAllocatorStore()->fetchAllocator(
+ C2AllocatorStore::PLATFORM_START + 1, // GRALLOC
+ &allocator);
+ if (err != OK) {
+ return UNKNOWN_ERROR;
+ }
+ mAllocator = allocator;
+ }
+
+ std::shared_ptr<C2GraphicAllocation> alloc;
+ C2Handle *handle = WrapNativeCodec2GrallocHandle(
+ buffer->handle, buffer->width, buffer->height,
+ buffer->format, buffer->usage, buffer->stride);
+ c2_status_t err = allocator->priorGraphicAllocation(handle, &alloc);
+ if (err != OK) {
+ return UNKNOWN_ERROR;
+ }
+ std::shared_ptr<C2GraphicBlock> block(new GraphicBlock(alloc));
+
+ std::unique_ptr<C2Work> work(new C2Work);
+ work->input.flags = (C2FrameData::flags_t)0;
+ work->input.ordinal.timestamp = timestamp;
+ work->input.ordinal.frameIndex = channel->mFrameIndex++;
+ work->input.buffers.clear();
+ work->input.buffers.emplace_back(new Buffer2D(
+ // TODO: fence
+ block->share(C2Rect(block->width(), block->height()), ::android::C2Fence())));
+ work->worklets.clear();
+ work->worklets.emplace_back(new C2Worklet);
+ std::list<std::unique_ptr<C2Work>> items;
+ items.push_back(std::move(work));
+
+ err = channel->mComponent->queue_nb(&items);
+ if (err != OK) {
+ native_handle_delete(handle);
+ return UNKNOWN_ERROR;
+ }
+
+ mLastTimestamp = timestamp;
+ if (mHandles.count(bufferId) > 0) {
+ native_handle_delete(mHandles[bufferId]);
+ }
+ mHandles[bufferId] = handle;
+
+ Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(channel->mInputBuffers);
+ ALOGV("releaseBufferIndex index = %d", bufferId);
+ (*buffers)->releaseBufferIndex(bufferId);
+
+ return OK;
+ }
+
+ status_t submitEos(int32_t bufferId) override {
+ std::shared_ptr<CCodecBufferChannel> channel = mChannel.lock();
+ if (!channel) {
+ return NO_INIT;
+ }
+
+ std::unique_ptr<C2Work> work(new C2Work);
+ work->input.flags = C2FrameData::FLAG_END_OF_STREAM;
+ work->input.ordinal.timestamp = mLastTimestamp;
+ work->input.ordinal.frameIndex = channel->mFrameIndex++;
+ work->input.buffers.clear();
+ work->input.buffers.push_back(nullptr);
+ work->worklets.clear();
+ work->worklets.emplace_back(new C2Worklet);
+ std::list<std::unique_ptr<C2Work>> items;
+ items.push_back(std::move(work));
+
+ c2_status_t err = channel->mComponent->queue_nb(&items);
+
+ Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(channel->mInputBuffers);
+ (*buffers)->releaseBufferIndex(bufferId);
+
+ return (err == C2_OK) ? OK : UNKNOWN_ERROR;
+ }
+
+ void dispatchDataSpaceChanged(
+ int32_t dataSpace, int32_t aspects, int32_t pixelFormat) override {
+ // TODO
+ (void)dataSpace;
+ (void)aspects;
+ (void)pixelFormat;
+ }
+
+private:
+ std::weak_ptr<CCodecBufferChannel> mChannel;
+ std::map<int32_t, C2Handle *> mHandles;
+ int64_t mLastTimestamp;
+ std::weak_ptr<C2Allocator> mAllocator;
+};
+
CCodecBufferChannel::QueueGuard::QueueGuard(
CCodecBufferChannel::QueueSync &sync) : mSync(sync) {
std::unique_lock<std::mutex> l(mSync.mMutex);
@@ -601,10 +972,14 @@
if (mCrypto != nullptr && mDealer != nullptr && mHeapSeqNum >= 0) {
mCrypto->unsetHeap(mHeapSeqNum);
}
+ // TODO: is this the right place?
+ mInputClient->onRelease();
}
void CCodecBufferChannel::setComponent(const std::shared_ptr<C2Component> &component) {
mComponent = component;
+ mInputClient.reset(new InputBufferClient(shared_from_this()));
+
C2StreamFormatConfig::input inputFormat(0u);
C2StreamFormatConfig::output outputFormat(0u);
c2_status_t err = mComponent->intf()->query_vb(
@@ -638,6 +1013,10 @@
} else {
// TODO: error
}
+ // TODO: remove once we switch to proper buffer pool.
+ if (!graphic) {
+ *buffers = (*buffers)->toArrayMode();
+ }
}
{
@@ -652,6 +1031,18 @@
}
}
+status_t CCodecBufferChannel::setGraphicBufferSource(
+ const sp<GraphicBufferSource> &source) {
+ ALOGV("setGraphicBufferSource");
+ mInputClient.reset(new BufferQueueClient(source));
+
+ // TODO: proper color aspect & dataspace
+ android_dataspace dataSpace = HAL_DATASPACE_BT709;
+ // TODO: read settings properly from the interface
+ return source->configure(new C2ComponentWrapper(
+ shared_from_this()), dataSpace, 16, 1080, 1920, GRALLOC_USAGE_SW_READ_OFTEN);
+}
+
status_t CCodecBufferChannel::queueInputBuffer(const sp<MediaCodecBuffer> &buffer) {
QueueGuard guard(mSync);
if (!guard.isRunning()) {
@@ -665,16 +1056,16 @@
int32_t flags = 0;
int32_t tmp = 0;
if (buffer->meta()->findInt32("eos", &tmp) && tmp) {
- flags |= C2BufferPack::FLAG_END_OF_STREAM;
+ flags |= C2FrameData::FLAG_END_OF_STREAM;
ALOGV("input EOS");
}
if (buffer->meta()->findInt32("csd", &tmp) && tmp) {
- flags |= C2BufferPack::FLAG_CODEC_CONFIG;
+ flags |= C2FrameData::FLAG_CODEC_CONFIG;
}
std::unique_ptr<C2Work> work(new C2Work);
- work->input.flags = (C2BufferPack::flags_t)flags;
+ work->input.flags = (C2FrameData::flags_t)flags;
work->input.ordinal.timestamp = timeUs;
- work->input.ordinal.frame_index = mFrameIndex++;
+ work->input.ordinal.frameIndex = mFrameIndex++;
work->input.buffers.clear();
{
Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
@@ -708,9 +1099,24 @@
return -ENOSYS;
}
+void CCodecBufferChannel::feedInputBufferIfAvailable() {
+ sp<MediaCodecBuffer> inBuffer;
+ size_t index;
+ {
+ Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
+ if (!(*buffers)->requestNewBuffer(&index, &inBuffer)) {
+ ALOGW("no new buffer available");
+ inBuffer = nullptr;
+ }
+ }
+ ALOGV("new input index = %zu", index);
+ mInputClient->onInputBufferAvailable(index, inBuffer);
+}
+
status_t CCodecBufferChannel::renderOutputBuffer(
const sp<MediaCodecBuffer> &buffer, int64_t timestampNs) {
ALOGV("renderOutputBuffer");
+ feedInputBufferIfAvailable();
std::shared_ptr<C2Buffer> c2Buffer;
{
@@ -724,7 +1130,7 @@
return OK;
}
- std::list<C2ConstGraphicBlock> blocks = c2Buffer->data().graphicBlocks();
+ std::vector<C2ConstGraphicBlock> blocks = c2Buffer->data().graphicBlocks();
if (blocks.size() != 1u) {
ALOGE("# of graphic blocks expected to be 1, but %zu", blocks.size());
return UNKNOWN_ERROR;
@@ -780,7 +1186,9 @@
}
{
Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
- (void)(*buffers)->releaseBuffer(buffer);
+ if((*buffers)->releaseBuffer(buffer)) {
+ feedInputBufferIfAvailable();
+ }
}
return OK;
}
@@ -832,13 +1240,15 @@
return;
}
}
- mCallback->onInputBufferAvailable(index, buffer);
+ mInputClient->onInputBufferAdded(index, buffer);
}
+ mInputClient->onStart();
}
void CCodecBufferChannel::stop() {
mSync.stop();
mFirstValidFrameIndex = mFrameIndex.load();
+ mInputClient->onStop();
}
void CCodecBufferChannel::flush(const std::list<std::unique_ptr<C2Work>> &flushedWork) {
@@ -852,106 +1262,110 @@
}
}
-void CCodecBufferChannel::onWorkDone(std::vector<std::unique_ptr<C2Work>> workItems) {
- for (const auto &work : workItems) {
- sp<MediaCodecBuffer> inBuffer;
- size_t index;
- {
- Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
- if (!(*buffers)->requestNewBuffer(&index, &inBuffer)) {
- ALOGW("no new buffer available");
- inBuffer = nullptr;
- }
- }
- if (inBuffer != nullptr) {
- mCallback->onInputBufferAvailable(index, inBuffer);
- }
+void CCodecBufferChannel::onWorkDone(const std::unique_ptr<C2Work> &work) {
+ if (work->result != OK) {
+ ALOGE("work failed to complete: %d", work->result);
+ mOnError(work->result, ACTION_CODE_FATAL);
+ return;
+ }
- if (work->result != OK) {
- ALOGE("work failed to complete: %d", work->result);
- mOnError(work->result, ACTION_CODE_FATAL);
+ // NOTE: MediaCodec usage supposedly have only one worklet
+ if (work->worklets.size() != 1u) {
+ ALOGE("onWorkDone: incorrect number of worklets: %zu",
+ work->worklets.size());
+ mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
+ return;
+ }
+
+ const std::unique_ptr<C2Worklet> &worklet = work->worklets.front();
+ if ((worklet->output.ordinal.frameIndex - mFirstValidFrameIndex.load()).peek() < 0) {
+ // Discard frames from previous generation.
+ return;
+ }
+ std::shared_ptr<C2Buffer> buffer;
+ // NOTE: MediaCodec usage supposedly have only one output stream.
+ if (worklet->output.buffers.size() > 1u) {
+ ALOGE("onWorkDone: incorrect number of output buffers: %zu",
+ worklet->output.buffers.size());
+ mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
+ return;
+ } else if (worklet->output.buffers.size() == 1u) {
+ buffer = worklet->output.buffers[0];
+ if (!buffer) {
+ ALOGW("onWorkDone: nullptr found in buffers; ignored.");
+ }
+ }
+
+ const C2StreamCsdInfo::output *csdInfo = nullptr;
+ for (const std::unique_ptr<C2Param> &info : worklet->output.configUpdate) {
+ if (info->coreIndex() == C2StreamCsdInfo::output::CORE_INDEX) {
+ ALOGV("onWorkDone: csd found");
+ csdInfo = static_cast<const C2StreamCsdInfo::output *>(info.get());
+ }
+ }
+
+ int32_t flags = 0;
+ if (worklet->output.flags & C2FrameData::FLAG_END_OF_STREAM) {
+ flags |= MediaCodec::BUFFER_FLAG_EOS;
+ ALOGV("onWorkDone: output EOS");
+ }
+
+ sp<MediaCodecBuffer> outBuffer;
+ size_t index;
+ if (csdInfo != nullptr) {
+ Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
+ if ((*buffers)->registerCsd(csdInfo, &index, &outBuffer)) {
+ outBuffer->meta()->setInt64("timeUs", worklet->output.ordinal.timestamp.peek());
+ outBuffer->meta()->setInt32("flags", flags | MediaCodec::BUFFER_FLAG_CODECCONFIG);
+ ALOGV("onWorkDone: csd index = %zu", index);
+
+ buffers.unlock();
+ mCallback->onOutputBufferAvailable(index, outBuffer);
+ buffers.lock();
+ } else {
+ ALOGE("onWorkDone: unable to register csd");
+ buffers.unlock();
+ mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
+ buffers.lock();
return;
}
-
- // NOTE: MediaCodec usage supposedly have only one worklet
- if (work->worklets.size() != 1u) {
- ALOGE("incorrect number of worklets: %zu", work->worklets.size());
- mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
- continue;
- }
-
- const std::unique_ptr<C2Worklet> &worklet = work->worklets.front();
- if (worklet->output.ordinal.frame_index < mFirstValidFrameIndex) {
- // Discard frames from previous generation.
- continue;
- }
- // NOTE: MediaCodec usage supposedly have only one output stream.
- if (worklet->output.buffers.size() != 1u) {
- ALOGE("incorrect number of output buffers: %zu", worklet->output.buffers.size());
- mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
- continue;
- }
-
- const std::shared_ptr<C2Buffer> &buffer = worklet->output.buffers[0];
- const C2StreamCsdInfo::output *csdInfo = nullptr;
- if (buffer) {
- // TODO: transfer infos() into buffer metadata
- }
- for (const auto &info : worklet->output.infos) {
- if (info->coreIndex() == C2StreamCsdInfo::output::CORE_INDEX) {
- ALOGV("csd found");
- csdInfo = static_cast<const C2StreamCsdInfo::output *>(info.get());
- }
- }
-
- int32_t flags = 0;
- if (worklet->output.flags & C2BufferPack::FLAG_END_OF_STREAM) {
- flags |= MediaCodec::BUFFER_FLAG_EOS;
- ALOGV("output EOS");
- }
-
- sp<MediaCodecBuffer> outBuffer;
- if (csdInfo != nullptr) {
- Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
- if ((*buffers)->registerCsd(csdInfo, &index, &outBuffer)) {
- outBuffer->meta()->setInt64("timeUs", worklet->output.ordinal.timestamp);
- outBuffer->meta()->setInt32("flags", flags | MediaCodec::BUFFER_FLAG_CODECCONFIG);
- ALOGV("csd index = %zu", index);
-
- buffers.unlock();
- mCallback->onOutputBufferAvailable(index, outBuffer);
- buffers.lock();
- } else {
- ALOGE("unable to register output buffer");
- buffers.unlock();
- mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
- buffers.lock();
- continue;
- }
- }
-
- if (!buffer && !flags) {
- ALOGV("Not reporting output buffer");
- continue;
- }
-
- {
- Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
- if (!(*buffers)->registerBuffer(buffer, &index, &outBuffer)) {
- ALOGE("unable to register output buffer");
-
- buffers.unlock();
- mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
- buffers.lock();
- continue;
- }
- }
-
- outBuffer->meta()->setInt64("timeUs", worklet->output.ordinal.timestamp);
- outBuffer->meta()->setInt32("flags", flags);
- ALOGV("index = %zu", index);
- mCallback->onOutputBufferAvailable(index, outBuffer);
}
+
+ if (!buffer && !flags) {
+ ALOGV("onWorkDone: Not reporting output buffer");
+ return;
+ }
+
+ if (buffer) {
+ for (const std::shared_ptr<const C2Info> &info : buffer->info()) {
+ // TODO: properly translate these to metadata
+ switch (info->coreIndex().coreIndex()) {
+ case C2StreamPictureTypeMaskInfo::CORE_INDEX:
+ if (((C2StreamPictureTypeMaskInfo *)info.get())->value & C2PictureTypeKeyFrame) {
+ flags |= MediaCodec::BUFFER_FLAG_SYNCFRAME;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ {
+ Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
+ if (!(*buffers)->registerBuffer(buffer, &index, &outBuffer)) {
+ ALOGE("onWorkDone: unable to register output buffer");
+ buffers.unlock();
+ mOnError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
+ buffers.lock();
+ return;
+ }
+ }
+
+ outBuffer->meta()->setInt64("timeUs", worklet->output.ordinal.timestamp.peek());
+ outBuffer->meta()->setInt32("flags", flags);
+ ALOGV("onWorkDone: out buffer index = %zu", index);
+ mCallback->onOutputBufferAvailable(index, outBuffer);
}
status_t CCodecBufferChannel::setSurface(const sp<Surface> &newSurface) {
diff --git a/media/libstagefright/InterfaceUtils.cpp b/media/libstagefright/InterfaceUtils.cpp
index f174ba4..284e63b 100644
--- a/media/libstagefright/InterfaceUtils.cpp
+++ b/media/libstagefright/InterfaceUtils.cpp
@@ -19,7 +19,6 @@
#include <media/stagefright/CallbackMediaSource.h>
#include <media/stagefright/InterfaceUtils.h>
#include <media/stagefright/RemoteDataSource.h>
-#include <media/stagefright/RemoteMediaExtractor.h>
#include <media/stagefright/RemoteMediaSource.h>
namespace android {
@@ -39,7 +38,7 @@
}
sp<IMediaExtractor> CreateIMediaExtractorFromMediaExtractor(
- const sp<MediaExtractor> &extractor, const sp<RefBase> &plugin) {
+ MediaExtractor *extractor, const sp<RefBase> &plugin) {
if (extractor == nullptr) {
return nullptr;
}
@@ -53,12 +52,13 @@
return new CallbackMediaSource(source);
}
-sp<IMediaSource> CreateIMediaSourceFromMediaSource(
- const sp<MediaSource> &source, const sp<RefBase> &plugin) {
+sp<IMediaSource> CreateIMediaSourceFromMediaSourceBase(
+ const sp<RemoteMediaExtractor> &extractor,
+ MediaSourceBase *source, const sp<RefBase> &plugin) {
if (source == nullptr) {
return nullptr;
}
- return RemoteMediaSource::wrap(source, plugin);
+ return RemoteMediaSource::wrap(extractor, source, plugin);
}
} // namespace android
diff --git a/media/libstagefright/MediaCodecSource.cpp b/media/libstagefright/MediaCodecSource.cpp
index 8bd0a51..04d83af 100644
--- a/media/libstagefright/MediaCodecSource.cpp
+++ b/media/libstagefright/MediaCodecSource.cpp
@@ -23,6 +23,7 @@
#include <gui/IGraphicBufferProducer.h>
#include <gui/Surface.h>
#include <media/ICrypto.h>
+#include <media/MediaBufferHolder.h>
#include <media/MediaCodecBuffer.h>
#include <media/MediaSource.h>
#include <media/stagefright/foundation/ABuffer.h>
@@ -739,7 +740,8 @@
if (mIsVideo) {
// video encoder will release MediaBuffer when done
// with underlying data.
- inbuf->setMediaBufferBase(mbuf);
+ inbuf->meta()->setObject("mediaBufferHolder", new MediaBufferHolder(mbuf));
+ mbuf->release();
} else {
mbuf->release();
}
diff --git a/media/libstagefright/MediaExtractorFactory.cpp b/media/libstagefright/MediaExtractorFactory.cpp
index 472c137..e79696c 100644
--- a/media/libstagefright/MediaExtractorFactory.cpp
+++ b/media/libstagefright/MediaExtractorFactory.cpp
@@ -358,12 +358,16 @@
Mutex::Autolock autoLock(gPluginMutex);
String8 out;
out.append("Available extractors:\n");
- for (auto it = gPlugins->begin(); it != gPlugins->end(); ++it) {
- out.appendFormat(" %25s: uuid(%s), version(%u), path(%s)\n",
- (*it)->def.extractor_name,
- (*it)->uuidString.c_str(),
- (*it)->def.extractor_version,
- (*it)->libPath.c_str());
+ if (gPluginsRegistered) {
+ for (auto it = gPlugins->begin(); it != gPlugins->end(); ++it) {
+ out.appendFormat(" %25s: uuid(%s), version(%u), path(%s)\n",
+ (*it)->def.extractor_name,
+ (*it)->uuidString.c_str(),
+ (*it)->def.extractor_version,
+ (*it)->libPath.c_str());
+ }
+ } else {
+ out.append(" (no plugins registered)\n");
}
write(fd, out.string(), out.size());
return OK;
diff --git a/media/libstagefright/NuMediaExtractor.cpp b/media/libstagefright/NuMediaExtractor.cpp
index 17c9648..d96f7e0 100644
--- a/media/libstagefright/NuMediaExtractor.cpp
+++ b/media/libstagefright/NuMediaExtractor.cpp
@@ -660,6 +660,28 @@
return err;
}
+status_t NuMediaExtractor::getSampleSize(size_t *sampleSize) {
+ Mutex::Autolock autoLock(mLock);
+
+ ssize_t minIndex = fetchAllTrackSamples();
+
+ if (minIndex < 0) {
+ return ERROR_END_OF_STREAM;
+ }
+
+ TrackInfo *info = &mSelectedTracks.editItemAt(minIndex);
+ auto it = info->mSamples.begin();
+ *sampleSize = it->mBuffer->range_length();
+
+ if (info->mTrackFlags & kIsVorbis) {
+ // Each sample's data is suffixed by the number of page samples
+ // or -1 if not available.
+ *sampleSize += sizeof(int32_t);
+ }
+
+ return OK;
+}
+
status_t NuMediaExtractor::getSampleTrackIndex(size_t *trackIndex) {
Mutex::Autolock autoLock(mLock);
diff --git a/media/libstagefright/RemoteMediaExtractor.cpp b/media/libstagefright/RemoteMediaExtractor.cpp
index 12654d9..43d33e5 100644
--- a/media/libstagefright/RemoteMediaExtractor.cpp
+++ b/media/libstagefright/RemoteMediaExtractor.cpp
@@ -37,7 +37,7 @@
static const char *kExtractorFormat = "android.media.mediaextractor.fmt";
RemoteMediaExtractor::RemoteMediaExtractor(
- const sp<MediaExtractor> &extractor, const sp<RefBase> &plugin)
+ MediaExtractor *extractor, const sp<RefBase> &plugin)
:mExtractor(extractor),
mExtractorPlugin(plugin) {
@@ -67,7 +67,7 @@
}
RemoteMediaExtractor::~RemoteMediaExtractor() {
- mExtractor = nullptr;
+ delete mExtractor;
mExtractorPlugin = nullptr;
// log the current record, provided it has some information worth recording
if (MEDIA_LOG) {
@@ -89,9 +89,9 @@
}
sp<IMediaSource> RemoteMediaExtractor::getTrack(size_t index) {
- sp<MediaSource> source = mExtractor->getTrack(index);
- return (source.get() == nullptr)
- ? nullptr : CreateIMediaSourceFromMediaSource(source, mExtractorPlugin);
+ MediaSourceBase *source = mExtractor->getTrack(index);
+ return (source == nullptr)
+ ? nullptr : CreateIMediaSourceFromMediaSourceBase(this, source, mExtractorPlugin);
}
sp<MetaData> RemoteMediaExtractor::getTrackMetaData(size_t index, uint32_t flags) {
@@ -139,8 +139,8 @@
// static
sp<IMediaExtractor> RemoteMediaExtractor::wrap(
- const sp<MediaExtractor> &extractor, const sp<RefBase> &plugin) {
- if (extractor.get() == nullptr) {
+ MediaExtractor *extractor, const sp<RefBase> &plugin) {
+ if (extractor == nullptr) {
return nullptr;
}
return new RemoteMediaExtractor(extractor, plugin);
diff --git a/media/libstagefright/RemoteMediaSource.cpp b/media/libstagefright/RemoteMediaSource.cpp
index d97329c..6b48ce8 100644
--- a/media/libstagefright/RemoteMediaSource.cpp
+++ b/media/libstagefright/RemoteMediaSource.cpp
@@ -14,17 +14,22 @@
* limitations under the License.
*/
+#include <media/stagefright/RemoteMediaExtractor.h>
#include <media/stagefright/RemoteMediaSource.h>
#include <media/IMediaSource.h>
namespace android {
-RemoteMediaSource::RemoteMediaSource(const sp<MediaSource> &source, const sp<RefBase> &plugin)
- :mSource(source),
- mExtractorPlugin(plugin) {}
+RemoteMediaSource::RemoteMediaSource(
+ const sp<RemoteMediaExtractor> &extractor,
+ MediaSourceBase *source,
+ const sp<RefBase> &plugin)
+ : mExtractor(extractor),
+ mSource(source),
+ mExtractorPlugin(plugin) {}
RemoteMediaSource::~RemoteMediaSource() {
- mSource = nullptr;
+ delete mSource;
mExtractorPlugin = nullptr;
}
@@ -55,11 +60,13 @@
////////////////////////////////////////////////////////////////////////////////
// static
-sp<IMediaSource> RemoteMediaSource::wrap(const sp<MediaSource> &source, const sp<RefBase> &plugin) {
- if (source.get() == nullptr) {
+sp<IMediaSource> RemoteMediaSource::wrap(
+ const sp<RemoteMediaExtractor> &extractor,
+ MediaSourceBase *source, const sp<RefBase> &plugin) {
+ if (source == nullptr) {
return nullptr;
}
- return new RemoteMediaSource(source, plugin);
+ return new RemoteMediaSource(extractor, source, plugin);
}
} // namespace android
diff --git a/media/libstagefright/codec2/SimpleC2Component.cpp b/media/libstagefright/codec2/SimpleC2Component.cpp
index 4d75a31..c3ae00e 100644
--- a/media/libstagefright/codec2/SimpleC2Component.cpp
+++ b/media/libstagefright/codec2/SimpleC2Component.cpp
@@ -342,7 +342,7 @@
return;
}
}
- if (work->worklets_processed != 0u) {
+ if (work->workletsProcessed != 0u) {
Mutexed<ExecState>::Locked state(mExecState);
ALOGV("returning this work");
state->mListener->onWorkDone_nb(shared_from_this(), vec(work));
@@ -351,7 +351,7 @@
std::unique_ptr<C2Work> unexpected;
{
Mutexed<PendingWork>::Locked pending(mPendingWork);
- uint64_t frameIndex = work->input.ordinal.frame_index;
+ uint64_t frameIndex = work->input.ordinal.frameIndex.peeku();
if (pending->count(frameIndex) != 0) {
unexpected = std::move(pending->at(frameIndex));
pending->erase(frameIndex);
diff --git a/media/libstagefright/codec2/include/C2.h b/media/libstagefright/codec2/include/C2.h
index fd99cce..442c89e 100644
--- a/media/libstagefright/codec2/include/C2.h
+++ b/media/libstagefright/codec2/include/C2.h
@@ -210,6 +210,219 @@
} \
DEFINE_OTHER_COMPARISON_OPERATORS(type)
+template<typename T, typename B>
+class C2_HIDE c2_cntr_t;
+
+/// \cond INTERNAL
+
+/// \defgroup utils_internal
+/// @{
+
+template<typename T>
+struct C2_HIDE _c2_cntr_compat_helper {
+ template<typename U, typename E=typename std::enable_if<std::is_integral<U>::value>::type>
+ __attribute__((no_sanitize("integer")))
+ inline static constexpr T get(const U &value) {
+ return T(value);
+ }
+
+ template<typename U, typename E=typename std::enable_if<(sizeof(U) >= sizeof(T))>::type>
+ __attribute__((no_sanitize("integer")))
+ inline static constexpr T get(const c2_cntr_t<U, void> &value) {
+ return T(value.mValue);
+ }
+};
+
+/// @}
+
+/// \endcond
+
+/**
+ * Integral counter type.
+ *
+ * This is basically an unsigned integral type that is NEVER checked for overflow/underflow - and
+ * comparison operators are redefined.
+ *
+ * \note Comparison of counter types is not fully transitive, e.g.
+ * it could be that a > b > c but a !> c.
+ * std::less<>, greater<>, less_equal<> and greater_equal<> specializations yield total ordering,
+ * but may not match semantic ordering of the values.
+ *
+ * Technically: counter types represent integer values: A * 2^N + value, where A can be arbitrary.
+ * This makes addition, subtraction, multiplication (as well as bitwise operations) well defined.
+ * However, division is in general not well defined, as the result may depend on A. This is also
+ * true for logical operators and boolean conversion.
+ *
+ * Even though well defined, bitwise operators are not implemented for counter types as they are not
+ * meaningful.
+ */
+template<typename T, typename B=typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value>::type>
+class C2_HIDE c2_cntr_t {
+ using compat = _c2_cntr_compat_helper<T>;
+
+ T mValue;
+ constexpr static T HALF_RANGE = T(~0) ^ (T(~0) >> 1);
+
+ template<typename U>
+ friend struct _c2_cntr_compat_helper;
+public:
+
+ /**
+ * Default constructor. Initialized counter to 0.
+ */
+ inline constexpr c2_cntr_t() : mValue(T(0)) {}
+
+ /**
+ * Construct from a compatible type.
+ */
+ template<typename U>
+ inline constexpr c2_cntr_t(const U &value) : mValue(compat::get(value)) {}
+
+ /**
+ * Peek as underlying signed type.
+ */
+ __attribute__((no_sanitize("integer")))
+ inline constexpr typename std::make_signed<T>::type peek() const {
+ return static_cast<typename std::make_signed<T>::type>(mValue);
+ }
+
+ /**
+ * Peek as underlying unsigned type.
+ */
+ inline constexpr T peeku() const {
+ return mValue;
+ }
+
+ /**
+ * Peek as long long - e.g. for printing.
+ */
+ __attribute__((no_sanitize("integer")))
+ inline constexpr long long peekll() const {
+ return (long long)mValue;
+ }
+
+ /**
+ * Peek as unsigned long long - e.g. for printing.
+ */
+ __attribute__((no_sanitize("integer")))
+ inline constexpr unsigned long long peekull() const {
+ return (unsigned long long)mValue;
+ }
+
+ /**
+ * Convert to a smaller counter type. This is always safe.
+ */
+ template<typename U, typename E=typename std::enable_if<sizeof(U) < sizeof(T)>::type>
+ inline operator c2_cntr_t<U>() {
+ return c2_cntr_t<U>(mValue);
+ }
+
+ /**
+ * Arithmetic operators
+ */
+
+#define DEFINE_C2_CNTR_BINARY_OP(attrib, op, op_assign) \
+ template<typename U> \
+ attrib inline c2_cntr_t<T>& operator op_assign(const U &value) { \
+ mValue op_assign compat::get(value); \
+ return *this; \
+ } \
+ \
+ template<typename U, typename E=decltype(compat::get(U(0)))> \
+ attrib inline constexpr c2_cntr_t<T> operator op(const U &value) const { \
+ return c2_cntr_t<T>(mValue op compat::get(value)); \
+ } \
+ \
+ template<typename U, typename E=typename std::enable_if<sizeof(U) < sizeof(T)>::type> \
+ attrib inline constexpr c2_cntr_t<U> operator op(const c2_cntr_t<U> &value) const { \
+ return c2_cntr_t<U>(U(mValue) op value.peeku()); \
+ }
+
+#define DEFINE_C2_CNTR_UNARY_OP(attrib, op) \
+ attrib inline constexpr c2_cntr_t<T> operator op() const { \
+ return c2_cntr_t<T>(op mValue); \
+ }
+
+#define DEFINE_C2_CNTR_CREMENT_OP(attrib, op) \
+ attrib inline c2_cntr_t<T> &operator op() { \
+ op mValue; \
+ return *this; \
+ } \
+ attrib inline c2_cntr_t<T> operator op(int) { \
+ return c2_cntr_t<T, void>(mValue op); \
+ }
+
+ DEFINE_C2_CNTR_BINARY_OP(__attribute__((no_sanitize("integer"))), +, +=)
+ DEFINE_C2_CNTR_BINARY_OP(__attribute__((no_sanitize("integer"))), -, -=)
+ DEFINE_C2_CNTR_BINARY_OP(__attribute__((no_sanitize("integer"))), *, *=)
+
+ DEFINE_C2_CNTR_UNARY_OP(__attribute__((no_sanitize("integer"))), -)
+ DEFINE_C2_CNTR_UNARY_OP(__attribute__((no_sanitize("integer"))), +)
+
+ DEFINE_C2_CNTR_CREMENT_OP(__attribute__((no_sanitize("integer"))), ++)
+ DEFINE_C2_CNTR_CREMENT_OP(__attribute__((no_sanitize("integer"))), --)
+
+ template<typename U, typename E=typename std::enable_if<std::is_unsigned<U>::value>::type>
+ __attribute__((no_sanitize("integer")))
+ inline constexpr c2_cntr_t<T> operator<<(const U &value) const {
+ return c2_cntr_t<T>(mValue << value);
+ }
+
+ template<typename U, typename E=typename std::enable_if<std::is_unsigned<U>::value>::type>
+ __attribute__((no_sanitize("integer")))
+ inline c2_cntr_t<T> &operator<<=(const U &value) {
+ mValue <<= value;
+ return *this;
+ }
+
+ /**
+ * Comparison operators
+ */
+ __attribute__((no_sanitize("integer")))
+ inline constexpr bool operator<=(const c2_cntr_t<T> &other) const {
+ return T(other.mValue - mValue) < HALF_RANGE;
+ }
+
+ __attribute__((no_sanitize("integer")))
+ inline constexpr bool operator>=(const c2_cntr_t<T> &other) const {
+ return T(mValue - other.mValue) < HALF_RANGE;
+ }
+
+ inline constexpr bool operator==(const c2_cntr_t<T> &other) const {
+ return mValue == other.mValue;
+ }
+
+ inline constexpr bool operator!=(const c2_cntr_t<T> &other) const {
+ return !(*this == other);
+ }
+
+ inline constexpr bool operator<(const c2_cntr_t<T> &other) const {
+ return *this <= other && *this != other;
+ }
+
+ inline constexpr bool operator>(const c2_cntr_t<T> &other) const {
+ return *this >= other && *this != other;
+ }
+};
+
+template<typename U, typename T, typename E=typename std::enable_if<std::is_integral<U>::value>::type>
+inline constexpr c2_cntr_t<T> operator+(const U &a, const c2_cntr_t<T> &b) {
+ return b + a;
+}
+
+template<typename U, typename T, typename E=typename std::enable_if<std::is_integral<U>::value>::type>
+inline constexpr c2_cntr_t<T> operator-(const U &a, const c2_cntr_t<T> &b) {
+ return c2_cntr_t<T>(a) - b;
+}
+
+template<typename U, typename T, typename E=typename std::enable_if<std::is_integral<U>::value>::type>
+inline constexpr c2_cntr_t<T> operator*(const U &a, const c2_cntr_t<T> &b) {
+ return b * a;
+}
+
+typedef c2_cntr_t<uint32_t> c2_cntr32_t; /** 32-bit counter type */
+typedef c2_cntr_t<uint64_t> c2_cntr64_t; /** 64-bit counter type */
+
/// \cond INTERNAL
/// \defgroup utils_internal
@@ -326,4 +539,30 @@
} // namespace android
#endif
+#include <functional>
+template<typename T>
+struct std::less<::android::c2_cntr_t<T>> {
+ constexpr bool operator()(const ::android::c2_cntr_t<T> &lh, const ::android::c2_cntr_t<T> &rh) const {
+ return lh.peeku() < rh.peeku();
+ }
+};
+template<typename T>
+struct std::less_equal<::android::c2_cntr_t<T>> {
+ constexpr bool operator()(const ::android::c2_cntr_t<T> &lh, const ::android::c2_cntr_t<T> &rh) const {
+ return lh.peeku() <= rh.peeku();
+ }
+};
+template<typename T>
+struct std::greater<::android::c2_cntr_t<T>> {
+ constexpr bool operator()(const ::android::c2_cntr_t<T> &lh, const ::android::c2_cntr_t<T> &rh) const {
+ return lh.peeku() > rh.peeku();
+ }
+};
+template<typename T>
+struct std::greater_equal<::android::c2_cntr_t<T>> {
+ constexpr bool operator()(const ::android::c2_cntr_t<T> &lh, const ::android::c2_cntr_t<T> &rh) const {
+ return lh.peeku() >= rh.peeku();
+ }
+};
+
#endif // C2_H_
diff --git a/media/libstagefright/codec2/include/C2Buffer.h b/media/libstagefright/codec2/include/C2Buffer.h
index cd90978..2ca8222 100644
--- a/media/libstagefright/codec2/include/C2Buffer.h
+++ b/media/libstagefright/codec2/include/C2Buffer.h
@@ -679,14 +679,14 @@
*
* \param size number of bytes to share
* \param fence fence to be used for the section
- * \param blocks list where the blocks of the section are appended to
+ * \param blocks vector where the blocks of the section are appended to
*
* \retval C2_OK the portion was successfully shared
* \retval C2_NO_MEMORY not enough memory to share the portion
* \retval C2_TIMED_OUT the operation timed out (unexpected)
* \retval C2_CORRUPTED some unknown error prevented sharing the data (unexpected)
*/
- c2_status_t share(size_t size, C2Fence fence, std::list<C2ConstLinearBlock> &blocks);
+ c2_status_t share(size_t size, C2Fence fence, std::vector<C2ConstLinearBlock> &blocks);
/**
* Returns the beginning offset of this segment from the start of this circular block.
@@ -1203,14 +1203,14 @@
* \return a constant list of const linear blocks of this buffer.
* \retval empty list if this buffer does not contain linear block(s).
*/
- const std::list<C2ConstLinearBlock> linearBlocks() const;
+ const std::vector<C2ConstLinearBlock> linearBlocks() const;
/**
* Gets the graphic blocks of this buffer.
* \return a constant list of const graphic blocks of this buffer.
* \retval empty list if this buffer does not contain graphic block(s).
*/
- const std::list<C2ConstGraphicBlock> graphicBlocks() const;
+ const std::vector<C2ConstGraphicBlock> graphicBlocks() const;
private:
class Impl;
@@ -1218,8 +1218,8 @@
protected:
// no public constructor
- explicit C2BufferData(const std::list<C2ConstLinearBlock> &blocks);
- explicit C2BufferData(const std::list<C2ConstGraphicBlock> &blocks);
+ explicit C2BufferData(const std::vector<C2ConstLinearBlock> &blocks);
+ explicit C2BufferData(const std::vector<C2ConstGraphicBlock> &blocks);
};
/**
@@ -1301,7 +1301,7 @@
*
* \return a constant list of info objects associated with this buffer.
*/
- const std::list<std::shared_ptr<const C2Info>> infos() const;
+ const std::vector<std::shared_ptr<const C2Info>> info() const;
/**
* Attaches (or updates) an (existing) metadata for this buffer.
@@ -1328,8 +1328,8 @@
protected:
// no public constructor
- explicit C2Buffer(const std::list<C2ConstLinearBlock> &blocks);
- explicit C2Buffer(const std::list<C2ConstGraphicBlock> &blocks);
+ explicit C2Buffer(const std::vector<C2ConstLinearBlock> &blocks);
+ explicit C2Buffer(const std::vector<C2ConstGraphicBlock> &blocks);
private:
class Impl;
diff --git a/media/libstagefright/codec2/include/C2Component.h b/media/libstagefright/codec2/include/C2Component.h
index a2168a0..e023db4 100644
--- a/media/libstagefright/codec2/include/C2Component.h
+++ b/media/libstagefright/codec2/include/C2Component.h
@@ -682,7 +682,7 @@
*/
virtual c2_status_t reset() { return C2_OK; }
- virtual c2_status_t parseFrame(C2BufferPack &frame);
+ virtual c2_status_t parseFrame(C2FrameData &frame);
virtual ~C2FrameInfoParser() = default;
};
diff --git a/media/libstagefright/codec2/include/C2Config.h b/media/libstagefright/codec2/include/C2Config.h
index 83cb72c..2a2b9de 100644
--- a/media/libstagefright/codec2/include/C2Config.h
+++ b/media/libstagefright/codec2/include/C2Config.h
@@ -67,6 +67,7 @@
kParamIndexVideoSizeTuning,
kParamIndexCsd,
+ kParamIndexPictureTypeMask,
// video info
@@ -133,6 +134,12 @@
typedef C2StreamParam<C2Info, C2BlobValue, kParamIndexCsd> C2StreamCsdInfo;
+C2ENUM(C2PictureTypeMask, uint32_t,
+ C2PictureTypeKeyFrame = (1u << 0),
+)
+
+typedef C2StreamParam<C2Info, C2Uint32Value, kParamIndexPictureTypeMask> C2StreamPictureTypeMaskInfo;
+
/*
Component description fields:
diff --git a/media/libstagefright/codec2/include/C2Param.h b/media/libstagefright/codec2/include/C2Param.h
index 9785c87..e2df62d 100644
--- a/media/libstagefright/codec2/include/C2Param.h
+++ b/media/libstagefright/codec2/include/C2Param.h
@@ -721,6 +721,10 @@
DEFINE_OTHER_COMPARISON_OPERATORS(C2ParamField)
+protected:
+ inline C2ParamField(C2Param::Index index, uint32_t offset, uint32_t size)
+ : _mIndex(index), _mFieldId(offset, size) {}
+
private:
friend struct _C2ParamInspector;
@@ -729,24 +733,39 @@
};
/**
+ * Structure uniquely specifying a field, an array element of a field, or a
+ * parameter in a configuration
+ */
+struct C2ParamOrField : public C2ParamField {
+//public:
+ template<typename S>
+ inline C2ParamOrField(S* param)
+ : C2ParamField(param->index(), 0u, param->size()) {}
+};
+
+/**
* A shared (union) representation of numeric values
*/
class C2Value {
public:
/// A union of supported primitive types.
union Primitive {
- int32_t i32; ///< int32_t value
- uint32_t u32; ///< uint32_t value
- int64_t i64; ///< int64_t value
- uint64_t u64; ///< uint64_t value
- float fp; ///< float value
+ int32_t i32; ///< int32_t value
+ uint32_t u32; ///< uint32_t value
+ c2_cntr32_t c32; ///< c2_cntr32_t value
+ int64_t i64; ///< int64_t value
+ uint64_t u64; ///< uint64_t value
+ c2_cntr64_t c64; ///< c2_cntr64_t value
+ float fp; ///< float value
// constructors - implicit
- Primitive(int32_t value) : i32(value) { }
- Primitive(uint32_t value) : u32(value) { }
- Primitive(int64_t value) : i64(value) { }
- Primitive(uint64_t value) : u64(value) { }
- Primitive(float value) : fp(value) { }
+ Primitive(int32_t value) : i32(value) { }
+ Primitive(uint32_t value) : u32(value) { }
+ Primitive(c2_cntr32_t value) : c32(value) { }
+ Primitive(int64_t value) : i64(value) { }
+ Primitive(uint64_t value) : u64(value) { }
+ Primitive(c2_cntr64_t value) : c64(value) { }
+ Primitive(float value) : fp(value) { }
Primitive() : u64(0) { }
@@ -759,8 +778,10 @@
NO_INIT,
INT32,
UINT32,
+ CNTR32,
INT64,
UINT64,
+ CNTR64,
FLOAT,
};
@@ -792,12 +813,16 @@
template<> inline const int64_t &C2Value::Primitive::ref<int64_t>() const { return i64; }
template<> inline const uint32_t &C2Value::Primitive::ref<uint32_t>() const { return u32; }
template<> inline const uint64_t &C2Value::Primitive::ref<uint64_t>() const { return u64; }
+template<> inline const c2_cntr32_t &C2Value::Primitive::ref<c2_cntr32_t>() const { return c32; }
+template<> inline const c2_cntr64_t &C2Value::Primitive::ref<c2_cntr64_t>() const { return c64; }
template<> inline const float &C2Value::Primitive::ref<float>() const { return fp; }
template<> constexpr C2Value::type_t C2Value::typeFor<int32_t>() { return INT32; }
template<> constexpr C2Value::type_t C2Value::typeFor<int64_t>() { return INT64; }
template<> constexpr C2Value::type_t C2Value::typeFor<uint32_t>() { return UINT32; }
template<> constexpr C2Value::type_t C2Value::typeFor<uint64_t>() { return UINT64; }
+template<> constexpr C2Value::type_t C2Value::typeFor<c2_cntr32_t>() { return CNTR32; }
+template<> constexpr C2Value::type_t C2Value::typeFor<c2_cntr64_t>() { return CNTR64; }
template<> constexpr C2Value::type_t C2Value::typeFor<float>() { return FLOAT; }
/**
@@ -817,8 +842,10 @@
// primitive types
INT32 = C2Value::INT32, ///< 32-bit signed integer
UINT32 = C2Value::UINT32, ///< 32-bit unsigned integer
+ CNTR32 = C2Value::CNTR32, ///< 32-bit counter
INT64 = C2Value::INT64, ///< 64-bit signed integer
UINT64 = C2Value::UINT64, ///< 64-bit signed integer
+ CNTR64 = C2Value::CNTR64, ///< 64-bit counter
FLOAT = C2Value::FLOAT, ///< 32-bit floating point
// array types
@@ -903,13 +930,15 @@
// 2) this is at parameter granularity.
// type resolution
- inline static type_t getType(int32_t*) { return INT32; }
- inline static type_t getType(uint32_t*) { return UINT32; }
- inline static type_t getType(int64_t*) { return INT64; }
- inline static type_t getType(uint64_t*) { return UINT64; }
- inline static type_t getType(float*) { return FLOAT; }
- inline static type_t getType(char*) { return STRING; }
- inline static type_t getType(uint8_t*) { return BLOB; }
+ inline static type_t getType(int32_t*) { return INT32; }
+ inline static type_t getType(uint32_t*) { return UINT32; }
+ inline static type_t getType(c2_cntr32_t*) { return CNTR32; }
+ inline static type_t getType(int64_t*) { return INT64; }
+ inline static type_t getType(uint64_t*) { return UINT64; }
+ inline static type_t getType(c2_cntr64_t*) { return CNTR64; }
+ inline static type_t getType(float*) { return FLOAT; }
+ inline static type_t getType(char*) { return STRING; }
+ inline static type_t getType(uint8_t*) { return BLOB; }
template<typename T,
class=typename std::enable_if<std::is_enum<T>::value>::type>
@@ -936,8 +965,10 @@
// non-enumerated integral types.
DEFINE_NO_NAMED_VALUES_FOR(int32_t)
DEFINE_NO_NAMED_VALUES_FOR(uint32_t)
+DEFINE_NO_NAMED_VALUES_FOR(c2_cntr32_t)
DEFINE_NO_NAMED_VALUES_FOR(int64_t)
DEFINE_NO_NAMED_VALUES_FOR(uint64_t)
+DEFINE_NO_NAMED_VALUES_FOR(c2_cntr64_t)
DEFINE_NO_NAMED_VALUES_FOR(uint8_t)
DEFINE_NO_NAMED_VALUES_FOR(char)
DEFINE_NO_NAMED_VALUES_FOR(float)
diff --git a/media/libstagefright/codec2/include/C2Work.h b/media/libstagefright/codec2/include/C2Work.h
index 105cf81..58a9174 100644
--- a/media/libstagefright/codec2/include/C2Work.h
+++ b/media/libstagefright/codec2/include/C2Work.h
@@ -83,32 +83,64 @@
kParamIndexWorkOrdinal,
};
+/**
+ * Information for ordering work items on a component port.
+ */
struct C2WorkOrdinalStruct {
- uint64_t timestamp;
- uint64_t frame_index; // submission ordinal on the initial component
- uint64_t custom_ordinal; // can be given by the component, e.g. decode order
+//public:
+ c2_cntr64_t timestamp; /** frame timestamp in microseconds */
+ c2_cntr64_t frameIndex; /** submission ordinal on the initial component */
+ c2_cntr64_t customOrdinal; /** can be given by the component, e.g. decode order */
DEFINE_AND_DESCRIBE_C2STRUCT(WorkOrdinal)
C2FIELD(timestamp, "timestamp")
- C2FIELD(frame_index, "frame-index")
- C2FIELD(custom_ordinal, "custom-ordinal")
+ C2FIELD(frameIndex, "frame-index")
+ C2FIELD(customOrdinal, "custom-ordinal")
};
-struct C2BufferPack {
+/**
+ * This structure represents a Codec 2.0 frame with its metadata.
+ *
+ * A frame basically consists of an ordered sets of buffers, configuration changes and info buffers
+ * along with some non-configuration metadata.
+ */
+struct C2FrameData {
//public:
enum flags_t : uint32_t {
- FLAG_CODEC_CONFIG = (1 << 0),
- FLAG_DROP_FRAME = (1 << 1),
- FLAG_END_OF_STREAM = (1 << 2),
+ /**
+ * For input frames: no output frame shall be generated when processing this frame, but
+ * metadata shall still be processed.
+ * For output frames: this frame shall be discarded and but metadata is still valid.
+ */
+ FLAG_DROP_FRAME = (1 << 0),
+ /**
+ * This frame is the last frame of the current stream. Further frames are part of a new
+ * stream.
+ */
+ FLAG_END_OF_STREAM = (1 << 1),
+ /**
+ * This frame shall be discarded with its metadata.
+ * This flag is only set by components - e.g. as a response to the flush command.
+ */
+ FLAG_DISCARD_FRAME = (1 << 2),
+ /**
+ * This frame contains only codec-specific configuration data, and no actual access unit.
+ *
+ * \deprecated pass codec configuration with using the \todo codec-specific configuration
+ * info together with the access unit.
+ */
+ FLAG_CODEC_CONFIG = (1u << 31),
};
+ /**
+ * Frame flags */
flags_t flags;
C2WorkOrdinalStruct ordinal;
std::vector<std::shared_ptr<C2Buffer>> buffers;
//< for initial work item, these may also come from the parser - if provided
//< for output buffers, these are the responses to requestedInfos
- std::list<std::unique_ptr<C2Info>> infos;
- std::list<std::shared_ptr<C2InfoBuffer>> infoBuffers;
+ std::vector<std::unique_ptr<C2Param>> configUpdate;
+ std::vector<std::shared_ptr<C2InfoBuffer>> infoBuffers;
};
struct C2Worklet {
@@ -116,59 +148,61 @@
// IN
c2_node_id_t component;
- std::list<std::unique_ptr<C2Param>> tunings; //< tunings to be applied before processing this
- // worklet
- std::list<C2Param::Type> requestedInfos;
- std::vector<std::shared_ptr<C2BlockPool>> allocators; //< This vector shall be the same size as
- //< output.buffers. \deprecated
+ /** Configuration changes to be applied before processing this worklet. */
+ std::vector<std::unique_ptr<C2Tuning>> tunings;
+ std::vector<std::unique_ptr<C2SettingResult>> failures;
// OUT
- C2BufferPack output;
- std::list<std::unique_ptr<C2SettingResult>> failures;
+ C2FrameData output;
};
/**
+ * Information about partial work-chains not part of the current work items.
+ *
+ * To be defined later.
+ */
+struct C2WorkChainInfo;
+
+/**
* This structure holds information about all a single work item.
*
* This structure shall be passed by the client to the component for the first worklet. As such,
* worklets must not be empty. The ownership of this object is passed.
- *
- * input:
- * The input data to be processed. This is provided by the client with ownership. When the work
- * is returned, the input buffer-pack's buffer vector shall contain nullptrs.
- *
- * worklets:
- * The chain of components and associated allocators, tunings and info requests that the data
- * must pass through. If this has more than a single element, the tunnels between successive
- * components of the worklet chain must have been (successfully) pre-registered at the time
- * the work is submitted. Allocating the output buffers in the worklets is the responsibility
- * of each component. Upon work submission, each output buffer-pack shall be an appropriately
- * sized vector containing nullptrs. When the work is completed/returned to the client,
- *
- * worklets_processed:
- * It shall be initialized to 0 by the client when the work is submitted.
- * It shall contain the number of worklets that were successfully processed when the work is
- * returned. If this is less then the number of worklets, result must not be success.
- * It must be in the range of [0, worklets.size()].
- *
- * result:
- * The final outcome of the work. If 0 when work is returned, it is assumed that all worklets
- * have been processed.
*/
struct C2Work {
//public:
- // pre-chain infos (for portions of a tunneling chain that happend before this work-chain for
- // this work item - due to framework facilitated (non-tunneled) work-chaining)
- std::list<std::pair<std::unique_ptr<C2PortMimeConfig>, std::unique_ptr<C2Info>>> preChainInfos;
- std::list<std::pair<std::unique_ptr<C2PortMimeConfig>, std::unique_ptr<C2Buffer>>> preChainInfoBlobs;
+ /// additional work chain info not part of this work
+ std::shared_ptr<C2WorkChainInfo> chainInfo;
- C2BufferPack input;
+ /// The input data to be processed as part of this work/work-chain. This is provided by the
+ /// client with ownership. When the work is returned (via onWorkDone), the input buffer-pack's
+ /// buffer vector shall contain nullptrs.
+ C2FrameData input;
+
+ /// The chain of components, tunings (including output buffer pool IDs) and info requests that the
+ /// data must pass through. If this has more than a single element, the tunnels between successive
+ /// components of the worklet chain must have been (successfully) pre-registered at the time that
+ /// the work is submitted. Allocating the output buffers in the worklets is the responsibility of
+ /// each component. Upon work submission, each output buffer-pack shall be an appropriately sized
+ /// vector containing nullptrs. When the work is completed/returned to the client, output buffers
+ /// pointers from all but the final worklet shall be nullptrs.
std::list<std::unique_ptr<C2Worklet>> worklets;
- uint32_t worklets_processed;
+ /// Number of worklets successfully processed in this chain. This shall be initialized to 0 by the
+ /// client when the work is submitted. It shall contain the number of worklets that were
+ /// successfully processed when the work is returned to the client. If this is less then the number
+ /// of worklets, result must not be success. It must be in the range of [0, worklets.size()].
+ uint32_t workletsProcessed;
+
+ /// The final outcome of the work (corresponding to the current workletsProcessed). If 0 when
+ /// work is returned, it is assumed that all worklets have been processed.
c2_status_t result;
};
+/**
+ * Information about a future work to be submitted to the component. The information is used to
+ * reserve the work for work ordering purposes.
+ */
struct C2WorkOutline {
//public:
C2WorkOrdinalStruct ordinal;
diff --git a/media/libstagefright/codec2/tests/C2_test.cpp b/media/libstagefright/codec2/tests/C2_test.cpp
index 92a3d91..46f545f 100644
--- a/media/libstagefright/codec2/tests/C2_test.cpp
+++ b/media/libstagefright/codec2/tests/C2_test.cpp
@@ -75,4 +75,88 @@
static_assert(c2_const_checker<max_u32_u64>::num() == 3, "should be 3");
static_assert(c2_const_checker<max_u32_u8>::num() == 0x7fffffff, "should be 0x7fffffff");
+/* ======================================= COUNTER TESTS ======================================= */
+
+void c2_cntr_static_test() {
+ // sanity checks for construction/assignment
+ constexpr c2_cntr32_t c32_a(123);
+ constexpr c2_cntr64_t c64_a(-456);
+ c2_cntr32_t c32_b __unused = c64_a;
+ // c32_b = 64.; // DISALLOWED
+ // c2_cntr64_t c64_b = c32_a; // DISALLOWED
+
+ // sanity checks for some constexpr operators
+ static_assert(std::is_same<decltype(c32_a + c64_a), decltype(c64_a + c32_a)>::value, "+ should result same type");
+ static_assert(c32_a + c64_a == c2_cntr32_t(-333), "123 + -456 = -333");
+ static_assert(c32_a + c32_a == c2_cntr32_t(246), "123 + 123 = 246");
+ static_assert(c64_a + c32_a == c2_cntr32_t(-333), "-456 + 123 = 579");
+ static_assert(std::is_same<decltype(c32_a + 1), decltype(1 + c32_a)>::value, "+ should result same type");
+ static_assert(c32_a + 456 == c2_cntr32_t(579), "123 + 456 = 579");
+ static_assert(456 + c64_a == c2_cntr64_t(0), "456 + -456 = 0");
+ static_assert(std::is_same<decltype(c32_a - c64_a), decltype(c64_a - c32_a)>::value, "- should result same type");
+ static_assert(c32_a - c64_a == c2_cntr32_t(579), "123 - -456 = 579");
+ static_assert(c32_a - c32_a == c2_cntr32_t(0), "123 - 123 = 0");
+ static_assert(c64_a - c32_a == c2_cntr32_t(-579), "-456 - 123 = -579");
+ static_assert(std::is_same<decltype(c32_a - 1), decltype(1 - c32_a)>::value, "- should result same type");
+ static_assert(c32_a - 456 == c2_cntr32_t(-333), "123 - 456 = -333");
+ static_assert(456 - c64_a == c2_cntr64_t(912), "456 - -456 = 912");
+ static_assert(std::is_same<decltype(c32_a * c64_a), decltype(c64_a * c32_a)>::value, "* should result same type");
+ static_assert(c32_a * c64_a == c2_cntr32_t(-56088), "123 * -456 = -56088");
+ static_assert(c32_a * c32_a == c2_cntr32_t(15129), "123 * 123 = 15129");
+ static_assert(c64_a * c32_a == c2_cntr32_t(-56088), "-456 * 123 = -56088");
+ static_assert(std::is_same<decltype(c32_a * 1), decltype(1 * c32_a)>::value, "* should result same type");
+ static_assert(c32_a * 456 == c2_cntr32_t(56088), "123 * 456 = 56088");
+ static_assert(456 * c64_a == c2_cntr64_t(-207936), "456 * -456 = -207936");
+
+ static_assert((c32_a << 26u) == c2_cntr32_t(0xEC000000), "123 << 26 = 0xEC000000");
+
+ // sanity checks for unary operators
+ static_assert(c2_cntr32_t(1) == +c2_cntr32_t(1), "1 == +1");
+ static_assert(c2_cntr32_t(1) == -c2_cntr32_t(-1), "1 == --1");
+
+ // sanity checks for comparison
+ using c8_t = c2_cntr_t<uint8_t>;
+ static_assert(c8_t(-0x80) > c8_t(0x7f), "80 > 7F");
+ static_assert(c8_t(-0x80) >= c8_t(0x7f), "80 >= 7F");
+ static_assert(c8_t(0x7f) > c8_t(0x7e), "7F > 7E");
+ static_assert(c8_t(0x7f) >= c8_t(0x7e), "7F >= 7E");
+ static_assert(!(c8_t(-0x80) > c8_t(0)), "80 !> 00");
+ static_assert(!(c8_t(-0x80) >= c8_t(0)), "80 !>= 00");
+ static_assert(!(c8_t(-0x80) > c8_t(-0x80)), "80 !> 80");
+ static_assert(c8_t(-0x80) >= c8_t(-0x80), "80 >= 80");
+
+ static_assert(c8_t(-0x80) == c8_t(0x80), "80 == 80");
+ static_assert(!(c8_t(-0x80) == c8_t(0)), "80 != 0");
+ static_assert(c8_t(-0x80) != c8_t(0x7f), "80 != 7F");
+ static_assert(!(c8_t(0x7f) != c8_t(0x7f)), "80 != 7F");
+
+ static_assert(c8_t(0x7f) < c8_t(-0x80), "7F < 80");
+ static_assert(c8_t(0x7f) <= c8_t(-0x80), "7F < 80");
+ static_assert(c8_t(0x7e) < c8_t(0x7f), "7E < 7F");
+ static_assert(c8_t(0x7e) <= c8_t(0x7f), "7E < 7F");
+ static_assert(!(c8_t(-0x40) < c8_t(0x40)), "-40 !< 40");
+ static_assert(!(c8_t(-0x40) <= c8_t(0x40)), "-40 !<= 40");
+ static_assert(!(c8_t(-0x40) < c8_t(-0x40)), "-40 !< -40");
+ static_assert(c8_t(-0x40) <= c8_t(-0x40), "-40 <= -40");
+
+ static_assert(c2_cntr32_t(-0x7fffffff - 1) > c2_cntr32_t(0x7fffffff), "80 > 7F");
+ static_assert(!(c2_cntr32_t(-0x7fffffff - 1) > c2_cntr32_t(0)), "80 !> 00");
+ static_assert(c2_cntr32_t(1) == c2_cntr32_t(c2_cntr64_t(0x100000001ul)), "1 == 1");
+}
+
+class C2Test : public ::testing::Test {
+};
+
+TEST_F(C2Test, CounterTest) {
+ c2_cntr32_t c32_a(123);
+ c2_cntr64_t c64_a(-456);
+ EXPECT_EQ(c32_a += 3, c2_cntr32_t(126));
+ EXPECT_EQ(c32_a += c64_a, c2_cntr32_t(-330));
+ EXPECT_EQ(c32_a <<= 2u, c2_cntr32_t(-1320));
+ EXPECT_EQ(c64_a *= 3, c2_cntr64_t(-1368));
+ EXPECT_EQ(c32_a -= c64_a, c2_cntr32_t(48));
+ EXPECT_EQ(c32_a -= 40, c2_cntr32_t(8));
+ EXPECT_EQ(c32_a *= c32_a, c2_cntr32_t(64));
+}
+
} // namespace android
diff --git a/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp b/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp
index f6e6478..a310717 100644
--- a/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp
+++ b/media/libstagefright/codec2/tests/vndk/C2BufferTest.cpp
@@ -359,14 +359,14 @@
class BufferData : public C2BufferData {
public:
- explicit BufferData(const std::list<C2ConstLinearBlock> &blocks) : C2BufferData(blocks) {}
- explicit BufferData(const std::list<C2ConstGraphicBlock> &blocks) : C2BufferData(blocks) {}
+ explicit BufferData(const std::vector<C2ConstLinearBlock> &blocks) : C2BufferData(blocks) {}
+ explicit BufferData(const std::vector<C2ConstGraphicBlock> &blocks) : C2BufferData(blocks) {}
};
class Buffer : public C2Buffer {
public:
- explicit Buffer(const std::list<C2ConstLinearBlock> &blocks) : C2Buffer(blocks) {}
- explicit Buffer(const std::list<C2ConstGraphicBlock> &blocks) : C2Buffer(blocks) {}
+ explicit Buffer(const std::vector<C2ConstLinearBlock> &blocks) : C2Buffer(blocks) {}
+ explicit Buffer(const std::vector<C2ConstGraphicBlock> &blocks) : C2Buffer(blocks) {}
};
TEST_F(C2BufferTest, BufferDataTest) {
@@ -487,45 +487,45 @@
std::shared_ptr<C2Info> info1(new C2Number1Info(1));
std::shared_ptr<C2Info> info2(new C2Number2Info(2));
buffer.reset(new Buffer( { block->share(0, kCapacity, C2Fence()) }));
- EXPECT_TRUE(buffer->infos().empty());
+ EXPECT_TRUE(buffer->info().empty());
EXPECT_FALSE(buffer->hasInfo(info1->type()));
EXPECT_FALSE(buffer->hasInfo(info2->type()));
ASSERT_EQ(C2_OK, buffer->setInfo(info1));
- EXPECT_EQ(1u, buffer->infos().size());
- EXPECT_EQ(*info1, *buffer->infos().front());
+ EXPECT_EQ(1u, buffer->info().size());
+ EXPECT_EQ(*info1, *buffer->info().front());
EXPECT_TRUE(buffer->hasInfo(info1->type()));
EXPECT_FALSE(buffer->hasInfo(info2->type()));
ASSERT_EQ(C2_OK, buffer->setInfo(info2));
- EXPECT_EQ(2u, buffer->infos().size());
+ EXPECT_EQ(2u, buffer->info().size());
EXPECT_TRUE(buffer->hasInfo(info1->type()));
EXPECT_TRUE(buffer->hasInfo(info2->type()));
std::shared_ptr<C2Info> removed = buffer->removeInfo(info1->type());
ASSERT_TRUE(removed);
EXPECT_EQ(*removed, *info1);
- EXPECT_EQ(1u, buffer->infos().size());
- EXPECT_EQ(*info2, *buffer->infos().front());
+ EXPECT_EQ(1u, buffer->info().size());
+ EXPECT_EQ(*info2, *buffer->info().front());
EXPECT_FALSE(buffer->hasInfo(info1->type()));
EXPECT_TRUE(buffer->hasInfo(info2->type()));
removed = buffer->removeInfo(info1->type());
ASSERT_FALSE(removed);
- EXPECT_EQ(1u, buffer->infos().size());
+ EXPECT_EQ(1u, buffer->info().size());
EXPECT_FALSE(buffer->hasInfo(info1->type()));
EXPECT_TRUE(buffer->hasInfo(info2->type()));
std::shared_ptr<C2Info> info3(new C2Number2Info(3));
ASSERT_EQ(C2_OK, buffer->setInfo(info3));
- EXPECT_EQ(1u, buffer->infos().size());
+ EXPECT_EQ(1u, buffer->info().size());
EXPECT_FALSE(buffer->hasInfo(info1->type()));
EXPECT_TRUE(buffer->hasInfo(info2->type()));
removed = buffer->removeInfo(info2->type());
ASSERT_TRUE(removed);
EXPECT_EQ(*info3, *removed);
- EXPECT_TRUE(buffer->infos().empty());
+ EXPECT_TRUE(buffer->info().empty());
EXPECT_FALSE(buffer->hasInfo(info1->type()));
EXPECT_FALSE(buffer->hasInfo(info2->type()));
}
diff --git a/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp b/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp
index 18db3e9..a5ea511 100644
--- a/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp
+++ b/media/libstagefright/codec2/vndk/C2AllocatorGralloc.cpp
@@ -38,6 +38,15 @@
using ::android::hardware::hidl_handle;
using ::android::hardware::hidl_vec;
+namespace {
+
+struct BufferDescriptorInfo {
+ IMapper::BufferDescriptorInfo mapperInfo;
+ uint32_t stride;
+};
+
+}
+
/* ===================================== GRALLOC ALLOCATION ==================================== */
static c2_status_t maperr2error(Error maperr) {
switch (maperr) {
@@ -73,6 +82,7 @@
uint32_t format;
uint32_t usage_lo;
uint32_t usage_hi;
+ uint32_t stride;
uint32_t magic;
};
@@ -109,13 +119,16 @@
static C2HandleGralloc* WrapNativeHandle(
const native_handle_t *const handle,
- uint32_t width, uint32_t height, uint32_t format, uint64_t usage) {
+ uint32_t width, uint32_t height, uint32_t format, uint64_t usage, uint32_t stride) {
//CHECK(handle != nullptr);
if (native_handle_is_invalid(handle) ||
handle->numInts > int((INT_MAX - handle->version) / sizeof(int)) - NUM_INTS - handle->numFds) {
return nullptr;
}
- ExtraData xd = { width, height, format, uint32_t(usage & 0xFFFFFFFF), uint32_t(usage >> 32), MAGIC };
+ ExtraData xd = {
+ width, height, format, uint32_t(usage & 0xFFFFFFFF), uint32_t(usage >> 32),
+ stride, MAGIC
+ };
native_handle_t *res = native_handle_create(handle->numFds, handle->numInts + NUM_INTS);
if (res != nullptr) {
memcpy(&res->data, &handle->data, sizeof(int) * (handle->numFds + handle->numInts));
@@ -138,7 +151,8 @@
static const C2HandleGralloc* Import(
const C2Handle *const handle,
- uint32_t *width, uint32_t *height, uint32_t *format, uint64_t *usage) {
+ uint32_t *width, uint32_t *height, uint32_t *format,
+ uint64_t *usage, uint32_t *stride) {
const ExtraData *xd = getExtraData(handle);
if (xd == nullptr) {
return nullptr;
@@ -147,15 +161,22 @@
*height = xd->height;
*format = xd->format;
*usage = xd->usage_lo | (uint64_t(xd->usage_hi) << 32);
+ *stride = xd->stride;
return reinterpret_cast<const C2HandleGralloc *>(handle);
}
};
-native_handle_t* UnwrapNativeCodec2GrallocHandle(const C2Handle *const handle) {
+native_handle_t *UnwrapNativeCodec2GrallocHandle(const C2Handle *const handle) {
return C2HandleGralloc::UnwrapNativeHandle(handle);
}
+C2Handle *WrapNativeCodec2GrallocHandle(
+ const native_handle_t *const handle,
+ uint32_t width, uint32_t height, uint32_t format, uint64_t usage, uint32_t stride) {
+ return C2HandleGralloc::WrapNativeHandle(handle, width, height, format, usage, stride);
+}
+
class C2AllocationGralloc : public C2GraphicAllocation {
public:
virtual ~C2AllocationGralloc() override;
@@ -171,7 +192,7 @@
// internal methods
// |handle| will be moved.
C2AllocationGralloc(
- const IMapper::BufferDescriptorInfo &info,
+ const BufferDescriptorInfo &info,
const sp<IMapper> &mapper,
hidl_handle &hidlHandle,
const C2HandleGralloc *const handle);
@@ -179,7 +200,7 @@
c2_status_t status() const;
private:
- const IMapper::BufferDescriptorInfo mInfo;
+ const BufferDescriptorInfo mInfo;
const sp<IMapper> mMapper;
const hidl_handle mHidlHandle;
const C2HandleGralloc *mHandle;
@@ -189,11 +210,11 @@
};
C2AllocationGralloc::C2AllocationGralloc(
- const IMapper::BufferDescriptorInfo &info,
+ const BufferDescriptorInfo &info,
const sp<IMapper> &mapper,
hidl_handle &hidlHandle,
const C2HandleGralloc *const handle)
- : C2GraphicAllocation(info.width, info.height),
+ : C2GraphicAllocation(info.mapperInfo.width, info.mapperInfo.height),
mInfo(info),
mMapper(mapper),
mHidlHandle(std::move(hidlHandle)),
@@ -241,83 +262,133 @@
return C2_CORRUPTED;
}
mLockedHandle = C2HandleGralloc::WrapNativeHandle(
- mBuffer, mInfo.width, mInfo.height, (uint32_t)mInfo.format, mInfo.usage);
+ mBuffer, mInfo.mapperInfo.width, mInfo.mapperInfo.height,
+ (uint32_t)mInfo.mapperInfo.format, mInfo.mapperInfo.usage, mInfo.stride);
}
- if (mInfo.format == PixelFormat::YCBCR_420_888 || mInfo.format == PixelFormat::YV12) {
- YCbCrLayout ycbcrLayout;
- mMapper->lockYCbCr(
- const_cast<native_handle_t *>(mBuffer),
- BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
- { (int32_t)rect.left, (int32_t)rect.top, (int32_t)rect.width, (int32_t)rect.height },
- // TODO: fence
- hidl_handle(),
- [&err, &ycbcrLayout](const auto &maperr, const auto &mapLayout) {
- err = maperr2error(maperr);
- if (err == C2_OK) {
- ycbcrLayout = mapLayout;
- }
- });
- if (err != C2_OK) {
- return err;
+ switch (mInfo.mapperInfo.format) {
+ case PixelFormat::YCBCR_420_888:
+ case PixelFormat::YV12: {
+ YCbCrLayout ycbcrLayout;
+ mMapper->lockYCbCr(
+ const_cast<native_handle_t *>(mBuffer),
+ BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
+ { (int32_t)rect.left, (int32_t)rect.top, (int32_t)rect.width, (int32_t)rect.height },
+ // TODO: fence
+ hidl_handle(),
+ [&err, &ycbcrLayout](const auto &maperr, const auto &mapLayout) {
+ err = maperr2error(maperr);
+ if (err == C2_OK) {
+ ycbcrLayout = mapLayout;
+ }
+ });
+ if (err != C2_OK) {
+ return err;
+ }
+ addr[C2PlanarLayout::PLANE_Y] = (uint8_t *)ycbcrLayout.y;
+ addr[C2PlanarLayout::PLANE_U] = (uint8_t *)ycbcrLayout.cb;
+ addr[C2PlanarLayout::PLANE_V] = (uint8_t *)ycbcrLayout.cr;
+ layout->type = C2PlanarLayout::TYPE_YUV;
+ layout->numPlanes = 3;
+ layout->planes[C2PlanarLayout::PLANE_Y] = {
+ C2PlaneInfo::CHANNEL_Y, // channel
+ 1, // colInc
+ (int32_t)ycbcrLayout.yStride, // rowInc
+ 1, // mColSampling
+ 1, // mRowSampling
+ 8, // allocatedDepth
+ 8, // bitDepth
+ 0, // rightShift
+ C2PlaneInfo::NATIVE, // endianness
+ };
+ layout->planes[C2PlanarLayout::PLANE_U] = {
+ C2PlaneInfo::CHANNEL_CB, // channel
+ (int32_t)ycbcrLayout.chromaStep, // colInc
+ (int32_t)ycbcrLayout.cStride, // rowInc
+ 2, // mColSampling
+ 2, // mRowSampling
+ 8, // allocatedDepth
+ 8, // bitDepth
+ 0, // rightShift
+ C2PlaneInfo::NATIVE, // endianness
+ };
+ layout->planes[C2PlanarLayout::PLANE_V] = {
+ C2PlaneInfo::CHANNEL_CR, // channel
+ (int32_t)ycbcrLayout.chromaStep, // colInc
+ (int32_t)ycbcrLayout.cStride, // rowInc
+ 2, // mColSampling
+ 2, // mRowSampling
+ 8, // allocatedDepth
+ 8, // bitDepth
+ 0, // rightShift
+ C2PlaneInfo::NATIVE, // endianness
+ };
+ break;
}
- addr[C2PlanarLayout::PLANE_Y] = (uint8_t *)ycbcrLayout.y;
- addr[C2PlanarLayout::PLANE_U] = (uint8_t *)ycbcrLayout.cb;
- addr[C2PlanarLayout::PLANE_V] = (uint8_t *)ycbcrLayout.cr;
- layout->type = C2PlanarLayout::TYPE_YUV;
- layout->numPlanes = 3;
- layout->planes[C2PlanarLayout::PLANE_Y] = {
- C2PlaneInfo::CHANNEL_Y, // channel
- 1, // colInc
- (int32_t)ycbcrLayout.yStride, // rowInc
- 1, // mColSampling
- 1, // mRowSampling
- 8, // allocatedDepth
- 8, // bitDepth
- 0, // rightShift
- C2PlaneInfo::NATIVE, // endianness
- };
- layout->planes[C2PlanarLayout::PLANE_U] = {
- C2PlaneInfo::CHANNEL_CB, // channel
- (int32_t)ycbcrLayout.chromaStep, // colInc
- (int32_t)ycbcrLayout.cStride, // rowInc
- 2, // mColSampling
- 2, // mRowSampling
- 8, // allocatedDepth
- 8, // bitDepth
- 0, // rightShift
- C2PlaneInfo::NATIVE, // endianness
- };
- layout->planes[C2PlanarLayout::PLANE_V] = {
- C2PlaneInfo::CHANNEL_CR, // channel
- (int32_t)ycbcrLayout.chromaStep, // colInc
- (int32_t)ycbcrLayout.cStride, // rowInc
- 2, // mColSampling
- 2, // mRowSampling
- 8, // allocatedDepth
- 8, // bitDepth
- 0, // rightShift
- C2PlaneInfo::NATIVE, // endianness
- };
- } else {
- void *pointer = nullptr;
- mMapper->lock(
- const_cast<native_handle_t *>(mBuffer),
- BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
- { (int32_t)rect.left, (int32_t)rect.top, (int32_t)rect.width, (int32_t)rect.height },
- // TODO: fence
- hidl_handle(),
- [&err, &pointer](const auto &maperr, const auto &mapPointer) {
- err = maperr2error(maperr);
- if (err == C2_OK) {
- pointer = mapPointer;
- }
- });
- if (err != C2_OK) {
- return err;
+
+ case PixelFormat::RGBA_8888:
+ // TODO: alpha channel
+ // fall-through
+ case PixelFormat::RGBX_8888: {
+ void *pointer = nullptr;
+ mMapper->lock(
+ const_cast<native_handle_t *>(mBuffer),
+ BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
+ { (int32_t)rect.left, (int32_t)rect.top, (int32_t)rect.width, (int32_t)rect.height },
+ // TODO: fence
+ hidl_handle(),
+ [&err, &pointer](const auto &maperr, const auto &mapPointer) {
+ err = maperr2error(maperr);
+ if (err == C2_OK) {
+ pointer = mapPointer;
+ }
+ });
+ if (err != C2_OK) {
+ return err;
+ }
+ addr[C2PlanarLayout::PLANE_R] = (uint8_t *)pointer;
+ addr[C2PlanarLayout::PLANE_G] = (uint8_t *)pointer + 1;
+ addr[C2PlanarLayout::PLANE_B] = (uint8_t *)pointer + 2;
+ layout->type = C2PlanarLayout::TYPE_RGB;
+ layout->numPlanes = 3;
+ layout->planes[C2PlanarLayout::PLANE_R] = {
+ C2PlaneInfo::CHANNEL_R, // channel
+ 4, // colInc
+ 4 * (int32_t)mInfo.stride, // rowInc
+ 1, // mColSampling
+ 1, // mRowSampling
+ 8, // allocatedDepth
+ 8, // bitDepth
+ 0, // rightShift
+ C2PlaneInfo::NATIVE, // endianness
+ };
+ layout->planes[C2PlanarLayout::PLANE_G] = {
+ C2PlaneInfo::CHANNEL_G, // channel
+ 4, // colInc
+ 4 * (int32_t)mInfo.stride, // rowInc
+ 1, // mColSampling
+ 1, // mRowSampling
+ 8, // allocatedDepth
+ 8, // bitDepth
+ 0, // rightShift
+ C2PlaneInfo::NATIVE, // endianness
+ };
+ layout->planes[C2PlanarLayout::PLANE_B] = {
+ C2PlaneInfo::CHANNEL_B, // channel
+ 4, // colInc
+ 4 * (int32_t)mInfo.stride, // rowInc
+ 1, // mColSampling
+ 1, // mRowSampling
+ 8, // allocatedDepth
+ 8, // bitDepth
+ 0, // rightShift
+ C2PlaneInfo::NATIVE, // endianness
+ };
+ break;
}
- // TODO
- return C2_OMITTED;
+ default: {
+ return C2_OMITTED;
+ }
}
mLocked = true;
@@ -396,17 +467,20 @@
// TODO: buffer usage should be determined according to |usage|
(void) usage;
- IMapper::BufferDescriptorInfo info = {
- width,
- height,
- 1u, // layerCount
- (PixelFormat)format,
- BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
+ BufferDescriptorInfo info = {
+ {
+ width,
+ height,
+ 1u, // layerCount
+ (PixelFormat)format,
+ BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
+ },
+ 0u, // stride placeholder
};
c2_status_t err = C2_OK;
BufferDescriptor desc;
mMapper->createDescriptor(
- info, [&err, &desc](const auto &maperr, const auto &descriptor) {
+ info.mapperInfo, [&err, &desc](const auto &maperr, const auto &descriptor) {
err = maperr2error(maperr);
if (err == C2_OK) {
desc = descriptor;
@@ -421,8 +495,7 @@
mAllocator->allocate(
desc,
1u,
- [&err, &buffer](const auto &maperr, const auto &stride, auto &buffers) {
- (void) stride;
+ [&err, &buffer, &info](const auto &maperr, const auto &stride, auto &buffers) {
err = maperr2error(maperr);
if (err != C2_OK) {
return;
@@ -431,6 +504,7 @@
err = C2_CORRUPTED;
return;
}
+ info.stride = stride;
buffer = std::move(buffers[0]);
});
if (err != C2_OK) {
@@ -442,18 +516,20 @@
info, mMapper, buffer,
C2HandleGralloc::WrapNativeHandle(
buffer.getNativeHandle(),
- info.width, info.height, (uint32_t)info.format, info.usage)));
+ info.mapperInfo.width, info.mapperInfo.height,
+ (uint32_t)info.mapperInfo.format, info.mapperInfo.usage, info.stride)));
return C2_OK;
}
c2_status_t C2AllocatorGralloc::Impl::priorGraphicAllocation(
const C2Handle *handle,
std::shared_ptr<C2GraphicAllocation> *allocation) {
- IMapper::BufferDescriptorInfo info;
- info.layerCount = 1u;
+ BufferDescriptorInfo info;
+ info.mapperInfo.layerCount = 1u;
const C2HandleGralloc *grallocHandle = C2HandleGralloc::Import(
handle,
- &info.width, &info.height, (uint32_t *)&info.format, (uint64_t *)&info.usage);
+ &info.mapperInfo.width, &info.mapperInfo.height,
+ (uint32_t *)&info.mapperInfo.format, (uint64_t *)&info.mapperInfo.usage, &info.stride);
if (grallocHandle == nullptr) {
return C2_BAD_VALUE;
}
@@ -461,7 +537,7 @@
hidl_handle hidlHandle = C2HandleGralloc::UnwrapNativeHandle(grallocHandle);
allocation->reset(new C2AllocationGralloc(info, mMapper, hidlHandle, grallocHandle));
- return C2_OMITTED;
+ return C2_OK;
}
C2AllocatorGralloc::C2AllocatorGralloc() : mImpl(new Impl) {}
diff --git a/media/libstagefright/codec2/vndk/C2Buffer.cpp b/media/libstagefright/codec2/vndk/C2Buffer.cpp
index 65a271e..4ab3e05 100644
--- a/media/libstagefright/codec2/vndk/C2Buffer.cpp
+++ b/media/libstagefright/codec2/vndk/C2Buffer.cpp
@@ -602,44 +602,44 @@
class C2BufferData::Impl {
public:
- explicit Impl(const std::list<C2ConstLinearBlock> &blocks)
+ explicit Impl(const std::vector<C2ConstLinearBlock> &blocks)
: mType(blocks.size() == 1 ? LINEAR : LINEAR_CHUNKS),
mLinearBlocks(blocks) {
}
- explicit Impl(const std::list<C2ConstGraphicBlock> &blocks)
+ explicit Impl(const std::vector<C2ConstGraphicBlock> &blocks)
: mType(blocks.size() == 1 ? GRAPHIC : GRAPHIC_CHUNKS),
mGraphicBlocks(blocks) {
}
Type type() const { return mType; }
- const std::list<C2ConstLinearBlock> &linearBlocks() const { return mLinearBlocks; }
- const std::list<C2ConstGraphicBlock> &graphicBlocks() const { return mGraphicBlocks; }
+ const std::vector<C2ConstLinearBlock> &linearBlocks() const { return mLinearBlocks; }
+ const std::vector<C2ConstGraphicBlock> &graphicBlocks() const { return mGraphicBlocks; }
private:
Type mType;
- std::list<C2ConstLinearBlock> mLinearBlocks;
- std::list<C2ConstGraphicBlock> mGraphicBlocks;
+ std::vector<C2ConstLinearBlock> mLinearBlocks;
+ std::vector<C2ConstGraphicBlock> mGraphicBlocks;
};
-C2BufferData::C2BufferData(const std::list<C2ConstLinearBlock> &blocks) : mImpl(new Impl(blocks)) {}
-C2BufferData::C2BufferData(const std::list<C2ConstGraphicBlock> &blocks) : mImpl(new Impl(blocks)) {}
+C2BufferData::C2BufferData(const std::vector<C2ConstLinearBlock> &blocks) : mImpl(new Impl(blocks)) {}
+C2BufferData::C2BufferData(const std::vector<C2ConstGraphicBlock> &blocks) : mImpl(new Impl(blocks)) {}
C2BufferData::Type C2BufferData::type() const { return mImpl->type(); }
-const std::list<C2ConstLinearBlock> C2BufferData::linearBlocks() const {
+const std::vector<C2ConstLinearBlock> C2BufferData::linearBlocks() const {
return mImpl->linearBlocks();
}
-const std::list<C2ConstGraphicBlock> C2BufferData::graphicBlocks() const {
+const std::vector<C2ConstGraphicBlock> C2BufferData::graphicBlocks() const {
return mImpl->graphicBlocks();
}
class C2Buffer::Impl {
public:
- Impl(C2Buffer *thiz, const std::list<C2ConstLinearBlock> &blocks)
+ Impl(C2Buffer *thiz, const std::vector<C2ConstLinearBlock> &blocks)
: mThis(thiz), mData(blocks) {}
- Impl(C2Buffer *thiz, const std::list<C2ConstGraphicBlock> &blocks)
+ Impl(C2Buffer *thiz, const std::vector<C2ConstGraphicBlock> &blocks)
: mThis(thiz), mData(blocks) {}
~Impl() {
@@ -676,8 +676,8 @@
return C2_OK;
}
- std::list<std::shared_ptr<const C2Info>> infos() const {
- std::list<std::shared_ptr<const C2Info>> result(mInfos.size());
+ std::vector<std::shared_ptr<const C2Info>> info() const {
+ std::vector<std::shared_ptr<const C2Info>> result(mInfos.size());
std::transform(
mInfos.begin(), mInfos.end(), result.begin(),
[] (const auto &elem) { return elem.second; });
@@ -712,10 +712,10 @@
std::list<std::pair<OnDestroyNotify, void *>> mNotify;
};
-C2Buffer::C2Buffer(const std::list<C2ConstLinearBlock> &blocks)
+C2Buffer::C2Buffer(const std::vector<C2ConstLinearBlock> &blocks)
: mImpl(new Impl(this, blocks)) {}
-C2Buffer::C2Buffer(const std::list<C2ConstGraphicBlock> &blocks)
+C2Buffer::C2Buffer(const std::vector<C2ConstGraphicBlock> &blocks)
: mImpl(new Impl(this, blocks)) {}
const C2BufferData C2Buffer::data() const { return mImpl->data(); }
@@ -728,8 +728,8 @@
return mImpl->unregisterOnDestroyNotify(onDestroyNotify, arg);
}
-const std::list<std::shared_ptr<const C2Info>> C2Buffer::infos() const {
- return mImpl->infos();
+const std::vector<std::shared_ptr<const C2Info>> C2Buffer::info() const {
+ return mImpl->info();
}
c2_status_t C2Buffer::setInfo(const std::shared_ptr<C2Info> &info) {
diff --git a/media/libstagefright/codec2/vndk/C2Store.cpp b/media/libstagefright/codec2/vndk/C2Store.cpp
index 555e77b..496cbe1 100644
--- a/media/libstagefright/codec2/vndk/C2Store.cpp
+++ b/media/libstagefright/codec2/vndk/C2Store.cpp
@@ -404,6 +404,7 @@
C2PlatformComponentStore::C2PlatformComponentStore() {
// TODO: move this also into a .so so it can be updated
mComponents.emplace("c2.google.avc.decoder", "libstagefright_soft_c2avcdec.so");
+ mComponents.emplace("c2.google.avc.encoder", "libstagefright_soft_c2avcenc.so");
mComponents.emplace("c2.google.aac.decoder", "libstagefright_soft_c2aacdec.so");
mComponents.emplace("c2.google.aac.encoder", "libstagefright_soft_c2aacenc.so");
}
diff --git a/media/libstagefright/codec2/vndk/include/C2AllocatorGralloc.h b/media/libstagefright/codec2/vndk/include/C2AllocatorGralloc.h
index 5311747..56fa317 100644
--- a/media/libstagefright/codec2/vndk/include/C2AllocatorGralloc.h
+++ b/media/libstagefright/codec2/vndk/include/C2AllocatorGralloc.h
@@ -1,4 +1,3 @@
-
/*
* Copyright (C) 2016 The Android Open Source Project
*
@@ -32,7 +31,17 @@
*
* @return a new NON-OWNING native handle that must be deleted using native_handle_delete.
*/
-native_handle_t*UnwrapNativeCodec2GrallocHandle(const C2Handle *const handle);
+native_handle_t *UnwrapNativeCodec2GrallocHandle(const C2Handle *const handle);
+
+/**
+ * Wrap the gralloc handle and metadata into Codec2 handle recognized by
+ * C2AllocatorGralloc.
+ *
+ * @return a new NON-OWNING C2Handle that must be deleted using native_handle_delete.
+ */
+C2Handle *WrapNativeCodec2GrallocHandle(
+ const native_handle_t *const handle,
+ uint32_t width, uint32_t height, uint32_t format, uint64_t usage, uint32_t stride);
class C2AllocatorGralloc : public C2Allocator {
public:
diff --git a/media/libstagefright/codecs/aacdec/C2SoftAac.cpp b/media/libstagefright/codecs/aacdec/C2SoftAac.cpp
index 390f36c..b57c2aa 100644
--- a/media/libstagefright/codecs/aacdec/C2SoftAac.cpp
+++ b/media/libstagefright/codecs/aacdec/C2SoftAac.cpp
@@ -316,9 +316,9 @@
work->worklets.front()->output.buffers.clear();
work->worklets.front()->output.buffers.push_back(buffer);
work->worklets.front()->output.ordinal = work->input.ordinal;
- work->worklets_processed = 1u;
+ work->workletsProcessed = 1u;
};
- if (work && work->input.ordinal.frame_index == outInfo.frameIndex) {
+ if (work && work->input.ordinal.frameIndex == c2_cntr64_t(outInfo.frameIndex)) {
fillWork(work);
} else {
finish(outInfo.frameIndex, fillWork);
@@ -332,7 +332,7 @@
void C2SoftAac::process(
const std::unique_ptr<C2Work> &work,
const std::shared_ptr<C2BlockPool> &pool) {
- work->worklets_processed = 0u;
+ work->workletsProcessed = 0u;
if (mSignalledError) {
return;
}
@@ -346,8 +346,8 @@
size_t offset = 0u;
size_t size = view.capacity();
- bool eos = (work->input.flags & C2BufferPack::FLAG_END_OF_STREAM) != 0;
- bool codecConfig = (work->input.flags & C2BufferPack::FLAG_CODEC_CONFIG) != 0;
+ bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0;
+ bool codecConfig = (work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) != 0;
//TODO
#if 0
@@ -375,14 +375,13 @@
work->worklets.front()->output.ordinal = work->input.ordinal;
work->worklets.front()->output.buffers.clear();
- work->worklets.front()->output.buffers.push_back(nullptr);
return;
}
Info inInfo;
- inInfo.frameIndex = work->input.ordinal.frame_index;
- inInfo.timestamp = work->input.ordinal.timestamp;
+ inInfo.frameIndex = work->input.ordinal.frameIndex.peeku();
+ inInfo.timestamp = work->input.ordinal.timestamp.peeku();
inInfo.bufferSize = size;
inInfo.decodedSizes.clear();
while (size > 0u) {
@@ -602,15 +601,14 @@
auto fillEmptyWork = [](const std::unique_ptr<C2Work> &work) {
work->worklets.front()->output.flags = work->input.flags;
work->worklets.front()->output.buffers.clear();
- work->worklets.front()->output.buffers.emplace_back(nullptr);
work->worklets.front()->output.ordinal = work->input.ordinal;
- work->worklets_processed = 1u;
+ work->workletsProcessed = 1u;
};
while (mBuffersInfo.size() > 1u) {
finish(mBuffersInfo.front().frameIndex, fillEmptyWork);
mBuffersInfo.pop_front();
}
- if (work->worklets_processed == 0u) {
+ if (work->workletsProcessed == 0u) {
fillEmptyWork(work);
}
mBuffersInfo.clear();
diff --git a/media/libstagefright/codecs/aacenc/C2SoftAacEnc.cpp b/media/libstagefright/codecs/aacenc/C2SoftAacEnc.cpp
index 94308c4..6f1b325 100644
--- a/media/libstagefright/codecs/aacenc/C2SoftAacEnc.cpp
+++ b/media/libstagefright/codecs/aacenc/C2SoftAacEnc.cpp
@@ -176,12 +176,12 @@
void C2SoftAacEnc::process(
const std::unique_ptr<C2Work> &work,
const std::shared_ptr<C2BlockPool> &pool) {
- work->worklets_processed = 0u;
+ work->workletsProcessed = 0u;
if (mSignalledError) {
return;
}
- bool eos = (work->input.flags & C2BufferPack::FLAG_END_OF_STREAM) != 0;
+ bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0;
if (!mSentCodecSpecificData) {
// The very first thing we want to output is the codec specific
@@ -215,7 +215,7 @@
#if defined(LOG_NDEBUG) && !LOG_NDEBUG
hexdump(csd->m.value, csd->flexCount());
#endif
- work->worklets.front()->output.infos.push_back(std::move(csd));
+ work->worklets.front()->output.configUpdate.push_back(std::move(csd));
mOutBufferSize = encInfo.maxOutBufBytes;
mNumBytesPerInputFrame = encInfo.frameLength * mNumChannels * sizeof(int16_t);
@@ -225,7 +225,7 @@
}
C2ReadView view = work->input.buffers[0]->data().linearBlocks().front().map().get();
- uint64_t timestamp = mInputTimeUs;
+ uint64_t timestamp = mInputTimeUs.peeku();
size_t numFrames = (view.capacity() + mInputSize + (eos ? mNumBytesPerInputFrame - 1 : 0))
/ mNumBytesPerInputFrame;
@@ -336,21 +336,19 @@
}
work->worklets.front()->output.flags =
- (C2BufferPack::flags_t)(eos ? C2BufferPack::FLAG_END_OF_STREAM : 0);
+ (C2FrameData::flags_t)(eos ? C2FrameData::FLAG_END_OF_STREAM : 0);
work->worklets.front()->output.buffers.clear();
work->worklets.front()->output.ordinal = work->input.ordinal;
work->worklets.front()->output.ordinal.timestamp = timestamp;
- work->worklets_processed = 1u;
+ work->workletsProcessed = 1u;
if (nOutputBytes) {
work->worklets.front()->output.buffers.push_back(
createLinearBuffer(block, 0, nOutputBytes));
- } else {
- work->worklets.front()->output.buffers.emplace_back(nullptr);
}
#if 0
ALOGI("sending %d bytes of data (time = %lld us, flags = 0x%08lx)",
- nOutputBytes, mInputTimeUs, outHeader->nFlags);
+ nOutputBytes, mInputTimeUs.peekll(), outHeader->nFlags);
hexdump(outHeader->pBuffer + outHeader->nOffset, outHeader->nFilledLen);
#endif
diff --git a/media/libstagefright/codecs/aacenc/C2SoftAacEnc.h b/media/libstagefright/codecs/aacenc/C2SoftAacEnc.h
index 947c960..c9f440f 100644
--- a/media/libstagefright/codecs/aacenc/C2SoftAacEnc.h
+++ b/media/libstagefright/codecs/aacenc/C2SoftAacEnc.h
@@ -57,7 +57,7 @@
bool mSentCodecSpecificData;
size_t mInputSize;
- int64_t mInputTimeUs;
+ c2_cntr64_t mInputTimeUs;
bool mSignalledError;
diff --git a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
index 0da9cc7..4f64f6e 100644
--- a/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
+++ b/media/libstagefright/codecs/avcdec/C2SoftAvcDec.cpp
@@ -206,14 +206,13 @@
void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
uint32_t flags = 0;
- if ((work->input.flags & C2BufferPack::FLAG_END_OF_STREAM)) {
- flags |= C2BufferPack::FLAG_END_OF_STREAM;
+ if ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM)) {
+ flags |= C2FrameData::FLAG_END_OF_STREAM;
}
- work->worklets.front()->output.flags = (C2BufferPack::flags_t)flags;
+ work->worklets.front()->output.flags = (C2FrameData::flags_t)flags;
work->worklets.front()->output.buffers.clear();
- work->worklets.front()->output.buffers.emplace_back(nullptr);
work->worklets.front()->output.ordinal = work->input.ordinal;
- work->worklets_processed = 1u;
+ work->workletsProcessed = 1u;
}
} // namespace
@@ -1061,17 +1060,17 @@
std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(std::move(mAllocatedBlock));
auto fillWork = [buffer](const std::unique_ptr<C2Work> &work) {
uint32_t flags = 0;
- if (work->input.flags & C2BufferPack::FLAG_END_OF_STREAM) {
- flags |= C2BufferPack::FLAG_END_OF_STREAM;
+ if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
+ flags |= C2FrameData::FLAG_END_OF_STREAM;
ALOGV("EOS");
}
- work->worklets.front()->output.flags = (C2BufferPack::flags_t)flags;
+ work->worklets.front()->output.flags = (C2FrameData::flags_t)flags;
work->worklets.front()->output.buffers.clear();
work->worklets.front()->output.buffers.push_back(buffer);
work->worklets.front()->output.ordinal = work->input.ordinal;
- work->worklets_processed = 1u;
+ work->workletsProcessed = 1u;
};
- if (work && index == work->input.ordinal.frame_index) {
+ if (work && c2_cntr64_t(index) == work->input.ordinal.frameIndex) {
fillWork(work);
} else {
finish(index, fillWork);
@@ -1084,25 +1083,25 @@
bool eos = false;
work->result = C2_OK;
- work->worklets_processed = 0u;
+ work->workletsProcessed = 0u;
const C2ConstLinearBlock &buffer =
work->input.buffers[0]->data().linearBlocks().front();
if (buffer.capacity() == 0) {
- ALOGV("empty input: %llu", (long long)work->input.ordinal.frame_index);
+ ALOGV("empty input: %llu", work->input.ordinal.frameIndex.peekull());
// TODO: result?
fillEmptyWork(work);
- if ((work->input.flags & C2BufferPack::FLAG_END_OF_STREAM)) {
+ if ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM)) {
eos = true;
}
return;
- } else if (work->input.flags & C2BufferPack::FLAG_END_OF_STREAM) {
- ALOGV("input EOS: %llu", (long long)work->input.ordinal.frame_index);
+ } else if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
+ ALOGV("input EOS: %llu", work->input.ordinal.frameIndex.peekull());
eos = true;
}
C2ReadView input = work->input.buffers[0]->data().linearBlocks().front().map().get();
- uint32_t workIndex = work->input.ordinal.frame_index & 0xFFFFFFFF;
+ uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF;
size_t inOffset = 0u;
while (inOffset < input.capacity()) {
@@ -1266,7 +1265,7 @@
}
if (drainMode == DRAIN_COMPONENT_WITH_EOS
- && work && work->worklets_processed == 0u) {
+ && work && work->workletsProcessed == 0u) {
fillEmptyWork(work);
}
diff --git a/media/libstagefright/codecs/avcenc/Android.bp b/media/libstagefright/codecs/avcenc/Android.bp
index cefe77c..67a2fdc 100644
--- a/media/libstagefright/codecs/avcenc/Android.bp
+++ b/media/libstagefright/codecs/avcenc/Android.bp
@@ -1,4 +1,48 @@
cc_library_shared {
+ name: "libstagefright_soft_c2avcenc",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ static_libs: [ "libavcenc" ],
+ srcs: ["C2SoftAvcEnc.cpp"],
+
+ include_dirs: [
+ "external/libavc/encoder",
+ "external/libavc/common",
+ "frameworks/av/media/libstagefright/include",
+ "frameworks/native/include/media/hardware",
+ ],
+
+ shared_libs: [
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_foundation",
+ "libstagefright_simple_c2component",
+ "libutils",
+ "liblog",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-variable",
+ ],
+ ldflags: ["-Wl,-Bsymbolic"],
+}
+
+cc_library_shared {
name: "libstagefright_soft_avcenc",
vendor_available: true,
vndk: {
diff --git a/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.cpp b/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.cpp
new file mode 100644
index 0000000..7c281e3
--- /dev/null
+++ b/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.cpp
@@ -0,0 +1,1239 @@
+/*
+ * Copyright 2018 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 "C2SoftAvcEncEnc"
+#include <utils/Log.h>
+#include <utils/misc.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MetaData.h>
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include "ih264_typedefs.h"
+#include "ih264e.h"
+#include "ih264e_error.h"
+#include "iv2.h"
+#include "ive2.h"
+#include "C2SoftAvcEnc.h"
+
+namespace android {
+
+#define ive_api_function ih264e_api_function
+
+namespace {
+
+// From external/libavc/encoder/ih264e_bitstream.h
+constexpr uint32_t MIN_STREAM_SIZE = 0x800;
+
+static size_t GetCPUCoreCount() {
+ long cpuCoreCount = 1;
+#if defined(_SC_NPROCESSORS_ONLN)
+ cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
+#else
+ // _SC_NPROC_ONLN must be defined...
+ cpuCoreCount = sysconf(_SC_NPROC_ONLN);
+#endif
+ CHECK(cpuCoreCount >= 1);
+ ALOGV("Number of CPU cores: %ld", cpuCoreCount);
+ return (size_t)cpuCoreCount;
+}
+
+void ConvertRGBToPlanarYUV(
+ uint8_t *dstY, size_t dstStride, size_t dstVStride,
+ const C2GraphicView &src) {
+ CHECK((src.width() & 1) == 0);
+ CHECK((src.height() & 1) == 0);
+
+ uint8_t *dstU = dstY + dstStride * dstVStride;
+ uint8_t *dstV = dstU + (dstStride >> 1) * (dstVStride >> 1);
+
+ const C2PlanarLayout &layout = src.layout();
+ const uint8_t *pRed = src.data()[C2PlanarLayout::PLANE_R];
+ const uint8_t *pGreen = src.data()[C2PlanarLayout::PLANE_G];
+ const uint8_t *pBlue = src.data()[C2PlanarLayout::PLANE_B];
+
+ for (size_t y = 0; y < src.height(); ++y) {
+ for (size_t x = 0; x < src.width(); ++x) {
+ unsigned red = *pRed;
+ unsigned green = *pGreen;
+ unsigned blue = *pBlue;
+
+ // using ITU-R BT.601 conversion matrix
+ unsigned luma =
+ ((red * 66 + green * 129 + blue * 25) >> 8) + 16;
+
+ dstY[x] = luma;
+
+ if ((x & 1) == 0 && (y & 1) == 0) {
+ unsigned U =
+ ((-red * 38 - green * 74 + blue * 112) >> 8) + 128;
+
+ unsigned V =
+ ((red * 112 - green * 94 - blue * 18) >> 8) + 128;
+
+ dstU[x >> 1] = U;
+ dstV[x >> 1] = V;
+ }
+ pRed += layout.planes[C2PlanarLayout::PLANE_R].colInc;
+ pGreen += layout.planes[C2PlanarLayout::PLANE_G].colInc;
+ pBlue += layout.planes[C2PlanarLayout::PLANE_B].colInc;
+ }
+
+ if ((y & 1) == 0) {
+ dstU += dstStride >> 1;
+ dstV += dstStride >> 1;
+ }
+
+ pRed -= layout.planes[C2PlanarLayout::PLANE_R].colInc * src.width();
+ pGreen -= layout.planes[C2PlanarLayout::PLANE_G].colInc * src.width();
+ pBlue -= layout.planes[C2PlanarLayout::PLANE_B].colInc * src.width();
+ pRed += layout.planes[C2PlanarLayout::PLANE_R].rowInc;
+ pGreen += layout.planes[C2PlanarLayout::PLANE_G].rowInc;
+ pBlue += layout.planes[C2PlanarLayout::PLANE_B].rowInc;
+
+ dstY += dstStride;
+ }
+}
+
+} // namespace
+
+C2SoftAvcEnc::C2SoftAvcEnc(const char *name, c2_node_id_t id)
+ : SimpleC2Component(
+ SimpleC2Interface::Builder(name, id)
+ .inputFormat(C2FormatVideo)
+ .outputFormat(C2FormatCompressed)
+ .build()),
+ mUpdateFlag(0),
+ mIvVideoColorFormat(IV_YUV_420P),
+ mAVCEncProfile(IV_PROFILE_BASE),
+ mAVCEncLevel(41),
+ mStarted(false),
+ mSawInputEOS(false),
+ mSawOutputEOS(false),
+ mSignalledError(false),
+ mCodecCtx(NULL),
+ mWidth(1080),
+ mHeight(1920),
+ mFramerate(60),
+ mBitrate(20000),
+ // TODO: output buffer size
+ mOutBufferSize(524288) {
+
+ // If dump is enabled, then open create an empty file
+ GENERATE_FILE_NAMES();
+ CREATE_DUMP_FILE(mInFile);
+ CREATE_DUMP_FILE(mOutFile);
+
+ initEncParams();
+}
+
+C2SoftAvcEnc::~C2SoftAvcEnc() {
+ releaseEncoder();
+}
+
+c2_status_t C2SoftAvcEnc::onInit() {
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::onStop() {
+ return C2_OK;
+}
+
+void C2SoftAvcEnc::onReset() {
+ // TODO: use IVE_CMD_CTL_RESET?
+ releaseEncoder();
+ initEncParams();
+}
+
+void C2SoftAvcEnc::onRelease() {
+ releaseEncoder();
+}
+
+c2_status_t C2SoftAvcEnc::onFlush_sm() {
+ // TODO: use IVE_CMD_CTL_FLUSH?
+ return C2_OK;
+}
+
+void C2SoftAvcEnc::initEncParams() {
+ mCodecCtx = NULL;
+ mMemRecords = NULL;
+ mNumMemRecords = DEFAULT_MEM_REC_CNT;
+ mHeaderGenerated = 0;
+ mNumCores = GetCPUCoreCount();
+ mArch = DEFAULT_ARCH;
+ mSliceMode = DEFAULT_SLICE_MODE;
+ mSliceParam = DEFAULT_SLICE_PARAM;
+ mHalfPelEnable = DEFAULT_HPEL;
+ mIInterval = DEFAULT_I_INTERVAL;
+ mIDRInterval = DEFAULT_IDR_INTERVAL;
+ mDisableDeblkLevel = DEFAULT_DISABLE_DEBLK_LEVEL;
+ mEnableFastSad = DEFAULT_ENABLE_FAST_SAD;
+ mEnableAltRef = DEFAULT_ENABLE_ALT_REF;
+ mEncSpeed = DEFAULT_ENC_SPEED;
+ mIntra4x4 = DEFAULT_INTRA4x4;
+ mConstrainedIntraFlag = DEFAULT_CONSTRAINED_INTRA;
+ mAIRMode = DEFAULT_AIR;
+ mAIRRefreshPeriod = DEFAULT_AIR_REFRESH_PERIOD;
+ mPSNREnable = DEFAULT_PSNR_ENABLE;
+ mReconEnable = DEFAULT_RECON_ENABLE;
+ mEntropyMode = DEFAULT_ENTROPY_MODE;
+ mBframes = DEFAULT_B_FRAMES;
+
+ gettimeofday(&mTimeStart, NULL);
+ gettimeofday(&mTimeEnd, NULL);
+}
+
+c2_status_t C2SoftAvcEnc::setDimensions() {
+ ive_ctl_set_dimensions_ip_t s_dimensions_ip;
+ ive_ctl_set_dimensions_op_t s_dimensions_op;
+ IV_STATUS_T status;
+
+ s_dimensions_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_dimensions_ip.e_sub_cmd = IVE_CMD_CTL_SET_DIMENSIONS;
+ s_dimensions_ip.u4_ht = mHeight;
+ s_dimensions_ip.u4_wd = mWidth;
+
+ s_dimensions_ip.u4_timestamp_high = -1;
+ s_dimensions_ip.u4_timestamp_low = -1;
+
+ s_dimensions_ip.u4_size = sizeof(ive_ctl_set_dimensions_ip_t);
+ s_dimensions_op.u4_size = sizeof(ive_ctl_set_dimensions_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_dimensions_ip, &s_dimensions_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set frame dimensions = 0x%x\n",
+ s_dimensions_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setNumCores() {
+ IV_STATUS_T status;
+ ive_ctl_set_num_cores_ip_t s_num_cores_ip;
+ ive_ctl_set_num_cores_op_t s_num_cores_op;
+ s_num_cores_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_num_cores_ip.e_sub_cmd = IVE_CMD_CTL_SET_NUM_CORES;
+ s_num_cores_ip.u4_num_cores = MIN(mNumCores, CODEC_MAX_CORES);
+ s_num_cores_ip.u4_timestamp_high = -1;
+ s_num_cores_ip.u4_timestamp_low = -1;
+ s_num_cores_ip.u4_size = sizeof(ive_ctl_set_num_cores_ip_t);
+
+ s_num_cores_op.u4_size = sizeof(ive_ctl_set_num_cores_op_t);
+
+ status = ive_api_function(
+ mCodecCtx, (void *) &s_num_cores_ip, (void *) &s_num_cores_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set processor params = 0x%x\n",
+ s_num_cores_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setFrameRate() {
+ ive_ctl_set_frame_rate_ip_t s_frame_rate_ip;
+ ive_ctl_set_frame_rate_op_t s_frame_rate_op;
+ IV_STATUS_T status;
+
+ s_frame_rate_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_frame_rate_ip.e_sub_cmd = IVE_CMD_CTL_SET_FRAMERATE;
+
+ s_frame_rate_ip.u4_src_frame_rate = mFramerate;
+ s_frame_rate_ip.u4_tgt_frame_rate = mFramerate;
+
+ s_frame_rate_ip.u4_timestamp_high = -1;
+ s_frame_rate_ip.u4_timestamp_low = -1;
+
+ s_frame_rate_ip.u4_size = sizeof(ive_ctl_set_frame_rate_ip_t);
+ s_frame_rate_op.u4_size = sizeof(ive_ctl_set_frame_rate_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_frame_rate_ip, &s_frame_rate_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set frame rate = 0x%x\n",
+ s_frame_rate_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setIpeParams() {
+ ive_ctl_set_ipe_params_ip_t s_ipe_params_ip;
+ ive_ctl_set_ipe_params_op_t s_ipe_params_op;
+ IV_STATUS_T status;
+
+ s_ipe_params_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_ipe_params_ip.e_sub_cmd = IVE_CMD_CTL_SET_IPE_PARAMS;
+
+ s_ipe_params_ip.u4_enable_intra_4x4 = mIntra4x4;
+ s_ipe_params_ip.u4_enc_speed_preset = mEncSpeed;
+ s_ipe_params_ip.u4_constrained_intra_pred = mConstrainedIntraFlag;
+
+ s_ipe_params_ip.u4_timestamp_high = -1;
+ s_ipe_params_ip.u4_timestamp_low = -1;
+
+ s_ipe_params_ip.u4_size = sizeof(ive_ctl_set_ipe_params_ip_t);
+ s_ipe_params_op.u4_size = sizeof(ive_ctl_set_ipe_params_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_ipe_params_ip, &s_ipe_params_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set ipe params = 0x%x\n",
+ s_ipe_params_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setBitRate() {
+ ive_ctl_set_bitrate_ip_t s_bitrate_ip;
+ ive_ctl_set_bitrate_op_t s_bitrate_op;
+ IV_STATUS_T status;
+
+ s_bitrate_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_bitrate_ip.e_sub_cmd = IVE_CMD_CTL_SET_BITRATE;
+
+ s_bitrate_ip.u4_target_bitrate = mBitrate;
+
+ s_bitrate_ip.u4_timestamp_high = -1;
+ s_bitrate_ip.u4_timestamp_low = -1;
+
+ s_bitrate_ip.u4_size = sizeof(ive_ctl_set_bitrate_ip_t);
+ s_bitrate_op.u4_size = sizeof(ive_ctl_set_bitrate_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_bitrate_ip, &s_bitrate_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set bit rate = 0x%x\n", s_bitrate_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setFrameType(IV_PICTURE_CODING_TYPE_T e_frame_type) {
+ ive_ctl_set_frame_type_ip_t s_frame_type_ip;
+ ive_ctl_set_frame_type_op_t s_frame_type_op;
+ IV_STATUS_T status;
+ s_frame_type_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_frame_type_ip.e_sub_cmd = IVE_CMD_CTL_SET_FRAMETYPE;
+
+ s_frame_type_ip.e_frame_type = e_frame_type;
+
+ s_frame_type_ip.u4_timestamp_high = -1;
+ s_frame_type_ip.u4_timestamp_low = -1;
+
+ s_frame_type_ip.u4_size = sizeof(ive_ctl_set_frame_type_ip_t);
+ s_frame_type_op.u4_size = sizeof(ive_ctl_set_frame_type_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_frame_type_ip, &s_frame_type_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set frame type = 0x%x\n",
+ s_frame_type_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setQp() {
+ ive_ctl_set_qp_ip_t s_qp_ip;
+ ive_ctl_set_qp_op_t s_qp_op;
+ IV_STATUS_T status;
+
+ s_qp_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_qp_ip.e_sub_cmd = IVE_CMD_CTL_SET_QP;
+
+ s_qp_ip.u4_i_qp = DEFAULT_I_QP;
+ s_qp_ip.u4_i_qp_max = DEFAULT_QP_MAX;
+ s_qp_ip.u4_i_qp_min = DEFAULT_QP_MIN;
+
+ s_qp_ip.u4_p_qp = DEFAULT_P_QP;
+ s_qp_ip.u4_p_qp_max = DEFAULT_QP_MAX;
+ s_qp_ip.u4_p_qp_min = DEFAULT_QP_MIN;
+
+ s_qp_ip.u4_b_qp = DEFAULT_P_QP;
+ s_qp_ip.u4_b_qp_max = DEFAULT_QP_MAX;
+ s_qp_ip.u4_b_qp_min = DEFAULT_QP_MIN;
+
+ s_qp_ip.u4_timestamp_high = -1;
+ s_qp_ip.u4_timestamp_low = -1;
+
+ s_qp_ip.u4_size = sizeof(ive_ctl_set_qp_ip_t);
+ s_qp_op.u4_size = sizeof(ive_ctl_set_qp_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_qp_ip, &s_qp_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set qp 0x%x\n", s_qp_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setEncMode(IVE_ENC_MODE_T e_enc_mode) {
+ IV_STATUS_T status;
+ ive_ctl_set_enc_mode_ip_t s_enc_mode_ip;
+ ive_ctl_set_enc_mode_op_t s_enc_mode_op;
+
+ s_enc_mode_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_enc_mode_ip.e_sub_cmd = IVE_CMD_CTL_SET_ENC_MODE;
+
+ s_enc_mode_ip.e_enc_mode = e_enc_mode;
+
+ s_enc_mode_ip.u4_timestamp_high = -1;
+ s_enc_mode_ip.u4_timestamp_low = -1;
+
+ s_enc_mode_ip.u4_size = sizeof(ive_ctl_set_enc_mode_ip_t);
+ s_enc_mode_op.u4_size = sizeof(ive_ctl_set_enc_mode_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_enc_mode_ip, &s_enc_mode_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set in header encode mode = 0x%x\n",
+ s_enc_mode_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setVbvParams() {
+ ive_ctl_set_vbv_params_ip_t s_vbv_ip;
+ ive_ctl_set_vbv_params_op_t s_vbv_op;
+ IV_STATUS_T status;
+
+ s_vbv_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_vbv_ip.e_sub_cmd = IVE_CMD_CTL_SET_VBV_PARAMS;
+
+ s_vbv_ip.u4_vbv_buf_size = 0;
+ s_vbv_ip.u4_vbv_buffer_delay = 1000;
+
+ s_vbv_ip.u4_timestamp_high = -1;
+ s_vbv_ip.u4_timestamp_low = -1;
+
+ s_vbv_ip.u4_size = sizeof(ive_ctl_set_vbv_params_ip_t);
+ s_vbv_op.u4_size = sizeof(ive_ctl_set_vbv_params_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_vbv_ip, &s_vbv_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set VBV params = 0x%x\n", s_vbv_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setAirParams() {
+ ive_ctl_set_air_params_ip_t s_air_ip;
+ ive_ctl_set_air_params_op_t s_air_op;
+ IV_STATUS_T status;
+
+ s_air_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_air_ip.e_sub_cmd = IVE_CMD_CTL_SET_AIR_PARAMS;
+
+ s_air_ip.e_air_mode = mAIRMode;
+ s_air_ip.u4_air_refresh_period = mAIRRefreshPeriod;
+
+ s_air_ip.u4_timestamp_high = -1;
+ s_air_ip.u4_timestamp_low = -1;
+
+ s_air_ip.u4_size = sizeof(ive_ctl_set_air_params_ip_t);
+ s_air_op.u4_size = sizeof(ive_ctl_set_air_params_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_air_ip, &s_air_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set air params = 0x%x\n", s_air_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setMeParams() {
+ IV_STATUS_T status;
+ ive_ctl_set_me_params_ip_t s_me_params_ip;
+ ive_ctl_set_me_params_op_t s_me_params_op;
+
+ s_me_params_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_me_params_ip.e_sub_cmd = IVE_CMD_CTL_SET_ME_PARAMS;
+
+ s_me_params_ip.u4_enable_fast_sad = mEnableFastSad;
+ s_me_params_ip.u4_enable_alt_ref = mEnableAltRef;
+
+ s_me_params_ip.u4_enable_hpel = mHalfPelEnable;
+ s_me_params_ip.u4_enable_qpel = DEFAULT_QPEL;
+ s_me_params_ip.u4_me_speed_preset = DEFAULT_ME_SPEED;
+ s_me_params_ip.u4_srch_rng_x = DEFAULT_SRCH_RNG_X;
+ s_me_params_ip.u4_srch_rng_y = DEFAULT_SRCH_RNG_Y;
+
+ s_me_params_ip.u4_timestamp_high = -1;
+ s_me_params_ip.u4_timestamp_low = -1;
+
+ s_me_params_ip.u4_size = sizeof(ive_ctl_set_me_params_ip_t);
+ s_me_params_op.u4_size = sizeof(ive_ctl_set_me_params_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_me_params_ip, &s_me_params_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set me params = 0x%x\n", s_me_params_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setGopParams() {
+ IV_STATUS_T status;
+ ive_ctl_set_gop_params_ip_t s_gop_params_ip;
+ ive_ctl_set_gop_params_op_t s_gop_params_op;
+
+ s_gop_params_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_gop_params_ip.e_sub_cmd = IVE_CMD_CTL_SET_GOP_PARAMS;
+
+ s_gop_params_ip.u4_i_frm_interval = mIInterval;
+ s_gop_params_ip.u4_idr_frm_interval = mIDRInterval;
+
+ s_gop_params_ip.u4_timestamp_high = -1;
+ s_gop_params_ip.u4_timestamp_low = -1;
+
+ s_gop_params_ip.u4_size = sizeof(ive_ctl_set_gop_params_ip_t);
+ s_gop_params_op.u4_size = sizeof(ive_ctl_set_gop_params_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_gop_params_ip, &s_gop_params_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set GOP params = 0x%x\n",
+ s_gop_params_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setProfileParams() {
+ IV_STATUS_T status;
+ ive_ctl_set_profile_params_ip_t s_profile_params_ip;
+ ive_ctl_set_profile_params_op_t s_profile_params_op;
+
+ s_profile_params_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_profile_params_ip.e_sub_cmd = IVE_CMD_CTL_SET_PROFILE_PARAMS;
+
+ s_profile_params_ip.e_profile = DEFAULT_EPROFILE;
+ s_profile_params_ip.u4_entropy_coding_mode = mEntropyMode;
+ s_profile_params_ip.u4_timestamp_high = -1;
+ s_profile_params_ip.u4_timestamp_low = -1;
+
+ s_profile_params_ip.u4_size = sizeof(ive_ctl_set_profile_params_ip_t);
+ s_profile_params_op.u4_size = sizeof(ive_ctl_set_profile_params_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_profile_params_ip, &s_profile_params_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to set profile params = 0x%x\n",
+ s_profile_params_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setDeblockParams() {
+ IV_STATUS_T status;
+ ive_ctl_set_deblock_params_ip_t s_deblock_params_ip;
+ ive_ctl_set_deblock_params_op_t s_deblock_params_op;
+
+ s_deblock_params_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_deblock_params_ip.e_sub_cmd = IVE_CMD_CTL_SET_DEBLOCK_PARAMS;
+
+ s_deblock_params_ip.u4_disable_deblock_level = mDisableDeblkLevel;
+
+ s_deblock_params_ip.u4_timestamp_high = -1;
+ s_deblock_params_ip.u4_timestamp_low = -1;
+
+ s_deblock_params_ip.u4_size = sizeof(ive_ctl_set_deblock_params_ip_t);
+ s_deblock_params_op.u4_size = sizeof(ive_ctl_set_deblock_params_op_t);
+
+ status = ive_api_function(mCodecCtx, &s_deblock_params_ip, &s_deblock_params_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to enable/disable deblock params = 0x%x\n",
+ s_deblock_params_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+void C2SoftAvcEnc::logVersion() {
+ ive_ctl_getversioninfo_ip_t s_ctl_ip;
+ ive_ctl_getversioninfo_op_t s_ctl_op;
+ UWORD8 au1_buf[512];
+ IV_STATUS_T status;
+
+ s_ctl_ip.e_cmd = IVE_CMD_VIDEO_CTL;
+ s_ctl_ip.e_sub_cmd = IVE_CMD_CTL_GETVERSION;
+ s_ctl_ip.u4_size = sizeof(ive_ctl_getversioninfo_ip_t);
+ s_ctl_op.u4_size = sizeof(ive_ctl_getversioninfo_op_t);
+ s_ctl_ip.pu1_version = au1_buf;
+ s_ctl_ip.u4_version_bufsize = sizeof(au1_buf);
+
+ status = ive_api_function(mCodecCtx, (void *) &s_ctl_ip, (void *) &s_ctl_op);
+
+ if (status != IV_SUCCESS) {
+ ALOGE("Error in getting version: 0x%x", s_ctl_op.u4_error_code);
+ } else {
+ ALOGV("Ittiam encoder version: %s", (char *)s_ctl_ip.pu1_version);
+ }
+ return;
+}
+
+c2_status_t C2SoftAvcEnc::initEncoder() {
+ IV_STATUS_T status;
+ WORD32 level;
+ uint32_t displaySizeY;
+
+ CHECK(!mStarted);
+
+ c2_status_t errType = C2_OK;
+
+ displaySizeY = mWidth * mHeight;
+ if (displaySizeY > (1920 * 1088)) {
+ level = 50;
+ } else if (displaySizeY > (1280 * 720)) {
+ level = 40;
+ } else if (displaySizeY > (720 * 576)) {
+ level = 31;
+ } else if (displaySizeY > (624 * 320)) {
+ level = 30;
+ } else if (displaySizeY > (352 * 288)) {
+ level = 21;
+ } else if (displaySizeY > (176 * 144)) {
+ level = 20;
+ } else {
+ level = 10;
+ }
+ mAVCEncLevel = MAX(level, mAVCEncLevel);
+
+ mStride = mWidth;
+
+ // TODO
+ mIvVideoColorFormat = IV_YUV_420P;
+
+ ALOGD("Params width %d height %d level %d colorFormat %d", mWidth,
+ mHeight, mAVCEncLevel, mIvVideoColorFormat);
+
+ /* Getting Number of MemRecords */
+ {
+ iv_num_mem_rec_ip_t s_num_mem_rec_ip;
+ iv_num_mem_rec_op_t s_num_mem_rec_op;
+
+ s_num_mem_rec_ip.u4_size = sizeof(iv_num_mem_rec_ip_t);
+ s_num_mem_rec_op.u4_size = sizeof(iv_num_mem_rec_op_t);
+
+ s_num_mem_rec_ip.e_cmd = IV_CMD_GET_NUM_MEM_REC;
+
+ status = ive_api_function(0, &s_num_mem_rec_ip, &s_num_mem_rec_op);
+
+ if (status != IV_SUCCESS) {
+ ALOGE("Get number of memory records failed = 0x%x\n",
+ s_num_mem_rec_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+
+ mNumMemRecords = s_num_mem_rec_op.u4_num_mem_rec;
+ }
+
+ /* Allocate array to hold memory records */
+ if (mNumMemRecords > SIZE_MAX / sizeof(iv_mem_rec_t)) {
+ ALOGE("requested memory size is too big.");
+ return C2_CORRUPTED;
+ }
+ mMemRecords = (iv_mem_rec_t *)malloc(mNumMemRecords * sizeof(iv_mem_rec_t));
+ if (NULL == mMemRecords) {
+ ALOGE("Unable to allocate memory for hold memory records: Size %zu",
+ mNumMemRecords * sizeof(iv_mem_rec_t));
+ mSignalledError = true;
+ // TODO: notify(error, C2_CORRUPTED, 0, 0);
+ return C2_CORRUPTED;
+ }
+
+ {
+ iv_mem_rec_t *ps_mem_rec;
+ ps_mem_rec = mMemRecords;
+ for (size_t i = 0; i < mNumMemRecords; i++) {
+ ps_mem_rec->u4_size = sizeof(iv_mem_rec_t);
+ ps_mem_rec->pv_base = NULL;
+ ps_mem_rec->u4_mem_size = 0;
+ ps_mem_rec->u4_mem_alignment = 0;
+ ps_mem_rec->e_mem_type = IV_NA_MEM_TYPE;
+
+ ps_mem_rec++;
+ }
+ }
+
+ /* Getting MemRecords Attributes */
+ {
+ iv_fill_mem_rec_ip_t s_fill_mem_rec_ip;
+ iv_fill_mem_rec_op_t s_fill_mem_rec_op;
+
+ s_fill_mem_rec_ip.u4_size = sizeof(iv_fill_mem_rec_ip_t);
+ s_fill_mem_rec_op.u4_size = sizeof(iv_fill_mem_rec_op_t);
+
+ s_fill_mem_rec_ip.e_cmd = IV_CMD_FILL_NUM_MEM_REC;
+ s_fill_mem_rec_ip.ps_mem_rec = mMemRecords;
+ s_fill_mem_rec_ip.u4_num_mem_rec = mNumMemRecords;
+ s_fill_mem_rec_ip.u4_max_wd = mWidth;
+ s_fill_mem_rec_ip.u4_max_ht = mHeight;
+ s_fill_mem_rec_ip.u4_max_level = mAVCEncLevel;
+ s_fill_mem_rec_ip.e_color_format = DEFAULT_INP_COLOR_FORMAT;
+ s_fill_mem_rec_ip.u4_max_ref_cnt = DEFAULT_MAX_REF_FRM;
+ s_fill_mem_rec_ip.u4_max_reorder_cnt = DEFAULT_MAX_REORDER_FRM;
+ s_fill_mem_rec_ip.u4_max_srch_rng_x = DEFAULT_MAX_SRCH_RANGE_X;
+ s_fill_mem_rec_ip.u4_max_srch_rng_y = DEFAULT_MAX_SRCH_RANGE_Y;
+
+ status = ive_api_function(0, &s_fill_mem_rec_ip, &s_fill_mem_rec_op);
+
+ if (status != IV_SUCCESS) {
+ ALOGE("Fill memory records failed = 0x%x\n",
+ s_fill_mem_rec_op.u4_error_code);
+ mSignalledError = true;
+ // TODO: notify(error, C2_CORRUPTED, 0, 0);
+ return C2_CORRUPTED;
+ }
+ }
+
+ /* Allocating Memory for Mem Records */
+ {
+ WORD32 total_size;
+ iv_mem_rec_t *ps_mem_rec;
+ total_size = 0;
+ ps_mem_rec = mMemRecords;
+
+ for (size_t i = 0; i < mNumMemRecords; i++) {
+ ps_mem_rec->pv_base = ive_aligned_malloc(
+ ps_mem_rec->u4_mem_alignment, ps_mem_rec->u4_mem_size);
+ if (ps_mem_rec->pv_base == NULL) {
+ ALOGE("Allocation failure for mem record id %zu size %u\n", i,
+ ps_mem_rec->u4_mem_size);
+ mSignalledError = true;
+ // TODO: notify(error, C2_CORRUPTED, 0, 0);
+ return C2_CORRUPTED;
+
+ }
+ total_size += ps_mem_rec->u4_mem_size;
+
+ ps_mem_rec++;
+ }
+ }
+
+ /* Codec Instance Creation */
+ {
+ ive_init_ip_t s_init_ip;
+ ive_init_op_t s_init_op;
+
+ mCodecCtx = (iv_obj_t *)mMemRecords[0].pv_base;
+ mCodecCtx->u4_size = sizeof(iv_obj_t);
+ mCodecCtx->pv_fxns = (void *)ive_api_function;
+
+ s_init_ip.u4_size = sizeof(ive_init_ip_t);
+ s_init_op.u4_size = sizeof(ive_init_op_t);
+
+ s_init_ip.e_cmd = IV_CMD_INIT;
+ s_init_ip.u4_num_mem_rec = mNumMemRecords;
+ s_init_ip.ps_mem_rec = mMemRecords;
+ s_init_ip.u4_max_wd = mWidth;
+ s_init_ip.u4_max_ht = mHeight;
+ s_init_ip.u4_max_ref_cnt = DEFAULT_MAX_REF_FRM;
+ s_init_ip.u4_max_reorder_cnt = DEFAULT_MAX_REORDER_FRM;
+ s_init_ip.u4_max_level = mAVCEncLevel;
+ s_init_ip.e_inp_color_fmt = mIvVideoColorFormat;
+
+ if (mReconEnable || mPSNREnable) {
+ s_init_ip.u4_enable_recon = 1;
+ } else {
+ s_init_ip.u4_enable_recon = 0;
+ }
+ s_init_ip.e_recon_color_fmt = DEFAULT_RECON_COLOR_FORMAT;
+ s_init_ip.e_rc_mode = DEFAULT_RC_MODE;
+ s_init_ip.u4_max_framerate = DEFAULT_MAX_FRAMERATE;
+ s_init_ip.u4_max_bitrate = DEFAULT_MAX_BITRATE;
+ s_init_ip.u4_num_bframes = mBframes;
+ s_init_ip.e_content_type = IV_PROGRESSIVE;
+ s_init_ip.u4_max_srch_rng_x = DEFAULT_MAX_SRCH_RANGE_X;
+ s_init_ip.u4_max_srch_rng_y = DEFAULT_MAX_SRCH_RANGE_Y;
+ s_init_ip.e_slice_mode = mSliceMode;
+ s_init_ip.u4_slice_param = mSliceParam;
+ s_init_ip.e_arch = mArch;
+ s_init_ip.e_soc = DEFAULT_SOC;
+
+ status = ive_api_function(mCodecCtx, &s_init_ip, &s_init_op);
+
+ if (status != IV_SUCCESS) {
+ ALOGE("Init encoder failed = 0x%x\n", s_init_op.u4_error_code);
+ mSignalledError = true;
+ // TODO: notify(error, C2_CORRUPTED, 0 /* arg2 */, NULL /* data */);
+ return C2_CORRUPTED;
+ }
+ }
+
+ /* Get Codec Version */
+ logVersion();
+
+ /* set processor details */
+ setNumCores();
+
+ /* Video control Set Frame dimensions */
+ setDimensions();
+
+ /* Video control Set Frame rates */
+ setFrameRate();
+
+ /* Video control Set IPE Params */
+ setIpeParams();
+
+ /* Video control Set Bitrate */
+ setBitRate();
+
+ /* Video control Set QP */
+ setQp();
+
+ /* Video control Set AIR params */
+ setAirParams();
+
+ /* Video control Set VBV params */
+ setVbvParams();
+
+ /* Video control Set Motion estimation params */
+ setMeParams();
+
+ /* Video control Set GOP params */
+ setGopParams();
+
+ /* Video control Set Deblock params */
+ setDeblockParams();
+
+ /* Video control Set Profile params */
+ setProfileParams();
+
+ /* Video control Set in Encode header mode */
+ setEncMode(IVE_ENC_MODE_HEADER);
+
+ ALOGV("init_codec successfull");
+
+ mSpsPpsHeaderReceived = false;
+ mStarted = true;
+
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::releaseEncoder() {
+ IV_STATUS_T status = IV_SUCCESS;
+ iv_retrieve_mem_rec_ip_t s_retrieve_mem_ip;
+ iv_retrieve_mem_rec_op_t s_retrieve_mem_op;
+ iv_mem_rec_t *ps_mem_rec;
+
+ if (!mStarted) {
+ return C2_OK;
+ }
+
+ s_retrieve_mem_ip.u4_size = sizeof(iv_retrieve_mem_rec_ip_t);
+ s_retrieve_mem_op.u4_size = sizeof(iv_retrieve_mem_rec_op_t);
+ s_retrieve_mem_ip.e_cmd = IV_CMD_RETRIEVE_MEMREC;
+ s_retrieve_mem_ip.ps_mem_rec = mMemRecords;
+
+ status = ive_api_function(mCodecCtx, &s_retrieve_mem_ip, &s_retrieve_mem_op);
+
+ if (status != IV_SUCCESS) {
+ ALOGE("Unable to retrieve memory records = 0x%x\n",
+ s_retrieve_mem_op.u4_error_code);
+ return C2_CORRUPTED;
+ }
+
+ /* Free memory records */
+ ps_mem_rec = mMemRecords;
+ for (size_t i = 0; i < s_retrieve_mem_op.u4_num_mem_rec_filled; i++) {
+ ive_aligned_free(ps_mem_rec->pv_base);
+ ps_mem_rec++;
+ }
+
+ free(mMemRecords);
+
+ // clear other pointers into the space being free()d
+ mCodecCtx = NULL;
+
+ mStarted = false;
+
+ return C2_OK;
+}
+
+c2_status_t C2SoftAvcEnc::setEncodeArgs(
+ ive_video_encode_ip_t *ps_encode_ip,
+ ive_video_encode_op_t *ps_encode_op,
+ const C2GraphicView *const input,
+ uint8_t *base,
+ uint32_t capacity,
+ uint64_t timestamp) {
+ iv_raw_buf_t *ps_inp_raw_buf;
+
+ ps_inp_raw_buf = &ps_encode_ip->s_inp_buf;
+ ps_encode_ip->s_out_buf.pv_buf = base;
+ ps_encode_ip->s_out_buf.u4_bytes = 0;
+ ps_encode_ip->s_out_buf.u4_bufsize = capacity;
+ ps_encode_ip->u4_size = sizeof(ive_video_encode_ip_t);
+ ps_encode_op->u4_size = sizeof(ive_video_encode_op_t);
+
+ ps_encode_ip->e_cmd = IVE_CMD_VIDEO_ENCODE;
+ ps_encode_ip->pv_bufs = NULL;
+ ps_encode_ip->pv_mb_info = NULL;
+ ps_encode_ip->pv_pic_info = NULL;
+ ps_encode_ip->u4_mb_info_type = 0;
+ ps_encode_ip->u4_pic_info_type = 0;
+ ps_encode_ip->u4_is_last = 0;
+ ps_encode_ip->u4_timestamp_high = timestamp >> 32;
+ ps_encode_ip->u4_timestamp_low = timestamp & 0xFFFFFFFF;
+ ps_encode_op->s_out_buf.pv_buf = NULL;
+
+ /* Initialize color formats */
+ memset(ps_inp_raw_buf, 0, sizeof(iv_raw_buf_t));
+ ps_inp_raw_buf->u4_size = sizeof(iv_raw_buf_t);
+ ps_inp_raw_buf->e_color_fmt = mIvVideoColorFormat;
+ if (input == nullptr) {
+ if (mSawInputEOS){
+ ps_encode_ip->u4_is_last = 1;
+ }
+ return C2_OK;
+ }
+
+ ALOGV("width = %d, height = %d", input->width(), input->height());
+ const C2PlanarLayout &layout = input->layout();
+ uint8_t *yPlane = const_cast<uint8_t *>(input->data()[C2PlanarLayout::PLANE_Y]);
+ uint8_t *uPlane = const_cast<uint8_t *>(input->data()[C2PlanarLayout::PLANE_U]);
+ uint8_t *vPlane = const_cast<uint8_t *>(input->data()[C2PlanarLayout::PLANE_V]);
+ int32_t yStride = layout.planes[C2PlanarLayout::PLANE_Y].rowInc;
+ int32_t uStride = layout.planes[C2PlanarLayout::PLANE_U].rowInc;
+ int32_t vStride = layout.planes[C2PlanarLayout::PLANE_V].rowInc;
+
+ switch (layout.type) {
+ case C2PlanarLayout::TYPE_RGB:
+ // fall-through
+ case C2PlanarLayout::TYPE_RGBA: {
+ size_t yPlaneSize = input->width() * input->height();
+ std::unique_ptr<uint8_t[]> freeBuffer;
+ if (mFreeConversionBuffers.empty()) {
+ freeBuffer.reset(new uint8_t[yPlaneSize * 3 / 2]);
+ } else {
+ freeBuffer.swap(mFreeConversionBuffers.front());
+ mFreeConversionBuffers.pop_front();
+ }
+ yPlane = freeBuffer.get();
+ mConversionBuffersInUse.push_back(std::move(freeBuffer));
+ uPlane = yPlane + yPlaneSize;
+ vPlane = uPlane + yPlaneSize / 4;
+ yStride = input->width();
+ uStride = vStride = input->width() / 2;
+ ConvertRGBToPlanarYUV(yPlane, yStride, input->height(), *input);
+ break;
+ }
+ case C2PlanarLayout::TYPE_YUV:
+ // fall-through
+ case C2PlanarLayout::TYPE_YUVA:
+ // Do nothing
+ break;
+ default:
+ ALOGE("Unrecognized plane type: %d", layout.type);
+ return C2_BAD_VALUE;
+ }
+
+ switch (mIvVideoColorFormat) {
+ case IV_YUV_420P:
+ {
+ // input buffer is supposed to be const but Ittiam API wants bare pointer.
+ ps_inp_raw_buf->apv_bufs[0] = yPlane;
+ ps_inp_raw_buf->apv_bufs[1] = uPlane;
+ ps_inp_raw_buf->apv_bufs[2] = vPlane;
+
+ ps_inp_raw_buf->au4_wd[0] = input->width();
+ ps_inp_raw_buf->au4_wd[1] = input->width() / 2;
+ ps_inp_raw_buf->au4_wd[2] = input->width() / 2;
+
+ ps_inp_raw_buf->au4_ht[0] = input->height();
+ ps_inp_raw_buf->au4_ht[1] = input->height() / 2;
+ ps_inp_raw_buf->au4_ht[2] = input->height() / 2;
+
+ ps_inp_raw_buf->au4_strd[0] = yStride;
+ ps_inp_raw_buf->au4_strd[1] = uStride;
+ ps_inp_raw_buf->au4_strd[2] = vStride;
+ break;
+ }
+
+ case IV_YUV_422ILE:
+ {
+ // TODO
+ // ps_inp_raw_buf->apv_bufs[0] = pu1_buf;
+ // ps_inp_raw_buf->au4_wd[0] = mWidth * 2;
+ // ps_inp_raw_buf->au4_ht[0] = mHeight;
+ // ps_inp_raw_buf->au4_strd[0] = mStride * 2;
+ break;
+ }
+
+ case IV_YUV_420SP_UV:
+ case IV_YUV_420SP_VU:
+ default:
+ {
+ ps_inp_raw_buf->apv_bufs[0] = yPlane;
+ ps_inp_raw_buf->apv_bufs[1] = uPlane;
+
+ ps_inp_raw_buf->au4_wd[0] = input->width();
+ ps_inp_raw_buf->au4_wd[1] = input->width();
+
+ ps_inp_raw_buf->au4_ht[0] = input->height();
+ ps_inp_raw_buf->au4_ht[1] = input->height() / 2;
+
+ ps_inp_raw_buf->au4_strd[0] = yStride;
+ ps_inp_raw_buf->au4_strd[1] = uStride;
+ break;
+ }
+ }
+ return C2_OK;
+}
+
+void C2SoftAvcEnc::process(
+ const std::unique_ptr<C2Work> &work,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ work->workletsProcessed = 0u;
+
+ IV_STATUS_T status;
+ WORD32 timeDelay, timeTaken;
+ uint64_t timestamp = work->input.ordinal.timestamp.peekull();
+
+ // Initialize encoder if not already initialized
+ if (mCodecCtx == NULL) {
+ if (C2_OK != initEncoder()) {
+ ALOGE("Failed to initialize encoder");
+ // TODO: notify(error, C2_CORRUPTED, 0 /* arg2 */, NULL /* data */);
+ return;
+ }
+ }
+ if (mSignalledError) {
+ return;
+ }
+
+ // while (!mSawOutputEOS && !outQueue.empty()) {
+ c2_status_t error;
+ ive_video_encode_ip_t s_encode_ip;
+ ive_video_encode_op_t s_encode_op;
+
+ if (!mSpsPpsHeaderReceived) {
+ constexpr uint32_t kHeaderLength = MIN_STREAM_SIZE;
+ uint8_t header[kHeaderLength];
+ error = setEncodeArgs(
+ &s_encode_ip, &s_encode_op, NULL, header, kHeaderLength, timestamp);
+ if (error != C2_OK) {
+ mSignalledError = true;
+ // TODO: notify(error, C2_CORRUPTED, 0, 0);
+ return;
+ }
+ status = ive_api_function(mCodecCtx, &s_encode_ip, &s_encode_op);
+
+ if (IV_SUCCESS != status) {
+ ALOGE("Encode header failed = 0x%x\n",
+ s_encode_op.u4_error_code);
+ return;
+ } else {
+ ALOGV("Bytes Generated in header %d\n",
+ s_encode_op.s_out_buf.u4_bytes);
+ }
+
+ mSpsPpsHeaderReceived = true;
+
+ std::unique_ptr<C2StreamCsdInfo::output> csd =
+ C2StreamCsdInfo::output::alloc_unique(s_encode_op.s_out_buf.u4_bytes, 0u);
+ memcpy(csd->m.value, header, s_encode_op.s_out_buf.u4_bytes);
+ work->worklets.front()->output.configUpdate.push_back(std::move(csd));
+
+ DUMP_TO_FILE(
+ mOutFile, csd->m.value, csd->flexCount());
+ }
+
+ if (mUpdateFlag) {
+ if (mUpdateFlag & kUpdateBitrate) {
+ setBitRate();
+ }
+ if (mUpdateFlag & kRequestKeyFrame) {
+ setFrameType(IV_IDR_FRAME);
+ }
+ if (mUpdateFlag & kUpdateAIRMode) {
+ setAirParams();
+ // notify(OMX_EventPortSettingsChanged, kOutputPortIndex,
+ // OMX_IndexConfigAndroidIntraRefresh, NULL);
+ }
+ mUpdateFlag = 0;
+ }
+
+ if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
+ mSawInputEOS = true;
+ }
+
+ /* In normal mode, store inputBufferInfo and this will be returned
+ when encoder consumes this input */
+ // if (!mInputDataIsMeta && (inputBufferInfo != NULL)) {
+ // for (size_t i = 0; i < MAX_INPUT_BUFFER_HEADERS; i++) {
+ // if (NULL == mInputBufferInfo[i]) {
+ // mInputBufferInfo[i] = inputBufferInfo;
+ // break;
+ // }
+ // }
+ // }
+ const C2GraphicView view =
+ work->input.buffers[0]->data().graphicBlocks().front().map().get();
+ if (view.error() != C2_OK) {
+ ALOGE("graphic view map err = %d", view.error());
+ return;
+ }
+
+ std::shared_ptr<C2LinearBlock> block;
+
+ do {
+ C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+ // TODO: error handling, proper usage, etc.
+ c2_status_t err = pool->fetchLinearBlock(mOutBufferSize, usage, &block);
+ if (err != C2_OK) {
+ ALOGE("fetch linear block err = %d", err);
+ return;
+ }
+ C2WriteView wView = block->map().get();
+ if (wView.error() != C2_OK) {
+ ALOGE("write view map err = %d", wView.error());
+ return;
+ }
+
+ error = setEncodeArgs(
+ &s_encode_ip, &s_encode_op, &view, wView.base(), wView.capacity(), timestamp);
+ if (error != C2_OK) {
+ mSignalledError = true;
+ ALOGE("setEncodeArgs failed : %d", error);
+ // TODO: notify(error, C2_CORRUPTED, 0, 0);
+ return;
+ }
+
+ // DUMP_TO_FILE(
+ // mInFile, s_encode_ip.s_inp_buf.apv_bufs[0],
+ // (mHeight * mStride * 3 / 2));
+
+ GETTIME(&mTimeStart, NULL);
+ /* Compute time elapsed between end of previous decode()
+ * to start of current decode() */
+ TIME_DIFF(mTimeEnd, mTimeStart, timeDelay);
+ status = ive_api_function(mCodecCtx, &s_encode_ip, &s_encode_op);
+
+ if (IV_SUCCESS != status) {
+ if ((s_encode_op.u4_error_code & 0xFF) == IH264E_BITSTREAM_BUFFER_OVERFLOW) {
+ // TODO: use IVE_CMD_CTL_GETBUFINFO for proper max input size?
+ mOutBufferSize *= 2;
+ continue;
+ }
+ ALOGE("Encode Frame failed = 0x%x\n",
+ s_encode_op.u4_error_code);
+ mSignalledError = true;
+ // TODO: notify(error, C2_CORRUPTED, 0, 0);
+ return;
+ }
+ } while (IV_SUCCESS != status);
+
+ // Hold input buffer reference
+ mBuffers[s_encode_ip.s_inp_buf.apv_bufs[0]] = work->input.buffers[0];
+
+ GETTIME(&mTimeEnd, NULL);
+ /* Compute time taken for decode() */
+ TIME_DIFF(mTimeStart, mTimeEnd, timeTaken);
+
+ ALOGV("timeTaken=%6d delay=%6d numBytes=%6d", timeTaken, timeDelay,
+ s_encode_op.s_out_buf.u4_bytes);
+
+ void *freed = s_encode_op.s_inp_buf.apv_bufs[0];
+ /* If encoder frees up an input buffer, mark it as free */
+ if (freed != NULL) {
+ if (mBuffers.count(freed) == 0u) {
+ // TODO: error
+ return;
+ }
+ // Release input buffer reference
+ mBuffers.erase(freed);
+
+ auto it = std::find_if(
+ mConversionBuffersInUse.begin(), mConversionBuffersInUse.end(),
+ [freed](const auto &elem) { return elem.get() == freed; });
+ if (it != mConversionBuffersInUse.end()) {
+ mFreeConversionBuffers.push_back(std::move(*it));
+ mConversionBuffersInUse.erase(it);
+ }
+ }
+
+ work->worklets.front()->output.flags = work->input.flags;
+ work->worklets.front()->output.ordinal = work->input.ordinal;
+ work->worklets.front()->output.ordinal.timestamp =
+ ((uint64_t)s_encode_op.u4_timestamp_high << 32) | s_encode_op.u4_timestamp_low;
+ work->worklets.front()->output.buffers.clear();
+ std::shared_ptr<C2Buffer> buffer =
+ createLinearBuffer(block, 0, s_encode_op.s_out_buf.u4_bytes);
+ work->worklets.front()->output.buffers.push_back(buffer);
+ work->workletsProcessed = 1u;
+
+ if (IV_IDR_FRAME == s_encode_op.u4_encoded_frame_type) {
+ buffer->setInfo(std::make_shared<C2StreamPictureTypeMaskInfo::output>(
+ 0u /* stream id */, C2PictureTypeKeyFrame));
+ }
+
+ if (s_encode_op.u4_is_last) {
+ // outputBufferHeader->nFlags |= OMX_BUFFERFLAG_EOS;
+ mSawOutputEOS = true;
+ } else {
+ // outputBufferHeader->nFlags &= ~OMX_BUFFERFLAG_EOS;
+ }
+}
+
+c2_status_t C2SoftAvcEnc::drain(
+ uint32_t drainMode,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ // TODO: use IVE_CMD_CTL_FLUSH?
+ (void)drainMode;
+ (void)pool;
+ return C2_OK;
+}
+
+
+class C2SoftAvcEncFactory : public C2ComponentFactory {
+public:
+ virtual c2_status_t createComponent(
+ c2_node_id_t id, std::shared_ptr<C2Component>* const component,
+ std::function<void(::android::C2Component*)> deleter) override {
+ *component = std::shared_ptr<C2Component>(new C2SoftAvcEnc("avcenc", id), deleter);
+ return C2_OK;
+ }
+
+ virtual c2_status_t createInterface(
+ c2_node_id_t id, std::shared_ptr<C2ComponentInterface>* const interface,
+ std::function<void(::android::C2ComponentInterface*)> deleter) override {
+ *interface =
+ SimpleC2Interface::Builder("avcenc", id, deleter)
+ .inputFormat(C2FormatVideo)
+ .outputFormat(C2FormatCompressed)
+ .build();
+ return C2_OK;
+ }
+
+ virtual ~C2SoftAvcEncFactory() override = default;
+};
+
+} // namespace android
+
+extern "C" ::android::C2ComponentFactory* CreateCodec2Factory() {
+ ALOGV("in %s", __func__);
+ return new ::android::C2SoftAvcEncFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::android::C2ComponentFactory* factory) {
+ ALOGV("in %s", __func__);
+ delete factory;
+}
diff --git a/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.h b/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.h
new file mode 100644
index 0000000..5b2a514
--- /dev/null
+++ b/media/libstagefright/codecs/avcenc/C2SoftAvcEnc.h
@@ -0,0 +1,293 @@
+/*
+ * Copyright 2018 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 C2_SOFT_AVC_ENC_H__
+#define C2_SOFT_AVC_ENC_H__
+
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Vector.h>
+
+#include <SimpleC2Component.h>
+
+namespace android {
+
+#define CODEC_MAX_CORES 4
+#define LEN_STATUS_BUFFER (10 * 1024)
+#define MAX_VBV_BUFF_SIZE (120 * 16384)
+#define MAX_NUM_IO_BUFS 3
+
+#define DEFAULT_MAX_REF_FRM 2
+#define DEFAULT_MAX_REORDER_FRM 0
+#define DEFAULT_QP_MIN 10
+#define DEFAULT_QP_MAX 40
+#define DEFAULT_MAX_BITRATE 20000000
+#define DEFAULT_MAX_SRCH_RANGE_X 256
+#define DEFAULT_MAX_SRCH_RANGE_Y 256
+#define DEFAULT_MAX_FRAMERATE 120000
+#define DEFAULT_NUM_CORES 1
+#define DEFAULT_NUM_CORES_PRE_ENC 0
+#define DEFAULT_FPS 30
+#define DEFAULT_ENC_SPEED IVE_NORMAL
+
+#define DEFAULT_MEM_REC_CNT 0
+#define DEFAULT_RECON_ENABLE 0
+#define DEFAULT_CHKSUM_ENABLE 0
+#define DEFAULT_START_FRM 0
+#define DEFAULT_NUM_FRMS 0xFFFFFFFF
+#define DEFAULT_INP_COLOR_FORMAT IV_YUV_420SP_VU
+#define DEFAULT_RECON_COLOR_FORMAT IV_YUV_420P
+#define DEFAULT_LOOPBACK 0
+#define DEFAULT_SRC_FRAME_RATE 30
+#define DEFAULT_TGT_FRAME_RATE 30
+#define DEFAULT_MAX_WD 1920
+#define DEFAULT_MAX_HT 1920
+#define DEFAULT_MAX_LEVEL 41
+#define DEFAULT_STRIDE 0
+#define DEFAULT_WD 1280
+#define DEFAULT_HT 720
+#define DEFAULT_PSNR_ENABLE 0
+#define DEFAULT_ME_SPEED 100
+#define DEFAULT_ENABLE_FAST_SAD 0
+#define DEFAULT_ENABLE_ALT_REF 0
+#define DEFAULT_RC_MODE IVE_RC_STORAGE
+#define DEFAULT_BITRATE 6000000
+#define DEFAULT_I_QP 22
+#define DEFAULT_I_QP_MAX DEFAULT_QP_MAX
+#define DEFAULT_I_QP_MIN DEFAULT_QP_MIN
+#define DEFAULT_P_QP 28
+#define DEFAULT_P_QP_MAX DEFAULT_QP_MAX
+#define DEFAULT_P_QP_MIN DEFAULT_QP_MIN
+#define DEFAULT_B_QP 22
+#define DEFAULT_B_QP_MAX DEFAULT_QP_MAX
+#define DEFAULT_B_QP_MIN DEFAULT_QP_MIN
+#define DEFAULT_AIR IVE_AIR_MODE_NONE
+#define DEFAULT_AIR_REFRESH_PERIOD 30
+#define DEFAULT_SRCH_RNG_X 64
+#define DEFAULT_SRCH_RNG_Y 48
+#define DEFAULT_I_INTERVAL 30
+#define DEFAULT_IDR_INTERVAL 1000
+#define DEFAULT_B_FRAMES 0
+#define DEFAULT_DISABLE_DEBLK_LEVEL 0
+#define DEFAULT_HPEL 1
+#define DEFAULT_QPEL 1
+#define DEFAULT_I4 1
+#define DEFAULT_EPROFILE IV_PROFILE_BASE
+#define DEFAULT_ENTROPY_MODE 0
+#define DEFAULT_SLICE_MODE IVE_SLICE_MODE_NONE
+#define DEFAULT_SLICE_PARAM 256
+#define DEFAULT_ARCH ARCH_ARM_A9Q
+#define DEFAULT_SOC SOC_GENERIC
+#define DEFAULT_INTRA4x4 0
+#define STRLENGTH 500
+#define DEFAULT_CONSTRAINED_INTRA 0
+
+#define MIN(a, b) ((a) < (b))? (a) : (b)
+#define MAX(a, b) ((a) > (b))? (a) : (b)
+#define ALIGN16(x) ((((x) + 15) >> 4) << 4)
+#define ALIGN128(x) ((((x) + 127) >> 7) << 7)
+#define ALIGN4096(x) ((((x) + 4095) >> 12) << 12)
+
+/** Used to remove warnings about unused parameters */
+#define UNUSED(x) ((void)(x))
+
+/** Get time */
+#define GETTIME(a, b) gettimeofday(a, b);
+
+/** Compute difference between start and end */
+#define TIME_DIFF(start, end, diff) \
+ diff = (((end).tv_sec - (start).tv_sec) * 1000000) + \
+ ((end).tv_usec - (start).tv_usec);
+
+#define ive_aligned_malloc(alignment, size) memalign(alignment, size)
+#define ive_aligned_free(buf) free(buf)
+
+struct C2SoftAvcEnc : public SimpleC2Component {
+ C2SoftAvcEnc(const char *name, c2_node_id_t id);
+
+ // From SimpleC2Component
+ c2_status_t onInit() override;
+ c2_status_t onStop() override;
+ void onReset() override;
+ void onRelease() override;
+ c2_status_t onFlush_sm() override;
+ void process(
+ const std::unique_ptr<C2Work> &work,
+ const std::shared_ptr<C2BlockPool> &pool) override;
+ c2_status_t drain(
+ uint32_t drainMode,
+ const std::shared_ptr<C2BlockPool> &pool) override;
+
+protected:
+ virtual ~C2SoftAvcEnc();
+
+private:
+ enum {
+ kUpdateBitrate = 1 << 0,
+ kRequestKeyFrame = 1 << 1,
+ kUpdateAIRMode = 1 << 2,
+ };
+
+ // OMX input buffer's timestamp and flags
+ typedef struct {
+ int64_t mTimeUs;
+ int32_t mFlags;
+ } InputBufferInfo;
+
+ int32_t mStride;
+
+ struct timeval mTimeStart; // Time at the start of decode()
+ struct timeval mTimeEnd; // Time at the end of decode()
+
+ int mUpdateFlag;
+
+#ifdef FILE_DUMP_ENABLE
+ char mInFile[200];
+ char mOutFile[200];
+#endif /* FILE_DUMP_ENABLE */
+
+ IV_COLOR_FORMAT_T mIvVideoColorFormat;
+
+ IV_PROFILE_T mAVCEncProfile;
+ WORD32 mAVCEncLevel;
+ bool mStarted;
+ bool mSpsPpsHeaderReceived;
+
+ bool mSawInputEOS;
+ bool mSawOutputEOS;
+ bool mSignalledError;
+ bool mIntra4x4;
+ bool mEnableFastSad;
+ bool mEnableAltRef;
+ bool mReconEnable;
+ bool mPSNREnable;
+ bool mEntropyMode;
+ bool mConstrainedIntraFlag;
+ IVE_SPEED_CONFIG mEncSpeed;
+
+ iv_obj_t *mCodecCtx; // Codec context
+ iv_mem_rec_t *mMemRecords; // Memory records requested by the codec
+ size_t mNumMemRecords; // Number of memory records requested by codec
+ size_t mNumCores; // Number of cores used by the codec
+
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mFramerate;
+ uint32_t mBitrate;
+ uint32_t mOutBufferSize;
+ UWORD32 mHeaderGenerated;
+ UWORD32 mBframes;
+ IV_ARCH_T mArch;
+ IVE_SLICE_MODE_T mSliceMode;
+ UWORD32 mSliceParam;
+ bool mHalfPelEnable;
+ UWORD32 mIInterval;
+ UWORD32 mIDRInterval;
+ UWORD32 mDisableDeblkLevel;
+ IVE_AIR_MODE_T mAIRMode;
+ UWORD32 mAIRRefreshPeriod;
+ std::map<const void *, std::shared_ptr<C2Buffer>> mBuffers;
+ std::list<std::unique_ptr<uint8_t[]>> mFreeConversionBuffers;
+ std::list<std::unique_ptr<uint8_t[]>> mConversionBuffersInUse;
+
+ void initEncParams();
+ c2_status_t initEncoder();
+ c2_status_t releaseEncoder();
+
+ c2_status_t setFrameType(IV_PICTURE_CODING_TYPE_T e_frame_type);
+ c2_status_t setQp();
+ c2_status_t setEncMode(IVE_ENC_MODE_T e_enc_mode);
+ c2_status_t setDimensions();
+ c2_status_t setNumCores();
+ c2_status_t setFrameRate();
+ c2_status_t setIpeParams();
+ c2_status_t setBitRate();
+ c2_status_t setAirParams();
+ c2_status_t setMeParams();
+ c2_status_t setGopParams();
+ c2_status_t setProfileParams();
+ c2_status_t setDeblockParams();
+ c2_status_t setVbvParams();
+ void logVersion();
+ c2_status_t setEncodeArgs(
+ ive_video_encode_ip_t *ps_encode_ip,
+ ive_video_encode_op_t *ps_encode_op,
+ const C2GraphicView *const input,
+ uint8_t *base,
+ uint32_t capacity,
+ uint64_t timestamp);
+
+ DISALLOW_EVIL_CONSTRUCTORS(C2SoftAvcEnc);
+};
+
+#ifdef FILE_DUMP_ENABLE
+
+#define INPUT_DUMP_PATH "/sdcard/media/avce_input"
+#define INPUT_DUMP_EXT "yuv"
+#define OUTPUT_DUMP_PATH "/sdcard/media/avce_output"
+#define OUTPUT_DUMP_EXT "h264"
+
+#define GENERATE_FILE_NAMES() { \
+ GETTIME(&mTimeStart, NULL); \
+ strcpy(mInFile, ""); \
+ sprintf(mInFile, "%s_%ld.%ld.%s", INPUT_DUMP_PATH, \
+ mTimeStart.tv_sec, mTimeStart.tv_usec, \
+ INPUT_DUMP_EXT); \
+ strcpy(mOutFile, ""); \
+ sprintf(mOutFile, "%s_%ld.%ld.%s", OUTPUT_DUMP_PATH,\
+ mTimeStart.tv_sec, mTimeStart.tv_usec, \
+ OUTPUT_DUMP_EXT); \
+}
+
+#define CREATE_DUMP_FILE(m_filename) { \
+ FILE *fp = fopen(m_filename, "wb"); \
+ if (fp != NULL) { \
+ ALOGD("Opened file %s", m_filename); \
+ fclose(fp); \
+ } else { \
+ ALOGD("Could not open file %s", m_filename); \
+ } \
+}
+#define DUMP_TO_FILE(m_filename, m_buf, m_size) \
+{ \
+ FILE *fp = fopen(m_filename, "ab"); \
+ if (fp != NULL && m_buf != NULL) { \
+ int i; \
+ i = fwrite(m_buf, 1, m_size, fp); \
+ ALOGD("fwrite ret %d to write %d", i, m_size); \
+ if (i != (int)m_size) { \
+ ALOGD("Error in fwrite, returned %d", i); \
+ perror("Error in write to file"); \
+ } \
+ fclose(fp); \
+ } else { \
+ ALOGD("Could not write to file %s", m_filename);\
+ if (fp != NULL) \
+ fclose(fp); \
+ } \
+}
+#else /* FILE_DUMP_ENABLE */
+#define INPUT_DUMP_PATH
+#define INPUT_DUMP_EXT
+#define OUTPUT_DUMP_PATH
+#define OUTPUT_DUMP_EXT
+#define GENERATE_FILE_NAMES()
+#define CREATE_DUMP_FILE(m_filename)
+#define DUMP_TO_FILE(m_filename, m_buf, m_size)
+#endif /* FILE_DUMP_ENABLE */
+
+} // namespace android
+
+#endif // C2_SOFT_AVC_ENC_H__
diff --git a/media/libstagefright/codecs/cmds/Android.bp b/media/libstagefright/codecs/cmds/Android.bp
index 40f1a3d..6dba0a3 100644
--- a/media/libstagefright/codecs/cmds/Android.bp
+++ b/media/libstagefright/codecs/cmds/Android.bp
@@ -13,6 +13,7 @@
"libcutils",
"libgui",
"liblog",
+ "libmediaextractor",
"libstagefright",
"libstagefright_codec2",
"libstagefright_codec2_vndk",
diff --git a/media/libstagefright/codecs/cmds/codec2.cpp b/media/libstagefright/codecs/cmds/codec2.cpp
index 78fb527..ec05d7a 100644
--- a/media/libstagefright/codecs/cmds/codec2.cpp
+++ b/media/libstagefright/codecs/cmds/codec2.cpp
@@ -247,7 +247,7 @@
}
int slot;
sp<Fence> fence;
- ALOGV("Render: Frame #%" PRId64, work->worklets.front()->output.ordinal.frame_index);
+ ALOGV("Render: Frame #%lld", work->worklets.front()->output.ordinal.frameIndex.peekll());
const std::shared_ptr<C2Buffer> &output = work->worklets.front()->output.buffers[0];
if (output) {
const C2ConstGraphicBlock &block = output->data().graphicBlocks().front();
@@ -266,7 +266,7 @@
status_t err = igbp->attachBuffer(&slot, buffer);
IGraphicBufferProducer::QueueBufferInput qbi(
- work->worklets.front()->output.ordinal.timestamp * 1000ll,
+ (work->worklets.front()->output.ordinal.timestamp * 1000ll).peekll(),
false,
HAL_DATASPACE_UNKNOWN,
Rect(block.width(), block.height()),
@@ -338,9 +338,9 @@
mQueueCondition.wait_for(l, 100ms);
}
}
- work->input.flags = (C2BufferPack::flags_t)0;
+ work->input.flags = (C2FrameData::flags_t)0;
work->input.ordinal.timestamp = timestamp;
- work->input.ordinal.frame_index = numFrames;
+ work->input.ordinal.frameIndex = numFrames;
std::shared_ptr<C2LinearBlock> block;
mLinearPool->fetchLinearBlock(
diff --git a/media/libstagefright/colorconversion/ColorConverter.cpp b/media/libstagefright/colorconversion/ColorConverter.cpp
index c7f9001..eae73fc 100644
--- a/media/libstagefright/colorconversion/ColorConverter.cpp
+++ b/media/libstagefright/colorconversion/ColorConverter.cpp
@@ -64,7 +64,7 @@
|| mDstFormat == OMX_COLOR_Format32bitBGRA8888;
case OMX_COLOR_FormatYUV420Planar16:
- return mDstFormat == OMX_COLOR_Format32BitRGBA1010102;
+ return mDstFormat == OMX_COLOR_FormatYUV444Y410;
case OMX_COLOR_FormatCbYCrY:
case OMX_QCOM_COLOR_FormatYVU420SemiPlanar:
@@ -77,6 +77,12 @@
}
}
+bool ColorConverter::isDstRGB() const {
+ return mDstFormat == OMX_COLOR_Format16bitRGB565
+ || mDstFormat == OMX_COLOR_Format32BitRGBA8888
+ || mDstFormat == OMX_COLOR_Format32bitBGRA8888;
+}
+
ColorConverter::BitmapParams::BitmapParams(
void *bits,
size_t width, size_t height,
@@ -99,7 +105,7 @@
case OMX_COLOR_Format32bitBGRA8888:
case OMX_COLOR_Format32BitRGBA8888:
- case OMX_COLOR_Format32BitRGBA1010102:
+ case OMX_COLOR_FormatYUV444Y410:
mBpp = 4;
mStride = 4 * mWidth;
break;
diff --git a/media/libstagefright/colorconversion/SoftwareRenderer.cpp b/media/libstagefright/colorconversion/SoftwareRenderer.cpp
index fca9c09..c9ebbdc 100644
--- a/media/libstagefright/colorconversion/SoftwareRenderer.cpp
+++ b/media/libstagefright/colorconversion/SoftwareRenderer.cpp
@@ -134,6 +134,10 @@
}
case OMX_COLOR_FormatYUV420Planar16:
{
+ // Here we would convert OMX_COLOR_FormatYUV420Planar16 into
+ // OMX_COLOR_FormatYUV444Y410, and put it inside a buffer with
+ // format HAL_PIXEL_FORMAT_RGBA_1010102. Surfaceflinger will
+ // use render engine to convert it to RGB if needed.
halFormat = HAL_PIXEL_FORMAT_RGBA_1010102;
bufWidth = (mCropWidth + 1) & ~1;
bufHeight = (mCropHeight + 1) & ~1;
@@ -152,7 +156,7 @@
CHECK(mConverter->isValid());
} else if (mColorFormat == OMX_COLOR_FormatYUV420Planar16) {
mConverter = new ColorConverter(
- mColorFormat, OMX_COLOR_Format32BitRGBA1010102);
+ mColorFormat, OMX_COLOR_FormatYUV444Y410);
CHECK(mConverter->isValid());
}
@@ -380,7 +384,7 @@
if (format->findInt32("android._dataspace", (int32_t *)&dataSpace) && dataSpace != mDataSpace) {
mDataSpace = dataSpace;
- if (mConverter != NULL) {
+ if (mConverter != NULL && mConverter->isDstRGB()) {
// graphics only supports full range RGB. ColorConverter should have
// converted any YUV to full range.
dataSpace = (android_dataspace)
diff --git a/media/libstagefright/foundation/ABuffer.cpp b/media/libstagefright/foundation/ABuffer.cpp
index 804046a..c8965d9 100644
--- a/media/libstagefright/foundation/ABuffer.cpp
+++ b/media/libstagefright/foundation/ABuffer.cpp
@@ -19,13 +19,11 @@
#include "ADebug.h"
#include "ALooper.h"
#include "AMessage.h"
-#include "MediaBufferBase.h"
namespace android {
ABuffer::ABuffer(size_t capacity)
- : mMediaBufferBase(NULL),
- mRangeOffset(0),
+ : mRangeOffset(0),
mInt32Data(0),
mOwnsData(true) {
mData = malloc(capacity);
@@ -39,8 +37,7 @@
}
ABuffer::ABuffer(void *data, size_t capacity)
- : mMediaBufferBase(NULL),
- mData(data),
+ : mData(data),
mCapacity(capacity),
mRangeOffset(0),
mRangeLength(capacity),
@@ -66,8 +63,6 @@
mData = NULL;
}
}
-
- setMediaBufferBase(NULL);
}
void ABuffer::setRange(size_t offset, size_t size) {
@@ -85,19 +80,5 @@
return mMeta;
}
-MediaBufferBase *ABuffer::getMediaBufferBase() {
- if (mMediaBufferBase != NULL) {
- mMediaBufferBase->add_ref();
- }
- return mMediaBufferBase;
-}
-
-void ABuffer::setMediaBufferBase(MediaBufferBase *mediaBuffer) {
- if (mMediaBufferBase != NULL) {
- mMediaBufferBase->release();
- }
- mMediaBufferBase = mediaBuffer;
-}
-
} // namespace android
diff --git a/media/libstagefright/foundation/Android.bp b/media/libstagefright/foundation/Android.bp
index 4dfe5e5..2258e2c 100644
--- a/media/libstagefright/foundation/Android.bp
+++ b/media/libstagefright/foundation/Android.bp
@@ -62,8 +62,6 @@
"AStringUtils.cpp",
"ByteUtils.cpp",
"ColorUtils.cpp",
- "MediaBuffer.cpp",
- "MediaBufferGroup.cpp",
"MediaDefs.cpp",
"MediaKeys.cpp",
"MetaData.cpp",
diff --git a/media/libstagefright/foundation/MetaData.cpp b/media/libstagefright/foundation/MetaData.cpp
index a8965f0..2415c61 100644
--- a/media/libstagefright/foundation/MetaData.cpp
+++ b/media/libstagefright/foundation/MetaData.cpp
@@ -17,6 +17,7 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "MetaData"
#include <inttypes.h>
+#include <utils/KeyedVector.h>
#include <utils/Log.h>
#include <stdlib.h>
@@ -29,30 +30,81 @@
namespace android {
-MetaData::MetaData() {
+struct MetaData::typed_data {
+ typed_data();
+ ~typed_data();
+
+ typed_data(const MetaData::typed_data &);
+ typed_data &operator=(const MetaData::typed_data &);
+
+ void clear();
+ void setData(uint32_t type, const void *data, size_t size);
+ void getData(uint32_t *type, const void **data, size_t *size) const;
+ // may include hexdump of binary data if verbose=true
+ String8 asString(bool verbose) const;
+
+private:
+ uint32_t mType;
+ size_t mSize;
+
+ union {
+ void *ext_data;
+ float reservoir;
+ } u;
+
+ bool usesReservoir() const {
+ return mSize <= sizeof(u.reservoir);
+ }
+
+ void *allocateStorage(size_t size);
+ void freeStorage();
+
+ void *storage() {
+ return usesReservoir() ? &u.reservoir : u.ext_data;
+ }
+
+ const void *storage() const {
+ return usesReservoir() ? &u.reservoir : u.ext_data;
+ }
+};
+
+struct MetaData::Rect {
+ int32_t mLeft, mTop, mRight, mBottom;
+};
+
+
+struct MetaData::MetaDataInternal {
+ KeyedVector<uint32_t, MetaData::typed_data> mItems;
+};
+
+
+MetaData::MetaData()
+ : mInternalData(new MetaDataInternal()) {
}
MetaData::MetaData(const MetaData &from)
: RefBase(),
- mItems(from.mItems) {
+ mInternalData(new MetaDataInternal()) {
+ mInternalData->mItems = from.mInternalData->mItems;
}
MetaData::~MetaData() {
clear();
+ delete mInternalData;
}
void MetaData::clear() {
- mItems.clear();
+ mInternalData->mItems.clear();
}
bool MetaData::remove(uint32_t key) {
- ssize_t i = mItems.indexOfKey(key);
+ ssize_t i = mInternalData->mItems.indexOfKey(key);
if (i < 0) {
return false;
}
- mItems.removeItemsAt(i);
+ mInternalData->mItems.removeItemsAt(i);
return true;
}
@@ -192,15 +244,15 @@
uint32_t key, uint32_t type, const void *data, size_t size) {
bool overwrote_existing = true;
- ssize_t i = mItems.indexOfKey(key);
+ ssize_t i = mInternalData->mItems.indexOfKey(key);
if (i < 0) {
typed_data item;
- i = mItems.add(key, item);
+ i = mInternalData->mItems.add(key, item);
overwrote_existing = false;
}
- typed_data &item = mItems.editValueAt(i);
+ typed_data &item = mInternalData->mItems.editValueAt(i);
item.setData(type, data, size);
@@ -209,13 +261,13 @@
bool MetaData::findData(uint32_t key, uint32_t *type,
const void **data, size_t *size) const {
- ssize_t i = mItems.indexOfKey(key);
+ ssize_t i = mInternalData->mItems.indexOfKey(key);
if (i < 0) {
return false;
}
- const typed_data &item = mItems.valueAt(i);
+ const typed_data &item = mInternalData->mItems.valueAt(i);
item.getData(type, data, size);
@@ -223,7 +275,7 @@
}
bool MetaData::hasData(uint32_t key) const {
- ssize_t i = mItems.indexOfKey(key);
+ ssize_t i = mInternalData->mItems.indexOfKey(key);
if (i < 0) {
return false;
@@ -369,11 +421,11 @@
String8 MetaData::toString() const {
String8 s;
- for (int i = mItems.size(); --i >= 0;) {
- int32_t key = mItems.keyAt(i);
+ for (int i = mInternalData->mItems.size(); --i >= 0;) {
+ int32_t key = mInternalData->mItems.keyAt(i);
char cc[5];
MakeFourCCString(key, cc);
- const typed_data &item = mItems.valueAt(i);
+ const typed_data &item = mInternalData->mItems.valueAt(i);
s.appendFormat("%s: %s", cc, item.asString(false).string());
if (i != 0) {
s.append(", ");
@@ -382,25 +434,25 @@
return s;
}
void MetaData::dumpToLog() const {
- for (int i = mItems.size(); --i >= 0;) {
- int32_t key = mItems.keyAt(i);
+ for (int i = mInternalData->mItems.size(); --i >= 0;) {
+ int32_t key = mInternalData->mItems.keyAt(i);
char cc[5];
MakeFourCCString(key, cc);
- const typed_data &item = mItems.valueAt(i);
+ const typed_data &item = mInternalData->mItems.valueAt(i);
ALOGI("%s: %s", cc, item.asString(true /* verbose */).string());
}
}
status_t MetaData::writeToParcel(Parcel &parcel) {
status_t ret;
- size_t numItems = mItems.size();
+ size_t numItems = mInternalData->mItems.size();
ret = parcel.writeUint32(uint32_t(numItems));
if (ret) {
return ret;
}
for (size_t i = 0; i < numItems; i++) {
- int32_t key = mItems.keyAt(i);
- const typed_data &item = mItems.valueAt(i);
+ int32_t key = mInternalData->mItems.keyAt(i);
+ const typed_data &item = mInternalData->mItems.valueAt(i);
uint32_t type;
const void *data;
size_t size;
diff --git a/media/libstagefright/foundation/include/media/stagefright/foundation/ABuffer.h b/media/libstagefright/foundation/include/media/stagefright/foundation/ABuffer.h
index ef11434..8fe9f8d 100644
--- a/media/libstagefright/foundation/include/media/stagefright/foundation/ABuffer.h
+++ b/media/libstagefright/foundation/include/media/stagefright/foundation/ABuffer.h
@@ -27,7 +27,6 @@
namespace android {
struct AMessage;
-class MediaBufferBase;
struct ABuffer : public RefBase {
explicit ABuffer(size_t capacity);
@@ -49,17 +48,12 @@
sp<AMessage> meta();
- MediaBufferBase *getMediaBufferBase();
- void setMediaBufferBase(MediaBufferBase *mediaBuffer);
-
protected:
virtual ~ABuffer();
private:
sp<AMessage> mMeta;
- MediaBufferBase *mMediaBufferBase;
-
void *mData;
size_t mCapacity;
size_t mRangeOffset;
diff --git a/media/libstagefright/foundation/include/media/stagefright/foundation/ByteUtils.h b/media/libstagefright/foundation/include/media/stagefright/foundation/ByteUtils.h
index dc4125f..a434f81 100644
--- a/media/libstagefright/foundation/include/media/stagefright/foundation/ByteUtils.h
+++ b/media/libstagefright/foundation/include/media/stagefright/foundation/ByteUtils.h
@@ -22,8 +22,20 @@
namespace android {
-#define FOURCC(c1, c2, c3, c4) \
- ((c1) << 24 | (c2) << 16 | (c3) << 8 | (c4))
+constexpr int FOURCC(unsigned char c1, unsigned char c2, unsigned char c3, unsigned char c4) {
+ return ((c1) << 24 | (c2) << 16 | (c3) << 8 | (c4));
+}
+
+template <size_t N>
+constexpr int32_t FOURCC(const char (&s) [N]) {
+ static_assert(N == 5, "fourcc: wrong length");
+ return
+ (unsigned char) s[0] << 24 |
+ (unsigned char) s[1] << 16 |
+ (unsigned char) s[2] << 8 |
+ (unsigned char) s[3] << 0;
+}
+
uint16_t U16_AT(const uint8_t *ptr);
uint32_t U32_AT(const uint8_t *ptr);
diff --git a/media/libstagefright/foundation/include/media/stagefright/foundation/MediaBufferBase.h b/media/libstagefright/foundation/include/media/stagefright/foundation/MediaBufferBase.h
deleted file mode 100644
index 99418fb..0000000
--- a/media/libstagefright/foundation/include/media/stagefright/foundation/MediaBufferBase.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MEDIA_BUFFER_BASE_H_
-
-#define MEDIA_BUFFER_BASE_H_
-
-namespace android {
-
-class MediaBufferBase {
-public:
- MediaBufferBase() {}
-
- virtual void release() = 0;
- virtual void add_ref() = 0;
-
-protected:
- virtual ~MediaBufferBase() {}
-
-private:
- MediaBufferBase(const MediaBufferBase &);
- MediaBufferBase &operator=(const MediaBufferBase &);
-};
-
-} // namespace android
-
-#endif // MEDIA_BUFFER_BASE_H_
diff --git a/media/libstagefright/gbs/Android.bp b/media/libstagefright/gbs/Android.bp
new file mode 100644
index 0000000..a53b7b7
--- /dev/null
+++ b/media/libstagefright/gbs/Android.bp
@@ -0,0 +1,58 @@
+cc_library_shared {
+ name: "libstagefright_gbs",
+ vendor_available: true,
+ vndk: {
+ enabled: true,
+ },
+
+ srcs: [
+ "FrameDropper.cpp",
+ "GraphicBufferSource.cpp",
+ ],
+
+ export_include_dirs: [
+ "include",
+ ],
+
+ header_libs: [
+ "media_plugin_headers",
+ ],
+
+ export_header_lib_headers: [
+ "media_plugin_headers",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "libutils",
+ "liblog",
+ "libui",
+ "libgui",
+ "libcutils",
+ "libstagefright_foundation",
+ "libnativewindow", // TODO(b/62923479): use header library
+ ],
+
+ export_shared_lib_headers: [
+ "libstagefright_foundation",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ "-Wno-unused-parameter",
+ "-Wno-documentation",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ "unsigned-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+}
diff --git a/media/libstagefright/omx/FrameDropper.cpp b/media/libstagefright/gbs/FrameDropper.cpp
similarity index 97%
rename from media/libstagefright/omx/FrameDropper.cpp
rename to media/libstagefright/gbs/FrameDropper.cpp
index 0c50c58..9f0b8cc 100644
--- a/media/libstagefright/omx/FrameDropper.cpp
+++ b/media/libstagefright/gbs/FrameDropper.cpp
@@ -18,7 +18,7 @@
#define LOG_TAG "FrameDropper"
#include <utils/Log.h>
-#include <media/stagefright/omx/FrameDropper.h>
+#include <media/stagefright/gbs/FrameDropper.h>
#include <media/stagefright/foundation/ADebug.h>
namespace android {
diff --git a/media/libstagefright/omx/GraphicBufferSource.cpp b/media/libstagefright/gbs/GraphicBufferSource.cpp
similarity index 96%
rename from media/libstagefright/omx/GraphicBufferSource.cpp
rename to media/libstagefright/gbs/GraphicBufferSource.cpp
index f331dbb..139c916 100644
--- a/media/libstagefright/omx/GraphicBufferSource.cpp
+++ b/media/libstagefright/gbs/GraphicBufferSource.cpp
@@ -22,9 +22,8 @@
#define STRINGIFY_ENUMS // for asString in HardwareAPI.h/VideoAPI.h
-#include <media/stagefright/omx/GraphicBufferSource.h>
-#include <media/stagefright/omx/FrameDropper.h>
-#include <media/stagefright/omx/OMXUtils.h>
+#include <media/stagefright/gbs/GraphicBufferSource.h>
+#include <media/stagefright/gbs/FrameDropper.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/ColorUtils.h>
@@ -34,9 +33,6 @@
#include <ui/GraphicBuffer.h>
#include <gui/BufferItem.h>
#include <media/hardware/HardwareAPI.h>
-#include <media/openmax/OMX_Component.h>
-#include <media/openmax/OMX_IndexExt.h>
-#include <media/OMXBuffer.h>
#include <inttypes.h>
@@ -361,9 +357,9 @@
}
}
-Status GraphicBufferSource::onOmxExecuting() {
+Status GraphicBufferSource::start() {
Mutex::Autolock autoLock(mMutex);
- ALOGV("--> executing; available=%zu, submittable=%zd",
+ ALOGV("--> start; available=%zu, submittable=%zd",
mAvailableBuffers.size(), mFreeCodecBuffers.size());
CHECK(!mExecuting);
mExecuting = true;
@@ -411,8 +407,8 @@
return Status::ok();
}
-Status GraphicBufferSource::onOmxIdle() {
- ALOGV("omxIdle");
+Status GraphicBufferSource::stop() {
+ ALOGV("stop");
Mutex::Autolock autoLock(mMutex);
@@ -424,7 +420,7 @@
return Status::ok();
}
-Status GraphicBufferSource::onOmxLoaded(){
+Status GraphicBufferSource::release(){
Mutex::Autolock autoLock(mMutex);
if (mLooper != NULL) {
mLooper->unregisterHandler(mReflector->id());
@@ -434,7 +430,7 @@
mLooper.clear();
}
- ALOGV("--> loaded; available=%zu+%d eos=%d eosSent=%d acquired=%d",
+ ALOGV("--> release; available=%zu+%d eos=%d eosSent=%d acquired=%d",
mAvailableBuffers.size(), mNumAvailableUnacquiredBuffers,
mEndOfStream, mEndOfStreamSent, mNumOutstandingAcquires);
@@ -442,7 +438,7 @@
mFreeCodecBuffers.clear();
mSubmittedCodecBuffers.clear();
mLatestBuffer.mBuffer.reset();
- mOMXNode.clear();
+ mComponent.clear();
mExecuting = false;
return Status::ok();
@@ -537,7 +533,8 @@
mLastDataspace = dataspace;
if (ColorUtils::convertDataSpaceToV0(dataspace)) {
- mOMXNode->dispatchDataSpaceChanged(mLastDataspace, mDefaultColorAspectsPacked, pixelFormat);
+ mComponent->dispatchDataSpaceChanged(
+ mLastDataspace, mDefaultColorAspectsPacked, pixelFormat);
}
}
@@ -631,7 +628,7 @@
default:
TRESPASS_DBG("Unknown action type");
// return true here because we did consume an available buffer, so the
- // loop in onOmxExecuting will eventually terminate even if we hit this.
+ // loop in start will eventually terminate even if we hit this.
return false;
}
}
@@ -799,7 +796,7 @@
status_t GraphicBufferSource::submitBuffer_l(const VideoBuffer &item) {
CHECK(!mFreeCodecBuffers.empty());
- IOMX::buffer_id codecBufferId = *mFreeCodecBuffers.begin();
+ uint32_t codecBufferId = *mFreeCodecBuffers.begin();
ALOGV("submitBuffer_l [slot=%d, bufferId=%d]", item.mBuffer->getSlot(), codecBufferId);
@@ -815,15 +812,14 @@
}
std::shared_ptr<AcquiredBuffer> buffer = item.mBuffer;
- // use a GraphicBuffer for now as OMXNodeInstance is using GraphicBuffers to hold references
+ // use a GraphicBuffer for now as component is using GraphicBuffers to hold references
// and it requires this graphic buffer to be able to hold its reference
// and thus we would need to create a new GraphicBuffer from an ANWBuffer separate from the
// acquired GraphicBuffer.
// TODO: this can be reworked globally to use ANWBuffer references
sp<GraphicBuffer> graphicBuffer = buffer->getGraphicBuffer();
- status_t err = mOMXNode->emptyBuffer(
- codecBufferId, OMX_BUFFERFLAG_ENDOFFRAME, graphicBuffer, codecTimeUs,
- buffer->getAcquireFenceFd());
+ status_t err = mComponent->submitBuffer(
+ codecBufferId, graphicBuffer, codecTimeUs, buffer->getAcquireFenceFd());
if (err != OK) {
ALOGW("WARNING: emptyGraphicBuffer failed: 0x%x", err);
@@ -849,11 +845,10 @@
ALOGV("submitEndOfInputStream_l: no codec buffers available");
return;
}
- IOMX::buffer_id codecBufferId = *mFreeCodecBuffers.begin();
+ uint32_t codecBufferId = *mFreeCodecBuffers.begin();
// We reject any additional incoming graphic buffers. There is no acquired buffer used for EOS
- status_t err = mOMXNode->emptyBuffer(
- codecBufferId, OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_EOS);
+ status_t err = mComponent->submitEos(codecBufferId);
if (err != OK) {
ALOGW("emptyDirectBuffer EOS failed: 0x%x", err);
} else {
@@ -959,7 +954,7 @@
bool GraphicBufferSource::areWeDiscardingAvailableBuffers_l() {
return mEndOfStreamSent // already sent EOS to codec
- || mOMXNode == nullptr // there is no codec connected
+ || mComponent == nullptr // there is no codec connected
|| (mSuspended && mActionQueue.empty()) // we are suspended and not waiting for
// any further action
|| !mExecuting;
@@ -970,7 +965,7 @@
// This should only be possible if a new buffer was queued after
// EOS was signaled, i.e. the app is misbehaving.
ALOGW("onFrameAvailable: EOS is sent, ignoring frame");
- } else if (mOMXNode == NULL || (mSuspended && mActionQueue.empty())) {
+ } else if (mComponent == NULL || (mSuspended && mActionQueue.empty())) {
// FIXME: if we are suspended but have a resume queued we will stop repeating the last
// frame. Is that the desired behavior?
ALOGV("onFrameAvailable: suspended, ignoring frame");
@@ -1064,13 +1059,13 @@
}
status_t GraphicBufferSource::configure(
- const sp<IOmxNodeWrapper>& omxNode,
+ const sp<ComponentWrapper>& component,
int32_t dataSpace,
int32_t bufferCount,
uint32_t frameWidth,
uint32_t frameHeight,
uint32_t consumerUsage) {
- if (omxNode == NULL) {
+ if (component == NULL) {
return BAD_VALUE;
}
@@ -1088,7 +1083,7 @@
{
Mutex::Autolock autoLock(mMutex);
- mOMXNode = omxNode;
+ mComponent = component;
err = mConsumer->setDefaultBufferSize(frameWidth, frameHeight);
if (err != NO_ERROR) {
@@ -1320,7 +1315,7 @@
// Set the end-of-stream flag. If no frames are pending from the
// BufferQueue, and a codec buffer is available, and we're executing,
// and there is no stop timestamp, we initiate the EOS from here.
- // Otherwise, we'll let codecBufferEmptied() (or omxExecuting) do it.
+ // Otherwise, we'll let codecBufferEmptied() (or start) do it.
//
// Note: if there are no pending frames and all codec buffers are
// available, we *must* submit the EOS from here or we'll just
diff --git a/media/libstagefright/gbs/include/media/stagefright/gbs/ComponentWrapper.h b/media/libstagefright/gbs/include/media/stagefright/gbs/ComponentWrapper.h
new file mode 100644
index 0000000..e27829b
--- /dev/null
+++ b/media/libstagefright/gbs/include/media/stagefright/gbs/ComponentWrapper.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018, 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 COMPONENT_WRAPPER_H_
+#define COMPONENT_WRAPPER_H_
+
+#include <utils/RefBase.h>
+#include <utils/StrongPointer.h>
+#include <ui/GraphicBuffer.h>
+
+#include <stdint.h>
+
+namespace android {
+
+struct ComponentWrapper : public RefBase {
+ virtual status_t submitBuffer(
+ int32_t bufferId, const sp<GraphicBuffer> &buffer = nullptr,
+ int64_t timestamp = 0, int fenceFd = -1) = 0;
+ virtual status_t submitEos(int32_t bufferId) = 0;
+ virtual void dispatchDataSpaceChanged(
+ int32_t dataSpace, int32_t aspects, int32_t pixelFormat) = 0;
+};
+
+} // namespace android
+
+#endif // COMPONENT_WRAPPER_H_
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/FrameDropper.h b/media/libstagefright/gbs/include/media/stagefright/gbs/FrameDropper.h
similarity index 100%
rename from media/libstagefright/omx/include/media/stagefright/omx/FrameDropper.h
rename to media/libstagefright/gbs/include/media/stagefright/gbs/FrameDropper.h
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/GraphicBufferSource.h b/media/libstagefright/gbs/include/media/stagefright/gbs/GraphicBufferSource.h
similarity index 93%
rename from media/libstagefright/omx/include/media/stagefright/omx/GraphicBufferSource.h
rename to media/libstagefright/gbs/include/media/stagefright/gbs/GraphicBufferSource.h
index 84fee6f..89f6cf8 100644
--- a/media/libstagefright/omx/include/media/stagefright/omx/GraphicBufferSource.h
+++ b/media/libstagefright/gbs/include/media/stagefright/gbs/GraphicBufferSource.h
@@ -18,21 +18,16 @@
#define GRAPHIC_BUFFER_SOURCE_H_
-#include <gui/IGraphicBufferProducer.h>
+#include <binder/Status.h>
#include <gui/BufferQueue.h>
+#include <gui/IGraphicBufferProducer.h>
#include <utils/RefBase.h>
#include <media/hardware/VideoAPI.h>
-#include <media/IOMX.h>
-#include <media/OMXFenceParcelable.h>
#include <media/stagefright/foundation/ABase.h>
#include <media/stagefright/foundation/AHandlerReflector.h>
#include <media/stagefright/foundation/ALooper.h>
-
-#include <android/BnGraphicBufferSource.h>
-#include <android/BnOMXBufferSource.h>
-
-#include "IOmxNodeWrapper.h"
+#include <media/stagefright/gbs/ComponentWrapper.h>
namespace android {
@@ -41,7 +36,7 @@
struct FrameDropper;
/*
- * This class is used to feed OMX codecs from a Surface via BufferQueue or
+ * This class is used to feed codecs from a Surface via BufferQueue or
* HW producer.
*
* Instances of the class don't run on a dedicated thread. Instead,
@@ -49,7 +44,7 @@
*
* - Availability of a new frame of data from the BufferQueue (notified
* via the onFrameAvailable callback).
- * - The return of a codec buffer (via OnEmptyBufferDone).
+ * - The return of a codec buffer.
* - Application signaling end-of-stream.
* - Transition to or from "executing" state.
*
@@ -91,39 +86,37 @@
return mProducer;
}
- // OmxBufferSource interface
- // ------------------------------
-
- // This is called when OMX transitions to OMX_StateExecuting, which means
+ // This is called when component transitions to running state, which means
// we can start handing it buffers. If we already have buffers of data
// sitting in the BufferQueue, this will send them to the codec.
- Status onOmxExecuting();
+ Status start();
- // This is called when OMX transitions to OMX_StateIdle, indicating that
+ // This is called when component transitions to stopped, indicating that
// the codec is meant to return all buffers back to the client for them
// to be freed. Do NOT submit any more buffers to the component.
- Status onOmxIdle();
+ Status stop();
- // This is called when OMX transitions to OMX_StateLoaded, indicating that
+ // This is called when component transitions to released, indicating that
// we are shutting down.
- Status onOmxLoaded();
+ Status release();
// A "codec buffer", i.e. a buffer that can be used to pass data into
// the encoder, has been allocated. (This call does not call back into
- // OMXNodeInstance.)
+ // component.)
Status onInputBufferAdded(int32_t bufferId);
- // Called from OnEmptyBufferDone. If we have a BQ buffer available,
- // fill it with a new frame of data; otherwise, just mark it as available.
+ // Called when encoder is no longer using the buffer. If we have a BQ
+ // buffer available, fill it with a new frame of data; otherwise, just mark
+ // it as available.
Status onInputBufferEmptied(int32_t bufferId, int fenceFd);
// IGraphicBufferSource interface
// ------------------------------
- // Configure the buffer source to be used with an OMX node with the default
+ // Configure the buffer source to be used with a component with the default
// data space.
status_t configure(
- const sp<IOmxNodeWrapper> &omxNode,
+ const sp<ComponentWrapper> &component,
int32_t dataSpace,
int32_t bufferCount,
uint32_t frameWidth,
@@ -335,10 +328,10 @@
// called when the data space of the input buffer changes
void onDataspaceChanged_l(android_dataspace dataspace, android_pixel_format pixelFormat);
- // Pointer back to the Omx node that created us. We send buffers here.
- sp<IOmxNodeWrapper> mOMXNode;
+ // Pointer back to the component that created us. We send buffers here.
+ sp<ComponentWrapper> mComponent;
- // Set by omxExecuting() / omxIdling().
+ // Set by start() / stop().
bool mExecuting;
bool mSuspended;
diff --git a/media/libstagefright/gbs/tests/Android.bp b/media/libstagefright/gbs/tests/Android.bp
new file mode 100644
index 0000000..1463e73
--- /dev/null
+++ b/media/libstagefright/gbs/tests/Android.bp
@@ -0,0 +1,15 @@
+cc_test {
+ name: "FrameDropper_test",
+
+ srcs: ["FrameDropper_test.cpp"],
+
+ shared_libs: [
+ "libstagefright_gbs",
+ "libutils",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+}
diff --git a/media/libstagefright/omx/tests/FrameDropper_test.cpp b/media/libstagefright/gbs/tests/FrameDropper_test.cpp
similarity index 98%
rename from media/libstagefright/omx/tests/FrameDropper_test.cpp
rename to media/libstagefright/gbs/tests/FrameDropper_test.cpp
index a925da6..54a84d3 100644
--- a/media/libstagefright/omx/tests/FrameDropper_test.cpp
+++ b/media/libstagefright/gbs/tests/FrameDropper_test.cpp
@@ -20,7 +20,7 @@
#include <gtest/gtest.h>
-#include <media/stagefright/omx/FrameDropper.h>
+#include <media/stagefright/gbs/FrameDropper.h>
#include <media/stagefright/foundation/ADebug.h>
namespace android {
diff --git a/media/libstagefright/include/CCodecBufferChannel.h b/media/libstagefright/include/CCodecBufferChannel.h
index c5062d6..57a1374 100644
--- a/media/libstagefright/include/CCodecBufferChannel.h
+++ b/media/libstagefright/include/CCodecBufferChannel.h
@@ -27,6 +27,7 @@
#include <C2Component.h>
#include <media/stagefright/foundation/Mutexed.h>
+#include <media/stagefright/gbs/GraphicBufferSource.h>
#include <media/stagefright/CodecBase.h>
#include <media/ICrypto.h>
@@ -35,134 +36,9 @@
/**
* BufferChannelBase implementation for CCodec.
*/
-class CCodecBufferChannel : public BufferChannelBase {
+class CCodecBufferChannel
+ : public BufferChannelBase, public std::enable_shared_from_this<CCodecBufferChannel> {
public:
- /**
- * Base class for representation of buffers at one port.
- */
- class Buffers {
- public:
- Buffers() = default;
- virtual ~Buffers() = default;
-
- /**
- * Set format for MediaCodec-facing buffers.
- */
- inline void setFormat(const sp<AMessage> &format) { mFormat = format; }
-
- /**
- * Returns true if the buffers are operating under array mode.
- */
- virtual bool isArrayMode() { return false; }
-
- /**
- * Fills the vector with MediaCodecBuffer's if in array mode; otherwise,
- * no-op.
- */
- virtual void getArray(Vector<sp<MediaCodecBuffer>> *) {}
-
- protected:
- // Format to be used for creating MediaCodec-facing buffers.
- sp<AMessage> mFormat;
-
- private:
- DISALLOW_EVIL_CONSTRUCTORS(Buffers);
- };
-
- class InputBuffers : public Buffers {
- public:
- using Buffers::Buffers;
- virtual ~InputBuffers() = default;
-
- /**
- * Set a block pool to obtain input memory blocks.
- */
- inline void setPool(const std::shared_ptr<C2BlockPool> &pool) { mPool = pool; }
-
- /**
- * Get a new MediaCodecBuffer for input and its corresponding index.
- * Returns false if no new buffer can be obtained at the moment.
- */
- virtual bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) = 0;
-
- /**
- * Release the buffer obtained from requestNewBuffer() and get the
- * associated C2Buffer object back. Returns empty shared_ptr if the
- * buffer is not on file.
- */
- virtual std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) = 0;
-
- /**
- * Flush internal state. After this call, no index or buffer previously
- * returned from requestNewBuffer() is valid.
- */
- virtual void flush() = 0;
-
- /**
- * Return array-backed version of input buffers. The returned object
- * shall retain the internal state so that it will honor index and
- * buffer from previous calls of requestNewBuffer().
- */
- virtual std::unique_ptr<InputBuffers> toArrayMode() = 0;
-
- protected:
- // Pool to obtain blocks for input buffers.
- std::shared_ptr<C2BlockPool> mPool;
-
- private:
- DISALLOW_EVIL_CONSTRUCTORS(InputBuffers);
- };
-
- class OutputBuffers : public Buffers {
- public:
- using Buffers::Buffers;
- virtual ~OutputBuffers() = default;
-
- /**
- * Register output C2Buffer from the component and obtain corresponding
- * index and MediaCodecBuffer object. Returns false if registration
- * fails.
- */
- virtual bool registerBuffer(
- const std::shared_ptr<C2Buffer> &buffer,
- size_t *index,
- sp<MediaCodecBuffer> *codecBuffer) = 0;
-
- /**
- * Register codec specific data as a buffer to be consistent with
- * MediaCodec behavior.
- */
- virtual bool registerCsd(
- const C2StreamCsdInfo::output * /* csd */,
- size_t * /* index */,
- sp<MediaCodecBuffer> * /* codecBuffer */) {
- return false;
- }
-
- /**
- * Release the buffer obtained from registerBuffer() and get the
- * associated C2Buffer object back. Returns empty shared_ptr if the
- * buffer is not on file.
- */
- virtual std::shared_ptr<C2Buffer> releaseBuffer(const sp<MediaCodecBuffer> &buffer) = 0;
-
- /**
- * Flush internal state. After this call, no index or buffer previously
- * returned from registerBuffer() is valid.
- */
- virtual void flush(const std::list<std::unique_ptr<C2Work>> &flushedWork) = 0;
-
- /**
- * Return array-backed version of output buffers. The returned object
- * shall retain the internal state so that it will honor index and
- * buffer from previous calls of registerBuffer().
- */
- virtual std::unique_ptr<OutputBuffers> toArrayMode() = 0;
-
- private:
- DISALLOW_EVIL_CONSTRUCTORS(OutputBuffers);
- };
-
CCodecBufferChannel(const std::function<void(status_t, enum ActionCode)> &onError);
virtual ~CCodecBufferChannel();
@@ -186,23 +62,21 @@
// Methods below are interface for CCodec to use.
+ /**
+ * Set the component object for buffer processing.
+ */
void setComponent(const std::shared_ptr<C2Component> &component);
+
+ /**
+ * Set output graphic surface for rendering.
+ */
status_t setSurface(const sp<Surface> &surface);
/**
- * Set C2BlockPool for input buffers.
- *
- * TODO: start timestamp?
+ * Set GraphicBufferSource object from which the component extracts input
+ * buffers.
*/
- void setInputBufferAllocator(const sp<C2BlockPool> &inAlloc);
-
- /**
- * Set C2BlockPool for output buffers. This object shall never use the
- * allocator itself; it's just passed
- *
- * TODO: start timestamp?
- */
- void setOutputBufferAllocator(const sp<C2BlockPool> &outAlloc);
+ status_t setGraphicBufferSource(const sp<GraphicBufferSource> &source);
/**
* Start queueing buffers to the component. This object should never queue
@@ -219,11 +93,17 @@
void flush(const std::list<std::unique_ptr<C2Work>> &flushedWork);
/**
- * Notify MediaCodec about work done.
+ * Notify input client about work done.
*
- * @param workItems finished work items.
+ * @param workItems finished work item.
*/
- void onWorkDone(std::vector<std::unique_ptr<C2Work>> workItems);
+ void onWorkDone(const std::unique_ptr<C2Work> &work);
+
+ // Internal classes
+ class Buffers;
+ class InputBuffers;
+ class OutputBuffers;
+ class InputBufferClient;
private:
class QueueGuard;
@@ -276,12 +156,17 @@
bool mRunning;
};
+ class C2ComponentWrapper;
+
+ void feedInputBufferIfAvailable();
+
QueueSync mSync;
sp<MemoryDealer> mDealer;
sp<IMemory> mDecryptDestination;
int32_t mHeapSeqNum;
std::shared_ptr<C2Component> mComponent;
+ std::shared_ptr<InputBufferClient> mInputClient;
std::function<void(status_t, enum ActionCode)> mOnError;
std::shared_ptr<C2BlockPool> mInputAllocator;
QueueSync mQueueSync;
diff --git a/media/libstagefright/include/FrameDecoder.h b/media/libstagefright/include/FrameDecoder.h
index d7c074c..6f07838 100644
--- a/media/libstagefright/include/FrameDecoder.h
+++ b/media/libstagefright/include/FrameDecoder.h
@@ -27,6 +27,7 @@
struct AMessage;
class MediaCodecBuffer;
+class IMediaSource;
class VideoFrame;
struct FrameDecoder {
diff --git a/media/libstagefright/include/HTTPBase.h b/media/libstagefright/include/HTTPBase.h
index 26d7e8a..a924197 100644
--- a/media/libstagefright/include/HTTPBase.h
+++ b/media/libstagefright/include/HTTPBase.h
@@ -21,6 +21,7 @@
#include <media/DataSource.h>
#include <media/stagefright/foundation/ABase.h>
#include <media/stagefright/MediaErrors.h>
+#include <utils/List.h>
#include <utils/threads.h>
namespace android {
diff --git a/media/libstagefright/include/media/stagefright/ACodec.h b/media/libstagefright/include/media/stagefright/ACodec.h
index 3196b10..fa22003 100644
--- a/media/libstagefright/include/media/stagefright/ACodec.h
+++ b/media/libstagefright/include/media/stagefright/ACodec.h
@@ -495,7 +495,7 @@
status_t verifySupportForProfileAndLevel(int32_t profile, int32_t level);
status_t configureBitrate(
- int32_t bitrate, OMX_VIDEO_CONTROLRATETYPE bitrateMode);
+ OMX_VIDEO_CONTROLRATETYPE bitrateMode, int32_t bitrate, int32_t quality = 0);
void configureEncoderLatency(const sp<AMessage> &msg);
status_t setupErrorCorrectionParameters();
diff --git a/media/libstagefright/include/media/stagefright/CCodec.h b/media/libstagefright/include/media/stagefright/CCodec.h
index 3e24bbe..c689761 100644
--- a/media/libstagefright/include/media/stagefright/CCodec.h
+++ b/media/libstagefright/include/media/stagefright/CCodec.h
@@ -24,6 +24,7 @@
#include <android/native_window.h>
#include <media/hardware/MetadataBufferType.h>
#include <media/stagefright/foundation/Mutexed.h>
+#include <media/stagefright/gbs/GraphicBufferSource.h>
#include <media/stagefright/CodecBase.h>
#include <media/stagefright/FrameRenderTracker.h>
#include <media/stagefright/MediaDefs.h>
@@ -58,6 +59,7 @@
virtual void signalRequestIDRFrame() override;
void initiateReleaseIfStuck();
+ void onWorkDone(std::vector<std::unique_ptr<C2Work>> &workItems);
protected:
virtual ~CCodec();
@@ -77,6 +79,10 @@
void flush();
void release(bool sendCallback);
+ void createInputSurface();
+ void setInputSurface(const sp<PersistentSurface> &surface);
+ status_t setupInputSurface(const sp<GraphicBufferSource> &source);
+
void setDeadline(const TimePoint &deadline);
enum {
@@ -86,6 +92,9 @@
kWhatFlush,
kWhatStop,
kWhatRelease,
+ kWhatCreateInputSurface,
+ kWhatSetInputSurface,
+ kWhatWorkDone,
};
enum {
@@ -104,14 +113,17 @@
struct State {
inline State() : mState(RELEASED) {}
+ inline int get() const { return mState; }
+ inline void set(int newState) { mState = newState; }
+ std::shared_ptr<C2Component> comp;
+ private:
int mState;
- std::shared_ptr<C2Component> mComp;
};
struct Formats {
- sp<AMessage> mInputFormat;
- sp<AMessage> mOutputFormat;
+ sp<AMessage> inputFormat;
+ sp<AMessage> outputFormat;
};
Mutexed<State> mState;
@@ -119,6 +131,7 @@
std::shared_ptr<C2Component::Listener> mListener;
Mutexed<TimePoint> mDeadline;
Mutexed<Formats> mFormats;
+ Mutexed<std::list<std::unique_ptr<C2Work>>> mWorkDoneQueue;
DISALLOW_EVIL_CONSTRUCTORS(CCodec);
};
diff --git a/media/libstagefright/include/media/stagefright/CallbackMediaSource.h b/media/libstagefright/include/media/stagefright/CallbackMediaSource.h
index 3459de1..944d951 100644
--- a/media/libstagefright/include/media/stagefright/CallbackMediaSource.h
+++ b/media/libstagefright/include/media/stagefright/CallbackMediaSource.h
@@ -22,6 +22,8 @@
namespace android {
+class IMediaSource;
+
// A stagefright MediaSource that wraps a binder IMediaSource.
class CallbackMediaSource : public MediaSource {
public:
diff --git a/media/libstagefright/include/media/stagefright/ColorConverter.h b/media/libstagefright/include/media/stagefright/ColorConverter.h
index f6bd353..a6c8981 100644
--- a/media/libstagefright/include/media/stagefright/ColorConverter.h
+++ b/media/libstagefright/include/media/stagefright/ColorConverter.h
@@ -33,6 +33,8 @@
bool isValid() const;
+ bool isDstRGB() const;
+
status_t convert(
const void *srcBits,
size_t srcWidth, size_t srcHeight,
diff --git a/media/libstagefright/include/media/stagefright/InterfaceUtils.h b/media/libstagefright/include/media/stagefright/InterfaceUtils.h
index 224c1f1..ceeaf31 100644
--- a/media/libstagefright/include/media/stagefright/InterfaceUtils.h
+++ b/media/libstagefright/include/media/stagefright/InterfaceUtils.h
@@ -17,13 +17,22 @@
#ifndef INTERFACE_UTILS_H_
#define INTERFACE_UTILS_H_
+#include <utils/RefBase.h>
#include <media/MediaExtractor.h>
+#include <media/stagefright/RemoteMediaExtractor.h>
#include <media/MediaSource.h>
#include <media/IMediaExtractor.h>
#include <media/IMediaSource.h>
namespace android {
+class DataSource;
+class MediaExtractor;
+struct MediaSource;
+class IDataSource;
+class IMediaExtractor;
+class IMediaSource;
+
// Creates a DataSource which wraps the given IDataSource object.
sp<DataSource> CreateDataSourceFromIDataSource(const sp<IDataSource> &source);
@@ -32,14 +41,15 @@
// Creates an IMediaExtractor wrapper to the given MediaExtractor.
sp<IMediaExtractor> CreateIMediaExtractorFromMediaExtractor(
- const sp<MediaExtractor> &extractor, const sp<RefBase> &plugin);
+ MediaExtractor *extractor, const sp<RefBase> &plugin);
// Creates a MediaSource which wraps the given IMediaSource object.
sp<MediaSource> CreateMediaSourceFromIMediaSource(const sp<IMediaSource> &source);
// Creates an IMediaSource wrapper to the given MediaSource.
-sp<IMediaSource> CreateIMediaSourceFromMediaSource(
- const sp<MediaSource> &source, const sp<RefBase> &plugin);
+sp<IMediaSource> CreateIMediaSourceFromMediaSourceBase(
+ const sp<RemoteMediaExtractor> &extractor,
+ MediaSourceBase *source, const sp<RefBase> &plugin);
} // namespace android
diff --git a/media/libstagefright/include/media/stagefright/MediaBuffer.h b/media/libstagefright/include/media/stagefright/MediaBuffer.h
new file mode 120000
index 0000000..1d49c1a
--- /dev/null
+++ b/media/libstagefright/include/media/stagefright/MediaBuffer.h
@@ -0,0 +1 @@
+../../../../libmediaextractor/include/media/stagefright/MediaBuffer.h
\ No newline at end of file
diff --git a/media/libstagefright/include/media/stagefright/MediaBufferGroup.h b/media/libstagefright/include/media/stagefright/MediaBufferGroup.h
new file mode 120000
index 0000000..009b3d9
--- /dev/null
+++ b/media/libstagefright/include/media/stagefright/MediaBufferGroup.h
@@ -0,0 +1 @@
+../../../../libmediaextractor/include/media/stagefright/MediaBufferGroup.h
\ No newline at end of file
diff --git a/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h b/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h
index e5c67e1..4610359 100644
--- a/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h
+++ b/media/libstagefright/include/media/stagefright/MediaExtractorFactory.h
@@ -22,6 +22,7 @@
#include <media/IMediaExtractor.h>
#include <media/MediaExtractor.h>
+#include <utils/List.h>
namespace android {
diff --git a/media/libstagefright/include/media/stagefright/MetaData.h b/media/libstagefright/include/media/stagefright/MetaData.h
index 3438c56..5959e86 100644
--- a/media/libstagefright/include/media/stagefright/MetaData.h
+++ b/media/libstagefright/include/media/stagefright/MetaData.h
@@ -24,7 +24,6 @@
#include <binder/Parcel.h>
#include <utils/RefBase.h>
-#include <utils/KeyedVector.h>
#include <utils/String8.h>
namespace android {
@@ -229,7 +228,7 @@
kTypeD263 = 'd263',
};
-class MetaData : public RefBase {
+class MetaData final : public RefBase {
public:
MetaData();
MetaData(const MetaData &from);
@@ -279,59 +278,22 @@
String8 toString() const;
void dumpToLog() const;
- status_t writeToParcel(Parcel &parcel);
- status_t updateFromParcel(const Parcel &parcel);
- static sp<MetaData> createFromParcel(const Parcel &parcel);
-
protected:
virtual ~MetaData();
private:
- struct typed_data {
- typed_data();
- ~typed_data();
+ friend class BpMediaSource;
+ friend class BnMediaSource;
+ friend class BpMediaExtractor;
+ friend class BnMediaExtractor;
- typed_data(const MetaData::typed_data &);
- typed_data &operator=(const MetaData::typed_data &);
-
- void clear();
- void setData(uint32_t type, const void *data, size_t size);
- void getData(uint32_t *type, const void **data, size_t *size) const;
- // may include hexdump of binary data if verbose=true
- String8 asString(bool verbose) const;
-
- private:
- uint32_t mType;
- size_t mSize;
-
- union {
- void *ext_data;
- float reservoir;
- } u;
-
- bool usesReservoir() const {
- return mSize <= sizeof(u.reservoir);
- }
-
- void *allocateStorage(size_t size);
- void freeStorage();
-
- void *storage() {
- return usesReservoir() ? &u.reservoir : u.ext_data;
- }
-
- const void *storage() const {
- return usesReservoir() ? &u.reservoir : u.ext_data;
- }
- };
-
- struct Rect {
- int32_t mLeft, mTop, mRight, mBottom;
- };
-
- KeyedVector<uint32_t, typed_data> mItems;
-
- // MetaData &operator=(const MetaData &);
+ status_t writeToParcel(Parcel &parcel);
+ status_t updateFromParcel(const Parcel &parcel);
+ static sp<MetaData> createFromParcel(const Parcel &parcel);
+ struct typed_data;
+ struct Rect;
+ struct MetaDataInternal;
+ MetaDataInternal *mInternalData;
};
} // namespace android
diff --git a/media/libstagefright/include/media/stagefright/NuMediaExtractor.h b/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
index eed0f05..6a2e39b 100644
--- a/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
+++ b/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
@@ -85,6 +85,7 @@
// readSampleData() reads the sample with the lowest timestamp.
status_t readSampleData(const sp<ABuffer> &buffer);
+ status_t getSampleSize(size_t *sampleSize);
status_t getSampleTrackIndex(size_t *trackIndex);
status_t getSampleTime(int64_t *sampleTimeUs);
status_t getSampleMeta(sp<MetaData> *sampleMeta);
diff --git a/media/libstagefright/include/media/stagefright/RemoteMediaExtractor.h b/media/libstagefright/include/media/stagefright/RemoteMediaExtractor.h
index 2bd71ee..78f3a84 100644
--- a/media/libstagefright/include/media/stagefright/RemoteMediaExtractor.h
+++ b/media/libstagefright/include/media/stagefright/RemoteMediaExtractor.h
@@ -19,6 +19,7 @@
#include <media/IMediaExtractor.h>
#include <media/MediaExtractor.h>
+#include <media/stagefright/foundation/ABase.h>
namespace android {
@@ -27,7 +28,7 @@
// IMediaExtractor wrapper to the MediaExtractor.
class RemoteMediaExtractor : public BnMediaExtractor {
public:
- static sp<IMediaExtractor> wrap(const sp<MediaExtractor> &extractor, const sp<RefBase> &plugin);
+ static sp<IMediaExtractor> wrap(MediaExtractor *extractor, const sp<RefBase> &plugin);
virtual ~RemoteMediaExtractor();
virtual size_t countTracks();
@@ -43,12 +44,12 @@
virtual void release();
private:
- sp<MediaExtractor> mExtractor;
+ MediaExtractor *mExtractor;
sp<RefBase> mExtractorPlugin;
MediaAnalyticsItem *mAnalyticsItem;
- explicit RemoteMediaExtractor(const sp<MediaExtractor> &extractor, const sp<RefBase> &plugin);
+ explicit RemoteMediaExtractor(MediaExtractor *extractor, const sp<RefBase> &plugin);
DISALLOW_EVIL_CONSTRUCTORS(RemoteMediaExtractor);
};
diff --git a/media/libstagefright/include/media/stagefright/RemoteMediaSource.h b/media/libstagefright/include/media/stagefright/RemoteMediaSource.h
index cb222cc..d1afa6a 100644
--- a/media/libstagefright/include/media/stagefright/RemoteMediaSource.h
+++ b/media/libstagefright/include/media/stagefright/RemoteMediaSource.h
@@ -26,7 +26,10 @@
// IMediaSrouce wrapper to the MediaSource.
class RemoteMediaSource : public BnMediaSource {
public:
- static sp<IMediaSource> wrap(const sp<MediaSource> &source, const sp<RefBase> &plugin);
+ static sp<IMediaSource> wrap(
+ const sp<RemoteMediaExtractor> &extractor,
+ MediaSourceBase *source,
+ const sp<RefBase> &plugin);
virtual ~RemoteMediaSource();
virtual status_t start(MetaData *params = NULL);
virtual status_t stop();
@@ -38,10 +41,14 @@
virtual status_t setStopTimeUs(int64_t stopTimeUs);
private:
- sp<MediaSource> mSource;
+ sp<RemoteMediaExtractor> mExtractor;
+ MediaSourceBase *mSource;
sp<RefBase> mExtractorPlugin;
- explicit RemoteMediaSource(const sp<MediaSource> &source, const sp<RefBase> &plugin);
+ explicit RemoteMediaSource(
+ const sp<RemoteMediaExtractor> &extractor,
+ MediaSourceBase *source,
+ const sp<RefBase> &plugin);
DISALLOW_EVIL_CONSTRUCTORS(RemoteMediaSource);
};
diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp
index 464ee90..9b12b2d 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.cpp
+++ b/media/libstagefright/mpeg2ts/ATSParser.cpp
@@ -23,6 +23,8 @@
#include "ESQueue.h"
#include <android/hardware/cas/native/1.0/IDescrambler.h>
+#include <binder/IMemory.h>
+#include <binder/MemoryDealer.h>
#include <cutils/native_handle.h>
#include <media/stagefright/foundation/ABitReader.h>
#include <media/stagefright/foundation/ABuffer.h>
@@ -77,7 +79,7 @@
void signalEOS(status_t finalResult);
- sp<MediaSource> getSource(SourceType type);
+ sp<AnotherPacketSource> getSource(SourceType type);
bool hasSource(SourceType type) const;
int64_t convertPTSToTimestamp(uint64_t PTS);
@@ -170,7 +172,7 @@
void signalEOS(status_t finalResult);
SourceType getSourceType();
- sp<MediaSource> getSource(SourceType type);
+ sp<AnotherPacketSource> getSource(SourceType type);
bool isAudio() const;
bool isVideo() const;
@@ -274,7 +276,7 @@
ATSParser::SyncEvent::SyncEvent(off64_t offset)
: mHasReturnedData(false), mOffset(offset), mTimeUs(0) {}
-void ATSParser::SyncEvent::init(off64_t offset, const sp<MediaSource> &source,
+void ATSParser::SyncEvent::init(off64_t offset, const sp<AnotherPacketSource> &source,
int64_t timeUs, SourceType type) {
mHasReturnedData = true;
mOffset = offset;
@@ -641,9 +643,9 @@
return mLastRecoveredPTS;
}
-sp<MediaSource> ATSParser::Program::getSource(SourceType type) {
+sp<AnotherPacketSource> ATSParser::Program::getSource(SourceType type) {
for (size_t i = 0; i < mStreams.size(); ++i) {
- sp<MediaSource> source = mStreams.editValueAt(i)->getSource(type);
+ sp<AnotherPacketSource> source = mStreams.editValueAt(i)->getSource(type);
if (source != NULL) {
return source;
}
@@ -1607,7 +1609,7 @@
return NUM_SOURCE_TYPES;
}
-sp<MediaSource> ATSParser::Stream::getSource(SourceType type) {
+sp<AnotherPacketSource> ATSParser::Stream::getSource(SourceType type) {
switch (type) {
case VIDEO:
{
@@ -2042,11 +2044,11 @@
return err;
}
-sp<MediaSource> ATSParser::getSource(SourceType type) {
- sp<MediaSource> firstSourceFound;
+sp<AnotherPacketSource> ATSParser::getSource(SourceType type) {
+ sp<AnotherPacketSource> firstSourceFound;
for (size_t i = 0; i < mPrograms.size(); ++i) {
const sp<Program> &program = mPrograms.editItemAt(i);
- sp<MediaSource> source = program->getSource(type);
+ sp<AnotherPacketSource> source = program->getSource(type);
if (source == NULL) {
continue;
}
diff --git a/media/libstagefright/mpeg2ts/ATSParser.h b/media/libstagefright/mpeg2ts/ATSParser.h
index 6079afc..45ca06b 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.h
+++ b/media/libstagefright/mpeg2ts/ATSParser.h
@@ -81,13 +81,13 @@
struct SyncEvent {
explicit SyncEvent(off64_t offset);
- void init(off64_t offset, const sp<MediaSource> &source,
+ void init(off64_t offset, const sp<AnotherPacketSource> &source,
int64_t timeUs, SourceType type);
bool hasReturnedData() const { return mHasReturnedData; }
void reset();
off64_t getOffset() const { return mOffset; }
- const sp<MediaSource> &getMediaSource() const { return mMediaSource; }
+ const sp<AnotherPacketSource> &getMediaSource() const { return mMediaSource; }
int64_t getTimeUs() const { return mTimeUs; }
SourceType getType() const { return mType; }
@@ -100,7 +100,7 @@
*/
off64_t mOffset;
/* The media source object for this event. */
- sp<MediaSource> mMediaSource;
+ sp<AnotherPacketSource> mMediaSource;
/* The timestamp of the sync frame. */
int64_t mTimeUs;
SourceType mType;
@@ -126,7 +126,7 @@
void signalEOS(status_t finalResult);
- sp<MediaSource> getSource(SourceType type);
+ sp<AnotherPacketSource> getSource(SourceType type);
bool hasSource(SourceType type) const;
bool PTSTimeDeltaEstablished();
diff --git a/media/libstagefright/omx/1.0/Omx.cpp b/media/libstagefright/omx/1.0/Omx.cpp
index fe50656..4e2d398 100644
--- a/media/libstagefright/omx/1.0/Omx.cpp
+++ b/media/libstagefright/omx/1.0/Omx.cpp
@@ -24,7 +24,7 @@
#include <media/stagefright/omx/OMXUtils.h>
#include <media/stagefright/omx/OMXMaster.h>
-#include <media/stagefright/omx/GraphicBufferSource.h>
+#include <media/stagefright/omx/OmxGraphicBufferSource.h>
#include <media/stagefright/omx/1.0/WOmxNode.h>
#include <media/stagefright/omx/1.0/WOmxObserver.h>
@@ -148,7 +148,7 @@
Return<void> Omx::createInputSurface(createInputSurface_cb _hidl_cb) {
sp<::android::IGraphicBufferProducer> bufferProducer;
- sp<GraphicBufferSource> graphicBufferSource = new GraphicBufferSource();
+ sp<OmxGraphicBufferSource> graphicBufferSource = new OmxGraphicBufferSource();
status_t err = graphicBufferSource->initCheck();
if (err != OK) {
LOG(ERROR) << "Failed to create persistent input surface: "
diff --git a/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp b/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
index 3201c32..ed272bb 100644
--- a/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
+++ b/media/libstagefright/omx/1.0/WGraphicBufferSource.cpp
@@ -79,9 +79,9 @@
};
struct TWGraphicBufferSource::TWOmxBufferSource : public IOmxBufferSource {
- sp<GraphicBufferSource> mSource;
+ sp<OmxGraphicBufferSource> mSource;
- TWOmxBufferSource(const sp<GraphicBufferSource> &source): mSource(source) {
+ TWOmxBufferSource(const sp<OmxGraphicBufferSource> &source): mSource(source) {
}
Return<void> onOmxExecuting() override {
@@ -115,7 +115,7 @@
// TWGraphicBufferSource
TWGraphicBufferSource::TWGraphicBufferSource(
- sp<GraphicBufferSource> const& base) :
+ sp<OmxGraphicBufferSource> const& base) :
mBase(base),
mOmxBufferSource(new TWOmxBufferSource(base)) {
}
diff --git a/media/libstagefright/omx/Android.bp b/media/libstagefright/omx/Android.bp
index 8539864..306a8eb 100644
--- a/media/libstagefright/omx/Android.bp
+++ b/media/libstagefright/omx/Android.bp
@@ -6,12 +6,11 @@
},
srcs: [
- "FrameDropper.cpp",
- "GraphicBufferSource.cpp",
"BWGraphicBufferSource.cpp",
"OMXMaster.cpp",
"OMXNodeInstance.cpp",
"OMXUtils.cpp",
+ "OmxGraphicBufferSource.cpp",
"SimpleSoftOMXComponent.cpp",
"SoftOMXComponent.cpp",
"SoftOMXPlugin.cpp",
@@ -49,6 +48,7 @@
"libgui",
"libcutils",
"libstagefright_foundation",
+ "libstagefright_gbs",
"libstagefright_xmlparser",
"libdl",
"libhidlbase",
diff --git a/media/libstagefright/omx/BWGraphicBufferSource.cpp b/media/libstagefright/omx/BWGraphicBufferSource.cpp
index 94ef598..fa30a46 100644
--- a/media/libstagefright/omx/BWGraphicBufferSource.cpp
+++ b/media/libstagefright/omx/BWGraphicBufferSource.cpp
@@ -55,9 +55,9 @@
};
struct BWGraphicBufferSource::BWOMXBufferSource : public BnOMXBufferSource {
- sp<GraphicBufferSource> mSource;
+ sp<OmxGraphicBufferSource> mSource;
- BWOMXBufferSource(const sp<GraphicBufferSource> &source): mSource(source) {
+ BWOMXBufferSource(const sp<OmxGraphicBufferSource> &source): mSource(source) {
}
Status onOmxExecuting() override {
@@ -83,7 +83,7 @@
};
BWGraphicBufferSource::BWGraphicBufferSource(
- sp<GraphicBufferSource> const& base) :
+ sp<OmxGraphicBufferSource> const& base) :
mBase(base),
mOMXBufferSource(new BWOMXBufferSource(base)) {
}
diff --git a/media/libstagefright/omx/OmxGraphicBufferSource.cpp b/media/libstagefright/omx/OmxGraphicBufferSource.cpp
new file mode 100644
index 0000000..83feac8
--- /dev/null
+++ b/media/libstagefright/omx/OmxGraphicBufferSource.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2018 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 <inttypes.h>
+
+#define LOG_TAG "OmxGraphicBufferSource"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <media/stagefright/gbs/ComponentWrapper.h>
+#include <media/stagefright/gbs/GraphicBufferSource.h>
+#include <media/stagefright/omx/OmxGraphicBufferSource.h>
+
+namespace android {
+
+namespace {
+
+class OmxComponentWrapper : public ComponentWrapper {
+public:
+ explicit OmxComponentWrapper(const sp<IOmxNodeWrapper> &node)
+ : mOmxNode(node) {}
+ virtual ~OmxComponentWrapper() = default;
+
+ status_t submitBuffer(
+ int32_t bufferId, const sp<GraphicBuffer> &buffer,
+ int64_t timestamp, int fenceFd) override {
+ return mOmxNode->emptyBuffer(
+ bufferId, OMX_BUFFERFLAG_ENDOFFRAME, buffer, timestamp, fenceFd);
+ }
+
+ status_t submitEos(int32_t bufferId) override {
+ return mOmxNode->emptyBuffer(bufferId, OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_EOS);
+ }
+
+ void dispatchDataSpaceChanged(
+ int32_t dataSpace, int32_t aspects, int32_t pixelFormat) override {
+ mOmxNode->dispatchDataSpaceChanged(dataSpace, aspects, pixelFormat);
+ }
+
+private:
+ sp<IOmxNodeWrapper> mOmxNode;
+
+ DISALLOW_EVIL_CONSTRUCTORS(OmxComponentWrapper);
+};
+
+} // namespace
+
+Status OmxGraphicBufferSource::onOmxExecuting() {
+ return start();
+}
+
+Status OmxGraphicBufferSource::onOmxIdle() {
+ return stop();
+}
+
+Status OmxGraphicBufferSource::onOmxLoaded(){
+ return release();
+}
+
+status_t OmxGraphicBufferSource::configure(
+ const sp<IOmxNodeWrapper>& omxNode,
+ int32_t dataSpace,
+ int32_t bufferCount,
+ uint32_t frameWidth,
+ uint32_t frameHeight,
+ uint32_t consumerUsage) {
+ if (omxNode == NULL) {
+ return BAD_VALUE;
+ }
+
+ return GraphicBufferSource::configure(
+ new OmxComponentWrapper(omxNode), dataSpace, bufferCount,
+ frameWidth, frameHeight, consumerUsage);
+}
+
+} // namespace android
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/1.0/WGraphicBufferSource.h b/media/libstagefright/omx/include/media/stagefright/omx/1.0/WGraphicBufferSource.h
index b9f22ab..4e56c98 100644
--- a/media/libstagefright/omx/include/media/stagefright/omx/1.0/WGraphicBufferSource.h
+++ b/media/libstagefright/omx/include/media/stagefright/omx/1.0/WGraphicBufferSource.h
@@ -28,7 +28,7 @@
#include <android/BnGraphicBufferSource.h>
-#include <media/stagefright/omx/GraphicBufferSource.h>
+#include <media/stagefright/omx/OmxGraphicBufferSource.h>
namespace android {
namespace hardware {
@@ -37,7 +37,7 @@
namespace V1_0 {
namespace implementation {
-using ::android::GraphicBufferSource;
+using ::android::OmxGraphicBufferSource;
using ::android::hardware::graphics::common::V1_0::Dataspace;
using ::android::hardware::media::omx::V1_0::ColorAspects;
using ::android::hardware::media::omx::V1_0::IGraphicBufferSource;
@@ -69,10 +69,10 @@
struct TWGraphicBufferSource : public TGraphicBufferSource {
struct TWOmxNodeWrapper;
struct TWOmxBufferSource;
- sp<GraphicBufferSource> mBase;
+ sp<OmxGraphicBufferSource> mBase;
sp<IOmxBufferSource> mOmxBufferSource;
- TWGraphicBufferSource(sp<GraphicBufferSource> const& base);
+ TWGraphicBufferSource(sp<OmxGraphicBufferSource> const& base);
Return<Status> configure(
const sp<IOmxNode>& omxNode, Dataspace dataspace) override;
Return<Status> setSuspend(bool suspend, int64_t timeUs) override;
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/BWGraphicBufferSource.h b/media/libstagefright/omx/include/media/stagefright/omx/BWGraphicBufferSource.h
index 0f78eb6..0efff22 100644
--- a/media/libstagefright/omx/include/media/stagefright/omx/BWGraphicBufferSource.h
+++ b/media/libstagefright/omx/include/media/stagefright/omx/BWGraphicBufferSource.h
@@ -23,14 +23,14 @@
#include <android/BnOMXBufferSource.h>
#include <media/IOMX.h>
-#include "GraphicBufferSource.h"
+#include "OmxGraphicBufferSource.h"
#include "IOmxNodeWrapper.h"
namespace android {
using ::android::binder::Status;
using ::android::BnGraphicBufferSource;
-using ::android::GraphicBufferSource;
+using ::android::OmxGraphicBufferSource;
using ::android::IOMXNode;
using ::android::sp;
@@ -38,10 +38,10 @@
struct BWOMXBufferSource;
struct BWOmxNodeWrapper;
- sp<GraphicBufferSource> mBase;
+ sp<OmxGraphicBufferSource> mBase;
sp<IOMXBufferSource> mOMXBufferSource;
- BWGraphicBufferSource(sp<GraphicBufferSource> const &base);
+ BWGraphicBufferSource(sp<OmxGraphicBufferSource> const &base);
Status configure(
const sp<IOMXNode>& omxNode, int32_t dataSpace) override;
diff --git a/media/libstagefright/omx/include/media/stagefright/omx/OmxGraphicBufferSource.h b/media/libstagefright/omx/include/media/stagefright/omx/OmxGraphicBufferSource.h
new file mode 100644
index 0000000..4b0f3d2
--- /dev/null
+++ b/media/libstagefright/omx/include/media/stagefright/omx/OmxGraphicBufferSource.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 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 OMX_GRAPHIC_BUFFER_SOURCE_H_
+
+#define OMX_GRAPHIC_BUFFER_SOURCE_H_
+
+#include <media/stagefright/gbs/GraphicBufferSource.h>
+#include <media/stagefright/foundation/ABase.h>
+
+#include <android/BnGraphicBufferSource.h>
+#include <android/BnOMXBufferSource.h>
+
+#include "IOmxNodeWrapper.h"
+
+namespace android {
+
+using ::android::binder::Status;
+
+/*
+ * This class is used to feed OMX codecs from a Surface via BufferQueue or
+ * HW producer.
+ *
+ * See media/stagefright/gbs/GraphicBufferSource.h for documentation.
+ */
+class OmxGraphicBufferSource : public GraphicBufferSource {
+public:
+ OmxGraphicBufferSource() = default;
+ virtual ~OmxGraphicBufferSource() = default;
+
+ // OmxBufferSource interface
+ // ------------------------------
+
+ // This is called when OMX transitions to OMX_StateExecuting, which means
+ // we can start handing it buffers. If we already have buffers of data
+ // sitting in the BufferQueue, this will send them to the codec.
+ Status onOmxExecuting();
+
+ // This is called when OMX transitions to OMX_StateIdle, indicating that
+ // the codec is meant to return all buffers back to the client for them
+ // to be freed. Do NOT submit any more buffers to the component.
+ Status onOmxIdle();
+
+ // This is called when OMX transitions to OMX_StateLoaded, indicating that
+ // we are shutting down.
+ Status onOmxLoaded();
+
+ // Rest of the interface in GraphicBufferSource.
+
+ // IGraphicBufferSource interface
+ // ------------------------------
+
+ // Configure the buffer source to be used with an OMX node with the default
+ // data space.
+ status_t configure(
+ const sp<IOmxNodeWrapper> &omxNode,
+ int32_t dataSpace,
+ int32_t bufferCount,
+ uint32_t frameWidth,
+ uint32_t frameHeight,
+ uint32_t consumerUsage);
+
+ // Rest of the interface in GraphicBufferSource.
+
+private:
+ DISALLOW_EVIL_CONSTRUCTORS(OmxGraphicBufferSource);
+};
+
+} // namespace android
+
+#endif // OMX_GRAPHIC_BUFFER_SOURCE_H_
diff --git a/media/libstagefright/omx/tests/Android.bp b/media/libstagefright/omx/tests/Android.bp
index 999d9d4..3b521ab 100644
--- a/media/libstagefright/omx/tests/Android.bp
+++ b/media/libstagefright/omx/tests/Android.bp
@@ -34,21 +34,3 @@
compile_multilib: "32",
}
-
-cc_test {
- name: "FrameDropper_test",
-
- srcs: ["FrameDropper_test.cpp"],
-
- shared_libs: [
- "libstagefright_omx",
- "libutils",
- ],
-
- include_dirs: ["frameworks/av/media/libstagefright/omx"],
-
- cflags: [
- "-Werror",
- "-Wall",
- ],
-}
diff --git a/media/libstagefright/tests/Android.bp b/media/libstagefright/tests/Android.bp
index 35119c2..e67a949 100644
--- a/media/libstagefright/tests/Android.bp
+++ b/media/libstagefright/tests/Android.bp
@@ -15,6 +15,7 @@
"libcutils",
"libgui",
"libmedia",
+ "libmediaextractor",
"libstagefright",
"libstagefright_foundation",
"libstagefright_omx",
diff --git a/media/ndk/Android.bp b/media/ndk/Android.bp
index cea2f9e..ca691f7 100644
--- a/media/ndk/Android.bp
+++ b/media/ndk/Android.bp
@@ -37,6 +37,7 @@
srcs: [
"NdkMediaCodec.cpp",
"NdkMediaCrypto.cpp",
+ "NdkMediaDataSource.cpp",
"NdkMediaExtractor.cpp",
"NdkMediaFormat.cpp",
"NdkMediaMuxer.cpp",
diff --git a/media/ndk/NdkMediaDataSource.cpp b/media/ndk/NdkMediaDataSource.cpp
new file mode 100644
index 0000000..0cae6f4
--- /dev/null
+++ b/media/ndk/NdkMediaDataSource.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2018 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 "NdkMediaDataSource"
+
+#include "NdkMediaDataSourcePriv.h"
+
+#include <inttypes.h>
+#include <jni.h>
+#include <unistd.h>
+
+#include <binder/IServiceManager.h>
+#include <cutils/properties.h>
+#include <utils/Log.h>
+#include <utils/StrongPointer.h>
+#include <media/NdkMediaError.h>
+#include <media/NdkMediaDataSource.h>
+#include <media/stagefright/InterfaceUtils.h>
+
+#include "../../libstagefright/include/HTTPBase.h"
+#include "../../libstagefright/include/NuCachedSource2.h"
+
+using namespace android;
+
+struct AMediaDataSource {
+ void *userdata;
+ AMediaDataSourceReadAt readAt;
+ AMediaDataSourceGetSize getSize;
+};
+
+NdkDataSource::NdkDataSource(AMediaDataSource *dataSource)
+ : mDataSource(dataSource) {
+}
+
+status_t NdkDataSource::initCheck() const {
+ return OK;
+}
+
+ssize_t NdkDataSource::readAt(off64_t offset, void *data, size_t size) {
+ Mutex::Autolock l(mLock);
+ if (mDataSource->getSize == NULL || mDataSource->userdata == NULL) {
+ return -1;
+ }
+ return mDataSource->readAt(mDataSource->userdata, offset, data, size);
+}
+
+status_t NdkDataSource::getSize(off64_t *size) {
+ Mutex::Autolock l(mLock);
+ if (mDataSource->getSize == NULL || mDataSource->userdata == NULL) {
+ return NO_INIT;
+ }
+ if (size != NULL) {
+ *size = mDataSource->getSize(mDataSource->userdata);
+ }
+ return OK;
+}
+
+String8 NdkDataSource::toString() {
+ return String8::format("NdkDataSource(pid %d, uid %d)", getpid(), getuid());
+}
+
+String8 NdkDataSource::getMIMEType() const {
+ return String8("application/octet-stream");
+}
+
+extern "C" {
+
+EXPORT
+AMediaDataSource* AMediaDataSource_new() {
+ AMediaDataSource *mSource = new AMediaDataSource();
+ mSource->userdata = NULL;
+ mSource->readAt = NULL;
+ mSource->getSize = NULL;
+ return mSource;
+}
+
+EXPORT
+void AMediaDataSource_delete(AMediaDataSource *mSource) {
+ ALOGV("dtor");
+ if (mSource != NULL) {
+ delete mSource;
+ }
+}
+
+EXPORT
+void AMediaDataSource_setUserdata(AMediaDataSource *mSource, void *userdata) {
+ mSource->userdata = userdata;
+}
+
+EXPORT
+void AMediaDataSource_setReadAt(AMediaDataSource *mSource, AMediaDataSourceReadAt readAt) {
+ mSource->readAt = readAt;
+}
+
+EXPORT
+void AMediaDataSource_setGetSize(AMediaDataSource *mSource, AMediaDataSourceGetSize getSize) {
+ mSource->getSize = getSize;
+}
+
+} // extern "C"
+
diff --git a/media/ndk/NdkMediaDataSourcePriv.h b/media/ndk/NdkMediaDataSourcePriv.h
new file mode 100644
index 0000000..a1cb331
--- /dev/null
+++ b/media/ndk/NdkMediaDataSourcePriv.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+
+/*
+ * This file defines an NDK API.
+ * Do not remove methods.
+ * Do not change method signatures.
+ * Do not change the value of constants.
+ * Do not change the size of any of the classes defined in here.
+ * Do not reference types that are not part of the NDK.
+ * Do not #include files that aren't part of the NDK.
+ */
+
+#ifndef _NDK_MEDIA_DATASOURCE_PRIV_H
+#define _NDK_MEDIA_DATASOURCE_PRIV_H
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+#include <media/DataSource.h>
+#include <media/NdkMediaDataSource.h>
+#include <utils/Mutex.h>
+#include <utils/String8.h>
+
+using namespace android;
+
+struct NdkDataSource : public DataSource {
+
+ NdkDataSource(AMediaDataSource *);
+
+ virtual status_t initCheck() const;
+ virtual ssize_t readAt(off64_t offset, void *data, size_t size);
+ virtual status_t getSize(off64_t *);
+ virtual String8 toString();
+ virtual String8 getMIMEType() const;
+
+private:
+
+ Mutex mLock;
+ AMediaDataSource *mDataSource;
+
+};
+
+#endif // _NDK_MEDIA_DATASOURCE_PRIV_H
+
diff --git a/media/ndk/NdkMediaExtractor.cpp b/media/ndk/NdkMediaExtractor.cpp
index e677d00..ea43d2e 100644
--- a/media/ndk/NdkMediaExtractor.cpp
+++ b/media/ndk/NdkMediaExtractor.cpp
@@ -20,6 +20,7 @@
#include <media/NdkMediaError.h>
#include <media/NdkMediaExtractor.h>
+#include "NdkMediaDataSourcePriv.h"
#include "NdkMediaFormatPriv.h"
@@ -50,7 +51,6 @@
struct AMediaExtractor {
sp<NuMediaExtractor> mImpl;
sp<ABuffer> mPsshBuf;
-
};
extern "C" {
@@ -121,6 +121,18 @@
}
EXPORT
+media_status_t AMediaExtractor_setDataSourceCustom(AMediaExtractor* mData, AMediaDataSource *src) {
+ return translate_error(mData->mImpl->setDataSource(new NdkDataSource(src)));
+}
+
+EXPORT
+AMediaFormat* AMediaExtractor_getFileFormat(AMediaExtractor *mData) {
+ sp<AMessage> format;
+ mData->mImpl->getFileFormat(&format);
+ return AMediaFormat_fromMsg(&format);
+}
+
+EXPORT
size_t AMediaExtractor_getTrackCount(AMediaExtractor *mData) {
return mData->mImpl->countTracks();
}
@@ -182,6 +194,16 @@
}
EXPORT
+ssize_t AMediaExtractor_getSampleSize(AMediaExtractor *mData) {
+ size_t sampleSize;
+ status_t err = mData->mImpl->getSampleSize(&sampleSize);
+ if (err != OK) {
+ return -1;
+ }
+ return sampleSize;
+}
+
+EXPORT
uint32_t AMediaExtractor_getSampleFlags(AMediaExtractor *mData) {
int sampleFlags = 0;
sp<MetaData> meta;
@@ -379,6 +401,15 @@
(size_t*) crypteddata);
}
+EXPORT
+int64_t AMediaExtractor_getCachedDuration(AMediaExtractor *ex) {
+ bool eos;
+ int64_t durationUs;
+ if (ex->mImpl->getCachedDuration(&durationUs, &eos)) {
+ return durationUs;
+ }
+ return -1;
+}
} // extern "C"
diff --git a/media/ndk/include/media/NdkMediaDataSource.h b/media/ndk/include/media/NdkMediaDataSource.h
new file mode 100644
index 0000000..752b684
--- /dev/null
+++ b/media/ndk/include/media/NdkMediaDataSource.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+
+/*
+ * This file defines an NDK API.
+ * Do not remove methods.
+ * Do not change method signatures.
+ * Do not change the value of constants.
+ * Do not change the size of any of the classes defined in here.
+ * Do not reference types that are not part of the NDK.
+ * Do not #include files that aren't part of the NDK.
+ */
+
+#ifndef _NDK_MEDIA_DATASOURCE_H
+#define _NDK_MEDIA_DATASOURCE_H
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+#include <media/NdkMediaError.h>
+
+__BEGIN_DECLS
+
+struct AMediaDataSource;
+typedef struct AMediaDataSource AMediaDataSource;
+
+#if __ANDROID_API__ >= 28
+
+/*
+ * AMediaDataSource's callbacks will be invoked on an implementation-defined thread
+ * or thread pool. No guarantees are provided about which thread(s) will be used for
+ * callbacks. However, it is guaranteed that AMediaDataSource's callbacks will only
+ * ever be invoked by a single thread at a time.
+ *
+ * There will be a thread synchronization point between each call to ensure that
+ * modifications to the state of your AMediaDataSource are visible to future
+ * calls. This means you don't need to do your own synchronization unless you're
+ * modifying the AMediaDataSource from another thread while it's being used by the
+ * framework.
+ */
+
+/**
+ * Called to request data from the given |offset|.
+ *
+ * Implementations should should write up to |size| bytes into
+ * |buffer|, and return the number of bytes written.
+ *
+ * Return 0 if size is zero (thus no bytes are read).
+ *
+ * Return -1 to indicate that end of stream is reached.
+ */
+typedef ssize_t (*AMediaDataSourceReadAt)(
+ void *userdata, off64_t offset, void * buffer, size_t size);
+
+/**
+ * Called to get the size of the data source.
+ *
+ * Return the size of data source in bytes, or -1 if the size is unknown.
+ */
+typedef ssize_t (*AMediaDataSourceGetSize)(void *userdata);
+
+/**
+ * Create new media data source. Returns NULL if memory allocation
+ * for the new data source object fails.
+ */
+AMediaDataSource* AMediaDataSource_new();
+
+/**
+ * Delete a previously created media data source.
+ */
+void AMediaDataSource_delete(AMediaDataSource*);
+
+/**
+ * Set an user provided opaque handle. This opaque handle is passed as
+ * the first argument to the data source callbacks.
+ */
+void AMediaDataSource_setUserdata(
+ AMediaDataSource*, void *userdata);
+
+/**
+ * Set a custom callback for supplying random access media data to the
+ * NDK media framework.
+ *
+ * Implement this if your app has special requirements for the way media
+ * data is obtained, or if you need a callback when data is read by the
+ * NDK media framework.
+ *
+ * Please refer to the definition of AMediaDataSourceReadAt for
+ * additional details.
+ */
+void AMediaDataSource_setReadAt(
+ AMediaDataSource*,
+ AMediaDataSourceReadAt);
+
+/**
+ * Set a custom callback for supplying the size of the data source to the
+ * NDK media framework.
+ *
+ * Please refer to the definition of AMediaDataSourceGetSize for
+ * additional details.
+ */
+void AMediaDataSource_setGetSize(
+ AMediaDataSource*,
+ AMediaDataSourceGetSize);
+
+#endif /*__ANDROID_API__ >= 28 */
+
+__END_DECLS
+
+#endif // _NDK_MEDIA_DATASOURCE_H
diff --git a/media/ndk/include/media/NdkMediaExtractor.h b/media/ndk/include/media/NdkMediaExtractor.h
index bf0e46d..3c9e23d 100644
--- a/media/ndk/include/media/NdkMediaExtractor.h
+++ b/media/ndk/include/media/NdkMediaExtractor.h
@@ -32,6 +32,7 @@
#include <sys/types.h>
#include "NdkMediaCodec.h"
+#include "NdkMediaDataSource.h"
#include "NdkMediaFormat.h"
#include "NdkMediaCrypto.h"
@@ -64,6 +65,15 @@
media_status_t AMediaExtractor_setDataSource(AMediaExtractor*, const char *location);
// TODO support headers
+#if __ANDROID_API__ >= 28
+
+/**
+ * Set the custom data source implementation from which the extractor will read.
+ */
+media_status_t AMediaExtractor_setDataSourceCustom(AMediaExtractor*, AMediaDataSource *src);
+
+#endif /* __ANDROID_API__ >= 28 */
+
/**
* Return the number of tracks in the previously specified media file
*/
@@ -152,12 +162,49 @@
AMediaCodecCryptoInfo *AMediaExtractor_getSampleCryptoInfo(AMediaExtractor *);
-
enum {
AMEDIAEXTRACTOR_SAMPLE_FLAG_SYNC = 1,
AMEDIAEXTRACTOR_SAMPLE_FLAG_ENCRYPTED = 2,
};
+#if __ANDROID_API__ >= 28
+
+/**
+ * Returns the format of the extractor. The caller must free the returned format
+ * using AMediaFormat_delete(format).
+ *
+ * This function will always return a format; however, the format could be empty
+ * (no key-value pairs) if the media container does not provide format information.
+ */
+AMediaFormat* AMediaExtractor_getFileFormat(AMediaExtractor*);
+
+/**
+ * Returns the size of the current sample in bytes, or -1 when no samples are
+ * available (end of stream). This API can be used in in conjunction with
+ * AMediaExtractor_readSampleData:
+ *
+ * ssize_t sampleSize = AMediaExtractor_getSampleSize(ex);
+ * uint8_t *buf = new uint8_t[sampleSize];
+ * AMediaExtractor_readSampleData(ex, buf, sampleSize);
+ *
+ */
+ssize_t AMediaExtractor_getSampleSize(AMediaExtractor*);
+
+/**
+ * Returns the duration of cached media samples downloaded from a network data source
+ * (AMediaExtractor_setDataSource with a "http(s)" URI) in microseconds.
+ *
+ * This information is calculated using total bitrate; if total bitrate is not in the
+ * media container it is calculated using total duration and file size.
+ *
+ * Returns -1 when the extractor is not reading from a network data source, or when the
+ * cached duration cannot be calculated (bitrate, duration, and file size information
+ * not available).
+ */
+int64_t AMediaExtractor_getCachedDuration(AMediaExtractor *);
+
+#endif /* __ANDROID_API__ >= 28 */
+
#endif /* __ANDROID_API__ >= 21 */
__END_DECLS
diff --git a/media/ndk/libmediandk.map.txt b/media/ndk/libmediandk.map.txt
index f2d97cd..37c557a 100644
--- a/media/ndk/libmediandk.map.txt
+++ b/media/ndk/libmediandk.map.txt
@@ -123,6 +123,11 @@
AMediaCrypto_isCryptoSchemeSupported;
AMediaCrypto_new;
AMediaCrypto_requiresSecureDecoderComponent;
+ AMediaDataSource_delete; # introduced=28
+ AMediaDataSource_new; # introduced=28
+ AMediaDataSource_setGetSize; # introduced=28
+ AMediaDataSource_setReadAt; # introduced=28
+ AMediaDataSource_setUserdata; # introduced=28
AMediaDrm_closeSession;
AMediaDrm_createByUUID;
AMediaDrm_decrypt;
@@ -148,9 +153,12 @@
AMediaDrm_verify;
AMediaExtractor_advance;
AMediaExtractor_delete;
+ AMediaExtractor_getCachedDuration; # introduced=28
+ AMediaExtractor_getFileFormat; # introduced=28
AMediaExtractor_getPsshInfo;
AMediaExtractor_getSampleCryptoInfo;
AMediaExtractor_getSampleFlags;
+ AMediaExtractor_getSampleSize; # introduced=28
AMediaExtractor_getSampleTime;
AMediaExtractor_getSampleTrackIndex;
AMediaExtractor_getTrackCount;
@@ -160,6 +168,7 @@
AMediaExtractor_seekTo;
AMediaExtractor_selectTrack;
AMediaExtractor_setDataSource;
+ AMediaExtractor_setDataSourceCustom; # introduced=28
AMediaExtractor_setDataSourceFd;
AMediaExtractor_unselectTrack;
AMediaFormat_delete;
diff --git a/packages/MediaComponents/Android.mk b/packages/MediaComponents/Android.mk
index 61d73a0..7c7718e 100644
--- a/packages/MediaComponents/Android.mk
+++ b/packages/MediaComponents/Android.mk
@@ -27,7 +27,10 @@
# TODO: Use System SDK once public APIs are approved
# LOCAL_SDK_VERSION := system_current
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-Iaidl-files-under, src)
+
LOCAL_PROGUARD_FLAG_FILES := proguard.cfg
LOCAL_MULTILIB := first
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_disabled.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_subtitle_disabled.png
similarity index 100%
rename from packages/MediaComponents/res/drawable-hdpi/ic_media_cc_disabled.png
rename to packages/MediaComponents/res/drawable-hdpi/ic_media_subtitle_disabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-hdpi/ic_media_cc_enabled.png b/packages/MediaComponents/res/drawable-hdpi/ic_media_subtitle_enabled.png
similarity index 100%
rename from packages/MediaComponents/res/drawable-hdpi/ic_media_cc_enabled.png
rename to packages/MediaComponents/res/drawable-hdpi/ic_media_subtitle_enabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_disabled.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_subtitle_disabled.png
similarity index 100%
rename from packages/MediaComponents/res/drawable-mdpi/ic_media_cc_disabled.png
rename to packages/MediaComponents/res/drawable-mdpi/ic_media_subtitle_disabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable-mdpi/ic_media_cc_enabled.png b/packages/MediaComponents/res/drawable-mdpi/ic_media_subtitle_enabled.png
similarity index 100%
rename from packages/MediaComponents/res/drawable-mdpi/ic_media_cc_enabled.png
rename to packages/MediaComponents/res/drawable-mdpi/ic_media_subtitle_enabled.png
Binary files differ
diff --git a/packages/MediaComponents/res/drawable/ic_aspect_ratio.xml b/packages/MediaComponents/res/drawable/ic_aspect_ratio.xml
new file mode 100644
index 0000000..c6228e6
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_aspect_ratio.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_cast.xml b/packages/MediaComponents/res/drawable/ic_cast.xml
deleted file mode 100644
index ac22a4b..0000000
--- a/packages/MediaComponents/res/drawable/ic_cast.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v3h2L3,5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM1,18v3h3c0,-1.66 -1.34,-3 -3,-3zM1,14v2c2.76,0 5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zM1,10v2c4.97,0 9,4.03 9,9h2c0,-6.08 -4.93,-11 -11,-11z"
- android:fillColor="#FFFFFF"/>
-</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_mute.xml b/packages/MediaComponents/res/drawable/ic_mute.xml
new file mode 100644
index 0000000..560aaec
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_mute.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v2.21l2.45,2.45c0.03,-0.2 0.05,-0.41 0.05,-0.63zM19,12c0,0.94 -0.2,1.82 -0.54,2.64l1.51,1.51C20.63,14.91 21,13.5 21,12c0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM4.27,3L3,4.27 7.73,9L3,9v6h4l5,5v-6.73l4.25,4.25c-0.67,0.52 -1.42,0.93 -2.25,1.18v2.06c1.38,-0.31 2.63,-0.95 3.69,-1.81L19.73,21 21,19.73l-9,-9L4.27,3zM12,4L9.91,6.09 12,8.18L12,4z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_settings.xml b/packages/MediaComponents/res/drawable/ic_settings.xml
new file mode 100644
index 0000000..a59ecc1
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_settings.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/drawable/ic_unmute.xml b/packages/MediaComponents/res/drawable/ic_unmute.xml
new file mode 100644
index 0000000..9dfb2b9
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_unmute.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/MediaComponents/res/layout/media_controller.xml b/packages/MediaComponents/res/layout/media_controller.xml
index ff4f12a..74a8306 100644
--- a/packages/MediaComponents/res/layout/media_controller.xml
+++ b/packages/MediaComponents/res/layout/media_controller.xml
@@ -46,14 +46,12 @@
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:textSize="20sp"
- android:text="North by Northwest"
android:textColor="#FFFFFFFF" />
- <ImageButton
- android:id="@+id/cast"
+ <view class="com.android.support.mediarouter.app.MediaRouteButton" android:id="@+id/cast"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
- style="@style/TitleBarButton.MediaRouteButton"/>
+ style="@style/TitleBarButton" />
</RelativeLayout>
@@ -85,7 +83,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="15dp"
- android:paddingRight="15dp"
android:orientation="horizontal">
<TextView
@@ -112,25 +109,56 @@
android:textStyle="bold"
android:textColor="#BBBBBB" />
- <ImageButton
- android:id="@+id/overflow"
- android:layout_alignParentEnd="true"
+ <LinearLayout
+ android:id="@+id/basic_controls"
+ android:layout_alignParentRight="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_centerVertical="true"
- style="@style/BottomBarButton.Overflow"/>
+ android:orientation="horizontal" >
- <ImageButton
- android:id="@+id/fullscreen"
- android:layout_toLeftOf="@id/overflow"
- android:layout_centerVertical="true"
- style="@style/BottomBarButton.FullScreen"/>
+ <ImageButton
+ android:id="@+id/subtitle"
+ android:scaleType="fitCenter"
+ style="@style/BottomBarButton.CC" />
+ <ImageButton
+ android:id="@+id/fullscreen"
+ style="@style/BottomBarButton.FullScreen"/>
+ <ImageButton
+ android:id="@+id/overflow_right"
+ style="@style/BottomBarButton.OverflowRight"/>
+ </LinearLayout>
- <ImageButton
- android:id="@+id/cc"
- android:scaleType="fitCenter"
- android:layout_toLeftOf="@id/fullscreen"
+ <LinearLayout
+ android:id="@+id/extra_controls"
+ android:layout_alignParentRight="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_centerVertical="true"
- style="@style/BottomBarButton.CC" />
+ android:visibility="gone"
+ android:orientation="horizontal"
+ android:gravity="center">
+
+ <LinearLayout
+ android:id="@+id/custom_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageButton
+ android:id="@+id/mute"
+ style="@style/BottomBarButton.Mute" />
+ <ImageButton
+ android:id="@+id/aspect_ratio"
+ style="@style/BottomBarButton.AspectRatio" />
+ <ImageButton
+ android:id="@+id/settings"
+ style="@style/BottomBarButton.Settings" />
+ <ImageButton
+ android:id="@+id/overflow_left"
+ style="@style/BottomBarButton.OverflowLeft"/>
+ </LinearLayout>
</RelativeLayout>
</LinearLayout>
+
diff --git a/packages/MediaComponents/res/values/colors.xml b/packages/MediaComponents/res/values/colors.xml
new file mode 100644
index 0000000..9e071d7
--- /dev/null
+++ b/packages/MediaComponents/res/values/colors.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 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.
+-->
+
+<resources>
+ <integer name="gray">0xff444444</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/values/style.xml b/packages/MediaComponents/res/values/style.xml
index d31b41d..c59380c 100644
--- a/packages/MediaComponents/res/values/style.xml
+++ b/packages/MediaComponents/res/values/style.xml
@@ -34,29 +34,42 @@
<item name="android:layout_margin">10dp</item>
</style>
- <style name="TitleBarButton.MediaRouteButton">
- <item name="android:src">@drawable/ic_cast</item>
- </style>
-
-
<style name="BottomBarButton">
<item name="android:background">@null</item>
- <item name="android:layout_width">24dp</item>
- <item name="android:layout_height">24dp</item>
- <item name="android:layout_margin">10dp</item>
+ <item name="android:layout_width">44dp</item>
+ <item name="android:layout_height">34dp</item>
+ <item name="android:layout_marginTop">5dp</item>
+ <item name="android:layout_marginBottom">5dp</item>
+ <item name="android:paddingLeft">5dp</item>
+ <item name="android:paddingRight">5dp</item>
</style>
<style name="BottomBarButton.CC">
- <item name="android:src">@drawable/ic_media_cc_disabled</item>
+ <item name="android:src">@drawable/ic_media_subtitle_disabled</item>
</style>
<style name="BottomBarButton.FullScreen">
<item name="android:src">@drawable/ic_fullscreen</item>
</style>
- <style name="BottomBarButton.Overflow">
+ <style name="BottomBarButton.OverflowRight">
<item name="android:src">@drawable/ic_chevron_right</item>
</style>
-</resources>
+ <style name="BottomBarButton.OverflowLeft">
+ <item name="android:src">@drawable/ic_chevron_left</item>
+ </style>
+ <style name="BottomBarButton.Settings">
+ <item name="android:src">@drawable/ic_settings</item>
+ </style>
+
+ <style name="BottomBarButton.AspectRatio">
+ <item name="android:src">@drawable/ic_aspect_ratio</item>
+ </style>
+
+ <style name="BottomBarButton.Mute">
+ <item name="android:src">@drawable/ic_mute</item>
+ </style>
+
+</resources>
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
new file mode 100644
index 0000000..fc62a68
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2018 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.
+ */
+
+package com.android.media;
+
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+import com.android.media.IMediaSession2Callback;
+
+/**
+ * Interface to MediaSession2.
+ * <p>
+ * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
+ * and holds calls from session to make session owner(s) frozen.
+ */
+interface IMediaSession2 {
+ // TODO(jaewan): add onCommand() to send private command
+ // TODO(jaewan): Due to the nature of oneway calls, APIs can be called in out of order
+ // Add id for individual calls to address this.
+
+ // TODO(jaewan): We may consider to add another binder just for the connection
+ // not to expose other methods to the controller whose connection wasn't accepted.
+ // But this would be enough for now because it's the same as existing
+ // MediaBrowser and MediaBrowserService.
+ void connect(String callingPackage, IMediaSession2Callback callback);
+ void release(IMediaSession2Callback caller);
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ // send command
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ void sendCommand(IMediaSession2Callback caller, in Bundle command, in Bundle args);
+ void sendTransportControlCommand(IMediaSession2Callback caller,
+ int commandCode, in Bundle args);
+ void sendCustomCommand(IMediaSession2Callback caller, in Bundle command, in Bundle args,
+ in ResultReceiver receiver);
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ // Get library service specific
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ void getBrowserRoot(IMediaSession2Callback callback, in Bundle rootHints);
+}
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
new file mode 100644
index 0000000..aabbc69
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2018 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.
+ */
+
+package com.android.media;
+
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+import com.android.media.IMediaSession2;
+
+/**
+ * Interface from MediaSession2 to MediaSession2Record.
+ * <p>
+ * Keep this interface oneway. Otherwise a malicious app may implement fake version of this,
+ * and holds calls from session to make session owner(s) frozen.
+ */
+oneway interface IMediaSession2Callback {
+ void onPlaybackStateChanged(in Bundle state);
+ void onPlaylistChanged(in List<Bundle> playlist);
+ void onPlaylistParamsChanged(in Bundle params);
+
+ /**
+ * Called only when the controller is created with service's token.
+ *
+ * @param sessionBinder {@code null} if the connect is rejected or is disconnected. a session
+ * binder if the connect is accepted.
+ * @param commands initially allowed commands.
+ */
+ // TODO(jaewan): Also need to pass flags for allowed actions for permission check.
+ // For example, a media can allow setRating only for whitelisted apps
+ // it's better for controller to know such information in advance.
+ // Follow-up TODO: Add similar functions to the session.
+ // TODO(jaewan): Is term 'accepted/rejected' correct? For permission, 'grant' is used.
+ void onConnectionChanged(IMediaSession2 sessionBinder, in Bundle commandGroup);
+
+ void onCustomLayoutChanged(in List<Bundle> commandButtonlist);
+
+ void sendCustomCommand(in Bundle command, in Bundle args, in ResultReceiver receiver);
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ // Browser sepcific
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ void onGetRootResult(in Bundle rootHints, String rootMediaId, in Bundle rootExtra);
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
new file mode 100644
index 0000000..d21edae
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2018 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.
+ */
+
+package com.android.media;
+
+import android.content.Context;
+import android.media.MediaBrowser2;
+import android.media.MediaBrowser2.BrowserCallback;
+import android.media.MediaSession2.CommandButton;
+import android.media.SessionToken2;
+import android.media.update.MediaBrowser2Provider;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class MediaBrowser2Impl extends MediaController2Impl implements MediaBrowser2Provider {
+ private final String TAG = "MediaBrowser2";
+ private final boolean DEBUG = true; // TODO(jaewan): change.
+
+ private final MediaBrowser2 mInstance;
+ private final MediaBrowser2.BrowserCallback mCallback;
+
+ public MediaBrowser2Impl(Context context, MediaBrowser2 instance, SessionToken2 token,
+ Executor executor, BrowserCallback callback) {
+ super(context, instance, token, executor, callback);
+ mInstance = instance;
+ mCallback = callback;
+ }
+
+ @Override
+ public void getBrowserRoot_impl(Bundle rootHints) {
+ final IMediaSession2 binder = getSessionBinder();
+ if (binder != null) {
+ try {
+ binder.getBrowserRoot(getControllerStub(), rootHints);
+ } catch (RemoteException e) {
+ // TODO(jaewan): Handle disconnect.
+ if (DEBUG) {
+ Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+ }
+ }
+ } else {
+ Log.w(TAG, "Session isn't active", new IllegalStateException());
+ }
+ }
+
+ @Override
+ public void subscribe_impl(String parentId, Bundle options) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void unsubscribe_impl(String parentId, Bundle options) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void getItem_impl(String mediaId) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void getChildren_impl(String parentId, int page, int pageSize, Bundle options) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void search_impl(String query, int page, int pageSize, Bundle extras) {
+ // TODO(jaewan): Implement
+ }
+
+ public void onGetRootResult(
+ final Bundle rootHints, final String rootMediaId, final Bundle rootExtra) {
+ getCallbackExecutor().execute(() -> {
+ mCallback.onGetRootResult(rootHints, rootMediaId, rootExtra);
+ });
+ }
+
+ public void onCustomLayoutChanged(final List<CommandButton> layout) {
+ getCallbackExecutor().execute(() -> {
+ mCallback.onCustomLayoutChanged(layout);
+ });
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index 1a27056..4144342 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -16,26 +16,30 @@
package com.android.media;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-import android.media.IMediaSession2;
-import android.media.IMediaSession2Callback;
+import android.media.MediaController2.PlaybackInfo;
+import android.media.MediaItem2;
import android.media.MediaSession2;
import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaController2;
import android.media.MediaController2.ControllerCallback;
-import android.media.MediaPlayerBase;
+import android.media.MediaSession2.PlaylistParams;
import android.media.MediaSessionService2;
-import android.media.SessionToken;
-import android.media.session.PlaybackState;
+import android.media.PlaybackState2;
+import android.media.Rating2;
+import android.media.SessionToken2;
import android.media.update.MediaController2Provider;
+import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.support.annotation.GuardedBy;
import android.util.Log;
@@ -49,29 +53,25 @@
private static final boolean DEBUG = true; // TODO(jaewan): Change
private final MediaController2 mInstance;
-
- /**
- * Flag used by MediaController2Record to filter playback callback.
- */
- static final int CALLBACK_FLAG_PLAYBACK = 0x1;
-
- static final int REQUEST_CODE_ALL = 0;
-
+ private final Context mContext;
private final Object mLock = new Object();
- private final Context mContext;
private final MediaSession2CallbackStub mSessionCallbackStub;
- private final SessionToken mToken;
+ private final SessionToken2 mToken;
private final ControllerCallback mCallback;
private final Executor mCallbackExecutor;
private final IBinder.DeathRecipient mDeathRecipient;
@GuardedBy("mLock")
- private final List<PlaybackListenerHolder> mPlaybackListeners = new ArrayList<>();
- @GuardedBy("mLock")
private SessionServiceConnection mServiceConnection;
@GuardedBy("mLock")
private boolean mIsReleased;
+ @GuardedBy("mLock")
+ private PlaybackState2 mPlaybackState;
+ @GuardedBy("mLock")
+ private List<MediaItem2> mPlaylist;
+ @GuardedBy("mLock")
+ private PlaylistParams mPlaylistParams;
// Assignment should be used with the lock hold, but should be used without a lock to prevent
// potential deadlock.
@@ -83,10 +83,9 @@
// TODO(jaewan): Require session activeness changed listener, because controller can be
// available when the session's player is null.
- public MediaController2Impl(MediaController2 instance, Context context, SessionToken token,
- ControllerCallback callback, Executor executor) {
+ public MediaController2Impl(Context context, MediaController2 instance, SessionToken2 token,
+ Executor executor, ControllerCallback callback) {
mInstance = instance;
-
if (context == null) {
throw new IllegalArgumentException("context shouldn't be null");
}
@@ -105,25 +104,32 @@
mCallback = callback;
mCallbackExecutor = executor;
mDeathRecipient = () -> {
- mInstance.release();
+ mInstance.close();
};
mSessionBinder = null;
+ }
- if (token.getSessionBinder() == null) {
+ @Override
+ public void initialize() {
+ SessionToken2Impl impl = SessionToken2Impl.from(mToken);
+ // TODO(jaewan): More sanity checks.
+ if (impl.getSessionBinder() == null) {
+ // Session service
mServiceConnection = new SessionServiceConnection();
connectToService();
} else {
+ // Session
mServiceConnection = null;
- connectToSession(token.getSessionBinder());
+ connectToSession(impl.getSessionBinder());
}
}
- // Should be only called by constructor.
private void connectToService() {
// Service. Needs to get fresh binder whenever connection is needed.
+ SessionToken2Impl impl = SessionToken2Impl.from(mToken);
final Intent intent = new Intent(MediaSessionService2.SERVICE_INTERFACE);
- intent.setClassName(mToken.getPackageName(), mToken.getServiceName());
+ intent.setClassName(mToken.getPackageName(), impl.getServiceName());
// Use bindService() instead of startForegroundService() to start session service for three
// reasons.
@@ -158,7 +164,10 @@
}
@Override
- public void release_impl() {
+ public void close_impl() {
+ if (DEBUG) {
+ Log.d(TAG, "release from " + mToken);
+ }
final IMediaSession2 binder;
synchronized (mLock) {
if (mIsReleased) {
@@ -170,7 +179,6 @@
mContext.unbindService(mServiceConnection);
mServiceConnection = null;
}
- mPlaybackListeners.clear();
binder = mSessionBinder;
mSessionBinder = null;
mSessionCallbackStub.destroy();
@@ -188,8 +196,24 @@
});
}
+ IMediaSession2 getSessionBinder() {
+ return mSessionBinder;
+ }
+
+ MediaSession2CallbackStub getControllerStub() {
+ return mSessionCallbackStub;
+ }
+
+ Executor getCallbackExecutor() {
+ return mCallbackExecutor;
+ }
+
+ Context getContext() {
+ return mContext;
+ }
+
@Override
- public SessionToken getSessionToken_impl() {
+ public SessionToken2 getSessionToken_impl() {
return mToken;
}
@@ -201,38 +225,38 @@
@Override
public void play_impl() {
- sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_START);
+ sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_START);
}
@Override
public void pause_impl() {
- sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE);
+ sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE);
}
@Override
public void stop_impl() {
- sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_STOP);
+ sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_STOP);
}
@Override
public void skipToPrevious_impl() {
- sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM);
+ sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM);
}
@Override
public void skipToNext_impl() {
- sendCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM);
+ sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM);
}
- private void sendCommand(int code) {
- // TODO(jaewan): optimization) Cache Command objects?
- Command command = new Command(code);
- // TODO(jaewan): Check if the command is in the allowed group.
+ private void sendTransportControlCommand(int commandCode) {
+ sendTransportControlCommand(commandCode, null);
+ }
+ private void sendTransportControlCommand(int commandCode, Bundle args) {
final IMediaSession2 binder = mSessionBinder;
if (binder != null) {
try {
- binder.sendCommand(mSessionCallbackStub, command.toBundle(), null);
+ binder.sendTransportControlCommand(mSessionCallbackStub, commandCode, args);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -241,92 +265,205 @@
}
}
+ //////////////////////////////////////////////////////////////////////////////////////
+ // TODO(jaewan): Implement follows
+ //////////////////////////////////////////////////////////////////////////////////////
@Override
- public PlaybackState getPlaybackState_impl() {
- final IMediaSession2 binder = mSessionBinder;
- if (binder != null) {
- try {
- return binder.getPlaybackState();
- } catch (RemoteException e) {
- Log.w(TAG, "Cannot connect to the service or the session is gone", e);
- }
- } else {
- Log.w(TAG, "Session isn't active", new IllegalStateException());
- }
- // TODO(jaewan): What to return for error case?
+ public PendingIntent getSessionActivity_impl() {
+ // TODO(jaewan): Implement
return null;
}
@Override
- public void addPlaybackListener_impl(
- MediaPlayerBase.PlaybackListener listener, Handler handler) {
- if (listener == null) {
- throw new IllegalArgumentException("listener shouldn't be null");
+ public int getRatingType_impl() {
+ // TODO(jaewan): Implement
+ return 0;
+ }
+
+ @Override
+ public void setVolumeTo_impl(int value, int flags) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void adjustVolume_impl(int direction, int flags) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public PlaybackInfo getPlaybackInfo_impl() {
+ // TODO(jaewan): Implement
+ return null;
+ }
+
+ @Override
+ public void prepareFromUri_impl(Uri uri, Bundle extras) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void prepareFromSearch_impl(String query, Bundle extras) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void prepareMediaId_impl(String mediaId, Bundle extras) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void playFromSearch_impl(String query, Bundle extras) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void playFromUri_impl(String uri, Bundle extras) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void playFromMediaId_impl(String mediaId, Bundle extras) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void setRating_impl(Rating2 rating) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void sendCustomCommand_impl(Command command, Bundle args, ResultReceiver cb) {
+ if (command == null) {
+ throw new IllegalArgumentException("command shouldn't be null");
}
- if (handler == null) {
- throw new IllegalArgumentException("handler shouldn't be null");
- }
- boolean registerCallback;
- synchronized (mLock) {
- if (PlaybackListenerHolder.contains(mPlaybackListeners, listener)) {
- throw new IllegalArgumentException("listener is already added. Ignoring.");
+ // TODO(jaewan): Also check if the command is allowed.
+ final IMediaSession2 binder = mSessionBinder;
+ if (binder != null) {
+ try {
+ binder.sendCustomCommand(mSessionCallbackStub, command.toBundle(), args, cb);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
- registerCallback = mPlaybackListeners.isEmpty();
- mPlaybackListeners.add(new PlaybackListenerHolder(listener, handler));
- }
- if (registerCallback) {
- registerCallbackForPlaybackNotLocked();
+ } else {
+ Log.w(TAG, "Session isn't active", new IllegalStateException());
}
}
@Override
- public void removePlaybackListener_impl(MediaPlayerBase.PlaybackListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("listener shouldn't be null");
- }
- boolean unregisterCallback;
+ public List<MediaItem2> getPlaylist_impl() {
synchronized (mLock) {
- int idx = PlaybackListenerHolder.indexOf(mPlaybackListeners, listener);
- if (idx >= 0) {
- mPlaybackListeners.get(idx).removeCallbacksAndMessages(null);
- mPlaybackListeners.remove(idx);
- }
- unregisterCallback = mPlaybackListeners.isEmpty();
+ return mPlaylist;
}
- if (unregisterCallback) {
- final IMediaSession2 binder = mSessionBinder;
- if (binder != null) {
- // Lazy unregister
- try {
- binder.unregisterCallback(mSessionCallbackStub, CALLBACK_FLAG_PLAYBACK);
- } catch (RemoteException e) {
- Log.e(TAG, "Cannot connect to the service or the session is gone", e);
- }
- }
+ }
+
+ @Override
+ public void prepare_impl() {
+ sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE);
+ }
+
+ @Override
+ public void fastForward_impl() {
+ sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_FAST_FORWARD);
+ }
+
+ @Override
+ public void rewind_impl() {
+ sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_REWIND);
+ }
+
+ @Override
+ public void seekTo_impl(long pos) {
+ Bundle args = new Bundle();
+ args.putLong(MediaSession2Stub.ARGUMENT_KEY_POSITION, pos);
+ sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SEEK_TO, args);
+ }
+
+ @Override
+ public void setCurrentPlaylistItem_impl(int index) {
+ Bundle args = new Bundle();
+ args.putInt(MediaSession2Stub.ARGUMENT_KEY_ITEM_INDEX, index);
+ sendTransportControlCommand(
+ MediaSession2.COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM, args);
+ }
+
+ @Override
+ public PlaybackState2 getPlaybackState_impl() {
+ synchronized (mLock) {
+ return mPlaybackState;
}
}
+ @Override
+ public void removePlaylistItem_impl(MediaItem2 index) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void addPlaylistItem_impl(int index, MediaItem2 item) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public PlaylistParams getPlaylistParams_impl() {
+ synchronized (mLock) {
+ return mPlaylistParams;
+ }
+ }
+
+ @Override
+ public void setPlaylistParams_impl(PlaylistParams params) {
+ if (params == null) {
+ throw new IllegalArgumentException("PlaylistParams should not be null!");
+ }
+ Bundle args = new Bundle();
+ args.putBundle(MediaSession2Stub.ARGUMENT_KEY_PLAYLIST_PARAMS, params.toBundle());
+ sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS, args);
+ }
+
///////////////////////////////////////////////////
// Protected or private methods
///////////////////////////////////////////////////
- // Should be used without a lock to prevent potential deadlock.
- private void registerCallbackForPlaybackNotLocked() {
- final IMediaSession2 binder = mSessionBinder;
- if (binder != null) {
- try {
- binder.registerCallback(mSessionCallbackStub,
- CALLBACK_FLAG_PLAYBACK, REQUEST_CODE_ALL);
- } catch (RemoteException e) {
- Log.e(TAG, "Cannot connect to the service or the session is gone", e);
- }
+ private void pushPlaybackStateChanges(final PlaybackState2 state) {
+ synchronized (mLock) {
+ mPlaybackState = state;
}
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ mCallback.onPlaybackStateChanged(state);
+ });
}
- private void pushPlaybackStateChanges(final PlaybackState state) {
+ private void pushPlaylistParamsChanges(final PlaylistParams params) {
synchronized (mLock) {
- for (int i = 0; i < mPlaybackListeners.size(); i++) {
- mPlaybackListeners.get(i).postPlaybackChange(state);
+ mPlaylistParams = params;
+ }
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
}
+ mCallback.onPlaylistParamsChanged(params);
+ });
+ }
+
+ private void pushPlaylistChanges(final List<Bundle> list) {
+ final List<MediaItem2> playlist = new ArrayList<>();
+ for (int i = 0; i < list.size(); i++) {
+ MediaItem2 item = MediaItem2.fromBundle(mContext, list.get(i));
+ if (item != null) {
+ playlist.add(item);
+ }
+ }
+
+ synchronized (mLock) {
+ mPlaylist = playlist;
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ mCallback.onPlaylistChanged(playlist);
+ });
}
}
@@ -335,7 +472,8 @@
private void onConnectionChangedNotLocked(IMediaSession2 sessionBinder,
CommandGroup commandGroup) {
if (DEBUG) {
- Log.d(TAG, "onConnectionChangedNotLocked sessionBinder=" + sessionBinder);
+ Log.d(TAG, "onConnectionChangedNotLocked sessionBinder=" + sessionBinder
+ + ", commands=" + commandGroup);
}
boolean release = false;
try {
@@ -344,7 +482,6 @@
release = true;
return;
}
- boolean registerCallbackForPlaybackNeeded;
synchronized (mLock) {
if (mIsReleased) {
return;
@@ -361,28 +498,40 @@
// so can be used without worrying about deadlock.
mSessionBinder.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
+ if (DEBUG) {
+ Log.d(TAG, "Session died too early.", e);
+ }
release = true;
return;
}
- registerCallbackForPlaybackNeeded = !mPlaybackListeners.isEmpty();
}
// TODO(jaewan): Keep commands to prevents illegal API calls.
mCallbackExecutor.execute(() -> {
mCallback.onConnected(commandGroup);
});
- if (registerCallbackForPlaybackNeeded) {
- registerCallbackForPlaybackNotLocked();
- }
} finally {
if (release) {
// Trick to call release() without holding the lock, to prevent potential deadlock
// with the developer's custom lock within the ControllerCallback.onDisconnected().
- mInstance.release();
+ mInstance.close();
}
}
}
- private static class MediaSession2CallbackStub extends IMediaSession2Callback.Stub {
+ private void onCustomCommand(final Command command, final Bundle args,
+ final ResultReceiver receiver) {
+ if (DEBUG) {
+ Log.d(TAG, "onCustomCommand cmd=" + command);
+ }
+ mCallbackExecutor.execute(() -> {
+ // TODO(jaewan): Double check if the controller exists.
+ mCallback.onCustomCommand(command, args, receiver);
+ });
+ }
+
+ // TODO(jaewan): Pull out this from the controller2, and rename it to the MediaController2Stub
+ // or MediaBrowser2Stub.
+ static class MediaSession2CallbackStub extends IMediaSession2Callback.Stub {
private final WeakReference<MediaController2Impl> mController;
private MediaSession2CallbackStub(MediaController2Impl controller) {
@@ -397,14 +546,56 @@
return controller;
}
+ // TODO(jaewan): Refactor code to get rid of these pattern.
+ private MediaBrowser2Impl getBrowser() throws IllegalStateException {
+ final MediaController2Impl controller = getController();
+ if (controller instanceof MediaBrowser2Impl) {
+ return (MediaBrowser2Impl) controller;
+ }
+ return null;
+ }
+
public void destroy() {
mController.clear();
}
@Override
- public void onPlaybackStateChanged(PlaybackState state) throws RuntimeException {
- final MediaController2Impl controller = getController();
- controller.pushPlaybackStateChanges(state);
+ public void onPlaybackStateChanged(Bundle state) throws RuntimeException {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ controller.pushPlaybackStateChanges(PlaybackState2.fromBundle(state));
+ }
+
+ @Override
+ public void onPlaylistChanged(List<Bundle> playlist) throws RuntimeException {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ if (playlist == null) {
+ return;
+ }
+ controller.pushPlaylistChanges(playlist);
+ }
+
+ @Override
+ public void onPlaylistParamsChanged(Bundle params) throws RuntimeException {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ controller.pushPlaylistParamsChanges(PlaylistParams.fromBundle(params));
}
@Override
@@ -418,7 +609,68 @@
return;
}
controller.onConnectionChangedNotLocked(
- sessionBinder, CommandGroup.fromBundle(commandGroup));
+ sessionBinder, CommandGroup.fromBundle(controller.getContext(), commandGroup));
+ }
+
+ @Override
+ public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra)
+ throws RuntimeException {
+ final MediaBrowser2Impl browser;
+ try {
+ browser = getBrowser();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ if (browser == null) {
+ // TODO(jaewan): Revisit here. Could be a bug
+ return;
+ }
+ browser.onGetRootResult(rootHints, rootMediaId, rootExtra);
+ }
+
+ @Override
+ public void onCustomLayoutChanged(List<Bundle> commandButtonlist) {
+ if (commandButtonlist == null) {
+ // Illegal call. Ignore
+ return;
+ }
+ final MediaBrowser2Impl browser;
+ try {
+ browser = getBrowser();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ if (browser == null) {
+ // TODO(jaewan): Revisit here. Could be a bug
+ return;
+ }
+ List<CommandButton> layout = new ArrayList<>();
+ for (int i = 0; i < commandButtonlist.size(); i++) {
+ CommandButton button = CommandButton.fromBundle(
+ browser.getContext(), commandButtonlist.get(i));
+ if (button != null) {
+ layout.add(button);
+ }
+ }
+ browser.onCustomLayoutChanged(layout);
+ }
+
+ @Override
+ public void sendCustomCommand(Bundle commandBundle, Bundle args, ResultReceiver receiver) {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ Command command = Command.fromBundle(controller.getContext(), commandBundle);
+ if (command == null) {
+ return;
+ }
+ controller.onCustomCommand(command, args, receiver);
}
}
@@ -455,7 +707,7 @@
// Permanent lose of the binding because of the service package update or removed.
// This SessionServiceRecord will be removed accordingly, but forget session binder here
// for sure.
- mInstance.release();
+ mInstance.close();
}
}
}
diff --git a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
new file mode 100644
index 0000000..5639346
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2018 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.
+ */
+
+package com.android.media;
+
+import static android.media.MediaItem2.FLAG_BROWSABLE;
+import static android.media.MediaItem2.FLAG_PLAYABLE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.DataSourceDesc;
+import android.media.MediaItem2;
+import android.media.MediaItem2.Flags;
+import android.media.MediaMetadata2;
+import android.media.update.MediaItem2Provider;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+public class MediaItem2Impl implements MediaItem2Provider {
+ private static final String KEY_ID = "android.media.mediaitem2.id";
+ private static final String KEY_FLAGS = "android.media.mediaitem2.flags";
+ private static final String KEY_METADATA = "android.media.mediaitem2.metadata";
+
+ private final Context mContext;
+ private final MediaItem2 mInstance;
+ private final String mId;
+ private final int mFlags;
+ private MediaMetadata2 mMetadata;
+ private DataSourceDesc mDataSourceDesc;
+
+ // From the public API
+ public MediaItem2Impl(Context context, MediaItem2 instance, String mediaId,
+ DataSourceDesc dsd, MediaMetadata2 metadata, @Flags int flags) {
+ if (mediaId == null) {
+ throw new IllegalArgumentException("mediaId shouldn't be null");
+ }
+ if (dsd == null) {
+ throw new IllegalArgumentException("dsd shouldn't be null");
+ }
+ if (metadata != null && !TextUtils.equals(mediaId, metadata.getMediaId())) {
+ throw new IllegalArgumentException("metadata's id should be match with the mediaid");
+ }
+
+ mContext = context;
+ mInstance = instance;
+
+ mId = mediaId;
+ mDataSourceDesc = dsd;
+ mMetadata = metadata;
+ mFlags = flags;
+ }
+
+ // Create anonymized version
+ public MediaItem2Impl(Context context, String mediaId, MediaMetadata2 metadata,
+ @Flags int flags) {
+ if (mediaId == null) {
+ throw new IllegalArgumentException("mediaId shouldn't be null");
+ }
+ if (metadata != null && !TextUtils.equals(mediaId, metadata.getMediaId())) {
+ throw new IllegalArgumentException("metadata's id should be match with the mediaid");
+ }
+ mContext =context;
+ mId = mediaId;
+ mMetadata = metadata;
+ mFlags = flags;
+ mInstance = new MediaItem2(this);
+ }
+
+ /**
+ * Return this object as a bundle to share between processes.
+ *
+ * @return a new bundle instance
+ */
+ public Bundle toBundle_impl() {
+ Bundle bundle = new Bundle();
+ bundle.putString(KEY_ID, mId);
+ bundle.putInt(KEY_FLAGS, mFlags);
+ if (mMetadata != null) {
+ bundle.putBundle(KEY_METADATA, mMetadata.getBundle());
+ }
+ return bundle;
+ }
+
+ public static MediaItem2 fromBundle(Context context, Bundle bundle) {
+ if (bundle == null) {
+ return null;
+ }
+ final String id = bundle.getString(KEY_ID);
+ final Bundle metadataBundle = bundle.getBundle(KEY_METADATA);
+ final MediaMetadata2 metadata = metadataBundle != null
+ ? new MediaMetadata2(metadataBundle) : null;
+ final int flags = bundle.getInt(KEY_FLAGS);
+ return new MediaItem2Impl(context, id, metadata, flags).getInstance();
+ }
+
+ private MediaItem2 getInstance() {
+ return mInstance;
+ }
+
+ @Override
+ public String toString_impl() {
+ final StringBuilder sb = new StringBuilder("MediaItem2{");
+ sb.append("mFlags=").append(mFlags);
+ sb.append(", mMetadata=").append(mMetadata);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public @Flags int getFlags_impl() {
+ return mFlags;
+ }
+
+ @Override
+ public boolean isBrowsable_impl() {
+ return (mFlags & FLAG_BROWSABLE) != 0;
+ }
+
+ @Override
+ public boolean isPlayable_impl() {
+ return (mFlags & FLAG_PLAYABLE) != 0;
+ }
+
+ @Override
+ public void setMetadata_impl(@NonNull MediaMetadata2 metadata) {
+ if (metadata == null) {
+ throw new IllegalArgumentException("metadata shouldn't be null");
+ }
+ if (TextUtils.isEmpty(metadata.getMediaId())) {
+ throw new IllegalArgumentException("metadata must have a non-empty media id");
+ }
+ mMetadata = metadata;
+ }
+
+ @Override
+ public MediaMetadata2 getMetadata_impl() {
+ return mMetadata;
+ }
+
+ @Override
+ public @Nullable String getMediaId_impl() {
+ return mMetadata.getMediaId();
+ }
+
+ @Override
+ public @Nullable DataSourceDesc getDataSourceDesc_impl() {
+ return mDataSourceDesc;
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
new file mode 100644
index 0000000..4b1b9de
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2018 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.
+ */
+
+package com.android.media;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.media.MediaLibraryService2;
+import android.media.MediaLibraryService2.MediaLibrarySession;
+import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
+import android.media.MediaPlayerInterface;
+import android.media.MediaSession2;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSessionService2;
+import android.media.SessionToken2;
+import android.media.VolumeProvider;
+import android.media.update.MediaLibraryService2Provider;
+import android.os.Bundle;
+
+import java.util.concurrent.Executor;
+
+public class MediaLibraryService2Impl extends MediaSessionService2Impl implements
+ MediaLibraryService2Provider {
+ private final MediaSessionService2 mInstance;
+ private MediaLibrarySession mLibrarySession;
+
+ public MediaLibraryService2Impl(MediaLibraryService2 instance) {
+ super(instance);
+ mInstance = instance;
+ }
+
+ @Override
+ public void onCreate_impl() {
+ super.onCreate_impl();
+
+ // Effectively final
+ MediaSession2 session = getSession();
+ if (!(session instanceof MediaLibrarySession)) {
+ throw new RuntimeException("Expected MediaLibrarySession, but returned MediaSession2");
+ }
+ mLibrarySession = (MediaLibrarySession) getSession();
+ }
+
+ @Override
+ int getSessionType() {
+ return SessionToken2.TYPE_LIBRARY_SERVICE;
+ }
+
+ public static class MediaLibrarySessionImpl extends MediaSession2Impl
+ implements MediaLibrarySessionProvider {
+ private final MediaLibrarySession mInstance;
+ private final MediaLibrarySessionCallback mCallback;
+
+ public MediaLibrarySessionImpl(Context context, MediaLibrarySession instance,
+ MediaPlayerInterface player, String id, VolumeProvider volumeProvider,
+ int ratingType, PendingIntent sessionActivity, Executor callbackExecutor,
+ MediaLibrarySessionCallback callback) {
+ super(context, instance, player, id, volumeProvider, ratingType, sessionActivity,
+ callbackExecutor, callback);
+ mInstance = instance;
+ mCallback = callback;
+ }
+
+ @Override
+ public void notifyChildrenChanged_impl(ControllerInfo controller, String parentId,
+ Bundle options) {
+ // TODO(jaewan): Implements
+ }
+
+ @Override
+ public void notifyChildrenChanged_impl(String parentId, Bundle options) {
+ // TODO(jaewan): Implements
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
index 09d7adc..5bb608d 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -16,47 +16,73 @@
package com.android.media;
+import static android.media.SessionToken2.TYPE_LIBRARY_SERVICE;
+import static android.media.SessionToken2.TYPE_SESSION;
+import static android.media.SessionToken2.TYPE_SESSION_SERVICE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.Manifest.permission;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.media.IMediaSession2Callback;
-import android.media.MediaController2;
-import android.media.MediaPlayerBase;
+import android.content.pm.ResolveInfo;
+import android.media.MediaItem2;
+import android.media.MediaLibraryService2;
+import android.media.MediaPlayerInterface;
+import android.media.MediaPlayerInterface.PlaybackListener;
import android.media.MediaSession2;
import android.media.MediaSession2.Builder;
+import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandButton;
+import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.PlaylistParams;
import android.media.MediaSession2.SessionCallback;
-import android.media.SessionToken;
+import android.media.MediaSessionService2;
+import android.media.PlaybackState2;
+import android.media.SessionToken2;
+import android.media.VolumeProvider;
import android.media.session.MediaSessionManager;
-import android.media.session.PlaybackState;
import android.media.update.MediaSession2Provider;
-import android.os.Handler;
+import android.os.Bundle;
+import android.os.Process;
import android.os.IBinder;
-import android.os.Looper;
+import android.os.ResultReceiver;
+import android.support.annotation.GuardedBy;
+import android.text.TextUtils;
import android.util.Log;
+
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
public class MediaSession2Impl implements MediaSession2Provider {
private static final String TAG = "MediaSession2";
private static final boolean DEBUG = true;//Log.isLoggable(TAG, Log.DEBUG);
- private final MediaSession2 mInstance;
+ private final Object mLock = new Object();
+ private final MediaSession2 mInstance;
private final Context mContext;
private final String mId;
- private final Handler mHandler;
+ private final Executor mCallbackExecutor;
private final SessionCallback mCallback;
private final MediaSession2Stub mSessionStub;
- private final SessionToken mSessionToken;
-
- private MediaPlayerBase mPlayer;
-
+ private final SessionToken2 mSessionToken;
private final List<PlaybackListenerHolder> mListeners = new ArrayList<>();
+
+ @GuardedBy("mLock")
+ private MediaPlayerInterface mPlayer;
+ @GuardedBy("mLock")
private MyPlaybackListener mListener;
- private MediaSession2 instance;
+ @GuardedBy("mLock")
+ private PlaylistParams mPlaylistParams;
+ @GuardedBy("mLock")
+ private List<MediaItem2> mPlaylist;
/**
* Can be only called by the {@link Builder#build()}.
@@ -66,92 +92,147 @@
* @param player
* @param id
* @param callback
+ * @param volumeProvider
+ * @param ratingType
+ * @param sessionActivity
*/
- public MediaSession2Impl(MediaSession2 instance, Context context, MediaPlayerBase player,
- String id, SessionCallback callback) {
+ public MediaSession2Impl(Context context, MediaSession2 instance, MediaPlayerInterface player,
+ String id, VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity,
+ Executor callbackExecutor, SessionCallback callback) {
mInstance = instance;
+ // TODO(jaewan): Keep other params.
// Argument checks are done by builder already.
// Initialize finals first.
mContext = context;
mId = id;
- mHandler = new Handler(Looper.myLooper());
mCallback = callback;
+ mCallbackExecutor = callbackExecutor;
mSessionStub = new MediaSession2Stub(this);
- // Ask server to create session token for following reasons.
- // 1. Make session ID unique per package.
- // Server can only know if the package has another process and has another session
- // with the same id. Let server check this.
- // Note that 'ID is unique per package' is important for controller to distinguish
- // a session in another package.
- // 2. Easier to know the type of session.
- // Session created here can be the session service token. In order distinguish,
- // we need to iterate AndroidManifest.xml but it's already done by the server.
- // Let server to create token with the type.
+
+ // Infer type from the id and package name.
+ String libraryService = getServiceName(context, MediaLibraryService2.SERVICE_INTERFACE, id);
+ String sessionService = getServiceName(context, MediaSessionService2.SERVICE_INTERFACE, id);
+ if (sessionService != null && libraryService != null) {
+ throw new IllegalArgumentException("Ambiguous session type. Multiple"
+ + " session services define the same id=" + id);
+ } else if (libraryService != null) {
+ mSessionToken = new SessionToken2Impl(context, Process.myUid(), TYPE_LIBRARY_SERVICE,
+ mContext.getPackageName(), libraryService, id, mSessionStub).getInstance();
+ } else if (sessionService != null) {
+ mSessionToken = new SessionToken2Impl(context, Process.myUid(), TYPE_SESSION_SERVICE,
+ mContext.getPackageName(), sessionService, id, mSessionStub).getInstance();
+ } else {
+ mSessionToken = new SessionToken2Impl(context, Process.myUid(), TYPE_SESSION,
+ mContext.getPackageName(), null, id, mSessionStub).getInstance();
+ }
+
+ // Only remember player. Actual settings will be done in the initialize().
+ mPlayer = player;
+ }
+
+ private static String getServiceName(Context context, String serviceAction, String id) {
+ PackageManager manager = context.getPackageManager();
+ Intent serviceIntent = new Intent(serviceAction);
+ serviceIntent.setPackage(context.getPackageName());
+ List<ResolveInfo> services = manager.queryIntentServices(serviceIntent,
+ PackageManager.GET_META_DATA);
+ String serviceName = null;
+ if (services != null) {
+ for (int i = 0; i < services.size(); i++) {
+ String serviceId = SessionToken2Impl.getSessionId(services.get(i));
+ if (serviceId != null && TextUtils.equals(id, serviceId)) {
+ if (services.get(i).serviceInfo == null) {
+ continue;
+ }
+ if (serviceName != null) {
+ throw new IllegalArgumentException("Ambiguous session type. Multiple"
+ + " session services define the same id=" + id);
+ }
+ serviceName = services.get(i).serviceInfo.name;
+ }
+ }
+ }
+ return serviceName;
+ }
+
+ @Override
+ public void initialize() {
+ synchronized (mLock) {
+ setPlayerLocked(mPlayer);
+ }
+ // Ask server for the sanity check, and starts
+ // Sanity check for making session ID unique 'per package' cannot be done in here.
+ // Server can only know if the package has another process and has another session with the
+ // same id. Note that 'ID is unique per package' is important for controller to distinguish
+ // a session in another package.
MediaSessionManager manager =
(MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
- mSessionToken = manager.createSessionToken(mContext.getPackageName(), mId, mSessionStub);
- if (mSessionToken == null) {
+ if (!manager.onSessionCreated(mSessionToken)) {
throw new IllegalStateException("Session with the same id is already used by"
+ " another process. Use MediaController2 instead.");
}
-
- setPlayerInternal(player);
}
// TODO(jaewan): Add explicit release() and do not remove session object with the
// setPlayer(null). Token can be available when player is null, and
// controller can also attach to session.
@Override
- public void setPlayer_impl(MediaPlayerBase player) throws IllegalArgumentException {
+ public void setPlayer_impl(MediaPlayerInterface player, VolumeProvider volumeProvider)
+ throws IllegalArgumentException {
ensureCallingThread();
- // TODO(jaewan): Remove this when we don't inherits MediaPlayerBase.
- if (player instanceof MediaSession2 || player instanceof MediaController2) {
- throw new IllegalArgumentException("player doesn't accept MediaSession2 nor"
- + " MediaController2");
+ if (player == null) {
+ throw new IllegalArgumentException("player shouldn't be null");
}
- if (player != null && mPlayer == player) {
- // Player didn't changed. No-op.
+ if (player == mPlayer) {
return;
}
- setPlayerInternal(player);
+ synchronized (mLock) {
+ setPlayerLocked(player);
+ }
}
- private void setPlayerInternal(MediaPlayerBase player) {
- mHandler.removeCallbacksAndMessages(null);
- if (mPlayer == null && player != null) {
- if (DEBUG) {
- Log.d(TAG, "session is ready to use, id=" + mId);
- }
- } else if (mPlayer != null && player == null) {
- if (DEBUG) {
- Log.d(TAG, "session is now unavailable, id=" + mId);
- }
- if (mSessionStub != null) {
- // Invalidate previously published session stub.
- mSessionStub.destroyNotLocked();
- }
- }
+ private void setPlayerLocked(MediaPlayerInterface player) {
if (mPlayer != null && mListener != null) {
// This might not work for a poorly implemented player.
mPlayer.removePlaybackListener(mListener);
}
- if (player != null) {
- mListener = new MyPlaybackListener(this, player);
- player.addPlaybackListener(mListener, mHandler);
- notifyPlaybackStateChanged(player.getPlaybackState());
- }
mPlayer = player;
+ mListener = new MyPlaybackListener(this, player);
+ player.addPlaybackListener(mCallbackExecutor, mListener);
}
@Override
- public MediaPlayerBase getPlayer_impl() {
+ public void close_impl() {
+ // Stop system service from listening this session first.
+ MediaSessionManager manager =
+ (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
+ manager.onSessionDestroyed(mSessionToken);
+
+ if (mSessionStub != null) {
+ if (DEBUG) {
+ Log.d(TAG, "session is now unavailable, id=" + mId);
+ }
+ // Invalidate previously published session stub.
+ mSessionStub.destroyNotLocked();
+ }
+ synchronized (mLock) {
+ if (mPlayer != null) {
+ // close can be called multiple times
+ mPlayer.removePlaybackListener(mListener);
+ mPlayer = null;
+ }
+ }
+ }
+
+ @Override
+ public MediaPlayerInterface getPlayer_impl() {
return getPlayer();
}
// TODO(jaewan): Change this to @NonNull
@Override
- public SessionToken getToken_impl() {
+ public SessionToken2 getToken_impl() {
return mSessionToken;
}
@@ -161,6 +242,11 @@
}
@Override
+ public void setAudioFocusRequest_impl(int focusGain) {
+ // implement
+ }
+
+ @Override
public void play_impl() {
ensureCallingThread();
ensurePlayer();
@@ -196,42 +282,160 @@
}
@Override
- public PlaybackState getPlaybackState_impl() {
+ public void setCustomLayout_impl(ControllerInfo controller, List<CommandButton> layout) {
ensureCallingThread();
- ensurePlayer();
- return mPlayer.getPlaybackState();
+ if (controller == null) {
+ throw new IllegalArgumentException("controller shouldn't be null");
+ }
+ if (layout == null) {
+ throw new IllegalArgumentException("layout shouldn't be null");
+ }
+ mSessionStub.notifyCustomLayoutNotLocked(controller, layout);
}
@Override
- public void addPlaybackListener_impl(
- MediaPlayerBase.PlaybackListener listener, Handler handler) {
+ public void setPlaylistParams_impl(PlaylistParams params) {
+ if (params == null) {
+ throw new IllegalArgumentException("PlaylistParams should not be null!");
+ }
+ ensureCallingThread();
+ ensurePlayer();
+ synchronized (mLock) {
+ mPlaylistParams = params;
+ }
+ mPlayer.setPlaylistParams(params);
+ mSessionStub.notifyPlaylistParamsChanged(params);
+ }
+
+ @Override
+ public PlaylistParams getPlaylistParams_impl() {
+ // TODO: Do we need to synchronize here for preparing Controller2.setPlaybackParams?
+ return mPlaylistParams;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ // TODO(jaewan): Implement follows
+ //////////////////////////////////////////////////////////////////////////////////////
+ @Override
+ public void setPlayer_impl(MediaPlayerInterface player) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void setAllowedCommands_impl(ControllerInfo controller, CommandGroup commands) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void notifyMetadataChanged_impl() {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
+ public void sendCustomCommand_impl(ControllerInfo controller, Command command, Bundle args,
+ ResultReceiver receiver) {
+ mSessionStub.sendCustomCommand(controller, command, args, receiver);
+ }
+
+ @Override
+ public void sendCustomCommand_impl(Command command, Bundle args) {
+ mSessionStub.sendCustomCommand(command, args);
+ }
+
+ @Override
+ public void setPlaylist_impl(List<MediaItem2> playlist) {
+ if (playlist == null) {
+ throw new IllegalArgumentException("Playlist should not be null!");
+ }
+ ensureCallingThread();
+ ensurePlayer();
+ synchronized (mLock) {
+ mPlaylist = playlist;
+ }
+ mPlayer.setPlaylist(playlist);
+ mSessionStub.notifyPlaylistChanged(playlist);
+ }
+
+ @Override
+ public List<MediaItem2> getPlaylist_impl() {
+ synchronized (mLock) {
+ return mPlaylist;
+ }
+ }
+
+ @Override
+ public void prepare_impl() {
+ ensureCallingThread();
+ ensurePlayer();
+ mPlayer.prepare();
+ }
+
+ @Override
+ public void fastForward_impl() {
+ ensureCallingThread();
+ ensurePlayer();
+ mPlayer.fastForward();
+ }
+
+ @Override
+ public void rewind_impl() {
+ ensureCallingThread();
+ ensurePlayer();
+ mPlayer.rewind();
+ }
+
+ @Override
+ public void seekTo_impl(long pos) {
+ ensureCallingThread();
+ ensurePlayer();
+ mPlayer.seekTo(pos);
+ }
+
+ @Override
+ public void setCurrentPlaylistItem_impl(int index) {
+ ensureCallingThread();
+ ensurePlayer();
+ mPlayer.setCurrentPlaylistItem(index);
+ }
+
+ @Override
+ public void addPlaybackListener_impl(Executor executor, PlaybackListener listener) {
+ if (executor == null) {
+ throw new IllegalArgumentException("executor shouldn't be null");
+ }
if (listener == null) {
throw new IllegalArgumentException("listener shouldn't be null");
}
- if (handler == null) {
- throw new IllegalArgumentException("handler shouldn't be null");
- }
ensureCallingThread();
if (PlaybackListenerHolder.contains(mListeners, listener)) {
Log.w(TAG, "listener is already added. Ignoring.");
return;
}
- mListeners.add(new PlaybackListenerHolder(listener, handler));
+ mListeners.add(new PlaybackListenerHolder(executor, listener));
+ executor.execute(() -> listener.onPlaybackChanged(getInstance().getPlaybackState()));
}
@Override
- public void removePlaybackListener_impl(MediaPlayerBase.PlaybackListener listener) {
+ public void removePlaybackListener_impl(PlaybackListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener shouldn't be null");
}
ensureCallingThread();
int idx = PlaybackListenerHolder.indexOf(mListeners, listener);
if (idx >= 0) {
- mListeners.get(idx).removeCallbacksAndMessages(null);
mListeners.remove(idx);
}
}
+ @Override
+ public PlaybackState2 getPlaybackState_impl() {
+ ensureCallingThread();
+ ensurePlayer();
+ // TODO(jaewan): Is it safe to be called on any thread?
+ // Otherwise we should cache the result from listener.
+ return mPlayer.getPlaybackState();
+ }
+
///////////////////////////////////////////////////
// Protected or private methods
///////////////////////////////////////////////////
@@ -248,11 +452,12 @@
// 1. Allow calls from random threads for all methods.
// 2. Allow calls from random threads for all methods, except for the
// {@link #setPlayer()}.
- // TODO(jaewan): Should we pend command instead of exception?
private void ensureCallingThread() {
+ // TODO(jaewan): Uncomment or remove
+ /*
if (mHandler.getLooper() != Looper.myLooper()) {
throw new IllegalStateException("Run this on the given thread");
- }
+ }*/
}
private void ensurePlayer() {
@@ -263,14 +468,14 @@
}
}
- Handler getHandler() {
- return mHandler;
- }
-
- private void notifyPlaybackStateChanged(PlaybackState state) {
+ private void notifyPlaybackStateChangedNotLocked(PlaybackState2 state) {
+ List<PlaybackListenerHolder> listeners = new ArrayList<>();
+ synchronized (mLock) {
+ listeners.addAll(mListeners);
+ }
// Notify to listeners added directly to this session
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).postPlaybackChange(state);
+ for (int i = 0; i < listeners.size(); i++) {
+ listeners.get(i).postPlaybackChange(state);
}
// Notify to controllers as well.
mSessionStub.notifyPlaybackStateChangedNotLocked(state);
@@ -284,33 +489,126 @@
return mInstance;
}
+ MediaPlayerInterface getPlayer() {
+ return mPlayer;
+ }
+
+ Executor getCallbackExecutor() {
+ return mCallbackExecutor;
+ }
+
SessionCallback getCallback() {
return mCallback;
}
- MediaPlayerBase getPlayer() {
- return mPlayer;
- }
-
- private static class MyPlaybackListener implements MediaPlayerBase.PlaybackListener {
+ private static class MyPlaybackListener implements MediaPlayerInterface.PlaybackListener {
private final WeakReference<MediaSession2Impl> mSession;
- private final MediaPlayerBase mPlayer;
+ private final MediaPlayerInterface mPlayer;
- private MyPlaybackListener(MediaSession2Impl session, MediaPlayerBase player) {
+ private MyPlaybackListener(MediaSession2Impl session, MediaPlayerInterface player) {
mSession = new WeakReference<>(session);
mPlayer = player;
}
@Override
- public void onPlaybackChanged(PlaybackState state) {
+ public void onPlaybackChanged(PlaybackState2 state) {
MediaSession2Impl session = mSession.get();
- if (session == null || session.getHandler().getLooper() != Looper.myLooper()
- || mPlayer != session.mInstance.getPlayer()) {
+ if (mPlayer != session.mInstance.getPlayer()) {
Log.w(TAG, "Unexpected playback state change notifications. Ignoring.",
new IllegalStateException());
return;
}
- session.notifyPlaybackStateChanged(state);
+ session.notifyPlaybackStateChangedNotLocked(state);
+ }
+ }
+
+ public static final class CommandImpl implements CommandProvider {
+ private static final String KEY_COMMAND_CODE
+ = "android.media.media_session2.command.command_code";
+ private static final String KEY_COMMAND_CUSTOM_COMMAND
+ = "android.media.media_session2.command.custom_command";
+ private static final String KEY_COMMAND_EXTRA
+ = "android.media.media_session2.command.extra";
+
+ private final Command mInstance;
+ private final int mCommandCode;
+ // Nonnull if it's custom command
+ private final String mCustomCommand;
+ private final Bundle mExtra;
+
+ public CommandImpl(Command instance, int commandCode) {
+ mInstance = instance;
+ mCommandCode = commandCode;
+ mCustomCommand = null;
+ mExtra = null;
+ }
+
+ public CommandImpl(Command instance, @NonNull String action, @Nullable Bundle extra) {
+ if (action == null) {
+ throw new IllegalArgumentException("action shouldn't be null");
+ }
+ mInstance = instance;
+ mCommandCode = MediaSession2.COMMAND_CODE_CUSTOM;
+ mCustomCommand = action;
+ mExtra = extra;
+ }
+
+ public int getCommandCode_impl() {
+ return mCommandCode;
+ }
+
+ public @Nullable String getCustomCommand_impl() {
+ return mCustomCommand;
+ }
+
+ public @Nullable Bundle getExtra_impl() {
+ return mExtra;
+ }
+
+ /**
+ * @ 7return a new Bundle instance from the Command
+ */
+ public Bundle toBundle_impl() {
+ Bundle bundle = new Bundle();
+ bundle.putInt(KEY_COMMAND_CODE, mCommandCode);
+ bundle.putString(KEY_COMMAND_CUSTOM_COMMAND, mCustomCommand);
+ bundle.putBundle(KEY_COMMAND_EXTRA, mExtra);
+ return bundle;
+ }
+
+ /**
+ * @return a new Command instance from the Bundle
+ */
+ public static Command fromBundle_impl(Context context, Bundle command) {
+ int code = command.getInt(KEY_COMMAND_CODE);
+ if (code != MediaSession2.COMMAND_CODE_CUSTOM) {
+ return new Command(context, code);
+ } else {
+ String customCommand = command.getString(KEY_COMMAND_CUSTOM_COMMAND);
+ if (customCommand == null) {
+ return null;
+ }
+ return new Command(context, customCommand, command.getBundle(KEY_COMMAND_EXTRA));
+ }
+ }
+
+ @Override
+ public boolean equals_impl(Object obj) {
+ if (!(obj instanceof CommandImpl)) {
+ return false;
+ }
+ CommandImpl other = (CommandImpl) obj;
+ // TODO(jaewan): Should we also compare contents in bundle?
+ // It may not be possible if the bundle contains private class.
+ return mCommandCode == other.mCommandCode
+ && TextUtils.equals(mCustomCommand, other.mCustomCommand);
+ }
+
+ @Override
+ public int hashCode_impl() {
+ final int prime = 31;
+ return ((mCustomCommand != null)
+ ? mCustomCommand.hashCode() : 0) * prime + mCommandCode;
}
}
@@ -321,12 +619,7 @@
private final boolean mIsTrusted;
private final IMediaSession2Callback mControllerBinder;
- // Flag to indicate which callbacks should be returned for the controller binder.
- // Either 0 or combination of {@link #CALLBACK_FLAG_PLAYBACK},
- // {@link #CALLBACK_FLAG_SESSION_ACTIVENESS}
- private int mFlag;
-
- public ControllerInfoImpl(ControllerInfo instance, Context context, int uid,
+ public ControllerInfoImpl(Context context, ControllerInfo instance, int uid,
int pid, String packageName, IMediaSession2Callback callback) {
mInstance = instance;
mUid = uid;
@@ -409,16 +702,8 @@
return mControllerBinder;
}
- public boolean containsFlag(int flag) {
- return (mFlag & flag) != 0;
- }
-
- public void addFlag(int flag) {
- mFlag |= flag;
- }
-
- public void removeFlag(int flag) {
- mFlag &= ~flag;
+ public static ControllerInfoImpl from(ControllerInfo controller) {
+ return (ControllerInfoImpl) controller.getProvider();
}
}
}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index bae02e5..4bb5f47 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -16,57 +16,56 @@
package com.android.media;
-import static com.android.media.MediaController2Impl.CALLBACK_FLAG_PLAYBACK;
-
import android.content.Context;
-import android.media.IMediaSession2;
-import android.media.IMediaSession2Callback;
+import android.media.MediaItem2;
+import android.media.MediaLibraryService2.BrowserRoot;
+import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
import android.media.MediaSession2;
import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
-import android.media.session.PlaybackState;
+import android.media.MediaSession2.PlaylistParams;
+import android.media.PlaybackState2;
import android.os.Binder;
import android.os.Bundle;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.support.annotation.GuardedBy;
import android.util.ArrayMap;
import android.util.Log;
+
import com.android.media.MediaSession2Impl.ControllerInfoImpl;
+
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
-// TODO(jaewan): Add a hook for media apps to log which app requested specific command.
-// TODO(jaewan): Add a way to block specific command from a specific app. Also add supported
-// command per apps.
public class MediaSession2Stub extends IMediaSession2.Stub {
+
+ static final String ARGUMENT_KEY_POSITION = "android.media.media_session2.key_position";
+ static final String ARGUMENT_KEY_ITEM_INDEX = "android.media.media_session2.key_item_index";
+ static final String ARGUMENT_KEY_PLAYLIST_PARAMS =
+ "android.media.media_session2.key_playlist_params";
+
private static final String TAG = "MediaSession2Stub";
private static final boolean DEBUG = true; // TODO(jaewan): Rename.
private final Object mLock = new Object();
- private final CommandHandler mCommandHandler;
private final WeakReference<MediaSession2Impl> mSession;
- private final Context mContext;
@GuardedBy("mLock")
private final ArrayMap<IBinder, ControllerInfo> mControllers = new ArrayMap<>();
public MediaSession2Stub(MediaSession2Impl session) {
mSession = new WeakReference<>(session);
- mContext = session.getContext();
- mCommandHandler = new CommandHandler(session.getHandler().getLooper());
}
public void destroyNotLocked() {
final List<ControllerInfo> list;
synchronized (mLock) {
mSession.clear();
- mCommandHandler.removeCallbacksAndMessages(null);
list = getControllers();
mControllers.clear();
}
@@ -91,14 +90,61 @@
}
@Override
- public void connect(String callingPackage, IMediaSession2Callback callback) {
- if (callback == null) {
- // Requesting connect without callback to receive result.
- return;
- }
- ControllerInfo request = new ControllerInfo(mContext,
+ public void connect(String callingPackage, final IMediaSession2Callback callback)
+ throws RuntimeException {
+ final MediaSession2Impl sessionImpl = getSession();
+ final Context context = sessionImpl.getContext();
+ final ControllerInfo request = new ControllerInfo(context,
Binder.getCallingUid(), Binder.getCallingPid(), callingPackage, callback);
- mCommandHandler.postConnect(request);
+ sessionImpl.getCallbackExecutor().execute(() -> {
+ final MediaSession2Impl session = mSession.get();
+ if (session == null) {
+ return;
+ }
+ CommandGroup allowedCommands = session.getCallback().onConnect(request);
+ // Don't reject connection for the request from trusted app.
+ // Otherwise server will fail to retrieve session's information to dispatch
+ // media keys to.
+ boolean accept = allowedCommands != null || request.isTrusted();
+ ControllerInfoImpl impl = ControllerInfoImpl.from(request);
+ if (accept) {
+ synchronized (mLock) {
+ mControllers.put(impl.getId(), request);
+ }
+ if (allowedCommands == null) {
+ // For trusted apps, send non-null allowed commands to keep connection.
+ allowedCommands = new CommandGroup(context);
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "onConnectResult, request=" + request
+ + " accept=" + accept);
+ }
+ try {
+ callback.onConnectionChanged(
+ accept ? MediaSession2Stub.this : null,
+ allowedCommands == null ? null : allowedCommands.toBundle());
+ } catch (RemoteException e) {
+ // Controller may be died prematurely.
+ }
+ if (accept) {
+ // If connection is accepted, notify the current state to the controller.
+ // It's needed because we cannot call synchronous calls between session/controller.
+ // Note: We're doing this after the onConnectionChanged(), but there's no guarantee
+ // that events here are notified after the onConnected() because
+ // IMediaSession2Callback is oneway (i.e. async call) and CallbackStub will
+ // use thread poll for incoming calls.
+ // TODO(jaewan): Should we protect getting playback state?
+ final PlaybackState2 state = session.getInstance().getPlaybackState();
+ final Bundle bundle = state != null ? state.toBundle() : null;
+ try {
+ callback.onPlaybackStateChanged(bundle);
+ } catch (RemoteException e) {
+ // TODO(jaewan): Handle this.
+ // Controller may be died prematurely.
+ }
+ }
+ });
}
@Override
@@ -114,60 +160,148 @@
@Override
public void sendCommand(IMediaSession2Callback caller, Bundle command, Bundle args)
throws RuntimeException {
- ControllerInfo controller = getController(caller);
+ // TODO(jaewan): Generic command
+ }
+
+ @Override
+ public void sendTransportControlCommand(IMediaSession2Callback caller,
+ int commandCode, Bundle args) throws RuntimeException {
+ final MediaSession2Impl sessionImpl = getSession();
+ final ControllerInfo controller = getController(caller);
if (controller == null) {
if (DEBUG) {
Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
}
return;
}
- mCommandHandler.postCommand(controller, Command.fromBundle(command), args);
- }
-
- @Deprecated
- @Override
- public PlaybackState getPlaybackState() throws RemoteException {
- MediaSession2Impl session = getSession();
- // TODO(jaewan): Check if mPlayer.getPlaybackState() is safe here.
- return session.getInstance().getPlayer().getPlaybackState();
- }
-
- @Deprecated
- @Override
- public void registerCallback(final IMediaSession2Callback callbackBinder,
- final int callbackFlag, final int requestCode) throws RemoteException {
- // TODO(jaewan): Call onCommand() here. To do so, you should pend message.
- synchronized (mLock) {
- ControllerInfo controllerInfo = getController(callbackBinder);
- if (controllerInfo == null) {
+ sessionImpl.getCallbackExecutor().execute(() -> {
+ final MediaSession2Impl session = mSession.get();
+ if (session == null) {
return;
}
- ((ControllerInfoImpl) controllerInfo.getProvider()).addFlag(callbackFlag);
- }
- }
-
- @Deprecated
- @Override
- public void unregisterCallback(IMediaSession2Callback callbackBinder, int callbackFlag)
- throws RemoteException {
- // TODO(jaewan): Call onCommand() here. To do so, you should pend message.
- synchronized (mLock) {
- ControllerInfo controllerInfo = getController(callbackBinder);
- if (controllerInfo == null) {
+ // TODO(jaewan): Sanity check.
+ Command command = new Command(session.getContext(), commandCode);
+ boolean accepted = session.getCallback().onCommandRequest(controller, command);
+ if (!accepted) {
+ // Don't run rejected command.
+ if (DEBUG) {
+ Log.d(TAG, "Command " + commandCode + " from "
+ + controller + " was rejected by " + session);
+ }
return;
}
- ControllerInfoImpl impl =
- ((ControllerInfoImpl) controllerInfo.getProvider());
- impl.removeFlag(callbackFlag);
+
+ switch (commandCode) {
+ case MediaSession2.COMMAND_CODE_PLAYBACK_START:
+ session.getInstance().play();
+ break;
+ case MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE:
+ session.getInstance().pause();
+ break;
+ case MediaSession2.COMMAND_CODE_PLAYBACK_STOP:
+ session.getInstance().stop();
+ break;
+ case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM:
+ session.getInstance().skipToPrevious();
+ break;
+ case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM:
+ session.getInstance().skipToNext();
+ break;
+ case MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE:
+ session.getInstance().prepare();
+ break;
+ case MediaSession2.COMMAND_CODE_PLAYBACK_FAST_FORWARD:
+ session.getInstance().fastForward();
+ break;
+ case MediaSession2.COMMAND_CODE_PLAYBACK_REWIND:
+ session.getInstance().rewind();
+ break;
+ case MediaSession2.COMMAND_CODE_PLAYBACK_SEEK_TO:
+ session.getInstance().seekTo(args.getLong(ARGUMENT_KEY_POSITION));
+ break;
+ case MediaSession2.COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM:
+ session.getInstance().setCurrentPlaylistItem(
+ args.getInt(ARGUMENT_KEY_ITEM_INDEX));
+ break;
+ case MediaSession2.COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS:
+ session.getInstance().setPlaylistParams(
+ PlaylistParams.fromBundle(
+ args.getBundle(ARGUMENT_KEY_PLAYLIST_PARAMS)));
+ break;
+ default:
+ // TODO(jaewan): Resend unknown (new) commands through the custom command.
+ }
+ });
+ }
+
+ @Override
+ public void sendCustomCommand(final IMediaSession2Callback caller, final Bundle commandBundle,
+ final Bundle args, final ResultReceiver receiver) {
+ final MediaSession2Impl sessionImpl = getSession();
+ final ControllerInfo controller = getController(caller);
+ if (controller == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
+ }
+ return;
}
+ sessionImpl.getCallbackExecutor().execute(() -> {
+ final MediaSession2Impl session = mSession.get();
+ if (session == null) {
+ return;
+ }
+ final Command command = Command.fromBundle(session.getContext(), commandBundle);
+ session.getCallback().onCustomCommand(controller, command, args, receiver);
+ });
+ }
+
+ @Override
+ public void getBrowserRoot(IMediaSession2Callback caller, Bundle rootHints)
+ throws RuntimeException {
+ final MediaSession2Impl sessionImpl = getSession();
+ if (!(sessionImpl.getCallback() instanceof MediaLibrarySessionCallback)) {
+ if (DEBUG) {
+ Log.d(TAG, "Session cannot hand getBrowserRoot()");
+ }
+ return;
+ }
+ final ControllerInfo controller = getController(caller);
+ if (controller == null) {
+ if (DEBUG) {
+ Log.d(TAG, "getBrowerRoot from a controller that hasn't connected. Ignore");
+ }
+ return;
+ }
+ sessionImpl.getCallbackExecutor().execute(() -> {
+ final MediaSession2Impl session = mSession.get();
+ if (session == null) {
+ return;
+ }
+ final MediaLibrarySessionCallback libraryCallback =
+ (MediaLibrarySessionCallback) session.getCallback();
+ final ControllerInfoImpl controllerImpl = ControllerInfoImpl.from(controller);
+ BrowserRoot root = libraryCallback.onGetRoot(controller, rootHints);
+ try {
+ controllerImpl.getControllerBinder().onGetRootResult(rootHints,
+ root == null ? null : root.getRootId(),
+ root == null ? null : root.getExtras());
+ } catch (RemoteException e) {
+ // Controller may be died prematurely.
+ // TODO(jaewan): Handle this.
+ }
+ });
}
private ControllerInfo getController(IMediaSession2Callback caller) {
+ // TODO(jaewan): Device a way to return connection-in-progress-controller
+ // to be included here, because session owner may want to send some datas
+ // while onConnected() hasn't returned.
synchronized (mLock) {
return mControllers.get(caller.asBinder());
}
}
+ // TODO(jaewan): Need a way to get controller with permissions
public List<ControllerInfo> getControllers() {
ArrayList<ControllerInfo> controllers = new ArrayList<>();
synchronized (mLock) {
@@ -178,28 +312,15 @@
return controllers;
}
- public List<ControllerInfo> getControllersWithFlag(int flag) {
- ArrayList<ControllerInfo> controllers = new ArrayList<>();
- synchronized (mLock) {
- for (int i = 0; i < mControllers.size(); i++) {
- ControllerInfo controllerInfo = mControllers.valueAt(i);
- if (((ControllerInfoImpl) controllerInfo.getProvider()).containsFlag(flag)) {
- controllers.add(controllerInfo);
- }
- }
- }
- return controllers;
- }
-
// Should be used without a lock to prevent potential deadlock.
- public void notifyPlaybackStateChangedNotLocked(PlaybackState state) {
- final List<ControllerInfo> list = getControllersWithFlag(CALLBACK_FLAG_PLAYBACK);
+ public void notifyPlaybackStateChangedNotLocked(PlaybackState2 state) {
+ final List<ControllerInfo> list = getControllers();
for (int i = 0; i < list.size(); i++) {
IMediaSession2Callback callbackBinder =
- ((ControllerInfoImpl) list.get(i).getProvider())
- .getControllerBinder();
+ ControllerInfoImpl.from(list.get(i)).getControllerBinder();
try {
- callbackBinder.onPlaybackStateChanged(state);
+ final Bundle bundle = state != null ? state.toBundle() : null;
+ callbackBinder.onPlaybackStateChanged(bundle);
} catch (RemoteException e) {
Log.w(TAG, "Controller is gone", e);
// TODO(jaewan): What to do when the controller is gone?
@@ -207,103 +328,103 @@
}
}
- private class CommandHandler extends Handler {
- public static final int MSG_CONNECT = 1000;
- public static final int MSG_COMMAND = 1001;
-
- public CommandHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- final MediaSession2Impl session = MediaSession2Stub.this.mSession.get();
- if (session == null || session.getPlayer() == null) {
- return;
+ public void notifyCustomLayoutNotLocked(ControllerInfo controller, List<CommandButton> layout) {
+ // TODO(jaewan): It's OK to be called while it's connecting, but not OK if the connection
+ // is rejected. Handle the case.
+ IMediaSession2Callback callbackBinder =
+ ControllerInfoImpl.from(controller).getControllerBinder();
+ try {
+ List<Bundle> layoutBundles = new ArrayList<>();
+ for (int i = 0; i < layout.size(); i++) {
+ Bundle bundle = layout.get(i).toBundle();
+ if (bundle != null) {
+ layoutBundles.add(bundle);
+ }
}
-
- switch (msg.what) {
- case MSG_CONNECT:
- ControllerInfo request = (ControllerInfo) msg.obj;
- CommandGroup allowedCommands = session.getCallback().onConnect(request);
- // Don't reject connection for the request from trusted app.
- // Otherwise server will fail to retrieve session's information to dispatch
- // media keys to.
- boolean accept = allowedCommands != null || request.isTrusted();
- ControllerInfoImpl impl = (ControllerInfoImpl) request.getProvider();
- if (accept) {
- synchronized (mLock) {
- mControllers.put(impl.getId(), request);
- }
- }
- if (DEBUG) {
- Log.d(TAG, "onConnectResult, request=" + request
- + " accept=" + accept);
- }
- try {
- impl.getControllerBinder().onConnectionChanged(
- accept ? MediaSession2Stub.this : null,
- allowedCommands == null ? null : allowedCommands.toBundle());
- } catch (RemoteException e) {
- // Controller may be died prematurely.
- }
- break;
- case MSG_COMMAND:
- CommandParam param = (CommandParam) msg.obj;
- Command command = param.command;
- boolean accepted = session.getCallback().onCommandRequest(
- param.controller, command);
- if (!accepted) {
- // Don't run rejected command.
- if (DEBUG) {
- Log.d(TAG, "Command " + command + " from "
- + param.controller + " was rejected by " + session);
- }
- return;
- }
-
- switch (param.command.getCommandCode()) {
- case MediaSession2.COMMAND_CODE_PLAYBACK_START:
- session.getInstance().play();
- break;
- case MediaSession2.COMMAND_CODE_PLAYBACK_PAUSE:
- session.getInstance().pause();
- break;
- case MediaSession2.COMMAND_CODE_PLAYBACK_STOP:
- session.getInstance().stop();
- break;
- case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM:
- session.getInstance().skipToPrevious();
- break;
- case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM:
- session.getInstance().skipToNext();
- break;
- default:
- // TODO(jaewan): Handle custom command.
- }
- break;
- }
- }
-
- public void postConnect(ControllerInfo request) {
- obtainMessage(MSG_CONNECT, request).sendToTarget();
- }
-
- public void postCommand(ControllerInfo controller, Command command, Bundle args) {
- CommandParam param = new CommandParam(controller, command, args);
- obtainMessage(MSG_COMMAND, param).sendToTarget();
+ callbackBinder.onCustomLayoutChanged(layoutBundles);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
}
}
- private static class CommandParam {
- public final ControllerInfo controller;
- public final Command command;
- public final Bundle args;
+ public void notifyPlaylistChanged(List<MediaItem2> playlist) {
+ if (playlist == null) {
+ return;
+ }
+ final List<Bundle> bundleList = new ArrayList<>();
+ for (int i = 0; i < playlist.size(); i++) {
+ if (playlist.get(i) != null) {
+ Bundle bundle = playlist.get(i).toBundle();
+ if (bundle != null) {
+ bundleList.add(bundle);
+ }
+ }
+ }
+ final List<ControllerInfo> list = getControllers();
+ for (int i = 0; i < list.size(); i++) {
+ IMediaSession2Callback callbackBinder =
+ ControllerInfoImpl.from(list.get(i)).getControllerBinder();
+ try {
+ callbackBinder.onPlaylistChanged(bundleList);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ }
+ }
- private CommandParam(ControllerInfo controller, Command command, Bundle args) {
- this.controller = controller;
- this.command = command;
- this.args = args;
+ public void notifyPlaylistParamsChanged(MediaSession2.PlaylistParams params) {
+ final List<ControllerInfo> list = getControllers();
+ for (int i = 0; i < list.size(); i++) {
+ IMediaSession2Callback callbackBinder =
+ ControllerInfoImpl.from(list.get(i)).getControllerBinder();
+ try {
+ callbackBinder.onPlaylistParamsChanged(params.toBundle());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
+ }
+ }
+ }
+
+ public void sendCustomCommand(ControllerInfo controller, Command command, Bundle args,
+ ResultReceiver receiver) {
+ if (receiver != null && controller == null) {
+ throw new IllegalArgumentException("Controller shouldn't be null if result receiver is"
+ + " specified");
+ }
+ if (command == null) {
+ throw new IllegalArgumentException("command shouldn't be null");
+ }
+ final IMediaSession2Callback callbackBinder =
+ ControllerInfoImpl.from(controller).getControllerBinder();
+ if (getController(callbackBinder) == null) {
+ throw new IllegalArgumentException("Controller is gone");
+ }
+ sendCustomCommandInternal(controller, command, args, receiver);
+ }
+
+ public void sendCustomCommand(Command command, Bundle args) {
+ if (command == null) {
+ throw new IllegalArgumentException("command shouldn't be null");
+ }
+ final List<ControllerInfo> controllers = getControllers();
+ for (int i = 0; i < controllers.size(); i++) {
+ sendCustomCommand(controllers.get(i), command, args, null);
+ }
+ }
+
+ private void sendCustomCommandInternal(ControllerInfo controller, Command command, Bundle args,
+ ResultReceiver receiver) {
+ final IMediaSession2Callback callbackBinder =
+ ControllerInfoImpl.from(controller).getControllerBinder();
+ try {
+ Bundle commandBundle = command.toBundle();
+ callbackBinder.sendCustomCommand(commandBundle, args, receiver);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Controller is gone", e);
+ // TODO(jaewan): What to do when the controller is gone?
}
}
}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
index 6a4760d..7dce109 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
@@ -17,27 +17,23 @@
package com.android.media;
import static android.content.Context.NOTIFICATION_SERVICE;
-import static android.media.MediaSessionService2.DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID;
-import static android.media.MediaSessionService2.DEFAULT_MEDIA_NOTIFICATION_ID;
-import android.app.Notification;
-import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.media.MediaPlayerBase.PlaybackListener;
+import android.media.MediaPlayerInterface.PlaybackListener;
import android.media.MediaSession2;
import android.media.MediaSessionService2;
import android.media.MediaSessionService2.MediaNotification;
+import android.media.PlaybackState2;
+import android.media.SessionToken2;
+import android.media.SessionToken2.TokenType;
import android.media.session.PlaybackState;
import android.media.update.MediaSessionService2Provider;
import android.os.IBinder;
-import android.os.Looper;
import android.support.annotation.GuardedBy;
import android.util.Log;
-// Need a test for session service itself.
+// TODO(jaewan): Need a test for session service itself.
public class MediaSessionService2Impl implements MediaSessionService2Provider {
private static final String TAG = "MPSessionService"; // to meet 23 char limit in Log tag
@@ -53,7 +49,6 @@
private Intent mStartSelfIntent;
private boolean mIsRunningForeground;
- private NotificationChannel mDefaultNotificationChannel;
private MediaSession2 mSession;
public MediaSessionService2Impl(MediaSessionService2 instance) {
@@ -65,76 +60,56 @@
@Override
public MediaSession2 getSession_impl() {
+ return getSession();
+ }
+
+ MediaSession2 getSession() {
synchronized (mLock) {
return mSession;
}
}
@Override
- public MediaNotification onUpdateNotification_impl(PlaybackState state) {
- return createDefaultNotification(state);
+ public MediaNotification onUpdateNotification_impl(PlaybackState2 state) {
+ // Provide default notification UI later.
+ return null;
}
- // TODO(jaewan): Remove this for framework release.
- private MediaNotification createDefaultNotification(PlaybackState state) {
- // TODO(jaewan): Place better notification here.
- if (mDefaultNotificationChannel == null) {
- mDefaultNotificationChannel = new NotificationChannel(
- DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
- DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
- NotificationManager.IMPORTANCE_DEFAULT);
- mNotificationManager.createNotificationChannel(mDefaultNotificationChannel);
- }
- Notification notification = new Notification.Builder(
- mInstance, DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID)
- .setContentTitle(mInstance.getPackageName())
- .setContentText("Playback state: " + state.getState())
- .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
- return MediaNotification.create(DEFAULT_MEDIA_NOTIFICATION_ID, notification);
- }
-
-
@Override
public void onCreate_impl() {
mNotificationManager = (NotificationManager) mInstance.getSystemService(
NOTIFICATION_SERVICE);
mStartSelfIntent = new Intent(mInstance, mInstance.getClass());
- Intent serviceIntent = new Intent(mInstance, mInstance.getClass());
- serviceIntent.setAction(MediaSessionService2.SERVICE_INTERFACE);
- ResolveInfo resolveInfo = mInstance.getPackageManager()
- .resolveService(serviceIntent,
- PackageManager.GET_META_DATA);
- String id;
- if (resolveInfo == null || resolveInfo.serviceInfo == null) {
- throw new IllegalArgumentException("service " + mInstance + " doesn't implement"
- + MediaSessionService2.SERVICE_INTERFACE);
- } else if (resolveInfo.serviceInfo.metaData == null) {
- if (DEBUG) {
- Log.d(TAG, "Failed to resolve ID for " + mInstance + ". Using empty id");
- }
- id = "";
- } else {
- id = resolveInfo.serviceInfo.metaData.getString(
- MediaSessionService2.SERVICE_META_DATA, "");
+ SessionToken2 token = new SessionToken2(mInstance, mInstance.getPackageName(),
+ mInstance.getClass().getName());
+ if (token.getType() != getSessionType()) {
+ throw new RuntimeException("Expected session service, but was " + token.getType());
}
- mSession = mInstance.onCreateSession(id);
- if (mSession == null || !id.equals(mSession.getToken().getId())) {
- throw new RuntimeException("Expected session with id " + id + ", but got " + mSession);
+ mSession = mInstance.onCreateSession(token.getId());
+ if (mSession == null || !token.getId().equals(mSession.getToken().getId())) {
+ throw new RuntimeException("Expected session with id " + token.getId()
+ + ", but got " + mSession);
}
+ // TODO(jaewan): Uncomment here.
+ // mSession.addPlaybackListener(mListener, mSession.getExecutor());
+ }
+
+ @TokenType int getSessionType() {
+ return SessionToken2.TYPE_SESSION_SERVICE;
}
public IBinder onBind_impl(Intent intent) {
if (MediaSessionService2.SERVICE_INTERFACE.equals(intent.getAction())) {
- return mSession.getToken().getSessionBinder().asBinder();
+ return SessionToken2Impl.from(mSession.getToken()).getSessionBinder().asBinder();
}
return null;
}
- private void updateNotification(PlaybackState state) {
+ private void updateNotification(PlaybackState2 state) {
MediaNotification mediaNotification = mInstance.onUpdateNotification(state);
if (mediaNotification == null) {
- mediaNotification = createDefaultNotification(state);
+ return;
}
switch((int) state.getState()) {
case PlaybackState.STATE_PLAYING:
@@ -158,17 +133,12 @@
private class SessionServicePlaybackListener implements PlaybackListener {
@Override
- public void onPlaybackChanged(PlaybackState state) {
+ public void onPlaybackChanged(PlaybackState2 state) {
if (state == null) {
Log.w(TAG, "Ignoring null playback state");
return;
}
MediaSession2Impl impl = (MediaSession2Impl) mSession.getProvider();
- if (impl.getHandler().getLooper() != Looper.myLooper()) {
- Log.w(TAG, "Ignoring " + state + ". Expected " + impl.getHandler().getLooper()
- + " but " + Looper.myLooper());
- return;
- }
updateNotification(state);
}
}
diff --git a/packages/MediaComponents/src/com/android/media/PlaybackListenerHolder.java b/packages/MediaComponents/src/com/android/media/PlaybackListenerHolder.java
index 4d06463..4241f85 100644
--- a/packages/MediaComponents/src/com/android/media/PlaybackListenerHolder.java
+++ b/packages/MediaComponents/src/com/android/media/PlaybackListenerHolder.java
@@ -16,39 +16,28 @@
package com.android.media;
-import android.media.MediaPlayerBase;
-import android.media.MediaPlayerBase.PlaybackListener;
-import android.media.session.PlaybackState;
+import android.media.MediaPlayerInterface.PlaybackListener;
+import android.media.PlaybackState2;
import android.os.Handler;
-import android.os.Message;
import android.support.annotation.NonNull;
+
import java.util.List;
+import java.util.concurrent.Executor;
/**
- * Holds {@link android.media.MediaPlayerBase.PlaybackListener} with the {@link Handler}.
+ * Holds {@link PlaybackListener} with the {@link Handler}.
*/
-public class PlaybackListenerHolder extends Handler {
- private static final int ON_PLAYBACK_CHANGED = 1;
+public class PlaybackListenerHolder {
+ public final Executor executor;
+ public final PlaybackListener listener;
- public final MediaPlayerBase.PlaybackListener listener;
-
- public PlaybackListenerHolder(
- @NonNull MediaPlayerBase.PlaybackListener listener, @NonNull Handler handler) {
- super(handler.getLooper());
+ public PlaybackListenerHolder(Executor executor, @NonNull PlaybackListener listener) {
+ this.executor = executor;
this.listener = listener;
}
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case ON_PLAYBACK_CHANGED:
- listener.onPlaybackChanged((PlaybackState) msg.obj);
- break;
- }
- }
-
- public void postPlaybackChange(PlaybackState state) {
- obtainMessage(ON_PLAYBACK_CHANGED, state).sendToTarget();
+ public void postPlaybackChange(final PlaybackState2 state) {
+ executor.execute(() -> listener.onPlaybackChanged(state));
}
/**
diff --git a/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java b/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java
new file mode 100644
index 0000000..b2b7959
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/SessionToken2Impl.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2018 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.
+ */
+
+package com.android.media;
+
+import static android.media.SessionToken2.TYPE_SESSION;
+import static android.media.SessionToken2.TYPE_SESSION_SERVICE;
+import static android.media.SessionToken2.TYPE_LIBRARY_SERVICE;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.media.MediaLibraryService2;
+import android.media.MediaSessionService2;
+import android.media.SessionToken2;
+import android.media.SessionToken2.TokenType;
+import android.media.update.SessionToken2Provider;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.text.TextUtils;
+
+public class SessionToken2Impl implements SessionToken2Provider {
+ private static final String KEY_UID = "android.media.token.uid";
+ private static final String KEY_TYPE = "android.media.token.type";
+ private static final String KEY_PACKAGE_NAME = "android.media.token.package_name";
+ private static final String KEY_SERVICE_NAME = "android.media.token.service_name";
+ private static final String KEY_ID = "android.media.token.id";
+ private static final String KEY_SESSION_BINDER = "android.media.token.session_binder";
+
+ private final SessionToken2 mInstance;
+ private final int mUid;
+ private final @TokenType int mType;
+ private final String mPackageName;
+ private final String mServiceName;
+ private final String mId;
+ private final IMediaSession2 mSessionBinder;
+
+ /**
+ * Public constructor for the legacy support (i.e. browser can try connecting to any browser service
+ * if it knows the service name)
+ */
+ public SessionToken2Impl(Context context, SessionToken2 instance,
+ String packageName, String serviceName, int uid) {
+ if (TextUtils.isEmpty(packageName)) {
+ throw new IllegalArgumentException("package name shouldn't be null");
+ }
+ if (TextUtils.isEmpty(serviceName)) {
+ throw new IllegalArgumentException("service name shouldn't be null");
+ }
+ mInstance = instance;
+ // Calculate uid if it's not specified.
+ final PackageManager manager = context.getPackageManager();
+ if (uid < 0) {
+ try {
+ uid = manager.getPackageUid(packageName, 0);
+ } catch (NameNotFoundException e) {
+ throw new IllegalArgumentException("Cannot find package " + packageName);
+ }
+ }
+ mUid = uid;
+ // calculate id and type
+ Intent serviceIntent = new Intent(MediaLibraryService2.SERVICE_INTERFACE);
+ serviceIntent.setClassName(packageName, serviceName);
+ String id = getSessionId(manager.resolveService(serviceIntent,
+ PackageManager.GET_META_DATA));
+ int type = TYPE_LIBRARY_SERVICE;
+ if (id == null) {
+ // retry with session service
+ serviceIntent.setClassName(packageName, serviceName);
+ id = getSessionId(manager.resolveService(serviceIntent,
+ PackageManager.GET_META_DATA));
+ type = TYPE_SESSION_SERVICE;
+ }
+ if (id == null) {
+ throw new IllegalArgumentException("service " + serviceName + " doesn't implement"
+ + " session service nor library service");
+ }
+ mId = id;
+ mType = type;
+ mPackageName = packageName;
+ mServiceName = serviceName;
+ mSessionBinder = null;
+ }
+
+ public SessionToken2Impl(Context context, int uid, int type,
+ String packageName, String serviceName, String id, IMediaSession2 sessionBinder) {
+ // TODO(jaewan): Add sanity check
+ mUid = uid;
+ mType = type;
+ mPackageName = packageName;
+ mServiceName = serviceName;
+ mId = id;
+ mSessionBinder = sessionBinder;
+ mInstance = new SessionToken2(this);
+ }
+
+ public static String getSessionId(ResolveInfo resolveInfo) {
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ return null;
+ } else if (resolveInfo.serviceInfo.metaData == null) {
+ return "";
+ } else {
+ return resolveInfo.serviceInfo.metaData.getString(
+ MediaSessionService2.SERVICE_META_DATA, "");
+ }
+ }
+
+ public SessionToken2 getInstance() {
+ return mInstance;
+ }
+
+ @Override
+ public String getPackageName_impl() {
+ return mPackageName;
+ }
+
+ @Override
+ public int getUid_impl() {
+ return mUid;
+ }
+
+ @Override
+ public String getId_imp() {
+ return mId;
+ }
+
+ @Override
+ public int getType_impl() {
+ return mType;
+ }
+
+ public String getServiceName() {
+ return mServiceName;
+ }
+
+ public IMediaSession2 getSessionBinder() {
+ return mSessionBinder;
+ }
+
+ public static SessionToken2 fromBundle_impl(Context context, Bundle bundle) {
+ if (bundle == null) {
+ return null;
+ }
+ final int uid = bundle.getInt(KEY_UID);
+ final @TokenType int type = bundle.getInt(KEY_TYPE, -1);
+ final String packageName = bundle.getString(KEY_PACKAGE_NAME);
+ final String serviceName = bundle.getString(KEY_SERVICE_NAME);
+ final String id = bundle.getString(KEY_ID);
+ final IBinder sessionBinder = bundle.getBinder(KEY_SESSION_BINDER);
+
+ // Sanity check.
+ switch (type) {
+ case TYPE_SESSION:
+ if (sessionBinder == null) {
+ throw new IllegalArgumentException("Unexpected sessionBinder for session,"
+ + " binder=" + sessionBinder);
+ }
+ break;
+ case TYPE_SESSION_SERVICE:
+ case TYPE_LIBRARY_SERVICE:
+ if (TextUtils.isEmpty(serviceName)) {
+ throw new IllegalArgumentException("Session service needs service name");
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid type");
+ }
+ if (TextUtils.isEmpty(packageName) || id == null) {
+ throw new IllegalArgumentException("Package name nor ID cannot be null.");
+ }
+ // TODO(jaewan): Revisit here when we add connection callback to the session for individual
+ // controller's permission check. With it, sessionBinder should be available
+ // if and only if for session, not session service.
+ return new SessionToken2Impl(context, uid, type, packageName, serviceName, id,
+ sessionBinder != null ? IMediaSession2.Stub.asInterface(sessionBinder) : null)
+ .getInstance();
+ }
+
+ @Override
+ public Bundle toBundle_impl() {
+ Bundle bundle = new Bundle();
+ bundle.putInt(KEY_UID, mUid);
+ bundle.putString(KEY_PACKAGE_NAME, mPackageName);
+ bundle.putString(KEY_SERVICE_NAME, mServiceName);
+ bundle.putString(KEY_ID, mId);
+ bundle.putInt(KEY_TYPE, mType);
+ bundle.putBinder(KEY_SESSION_BINDER,
+ mSessionBinder != null ? mSessionBinder.asBinder() : null);
+ return bundle;
+ }
+
+ @Override
+ public int hashCode_impl() {
+ final int prime = 31;
+ return mType
+ + prime * (mUid
+ + prime * (mPackageName.hashCode()
+ + prime * (mId.hashCode()
+ + prime * ((mServiceName != null ? mServiceName.hashCode() : 0)
+ + prime * (mSessionBinder != null ? mSessionBinder.asBinder().hashCode() : 0)))));
+ }
+
+ @Override
+ public boolean equals_impl(Object obj) {
+ if (!(obj instanceof SessionToken2)) {
+ return false;
+ }
+ SessionToken2Impl other = from((SessionToken2) obj);
+ if (mUid != other.mUid
+ || !TextUtils.equals(mPackageName, other.mPackageName)
+ || !TextUtils.equals(mServiceName, other.mServiceName)
+ || !TextUtils.equals(mId, other.mId)
+ || mType != other.mType) {
+ return false;
+ }
+ if (mSessionBinder == other.mSessionBinder) {
+ return true;
+ } else if (mSessionBinder == null || other.mSessionBinder == null) {
+ return false;
+ }
+ return mSessionBinder.asBinder().equals(other.mSessionBinder.asBinder());
+ }
+
+ @Override
+ public String toString_impl() {
+ return "SessionToken {pkg=" + mPackageName + " id=" + mId + " type=" + mType
+ + " service=" + mServiceName + " binder=" + mSessionBinder + "}";
+ }
+
+ public static SessionToken2Impl from(SessionToken2 token) {
+ return ((SessionToken2Impl) token.getProvider());
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
index cfef77f..0fc1ac1 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -16,32 +16,58 @@
package com.android.media.update;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
+import android.media.DataSourceDesc;
+import android.media.MediaBrowser2;
+import android.media.MediaBrowser2.BrowserCallback;
import android.media.MediaController2;
-import android.media.MediaPlayerBase;
+import android.media.MediaController2.ControllerCallback;
+import android.media.MediaItem2;
+import android.media.MediaLibraryService2;
+import android.media.MediaLibraryService2.MediaLibrarySession;
+import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
+import android.media.MediaMetadata2;
+import android.media.MediaPlayerInterface;
import android.media.MediaSession2;
+import android.media.MediaSession2.Command;
import android.media.MediaSession2.ControllerInfo;
import android.media.MediaSession2.SessionCallback;
import android.media.MediaSessionService2;
-import android.media.IMediaSession2Callback;
-import android.media.SessionToken;
+import android.media.SessionPlayer2;
+import android.media.SessionToken2;
+import android.media.VolumeProvider;
+import android.media.update.MediaBrowser2Provider;
import android.media.update.MediaControlView2Provider;
import android.media.update.MediaController2Provider;
+import android.media.update.MediaItem2Provider;
+import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider;
import android.media.update.MediaSession2Provider;
import android.media.update.MediaSessionService2Provider;
+import android.media.update.SessionPlayer2Provider;
+import android.media.update.SessionToken2Provider;
import android.media.update.VideoView2Provider;
import android.media.update.StaticProvider;
import android.media.update.ViewProvider;
+import android.os.Bundle;
+import android.os.IInterface;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.widget.MediaControlView2;
import android.widget.VideoView2;
+import com.android.media.IMediaSession2;
+import com.android.media.IMediaSession2Callback;
+import com.android.media.MediaBrowser2Impl;
import com.android.media.MediaController2Impl;
+import com.android.media.MediaItem2Impl;
+import com.android.media.MediaLibraryService2Impl;
+import com.android.media.MediaLibraryService2Impl.MediaLibrarySessionImpl;
import com.android.media.MediaSession2Impl;
import com.android.media.MediaSessionService2Impl;
+import com.android.media.SessionToken2Impl;
import com.android.widget.MediaControlView2Impl;
import com.android.widget.VideoView2Impl;
@@ -56,23 +82,46 @@
@Override
public MediaController2Provider createMediaController2(
- MediaController2 instance, Context context, SessionToken token,
- MediaController2.ControllerCallback callback, Executor executor) {
- return new MediaController2Impl(instance, context, token, callback, executor);
+ Context context, MediaController2 instance, SessionToken2 token,
+ Executor executor, ControllerCallback callback) {
+ return new MediaController2Impl(context, instance, token, executor, callback);
}
@Override
- public MediaSession2Provider createMediaSession2(MediaSession2 instance, Context context,
- MediaPlayerBase player, String id, SessionCallback callback) {
- return new MediaSession2Impl(instance, context, player, id, callback);
+ public MediaBrowser2Provider createMediaBrowser2(Context context, MediaBrowser2 instance,
+ SessionToken2 token, Executor executor, BrowserCallback callback) {
+ return new MediaBrowser2Impl(context, instance, token, executor, callback);
+ }
+
+ @Override
+ public MediaSession2Provider createMediaSession2(Context context, MediaSession2 instance,
+ MediaPlayerInterface player, String id, VolumeProvider volumeProvider,
+ int ratingType, PendingIntent sessionActivity, Executor callbackExecutor,
+ SessionCallback callback) {
+ return new MediaSession2Impl(context, instance, player, id, volumeProvider, ratingType,
+ sessionActivity, callbackExecutor, callback);
}
@Override
public MediaSession2Provider.ControllerInfoProvider createMediaSession2ControllerInfoProvider(
- ControllerInfo instance, Context context, int uid, int pid, String packageName,
- IMediaSession2Callback callback) {
- return new MediaSession2Impl.ControllerInfoImpl(
- instance, context, uid, pid, packageName, callback);
+ Context context, ControllerInfo instance, int uid, int pid, String packageName,
+ IInterface callback) {
+ return new MediaSession2Impl.ControllerInfoImpl(context,
+ instance, uid, pid, packageName, (IMediaSession2Callback) callback);
+ }
+
+ @Override
+ public MediaSession2Provider.CommandProvider createMediaSession2Command(Command instance,
+ int commandCode, String action, Bundle extra) {
+ if (action == null && extra == null) {
+ return new MediaSession2Impl.CommandImpl(instance, commandCode);
+ }
+ return new MediaSession2Impl.CommandImpl(instance, action, extra);
+ }
+
+ @Override
+ public Command fromBundle_MediaSession2Command(Context context, Bundle command) {
+ return MediaSession2Impl.CommandImpl.fromBundle_impl(context, command);
}
@Override
@@ -82,6 +131,21 @@
}
@Override
+ public MediaSessionService2Provider createMediaLibraryService2(
+ MediaLibraryService2 instance) {
+ return new MediaLibraryService2Impl(instance);
+ }
+
+ @Override
+ public MediaLibrarySessionProvider createMediaLibraryService2MediaLibrarySession(
+ Context context, MediaLibrarySession instance, MediaPlayerInterface player,
+ String id, VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity,
+ Executor callbackExecutor, MediaLibrarySessionCallback callback) {
+ return new MediaLibrarySessionImpl(context, instance, player, id, volumeProvider,
+ ratingType, sessionActivity, callbackExecutor, callback);
+ }
+
+ @Override
public MediaControlView2Provider createMediaControlView2(
MediaControlView2 instance, ViewProvider superProvider) {
return new MediaControlView2Impl(instance, superProvider);
@@ -93,4 +157,32 @@
@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
return new VideoView2Impl(instance, superProvider, attrs, defStyleAttr, defStyleRes);
}
+
+ @Override
+ public SessionToken2Provider createSessionToken2(Context context, SessionToken2 instance,
+ String packageName, String serviceName, int uid) {
+ return new SessionToken2Impl(context, instance, packageName, serviceName, uid);
+ }
+
+ @Override
+ public SessionToken2 SessionToken2_fromBundle(Context context, Bundle bundle) {
+ return SessionToken2Impl.fromBundle_impl(context, bundle);
+ }
+
+ @Override
+ public SessionPlayer2Provider createSessionPlayer2(Context context, SessionPlayer2 instance) {
+ // TODO(jaewan): Implement this
+ return null;
+ }
+
+ @Override
+ public MediaItem2Provider createMediaItem2Provider(Context context, MediaItem2 instance,
+ String mediaId, DataSourceDesc dsd, MediaMetadata2 metadata, int flags) {
+ return new MediaItem2Impl(context, instance, mediaId, dsd, metadata, flags);
+ }
+
+ @Override
+ public MediaItem2 fromBundle_MediaItem2(Context context, Bundle bundle) {
+ return MediaItem2Impl.fromBundle(context, bundle);
+ }
}
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
index a83e449..b0ca1bd 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
@@ -20,10 +20,13 @@
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
+import com.android.support.mediarouter.app.MediaRouteButton;
+
public class ApiHelper {
private static ApiHelper sInstance;
private final Resources mLibResources;
@@ -55,7 +58,21 @@
public static LayoutInflater getLayoutInflater(Context context) {
LayoutInflater layoutInflater = LayoutInflater.from(context).cloneInContext(
new ContextThemeWrapper(context, getLibTheme()));
- // TODO: call layoutInflater.setFactory2()
+ layoutInflater.setFactory2(new LayoutInflater.Factory2() {
+ @Override
+ public View onCreateView(
+ View parent, String name, Context context, AttributeSet attrs) {
+ if (MediaRouteButton.class.getCanonicalName().equals(name)) {
+ return new MediaRouteButton(context, attrs);
+ }
+ return null;
+ }
+
+ @Override
+ public View onCreateView(String name, Context context, AttributeSet attrs) {
+ return onCreateView(null, name, context, attrs);
+ }
+ });
return layoutInflater;
}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
index 68e8d3a..65fc88c 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.AnimationDrawable;
@@ -36,6 +37,7 @@
import android.view.SoundEffectConstants;
import android.view.View;
+import com.android.media.update.ApiHelper;
import com.android.media.update.R;
import com.android.support.mediarouter.media.MediaRouteSelector;
import com.android.support.mediarouter.media.MediaRouter;
@@ -128,8 +130,11 @@
mRouter = MediaRouter.getInstance(context);
mCallback = new MediaRouterCallback();
- TypedArray a = context.obtainStyledAttributes(attrs,
+ Resources.Theme theme = ApiHelper.getLibResources().newTheme();
+ theme.applyStyle(MediaRouterThemeHelper.getRouterThemeId(context), true);
+ TypedArray a = theme.obtainStyledAttributes(attrs,
R.styleable.MediaRouteButton, defStyleAttr, 0);
+
mButtonTint = a.getColorStateList(R.styleable.MediaRouteButton_mediaRouteButtonTint);
mMinWidth = a.getDimensionPixelSize(
R.styleable.MediaRouteButton_android_minWidth, 0);
@@ -290,8 +295,9 @@
* button when the button is long pressed.
*/
void setCheatSheetEnabled(boolean enable) {
- TooltipCompat.setTooltipText(this,
- enable ? getContext().getString(R.string.mr_button_content_description) : null);
+ TooltipCompat.setTooltipText(this, enable
+ ? ApiHelper.getLibResources().getString(R.string.mr_button_content_description)
+ : null);
}
@Override
@@ -533,7 +539,7 @@
} else {
resId = R.string.mr_cast_button_disconnected;
}
- setContentDescription(getContext().getString(resId));
+ setContentDescription(ApiHelper.getLibResources().getString(resId));
}
private final class MediaRouterCallback extends MediaRouter.Callback {
@@ -590,7 +596,7 @@
@Override
protected Drawable doInBackground(Void... params) {
- return getContext().getResources().getDrawable(mResId);
+ return ApiHelper.getLibResources().getDrawable(mResId);
}
@Override
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouterThemeHelper.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouterThemeHelper.java
index b4b49df..7440130 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouterThemeHelper.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouterThemeHelper.java
@@ -196,7 +196,7 @@
return value.data;
}
- private static int getRouterThemeId(Context context) {
+ static int getRouterThemeId(Context context) {
int themeId;
if (isLightTheme(context)) {
if (getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java b/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java
index f5e1e61..33d92b4 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java
@@ -27,6 +27,7 @@
import android.support.annotation.RequiresApi;
import android.view.Display;
+import com.android.media.update.ApiHelper;
import com.android.media.update.R;
import java.util.ArrayList;
@@ -266,7 +267,7 @@
mCallbackObj = createCallbackObj();
mVolumeCallbackObj = createVolumeCallbackObj();
- Resources r = context.getResources();
+ Resources r = ApiHelper.getLibResources();
mUserRouteCategoryObj = MediaRouterJellybean.createRouteCategory(
mRouterObj, r.getString(R.string.mr_user_route_category_name), false);
diff --git a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
index a814d5e..688aadc 100644
--- a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 2017 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.
@@ -22,12 +22,13 @@
import android.media.session.PlaybackState;
import android.media.update.MediaControlView2Provider;
import android.media.update.ViewProvider;
+import android.os.Bundle;
import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
+import android.widget.Button;
import android.widget.ImageButton;
import android.widget.MediaControlView2;
import android.widget.ProgressBar;
@@ -39,6 +40,7 @@
import com.android.media.update.R;
import java.util.Formatter;
+import java.util.List;
import java.util.Locale;
public class MediaControlView2Impl implements MediaControlView2Provider {
@@ -47,8 +49,10 @@
private final MediaControlView2 mInstance;
private final ViewProvider mSuperProvider;
- static final String ACTION_SHOW_SUBTITLE = "showSubtitle";
- static final String ACTION_HIDE_SUBTITLE = "hideSubtitle";
+ static final String ARGUMENT_KEY_FULLSCREEN = "fullScreen";
+
+ static final String KEY_STATE_CONTAINS_SUBTITLE = "StateContainsSubtitle";
+ static final String EVENT_UPDATE_SUBTITLE_STATUS = "UpdateSubtitleStatus";
private static final int MAX_PROGRESS = 1000;
private static final int DEFAULT_PROGRESS_UPDATE_TIME_MS = 1000;
@@ -57,7 +61,6 @@
private static final int REWIND_TIME_MS = 10000;
private static final int FORWARD_TIME_MS = 30000;
- private final boolean mUseClosedCaption;
private final AccessibilityManager mAccessibilityManager;
private MediaController mController;
@@ -66,20 +69,36 @@
private MediaMetadata mMetadata;
private ProgressBar mProgress;
private TextView mEndTime, mCurrentTime;
+ private TextView mTitleView;
private int mDuration;
private int mPrevState;
+ private long mPlaybackActions;
private boolean mShowing;
private boolean mDragging;
- private boolean mListenersSet;
- private boolean mCCIsEnabled;
+ private boolean mIsFullScreen;
+ private boolean mOverflowExpanded;
private boolean mIsStopped;
- private View.OnClickListener mNextListener, mPrevListener;
+ private boolean mSubtitleIsEnabled;
+ private boolean mContainsSubtitle;
+ private boolean mSeekAvailable;
private ImageButton mPlayPauseButton;
private ImageButton mFfwdButton;
private ImageButton mRewButton;
private ImageButton mNextButton;
private ImageButton mPrevButton;
- private ImageButton mCCButton;
+
+ private ViewGroup mBasicControls;
+ private ImageButton mSubtitleButton;
+ private ImageButton mFullScreenButton;
+ private ImageButton mOverflowButtonRight;
+
+ private ViewGroup mExtraControls;
+ private ViewGroup mCustomButtons;
+ private ImageButton mOverflowButtonLeft;
+ private ImageButton mMuteButton;
+ private ImageButton mAspectRationButton;
+ private ImageButton mSettingsButton;
+
private CharSequence mPlayDescription;
private CharSequence mPauseDescription;
private CharSequence mReplayDescription;
@@ -87,10 +106,10 @@
private StringBuilder mFormatBuilder;
private Formatter mFormatter;
- public MediaControlView2Impl(MediaControlView2 instance, ViewProvider superProvider) {
+ public MediaControlView2Impl(
+ MediaControlView2 instance, ViewProvider superProvider) {
mInstance = instance;
mSuperProvider = superProvider;
- mUseClosedCaption = true;
mAccessibilityManager = AccessibilityManager.getInstance(mInstance.getContext());
// Inflate MediaControlView2 from XML
@@ -108,6 +127,7 @@
mPlaybackState = mController.getPlaybackState();
mMetadata = mController.getMetadata();
updateDuration();
+ updateTitle();
mController.registerCallback(new MediaControllerCallback());
}
@@ -119,7 +139,7 @@
}
@Override
- public void show_impl(int timeout) {
+ public void show_impl(long timeout) {
if (!mShowing) {
setProgress();
if (mPlayPauseButton != null) {
@@ -161,79 +181,77 @@
}
@Override
- public void showCCButton_impl() {
- if (mCCButton != null) {
- mCCButton.setVisibility(View.VISIBLE);
+ public void setButtonVisibility_impl(int button, int visibility) {
+ switch (button) {
+ case MediaControlView2.BUTTON_PLAY_PAUSE:
+ if (mPlayPauseButton != null && canPause()) {
+ mPlayPauseButton.setVisibility(visibility);
+ }
+ break;
+ case MediaControlView2.BUTTON_FFWD:
+ if (mFfwdButton != null && canSeekForward()) {
+ mFfwdButton.setVisibility(visibility);
+ }
+ break;
+ case MediaControlView2.BUTTON_REW:
+ if (mRewButton != null && canSeekBackward()) {
+ mRewButton.setVisibility(visibility);
+ }
+ break;
+ case MediaControlView2.BUTTON_NEXT:
+ // TODO: this button is not visible unless its listener is manually set. Should this
+ // function still be provided?
+ if (mNextButton != null) {
+ mNextButton.setVisibility(visibility);
+ }
+ break;
+ case MediaControlView2.BUTTON_PREV:
+ // TODO: this button is not visible unless its listener is manually set. Should this
+ // function still be provided?
+ if (mPrevButton != null) {
+ mPrevButton.setVisibility(visibility);
+ }
+ break;
+ case MediaControlView2.BUTTON_SUBTITLE:
+ if (mSubtitleButton != null && mContainsSubtitle) {
+ mSubtitleButton.setVisibility(visibility);
+ }
+ break;
+ case MediaControlView2.BUTTON_FULL_SCREEN:
+ if (mFullScreenButton != null) {
+ mFullScreenButton.setVisibility(visibility);
+ }
+ break;
+ case MediaControlView2.BUTTON_OVERFLOW:
+ if (mOverflowButtonRight != null) {
+ mOverflowButtonRight.setVisibility(visibility);
+ }
+ break;
+ case MediaControlView2.BUTTON_MUTE:
+ if (mMuteButton != null) {
+ mMuteButton.setVisibility(visibility);
+ }
+ break;
+ case MediaControlView2.BUTTON_ASPECT_RATIO:
+ if (mAspectRationButton != null) {
+ mAspectRationButton.setVisibility(visibility);
+ }
+ break;
+ case MediaControlView2.BUTTON_SETTINGS:
+ if (mSettingsButton != null) {
+ mSettingsButton.setVisibility(visibility);
+ }
+ break;
+ default:
+ break;
}
}
@Override
- public boolean isPlaying_impl() {
- if (mPlaybackState != null) {
- return mPlaybackState.getState() == PlaybackState.STATE_PLAYING;
- }
- return false;
- }
-
- @Override
- public int getCurrentPosition_impl() {
- mPlaybackState = mController.getPlaybackState();
- if (mPlaybackState != null) {
- return (int) mPlaybackState.getPosition();
- }
- return 0;
- }
-
- @Override
- public int getBufferPercentage_impl() {
- if (mDuration == 0) {
- return 0;
- }
- mPlaybackState = mController.getPlaybackState();
- if (mPlaybackState != null) {
- return (int) (mPlaybackState.getBufferedPosition() * 100) / mDuration;
- }
- return 0;
- }
-
- @Override
- public boolean canPause_impl() {
- if (mPlaybackState != null) {
- return (mPlaybackState.getActions() & PlaybackState.ACTION_PAUSE) != 0;
- }
- return true;
- }
-
- @Override
- public boolean canSeekBackward_impl() {
- if (mPlaybackState != null) {
- return (mPlaybackState.getActions() & PlaybackState.ACTION_REWIND) != 0;
- }
- return true;
- }
-
- @Override
- public boolean canSeekForward_impl() {
- if (mPlaybackState != null) {
- return (mPlaybackState.getActions() & PlaybackState.ACTION_FAST_FORWARD) != 0;
- }
- return true;
- }
-
- @Override
- public void showSubtitle_impl() {
- mController.sendCommand(ACTION_SHOW_SUBTITLE, null, null);
- }
-
- @Override
- public void hideSubtitle_impl() {
- mController.sendCommand(ACTION_HIDE_SUBTITLE, null, null);
- }
-
- @Override
public void onAttachedToWindow_impl() {
mSuperProvider.onAttachedToWindow_impl();
}
+
@Override
public void onDetachedFromWindow_impl() {
mSuperProvider.onDetachedFromWindow_impl();
@@ -257,62 +275,11 @@
}
@Override
- public boolean onKeyDown_impl(int keyCode, KeyEvent event) {
- return mSuperProvider.onKeyDown_impl(keyCode, event);
- }
-
- @Override
public void onFinishInflate_impl() {
mSuperProvider.onFinishInflate_impl();
}
@Override
- public boolean dispatchKeyEvent_impl(KeyEvent event) {
- int keyCode = event.getKeyCode();
- final boolean uniqueDown = event.getRepeatCount() == 0
- && event.getAction() == KeyEvent.ACTION_DOWN;
- if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
- || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
- || keyCode == KeyEvent.KEYCODE_SPACE) {
- if (uniqueDown) {
- togglePausePlayState();
- mInstance.show(DEFAULT_TIMEOUT_MS);
- if (mPlayPauseButton != null) {
- mPlayPauseButton.requestFocus();
- }
- }
- return true;
- } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
- if (uniqueDown && !mInstance.isPlaying()) {
- togglePausePlayState();
- mInstance.show(DEFAULT_TIMEOUT_MS);
- }
- return true;
- } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
- || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
- if (uniqueDown && mInstance.isPlaying()) {
- togglePausePlayState();
- mInstance.show(DEFAULT_TIMEOUT_MS);
- }
- return true;
- } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
- || keyCode == KeyEvent.KEYCODE_VOLUME_UP
- || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE
- || keyCode == KeyEvent.KEYCODE_CAMERA) {
- // don't show the controls for volume adjustment
- return mSuperProvider.dispatchKeyEvent_impl(event);
- } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
- if (uniqueDown) {
- mInstance.hide();
- }
- return true;
- }
-
- mInstance.show(DEFAULT_TIMEOUT_MS);
- return mSuperProvider.dispatchKeyEvent_impl(event);
- }
-
- @Override
public void setEnabled_impl(boolean enabled) {
if (mPlayPauseButton != null) {
mPlayPauseButton.setEnabled(enabled);
@@ -324,10 +291,10 @@
mRewButton.setEnabled(enabled);
}
if (mNextButton != null) {
- mNextButton.setEnabled(enabled && mNextListener != null);
+ mNextButton.setEnabled(enabled);
}
if (mPrevButton != null) {
- mPrevButton.setEnabled(enabled && mPrevListener != null);
+ mPrevButton.setEnabled(enabled);
}
if (mProgress != null) {
mProgress.setEnabled(enabled);
@@ -340,6 +307,53 @@
// Protected or private methods
///////////////////////////////////////////////////
+ private boolean isPlaying() {
+ if (mPlaybackState != null) {
+ return mPlaybackState.getState() == PlaybackState.STATE_PLAYING;
+ }
+ return false;
+ }
+
+ private int getCurrentPosition() {
+ mPlaybackState = mController.getPlaybackState();
+ if (mPlaybackState != null) {
+ return (int) mPlaybackState.getPosition();
+ }
+ return 0;
+ }
+
+ private int getBufferPercentage() {
+ if (mDuration == 0) {
+ return 0;
+ }
+ mPlaybackState = mController.getPlaybackState();
+ if (mPlaybackState != null) {
+ return (int) (mPlaybackState.getBufferedPosition() * 100) / mDuration;
+ }
+ return 0;
+ }
+
+ private boolean canPause() {
+ if (mPlaybackState != null) {
+ return (mPlaybackState.getActions() & PlaybackState.ACTION_PAUSE) != 0;
+ }
+ return true;
+ }
+
+ private boolean canSeekBackward() {
+ if (mPlaybackState != null) {
+ return (mPlaybackState.getActions() & PlaybackState.ACTION_REWIND) != 0;
+ }
+ return true;
+ }
+
+ private boolean canSeekForward() {
+ if (mPlaybackState != null) {
+ return (mPlaybackState.getActions() & PlaybackState.ACTION_FAST_FORWARD) != 0;
+ }
+ return true;
+ }
+
/**
* Create the view that holds the widgets that control playback.
* Derived classes can override this to create their own.
@@ -358,42 +372,64 @@
mPlayDescription = res.getText(R.string.lockscreen_play_button_content_description);
mPauseDescription = res.getText(R.string.lockscreen_pause_button_content_description);
mReplayDescription = res.getText(R.string.lockscreen_replay_button_content_description);
+
mPlayPauseButton = v.findViewById(R.id.pause);
if (mPlayPauseButton != null) {
mPlayPauseButton.requestFocus();
mPlayPauseButton.setOnClickListener(mPlayPauseListener);
+ mPlayPauseButton.setColorFilter(R.integer.gray);
+ mPlayPauseButton.setEnabled(false);
}
-
- // TODO: make the following buttons visible based upon whether they are supported for
- // individual media files
mFfwdButton = v.findViewById(R.id.ffwd);
if (mFfwdButton != null) {
mFfwdButton.setOnClickListener(mFfwdListener);
- mFfwdButton.setVisibility(View.GONE);
+ mFfwdButton.setColorFilter(R.integer.gray);
+ mFfwdButton.setEnabled(false);
}
mRewButton = v.findViewById(R.id.rew);
if (mRewButton != null) {
mRewButton.setOnClickListener(mRewListener);
- mRewButton.setVisibility(View.GONE);
+ mRewButton.setColorFilter(R.integer.gray);
+ mRewButton.setEnabled(false);
}
mNextButton = v.findViewById(R.id.next);
- if (mNextButton != null && !mListenersSet) {
- mNextButton.setVisibility(View.GONE);
+ if (mNextButton != null) {
+ mNextButton.setOnClickListener(mNextListener);
}
mPrevButton = v.findViewById(R.id.prev);
- if (mPrevButton != null && !mListenersSet) {
- mPrevButton.setVisibility(View.GONE);
+ if (mPrevButton != null) {
+ mPrevButton.setOnClickListener(mPrevListener);
}
- // TODO: make CC button visible if the media file has a subtitle track
- mCCButton = v.findViewById(R.id.cc);
- if (mCCButton != null) {
- mCCButton.setOnClickListener(mCCListener);
- mCCButton.setVisibility(mUseClosedCaption ? View.VISIBLE : View.GONE);
+ mBasicControls = v.findViewById(R.id.basic_controls);
+ mSubtitleButton = v.findViewById(R.id.subtitle);
+ if (mSubtitleButton != null) {
+ mSubtitleButton.setOnClickListener(mSubtitleListener);
+ mSubtitleButton.setColorFilter(R.integer.gray);
+ mSubtitleButton.setEnabled(false);
+ }
+ mFullScreenButton = v.findViewById(R.id.fullscreen);
+ if (mFullScreenButton != null) {
+ mFullScreenButton.setOnClickListener(mFullScreenListener);
+ // TODO: Show Fullscreen button when only it is possible.
+ }
+ mOverflowButtonRight = v.findViewById(R.id.overflow_right);
+ if (mOverflowButtonRight != null) {
+ mOverflowButtonRight.setOnClickListener(mOverflowRightListener);
}
- mProgress =
- v.findViewById(R.id.mediacontroller_progress);
+ // TODO: should these buttons be shown as default?
+ mExtraControls = v.findViewById(R.id.extra_controls);
+ mCustomButtons = v.findViewById(R.id.custom_buttons);
+ mOverflowButtonLeft = v.findViewById(R.id.overflow_left);
+ if (mOverflowButtonLeft != null) {
+ mOverflowButtonLeft.setOnClickListener(mOverflowLeftListener);
+ }
+ mMuteButton = v.findViewById(R.id.mute);
+ mAspectRationButton = v.findViewById(R.id.aspect_ratio);
+ mSettingsButton = v.findViewById(R.id.settings);
+
+ mProgress = v.findViewById(R.id.mediacontroller_progress);
if (mProgress != null) {
if (mProgress instanceof SeekBar) {
SeekBar seeker = (SeekBar) mProgress;
@@ -402,12 +438,12 @@
mProgress.setMax(MAX_PROGRESS);
}
+ mTitleView = v.findViewById(R.id.title_text);
+
mEndTime = v.findViewById(R.id.time);
mCurrentTime = v.findViewById(R.id.time_current);
mFormatBuilder = new StringBuilder();
mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
-
- installPrevNextListeners();
}
/**
@@ -416,13 +452,13 @@
*/
private void disableUnsupportedButtons() {
try {
- if (mPlayPauseButton != null && !mInstance.canPause()) {
+ if (mPlayPauseButton != null && !canPause()) {
mPlayPauseButton.setEnabled(false);
}
- if (mRewButton != null && !mInstance.canSeekBackward()) {
+ if (mRewButton != null && !canSeekBackward()) {
mRewButton.setEnabled(false);
}
- if (mFfwdButton != null && !mInstance.canSeekForward()) {
+ if (mFfwdButton != null && !canSeekForward()) {
mFfwdButton.setEnabled(false);
}
// TODO What we really should do is add a canSeek to the MediaPlayerControl interface;
@@ -432,7 +468,7 @@
// However, currently the flags SEEK_BACKWARD_AVAILABLE, SEEK_FORWARD_AVAILABLE,
// and SEEK_AVAILABLE are all (un)set together; as such the aforementioned issue
// shouldn't arise in existing applications.
- if (mProgress != null && !mInstance.canSeekBackward() && !mInstance.canSeekForward()) {
+ if (mProgress != null && !canSeekBackward() && !canSeekForward()) {
mProgress.setEnabled(false);
}
} catch (IncompatibleClassChangeError ex) {
@@ -446,7 +482,9 @@
private final Runnable mFadeOut = new Runnable() {
@Override
public void run() {
- mInstance.hide();
+ if (isPlaying()) {
+ mInstance.hide();
+ }
}
};
@@ -454,7 +492,7 @@
@Override
public void run() {
int pos = setProgress();
- if (!mDragging && mShowing && mInstance.isPlaying()) {
+ if (!mDragging && mShowing && isPlaying()) {
mInstance.postDelayed(mShowProgress,
DEFAULT_PROGRESS_UPDATE_TIME_MS - (pos % DEFAULT_PROGRESS_UPDATE_TIME_MS));
}
@@ -481,13 +519,13 @@
return 0;
}
int positionOnProgressBar = 0;
- int currentPosition = mInstance.getCurrentPosition();
+ int currentPosition = getCurrentPosition();
if (mDuration > 0) {
positionOnProgressBar = (int) (MAX_PROGRESS * (long) currentPosition / mDuration);
}
if (mProgress != null && currentPosition != mDuration) {
mProgress.setProgress(positionOnProgressBar);
- mProgress.setSecondaryProgress(mInstance.getBufferPercentage() * 10);
+ mProgress.setSecondaryProgress(getBufferPercentage() * 10);
}
if (mEndTime != null) {
@@ -502,13 +540,17 @@
}
private void togglePausePlayState() {
- if (mInstance.isPlaying()) {
+ if (isPlaying()) {
mControls.pause();
- mPlayPauseButton.setImageResource(R.drawable.ic_play_circle_filled);
+ mPlayPauseButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_play_circle_filled, null));
mPlayPauseButton.setContentDescription(mPlayDescription);
} else {
mControls.play();
- mPlayPauseButton.setImageResource(R.drawable.ic_pause_circle_filled);
+ mPlayPauseButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_pause_circle_filled, null));
mPlayPauseButton.setContentDescription(mPauseDescription);
}
}
@@ -527,6 +569,9 @@
private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
@Override
public void onStartTrackingTouch(SeekBar bar) {
+ if (!mSeekAvailable) {
+ return;
+ }
mInstance.show(3600000);
mDragging = true;
@@ -538,10 +583,12 @@
// this ensures that there will be exactly one message queued up.
mInstance.removeCallbacks(mShowProgress);
- // Check if playback is currently stopped. In this case, update the pause button to show
- // the play image instead of the replay image.
+ // Check if playback is currently stopped. In this case, update the pause button to
+ // show the play image instead of the replay image.
if (mIsStopped) {
- mPlayPauseButton.setImageResource(R.drawable.ic_play_circle_filled);
+ mPlayPauseButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_play_circle_filled, null));
mPlayPauseButton.setContentDescription(mPlayDescription);
mIsStopped = false;
}
@@ -549,6 +596,9 @@
@Override
public void onProgressChanged(SeekBar bar, int progress, boolean fromUser) {
+ if (!mSeekAvailable) {
+ return;
+ }
if (!fromUser) {
// We're not interested in programmatically generated changes to
// the progress bar's position.
@@ -566,6 +616,9 @@
@Override
public void onStopTrackingTouch(SeekBar bar) {
+ if (!mSeekAvailable) {
+ return;
+ }
mDragging = false;
setProgress();
@@ -589,7 +642,7 @@
private final View.OnClickListener mRewListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
- int pos = mInstance.getCurrentPosition() - REWIND_TIME_MS;
+ int pos = getCurrentPosition() - REWIND_TIME_MS;
mControls.seekTo(pos);
setProgress();
@@ -600,7 +653,7 @@
private final View.OnClickListener mFfwdListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
- int pos = mInstance.getCurrentPosition() + FORWARD_TIME_MS;
+ int pos = getCurrentPosition() + FORWARD_TIME_MS;
mControls.seekTo(pos);
setProgress();
@@ -608,32 +661,80 @@
}
};
- private final View.OnClickListener mCCListener = new View.OnClickListener() {
+ private final View.OnClickListener mNextListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
- if (!mCCIsEnabled) {
- mCCButton.setImageResource(R.drawable.ic_media_cc_enabled);
- mCCIsEnabled = true;
- mInstance.showSubtitle();
- } else {
- mCCButton.setImageResource(R.drawable.ic_media_cc_disabled);
- mCCIsEnabled = false;
- mInstance.hideSubtitle();
- }
+ mControls.skipToNext();
+ mInstance.show(DEFAULT_TIMEOUT_MS);
}
};
- private void installPrevNextListeners() {
- if (mNextButton != null) {
- mNextButton.setOnClickListener(mNextListener);
- mNextButton.setEnabled(mNextListener != null);
+ private final View.OnClickListener mPrevListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mControls.skipToPrevious();
+ mInstance.show(DEFAULT_TIMEOUT_MS);
}
+ };
- if (mPrevButton != null) {
- mPrevButton.setOnClickListener(mPrevListener);
- mPrevButton.setEnabled(mPrevListener != null);
+ private final View.OnClickListener mSubtitleListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!mSubtitleIsEnabled) {
+ mSubtitleButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_media_subtitle_enabled, null));
+ mController.sendCommand(MediaControlView2.COMMAND_SHOW_SUBTITLE, null, null);
+ mSubtitleIsEnabled = true;
+ } else {
+ mSubtitleButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_media_subtitle_disabled, null));
+ mController.sendCommand(MediaControlView2.COMMAND_HIDE_SUBTITLE, null, null);
+ mSubtitleIsEnabled = false;
+ }
+ mInstance.show(DEFAULT_TIMEOUT_MS);
}
- }
+ };
+
+ private final View.OnClickListener mFullScreenListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final boolean isEnteringFullScreen = !mIsFullScreen;
+ // TODO: Re-arrange the button layouts according to the UX.
+ if (isEnteringFullScreen) {
+ mFullScreenButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_fullscreen_exit, null));
+ } else {
+ mFullScreenButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(R.drawable.ic_fullscreen, null));
+ }
+ Bundle args = new Bundle();
+ args.putBoolean(ARGUMENT_KEY_FULLSCREEN, isEnteringFullScreen);
+ mController.sendCommand(MediaControlView2.COMMAND_SET_FULLSCREEN, args, null);
+
+ mIsFullScreen = isEnteringFullScreen;
+ mInstance.show(DEFAULT_TIMEOUT_MS);
+ }
+ };
+
+ private final View.OnClickListener mOverflowRightListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mBasicControls.setVisibility(View.GONE);
+ mExtraControls.setVisibility(View.VISIBLE);
+ mInstance.show(DEFAULT_TIMEOUT_MS);
+ }
+ };
+
+ private final View.OnClickListener mOverflowLeftListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mBasicControls.setVisibility(View.VISIBLE);
+ mExtraControls.setVisibility(View.GONE);
+ }
+ };
private void updateDuration() {
if (mMetadata != null) {
@@ -645,6 +746,14 @@
}
}
+ private void updateTitle() {
+ if (mMetadata != null) {
+ if (mMetadata.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
+ mTitleView.setText(mMetadata.getString(MediaMetadata.METADATA_KEY_TITLE));
+ }
+ }
+ }
+
private class MediaControllerCallback extends MediaController.Callback {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
@@ -657,15 +766,21 @@
if (mPlaybackState.getState() != mPrevState) {
switch (mPlaybackState.getState()) {
case PlaybackState.STATE_PLAYING:
- mPlayPauseButton.setImageResource(R.drawable.ic_pause_circle_filled);
+ mPlayPauseButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_pause_circle_filled, null));
mPlayPauseButton.setContentDescription(mPauseDescription);
break;
case PlaybackState.STATE_PAUSED:
- mPlayPauseButton.setImageResource(R.drawable.ic_play_circle_filled);
+ mPlayPauseButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_play_circle_filled, null));
mPlayPauseButton.setContentDescription(mPlayDescription);
break;
case PlaybackState.STATE_STOPPED:
- mPlayPauseButton.setImageResource(R.drawable.ic_replay);
+ mPlayPauseButton.setImageDrawable(
+ ApiHelper.getLibResources().getDrawable(
+ R.drawable.ic_replay, null));
mPlayPauseButton.setContentDescription(mReplayDescription);
mIsStopped = true;
break;
@@ -674,12 +789,77 @@
}
mPrevState = mPlaybackState.getState();
}
+
+ if (mPlaybackActions != mPlaybackState.getActions()) {
+ long newActions = mPlaybackState.getActions();
+ if ((newActions & PlaybackState.ACTION_PAUSE) != 0) {
+ mPlayPauseButton.clearColorFilter();
+ mPlayPauseButton.setEnabled(true);
+ }
+ if ((newActions & PlaybackState.ACTION_REWIND) != 0) {
+ mRewButton.clearColorFilter();
+ mRewButton.setEnabled(true);
+ }
+ if ((newActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
+ mFfwdButton.clearColorFilter();
+ mFfwdButton.setEnabled(true);
+ }
+ if ((newActions & PlaybackState.ACTION_SEEK_TO) != 0) {
+ mSeekAvailable = true;
+ } else {
+ mSeekAvailable = false;
+ }
+ mPlaybackActions = newActions;
+ }
+
+ // Add buttons if custom actions are present.
+ List<PlaybackState.CustomAction> customActions = mPlaybackState.getCustomActions();
+ mCustomButtons.removeAllViews();
+ if (customActions.size() > 0) {
+ for (PlaybackState.CustomAction action : customActions) {
+ ImageButton button = new ImageButton(mInstance.getContext(),
+ null /* AttributeSet */, 0 /* Style */);
+ // TODO: Apply R.style.BottomBarButton to this button using library context.
+ // Refer Constructor with argument (int defStyleRes) of View.java
+ button.setImageResource(action.getIcon());
+ button.setTooltipText(action.getName());
+ final String actionString = action.getAction().toString();
+ button.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // TODO: Currently, we are just sending extras that came from session.
+ // Is it the right behavior?
+ mControls.sendCustomAction(actionString, action.getExtras());
+ mInstance.show(DEFAULT_TIMEOUT_MS);
+ }
+ });
+ mCustomButtons.addView(button);
+ }
+ }
}
@Override
public void onMetadataChanged(MediaMetadata metadata) {
mMetadata = metadata;
updateDuration();
+ updateTitle();
+ }
+
+ @Override
+ public void onSessionEvent(String event, Bundle extras) {
+ if (event.equals(EVENT_UPDATE_SUBTITLE_STATUS)) {
+ boolean newSubtitleStatus = extras.getBoolean(KEY_STATE_CONTAINS_SUBTITLE);
+ if (newSubtitleStatus != mContainsSubtitle) {
+ if (newSubtitleStatus) {
+ mSubtitleButton.clearColorFilter();
+ mSubtitleButton.setEnabled(true);
+ } else {
+ mSubtitleButton.setColorFilter(R.integer.gray);
+ mSubtitleButton.setEnabled(false);
+ }
+ mContainsSubtitle = newSubtitleStatus;
+ }
+ }
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index 57534f1..38ade41 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -16,6 +16,7 @@
package com.android.widget;
+import android.annotation.NonNull;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -25,15 +26,17 @@
import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.MediaPlayer;
+import android.media.MediaPlayerInterface;
import android.media.Cea708CaptionRenderer;
import android.media.ClosedCaptionRenderer;
import android.media.Metadata;
import android.media.PlaybackParams;
import android.media.SubtitleController;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
import android.media.TtmlRenderer;
import android.media.WebVttRenderer;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
import android.media.update.VideoView2Provider;
import android.media.update.ViewProvider;
import android.net.Uri;
@@ -43,20 +46,25 @@
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
-import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout.LayoutParams;
import android.widget.MediaControlView2;
import android.widget.VideoView2;
+import com.android.media.update.ApiHelper;
+import com.android.media.update.R;
+import com.android.support.mediarouter.media.MediaRouter;
+import com.android.support.mediarouter.media.MediaRouteSelector;
+import com.android.support.mediarouter.media.RemotePlaybackClient;
+import com.android.support.mediarouter.media.MediaItemStatus;
+import com.android.support.mediarouter.media.MediaSessionStatus;
+
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-
-import com.android.media.update.ApiHelper;
-import com.android.media.update.R;
+import java.util.concurrent.Executor;
public class VideoView2Impl implements VideoView2Provider, VideoViewInterface.SurfaceListener {
private static final String TAG = "VideoView2";
@@ -76,13 +84,14 @@
private final AudioManager mAudioManager;
private AudioAttributes mAudioAttributes;
private int mAudioFocusType = AudioManager.AUDIOFOCUS_GAIN; // legacy focus gain
- private int mAudioSession;
+ private VideoView2.OnCustomActionListener mOnCustomActionListener;
private VideoView2.OnPreparedListener mOnPreparedListener;
private VideoView2.OnCompletionListener mOnCompletionListener;
private VideoView2.OnErrorListener mOnErrorListener;
private VideoView2.OnInfoListener mOnInfoListener;
private VideoView2.OnViewTypeChangedListener mOnViewTypeChangedListener;
+ private VideoView2.OnFullScreenRequestListener mOnFullScreenRequestListener;
private VideoViewInterface mCurrentView;
private VideoTextureView mTextureView;
@@ -91,12 +100,19 @@
private MediaPlayer mMediaPlayer;
private MediaControlView2 mMediaControlView;
private MediaSession mMediaSession;
+ private MediaController mMediaController;
+ private MediaSession.Callback mRouteSessionCallback = new RouteSessionCallback();
+ private MediaRouter mMediaRouter;
+ private MediaRouteSelector mMediaRouteSelector;
+ private Metadata mMetadata;
+ private String mTitle;
private PlaybackState.Builder mStateBuilder;
+ private List<PlaybackState.CustomAction> mCustomActionList;
private int mTargetState = STATE_IDLE;
private int mCurrentState = STATE_IDLE;
private int mCurrentBufferPercentage;
- private int mSeekWhenPrepared; // recording the seek position while preparing
+ private long mSeekWhenPrepared; // recording the seek position while preparing
private int mVideoWidth;
private int mVideoHeight;
@@ -110,8 +126,7 @@
// Refer: https://docs.google.com/document/d/1nzAfns6i2hJ3RkaUre3QMT6wsDedJ5ONLiA_OOBFFX8/edit
private float mFallbackSpeed; // keep the original speed before 'pause' is called.
- public VideoView2Impl(
- VideoView2 instance, ViewProvider superProvider,
+ public VideoView2Impl(VideoView2 instance, ViewProvider superProvider,
@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mInstance = instance;
mSuperProvider = superProvider;
@@ -174,89 +189,19 @@
}
@Override
+ public MediaController getMediaController_impl() {
+ if (mMediaSession == null) {
+ throw new IllegalStateException("MediaSession instance is not available.");
+ }
+ return mMediaController;
+ }
+
+ @Override
public MediaControlView2 getMediaControlView2_impl() {
return mMediaControlView;
}
@Override
- public void start_impl() {
- if (isInPlaybackState() && mCurrentView.hasAvailableSurface()) {
- applySpeed();
- mMediaPlayer.start();
- mCurrentState = STATE_PLAYING;
- updatePlaybackState();
- }
- mTargetState = STATE_PLAYING;
- if (DEBUG) {
- Log.d(TAG, "start(). mCurrentState=" + mCurrentState
- + ", mTargetState=" + mTargetState);
- }
- }
-
- @Override
- public void pause_impl() {
- if (isInPlaybackState()) {
- if (mMediaPlayer.isPlaying()) {
- mMediaPlayer.pause();
- mCurrentState = STATE_PAUSED;
- updatePlaybackState();
- }
- }
- mTargetState = STATE_PAUSED;
- if (DEBUG) {
- Log.d(TAG, "pause(). mCurrentState=" + mCurrentState
- + ", mTargetState=" + mTargetState);
- }
- }
-
- @Override
- public int getDuration_impl() {
- if (isInPlaybackState()) {
- return mMediaPlayer.getDuration();
- }
- return -1;
- }
-
- @Override
- public int getCurrentPosition_impl() {
- if (isInPlaybackState()) {
- return mMediaPlayer.getCurrentPosition();
- }
- return 0;
- }
-
- @Override
- public void seekTo_impl(int msec) {
- if (isInPlaybackState()) {
- mMediaPlayer.seekTo(msec);
- mSeekWhenPrepared = 0;
- updatePlaybackState();
- } else {
- mSeekWhenPrepared = msec;
- }
- }
-
- @Override
- public boolean isPlaying_impl() {
- return (isInPlaybackState()) && mMediaPlayer.isPlaying();
- }
-
- @Override
- public int getBufferPercentage_impl() {
- return mCurrentBufferPercentage;
- }
-
- @Override
- public int getAudioSessionId_impl() {
- if (mAudioSession == 0) {
- MediaPlayer foo = new MediaPlayer();
- mAudioSession = foo.getAudioSessionId();
- foo.release();
- }
- return mAudioSession;
- }
-
- @Override
public void showSubtitle_impl() {
// Retrieve all tracks that belong to the current video.
MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
@@ -284,6 +229,7 @@
}
}
+ // TODO: remove setSpeed_impl once MediaController2 is ready.
@Override
public void setSpeed_impl(float speed) {
if (speed <= 0.0f) {
@@ -294,20 +240,7 @@
if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
applySpeed();
}
- }
-
- @Override
- public float getSpeed_impl() {
- if (DEBUG) {
- if (mMediaPlayer != null) {
- float speed = mMediaPlayer.getPlaybackParams().getSpeed();
- if (speed != mSpeed) {
- Log.w(TAG, "VideoView2's speed : " + mSpeed + " is different from "
- + "MediaPlayer's speed : " + speed);
- }
- }
- }
- return mSpeed;
+ updatePlaybackState();
}
@Override
@@ -331,17 +264,37 @@
}
@Override
+ public void setRouteAttributes_impl(List<String> routeCategories, MediaPlayerInterface player) {
+ // TODO: implement this.
+ }
+
+ @Override
+ public void setRouteAttributes_impl(@NonNull List<String> routeCategories,
+ MediaSession.Callback sessionPlayer) {
+ MediaRouteSelector.Builder builder = new MediaRouteSelector.Builder();
+ for (String category : routeCategories) {
+ builder.addControlCategory(category);
+ }
+ mMediaRouteSelector = builder.build();
+ mMediaRouter = MediaRouter.getInstance(mInstance.getContext());
+ mRouteSessionCallback = sessionPlayer;
+ if (mMediaSession != null) {
+ mMediaRouter.setMediaSession(mMediaSession);
+ }
+ }
+
+ @Override
public void setVideoPath_impl(String path) {
- mInstance.setVideoURI(Uri.parse(path));
+ mInstance.setVideoUri(Uri.parse(path));
}
@Override
- public void setVideoURI_impl(Uri uri) {
- mInstance.setVideoURI(uri, null);
+ public void setVideoUri_impl(Uri uri) {
+ mInstance.setVideoUri(uri, null);
}
@Override
- public void setVideoURI_impl(Uri uri, Map<String, String> headers) {
+ public void setVideoUri_impl(Uri uri, Map<String, String> headers) {
mSeekWhenPrepared = 0;
openVideo(uri, headers);
}
@@ -371,43 +324,62 @@
return mCurrentView.getViewType();
}
+ // TODO: Handle executor properly for all the set listener methods.
@Override
- public void stopPlayback_impl() {
- resetPlayer();
+ public void setCustomActions_impl(
+ List<PlaybackState.CustomAction> actionList,
+ Executor executor, VideoView2.OnCustomActionListener listener) {
+ mCustomActionList = actionList;
+ mOnCustomActionListener = listener;
+
+ // Create a new playback builder in order to clear existing the custom actions.
+ mStateBuilder = null;
+ updatePlaybackState();
}
@Override
- public void setOnPreparedListener_impl(VideoView2.OnPreparedListener l) {
+ public void setOnPreparedListener_impl(Executor executor, VideoView2.OnPreparedListener l) {
mOnPreparedListener = l;
}
@Override
- public void setOnCompletionListener_impl(VideoView2.OnCompletionListener l) {
+ public void setOnCompletionListener_impl(Executor executor, VideoView2.OnCompletionListener l) {
mOnCompletionListener = l;
}
@Override
- public void setOnErrorListener_impl(VideoView2.OnErrorListener l) {
+ public void setOnErrorListener_impl(Executor executor, VideoView2.OnErrorListener l) {
mOnErrorListener = l;
}
@Override
- public void setOnInfoListener_impl(VideoView2.OnInfoListener l) {
+ public void setOnInfoListener_impl(Executor executor, VideoView2.OnInfoListener l) {
mOnInfoListener = l;
}
@Override
- public void setOnViewTypeChangedListener_impl(VideoView2.OnViewTypeChangedListener l) {
+ public void setOnViewTypeChangedListener_impl(Executor executor,
+ VideoView2.OnViewTypeChangedListener l) {
mOnViewTypeChangedListener = l;
}
@Override
+ public void setFullScreenRequestListener_impl(Executor executor,
+ VideoView2.OnFullScreenRequestListener l) {
+ mOnFullScreenRequestListener = l;
+ }
+
+ @Override
public void onAttachedToWindow_impl() {
mSuperProvider.onAttachedToWindow_impl();
// Create MediaSession
mMediaSession = new MediaSession(mInstance.getContext(), "VideoView2MediaSession");
mMediaSession.setCallback(new MediaSessionCallback());
+ mMediaController = mMediaSession.getController();
+ if (mMediaRouter != null) {
+ mMediaRouter.setMediaSession(mMediaSession);
+ }
attachMediaControlView();
}
@@ -417,9 +389,9 @@
mSuperProvider.onDetachedFromWindow_impl();
mMediaSession.release();
mMediaSession = null;
+ mMediaController = null;
}
-
@Override
public CharSequence getAccessibilityClassName_impl() {
return VideoView2.class.getName();
@@ -448,58 +420,11 @@
}
@Override
- public boolean onKeyDown_impl(int keyCode, KeyEvent event) {
- Log.v(TAG, "onKeyDown_impl: " + keyCode);
- boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK
- && keyCode != KeyEvent.KEYCODE_VOLUME_UP
- && keyCode != KeyEvent.KEYCODE_VOLUME_DOWN
- && keyCode != KeyEvent.KEYCODE_VOLUME_MUTE
- && keyCode != KeyEvent.KEYCODE_MENU
- && keyCode != KeyEvent.KEYCODE_CALL
- && keyCode != KeyEvent.KEYCODE_ENDCALL;
- if (isInPlaybackState() && isKeyCodeSupported && mMediaControlView != null) {
- if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
- || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
- if (mMediaPlayer.isPlaying()) {
- mInstance.pause();
- mMediaControlView.show();
- } else {
- mInstance.start();
- mMediaControlView.hide();
- }
- return true;
- } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
- if (!mMediaPlayer.isPlaying()) {
- mInstance.start();
- mMediaControlView.hide();
- }
- return true;
- } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
- || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
- if (mMediaPlayer.isPlaying()) {
- mInstance.pause();
- mMediaControlView.show();
- }
- return true;
- } else {
- toggleMediaControlViewVisibility();
- }
- }
-
- return mSuperProvider.onKeyDown_impl(keyCode, event);
- }
-
- @Override
public void onFinishInflate_impl() {
mSuperProvider.onFinishInflate_impl();
}
@Override
- public boolean dispatchKeyEvent_impl(KeyEvent event) {
- return mSuperProvider.dispatchKeyEvent_impl(event);
- }
-
- @Override
public void setEnabled_impl(boolean enabled) {
mSuperProvider.setEnabled_impl(enabled);
}
@@ -516,7 +441,7 @@
+ ", " + view.toString());
}
if (needToStart()) {
- mInstance.start();
+ mMediaController.getTransportControls().play();
}
}
@@ -547,10 +472,10 @@
}
mCurrentView = view;
if (mOnViewTypeChangedListener != null) {
- mOnViewTypeChangedListener.onViewTypeChanged(view.getViewType());
+ mOnViewTypeChangedListener.onViewTypeChanged(mInstance, view.getViewType());
}
if (needToStart()) {
- mInstance.start();
+ mMediaController.getTransportControls().play();
}
}
@@ -559,9 +484,6 @@
///////////////////////////////////////////////////
private void attachMediaControlView() {
- // TODO: change this so that the CC button appears only where there is a subtitle track.
- // mMediaControlView.showCCButton();
-
// Get MediaController from MediaSession and set it inside MediaControlView
mMediaControlView.setController(mMediaSession.getController());
@@ -612,12 +534,6 @@
controller.registerRenderer(new Cea708CaptionRenderer(context));
controller.registerRenderer(new ClosedCaptionRenderer(context));
mMediaPlayer.setSubtitleAnchor(controller, (SubtitleController.Anchor) mSubtitleView);
-
- if (mAudioSession != 0) {
- mMediaPlayer.setAudioSessionId(mAudioSession);
- } else {
- mAudioSession = mMediaPlayer.getAudioSessionId();
- }
mMediaPlayer.setOnPreparedListener(mPreparedListener);
mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
mMediaPlayer.setOnCompletionListener(mCompletionListener);
@@ -632,6 +548,13 @@
mCurrentState = STATE_PREPARING;
mMediaPlayer.prepareAsync();
+ // Save file name as title since the file may not have a title Metadata.
+ mTitle = uri.getPath();
+ String scheme = uri.getScheme();
+ if (scheme != null && scheme.equals("file")) {
+ mTitle = uri.getLastPathSegment();
+ }
+
if (DEBUG) {
Log.d(TAG, "openVideo(). mCurrentState=" + mCurrentState
+ ", mTargetState=" + mTargetState);
@@ -680,26 +603,26 @@
private void updatePlaybackState() {
if (mStateBuilder == null) {
// Get the capabilities of the player for this stream
- Metadata data = mMediaPlayer.getMetadata(MediaPlayer.METADATA_ALL,
+ mMetadata = mMediaPlayer.getMetadata(MediaPlayer.METADATA_ALL,
MediaPlayer.BYPASS_METADATA_FILTER);
// Add Play action as default
long playbackActions = PlaybackState.ACTION_PLAY;
- if (data != null) {
- if (!data.has(Metadata.PAUSE_AVAILABLE)
- || data.getBoolean(Metadata.PAUSE_AVAILABLE)) {
+ if (mMetadata != null) {
+ if (!mMetadata.has(Metadata.PAUSE_AVAILABLE)
+ || mMetadata.getBoolean(Metadata.PAUSE_AVAILABLE)) {
playbackActions |= PlaybackState.ACTION_PAUSE;
}
- if (!data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
- || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE)) {
+ if (!mMetadata.has(Metadata.SEEK_BACKWARD_AVAILABLE)
+ || mMetadata.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE)) {
playbackActions |= PlaybackState.ACTION_REWIND;
}
- if (!data.has(Metadata.SEEK_FORWARD_AVAILABLE)
- || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE)) {
+ if (!mMetadata.has(Metadata.SEEK_FORWARD_AVAILABLE)
+ || mMetadata.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE)) {
playbackActions |= PlaybackState.ACTION_FAST_FORWARD;
}
- if (!data.has(Metadata.SEEK_AVAILABLE)
- || data.getBoolean(Metadata.SEEK_AVAILABLE)) {
+ if (!mMetadata.has(Metadata.SEEK_AVAILABLE)
+ || mMetadata.getBoolean(Metadata.SEEK_AVAILABLE)) {
playbackActions |= PlaybackState.ACTION_SEEK_TO;
}
} else {
@@ -709,13 +632,17 @@
}
mStateBuilder = new PlaybackState.Builder();
mStateBuilder.setActions(playbackActions);
- mStateBuilder.addCustomAction(MediaControlView2Impl.ACTION_SHOW_SUBTITLE, null, -1);
- mStateBuilder.addCustomAction(MediaControlView2Impl.ACTION_HIDE_SUBTITLE, null, -1);
+
+ if (mCustomActionList != null) {
+ for (PlaybackState.CustomAction action : mCustomActionList) {
+ mStateBuilder.addCustomAction(action);
+ }
+ }
}
mStateBuilder.setState(getCorrespondingPlaybackState(),
- mInstance.getCurrentPosition(), 1.0f);
+ mMediaPlayer.getCurrentPosition(), mSpeed);
mStateBuilder.setBufferedPosition(
- (long) (mCurrentBufferPercentage / 100.0) * mInstance.getDuration());
+ (long) (mCurrentBufferPercentage / 100.0) * mMediaPlayer.getDuration());
// Set PlaybackState for MediaSession
if (mMediaSession != null) {
@@ -733,7 +660,7 @@
case STATE_PREPARING:
return PlaybackState.STATE_CONNECTING;
case STATE_PREPARED:
- return PlaybackState.STATE_STOPPED;
+ return PlaybackState.STATE_PAUSED;
case STATE_PLAYING:
return PlaybackState.STATE_PLAYING;
case STATE_PAUSED:
@@ -803,7 +730,7 @@
}
mCurrentState = STATE_PREPARED;
if (mOnPreparedListener != null) {
- mOnPreparedListener.onPrepared();
+ mOnPreparedListener.onPrepared(mInstance);
}
if (mMediaControlView != null) {
mMediaControlView.setEnabled(true);
@@ -812,19 +739,9 @@
int videoHeight = mp.getVideoHeight();
// mSeekWhenPrepared may be changed after seekTo() call
- int seekToPosition = mSeekWhenPrepared;
+ long seekToPosition = mSeekWhenPrepared;
if (seekToPosition != 0) {
- mInstance.seekTo(seekToPosition);
- }
-
- // Create and set playback state for MediaControlView2
- updatePlaybackState();
-
- // Get and set duration value as MediaMetadata for MediaControlView2
- MediaMetadata.Builder builder = new MediaMetadata.Builder();
- builder.putLong(MediaMetadata.METADATA_KEY_DURATION, mInstance.getDuration());
- if (mMediaSession != null) {
- mMediaSession.setMetadata(builder.build());
+ mMediaController.getTransportControls().seekTo(seekToPosition);
}
if (videoWidth != 0 && videoHeight != 0) {
@@ -842,13 +759,14 @@
mVideoHeight = videoHeight;
mInstance.requestLayout();
}
+
if (needToStart()) {
- mInstance.start();
+ mMediaController.getTransportControls().play();
if (mMediaControlView != null) {
mMediaControlView.show();
}
- } else if (!mInstance.isPlaying() && (seekToPosition != 0
- || mInstance.getCurrentPosition() > 0)) {
+ } else if (!(isInPlaybackState() && mMediaPlayer.isPlaying())
+ && (seekToPosition != 0 || mMediaPlayer.getCurrentPosition() > 0)) {
if (mMediaControlView != null) {
// Show the media controls when we're paused into a video and
// make them stick.
@@ -859,9 +777,23 @@
// We don't know the video size yet, but should start anyway.
// The video size might be reported to us later.
if (needToStart()) {
- mInstance.start();
+ mMediaController.getTransportControls().play();
}
}
+ // Create and set playback state for MediaControlView2
+ updatePlaybackState();
+
+ // Get and set duration and title values as MediaMetadata for MediaControlView2
+ MediaMetadata.Builder builder = new MediaMetadata.Builder();
+ if (mMetadata != null && mMetadata.has(Metadata.TITLE)) {
+ mTitle = mMetadata.getString(Metadata.TITLE);
+ }
+ builder.putString(MediaMetadata.METADATA_KEY_TITLE, mTitle);
+ builder.putLong(MediaMetadata.METADATA_KEY_DURATION, mMediaPlayer.getDuration());
+
+ if (mMediaSession != null) {
+ mMediaSession.setMetadata(builder.build());
+ }
}
};
@@ -872,11 +804,8 @@
mTargetState = STATE_PLAYBACK_COMPLETED;
updatePlaybackState();
- if (mMediaControlView != null) {
- mMediaControlView.hide();
- }
if (mOnCompletionListener != null) {
- mOnCompletionListener.onCompletion();
+ mOnCompletionListener.onCompletion(mInstance);
}
if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) {
mAudioManager.abandonAudioFocus(null);
@@ -888,7 +817,7 @@
new MediaPlayer.OnInfoListener() {
public boolean onInfo(MediaPlayer mp, int what, int extra) {
if (mOnInfoListener != null) {
- mOnInfoListener.onInfo(what, extra);
+ mOnInfoListener.onInfo(mInstance, what, extra);
}
return true;
}
@@ -910,7 +839,7 @@
/* If an error handler has been supplied, use it and finish. */
if (mOnErrorListener != null) {
- if (mOnErrorListener.onError(frameworkErr, implErr)) {
+ if (mOnErrorListener.onError(mInstance, frameworkErr, implErr)) {
return true;
}
}
@@ -936,12 +865,12 @@
.setPositiveButton(res.getString(R.string.VideoView2_error_button),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
- int whichButton) {
+ int whichButton) {
/* If we get here, there is no onError listener, so
* at least inform them that the video is over.
*/
if (mOnCompletionListener != null) {
- mOnCompletionListener.onCompletion();
+ mOnCompletionListener.onCompletion(mInstance);
}
}
})
@@ -963,29 +892,161 @@
private class MediaSessionCallback extends MediaSession.Callback {
@Override
public void onCommand(String command, Bundle args, ResultReceiver receiver) {
- switch (command) {
- case MediaControlView2Impl.ACTION_SHOW_SUBTITLE:
- mInstance.showSubtitle();
- break;
- case MediaControlView2Impl.ACTION_HIDE_SUBTITLE:
- mInstance.hideSubtitle();
- break;
+ if (mMediaController.getPlaybackInfo().getPlaybackType()
+ == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
+ mRouteSessionCallback.onCommand(command, args, receiver);
+ } else {
+ switch (command) {
+ case MediaControlView2.COMMAND_SHOW_SUBTITLE:
+ mInstance.showSubtitle();
+ break;
+ case MediaControlView2.COMMAND_HIDE_SUBTITLE:
+ mInstance.hideSubtitle();
+ break;
+ case MediaControlView2.COMMAND_SET_FULLSCREEN:
+ if (mOnFullScreenRequestListener != null) {
+ mOnFullScreenRequestListener.onFullScreenRequest(
+ mInstance,
+ args.getBoolean(MediaControlView2Impl.ARGUMENT_KEY_FULLSCREEN));
+ }
+ break;
+ }
}
}
@Override
+ public void onCustomAction(String action, Bundle extras) {
+ mOnCustomActionListener.onCustomAction(action, extras);
+ }
+
+ @Override
public void onPlay() {
- mInstance.start();
+ if (mMediaController.getPlaybackInfo().getPlaybackType()
+ == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
+ mRouteSessionCallback.onPlay();
+ } else {
+ if (isInPlaybackState() && mCurrentView.hasAvailableSurface()) {
+ applySpeed();
+ mMediaPlayer.start();
+ mCurrentState = STATE_PLAYING;
+ updatePlaybackState();
+ }
+ mTargetState = STATE_PLAYING;
+ if (DEBUG) {
+ Log.d(TAG, "onPlay(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState);
+ }
+ }
}
@Override
public void onPause() {
- mInstance.pause();
+ if (mMediaController.getPlaybackInfo().getPlaybackType()
+ == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
+ mRouteSessionCallback.onPause();
+ } else {
+ if (isInPlaybackState()) {
+ if (mMediaPlayer.isPlaying()) {
+ mMediaPlayer.pause();
+ mCurrentState = STATE_PAUSED;
+ updatePlaybackState();
+ }
+ }
+ mTargetState = STATE_PAUSED;
+ if (DEBUG) {
+ Log.d(TAG, "onPause(). mCurrentState=" + mCurrentState
+ + ", mTargetState=" + mTargetState);
+ }
+ }
}
@Override
public void onSeekTo(long pos) {
- mInstance.seekTo((int) pos);
+ if (mMediaController.getPlaybackInfo().getPlaybackType()
+ == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
+ mRouteSessionCallback.onSeekTo(pos);
+ } else {
+ if (isInPlaybackState()) {
+ mMediaPlayer.seekTo(pos, MediaPlayer.SEEK_PREVIOUS_SYNC);
+ mSeekWhenPrepared = 0;
+ updatePlaybackState();
+ } else {
+ mSeekWhenPrepared = pos;
+ }
+ }
+ }
+
+ @Override
+ public void onStop() {
+ if (mMediaController.getPlaybackInfo().getPlaybackType()
+ == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
+ mRouteSessionCallback.onStop();
+ } else {
+ resetPlayer();
+ }
+ }
+ }
+
+ private class RouteSessionCallback extends MediaSession.Callback {
+ RemotePlaybackClient mClient;
+
+ RemotePlaybackClient.StatusCallback mStatusCallback =
+ new RemotePlaybackClient.StatusCallback() {
+ @Override
+ public void onItemStatusChanged(Bundle data,
+ String sessionId, MediaSessionStatus sessionStatus,
+ String itemId, MediaItemStatus itemStatus) {
+ // TODO: implement this
+ }
+
+ @Override
+ public void onSessionStatusChanged(Bundle data,
+ String sessionId, MediaSessionStatus sessionStatus) {
+ // TODO: implement this
+ }
+
+ @Override
+ public void onSessionChanged(String sessionId) {
+ // TODO: implement this
+ }
+ };
+
+ @Override
+ public void onCommand(String command, Bundle args, ResultReceiver receiver) {
+ ensureClient();
+ // TODO: implement this
+ }
+
+ @Override
+ public void onPlay() {
+ ensureClient();
+ // TODO: implement this
+ }
+
+ @Override
+ public void onPause() {
+ ensureClient();
+ // TODO: implement this
+ }
+
+ @Override
+ public void onSeekTo(long pos) {
+ ensureClient();
+ // TODO: implement this
+ }
+
+ @Override
+ public void onStop() {
+ ensureClient();
+ // TODO: implement this
+ }
+
+ private void ensureClient() {
+ if (mClient == null) {
+ mClient = new RemotePlaybackClient(
+ mInstance.getContext(), mMediaRouter.getSelectedRoute());
+ mClient.setStatusCallback(mStatusCallback);
+ }
}
}
}
diff --git a/packages/MediaComponents/test/AndroidManifest.xml b/packages/MediaComponents/test/AndroidManifest.xml
index fe16583..30bac87 100644
--- a/packages/MediaComponents/test/AndroidManifest.xml
+++ b/packages/MediaComponents/test/AndroidManifest.xml
@@ -32,10 +32,17 @@
<!-- Keep the test services synced together with the TestUtils.java -->
<service android:name="android.media.MockMediaSessionService2">
<intent-filter>
- <action android:name="android.media.session.MediaSessionService2" />
+ <action android:name="android.media.MediaSessionService2" />
</intent-filter>
<meta-data android:name="android.media.session" android:value="TestSession" />
</service>
+ <!-- Keep the test services synced together with the MockMediaLibraryService -->
+ <service android:name="android.media.MockMediaLibraryService2">
+ <intent-filter>
+ <action android:name="android.media.MediaLibraryService2" />
+ </intent-filter>
+ <meta-data android:name="android.media.session" android:value="TestBrowser" />
+ </service>
</application>
<instrumentation
diff --git a/packages/MediaComponents/test/runtest.sh b/packages/MediaComponents/test/runtest.sh
index 5c0ef51..920fa96 100644
--- a/packages/MediaComponents/test/runtest.sh
+++ b/packages/MediaComponents/test/runtest.sh
@@ -129,11 +129,12 @@
${adb} root
${adb} remount
${adb} shell stop
+ ${adb} shell setprop log.tag.MediaSessionService DEBUG
${adb} sync
${adb} shell start
${adb} wait-for-device || break
# Ensure package manager is loaded.
- sleep 5
+ sleep 15
# Install apks
local install_failed="false"
diff --git a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
new file mode 100644
index 0000000..50e59b8
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2018 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.
+ */
+
+package android.media;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.MediaBrowser2.BrowserCallback;
+import android.media.MediaSession2.Command;
+import android.media.MediaSession2.CommandGroup;
+import android.media.MediaSession2.PlaylistParams;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.support.annotation.CallSuper;
+import android.support.annotation.NonNull;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests {@link MediaBrowser2}.
+ * <p>
+ * This test inherits {@link MediaController2Test} to ensure that inherited APIs from
+ * {@link MediaController2} works cleanly.
+ */
+// TODO(jaewan): Implement host-side test so browser and service can run in different processes.
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaBrowser2Test extends MediaController2Test {
+ private static final String TAG = "MediaBrowser2Test";
+
+ @Override
+ TestControllerInterface onCreateController(@NonNull SessionToken2 token,
+ @Nullable TestControllerCallbackInterface callback) {
+ if (callback == null) {
+ callback = new TestBrowserCallbackInterface() {};
+ }
+ return new TestMediaBrowser(mContext, token, new TestBrowserCallback(callback));
+ }
+
+ interface TestBrowserCallbackInterface extends TestControllerCallbackInterface {
+ // Browser specific callbacks
+ default void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {}
+ }
+
+ @Test
+ public void testGetBrowserRoot() throws InterruptedException {
+ final Bundle param = new Bundle();
+ param.putString(TAG, TAG);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final TestControllerCallbackInterface callback = new TestBrowserCallbackInterface() {
+ @Override
+ public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {
+ assertTrue(TestUtils.equals(param, rootHints));
+ assertEquals(MockMediaLibraryService2.ROOT_ID, rootMediaId);
+ assertTrue(TestUtils.equals(MockMediaLibraryService2.EXTRA, rootExtra));
+ latch.countDown();
+ }
+ };
+
+ final SessionToken2 token = MockMediaLibraryService2.getToken(mContext);
+ MediaBrowser2 browser =
+ (MediaBrowser2) createController(token,true, callback);
+ browser.getBrowserRoot(param);
+ assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ }
+
+ public static class TestBrowserCallback extends BrowserCallback
+ implements WaitForConnectionInterface {
+ private final TestControllerCallbackInterface mCallbackProxy;
+ public final CountDownLatch connectLatch = new CountDownLatch(1);
+ public final CountDownLatch disconnectLatch = new CountDownLatch(1);
+
+ TestBrowserCallback(TestControllerCallbackInterface callbackProxy) {
+ if (callbackProxy == null) {
+ throw new IllegalArgumentException("Callback proxy shouldn't be null. Test bug");
+ }
+ mCallbackProxy = callbackProxy;
+ }
+
+ @CallSuper
+ @Override
+ public void onConnected(CommandGroup commands) {
+ super.onConnected(commands);
+ connectLatch.countDown();
+ }
+
+ @CallSuper
+ @Override
+ public void onDisconnected() {
+ super.onDisconnected();
+ disconnectLatch.countDown();
+ }
+
+ @Override
+ public void onPlaybackStateChanged(PlaybackState2 state) {
+ super.onPlaybackStateChanged(state);
+ mCallbackProxy.onPlaybackStateChanged(state);
+ }
+
+ @Override
+ public void onPlaylistParamsChanged(PlaylistParams params) {
+ super.onPlaylistParamsChanged(params);
+ mCallbackProxy.onPlaylistParamsChanged(params);
+ }
+
+ @Override
+ public void onCustomCommand(Command command, Bundle args, ResultReceiver receiver) {
+ super.onCustomCommand(command, args, receiver);
+ mCallbackProxy.onCustomCommand(command, args, receiver);
+ }
+
+ @Override
+ public void onGetRootResult(Bundle rootHints, String rootMediaId, Bundle rootExtra) {
+ super.onGetRootResult(rootHints, rootMediaId, rootExtra);
+ if (mCallbackProxy instanceof TestBrowserCallbackInterface) {
+ ((TestBrowserCallbackInterface) mCallbackProxy)
+ .onGetRootResult(rootHints, rootMediaId, rootExtra);
+ }
+ }
+
+ @Override
+ public void waitForConnect(boolean expect) throws InterruptedException {
+ if (expect) {
+ assertTrue(connectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } else {
+ assertFalse(connectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+ }
+
+ @Override
+ public void waitForDisconnect(boolean expect) throws InterruptedException {
+ if (expect) {
+ assertTrue(disconnectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } else {
+ assertFalse(disconnectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+ }
+ }
+
+ public class TestMediaBrowser extends MediaBrowser2 implements TestControllerInterface {
+ private final BrowserCallback mCallback;
+
+ public TestMediaBrowser(@NonNull Context context, @NonNull SessionToken2 token,
+ @NonNull ControllerCallback callback) {
+ super(context, token, sHandlerExecutor, (BrowserCallback) callback);
+ mCallback = (BrowserCallback) callback;
+ }
+
+ @Override
+ public BrowserCallback getCallback() {
+ return mCallback;
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/MediaComponents/test/src/android/media/MediaController2Test.java b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
index 38d34cc..27dbaf8 100644
--- a/packages/MediaComponents/test/src/android/media/MediaController2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
@@ -16,14 +16,19 @@
package android.media;
-import android.media.MediaPlayerBase.PlaybackListener;
+import android.media.MediaController2.ControllerCallback;
+import android.media.MediaPlayerInterface.PlaybackListener;
+import android.media.MediaSession2.Command;
import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.PlaylistParams;
import android.media.MediaSession2.SessionCallback;
import android.media.TestUtils.SyncHandler;
import android.media.session.PlaybackState;
+import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
+import android.os.ResultReceiver;
import android.support.test.filters.FlakyTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -45,26 +50,26 @@
*/
// TODO(jaewan): Implement host-side test so controller and session can run in different processes.
// TODO(jaewan): Fix flaky failure -- see MediaController2Impl.getController()
+// TODO(jaeawn): Revisit create/close session in the sHandler. It's no longer necessary.
@RunWith(AndroidJUnit4.class)
@SmallTest
@FlakyTest
public class MediaController2Test extends MediaSession2TestBase {
private static final String TAG = "MediaController2Test";
- private MediaSession2 mSession;
- private MediaController2Wrapper mController;
- private MockPlayer mPlayer;
+ MediaSession2 mSession;
+ MediaController2 mController;
+ MockPlayer mPlayer;
@Before
@Override
public void setUp() throws Exception {
super.setUp();
// Create this test specific MediaSession2 to use our own Handler.
- sHandler.postAndSync(()->{
- mPlayer = new MockPlayer(1);
- mSession = new MediaSession2.Builder(mContext, mPlayer).setId(TAG).build();
- });
-
+ mPlayer = new MockPlayer(1);
+ mSession = new MediaSession2.Builder(mContext, mPlayer)
+ .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext))
+ .setId(TAG).build();
mController = createController(mSession.getToken());
TestServiceRegistry.getInstance().setHandler(sHandler);
}
@@ -73,11 +78,9 @@
@Override
public void cleanUp() throws Exception {
super.cleanUp();
- sHandler.postAndSync(() -> {
- if (mSession != null) {
- mSession.setPlayer(null);
- }
- });
+ if (mSession != null) {
+ mSession.close();
+ }
TestServiceRegistry.getInstance().cleanUp();
}
@@ -103,7 +106,6 @@
assertTrue(mPlayer.mPauseCalled);
}
-
@Test
public void testSkipToPrevious() throws InterruptedException {
mController.skipToPrevious();
@@ -138,59 +140,146 @@
}
@Test
+ public void testPrepare() throws InterruptedException {
+ mController.prepare();
+ try {
+ assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+ assertTrue(mPlayer.mPrepareCalled);
+ }
+
+ @Test
+ public void testFastForward() throws InterruptedException {
+ mController.fastForward();
+ try {
+ assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+ assertTrue(mPlayer.mFastForwardCalled);
+ }
+
+ @Test
+ public void testRewind() throws InterruptedException {
+ mController.rewind();
+ try {
+ assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+ assertTrue(mPlayer.mRewindCalled);
+ }
+
+ @Test
+ public void testSeekTo() throws InterruptedException {
+ final long seekPosition = 12125L;
+ mController.seekTo(seekPosition);
+ try {
+ assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+ assertTrue(mPlayer.mSeekToCalled);
+ assertEquals(seekPosition, mPlayer.mSeekPosition);
+ }
+
+ @Test
+ public void testSetCurrentPlaylistItem() throws InterruptedException {
+ final int itemIndex = 9;
+ mController.setCurrentPlaylistItem(itemIndex);
+ try {
+ assertTrue(mPlayer.mCountDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+ assertTrue(mPlayer.mSetCurrentPlaylistItemCalled);
+ assertEquals(itemIndex, mPlayer.mItemIndex);
+ }
+
+ @Test
+ public void testGetSetPlaylistParams() throws Exception {
+ final PlaylistParams params = new PlaylistParams(
+ PlaylistParams.REPEAT_MODE_ALL,
+ PlaylistParams.SHUFFLE_MODE_ALL,
+ null /* PlaylistMetadata */);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
+ @Override
+ public void onPlaylistParamsChanged(PlaylistParams givenParams) {
+ TestUtils.equals(params.toBundle(), givenParams.toBundle());
+ latch.countDown();
+ }
+ };
+
+ final MediaController2 controller = createController(mSession.getToken(), true, callback);
+ controller.setPlaylistParams(params);
+
+ assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ TestUtils.equals(params.toBundle(), mSession.getPlaylistParams().toBundle());
+ TestUtils.equals(params.toBundle(), controller.getPlaylistParams().toBundle());
+ }
+
+ @Test
public void testGetPackageName() {
assertEquals(mContext.getPackageName(), mController.getSessionToken().getPackageName());
}
+ // This also tests testGetPlaybackState().
@Test
- public void testGetPlaybackState() throws InterruptedException {
- final CountDownLatch latch = new CountDownLatch(1);
- final MediaPlayerBase.PlaybackListener listener = (state) -> {
- assertEquals(PlaybackState.STATE_BUFFERING, state.getState());
- latch.countDown();
- };
- assertNull(mController.getPlaybackState());
- mController.addPlaybackListener(listener, sHandler);
-
- mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_BUFFERING));
- assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- assertEquals(PlaybackState.STATE_BUFFERING, mController.getPlaybackState().getState());
- }
-
- @Test
- public void testAddPlaybackListener() throws InterruptedException {
+ public void testControllerCallback_onPlaybackStateChanged() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(2);
- final MediaPlayerBase.PlaybackListener listener = (state) -> {
- switch ((int) latch.getCount()) {
- case 2:
- assertEquals(PlaybackState.STATE_PLAYING, state.getState());
- break;
- case 1:
- assertEquals(PlaybackState.STATE_PAUSED, state.getState());
- break;
+ final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackState2 state) {
+ switch ((int) latch.getCount()) {
+ case 2:
+ assertEquals(PlaybackState.STATE_PLAYING, state.getState());
+ break;
+ case 1:
+ assertEquals(PlaybackState.STATE_PAUSED, state.getState());
+ break;
+ }
+ latch.countDown();
}
- latch.countDown();
};
- mController.addPlaybackListener(listener, sHandler);
- sHandler.postAndSync(()->{
- mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PLAYING));
- mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PAUSED));
- });
+ mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PLAYING));
+ mController = createController(mSession.getToken(), true, callback);
+ mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PAUSED));
assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ assertEquals(PlaybackState.STATE_PAUSED, mController.getPlaybackState().getState());
}
@Test
- public void testRemovePlaybackListener() throws InterruptedException {
+ public void testSendCustomCommand() throws InterruptedException {
+ // TODO(jaewan): Need to revisit with the permission.
+ final Command testCommand =
+ new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE);
+ final Bundle testArgs = new Bundle();
+ testArgs.putString("args", "testSendCustomAction");
+
final CountDownLatch latch = new CountDownLatch(1);
- final MediaPlayerBase.PlaybackListener listener = (state) -> {
- fail();
- latch.countDown();
+ final SessionCallback callback = new SessionCallback(mContext) {
+ @Override
+ public void onCustomCommand(ControllerInfo controller, Command customCommand,
+ Bundle args, ResultReceiver cb) {
+ super.onCustomCommand(controller, customCommand, args, cb);
+ assertEquals(mContext.getPackageName(), controller.getPackageName());
+ assertEquals(testCommand, customCommand);
+ assertTrue(TestUtils.equals(testArgs, args));
+ assertNull(cb);
+ latch.countDown();
+ }
};
- mController.addPlaybackListener(listener, sHandler);
- mController.removePlaybackListener(listener);
- mPlayer.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PLAYING));
- assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ mSession.close();
+ mSession = new MediaSession2.Builder(mContext, mPlayer)
+ .setSessionCallback(sHandlerExecutor, callback).setId(TAG).build();
+ final MediaController2 controller = createController(mSession.getToken());
+ controller.sendCustomCommand(testCommand, testArgs, null);
+ assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
}
@Test
@@ -203,42 +292,43 @@
@Test
public void testControllerCallback_sessionRejects() throws InterruptedException {
- final MediaSession2.SessionCallback sessionCallback = new SessionCallback() {
+ final MediaSession2.SessionCallback sessionCallback = new SessionCallback(mContext) {
@Override
public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
return null;
}
};
sHandler.postAndSync(() -> {
- mSession.setPlayer(null);
+ mSession.close();
mSession = new MediaSession2.Builder(mContext, mPlayer)
- .setSessionCallback(sessionCallback).build();
+ .setSessionCallback(sHandlerExecutor, sessionCallback).build();
});
- MediaController2Wrapper controller = createController(mSession.getToken(), false, null);
+ MediaController2 controller =
+ createController(mSession.getToken(), false, null);
assertNotNull(controller);
- controller.waitForConnect(false);
- controller.waitForDisconnect(true);
+ waitForConnect(controller, false);
+ waitForDisconnect(controller, true);
}
@Test
public void testControllerCallback_releaseSession() throws InterruptedException {
sHandler.postAndSync(() -> {
- mSession.setPlayer(null);
+ mSession.close();
});
- mController.waitForDisconnect(true);
+ waitForDisconnect(mController, true);
}
@Test
public void testControllerCallback_release() throws InterruptedException {
- mController.release();
- mController.waitForDisconnect(true);
+ mController.close();
+ waitForDisconnect(mController, true);
}
@Test
public void testIsConnected() throws InterruptedException {
assertTrue(mController.isConnected());
sHandler.postAndSync(()->{
- mSession.setPlayer(null);
+ mSession.close();
});
// postAndSync() to wait until the disconnection is propagated.
sHandler.postAndSync(()->{
@@ -252,7 +342,7 @@
@Test
public void testDeadlock() throws InterruptedException {
sHandler.postAndSync(() -> {
- mSession.setPlayer(null);
+ mSession.close();
mSession = null;
});
@@ -268,14 +358,12 @@
final MockPlayer player = new MockPlayer(0);
sessionHandler.postAndSync(() -> {
mSession = new MediaSession2.Builder(mContext, mPlayer)
+ .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext))
.setId("testDeadlock").build();
});
final MediaController2 controller = createController(mSession.getToken());
testHandler.post(() -> {
- controller.addPlaybackListener((state) -> {
- // no-op. Just to set a binder call path from session to controller.
- }, sessionHandler);
- final PlaybackState state = createPlaybackState(PlaybackState.STATE_ERROR);
+ final PlaybackState2 state = createPlaybackState(PlaybackState.STATE_ERROR);
for (int i = 0; i < 100; i++) {
// triggers call from session to controller.
player.notifyPlaybackState(state);
@@ -300,7 +388,7 @@
if (mSession != null) {
sessionHandler.postAndSync(() -> {
// Clean up here because sessionHandler will be removed afterwards.
- mSession.setPlayer(null);
+ mSession.close();
mSession = null;
});
}
@@ -316,25 +404,36 @@
@Ignore
@Test
public void testGetServiceToken() {
- SessionToken token = TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID);
+ SessionToken2 token = TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID);
assertNotNull(token);
assertEquals(mContext.getPackageName(), token.getPackageName());
assertEquals(MockMediaSessionService2.ID, token.getId());
- assertNull(token.getSessionBinder());
- assertEquals(SessionToken.TYPE_SESSION_SERVICE, token.getType());
+ assertEquals(SessionToken2.TYPE_SESSION_SERVICE, token.getType());
}
- private void connectToService(SessionToken token) throws InterruptedException {
+ private void connectToService(SessionToken2 token) throws InterruptedException {
mController = createController(token);
mSession = TestServiceRegistry.getInstance().getServiceInstance().getSession();
mPlayer = (MockPlayer) mSession.getPlayer();
}
+ // TODO(jaewan): Reenable when session manager detects app installs
@Ignore
@Test
- public void testConnectToService() throws InterruptedException {
+ public void testConnectToService_sessionService() throws InterruptedException {
connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+ testConnectToService();
+ }
+ // TODO(jaewan): Reenable when session manager detects app installs
+ @Ignore
+ @Test
+ public void testConnectToService_libraryService() throws InterruptedException {
+ connectToService(TestUtils.getServiceToken(mContext, MockMediaLibraryService2.ID));
+ testConnectToService();
+ }
+
+ public void testConnectToService() throws InterruptedException {
TestServiceRegistry serviceInfo = TestServiceRegistry.getInstance();
ControllerInfo info = serviceInfo.getOnConnectControllerInfo();
assertEquals(mContext.getPackageName(), info.getPackageName());
@@ -347,6 +446,8 @@
assertTrue(mPlayer.mPlayCalled);
// Test command from session service to controller
+ // TODO(jaewan): Add equivalent tests again
+ /*
final CountDownLatch latch = new CountDownLatch(1);
mController.addPlaybackListener((state) -> {
assertNotNull(state);
@@ -356,6 +457,7 @@
mPlayer.notifyPlaybackState(
TestUtils.createPlaybackState(PlaybackState.STATE_REWINDING));
assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ */
}
@Test
@@ -371,34 +473,48 @@
}
@Test
- public void testRelease_beforeConnected() throws InterruptedException {
+ public void testClose_beforeConnected() throws InterruptedException {
MediaController2 controller =
createController(mSession.getToken(), false, null);
- controller.release();
+ controller.close();
}
@Test
- public void testRelease_twice() throws InterruptedException {
- mController.release();
- mController.release();
+ public void testClose_twice() throws InterruptedException {
+ mController.close();
+ mController.close();
}
@Test
- public void testRelease_session() throws InterruptedException {
+ public void testClose_session() throws InterruptedException {
final String id = mSession.getToken().getId();
- mController.release();
- // Release is done immediately for session.
+ mController.close();
+ // close is done immediately for session.
testNoInteraction();
- // Test whether the controller is notified about later release of the session or
+ // Test whether the controller is notified about later close of the session or
// re-creation.
testControllerAfterSessionIsGone(id);
}
+ // TODO(jaewan): Reenable when session manager detects app installs
@Ignore
@Test
- public void testRelease_sessionService() throws InterruptedException {
+ public void testClose_sessionService() throws InterruptedException {
connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+ testCloseFromService();
+ }
+
+ // TODO(jaewan): Reenable when session manager detects app installs
+ @Ignore
+ @Test
+ public void testClose_libraryService() throws InterruptedException {
+ connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
+ testCloseFromService();
+ }
+
+ private void testCloseFromService() throws InterruptedException {
+ final String id = mController.getSessionToken().getId();
final CountDownLatch latch = new CountDownLatch(1);
TestServiceRegistry.getInstance().setServiceInstanceChangedCallback((service) -> {
if (service == null) {
@@ -406,45 +522,49 @@
latch.countDown();
}
});
- mController.release();
- // Wait until release triggers onDestroy() of the session service.
+ mController.close();
+ // Wait until close triggers onDestroy() of the session service.
assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
assertNull(TestServiceRegistry.getInstance().getServiceInstance());
testNoInteraction();
- // Test whether the controller is notified about later release of the session or
+ // Test whether the controller is notified about later close of the session or
// re-creation.
- testControllerAfterSessionIsGone(MockMediaSessionService2.ID);
+ testControllerAfterSessionIsGone(id);
}
private void testControllerAfterSessionIsGone(final String id) throws InterruptedException {
sHandler.postAndSync(() -> {
- // TODO(jaewan): Use Session.release later when we add the API.
- mSession.setPlayer(null);
+ // TODO(jaewan): Use Session.close later when we add the API.
+ mSession.close();
});
- mController.waitForDisconnect(true);
+ waitForDisconnect(mController, true);
testNoInteraction();
// Test with the newly created session.
sHandler.postAndSync(() -> {
// Recreated session has different session stub, so previously created controller
// shouldn't be available.
- mSession = new MediaSession2.Builder(mContext, mPlayer).setId(id).build();
+ mSession = new MediaSession2.Builder(mContext, mPlayer)
+ .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext))
+ .setId(id).build();
});
testNoInteraction();
}
-
private void testNoInteraction() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
final PlaybackListener playbackListener = (state) -> {
- fail("Controller shouldn't be notified about change in session after the release.");
+ fail("Controller shouldn't be notified about change in session after the close.");
latch.countDown();
};
+ // TODO(jaewan): Add equivalent tests again
+ /*
mController.addPlaybackListener(playbackListener, sHandler);
mPlayer.notifyPlaybackState(TestUtils.createPlaybackState(PlaybackState.STATE_BUFFERING));
assertFalse(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
mController.removePlaybackListener(playbackListener);
+ */
}
// TODO(jaewan): Add test for service connect rejection, when we differentiate session
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
index f7224ea..c5bcfff 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
@@ -16,27 +16,37 @@
package android.media;
-import android.media.MediaPlayerBase.PlaybackListener;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import static android.media.TestUtils.createPlaybackState;
+
+import android.media.MediaPlayerInterface.PlaybackListener;
import android.media.MediaSession2.Builder;
+import android.media.MediaSession2.Command;
import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.PlaylistParams;
import android.media.MediaSession2.SessionCallback;
-import android.media.session.PlaybackState;
-import android.os.Process;
+import android.os.Bundle;
import android.os.Looper;
+import android.os.Process;
+import android.os.ResultReceiver;
import android.support.annotation.NonNull;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
import java.util.ArrayList;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import static android.media.TestUtils.createPlaybackState;
import static org.junit.Assert.*;
/**
@@ -54,19 +64,16 @@
@Override
public void setUp() throws Exception {
super.setUp();
- sHandler.postAndSync(() -> {
- mPlayer = new MockPlayer(0);
- mSession = new MediaSession2.Builder(mContext, mPlayer).build();
- });
+ mPlayer = new MockPlayer(0);
+ mSession = new MediaSession2.Builder(mContext, mPlayer)
+ .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext)).build();
}
@After
@Override
public void cleanUp() throws Exception {
super.cleanUp();
- sHandler.postAndSync(() -> {
- mSession.setPlayer(null);
- });
+ mSession.close();
}
@Test
@@ -93,7 +100,7 @@
// Test if setPlayer doesn't crash with various situations.
mSession.setPlayer(mPlayer);
mSession.setPlayer(player);
- mSession.setPlayer(null);
+ mSession.close();
});
}
@@ -138,6 +145,56 @@
}
@Test
+ public void testSetPlaylist() throws Exception {
+ final List<MediaItem2> playlist = new ArrayList<>();
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
+ @Override
+ public void onPlaylistChanged(List<MediaItem2> givenList) {
+ assertMediaItemListEquals(playlist, givenList);
+ latch.countDown();
+ }
+ };
+
+ final MediaController2 controller = createController(mSession.getToken(), true, callback);
+ mSession.setPlaylist(playlist);
+
+ assertTrue(mPlayer.mSetPlaylistCalled);
+ assertMediaItemListEquals(playlist, mPlayer.mPlaylist);
+ assertMediaItemListEquals(playlist, mSession.getPlaylist());
+
+ assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ assertMediaItemListEquals(playlist, controller.getPlaylist());
+ }
+
+ @Test
+ public void testSetPlaylistParams() throws Exception {
+ final PlaylistParams params = new PlaylistParams(
+ PlaylistParams.REPEAT_MODE_ALL,
+ PlaylistParams.SHUFFLE_MODE_ALL,
+ null /* PlaylistMetadata */);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
+ @Override
+ public void onPlaylistParamsChanged(PlaylistParams givenParams) {
+ TestUtils.equals(params.toBundle(), givenParams.toBundle());
+ latch.countDown();
+ }
+ };
+
+ final MediaController2 controller = createController(mSession.getToken(), true, callback);
+ mSession.setPlaylistParams(params);
+ assertTrue(mPlayer.mSetPlaylistParamsCalled);
+ TestUtils.equals(params.toBundle(), mPlayer.mPlaylistParams.toBundle());
+ TestUtils.equals(params.toBundle(), mSession.getPlaylistParams().toBundle());
+ assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ }
+
+ // TODO(jaewan): Re-enable test..
+ @Ignore
+ @Test
public void testPlaybackStateChangedListener() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(2);
final MockPlayer player = new MockPlayer(0);
@@ -146,40 +203,41 @@
assertNotNull(state);
switch ((int) latch.getCount()) {
case 2:
- assertEquals(PlaybackState.STATE_PLAYING, state.getState());
+ assertEquals(PlaybackState2.STATE_PLAYING, state.getState());
break;
case 1:
- assertEquals(PlaybackState.STATE_PAUSED, state.getState());
+ assertEquals(PlaybackState2.STATE_PAUSED, state.getState());
break;
case 0:
fail();
}
latch.countDown();
};
- player.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PLAYING));
+ player.notifyPlaybackState(createPlaybackState(PlaybackState2.STATE_PLAYING));
sHandler.postAndSync(() -> {
- mSession.addPlaybackListener(listener, sHandler);
+ mSession.addPlaybackListener(sHandlerExecutor, listener);
// When the player is set, listeners will be notified about the player's current state.
mSession.setPlayer(player);
});
- player.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PAUSED));
+ player.notifyPlaybackState(createPlaybackState(PlaybackState2.STATE_PAUSED));
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@Test
public void testBadPlayer() throws InterruptedException {
+ // TODO(jaewan): Add equivalent tests again
final CountDownLatch latch = new CountDownLatch(3); // expected call + 1
final BadPlayer player = new BadPlayer(0);
sHandler.postAndSync(() -> {
- mSession.addPlaybackListener((state) -> {
+ mSession.addPlaybackListener(sHandlerExecutor, (state) -> {
// This will be called for every setPlayer() calls, but no more.
assertNull(state);
latch.countDown();
- }, sHandler);
+ });
mSession.setPlayer(player);
mSession.setPlayer(mPlayer);
});
- player.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_PAUSED));
+ player.notifyPlaybackState(createPlaybackState(PlaybackState2.STATE_PAUSED));
assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
}
@@ -199,10 +257,10 @@
public void testOnCommandCallback() throws InterruptedException {
final MockOnCommandCallback callback = new MockOnCommandCallback();
sHandler.postAndSync(() -> {
- mSession.setPlayer(null);
+ mSession.close();
mPlayer = new MockPlayer(1);
mSession = new MediaSession2.Builder(mContext, mPlayer)
- .setSessionCallback(callback).build();
+ .setSessionCallback(sHandlerExecutor, callback).build();
});
MediaController2 controller = createController(mSession.getToken());
controller.pause();
@@ -224,17 +282,64 @@
public void testOnConnectCallback() throws InterruptedException {
final MockOnConnectCallback sessionCallback = new MockOnConnectCallback();
sHandler.postAndSync(() -> {
- mSession.setPlayer(null);
+ mSession.close();
mSession = new MediaSession2.Builder(mContext, mPlayer)
- .setSessionCallback(sessionCallback).build();
+ .setSessionCallback(sHandlerExecutor, sessionCallback).build();
});
- MediaController2Wrapper controller = createController(mSession.getToken(), false, null);
+ MediaController2 controller =
+ createController(mSession.getToken(), false, null);
assertNotNull(controller);
- controller.waitForConnect(false);
- controller.waitForDisconnect(true);
+ waitForConnect(controller, false);
+ waitForDisconnect(controller, true);
+ }
+
+ @Test
+ public void testSendCustomAction() throws InterruptedException {
+ final Command testCommand =
+ new Command(mContext, MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE);
+ final Bundle testArgs = new Bundle();
+ testArgs.putString("args", "testSendCustomAction");
+
+ final CountDownLatch latch = new CountDownLatch(2);
+ final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
+ @Override
+ public void onCustomCommand(Command command, Bundle args, ResultReceiver receiver) {
+ assertEquals(testCommand, command);
+ assertTrue(TestUtils.equals(testArgs, args));
+ assertNull(receiver);
+ latch.countDown();
+ }
+ };
+ final MediaController2 controller =
+ createController(mSession.getToken(), true, callback);
+ // TODO(jaewan): Test with multiple controllers
+ mSession.sendCustomCommand(testCommand, testArgs);
+
+ ControllerInfo controllerInfo = getTestControllerInfo();
+ assertNotNull(controllerInfo);
+ // TODO(jaewan): Test receivers as well.
+ mSession.sendCustomCommand(controllerInfo, testCommand, testArgs, null);
+ assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ }
+
+ private ControllerInfo getTestControllerInfo() {
+ List<ControllerInfo> controllers = mSession.getConnectedControllers();
+ assertNotNull(controllers);
+ final String packageName = mContext.getPackageName();
+ for (int i = 0; i < controllers.size(); i++) {
+ if (TextUtils.equals(packageName, controllers.get(i).getPackageName())) {
+ return controllers.get(i);
+ }
+ }
+ fail("Fails to get custom command");
+ return null;
}
public class MockOnConnectCallback extends SessionCallback {
+ public MockOnConnectCallback() {
+ super(mContext);
+ }
+
@Override
public MediaSession2.CommandGroup onConnect(ControllerInfo controllerInfo) {
if (Process.myUid() != controllerInfo.getUid()) {
@@ -251,6 +356,10 @@
public class MockOnCommandCallback extends SessionCallback {
public final ArrayList<MediaSession2.Command> commands = new ArrayList<>();
+ public MockOnCommandCallback() {
+ super(mContext);
+ }
+
@Override
public boolean onCommandRequest(ControllerInfo controllerInfo, MediaSession2.Command command) {
assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
@@ -263,4 +372,28 @@
return true;
}
}
+
+ private static void assertMediaItemListEquals(List<MediaItem2> a, List<MediaItem2> b) {
+ if (a == null || b == null) {
+ assertEquals(a, b);
+ }
+ assertEquals(a.size(), b.size());
+
+ for (int i = 0; i < a.size(); i++) {
+ MediaItem2 aItem = a.get(i);
+ MediaItem2 bItem = b.get(i);
+
+ if (aItem == null || bItem == null) {
+ assertEquals(aItem, bItem);
+ continue;
+ }
+
+ assertEquals(aItem.getMediaId(), bItem.getMediaId());
+ assertEquals(aItem.getFlags(), bItem.getFlags());
+ TestUtils.equals(aItem.getMetadata().getBundle(), bItem.getMetadata().getBundle());
+
+ // Note: Here it does not check whether DataSourceDesc are equal,
+ // since there DataSourceDec is not comparable.
+ }
+ }
}
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
index ab842c4..8e1c782 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2TestBase.java
@@ -20,12 +20,17 @@
import static junit.framework.Assert.assertTrue;
import android.content.Context;
+import android.media.MediaController2.ControllerCallback;
+import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandGroup;
+import android.os.Bundle;
import android.os.HandlerThread;
+import android.os.ResultReceiver;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.test.InstrumentationRegistry;
+
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -50,6 +55,26 @@
Context mContext;
private List<MediaController2> mControllers = new ArrayList<>();
+ interface TestControllerInterface {
+ ControllerCallback getCallback();
+ }
+
+ interface TestControllerCallbackInterface {
+ // Add methods in ControllerCallback/BrowserCallback that you want to test.
+ default void onPlaylistChanged(List<MediaItem2> playlist) {}
+ default void onPlaylistParamsChanged(MediaSession2.PlaylistParams params) {}
+
+ // Currently empty. Add methods in ControllerCallback/BrowserCallback that you want to test.
+ default void onPlaybackStateChanged(PlaybackState2 state) { }
+
+ default void onCustomCommand(Command command, Bundle args, ResultReceiver receiver) {}
+ }
+
+ interface WaitForConnectionInterface {
+ void waitForConnect(boolean expect) throws InterruptedException;
+ void waitForDisconnect(boolean expect) throws InterruptedException;
+ }
+
@BeforeClass
public static void setUpThread() {
if (sHandler == null) {
@@ -79,32 +104,75 @@
@CallSuper
public void cleanUp() throws Exception {
for (int i = 0; i < mControllers.size(); i++) {
- mControllers.get(i).release();
+ mControllers.get(i).close();
}
}
- MediaController2Wrapper createController(SessionToken token) throws InterruptedException {
+ final MediaController2 createController(SessionToken2 token) throws InterruptedException {
return createController(token, true, null);
}
- MediaController2Wrapper createController(@NonNull SessionToken token, boolean waitForConnect,
- @Nullable TestControllerCallback callback)
+ final MediaController2 createController(@NonNull SessionToken2 token,
+ boolean waitForConnect, @Nullable TestControllerCallbackInterface callback)
throws InterruptedException {
- if (callback == null) {
- callback = new TestControllerCallback();
+ TestControllerInterface instance = onCreateController(token, callback);
+ if (!(instance instanceof MediaController2)) {
+ throw new RuntimeException("Test has a bug. Expected MediaController2 but returned "
+ + instance);
}
- MediaController2Wrapper controller = new MediaController2Wrapper(mContext, token, callback);
+ MediaController2 controller = (MediaController2) instance;
mControllers.add(controller);
if (waitForConnect) {
- controller.waitForConnect(true);
+ waitForConnect(controller, true);
}
return controller;
}
- public static class TestControllerCallback extends MediaController2.ControllerCallback {
+ private static WaitForConnectionInterface getWaitForConnectionInterface(
+ MediaController2 controller) {
+ if (!(controller instanceof TestControllerInterface)) {
+ throw new RuntimeException("Test has a bug. Expected controller implemented"
+ + " TestControllerInterface but got " + controller);
+ }
+ ControllerCallback callback = ((TestControllerInterface) controller).getCallback();
+ if (!(callback instanceof WaitForConnectionInterface)) {
+ throw new RuntimeException("Test has a bug. Expected controller with callback "
+ + " implemented WaitForConnectionInterface but got " + controller);
+ }
+ return (WaitForConnectionInterface) callback;
+ }
+
+ public static void waitForConnect(MediaController2 controller, boolean expected)
+ throws InterruptedException {
+ getWaitForConnectionInterface(controller).waitForConnect(expected);
+ }
+
+ public static void waitForDisconnect(MediaController2 controller, boolean expected)
+ throws InterruptedException {
+ getWaitForConnectionInterface(controller).waitForDisconnect(expected);
+ }
+
+ TestControllerInterface onCreateController(@NonNull SessionToken2 token,
+ @Nullable TestControllerCallbackInterface callback) {
+ if (callback == null) {
+ callback = new TestControllerCallbackInterface() {};
+ }
+ return new TestMediaController(mContext, token, new TestControllerCallback(callback));
+ }
+
+ public static class TestControllerCallback extends MediaController2.ControllerCallback
+ implements WaitForConnectionInterface {
+ public final TestControllerCallbackInterface mCallbackProxy;
public final CountDownLatch connectLatch = new CountDownLatch(1);
public final CountDownLatch disconnectLatch = new CountDownLatch(1);
+ TestControllerCallback(@NonNull TestControllerCallbackInterface callbackProxy) {
+ if (callbackProxy == null) {
+ throw new IllegalArgumentException("Callback proxy shouldn't be null. Test bug");
+ }
+ mCallbackProxy = callbackProxy;
+ }
+
@CallSuper
@Override
public void onConnected(CommandGroup commands) {
@@ -118,31 +186,64 @@
super.onDisconnected();
disconnectLatch.countDown();
}
+
+ @Override
+ public void onPlaybackStateChanged(PlaybackState2 state) {
+ super.onPlaybackStateChanged(state);
+ mCallbackProxy.onPlaybackStateChanged(state);
+ }
+
+ @Override
+ public void onCustomCommand(Command command, Bundle args, ResultReceiver receiver) {
+ super.onCustomCommand(command, args, receiver);
+ mCallbackProxy.onCustomCommand(command, args, receiver);
+ }
+
+ @Override
+ public void waitForConnect(boolean expect) throws InterruptedException {
+ if (expect) {
+ assertTrue(connectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } else {
+ assertFalse(connectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+ }
+
+ @Override
+ public void waitForDisconnect(boolean expect) throws InterruptedException {
+ if (expect) {
+ assertTrue(disconnectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ } else {
+ assertFalse(disconnectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+ }
+
+ @Override
+ public void onPlaylistChanged(List<MediaItem2> params) {
+ if (mCallbackProxy != null) {
+ mCallbackProxy.onPlaylistChanged(params);
+ }
+ }
+
+ @Override
+ public void onPlaylistParamsChanged(MediaSession2.PlaylistParams params) {
+ if (mCallbackProxy != null) {
+ mCallbackProxy.onPlaylistParamsChanged(params);
+ }
+ }
}
- public class MediaController2Wrapper extends MediaController2 {
- private final TestControllerCallback mCallback;
+ public class TestMediaController extends MediaController2 implements TestControllerInterface {
+ private final ControllerCallback mCallback;
- public MediaController2Wrapper(@NonNull Context context, @NonNull SessionToken token,
- @NonNull TestControllerCallback callback) {
- super(context, token, callback, sHandlerExecutor);
+ public TestMediaController(@NonNull Context context, @NonNull SessionToken2 token,
+ @NonNull ControllerCallback callback) {
+ super(context, token, sHandlerExecutor, callback);
mCallback = callback;
}
- public void waitForConnect(boolean expect) throws InterruptedException {
- if (expect) {
- assertTrue(mCallback.connectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } else {
- assertFalse(mCallback.connectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
- }
-
- public void waitForDisconnect(boolean expect) throws InterruptedException {
- if (expect) {
- assertTrue(mCallback.disconnectLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- } else {
- assertFalse(mCallback.disconnectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
+ @Override
+ public ControllerCallback getCallback() {
+ return mCallback;
}
}
}
diff --git a/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
index 0ee37b1..96ae8b7 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
@@ -59,9 +59,10 @@
// Specify TAG here so {@link MediaSession2.getInstance()} doesn't complaint about
// per test thread differs across the {@link MediaSession2} with the same TAG.
final MockPlayer player = new MockPlayer(1);
- sHandler.postAndSync(() -> {
- mSession = new MediaSession2.Builder(mContext, player).setId(TAG).build();
- });
+ mSession = new MediaSession2.Builder(mContext, player)
+ .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext))
+ .setId(TAG)
+ .build();
ensureChangeInSession();
}
@@ -70,9 +71,7 @@
public void cleanUp() throws Exception {
super.cleanUp();
sHandler.removeCallbacksAndMessages(null);
- sHandler.postAndSync(() -> {
- mSession.setPlayer(null);
- });
+ mSession.close();
}
// TODO(jaewan): Make this host-side test to see per-user behavior.
@@ -82,13 +81,12 @@
player.notifyPlaybackState(createPlaybackState(PlaybackState.STATE_STOPPED));
MediaController2 controller = null;
- List<SessionToken> tokens = mManager.getActiveSessionTokens();
+ List<SessionToken2> tokens = mManager.getActiveSessionTokens();
assertNotNull(tokens);
for (int i = 0; i < tokens.size(); i++) {
- SessionToken token = tokens.get(i);
+ SessionToken2 token = tokens.get(i);
if (mContext.getPackageName().equals(token.getPackageName())
&& TAG.equals(token.getId())) {
- assertNotNull(token.getSessionBinder());
assertNull(controller);
controller = createController(token);
}
@@ -111,9 +109,9 @@
@Test
public void testGetSessionTokens_sessionRejected() throws InterruptedException {
sHandler.postAndSync(() -> {
- mSession.setPlayer(null);
+ mSession.close();
mSession = new MediaSession2.Builder(mContext, new MockPlayer(0)).setId(TAG)
- .setSessionCallback(new SessionCallback() {
+ .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext) {
@Override
public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
// Reject all connection request.
@@ -124,10 +122,10 @@
ensureChangeInSession();
boolean foundSession = false;
- List<SessionToken> tokens = mManager.getActiveSessionTokens();
+ List<SessionToken2> tokens = mManager.getActiveSessionTokens();
assertNotNull(tokens);
for (int i = 0; i < tokens.size(); i++) {
- SessionToken token = tokens.get(i);
+ SessionToken2 token = tokens.get(i);
if (mContext.getPackageName().equals(token.getPackageName())
&& TAG.equals(token.getId())) {
assertFalse(foundSession);
@@ -141,15 +139,15 @@
public void testGetMediaSession2Tokens_playerRemoved() throws InterruptedException {
// Release
sHandler.postAndSync(() -> {
- mSession.setPlayer(null);
+ mSession.close();
});
ensureChangeInSession();
// When the mSession's player becomes null, it should lose binder connection between server.
// So server will forget the session.
- List<SessionToken> tokens = mManager.getActiveSessionTokens();
+ List<SessionToken2> tokens = mManager.getActiveSessionTokens();
for (int i = 0; i < tokens.size(); i++) {
- SessionToken token = tokens.get(i);
+ SessionToken2 token = tokens.get(i);
assertFalse(mContext.getPackageName().equals(token.getPackageName())
&& TAG.equals(token.getId()));
}
@@ -158,27 +156,34 @@
@Test
public void testGetMediaSessionService2Token() throws InterruptedException {
boolean foundTestSessionService = false;
- List<SessionToken> tokens = mManager.getSessionServiceTokens();
+ boolean foundTestLibraryService = false;
+ List<SessionToken2> tokens = mManager.getSessionServiceTokens();
for (int i = 0; i < tokens.size(); i++) {
- SessionToken token = tokens.get(i);
+ SessionToken2 token = tokens.get(i);
if (mContext.getPackageName().equals(token.getPackageName())
&& MockMediaSessionService2.ID.equals(token.getId())) {
assertFalse(foundTestSessionService);
- assertEquals(SessionToken.TYPE_SESSION_SERVICE, token.getType());
- assertNull(token.getSessionBinder());
+ assertEquals(SessionToken2.TYPE_SESSION_SERVICE, token.getType());
foundTestSessionService = true;
+ } else if (mContext.getPackageName().equals(token.getPackageName())
+ && MockMediaLibraryService2.ID.equals(token.getId())) {
+ assertFalse(foundTestLibraryService);
+ assertEquals(SessionToken2.TYPE_LIBRARY_SERVICE, token.getType());
+ foundTestLibraryService = true;
}
}
assertTrue(foundTestSessionService);
+ assertTrue(foundTestLibraryService);
}
@Test
public void testGetAllSessionTokens() throws InterruptedException {
boolean foundTestSession = false;
boolean foundTestSessionService = false;
- List<SessionToken> tokens = mManager.getAllSessionTokens();
+ boolean foundTestLibraryService = false;
+ List<SessionToken2> tokens = mManager.getAllSessionTokens();
for (int i = 0; i < tokens.size(); i++) {
- SessionToken token = tokens.get(i);
+ SessionToken2 token = tokens.get(i);
if (!mContext.getPackageName().equals(token.getPackageName())) {
continue;
}
@@ -190,7 +195,12 @@
case MockMediaSessionService2.ID:
assertFalse(foundTestSessionService);
foundTestSessionService = true;
- assertEquals(SessionToken.TYPE_SESSION_SERVICE, token.getType());
+ assertEquals(SessionToken2.TYPE_SESSION_SERVICE, token.getType());
+ break;
+ case MockMediaLibraryService2.ID:
+ assertFalse(foundTestLibraryService);
+ assertEquals(SessionToken2.TYPE_LIBRARY_SERVICE, token.getType());
+ foundTestLibraryService = true;
break;
default:
fail("Unexpected session " + token + " exists in the package");
@@ -198,6 +208,7 @@
}
assertTrue(foundTestSession);
assertTrue(foundTestSessionService);
+ assertTrue(foundTestLibraryService);
}
// Ensures if the session creation/release is notified to the server.
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
new file mode 100644
index 0000000..6e1501a
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
@@ -0,0 +1,104 @@
+/*
+* Copyright 2018 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.
+*/
+
+package android.media;
+
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.media.MediaSession2.CommandGroup;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.TestUtils.SyncHandler;
+import android.os.Bundle;
+import android.os.Process;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Mock implementation of {@link MediaLibraryService2} for testing.
+ */
+public class MockMediaLibraryService2 extends MediaLibraryService2 {
+ // Keep in sync with the AndroidManifest.xml
+ public static final String ID = "TestLibrary";
+
+ public static final String ROOT_ID = "rootId";
+ public static final Bundle EXTRA = new Bundle();
+ static {
+ EXTRA.putString(ROOT_ID, ROOT_ID);
+ }
+ @GuardedBy("MockMediaLibraryService2.class")
+ private static SessionToken2 sToken;
+
+ private MediaLibrarySession mSession;
+
+ @Override
+ public MediaLibrarySession onCreateSession(String sessionId) {
+ final MockPlayer player = new MockPlayer(1);
+ final SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
+ try {
+ handler.postAndSync(() -> {
+ TestLibrarySessionCallback callback = new TestLibrarySessionCallback();
+ mSession = new MediaLibrarySessionBuilder(MockMediaLibraryService2.this,
+ player, (runnable) -> handler.post(runnable), callback)
+ .setId(sessionId).build();
+ });
+ } catch (InterruptedException e) {
+ fail(e.toString());
+ }
+ return mSession;
+ }
+
+ @Override
+ public void onDestroy() {
+ TestServiceRegistry.getInstance().cleanUp();
+ super.onDestroy();
+ }
+
+ public static SessionToken2 getToken(Context context) {
+ synchronized (MockMediaLibraryService2.class) {
+ if (sToken == null) {
+ sToken = new SessionToken2(context, context.getPackageName(),
+ MockMediaLibraryService2.class.getName());
+ assertEquals(SessionToken2.TYPE_LIBRARY_SERVICE, sToken.getType());
+ }
+ return sToken;
+ }
+ }
+
+ private class TestLibrarySessionCallback extends MediaLibrarySessionCallback {
+ public TestLibrarySessionCallback() {
+ super(MockMediaLibraryService2.this);
+ }
+
+ @Override
+ public CommandGroup onConnect(ControllerInfo controller) {
+ if (Process.myUid() != controller.getUid()) {
+ // It's system app wants to listen changes. Ignore.
+ return super.onConnect(controller);
+ }
+ TestServiceRegistry.getInstance().setServiceInstance(
+ MockMediaLibraryService2.this, controller);
+ return super.onConnect(controller);
+ }
+
+ @Override
+ public BrowserRoot onGetRoot(ControllerInfo controller, Bundle rootHints) {
+ return new BrowserRoot(ROOT_ID, EXTRA);
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
index e4a7485..d85875e 100644
--- a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
+++ b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
@@ -18,27 +18,42 @@
import static junit.framework.Assert.fail;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
import android.media.MediaSession2.ControllerInfo;
import android.media.MediaSession2.SessionCallback;
import android.media.TestUtils.SyncHandler;
+import android.media.session.PlaybackState;
import android.os.Process;
+import java.util.concurrent.Executor;
+
/**
* Mock implementation of {@link android.media.MediaSessionService2} for testing.
*/
public class MockMediaSessionService2 extends MediaSessionService2 {
// Keep in sync with the AndroidManifest.xml
public static final String ID = "TestSession";
- public MediaSession2 mSession;
+
+ private static final String DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID = "media_session_service";
+ private static final int DEFAULT_MEDIA_NOTIFICATION_ID = 1001;
+
+ private NotificationChannel mDefaultNotificationChannel;
+ private MediaSession2 mSession;
+ private NotificationManager mNotificationManager;
@Override
public MediaSession2 onCreateSession(String sessionId) {
final MockPlayer player = new MockPlayer(1);
- SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
+ final SyncHandler handler = (SyncHandler) TestServiceRegistry.getInstance().getHandler();
+ final Executor executor = (runnable) -> handler.post(runnable);
try {
handler.postAndSync(() -> {
mSession = new MediaSession2.Builder(MockMediaSessionService2.this, player)
- .setId(sessionId).setSessionCallback(new MySessionCallback()).build();
+ .setSessionCallback(executor, new MySessionCallback())
+ .setId(sessionId).build();
});
} catch (InterruptedException e) {
fail(e.toString());
@@ -49,6 +64,7 @@
@Override
public void onCreate() {
super.onCreate();
+ mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
}
@Override
@@ -57,7 +73,28 @@
super.onDestroy();
}
+ @Override
+ public MediaNotification onUpdateNotification(PlaybackState2 state) {
+ if (mDefaultNotificationChannel == null) {
+ mDefaultNotificationChannel = new NotificationChannel(
+ DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
+ DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT);
+ mNotificationManager.createNotificationChannel(mDefaultNotificationChannel);
+ }
+ Notification notification = new Notification.Builder(
+ this, DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(getPackageName())
+ .setContentText("Playback state: " + state.getState())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
+ return MediaNotification.create(DEFAULT_MEDIA_NOTIFICATION_ID, notification);
+ }
+
private class MySessionCallback extends SessionCallback {
+ public MySessionCallback() {
+ super(MockMediaSessionService2.this);
+ }
+
@Override
public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
if (Process.myUid() != controller.getUid()) {
diff --git a/packages/MediaComponents/test/src/android/media/MockPlayer.java b/packages/MediaComponents/test/src/android/media/MockPlayer.java
index b0d7a69..1faf0f4 100644
--- a/packages/MediaComponents/test/src/android/media/MockPlayer.java
+++ b/packages/MediaComponents/test/src/android/media/MockPlayer.java
@@ -16,19 +16,19 @@
package android.media;
-import android.media.session.PlaybackState;
-import android.os.Handler;
+import android.media.MediaSession2.PlaylistParams;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
/**
- * A mock implementation of {@link MediaPlayerBase} for testing.
+ * A mock implementation of {@link MediaPlayerInterface} for testing.
*/
-public class MockPlayer extends MediaPlayerBase {
+public class MockPlayer implements MediaPlayerInterface {
public final CountDownLatch mCountDownLatch;
public boolean mPlayCalled;
@@ -36,8 +36,22 @@
public boolean mStopCalled;
public boolean mSkipToPreviousCalled;
public boolean mSkipToNextCalled;
+ public boolean mPrepareCalled;
+ public boolean mFastForwardCalled;
+ public boolean mRewindCalled;
+ public boolean mSeekToCalled;
+ public long mSeekPosition;
+ public boolean mSetCurrentPlaylistItemCalled;
+ public int mItemIndex;
+ public boolean mSetPlaylistCalled;
+ public boolean mSetPlaylistParamsCalled;
+
public List<PlaybackListenerHolder> mListeners = new ArrayList<>();
- private PlaybackState mLastPlaybackState;
+ public List<MediaItem2> mPlaylist;
+ public PlaylistParams mPlaylistParams;
+
+ private PlaybackState2 mLastPlaybackState;
+ private AudioAttributes mAudioAttributes;
public MockPlayer(int count) {
mCountDownLatch = (count > 0) ? new CountDownLatch(count) : null;
@@ -83,16 +97,58 @@
}
}
+ @Override
+ public void prepare() {
+ mPrepareCalled = true;
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
+ }
+
+ @Override
+ public void fastForward() {
+ mFastForwardCalled = true;
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
+ }
+
+ @Override
+ public void rewind() {
+ mRewindCalled = true;
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
+ }
+
+ @Override
+ public void seekTo(long pos) {
+ mSeekToCalled = true;
+ mSeekPosition = pos;
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
+ }
+
+ @Override
+ public void setCurrentPlaylistItem(int index) {
+ mSetCurrentPlaylistItemCalled = true;
+ mItemIndex = index;
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
+ }
+
@Nullable
@Override
- public PlaybackState getPlaybackState() {
+ public PlaybackState2 getPlaybackState() {
return mLastPlaybackState;
}
@Override
- public void addPlaybackListener(
- @NonNull PlaybackListener listener, @NonNull Handler handler) {
- mListeners.add(new PlaybackListenerHolder(listener, handler));
+ public void addPlaybackListener(@NonNull Executor executor,
+ @NonNull PlaybackListener listener) {
+ mListeners.add(new PlaybackListenerHolder(executor, listener));
}
@Override
@@ -103,10 +159,50 @@
}
}
- public void notifyPlaybackState(final PlaybackState state) {
+ public void notifyPlaybackState(final PlaybackState2 state) {
mLastPlaybackState = state;
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).postPlaybackChange(state);
}
}
+
+ @Override
+ public void setPlaylistParams(PlaylistParams params) {
+ mSetPlaylistParamsCalled = true;
+ mPlaylistParams = params;
+ }
+
+ @Override
+ public void addPlaylistItem(int index, MediaItem2 item) {
+ }
+
+ @Override
+ public void removePlaylistItem(MediaItem2 item) {
+ }
+
+ @Override
+ public PlaylistParams getPlaylistParams() {
+ return mPlaylistParams;
+ }
+
+ @Override
+ public void setAudioAttributes(AudioAttributes attributes) {
+ mAudioAttributes = attributes;
+ }
+
+ @Override
+ public AudioAttributes getAudioAttributes() {
+ return mAudioAttributes;
+ }
+
+ @Override
+ public void setPlaylist(List<MediaItem2> playlist) {
+ mSetPlaylistCalled = true;
+ mPlaylist = playlist;
+ }
+
+ @Override
+ public List<MediaItem2> getPlaylist() {
+ return mPlaylist;
+ }
}
diff --git a/packages/MediaComponents/test/src/android/media/PlaybackListenerHolder.java b/packages/MediaComponents/test/src/android/media/PlaybackListenerHolder.java
index b0b87de..0f1644c 100644
--- a/packages/MediaComponents/test/src/android/media/PlaybackListenerHolder.java
+++ b/packages/MediaComponents/test/src/android/media/PlaybackListenerHolder.java
@@ -16,39 +16,27 @@
package android.media;
-import android.media.MediaPlayerBase.PlaybackListener;
-import android.media.session.PlaybackState;
+import android.media.MediaPlayerInterface.PlaybackListener;
import android.os.Handler;
-import android.os.Message;
import android.support.annotation.NonNull;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* Holds {@link PlaybackListener} with the {@link Handler}.
*/
-public class PlaybackListenerHolder extends Handler {
- private static final int ON_PLAYBACK_CHANGED = 1;
-
+public class PlaybackListenerHolder {
+ public final Executor executor;
public final PlaybackListener listener;
- public PlaybackListenerHolder(
- @NonNull PlaybackListener listener, @NonNull Handler handler) {
- super(handler.getLooper());
+ public PlaybackListenerHolder(Executor executor, @NonNull PlaybackListener listener) {
+ this.executor = executor;
this.listener = listener;
}
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case ON_PLAYBACK_CHANGED:
- listener.onPlaybackChanged((PlaybackState) msg.obj);
- break;
- }
- }
-
- public void postPlaybackChange(PlaybackState state) {
- obtainMessage(ON_PLAYBACK_CHANGED, state).sendToTarget();
+ public void postPlaybackChange(final PlaybackState2 state) {
+ executor.execute(() -> listener.onPlaybackChanged(state));
}
/**
diff --git a/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java b/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
index 378a6c4..6f5512e 100644
--- a/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
+++ b/packages/MediaComponents/test/src/android/media/TestServiceRegistry.java
@@ -106,17 +106,17 @@
if (mService != null) {
try {
if (mHandler.getLooper() == Looper.myLooper()) {
- mService.getSession().setPlayer(null);
+ mService.getSession().close();
} else {
mHandler.postAndSync(() -> {
- mService.getSession().setPlayer(null);
+ mService.getSession().close();
});
}
} catch (InterruptedException e) {
// No-op. Service containing session will die, but shouldn't be a huge issue.
}
// stopSelf() would not kill service while the binder connection established by
- // bindService() exists, and setPlayer(null) above will do the job instead.
+ // bindService() exists, and close() above will do the job instead.
// So stopSelf() isn't really needed, but just for sure.
mService.stopSelf();
mService = null;
diff --git a/packages/MediaComponents/test/src/android/media/TestUtils.java b/packages/MediaComponents/test/src/android/media/TestUtils.java
index 0cca12c..9a1fa10 100644
--- a/packages/MediaComponents/test/src/android/media/TestUtils.java
+++ b/packages/MediaComponents/test/src/android/media/TestUtils.java
@@ -19,11 +19,13 @@
import android.content.Context;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -43,16 +45,25 @@
* @param state one of the PlaybackState.STATE_xxx.
* @return a PlaybackState
*/
- public static PlaybackState createPlaybackState(int state) {
- return new PlaybackState.Builder().setState(state, 0, 1.0f).build();
+ public static PlaybackState2 createPlaybackState(int state) {
+ return new PlaybackState2(state, 0, 0, 1.0f,
+ 0, 0, null);
}
- public static SessionToken getServiceToken(Context context, String id) {
+ /**
+ * Finds the session with id in this test package.
+ *
+ * @param context
+ * @param id
+ * @return
+ */
+ // TODO(jaewan): Currently not working.
+ public static SessionToken2 getServiceToken(Context context, String id) {
MediaSessionManager manager =
(MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
- List<SessionToken> tokens = manager.getSessionServiceTokens();
+ List<SessionToken2> tokens = manager.getSessionServiceTokens();
for (int i = 0; i < tokens.size(); i++) {
- SessionToken token = tokens.get(i);
+ SessionToken2 token = tokens.get(i);
if (context.getPackageName().equals(token.getPackageName())
&& id.equals(token.getId())) {
return token;
@@ -63,6 +74,33 @@
}
/**
+ * Compares contents of two bundles.
+ *
+ * @param a a bundle
+ * @param b another bundle
+ * @return {@code true} if two bundles are the same. {@code false} otherwise. This may be
+ * incorrect if any bundle contains a bundle.
+ */
+ public static boolean equals(Bundle a, Bundle b) {
+ if (a == b) {
+ return true;
+ }
+ if (a == null || b == null) {
+ return false;
+ }
+ if (!a.keySet().containsAll(b.keySet())
+ || !b.keySet().containsAll(a.keySet())) {
+ return false;
+ }
+ for (String key : a.keySet()) {
+ if (!Objects.equals(a.get(key), b.get(key))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Handler that always waits until the Runnable finishes.
*/
public static class SyncHandler extends Handler {
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 686e3e3..592273e 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -155,6 +155,8 @@
mBtNrecIsOff(false),
mIsLowRamDevice(true),
mIsDeviceTypeKnown(false),
+ mTotalMemory(0),
+ mClientSharedHeapSize(kMinimumClientSharedHeapSizeBytes),
mGlobalEffectEnableTime(0),
mSystemReady(false)
{
@@ -1512,17 +1514,9 @@
mAudioFlinger(audioFlinger),
mPid(pid)
{
- size_t heapSize = property_get_int32("ro.af.client_heap_size_kbyte", 0);
- heapSize *= 1024;
- if (!heapSize) {
- heapSize = kClientSharedHeapSizeBytes;
- // Increase heap size on non low ram devices to limit risk of reconnection failure for
- // invalidated tracks
- if (!audioFlinger->isLowRamDevice()) {
- heapSize *= kClientSharedHeapSizeMultiplier;
- }
- }
- mMemoryDealer = new MemoryDealer(heapSize, "AudioFlinger::Client");
+ mMemoryDealer = new MemoryDealer(
+ audioFlinger->getClientSharedHeapSize(),
+ (std::string("AudioFlinger::Client(") + std::to_string(pid) + ")").c_str());
}
// Client destructor must be called with AudioFlinger::mClientLock held
@@ -1860,7 +1854,7 @@
// ----------------------------------------------------------------------------
-status_t AudioFlinger::setLowRamDevice(bool isLowRamDevice)
+status_t AudioFlinger::setLowRamDevice(bool isLowRamDevice, int64_t totalMemory)
{
uid_t uid = IPCThreadState::self()->getCallingUid();
if (uid != AID_SYSTEM) {
@@ -1871,10 +1865,43 @@
return INVALID_OPERATION;
}
mIsLowRamDevice = isLowRamDevice;
+ mTotalMemory = totalMemory;
+ // mIsLowRamDevice and mTotalMemory are obtained through ActivityManager;
+ // see ActivityManager.isLowRamDevice() and ActivityManager.getMemoryInfo().
+ // mIsLowRamDevice generally represent devices with less than 1GB of memory,
+ // though actual setting is determined through device configuration.
+ constexpr int64_t GB = 1024 * 1024 * 1024;
+ mClientSharedHeapSize =
+ isLowRamDevice ? kMinimumClientSharedHeapSizeBytes
+ : mTotalMemory < 2 * GB ? 4 * kMinimumClientSharedHeapSizeBytes
+ : mTotalMemory < 3 * GB ? 8 * kMinimumClientSharedHeapSizeBytes
+ : mTotalMemory < 4 * GB ? 16 * kMinimumClientSharedHeapSizeBytes
+ : 32 * kMinimumClientSharedHeapSizeBytes;
mIsDeviceTypeKnown = true;
+
+ // TODO: Cache the client shared heap size in a persistent property.
+ // It's possible that a native process or Java service or app accesses audioserver
+ // after it is registered by system server, but before AudioService updates
+ // the memory info. This would occur immediately after boot or an audioserver
+ // crash and restore. Before update from AudioService, the client would get the
+ // minimum heap size.
+
+ ALOGD("isLowRamDevice:%s totalMemory:%lld mClientSharedHeapSize:%zu",
+ (isLowRamDevice ? "true" : "false"),
+ (long long)mTotalMemory,
+ mClientSharedHeapSize.load());
return NO_ERROR;
}
+size_t AudioFlinger::getClientSharedHeapSize() const
+{
+ size_t heapSizeInBytes = property_get_int32("ro.af.client_heap_size_kbyte", 0) * 1024;
+ if (heapSizeInBytes != 0) { // read-only property overrides all.
+ return heapSizeInBytes;
+ }
+ return mClientSharedHeapSize;
+}
+
audio_hw_sync_t AudioFlinger::getAudioHwSyncForSession(audio_session_t sessionId)
{
Mutex::Autolock _l(mLock);
@@ -1949,6 +1976,43 @@
return NO_ERROR;
}
+status_t AudioFlinger::getMicrophones(std::vector<media::MicrophoneInfo> *microphones)
+{
+ // Fake data
+ size_t fakeNum = 2;
+ audio_devices_t fakeTypes[] = { AUDIO_DEVICE_IN_BUILTIN_MIC, AUDIO_DEVICE_IN_BACK_MIC };
+ for (size_t i = 0; i < fakeNum; i++) {
+ struct audio_microphone_characteristic_t characteristics;
+ sprintf(characteristics.device_id, "microphone:%zu", i);
+ characteristics.type = fakeTypes[i];
+ sprintf(characteristics.address, "");
+ characteristics.location = AUDIO_MICROPHONE_LOCATION_MAINBODY;
+ characteristics.group = 0;
+ characteristics.index_in_the_group = i;
+ characteristics.sensitivity = 1.0f;
+ characteristics.max_spl = 100.0f;
+ characteristics.min_spl = 0.0f;
+ characteristics.directionality = AUDIO_MICROPHONE_DIRECTIONALITY_OMNI;
+ characteristics.num_frequency_responses = 5 - i;
+ for (size_t j = 0; j < characteristics.num_frequency_responses; j++) {
+ characteristics.frequency_responses[0][j] = 100.0f - j;
+ characteristics.frequency_responses[1][j] = 100.0f + j;
+ }
+ for (size_t j = 0; j < AUDIO_CHANNEL_COUNT_MAX; j++) {
+ characteristics.channel_mapping[j] = AUDIO_MICROPHONE_CHANNEL_MAPPING_UNUSED;
+ }
+ characteristics.geometric_location.x = 0.1f;
+ characteristics.geometric_location.y = 0.2f;
+ characteristics.geometric_location.z = 0.3f;
+ characteristics.orientation.x = 0.0f;
+ characteristics.orientation.y = 1.0f;
+ characteristics.orientation.z = 0.0f;
+ media::MicrophoneInfo microphoneInfo = media::MicrophoneInfo(characteristics);
+ microphones->push_back(microphoneInfo);
+ }
+ return NO_ERROR;
+}
+
// setAudioHwSyncForSession_l() must be called with AudioFlinger::mLock held
void AudioFlinger::setAudioHwSyncForSession_l(PlaybackThread *thread, audio_session_t sessionId)
{
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index a1c3f36..83caca7 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -94,12 +94,6 @@
static const nsecs_t kDefaultStandbyTimeInNsecs = seconds(3);
-
-// Max shared memory size for audio tracks and audio records per client process
-static const size_t kClientSharedHeapSizeBytes = 1024*1024;
-// Shared memory size multiplier for non low ram devices
-static const size_t kClientSharedHeapSizeMultiplier = 4;
-
#define INCLUDING_FROM_AUDIOFLINGER_H
class AudioFlinger :
@@ -227,7 +221,7 @@
virtual uint32_t getPrimaryOutputSamplingRate();
virtual size_t getPrimaryOutputFrameCount();
- virtual status_t setLowRamDevice(bool isLowRamDevice);
+ virtual status_t setLowRamDevice(bool isLowRamDevice, int64_t totalMemory) override;
/* List available audio ports and their attributes */
virtual status_t listAudioPorts(unsigned int *num_ports,
@@ -256,6 +250,8 @@
/* Indicate JAVA services are ready (scheduling, power management ...) */
virtual status_t systemReady();
+ virtual status_t getMicrophones(std::vector<media::MicrophoneInfo> *microphones);
+
virtual status_t onTransact(
uint32_t code,
const Parcel& data,
@@ -829,15 +825,18 @@
static const size_t kTeeSinkTrackFramesDefault = 0x200000;
#endif
- // This method reads from a variable without mLock, but the variable is updated under mLock. So
- // we might read a stale value, or a value that's inconsistent with respect to other variables.
- // In this case, it's safe because the return value isn't used for making an important decision.
- // The reason we don't want to take mLock is because it could block the caller for a long time.
+ // These methods read variables atomically without mLock,
+ // though the variables are updated with mLock.
bool isLowRamDevice() const { return mIsLowRamDevice; }
+ size_t getClientSharedHeapSize() const;
private:
- bool mIsLowRamDevice;
+ std::atomic<bool> mIsLowRamDevice;
bool mIsDeviceTypeKnown;
+ int64_t mTotalMemory;
+ std::atomic<size_t> mClientSharedHeapSize;
+ static constexpr size_t kMinimumClientSharedHeapSizeBytes = 1024 * 1024; // 1MB
+
nsecs_t mGlobalEffectEnableTime; // when a global effect was last enabled
sp<PatchPanel> mPatchPanel;
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 70ac32c..420e6e1 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -4874,7 +4874,7 @@
{
PlaybackThread::dumpInternals(fd, args);
dprintf(fd, " Thread throttle time (msecs): %u\n", mThreadThrottleTimeMs);
- dprintf(fd, " AudioMixer tracks: 0x%08x\n", mAudioMixer->trackNames());
+ dprintf(fd, " AudioMixer tracks: %s\n", mAudioMixer->trackNames().c_str());
dprintf(fd, " Master mono: %s\n", mMasterMono ? "on" : "off");
if (hasFastMixer()) {
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 41d87a4..eb29497 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -623,8 +623,7 @@
static const int8_t kMaxTrackRetriesOffload = 20;
static const int8_t kMaxTrackStartupRetriesOffload = 100;
static const int8_t kMaxTrackStopRetriesOffload = 2;
- // 14 tracks max per client allows for 2 misbehaving application leaving 4 available tracks.
- static const uint32_t kMaxTracksPerUid = 14;
+ static constexpr uint32_t kMaxTracksPerUid = 40;
// Maximum delay (in nanoseconds) for upcoming buffers in suspend mode, otherwise
// if delay is greater, the estimated time for timeLoopNextNs is reset.
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
index 2803ec1..cd2174d 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
@@ -187,6 +187,15 @@
bool isStreamActiveRemotely(audio_stream_type_t stream, uint32_t inPastMs = 0) const;
/**
+ * return whether a stream is playing, but not on a "remote" device.
+ * Override to change the definition of a local/remote playback.
+ * Used for instance by policy manager to alter the speaker playback ("speaker safe" behavior)
+ * when media plays or not locally.
+ * For the base implementation, "remotely" means playing during screen mirroring.
+ */
+ bool isStreamActiveLocally(audio_stream_type_t stream, uint32_t inPastMs = 0) const;
+
+ /**
* returns the A2DP output handle if it is open or 0 otherwise
*/
audio_io_handle_t getA2dpOutput() const;
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
index d5e8e1b..17fc272 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
@@ -579,6 +579,19 @@
return false;
}
+bool SwAudioOutputCollection::isStreamActiveLocally(audio_stream_type_t stream, uint32_t inPastMs) const
+{
+ nsecs_t sysTime = systemTime();
+ for (size_t i = 0; i < this->size(); i++) {
+ const sp<SwAudioOutputDescriptor> outputDesc = this->valueAt(i);
+ if (outputDesc->isStreamActive(stream, inPastMs, sysTime)
+ && ((outputDesc->device() & APM_AUDIO_OUT_DEVICE_REMOTE_ALL) == 0)) {
+ return true;
+ }
+ }
+ return false;
+}
+
bool SwAudioOutputCollection::isStreamActiveRemotely(audio_stream_type_t stream,
uint32_t inPastMs) const
{
diff --git a/services/audiopolicy/enginedefault/src/Engine.cpp b/services/audiopolicy/enginedefault/src/Engine.cpp
index 9bdb98c..5ec0475 100644
--- a/services/audiopolicy/enginedefault/src/Engine.cpp
+++ b/services/audiopolicy/enginedefault/src/Engine.cpp
@@ -238,18 +238,19 @@
const SwAudioOutputCollection &outputs = mApmObserver->getOutputs();
return getDeviceForStrategyInt(strategy, availableOutputDevices,
- availableInputDevices, outputs);
+ availableInputDevices, outputs, (uint32_t)AUDIO_DEVICE_NONE);
}
-
audio_devices_t Engine::getDeviceForStrategyInt(routing_strategy strategy,
- DeviceVector availableOutputDevices,
- DeviceVector availableInputDevices,
- const SwAudioOutputCollection &outputs) const
+ DeviceVector availableOutputDevices,
+ DeviceVector availableInputDevices,
+ const SwAudioOutputCollection &outputs,
+ uint32_t outputDeviceTypesToIgnore) const
{
uint32_t device = AUDIO_DEVICE_NONE;
- uint32_t availableOutputDevicesType = availableOutputDevices.types();
+ uint32_t availableOutputDevicesType =
+ availableOutputDevices.types() & ~outputDeviceTypesToIgnore;
switch (strategy) {
@@ -260,38 +261,24 @@
case STRATEGY_SONIFICATION_RESPECTFUL:
if (isInCall()) {
device = getDeviceForStrategyInt(
- STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
- } else if (outputs.isStreamActiveRemotely(AUDIO_STREAM_MUSIC,
- SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY)) {
- // while media is playing on a remote device, use the the sonification behavior.
- // Note that we test this usecase before testing if media is playing because
- // the isStreamActive() method only informs about the activity of a stream, not
- // if it's for local playback. Note also that we use the same delay between both tests
- device = getDeviceForStrategyInt(
- STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
- //user "safe" speaker if available instead of normal speaker to avoid triggering
- //other acoustic safety mechanisms for notification
- if ((device & AUDIO_DEVICE_OUT_SPEAKER) &&
- (availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER_SAFE)) {
- device |= AUDIO_DEVICE_OUT_SPEAKER_SAFE;
- device &= ~AUDIO_DEVICE_OUT_SPEAKER;
- }
- } else if (outputs.isStreamActive(
- AUDIO_STREAM_MUSIC, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY)
- || outputs.isStreamActive(
- AUDIO_STREAM_ACCESSIBILITY, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY))
- {
- // while media/a11y is playing (or has recently played), use the same device
- device = getDeviceForStrategyInt(
- STRATEGY_MEDIA, availableOutputDevices, availableInputDevices, outputs);
+ STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs,
+ outputDeviceTypesToIgnore);
} else {
- // when media is not playing anymore, fall back on the sonification behavior
- device = getDeviceForStrategyInt(
- STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
- //user "safe" speaker if available instead of normal speaker to avoid triggering
- //other acoustic safety mechanisms for notification
- if ((device & AUDIO_DEVICE_OUT_SPEAKER) &&
- (availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER_SAFE)) {
+ bool media_active_locally =
+ outputs.isStreamActiveLocally(
+ AUDIO_STREAM_MUSIC, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY)
+ || outputs.isStreamActiveLocally(
+ AUDIO_STREAM_ACCESSIBILITY, SONIFICATION_RESPECTFUL_AFTER_MUSIC_DELAY);
+ // routing is same as media without the "remote" device
+ device = getDeviceForStrategyInt(STRATEGY_MEDIA,
+ availableOutputDevices,
+ availableInputDevices, outputs,
+ AUDIO_DEVICE_OUT_REMOTE_SUBMIX | outputDeviceTypesToIgnore);
+ // if no media is playing on the device, check for mandatory use of "safe" speaker
+ // when media would have played on speaker, and the safe speaker path is available
+ if (!media_active_locally
+ && (device & AUDIO_DEVICE_OUT_SPEAKER)
+ && (availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER_SAFE)) {
device |= AUDIO_DEVICE_OUT_SPEAKER_SAFE;
device &= ~AUDIO_DEVICE_OUT_SPEAKER;
}
@@ -302,7 +289,8 @@
if (!isInCall()) {
// when off call, DTMF strategy follows the same rules as MEDIA strategy
device = getDeviceForStrategyInt(
- STRATEGY_MEDIA, availableOutputDevices, availableInputDevices, outputs);
+ STRATEGY_MEDIA, availableOutputDevices, availableInputDevices, outputs,
+ outputDeviceTypesToIgnore);
break;
}
// when in call, DTMF and PHONE strategies follow the same rules
@@ -408,7 +396,8 @@
// handleIncallSonification().
if (isInCall()) {
device = getDeviceForStrategyInt(
- STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs);
+ STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs,
+ outputDeviceTypesToIgnore);
break;
}
// FALL THROUGH
@@ -463,11 +452,13 @@
if (outputs.isStreamActive(AUDIO_STREAM_RING) ||
outputs.isStreamActive(AUDIO_STREAM_ALARM)) {
return getDeviceForStrategyInt(
- STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs);
+ STRATEGY_SONIFICATION, availableOutputDevices, availableInputDevices, outputs,
+ outputDeviceTypesToIgnore);
}
if (isInCall()) {
return getDeviceForStrategyInt(
- STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs);
+ STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs,
+ outputDeviceTypesToIgnore);
}
}
// For other cases, STRATEGY_ACCESSIBILITY behaves like STRATEGY_MEDIA
@@ -486,7 +477,8 @@
}
if (isInCall() && (strategy == STRATEGY_MEDIA)) {
device = getDeviceForStrategyInt(
- STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs);
+ STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs,
+ outputDeviceTypesToIgnore);
break;
}
if ((device2 == AUDIO_DEVICE_NONE) &&
diff --git a/services/audiopolicy/enginedefault/src/Engine.h b/services/audiopolicy/enginedefault/src/Engine.h
index 57538c4..06186c1 100644
--- a/services/audiopolicy/enginedefault/src/Engine.h
+++ b/services/audiopolicy/enginedefault/src/Engine.h
@@ -126,9 +126,10 @@
routing_strategy getStrategyForUsage(audio_usage_t usage);
audio_devices_t getDeviceForStrategy(routing_strategy strategy) const;
audio_devices_t getDeviceForStrategyInt(routing_strategy strategy,
- DeviceVector availableOutputDevices,
- DeviceVector availableInputDevices,
- const SwAudioOutputCollection &outputs) const;
+ DeviceVector availableOutputDevices,
+ DeviceVector availableInputDevices,
+ const SwAudioOutputCollection &outputs,
+ uint32_t outputDeviceTypesToIgnore) const;
audio_devices_t getDeviceForInputSource(audio_source_t inputSource) const;
audio_mode_t mPhoneState; /**< current phone state. */
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index e058dc8..7343601 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -2939,7 +2939,7 @@
sinkDeviceDesc->toAudioPortConfig(&newPatch.sinks[i], &patch->sinks[i]);
// create a software bridge in PatchPanel if:
- // - source and sink devices are on differnt HW modules OR
+ // - source and sink devices are on different HW modules OR
// - audio HAL version is < 3.0
if (!srcDeviceDesc->hasSameHwModuleAs(sinkDeviceDesc) ||
(srcDeviceDesc->mModule->getHalVersionMajor() < 3)) {
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index 536b54d..e7609ed 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -2054,7 +2054,8 @@
mp->prepare();
} else {
ALOGE("Failed to load CameraService sounds: %s", file);
- return NULL;
+ delete mp;
+ return nullptr;
}
return mp;
}
@@ -2066,9 +2067,19 @@
LOG1("CameraService::loadSound ref=%d", mSoundRef);
if (mSoundRef++) return;
- mSoundPlayer[SOUND_SHUTTER] = newMediaPlayer("/system/media/audio/ui/camera_click.ogg");
- mSoundPlayer[SOUND_RECORDING_START] = newMediaPlayer("/system/media/audio/ui/VideoRecord.ogg");
- mSoundPlayer[SOUND_RECORDING_STOP] = newMediaPlayer("/system/media/audio/ui/VideoStop.ogg");
+ mSoundPlayer[SOUND_SHUTTER] = newMediaPlayer("/product/media/audio/ui/camera_click.ogg");
+ if (mSoundPlayer[SOUND_SHUTTER] == nullptr) {
+ mSoundPlayer[SOUND_SHUTTER] = newMediaPlayer("/system/media/audio/ui/camera_click.ogg");
+ }
+ mSoundPlayer[SOUND_RECORDING_START] = newMediaPlayer("/product/media/audio/ui/VideoRecord.ogg");
+ if (mSoundPlayer[SOUND_RECORDING_START] == nullptr) {
+ mSoundPlayer[SOUND_RECORDING_START] =
+ newMediaPlayer("/system/media/audio/ui/VideoRecord.ogg");
+ }
+ mSoundPlayer[SOUND_RECORDING_STOP] = newMediaPlayer("/product/media/audio/ui/VideoStop.ogg");
+ if (mSoundPlayer[SOUND_RECORDING_STOP] == nullptr) {
+ mSoundPlayer[SOUND_RECORDING_STOP] = newMediaPlayer("/system/media/audio/ui/VideoStop.ogg");
+ }
}
void CameraService::releaseSound() {
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
index c36c7cf..1ebaea9 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
@@ -1697,7 +1697,8 @@
// Thread-safe. No lock necessary.
sp<hardware::camera2::ICameraDeviceCallbacks> remoteCb = mRemoteCallback;
if (remoteCb != NULL) {
- remoteCb->onResultReceived(result.mMetadata, result.mResultExtras);
+ remoteCb->onResultReceived(result.mMetadata, result.mResultExtras,
+ result.mPhysicalMetadatas);
}
}
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index 2d77b2e..f149f8a 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -39,6 +39,8 @@
#include <inttypes.h>
+#include <utility>
+
#include <utils/Log.h>
#include <utils/Trace.h>
#include <utils/Timers.h>
@@ -55,8 +57,6 @@
#include "device3/Camera3SharedOutputStream.h"
#include "CameraService.h"
-#include <android/hardware/camera/device/3.4/ICameraDeviceSession.h>
-
using namespace android::camera3;
using namespace android::hardware::camera;
using namespace android::hardware::camera::device::V3_2;
@@ -669,13 +669,15 @@
}
if (dumpTemplates) {
- const char *templateNames[] = {
+ const char *templateNames[CAMERA3_TEMPLATE_COUNT] = {
"TEMPLATE_PREVIEW",
"TEMPLATE_STILL_CAPTURE",
"TEMPLATE_VIDEO_RECORD",
"TEMPLATE_VIDEO_SNAPSHOT",
"TEMPLATE_ZERO_SHUTTER_LAG",
- "TEMPLATE_MANUAL"
+ "TEMPLATE_MANUAL",
+ "TEMPLATE_MOTION_TRACKING_PREVIEW",
+ "TEMPALTE_MOTION_TRACKING_BEST"
};
for (int i = 1; i < CAMERA3_TEMPLATE_COUNT; i++) {
@@ -874,11 +876,9 @@
return res;
}
-// Only one processCaptureResult should be called at a time, so
-// the locks won't block. The locks are present here simply to enforce this.
-hardware::Return<void> Camera3Device::processCaptureResult(
+hardware::Return<void> Camera3Device::processCaptureResult_3_4(
const hardware::hidl_vec<
- hardware::camera::device::V3_2::CaptureResult>& results) {
+ hardware::camera::device::V3_4::CaptureResult>& results) {
// Ideally we should grab mLock, but that can lead to deadlock, and
// it's not super important to get up to date value of mStatus for this
// warning print, hence skipping the lock here
@@ -902,45 +902,121 @@
}
}
for (const auto& result : results) {
- processOneCaptureResultLocked(result);
+ processOneCaptureResultLocked(result.v3_2, result.physicalCameraMetadata);
}
mProcessCaptureResultLock.unlock();
return hardware::Void();
}
+// Only one processCaptureResult should be called at a time, so
+// the locks won't block. The locks are present here simply to enforce this.
+hardware::Return<void> Camera3Device::processCaptureResult(
+ const hardware::hidl_vec<
+ hardware::camera::device::V3_2::CaptureResult>& results) {
+ hardware::hidl_vec<hardware::camera::device::V3_4::PhysicalCameraMetadata> noPhysMetadata;
+
+ // Ideally we should grab mLock, but that can lead to deadlock, and
+ // it's not super important to get up to date value of mStatus for this
+ // warning print, hence skipping the lock here
+ if (mStatus == STATUS_ERROR) {
+ // Per API contract, HAL should act as closed after device error
+ // But mStatus can be set to error by framework as well, so just log
+ // a warning here.
+ ALOGW("%s: received capture result in error state.", __FUNCTION__);
+ }
+
+ if (mProcessCaptureResultLock.tryLock() != OK) {
+ // This should never happen; it indicates a wrong client implementation
+ // that doesn't follow the contract. But, we can be tolerant here.
+ ALOGE("%s: callback overlapped! waiting 1s...",
+ __FUNCTION__);
+ if (mProcessCaptureResultLock.timedLock(1000000000 /* 1s */) != OK) {
+ ALOGE("%s: cannot acquire lock in 1s, dropping results",
+ __FUNCTION__);
+ // really don't know what to do, so bail out.
+ return hardware::Void();
+ }
+ }
+ for (const auto& result : results) {
+ processOneCaptureResultLocked(result, noPhysMetadata);
+ }
+ mProcessCaptureResultLock.unlock();
+ return hardware::Void();
+}
+
+status_t Camera3Device::readOneCameraMetadataLocked(
+ uint64_t fmqResultSize, hardware::camera::device::V3_2::CameraMetadata& resultMetadata,
+ const hardware::camera::device::V3_2::CameraMetadata& result) {
+ if (fmqResultSize > 0) {
+ resultMetadata.resize(fmqResultSize);
+ if (mResultMetadataQueue == nullptr) {
+ return NO_MEMORY; // logged in initialize()
+ }
+ if (!mResultMetadataQueue->read(resultMetadata.data(), fmqResultSize)) {
+ ALOGE("%s: Cannot read camera metadata from fmq, size = %" PRIu64,
+ __FUNCTION__, fmqResultSize);
+ return INVALID_OPERATION;
+ }
+ } else {
+ resultMetadata.setToExternal(const_cast<uint8_t *>(result.data()),
+ result.size());
+ }
+
+ if (resultMetadata.size() != 0) {
+ status_t res;
+ const camera_metadata_t* metadata =
+ reinterpret_cast<const camera_metadata_t*>(resultMetadata.data());
+ size_t expected_metadata_size = resultMetadata.size();
+ if ((res = validate_camera_metadata_structure(metadata, &expected_metadata_size)) != OK) {
+ ALOGE("%s: Invalid camera metadata received by camera service from HAL: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ return INVALID_OPERATION;
+ }
+ }
+
+ return OK;
+}
+
void Camera3Device::processOneCaptureResultLocked(
- const hardware::camera::device::V3_2::CaptureResult& result) {
+ const hardware::camera::device::V3_2::CaptureResult& result,
+ const hardware::hidl_vec<
+ hardware::camera::device::V3_4::PhysicalCameraMetadata> physicalCameraMetadatas) {
camera3_capture_result r;
status_t res;
r.frame_number = result.frameNumber;
+ // Read and validate the result metadata.
hardware::camera::device::V3_2::CameraMetadata resultMetadata;
- if (result.fmqResultSize > 0) {
- resultMetadata.resize(result.fmqResultSize);
- if (mResultMetadataQueue == nullptr) {
- return; // logged in initialize()
- }
- if (!mResultMetadataQueue->read(resultMetadata.data(), result.fmqResultSize)) {
- ALOGE("%s: Frame %d: Cannot read camera metadata from fmq, size = %" PRIu64,
- __FUNCTION__, result.frameNumber, result.fmqResultSize);
- return;
- }
- } else {
- resultMetadata.setToExternal(const_cast<uint8_t *>(result.result.data()),
- result.result.size());
+ res = readOneCameraMetadataLocked(result.fmqResultSize, resultMetadata, result.result);
+ if (res != OK) {
+ ALOGE("%s: Frame %d: Failed to read capture result metadata",
+ __FUNCTION__, result.frameNumber);
+ return;
}
+ r.result = reinterpret_cast<const camera_metadata_t*>(resultMetadata.data());
- if (resultMetadata.size() != 0) {
- r.result = reinterpret_cast<const camera_metadata_t*>(resultMetadata.data());
- size_t expected_metadata_size = resultMetadata.size();
- if ((res = validate_camera_metadata_structure(r.result, &expected_metadata_size)) != OK) {
- ALOGE("%s: Frame %d: Invalid camera metadata received by camera service from HAL: %s (%d)",
- __FUNCTION__, result.frameNumber, strerror(-res), res);
+ // Read and validate physical camera metadata
+ size_t physResultCount = physicalCameraMetadatas.size();
+ std::vector<const char*> physCamIds(physResultCount);
+ std::vector<const camera_metadata_t *> phyCamMetadatas(physResultCount);
+ std::vector<hardware::camera::device::V3_2::CameraMetadata> physResultMetadata;
+ physResultMetadata.resize(physResultCount);
+ for (size_t i = 0; i < physicalCameraMetadatas.size(); i++) {
+ res = readOneCameraMetadataLocked(physicalCameraMetadatas[i].fmqMetadataSize,
+ physResultMetadata[i], physicalCameraMetadatas[i].metadata);
+ if (res != OK) {
+ ALOGE("%s: Frame %d: Failed to read capture result metadata for camera %s",
+ __FUNCTION__, result.frameNumber,
+ physicalCameraMetadatas[i].physicalCameraId.c_str());
return;
}
- } else {
- r.result = nullptr;
+ physCamIds[i] = physicalCameraMetadatas[i].physicalCameraId.c_str();
+ phyCamMetadatas[i] = reinterpret_cast<const camera_metadata_t*>(
+ physResultMetadata[i].data());
}
+ r.num_physcam_metadata = physResultCount;
+ r.physcam_ids = physCamIds.data();
+ r.physcam_metadata = phyCamMetadatas.data();
std::vector<camera3_stream_buffer_t> outputBuffers(result.outputBuffers.size());
std::vector<buffer_handle_t> outputBufferHandles(result.outputBuffers.size());
@@ -1668,10 +1744,16 @@
mStatusChanged.broadcast();
}
+void Camera3Device::pauseStateNotify(bool enable) {
+ Mutex::Autolock il(mInterfaceLock);
+ Mutex::Autolock l(mLock);
+
+ mPauseStateNotify = enable;
+}
+
// Pause to reconfigure
status_t Camera3Device::internalPauseAndWaitLocked(nsecs_t maxExpectedDuration) {
mRequestThread->setPaused(true);
- mPauseStateNotify = true;
ALOGV("%s: Camera %s: Internal wait until idle (% " PRIi64 " ns)", __FUNCTION__, mId.string(),
maxExpectedDuration);
@@ -1690,6 +1772,8 @@
mRequestThread->setPaused(false);
+ ALOGV("%s: Camera %s: Internal wait until active (% " PRIi64 " ns)", __FUNCTION__, mId.string(),
+ kActiveTimeout);
res = waitUntilStateThenRelock(/*active*/ true, kActiveTimeout);
if (res != OK) {
SET_ERR_L("Can't transition to active in %f seconds!",
@@ -1798,6 +1882,7 @@
CaptureResult &result = *(mResultQueue.begin());
frame->mResultExtras = result.mResultExtras;
frame->mMetadata.acquire(result.mMetadata);
+ frame->mPhysicalMetadatas = std::move(result.mPhysicalMetadatas);
mResultQueue.erase(mResultQueue.begin());
return OK;
@@ -1970,8 +2055,8 @@
if (mStatus != STATUS_ACTIVE && mStatus != STATUS_CONFIGURED) {
return;
}
- ALOGV("%s: Camera %s: Now %s", __FUNCTION__, mId.string(),
- idle ? "idle" : "active");
+ ALOGV("%s: Camera %s: Now %s, pauseState: %s", __FUNCTION__, mId.string(),
+ idle ? "idle" : "active", mPauseStateNotify ? "true" : "false");
internalUpdateStatusLocked(idle ? STATUS_CONFIGURED : STATUS_ACTIVE);
// Skip notifying listener if we're doing some user-transparent
@@ -2579,13 +2664,14 @@
status_t Camera3Device::registerInFlight(uint32_t frameNumber,
int32_t numBuffers, CaptureResultExtras resultExtras, bool hasInput,
- bool hasAppCallback, nsecs_t maxExpectedDuration) {
+ bool hasAppCallback, nsecs_t maxExpectedDuration,
+ std::set<String8>& physicalCameraIds) {
ATRACE_CALL();
Mutex::Autolock l(mInFlightLock);
ssize_t res;
res = mInFlightMap.add(frameNumber, InFlightRequest(numBuffers, resultExtras, hasInput,
- hasAppCallback, maxExpectedDuration));
+ hasAppCallback, maxExpectedDuration, physicalCameraIds));
if (res < 0) return res;
if (mInFlightMap.size() == 1) {
@@ -2825,7 +2911,8 @@
CaptureResultExtras &resultExtras,
CameraMetadata &collectedPartialResult,
uint32_t frameNumber,
- bool reprocess) {
+ bool reprocess,
+ const std::vector<PhysicalCaptureResultInfo>& physicalMetadatas) {
ATRACE_CALL();
if (pendingMetadata.isEmpty())
return;
@@ -2854,6 +2941,7 @@
CaptureResult captureResult;
captureResult.mResultExtras = resultExtras;
captureResult.mMetadata = pendingMetadata;
+ captureResult.mPhysicalMetadatas = physicalMetadatas;
// Append any previous partials to form a complete result
if (mUsePartialResult && !collectedPartialResult.isEmpty()) {
@@ -2869,6 +2957,15 @@
frameNumber);
return;
}
+ for (auto& physicalMetadata : captureResult.mPhysicalMetadatas) {
+ camera_metadata_entry timestamp =
+ physicalMetadata.mPhysicalCameraMetadata.find(ANDROID_SENSOR_TIMESTAMP);
+ if (timestamp.count == 0) {
+ SET_ERR("No timestamp provided by HAL for physical camera %s frame %d!",
+ String8(physicalMetadata.mPhysicalCameraId).c_str(), frameNumber);
+ return;
+ }
+ }
mTagMonitor.monitorMetadata(TagMonitor::RESULT,
frameNumber, timestamp.data.i64[0], captureResult.mMetadata);
@@ -2904,7 +3001,6 @@
bool isPartialResult = false;
CameraMetadata collectedPartialResult;
- CaptureResultExtras resultExtras;
bool hasInputBufferInRequest = false;
// Get shutter timestamp and resultExtras from list of in-flight requests,
@@ -2945,6 +3041,11 @@
return;
}
isPartialResult = (result->partial_result < mNumPartialResults);
+ if (isPartialResult && result->num_physcam_metadata) {
+ SET_ERR("Result is malformed for frame %d: partial_result not allowed for"
+ " physical camera result", frameNumber);
+ return;
+ }
if (isPartialResult) {
request.collectedPartialResult.append(result->result);
}
@@ -2961,11 +3062,28 @@
// Did we get the (final) result metadata for this capture?
if (result->result != NULL && !isPartialResult) {
+ if (request.physicalCameraIds.size() != result->num_physcam_metadata) {
+ SET_ERR("Requested physical Camera Ids %d not equal to number of metadata %d",
+ request.physicalCameraIds.size(), result->num_physcam_metadata);
+ return;
+ }
if (request.haveResultMetadata) {
SET_ERR("Called multiple times with metadata for frame %d",
frameNumber);
return;
}
+ for (uint32_t i = 0; i < result->num_physcam_metadata; i++) {
+ String8 physicalId(result->physcam_ids[i]);
+ std::set<String8>::iterator cameraIdIter =
+ request.physicalCameraIds.find(physicalId);
+ if (cameraIdIter != request.physicalCameraIds.end()) {
+ request.physicalCameraIds.erase(cameraIdIter);
+ } else {
+ SET_ERR("Total result for frame %d has already returned for camera %s",
+ frameNumber, physicalId.c_str());
+ return;
+ }
+ }
if (mUsePartialResult &&
!request.collectedPartialResult.isEmpty()) {
collectedPartialResult.acquire(
@@ -3010,15 +3128,21 @@
}
if (result->result != NULL && !isPartialResult) {
+ for (uint32_t i = 0; i < result->num_physcam_metadata; i++) {
+ CameraMetadata physicalMetadata;
+ physicalMetadata.append(result->physcam_metadata[i]);
+ request.physicalMetadatas.push_back({String16(result->physcam_ids[i]),
+ physicalMetadata});
+ }
if (shutterTimestamp == 0) {
request.pendingMetadata = result->result;
request.collectedPartialResult = collectedPartialResult;
- } else if (request.hasCallback) {
+ } else if (request.hasCallback) {
CameraMetadata metadata;
metadata = result->result;
sendCaptureResult(metadata, request.resultExtras,
collectedPartialResult, frameNumber,
- hasInputBufferInRequest);
+ hasInputBufferInRequest, request.physicalMetadatas);
}
}
@@ -3204,7 +3328,7 @@
// send pending result and buffers
sendCaptureResult(r.pendingMetadata, r.resultExtras,
r.collectedPartialResult, msg.frame_number,
- r.hasInputBuffer);
+ r.hasInputBuffer, r.physicalMetadatas);
}
returnOutputBuffers(r.pendingOutputBuffers.array(),
r.pendingOutputBuffers.size(), r.shutterTimestamp);
@@ -3219,7 +3343,6 @@
}
}
-
CameraMetadata Camera3Device::getLatestRequestLocked() {
ALOGV("%s", __FUNCTION__);
@@ -3246,7 +3369,18 @@
sp<ICameraDeviceSession> &session,
std::shared_ptr<RequestMetadataQueue> queue) :
mHidlSession(session),
- mRequestMetadataQueue(queue) {}
+ mRequestMetadataQueue(queue) {
+ // Check with hardware service manager if we can downcast these interfaces
+ // Somewhat expensive, so cache the results at startup
+ auto castResult_3_4 = device::V3_4::ICameraDeviceSession::castFrom(mHidlSession);
+ if (castResult_3_4.isOk()) {
+ mHidlSession_3_4 = castResult_3_4;
+ }
+ auto castResult_3_3 = device::V3_3::ICameraDeviceSession::castFrom(mHidlSession);
+ if (castResult_3_3.isOk()) {
+ mHidlSession_3_3 = castResult_3_3;
+ }
+}
Camera3Device::HalInterface::HalInterface() {}
@@ -3274,52 +3408,89 @@
status_t res = OK;
common::V1_0::Status status;
- RequestTemplate id;
- switch (templateId) {
- case CAMERA3_TEMPLATE_PREVIEW:
- id = RequestTemplate::PREVIEW;
- break;
- case CAMERA3_TEMPLATE_STILL_CAPTURE:
- id = RequestTemplate::STILL_CAPTURE;
- break;
- case CAMERA3_TEMPLATE_VIDEO_RECORD:
- id = RequestTemplate::VIDEO_RECORD;
- break;
- case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
- id = RequestTemplate::VIDEO_SNAPSHOT;
- break;
- case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
- id = RequestTemplate::ZERO_SHUTTER_LAG;
- break;
- case CAMERA3_TEMPLATE_MANUAL:
- id = RequestTemplate::MANUAL;
- break;
- default:
- // Unknown template ID
- return BAD_VALUE;
- }
- auto err = mHidlSession->constructDefaultRequestSettings(id,
- [&status, &requestTemplate]
+
+ auto requestCallback = [&status, &requestTemplate]
(common::V1_0::Status s, const device::V3_2::CameraMetadata& request) {
- status = s;
- if (status == common::V1_0::Status::OK) {
- const camera_metadata *r =
- reinterpret_cast<const camera_metadata_t*>(request.data());
- size_t expectedSize = request.size();
- int ret = validate_camera_metadata_structure(r, &expectedSize);
- if (ret == OK || ret == CAMERA_METADATA_VALIDATION_SHIFTED) {
- *requestTemplate = clone_camera_metadata(r);
- if (*requestTemplate == nullptr) {
- ALOGE("%s: Unable to clone camera metadata received from HAL",
- __FUNCTION__);
- status = common::V1_0::Status::INTERNAL_ERROR;
- }
- } else {
- ALOGE("%s: Malformed camera metadata received from HAL", __FUNCTION__);
+ status = s;
+ if (status == common::V1_0::Status::OK) {
+ const camera_metadata *r =
+ reinterpret_cast<const camera_metadata_t*>(request.data());
+ size_t expectedSize = request.size();
+ int ret = validate_camera_metadata_structure(r, &expectedSize);
+ if (ret == OK || ret == CAMERA_METADATA_VALIDATION_SHIFTED) {
+ *requestTemplate = clone_camera_metadata(r);
+ if (*requestTemplate == nullptr) {
+ ALOGE("%s: Unable to clone camera metadata received from HAL",
+ __FUNCTION__);
status = common::V1_0::Status::INTERNAL_ERROR;
}
+ } else {
+ ALOGE("%s: Malformed camera metadata received from HAL", __FUNCTION__);
+ status = common::V1_0::Status::INTERNAL_ERROR;
}
- });
+ }
+ };
+ hardware::Return<void> err;
+ if (mHidlSession_3_4 != nullptr) {
+ device::V3_4::RequestTemplate id;
+ switch (templateId) {
+ case CAMERA3_TEMPLATE_PREVIEW:
+ id = device::V3_4::RequestTemplate::PREVIEW;
+ break;
+ case CAMERA3_TEMPLATE_STILL_CAPTURE:
+ id = device::V3_4::RequestTemplate::STILL_CAPTURE;
+ break;
+ case CAMERA3_TEMPLATE_VIDEO_RECORD:
+ id = device::V3_4::RequestTemplate::VIDEO_RECORD;
+ break;
+ case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
+ id = device::V3_4::RequestTemplate::VIDEO_SNAPSHOT;
+ break;
+ case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
+ id = device::V3_4::RequestTemplate::ZERO_SHUTTER_LAG;
+ break;
+ case CAMERA3_TEMPLATE_MANUAL:
+ id = device::V3_4::RequestTemplate::MANUAL;
+ break;
+ case CAMERA3_TEMPLATE_MOTION_TRACKING_PREVIEW:
+ id = device::V3_4::RequestTemplate::MOTION_TRACKING_PREVIEW;
+ break;
+ case CAMERA3_TEMPLATE_MOTION_TRACKING_BEST:
+ id = device::V3_4::RequestTemplate::MOTION_TRACKING_BEST;
+ break;
+ default:
+ // Unknown template ID
+ return BAD_VALUE;
+ }
+ err = mHidlSession_3_4->constructDefaultRequestSettings_3_4(id, requestCallback);
+ } else {
+ RequestTemplate id;
+ switch (templateId) {
+ case CAMERA3_TEMPLATE_PREVIEW:
+ id = RequestTemplate::PREVIEW;
+ break;
+ case CAMERA3_TEMPLATE_STILL_CAPTURE:
+ id = RequestTemplate::STILL_CAPTURE;
+ break;
+ case CAMERA3_TEMPLATE_VIDEO_RECORD:
+ id = RequestTemplate::VIDEO_RECORD;
+ break;
+ case CAMERA3_TEMPLATE_VIDEO_SNAPSHOT:
+ id = RequestTemplate::VIDEO_SNAPSHOT;
+ break;
+ case CAMERA3_TEMPLATE_ZERO_SHUTTER_LAG:
+ id = RequestTemplate::ZERO_SHUTTER_LAG;
+ break;
+ case CAMERA3_TEMPLATE_MANUAL:
+ id = RequestTemplate::MANUAL;
+ break;
+ default:
+ // Unknown template ID, or this HAL is too old to support it
+ return BAD_VALUE;
+ }
+ err = mHidlSession->constructDefaultRequestSettings(id, requestCallback);
+ }
+
if (!err.isOk()) {
ALOGE("%s: Transaction error: %s", __FUNCTION__, err.description().c_str());
res = DEAD_OBJECT;
@@ -3412,24 +3583,11 @@
common::V1_0::Status status;
// See if we have v3.4 or v3.3 HAL
- sp<device::V3_4::ICameraDeviceSession> hidlSession_3_4;
- sp<device::V3_3::ICameraDeviceSession> hidlSession_3_3;
- auto castResult_3_4 = device::V3_4::ICameraDeviceSession::castFrom(mHidlSession);
- if (castResult_3_4.isOk()) {
- hidlSession_3_4 = castResult_3_4;
- } else {
- auto castResult_3_3 = device::V3_3::ICameraDeviceSession::castFrom(mHidlSession);
- if (castResult_3_3.isOk()) {
- hidlSession_3_3 = castResult_3_3;
- }
- }
-
- if (hidlSession_3_4 != nullptr) {
- // We do; use v3.4 for the call, and construct a v3.4
- // HalStreamConfiguration
+ if (mHidlSession_3_4 != nullptr) {
+ // We do; use v3.4 for the call
ALOGV("%s: v3.4 device found", __FUNCTION__);
device::V3_4::HalStreamConfiguration finalConfiguration3_4;
- auto err = hidlSession_3_4->configureStreams_3_4(requestedConfiguration3_4,
+ auto err = mHidlSession_3_4->configureStreams_3_4(requestedConfiguration3_4,
[&status, &finalConfiguration3_4]
(common::V1_0::Status s, const device::V3_4::HalStreamConfiguration& halConfiguration) {
finalConfiguration3_4 = halConfiguration;
@@ -3443,10 +3601,10 @@
for (size_t i = 0; i < finalConfiguration3_4.streams.size(); i++) {
finalConfiguration.streams[i] = finalConfiguration3_4.streams[i].v3_3;
}
- } else if (hidlSession_3_3 != nullptr) {
+ } else if (mHidlSession_3_3 != nullptr) {
// We do; use v3.3 for the call
ALOGV("%s: v3.3 device found", __FUNCTION__);
- auto err = hidlSession_3_3->configureStreams_3_3(requestedConfiguration3_2,
+ auto err = mHidlSession_3_3->configureStreams_3_3(requestedConfiguration3_2,
[&status, &finalConfiguration]
(common::V1_0::Status s, const device::V3_3::HalStreamConfiguration& halConfiguration) {
finalConfiguration = halConfiguration;
@@ -4442,9 +4600,13 @@
if (res == OK) {
sp<StatusTracker> statusTracker = mStatusTracker.promote();
if (statusTracker != 0) {
+ sp<Camera3Device> parent = mParent.promote();
+ if (parent != nullptr) {
+ parent->pauseStateNotify(true);
+ }
+
statusTracker->markComponentIdle(mStatusId, Fence::NO_FENCE);
- sp<Camera3Device> parent = mParent.promote();
if (parent != nullptr) {
mReconfigured |= parent->reconfigureCamera(mLatestSessionParams);
}
@@ -4619,6 +4781,7 @@
outputBuffers->insertAt(camera3_stream_buffer_t(), 0,
captureRequest->mOutputStreams.size());
halRequest->output_buffers = outputBuffers->array();
+ std::set<String8> requestedPhysicalCameras;
for (size_t j = 0; j < captureRequest->mOutputStreams.size(); j++) {
sp<Camera3OutputStreamInterface> outputStream = captureRequest->mOutputStreams.editItemAt(j);
@@ -4649,8 +4812,18 @@
return TIMED_OUT;
}
- halRequest->num_output_buffers++;
+ String8 physicalCameraId = outputStream->getPhysicalCameraId();
+
+ if (!physicalCameraId.isEmpty()) {
+ // Physical stream isn't supported for input request.
+ if (halRequest->input_buffer) {
+ CLOGE("Physical stream is not supported for input request");
+ return INVALID_OPERATION;
+ }
+ requestedPhysicalCameras.insert(physicalCameraId);
+ }
+ halRequest->num_output_buffers++;
}
totalNumBuffers += halRequest->num_output_buffers;
@@ -4673,7 +4846,8 @@
totalNumBuffers, captureRequest->mResultExtras,
/*hasInput*/halRequest->input_buffer != NULL,
hasCallback,
- calculateMaxExpectedDuration(halRequest->settings));
+ calculateMaxExpectedDuration(halRequest->settings),
+ requestedPhysicalCameras);
ALOGVV("%s: registered in flight requestId = %" PRId32 ", frameNumber = %" PRId64
", burstId = %" PRId32 ".",
__FUNCTION__,
diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
index 30ba800..ccd9d7a 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.h
+++ b/services/camera/libcameraservice/device3/Camera3Device.h
@@ -19,6 +19,7 @@
#include <utility>
#include <unordered_map>
+#include <set>
#include <utils/Condition.h>
#include <utils/Errors.h>
@@ -31,7 +32,9 @@
#include <android/hardware/camera/device/3.2/ICameraDevice.h>
#include <android/hardware/camera/device/3.2/ICameraDeviceSession.h>
#include <android/hardware/camera/device/3.3/ICameraDeviceSession.h>
+#include <android/hardware/camera/device/3.4/ICameraDeviceSession.h>
#include <android/hardware/camera/device/3.2/ICameraDeviceCallback.h>
+#include <android/hardware/camera/device/3.4/ICameraDeviceCallback.h>
#include <fmq/MessageQueue.h>
#include <hardware/camera3.h>
@@ -76,7 +79,7 @@
*/
class Camera3Device :
public CameraDeviceBase,
- virtual public hardware::camera::device::V3_2::ICameraDeviceCallback,
+ virtual public hardware::camera::device::V3_4::ICameraDeviceCallback,
private camera3_callback_ops {
public:
@@ -298,7 +301,13 @@
void getInflightBufferKeys(std::vector<std::pair<int32_t, int32_t>>* out);
private:
+ // Always valid
sp<hardware::camera::device::V3_2::ICameraDeviceSession> mHidlSession;
+ // Valid if ICameraDeviceSession is @3.3 or newer
+ sp<hardware::camera::device::V3_3::ICameraDeviceSession> mHidlSession_3_3;
+ // Valid if ICameraDeviceSession is @3.4 or newer
+ sp<hardware::camera::device::V3_4::ICameraDeviceSession> mHidlSession_3_4;
+
std::shared_ptr<RequestMetadataQueue> mRequestMetadataQueue;
std::mutex mInflightLock;
@@ -466,9 +475,11 @@
/**
- * Implementation of android::hardware::camera::device::V3_2::ICameraDeviceCallback
+ * Implementation of android::hardware::camera::device::V3_4::ICameraDeviceCallback
*/
-
+ hardware::Return<void> processCaptureResult_3_4(
+ const hardware::hidl_vec<
+ hardware::camera::device::V3_4::CaptureResult>& results) override;
hardware::Return<void> processCaptureResult(
const hardware::hidl_vec<
hardware::camera::device::V3_2::CaptureResult>& results) override;
@@ -478,7 +489,13 @@
// Handle one capture result. Assume that mProcessCaptureResultLock is held.
void processOneCaptureResultLocked(
- const hardware::camera::device::V3_2::CaptureResult& results);
+ const hardware::camera::device::V3_2::CaptureResult& result,
+ const hardware::hidl_vec<
+ hardware::camera::device::V3_4::PhysicalCameraMetadata> physicalCameraMetadatas);
+ status_t readOneCameraMetadataLocked(uint64_t fmqResultSize,
+ hardware::camera::device::V3_2::CameraMetadata& resultMetadata,
+ const hardware::camera::device::V3_2::CameraMetadata& result);
+
// Handle one notify message
void notify(const hardware::camera::device::V3_2::NotifyMsg& msg);
@@ -555,8 +572,15 @@
const SurfaceMap &surfaceMap);
/**
+ * Pause state updates to the client application. Needed to mask out idle/active
+ * transitions during internal reconfigure
+ */
+ void pauseStateNotify(bool enable);
+
+ /**
* Internally re-configure camera device using new session parameters.
- * This will get triggered by the request thread.
+ * This will get triggered by the request thread. Be sure to call
+ * pauseStateNotify(true) before going idle in the requesting location.
*/
bool reconfigureCamera(const CameraMetadata& sessionParams);
@@ -952,6 +976,12 @@
// REQUEST/RESULT error.
bool skipResultMetadata;
+ // The physical camera ids being requested.
+ std::set<String8> physicalCameraIds;
+
+ // Map of physicalCameraId <-> Metadata
+ std::vector<PhysicalCaptureResultInfo> physicalMetadatas;
+
// Default constructor needed by KeyedVector
InFlightRequest() :
shutterTimestamp(0),
@@ -966,7 +996,8 @@
}
InFlightRequest(int numBuffers, CaptureResultExtras extras, bool hasInput,
- bool hasAppCallback, nsecs_t maxDuration) :
+ bool hasAppCallback, nsecs_t maxDuration,
+ const std::set<String8>& physicalCameraIdSet) :
shutterTimestamp(0),
sensorTimestamp(0),
requestStatus(OK),
@@ -976,7 +1007,8 @@
hasInputBuffer(hasInput),
hasCallback(hasAppCallback),
maxExpectedDuration(maxDuration),
- skipResultMetadata(false) {
+ skipResultMetadata(false),
+ physicalCameraIds(physicalCameraIdSet) {
}
};
@@ -993,7 +1025,7 @@
status_t registerInFlight(uint32_t frameNumber,
int32_t numBuffers, CaptureResultExtras resultExtras, bool hasInput,
- bool callback, nsecs_t maxExpectedDuration);
+ bool callback, nsecs_t maxExpectedDuration, std::set<String8>& physicalCameraIds);
/**
* Returns the maximum expected time it'll take for all currently in-flight
@@ -1114,7 +1146,9 @@
void sendCaptureResult(CameraMetadata &pendingMetadata,
CaptureResultExtras &resultExtras,
CameraMetadata &collectedPartialResult, uint32_t frameNumber,
- bool reprocess);
+ bool reprocess, const std::vector<PhysicalCaptureResultInfo>& physicalMetadatas);
+
+ bool isLastFullResult(const InFlightRequest& inFlightRequest);
// Insert the result to the result queue after updating frame number and overriding AE
// trigger cancel.
diff --git a/services/camera/libcameraservice/device3/Camera3DummyStream.cpp b/services/camera/libcameraservice/device3/Camera3DummyStream.cpp
index 44eb68a..fb1ff77 100644
--- a/services/camera/libcameraservice/device3/Camera3DummyStream.cpp
+++ b/services/camera/libcameraservice/device3/Camera3DummyStream.cpp
@@ -115,6 +115,10 @@
return OK;
}
+const String8& Camera3DummyStream::getPhysicalCameraId() const {
+ return DUMMY_ID;
+}
+
status_t Camera3DummyStream::setConsumers(const std::vector<sp<Surface>>& /*consumers*/) {
ALOGE("%s: Stream %d: Dummy stream doesn't support set consumer surface!",
__FUNCTION__, mId);
diff --git a/services/camera/libcameraservice/device3/Camera3DummyStream.h b/services/camera/libcameraservice/device3/Camera3DummyStream.h
index dcf9160..4627548 100644
--- a/services/camera/libcameraservice/device3/Camera3DummyStream.h
+++ b/services/camera/libcameraservice/device3/Camera3DummyStream.h
@@ -63,6 +63,11 @@
virtual status_t dropBuffers(bool /*dropping*/) override;
/**
+ * Query the physical camera id for the output stream.
+ */
+ virtual const String8& getPhysicalCameraId() const override;
+
+ /**
* Return if this output stream is for video encoding.
*/
bool isVideoStream() const;
diff --git a/services/camera/libcameraservice/device3/Camera3OutputStream.cpp b/services/camera/libcameraservice/device3/Camera3OutputStream.cpp
index b73e256..b3c3717 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputStream.cpp
+++ b/services/camera/libcameraservice/device3/Camera3OutputStream.cpp
@@ -806,6 +806,11 @@
return OK;
}
+const String8& Camera3OutputStream::getPhysicalCameraId() const {
+ Mutex::Autolock l(mLock);
+ return physicalCameraId();
+}
+
status_t Camera3OutputStream::notifyBufferReleased(ANativeWindowBuffer* /*anwBuffer*/) {
return OK;
}
diff --git a/services/camera/libcameraservice/device3/Camera3OutputStream.h b/services/camera/libcameraservice/device3/Camera3OutputStream.h
index 824aef7..6f36f92 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputStream.h
+++ b/services/camera/libcameraservice/device3/Camera3OutputStream.h
@@ -174,6 +174,11 @@
virtual status_t dropBuffers(bool dropping) override;
/**
+ * Query the physical camera id for the output stream.
+ */
+ virtual const String8& getPhysicalCameraId() const override;
+
+ /**
* Set the graphic buffer manager to get/return the stream buffers.
*
* It is only legal to call this method when stream is in STATE_CONSTRUCTED state.
diff --git a/services/camera/libcameraservice/device3/Camera3OutputStreamInterface.h b/services/camera/libcameraservice/device3/Camera3OutputStreamInterface.h
index 08fcf38..a711a6d 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputStreamInterface.h
+++ b/services/camera/libcameraservice/device3/Camera3OutputStreamInterface.h
@@ -78,6 +78,11 @@
* Drop buffers if dropping is true. If dropping is false, do not drop buffers.
*/
virtual status_t dropBuffers(bool /*dropping*/) = 0;
+
+ /**
+ * Query the physical camera id for the output stream.
+ */
+ virtual const String8& getPhysicalCameraId() const = 0;
};
} // namespace camera3
diff --git a/services/camera/libcameraservice/device3/Camera3Stream.cpp b/services/camera/libcameraservice/device3/Camera3Stream.cpp
index 0d91620..1105b75 100644
--- a/services/camera/libcameraservice/device3/Camera3Stream.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Stream.cpp
@@ -143,6 +143,10 @@
return mOriginalDataSpace;
}
+const String8& Camera3Stream::physicalCameraId() const {
+ return mPhysicalCameraId;
+}
+
status_t Camera3Stream::forceToIdle() {
ATRACE_CALL();
Mutex::Autolock l(mLock);
diff --git a/services/camera/libcameraservice/device3/Camera3Stream.h b/services/camera/libcameraservice/device3/Camera3Stream.h
index f85dff2..a60cb56 100644
--- a/services/camera/libcameraservice/device3/Camera3Stream.h
+++ b/services/camera/libcameraservice/device3/Camera3Stream.h
@@ -165,6 +165,7 @@
void setDataSpaceOverride(bool dataSpaceOverriden);
bool isDataSpaceOverridden() const;
android_dataspace getOriginalDataSpace() const;
+ const String8& physicalCameraId() const;
camera3_stream* asHalStream() override {
return this;
diff --git a/services/mediaanalytics/Android.mk b/services/mediaanalytics/Android.mk
index 9e2813e..2eeb7fa 100644
--- a/services/mediaanalytics/Android.mk
+++ b/services/mediaanalytics/Android.mk
@@ -6,11 +6,6 @@
LOCAL_SRC_FILES:= \
main_mediametrics.cpp \
- MetricsSummarizerCodec.cpp \
- MetricsSummarizerExtractor.cpp \
- MetricsSummarizerPlayer.cpp \
- MetricsSummarizerRecorder.cpp \
- MetricsSummarizer.cpp \
MediaAnalyticsService.cpp
LOCAL_SHARED_LIBRARIES := \
diff --git a/services/mediaanalytics/MediaAnalyticsService.cpp b/services/mediaanalytics/MediaAnalyticsService.cpp
index 2954b3b..a132e25 100644
--- a/services/mediaanalytics/MediaAnalyticsService.cpp
+++ b/services/mediaanalytics/MediaAnalyticsService.cpp
@@ -74,26 +74,11 @@
#include "MediaAnalyticsService.h"
-#include "MetricsSummarizer.h"
-#include "MetricsSummarizerCodec.h"
-#include "MetricsSummarizerExtractor.h"
-#include "MetricsSummarizerPlayer.h"
-#include "MetricsSummarizerRecorder.h"
-
-
namespace android {
using namespace android::base;
using namespace android::content::pm;
-
-
-// summarized records
-// up to 36 sets, each covering an hour -- so at least 1.5 days
-// (will be longer if there are hours without any media action)
-static const nsecs_t kNewSetIntervalNs = 3600*(1000*1000*1000ll);
-static const int kMaxRecordSets = 36;
-
// individual records kept in memory: age or count
// age: <= 36 hours (1.5 days)
// count: hard limit of # records
@@ -108,57 +93,9 @@
String16(kServiceName), new MediaAnalyticsService());
}
-// handle sets of summarizers
-MediaAnalyticsService::SummarizerSet::SummarizerSet() {
- mSummarizers = new List<MetricsSummarizer *>();
-}
-
-MediaAnalyticsService::SummarizerSet::~SummarizerSet() {
- // empty the list
- List<MetricsSummarizer *> *l = mSummarizers;
- while (l->size() > 0) {
- MetricsSummarizer *summarizer = *(l->begin());
- l->erase(l->begin());
- delete summarizer;
- }
-}
-
-void MediaAnalyticsService::newSummarizerSet() {
- ALOGD("MediaAnalyticsService::newSummarizerSet");
- MediaAnalyticsService::SummarizerSet *set = new MediaAnalyticsService::SummarizerSet();
- nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
- set->setStarted(now);
-
- set->appendSummarizer(new MetricsSummarizerExtractor("extractor"));
- set->appendSummarizer(new MetricsSummarizerCodec("codec"));
- set->appendSummarizer(new MetricsSummarizerPlayer("nuplayer"));
- set->appendSummarizer(new MetricsSummarizerRecorder("recorder"));
-
- // ALWAYS at the end, since it catches everything
- set->appendSummarizer(new MetricsSummarizer(NULL));
-
- // inject this set at the BACK of the list.
- mSummarizerSets->push_back(set);
- mCurrentSet = set;
-
- // limit the # that we have
- if (mMaxRecordSets > 0) {
- List<SummarizerSet *> *l = mSummarizerSets;
- while (l->size() > (size_t) mMaxRecordSets) {
- ALOGD("Deleting oldest record set....");
- MediaAnalyticsService::SummarizerSet *oset = *(l->begin());
- l->erase(l->begin());
- delete oset;
- mSetsDiscarded++;
- }
- }
-}
-
MediaAnalyticsService::MediaAnalyticsService()
: mMaxRecords(kMaxRecords),
mMaxRecordAgeNs(kMaxRecordAgeNs),
- mMaxRecordSets(kMaxRecordSets),
- mNewSetInterval(kNewSetIntervalNs),
mDumpProto(MediaAnalyticsItem::PROTO_V1),
mDumpProtoDefault(MediaAnalyticsItem::PROTO_V1) {
@@ -167,9 +104,6 @@
mOpen = new List<MediaAnalyticsItem *>();
mFinalized = new List<MediaAnalyticsItem *>();
- mSummarizerSets = new List<MediaAnalyticsService::SummarizerSet *>();
- newSummarizerSet();
-
mItemsSubmitted = 0;
mItemsFinalized = 0;
mItemsDiscarded = 0;
@@ -204,8 +138,6 @@
}
delete mFinalized;
mFinalized = NULL;
-
- // XXX: clean out the summaries
}
@@ -315,13 +247,11 @@
oitem = NULL;
} else {
oitem->setFinalized(true);
- summarize(oitem);
saveItem(mFinalized, oitem, 0);
}
// new record could itself be marked finalized...
id = item->getSessionID();
if (finalizing) {
- summarize(item);
saveItem(mFinalized, item, 0);
mItemsFinalized++;
} else {
@@ -332,7 +262,6 @@
oitem->merge(item);
id = oitem->getSessionID();
if (finalizing) {
- summarize(oitem);
saveItem(mFinalized, oitem, 0);
mItemsFinalized++;
}
@@ -350,7 +279,6 @@
delete item;
item = NULL;
} else {
- summarize(item);
saveItem(mFinalized, item, 0);
mItemsFinalized++;
}
@@ -379,8 +307,6 @@
}
// crack any parameters
- String16 summaryOption("-summary");
- bool summary = false;
String16 protoOption("-proto");
int chosenProto = mDumpProtoDefault;
String16 clearOption("-clear");
@@ -396,8 +322,6 @@
String8 myarg(args[i]);
if (args[i] == clearOption) {
clear = true;
- } else if (args[i] == summaryOption) {
- summary = true;
} else if (args[i] == protoOption) {
i++;
if (i < n) {
@@ -444,7 +368,6 @@
result.append("Recognized parameters:\n");
result.append("-help this help message\n");
result.append("-proto # dump using protocol #");
- result.append("-summary show summary info\n");
result.append("-clear clears out saved records\n");
result.append("-only X process records for component X\n");
result.append("-since X include records since X\n");
@@ -464,12 +387,7 @@
dumpHeaders(result, ts_since);
- // want exactly 1, to avoid confusing folks that parse the output
- if (summary) {
- dumpSummaries(result, ts_since, only.c_str());
- } else {
- dumpRecent(result, ts_since, only.c_str());
- }
+ dumpRecent(result, ts_since, only.c_str());
if (clear) {
@@ -526,40 +444,6 @@
}
}
-// dump summary info
-void MediaAnalyticsService::dumpSummaries(String8 &result, nsecs_t ts_since, const char *only) {
- const size_t SIZE = 512;
- char buffer[SIZE];
- int slot = 0;
-
- snprintf(buffer, SIZE, "\nSummarized Metrics:\n");
- result.append(buffer);
-
- if (only != NULL && *only == '\0') {
- only = NULL;
- }
-
- // have each of the distillers dump records
- if (mSummarizerSets != NULL) {
- List<SummarizerSet *>::iterator itSet = mSummarizerSets->begin();
- for (; itSet != mSummarizerSets->end(); itSet++) {
- nsecs_t when = (*itSet)->getStarted();
- if (when < ts_since) {
- continue;
- }
- List<MetricsSummarizer *> *list = (*itSet)->getSummarizers();
- List<MetricsSummarizer *>::iterator it = list->begin();
- for (; it != list->end(); it++) {
- if (only != NULL && strcmp(only, (*it)->getKey()) != 0) {
- ALOGV("Told to omit '%s'", (*it)->getKey());
- }
- std::string distilled = (*it)->dumpSummary(slot, only);
- result.append(distilled.c_str());
- }
- }
- }
-}
-
// the recent, detailed queues
void MediaAnalyticsService::dumpRecent(String8 &result, nsecs_t ts_since, const char * only) {
const size_t SIZE = 512;
@@ -748,8 +632,11 @@
static std::string allowedKeys[] =
{
+ "audiorecord",
+ "audiotrack",
"codec",
- "extractor"
+ "extractor",
+ "nuplayer",
};
static const int nAllowedKeys = sizeof(allowedKeys) / sizeof(allowedKeys[0]);
@@ -785,45 +672,6 @@
return false;
}
-// insert into the appropriate summarizer.
-// we make our own copy to save/summarize
-void MediaAnalyticsService::summarize(MediaAnalyticsItem *item) {
-
- ALOGV("MediaAnalyticsService::summarize()");
-
- if (item == NULL) {
- return;
- }
-
- nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
- if (mCurrentSet == NULL
- || (mCurrentSet->getStarted() + mNewSetInterval < now)) {
- newSummarizerSet();
- }
-
- if (mCurrentSet == NULL) {
- return;
- }
-
- List<MetricsSummarizer *> *summarizers = mCurrentSet->getSummarizers();
- List<MetricsSummarizer *>::iterator it = summarizers->begin();
- for (; it != summarizers->end(); it++) {
- if ((*it)->isMine(*item)) {
- break;
- }
- }
- if (it == summarizers->end()) {
- ALOGD("no handler for type %s", item->getKey().c_str());
- return; // no handler
- }
-
- // invoke the summarizer. summarizer will make whatever copies
- // it wants; the caller retains ownership of item.
-
- (*it)->handleRecord(item);
-
-}
-
// how long we hold package info before we re-fetch it
#define PKG_EXPIRATION_NS (30*60*1000000000ll) // 30 minutes, in nsecs
diff --git a/services/mediaanalytics/MediaAnalyticsService.h b/services/mediaanalytics/MediaAnalyticsService.h
index 1287835..484339c 100644
--- a/services/mediaanalytics/MediaAnalyticsService.h
+++ b/services/mediaanalytics/MediaAnalyticsService.h
@@ -28,9 +28,6 @@
#include <media/IMediaAnalyticsService.h>
-#include "MetricsSummarizer.h"
-
-
namespace android {
class MediaAnalyticsService : public BnMediaAnalyticsService
@@ -89,36 +86,6 @@
MediaAnalyticsItem *findItem(List<MediaAnalyticsItem *> *,
MediaAnalyticsItem *, bool removeit);
- // summarizers
- void summarize(MediaAnalyticsItem *item);
- class SummarizerSet {
- nsecs_t mStarted;
- List<MetricsSummarizer *> *mSummarizers;
-
- public:
- void appendSummarizer(MetricsSummarizer *s) {
- if (s) {
- mSummarizers->push_back(s);
- }
- };
- nsecs_t getStarted() { return mStarted;}
- void setStarted(nsecs_t started) {mStarted = started;}
- List<MetricsSummarizer *> *getSummarizers() { return mSummarizers;}
-
- SummarizerSet();
- ~SummarizerSet();
- };
- void newSummarizerSet();
- List<SummarizerSet *> *mSummarizerSets;
- SummarizerSet *mCurrentSet;
- List<MetricsSummarizer *> *getFirstSet() {
- List<SummarizerSet *>::iterator first = mSummarizerSets->begin();
- if (first != mSummarizerSets->end()) {
- return (*first)->getSummarizers();
- }
- return NULL;
- }
-
void saveItem(MediaAnalyticsItem);
void saveItem(List<MediaAnalyticsItem *> *, MediaAnalyticsItem *, int);
void deleteItem(List<MediaAnalyticsItem *> *, MediaAnalyticsItem *);
diff --git a/services/mediaanalytics/MetricsSummarizer.cpp b/services/mediaanalytics/MetricsSummarizer.cpp
deleted file mode 100644
index e7c26e3..0000000
--- a/services/mediaanalytics/MetricsSummarizer.cpp
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Copyright (C) 2017 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_TAG "MetricsSummarizer"
-#include <utils/Log.h>
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <string>
-#include <inttypes.h>
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-
-#include "MetricsSummarizer.h"
-
-
-namespace android {
-
-#define DEBUG_SORT 0
-#define DEBUG_QUEUE 0
-
-
-MetricsSummarizer::MetricsSummarizer(const char *key)
- : mIgnorables(NULL)
-{
- ALOGV("MetricsSummarizer::MetricsSummarizer");
-
- if (key == NULL) {
- mKey = key;
- } else {
- mKey = strdup(key);
- }
-
- mSummaries = new List<MediaAnalyticsItem *>();
-}
-
-MetricsSummarizer::~MetricsSummarizer()
-{
- ALOGV("MetricsSummarizer::~MetricsSummarizer");
- if (mKey) {
- free((void *)mKey);
- mKey = NULL;
- }
-
- // clear the list of items we have saved
- while (mSummaries->size() > 0) {
- MediaAnalyticsItem * oitem = *(mSummaries->begin());
- if (DEBUG_QUEUE) {
- ALOGD("zap old record: key %s sessionID %" PRId64 " ts %" PRId64 "",
- oitem->getKey().c_str(), oitem->getSessionID(),
- oitem->getTimestamp());
- }
- mSummaries->erase(mSummaries->begin());
- delete oitem;
- }
-}
-
-// so we know what summarizer we were using
-const char *MetricsSummarizer::getKey() {
- const char *value = mKey;
- if (value == NULL) {
- value = "unknown";
- }
- return value;
-}
-
-// should the record be given to this summarizer
-bool MetricsSummarizer::isMine(MediaAnalyticsItem &item)
-{
- if (mKey == NULL)
- return true;
- std::string itemKey = item.getKey();
- if (strcmp(mKey, itemKey.c_str()) != 0) {
- return false;
- }
- return true;
-}
-
-std::string MetricsSummarizer::dumpSummary(int &slot)
-{
- return dumpSummary(slot, NULL);
-}
-
-std::string MetricsSummarizer::dumpSummary(int &slot, const char *only)
-{
- std::string value;
-
- List<MediaAnalyticsItem *>::iterator it = mSummaries->begin();
- if (it != mSummaries->end()) {
- char buf[16]; // enough for "#####: "
- for (; it != mSummaries->end(); it++) {
- if (only != NULL && strcmp(only, (*it)->getKey().c_str()) != 0) {
- continue;
- }
- std::string entry = (*it)->toString();
- snprintf(buf, sizeof(buf), "%5d: ", slot);
- value.append(buf);
- value.append(entry.c_str());
- value.append("\n");
- slot++;
- }
- }
- return value;
-}
-
-void MetricsSummarizer::setIgnorables(const char **ignorables) {
- mIgnorables = ignorables;
-}
-
-const char **MetricsSummarizer::getIgnorables() {
- return mIgnorables;
-}
-
-void MetricsSummarizer::handleRecord(MediaAnalyticsItem *item) {
-
- ALOGV("MetricsSummarizer::handleRecord() for %s",
- item == NULL ? "<nothing>" : item->toString().c_str());
-
- if (item == NULL) {
- return;
- }
-
- List<MediaAnalyticsItem *>::iterator it = mSummaries->begin();
- for (; it != mSummaries->end(); it++) {
- bool good = sameAttributes((*it), item, getIgnorables());
- ALOGV("Match against %s says %d", (*it)->toString().c_str(), good);
- if (good)
- break;
- }
- if (it == mSummaries->end()) {
- ALOGV("save new record");
- MediaAnalyticsItem *nitem = item->dup();
- if (nitem == NULL) {
- ALOGE("unable to save MediaMetrics record");
- }
- sortProps(nitem);
- nitem->setInt32("aggregated",1);
- mergeRecord(*nitem, *item);
- mSummaries->push_back(nitem);
- } else {
- ALOGV("increment existing record");
- (*it)->addInt32("aggregated",1);
- mergeRecord(*(*it), *item);
- }
-}
-
-void MetricsSummarizer::mergeRecord(MediaAnalyticsItem &/*have*/, MediaAnalyticsItem &/*item*/) {
- // default is no further massaging.
- ALOGV("MetricsSummarizer::mergeRecord() [default]");
- return;
-}
-
-// keep some stats for things: sums, counts, standard deviation
-// the integer version -- all of these pieces are in 64 bits
-void MetricsSummarizer::minMaxVar64(MediaAnalyticsItem &summation, const char *key, int64_t value) {
- if (key == NULL)
- return;
- int len = strlen(key) + 32;
- char *tmpKey = (char *)malloc(len);
-
- if (tmpKey == NULL) {
- return;
- }
-
- // N - count of samples
- snprintf(tmpKey, len, "%s.n", key);
- summation.addInt64(tmpKey, 1);
-
- // zero - count of samples that are zero
- if (value == 0) {
- snprintf(tmpKey, len, "%s.zero", key);
- int64_t zero = 0;
- (void) summation.getInt64(tmpKey,&zero);
- zero++;
- summation.setInt64(tmpKey, zero);
- }
-
- // min
- snprintf(tmpKey, len, "%s.min", key);
- int64_t min = value;
- if (summation.getInt64(tmpKey,&min)) {
- if (min > value) {
- summation.setInt64(tmpKey, value);
- }
- } else {
- summation.setInt64(tmpKey, value);
- }
-
- // max
- snprintf(tmpKey, len, "%s.max", key);
- int64_t max = value;
- if (summation.getInt64(tmpKey,&max)) {
- if (max < value) {
- summation.setInt64(tmpKey, value);
- }
- } else {
- summation.setInt64(tmpKey, value);
- }
-
- // components for mean, stddev;
- // stddev = sqrt(1/4*(sumx2 - (2*sumx*sumx/n) + ((sumx/n)^2)))
- // sum x
- snprintf(tmpKey, len, "%s.sumX", key);
- summation.addInt64(tmpKey, value);
- // sum x^2
- snprintf(tmpKey, len, "%s.sumX2", key);
- summation.addInt64(tmpKey, value*value);
-
-
- // last thing we do -- remove the base key from the summation
- // record so we won't get confused about it having both individual
- // and summary information in there.
- summation.removeProp(key);
-
- free(tmpKey);
-}
-
-
-//
-// Comparators
-//
-
-// testing that all of 'single' is in 'summ'
-// and that the values match.
-// 'summ' may have extra fields.
-// 'ignorable' is a set of things that we don't worry about matching up
-// (usually time- or count-based values we'll sum elsewhere)
-bool MetricsSummarizer::sameAttributes(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignorable) {
-
- if (single == NULL || summ == NULL) {
- return false;
- }
- ALOGV("MetricsSummarizer::sameAttributes(): summ %s", summ->toString().c_str());
- ALOGV("MetricsSummarizer::sameAttributes(): single %s", single->toString().c_str());
-
- // keep different sources/users separate
- if (single->mUid != summ->mUid) {
- return false;
- }
-
- // this can be made better.
- for(size_t i=0;i<single->mPropCount;i++) {
- MediaAnalyticsItem::Prop *prop1 = &(single->mProps[i]);
- const char *attrName = prop1->mName;
-
- // is it something we should ignore
- if (ignorable != NULL) {
- const char **ig = ignorable;
- for (;*ig; ig++) {
- if (strcmp(*ig, attrName) == 0) {
- break;
- }
- }
- if (*ig) {
- ALOGV("we don't mind that it has attr '%s'", attrName);
- continue;
- }
- }
-
- MediaAnalyticsItem::Prop *prop2 = summ->findProp(attrName);
- if (prop2 == NULL) {
- ALOGV("summ doesn't have this attr");
- return false;
- }
- if (prop1->mType != prop2->mType) {
- ALOGV("mismatched attr types");
- return false;
- }
- switch (prop1->mType) {
- case MediaAnalyticsItem::kTypeInt32:
- if (prop1->u.int32Value != prop2->u.int32Value) {
- ALOGV("mismatch values");
- return false;
- }
- break;
- case MediaAnalyticsItem::kTypeInt64:
- if (prop1->u.int64Value != prop2->u.int64Value) {
- ALOGV("mismatch values");
- return false;
- }
- break;
- case MediaAnalyticsItem::kTypeDouble:
- // XXX: watch out for floating point comparisons!
- if (prop1->u.doubleValue != prop2->u.doubleValue) {
- ALOGV("mismatch values");
- return false;
- }
- break;
- case MediaAnalyticsItem::kTypeCString:
- if (strcmp(prop1->u.CStringValue, prop2->u.CStringValue) != 0) {
- ALOGV("mismatch values");
- return false;
- }
- break;
- case MediaAnalyticsItem::kTypeRate:
- if (prop1->u.rate.count != prop2->u.rate.count) {
- ALOGV("mismatch values");
- return false;
- }
- if (prop1->u.rate.duration != prop2->u.rate.duration) {
- ALOGV("mismatch values");
- return false;
- }
- break;
- default:
- ALOGV("mismatch values in default type");
- return false;
- }
- }
-
- return true;
-}
-
-
-int MetricsSummarizer::PropSorter(const void *a, const void *b) {
- MediaAnalyticsItem::Prop *ai = (MediaAnalyticsItem::Prop *)a;
- MediaAnalyticsItem::Prop *bi = (MediaAnalyticsItem::Prop *)b;
- return strcmp(ai->mName, bi->mName);
-}
-
-// we sort in the summaries so that it looks pretty in the dumpsys
-void MetricsSummarizer::sortProps(MediaAnalyticsItem *item) {
- if (item->mPropCount != 0) {
- qsort(item->mProps, item->mPropCount,
- sizeof(MediaAnalyticsItem::Prop), MetricsSummarizer::PropSorter);
- }
-}
-
-} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizer.h b/services/mediaanalytics/MetricsSummarizer.h
deleted file mode 100644
index a16c7bc..0000000
--- a/services/mediaanalytics/MetricsSummarizer.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2017 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_METRICSSUMMARIZER_H
-#define ANDROID_METRICSSUMMARIZER_H
-
-#include <string>
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-
-
-namespace android {
-
-class MetricsSummarizer
-{
-
- public:
-
- MetricsSummarizer(const char *key);
- virtual ~MetricsSummarizer();
-
- // show the key
- const char * getKey();
-
- // should the record be given to this summarizer
- bool isMine(MediaAnalyticsItem &item);
-
- // hand the record to this summarizer
- void handleRecord(MediaAnalyticsItem *item);
-
- virtual void mergeRecord(MediaAnalyticsItem &have, MediaAnalyticsItem &incoming);
-
- // dump the summarized records (for dumpsys)
- std::string dumpSummary(int &slot);
- std::string dumpSummary(int &slot, const char *only);
-
- void setIgnorables(const char **);
- const char **getIgnorables();
-
- protected:
-
- // various comparators
- // "do these records have same attributes and values in those attrs"
- bool sameAttributes(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignoreables);
-
- void minMaxVar64(MediaAnalyticsItem &summ, const char *key, int64_t value);
-
- static int PropSorter(const void *a, const void *b);
- void sortProps(MediaAnalyticsItem *item);
-
- private:
- const char *mKey;
- const char **mIgnorables;
- List<MediaAnalyticsItem *> *mSummaries;
-
-
-};
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_METRICSSUMMARIZER_H
diff --git a/services/mediaanalytics/MetricsSummarizerCodec.cpp b/services/mediaanalytics/MetricsSummarizerCodec.cpp
deleted file mode 100644
index 6af3c9a..0000000
--- a/services/mediaanalytics/MetricsSummarizerCodec.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 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_TAG "MetricsSummarizerCodec"
-#include <utils/Log.h>
-
-#include <stdint.h>
-#include <inttypes.h>
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-
-#include "MetricsSummarizer.h"
-#include "MetricsSummarizerCodec.h"
-
-
-
-
-namespace android {
-
-MetricsSummarizerCodec::MetricsSummarizerCodec(const char *key)
- : MetricsSummarizer(key)
-{
- ALOGV("MetricsSummarizerCodec::MetricsSummarizerCodec");
-}
-
-} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerCodec.h b/services/mediaanalytics/MetricsSummarizerCodec.h
deleted file mode 100644
index c01196f..0000000
--- a/services/mediaanalytics/MetricsSummarizerCodec.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2017 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_METRICSSUMMARIZERCODEC_H
-#define ANDROID_METRICSSUMMARIZERCODEC_H
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-#include "MetricsSummarizer.h"
-
-
-namespace android {
-
-class MetricsSummarizerCodec : public MetricsSummarizer
-{
-
- public:
-
- MetricsSummarizerCodec(const char *key);
- virtual ~MetricsSummarizerCodec() {};
-
-};
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_METRICSSUMMARIZERCODEC_H
diff --git a/services/mediaanalytics/MetricsSummarizerExtractor.cpp b/services/mediaanalytics/MetricsSummarizerExtractor.cpp
deleted file mode 100644
index 190f87d..0000000
--- a/services/mediaanalytics/MetricsSummarizerExtractor.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2017 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_TAG "MetricsSummarizerExtractor"
-#include <utils/Log.h>
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-
-#include "MetricsSummarizer.h"
-#include "MetricsSummarizerExtractor.h"
-
-
-
-
-namespace android {
-
-MetricsSummarizerExtractor::MetricsSummarizerExtractor(const char *key)
- : MetricsSummarizer(key)
-{
- ALOGV("MetricsSummarizerExtractor::MetricsSummarizerExtractor");
-}
-
-} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerExtractor.h b/services/mediaanalytics/MetricsSummarizerExtractor.h
deleted file mode 100644
index eee052b..0000000
--- a/services/mediaanalytics/MetricsSummarizerExtractor.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2017 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_METRICSSUMMARIZEREXTRACTOR_H
-#define ANDROID_METRICSSUMMARIZEREXTRACTOR_H
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-#include "MetricsSummarizer.h"
-
-
-namespace android {
-
-class MetricsSummarizerExtractor : public MetricsSummarizer
-{
-
- public:
-
- MetricsSummarizerExtractor(const char *key);
- virtual ~MetricsSummarizerExtractor() {};
-
-};
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_METRICSSUMMARIZEREXTRACTOR_H
diff --git a/services/mediaanalytics/MetricsSummarizerPlayer.cpp b/services/mediaanalytics/MetricsSummarizerPlayer.cpp
deleted file mode 100644
index f882cb9..0000000
--- a/services/mediaanalytics/MetricsSummarizerPlayer.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2017 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_TAG "MetricsSummarizerPlayer"
-#include <utils/Log.h>
-
-#include <stdint.h>
-#include <inttypes.h>
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-
-#include "MetricsSummarizer.h"
-#include "MetricsSummarizerPlayer.h"
-
-
-
-
-namespace android {
-
-static const char *player_ignorable[] = {
- "android.media.mediaplayer.durationMs",
- "android.media.mediaplayer.playingMs",
- "android.media.mediaplayer.frames",
- "android.media.mediaplayer.dropped",
- 0
-};
-
-MetricsSummarizerPlayer::MetricsSummarizerPlayer(const char *key)
- : MetricsSummarizer(key)
-{
- ALOGV("MetricsSummarizerPlayer::MetricsSummarizerPlayer");
- setIgnorables(player_ignorable);
-}
-
-// NB: this is also called for the first time -- so summation == item
-// Not sure if we need a flag for that or not.
-// In this particular mergeRecord() code -- we're' ok for this.
-void MetricsSummarizerPlayer::mergeRecord(MediaAnalyticsItem &summation, MediaAnalyticsItem &item) {
-
- ALOGV("MetricsSummarizerPlayer::mergeRecord()");
-
-
- int64_t duration = 0;
- if (item.getInt64("android.media.mediaplayer.durationMs", &duration)) {
- ALOGV("found durationMs of %" PRId64, duration);
- minMaxVar64(summation, "android.media.mediaplayer.durationMs", duration);
- }
-
- int64_t playing = 0;
- if (item.getInt64("android.media.mediaplayer.playingMs", &playing)) {
- ALOGV("found playingMs of %" PRId64, playing);
- }
- if (playing >= 0) {
- minMaxVar64(summation,"android.media.mediaplayer.playingMs",playing);
- }
-
- int64_t frames = 0;
- if (item.getInt64("android.media.mediaplayer.frames", &frames)) {
- ALOGV("found framess of %" PRId64, frames);
- }
- if (frames >= 0) {
- minMaxVar64(summation,"android.media.mediaplayer.frames",frames);
- }
-
- int64_t dropped = 0;
- if (item.getInt64("android.media.mediaplayer.dropped", &dropped)) {
- ALOGV("found dropped of %" PRId64, dropped);
- }
- if (dropped >= 0) {
- minMaxVar64(summation,"android.media.mediaplayer.dropped",dropped);
- }
-}
-
-} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerPlayer.h b/services/mediaanalytics/MetricsSummarizerPlayer.h
deleted file mode 100644
index ad1bf74..0000000
--- a/services/mediaanalytics/MetricsSummarizerPlayer.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2017 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_METRICSSUMMARIZERPLAYER_H
-#define ANDROID_METRICSSUMMARIZERPLAYER_H
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-#include "MetricsSummarizer.h"
-
-
-namespace android {
-
-class MetricsSummarizerPlayer : public MetricsSummarizer
-{
-
- public:
-
- MetricsSummarizerPlayer(const char *key);
- virtual ~MetricsSummarizerPlayer() {};
-
- virtual void mergeRecord(MediaAnalyticsItem &have, MediaAnalyticsItem &incoming);
-
-};
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_METRICSSUMMARIZERPLAYER_H
diff --git a/services/mediaanalytics/MetricsSummarizerRecorder.cpp b/services/mediaanalytics/MetricsSummarizerRecorder.cpp
deleted file mode 100644
index c2919c3..0000000
--- a/services/mediaanalytics/MetricsSummarizerRecorder.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 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_TAG "MetricsSummarizerRecorder"
-#include <utils/Log.h>
-
-#include <stdint.h>
-#include <inttypes.h>
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-
-#include "MetricsSummarizer.h"
-#include "MetricsSummarizerRecorder.h"
-
-
-
-
-namespace android {
-
-MetricsSummarizerRecorder::MetricsSummarizerRecorder(const char *key)
- : MetricsSummarizer(key)
-{
- ALOGV("MetricsSummarizerRecorder::MetricsSummarizerRecorder");
-}
-
-} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerRecorder.h b/services/mediaanalytics/MetricsSummarizerRecorder.h
deleted file mode 100644
index 963baab..0000000
--- a/services/mediaanalytics/MetricsSummarizerRecorder.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2017 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_METRICSSUMMARIZERRECORDER_H
-#define ANDROID_METRICSSUMMARIZERRECORDER_H
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
-#include <media/IMediaAnalyticsService.h>
-#include "MetricsSummarizer.h"
-
-
-namespace android {
-
-class MetricsSummarizerRecorder : public MetricsSummarizer
-{
-
- public:
-
- MetricsSummarizerRecorder(const char *key);
- virtual ~MetricsSummarizerRecorder() {};
-
-};
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_METRICSSUMMARIZERRECORDER_H
diff --git a/services/mediacodec/Android.mk b/services/mediacodec/Android.mk
index 8e5b260..97eaf77 100644
--- a/services/mediacodec/Android.mk
+++ b/services/mediacodec/Android.mk
@@ -1,5 +1,28 @@
LOCAL_PATH := $(call my-dir)
+_software_codecs := \
+ libstagefright_soft_aacdec \
+ libstagefright_soft_aacenc \
+ libstagefright_soft_amrdec \
+ libstagefright_soft_amrnbenc \
+ libstagefright_soft_amrwbenc \
+ libstagefright_soft_avcdec \
+ libstagefright_soft_avcenc \
+ libstagefright_soft_flacdec \
+ libstagefright_soft_flacenc \
+ libstagefright_soft_g711dec \
+ libstagefright_soft_gsmdec \
+ libstagefright_soft_hevcdec \
+ libstagefright_soft_mp3dec \
+ libstagefright_soft_mpeg2dec \
+ libstagefright_soft_mpeg4dec \
+ libstagefright_soft_mpeg4enc \
+ libstagefright_soft_opusdec \
+ libstagefright_soft_rawdec \
+ libstagefright_soft_vorbisdec \
+ libstagefright_soft_vpxdec \
+ libstagefright_soft_vpxenc \
+
# service executable
include $(CLEAR_VARS)
# seccomp is not required for coverage build.
@@ -27,6 +50,15 @@
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_VENDOR_MODULE := true
LOCAL_32_BIT_ONLY := true
+# Since this is 32-bit-only module, only 32-bit version of the codecs are installed.
+# TODO(b/72343507): eliminate the need for manually adding .vendor suffix. This should be done
+# by the build system.
+LOCAL_REQUIRED_MODULES := \
+$(foreach codec,$(_software_codecs),\
+ $(eval _vendor_suffix := $(if $(BOARD_VNDK_VERSION),.vendor))\
+ $(codec)$(_vendor_suffix)\
+)
+_software_codecs :=
LOCAL_INIT_RC := android.hardware.media.omx@1.0-service.rc
include $(BUILD_EXECUTABLE)
diff --git a/services/mediaextractor/main_extractorservice.cpp b/services/mediaextractor/main_extractorservice.cpp
index 0dc5e29..8d3359a 100644
--- a/services/mediaextractor/main_extractorservice.cpp
+++ b/services/mediaextractor/main_extractorservice.cpp
@@ -25,6 +25,7 @@
#include <string>
#include <android-base/logging.h>
+#include <android-base/properties.h>
#include <utils/misc.h>
// from LOCAL_C_INCLUDES
@@ -65,14 +66,10 @@
sp<IServiceManager> sm = defaultServiceManager();
MediaExtractorService::instantiate();
- // TODO: Uncomment below once sepolicy change is landed.
- /*
- char value[PROPERTY_VALUE_MAX];
- property_get("ro.build.type", value, "unknown");
- if (strcmp(value, "userdebug") == 0 || strcmp(value, "eng") == 0) {
+ std::string value = base::GetProperty("ro.build.type", "unknown");
+ if (value == "userdebug" || value == "eng") {
media::MediaExtractorUpdateService::instantiate();
}
- */
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
diff --git a/services/oboeservice/AAudioServiceStreamBase.cpp b/services/oboeservice/AAudioServiceStreamBase.cpp
index 864fc35..e8c9e41 100644
--- a/services/oboeservice/AAudioServiceStreamBase.cpp
+++ b/services/oboeservice/AAudioServiceStreamBase.cpp
@@ -271,11 +271,11 @@
}
aaudio_result_t AAudioServiceStreamBase::flush() {
- if (getState() != AAUDIO_STREAM_STATE_PAUSED) {
- ALOGE("flush() stream not paused, state = %s",
- AAudio_convertStreamStateToText(mState));
- return AAUDIO_ERROR_INVALID_STATE;
+ aaudio_result_t result = AAudio_isFlushAllowed(getState());
+ if (result != AAUDIO_OK) {
+ return result;
}
+
// Data will get flushed when the client receives the FLUSHED event.
sendServiceEvent(AAUDIO_SERVICE_EVENT_FLUSHED);
setState(AAUDIO_STREAM_STATE_FLUSHED);