Merge "NdkMediaExtractor: per-sample format metadata API"
diff --git a/drm/libmediadrm/Android.bp b/drm/libmediadrm/Android.bp
index 5ea4614..66e4400 100644
--- a/drm/libmediadrm/Android.bp
+++ b/drm/libmediadrm/Android.bp
@@ -7,7 +7,6 @@
cc_library {
name: "libmediadrm",
-
srcs: [
"DrmPluginPath.cpp",
"DrmSessionManager.cpp",
@@ -62,6 +61,7 @@
"android.hardware.drm@1.1",
"libbase",
"libbinder",
+ "libhidlbase",
"liblog",
"libmediametrics",
"libprotobuf-cpp-lite",
@@ -93,6 +93,7 @@
"android.hardware.drm@1.1",
"libbase",
"libbinder",
+ "libhidlbase",
"liblog",
"libmediametrics",
"libprotobuf-cpp-full",
diff --git a/drm/libmediadrm/DrmHal.cpp b/drm/libmediadrm/DrmHal.cpp
index 292895f..5d97188 100644
--- a/drm/libmediadrm/DrmHal.cpp
+++ b/drm/libmediadrm/DrmHal.cpp
@@ -48,6 +48,7 @@
using drm::V1_0::SecureStopId;
using drm::V1_1::SecurityLevel;
using drm::V1_0::Status;
+using ::android::hardware::drm::V1_1::DrmMetricGroup;
using ::android::hardware::hidl_array;
using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
@@ -1122,12 +1123,48 @@
return status.isOk() ? toStatusT(status) : DEAD_OBJECT;
}
-status_t DrmHal::getMetrics(PersistableBundle* item) {
- if (item == nullptr) {
- return UNEXPECTED_NULL;
+status_t DrmHal::getMetrics(PersistableBundle* metrics) {
+ if (metrics == nullptr) {
+ return UNEXPECTED_NULL;
+ }
+ mMetrics.Export(metrics);
+
+ // Append vendor metrics if they are supported.
+ if (mPluginV1_1 != NULL) {
+ String8 vendor;
+ String8 description;
+ if (getPropertyStringInternal(String8("vendor"), vendor) != OK
+ || vendor.isEmpty()) {
+ ALOGE("Get vendor failed or is empty");
+ vendor = "NONE";
+ }
+ if (getPropertyStringInternal(String8("description"), description) != OK
+ || description.isEmpty()) {
+ ALOGE("Get description failed or is empty.");
+ description = "NONE";
+ }
+ vendor += ".";
+ vendor += description;
+
+ hidl_vec<DrmMetricGroup> pluginMetrics;
+ status_t err = UNKNOWN_ERROR;
+
+ Return<void> status = mPluginV1_1->getMetrics(
+ [&](Status status, hidl_vec<DrmMetricGroup> pluginMetrics) {
+ if (status != Status::OK) {
+ ALOGV("Error getting plugin metrics: %d", status);
+ } else {
+ PersistableBundle pluginBundle;
+ if (MediaDrmMetrics::HidlMetricsToBundle(
+ pluginMetrics, &pluginBundle) == OK) {
+ metrics->putPersistableBundle(String16(vendor), pluginBundle);
+ }
+ }
+ err = toStatusT(status);
+ });
+ return status.isOk() ? err : DEAD_OBJECT;
}
- mMetrics.Export(item);
return OK;
}
diff --git a/drm/libmediadrm/DrmMetrics.cpp b/drm/libmediadrm/DrmMetrics.cpp
index 96cc2cd..fce1717 100644
--- a/drm/libmediadrm/DrmMetrics.cpp
+++ b/drm/libmediadrm/DrmMetrics.cpp
@@ -29,8 +29,10 @@
using ::android::String16;
using ::android::String8;
using ::android::drm_metrics::DrmFrameworkMetrics;
+using ::android::hardware::hidl_vec;
using ::android::hardware::drm::V1_0::EventType;
using ::android::hardware::drm::V1_0::KeyStatusType;
+using ::android::hardware::drm::V1_1::DrmMetricGroup;
using ::android::os::PersistableBundle;
namespace {
@@ -172,6 +174,24 @@
return out.str();
}
+template <typename CT>
+void SetValue(const String16 &name, DrmMetricGroup::ValueType type,
+ const CT &value, PersistableBundle *bundle) {
+ switch (type) {
+ case DrmMetricGroup::ValueType::INT64_TYPE:
+ bundle->putLong(name, value.int64Value);
+ break;
+ case DrmMetricGroup::ValueType::DOUBLE_TYPE:
+ bundle->putDouble(name, value.doubleValue);
+ break;
+ case DrmMetricGroup::ValueType::STRING_TYPE:
+ bundle->putString(name, String16(value.stringValue.c_str()));
+ break;
+ default:
+ ALOGE("Unexpected value type: %hhu", type);
+ }
+}
+
} // namespace
namespace android {
@@ -339,4 +359,46 @@
return ((int64_t)tv.tv_sec * 1000) + ((int64_t)tv.tv_usec / 1000);
}
+status_t MediaDrmMetrics::HidlMetricsToBundle(
+ const hidl_vec<DrmMetricGroup> &hidlMetricGroups,
+ PersistableBundle *bundleMetricGroups) {
+ if (bundleMetricGroups == nullptr) {
+ return UNEXPECTED_NULL;
+ }
+ if (hidlMetricGroups.size() == 0) {
+ return OK;
+ }
+
+ int groupIndex = 0;
+ for (const auto &hidlMetricGroup : hidlMetricGroups) {
+ PersistableBundle bundleMetricGroup;
+ for (const auto &hidlMetric : hidlMetricGroup.metrics) {
+ PersistableBundle bundleMetric;
+ // Add metric component values.
+ for (const auto &value : hidlMetric.values) {
+ SetValue(String16(value.componentName.c_str()), value.type,
+ value, &bundleMetric);
+ }
+ // Set metric attributes.
+ PersistableBundle bundleMetricAttributes;
+ for (const auto &attribute : hidlMetric.attributes) {
+ SetValue(String16(attribute.name.c_str()), attribute.type,
+ attribute, &bundleMetricAttributes);
+ }
+ // Add attributes to the bundle metric.
+ bundleMetric.putPersistableBundle(String16("attributes"),
+ bundleMetricAttributes);
+ // Add the bundle metric to the group of metrics.
+ bundleMetricGroup.putPersistableBundle(
+ String16(hidlMetric.name.c_str()), bundleMetric);
+ }
+ // Add the bundle metric group to the collection of groups.
+ bundleMetricGroups->putPersistableBundle(
+ String16(std::to_string(groupIndex).c_str()), bundleMetricGroup);
+ groupIndex++;
+ }
+
+ return OK;
+}
+
} // namespace android
diff --git a/drm/libmediadrm/tests/Android.bp b/drm/libmediadrm/tests/Android.bp
index 670d3b9..66c906f 100644
--- a/drm/libmediadrm/tests/Android.bp
+++ b/drm/libmediadrm/tests/Android.bp
@@ -16,7 +16,9 @@
srcs: ["DrmMetrics_test.cpp"],
shared_libs: [
"android.hardware.drm@1.0",
+ "android.hardware.drm@1.1",
"libbinder",
+ "libhidlbase",
"liblog",
"libmediadrmmetrics_full",
"libmediametrics",
diff --git a/drm/libmediadrm/tests/DrmMetrics_test.cpp b/drm/libmediadrm/tests/DrmMetrics_test.cpp
index 7303806..1a20342 100644
--- a/drm/libmediadrm/tests/DrmMetrics_test.cpp
+++ b/drm/libmediadrm/tests/DrmMetrics_test.cpp
@@ -17,6 +17,8 @@
#define LOG_TAG "DrmMetricsTest"
#include "mediadrm/DrmMetrics.h"
+#include <android/hardware/drm/1.0/types.h>
+#include <android/hardware/drm/1.1/types.h>
#include <binder/PersistableBundle.h>
#include <google/protobuf/text_format.h>
#include <google/protobuf/util/message_differencer.h>
@@ -26,8 +28,11 @@
#include "protos/metrics.pb.h"
using ::android::drm_metrics::DrmFrameworkMetrics;
+using ::android::hardware::hidl_vec;
using ::android::hardware::drm::V1_0::EventType;
using ::android::hardware::drm::V1_0::KeyStatusType;
+using ::android::hardware::drm::V1_0::Status;
+using ::android::hardware::drm::V1_1::DrmMetricGroup;
using ::android::os::PersistableBundle;
using ::google::protobuf::util::MessageDifferencer;
using ::google::protobuf::TextFormat;
@@ -343,19 +348,19 @@
ASSERT_TRUE(metricsProto.ParseFromString(serializedMetrics));
std::string expectedMetrics =
- "get_key_request_timing { "
+ "get_key_request_time_us { "
" min: 1 max: 5 mean: 3.5 variance: 1 operation_count: 5 "
" attributes { error_code: -0x7FFFFFF8 } "
"} "
- "get_key_request_timing { "
+ "get_key_request_time_us { "
" min: 1 max: 5 mean: 3.5 variance: 1 operation_count: 5 "
" attributes { error_code: 0 } "
"} "
- "provide_key_response_timing { "
+ "provide_key_response_time_us { "
" min: 1 max: 5 mean: 3.5 variance: 1 operation_count: 5 "
" attributes { error_code: -0x7FFFFFF8 } "
"} "
- "provide_key_response_timing { "
+ "provide_key_response_time_us { "
" min: 1 max: 5 mean: 3.5 variance: 1 operation_count: 5 "
" attributes { error_code: 0 } "
"} ";
@@ -412,4 +417,55 @@
<< diffString;
}
+TEST_F(MediaDrmMetricsTest, HidlToBundleMetricsEmpty) {
+ hidl_vec<DrmMetricGroup> hidlMetricGroups;
+ PersistableBundle bundleMetricGroups;
+
+ ASSERT_EQ(OK, MediaDrmMetrics::HidlMetricsToBundle(hidlMetricGroups, &bundleMetricGroups));
+ ASSERT_EQ(0U, bundleMetricGroups.size());
+}
+
+TEST_F(MediaDrmMetricsTest, HidlToBundleMetricsMultiple) {
+ DrmMetricGroup hidlMetricGroup =
+ { { {
+ "open_session_ok",
+ { { "status", DrmMetricGroup::ValueType::INT64_TYPE, (int64_t) Status::OK, 0.0, "" } },
+ { { "count", DrmMetricGroup::ValueType::INT64_TYPE, 3, 0.0, "" } }
+ },
+ {
+ "close_session_not_opened",
+ { { "status", DrmMetricGroup::ValueType::INT64_TYPE,
+ (int64_t) Status::ERROR_DRM_SESSION_NOT_OPENED, 0.0, "" } },
+ { { "count", DrmMetricGroup::ValueType::INT64_TYPE, 7, 0.0, "" } }
+ } } };
+
+ PersistableBundle bundleMetricGroups;
+ ASSERT_EQ(OK, MediaDrmMetrics::HidlMetricsToBundle(hidl_vec<DrmMetricGroup>({hidlMetricGroup}),
+ &bundleMetricGroups));
+ ASSERT_EQ(1U, bundleMetricGroups.size());
+ PersistableBundle bundleMetricGroup;
+ ASSERT_TRUE(bundleMetricGroups.getPersistableBundle(String16("0"), &bundleMetricGroup));
+ ASSERT_EQ(2U, bundleMetricGroup.size());
+
+ // Verify each metric.
+ PersistableBundle metric;
+ ASSERT_TRUE(bundleMetricGroup.getPersistableBundle(String16("open_session_ok"), &metric));
+ int64_t value = 0;
+ ASSERT_TRUE(metric.getLong(String16("count"), &value));
+ ASSERT_EQ(3, value);
+ PersistableBundle attributeBundle;
+ ASSERT_TRUE(metric.getPersistableBundle(String16("attributes"), &attributeBundle));
+ ASSERT_TRUE(attributeBundle.getLong(String16("status"), &value));
+ ASSERT_EQ((int64_t) Status::OK, value);
+
+ ASSERT_TRUE(bundleMetricGroup.getPersistableBundle(String16("close_session_not_opened"),
+ &metric));
+ ASSERT_TRUE(metric.getLong(String16("count"), &value));
+ ASSERT_EQ(7, value);
+ ASSERT_TRUE(metric.getPersistableBundle(String16("attributes"), &attributeBundle));
+ value = 0;
+ ASSERT_TRUE(attributeBundle.getLong(String16("status"), &value));
+ ASSERT_EQ((int64_t) Status::ERROR_DRM_SESSION_NOT_OPENED, value);
+}
+
} // namespace android
diff --git a/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp b/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp
index e27631f..ed9534f 100644
--- a/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp
+++ b/drm/mediacas/plugins/clearkey/ClearKeyCasPlugin.cpp
@@ -347,6 +347,9 @@
return ERROR_CAS_CANNOT_HANDLE;
}
+ scramblingControl = (DescramblerPlugin::ScramblingControl)
+ (scramblingControl & DescramblerPlugin::kScrambling_Mask_Key);
+
AES_KEY contentKey;
if (scramblingControl != DescramblerPlugin::kScrambling_Unscrambled) {
diff --git a/media/extractors/aac/AACExtractor.cpp b/media/extractors/aac/AACExtractor.cpp
index bbc1ff8..9fc5a76 100644
--- a/media/extractors/aac/AACExtractor.cpp
+++ b/media/extractors/aac/AACExtractor.cpp
@@ -373,6 +373,7 @@
*confidence = 0.2;
off64_t *offPtr = (off64_t*) malloc(sizeof(off64_t));
+ *offPtr = pos;
*meta = offPtr;
*freeMeta = ::free;
diff --git a/media/libmedia/include/media/DrmMetrics.h b/media/libmedia/include/media/DrmMetrics.h
index 5c2fdf2..261998f 100644
--- a/media/libmedia/include/media/DrmMetrics.h
+++ b/media/libmedia/include/media/DrmMetrics.h
@@ -20,6 +20,7 @@
#include <map>
#include <android/hardware/drm/1.0/types.h>
+#include <android/hardware/drm/1.1/types.h>
#include <binder/PersistableBundle.h>
#include <media/CounterMetric.h>
#include <media/EventMetric.h>
@@ -78,6 +79,47 @@
// caller and must not be null.
status_t GetSerializedMetrics(std::string* serializedMetrics);
+ // Converts the DRM plugin metrics to a PersistableBundle. All of the metrics
+ // found in |pluginMetrics| are added to the |metricsBundle| parameter.
+ // |pluginBundle| is owned by the caller and must not be null.
+ //
+ // Each item in the pluginMetrics vector is added as a new PersistableBundle. E.g.
+ // DrmMetricGroup {
+ // metrics[0] {
+ // name: "buf_copy"
+ // attributes[0] {
+ // name: "size"
+ // type: INT64_TYPE
+ // int64Value: 1024
+ // }
+ // values[0] {
+ // componentName: "operation_count"
+ // type: INT64_TYPE
+ // int64Value: 75
+ // }
+ // values[1] {
+ // component_name: "average_time_seconds"
+ // type: DOUBLE_TYPE
+ // doubleValue: 0.00000042
+ // }
+ // }
+ // }
+ //
+ // becomes
+ //
+ // metricsBundle {
+ // "0": (PersistableBundle) {
+ // "attributes" : (PersistableBundle) {
+ // "size" : (int64) 1024
+ // }
+ // "operation_count" : (int64) 75
+ // "average_time_seconds" : (double) 0.00000042
+ // }
+ //
+ static status_t HidlMetricsToBundle(
+ const hardware::hidl_vec<hardware::drm::V1_1::DrmMetricGroup>& pluginMetrics,
+ os::PersistableBundle* metricsBundle);
+
protected:
// This is visible for testing only.
virtual int64_t GetCurrentTimeMs();
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index abb8fb0..d252150 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -184,6 +184,7 @@
mVideoDecoderGeneration(0),
mRendererGeneration(0),
mLastStartedPlayingTimeNs(0),
+ mLastStartedRebufferingTimeNs(0),
mPreviousSeekTimeUs(0),
mAudioEOS(false),
mVideoEOS(false),
@@ -1311,8 +1312,8 @@
ALOGV("kWhatReset");
mResetting = true;
- stopPlaybackTimer("kWhatReset");
- stopRebufferingTimer(true);
+ updatePlaybackTimer(true /* stopping */, "kWhatReset");
+ updateRebufferingTimer(true /* stopping */, true /* exiting */);
mDeferredActions.push_back(
new FlushDecoderAction(
@@ -1586,23 +1587,28 @@
}
}
-void NuPlayer::stopPlaybackTimer(const char *where) {
+void NuPlayer::updatePlaybackTimer(bool stopping, const char *where) {
Mutex::Autolock autoLock(mPlayingTimeLock);
- ALOGV("stopPlaybackTimer() time %20" PRId64 " (%s)", mLastStartedPlayingTimeNs, where);
+ ALOGV("updatePlaybackTimer(%s) time %20" PRId64 " (%s)",
+ stopping ? "stop" : "snap", mLastStartedPlayingTimeNs, where);
if (mLastStartedPlayingTimeNs != 0) {
sp<NuPlayerDriver> driver = mDriver.promote();
+ int64_t now = systemTime();
if (driver != NULL) {
- int64_t now = systemTime();
int64_t played = now - mLastStartedPlayingTimeNs;
- ALOGV("stopPlaybackTimer() log %20" PRId64 "", played);
+ ALOGV("updatePlaybackTimer() log %20" PRId64 "", played);
if (played > 0) {
driver->notifyMorePlayingTimeUs((played+500)/1000);
}
}
- mLastStartedPlayingTimeNs = 0;
+ if (stopping) {
+ mLastStartedPlayingTimeNs = 0;
+ } else {
+ mLastStartedPlayingTimeNs = now;
+ }
}
}
@@ -1614,17 +1620,18 @@
}
}
-void NuPlayer::stopRebufferingTimer(bool exitingPlayback) {
+void NuPlayer::updateRebufferingTimer(bool stopping, bool exitingPlayback) {
Mutex::Autolock autoLock(mPlayingTimeLock);
- ALOGV("stopRebufferTimer() time %20" PRId64 " (exiting %d)", mLastStartedRebufferingTimeNs, exitingPlayback);
+ ALOGV("updateRebufferingTimer(%s) time %20" PRId64 " (exiting %d)",
+ stopping ? "stop" : "snap", mLastStartedRebufferingTimeNs, exitingPlayback);
if (mLastStartedRebufferingTimeNs != 0) {
sp<NuPlayerDriver> driver = mDriver.promote();
+ int64_t now = systemTime();
if (driver != NULL) {
- int64_t now = systemTime();
int64_t rebuffered = now - mLastStartedRebufferingTimeNs;
- ALOGV("stopRebufferingTimer() log %20" PRId64 "", rebuffered);
+ ALOGV("updateRebufferingTimer() log %20" PRId64 "", rebuffered);
if (rebuffered > 0) {
driver->notifyMoreRebufferingTimeUs((rebuffered+500)/1000);
@@ -1633,13 +1640,24 @@
}
}
}
- mLastStartedRebufferingTimeNs = 0;
+ if (stopping) {
+ mLastStartedRebufferingTimeNs = 0;
+ } else {
+ mLastStartedRebufferingTimeNs = now;
+ }
}
}
+void NuPlayer::updateInternalTimers() {
+ // update values, but ticking clocks keep ticking
+ ALOGV("updateInternalTimers()");
+ updatePlaybackTimer(false /* stopping */, "updateInternalTimers");
+ updateRebufferingTimer(false /* stopping */, false /* exiting */);
+}
+
void NuPlayer::onPause() {
- stopPlaybackTimer("onPause");
+ updatePlaybackTimer(true /* stopping */, "onPause");
if (mPaused) {
return;
@@ -2282,8 +2300,8 @@
CHECK(mAudioDecoder == NULL);
CHECK(mVideoDecoder == NULL);
- stopPlaybackTimer("performReset");
- stopRebufferingTimer(true);
+ updatePlaybackTimer(true /* stopping */, "performReset");
+ updateRebufferingTimer(true /* stopping */, true /* exiting */);
cancelPollDuration();
@@ -2551,7 +2569,7 @@
if (mStarted) {
ALOGI("buffer ready, resuming...");
- stopRebufferingTimer(false);
+ updateRebufferingTimer(true /* stopping */, false /* exiting */);
mPausedForBuffering = false;
// do not resume yet if client didn't unpause
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h
index fda69e8..9481234 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h
@@ -99,6 +99,8 @@
const char *getDataSourceType();
+ void updateInternalTimers();
+
protected:
virtual ~NuPlayer();
@@ -180,12 +182,12 @@
Mutex mPlayingTimeLock;
int64_t mLastStartedPlayingTimeNs;
- void stopPlaybackTimer(const char *where);
+ void updatePlaybackTimer(bool stopping, const char *where);
void startPlaybackTimer(const char *where);
int64_t mLastStartedRebufferingTimeNs;
void startRebufferingTimer();
- void stopRebufferingTimer(bool exitingPlayback);
+ void updateRebufferingTimer(bool stopping, bool exitingPlayback);
int64_t mPreviousSeekTimeUs;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
index c455951..731fdba 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
@@ -535,6 +535,7 @@
}
void NuPlayerDriver::updateMetrics(const char *where) {
+
if (where == NULL) {
where = "unknown";
}
@@ -592,6 +593,8 @@
getDuration(&duration_ms);
mAnalyticsItem->setInt64(kPlayerDuration, duration_ms);
+ mPlayer->updateInternalTimers();
+
mAnalyticsItem->setInt64(kPlayerPlaying, (mPlayingTimeUs+500)/1000 );
if (mRebufferingEvents != 0) {
@@ -630,7 +633,7 @@
mAnalyticsItem->setUid(mClientUid);
}
} else {
- ALOGV("did not have anything to record");
+ ALOGV("nothing to record (only %d fields)", mAnalyticsItem->count());
}
}
diff --git a/media/libstagefright/codec2/vndk/C2Store.cpp b/media/libstagefright/codec2/vndk/C2Store.cpp
index f612b4f..216a897 100644
--- a/media/libstagefright/codec2/vndk/C2Store.cpp
+++ b/media/libstagefright/codec2/vndk/C2Store.cpp
@@ -433,9 +433,29 @@
mComponents.emplace("c2.google.avc.encoder", "libstagefright_soft_c2avcenc.so");
mComponents.emplace("c2.google.aac.decoder", "libstagefright_soft_c2aacdec.so");
mComponents.emplace("c2.google.aac.encoder", "libstagefright_soft_c2aacenc.so");
- mComponents.emplace("c2.google.mp3.decoder", "libstagefright_soft_c2mp3dec.so");
+ mComponents.emplace("c2.google.amrnb.decoder", "libstagefright_soft_c2amrnbdec.so");
+ mComponents.emplace("c2.google.amrnb.encoder", "libstagefright_soft_c2amrnbenc.so");
+ mComponents.emplace("c2.google.amrwb.decoder", "libstagefright_soft_c2amrwbdec.so");
+ mComponents.emplace("c2.google.amrwb.encoder", "libstagefright_soft_c2amrwbenc.so");
+ mComponents.emplace("c2.google.hevc.decoder", "libstagefright_soft_c2hevcdec.so");
mComponents.emplace("c2.google.g711.alaw.decoder", "libstagefright_soft_c2g711alawdec.so");
mComponents.emplace("c2.google.g711.mlaw.decoder", "libstagefright_soft_c2g711mlawdec.so");
+ mComponents.emplace("c2.google.mpeg2.decoder", "libstagefright_soft_c2mpeg2dec.so");
+ mComponents.emplace("c2.google.h263.decoder", "libstagefright_soft_c2h263dec.so");
+ mComponents.emplace("c2.google.h263.encoder", "libstagefright_soft_c2h263enc.so");
+ mComponents.emplace("c2.google.mpeg4.decoder", "libstagefright_soft_c2mpeg4dec.so");
+ mComponents.emplace("c2.google.mpeg4.encoder", "libstagefright_soft_c2mpeg4enc.so");
+ mComponents.emplace("c2.google.mp3.decoder", "libstagefright_soft_c2mp3dec.so");
+ mComponents.emplace("c2.google.vorbis.decoder", "libstagefright_soft_c2vorbisdec.so");
+ mComponents.emplace("c2.google.opus.decoder", "libstagefright_soft_c2opusdec.so");
+ mComponents.emplace("c2.google.vp8.decoder", "libstagefright_soft_c2vp8dec.so");
+ mComponents.emplace("c2.google.vp9.decoder", "libstagefright_soft_c2vp9dec.so");
+ mComponents.emplace("c2.google.vp8.encoder", "libstagefright_soft_c2vp8enc.so");
+ mComponents.emplace("c2.google.vp9.encoder", "libstagefright_soft_c2vp9enc.so");
+ mComponents.emplace("c2.google.raw.decoder", "libstagefright_soft_c2rawdec.so");
+ mComponents.emplace("c2.google.flac.decoder", "libstagefright_soft_c2flacdec.so");
+ mComponents.emplace("c2.google.flac.encoder", "libstagefright_soft_c2flacenc.so");
+ mComponents.emplace("c2.google.gsm.decoder", "libstagefright_soft_c2gsmdec.so");
}
c2_status_t C2PlatformComponentStore::copyBuffer(
diff --git a/media/libstagefright/codecs/amrnb/enc/Android.bp b/media/libstagefright/codecs/amrnb/enc/Android.bp
index f844459..4001d46 100644
--- a/media/libstagefright/codecs/amrnb/enc/Android.bp
+++ b/media/libstagefright/codecs/amrnb/enc/Android.bp
@@ -122,6 +122,50 @@
//###############################################################################
+cc_library_shared {
+ name: "libstagefright_soft_c2amrnbenc",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ srcs: ["C2SoftAmrNbEnc.cpp"],
+
+ local_include_dirs: ["src"],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ "unsigned-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+
+ static_libs: [
+ "libstagefright_amrnbenc",
+ ],
+
+ shared_libs: [
+ "libutils",
+ "liblog",
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_foundation",
+ "libstagefright_simple_c2component",
+ "libstagefright_amrnb_common",
+ ],
+}
+
+//###############################################################################
+
cc_test {
name: "libstagefright_amrnbenc_test",
gtest: false,
diff --git a/media/libstagefright/codecs/amrnb/enc/C2SoftAmrNbEnc.cpp b/media/libstagefright/codecs/amrnb/enc/C2SoftAmrNbEnc.cpp
new file mode 100644
index 0000000..4dd0309
--- /dev/null
+++ b/media/libstagefright/codecs/amrnb/enc/C2SoftAmrNbEnc.cpp
@@ -0,0 +1,252 @@
+/*
+ * 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 "C2SoftAmrNbEnc"
+#include <utils/Log.h>
+
+#include "gsmamr_enc.h"
+
+#include "C2SoftAmrNbEnc.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.amrnb.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_NB)
+ .build();
+}
+
+C2SoftAmrNbEnc::C2SoftAmrNbEnc(const char *name, c2_node_id_t id)
+ : SimpleC2Component(BuildIntf(name, id)),
+ mEncState(nullptr),
+ mSidState(nullptr) {
+}
+
+C2SoftAmrNbEnc::~C2SoftAmrNbEnc() {
+ onRelease();
+}
+
+c2_status_t C2SoftAmrNbEnc::onInit() {
+ bool dtx_enable = false;
+
+ if (AMREncodeInit(&mEncState, &mSidState, dtx_enable) != 0)
+ return C2_CORRUPTED;
+ mMode = MR795;
+ mIsFirst = true;
+ mSignalledError = false;
+ mSignalledOutputEos = false;
+ mAnchorTimeStamp = 0;
+ mProcessedSamples = 0;
+ mFilledLen = 0;
+
+ return C2_OK;
+}
+
+void C2SoftAmrNbEnc::onRelease() {
+ if (mEncState) {
+ AMREncodeExit(&mEncState, &mSidState);
+ mEncState = mSidState = nullptr;
+ }
+}
+
+c2_status_t C2SoftAmrNbEnc::onStop() {
+ if (AMREncodeReset(mEncState, mSidState) != 0)
+ return C2_CORRUPTED;
+ mIsFirst = true;
+ mSignalledError = false;
+ mSignalledOutputEos = false;
+ mAnchorTimeStamp = 0;
+ mProcessedSamples = 0;
+ mFilledLen = 0;
+
+ return C2_OK;
+}
+
+void C2SoftAmrNbEnc::onReset() {
+ (void) onStop();
+}
+
+c2_status_t C2SoftAmrNbEnc::onFlush_sm() {
+ return onStop();
+}
+
+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 C2SoftAmrNbEnc::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 = C2_CORRUPTED;
+ 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 = C2_CORRUPTED;
+ 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;
+ }
+ Frame_Type_3GPP frameType;
+ int numEncBytes = AMREncode(mEncState, mSidState, mMode, mInputFrame,
+ wView.data() + outPos, &frameType,
+ AMR_TX_WMF);
+ if (numEncBytes < 0 || numEncBytes > ((int)outCapacity - (int)outPos)) {
+ ALOGE("encodeFrame call failed, state [%d %zu %zu]", numEncBytes, outPos, outCapacity);
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+ // Convert header byte from WMF to IETF format.
+ if (numEncBytes > 0)
+ wView.data()[outPos] = ((wView.data()[outPos] << 3) | 4) & 0x7c;
+ 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 C2SoftAmrNbEnc::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 C2SoftAmrNbEncFactory : 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 C2SoftAmrNbEnc(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 ~C2SoftAmrNbEncFactory() override = default;
+};
+
+} // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+ ALOGV("in %s", __func__);
+ return new ::android::C2SoftAmrNbEncFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+ ALOGV("in %s", __func__);
+ delete factory;
+}
diff --git a/media/libstagefright/codecs/amrnb/enc/C2SoftAmrNbEnc.h b/media/libstagefright/codecs/amrnb/enc/C2SoftAmrNbEnc.h
new file mode 100644
index 0000000..9ced921
--- /dev/null
+++ b/media/libstagefright/codecs/amrnb/enc/C2SoftAmrNbEnc.h
@@ -0,0 +1,65 @@
+/*
+ * 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_AMRNB_ENC_H_
+#define C2_SOFT_AMRNB_ENC_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+
+namespace android {
+
+class C2SoftAmrNbEnc : public SimpleC2Component {
+public:
+ C2SoftAmrNbEnc(const char *name, c2_node_id_t id);
+ virtual ~C2SoftAmrNbEnc();
+
+ // 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 = L_FRAME;
+ static const int32_t kNumBytesPerInputFrame = kNumSamplesPerFrame * sizeof(int16_t);
+ static const int32_t kSampleRate = 8000;
+
+ void *mEncState;
+ void *mSidState;
+ Mode mMode;
+ bool mIsFirst;
+ bool mSignalledError;
+ bool mSignalledOutputEos;
+ uint64_t mAnchorTimeStamp;
+ uint64_t mProcessedSamples;
+ int32_t mFilledLen;
+ int16_t mInputFrame[kNumSamplesPerFrame];
+
+ DISALLOW_EVIL_CONSTRUCTORS(C2SoftAmrNbEnc);
+};
+
+} // namespace android
+
+#endif // C2_SOFT_AMRNB_ENC_H_
diff --git a/media/libstagefright/codecs/flac/dec/Android.bp b/media/libstagefright/codecs/flac/dec/Android.bp
index 9af086b..68fdd15 100644
--- a/media/libstagefright/codecs/flac/dec/Android.bp
+++ b/media/libstagefright/codecs/flac/dec/Android.bp
@@ -1,4 +1,48 @@
cc_library_shared {
+ name: "libstagefright_soft_c2flacdec",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ srcs: [
+ "C2SoftFlacDecoder.cpp",
+ ],
+
+ include_dirs: [
+ "external/flac/include",
+ "frameworks/av/media/libstagefright/flac/dec",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ "unsigned-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+
+ shared_libs: [
+ "liblog",
+ "libutils",
+ "libstagefright_flacdec",
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_foundation",
+ "libstagefright_simple_c2component",
+ ],
+}
+
+
+cc_library_shared {
name: "libstagefright_soft_flacdec",
vendor_available: true,
vndk: {
diff --git a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
new file mode 100644
index 0000000..ce40d6b
--- /dev/null
+++ b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.cpp
@@ -0,0 +1,266 @@
+/*
+ * 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 "C2SoftFlacDecoder"
+#include <utils/Log.h>
+
+#include "C2SoftFlacDecoder.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.flac.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_FLAC)
+ .outputMediaType(MEDIA_MIMETYPE_AUDIO_RAW)
+ .build();
+}
+
+C2SoftFlacDecoder::C2SoftFlacDecoder(const char *name, c2_node_id_t id)
+ : SimpleC2Component(BuildIntf(name, id)),
+ mFLACDecoder(nullptr) {
+}
+
+C2SoftFlacDecoder::~C2SoftFlacDecoder() {
+}
+
+c2_status_t C2SoftFlacDecoder::onInit() {
+ status_t err = initDecoder();
+ return err == OK ? C2_OK : C2_NO_MEMORY;
+}
+
+c2_status_t C2SoftFlacDecoder::onStop() {
+ if (mFLACDecoder) mFLACDecoder->flush();
+ memset(&mStreamInfo, 0, sizeof(mStreamInfo));
+ mHasStreamInfo = false;
+ mSignalledError = false;
+ mSignalledOutputEos = false;
+ mInputBufferCount = 0;
+ return C2_OK;
+}
+
+void C2SoftFlacDecoder::onReset() {
+ (void)onStop();
+}
+
+void C2SoftFlacDecoder::onRelease() {
+}
+
+c2_status_t C2SoftFlacDecoder::onFlush_sm() {
+ return onStop();
+}
+
+status_t C2SoftFlacDecoder::initDecoder() {
+ mFLACDecoder = FLACDecoder::Create();
+ if (!mFLACDecoder) {
+ ALOGE("initDecoder: failed to create FLACDecoder");
+ mSignalledError = true;
+ return NO_MEMORY;
+ }
+
+ memset(&mStreamInfo, 0, sizeof(mStreamInfo));
+ mHasStreamInfo = false;
+ mSignalledError = false;
+ mSignalledOutputEos = false;
+ mInputBufferCount = 0;
+
+ return 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;
+}
+
+// (TODO) add multiframe support, in plugin and FLACDecoder.cpp
+void C2SoftFlacDecoder::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();
+ 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;
+ return;
+ }
+ bool eos = (work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0;
+ bool codecConfig = (work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) != 0;
+
+ ALOGV("in buffer attr. size %zu timestamp %d frameindex %d", inSize,
+ (int)work->input.ordinal.timestamp.peeku(), (int)work->input.ordinal.frameIndex.peeku());
+
+ if (inSize == 0) {
+ fillEmptyWork(work);
+ if (eos) {
+ mSignalledOutputEos = true;
+ ALOGV("signalled EOS");
+ }
+ return;
+ }
+
+ if (mInputBufferCount == 0 && !codecConfig) {
+ ALOGV("First frame has to include configuration, forcing config");
+ codecConfig = true;
+ }
+
+ uint8_t *input = const_cast<uint8_t *>(rView.data() + inOffset);
+ if (codecConfig) {
+ status_t decoderErr = mFLACDecoder->parseMetadata(input, inSize);
+ if (decoderErr != OK && decoderErr != WOULD_BLOCK) {
+ ALOGE("process: FLACDecoder parseMetaData returns error %d", decoderErr);
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+
+ mInputBufferCount++;
+ fillEmptyWork(work);
+ if (eos) {
+ mSignalledOutputEos = true;
+ ALOGV("signalled EOS");
+ }
+
+ if (decoderErr == WOULD_BLOCK) {
+ ALOGV("process: parseMetadata is Blocking, Continue %d", decoderErr);
+ } else {
+ mStreamInfo = mFLACDecoder->getStreamInfo();
+ if (mStreamInfo.max_blocksize && mStreamInfo.channels)
+ mHasStreamInfo = true;
+ ALOGD("process: decoder configuration : %d Hz, %d channels, %d samples,"
+ " %d block size", mStreamInfo.sample_rate, mStreamInfo.channels,
+ (int)mStreamInfo.total_samples, mStreamInfo.max_blocksize);
+ }
+ return;
+ }
+
+ size_t outSize;
+ if (mHasStreamInfo)
+ outSize = mStreamInfo.max_blocksize * mStreamInfo.channels * sizeof(short);
+ else
+ outSize = kMaxBlockSize * FLACDecoder::kMaxChannels * sizeof(short);
+
+ std::shared_ptr<C2LinearBlock> block;
+ C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+ c2_status_t err = pool->fetchLinearBlock(outSize, 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 = C2_CORRUPTED;
+ return;
+ }
+
+ short *output = reinterpret_cast<short *>(wView.data());
+ status_t decoderErr = mFLACDecoder->decodeOneFrame(
+ input, inSize, output, &outSize);
+ if (decoderErr != OK) {
+ ALOGE("process: FLACDecoder decodeOneFrame returns error %d", decoderErr);
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+
+ mInputBufferCount++;
+ 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) {
+ mSignalledOutputEos = true;
+ ALOGV("signalled EOS");
+ }
+}
+
+c2_status_t C2SoftFlacDecoder::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;
+ }
+
+ if (mFLACDecoder) mFLACDecoder->flush();
+
+ return C2_OK;
+}
+
+class C2SoftFlacDecFactory : 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 C2SoftFlacDecoder(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 ~C2SoftFlacDecFactory() override = default;
+};
+
+} // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+ ALOGV("in %s", __func__);
+ return new ::android::C2SoftFlacDecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+ ALOGV("in %s", __func__);
+ delete factory;
+}
diff --git a/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
new file mode 100644
index 0000000..a5c01a9
--- /dev/null
+++ b/media/libstagefright/codecs/flac/dec/C2SoftFlacDecoder.h
@@ -0,0 +1,63 @@
+/*
+ * 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_FLAC_DECODER_H_
+#define C2_SOFT_FLAC_DECODER_H_
+
+#include <SimpleC2Component.h>
+
+#include "FLACDecoder.h"
+#include <media/stagefright/foundation/ABase.h>
+
+namespace android {
+
+struct C2SoftFlacDecoder : public SimpleC2Component {
+ C2SoftFlacDecoder(const char *name, c2_node_id_t id);
+ virtual ~C2SoftFlacDecoder();
+
+ // 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 {
+ kMaxBlockSize = 4096
+ };
+
+ sp<FLACDecoder> mFLACDecoder;
+ FLAC__StreamMetadata_StreamInfo mStreamInfo;
+ bool mSignalledError;
+ bool mSignalledOutputEos;
+ bool mHasStreamInfo;
+ size_t mInputBufferCount;
+
+ status_t initDecoder();
+
+ DISALLOW_EVIL_CONSTRUCTORS(C2SoftFlacDecoder);
+};
+
+} // namespace android
+
+#endif // C2_SOFT_FLAC_DECODER_H_
diff --git a/media/libstagefright/codecs/flac/enc/Android.bp b/media/libstagefright/codecs/flac/enc/Android.bp
index 46b974d..bb705dd 100644
--- a/media/libstagefright/codecs/flac/enc/Android.bp
+++ b/media/libstagefright/codecs/flac/enc/Android.bp
@@ -1,4 +1,43 @@
cc_library_shared {
+ name: "libstagefright_soft_c2flacenc",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ srcs: ["C2SoftFlacEnc.cpp"],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ "unsigned-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+
+ static_libs: [
+ "libFLAC",
+ ],
+
+ shared_libs: [
+ "liblog",
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_foundation",
+ "libstagefright_simple_c2component",
+ "libutils",
+ ],
+}
+
+cc_library_shared {
srcs: ["SoftFlacEncoder.cpp"],
diff --git a/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.cpp b/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.cpp
new file mode 100644
index 0000000..6c147ad
--- /dev/null
+++ b/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.cpp
@@ -0,0 +1,389 @@
+/*
+ * 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 "C2SoftFlacEnc"
+#include <utils/Log.h>
+
+#include "C2SoftFlacEnc.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.flac.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_FLAC)
+ .build();
+}
+
+C2SoftFlacEnc::C2SoftFlacEnc(const char *name, c2_node_id_t id)
+ : SimpleC2Component(BuildIntf(name, id)),
+ mFlacStreamEncoder(nullptr),
+ mInputBufferPcm32(nullptr) {
+}
+
+C2SoftFlacEnc::~C2SoftFlacEnc() {
+ onRelease();
+}
+
+c2_status_t C2SoftFlacEnc::onInit() {
+ mFlacStreamEncoder = FLAC__stream_encoder_new();
+ if (!mFlacStreamEncoder) return C2_CORRUPTED;
+
+ mInputBufferPcm32 = (FLAC__int32*) malloc(
+ kInBlockSize * kMaxNumChannels * sizeof(FLAC__int32));
+ if (!mInputBufferPcm32) return C2_NO_MEMORY;
+
+ mSignalledError = false;
+ mSignalledOutputEos = false;
+ mNumChannels = 1;
+ mSampleRate = 44100;
+ mCompressionLevel = FLAC_COMPRESSION_LEVEL_DEFAULT;
+ mIsFirstFrame = true;
+ mAnchorTimeStamp = 0ull;
+ mProcessedSamples = 0u;
+ mEncoderWriteData = false;
+ mEncoderReturnedNbBytes = 0;
+#ifdef WRITE_FLAC_HEADER_IN_FIRST_BUFFER
+ mHeaderOffset = 0;
+ mWroteHeader = false;
+#endif
+
+ status_t err = configureEncoder();
+ return err == OK ? C2_OK : C2_CORRUPTED;
+}
+
+void C2SoftFlacEnc::onRelease() {
+ if (mFlacStreamEncoder) {
+ FLAC__stream_encoder_delete(mFlacStreamEncoder);
+ mFlacStreamEncoder = nullptr;
+ }
+
+ if (mInputBufferPcm32) {
+ free(mInputBufferPcm32);
+ mInputBufferPcm32 = nullptr;
+ }
+}
+
+void C2SoftFlacEnc::onReset() {
+ mNumChannels = 1;
+ mSampleRate = 44100;
+ mCompressionLevel = FLAC_COMPRESSION_LEVEL_DEFAULT;
+ (void) onStop();
+}
+
+c2_status_t C2SoftFlacEnc::onStop() {
+ mSignalledError = false;
+ mSignalledOutputEos = false;
+ mIsFirstFrame = true;
+ mAnchorTimeStamp = 0ull;
+ mProcessedSamples = 0u;
+ mEncoderWriteData = false;
+ mEncoderReturnedNbBytes = 0;
+#ifdef WRITE_FLAC_HEADER_IN_FIRST_BUFFER
+ mHeaderOffset = 0;
+ mWroteHeader = false;
+#endif
+
+ c2_status_t status = drain(DRAIN_COMPONENT_NO_EOS, nullptr);
+ if (C2_OK != status) return status;
+
+ status_t err = configureEncoder();
+ if (err != OK) mSignalledError = true;
+ return C2_OK;
+}
+
+c2_status_t C2SoftFlacEnc::onFlush_sm() {
+ return onStop();
+}
+
+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 C2SoftFlacEnc::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 = C2_CORRUPTED;
+ 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);
+ if (mIsFirstFrame && inSize) {
+ mAnchorTimeStamp = work->input.ordinal.timestamp.peekull();
+ mIsFirstFrame = false;
+ }
+ uint64_t outTimeStamp = mProcessedSamples * 1000000ll / mSampleRate;
+
+ size_t outCapacity = inSize;
+ outCapacity += mBlockSize * mNumChannels * sizeof(int16_t);
+#ifdef WRITE_FLAC_HEADER_IN_FIRST_BUFFER
+ outCapacity += FLAC_HEADER_SIZE;
+#endif
+ C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+ c2_status_t err = pool->fetchLinearBlock(outCapacity, usage, &mOutputBlock);
+ if (err != C2_OK) {
+ ALOGE("fetchLinearBlock for Output failed with status %d", err);
+ work->result = C2_NO_MEMORY;
+ return;
+ }
+ C2WriteView wView = mOutputBlock->map().get();
+ if (wView.error()) {
+ ALOGE("write view map failed %d", wView.error());
+ work->result = C2_CORRUPTED;
+ return;
+ }
+
+ mEncoderWriteData = true;
+ mEncoderReturnedNbBytes = 0;
+ while (inOffset < inSize) {
+ size_t processSize = MIN(kInBlockSize * mNumChannels * sizeof(int16_t), (inSize - inOffset));
+ 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);
+ ALOGV("about to encode %zu bytes", processSize);
+
+ for (unsigned i = 0; i < nbInputSamples; i++) {
+ mInputBufferPcm32[i] = (FLAC__int32) pcm16[i];
+ }
+
+ FLAC__bool ok = FLAC__stream_encoder_process_interleaved(
+ mFlacStreamEncoder, mInputBufferPcm32, nbInputFrames);
+ if (!ok) {
+ ALOGE("error encountered during encoding");
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ mOutputBlock.reset();
+ return;
+ }
+ inOffset += processSize;
+ }
+ if (eos && !drain(DRAIN_COMPONENT_WITH_EOS, pool)) {
+ ALOGE("error encountered during encoding");
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ mOutputBlock.reset();
+ return;
+ }
+ fillEmptyWork(work);
+ if (mEncoderReturnedNbBytes != 0) {
+ std::shared_ptr<C2Buffer> buffer = createLinearBuffer(std::move(mOutputBlock), 0, mEncoderReturnedNbBytes);
+ work->worklets.front()->output.buffers.push_back(buffer);
+ work->worklets.front()->output.ordinal.timestamp = mAnchorTimeStamp + outTimeStamp;
+ } else {
+ ALOGV("encoder process_interleaved returned without data to write");
+ }
+ mOutputBlock = nullptr;
+ if (eos) {
+ mSignalledOutputEos = true;
+ ALOGV("signalled EOS");
+ }
+ mEncoderWriteData = false;
+ mEncoderReturnedNbBytes = 0;
+}
+
+FLAC__StreamEncoderWriteStatus C2SoftFlacEnc::onEncodedFlacAvailable(
+ const FLAC__byte buffer[], size_t bytes, unsigned samples,
+ unsigned current_frame) {
+ (void) current_frame;
+ ALOGV("%s (bytes=%zu, samples=%u, curr_frame=%u)", __func__, bytes, samples,
+ current_frame);
+
+#ifdef WRITE_FLAC_HEADER_IN_FIRST_BUFFER
+ if (samples == 0) {
+ ALOGI("saving %zu bytes of header", bytes);
+ memcpy(mHeader + mHeaderOffset, buffer, bytes);
+ mHeaderOffset += bytes;// will contain header size when finished receiving header
+ return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
+ }
+#endif
+
+ if ((samples == 0) || !mEncoderWriteData) {
+ // called by the encoder because there's header data to save, but it's not the role
+ // of this component (unless WRITE_FLAC_HEADER_IN_FIRST_BUFFER is defined)
+ ALOGV("ignoring %zu bytes of header data (samples=%d)", bytes, samples);
+ return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
+ }
+
+ // write encoded data
+ C2WriteView wView = mOutputBlock->map().get();
+ uint8_t* outData = wView.data();
+#ifdef WRITE_FLAC_HEADER_IN_FIRST_BUFFER
+ if (!mWroteHeader) {
+ ALOGI("writing %d bytes of header on output", mHeaderOffset);
+ memcpy(outData + mEncoderReturnedNbBytes, mHeader, mHeaderOffset);
+ mEncoderReturnedNbBytes += mHeaderOffset;
+ mWroteHeader = true;
+ }
+#endif
+ ALOGV("writing %zu bytes of encoded data on output", bytes);
+ // increment mProcessedSamples to maintain audio synchronization during
+ // play back
+ mProcessedSamples += samples;
+ if (bytes + mEncoderReturnedNbBytes > mOutputBlock->capacity()) {
+ ALOGE("not enough space left to write encoded data, dropping %zu bytes", bytes);
+ // a fatal error would stop the encoding
+ return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
+ }
+ memcpy(outData + mEncoderReturnedNbBytes, buffer, bytes);
+ mEncoderReturnedNbBytes += bytes;
+ return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
+}
+
+
+status_t C2SoftFlacEnc::configureEncoder() {
+ ALOGV("%s numChannel=%d, sampleRate=%d", __func__, mNumChannels, mSampleRate);
+
+ if (mSignalledError || !mFlacStreamEncoder) {
+ ALOGE("can't configure encoder: no encoder or invalid state");
+ return UNKNOWN_ERROR;
+ }
+
+ FLAC__bool ok = true;
+ ok = ok && FLAC__stream_encoder_set_channels(mFlacStreamEncoder, mNumChannels);
+ ok = ok && FLAC__stream_encoder_set_sample_rate(mFlacStreamEncoder, mSampleRate);
+ ok = ok && FLAC__stream_encoder_set_bits_per_sample(mFlacStreamEncoder, 16);
+ ok = ok && FLAC__stream_encoder_set_compression_level(mFlacStreamEncoder, mCompressionLevel);
+ ok = ok && FLAC__stream_encoder_set_verify(mFlacStreamEncoder, false);
+ if (!ok) {
+ ALOGE("unknown error when configuring encoder");
+ return UNKNOWN_ERROR;
+ }
+
+ ok &= FLAC__STREAM_ENCODER_INIT_STATUS_OK ==
+ FLAC__stream_encoder_init_stream(mFlacStreamEncoder,
+ flacEncoderWriteCallback /*write_callback*/,
+ nullptr /*seek_callback*/,
+ nullptr /*tell_callback*/,
+ nullptr /*metadata_callback*/,
+ (void *) this /*client_data*/);
+
+ if (!ok) {
+ ALOGE("unknown error when configuring encoder");
+ return UNKNOWN_ERROR;
+ }
+
+ mBlockSize = FLAC__stream_encoder_get_blocksize(mFlacStreamEncoder);
+
+ ALOGV("encoder successfully configured");
+ return OK;
+}
+
+FLAC__StreamEncoderWriteStatus C2SoftFlacEnc::flacEncoderWriteCallback(
+ const FLAC__StreamEncoder *,
+ const FLAC__byte buffer[],
+ size_t bytes,
+ unsigned samples,
+ unsigned current_frame,
+ void *client_data) {
+ return ((C2SoftFlacEnc*) client_data)->onEncodedFlacAvailable(
+ buffer, bytes, samples, current_frame);
+}
+
+c2_status_t C2SoftFlacEnc::drain(
+ uint32_t drainMode,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ (void) pool;
+ switch (drainMode) {
+ case NO_DRAIN:
+ ALOGW("drain with NO_DRAIN: no-op");
+ return C2_OK;
+ case DRAIN_CHAIN:
+ ALOGW("DRAIN_CHAIN not supported");
+ return C2_OMITTED;
+ case DRAIN_COMPONENT_WITH_EOS:
+ // TODO: This flag is not being sent back to the client
+ // because there are no items in PendingWork queue as all the
+ // inputs are being sent back with emptywork or valid encoded data
+ // mSignalledOutputEos = true;
+ case DRAIN_COMPONENT_NO_EOS:
+ break;
+ default:
+ return C2_BAD_VALUE;
+ }
+ FLAC__bool ok = FLAC__stream_encoder_finish(mFlacStreamEncoder);
+ if (!ok) return C2_CORRUPTED;
+ mIsFirstFrame = true;
+ mAnchorTimeStamp = 0ull;
+ mProcessedSamples = 0u;
+
+ return C2_OK;
+}
+
+class C2SoftFlacEncFactory : 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 C2SoftFlacEnc(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 ~C2SoftFlacEncFactory() override = default;
+};
+
+} // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+ ALOGV("in %s", __func__);
+ return new ::android::C2SoftFlacEncFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+ ALOGV("in %s", __func__);
+ delete factory;
+}
diff --git a/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.h b/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.h
new file mode 100644
index 0000000..cc8cb86
--- /dev/null
+++ b/media/libstagefright/codecs/flac/enc/C2SoftFlacEnc.h
@@ -0,0 +1,96 @@
+/*
+ * 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_FLAC_ENC_H_
+#define C2_SOFT_FLAC_ENC_H_
+
+#include <SimpleC2Component.h>
+
+#include "FLAC/stream_encoder.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+
+// use this symbol to have the first output buffer start with FLAC frame header so a dump of
+// all the output buffers can be opened as a .flac file
+//#define WRITE_FLAC_HEADER_IN_FIRST_BUFFER
+
+#define FLAC_COMPRESSION_LEVEL_MIN 0
+#define FLAC_COMPRESSION_LEVEL_DEFAULT 5
+#define FLAC_COMPRESSION_LEVEL_MAX 8
+
+#define FLAC_HEADER_SIZE 128
+
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+
+namespace android {
+
+class C2SoftFlacEnc : public SimpleC2Component {
+public:
+ C2SoftFlacEnc(const char *name, c2_node_id_t id);
+ virtual ~C2SoftFlacEnc();
+
+ // 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 configureEncoder();
+ static FLAC__StreamEncoderWriteStatus flacEncoderWriteCallback(
+ const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[],
+ size_t bytes, unsigned samples, unsigned current_frame,
+ void *client_data);
+ FLAC__StreamEncoderWriteStatus onEncodedFlacAvailable(
+ const FLAC__byte buffer[], size_t bytes, unsigned samples,
+ unsigned current_frame);
+
+ const unsigned int kInBlockSize = 1152;
+ const unsigned int kMaxNumChannels = 2;
+ FLAC__StreamEncoder* mFlacStreamEncoder;
+ FLAC__int32* mInputBufferPcm32;
+ std::shared_ptr<C2LinearBlock> mOutputBlock;
+ bool mSignalledError;
+ bool mSignalledOutputEos;
+ uint32_t mNumChannels;
+ uint32_t mSampleRate;
+ uint32_t mCompressionLevel;
+ uint32_t mBlockSize;
+ bool mIsFirstFrame;
+ uint64_t mAnchorTimeStamp;
+ uint64_t mProcessedSamples;
+ // should the data received by the callback be written to the output port
+ bool mEncoderWriteData;
+ size_t mEncoderReturnedNbBytes;
+#ifdef WRITE_FLAC_HEADER_IN_FIRST_BUFFER
+ unsigned mHeaderOffset;
+ bool mWroteHeader;
+ char mHeader[FLAC_HEADER_SIZE];
+#endif
+
+ DISALLOW_EVIL_CONSTRUCTORS(C2SoftFlacEnc);
+};
+
+} // namespace android
+
+#endif // C2_SOFT_FLAC_ENC_H_
diff --git a/media/libstagefright/codecs/m4v_h263/dec/Android.bp b/media/libstagefright/codecs/m4v_h263/dec/Android.bp
index ca70cc2..d24a116 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/Android.bp
+++ b/media/libstagefright/codecs/m4v_h263/dec/Android.bp
@@ -112,3 +112,91 @@
},
compile_multilib: "32",
}
+
+//###############################################################################
+
+cc_library_shared {
+ name: "libstagefright_soft_c2mpeg4dec",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ srcs: ["C2SoftMpeg4Dec.cpp"],
+
+ cflags: [
+ "-DOSCL_IMPORT_REF=",
+ "-DMPEG4",
+ "-Wall",
+ "-Werror",
+ ],
+
+ local_include_dirs: ["src"],
+ export_include_dirs: ["include"],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+
+ static_libs: ["libstagefright_m4vh263dec"],
+
+ shared_libs: [
+ "liblog",
+ "libutils",
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_foundation",
+ "libstagefright_simple_c2component",
+ ],
+
+ ldflags: ["-Wl,-Bsymbolic"],
+}
+
+//###############################################################################
+cc_library_shared {
+ name: "libstagefright_soft_c2h263dec",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ srcs: ["C2SoftMpeg4Dec.cpp"],
+
+ cflags: [
+ "-DOSCL_IMPORT_REF=",
+ "-Wall",
+ "-Werror",
+ ],
+
+ local_include_dirs: ["src"],
+ export_include_dirs: ["include"],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+
+ static_libs: ["libstagefright_m4vh263dec"],
+
+ shared_libs: [
+ "liblog",
+ "libutils",
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_foundation",
+ "libstagefright_simple_c2component",
+ ],
+
+ ldflags: ["-Wl,-Bsymbolic"],
+}
diff --git a/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.cpp b/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.cpp
new file mode 100644
index 0000000..2a3239f
--- /dev/null
+++ b/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.cpp
@@ -0,0 +1,502 @@
+/*
+ * 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 "C2SoftMpeg4Dec"
+#include <utils/Log.h>
+
+#include "C2SoftMpeg4Dec.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AUtils.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+#include "mp4dec_api.h"
+
+namespace android {
+
+#ifdef MPEG4
+constexpr char kComponentName[] = "c2.google.mpeg4.decoder";
+#else
+constexpr char kComponentName[] = "c2.google.h263.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(C2FormatVideo)
+ .inputMediaType(
+#ifdef MPEG4
+ MEDIA_MIMETYPE_VIDEO_MPEG4
+#else
+ MEDIA_MIMETYPE_VIDEO_H263
+#endif
+ )
+ .outputMediaType(MEDIA_MIMETYPE_VIDEO_RAW)
+ .build();
+}
+
+C2SoftMpeg4Dec::C2SoftMpeg4Dec(const char *name, c2_node_id_t id)
+ : SimpleC2Component(BuildIntf(name, id)),
+ mDecHandle(nullptr) {
+}
+
+C2SoftMpeg4Dec::~C2SoftMpeg4Dec() {
+ onRelease();
+}
+
+c2_status_t C2SoftMpeg4Dec::onInit() {
+ status_t err = initDecoder();
+ return err == OK ? C2_OK : C2_CORRUPTED;
+}
+
+c2_status_t C2SoftMpeg4Dec::onStop() {
+ if (mInitialized) {
+ PVCleanUpVideoDecoder(mDecHandle);
+ mInitialized = false;
+ }
+ for (int32_t i = 0; i < kNumOutputBuffers; ++i) {
+ if (mOutputBuffer[i]) {
+ free(mOutputBuffer[i]);
+ mOutputBuffer[i] = nullptr;
+ }
+ }
+ mNumSamplesOutput = 0;
+ mFramesConfigured = false;
+ mSignalledOutputEos = false;
+ mSignalledError = false;
+
+ return C2_OK;
+}
+
+void C2SoftMpeg4Dec::onReset() {
+ (void) onStop();
+}
+
+void C2SoftMpeg4Dec::onRelease() {
+ if (mInitialized) {
+ PVCleanUpVideoDecoder(mDecHandle);
+ }
+ if (mOutBlock) {
+ mOutBlock.reset();
+ }
+ for (int32_t i = 0; i < kNumOutputBuffers; ++i) {
+ if (mOutputBuffer[i]) {
+ free(mOutputBuffer[i]);
+ mOutputBuffer[i] = nullptr;
+ }
+ }
+
+ delete mDecHandle;
+ mDecHandle = nullptr;
+}
+
+c2_status_t C2SoftMpeg4Dec::onFlush_sm() {
+ if (mInitialized) {
+ if (PV_TRUE != PVResetVideoDecoder(mDecHandle)) return C2_CORRUPTED;
+ }
+ mSignalledOutputEos = false;
+ mSignalledError = false;
+ return C2_OK;
+}
+
+status_t C2SoftMpeg4Dec::initDecoder() {
+#ifdef MPEG4
+ mIsMpeg4 = true;
+#else
+ mIsMpeg4 = false;
+#endif
+ if (!mDecHandle) {
+ mDecHandle = new tagvideoDecControls;
+ }
+ 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
+ * overcome by adaptivePlayBack() api call. This call gets width and height
+ * information from extractor. Such a thing is not possible here.
+ * So we are configuring to larger values.*/
+ mWidth = 1408;
+ mHeight = 1152;
+ mNumSamplesOutput = 0;
+ mInitialized = false;
+ mFramesConfigured = false;
+ mSignalledOutputEos = false;
+ mSignalledError = false;
+
+ 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 C2SoftMpeg4Dec::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 C2SoftMpeg4Dec::ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool) {
+ if (!mDecHandle) {
+ ALOGE("not supposed to be here, invalid decoder context");
+ return C2_CORRUPTED;
+ }
+
+ uint32_t outSize = align(mWidth, 16) * align(mHeight, 16) * 3 / 2;
+ for (int32_t i = 0; i < kNumOutputBuffers; ++i) {
+ if (!mOutputBuffer[i]) {
+ mOutputBuffer[i] = (uint8_t *)malloc(outSize * sizeof(uint8_t));
+ if (!mOutputBuffer[i]) return C2_NO_MEMORY;
+ }
+ }
+ if (mOutBlock &&
+ (mOutBlock->width() != align(mWidth, 16) || 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(align(mWidth, 16), 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(), mWidth, mHeight);
+ }
+ return C2_OK;
+}
+
+bool C2SoftMpeg4Dec::handleResChange(const std::unique_ptr<C2Work> &work) {
+ uint32_t disp_width, disp_height;
+ PVGetVideoDimensions(mDecHandle, (int32 *)&disp_width, (int32 *)&disp_height);
+
+ uint32_t buf_width, buf_height;
+ PVGetBufferDimensions(mDecHandle, (int32 *)&buf_width, (int32 *)&buf_height);
+
+ CHECK_LE(disp_width, buf_width);
+ CHECK_LE(disp_height, buf_height);
+
+ ALOGV("display size (%dx%d), buffer size (%dx%d)",
+ disp_width, disp_height, buf_width, buf_height);
+
+ bool resChanged = false;
+ if (disp_width != mWidth || disp_height != mHeight) {
+ mWidth = disp_width;
+ mHeight = disp_height;
+ resChanged = true;
+ for (int32_t i = 0; i < kNumOutputBuffers; ++i) {
+ if (mOutputBuffer[i]) {
+ free(mOutputBuffer[i]);
+ mOutputBuffer[i] = nullptr;
+ }
+ }
+
+ if (!mIsMpeg4) {
+ PVCleanUpVideoDecoder(mDecHandle);
+
+ uint8_t *vol_data[1]{};
+ int32_t vol_size = 0;
+
+ if (!PVInitVideoDecoder(
+ mDecHandle, vol_data, &vol_size, 1, mWidth, mHeight, H263_MODE)) {
+ ALOGE("Error in PVInitVideoDecoder H263_MODE while resChanged was set to true");
+ work->result = C2_CORRUPTED;
+ mSignalledError = true;
+ return true;
+ }
+ }
+ mFramesConfigured = false;
+ }
+ return resChanged;
+}
+
+/* TODO: can remove temporary copy after library supports writing to display
+ * buffer Y, U and V plane pointers using stride info. */
+static void copyOutputBufferToYV12Frame(uint8_t *dst, uint8_t *src, size_t dstYStride,
+ size_t srcYStride, uint32_t width, uint32_t height) {
+ size_t dstUVStride = align(dstYStride / 2, 16);
+ size_t srcUVStride = srcYStride / 2;
+ uint8_t *srcStart = src;
+ uint8_t *dstStart = dst;
+ size_t vStride = align(height, 16);
+ for (size_t i = 0; i < height; ++i) {
+ memcpy(dst, src, width);
+ src += srcYStride;
+ dst += dstYStride;
+ }
+ /* U buffer */
+ src = srcStart + vStride * srcYStride;
+ dst = dstStart + (dstYStride * height) + (dstUVStride * height / 2);
+ for (size_t i = 0; i < height / 2; ++i) {
+ memcpy(dst, src, width / 2);
+ src += srcUVStride;
+ dst += dstUVStride;
+ }
+ /* V buffer */
+ src = srcStart + vStride * srcYStride * 5 / 4;
+ dst = dstStart + (dstYStride * height);
+ for (size_t i = 0; i < height / 2; ++i) {
+ memcpy(dst, src, width / 2);
+ src += srcUVStride;
+ dst += dstUVStride;
+ }
+}
+
+void C2SoftMpeg4Dec::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 = C2_CORRUPTED;
+ 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);
+
+ bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
+ if (inSize == 0) {
+ fillEmptyWork(work);
+ if (eos) {
+ mSignalledOutputEos = true;
+ }
+ return;
+ }
+
+ uint8_t *bitstream = const_cast<uint8_t *>(rView.data() + inOffset);
+ uint32_t *start_code = (uint32_t *)bitstream;
+ bool volHeader = *start_code == 0xB0010000;
+ if (volHeader) {
+ PVCleanUpVideoDecoder(mDecHandle);
+ mInitialized = false;
+ }
+
+ if (!mInitialized) {
+ uint8_t *vol_data[1]{};
+ int32_t vol_size = 0;
+
+ bool codecConfig = (work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) != 0;
+ if (codecConfig || volHeader) {
+ vol_data[0] = bitstream;
+ vol_size = inSize;
+ }
+ MP4DecodingMode mode = (mIsMpeg4) ? MPEG4_MODE : H263_MODE;
+
+ if (!PVInitVideoDecoder(
+ mDecHandle, vol_data, &vol_size, 1,
+ mWidth, mHeight, mode)) {
+ ALOGE("PVInitVideoDecoder failed. Unsupported content?");
+ work->result = C2_CORRUPTED;
+ mSignalledError = true;
+ return;
+ }
+ mInitialized = true;
+ MP4DecodingMode actualMode = PVGetDecBitstreamMode(mDecHandle);
+ if (mode != actualMode) {
+ ALOGE("Decoded mode not same as actual mode of the decoder");
+ work->result = C2_CORRUPTED;
+ mSignalledError = true;
+ return;
+ }
+
+ PVSetPostProcType(mDecHandle, 0);
+ (void) handleResChange(work);
+ if (codecConfig) {
+ fillEmptyWork(work);
+ return;
+ }
+ }
+
+ while (inOffset < inSize) {
+ c2_status_t err = ensureDecoderState(pool);
+ if (C2_OK != err) {
+ mSignalledError = true;
+ work->result = err;
+ return;
+ }
+ C2GraphicView wView = mOutBlock->map().get();
+ if (wView.error()) {
+ ALOGE("graphic view map failed %d", wView.error());
+ work->result = C2_CORRUPTED;
+ return;
+ }
+
+ uint32_t outSize = align(mWidth, 16) * align(mHeight, 16) * 3 / 2;
+ uint32_t yFrameSize = sizeof(uint8) * mDecHandle->size;
+ if (outSize < yFrameSize * 3 / 2){
+ ALOGE("Too small output buffer: %d bytes", outSize);
+ work->result = C2_NO_MEMORY;
+ mSignalledError = true;
+ return;
+ }
+
+ if (!mFramesConfigured) {
+ PVSetReferenceYUV(mDecHandle,mOutputBuffer[1]);
+ mFramesConfigured = true;
+ }
+
+ // Need to check if header contains new info, e.g., width/height, etc.
+ VopHeaderInfo header_info;
+ uint32_t useExtTimestamp = (inOffset == 0);
+ int32_t tmpInSize = (int32_t)inSize;
+ uint8_t *bitstreamTmp = bitstream;
+ uint32_t timestamp = workIndex;
+ if (PVDecodeVopHeader(
+ mDecHandle, &bitstreamTmp, ×tamp, &tmpInSize,
+ &header_info, &useExtTimestamp,
+ mOutputBuffer[mNumSamplesOutput & 1]) != PV_TRUE) {
+ ALOGE("failed to decode vop header.");
+ work->result = C2_CORRUPTED;
+ mSignalledError = true;
+ return;
+ }
+
+ // H263 doesn't have VOL header, the frame size information is in short header, i.e. the
+ // decoder may detect size change after PVDecodeVopHeader.
+ bool resChange = handleResChange(work);
+ if (mIsMpeg4 && resChange) {
+ work->result = C2_CORRUPTED;
+ mSignalledError = true;
+ return;
+ } else if (resChange) {
+ continue;
+ }
+
+ if (PVDecodeVopBody(mDecHandle, &tmpInSize) != PV_TRUE) {
+ ALOGE("failed to decode video frame.");
+ work->result = C2_CORRUPTED;
+ mSignalledError = true;
+ return;
+ }
+ if (handleResChange(work)) {
+ work->result = C2_CORRUPTED;
+ mSignalledError = true;
+ return;
+ }
+
+ uint8_t *outputBufferY = wView.data()[C2PlanarLayout::PLANE_Y];
+ (void)copyOutputBufferToYV12Frame(outputBufferY, mOutputBuffer[mNumSamplesOutput & 1],
+ wView.width(), align(mWidth, 16), mWidth, mHeight);
+
+ inOffset += inSize - (size_t)tmpInSize;
+ finishWork(workIndex, work);
+ ++mNumSamplesOutput;
+ if (inSize - inOffset) {
+ ALOGD("decoded frame, ignoring further trailing bytes %zu",
+ inSize - (size_t)tmpInSize);
+ break;
+ }
+ }
+}
+
+c2_status_t C2SoftMpeg4Dec::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 C2SoftMpeg4DecFactory : 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 C2SoftMpeg4Dec(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 ~C2SoftMpeg4DecFactory() override = default;
+};
+
+} // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+ ALOGV("in %s", __func__);
+ return new ::android::C2SoftMpeg4DecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+ ALOGV("in %s", __func__);
+ delete factory;
+}
diff --git a/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.h b/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.h
new file mode 100644
index 0000000..8eb316e
--- /dev/null
+++ b/media/libstagefright/codecs/m4v_h263/dec/C2SoftMpeg4Dec.h
@@ -0,0 +1,73 @@
+/*
+ * 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_MPEG4_DEC_H_
+#define C2_SOFT_MPEG4_DEC_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+
+struct tagvideoDecControls;
+
+namespace android {
+
+struct C2SoftMpeg4Dec : public SimpleC2Component {
+ C2SoftMpeg4Dec(const char *name, c2_node_id_t id);
+ virtual ~C2SoftMpeg4Dec();
+
+ // 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 {
+ kNumOutputBuffers = 2,
+ };
+
+ status_t initDecoder();
+ c2_status_t ensureDecoderState(const std::shared_ptr<C2BlockPool> &pool);
+ void finishWork(uint64_t index, const std::unique_ptr<C2Work> &work);
+ bool handleResChange(const std::unique_ptr<C2Work> &work);
+
+ tagvideoDecControls *mDecHandle;
+ std::shared_ptr<C2GraphicBlock> mOutBlock;
+ uint8_t *mOutputBuffer[kNumOutputBuffers];
+
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mNumSamplesOutput;
+
+ bool mIsMpeg4;
+ bool mInitialized;
+ bool mFramesConfigured;
+ bool mSignalledOutputEos;
+ bool mSignalledError;
+
+ DISALLOW_EVIL_CONSTRUCTORS(C2SoftMpeg4Dec);
+};
+
+} // namespace android
+
+#endif // C2_SOFT_MPEG4_DEC_H_
diff --git a/media/libstagefright/codecs/mpeg2dec/Android.bp b/media/libstagefright/codecs/mpeg2dec/Android.bp
index fb0db8f..3123376 100644
--- a/media/libstagefright/codecs/mpeg2dec/Android.bp
+++ b/media/libstagefright/codecs/mpeg2dec/Android.bp
@@ -1,4 +1,47 @@
cc_library_shared {
+ name: "libstagefright_soft_c2mpeg2dec",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ srcs: ["C2SoftMpeg2Dec.cpp"],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ include_dirs: [
+ "external/libmpeg2/decoder",
+ "external/libmpeg2/common",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ ],
+ cfi: false, // true,
+ diag: {
+ cfi: false, // true,
+ },
+ },
+
+ static_libs: ["libmpeg2dec"],
+
+ shared_libs: [
+ "liblog",
+ "libutils",
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_foundation",
+ "libstagefright_simple_c2component",
+ ],
+
+ ldflags: ["-Wl,-Bsymbolic"],
+}
+
+cc_library_shared {
name: "libstagefright_soft_mpeg2dec",
vendor_available: true,
vndk: {
diff --git a/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.cpp b/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.cpp
new file mode 100644
index 0000000..0ebe7d6
--- /dev/null
+++ b/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.cpp
@@ -0,0 +1,795 @@
+/*
+ * 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 "C2SoftMpeg2Dec"
+#include <utils/Log.h>
+
+#include "iv_datatypedef.h"
+#include "iv.h"
+#include "ivd.h"
+#include "impeg2d.h"
+#include "C2SoftMpeg2Dec.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.mpeg2.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_MPEG2)
+ .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(WORD32 alignment, WORD32 size) {
+ return memalign(alignment, size);
+}
+
+static void ivd_aligned_free(void *mem) {
+ free(mem);
+}
+
+C2SoftMpeg2Dec::C2SoftMpeg2Dec(const char *name, c2_node_id_t id)
+ : SimpleC2Component(BuildIntf(name, id)),
+ mDecHandle(nullptr),
+ mMemRecords(nullptr),
+ mOutBufferDrain(nullptr),
+ mIvColorformat(IV_YUV_420P),
+ mWidth(320),
+ mHeight(240) {
+ // If input dump is enabled, then open create an empty file
+ GENERATE_FILE_NAMES();
+ CREATE_DUMP_FILE(mInFile);
+}
+
+C2SoftMpeg2Dec::~C2SoftMpeg2Dec() {
+ onRelease();
+}
+
+c2_status_t C2SoftMpeg2Dec::onInit() {
+ status_t err = initDecoder();
+ return err == OK ? C2_OK : C2_CORRUPTED;
+}
+
+c2_status_t C2SoftMpeg2Dec::onStop() {
+ if (OK != resetDecoder()) return C2_CORRUPTED;
+ resetPlugin();
+ return C2_OK;
+}
+
+void C2SoftMpeg2Dec::onReset() {
+ (void) onStop();
+}
+
+void C2SoftMpeg2Dec::onRelease() {
+ (void) deleteDecoder();
+ if (mOutBufferDrain) {
+ ivd_aligned_free(mOutBufferDrain);
+ mOutBufferDrain = nullptr;
+ }
+ if (mOutBlock) {
+ mOutBlock.reset();
+ }
+ if (mMemRecords) {
+ ivd_aligned_free(mMemRecords);
+ mMemRecords = nullptr;
+ }
+}
+
+c2_status_t C2SoftMpeg2Dec::onFlush_sm() {
+ if (OK != setFlushMode()) return C2_CORRUPTED;
+
+ uint32_t displayStride = mStride;
+ uint32_t displayHeight = mHeight;
+ uint32_t bufferSize = displayStride * displayHeight * 3 / 2;
+ mOutBufferDrain = (uint8_t *)ivd_aligned_malloc(128, bufferSize);
+ if (!mOutBufferDrain) {
+ 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(mOutBufferDrain);
+ mOutBufferDrain = nullptr;
+
+ return C2_OK;
+}
+
+status_t C2SoftMpeg2Dec::getNumMemRecords() {
+ iv_num_mem_rec_ip_t s_num_mem_rec_ip;
+ iv_num_mem_rec_op_t s_num_mem_rec_op;
+
+ s_num_mem_rec_ip.u4_size = sizeof(s_num_mem_rec_ip);
+ s_num_mem_rec_ip.e_cmd = IV_CMD_GET_NUM_MEM_REC;
+ s_num_mem_rec_op.u4_size = sizeof(s_num_mem_rec_op);
+
+ IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+ &s_num_mem_rec_ip,
+ &s_num_mem_rec_op);
+ if (IV_SUCCESS != status) {
+ ALOGE("Error in getting mem records: 0x%x", s_num_mem_rec_op.u4_error_code);
+ return UNKNOWN_ERROR;
+ }
+ mNumMemRecords = s_num_mem_rec_op.u4_num_mem_rec;
+
+ return OK;
+}
+
+status_t C2SoftMpeg2Dec::fillMemRecords() {
+ iv_mem_rec_t *ps_mem_rec = (iv_mem_rec_t *) ivd_aligned_malloc(
+ 128, mNumMemRecords * sizeof(iv_mem_rec_t));
+ if (!ps_mem_rec) {
+ ALOGE("Allocation failure");
+ return NO_MEMORY;
+ }
+ memset(ps_mem_rec, 0, mNumMemRecords * sizeof(iv_mem_rec_t));
+ for (size_t i = 0; i < mNumMemRecords; i++)
+ ps_mem_rec[i].u4_size = sizeof(iv_mem_rec_t);
+ mMemRecords = ps_mem_rec;
+
+ ivdext_fill_mem_rec_ip_t s_fill_mem_ip;
+ ivdext_fill_mem_rec_op_t s_fill_mem_op;
+
+ s_fill_mem_ip.s_ivd_fill_mem_rec_ip_t.u4_size = sizeof(ivdext_fill_mem_rec_ip_t);
+ s_fill_mem_ip.u4_share_disp_buf = 0;
+ s_fill_mem_ip.e_output_format = mIvColorformat;
+ s_fill_mem_ip.u4_deinterlace = 1;
+ s_fill_mem_ip.s_ivd_fill_mem_rec_ip_t.e_cmd = IV_CMD_FILL_NUM_MEM_REC;
+ s_fill_mem_ip.s_ivd_fill_mem_rec_ip_t.pv_mem_rec_location = mMemRecords;
+ s_fill_mem_ip.s_ivd_fill_mem_rec_ip_t.u4_max_frm_wd = mWidth;
+ s_fill_mem_ip.s_ivd_fill_mem_rec_ip_t.u4_max_frm_ht = mHeight;
+ s_fill_mem_op.s_ivd_fill_mem_rec_op_t.u4_size = sizeof(ivdext_fill_mem_rec_op_t);
+ IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+ &s_fill_mem_ip,
+ &s_fill_mem_op);
+ if (IV_SUCCESS != status) {
+ ALOGE("Error in filling mem records: 0x%x",
+ s_fill_mem_op.s_ivd_fill_mem_rec_op_t.u4_error_code);
+ return UNKNOWN_ERROR;
+ }
+
+ CHECK_EQ(mNumMemRecords, s_fill_mem_op.s_ivd_fill_mem_rec_op_t.u4_num_mem_rec_filled);
+ for (size_t i = 0; i < mNumMemRecords; i++, ps_mem_rec++) {
+ ps_mem_rec->pv_base = ivd_aligned_malloc(
+ ps_mem_rec->u4_mem_alignment, ps_mem_rec->u4_mem_size);
+ if (!ps_mem_rec->pv_base) {
+ ALOGE("Allocation failure for memory record #%zu of size %u",
+ i, ps_mem_rec->u4_mem_size);
+ return NO_MEMORY;
+ }
+ }
+
+ return OK;
+}
+
+status_t C2SoftMpeg2Dec::createDecoder() {
+ ivdext_init_ip_t s_init_ip;
+ ivdext_init_op_t s_init_op;
+
+ s_init_ip.s_ivd_init_ip_t.u4_size = sizeof(ivdext_init_ip_t);
+ s_init_ip.s_ivd_init_ip_t.e_cmd = (IVD_API_COMMAND_TYPE_T)IV_CMD_INIT;
+ s_init_ip.s_ivd_init_ip_t.pv_mem_rec_location = mMemRecords;
+ s_init_ip.s_ivd_init_ip_t.u4_frm_max_wd = mWidth;
+ s_init_ip.s_ivd_init_ip_t.u4_frm_max_ht = mHeight;
+ s_init_ip.u4_share_disp_buf = 0;
+ s_init_ip.u4_deinterlace = 1;
+ s_init_ip.s_ivd_init_ip_t.u4_num_mem_rec = mNumMemRecords;
+ s_init_ip.s_ivd_init_ip_t.e_output_format = mIvColorformat;
+ s_init_op.s_ivd_init_op_t.u4_size = sizeof(ivdext_init_op_t);
+
+ mDecHandle = (iv_obj_t *)mMemRecords[0].pv_base;
+ mDecHandle->pv_fxns = (void *)ivdec_api_function;
+ mDecHandle->u4_size = sizeof(iv_obj_t);
+
+ IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+ &s_init_ip,
+ &s_init_op);
+ if (status != IV_SUCCESS) {
+ ALOGE("error in %s: 0x%x", __func__,
+ s_init_op.s_ivd_init_op_t.u4_error_code);
+ return UNKNOWN_ERROR;
+ }
+
+ return OK;
+}
+
+status_t C2SoftMpeg2Dec::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 (status != IV_SUCCESS) {
+ ALOGD("error in %s: 0x%x", __func__, s_set_num_cores_op.u4_error_code);
+ return UNKNOWN_ERROR;
+ }
+
+ return OK;
+}
+
+status_t C2SoftMpeg2Dec::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 C2SoftMpeg2Dec::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 C2SoftMpeg2Dec::initDecoder() {
+ status_t ret = getNumMemRecords();
+ if (OK != ret) return ret;
+
+ ret = fillMemRecords();
+ if (OK != ret) return ret;
+
+ 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 C2SoftMpeg2Dec::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 - inOffset;
+ } 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] = mOutBufferDrain;
+ ps_decode_ip->s_out_buffer.pu1_bufs[1] = mOutBufferDrain + lumaSize;
+ ps_decode_ip->s_out_buffer.pu1_bufs[2] = mOutBufferDrain + lumaSize + chromaSize;
+ }
+ ps_decode_ip->s_out_buffer.u4_num_bufs = 3;
+ ps_decode_op->u4_size = sizeof(ivd_video_decode_op_t);
+
+ return true;
+}
+
+bool C2SoftMpeg2Dec::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 C2SoftMpeg2Dec::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 C2SoftMpeg2Dec::handleColorAspectsChange() {
+ if (mPreference == kPreferBitstream) {
+ updateFinalColorAspects(mDefaultColorAspects, mBitstreamColorAspects);
+ } else if (mPreference == kPreferContainer) {
+ updateFinalColorAspects(mBitstreamColorAspects, mDefaultColorAspects);
+ } else {
+ return C2_CORRUPTED;
+ }
+ return C2_OK;
+}
+
+bool C2SoftMpeg2Dec::getSeqInfo() {
+ ivdext_ctl_get_seq_info_ip_t s_ctl_get_seq_info_ip;
+ ivdext_ctl_get_seq_info_op_t s_ctl_get_seq_info_op;
+
+ s_ctl_get_seq_info_ip.u4_size = sizeof(ivdext_ctl_get_seq_info_ip_t);
+ s_ctl_get_seq_info_ip.e_cmd = IVD_CMD_VIDEO_CTL;
+ s_ctl_get_seq_info_ip.e_sub_cmd =
+ (IVD_CONTROL_API_COMMAND_TYPE_T)IMPEG2D_CMD_CTL_GET_SEQ_INFO;
+ s_ctl_get_seq_info_op.u4_size = sizeof(ivdext_ctl_get_seq_info_op_t);
+ IV_API_CALL_STATUS_T status = ivdec_api_function(mDecHandle,
+ &s_ctl_get_seq_info_ip,
+ &s_ctl_get_seq_info_op);
+ if (status != IV_SUCCESS) {
+ ALOGW("Error in getting Sequence info: 0x%x", s_ctl_get_seq_info_op.u4_error_code);
+ return false;
+ }
+
+ int32_t primaries = s_ctl_get_seq_info_op.u1_colour_primaries;
+ int32_t transfer = s_ctl_get_seq_info_op.u1_transfer_characteristics;
+ int32_t coeffs = s_ctl_get_seq_info_op.u1_matrix_coefficients;
+ bool full_range = false; // mpeg2 video has limited range.
+
+ 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 C2SoftMpeg2Dec::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 C2SoftMpeg2Dec::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;
+ }
+ (void) setNumCores();
+ mStride = 0;
+ mSignalledError = false;
+
+ return OK;
+}
+
+void C2SoftMpeg2Dec::resetPlugin() {
+ mSignalledOutputEos = false;
+ gettimeofday(&mTimeStart, nullptr);
+ gettimeofday(&mTimeEnd, nullptr);
+}
+
+status_t C2SoftMpeg2Dec::deleteDecoder() {
+ if (mMemRecords) {
+ iv_mem_rec_t *ps_mem_rec = mMemRecords;
+
+ for (size_t i = 0; i < mNumMemRecords; i++, ps_mem_rec++) {
+ if (ps_mem_rec->pv_base) {
+ ivd_aligned_free(ps_mem_rec->pv_base);
+ }
+ }
+ ivd_aligned_free(mMemRecords);
+ mMemRecords = nullptr;
+ }
+ mDecHandle = nullptr;
+
+ return OK;
+}
+
+status_t C2SoftMpeg2Dec::reInitDecoder() {
+ deleteDecoder();
+
+ status_t ret = initDecoder();
+ if (OK != ret) {
+ ALOGE("Failed to initialize decoder");
+ deleteDecoder();
+ return ret;
+ }
+ 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 C2SoftMpeg2Dec::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 C2SoftMpeg2Dec::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 C2SoftMpeg2Dec::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 = C2_CORRUPTED;
+ 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);
+ while (inOffset < 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 = C2_CORRUPTED;
+ 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, inSize, workIndex)) {
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+ // If input dump is enabled, then write to file
+ DUMP_TO_FILE(mInFile, s_decode_ip.pv_stream_buffer, s_decode_ip.u4_num_Bytes);
+ 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 (IMPEG2D_UNSUPPORTED_DIMENSIONS == s_decode_op.u4_error_code) {
+ ALOGV("unsupported resolution : %dx%d", s_decode_op.u4_pic_wd, s_decode_op.u4_pic_ht);
+ drainInternal(DRAIN_COMPONENT_NO_EOS, pool, work);
+ resetPlugin();
+ mWidth = s_decode_op.u4_pic_wd;
+ mHeight = s_decode_op.u4_pic_ht;
+ if (OK != reInitDecoder()) {
+ ALOGE("Failed to reinitialize decoder");
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+ continue;
+ } else if (IVD_RES_CHANGED == (s_decode_op.u4_error_code & 0xFF)) {
+ ALOGV("resolution changed");
+ drainInternal(DRAIN_COMPONENT_NO_EOS, pool, work);
+ resetDecoder();
+ resetPlugin();
+ mWidth = s_decode_op.u4_pic_wd;
+ mHeight = s_decode_op.u4_pic_ht;
+ continue;
+ }
+
+ (void) getSeqInfo();
+ 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);
+ }
+ inOffset += s_decode_op.u4_num_bytes_consumed;
+ if (hasPicture && (inSize - inOffset)) {
+ ALOGD("decoded frame in current access nal, ignoring further trailing bytes %d",
+ (int)inSize - (int)inOffset);
+ break;
+ }
+ }
+
+ if (eos) {
+ drainInternal(DRAIN_COMPONENT_WITH_EOS, pool, work);
+ mSignalledOutputEos = true;
+ } else if (!hasPicture) {
+ fillEmptyWork(work);
+ }
+}
+
+c2_status_t C2SoftMpeg2Dec::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 C2SoftMpeg2Dec::drain(
+ uint32_t drainMode,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ return drainInternal(drainMode, pool, nullptr);
+}
+
+class C2SoftMpeg2DecFactory : 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 C2SoftMpeg2Dec(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 ~C2SoftMpeg2DecFactory() override = default;
+};
+
+} // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+ ALOGV("in %s", __func__);
+ return new ::android::C2SoftMpeg2DecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+ ALOGV("in %s", __func__);
+ delete factory;
+}
diff --git a/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.h b/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.h
new file mode 100644
index 0000000..64e5b05
--- /dev/null
+++ b/media/libstagefright/codecs/mpeg2dec/C2SoftMpeg2Dec.h
@@ -0,0 +1,185 @@
+/*
+ * 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_MPEG2_DEC_H_
+#define C2_SOFT_MPEG2_DEC_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/ColorUtils.h>
+
+namespace android {
+
+#define ivdec_api_function impeg2d_api_function
+#define ivdext_init_ip_t impeg2d_init_ip_t
+#define ivdext_init_op_t impeg2d_init_op_t
+#define ivdext_fill_mem_rec_ip_t impeg2d_fill_mem_rec_ip_t
+#define ivdext_fill_mem_rec_op_t impeg2d_fill_mem_rec_op_t
+#define ivdext_ctl_set_num_cores_ip_t impeg2d_ctl_set_num_cores_ip_t
+#define ivdext_ctl_set_num_cores_op_t impeg2d_ctl_set_num_cores_op_t
+#define ivdext_ctl_get_seq_info_ip_t impeg2d_ctl_get_seq_info_ip_t
+#define ivdext_ctl_get_seq_info_op_t impeg2d_ctl_get_seq_info_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)IMPEG2D_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);
+
+#ifdef FILE_DUMP_ENABLE
+ #define INPUT_DUMP_PATH "/sdcard/clips/mpeg2d_input"
+ #define INPUT_DUMP_EXT "m2v"
+ #define GENERATE_FILE_NAMES() { \
+ GETTIME(&mTimeStart, NULL); \
+ strcpy(mInFile, ""); \
+ sprintf(mInFile, "%s_%ld.%ld.%s", INPUT_DUMP_PATH, \
+ mTimeStart.tv_sec, mTimeStart.tv_usec, \
+ INPUT_DUMP_EXT); \
+ }
+ #define CREATE_DUMP_FILE(m_filename) { \
+ FILE *fp = fopen(m_filename, "wb"); \
+ if (fp != NULL) { \
+ fclose(fp); \
+ } else { \
+ ALOGD("Could not open file %s", m_filename); \
+ } \
+ }
+ #define DUMP_TO_FILE(m_filename, m_buf, m_size) \
+ { \
+ FILE *fp = fopen(m_filename, "ab"); \
+ if (fp != NULL && m_buf != NULL) { \
+ uint32_t i; \
+ i = fwrite(m_buf, 1, m_size, fp); \
+ ALOGD("fwrite ret %d to write %d", i, m_size); \
+ if (i != (uint32_t)m_size) { \
+ ALOGD("Error in fwrite, returned %d", i); \
+ perror("Error in write to file"); \
+ } \
+ fclose(fp); \
+ } else { \
+ ALOGD("Could not write to file %s", m_filename);\
+ } \
+ }
+#else /* FILE_DUMP_ENABLE */
+ #define INPUT_DUMP_PATH
+ #define INPUT_DUMP_EXT
+ #define OUTPUT_DUMP_PATH
+ #define OUTPUT_DUMP_EXT
+ #define GENERATE_FILE_NAMES()
+ #define CREATE_DUMP_FILE(m_filename)
+ #define DUMP_TO_FILE(m_filename, m_buf, m_size)
+#endif /* FILE_DUMP_ENABLE */
+
+struct C2SoftMpeg2Dec : public SimpleC2Component {
+ C2SoftMpeg2Dec(const char *name, c2_node_id_t id);
+ virtual ~C2SoftMpeg2Dec();
+
+ // 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 getNumMemRecords();
+ status_t fillMemRecords();
+ 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 getSeqInfo();
+ // 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();
+ status_t reInitDecoder();
+
+ // 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;
+ iv_mem_rec_t *mMemRecords;
+ size_t mNumMemRecords;
+ std::shared_ptr<C2GraphicBlock> mOutBlock;
+ uint8_t *mOutBufferDrain;
+
+ 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;
+#ifdef FILE_DUMP_ENABLE
+ char mInFile[200];
+#endif /* FILE_DUMP_ENABLE */
+
+ DISALLOW_EVIL_CONSTRUCTORS(C2SoftMpeg2Dec);
+};
+
+} // namespace android
+
+#endif // C2_SOFT_MPEG2_DEC_H_
diff --git a/media/libstagefright/codecs/on2/dec/Android.bp b/media/libstagefright/codecs/on2/dec/Android.bp
index 8a9399a..03f0c05 100644
--- a/media/libstagefright/codecs/on2/dec/Android.bp
+++ b/media/libstagefright/codecs/on2/dec/Android.bp
@@ -37,3 +37,82 @@
},
compile_multilib: "32",
}
+
+cc_library_shared {
+ name: "libstagefright_soft_c2vp9dec",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ srcs: ["C2SoftVpx.cpp"],
+
+ static_libs: ["libvpx"],
+
+ shared_libs: [
+ "liblog",
+ "libutils",
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_foundation",
+ "libstagefright_simple_c2component",
+ ],
+
+ cflags: [
+ "-DVP9",
+ "-Wall",
+ "-Werror",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ "unsigned-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+
+ ldflags: ["-Wl,-Bsymbolic"],
+}
+
+cc_library_shared {
+ name: "libstagefright_soft_c2vp8dec",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ srcs: ["C2SoftVpx.cpp"],
+
+ static_libs: ["libvpx"],
+
+ shared_libs: [
+ "liblog",
+ "libutils",
+ "libstagefright_codec2",
+ "libstagefright_codec2_vndk",
+ "libstagefright_foundation",
+ "libstagefright_simple_c2component",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ "unsigned-integer-overflow",
+ ],
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
+
+ ldflags: ["-Wl,-Bsymbolic"],
+}
diff --git a/media/libstagefright/codecs/on2/dec/C2SoftVpx.cpp b/media/libstagefright/codecs/on2/dec/C2SoftVpx.cpp
new file mode 100644
index 0000000..96b303c
--- /dev/null
+++ b/media/libstagefright/codecs/on2/dec/C2SoftVpx.cpp
@@ -0,0 +1,411 @@
+/*
+ * 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 "C2SoftVpx"
+#include <utils/Log.h>
+
+#include "C2SoftVpx.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AUtils.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+namespace android {
+
+#ifdef VP9
+constexpr char kComponentName[] = "c2.google.vp9.decoder";
+#else
+constexpr char kComponentName[] = "c2.google.vp8.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(C2FormatVideo)
+ .inputMediaType(
+#ifdef VP9
+ MEDIA_MIMETYPE_VIDEO_VP9
+#else
+ MEDIA_MIMETYPE_VIDEO_VP8
+#endif
+ )
+ .outputMediaType(MEDIA_MIMETYPE_VIDEO_RAW)
+ .build();
+}
+
+C2SoftVpx::C2SoftVpx(const char *name, c2_node_id_t id)
+ : SimpleC2Component(BuildIntf(name, id)),
+ mCodecCtx(nullptr) {
+}
+
+C2SoftVpx::~C2SoftVpx() {
+ onRelease();
+}
+
+c2_status_t C2SoftVpx::onInit() {
+ status_t err = initDecoder();
+ return err == OK ? C2_OK : C2_CORRUPTED;
+}
+
+c2_status_t C2SoftVpx::onStop() {
+ (void) onFlush_sm();
+ destroyDecoder();
+
+ mSignalledError = false;
+ mSignalledOutputEos = false;
+
+ return C2_OK;
+}
+
+void C2SoftVpx::onReset() {
+ (void) onStop();
+ (void) initDecoder();
+}
+
+void C2SoftVpx::onRelease() {
+ destroyDecoder();
+}
+
+c2_status_t C2SoftVpx::onFlush_sm() {
+ if (mFrameParallelMode) {
+ // Flush decoder by passing nullptr data ptr and 0 size.
+ // Ideally, this should never fail.
+ if (vpx_codec_decode(mCodecCtx, nullptr, 0, nullptr, 0)) {
+ ALOGE("Failed to flush on2 decoder.");
+ return C2_CORRUPTED;
+ }
+ }
+
+ // Drop all the decoded frames in decoder.
+ vpx_codec_iter_t iter = nullptr;
+ while (vpx_codec_get_frame(mCodecCtx, &iter)) {
+ }
+
+ mSignalledError = false;
+ mSignalledOutputEos = false;
+ return C2_OK;
+}
+
+static int GetCPUCoreCount() {
+ int 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: %d", cpuCoreCount);
+ return cpuCoreCount;
+}
+
+status_t C2SoftVpx::initDecoder() {
+#ifdef VP9
+ mMode = MODE_VP9;
+#else
+ mMode = MODE_VP8;
+#endif
+
+ mWidth = 320;
+ mHeight = 240;
+ mFrameParallelMode = false;
+ mSignalledOutputEos = false;
+ mSignalledError = false;
+
+ mCodecCtx = new vpx_codec_ctx_t;
+
+ vpx_codec_dec_cfg_t cfg;
+ memset(&cfg, 0, sizeof(vpx_codec_dec_cfg_t));
+ cfg.threads = GetCPUCoreCount();
+
+ vpx_codec_flags_t flags;
+ memset(&flags, 0, sizeof(vpx_codec_flags_t));
+ if (mFrameParallelMode) flags |= VPX_CODEC_USE_FRAME_THREADING;
+
+ vpx_codec_err_t vpx_err;
+ if ((vpx_err = vpx_codec_dec_init(
+ mCodecCtx, mMode == MODE_VP8 ? &vpx_codec_vp8_dx_algo : &vpx_codec_vp9_dx_algo,
+ &cfg, flags))) {
+ ALOGE("on2 decoder failed to initialize. (%d)", vpx_err);
+ return UNKNOWN_ERROR;
+ }
+
+ return OK;
+}
+
+status_t C2SoftVpx::destroyDecoder() {
+ if (mCodecCtx) {
+ vpx_codec_destroy(mCodecCtx);
+ delete mCodecCtx;
+ mCodecCtx = 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 C2SoftVpx::finishWork(uint64_t index, const std::unique_ptr<C2Work> &work,
+ const std::shared_ptr<C2GraphicBlock> &block) {
+ std::shared_ptr<C2Buffer> buffer = createGraphicBuffer(block,
+ C2Rect(mWidth, mHeight));
+ 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);
+ }
+}
+
+void C2SoftVpx::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();
+ 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;
+ return;
+ }
+
+ bool codecConfig = ((work->input.flags & C2FrameData::FLAG_CODEC_CONFIG) !=0);
+ bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
+
+ 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);
+
+ // Software VP9 Decoder does not need the Codec Specific Data (CSD)
+ // (specified in http://www.webmproject.org/vp9/profiles/). Ignore it if
+ // it was passed.
+ if (codecConfig) {
+ // Ignore CSD buffer for VP9.
+ if (mMode == MODE_VP9) {
+ fillEmptyWork(work);
+ return;
+ } else {
+ // Tolerate the CSD buffer for VP8. This is a workaround
+ // for b/28689536. continue
+ ALOGW("WARNING: Got CSD buffer for VP8. Continue");
+ }
+ }
+
+ uint8_t *bitstream = const_cast<uint8_t *>(rView.data() + inOffset);
+ int64_t frameIndex = work->input.ordinal.frameIndex.peekll();
+
+ if (inSize) {
+ vpx_codec_err_t err = vpx_codec_decode(
+ mCodecCtx, bitstream, inSize, &frameIndex, 0);
+ if (err != VPX_CODEC_OK) {
+ ALOGE("on2 decoder failed to decode frame. err: %d", err);
+ work->result = C2_CORRUPTED;
+ mSignalledError = true;
+ return;
+ }
+ }
+
+ (void)outputBuffer(pool, work);
+
+ if (eos) {
+ drainInternal(DRAIN_COMPONENT_WITH_EOS, pool, work);
+ mSignalledOutputEos = true;
+ } else if (!inSize) {
+ fillEmptyWork(work);
+ }
+}
+
+static void copyOutputBufferToYV12Frame(uint8_t *dst,
+ const uint8_t *srcY, const uint8_t *srcU, const uint8_t *srcV,
+ size_t srcYStride, size_t srcUStride, size_t srcVStride,
+ uint32_t width, uint32_t height, int32_t bpp) {
+ size_t dstYStride = align(width, 16) * bpp ;
+ size_t dstUVStride = align(dstYStride / 2, 16);
+ uint8_t *dstStart = dst;
+
+ for (size_t i = 0; i < height; ++i) {
+ memcpy(dst, srcY, width * bpp);
+ srcY += srcYStride;
+ dst += dstYStride;
+ }
+
+ dst = dstStart + dstYStride * height;
+ for (size_t i = 0; i < height / 2; ++i) {
+ memcpy(dst, srcV, width / 2 * bpp);
+ srcV += srcVStride;
+ dst += dstUVStride;
+ }
+
+ dst = dstStart + (dstYStride * height) + (dstUVStride * height / 2);
+ for (size_t i = 0; i < height / 2; ++i) {
+ memcpy(dst, srcU, width / 2 * bpp);
+ srcU += srcUStride;
+ dst += dstUVStride;
+ }
+}
+
+bool C2SoftVpx::outputBuffer(
+ const std::shared_ptr<C2BlockPool> &pool,
+ const std::unique_ptr<C2Work> &work)
+{
+ if (!(work && pool)) return false;
+
+ vpx_codec_iter_t iter = nullptr;
+ vpx_image_t *img = vpx_codec_get_frame(mCodecCtx, &iter);
+
+ if (!img) return false;
+
+ mWidth = img->d_w;
+ mHeight = img->d_h;
+
+ CHECK(img->fmt == VPX_IMG_FMT_I420 || img->fmt == VPX_IMG_FMT_I42016);
+ int32_t bpp = 1;
+ if (img->fmt == VPX_IMG_FMT_I42016) {
+ bpp = 2;
+ }
+
+ std::shared_ptr<C2GraphicBlock> block;
+ uint32_t format = HAL_PIXEL_FORMAT_YV12;
+ C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+ c2_status_t err = pool->fetchGraphicBlock(align(mWidth, 16) * bpp, mHeight, format, usage, &block);
+ if (err != C2_OK) {
+ ALOGE("fetchGraphicBlock for Output failed with status %d", err);
+ work->result = err;
+ return false;
+ }
+
+ C2GraphicView wView = block->map().get();
+ if (wView.error()) {
+ ALOGE("graphic view map failed %d", wView.error());
+ work->result = C2_CORRUPTED;
+ return false;
+ }
+
+ ALOGV("provided (%dx%d) required (%dx%d), out frameindex %d",
+ block->width(), block->height(), mWidth, mHeight, (int)*(int64_t *)img->user_priv);
+
+ uint8_t *dst = const_cast<uint8_t *>(wView.data()[C2PlanarLayout::PLANE_Y]);
+ size_t srcYStride = img->stride[VPX_PLANE_Y];
+ size_t srcUStride = img->stride[VPX_PLANE_U];
+ size_t srcVStride = img->stride[VPX_PLANE_V];
+ const uint8_t *srcY = (const uint8_t *)img->planes[VPX_PLANE_Y];
+ const uint8_t *srcU = (const uint8_t *)img->planes[VPX_PLANE_U];
+ const uint8_t *srcV = (const uint8_t *)img->planes[VPX_PLANE_V];
+ copyOutputBufferToYV12Frame(dst, srcY, srcU, srcV,
+ srcYStride, srcUStride, srcVStride, mWidth, mHeight, bpp);
+
+ finishWork(*(int64_t *)img->user_priv, work, std::move(block));
+ return true;
+}
+
+c2_status_t C2SoftVpx::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;
+ }
+
+ while ((outputBuffer(pool, work))) {
+ }
+
+ if (drainMode == DRAIN_COMPONENT_WITH_EOS &&
+ work && work->workletsProcessed == 0u) {
+ fillEmptyWork(work);
+ }
+
+ return C2_OK;
+}
+c2_status_t C2SoftVpx::drain(
+ uint32_t drainMode,
+ const std::shared_ptr<C2BlockPool> &pool) {
+ return drainInternal(drainMode, pool, nullptr);
+}
+
+class C2SoftVpxFactory : 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 C2SoftVpx(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 ~C2SoftVpxFactory() override = default;
+};
+
+} // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+ ALOGV("in %s", __func__);
+ return new ::android::C2SoftVpxFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+ ALOGV("in %s", __func__);
+ delete factory;
+}
diff --git a/media/libstagefright/codecs/on2/dec/C2SoftVpx.h b/media/libstagefright/codecs/on2/dec/C2SoftVpx.h
new file mode 100644
index 0000000..b5d4e21
--- /dev/null
+++ b/media/libstagefright/codecs/on2/dec/C2SoftVpx.h
@@ -0,0 +1,76 @@
+/*
+ * 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_VPX_H_
+#define C2_SOFT_VPX_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+
+#include "vpx/vpx_decoder.h"
+#include "vpx/vp8dx.h"
+
+namespace android {
+
+struct C2SoftVpx : public SimpleC2Component {
+ C2SoftVpx(const char *name, c2_node_id_t id);
+ virtual ~C2SoftVpx();
+
+ // 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 {
+ MODE_VP8,
+ MODE_VP9,
+ } mMode;
+
+ vpx_codec_ctx_t *mCodecCtx;
+ bool mFrameParallelMode; // Frame parallel is only supported by VP9 decoder.
+
+ uint32_t mWidth;
+ uint32_t mHeight;
+ bool mSignalledOutputEos;
+ bool mSignalledError;
+
+ status_t initDecoder();
+ status_t destroyDecoder();
+ void finishWork(uint64_t index, const std::unique_ptr<C2Work> &work,
+ const std::shared_ptr<C2GraphicBlock> &block);
+ bool outputBuffer(
+ const std::shared_ptr<C2BlockPool> &pool,
+ const std::unique_ptr<C2Work> &work);
+ c2_status_t drainInternal(
+ uint32_t drainMode,
+ const std::shared_ptr<C2BlockPool> &pool,
+ const std::unique_ptr<C2Work> &work);
+
+ DISALLOW_EVIL_CONSTRUCTORS(C2SoftVpx);
+};
+
+} // namespace android
+
+#endif // C2_SOFT_VPX_H_
diff --git a/media/libstagefright/codecs/opus/dec/Android.bp b/media/libstagefright/codecs/opus/dec/Android.bp
index 43318f2..38d72e6 100644
--- a/media/libstagefright/codecs/opus/dec/Android.bp
+++ b/media/libstagefright/codecs/opus/dec/Android.bp
@@ -1,4 +1,40 @@
cc_library_shared {
+ name: "libstagefright_soft_c2opusdec",
+// vendor_available: true,
+// vndk: {
+// enabled: true,
+// },
+
+ srcs: ["C2SoftOpus.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",
+ "libopus",
+ ],
+}
+
+cc_library_shared {
name: "libstagefright_soft_opusdec",
vendor_available: true,
vndk: {
diff --git a/media/libstagefright/codecs/opus/dec/C2SoftOpus.cpp b/media/libstagefright/codecs/opus/dec/C2SoftOpus.cpp
new file mode 100644
index 0000000..4eec362
--- /dev/null
+++ b/media/libstagefright/codecs/opus/dec/C2SoftOpus.cpp
@@ -0,0 +1,448 @@
+/*
+ * 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 "C2SoftOpus"
+#include <utils/Log.h>
+
+#include "C2SoftOpus.h"
+
+#include <C2PlatformSupport.h>
+#include <SimpleC2Interface.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/MediaDefs.h>
+
+extern "C" {
+ #include <opus.h>
+ #include <opus_multistream.h>
+}
+
+namespace android {
+
+constexpr char kComponentName[] = "c2.google.opus.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_OPUS)
+ .outputMediaType(MEDIA_MIMETYPE_AUDIO_RAW)
+ .build();
+}
+
+C2SoftOpus::C2SoftOpus(const char *name, c2_node_id_t id)
+ : SimpleC2Component(BuildIntf(name, id)),
+ mDecoder(nullptr) {
+}
+
+C2SoftOpus::~C2SoftOpus() {
+ onRelease();
+}
+
+c2_status_t C2SoftOpus::onInit() {
+ status_t err = initDecoder();
+ return err == OK ? C2_OK : C2_NO_MEMORY;
+}
+
+c2_status_t C2SoftOpus::onStop() {
+ if (mDecoder) {
+ opus_multistream_decoder_destroy(mDecoder);
+ mDecoder = nullptr;
+ }
+ memset(&mHeader, 0, sizeof(mHeader));
+ mCodecDelay = 0;
+ mSeekPreRoll = 0;
+ mSamplesToDiscard = 0;
+ mInputBufferCount = 0;
+ mSignalledError = false;
+ mSignalledOutputEos = false;
+
+ return C2_OK;
+}
+
+void C2SoftOpus::onReset() {
+ (void)onStop();
+}
+
+void C2SoftOpus::onRelease() {
+ if (mDecoder) {
+ opus_multistream_decoder_destroy(mDecoder);
+ mDecoder = nullptr;
+ }
+}
+
+status_t C2SoftOpus::initDecoder() {
+ memset(&mHeader, 0, sizeof(mHeader));
+ mCodecDelay = 0;
+ mSeekPreRoll = 0;
+ mSamplesToDiscard = 0;
+ mInputBufferCount = 0;
+ mSignalledError = false;
+ mSignalledOutputEos = false;
+
+ return OK;
+}
+
+c2_status_t C2SoftOpus::onFlush_sm() {
+ if (mDecoder) {
+ opus_multistream_decoder_ctl(mDecoder, OPUS_RESET_STATE);
+ mSamplesToDiscard = mSeekPreRoll;
+ mSignalledOutputEos = false;
+ }
+ return C2_OK;
+}
+
+c2_status_t C2SoftOpus::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 uint16_t ReadLE16(const uint8_t *data, size_t data_size,
+ uint32_t read_offset) {
+ if (read_offset + 1 > data_size)
+ return 0;
+ uint16_t val;
+ val = data[read_offset];
+ val |= data[read_offset + 1] << 8;
+ return val;
+}
+
+static const int kRate = 48000;
+
+// Opus uses Vorbis channel mapping, and Vorbis channel mapping specifies
+// mappings for up to 8 channels. This information is part of the Vorbis I
+// Specification:
+// http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html
+static const int kMaxChannels = 8;
+
+// Maximum packet size used in Xiph's opusdec.
+static const int kMaxOpusOutputPacketSizeSamples = 960 * 6;
+
+// Default audio output channel layout. Used to initialize |stream_map| in
+// OpusHeader, and passed to opus_multistream_decoder_create() when the header
+// does not contain mapping information. The values are valid only for mono and
+// stereo output: Opus streams with more than 2 channels require a stream map.
+static const int kMaxChannelsWithDefaultLayout = 2;
+static const uint8_t kDefaultOpusChannelLayout[kMaxChannelsWithDefaultLayout] = { 0, 1 };
+
+// Parses Opus Header. Header spec: http://wiki.xiph.org/OggOpus#ID_Header
+static bool ParseOpusHeader(const uint8_t *data, size_t data_size,
+ OpusHeader* header) {
+ // Size of the Opus header excluding optional mapping information.
+ const size_t kOpusHeaderSize = 19;
+
+ // Offset to the channel count byte in the Opus header.
+ const size_t kOpusHeaderChannelsOffset = 9;
+
+ // Offset to the pre-skip value in the Opus header.
+ const size_t kOpusHeaderSkipSamplesOffset = 10;
+
+ // Offset to the gain value in the Opus header.
+ const size_t kOpusHeaderGainOffset = 16;
+
+ // Offset to the channel mapping byte in the Opus header.
+ const size_t kOpusHeaderChannelMappingOffset = 18;
+
+ // Opus Header contains a stream map. The mapping values are in the header
+ // beyond the always present |kOpusHeaderSize| bytes of data. The mapping
+ // data contains stream count, coupling information, and per channel mapping
+ // values:
+ // - Byte 0: Number of streams.
+ // - Byte 1: Number coupled.
+ // - Byte 2: Starting at byte 2 are |header->channels| uint8 mapping
+ // values.
+ const size_t kOpusHeaderNumStreamsOffset = kOpusHeaderSize;
+ const size_t kOpusHeaderNumCoupledOffset = kOpusHeaderNumStreamsOffset + 1;
+ const size_t kOpusHeaderStreamMapOffset = kOpusHeaderNumStreamsOffset + 2;
+
+ if (data_size < kOpusHeaderSize) {
+ ALOGE("Header size is too small.");
+ return false;
+ }
+ header->channels = *(data + kOpusHeaderChannelsOffset);
+ if (header->channels <= 0 || header->channels > kMaxChannels) {
+ ALOGE("Invalid Header, wrong channel count: %d", header->channels);
+ return false;
+ }
+
+ header->skip_samples = ReadLE16(data,
+ data_size,
+ kOpusHeaderSkipSamplesOffset);
+
+ header->gain_db = static_cast<int16_t>(ReadLE16(data,
+ data_size,
+ kOpusHeaderGainOffset));
+
+ header->channel_mapping = *(data + kOpusHeaderChannelMappingOffset);
+ if (!header->channel_mapping) {
+ if (header->channels > kMaxChannelsWithDefaultLayout) {
+ ALOGE("Invalid Header, missing stream map.");
+ return false;
+ }
+ header->num_streams = 1;
+ header->num_coupled = header->channels > 1;
+ header->stream_map[0] = 0;
+ header->stream_map[1] = 1;
+ return true;
+ }
+ if (data_size < kOpusHeaderStreamMapOffset + header->channels) {
+ ALOGE("Invalid stream map; insufficient data for current channel "
+ "count: %d", header->channels);
+ return false;
+ }
+ header->num_streams = *(data + kOpusHeaderNumStreamsOffset);
+ header->num_coupled = *(data + kOpusHeaderNumCoupledOffset);
+ if (header->num_streams + header->num_coupled != header->channels) {
+ ALOGE("Inconsistent channel mapping.");
+ return false;
+ }
+ for (int i = 0; i < header->channels; ++i)
+ header->stream_map[i] = *(data + kOpusHeaderStreamMapOffset + i);
+ return true;
+}
+
+// Convert nanoseconds to number of samples.
+static uint64_t ns_to_samples(uint64_t ns, int rate) {
+ return static_cast<double>(ns) * rate / 1000000000;
+}
+
+void C2SoftOpus::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 = C2_CORRUPTED;
+ 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 < 3) {
+ if (mInputBufferCount == 0) {
+ if (!ParseOpusHeader(data, inSize, &mHeader)) {
+ ALOGE("Encountered error while Parsing Opus Header.");
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+ uint8_t channel_mapping[kMaxChannels] = {0};
+ if (mHeader.channels <= kMaxChannelsWithDefaultLayout) {
+ memcpy(&channel_mapping,
+ kDefaultOpusChannelLayout,
+ kMaxChannelsWithDefaultLayout);
+ } else {
+ memcpy(&channel_mapping,
+ mHeader.stream_map,
+ mHeader.channels);
+ }
+ int status = OPUS_INVALID_STATE;
+ mDecoder = opus_multistream_decoder_create(kRate,
+ mHeader.channels,
+ mHeader.num_streams,
+ mHeader.num_coupled,
+ channel_mapping,
+ &status);
+ if (!mDecoder || status != OPUS_OK) {
+ ALOGE("opus_multistream_decoder_create failed status = %s",
+ opus_strerror(status));
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+ status = opus_multistream_decoder_ctl(mDecoder,
+ OPUS_SET_GAIN(mHeader.gain_db));
+ if (status != OPUS_OK) {
+ ALOGE("Failed to set OPUS header gain; status = %s",
+ opus_strerror(status));
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+ } else {
+ if (inSize < 8) {
+ ALOGE("Input sample size is too small.");
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+ int64_t samples = ns_to_samples( *(reinterpret_cast<int64_t*>
+ (const_cast<uint8_t *> (data))), kRate);
+ if (mInputBufferCount == 1) {
+ mCodecDelay = samples;
+ mSamplesToDiscard = mCodecDelay;
+ }
+ else {
+ mSeekPreRoll = samples;
+ }
+ }
+
+ ++mInputBufferCount;
+ fillEmptyWork(work);
+ if (eos) {
+ mSignalledOutputEos = true;
+ ALOGV("signalled EOS");
+ }
+ return;
+ }
+
+ // Ignore CSD re-submissions.
+ if ((work->input.flags & C2FrameData::FLAG_CODEC_CONFIG)) {
+ fillEmptyWork(work);
+ return;
+ }
+
+ // When seeking to zero, |mCodecDelay| samples has to be discarded
+ // instead of |mSeekPreRoll| samples (as we would when seeking to any
+ // other timestamp).
+ if (work->input.ordinal.timestamp.peeku() == 0) mSamplesToDiscard = mCodecDelay;
+
+ std::shared_ptr<C2LinearBlock> block;
+ C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
+ c2_status_t err = pool->fetchLinearBlock(
+ kMaxNumSamplesPerBuffer * kMaxChannels * sizeof(int16_t),
+ 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 = C2_CORRUPTED;
+ return;
+ }
+
+ int numSamples = opus_multistream_decode(mDecoder,
+ data,
+ inSize,
+ reinterpret_cast<int16_t *> (wView.data()),
+ kMaxOpusOutputPacketSizeSamples,
+ 0);
+ if (numSamples < 0) {
+ ALOGE("opus_multistream_decode returned numSamples %d", numSamples);
+ numSamples = 0;
+ mSignalledError = true;
+ work->result = C2_CORRUPTED;
+ return;
+ }
+
+ int outOffset = 0;
+ if (mSamplesToDiscard > 0) {
+ if (mSamplesToDiscard > numSamples) {
+ mSamplesToDiscard -= numSamples;
+ numSamples = 0;
+ } else {
+ numSamples -= mSamplesToDiscard;
+ outOffset = mSamplesToDiscard * sizeof(int16_t) * mHeader.channels;
+ mSamplesToDiscard = 0;
+ }
+ }
+
+ if (numSamples) {
+ int outSize = numSamples * sizeof(int16_t) * mHeader.channels;
+ ALOGV("out buffer attr. offset %d size %d ", outOffset, outSize);
+
+ 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.ordinal = work->input.ordinal;
+ work->workletsProcessed = 1u;
+ } else {
+ fillEmptyWork(work);
+ block.reset();
+ }
+ if (eos) {
+ mSignalledOutputEos = true;
+ ALOGV("signalled EOS");
+ }
+}
+
+class C2SoftOpusDecFactory : 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 C2SoftOpus(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 ~C2SoftOpusDecFactory() override = default;
+};
+
+} // namespace android
+
+extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
+ ALOGV("in %s", __func__);
+ return new ::android::C2SoftOpusDecFactory();
+}
+
+extern "C" void DestroyCodec2Factory(::C2ComponentFactory* factory) {
+ ALOGV("in %s", __func__);
+ delete factory;
+}
diff --git a/media/libstagefright/codecs/opus/dec/C2SoftOpus.h b/media/libstagefright/codecs/opus/dec/C2SoftOpus.h
new file mode 100644
index 0000000..70ad2de
--- /dev/null
+++ b/media/libstagefright/codecs/opus/dec/C2SoftOpus.h
@@ -0,0 +1,76 @@
+/*
+ * 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_OPUS_H_
+#define C2_SOFT_OPUS_H_
+
+#include <SimpleC2Component.h>
+
+#include <media/stagefright/foundation/ABase.h>
+
+struct OpusMSDecoder;
+
+namespace android {
+
+struct OpusHeader {
+ int channels;
+ int skip_samples;
+ int channel_mapping;
+ int num_streams;
+ int num_coupled;
+ int16_t gain_db;
+ uint8_t stream_map[8];
+};
+
+struct C2SoftOpus : public SimpleC2Component {
+ C2SoftOpus(const char *name, c2_node_id_t id);
+ virtual ~C2SoftOpus();
+
+ // 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 {
+ kMaxNumSamplesPerBuffer = 960 * 6
+ };
+
+ OpusMSDecoder *mDecoder;
+ OpusHeader mHeader;
+
+ int64_t mCodecDelay;
+ int64_t mSeekPreRoll;
+ int64_t mSamplesToDiscard;
+ size_t mInputBufferCount;
+ bool mSignalledError;
+ bool mSignalledOutputEos;
+
+ status_t initDecoder();
+
+ DISALLOW_EVIL_CONSTRUCTORS(C2SoftOpus);
+};
+
+} // namespace android
+
+#endif // C2_SOFT_OPUS_H_
diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp
index bef2db4..5cc5093 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.cpp
+++ b/media/libstagefright/mpeg2ts/ATSParser.cpp
@@ -27,6 +27,7 @@
#include <binder/MemoryDealer.h>
#include <cutils/native_handle.h>
#include <hidlmemory/FrameworkUtils.h>
+#include <media/cas/DescramblerAPI.h>
#include <media/stagefright/foundation/ABitReader.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
@@ -1387,6 +1388,9 @@
uint32_t sctrl = tsScramblingControl != 0 ?
tsScramblingControl : pesScramblingControl;
+ if (mQueue->isScrambled()) {
+ sctrl |= DescramblerPlugin::kScrambling_Flag_PesHeader;
+ }
// Perform the 1st pass descrambling if needed
if (descrambleBytes > 0) {
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index 13f9208..4e29d37 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -471,7 +471,7 @@
Bundle args = new Bundle();
args.putInt(MediaSession2Stub.ARGUMENT_KEY_ITEM_INDEX, item);
sendTransportControlCommand(
- MediaSession2.COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM, args);
+ MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_TO_PLAYLIST_ITEM, args);
*/
}
@@ -483,16 +483,21 @@
}
@Override
- public void removePlaylistItem_impl(MediaItem2 index) {
+ public void addPlaylistItem_impl(int index, MediaItem2 item) {
// TODO(jaewan): Implement
}
@Override
- public void addPlaylistItem_impl(int index, MediaItem2 item) {
+ public void removePlaylistItem_impl(MediaItem2 item) {
// TODO(jaewan): Implement
}
@Override
+ public void replacePlaylistItem_impl(int index, MediaItem2 item) {
+ // TODO: Implement this
+ }
+
+ @Override
public PlaylistParams getPlaylistParams_impl() {
synchronized (mLock) {
return mPlaylistParams;
diff --git a/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
index 18ff1b9..a21fda6 100644
--- a/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaLibraryService2Impl.java
@@ -131,9 +131,8 @@
public static class BuilderImpl
extends BuilderBaseImpl<MediaLibrarySession, MediaLibrarySessionCallback> {
public BuilderImpl(MediaLibraryService2 service, Builder instance,
- MediaPlayerBase player, Executor callbackExecutor,
- MediaLibrarySessionCallback callback) {
- super(service, player);
+ Executor callbackExecutor, MediaLibrarySessionCallback callback) {
+ super(service);
setSessionCallback_impl(callbackExecutor, callback);
}
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
index ec4ce8d..5e7af3b 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -38,7 +38,8 @@
import android.media.MediaLibraryService2;
import android.media.MediaMetadata2;
import android.media.MediaPlayerBase;
-import android.media.MediaPlayerBase.EventCallback;
+import android.media.MediaPlayerBase.PlayerEventCallback;
+import android.media.MediaPlaylistController;
import android.media.MediaSession2;
import android.media.MediaSession2.Builder;
import android.media.MediaSession2.Command;
@@ -85,7 +86,7 @@
private final MediaSession2Stub mSessionStub;
private final SessionToken2 mSessionToken;
private final AudioManager mAudioManager;
- private final ArrayMap<EventCallback, Executor> mCallbacks = new ArrayMap<>();
+ private final ArrayMap<PlayerEventCallback, Executor> mCallbacks = new ArrayMap<>();
private final PendingIntent mSessionActivity;
// mPlayer is set to null when the session is closed, and we shouldn't throw an exception
@@ -202,24 +203,13 @@
}
@Override
- public void setPlayer_impl(MediaPlayerBase player) {
+ public void setPlayer_impl(MediaPlayerBase player, MediaPlaylistController mpcl,
+ VolumeProvider2 volumeProvider) throws IllegalArgumentException {
ensureCallingThread();
if (player == null) {
throw new IllegalArgumentException("player shouldn't be null");
}
- setPlayer(player, null);
- }
- @Override
- public void setPlayer_impl(MediaPlayerBase player, VolumeProvider2 volumeProvider)
- throws IllegalArgumentException {
- ensureCallingThread();
- if (player == null) {
- throw new IllegalArgumentException("player shouldn't be null");
- }
- if (volumeProvider == null) {
- throw new IllegalArgumentException("volumeProvider shouldn't be null");
- }
setPlayer(player, volumeProvider);
}
@@ -228,11 +218,11 @@
synchronized (mLock) {
if (mPlayer != null && mEventCallback != null) {
// This might not work for a poorly implemented player.
- mPlayer.unregisterEventCallback(mEventCallback);
+ mPlayer.unregisterPlayerEventCallback(mEventCallback);
}
mPlayer = player;
mEventCallback = new MyEventCallback(this, player);
- player.registerEventCallback(mCallbackExecutor, mEventCallback);
+ player.registerPlayerEventCallback(mCallbackExecutor, mEventCallback);
mVolumeProvider = volumeProvider;
mPlaybackInfo = info;
}
@@ -293,7 +283,7 @@
synchronized (mLock) {
if (mPlayer != null) {
// close can be called multiple times
- mPlayer.unregisterEventCallback(mEventCallback);
+ mPlayer.unregisterPlayerEventCallback(mEventCallback);
mPlayer = null;
}
}
@@ -347,7 +337,9 @@
ensureCallingThread();
final MediaPlayerBase player = mPlayer;
if (player != null) {
- player.stop();
+ // TODO: Uncomment or remove
+ //player.stop();
+ player.pause();
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
@@ -356,12 +348,16 @@
@Override
public void skipToPrevious_impl() {
ensureCallingThread();
+ // TODO: Uncomment or remove
+ /*
final MediaPlayerBase player = mPlayer;
if (player != null) {
- player.skipToPrevious();
+ // TODO implement
+ //player.skipToPrevious();
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
+ */
}
@Override
@@ -393,23 +389,32 @@
throw new IllegalArgumentException("params shouldn't be null");
}
ensureCallingThread();
+ // TODO: Uncomment or remove
+ /*
final MediaPlayerBase player = mPlayer;
if (player != null) {
- player.setPlaylistParams(params);
+ // 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.
- return player.getPlaylistParams();
+ // TODO implement
+ //return player.getPlaylistParams();
+ return null;
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
+ */
return null;
}
@@ -439,13 +444,17 @@
throw new IllegalArgumentException("playlist shouldn't be null");
}
ensureCallingThread();
+ // TODO: Uncomment or remove
+ /*
final MediaPlayerBase player = mPlayer;
if (player != null) {
- player.setPlaylist(playlist);
+ // TODO implement, use the SessionPlaylistController itf
+ //player.setPlaylist(playlist);
mSessionStub.notifyPlaylistChanged(playlist);
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
+ */
}
@Override
@@ -464,15 +473,25 @@
}
@Override
+ public void replacePlaylistItem_impl(int index, MediaItem2 item) {
+ // TODO(jaewan): Implement
+ }
+
+ @Override
public List<MediaItem2> getPlaylist_impl() {
+ // TODO: Uncomment or remove
+ /*
final MediaPlayerBase player = mPlayer;
if (player != null) {
// TODO(jaewan): Is it safe to be called on any thread?
// Otherwise MediaSession2 should cache parameter of setPlaylist.
- return player.getPlaylist();
+ // TODO implement
+ //return player.getPlaylist();
+ return null;
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
+ */
return null;
}
@@ -496,23 +515,31 @@
@Override
public void fastForward_impl() {
ensureCallingThread();
+ // TODO: Uncomment or remove
+ /*
final MediaPlayerBase player = mPlayer;
if (player != null) {
- player.fastForward();
+ // TODO implement
+ //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) {
- player.rewind();
+ // TODO implement
+ //player.rewind();
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
+ */
}
@Override
@@ -529,16 +556,20 @@
@Override
public void skipToPlaylistItem_impl(MediaItem2 item) {
ensureCallingThread();
+ // TODO: Uncomment or remove
+ /*
final MediaPlayerBase player = mPlayer;
if (player != null) {
- player.setCurrentPlaylistItem(item);
+ // TODO implement
+ //player.setCurrentPlaylistItem(item);
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
+ */
}
@Override
- public void registerPlayerEventCallback_impl(Executor executor, EventCallback callback) {
+ public void registerPlayerEventCallback_impl(Executor executor, PlayerEventCallback callback) {
if (executor == null) {
throw new IllegalArgumentException("executor shouldn't be null");
}
@@ -551,13 +582,16 @@
return;
}
mCallbacks.put(callback, executor);
+ // TODO: Uncomment or remove
+ /*
// TODO(jaewan): Double check if we need this.
final PlaybackState2 state = getInstance().getPlaybackState();
executor.execute(() -> callback.onPlaybackStateChanged(state));
+ */
}
@Override
- public void unregisterPlayerEventCallback_impl(EventCallback callback) {
+ public void unregisterPlayerEventCallback_impl(PlayerEventCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback shouldn't be null");
}
@@ -568,19 +602,24 @@
@Override
public PlaybackState2 getPlaybackState_impl() {
ensureCallingThread();
+ // TODO: Uncomment or remove
+ /*
final MediaPlayerBase player = mPlayer;
if (player != null) {
- // TODO(jaewan): Is it safe to be called on any thread?
+ // TODO(jaewan): Is it safe to be called on any thread?
// Otherwise MediaSession2 should cache the result from listener.
- return player.getPlaybackState();
+ // TODO implement
+ //return player.getPlaybackState();
+ return null;
} else if (DEBUG) {
Log.d(TAG, "API calls after the close()", new IllegalStateException());
}
+ */
return null;
}
@Override
- public void notifyError_impl(int errorCode, int extra) {
+ public void notifyError_impl(int errorCode, Bundle extras) {
// TODO(jaewan): Implement
}
@@ -609,30 +648,32 @@
}
private void notifyPlaybackStateChangedNotLocked(final PlaybackState2 state) {
- ArrayMap<EventCallback, Executor> callbacks = new ArrayMap<>();
+ 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 EventCallback callback = callbacks.keyAt(i);
+ final PlayerEventCallback callback = callbacks.keyAt(i);
final Executor executor = callbacks.valueAt(i);
- executor.execute(() -> callback.onPlaybackStateChanged(state));
+ // TODO: Uncomment or remove
+ //executor.execute(() -> callback.onPlaybackStateChanged(state));
}
// Notify to controllers as well.
mSessionStub.notifyPlaybackStateChangedNotLocked(state);
}
private void notifyErrorNotLocked(String mediaId, int what, int extra) {
- ArrayMap<EventCallback, Executor> callbacks = new ArrayMap<>();
+ 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 EventCallback callback = callbacks.keyAt(i);
+ final PlayerEventCallback callback = callbacks.keyAt(i);
final Executor executor = callbacks.valueAt(i);
- executor.execute(() -> callback.onError(mediaId, what, extra));
+ // TODO: Uncomment or remove
+ //executor.execute(() -> callback.onError(mediaId, what, extra));
}
// TODO(jaewan): Notify to controllers as well.
}
@@ -675,7 +716,7 @@
return mSessionActivity;
}
- private static class MyEventCallback extends EventCallback {
+ private static class MyEventCallback extends PlayerEventCallback {
private final WeakReference<MediaSession2Impl> mSession;
private final MediaPlayerBase mPlayer;
@@ -684,6 +725,8 @@
mPlayer = player;
}
+ // TODO: Uncomment or remove
+ /*
@Override
public void onPlaybackStateChanged(PlaybackState2 state) {
MediaSession2Impl session = mSession.get();
@@ -697,7 +740,10 @@
}
session.notifyPlaybackStateChangedNotLocked(state);
}
+ */
+ // TODO: Uncomment or remove
+ /*
@Override
public void onError(String mediaId, int what, int extra) {
MediaSession2Impl session = mSession.get();
@@ -712,6 +758,9 @@
}
session.notifyErrorNotLocked(mediaId, what, extra);
}
+ */
+
+ //TODO implement the real PlayerEventCallback methods
}
public static final class CommandImpl implements CommandProvider {
@@ -719,30 +768,30 @@
= "android.media.media_session2.command.command_code";
private static final String KEY_COMMAND_CUSTOM_COMMAND
= "android.media.media_session2.command.custom_command";
- private static final String KEY_COMMAND_EXTRA
- = "android.media.media_session2.command.extra";
+ private static final String KEY_COMMAND_EXTRAS
+ = "android.media.media_session2.command.extras";
private final Command mInstance;
private final int mCommandCode;
// Nonnull if it's custom command
private final String mCustomCommand;
- private final Bundle mExtra;
+ private final Bundle mExtras;
public CommandImpl(Command instance, int commandCode) {
mInstance = instance;
mCommandCode = commandCode;
mCustomCommand = null;
- mExtra = null;
+ mExtras = null;
}
- public CommandImpl(Command instance, @NonNull String action, @Nullable Bundle extra) {
+ public CommandImpl(Command instance, @NonNull String action, @Nullable Bundle extras) {
if (action == null) {
throw new IllegalArgumentException("action shouldn't be null");
}
mInstance = instance;
mCommandCode = COMMAND_CODE_CUSTOM;
mCustomCommand = action;
- mExtra = extra;
+ mExtras = extras;
}
public int getCommandCode_impl() {
@@ -753,8 +802,8 @@
return mCustomCommand;
}
- public @Nullable Bundle getExtra_impl() {
- return mExtra;
+ public @Nullable Bundle getExtras_impl() {
+ return mExtras;
}
/**
@@ -764,7 +813,7 @@
Bundle bundle = new Bundle();
bundle.putInt(KEY_COMMAND_CODE, mCommandCode);
bundle.putString(KEY_COMMAND_CUSTOM_COMMAND, mCustomCommand);
- bundle.putBundle(KEY_COMMAND_EXTRA, mExtra);
+ bundle.putBundle(KEY_COMMAND_EXTRAS, mExtras);
return bundle;
}
@@ -780,7 +829,7 @@
if (customCommand == null) {
return null;
}
- return new Command(context, customCommand, command.getBundle(KEY_COMMAND_EXTRA));
+ return new Command(context, customCommand, command.getBundle(KEY_COMMAND_EXTRAS));
}
}
@@ -1073,8 +1122,8 @@
= "android.media.media_session2.command_button.icon_res_id";
private static final String KEY_DISPLAY_NAME
= "android.media.media_session2.command_button.display_name";
- private static final String KEY_EXTRA
- = "android.media.media_session2.command_button.extra";
+ private static final String KEY_EXTRAS
+ = "android.media.media_session2.command_button.extras";
private static final String KEY_ENABLED
= "android.media.media_session2.command_button.enabled";
@@ -1082,15 +1131,15 @@
private Command mCommand;
private int mIconResId;
private String mDisplayName;
- private Bundle mExtra;
+ private Bundle mExtras;
private boolean mEnabled;
public CommandButtonImpl(Context context, @Nullable Command command, int iconResId,
- @Nullable String displayName, Bundle extra, boolean enabled) {
+ @Nullable String displayName, Bundle extras, boolean enabled) {
mCommand = command;
mIconResId = iconResId;
mDisplayName = displayName;
- mExtra = extra;
+ mExtras = extras;
mEnabled = enabled;
mInstance = new CommandButton(this);
}
@@ -1111,8 +1160,8 @@
}
@Override
- public @Nullable Bundle getExtra_impl() {
- return mExtra;
+ public @Nullable Bundle getExtras_impl() {
+ return mExtras;
}
@Override
@@ -1125,7 +1174,7 @@
bundle.putBundle(KEY_COMMAND, mCommand.toBundle());
bundle.putInt(KEY_ICON_RES_ID, mIconResId);
bundle.putString(KEY_DISPLAY_NAME, mDisplayName);
- bundle.putBundle(KEY_EXTRA, mExtra);
+ bundle.putBundle(KEY_EXTRAS, mExtras);
bundle.putBoolean(KEY_ENABLED, mEnabled);
return bundle;
}
@@ -1138,7 +1187,7 @@
builder.setCommand(Command.fromBundle(context, bundle.getBundle(KEY_COMMAND)));
builder.setIconResId(bundle.getInt(KEY_ICON_RES_ID, 0));
builder.setDisplayName(bundle.getString(KEY_DISPLAY_NAME));
- builder.setExtra(bundle.getBundle(KEY_EXTRA));
+ builder.setExtras(bundle.getBundle(KEY_EXTRAS));
builder.setEnabled(bundle.getBoolean(KEY_ENABLED));
try {
return builder.build();
@@ -1157,7 +1206,7 @@
private Command mCommand;
private int mIconResId;
private String mDisplayName;
- private Bundle mExtra;
+ private Bundle mExtras;
private boolean mEnabled;
public BuilderImpl(Context context, CommandButton.Builder instance) {
@@ -1191,8 +1240,8 @@
}
@Override
- public CommandButton.Builder setExtra_impl(Bundle extra) {
- mExtra = extra;
+ public CommandButton.Builder setExtras_impl(Bundle extras) {
+ mExtras = extras;
return mInstance;
}
@@ -1208,7 +1257,7 @@
+ " and name to display");
}
return new CommandButtonImpl(
- mContext, mCommand, mIconResId, mDisplayName, mExtra, mEnabled).mInstance;
+ mContext, mCommand, mIconResId, mDisplayName, mExtras, mEnabled).mInstance;
}
}
}
@@ -1216,7 +1265,7 @@
public static abstract class BuilderBaseImpl<T extends MediaSession2, C extends SessionCallback>
implements BuilderBaseProvider<T, C> {
final Context mContext;
- final MediaPlayerBase mPlayer;
+ MediaPlayerBase mPlayer;
String mId;
Executor mCallbackExecutor;
C mCallback;
@@ -1232,17 +1281,23 @@
* {@link MediaSession2} or {@link MediaController2}.
*/
// TODO(jaewan): Also need executor
- public BuilderBaseImpl(Context context, MediaPlayerBase player) {
+ public BuilderBaseImpl(Context context) {
if (context == null) {
throw new IllegalArgumentException("context shouldn't be null");
}
+ mContext = context;
+ // Ensure non-null
+ mId = "";
+ }
+
+ public void setPlayer_impl(MediaPlayerBase player, MediaPlaylistController mplc,
+ VolumeProvider2 volumeProvider) {
+ // TODO: Use MediaPlaylistController
if (player == null) {
throw new IllegalArgumentException("player shouldn't be null");
}
- mContext = context;
mPlayer = player;
- // Ensure non-null
- mId = "";
+ mVolumeProvider = volumeProvider;
}
public void setVolumeProvider_impl(VolumeProvider2 volumeProvider) {
@@ -1275,8 +1330,8 @@
}
public static class BuilderImpl extends BuilderBaseImpl<MediaSession2, SessionCallback> {
- public BuilderImpl(Context context, Builder instance, MediaPlayerBase player) {
- super(context, player);
+ public BuilderImpl(Context context, Builder instance) {
+ super(context);
}
@Override
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index 7fb6f0f..b1e71e2 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -356,7 +356,7 @@
case MediaSession2.COMMAND_CODE_PLAYBACK_SEEK_TO:
session.getInstance().seekTo(args.getLong(ARGUMENT_KEY_POSITION));
break;
- case MediaSession2.COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM:
+ case MediaSession2.COMMAND_CODE_PLAYBACK_SKIP_TO_PLAYLIST_ITEM:
// TODO(jaewan): Implement
/*
session.getInstance().skipToPlaylistItem(
diff --git a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
index e640f4c..5bf67d2 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSessionService2Impl.java
@@ -22,7 +22,8 @@
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
-import android.media.MediaPlayerBase.EventCallback;
+import android.media.MediaPlayerBase;
+import android.media.MediaPlayerBase.PlayerEventCallback;
import android.media.MediaSession2;
import android.media.MediaSessionService2;
import android.media.MediaSessionService2.MediaNotification;
@@ -42,7 +43,7 @@
private static final boolean DEBUG = true; // TODO(jaewan): Change this.
private final MediaSessionService2 mInstance;
- private final EventCallback mCallback = new SessionServiceEventCallback();
+ private final PlayerEventCallback mCallback = new SessionServiceEventCallback();
private final Object mLock = new Object();
@GuardedBy("mLock")
@@ -135,16 +136,21 @@
mediaNotification.getNotification());
}
- private class SessionServiceEventCallback extends EventCallback {
+ private class SessionServiceEventCallback extends PlayerEventCallback {
@Override
- public void onPlaybackStateChanged(PlaybackState2 state) {
- if (state == null) {
- Log.w(TAG, "Ignoring null playback state");
- return;
- }
- MediaSession2Impl impl = (MediaSession2Impl) mSession.getProvider();
- updateNotification(impl.getInstance().getPlaybackState());
+ public void onPlayerStateChanged(MediaPlayerBase mpb, int state) {
+ // TODO: Implement this
+ return;
}
+ // TODO: Uncomment or remove
+ //public void onPlaybackStateChanged(PlaybackState2 state) {
+ // if (state == null) {
+ // Log.w(TAG, "Ignoring null playback state");
+ // return;
+ // }
+ // MediaSession2Impl impl = (MediaSession2Impl) mSession.getProvider();
+ // updateNotification(impl.getInstance().getPlaybackState());
+ //}
}
public static class MediaNotificationImpl implements MediaNotificationProvider {
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
index 577173d..b0d435d 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiFactory.java
@@ -162,8 +162,8 @@
}
public BuilderBaseProvider<MediaSession2, SessionCallback> createMediaSession2Builder(
- Context context, MediaSession2.Builder instance, MediaPlayerBase player) {
- return new MediaSession2Impl.BuilderImpl(context, instance, player);
+ Context context, MediaSession2.Builder instance) {
+ return new MediaSession2Impl.BuilderImpl(context, instance);
}
@Override
@@ -188,9 +188,9 @@
@Override
public BuilderBaseProvider<MediaLibrarySession, MediaLibrarySessionCallback>
createMediaLibraryService2Builder(MediaLibraryService2 service,
- MediaLibrarySession.Builder instance, MediaPlayerBase player,
- Executor callbackExecutor, MediaLibrarySessionCallback callback) {
- return new MediaLibraryService2Impl.BuilderImpl(service, instance, player, callbackExecutor,
+ MediaLibrarySession.Builder instance, Executor callbackExecutor,
+ MediaLibrarySessionCallback callback) {
+ return new MediaLibraryService2Impl.BuilderImpl(service, instance, callbackExecutor,
callback);
}
diff --git a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
index f440ad6..c4787d1 100644
--- a/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/MediaControlView2Impl.java
@@ -20,6 +20,7 @@
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.PlaybackState;
+import android.media.SessionToken2;
import android.media.update.MediaControlView2Provider;
import android.media.update.ViewGroupProvider;
import android.os.Bundle;
@@ -150,6 +151,16 @@
}
@Override
+ public void setMediaSessionToken_impl(SessionToken2 token) {
+ // TODO: implement this
+ }
+
+ @Override
+ public void setOnFullScreenListener_impl(MediaControlView2.OnFullScreenListener l) {
+ // TODO: implement this
+ }
+
+ @Override
public void setController_impl(MediaController controller) {
mController = controller;
if (controller != null) {
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index 928b2a5..cdb1470 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -22,11 +22,13 @@
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
+import android.media.DataSourceDesc;
import android.media.MediaMetadata;
import android.media.MediaPlayer;
import android.media.MediaPlayerBase;
import android.media.Cea708CaptionRenderer;
import android.media.ClosedCaptionRenderer;
+import android.media.MediaItem2;
import android.media.MediaMetadata2;
import android.media.Metadata;
import android.media.PlaybackParams;
@@ -39,6 +41,7 @@
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
+import android.media.SessionToken2;
import android.media.update.VideoView2Provider;
import android.media.update.ViewGroupProvider;
import android.net.Uri;
@@ -234,6 +237,12 @@
}
@Override
+ public SessionToken2 getMediaSessionToken_impl() {
+ // TODO: implement this
+ return null;
+ }
+
+ @Override
public MediaControlView2 getMediaControlView2_impl() {
return mMediaControlView;
}
@@ -352,6 +361,16 @@
}
@Override
+ public void setMediaItem_impl(MediaItem2 mediaItem) {
+ // TODO: implement this
+ }
+
+ @Override
+ public void setDataSource_impl(DataSourceDesc dsd) {
+ // TODO: implement this
+ }
+
+ @Override
public void setViewType_impl(int viewType) {
if (viewType == mCurrentView.getViewType()) {
return;
diff --git a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
index 1abb9b4..27822e6 100644
--- a/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaBrowser2Test.java
@@ -41,6 +41,7 @@
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -227,6 +228,7 @@
assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
}
+ @Ignore
@Test
public void testSearch() throws InterruptedException {
final String query = MockMediaLibraryService2.SEARCH_QUERY;
@@ -352,6 +354,7 @@
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
+ @Ignore
@Test
public void testUnsubscribe() throws InterruptedException {
final String testParentId = "testUnsubscribeId";
diff --git a/packages/MediaComponents/test/src/android/media/MediaController2Test.java b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
index 69769bf..0efb84a 100644
--- a/packages/MediaComponents/test/src/android/media/MediaController2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
@@ -19,7 +19,7 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.media.MediaPlayerBase.EventCallback;
+import android.media.MediaPlayerBase.PlayerEventCallback;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
@@ -76,7 +76,8 @@
mIntent = PendingIntent.getActivity(mContext, 0, sessionActivity, 0);
mPlayer = new MockPlayer(1);
- mSession = new MediaSession2.Builder(mContext, mPlayer)
+ mSession = new MediaSession2.Builder(mContext)
+ .setPlayer(mPlayer)
.setSessionCallback(sHandlerExecutor, new SessionCallback(mContext) {})
.setSessionActivity(mIntent)
.setId(TAG).build();
@@ -116,6 +117,7 @@
assertTrue(mPlayer.mPauseCalled);
}
+ @Ignore
@Test
public void testSkipToPrevious() throws InterruptedException {
mController.skipToPrevious();
@@ -138,6 +140,7 @@
assertTrue(mPlayer.mSkipToNextCalled);
}
+ @Ignore
@Test
public void testStop() throws InterruptedException {
mController.stop();
@@ -160,6 +163,7 @@
assertTrue(mPlayer.mPrepareCalled);
}
+ @Ignore
@Test
public void testFastForward() throws InterruptedException {
mController.fastForward();
@@ -171,6 +175,7 @@
assertTrue(mPlayer.mFastForwardCalled);
}
+ @Ignore
@Test
public void testRewind() throws InterruptedException {
mController.rewind();
@@ -219,6 +224,7 @@
assertEquals(Process.myUid(), sessionActivity.getCreatorUid());
}
+ @Ignore
@Test
public void testGetSetPlaylistParams() throws Exception {
final PlaylistParams params = new PlaylistParams(mContext,
@@ -251,7 +257,7 @@
TestVolumeProvider volumeProvider =
new TestVolumeProvider(mContext, volumeControlType, maxVolume, currentVolume);
- mSession.setPlayer(new MockPlayer(0), volumeProvider);
+ mSession.updatePlayer(new MockPlayer(0), null, volumeProvider);
final MediaController2 controller = createController(mSession.getToken(), true, null);
final int targetVolume = 50;
@@ -269,7 +275,7 @@
TestVolumeProvider volumeProvider =
new TestVolumeProvider(mContext, volumeControlType, maxVolume, currentVolume);
- mSession.setPlayer(new MockPlayer(0), volumeProvider);
+ mSession.updatePlayer(new MockPlayer(0), null, volumeProvider);
final MediaController2 controller = createController(mSession.getToken(), true, null);
final int direction = AudioManager.ADJUST_RAISE;
@@ -285,6 +291,7 @@
}
// This also tests getPlaybackState().
+ @Ignore
@Test
public void testControllerCallback_onPlaybackStateChanged() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
@@ -326,7 +333,7 @@
}
};
mSession.close();
- mSession = new MediaSession2.Builder(mContext, mPlayer)
+ mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
.setSessionCallback(sHandlerExecutor, callback).setId(TAG).build();
final MediaController2 controller = createController(mSession.getToken());
controller.sendCustomCommand(testCommand, testArgs, null);
@@ -351,7 +358,7 @@
};
sHandler.postAndSync(() -> {
mSession.close();
- mSession = new MediaSession2.Builder(mContext, mPlayer)
+ mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
.setSessionCallback(sHandlerExecutor, sessionCallback).build();
});
MediaController2 controller =
@@ -390,7 +397,8 @@
latch.countDown();
}
};
- try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
+ try (MediaSession2 session = new MediaSession2.Builder(mContext)
+ .setPlayer(mPlayer)
.setSessionCallback(sHandlerExecutor, callback)
.setId("testPlayFromSearch").build()) {
MediaController2 controller = createController(session.getToken());
@@ -414,7 +422,8 @@
latch.countDown();
}
};
- try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
+ try (MediaSession2 session = new MediaSession2.Builder(mContext)
+ .setPlayer(mPlayer)
.setSessionCallback(sHandlerExecutor, callback)
.setId("testPlayFromUri").build()) {
MediaController2 controller = createController(session.getToken());
@@ -438,7 +447,8 @@
latch.countDown();
}
};
- try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
+ try (MediaSession2 session = new MediaSession2.Builder(mContext)
+ .setPlayer(mPlayer)
.setSessionCallback(sHandlerExecutor, callback)
.setId("testPlayFromMediaId").build()) {
MediaController2 controller = createController(session.getToken());
@@ -464,7 +474,8 @@
latch.countDown();
}
};
- try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
+ try (MediaSession2 session = new MediaSession2.Builder(mContext)
+ .setPlayer(mPlayer)
.setSessionCallback(sHandlerExecutor, callback)
.setId("testPrepareFromSearch").build()) {
MediaController2 controller = createController(session.getToken());
@@ -488,7 +499,8 @@
latch.countDown();
}
};
- try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
+ try (MediaSession2 session = new MediaSession2.Builder(mContext)
+ .setPlayer(mPlayer)
.setSessionCallback(sHandlerExecutor, callback)
.setId("testPrepareFromUri").build()) {
MediaController2 controller = createController(session.getToken());
@@ -512,7 +524,8 @@
latch.countDown();
}
};
- try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
+ try (MediaSession2 session = new MediaSession2.Builder(mContext)
+ .setPlayer(mPlayer)
.setSessionCallback(sHandlerExecutor, callback)
.setId("testPrepareFromMediaId").build()) {
MediaController2 controller = createController(session.getToken());
@@ -540,7 +553,8 @@
}
};
- try (MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
+ try (MediaSession2 session = new MediaSession2.Builder(mContext)
+ .setPlayer(mPlayer)
.setSessionCallback(sHandlerExecutor, callback)
.setId("testSetRating").build()) {
MediaController2 controller = createController(session.getToken());
@@ -582,7 +596,8 @@
try {
final MockPlayer player = new MockPlayer(0);
sessionHandler.postAndSync(() -> {
- mSession = new MediaSession2.Builder(mContext, mPlayer)
+ mSession = new MediaSession2.Builder(mContext)
+ .setPlayer(mPlayer)
.setSessionCallback(sHandlerExecutor, new SessionCallback(mContext) {})
.setId("testDeadlock").build();
});
@@ -649,6 +664,7 @@
testConnectToService(MockMediaSessionService2.ID);
}
+ @Ignore
@Test
public void testConnectToService_libraryService() throws InterruptedException {
testConnectToService(MockMediaLibraryService2.ID);
@@ -696,6 +712,7 @@
testControllerAfterSessionIsGone(mSession.getToken().getId());
}
+ @Ignore
@Test
public void testControllerAfterSessionIsGone_sessionService() throws InterruptedException {
connectToService(TestUtils.getServiceToken(mContext, MockMediaSessionService2.ID));
@@ -772,7 +789,8 @@
sHandler.postAndSync(() -> {
// Recreated session has different session stub, so previously created controller
// shouldn't be available.
- mSession = new MediaSession2.Builder(mContext, mPlayer)
+ mSession = new MediaSession2.Builder(mContext)
+ .setPlayer(mPlayer)
.setSessionCallback(sHandlerExecutor, new SessionCallback(mContext) {})
.setId(id).build();
});
@@ -780,14 +798,18 @@
}
private void testNoInteraction() throws InterruptedException {
+ // TODO: Uncomment
+ /*
final CountDownLatch latch = new CountDownLatch(1);
- final EventCallback callback = new EventCallback() {
+ final PlayerEventCallback callback = new PlayerEventCallback() {
@Override
public void onPlaybackStateChanged(PlaybackState2 state) {
fail("Controller shouldn't be notified about change in session after the close.");
latch.countDown();
}
};
+ */
+
// TODO(jaewan): Add equivalent tests again
/*
mController.registerPlayerEventCallback(playbackListener, sHandler);
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
index 441d38c..3c72e7d 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
@@ -29,7 +29,7 @@
import android.content.Context;
import android.media.MediaController2.PlaybackInfo;
-import android.media.MediaPlayerBase.EventCallback;
+import android.media.MediaPlayerBase.PlayerEventCallback;
import android.media.MediaSession2.Builder;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
@@ -48,6 +48,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -72,7 +73,7 @@
public void setUp() throws Exception {
super.setUp();
mPlayer = new MockPlayer(0);
- mSession = new MediaSession2.Builder(mContext, mPlayer)
+ mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
.setSessionCallback(sHandlerExecutor, new SessionCallback(mContext) {}).build();
}
@@ -83,15 +84,16 @@
mSession.close();
}
+ @Ignore
@Test
public void testBuilder() throws Exception {
try {
- MediaSession2.Builder builder = new Builder(mContext, null);
+ MediaSession2.Builder builder = new Builder(mContext);
fail("null player shouldn't be allowed");
} catch (IllegalArgumentException e) {
// expected. pass-through
}
- MediaSession2.Builder builder = new Builder(mContext, mPlayer);
+ MediaSession2.Builder builder = new Builder(mContext).setPlayer(mPlayer);
try {
builder.setId(null);
fail("null id shouldn't be allowed");
@@ -101,11 +103,11 @@
}
@Test
- public void testSetPlayer() throws Exception {
+ public void testUpdatePlayer() throws Exception {
MockPlayer player = new MockPlayer(0);
// Test if setPlayer doesn't crash with various situations.
- mSession.setPlayer(mPlayer);
- mSession.setPlayer(player);
+ mSession.updatePlayer(mPlayer, null, null);
+ mSession.updatePlayer(player, null, null);
mSession.close();
}
@@ -137,7 +139,7 @@
}
};
- mSession.setPlayer(player);
+ mSession.updatePlayer(player, null, null);
final MediaController2 controller = createController(mSession.getToken(), true, callback);
PlaybackInfo info = controller.getPlaybackInfo();
@@ -151,7 +153,7 @@
assertEquals(manager.getStreamMaxVolume(AudioManager.STREAM_MUSIC), info.getMaxVolume());
assertEquals(manager.getStreamVolume(AudioManager.STREAM_MUSIC), info.getCurrentVolume());
- mSession.setPlayer(player, volumeProvider);
+ mSession.updatePlayer(player, null, volumeProvider);
assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
info = controller.getPlaybackInfo();
@@ -179,6 +181,7 @@
});
}
+ @Ignore
@Test
public void testStop() throws Exception {
sHandler.postAndSync(() -> {
@@ -195,6 +198,7 @@
});
}
+ @Ignore
@Test
public void testSkipToPrevious() throws Exception {
sHandler.postAndSync(() -> {
@@ -203,6 +207,7 @@
});
}
+ @Ignore
@Test
public void testSetPlaylist() throws Exception {
final List<MediaItem2> playlist = new ArrayList<>();
@@ -227,6 +232,7 @@
assertMediaItemListEquals(playlist, controller.getPlaylist());
}
+ @Ignore
@Test
public void testSetPlaylistParams() throws Exception {
final PlaylistParams params = new PlaylistParams(mContext,
@@ -251,13 +257,16 @@
assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
}
+ @Ignore
@Test
public void testRegisterEventCallback() throws InterruptedException {
final int testWhat = 1001;
final MockPlayer player = new MockPlayer(0);
final CountDownLatch playbackLatch = new CountDownLatch(3);
final CountDownLatch errorLatch = new CountDownLatch(1);
- final EventCallback callback = new EventCallback() {
+ // TODO: Uncomment or remove
+ /*
+ final PlayerEventCallback callback = new PlayerEventCallback() {
@Override
public void onPlaybackStateChanged(PlaybackState2 state) {
assertEquals(sHandler.getLooper(), Looper.myLooper());
@@ -285,11 +294,13 @@
errorLatch.countDown();
}
};
+ */
player.notifyPlaybackState(createPlaybackState(PlaybackState2.STATE_PLAYING));
// EventCallback will be notified with the mPlayer's playback state (null)
- mSession.registerPlayerEventCallback(sHandlerExecutor, callback);
+ // TODO: Uncomment or remove
+ //mSession.registerPlayerEventCallback(sHandlerExecutor, callback);
// When the player is set, EventCallback will be notified about the new player's state.
- mSession.setPlayer(player);
+ mSession.updatePlayer(player, null, null);
// When the player is set, EventCallback will be notified about the new player's state.
player.notifyPlaybackState(createPlaybackState(PlaybackState2.STATE_PAUSED));
assertTrue(playbackLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
@@ -302,7 +313,9 @@
// TODO(jaewan): Add equivalent tests again
final CountDownLatch latch = new CountDownLatch(4); // expected call + 1
final BadPlayer player = new BadPlayer(0);
- mSession.registerPlayerEventCallback(sHandlerExecutor, new EventCallback() {
+ // TODO: Uncomment or remove
+ /*
+ mSession.registerPlayerEventCallback(sHandlerExecutor, new PlayerEventCallback() {
@Override
public void onPlaybackStateChanged(PlaybackState2 state) {
// This will be called for every setPlayer() calls, but no more.
@@ -310,8 +323,9 @@
latch.countDown();
}
});
- mSession.setPlayer(player);
- mSession.setPlayer(mPlayer);
+ */
+ mSession.updatePlayer(player, null, null);
+ mSession.updatePlayer(mPlayer, null, null);
player.notifyPlaybackState(createPlaybackState(PlaybackState2.STATE_PAUSED));
assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
}
@@ -322,7 +336,7 @@
}
@Override
- public void unregisterEventCallback(@NonNull EventCallback listener) {
+ public void unregisterPlayerEventCallback(@NonNull PlayerEventCallback listener) {
// No-op. This bad player will keep push notification to the listener that is previously
// registered by session.setPlayer().
}
@@ -334,7 +348,7 @@
sHandler.postAndSync(() -> {
mSession.close();
mPlayer = new MockPlayer(1);
- mSession = new MediaSession2.Builder(mContext, mPlayer)
+ mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
.setSessionCallback(sHandlerExecutor, callback).build();
});
MediaController2 controller = createController(mSession.getToken());
@@ -358,7 +372,7 @@
final MockOnConnectCallback sessionCallback = new MockOnConnectCallback();
sHandler.postAndSync(() -> {
mSession.close();
- mSession = new MediaSession2.Builder(mContext, mPlayer)
+ mSession = new MediaSession2.Builder(mContext).setPlayer(mPlayer)
.setSessionCallback(sHandlerExecutor, sessionCallback).build();
});
MediaController2 controller =
@@ -385,7 +399,8 @@
}
};
- try (final MediaSession2 session = new MediaSession2.Builder(mContext, mPlayer)
+ try (final MediaSession2 session = new MediaSession2.Builder(mContext)
+ .setPlayer(mPlayer)
.setId("testSetCustomLayout")
.setSessionCallback(sHandlerExecutor, sessionCallback)
.build()) {
diff --git a/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
index 50599b6..4cdd140 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSessionManager_MediaSession2.java
@@ -26,6 +26,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -54,7 +55,8 @@
// Specify TAG here so {@link MediaSession2.getInstance()} doesn't complaint about
// per test thread differs across the {@link MediaSession2} with the same TAG.
final MockPlayer player = new MockPlayer(1);
- mSession = new MediaSession2.Builder(mContext, player)
+ mSession = new MediaSession2.Builder(mContext)
+ .setPlayer(player)
.setSessionCallback(sHandlerExecutor, new SessionCallback(mContext) { })
.setId(TAG)
.build();
@@ -70,6 +72,7 @@
}
// TODO(jaewan): Make this host-side test to see per-user behavior.
+ @Ignore
@Test
public void testGetMediaSession2Tokens_hasMediaController() throws InterruptedException {
final MockPlayer player = (MockPlayer) mSession.getPlayer();
@@ -105,8 +108,8 @@
public void testGetSessionTokens_sessionRejected() throws InterruptedException {
sHandler.postAndSync(() -> {
mSession.close();
- mSession = new MediaSession2.Builder(mContext, new MockPlayer(0)).setId(TAG)
- .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext) {
+ mSession = new MediaSession2.Builder(mContext).setPlayer(new MockPlayer(0))
+ .setId(TAG).setSessionCallback(sHandlerExecutor, new SessionCallback(mContext) {
@Override
public MediaSession2.CommandGroup onConnect(ControllerInfo controller) {
// Reject all connection request.
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
index b0fbde0..20ea3d2 100644
--- a/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
+++ b/packages/MediaComponents/test/src/android/media/MockMediaLibraryService2.java
@@ -114,8 +114,8 @@
}
TestLibrarySessionCallback callback =
new TestLibrarySessionCallback(sessionCallbackProxy);
- mSession = new MediaLibrarySession.Builder(MockMediaLibraryService2.this, player,
- executor, callback).setId(sessionId).build();
+ mSession = new MediaLibrarySession.Builder(MockMediaLibraryService2.this, executor,
+ callback).setPlayer(player).setId(sessionId).build();
return mSession;
}
diff --git a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
index b63104a..1c6534d 100644
--- a/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
+++ b/packages/MediaComponents/test/src/android/media/MockMediaSessionService2.java
@@ -64,7 +64,8 @@
}
TestSessionServiceCallback callback =
new TestSessionServiceCallback(sessionCallbackProxy);
- mSession = new MediaSession2.Builder(this, player)
+ mSession = new MediaSession2.Builder(this)
+ .setPlayer(player)
.setSessionCallback(executor, callback)
.setId(sessionId).build();
return mSession;
diff --git a/packages/MediaComponents/test/src/android/media/MockPlayer.java b/packages/MediaComponents/test/src/android/media/MockPlayer.java
index 1b3da73..da4acb5 100644
--- a/packages/MediaComponents/test/src/android/media/MockPlayer.java
+++ b/packages/MediaComponents/test/src/android/media/MockPlayer.java
@@ -46,7 +46,7 @@
public boolean mSetPlaylistCalled;
public boolean mSetPlaylistParamsCalled;
- public ArrayMap<EventCallback, Executor> mCallbacks = new ArrayMap<>();
+ public ArrayMap<PlayerEventCallback, Executor> mCallbacks = new ArrayMap<>();
public List<MediaItem2> mPlaylist;
public PlaylistParams mPlaylistParams;
@@ -78,6 +78,8 @@
}
}
+ // TODO: Uncomment or remove
+ /*
@Override
public void stop() {
mStopCalled = true;
@@ -85,7 +87,10 @@
mCountDownLatch.countDown();
}
}
+ */
+ // TODO: Uncomment or remove
+ /*
@Override
public void skipToPrevious() {
mSkipToPreviousCalled = true;
@@ -93,6 +98,7 @@
mCountDownLatch.countDown();
}
}
+ */
@Override
public void skipToNext() {
@@ -110,6 +116,8 @@
}
}
+ // TODO: Uncomment or remove
+ /*
@Override
public void fastForward() {
mFastForwardCalled = true;
@@ -117,7 +125,10 @@
mCountDownLatch.countDown();
}
}
+ */
+ // TODO: Uncomment or remove
+ /*
@Override
public void rewind() {
mRewindCalled = true;
@@ -125,6 +136,7 @@
mCountDownLatch.countDown();
}
}
+ */
@Override
public void seekTo(long pos) {
@@ -135,6 +147,8 @@
}
}
+ // TODO: Uncomment or remove
+ /*
@Override
public void setCurrentPlaylistItem(MediaItem2 item) {
mSetCurrentPlaylistItemCalled = true;
@@ -143,12 +157,16 @@
mCountDownLatch.countDown();
}
}
+ */
+ // TODO: Uncomment or remove
+ /*
@Nullable
@Override
public PlaybackState2 getPlaybackState() {
return mLastPlaybackState;
}
+ */
@Override
public int getPlayerState() {
@@ -156,51 +174,71 @@
}
@Override
- public void registerEventCallback(@NonNull Executor executor,
- @NonNull EventCallback callback) {
+ public int getBufferingState() {
+ // TODO: implement this
+ return -1;
+ }
+
+ @Override
+ public void registerPlayerEventCallback(@NonNull Executor executor,
+ @NonNull PlayerEventCallback callback) {
mCallbacks.put(callback, executor);
}
@Override
- public void unregisterEventCallback(@NonNull EventCallback callback) {
+ public void unregisterPlayerEventCallback(@NonNull PlayerEventCallback callback) {
mCallbacks.remove(callback);
}
public void notifyPlaybackState(final PlaybackState2 state) {
mLastPlaybackState = state;
for (int i = 0; i < mCallbacks.size(); i++) {
- final EventCallback callback = mCallbacks.keyAt(i);
+ final PlayerEventCallback callback = mCallbacks.keyAt(i);
final Executor executor = mCallbacks.valueAt(i);
- executor.execute(() -> callback.onPlaybackStateChanged(state));
+ // TODO: Uncomment or remove
+ //executor.execute(() -> callback.onPlaybackStateChanged(state));
}
}
public void notifyError(int what) {
for (int i = 0; i < mCallbacks.size(); i++) {
- final EventCallback callback = mCallbacks.keyAt(i);
+ final PlayerEventCallback callback = mCallbacks.keyAt(i);
final Executor executor = mCallbacks.valueAt(i);
- executor.execute(() -> callback.onError(null, what, 0));
+ // TODO: Uncomment or remove
+ //executor.execute(() -> callback.onError(null, what, 0));
}
}
+ // TODO: Uncomment or remove
+ /*
@Override
public void setPlaylistParams(PlaylistParams params) {
mSetPlaylistParamsCalled = true;
mPlaylistParams = params;
}
+ */
+ // TODO: Uncomment or remove
+ /*
@Override
public void addPlaylistItem(int index, MediaItem2 item) {
}
+ */
+ // TODO: Uncomment or remove
+ /*
@Override
public void removePlaylistItem(MediaItem2 item) {
}
+ */
+ // TODO: Uncomment or remove
+ /*
@Override
public PlaylistParams getPlaylistParams() {
return mPlaylistParams;
}
+ */
@Override
public void setAudioAttributes(AudioAttributes attributes) {
@@ -212,14 +250,62 @@
return mAudioAttributes;
}
+ // TODO: Uncomment or remove
+ /*
@Override
public void setPlaylist(List<MediaItem2> playlist) {
mSetPlaylistCalled = true;
mPlaylist = playlist;
}
+ */
+ // TODO: Uncomment or remove
+ /*
@Override
public List<MediaItem2> getPlaylist() {
return mPlaylist;
}
+ */
+
+ @Override
+ public void setDataSource(@NonNull DataSourceDesc dsd) {
+ // TODO: Implement this
+ }
+
+ @Override
+ public void setNextDataSource(@NonNull DataSourceDesc dsd) {
+ // TODO: Implement this
+ }
+
+ @Override
+ public void setNextDataSources(@NonNull List<DataSourceDesc> dsds) {
+ // TODO: Implement this
+ }
+
+ @Override
+ public DataSourceDesc getCurrentDataSource() {
+ // TODO: Implement this
+ return null;
+ }
+
+ @Override
+ public void loopCurrent(boolean loop) {
+ // TODO: implement this
+ }
+
+ @Override
+ public void setPlaybackSpeed(float speed) {
+ // TODO: implement this
+ }
+
+ @Override
+ public void setPlayerVolume(float volume) {
+ // TODO: implement this
+ }
+
+ @Override
+ public float getPlayerVolume() {
+ // TODO: implement this
+ return -1;
+ }
}
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 229e08e..8033382 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -3051,6 +3051,7 @@
// check recording permission for visualizer
if ((memcmp(&desc.type, SL_IID_VISUALIZATION, sizeof(effect_uuid_t)) == 0) &&
+ // TODO: Do we need to start/stop op - i.e. is there recording being performed?
!recordingAllowed(opPackageName, pid, IPCThreadState::self()->getCallingUid())) {
lStatus = PERMISSION_DENIED;
goto Exit;
diff --git a/services/audioflinger/ServiceUtilities.cpp b/services/audioflinger/ServiceUtilities.cpp
index f08698e..84b43ce 100644
--- a/services/audioflinger/ServiceUtilities.cpp
+++ b/services/audioflinger/ServiceUtilities.cpp
@@ -30,6 +30,8 @@
namespace android {
+static const String16 sAndroidPermissionRecordAudio("android.permission.RECORD_AUDIO");
+
// Not valid until initialized by AudioFlinger constructor. It would have to be
// re-initialized if the process containing AudioFlinger service forks (which it doesn't).
// This is often used to validate binder interface calls within audioserver
@@ -49,25 +51,72 @@
}
bool recordingAllowed(const String16& opPackageName, pid_t pid, uid_t uid) {
- // we're always OK.
- if (getpid_cached == IPCThreadState::self()->getCallingPid()) return true;
+ return checkRecordingInternal(opPackageName, pid, uid, false);
+}
- static const String16 sRecordAudio("android.permission.RECORD_AUDIO");
+bool startRecording(const String16& opPackageName, pid_t pid, uid_t uid) {
+ return checkRecordingInternal(opPackageName, pid, uid, true);
+}
- // We specify a pid and uid here as mediaserver (aka MediaRecorder or StageFrightRecorder)
- // may open a record track on behalf of a client. Note that pid may be a tid.
- // IMPORTANT: Don't use PermissionCache - a runtime permission and may change.
- const bool ok = checkPermission(sRecordAudio, pid, uid);
- if (!ok) {
- ALOGE("Request requires android.permission.RECORD_AUDIO");
- return false;
+bool checkRecordingInternal(const String16& opPackageName, pid_t pid, uid_t uid, bool start) {
+ // we're always OK.
+ if (getpid_cached == IPCThreadState::self()->getCallingPid()) return true;
+
+ // To permit command-line native tests
+ if (uid == AID_ROOT) return true;
+
+ // We specify a pid and uid here as mediaserver (aka MediaRecorder or StageFrightRecorder)
+ // may open a record track on behalf of a client. Note that pid may be a tid.
+ // IMPORTANT: DON'T USE PermissionCache - RUNTIME PERMISSIONS CHANGE.
+ PermissionController permissionController;
+ const bool ok = permissionController.checkPermission(sAndroidPermissionRecordAudio, pid, uid);
+ if (!ok) {
+ ALOGE("Request requires %s", String8(sAndroidPermissionRecordAudio).c_str());
+ return false;
+ }
+
+ const String16 resolvedOpPackageName = resolveCallingPackage(
+ permissionController, opPackageName, uid);
+ if (opPackageName.size() <= 0) {
+ return false;
+ }
+
+ AppOpsManager appOps;
+ const int32_t op = appOps.permissionToOpCode(sAndroidPermissionRecordAudio);
+ if (start) {
+ if (appOps.startOpNoThrow(op, uid, resolvedOpPackageName, /*startIfModeDefault*/ false)
+ != AppOpsManager::MODE_ALLOWED) {
+ ALOGE("Request denied by app op: %d", op);
+ return false;
+ }
+ } else {
+ if (appOps.noteOp(op, uid, resolvedOpPackageName) != AppOpsManager::MODE_ALLOWED) {
+ ALOGE("Request denied by app op: %d", op);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void finishRecording(const String16& opPackageName, uid_t uid) {
+ PermissionController permissionController;
+ const String16 resolvedOpPackageName = resolveCallingPackage(
+ permissionController, opPackageName, uid);
+ if (opPackageName.size() <= 0) {
+ return;
}
- // To permit command-line native tests
- if (uid == AID_ROOT) return true;
+ AppOpsManager appOps;
+ const int32_t op = appOps.permissionToOpCode(sAndroidPermissionRecordAudio);
+ appOps.finishOp(op, uid, resolvedOpPackageName);
+}
- String16 checkedOpPackageName = opPackageName;
-
+const String16 resolveCallingPackage(PermissionController& permissionController,
+ const String16& opPackageName, uid_t uid) {
+ if (opPackageName.size() > 0) {
+ return opPackageName;
+ }
// In some cases the calling code has no access to the package it runs under.
// For example, code using the wilhelm framework's OpenSL-ES APIs. In this
// case we will get the packages for the calling UID and pick the first one
@@ -75,40 +124,19 @@
// as for legacy apps we will toggle the app op for all packages in the UID.
// The caveat is that the operation may be attributed to the wrong package and
// stats based on app ops may be slightly off.
- if (checkedOpPackageName.size() <= 0) {
- sp<IServiceManager> sm = defaultServiceManager();
- sp<IBinder> binder = sm->getService(String16("permission"));
- if (binder == 0) {
- ALOGE("Cannot get permission service");
- return false;
- }
-
- sp<IPermissionController> permCtrl = interface_cast<IPermissionController>(binder);
- Vector<String16> packages;
-
- permCtrl->getPackagesForUid(uid, packages);
-
- if (packages.isEmpty()) {
- ALOGE("No packages for calling UID");
- return false;
- }
- checkedOpPackageName = packages[0];
+ Vector<String16> packages;
+ permissionController.getPackagesForUid(uid, packages);
+ if (packages.isEmpty()) {
+ ALOGE("No packages for uid %d", uid);
+ return opPackageName;
}
-
- AppOpsManager appOps;
- if (appOps.noteOp(AppOpsManager::OP_RECORD_AUDIO, uid, checkedOpPackageName)
- != AppOpsManager::MODE_ALLOWED) {
- ALOGE("Request denied by app op OP_RECORD_AUDIO");
- return false;
- }
-
- return true;
+ return packages[0];
}
bool captureAudioOutputAllowed(pid_t pid, uid_t uid) {
if (getpid_cached == IPCThreadState::self()->getCallingPid()) return true;
static const String16 sCaptureAudioOutput("android.permission.CAPTURE_AUDIO_OUTPUT");
- bool ok = checkPermission(sCaptureAudioOutput, pid, uid);
+ bool ok = PermissionCache::checkPermission(sCaptureAudioOutput, pid, uid);
if (!ok) ALOGE("Request requires android.permission.CAPTURE_AUDIO_OUTPUT");
return ok;
}
@@ -155,7 +183,7 @@
bool modifyPhoneStateAllowed(pid_t pid, uid_t uid) {
static const String16 sModifyPhoneState("android.permission.MODIFY_PHONE_STATE");
- bool ok = checkPermission(sModifyPhoneState, pid, uid);
+ bool ok = PermissionCache::checkPermission(sModifyPhoneState, pid, uid);
if (!ok) ALOGE("Request requires android.permission.MODIFY_PHONE_STATE");
return ok;
}
diff --git a/services/audioflinger/ServiceUtilities.h b/services/audioflinger/ServiceUtilities.h
index 83533dd..8f96282 100644
--- a/services/audioflinger/ServiceUtilities.h
+++ b/services/audioflinger/ServiceUtilities.h
@@ -16,11 +16,19 @@
#include <unistd.h>
+#include <binder/PermissionController.h>
+
namespace android {
extern pid_t getpid_cached;
bool isTrustedCallingUid(uid_t uid);
bool recordingAllowed(const String16& opPackageName, pid_t pid, uid_t uid);
+bool startRecording(const String16& opPackageName, pid_t pid, uid_t uid);
+void finishRecording(const String16& opPackageName, uid_t uid);
+// DON'T USE THIS INTERNAL METHOD
+bool checkRecordingInternal(const String16& opPackageName, pid_t pid, uid_t uid, bool start);
+const String16 resolveCallingPackage(PermissionController& permissionController,
+ const String16& opPackageName, uid_t uid);
bool captureAudioOutputAllowed(pid_t pid, uid_t uid);
bool captureHotwordAllowed(pid_t pid, uid_t uid);
bool settingsAllowed();
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 3134323..1301998 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -1837,10 +1837,6 @@
void AudioFlinger::PlaybackThread::preExit()
{
ALOGV(" preExit()");
- // FIXME this is using hard-coded strings but in the future, this functionality will be
- // converted to use audio HAL extensions required to support tunneling
- status_t result = mOutput->stream->setParameters(String8("exiting=1"));
- ALOGE_IF(result != OK, "Error when setting parameters on exit: %d", result);
}
// PlaybackThread::createTrack_l() must be called with AudioFlinger::mLock held
diff --git a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
index 306de3f..d04b21e 100644
--- a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
+++ b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
@@ -412,7 +412,7 @@
}
// check calling permissions
- if (!recordingAllowed(client->opPackageName, client->pid, client->uid)) {
+ if (!startRecording(client->opPackageName, client->pid, client->uid)) {
ALOGE("%s permission denied: recording not allowed for uid %d pid %d",
__func__, client->uid, client->pid);
return PERMISSION_DENIED;
@@ -457,6 +457,9 @@
}
sp<AudioRecordClient> client = mAudioRecordClients.valueAt(index);
+ // finish the recording app op
+ finishRecording(client->opPackageName, client->uid);
+
return mAudioPolicyManager->stopInput(client->input, client->session);
}
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index dcc7ad2..ad63899 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -2218,8 +2218,8 @@
mAppOpsManager.startWatchingMode(AppOpsManager::OP_CAMERA,
mClientPackageName, mOpsCallback);
- res = mAppOpsManager.startOp(AppOpsManager::OP_CAMERA,
- mClientUid, mClientPackageName);
+ res = mAppOpsManager.startOpNoThrow(AppOpsManager::OP_CAMERA,
+ mClientUid, mClientPackageName, /*startIfModeDefault*/ false);
if (res == AppOpsManager::MODE_ERRORED) {
ALOGI("Camera %s: Access for \"%s\" has been revoked",
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
index efe3ca1..9ab2d88 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
@@ -1083,9 +1083,10 @@
}
// FIXME: remove this override since the default format should be
- // IMPLEMENTATION_DEFINED. b/9487482
- if (format >= HAL_PIXEL_FORMAT_RGBA_8888 &&
- format <= HAL_PIXEL_FORMAT_BGRA_8888) {
+ // IMPLEMENTATION_DEFINED. b/9487482 & b/35317944
+ if ((format >= HAL_PIXEL_FORMAT_RGBA_8888 && format <= HAL_PIXEL_FORMAT_BGRA_8888) &&
+ ((consumerUsage & GRALLOC_USAGE_HW_MASK) &&
+ ((consumerUsage & GRALLOC_USAGE_SW_READ_MASK) == 0))) {
ALOGW("%s: Camera %s: Overriding format %#x to IMPLEMENTATION_DEFINED",
__FUNCTION__, mCameraIdStr.string(), format);
format = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;