Merge "Camera: Resize the output slot vector when needed" into pi-dev
diff --git a/camera/ndk/include/camera/NdkCameraMetadataTags.h b/camera/ndk/include/camera/NdkCameraMetadataTags.h
index 839b134..940ac5f 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
*/
@@ -5500,9 +5501,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,
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/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/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/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index 6fd5677..3570aaf 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -552,6 +552,7 @@
mNativeWindowUsageBits(0),
mLastNativeWindowDataSpace(HAL_DATASPACE_UNKNOWN),
mIsVideo(false),
+ mIsImage(false),
mIsEncoder(false),
mFatalError(false),
mShutdownInProgress(false),
@@ -1712,6 +1713,7 @@
mIsEncoder = encoder;
mIsVideo = !strncasecmp(mime, "video/", 6);
+ mIsImage = !strncasecmp(mime, "image/", 6);
mPortMode[kPortIndexInput] = IOMX::kPortModePresetByteBuffer;
mPortMode[kPortIndexOutput] = IOMX::kPortModePresetByteBuffer;
@@ -1727,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;
}
@@ -2009,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.")) {
@@ -2289,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);
@@ -3214,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(
@@ -3871,7 +3875,8 @@
break;
case OMX_VIDEO_CodingHEVC:
- err = setupHEVCEncoderParameters(msg);
+ case OMX_VIDEO_CodingImageHEIC:
+ err = setupHEVCEncoderParameters(msg, outputFormat);
break;
case OMX_VIDEO_CodingVP8:
@@ -4377,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 gridWidth, gridHeight, gridRows, gridCols;
+ if (!msg->findInt32("grid-width", &gridWidth) ||
+ !msg->findInt32("grid-height", &gridHeight) ||
+ !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.nGridWidth = gridWidth;
+ gridType.nGridHeight = gridHeight;
+ 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("grid-width", gridType.nGridWidth);
+ outputFormat->setInt32("grid-height", gridType.nGridHeight);
+ 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;
@@ -4425,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));
@@ -4433,6 +4494,12 @@
return err;
}
+ err = configureImageGrid(msg, outputFormat);
+
+ if (err != OK) {
+ return err;
+ }
+
return configureBitrate(bitrateMode, bitrate, quality);
}
@@ -4874,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);
}
}
@@ -4930,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);
}
@@ -8207,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/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index c66a18f..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;
@@ -1217,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;
@@ -2610,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/NuMediaExtractor.cpp b/media/libstagefright/NuMediaExtractor.cpp
index 8c0cd3e..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>
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/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/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/MediaCodec.h b/media/libstagefright/include/media/stagefright/MediaCodec.h
index 5a7d26a..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',
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/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/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/layout/settings_list_item.xml b/packages/MediaComponents/res/layout/embedded_settings_list_item.xml
similarity index 63%
copy from packages/MediaComponents/res/layout/settings_list_item.xml
copy to packages/MediaComponents/res/layout/embedded_settings_list_item.xml
index 81b3275..1156dca 100644
--- a/packages/MediaComponents/res/layout/settings_list_item.xml
+++ b/packages/MediaComponents/res/layout/embedded_settings_list_item.xml
@@ -15,49 +15,49 @@
-->
<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:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_embedded_settings_height"
android:orientation="horizontal"
- android:background="@color/black_transparent_70">
+ android:background="@color/black_opacity_70">
<LinearLayout
android:layout_width="wrap_content"
- android:layout_height="@dimen/MediaControlView2_settings_height"
- android:paddingRight="2dp"
+ android:layout_height="@dimen/mcv2_embedded_settings_height"
android:gravity="center"
android:orientation="horizontal">
<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"/>
+ 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/MediaControlView2_settings_height"
+ 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/MediaControlView2_text_width"
+ android:layout_height="@dimen/mcv2_embedded_settings_text_height"
+ android:gravity="center"
android:paddingLeft="2dp"
android:textColor="@color/white"
- android:textSize="@dimen/MediaControlView2_settings_main_text_size"/>
+ android:textSize="@dimen/mcv2_embedded_settings_main_text_size"/>
<TextView
android:id="@+id/sub_text"
android:layout_width="wrap_content"
- android:layout_height="@dimen/MediaControlView2_text_width"
+ 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_transparent_70"
- android:textSize="@dimen/MediaControlView2_settings_sub_text_size"/>
+ android:textColor="@color/white_opacity_70"
+ android:textSize="@dimen/mcv2_embedded_settings_sub_text_size"/>
</RelativeLayout>
-
</LinearLayout>
diff --git a/packages/MediaComponents/res/layout/sub_settings_list_item.xml b/packages/MediaComponents/res/layout/embedded_sub_settings_list_item.xml
similarity index 66%
rename from packages/MediaComponents/res/layout/sub_settings_list_item.xml
rename to packages/MediaComponents/res/layout/embedded_sub_settings_list_item.xml
index 9de7f2b..5947a72 100644
--- a/packages/MediaComponents/res/layout/sub_settings_list_item.xml
+++ b/packages/MediaComponents/res/layout/embedded_sub_settings_list_item.xml
@@ -15,40 +15,39 @@
-->
<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:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_embedded_settings_height"
android:orientation="horizontal"
- android:background="@color/black_transparent_70">
+ android:background="@color/black_opacity_70">
<LinearLayout
android:layout_width="wrap_content"
- android:layout_height="@dimen/MediaControlView2_settings_height"
- android:paddingRight="2dp"
+ android:layout_height="@dimen/mcv2_embedded_settings_height"
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: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:paddingLeft="2dp"
android:src="@drawable/ic_check"/>
</LinearLayout>
<RelativeLayout
android:layout_width="wrap_content"
- android:layout_height="@dimen/MediaControlView2_settings_height"
+ 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/MediaControlView2_text_width"
+ android:layout_height="@dimen/mcv2_embedded_settings_text_height"
+ android:gravity="center"
android:paddingLeft="2dp"
android:textColor="@color/white"
- android:textSize="@dimen/MediaControlView2_settings_main_text_size"/>
+ 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/settings_list_item.xml b/packages/MediaComponents/res/layout/full_settings_list_item.xml
similarity index 63%
rename from packages/MediaComponents/res/layout/settings_list_item.xml
rename to packages/MediaComponents/res/layout/full_settings_list_item.xml
index 81b3275..f92ea5e 100644
--- a/packages/MediaComponents/res/layout/settings_list_item.xml
+++ b/packages/MediaComponents/res/layout/full_settings_list_item.xml
@@ -15,49 +15,48 @@
-->
<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:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_full_settings_height"
android:orientation="horizontal"
- android:background="@color/black_transparent_70">
+ android:background="@color/black_opacity_70">
<LinearLayout
android:layout_width="wrap_content"
- android:layout_height="@dimen/MediaControlView2_settings_height"
- android:paddingRight="2dp"
+ android:layout_height="@dimen/mcv2_full_settings_height"
android:gravity="center"
android:orientation="horizontal">
<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"/>
+ 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/MediaControlView2_settings_height"
+ 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/MediaControlView2_text_width"
+ android:layout_height="@dimen/mcv2_full_settings_text_height"
android:paddingLeft="2dp"
+ android:gravity="center"
android:textColor="@color/white"
- android:textSize="@dimen/MediaControlView2_settings_main_text_size"/>
+ android:textSize="@dimen/mcv2_full_settings_main_text_size"/>
<TextView
android:id="@+id/sub_text"
android:layout_width="wrap_content"
- android:layout_height="@dimen/MediaControlView2_text_width"
+ 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_transparent_70"
- android:textSize="@dimen/MediaControlView2_settings_sub_text_size"/>
+ android:textColor="@color/white_opacity_70"
+ android:textSize="@dimen/mcv2_full_settings_sub_text_size"/>
</RelativeLayout>
-
</LinearLayout>
-
diff --git a/packages/MediaComponents/res/layout/sub_settings_list_item.xml b/packages/MediaComponents/res/layout/full_sub_settings_list_item.xml
similarity index 66%
copy from packages/MediaComponents/res/layout/sub_settings_list_item.xml
copy to packages/MediaComponents/res/layout/full_sub_settings_list_item.xml
index 9de7f2b..49128d0 100644
--- a/packages/MediaComponents/res/layout/sub_settings_list_item.xml
+++ b/packages/MediaComponents/res/layout/full_sub_settings_list_item.xml
@@ -15,40 +15,39 @@
-->
<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:layout_width="wrap_content"
+ android:layout_height="@dimen/mcv2_full_settings_height"
android:orientation="horizontal"
- android:background="@color/black_transparent_70">
+ android:background="@color/black_opacity_70">
<LinearLayout
android:layout_width="wrap_content"
- android:layout_height="@dimen/MediaControlView2_settings_height"
- android:paddingRight="2dp"
+ android:layout_height="@dimen/mcv2_full_settings_height"
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: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:paddingLeft="2dp"
android:src="@drawable/ic_check"/>
</LinearLayout>
<RelativeLayout
android:layout_width="wrap_content"
- android:layout_height="@dimen/MediaControlView2_settings_height"
+ 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/MediaControlView2_text_width"
+ android:layout_height="@dimen/mcv2_full_settings_text_height"
+ android:gravity="center"
android:paddingLeft="2dp"
android:textColor="@color/white"
- android:textSize="@dimen/MediaControlView2_settings_main_text_size"/>
+ android:textSize="@dimen/mcv2_full_settings_main_text_size"/>
</RelativeLayout>
-
-</LinearLayout>
+</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 eab2309..c5fbee8 100644
--- a/packages/MediaComponents/res/layout/media_controller.xml
+++ b/packages/MediaComponents/res/layout/media_controller.xml
@@ -111,19 +111,11 @@
</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
@@ -137,103 +129,108 @@
<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"
- 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_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/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/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 c80aaf3..69e9dff 100644
--- a/packages/MediaComponents/res/values/strings.xml
+++ b/packages/MediaComponents/res/values/strings.xml
@@ -122,6 +122,8 @@
<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">
diff --git a/packages/MediaComponents/res/values/style.xml b/packages/MediaComponents/res/values/style.xml
index e6ed039..b1da137 100644
--- a/packages/MediaComponents/res/values/style.xml
+++ b/packages/MediaComponents/res/values/style.xml
@@ -1,30 +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:visibility">gone</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">
@@ -45,14 +101,38 @@
<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">
diff --git a/packages/MediaComponents/src/com/android/media/IMediaController2.aidl b/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
index d6c8e21..0488b70 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaController2.aidl
@@ -37,12 +37,14 @@
void onBufferedPositionChanged(long bufferedPositionMs);
void onPlaylistChanged(in List<Bundle> playlist, in Bundle metadata);
void onPlaylistMetadataChanged(in Bundle metadata);
- void onPlaylistParamsChanged(in Bundle params);
void onPlaybackInfoChanged(in Bundle playbackInfo);
+ void onRepeatModeChanged(int repeatMode);
+ void onShuffleModeChanged(int shuffleMode);
+ void onError(int errorCode, in Bundle extras);
void onConnected(IMediaSession2 sessionBinder, in Bundle commandGroup,
int playerState, long positionEventTimeMs, long positionMs, float playbackSpeed,
- long bufferedPositionMs, in Bundle playbackInfo, in Bundle params,
+ long bufferedPositionMs, in Bundle playbackInfo, int repeatMode, int shuffleMode,
in List<Bundle> playlist, in PendingIntent sessionActivity);
void onDisconnected();
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
index a241abc..664467d 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
@@ -64,6 +64,11 @@
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
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index b6d3e55..0091816 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -22,6 +22,8 @@
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;
@@ -40,11 +42,12 @@
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.Rating2;
import android.media.SessionToken2;
@@ -87,7 +90,9 @@
@GuardedBy("mLock")
private MediaMetadata2 mPlaylistMetadata;
@GuardedBy("mLock")
- private PlaylistParams mPlaylistParams;
+ private @RepeatMode int mRepeatMode;
+ @GuardedBy("mLock")
+ private @ShuffleMode int mShuffleMode;
@GuardedBy("mLock")
private int mPlayerState;
@GuardedBy("mLock")
@@ -335,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) {
@@ -361,9 +401,6 @@
}
}
- //////////////////////////////////////////////////////////////////////////////////////
- // TODO(jaewan): Implement follows
- //////////////////////////////////////////////////////////////////////////////////////
@Override
public PendingIntent getSessionActivity_impl() {
return mSessionActivity;
@@ -617,21 +654,6 @@
}
@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 void addPlaylistItem_impl(int index, MediaItem2 item) {
if (index < 0) {
throw new IllegalArgumentException("index shouldn't be negative");
@@ -689,9 +711,41 @@
}
@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());
}
}
@@ -702,19 +756,6 @@
}
}
- // TODO(jaewan): Remove (b/74116823)
- @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() {
synchronized (mLock) {
@@ -800,18 +841,6 @@
});
}
- void pushPlaylistParamsChanges(final PlaylistParams params) {
- synchronized (mLock) {
- mPlaylistParams = params;
- }
- mCallbackExecutor.execute(() -> {
- if (!mInstance.isConnected()) {
- return;
- }
- mCallback.onPlaylistParamsChanged(mInstance, params);
- });
- }
-
void pushPlaybackInfoChanges(final PlaybackInfo info) {
synchronized (mLock) {
mPlaybackInfo = info;
@@ -838,7 +867,7 @@
});
}
- public void pushPlaylistMetadataChanges(MediaMetadata2 metadata) {
+ void pushPlaylistMetadataChanges(MediaMetadata2 metadata) {
synchronized (mLock) {
mPlaylistMetadata = metadata;
}
@@ -851,6 +880,41 @@
});
}
+ 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,
@@ -860,7 +924,9 @@
final float playbackSpeed,
final long bufferedPositionMs,
final PlaybackInfo info,
- final PlaylistParams params, final List<MediaItem2> playlist,
+ final int repeatMode,
+ final int shuffleMode,
+ final List<MediaItem2> playlist,
final PendingIntent sessionActivity) {
if (DEBUG) {
Log.d(TAG, "onConnectedNotLocked sessionBinder=" + sessionBinder
@@ -890,7 +956,8 @@
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/MediaController2Stub.java b/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
index 99bdfce..721c963 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Stub.java
@@ -24,7 +24,6 @@
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
-import android.media.MediaSession2.PlaylistParams;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.text.TextUtils;
@@ -169,7 +168,7 @@
}
@Override
- public void onPlaylistParamsChanged(Bundle paramsBundle) throws RuntimeException {
+ public void onRepeatModeChanged(int repeatMode) {
final MediaController2Impl controller;
try {
controller = getController();
@@ -177,12 +176,7 @@
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");
- return;
- }
- controller.pushPlaylistParamsChanges(params);
+ controller.pushRepeatModeChanges(repeatMode);
}
@Override
@@ -208,9 +202,33 @@
}
@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,
int playerState, long positionEventTimeMs, long positionMs, float playbackSpeed,
- long bufferedPositionMs, Bundle playbackInfo, Bundle playlistParams,
+ long bufferedPositionMs, Bundle playbackInfo, int shuffleMode, int repeatMode,
List<Bundle> itemBundleList, PendingIntent sessionActivity) {
final MediaController2Impl controller = mController.get();
if (controller == null) {
@@ -233,8 +251,7 @@
controller.onConnectedNotLocked(sessionBinder,
CommandGroup.fromBundle(context, commandGroup),
playerState, positionEventTimeMs, positionMs, playbackSpeed, bufferedPositionMs,
- PlaybackInfoImpl.fromBundle(context, playbackInfo),
- PlaylistParams.fromBundle(context, playlistParams),
+ PlaybackInfoImpl.fromBundle(context, playbackInfo), repeatMode, shuffleMode,
itemList, sessionActivity);
}
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 b8c185a..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,9 +48,6 @@
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.SessionToken2;
@@ -71,6 +69,7 @@
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 {
@@ -87,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;
@@ -254,6 +252,7 @@
mSessionStub.notifyPlaybackInfoChanged(info);
notifyPlayerUpdatedNotLocked(oldPlayer);
}
+ // TODO(jaewan): Repeat the same thing for the playlist agent.
}
private PlaybackInfo createPlaybackInfo(VolumeProvider2 volumeProvider, AudioAttributes attrs) {
@@ -386,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
@@ -426,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
//////////////////////////////////////////////////////////////////////////////////////
@@ -594,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;
@@ -607,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
@@ -646,51 +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");
+ public long getPosition_impl() {
+ final MediaPlayerBase player = mPlayer;
+ if (player != null) {
+ return mPlayer.getCurrentPosition();
+ } else if (DEBUG) {
+ Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
- 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);
+ return MediaPlayerBase.UNKNOWN_TIME;
}
@Override
- public void unregisterPlayerEventCallback_impl(PlayerEventCallback callback) {
- if (callback == null) {
- throw new IllegalArgumentException("callback shouldn't be null");
+ 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());
}
- ensureCallingThread();
- mCallbacks.remove(callback);
+ return MediaPlayerBase.UNKNOWN_TIME;
}
@Override
public void notifyError_impl(int errorCode, Bundle extras) {
- // TODO(jaewan): Implement
+ mSessionStub.notifyError(errorCode, extras);
}
///////////////////////////////////////////////////
@@ -720,7 +712,7 @@
private void notifyPlaylistChangedOnExecutor(MediaPlaylistAgent playlistAgent,
List<MediaItem2> list, MediaMetadata2 metadata) {
if (playlistAgent != mPlaylistAgent) {
- // Ignore calls from the old agent. Ignore.
+ // Ignore calls from the old agent.
return;
}
mCallback.onPlaylistChanged(mInstance, playlistAgent, list, metadata);
@@ -730,27 +722,39 @@
private void notifyPlaylistMetadataChangedOnExecutor(MediaPlaylistAgent playlistAgent,
MediaMetadata2 metadata) {
if (playlistAgent != mPlaylistAgent) {
- // Ignore calls from the old agent. Ignore.
+ // Ignore calls from the old agent.
return;
}
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;
+ }
+ 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) {
- ArrayMap<PlayerEventCallback, Executor> callbacks = new ArrayMap<>();
- MediaPlayerBase player;
- synchronized (mLock) {
- player = mPlayer;
- callbacks.putAll(mCallbacks);
- }
- // 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));
- }
+ 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()) {
@@ -774,21 +778,6 @@
}
}
- private void notifyErrorNotLocked(String mediaId, int what, int extra) {
- ArrayMap<PlayerEventCallback, Executor> callbacks = new ArrayMap<>();
- synchronized (mLock) {
- callbacks.putAll(mCallbacks);
- }
- // 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));
- }
- // TODO(jaewan): Notify to controllers as well.
- }
-
Context getContext() {
return mContext;
}
@@ -841,28 +830,32 @@
@Override
public void onCurrentDataSourceChanged(MediaPlayerBase mpb, DataSourceDesc dsd) {
MediaSession2Impl session = getSession();
- if (session == null) {
+ if (session == null || dsd == null) {
return;
}
session.getCallbackExecutor().execute(() -> {
- // TODO (jaewan): Convert dsd to MediaItem (b/74506462)
+ 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)
- session.getCallback().onCurrentMediaItemChanged(
- session.getInstance(), mpb, null /* MediaItem */);
});
}
@Override
public void onMediaPrepared(MediaPlayerBase mpb, DataSourceDesc dsd) {
MediaSession2Impl session = getSession();
- if (session == null) {
+ if (session == null || dsd == null) {
return;
}
session.getCallbackExecutor().execute(() -> {
- // TODO (jaewan): Convert dsd to MediaItem (b/74506462)
+ 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)
- session.getCallback().onMediaPrepared(
- session.getInstance(), mpb, null /* MediaItem */);
});
}
@@ -881,14 +874,17 @@
@Override
public void onBufferingStateChanged(MediaPlayerBase mpb, DataSourceDesc dsd, int state) {
MediaSession2Impl session = getSession();
- if (session == null) {
+ if (session == null || dsd == null) {
return;
}
session.getCallbackExecutor().execute(() -> {
- // TODO (jaewan): Convert dsd to MediaItem (b/74506462)
- // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
+ MediaItem2 item = getMediaItem(session, dsd);
+ if (item == null) {
+ return;
+ }
session.getCallback().onBufferingStateChanged(
- session.getInstance(), mpb, null /* MediaItem */, state);
+ session.getInstance(), mpb, item, state);
+ // TODO (jaewan): Notify controllers through appropriate callback. (b/74505936)
});
}
@@ -899,6 +895,24 @@
}
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;
+ }
}
private static class MyPlaylistEventCallback extends PlaylistEventCallback {
@@ -929,15 +943,21 @@
}
@Override
- public void onShuffleModeChanged(MediaPlaylistAgent playlistAgent, int shuffleMode) {
- super.onShuffleModeChanged(playlistAgent, shuffleMode);
- // TODO(jaewan): Handle this (b/74118768)
+ public void onRepeatModeChanged(MediaPlaylistAgent playlistAgent, int repeatMode) {
+ final MediaSession2Impl session = mSession.get();
+ if (session == null) {
+ return;
+ }
+ session.notifyRepeatModeChangedOnExecutor(playlistAgent, repeatMode);
}
@Override
- public void onRepeatModeChanged(MediaPlaylistAgent playlistAgent, int repeatMode) {
- super.onRepeatModeChanged(playlistAgent, repeatMode);
- // TODO(jaewan): Handle this (b/74118768)
+ public void onShuffleModeChanged(MediaPlaylistAgent playlistAgent, int shuffleMode) {
+ final MediaSession2Impl session = mSession.get();
+ if (session == null) {
+ return;
+ }
+ session.notifyShuffleModeChangedOnExecutor(playlistAgent, shuffleMode);
}
}
@@ -1282,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 8d9cf64..caf834a 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -22,18 +22,17 @@
import android.media.MediaItem2;
import android.media.MediaLibraryService2.LibraryRoot;
import android.media.MediaMetadata2;
-import android.media.MediaPlayerBase;
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.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;
@@ -271,6 +270,73 @@
});
}
+
+ 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
//////////////////////////////////////////////////////////////////////////////////////////////
@@ -323,13 +389,13 @@
// use thread poll for incoming calls.
final int playerState = session.getInstance().getPlayerState();
final long positionEventTimeMs = System.currentTimeMillis();
- final long positionMs = session.getInstance().getCurrentPosition();
+ 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)
@@ -359,8 +425,8 @@
try {
caller.onConnected(MediaSession2Stub.this, allowedCommands.toBundle(),
playerState, positionEventTimeMs, positionMs, playbackSpeed,
- bufferedPositionMs, playbackInfoBundle, paramsBundle, playlistBundle,
- sessionActivity);
+ bufferedPositionMs, playbackInfoBundle, repeatMode, shuffleMode,
+ playlistBundle, sessionActivity);
} catch (RemoteException e) {
// Controller may be died prematurely.
// TODO(jaewan): Handle here.
@@ -384,21 +450,7 @@
@Override
public void release(final IMediaController2 caller) throws RemoteException {
- ControllerInfo controller;
- synchronized (mLock) {
- controller = mControllers.remove(caller.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);
- });
+ onControllerClosed(caller);
}
@Override
@@ -441,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;
@@ -459,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.
}
@@ -654,9 +685,8 @@
onCommand(caller, MediaSession2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM,
(session, controller) -> {
MediaItem2 item = MediaItem2.fromBundle(session.getContext(), mediaItem);
- List<MediaItem2> list = session.getInstance().getPlaylist();
- // Trick to use the same reference for calls from the controller.
- session.getInstance().removePlaylistItem(list.get(list.indexOf(item)));
+ // Note: MediaItem2 has hidden UUID to identify it across the processes.
+ session.getInstance().removePlaylistItem(item);
});
}
@@ -667,10 +697,56 @@
// 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
//////////////////////////////////////////////////////////////////////////////////////////////
@@ -679,16 +755,13 @@
public void getLibraryRoot(final IMediaController2 caller, final Bundle rootHints)
throws RuntimeException {
onBrowserCommand(caller, (session, controller) -> {
- LibraryRoot root = session.getCallback().onGetLibraryRoot(session.getInstance(),
+ 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.
- }
+ });
});
}
@@ -702,14 +775,11 @@
}
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());
+ });
});
}
@@ -736,19 +806,18 @@
+ "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);
+ });
});
}
@@ -783,19 +852,18 @@
+ "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);
+ });
});
}
@@ -838,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) {
@@ -851,75 +919,31 @@
// Should be used without a lock to prevent potential deadlock.
public void notifyPlayerStateChangedNotLocked(int state) {
- final List<ControllerInfo> list = getControllers();
- for (int i = 0; i < list.size(); i++) {
- final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
- if (controllerBinder == null) {
- return;
- }
- try {
- controllerBinder.onPlayerStateChanged(state);
- } catch (RemoteException e) {
- Log.w(TAG, "Controller is gone", e);
- // TODO(jaewan): What to do when the controller is gone?
- }
- }
+ notifyAll((controller, iController) -> {
+ iController.onPlayerStateChanged(state);
+ });
}
public void notifyPositionChangedNotLocked(long eventTimeMs, long positionMs) {
- final List<ControllerInfo> list = getControllers();
- for (int i = 0; i < list.size(); i++) {
- final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
- if (controllerBinder == null) {
- return;
- }
- try {
- controllerBinder.onPositionChanged(eventTimeMs, positionMs);
- } catch (RemoteException e) {
- Log.w(TAG, "Controller is gone", e);
- // TODO(jaewan): What to do when the controller is gone?
- }
- }
+ notifyAll((controller, iController) -> {
+ iController.onPositionChanged(eventTimeMs, positionMs);
+ });
}
public void notifyPlaybackSpeedChangedNotLocked(float speed) {
- final List<ControllerInfo> list = getControllers();
- for (int i = 0; i < list.size(); i++) {
- final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
- if (controllerBinder == null) {
- return;
- }
- try {
- controllerBinder.onPlaybackSpeedChanged(speed);
- } catch (RemoteException e) {
- Log.w(TAG, "Controller is gone", e);
- // TODO(jaewan): What to do when the controller is gone?
- }
- }
+ notifyAll((controller, iController) -> {
+ iController.onPlaybackSpeedChanged(speed);
+ });
}
public void notifyBufferedPositionChangedNotLocked(long bufferedPositionMs) {
- final List<ControllerInfo> list = getControllers();
- for (int i = 0; i < list.size(); i++) {
- final IMediaController2 controllerBinder = getControllerBinderIfAble(list.get(i));
- if (controllerBinder == null) {
- return;
- }
- try {
- controllerBinder.onBufferedPositionChanged(bufferedPositionMs);
- } catch (RemoteException e) {
- Log.w(TAG, "Controller is gone", e);
- // TODO(jaewan): What to do when the controller is gone?
- }
- }
+ notifyAll((controller, iController) -> {
+ iController.onBufferedPositionChanged(bufferedPositionMs);
+ });
}
public void notifyCustomLayoutNotLocked(ControllerInfo controller, List<CommandButton> layout) {
- final IMediaController2 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();
@@ -927,11 +951,8 @@
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 notifyPlaylistChangedNotLocked(List<MediaItem2> playlist, MediaMetadata2 metadata) {
@@ -950,96 +971,52 @@
bundleList = null;
}
final Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
- final List<ControllerInfo> list = getControllers();
- for (int i = 0; i < list.size(); i++) {
- final IMediaController2 controllerBinder = getControllerBinderIfAble(
- list.get(i), MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST);
- if (controllerBinder != null) {
- try {
- controllerBinder.onPlaylistChanged(bundleList, metadataBundle);
- } catch (RemoteException e) {
- Log.w(TAG, "Controller is gone", e);
- // TODO(jaewan): What to do when the controller is gone?
- }
- } else {
- final IMediaController2 binder = getControllerBinderIfAble(
- list.get(i), MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA);
- if (binder != null) {
- try {
- binder.onPlaylistMetadataChanged(metadataBundle);
- } catch (RemoteException e) {
- Log.w(TAG, "Controller is gone", e);
- // TODO(jaewan): What to do when the controller is gone?
- }
- }
+ 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 notifyPlaylistMetadataChangedNotLocked(MediaMetadata2 metadata) {
final Bundle metadataBundle = (metadata == null) ? null : metadata.toBundle();
- final List<ControllerInfo> list = getControllers();
- for (int i = 0; i < list.size(); i++) {
- final IMediaController2 controllerBinder = getControllerBinderIfAble(
- list.get(i), MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA);
- if (controllerBinder != null) {
- try {
- controllerBinder.onPlaylistMetadataChanged(metadataBundle);
- } catch (RemoteException e) {
- Log.w(TAG, "Controller is gone", e);
- // TODO(jaewan): What to do when the controller is gone?
- }
- }
- }
+ notifyAll(MediaSession2.COMMAND_CODE_PLAYLIST_GET_LIST_METADATA,
+ (unused, iController) -> {
+ iController.onPlaylistMetadataChanged(metadataBundle);
+ });
}
- public void notifyPlaylistParamsChanged(MediaSession2.PlaylistParams params) {
- final List<ControllerInfo> list = getControllers();
- for (int i = 0; i < list.size(); i++) {
- final IMediaController2 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 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 IMediaController2 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 IMediaController2 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,
@@ -1051,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 IMediaController2 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);
+ });
}
//////////////////////////////////////////////////////////////////////////////////////////////
@@ -1085,49 +1056,36 @@
public void notifySearchResultChanged(ControllerInfo controller, String query, int itemCount,
Bundle extras) {
- final IMediaController2 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 IMediaController2 controllerBinder = getControllerBinderIfAble(controller);
- if (controller == null) {
- return;
- }
- try {
- controllerBinder.onChildrenChanged(parentId, itemCount, extras);
- } catch (RemoteException e) {
- // TODO(jaewan): Handle controller removed?
- }
+ return true;
}
//////////////////////////////////////////////////////////////////////////////////////////////
@@ -1143,4 +1101,10 @@
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/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 7f225de..05a54d5 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -18,8 +18,7 @@
import android.app.Notification;
import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.Resources.Theme;
+import android.content.pm.ApplicationInfo;
import android.media.MediaBrowser2;
import android.media.MediaBrowser2.BrowserCallback;
import android.media.MediaController2;
@@ -35,7 +34,6 @@
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;
@@ -52,7 +50,6 @@
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.SessionToken2Provider;
@@ -76,7 +73,6 @@
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.Rating2Impl;
import com.android.media.SessionToken2Impl;
@@ -86,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();
}
@@ -141,19 +138,6 @@
}
@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);
- }
-
- @Override
public BuilderProvider createMediaSession2CommandButtonBuilder(Context context,
MediaSession2.CommandButton.Builder instance) {
return new MediaSession2Impl.CommandButtonImpl.BuilderImpl(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 16707c6..9ae75b0 100644
--- a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -101,6 +101,12 @@
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;
private static final int REWIND_TIME_MS = 10000;
@@ -114,16 +120,10 @@
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 mTitleBar;
- 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;
@@ -132,8 +132,13 @@
private int mSelectedAudioTrackIndex;
private int mSelectedVideoQualityIndex;
private int mSelectedSpeedIndex;
- private int mSettingsItemHeight;
+ 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;
@@ -143,30 +148,56 @@
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;
- private ImageButton mBackButton;
+ // 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 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;
@@ -180,12 +211,6 @@
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);
@@ -194,7 +219,7 @@
@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();
mInstance.addView(mRoot);
@@ -310,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);
@@ -431,35 +485,47 @@
mReplayDescription =
mResources.getText(R.string.lockscreen_replay_button_content_description);
- mRouteButton = v.findViewById(R.id.cast);
-
- 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);
- }
- 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 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);
+ // 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);
@@ -473,10 +539,6 @@
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);
@@ -493,33 +555,9 @@
if (mVideoQualityButton != null) {
mVideoQualityButton.setOnClickListener(mVideoQualityListener);
}
-
- 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);
- }
-
- mTitleBar = v.findViewById(R.id.title_bar);
- if (mTitleBar != null) {
- mTitleBar.addOnLayoutChangeListener(mTitleBarLayoutChangeListener);
- }
- 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);
@@ -530,12 +568,16 @@
mSettingsListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
mSettingsListView.setOnItemClickListener(mSettingsItemClickListener);
- mSettingsItemHeight = mResources.getDimensionPixelSize(
- R.dimen.MediaControlView2_settings_height);
+ 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.MediaControlView2_settings_offset);
- int width = mResources.getDimensionPixelSize(R.dimen.MediaControlView2_settings_width);
- mSettingsWindow = new PopupWindow(mSettingsListView, width,
+ R.dimen.mcv2_settings_offset);
+ mSettingsWindow = new PopupWindow(mSettingsListView, mEmbeddedSettingsItemWidth,
ViewGroup.LayoutParams.WRAP_CONTENT, true);
}
@@ -954,36 +996,6 @@
}
};
- // 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);
- }
- }
- };
-
private void updateDuration() {
if (mMetadata != null) {
if (mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
@@ -1002,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);
@@ -1021,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);
@@ -1035,6 +1070,79 @@
}
}
+ 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() {
mSettingsMainTextsList = new ArrayList<String>();
mSettingsMainTextsList.add(
@@ -1079,8 +1187,18 @@
}
private void displaySettingsWindow(BaseAdapter adapter) {
+ // Set Adapter
mSettingsListView.setAdapter(adapter);
- int totalHeight = adapter.getCount() * mSettingsItemHeight;
+
+ // 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);
@@ -1273,8 +1391,14 @@
@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);
@@ -1347,8 +1471,14 @@
@Override
public View getView(int position, View convertView, ViewGroup container) {
- View row = ApiHelper.inflateLibLayout(mInstance.getContext(),
- R.layout.sub_settings_list_item);
+ 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);
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 264f632..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;
@@ -124,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;
@@ -635,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) {
@@ -658,6 +651,7 @@
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;
@@ -862,6 +856,9 @@
}
if (select) {
if (mSubtitleTrackIndices.size() > 0) {
+ // TODO: make this selection dynamic
+ mSelectedSubtitleTrackIndex = mSubtitleTrackIndices.get(0).first;
+ mSubtitleController.selectTrack(mSubtitleTrackIndices.get(0).second);
mMediaPlayer.selectTrack(mSelectedSubtitleTrackIndex);
mSubtitleView.setVisibility(View.VISIBLE);
}
@@ -879,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) {
@@ -887,7 +885,10 @@
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
@@ -908,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
@@ -1066,15 +1096,15 @@
MediaControlView2Impl.KEY_SELECTED_SUBTITLE_INDEX,
INVALID_TRACK_INDEX);
if (subtitleIndex != INVALID_TRACK_INDEX) {
- int subtitleTrackIndex = mSubtitleTrackIndices.get(subtitleIndex);
+ int subtitleTrackIndex = mSubtitleTrackIndices.get(subtitleIndex).first;
if (subtitleTrackIndex != mSelectedSubtitleTrackIndex) {
mSelectedSubtitleTrackIndex = subtitleTrackIndex;
- selectOrDeselectSubtitle(true);
+ mInstance.setSubtitleEnabled(true);
}
}
break;
case MediaControlView2Impl.COMMAND_HIDE_SUBTITLE:
- selectOrDeselectSubtitle(false);
+ mInstance.setSubtitleEnabled(false);
break;
case MediaControlView2Impl.COMMAND_SET_FULLSCREEN:
if (mFullScreenRequestListener != null) {
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 3134323..20447d8 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -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;
}
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);
}
}