Merge "Make prior{Linear/Graphic}Allocation own handle" into pi-dev
diff --git a/camera/ndk/include/camera/NdkCameraMetadataTags.h b/camera/ndk/include/camera/NdkCameraMetadataTags.h
index 839b134..346761c 100644
--- a/camera/ndk/include/camera/NdkCameraMetadataTags.h
+++ b/camera/ndk/include/camera/NdkCameraMetadataTags.h
@@ -1309,8 +1309,8 @@
* Any state (excluding LOCKED) | ACAMERA_CONTROL_AE_PRECAPTURE_TRIGGER is START | PRECAPTURE | Start AE precapture metering sequence
* Any state (excluding LOCKED) | ACAMERA_CONTROL_AE_PRECAPTURE_TRIGGER is CANCEL| INACTIVE | Currently active precapture metering sequence is canceled</p>
* <p>If the camera device supports AE external flash mode (ON_EXTERNAL_FLASH is included in
- * ACAMERA_CONTROL_AE_AVAILABLE_MODES), aeState must be FLASH_REQUIRED after the camera device
- * finishes AE scan and it's too dark without flash.</p>
+ * ACAMERA_CONTROL_AE_AVAILABLE_MODES), ACAMERA_CONTROL_AE_STATE must be FLASH_REQUIRED after
+ * the camera device finishes AE scan and it's too dark without flash.</p>
* <p>For the above table, the camera device may skip reporting any state changes that happen
* without application intervention (i.e. mode switch, trigger, locking). Any state that
* can be skipped in that manner is called a transient state.</p>
@@ -1331,6 +1331,7 @@
* @see ACAMERA_CONTROL_AE_LOCK
* @see ACAMERA_CONTROL_AE_MODE
* @see ACAMERA_CONTROL_AE_PRECAPTURE_TRIGGER
+ * @see ACAMERA_CONTROL_AE_STATE
* @see ACAMERA_CONTROL_MODE
* @see ACAMERA_CONTROL_SCENE_MODE
*/
@@ -2256,7 +2257,7 @@
* from the main sensor along the +X axis (to the right from the user's perspective) will
* report <code>(0.03, 0, 0)</code>.</p>
* <p>To transform a pixel coordinates between two cameras facing the same direction, first
- * the source camera ACAMERA_LENS_RADIAL_DISTORTION must be corrected for. Then the source
+ * the source camera ACAMERA_LENS_DISTORTION must be corrected for. Then the source
* camera ACAMERA_LENS_INTRINSIC_CALIBRATION needs to be applied, followed by the
* ACAMERA_LENS_POSE_ROTATION of the source camera, the translation of the source camera
* relative to the destination camera, the ACAMERA_LENS_POSE_ROTATION of the destination
@@ -2268,10 +2269,10 @@
* <p>When ACAMERA_LENS_POSE_REFERENCE is GYROSCOPE, then this position is relative to
* the center of the primary gyroscope on the device.</p>
*
+ * @see ACAMERA_LENS_DISTORTION
* @see ACAMERA_LENS_INTRINSIC_CALIBRATION
* @see ACAMERA_LENS_POSE_REFERENCE
* @see ACAMERA_LENS_POSE_ROTATION
- * @see ACAMERA_LENS_RADIAL_DISTORTION
*/
ACAMERA_LENS_POSE_TRANSLATION = // float[3]
ACAMERA_LENS_START + 7,
@@ -2381,7 +2382,7 @@
* where <code>(0,0)</code> is the top-left of the
* preCorrectionActiveArraySize rectangle. Once the pose and
* intrinsic calibration transforms have been applied to a
- * world point, then the ACAMERA_LENS_RADIAL_DISTORTION
+ * world point, then the ACAMERA_LENS_DISTORTION
* transform needs to be applied, and the result adjusted to
* be in the ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE coordinate
* system (where <code>(0, 0)</code> is the top-left of the
@@ -2389,56 +2390,15 @@
* coordinate of the world point for processed (non-RAW)
* output buffers.</p>
*
+ * @see ACAMERA_LENS_DISTORTION
* @see ACAMERA_LENS_POSE_ROTATION
* @see ACAMERA_LENS_POSE_TRANSLATION
- * @see ACAMERA_LENS_RADIAL_DISTORTION
* @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
* @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
*/
ACAMERA_LENS_INTRINSIC_CALIBRATION = // float[5]
ACAMERA_LENS_START + 10,
- /**
- * <p>The correction coefficients to correct for this camera device's
- * radial and tangential lens distortion.</p>
- *
- * <p>Type: float[6]</p>
- *
- * <p>This tag may appear in:
- * <ul>
- * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li>
- * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li>
- * </ul></p>
- *
- * <p>Four radial distortion coefficients <code>[kappa_0, kappa_1, kappa_2,
- * kappa_3]</code> and two tangential distortion coefficients
- * <code>[kappa_4, kappa_5]</code> that can be used to correct the
- * lens's geometric distortion with the mapping equations:</p>
- * <pre><code> x_c = x_i * ( kappa_0 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) +
- * kappa_4 * (2 * x_i * y_i) + kappa_5 * ( r^2 + 2 * x_i^2 )
- * y_c = y_i * ( kappa_0 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) +
- * kappa_5 * (2 * x_i * y_i) + kappa_4 * ( r^2 + 2 * y_i^2 )
- * </code></pre>
- * <p>Here, <code>[x_c, y_c]</code> are the coordinates to sample in the
- * input image that correspond to the pixel values in the
- * corrected image at the coordinate <code>[x_i, y_i]</code>:</p>
- * <pre><code> correctedImage(x_i, y_i) = sample_at(x_c, y_c, inputImage)
- * </code></pre>
- * <p>The pixel coordinates are defined in a normalized
- * coordinate system related to the
- * ACAMERA_LENS_INTRINSIC_CALIBRATION calibration fields.
- * Both <code>[x_i, y_i]</code> and <code>[x_c, y_c]</code> have <code>(0,0)</code> at the
- * lens optical center <code>[c_x, c_y]</code>. The maximum magnitudes
- * of both x and y coordinates are normalized to be 1 at the
- * edge further from the optical center, so the range
- * for both dimensions is <code>-1 <= x <= 1</code>.</p>
- * <p>Finally, <code>r</code> represents the radial distance from the
- * optical center, <code>r^2 = x_i^2 + y_i^2</code>, and its magnitude
- * is therefore no larger than <code>|r| <= sqrt(2)</code>.</p>
- * <p>The distortion model used is the Brown-Conrady model.</p>
- *
- * @see ACAMERA_LENS_INTRINSIC_CALIBRATION
- */
- ACAMERA_LENS_RADIAL_DISTORTION = // float[6]
+ ACAMERA_LENS_RADIAL_DISTORTION = // Deprecated! DO NOT USE
ACAMERA_LENS_START + 11,
/**
* <p>The origin for ACAMERA_LENS_POSE_TRANSLATION.</p>
@@ -2457,6 +2417,51 @@
*/
ACAMERA_LENS_POSE_REFERENCE = // byte (acamera_metadata_enum_android_lens_pose_reference_t)
ACAMERA_LENS_START + 12,
+ /**
+ * <p>The correction coefficients to correct for this camera device's
+ * radial and tangential lens distortion.</p>
+ * <p>Replaces the deprecated ACAMERA_LENS_RADIAL_DISTORTION field, which was
+ * inconsistently defined.</p>
+ *
+ * @see ACAMERA_LENS_RADIAL_DISTORTION
+ *
+ * <p>Type: float[5]</p>
+ *
+ * <p>This tag may appear in:
+ * <ul>
+ * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li>
+ * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li>
+ * </ul></p>
+ *
+ * <p>Three radial distortion coefficients <code>[kappa_1, kappa_2,
+ * kappa_3]</code> and two tangential distortion coefficients
+ * <code>[kappa_4, kappa_5]</code> that can be used to correct the
+ * lens's geometric distortion with the mapping equations:</p>
+ * <pre><code> x_c = x_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) +
+ * kappa_4 * (2 * x_i * y_i) + kappa_5 * ( r^2 + 2 * x_i^2 )
+ * y_c = y_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 ) +
+ * kappa_5 * (2 * x_i * y_i) + kappa_4 * ( r^2 + 2 * y_i^2 )
+ * </code></pre>
+ * <p>Here, <code>[x_c, y_c]</code> are the coordinates to sample in the
+ * input image that correspond to the pixel values in the
+ * corrected image at the coordinate <code>[x_i, y_i]</code>:</p>
+ * <pre><code> correctedImage(x_i, y_i) = sample_at(x_c, y_c, inputImage)
+ * </code></pre>
+ * <p>The pixel coordinates are defined in a coordinate system
+ * related to the ACAMERA_LENS_INTRINSIC_CALIBRATION
+ * calibration fields; see that entry for details of the mapping stages.
+ * Both <code>[x_i, y_i]</code> and <code>[x_c, y_c]</code>
+ * have <code>(0,0)</code> at the lens optical center <code>[c_x, c_y]</code>, and
+ * the range of the coordinates depends on the focal length
+ * terms of the intrinsic calibration.</p>
+ * <p>Finally, <code>r</code> represents the radial distance from the
+ * optical center, <code>r^2 = x_i^2 + y_i^2</code>.</p>
+ * <p>The distortion model used is the Brown-Conrady model.</p>
+ *
+ * @see ACAMERA_LENS_INTRINSIC_CALIBRATION
+ */
+ ACAMERA_LENS_DISTORTION = // float[5]
+ ACAMERA_LENS_START + 13,
ACAMERA_LENS_END,
/**
@@ -4211,7 +4216,7 @@
* ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE.</p>
* <p>The currently supported fields that correct for geometric distortion are:</p>
* <ol>
- * <li>ACAMERA_LENS_RADIAL_DISTORTION.</li>
+ * <li>ACAMERA_LENS_DISTORTION.</li>
* </ol>
* <p>If all of the geometric distortion fields are no-ops, this rectangle will be the same
* as the post-distortion-corrected rectangle given in
@@ -4223,7 +4228,7 @@
* full array may include black calibration pixels or other inactive regions.</p>
* <p>The data representation is <code>int[4]</code>, which maps to <code>(left, top, width, height)</code>.</p>
*
- * @see ACAMERA_LENS_RADIAL_DISTORTION
+ * @see ACAMERA_LENS_DISTORTION
* @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
* @see ACAMERA_SENSOR_INFO_PIXEL_ARRAY_SIZE
* @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
@@ -5500,9 +5505,11 @@
* for the external flash. Otherwise, this mode acts like ON.</p>
* <p>When the external flash is turned off, AE mode should be changed to one of the
* other available AE modes.</p>
- * <p>If the camera device supports AE external flash mode, aeState must be
- * FLASH_REQUIRED after the camera device finishes AE scan and it's too dark without
+ * <p>If the camera device supports AE external flash mode, ACAMERA_CONTROL_AE_STATE must
+ * be FLASH_REQUIRED after the camera device finishes AE scan and it's too dark without
* flash.</p>
+ *
+ * @see ACAMERA_CONTROL_AE_STATE
*/
ACAMERA_CONTROL_AE_MODE_ON_EXTERNAL_FLASH = 5,
@@ -6938,7 +6945,7 @@
* <li>ACAMERA_LENS_POSE_TRANSLATION</li>
* <li>ACAMERA_LENS_POSE_ROTATION</li>
* <li>ACAMERA_LENS_INTRINSIC_CALIBRATION</li>
- * <li>ACAMERA_LENS_RADIAL_DISTORTION</li>
+ * <li>ACAMERA_LENS_DISTORTION</li>
* </ul>
* </li>
* <li>The ACAMERA_DEPTH_DEPTH_IS_EXCLUSIVE entry is listed by this device.</li>
@@ -6956,12 +6963,12 @@
* rate, including depth stall time.</p>
*
* @see ACAMERA_DEPTH_DEPTH_IS_EXCLUSIVE
+ * @see ACAMERA_LENS_DISTORTION
* @see ACAMERA_LENS_FACING
* @see ACAMERA_LENS_INTRINSIC_CALIBRATION
* @see ACAMERA_LENS_POSE_REFERENCE
* @see ACAMERA_LENS_POSE_ROTATION
* @see ACAMERA_LENS_POSE_TRANSLATION
- * @see ACAMERA_LENS_RADIAL_DISTORTION
*/
ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT = 8,
@@ -6991,7 +6998,7 @@
* <li>ACAMERA_LENS_POSE_ROTATION</li>
* <li>ACAMERA_LENS_POSE_TRANSLATION</li>
* <li>ACAMERA_LENS_INTRINSIC_CALIBRATION</li>
- * <li>ACAMERA_LENS_RADIAL_DISTORTION</li>
+ * <li>ACAMERA_LENS_DISTORTION</li>
* </ul>
* </li>
* <li>The SENSOR_INFO_TIMESTAMP_SOURCE of the logical device and physical devices must be
@@ -7017,11 +7024,11 @@
* not slow down the frame rate of the capture, as long as the minimum frame duration
* of the physical and logical streams are the same.</p>
*
+ * @see ACAMERA_LENS_DISTORTION
* @see ACAMERA_LENS_INTRINSIC_CALIBRATION
* @see ACAMERA_LENS_POSE_REFERENCE
* @see ACAMERA_LENS_POSE_ROTATION
* @see ACAMERA_LENS_POSE_TRANSLATION
- * @see ACAMERA_LENS_RADIAL_DISTORTION
* @see ACAMERA_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE
*/
ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA = 11,
diff --git a/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp b/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp
index 6b0201a..300c688 100644
--- a/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp
+++ b/drm/mediadrm/plugins/clearkey/hidl/DrmPlugin.cpp
@@ -19,6 +19,7 @@
#include <utils/Log.h>
#include <stdio.h>
+#include <inttypes.h>
#include "DrmPlugin.h"
#include "ClearKeyDrmProperties.h"
@@ -26,6 +27,7 @@
#include "TypeConvert.h"
namespace {
+const int kSecureStopIdStart = 100;
const std::string kStreaming("Streaming");
const std::string kOffline("Offline");
const std::string kTrue("True");
@@ -36,7 +38,18 @@
// Value: "True" or "False"
const std::string kQueryKeyRenewAllowed("RenewAllowed");
// Value: "True" or "False"
-};
+
+const int kSecureStopIdSize = 10;
+
+std::vector<uint8_t> uint32ToVector(uint32_t value) {
+ // 10 bytes to display max value 4294967295 + one byte null terminator
+ char buffer[kSecureStopIdSize];
+ memset(buffer, 0, kSecureStopIdSize);
+ snprintf(buffer, kSecureStopIdSize, "%" PRIu32, value);
+ return std::vector<uint8_t>(buffer, buffer + sizeof(buffer));
+}
+
+}; // unnamed namespace
namespace android {
namespace hardware {
@@ -48,9 +61,11 @@
: mSessionLibrary(sessionLibrary),
mOpenSessionOkCount(0),
mCloseSessionOkCount(0),
- mCloseSessionNotOpenedCount(0) {
+ mCloseSessionNotOpenedCount(0),
+ mNextSecureStopId(kSecureStopIdStart) {
mPlayPolicy.clear();
initProperties();
+ mSecureStops.clear();
}
void DrmPlugin::initProperties() {
@@ -73,6 +88,18 @@
mByteArrayProperties[kMetricsKey] = valueVector;
}
+// The secure stop in ClearKey implementation is not installed securely.
+// This function merely creates a test environment for testing secure stops APIs.
+// The content in this secure stop is implementation dependent, the clearkey
+// secureStop does not serve as a reference implementation.
+void DrmPlugin::installSecureStop(const hidl_vec<uint8_t>& sessionId) {
+ ClearkeySecureStop clearkeySecureStop;
+ clearkeySecureStop.id = uint32ToVector(++mNextSecureStopId);
+ clearkeySecureStop.data.assign(sessionId.begin(), sessionId.end());
+
+ mSecureStops.insert(std::pair<std::vector<uint8_t>, ClearkeySecureStop>(
+ clearkeySecureStop.id, clearkeySecureStop));
+}
Return<void> DrmPlugin::openSession(openSession_cb _hidl_cb) {
sp<Session> session = mSessionLibrary->createSession();
@@ -209,6 +236,7 @@
_hidl_cb(Status::BAD_VALUE, hidl_vec<uint8_t>());
return Void();
}
+
sp<Session> session = mSessionLibrary->findSession(toVector(scope));
if (!session.get()) {
_hidl_cb(Status::ERROR_DRM_SESSION_NOT_OPENED, hidl_vec<uint8_t>());
@@ -224,6 +252,8 @@
keySetId.clear();
}
+ installSecureStop(scope);
+
// Returns status and empty keySetId
_hidl_cb(status, toHidlVec(keySetId));
return Void();
@@ -435,7 +465,106 @@
return Void();
}
+Return<void> DrmPlugin::getSecureStops(getSecureStops_cb _hidl_cb) {
+ std::vector<SecureStop> stops;
+ for (auto itr = mSecureStops.begin(); itr != mSecureStops.end(); ++itr) {
+ ClearkeySecureStop clearkeyStop = itr->second;
+ std::vector<uint8_t> stopVec;
+ stopVec.insert(stopVec.end(), clearkeyStop.id.begin(), clearkeyStop.id.end());
+ stopVec.insert(stopVec.end(), clearkeyStop.data.begin(), clearkeyStop.data.end());
+ SecureStop stop;
+ stop.opaqueData = toHidlVec(stopVec);
+ stops.push_back(stop);
+ }
+ _hidl_cb(Status::OK, stops);
+ return Void();
+}
+
+Return<void> DrmPlugin::getSecureStop(const hidl_vec<uint8_t>& secureStopId,
+ getSecureStop_cb _hidl_cb) {
+ SecureStop stop;
+ auto itr = mSecureStops.find(toVector(secureStopId));
+ if (itr != mSecureStops.end()) {
+ ClearkeySecureStop clearkeyStop = itr->second;
+ std::vector<uint8_t> stopVec;
+ stopVec.insert(stopVec.end(), clearkeyStop.id.begin(), clearkeyStop.id.end());
+ stopVec.insert(stopVec.end(), clearkeyStop.data.begin(), clearkeyStop.data.end());
+
+ stop.opaqueData = toHidlVec(stopVec);
+ _hidl_cb(Status::OK, stop);
+ } else {
+ _hidl_cb(Status::BAD_VALUE, stop);
+ }
+
+ return Void();
+}
+
+Return<Status> DrmPlugin::releaseSecureStop(const hidl_vec<uint8_t>& secureStopId) {
+ return removeSecureStop(secureStopId);
+}
+
+Return<Status> DrmPlugin::releaseAllSecureStops() {
+ return removeAllSecureStops();
+}
+
+Return<void> DrmPlugin::getSecureStopIds(getSecureStopIds_cb _hidl_cb) {
+ std::vector<SecureStopId> ids;
+ for (auto itr = mSecureStops.begin(); itr != mSecureStops.end(); ++itr) {
+ ids.push_back(itr->first);
+ }
+
+ _hidl_cb(Status::OK, toHidlVec(ids));
+ return Void();
+}
+
+Return<Status> DrmPlugin::releaseSecureStops(const SecureStopRelease& ssRelease) {
+ if (ssRelease.opaqueData.size() == 0) {
+ return Status::BAD_VALUE;
+ }
+
+ Status status = Status::OK;
+ std::vector<uint8_t> input = toVector(ssRelease.opaqueData);
+
+ // The format of opaqueData is shared between the server
+ // and the drm service. The clearkey implementation consists of:
+ // count - number of secure stops
+ // list of fixed length secure stops
+ size_t countBufferSize = sizeof(uint32_t);
+ uint32_t count = 0;
+ sscanf(reinterpret_cast<char*>(input.data()), "%04" PRIu32, &count);
+
+ // Avoid divide by 0 below.
+ if (count == 0) {
+ return Status::BAD_VALUE;
+ }
+
+ size_t secureStopSize = (input.size() - countBufferSize) / count;
+ uint8_t buffer[secureStopSize];
+ size_t offset = countBufferSize; // skip the count
+ for (size_t i = 0; i < count; ++i, offset += secureStopSize) {
+ memcpy(buffer, input.data() + offset, secureStopSize);
+ std::vector<uint8_t> id(buffer, buffer + kSecureStopIdSize);
+
+ status = removeSecureStop(toHidlVec(id));
+ if (Status::OK != status) break;
+ }
+
+ return status;
+}
+
+Return<Status> DrmPlugin::removeSecureStop(const hidl_vec<uint8_t>& secureStopId) {
+ if (1 != mSecureStops.erase(toVector(secureStopId))) {
+ return Status::BAD_VALUE;
+ }
+ return Status::OK;
+}
+
+Return<Status> DrmPlugin::removeAllSecureStops() {
+ mSecureStops.clear();
+ mNextSecureStopId = kSecureStopIdStart;
+ return Status::OK;
+}
} // namespace clearkey
} // namespace V1_1
diff --git a/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h b/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h
index 19baf0b..fb0695a 100644
--- a/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h
+++ b/drm/mediadrm/plugins/clearkey/hidl/include/DrmPlugin.h
@@ -17,9 +17,11 @@
#ifndef CLEARKEY_DRM_PLUGIN_H_
#define CLEARKEY_DRM_PLUGIN_H_
-
#include <android/hardware/drm/1.1/IDrmPlugin.h>
+#include <stdio.h>
+#include <map>
+
#include "SessionLibrary.h"
#include "Utils.h"
@@ -36,6 +38,7 @@
using ::android::hardware::drm::V1_0::KeyValue;
using ::android::hardware::drm::V1_0::SecureStop;
using ::android::hardware::drm::V1_0::SecureStopId;
+using ::android::hardware::drm::V1_0::SessionId;
using ::android::hardware::drm::V1_0::Status;
using ::android::hardware::drm::V1_1::DrmMetricGroup;
using ::android::hardware::drm::V1_1::IDrmPlugin;
@@ -124,34 +127,6 @@
return Void();
}
- Return<void> getSecureStops(getSecureStops_cb _hidl_cb) {
- _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, hidl_vec<SecureStop>());
- return Void();
- }
-
- Return<void> getSecureStop(
- const hidl_vec<uint8_t>& secureStopId,
- getSecureStop_cb _hidl_cb) {
-
- if (secureStopId.size() == 0) {
- _hidl_cb(Status::BAD_VALUE, SecureStop());
- return Void();
- }
- _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, SecureStop());
- return Void();
- }
-
- Return<Status> releaseSecureStop(const hidl_vec<uint8_t>& ssRelease) {
- if (ssRelease.size() == 0) {
- return Status::BAD_VALUE;
- }
- return Status::ERROR_DRM_CANNOT_HANDLE;
- }
-
- Return<Status> releaseAllSecureStops() {
- return Status::ERROR_DRM_CANNOT_HANDLE;
- }
-
Return<void> getHdcpLevels(getHdcpLevels_cb _hidl_cb) {
HdcpLevel connectedLevel = HdcpLevel::HDCP_NONE;
HdcpLevel maxLevel = HdcpLevel::HDCP_NO_OUTPUT;
@@ -305,31 +280,26 @@
return Void();
}
- Return<void> getSecureStopIds(getSecureStopIds_cb _hidl_cb) {
- _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, hidl_vec<SecureStopId>());
- return Void();
- }
+ Return<void> getSecureStops(getSecureStops_cb _hidl_cb);
- Return<Status> releaseSecureStops(const SecureStopRelease& ssRelease) {
- if (ssRelease.opaqueData.size() == 0) {
- return Status::BAD_VALUE;
- }
- return Status::ERROR_DRM_CANNOT_HANDLE;
- }
+ Return<void> getSecureStop(const hidl_vec<uint8_t>& secureStopId,
+ getSecureStop_cb _hidl_cb);
- Return<Status> removeSecureStop(const hidl_vec<uint8_t>& secureStopId) {
- if (secureStopId.size() == 0) {
- return Status::BAD_VALUE;
- }
- return Status::ERROR_DRM_CANNOT_HANDLE;
- }
+ Return<Status> releaseSecureStop(const hidl_vec<uint8_t>& ssRelease);
- Return<Status> removeAllSecureStops() {
- return Status::ERROR_DRM_CANNOT_HANDLE;
- }
+ Return<Status> releaseAllSecureStops();
+
+ Return<void> getSecureStopIds(getSecureStopIds_cb _hidl_cb);
+
+ Return<Status> releaseSecureStops(const SecureStopRelease& ssRelease);
+
+ Return<Status> removeSecureStop(const hidl_vec<uint8_t>& secureStopId);
+
+ Return<Status> removeAllSecureStops();
private:
void initProperties();
+ void installSecureStop(const hidl_vec<uint8_t>& sessionId);
void setPlayPolicy();
Return<Status> setSecurityLevel(const hidl_vec<uint8_t>& sessionId,
@@ -344,6 +314,12 @@
KeyRequestType *getKeyRequestType,
std::string *defaultUrl);
+ struct ClearkeySecureStop {
+ std::vector<uint8_t> id;
+ std::vector<uint8_t> data;
+ };
+
+ std::map<std::vector<uint8_t>, ClearkeySecureStop> mSecureStops;
std::vector<KeyValue> mPlayPolicy;
std::map<std::string, std::string> mStringProperties;
std::map<std::string, std::vector<uint8_t> > mByteArrayProperties;
@@ -353,6 +329,7 @@
int64_t mOpenSessionOkCount;
int64_t mCloseSessionOkCount;
int64_t mCloseSessionNotOpenedCount;
+ uint32_t mNextSecureStopId;
CLEARKEY_DISALLOW_COPY_AND_ASSIGN_AND_NEW(DrmPlugin);
};
diff --git a/include/media/ExtractorUtils.h b/include/media/ExtractorUtils.h
new file mode 120000
index 0000000..e2dd082
--- /dev/null
+++ b/include/media/ExtractorUtils.h
@@ -0,0 +1 @@
+../../media/libmediaextractor/include/media/ExtractorUtils.h
\ No newline at end of file
diff --git a/media/extractors/mkv/MatroskaExtractor.cpp b/media/extractors/mkv/MatroskaExtractor.cpp
index 3f832bc..fc60fd4 100644
--- a/media/extractors/mkv/MatroskaExtractor.cpp
+++ b/media/extractors/mkv/MatroskaExtractor.cpp
@@ -22,6 +22,7 @@
#include "MatroskaExtractor.h"
#include <media/DataSourceBase.h>
+#include <media/ExtractorUtils.h>
#include <media/MediaTrack.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AUtils.h>
@@ -1108,7 +1109,7 @@
meta.setData(kKeyFlacMetadata, 0, codecPrivate, codecPrivateSize);
int32_t maxInputSize = 64 << 10;
- sp<FLACDecoder> flacDecoder = FLACDecoder::Create();
+ FLACDecoder *flacDecoder = FLACDecoder::Create();
if (flacDecoder != NULL
&& flacDecoder->parseMetadata((const uint8_t*)codecPrivate, codecPrivateSize) == OK) {
FLAC__StreamMetadata_StreamInfo streamInfo = flacDecoder->getStreamInfo();
@@ -1120,6 +1121,7 @@
&& streamInfo.channels != 0
&& ((streamInfo.bits_per_sample + 7) / 8) >
INT32_MAX / streamInfo.max_blocksize / streamInfo.channels) {
+ delete flacDecoder;
return ERROR_MALFORMED;
}
maxInputSize = ((streamInfo.bits_per_sample + 7) / 8)
@@ -1128,6 +1130,7 @@
}
meta.setInt32(kKeyMaxInputSize, maxInputSize);
+ delete flacDecoder;
return OK;
}
@@ -1143,13 +1146,13 @@
}
const mkvparser::Block::Frame &frame = block->GetFrame(0);
- sp<ABuffer> abuf = new ABuffer(frame.len);
- long n = frame.Read(mReader, abuf->data());
+ auto tmpData = heapbuffer<unsigned char>(frame.len);
+ long n = frame.Read(mReader, tmpData.get());
if (n != 0) {
return ERROR_MALFORMED;
}
- if (!MakeAVCCodecSpecificData(trackInfo->mMeta, abuf)) {
+ if (!MakeAVCCodecSpecificData(trackInfo->mMeta, tmpData.get(), frame.len)) {
return ERROR_MALFORMED;
}
diff --git a/media/extractors/mp4/ItemTable.cpp b/media/extractors/mp4/ItemTable.cpp
index c082fc1..666d68a 100644
--- a/media/extractors/mp4/ItemTable.cpp
+++ b/media/extractors/mp4/ItemTable.cpp
@@ -1471,8 +1471,8 @@
// point image to the first tile for grid size and HVCC
image = &mItemIdToItemMap.editValueAt(tileItemIndex);
- meta->setInt32(kKeyGridWidth, image->width);
- meta->setInt32(kKeyGridHeight, image->height);
+ meta->setInt32(kKeyTileWidth, image->width);
+ meta->setInt32(kKeyTileHeight, image->height);
meta->setInt32(kKeyMaxInputSize, image->width * image->height * 1.5);
}
diff --git a/media/extractors/mp4/MPEG4Extractor.cpp b/media/extractors/mp4/MPEG4Extractor.cpp
index 78d2ac6..a9f66fa 100644
--- a/media/extractors/mp4/MPEG4Extractor.cpp
+++ b/media/extractors/mp4/MPEG4Extractor.cpp
@@ -31,6 +31,7 @@
#include "ItemTable.h"
#include "include/ESDS.h"
+#include <media/ExtractorUtils.h>
#include <media/MediaTrack.h>
#include <media/stagefright/foundation/ABitReader.h>
#include <media/stagefright/foundation/ABuffer.h>
@@ -1324,17 +1325,17 @@
if (mLastTrack == NULL)
return ERROR_MALFORMED;
- sp<ABuffer> buffer = new ABuffer(chunk_data_size);
- if (buffer->data() == NULL) {
+ auto buffer = heapbuffer<uint8_t>(chunk_data_size);
+ if (buffer.get() == NULL) {
return NO_MEMORY;
}
if (mDataSource->readAt(
- data_offset, buffer->data(), chunk_data_size) < chunk_data_size) {
+ data_offset, buffer.get(), chunk_data_size) < chunk_data_size) {
return ERROR_IO;
}
- String8 mimeFormat((const char *)(buffer->data()), chunk_data_size);
+ String8 mimeFormat((const char *)(buffer.get()), chunk_data_size);
mLastTrack->meta.setCString(kKeyMIMEType, mimeFormat.string());
break;
@@ -1833,15 +1834,15 @@
{
*offset += chunk_size;
- sp<ABuffer> buffer = new ABuffer(chunk_data_size);
+ auto buffer = heapbuffer<uint8_t>(chunk_data_size);
- if (buffer->data() == NULL) {
+ if (buffer.get() == NULL) {
ALOGE("b/28471206");
return NO_MEMORY;
}
if (mDataSource->readAt(
- data_offset, buffer->data(), chunk_data_size) < chunk_data_size) {
+ data_offset, buffer.get(), chunk_data_size) < chunk_data_size) {
return ERROR_IO;
}
@@ -1849,21 +1850,21 @@
return ERROR_MALFORMED;
mLastTrack->meta.setData(
- kKeyAVCC, kTypeAVCC, buffer->data(), chunk_data_size);
+ kKeyAVCC, kTypeAVCC, buffer.get(), chunk_data_size);
break;
}
case FOURCC('h', 'v', 'c', 'C'):
{
- sp<ABuffer> buffer = new ABuffer(chunk_data_size);
+ auto buffer = heapbuffer<uint8_t>(chunk_data_size);
- if (buffer->data() == NULL) {
+ if (buffer.get() == NULL) {
ALOGE("b/28471206");
return NO_MEMORY;
}
if (mDataSource->readAt(
- data_offset, buffer->data(), chunk_data_size) < chunk_data_size) {
+ data_offset, buffer.get(), chunk_data_size) < chunk_data_size) {
return ERROR_IO;
}
@@ -1871,7 +1872,7 @@
return ERROR_MALFORMED;
mLastTrack->meta.setData(
- kKeyHVCC, kTypeHVCC, buffer->data(), chunk_data_size);
+ kKeyHVCC, kTypeHVCC, buffer.get(), chunk_data_size);
*offset += chunk_size;
break;
@@ -2212,13 +2213,13 @@
if (chunk_data_size < 0 || static_cast<uint64_t>(chunk_data_size) >= SIZE_MAX - 1) {
return ERROR_MALFORMED;
}
- sp<ABuffer> buffer = new ABuffer(chunk_data_size + 1);
- if (buffer->data() == NULL) {
+ auto buffer = heapbuffer<uint8_t>(chunk_data_size);
+ if (buffer.get() == NULL) {
ALOGE("b/28471206");
return NO_MEMORY;
}
if (mDataSource->readAt(
- data_offset, buffer->data(), chunk_data_size) != (ssize_t)chunk_data_size) {
+ data_offset, buffer.get(), chunk_data_size) != (ssize_t)chunk_data_size) {
return ERROR_IO;
}
const int kSkipBytesOfDataBox = 16;
@@ -2228,7 +2229,7 @@
mFileMetaData.setData(
kKeyAlbumArt, MetaData::TYPE_NONE,
- buffer->data() + kSkipBytesOfDataBox, chunk_data_size - kSkipBytesOfDataBox);
+ buffer.get() + kSkipBytesOfDataBox, chunk_data_size - kSkipBytesOfDataBox);
break;
}
@@ -2626,16 +2627,16 @@
keySize -= 8;
keyOffset += 8;
- sp<ABuffer> keyData = new ABuffer(keySize);
- if (keyData->data() == NULL) {
+ auto keyData = heapbuffer<uint8_t>(keySize);
+ if (keyData.get() == NULL) {
return ERROR_MALFORMED;
}
if (mDataSource->readAt(
- keyOffset, keyData->data(), keySize) < (ssize_t) keySize) {
+ keyOffset, keyData.get(), keySize) < (ssize_t) keySize) {
return ERROR_MALFORMED;
}
- AString key((const char *)keyData->data(), keySize);
+ AString key((const char *)keyData.get(), keySize);
mMetaKeyMap.add(i, key);
keyOffset += keySize;
diff --git a/media/extractors/ogg/OggExtractor.cpp b/media/extractors/ogg/OggExtractor.cpp
index 4d49013..b2fe69c 100644
--- a/media/extractors/ogg/OggExtractor.cpp
+++ b/media/extractors/ogg/OggExtractor.cpp
@@ -22,6 +22,7 @@
#include <cutils/properties.h>
#include <media/DataSourceBase.h>
+#include <media/ExtractorUtils.h>
#include <media/MediaTrack.h>
#include <media/VorbisComment.h>
#include <media/stagefright/foundation/ABuffer.h>
@@ -990,21 +991,11 @@
return OK;
}
-struct TmpData {
- uint8_t *data;
- TmpData(size_t size) {
- data = (uint8_t*) malloc(size);
- }
- ~TmpData() {
- free(data);
- }
-};
-
status_t MyOpusExtractor::verifyOpusComments(MediaBufferBase *buffer) {
// add artificial framing bit so we can reuse _vorbis_unpack_comment
int32_t commentSize = buffer->range_length() + 1;
- TmpData commentDataHolder(commentSize);
- uint8_t *commentData = commentDataHolder.data;
+ auto tmp = heapbuffer<uint8_t>(commentSize);
+ uint8_t *commentData = tmp.get();
if (commentData == nullptr) {
return ERROR_MALFORMED;
}
diff --git a/media/libaaudio/src/binding/AudioEndpointParcelable.cpp b/media/libaaudio/src/binding/AudioEndpointParcelable.cpp
index 9eed96d..61d7d27 100644
--- a/media/libaaudio/src/binding/AudioEndpointParcelable.cpp
+++ b/media/libaaudio/src/binding/AudioEndpointParcelable.cpp
@@ -64,27 +64,54 @@
* The read and write must be symmetric.
*/
status_t AudioEndpointParcelable::writeToParcel(Parcel* parcel) const {
- parcel->writeInt32(mNumSharedMemories);
+ status_t status = AAudioConvert_aaudioToAndroidStatus(validate());
+ if (status != NO_ERROR) goto error;
+
+ status = parcel->writeInt32(mNumSharedMemories);
+ if (status != NO_ERROR) goto error;
+
for (int i = 0; i < mNumSharedMemories; i++) {
- mSharedMemories[i].writeToParcel(parcel);
+ status = mSharedMemories[i].writeToParcel(parcel);
+ if (status != NO_ERROR) goto error;
}
- mUpMessageQueueParcelable.writeToParcel(parcel);
- mDownMessageQueueParcelable.writeToParcel(parcel);
- mUpDataQueueParcelable.writeToParcel(parcel);
- mDownDataQueueParcelable.writeToParcel(parcel);
- return NO_ERROR; // TODO check for errors above
+ status = mUpMessageQueueParcelable.writeToParcel(parcel);
+ if (status != NO_ERROR) goto error;
+ status = mDownMessageQueueParcelable.writeToParcel(parcel);
+ if (status != NO_ERROR) goto error;
+ status = mUpDataQueueParcelable.writeToParcel(parcel);
+ if (status != NO_ERROR) goto error;
+ status = mDownDataQueueParcelable.writeToParcel(parcel);
+ if (status != NO_ERROR) goto error;
+
+ return NO_ERROR;
+
+error:
+ ALOGE("%s returning %d", __func__, status);
+ return status;
}
status_t AudioEndpointParcelable::readFromParcel(const Parcel* parcel) {
- parcel->readInt32(&mNumSharedMemories);
+ status_t status = parcel->readInt32(&mNumSharedMemories);
+ if (status != NO_ERROR) goto error;
+
for (int i = 0; i < mNumSharedMemories; i++) {
mSharedMemories[i].readFromParcel(parcel);
+ if (status != NO_ERROR) goto error;
}
- mUpMessageQueueParcelable.readFromParcel(parcel);
- mDownMessageQueueParcelable.readFromParcel(parcel);
- mUpDataQueueParcelable.readFromParcel(parcel);
- mDownDataQueueParcelable.readFromParcel(parcel);
- return NO_ERROR; // TODO check for errors above
+ status = mUpMessageQueueParcelable.readFromParcel(parcel);
+ if (status != NO_ERROR) goto error;
+ status = mDownMessageQueueParcelable.readFromParcel(parcel);
+ if (status != NO_ERROR) goto error;
+ status = mUpDataQueueParcelable.readFromParcel(parcel);
+ if (status != NO_ERROR) goto error;
+ status = mDownDataQueueParcelable.readFromParcel(parcel);
+ if (status != NO_ERROR) goto error;
+
+ return AAudioConvert_aaudioToAndroidStatus(validate());
+
+error:
+ ALOGE("%s returning %d", __func__, status);
+ return status;
}
aaudio_result_t AudioEndpointParcelable::resolve(EndpointDescriptor *descriptor) {
@@ -109,35 +136,11 @@
return AAudioConvert_androidToAAudioResult(err);
}
-aaudio_result_t AudioEndpointParcelable::validate() {
- aaudio_result_t result;
+aaudio_result_t AudioEndpointParcelable::validate() const {
if (mNumSharedMemories < 0 || mNumSharedMemories >= MAX_SHARED_MEMORIES) {
ALOGE("invalid mNumSharedMemories = %d", mNumSharedMemories);
return AAUDIO_ERROR_INTERNAL;
}
- for (int i = 0; i < mNumSharedMemories; i++) {
- result = mSharedMemories[i].validate();
- if (result != AAUDIO_OK) {
- ALOGE("invalid mSharedMemories[%d] = %d", i, result);
- return result;
- }
- }
- if ((result = mUpMessageQueueParcelable.validate()) != AAUDIO_OK) {
- ALOGE("invalid mUpMessageQueueParcelable = %d", result);
- return result;
- }
- if ((result = mDownMessageQueueParcelable.validate()) != AAUDIO_OK) {
- ALOGE("invalid mDownMessageQueueParcelable = %d", result);
- return result;
- }
- if ((result = mUpDataQueueParcelable.validate()) != AAUDIO_OK) {
- ALOGE("invalid mUpDataQueueParcelable = %d", result);
- return result;
- }
- if ((result = mDownDataQueueParcelable.validate()) != AAUDIO_OK) {
- ALOGE("invalid mDownDataQueueParcelable = %d", result);
- return result;
- }
return AAUDIO_OK;
}
diff --git a/media/libaaudio/src/binding/AudioEndpointParcelable.h b/media/libaaudio/src/binding/AudioEndpointParcelable.h
index aa8573f..e4f8b9e 100644
--- a/media/libaaudio/src/binding/AudioEndpointParcelable.h
+++ b/media/libaaudio/src/binding/AudioEndpointParcelable.h
@@ -56,8 +56,6 @@
aaudio_result_t resolve(EndpointDescriptor *descriptor);
- aaudio_result_t validate();
-
aaudio_result_t close();
void dump();
@@ -70,6 +68,8 @@
RingBufferParcelable mDownDataQueueParcelable; // eg. playback
private:
+ aaudio_result_t validate() const;
+
int32_t mNumSharedMemories = 0;
SharedMemoryParcelable mSharedMemories[MAX_SHARED_MEMORIES];
};
diff --git a/media/libaaudio/src/binding/IAAudioService.cpp b/media/libaaudio/src/binding/IAAudioService.cpp
index b3c4934..620edc7 100644
--- a/media/libaaudio/src/binding/IAAudioService.cpp
+++ b/media/libaaudio/src/binding/IAAudioService.cpp
@@ -121,17 +121,11 @@
ALOGE("BpAAudioService::client GET_STREAM_DESCRIPTION passed result %d", result);
return result;
}
- err = parcelable.readFromParcel(&reply);;
+ err = parcelable.readFromParcel(&reply);
if (err != NO_ERROR) {
ALOGE("BpAAudioService::client transact(GET_STREAM_DESCRIPTION) read endpoint %d", err);
return AAudioConvert_androidToAAudioResult(err);
}
- //parcelable.dump();
- result = parcelable.validate();
- if (result != AAUDIO_OK) {
- ALOGE("BpAAudioService::client GET_STREAM_DESCRIPTION validation fails %d", result);
- return result;
- }
return result;
}
@@ -250,6 +244,7 @@
pid_t tid;
int64_t nanoseconds;
aaudio_result_t result;
+ status_t status = NO_ERROR;
ALOGV("BnAAudioService::onTransact(%i) %i", code, flags);
switch(code) {
@@ -294,21 +289,20 @@
case GET_STREAM_DESCRIPTION: {
CHECK_INTERFACE(IAAudioService, data, reply);
- data.readInt32(&streamHandle);
+ status = data.readInt32(&streamHandle);
+ if (status != NO_ERROR) {
+ return status;
+ }
aaudio::AudioEndpointParcelable parcelable;
result = getStreamDescription(streamHandle, parcelable);
if (result != AAUDIO_OK) {
return AAudioConvert_aaudioToAndroidStatus(result);
}
- result = parcelable.validate();
- if (result != AAUDIO_OK) {
- ALOGE("BnAAudioService::onTransact getStreamDescription() returns %d", result);
- parcelable.dump();
- return AAudioConvert_aaudioToAndroidStatus(result);
+ status = reply->writeInt32(result);
+ if (status != NO_ERROR) {
+ return status;
}
- reply->writeInt32(result);
- parcelable.writeToParcel(reply);
- return NO_ERROR;
+ return parcelable.writeToParcel(reply);
} break;
case START_STREAM: {
diff --git a/media/libaaudio/src/binding/RingBufferParcelable.cpp b/media/libaaudio/src/binding/RingBufferParcelable.cpp
index 2babbff..4996b3f 100644
--- a/media/libaaudio/src/binding/RingBufferParcelable.cpp
+++ b/media/libaaudio/src/binding/RingBufferParcelable.cpp
@@ -21,6 +21,7 @@
#include <stdint.h>
#include <binder/Parcelable.h>
+#include <utility/AAudioUtilities.h>
#include "binding/AAudioServiceDefinitions.h"
#include "binding/SharedRegionParcelable.h"
@@ -79,7 +80,10 @@
* The read and write must be symmetric.
*/
status_t RingBufferParcelable::writeToParcel(Parcel* parcel) const {
- status_t status = parcel->writeInt32(mCapacityInFrames);
+ status_t status = AAudioConvert_aaudioToAndroidStatus(validate());
+ if (status != NO_ERROR) goto error;
+
+ status = parcel->writeInt32(mCapacityInFrames);
if (status != NO_ERROR) goto error;
if (mCapacityInFrames > 0) {
status = parcel->writeInt32(mBytesPerFrame);
@@ -97,7 +101,7 @@
}
return NO_ERROR;
error:
- ALOGE("writeToParcel() error = %d", status);
+ ALOGE("%s returning %d", __func__, status);
return status;
}
@@ -118,9 +122,9 @@
status = mDataParcelable.readFromParcel(parcel);
if (status != NO_ERROR) goto error;
}
- return NO_ERROR;
+ return AAudioConvert_aaudioToAndroidStatus(validate());
error:
- ALOGE("readFromParcel() error = %d", status);
+ ALOGE("%s returning %d", __func__, status);
return status;
}
@@ -151,8 +155,7 @@
return AAUDIO_OK;
}
-aaudio_result_t RingBufferParcelable::validate() {
- aaudio_result_t result;
+aaudio_result_t RingBufferParcelable::validate() const {
if (mCapacityInFrames < 0 || mCapacityInFrames >= 32 * 1024) {
ALOGE("invalid mCapacityInFrames = %d", mCapacityInFrames);
return AAUDIO_ERROR_INTERNAL;
@@ -165,18 +168,6 @@
ALOGE("invalid mFramesPerBurst = %d", mFramesPerBurst);
return AAUDIO_ERROR_INTERNAL;
}
- if ((result = mReadCounterParcelable.validate()) != AAUDIO_OK) {
- ALOGE("invalid mReadCounterParcelable = %d", result);
- return result;
- }
- if ((result = mWriteCounterParcelable.validate()) != AAUDIO_OK) {
- ALOGE("invalid mWriteCounterParcelable = %d", result);
- return result;
- }
- if ((result = mDataParcelable.validate()) != AAUDIO_OK) {
- ALOGE("invalid mDataParcelable = %d", result);
- return result;
- }
return AAUDIO_OK;
}
diff --git a/media/libaaudio/src/binding/RingBufferParcelable.h b/media/libaaudio/src/binding/RingBufferParcelable.h
index bd562f2..1dbcf07 100644
--- a/media/libaaudio/src/binding/RingBufferParcelable.h
+++ b/media/libaaudio/src/binding/RingBufferParcelable.h
@@ -66,11 +66,12 @@
aaudio_result_t resolve(SharedMemoryParcelable *memoryParcels, RingBufferDescriptor *descriptor);
- aaudio_result_t validate();
-
void dump();
private:
+
+ aaudio_result_t validate() const;
+
SharedRegionParcelable mReadCounterParcelable;
SharedRegionParcelable mWriteCounterParcelable;
SharedRegionParcelable mDataParcelable;
diff --git a/media/libaaudio/src/binding/SharedMemoryParcelable.cpp b/media/libaaudio/src/binding/SharedMemoryParcelable.cpp
index 4e3e5d1..0b0cf77 100644
--- a/media/libaaudio/src/binding/SharedMemoryParcelable.cpp
+++ b/media/libaaudio/src/binding/SharedMemoryParcelable.cpp
@@ -48,7 +48,10 @@
}
status_t SharedMemoryParcelable::writeToParcel(Parcel* parcel) const {
- status_t status = parcel->writeInt32(mSizeInBytes);
+ status_t status = AAudioConvert_aaudioToAndroidStatus(validate());
+ if (status != NO_ERROR) return status;
+
+ status = parcel->writeInt32(mSizeInBytes);
if (status != NO_ERROR) return status;
if (mSizeInBytes > 0) {
ALOGV("writeToParcel() mFd = %d, this = %p\n", mFd.get(), this);
@@ -61,21 +64,27 @@
status_t SharedMemoryParcelable::readFromParcel(const Parcel* parcel) {
status_t status = parcel->readInt32(&mSizeInBytes);
- if (status != NO_ERROR) {
- return status;
- }
+ if (status != NO_ERROR) goto error;
+
if (mSizeInBytes > 0) {
// The Parcel owns the file descriptor and will close it later.
unique_fd mmapFd;
status = parcel->readUniqueFileDescriptor(&mmapFd);
if (status != NO_ERROR) {
ALOGE("readFromParcel() readUniqueFileDescriptor() failed : %d", status);
- } else {
- // Resolve the memory now while we still have the FD from the Parcel.
- // Closing the FD will not affect the shared memory once mmap() has been called.
- status = AAudioConvert_androidToAAudioResult(resolveSharedMemory(mmapFd));
+ goto error;
}
+
+ // Resolve the memory now while we still have the FD from the Parcel.
+ // Closing the FD will not affect the shared memory once mmap() has been called.
+ aaudio_result_t result = resolveSharedMemory(mmapFd);
+ status = AAudioConvert_aaudioToAndroidStatus(result);
+ if (status != NO_ERROR) goto error;
}
+
+ return AAudioConvert_aaudioToAndroidStatus(validate());
+
+error:
return status;
}
@@ -136,7 +145,7 @@
return mSizeInBytes;
}
-aaudio_result_t SharedMemoryParcelable::validate() {
+aaudio_result_t SharedMemoryParcelable::validate() const {
if (mSizeInBytes < 0 || mSizeInBytes >= MAX_MMAP_SIZE_BYTES) {
ALOGE("invalid mSizeInBytes = %d", mSizeInBytes);
return AAUDIO_ERROR_OUT_OF_RANGE;
diff --git a/media/libaaudio/src/binding/SharedMemoryParcelable.h b/media/libaaudio/src/binding/SharedMemoryParcelable.h
index 2a634e0..82c2240 100644
--- a/media/libaaudio/src/binding/SharedMemoryParcelable.h
+++ b/media/libaaudio/src/binding/SharedMemoryParcelable.h
@@ -61,8 +61,6 @@
int32_t getSizeInBytes();
- aaudio_result_t validate();
-
void dump();
protected:
@@ -74,6 +72,11 @@
android::base::unique_fd mFd;
int32_t mSizeInBytes = 0;
uint8_t *mResolvedAddress = MMAP_UNRESOLVED_ADDRESS;
+
+private:
+
+ aaudio_result_t validate() const;
+
};
} /* namespace aaudio */
diff --git a/media/libaaudio/src/binding/SharedRegionParcelable.cpp b/media/libaaudio/src/binding/SharedRegionParcelable.cpp
index 7aa80bf..c776116 100644
--- a/media/libaaudio/src/binding/SharedRegionParcelable.cpp
+++ b/media/libaaudio/src/binding/SharedRegionParcelable.cpp
@@ -24,6 +24,7 @@
#include <binder/Parcelable.h>
#include <aaudio/AAudio.h>
+#include <utility/AAudioUtilities.h>
#include "binding/SharedMemoryParcelable.h"
#include "binding/SharedRegionParcelable.h"
@@ -47,21 +48,38 @@
}
status_t SharedRegionParcelable::writeToParcel(Parcel* parcel) const {
- parcel->writeInt32(mSizeInBytes);
+ status_t status = AAudioConvert_aaudioToAndroidStatus(validate());
+ if (status != NO_ERROR) goto error;
+
+ status = parcel->writeInt32(mSizeInBytes);
+ if (status != NO_ERROR) goto error;
if (mSizeInBytes > 0) {
- parcel->writeInt32(mSharedMemoryIndex);
- parcel->writeInt32(mOffsetInBytes);
+ status = parcel->writeInt32(mSharedMemoryIndex);
+ if (status != NO_ERROR) goto error;
+ status = parcel->writeInt32(mOffsetInBytes);
+ if (status != NO_ERROR) goto error;
}
- return NO_ERROR; // TODO check for errors above
+ return NO_ERROR;
+
+error:
+ ALOGE("%s returning %d", __func__, status);
+ return status;
}
status_t SharedRegionParcelable::readFromParcel(const Parcel* parcel) {
- parcel->readInt32(&mSizeInBytes);
+ status_t status = parcel->readInt32(&mSizeInBytes);
+ if (status != NO_ERROR) goto error;
if (mSizeInBytes > 0) {
- parcel->readInt32(&mSharedMemoryIndex);
- parcel->readInt32(&mOffsetInBytes);
+ status = parcel->readInt32(&mSharedMemoryIndex);
+ if (status != NO_ERROR) goto error;
+ status = parcel->readInt32(&mOffsetInBytes);
+ if (status != NO_ERROR) goto error;
}
- return NO_ERROR; // TODO check for errors above
+ return AAudioConvert_aaudioToAndroidStatus(validate());
+
+error:
+ ALOGE("%s returning %d", __func__, status);
+ return status;
}
aaudio_result_t SharedRegionParcelable::resolve(SharedMemoryParcelable *memoryParcels,
@@ -78,7 +96,7 @@
return memoryParcel->resolve(mOffsetInBytes, mSizeInBytes, regionAddressPtr);
}
-aaudio_result_t SharedRegionParcelable::validate() {
+aaudio_result_t SharedRegionParcelable::validate() const {
if (mSizeInBytes < 0 || mSizeInBytes >= MAX_MMAP_SIZE_BYTES) {
ALOGE("invalid mSizeInBytes = %d", mSizeInBytes);
return AAUDIO_ERROR_OUT_OF_RANGE;
diff --git a/media/libaaudio/src/binding/SharedRegionParcelable.h b/media/libaaudio/src/binding/SharedRegionParcelable.h
index f6babfd..0cd8c04 100644
--- a/media/libaaudio/src/binding/SharedRegionParcelable.h
+++ b/media/libaaudio/src/binding/SharedRegionParcelable.h
@@ -47,14 +47,15 @@
bool isFileDescriptorSafe(SharedMemoryParcelable *memoryParcels);
- aaudio_result_t validate();
-
void dump();
protected:
int32_t mSharedMemoryIndex = -1;
int32_t mOffsetInBytes = 0;
int32_t mSizeInBytes = 0;
+
+private:
+ aaudio_result_t validate() const;
};
} /* namespace aaudio */
diff --git a/media/libaaudio/src/utility/AAudioUtilities.cpp b/media/libaaudio/src/utility/AAudioUtilities.cpp
index 40b31b9..4a2a0a8 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.cpp
+++ b/media/libaaudio/src/utility/AAudioUtilities.cpp
@@ -597,16 +597,21 @@
}
int32_t AAudioConvert_framesToBytes(int32_t numFrames,
- int32_t bytesPerFrame,
- int32_t *sizeInBytes) {
- // TODO implement more elegantly
- const int32_t maxChannels = 256; // ridiculously large
- const int32_t maxBytesPerFrame = maxChannels * sizeof(float);
- // Prevent overflow by limiting multiplicands.
- if (bytesPerFrame > maxBytesPerFrame || numFrames > (0x3FFFFFFF / maxBytesPerFrame)) {
+ int32_t bytesPerFrame,
+ int32_t *sizeInBytes) {
+ *sizeInBytes = 0;
+
+ if (numFrames < 0 || bytesPerFrame < 0) {
+ ALOGE("negative size, numFrames = %d, frameSize = %d", numFrames, bytesPerFrame);
+ return AAUDIO_ERROR_OUT_OF_RANGE;
+ }
+
+ // Prevent numeric overflow.
+ if (numFrames > (INT32_MAX / bytesPerFrame)) {
ALOGE("size overflow, numFrames = %d, frameSize = %d", numFrames, bytesPerFrame);
return AAUDIO_ERROR_OUT_OF_RANGE;
}
+
*sizeInBytes = numFrames * bytesPerFrame;
return AAUDIO_OK;
}
diff --git a/media/libaaudio/src/utility/AAudioUtilities.h b/media/libaaudio/src/utility/AAudioUtilities.h
index cea88fb..4b975e8 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.h
+++ b/media/libaaudio/src/utility/AAudioUtilities.h
@@ -196,14 +196,16 @@
/**
* Calculate the number of bytes and prevent numeric overflow.
+ * The *sizeInBytes will be set to zero if there is an error.
+ *
* @param numFrames frame count
* @param bytesPerFrame size of a frame in bytes
- * @param sizeInBytes total size in bytes
+ * @param sizeInBytes pointer to a variable to receive total size in bytes
* @return AAUDIO_OK or negative error, eg. AAUDIO_ERROR_OUT_OF_RANGE
*/
int32_t AAudioConvert_framesToBytes(int32_t numFrames,
- int32_t bytesPerFrame,
- int32_t *sizeInBytes);
+ int32_t bytesPerFrame,
+ int32_t *sizeInBytes);
audio_format_t AAudioConvert_aaudioToAndroidDataFormat(aaudio_format_t aaudio_format);
diff --git a/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp b/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
index c43509c..7ff1ec7d 100644
--- a/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
+++ b/media/libaudiohal/4.0/DevicesFactoryHalHybrid.cpp
@@ -33,7 +33,8 @@
}
status_t DevicesFactoryHalHybrid::openDevice(const char *name, sp<DeviceHalInterface> *device) {
- if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0) {
+ if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0 &&
+ strcmp(AUDIO_HARDWARE_MODULE_ID_HEARING_AID, name) != 0) {
return mHidlFactory->openDevice(name, device);
}
return mLocalFactory->openDevice(name, device);
diff --git a/media/libheif/HeifDecoderImpl.cpp b/media/libheif/HeifDecoderImpl.cpp
index 63130c4..8dae251 100644
--- a/media/libheif/HeifDecoderImpl.cpp
+++ b/media/libheif/HeifDecoderImpl.cpp
@@ -337,8 +337,8 @@
if (frameInfo != nullptr) {
frameInfo->set(
- videoFrame->mDisplayWidth,
- videoFrame->mDisplayHeight,
+ videoFrame->mWidth,
+ videoFrame->mHeight,
videoFrame->mRotationAngle,
videoFrame->mBytesPerPixel,
videoFrame->mIccSize,
@@ -415,8 +415,8 @@
if (frameInfo != nullptr) {
frameInfo->set(
- videoFrame->mDisplayWidth,
- videoFrame->mDisplayHeight,
+ videoFrame->mWidth,
+ videoFrame->mHeight,
videoFrame->mRotationAngle,
videoFrame->mBytesPerPixel,
videoFrame->mIccSize,
@@ -435,12 +435,12 @@
return false;
}
VideoFrame* videoFrame = static_cast<VideoFrame*>(mFrameMemory->pointer());
- if (mCurScanline >= videoFrame->mDisplayHeight) {
+ if (mCurScanline >= videoFrame->mHeight) {
ALOGE("no more scanline available");
return false;
}
uint8_t* src = videoFrame->getFlattenedData() + videoFrame->mRowBytes * mCurScanline++;
- memcpy(dst, src, videoFrame->mBytesPerPixel * videoFrame->mDisplayWidth);
+ memcpy(dst, src, videoFrame->mBytesPerPixel * videoFrame->mWidth);
return true;
}
@@ -452,8 +452,8 @@
uint32_t oldScanline = mCurScanline;
mCurScanline += count;
- if (mCurScanline > videoFrame->mDisplayHeight) {
- mCurScanline = videoFrame->mDisplayHeight;
+ if (mCurScanline > videoFrame->mHeight) {
+ mCurScanline = videoFrame->mHeight;
}
return (mCurScanline > oldScanline) ? (mCurScanline - oldScanline) : 0;
}
diff --git a/media/libmedia/NdkWrapper.cpp b/media/libmedia/NdkWrapper.cpp
index 5418af9..6f56d0c 100644
--- a/media/libmedia/NdkWrapper.cpp
+++ b/media/libmedia/NdkWrapper.cpp
@@ -56,10 +56,8 @@
AMEDIAFORMAT_KEY_COLOR_TRANSFER,
AMEDIAFORMAT_KEY_COMPLEXITY,
AMEDIAFORMAT_KEY_FLAC_COMPRESSION_LEVEL,
- AMEDIAFORMAT_KEY_GRID_COLS,
- AMEDIAFORMAT_KEY_GRID_HEIGHT,
+ AMEDIAFORMAT_KEY_GRID_COLUMNS,
AMEDIAFORMAT_KEY_GRID_ROWS,
- AMEDIAFORMAT_KEY_GRID_WIDTH,
AMEDIAFORMAT_KEY_HEIGHT,
AMEDIAFORMAT_KEY_INTRA_REFRESH_PERIOD,
AMEDIAFORMAT_KEY_IS_ADTS,
@@ -84,6 +82,8 @@
AMEDIAFORMAT_KEY_DISPLAY_HEIGHT,
AMEDIAFORMAT_KEY_DISPLAY_WIDTH,
AMEDIAFORMAT_KEY_TEMPORAL_LAYER_ID,
+ AMEDIAFORMAT_KEY_TILE_HEIGHT,
+ AMEDIAFORMAT_KEY_TILE_WIDTH,
AMEDIAFORMAT_KEY_TRACK_INDEX,
};
@@ -1068,6 +1068,14 @@
return OK;
}
+status_t AMediaExtractorWrapper::disconnect() {
+ if (mAMediaExtractor != NULL) {
+ media_status_t err = AMediaExtractor_disconnect(mAMediaExtractor);
+ return translateErrorCode(err);
+ }
+ return DEAD_OBJECT;
+}
+
AMediaExtractor *AMediaExtractorWrapper::getAMediaExtractor() const {
return mAMediaExtractor;
}
diff --git a/media/libmedia/TypeConverter.cpp b/media/libmedia/TypeConverter.cpp
index 9323d6d..03700bf 100644
--- a/media/libmedia/TypeConverter.cpp
+++ b/media/libmedia/TypeConverter.cpp
@@ -226,8 +226,12 @@
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_5POINT1),
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_5POINT1_BACK),
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_5POINT1_SIDE),
+ MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_5POINT1POINT2),
+ MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_5POINT1POINT4),
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_6POINT1),
MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_7POINT1),
+ MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_7POINT1POINT2),
+ MAKE_STRING_FROM_ENUM(AUDIO_CHANNEL_OUT_7POINT1POINT4),
TERMINATOR
};
diff --git a/media/libmedia/include/media/NdkWrapper.h b/media/libmedia/include/media/NdkWrapper.h
index 191665a..c97d171 100644
--- a/media/libmedia/include/media/NdkWrapper.h
+++ b/media/libmedia/include/media/NdkWrapper.h
@@ -287,6 +287,8 @@
status_t release();
+ status_t disconnect();
+
status_t setDataSource(int fd, off64_t offset, off64_t length);
status_t setDataSource(const char *location);
diff --git a/media/libmediaextractor/include/media/ExtractorUtils.h b/media/libmediaextractor/include/media/ExtractorUtils.h
new file mode 100644
index 0000000..22f9349
--- /dev/null
+++ b/media/libmediaextractor/include/media/ExtractorUtils.h
@@ -0,0 +1,32 @@
+/*
+ * 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 EXTRACTOR_UTILS_H_
+
+#define EXTRACTOR_UTILS_H_
+
+#include <memory>
+
+namespace android {
+
+template <class T>
+std::unique_ptr<T[]> heapbuffer(size_t size) {
+ return std::unique_ptr<T[]>(new (std::nothrow) T[size]);
+}
+
+} // namespace android
+
+#endif // UTILS_H_
diff --git a/media/libmediaextractor/include/media/stagefright/MetaDataBase.h b/media/libmediaextractor/include/media/stagefright/MetaDataBase.h
index 6010af4..18e63f2 100644
--- a/media/libmediaextractor/include/media/stagefright/MetaDataBase.h
+++ b/media/libmediaextractor/include/media/stagefright/MetaDataBase.h
@@ -211,11 +211,11 @@
kKeyTemporalLayerId = 'iLyr', // int32_t, temporal layer-id. 0-based (0 => base layer)
kKeyTemporalLayerCount = 'cLyr', // int32_t, number of temporal layers encoded
- kKeyGridWidth = 'grdW', // int32_t, HEIF grid width
- kKeyGridHeight = 'grdH', // int32_t, HEIF grid height
+ kKeyTileWidth = 'tilW', // int32_t, HEIF tile width
+ kKeyTileHeight = 'tilH', // int32_t, HEIF tile height
kKeyGridRows = 'grdR', // int32_t, HEIF grid rows
kKeyGridCols = 'grdC', // int32_t, HEIF grid columns
- kKeyIccProfile = 'prof', // raw data, ICC prifile data
+ kKeyIccProfile = 'prof', // raw data, ICC profile data
kKeyIsPrimaryImage = 'prim', // bool (int32_t), image track is the primary image
kKeyFrameCount = 'nfrm', // int32_t, total number of frame in video track
};
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index b296622..a8c6d15 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -37,7 +37,6 @@
#include <media/stagefright/BufferProducerWrapper.h>
#include <media/stagefright/MediaCodec.h>
-#include <media/stagefright/MediaCodecList.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/OMXClient.h>
#include <media/stagefright/PersistentSurface.h>
@@ -553,6 +552,7 @@
mNativeWindowUsageBits(0),
mLastNativeWindowDataSpace(HAL_DATASPACE_UNKNOWN),
mIsVideo(false),
+ mIsImage(false),
mIsEncoder(false),
mFatalError(false),
mShutdownInProgress(false),
@@ -1713,6 +1713,7 @@
mIsEncoder = encoder;
mIsVideo = !strncasecmp(mime, "video/", 6);
+ mIsImage = !strncasecmp(mime, "image/", 6);
mPortMode[kPortIndexInput] = IOMX::kPortModePresetByteBuffer;
mPortMode[kPortIndexOutput] = IOMX::kPortModePresetByteBuffer;
@@ -1728,10 +1729,11 @@
// 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)
+ if (mIsVideo || mIsImage) {
+ if (!findVideoBitrateControlInfo(msg, &bitrateMode, &bitrate, &quality)) {
+ return INVALID_OPERATION;
+ }
+ } else if (strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)
&& !msg->findInt32("bitrate", &bitrate)) {
return INVALID_OPERATION;
}
@@ -2010,7 +2012,7 @@
(void)msg->findInt32("pcm-encoding", (int32_t*)&pcmEncoding);
// invalid encodings will default to PCM-16bit in setupRawAudioFormat.
- if (mIsVideo) {
+ if (mIsVideo || mIsImage) {
// determine need for software renderer
bool usingSwRenderer = false;
if (haveNativeWindow && mComponentName.startsWith("OMX.google.")) {
@@ -2290,7 +2292,7 @@
}
// create data converters if needed
- if (!mIsVideo && err == OK) {
+ if (!mIsVideo && !mIsImage && err == OK) {
AudioEncoding codecPcmEncoding = kAudioEncodingPcm16bit;
if (encoder) {
(void)mInputFormat->findInt32("pcm-encoding", (int32_t*)&codecPcmEncoding);
@@ -3215,6 +3217,7 @@
{ MEDIA_MIMETYPE_VIDEO_VP8, OMX_VIDEO_CodingVP8 },
{ MEDIA_MIMETYPE_VIDEO_VP9, OMX_VIDEO_CodingVP9 },
{ MEDIA_MIMETYPE_VIDEO_DOLBY_VISION, OMX_VIDEO_CodingDolbyVision },
+ { MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC, OMX_VIDEO_CodingImageHEIC },
};
static status_t GetVideoCodingTypeFromMime(
@@ -3872,7 +3875,8 @@
break;
case OMX_VIDEO_CodingHEVC:
- err = setupHEVCEncoderParameters(msg);
+ case OMX_VIDEO_CodingImageHEIC:
+ err = setupHEVCEncoderParameters(msg, outputFormat);
break;
case OMX_VIDEO_CodingVP8:
@@ -4378,27 +4382,63 @@
return configureBitrate(bitrateMode, bitrate);
}
-status_t ACodec::setupHEVCEncoderParameters(const sp<AMessage> &msg) {
- float iFrameInterval;
- if (!msg->findAsFloat("i-frame-interval", &iFrameInterval)) {
- return INVALID_OPERATION;
+status_t ACodec::configureImageGrid(
+ const sp<AMessage> &msg, sp<AMessage> &outputFormat) {
+ int32_t tileWidth, tileHeight, gridRows, gridCols;
+ if (!msg->findInt32("tile-width", &tileWidth) ||
+ !msg->findInt32("tile-height", &tileHeight) ||
+ !msg->findInt32("grid-rows", &gridRows) ||
+ !msg->findInt32("grid-cols", &gridCols)) {
+ return OK;
}
+ OMX_VIDEO_PARAM_ANDROID_IMAGEGRIDTYPE gridType;
+ InitOMXParams(&gridType);
+ gridType.nPortIndex = kPortIndexOutput;
+ gridType.bEnabled = OMX_TRUE;
+ gridType.nTileWidth = tileWidth;
+ gridType.nTileHeight = tileHeight;
+ gridType.nGridRows = gridRows;
+ gridType.nGridCols = gridCols;
+
+ status_t err = mOMXNode->setParameter(
+ (OMX_INDEXTYPE)OMX_IndexParamVideoAndroidImageGrid,
+ &gridType, sizeof(gridType));
+
+ // for video encoders, grid config is only a hint.
+ if (!mIsImage) {
+ return OK;
+ }
+
+ // image encoders must support grid config.
+ if (err != OK) {
+ return err;
+ }
+
+ // query to get the image encoder's real grid config as it might be
+ // different from the requested, and transfer that to the output.
+ err = mOMXNode->getParameter(
+ (OMX_INDEXTYPE)OMX_IndexParamVideoAndroidImageGrid,
+ &gridType, sizeof(gridType));
+
+ if (err == OK && gridType.bEnabled) {
+ outputFormat->setInt32("tile-width", gridType.nTileWidth);
+ outputFormat->setInt32("tile-height", gridType.nTileHeight);
+ outputFormat->setInt32("grid-rows", gridType.nGridRows);
+ outputFormat->setInt32("grid-cols", gridType.nGridCols);
+ }
+
+ return err;
+}
+
+status_t ACodec::setupHEVCEncoderParameters(
+ const sp<AMessage> &msg, sp<AMessage> &outputFormat) {
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)) {
- int32_t tmp;
- if (!msg->findInt32("frame-rate", &tmp)) {
- return INVALID_OPERATION;
- }
- frameRate = (float)tmp;
- }
-
OMX_VIDEO_PARAM_HEVCTYPE hevcType;
InitOMXParams(&hevcType);
hevcType.nPortIndex = kPortIndexOutput;
@@ -4426,7 +4466,27 @@
hevcType.eLevel = static_cast<OMX_VIDEO_HEVCLEVELTYPE>(level);
}
// TODO: finer control?
- hevcType.nKeyFrameInterval = setPFramesSpacing(iFrameInterval, frameRate) + 1;
+ if (mIsImage) {
+ hevcType.nKeyFrameInterval = 1;
+ } else {
+ float iFrameInterval;
+ if (!msg->findAsFloat("i-frame-interval", &iFrameInterval)) {
+ return INVALID_OPERATION;
+ }
+
+ float frameRate;
+ if (!msg->findFloat("frame-rate", &frameRate)) {
+ int32_t tmp;
+ if (!msg->findInt32("frame-rate", &tmp)) {
+ return INVALID_OPERATION;
+ }
+ frameRate = (float)tmp;
+ }
+
+ hevcType.nKeyFrameInterval =
+ setPFramesSpacing(iFrameInterval, frameRate) + 1;
+ }
+
err = mOMXNode->setParameter(
(OMX_INDEXTYPE)OMX_IndexParamVideoHevc, &hevcType, sizeof(hevcType));
@@ -4434,6 +4494,12 @@
return err;
}
+ err = configureImageGrid(msg, outputFormat);
+
+ if (err != OK) {
+ return err;
+ }
+
return configureBitrate(bitrateMode, bitrate, quality);
}
@@ -4875,7 +4941,8 @@
(void)getHDRStaticInfoForVideoCodec(kPortIndexInput, notify);
}
uint32_t latency = 0;
- if (mIsEncoder && getLatency(&latency) == OK && latency > 0) {
+ if (mIsEncoder && !mIsImage &&
+ getLatency(&latency) == OK && latency > 0) {
notify->setInt32("latency", latency);
}
}
@@ -4931,7 +4998,8 @@
notify->setString("mime", mime.c_str());
}
uint32_t intraRefreshPeriod = 0;
- if (mIsEncoder && getIntraRefreshPeriod(&intraRefreshPeriod) == OK
+ if (mIsEncoder && !mIsImage &&
+ getIntraRefreshPeriod(&intraRefreshPeriod) == OK
&& intraRefreshPeriod > 0) {
notify->setInt32("intra-refresh-period", intraRefreshPeriod);
}
@@ -6346,37 +6414,19 @@
sp<AMessage> notify = new AMessage(kWhatOMXDied, mCodec);
- Vector<AString> matchingCodecs;
- Vector<AString> owners;
-
- AString componentName;
- CHECK(msg->findString("componentName", &componentName));
-
- sp<IMediaCodecList> list = MediaCodecList::getInstance();
- if (list == nullptr) {
- ALOGE("Unable to obtain MediaCodecList while "
- "attempting to create codec \"%s\"",
- componentName.c_str());
- mCodec->signalError(OMX_ErrorUndefined, NO_INIT);
- return false;
- }
- ssize_t index = list->findCodecByName(componentName.c_str());
- if (index < 0) {
- ALOGE("Unable to find codec \"%s\"",
- componentName.c_str());
- mCodec->signalError(OMX_ErrorInvalidComponent, NAME_NOT_FOUND);
- return false;
- }
- sp<MediaCodecInfo> info = list->getCodecInfo(index);
+ sp<RefBase> obj;
+ CHECK(msg->findObject("codecInfo", &obj));
+ sp<MediaCodecInfo> info = (MediaCodecInfo *)obj.get();
if (info == nullptr) {
- ALOGE("Unexpected error (index out-of-bound) while "
- "retrieving information for codec \"%s\"",
- componentName.c_str());
+ ALOGE("Unexpected nullptr for codec information");
mCodec->signalError(OMX_ErrorUndefined, UNKNOWN_ERROR);
return false;
}
AString owner = (info->getOwnerName() == nullptr) ? "default" : info->getOwnerName();
+ AString componentName;
+ CHECK(msg->findString("componentName", &componentName));
+
sp<CodecObserver> observer = new CodecObserver;
sp<IOMX> omx;
sp<IOMXNode> omxNode;
@@ -8226,8 +8276,9 @@
}
bool isVideo = strncasecmp(mime, "video/", 6) == 0;
+ bool isImage = strncasecmp(mime, "image/", 6) == 0;
- if (isVideo) {
+ if (isVideo || isImage) {
OMX_VIDEO_PARAM_PROFILELEVELTYPE param;
InitOMXParams(¶m);
param.nPortIndex = isEncoder ? kPortIndexOutput : kPortIndexInput;
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index 13d80f5..71bff84 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -49,6 +49,100 @@
}
cc_library_shared {
+ name: "libstagefright_codecbase",
+
+ export_include_dirs: ["include"],
+
+ srcs: [
+ "CodecBase.cpp",
+ "FrameRenderTracker.cpp",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ shared_libs: [
+ "libgui",
+ "liblog",
+ "libmedia",
+ "libstagefright_foundation",
+ "libui",
+ "libutils",
+ "android.hardware.cas.native@1.0",
+ ],
+
+ sanitize: {
+ cfi: true,
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ diag: {
+ cfi: true,
+ },
+ },
+}
+
+cc_library_shared {
+ name: "libstagefright_ccodec",
+
+ local_include_dirs: ["include"],
+
+ srcs: [
+ "C2OMXNode.cpp",
+ "CCodec.cpp",
+ "CCodecBufferChannel.cpp",
+ "Codec2Buffer.cpp",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ header_libs: [
+ "libstagefright_codec2_internal",
+ ],
+
+ shared_libs: [
+ "libbinder",
+ "libcutils",
+ "libgui",
+ "libhidlallocatorutils",
+ "libhidlbase",
+ "liblog",
+ "libmedia",
+ "libmedia_omx",
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_codecbase",
+ "libstagefright_foundation",
+ "libstagefright_omx_utils",
+ "libui",
+ "libutils",
+ "libv4l2_c2componentstore",
+ "android.hardware.cas.native@1.0",
+
+ // TODO: do not link directly with impl
+ "libstagefright_bufferqueue_helper",
+ "android.hardware.media.c2@1.0-service-impl",
+ ],
+
+ sanitize: {
+ cfi: true,
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ diag: {
+ cfi: true,
+ },
+ },
+}
+
+cc_library_shared {
name: "libstagefright",
srcs: [
@@ -60,11 +154,7 @@
"AudioPresentationInfo.cpp",
"AudioSource.cpp",
"BufferImpl.cpp",
- "C2OMXNode.cpp",
- "CCodec.cpp",
- "CCodecBufferChannel.cpp",
"Codec2InfoBuilder.cpp",
- "CodecBase.cpp",
"CallbackDataSource.cpp",
"CallbackMediaSource.cpp",
"CameraSource.cpp",
@@ -74,7 +164,6 @@
"DataURISource.cpp",
"FileSource.cpp",
"FrameDecoder.cpp",
- "FrameRenderTracker.cpp",
"HTTPBase.cpp",
"HevcUtils.cpp",
"InterfaceUtils.cpp",
@@ -107,10 +196,6 @@
"VideoFrameScheduler.cpp",
],
- header_libs: [
- "libstagefright_codec2_internal",
- ],
-
shared_libs: [
"libaudioutils",
"libbinder",
@@ -131,8 +216,10 @@
"libui",
"libutils",
"libmedia_helper",
+ "libstagefright_ccodec",
"libstagefright_codec2",
"libstagefright_codec2_vndk",
+ "libstagefright_codecbase",
"libstagefright_foundation",
"libstagefright_omx",
"libstagefright_omx_utils",
@@ -149,10 +236,6 @@
"android.hardware.media.omx@1.0",
"android.hardware.graphics.allocator@2.0",
"android.hardware.graphics.mapper@2.0",
-
- // TODO: do not link directly with impl
- "android.hardware.media.c2@1.0-service-impl",
- "libstagefright_bufferqueue_helper",
],
static_libs: [
diff --git a/media/libstagefright/BufferImpl.cpp b/media/libstagefright/BufferImpl.cpp
index 7b3fa02..b760273 100644
--- a/media/libstagefright/BufferImpl.cpp
+++ b/media/libstagefright/BufferImpl.cpp
@@ -24,7 +24,6 @@
#include <media/ICrypto.h>
#include <utils/NativeHandle.h>
-#include "include/Codec2Buffer.h"
#include "include/SecureBuffer.h"
#include "include/SharedMemoryBuffer.h"
@@ -64,592 +63,4 @@
return ICrypto::kDestinationTypeNativeHandle;
}
-// Codec2Buffer
-
-bool Codec2Buffer::canCopyLinear(const std::shared_ptr<C2Buffer> &buffer) const {
- if (const_cast<Codec2Buffer *>(this)->base() == nullptr) {
- return false;
- }
- if (!buffer) {
- // Nothing to copy, so we can copy by doing nothing.
- return true;
- }
- if (buffer->data().type() != C2BufferData::LINEAR) {
- return false;
- }
- if (buffer->data().linearBlocks().size() == 0u) {
- // Nothing to copy, so we can copy by doing nothing.
- return true;
- } else if (buffer->data().linearBlocks().size() > 1u) {
- // We don't know how to copy more than one blocks.
- return false;
- }
- if (buffer->data().linearBlocks()[0].size() > capacity()) {
- // It won't fit.
- return false;
- }
- return true;
-}
-
-bool Codec2Buffer::copyLinear(const std::shared_ptr<C2Buffer> &buffer) {
- // We assume that all canCopyLinear() checks passed.
- if (!buffer || buffer->data().linearBlocks().size() == 0u) {
- setRange(0, 0);
- return true;
- }
- C2ReadView view = buffer->data().linearBlocks()[0].map().get();
- if (view.error() != C2_OK) {
- ALOGD("Error while mapping: %d", view.error());
- return false;
- }
- if (view.capacity() > capacity()) {
- ALOGD("C2ConstLinearBlock lied --- it actually doesn't fit: view(%u) > this(%zu)",
- view.capacity(), capacity());
- return false;
- }
- memcpy(base(), view.data(), view.capacity());
- setRange(0, view.capacity());
- return true;
-}
-
-// LocalLinearBuffer
-
-bool LocalLinearBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
- return canCopyLinear(buffer);
-}
-
-bool LocalLinearBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
- return copyLinear(buffer);
-}
-
-// DummyContainerBuffer
-
-DummyContainerBuffer::DummyContainerBuffer(
- const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer)
- : Codec2Buffer(format, new ABuffer(nullptr, 1)),
- mBufferRef(buffer) {
- setRange(0, buffer ? 1 : 0);
-}
-
-std::shared_ptr<C2Buffer> DummyContainerBuffer::asC2Buffer() {
- return std::move(mBufferRef);
-}
-
-bool DummyContainerBuffer::canCopy(const std::shared_ptr<C2Buffer> &) const {
- return !mBufferRef;
-}
-
-bool DummyContainerBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
- mBufferRef = buffer;
- setRange(0, mBufferRef ? 1 : 0);
- return true;
-}
-
-// LinearBlockBuffer
-
-// static
-sp<LinearBlockBuffer> LinearBlockBuffer::Allocate(
- const sp<AMessage> &format, const std::shared_ptr<C2LinearBlock> &block) {
- C2WriteView writeView(block->map().get());
- if (writeView.error() != C2_OK) {
- return nullptr;
- }
- return new LinearBlockBuffer(format, std::move(writeView), block);
-}
-
-std::shared_ptr<C2Buffer> LinearBlockBuffer::asC2Buffer() {
- return C2Buffer::CreateLinearBuffer(mBlock->share(offset(), size(), C2Fence()));
-}
-
-bool LinearBlockBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
- return canCopyLinear(buffer);
-}
-
-bool LinearBlockBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
- return copyLinear(buffer);
-}
-
-LinearBlockBuffer::LinearBlockBuffer(
- const sp<AMessage> &format,
- C2WriteView&& writeView,
- const std::shared_ptr<C2LinearBlock> &block)
- : Codec2Buffer(format, new ABuffer(writeView.data(), writeView.size())),
- mWriteView(writeView),
- mBlock(block) {
-}
-
-// ConstLinearBlockBuffer
-
-// static
-sp<ConstLinearBlockBuffer> ConstLinearBlockBuffer::Allocate(
- const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer) {
- if (!buffer
- || buffer->data().type() != C2BufferData::LINEAR
- || buffer->data().linearBlocks().size() != 1u) {
- return nullptr;
- }
- C2ReadView readView(buffer->data().linearBlocks()[0].map().get());
- if (readView.error() != C2_OK) {
- return nullptr;
- }
- return new ConstLinearBlockBuffer(format, std::move(readView), buffer);
-}
-
-ConstLinearBlockBuffer::ConstLinearBlockBuffer(
- const sp<AMessage> &format,
- C2ReadView&& readView,
- const std::shared_ptr<C2Buffer> &buffer)
- : Codec2Buffer(format, new ABuffer(
- // NOTE: ABuffer only takes non-const pointer but this data is
- // supposed to be read-only.
- const_cast<uint8_t *>(readView.data()), readView.capacity())),
- mReadView(readView),
- mBufferRef(buffer) {
-}
-
-std::shared_ptr<C2Buffer> ConstLinearBlockBuffer::asC2Buffer() {
- return std::move(mBufferRef);
-}
-
-// GraphicView2MediaImageConverter
-
-namespace {
-
-class GraphicView2MediaImageConverter {
-public:
- explicit GraphicView2MediaImageConverter(const C2GraphicView &view)
- : mInitCheck(NO_INIT),
- mView(view),
- mWidth(view.width()),
- mHeight(view.height()),
- mAllocatedDepth(0),
- mBackBufferSize(0),
- mMediaImage(new ABuffer(sizeof(MediaImage2))) {
- if (view.error() != C2_OK) {
- ALOGD("Converter: view.error() = %d", view.error());
- mInitCheck = BAD_VALUE;
- return;
- }
- MediaImage2 *mediaImage = (MediaImage2 *)mMediaImage->base();
- const C2PlanarLayout &layout = view.layout();
- if (layout.numPlanes == 0) {
- ALOGD("Converter: 0 planes");
- mInitCheck = BAD_VALUE;
- return;
- }
- mAllocatedDepth = layout.planes[0].allocatedDepth;
- uint32_t bitDepth = layout.planes[0].bitDepth;
-
- switch (layout.type) {
- case C2PlanarLayout::TYPE_YUV:
- mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUV; break;
- case C2PlanarLayout::TYPE_YUVA:
- mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUVA; break;
- case C2PlanarLayout::TYPE_RGB:
- mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGB; break;
- case C2PlanarLayout::TYPE_RGBA:
- mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGBA; break;
- default:
- mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_UNKNOWN; break;
- }
- mediaImage->mNumPlanes = layout.numPlanes;
- mediaImage->mWidth = mWidth;
- mediaImage->mHeight = mHeight;
- mediaImage->mBitDepth = bitDepth;
- mediaImage->mBitDepthAllocated = mAllocatedDepth;
-
- uint32_t bufferSize = 0;
- for (uint32_t i = 0; i < layout.numPlanes; ++i) {
- const C2PlaneInfo &plane = layout.planes[i];
- if (plane.rightShift != 0) {
- ALOGV("rightShift value of %u unsupported", plane.rightShift);
- mInitCheck = BAD_VALUE;
- return;
- }
- if (plane.endianness != C2PlaneInfo::NATIVE) {
- ALOGV("endianness value of %u unsupported", plane.endianness);
- mInitCheck = BAD_VALUE;
- return;
- }
- if (plane.allocatedDepth != mAllocatedDepth || plane.bitDepth != bitDepth) {
- ALOGV("different allocatedDepth/bitDepth per plane unsupported");
- mInitCheck = BAD_VALUE;
- return;
- }
- bufferSize += mWidth * mHeight
- / plane.rowSampling / plane.colSampling * (plane.allocatedDepth / 8);
- }
-
- mBackBufferSize = bufferSize;
- mInitCheck = OK;
- }
-
- status_t initCheck() const { return mInitCheck; }
-
- uint32_t backBufferSize() const { return mBackBufferSize; }
-
- /**
- * Convert C2GraphicView to MediaImage2. Note that if not wrapped, the content
- * is not copied over in this function --- the caller should use
- * CopyGraphicView2MediaImage() function to do that explicitly.
- *
- * \param view[in] source C2GraphicView object.
- * \param alloc[in] allocator function for ABuffer.
- * \param mediaImage[out] destination MediaImage2 object.
- * \param buffer[out] new buffer object.
- * \param wrapped[out] whether we wrapped around existing map or
- * allocated a new buffer
- *
- * \return true if conversion succeeds,
- * false otherwise; all output params should be ignored.
- */
- sp<ABuffer> wrap() {
- MediaImage2 *mediaImage = getMediaImage();
- const C2PlanarLayout &layout = mView.layout();
- if (layout.numPlanes == 1) {
- const C2PlaneInfo &plane = layout.planes[0];
- ssize_t offset = plane.minOffset(mWidth, mHeight);
- mediaImage->mPlane[0].mOffset = -offset;
- mediaImage->mPlane[0].mColInc = plane.colInc;
- mediaImage->mPlane[0].mRowInc = plane.rowInc;
- mediaImage->mPlane[0].mHorizSubsampling = plane.colSampling;
- mediaImage->mPlane[0].mVertSubsampling = plane.rowSampling;
- return new ABuffer(
- const_cast<uint8_t *>(mView.data()[0] + offset),
- plane.maxOffset(mWidth, mHeight) - offset + 1);
- }
- const uint8_t *minPtr = mView.data()[0];
- const uint8_t *maxPtr = mView.data()[0];
- int32_t planeSize = 0;
- for (uint32_t i = 0; i < layout.numPlanes; ++i) {
- const C2PlaneInfo &plane = layout.planes[i];
- ssize_t minOffset = plane.minOffset(mWidth, mHeight);
- ssize_t maxOffset = plane.maxOffset(mWidth, mHeight);
- if (minPtr > mView.data()[i] + minOffset) {
- minPtr = mView.data()[i] + minOffset;
- }
- if (maxPtr < mView.data()[i] + maxOffset) {
- maxPtr = mView.data()[i] + maxOffset;
- }
- planeSize += std::abs(plane.rowInc) * mHeight
- / plane.rowSampling / plane.colSampling * (mAllocatedDepth / 8);
- }
-
- if ((maxPtr - minPtr + 1) <= planeSize) {
- // FIXME: this is risky as reading/writing data out of bound results in
- // an undefined behavior.
- for (uint32_t i = 0; i < layout.numPlanes; ++i) {
- const C2PlaneInfo &plane = layout.planes[i];
- mediaImage->mPlane[i].mOffset = mView.data()[i] - minPtr;
- mediaImage->mPlane[i].mColInc = plane.colInc;
- mediaImage->mPlane[i].mRowInc = plane.rowInc;
- mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
- mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
- }
- return new ABuffer(const_cast<uint8_t *>(minPtr), maxPtr - minPtr + 1);
- }
-
- return nullptr;
- }
-
- bool setBackBuffer(const sp<ABuffer> &backBuffer) {
- if (backBuffer->capacity() < mBackBufferSize) {
- return false;
- }
- backBuffer->setRange(0, mBackBufferSize);
-
- const C2PlanarLayout &layout = mView.layout();
- MediaImage2 *mediaImage = getMediaImage();
- uint32_t offset = 0;
- // TODO: keep interleaved planes together
- for (uint32_t i = 0; i < layout.numPlanes; ++i) {
- const C2PlaneInfo &plane = layout.planes[i];
- mediaImage->mPlane[i].mOffset = offset;
- mediaImage->mPlane[i].mColInc = mAllocatedDepth / 8;
- mediaImage->mPlane[i].mRowInc =
- mediaImage->mPlane[i].mColInc * mWidth / plane.colSampling;
- mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
- mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
- offset += mediaImage->mPlane[i].mRowInc * mHeight / plane.rowSampling;
- }
- mBackBuffer = backBuffer;
- return true;
- }
-
- /**
- * Copy C2GraphicView to MediaImage2. This function assumes that |mediaImage| is
- * an output from GraphicView2MediaImage(), so it mostly skips sanity check.
- *
- * \param view[in] source C2GraphicView object.
- * \param mediaImage[in] destination MediaImage2 object.
- * \param buffer[out] new buffer object.
- */
- void copy() {
- // TODO: more efficient copying --- e.g. one row at a time, copying
- // interleaved planes together, etc.
- const C2PlanarLayout &layout = mView.layout();
- MediaImage2 *mediaImage = getMediaImage();
- uint8_t *dst = mBackBuffer->base();
- for (uint32_t i = 0; i < layout.numPlanes; ++i) {
- const C2PlaneInfo &plane = layout.planes[i];
- const uint8_t *src = mView.data()[i];
- int32_t planeW = mWidth / plane.colSampling;
- int32_t planeH = mHeight / plane.rowSampling;
- for (int32_t row = 0; row < planeH; ++row) {
- for(int32_t col = 0; col < planeW; ++col) {
- memcpy(dst, src, mAllocatedDepth / 8);
- dst += mediaImage->mPlane[i].mColInc;
- src += plane.colInc;
- }
- dst -= mediaImage->mPlane[i].mColInc * planeW;
- dst += mediaImage->mPlane[i].mRowInc;
- src -= plane.colInc * planeW;
- src += plane.rowInc;
- }
- }
- }
-
- const sp<ABuffer> &imageData() const { return mMediaImage; }
-
-private:
- status_t mInitCheck;
-
- const C2GraphicView mView;
- uint32_t mWidth;
- uint32_t mHeight;
- uint32_t mAllocatedDepth;
- uint32_t mBackBufferSize;
- sp<ABuffer> mMediaImage;
- std::function<sp<ABuffer>(size_t)> mAlloc;
-
- sp<ABuffer> mBackBuffer;
-
- MediaImage2 *getMediaImage() {
- return (MediaImage2 *)mMediaImage->base();
- }
-};
-
-} // namespace
-
-// GraphicBlockBuffer
-
-// static
-sp<GraphicBlockBuffer> GraphicBlockBuffer::Allocate(
- const sp<AMessage> &format,
- const std::shared_ptr<C2GraphicBlock> &block,
- std::function<sp<ABuffer>(size_t)> alloc) {
- C2GraphicView view(block->map().get());
- if (view.error() != C2_OK) {
- ALOGD("C2GraphicBlock::map failed: %d", view.error());
- return nullptr;
- }
- GraphicView2MediaImageConverter converter(view);
- if (converter.initCheck() != OK) {
- ALOGD("Converter init failed: %d", converter.initCheck());
- return nullptr;
- }
- bool wrapped = true;
- sp<ABuffer> buffer = converter.wrap();
- if (buffer == nullptr) {
- buffer = alloc(converter.backBufferSize());
- if (!converter.setBackBuffer(buffer)) {
- ALOGD("Converter failed to set back buffer");
- return nullptr;
- }
- wrapped = false;
- }
- return new GraphicBlockBuffer(
- format,
- buffer,
- std::move(view),
- block,
- converter.imageData(),
- wrapped);
-}
-
-GraphicBlockBuffer::GraphicBlockBuffer(
- const sp<AMessage> &format,
- const sp<ABuffer> &buffer,
- C2GraphicView &&view,
- const std::shared_ptr<C2GraphicBlock> &block,
- const sp<ABuffer> &imageData,
- bool wrapped)
- : Codec2Buffer(format, buffer),
- mView(view),
- mBlock(block),
- mImageData(imageData),
- mWrapped(wrapped) {
- meta()->setBuffer("image-data", imageData);
-}
-
-std::shared_ptr<C2Buffer> GraphicBlockBuffer::asC2Buffer() {
- uint32_t width = mView.width();
- uint32_t height = mView.height();
- if (!mWrapped) {
- MediaImage2 *mediaImage = imageData();
- const C2PlanarLayout &layout = mView.layout();
- for (uint32_t i = 0; i < mediaImage->mNumPlanes; ++i) {
- const C2PlaneInfo &plane = layout.planes[i];
- int32_t planeW = width / plane.colSampling;
- int32_t planeH = height / plane.rowSampling;
- const uint8_t *src = base() + mediaImage->mPlane[i].mOffset;
- uint8_t *dst = mView.data()[i];
- for (int32_t row = 0; row < planeH; ++row) {
- for (int32_t col = 0; col < planeW; ++col) {
- memcpy(dst, src, mediaImage->mBitDepthAllocated / 8);
- src += mediaImage->mPlane[i].mColInc;
- dst += plane.colInc;
- }
- src -= mediaImage->mPlane[i].mColInc * planeW;
- dst -= plane.colInc * planeW;
- src += mediaImage->mPlane[i].mRowInc;
- dst += plane.rowInc;
- }
- }
- }
- return C2Buffer::CreateGraphicBuffer(
- mBlock->share(C2Rect(width, height), C2Fence()));
-}
-
-// ConstGraphicBlockBuffer
-
-// static
-sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::Allocate(
- const sp<AMessage> &format,
- const std::shared_ptr<C2Buffer> &buffer,
- std::function<sp<ABuffer>(size_t)> alloc) {
- if (!buffer
- || buffer->data().type() != C2BufferData::GRAPHIC
- || buffer->data().graphicBlocks().size() != 1u) {
- ALOGD("C2Buffer precond fail");
- return nullptr;
- }
- std::unique_ptr<const C2GraphicView> view(std::make_unique<const C2GraphicView>(
- buffer->data().graphicBlocks()[0].map().get()));
- std::unique_ptr<const C2GraphicView> holder;
-
- GraphicView2MediaImageConverter converter(*view);
- if (converter.initCheck() != OK) {
- ALOGD("Converter init failed: %d", converter.initCheck());
- return nullptr;
- }
- bool wrapped = true;
- sp<ABuffer> aBuffer = converter.wrap();
- if (aBuffer == nullptr) {
- aBuffer = alloc(converter.backBufferSize());
- if (!converter.setBackBuffer(aBuffer)) {
- ALOGD("Converter failed to set back buffer");
- return nullptr;
- }
- wrapped = false;
- converter.copy();
- // We don't need the view.
- holder = std::move(view);
- }
- return new ConstGraphicBlockBuffer(
- format,
- aBuffer,
- std::move(view),
- buffer,
- converter.imageData(),
- wrapped);
-}
-
-// static
-sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::AllocateEmpty(
- const sp<AMessage> &format,
- std::function<sp<ABuffer>(size_t)> alloc) {
- int32_t width, height;
- if (!format->findInt32("width", &width)
- || !format->findInt32("height", &height)) {
- ALOGD("format had no width / height");
- return nullptr;
- }
- sp<ABuffer> aBuffer(alloc(width * height * 4));
- return new ConstGraphicBlockBuffer(
- format,
- aBuffer,
- nullptr,
- nullptr,
- nullptr,
- false);
-}
-
-ConstGraphicBlockBuffer::ConstGraphicBlockBuffer(
- const sp<AMessage> &format,
- const sp<ABuffer> &aBuffer,
- std::unique_ptr<const C2GraphicView> &&view,
- const std::shared_ptr<C2Buffer> &buffer,
- const sp<ABuffer> &imageData,
- bool wrapped)
- : Codec2Buffer(format, aBuffer),
- mView(std::move(view)),
- mBufferRef(buffer),
- mWrapped(wrapped) {
- if (imageData != nullptr) {
- meta()->setBuffer("image-data", imageData);
- }
-}
-
-std::shared_ptr<C2Buffer> ConstGraphicBlockBuffer::asC2Buffer() {
- mView.reset();
- return std::move(mBufferRef);
-}
-
-bool ConstGraphicBlockBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
- if (mWrapped || mBufferRef) {
- ALOGD("ConstGraphicBlockBuffer::canCopy: %swrapped ; buffer ref %s",
- mWrapped ? "" : "not ", mBufferRef ? "exists" : "doesn't exist");
- return false;
- }
- if (!buffer) {
- // Nothing to copy, so we can copy by doing nothing.
- return true;
- }
- if (buffer->data().type() != C2BufferData::GRAPHIC) {
- ALOGD("ConstGraphicBlockBuffer::canCopy: buffer precondition unsatisfied");
- return false;
- }
- if (buffer->data().graphicBlocks().size() == 0) {
- return true;
- } else if (buffer->data().graphicBlocks().size() != 1u) {
- ALOGD("ConstGraphicBlockBuffer::canCopy: too many blocks");
- return false;
- }
- GraphicView2MediaImageConverter converter(
- buffer->data().graphicBlocks()[0].map().get());
- if (converter.initCheck() != OK) {
- ALOGD("ConstGraphicBlockBuffer::canCopy: converter init failed: %d", converter.initCheck());
- return false;
- }
- if (converter.backBufferSize() > capacity()) {
- ALOGD("ConstGraphicBlockBuffer::canCopy: insufficient capacity: req %u has %zu",
- converter.backBufferSize(), capacity());
- return false;
- }
- return true;
-}
-
-bool ConstGraphicBlockBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
- if (!buffer || buffer->data().graphicBlocks().size() == 0) {
- setRange(0, 0);
- return true;
- }
- GraphicView2MediaImageConverter converter(
- buffer->data().graphicBlocks()[0].map().get());
- if (converter.initCheck() != OK) {
- ALOGD("ConstGraphicBlockBuffer::copy: converter init failed: %d", converter.initCheck());
- return false;
- }
- sp<ABuffer> aBuffer = new ABuffer(base(), capacity());
- if (!converter.setBackBuffer(aBuffer)) {
- ALOGD("ConstGraphicBlockBuffer::copy: set back buffer failed");
- return false;
- }
- converter.copy();
- meta()->setBuffer("image-data", converter.imageData());
- mBufferRef = buffer;
- return true;
-}
-
} // namespace android
diff --git a/media/libstagefright/CCodec.cpp b/media/libstagefright/CCodec.cpp
index 0bdd808..0a20d34 100644
--- a/media/libstagefright/CCodec.cpp
+++ b/media/libstagefright/CCodec.cpp
@@ -259,20 +259,26 @@
return;
}
- AString componentName;
- if (!msg->findString("componentName", &componentName)) {
- // TODO: find componentName appropriate with the media type
- }
+ sp<RefBase> codecInfo;
+ CHECK(msg->findObject("codecInfo", &codecInfo));
+ // For Codec 2.0 components, componentName == codecInfo->getCodecName().
sp<AMessage> allocMsg(new AMessage(kWhatAllocate, this));
- allocMsg->setString("componentName", componentName);
+ allocMsg->setObject("codecInfo", codecInfo);
allocMsg->post();
}
-void CCodec::allocate(const AString &componentName) {
- ALOGV("allocate(%s)", componentName.c_str());
+void CCodec::allocate(const sp<MediaCodecInfo> &codecInfo) {
+ if (codecInfo == nullptr) {
+ mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
+ return;
+ }
+ ALOGV("allocate(%s)", codecInfo->getCodecName());
mListener.reset(new CCodecListener(this));
+ AString componentName = codecInfo->getCodecName();
+ // TODO: use codecInfo->getOwnerName() for connecting to remote process.
+
std::shared_ptr<C2Component> comp;
c2_status_t err = GetCodec2PlatformComponentStore()->createComponent(
componentName.c_str(), &comp);
@@ -812,9 +818,9 @@
case kWhatAllocate: {
// C2ComponentStore::createComponent() should return within 100ms.
setDeadline(now + 150ms, "allocate");
- AString componentName;
- CHECK(msg->findString("componentName", &componentName));
- allocate(componentName);
+ sp<RefBase> obj;
+ CHECK(msg->findObject("codecInfo", &obj));
+ allocate((MediaCodecInfo *)obj.get());
break;
}
case kWhatConfigure: {
diff --git a/media/libstagefright/CCodecBufferChannel.cpp b/media/libstagefright/CCodecBufferChannel.cpp
index 65d637b..cbe4f16 100644
--- a/media/libstagefright/CCodecBufferChannel.cpp
+++ b/media/libstagefright/CCodecBufferChannel.cpp
@@ -49,6 +49,8 @@
using namespace hardware::cas::V1_0;
using namespace hardware::cas::native::V1_0;
+using CasStatus = hardware::cas::V1_0::Status;
+
/**
* Base class for representation of buffers at one port.
*/
@@ -181,6 +183,7 @@
// TODO: get this info from component
const static size_t kMinBufferArraySize = 16;
const static size_t kLinearBufferSize = 524288;
+const static size_t kMaxGraphicBufferRefCount = 4;
/**
* Simple local buffer pool backed by std::vector.
@@ -291,21 +294,6 @@
DISALLOW_EVIL_CONSTRUCTORS(LocalBufferPool);
};
-sp<LinearBlockBuffer> AllocateLinearBuffer(
- const std::shared_ptr<C2BlockPool> &pool,
- const sp<AMessage> &format,
- size_t size,
- const C2MemoryUsage &usage) {
- std::shared_ptr<C2LinearBlock> block;
-
- c2_status_t err = pool->fetchLinearBlock(size, usage, &block);
- if (err != C2_OK) {
- return nullptr;
- }
-
- return LinearBlockBuffer::Allocate(format, block);
-}
-
sp<GraphicBlockBuffer> AllocateGraphicBuffer(
const std::shared_ptr<C2BlockPool> &pool,
const sp<AMessage> &format,
@@ -572,9 +560,7 @@
bool requestNewBuffer(size_t *index, sp<MediaCodecBuffer> *buffer) override {
// TODO: proper max input size
// TODO: read usage from intf
- C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
- sp<LinearBlockBuffer> newBuffer = AllocateLinearBuffer(
- mPool, mFormat, kLinearBufferSize, usage);
+ sp<Codec2Buffer> newBuffer = alloc(kLinearBufferSize);
if (newBuffer == nullptr) {
return false;
}
@@ -598,17 +584,88 @@
array->initialize(
mImpl,
kMinBufferArraySize,
- [pool = mPool, format = mFormat] () -> sp<Codec2Buffer> {
- C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
- return AllocateLinearBuffer(pool, format, kLinearBufferSize, usage);
- });
+ [this] () -> sp<Codec2Buffer> { return alloc(kLinearBufferSize); });
return std::move(array);
}
+ virtual sp<Codec2Buffer> alloc(size_t size) const {
+ C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+ std::shared_ptr<C2LinearBlock> block;
+
+ c2_status_t err = mPool->fetchLinearBlock(size, usage, &block);
+ if (err != C2_OK) {
+ return nullptr;
+ }
+
+ return LinearBlockBuffer::Allocate(mFormat, block);
+ }
+
private:
FlexBuffersImpl mImpl;
};
+class EncryptedLinearInputBuffers : public LinearInputBuffers {
+public:
+ EncryptedLinearInputBuffers(
+ bool secure,
+ const sp<MemoryDealer> &dealer,
+ const sp<ICrypto> &crypto,
+ int32_t heapSeqNum)
+ : mUsage({0, 0}),
+ mDealer(dealer),
+ mCrypto(crypto),
+ mHeapSeqNum(heapSeqNum) {
+ if (secure) {
+ mUsage = { C2MemoryUsage::READ_PROTECTED, 0 };
+ } else {
+ mUsage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+ }
+ for (size_t i = 0; i < kMinBufferArraySize; ++i) {
+ sp<IMemory> memory = mDealer->allocate(kLinearBufferSize);
+ if (memory == nullptr) {
+ ALOGD("Failed to allocate memory from dealer: only %zu slots allocated", i);
+ break;
+ }
+ mMemoryVector.push_back({std::weak_ptr<C2LinearBlock>(), memory});
+ }
+ }
+
+ ~EncryptedLinearInputBuffers() override {
+ }
+
+ sp<Codec2Buffer> alloc(size_t size) const override {
+ sp<IMemory> memory;
+ for (const Entry &entry : mMemoryVector) {
+ if (entry.block.expired()) {
+ memory = entry.memory;
+ break;
+ }
+ }
+ if (memory == nullptr) {
+ return nullptr;
+ }
+
+ std::shared_ptr<C2LinearBlock> block;
+ c2_status_t err = mPool->fetchLinearBlock(size, mUsage, &block);
+ if (err != C2_OK) {
+ return nullptr;
+ }
+
+ return new EncryptedLinearBlockBuffer(mFormat, block, memory, mHeapSeqNum);
+ }
+
+private:
+ C2MemoryUsage mUsage;
+ sp<MemoryDealer> mDealer;
+ sp<ICrypto> mCrypto;
+ int32_t mHeapSeqNum;
+ struct Entry {
+ std::weak_ptr<C2LinearBlock> block;
+ sp<IMemory> memory;
+ };
+ std::vector<Entry> mMemoryVector;
+};
+
class GraphicInputBuffers : public CCodecBufferChannel::InputBuffers {
public:
GraphicInputBuffers() : mLocalBufferPool(LocalBufferPool::Create(1920 * 1080 * 16)) {}
@@ -956,7 +1013,8 @@
CCodecBufferChannel::CCodecBufferChannel(
const std::function<void(status_t, enum ActionCode)> &onError)
- : mOnError(onError),
+ : mHeapSeqNum(-1),
+ mOnError(onError),
mFrameIndex(0u),
mFirstValidFrameIndex(0u) {
}
@@ -978,13 +1036,7 @@
return OK;
}
-status_t CCodecBufferChannel::queueInputBuffer(const sp<MediaCodecBuffer> &buffer) {
- QueueGuard guard(mSync);
- if (!guard.isRunning()) {
- ALOGW("No more buffers should be queued at current state.");
- return -ENOSYS;
- }
-
+status_t CCodecBufferChannel::queueInputBufferInternal(const sp<MediaCodecBuffer> &buffer) {
int64_t timeUs;
CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
@@ -1005,7 +1057,11 @@
work->input.buffers.clear();
{
Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
- work->input.buffers.push_back((*buffers)->releaseBuffer(buffer));
+ std::shared_ptr<C2Buffer> c2buffer = (*buffers)->releaseBuffer(buffer);
+ if (!c2buffer) {
+ return -ENOENT;
+ }
+ work->input.buffers.push_back(c2buffer);
}
// TODO: fill info's
@@ -1017,22 +1073,103 @@
return mComponent->queue_nb(&items);
}
+status_t CCodecBufferChannel::queueInputBuffer(const sp<MediaCodecBuffer> &buffer) {
+ QueueGuard guard(mSync);
+ if (!guard.isRunning()) {
+ ALOGW("No more buffers should be queued at current state.");
+ return -ENOSYS;
+ }
+ return queueInputBufferInternal(buffer);
+}
+
status_t CCodecBufferChannel::queueSecureInputBuffer(
const sp<MediaCodecBuffer> &buffer, bool secure, const uint8_t *key,
const uint8_t *iv, CryptoPlugin::Mode mode, CryptoPlugin::Pattern pattern,
const CryptoPlugin::SubSample *subSamples, size_t numSubSamples,
AString *errorDetailMsg) {
- // TODO
- (void) buffer;
- (void) secure;
- (void) key;
- (void) iv;
- (void) mode;
- (void) pattern;
- (void) subSamples;
- (void) numSubSamples;
- (void) errorDetailMsg;
- return -ENOSYS;
+ QueueGuard guard(mSync);
+ if (!guard.isRunning()) {
+ ALOGW("No more buffers should be queued at current state.");
+ return -ENOSYS;
+ }
+
+ if (!hasCryptoOrDescrambler()) {
+ return -ENOSYS;
+ }
+ sp<EncryptedLinearBlockBuffer> encryptedBuffer((EncryptedLinearBlockBuffer *)buffer.get());
+
+ ssize_t result = -1;
+ if (mCrypto != nullptr) {
+ ICrypto::DestinationBuffer destination;
+ if (secure) {
+ destination.mType = ICrypto::kDestinationTypeNativeHandle;
+ destination.mHandle = encryptedBuffer->handle();
+ } else {
+ destination.mType = ICrypto::kDestinationTypeSharedMemory;
+ destination.mSharedMemory = mDecryptDestination;
+ }
+ ICrypto::SourceBuffer source;
+ encryptedBuffer->fillSourceBuffer(&source);
+ result = mCrypto->decrypt(
+ key, iv, mode, pattern, source, buffer->offset(),
+ subSamples, numSubSamples, destination, errorDetailMsg);
+ if (result < 0) {
+ return result;
+ }
+ if (destination.mType == ICrypto::kDestinationTypeSharedMemory) {
+ encryptedBuffer->copyDecryptedContent(mDecryptDestination, result);
+ }
+ } else {
+ // Here we cast CryptoPlugin::SubSample to hardware::cas::native::V1_0::SubSample
+ // directly, the structure definitions should match as checked in DescramblerImpl.cpp.
+ hidl_vec<SubSample> hidlSubSamples;
+ hidlSubSamples.setToExternal((SubSample *)subSamples, numSubSamples, false /*own*/);
+
+ hardware::cas::native::V1_0::SharedBuffer srcBuffer;
+ encryptedBuffer->fillSourceBuffer(&srcBuffer);
+
+ DestinationBuffer dstBuffer;
+ if (secure) {
+ dstBuffer.type = BufferType::NATIVE_HANDLE;
+ dstBuffer.secureMemory = hidl_handle(encryptedBuffer->handle());
+ } else {
+ dstBuffer.type = BufferType::SHARED_MEMORY;
+ dstBuffer.nonsecureMemory = srcBuffer;
+ }
+
+ CasStatus status = CasStatus::OK;
+ hidl_string detailedError;
+
+ auto returnVoid = mDescrambler->descramble(
+ key != NULL ? (ScramblingControl)key[0] : ScramblingControl::UNSCRAMBLED,
+ hidlSubSamples,
+ srcBuffer,
+ 0,
+ dstBuffer,
+ 0,
+ [&status, &result, &detailedError] (
+ CasStatus _status, uint32_t _bytesWritten,
+ const hidl_string& _detailedError) {
+ status = _status;
+ result = (ssize_t)_bytesWritten;
+ detailedError = _detailedError;
+ });
+
+ if (!returnVoid.isOk() || status != CasStatus::OK || result < 0) {
+ ALOGE("descramble failed, trans=%s, status=%d, result=%zd",
+ returnVoid.description().c_str(), status, result);
+ return UNKNOWN_ERROR;
+ }
+
+ ALOGV("descramble succeeded, %zd bytes", result);
+
+ if (dstBuffer.type == BufferType::SHARED_MEMORY) {
+ encryptedBuffer->copyDecryptedContentFromMemory(result);
+ }
+ }
+
+ buffer->setRange(0, result);
+ return queueInputBufferInternal(buffer);
}
void CCodecBufferChannel::feedInputBufferIfAvailable() {
@@ -1061,8 +1198,8 @@
c2Buffer = (*buffers)->releaseBuffer(buffer);
}
- Mutexed<sp<Surface>>::Locked surface(mSurface);
- if (*surface == nullptr) {
+ Mutexed<OutputSurface>::Locked output(mOutputSurface);
+ if (output->surface == nullptr) {
ALOGE("no surface");
return OK;
}
@@ -1079,7 +1216,7 @@
GraphicBuffer::CLONE_HANDLE,
blocks.front().width(),
blocks.front().height(),
- HAL_PIXEL_FORMAT_YV12,
+ HAL_PIXEL_FORMAT_YCbCr_420_888,
// TODO
1,
(uint64_t)GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -1087,7 +1224,7 @@
blocks.front().width()));
native_handle_delete(grallocHandle);
- status_t result = (*surface)->attachBuffer(graphicBuffer.get());
+ status_t result = output->surface->attachBuffer(graphicBuffer.get());
if (result != OK) {
ALOGE("attachBuffer failed: %d", result);
return result;
@@ -1095,23 +1232,32 @@
// TODO: read and set crop
- result = native_window_set_buffers_timestamp((*surface).get(), timestampNs);
+ result = native_window_set_buffers_timestamp(output->surface.get(), timestampNs);
ALOGW_IF(result != OK, "failed to set buffer timestamp: %d", result);
// TODO: fix after C2Fence implementation
#if 0
const C2Fence &fence = blocks.front().fence();
- result = ((ANativeWindow *)(*surface).get())->queueBuffer(
- (*surface).get(), graphicBuffer.get(), fence.valid() ? fence.fd() : -1);
+ result = ((ANativeWindow *)output->surface.get())->queueBuffer(
+ output->surface.get(), graphicBuffer.get(), fence.valid() ? fence.fd() : -1);
#else
- result = ((ANativeWindow *)(*surface).get())->queueBuffer(
- (*surface).get(), graphicBuffer.get(), -1);
+ result = ((ANativeWindow *)output->surface.get())->queueBuffer(
+ output->surface.get(), graphicBuffer.get(), -1);
#endif
if (result != OK) {
ALOGE("queueBuffer failed: %d", result);
return result;
}
+ // XXX: Hack to keep C2Buffers unreleased until the consumer is done
+ // reading the content. Eventually IGBP-based C2BlockPool should handle
+ // the lifecycle.
+ output->bufferRefs.push_back(c2Buffer);
+ if (output->bufferRefs.size() > output->maxBufferCount + 1) {
+ output->bufferRefs.pop_front();
+ ALOGV("%zu buffer refs remaining", output->bufferRefs.size());
+ }
+
return OK;
}
@@ -1166,6 +1312,7 @@
if (err != C2_OK) {
return UNKNOWN_ERROR;
}
+ bool secure = mComponent->intf()->getName().find(".secure") != std::string::npos;
if (inputFormat != nullptr) {
Mutexed<std::unique_ptr<InputBuffers>>::Locked buffers(mInputBuffers);
@@ -1178,7 +1325,24 @@
buffers->reset(new GraphicInputBuffers);
}
} else {
- buffers->reset(new LinearInputBuffers);
+ if (hasCryptoOrDescrambler()) {
+ if (mDealer == nullptr) {
+ mDealer = new MemoryDealer(
+ align(kLinearBufferSize, MemoryDealer::getAllocationAlignment())
+ * (kMinBufferArraySize + 1),
+ "EncryptedLinearInputBuffers");
+ mDecryptDestination = mDealer->allocate(kLinearBufferSize);
+ }
+ if (mCrypto != nullptr && mHeapSeqNum < 0) {
+ mHeapSeqNum = mCrypto->setHeap(mDealer->getMemoryHeap());
+ } else {
+ mHeapSeqNum = -1;
+ }
+ buffers->reset(new EncryptedLinearInputBuffers(
+ secure, mDealer, mCrypto, mHeapSeqNum));
+ } else {
+ buffers->reset(new LinearInputBuffers);
+ }
}
(*buffers)->setFormat(inputFormat);
@@ -1200,8 +1364,8 @@
if (outputFormat != nullptr) {
bool hasOutputSurface = false;
{
- Mutexed<sp<Surface>>::Locked surface(mSurface);
- hasOutputSurface = (*surface != nullptr);
+ Mutexed<OutputSurface>::Locked output(mOutputSurface);
+ hasOutputSurface = (output->surface != nullptr);
}
Mutexed<std::unique_ptr<OutputBuffers>>::Locked buffers(mOutputBuffers);
@@ -1381,7 +1545,7 @@
newSurface->setScalingMode(NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
}
- Mutexed<sp<Surface>>::Locked surface(mSurface);
+ Mutexed<OutputSurface>::Locked output(mOutputSurface);
// if (newSurface == nullptr) {
// if (*surface != nullptr) {
// ALOGW("cannot unset a surface");
@@ -1395,7 +1559,11 @@
// return INVALID_OPERATION;
// }
- *surface = newSurface;
+ output->surface = newSurface;
+ output->bufferRefs.clear();
+ // XXX: hack
+ output->maxBufferCount = kMaxGraphicBufferRefCount;
+
return OK;
}
diff --git a/media/libstagefright/Codec2Buffer.cpp b/media/libstagefright/Codec2Buffer.cpp
new file mode 100644
index 0000000..d2ef229
--- /dev/null
+++ b/media/libstagefright/Codec2Buffer.cpp
@@ -0,0 +1,672 @@
+/*
+ * 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 "Codec2Buffer"
+#include <utils/Log.h>
+
+#include <hidlmemory/FrameworkUtils.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#include "include/Codec2Buffer.h"
+
+namespace android {
+
+// Codec2Buffer
+
+bool Codec2Buffer::canCopyLinear(const std::shared_ptr<C2Buffer> &buffer) const {
+ if (const_cast<Codec2Buffer *>(this)->base() == nullptr) {
+ return false;
+ }
+ if (!buffer) {
+ // Nothing to copy, so we can copy by doing nothing.
+ return true;
+ }
+ if (buffer->data().type() != C2BufferData::LINEAR) {
+ return false;
+ }
+ if (buffer->data().linearBlocks().size() == 0u) {
+ // Nothing to copy, so we can copy by doing nothing.
+ return true;
+ } else if (buffer->data().linearBlocks().size() > 1u) {
+ // We don't know how to copy more than one blocks.
+ return false;
+ }
+ if (buffer->data().linearBlocks()[0].size() > capacity()) {
+ // It won't fit.
+ return false;
+ }
+ return true;
+}
+
+bool Codec2Buffer::copyLinear(const std::shared_ptr<C2Buffer> &buffer) {
+ // We assume that all canCopyLinear() checks passed.
+ if (!buffer || buffer->data().linearBlocks().size() == 0u) {
+ setRange(0, 0);
+ return true;
+ }
+ C2ReadView view = buffer->data().linearBlocks()[0].map().get();
+ if (view.error() != C2_OK) {
+ ALOGD("Error while mapping: %d", view.error());
+ return false;
+ }
+ if (view.capacity() > capacity()) {
+ ALOGD("C2ConstLinearBlock lied --- it actually doesn't fit: view(%u) > this(%zu)",
+ view.capacity(), capacity());
+ return false;
+ }
+ memcpy(base(), view.data(), view.capacity());
+ setRange(0, view.capacity());
+ return true;
+}
+
+// LocalLinearBuffer
+
+bool LocalLinearBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
+ return canCopyLinear(buffer);
+}
+
+bool LocalLinearBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
+ return copyLinear(buffer);
+}
+
+// DummyContainerBuffer
+
+DummyContainerBuffer::DummyContainerBuffer(
+ const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer)
+ : Codec2Buffer(format, new ABuffer(nullptr, 1)),
+ mBufferRef(buffer) {
+ setRange(0, buffer ? 1 : 0);
+}
+
+std::shared_ptr<C2Buffer> DummyContainerBuffer::asC2Buffer() {
+ return std::move(mBufferRef);
+}
+
+bool DummyContainerBuffer::canCopy(const std::shared_ptr<C2Buffer> &) const {
+ return !mBufferRef;
+}
+
+bool DummyContainerBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
+ mBufferRef = buffer;
+ setRange(0, mBufferRef ? 1 : 0);
+ return true;
+}
+
+// LinearBlockBuffer
+
+// static
+sp<LinearBlockBuffer> LinearBlockBuffer::Allocate(
+ const sp<AMessage> &format, const std::shared_ptr<C2LinearBlock> &block) {
+ C2WriteView writeView(block->map().get());
+ if (writeView.error() != C2_OK) {
+ return nullptr;
+ }
+ return new LinearBlockBuffer(format, std::move(writeView), block);
+}
+
+std::shared_ptr<C2Buffer> LinearBlockBuffer::asC2Buffer() {
+ return C2Buffer::CreateLinearBuffer(mBlock->share(offset(), size(), C2Fence()));
+}
+
+bool LinearBlockBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
+ return canCopyLinear(buffer);
+}
+
+bool LinearBlockBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
+ return copyLinear(buffer);
+}
+
+LinearBlockBuffer::LinearBlockBuffer(
+ const sp<AMessage> &format,
+ C2WriteView&& writeView,
+ const std::shared_ptr<C2LinearBlock> &block)
+ : Codec2Buffer(format, new ABuffer(writeView.data(), writeView.size())),
+ mWriteView(writeView),
+ mBlock(block) {
+}
+
+// ConstLinearBlockBuffer
+
+// static
+sp<ConstLinearBlockBuffer> ConstLinearBlockBuffer::Allocate(
+ const sp<AMessage> &format, const std::shared_ptr<C2Buffer> &buffer) {
+ if (!buffer
+ || buffer->data().type() != C2BufferData::LINEAR
+ || buffer->data().linearBlocks().size() != 1u) {
+ return nullptr;
+ }
+ C2ReadView readView(buffer->data().linearBlocks()[0].map().get());
+ if (readView.error() != C2_OK) {
+ return nullptr;
+ }
+ return new ConstLinearBlockBuffer(format, std::move(readView), buffer);
+}
+
+ConstLinearBlockBuffer::ConstLinearBlockBuffer(
+ const sp<AMessage> &format,
+ C2ReadView&& readView,
+ const std::shared_ptr<C2Buffer> &buffer)
+ : Codec2Buffer(format, new ABuffer(
+ // NOTE: ABuffer only takes non-const pointer but this data is
+ // supposed to be read-only.
+ const_cast<uint8_t *>(readView.data()), readView.capacity())),
+ mReadView(readView),
+ mBufferRef(buffer) {
+}
+
+std::shared_ptr<C2Buffer> ConstLinearBlockBuffer::asC2Buffer() {
+ return std::move(mBufferRef);
+}
+
+// GraphicView2MediaImageConverter
+
+namespace {
+
+class GraphicView2MediaImageConverter {
+public:
+ explicit GraphicView2MediaImageConverter(const C2GraphicView &view)
+ : mInitCheck(NO_INIT),
+ mView(view),
+ mWidth(view.width()),
+ mHeight(view.height()),
+ mAllocatedDepth(0),
+ mBackBufferSize(0),
+ mMediaImage(new ABuffer(sizeof(MediaImage2))) {
+ if (view.error() != C2_OK) {
+ ALOGD("Converter: view.error() = %d", view.error());
+ mInitCheck = BAD_VALUE;
+ return;
+ }
+ MediaImage2 *mediaImage = (MediaImage2 *)mMediaImage->base();
+ const C2PlanarLayout &layout = view.layout();
+ if (layout.numPlanes == 0) {
+ ALOGD("Converter: 0 planes");
+ mInitCheck = BAD_VALUE;
+ return;
+ }
+ mAllocatedDepth = layout.planes[0].allocatedDepth;
+ uint32_t bitDepth = layout.planes[0].bitDepth;
+
+ switch (layout.type) {
+ case C2PlanarLayout::TYPE_YUV:
+ mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUV; break;
+ case C2PlanarLayout::TYPE_YUVA:
+ mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_YUVA; break;
+ case C2PlanarLayout::TYPE_RGB:
+ mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGB; break;
+ case C2PlanarLayout::TYPE_RGBA:
+ mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_RGBA; break;
+ default:
+ mediaImage->mType = MediaImage2::MEDIA_IMAGE_TYPE_UNKNOWN; break;
+ }
+ mediaImage->mNumPlanes = layout.numPlanes;
+ mediaImage->mWidth = mWidth;
+ mediaImage->mHeight = mHeight;
+ mediaImage->mBitDepth = bitDepth;
+ mediaImage->mBitDepthAllocated = mAllocatedDepth;
+
+ uint32_t bufferSize = 0;
+ for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+ const C2PlaneInfo &plane = layout.planes[i];
+ if (plane.rightShift != 0) {
+ ALOGV("rightShift value of %u unsupported", plane.rightShift);
+ mInitCheck = BAD_VALUE;
+ return;
+ }
+ if (plane.endianness != C2PlaneInfo::NATIVE) {
+ ALOGV("endianness value of %u unsupported", plane.endianness);
+ mInitCheck = BAD_VALUE;
+ return;
+ }
+ if (plane.allocatedDepth != mAllocatedDepth || plane.bitDepth != bitDepth) {
+ ALOGV("different allocatedDepth/bitDepth per plane unsupported");
+ mInitCheck = BAD_VALUE;
+ return;
+ }
+ bufferSize += mWidth * mHeight
+ / plane.rowSampling / plane.colSampling * (plane.allocatedDepth / 8);
+ }
+
+ mBackBufferSize = bufferSize;
+ mInitCheck = OK;
+ }
+
+ status_t initCheck() const { return mInitCheck; }
+
+ uint32_t backBufferSize() const { return mBackBufferSize; }
+
+ /**
+ * Convert C2GraphicView to MediaImage2. Note that if not wrapped, the content
+ * is not copied over in this function --- the caller should use
+ * CopyGraphicView2MediaImage() function to do that explicitly.
+ *
+ * \param view[in] source C2GraphicView object.
+ * \param alloc[in] allocator function for ABuffer.
+ * \param mediaImage[out] destination MediaImage2 object.
+ * \param buffer[out] new buffer object.
+ * \param wrapped[out] whether we wrapped around existing map or
+ * allocated a new buffer
+ *
+ * \return true if conversion succeeds,
+ * false otherwise; all output params should be ignored.
+ */
+ sp<ABuffer> wrap() {
+ MediaImage2 *mediaImage = getMediaImage();
+ const C2PlanarLayout &layout = mView.layout();
+ if (layout.numPlanes == 1) {
+ const C2PlaneInfo &plane = layout.planes[0];
+ ssize_t offset = plane.minOffset(mWidth, mHeight);
+ mediaImage->mPlane[0].mOffset = -offset;
+ mediaImage->mPlane[0].mColInc = plane.colInc;
+ mediaImage->mPlane[0].mRowInc = plane.rowInc;
+ mediaImage->mPlane[0].mHorizSubsampling = plane.colSampling;
+ mediaImage->mPlane[0].mVertSubsampling = plane.rowSampling;
+ return new ABuffer(
+ const_cast<uint8_t *>(mView.data()[0] + offset),
+ plane.maxOffset(mWidth, mHeight) - offset + 1);
+ }
+ const uint8_t *minPtr = mView.data()[0];
+ const uint8_t *maxPtr = mView.data()[0];
+ int32_t planeSize = 0;
+ for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+ const C2PlaneInfo &plane = layout.planes[i];
+ ssize_t minOffset = plane.minOffset(mWidth, mHeight);
+ ssize_t maxOffset = plane.maxOffset(mWidth, mHeight);
+ if (minPtr > mView.data()[i] + minOffset) {
+ minPtr = mView.data()[i] + minOffset;
+ }
+ if (maxPtr < mView.data()[i] + maxOffset) {
+ maxPtr = mView.data()[i] + maxOffset;
+ }
+ planeSize += std::abs(plane.rowInc) * mHeight
+ / plane.rowSampling / plane.colSampling * (mAllocatedDepth / 8);
+ }
+
+ if ((maxPtr - minPtr + 1) <= planeSize) {
+ // FIXME: this is risky as reading/writing data out of bound results in
+ // an undefined behavior.
+ for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+ const C2PlaneInfo &plane = layout.planes[i];
+ mediaImage->mPlane[i].mOffset = mView.data()[i] - minPtr;
+ mediaImage->mPlane[i].mColInc = plane.colInc;
+ mediaImage->mPlane[i].mRowInc = plane.rowInc;
+ mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
+ mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
+ }
+ return new ABuffer(const_cast<uint8_t *>(minPtr), maxPtr - minPtr + 1);
+ }
+
+ return nullptr;
+ }
+
+ bool setBackBuffer(const sp<ABuffer> &backBuffer) {
+ if (backBuffer->capacity() < mBackBufferSize) {
+ return false;
+ }
+ backBuffer->setRange(0, mBackBufferSize);
+
+ const C2PlanarLayout &layout = mView.layout();
+ MediaImage2 *mediaImage = getMediaImage();
+ uint32_t offset = 0;
+ // TODO: keep interleaved planes together
+ for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+ const C2PlaneInfo &plane = layout.planes[i];
+ mediaImage->mPlane[i].mOffset = offset;
+ mediaImage->mPlane[i].mColInc = mAllocatedDepth / 8;
+ mediaImage->mPlane[i].mRowInc =
+ mediaImage->mPlane[i].mColInc * mWidth / plane.colSampling;
+ mediaImage->mPlane[i].mHorizSubsampling = plane.colSampling;
+ mediaImage->mPlane[i].mVertSubsampling = plane.rowSampling;
+ offset += mediaImage->mPlane[i].mRowInc * mHeight / plane.rowSampling;
+ }
+ mBackBuffer = backBuffer;
+ return true;
+ }
+
+ /**
+ * Copy C2GraphicView to MediaImage2. This function assumes that |mediaImage| is
+ * an output from GraphicView2MediaImage(), so it mostly skips sanity check.
+ *
+ * \param view[in] source C2GraphicView object.
+ * \param mediaImage[in] destination MediaImage2 object.
+ * \param buffer[out] new buffer object.
+ */
+ void copy() {
+ // TODO: more efficient copying --- e.g. one row at a time, copying
+ // interleaved planes together, etc.
+ const C2PlanarLayout &layout = mView.layout();
+ MediaImage2 *mediaImage = getMediaImage();
+ uint8_t *dst = mBackBuffer->base();
+ for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+ const C2PlaneInfo &plane = layout.planes[i];
+ const uint8_t *src = mView.data()[i];
+ int32_t planeW = mWidth / plane.colSampling;
+ int32_t planeH = mHeight / plane.rowSampling;
+ for (int32_t row = 0; row < planeH; ++row) {
+ for(int32_t col = 0; col < planeW; ++col) {
+ memcpy(dst, src, mAllocatedDepth / 8);
+ dst += mediaImage->mPlane[i].mColInc;
+ src += plane.colInc;
+ }
+ dst -= mediaImage->mPlane[i].mColInc * planeW;
+ dst += mediaImage->mPlane[i].mRowInc;
+ src -= plane.colInc * planeW;
+ src += plane.rowInc;
+ }
+ }
+ }
+
+ const sp<ABuffer> &imageData() const { return mMediaImage; }
+
+private:
+ status_t mInitCheck;
+
+ const C2GraphicView mView;
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mAllocatedDepth;
+ uint32_t mBackBufferSize;
+ sp<ABuffer> mMediaImage;
+ std::function<sp<ABuffer>(size_t)> mAlloc;
+
+ sp<ABuffer> mBackBuffer;
+
+ MediaImage2 *getMediaImage() {
+ return (MediaImage2 *)mMediaImage->base();
+ }
+};
+
+} // namespace
+
+// GraphicBlockBuffer
+
+// static
+sp<GraphicBlockBuffer> GraphicBlockBuffer::Allocate(
+ const sp<AMessage> &format,
+ const std::shared_ptr<C2GraphicBlock> &block,
+ std::function<sp<ABuffer>(size_t)> alloc) {
+ C2GraphicView view(block->map().get());
+ if (view.error() != C2_OK) {
+ ALOGD("C2GraphicBlock::map failed: %d", view.error());
+ return nullptr;
+ }
+ GraphicView2MediaImageConverter converter(view);
+ if (converter.initCheck() != OK) {
+ ALOGD("Converter init failed: %d", converter.initCheck());
+ return nullptr;
+ }
+ bool wrapped = true;
+ sp<ABuffer> buffer = converter.wrap();
+ if (buffer == nullptr) {
+ buffer = alloc(converter.backBufferSize());
+ if (!converter.setBackBuffer(buffer)) {
+ ALOGD("Converter failed to set back buffer");
+ return nullptr;
+ }
+ wrapped = false;
+ }
+ return new GraphicBlockBuffer(
+ format,
+ buffer,
+ std::move(view),
+ block,
+ converter.imageData(),
+ wrapped);
+}
+
+GraphicBlockBuffer::GraphicBlockBuffer(
+ const sp<AMessage> &format,
+ const sp<ABuffer> &buffer,
+ C2GraphicView &&view,
+ const std::shared_ptr<C2GraphicBlock> &block,
+ const sp<ABuffer> &imageData,
+ bool wrapped)
+ : Codec2Buffer(format, buffer),
+ mView(view),
+ mBlock(block),
+ mImageData(imageData),
+ mWrapped(wrapped) {
+ meta()->setBuffer("image-data", imageData);
+}
+
+std::shared_ptr<C2Buffer> GraphicBlockBuffer::asC2Buffer() {
+ uint32_t width = mView.width();
+ uint32_t height = mView.height();
+ if (!mWrapped) {
+ MediaImage2 *mediaImage = imageData();
+ const C2PlanarLayout &layout = mView.layout();
+ for (uint32_t i = 0; i < mediaImage->mNumPlanes; ++i) {
+ const C2PlaneInfo &plane = layout.planes[i];
+ int32_t planeW = width / plane.colSampling;
+ int32_t planeH = height / plane.rowSampling;
+ const uint8_t *src = base() + mediaImage->mPlane[i].mOffset;
+ uint8_t *dst = mView.data()[i];
+ for (int32_t row = 0; row < planeH; ++row) {
+ for (int32_t col = 0; col < planeW; ++col) {
+ memcpy(dst, src, mediaImage->mBitDepthAllocated / 8);
+ src += mediaImage->mPlane[i].mColInc;
+ dst += plane.colInc;
+ }
+ src -= mediaImage->mPlane[i].mColInc * planeW;
+ dst -= plane.colInc * planeW;
+ src += mediaImage->mPlane[i].mRowInc;
+ dst += plane.rowInc;
+ }
+ }
+ }
+ return C2Buffer::CreateGraphicBuffer(
+ mBlock->share(C2Rect(width, height), C2Fence()));
+}
+
+// ConstGraphicBlockBuffer
+
+// static
+sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::Allocate(
+ const sp<AMessage> &format,
+ const std::shared_ptr<C2Buffer> &buffer,
+ std::function<sp<ABuffer>(size_t)> alloc) {
+ if (!buffer
+ || buffer->data().type() != C2BufferData::GRAPHIC
+ || buffer->data().graphicBlocks().size() != 1u) {
+ ALOGD("C2Buffer precond fail");
+ return nullptr;
+ }
+ std::unique_ptr<const C2GraphicView> view(std::make_unique<const C2GraphicView>(
+ buffer->data().graphicBlocks()[0].map().get()));
+ std::unique_ptr<const C2GraphicView> holder;
+
+ GraphicView2MediaImageConverter converter(*view);
+ if (converter.initCheck() != OK) {
+ ALOGD("Converter init failed: %d", converter.initCheck());
+ return nullptr;
+ }
+ bool wrapped = true;
+ sp<ABuffer> aBuffer = converter.wrap();
+ if (aBuffer == nullptr) {
+ aBuffer = alloc(converter.backBufferSize());
+ if (!converter.setBackBuffer(aBuffer)) {
+ ALOGD("Converter failed to set back buffer");
+ return nullptr;
+ }
+ wrapped = false;
+ converter.copy();
+ // We don't need the view.
+ holder = std::move(view);
+ }
+ return new ConstGraphicBlockBuffer(
+ format,
+ aBuffer,
+ std::move(view),
+ buffer,
+ converter.imageData(),
+ wrapped);
+}
+
+// static
+sp<ConstGraphicBlockBuffer> ConstGraphicBlockBuffer::AllocateEmpty(
+ const sp<AMessage> &format,
+ std::function<sp<ABuffer>(size_t)> alloc) {
+ int32_t width, height;
+ if (!format->findInt32("width", &width)
+ || !format->findInt32("height", &height)) {
+ ALOGD("format had no width / height");
+ return nullptr;
+ }
+ sp<ABuffer> aBuffer(alloc(width * height * 4));
+ return new ConstGraphicBlockBuffer(
+ format,
+ aBuffer,
+ nullptr,
+ nullptr,
+ nullptr,
+ false);
+}
+
+ConstGraphicBlockBuffer::ConstGraphicBlockBuffer(
+ const sp<AMessage> &format,
+ const sp<ABuffer> &aBuffer,
+ std::unique_ptr<const C2GraphicView> &&view,
+ const std::shared_ptr<C2Buffer> &buffer,
+ const sp<ABuffer> &imageData,
+ bool wrapped)
+ : Codec2Buffer(format, aBuffer),
+ mView(std::move(view)),
+ mBufferRef(buffer),
+ mWrapped(wrapped) {
+ if (imageData != nullptr) {
+ meta()->setBuffer("image-data", imageData);
+ }
+}
+
+std::shared_ptr<C2Buffer> ConstGraphicBlockBuffer::asC2Buffer() {
+ mView.reset();
+ return std::move(mBufferRef);
+}
+
+bool ConstGraphicBlockBuffer::canCopy(const std::shared_ptr<C2Buffer> &buffer) const {
+ if (mWrapped || mBufferRef) {
+ ALOGD("ConstGraphicBlockBuffer::canCopy: %swrapped ; buffer ref %s",
+ mWrapped ? "" : "not ", mBufferRef ? "exists" : "doesn't exist");
+ return false;
+ }
+ if (!buffer) {
+ // Nothing to copy, so we can copy by doing nothing.
+ return true;
+ }
+ if (buffer->data().type() != C2BufferData::GRAPHIC) {
+ ALOGD("ConstGraphicBlockBuffer::canCopy: buffer precondition unsatisfied");
+ return false;
+ }
+ if (buffer->data().graphicBlocks().size() == 0) {
+ return true;
+ } else if (buffer->data().graphicBlocks().size() != 1u) {
+ ALOGD("ConstGraphicBlockBuffer::canCopy: too many blocks");
+ return false;
+ }
+ GraphicView2MediaImageConverter converter(
+ buffer->data().graphicBlocks()[0].map().get());
+ if (converter.initCheck() != OK) {
+ ALOGD("ConstGraphicBlockBuffer::canCopy: converter init failed: %d", converter.initCheck());
+ return false;
+ }
+ if (converter.backBufferSize() > capacity()) {
+ ALOGD("ConstGraphicBlockBuffer::canCopy: insufficient capacity: req %u has %zu",
+ converter.backBufferSize(), capacity());
+ return false;
+ }
+ return true;
+}
+
+bool ConstGraphicBlockBuffer::copy(const std::shared_ptr<C2Buffer> &buffer) {
+ if (!buffer || buffer->data().graphicBlocks().size() == 0) {
+ setRange(0, 0);
+ return true;
+ }
+ GraphicView2MediaImageConverter converter(
+ buffer->data().graphicBlocks()[0].map().get());
+ if (converter.initCheck() != OK) {
+ ALOGD("ConstGraphicBlockBuffer::copy: converter init failed: %d", converter.initCheck());
+ return false;
+ }
+ sp<ABuffer> aBuffer = new ABuffer(base(), capacity());
+ if (!converter.setBackBuffer(aBuffer)) {
+ ALOGD("ConstGraphicBlockBuffer::copy: set back buffer failed");
+ return false;
+ }
+ converter.copy();
+ meta()->setBuffer("image-data", converter.imageData());
+ mBufferRef = buffer;
+ return true;
+}
+
+// EncryptedLinearBlockBuffer
+
+EncryptedLinearBlockBuffer::EncryptedLinearBlockBuffer(
+ const sp<AMessage> &format,
+ const std::shared_ptr<C2LinearBlock> &block,
+ const sp<IMemory> &memory,
+ int32_t heapSeqNum)
+ : Codec2Buffer(format, new ABuffer(memory->pointer(), memory->size())),
+ mBlock(block),
+ mMemory(memory),
+ mHeapSeqNum(heapSeqNum) {
+}
+
+std::shared_ptr<C2Buffer> EncryptedLinearBlockBuffer::asC2Buffer() {
+ return C2Buffer::CreateLinearBuffer(mBlock->share(offset(), size(), C2Fence()));
+}
+
+void EncryptedLinearBlockBuffer::fillSourceBuffer(
+ ICrypto::SourceBuffer *source) {
+ source->mSharedMemory = mMemory;
+ source->mHeapSeqNum = mHeapSeqNum;
+}
+
+void EncryptedLinearBlockBuffer::fillSourceBuffer(
+ hardware::cas::native::V1_0::SharedBuffer *source) {
+ ssize_t offset;
+ size_t size;
+
+ mHidlMemory = hardware::fromHeap(mMemory->getMemory(&offset, &size));
+ source->heapBase = *mHidlMemory;
+ source->offset = offset;
+ source->size = size;
+}
+
+bool EncryptedLinearBlockBuffer::copyDecryptedContent(
+ const sp<IMemory> &decrypted, size_t length) {
+ C2WriteView view = mBlock->map().get();
+ if (view.error() != C2_OK) {
+ return false;
+ }
+ if (view.size() < length) {
+ return false;
+ }
+ memcpy(view.data(), decrypted->pointer(), length);
+ return true;
+}
+
+bool EncryptedLinearBlockBuffer::copyDecryptedContentFromMemory(size_t length) {
+ return copyDecryptedContent(mMemory, length);
+}
+
+native_handle_t *EncryptedLinearBlockBuffer::handle() const {
+ return const_cast<native_handle_t *>(mBlock->handle());
+}
+
+} // namespace android
diff --git a/media/libstagefright/Codec2InfoBuilder.cpp b/media/libstagefright/Codec2InfoBuilder.cpp
index 7ce2ff1..78c4e38 100644
--- a/media/libstagefright/Codec2InfoBuilder.cpp
+++ b/media/libstagefright/Codec2InfoBuilder.cpp
@@ -31,6 +31,63 @@
using ConstTraitsPtr = std::shared_ptr<const C2Component::Traits>;
+struct ProfileLevel {
+ uint32_t profile;
+ uint32_t level;
+};
+static const ProfileLevel kAvcProfileLevels[] = {
+ { 0x01, 0x0001 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel1 },
+ { 0x01, 0x0002 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel1b },
+ { 0x01, 0x0004 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel11 },
+ { 0x01, 0x0008 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel12 },
+ { 0x01, 0x0010 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel13 },
+ { 0x01, 0x0020 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel2 },
+ { 0x01, 0x0040 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel21 },
+ { 0x01, 0x0080 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel22 },
+ { 0x01, 0x0100 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel3 },
+ { 0x01, 0x0200 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel31 },
+ { 0x01, 0x0400 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel32 },
+ { 0x01, 0x0800 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel4 },
+ { 0x01, 0x1000 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel41 },
+ { 0x01, 0x2000 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel42 },
+ { 0x01, 0x4000 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel5 },
+ { 0x01, 0x8000 }, // { OMX_VIDEO_AVCProfileBaseline, OMX_VIDEO_AVCLevel51 },
+
+ { 0x02, 0x0001 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel1 },
+ { 0x02, 0x0002 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel1b },
+ { 0x02, 0x0004 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel11 },
+ { 0x02, 0x0008 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel12 },
+ { 0x02, 0x0010 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel13 },
+ { 0x02, 0x0020 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel2 },
+ { 0x02, 0x0040 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel21 },
+ { 0x02, 0x0080 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel22 },
+ { 0x02, 0x0100 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel3 },
+ { 0x02, 0x0200 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel31 },
+ { 0x02, 0x0400 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel32 },
+ { 0x02, 0x0800 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel4 },
+ { 0x02, 0x1000 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel41 },
+ { 0x02, 0x2000 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel42 },
+ { 0x02, 0x4000 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel5 },
+ { 0x02, 0x8000 }, // { OMX_VIDEO_AVCProfileMain, OMX_VIDEO_AVCLevel51 },
+
+ { 0x04, 0x0001 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel1 },
+ { 0x04, 0x0002 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel1b },
+ { 0x04, 0x0004 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel11 },
+ { 0x04, 0x0008 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel12 },
+ { 0x04, 0x0010 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel13 },
+ { 0x04, 0x0020 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel2 },
+ { 0x04, 0x0040 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel21 },
+ { 0x04, 0x0080 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel22 },
+ { 0x04, 0x0100 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel3 },
+ { 0x04, 0x0200 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel31 },
+ { 0x04, 0x0400 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel32 },
+ { 0x04, 0x0800 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel4 },
+ { 0x04, 0x1000 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel41 },
+ { 0x04, 0x2000 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel42 },
+ { 0x04, 0x4000 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel5 },
+ { 0x04, 0x8000 }, // { OMX_VIDEO_AVCProfileHigh, OMX_VIDEO_AVCLevel51 },
+};
+
status_t Codec2InfoBuilder::buildMediaCodecList(MediaCodecListWriter* writer) {
// Obtain C2ComponentStore
std::shared_ptr<C2ComponentStore> store = GetCodec2PlatformComponentStore();
@@ -86,6 +143,12 @@
caps->addDetail(key.c_str(), value.c_str());
}
}
+ // TODO: get this from intf(), and apply to other codecs as well.
+ if (mediaType.find("video/avc") != std::string::npos && !encoder) {
+ for (const auto& pl : kAvcProfileLevels) {
+ caps->addProfileLevel(pl.profile, pl.level);
+ }
+ }
// TODO: get this from intf().
if (mediaType.find("video") != std::string::npos && !encoder) {
caps->addColorFormat(0x7F420888); // COLOR_FormatYUV420Flexible
diff --git a/media/libstagefright/FrameDecoder.cpp b/media/libstagefright/FrameDecoder.cpp
index 9748a8b..3d0aad1 100644
--- a/media/libstagefright/FrameDecoder.cpp
+++ b/media/libstagefright/FrameDecoder.cpp
@@ -495,27 +495,27 @@
mGridRows = mGridCols = 1;
if (overrideMeta == NULL) {
// check if we're dealing with a tiled heif
- int32_t gridWidth, gridHeight, gridRows, gridCols;
- if (trackMeta()->findInt32(kKeyGridWidth, &gridWidth) && gridWidth > 0
- && trackMeta()->findInt32(kKeyGridHeight, &gridHeight) && gridHeight > 0
+ int32_t tileWidth, tileHeight, gridRows, gridCols;
+ if (trackMeta()->findInt32(kKeyTileWidth, &tileWidth) && tileWidth > 0
+ && trackMeta()->findInt32(kKeyTileHeight, &tileHeight) && tileHeight > 0
&& trackMeta()->findInt32(kKeyGridRows, &gridRows) && gridRows > 0
&& trackMeta()->findInt32(kKeyGridCols, &gridCols) && gridCols > 0) {
int32_t width, height;
CHECK(trackMeta()->findInt32(kKeyWidth, &width));
CHECK(trackMeta()->findInt32(kKeyHeight, &height));
- if (width <= gridWidth * gridCols && height <= gridHeight * gridRows) {
- ALOGV("grid: %dx%d, size: %dx%d, picture size: %dx%d",
- gridCols, gridRows, gridWidth, gridHeight, width, height);
+ if (width <= tileWidth * gridCols && height <= tileHeight * gridRows) {
+ ALOGV("grid: %dx%d, tile size: %dx%d, picture size: %dx%d",
+ gridCols, gridRows, tileWidth, tileHeight, width, height);
overrideMeta = new MetaData(*(trackMeta()));
- overrideMeta->setInt32(kKeyWidth, gridWidth);
- overrideMeta->setInt32(kKeyHeight, gridHeight);
+ overrideMeta->setInt32(kKeyWidth, tileWidth);
+ overrideMeta->setInt32(kKeyHeight, tileHeight);
mGridCols = gridCols;
mGridRows = gridRows;
} else {
- ALOGE("bad grid: %dx%d, size: %dx%d, picture size: %dx%d",
- gridCols, gridRows, gridWidth, gridHeight, width, height);
+ ALOGE("bad grid: %dx%d, tile size: %dx%d, picture size: %dx%d",
+ gridCols, gridRows, tileWidth, tileHeight, width, height);
}
}
if (overrideMeta == NULL) {
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index cfbbcb2..a3261d7 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -358,7 +358,7 @@
Vector<uint16_t> mDimgRefs;
int32_t mIsPrimary;
int32_t mWidth, mHeight;
- int32_t mGridWidth, mGridHeight;
+ int32_t mTileWidth, mTileHeight;
int32_t mGridRows, mGridCols;
size_t mNumTiles, mTileIndex;
@@ -1376,15 +1376,12 @@
}
void MPEG4Writer::addMultipleLengthPrefixedSamples_l(MediaBuffer *buffer) {
- const size_t kExtensionNALSearchRange = 64; // bytes to look for non-VCL NALUs
-
const uint8_t *dataStart = (const uint8_t *)buffer->data() + buffer->range_offset();
const uint8_t *currentNalStart = dataStart;
const uint8_t *nextNalStart;
const uint8_t *data = dataStart;
size_t nextNalSize;
- size_t searchSize = buffer->range_length() > kExtensionNALSearchRange ?
- kExtensionNALSearchRange : buffer->range_length();
+ size_t searchSize = buffer->range_length();
while (getNextNALUnit(&data, &searchSize, &nextNalStart,
&nextNalSize, true) == OK) {
@@ -1773,8 +1770,8 @@
mIsPrimary(0),
mWidth(0),
mHeight(0),
- mGridWidth(0),
- mGridHeight(0),
+ mTileWidth(0),
+ mTileHeight(0),
mGridRows(0),
mGridCols(0),
mNumTiles(1),
@@ -1805,13 +1802,13 @@
CHECK(mMeta->findInt32(kKeyWidth, &mWidth) && (mWidth > 0));
CHECK(mMeta->findInt32(kKeyHeight, &mHeight) && (mHeight > 0));
- int32_t gridWidth, gridHeight, gridRows, gridCols;
- if (mMeta->findInt32(kKeyGridWidth, &gridWidth) && (gridWidth > 0) &&
- mMeta->findInt32(kKeyGridHeight, &gridHeight) && (gridHeight > 0) &&
+ int32_t tileWidth, tileHeight, gridRows, gridCols;
+ if (mMeta->findInt32(kKeyTileWidth, &tileWidth) && (tileWidth > 0) &&
+ mMeta->findInt32(kKeyTileHeight, &tileHeight) && (tileHeight > 0) &&
mMeta->findInt32(kKeyGridRows, &gridRows) && (gridRows > 0) &&
mMeta->findInt32(kKeyGridCols, &gridCols) && (gridCols > 0)) {
- mGridWidth = gridWidth;
- mGridHeight = gridHeight;
+ mTileWidth = tileWidth;
+ mTileHeight = tileHeight;
mGridRows = gridRows;
mGridCols = gridCols;
mNumTiles = gridRows * gridCols;
@@ -1962,6 +1959,17 @@
return;
}
+ // Rotation angle in HEIF is CCW, framework angle is CW.
+ int32_t heifRotation = 0;
+ switch(mRotation) {
+ case 90: heifRotation = 3; break;
+ case 180: heifRotation = 2; break;
+ case 270: heifRotation = 1; break;
+ default: break; // don't set if invalid
+ }
+
+ bool hasGrid = (mNumTiles > 1);
+
if (mProperties.empty()) {
mProperties.push_back(mOwner->addProperty_l({
.type = FOURCC('h', 'v', 'c', 'C'),
@@ -1970,22 +1978,29 @@
mProperties.push_back(mOwner->addProperty_l({
.type = FOURCC('i', 's', 'p', 'e'),
- .width = (mNumTiles > 1) ? mGridWidth : mWidth,
- .height = (mNumTiles > 1) ? mGridHeight : mHeight,
+ .width = hasGrid ? mTileWidth : mWidth,
+ .height = hasGrid ? mTileHeight : mHeight,
}));
+
+ if (!hasGrid && heifRotation > 0) {
+ mProperties.push_back(mOwner->addProperty_l({
+ .type = FOURCC('i', 'r', 'o', 't'),
+ .rotation = heifRotation,
+ }));
+ }
}
uint16_t itemId = mOwner->addItem_l({
.itemType = "hvc1",
- .isPrimary = (mNumTiles > 1) ? false : (mIsPrimary != 0),
- .isHidden = (mNumTiles > 1),
+ .isPrimary = hasGrid ? false : (mIsPrimary != 0),
+ .isHidden = hasGrid,
.offset = (uint32_t)offset,
.size = (uint32_t)size,
.properties = mProperties,
});
mTileIndex++;
- if (mNumTiles > 1) {
+ if (hasGrid) {
mDimgRefs.push_back(itemId);
if (mTileIndex == mNumTiles) {
@@ -1995,6 +2010,12 @@
.width = mWidth,
.height = mHeight,
}));
+ if (heifRotation > 0) {
+ mProperties.push_back(mOwner->addProperty_l({
+ .type = FOURCC('i', 'r', 'o', 't'),
+ .rotation = heifRotation,
+ }));
+ }
mOwner->addItem_l({
.itemType = "grid",
.isPrimary = (mIsPrimary != 0),
@@ -2305,7 +2326,8 @@
mStartTimeRealUs = startTimeUs;
int32_t rotationDegrees;
- if (mIsVideo && params && params->findInt32(kKeyRotation, &rotationDegrees)) {
+ if ((mIsVideo || mIsHeic) && params &&
+ params->findInt32(kKeyRotation, &rotationDegrees)) {
mRotation = rotationDegrees;
}
@@ -3430,16 +3452,36 @@
int32_t MPEG4Writer::Track::getMetaSizeIncrease() const {
CHECK(mIsHeic);
- return 20 // 1. 'ispe' property
- + (8 + mCodecSpecificDataSize) // 2. 'hvcC' property
- + (20 // 3. extra 'ispe'
- + (8 + 2 + 2 + mNumTiles * 2) // 4. 'dimg' ref
- + 12) // 5. ImageGrid in 'idat' (worst case)
- * (mNumTiles > 1) // - (3~5: applicable only if grid)
- + (16 // 6. increase to 'iloc'
- + 21 // 7. increase to 'iinf'
- + (3 + 2 * 2)) // 8. increase to 'ipma' (worst case)
- * (mNumTiles + 1); // - (6~8: are per-item)
+
+ int32_t grid = (mNumTiles > 1);
+
+ // Note that the rotation angle is in the file meta, and we don't have
+ // it until start, so here the calculation has to assume rotation.
+
+ // increase to ipco
+ int32_t increase = 20 * (grid + 1) // 'ispe' property
+ + (8 + mCodecSpecificDataSize) // 'hvcC' property
+ + 9; // 'irot' property (worst case)
+
+ // increase to iref and idat
+ if (grid) {
+ increase += (8 + 2 + 2 + mNumTiles * 2) // 'dimg' in iref
+ + 12; // ImageGrid in 'idat' (worst case)
+ }
+
+ // increase to iloc, iinf and ipma
+ increase += (16 // increase to 'iloc'
+ + 21 // increase to 'iinf'
+ + (3 + 2 * 2)) // increase to 'ipma' (worst case, 2 props x 2 bytes)
+ * (mNumTiles + grid);
+
+ // adjust to ipma:
+ // if rotation is present and only one tile, it could ref 3 properties
+ if (!grid) {
+ increase += 2;
+ }
+
+ return increase;
}
status_t MPEG4Writer::Track::checkCodecSpecificData() const {
@@ -4267,24 +4309,38 @@
numProperties = 32767;
}
for (size_t propIndex = 0; propIndex < numProperties; propIndex++) {
- if (mProperties[propIndex].type == FOURCC('h', 'v', 'c', 'C')) {
- beginBox("hvcC");
- sp<ABuffer> hvcc = mProperties[propIndex].hvcc;
- // Patch avcc's lengthSize field to match the number
- // of bytes we use to indicate the size of a nal unit.
- uint8_t *ptr = (uint8_t *)hvcc->data();
- ptr[21] = (ptr[21] & 0xfc) | (useNalLengthFour() ? 3 : 1);
- write(hvcc->data(), hvcc->size());
- endBox();
- } else if (mProperties[propIndex].type == FOURCC('i', 's', 'p', 'e')) {
- beginBox("ispe");
- writeInt32(0); // Version = 0, Flags = 0
- writeInt32(mProperties[propIndex].width);
- writeInt32(mProperties[propIndex].height);
- endBox();
- } else {
- ALOGW("Skipping unrecognized property: type 0x%08x",
- mProperties[propIndex].type);
+ switch (mProperties[propIndex].type) {
+ case FOURCC('h', 'v', 'c', 'C'):
+ {
+ beginBox("hvcC");
+ sp<ABuffer> hvcc = mProperties[propIndex].hvcc;
+ // Patch avcc's lengthSize field to match the number
+ // of bytes we use to indicate the size of a nal unit.
+ uint8_t *ptr = (uint8_t *)hvcc->data();
+ ptr[21] = (ptr[21] & 0xfc) | (useNalLengthFour() ? 3 : 1);
+ write(hvcc->data(), hvcc->size());
+ endBox();
+ break;
+ }
+ case FOURCC('i', 's', 'p', 'e'):
+ {
+ beginBox("ispe");
+ writeInt32(0); // Version = 0, Flags = 0
+ writeInt32(mProperties[propIndex].width);
+ writeInt32(mProperties[propIndex].height);
+ endBox();
+ break;
+ }
+ case FOURCC('i', 'r', 'o', 't'):
+ {
+ beginBox("irot");
+ writeInt8(mProperties[propIndex].rotation);
+ endBox();
+ break;
+ }
+ default:
+ ALOGW("Skipping unrecognized property: type 0x%08x",
+ mProperties[propIndex].type);
}
}
endBox();
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index 7d5c63a..edcb8c7 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -422,14 +422,12 @@
const sp<ALooper> &looper, const AString &mime, bool encoder, status_t *err, pid_t pid,
uid_t uid) {
Vector<AString> matchingCodecs;
- Vector<AString> owners;
MediaCodecList::findMatchingCodecs(
mime.c_str(),
encoder,
0,
- &matchingCodecs,
- &owners);
+ &matchingCodecs);
if (err != NULL) {
*err = NAME_NOT_FOUND;
@@ -603,6 +601,8 @@
return NAME_NOT_FOUND;
}
+ mCodecInfo.clear();
+
bool secureCodec = false;
AString tmp = name;
if (tmp.endsWith(".secure")) {
@@ -614,17 +614,24 @@
mCodec = NULL; // remove the codec.
return NO_INIT; // if called from Java should raise IOException
}
- ssize_t codecIdx = mcl->findCodecByName(tmp.c_str());
- if (codecIdx >= 0) {
- const sp<MediaCodecInfo> info = mcl->getCodecInfo(codecIdx);
+ for (const AString &codecName : { name, tmp }) {
+ ssize_t codecIdx = mcl->findCodecByName(codecName.c_str());
+ if (codecIdx < 0) {
+ continue;
+ }
+ mCodecInfo = mcl->getCodecInfo(codecIdx);
Vector<AString> mimes;
- info->getSupportedMimes(&mimes);
+ mCodecInfo->getSupportedMimes(&mimes);
for (size_t i = 0; i < mimes.size(); i++) {
if (mimes[i].startsWith("video/")) {
mIsVideo = true;
break;
}
}
+ break;
+ }
+ if (mCodecInfo == nullptr) {
+ return NAME_NOT_FOUND;
}
if (mIsVideo) {
@@ -651,6 +658,9 @@
new BufferCallback(new AMessage(kWhatCodecNotify, this))));
sp<AMessage> msg = new AMessage(kWhatInit, this);
+ msg->setObject("codecInfo", mCodecInfo);
+ // name may be different from mCodecInfo->getCodecName() if we stripped
+ // ".secure"
msg->setString("name", name);
if (mAnalyticsItem != NULL) {
@@ -1205,6 +1215,22 @@
return OK;
}
+status_t MediaCodec::getCodecInfo(sp<MediaCodecInfo> *codecInfo) const {
+ sp<AMessage> msg = new AMessage(kWhatGetCodecInfo, this);
+
+ sp<AMessage> response;
+ status_t err;
+ if ((err = PostAndAwaitResponse(msg, &response)) != OK) {
+ return err;
+ }
+
+ sp<RefBase> obj;
+ CHECK(response->findObject("codecInfo", &obj));
+ *codecInfo = static_cast<MediaCodecInfo *>(obj.get());
+
+ return OK;
+}
+
status_t MediaCodec::getMetrics(MediaAnalyticsItem * &reply) {
reply = NULL;
@@ -1972,11 +1998,14 @@
mReplyID = replyID;
setState(INITIALIZING);
+ sp<RefBase> codecInfo;
+ CHECK(msg->findObject("codecInfo", &codecInfo));
AString name;
CHECK(msg->findString("name", &name));
sp<AMessage> format = new AMessage;
- format->setString("componentName", name.c_str());
+ format->setObject("codecInfo", codecInfo);
+ format->setString("componentName", name);
mCodec->initiateAllocateComponent(format);
break;
@@ -2595,6 +2624,17 @@
break;
}
+ case kWhatGetCodecInfo:
+ {
+ sp<AReplyToken> replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
+ sp<AMessage> response = new AMessage;
+ response->setObject("codecInfo", mCodecInfo);
+ response->postReply(replyID);
+ break;
+ }
+
case kWhatSetParameters:
{
sp<AReplyToken> replyID;
diff --git a/media/libstagefright/MediaCodecList.cpp b/media/libstagefright/MediaCodecList.cpp
index f595646..9244886 100644
--- a/media/libstagefright/MediaCodecList.cpp
+++ b/media/libstagefright/MediaCodecList.cpp
@@ -307,11 +307,8 @@
//static
void MediaCodecList::findMatchingCodecs(
const char *mime, bool encoder, uint32_t flags,
- Vector<AString> *matches, Vector<AString> *owners) {
+ Vector<AString> *matches) {
matches->clear();
- if (owners != nullptr) {
- owners->clear();
- }
const sp<IMediaCodecList> list = getInstance();
if (list == nullptr) {
@@ -337,9 +334,6 @@
ALOGV("skipping SW codec '%s'", componentName.c_str());
} else {
matches->push(componentName);
- if (owners != nullptr) {
- owners->push(AString(info->getOwnerName()));
- }
ALOGV("matching '%s'", componentName.c_str());
}
}
diff --git a/media/libstagefright/MetaDataUtils.cpp b/media/libstagefright/MetaDataUtils.cpp
index af8f539..04f6ade 100644
--- a/media/libstagefright/MetaDataUtils.cpp
+++ b/media/libstagefright/MetaDataUtils.cpp
@@ -24,11 +24,12 @@
namespace android {
-bool MakeAVCCodecSpecificData(MetaDataBase &meta, const sp<ABuffer> &accessUnit) {
+bool MakeAVCCodecSpecificData(MetaDataBase &meta, const uint8_t *data, size_t size) {
int32_t width;
int32_t height;
int32_t sarWidth;
int32_t sarHeight;
+ sp<ABuffer> accessUnit = new ABuffer((void*)data, size);
sp<ABuffer> csd = MakeAVCCodecSpecificData(accessUnit, &width, &height, &sarWidth, &sarHeight);
if (csd == nullptr) {
return false;
diff --git a/media/libstagefright/NuMediaExtractor.cpp b/media/libstagefright/NuMediaExtractor.cpp
index f6fc813..27b9a5d 100644
--- a/media/libstagefright/NuMediaExtractor.cpp
+++ b/media/libstagefright/NuMediaExtractor.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_NDEBUG 0
+//#define LOG_NDEBUG 0
#define LOG_TAG "NuMediaExtractor"
#include <utils/Log.h>
@@ -205,6 +205,15 @@
return OK;
}
+void NuMediaExtractor::disconnect() {
+ if (mDataSource != NULL) {
+ // disconnect data source
+ if (mDataSource->flags() & DataSource::kIsCachingDataSource) {
+ static_cast<NuCachedSource2 *>(mDataSource.get())->disconnect();
+ }
+ }
+}
+
status_t NuMediaExtractor::updateDurationAndBitrate() {
if (mImpl->countTracks() > kMaxTrackCount) {
return ERROR_UNSUPPORTED;
diff --git a/media/libstagefright/Utils.cpp b/media/libstagefright/Utils.cpp
index 4a93051..0c6e988 100644
--- a/media/libstagefright/Utils.cpp
+++ b/media/libstagefright/Utils.cpp
@@ -665,13 +665,13 @@
}
if (!strncasecmp("image/", mime, 6)) {
- int32_t gridWidth, gridHeight, gridRows, gridCols;
- if (meta->findInt32(kKeyGridWidth, &gridWidth)
- && meta->findInt32(kKeyGridHeight, &gridHeight)
+ int32_t tileWidth, tileHeight, gridRows, gridCols;
+ if (meta->findInt32(kKeyTileWidth, &tileWidth)
+ && meta->findInt32(kKeyTileHeight, &tileHeight)
&& meta->findInt32(kKeyGridRows, &gridRows)
&& meta->findInt32(kKeyGridCols, &gridCols)) {
- msg->setInt32("grid-width", gridWidth);
- msg->setInt32("grid-height", gridHeight);
+ msg->setInt32("tile-width", tileWidth);
+ msg->setInt32("tile-height", tileHeight);
msg->setInt32("grid-rows", gridRows);
msg->setInt32("grid-cols", gridCols);
}
@@ -1341,12 +1341,12 @@
if (msg->findInt32("is-default", &isPrimary) && isPrimary) {
meta->setInt32(kKeyTrackIsDefault, 1);
}
- int32_t gridWidth, gridHeight, gridRows, gridCols;
- if (msg->findInt32("grid-width", &gridWidth)) {
- meta->setInt32(kKeyGridWidth, gridWidth);
+ int32_t tileWidth, tileHeight, gridRows, gridCols;
+ if (msg->findInt32("tile-width", &tileWidth)) {
+ meta->setInt32(kKeyTileWidth, tileWidth);
}
- if (msg->findInt32("grid-height", &gridHeight)) {
- meta->setInt32(kKeyGridHeight, gridHeight);
+ if (msg->findInt32("tile-height", &tileHeight)) {
+ meta->setInt32(kKeyTileHeight, tileHeight);
}
if (msg->findInt32("grid-rows", &gridRows)) {
meta->setInt32(kKeyGridRows, gridRows);
diff --git a/media/libstagefright/codecs/amrnb/dec/Android.bp b/media/libstagefright/codecs/amrnb/dec/Android.bp
index e4a2607..f97af89 100644
--- a/media/libstagefright/codecs/amrnb/dec/Android.bp
+++ b/media/libstagefright/codecs/amrnb/dec/Android.bp
@@ -110,7 +110,6 @@
],
compile_multilib: "32",
}
-
//###############################################################################
cc_test {
name: "libstagefright_amrnbdec_test",
@@ -139,3 +138,100 @@
// ],
//},
}
+
+//###############################################################################
+cc_library_shared {
+ name: "libstagefright_soft_c2amrnbdec",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ srcs: ["C2SoftAMR.cpp",],
+
+ include_dirs: [
+ "frameworks/av/media/libstagefright/codecs/amrwb/src",
+ ],
+
+ local_include_dirs: ["src"],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-DAMRNB",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ "unsigned-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+
+ static_libs: [
+ "libstagefright_amrnbdec",
+ "libstagefright_amrwbdec",
+ ],
+
+ shared_libs: [
+ "liblog",
+ "libutils",
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_foundation",
+ "libstagefright_simple_c2component",
+ "libstagefright_amrnb_common",
+ ],
+}
+
+//###############################################################################
+cc_library_shared {
+ name: "libstagefright_soft_c2amrwbdec",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ srcs: ["C2SoftAMR.cpp",],
+
+ include_dirs: [
+ "frameworks/av/media/libstagefright/codecs/amrwb/src",
+ ],
+
+ local_include_dirs: ["src"],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ "unsigned-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+
+ static_libs: [
+ "libstagefright_amrnbdec",
+ "libstagefright_amrwbdec",
+ ],
+
+ shared_libs: [
+ "liblog",
+ "libutils",
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_foundation",
+ "libstagefright_simple_c2component",
+ "libstagefright_amrnb_common",
+ ],
+}
\ No newline at end of file
diff --git a/media/libstagefright/codecs/amrnb/dec/C2SoftAMR.cpp b/media/libstagefright/codecs/amrnb/dec/C2SoftAMR.cpp
new file mode 100644
index 0000000..1d4928a
--- /dev/null
+++ b/media/libstagefright/codecs/amrnb/dec/C2SoftAMR.cpp
@@ -0,0 +1,348 @@
+/*
+ * 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 "C2SoftAMR"
+#include <utils/Log.h>
+
+#include "C2SoftAMR.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+#include "gsmamr_dec.h"
+#include "pvamrwbdecoder.h"
+
+namespace android {
+
+#ifdef AMRNB
+ constexpr char kComponentName[] = "c2.google.amrnb.decoder";
+#else
+ constexpr char kComponentName[] = "c2.google.amrwb.decoder";
+#endif
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+ const char *name, c2_node_id_t id,
+ std::function<void(C2ComponentInterface*)> deleter =
+ std::default_delete<C2ComponentInterface>()) {
+ return SimpleC2Interface::Builder(name, id, deleter)
+ .inputFormat(C2FormatCompressed)
+ .outputFormat(C2FormatAudio)
+ .inputMediaType(
+#ifdef AMRNB
+ MEDIA_MIMETYPE_AUDIO_AMR_NB
+#else
+ MEDIA_MIMETYPE_AUDIO_AMR_WB
+#endif
+ )
+ .outputMediaType(MEDIA_MIMETYPE_AUDIO_RAW)
+ .build();
+}
+
+C2SoftAMR::C2SoftAMR(const char *name, c2_node_id_t id)
+ : SimpleC2Component(BuildIntf(name, id)),
+ mAmrHandle(nullptr),
+ mDecoderBuf(nullptr),
+ mDecoderCookie(nullptr) {
+}
+
+C2SoftAMR::~C2SoftAMR() {
+ (void)onRelease();
+}
+
+c2_status_t C2SoftAMR::onInit() {
+ status_t err = initDecoder();
+ return err == OK ? C2_OK : C2_NO_MEMORY;
+}
+
+c2_status_t C2SoftAMR::onStop() {
+ if (!mIsWide) {
+ Speech_Decode_Frame_reset(mAmrHandle);
+ } else {
+ pvDecoder_AmrWb_Reset(mAmrHandle, 0 /* reset_all */);
+ }
+ mSignalledError = false;
+ mSignalledOutputEos = false;
+
+ return C2_OK;
+}
+
+void C2SoftAMR::onReset() {
+ (void)onStop();
+}
+
+void C2SoftAMR::onRelease() {
+ if (!mIsWide) {
+ GSMDecodeFrameExit(&mAmrHandle);
+ mAmrHandle = nullptr;
+ } else {
+ free(mDecoderBuf);
+ mDecoderBuf = nullptr;
+
+ mAmrHandle = nullptr;
+ mDecoderCookie = nullptr;
+ }
+}
+
+c2_status_t C2SoftAMR::onFlush_sm() {
+ return onStop();
+}
+
+status_t C2SoftAMR::initDecoder() {
+#ifdef AMRNB
+ mIsWide = false;
+#else
+ mIsWide = true;
+#endif
+ if (!mIsWide) {
+ if (GSMInitDecode(&mAmrHandle, (int8_t *)"AMRNBDecoder"))
+ return UNKNOWN_ERROR;
+ } else {
+ uint32_t memReq = pvDecoder_AmrWbMemRequirements();
+ mDecoderBuf = malloc(memReq);
+ if (mDecoderBuf) {
+ pvDecoder_AmrWb_Init(&mAmrHandle, mDecoderBuf, &mDecoderCookie);
+ }
+ else {
+ return NO_MEMORY;
+ }
+ }
+ mSignalledError = false;
+ mSignalledOutputEos = false;
+
+ return OK;
+}
+
+static size_t getFrameSize(bool isWide, unsigned FM) {
+ static const size_t kFrameSizeNB[16] = {
+ 12, 13, 15, 17, 19, 20, 26, 31,
+ 5, 6, 5, 5, // SID
+ 0, 0, 0, // future use
+ 0 // no data
+ };
+ static const size_t kFrameSizeWB[16] = {
+ 17, 23, 32, 36, 40, 46, 50, 58, 60,
+ 5, // SID
+ 0, 0, 0, 0, // future use
+ 0, // speech lost
+ 0 // no data
+ };
+
+ if (FM > 15 || (isWide && FM > 9 && FM < 14) || (!isWide && FM > 11 && FM < 15)) {
+ ALOGE("illegal AMR frame mode %d", FM);
+ return 0;
+ }
+ // add 1 for header byte
+ return (isWide ? kFrameSizeWB[FM] : kFrameSizeNB[FM]) + 1;
+}
+
+static status_t calculateNumFrames(const uint8 *input, size_t inSize,
+ std::vector<size_t> *frameSizeList, bool isWide) {
+ for (size_t k = 0; k < inSize;) {
+ int16_t FM = ((input[0] >> 3) & 0x0f);
+ size_t frameSize = getFrameSize(isWide, FM);
+ if (frameSize == 0) return UNKNOWN_ERROR;
+ if ((inSize - k) >= frameSize) {
+ input += frameSize;
+ k += frameSize;
+ }
+ else break;
+ frameSizeList->push_back(frameSize);
+ }
+ return OK;
+}
+
+void C2SoftAMR::process(
+ const std::unique_ptr<C2Work> &work,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ work->result = C2_OK;
+ work->workletsProcessed = 0u;
+ if (mSignalledError || mSignalledOutputEos) {
+ work->result = C2_BAD_VALUE;
+ return;
+ }
+
+ const C2ConstLinearBlock inBuffer =
+ work->input.buffers[0]->data().linearBlocks().front();
+ C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
+ size_t inOffset = inBuffer.offset();
+ size_t inSize = inBuffer.size();
+ bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0;
+
+ if (inSize && rView.error()) {
+ ALOGE("read view map failed %d", rView.error());
+ work->result = rView.error();
+ return;
+ }
+ if (inSize == 0) {
+ work->worklets.front()->output.flags = work->input.flags;
+ work->worklets.front()->output.buffers.clear();
+ work->worklets.front()->output.ordinal = work->input.ordinal;
+ work->workletsProcessed = 1u;
+ if (eos) {
+ mSignalledOutputEos = true;
+ ALOGV("signalled EOS");
+ }
+ return;
+ }
+
+ ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize,
+ (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku());
+
+ std::vector<size_t> frameSizeList;
+ if (OK != calculateNumFrames(rView.data() + inOffset, inSize, &frameSizeList,
+ mIsWide)) {
+ work->result = C2_CORRUPTED;
+ mSignalledError = true;
+ return;
+ }
+ if (frameSizeList.empty()) {
+ ALOGE("input size smaller than expected");
+ work->result = C2_CORRUPTED;
+ mSignalledError = true;
+ return;
+ }
+
+ int16_t outSamples = mIsWide ? kNumSamplesPerFrameWB : kNumSamplesPerFrameNB;
+ size_t calOutSize = outSamples * frameSizeList.size() * sizeof(int16_t);
+ std::shared_ptr<C2LinearBlock> block;
+ C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+ c2_status_t err = pool->fetchLinearBlock(calOutSize, usage, &block);
+ if (err != C2_OK) {
+ ALOGE("fetchLinearBlock for Output failed with status %d", err);
+ work->result = C2_NO_MEMORY;
+ return;
+ }
+ C2WriteView wView = block->map().get();
+ if (wView.error()) {
+ ALOGE("write view map failed %d", wView.error());
+ work->result = wView.error();
+ return;
+ }
+
+ int16_t *output = reinterpret_cast<int16_t *>(wView.data());
+ auto it = frameSizeList.begin();
+ const uint8_t *inPtr = rView.data() + inOffset;
+ size_t inPos = 0;
+ while (inPos < inSize) {
+ if (it == frameSizeList.end()) {
+ ALOGD("unexpected trailing bytes, ignoring them");
+ break;
+ }
+ uint8_t *input = const_cast<uint8_t *>(inPtr + inPos);
+ int16_t FM = ((*input >> 3) & 0x0f);
+ if (!mIsWide) {
+ int32_t numBytesRead = AMRDecode(mAmrHandle,
+ (Frame_Type_3GPP) FM,
+ input + 1, output, MIME_IETF);
+ if (static_cast<size_t>(numBytesRead + 1) != *it) {
+ ALOGE("panic, parsed size does not match decoded size");
+ work->result = C2_CORRUPTED;
+ mSignalledError = true;
+ return;
+ }
+ } else {
+ if (FM >= 9) {
+ // Produce silence instead of comfort noise and for
+ // speech lost/no data.
+ memset(output, 0, outSamples * sizeof(int16_t));
+ } else {
+ int16_t FT;
+ RX_State_wb rx_state;
+ int16_t numRecSamples;
+
+ mime_unsorting(const_cast<uint8_t *>(&input[1]),
+ mInputSampleBuffer, &FT, &FM, 1, &rx_state);
+ pvDecoder_AmrWb(FM, mInputSampleBuffer, output, &numRecSamples,
+ mDecoderBuf, FT, mDecoderCookie);
+ if (numRecSamples != outSamples) {
+ ALOGE("Sample output per frame incorrect");
+ work->result = C2_CORRUPTED;
+ mSignalledError = true;
+ return;
+ }
+ /* Delete the 2 LSBs (14-bit output) */
+ for (int i = 0; i < numRecSamples; ++i) {
+ output[i] &= 0xfffC;
+ }
+ }
+ }
+ inPos += *it;
+ output += outSamples;
+ ++it;
+ }
+
+ work->worklets.front()->output.flags = work->input.flags;
+ work->worklets.front()->output.buffers.clear();
+ work->worklets.front()->output.buffers.push_back(createLinearBuffer(block));
+ work->worklets.front()->output.ordinal = work->input.ordinal;
+ work->workletsProcessed = 1u;
+ if (eos) {
+ mSignalledOutputEos = true;
+ ALOGV("signalled EOS");
+ }
+}
+
+c2_status_t C2SoftAMR::drain(
+ uint32_t drainMode,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ (void) pool;
+ if (drainMode == NO_DRAIN) {
+ ALOGW("drain with NO_DRAIN: no-op");
+ return C2_OK;
+ }
+ if (drainMode == DRAIN_CHAIN) {
+ ALOGW("DRAIN_CHAIN not supported");
+ return C2_OMITTED;
+ }
+ return C2_OK;
+}
+
+class C2SoftAMRDecFactory : public C2ComponentFactory {
+public:
+ virtual c2_status_t createComponent(
+ c2_node_id_t id,
+ std::shared_ptr<C2Component>* const component,
+ std::function<void(C2Component*)> deleter) override {
+ *component = std::shared_ptr<C2Component>(new C2SoftAMR(kComponentName, id), deleter);
+ return C2_OK;
+ }
+
+ virtual c2_status_t createInterface(
+ c2_node_id_t id,
+ std::shared_ptr<C2ComponentInterface>* const interface,
+ std::function<void(C2ComponentInterface*)> deleter) override {
+ *interface = BuildIntf(kComponentName, id, deleter);
+ return C2_OK;
+ }
+
+ virtual ~C2SoftAMRDecFactory() override = default;
+};
+
+} // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+ ALOGV("in %s", __func__);
+ return new ::android::C2SoftAMRDecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+ ALOGV("in %s", __func__);
+ delete factory;
+}
diff --git a/media/libstagefright/codecs/amrnb/dec/C2SoftAMR.h b/media/libstagefright/codecs/amrnb/dec/C2SoftAMR.h
new file mode 100644
index 0000000..69fe213
--- /dev/null
+++ b/media/libstagefright/codecs/amrnb/dec/C2SoftAMR.h
@@ -0,0 +1,67 @@
+/*
+ * 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 C2_SOFT_AMR_H_
+#define C2_SOFT_AMR_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+
+namespace android {
+
+struct C2SoftAMR : public SimpleC2Component {
+ C2SoftAMR(const char *name, c2_node_id_t id);
+ virtual ~C2SoftAMR();
+
+ // 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;
+private:
+ enum {
+ kSampleRateNB = 8000,
+ kSampleRateWB = 16000,
+ kNumSamplesPerFrameNB = 160,
+ kNumSamplesPerFrameWB = 320,
+ };
+
+ void *mAmrHandle;
+ void *mDecoderBuf;
+ int16_t *mDecoderCookie;
+
+ int16_t mInputSampleBuffer[477];
+
+ bool mIsWide;
+ bool mSignalledError;
+ bool mSignalledOutputEos;
+
+ status_t initDecoder();
+
+ DISALLOW_EVIL_CONSTRUCTORS(C2SoftAMR);
+};
+
+} // namespace android
+
+#endif // C2_SOFT_AMR_H_
diff --git a/media/libstagefright/codecs/amrwbenc/Android.bp b/media/libstagefright/codecs/amrwbenc/Android.bp
index ebe08c6..88e9206 100644
--- a/media/libstagefright/codecs/amrwbenc/Android.bp
+++ b/media/libstagefright/codecs/amrwbenc/Android.bp
@@ -185,4 +185,46 @@
//###############################################################################
+cc_library_shared {
+ name: "libstagefright_soft_c2amrwbenc",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ srcs: ["C2SoftAmrWbEnc.cpp"],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ "unsigned-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+
+ static_libs: [
+ "libstagefright_amrwbenc",
+ ],
+
+ shared_libs: [
+ "libutils",
+ "liblog",
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_foundation",
+ "libstagefright_simple_c2component",
+ "libstagefright_enc_common",
+ ],
+}
+
+//###############################################################################
+
subdirs = ["SampleCode"]
diff --git a/media/libstagefright/codecs/amrwbenc/C2SoftAmrWbEnc.cpp b/media/libstagefright/codecs/amrwbenc/C2SoftAmrWbEnc.cpp
new file mode 100644
index 0000000..2a831e5
--- /dev/null
+++ b/media/libstagefright/codecs/amrwbenc/C2SoftAmrWbEnc.cpp
@@ -0,0 +1,325 @@
+/*
+ * 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 "C2SoftAmrWbEnc"
+#include <utils/Log.h>
+
+#include "C2SoftAmrWbEnc.h"
+
+#include "cmnMemory.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+namespace android {
+
+constexpr char kComponentName[] = "c2.google.amrwb.encoder";
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+ const char *name, c2_node_id_t id,
+ std::function<void(C2ComponentInterface*)> deleter =
+ std::default_delete<C2ComponentInterface>()) {
+ return SimpleC2Interface::Builder(name, id, deleter)
+ .inputFormat(C2FormatAudio)
+ .outputFormat(C2FormatCompressed)
+ .inputMediaType(MEDIA_MIMETYPE_AUDIO_RAW)
+ .outputMediaType(MEDIA_MIMETYPE_AUDIO_AMR_WB)
+ .build();
+}
+
+C2SoftAmrWbEnc::C2SoftAmrWbEnc(const char *name, c2_node_id_t id)
+ : SimpleC2Component(BuildIntf(name, id)),
+ mEncoderHandle(nullptr),
+ mApiHandle(nullptr),
+ mMemOperator(nullptr) {
+}
+
+C2SoftAmrWbEnc::~C2SoftAmrWbEnc() {
+ onRelease();
+}
+
+c2_status_t C2SoftAmrWbEnc::onInit() {
+ status_t err = initEncoder();
+ mMode = VOAMRWB_MD2305;
+ mIsFirst = true;
+ mSignalledError = false;
+ mSignalledOutputEos = false;
+ mAnchorTimeStamp = 0;
+ mProcessedSamples = 0;
+ mFilledLen = 0;
+
+ return err == OK ? C2_OK : C2_NO_MEMORY;
+}
+
+void C2SoftAmrWbEnc::onRelease() {
+ if (mEncoderHandle) {
+ CHECK_EQ((VO_U32)VO_ERR_NONE, mApiHandle->Uninit(mEncoderHandle));
+ mEncoderHandle = nullptr;
+ }
+ if (mApiHandle) {
+ delete mApiHandle;
+ mApiHandle = nullptr;
+ }
+ if (mMemOperator) {
+ delete mMemOperator;
+ mMemOperator = nullptr;
+ }
+}
+
+c2_status_t C2SoftAmrWbEnc::onStop() {
+ for (int i = 0; i < kNumSamplesPerFrame; i++) {
+ mInputFrame[i] = 0x0008; /* EHF_MASK */
+ }
+ uint8_t outBuffer[kNumBytesPerInputFrame];
+ (void) encodeInput(outBuffer, kNumBytesPerInputFrame);
+ mIsFirst = true;
+ mSignalledError = false;
+ mSignalledOutputEos = false;
+ mAnchorTimeStamp = 0;
+ mProcessedSamples = 0;
+ mFilledLen = 0;
+
+ return C2_OK;
+}
+
+void C2SoftAmrWbEnc::onReset() {
+ (void) onStop();
+}
+
+c2_status_t C2SoftAmrWbEnc::onFlush_sm() {
+ return onStop();
+}
+
+status_t C2SoftAmrWbEnc::initEncoder() {
+ mApiHandle = new VO_AUDIO_CODECAPI;
+ if (!mApiHandle) return NO_MEMORY;
+
+ if (VO_ERR_NONE != voGetAMRWBEncAPI(mApiHandle)) {
+ ALOGE("Failed to get api handle");
+ return UNKNOWN_ERROR;
+ }
+
+ mMemOperator = new VO_MEM_OPERATOR;
+ if (!mMemOperator) return NO_MEMORY;
+
+ mMemOperator->Alloc = cmnMemAlloc;
+ mMemOperator->Copy = cmnMemCopy;
+ mMemOperator->Free = cmnMemFree;
+ mMemOperator->Set = cmnMemSet;
+ mMemOperator->Check = cmnMemCheck;
+
+ VO_CODEC_INIT_USERDATA userData;
+ memset(&userData, 0, sizeof(userData));
+ userData.memflag = VO_IMF_USERMEMOPERATOR;
+ userData.memData = (VO_PTR) mMemOperator;
+
+ if (VO_ERR_NONE != mApiHandle->Init(
+ &mEncoderHandle, VO_AUDIO_CodingAMRWB, &userData)) {
+ ALOGE("Failed to init AMRWB encoder");
+ return UNKNOWN_ERROR;
+ }
+
+ VOAMRWBFRAMETYPE type = VOAMRWB_RFC3267;
+ if (VO_ERR_NONE != mApiHandle->SetParam(
+ mEncoderHandle, VO_PID_AMRWB_FRAMETYPE, &type)) {
+ ALOGE("Failed to set AMRWB encoder frame type to %d", type);
+ return UNKNOWN_ERROR;
+ }
+
+ if (VO_ERR_NONE !=
+ mApiHandle->SetParam(
+ mEncoderHandle, VO_PID_AMRWB_MODE, &mMode)) {
+ ALOGE("Failed to set AMRWB encoder mode to %d", mMode);
+ return UNKNOWN_ERROR;
+ }
+
+ return OK;
+}
+
+int C2SoftAmrWbEnc::encodeInput(uint8_t *buffer, uint32_t length) {
+ VO_CODECBUFFER inputData;
+ memset(&inputData, 0, sizeof(inputData));
+ inputData.Buffer = (unsigned char *) mInputFrame;
+ inputData.Length = kNumBytesPerInputFrame;
+
+ CHECK_EQ((VO_U32)VO_ERR_NONE,
+ mApiHandle->SetInputData(mEncoderHandle, &inputData));
+
+ VO_AUDIO_OUTPUTINFO outputInfo;
+ memset(&outputInfo, 0, sizeof(outputInfo));
+ VO_CODECBUFFER outputData;
+ memset(&outputData, 0, sizeof(outputData));
+ outputData.Buffer = buffer;
+ outputData.Length = length;
+ VO_U32 ret = mApiHandle->GetOutputData(
+ mEncoderHandle, &outputData, &outputInfo);
+ if (ret != VO_ERR_NONE && ret != VO_ERR_INPUT_BUFFER_SMALL) {
+ ALOGD("encountered error during encode call");
+ return -1;
+ }
+ return outputData.Length;
+}
+
+static void 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.ordinal = work->input.ordinal;
+ work->workletsProcessed = 1u;
+}
+
+void C2SoftAmrWbEnc::process(
+ const std::unique_ptr<C2Work> &work,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ work->result = C2_OK;
+ work->workletsProcessed = 0u;
+ if (mSignalledError || mSignalledOutputEos) {
+ work->result = C2_BAD_VALUE;
+ return;
+ }
+
+ const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+ bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
+ size_t inOffset = inBuffer.offset();
+ size_t inSize = inBuffer.size();
+ C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
+ if (inSize && rView.error()) {
+ ALOGE("read view map failed %d", rView.error());
+ work->result = rView.error();
+ return;
+ }
+
+ ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x",
+ inSize, (int)work->input.ordinal.timestamp.peeku(),
+ (int)work->input.ordinal.frameIndex.peeku(), work->input.flags);
+
+ size_t outCapacity = kNumBytesPerInputFrame;
+ outCapacity += mFilledLen + inSize;
+ std::shared_ptr<C2LinearBlock> outputBlock;
+ C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+ c2_status_t err = pool->fetchLinearBlock(outCapacity, usage, &outputBlock);
+ if (err != C2_OK) {
+ ALOGE("fetchLinearBlock for Output failed with status %d", err);
+ work->result = C2_NO_MEMORY;
+ return;
+ }
+ C2WriteView wView = outputBlock->map().get();
+ if (wView.error()) {
+ ALOGE("write view map failed %d", wView.error());
+ work->result = wView.error();
+ return;
+ }
+
+ uint64_t outTimeStamp = mProcessedSamples * 1000000ll / kSampleRate;
+ const uint8_t *inPtr = rView.data() + inOffset;
+ size_t inPos = 0;
+ size_t outPos = 0;
+ while (inPos < inSize) {
+ int validSamples = mFilledLen / sizeof(int16_t);
+ if ((inPos + (kNumBytesPerInputFrame - mFilledLen)) <= inSize) {
+ memcpy(mInputFrame + validSamples, inPtr + inPos,
+ (kNumBytesPerInputFrame - mFilledLen));
+ inPos += (kNumBytesPerInputFrame - mFilledLen);
+ } else {
+ memcpy(mInputFrame + validSamples, inPtr + inPos, (inSize - inPos));
+ mFilledLen += (inSize - inPos);
+ inPos += (inSize - inPos);
+ if (eos) {
+ validSamples = mFilledLen / sizeof(int16_t);
+ memset(mInputFrame + validSamples, 0, (kNumBytesPerInputFrame - mFilledLen));
+ } else break;
+ }
+ int numEncBytes = encodeInput((wView.data() + outPos), outCapacity - outPos);
+ if (numEncBytes < 0) {
+ ALOGE("encodeFrame call failed, state [%d %zu %zu]", numEncBytes, outPos, outCapacity);
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+ outPos += numEncBytes;
+ mProcessedSamples += kNumSamplesPerFrame;
+ mFilledLen = 0;
+ }
+ ALOGV("causal sample size %d", mFilledLen);
+ if (mIsFirst) {
+ mIsFirst = false;
+ mAnchorTimeStamp = work->input.ordinal.timestamp.peekull();
+ }
+ fillEmptyWork(work);
+ if (outPos != 0) {
+ work->worklets.front()->output.buffers.push_back(
+ createLinearBuffer(std::move(outputBlock), 0, outPos));
+ work->worklets.front()->output.ordinal.timestamp = mAnchorTimeStamp + outTimeStamp;
+ }
+ if (eos) {
+ mSignalledOutputEos = true;
+ ALOGV("signalled EOS");
+ if (mFilledLen) ALOGV("Discarding trailing %d bytes", mFilledLen);
+ }
+}
+
+c2_status_t C2SoftAmrWbEnc::drain(
+ uint32_t drainMode,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ (void) pool;
+ if (drainMode == NO_DRAIN) {
+ ALOGW("drain with NO_DRAIN: no-op");
+ return C2_OK;
+ }
+ if (drainMode == DRAIN_CHAIN) {
+ ALOGW("DRAIN_CHAIN not supported");
+ return C2_OMITTED;
+ }
+
+ onFlush_sm();
+ return C2_OK;
+}
+
+class C2SoftAmrWbEncFactory : public C2ComponentFactory {
+public:
+ virtual c2_status_t createComponent(
+ c2_node_id_t id,
+ std::shared_ptr<C2Component>* const component,
+ std::function<void(C2Component*)> deleter) override {
+ *component = std::shared_ptr<C2Component>(new C2SoftAmrWbEnc(kComponentName, id), deleter);
+ return C2_OK;
+ }
+
+ virtual c2_status_t createInterface(
+ c2_node_id_t id,
+ std::shared_ptr<C2ComponentInterface>* const interface,
+ std::function<void(C2ComponentInterface*)> deleter) override {
+ *interface = BuildIntf(kComponentName, id, deleter);
+ return C2_OK;
+ }
+
+ virtual ~C2SoftAmrWbEncFactory() override = default;
+};
+
+} // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+ ALOGV("in %s", __func__);
+ return new ::android::C2SoftAmrWbEncFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+ ALOGV("in %s", __func__);
+ delete factory;
+}
diff --git a/media/libstagefright/codecs/amrwbenc/C2SoftAmrWbEnc.h b/media/libstagefright/codecs/amrwbenc/C2SoftAmrWbEnc.h
new file mode 100644
index 0000000..29e68ac
--- /dev/null
+++ b/media/libstagefright/codecs/amrwbenc/C2SoftAmrWbEnc.h
@@ -0,0 +1,71 @@
+/*
+ * 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 C2_SOFT_AMRWB_ENC_H_
+#define C2_SOFT_AMRWB_ENC_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+
+#include "voAMRWB.h"
+
+namespace android {
+
+class C2SoftAmrWbEnc : public SimpleC2Component {
+public:
+ C2SoftAmrWbEnc(const char *name, c2_node_id_t id);
+ virtual ~C2SoftAmrWbEnc();
+
+ // 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;
+
+private:
+ static const int32_t kNumSamplesPerFrame = 320;
+ static const int32_t kNumBytesPerInputFrame = kNumSamplesPerFrame * sizeof(int16_t);
+ static const int32_t kSampleRate = 16000;
+
+ void *mEncoderHandle;
+ VO_AUDIO_CODECAPI *mApiHandle;
+ VO_MEM_OPERATOR *mMemOperator;
+ VOAMRWBMODE mMode;
+ bool mIsFirst;
+ bool mSignalledError;
+ bool mSignalledOutputEos;
+ uint64_t mAnchorTimeStamp;
+ uint64_t mProcessedSamples;
+ int32_t mFilledLen;
+ int16_t mInputFrame[kNumSamplesPerFrame];
+
+ status_t initEncoder();
+ int encodeInput(uint8_t *buffer, uint32_t length);
+
+ DISALLOW_EVIL_CONSTRUCTORS(C2SoftAmrWbEnc);
+};
+
+} // namespace android
+
+#endif // C2_SOFT_AMRWB_ENC_H_
diff --git a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
index 0f1fecc..87138af 100644
--- a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
+++ b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
@@ -48,6 +48,7 @@
}
C2SoftFlacDecoder::~C2SoftFlacDecoder() {
+ delete mFLACDecoder;
}
c2_status_t C2SoftFlacDecoder::onInit() {
@@ -77,6 +78,9 @@
}
status_t C2SoftFlacDecoder::initDecoder() {
+ if (mFLACDecoder) {
+ delete mFLACDecoder;
+ }
mFLACDecoder = FLACDecoder::Create();
if (!mFLACDecoder) {
ALOGE("initDecoder: failed to create FLACDecoder");
diff --git a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
index a5c01a9..43d913b 100644
--- a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
+++ b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
@@ -46,7 +46,7 @@
kMaxBlockSize = 4096
};
- sp<FLACDecoder> mFLACDecoder;
+ FLACDecoder *mFLACDecoder;
FLAC__StreamMetadata_StreamInfo mStreamInfo;
bool mSignalledError;
bool mSignalledOutputEos;
diff --git a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
index 4ab1ab2..d0b72b7 100644
--- a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
+++ b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
@@ -57,6 +57,7 @@
SoftFlacDecoder::~SoftFlacDecoder() {
ALOGV("dtor:");
+ delete mFLACDecoder;
}
void SoftFlacDecoder::initPorts() {
diff --git a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h
index 4a21c34..0f17ed8 100644
--- a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h
+++ b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.h
@@ -50,7 +50,7 @@
kNumOutputBuffers = 4,
};
- sp<FLACDecoder> mFLACDecoder;
+ FLACDecoder *mFLACDecoder;
FLAC__StreamMetadata_StreamInfo mStreamInfo;
bool mHasStreamInfo;
size_t mInputBufferCount;
diff --git a/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.cpp b/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.cpp
index fa93fe5..f94ed49 100644
--- a/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.cpp
+++ b/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.cpp
@@ -181,11 +181,13 @@
mEncoderWriteData = true;
mEncoderReturnedNbBytes = 0;
- while (inOffset < inSize) {
- size_t processSize = MIN(kInBlockSize * mNumChannels * sizeof(int16_t), (inSize - inOffset));
+ size_t inPos = 0;
+ const uint8_t *inPtr = rView.data() + inOffset;
+ while (inPos < inSize) {
+ size_t processSize = MIN(kInBlockSize * mNumChannels * sizeof(int16_t), (inSize - inPos));
const unsigned nbInputFrames = processSize / (mNumChannels * sizeof(int16_t));
const unsigned nbInputSamples = processSize / sizeof(int16_t);
- const int16_t *pcm16 = reinterpret_cast<const int16_t *>(rView.data() + inOffset);
+ const int16_t *pcm16 = reinterpret_cast<const int16_t *>(inPtr + inPos);
ALOGV("about to encode %zu bytes", processSize);
for (unsigned i = 0; i < nbInputSamples; i++) {
@@ -201,7 +203,7 @@
mOutputBlock.reset();
return;
}
- inOffset += processSize;
+ inPos += processSize;
}
if (eos && !drain(DRAIN_COMPONENT_WITH_EOS, pool)) {
ALOGE("error encountered during encoding");
diff --git a/media/libstagefright/codecs/gsm/dec/Android.bp b/media/libstagefright/codecs/gsm/dec/Android.bp
index 1c3208b..c057d8a 100644
--- a/media/libstagefright/codecs/gsm/dec/Android.bp
+++ b/media/libstagefright/codecs/gsm/dec/Android.bp
@@ -1,4 +1,45 @@
cc_library_shared {
+ name: "libstagefright_soft_c2gsmdec",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ srcs: [
+ "C2SoftGSM.cpp"
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ "unsigned-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+
+ shared_libs: [
+ "liblog",
+ "libutils",
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_foundation",
+ "libstagefright_simple_c2component",
+ ],
+
+ static_libs: [
+ "libgsm"
+ ],
+}
+
+cc_library_shared {
name: "libstagefright_soft_gsmdec",
vendor_available: true,
vndk: {
diff --git a/media/libstagefright/codecs/gsm/dec/C2SoftGSM.cpp b/media/libstagefright/codecs/gsm/dec/C2SoftGSM.cpp
new file mode 100644
index 0000000..d021768
--- /dev/null
+++ b/media/libstagefright/codecs/gsm/dec/C2SoftGSM.cpp
@@ -0,0 +1,237 @@
+/*
+ * 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 "C2SoftGSM"
+#include <utils/Log.h>
+
+#include "C2SoftGSM.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+namespace android {
+
+constexpr char kComponentName[] = "c2.google.gsm.decoder";
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+ const char *name, c2_node_id_t id,
+ std::function<void(C2ComponentInterface*)> deleter =
+ std::default_delete<C2ComponentInterface>()) {
+ return SimpleC2Interface::Builder(name, id, deleter)
+ .inputFormat(C2FormatCompressed)
+ .outputFormat(C2FormatAudio)
+ .inputMediaType(MEDIA_MIMETYPE_AUDIO_MSGSM)
+ .outputMediaType(MEDIA_MIMETYPE_AUDIO_RAW)
+ .build();
+}
+
+C2SoftGSM::C2SoftGSM(const char *name, c2_node_id_t id)
+ : SimpleC2Component(BuildIntf(name, id)),
+ mGsm(nullptr) {
+}
+
+C2SoftGSM::~C2SoftGSM() {
+ onRelease();
+}
+
+c2_status_t C2SoftGSM::onInit() {
+ if (!mGsm) mGsm = gsm_create();
+ if (!mGsm) return C2_NO_MEMORY;
+ int msopt = 1;
+ (void)gsm_option(mGsm, GSM_OPT_WAV49, &msopt);
+ mSignalledError = false;
+ mSignalledEos = false;
+ return C2_OK;
+}
+
+c2_status_t C2SoftGSM::onStop() {
+ if (mGsm) {
+ gsm_destroy(mGsm);
+ mGsm = nullptr;
+ }
+ if (!mGsm) mGsm = gsm_create();
+ if (!mGsm) return C2_NO_MEMORY;
+ int msopt = 1;
+ (void)gsm_option(mGsm, GSM_OPT_WAV49, &msopt);
+ mSignalledError = false;
+ mSignalledEos = false;
+ return C2_OK;
+}
+
+void C2SoftGSM::onReset() {
+ (void)onStop();
+}
+
+void C2SoftGSM::onRelease() {
+ if (mGsm) {
+ gsm_destroy(mGsm);
+ mGsm = nullptr;
+ }
+}
+
+c2_status_t C2SoftGSM::onFlush_sm() {
+ return onStop();
+}
+
+static size_t decodeGSM(gsm handle, int16_t *out, size_t outCapacity,
+ uint8_t *in, size_t inSize) {
+ size_t outSize = 0;
+
+ if (inSize % MSGSM_IN_FRM_SZ == 0
+ && (inSize / MSGSM_IN_FRM_SZ * MSGSM_OUT_FRM_SZ * sizeof(*out)
+ <= outCapacity)) {
+ while (inSize > 0) {
+ gsm_decode(handle, in, out);
+ in += FRGSM_IN_FRM_SZ;
+ inSize -= FRGSM_IN_FRM_SZ;
+ out += FRGSM_OUT_FRM_SZ;
+ outSize += FRGSM_OUT_FRM_SZ;
+
+ gsm_decode(handle, in, out);
+ in += FRGSM_IN_FRM_SZ_MINUS_1;
+ inSize -= FRGSM_IN_FRM_SZ_MINUS_1;
+ out += FRGSM_OUT_FRM_SZ;
+ outSize += FRGSM_OUT_FRM_SZ;
+ }
+ }
+
+ return outSize * sizeof(int16_t);
+}
+
+void C2SoftGSM::process(
+ const std::unique_ptr<C2Work> &work,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ work->result = C2_OK;
+ work->workletsProcessed = 0u;
+ if (mSignalledError || mSignalledEos) {
+ work->result = C2_BAD_VALUE;
+ return;
+ }
+
+ const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+ bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
+ C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
+ size_t inOffset = inBuffer.offset();
+ size_t inSize = inBuffer.size();
+ if (inSize && rView.error()) {
+ ALOGE("read view map failed %d", rView.error());
+ work->result = rView.error();
+ return;
+ }
+
+ if (inSize == 0) {
+ work->worklets.front()->output.flags = work->input.flags;
+ work->worklets.front()->output.buffers.clear();
+ work->worklets.front()->output.ordinal = work->input.ordinal;
+ work->workletsProcessed = 1u;
+ if (eos) {
+ mSignalledEos = true;
+ ALOGV("signalled EOS");
+ }
+ return;
+ }
+ ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize,
+ (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku());
+
+ size_t outCapacity = (inSize / MSGSM_IN_FRM_SZ ) * MSGSM_OUT_FRM_SZ * sizeof(int16_t);
+ std::shared_ptr<C2LinearBlock> block;
+ C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+ c2_status_t err = pool->fetchLinearBlock(outCapacity, usage, &block);
+ if (err != C2_OK) {
+ ALOGE("fetchLinearBlock for Output failed with status %d", err);
+ work->result = C2_NO_MEMORY;
+ return;
+ }
+ C2WriteView wView = block->map().get();
+ if (wView.error()) {
+ ALOGE("write view map failed %d", wView.error());
+ work->result = wView.error();
+ return;
+ }
+
+ int16_t *output = reinterpret_cast<int16_t *>(wView.data());
+ uint8_t *input = const_cast<uint8_t *>(rView.data() + inOffset);
+ size_t outSize = decodeGSM(mGsm, output, outCapacity, input, inSize);
+ if (!outSize) {
+ ALOGE("encountered improper insize or outsize");
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+ ALOGV("out buffer attr. size %zu", outSize);
+ work->worklets.front()->output.flags = work->input.flags;
+ work->worklets.front()->output.buffers.clear();
+ work->worklets.front()->output.buffers.push_back(createLinearBuffer(block, 0, outSize));
+ work->worklets.front()->output.ordinal = work->input.ordinal;
+ work->workletsProcessed = 1u;
+ if (eos) {
+ mSignalledEos = true;
+ ALOGV("signalled EOS");
+ }
+}
+
+c2_status_t C2SoftGSM::drain(
+ uint32_t drainMode,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ (void) pool;
+ if (drainMode == NO_DRAIN) {
+ ALOGW("drain with NO_DRAIN: no-op");
+ return C2_OK;
+ }
+ if (drainMode == DRAIN_CHAIN) {
+ ALOGW("DRAIN_CHAIN not supported");
+ return C2_OMITTED;
+ }
+
+ return C2_OK;
+}
+
+class C2SoftGSMDecFactory : public C2ComponentFactory {
+public:
+ virtual c2_status_t createComponent(
+ c2_node_id_t id,
+ std::shared_ptr<C2Component>* const component,
+ std::function<void(C2Component*)> deleter) override {
+ *component = std::shared_ptr<C2Component>(new C2SoftGSM(kComponentName, id), deleter);
+ return C2_OK;
+ }
+
+ virtual c2_status_t createInterface(
+ c2_node_id_t id,
+ std::shared_ptr<C2ComponentInterface>* const interface,
+ std::function<void(C2ComponentInterface*)> deleter) override {
+ *interface = BuildIntf(kComponentName, id, deleter);
+ return C2_OK;
+ }
+
+ virtual ~C2SoftGSMDecFactory() override = default;
+};
+
+} // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+ ALOGV("in %s", __func__);
+ return new ::android::C2SoftGSMDecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+ ALOGV("in %s", __func__);
+ delete factory;
+}
diff --git a/media/libstagefright/codecs/gsm/dec/C2SoftGSM.h b/media/libstagefright/codecs/gsm/dec/C2SoftGSM.h
new file mode 100644
index 0000000..597711c
--- /dev/null
+++ b/media/libstagefright/codecs/gsm/dec/C2SoftGSM.h
@@ -0,0 +1,62 @@
+/*
+ * 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 C2_SOFT_GSM_H_
+#define C2_SOFT_GSM_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+
+extern "C" {
+ #include "gsm.h"
+}
+
+namespace android {
+
+#define FRGSM_IN_FRM_SZ 33
+#define FRGSM_IN_FRM_SZ_MINUS_1 32
+#define FRGSM_OUT_FRM_SZ 160
+#define MSGSM_IN_FRM_SZ (FRGSM_IN_FRM_SZ + FRGSM_IN_FRM_SZ_MINUS_1)
+#define MSGSM_OUT_FRM_SZ (FRGSM_OUT_FRM_SZ * 2)
+
+struct C2SoftGSM : public SimpleC2Component {
+ C2SoftGSM(const char *name, c2_node_id_t id);
+ virtual ~C2SoftGSM();
+
+ // 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;
+ private:
+ gsm mGsm;
+ bool mSignalledError;
+ bool mSignalledEos;
+
+ DISALLOW_EVIL_CONSTRUCTORS(C2SoftGSM);
+};
+
+} // namespace android
+
+#endif // C2_SOFT_GSM_H_
diff --git a/media/libstagefright/codecs/hevcdec/Android.bp b/media/libstagefright/codecs/hevcdec/Android.bp
index 45920e6..385807a 100644
--- a/media/libstagefright/codecs/hevcdec/Android.bp
+++ b/media/libstagefright/codecs/hevcdec/Android.bp
@@ -46,3 +46,48 @@
ldflags: ["-Wl,-Bsymbolic"],
compile_multilib: "32",
}
+
+cc_library_shared {
+ name: "libstagefright_soft_c2hevcdec",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ srcs: ["C2SoftHevcDec.cpp"],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ include_dirs: [
+ "external/libhevc/decoder",
+ "external/libhevc/common",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ ],
+ cfi: false, // true,
+ diag: {
+ cfi: false, // true,
+ },
+ },
+
+ static_libs: ["libhevcdec"],
+
+ shared_libs: [
+ "liblog",
+ "libutils",
+ "libmedia",
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_foundation",
+ "libstagefright_simple_c2component",
+ ],
+
+ ldflags: ["-Wl,-Bsymbolic"],
+
+}
diff --git a/media/libstagefright/codecs/hevcdec/C2SoftHevcDec.cpp b/media/libstagefright/codecs/hevcdec/C2SoftHevcDec.cpp
new file mode 100644
index 0000000..6d208bd
--- /dev/null
+++ b/media/libstagefright/codecs/hevcdec/C2SoftHevcDec.cpp
@@ -0,0 +1,705 @@
+/*
+ * 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 "C2SoftHevcDec"
+#include <utils/Log.h>
+
+#include "ihevc_typedefs.h"
+#include "iv.h"
+#include "ivd.h"
+#include "ihevcd_cxa.h"
+#include "C2SoftHevcDec.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+namespace android {
+
+constexpr char kComponentName[] = "c2.google.hevc.decoder";
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+ const char *name, c2_node_id_t id,
+ std::function<void(C2ComponentInterface*)> deleter =
+ std::default_delete<C2ComponentInterface>()) {
+ return SimpleC2Interface::Builder(name, id, deleter)
+ .inputFormat(C2FormatCompressed)
+ .outputFormat(C2FormatVideo)
+ .inputMediaType(MEDIA_MIMETYPE_VIDEO_HEVC)
+ .outputMediaType(MEDIA_MIMETYPE_VIDEO_RAW)
+ .build();
+}
+
+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;
+}
+
+static void *ivd_aligned_malloc(void *ctxt, WORD32 alignment, WORD32 size) {
+ (void) ctxt;
+ return memalign(alignment, size);
+}
+
+static void ivd_aligned_free(void *ctxt, void *mem) {
+ (void) ctxt;
+ free(mem);
+}
+
+C2SoftHevcDec::C2SoftHevcDec(const char *name, c2_node_id_t id)
+ : SimpleC2Component(BuildIntf(name, id)),
+ mDecHandle(nullptr),
+ mOutBufferFlush(nullptr),
+ mIvColorformat(IV_YUV_420P),
+ mWidth(320),
+ mHeight(240) {
+}
+
+C2SoftHevcDec::~C2SoftHevcDec() {
+ onRelease();
+}
+
+c2_status_t C2SoftHevcDec::onInit() {
+ status_t err = initDecoder();
+ return err == OK ? C2_OK : C2_CORRUPTED;
+}
+
+c2_status_t C2SoftHevcDec::onStop() {
+ if (OK != resetDecoder()) return C2_CORRUPTED;
+ resetPlugin();
+ return C2_OK;
+}
+
+void C2SoftHevcDec::onReset() {
+ (void) onStop();
+}
+
+void C2SoftHevcDec::onRelease() {
+ (void) deleteDecoder();
+ if (mOutBufferFlush) {
+ ivd_aligned_free(nullptr, mOutBufferFlush);
+ mOutBufferFlush = nullptr;
+ }
+ if (mOutBlock) {
+ mOutBlock.reset();
+ }
+}
+
+c2_status_t C2SoftHevcDec::onFlush_sm() {
+ if (OK != setFlushMode()) return C2_CORRUPTED;
+
+ uint32_t displayStride = mStride;
+ uint32_t displayHeight = mHeight;
+ uint32_t bufferSize = displayStride * displayHeight * 3 / 2;
+ mOutBufferFlush = (uint8_t *)ivd_aligned_malloc(nullptr, 128, bufferSize);
+ if (!mOutBufferFlush) {
+ ALOGE("could not allocate tmp output buffer (for flush) of size %u ", bufferSize);
+ return C2_NO_MEMORY;
+ }
+
+ while (true) {
+ ivd_video_decode_ip_t s_decode_ip;
+ ivd_video_decode_op_t s_decode_op;
+
+ setDecodeArgs(&s_decode_ip, &s_decode_op, nullptr, nullptr, 0, 0, 0);
+ (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op);
+ if (0 == s_decode_op.u4_output_present) {
+ resetPlugin();
+ break;
+ }
+ }
+
+ ivd_aligned_free(nullptr, mOutBufferFlush);
+ mOutBufferFlush = nullptr;
+
+ return C2_OK;
+}
+
+status_t C2SoftHevcDec::createDecoder() {
+ ivdext_create_ip_t s_create_ip;
+ ivdext_create_op_t s_create_op;
+
+ s_create_ip.s_ivd_create_ip_t.u4_size = sizeof(ivdext_create_ip_t);
+ s_create_ip.s_ivd_create_ip_t.e_cmd = IVD_CMD_CREATE;
+ s_create_ip.s_ivd_create_ip_t.u4_share_disp_buf = 0;
+ s_create_ip.s_ivd_create_ip_t.e_output_format = mIvColorformat;
+ s_create_ip.s_ivd_create_ip_t.pf_aligned_alloc = ivd_aligned_malloc;
+ s_create_ip.s_ivd_create_ip_t.pf_aligned_free = ivd_aligned_free;
+ s_create_ip.s_ivd_create_ip_t.pv_mem_ctxt = nullptr;
+ s_create_op.s_ivd_create_op_t.u4_size = sizeof(ivdext_create_op_t);
+ IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+ &s_create_ip,
+ &s_create_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("error in %s: 0x%x", __func__,
+ s_create_op.s_ivd_create_op_t.u4_error_code);
+ return UNKNOWN_ERROR;
+ }
+ mDecHandle = (iv_obj_t*)s_create_op.s_ivd_create_op_t.pv_handle;
+ mDecHandle->pv_fxns = (void *)ivdec_api_function;
+ mDecHandle->u4_size = sizeof(iv_obj_t);
+
+ return OK;
+}
+
+status_t C2SoftHevcDec::setNumCores() {
+ ivdext_ctl_set_num_cores_ip_t s_set_num_cores_ip;
+ ivdext_ctl_set_num_cores_op_t s_set_num_cores_op;
+
+ s_set_num_cores_ip.u4_size = sizeof(ivdext_ctl_set_num_cores_ip_t);
+ s_set_num_cores_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+ s_set_num_cores_ip.e_sub_cmd = IVDEXT_CMD_CTL_SET_NUM_CORES;
+ s_set_num_cores_ip.u4_num_cores = mNumCores;
+ s_set_num_cores_op.u4_size = sizeof(ivdext_ctl_set_num_cores_op_t);
+ IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+ &s_set_num_cores_ip,
+ &s_set_num_cores_op);
+ if (IV_SUCCESS != status) {
+ ALOGD("error in %s: 0x%x", __func__, s_set_num_cores_op.u4_error_code);
+ return UNKNOWN_ERROR;
+ }
+
+ return OK;
+}
+
+status_t C2SoftHevcDec::setParams(size_t stride) {
+ ivd_ctl_set_config_ip_t s_set_dyn_params_ip;
+ ivd_ctl_set_config_op_t s_set_dyn_params_op;
+
+ s_set_dyn_params_ip.u4_size = sizeof(ivd_ctl_set_config_ip_t);
+ s_set_dyn_params_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+ s_set_dyn_params_ip.e_sub_cmd = IVD_CMD_CTL_SETPARAMS;
+ s_set_dyn_params_ip.u4_disp_wd = (UWORD32) stride;
+ s_set_dyn_params_ip.e_frm_skip_mode = IVD_SKIP_NONE;
+ s_set_dyn_params_ip.e_frm_out_mode = IVD_DISPLAY_FRAME_OUT;
+ s_set_dyn_params_ip.e_vid_dec_mode = IVD_DECODE_FRAME;
+ s_set_dyn_params_op.u4_size = sizeof(ivd_ctl_set_config_op_t);
+ IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+ &s_set_dyn_params_ip,
+ &s_set_dyn_params_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("error in %s: 0x%x", __func__, s_set_dyn_params_op.u4_error_code);
+ return UNKNOWN_ERROR;
+ }
+
+ return OK;
+}
+
+status_t C2SoftHevcDec::getVersion() {
+ ivd_ctl_getversioninfo_ip_t s_get_versioninfo_ip;
+ ivd_ctl_getversioninfo_op_t s_get_versioninfo_op;
+ UWORD8 au1_buf[512];
+
+ s_get_versioninfo_ip.u4_size = sizeof(ivd_ctl_getversioninfo_ip_t);
+ s_get_versioninfo_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+ s_get_versioninfo_ip.e_sub_cmd = IVD_CMD_CTL_GETVERSION;
+ s_get_versioninfo_ip.pv_version_buffer = au1_buf;
+ s_get_versioninfo_ip.u4_version_buffer_size = sizeof(au1_buf);
+ s_get_versioninfo_op.u4_size = sizeof(ivd_ctl_getversioninfo_op_t);
+ IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+ &s_get_versioninfo_ip,
+ &s_get_versioninfo_op);
+ if (status != IV_SUCCESS) {
+ ALOGD("error in %s: 0x%x", __func__,
+ s_get_versioninfo_op.u4_error_code);
+ } else {
+ ALOGV("ittiam decoder version number: %s",
+ (char *) s_get_versioninfo_ip.pv_version_buffer);
+ }
+
+ return OK;
+}
+
+status_t C2SoftHevcDec::initDecoder() {
+ if (OK != createDecoder()) return UNKNOWN_ERROR;
+ mNumCores = MIN(getCpuCoreCount(), MAX_NUM_CORES);
+ mStride = ALIGN64(mWidth);
+ mSignalledError = false;
+ mPreference = kPreferBitstream;
+ memset(&mDefaultColorAspects, 0, sizeof(ColorAspects));
+ memset(&mBitstreamColorAspects, 0, sizeof(ColorAspects));
+ memset(&mFinalColorAspects, 0, sizeof(ColorAspects));
+ mUpdateColorAspects = false;
+ resetPlugin();
+ (void) setNumCores();
+ if (OK != setParams(mStride)) return UNKNOWN_ERROR;
+ (void) getVersion();
+
+ return OK;
+}
+
+bool C2SoftHevcDec::setDecodeArgs(ivd_video_decode_ip_t *ps_decode_ip,
+ ivd_video_decode_op_t *ps_decode_op,
+ C2ReadView *inBuffer,
+ C2GraphicView *outBuffer,
+ size_t inOffset,
+ size_t inSize,
+ uint32_t tsMarker) {
+ uint32_t displayStride = mStride;
+ uint32_t displayHeight = mHeight;
+ size_t lumaSize = displayStride * displayHeight;
+ size_t chromaSize = lumaSize >> 2;
+
+ ps_decode_ip->u4_size = sizeof(ivd_video_decode_ip_t);
+ ps_decode_ip->e_cmd = IVD_CMD_VIDEO_DECODE;
+ if (inBuffer) {
+ ps_decode_ip->u4_ts = tsMarker;
+ ps_decode_ip->pv_stream_buffer = const_cast<uint8_t *>(inBuffer->data() + inOffset);
+ ps_decode_ip->u4_num_Bytes = inSize;
+ } else {
+ ps_decode_ip->u4_ts = 0;
+ ps_decode_ip->pv_stream_buffer = nullptr;
+ ps_decode_ip->u4_num_Bytes = 0;
+ }
+ ps_decode_ip->s_out_buffer.u4_min_out_buf_size[0] = lumaSize;
+ ps_decode_ip->s_out_buffer.u4_min_out_buf_size[1] = chromaSize;
+ ps_decode_ip->s_out_buffer.u4_min_out_buf_size[2] = chromaSize;
+ if (outBuffer) {
+ if (outBuffer->width() < displayStride || outBuffer->height() < displayHeight) {
+ ALOGE("Output buffer too small: provided (%dx%d) required (%ux%u)",
+ outBuffer->width(), outBuffer->height(), displayStride, displayHeight);
+ return false;
+ }
+ ps_decode_ip->s_out_buffer.pu1_bufs[0] = outBuffer->data()[C2PlanarLayout::PLANE_Y];
+ ps_decode_ip->s_out_buffer.pu1_bufs[1] = outBuffer->data()[C2PlanarLayout::PLANE_U];
+ ps_decode_ip->s_out_buffer.pu1_bufs[2] = outBuffer->data()[C2PlanarLayout::PLANE_V];
+ } else {
+ ps_decode_ip->s_out_buffer.pu1_bufs[0] = mOutBufferFlush;
+ ps_decode_ip->s_out_buffer.pu1_bufs[1] = mOutBufferFlush + lumaSize;
+ ps_decode_ip->s_out_buffer.pu1_bufs[2] = mOutBufferFlush + lumaSize + chromaSize;
+ }
+ ps_decode_ip->s_out_buffer.u4_num_bufs = 3;
+ ps_decode_op->u4_size = sizeof(ivd_video_decode_op_t);
+ ps_decode_op->u4_output_present = 0;
+
+ return true;
+}
+
+bool C2SoftHevcDec::colorAspectsDiffer(
+ const ColorAspects &a, const ColorAspects &b) {
+ if (a.mRange != b.mRange
+ || a.mPrimaries != b.mPrimaries
+ || a.mTransfer != b.mTransfer
+ || a.mMatrixCoeffs != b.mMatrixCoeffs) {
+ return true;
+ }
+ return false;
+}
+
+void C2SoftHevcDec::updateFinalColorAspects(
+ const ColorAspects &otherAspects, const ColorAspects &preferredAspects) {
+ Mutex::Autolock autoLock(mColorAspectsLock);
+ ColorAspects newAspects;
+ newAspects.mRange = preferredAspects.mRange != ColorAspects::RangeUnspecified ?
+ preferredAspects.mRange : otherAspects.mRange;
+ newAspects.mPrimaries = preferredAspects.mPrimaries != ColorAspects::PrimariesUnspecified ?
+ preferredAspects.mPrimaries : otherAspects.mPrimaries;
+ newAspects.mTransfer = preferredAspects.mTransfer != ColorAspects::TransferUnspecified ?
+ preferredAspects.mTransfer : otherAspects.mTransfer;
+ newAspects.mMatrixCoeffs = preferredAspects.mMatrixCoeffs != ColorAspects::MatrixUnspecified ?
+ preferredAspects.mMatrixCoeffs : otherAspects.mMatrixCoeffs;
+
+ // Check to see if need update mFinalColorAspects.
+ if (colorAspectsDiffer(mFinalColorAspects, newAspects)) {
+ mFinalColorAspects = newAspects;
+ mUpdateColorAspects = true;
+ }
+}
+
+status_t C2SoftHevcDec::handleColorAspectsChange() {
+ if (mPreference == kPreferBitstream) {
+ updateFinalColorAspects(mDefaultColorAspects, mBitstreamColorAspects);
+ } else if (mPreference == kPreferContainer) {
+ updateFinalColorAspects(mBitstreamColorAspects, mDefaultColorAspects);
+ } else {
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+bool C2SoftHevcDec::getVuiParams() {
+ ivdext_ctl_get_vui_params_ip_t s_get_vui_params_ip;
+ ivdext_ctl_get_vui_params_op_t s_get_vui_params_op;
+
+ s_get_vui_params_ip.u4_size = sizeof(ivdext_ctl_get_vui_params_ip_t);
+ s_get_vui_params_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+ s_get_vui_params_ip.e_sub_cmd =
+ (IVD_CONTROL_API_COMMAND_TYPE_T) IHEVCD_CXA_CMD_CTL_GET_VUI_PARAMS;
+ s_get_vui_params_op.u4_size = sizeof(ivdext_ctl_get_vui_params_op_t);
+ IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+ &s_get_vui_params_ip,
+ &s_get_vui_params_op);
+ if (status != IV_SUCCESS) {
+ ALOGD("error in %s: 0x%x", __func__, s_get_vui_params_op.u4_error_code);
+ return false;
+ }
+
+ int32_t primaries = s_get_vui_params_op.u1_colour_primaries;
+ int32_t transfer = s_get_vui_params_op.u1_transfer_characteristics;
+ int32_t coeffs = s_get_vui_params_op.u1_matrix_coefficients;
+ bool full_range = s_get_vui_params_op.u1_video_full_range_flag;
+
+ ColorAspects colorAspects;
+ ColorUtils::convertIsoColorAspectsToCodecAspects(
+ primaries, transfer, coeffs, full_range, colorAspects);
+ // Update color aspects if necessary.
+ if (colorAspectsDiffer(colorAspects, mBitstreamColorAspects)) {
+ mBitstreamColorAspects = colorAspects;
+ status_t err = handleColorAspectsChange();
+ CHECK(err == OK);
+ }
+
+ return true;
+}
+
+status_t C2SoftHevcDec::setFlushMode() {
+ ivd_ctl_flush_ip_t s_set_flush_ip;
+ ivd_ctl_flush_op_t s_set_flush_op;
+
+ s_set_flush_ip.u4_size = sizeof(ivd_ctl_flush_ip_t);
+ s_set_flush_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+ s_set_flush_ip.e_sub_cmd = IVD_CMD_CTL_FLUSH;
+ s_set_flush_op.u4_size = sizeof(ivd_ctl_flush_op_t);
+ IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+ &s_set_flush_ip,
+ &s_set_flush_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("error in %s: 0x%x", __func__, s_set_flush_op.u4_error_code);
+ return UNKNOWN_ERROR;
+ }
+
+ return OK;
+}
+
+status_t C2SoftHevcDec::resetDecoder() {
+ ivd_ctl_reset_ip_t s_reset_ip;
+ ivd_ctl_reset_op_t s_reset_op;
+
+ s_reset_ip.u4_size = sizeof(ivd_ctl_reset_ip_t);
+ s_reset_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+ s_reset_ip.e_sub_cmd = IVD_CMD_CTL_RESET;
+ s_reset_op.u4_size = sizeof(ivd_ctl_reset_op_t);
+ IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+ &s_reset_ip,
+ &s_reset_op);
+ if (IV_SUCCESS != status) {
+ ALOGE("error in %s: 0x%x", __func__, s_reset_op.u4_error_code);
+ return UNKNOWN_ERROR;
+ }
+ mStride = 0;
+ (void) setNumCores();
+ mSignalledError = false;
+
+ return OK;
+}
+
+void C2SoftHevcDec::resetPlugin() {
+ mSignalledOutputEos = false;
+ gettimeofday(&mTimeStart, nullptr);
+ gettimeofday(&mTimeEnd, nullptr);
+}
+
+status_t C2SoftHevcDec::deleteDecoder() {
+ if (mDecHandle) {
+ ivdext_delete_ip_t s_delete_ip;
+ ivdext_delete_op_t s_delete_op;
+
+ s_delete_ip.s_ivd_delete_ip_t.u4_size = sizeof(ivdext_delete_ip_t);
+ s_delete_ip.s_ivd_delete_ip_t.e_cmd = IVD_CMD_DELETE;
+ s_delete_op.s_ivd_delete_op_t.u4_size = sizeof(ivdext_delete_op_t);
+ IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+ &s_delete_ip,
+ &s_delete_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("error in %s: 0x%x", __func__,
+ s_delete_op.s_ivd_delete_op_t.u4_error_code);
+ return UNKNOWN_ERROR;
+ }
+ mDecHandle = nullptr;
+ }
+
+ return OK;
+}
+
+void fillEmptyWork(const std::unique_ptr<C2Work> &work) {
+ uint32_t flags = 0;
+ if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
+ flags |= C2FrameData::FLAG_END_OF_STREAM;
+ ALOGV("signalling eos");
+ }
+ work->worklets.front()->output.flags = (C2FrameData::flags_t)flags;
+ work->worklets.front()->output.buffers.clear();
+ work->worklets.front()->output.ordinal = work->input.ordinal;
+ work->workletsProcessed = 1u;
+}
+
+void C2SoftHevcDec::finishWork(uint64_t index, const std::unique_ptr<C2Work> &work) {
+ std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(std::move(mOutBlock),
+ C2Rect(mWidth, mHeight));
+ mOutBlock = nullptr;
+ auto fillWork = [buffer, index](const std::unique_ptr<C2Work> &work) {
+ uint32_t flags = 0;
+ if ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) &&
+ (c2_cntr64_t(index) == work->input.ordinal.frameIndex)) {
+ flags |= C2FrameData::FLAG_END_OF_STREAM;
+ ALOGV("signalling eos");
+ }
+ 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->workletsProcessed = 1u;
+ };
+ if (work && c2_cntr64_t(index) == work->input.ordinal.frameIndex) {
+ fillWork(work);
+ } else {
+ finish(index, fillWork);
+ }
+}
+
+c2_status_t C2SoftHevcDec::ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool) {
+ if (!mDecHandle) {
+ ALOGE("not supposed to be here, invalid decoder context");
+ return C2_CORRUPTED;
+ }
+ if (mStride != ALIGN64(mWidth)) {
+ mStride = ALIGN64(mWidth);
+ if (OK != setParams(mStride)) return C2_CORRUPTED;
+ }
+ if (mOutBlock &&
+ (mOutBlock->width() != mStride || mOutBlock->height() != mHeight)) {
+ mOutBlock.reset();
+ }
+ if (!mOutBlock) {
+ uint32_t format = HAL_PIXEL_FORMAT_YV12;
+ C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+ c2_status_t err = pool->fetchGraphicBlock(mStride, mHeight, format, usage, &mOutBlock);
+ if (err != C2_OK) {
+ ALOGE("fetchGraphicBlock for Output failed with status %d", err);
+ return err;
+ }
+ ALOGV("provided (%dx%d) required (%dx%d)",
+ mOutBlock->width(), mOutBlock->height(), mStride, mHeight);
+ }
+
+ return C2_OK;
+}
+
+// TODO: can overall error checking be improved?
+// TODO: allow configuration of color format and usage for graphic buffers instead
+// of hard coding them to HAL_PIXEL_FORMAT_YV12
+// TODO: pass coloraspects information to surface
+// TODO: test support for dynamic change in resolution
+// TODO: verify if the decoder sent back all frames
+void C2SoftHevcDec::process(
+ const std::unique_ptr<C2Work> &work,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ work->result = C2_OK;
+ work->workletsProcessed = 0u;
+ if (mSignalledError || mSignalledOutputEos) {
+ work->result = C2_BAD_VALUE;
+ return;
+ }
+
+ const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+ size_t inOffset = inBuffer.offset();
+ size_t inSize = inBuffer.size();
+ uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF;
+ C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
+ if (inSize && rView.error()) {
+ ALOGE("read view map failed %d", rView.error());
+ work->result = rView.error();
+ return;
+ }
+ bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
+ bool hasPicture = false;
+
+ ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x",
+ inSize, (int)work->input.ordinal.timestamp.peeku(),
+ (int)work->input.ordinal.frameIndex.peeku(), work->input.flags);
+ size_t inPos = 0;
+ while (inPos < inSize) {
+ if (C2_OK != ensureDecoderState(pool)) {
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+ C2GraphicView wView = mOutBlock->map().get();
+ if (wView.error()) {
+ ALOGE("graphic view map failed %d", wView.error());
+ work->result = wView.error();
+ return;
+ }
+ ivd_video_decode_ip_t s_decode_ip;
+ ivd_video_decode_op_t s_decode_op;
+ if (!setDecodeArgs(&s_decode_ip, &s_decode_op, &rView, &wView,
+ inOffset + inPos, inSize - inPos, workIndex)) {
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+ WORD32 delay;
+ GETTIME(&mTimeStart, NULL);
+ TIME_DIFF(mTimeEnd, mTimeStart, delay);
+ (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op);
+ WORD32 decodeTime;
+ GETTIME(&mTimeEnd, nullptr);
+ TIME_DIFF(mTimeStart, mTimeEnd, decodeTime);
+ ALOGV("decodeTime=%6d delay=%6d numBytes=%6d", decodeTime, delay,
+ s_decode_op.u4_num_bytes_consumed);
+ if (IVD_MEM_ALLOC_FAILED == (s_decode_op.u4_error_code & 0xFF)) {
+ ALOGE("allocation failure in decoder");
+ work->result = C2_CORRUPTED;
+ mSignalledError = true;
+ return;
+ } else if (IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED == (s_decode_op.u4_error_code & 0xFF)) {
+ ALOGE("unsupported resolution : %dx%d", mWidth, mHeight);
+ work->result = C2_CORRUPTED;
+ mSignalledError = true;
+ return;
+ } else if (IVD_RES_CHANGED == (s_decode_op.u4_error_code & 0xFF)) {
+ ALOGV("resolution changed");
+ drainInternal(DRAIN_COMPONENT_NO_EOS, pool, work);
+ resetDecoder();
+ resetPlugin();
+ continue;
+ }
+ if (0 < s_decode_op.u4_pic_wd && 0 < s_decode_op.u4_pic_ht) {
+ if (s_decode_op.u4_pic_wd != mWidth || s_decode_op.u4_pic_ht != mHeight) {
+ mWidth = s_decode_op.u4_pic_wd;
+ mHeight = s_decode_op.u4_pic_ht;
+ CHECK_EQ(0u, s_decode_op.u4_output_present);
+ }
+ }
+ (void) getVuiParams();
+ if (mUpdateColorAspects) {
+ mUpdateColorAspects = false;
+ }
+ hasPicture |= (1 == s_decode_op.u4_frame_decoded_flag);
+ if (s_decode_op.u4_output_present) {
+ finishWork(s_decode_op.u4_ts, work);
+ }
+ inPos += s_decode_op.u4_num_bytes_consumed;
+ if (hasPicture && (inSize - inPos)) {
+ ALOGD("decoded frame in current access nal, ignoring further trailing bytes %d",
+ (int)inSize - (int)inPos);
+ break;
+ }
+ }
+
+ if (eos) {
+ drainInternal(DRAIN_COMPONENT_WITH_EOS, pool, work);
+ mSignalledOutputEos = true;
+ } else if (!hasPicture) {
+ fillEmptyWork(work);
+ }
+}
+
+c2_status_t C2SoftHevcDec::drainInternal(
+ uint32_t drainMode,
+ const std::shared_ptr<C2BlockPool> &pool,
+ const std::unique_ptr<C2Work> &work) {
+ if (drainMode == NO_DRAIN) {
+ ALOGW("drain with NO_DRAIN: no-op");
+ return C2_OK;
+ }
+ if (drainMode == DRAIN_CHAIN) {
+ ALOGW("DRAIN_CHAIN not supported");
+ return C2_OMITTED;
+ }
+
+ if (OK != setFlushMode()) return C2_CORRUPTED;
+ while (true) {
+ if (C2_OK != ensureDecoderState(pool)) {
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return C2_CORRUPTED;
+ }
+ C2GraphicView wView = mOutBlock->map().get();
+ if (wView.error()) {
+ ALOGE("graphic view map failed %d", wView.error());
+ return C2_CORRUPTED;
+ }
+ ivd_video_decode_ip_t s_decode_ip;
+ ivd_video_decode_op_t s_decode_op;
+ if (!setDecodeArgs(&s_decode_ip, &s_decode_op, nullptr, &wView, 0, 0, 0)) {
+ mSignalledError = true;
+ return C2_CORRUPTED;
+ }
+ (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op);
+ if (s_decode_op.u4_output_present) {
+ finishWork(s_decode_op.u4_ts, work);
+ } else {
+ break;
+ }
+ }
+
+ if (drainMode == DRAIN_COMPONENT_WITH_EOS &&
+ work && work->workletsProcessed == 0u) {
+ fillEmptyWork(work);
+ }
+
+ return C2_OK;
+}
+
+c2_status_t C2SoftHevcDec::drain(
+ uint32_t drainMode,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ return drainInternal(drainMode, pool, nullptr);
+}
+
+class C2SoftHevcDecFactory : public C2ComponentFactory {
+public:
+ virtual c2_status_t createComponent(
+ c2_node_id_t id,
+ std::shared_ptr<C2Component>* const component,
+ std::function<void(C2Component*)> deleter) override {
+ *component = std::shared_ptr<C2Component>(new C2SoftHevcDec(kComponentName, id), deleter);
+ return C2_OK;
+ }
+
+ virtual c2_status_t createInterface(
+ c2_node_id_t id,
+ std::shared_ptr<C2ComponentInterface>* const interface,
+ std::function<void(C2ComponentInterface*)> deleter) override {
+ *interface = BuildIntf(kComponentName, id, deleter);
+ return C2_OK;
+ }
+
+ virtual ~C2SoftHevcDecFactory() override = default;
+};
+
+} // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+ ALOGV("in %s", __func__);
+ return new ::android::C2SoftHevcDecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+ ALOGV("in %s", __func__);
+ delete factory;
+}
diff --git a/media/libstagefright/codecs/hevcdec/C2SoftHevcDec.h b/media/libstagefright/codecs/hevcdec/C2SoftHevcDec.h
new file mode 100644
index 0000000..28a5a78
--- /dev/null
+++ b/media/libstagefright/codecs/hevcdec/C2SoftHevcDec.h
@@ -0,0 +1,134 @@
+/*
+ * 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 C2_SOFT_HEVC_DEC_H_
+#define C2_SOFT_HEVC_DEC_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/ColorUtils.h>
+
+namespace android {
+
+#define ivdec_api_function ihevcd_cxa_api_function
+#define ivdext_create_ip_t ihevcd_cxa_create_ip_t
+#define ivdext_create_op_t ihevcd_cxa_create_op_t
+#define ivdext_delete_ip_t ihevcd_cxa_delete_ip_t
+#define ivdext_delete_op_t ihevcd_cxa_delete_op_t
+#define ivdext_ctl_set_num_cores_ip_t ihevcd_cxa_ctl_set_num_cores_ip_t
+#define ivdext_ctl_set_num_cores_op_t ihevcd_cxa_ctl_set_num_cores_op_t
+#define ivdext_ctl_get_vui_params_ip_t ihevcd_cxa_ctl_get_vui_params_ip_t
+#define ivdext_ctl_get_vui_params_op_t ihevcd_cxa_ctl_get_vui_params_op_t
+#define ALIGN64(x) ((((x) + 63) >> 6) << 6)
+#define MAX_NUM_CORES 4
+#define IVDEXT_CMD_CTL_SET_NUM_CORES \
+ (IVD_CONTROL_API_COMMAND_TYPE_T)IHEVCD_CXA_CMD_CTL_SET_NUM_CORES
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#define GETTIME(a, b) gettimeofday(a, b);
+#define TIME_DIFF(start, end, diff) \
+ diff = (((end).tv_sec - (start).tv_sec) * 1000000) + \
+ ((end).tv_usec - (start).tv_usec);
+
+
+struct C2SoftHevcDec : public SimpleC2Component {
+ C2SoftHevcDec(const char *name, c2_node_id_t id);
+ virtual ~C2SoftHevcDec();
+
+ // 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;
+ private:
+ status_t createDecoder();
+ status_t setNumCores();
+ status_t setParams(size_t stride);
+ status_t getVersion();
+ status_t initDecoder();
+ bool setDecodeArgs(ivd_video_decode_ip_t *ps_decode_ip,
+ ivd_video_decode_op_t *ps_decode_op,
+ C2ReadView *inBuffer,
+ C2GraphicView *outBuffer,
+ size_t inOffset,
+ size_t inSize,
+ uint32_t tsMarker);
+ bool getVuiParams();
+ // TODO:This is not the right place for colorAspects functions. These should
+ // be part of c2-vndk so that they can be accessed by all video plugins
+ // until then, make them feel at home
+ bool colorAspectsDiffer(const ColorAspects &a, const ColorAspects &b);
+ void updateFinalColorAspects(
+ const ColorAspects &otherAspects, const ColorAspects &preferredAspects);
+ status_t handleColorAspectsChange();
+ c2_status_t ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool);
+ void finishWork(uint64_t index, const std::unique_ptr<C2Work> &work);
+ status_t setFlushMode();
+ c2_status_t drainInternal(
+ uint32_t drainMode,
+ const std::shared_ptr<C2BlockPool> &pool,
+ const std::unique_ptr<C2Work> &work);
+ status_t resetDecoder();
+ void resetPlugin();
+ status_t deleteDecoder();
+
+ // TODO:This is not the right place for this enum. These should
+ // be part of c2-vndk so that they can be accessed by all video plugins
+ // until then, make them feel at home
+ enum {
+ kNotSupported,
+ kPreferBitstream,
+ kPreferContainer,
+ };
+
+ iv_obj_t *mDecHandle;
+ std::shared_ptr<C2GraphicBlock> mOutBlock;
+ uint8_t *mOutBufferFlush;
+
+ size_t mNumCores;
+ IV_COLOR_FORMAT_T mIvColorformat;
+
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mStride;
+ bool mSignalledOutputEos;
+ bool mSignalledError;
+
+ // ColorAspects
+ Mutex mColorAspectsLock;
+ int mPreference;
+ ColorAspects mDefaultColorAspects;
+ ColorAspects mBitstreamColorAspects;
+ ColorAspects mFinalColorAspects;
+ bool mUpdateColorAspects;
+
+ // profile
+ struct timeval mTimeStart;
+ struct timeval mTimeEnd;
+
+ DISALLOW_EVIL_CONSTRUCTORS(C2SoftHevcDec);
+};
+
+} // namespace android
+
+#endif // C2_SOFT_HEVC_DEC_H_
diff --git a/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.cpp b/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.cpp
index 641c342..eaef532 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.cpp
+++ b/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.cpp
@@ -57,7 +57,8 @@
C2SoftMpeg4Dec::C2SoftMpeg4Dec(const char *name, c2_node_id_t id)
: SimpleC2Component(BuildIntf(name, id)),
- mDecHandle(nullptr) {
+ mDecHandle(nullptr),
+ mOutputBuffer{} {
}
C2SoftMpeg4Dec::~C2SoftMpeg4Dec() {
@@ -95,6 +96,7 @@
void C2SoftMpeg4Dec::onRelease() {
if (mInitialized) {
PVCleanUpVideoDecoder(mDecHandle);
+ mInitialized = false;
}
if (mOutBlock) {
mOutBlock.reset();
@@ -130,10 +132,6 @@
}
memset(mDecHandle, 0, sizeof(tagvideoDecControls));
- for (int32_t i = 0; i < kNumOutputBuffers; ++i) {
- mOutputBuffer[i] = nullptr;
- }
-
/* TODO: bring these values to 352 and 288. It cannot be done as of now
* because, h263 doesn't seem to allow port reconfiguration. In OMX, the
* problem of larger width and height than default width and height is
@@ -371,7 +369,8 @@
}
}
- while (inOffset < inSize) {
+ size_t inPos = 0;
+ while (inPos < inSize) {
c2_status_t err = ensureDecoderState(pool);
if (C2_OK != err) {
mSignalledError = true;
@@ -401,7 +400,7 @@
// Need to check if header contains new info, e.g., width/height, etc.
VopHeaderInfo header_info;
- uint32_t useExtTimestamp = (inOffset == 0);
+ uint32_t useExtTimestamp = (inPos == 0);
int32_t tmpInSize = (int32_t)inSize;
uint8_t *bitstreamTmp = bitstream;
uint32_t timestamp = workIndex;
@@ -442,12 +441,12 @@
(void)copyOutputBufferToYV12Frame(outputBufferY, mOutputBuffer[mNumSamplesOutput & 1],
wView.width(), align(mWidth, 16), mWidth, mHeight);
- inOffset += inSize - (size_t)tmpInSize;
+ inPos += inSize - (size_t)tmpInSize;
finishWork(workIndex, work);
++mNumSamplesOutput;
- if (inSize - inOffset) {
- ALOGD("decoded frame, ignoring further trailing bytes %zu",
- inSize - (size_t)tmpInSize);
+ if (inSize - inPos != 0) {
+ ALOGD("decoded frame, ignoring further trailing bytes %d",
+ (int)inSize - (int)inPos);
break;
}
}
diff --git a/media/libstagefright/codecs/mp3dec/C2SoftMP3.cpp b/media/libstagefright/codecs/mp3dec/C2SoftMP3.cpp
index 0a8891e..51b9656 100644
--- a/media/libstagefright/codecs/mp3dec/C2SoftMP3.cpp
+++ b/media/libstagefright/codecs/mp3dec/C2SoftMP3.cpp
@@ -32,7 +32,7 @@
namespace android {
-constexpr char kComponentName[] = "c2.google.aac.encoder";
+constexpr char kComponentName[] = "c2.google.mp3.decoder";
static std::shared_ptr<C2ComponentInterface> BuildIntf(
const char *name, c2_node_id_t id,
@@ -68,6 +68,8 @@
mSignalledError = false;
mIsFirst = true;
mSignalledOutputEos = false;
+ mAnchorTimeStamp = 0;
+ mProcessedSamples = 0;
return C2_OK;
}
@@ -105,6 +107,8 @@
mIsFirst = true;
mSignalledError = false;
mSignalledOutputEos = false;
+ mAnchorTimeStamp = 0;
+ mProcessedSamples = 0;
return OK;
}
@@ -269,11 +273,6 @@
// TODO: Can overall error checking be improved? As in the check for validity of
// work, pool ptr, work->input.buffers.size() == 1, ...
-// TODO: Gapless playback: decoder has a delay of 529 samples. For the first
-// frame we intend to remove 529 samples worth of data. When this is
-// done it is going to effect the timestamps of future frames. This
-// timestamp correction is handled by the client or plugin? Soft omx mp3
-// plugin also has this problem
// TODO: Blind removal of 529 samples from the output may not work. Because
// mpeg layer 1 frame size is 384 samples per frame. This should introduce
// negative values and can cause SEG faults. Soft omx mp3 plugin can have
@@ -292,10 +291,10 @@
bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
size_t inOffset = inBuffer.offset();
size_t inSize = inBuffer.size();
- C2ReadView rView = inBuffer.map().get();
+ C2ReadView rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
if (inSize && rView.error()) {
ALOGE("read view map failed %d", rView.error());
- work->result = C2_CORRUPTED;
+ work->result = rView.error();
return;
}
@@ -315,8 +314,8 @@
size_t calOutSize;
std::vector<size_t> decodedSizes;
- if (OK != calculateOutSize(const_cast<uint8 *>(rView.data() + inOffset),
- inSize, &decodedSizes)) {
+ const uint8_t *inPtr = rView.data() + inOffset;
+ if (OK != calculateOutSize(const_cast<uint8 *>(inPtr), inSize, &decodedSizes)) {
work->result = C2_CORRUPTED;
return;
}
@@ -332,21 +331,22 @@
C2WriteView wView = block->map().get();
if (wView.error()) {
ALOGE("write view map failed %d", wView.error());
- work->result = C2_CORRUPTED;
+ work->result = wView.error();
return;
}
int outSize = 0;
int outOffset = 0;
auto it = decodedSizes.begin();
- while (inOffset < inSize) {
+ size_t inPos = 0;
+ while (inPos < inSize) {
if (it == decodedSizes.end()) {
ALOGE("unexpected trailing bytes, ignoring them");
break;
}
- mConfig->pInputBuffer = const_cast<uint8 *>(rView.data() + inOffset);
- mConfig->inputBufferCurrentLength = (inSize - inOffset);
+ mConfig->pInputBuffer = const_cast<uint8 *>(inPtr + inPos);
+ mConfig->inputBufferCurrentLength = (inSize - inPos);
mConfig->inputBufferMaxLength = 0;
mConfig->inputBufferUsedLength = 0;
mConfig->outputFrameSize = (calOutSize - outSize);
@@ -382,7 +382,7 @@
return;
}
outSize += mConfig->outputFrameSize * sizeof(int16_t);
- inOffset += mConfig->inputBufferUsedLength;
+ inPos += mConfig->inputBufferUsedLength;
it++;
}
if (mIsFirst) {
@@ -391,13 +391,19 @@
// the start of the first output buffer. This essentially makes this
// decoder have zero delay, which the rest of the pipeline assumes.
outOffset = kPVMP3DecoderDelay * mNumChannels * sizeof(int16_t);
+ mAnchorTimeStamp = work->input.ordinal.timestamp.peekull();
}
- ALOGV("out buffer attr. offset %d size %d", outOffset, outSize);
+ uint64_t outTimeStamp = mProcessedSamples * 1000000ll / mSamplingRate;
+ mProcessedSamples += ((outSize - outOffset) / (mNumChannels * sizeof(int16_t)));
+ ALOGV("out buffer attr. offset %d size %d timestamp %u", outOffset, outSize - outOffset,
+ (uint32_t)(mAnchorTimeStamp + outTimeStamp));
decodedSizes.clear();
work->worklets.front()->output.flags = work->input.flags;
work->worklets.front()->output.buffers.clear();
- work->worklets.front()->output.buffers.push_back(createLinearBuffer(block, outOffset, outSize));
+ work->worklets.front()->output.buffers.push_back(
+ createLinearBuffer(block, outOffset, outSize - outOffset));
work->worklets.front()->output.ordinal = work->input.ordinal;
+ work->worklets.front()->output.ordinal.timestamp = mAnchorTimeStamp + outTimeStamp;
work->workletsProcessed = 1u;
if (eos) {
mSignalledOutputEos = true;
diff --git a/media/libstagefright/codecs/mp3dec/C2SoftMP3.h b/media/libstagefright/codecs/mp3dec/C2SoftMP3.h
index ad974bd..6e9a571 100644
--- a/media/libstagefright/codecs/mp3dec/C2SoftMP3.h
+++ b/media/libstagefright/codecs/mp3dec/C2SoftMP3.h
@@ -61,6 +61,8 @@
bool mIsFirst;
bool mSignalledError;
bool mSignalledOutputEos;
+ uint64_t mAnchorTimeStamp;
+ uint64_t mProcessedSamples;
status_t initDecoder();
diff --git a/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.cpp b/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.cpp
index 74ea340..f8008aa 100644
--- a/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.cpp
+++ b/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.cpp
@@ -352,7 +352,7 @@
if (inBuffer) {
ps_decode_ip->u4_ts = tsMarker;
ps_decode_ip->pv_stream_buffer = const_cast<uint8_t *>(inBuffer->data() + inOffset);
- ps_decode_ip->u4_num_Bytes = inSize - inOffset;
+ ps_decode_ip->u4_num_Bytes = inSize;
} else {
ps_decode_ip->u4_ts = 0;
ps_decode_ip->pv_stream_buffer = nullptr;
@@ -630,7 +630,8 @@
ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x",
inSize, (int)work->input.ordinal.timestamp.peeku(),
(int)work->input.ordinal.frameIndex.peeku(), work->input.flags);
- while (inOffset < inSize) {
+ size_t inPos = 0;
+ while (inPos < inSize) {
if (C2_OK != ensureDecoderState(pool)) {
mSignalledError = true;
work->result = C2_CORRUPTED;
@@ -646,7 +647,7 @@
ivd_video_decode_ip_t s_decode_ip;
ivd_video_decode_op_t s_decode_op;
if (!setDecodeArgs(&s_decode_ip, &s_decode_op, &rView, &wView,
- inOffset, inSize, workIndex)) {
+ inOffset + inPos, inSize - inPos, workIndex)) {
mSignalledError = true;
work->result = C2_CORRUPTED;
return;
@@ -693,10 +694,10 @@
if (s_decode_op.u4_output_present) {
finishWork(s_decode_op.u4_ts, work);
}
- inOffset += s_decode_op.u4_num_bytes_consumed;
- if (hasPicture && (inSize - inOffset)) {
+ inPos += s_decode_op.u4_num_bytes_consumed;
+ if (hasPicture && (inSize - inPos) != 0) {
ALOGD("decoded frame in current access nal, ignoring further trailing bytes %d",
- (int)inSize - (int)inOffset);
+ (int)inSize - (int)inPos);
break;
}
}
diff --git a/media/libstagefright/codecs/raw/Android.bp b/media/libstagefright/codecs/raw/Android.bp
index c8d7d00..c431c18 100644
--- a/media/libstagefright/codecs/raw/Android.bp
+++ b/media/libstagefright/codecs/raw/Android.bp
@@ -35,3 +35,38 @@
],
compile_multilib: "32",
}
+
+cc_library_shared {
+ name: "libstagefright_soft_c2rawdec",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ srcs: ["C2SoftRaw.cpp",],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ "unsigned-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+
+ shared_libs: [
+ "liblog",
+ "libutils",
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_foundation",
+ "libstagefright_simple_c2component",
+ ],
+}
diff --git a/media/libstagefright/codecs/raw/C2SoftRaw.cpp b/media/libstagefright/codecs/raw/C2SoftRaw.cpp
new file mode 100644
index 0000000..91b920d
--- /dev/null
+++ b/media/libstagefright/codecs/raw/C2SoftRaw.cpp
@@ -0,0 +1,151 @@
+/*
+ * 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 "C2SoftRAW"
+#include <utils/Log.h>
+
+#include "C2SoftRaw.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+namespace android {
+
+constexpr char kComponentName[] = "c2.google.raw.decoder";
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+ const char *name, c2_node_id_t id,
+ std::function<void(C2ComponentInterface*)> deleter =
+ std::default_delete<C2ComponentInterface>()) {
+ return SimpleC2Interface::Builder(name, id, deleter)
+ .inputFormat(C2FormatCompressed)
+ .outputFormat(C2FormatAudio)
+ .inputMediaType(MEDIA_MIMETYPE_AUDIO_RAW)
+ .outputMediaType(MEDIA_MIMETYPE_AUDIO_RAW)
+ .build();
+}
+
+C2SoftRaw::C2SoftRaw(const char *name, c2_node_id_t id)
+ : SimpleC2Component(BuildIntf(name, id)) {
+}
+
+C2SoftRaw::~C2SoftRaw() {
+ onRelease();
+}
+
+c2_status_t C2SoftRaw::onInit() {
+ mSignalledEos = false;
+ return C2_OK;
+}
+
+c2_status_t C2SoftRaw::onStop() {
+ mSignalledEos = false;
+ return C2_OK;
+}
+
+void C2SoftRaw::onReset() {
+ (void)onStop();
+}
+
+void C2SoftRaw::onRelease() {
+}
+
+c2_status_t C2SoftRaw::onFlush_sm() {
+ return onStop();
+}
+
+void C2SoftRaw::process(
+ const std::unique_ptr<C2Work> &work,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ (void)pool;
+ work->result = C2_OK;
+ work->workletsProcessed = 0u;
+ if (mSignalledEos) {
+ work->result = C2_BAD_VALUE;
+ return;
+ }
+
+ const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+ size_t inSize = inBuffer.size();
+
+ ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize,
+ (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku());
+
+ work->worklets.front()->output.flags = work->input.flags;
+ work->worklets.front()->output.buffers.clear();
+ work->worklets.front()->output.ordinal = work->input.ordinal;
+ if (inSize != 0) {
+ work->worklets.front()->output.buffers.push_back(work->input.buffers[0]);
+ }
+ work->workletsProcessed = 1u;
+ if (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) {
+ mSignalledEos = true;
+ ALOGV("signalled EOS");
+ }
+}
+
+c2_status_t C2SoftRaw::drain(
+ uint32_t drainMode,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ (void) pool;
+ if (drainMode == NO_DRAIN) {
+ ALOGW("drain with NO_DRAIN: no-op");
+ return C2_OK;
+ }
+ if (drainMode == DRAIN_CHAIN) {
+ ALOGW("DRAIN_CHAIN not supported");
+ return C2_OMITTED;
+ }
+
+ return C2_OK;
+}
+
+class C2SoftRawDecFactory : public C2ComponentFactory {
+public:
+ virtual c2_status_t createComponent(
+ c2_node_id_t id,
+ std::shared_ptr<C2Component>* const component,
+ std::function<void(C2Component*)> deleter) override {
+ *component = std::shared_ptr<C2Component>(new C2SoftRaw(kComponentName, id), deleter);
+ return C2_OK;
+ }
+
+ virtual c2_status_t createInterface(
+ c2_node_id_t id,
+ std::shared_ptr<C2ComponentInterface>* const interface,
+ std::function<void(C2ComponentInterface*)> deleter) override {
+ *interface = BuildIntf(kComponentName, id, deleter);
+ return C2_OK;
+ }
+
+ virtual ~C2SoftRawDecFactory() override = default;
+};
+
+} // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+ ALOGV("in %s", __func__);
+ return new ::android::C2SoftRawDecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+ ALOGV("in %s", __func__);
+ delete factory;
+}
diff --git a/media/libstagefright/codecs/raw/C2SoftRaw.h b/media/libstagefright/codecs/raw/C2SoftRaw.h
new file mode 100644
index 0000000..f7d2cfd
--- /dev/null
+++ b/media/libstagefright/codecs/raw/C2SoftRaw.h
@@ -0,0 +1,50 @@
+/*
+ * 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 C2_SOFT_RAW_H_
+#define C2_SOFT_RAW_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+
+namespace android {
+
+struct C2SoftRaw : public SimpleC2Component {
+ C2SoftRaw(const char *name, c2_node_id_t id);
+ virtual ~C2SoftRaw();
+
+ // 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;
+private:
+ bool mSignalledEos;
+
+ DISALLOW_EVIL_CONSTRUCTORS(C2SoftRaw);
+};
+
+} // namespace android
+
+#endif // C2_SOFT_RAW_H_
diff --git a/media/libstagefright/codecs/vorbis/dec/Android.bp b/media/libstagefright/codecs/vorbis/dec/Android.bp
index a9265cb..78dd61b 100644
--- a/media/libstagefright/codecs/vorbis/dec/Android.bp
+++ b/media/libstagefright/codecs/vorbis/dec/Android.bp
@@ -1,4 +1,40 @@
cc_library_shared {
+ name: "libstagefright_soft_c2vorbisdec",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ srcs: ["C2SoftVorbis.cpp"],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ "unsigned-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+
+ shared_libs: [
+ "liblog",
+ "libutils",
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_foundation",
+ "libstagefright_simple_c2component",
+ "libvorbisidec",
+ ],
+}
+
+cc_library_shared {
name: "libstagefright_soft_vorbisdec",
vendor_available: true,
vndk: {
diff --git a/media/libstagefright/codecs/vorbis/dec/C2SoftVorbis.cpp b/media/libstagefright/codecs/vorbis/dec/C2SoftVorbis.cpp
new file mode 100644
index 0000000..8342725
--- /dev/null
+++ b/media/libstagefright/codecs/vorbis/dec/C2SoftVorbis.cpp
@@ -0,0 +1,388 @@
+/*
+ * 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 "C2SoftVorbis"
+#include <utils/Log.h>
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+#include "C2SoftVorbis.h"
+
+extern "C" {
+ #include <Tremolo/codec_internal.h>
+
+ int _vorbis_unpack_books(vorbis_info *vi,oggpack_buffer *opb);
+ int _vorbis_unpack_info(vorbis_info *vi,oggpack_buffer *opb);
+ int _vorbis_unpack_comment(vorbis_comment *vc,oggpack_buffer *opb);
+}
+
+namespace android {
+
+constexpr char kComponentName[] = "c2.google.vorbis.decoder";
+
+static std::shared_ptr<C2ComponentInterface> BuildIntf(
+ const char *name, c2_node_id_t id,
+ std::function<void(C2ComponentInterface*)> deleter =
+ std::default_delete<C2ComponentInterface>()) {
+ return SimpleC2Interface::Builder(name, id, deleter)
+ .inputFormat(C2FormatCompressed)
+ .outputFormat(C2FormatAudio)
+ .inputMediaType(MEDIA_MIMETYPE_AUDIO_VORBIS)
+ .outputMediaType(MEDIA_MIMETYPE_AUDIO_RAW)
+ .build();
+}
+
+C2SoftVorbis::C2SoftVorbis(const char *name, c2_node_id_t id)
+ : SimpleC2Component(BuildIntf(name, id)),
+ mState(nullptr),
+ mVi(nullptr) {
+}
+
+C2SoftVorbis::~C2SoftVorbis() {
+ onRelease();
+}
+
+c2_status_t C2SoftVorbis::onInit() {
+ status_t err = initDecoder();
+ return err == OK ? C2_OK : C2_NO_MEMORY;
+}
+
+c2_status_t C2SoftVorbis::onStop() {
+ if (mState) vorbis_dsp_clear(mState);
+ if (mVi) vorbis_info_clear(mVi);
+ mNumFramesLeftOnPage = -1;
+ mNumChannels = 1;
+ mSamplingRate = 48000;
+ mInputBufferCount = 0;
+ mSignalledOutputEos = false;
+ mSignalledError = false;
+
+ return C2_OK;
+}
+
+void C2SoftVorbis::onReset() {
+ (void)onStop();
+}
+
+void C2SoftVorbis::onRelease() {
+ if (mState) {
+ vorbis_dsp_clear(mState);
+ delete mState;
+ mState = nullptr;
+ }
+
+ if (mVi) {
+ vorbis_info_clear(mVi);
+ delete mVi;
+ mVi = nullptr;
+ }
+}
+
+status_t C2SoftVorbis::initDecoder() {
+ mVi = new vorbis_info{};
+ if (!mVi) return NO_MEMORY;
+ vorbis_info_clear(mVi);
+
+ mState = new vorbis_dsp_state{};
+ if (!mState) return NO_MEMORY;
+ vorbis_dsp_clear(mState);
+
+ mNumFramesLeftOnPage = -1;
+ mNumChannels = 1;
+ mSamplingRate = 48000;
+ mInputBufferCount = 0;
+ mSignalledError = false;
+ mSignalledOutputEos = false;
+
+ return OK;
+}
+
+c2_status_t C2SoftVorbis::onFlush_sm() {
+ mNumFramesLeftOnPage = -1;
+ mSignalledOutputEos = false;
+ if (mState) vorbis_dsp_restart(mState);
+
+ return C2_OK;
+}
+
+c2_status_t C2SoftVorbis::drain(
+ uint32_t drainMode,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ (void) pool;
+ if (drainMode == NO_DRAIN) {
+ ALOGW("drain with NO_DRAIN: no-op");
+ return C2_OK;
+ }
+ if (drainMode == DRAIN_CHAIN) {
+ ALOGW("DRAIN_CHAIN not supported");
+ return C2_OMITTED;
+ }
+
+ return C2_OK;
+}
+
+static void 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.ordinal = work->input.ordinal;
+ work->workletsProcessed = 1u;
+}
+
+static void makeBitReader(
+ const void *data, size_t size,
+ ogg_buffer *buf, ogg_reference *ref, oggpack_buffer *bits) {
+ buf->data = (uint8_t *)data;
+ buf->size = size;
+ buf->refcount = 1;
+ buf->ptr.owner = nullptr;
+
+ ref->buffer = buf;
+ ref->begin = 0;
+ ref->length = size;
+ ref->next = nullptr;
+
+ oggpack_readinit(bits, ref);
+}
+
+// (CHECK!) multiframe is tricky. decode call doesnt return the number of bytes
+// consumed by the component. Also it is unclear why numPageFrames is being
+// tagged at the end of input buffers for new pages. Refer lines 297-300 in
+// SimpleDecodingSource.cpp
+void C2SoftVorbis::process(
+ const std::unique_ptr<C2Work> &work,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ work->result = C2_OK;
+ work->workletsProcessed = 0u;
+ if (mSignalledError || mSignalledOutputEos) {
+ work->result = C2_BAD_VALUE;
+ return;
+ }
+
+ const C2ConstLinearBlock inBuffer = work->input.buffers[0]->data().linearBlocks().front();
+ bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
+ size_t inOffset = inBuffer.offset();
+ size_t inSize = inBuffer.size();
+ C2ReadView rView = inBuffer.map().get();
+ if (inSize && rView.error()) {
+ ALOGE("read view map failed %d", rView.error());
+ work->result = rView.error();
+ return;
+ }
+
+ if (inSize == 0) {
+ fillEmptyWork(work);
+ if (eos) {
+ mSignalledOutputEos = true;
+ ALOGV("signalled EOS");
+ }
+ return;
+ }
+
+ ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize,
+ (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku());
+ const uint8_t *data = rView.data() + inOffset;
+ if (mInputBufferCount < 2) {
+ if (inSize < 7 || memcmp(&data[1], "vorbis", 6)) {
+ ALOGE("unexpected first 7 bytes in CSD");
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+
+ ogg_buffer buf;
+ ogg_reference ref;
+ oggpack_buffer bits;
+
+ // skip 7 <type + "vorbis"> bytes
+ makeBitReader((const uint8_t *)data + 7, inSize - 7, &buf, &ref, &bits);
+ if (mInputBufferCount == 0) {
+ if (data[0] != 1) {
+ ALOGE("unexpected type received %d", data[0]);
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+ vorbis_info_init(mVi);
+ if (0 != _vorbis_unpack_info(mVi, &bits)) {
+ ALOGE("Encountered error while unpacking info");
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+ if (mVi->rate != mSamplingRate ||
+ mVi->channels != mNumChannels) {
+ ALOGV("vorbis: rate/channels changed: %ld/%d", mVi->rate, mVi->channels);
+ mSamplingRate = mVi->rate;
+ mNumChannels = mVi->channels;
+ }
+ } else {
+ if (data[0] != 5) {
+ ALOGE("unexpected type received %d", data[0]);
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+ if (0 != _vorbis_unpack_books(mVi, &bits)) {
+ ALOGE("Encountered error while unpacking books");
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+ if (0 != vorbis_dsp_init(mState, mVi)) {
+ ALOGE("Encountered error while dsp init");
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+ }
+ ++mInputBufferCount;
+ fillEmptyWork(work);
+ if (eos) {
+ mSignalledOutputEos = true;
+ ALOGV("signalled EOS");
+ }
+ return;
+ }
+ int32_t numPageFrames = 0;
+ if (inSize < sizeof(numPageFrames)) {
+ ALOGE("input header has size %zu, expected %zu", inSize, sizeof(numPageFrames));
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+ memcpy(&numPageFrames, data + inSize - sizeof(numPageFrames), sizeof(numPageFrames));
+ inSize -= sizeof(numPageFrames);
+ if (numPageFrames >= 0) {
+ mNumFramesLeftOnPage = numPageFrames;
+ }
+
+ ogg_buffer buf;
+ buf.data = const_cast<unsigned char*>(data);
+ buf.size = inSize;
+ buf.refcount = 1;
+ buf.ptr.owner = nullptr;
+
+ ogg_reference ref;
+ ref.buffer = &buf;
+ ref.begin = 0;
+ ref.length = buf.size;
+ ref.next = nullptr;
+
+ ogg_packet pack;
+ pack.packet = &ref;
+ pack.bytes = ref.length;
+ pack.b_o_s = 0;
+ pack.e_o_s = 0;
+ pack.granulepos = 0;
+ pack.packetno = 0;
+
+ size_t maxSamplesInBuffer = kMaxNumSamplesPerChannel * mVi->channels;
+ size_t outCapacity = maxSamplesInBuffer * sizeof(int16_t);
+ std::shared_ptr<C2LinearBlock> block;
+ C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+ c2_status_t err = pool->fetchLinearBlock(outCapacity, usage, &block);
+ if (err != C2_OK) {
+ ALOGE("fetchLinearBlock for Output failed with status %d", err);
+ work->result = C2_NO_MEMORY;
+ return;
+ }
+ C2WriteView wView = block->map().get();
+ if (wView.error()) {
+ ALOGE("write view map failed %d", wView.error());
+ work->result = wView.error();
+ return;
+ }
+
+ int numFrames = 0;
+ int ret = vorbis_dsp_synthesis(mState, &pack, 1);
+ if (0 != ret) {
+ ALOGE("vorbis_dsp_synthesis returned %d", ret);
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ } else {
+ numFrames = vorbis_dsp_pcmout(
+ mState, reinterpret_cast<int16_t *> (wView.data()),
+ kMaxNumSamplesPerChannel);
+ if (numFrames < 0) {
+ ALOGD("vorbis_dsp_pcmout returned %d", numFrames);
+ numFrames = 0;
+ }
+ }
+
+ if (mNumFramesLeftOnPage >= 0) {
+ if (numFrames > mNumFramesLeftOnPage) {
+ ALOGV("discarding %d frames at end of page", numFrames - mNumFramesLeftOnPage);
+ numFrames = mNumFramesLeftOnPage;
+ }
+ mNumFramesLeftOnPage -= numFrames;
+ }
+
+ if (numFrames) {
+ int outSize = numFrames * sizeof(int16_t) * mVi->channels;
+
+ work->worklets.front()->output.flags = work->input.flags;
+ work->worklets.front()->output.buffers.clear();
+ work->worklets.front()->output.buffers.push_back(createLinearBuffer(block, 0, outSize));
+ work->worklets.front()->output.ordinal = work->input.ordinal;
+ work->workletsProcessed = 1u;
+ } else {
+ fillEmptyWork(work);
+ block.reset();
+ }
+ if (eos) {
+ mSignalledOutputEos = true;
+ ALOGV("signalled EOS");
+ }
+}
+
+class C2SoftVorbisDecFactory : public C2ComponentFactory {
+public:
+ virtual c2_status_t createComponent(
+ c2_node_id_t id,
+ std::shared_ptr<C2Component>* const component,
+ std::function<void(C2Component*)> deleter) override {
+ *component = std::shared_ptr<C2Component>(new C2SoftVorbis(kComponentName, id), deleter);
+ return C2_OK;
+ }
+
+ virtual c2_status_t createInterface(
+ c2_node_id_t id,
+ std::shared_ptr<C2ComponentInterface>* const interface,
+ std::function<void(C2ComponentInterface*)> deleter) override {
+ *interface = BuildIntf(kComponentName, id, deleter);
+ return C2_OK;
+ }
+
+ virtual ~C2SoftVorbisDecFactory() override = default;
+};
+
+} // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+ ALOGV("in %s", __func__);
+ return new ::android::C2SoftVorbisDecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+ ALOGV("in %s", __func__);
+ delete factory;
+}
diff --git a/media/libstagefright/codecs/vorbis/dec/C2SoftVorbis.h b/media/libstagefright/codecs/vorbis/dec/C2SoftVorbis.h
new file mode 100644
index 0000000..340c235
--- /dev/null
+++ b/media/libstagefright/codecs/vorbis/dec/C2SoftVorbis.h
@@ -0,0 +1,69 @@
+/*
+ * 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 C2_SOFT_VORBIS_H_
+#define C2_SOFT_VORBIS_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+
+struct vorbis_dsp_state;
+struct vorbis_info;
+
+namespace android {
+
+struct C2SoftVorbis : public SimpleC2Component {
+ C2SoftVorbis(const char *name, c2_node_id_t id);
+ virtual ~C2SoftVorbis();
+
+ // 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;
+
+ private:
+ enum {
+ kMaxNumSamplesPerChannel = 8192,
+ };
+
+ vorbis_dsp_state *mState;
+ vorbis_info *mVi;
+
+ int32_t mNumFramesLeftOnPage;
+ int32_t mNumChannels;
+ int32_t mSamplingRate;
+ size_t mInputBufferCount;
+ bool mSignalledError;
+ bool mSignalledOutputEos;
+
+ status_t initDecoder();
+
+ DISALLOW_EVIL_CONSTRUCTORS(C2SoftVorbis);
+};
+
+} // namespace android
+
+#endif // C2_SOFT_VORBIS_H_
+
diff --git a/media/libstagefright/flac/dec/FLACDecoder.cpp b/media/libstagefright/flac/dec/FLACDecoder.cpp
index 8c7137c..e0e9211 100644
--- a/media/libstagefright/flac/dec/FLACDecoder.cpp
+++ b/media/libstagefright/flac/dec/FLACDecoder.cpp
@@ -220,9 +220,10 @@
}
// static
-sp<FLACDecoder> FLACDecoder::Create() {
- sp<FLACDecoder> decoder = new FLACDecoder();
- if (decoder->init() != OK) {
+FLACDecoder *FLACDecoder::Create() {
+ FLACDecoder *decoder = new (std::nothrow) FLACDecoder();
+ if (decoder == NULL || decoder->init() != OK) {
+ delete decoder;
return NULL;
}
return decoder;
diff --git a/media/libstagefright/flac/dec/FLACDecoder.h b/media/libstagefright/flac/dec/FLACDecoder.h
index 36282a8..1a33cae 100644
--- a/media/libstagefright/flac/dec/FLACDecoder.h
+++ b/media/libstagefright/flac/dec/FLACDecoder.h
@@ -26,14 +26,14 @@
namespace android {
// packet based FLAC decoder, wrapps libFLAC stream decoder.
-class FLACDecoder : public RefBase {
+class FLACDecoder {
public:
enum {
kMaxChannels = 8,
};
- static sp<FLACDecoder> Create();
+ static FLACDecoder *Create();
FLAC__StreamMetadata_StreamInfo getStreamInfo() const {
return mStreamInfo;
@@ -43,10 +43,10 @@
status_t decodeOneFrame(const uint8_t *inBuffer, size_t inBufferLen,
short *outBuffer, size_t *outBufferLen);
void flush();
+ virtual ~FLACDecoder();
protected:
FLACDecoder();
- virtual ~FLACDecoder() override;
private:
// stream properties
diff --git a/media/libstagefright/include/CCodecBufferChannel.h b/media/libstagefright/include/CCodecBufferChannel.h
index 51eee10..c8618db 100644
--- a/media/libstagefright/include/CCodecBufferChannel.h
+++ b/media/libstagefright/include/CCodecBufferChannel.h
@@ -162,6 +162,7 @@
};
void feedInputBufferIfAvailable();
+ status_t queueInputBufferInternal(const sp<MediaCodecBuffer> &buffer);
QueueSync mSync;
sp<MemoryDealer> mDealer;
@@ -180,7 +181,13 @@
std::atomic_uint64_t mFirstValidFrameIndex;
sp<MemoryDealer> makeMemoryDealer(size_t heapSize);
- Mutexed<sp<Surface>> mSurface;
+
+ struct OutputSurface {
+ sp<Surface> surface;
+ std::list<std::shared_ptr<C2Buffer>> bufferRefs;
+ size_t maxBufferCount;
+ };
+ Mutexed<OutputSurface> mOutputSurface;
std::shared_ptr<InputSurfaceWrapper> mInputSurface;
diff --git a/media/libstagefright/include/Codec2Buffer.h b/media/libstagefright/include/Codec2Buffer.h
index eeb889d..b2e3b1b 100644
--- a/media/libstagefright/include/Codec2Buffer.h
+++ b/media/libstagefright/include/Codec2Buffer.h
@@ -20,8 +20,11 @@
#include <C2Buffer.h>
+#include <android/hardware/cas/native/1.0/types.h>
+#include <binder/IMemory.h>
#include <media/hardware/VideoAPI.h>
#include <media/MediaCodecBuffer.h>
+#include <media/ICrypto.h>
namespace android {
@@ -271,6 +274,79 @@
const bool mWrapped;
};
+/**
+ * MediaCodecBuffer implementation wraps around C2LinearBlock for component
+ * and IMemory for client. Underlying C2LinearBlock won't be mapped for secure
+ * usecases..
+ */
+class EncryptedLinearBlockBuffer : public Codec2Buffer {
+public:
+ /**
+ * Construct a new EncryptedLinearBufferBlock wrapping around C2LinearBlock
+ * object and writable IMemory region.
+ *
+ * \param format mandatory buffer format for MediaCodecBuffer
+ * \param block C2LinearBlock object to wrap around.
+ * \param memory IMemory object to store encrypted content.
+ * \param heapSeqNum Heap sequence number from ICrypto; -1 if N/A
+ */
+ EncryptedLinearBlockBuffer(
+ const sp<AMessage> &format,
+ const std::shared_ptr<C2LinearBlock> &block,
+ const sp<IMemory> &memory,
+ int32_t heapSeqNum = -1);
+ EncryptedLinearBlockBuffer() = delete;
+
+ virtual ~EncryptedLinearBlockBuffer() = default;
+
+ std::shared_ptr<C2Buffer> asC2Buffer() override;
+
+ /**
+ * Fill the source buffer structure with appropriate value based on
+ * internal IMemory object.
+ *
+ * \param source source buffer structure to fill.
+ */
+ void fillSourceBuffer(ICrypto::SourceBuffer *source);
+ void fillSourceBuffer(
+ hardware::cas::native::V1_0::SharedBuffer *source);
+
+ /**
+ * Copy the content of |decrypted| into C2LinearBlock inside. This shall
+ * only be called in non-secure usecases.
+ *
+ * \param decrypted decrypted content to copy from.
+ * \param length length of the content
+ * \return true if successful
+ * false otherwise.
+ */
+ bool copyDecryptedContent(const sp<IMemory> &decrypted, size_t length);
+
+ /**
+ * Copy the content of internal IMemory object into C2LinearBlock inside.
+ * This shall only be called in non-secure usecases.
+ *
+ * \param length length of the content
+ * \return true if successful
+ * false otherwise.
+ */
+ bool copyDecryptedContentFromMemory(size_t length);
+
+ /**
+ * Return native handle of secure buffer understood by ICrypto.
+ *
+ * \return secure buffer handle
+ */
+ native_handle_t *handle() const;
+
+private:
+
+ std::shared_ptr<C2LinearBlock> mBlock;
+ sp<IMemory> mMemory;
+ sp<hardware::HidlMemory> mHidlMemory;
+ int32_t mHeapSeqNum;
+};
+
} // namespace android
#endif // CODEC2_BUFFER_H_
diff --git a/media/libstagefright/include/media/stagefright/ACodec.h b/media/libstagefright/include/media/stagefright/ACodec.h
index fa22003..1a5304b 100644
--- a/media/libstagefright/include/media/stagefright/ACodec.h
+++ b/media/libstagefright/include/media/stagefright/ACodec.h
@@ -253,6 +253,7 @@
sp<AMessage> mLastOutputFormat;
bool mIsVideo;
+ bool mIsImage;
bool mIsEncoder;
bool mFatalError;
bool mShutdownInProgress;
@@ -489,11 +490,12 @@
status_t setupMPEG4EncoderParameters(const sp<AMessage> &msg);
status_t setupH263EncoderParameters(const sp<AMessage> &msg);
status_t setupAVCEncoderParameters(const sp<AMessage> &msg);
- status_t setupHEVCEncoderParameters(const sp<AMessage> &msg);
+ status_t setupHEVCEncoderParameters(const sp<AMessage> &msg, sp<AMessage> &outputFormat);
status_t setupVPXEncoderParameters(const sp<AMessage> &msg, sp<AMessage> &outputFormat);
status_t verifySupportForProfileAndLevel(int32_t profile, int32_t level);
+ status_t configureImageGrid(const sp<AMessage> &msg, sp<AMessage> &outputFormat);
status_t configureBitrate(
OMX_VIDEO_CONTROLRATETYPE bitrateMode, int32_t bitrate, int32_t quality = 0);
void configureEncoderLatency(const sp<AMessage> &msg);
diff --git a/media/libstagefright/include/media/stagefright/CCodec.h b/media/libstagefright/include/media/stagefright/CCodec.h
index 078b03e..86fbd3a 100644
--- a/media/libstagefright/include/media/stagefright/CCodec.h
+++ b/media/libstagefright/include/media/stagefright/CCodec.h
@@ -36,6 +36,7 @@
class CCodecBufferChannel;
class InputSurfaceWrapper;
+struct MediaCodecInfo;
class CCodec : public CodecBase {
public:
@@ -74,7 +75,7 @@
void initiateStop();
void initiateRelease(bool sendCallback = true);
- void allocate(const AString &componentName);
+ void allocate(const sp<MediaCodecInfo> &codecInfo);
void configure(const sp<AMessage> &msg);
void start();
void stop();
diff --git a/media/libstagefright/include/media/stagefright/MPEG4Writer.h b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
index 2a062cc..7b41362 100644
--- a/media/libstagefright/include/media/stagefright/MPEG4Writer.h
+++ b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
@@ -195,6 +195,7 @@
uint32_t type;
int32_t width;
int32_t height;
+ int32_t rotation;
sp<ABuffer> hvcc;
} ItemProperty;
diff --git a/media/libstagefright/include/media/stagefright/MediaCodec.h b/media/libstagefright/include/media/stagefright/MediaCodec.h
index e7faea5..ef8de1f 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodec.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodec.h
@@ -184,6 +184,8 @@
status_t getName(AString *componentName) const;
+ status_t getCodecInfo(sp<MediaCodecInfo> *codecInfo) const;
+
status_t getMetrics(MediaAnalyticsItem * &reply);
status_t setParameters(const sp<AMessage> ¶ms);
@@ -248,6 +250,7 @@
kWhatRequestIDRFrame = 'ridr',
kWhatRequestActivityNotification = 'racN',
kWhatGetName = 'getN',
+ kWhatGetCodecInfo = 'gCoI',
kWhatSetParameters = 'setP',
kWhatSetCallback = 'setC',
kWhatSetNotification = 'setN',
@@ -308,6 +311,7 @@
sp<ALooper> mCodecLooper;
sp<CodecBase> mCodec;
AString mComponentName;
+ sp<MediaCodecInfo> mCodecInfo;
sp<AReplyToken> mReplyID;
uint32_t mFlags;
status_t mStickyError;
diff --git a/media/libstagefright/include/media/stagefright/MediaCodecList.h b/media/libstagefright/include/media/stagefright/MediaCodecList.h
index d46fe85..bb4da09 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodecList.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodecList.h
@@ -74,8 +74,7 @@
const char *mime,
bool createEncoder,
uint32_t flags,
- Vector<AString> *matchingCodecs,
- Vector<AString> *owners = nullptr);
+ Vector<AString> *matchingCodecs);
static bool isSoftwareCodec(const AString &componentName);
diff --git a/media/libstagefright/include/media/stagefright/MetaDataUtils.h b/media/libstagefright/include/media/stagefright/MetaDataUtils.h
index 3af2218..d5a8080 100644
--- a/media/libstagefright/include/media/stagefright/MetaDataUtils.h
+++ b/media/libstagefright/include/media/stagefright/MetaDataUtils.h
@@ -23,7 +23,7 @@
namespace android {
struct ABuffer;
-bool MakeAVCCodecSpecificData(MetaDataBase &meta, const sp<ABuffer> &accessUnit);
+bool MakeAVCCodecSpecificData(MetaDataBase &meta, const uint8_t *data, size_t size);
bool MakeAACCodecSpecificData(MetaDataBase &meta, unsigned profile, unsigned sampling_freq_index,
unsigned channel_configuration);
diff --git a/media/libstagefright/include/media/stagefright/NuMediaExtractor.h b/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
index 5e5ef6e..675c932 100644
--- a/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
+++ b/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
@@ -64,6 +64,8 @@
status_t setMediaCas(const HInterfaceToken &casToken);
+ void disconnect();
+
size_t countTracks() const;
status_t getTrackFormat(size_t index, sp<AMessage> *format, uint32_t flags = 0) const;
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index 090d4e1..0fa9fcb 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.cpp
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -634,7 +634,7 @@
if (mFormat == NULL) {
mFormat = new MetaData;
- if (!MakeAVCCodecSpecificData(*mFormat, accessUnit)) {
+ if (!MakeAVCCodecSpecificData(*mFormat, accessUnit->data(), accessUnit->size())) {
mFormat.clear();
}
}
@@ -1010,7 +1010,7 @@
}
if (mFormat == NULL) {
mFormat = new MetaData;
- if (!MakeAVCCodecSpecificData(*mFormat, mBuffer)) {
+ if (!MakeAVCCodecSpecificData(*mFormat, mBuffer->data(), mBuffer->size())) {
ALOGW("Creating dummy AVC format for scrambled content");
mFormat = new MetaData;
mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
@@ -1172,7 +1172,9 @@
if (mFormat == NULL) {
mFormat = new MetaData;
- if (!MakeAVCCodecSpecificData(*mFormat, accessUnit)) {
+ if (!MakeAVCCodecSpecificData(*mFormat,
+ accessUnit->data(),
+ accessUnit->size())) {
mFormat.clear();
}
}
diff --git a/media/libstagefright/omx/OMXUtils.cpp b/media/libstagefright/omx/OMXUtils.cpp
index f597e02..f7b569d 100644
--- a/media/libstagefright/omx/OMXUtils.cpp
+++ b/media/libstagefright/omx/OMXUtils.cpp
@@ -163,6 +163,8 @@
"audio_decoder.ac3", "audio_encoder.ac3" },
{ MEDIA_MIMETYPE_AUDIO_EAC3,
"audio_decoder.eac3", "audio_encoder.eac3" },
+ { MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC,
+ "image_decoder.heic", "image_encoder.heic" },
};
static const size_t kNumMimeToRole =
diff --git a/media/ndk/NdkMediaExtractor.cpp b/media/ndk/NdkMediaExtractor.cpp
index ac837a3..b5e60a4 100644
--- a/media/ndk/NdkMediaExtractor.cpp
+++ b/media/ndk/NdkMediaExtractor.cpp
@@ -475,5 +475,11 @@
return AMEDIA_OK;
}
+EXPORT
+media_status_t AMediaExtractor_disconnect(AMediaExtractor * ex) {
+ ex->mImpl->disconnect();
+ return AMEDIA_OK;
+}
+
} // extern "C"
diff --git a/media/ndk/NdkMediaFormat.cpp b/media/ndk/NdkMediaFormat.cpp
index 9bf450c..f32b83e 100644
--- a/media/ndk/NdkMediaFormat.cpp
+++ b/media/ndk/NdkMediaFormat.cpp
@@ -296,10 +296,8 @@
EXPORT const char* AMEDIAFORMAT_KEY_DURATION = "durationUs";
EXPORT const char* AMEDIAFORMAT_KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
EXPORT const char* AMEDIAFORMAT_KEY_FRAME_RATE = "frame-rate";
-EXPORT const char* AMEDIAFORMAT_KEY_GRID_COLS = "grid-cols";
-EXPORT const char* AMEDIAFORMAT_KEY_GRID_HEIGHT = "grid-height";
+EXPORT const char* AMEDIAFORMAT_KEY_GRID_COLUMNS = "grid-cols";
EXPORT const char* AMEDIAFORMAT_KEY_GRID_ROWS = "grid-rows";
-EXPORT const char* AMEDIAFORMAT_KEY_GRID_WIDTH = "grid-width";
EXPORT const char* AMEDIAFORMAT_KEY_HDR_STATIC_INFO = "hdr-static-info";
EXPORT const char* AMEDIAFORMAT_KEY_HEIGHT = "height";
EXPORT const char* AMEDIAFORMAT_KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period";
@@ -329,6 +327,8 @@
EXPORT const char* AMEDIAFORMAT_KEY_STRIDE = "stride";
EXPORT const char* AMEDIAFORMAT_KEY_TEMPORAL_LAYER_ID = "temporal-layer-id";
EXPORT const char* AMEDIAFORMAT_KEY_TEMPORAL_LAYERING = "ts-schema";
+EXPORT const char* AMEDIAFORMAT_KEY_TILE_HEIGHT = "tile-height";
+EXPORT const char* AMEDIAFORMAT_KEY_TILE_WIDTH = "tile-width";
EXPORT const char* AMEDIAFORMAT_KEY_TIME_US = "timeUs";
EXPORT const char* AMEDIAFORMAT_KEY_TRACK_ID = "track-id";
EXPORT const char* AMEDIAFORMAT_KEY_TRACK_INDEX = "track-index";
diff --git a/media/ndk/include/media/NdkMediaExtractor.h b/media/ndk/include/media/NdkMediaExtractor.h
index f7b9cfd..1d295e4 100644
--- a/media/ndk/include/media/NdkMediaExtractor.h
+++ b/media/ndk/include/media/NdkMediaExtractor.h
@@ -216,6 +216,12 @@
#endif /* __ANDROID_API__ >= 28 */
+#if __ANDROID_API__ >= 29
+
+media_status_t AMediaExtractor_disconnect(AMediaExtractor *ex);
+
+#endif /* __ANDROID_API__ >= 29 */
+
#endif /* __ANDROID_API__ >= 21 */
__END_DECLS
diff --git a/media/ndk/include/media/NdkMediaFormat.h b/media/ndk/include/media/NdkMediaFormat.h
index 1da9197..687054e 100644
--- a/media/ndk/include/media/NdkMediaFormat.h
+++ b/media/ndk/include/media/NdkMediaFormat.h
@@ -110,10 +110,8 @@
extern const char* AMEDIAFORMAT_KEY_DURATION;
extern const char* AMEDIAFORMAT_KEY_FLAC_COMPRESSION_LEVEL;
extern const char* AMEDIAFORMAT_KEY_FRAME_RATE;
-extern const char* AMEDIAFORMAT_KEY_GRID_COLS;
-extern const char* AMEDIAFORMAT_KEY_GRID_HEIGHT;
+extern const char* AMEDIAFORMAT_KEY_GRID_COLUMNS;
extern const char* AMEDIAFORMAT_KEY_GRID_ROWS;
-extern const char* AMEDIAFORMAT_KEY_GRID_WIDTH;
extern const char* AMEDIAFORMAT_KEY_HDR_STATIC_INFO;
extern const char* AMEDIAFORMAT_KEY_HEIGHT;
extern const char* AMEDIAFORMAT_KEY_INTRA_REFRESH_PERIOD;
@@ -143,6 +141,8 @@
extern const char* AMEDIAFORMAT_KEY_STRIDE;
extern const char* AMEDIAFORMAT_KEY_TEMPORAL_LAYER_ID;
extern const char* AMEDIAFORMAT_KEY_TEMPORAL_LAYERING;
+extern const char* AMEDIAFORMAT_KEY_TILE_HEIGHT;
+extern const char* AMEDIAFORMAT_KEY_TILE_WIDTH;
extern const char* AMEDIAFORMAT_KEY_TIME_US;
extern const char* AMEDIAFORMAT_KEY_TRACK_ID;
extern const char* AMEDIAFORMAT_KEY_TRACK_INDEX;
diff --git a/packages/MediaComponents/Android.mk b/packages/MediaComponents/Android.mk
index 19e4b24..b6480a2 100644
--- a/packages/MediaComponents/Android.mk
+++ b/packages/MediaComponents/Android.mk
@@ -19,7 +19,7 @@
ifneq ($(TARGET_BUILD_PDK),true)
# Build MediaComponents only if this is not a PDK build. MediaComponents won't
# build in PDK builds because frameworks/base/core/java is not available but
-# IMediaSession2.aidl and IMediaSession2Callback.aidl are using classes from
+# IMediaSession2.aidl and IMediaController2.aidl are using classes from
# frameworks/base/core/java.
include $(CLEAR_VARS)
@@ -40,6 +40,9 @@
LOCAL_PROGUARD_FLAG_FILES := proguard.cfg
+# TODO: Enable proguard (b/74090741)
+LOCAL_PROGUARD_ENABLED := disabled
+
LOCAL_MULTILIB := first
LOCAL_JAVA_LIBRARIES += android-support-annotations
diff --git a/packages/MediaComponents/proguard.cfg b/packages/MediaComponents/proguard.cfg
index 43f2e63..d7bf730 100644
--- a/packages/MediaComponents/proguard.cfg
+++ b/packages/MediaComponents/proguard.cfg
@@ -16,5 +16,5 @@
# Keep entry point for updatable Java classes
-keep public class com.android.media.update.ApiFactory {
- public static java.lang.Object initialize(android.content.res.Resources, android.content.res.Resources$Theme);
+ public static com.android.media.update.ApiFactory initialize(android.content.pm.ApplicationInfo);
}
diff --git a/packages/MediaComponents/res/drawable/custom_progress.xml b/packages/MediaComponents/res/drawable/custom_progress.xml
new file mode 100644
index 0000000..9731a6e
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/custom_progress.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item android:id="@android:id/background">
+ <shape android:shape="rectangle" >
+ <solid android:color="#26000000" />
+ </shape>
+ </item>
+ <item android:id="@android:id/secondaryProgress">
+ <clip>
+ <shape android:shape="rectangle" >
+ <solid android:color="#5Cffffff" />
+ </shape>
+ </clip>
+ </item>
+ <item android:id="@android:id/progress">
+ <clip>
+ <shape android:shape="rectangle" >
+ <solid android:color="#ffffff" />
+ </shape>
+ </clip>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/custom_progress_thumb.xml b/packages/MediaComponents/res/drawable/custom_progress_thumb.xml
new file mode 100644
index 0000000..2e247f2
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/custom_progress_thumb.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval" >
+ <solid android:color="#ffffff" />
+ <size
+ android:height="12dp"
+ android:width="12dp" />
+</shape>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/ic_high_quality.xml b/packages/MediaComponents/res/drawable/ic_high_quality.xml
index e27d3e2..f76e22f 100644
--- a/packages/MediaComponents/res/drawable/ic_high_quality.xml
+++ b/packages/MediaComponents/res/drawable/ic_high_quality.xml
@@ -1,9 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:width="34dp"
+ android:height="34dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M0 0h24v24H0z" />
<path
android:fillColor="#FFFFFF"
- android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.9,-2 -2,-2zM11,15L9.5,15v-2h-2v2L6,15L6,9h1.5v2.5h2L9.5,9L11,9v6zM18,14c0,0.55 -0.45,1 -1,1h-0.75v1.5h-1.5L14.75,15L14,15c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h3c0.55,0 1,0.45 1,1v4zM14.5,13.5h2v-3h-2v3z"/>
+ android:pathData="M19 4H5c-1.11 0-2 0.9-2 2v12c0 1.1 0.89 2 2 2h14c1.1 0 2-0.9 2-2V6c0-1.1-0.9-2-2-2zm-8 11H9.5v-2h-2v2H6V9h1.5v2.5h2V9H11v6zm7-1c0 0.55-0.45 1-1 1h-0.75v1.5h-1.5V15H14c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45 -1 1-1h3c0.55 0 1 0.45 1 1v4zm-3.5-0.5h2v-3h-2v3z" />
</vector>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml b/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml
index 389396b..a56d5d9 100644
--- a/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml
+++ b/packages/MediaComponents/res/drawable/ic_replay_circle_filled.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
+ android:width="40dp"
+ android:height="40dp"
android:viewportWidth="24"
android:viewportHeight="24">
diff --git a/packages/MediaComponents/res/drawable/ic_closed_caption_off.xml b/packages/MediaComponents/res/drawable/ic_subtitle_off.xml
similarity index 75%
rename from packages/MediaComponents/res/drawable/ic_closed_caption_off.xml
rename to packages/MediaComponents/res/drawable/ic_subtitle_off.xml
index a79cd11..c0a727a 100644
--- a/packages/MediaComponents/res/drawable/ic_closed_caption_off.xml
+++ b/packages/MediaComponents/res/drawable/ic_subtitle_off.xml
@@ -9,8 +9,7 @@
android:pathData="M0,0h24v24H0V0z" />
<path
android:fillColor="#FFFFFF"
- android:pathData="M19,5.5c0.27,0,0.5,0.23,0.5,0.5v12c0,0.27-0.23,0.5-0.5,0.5H5c-0.28,0-0.5-0.22-0.5-0.5V6c0-0.28,0.22-0.5,0.5-0.5H19
-M19,4H5C3.89,4,3,4.9,3,6v12c0,1.1,0.89,2,2,2h14c1.1,0,2-0.9,2-2V6C21,4.9,20.1,4,19,4L19,4z" />
+ android:pathData="M19.5,5.5v13h-15v-13H19.5z M19,4H5C3.89,4,3,4.9,3,6v12c0,1.1,0.89,2,2,2h14c1.1,0,2-0.9,2-2V6C21,4.9,20.1,4,19,4L19,4z" />
<path
android:fillColor="#FFFFFF"
android:pathData="M11,11H9.5v-0.5h-2v3h2V13H11v1c0,0.55-0.45,1-1,1H7c-0.55,0-1-0.45-1-1v-4c0-0.55,0.45-1,1-1h3c0.55,0,1,0.45,1,1V11z" />
diff --git a/packages/MediaComponents/res/drawable/ic_subtitle_on.xml b/packages/MediaComponents/res/drawable/ic_subtitle_on.xml
new file mode 100644
index 0000000..7c91c06
--- /dev/null
+++ b/packages/MediaComponents/res/drawable/ic_subtitle_on.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M0 0h24v24H0z" />
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M19 4H5c-1.11 0-2 0.9-2 2v12c0 1.1 0.89 2 2 2h14c1.1 0 2-0.9 2-2V6c0-1.1-0.9-2-2-2zm-8 7H9.5v-0.5h-2v3h2V13H11v1c0 0.55-0.45 1-1 1H7c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45 -1 1-1h3c0.55 0 1 0.45 1 1v1zm7 0h-1.5v-0.5h-2v3h2V13H18v1c0 0.55-0.45 1-1 1h-3c-0.55 0-1-0.45-1-1v-4c0-0.55 0.45 -1 1-1h3c0.55 0 1 0.45 1 1v1z" />
+</vector>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/layout/embedded_settings_list_item.xml b/packages/MediaComponents/res/layout/embedded_settings_list_item.xml
new file mode 100644
index 0000000..1156dca
--- /dev/null
+++ b/packages/MediaComponents/res/layout/embedded_settings_list_item.xml
@@ -0,0 +1,63 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_embedded_settings_height"
+ android:orientation="horizontal"
+ android:background="@color/black_opacity_70">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_embedded_settings_height"
+ android:gravity="center"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/mcv2_embedded_settings_icon_size"
+ android:layout_height="@dimen/mcv2_embedded_settings_icon_size"
+ android:layout_margin="8dp"
+ android:gravity="center" />
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_embedded_settings_height"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/main_text"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_embedded_settings_text_height"
+ android:gravity="center"
+ android:paddingLeft="2dp"
+ android:textColor="@color/white"
+ android:textSize="@dimen/mcv2_embedded_settings_main_text_size"/>
+
+ <TextView
+ android:id="@+id/sub_text"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_embedded_settings_text_height"
+ android:layout_below="@id/main_text"
+ android:gravity="center"
+ android:paddingLeft="2dp"
+ android:textColor="@color/white_opacity_70"
+ android:textSize="@dimen/mcv2_embedded_settings_sub_text_size"/>
+ </RelativeLayout>
+</LinearLayout>
+
diff --git a/packages/MediaComponents/res/layout/embedded_sub_settings_list_item.xml b/packages/MediaComponents/res/layout/embedded_sub_settings_list_item.xml
new file mode 100644
index 0000000..5947a72
--- /dev/null
+++ b/packages/MediaComponents/res/layout/embedded_sub_settings_list_item.xml
@@ -0,0 +1,53 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_embedded_settings_height"
+ android:orientation="horizontal"
+ android:background="@color/black_opacity_70">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_embedded_settings_height"
+ android:gravity="center"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/check"
+ android:layout_width="@dimen/mcv2_embedded_settings_icon_size"
+ android:layout_height="@dimen/mcv2_embedded_settings_icon_size"
+ android:layout_margin="8dp"
+ android:gravity="center"
+ android:src="@drawable/ic_check"/>
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_embedded_settings_height"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_embedded_settings_text_height"
+ android:gravity="center"
+ android:paddingLeft="2dp"
+ android:textColor="@color/white"
+ android:textSize="@dimen/mcv2_embedded_settings_main_text_size"/>
+ </RelativeLayout>
+</LinearLayout>
diff --git a/packages/MediaComponents/res/layout/embedded_transport_controls.xml b/packages/MediaComponents/res/layout/embedded_transport_controls.xml
new file mode 100644
index 0000000..a3a5957
--- /dev/null
+++ b/packages/MediaComponents/res/layout/embedded_transport_controls.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="horizontal"
+ android:paddingLeft="@dimen/mcv2_transport_controls_padding"
+ android:paddingRight="@dimen/mcv2_transport_controls_padding"
+ android:visibility="visible">
+
+ <ImageButton android:id="@+id/prev" style="@style/EmbeddedTransportControlsButton.Previous" />
+ <ImageButton android:id="@+id/rew" style="@style/EmbeddedTransportControlsButton.Rew" />
+ <ImageButton android:id="@+id/pause" style="@style/EmbeddedTransportControlsButton.Pause" />
+ <ImageButton android:id="@+id/ffwd" style="@style/EmbeddedTransportControlsButton.Ffwd" />
+ <ImageButton android:id="@+id/next" style="@style/EmbeddedTransportControlsButton.Next" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/layout/full_settings_list_item.xml b/packages/MediaComponents/res/layout/full_settings_list_item.xml
new file mode 100644
index 0000000..f92ea5e
--- /dev/null
+++ b/packages/MediaComponents/res/layout/full_settings_list_item.xml
@@ -0,0 +1,62 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_full_settings_height"
+ android:orientation="horizontal"
+ android:background="@color/black_opacity_70">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_full_settings_height"
+ android:gravity="center"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/mcv2_full_settings_icon_size"
+ android:layout_height="@dimen/mcv2_full_settings_icon_size"
+ android:layout_margin="8dp"
+ android:gravity="center"/>
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_full_settings_height"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/main_text"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_full_settings_text_height"
+ android:paddingLeft="2dp"
+ android:gravity="center"
+ android:textColor="@color/white"
+ android:textSize="@dimen/mcv2_full_settings_main_text_size"/>
+
+ <TextView
+ android:id="@+id/sub_text"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_full_settings_text_height"
+ android:layout_below="@id/main_text"
+ android:gravity="center"
+ android:paddingLeft="2dp"
+ android:textColor="@color/white_opacity_70"
+ android:textSize="@dimen/mcv2_full_settings_sub_text_size"/>
+ </RelativeLayout>
+</LinearLayout>
diff --git a/packages/MediaComponents/res/layout/full_sub_settings_list_item.xml b/packages/MediaComponents/res/layout/full_sub_settings_list_item.xml
new file mode 100644
index 0000000..49128d0
--- /dev/null
+++ b/packages/MediaComponents/res/layout/full_sub_settings_list_item.xml
@@ -0,0 +1,53 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_full_settings_height"
+ android:orientation="horizontal"
+ android:background="@color/black_opacity_70">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_full_settings_height"
+ android:gravity="center"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/check"
+ android:layout_width="@dimen/mcv2_full_settings_icon_size"
+ android:layout_height="@dimen/mcv2_full_settings_icon_size"
+ android:layout_margin="8dp"
+ android:gravity="center"
+ android:src="@drawable/ic_check"/>
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_full_settings_height"
+ android:gravity="center"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_full_settings_text_height"
+ android:gravity="center"
+ android:paddingLeft="2dp"
+ android:textColor="@color/white"
+ android:textSize="@dimen/mcv2_full_settings_main_text_size"/>
+ </RelativeLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/layout/full_transport_controls.xml b/packages/MediaComponents/res/layout/full_transport_controls.xml
new file mode 100644
index 0000000..dd41a1f
--- /dev/null
+++ b/packages/MediaComponents/res/layout/full_transport_controls.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="horizontal"
+ android:paddingLeft="@dimen/mcv2_transport_controls_padding"
+ android:paddingRight="@dimen/mcv2_transport_controls_padding"
+ android:visibility="visible">
+
+ <ImageButton android:id="@+id/prev" style="@style/FullTransportControlsButton.Previous" />
+ <ImageButton android:id="@+id/rew" style="@style/FullTransportControlsButton.Rew" />
+ <ImageButton android:id="@+id/pause" style="@style/FullTransportControlsButton.Pause" />
+ <ImageButton android:id="@+id/ffwd" style="@style/FullTransportControlsButton.Ffwd" />
+ <ImageButton android:id="@+id/next" style="@style/FullTransportControlsButton.Next" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/layout/media_controller.xml b/packages/MediaComponents/res/layout/media_controller.xml
index 8488c84..c5fbee8 100644
--- a/packages/MediaComponents/res/layout/media_controller.xml
+++ b/packages/MediaComponents/res/layout/media_controller.xml
@@ -111,131 +111,126 @@
</RelativeLayout>
<LinearLayout
+ android:id="@+id/center_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
- android:gravity="center"
- android:paddingTop="4dp"
- android:orientation="horizontal">
-
- <ImageButton android:id="@+id/prev" style="@style/TransportControlsButton.Previous" />
- <ImageButton android:id="@+id/rew" style="@style/TransportControlsButton.Rew" />
- <ImageButton android:id="@+id/pause" style="@style/TransportControlsButton.Pause" />
- <ImageButton android:id="@+id/ffwd" style="@style/TransportControlsButton.Ffwd" />
- <ImageButton android:id="@+id/next" style="@style/TransportControlsButton.Next" />
-
+ android:gravity="center">
</LinearLayout>
<SeekBar
android:id="@+id/mediacontroller_progress"
android:layout_width="match_parent"
- android:layout_height="32dp"
- android:padding="0dp"
- android:progressTint="#FFFFFFFF"
- android:thumbTint="#FFFFFFFF"/>
+ android:layout_height="12dp"
+ android:maxHeight="2dp"
+ android:minHeight="2dp"
+ android:padding="0dp"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="44dp"
- android:paddingLeft="15dp"
android:orientation="horizontal">
- <TextView
- android:id="@+id/time_current"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:layout_alignParentStart="true"
- android:paddingEnd="4dp"
- android:paddingStart="4dp"
- android:textSize="14sp"
- android:textStyle="bold"
- android:textColor="#FFFFFF" />
-
- <TextView
- android:id="@+id/time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:layout_toRightOf="@id/time_current"
- android:paddingStart="4dp"
- android:paddingEnd="4dp"
- android:textSize="14sp"
- android:textStyle="bold"
- android:textColor="#BBBBBB" />
-
- <TextView
- android:id="@+id/ad_skip_time"
- android:gravity="center"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:textSize="12sp"
- android:textColor="#FFFFFF"
- android:visibility="gone" />
-
<LinearLayout
- android:id="@+id/basic_controls"
- android:gravity="center"
- android:layout_alignParentRight="true"
+ android:id="@+id/bottom_bar_left"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:orientation="horizontal" >
+ android:layout_height="match_parent"
+ android:layout_alignParentStart="true"
+ android:layout_centerVertical="true">
<TextView
- android:id="@+id/ad_remaining"
+ android:id="@+id/ad_skip_time"
android:gravity="center"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="4dp"
android:textSize="12sp"
android:textColor="#FFFFFF"
android:visibility="gone" />
-
- <ImageButton
- android:id="@+id/mute"
- style="@style/BottomBarButton.Mute" />
- <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>
<LinearLayout
- android:id="@+id/extra_controls"
- android:layout_alignParentRight="true"
+ android:id="@+id/time"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:visibility="gone"
- android:orientation="horizontal"
- android:gravity="center">
+ android:layout_height="match_parent"
+ android:layout_toRightOf="@id/bottom_bar_left"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:gravity="center" >
- <LinearLayout
- android:id="@+id/custom_buttons"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
- <ImageButton
- android:id="@+id/aspect_ratio"
- style="@style/BottomBarButton.AspectRatio" />
- <ImageButton
- android:id="@+id/video_quality"
- style="@style/BottomBarButton.VideoQuality" />
- <ImageButton
- android:id="@+id/settings"
- style="@style/BottomBarButton.Settings" />
- <ImageButton
- android:id="@+id/overflow_left"
- style="@style/BottomBarButton.OverflowLeft"/>
+ <TextView
+ android:id="@+id/time_current"
+ style="@style/TimeText.Current"/>
+ <TextView
+ android:id="@+id/time_interpunct"
+ style="@style/TimeText.Interpunct"/>
+ <TextView
+ android:id="@+id/time_end"
+ style="@style/TimeText.End"/>
</LinearLayout>
+ <LinearLayout
+ android:id="@+id/bottom_bar_right"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:gravity="right">
+
+ <LinearLayout
+ android:id="@+id/basic_controls"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:id="@+id/ad_remaining"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textSize="12sp"
+ android:textColor="#FFFFFF"
+ android:visibility="gone" />
+
+ <ImageButton
+ android:id="@+id/mute"
+ style="@style/BottomBarButton.Mute" />
+ <ImageButton
+ android:id="@+id/subtitle"
+ android:scaleType="fitCenter"
+ android:visibility="gone"
+ style="@style/BottomBarButton.CC" />
+ <ImageButton
+ android:id="@+id/fullscreen"
+ style="@style/BottomBarButton.FullScreen"/>
+ <ImageButton
+ android:id="@+id/overflow_right"
+ style="@style/BottomBarButton.OverflowRight"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/extra_controls"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
+
+ <LinearLayout
+ android:id="@+id/custom_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageButton
+ android:id="@+id/video_quality"
+ style="@style/BottomBarButton.VideoQuality" />
+ <ImageButton
+ android:id="@+id/settings"
+ style="@style/BottomBarButton.Settings" />
+ <ImageButton
+ android:id="@+id/overflow_left"
+ style="@style/BottomBarButton.OverflowLeft"/>
+ </LinearLayout>
+ </LinearLayout>
</RelativeLayout>
</LinearLayout>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/layout/minimal_transport_controls.xml b/packages/MediaComponents/res/layout/minimal_transport_controls.xml
new file mode 100644
index 0000000..9ca3721
--- /dev/null
+++ b/packages/MediaComponents/res/layout/minimal_transport_controls.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="horizontal"
+ android:visibility="visible">
+
+ <ImageButton android:id="@+id/pause" style="@style/MinimalTransportControlsButton.Pause" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/layout/settings_list.xml b/packages/MediaComponents/res/layout/settings_list.xml
index 37a3a60..ea30538 100644
--- a/packages/MediaComponents/res/layout/settings_list.xml
+++ b/packages/MediaComponents/res/layout/settings_list.xml
@@ -15,8 +15,8 @@
-->
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/MediaControlView2_settings_width"
- android:layout_height="@dimen/MediaControlView2_settings_height"
+ android:layout_width="@dimen/mcv2_embedded_settings_width"
+ android:layout_height="@dimen/mcv2_embedded_settings_height"
android:divider="@null"
android:dividerHeight="0dp">
</ListView>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/layout/settings_list_item.xml b/packages/MediaComponents/res/layout/settings_list_item.xml
deleted file mode 100644
index e7522b7..0000000
--- a/packages/MediaComponents/res/layout/settings_list_item.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-<?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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/MediaControlView2_settings_width"
- android:layout_height="@dimen/MediaControlView2_settings_height"
- android:orientation="horizontal"
- android:background="@color/black_transparent_70">
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="@dimen/MediaControlView2_settings_height"
- android:paddingRight="2dp"
- android:gravity="center"
- android:orientation="horizontal">
-
- <ImageView
- android:id="@+id/check"
- android:layout_width="@dimen/MediaControlView2_settings_icon_size"
- android:layout_height="@dimen/MediaControlView2_settings_icon_size"
- android:gravity="center"
- android:paddingLeft="2dp"
- android:src="@drawable/ic_check"/>
-
- <ImageView
- android:id="@+id/icon"
- android:layout_width="@dimen/MediaControlView2_settings_icon_size"
- android:layout_height="@dimen/MediaControlView2_settings_icon_size"
- android:gravity="center"
- android:paddingLeft="2dp"/>
- </LinearLayout>
-
- <RelativeLayout
- android:layout_width="wrap_content"
- android:layout_height="@dimen/MediaControlView2_settings_height"
- android:gravity="center"
- android:orientation="vertical">
-
- <TextView
- android:id="@+id/main_text"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/MediaControlView2_text_width"
- android:paddingLeft="2dp"
- android:textColor="@color/white"
- android:textSize="@dimen/MediaControlView2_settings_main_text_size"/>
-
- <TextView
- android:id="@+id/sub_text"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/MediaControlView2_text_width"
- android:layout_below="@id/main_text"
- android:paddingLeft="2dp"
- android:textColor="@color/white_transparent_70"
- android:textSize="@dimen/MediaControlView2_settings_sub_text_size"/>
- </RelativeLayout>
-
-</LinearLayout>
-
diff --git a/packages/MediaComponents/res/values/arrays.xml b/packages/MediaComponents/res/values/arrays.xml
new file mode 100644
index 0000000..1187320
--- /dev/null
+++ b/packages/MediaComponents/res/values/arrays.xml
@@ -0,0 +1,27 @@
+<?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-array name="speed_multiplied_by_100">
+ <item>25</item>
+ <item>50</item>
+ <item>75</item>
+ <item>100</item>
+ <item>125</item>
+ <item>150</item>
+ <item>200</item>
+ </integer-array>
+</resources>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/values/colors.xml b/packages/MediaComponents/res/values/colors.xml
index 8ba069c..6a65ef5 100644
--- a/packages/MediaComponents/res/values/colors.xml
+++ b/packages/MediaComponents/res/values/colors.xml
@@ -17,6 +17,6 @@
<resources>
<color name="gray">#808080</color>
<color name="white">#ffffff</color>
- <color name="white_transparent_70">#B3ffffff</color>
- <color name="black_transparent_70">#B3000000</color>
+ <color name="white_opacity_70">#B3ffffff</color>
+ <color name="black_opacity_70">#B3000000</color>
</resources>
\ No newline at end of file
diff --git a/packages/MediaComponents/res/values/dimens.xml b/packages/MediaComponents/res/values/dimens.xml
index b5ef626..8d72d40 100644
--- a/packages/MediaComponents/res/values/dimens.xml
+++ b/packages/MediaComponents/res/values/dimens.xml
@@ -41,12 +41,26 @@
<!-- Group list fade out animation duration. -->
<integer name="mr_controller_volume_group_list_fade_out_duration_ms">200</integer>
- <dimen name="MediaControlView2_settings_width">200dp</dimen>
- <dimen name="MediaControlView2_settings_height">36dp</dimen>
- <dimen name="MediaControlView2_settings_icon_size">28dp</dimen>
- <dimen name="MediaControlView2_settings_offset">8dp</dimen>
- <dimen name="MediaControlView2_text_width">18dp</dimen>
+ <dimen name="mcv2_embedded_settings_width">150dp</dimen>
+ <dimen name="mcv2_embedded_settings_height">36dp</dimen>
+ <dimen name="mcv2_embedded_settings_icon_size">20dp</dimen>
+ <dimen name="mcv2_embedded_settings_text_height">18dp</dimen>
+ <dimen name="mcv2_embedded_settings_main_text_size">12sp</dimen>
+ <dimen name="mcv2_embedded_settings_sub_text_size">10sp</dimen>
+ <dimen name="mcv2_full_settings_width">225dp</dimen>
+ <dimen name="mcv2_full_settings_height">54dp</dimen>
+ <dimen name="mcv2_full_settings_icon_size">30dp</dimen>
+ <dimen name="mcv2_full_settings_text_height">27dp</dimen>
+ <dimen name="mcv2_full_settings_main_text_size">16sp</dimen>
+ <dimen name="mcv2_full_settings_sub_text_size">13sp</dimen>
+ <dimen name="mcv2_settings_offset">8dp</dimen>
- <dimen name="MediaControlView2_settings_main_text_size">12sp</dimen>
- <dimen name="MediaControlView2_settings_sub_text_size">10sp</dimen>
+ <dimen name="mcv2_transport_controls_padding">4dp</dimen>
+ <dimen name="mcv2_pause_icon_size">36dp</dimen>
+ <dimen name="mcv2_full_icon_size">28dp</dimen>
+ <dimen name="mcv2_embedded_icon_size">24dp</dimen>
+ <dimen name="mcv2_minimal_icon_size">24dp</dimen>
+ <dimen name="mcv2_icon_margin">10dp</dimen>
+
+ <!-- TODO: adjust bottom bar view -->
</resources>
diff --git a/packages/MediaComponents/res/values/strings.xml b/packages/MediaComponents/res/values/strings.xml
index 305cef4..69e9dff 100644
--- a/packages/MediaComponents/res/values/strings.xml
+++ b/packages/MediaComponents/res/values/strings.xml
@@ -111,15 +111,20 @@
<string name="MediaControlView2_audio_track_none_text">None</string>
<string name="MediaControlView2_video_quality_text">Video quality</string>
<string name="MediaControlView2_video_quality_auto_text">Auto</string>
- <string name="MediaControlView2_playback_speed_text">Playback speed</string>
- <string name="MediaControlView2_playback_speed_0_25x_text">0.25x</string>
- <string name="MediaControlView2_playback_speed_0_5x_text">0.5x</string>
- <string name="MediaControlView2_playback_speed_0_75x_text">0.75x</string>
- <string name="MediaControlView2_playback_speed_1x_text">Normal</string>
- <string name="MediaControlView2_playback_speed_1_25x_text">1.25x</string>
- <string name="MediaControlView2_playback_speed_1_5x_text">1.5x</string>
- <string name="MediaControlView2_playback_speed_2x_text">2x</string>
<string name="MediaControlView2_help_text">Help & feedback</string>
+ <string name="MediaControlView2_playback_speed_text">Playback speed</string>
+ <string-array name="MediaControlView2_playback_speeds">
+ <item>0.25x</item>
+ <item>0.5x</item>
+ <item>0.75x</item>
+ <item>Normal</item>
+ <item>1.25x</item>
+ <item>1.5x</item>
+ <item>2x</item>
+ </string-array>
+ <!-- Placeholder text for displaying time. Used to calculate which size layout to use. -->
+ <string name="MediaControlView2_time_placeholder">00:00:00</string>
+
<!-- Text for displaying subtitle track number. -->
<string name="MediaControlView2_subtitle_track_number_text">
Track <xliff:g id="track_number" example="1">%1$s</xliff:g>
diff --git a/packages/MediaComponents/res/values/style.xml b/packages/MediaComponents/res/values/style.xml
index 23c7bc9..b1da137 100644
--- a/packages/MediaComponents/res/values/style.xml
+++ b/packages/MediaComponents/res/values/style.xml
@@ -1,29 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <style name="TransportControlsButton">
+ <style name="FullTransportControlsButton">
<item name="android:background">@null</item>
- <item name="android:layout_width">70dp</item>
- <item name="android:layout_height">40dp</item>
+ <item name="android:layout_margin">@dimen/mcv2_icon_margin</item>
+ <item name="android:scaleType">fitXY</item>
</style>
- <style name="TransportControlsButton.Previous">
+ <style name="FullTransportControlsButton.Previous">
<item name="android:src">@drawable/ic_skip_previous</item>
+ <item name="android:layout_width">@dimen/mcv2_full_icon_size</item>
+ <item name="android:layout_height">@dimen/mcv2_full_icon_size</item>
</style>
- <style name="TransportControlsButton.Next">
+ <style name="FullTransportControlsButton.Next">
<item name="android:src">@drawable/ic_skip_next</item>
+ <item name="android:layout_width">@dimen/mcv2_full_icon_size</item>
+ <item name="android:layout_height">@dimen/mcv2_full_icon_size</item>
</style>
- <style name="TransportControlsButton.Pause">
+ <style name="FullTransportControlsButton.Pause">
<item name="android:src">@drawable/ic_pause_circle_filled</item>
+ <item name="android:layout_width">@dimen/mcv2_pause_icon_size</item>
+ <item name="android:layout_height">@dimen/mcv2_pause_icon_size</item>
</style>
- <style name="TransportControlsButton.Ffwd">
+ <style name="FullTransportControlsButton.Ffwd">
<item name="android:src">@drawable/ic_forward_30</item>
+ <item name="android:layout_width">@dimen/mcv2_full_icon_size</item>
+ <item name="android:layout_height">@dimen/mcv2_full_icon_size</item>
</style>
- <style name="TransportControlsButton.Rew">
+ <style name="FullTransportControlsButton.Rew">
<item name="android:src">@drawable/ic_rewind_10</item>
+ <item name="android:layout_width">@dimen/mcv2_full_icon_size</item>
+ <item name="android:layout_height">@dimen/mcv2_full_icon_size</item>
+ </style>
+
+ <style name="EmbeddedTransportControlsButton">
+ <item name="android:background">@null</item>
+ <item name="android:layout_margin">@dimen/mcv2_icon_margin</item>
+ <item name="android:scaleType">fitXY</item>
+ </style>
+
+ <style name="EmbeddedTransportControlsButton.Previous">
+ <item name="android:src">@drawable/ic_skip_previous</item>
+ <item name="android:layout_width">@dimen/mcv2_embedded_icon_size</item>
+ <item name="android:layout_height">@dimen/mcv2_embedded_icon_size</item>
+ </style>
+
+ <style name="EmbeddedTransportControlsButton.Next">
+ <item name="android:src">@drawable/ic_skip_next</item>
+ <item name="android:layout_width">@dimen/mcv2_embedded_icon_size</item>
+ <item name="android:layout_height">@dimen/mcv2_embedded_icon_size</item>
+ </style>
+
+ <style name="EmbeddedTransportControlsButton.Pause">
+ <item name="android:src">@drawable/ic_pause_circle_filled</item>
+ <item name="android:layout_width">@dimen/mcv2_pause_icon_size</item>
+ <item name="android:layout_height">@dimen/mcv2_pause_icon_size</item>
+ </style>
+
+ <style name="EmbeddedTransportControlsButton.Ffwd">
+ <item name="android:src">@drawable/ic_forward_30</item>
+ <item name="android:layout_width">@dimen/mcv2_embedded_icon_size</item>
+ <item name="android:layout_height">@dimen/mcv2_embedded_icon_size</item>
+ </style>
+
+ <style name="EmbeddedTransportControlsButton.Rew">
+ <item name="android:src">@drawable/ic_rewind_10</item>
+ <item name="android:layout_width">@dimen/mcv2_embedded_icon_size</item>
+ <item name="android:layout_height">@dimen/mcv2_embedded_icon_size</item>
+ </style>
+
+ <style name="MinimalTransportControlsButton">
+ <item name="android:background">@null</item>
+ <item name="android:scaleType">fitXY</item>
+ </style>
+
+ <style name="MinimalTransportControlsButton.Pause">
+ <item name="android:src">@drawable/ic_pause_circle_filled</item>
+ <item name="android:layout_width">@dimen/mcv2_minimal_icon_size</item>
+ <item name="android:layout_height">@dimen/mcv2_minimal_icon_size</item>
</style>
<style name="TitleBar">
@@ -44,18 +101,42 @@
<item name="android:src">@drawable/ic_launch</item>
</style>
+ <style name="TimeText">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:paddingStart">4dp</item>
+ <item name="android:paddingEnd">4dp</item>
+ <item name="android:textStyle">bold</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:gravity">center</item>
+ </style>
+
+ <style name="TimeText.Current">
+ <item name="android:textColor">@color/white</item>
+ <item name="android:text">@string/MediaControlView2_time_placeholder</item>
+ </style>
+
+ <style name="TimeText.Interpunct">
+ <item name="android:textColor">@color/white_opacity_70</item>
+ <item name="android:text">·</item>
+ </style>
+
+ <style name="TimeText.End">
+ <item name="android:textColor">@color/white_opacity_70</item>
+ <item name="android:text">@string/MediaControlView2_time_placeholder</item>
+ </style>
+
<style name="BottomBarButton">
<item name="android:background">@null</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>
+ <item name="android:layout_width">@dimen/mcv2_embedded_icon_size</item>
+ <item name="android:layout_height">@dimen/mcv2_embedded_icon_size</item>
+ <item name="android:layout_margin">@dimen/mcv2_icon_margin</item>
+ <item name="android:gravity">center_horizontal</item>
+ <item name="android:scaleType">fitXY</item>
</style>
<style name="BottomBarButton.CC">
- <item name="android:src">@drawable/ic_media_subtitle_disabled</item>
+ <item name="android:src">@drawable/ic_subtitle_off</item>
</style>
<style name="BottomBarButton.FullScreen">
@@ -74,12 +155,8 @@
<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>
+ <item name="android:src">@drawable/ic_unmute</item>
</style>
<style name="BottomBarButton.VideoQuality">
diff --git a/packages/MediaComponents/runcts.sh b/packages/MediaComponents/runcts.sh
index 0cf0e44..61b1a1e 100644
--- a/packages/MediaComponents/runcts.sh
+++ b/packages/MediaComponents/runcts.sh
@@ -23,6 +23,7 @@
echo ' -h|--help: This help'
echo ' --skip: Skip build and flash. Just rerun-tests'
echo ' --min: Only rebuild tests and updatable library.'
+ echo ' --test: Only rebuild tests'
echo ' -s [device_id]: Specify a device name to run test against.'
echo ' You can define ${ADBHOST} instead.'
echo ' -r [count]: Repeat tests for given count. It will stop when fails.'
@@ -58,6 +59,7 @@
while true; do
local OPTION_SKIP="false"
local OPTION_MIN="false"
+ local OPTION_TEST="false"
local OPTION_REPEAT_COUNT="1"
local OPTION_IGNORE="false"
local OPTION_TEST_TARGET="${DEFAULT_TEST_TARGET}"
@@ -74,6 +76,9 @@
--min)
OPTION_MIN="true"
;;
+ --test)
+ OPTION_TEST="true"
+ ;;
-s)
shift
adbhost_local=${1}
@@ -133,36 +138,43 @@
fi
# Build test apk and required apk.
- local build_targets="${BUILD_TARGETS[@]}"
- if [[ "${OPTION_MIN}" != "true" ]]; then
- build_targets="${build_targets} droid"
+ local build_targets
+ if [[ "${OPTION_TEST}" == "true" ]]; then
+ build_targets="${INSTALL_TARGETS[@]}"
+ elif [[ "${OPTION_MIN}" == "true" ]]; then
+ build_targets="${BUILD_TARGETS[@]}"
+ else
+ build_targets="${BUILD_TARGETS[@]} droid"
fi
m ${build_targets} -j || break
- local device_build_type="$(${adb} shell getprop ro.build.type)"
- if [[ "${device_build_type}" == "user" ]]; then
- # User build. Cannot adb sync
- ${adb} reboot bootloader
- fastboot flashall
- else
- ${adb} root
- local device_verity_mode="$(${adb} shell getprop ro.boot.veritymode)"
- if [[ "${device_verity_mode}" != "disabled" ]]; then
- ${adb} disable-verity
- ${adb} reboot
- ${adb} wait-for-device || break
+ if [[ "${OPTION_TEST}" != "true" ]]; then
+ # Flash only when needed
+ local device_build_type="$(${adb} shell getprop ro.build.type)"
+ if [[ "${device_build_type}" == "user" ]]; then
+ # User build. Cannot adb sync
+ ${adb} reboot bootloader
+ fastboot flashall
+ else
${adb} root
+ local device_verity_mode="$(${adb} shell getprop ro.boot.veritymode)"
+ if [[ "${device_verity_mode}" != "disabled" ]]; then
+ ${adb} disable-verity
+ ${adb} reboot
+ ${adb} wait-for-device || break
+ ${adb} root
+ fi
+ ${adb} remount
+ ${adb} shell stop
+ ${adb} shell setprop log.tag.MediaSessionService DEBUG
+ ${adb} sync
+ ${adb} shell start
fi
- ${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.
+ # TODO(jaewan): Find better way to wait
+ sleep 15
fi
- ${adb} wait-for-device || break
- # Ensure package manager is loaded.
- # TODO(jaewan): Find better way to wait
- sleep 15
# Install apks
local install_failed="false"
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl b/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
similarity index 67%
rename from packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
rename to packages/MediaComponents/src/com/android/media/IMediaController2.aidl
index 3d812f8..0488b70 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
@@ -23,21 +23,29 @@
import com.android.media.IMediaSession2;
/**
- * Interface from MediaSession2 to MediaSession2Record.
+ * Interface from MediaSession2 to MediaController2.
* <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);
+// TODO(jaewan): (Post P) Handle when the playlist becomes too huge.
+// Note that ParcelledSliceList isn't a good idea for the purpose. (see: b/37493677)
+oneway interface IMediaController2 {
+ void onPlayerStateChanged(int state);
+ void onPositionChanged(long eventTimeMs, long positionMs);
+ void onPlaybackSpeedChanged(float speed);
+ void onBufferedPositionChanged(long bufferedPositionMs);
+ void onPlaylistChanged(in List<Bundle> playlist, in Bundle metadata);
+ void onPlaylistMetadataChanged(in Bundle metadata);
void onPlaybackInfoChanged(in Bundle playbackInfo);
+ void onRepeatModeChanged(int repeatMode);
+ void onShuffleModeChanged(int shuffleMode);
+ void onError(int errorCode, in Bundle extras);
- // TODO(jaewan): Handle when the playlist becomes too huge.
- void onConnected(IMediaSession2 sessionBinder, in Bundle commandGroup, in Bundle playbackState,
- in Bundle playbackInfo, in Bundle params, in List<Bundle> playlist,
- in PendingIntent sessionActivity);
+ void onConnected(IMediaSession2 sessionBinder, in Bundle commandGroup,
+ int playerState, long positionEventTimeMs, long positionMs, float playbackSpeed,
+ long bufferedPositionMs, in Bundle playbackInfo, int repeatMode, int shuffleMode,
+ in List<Bundle> playlist, in PendingIntent sessionActivity);
void onDisconnected();
void onCustomLayoutChanged(in List<Bundle> commandButtonlist);
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
index ef7120d..664467d 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
@@ -20,55 +20,66 @@
import android.os.ResultReceiver;
import android.net.Uri;
-import com.android.media.IMediaSession2Callback;
+import com.android.media.IMediaController2;
/**
- * Interface to MediaSession2.
+ * Interface from MediaController2 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.
*/
+ // TODO(jaewan): (Post P) Handle when the playlist becomes too huge.
+ // Note that ParcelledSliceList isn't a good idea for the purpose. (see: b/37493677)
oneway 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
+ // TODO(jaewan): (Post P) 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(IMediaSession2Callback caller, String callingPackage);
- void release(IMediaSession2Callback caller);
+ void connect(IMediaController2 caller, String callingPackage);
+ void release(IMediaController2 caller);
- void setVolumeTo(IMediaSession2Callback caller, int value, int flags);
- void adjustVolume(IMediaSession2Callback caller, int direction, int flags);
+ void setVolumeTo(IMediaController2 caller, int value, int flags);
+ void adjustVolume(IMediaController2 caller, int direction, int flags);
//////////////////////////////////////////////////////////////////////////////////////////////
// send command
//////////////////////////////////////////////////////////////////////////////////////////////
- void sendTransportControlCommand(IMediaSession2Callback caller,
+ void sendTransportControlCommand(IMediaController2 caller,
int commandCode, in Bundle args);
- void sendCustomCommand(IMediaSession2Callback caller, in Bundle command, in Bundle args,
+ void sendCustomCommand(IMediaController2 caller, in Bundle command, in Bundle args,
in ResultReceiver receiver);
- void prepareFromUri(IMediaSession2Callback caller, in Uri uri, in Bundle extras);
- void prepareFromSearch(IMediaSession2Callback caller, String query, in Bundle extras);
- void prepareFromMediaId(IMediaSession2Callback caller, String mediaId, in Bundle extras);
- void playFromUri(IMediaSession2Callback caller, in Uri uri, in Bundle extras);
- void playFromSearch(IMediaSession2Callback caller, String query, in Bundle extras);
- void playFromMediaId(IMediaSession2Callback caller, String mediaId, in Bundle extras);
- void setRating(IMediaSession2Callback caller, String mediaId, in Bundle rating);
+ void prepareFromUri(IMediaController2 caller, in Uri uri, in Bundle extras);
+ void prepareFromSearch(IMediaController2 caller, String query, in Bundle extras);
+ void prepareFromMediaId(IMediaController2 caller, String mediaId, in Bundle extras);
+ void playFromUri(IMediaController2 caller, in Uri uri, in Bundle extras);
+ void playFromSearch(IMediaController2 caller, String query, in Bundle extras);
+ void playFromMediaId(IMediaController2 caller, String mediaId, in Bundle extras);
+ void setRating(IMediaController2 caller, String mediaId, in Bundle rating);
+
+ void setPlaylist(IMediaController2 caller, in List<Bundle> playlist, in Bundle metadata);
+ void updatePlaylistMetadata(IMediaController2 caller, in Bundle metadata);
+ void addPlaylistItem(IMediaController2 caller, int index, in Bundle mediaItem);
+ void removePlaylistItem(IMediaController2 caller, in Bundle mediaItem);
+ void replacePlaylistItem(IMediaController2 caller, int index, in Bundle mediaItem);
+ void skipToPlaylistItem(IMediaController2 caller, in Bundle mediaItem);
+ void skipToPreviousItem(IMediaController2 caller);
+ void skipToNextItem(IMediaController2 caller);
+ void setRepeatMode(IMediaController2 caller, int repeatMode);
+ void setShuffleMode(IMediaController2 caller, int shuffleMode);
//////////////////////////////////////////////////////////////////////////////////////////////
// library service specific
//////////////////////////////////////////////////////////////////////////////////////////////
- void getLibraryRoot(IMediaSession2Callback caller, in Bundle rootHints);
- void getItem(IMediaSession2Callback caller, String mediaId);
- void getChildren(IMediaSession2Callback caller, String parentId, int page, int pageSize,
+ void getLibraryRoot(IMediaController2 caller, in Bundle rootHints);
+ void getItem(IMediaController2 caller, String mediaId);
+ void getChildren(IMediaController2 caller, String parentId, int page, int pageSize,
in Bundle extras);
- void search(IMediaSession2Callback caller, String query, in Bundle extras);
- void getSearchResult(IMediaSession2Callback caller, String query, int page, int pageSize,
+ void search(IMediaController2 caller, String query, in Bundle extras);
+ void getSearchResult(IMediaController2 caller, String query, int page, int pageSize,
in Bundle extras);
- void subscribe(IMediaSession2Callback caller, String parentId, in Bundle extras);
- void unsubscribe(IMediaSession2Callback caller, String parentId);
+ void subscribe(IMediaController2 caller, String parentId, in Bundle extras);
+ void unsubscribe(IMediaController2 caller, String parentId);
}
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index 96fdda0..0091816 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -16,7 +16,20 @@
package com.android.media;
-import static android.media.MediaSession2.*;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_ADD_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_SET_REPEAT_MODE;
+import static android.media.MediaSession2.COMMAND_CODE_PLAYLIST_SET_SHUFFLE_MODE;
+import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_MEDIA_ID;
+import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH;
+import static android.media.MediaSession2.COMMAND_CODE_PLAY_FROM_URI;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH;
+import static android.media.MediaSession2.COMMAND_CODE_PREPARE_FROM_URI;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -28,13 +41,14 @@
import android.media.MediaController2.ControllerCallback;
import android.media.MediaController2.PlaybackInfo;
import android.media.MediaItem2;
+import android.media.MediaMetadata2;
+import android.media.MediaPlaylistAgent.RepeatMode;
+import android.media.MediaPlaylistAgent.ShuffleMode;
import android.media.MediaSession2;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
-import android.media.MediaSession2.PlaylistParams;
import android.media.MediaSessionService2;
-import android.media.PlaybackState2;
import android.media.Rating2;
import android.media.SessionToken2;
import android.media.update.MediaController2Provider;
@@ -61,7 +75,7 @@
private final Context mContext;
private final Object mLock = new Object();
- private final MediaSession2CallbackStub mSessionCallbackStub;
+ private final MediaController2Stub mControllerStub;
private final SessionToken2 mToken;
private final ControllerCallback mCallback;
private final Executor mCallbackExecutor;
@@ -72,11 +86,23 @@
@GuardedBy("mLock")
private boolean mIsReleased;
@GuardedBy("mLock")
- private PlaybackState2 mPlaybackState;
- @GuardedBy("mLock")
private List<MediaItem2> mPlaylist;
@GuardedBy("mLock")
- private PlaylistParams mPlaylistParams;
+ private MediaMetadata2 mPlaylistMetadata;
+ @GuardedBy("mLock")
+ private @RepeatMode int mRepeatMode;
+ @GuardedBy("mLock")
+ private @ShuffleMode int mShuffleMode;
+ @GuardedBy("mLock")
+ private int mPlayerState;
+ @GuardedBy("mLock")
+ private long mPositionEventTimeMs;
+ @GuardedBy("mLock")
+ private long mPositionMs;
+ @GuardedBy("mLock")
+ private float mPlaybackSpeed;
+ @GuardedBy("mLock")
+ private long mBufferedPositionMs;
@GuardedBy("mLock")
private PlaybackInfo mPlaybackInfo;
@GuardedBy("mLock")
@@ -110,7 +136,7 @@
throw new IllegalArgumentException("executor shouldn't be null");
}
mContext = context;
- mSessionCallbackStub = new MediaSession2CallbackStub(this);
+ mControllerStub = new MediaController2Stub(this);
mToken = token;
mCallback = callback;
mCallbackExecutor = executor;
@@ -191,7 +217,7 @@
private void connectToSession(IMediaSession2 sessionBinder) {
try {
- sessionBinder.connect(mSessionCallbackStub, mContext.getPackageName());
+ sessionBinder.connect(mControllerStub, mContext.getPackageName());
} catch (RemoteException e) {
Log.w(TAG, "Failed to call connection request. Framework will retry"
+ " automatically");
@@ -216,12 +242,12 @@
}
binder = mSessionBinder;
mSessionBinder = null;
- mSessionCallbackStub.destroy();
+ mControllerStub.destroy();
}
if (binder != null) {
try {
binder.asBinder().unlinkToDeath(mDeathRecipient, 0);
- binder.release(mSessionCallbackStub);
+ binder.release(mControllerStub);
} catch (RemoteException e) {
// No-op.
}
@@ -235,8 +261,8 @@
return mSessionBinder;
}
- MediaSession2CallbackStub getControllerStub() {
- return mSessionCallbackStub;
+ MediaController2Stub getControllerStub() {
+ return mControllerStub;
}
Executor getCallbackExecutor() {
@@ -314,13 +340,48 @@
}
@Override
+ public void skipToPlaylistItem_impl(MediaItem2 item) {
+ if (item == null) {
+ throw new IllegalArgumentException("item shouldn't be null");
+ }
+ final IMediaSession2 binder = mSessionBinder;
+ if (binder != null) {
+ try {
+ binder.skipToPlaylistItem(mControllerStub, item.toBundle());
+ } 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());
+ }
+ }
+
+ @Override
public void skipToPreviousItem_impl() {
- sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM);
+ final IMediaSession2 binder = mSessionBinder;
+ if (binder != null) {
+ try {
+ binder.skipToPreviousItem(mControllerStub);
+ } 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());
+ }
}
@Override
public void skipToNextItem_impl() {
- sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM);
+ final IMediaSession2 binder = mSessionBinder;
+ if (binder != null) {
+ try {
+ binder.skipToNextItem(mControllerStub);
+ } 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());
+ }
}
private void sendTransportControlCommand(int commandCode) {
@@ -331,7 +392,7 @@
final IMediaSession2 binder = mSessionBinder;
if (binder != null) {
try {
- binder.sendTransportControlCommand(mSessionCallbackStub, commandCode, args);
+ binder.sendTransportControlCommand(mControllerStub, commandCode, args);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -340,9 +401,6 @@
}
}
- //////////////////////////////////////////////////////////////////////////////////////
- // TODO(jaewan): Implement follows
- //////////////////////////////////////////////////////////////////////////////////////
@Override
public PendingIntent getSessionActivity_impl() {
return mSessionActivity;
@@ -354,7 +412,7 @@
final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYBACK_SET_VOLUME);
if (binder != null) {
try {
- binder.setVolumeTo(mSessionCallbackStub, value, flags);
+ binder.setVolumeTo(mControllerStub, value, flags);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -369,7 +427,7 @@
final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYBACK_SET_VOLUME);
if (binder != null) {
try {
- binder.adjustVolume(mSessionCallbackStub, direction, flags);
+ binder.adjustVolume(mControllerStub, direction, flags);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -386,7 +444,7 @@
}
if (binder != null) {
try {
- binder.prepareFromUri(mSessionCallbackStub, uri, extras);
+ binder.prepareFromUri(mControllerStub, uri, extras);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -403,7 +461,7 @@
}
if (binder != null) {
try {
- binder.prepareFromSearch(mSessionCallbackStub, query, extras);
+ binder.prepareFromSearch(mControllerStub, query, extras);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -420,7 +478,7 @@
}
if (binder != null) {
try {
- binder.prepareFromMediaId(mSessionCallbackStub, mediaId, extras);
+ binder.prepareFromMediaId(mControllerStub, mediaId, extras);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -437,7 +495,7 @@
}
if (binder != null) {
try {
- binder.playFromUri(mSessionCallbackStub, uri, extras);
+ binder.playFromUri(mControllerStub, uri, extras);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -454,7 +512,7 @@
}
if (binder != null) {
try {
- binder.playFromSearch(mSessionCallbackStub, query, extras);
+ binder.playFromSearch(mControllerStub, query, extras);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -471,7 +529,7 @@
}
if (binder != null) {
try {
- binder.playFromMediaId(mSessionCallbackStub, mediaId, extras);
+ binder.playFromMediaId(mControllerStub, mediaId, extras);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -492,7 +550,7 @@
final IMediaSession2 binder = mSessionBinder;
if (binder != null) {
try {
- binder.setRating(mSessionCallbackStub, mediaId, rating.toBundle());
+ binder.setRating(mControllerStub, mediaId, rating.toBundle());
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -509,7 +567,7 @@
final IMediaSession2 binder = getSessionBinderIfAble(command);
if (binder != null) {
try {
- binder.sendCustomCommand(mSessionCallbackStub, command.toBundle(), args, cb);
+ binder.sendCustomCommand(mControllerStub, command.toBundle(), args, cb);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to the service or the session is gone", e);
}
@@ -526,6 +584,51 @@
}
@Override
+ public void setPlaylist_impl(List<MediaItem2> list, MediaMetadata2 metadata) {
+ if (list == null) {
+ throw new IllegalArgumentException("list shouldn't be null");
+ }
+ final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_SET_LIST);
+ if (binder != null) {
+ List<Bundle> bundleList = new ArrayList<>();
+ for (int i = 0; i < list.size(); i++) {
+ bundleList.add(list.get(i).toBundle());
+ }
+ Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
+ try {
+ binder.setPlaylist(mControllerStub, bundleList, metadataBundle);
+ } 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());
+ }
+ }
+
+ @Override
+ public MediaMetadata2 getPlaylistMetadata_impl() {
+ synchronized (mLock) {
+ return mPlaylistMetadata;
+ }
+ }
+
+ @Override
+ public void updatePlaylistMetadata_impl(MediaMetadata2 metadata) {
+ final IMediaSession2 binder = getSessionBinderIfAble(
+ COMMAND_CODE_PLAYLIST_SET_LIST_METADATA);
+ if (binder != null) {
+ Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
+ try {
+ binder.updatePlaylistMetadata(mControllerStub, metadataBundle);
+ } 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());
+ }
+ }
+
+ @Override
public void prepare_impl() {
sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE);
}
@@ -551,61 +654,98 @@
}
@Override
- public void skipToPlaylistItem_impl(MediaItem2 item) {
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
- }
-
- // TODO(jaewan): Implement this
- /*
- Bundle args = new Bundle();
- args.putInt(MediaSession2Stub.ARGUMENT_KEY_ITEM_INDEX, item);
- sendTransportControlCommand(
- MediaSession2.COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM, args);
- */
- }
-
- @Override
- public PlaybackState2 getPlaybackState_impl() {
- synchronized (mLock) {
- return mPlaybackState;
- }
- }
-
- @Override
public void addPlaylistItem_impl(int index, MediaItem2 item) {
- // TODO(jaewan): Implement (b/73149584)
if (index < 0) {
throw new IllegalArgumentException("index shouldn't be negative");
}
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
+ final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_ADD_ITEM);
+ if (binder != null) {
+ try {
+ binder.addPlaylistItem(mControllerStub, index, item.toBundle());
+ } 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());
+ }
}
@Override
public void removePlaylistItem_impl(MediaItem2 item) {
- // TODO(jaewan): Implement (b/73149584)
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
+ final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_REMOVE_ITEM);
+ if (binder != null) {
+ try {
+ binder.removePlaylistItem(mControllerStub, item.toBundle());
+ } 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());
+ }
}
@Override
public void replacePlaylistItem_impl(int index, MediaItem2 item) {
- // TODO: Implement this (b/73149407)
if (index < 0) {
throw new IllegalArgumentException("index shouldn't be negative");
}
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
+ final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_REPLACE_ITEM);
+ if (binder != null) {
+ try {
+ binder.replacePlaylistItem(mControllerStub, index, item.toBundle());
+ } 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());
+ }
}
@Override
- public PlaylistParams getPlaylistParams_impl() {
- synchronized (mLock) {
- return mPlaylistParams;
+ public int getShuffleMode_impl() {
+ return mShuffleMode;
+ }
+
+ @Override
+ public void setShuffleMode_impl(int shuffleMode) {
+ final IMediaSession2 binder = getSessionBinderIfAble(
+ COMMAND_CODE_PLAYLIST_SET_SHUFFLE_MODE);
+ if (binder != null) {
+ try {
+ binder.setShuffleMode(mControllerStub, shuffleMode);
+ } 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());
+ }
+ }
+
+ @Override
+ public int getRepeatMode_impl() {
+ return mRepeatMode;
+ }
+
+ @Override
+ public void setRepeatMode_impl(int repeatMode) {
+ final IMediaSession2 binder = getSessionBinderIfAble(COMMAND_CODE_PLAYLIST_SET_REPEAT_MODE);
+ if (binder != null) {
+ try {
+ binder.setRepeatMode(mControllerStub, repeatMode);
+ } 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());
}
}
@@ -617,37 +757,33 @@
}
@Override
- public void setPlaylistParams_impl(PlaylistParams params) {
- if (params == null) {
- throw new IllegalArgumentException("params shouldn't be null");
- }
- Bundle args = new Bundle();
- args.putBundle(MediaSession2Stub.ARGUMENT_KEY_PLAYLIST_PARAMS, params.toBundle());
- sendTransportControlCommand(MediaSession2.COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS, args);
- }
-
- @Override
public int getPlayerState_impl() {
- // TODO(jaewan): Implement
- return 0;
+ synchronized (mLock) {
+ return mPlayerState;
+ }
}
@Override
public long getPosition_impl() {
- // TODO(jaewan): Implement
- return 0;
+ synchronized (mLock) {
+ long timeDiff = System.currentTimeMillis() - mPositionEventTimeMs;
+ long expectedPosition = mPositionMs + (long) (mPlaybackSpeed * timeDiff);
+ return Math.max(0, expectedPosition);
+ }
}
@Override
public float getPlaybackSpeed_impl() {
- // TODO(jaewan): Implement
- return 0;
+ synchronized (mLock) {
+ return mPlaybackSpeed;
+ }
}
@Override
public long getBufferedPosition_impl() {
- // TODO(jaewan): Implement
- return 0;
+ synchronized (mLock) {
+ return mBufferedPositionMs;
+ }
}
@Override
@@ -656,27 +792,52 @@
return null;
}
- void pushPlaybackStateChanges(final PlaybackState2 state) {
+ void pushPlayerStateChanges(final int state) {
synchronized (mLock) {
- mPlaybackState = state;
+ mPlayerState = state;
}
mCallbackExecutor.execute(() -> {
if (!mInstance.isConnected()) {
return;
}
- mCallback.onPlaybackStateChanged(mInstance, state);
+ mCallback.onPlayerStateChanged(mInstance, state);
});
}
- void pushPlaylistParamsChanges(final PlaylistParams params) {
+ void pushPositionChanges(final long eventTimeMs, final long positionMs) {
synchronized (mLock) {
- mPlaylistParams = params;
+ mPositionEventTimeMs = eventTimeMs;
+ mPositionMs = positionMs;
}
mCallbackExecutor.execute(() -> {
if (!mInstance.isConnected()) {
return;
}
- mCallback.onPlaylistParamsChanged(mInstance, params);
+ mCallback.onPositionChanged(mInstance, eventTimeMs, positionMs);
+ });
+ }
+
+ void pushPlaybackSpeedChanges(final float speed) {
+ synchronized (mLock) {
+ mPlaybackSpeed = speed;
+ }
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ mCallback.onPlaybackSpeedChanged(mInstance, speed);
+ });
+ }
+
+ void pushBufferedPositionChanges(final long bufferedPositionMs) {
+ synchronized (mLock) {
+ mBufferedPositionMs = bufferedPositionMs;
+ }
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ mCallback.onBufferedPositionChanged(mInstance, bufferedPositionMs);
});
}
@@ -692,22 +853,80 @@
});
}
- void pushPlaylistChanges(final List<MediaItem2> playlist) {
+ void pushPlaylistChanges(final List<MediaItem2> playlist, final MediaMetadata2 metadata) {
synchronized (mLock) {
mPlaylist = playlist;
- mCallbackExecutor.execute(() -> {
- if (!mInstance.isConnected()) {
- return;
- }
- mCallback.onPlaylistChanged(mInstance, playlist);
- });
+ mPlaylistMetadata = metadata;
}
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ // TODO(jaewan): Fix public API not to take playlistAgent.
+ mCallback.onPlaylistChanged(mInstance, null, playlist, metadata);
+ });
+ }
+
+ void pushPlaylistMetadataChanges(MediaMetadata2 metadata) {
+ synchronized (mLock) {
+ mPlaylistMetadata = metadata;
+ }
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ // TODO(jaewan): Fix public API not to take playlistAgent.
+ mCallback.onPlaylistMetadataChanged(mInstance, null, metadata);
+ });
+ }
+
+ void pushShuffleModeChanges(int shuffleMode) {
+ synchronized (mLock) {
+ mShuffleMode = shuffleMode;
+ }
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ // TODO(jaewan): Fix public API not to take playlistAgent.
+ mCallback.onShuffleModeChanged(mInstance, null, shuffleMode);
+ });
+ }
+
+ void pushRepeatModeChanges(int repeatMode) {
+ synchronized (mLock) {
+ mRepeatMode = repeatMode;
+ }
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ // TODO(jaewan): Fix public API not to take playlistAgent.
+ mCallback.onRepeatModeChanged(mInstance, null, repeatMode);
+ });
+ }
+
+ void pushError(int errorCode, Bundle extras) {
+ mCallbackExecutor.execute(() -> {
+ if (!mInstance.isConnected()) {
+ return;
+ }
+ mCallback.onError(mInstance, errorCode, extras);
+ });
}
// Should be used without a lock to prevent potential deadlock.
void onConnectedNotLocked(IMediaSession2 sessionBinder,
- final CommandGroup allowedCommands, final PlaybackState2 state, final PlaybackInfo info,
- final PlaylistParams params, final List<MediaItem2> playlist,
+ final CommandGroup allowedCommands,
+ final int playerState,
+ final long positionEventTimeMs,
+ final long positionMs,
+ final float playbackSpeed,
+ final long bufferedPositionMs,
+ final PlaybackInfo info,
+ final int repeatMode,
+ final int shuffleMode,
+ final List<MediaItem2> playlist,
final PendingIntent sessionActivity) {
if (DEBUG) {
Log.d(TAG, "onConnectedNotLocked sessionBinder=" + sessionBinder
@@ -731,9 +950,14 @@
return;
}
mAllowedCommands = allowedCommands;
- mPlaybackState = state;
+ mPlayerState = playerState;
+ mPositionEventTimeMs = positionEventTimeMs;
+ mPositionMs = positionMs;
+ mPlaybackSpeed = playbackSpeed;
+ mBufferedPositionMs = bufferedPositionMs;
mPlaybackInfo = info;
- mPlaylistParams = params;
+ mRepeatMode = repeatMode;
+ mShuffleMode = shuffleMode;
mPlaylist = playlist;
mSessionActivity = sessionActivity;
mSessionBinder = sessionBinder;
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java b/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
similarity index 76%
rename from packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
rename to packages/MediaComponents/src/com/android/media/MediaController2Stub.java
index 451368f..721c963 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
@@ -20,11 +20,10 @@
import android.content.Context;
import android.media.MediaController2;
import android.media.MediaItem2;
+import android.media.MediaMetadata2;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
-import android.media.MediaSession2.PlaylistParams;
-import android.media.PlaybackState2;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.text.TextUtils;
@@ -37,13 +36,13 @@
import java.util.ArrayList;
import java.util.List;
-public class MediaSession2CallbackStub extends IMediaSession2Callback.Stub {
- private static final String TAG = "MS2CallbackStub";
+public class MediaController2Stub extends IMediaController2.Stub {
+ private static final String TAG = "MediaController2Stub";
private static final boolean DEBUG = true; // TODO(jaewan): Change
private final WeakReference<MediaController2Impl> mController;
- MediaSession2CallbackStub(MediaController2Impl controller) {
+ MediaController2Stub(MediaController2Impl controller) {
mController = new WeakReference<>(controller);
}
@@ -68,7 +67,7 @@
}
@Override
- public void onPlaybackStateChanged(Bundle state) throws RuntimeException {
+ public void onPlayerStateChanged(int state) {
final MediaController2Impl controller;
try {
controller = getController();
@@ -76,12 +75,59 @@
Log.w(TAG, "Don't fail silently here. Highly likely a bug");
return;
}
- controller.pushPlaybackStateChanges(
- PlaybackState2.fromBundle(controller.getContext(), state));
+ controller.pushPlayerStateChanges(state);
}
@Override
- public void onPlaylistChanged(List<Bundle> playlistBundle) throws RuntimeException {
+ public void onPositionChanged(long eventTimeMs, long positionMs) {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ if (eventTimeMs < 0) {
+ Log.w(TAG, "onPositionChanged(): Ignoring negative eventTimeMs");
+ return;
+ }
+ if (positionMs < 0) {
+ Log.w(TAG, "onPositionChanged(): Ignoring negative positionMs");
+ return;
+ }
+ controller.pushPositionChanges(eventTimeMs, positionMs);
+ }
+
+ @Override
+ public void onPlaybackSpeedChanged(float speed) {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ controller.pushPlaybackSpeedChanges(speed);
+ }
+
+ @Override
+ public void onBufferedPositionChanged(long bufferedPositionMs) {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ if (bufferedPositionMs < 0) {
+ Log.w(TAG, "onBufferedPositionChanged(): Ignoring negative bufferedPositionMs");
+ return;
+ }
+ controller.pushBufferedPositionChanges(bufferedPositionMs);
+ }
+
+ @Override
+ public void onPlaylistChanged(List<Bundle> playlistBundle, Bundle metadataBundle) {
final MediaController2Impl controller;
try {
controller = getController();
@@ -90,7 +136,7 @@
return;
}
if (playlistBundle == null) {
- Log.w(TAG, "onPlaylistChanged(): Ignoring null playlist");
+ Log.w(TAG, "onPlaylistChanged(): Ignoring null playlist from " + controller);
return;
}
List<MediaItem2> playlist = new ArrayList<>();
@@ -102,11 +148,13 @@
playlist.add(item);
}
}
- controller.pushPlaylistChanges(playlist);
+ MediaMetadata2 metadata =
+ MediaMetadata2.fromBundle(controller.getContext(), metadataBundle);
+ controller.pushPlaylistChanges(playlist, metadata);
}
@Override
- public void onPlaylistParamsChanged(Bundle paramsBundle) throws RuntimeException {
+ public void onPlaylistMetadataChanged(Bundle metadataBundle) throws RuntimeException {
final MediaController2Impl controller;
try {
controller = getController();
@@ -114,12 +162,21 @@
Log.w(TAG, "Don't fail silently here. Highly likely a bug");
return;
}
- PlaylistParams params = PlaylistParams.fromBundle(controller.getContext(), paramsBundle);
- if (params == null) {
- Log.w(TAG, "onPlaylistParamsChanged(): Ignoring null playlistParams");
+ MediaMetadata2 metadata =
+ MediaMetadata2.fromBundle(controller.getContext(), metadataBundle);
+ controller.pushPlaylistMetadataChanges(metadata);
+ }
+
+ @Override
+ public void onRepeatModeChanged(int repeatMode) {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
return;
}
- controller.pushPlaylistParamsChanges(params);
+ controller.pushRepeatModeChanges(repeatMode);
}
@Override
@@ -145,9 +202,34 @@
}
@Override
+ public void onShuffleModeChanged(int shuffleMode) {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ controller.pushShuffleModeChanges(shuffleMode);
+ }
+
+ @Override
+ public void onError(int errorCode, Bundle extras) {
+ final MediaController2Impl controller;
+ try {
+ controller = getController();
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+ return;
+ }
+ controller.pushError(errorCode, extras);
+ }
+
+ @Override
public void onConnected(IMediaSession2 sessionBinder, Bundle commandGroup,
- Bundle playbackState, Bundle playbackInfo, Bundle playlistParams, List<Bundle>
- itemBundleList, PendingIntent sessionActivity) {
+ int playerState, long positionEventTimeMs, long positionMs, float playbackSpeed,
+ long bufferedPositionMs, Bundle playbackInfo, int shuffleMode, int repeatMode,
+ List<Bundle> itemBundleList, PendingIntent sessionActivity) {
final MediaController2Impl controller = mController.get();
if (controller == null) {
if (DEBUG) {
@@ -168,9 +250,8 @@
}
controller.onConnectedNotLocked(sessionBinder,
CommandGroup.fromBundle(context, commandGroup),
- PlaybackState2.fromBundle(context, playbackState),
- PlaybackInfoImpl.fromBundle(context, playbackInfo),
- PlaylistParams.fromBundle(context, playlistParams),
+ playerState, positionEventTimeMs, positionMs, playbackSpeed, bufferedPositionMs,
+ PlaybackInfoImpl.fromBundle(context, playbackInfo), repeatMode, shuffleMode,
itemList, sessionActivity);
}
diff --git a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
index 039ff8f..c95b43f 100644
--- a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
@@ -31,21 +31,31 @@
import android.os.Bundle;
import android.text.TextUtils;
+import java.util.UUID;
+
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 static final String KEY_UUID = "android.media.mediaitem2.uuid";
private final Context mContext;
private final MediaItem2 mInstance;
private final String mId;
private final int mFlags;
+ private final UUID mUUID;
private MediaMetadata2 mMetadata;
private DataSourceDesc mDataSourceDesc;
// From the public API
public MediaItem2Impl(@NonNull Context context, @NonNull String mediaId,
@Nullable DataSourceDesc dsd, @Nullable MediaMetadata2 metadata, @Flags int flags) {
+ this(context, mediaId, dsd, metadata, flags, null);
+ }
+
+ private MediaItem2Impl(@NonNull Context context, @NonNull String mediaId,
+ @Nullable DataSourceDesc dsd, @Nullable MediaMetadata2 metadata, @Flags int flags,
+ @Nullable UUID uuid) {
if (mediaId == null) {
throw new IllegalArgumentException("mediaId shouldn't be null");
}
@@ -58,24 +68,18 @@
mDataSourceDesc = dsd;
mMetadata = metadata;
mFlags = flags;
+ mUUID = (uuid == null) ? UUID.randomUUID() : uuid;
mInstance = new MediaItem2(this);
}
- // 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");
+ @Override
+ public boolean equals_impl(Object obj) {
+ if (!(obj instanceof MediaItem2)) {
+ return false;
}
- if (metadata != null && !TextUtils.equals(mediaId, metadata.getMediaId())) {
- throw new IllegalArgumentException("metadata's id should be matched with the mediaid");
- }
- mContext = context;
- mId = mediaId;
- mMetadata = metadata;
- mFlags = flags;
- mInstance = new MediaItem2(this);
+ MediaItem2 other = (MediaItem2) obj;
+ return mUUID.equals(((MediaItem2Impl) other.getProvider()).mUUID);
}
/**
@@ -90,10 +94,37 @@
if (mMetadata != null) {
bundle.putBundle(KEY_METADATA, mMetadata.toBundle());
}
+ bundle.putString(KEY_UUID, mUUID.toString());
return bundle;
}
- public static MediaItem2 fromBundle(Context context, Bundle bundle) {
+ /**
+ * Create a MediaItem2 from the {@link Bundle}.
+ *
+ * @param context A context.
+ * @param bundle The bundle which was published by {@link MediaItem2#toBundle()}.
+ * @return The newly created MediaItem2
+ */
+ public static MediaItem2 fromBundle(@NonNull Context context, @NonNull Bundle bundle) {
+ if (bundle == null) {
+ return null;
+ }
+ final String uuidString = bundle.getString(KEY_UUID);
+ return fromBundle(context, bundle, UUID.fromString(uuidString));
+ }
+
+ /**
+ * Create a MediaItem2 from the {@link Bundle} with the specified {@link UUID}.
+ * If {@link UUID}
+ * can be null for creating new.
+ *
+ * @param context A context.
+ * @param bundle The bundle which was published by {@link MediaItem2#toBundle()}.
+ * @param uuid A {@link UUID} to override. Can be {@link null} for override.
+ * @return The newly created MediaItem2
+ */
+ static MediaItem2 fromBundle(@NonNull Context context, @NonNull Bundle bundle,
+ @Nullable UUID uuid) {
if (bundle == null) {
return null;
}
@@ -102,7 +133,7 @@
final MediaMetadata2 metadata = metadataBundle != null
? MediaMetadata2.fromBundle(context, metadataBundle) : null;
final int flags = bundle.getInt(KEY_FLAGS);
- return new MediaItem2Impl(context, id, metadata, flags).getInstance();
+ return new MediaItem2Impl(context, id, null, metadata, flags, uuid).getInstance();
}
private MediaItem2 getInstance() {
diff --git a/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java b/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
index 333455d..286f5ee 100644
--- a/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaMetadata2Impl.java
@@ -228,7 +228,7 @@
}
public static MediaMetadata2 fromBundle(Context context, Bundle bundle) {
- return new MediaMetadata2Impl(context, bundle).getInstance();
+ return (bundle == null) ? null : new MediaMetadata2Impl(context, bundle).getInstance();
}
public static final class BuilderImpl implements MediaMetadata2Provider.BuilderProvider {
diff --git a/packages/MediaComponents/src/com/android/media/MediaPlaylistAgentImpl.java b/packages/MediaComponents/src/com/android/media/MediaPlaylistAgentImpl.java
index bab29b7..ab6b167 100644
--- a/packages/MediaComponents/src/com/android/media/MediaPlaylistAgentImpl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaPlaylistAgentImpl.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.media.DataSourceDesc;
import android.media.MediaItem2;
import android.media.MediaMetadata2;
import android.media.MediaPlaylistAgent;
@@ -48,6 +49,7 @@
mInstance = instance;
}
+ @Override
final public void registerPlaylistEventCallback_impl(
@NonNull @CallbackExecutor Executor executor, @NonNull PlaylistEventCallback callback) {
if (executor == null) {
@@ -66,6 +68,7 @@
}
}
+ @Override
final public void unregisterPlaylistEventCallback_impl(
@NonNull PlaylistEventCallback callback) {
if (callback == null) {
@@ -76,6 +79,7 @@
}
}
+ @Override
final public void notifyPlaylistChanged_impl() {
ArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
List<MediaItem2> playlist= mInstance.getPlaylist();
@@ -88,6 +92,7 @@
}
}
+ @Override
final public void notifyPlaylistMetadataChanged_impl() {
ArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
for (int i = 0; i < callbacks.size(); i++) {
@@ -98,6 +103,7 @@
}
}
+ @Override
final public void notifyShuffleModeChanged_impl() {
ArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
for (int i = 0; i < callbacks.size(); i++) {
@@ -108,6 +114,7 @@
}
}
+ @Override
final public void notifyRepeatModeChanged_impl() {
ArrayMap<PlaylistEventCallback, Executor> callbacks = getCallbacks();
for (int i = 0; i < callbacks.size(); i++) {
@@ -118,66 +125,98 @@
}
}
+ @Override
public @Nullable List<MediaItem2> getPlaylist_impl() {
// empty implementation
return null;
}
+ @Override
public void setPlaylist_impl(@NonNull List<MediaItem2> list,
@Nullable MediaMetadata2 metadata) {
// empty implementation
}
+ @Override
public @Nullable MediaMetadata2 getPlaylistMetadata_impl() {
// empty implementation
return null;
}
+ @Override
public void updatePlaylistMetadata_impl(@Nullable MediaMetadata2 metadata) {
// empty implementation
}
+ @Override
public void addPlaylistItem_impl(int index, @NonNull MediaItem2 item) {
// empty implementation
}
+ @Override
public void removePlaylistItem_impl(@NonNull MediaItem2 item) {
// empty implementation
}
+ @Override
public void replacePlaylistItem_impl(int index, @NonNull MediaItem2 item) {
// empty implementation
}
+ @Override
public void skipToPlaylistItem_impl(@NonNull MediaItem2 item) {
// empty implementation
}
+ @Override
public void skipToPreviousItem_impl() {
// empty implementation
}
+ @Override
public void skipToNextItem_impl() {
// empty implementation
}
+ @Override
public int getRepeatMode_impl() {
return MediaPlaylistAgent.REPEAT_MODE_NONE;
}
+ @Override
public void setRepeatMode_impl(int repeatMode) {
// empty implementation
}
+ @Override
public int getShuffleMode_impl() {
// empty implementation
return MediaPlaylistAgent.SHUFFLE_MODE_NONE;
}
+ @Override
public void setShuffleMode_impl(int shuffleMode) {
// empty implementation
}
+ @Override
+ public @Nullable MediaItem2 getMediaItem_impl(@NonNull DataSourceDesc dsd) {
+ if (dsd == null) {
+ throw new IllegalArgumentException("dsd shouldn't be null");
+ }
+ List<MediaItem2> itemList = mInstance.getPlaylist();
+ if (itemList == null) {
+ return null;
+ }
+ for (int i = 0; i < itemList.size(); i++) {
+ MediaItem2 item = itemList.get(i);
+ if (item != null && item.getDataSourceDesc() == dsd) {
+ return item;
+ }
+ }
+ return null;
+ }
+
private ArrayMap<PlaylistEventCallback, Executor> getCallbacks() {
ArrayMap<PlaylistEventCallback, Executor> callbacks = new ArrayMap<>();
synchronized (mLock) {
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
index 5c18515..2b7c72a 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -39,6 +39,7 @@
import android.media.MediaMetadata2;
import android.media.MediaPlayerBase;
import android.media.MediaPlayerBase.PlayerEventCallback;
+import android.media.MediaPlayerBase.PlayerState;
import android.media.MediaPlaylistAgent;
import android.media.MediaPlaylistAgent.PlaylistEventCallback;
import android.media.MediaSession2;
@@ -47,12 +48,8 @@
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2.PlaylistParams;
-import android.media.MediaSession2.PlaylistParams.RepeatMode;
-import android.media.MediaSession2.PlaylistParams.ShuffleMode;
import android.media.MediaSession2.SessionCallback;
import android.media.MediaSessionService2;
-import android.media.PlaybackState2;
import android.media.SessionToken2;
import android.media.VolumeProvider2;
import android.media.session.MediaSessionManager;
@@ -68,8 +65,11 @@
import android.util.Log;
import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.NoSuchElementException;
import java.util.concurrent.Executor;
public class MediaSession2Impl implements MediaSession2Provider {
@@ -86,7 +86,6 @@
private final MediaSession2Stub mSessionStub;
private final SessionToken2 mSessionToken;
private final AudioManager mAudioManager;
- private final ArrayMap<PlayerEventCallback, Executor> mCallbacks = new ArrayMap<>();
private final PendingIntent mSessionActivity;
private final PlayerEventCallback mPlayerEventCallback;
private final PlaylistEventCallback mPlaylistEventCallback;
@@ -248,11 +247,12 @@
oldAgent.unregisterPlaylistEventCallback(mPlaylistEventCallback);
}
}
- // TODO(jaewan): Notify controllers about the change in the media player base (b/74370608)
- // Note that notification will be done indirectly by telling player state,
- // position, buffered position, etc.
- mSessionStub.notifyPlaybackInfoChanged(info);
- notifyPlaybackStateChangedNotLocked(mInstance.getPlaybackState());
+
+ if (oldPlayer != null) {
+ mSessionStub.notifyPlaybackInfoChanged(info);
+ notifyPlayerUpdatedNotLocked(oldPlayer);
+ }
+ // TODO(jaewan): Repeat the same thing for the playlist agent.
}
private PlaybackInfo createPlaybackInfo(VolumeProvider2 volumeProvider, AudioAttributes attrs) {
@@ -385,32 +385,36 @@
}
@Override
- public void skipToPreviousItem_impl() {
- ensureCallingThread();
- // TODO(jaewan): Implement this (b/74175632)
- /*
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- // TODO implement
- //player.skipToPrevious();
+ public void skipToPlaylistItem_impl(MediaItem2 item) {
+ if (item == null) {
+ throw new IllegalArgumentException("item shouldn't be null");
+ }
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.skipToPlaylistItem(item);
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
- */
+ }
+
+ @Override
+ public void skipToPreviousItem_impl() {
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.skipToPreviousItem();
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
+ }
}
@Override
public void skipToNextItem_impl() {
- ensureCallingThread();
- // TODO(jaewan): Implement this (b/74175632)
- /*
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- player.skipToNext();
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.skipToNextItem();
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
- */
}
@Override
@@ -425,41 +429,6 @@
mSessionStub.notifyCustomLayoutNotLocked(controller, layout);
}
- @Override
- public void setPlaylistParams_impl(PlaylistParams params) {
- if (params == null) {
- throw new IllegalArgumentException("params shouldn't be null");
- }
- ensureCallingThread();
- // TODO: Uncomment or remove
- /*
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- // TODO implement
- //player.setPlaylistParams(params);
- mSessionStub.notifyPlaylistParamsChanged(params);
- }
- */
- }
-
- @Override
- public PlaylistParams getPlaylistParams_impl() {
- // TODO: Uncomment or remove
- /*
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- // TODO(jaewan): Is it safe to be called on any thread?
- // Otherwise MediaSession2 should cache parameter of setPlaylistParams.
- // TODO implement
- //return player.getPlaylistParams();
- return null;
- } else if (DEBUG) {
- Log.d(TAG, "API calls after the close()", new IllegalStateException());
- }
- */
- return null;
- }
-
//////////////////////////////////////////////////////////////////////////////////////
// TODO(jaewan): Implement follows
//////////////////////////////////////////////////////////////////////////////////////
@@ -496,22 +465,27 @@
}
@Override
- public void setPlaylist_impl(List<MediaItem2> playlist) {
- if (playlist == null) {
- throw new IllegalArgumentException("playlist shouldn't be null");
+ public void setPlaylist_impl(List<MediaItem2> list, MediaMetadata2 metadata) {
+ if (list == null) {
+ throw new IllegalArgumentException("list shouldn't be null");
}
ensureCallingThread();
- // TODO: Uncomment or remove
- /*
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- // TODO implement and use SessionPlaylistAgent
- //player.setPlaylist(playlist);
- mSessionStub.notifyPlaylistChanged(playlist);
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.setPlaylist(list, metadata);
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
- */
+ }
+
+ @Override
+ public void updatePlaylistMetadata_impl(MediaMetadata2 metadata) {
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.updatePlaylistMetadata(metadata);
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
+ }
}
@Override
@@ -522,7 +496,12 @@
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
- // TODO(jaewan): Implement
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.addPlaylistItem(index, item);
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
+ }
}
@Override
@@ -530,15 +509,12 @@
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
- // TODO(jaewan): Implement
- }
-
- @Override
- public void editPlaylistItem_impl(MediaItem2 item) {
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.removePlaylistItem(item);
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
- // TODO(jaewan): Implement
}
@Override
@@ -549,24 +525,33 @@
if (item == null) {
throw new IllegalArgumentException("item shouldn't be null");
}
- // TODO(jaewan): Implement
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.replacePlaylistItem(index, item);
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
+ }
}
@Override
public List<MediaItem2> getPlaylist_impl() {
- // TODO: Uncomment or remove
- /*
- final MediaPlayerBase player = mPlayer;
- if (player != null) {
- // TODO(jaewan): Is it safe to be called on any thread?
- // Otherwise MediaSession2 should cache parameter of setPlaylist.
- // TODO implement
- //return player.getPlaylist();
- return null;
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ return agent.getPlaylist();
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
- */
+ return null;
+ }
+
+ @Override
+ public MediaMetadata2 getPlaylistMetadata_impl() {
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ return agent.getPlaylistMetadata();
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
+ }
return null;
}
@@ -577,6 +562,48 @@
}
@Override
+ public int getRepeatMode_impl() {
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ return agent.getRepeatMode();
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
+ }
+ return MediaPlaylistAgent.REPEAT_MODE_NONE;
+ }
+
+ @Override
+ public void setRepeatMode_impl(int repeatMode) {
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.setRepeatMode(repeatMode);
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
+ }
+ }
+
+ @Override
+ public int getShuffleMode_impl() {
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ return agent.getShuffleMode();
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
+ }
+ return MediaPlaylistAgent.SHUFFLE_MODE_NONE;
+ }
+
+ @Override
+ public void setShuffleMode_impl(int shuffleMode) {
+ final MediaPlaylistAgent agent = mPlaylistAgent;
+ if (agent != null) {
+ agent.setShuffleMode(shuffleMode);
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
+ }
+ }
+
+ @Override
public void prepare_impl() {
ensureCallingThread();
final MediaPlayerBase player = mPlayer;
@@ -590,31 +617,23 @@
@Override
public void fastForward_impl() {
ensureCallingThread();
- // TODO: Uncomment or remove
- /*
final MediaPlayerBase player = mPlayer;
if (player != null) {
- // TODO implement
- //player.fastForward();
+ player.fastForward();
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
- */
}
@Override
public void rewind_impl() {
ensureCallingThread();
- // TODO: Uncomment or remove
- /*
final MediaPlayerBase player = mPlayer;
if (player != null) {
- // TODO implement
- //player.rewind();
+ player.rewind();
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
- */
}
@Override
@@ -629,76 +648,41 @@
}
@Override
- public void skipToPlaylistItem_impl(MediaItem2 item) {
- ensureCallingThread();
- if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
- }
- // TODO: Uncomment or remove
- /*
+ public @PlayerState int getPlayerState_impl() {
final MediaPlayerBase player = mPlayer;
if (player != null) {
- // TODO implement
- //player.setCurrentPlaylistItem(item);
+ return mPlayer.getPlayerState();
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
- */
+ return MediaPlayerBase.PLAYER_STATE_ERROR;
}
@Override
- public void registerPlayerEventCallback_impl(Executor executor, PlayerEventCallback callback) {
- if (executor == null) {
- throw new IllegalArgumentException("executor shouldn't be null");
- }
- if (callback == null) {
- throw new IllegalArgumentException("callback shouldn't be null");
- }
- ensureCallingThread();
- if (mCallbacks.get(callback) != null) {
- Log.w(TAG, "callback is already added. Ignoring.");
- return;
- }
- mCallbacks.put(callback, executor);
- // TODO: Uncomment or remove
- /*
- // TODO(jaewan): Double check if we need this.
- final PlaybackState2 state = getInstance().getPlaybackState();
- executor.execute(() -> callback.onPlaybackStateChanged(state));
- */
- }
-
- @Override
- public void unregisterPlayerEventCallback_impl(PlayerEventCallback callback) {
- if (callback == null) {
- throw new IllegalArgumentException("callback shouldn't be null");
- }
- ensureCallingThread();
- mCallbacks.remove(callback);
- }
-
- @Override
- public PlaybackState2 getPlaybackState_impl() {
- ensureCallingThread();
- // TODO: Uncomment or remove
- /*
+ public long getPosition_impl() {
final MediaPlayerBase player = mPlayer;
if (player != null) {
- // TODO(jaewan): Is it safe to be called on any thread?
- // Otherwise MediaSession2 should cache the result from listener.
- // TODO implement
- //return player.getPlaybackState();
- return null;
+ return mPlayer.getCurrentPosition();
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
- */
- return null;
+ return MediaPlayerBase.UNKNOWN_TIME;
+ }
+
+ @Override
+ public long getBufferedPosition_impl() {
+ final MediaPlayerBase player = mPlayer;
+ if (player != null) {
+ return mPlayer.getBufferedPosition();
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
+ }
+ return MediaPlayerBase.UNKNOWN_TIME;
}
@Override
public void notifyError_impl(int errorCode, Bundle extras) {
- // TODO(jaewan): Implement
+ mSessionStub.notifyError(errorCode, extras);
}
///////////////////////////////////////////////////
@@ -725,35 +709,73 @@
}*/
}
- private void notifyPlaybackStateChangedNotLocked(final PlaybackState2 state) {
- ArrayMap<PlayerEventCallback, Executor> callbacks = new ArrayMap<>();
- synchronized (mLock) {
- callbacks.putAll(mCallbacks);
+ private void notifyPlaylistChangedOnExecutor(MediaPlaylistAgent playlistAgent,
+ List<MediaItem2> list, MediaMetadata2 metadata) {
+ if (playlistAgent != mPlaylistAgent) {
+ // Ignore calls from the old agent.
+ return;
}
- // Notify to callbacks added directly to this session
- for (int i = 0; i < callbacks.size(); i++) {
- final PlayerEventCallback callback = callbacks.keyAt(i);
- final Executor executor = callbacks.valueAt(i);
- // TODO: Uncomment or remove
- //executor.execute(() -> callback.onPlaybackStateChanged(state));
- }
- // Notify to controllers as well.
- mSessionStub.notifyPlaybackStateChangedNotLocked(state);
+ mCallback.onPlaylistChanged(mInstance, playlistAgent, list, metadata);
+ mSessionStub.notifyPlaylistChangedNotLocked(list, metadata);
}
- private void notifyErrorNotLocked(String mediaId, int what, int extra) {
- ArrayMap<PlayerEventCallback, Executor> callbacks = new ArrayMap<>();
- synchronized (mLock) {
- callbacks.putAll(mCallbacks);
+ private void notifyPlaylistMetadataChangedOnExecutor(MediaPlaylistAgent playlistAgent,
+ MediaMetadata2 metadata) {
+ if (playlistAgent != mPlaylistAgent) {
+ // Ignore calls from the old agent.
+ return;
}
- // Notify to callbacks added directly to this session
- for (int i = 0; i < callbacks.size(); i++) {
- final PlayerEventCallback callback = callbacks.keyAt(i);
- final Executor executor = callbacks.valueAt(i);
- // TODO: Uncomment or remove
- //executor.execute(() -> callback.onError(mediaId, what, extra));
+ mCallback.onPlaylistMetadataChanged(mInstance, playlistAgent, metadata);
+ mSessionStub.notifyPlaylistMetadataChangedNotLocked(metadata);
+ }
+
+ private void notifyRepeatModeChangedOnExecutor(MediaPlaylistAgent playlistAgent,
+ int repeatMode) {
+ if (playlistAgent != mPlaylistAgent) {
+ // Ignore calls from the old agent.
+ return;
}
- // TODO(jaewan): Notify to controllers as well.
+ mCallback.onRepeatModeChanged(mInstance, playlistAgent, repeatMode);
+ mSessionStub.notifyRepeatModeChangedNotLocked(repeatMode);
+ }
+
+ private void notifyShuffleModeChangedOnExecutor(MediaPlaylistAgent playlistAgent,
+ int shuffleMode) {
+ if (playlistAgent != mPlaylistAgent) {
+ // Ignore calls from the old agent.
+ return;
+ }
+ mCallback.onShuffleModeChanged(mInstance, playlistAgent, shuffleMode);
+ mSessionStub.notifyShuffleModeChangedNotLocked(shuffleMode);
+ }
+
+ private void notifyPlayerUpdatedNotLocked(MediaPlayerBase oldPlayer) {
+ final MediaPlayerBase player = mPlayer;
+ // TODO(jaewan): (Can be post-P) Find better way for player.getPlayerState() //
+ // In theory, Session.getXXX() may not be the same as Player.getXXX()
+ // and we should notify information of the session.getXXX() instead of
+ // player.getXXX()
+ // Notify to controllers as well.
+ final int state = player.getPlayerState();
+ if (state != oldPlayer.getPlayerState()) {
+ mSessionStub.notifyPlayerStateChangedNotLocked(state);
+ }
+
+ final long currentTimeMs = System.currentTimeMillis();
+ final long position = player.getCurrentPosition();
+ if (position != oldPlayer.getCurrentPosition()) {
+ mSessionStub.notifyPositionChangedNotLocked(currentTimeMs, position);
+ }
+
+ final float speed = player.getPlaybackSpeed();
+ if (speed != oldPlayer.getPlaybackSpeed()) {
+ mSessionStub.notifyPlaybackSpeedChangedNotLocked(speed);
+ }
+
+ final long bufferedPosition = player.getBufferedPosition();
+ if (bufferedPosition != oldPlayer.getBufferedPosition()) {
+ mSessionStub.notifyBufferedPositionChangedNotLocked(bufferedPosition);
+ }
}
Context getContext() {
@@ -768,6 +790,10 @@
return mPlayer;
}
+ MediaPlaylistAgent getPlaylistAgent() {
+ return mPlaylistAgent;
+ }
+
Executor getCallbackExecutor() {
return mCallbackExecutor;
}
@@ -803,26 +829,89 @@
@Override
public void onCurrentDataSourceChanged(MediaPlayerBase mpb, DataSourceDesc dsd) {
- super.onCurrentDataSourceChanged(mpb, dsd);
- // TODO(jaewan): Handle this b/74370608
+ MediaSession2Impl session = getSession();
+ if (session == null || dsd == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ MediaItem2 item = getMediaItem(session, dsd);
+ if (item == null) {
+ return;
+ }
+ session.getCallback().onCurrentMediaItemChanged(session.getInstance(), mpb, item);
+ // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
+ });
}
@Override
public void onMediaPrepared(MediaPlayerBase mpb, DataSourceDesc dsd) {
- super.onMediaPrepared(mpb, dsd);
- // TODO(jaewan): Handle this b/74370608
+ MediaSession2Impl session = getSession();
+ if (session == null || dsd == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ MediaItem2 item = getMediaItem(session, dsd);
+ if (item == null) {
+ return;
+ }
+ session.getCallback().onMediaPrepared(session.getInstance(), mpb, item);
+ // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
+ });
}
@Override
public void onPlayerStateChanged(MediaPlayerBase mpb, int state) {
- super.onPlayerStateChanged(mpb, state);
- // TODO(jaewan): Handle this b/74370608
+ MediaSession2Impl session = getSession();
+ if (session == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ session.getCallback().onPlayerStateChanged(session.getInstance(), mpb, state);
+ session.getSessionStub().notifyPlayerStateChangedNotLocked(state);
+ });
}
@Override
public void onBufferingStateChanged(MediaPlayerBase mpb, DataSourceDesc dsd, int state) {
- super.onBufferingStateChanged(mpb, dsd, state);
- // TODO(jaewan): Handle this b/74370608
+ MediaSession2Impl session = getSession();
+ if (session == null || dsd == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ MediaItem2 item = getMediaItem(session, dsd);
+ if (item == null) {
+ return;
+ }
+ session.getCallback().onBufferingStateChanged(
+ session.getInstance(), mpb, item, state);
+ // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
+ });
+ }
+
+ private MediaSession2Impl getSession() {
+ final MediaSession2Impl session = mSession.get();
+ if (session == null && DEBUG) {
+ Log.d(TAG, "Session is closed", new IllegalStateException());
+ }
+ return session;
+ }
+
+ private MediaItem2 getMediaItem(MediaSession2Impl session, DataSourceDesc dsd) {
+ MediaPlaylistAgent agent = session.getPlaylistAgent();
+ if (agent == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Session is closed", new IllegalStateException());
+ }
+ return null;
+ }
+ MediaItem2 item = agent.getMediaItem(dsd);
+ if (item == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Could not find matching item for dsd=" + dsd,
+ new NoSuchElementException());
+ }
+ }
+ return item;
}
}
@@ -836,27 +925,39 @@
@Override
public void onPlaylistChanged(MediaPlaylistAgent playlistAgent, List<MediaItem2> list,
MediaMetadata2 metadata) {
- super.onPlaylistChanged(playlistAgent, list, metadata);
- // TODO(jaewan): Handle this (b/74326040)
+ final MediaSession2Impl session = mSession.get();
+ if (session == null) {
+ return;
+ }
+ session.notifyPlaylistChangedOnExecutor(playlistAgent, list, metadata);
}
@Override
public void onPlaylistMetadataChanged(MediaPlaylistAgent playlistAgent,
MediaMetadata2 metadata) {
- super.onPlaylistMetadataChanged(playlistAgent, metadata);
- // TODO(jaewan): Handle this (b/74174649)
- }
-
- @Override
- public void onShuffleModeChanged(MediaPlaylistAgent playlistAgent, int shuffleMode) {
- super.onShuffleModeChanged(playlistAgent, shuffleMode);
- // TODO(jaewan): Handle this (b/74118768)
+ final MediaSession2Impl session = mSession.get();
+ if (session == null) {
+ return;
+ }
+ session.notifyPlaylistMetadataChangedOnExecutor(playlistAgent, metadata);
}
@Override
public void onRepeatModeChanged(MediaPlaylistAgent playlistAgent, int repeatMode) {
- super.onRepeatModeChanged(playlistAgent, repeatMode);
- // TODO(jaewan): Handle this (b/74118768)
+ final MediaSession2Impl session = mSession.get();
+ if (session == null) {
+ return;
+ }
+ session.notifyRepeatModeChangedOnExecutor(playlistAgent, repeatMode);
+ }
+
+ @Override
+ public void onShuffleModeChanged(MediaPlaylistAgent playlistAgent, int shuffleMode) {
+ final MediaSession2Impl session = mSession.get();
+ if (session == null) {
+ return;
+ }
+ session.notifyShuffleModeChangedOnExecutor(playlistAgent, shuffleMode);
}
}
@@ -891,14 +992,17 @@
mExtras = extras;
}
+ @Override
public int getCommandCode_impl() {
return mCommandCode;
}
+ @Override
public @Nullable String getCustomCommand_impl() {
return mCustomCommand;
}
+ @Override
public @Nullable Bundle getExtras_impl() {
return mExtras;
}
@@ -906,6 +1010,7 @@
/**
* @return a new Bundle instance from the Command
*/
+ @Override
public Bundle toBundle_impl() {
Bundle bundle = new Bundle();
bundle.putInt(KEY_COMMAND_CODE, mCommandCode);
@@ -959,6 +1064,16 @@
public static class CommandGroupImpl implements CommandGroupProvider {
private static final String KEY_COMMANDS =
"android.media.mediasession2.commandgroup.commands";
+
+ // Prefix for all command codes
+ private static final String PREFIX_COMMAND_CODE = "COMMAND_CODE_";
+
+ // Prefix for command codes that will be sent directly to the MediaPlayerBase
+ private static final String PREFIX_COMMAND_CODE_PLAYBACK = "COMMAND_CODE_PLAYBACK_";
+
+ // Prefix for command codes that will be sent directly to the MediaPlaylistAgent
+ private static final String PREFIX_COMMAND_CODE_PLAYLIST = "COMMAND_CODE_PLAYLIST_";
+
private List<Command> mCommands = new ArrayList<>();
private final Context mContext;
private final CommandGroup mInstance;
@@ -971,6 +1086,11 @@
}
}
+ public CommandGroupImpl(Context context) {
+ mContext = context;
+ mInstance = new CommandGroup(this);
+ }
+
@Override
public void addCommand_impl(Command command) {
if (command == null) {
@@ -981,8 +1101,30 @@
@Override
public void addAllPredefinedCommands_impl() {
- for (int i = 1; i <= MediaSession2.COMMAND_CODE_MAX; i++) {
- mCommands.add(new Command(mContext, i));
+ addCommandsWithPrefix(PREFIX_COMMAND_CODE);
+ }
+
+ public void addAllPlaybackCommands() {
+ addCommandsWithPrefix(PREFIX_COMMAND_CODE_PLAYBACK);
+ }
+
+ public void addAllPlaylistCommands() {
+ addCommandsWithPrefix(PREFIX_COMMAND_CODE_PLAYLIST);
+ }
+
+ private void addCommandsWithPrefix(String prefix) {
+ // TODO(jaewan): (Can be post-P): Don't use reflection for this purpose.
+ final Field[] fields = MediaSession2.class.getFields();
+ if (fields != null) {
+ for (int i = 0; i < fields.length; i++) {
+ if (fields[i].getName().startsWith(prefix)) {
+ try {
+ mCommands.add(new Command(mContext, fields[i].getInt(null)));
+ } catch (IllegalAccessException e) {
+ Log.w(TAG, "Unexpected " + fields[i] + " in MediaSession2");
+ }
+ }
+ }
}
}
@@ -1017,7 +1159,11 @@
@Override
public List<Command> getCommands_impl() {
- return mCommands;
+ return getCommands();
+ }
+
+ public List<Command> getCommands() {
+ return Collections.unmodifiableList(mCommands);
}
/**
@@ -1068,10 +1214,10 @@
private final int mUid;
private final String mPackageName;
private final boolean mIsTrusted;
- private final IMediaSession2Callback mControllerBinder;
+ private final IMediaController2 mControllerBinder;
public ControllerInfoImpl(Context context, ControllerInfo instance, int uid,
- int pid, String packageName, IMediaSession2Callback callback) {
+ int pid, String packageName, IMediaController2 callback) {
if (TextUtils.isEmpty(packageName)) {
throw new IllegalArgumentException("packageName shouldn't be empty");
}
@@ -1147,7 +1293,7 @@
return mControllerBinder.asBinder();
}
- public IMediaSession2Callback getControllerBinder() {
+ public IMediaController2 getControllerBinder() {
return mControllerBinder;
}
@@ -1156,76 +1302,6 @@
}
}
- public static class PlaylistParamsImpl implements PlaylistParamsProvider {
- /**
- * Keys used for converting a PlaylistParams object to a bundle object and vice versa.
- */
- private static final String KEY_REPEAT_MODE =
- "android.media.session2.playlistparams2.repeat_mode";
- private static final String KEY_SHUFFLE_MODE =
- "android.media.session2.playlistparams2.shuffle_mode";
- private static final String KEY_MEDIA_METADATA2_BUNDLE =
- "android.media.session2.playlistparams2.metadata2_bundle";
-
- private Context mContext;
- private PlaylistParams mInstance;
- private @RepeatMode int mRepeatMode;
- private @ShuffleMode int mShuffleMode;
- private MediaMetadata2 mPlaylistMetadata;
-
- public PlaylistParamsImpl(Context context, PlaylistParams instance,
- @RepeatMode int repeatMode, @ShuffleMode int shuffleMode,
- MediaMetadata2 playlistMetadata) {
- // TODO(jaewan): Sanity check
- mContext = context;
- mInstance = instance;
- mRepeatMode = repeatMode;
- mShuffleMode = shuffleMode;
- mPlaylistMetadata = playlistMetadata;
- }
-
- public @RepeatMode int getRepeatMode_impl() {
- return mRepeatMode;
- }
-
- public @ShuffleMode int getShuffleMode_impl() {
- return mShuffleMode;
- }
-
- public MediaMetadata2 getPlaylistMetadata_impl() {
- return mPlaylistMetadata;
- }
-
- @Override
- public Bundle toBundle_impl() {
- Bundle bundle = new Bundle();
- bundle.putInt(KEY_REPEAT_MODE, mRepeatMode);
- bundle.putInt(KEY_SHUFFLE_MODE, mShuffleMode);
- if (mPlaylistMetadata != null) {
- bundle.putBundle(KEY_MEDIA_METADATA2_BUNDLE, mPlaylistMetadata.toBundle());
- }
- return bundle;
- }
-
- public static PlaylistParams fromBundle(Context context, Bundle bundle) {
- if (bundle == null) {
- return null;
- }
- if (!bundle.containsKey(KEY_REPEAT_MODE) || !bundle.containsKey(KEY_SHUFFLE_MODE)) {
- return null;
- }
-
- Bundle metadataBundle = bundle.getBundle(KEY_MEDIA_METADATA2_BUNDLE);
- MediaMetadata2 metadata = metadataBundle == null
- ? null : MediaMetadata2.fromBundle(context, metadataBundle);
-
- return new PlaylistParams(context,
- bundle.getInt(KEY_REPEAT_MODE),
- bundle.getInt(KEY_SHUFFLE_MODE),
- metadata);
- }
- }
-
public static class CommandButtonImpl implements CommandButtonProvider {
private static final String KEY_COMMAND
= "android.media.media_session2.command_button.command";
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index a9c5224..caf834a 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -21,28 +21,31 @@
import android.media.MediaController2;
import android.media.MediaItem2;
import android.media.MediaLibraryService2.LibraryRoot;
+import android.media.MediaMetadata2;
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.MediaSession2.PlaylistParams;
-import android.media.PlaybackState2;
import android.media.Rating2;
import android.media.VolumeProvider2;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
+import android.os.DeadObjectException;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.support.annotation.GuardedBy;
+import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.SparseArray;
import com.android.media.MediaLibraryService2Impl.MediaLibrarySessionImpl;
import com.android.media.MediaSession2Impl.CommandButtonImpl;
+import com.android.media.MediaSession2Impl.CommandGroupImpl;
import com.android.media.MediaSession2Impl.ControllerInfoImpl;
import java.lang.ref.WeakReference;
@@ -61,6 +64,8 @@
private static final String TAG = "MediaSession2Stub";
private static final boolean DEBUG = true; // TODO(jaewan): Rename.
+ private static final SparseArray<Command> sCommandsForOnCommandRequest = new SparseArray<>();
+
private final Object mLock = new Object();
private final WeakReference<MediaSession2Impl> mSession;
@@ -75,6 +80,19 @@
public MediaSession2Stub(MediaSession2Impl session) {
mSession = new WeakReference<>(session);
+
+ synchronized (sCommandsForOnCommandRequest) {
+ if (sCommandsForOnCommandRequest.size() == 0) {
+ CommandGroupImpl group = new CommandGroupImpl(session.getContext());
+ group.addAllPlaybackCommands();
+ group.addAllPlaylistCommands();
+ List<Command> commands = group.getCommands();
+ for (int i = 0; i < commands.size(); i++) {
+ Command command = commands.get(i);
+ sCommandsForOnCommandRequest.append(command.getCommandCode(), command);
+ }
+ }
+ }
}
public void destroyNotLocked() {
@@ -85,7 +103,7 @@
mControllers.clear();
}
for (int i = 0; i < list.size(); i++) {
- IMediaSession2Callback controllerBinder =
+ IMediaController2 controllerBinder =
((ControllerInfoImpl) list.get(i).getProvider()).getControllerBinder();
try {
// Should be used without a lock hold to prevent potential deadlock.
@@ -113,7 +131,7 @@
}
// Get controller if the command from caller to session is able to be handled.
- private ControllerInfo getControllerIfAble(IMediaSession2Callback caller) {
+ private ControllerInfo getControllerIfAble(IMediaController2 caller) {
synchronized (mLock) {
final ControllerInfo controllerInfo = mControllers.get(caller.asBinder());
if (controllerInfo == null && DEBUG) {
@@ -124,7 +142,7 @@
}
// Get controller if the command from caller to session is able to be handled.
- private ControllerInfo getControllerIfAble(IMediaSession2Callback caller, int commandCode) {
+ private ControllerInfo getControllerIfAble(IMediaController2 caller, int commandCode) {
synchronized (mLock) {
final ControllerInfo controllerInfo = getControllerIfAble(caller);
if (controllerInfo == null) {
@@ -147,7 +165,7 @@
}
// Get controller if the command from caller to session is able to be handled.
- private ControllerInfo getControllerIfAble(IMediaSession2Callback caller, Command command) {
+ private ControllerInfo getControllerIfAble(IMediaController2 caller, Command command) {
synchronized (mLock) {
final ControllerInfo controllerInfo = getControllerIfAble(caller);
if (controllerInfo == null) {
@@ -170,7 +188,7 @@
}
// Return binder if the session is able to send a command to the controller.
- private IMediaSession2Callback getControllerBinderIfAble(ControllerInfo controller) {
+ private IMediaController2 getControllerBinderIfAble(ControllerInfo controller) {
if (getSession() == null) {
// getSession() already logged if session is closed.
return null;
@@ -190,7 +208,7 @@
}
// Return binder if the session is able to send a command to the controller.
- private IMediaSession2Callback getControllerBinderIfAble(ControllerInfo controller,
+ private IMediaController2 getControllerBinderIfAble(ControllerInfo controller,
int commandCode) {
synchronized (mLock) {
CommandGroup allowedCommands = mAllowedCommandGroupMap.get(controller);
@@ -208,11 +226,122 @@
}
}
+ private void onCommand(@NonNull IMediaController2 caller, int commandCode,
+ @NonNull SessionRunnable runnable) {
+ final MediaSession2Impl session = getSession();
+ final ControllerInfo controller = getControllerIfAble(caller, commandCode);
+ if (session == null || controller == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ if (getControllerIfAble(caller, commandCode) == null) {
+ return;
+ }
+ Command command = sCommandsForOnCommandRequest.get(commandCode);
+ if (command != null) {
+ boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
+ controller, command);
+ if (!accepted) {
+ // Don't run rejected command.
+ if (DEBUG) {
+ Log.d(TAG, "Command (code=" + commandCode + ") from "
+ + controller + " was rejected by " + session);
+ }
+ return;
+ }
+ }
+ runnable.run(session, controller);
+ });
+ }
+
+ private void onBrowserCommand(@NonNull IMediaController2 caller,
+ @NonNull LibrarySessionRunnable runnable) {
+ final MediaLibrarySessionImpl session = getLibrarySession();
+ final ControllerInfo controller =
+ getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER);
+ if (session == null || controller == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+ return;
+ }
+ runnable.run(session, controller);
+ });
+ }
+
+
+ private void notifyAll(int commandCode, @NonNull NotifyRunnable runnable) {
+ List<ControllerInfo> controllers = getControllers();
+ for (int i = 0; i < controllers.size(); i++) {
+ notifyInternal(controllers.get(i),
+ getControllerBinderIfAble(controllers.get(i), commandCode), runnable);
+ }
+ }
+
+ private void notifyAll(@NonNull NotifyRunnable runnable) {
+ List<ControllerInfo> controllers = getControllers();
+ for (int i = 0; i < controllers.size(); i++) {
+ notifyInternal(controllers.get(i),
+ getControllerBinderIfAble(controllers.get(i)), runnable);
+ }
+ }
+
+ private void notify(@NonNull ControllerInfo controller, @NonNull NotifyRunnable runnable) {
+ notifyInternal(controller, getControllerBinderIfAble(controller), runnable);
+ }
+
+ private void notify(@NonNull ControllerInfo controller, int commandCode,
+ @NonNull NotifyRunnable runnable) {
+ notifyInternal(controller, getControllerBinderIfAble(controller, commandCode), runnable);
+ }
+
+ // Do not call this API directly. Use notify() instead.
+ private void notifyInternal(@NonNull ControllerInfo controller,
+ @NonNull IMediaController2 iController, @NonNull NotifyRunnable runnable) {
+ if (controller == null || iController == null) {
+ return;
+ }
+ try {
+ runnable.run(controller, iController);
+ } catch (DeadObjectException e) {
+ if (DEBUG) {
+ Log.d(TAG, controller.toString() + " is gone", e);
+ }
+ onControllerClosed(iController);
+ } catch (RemoteException e) {
+ // Currently it's TransactionTooLargeException or DeadSystemException.
+ // We'd better to leave log for those cases because
+ // - TransactionTooLargeException means that we may need to fix our code.
+ // (e.g. add pagination or special way to deliver Bitmap)
+ // - DeadSystemException means that errors around it can be ignored.
+ Log.w(TAG, "Exception in " + controller.toString(), e);
+ }
+ }
+
+ private void onControllerClosed(IMediaController2 iController) {
+ ControllerInfo controller;
+ synchronized (mLock) {
+ controller = mControllers.remove(iController.asBinder());
+ if (DEBUG) {
+ Log.d(TAG, "releasing " + controller);
+ }
+ mSubscriptions.remove(controller);
+ }
+ final MediaSession2Impl session = getSession();
+ if (session == null || controller == null) {
+ return;
+ }
+ session.getCallbackExecutor().execute(() -> {
+ session.getCallback().onDisconnected(session.getInstance(), controller);
+ });
+ }
+
//////////////////////////////////////////////////////////////////////////////////////////////
// AIDL methods for session overrides
//////////////////////////////////////////////////////////////////////////////////////////////
@Override
- public void connect(final IMediaSession2Callback caller, final String callingPackage)
+ public void connect(final IMediaController2 caller, final String callingPackage)
throws RuntimeException {
final MediaSession2Impl session = getSession();
if (session == null) {
@@ -256,15 +385,17 @@
// 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
+ // IMediaController2 is oneway (i.e. async call) and Stub will
// use thread poll for incoming calls.
- // TODO(jaewan): Should we protect getting playback state?
- final PlaybackState2 state = session.getInstance().getPlaybackState();
- final Bundle playbackStateBundle = (state != null) ? state.toBundle() : null;
+ final int playerState = session.getInstance().getPlayerState();
+ final long positionEventTimeMs = System.currentTimeMillis();
+ final long positionMs = session.getInstance().getPosition();
+ final float playbackSpeed = session.getInstance().getPlaybackSpeed();
+ final long bufferedPositionMs = session.getInstance().getBufferedPosition();
final Bundle playbackInfoBundle = ((MediaController2Impl.PlaybackInfoImpl)
session.getPlaybackInfo().getProvider()).toBundle();
- final PlaylistParams params = session.getInstance().getPlaylistParams();
- final Bundle paramsBundle = (params != null) ? params.toBundle() : null;
+ final int repeatMode = session.getInstance().getRepeatMode();
+ final int shuffleMode = session.getInstance().getShuffleMode();
final PendingIntent sessionActivity = session.getSessionActivity();
final List<MediaItem2> playlist =
allowedCommands.hasCommand(MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST)
@@ -292,9 +423,10 @@
return;
}
try {
- caller.onConnected(MediaSession2Stub.this,
- allowedCommands.toBundle(), playbackStateBundle, playbackInfoBundle,
- paramsBundle, playlistBundle, sessionActivity);
+ caller.onConnected(MediaSession2Stub.this, allowedCommands.toBundle(),
+ playerState, positionEventTimeMs, positionMs, playbackSpeed,
+ bufferedPositionMs, playbackInfoBundle, repeatMode, shuffleMode,
+ playlistBundle, sessionActivity);
} catch (RemoteException e) {
// Controller may be died prematurely.
// TODO(jaewan): Handle here.
@@ -317,43 +449,14 @@
}
@Override
- public void release(final IMediaSession2Callback caller) throws RemoteException {
- synchronized (mLock) {
- ControllerInfo controllerInfo = mControllers.remove(caller.asBinder());
- if (DEBUG) {
- Log.d(TAG, "releasing " + controllerInfo);
- }
- mSubscriptions.remove(controllerInfo);
- }
+ public void release(final IMediaController2 caller) throws RemoteException {
+ onControllerClosed(caller);
}
@Override
- public void setVolumeTo(final IMediaSession2Callback caller, final int value, final int flags)
+ public void setVolumeTo(final IMediaController2 caller, final int value, final int flags)
throws RuntimeException {
- final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
- if (session == null || controller == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME) == null) {
- return;
- }
- // TODO(jaewan): Sanity check.
- Command command = new Command(
- session.getContext(), MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
- boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
- controller, command);
- if (!accepted) {
- // Don't run rejected command.
- if (DEBUG) {
- Log.d(TAG, "Command " + MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME + " from "
- + controller + " was rejected by " + session);
- }
- return;
- }
-
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME, (session, controller) -> {
VolumeProvider2 volumeProvider = session.getVolumeProvider();
if (volumeProvider == null) {
// TODO(jaewan): Set local stream volume
@@ -364,32 +467,9 @@
}
@Override
- public void adjustVolume(IMediaSession2Callback caller, int direction, int flags)
+ public void adjustVolume(IMediaController2 caller, int direction, int flags)
throws RuntimeException {
- final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
- if (session == null || controller == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME) == null) {
- return;
- }
- // TODO(jaewan): Sanity check.
- Command command = new Command(
- session.getContext(), MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME);
- boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
- controller, command);
- if (!accepted) {
- // Don't run rejected command.
- if (DEBUG) {
- Log.d(TAG, "Command " + MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME + " from "
- + controller + " was rejected by " + session);
- }
- return;
- }
-
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SET_VOLUME, (session, controller) -> {
VolumeProvider2 volumeProvider = session.getVolumeProvider();
if (volumeProvider == null) {
// TODO(jaewan): Adjust local stream volume
@@ -400,30 +480,9 @@
}
@Override
- public void sendTransportControlCommand(IMediaSession2Callback caller,
+ public void sendTransportControlCommand(IMediaController2 caller,
int commandCode, Bundle args) throws RuntimeException {
- final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(caller, commandCode);
- if (session == null || controller == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, commandCode) == null) {
- return;
- }
- // TODO(jaewan): Sanity check.
- Command command = new Command(session.getContext(), commandCode);
- boolean accepted = session.getCallback().onCommandRequest(session.getInstance(),
- controller, command);
- if (!accepted) {
- // Don't run rejected command.
- if (DEBUG) {
- Log.d(TAG, "Command " + commandCode + " from "
- + controller + " was rejected by " + session);
- }
- return;
- }
-
+ onCommand(caller, commandCode, (session, controller) -> {
switch (commandCode) {
case MediaSession2.COMMAND_CODE_PLAYBACK_PLAY:
session.getInstance().play();
@@ -434,12 +493,6 @@
case MediaSession2.COMMAND_CODE_PLAYBACK_STOP:
session.getInstance().stop();
break;
- case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM:
- session.getInstance().skipToPreviousItem();
- break;
- case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM:
- session.getInstance().skipToNextItem();
- break;
case MediaSession2.COMMAND_CODE_PLAYBACK_PREPARE:
session.getInstance().prepare();
break;
@@ -452,21 +505,6 @@
case MediaSession2.COMMAND_CODE_PLAYBACK_SEEK_TO:
session.getInstance().seekTo(args.getLong(ARGUMENT_KEY_POSITION));
break;
- case MediaSession2.COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM:
- // TODO(jaewan): Implement
- /*
- session.getInstance().skipToPlaylistItem(
- args.getInt(ARGUMENT_KEY_ITEM_INDEX));
- */
- break;
- // TODO(jaewan): Remove (b/74116823)
- /*
- case MediaSession2.COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS:
- session.getInstance().setPlaylistParams(
- PlaylistParams.fromBundle(session.getContext(),
- args.getBundle(ARGUMENT_KEY_PLAYLIST_PARAMS)));
- break;
- */
default:
// TODO(jaewan): Resend unknown (new) commands through the custom command.
}
@@ -474,7 +512,7 @@
}
@Override
- public void sendCustomCommand(final IMediaSession2Callback caller, final Bundle commandBundle,
+ public void sendCustomCommand(final IMediaController2 caller, final Bundle commandBundle,
final Bundle args, final ResultReceiver receiver) {
final MediaSession2Impl session = getSession();
if (session == null) {
@@ -500,44 +538,23 @@
}
@Override
- public void prepareFromUri(final IMediaSession2Callback caller, final Uri uri,
+ public void prepareFromUri(final IMediaController2 caller, final Uri uri,
final Bundle extras) {
- final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_URI);
- if (session == null || controller == null) {
- return;
- }
- if (uri == null) {
- Log.w(TAG, "prepareFromUri(): Ignoring null uri from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_URI) == null) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_URI, (session, controller) -> {
+ if (uri == null) {
+ Log.w(TAG, "prepareFromUri(): Ignoring null uri from " + controller);
return;
}
- session.getCallback().onPrepareFromUri(session.getInstance(),
- controller, uri, extras);
+ session.getCallback().onPrepareFromUri(session.getInstance(), controller, uri, extras);
});
}
@Override
- public void prepareFromSearch(final IMediaSession2Callback caller, final String query,
+ public void prepareFromSearch(final IMediaController2 caller, final String query,
final Bundle extras) {
- final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH);
- if (session == null || controller == null) {
- return;
- }
- if (TextUtils.isEmpty(query)) {
- Log.w(TAG, "prepareFromSearch(): Ignoring empty query from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH) == null) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_SEARCH, (session, controller) -> {
+ if (TextUtils.isEmpty(query)) {
+ Log.w(TAG, "prepareFromSearch(): Ignoring empty query from " + controller);
return;
}
session.getCallback().onPrepareFromSearch(session.getInstance(),
@@ -546,21 +563,12 @@
}
@Override
- public void prepareFromMediaId(final IMediaSession2Callback caller, final String mediaId,
+ public void prepareFromMediaId(final IMediaController2 caller, final String mediaId,
final Bundle extras) {
- final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID);
- if (session == null || controller == null) {
- return;
- }
- if (mediaId == null) {
- Log.w(TAG, "prepareFromMediaId(): Ignoring null mediaId from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID) == null) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PREPARE_FROM_MEDIA_ID,
+ (session, controller) -> {
+ if (mediaId == null) {
+ Log.w(TAG, "prepareFromMediaId(): Ignoring null mediaId from " + controller);
return;
}
session.getCallback().onPrepareFromMediaId(session.getInstance(),
@@ -569,21 +577,11 @@
}
@Override
- public void playFromUri(final IMediaSession2Callback caller, final Uri uri,
+ public void playFromUri(final IMediaController2 caller, final Uri uri,
final Bundle extras) {
- final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PLAY_FROM_URI);
- if (session == null || controller == null) {
- return;
- }
- if (uri == null) {
- Log.w(TAG, "playFromUri(): Ignoring null uri from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PLAY_FROM_URI) == null) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAY_FROM_URI, (session, controller) -> {
+ if (uri == null) {
+ Log.w(TAG, "playFromUri(): Ignoring null uri from " + controller);
return;
}
session.getCallback().onPlayFromUri(session.getInstance(), controller, uri, extras);
@@ -591,21 +589,11 @@
}
@Override
- public void playFromSearch(final IMediaSession2Callback caller, final String query,
+ public void playFromSearch(final IMediaController2 caller, final String query,
final Bundle extras) {
- final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH);
- if (session == null || controller == null) {
- return;
- }
- if (TextUtils.isEmpty(query)) {
- Log.w(TAG, "playFromSearch(): Ignoring empty query from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH) == null) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAY_FROM_SEARCH, (session, controller) -> {
+ if (TextUtils.isEmpty(query)) {
+ Log.w(TAG, "playFromSearch(): Ignoring empty query from " + controller);
return;
}
session.getCallback().onPlayFromSearch(session.getInstance(),
@@ -614,20 +602,11 @@
}
@Override
- public void playFromMediaId(final IMediaSession2Callback caller, final String mediaId,
+ public void playFromMediaId(final IMediaController2 caller, final String mediaId,
final Bundle extras) {
- final MediaSession2Impl session = getSession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_PLAY_FROM_MEDIA_ID);
- if (session == null || controller == null) {
- return;
- }
- if (mediaId == null) {
- Log.w(TAG, "playFromMediaId(): Ignoring null mediaId from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (session == null) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAY_FROM_MEDIA_ID, (session, controller) -> {
+ if (mediaId == null) {
+ Log.w(TAG, "playFromMediaId(): Ignoring null mediaId from " + controller);
return;
}
session.getCallback().onPlayFromMediaId(session.getInstance(),
@@ -636,113 +615,188 @@
}
@Override
- public void setRating(final IMediaSession2Callback caller, final String mediaId,
+ public void setRating(final IMediaController2 caller, final String mediaId,
final Bundle ratingBundle) {
- final MediaSession2Impl sessionImpl = getSession();
- final ControllerInfo controller = getControllerIfAble(caller);
- if (controller == null) {
- if (DEBUG) {
- Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
+ // TODO(jaewan): Define COMMAND_CODE_SET_RATING
+ onCommand(caller, MediaSession2.COMMAND_CODE_SET_RATING, (session, controller) -> {
+ if (mediaId == null) {
+ Log.w(TAG, "setRating(): Ignoring null mediaId from " + controller);
+ return;
}
- return;
- }
- Rating2 rating = Rating2Impl.fromBundle(sessionImpl.getContext(), ratingBundle);
- if (rating == null) {
- Log.w(TAG, "setRating(): Ignore null rating");
- return;
- }
- sessionImpl.getCallbackExecutor().execute(() -> {
- final MediaSession2Impl session = mSession.get();
- if (session == null) {
+ if (ratingBundle == null) {
+ Log.w(TAG, "setRating(): Ignoring null ratingBundle from " + controller);
+ return;
+ }
+ Rating2 rating = Rating2Impl.fromBundle(session.getContext(), ratingBundle);
+ if (rating == null) {
+ if (ratingBundle == null) {
+ Log.w(TAG, "setRating(): Ignoring null rating from " + controller);
+ return;
+ }
return;
}
session.getCallback().onSetRating(session.getInstance(), controller, mediaId, rating);
});
}
+ @Override
+ public void setPlaylist(final IMediaController2 caller, final List<Bundle> playlist,
+ final Bundle metadata) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST, (session, controller) -> {
+ if (playlist == null) {
+ Log.w(TAG, "setPlaylist(): Ignoring null playlist from " + controller);
+ return;
+ }
+ List<MediaItem2> list = new ArrayList<>();
+ for (int i = 0; i < playlist.size(); i++) {
+ // Recreates UUID in the playlist
+ MediaItem2 item = MediaItem2Impl.fromBundle(
+ session.getContext(), playlist.get(i), null);
+ if (item != null) {
+ list.add(item);
+ }
+ }
+ session.getInstance().setPlaylist(list,
+ MediaMetadata2.fromBundle(session.getContext(), metadata));
+ });
+ }
+
+ @Override
+ public void updatePlaylistMetadata(final IMediaController2 caller, final Bundle metadata) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA,
+ (session, controller) -> {
+ session.getInstance().updatePlaylistMetadata(
+ MediaMetadata2.fromBundle(session.getContext(), metadata));
+ });
+ }
+
+ @Override
+ public void addPlaylistItem(IMediaController2 caller, int index, Bundle mediaItem) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_ADD_ITEM, (session, controller) -> {
+ // Resets the UUID from the incoming media id, so controller may reuse a media item
+ // multiple times for addPlaylistItem.
+ session.getInstance().addPlaylistItem(index,
+ MediaItem2Impl.fromBundle(session.getContext(), mediaItem, null));
+ });
+ }
+
+ @Override
+ public void removePlaylistItem(IMediaController2 caller, Bundle mediaItem) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM,
+ (session, controller) -> {
+ MediaItem2 item = MediaItem2.fromBundle(session.getContext(), mediaItem);
+ // Note: MediaItem2 has hidden UUID to identify it across the processes.
+ session.getInstance().removePlaylistItem(item);
+ });
+ }
+
+ @Override
+ public void replacePlaylistItem(IMediaController2 caller, int index, Bundle mediaItem) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM,
+ (session, controller) -> {
+ // Resets the UUID from the incoming media id, so controller may reuse a media
+ // item multiple times for replacePlaylistItem.
+ session.getInstance().replacePlaylistItem(index,
+ MediaItem2Impl.fromBundle(session.getContext(), mediaItem, null));
+ });
+ }
+
+ @Override
+ public void skipToPlaylistItem(IMediaController2 caller, Bundle mediaItem) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM,
+ (session, controller) -> {
+ if (mediaItem == null) {
+ Log.w(TAG, "skipToPlaylistItem(): Ignoring null mediaItem from "
+ + controller);
+ }
+ // Note: MediaItem2 has hidden UUID to identify it across the processes.
+ session.getInstance().skipToPlaylistItem(
+ MediaItem2.fromBundle(session.getContext(), mediaItem));
+ });
+ }
+
+ @Override
+ public void skipToPreviousItem(IMediaController2 caller) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM,
+ (session, controller) -> {
+ session.getInstance().skipToPreviousItem();
+ });
+ }
+
+ @Override
+ public void skipToNextItem(IMediaController2 caller) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM,
+ (session, controller) -> {
+ session.getInstance().skipToNextItem();
+ });
+ }
+
+ @Override
+ public void setRepeatMode(IMediaController2 caller, int repeatMode) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_REPEAT_MODE,
+ (session, controller) -> {
+ session.getInstance().setRepeatMode(repeatMode);
+ });
+ }
+
+ @Override
+ public void setShuffleMode(IMediaController2 caller, int shuffleMode) {
+ onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_SET_SHUFFLE_MODE,
+ (session, controller) -> {
+ session.getInstance().setShuffleMode(shuffleMode);
+ });
+ }
+
//////////////////////////////////////////////////////////////////////////////////////////////
// AIDL methods for LibrarySession overrides
//////////////////////////////////////////////////////////////////////////////////////////////
@Override
- public void getLibraryRoot(final IMediaSession2Callback caller, final Bundle rootHints)
+ public void getLibraryRoot(final IMediaController2 caller, final Bundle rootHints)
throws RuntimeException {
- final MediaLibrarySessionImpl session = getLibrarySession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_BROWSER);
- if (session == null || controller == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
- return;
- }
- LibraryRoot root = session.getCallback().onGetLibraryRoot(session.getInstance(),
+ onBrowserCommand(caller, (session, controller) -> {
+ final LibraryRoot root = session.getCallback().onGetLibraryRoot(session.getInstance(),
controller, rootHints);
- try {
- caller.onGetLibraryRootDone(rootHints,
+ notify(controller, (unused, iController) -> {
+ iController.onGetLibraryRootDone(rootHints,
root == null ? null : root.getRootId(),
root == null ? null : root.getExtras());
- } catch (RemoteException e) {
- // Controller may be died prematurely.
- // TODO(jaewan): Handle this.
- }
+ });
});
}
@Override
- public void getItem(final IMediaSession2Callback caller, final String mediaId)
+ public void getItem(final IMediaController2 caller, final String mediaId)
throws RuntimeException {
- if (mediaId == null) {
- if (DEBUG) {
- Log.d(TAG, "mediaId shouldn't be null");
- }
- return;
- }
- final MediaLibrarySessionImpl session = getLibrarySession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_BROWSER);
- if (session == null || controller == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+ onBrowserCommand(caller, (session, controller) -> {
+ if (mediaId == null) {
+ if (DEBUG) {
+ Log.d(TAG, "mediaId shouldn't be null");
+ }
return;
}
- MediaItem2 result = session.getCallback().onGetItem(session.getInstance(),
+ final MediaItem2 result = session.getCallback().onGetItem(session.getInstance(),
controller, mediaId);
- try {
- caller.onGetItemDone(mediaId, result == null ? null : result.toBundle());
- } catch (RemoteException e) {
- // Controller may be died prematurely.
- // TODO(jaewan): Handle this.
- }
+ notify(controller, (unused, iController) -> {
+ iController.onGetItemDone(mediaId, result == null ? null : result.toBundle());
+ });
});
}
@Override
- public void getChildren(final IMediaSession2Callback caller, final String parentId,
+ public void getChildren(final IMediaController2 caller, final String parentId,
final int page, final int pageSize, final Bundle extras) throws RuntimeException {
- if (parentId == null) {
- if (DEBUG) {
- Log.d(TAG, "parentId shouldn't be null");
+ onBrowserCommand(caller, (session, controller) -> {
+ if (parentId == null) {
+ if (DEBUG) {
+ Log.d(TAG, "parentId shouldn't be null");
+ }
+ return;
}
- return;
- }
- if (page < 1 || pageSize < 1) {
- if (DEBUG) {
- Log.d(TAG, "Neither page nor pageSize should be less than 1");
- }
- return;
- }
- final MediaLibrarySessionImpl session = getLibrarySession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_BROWSER);
- if (session == null || controller == null) {
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+ if (page < 1 || pageSize < 1) {
+ if (DEBUG) {
+ Log.d(TAG, "Neither page nor pageSize should be less than 1");
+ }
return;
}
List<MediaItem2> result = session.getCallback().onGetChildren(session.getInstance(),
@@ -752,36 +806,26 @@
+ "more than pageSize. result.size()=" + result.size() + " pageSize="
+ pageSize);
}
- List<Bundle> bundleList = null;
+ final List<Bundle> bundleList;
if (result != null) {
bundleList = new ArrayList<>();
for (MediaItem2 item : result) {
bundleList.add(item == null ? null : item.toBundle());
}
+ } else {
+ bundleList = null;
}
- try {
- caller.onGetChildrenDone(parentId, page, pageSize, bundleList, extras);
- } catch (RemoteException e) {
- // Controller may be died prematurely.
- // TODO(jaewan): Handle this.
- }
+ notify(controller, (unused, iController) -> {
+ iController.onGetChildrenDone(parentId, page, pageSize, bundleList, extras);
+ });
});
}
@Override
- public void search(IMediaSession2Callback caller, String query, Bundle extras) {
- final MediaLibrarySessionImpl session = getLibrarySession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_BROWSER);
- if (session == null || controller == null) {
- return;
- }
- if (TextUtils.isEmpty(query)) {
- Log.w(TAG, "search(): Ignoring empty query from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+ public void search(IMediaController2 caller, String query, Bundle extras) {
+ onBrowserCommand(caller, (session, controller) -> {
+ if (TextUtils.isEmpty(query)) {
+ Log.w(TAG, "search(): Ignoring empty query from " + controller);
return;
}
session.getCallback().onSearch(session.getInstance(), controller, query, extras);
@@ -789,25 +833,16 @@
}
@Override
- public void getSearchResult(final IMediaSession2Callback caller, final String query,
+ public void getSearchResult(final IMediaController2 caller, final String query,
final int page, final int pageSize, final Bundle extras) {
- final MediaLibrarySessionImpl session = getLibrarySession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_BROWSER);
- if (session == null || controller == null) {
- return;
- }
- if (TextUtils.isEmpty(query)) {
- Log.w(TAG, "getSearchResult(): Ignoring empty query from " + controller);
- return;
- }
- if (page < 1 || pageSize < 1) {
- Log.w(TAG, "getSearchResult(): Ignoring negative page / pageSize."
- + " page=" + page + " pageSize=" + pageSize + " from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+ onBrowserCommand(caller, (session, controller) -> {
+ if (TextUtils.isEmpty(query)) {
+ Log.w(TAG, "getSearchResult(): Ignoring empty query from " + controller);
+ return;
+ }
+ if (page < 1 || pageSize < 1) {
+ Log.w(TAG, "getSearchResult(): Ignoring negative page / pageSize."
+ + " page=" + page + " pageSize=" + pageSize + " from " + controller);
return;
}
List<MediaItem2> result = session.getCallback().onGetSearchResult(session.getInstance(),
@@ -817,38 +852,27 @@
+ "items more than pageSize. result.size()=" + result.size() + " pageSize="
+ pageSize);
}
- List<Bundle> bundleList = null;
+ final List<Bundle> bundleList;
if (result != null) {
bundleList = new ArrayList<>();
for (MediaItem2 item : result) {
bundleList.add(item == null ? null : item.toBundle());
}
+ } else {
+ bundleList = null;
}
-
- try {
- caller.onGetSearchResultDone(query, page, pageSize, bundleList, extras);
- } catch (RemoteException e) {
- // Controller may be died prematurely.
- // TODO(jaewan): Handle this.
- }
+ notify(controller, (unused, iController) -> {
+ iController.onGetSearchResultDone(query, page, pageSize, bundleList, extras);
+ });
});
}
@Override
- public void subscribe(final IMediaSession2Callback caller, final String parentId,
+ public void subscribe(final IMediaController2 caller, final String parentId,
final Bundle option) {
- final MediaLibrarySessionImpl session = getLibrarySession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_BROWSER);
- if (session == null || controller == null) {
- return;
- }
- if (parentId == null) {
- Log.w(TAG, "subscribe(): Ignoring null parentId from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+ onBrowserCommand(caller, (session, controller) -> {
+ if (parentId == null) {
+ Log.w(TAG, "subscribe(): Ignoring null parentId from " + controller);
return;
}
session.getCallback().onSubscribe(session.getInstance(),
@@ -865,19 +889,10 @@
}
@Override
- public void unsubscribe(final IMediaSession2Callback caller, final String parentId) {
- final MediaLibrarySessionImpl session = getLibrarySession();
- final ControllerInfo controller = getControllerIfAble(
- caller, MediaSession2.COMMAND_CODE_BROWSER);
- if (session == null || controller == null) {
- return;
- }
- if (parentId == null) {
- Log.w(TAG, "unsubscribe(): Ignoring null parentId from " + controller);
- return;
- }
- session.getCallbackExecutor().execute(() -> {
- if (getControllerIfAble(caller, MediaSession2.COMMAND_CODE_BROWSER) == null) {
+ public void unsubscribe(final IMediaController2 caller, final String parentId) {
+ onBrowserCommand(caller, (session, controller) -> {
+ if (parentId == null) {
+ Log.w(TAG, "unsubscribe(): Ignoring null parentId from " + controller);
return;
}
session.getCallback().onUnsubscribe(session.getInstance(), controller, parentId);
@@ -891,7 +906,7 @@
// APIs for MediaSession2Impl
//////////////////////////////////////////////////////////////////////////////////////////////
- // TODO(jaewan): Need a way to get controller with permissions
+ // TODO(jaewan): (Can be Post-P) Need a way to get controller with permissions
public List<ControllerInfo> getControllers() {
ArrayList<ControllerInfo> controllers = new ArrayList<>();
synchronized (mLock) {
@@ -903,29 +918,32 @@
}
// Should be used without a lock to prevent potential deadlock.
- public void notifyPlaybackStateChangedNotLocked(PlaybackState2 state) {
- final List<ControllerInfo> list = getControllers();
- for (int i = 0; i < list.size(); i++) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(list.get(i));
- if (controllerBinder == null) {
- return;
- }
- try {
- final Bundle bundle = state != null ? state.toBundle() : null;
- controllerBinder.onPlaybackStateChanged(bundle);
- } catch (RemoteException e) {
- Log.w(TAG, "Controller is gone", e);
- // TODO(jaewan): What to do when the controller is gone?
- }
- }
+ public void notifyPlayerStateChangedNotLocked(int state) {
+ notifyAll((controller, iController) -> {
+ iController.onPlayerStateChanged(state);
+ });
+ }
+
+ public void notifyPositionChangedNotLocked(long eventTimeMs, long positionMs) {
+ notifyAll((controller, iController) -> {
+ iController.onPositionChanged(eventTimeMs, positionMs);
+ });
+ }
+
+ public void notifyPlaybackSpeedChangedNotLocked(float speed) {
+ notifyAll((controller, iController) -> {
+ iController.onPlaybackSpeedChanged(speed);
+ });
+ }
+
+ public void notifyBufferedPositionChangedNotLocked(long bufferedPositionMs) {
+ notifyAll((controller, iController) -> {
+ iController.onBufferedPositionChanged(bufferedPositionMs);
+ });
}
public void notifyCustomLayoutNotLocked(ControllerInfo controller, List<CommandButton> layout) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
- if (controllerBinder == null) {
- return;
- }
- try {
+ notify(controller, (unused, iController) -> {
List<Bundle> layoutBundles = new ArrayList<>();
for (int i = 0; i < layout.size(); i++) {
Bundle bundle = ((CommandButtonImpl) layout.get(i).getProvider()).toBundle();
@@ -933,85 +951,72 @@
layoutBundles.add(bundle);
}
}
- controllerBinder.onCustomLayoutChanged(layoutBundles);
- } catch (RemoteException e) {
- Log.w(TAG, "Controller is gone", e);
- // TODO(jaewan): What to do when the controller is gone?
- }
+ iController.onCustomLayoutChanged(layoutBundles);
+ });
}
- public void notifyPlaylistChanged(List<MediaItem2> playlist) {
- 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);
+ public void notifyPlaylistChangedNotLocked(List<MediaItem2> playlist, MediaMetadata2 metadata) {
+ final List<Bundle> bundleList;
+ if (playlist != null) {
+ 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);
+ }
}
}
+ } else {
+ bundleList = null;
}
- final List<ControllerInfo> list = getControllers();
- for (int i = 0; i < list.size(); i++) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(
- list.get(i), MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST);
- if (controllerBinder != null) {
- try {
- controllerBinder.onPlaylistChanged(bundleList);
- } catch (RemoteException e) {
- Log.w(TAG, "Controller is gone", e);
- // TODO(jaewan): What to do when the controller is gone?
- }
+ final Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
+ notifyAll((controller, iController) -> {
+ if (getControllerBinderIfAble(controller,
+ MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST) != null) {
+ iController.onPlaylistChanged(bundleList, metadataBundle);
+ } else if (getControllerBinderIfAble(controller,
+ MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA) != null) {
+ iController.onPlaylistMetadataChanged(metadataBundle);
}
- }
+ });
}
- public void notifyPlaylistParamsChanged(MediaSession2.PlaylistParams params) {
- final List<ControllerInfo> list = getControllers();
- for (int i = 0; i < list.size(); i++) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(list.get(i));
- if (controllerBinder == null) {
- return;
- }
- try {
- controllerBinder.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 notifyPlaylistMetadataChangedNotLocked(MediaMetadata2 metadata) {
+ final Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
+ notifyAll(MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA,
+ (unused, iController) -> {
+ iController.onPlaylistMetadataChanged(metadataBundle);
+ });
+ }
+
+ public void notifyRepeatModeChangedNotLocked(int repeatMode) {
+ notifyAll((unused, iController) -> {
+ iController.onRepeatModeChanged(repeatMode);
+ });
+ }
+
+ public void notifyShuffleModeChangedNotLocked(int shuffleMode) {
+ notifyAll((unused, iController) -> {
+ iController.onShuffleModeChanged(shuffleMode);
+ });
}
public void notifyPlaybackInfoChanged(MediaController2.PlaybackInfo playbackInfo) {
- final List<ControllerInfo> list = getControllers();
- for (int i = 0; i < list.size(); i++) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(list.get(i));
- if (controllerBinder == null) {
- return;
- }
- try {
- controllerBinder.onPlaybackInfoChanged(((MediaController2Impl.PlaybackInfoImpl)
- playbackInfo.getProvider()).toBundle());
- } catch (RemoteException e) {
- Log.w(TAG, "Controller is gone", e);
- // TODO(jaewan): What to do when the controller is gone?
- }
- }
+ final Bundle playbackInfoBundle =
+ ((MediaController2Impl.PlaybackInfoImpl) playbackInfo.getProvider()).toBundle();
+ notifyAll((unused, iController) -> {
+ iController.onPlaybackInfoChanged(playbackInfoBundle);
+ });
}
public void setAllowedCommands(ControllerInfo controller, CommandGroup commands) {
synchronized (mLock) {
mAllowedCommandGroupMap.put(controller, commands);
}
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
- if (controllerBinder == null) {
- return;
- }
- try {
- controllerBinder.onAllowedCommandsChanged(commands.toBundle());
- } catch (RemoteException e) {
- Log.w(TAG, "Controller is gone", e);
- // TODO(jaewan): What to do when the controller is gone?
- }
+ notify(controller, (unused, iController) -> {
+ iController.onAllowedCommandsChanged(commands.toBundle());
+ });
}
public void sendCustomCommand(ControllerInfo controller, Command command, Bundle args,
@@ -1023,32 +1028,26 @@
if (command == null) {
throw new IllegalArgumentException("command shouldn't be null");
}
- sendCustomCommandInternal(controller, command, args, receiver);
+ notify(controller, (unused, iController) -> {
+ Bundle commandBundle = command.toBundle();
+ iController.onCustomCommand(commandBundle, args, null);
+ });
}
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);
- }
+ Bundle commandBundle = command.toBundle();
+ notifyAll((unused, iController) -> {
+ iController.onCustomCommand(commandBundle, args, null);
+ });
}
- private void sendCustomCommandInternal(ControllerInfo controller, Command command, Bundle args,
- ResultReceiver receiver) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
- if (controllerBinder == null) {
- return;
- }
- try {
- Bundle commandBundle = command.toBundle();
- controllerBinder.onCustomCommand(commandBundle, args, receiver);
- } catch (RemoteException e) {
- Log.w(TAG, "Controller is gone", e);
- // TODO(jaewan): What to do when the controller is gone?
- }
+ public void notifyError(int errorCode, Bundle extras) {
+ notifyAll((unused, iController) -> {
+ iController.onError(errorCode, extras);
+ });
}
//////////////////////////////////////////////////////////////////////////////////////////////
@@ -1057,48 +1056,55 @@
public void notifySearchResultChanged(ControllerInfo controller, String query, int itemCount,
Bundle extras) {
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
- if (controllerBinder == null) {
- return;
- }
- try {
- controllerBinder.onSearchResultChanged(query, itemCount, extras);
- } catch (RemoteException e) {
- Log.w(TAG, "Controller is gone", e);
- // TODO(jaewan): What to do when the controller is gone?
- }
+ notify(controller, (unused, iController) -> {
+ iController.onSearchResultChanged(query, itemCount, extras);
+ });
}
public void notifyChildrenChangedNotLocked(ControllerInfo controller, String parentId,
int itemCount, Bundle extras) {
- notifyChildrenChangedInternalNotLocked(controller, parentId, itemCount, extras);
+ notify(controller, (unused, iController) -> {
+ if (isSubscribed(controller, parentId)) {
+ iController.onChildrenChanged(parentId, itemCount, extras);
+ }
+ });
}
public void notifyChildrenChangedNotLocked(String parentId, int itemCount, Bundle extras) {
- final List<ControllerInfo> controllers = getControllers();
- for (int i = 0; i < controllers.size(); i++) {
- notifyChildrenChangedInternalNotLocked(controllers.get(i), parentId, itemCount,
- extras);
- }
+ notifyAll((controller, iController) -> {
+ if (isSubscribed(controller, parentId)) {
+ iController.onChildrenChanged(parentId, itemCount, extras);
+ }
+ });
}
- public void notifyChildrenChangedInternalNotLocked(final ControllerInfo controller,
- String parentId, int itemCount, Bundle extras) {
- // Ensure subscription
+ private boolean isSubscribed(ControllerInfo controller, String parentId) {
synchronized (mLock) {
Set<String> subscriptions = mSubscriptions.get(controller);
if (subscriptions == null || !subscriptions.contains(parentId)) {
- return;
+ return false;
}
}
- final IMediaSession2Callback controllerBinder = getControllerBinderIfAble(controller);
- if (controller == null) {
- return;
- }
- try {
- controllerBinder.onChildrenChanged(parentId, itemCount, extras);
- } catch (RemoteException e) {
- // TODO(jaewan): Handle controller removed?
- }
+ return true;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ // Misc
+ //////////////////////////////////////////////////////////////////////////////////////////////
+
+ @FunctionalInterface
+ private interface SessionRunnable {
+ void run(final MediaSession2Impl session, final ControllerInfo controller);
+ }
+
+ @FunctionalInterface
+ private interface LibrarySessionRunnable {
+ void run(final MediaLibrarySessionImpl session, final ControllerInfo controller);
+ }
+
+ @FunctionalInterface
+ private interface NotifyRunnable {
+ void run(final ControllerInfo controller,
+ final IMediaController2 iController) throws RemoteException;
}
}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
index 6bd2b2a..a0123b5 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
@@ -27,10 +27,8 @@
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.support.annotation.GuardedBy;
@@ -109,13 +107,13 @@
return null;
}
- private void updateNotification(PlaybackState2 state) {
+ private void updateNotification(int playerState) {
MediaNotification mediaNotification = mInstance.onUpdateNotification();
if (mediaNotification == null) {
return;
}
- switch((int) state.getState()) {
- case PlaybackState.STATE_PLAYING:
+ switch(playerState) {
+ case MediaPlayerBase.PLAYER_STATE_PLAYING:
if (!mIsRunningForeground) {
mIsRunningForeground = true;
mInstance.startForegroundService(mStartSelfIntent);
@@ -124,7 +122,8 @@
return;
}
break;
- case PlaybackState.STATE_STOPPED:
+ case MediaPlayerBase.PLAYER_STATE_IDLE:
+ case MediaPlayerBase.PLAYER_STATE_ERROR:
if (mIsRunningForeground) {
mIsRunningForeground = false;
mInstance.stopForeground(true);
@@ -142,15 +141,6 @@
// TODO: Implement this
return;
}
- // TODO: Uncomment or remove
- //public void onPlaybackStateChanged(PlaybackState2 state) {
- // if (state == null) {
- // Log.w(TAG, "Ignoring null playback state");
- // return;
- // }
- // MediaSession2Impl impl = (MediaSession2Impl) mSession.getProvider();
- // updateNotification(impl.getInstance().getPlaybackState());
- //}
}
public static class MediaNotificationImpl implements MediaNotificationProvider {
diff --git a/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java b/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java
deleted file mode 100644
index ee8d6d7..0000000
--- a/packages/MediaComponents/src/com/android/media/PlaybackState2Impl.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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.PlaybackState2;
-import android.media.update.PlaybackState2Provider;
-import android.os.Bundle;
-
-public final class PlaybackState2Impl implements PlaybackState2Provider {
- /**
- * Keys used for converting a PlaybackState2 to a bundle object and vice versa.
- */
- private static final String KEY_STATE = "android.media.playbackstate2.state";
- private static final String KEY_POSITION = "android.media.playbackstate2.position";
- private static final String KEY_BUFFERED_POSITION =
- "android.media.playbackstate2.buffered_position";
- private static final String KEY_SPEED = "android.media.playbackstate2.speed";
- private static final String KEY_UPDATE_TIME = "android.media.playbackstate2.update_time";
- private static final String KEY_ACTIVE_ITEM_ID = "android.media.playbackstate2.active_item_id";
-
- private final Context mContext;
- private final PlaybackState2 mInstance;
- private final int mState;
- private final long mPosition;
- private final long mUpdateTime;
- private final float mSpeed;
- private final long mBufferedPosition;
- private final long mActiveItemId;
-
- public PlaybackState2Impl(Context context, PlaybackState2 instance, int state, long position,
- long updateTime, float speed, long bufferedPosition, long activeItemId) {
- mContext = context;
- mInstance = instance;
- mState = state;
- mPosition = position;
- mSpeed = speed;
- mUpdateTime = updateTime;
- mBufferedPosition = bufferedPosition;
- mActiveItemId = activeItemId;
- }
-
- @Override
- public String toString_impl() {
- StringBuilder bob = new StringBuilder("PlaybackState {");
- bob.append("state=").append(mState);
- bob.append(", position=").append(mPosition);
- bob.append(", buffered position=").append(mBufferedPosition);
- bob.append(", speed=").append(mSpeed);
- bob.append(", updated=").append(mUpdateTime);
- bob.append(", active item id=").append(mActiveItemId);
- bob.append("}");
- return bob.toString();
- }
-
- @Override
- public int getState_impl() {
- return mState;
- }
-
- @Override
- public long getPosition_impl() {
- return mPosition;
- }
-
- @Override
- public long getBufferedPosition_impl() {
- return mBufferedPosition;
- }
-
- @Override
- public float getPlaybackSpeed_impl() {
- return mSpeed;
- }
-
- @Override
- public long getLastPositionUpdateTime_impl() {
- return mUpdateTime;
- }
-
- @Override
- public long getCurrentPlaylistItemIndex_impl() {
- return mActiveItemId;
- }
-
- @Override
- public Bundle toBundle_impl() {
- Bundle bundle = new Bundle();
- bundle.putInt(KEY_STATE, mState);
- bundle.putLong(KEY_POSITION, mPosition);
- bundle.putLong(KEY_UPDATE_TIME, mUpdateTime);
- bundle.putFloat(KEY_SPEED, mSpeed);
- bundle.putLong(KEY_BUFFERED_POSITION, mBufferedPosition);
- bundle.putLong(KEY_ACTIVE_ITEM_ID, mActiveItemId);
- return bundle;
- }
-
- public static PlaybackState2 fromBundle(Context context, Bundle bundle) {
- if (bundle == null) {
- return null;
- }
- if (!bundle.containsKey(KEY_STATE)
- || !bundle.containsKey(KEY_POSITION)
- || !bundle.containsKey(KEY_UPDATE_TIME)
- || !bundle.containsKey(KEY_SPEED)
- || !bundle.containsKey(KEY_BUFFERED_POSITION)
- || !bundle.containsKey(KEY_ACTIVE_ITEM_ID)) {
- return null;
- }
- return new PlaybackState2(context,
- bundle.getInt(KEY_STATE),
- bundle.getLong(KEY_POSITION),
- bundle.getLong(KEY_UPDATE_TIME),
- bundle.getFloat(KEY_SPEED),
- bundle.getLong(KEY_BUFFERED_POSITION),
- bundle.getLong(KEY_ACTIVE_ITEM_ID));
- }
-}
\ No newline at end of file
diff --git a/packages/MediaComponents/src/com/android/media/SessionPlaylistAgent.java b/packages/MediaComponents/src/com/android/media/SessionPlaylistAgent.java
new file mode 100644
index 0000000..6805dad
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/SessionPlaylistAgent.java
@@ -0,0 +1,433 @@
+/*
+ * 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.DataSourceDesc;
+import android.media.MediaItem2;
+import android.media.MediaMetadata2;
+import android.media.MediaPlayerBase;
+import android.media.MediaPlaylistAgent;
+import android.media.MediaSession2;
+import android.media.MediaSession2.OnDataSourceMissingHelper;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class SessionPlaylistAgent extends MediaPlaylistAgent {
+ private static final String TAG = "SessionPlaylistAgent";
+ @VisibleForTesting
+ static final int END_OF_PLAYLIST = -1;
+ @VisibleForTesting
+ static final int NO_VALID_ITEMS = -2;
+
+ private final PlayItem mEopPlayItem = new PlayItem(END_OF_PLAYLIST, null);
+
+ private final Object mLock = new Object();
+ private final MediaSession2 mSession;
+
+ // TODO: Set data sources properly into mPlayer (b/74090741)
+ @GuardedBy("mLock")
+ private MediaPlayerBase mPlayer;
+ @GuardedBy("mLock")
+ private OnDataSourceMissingHelper mDsdHelper;
+
+ // TODO: Check if having the same item is okay (b/74090741)
+ @GuardedBy("mLock")
+ private ArrayList<MediaItem2> mPlaylist = new ArrayList<>();
+ @GuardedBy("mLock")
+ private ArrayList<MediaItem2> mShuffledList = new ArrayList<>();
+ @GuardedBy("mLock")
+ private Map<MediaItem2, DataSourceDesc> mItemDsdMap = new ArrayMap<>();
+ @GuardedBy("mLock")
+ private MediaMetadata2 mMetadata;
+ @GuardedBy("mLock")
+ private int mRepeatMode;
+ @GuardedBy("mLock")
+ private int mShuffleMode;
+ @GuardedBy("mLock")
+ private PlayItem mCurrent;
+
+ private class PlayItem {
+ int shuffledIdx;
+ DataSourceDesc dsd;
+ MediaItem2 mediaItem;
+
+ PlayItem(int shuffledIdx) {
+ this(shuffledIdx, null);
+ }
+
+ PlayItem(int shuffledIdx, DataSourceDesc dsd) {
+ this.shuffledIdx = shuffledIdx;
+ if (shuffledIdx >= 0) {
+ this.mediaItem = mShuffledList.get(shuffledIdx);
+ if (dsd == null) {
+ synchronized (mLock) {
+ this.dsd = retrieveDataSourceDescLocked(this.mediaItem);
+ }
+ } else {
+ this.dsd = dsd;
+ }
+ }
+ }
+
+ boolean isValid() {
+ if (this == mEopPlayItem) {
+ return true;
+ }
+ if (mediaItem == null) {
+ return false;
+ }
+ if (dsd == null) {
+ return false;
+ }
+ if (shuffledIdx >= mShuffledList.size()) {
+ return false;
+ }
+ if (mediaItem != mShuffledList.get(shuffledIdx)) {
+ return false;
+ }
+ if (mediaItem.getDataSourceDesc() != null
+ && !mediaItem.getDataSourceDesc().equals(dsd)) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ public SessionPlaylistAgent(@NonNull Context context, @NonNull MediaSession2 session,
+ @NonNull MediaPlayerBase player) {
+ super(context);
+ if (session == null) {
+ throw new IllegalArgumentException("session shouldn't be null");
+ }
+ if (player == null) {
+ throw new IllegalArgumentException("player shouldn't be null");
+ }
+ mSession = session;
+ mPlayer = player;
+ }
+
+ public void setPlayer(MediaPlayerBase player) {
+ if (player == null) {
+ throw new IllegalArgumentException("player shouldn't be null");
+ }
+ synchronized (mLock) {
+ mPlayer = player;
+ }
+ }
+
+ public void setOnDataSourceMissingHelper(OnDataSourceMissingHelper helper) {
+ synchronized (mLock) {
+ mDsdHelper = helper;
+ }
+ }
+
+ @Override
+ public @Nullable List<MediaItem2> getPlaylist() {
+ synchronized (mLock) {
+ return Collections.unmodifiableList(mPlaylist);
+ }
+ }
+
+ @Override
+ public void setPlaylist(@NonNull List<MediaItem2> list,
+ @Nullable MediaMetadata2 metadata) {
+ if (list == null) {
+ throw new IllegalArgumentException("list shouldn't be null");
+ }
+
+ synchronized (mLock) {
+ mItemDsdMap.clear();
+
+ mPlaylist.clear();
+ mPlaylist.addAll(list);
+ applyShuffleModeLocked();
+
+ mMetadata = metadata;
+ mCurrent = getNextValidPlayItemLocked(END_OF_PLAYLIST, 1);
+ }
+ notifyPlaylistChanged();
+ }
+
+ @Override
+ public @Nullable MediaMetadata2 getPlaylistMetadata() {
+ return mMetadata;
+ }
+
+ @Override
+ public void updatePlaylistMetadata(@Nullable MediaMetadata2 metadata) {
+ synchronized (mLock) {
+ if (metadata == mMetadata) {
+ return;
+ }
+ mMetadata = metadata;
+ }
+ notifyPlaylistMetadataChanged();
+ }
+
+ @Override
+ public void addPlaylistItem(int index, @NonNull MediaItem2 item) {
+ if (item == null) {
+ throw new IllegalArgumentException("item shouldn't be null");
+ }
+ synchronized (mLock) {
+ index = clamp(index, mPlaylist.size());
+ int shuffledIdx = index;
+ mPlaylist.add(index, item);
+ if (mShuffleMode == MediaPlaylistAgent.SHUFFLE_MODE_NONE) {
+ mShuffledList.add(index, item);
+ } else {
+ // Add the item in random position of mShuffledList.
+ shuffledIdx = ThreadLocalRandom.current().nextInt(mShuffledList.size() + 1);
+ mShuffledList.add(shuffledIdx, item);
+ }
+ if (!hasValidItem()) {
+ mCurrent = getNextValidPlayItemLocked(END_OF_PLAYLIST, 1);
+ } else {
+ updateCurrentIfNeededLocked();
+ }
+ }
+ notifyPlaylistChanged();
+ }
+
+ @Override
+ public void removePlaylistItem(@NonNull MediaItem2 item) {
+ if (item == null) {
+ throw new IllegalArgumentException("item shouldn't be null");
+ }
+ synchronized (mLock) {
+ if (!mPlaylist.remove(item)) {
+ return;
+ }
+ mShuffledList.remove(item);
+ mItemDsdMap.remove(item);
+ updateCurrentIfNeededLocked();
+ }
+ notifyPlaylistChanged();
+ }
+
+ @Override
+ public void replacePlaylistItem(int index, @NonNull MediaItem2 item) {
+ if (item == null) {
+ throw new IllegalArgumentException("item shouldn't be null");
+ }
+ synchronized (mLock) {
+ if (mPlaylist.size() <= 0) {
+ return;
+ }
+ index = clamp(index, mPlaylist.size() - 1);
+ int shuffledIdx = mShuffledList.indexOf(mPlaylist.get(index));
+ mItemDsdMap.remove(mShuffledList.get(shuffledIdx));
+ mShuffledList.set(shuffledIdx, item);
+ mPlaylist.set(index, item);
+ if (!hasValidItem()) {
+ mCurrent = getNextValidPlayItemLocked(END_OF_PLAYLIST, 1);
+ } else {
+ updateCurrentIfNeededLocked();
+ }
+ }
+ notifyPlaylistChanged();
+ }
+
+ @Override
+ public void skipToPlaylistItem(@NonNull MediaItem2 item) {
+ if (item == null) {
+ throw new IllegalArgumentException("item shouldn't be null");
+ }
+ synchronized (mLock) {
+ if (!hasValidItem() || item.equals(mCurrent.mediaItem)) {
+ return;
+ }
+ int shuffledIdx = mShuffledList.indexOf(item);
+ if (shuffledIdx < 0) {
+ return;
+ }
+ mCurrent = new PlayItem(shuffledIdx);
+ updateCurrentIfNeededLocked();
+ }
+ }
+
+ @Override
+ public void skipToPreviousItem() {
+ synchronized (mLock) {
+ if (!hasValidItem()) {
+ return;
+ }
+ PlayItem prev = getNextValidPlayItemLocked(mCurrent.shuffledIdx, -1);
+ if (prev != mEopPlayItem) {
+ mCurrent = prev;
+ }
+ updateCurrentIfNeededLocked();
+ }
+ }
+
+ @Override
+ public void skipToNextItem() {
+ synchronized (mLock) {
+ if (!hasValidItem()) {
+ return;
+ }
+ PlayItem next = getNextValidPlayItemLocked(mCurrent.shuffledIdx, 1);
+ if (next != mEopPlayItem) {
+ mCurrent = next;
+ }
+ updateCurrentIfNeededLocked();
+ }
+ }
+
+ @Override
+ public int getRepeatMode() {
+ return mRepeatMode;
+ }
+
+ @Override
+ public void setRepeatMode(int repeatMode) {
+ if (repeatMode < MediaPlaylistAgent.REPEAT_MODE_NONE
+ || repeatMode > MediaPlaylistAgent.REPEAT_MODE_GROUP) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mRepeatMode == repeatMode) {
+ return;
+ }
+ mRepeatMode = repeatMode;
+ }
+ notifyRepeatModeChanged();
+ }
+
+ @Override
+ public int getShuffleMode() {
+ return mShuffleMode;
+ }
+
+ @Override
+ public void setShuffleMode(int shuffleMode) {
+ if (shuffleMode < MediaPlaylistAgent.SHUFFLE_MODE_NONE
+ || shuffleMode > MediaPlaylistAgent.SHUFFLE_MODE_GROUP) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mShuffleMode == shuffleMode) {
+ return;
+ }
+ mShuffleMode = shuffleMode;
+ applyShuffleModeLocked();
+ }
+ notifyShuffleModeChanged();
+ }
+
+ @VisibleForTesting
+ int getCurShuffledIndex() {
+ return hasValidItem() ? mCurrent.shuffledIdx : NO_VALID_ITEMS;
+ }
+
+ private boolean hasValidItem() {
+ return mCurrent != null;
+ }
+
+ private DataSourceDesc retrieveDataSourceDescLocked(MediaItem2 item) {
+ DataSourceDesc dsd = item.getDataSourceDesc();
+ if (dsd != null) {
+ mItemDsdMap.put(item, dsd);
+ return dsd;
+ }
+ dsd = mItemDsdMap.get(item);
+ if (dsd != null) {
+ return dsd;
+ }
+ OnDataSourceMissingHelper helper = mDsdHelper;
+ if (helper != null) {
+ // TODO: Do not call onDataSourceMissing with the lock (b/74090741).
+ dsd = helper.onDataSourceMissing(mSession, item);
+ if (dsd != null) {
+ mItemDsdMap.put(item, dsd);
+ }
+ }
+ return dsd;
+ }
+
+ private PlayItem getNextValidPlayItemLocked(int curShuffledIdx, int direction) {
+ int size = mPlaylist.size();
+ if (curShuffledIdx == END_OF_PLAYLIST) {
+ curShuffledIdx = (direction > 0) ? -1 : size;
+ }
+ for (int i = 0; i < size; i++) {
+ curShuffledIdx += direction;
+ if (curShuffledIdx < 0 || curShuffledIdx >= mPlaylist.size()) {
+ if (mRepeatMode == REPEAT_MODE_NONE) {
+ return (i == size - 1) ? null : mEopPlayItem;
+ } else {
+ curShuffledIdx = curShuffledIdx < 0 ? mPlaylist.size() - 1 : 0;
+ }
+ }
+ DataSourceDesc dsd = retrieveDataSourceDescLocked(mShuffledList.get(curShuffledIdx));
+ if (dsd != null) {
+ return new PlayItem(curShuffledIdx, dsd);
+ }
+ }
+ return null;
+ }
+
+ private void updateCurrentIfNeededLocked() {
+ if (!hasValidItem() || mCurrent.isValid()) {
+ return;
+ }
+ int shuffledIdx = mShuffledList.indexOf(mCurrent.mediaItem);
+ if (shuffledIdx >= 0) {
+ // Added an item.
+ mCurrent.shuffledIdx = shuffledIdx;
+ return;
+ }
+
+ if (mCurrent.shuffledIdx >= mShuffledList.size()) {
+ mCurrent = getNextValidPlayItemLocked(mShuffledList.size() - 1, 1);
+ } else {
+ mCurrent.mediaItem = mShuffledList.get(mCurrent.shuffledIdx);
+ if (retrieveDataSourceDescLocked(mCurrent.mediaItem) == null) {
+ mCurrent = getNextValidPlayItemLocked(mCurrent.shuffledIdx, 1);
+ }
+ }
+ return;
+ }
+
+ private void applyShuffleModeLocked() {
+ mShuffledList.clear();
+ mShuffledList.addAll(mPlaylist);
+ if (mShuffleMode == MediaPlaylistAgent.SHUFFLE_MODE_ALL
+ || mShuffleMode == MediaPlaylistAgent.SHUFFLE_MODE_GROUP) {
+ Collections.shuffle(mShuffledList);
+ }
+ }
+
+ // Clamps value to [0, size]
+ private static int clamp(int value, int size) {
+ if (value < 0) {
+ return 0;
+ }
+ return (value > size) ? size : value;
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/media/subtitle/ClosedCaptionRenderer.java b/packages/MediaComponents/src/com/android/media/subtitle/ClosedCaptionRenderer.java
new file mode 100644
index 0000000..ff7eec9
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/subtitle/ClosedCaptionRenderer.java
@@ -0,0 +1,1501 @@
+/*
+ * 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.subtitle;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.media.MediaFormat;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.TextPaint;
+import android.text.style.CharacterStyle;
+import android.text.style.StyleSpan;
+import android.text.style.UnderlineSpan;
+import android.text.style.UpdateAppearance;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.CaptioningManager;
+import android.view.accessibility.CaptioningManager.CaptionStyle;
+import android.view.accessibility.CaptioningManager.CaptioningChangeListener;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Vector;
+
+// Note: This is forked from android.media.ClosedCaptionRenderer since P
+public class ClosedCaptionRenderer extends SubtitleController.Renderer {
+ private final Context mContext;
+ private Cea608CCWidget mCCWidget;
+
+ public ClosedCaptionRenderer(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean supports(MediaFormat format) {
+ if (format.containsKey(MediaFormat.KEY_MIME)) {
+ String mimeType = format.getString(MediaFormat.KEY_MIME);
+ return MediaFormat.MIMETYPE_TEXT_CEA_608.equals(mimeType);
+ }
+ return false;
+ }
+
+ @Override
+ public SubtitleTrack createTrack(MediaFormat format) {
+ String mimeType = format.getString(MediaFormat.KEY_MIME);
+ if (MediaFormat.MIMETYPE_TEXT_CEA_608.equals(mimeType)) {
+ if (mCCWidget == null) {
+ mCCWidget = new Cea608CCWidget(mContext);
+ }
+ return new Cea608CaptionTrack(mCCWidget, format);
+ }
+ throw new RuntimeException("No matching format: " + format.toString());
+ }
+}
+
+class Cea608CaptionTrack extends SubtitleTrack {
+ private final Cea608CCParser mCCParser;
+ private final Cea608CCWidget mRenderingWidget;
+
+ Cea608CaptionTrack(Cea608CCWidget renderingWidget, MediaFormat format) {
+ super(format);
+
+ mRenderingWidget = renderingWidget;
+ mCCParser = new Cea608CCParser(mRenderingWidget);
+ }
+
+ @Override
+ public void onData(byte[] data, boolean eos, long runID) {
+ mCCParser.parse(data);
+ }
+
+ @Override
+ public RenderingWidget getRenderingWidget() {
+ return mRenderingWidget;
+ }
+
+ @Override
+ public void updateView(Vector<Cue> activeCues) {
+ // Overriding with NO-OP, CC rendering by-passes this
+ }
+}
+
+/**
+ * Abstract widget class to render a closed caption track.
+ */
+abstract class ClosedCaptionWidget extends ViewGroup implements SubtitleTrack.RenderingWidget {
+
+ interface ClosedCaptionLayout {
+ void setCaptionStyle(CaptionStyle captionStyle);
+ void setFontScale(float scale);
+ }
+
+ private static final CaptionStyle DEFAULT_CAPTION_STYLE = CaptionStyle.DEFAULT;
+
+ /** Captioning manager, used to obtain and track caption properties. */
+ private final CaptioningManager mManager;
+
+ /** Current caption style. */
+ protected CaptionStyle mCaptionStyle;
+
+ /** Callback for rendering changes. */
+ protected OnChangedListener mListener;
+
+ /** Concrete layout of CC. */
+ protected ClosedCaptionLayout mClosedCaptionLayout;
+
+ /** Whether a caption style change listener is registered. */
+ private boolean mHasChangeListener;
+
+ public ClosedCaptionWidget(Context context) {
+ this(context, null);
+ }
+
+ public ClosedCaptionWidget(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ClosedCaptionWidget(Context context, AttributeSet attrs, int defStyle) {
+ this(context, attrs, defStyle, 0);
+ }
+
+ public ClosedCaptionWidget(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ // Cannot render text over video when layer type is hardware.
+ setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+
+ mManager = (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
+ mCaptionStyle = DEFAULT_CAPTION_STYLE.applyStyle(mManager.getUserStyle());
+
+ mClosedCaptionLayout = createCaptionLayout(context);
+ mClosedCaptionLayout.setCaptionStyle(mCaptionStyle);
+ mClosedCaptionLayout.setFontScale(mManager.getFontScale());
+ addView((ViewGroup) mClosedCaptionLayout, LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT);
+
+ requestLayout();
+ }
+
+ public abstract ClosedCaptionLayout createCaptionLayout(Context context);
+
+ @Override
+ public void setOnChangedListener(OnChangedListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void setSize(int width, int height) {
+ final int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+ final int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+
+ measure(widthSpec, heightSpec);
+ layout(0, 0, width, height);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ if (visible) {
+ setVisibility(View.VISIBLE);
+ } else {
+ setVisibility(View.GONE);
+ }
+
+ manageChangeListener();
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ manageChangeListener();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ manageChangeListener();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ ((ViewGroup) mClosedCaptionLayout).measure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ ((ViewGroup) mClosedCaptionLayout).layout(l, t, r, b);
+ }
+
+ /**
+ * Manages whether this renderer is listening for caption style changes.
+ */
+ private final CaptioningChangeListener mCaptioningListener = new CaptioningChangeListener() {
+ @Override
+ public void onUserStyleChanged(CaptionStyle userStyle) {
+ mCaptionStyle = DEFAULT_CAPTION_STYLE.applyStyle(userStyle);
+ mClosedCaptionLayout.setCaptionStyle(mCaptionStyle);
+ }
+
+ @Override
+ public void onFontScaleChanged(float fontScale) {
+ mClosedCaptionLayout.setFontScale(fontScale);
+ }
+ };
+
+ private void manageChangeListener() {
+ final boolean needsListener = isAttachedToWindow() && getVisibility() == View.VISIBLE;
+ if (mHasChangeListener != needsListener) {
+ mHasChangeListener = needsListener;
+
+ if (needsListener) {
+ mManager.addCaptioningChangeListener(mCaptioningListener);
+ } else {
+ mManager.removeCaptioningChangeListener(mCaptioningListener);
+ }
+ }
+ }
+}
+
+/**
+ * CCParser processes CEA-608 closed caption data.
+ *
+ * It calls back into OnDisplayChangedListener upon
+ * display change with styled text for rendering.
+ *
+ */
+class Cea608CCParser {
+ public static final int MAX_ROWS = 15;
+ public static final int MAX_COLS = 32;
+
+ private static final String TAG = "Cea608CCParser";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final int INVALID = -1;
+
+ // EIA-CEA-608: Table 70 - Control Codes
+ private static final int RCL = 0x20;
+ private static final int BS = 0x21;
+ private static final int AOF = 0x22;
+ private static final int AON = 0x23;
+ private static final int DER = 0x24;
+ private static final int RU2 = 0x25;
+ private static final int RU3 = 0x26;
+ private static final int RU4 = 0x27;
+ private static final int FON = 0x28;
+ private static final int RDC = 0x29;
+ private static final int TR = 0x2a;
+ private static final int RTD = 0x2b;
+ private static final int EDM = 0x2c;
+ private static final int CR = 0x2d;
+ private static final int ENM = 0x2e;
+ private static final int EOC = 0x2f;
+
+ // Transparent Space
+ private static final char TS = '\u00A0';
+
+ // Captioning Modes
+ private static final int MODE_UNKNOWN = 0;
+ private static final int MODE_PAINT_ON = 1;
+ private static final int MODE_ROLL_UP = 2;
+ private static final int MODE_POP_ON = 3;
+ private static final int MODE_TEXT = 4;
+
+ private final DisplayListener mListener;
+
+ private int mMode = MODE_PAINT_ON;
+ private int mRollUpSize = 4;
+ private int mPrevCtrlCode = INVALID;
+
+ private CCMemory mDisplay = new CCMemory();
+ private CCMemory mNonDisplay = new CCMemory();
+ private CCMemory mTextMem = new CCMemory();
+
+ Cea608CCParser(DisplayListener listener) {
+ mListener = listener;
+ }
+
+ public void parse(byte[] data) {
+ CCData[] ccData = CCData.fromByteArray(data);
+
+ for (int i = 0; i < ccData.length; i++) {
+ if (DEBUG) {
+ Log.d(TAG, ccData[i].toString());
+ }
+
+ if (handleCtrlCode(ccData[i])
+ || handleTabOffsets(ccData[i])
+ || handlePACCode(ccData[i])
+ || handleMidRowCode(ccData[i])) {
+ continue;
+ }
+
+ handleDisplayableChars(ccData[i]);
+ }
+ }
+
+ interface DisplayListener {
+ void onDisplayChanged(SpannableStringBuilder[] styledTexts);
+ CaptionStyle getCaptionStyle();
+ }
+
+ private CCMemory getMemory() {
+ // get the CC memory to operate on for current mode
+ switch (mMode) {
+ case MODE_POP_ON:
+ return mNonDisplay;
+ case MODE_TEXT:
+ // TODO(chz): support only caption mode for now,
+ // in text mode, dump everything to text mem.
+ return mTextMem;
+ case MODE_PAINT_ON:
+ case MODE_ROLL_UP:
+ return mDisplay;
+ default:
+ Log.w(TAG, "unrecoginized mode: " + mMode);
+ }
+ return mDisplay;
+ }
+
+ private boolean handleDisplayableChars(CCData ccData) {
+ if (!ccData.isDisplayableChar()) {
+ return false;
+ }
+
+ // Extended char includes 1 automatic backspace
+ if (ccData.isExtendedChar()) {
+ getMemory().bs();
+ }
+
+ getMemory().writeText(ccData.getDisplayText());
+
+ if (mMode == MODE_PAINT_ON || mMode == MODE_ROLL_UP) {
+ updateDisplay();
+ }
+
+ return true;
+ }
+
+ private boolean handleMidRowCode(CCData ccData) {
+ StyleCode m = ccData.getMidRow();
+ if (m != null) {
+ getMemory().writeMidRowCode(m);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean handlePACCode(CCData ccData) {
+ PAC pac = ccData.getPAC();
+
+ if (pac != null) {
+ if (mMode == MODE_ROLL_UP) {
+ getMemory().moveBaselineTo(pac.getRow(), mRollUpSize);
+ }
+ getMemory().writePAC(pac);
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean handleTabOffsets(CCData ccData) {
+ int tabs = ccData.getTabOffset();
+
+ if (tabs > 0) {
+ getMemory().tab(tabs);
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean handleCtrlCode(CCData ccData) {
+ int ctrlCode = ccData.getCtrlCode();
+
+ if (mPrevCtrlCode != INVALID && mPrevCtrlCode == ctrlCode) {
+ // discard double ctrl codes (but if there's a 3rd one, we still take that)
+ mPrevCtrlCode = INVALID;
+ return true;
+ }
+
+ switch(ctrlCode) {
+ case RCL:
+ // select pop-on style
+ mMode = MODE_POP_ON;
+ break;
+ case BS:
+ getMemory().bs();
+ break;
+ case DER:
+ getMemory().der();
+ break;
+ case RU2:
+ case RU3:
+ case RU4:
+ mRollUpSize = (ctrlCode - 0x23);
+ // erase memory if currently in other style
+ if (mMode != MODE_ROLL_UP) {
+ mDisplay.erase();
+ mNonDisplay.erase();
+ }
+ // select roll-up style
+ mMode = MODE_ROLL_UP;
+ break;
+ case FON:
+ Log.i(TAG, "Flash On");
+ break;
+ case RDC:
+ // select paint-on style
+ mMode = MODE_PAINT_ON;
+ break;
+ case TR:
+ mMode = MODE_TEXT;
+ mTextMem.erase();
+ break;
+ case RTD:
+ mMode = MODE_TEXT;
+ break;
+ case EDM:
+ // erase display memory
+ mDisplay.erase();
+ updateDisplay();
+ break;
+ case CR:
+ if (mMode == MODE_ROLL_UP) {
+ getMemory().rollUp(mRollUpSize);
+ } else {
+ getMemory().cr();
+ }
+ if (mMode == MODE_ROLL_UP) {
+ updateDisplay();
+ }
+ break;
+ case ENM:
+ // erase non-display memory
+ mNonDisplay.erase();
+ break;
+ case EOC:
+ // swap display/non-display memory
+ swapMemory();
+ // switch to pop-on style
+ mMode = MODE_POP_ON;
+ updateDisplay();
+ break;
+ case INVALID:
+ default:
+ mPrevCtrlCode = INVALID;
+ return false;
+ }
+
+ mPrevCtrlCode = ctrlCode;
+
+ // handled
+ return true;
+ }
+
+ private void updateDisplay() {
+ if (mListener != null) {
+ CaptionStyle captionStyle = mListener.getCaptionStyle();
+ mListener.onDisplayChanged(mDisplay.getStyledText(captionStyle));
+ }
+ }
+
+ private void swapMemory() {
+ CCMemory temp = mDisplay;
+ mDisplay = mNonDisplay;
+ mNonDisplay = temp;
+ }
+
+ private static class StyleCode {
+ static final int COLOR_WHITE = 0;
+ static final int COLOR_GREEN = 1;
+ static final int COLOR_BLUE = 2;
+ static final int COLOR_CYAN = 3;
+ static final int COLOR_RED = 4;
+ static final int COLOR_YELLOW = 5;
+ static final int COLOR_MAGENTA = 6;
+ static final int COLOR_INVALID = 7;
+
+ static final int STYLE_ITALICS = 0x00000001;
+ static final int STYLE_UNDERLINE = 0x00000002;
+
+ static final String[] mColorMap = {
+ "WHITE", "GREEN", "BLUE", "CYAN", "RED", "YELLOW", "MAGENTA", "INVALID"
+ };
+
+ final int mStyle;
+ final int mColor;
+
+ static StyleCode fromByte(byte data2) {
+ int style = 0;
+ int color = (data2 >> 1) & 0x7;
+
+ if ((data2 & 0x1) != 0) {
+ style |= STYLE_UNDERLINE;
+ }
+
+ if (color == COLOR_INVALID) {
+ // WHITE ITALICS
+ color = COLOR_WHITE;
+ style |= STYLE_ITALICS;
+ }
+
+ return new StyleCode(style, color);
+ }
+
+ StyleCode(int style, int color) {
+ mStyle = style;
+ mColor = color;
+ }
+
+ boolean isItalics() {
+ return (mStyle & STYLE_ITALICS) != 0;
+ }
+
+ boolean isUnderline() {
+ return (mStyle & STYLE_UNDERLINE) != 0;
+ }
+
+ int getColor() {
+ return mColor;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder str = new StringBuilder();
+ str.append("{");
+ str.append(mColorMap[mColor]);
+ if ((mStyle & STYLE_ITALICS) != 0) {
+ str.append(", ITALICS");
+ }
+ if ((mStyle & STYLE_UNDERLINE) != 0) {
+ str.append(", UNDERLINE");
+ }
+ str.append("}");
+
+ return str.toString();
+ }
+ }
+
+ private static class PAC extends StyleCode {
+ final int mRow;
+ final int mCol;
+
+ static PAC fromBytes(byte data1, byte data2) {
+ int[] rowTable = {11, 1, 3, 12, 14, 5, 7, 9};
+ int row = rowTable[data1 & 0x07] + ((data2 & 0x20) >> 5);
+ int style = 0;
+ if ((data2 & 1) != 0) {
+ style |= STYLE_UNDERLINE;
+ }
+ if ((data2 & 0x10) != 0) {
+ // indent code
+ int indent = (data2 >> 1) & 0x7;
+ return new PAC(row, indent * 4, style, COLOR_WHITE);
+ } else {
+ // style code
+ int color = (data2 >> 1) & 0x7;
+
+ if (color == COLOR_INVALID) {
+ // WHITE ITALICS
+ color = COLOR_WHITE;
+ style |= STYLE_ITALICS;
+ }
+ return new PAC(row, -1, style, color);
+ }
+ }
+
+ PAC(int row, int col, int style, int color) {
+ super(style, color);
+ mRow = row;
+ mCol = col;
+ }
+
+ boolean isIndentPAC() {
+ return (mCol >= 0);
+ }
+
+ int getRow() {
+ return mRow;
+ }
+
+ int getCol() {
+ return mCol;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("{%d, %d}, %s",
+ mRow, mCol, super.toString());
+ }
+ }
+
+ /**
+ * Mutable version of BackgroundSpan to facilitate text rendering with edge styles.
+ */
+ public static class MutableBackgroundColorSpan extends CharacterStyle
+ implements UpdateAppearance {
+ private int mColor;
+
+ public MutableBackgroundColorSpan(int color) {
+ mColor = color;
+ }
+
+ public void setBackgroundColor(int color) {
+ mColor = color;
+ }
+
+ public int getBackgroundColor() {
+ return mColor;
+ }
+
+ @Override
+ public void updateDrawState(TextPaint ds) {
+ ds.bgColor = mColor;
+ }
+ }
+
+ /* CCLineBuilder keeps track of displayable chars, as well as
+ * MidRow styles and PACs, for a single line of CC memory.
+ *
+ * It generates styled text via getStyledText() method.
+ */
+ private static class CCLineBuilder {
+ private final StringBuilder mDisplayChars;
+ private final StyleCode[] mMidRowStyles;
+ private final StyleCode[] mPACStyles;
+
+ CCLineBuilder(String str) {
+ mDisplayChars = new StringBuilder(str);
+ mMidRowStyles = new StyleCode[mDisplayChars.length()];
+ mPACStyles = new StyleCode[mDisplayChars.length()];
+ }
+
+ void setCharAt(int index, char ch) {
+ mDisplayChars.setCharAt(index, ch);
+ mMidRowStyles[index] = null;
+ }
+
+ void setMidRowAt(int index, StyleCode m) {
+ mDisplayChars.setCharAt(index, ' ');
+ mMidRowStyles[index] = m;
+ }
+
+ void setPACAt(int index, PAC pac) {
+ mPACStyles[index] = pac;
+ }
+
+ char charAt(int index) {
+ return mDisplayChars.charAt(index);
+ }
+
+ int length() {
+ return mDisplayChars.length();
+ }
+
+ void applyStyleSpan(
+ SpannableStringBuilder styledText,
+ StyleCode s, int start, int end) {
+ if (s.isItalics()) {
+ styledText.setSpan(
+ new StyleSpan(android.graphics.Typeface.ITALIC),
+ start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ if (s.isUnderline()) {
+ styledText.setSpan(
+ new UnderlineSpan(),
+ start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+
+ SpannableStringBuilder getStyledText(CaptionStyle captionStyle) {
+ SpannableStringBuilder styledText = new SpannableStringBuilder(mDisplayChars);
+ int start = -1, next = 0;
+ int styleStart = -1;
+ StyleCode curStyle = null;
+ while (next < mDisplayChars.length()) {
+ StyleCode newStyle = null;
+ if (mMidRowStyles[next] != null) {
+ // apply mid-row style change
+ newStyle = mMidRowStyles[next];
+ } else if (mPACStyles[next] != null
+ && (styleStart < 0 || start < 0)) {
+ // apply PAC style change, only if:
+ // 1. no style set, or
+ // 2. style set, but prev char is none-displayable
+ newStyle = mPACStyles[next];
+ }
+ if (newStyle != null) {
+ curStyle = newStyle;
+ if (styleStart >= 0 && start >= 0) {
+ applyStyleSpan(styledText, newStyle, styleStart, next);
+ }
+ styleStart = next;
+ }
+
+ if (mDisplayChars.charAt(next) != TS) {
+ if (start < 0) {
+ start = next;
+ }
+ } else if (start >= 0) {
+ int expandedStart = mDisplayChars.charAt(start) == ' ' ? start : start - 1;
+ int expandedEnd = mDisplayChars.charAt(next - 1) == ' ' ? next : next + 1;
+ styledText.setSpan(
+ new MutableBackgroundColorSpan(captionStyle.backgroundColor),
+ expandedStart, expandedEnd,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ if (styleStart >= 0) {
+ applyStyleSpan(styledText, curStyle, styleStart, expandedEnd);
+ }
+ start = -1;
+ }
+ next++;
+ }
+
+ return styledText;
+ }
+ }
+
+ /*
+ * CCMemory models a console-style display.
+ */
+ private static class CCMemory {
+ private final String mBlankLine;
+ private final CCLineBuilder[] mLines = new CCLineBuilder[MAX_ROWS + 2];
+ private int mRow;
+ private int mCol;
+
+ CCMemory() {
+ char[] blank = new char[MAX_COLS + 2];
+ Arrays.fill(blank, TS);
+ mBlankLine = new String(blank);
+ }
+
+ void erase() {
+ // erase all lines
+ for (int i = 0; i < mLines.length; i++) {
+ mLines[i] = null;
+ }
+ mRow = MAX_ROWS;
+ mCol = 1;
+ }
+
+ void der() {
+ if (mLines[mRow] != null) {
+ for (int i = 0; i < mCol; i++) {
+ if (mLines[mRow].charAt(i) != TS) {
+ for (int j = mCol; j < mLines[mRow].length(); j++) {
+ mLines[j].setCharAt(j, TS);
+ }
+ return;
+ }
+ }
+ mLines[mRow] = null;
+ }
+ }
+
+ void tab(int tabs) {
+ moveCursorByCol(tabs);
+ }
+
+ void bs() {
+ moveCursorByCol(-1);
+ if (mLines[mRow] != null) {
+ mLines[mRow].setCharAt(mCol, TS);
+ if (mCol == MAX_COLS - 1) {
+ // Spec recommendation:
+ // if cursor was at col 32, move cursor
+ // back to col 31 and erase both col 31&32
+ mLines[mRow].setCharAt(MAX_COLS, TS);
+ }
+ }
+ }
+
+ void cr() {
+ moveCursorTo(mRow + 1, 1);
+ }
+
+ void rollUp(int windowSize) {
+ int i;
+ for (i = 0; i <= mRow - windowSize; i++) {
+ mLines[i] = null;
+ }
+ int startRow = mRow - windowSize + 1;
+ if (startRow < 1) {
+ startRow = 1;
+ }
+ for (i = startRow; i < mRow; i++) {
+ mLines[i] = mLines[i + 1];
+ }
+ for (i = mRow; i < mLines.length; i++) {
+ // clear base row
+ mLines[i] = null;
+ }
+ // default to col 1, in case PAC is not sent
+ mCol = 1;
+ }
+
+ void writeText(String text) {
+ for (int i = 0; i < text.length(); i++) {
+ getLineBuffer(mRow).setCharAt(mCol, text.charAt(i));
+ moveCursorByCol(1);
+ }
+ }
+
+ void writeMidRowCode(StyleCode m) {
+ getLineBuffer(mRow).setMidRowAt(mCol, m);
+ moveCursorByCol(1);
+ }
+
+ void writePAC(PAC pac) {
+ if (pac.isIndentPAC()) {
+ moveCursorTo(pac.getRow(), pac.getCol());
+ } else {
+ moveCursorTo(pac.getRow(), 1);
+ }
+ getLineBuffer(mRow).setPACAt(mCol, pac);
+ }
+
+ SpannableStringBuilder[] getStyledText(CaptionStyle captionStyle) {
+ ArrayList<SpannableStringBuilder> rows = new ArrayList<>(MAX_ROWS);
+ for (int i = 1; i <= MAX_ROWS; i++) {
+ rows.add(mLines[i] != null ?
+ mLines[i].getStyledText(captionStyle) : null);
+ }
+ return rows.toArray(new SpannableStringBuilder[MAX_ROWS]);
+ }
+
+ private static int clamp(int x, int min, int max) {
+ return x < min ? min : (x > max ? max : x);
+ }
+
+ private void moveCursorTo(int row, int col) {
+ mRow = clamp(row, 1, MAX_ROWS);
+ mCol = clamp(col, 1, MAX_COLS);
+ }
+
+ private void moveCursorToRow(int row) {
+ mRow = clamp(row, 1, MAX_ROWS);
+ }
+
+ private void moveCursorByCol(int col) {
+ mCol = clamp(mCol + col, 1, MAX_COLS);
+ }
+
+ private void moveBaselineTo(int baseRow, int windowSize) {
+ if (mRow == baseRow) {
+ return;
+ }
+ int actualWindowSize = windowSize;
+ if (baseRow < actualWindowSize) {
+ actualWindowSize = baseRow;
+ }
+ if (mRow < actualWindowSize) {
+ actualWindowSize = mRow;
+ }
+
+ int i;
+ if (baseRow < mRow) {
+ // copy from bottom to top row
+ for (i = actualWindowSize - 1; i >= 0; i--) {
+ mLines[baseRow - i] = mLines[mRow - i];
+ }
+ } else {
+ // copy from top to bottom row
+ for (i = 0; i < actualWindowSize; i++) {
+ mLines[baseRow - i] = mLines[mRow - i];
+ }
+ }
+ // clear rest of the rows
+ for (i = 0; i <= baseRow - windowSize; i++) {
+ mLines[i] = null;
+ }
+ for (i = baseRow + 1; i < mLines.length; i++) {
+ mLines[i] = null;
+ }
+ }
+
+ private CCLineBuilder getLineBuffer(int row) {
+ if (mLines[row] == null) {
+ mLines[row] = new CCLineBuilder(mBlankLine);
+ }
+ return mLines[row];
+ }
+ }
+
+ /*
+ * CCData parses the raw CC byte pair into displayable chars,
+ * misc control codes, Mid-Row or Preamble Address Codes.
+ */
+ private static class CCData {
+ private final byte mType;
+ private final byte mData1;
+ private final byte mData2;
+
+ private static final String[] mCtrlCodeMap = {
+ "RCL", "BS" , "AOF", "AON",
+ "DER", "RU2", "RU3", "RU4",
+ "FON", "RDC", "TR" , "RTD",
+ "EDM", "CR" , "ENM", "EOC",
+ };
+
+ private static final String[] mSpecialCharMap = {
+ "\u00AE",
+ "\u00B0",
+ "\u00BD",
+ "\u00BF",
+ "\u2122",
+ "\u00A2",
+ "\u00A3",
+ "\u266A", // Eighth note
+ "\u00E0",
+ "\u00A0", // Transparent space
+ "\u00E8",
+ "\u00E2",
+ "\u00EA",
+ "\u00EE",
+ "\u00F4",
+ "\u00FB",
+ };
+
+ private static final String[] mSpanishCharMap = {
+ // Spanish and misc chars
+ "\u00C1", // A
+ "\u00C9", // E
+ "\u00D3", // I
+ "\u00DA", // O
+ "\u00DC", // U
+ "\u00FC", // u
+ "\u2018", // opening single quote
+ "\u00A1", // inverted exclamation mark
+ "*",
+ "'",
+ "\u2014", // em dash
+ "\u00A9", // Copyright
+ "\u2120", // Servicemark
+ "\u2022", // round bullet
+ "\u201C", // opening double quote
+ "\u201D", // closing double quote
+ // French
+ "\u00C0",
+ "\u00C2",
+ "\u00C7",
+ "\u00C8",
+ "\u00CA",
+ "\u00CB",
+ "\u00EB",
+ "\u00CE",
+ "\u00CF",
+ "\u00EF",
+ "\u00D4",
+ "\u00D9",
+ "\u00F9",
+ "\u00DB",
+ "\u00AB",
+ "\u00BB"
+ };
+
+ private static final String[] mProtugueseCharMap = {
+ // Portuguese
+ "\u00C3",
+ "\u00E3",
+ "\u00CD",
+ "\u00CC",
+ "\u00EC",
+ "\u00D2",
+ "\u00F2",
+ "\u00D5",
+ "\u00F5",
+ "{",
+ "}",
+ "\\",
+ "^",
+ "_",
+ "|",
+ "~",
+ // German and misc chars
+ "\u00C4",
+ "\u00E4",
+ "\u00D6",
+ "\u00F6",
+ "\u00DF",
+ "\u00A5",
+ "\u00A4",
+ "\u2502", // vertical bar
+ "\u00C5",
+ "\u00E5",
+ "\u00D8",
+ "\u00F8",
+ "\u250C", // top-left corner
+ "\u2510", // top-right corner
+ "\u2514", // lower-left corner
+ "\u2518", // lower-right corner
+ };
+
+ static CCData[] fromByteArray(byte[] data) {
+ CCData[] ccData = new CCData[data.length / 3];
+
+ for (int i = 0; i < ccData.length; i++) {
+ ccData[i] = new CCData(
+ data[i * 3],
+ data[i * 3 + 1],
+ data[i * 3 + 2]);
+ }
+
+ return ccData;
+ }
+
+ CCData(byte type, byte data1, byte data2) {
+ mType = type;
+ mData1 = data1;
+ mData2 = data2;
+ }
+
+ int getCtrlCode() {
+ if ((mData1 == 0x14 || mData1 == 0x1c)
+ && mData2 >= 0x20 && mData2 <= 0x2f) {
+ return mData2;
+ }
+ return INVALID;
+ }
+
+ StyleCode getMidRow() {
+ // only support standard Mid-row codes, ignore
+ // optional background/foreground mid-row codes
+ if ((mData1 == 0x11 || mData1 == 0x19)
+ && mData2 >= 0x20 && mData2 <= 0x2f) {
+ return StyleCode.fromByte(mData2);
+ }
+ return null;
+ }
+
+ PAC getPAC() {
+ if ((mData1 & 0x70) == 0x10
+ && (mData2 & 0x40) == 0x40
+ && ((mData1 & 0x07) != 0 || (mData2 & 0x20) == 0)) {
+ return PAC.fromBytes(mData1, mData2);
+ }
+ return null;
+ }
+
+ int getTabOffset() {
+ if ((mData1 == 0x17 || mData1 == 0x1f)
+ && mData2 >= 0x21 && mData2 <= 0x23) {
+ return mData2 & 0x3;
+ }
+ return 0;
+ }
+
+ boolean isDisplayableChar() {
+ return isBasicChar() || isSpecialChar() || isExtendedChar();
+ }
+
+ String getDisplayText() {
+ String str = getBasicChars();
+
+ if (str == null) {
+ str = getSpecialChar();
+
+ if (str == null) {
+ str = getExtendedChar();
+ }
+ }
+
+ return str;
+ }
+
+ private String ctrlCodeToString(int ctrlCode) {
+ return mCtrlCodeMap[ctrlCode - 0x20];
+ }
+
+ private boolean isBasicChar() {
+ return mData1 >= 0x20 && mData1 <= 0x7f;
+ }
+
+ private boolean isSpecialChar() {
+ return ((mData1 == 0x11 || mData1 == 0x19)
+ && mData2 >= 0x30 && mData2 <= 0x3f);
+ }
+
+ private boolean isExtendedChar() {
+ return ((mData1 == 0x12 || mData1 == 0x1A
+ || mData1 == 0x13 || mData1 == 0x1B)
+ && mData2 >= 0x20 && mData2 <= 0x3f);
+ }
+
+ private char getBasicChar(byte data) {
+ char c;
+ // replace the non-ASCII ones
+ switch (data) {
+ case 0x2A: c = '\u00E1'; break;
+ case 0x5C: c = '\u00E9'; break;
+ case 0x5E: c = '\u00ED'; break;
+ case 0x5F: c = '\u00F3'; break;
+ case 0x60: c = '\u00FA'; break;
+ case 0x7B: c = '\u00E7'; break;
+ case 0x7C: c = '\u00F7'; break;
+ case 0x7D: c = '\u00D1'; break;
+ case 0x7E: c = '\u00F1'; break;
+ case 0x7F: c = '\u2588'; break; // Full block
+ default: c = (char) data; break;
+ }
+ return c;
+ }
+
+ private String getBasicChars() {
+ if (mData1 >= 0x20 && mData1 <= 0x7f) {
+ StringBuilder builder = new StringBuilder(2);
+ builder.append(getBasicChar(mData1));
+ if (mData2 >= 0x20 && mData2 <= 0x7f) {
+ builder.append(getBasicChar(mData2));
+ }
+ return builder.toString();
+ }
+
+ return null;
+ }
+
+ private String getSpecialChar() {
+ if ((mData1 == 0x11 || mData1 == 0x19)
+ && mData2 >= 0x30 && mData2 <= 0x3f) {
+ return mSpecialCharMap[mData2 - 0x30];
+ }
+
+ return null;
+ }
+
+ private String getExtendedChar() {
+ if ((mData1 == 0x12 || mData1 == 0x1A)
+ && mData2 >= 0x20 && mData2 <= 0x3f){
+ // 1 Spanish/French char
+ return mSpanishCharMap[mData2 - 0x20];
+ } else if ((mData1 == 0x13 || mData1 == 0x1B)
+ && mData2 >= 0x20 && mData2 <= 0x3f){
+ // 1 Portuguese/German/Danish char
+ return mProtugueseCharMap[mData2 - 0x20];
+ }
+
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ String str;
+
+ if (mData1 < 0x10 && mData2 < 0x10) {
+ // Null Pad, ignore
+ return String.format("[%d]Null: %02x %02x", mType, mData1, mData2);
+ }
+
+ int ctrlCode = getCtrlCode();
+ if (ctrlCode != INVALID) {
+ return String.format("[%d]%s", mType, ctrlCodeToString(ctrlCode));
+ }
+
+ int tabOffset = getTabOffset();
+ if (tabOffset > 0) {
+ return String.format("[%d]Tab%d", mType, tabOffset);
+ }
+
+ PAC pac = getPAC();
+ if (pac != null) {
+ return String.format("[%d]PAC: %s", mType, pac.toString());
+ }
+
+ StyleCode m = getMidRow();
+ if (m != null) {
+ return String.format("[%d]Mid-row: %s", mType, m.toString());
+ }
+
+ if (isDisplayableChar()) {
+ return String.format("[%d]Displayable: %s (%02x %02x)",
+ mType, getDisplayText(), mData1, mData2);
+ }
+
+ return String.format("[%d]Invalid: %02x %02x", mType, mData1, mData2);
+ }
+ }
+}
+
+/**
+ * Widget capable of rendering CEA-608 closed captions.
+ */
+class Cea608CCWidget extends ClosedCaptionWidget implements Cea608CCParser.DisplayListener {
+ private static final Rect mTextBounds = new Rect();
+ private static final String mDummyText = "1234567890123456789012345678901234";
+
+ public Cea608CCWidget(Context context) {
+ this(context, null);
+ }
+
+ public Cea608CCWidget(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public Cea608CCWidget(Context context, AttributeSet attrs, int defStyle) {
+ this(context, attrs, defStyle, 0);
+ }
+
+ public Cea608CCWidget(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public ClosedCaptionLayout createCaptionLayout(Context context) {
+ return new CCLayout(context);
+ }
+
+ @Override
+ public void onDisplayChanged(SpannableStringBuilder[] styledTexts) {
+ ((CCLayout) mClosedCaptionLayout).update(styledTexts);
+
+ if (mListener != null) {
+ mListener.onChanged(this);
+ }
+ }
+
+ @Override
+ public CaptionStyle getCaptionStyle() {
+ return mCaptionStyle;
+ }
+
+ private static class CCLineBox extends TextView {
+ private static final float FONT_PADDING_RATIO = 0.75f;
+ private static final float EDGE_OUTLINE_RATIO = 0.1f;
+ private static final float EDGE_SHADOW_RATIO = 0.05f;
+ private float mOutlineWidth;
+ private float mShadowRadius;
+ private float mShadowOffset;
+
+ private int mTextColor = Color.WHITE;
+ private int mBgColor = Color.BLACK;
+ private int mEdgeType = CaptionStyle.EDGE_TYPE_NONE;
+ private int mEdgeColor = Color.TRANSPARENT;
+
+ CCLineBox(Context context) {
+ super(context);
+ setGravity(Gravity.CENTER);
+ setBackgroundColor(Color.TRANSPARENT);
+ setTextColor(Color.WHITE);
+ setTypeface(Typeface.MONOSPACE);
+ setVisibility(View.INVISIBLE);
+
+ final Resources res = getContext().getResources();
+
+ // get the default (will be updated later during measure)
+ mOutlineWidth = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.subtitle_outline_width);
+ mShadowRadius = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.subtitle_shadow_radius);
+ mShadowOffset = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.subtitle_shadow_offset);
+ }
+
+ void setCaptionStyle(CaptionStyle captionStyle) {
+ mTextColor = captionStyle.foregroundColor;
+ mBgColor = captionStyle.backgroundColor;
+ mEdgeType = captionStyle.edgeType;
+ mEdgeColor = captionStyle.edgeColor;
+
+ setTextColor(mTextColor);
+ if (mEdgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
+ setShadowLayer(mShadowRadius, mShadowOffset, mShadowOffset, mEdgeColor);
+ } else {
+ setShadowLayer(0, 0, 0, 0);
+ }
+ invalidate();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ float fontSize = MeasureSpec.getSize(heightMeasureSpec) * FONT_PADDING_RATIO;
+ setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize);
+
+ mOutlineWidth = EDGE_OUTLINE_RATIO * fontSize + 1.0f;
+ mShadowRadius = EDGE_SHADOW_RATIO * fontSize + 1.0f;;
+ mShadowOffset = mShadowRadius;
+
+ // set font scale in the X direction to match the required width
+ setScaleX(1.0f);
+ getPaint().getTextBounds(mDummyText, 0, mDummyText.length(), mTextBounds);
+ float actualTextWidth = mTextBounds.width();
+ float requiredTextWidth = MeasureSpec.getSize(widthMeasureSpec);
+ setScaleX(requiredTextWidth / actualTextWidth);
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ protected void onDraw(Canvas c) {
+ if (mEdgeType == CaptionStyle.EDGE_TYPE_UNSPECIFIED
+ || mEdgeType == CaptionStyle.EDGE_TYPE_NONE
+ || mEdgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
+ // these edge styles don't require a second pass
+ super.onDraw(c);
+ return;
+ }
+
+ if (mEdgeType == CaptionStyle.EDGE_TYPE_OUTLINE) {
+ drawEdgeOutline(c);
+ } else {
+ // Raised or depressed
+ drawEdgeRaisedOrDepressed(c);
+ }
+ }
+
+ private void drawEdgeOutline(Canvas c) {
+ TextPaint textPaint = getPaint();
+
+ Paint.Style previousStyle = textPaint.getStyle();
+ Paint.Join previousJoin = textPaint.getStrokeJoin();
+ float previousWidth = textPaint.getStrokeWidth();
+
+ setTextColor(mEdgeColor);
+ textPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ textPaint.setStrokeJoin(Paint.Join.ROUND);
+ textPaint.setStrokeWidth(mOutlineWidth);
+
+ // Draw outline and background only.
+ super.onDraw(c);
+
+ // Restore original settings.
+ setTextColor(mTextColor);
+ textPaint.setStyle(previousStyle);
+ textPaint.setStrokeJoin(previousJoin);
+ textPaint.setStrokeWidth(previousWidth);
+
+ // Remove the background.
+ setBackgroundSpans(Color.TRANSPARENT);
+ // Draw foreground only.
+ super.onDraw(c);
+ // Restore the background.
+ setBackgroundSpans(mBgColor);
+ }
+
+ private void drawEdgeRaisedOrDepressed(Canvas c) {
+ TextPaint textPaint = getPaint();
+
+ Paint.Style previousStyle = textPaint.getStyle();
+ textPaint.setStyle(Paint.Style.FILL);
+
+ final boolean raised = mEdgeType == CaptionStyle.EDGE_TYPE_RAISED;
+ final int colorUp = raised ? Color.WHITE : mEdgeColor;
+ final int colorDown = raised ? mEdgeColor : Color.WHITE;
+ final float offset = mShadowRadius / 2f;
+
+ // Draw background and text with shadow up
+ setShadowLayer(mShadowRadius, -offset, -offset, colorUp);
+ super.onDraw(c);
+
+ // Remove the background.
+ setBackgroundSpans(Color.TRANSPARENT);
+
+ // Draw text with shadow down
+ setShadowLayer(mShadowRadius, +offset, +offset, colorDown);
+ super.onDraw(c);
+
+ // Restore settings
+ textPaint.setStyle(previousStyle);
+
+ // Restore the background.
+ setBackgroundSpans(mBgColor);
+ }
+
+ private void setBackgroundSpans(int color) {
+ CharSequence text = getText();
+ if (text instanceof Spannable) {
+ Spannable spannable = (Spannable) text;
+ Cea608CCParser.MutableBackgroundColorSpan[] bgSpans = spannable.getSpans(
+ 0, spannable.length(), Cea608CCParser.MutableBackgroundColorSpan.class);
+ for (int i = 0; i < bgSpans.length; i++) {
+ bgSpans[i].setBackgroundColor(color);
+ }
+ }
+ }
+ }
+
+ private static class CCLayout extends LinearLayout implements ClosedCaptionLayout {
+ private static final int MAX_ROWS = Cea608CCParser.MAX_ROWS;
+ private static final float SAFE_AREA_RATIO = 0.9f;
+
+ private final CCLineBox[] mLineBoxes = new CCLineBox[MAX_ROWS];
+
+ CCLayout(Context context) {
+ super(context);
+ setGravity(Gravity.START);
+ setOrientation(LinearLayout.VERTICAL);
+ for (int i = 0; i < MAX_ROWS; i++) {
+ mLineBoxes[i] = new CCLineBox(getContext());
+ addView(mLineBoxes[i], LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ }
+ }
+
+ @Override
+ public void setCaptionStyle(CaptionStyle captionStyle) {
+ for (int i = 0; i < MAX_ROWS; i++) {
+ mLineBoxes[i].setCaptionStyle(captionStyle);
+ }
+ }
+
+ @Override
+ public void setFontScale(float fontScale) {
+ // Ignores the font scale changes of the system wide CC preference.
+ }
+
+ void update(SpannableStringBuilder[] textBuffer) {
+ for (int i = 0; i < MAX_ROWS; i++) {
+ if (textBuffer[i] != null) {
+ mLineBoxes[i].setText(textBuffer[i], TextView.BufferType.SPANNABLE);
+ mLineBoxes[i].setVisibility(View.VISIBLE);
+ } else {
+ mLineBoxes[i].setVisibility(View.INVISIBLE);
+ }
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int safeWidth = getMeasuredWidth();
+ int safeHeight = getMeasuredHeight();
+
+ // CEA-608 assumes 4:3 video
+ if (safeWidth * 3 >= safeHeight * 4) {
+ safeWidth = safeHeight * 4 / 3;
+ } else {
+ safeHeight = safeWidth * 3 / 4;
+ }
+ safeWidth *= SAFE_AREA_RATIO;
+ safeHeight *= SAFE_AREA_RATIO;
+
+ int lineHeight = safeHeight / MAX_ROWS;
+ int lineHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ lineHeight, MeasureSpec.EXACTLY);
+ int lineWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+ safeWidth, MeasureSpec.EXACTLY);
+
+ for (int i = 0; i < MAX_ROWS; i++) {
+ mLineBoxes[i].measure(lineWidthMeasureSpec, lineHeightMeasureSpec);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ // safe caption area
+ int viewPortWidth = r - l;
+ int viewPortHeight = b - t;
+ int safeWidth, safeHeight;
+ // CEA-608 assumes 4:3 video
+ if (viewPortWidth * 3 >= viewPortHeight * 4) {
+ safeWidth = viewPortHeight * 4 / 3;
+ safeHeight = viewPortHeight;
+ } else {
+ safeWidth = viewPortWidth;
+ safeHeight = viewPortWidth * 3 / 4;
+ }
+ safeWidth *= SAFE_AREA_RATIO;
+ safeHeight *= SAFE_AREA_RATIO;
+ int left = (viewPortWidth - safeWidth) / 2;
+ int top = (viewPortHeight - safeHeight) / 2;
+
+ for (int i = 0; i < MAX_ROWS; i++) {
+ mLineBoxes[i].layout(
+ left,
+ top + safeHeight * i / MAX_ROWS,
+ left + safeWidth,
+ top + safeHeight * (i + 1) / MAX_ROWS);
+ }
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/media/subtitle/MediaTimeProvider.java b/packages/MediaComponents/src/com/android/media/subtitle/MediaTimeProvider.java
new file mode 100644
index 0000000..af36d7f
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/subtitle/MediaTimeProvider.java
@@ -0,0 +1,89 @@
+/*
+ * 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.subtitle;
+
+// Note: This is just copied from android.media.MediaTimeProvider.
+public interface MediaTimeProvider {
+ // we do not allow negative media time
+ /**
+ * Presentation time value if no timed event notification is requested.
+ */
+ public final static long NO_TIME = -1;
+
+ /**
+ * Cancels all previous notification request from this listener if any. It
+ * registers the listener to get seek and stop notifications. If timeUs is
+ * not negative, it also registers the listener for a timed event
+ * notification when the presentation time reaches (becomes greater) than
+ * the value specified. This happens immediately if the current media time
+ * is larger than or equal to timeUs.
+ *
+ * @param timeUs presentation time to get timed event callback at (or
+ * {@link #NO_TIME})
+ */
+ public void notifyAt(long timeUs, OnMediaTimeListener listener);
+
+ /**
+ * Cancels all previous notification request from this listener if any. It
+ * registers the listener to get seek and stop notifications. If the media
+ * is stopped, the listener will immediately receive a stop notification.
+ * Otherwise, it will receive a timed event notificaton.
+ */
+ public void scheduleUpdate(OnMediaTimeListener listener);
+
+ /**
+ * Cancels all previous notification request from this listener if any.
+ */
+ public void cancelNotifications(OnMediaTimeListener listener);
+
+ /**
+ * Get the current presentation time.
+ *
+ * @param precise Whether getting a precise time is important. This is
+ * more costly.
+ * @param monotonic Whether returned time should be monotonic: that is,
+ * greater than or equal to the last returned time. Don't
+ * always set this to true. E.g. this has undesired
+ * consequences if the media is seeked between calls.
+ * @throws IllegalStateException if the media is not initialized
+ */
+ public long getCurrentTimeUs(boolean precise, boolean monotonic)
+ throws IllegalStateException;
+
+ public static interface OnMediaTimeListener {
+ /**
+ * Called when the registered time was reached naturally.
+ *
+ * @param timeUs current media time
+ */
+ void onTimedEvent(long timeUs);
+
+ /**
+ * Called when the media time changed due to seeking.
+ *
+ * @param timeUs current media time
+ */
+ void onSeek(long timeUs);
+
+ /**
+ * Called when the playback stopped. This is not called on pause, only
+ * on full stop, at which point there is no further current media time.
+ */
+ void onStop();
+ }
+}
+
diff --git a/packages/MediaComponents/src/com/android/media/subtitle/SubtitleController.java b/packages/MediaComponents/src/com/android/media/subtitle/SubtitleController.java
new file mode 100644
index 0000000..a4d55d7
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/subtitle/SubtitleController.java
@@ -0,0 +1,508 @@
+/*
+ * 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.subtitle;
+
+import java.util.Locale;
+import java.util.Vector;
+
+import android.content.Context;
+import android.media.MediaFormat;
+import android.media.MediaPlayer2;
+import android.media.MediaPlayer2.TrackInfo;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.view.accessibility.CaptioningManager;
+
+import com.android.media.subtitle.SubtitleTrack.RenderingWidget;
+
+// Note: This is forked from android.media.SubtitleController since P
+/**
+ * The subtitle controller provides the architecture to display subtitles for a
+ * media source. It allows specifying which tracks to display, on which anchor
+ * to display them, and also allows adding external, out-of-band subtitle tracks.
+ */
+public class SubtitleController {
+ private MediaTimeProvider mTimeProvider;
+ private Vector<Renderer> mRenderers;
+ private Vector<SubtitleTrack> mTracks;
+ private SubtitleTrack mSelectedTrack;
+ private boolean mShowing;
+ private CaptioningManager mCaptioningManager;
+ private Handler mHandler;
+
+ private static final int WHAT_SHOW = 1;
+ private static final int WHAT_HIDE = 2;
+ private static final int WHAT_SELECT_TRACK = 3;
+ private static final int WHAT_SELECT_DEFAULT_TRACK = 4;
+
+ private final Handler.Callback mCallback = new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case WHAT_SHOW:
+ doShow();
+ return true;
+ case WHAT_HIDE:
+ doHide();
+ return true;
+ case WHAT_SELECT_TRACK:
+ doSelectTrack((SubtitleTrack)msg.obj);
+ return true;
+ case WHAT_SELECT_DEFAULT_TRACK:
+ doSelectDefaultTrack();
+ return true;
+ default:
+ return false;
+ }
+ }
+ };
+
+ private CaptioningManager.CaptioningChangeListener mCaptioningChangeListener =
+ new CaptioningManager.CaptioningChangeListener() {
+ @Override
+ public void onEnabledChanged(boolean enabled) {
+ selectDefaultTrack();
+ }
+
+ @Override
+ public void onLocaleChanged(Locale locale) {
+ selectDefaultTrack();
+ }
+ };
+
+ public SubtitleController(Context context) {
+ this(context, null, null);
+ }
+
+ /**
+ * Creates a subtitle controller for a media playback object that implements
+ * the MediaTimeProvider interface.
+ *
+ * @param timeProvider
+ */
+ public SubtitleController(
+ Context context,
+ MediaTimeProvider timeProvider,
+ Listener listener) {
+ mTimeProvider = timeProvider;
+ mListener = listener;
+
+ mRenderers = new Vector<Renderer>();
+ mShowing = false;
+ mTracks = new Vector<SubtitleTrack>();
+ mCaptioningManager =
+ (CaptioningManager)context.getSystemService(Context.CAPTIONING_SERVICE);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ mCaptioningManager.removeCaptioningChangeListener(
+ mCaptioningChangeListener);
+ super.finalize();
+ }
+
+ /**
+ * @return the available subtitle tracks for this media. These include
+ * the tracks found by {@link MediaPlayer} as well as any tracks added
+ * manually via {@link #addTrack}.
+ */
+ public SubtitleTrack[] getTracks() {
+ synchronized(mTracks) {
+ SubtitleTrack[] tracks = new SubtitleTrack[mTracks.size()];
+ mTracks.toArray(tracks);
+ return tracks;
+ }
+ }
+
+ /**
+ * @return the currently selected subtitle track
+ */
+ public SubtitleTrack getSelectedTrack() {
+ return mSelectedTrack;
+ }
+
+ private RenderingWidget getRenderingWidget() {
+ if (mSelectedTrack == null) {
+ return null;
+ }
+ return mSelectedTrack.getRenderingWidget();
+ }
+
+ /**
+ * Selects a subtitle track. As a result, this track will receive
+ * in-band data from the {@link MediaPlayer}. However, this does
+ * not change the subtitle visibility.
+ *
+ * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper}
+ *
+ * @param track The subtitle track to select. This must be one of the
+ * tracks in {@link #getTracks}.
+ * @return true if the track was successfully selected.
+ */
+ public boolean selectTrack(SubtitleTrack track) {
+ if (track != null && !mTracks.contains(track)) {
+ return false;
+ }
+
+ processOnAnchor(mHandler.obtainMessage(WHAT_SELECT_TRACK, track));
+ return true;
+ }
+
+ private void doSelectTrack(SubtitleTrack track) {
+ mTrackIsExplicit = true;
+ if (mSelectedTrack == track) {
+ return;
+ }
+
+ if (mSelectedTrack != null) {
+ mSelectedTrack.hide();
+ mSelectedTrack.setTimeProvider(null);
+ }
+
+ mSelectedTrack = track;
+ if (mAnchor != null) {
+ mAnchor.setSubtitleWidget(getRenderingWidget());
+ }
+
+ if (mSelectedTrack != null) {
+ mSelectedTrack.setTimeProvider(mTimeProvider);
+ mSelectedTrack.show();
+ }
+
+ if (mListener != null) {
+ mListener.onSubtitleTrackSelected(track);
+ }
+ }
+
+ /**
+ * @return the default subtitle track based on system preferences, or null,
+ * if no such track exists in this manager.
+ *
+ * Supports HLS-flags: AUTOSELECT, FORCED & DEFAULT.
+ *
+ * 1. If captioning is disabled, only consider FORCED tracks. Otherwise,
+ * consider all tracks, but prefer non-FORCED ones.
+ * 2. If user selected "Default" caption language:
+ * a. If there is a considered track with DEFAULT=yes, returns that track
+ * (favor the first one in the current language if there are more than
+ * one default tracks, or the first in general if none of them are in
+ * the current language).
+ * b. Otherwise, if there is a track with AUTOSELECT=yes in the current
+ * language, return that one.
+ * c. If there are no default tracks, and no autoselectable tracks in the
+ * current language, return null.
+ * 3. If there is a track with the caption language, select that one. Prefer
+ * the one with AUTOSELECT=no.
+ *
+ * The default values for these flags are DEFAULT=no, AUTOSELECT=yes
+ * and FORCED=no.
+ */
+ public SubtitleTrack getDefaultTrack() {
+ SubtitleTrack bestTrack = null;
+ int bestScore = -1;
+
+ Locale selectedLocale = mCaptioningManager.getLocale();
+ Locale locale = selectedLocale;
+ if (locale == null) {
+ locale = Locale.getDefault();
+ }
+ boolean selectForced = !mCaptioningManager.isEnabled();
+
+ synchronized(mTracks) {
+ for (SubtitleTrack track: mTracks) {
+ MediaFormat format = track.getFormat();
+ String language = format.getString(MediaFormat.KEY_LANGUAGE);
+ boolean forced =
+ format.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, 0) != 0;
+ boolean autoselect =
+ format.getInteger(MediaFormat.KEY_IS_AUTOSELECT, 1) != 0;
+ boolean is_default =
+ format.getInteger(MediaFormat.KEY_IS_DEFAULT, 0) != 0;
+
+ boolean languageMatches =
+ (locale == null ||
+ locale.getLanguage().equals("") ||
+ locale.getISO3Language().equals(language) ||
+ locale.getLanguage().equals(language));
+ // is_default is meaningless unless caption language is 'default'
+ int score = (forced ? 0 : 8) +
+ (((selectedLocale == null) && is_default) ? 4 : 0) +
+ (autoselect ? 0 : 2) + (languageMatches ? 1 : 0);
+
+ if (selectForced && !forced) {
+ continue;
+ }
+
+ // we treat null locale/language as matching any language
+ if ((selectedLocale == null && is_default) ||
+ (languageMatches &&
+ (autoselect || forced || selectedLocale != null))) {
+ if (score > bestScore) {
+ bestScore = score;
+ bestTrack = track;
+ }
+ }
+ }
+ }
+ return bestTrack;
+ }
+
+ private boolean mTrackIsExplicit = false;
+ private boolean mVisibilityIsExplicit = false;
+
+ /** should be called from anchor thread */
+ public void selectDefaultTrack() {
+ processOnAnchor(mHandler.obtainMessage(WHAT_SELECT_DEFAULT_TRACK));
+ }
+
+ private void doSelectDefaultTrack() {
+ if (mTrackIsExplicit) {
+ // If track selection is explicit, but visibility
+ // is not, it falls back to the captioning setting
+ if (!mVisibilityIsExplicit) {
+ if (mCaptioningManager.isEnabled() ||
+ (mSelectedTrack != null &&
+ mSelectedTrack.getFormat().getInteger(
+ MediaFormat.KEY_IS_FORCED_SUBTITLE, 0) != 0)) {
+ show();
+ } else if (mSelectedTrack != null
+ && mSelectedTrack.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
+ hide();
+ }
+ mVisibilityIsExplicit = false;
+ }
+ return;
+ }
+
+ // We can have a default (forced) track even if captioning
+ // is not enabled. This is handled by getDefaultTrack().
+ // Show this track unless subtitles were explicitly hidden.
+ SubtitleTrack track = getDefaultTrack();
+ if (track != null) {
+ selectTrack(track);
+ mTrackIsExplicit = false;
+ if (!mVisibilityIsExplicit) {
+ show();
+ mVisibilityIsExplicit = false;
+ }
+ }
+ }
+
+ /** must be called from anchor thread */
+ public void reset() {
+ checkAnchorLooper();
+ hide();
+ selectTrack(null);
+ mTracks.clear();
+ mTrackIsExplicit = false;
+ mVisibilityIsExplicit = false;
+ mCaptioningManager.removeCaptioningChangeListener(
+ mCaptioningChangeListener);
+ }
+
+ /**
+ * Adds a new, external subtitle track to the manager.
+ *
+ * @param format the format of the track that will include at least
+ * the MIME type {@link MediaFormat@KEY_MIME}.
+ * @return the created {@link SubtitleTrack} object
+ */
+ public SubtitleTrack addTrack(MediaFormat format) {
+ synchronized(mRenderers) {
+ for (Renderer renderer: mRenderers) {
+ if (renderer.supports(format)) {
+ SubtitleTrack track = renderer.createTrack(format);
+ if (track != null) {
+ synchronized(mTracks) {
+ if (mTracks.size() == 0) {
+ mCaptioningManager.addCaptioningChangeListener(
+ mCaptioningChangeListener);
+ }
+ mTracks.add(track);
+ }
+ return track;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Show the selected (or default) subtitle track.
+ *
+ * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper}
+ */
+ public void show() {
+ processOnAnchor(mHandler.obtainMessage(WHAT_SHOW));
+ }
+
+ private void doShow() {
+ mShowing = true;
+ mVisibilityIsExplicit = true;
+ if (mSelectedTrack != null) {
+ mSelectedTrack.show();
+ }
+ }
+
+ /**
+ * Hide the selected (or default) subtitle track.
+ *
+ * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper}
+ */
+ public void hide() {
+ processOnAnchor(mHandler.obtainMessage(WHAT_HIDE));
+ }
+
+ private void doHide() {
+ mVisibilityIsExplicit = true;
+ if (mSelectedTrack != null) {
+ mSelectedTrack.hide();
+ }
+ mShowing = false;
+ }
+
+ /**
+ * Interface for supporting a single or multiple subtitle types in {@link
+ * MediaPlayer}.
+ */
+ public abstract static class Renderer {
+ /**
+ * Called by {@link MediaPlayer}'s {@link SubtitleController} when a new
+ * subtitle track is detected, to see if it should use this object to
+ * parse and display this subtitle track.
+ *
+ * @param format the format of the track that will include at least
+ * the MIME type {@link MediaFormat@KEY_MIME}.
+ *
+ * @return true if and only if the track format is supported by this
+ * renderer
+ */
+ public abstract boolean supports(MediaFormat format);
+
+ /**
+ * Called by {@link MediaPlayer}'s {@link SubtitleController} for each
+ * subtitle track that was detected and is supported by this object to
+ * create a {@link SubtitleTrack} object. This object will be created
+ * for each track that was found. If the track is selected for display,
+ * this object will be used to parse and display the track data.
+ *
+ * @param format the format of the track that will include at least
+ * the MIME type {@link MediaFormat@KEY_MIME}.
+ * @return a {@link SubtitleTrack} object that will be used to parse
+ * and render the subtitle track.
+ */
+ public abstract SubtitleTrack createTrack(MediaFormat format);
+ }
+
+ /**
+ * Add support for a subtitle format in {@link MediaPlayer}.
+ *
+ * @param renderer a {@link SubtitleController.Renderer} object that adds
+ * support for a subtitle format.
+ */
+ public void registerRenderer(Renderer renderer) {
+ synchronized(mRenderers) {
+ // TODO how to get available renderers in the system
+ if (!mRenderers.contains(renderer)) {
+ // TODO should added renderers override existing ones (to allow replacing?)
+ mRenderers.add(renderer);
+ }
+ }
+ }
+
+ public boolean hasRendererFor(MediaFormat format) {
+ synchronized(mRenderers) {
+ // TODO how to get available renderers in the system
+ for (Renderer renderer: mRenderers) {
+ if (renderer.supports(format)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Subtitle anchor, an object that is able to display a subtitle renderer,
+ * e.g. a VideoView.
+ */
+ public interface Anchor {
+ /**
+ * Anchor should use the supplied subtitle rendering widget, or
+ * none if it is null.
+ */
+ public void setSubtitleWidget(RenderingWidget subtitleWidget);
+
+ /**
+ * Anchors provide the looper on which all track visibility changes
+ * (track.show/hide, setSubtitleWidget) will take place.
+ */
+ public Looper getSubtitleLooper();
+ }
+
+ private Anchor mAnchor;
+
+ /**
+ * called from anchor's looper (if any, both when unsetting and
+ * setting)
+ */
+ public void setAnchor(Anchor anchor) {
+ if (mAnchor == anchor) {
+ return;
+ }
+
+ if (mAnchor != null) {
+ checkAnchorLooper();
+ mAnchor.setSubtitleWidget(null);
+ }
+ mAnchor = anchor;
+ mHandler = null;
+ if (mAnchor != null) {
+ mHandler = new Handler(mAnchor.getSubtitleLooper(), mCallback);
+ checkAnchorLooper();
+ mAnchor.setSubtitleWidget(getRenderingWidget());
+ }
+ }
+
+ private void checkAnchorLooper() {
+ assert mHandler != null : "Should have a looper already";
+ assert Looper.myLooper() == mHandler.getLooper()
+ : "Must be called from the anchor's looper";
+ }
+
+ private void processOnAnchor(Message m) {
+ assert mHandler != null : "Should have a looper already";
+ if (Looper.myLooper() == mHandler.getLooper()) {
+ mHandler.dispatchMessage(m);
+ } else {
+ mHandler.sendMessage(m);
+ }
+ }
+
+ public interface Listener {
+ /**
+ * Called when a subtitle track has been selected.
+ *
+ * @param track selected subtitle track or null
+ */
+ public void onSubtitleTrackSelected(SubtitleTrack track);
+ }
+
+ private Listener mListener;
+}
diff --git a/packages/MediaComponents/src/com/android/media/subtitle/SubtitleTrack.java b/packages/MediaComponents/src/com/android/media/subtitle/SubtitleTrack.java
new file mode 100644
index 0000000..6b9064a
--- /dev/null
+++ b/packages/MediaComponents/src/com/android/media/subtitle/SubtitleTrack.java
@@ -0,0 +1,696 @@
+/*
+ * 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.subtitle;
+
+import android.graphics.Canvas;
+import android.media.MediaFormat;
+import android.media.MediaPlayer2.TrackInfo;
+import android.media.SubtitleData;
+import android.os.Handler;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Pair;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.Vector;
+
+// Note: This is forked from android.media.SubtitleTrack since P
+/**
+ * A subtitle track abstract base class that is responsible for parsing and displaying
+ * an instance of a particular type of subtitle.
+ */
+public abstract class SubtitleTrack implements MediaTimeProvider.OnMediaTimeListener {
+ private static final String TAG = "SubtitleTrack";
+ private long mLastUpdateTimeMs;
+ private long mLastTimeMs;
+
+ private Runnable mRunnable;
+
+ final private LongSparseArray<Run> mRunsByEndTime = new LongSparseArray<Run>();
+ final private LongSparseArray<Run> mRunsByID = new LongSparseArray<Run>();
+
+ private CueList mCues;
+ final private Vector<Cue> mActiveCues = new Vector<Cue>();
+ protected boolean mVisible;
+
+ public boolean DEBUG = false;
+
+ protected Handler mHandler = new Handler();
+
+ private MediaFormat mFormat;
+
+ public SubtitleTrack(MediaFormat format) {
+ mFormat = format;
+ mCues = new CueList();
+ clearActiveCues();
+ mLastTimeMs = -1;
+ }
+
+ public final MediaFormat getFormat() {
+ return mFormat;
+ }
+
+ private long mNextScheduledTimeMs = -1;
+
+ public void onData(SubtitleData data) {
+ long runID = data.getStartTimeUs() + 1;
+ onData(data.getData(), true /* eos */, runID);
+ setRunDiscardTimeMs(
+ runID,
+ (data.getStartTimeUs() + data.getDurationUs()) / 1000);
+ }
+
+ /**
+ * Called when there is input data for the subtitle track. The
+ * complete subtitle for a track can include multiple whole units
+ * (runs). Each of these units can have multiple sections. The
+ * contents of a run are submitted in sequential order, with eos
+ * indicating the last section of the run. Calls from different
+ * runs must not be intermixed.
+ *
+ * @param data subtitle data byte buffer
+ * @param eos true if this is the last section of the run.
+ * @param runID mostly-unique ID for this run of data. Subtitle cues
+ * with runID of 0 are discarded immediately after
+ * display. Cues with runID of ~0 are discarded
+ * only at the deletion of the track object. Cues
+ * with other runID-s are discarded at the end of the
+ * run, which defaults to the latest timestamp of
+ * any of its cues (with this runID).
+ */
+ protected abstract void onData(byte[] data, boolean eos, long runID);
+
+ /**
+ * Called when adding the subtitle rendering widget to the view hierarchy,
+ * as well as when showing or hiding the subtitle track, or when the video
+ * surface position has changed.
+ *
+ * @return the widget that renders this subtitle track. For most renderers
+ * there should be a single shared instance that is used for all
+ * tracks supported by that renderer, as at most one subtitle track
+ * is visible at one time.
+ */
+ public abstract RenderingWidget getRenderingWidget();
+
+ /**
+ * Called when the active cues have changed, and the contents of the subtitle
+ * view should be updated.
+ */
+ public abstract void updateView(Vector<Cue> activeCues);
+
+ protected synchronized void updateActiveCues(boolean rebuild, long timeMs) {
+ // out-of-order times mean seeking or new active cues being added
+ // (during their own timespan)
+ if (rebuild || mLastUpdateTimeMs > timeMs) {
+ clearActiveCues();
+ }
+
+ for(Iterator<Pair<Long, Cue> > it =
+ mCues.entriesBetween(mLastUpdateTimeMs, timeMs).iterator(); it.hasNext(); ) {
+ Pair<Long, Cue> event = it.next();
+ Cue cue = event.second;
+
+ if (cue.mEndTimeMs == event.first) {
+ // remove past cues
+ if (DEBUG) Log.v(TAG, "Removing " + cue);
+ mActiveCues.remove(cue);
+ if (cue.mRunID == 0) {
+ it.remove();
+ }
+ } else if (cue.mStartTimeMs == event.first) {
+ // add new cues
+ // TRICKY: this will happen in start order
+ if (DEBUG) Log.v(TAG, "Adding " + cue);
+ if (cue.mInnerTimesMs != null) {
+ cue.onTime(timeMs);
+ }
+ mActiveCues.add(cue);
+ } else if (cue.mInnerTimesMs != null) {
+ // cue is modified
+ cue.onTime(timeMs);
+ }
+ }
+
+ /* complete any runs */
+ while (mRunsByEndTime.size() > 0 &&
+ mRunsByEndTime.keyAt(0) <= timeMs) {
+ removeRunsByEndTimeIndex(0); // removes element
+ }
+ mLastUpdateTimeMs = timeMs;
+ }
+
+ private void removeRunsByEndTimeIndex(int ix) {
+ Run run = mRunsByEndTime.valueAt(ix);
+ while (run != null) {
+ Cue cue = run.mFirstCue;
+ while (cue != null) {
+ mCues.remove(cue);
+ Cue nextCue = cue.mNextInRun;
+ cue.mNextInRun = null;
+ cue = nextCue;
+ }
+ mRunsByID.remove(run.mRunID);
+ Run nextRun = run.mNextRunAtEndTimeMs;
+ run.mPrevRunAtEndTimeMs = null;
+ run.mNextRunAtEndTimeMs = null;
+ run = nextRun;
+ }
+ mRunsByEndTime.removeAt(ix);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ /* remove all cues (untangle all cross-links) */
+ int size = mRunsByEndTime.size();
+ for(int ix = size - 1; ix >= 0; ix--) {
+ removeRunsByEndTimeIndex(ix);
+ }
+
+ super.finalize();
+ }
+
+ private synchronized void takeTime(long timeMs) {
+ mLastTimeMs = timeMs;
+ }
+
+ protected synchronized void clearActiveCues() {
+ if (DEBUG) Log.v(TAG, "Clearing " + mActiveCues.size() + " active cues");
+ mActiveCues.clear();
+ mLastUpdateTimeMs = -1;
+ }
+
+ protected void scheduleTimedEvents() {
+ /* get times for the next event */
+ if (mTimeProvider != null) {
+ mNextScheduledTimeMs = mCues.nextTimeAfter(mLastTimeMs);
+ if (DEBUG) Log.d(TAG, "sched @" + mNextScheduledTimeMs + " after " + mLastTimeMs);
+ mTimeProvider.notifyAt(
+ mNextScheduledTimeMs >= 0 ?
+ (mNextScheduledTimeMs * 1000) : MediaTimeProvider.NO_TIME,
+ this);
+ }
+ }
+
+ @Override
+ public void onTimedEvent(long timeUs) {
+ if (DEBUG) Log.d(TAG, "onTimedEvent " + timeUs);
+ synchronized (this) {
+ long timeMs = timeUs / 1000;
+ updateActiveCues(false, timeMs);
+ takeTime(timeMs);
+ }
+ updateView(mActiveCues);
+ scheduleTimedEvents();
+ }
+
+ @Override
+ public void onSeek(long timeUs) {
+ if (DEBUG) Log.d(TAG, "onSeek " + timeUs);
+ synchronized (this) {
+ long timeMs = timeUs / 1000;
+ updateActiveCues(true, timeMs);
+ takeTime(timeMs);
+ }
+ updateView(mActiveCues);
+ scheduleTimedEvents();
+ }
+
+ @Override
+ public void onStop() {
+ synchronized (this) {
+ if (DEBUG) Log.d(TAG, "onStop");
+ clearActiveCues();
+ mLastTimeMs = -1;
+ }
+ updateView(mActiveCues);
+ mNextScheduledTimeMs = -1;
+ mTimeProvider.notifyAt(MediaTimeProvider.NO_TIME, this);
+ }
+
+ protected MediaTimeProvider mTimeProvider;
+
+ public void show() {
+ if (mVisible) {
+ return;
+ }
+
+ mVisible = true;
+ RenderingWidget renderingWidget = getRenderingWidget();
+ if (renderingWidget != null) {
+ renderingWidget.setVisible(true);
+ }
+ if (mTimeProvider != null) {
+ mTimeProvider.scheduleUpdate(this);
+ }
+ }
+
+ public void hide() {
+ if (!mVisible) {
+ return;
+ }
+
+ if (mTimeProvider != null) {
+ mTimeProvider.cancelNotifications(this);
+ }
+ RenderingWidget renderingWidget = getRenderingWidget();
+ if (renderingWidget != null) {
+ renderingWidget.setVisible(false);
+ }
+ mVisible = false;
+ }
+
+ protected synchronized boolean addCue(Cue cue) {
+ mCues.add(cue);
+
+ if (cue.mRunID != 0) {
+ Run run = mRunsByID.get(cue.mRunID);
+ if (run == null) {
+ run = new Run();
+ mRunsByID.put(cue.mRunID, run);
+ run.mEndTimeMs = cue.mEndTimeMs;
+ } else if (run.mEndTimeMs < cue.mEndTimeMs) {
+ run.mEndTimeMs = cue.mEndTimeMs;
+ }
+
+ // link-up cues in the same run
+ cue.mNextInRun = run.mFirstCue;
+ run.mFirstCue = cue;
+ }
+
+ // if a cue is added that should be visible, need to refresh view
+ long nowMs = -1;
+ if (mTimeProvider != null) {
+ try {
+ nowMs = mTimeProvider.getCurrentTimeUs(
+ false /* precise */, true /* monotonic */) / 1000;
+ } catch (IllegalStateException e) {
+ // handle as it we are not playing
+ }
+ }
+
+ if (DEBUG) Log.v(TAG, "mVisible=" + mVisible + ", " +
+ cue.mStartTimeMs + " <= " + nowMs + ", " +
+ cue.mEndTimeMs + " >= " + mLastTimeMs);
+
+ if (mVisible &&
+ cue.mStartTimeMs <= nowMs &&
+ // we don't trust nowMs, so check any cue since last callback
+ cue.mEndTimeMs >= mLastTimeMs) {
+ if (mRunnable != null) {
+ mHandler.removeCallbacks(mRunnable);
+ }
+ final SubtitleTrack track = this;
+ final long thenMs = nowMs;
+ mRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // even with synchronized, it is possible that we are going
+ // to do multiple updates as the runnable could be already
+ // running.
+ synchronized (track) {
+ mRunnable = null;
+ updateActiveCues(true, thenMs);
+ updateView(mActiveCues);
+ }
+ }
+ };
+ // delay update so we don't update view on every cue. TODO why 10?
+ if (mHandler.postDelayed(mRunnable, 10 /* delay */)) {
+ if (DEBUG) Log.v(TAG, "scheduling update");
+ } else {
+ if (DEBUG) Log.w(TAG, "failed to schedule subtitle view update");
+ }
+ return true;
+ }
+
+ if (mVisible &&
+ cue.mEndTimeMs >= mLastTimeMs &&
+ (cue.mStartTimeMs < mNextScheduledTimeMs ||
+ mNextScheduledTimeMs < 0)) {
+ scheduleTimedEvents();
+ }
+
+ return false;
+ }
+
+ public synchronized void setTimeProvider(MediaTimeProvider timeProvider) {
+ if (mTimeProvider == timeProvider) {
+ return;
+ }
+ if (mTimeProvider != null) {
+ mTimeProvider.cancelNotifications(this);
+ }
+ mTimeProvider = timeProvider;
+ if (mTimeProvider != null) {
+ mTimeProvider.scheduleUpdate(this);
+ }
+ }
+
+
+ static class CueList {
+ private static final String TAG = "CueList";
+ // simplistic, inefficient implementation
+ private SortedMap<Long, Vector<Cue> > mCues;
+ public boolean DEBUG = false;
+
+ private boolean addEvent(Cue cue, long timeMs) {
+ Vector<Cue> cues = mCues.get(timeMs);
+ if (cues == null) {
+ cues = new Vector<Cue>(2);
+ mCues.put(timeMs, cues);
+ } else if (cues.contains(cue)) {
+ // do not duplicate cues
+ return false;
+ }
+
+ cues.add(cue);
+ return true;
+ }
+
+ private void removeEvent(Cue cue, long timeMs) {
+ Vector<Cue> cues = mCues.get(timeMs);
+ if (cues != null) {
+ cues.remove(cue);
+ if (cues.size() == 0) {
+ mCues.remove(timeMs);
+ }
+ }
+ }
+
+ public void add(Cue cue) {
+ // ignore non-positive-duration cues
+ if (cue.mStartTimeMs >= cue.mEndTimeMs)
+ return;
+
+ if (!addEvent(cue, cue.mStartTimeMs)) {
+ return;
+ }
+
+ long lastTimeMs = cue.mStartTimeMs;
+ if (cue.mInnerTimesMs != null) {
+ for (long timeMs: cue.mInnerTimesMs) {
+ if (timeMs > lastTimeMs && timeMs < cue.mEndTimeMs) {
+ addEvent(cue, timeMs);
+ lastTimeMs = timeMs;
+ }
+ }
+ }
+
+ addEvent(cue, cue.mEndTimeMs);
+ }
+
+ public void remove(Cue cue) {
+ removeEvent(cue, cue.mStartTimeMs);
+ if (cue.mInnerTimesMs != null) {
+ for (long timeMs: cue.mInnerTimesMs) {
+ removeEvent(cue, timeMs);
+ }
+ }
+ removeEvent(cue, cue.mEndTimeMs);
+ }
+
+ public Iterable<Pair<Long, Cue>> entriesBetween(
+ final long lastTimeMs, final long timeMs) {
+ return new Iterable<Pair<Long, Cue> >() {
+ @Override
+ public Iterator<Pair<Long, Cue> > iterator() {
+ if (DEBUG) Log.d(TAG, "slice (" + lastTimeMs + ", " + timeMs + "]=");
+ try {
+ return new EntryIterator(
+ mCues.subMap(lastTimeMs + 1, timeMs + 1));
+ } catch(IllegalArgumentException e) {
+ return new EntryIterator(null);
+ }
+ }
+ };
+ }
+
+ public long nextTimeAfter(long timeMs) {
+ SortedMap<Long, Vector<Cue>> tail = null;
+ try {
+ tail = mCues.tailMap(timeMs + 1);
+ if (tail != null) {
+ return tail.firstKey();
+ } else {
+ return -1;
+ }
+ } catch(IllegalArgumentException e) {
+ return -1;
+ } catch(NoSuchElementException e) {
+ return -1;
+ }
+ }
+
+ class EntryIterator implements Iterator<Pair<Long, Cue> > {
+ @Override
+ public boolean hasNext() {
+ return !mDone;
+ }
+
+ @Override
+ public Pair<Long, Cue> next() {
+ if (mDone) {
+ throw new NoSuchElementException("");
+ }
+ mLastEntry = new Pair<Long, Cue>(
+ mCurrentTimeMs, mListIterator.next());
+ mLastListIterator = mListIterator;
+ if (!mListIterator.hasNext()) {
+ nextKey();
+ }
+ return mLastEntry;
+ }
+
+ @Override
+ public void remove() {
+ // only allow removing end tags
+ if (mLastListIterator == null ||
+ mLastEntry.second.mEndTimeMs != mLastEntry.first) {
+ throw new IllegalStateException("");
+ }
+
+ // remove end-cue
+ mLastListIterator.remove();
+ mLastListIterator = null;
+ if (mCues.get(mLastEntry.first).size() == 0) {
+ mCues.remove(mLastEntry.first);
+ }
+
+ // remove rest of the cues
+ Cue cue = mLastEntry.second;
+ removeEvent(cue, cue.mStartTimeMs);
+ if (cue.mInnerTimesMs != null) {
+ for (long timeMs: cue.mInnerTimesMs) {
+ removeEvent(cue, timeMs);
+ }
+ }
+ }
+
+ public EntryIterator(SortedMap<Long, Vector<Cue> > cues) {
+ if (DEBUG) Log.v(TAG, cues + "");
+ mRemainingCues = cues;
+ mLastListIterator = null;
+ nextKey();
+ }
+
+ private void nextKey() {
+ do {
+ try {
+ if (mRemainingCues == null) {
+ throw new NoSuchElementException("");
+ }
+ mCurrentTimeMs = mRemainingCues.firstKey();
+ mListIterator =
+ mRemainingCues.get(mCurrentTimeMs).iterator();
+ try {
+ mRemainingCues =
+ mRemainingCues.tailMap(mCurrentTimeMs + 1);
+ } catch (IllegalArgumentException e) {
+ mRemainingCues = null;
+ }
+ mDone = false;
+ } catch (NoSuchElementException e) {
+ mDone = true;
+ mRemainingCues = null;
+ mListIterator = null;
+ return;
+ }
+ } while (!mListIterator.hasNext());
+ }
+
+ private long mCurrentTimeMs;
+ private Iterator<Cue> mListIterator;
+ private boolean mDone;
+ private SortedMap<Long, Vector<Cue> > mRemainingCues;
+ private Iterator<Cue> mLastListIterator;
+ private Pair<Long,Cue> mLastEntry;
+ }
+
+ CueList() {
+ mCues = new TreeMap<Long, Vector<Cue>>();
+ }
+ }
+
+ public static class Cue {
+ public long mStartTimeMs;
+ public long mEndTimeMs;
+ public long[] mInnerTimesMs;
+ public long mRunID;
+
+ public Cue mNextInRun;
+
+ public void onTime(long timeMs) { }
+ }
+
+ /** update mRunsByEndTime (with default end time) */
+ protected void finishedRun(long runID) {
+ if (runID != 0 && runID != ~0) {
+ Run run = mRunsByID.get(runID);
+ if (run != null) {
+ run.storeByEndTimeMs(mRunsByEndTime);
+ }
+ }
+ }
+
+ /** update mRunsByEndTime with given end time */
+ public void setRunDiscardTimeMs(long runID, long timeMs) {
+ if (runID != 0 && runID != ~0) {
+ Run run = mRunsByID.get(runID);
+ if (run != null) {
+ run.mEndTimeMs = timeMs;
+ run.storeByEndTimeMs(mRunsByEndTime);
+ }
+ }
+ }
+
+ /** whether this is a text track who fires events instead getting rendered */
+ public int getTrackType() {
+ return getRenderingWidget() == null
+ ? TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT
+ : TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE;
+ }
+
+
+ private static class Run {
+ public Cue mFirstCue;
+ public Run mNextRunAtEndTimeMs;
+ public Run mPrevRunAtEndTimeMs;
+ public long mEndTimeMs = -1;
+ public long mRunID = 0;
+ private long mStoredEndTimeMs = -1;
+
+ public void storeByEndTimeMs(LongSparseArray<Run> runsByEndTime) {
+ // remove old value if any
+ int ix = runsByEndTime.indexOfKey(mStoredEndTimeMs);
+ if (ix >= 0) {
+ if (mPrevRunAtEndTimeMs == null) {
+ assert(this == runsByEndTime.valueAt(ix));
+ if (mNextRunAtEndTimeMs == null) {
+ runsByEndTime.removeAt(ix);
+ } else {
+ runsByEndTime.setValueAt(ix, mNextRunAtEndTimeMs);
+ }
+ }
+ removeAtEndTimeMs();
+ }
+
+ // add new value
+ if (mEndTimeMs >= 0) {
+ mPrevRunAtEndTimeMs = null;
+ mNextRunAtEndTimeMs = runsByEndTime.get(mEndTimeMs);
+ if (mNextRunAtEndTimeMs != null) {
+ mNextRunAtEndTimeMs.mPrevRunAtEndTimeMs = this;
+ }
+ runsByEndTime.put(mEndTimeMs, this);
+ mStoredEndTimeMs = mEndTimeMs;
+ }
+ }
+
+ public void removeAtEndTimeMs() {
+ Run prev = mPrevRunAtEndTimeMs;
+
+ if (mPrevRunAtEndTimeMs != null) {
+ mPrevRunAtEndTimeMs.mNextRunAtEndTimeMs = mNextRunAtEndTimeMs;
+ mPrevRunAtEndTimeMs = null;
+ }
+ if (mNextRunAtEndTimeMs != null) {
+ mNextRunAtEndTimeMs.mPrevRunAtEndTimeMs = prev;
+ mNextRunAtEndTimeMs = null;
+ }
+ }
+ }
+
+ /**
+ * Interface for rendering subtitles onto a Canvas.
+ */
+ public interface RenderingWidget {
+ /**
+ * Sets the widget's callback, which is used to send updates when the
+ * rendered data has changed.
+ *
+ * @param callback update callback
+ */
+ public void setOnChangedListener(OnChangedListener callback);
+
+ /**
+ * Sets the widget's size.
+ *
+ * @param width width in pixels
+ * @param height height in pixels
+ */
+ public void setSize(int width, int height);
+
+ /**
+ * Sets whether the widget should draw subtitles.
+ *
+ * @param visible true if subtitles should be drawn, false otherwise
+ */
+ public void setVisible(boolean visible);
+
+ /**
+ * Renders subtitles onto a {@link Canvas}.
+ *
+ * @param c canvas on which to render subtitles
+ */
+ public void draw(Canvas c);
+
+ /**
+ * Called when the widget is attached to a window.
+ */
+ public void onAttachedToWindow();
+
+ /**
+ * Called when the widget is detached from a window.
+ */
+ public void onDetachedFromWindow();
+
+ /**
+ * Callback used to send updates about changes to rendering data.
+ */
+ public interface OnChangedListener {
+ /**
+ * Called when the rendering data has changed.
+ *
+ * @param renderingWidget the widget whose data has changed
+ */
+ public void onChanged(RenderingWidget renderingWidget);
+ }
+ }
+}
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
index 07b8788..05a54d5 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -18,9 +18,7 @@
import android.app.Notification;
import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.Resources.Theme;
-import android.media.DataSourceDesc;
+import android.content.pm.ApplicationInfo;
import android.media.MediaBrowser2;
import android.media.MediaBrowser2.BrowserCallback;
import android.media.MediaController2;
@@ -31,17 +29,14 @@
import android.media.MediaLibraryService2.MediaLibrarySession;
import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
import android.media.MediaMetadata2;
-import android.media.MediaPlayerBase;
import android.media.MediaPlaylistAgent;
import android.media.MediaSession2;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2.PlaylistParams;
import android.media.MediaSession2.SessionCallback;
import android.media.MediaSessionService2;
import android.media.MediaSessionService2.MediaNotification;
-import android.media.PlaybackState2;
import android.media.Rating2;
import android.media.SessionToken2;
import android.media.VolumeProvider2;
@@ -55,10 +50,8 @@
import android.media.update.MediaSession2Provider;
import android.media.update.MediaSession2Provider.BuilderBaseProvider;
import android.media.update.MediaSession2Provider.CommandButtonProvider.BuilderProvider;
-import android.media.update.MediaSession2Provider.PlaylistParamsProvider;
import android.media.update.MediaSessionService2Provider;
import android.media.update.MediaSessionService2Provider.MediaNotificationProvider;
-import android.media.update.PlaybackState2Provider;
import android.media.update.SessionToken2Provider;
import android.media.update.StaticProvider;
import android.media.update.VideoView2Provider;
@@ -71,7 +64,7 @@
import android.widget.MediaControlView2;
import android.widget.VideoView2;
-import com.android.media.IMediaSession2Callback;
+import com.android.media.IMediaController2;
import com.android.media.MediaBrowser2Impl;
import com.android.media.MediaController2Impl;
import com.android.media.MediaItem2Impl;
@@ -80,9 +73,7 @@
import com.android.media.MediaMetadata2Impl;
import com.android.media.MediaPlaylistAgentImpl;
import com.android.media.MediaSession2Impl;
-import com.android.media.MediaSession2Impl.PlaylistParamsImpl;
import com.android.media.MediaSessionService2Impl;
-import com.android.media.PlaybackState2Impl;
import com.android.media.Rating2Impl;
import com.android.media.SessionToken2Impl;
import com.android.media.VolumeProvider2Impl;
@@ -91,10 +82,11 @@
import java.util.concurrent.Executor;
-public class ApiFactory implements StaticProvider {
- public static Object initialize(Resources libResources, Theme libTheme)
- throws ReflectiveOperationException {
- ApiHelper.initialize(libResources, libTheme);
+public final class ApiFactory implements StaticProvider {
+ private ApiFactory() { }
+
+ public static ApiFactory initialize(ApplicationInfo updatableInfo) {
+ ApiHelper.initialize(updatableInfo);
return new ApiFactory();
}
@@ -142,20 +134,7 @@
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 PlaylistParamsProvider createMediaSession2PlaylistParams(Context context,
- PlaylistParams playlistParams, int repeatMode, int shuffleMode,
- MediaMetadata2 playlistMetadata) {
- return new PlaylistParamsImpl(context, playlistParams, repeatMode, shuffleMode,
- playlistMetadata);
- }
-
- @Override
- public PlaylistParams fromBundle_PlaylistParams(Context context, Bundle bundle) {
- return PlaylistParamsImpl.fromBundle(context, bundle);
+ instance, uid, pid, packageName, (IMediaController2) callback);
}
@Override
@@ -293,19 +272,6 @@
}
@Override
- public PlaybackState2Provider createPlaybackState2(Context context, PlaybackState2 instance,
- int state, long position, long updateTime, float speed, long bufferedPosition,
- long activeItemId) {
- return new PlaybackState2Impl(context, instance, state, position, updateTime, speed,
- bufferedPosition, activeItemId);
- }
-
- @Override
- public PlaybackState2 fromBundle_PlaybackState2(Context context, Bundle bundle) {
- return PlaybackState2Impl.fromBundle(context, bundle);
- }
-
- @Override
public MediaPlaylistAgentProvider createMediaPlaylistAgent(Context context,
MediaPlaylistAgent instance) {
return new MediaPlaylistAgentImpl(context, instance);
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
index 7018844..ad8bb48 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
@@ -19,9 +19,12 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.XmlResourceParser;
+import android.support.annotation.GuardedBy;
import android.support.v4.widget.Space;
import android.support.v7.widget.ButtonBarLayout;
import android.util.AttributeSet;
@@ -35,45 +38,47 @@
import com.android.support.mediarouter.app.MediaRouteVolumeSlider;
import com.android.support.mediarouter.app.OverlayListView;
-public class ApiHelper {
- private static ApiHelper sInstance;
- private final Resources mLibResources;
- private final Theme mLibTheme;
+public final class ApiHelper {
+ private static ApplicationInfo sUpdatableInfo;
- public static ApiHelper getInstance() {
- return sInstance;
- }
+ @GuardedBy("this")
+ private static Theme sLibTheme;
- static void initialize(Resources libResources, Theme libTheme) {
- if (sInstance == null) {
- sInstance = new ApiHelper(libResources, libTheme);
+ private ApiHelper() { }
+
+ static void initialize(ApplicationInfo updatableInfo) {
+ if (sUpdatableInfo != null) {
+ throw new IllegalStateException("initialize should only be called once");
}
+
+ sUpdatableInfo = updatableInfo;
}
- private ApiHelper(Resources libResources, Theme libTheme) {
- mLibResources = libResources;
- mLibTheme = libTheme;
+ public static Resources getLibResources(Context context) {
+ return getLibTheme(context).getResources();
}
- public static Resources getLibResources() {
- return sInstance.mLibResources;
+ public static Theme getLibTheme(Context context) {
+ if (sLibTheme != null) return sLibTheme;
+
+ return getLibThemeSynchronized(context);
}
- public static Theme getLibTheme() {
- return sInstance.mLibTheme;
- }
-
- public static Theme getLibTheme(int themeId) {
- Theme theme = sInstance.mLibResources.newTheme();
+ public static Theme getLibTheme(Context context, int themeId) {
+ Theme theme = getLibResources(context).newTheme();
theme.applyStyle(themeId, true);
return theme;
}
public static LayoutInflater getLayoutInflater(Context context) {
- return getLayoutInflater(context, getLibTheme());
+ return getLayoutInflater(context, null);
}
public static LayoutInflater getLayoutInflater(Context context, Theme theme) {
+ if (theme == null) {
+ theme = getLibTheme(context);
+ }
+
// TODO (b/72975976): Avoid to use ContextThemeWrapper with app context and lib theme.
LayoutInflater layoutInflater = LayoutInflater.from(context).cloneInContext(
new ContextThemeWrapper(context, theme));
@@ -106,7 +111,7 @@
}
public static View inflateLibLayout(Context context, int libResId) {
- return inflateLibLayout(context, getLibTheme(), libResId, null, false);
+ return inflateLibLayout(context, getLibTheme(context), libResId, null, false);
}
public static View inflateLibLayout(Context context, Theme theme, int libResId) {
@@ -115,8 +120,23 @@
public static View inflateLibLayout(Context context, Theme theme, int libResId,
@Nullable ViewGroup root, boolean attachToRoot) {
- try (XmlResourceParser parser = getLibResources().getLayout(libResId)) {
+ try (XmlResourceParser parser = getLibResources(context).getLayout(libResId)) {
return getLayoutInflater(context, theme).inflate(parser, root, attachToRoot);
}
}
+
+ private static synchronized Theme getLibThemeSynchronized(Context context) {
+ if (sLibTheme != null) return sLibTheme;
+
+ if (sUpdatableInfo == null) {
+ throw new IllegalStateException("initialize hasn't been called yet");
+ }
+
+ try {
+ return sLibTheme = context.getPackageManager()
+ .getResourcesForApplication(sUpdatableInfo).newTheme();
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
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 fa94a81..fde8a63 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
@@ -130,7 +130,7 @@
mRouter = MediaRouter.getInstance(context);
mCallback = new MediaRouterCallback();
- Resources.Theme theme = ApiHelper.getLibResources().newTheme();
+ Resources.Theme theme = ApiHelper.getLibResources(context).newTheme();
theme.applyStyle(MediaRouterThemeHelper.getRouterThemeId(context), true);
TypedArray a = theme.obtainStyledAttributes(attrs,
R.styleable.MediaRouteButton, defStyleAttr, 0);
@@ -304,7 +304,8 @@
*/
void setCheatSheetEnabled(boolean enable) {
TooltipCompat.setTooltipText(this, enable
- ? ApiHelper.getLibResources().getString(R.string.mr_button_content_description)
+ ? ApiHelper.getLibResources(getContext())
+ .getString(R.string.mr_button_content_description)
: null);
}
@@ -547,7 +548,7 @@
} else {
resId = R.string.mr_cast_button_disconnected;
}
- setContentDescription(ApiHelper.getLibResources().getString(resId));
+ setContentDescription(ApiHelper.getLibResources(getContext()).getString(resId));
}
private final class MediaRouterCallback extends MediaRouter.Callback {
@@ -604,7 +605,7 @@
@Override
protected Drawable doInBackground(Void... params) {
- return ApiHelper.getLibResources().getDrawable(mResId);
+ return ApiHelper.getLibResources(getContext()).getDrawable(mResId);
}
@Override
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialog.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialog.java
index 6e70eaf..cac64d9 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialog.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialog.java
@@ -99,8 +99,8 @@
public MediaRouteChooserDialog(Context context, int theme) {
// TODO (b/72975976): Avoid to use ContextThemeWrapper with app context and lib theme.
- super(new ContextThemeWrapper(context,
- ApiHelper.getLibTheme(MediaRouterThemeHelper.getRouterThemeId(context))), theme);
+ super(new ContextThemeWrapper(context, ApiHelper.getLibTheme(context,
+ MediaRouterThemeHelper.getRouterThemeId(context))), theme);
context = getContext();
mRouter = MediaRouter.getInstance(context);
@@ -187,8 +187,8 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(ApiHelper.inflateLibLayout(getContext(),
- ApiHelper.getLibTheme(MediaRouterThemeHelper.getRouterThemeId(getContext())),
+ setContentView(ApiHelper.inflateLibLayout(getContext(), ApiHelper.getLibTheme(getContext(),
+ MediaRouterThemeHelper.getRouterThemeId(getContext())),
R.layout.mr_chooser_dialog));
mRoutes = new ArrayList<>();
@@ -206,7 +206,7 @@
* Sets the width of the dialog. Also called when configuration changes.
*/
void updateLayout() {
- getWindow().setLayout(MediaRouteDialogHelper.getDialogWidth(),
+ getWindow().setLayout(MediaRouteDialogHelper.getDialogWidth(getContext()),
ViewGroup.LayoutParams.WRAP_CONTENT);
}
@@ -263,7 +263,7 @@
public RouteAdapter(Context context, List<MediaRouter.RouteInfo> routes) {
super(context, 0, routes);
- TypedArray styledAttributes = ApiHelper.getLibTheme(
+ TypedArray styledAttributes = ApiHelper.getLibTheme(context,
MediaRouterThemeHelper.getRouterThemeId(context)).obtainStyledAttributes(
new int[] {
R.attr.mediaRouteDefaultIconDrawable,
@@ -294,7 +294,7 @@
View view = convertView;
if (view == null) {
view = ApiHelper.inflateLibLayout(getContext(),
- ApiHelper.getLibTheme(
+ ApiHelper.getLibTheme(getContext(),
MediaRouterThemeHelper.getRouterThemeId(getContext())),
R.layout.mr_chooser_list_item, parent, false);
}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialog.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialog.java
index 269a6e9..060cfca 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialog.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialog.java
@@ -206,8 +206,8 @@
public MediaRouteControllerDialog(Context context, int theme) {
// TODO (b/72975976): Avoid to use ContextThemeWrapper with app context and lib theme.
- super(new ContextThemeWrapper(context,
- ApiHelper.getLibTheme(MediaRouterThemeHelper.getRouterThemeId(context))), theme);
+ super(new ContextThemeWrapper(context, ApiHelper.getLibTheme(context,
+ MediaRouterThemeHelper.getRouterThemeId(context))), theme);
mContext = getContext();
mControllerCallback = new MediaControllerCallback();
@@ -215,7 +215,7 @@
mCallback = new MediaRouterCallback();
mRoute = mRouter.getSelectedRoute();
setMediaSession(mRouter.getMediaSessionToken());
- mVolumeGroupListPaddingTop = ApiHelper.getLibResources().getDimensionPixelSize(
+ mVolumeGroupListPaddingTop = ApiHelper.getLibResources(context).getDimensionPixelSize(
R.dimen.mr_controller_volume_group_list_padding_top);
mAccessibilityManager =
(AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
@@ -334,7 +334,7 @@
getWindow().setBackgroundDrawableResource(android.R.color.transparent);
setContentView(ApiHelper.inflateLibLayout(mContext,
- ApiHelper.getLibTheme(MediaRouterThemeHelper.getRouterThemeId(mContext)),
+ ApiHelper.getLibTheme(mContext, MediaRouterThemeHelper.getRouterThemeId(mContext)),
R.layout.mr_controller_material_dialog_b));
// Remove the neutral button.
@@ -359,13 +359,13 @@
int color = MediaRouterThemeHelper.getButtonTextColor(mContext);
mDisconnectButton = findViewById(BUTTON_DISCONNECT_RES_ID);
mDisconnectButton.setText(
- ApiHelper.getLibResources().getString(R.string.mr_controller_disconnect));
+ ApiHelper.getLibResources(mContext).getString(R.string.mr_controller_disconnect));
mDisconnectButton.setTextColor(color);
mDisconnectButton.setOnClickListener(listener);
mStopCastingButton = findViewById(BUTTON_STOP_RES_ID);
mStopCastingButton.setText(
- ApiHelper.getLibResources().getString(R.string.mr_controller_stop_casting));
+ ApiHelper.getLibResources(mContext).getString(R.string.mr_controller_stop_casting));
mStopCastingButton.setTextColor(color);
mStopCastingButton.setOnClickListener(listener);
@@ -440,11 +440,11 @@
}
});
loadInterpolator();
- mGroupListAnimationDurationMs = ApiHelper.getLibResources().getInteger(
+ mGroupListAnimationDurationMs = ApiHelper.getLibResources(mContext).getInteger(
R.integer.mr_controller_volume_group_list_animation_duration_ms);
- mGroupListFadeInDurationMs = ApiHelper.getLibResources().getInteger(
+ mGroupListFadeInDurationMs = ApiHelper.getLibResources(mContext).getInteger(
R.integer.mr_controller_volume_group_list_fade_in_duration_ms);
- mGroupListFadeOutDurationMs = ApiHelper.getLibResources().getInteger(
+ mGroupListFadeOutDurationMs = ApiHelper.getLibResources(mContext).getInteger(
R.integer.mr_controller_volume_group_list_fade_out_duration_ms);
mCustomControlView = onCreateMediaControlView(savedInstanceState);
@@ -460,13 +460,13 @@
* Sets the width of the dialog. Also called when configuration changes.
*/
void updateLayout() {
- int width = MediaRouteDialogHelper.getDialogWidth();
+ int width = MediaRouteDialogHelper.getDialogWidth(mContext);
getWindow().setLayout(width, ViewGroup.LayoutParams.WRAP_CONTENT);
View decorView = getWindow().getDecorView();
mDialogContentWidth = width - decorView.getPaddingLeft() - decorView.getPaddingRight();
- Resources res = ApiHelper.getLibResources();
+ Resources res = ApiHelper.getLibResources(mContext);
mVolumeGroupListItemIconSize = res.getDimensionPixelSize(
R.dimen.mr_controller_volume_group_list_item_icon_size);
mVolumeGroupListItemHeight = res.getDimensionPixelSize(
@@ -991,16 +991,16 @@
if (mRoute.getPresentationDisplayId()
!= MediaRouter.RouteInfo.PRESENTATION_DISPLAY_ID_NONE) {
// The user is currently casting screen.
- mTitleView.setText(ApiHelper.getLibResources().getString(
+ mTitleView.setText(ApiHelper.getLibResources(mContext).getString(
R.string.mr_controller_casting_screen));
showTitle = true;
} else if (mState == null || mState.getState() == PlaybackStateCompat.STATE_NONE) {
// Show "No media selected" as we don't yet know the playback state.
- mTitleView.setText(ApiHelper.getLibResources().getString(
+ mTitleView.setText(ApiHelper.getLibResources(mContext).getString(
R.string.mr_controller_no_media_selected));
showTitle = true;
} else if (!hasTitle && !hasSubtitle) {
- mTitleView.setText(ApiHelper.getLibResources().getString(
+ mTitleView.setText(ApiHelper.getLibResources(mContext).getString(
R.string.mr_controller_no_info_available));
showTitle = true;
} else {
@@ -1223,7 +1223,8 @@
AccessibilityEventCompat.TYPE_ANNOUNCEMENT);
event.setPackageName(mContext.getPackageName());
event.setClassName(getClass().getName());
- event.getText().add(ApiHelper.getLibResources().getString(actionDescResId));
+ event.getText().add(
+ ApiHelper.getLibResources(mContext).getString(actionDescResId));
mAccessibilityManager.sendAccessibilityEvent(event);
}
}
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogHelper.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogHelper.java
index 62c050b..9aabf7b 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogHelper.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogHelper.java
@@ -41,12 +41,13 @@
* The framework should set the dialog width properly, but somehow it doesn't work, hence
* duplicating a similar logic here to determine the appropriate dialog width.
*/
- public static int getDialogWidth() {
- DisplayMetrics metrics = ApiHelper.getLibResources().getDisplayMetrics();
+ public static int getDialogWidth(Context context) {
+ DisplayMetrics metrics = ApiHelper.getLibResources(context).getDisplayMetrics();
boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
TypedValue value = new TypedValue();
- ApiHelper.getLibResources().getValue(isPortrait ? R.dimen.mr_dialog_fixed_width_minor
+ ApiHelper.getLibResources(context).getValue(isPortrait
+ ? R.dimen.mr_dialog_fixed_width_minor
: R.dimen.mr_dialog_fixed_width_major, value, true);
if (value.type == TypedValue.TYPE_DIMENSION) {
return (int) value.getDimension(metrics);
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteExpandCollapseButton.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteExpandCollapseButton.java
index defeedb..6a0a95a 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteExpandCollapseButton.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteExpandCollapseButton.java
@@ -51,9 +51,9 @@
public MediaRouteExpandCollapseButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mExpandAnimationDrawable = (AnimationDrawable)
- ApiHelper.getLibResources().getDrawable(R.drawable.mr_group_expand);
+ ApiHelper.getLibResources(context).getDrawable(R.drawable.mr_group_expand);
mCollapseAnimationDrawable = (AnimationDrawable)
- ApiHelper.getLibResources().getDrawable(R.drawable.mr_group_collapse);
+ ApiHelper.getLibResources(context).getDrawable(R.drawable.mr_group_collapse);
ColorFilter filter = new PorterDuffColorFilter(
MediaRouterThemeHelper.getControllerColor(context, defStyleAttr),
@@ -62,9 +62,9 @@
mCollapseAnimationDrawable.setColorFilter(filter);
mExpandGroupDescription =
- ApiHelper.getLibResources().getString(R.string.mr_controller_expand_group);
+ ApiHelper.getLibResources(context).getString(R.string.mr_controller_expand_group);
mCollapseGroupDescription =
- ApiHelper.getLibResources().getString(R.string.mr_controller_collapse_group);
+ ApiHelper.getLibResources(context).getString(R.string.mr_controller_collapse_group);
setImageDrawable(mExpandAnimationDrawable.getFrame(0));
setContentDescription(mExpandGroupDescription);
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 33d92b4..a38491f 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/media/SystemMediaRouteProvider.java
@@ -267,7 +267,7 @@
mCallbackObj = createCallbackObj();
mVolumeCallbackObj = createVolumeCallbackObj();
- Resources r = ApiHelper.getLibResources();
+ Resources r = ApiHelper.getLibResources(context);
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 69febc2..9ae75b0 100644
--- a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -52,6 +52,7 @@
import com.android.support.mediarouter.media.MediaRouter;
import com.android.support.mediarouter.media.MediaRouteSelector;
+import java.util.Arrays;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
@@ -69,18 +70,42 @@
static final String KEY_VIDEO_TRACK_COUNT = "VideoTrackCount";
static final String KEY_AUDIO_TRACK_COUNT = "AudioTrackCount";
static final String KEY_SUBTITLE_TRACK_COUNT = "SubtitleTrackCount";
+ static final String KEY_PLAYBACK_SPEED = "PlaybackSpeed";
+ static final String KEY_SELECTED_AUDIO_INDEX = "SelectedAudioIndex";
+ static final String KEY_SELECTED_SUBTITLE_INDEX = "SelectedSubtitleIndex";
static final String EVENT_UPDATE_TRACK_STATUS = "UpdateTrackStatus";
// TODO: Remove this once integrating with MediaSession2 & MediaMetadata2
static final String KEY_STATE_IS_ADVERTISEMENT = "MediaTypeAdvertisement";
static final String EVENT_UPDATE_MEDIA_TYPE_STATUS = "UpdateMediaTypeStatus";
- // String for receiving command to show subtitle from MediaSession.
+ // String for sending command to show subtitle to MediaSession.
static final String COMMAND_SHOW_SUBTITLE = "showSubtitle";
- // String for receiving command to hide subtitle from MediaSession.
+ // String for sending command to hide subtitle to MediaSession.
static final String COMMAND_HIDE_SUBTITLE = "hideSubtitle";
// TODO: remove once the implementation is revised
public static final String COMMAND_SET_FULLSCREEN = "setFullscreen";
+ // String for sending command to select audio track to MediaSession.
+ static final String COMMAND_SELECT_AUDIO_TRACK = "SelectTrack";
+ // String for sending command to set playback speed to MediaSession.
+ static final String COMMAND_SET_PLAYBACK_SPEED = "SetPlaybackSpeed";
+ // String for sending command to mute audio to MediaSession.
+ static final String COMMAND_MUTE= "Mute";
+ // String for sending command to unmute audio to MediaSession.
+ static final String COMMAND_UNMUTE = "Unmute";
+
+ private static final int SETTINGS_MODE_AUDIO_TRACK = 0;
+ private static final int SETTINGS_MODE_PLAYBACK_SPEED = 1;
+ private static final int SETTINGS_MODE_HELP = 2;
+ private static final int SETTINGS_MODE_SUBTITLE_TRACK = 3;
+ private static final int SETTINGS_MODE_VIDEO_QUALITY = 4;
+ private static final int SETTINGS_MODE_MAIN = 5;
+ private static final int PLAYBACK_SPEED_1x_INDEX = 3;
+
+ private static final int SIZE_TYPE_EMBEDDED = 0;
+ private static final int SIZE_TYPE_FULL = 1;
+ // TODO: add support for Minimal size type.
+ private static final int SIZE_TYPE_MINIMAL = 2;
private static final int MAX_PROGRESS = 1000;
private static final int DEFAULT_PROGRESS_UPDATE_TIME_MS = 1000;
@@ -95,18 +120,25 @@
private MediaController.TransportControls mControls;
private PlaybackState mPlaybackState;
private MediaMetadata mMetadata;
- private ProgressBar mProgress;
- private TextView mEndTime, mCurrentTime;
- private TextView mTitleView;
- private TextView mAdSkipView, mAdRemainingView;
- private View mAdExternalLink;
- private View mRoot;
private int mDuration;
private int mPrevState;
- private int mPrevLeftBarWidth;
+ private int mPrevWidth;
+ private int mOriginalLeftBarWidth;
private int mVideoTrackCount;
private int mAudioTrackCount;
private int mSubtitleTrackCount;
+ private int mSettingsMode;
+ private int mSelectedSubtitleTrackIndex;
+ private int mSelectedAudioTrackIndex;
+ private int mSelectedVideoQualityIndex;
+ private int mSelectedSpeedIndex;
+ private int mEmbeddedSettingsItemWidth;
+ private int mFullSettingsItemWidth;
+ private int mEmbeddedSettingsItemHeight;
+ private int mFullSettingsItemHeight;
+ private int mSettingsWindowMargin;
+ private int mSizeType;
+ private int mOrientation;
private long mPlaybackActions;
private boolean mDragging;
private boolean mIsFullScreen;
@@ -115,46 +147,70 @@
private boolean mSubtitleIsEnabled;
private boolean mSeekAvailable;
private boolean mIsAdvertisement;
+ private boolean mIsMute;
+
+ // Relating to Title Bar View
+ private View mRoot;
+ private View mTitleBar;
+ private TextView mTitleView;
+ private View mAdExternalLink;
+ private ImageButton mBackButton;
+ private MediaRouteButton mRouteButton;
+ private MediaRouteSelector mRouteSelector;
+
+ // Relating to Center View
+ private ViewGroup mCenterView;
+ private View mTransportControls;
private ImageButton mPlayPauseButton;
private ImageButton mFfwdButton;
private ImageButton mRewButton;
private ImageButton mNextButton;
private ImageButton mPrevButton;
+ // Relating to Progress Bar View
+ private ProgressBar mProgress;
+
+ // Relating to Bottom Bar Left View
+ private ViewGroup mBottomBarLeftView;
+ private ViewGroup mTimeView;
+ private TextView mEndTime;
+ private TextView mCurrentTime;
+ private TextView mAdSkipView;
+ private StringBuilder mFormatBuilder;
+ private Formatter mFormatter;
+
+ // Relating to Bottom Bar Right View
+ private ViewGroup mBottomBarRightView;
private ViewGroup mBasicControls;
+ private ViewGroup mExtraControls;
+ private ViewGroup mCustomButtons;
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 mVideoQualityButton;
private ImageButton mSettingsButton;
+ private TextView mAdRemainingView;
+ // Relating to Settings List View
private ListView mSettingsListView;
private PopupWindow mSettingsWindow;
private SettingsAdapter mSettingsAdapter;
-
+ private SubSettingsAdapter mSubSettingsAdapter;
private List<String> mSettingsMainTextsList;
private List<String> mSettingsSubTextsList;
private List<Integer> mSettingsIconIdsList;
private List<String> mSubtitleDescriptionsList;
private List<String> mAudioTrackList;
private List<String> mVideoQualityList;
- private List<String> mPlaybackSpeedTextIdsList;
+ private List<String> mPlaybackSpeedTextList;
+ private List<Float> mPlaybackSpeedList;
private CharSequence mPlayDescription;
private CharSequence mPauseDescription;
private CharSequence mReplayDescription;
- private StringBuilder mFormatBuilder;
- private Formatter mFormatter;
-
- private MediaRouteButton mRouteButton;
- private MediaRouteSelector mRouteSelector;
-
public MediaControlView2Impl(MediaControlView2 instance,
ViewGroupProvider superProvider, ViewGroupProvider privateProvider) {
super(instance, superProvider, privateProvider);
@@ -163,10 +219,9 @@
@Override
public void initialize(@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- mResources = ApiHelper.getLibResources();
+ mResources = ApiHelper.getLibResources(mInstance.getContext());
// Inflate MediaControlView2 from XML
mRoot = makeControllerView();
- mRoot.addOnLayoutChangeListener(mTitleBarLayoutChangeListener);
mInstance.addView(mRoot);
}
@@ -217,15 +272,11 @@
}
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);
}
@@ -250,11 +301,6 @@
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);
@@ -289,6 +335,35 @@
}
@Override
+ public void onMeasure_impl(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure_impl(widthMeasureSpec, heightMeasureSpec);
+
+ if (mPrevWidth != mInstance.getMeasuredWidth()) {
+ int currWidth = mInstance.getMeasuredWidth();
+
+ int iconSize = mResources.getDimensionPixelSize(R.dimen.mcv2_full_icon_size);
+ int marginSize = mResources.getDimensionPixelSize(R.dimen.mcv2_icon_margin);
+ int bottomBarRightWidthMax = iconSize * 4 + marginSize * 8;
+
+ int fullWidth = mTransportControls.getWidth() + mTimeView.getWidth()
+ + bottomBarRightWidthMax;
+ // These views may not have been initialized yet.
+ if (mTransportControls.getWidth() == 0 || mTimeView.getWidth() == 0) {
+ return;
+ }
+ if (mSizeType == SIZE_TYPE_EMBEDDED && fullWidth <= currWidth) {
+ updateLayoutForSizeChange(SIZE_TYPE_FULL);
+ } else if (mSizeType == SIZE_TYPE_FULL && fullWidth > currWidth) {
+ updateLayoutForSizeChange(SIZE_TYPE_EMBEDDED);
+ }
+ // Dismiss SettingsWindow if it is showing.
+ mSettingsWindow.dismiss();
+ mPrevWidth = currWidth;
+ }
+ updateTitleBarLayout();
+ }
+
+ @Override
public void setEnabled_impl(boolean enabled) {
super.setEnabled_impl(enabled);
@@ -363,7 +438,8 @@
}
mPlaybackState = mController.getPlaybackState();
if (mPlaybackState != null) {
- return (int) (mPlaybackState.getBufferedPosition() * 100) / mDuration;
+ long bufferedPos = mPlaybackState.getBufferedPosition();
+ return (bufferedPos == -1) ? -1 : (int) (bufferedPos * 100 / mDuration);
}
return 0;
}
@@ -409,42 +485,50 @@
mReplayDescription =
mResources.getText(R.string.lockscreen_replay_button_content_description);
+ // Relating to Title Bar View
+ mTitleBar = v.findViewById(R.id.title_bar);
+ mTitleView = v.findViewById(R.id.title_text);
+ mAdExternalLink = v.findViewById(R.id.ad_external_link);
+ mBackButton = v.findViewById(R.id.back);
+ if (mBackButton != null) {
+ mBackButton.setOnClickListener(mBackListener);
+ }
mRouteButton = v.findViewById(R.id.cast);
- mPlayPauseButton = v.findViewById(R.id.pause);
- if (mPlayPauseButton != null) {
- mPlayPauseButton.requestFocus();
- mPlayPauseButton.setOnClickListener(mPlayPauseListener);
- mPlayPauseButton.setColorFilter(R.color.gray);
- mPlayPauseButton.setEnabled(false);
- }
- mFfwdButton = v.findViewById(R.id.ffwd);
- if (mFfwdButton != null) {
- mFfwdButton.setOnClickListener(mFfwdListener);
- mFfwdButton.setColorFilter(R.color.gray);
- mFfwdButton.setEnabled(false);
- }
- mRewButton = v.findViewById(R.id.rew);
- if (mRewButton != null) {
- mRewButton.setOnClickListener(mRewListener);
- mRewButton.setColorFilter(R.color.gray);
- mRewButton.setEnabled(false);
- }
- mNextButton = v.findViewById(R.id.next);
- if (mNextButton != null) {
- mNextButton.setOnClickListener(mNextListener);
- }
- mPrevButton = v.findViewById(R.id.prev);
- if (mPrevButton != null) {
- mPrevButton.setOnClickListener(mPrevListener);
+ // Relating to Center View
+ mCenterView = v.findViewById(R.id.center_view);
+ mTransportControls = inflateTransportControls(R.layout.embedded_transport_controls);
+ mCenterView.addView(mTransportControls);
+
+ // Relating to Progress Bar View
+ mProgress = v.findViewById(R.id.mediacontroller_progress);
+ if (mProgress != null) {
+ if (mProgress instanceof SeekBar) {
+ SeekBar seeker = (SeekBar) mProgress;
+ seeker.setOnSeekBarChangeListener(mSeekListener);
+ seeker.setProgressDrawable(mResources.getDrawable(R.drawable.custom_progress));
+ seeker.setThumb(mResources.getDrawable(R.drawable.custom_progress_thumb));
+ }
+ mProgress.setMax(MAX_PROGRESS);
}
+ // Relating to Bottom Bar Left View
+ mBottomBarLeftView = v.findViewById(R.id.bottom_bar_left);
+ mTimeView = v.findViewById(R.id.time);
+ mEndTime = v.findViewById(R.id.time_end);
+ mCurrentTime = v.findViewById(R.id.time_current);
+ mAdSkipView = v.findViewById(R.id.ad_skip_time);
+ mFormatBuilder = new StringBuilder();
+ mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
+
+ // Relating to Bottom Bar Right View
+ mBottomBarRightView = v.findViewById(R.id.bottom_bar_right);
mBasicControls = v.findViewById(R.id.basic_controls);
+ mExtraControls = v.findViewById(R.id.extra_controls);
+ mCustomButtons = v.findViewById(R.id.custom_buttons);
mSubtitleButton = v.findViewById(R.id.subtitle);
if (mSubtitleButton != null) {
mSubtitleButton.setOnClickListener(mSubtitleListener);
- mSubtitleButton.setColorFilter(R.color.gray);
- mSubtitleButton.setEnabled(false);
}
mFullScreenButton = v.findViewById(R.id.fullscreen);
if (mFullScreenButton != null) {
@@ -455,51 +539,45 @@
if (mOverflowButtonRight != null) {
mOverflowButtonRight.setOnClickListener(mOverflowRightListener);
}
-
- // 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);
+ if (mMuteButton != null) {
+ mMuteButton.setOnClickListener(mMuteButtonListener);
+ }
mSettingsButton = v.findViewById(R.id.settings);
if (mSettingsButton != null) {
mSettingsButton.setOnClickListener(mSettingsButtonListener);
}
-
- mProgress = v.findViewById(R.id.mediacontroller_progress);
- if (mProgress != null) {
- if (mProgress instanceof SeekBar) {
- SeekBar seeker = (SeekBar) mProgress;
- seeker.setOnSeekBarChangeListener(mSeekListener);
- }
- mProgress.setMax(MAX_PROGRESS);
+ mVideoQualityButton = v.findViewById(R.id.video_quality);
+ if (mVideoQualityButton != null) {
+ mVideoQualityButton.setOnClickListener(mVideoQualityListener);
}
-
- 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());
-
- mAdSkipView = v.findViewById(R.id.ad_skip_time);
mAdRemainingView = v.findViewById(R.id.ad_remaining);
- mAdExternalLink = v.findViewById(R.id.ad_external_link);
+ // Relating to Settings List View
initializeSettingsLists();
mSettingsListView = (ListView) ApiHelper.inflateLibLayout(mInstance.getContext(),
R.layout.settings_list);
mSettingsAdapter = new SettingsAdapter(mSettingsMainTextsList, mSettingsSubTextsList,
- mSettingsIconIdsList, false);
+ mSettingsIconIdsList);
+ mSubSettingsAdapter = new SubSettingsAdapter(null, 0);
mSettingsListView.setAdapter(mSettingsAdapter);
mSettingsListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
mSettingsListView.setOnItemClickListener(mSettingsItemClickListener);
- int width = mResources.getDimensionPixelSize(R.dimen.MediaControlView2_settings_width);
- mSettingsWindow = new PopupWindow(mSettingsListView, width,
+
+ mEmbeddedSettingsItemWidth = mResources.getDimensionPixelSize(
+ R.dimen.mcv2_embedded_settings_width);
+ mFullSettingsItemWidth = mResources.getDimensionPixelSize(R.dimen.mcv2_full_settings_width);
+ mEmbeddedSettingsItemHeight = mResources.getDimensionPixelSize(
+ R.dimen.mcv2_embedded_settings_height);
+ mFullSettingsItemHeight = mResources.getDimensionPixelSize(
+ R.dimen.mcv2_full_settings_height);
+ mSettingsWindowMargin = (-1) * mResources.getDimensionPixelSize(
+ R.dimen.mcv2_settings_offset);
+ mSettingsWindow = new PopupWindow(mSettingsListView, mEmbeddedSettingsItemWidth,
ViewGroup.LayoutParams.WRAP_CONTENT, true);
}
@@ -574,7 +652,13 @@
}
if (mProgress != null && currentPosition != mDuration) {
mProgress.setProgress(positionOnProgressBar);
- mProgress.setSecondaryProgress(getBufferPercentage() * 10);
+ // If the media is a local file, there is no need to set a buffer, so set secondary
+ // progress to maximum.
+ if (getBufferPercentage() < 0) {
+ mProgress.setSecondaryProgress(MAX_PROGRESS);
+ } else {
+ mProgress.setSecondaryProgress(getBufferPercentage() * 10);
+ }
}
if (mEndTime != null) {
@@ -744,20 +828,30 @@
}
};
+ private final View.OnClickListener mBackListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // TODO: implement
+ }
+ };
+
private final View.OnClickListener mSubtitleListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
- if (!mSubtitleIsEnabled) {
- mSubtitleButton.setImageDrawable(
- mResources.getDrawable(R.drawable.ic_media_subtitle_enabled, null));
- mController.sendCommand(MediaControlView2Impl.COMMAND_SHOW_SUBTITLE, null, null);
- mSubtitleIsEnabled = true;
- } else {
- mSubtitleButton.setImageDrawable(
- mResources.getDrawable(R.drawable.ic_media_subtitle_disabled, null));
- mController.sendCommand(MediaControlView2Impl.COMMAND_HIDE_SUBTITLE, null, null);
- mSubtitleIsEnabled = false;
- }
+ mSettingsMode = SETTINGS_MODE_SUBTITLE_TRACK;
+ mSubSettingsAdapter.setTexts(mSubtitleDescriptionsList);
+ mSubSettingsAdapter.setCheckPosition(mSelectedSubtitleTrackIndex);
+ displaySettingsWindow(mSubSettingsAdapter);
+ }
+ };
+
+ private final View.OnClickListener mVideoQualityListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSettingsMode = SETTINGS_MODE_VIDEO_QUALITY;
+ mSubSettingsAdapter.setTexts(mVideoQualityList);
+ mSubSettingsAdapter.setCheckPosition(mSelectedVideoQualityIndex);
+ displaySettingsWindow(mSubSettingsAdapter);
}
};
@@ -797,20 +891,29 @@
}
};
+ private final View.OnClickListener mMuteButtonListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!mIsMute) {
+ mMuteButton.setImageDrawable(
+ mResources.getDrawable(R.drawable.ic_mute, null));
+ mIsMute = true;
+ mController.sendCommand(COMMAND_MUTE, null, null);
+ } else {
+ mMuteButton.setImageDrawable(
+ mResources.getDrawable(R.drawable.ic_unmute, null));
+ mIsMute = false;
+ mController.sendCommand(COMMAND_UNMUTE, null, null);
+ }
+ }
+ };
+
private final View.OnClickListener mSettingsButtonListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
- mSettingsAdapter = new SettingsAdapter(mSettingsMainTextsList,
- mSettingsSubTextsList, mSettingsIconIdsList, false);
- mSettingsListView.setAdapter(mSettingsAdapter);
- int itemHeight = mResources.getDimensionPixelSize(
- R.dimen.MediaControlView2_settings_height);
- int totalHeight = mSettingsAdapter.getCount() * itemHeight;
- int margin = (-1) * mResources.getDimensionPixelSize(
- R.dimen.MediaControlView2_settings_offset);
- mSettingsWindow.dismiss();
- mSettingsWindow.showAsDropDown(mInstance, margin, margin - totalHeight,
- Gravity.BOTTOM | Gravity.RIGHT);
+ mSettingsMode = SETTINGS_MODE_MAIN;
+ mSettingsAdapter.setSubTexts(mSettingsSubTextsList);
+ displaySettingsWindow(mSettingsAdapter);
}
};
@@ -818,85 +921,77 @@
= new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- switch (position) {
- // change to identifiers
- case 0:
- // TODO: add additional subtitle track details
- mSubtitleDescriptionsList = new ArrayList<String>();
- mSubtitleDescriptionsList.add(mResources.getString(
- R.string.MediaControlView2_subtitle_off_text));
- for (int i = 0; i < mSubtitleTrackCount; i++) {
- String track = mResources.getString(
- R.string.MediaControlView2_subtitle_track_number_text, i + 1);
- mSubtitleDescriptionsList.add(track);
+ switch (mSettingsMode) {
+ case SETTINGS_MODE_MAIN:
+ if (position == SETTINGS_MODE_AUDIO_TRACK) {
+ mSubSettingsAdapter.setTexts(mAudioTrackList);
+ mSubSettingsAdapter.setCheckPosition(mSelectedAudioTrackIndex);
+ mSettingsMode = SETTINGS_MODE_AUDIO_TRACK;
+ } else if (position == SETTINGS_MODE_PLAYBACK_SPEED) {
+ mSubSettingsAdapter.setTexts(mPlaybackSpeedTextList);
+ mSubSettingsAdapter.setCheckPosition(mSelectedSpeedIndex);
+ mSettingsMode = SETTINGS_MODE_PLAYBACK_SPEED;
+ } else if (position == SETTINGS_MODE_HELP) {
+ // TODO: implement this.
+ mSettingsWindow.dismiss();
+ return;
}
- mSettingsAdapter = new SettingsAdapter(mSubtitleDescriptionsList, null,
- null, true);
+ displaySettingsWindow(mSubSettingsAdapter);
break;
- case 1:
- // TODO: add additional audio track details
- mAudioTrackList = new ArrayList<String>();
- mAudioTrackList.add(mResources.getString(
- R.string.MediaControlView2_audio_track_none_text));
- for (int i = 0; i < mAudioTrackCount; i++) {
- String track = mResources.getString(
- R.string.MediaControlView2_audio_track_number_text, i + 1);
- mAudioTrackList.add(track);
+ case SETTINGS_MODE_AUDIO_TRACK:
+ if (position != mSelectedAudioTrackIndex) {
+ mSelectedAudioTrackIndex = position;
+ if (mAudioTrackCount > 0) {
+ Bundle extra = new Bundle();
+ extra.putInt(KEY_SELECTED_AUDIO_INDEX, position);
+ mController.sendCommand(COMMAND_SELECT_AUDIO_TRACK, extra, null);
+ }
+ mSettingsSubTextsList.set(SETTINGS_MODE_AUDIO_TRACK,
+ mSubSettingsAdapter.getMainText(position));
}
- mSettingsAdapter = new SettingsAdapter(mAudioTrackList, null,
- null, true);
+ mSettingsWindow.dismiss();
break;
- case 2:
- // TODO: add support for multiple quality video tracks
- mSettingsAdapter = new SettingsAdapter(mVideoQualityList, null,
- null, true);
+ case SETTINGS_MODE_PLAYBACK_SPEED:
+ if (position != mSelectedSpeedIndex) {
+ mSelectedSpeedIndex = position;
+ Bundle extra = new Bundle();
+ extra.putFloat(KEY_PLAYBACK_SPEED, mPlaybackSpeedList.get(position));
+ mController.sendCommand(COMMAND_SET_PLAYBACK_SPEED, extra, null);
+ mSettingsSubTextsList.set(SETTINGS_MODE_PLAYBACK_SPEED,
+ mSubSettingsAdapter.getMainText(position));
+ }
+ mSettingsWindow.dismiss();
break;
- case 3:
- // TODO: implement code to reflect change in speed.
- mSettingsAdapter = new SettingsAdapter(mPlaybackSpeedTextIdsList, null,
- null, true);
+ case SETTINGS_MODE_HELP:
+ // TODO: implement this.
break;
- default:
- return;
- }
- mSettingsListView.setAdapter(mSettingsAdapter);
- int itemHeight = mResources.getDimensionPixelSize(
- R.dimen.MediaControlView2_settings_height);
- int totalHeight = mSettingsAdapter.getCount() * itemHeight;
- int margin = (-1) * mResources.getDimensionPixelSize(
- R.dimen.MediaControlView2_settings_offset);
- mSettingsWindow.dismiss();
- mSettingsWindow.showAsDropDown(mInstance, margin, margin - totalHeight,
- Gravity.BOTTOM | Gravity.RIGHT);
- }
- };
+ case SETTINGS_MODE_SUBTITLE_TRACK:
+ if (position != mSelectedSubtitleTrackIndex) {
+ mSelectedSubtitleTrackIndex = position;
+ if (position > 0) {
+ Bundle extra = new Bundle();
+ extra.putInt(KEY_SELECTED_SUBTITLE_INDEX, position - 1);
+ mController.sendCommand(
+ MediaControlView2Impl.COMMAND_SHOW_SUBTITLE, extra, null);
+ mSubtitleButton.setImageDrawable(
+ mResources.getDrawable(R.drawable.ic_subtitle_on, null));
+ mSubtitleIsEnabled = true;
+ } else {
+ mController.sendCommand(
+ MediaControlView2Impl.COMMAND_HIDE_SUBTITLE, null, null);
+ mSubtitleButton.setImageDrawable(
+ mResources.getDrawable(R.drawable.ic_subtitle_off, null));
- // The title bar is made up of two separate LinearLayouts. If the sum of the two bars are
- // greater than the length of the title bar, reduce the size of the left bar (which makes the
- // TextView that contains the title of the media file shrink).
- private final View.OnLayoutChangeListener mTitleBarLayoutChangeListener
- = new View.OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- if (mRoot != null) {
- int titleBarWidth = mRoot.findViewById(R.id.title_bar).getWidth();
-
- View leftBar = mRoot.findViewById(R.id.title_bar_left);
- View rightBar = mRoot.findViewById(R.id.title_bar_right);
- int leftBarWidth = leftBar.getWidth();
- int rightBarWidth = rightBar.getWidth();
-
- RelativeLayout.LayoutParams params =
- (RelativeLayout.LayoutParams) leftBar.getLayoutParams();
- if (leftBarWidth + rightBarWidth > titleBarWidth) {
- params.width = titleBarWidth - rightBarWidth;
- mPrevLeftBarWidth = leftBarWidth;
- } else if (leftBarWidth + rightBarWidth < titleBarWidth && mPrevLeftBarWidth != 0) {
- params.width = mPrevLeftBarWidth;
- mPrevLeftBarWidth = 0;
- }
- leftBar.setLayoutParams(params);
+ mSubtitleIsEnabled = false;
+ }
+ }
+ mSettingsWindow.dismiss();
+ break;
+ case SETTINGS_MODE_VIDEO_QUALITY:
+ // TODO: add support for video quality
+ mSelectedVideoQualityIndex = position;
+ mSettingsWindow.dismiss();
+ break;
}
}
};
@@ -919,13 +1014,37 @@
}
}
+ // The title bar is made up of two separate LinearLayouts. If the sum of the two bars are
+ // greater than the length of the title bar, reduce the size of the left bar (which makes the
+ // TextView that contains the title of the media file shrink).
+ private void updateTitleBarLayout() {
+ if (mTitleBar != null) {
+ int titleBarWidth = mTitleBar.getWidth();
+
+ View leftBar = mTitleBar.findViewById(R.id.title_bar_left);
+ View rightBar = mTitleBar.findViewById(R.id.title_bar_right);
+ int leftBarWidth = leftBar.getWidth();
+ int rightBarWidth = rightBar.getWidth();
+
+ RelativeLayout.LayoutParams params =
+ (RelativeLayout.LayoutParams) leftBar.getLayoutParams();
+ if (leftBarWidth + rightBarWidth > titleBarWidth) {
+ params.width = titleBarWidth - rightBarWidth;
+ mOriginalLeftBarWidth = leftBarWidth;
+ } else if (leftBarWidth + rightBarWidth < titleBarWidth && mOriginalLeftBarWidth != 0) {
+ params.width = mOriginalLeftBarWidth;
+ mOriginalLeftBarWidth = 0;
+ }
+ leftBar.setLayoutParams(params);
+ }
+ }
+
private void updateLayout() {
if (mIsAdvertisement) {
mRewButton.setVisibility(View.GONE);
mFfwdButton.setVisibility(View.GONE);
mPrevButton.setVisibility(View.GONE);
- mCurrentTime.setVisibility(View.GONE);
- mEndTime.setVisibility(View.GONE);
+ mTimeView.setVisibility(View.GONE);
mAdSkipView.setVisibility(View.VISIBLE);
mAdRemainingView.setVisibility(View.VISIBLE);
@@ -938,8 +1057,7 @@
mRewButton.setVisibility(View.VISIBLE);
mFfwdButton.setVisibility(View.VISIBLE);
mPrevButton.setVisibility(View.VISIBLE);
- mCurrentTime.setVisibility(View.VISIBLE);
- mEndTime.setVisibility(View.VISIBLE);
+ mTimeView.setVisibility(View.VISIBLE);
mAdSkipView.setVisibility(View.GONE);
mAdRemainingView.setVisibility(View.GONE);
@@ -952,81 +1070,140 @@
}
}
+ private void updateLayoutForSizeChange(int sizeType) {
+ mSizeType = sizeType;
+ RelativeLayout.LayoutParams params =
+ (RelativeLayout.LayoutParams) mTimeView.getLayoutParams();
+ switch (mSizeType) {
+ case SIZE_TYPE_EMBEDDED:
+ mBottomBarLeftView.removeView(mTransportControls);
+ mBottomBarLeftView.setVisibility(View.GONE);
+ mTransportControls = inflateTransportControls(R.layout.embedded_transport_controls);
+ mCenterView.addView(mTransportControls, 0);
+
+ if (params.getRule(RelativeLayout.LEFT_OF) != 0) {
+ params.removeRule(RelativeLayout.LEFT_OF);
+ params.addRule(RelativeLayout.RIGHT_OF, R.id.bottom_bar_left);
+ }
+ break;
+ case SIZE_TYPE_FULL:
+ mCenterView.removeView(mTransportControls);
+ mTransportControls = inflateTransportControls(R.layout.full_transport_controls);
+ mBottomBarLeftView.addView(mTransportControls, 0);
+ mBottomBarLeftView.setVisibility(View.VISIBLE);
+
+ if (params.getRule(RelativeLayout.RIGHT_OF) != 0) {
+ params.removeRule(RelativeLayout.RIGHT_OF);
+ params.addRule(RelativeLayout.LEFT_OF, R.id.bottom_bar_right);
+ }
+ break;
+ case SIZE_TYPE_MINIMAL:
+ // TODO: implement
+ break;
+ }
+ mTimeView.setLayoutParams(params);
+
+ if (isPlaying()) {
+ mPlayPauseButton.setImageDrawable(
+ mResources.getDrawable(R.drawable.ic_pause_circle_filled, null));
+ mPlayPauseButton.setContentDescription(mPauseDescription);
+ } else {
+ mPlayPauseButton.setImageDrawable(
+ mResources.getDrawable(R.drawable.ic_play_circle_filled, null));
+ mPlayPauseButton.setContentDescription(mPlayDescription);
+ }
+ }
+
+ private View inflateTransportControls(int layoutId) {
+ View v = ApiHelper.inflateLibLayout(mInstance.getContext(), layoutId);
+ mPlayPauseButton = v.findViewById(R.id.pause);
+ if (mPlayPauseButton != null) {
+ mPlayPauseButton.requestFocus();
+ mPlayPauseButton.setOnClickListener(mPlayPauseListener);
+ }
+ mFfwdButton = v.findViewById(R.id.ffwd);
+ if (mFfwdButton != null) {
+ mFfwdButton.setOnClickListener(mFfwdListener);
+ }
+ mRewButton = v.findViewById(R.id.rew);
+ if (mRewButton != null) {
+ mRewButton.setOnClickListener(mRewListener);
+ }
+ // TODO: Add support for Next and Previous buttons
+ mNextButton = v.findViewById(R.id.next);
+ if (mNextButton != null) {
+ mNextButton.setOnClickListener(mNextListener);
+ mNextButton.setVisibility(View.GONE);
+ }
+ mPrevButton = v.findViewById(R.id.prev);
+ if (mPrevButton != null) {
+ mPrevButton.setOnClickListener(mPrevListener);
+ mPrevButton.setVisibility(View.GONE);
+ }
+ return v;
+ }
+
private void initializeSettingsLists() {
- if (mSettingsMainTextsList == null) {
- mSettingsMainTextsList = new ArrayList<String>();
- mSettingsMainTextsList.add(
- mResources.getString(R.string.MediaControlView2_subtitle_text));
- mSettingsMainTextsList.add(
- mResources.getString(R.string.MediaControlView2_audio_track_text));
- mSettingsMainTextsList.add(
- mResources.getString(R.string.MediaControlView2_video_quality_text));
- mSettingsMainTextsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_text));
- mSettingsMainTextsList.add(
- mResources.getString(R.string.MediaControlView2_help_text));
- }
+ mSettingsMainTextsList = new ArrayList<String>();
+ mSettingsMainTextsList.add(
+ mResources.getString(R.string.MediaControlView2_audio_track_text));
+ mSettingsMainTextsList.add(
+ mResources.getString(R.string.MediaControlView2_playback_speed_text));
+ mSettingsMainTextsList.add(
+ mResources.getString(R.string.MediaControlView2_help_text));
- // TODO: Update the following code to be dynamic.
- if (mSettingsSubTextsList == null) {
- mSettingsSubTextsList = new ArrayList<String>();
- mSettingsSubTextsList.add(
- mResources.getString(R.string.MediaControlView2_subtitle_off_text));
- mSettingsSubTextsList.add(
- mResources.getString(R.string.MediaControlView2_audio_track_none_text));
- mSettingsSubTextsList.add(
- mResources.getString(R.string.MediaControlView2_video_quality_auto_text));
- mSettingsSubTextsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_1x_text));
- mSettingsSubTextsList.add(RESOURCE_EMPTY);
- }
+ mSettingsSubTextsList = new ArrayList<String>();
+ mSettingsSubTextsList.add(
+ mResources.getString(R.string.MediaControlView2_audio_track_none_text));
+ mSettingsSubTextsList.add(
+ mResources.getStringArray(
+ R.array.MediaControlView2_playback_speeds)[PLAYBACK_SPEED_1x_INDEX]);
+ mSettingsSubTextsList.add(RESOURCE_EMPTY);
- if (mSettingsIconIdsList == null) {
- mSettingsIconIdsList = new ArrayList<Integer>();
- mSettingsIconIdsList.add(R.drawable.ic_closed_caption_off);
- mSettingsIconIdsList.add(R.drawable.ic_audiotrack);
- mSettingsIconIdsList.add(R.drawable.ic_high_quality);
- mSettingsIconIdsList.add(R.drawable.ic_play_circle_filled);
- mSettingsIconIdsList.add(R.drawable.ic_help);
- }
+ mSettingsIconIdsList = new ArrayList<Integer>();
+ mSettingsIconIdsList.add(R.drawable.ic_audiotrack);
+ mSettingsIconIdsList.add(R.drawable.ic_play_circle_filled);
+ mSettingsIconIdsList.add(R.drawable.ic_help);
- if (mSubtitleDescriptionsList == null) {
- mSubtitleDescriptionsList = new ArrayList<String>();
- mSubtitleDescriptionsList.add(
- mResources.getString(R.string.MediaControlView2_subtitle_off_text));
- }
+ mAudioTrackList = new ArrayList<String>();
+ mAudioTrackList.add(
+ mResources.getString(R.string.MediaControlView2_audio_track_none_text));
- if (mAudioTrackList == null) {
- mAudioTrackList = new ArrayList<String>();
- mAudioTrackList.add(
- mResources.getString(R.string.MediaControlView2_audio_track_none_text));
- }
+ mVideoQualityList = new ArrayList<String>();
+ mVideoQualityList.add(
+ mResources.getString(R.string.MediaControlView2_video_quality_auto_text));
- if (mVideoQualityList == null) {
- mVideoQualityList = new ArrayList<String>();
- mVideoQualityList.add(
- mResources.getString(R.string.MediaControlView2_video_quality_auto_text));
- }
+ mPlaybackSpeedTextList = new ArrayList<String>(Arrays.asList(
+ mResources.getStringArray(R.array.MediaControlView2_playback_speeds)));
+ // Select the "1x" speed as the default value.
+ mSelectedSpeedIndex = PLAYBACK_SPEED_1x_INDEX;
- if (mPlaybackSpeedTextIdsList == null) {
- mPlaybackSpeedTextIdsList = new ArrayList<String>();
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_0_25x_text));
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_0_5x_text));
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_0_75x_text));
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_1x_text));
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_1_25x_text));
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_1_5x_text));
- mPlaybackSpeedTextIdsList.add(
- mResources.getString(R.string.MediaControlView2_playback_speed_2x_text));
+ mPlaybackSpeedList = new ArrayList<Float>();
+ int[] speeds = mResources.getIntArray(R.array.speed_multiplied_by_100);
+ for (int i = 0; i < speeds.length; i++) {
+ float speed = (float) speeds[i] / 100.0f;
+ mPlaybackSpeedList.add(speed);
}
}
+ private void displaySettingsWindow(BaseAdapter adapter) {
+ // Set Adapter
+ mSettingsListView.setAdapter(adapter);
+
+ // Set width of window
+ int itemWidth = (mSizeType == SIZE_TYPE_EMBEDDED)
+ ? mEmbeddedSettingsItemWidth : mFullSettingsItemWidth;
+ mSettingsWindow.setWidth(itemWidth);
+
+ // Calculate height of window and show
+ int itemHeight = (mSizeType == SIZE_TYPE_EMBEDDED)
+ ? mEmbeddedSettingsItemHeight : mFullSettingsItemHeight;
+ int totalHeight = adapter.getCount() * itemHeight;
+ mSettingsWindow.dismiss();
+ mSettingsWindow.showAsDropDown(mInstance, mSettingsWindowMargin,
+ mSettingsWindowMargin - totalHeight, Gravity.BOTTOM | Gravity.RIGHT);
+ }
+
private class MediaControllerCallback extends MediaController.Callback {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
@@ -1065,16 +1242,13 @@
if (mPlaybackActions != mPlaybackState.getActions()) {
long newActions = mPlaybackState.getActions();
if ((newActions & PlaybackState.ACTION_PAUSE) != 0) {
- mPlayPauseButton.clearColorFilter();
- mPlayPauseButton.setEnabled(true);
+ mPlayPauseButton.setVisibility(View.VISIBLE);
}
if ((newActions & PlaybackState.ACTION_REWIND) != 0) {
- mRewButton.clearColorFilter();
- mRewButton.setEnabled(true);
+ mRewButton.setVisibility(View.VISIBLE);
}
if ((newActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
- mFfwdButton.clearColorFilter();
- mFfwdButton.setEnabled(true);
+ mFfwdButton.setVisibility(View.VISIBLE);
}
if ((newActions & PlaybackState.ACTION_SEEK_TO) != 0) {
mSeekAvailable = true;
@@ -1122,16 +1296,42 @@
switch (event) {
case EVENT_UPDATE_TRACK_STATUS:
mVideoTrackCount = extras.getInt(KEY_VIDEO_TRACK_COUNT);
+ // If there is one or more audio tracks, and this information has not been
+ // reflected into the Settings window yet, automatically check the first track.
+ // Otherwise, the Audio Track selection will be defaulted to "None".
mAudioTrackCount = extras.getInt(KEY_AUDIO_TRACK_COUNT);
- int newSubtitleTrackCount = extras.getInt(KEY_SUBTITLE_TRACK_COUNT);
- if (newSubtitleTrackCount > 0) {
- mSubtitleButton.clearColorFilter();
- mSubtitleButton.setEnabled(true);
+ mAudioTrackList = new ArrayList<String>();
+ if (mAudioTrackCount > 0) {
+ // TODO: add more text about track info.
+ for (int i = 0; i < mAudioTrackCount; i++) {
+ String track = mResources.getString(
+ R.string.MediaControlView2_audio_track_number_text, i + 1);
+ mAudioTrackList.add(track);
+ }
+ // Change sub text inside the Settings window.
+ mSettingsSubTextsList.set(SETTINGS_MODE_AUDIO_TRACK,
+ mAudioTrackList.get(0));
} else {
- mSubtitleButton.setColorFilter(R.color.gray);
+ mAudioTrackList.add(mResources.getString(
+ R.string.MediaControlView2_audio_track_none_text));
+ }
+
+ mSubtitleTrackCount = extras.getInt(KEY_SUBTITLE_TRACK_COUNT);
+ mSubtitleDescriptionsList = new ArrayList<String>();
+ if (mSubtitleTrackCount > 0) {
+ mSubtitleButton.setVisibility(View.VISIBLE);
+ mSubtitleButton.setEnabled(true);
+ mSubtitleDescriptionsList.add(mResources.getString(
+ R.string.MediaControlView2_subtitle_off_text));
+ for (int i = 0; i < mSubtitleTrackCount; i++) {
+ String track = mResources.getString(
+ R.string.MediaControlView2_subtitle_track_number_text, i + 1);
+ mSubtitleDescriptionsList.add(track);
+ }
+ } else {
+ mSubtitleButton.setVisibility(View.GONE);
mSubtitleButton.setEnabled(false);
}
- mSubtitleTrackCount = newSubtitleTrackCount;
break;
case EVENT_UPDATE_MEDIA_TYPE_STATUS:
boolean newStatus = extras.getBoolean(KEY_STATE_IS_ADVERTISEMENT);
@@ -1145,17 +1345,29 @@
}
private class SettingsAdapter extends BaseAdapter {
- List<Integer> mIconIds;
- List<String> mMainTexts;
- List<String> mSubTexts;
- boolean mIsCheckable;
+ private List<Integer> mIconIds;
+ private List<String> mMainTexts;
+ private List<String> mSubTexts;
public SettingsAdapter(List<String> mainTexts, @Nullable List<String> subTexts,
- @Nullable List<Integer> iconIds, boolean isCheckable) {
+ @Nullable List<Integer> iconIds) {
mMainTexts = mainTexts;
mSubTexts = subTexts;
mIconIds = iconIds;
- mIsCheckable = isCheckable;
+ }
+
+ public void updateSubTexts(List<String> subTexts) {
+ mSubTexts = subTexts;
+ notifyDataSetChanged();
+ }
+
+ public String getMainText(int position) {
+ if (mMainTexts != null) {
+ if (position < mMainTexts.size()) {
+ return mMainTexts.get(position);
+ }
+ }
+ return RESOURCE_EMPTY;
}
@Override
@@ -1179,12 +1391,17 @@
@Override
public View getView(int position, View convertView, ViewGroup container) {
- View row = ApiHelper.inflateLibLayout(mInstance.getContext(),
- R.layout.settings_list_item);
+ View row;
+ if (mSizeType == SIZE_TYPE_FULL) {
+ row = ApiHelper.inflateLibLayout(mInstance.getContext(),
+ R.layout.full_settings_list_item);
+ } else {
+ row = ApiHelper.inflateLibLayout(mInstance.getContext(),
+ R.layout.embedded_settings_list_item);
+ }
TextView mainTextView = (TextView) row.findViewById(R.id.main_text);
TextView subTextView = (TextView) row.findViewById(R.id.sub_text);
ImageView iconView = (ImageView) row.findViewById(R.id.icon);
- ImageView checkView = (ImageView) row.findViewById(R.id.check);
// Set main text
mainTextView.setText(mMainTexts.get(position));
@@ -1206,13 +1423,78 @@
// Otherwise, set main icon.
iconView.setImageDrawable(mResources.getDrawable(mIconIds.get(position), null));
}
+ return row;
+ }
- // Set check icon
- // TODO: make the following code dynamic
- if (!mIsCheckable) {
- checkView.setVisibility(View.GONE);
+ public void setSubTexts(List<String> subTexts) {
+ mSubTexts = subTexts;
+ }
+ }
+
+ // TODO: extend this class from SettingsAdapter
+ private class SubSettingsAdapter extends BaseAdapter {
+ private List<String> mTexts;
+ private int mCheckPosition;
+
+ public SubSettingsAdapter(List<String> texts, int checkPosition) {
+ mTexts = texts;
+ mCheckPosition = checkPosition;
+ }
+
+ public String getMainText(int position) {
+ if (mTexts != null) {
+ if (position < mTexts.size()) {
+ return mTexts.get(position);
+ }
+ }
+ return RESOURCE_EMPTY;
+ }
+
+ @Override
+ public int getCount() {
+ return (mTexts == null) ? 0 : mTexts.size();
+ }
+
+ @Override
+ public long getItemId(int position) {
+ // Auto-generated method stub--does not have any purpose here
+ // TODO: implement this.
+ return 0;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ // Auto-generated method stub--does not have any purpose here
+ // TODO: implement this.
+ return null;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup container) {
+ View row;
+ if (mSizeType == SIZE_TYPE_FULL) {
+ row = ApiHelper.inflateLibLayout(mInstance.getContext(),
+ R.layout.full_sub_settings_list_item);
+ } else {
+ row = ApiHelper.inflateLibLayout(mInstance.getContext(),
+ R.layout.embedded_sub_settings_list_item);
+ }
+ TextView textView = (TextView) row.findViewById(R.id.text);
+ ImageView checkView = (ImageView) row.findViewById(R.id.check);
+
+ textView.setText(mTexts.get(position));
+ if (position != mCheckPosition) {
+ checkView.setVisibility(View.INVISIBLE);
}
return row;
}
+
+ public void setTexts(List<String> texts) {
+ mTexts = texts;
+ }
+
+ public void setCheckPosition(int checkPosition) {
+ mCheckPosition = checkPosition;
+ }
}
}
diff --git a/packages/MediaComponents/src/com/android/widget/SubtitleView.java b/packages/MediaComponents/src/com/android/widget/SubtitleView.java
index 9071967..67b2cd1 100644
--- a/packages/MediaComponents/src/com/android/widget/SubtitleView.java
+++ b/packages/MediaComponents/src/com/android/widget/SubtitleView.java
@@ -18,13 +18,14 @@
import android.content.Context;
import android.graphics.Canvas;
-import android.media.SubtitleController.Anchor;
-import android.media.SubtitleTrack.RenderingWidget;
import android.os.Looper;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.widget.FrameLayout;
+import com.android.media.subtitle.SubtitleController.Anchor;
+import com.android.media.subtitle.SubtitleTrack.RenderingWidget;
+
class SubtitleView extends FrameLayout implements Anchor {
private static final String TAG = "SubtitleView";
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index 0bb659d..46ae359 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -24,18 +24,14 @@
import android.media.MediaMetadata;
import android.media.MediaPlayer2;
import android.media.MediaPlayer2.MediaPlayer2EventCallback;
+import android.media.MediaPlayer2.OnSubtitleDataListener;
import android.media.MediaPlayer2Impl;
-import android.media.Cea708CaptionRenderer;
-import android.media.ClosedCaptionRenderer;
+import android.media.SubtitleData;
import android.media.MediaItem2;
import android.media.MediaMetadata2;
import android.media.Metadata;
import android.media.PlaybackParams;
-import android.media.SRTRenderer;
-import android.media.SubtitleController;
import android.media.TimedText;
-import android.media.TtmlRenderer;
-import android.media.WebVttRenderer;
import android.media.session.MediaController;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession;
@@ -59,6 +55,9 @@
import android.widget.VideoView2;
import com.android.media.RoutePlayer;
+import com.android.media.subtitle.ClosedCaptionRenderer;
+import com.android.media.subtitle.SubtitleController;
+import com.android.media.subtitle.SubtitleTrack;
import com.android.support.mediarouter.media.MediaItemStatus;
import com.android.support.mediarouter.media.MediaControlIntent;
import com.android.support.mediarouter.media.MediaRouter;
@@ -86,6 +85,7 @@
private static final int STATE_PLAYBACK_COMPLETED = 5;
private static final int INVALID_TRACK_INDEX = -1;
+ private static final float INVALID_SPEED = 0f;
private AccessibilityManager mAccessibilityManager;
private AudioManager mAudioManager;
@@ -123,7 +123,8 @@
private ArrayList<Integer> mVideoTrackIndices;
private ArrayList<Integer> mAudioTrackIndices;
- private ArrayList<Integer> mSubtitleTrackIndices;
+ private ArrayList<Pair<Integer, SubtitleTrack>> mSubtitleTrackIndices;
+ private SubtitleController mSubtitleController;
// selected video/audio/subtitle track index as MediaPlayer2 returns
private int mSelectedVideoTrackIndex;
@@ -137,6 +138,9 @@
// TODO: Remove mFallbackSpeed when integration with MediaPlayer2's new setPlaybackParams().
// Refer: https://docs.google.com/document/d/1nzAfns6i2hJ3RkaUre3QMT6wsDedJ5ONLiA_OOBFFX8/edit
private float mFallbackSpeed; // keep the original speed before 'pause' is called.
+ private float mVolumeLevelFloat;
+ private int mVolumeLevel;
+
private long mShowControllerIntervalMs;
private MediaRouter mMediaRouter;
@@ -631,18 +635,11 @@
mTextureView.setMediaPlayer(mMediaPlayer);
mCurrentView.assignSurfaceToMediaPlayer(mMediaPlayer);
- // TODO: create SubtitleController in MediaPlayer2, but we need
- // a context for the subtitle renderers
final Context context = mInstance.getContext();
- final SubtitleController controller = new SubtitleController(
- context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);
- controller.registerRenderer(new WebVttRenderer(context));
- controller.registerRenderer(new TtmlRenderer(context));
- controller.registerRenderer(new Cea708CaptionRenderer(context));
- controller.registerRenderer(new ClosedCaptionRenderer(context));
- controller.registerRenderer(new SRTRenderer(context));
- mMediaPlayer.setSubtitleAnchor(
- controller, (SubtitleController.Anchor) mSubtitleView);
+ // TODO: Add timely firing logic for more accurate sync between CC and video frame
+ mSubtitleController = new SubtitleController(context);
+ mSubtitleController.registerRenderer(new ClosedCaptionRenderer(context));
+ mSubtitleController.setAnchor((SubtitleController.Anchor) mSubtitleView);
Executor executor = new Executor() {
@Override
public void execute(Runnable runnable) {
@@ -651,9 +648,10 @@
};
mMediaPlayer.setMediaPlayer2EventCallback(executor, mMediaPlayer2Callback);
- mCurrentBufferPercentage = 0;
+ mCurrentBufferPercentage = -1;
mMediaPlayer.setDataSource(dsd);
mMediaPlayer.setAudioAttributes(mAudioAttributes);
+ mMediaPlayer.setOnSubtitleDataListener(mSubtitleListener);
// we don't set the target state here either, but preserve the
// target state that was there before.
mCurrentState = STATE_PREPARING;
@@ -753,8 +751,12 @@
&& mCurrentState != STATE_PREPARING) {
// TODO: this should be replaced with MediaPlayer2.getBufferedPosition() once it is
// implemented.
- mStateBuilder.setBufferedPosition(
- (long) (mCurrentBufferPercentage / 100.0) * mMediaPlayer.getDuration());
+ if (mCurrentBufferPercentage == -1) {
+ mStateBuilder.setBufferedPosition(-1);
+ } else {
+ mStateBuilder.setBufferedPosition(
+ (long) (mCurrentBufferPercentage / 100.0 * mMediaPlayer.getDuration()));
+ }
}
// Set PlaybackState for MediaSession
@@ -855,7 +857,8 @@
if (select) {
if (mSubtitleTrackIndices.size() > 0) {
// TODO: make this selection dynamic
- mSelectedSubtitleTrackIndex = mSubtitleTrackIndices.get(0);
+ mSelectedSubtitleTrackIndex = mSubtitleTrackIndices.get(0).first;
+ mSubtitleController.selectTrack(mSubtitleTrackIndices.get(0).second);
mMediaPlayer.selectTrack(mSelectedSubtitleTrackIndex);
mSubtitleView.setVisibility(View.VISIBLE);
}
@@ -873,6 +876,7 @@
mVideoTrackIndices = new ArrayList<>();
mAudioTrackIndices = new ArrayList<>();
mSubtitleTrackIndices = new ArrayList<>();
+ mSubtitleController.reset();
for (int i = 0; i < trackInfos.size(); ++i) {
int trackType = trackInfos.get(i).getTrackType();
if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_VIDEO) {
@@ -881,9 +885,20 @@
mAudioTrackIndices.add(i);
} else if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE
|| trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
- mSubtitleTrackIndices.add(i);
+ SubtitleTrack track = mSubtitleController.addTrack(trackInfos.get(i).getFormat());
+ if (track != null) {
+ mSubtitleTrackIndices.add(new Pair<>(i, track));
+ }
}
}
+ // Select first tracks as default
+ if (mVideoTrackIndices.size() > 0) {
+ mSelectedVideoTrackIndex = 0;
+ }
+ if (mAudioTrackIndices.size() > 0) {
+ mSelectedAudioTrackIndex = 0;
+ }
+
Bundle data = new Bundle();
data.putInt(MediaControlView2Impl.KEY_VIDEO_TRACK_COUNT, mVideoTrackIndices.size());
data.putInt(MediaControlView2Impl.KEY_AUDIO_TRACK_COUNT, mAudioTrackIndices.size());
@@ -894,6 +909,35 @@
mMediaSession.sendSessionEvent(MediaControlView2Impl.EVENT_UPDATE_TRACK_STATUS, data);
}
+ OnSubtitleDataListener mSubtitleListener =
+ new OnSubtitleDataListener() {
+ @Override
+ public void onSubtitleData(MediaPlayer2 mp, SubtitleData data) {
+ if (DEBUG) {
+ Log.d(TAG, "onSubtitleData(): getTrackIndex: " + data.getTrackIndex()
+ + ", getCurrentPosition: " + mp.getCurrentPosition()
+ + ", getStartTimeUs(): " + data.getStartTimeUs()
+ + ", diff: "
+ + (data.getStartTimeUs()/1000 - mp.getCurrentPosition())
+ + "ms, getDurationUs(): " + data.getDurationUs()
+ );
+
+ }
+ final int index = data.getTrackIndex();
+ if (index != mSelectedSubtitleTrackIndex) {
+ Log.d(TAG, "onSubtitleData(): getTrackIndex: " + data.getTrackIndex()
+ + ", selected track index: " + mSelectedSubtitleTrackIndex);
+ return;
+ }
+ for (Pair<Integer, SubtitleTrack> p : mSubtitleTrackIndices) {
+ if (p.first == index) {
+ SubtitleTrack track = p.second;
+ track.onData(data);
+ }
+ }
+ }
+ };
+
MediaPlayer2EventCallback mMediaPlayer2Callback =
new MediaPlayer2EventCallback() {
@Override
@@ -1048,7 +1092,16 @@
} else {
switch (command) {
case MediaControlView2Impl.COMMAND_SHOW_SUBTITLE:
- mInstance.setSubtitleEnabled(true);
+ int subtitleIndex = args.getInt(
+ MediaControlView2Impl.KEY_SELECTED_SUBTITLE_INDEX,
+ INVALID_TRACK_INDEX);
+ if (subtitleIndex != INVALID_TRACK_INDEX) {
+ int subtitleTrackIndex = mSubtitleTrackIndices.get(subtitleIndex).first;
+ if (subtitleTrackIndex != mSelectedSubtitleTrackIndex) {
+ mSelectedSubtitleTrackIndex = subtitleTrackIndex;
+ mInstance.setSubtitleEnabled(true);
+ }
+ }
break;
case MediaControlView2Impl.COMMAND_HIDE_SUBTITLE:
mInstance.setSubtitleEnabled(false);
@@ -1060,6 +1113,32 @@
args.getBoolean(MediaControlView2Impl.ARGUMENT_KEY_FULLSCREEN));
}
break;
+ case MediaControlView2Impl.COMMAND_SELECT_AUDIO_TRACK:
+ int audioIndex = args.getInt(MediaControlView2Impl.KEY_SELECTED_AUDIO_INDEX,
+ INVALID_TRACK_INDEX);
+ if (audioIndex != INVALID_TRACK_INDEX) {
+ int audioTrackIndex = mAudioTrackIndices.get(audioIndex);
+ if (audioTrackIndex != mSelectedAudioTrackIndex) {
+ mSelectedAudioTrackIndex = audioTrackIndex;
+ mMediaPlayer.selectTrack(mSelectedAudioTrackIndex);
+ }
+ }
+ break;
+ case MediaControlView2Impl.COMMAND_SET_PLAYBACK_SPEED:
+ float speed = args.getFloat(
+ MediaControlView2Impl.KEY_PLAYBACK_SPEED, INVALID_SPEED);
+ if (speed != INVALID_SPEED && speed != mSpeed) {
+ mInstance.setSpeed(speed);
+ mSpeed = speed;
+ }
+ break;
+ case MediaControlView2Impl.COMMAND_MUTE:
+ mVolumeLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);
+ break;
+ case MediaControlView2Impl.COMMAND_UNMUTE:
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mVolumeLevel, 0);
+ break;
}
}
showController();
diff --git a/packages/MediaComponents/tests/Android.mk b/packages/MediaComponents/tests/Android.mk
new file mode 100644
index 0000000..bf81efb
--- /dev/null
+++ b/packages/MediaComponents/tests/Android.mk
@@ -0,0 +1,35 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android.test.runner.stubs \
+ android.test.base.stubs \
+ junit
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := MediaComponentsTest
+
+LOCAL_INSTRUMENTATION_FOR := MediaComponents
+
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/packages/MediaComponents/tests/AndroidManifest.xml b/packages/MediaComponents/tests/AndroidManifest.xml
new file mode 100644
index 0000000..7255265
--- /dev/null
+++ b/packages/MediaComponents/tests/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.media.tests">
+
+ <application android:label="Media API Test">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <!--
+ To run the tests use the command:
+ "adb shell am instrument -w com.android.media.tests/android.test.InstrumentationTestRunner"
+ -->
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.media.update"
+ android:label="Media API test" />
+
+</manifest>
diff --git a/packages/MediaComponents/tests/src/com/android/media/SessionPlaylistAgentTest.java b/packages/MediaComponents/tests/src/com/android/media/SessionPlaylistAgentTest.java
new file mode 100644
index 0000000..80370ec
--- /dev/null
+++ b/packages/MediaComponents/tests/src/com/android/media/SessionPlaylistAgentTest.java
@@ -0,0 +1,515 @@
+/*
+ * 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.DataSourceDesc;
+import android.media.MediaItem2;
+import android.media.MediaMetadata2;
+import android.media.MediaPlayer2;
+import android.media.MediaPlayerBase;
+import android.media.MediaPlaylistAgent;
+import android.media.MediaSession2;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Tests {@link SessionPlaylistAgent}.
+ */
+public class SessionPlaylistAgentTest extends AndroidTestCase {
+ private static final String TAG = "SessionPlaylistAgentTest";
+ private static final int WAIT_TIME_MS = 1000;
+ private static final int INVALID_REPEAT_MODE = -100;
+ private static final int INVALID_SHUFFLE_MODE = -100;
+
+ private Handler mHandler;
+ private Executor mHandlerExecutor;
+
+ private Object mWaitLock = new Object();
+ private Context mContext;
+ private MediaSession2 mSession;
+ private MediaPlayerBase mPlayer;
+ private SessionPlaylistAgent mAgent;
+ private MyDataSourceHelper mDataSourceHelper;
+ private MyPlaylistEventCallback mEventCallback;
+
+ public class MyPlaylistEventCallback extends MediaPlaylistAgent.PlaylistEventCallback {
+ boolean onPlaylistChangedCalled;
+ boolean onPlaylistMetadataChangedCalled;
+ boolean onRepeatModeChangedCalled;
+ boolean onShuffleModeChangedCalled;
+
+ private Object mWaitLock;
+
+ public MyPlaylistEventCallback(Object waitLock) {
+ mWaitLock = waitLock;
+ }
+
+ public void clear() {
+ onPlaylistChangedCalled = false;
+ onPlaylistMetadataChangedCalled = false;
+ onRepeatModeChangedCalled = false;
+ onShuffleModeChangedCalled = false;
+ }
+
+ public void onPlaylistChanged(MediaPlaylistAgent playlistAgent, List<MediaItem2> list,
+ MediaMetadata2 metadata) {
+ synchronized (mWaitLock) {
+ onPlaylistChangedCalled = true;
+ mWaitLock.notify();
+ }
+ }
+
+ public void onPlaylistMetadataChanged(MediaPlaylistAgent playlistAgent,
+ MediaMetadata2 metadata) {
+ synchronized (mWaitLock) {
+ onPlaylistMetadataChangedCalled = true;
+ mWaitLock.notify();
+ }
+ }
+
+ public void onRepeatModeChanged(MediaPlaylistAgent playlistAgent, int repeatMode) {
+ synchronized (mWaitLock) {
+ onRepeatModeChangedCalled = true;
+ mWaitLock.notify();
+ }
+ }
+
+ public void onShuffleModeChanged(MediaPlaylistAgent playlistAgent, int shuffleMode) {
+ synchronized (mWaitLock) {
+ onShuffleModeChangedCalled = true;
+ mWaitLock.notify();
+ }
+ }
+ }
+
+ public class MyDataSourceHelper implements MediaSession2.OnDataSourceMissingHelper {
+ @Override
+ public DataSourceDesc onDataSourceMissing(MediaSession2 session, MediaItem2 item) {
+ if (item.getMediaId().contains("WITHOUT_DSD")) {
+ return null;
+ }
+ return new DataSourceDesc.Builder()
+ .setDataSource(getContext(), Uri.parse("dsd://test"))
+ .setMediaId(item.getMediaId())
+ .build();
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ HandlerThread handlerThread = new HandlerThread("SessionPlaylistAgent");
+ handlerThread.start();
+ mHandler = new Handler(handlerThread.getLooper());
+ mHandlerExecutor = (runnable) -> {
+ mHandler.post(runnable);
+ };
+
+ mContext = getContext();
+ mPlayer = MediaPlayer2.create();
+ mSession = new MediaSession2.Builder(mContext)
+ .setPlayer(mPlayer)
+ .setSessionCallback(mHandlerExecutor,
+ new MediaSession2.SessionCallback(mContext) {})
+ .setId(TAG).build();
+ mDataSourceHelper = new MyDataSourceHelper();
+ mAgent = new SessionPlaylistAgent(mContext, mSession, mPlayer);
+ mAgent.setOnDataSourceMissingHelper(mDataSourceHelper);
+ mEventCallback = new MyPlaylistEventCallback(mWaitLock);
+ mAgent.registerPlaylistEventCallback(mHandlerExecutor, mEventCallback);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mSession.close();
+ mPlayer.close();
+ mHandler.getLooper().quitSafely();
+ mHandler = null;
+ mHandlerExecutor = null;
+ }
+
+ @Test
+ public void testSetAndGetShuflleMode() throws Exception {
+ int shuffleMode = mAgent.getShuffleMode();
+ if (shuffleMode != MediaPlaylistAgent.SHUFFLE_MODE_NONE) {
+ mEventCallback.clear();
+ synchronized (mWaitLock) {
+ mAgent.setShuffleMode(MediaPlaylistAgent.SHUFFLE_MODE_NONE);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onShuffleModeChangedCalled);
+ }
+ assertEquals(MediaPlaylistAgent.SHUFFLE_MODE_NONE, mAgent.getShuffleMode());
+ }
+
+ mEventCallback.clear();
+ synchronized (mWaitLock) {
+ mAgent.setShuffleMode(MediaPlaylistAgent.SHUFFLE_MODE_ALL);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onShuffleModeChangedCalled);
+ }
+ assertEquals(MediaPlaylistAgent.SHUFFLE_MODE_ALL, mAgent.getShuffleMode());
+
+ mEventCallback.clear();
+ synchronized (mWaitLock) {
+ mAgent.setShuffleMode(MediaPlaylistAgent.SHUFFLE_MODE_GROUP);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onShuffleModeChangedCalled);
+ }
+ assertEquals(MediaPlaylistAgent.SHUFFLE_MODE_GROUP, mAgent.getShuffleMode());
+
+ // INVALID_SHUFFLE_MODE will not change the shuffle mode.
+ mEventCallback.clear();
+ synchronized (mWaitLock) {
+ mAgent.setShuffleMode(INVALID_SHUFFLE_MODE);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertFalse(mEventCallback.onShuffleModeChangedCalled);
+ }
+ assertEquals(MediaPlaylistAgent.SHUFFLE_MODE_GROUP, mAgent.getShuffleMode());
+ }
+
+ @Test
+ public void testSetAndGetRepeatMode() throws Exception {
+ int repeatMode = mAgent.getRepeatMode();
+ if (repeatMode != MediaPlaylistAgent.REPEAT_MODE_NONE) {
+ mEventCallback.clear();
+ synchronized (mWaitLock) {
+ mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_NONE);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onRepeatModeChangedCalled);
+ }
+ assertEquals(MediaPlaylistAgent.REPEAT_MODE_NONE, mAgent.getRepeatMode());
+ }
+
+ mEventCallback.clear();
+ synchronized (mWaitLock) {
+ mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_ONE);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onRepeatModeChangedCalled);
+ }
+ assertEquals(MediaPlaylistAgent.REPEAT_MODE_ONE, mAgent.getRepeatMode());
+
+ mEventCallback.clear();
+ synchronized (mWaitLock) {
+ mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_ALL);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onRepeatModeChangedCalled);
+ }
+ assertEquals(MediaPlaylistAgent.REPEAT_MODE_ALL, mAgent.getRepeatMode());
+
+ mEventCallback.clear();
+ synchronized (mWaitLock) {
+ mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_GROUP);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onRepeatModeChangedCalled);
+ }
+ assertEquals(MediaPlaylistAgent.REPEAT_MODE_GROUP, mAgent.getRepeatMode());
+
+ // INVALID_SHUFFLE_MODE will not change the shuffle mode.
+ mEventCallback.clear();
+ synchronized (mWaitLock) {
+ mAgent.setRepeatMode(INVALID_REPEAT_MODE);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertFalse(mEventCallback.onRepeatModeChangedCalled);
+ }
+ assertEquals(MediaPlaylistAgent.REPEAT_MODE_GROUP, mAgent.getRepeatMode());
+ }
+
+ @Test
+ public void testSetPlaylist() throws Exception {
+ int listSize = 10;
+ createAndSetPlaylist(10);
+ assertEquals(listSize, mAgent.getPlaylist().size());
+ assertEquals(0, mAgent.getCurShuffledIndex());
+ }
+
+ @Test
+ public void testSkipItems() throws Exception {
+ int listSize = 5;
+ List<MediaItem2> playlist = createAndSetPlaylist(listSize);
+
+ mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_NONE);
+ // Test skipToPlaylistItem
+ for (int i = listSize - 1; i >= 0; --i) {
+ mAgent.skipToPlaylistItem(playlist.get(i));
+ assertEquals(i, mAgent.getCurShuffledIndex());
+ }
+
+ // Test skipToNextItem
+ // curPlayPos = 0
+ for (int curPlayPos = 0; curPlayPos < listSize - 1; ++curPlayPos) {
+ mAgent.skipToNextItem();
+ assertEquals(curPlayPos + 1, mAgent.getCurShuffledIndex());
+ }
+ mAgent.skipToNextItem();
+ assertEquals(listSize - 1, mAgent.getCurShuffledIndex());
+
+ // Test skipToPrevious
+ // curPlayPos = listSize - 1
+ for (int curPlayPos = listSize - 1; curPlayPos > 0; --curPlayPos) {
+ mAgent.skipToPreviousItem();
+ assertEquals(curPlayPos - 1, mAgent.getCurShuffledIndex());
+ }
+ mAgent.skipToPreviousItem();
+ assertEquals(0, mAgent.getCurShuffledIndex());
+
+ mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_ALL);
+ // Test skipToPrevious with repeat mode all
+ // curPlayPos = 0
+ mAgent.skipToPreviousItem();
+ assertEquals(listSize - 1, mAgent.getCurShuffledIndex());
+
+ // Test skipToNext with repeat mode all
+ // curPlayPos = listSize - 1
+ mAgent.skipToNextItem();
+ assertEquals(0, mAgent.getCurShuffledIndex());
+
+ mAgent.skipToPreviousItem();
+ // curPlayPos = listSize - 1, nextPlayPos = 0
+ // Test next play pos after setting repeat mode none.
+ mAgent.setRepeatMode(MediaPlaylistAgent.REPEAT_MODE_NONE);
+ assertEquals(listSize - 1, mAgent.getCurShuffledIndex());
+ }
+
+ @Test
+ public void testEditPlaylist() throws Exception {
+ int listSize = 5;
+ List<MediaItem2> playlist = createAndSetPlaylist(listSize);
+
+ // Test add item: [0 (cur), 1, 2, 3, 4] -> [0 (cur), 1, 5, 2, 3, 4]
+ mEventCallback.clear();
+ MediaItem2 item_5 = generateMediaItem(5);
+ synchronized (mWaitLock) {
+ playlist.add(2, item_5);
+ mAgent.addPlaylistItem(2, item_5);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onPlaylistChangedCalled);
+ }
+ assertPlaylistEquals(playlist, mAgent.getPlaylist());
+
+ mEventCallback.clear();
+ // Move current: [0 (cur), 1, 5, 2, 3, 4] -> [0, 1, 5 (cur), 2, 3, 4]
+ mAgent.skipToPlaylistItem(item_5);
+ // Remove current item: [0, 1, 5 (cur), 2, 3, 4] -> [0, 1, 2 (cur), 3, 4]
+ synchronized (mWaitLock) {
+ playlist.remove(item_5);
+ mAgent.removePlaylistItem(item_5);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onPlaylistChangedCalled);
+ }
+ assertPlaylistEquals(playlist, mAgent.getPlaylist());
+ assertEquals(2, mAgent.getCurShuffledIndex());
+
+ // Remove previous item: [0, 1, 2 (cur), 3, 4] -> [0, 2 (cur), 3, 4]
+ mEventCallback.clear();
+ MediaItem2 previousItem = playlist.get(1);
+ synchronized (mWaitLock) {
+ playlist.remove(previousItem);
+ mAgent.removePlaylistItem(previousItem);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onPlaylistChangedCalled);
+ }
+ assertPlaylistEquals(playlist, mAgent.getPlaylist());
+ assertEquals(1, mAgent.getCurShuffledIndex());
+
+ // Remove next item: [0, 2 (cur), 3, 4] -> [0, 2 (cur), 4]
+ mEventCallback.clear();
+ MediaItem2 nextItem = playlist.get(2);
+ synchronized (mWaitLock) {
+ playlist.remove(nextItem);
+ mAgent.removePlaylistItem(nextItem);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onPlaylistChangedCalled);
+ }
+ assertPlaylistEquals(playlist, mAgent.getPlaylist());
+ assertEquals(1, mAgent.getCurShuffledIndex());
+
+ // Replace item: [0, 2 (cur), 4] -> [0, 2 (cur), 5]
+ mEventCallback.clear();
+ synchronized (mWaitLock) {
+ playlist.set(2, item_5);
+ mAgent.replacePlaylistItem(2, item_5);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onPlaylistChangedCalled);
+ }
+ assertPlaylistEquals(playlist, mAgent.getPlaylist());
+ assertEquals(1, mAgent.getCurShuffledIndex());
+
+ // Move last and remove the last item: [0, 2 (cur), 5] -> [0, 2, 5 (cur)] -> [0, 2 (cur)]
+ MediaItem2 lastItem = playlist.get(1);
+ mAgent.skipToPlaylistItem(lastItem);
+ mEventCallback.clear();
+ synchronized (mWaitLock) {
+ playlist.remove(lastItem);
+ mAgent.removePlaylistItem(lastItem);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onPlaylistChangedCalled);
+ }
+ assertPlaylistEquals(playlist, mAgent.getPlaylist());
+ assertEquals(1, mAgent.getCurShuffledIndex());
+
+ // Remove all items
+ for (int i = playlist.size() - 1; i >= 0; --i) {
+ MediaItem2 item = playlist.get(i);
+ mAgent.skipToPlaylistItem(item);
+ mEventCallback.clear();
+ synchronized (mWaitLock) {
+ playlist.remove(item);
+ mAgent.removePlaylistItem(item);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onPlaylistChangedCalled);
+ }
+ assertPlaylistEquals(playlist, mAgent.getPlaylist());
+ }
+ assertEquals(SessionPlaylistAgent.NO_VALID_ITEMS, mAgent.getCurShuffledIndex());
+ }
+
+
+ @Test
+ public void testPlaylistWithInvalidItem() throws Exception {
+ int listSize = 2;
+ List<MediaItem2> playlist = createAndSetPlaylist(listSize);
+
+ // Add item: [0 (cur), 1] -> [0 (cur), 3 (no_dsd), 1]
+ mEventCallback.clear();
+ MediaItem2 invalidItem2 = generateMediaItemWithoutDataSourceDesc(2);
+ synchronized (mWaitLock) {
+ playlist.add(1, invalidItem2);
+ mAgent.addPlaylistItem(1, invalidItem2);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onPlaylistChangedCalled);
+ }
+ assertPlaylistEquals(playlist, mAgent.getPlaylist());
+ assertEquals(0, mAgent.getCurShuffledIndex());
+
+ // Test skip to next item: [0 (cur), 2 (no_dsd), 1] -> [0, 2 (no_dsd), 1 (cur)]
+ mAgent.skipToNextItem();
+ assertEquals(2, mAgent.getCurShuffledIndex());
+
+ // Test skip to previous item: [0, 2 (no_dsd), 1 (cur)] -> [0 (cur), 2 (no_dsd), 1]
+ mAgent.skipToPreviousItem();
+ assertEquals(0, mAgent.getCurShuffledIndex());
+
+ // Remove current item: [0 (cur), 2 (no_dsd), 1] -> [2 (no_dsd), 1 (cur)]
+ mEventCallback.clear();
+ MediaItem2 item = playlist.get(0);
+ synchronized (mWaitLock) {
+ playlist.remove(item);
+ mAgent.removePlaylistItem(item);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onPlaylistChangedCalled);
+ }
+ assertPlaylistEquals(playlist, mAgent.getPlaylist());
+ assertEquals(1, mAgent.getCurShuffledIndex());
+
+ // Remove current item: [2 (no_dsd), 1 (cur)] -> [2 (no_dsd)]
+ mEventCallback.clear();
+ item = playlist.get(1);
+ synchronized (mWaitLock) {
+ playlist.remove(item);
+ mAgent.removePlaylistItem(item);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onPlaylistChangedCalled);
+ }
+ assertPlaylistEquals(playlist, mAgent.getPlaylist());
+ assertEquals(SessionPlaylistAgent.NO_VALID_ITEMS, mAgent.getCurShuffledIndex());
+
+ // Add invalid item: [2 (no_dsd)] -> [0 (no_dsd), 2 (no_dsd)]
+ MediaItem2 invalidItem0 = generateMediaItemWithoutDataSourceDesc(0);
+ mEventCallback.clear();
+ synchronized (mWaitLock) {
+ playlist.add(0, invalidItem0);
+ mAgent.addPlaylistItem(0, invalidItem0);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onPlaylistChangedCalled);
+ }
+ assertPlaylistEquals(playlist, mAgent.getPlaylist());
+ assertEquals(SessionPlaylistAgent.NO_VALID_ITEMS, mAgent.getCurShuffledIndex());
+
+ // Add valid item: [0 (no_dsd), 2 (no_dsd)] -> [0 (no_dsd), 1, 2 (no_dsd)]
+ MediaItem2 invalidItem1 = generateMediaItem(1);
+ mEventCallback.clear();
+ synchronized (mWaitLock) {
+ playlist.add(1, invalidItem1);
+ mAgent.addPlaylistItem(1, invalidItem1);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onPlaylistChangedCalled);
+ }
+ assertPlaylistEquals(playlist, mAgent.getPlaylist());
+ assertEquals(1, mAgent.getCurShuffledIndex());
+
+ // Replace the valid item with an invalid item:
+ // [0 (no_dsd), 1 (cur), 2 (no_dsd)] -> [0 (no_dsd), 3 (no_dsd), 2 (no_dsd)]
+ MediaItem2 invalidItem3 = generateMediaItemWithoutDataSourceDesc(3);
+ mEventCallback.clear();
+ synchronized (mWaitLock) {
+ playlist.set(1, invalidItem3);
+ mAgent.replacePlaylistItem(1, invalidItem3);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onPlaylistChangedCalled);
+ }
+ assertPlaylistEquals(playlist, mAgent.getPlaylist());
+ assertEquals(SessionPlaylistAgent.END_OF_PLAYLIST, mAgent.getCurShuffledIndex());
+ }
+
+ private List<MediaItem2> createAndSetPlaylist(int listSize) throws Exception {
+ List<MediaItem2> items = new ArrayList<>();
+ for (int i = 0; i < listSize; ++i) {
+ items.add(generateMediaItem(i));
+ }
+ mEventCallback.clear();
+ synchronized (mWaitLock) {
+ mAgent.setPlaylist(items, null);
+ mWaitLock.wait(WAIT_TIME_MS);
+ assertTrue(mEventCallback.onPlaylistChangedCalled);
+ }
+ return items;
+ }
+
+ private void assertPlaylistEquals(List<MediaItem2> expected, List<MediaItem2> actual) {
+ if (expected == actual) {
+ return;
+ }
+ assertTrue(expected != null && actual != null);
+ assertEquals(expected.size(), actual.size());
+ for (int i = 0; i < expected.size(); ++i) {
+ assertTrue(expected.get(i).equals(actual.get(i)));
+ }
+ }
+
+ private MediaItem2 generateMediaItemWithoutDataSourceDesc(int key) {
+ return new MediaItem2.Builder(mContext, 0)
+ .setMediaId("TEST_MEDIA_ID_WITHOUT_DSD_" + key)
+ .build();
+ }
+
+ private MediaItem2 generateMediaItem(int key) {
+ return new MediaItem2.Builder(mContext, 0)
+ .setMediaId("TEST_MEDIA_ID_" + key)
+ .build();
+ }
+}
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 8033382..ea06b6c 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -3139,16 +3139,21 @@
bool pinned = (sessionId > AUDIO_SESSION_OUTPUT_MIX) && isSessionAcquired_l(sessionId);
handle = thread->createEffect_l(client, effectClient, priority, sessionId,
&desc, enabled, &lStatus, pinned);
- if (handle != 0 && id != NULL) {
- *id = handle->id();
- }
- if (handle == 0) {
+ if (lStatus != NO_ERROR && lStatus != ALREADY_EXISTS) {
// remove local strong reference to Client with mClientLock held
Mutex::Autolock _cl(mClientLock);
client.clear();
+ } else {
+ // handle must be valid here, but check again to be safe.
+ if (handle.get() != nullptr && id != nullptr) *id = handle->id();
}
}
+ if (lStatus != NO_ERROR && lStatus != ALREADY_EXISTS) {
+ // handle must be cleared outside lock.
+ handle.clear();
+ }
+
Exit:
*status = lStatus;
return handle;
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 3134323..62e9fe7 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -1360,7 +1360,7 @@
if (chainCreated) {
removeEffectChain_l(chain);
}
- handle.clear();
+ // handle must be cleared by caller to avoid deadlock.
}
*status = lStatus;
@@ -3481,7 +3481,8 @@
if (diff > 0) {
// notify of throttle end on debug log
// but prevent spamming for bluetooth
- ALOGD_IF(!audio_is_a2dp_out_device(outDevice()),
+ ALOGD_IF(!audio_is_a2dp_out_device(outDevice()) &&
+ !audio_is_hearing_aid_out_device(outDevice()),
"mixer(%p) throttle end: throttle time(%u)", this, diff);
mThreadThrottleEndMs = mThreadThrottleTimeMs;
}
@@ -8523,7 +8524,11 @@
audio_devices_t outDevice, audio_devices_t inDevice, bool systemReady)
: MmapThread(audioFlinger, id, hwDev, output->stream, outDevice, inDevice, systemReady),
mStreamType(AUDIO_STREAM_MUSIC),
- mStreamVolume(1.0), mStreamMute(false), mOutput(output)
+ mStreamVolume(1.0),
+ mStreamMute(false),
+ mHalVolFloat(-1.0f), // Initialize to illegal value so it always gets set properly later.
+ mNoCallbackWarningCount(0),
+ mOutput(output)
{
snprintf(mThreadName, kThreadNameLength, "AudioMmapOut_%X", id);
mChannelCount = audio_channel_count_from_out_mask(mChannelMask);
@@ -8631,7 +8636,6 @@
}
if (volume != mHalVolFloat) {
- mHalVolFloat = volume;
// Convert volumes from float to 8.24
uint32_t vol = (uint32_t)(volume * (1 << 24));
@@ -8644,7 +8648,10 @@
volume = (float)vol / (1 << 24);
}
// Try to use HW volume control and fall back to SW control if not implemented
- if (mOutput->stream->setVolume(volume, volume) != NO_ERROR) {
+ if (mOutput->stream->setVolume(volume, volume) == NO_ERROR) {
+ mHalVolFloat = volume; // HW volume control worked, so update value.
+ mNoCallbackWarningCount = 0;
+ } else {
sp<MmapStreamCallback> callback = mCallback.promote();
if (callback != 0) {
int channelCount;
@@ -8658,8 +8665,13 @@
values.add(volume);
}
callback->onVolumeChanged(mChannelMask, values);
+ mHalVolFloat = volume; // SW volume control worked, so update value.
+ mNoCallbackWarningCount = 0;
} else {
- ALOGW("Could not set MMAP stream volume: no volume callback!");
+ if (mNoCallbackWarningCount < kMaxNoCallbackWarnings) {
+ ALOGW("Could not set MMAP stream volume: no volume callback!");
+ mNoCallbackWarningCount++;
+ }
}
}
}
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index ae14ac1..7cd46a7 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -1666,6 +1666,8 @@
bool mMasterMute;
bool mStreamMute;
float mHalVolFloat;
+ int32_t mNoCallbackWarningCount;
+ static constexpr int32_t kMaxNoCallbackWarnings = 5;
AudioStreamOut* mOutput;
};
diff --git a/services/audiopolicy/common/include/Volume.h b/services/audiopolicy/common/include/Volume.h
index 1239fe0..4862684 100644
--- a/services/audiopolicy/common/include/Volume.h
+++ b/services/audiopolicy/common/include/Volume.h
@@ -125,6 +125,7 @@
case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP:
case AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES:
case AUDIO_DEVICE_OUT_USB_HEADSET:
+ case AUDIO_DEVICE_OUT_HEARING_AID:
return DEVICE_CATEGORY_HEADSET;
case AUDIO_DEVICE_OUT_LINE:
case AUDIO_DEVICE_OUT_AUX_DIGITAL:
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
index 78a184b..5e5d38b 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
@@ -203,6 +203,16 @@
*/
audio_io_handle_t getA2dpOutput() const;
+ /**
+ * returns true if primary HAL supports A2DP Offload
+ */
+ bool isA2dpOffloadedOnPrimary() const;
+
+ /**
+ * returns true if A2DP is supported (either via hardware offload or software encoding)
+ */
+ bool isA2dpSupported() const;
+
sp<SwAudioOutputDescriptor> getOutputFromId(audio_port_handle_t id) const;
sp<SwAudioOutputDescriptor> getPrimaryOutput() const;
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
index caaa0f7..294a2a6 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
@@ -645,6 +645,29 @@
return 0;
}
+bool SwAudioOutputCollection::isA2dpOffloadedOnPrimary() const
+{
+ sp<SwAudioOutputDescriptor> primaryOutput = getPrimaryOutput();
+
+ if ((primaryOutput != NULL) && (primaryOutput->mProfile != NULL)
+ && (primaryOutput->mProfile->mModule != NULL)) {
+ sp<HwModule> primaryHwModule = primaryOutput->mProfile->mModule;
+ Vector <sp<IOProfile>> primaryHwModuleOutputProfiles =
+ primaryHwModule->getOutputProfiles();
+ for (size_t i = 0; i < primaryHwModuleOutputProfiles.size(); i++) {
+ if (primaryHwModuleOutputProfiles[i]->supportDevice(AUDIO_DEVICE_OUT_ALL_A2DP)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool SwAudioOutputCollection::isA2dpSupported() const
+{
+ return (isA2dpOffloadedOnPrimary() || (getA2dpOutput() != 0));
+}
+
sp<SwAudioOutputDescriptor> SwAudioOutputCollection::getPrimaryOutput() const
{
for (size_t i = 0; i < size(); i++) {
diff --git a/services/audiopolicy/config/audio_policy_configuration.xml b/services/audiopolicy/config/audio_policy_configuration.xml
index 73efe8e..a75f1cb 100644
--- a/services/audiopolicy/config/audio_policy_configuration.xml
+++ b/services/audiopolicy/config/audio_policy_configuration.xml
@@ -182,6 +182,9 @@
<!-- Remote Submix Audio HAL -->
<xi:include href="r_submix_audio_policy_configuration.xml"/>
+ <!-- Hearing aid Audio HAL -->
+ <xi:include href="hearing_aid_audio_policy_configuration.xml"/>
+
</modules>
<!-- End of Modules section -->
diff --git a/services/audiopolicy/config/hearing_aid_audio_policy_configuration.xml b/services/audiopolicy/config/hearing_aid_audio_policy_configuration.xml
new file mode 100644
index 0000000..3c48e88
--- /dev/null
+++ b/services/audiopolicy/config/hearing_aid_audio_policy_configuration.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Hearing aid Audio HAL Audio Policy Configuration file -->
+<module name="hearing_aid" halVersion="2.0">
+ <mixPorts>
+ <mixPort name="hearing aid output" role="source" flags="AUDIO_OUTPUT_FLAG_PRIMARY">
+ <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
+ samplingRates="24000,16000"
+ channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
+ </mixPort>
+ </mixPorts>
+ <devicePorts>
+ <devicePort tagName="BT Hearing Aid Out" type="AUDIO_DEVICE_OUT_HEARING_AID" role="sink"/>
+ </devicePorts>
+ <routes>
+ <route type="mix" sink="BT Hearing Aid Out" sources="hearing aid output"/>
+ </routes>
+</module>
diff --git a/services/audiopolicy/enginedefault/src/Engine.cpp b/services/audiopolicy/enginedefault/src/Engine.cpp
index 5ec0475..977a396 100644
--- a/services/audiopolicy/enginedefault/src/Engine.cpp
+++ b/services/audiopolicy/enginedefault/src/Engine.cpp
@@ -306,8 +306,14 @@
sp<AudioOutputDescriptor> primaryOutput = outputs.getPrimaryOutput();
audio_devices_t availPrimaryInputDevices =
availableInputDevices.getDevicesFromHwModule(primaryOutput->getModuleHandle());
+
+ // TODO: getPrimaryOutput return only devices from first module in
+ // audio_policy_configuration.xml, hearing aid is not there, but it's
+ // a primary device
+ // FIXME: this is not the right way of solving this problem
audio_devices_t availPrimaryOutputDevices =
- primaryOutput->supportedDevices() & availableOutputDevices.types();
+ (primaryOutput->supportedDevices() | AUDIO_DEVICE_OUT_HEARING_AID) &
+ availableOutputDevices.types();
if (((availableInputDevices.types() &
AUDIO_DEVICE_IN_TELEPHONY_RX & ~AUDIO_DEVICE_BIT_IN) == 0) ||
@@ -332,10 +338,12 @@
// FALL THROUGH
default: // FORCE_NONE
+ device = availableOutputDevicesType & AUDIO_DEVICE_OUT_HEARING_AID;
+ if (device) break;
// when not in a phone call, phone strategy should route STREAM_VOICE_CALL to A2DP
if (!isInCall() &&
(mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&
- (outputs.getA2dpOutput() != 0)) {
+ outputs.isA2dpSupported()) {
device = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
if (device) break;
device = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
@@ -369,7 +377,7 @@
// A2DP speaker when forcing to speaker output
if (!isInCall() &&
(mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&
- (outputs.getA2dpOutput() != 0)) {
+ outputs.isA2dpSupported()) {
device = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER;
if (device) break;
}
@@ -481,9 +489,12 @@
outputDeviceTypesToIgnore);
break;
}
+ if (device2 == AUDIO_DEVICE_NONE) {
+ device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_HEARING_AID;
+ }
if ((device2 == AUDIO_DEVICE_NONE) &&
(mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&
- (outputs.getA2dpOutput() != 0)) {
+ outputs.isA2dpSupported()) {
device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
if (device2 == AUDIO_DEVICE_NONE) {
device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 42b199a..73d92ac 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -26,6 +26,7 @@
#define AUDIO_POLICY_XML_CONFIG_FILE_PATH_MAX_LENGTH 128
#define AUDIO_POLICY_XML_CONFIG_FILE_NAME "audio_policy_configuration.xml"
+#define AUDIO_POLICY_A2DP_OFFLOAD_XML_CONFIG_FILE_NAME "audio_policy_a2dp_offload_configuration.xml"
#include <inttypes.h>
#include <math.h>
@@ -1265,6 +1266,11 @@
// We do not introduce additional delay here.
}
+ if (stream == AUDIO_STREAM_ENFORCED_AUDIBLE &&
+ mEngine->getForceUse(AUDIO_POLICY_FORCE_FOR_SYSTEM) == AUDIO_POLICY_FORCE_SYSTEM_ENFORCED) {
+ setStrategyMute(STRATEGY_SONIFICATION, true, outputDesc);
+ }
+
return NO_ERROR;
}
@@ -1365,6 +1371,12 @@
// update the outputs if stopping one with a stream that can affect notification routing
handleNotificationRoutingForStream(stream);
}
+
+ if (stream == AUDIO_STREAM_ENFORCED_AUDIBLE &&
+ mEngine->getForceUse(AUDIO_POLICY_FORCE_FOR_SYSTEM) == AUDIO_POLICY_FORCE_SYSTEM_ENFORCED) {
+ setStrategyMute(STRATEGY_SONIFICATION, false, outputDesc);
+ }
+
if (stream == AUDIO_STREAM_MUSIC) {
selectOutputForMusicEffects();
}
@@ -3517,11 +3529,14 @@
for (int i = 0; i < kConfigLocationListSize; i++) {
PolicySerializer serializer;
+ bool use_a2dp_offload_config =
+ property_get_bool("persist.bluetooth.a2dp_offload.enable", false);
snprintf(audioPolicyXmlConfigFile,
sizeof(audioPolicyXmlConfigFile),
"%s/%s",
kConfigLocationList[i],
- AUDIO_POLICY_XML_CONFIG_FILE_NAME);
+ use_a2dp_offload_config ? AUDIO_POLICY_A2DP_OFFLOAD_XML_CONFIG_FILE_NAME :
+ AUDIO_POLICY_XML_CONFIG_FILE_NAME);
ret = serializer.deserialize(audioPolicyXmlConfigFile, config);
if (ret == NO_ERROR) {
break;
@@ -4381,7 +4396,7 @@
void AudioPolicyManager::checkA2dpSuspend()
{
audio_io_handle_t a2dpOutput = mOutputs.getA2dpOutput();
- if (a2dpOutput == 0) {
+ if (a2dpOutput == 0 || mOutputs.isA2dpOffloadedOnPrimary()) {
mA2dpSuspended = false;
return;
}
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
index 9ab2d88..c49de8e 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
@@ -1002,10 +1002,6 @@
case HAL_PIXEL_FORMAT_BLOB:
case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
case HAL_PIXEL_FORMAT_YCbCr_420_888:
- case HAL_PIXEL_FORMAT_YCbCr_422_888:
- case HAL_PIXEL_FORMAT_YCbCr_444_888:
- case HAL_PIXEL_FORMAT_FLEX_RGB_888:
- case HAL_PIXEL_FORMAT_FLEX_RGBA_8888:
case HAL_PIXEL_FORMAT_YCbCr_422_SP:
case HAL_PIXEL_FORMAT_YCrCb_420_SP:
case HAL_PIXEL_FORMAT_YCbCr_422_I:
diff --git a/services/camera/libcameraservice/common/CameraProviderManager.cpp b/services/camera/libcameraservice/common/CameraProviderManager.cpp
index b37f004..077e05e 100644
--- a/services/camera/libcameraservice/common/CameraProviderManager.cpp
+++ b/services/camera/libcameraservice/common/CameraProviderManager.cpp
@@ -39,9 +39,6 @@
const std::string kLegacyProviderName("legacy/0");
const std::string kExternalProviderName("external/0");
-// Slash-separated list of provider types to consider for use via the old camera API
-const std::string kStandardProviderTypes("internal/legacy/external");
-
} // anonymous namespace
CameraProviderManager::HardwareServiceInteractionProxy
@@ -101,10 +98,8 @@
std::lock_guard<std::mutex> lock(mInterfaceMutex);
std::vector<std::string> deviceIds;
for (auto& provider : mProviders) {
- if (kStandardProviderTypes.find(provider->getType()) != std::string::npos) {
- for (auto& id : provider->mUniqueAPI1CompatibleCameraIds) {
- deviceIds.push_back(id);
- }
+ for (auto& id : provider->mUniqueAPI1CompatibleCameraIds) {
+ deviceIds.push_back(id);
}
}
diff --git a/services/camera/libcameraservice/device3/Camera3StreamSplitter.cpp b/services/camera/libcameraservice/device3/Camera3StreamSplitter.cpp
index e3bb5dc..f4d5a18 100644
--- a/services/camera/libcameraservice/device3/Camera3StreamSplitter.cpp
+++ b/services/camera/libcameraservice/device3/Camera3StreamSplitter.cpp
@@ -423,12 +423,20 @@
__FUNCTION__, gbp.get(), strerror(-res), res);
return res;
}
+ if ((slot < 0) || (slot > BufferQueue::NUM_BUFFER_SLOTS)) {
+ SP_LOGE("%s: Slot received %d either bigger than expected maximum %d or negative!",
+ __FUNCTION__, slot, BufferQueue::NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ }
//During buffer attach 'mMutex' is not held which makes the removal of
//"gbp" possible. Check whether this is the case and continue.
if (mOutputSlots[gbp] == nullptr) {
continue;
}
auto& outputSlots = *mOutputSlots[gbp];
+ if (static_cast<size_t> (slot + 1) > outputSlots.size()) {
+ outputSlots.resize(slot + 1);
+ }
if (outputSlots[slot] != nullptr) {
// If the buffer is attached to a slot which already contains a buffer,
// the previous buffer will be removed from the output queue. Decrement
diff --git a/services/mediacodec/seccomp_policy/mediacodec-x86.policy b/services/mediacodec/seccomp_policy/mediacodec-x86.policy
index 1553ec7..bbbe552 100644
--- a/services/mediacodec/seccomp_policy/mediacodec-x86.policy
+++ b/services/mediacodec/seccomp_policy/mediacodec-x86.policy
@@ -16,12 +16,14 @@
mprotect: 1
prctl: 1
openat: 1
+open: 1
getuid32: 1
writev: 1
ioctl: 1
close: 1
mmap2: 1
fstat64: 1
+stat64: 1
madvise: 1
fstatat64: 1
futex: 1