Merge "MPEG4Writer:stop & notify,MediaMuxer:remove notify" into rvc-dev
diff --git a/media/codec2/hidl/1.0/vts/functional/Android.bp b/media/codec2/hidl/1.0/vts/functional/Android.bp
index cd3be81..5ea4825 100644
--- a/media/codec2/hidl/1.0/vts/functional/Android.bp
+++ b/media/codec2/hidl/1.0/vts/functional/Android.bp
@@ -91,6 +91,14 @@
"res/bbb_av1_176_144.info",
"res/bbb_vp9_704x480_280kbps_24fps_altref_2.vp9",
"res/bbb_vp9_704x480_280kbps_24fps_altref_2.info",
+ "res/bbb_avc_176x144_300kbps_60fps_chksum.md5",
+ "res/bbb_avc_640x360_768kbps_30fps_chksum.md5",
+ "res/bbb_hevc_176x144_176kbps_60fps_chksum.md5",
+ "res/bbb_hevc_640x360_1600kbps_30fps_chksum.md5",
+ "res/bbb_vp8_640x360_2mbps_30fps_chksm.md5",
+ "res/bbb_vp9_640x360_1600kbps_30fps_chksm.md5",
+ "res/bbb_av1_640_360_chksum.md5",
+ "res/bbb_av1_176_144_chksm.md5",
],
}
diff --git a/media/codec2/hidl/1.0/vts/functional/audio/VtsHalMediaC2V1_0TargetAudioDecTest.cpp b/media/codec2/hidl/1.0/vts/functional/audio/VtsHalMediaC2V1_0TargetAudioDecTest.cpp
index ed2016f..3a47ae9 100644
--- a/media/codec2/hidl/1.0/vts/functional/audio/VtsHalMediaC2V1_0TargetAudioDecTest.cpp
+++ b/media/codec2/hidl/1.0/vts/functional/audio/VtsHalMediaC2V1_0TargetAudioDecTest.cpp
@@ -37,6 +37,8 @@
static std::vector<std::tuple<std::string, std::string, std::string, std::string>>
kDecodeTestParameters;
+static std::vector<std::tuple<std::string, std::string, std::string>> kCsdFlushTestParameters;
+
// Resource directory
static std::string sResourceDir = "";
@@ -831,6 +833,109 @@
ASSERT_EQ(mComponent->stop(), C2_OK);
}
+class Codec2AudioDecCsdInputTests
+ : public Codec2AudioDecHidlTestBase,
+ public ::testing::WithParamInterface<std::tuple<std::string, std::string, std::string>> {
+ void getParams() {
+ mInstanceName = std::get<0>(GetParam());
+ mComponentName = std::get<1>(GetParam());
+ }
+};
+
+// Test the codecs for the following
+// start - csd - data… - (with/without)flush - data… - flush - data…
+TEST_P(Codec2AudioDecCsdInputTests, CSDFlushTest) {
+ description("Tests codecs for flush at different states");
+ if (mDisableTest) GTEST_SKIP() << "Test is disabled";
+
+ char mURL[512], info[512];
+ android::Vector<FrameInfo> Info;
+
+ strcpy(mURL, sResourceDir.c_str());
+ strcpy(info, sResourceDir.c_str());
+ GetURLForComponent(mCompName, mURL, info);
+ if (!strcmp(mURL, sResourceDir.c_str())) {
+ ALOGV("EMPTY INPUT sResourceDir.c_str() %s mURL %s ", sResourceDir.c_str(), mURL);
+ return;
+ }
+ ALOGV("mURL : %s", mURL);
+
+ int32_t numCsds = populateInfoVector(info, &Info, mTimestampDevTest, &mTimestampUslist);
+ ASSERT_GE(numCsds, 0) << "Error in parsing input info file";
+
+ int32_t bitStreamInfo[2] = {0};
+ if (mCompName == raw) {
+ bitStreamInfo[0] = 8000;
+ bitStreamInfo[1] = 1;
+ } else {
+ ASSERT_NO_FATAL_FAILURE(getInputChannelInfo(mComponent, mCompName, bitStreamInfo));
+ }
+ if (!setupConfigParam(mComponent, bitStreamInfo)) {
+ std::cout << "[ WARN ] Test Skipped \n";
+ return;
+ }
+
+ ASSERT_EQ(mComponent->start(), C2_OK);
+ std::ifstream eleStream;
+ eleStream.open(mURL, std::ifstream::binary);
+ ASSERT_EQ(eleStream.is_open(), true);
+
+ bool signalEOS = false;
+ bool flushCsd = !std::get<2>(GetParam()).compare("true");
+ ALOGV("sending %d csd data ", numCsds);
+ int framesToDecode = numCsds;
+ ASSERT_NO_FATAL_FAILURE(decodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
+ mFlushedIndices, mLinearPool, eleStream, &Info, 0,
+ framesToDecode, false));
+
+ c2_status_t err = C2_OK;
+ std::list<std::unique_ptr<C2Work>> flushedWork;
+ if (numCsds && flushCsd) {
+ // We wait for all the CSD buffers to get consumed.
+ // Once we have received all CSD work back, we call flush
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
+
+ err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
+ ASSERT_EQ(err, C2_OK);
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
+ MAX_INPUT_BUFFERS - flushedWork.size());
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+ oBufferMetaData.clear();
+ }
+
+ int offset = framesToDecode;
+ while (1) {
+ framesToDecode = c2_min(FLUSH_INTERVAL, (int)Info.size() - offset);
+ if (framesToDecode < FLUSH_INTERVAL) signalEOS = true;
+ ASSERT_NO_FATAL_FAILURE(decodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
+ mFlushedIndices, mLinearPool, eleStream, &Info,
+ offset, framesToDecode, signalEOS));
+ offset += framesToDecode;
+ err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
+ ASSERT_EQ(err, C2_OK);
+ // blocking call to ensures application to Wait till remaining
+ // 'non-flushed' inputs are consumed
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
+ MAX_INPUT_BUFFERS - flushedWork.size());
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+ if (signalEOS || offset >= (int)Info.size()) {
+ break;
+ }
+ }
+ if (!signalEOS) {
+ ASSERT_NO_FATAL_FAILURE(testInputBuffer(mComponent, mQueueLock, mWorkQueue,
+ C2FrameData::FLAG_END_OF_STREAM, false));
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
+ }
+ eleStream.close();
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+ ASSERT_EQ(mComponent->stop(), C2_OK);
+}
+
INSTANTIATE_TEST_SUITE_P(PerInstance, Codec2AudioDecHidlTest, testing::ValuesIn(kTestParameters),
android::hardware::PrintInstanceTupleNameToString<>);
@@ -839,6 +944,10 @@
testing::ValuesIn(kDecodeTestParameters),
android::hardware::PrintInstanceTupleNameToString<>);
+INSTANTIATE_TEST_SUITE_P(CsdInputs, Codec2AudioDecCsdInputTests,
+ testing::ValuesIn(kCsdFlushTestParameters),
+ android::hardware::PrintInstanceTupleNameToString<>);
+
} // anonymous namespace
int main(int argc, char** argv) {
@@ -852,6 +961,11 @@
std::make_tuple(std::get<0>(params), std::get<1>(params), "1", "false"));
kDecodeTestParameters.push_back(
std::make_tuple(std::get<0>(params), std::get<1>(params), "1", "true"));
+
+ kCsdFlushTestParameters.push_back(
+ std::make_tuple(std::get<0>(params), std::get<1>(params), "true"));
+ kCsdFlushTestParameters.push_back(
+ std::make_tuple(std::get<0>(params), std::get<1>(params), "false"));
}
// Set the resource directory based on command line args.
diff --git a/media/codec2/hidl/1.0/vts/functional/audio/VtsHalMediaC2V1_0TargetAudioEncTest.cpp b/media/codec2/hidl/1.0/vts/functional/audio/VtsHalMediaC2V1_0TargetAudioEncTest.cpp
index 63f39de..e3a4f68 100644
--- a/media/codec2/hidl/1.0/vts/functional/audio/VtsHalMediaC2V1_0TargetAudioEncTest.cpp
+++ b/media/codec2/hidl/1.0/vts/functional/audio/VtsHalMediaC2V1_0TargetAudioEncTest.cpp
@@ -96,6 +96,7 @@
mCsd = false;
mFramesReceived = 0;
mWorkResult = C2_OK;
+ mOutputSize = 0u;
if (mCompName == unknown_comp) mDisableTest = true;
if (mDisableTest) std::cout << "[ WARN ] Test Disabled \n";
getInputMaxBufSize();
@@ -117,6 +118,16 @@
for (std::unique_ptr<C2Work>& work : workItems) {
if (!work->worklets.empty()) {
mWorkResult |= work->result;
+ if (!work->worklets.front()->output.buffers.empty()) {
+ mOutputSize += work->worklets.front()
+ ->output.buffers[0]
+ ->data()
+ .linearBlocks()
+ .front()
+ .map()
+ .get()
+ .capacity();
+ }
workDone(mComponent, work, mFlushedIndices, mQueueLock, mQueueCondition, mWorkQueue,
mEos, mCsd, mFramesReceived);
}
@@ -141,6 +152,7 @@
int32_t mWorkResult;
uint32_t mFramesReceived;
int32_t mInputMaxBufSize;
+ uint64_t mOutputSize;
std::list<uint64_t> mFlushedIndices;
C2BlockPool::local_id_t mBlockPoolId;
@@ -569,6 +581,210 @@
ASSERT_EQ(mComponent->stop(), C2_OK);
}
+TEST_P(Codec2AudioEncHidlTest, MultiChannelCountTest) {
+ description("Encodes input file for different channel count");
+ if (mDisableTest) GTEST_SKIP() << "Test is disabled";
+
+ char mURL[512];
+ strcpy(mURL, sResourceDir.c_str());
+ GetURLForComponent(mCompName, mURL);
+
+ std::ifstream eleStream;
+ eleStream.open(mURL, std::ifstream::binary);
+ ASSERT_EQ(eleStream.is_open(), true) << mURL << " file not found";
+ ALOGV("mURL : %s", mURL);
+
+ int32_t nSampleRate;
+ int32_t samplesPerFrame;
+ int32_t nChannels;
+ int32_t numFrames = 16;
+ int32_t maxChannelCount = 8;
+
+ if (!getConfigParams(mCompName, &nChannels, &nSampleRate, &samplesPerFrame)) {
+ std::cout << "Failed to get the config params for " << mCompName << " component\n";
+ std::cout << "[ WARN ] Test Skipped \n";
+ return;
+ }
+
+ uint64_t prevOutputSize = 0u;
+ uint32_t prevChannelCount = 0u;
+
+ // Looping through the maximum number of channel count supported by encoder
+ for (nChannels = 1; nChannels < maxChannelCount; nChannels++) {
+ ALOGV("Configuring %u encoder for channel count = %d", mCompName, nChannels);
+ if (!setupConfigParam(mComponent, nChannels, nSampleRate)) {
+ std::cout << "[ WARN ] Test Skipped \n";
+ return;
+ }
+
+ std::vector<std::unique_ptr<C2Param>> inParams;
+ c2_status_t c2_status = mComponent->query({}, {C2StreamChannelCountInfo::input::PARAM_TYPE},
+ C2_DONT_BLOCK, &inParams);
+ ASSERT_TRUE(!c2_status && inParams.size())
+ << "Query configured channelCount failed => %d" << c2_status;
+
+ size_t offset = sizeof(C2Param);
+ C2Param* param = inParams[0].get();
+ int32_t channelCount = *(int32_t*)((uint8_t*)param + offset);
+ if (channelCount != nChannels) {
+ std::cout << "[ WARN ] Test Skipped for ChannelCount " << nChannels << "\n";
+ continue;
+ }
+
+ // To check if the input stream is sufficient to encode for the higher channel count
+ int32_t bytesCount = (samplesPerFrame * nChannels * 2) * numFrames;
+ if (eleStream.gcount() < bytesCount) {
+ std::cout << "[ WARN ] Test Skipped for ChannelCount " << nChannels
+ << " because of insufficient input data\n";
+ continue;
+ }
+
+ ASSERT_EQ(mComponent->start(), C2_OK);
+
+ ASSERT_NO_FATAL_FAILURE(encodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
+ mFlushedIndices, mLinearPool, eleStream, numFrames,
+ samplesPerFrame, nChannels, nSampleRate));
+
+ // mDisableTest will be set if buffer was not fetched properly.
+ // This may happen when config params is not proper but config succeeded
+ // In this cases, we skip encoding the input stream
+ if (mDisableTest) {
+ std::cout << "[ WARN ] Test Disabled for ChannelCount " << nChannels << "\n";
+ ASSERT_EQ(mComponent->stop(), C2_OK);
+ return;
+ }
+
+ // blocking call to ensures application to Wait till all the inputs are consumed
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
+
+ // Validate output size based on chosen ChannelCount
+ EXPECT_GE(mOutputSize, prevOutputSize);
+
+ prevChannelCount = nChannels;
+ prevOutputSize = mOutputSize;
+
+ if (mFramesReceived != numFrames) {
+ ALOGE("Input buffer count and Output buffer count mismatch");
+ ALOGE("framesReceived : %d inputFrames : %u", mFramesReceived, numFrames);
+ ASSERT_TRUE(false);
+ }
+ if ((mCompName == flac || mCompName == opus || mCompName == aac)) {
+ ASSERT_TRUE(mCsd) << "CSD buffer missing";
+ }
+ ASSERT_TRUE(mEos);
+ ASSERT_EQ(mComponent->stop(), C2_OK);
+ mFramesReceived = 0;
+ mOutputSize = 0;
+ mEos = false;
+ mCsd = false;
+ eleStream.seekg(0, eleStream.beg);
+ }
+}
+
+TEST_P(Codec2AudioEncHidlTest, MultiSampleRateTest) {
+ description("Encodes input file for different SampleRate");
+ if (mDisableTest) GTEST_SKIP() << "Test is disabled";
+
+ char mURL[512];
+ strcpy(mURL, sResourceDir.c_str());
+ GetURLForComponent(mCompName, mURL);
+
+ std::ifstream eleStream;
+ eleStream.open(mURL, std::ifstream::binary);
+ ASSERT_EQ(eleStream.is_open(), true) << mURL << " file not found";
+ ALOGV("mURL : %s", mURL);
+
+ int32_t nSampleRate;
+ int32_t samplesPerFrame;
+ int32_t nChannels;
+ int32_t numFrames = 16;
+
+ if (!getConfigParams(mCompName, &nChannels, &nSampleRate, &samplesPerFrame)) {
+ std::cout << "Failed to get the config params for " << mCompName << " component\n";
+ std::cout << "[ WARN ] Test Skipped \n";
+ return;
+ }
+
+ int32_t sampleRateValues[] = {1000, 8000, 16000, 24000, 48000, 96000, 192000};
+
+ uint64_t prevOutputSize = 0u;
+ uint32_t prevSampleRate = 0u;
+
+ for (int32_t nSampleRate : sampleRateValues) {
+ ALOGV("Configuring %u encoder for SampleRate = %d", mCompName, nSampleRate);
+ if (!setupConfigParam(mComponent, nChannels, nSampleRate)) {
+ std::cout << "[ WARN ] Test Skipped \n";
+ return;
+ }
+
+ std::vector<std::unique_ptr<C2Param>> inParams;
+ c2_status_t c2_status = mComponent->query({}, {C2StreamSampleRateInfo::input::PARAM_TYPE},
+ C2_DONT_BLOCK, &inParams);
+
+ ASSERT_TRUE(!c2_status && inParams.size())
+ << "Query configured SampleRate failed => %d" << c2_status;
+ size_t offset = sizeof(C2Param);
+ C2Param* param = inParams[0].get();
+ int32_t configuredSampleRate = *(int32_t*)((uint8_t*)param + offset);
+
+ if (configuredSampleRate != nSampleRate) {
+ std::cout << "[ WARN ] Test Skipped for SampleRate " << nSampleRate << "\n";
+ continue;
+ }
+
+ // To check if the input stream is sufficient to encode for the higher SampleRate
+ int32_t bytesCount = (samplesPerFrame * nChannels * 2) * numFrames;
+ if (eleStream.gcount() < bytesCount) {
+ std::cout << "[ WARN ] Test Skipped for SampleRate " << nSampleRate
+ << " because of insufficient input data\n";
+ continue;
+ }
+
+ ASSERT_EQ(mComponent->start(), C2_OK);
+
+ ASSERT_NO_FATAL_FAILURE(encodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
+ mFlushedIndices, mLinearPool, eleStream, numFrames,
+ samplesPerFrame, nChannels, nSampleRate));
+
+ // mDisableTest will be set if buffer was not fetched properly.
+ // This may happen when config params is not proper but config succeeded
+ // In this case, we skip encoding the input stream
+ if (mDisableTest) {
+ std::cout << "[ WARN ] Test Disabled for SampleRate" << nSampleRate << "\n";
+ ASSERT_EQ(mComponent->stop(), C2_OK);
+ return;
+ }
+
+ // blocking call to ensures application to Wait till all the inputs are consumed
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
+
+ // Validate output size based on chosen samplerate
+ if (prevSampleRate >= nSampleRate) {
+ EXPECT_LE(mOutputSize, prevOutputSize);
+ } else {
+ EXPECT_GT(mOutputSize, prevOutputSize);
+ }
+ prevSampleRate = nSampleRate;
+ prevOutputSize = mOutputSize;
+
+ if (mFramesReceived != numFrames) {
+ ALOGE("Input buffer count and Output buffer count mismatch");
+ ALOGE("framesReceived : %d inputFrames : %u", mFramesReceived, numFrames);
+ ASSERT_TRUE(false);
+ }
+ if ((mCompName == flac || mCompName == opus || mCompName == aac)) {
+ ASSERT_TRUE(mCsd) << "CSD buffer missing";
+ }
+ ASSERT_TRUE(mEos);
+ ASSERT_EQ(mComponent->stop(), C2_OK);
+ mFramesReceived = 0;
+ mOutputSize = 0;
+ mEos = false;
+ mCsd = false;
+ eleStream.seekg(0, eleStream.beg);
+ }
+}
+
INSTANTIATE_TEST_SUITE_P(PerInstance, Codec2AudioEncHidlTest, testing::ValuesIn(kTestParameters),
android::hardware::PrintInstanceTupleNameToString<>);
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_av1_176_144_chksm.md5 b/media/codec2/hidl/1.0/vts/functional/res/bbb_av1_176_144_chksm.md5
new file mode 100644
index 0000000..cb69709
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_av1_176_144_chksm.md5
Binary files differ
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_av1_640_360_chksum.md5 b/media/codec2/hidl/1.0/vts/functional/res/bbb_av1_640_360_chksum.md5
new file mode 100644
index 0000000..2693071
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_av1_640_360_chksum.md5
Binary files differ
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_avc_176x144_300kbps_60fps_chksum.md5 b/media/codec2/hidl/1.0/vts/functional/res/bbb_avc_176x144_300kbps_60fps_chksum.md5
new file mode 100644
index 0000000..5c802d9
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_avc_176x144_300kbps_60fps_chksum.md5
Binary files differ
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_avc_640x360_768kbps_30fps_chksum.md5 b/media/codec2/hidl/1.0/vts/functional/res/bbb_avc_640x360_768kbps_30fps_chksum.md5
new file mode 100644
index 0000000..073f8eb
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_avc_640x360_768kbps_30fps_chksum.md5
Binary files differ
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_hevc_176x144_176kbps_60fps_chksum.md5 b/media/codec2/hidl/1.0/vts/functional/res/bbb_hevc_176x144_176kbps_60fps_chksum.md5
new file mode 100644
index 0000000..83f11c0
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_hevc_176x144_176kbps_60fps_chksum.md5
Binary files differ
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_hevc_640x360_1600kbps_30fps_chksum.md5 b/media/codec2/hidl/1.0/vts/functional/res/bbb_hevc_640x360_1600kbps_30fps_chksum.md5
new file mode 100644
index 0000000..3344881
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_hevc_640x360_1600kbps_30fps_chksum.md5
Binary files differ
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_vp8_640x360_2mbps_30fps_chksm.md5 b/media/codec2/hidl/1.0/vts/functional/res/bbb_vp8_640x360_2mbps_30fps_chksm.md5
new file mode 100644
index 0000000..738b1da
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_vp8_640x360_2mbps_30fps_chksm.md5
Binary files differ
diff --git a/media/codec2/hidl/1.0/vts/functional/res/bbb_vp9_640x360_1600kbps_30fps_chksm.md5 b/media/codec2/hidl/1.0/vts/functional/res/bbb_vp9_640x360_1600kbps_30fps_chksm.md5
new file mode 100644
index 0000000..a52faf2
--- /dev/null
+++ b/media/codec2/hidl/1.0/vts/functional/res/bbb_vp9_640x360_1600kbps_30fps_chksm.md5
Binary files differ
diff --git a/media/codec2/hidl/1.0/vts/functional/video/Android.bp b/media/codec2/hidl/1.0/vts/functional/video/Android.bp
index 760f4da..c7b0c12 100644
--- a/media/codec2/hidl/1.0/vts/functional/video/Android.bp
+++ b/media/codec2/hidl/1.0/vts/functional/video/Android.bp
@@ -26,6 +26,7 @@
"libbinder",
"libgui",
"libutils",
+ "libcrypto",
],
data: [":media_c2_v1_video_decode_res"],
test_config: "VtsHalMediaC2V1_0TargetVideoDecTest.xml",
diff --git a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.cpp b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.cpp
index 3362336..74088dd 100644
--- a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.cpp
+++ b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.cpp
@@ -22,6 +22,8 @@
#include <hidl/GtestPrinter.h>
#include <stdio.h>
+#include <openssl/md5.h>
+
#include <C2AllocatorIon.h>
#include <C2Buffer.h>
#include <C2BufferPriv.h>
@@ -41,6 +43,8 @@
static std::vector<std::tuple<std::string, std::string, std::string, std::string>>
kDecodeTestParameters;
+static std::vector<std::tuple<std::string, std::string, std::string>> kCsdFlushTestParameters;
+
// Resource directory
static std::string sResourceDir = "";
@@ -106,6 +110,9 @@
mTimestampUs = 0u;
mWorkResult = C2_OK;
mTimestampDevTest = false;
+ mMd5Offset = 0;
+ mMd5Enable = false;
+ mRefMd5 = nullptr;
if (mCompName == unknown_comp) mDisableTest = true;
C2SecureModeTuning secureModeTuning{};
@@ -128,6 +135,77 @@
// Get the test parameters from GetParam call.
virtual void getParams() {}
+ /* Calculate the CKSUM for the data in inbuf */
+ void calc_md5_cksum(uint8_t* pu1_inbuf, uint32_t u4_stride, uint32_t u4_width,
+ uint32_t u4_height, uint8_t* pu1_cksum_p) {
+ int32_t row;
+ MD5_CTX s_md5_context;
+ MD5_Init(&s_md5_context);
+ for (row = 0; row < u4_height; row++) {
+ MD5_Update(&s_md5_context, pu1_inbuf, u4_width);
+ pu1_inbuf += u4_stride;
+ }
+ MD5_Final(pu1_cksum_p, &s_md5_context);
+ }
+
+ void compareMd5Chksm(std::unique_ptr<C2Work>& work) {
+ uint8_t chksum[48];
+ uint8_t* au1_y_chksum = chksum;
+ uint8_t* au1_u_chksum = chksum + 16;
+ uint8_t* au1_v_chksum = chksum + 32;
+ const C2GraphicView output = work->worklets.front()
+ ->output.buffers[0]
+ ->data()
+ .graphicBlocks()
+ .front()
+ .map()
+ .get();
+ uint8_t* yPlane = const_cast<uint8_t*>(output.data()[C2PlanarLayout::PLANE_Y]);
+ uint8_t* uPlane = const_cast<uint8_t*>(output.data()[C2PlanarLayout::PLANE_U]);
+ uint8_t* vPlane = const_cast<uint8_t*>(output.data()[C2PlanarLayout::PLANE_V]);
+ C2PlanarLayout layout = output.layout();
+
+ size_t yStride = layout.planes[C2PlanarLayout::PLANE_Y].rowInc;
+ size_t uvStride = layout.planes[C2PlanarLayout::PLANE_U].rowInc;
+ size_t colInc = layout.planes[C2PlanarLayout::PLANE_U].colInc;
+ size_t bitDepth = layout.planes[C2PlanarLayout::PLANE_Y].bitDepth;
+ uint32_t layoutType = layout.type;
+ size_t cropWidth = output.crop().width;
+ size_t cropHeight = output.crop().height;
+
+ if (bitDepth == 8 && layoutType == C2PlanarLayout::TYPE_YUV && colInc == 1) {
+ calc_md5_cksum(yPlane, yStride, cropWidth, cropHeight, au1_y_chksum);
+ calc_md5_cksum(uPlane, uvStride, cropWidth / 2, cropHeight / 2, au1_u_chksum);
+ calc_md5_cksum(vPlane, uvStride, cropWidth / 2, cropHeight / 2, au1_v_chksum);
+ } else if (bitDepth == 8 && layoutType == C2PlanarLayout::TYPE_YUV && colInc == 2) {
+ uint8_t* cbPlane = (uint8_t*)malloc(cropWidth * cropHeight / 4);
+ uint8_t* crPlane = (uint8_t*)malloc(cropWidth * cropHeight / 4);
+ ASSERT_NE(cbPlane, nullptr);
+ ASSERT_NE(crPlane, nullptr);
+ size_t count = 0;
+ for (size_t k = 0; k < (cropHeight / 2); k++) {
+ for (size_t l = 0; l < (cropWidth); l = l + 2) {
+ cbPlane[count] = uPlane[k * uvStride + l];
+ crPlane[count] = vPlane[k * uvStride + l];
+ count++;
+ }
+ }
+ calc_md5_cksum(yPlane, yStride, cropWidth, cropHeight, au1_y_chksum);
+ calc_md5_cksum(cbPlane, cropWidth / 2, cropWidth / 2, cropHeight / 2, au1_u_chksum);
+ calc_md5_cksum(crPlane, cropWidth / 2, cropWidth / 2, cropHeight / 2, au1_v_chksum);
+ free(cbPlane);
+ free(crPlane);
+ } else {
+ mMd5Enable = false;
+ ALOGV("Disabling MD5 chksm flag");
+ return;
+ }
+ if (memcmp(mRefMd5 + mMd5Offset, chksum, 48)) ASSERT_TRUE(false);
+ mMd5Offset += 48;
+ return;
+ }
+ bool configPixelFormat(uint32_t format);
+
// callback function to process onWorkDone received by Listener
void handleWorkDone(std::list<std::unique_ptr<C2Work>>& workItems) {
for (std::unique_ptr<C2Work>& work : workItems) {
@@ -164,6 +242,9 @@
}
}
}
+ if (mMd5Enable) {
+ compareMd5Chksm(work);
+ }
}
bool mCsd = false;
workDone(mComponent, work, mFlushedIndices, mQueueLock, mQueueCondition, mWorkQueue,
@@ -190,8 +271,11 @@
bool mEos;
bool mDisableTest;
+ bool mMd5Enable;
bool mTimestampDevTest;
uint64_t mTimestampUs;
+ uint64_t mMd5Offset;
+ char* mRefMd5;
std::list<uint64_t> mTimestampUslist;
std::list<uint64_t> mFlushedIndices;
standardComp mCompName;
@@ -265,54 +349,73 @@
// number of elementary streams per component
#define STREAM_COUNT 3
-// LookUpTable of clips and metadata for component testing
-void GetURLForComponent(Codec2VideoDecHidlTest::standardComp comp, char* mURL, char* info,
- size_t streamIndex = 1) {
+// LookUpTable of clips, metadata and chksum for component testing
+void GetURLChksmForComponent(Codec2VideoDecHidlTest::standardComp comp, char* mURL, char* info,
+ char* chksum, size_t streamIndex = 1) {
struct CompToURL {
Codec2VideoDecHidlTest::standardComp comp;
const char mURL[STREAM_COUNT][512];
const char info[STREAM_COUNT][512];
+ const char chksum[STREAM_COUNT][512];
};
ASSERT_TRUE(streamIndex < STREAM_COUNT);
static const CompToURL kCompToURL[] = {
{Codec2VideoDecHidlTest::standardComp::avc,
{"bbb_avc_176x144_300kbps_60fps.h264", "bbb_avc_640x360_768kbps_30fps.h264", ""},
- {"bbb_avc_176x144_300kbps_60fps.info", "bbb_avc_640x360_768kbps_30fps.info", ""}},
+ {"bbb_avc_176x144_300kbps_60fps.info", "bbb_avc_640x360_768kbps_30fps.info", ""},
+ {"bbb_avc_176x144_300kbps_60fps_chksum.md5",
+ "bbb_avc_640x360_768kbps_30fps_chksum.md5", ""}},
{Codec2VideoDecHidlTest::standardComp::hevc,
{"bbb_hevc_176x144_176kbps_60fps.hevc", "bbb_hevc_640x360_1600kbps_30fps.hevc", ""},
- {"bbb_hevc_176x144_176kbps_60fps.info", "bbb_hevc_640x360_1600kbps_30fps.info", ""}},
+ {"bbb_hevc_176x144_176kbps_60fps.info", "bbb_hevc_640x360_1600kbps_30fps.info", ""},
+ {"bbb_hevc_176x144_176kbps_60fps_chksum.md5",
+ "bbb_hevc_640x360_1600kbps_30fps_chksum.md5", ""}},
{Codec2VideoDecHidlTest::standardComp::mpeg2,
{"bbb_mpeg2_176x144_105kbps_25fps.m2v", "bbb_mpeg2_352x288_1mbps_60fps.m2v", ""},
- {"bbb_mpeg2_176x144_105kbps_25fps.info", "bbb_mpeg2_352x288_1mbps_60fps.info", ""}},
+ {"bbb_mpeg2_176x144_105kbps_25fps.info", "bbb_mpeg2_352x288_1mbps_60fps.info", ""},
+ {"", "", ""}},
{Codec2VideoDecHidlTest::standardComp::h263,
{"", "bbb_h263_352x288_300kbps_12fps.h263", ""},
- {"", "bbb_h263_352x288_300kbps_12fps.info", ""}},
+ {"", "bbb_h263_352x288_300kbps_12fps.info", ""},
+ {"", "", ""}},
{Codec2VideoDecHidlTest::standardComp::mpeg4,
{"", "bbb_mpeg4_352x288_512kbps_30fps.m4v", ""},
- {"", "bbb_mpeg4_352x288_512kbps_30fps.info", ""}},
+ {"", "bbb_mpeg4_352x288_512kbps_30fps.info", ""},
+ {"", "", ""}},
{Codec2VideoDecHidlTest::standardComp::vp8,
{"bbb_vp8_176x144_240kbps_60fps.vp8", "bbb_vp8_640x360_2mbps_30fps.vp8", ""},
- {"bbb_vp8_176x144_240kbps_60fps.info", "bbb_vp8_640x360_2mbps_30fps.info", ""}},
+ {"bbb_vp8_176x144_240kbps_60fps.info", "bbb_vp8_640x360_2mbps_30fps.info", ""},
+ {"", "bbb_vp8_640x360_2mbps_30fps_chksm.md5", ""}},
{Codec2VideoDecHidlTest::standardComp::vp9,
{"bbb_vp9_176x144_285kbps_60fps.vp9", "bbb_vp9_640x360_1600kbps_30fps.vp9",
"bbb_vp9_704x480_280kbps_24fps_altref_2.vp9"},
{"bbb_vp9_176x144_285kbps_60fps.info", "bbb_vp9_640x360_1600kbps_30fps.info",
- "bbb_vp9_704x480_280kbps_24fps_altref_2.info"}},
+ "bbb_vp9_704x480_280kbps_24fps_altref_2.info"},
+ {"", "bbb_vp9_640x360_1600kbps_30fps_chksm.md5", ""}},
{Codec2VideoDecHidlTest::standardComp::av1,
{"bbb_av1_640_360.av1", "bbb_av1_176_144.av1", ""},
- {"bbb_av1_640_360.info", "bbb_av1_176_144.info", ""}},
+ {"bbb_av1_640_360.info", "bbb_av1_176_144.info", ""},
+ {"bbb_av1_640_360_chksum.md5", "bbb_av1_176_144_chksm.md5", ""}},
};
for (size_t i = 0; i < sizeof(kCompToURL) / sizeof(kCompToURL[0]); ++i) {
if (kCompToURL[i].comp == comp) {
strcat(mURL, kCompToURL[i].mURL[streamIndex]);
strcat(info, kCompToURL[i].info[streamIndex]);
+ strcat(chksum, kCompToURL[i].chksum[streamIndex]);
return;
}
}
}
+void GetURLForComponent(Codec2VideoDecHidlTest::standardComp comp, char* mURL, char* info,
+ size_t streamIndex = 1) {
+ char chksum[512];
+ strcpy(chksum, sResourceDir.c_str());
+ GetURLChksmForComponent(comp, mURL, info, chksum, streamIndex);
+}
+
void decodeNFrames(const std::shared_ptr<android::Codec2Client::Component>& component,
std::mutex& queueLock, std::condition_variable& queueCondition,
std::list<std::unique_ptr<C2Work>>& workQueue,
@@ -443,6 +546,19 @@
ASSERT_EQ(producer->setSidebandStream(nativeHandle), NO_ERROR);
}
+// Config output pixel format
+bool Codec2VideoDecHidlTestBase::configPixelFormat(uint32_t format) {
+ std::vector<std::unique_ptr<C2SettingResult>> failures;
+ C2StreamPixelFormatInfo::output pixelformat(0u, format);
+
+ std::vector<C2Param*> configParam{&pixelformat};
+ c2_status_t status = mComponent->config(configParam, C2_DONT_BLOCK, &failures);
+ if (status == C2_OK && failures.size() == 0u) {
+ return true;
+ }
+ return false;
+}
+
class Codec2VideoDecDecodeTest
: public Codec2VideoDecHidlTestBase,
public ::testing::WithParamInterface<
@@ -461,13 +577,27 @@
uint32_t streamIndex = std::stoi(std::get<2>(GetParam()));
bool signalEOS = !std::get<2>(GetParam()).compare("true");
mTimestampDevTest = true;
- char mURL[512], info[512];
+
+ char mURL[512], info[512], chksum[512];
android::Vector<FrameInfo> Info;
strcpy(mURL, sResourceDir.c_str());
strcpy(info, sResourceDir.c_str());
+ strcpy(chksum, sResourceDir.c_str());
- GetURLForComponent(mCompName, mURL, info, streamIndex);
+ GetURLChksmForComponent(mCompName, mURL, info, chksum, streamIndex);
+ if (!(strcmp(mURL, sResourceDir.c_str())) || !(strcmp(info, sResourceDir.c_str()))) {
+ ALOGV("Skipping Test, Stream not available");
+ return;
+ }
+ mMd5Enable = true;
+ if (!strcmp(chksum, sResourceDir.c_str())) mMd5Enable = false;
+
+ uint32_t format = HAL_PIXEL_FORMAT_YCBCR_420_888;
+ if (!configPixelFormat(format)) {
+ std::cout << "[ WARN ] Test Skipped PixelFormat not configured\n";
+ return;
+ }
mFlushedIndices.clear();
mTimestampUslist.clear();
@@ -483,6 +613,24 @@
std::ifstream eleStream;
eleStream.open(mURL, std::ifstream::binary);
ASSERT_EQ(eleStream.is_open(), true);
+
+ size_t refChksmSize = 0;
+ std::ifstream refChksum;
+ if (mMd5Enable) {
+ ALOGV("chksum file name: %s", chksum);
+ refChksum.open(chksum, std::ifstream::binary | std::ifstream::ate);
+ ASSERT_EQ(refChksum.is_open(), true);
+ refChksmSize = refChksum.tellg();
+ refChksum.seekg(0, std::ifstream::beg);
+
+ ALOGV("chksum Size %zu ", refChksmSize);
+ mRefMd5 = (char*)malloc(refChksmSize);
+ ASSERT_NE(mRefMd5, nullptr);
+ refChksum.read(mRefMd5, refChksmSize);
+ ASSERT_EQ(refChksum.gcount(), refChksmSize);
+ refChksum.close();
+ }
+
ASSERT_NO_FATAL_FAILURE(decodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
mFlushedIndices, mLinearPool, eleStream, &Info, 0,
(int)Info.size(), signalEOS));
@@ -509,6 +657,14 @@
ASSERT_TRUE(false);
}
+ if (mRefMd5 != nullptr) free(mRefMd5);
+ if (mMd5Enable && refChksmSize != mMd5Offset) {
+ ALOGE("refChksum size and generated chksum size mismatch refChksum size %zu generated "
+ "chksum size %" PRId64 "",
+ refChksmSize, mMd5Offset);
+ ASSERT_TRUE(false);
+ }
+
if (mTimestampDevTest) EXPECT_EQ(mTimestampUslist.empty(), true);
ASSERT_EQ(mComponent->stop(), C2_OK);
ASSERT_EQ(mWorkResult, C2_OK);
@@ -847,6 +1003,110 @@
}
}
+class Codec2VideoDecCsdInputTests
+ : public Codec2VideoDecHidlTestBase,
+ public ::testing::WithParamInterface<std::tuple<std::string, std::string, std::string>> {
+ void getParams() {
+ mInstanceName = std::get<0>(GetParam());
+ mComponentName = std::get<1>(GetParam());
+ }
+};
+
+// Test the codecs for the following
+// start - csd - data… - (with/without)flush - data… - flush - data…
+TEST_P(Codec2VideoDecCsdInputTests, CSDFlushTest) {
+ description("Tests codecs for flush at different states");
+ if (mDisableTest) GTEST_SKIP() << "Test is disabled";
+
+ char mURL[512], info[512];
+
+ android::Vector<FrameInfo> Info;
+
+ strcpy(mURL, sResourceDir.c_str());
+ strcpy(info, sResourceDir.c_str());
+ GetURLForComponent(mCompName, mURL, info);
+
+ int32_t numCsds = populateInfoVector(info, &Info, mTimestampDevTest, &mTimestampUslist);
+ ASSERT_GE(numCsds, 0) << "Error in parsing input info file";
+
+ ASSERT_EQ(mComponent->start(), C2_OK);
+
+ ALOGV("mURL : %s", mURL);
+ std::ifstream eleStream;
+ eleStream.open(mURL, std::ifstream::binary);
+ ASSERT_EQ(eleStream.is_open(), true);
+ bool flushedDecoder = false;
+ bool signalEOS = false;
+ bool keyFrame = false;
+ bool flushCsd = !std::get<2>(GetParam()).compare("true");
+
+ ALOGV("sending %d csd data ", numCsds);
+ int framesToDecode = numCsds;
+ ASSERT_NO_FATAL_FAILURE(decodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
+ mFlushedIndices, mLinearPool, eleStream, &Info, 0,
+ framesToDecode, false));
+ c2_status_t err = C2_OK;
+ std::list<std::unique_ptr<C2Work>> flushedWork;
+ if (numCsds && flushCsd) {
+ // We wait for all the CSD buffers to get consumed.
+ // Once we have received all CSD work back, we call flush
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
+
+ err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
+ ASSERT_EQ(err, C2_OK);
+ flushedDecoder = true;
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
+ MAX_INPUT_BUFFERS - flushedWork.size());
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+ }
+
+ int offset = framesToDecode;
+ uint32_t flags = 0;
+ while (1) {
+ while (offset < (int)Info.size()) {
+ flags = 0;
+ if (Info[offset].flags) flags = 1u << (Info[offset].flags - 1);
+ if (flags & SYNC_FRAME) {
+ keyFrame = true;
+ break;
+ }
+ eleStream.ignore(Info[offset].bytesCount);
+ offset++;
+ }
+ if (keyFrame) {
+ framesToDecode = c2_min(FLUSH_INTERVAL, (int)Info.size() - offset);
+ if (framesToDecode < FLUSH_INTERVAL) signalEOS = true;
+ ASSERT_NO_FATAL_FAILURE(decodeNFrames(
+ mComponent, mQueueLock, mQueueCondition, mWorkQueue, mFlushedIndices,
+ mLinearPool, eleStream, &Info, offset, framesToDecode, signalEOS));
+ offset += framesToDecode;
+ }
+ err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
+ ASSERT_EQ(err, C2_OK);
+ keyFrame = false;
+ // blocking call to ensures application to Wait till remaining
+ // 'non-flushed' inputs are consumed
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
+ MAX_INPUT_BUFFERS - flushedWork.size());
+ ASSERT_NO_FATAL_FAILURE(
+ verifyFlushOutput(flushedWork, mWorkQueue, mFlushedIndices, mQueueLock));
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+ if (signalEOS || offset >= (int)Info.size()) {
+ break;
+ }
+ }
+ if (!signalEOS) {
+ ASSERT_NO_FATAL_FAILURE(testInputBuffer(mComponent, mQueueLock, mWorkQueue,
+ C2FrameData::FLAG_END_OF_STREAM, false));
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue);
+ }
+ eleStream.close();
+ ASSERT_EQ(mWorkQueue.size(), MAX_INPUT_BUFFERS);
+ ASSERT_EQ(mComponent->stop(), C2_OK);
+}
+
INSTANTIATE_TEST_SUITE_P(PerInstance, Codec2VideoDecHidlTest, testing::ValuesIn(kTestParameters),
android::hardware::PrintInstanceTupleNameToString<>);
@@ -855,6 +1115,10 @@
testing::ValuesIn(kDecodeTestParameters),
android::hardware::PrintInstanceTupleNameToString<>);
+INSTANTIATE_TEST_SUITE_P(CsdInputs, Codec2VideoDecCsdInputTests,
+ testing::ValuesIn(kCsdFlushTestParameters),
+ android::hardware::PrintInstanceTupleNameToString<>);
+
} // anonymous namespace
// TODO : Video specific configuration Test
@@ -873,6 +1137,11 @@
std::make_tuple(std::get<0>(params), std::get<1>(params), "2", "false"));
kDecodeTestParameters.push_back(
std::make_tuple(std::get<0>(params), std::get<1>(params), "2", "true"));
+
+ kCsdFlushTestParameters.push_back(
+ std::make_tuple(std::get<0>(params), std::get<1>(params), "true"));
+ kCsdFlushTestParameters.push_back(
+ std::make_tuple(std::get<0>(params), std::get<1>(params), "false"));
}
// Set the resource directory based on command line args.
diff --git a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.xml b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.xml
index 63e7a69..a1049df 100644
--- a/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.xml
+++ b/media/codec2/hidl/1.0/vts/functional/video/VtsHalMediaC2V1_0TargetVideoDecTest.xml
@@ -51,6 +51,14 @@
<option name="push-file" key="bbb_av1_176_144.info" value="/data/local/tmp/media/bbb_av1_176_144.info" />
<option name="push-file" key="bbb_vp9_704x480_280kbps_24fps_altref_2.vp9" value="/data/local/tmp/media/bbb_vp9_704x480_280kbps_24fps_altref_2.vp9" />
<option name="push-file" key="bbb_vp9_704x480_280kbps_24fps_altref_2.info" value="/data/local/tmp/media/bbb_vp9_704x480_280kbps_24fps_altref_2.info" />
+ <option name="push-file" key="bbb_avc_176x144_300kbps_60fps_chksum.md5" value="/data/local/tmp/media/bbb_avc_176x144_300kbps_60fps_chksum.md5" />
+ <option name="push-file" key="bbb_avc_640x360_768kbps_30fps_chksum.md5" value="/data/local/tmp/media/bbb_avc_640x360_768kbps_30fps_chksum.md5" />
+ <option name="push-file" key="bbb_hevc_176x144_176kbps_60fps_chksum.md5" value="/data/local/tmp/media/bbb_hevc_176x144_176kbps_60fps_chksum.md5" />
+ <option name="push-file" key="bbb_hevc_640x360_1600kbps_30fps_chksum.md5" value="/data/local/tmp/media/bbb_hevc_640x360_1600kbps_30fps_chksum.md5" />
+ <option name="push-file" key="bbb_vp8_640x360_2mbps_30fps_chksm.md5" value="/data/local/tmp/media/bbb_vp8_640x360_2mbps_30fps_chksm.md5" />
+ <option name="push-file" key="bbb_vp9_640x360_1600kbps_30fps_chksm.md5" value="/data/local/tmp/media/bbb_vp9_640x360_1600kbps_30fps_chksm.md5" />
+ <option name="push-file" key="bbb_av1_640_360_chksum.md5" value="/data/local/tmp/media/bbb_av1_640_360_chksum.md5" />
+ <option name="push-file" key="bbb_av1_176_144_chksm.md5" value="/data/local/tmp/media/bbb_av1_176_144_chksm.md5" />
</target_preparer>
<test class="com.android.tradefed.testtype.GTest" >
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.cpp b/media/codec2/sfplugin/CCodecBufferChannel.cpp
index 2fc1781..8059ff6 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.cpp
+++ b/media/codec2/sfplugin/CCodecBufferChannel.cpp
@@ -128,97 +128,6 @@
count->value = -1;
}
-// CCodecBufferChannel::ReorderStash
-
-CCodecBufferChannel::ReorderStash::ReorderStash() {
- clear();
-}
-
-void CCodecBufferChannel::ReorderStash::clear() {
- mPending.clear();
- mStash.clear();
- mDepth = 0;
- mKey = C2Config::ORDINAL;
-}
-
-void CCodecBufferChannel::ReorderStash::flush() {
- mPending.clear();
- mStash.clear();
-}
-
-void CCodecBufferChannel::ReorderStash::setDepth(uint32_t depth) {
- mPending.splice(mPending.end(), mStash);
- mDepth = depth;
-}
-
-void CCodecBufferChannel::ReorderStash::setKey(C2Config::ordinal_key_t key) {
- mPending.splice(mPending.end(), mStash);
- mKey = key;
-}
-
-bool CCodecBufferChannel::ReorderStash::pop(Entry *entry) {
- if (mPending.empty()) {
- return false;
- }
- entry->buffer = mPending.front().buffer;
- entry->timestamp = mPending.front().timestamp;
- entry->flags = mPending.front().flags;
- entry->ordinal = mPending.front().ordinal;
- mPending.pop_front();
- return true;
-}
-
-void CCodecBufferChannel::ReorderStash::emplace(
- const std::shared_ptr<C2Buffer> &buffer,
- int64_t timestamp,
- int32_t flags,
- const C2WorkOrdinalStruct &ordinal) {
- bool eos = flags & MediaCodec::BUFFER_FLAG_EOS;
- if (!buffer && eos) {
- // TRICKY: we may be violating ordering of the stash here. Because we
- // don't expect any more emplace() calls after this, the ordering should
- // not matter.
- mStash.emplace_back(buffer, timestamp, flags, ordinal);
- } else {
- flags = flags & ~MediaCodec::BUFFER_FLAG_EOS;
- auto it = mStash.begin();
- for (; it != mStash.end(); ++it) {
- if (less(ordinal, it->ordinal)) {
- break;
- }
- }
- mStash.emplace(it, buffer, timestamp, flags, ordinal);
- if (eos) {
- mStash.back().flags = mStash.back().flags | MediaCodec::BUFFER_FLAG_EOS;
- }
- }
- while (!mStash.empty() && mStash.size() > mDepth) {
- mPending.push_back(mStash.front());
- mStash.pop_front();
- }
-}
-
-void CCodecBufferChannel::ReorderStash::defer(
- const CCodecBufferChannel::ReorderStash::Entry &entry) {
- mPending.push_front(entry);
-}
-
-bool CCodecBufferChannel::ReorderStash::hasPending() const {
- return !mPending.empty();
-}
-
-bool CCodecBufferChannel::ReorderStash::less(
- const C2WorkOrdinalStruct &o1, const C2WorkOrdinalStruct &o2) {
- switch (mKey) {
- case C2Config::ORDINAL: return o1.frameIndex < o2.frameIndex;
- case C2Config::TIMESTAMP: return o1.timestamp < o2.timestamp;
- case C2Config::CUSTOM: return o1.customOrdinal < o2.customOrdinal;
- default:
- ALOGD("Unrecognized key; default to timestamp");
- return o1.frameIndex < o2.frameIndex;
- }
-}
-
// Input
CCodecBufferChannel::Input::Input() : extraBuffers("extra") {}
@@ -708,7 +617,7 @@
void CCodecBufferChannel::feedInputBufferIfAvailableInternal() {
if (mInputMetEos ||
- mReorderStash.lock()->hasPending() ||
+ mOutput.lock()->buffers->hasPending() ||
mPipelineWatcher.lock()->pipelineFull()) {
return;
} else {
@@ -989,17 +898,6 @@
return UNKNOWN_ERROR;
}
- {
- Mutexed<ReorderStash>::Locked reorder(mReorderStash);
- reorder->clear();
- if (reorderDepth) {
- reorder->setDepth(reorderDepth.value);
- }
- if (reorderKey) {
- reorder->setKey(reorderKey.value);
- }
- }
-
uint32_t inputDelayValue = inputDelay ? inputDelay.value : 0;
uint32_t pipelineDelayValue = pipelineDelay ? pipelineDelay.value : 0;
uint32_t outputDelayValue = outputDelay ? outputDelay.value : 0;
@@ -1268,6 +1166,13 @@
}
output->buffers->setFormat(outputFormat);
+ output->buffers->clearStash();
+ if (reorderDepth) {
+ output->buffers->setReorderDepth(reorderDepth.value);
+ }
+ if (reorderKey) {
+ output->buffers->setReorderKey(reorderKey.value);
+ }
// Try to set output surface to created block pool if given.
if (outputSurface) {
@@ -1402,6 +1307,7 @@
if (mInputSurface != nullptr) {
mInputSurface.reset();
}
+ mPipelineWatcher.lock()->flush();
}
void CCodecBufferChannel::reset() {
@@ -1409,6 +1315,7 @@
{
Mutexed<Input>::Locked input(mInput);
input->buffers.reset(new DummyInputBuffers(""));
+ input->extraBuffers.flush();
}
{
Mutexed<Output>::Locked output(mOutput);
@@ -1425,6 +1332,8 @@
blockPools->inputPool.reset();
blockPools->outputPoolIntf.reset();
}
+ setCrypto(nullptr);
+ setDescrambler(nullptr);
}
@@ -1460,9 +1369,9 @@
Mutexed<Output>::Locked output(mOutput);
if (output->buffers) {
output->buffers->flush(flushedWork);
+ output->buffers->flushStash();
}
}
- mReorderStash.lock()->flush();
mPipelineWatcher.lock()->flush();
}
@@ -1503,45 +1412,36 @@
if (!output->buffers) {
return false;
}
- if (outputFormat != nullptr) {
- ALOGD("[%s] onWorkDone: output format changed to %s",
- mName, outputFormat->debugString().c_str());
- output->buffers->setFormat(outputFormat);
-
- AString mediaType;
- if (outputFormat->findString(KEY_MIME, &mediaType)
- && mediaType == MIMETYPE_AUDIO_RAW) {
- int32_t channelCount;
- int32_t sampleRate;
- if (outputFormat->findInt32(KEY_CHANNEL_COUNT, &channelCount)
- && outputFormat->findInt32(KEY_SAMPLE_RATE, &sampleRate)) {
- output->buffers->updateSkipCutBuffer(sampleRate, channelCount);
- }
- }
- }
}
- if ((work->input.ordinal.frameIndex - mFirstValidFrameIndex.load()).peek() < 0) {
+ // Whether the output buffer should be reported to the client or not.
+ bool notifyClient = false;
+
+ if (work->result == C2_OK){
+ notifyClient = true;
+ } else if (work->result == C2_NOT_FOUND) {
+ ALOGD("[%s] flushed work; ignored.", mName);
+ } else {
+ // C2_OK and C2_NOT_FOUND are the only results that we accept for processing
+ // the config update.
+ ALOGD("[%s] work failed to complete: %d", mName, work->result);
+ mCCodecCallback->onError(work->result, ACTION_CODE_FATAL);
+ return false;
+ }
+
+ if ((work->input.ordinal.frameIndex -
+ mFirstValidFrameIndex.load()).peek() < 0) {
// Discard frames from previous generation.
ALOGD("[%s] Discard frames from previous generation.", mName);
- return false;
+ notifyClient = false;
}
if (mInputSurface == nullptr && (work->worklets.size() != 1u
|| !work->worklets.front()
- || !(work->worklets.front()->output.flags & C2FrameData::FLAG_INCOMPLETE))) {
- mPipelineWatcher.lock()->onWorkDone(work->input.ordinal.frameIndex.peeku());
- }
-
- if (work->result == C2_NOT_FOUND) {
- ALOGD("[%s] flushed work; ignored.", mName);
- return true;
- }
-
- if (work->result != C2_OK) {
- ALOGD("[%s] work failed to complete: %d", mName, work->result);
- mCCodecCallback->onError(work->result, ACTION_CODE_FATAL);
- return false;
+ || !(work->worklets.front()->output.flags &
+ C2FrameData::FLAG_INCOMPLETE))) {
+ mPipelineWatcher.lock()->onWorkDone(
+ work->input.ordinal.frameIndex.peeku());
}
// NOTE: MediaCodec usage supposedly have only one worklet
@@ -1577,8 +1477,10 @@
case C2PortReorderBufferDepthTuning::CORE_INDEX: {
C2PortReorderBufferDepthTuning::output reorderDepth;
if (reorderDepth.updateFrom(*param)) {
- bool secure = mComponent->getName().find(".secure") != std::string::npos;
- mReorderStash.lock()->setDepth(reorderDepth.value);
+ bool secure = mComponent->getName().find(".secure") !=
+ std::string::npos;
+ mOutput.lock()->buffers->setReorderDepth(
+ reorderDepth.value);
ALOGV("[%s] onWorkDone: updated reorder depth to %u",
mName, reorderDepth.value);
size_t numOutputSlots = mOutput.lock()->numSlots;
@@ -1590,17 +1492,19 @@
output->maxDequeueBuffers += numInputSlots;
}
if (output->surface) {
- output->surface->setMaxDequeuedBufferCount(output->maxDequeueBuffers);
+ output->surface->setMaxDequeuedBufferCount(
+ output->maxDequeueBuffers);
}
} else {
- ALOGD("[%s] onWorkDone: failed to read reorder depth", mName);
+ ALOGD("[%s] onWorkDone: failed to read reorder depth",
+ mName);
}
break;
}
case C2PortReorderKeySetting::CORE_INDEX: {
C2PortReorderKeySetting::output reorderKey;
if (reorderKey.updateFrom(*param)) {
- mReorderStash.lock()->setKey(reorderKey.value);
+ mOutput.lock()->buffers->setReorderKey(reorderKey.value);
ALOGV("[%s] onWorkDone: updated reorder key to %u",
mName, reorderKey.value);
} else {
@@ -1615,7 +1519,8 @@
ALOGV("[%s] onWorkDone: updating pipeline delay %u",
mName, pipelineDelay.value);
newPipelineDelay = pipelineDelay.value;
- (void)mPipelineWatcher.lock()->pipelineDelay(pipelineDelay.value);
+ (void)mPipelineWatcher.lock()->pipelineDelay(
+ pipelineDelay.value);
}
}
if (param->forInput()) {
@@ -1624,7 +1529,8 @@
ALOGV("[%s] onWorkDone: updating input delay %u",
mName, inputDelay.value);
newInputDelay = inputDelay.value;
- (void)mPipelineWatcher.lock()->inputDelay(inputDelay.value);
+ (void)mPipelineWatcher.lock()->inputDelay(
+ inputDelay.value);
}
}
if (param->forOutput()) {
@@ -1632,8 +1538,10 @@
if (outputDelay.updateFrom(*param)) {
ALOGV("[%s] onWorkDone: updating output delay %u",
mName, outputDelay.value);
- bool secure = mComponent->getName().find(".secure") != std::string::npos;
- (void)mPipelineWatcher.lock()->outputDelay(outputDelay.value);
+ bool secure = mComponent->getName().find(".secure") !=
+ std::string::npos;
+ (void)mPipelineWatcher.lock()->outputDelay(
+ outputDelay.value);
bool outputBuffersChanged = false;
size_t numOutputSlots = 0;
@@ -1644,7 +1552,8 @@
return false;
}
output->outputDelay = outputDelay.value;
- numOutputSlots = outputDelay.value + kSmoothnessFactor;
+ numOutputSlots = outputDelay.value +
+ kSmoothnessFactor;
if (output->numSlots < numOutputSlots) {
output->numSlots = numOutputSlots;
if (output->buffers->isArrayMode()) {
@@ -1663,7 +1572,7 @@
mCCodecCallback->onOutputBuffersChanged();
}
- uint32_t depth = mReorderStash.lock()->depth();
+ uint32_t depth = mOutput.lock()->buffers->getReorderDepth();
Mutexed<OutputSurface>::Locked output(mOutputSurface);
output->maxDequeueBuffers = numOutputSlots + depth + kRenderingDepth;
if (!secure) {
@@ -1707,9 +1616,6 @@
ALOGV("[%s] onWorkDone: output EOS", mName);
}
- sp<MediaCodecBuffer> outBuffer;
- size_t index;
-
// WORKAROUND: adjust output timestamp based on client input timestamp and codec
// input timestamp. Codec output timestamp (in the timestamp field) shall correspond to
// the codec input timestamp, but client output timestamp should (reported in timeUs)
@@ -1730,8 +1636,18 @@
worklet->output.ordinal.timestamp.peekll(),
timestamp.peekll());
+ // csd cannot be re-ordered and will always arrive first.
if (initData != nullptr) {
Mutexed<Output>::Locked output(mOutput);
+ if (output->buffers && outputFormat) {
+ output->buffers->updateSkipCutBuffer(outputFormat);
+ output->buffers->setFormat(outputFormat);
+ }
+ if (!notifyClient) {
+ return false;
+ }
+ size_t index;
+ sp<MediaCodecBuffer> outBuffer;
if (output->buffers && output->buffers->registerCsd(initData, &index, &outBuffer) == OK) {
outBuffer->meta()->setInt64("timeUs", timestamp.peek());
outBuffer->meta()->setInt32("flags", MediaCodec::BUFFER_FLAG_CODECCONFIG);
@@ -1747,10 +1663,10 @@
}
}
- if (!buffer && !flags) {
+ if (notifyClient && !buffer && !flags) {
ALOGV("[%s] onWorkDone: Not reporting output buffer (%lld)",
mName, work->input.ordinal.frameIndex.peekull());
- return true;
+ notifyClient = false;
}
if (buffer) {
@@ -1769,66 +1685,62 @@
}
{
- Mutexed<ReorderStash>::Locked reorder(mReorderStash);
- reorder->emplace(buffer, timestamp.peek(), flags, worklet->output.ordinal);
- if (flags & MediaCodec::BUFFER_FLAG_EOS) {
- // Flush reorder stash
- reorder->setDepth(0);
- }
+ Mutexed<Output>::Locked output(mOutput);
+ output->buffers->pushToStash(
+ buffer,
+ notifyClient,
+ timestamp.peek(),
+ flags,
+ outputFormat,
+ worklet->output.ordinal);
}
sendOutputBuffers();
return true;
}
void CCodecBufferChannel::sendOutputBuffers() {
- ReorderStash::Entry entry;
- sp<MediaCodecBuffer> outBuffer;
+ OutputBuffers::BufferAction action;
size_t index;
+ sp<MediaCodecBuffer> outBuffer;
+ std::shared_ptr<C2Buffer> c2Buffer;
while (true) {
- Mutexed<ReorderStash>::Locked reorder(mReorderStash);
- if (!reorder->hasPending()) {
- break;
- }
- if (!reorder->pop(&entry)) {
- break;
- }
-
Mutexed<Output>::Locked output(mOutput);
if (!output->buffers) {
return;
}
- status_t err = output->buffers->registerBuffer(entry.buffer, &index, &outBuffer);
- if (err != OK) {
- bool outputBuffersChanged = false;
- if (err != WOULD_BLOCK) {
- if (!output->buffers->isArrayMode()) {
- output->buffers = output->buffers->toArrayMode(output->numSlots);
- }
- OutputBuffersArray *array = (OutputBuffersArray *)output->buffers.get();
- array->realloc(entry.buffer);
- outputBuffersChanged = true;
- }
- ALOGV("[%s] sendOutputBuffers: unable to register output buffer", mName);
- reorder->defer(entry);
-
+ action = output->buffers->popFromStashAndRegister(
+ &c2Buffer, &index, &outBuffer);
+ switch (action) {
+ case OutputBuffers::SKIP:
+ return;
+ case OutputBuffers::DISCARD:
+ break;
+ case OutputBuffers::NOTIFY_CLIENT:
output.unlock();
- reorder.unlock();
-
- if (outputBuffersChanged) {
- mCCodecCallback->onOutputBuffersChanged();
+ mCallback->onOutputBufferAvailable(index, outBuffer);
+ break;
+ case OutputBuffers::REALLOCATE:
+ if (!output->buffers->isArrayMode()) {
+ output->buffers =
+ output->buffers->toArrayMode(output->numSlots);
}
+ static_cast<OutputBuffersArray*>(output->buffers.get())->
+ realloc(c2Buffer);
+ output.unlock();
+ mCCodecCallback->onOutputBuffersChanged();
+ return;
+ case OutputBuffers::RETRY:
+ ALOGV("[%s] sendOutputBuffers: unable to register output buffer",
+ mName);
+ return;
+ default:
+ LOG_ALWAYS_FATAL("[%s] sendOutputBuffers: "
+ "corrupted BufferAction value (%d) "
+ "returned from popFromStashAndRegister.",
+ mName, int(action));
return;
}
- output.unlock();
- reorder.unlock();
-
- outBuffer->meta()->setInt64("timeUs", entry.timestamp);
- outBuffer->meta()->setInt32("flags", entry.flags);
- ALOGV("[%s] sendOutputBuffers: out buffer index = %zu [%p] => %p + %zu (%lld)",
- mName, index, outBuffer.get(), outBuffer->data(), outBuffer->size(),
- (long long)entry.timestamp);
- mCallback->onOutputBufferAvailable(index, outBuffer);
}
}
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.h b/media/codec2/sfplugin/CCodecBufferChannel.h
index f6e7024..046c5c3 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.h
+++ b/media/codec2/sfplugin/CCodecBufferChannel.h
@@ -306,48 +306,6 @@
Mutexed<PipelineWatcher> mPipelineWatcher;
- class ReorderStash {
- public:
- struct Entry {
- inline Entry() : buffer(nullptr), timestamp(0), flags(0), ordinal({0, 0, 0}) {}
- inline Entry(
- const std::shared_ptr<C2Buffer> &b,
- int64_t t,
- int32_t f,
- const C2WorkOrdinalStruct &o)
- : buffer(b), timestamp(t), flags(f), ordinal(o) {}
- std::shared_ptr<C2Buffer> buffer;
- int64_t timestamp;
- int32_t flags;
- C2WorkOrdinalStruct ordinal;
- };
-
- ReorderStash();
-
- void clear();
- void flush();
- void setDepth(uint32_t depth);
- void setKey(C2Config::ordinal_key_t key);
- bool pop(Entry *entry);
- void emplace(
- const std::shared_ptr<C2Buffer> &buffer,
- int64_t timestamp,
- int32_t flags,
- const C2WorkOrdinalStruct &ordinal);
- void defer(const Entry &entry);
- bool hasPending() const;
- uint32_t depth() const { return mDepth; }
-
- private:
- std::list<Entry> mPending;
- std::list<Entry> mStash;
- uint32_t mDepth;
- C2Config::ordinal_key_t mKey;
-
- bool less(const C2WorkOrdinalStruct &o1, const C2WorkOrdinalStruct &o2);
- };
- Mutexed<ReorderStash> mReorderStash;
-
std::atomic_bool mInputMetEos;
std::once_flag mRenderWarningFlag;
diff --git a/media/codec2/sfplugin/CCodecBuffers.cpp b/media/codec2/sfplugin/CCodecBuffers.cpp
index a9120c4..e58a1e4 100644
--- a/media/codec2/sfplugin/CCodecBuffers.cpp
+++ b/media/codec2/sfplugin/CCodecBuffers.cpp
@@ -21,6 +21,7 @@
#include <C2PlatformSupport.h>
#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MediaCodec.h>
#include <media/stagefright/MediaCodecConstants.h>
#include <media/stagefright/SkipCutBuffer.h>
#include <mediadrm/ICrypto.h>
@@ -155,16 +156,29 @@
setSkipCutBuffer(delay, padding);
}
+void OutputBuffers::updateSkipCutBuffer(
+ const sp<AMessage> &format, bool notify) {
+ AString mediaType;
+ if (format->findString(KEY_MIME, &mediaType)
+ && mediaType == MIMETYPE_AUDIO_RAW) {
+ int32_t channelCount;
+ int32_t sampleRate;
+ if (format->findInt32(KEY_CHANNEL_COUNT, &channelCount)
+ && format->findInt32(KEY_SAMPLE_RATE, &sampleRate)) {
+ updateSkipCutBuffer(sampleRate, channelCount);
+ }
+ }
+ if (notify) {
+ mUnreportedFormat = nullptr;
+ }
+}
+
void OutputBuffers::submit(const sp<MediaCodecBuffer> &buffer) {
if (mSkipCutBuffer != nullptr) {
mSkipCutBuffer->submit(buffer);
}
}
-void OutputBuffers::transferSkipCutBuffer(const sp<SkipCutBuffer> &scb) {
- mSkipCutBuffer = scb;
-}
-
void OutputBuffers::setSkipCutBuffer(int32_t skip, int32_t cut) {
if (mSkipCutBuffer != nullptr) {
size_t prevSize = mSkipCutBuffer->size();
@@ -175,6 +189,175 @@
mSkipCutBuffer = new SkipCutBuffer(skip, cut, mChannelCount);
}
+void OutputBuffers::clearStash() {
+ mPending.clear();
+ mReorderStash.clear();
+ mDepth = 0;
+ mKey = C2Config::ORDINAL;
+ mUnreportedFormat = nullptr;
+}
+
+void OutputBuffers::flushStash() {
+ for (StashEntry& e : mPending) {
+ e.notify = false;
+ }
+ for (StashEntry& e : mReorderStash) {
+ e.notify = false;
+ }
+}
+
+uint32_t OutputBuffers::getReorderDepth() const {
+ return mDepth;
+}
+
+void OutputBuffers::setReorderDepth(uint32_t depth) {
+ mPending.splice(mPending.end(), mReorderStash);
+ mDepth = depth;
+}
+
+void OutputBuffers::setReorderKey(C2Config::ordinal_key_t key) {
+ mPending.splice(mPending.end(), mReorderStash);
+ mKey = key;
+}
+
+void OutputBuffers::pushToStash(
+ const std::shared_ptr<C2Buffer>& buffer,
+ bool notify,
+ int64_t timestamp,
+ int32_t flags,
+ const sp<AMessage>& format,
+ const C2WorkOrdinalStruct& ordinal) {
+ bool eos = flags & MediaCodec::BUFFER_FLAG_EOS;
+ if (!buffer && eos) {
+ // TRICKY: we may be violating ordering of the stash here. Because we
+ // don't expect any more emplace() calls after this, the ordering should
+ // not matter.
+ mReorderStash.emplace_back(
+ buffer, notify, timestamp, flags, format, ordinal);
+ } else {
+ flags = flags & ~MediaCodec::BUFFER_FLAG_EOS;
+ auto it = mReorderStash.begin();
+ for (; it != mReorderStash.end(); ++it) {
+ if (less(ordinal, it->ordinal)) {
+ break;
+ }
+ }
+ mReorderStash.emplace(it,
+ buffer, notify, timestamp, flags, format, ordinal);
+ if (eos) {
+ mReorderStash.back().flags =
+ mReorderStash.back().flags | MediaCodec::BUFFER_FLAG_EOS;
+ }
+ }
+ while (!mReorderStash.empty() && mReorderStash.size() > mDepth) {
+ mPending.push_back(mReorderStash.front());
+ mReorderStash.pop_front();
+ }
+ ALOGV("[%s] %s: pushToStash -- pending size = %zu", mName, __func__, mPending.size());
+}
+
+OutputBuffers::BufferAction OutputBuffers::popFromStashAndRegister(
+ std::shared_ptr<C2Buffer>* c2Buffer,
+ size_t* index,
+ sp<MediaCodecBuffer>* outBuffer) {
+ if (mPending.empty()) {
+ return SKIP;
+ }
+
+ // Retrieve the first entry.
+ StashEntry &entry = mPending.front();
+
+ *c2Buffer = entry.buffer;
+ sp<AMessage> outputFormat = entry.format;
+
+ // The output format can be processed without a registered slot.
+ if (outputFormat) {
+ ALOGD("[%s] popFromStashAndRegister: output format changed to %s",
+ mName, outputFormat->debugString().c_str());
+ updateSkipCutBuffer(outputFormat, entry.notify);
+ }
+
+ if (entry.notify) {
+ if (outputFormat) {
+ setFormat(outputFormat);
+ } else if (mUnreportedFormat) {
+ outputFormat = mUnreportedFormat;
+ setFormat(outputFormat);
+ }
+ mUnreportedFormat = nullptr;
+ } else {
+ if (outputFormat) {
+ mUnreportedFormat = outputFormat;
+ } else if (!mUnreportedFormat) {
+ mUnreportedFormat = mFormat;
+ }
+ }
+
+ // Flushing mReorderStash because no other buffers should come after output
+ // EOS.
+ if (entry.flags & MediaCodec::BUFFER_FLAG_EOS) {
+ // Flush reorder stash
+ setReorderDepth(0);
+ }
+
+ if (!entry.notify) {
+ mPending.pop_front();
+ return DISCARD;
+ }
+
+ // Try to register the buffer.
+ status_t err = registerBuffer(*c2Buffer, index, outBuffer);
+ if (err != OK) {
+ if (err != WOULD_BLOCK) {
+ return REALLOCATE;
+ }
+ return RETRY;
+ }
+
+ // Append information from the front stash entry to outBuffer.
+ (*outBuffer)->meta()->setInt64("timeUs", entry.timestamp);
+ (*outBuffer)->meta()->setInt32("flags", entry.flags);
+ ALOGV("[%s] popFromStashAndRegister: "
+ "out buffer index = %zu [%p] => %p + %zu (%lld)",
+ mName, *index, outBuffer->get(),
+ (*outBuffer)->data(), (*outBuffer)->size(),
+ (long long)entry.timestamp);
+
+ // The front entry of mPending will be removed now that the registration
+ // succeeded.
+ mPending.pop_front();
+ return NOTIFY_CLIENT;
+}
+
+bool OutputBuffers::popPending(StashEntry *entry) {
+ if (mPending.empty()) {
+ return false;
+ }
+ *entry = mPending.front();
+ mPending.pop_front();
+ return true;
+}
+
+void OutputBuffers::deferPending(const OutputBuffers::StashEntry &entry) {
+ mPending.push_front(entry);
+}
+
+bool OutputBuffers::hasPending() const {
+ return !mPending.empty();
+}
+
+bool OutputBuffers::less(
+ const C2WorkOrdinalStruct &o1, const C2WorkOrdinalStruct &o2) const {
+ switch (mKey) {
+ case C2Config::ORDINAL: return o1.frameIndex < o2.frameIndex;
+ case C2Config::TIMESTAMP: return o1.timestamp < o2.timestamp;
+ case C2Config::CUSTOM: return o1.customOrdinal < o2.customOrdinal;
+ default:
+ ALOGD("Unrecognized key; default to timestamp");
+ return o1.frameIndex < o2.frameIndex;
+ }
+}
+
// LocalBufferPool
constexpr size_t kInitialPoolCapacity = kMaxLinearBufferSize;
@@ -981,6 +1164,16 @@
mImpl.grow(newSize, mAlloc);
}
+void OutputBuffersArray::transferFrom(OutputBuffers* source) {
+ mFormat = source->mFormat;
+ mSkipCutBuffer = source->mSkipCutBuffer;
+ mUnreportedFormat = source->mUnreportedFormat;
+ mPending = std::move(source->mPending);
+ mReorderStash = std::move(source->mReorderStash);
+ mDepth = source->mDepth;
+ mKey = source->mKey;
+}
+
// FlexOutputBuffers
status_t FlexOutputBuffers::registerBuffer(
@@ -1023,13 +1216,12 @@
// track of the flushed work.
}
-std::unique_ptr<OutputBuffers> FlexOutputBuffers::toArrayMode(size_t size) {
+std::unique_ptr<OutputBuffersArray> FlexOutputBuffers::toArrayMode(size_t size) {
std::unique_ptr<OutputBuffersArray> array(new OutputBuffersArray(mComponentName.c_str()));
- array->setFormat(mFormat);
- array->transferSkipCutBuffer(mSkipCutBuffer);
+ array->transferFrom(this);
std::function<sp<Codec2Buffer>()> alloc = getAlloc();
array->initialize(mImpl, size, alloc);
- return std::move(array);
+ return array;
}
size_t FlexOutputBuffers::numClientBuffers() const {
diff --git a/media/codec2/sfplugin/CCodecBuffers.h b/media/codec2/sfplugin/CCodecBuffers.h
index eec79f1..0d4fa81 100644
--- a/media/codec2/sfplugin/CCodecBuffers.h
+++ b/media/codec2/sfplugin/CCodecBuffers.h
@@ -156,6 +156,8 @@
DISALLOW_EVIL_CONSTRUCTORS(InputBuffers);
};
+class OutputBuffersArray;
+
class OutputBuffers : public CCodecBuffers {
public:
OutputBuffers(const char *componentName, const char *name = "Output");
@@ -163,8 +165,12 @@
/**
* Register output C2Buffer from the component and obtain corresponding
- * index and MediaCodecBuffer object. Returns false if registration
- * fails.
+ * index and MediaCodecBuffer object.
+ *
+ * Returns:
+ * OK if registration succeeds.
+ * NO_MEMORY if all buffers are available but not compatible.
+ * WOULD_BLOCK if there are compatible buffers, but they are all in use.
*/
virtual status_t registerBuffer(
const std::shared_ptr<C2Buffer> &buffer,
@@ -199,7 +205,7 @@
* shall retain the internal state so that it will honor index and
* buffer from previous calls of registerBuffer().
*/
- virtual std::unique_ptr<OutputBuffers> toArrayMode(size_t size) = 0;
+ virtual std::unique_ptr<OutputBuffersArray> toArrayMode(size_t size) = 0;
/**
* Initialize SkipCutBuffer object.
@@ -208,6 +214,164 @@
int32_t delay, int32_t padding, int32_t sampleRate, int32_t channelCount);
/**
+ * Update SkipCutBuffer from format. The @p format must not be null.
+ * @p notify determines whether the format comes with a buffer that should
+ * be reported to the client or not.
+ */
+ void updateSkipCutBuffer(const sp<AMessage> &format, bool notify = true);
+
+ /**
+ * Output Stash
+ * ============
+ *
+ * The output stash is a place to hold output buffers temporarily before
+ * they are registered to output slots. It has 2 main functions:
+ * 1. Allow reordering of output frames as the codec may produce frames in a
+ * different order.
+ * 2. Act as a "buffer" between the codec and the client because the codec
+ * may produce more buffers than available slots. This excess of codec's
+ * output buffers should be registered to slots later, after the client
+ * has released some slots.
+ *
+ * The stash consists of 2 lists of buffers: mPending and mReorderStash.
+ * mPending is a normal FIFO queue with not size limit, while mReorderStash
+ * is a sorted list with size limit mDepth.
+ *
+ * The normal flow of a non-csd output buffer is as follows:
+ *
+ * |----------------OutputBuffers---------------|
+ * |----------Output stash----------| |
+ * Codec --|-> mReorderStash --> mPending --|-> slots --|-> client
+ * | | |
+ * pushToStash() popFromStashAndRegister()
+ *
+ * The buffer that comes from the codec first enters mReorderStash. The
+ * first buffer in mReorderStash gets moved to mPending when mReorderStash
+ * overflows. Buffers in mPending are registered to slots and given to the
+ * client as soon as slots are available.
+ *
+ * Every output buffer that is not a csd buffer should be put on the stash
+ * by calling pushToStash(), then later registered to a slot by calling
+ * popFromStashAndRegister() before notifying the client with
+ * onOutputBufferAvailable().
+ *
+ * Reordering
+ * ==========
+ *
+ * mReorderStash is a sorted list with a specified size limit. The size
+ * limit can be set by calling setReorderDepth().
+ *
+ * Every buffer in mReorderStash has a C2WorkOrdinalStruct, which contains 3
+ * members, all of which are comparable. Which member of C2WorkOrdinalStruct
+ * should be used for reordering can be chosen by calling setReorderKey().
+ */
+
+ /**
+ * Return the reorder depth---the size of mReorderStash.
+ */
+ uint32_t getReorderDepth() const;
+
+ /**
+ * Set the reorder depth.
+ */
+ void setReorderDepth(uint32_t depth);
+
+ /**
+ * Set the type of "key" to use in comparisons.
+ */
+ void setReorderKey(C2Config::ordinal_key_t key);
+
+ /**
+ * Return whether the output stash has any pending buffers.
+ */
+ bool hasPending() const;
+
+ /**
+ * Flush the stash and reset the depth and the key to their default values.
+ */
+ void clearStash();
+
+ /**
+ * Flush the stash.
+ */
+ void flushStash();
+
+ /**
+ * Push a buffer to the reorder stash.
+ *
+ * @param buffer C2Buffer object from the returned work.
+ * @param notify Whether the returned work contains a buffer that should
+ * be reported to the client. This may be false if the
+ * caller wants to process the buffer without notifying the
+ * client.
+ * @param timestamp Buffer timestamp to report to the client.
+ * @param flags Buffer flags to report to the client.
+ * @param format Buffer format to report to the client.
+ * @param ordinal Ordinal used in reordering. This determines when the
+ * buffer will be popped from the output stash by
+ * `popFromStashAndRegister()`.
+ */
+ void pushToStash(
+ const std::shared_ptr<C2Buffer>& buffer,
+ bool notify,
+ int64_t timestamp,
+ int32_t flags,
+ const sp<AMessage>& format,
+ const C2WorkOrdinalStruct& ordinal);
+
+ enum BufferAction : int {
+ SKIP,
+ DISCARD,
+ NOTIFY_CLIENT,
+ REALLOCATE,
+ RETRY,
+ };
+
+ /**
+ * Try to atomically pop the first buffer from the reorder stash and
+ * register it to an output slot. The function returns a value that
+ * indicates a recommended course of action for the caller.
+ *
+ * If the stash is empty, the function will return `SKIP`.
+ *
+ * If the stash is not empty, the function will peek at the first (oldest)
+ * entry in mPending process the buffer in the entry as follows:
+ * - If the buffer should not be sent to the client, the function will
+ * return `DISCARD`. The stash entry will be removed.
+ * - If the buffer should be sent to the client, the function will attempt
+ * to register the buffer to a slot. The registration may have 3 outcomes
+ * corresponding to the following return values:
+ * - `NOTIFY_CLIENT`: The buffer is successfully registered to a slot. The
+ * output arguments @p index and @p outBuffer will contain valid values
+ * that the caller can use to call onOutputBufferAvailable(). The stash
+ * entry will be removed.
+ * - `REALLOCATE`: The buffer is not registered because it is not
+ * compatible with the current slots (which are available). The caller
+ * should reallocate the OutputBuffers with slots that can fit the
+ * returned @p c2Buffer. The stash entry will not be removed
+ * - `RETRY`: All slots are currently occupied by the client. The caller
+ * should try to call this function again after the client has released
+ * some slots.
+ *
+ * @return What the caller should do afterwards.
+ *
+ * @param[out] c2Buffer Underlying C2Buffer associated to the first buffer
+ * on the stash. This value is guaranteed to be valid
+ * unless the return value is `SKIP`.
+ * @param[out] index Slot index. This value is valid only if the return
+ * value is `NOTIFY_CLIENT`.
+ * @param[out] outBuffer Registered buffer. This value is valid only if the
+ * return valu is `NOTIFY_CLIENT`.
+ */
+ BufferAction popFromStashAndRegister(
+ std::shared_ptr<C2Buffer>* c2Buffer,
+ size_t* index,
+ sp<MediaCodecBuffer>* outBuffer);
+
+protected:
+ sp<SkipCutBuffer> mSkipCutBuffer;
+
+ /**
* Update the SkipCutBuffer object. No-op if it's never initialized.
*/
void updateSkipCutBuffer(int32_t sampleRate, int32_t channelCount);
@@ -217,15 +381,8 @@
*/
void submit(const sp<MediaCodecBuffer> &buffer);
- /**
- * Transfer SkipCutBuffer object to the other Buffers object.
- */
- void transferSkipCutBuffer(const sp<SkipCutBuffer> &scb);
-
-protected:
- sp<SkipCutBuffer> mSkipCutBuffer;
-
private:
+ // SkipCutBuffer
int32_t mDelay;
int32_t mPadding;
int32_t mSampleRate;
@@ -233,7 +390,78 @@
void setSkipCutBuffer(int32_t skip, int32_t cut);
+ // Output stash
+
+ // Output format that has not been made available to the client.
+ sp<AMessage> mUnreportedFormat;
+
+ // Struct for an entry in the output stash (mPending and mReorderStash)
+ struct StashEntry {
+ inline StashEntry()
+ : buffer(nullptr),
+ notify(false),
+ timestamp(0),
+ flags(0),
+ format(),
+ ordinal({0, 0, 0}) {}
+ inline StashEntry(
+ const std::shared_ptr<C2Buffer> &b,
+ bool n,
+ int64_t t,
+ int32_t f,
+ const sp<AMessage> &fmt,
+ const C2WorkOrdinalStruct &o)
+ : buffer(b),
+ notify(n),
+ timestamp(t),
+ flags(f),
+ format(fmt),
+ ordinal(o) {}
+ std::shared_ptr<C2Buffer> buffer;
+ bool notify;
+ int64_t timestamp;
+ int32_t flags;
+ sp<AMessage> format;
+ C2WorkOrdinalStruct ordinal;
+ };
+
+ /**
+ * FIFO queue of stash entries.
+ */
+ std::list<StashEntry> mPending;
+ /**
+ * Sorted list of stash entries.
+ */
+ std::list<StashEntry> mReorderStash;
+ /**
+ * Size limit of mReorderStash.
+ */
+ uint32_t mDepth{0};
+ /**
+ * Choice of key to use in ordering of stash entries in mReorderStash.
+ */
+ C2Config::ordinal_key_t mKey{C2Config::ORDINAL};
+
+ /**
+ * Return false if mPending is empty; otherwise, pop the first entry from
+ * mPending and return true.
+ */
+ bool popPending(StashEntry *entry);
+
+ /**
+ * Push an entry as the first entry of mPending.
+ */
+ void deferPending(const StashEntry &entry);
+
+ /**
+ * Comparison of C2WorkOrdinalStruct based on mKey.
+ */
+ bool less(const C2WorkOrdinalStruct &o1,
+ const C2WorkOrdinalStruct &o2) const;
+
DISALLOW_EVIL_CONSTRUCTORS(OutputBuffers);
+
+ friend OutputBuffersArray;
};
/**
@@ -770,7 +998,7 @@
bool isArrayMode() const final { return true; }
- std::unique_ptr<OutputBuffers> toArrayMode(size_t) final {
+ std::unique_ptr<OutputBuffersArray> toArrayMode(size_t) final {
return nullptr;
}
@@ -809,6 +1037,12 @@
*/
void grow(size_t newSize);
+ /**
+ * Transfer the SkipCutBuffer and the output stash from another
+ * OutputBuffers.
+ */
+ void transferFrom(OutputBuffers* source);
+
private:
BuffersArrayImpl mImpl;
std::function<sp<Codec2Buffer>()> mAlloc;
@@ -837,7 +1071,7 @@
void flush(
const std::list<std::unique_ptr<C2Work>> &flushedWork) override;
- std::unique_ptr<OutputBuffers> toArrayMode(size_t size) override;
+ std::unique_ptr<OutputBuffersArray> toArrayMode(size_t size) override;
size_t numClientBuffers() const final;
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index dd84511..604d182 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -639,9 +639,17 @@
status_t AudioTrack::start()
{
- const int64_t beginNs = systemTime();
AutoMutex lock(mLock);
+ if (mState == STATE_ACTIVE) {
+ return INVALID_OPERATION;
+ }
+
+ ALOGV("%s(%d): prior state:%s", __func__, mPortId, stateToString(mState));
+
+ // Defer logging here due to OpenSL ES repeated start calls.
+ // TODO(b/154868033) after fix, restore this logging back to the beginning of start().
+ const int64_t beginNs = systemTime();
status_t status = NO_ERROR; // logged: make sure to set this before returning.
mediametrics::Defer defer([&] {
mediametrics::LogItem(mMetricsId)
@@ -655,12 +663,6 @@
.set(AMEDIAMETRICS_PROP_STATUS, (int32_t)status)
.record(); });
- ALOGV("%s(%d): prior state:%s", __func__, mPortId, stateToString(mState));
-
- if (mState == STATE_ACTIVE) {
- status = INVALID_OPERATION;
- return status;
- }
mInUnderrun = true;
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index a8fea90..3e49bae 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -2,10 +2,21 @@
name: "libstagefright_headers",
export_include_dirs: ["include"],
vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ "com.android.media.swcodec",
+ ],
+ min_sdk_version: "29",
}
cc_library_static {
name: "libstagefright_esds",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ ],
+ min_sdk_version: "29",
srcs: ["ESDS.cpp"],
@@ -27,6 +38,11 @@
cc_library_static {
name: "libstagefright_metadatautils",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ ],
+ min_sdk_version: "29",
srcs: ["MetaDataUtils.cpp"],
@@ -94,6 +110,11 @@
cc_library_static {
name: "libstagefright_mpeg2extractor",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ ],
+ min_sdk_version: "29",
srcs: [
"Utils.cpp",
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index 7fb5455..b90e6bd 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -3036,20 +3036,21 @@
int32_t async = 0;
if (msg->findInt32("async", &async) && async) {
- if ((mState == CONFIGURED || mState == STARTED || mState == FLUSHED)
- && mSurface != NULL) {
+ if (mSurface != NULL) {
if (!mReleaseSurface) {
mReleaseSurface.reset(new ReleaseSurface);
}
- status_t err = connectToSurface(mReleaseSurface->getSurface());
- ALOGW_IF(err != OK, "error connecting to release surface: err = %d", err);
- if (err == OK && !(mFlags & kFlagUsesSoftwareRenderer)) {
- err = mCodec->setSurface(mReleaseSurface->getSurface());
- ALOGW_IF(err != OK, "error setting release surface: err = %d", err);
- }
- if (err == OK) {
- (void)disconnectFromSurface();
- mSurface = mReleaseSurface->getSurface();
+ if (mSurface != mReleaseSurface->getSurface()) {
+ status_t err = connectToSurface(mReleaseSurface->getSurface());
+ ALOGW_IF(err != OK, "error connecting to release surface: err = %d", err);
+ if (err == OK && !(mFlags & kFlagUsesSoftwareRenderer)) {
+ err = mCodec->setSurface(mReleaseSurface->getSurface());
+ ALOGW_IF(err != OK, "error setting release surface: err = %d", err);
+ }
+ if (err == OK) {
+ (void)disconnectFromSurface();
+ mSurface = mReleaseSurface->getSurface();
+ }
}
}
}
diff --git a/media/libstagefright/codecs/m4v_h263/dec/Android.bp b/media/libstagefright/codecs/m4v_h263/dec/Android.bp
index c67dc2b..4303565 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/Android.bp
+++ b/media/libstagefright/codecs/m4v_h263/dec/Android.bp
@@ -1,6 +1,11 @@
cc_library_static {
name: "libstagefright_m4vh263dec",
vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media.swcodec",
+ ],
+ min_sdk_version: "29",
shared_libs: ["liblog"],
srcs: [
diff --git a/media/libstagefright/codecs/m4v_h263/enc/Android.bp b/media/libstagefright/codecs/m4v_h263/enc/Android.bp
index 846f614..b8bc24e 100644
--- a/media/libstagefright/codecs/m4v_h263/enc/Android.bp
+++ b/media/libstagefright/codecs/m4v_h263/enc/Android.bp
@@ -1,6 +1,11 @@
cc_library_static {
name: "libstagefright_m4vh263enc",
vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media.swcodec",
+ ],
+ min_sdk_version: "29",
srcs: [
"src/bitstream_io.cpp",
diff --git a/media/ndk/Android.bp b/media/ndk/Android.bp
index a04a962..a2f8230 100644
--- a/media/ndk/Android.bp
+++ b/media/ndk/Android.bp
@@ -35,6 +35,12 @@
cc_library_headers {
name: "media_ndk_headers",
vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ "com.android.media.swcodec",
+ ],
+ min_sdk_version: "29",
export_include_dirs: ["include"]
}
diff --git a/services/audioflinger/TrackBase.h b/services/audioflinger/TrackBase.h
index 15c66fb..01d5345 100644
--- a/services/audioflinger/TrackBase.h
+++ b/services/audioflinger/TrackBase.h
@@ -373,10 +373,15 @@
bool mIsInvalid; // non-resettable latch, set by invalidate()
// It typically takes 5 threadloop mix iterations for latency to stabilize.
- static inline constexpr int32_t LOG_START_COUNTDOWN = 8;
- int32_t mLogStartCountdown = 0;
- int64_t mLogStartTimeNs = 0;
- int64_t mLogStartFrames = 0;
+ // However, this can be 12+ iterations for BT.
+ // To be sure, we wait for latency to dip (it usually increases at the start)
+ // to assess stability and then log to MediaMetrics.
+ // Rapid start / pause calls may cause inaccurate numbers.
+ static inline constexpr int32_t LOG_START_COUNTDOWN = 12;
+ int32_t mLogStartCountdown = 0; // Mixer period countdown
+ int64_t mLogStartTimeNs = 0; // Monotonic time at start()
+ int64_t mLogStartFrames = 0; // Timestamp frames at start()
+ double mLogLatencyMs = 0.; // Track the last log latency
TrackMetrics mTrackMetrics;
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index 73a40d3..9386a42 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -978,7 +978,8 @@
mLogStartCountdown = LOG_START_COUNTDOWN;
mLogStartTimeNs = systemTime();
mLogStartFrames = mAudioTrackServerProxy->getTimestamp()
- .mPosition[ExtendedTimestamp::LOCATION_SERVER];
+ .mPosition[ExtendedTimestamp::LOCATION_KERNEL];
+ mLogLatencyMs = 0.;
}
if (status == NO_ERROR || status == ALREADY_EXISTS) {
@@ -1514,23 +1515,31 @@
mServerLatencyFromTrack.store(useTrackTimestamp);
mServerLatencyMs.store(latencyMs);
- if (mLogStartCountdown > 0) {
- if (--mLogStartCountdown == 0) {
+ if (mLogStartCountdown > 0
+ && local.mTimeNs[ExtendedTimestamp::LOCATION_KERNEL] > 0
+ && local.mPosition[ExtendedTimestamp::LOCATION_KERNEL] > 0)
+ {
+ if (mLogStartCountdown > 1) {
+ --mLogStartCountdown;
+ } else if (latencyMs < mLogLatencyMs) { // wait for latency to stabilize (dip)
+ mLogStartCountdown = 0;
// startup is the difference in times for the current timestamp and our start
double startUpMs =
- (local.mTimeNs[ExtendedTimestamp::LOCATION_SERVER] - mLogStartTimeNs) * 1e-6;
+ (local.mTimeNs[ExtendedTimestamp::LOCATION_KERNEL] - mLogStartTimeNs) * 1e-6;
// adjust for frames played.
- startUpMs -= (local.mPosition[ExtendedTimestamp::LOCATION_SERVER] - mLogStartFrames)
- * 1e3 / mSampleRate;
- ALOGV("%s: logging localTime:%lld, startTime:%lld"
- " localPosition:%lld, startPosition:%lld",
- __func__,
- (long long)local.mTimeNs[ExtendedTimestamp::LOCATION_SERVER],
+ startUpMs -= (local.mPosition[ExtendedTimestamp::LOCATION_KERNEL] - mLogStartFrames)
+ * 1e3 / mSampleRate;
+ ALOGV("%s: latencyMs:%lf startUpMs:%lf"
+ " localTime:%lld startTime:%lld"
+ " localPosition:%lld startPosition:%lld",
+ __func__, latencyMs, startUpMs,
+ (long long)local.mTimeNs[ExtendedTimestamp::LOCATION_KERNEL],
(long long)mLogStartTimeNs,
- (long long)local.mPosition[ExtendedTimestamp::LOCATION_SERVER],
+ (long long)local.mPosition[ExtendedTimestamp::LOCATION_KERNEL],
(long long)mLogStartFrames);
mTrackMetrics.logLatencyAndStartup(latencyMs, startUpMs);
}
+ mLogLatencyMs = latencyMs;
}
}
diff --git a/services/mediametrics/Android.bp b/services/mediametrics/Android.bp
index 645d151..f819f1b 100644
--- a/services/mediametrics/Android.bp
+++ b/services/mediametrics/Android.bp
@@ -119,6 +119,7 @@
srcs: [
"AudioAnalytics.cpp",
"AudioPowerUsage.cpp",
+ "AudioTypes.cpp",
"iface_statsd.cpp",
"MediaMetricsService.cpp",
"statsd_audiopolicy.cpp",
@@ -130,6 +131,7 @@
"statsd_extractor.cpp",
"statsd_nuplayer.cpp",
"statsd_recorder.cpp",
+ "StringUtils.cpp"
],
proto: {
@@ -137,9 +139,11 @@
},
shared_libs: [
+ "libbase", // android logging
"libbinder",
"libcutils",
"liblog",
+ "libmedia_helper",
"libmediametrics",
"libmediautils",
"libmemunreachable",
diff --git a/services/mediametrics/AudioAnalytics.cpp b/services/mediametrics/AudioAnalytics.cpp
index 31ad234..800f099 100644
--- a/services/mediametrics/AudioAnalytics.cpp
+++ b/services/mediametrics/AudioAnalytics.cpp
@@ -16,20 +16,45 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "AudioAnalytics"
+#include <android-base/logging.h>
#include <utils/Log.h>
#include "AudioAnalytics.h"
-#include "MediaMetricsService.h" // package info
+
#include <audio_utils/clock.h> // clock conversions
+#include <cutils/properties.h>
#include <statslog.h> // statsd
+#include "AudioTypes.h" // string to int conversions
+#include "MediaMetricsService.h" // package info
+#include "StringUtils.h"
+
+#define PROP_AUDIO_ANALYTICS_CLOUD_ENABLED "persist.audio.analytics.cloud.enabled"
+
// Enable for testing of delivery to statsd
-// #define STATSD
+//#define STATSD
+
+// Transmit to statsd in integer or strings
+//#define USE_INT
+
+#ifdef USE_INT
+using short_enum_type_t = int32_t;
+using long_enum_type_t = int64_t;
+#define ENUM_EXTRACT(x) (x)
+#else
+using short_enum_type_t = std::string;
+using long_enum_type_t = std::string;
+#define ENUM_EXTRACT(x) (x).c_str()
+#endif
+
+using android::base::DEBUG;
namespace android::mediametrics {
AudioAnalytics::AudioAnalytics()
+ : mDeliverStatistics(property_get_bool(PROP_AUDIO_ANALYTICS_CLOUD_ENABLED, true))
{
+ SetMinimumLogSeverity(DEBUG); // for LOG().
ALOGD("%s", __func__);
// Add action to save AnalyticsState if audioserver is restarted.
@@ -243,33 +268,47 @@
int32_t frameCount = 0;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_FRAMECOUNT, &frameCount);
- std::string inputDevices;
+ std::string inputDevicePairs;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
- key, AMEDIAMETRICS_PROP_INPUTDEVICES, &inputDevices);
+ key, AMEDIAMETRICS_PROP_INPUTDEVICES, &inputDevicePairs);
int32_t intervalCount = 0;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_INTERVALCOUNT, &intervalCount);
- std::string outputDevices;
+ std::string outputDevicePairs;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
- key, AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices);
+ key, AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevicePairs);
int32_t sampleRate = 0;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_SAMPLERATE, &sampleRate);
std::string flags;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_FLAGS, &flags);
+
// We may have several devices.
- // Strings allow us to mix input and output devices together.
- // TODO: review if we want to separate them.
- std::stringstream ss;
- for (const auto& devicePairs : { outputDevices, inputDevices }) {
- const auto devaddrvec = MediaMetricsService::getDeviceAddressPairs(devicePairs);
+ // Accumulate the bit flags for input and output devices.
+ std::stringstream oss;
+ long_enum_type_t outputDeviceBits{};
+ { // compute outputDevices
+ const auto devaddrvec = stringutils::getDeviceAddressPairs(outputDevicePairs);
for (const auto& [device, addr] : devaddrvec) {
- if (ss.tellp() > 0) ss << "|"; // delimit devices with '|'.
- ss << device;
+ if (oss.tellp() > 0) oss << "|"; // delimit devices with '|'.
+ oss << device;
+ outputDeviceBits += types::lookup<types::OUTPUT_DEVICE, long_enum_type_t>(device);
}
}
- std::string devices = ss.str();
+ const std::string outputDevices = oss.str();
+
+ std::stringstream iss;
+ long_enum_type_t inputDeviceBits{};
+ { // compute inputDevices
+ const auto devaddrvec = stringutils::getDeviceAddressPairs(inputDevicePairs);
+ for (const auto& [device, addr] : devaddrvec) {
+ if (iss.tellp() > 0) iss << "|"; // delimit devices with '|'.
+ iss << device;
+ inputDeviceBits += types::lookup<types::INPUT_DEVICE, long_enum_type_t>(device);
+ }
+ }
+ const std::string inputDevices = iss.str();
// Get connected device name if from bluetooth.
bool isBluetooth = false;
@@ -278,8 +317,8 @@
isBluetooth = true;
mAudioAnalytics.mAnalyticsState->timeMachine().get(
"audio.device.bt_a2dp", AMEDIAMETRICS_PROP_NAME, &deviceNames);
- // We don't check if deviceName is sanitized.
- // TODO: remove reserved chars such as '|' and replace with a char like '_'.
+ // Remove | if present
+ stringutils::replace(deviceNames, "|", '?');
}
switch (itemType) {
@@ -305,37 +344,43 @@
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_SOURCE, &source);
- ALOGD("(key=%s) id:%s endAudioIntervalGroup devices:%s deviceNames:%s "
- "deviceTimeNs:%lld encoding:%s frameCount:%d intervalCount:%d "
- "sampleRate:%d "
- "packageName:%s "
- "selectedDeviceId:%d "
- "callerName:%s source:%s",
- key.c_str(), id.c_str(), devices.c_str(), deviceNames.c_str(),
- (long long)deviceTimeNs, encoding.c_str(), frameCount, intervalCount,
- sampleRate,
- packageName.c_str(), selectedDeviceId,
- callerName.c_str(), source.c_str());
+ const auto callerNameForStats =
+ types::lookup<types::CALLER_NAME, short_enum_type_t>(callerName);
+ const auto encodingForStats = types::lookup<types::ENCODING, short_enum_type_t>(encoding);
+ const auto flagsForStats = types::lookup<types::INPUT_FLAG, short_enum_type_t>(flags);
+ const auto sourceForStats = types::lookup<types::SOURCE_TYPE, short_enum_type_t>(source);
+ LOG(DEBUG) << "key:" << key
+ << " id:" << id
+ << " inputDevices:" << inputDevices << "(" << inputDeviceBits
+ << ") deviceNames:" << deviceNames
+ << " deviceTimeNs:" << deviceTimeNs
+ << " encoding:" << encoding << "(" << encodingForStats
+ << ") frameCount:" << frameCount
+ << " intervalCount:" << intervalCount
+ << " sampleRate:" << sampleRate
+ << " flags:" << flags << "(" << flagsForStats
+ << ") packageName:" << packageName
+ << " selectedDeviceId:" << selectedDeviceId
+ << " callerName:" << callerName << "(" << callerNameForStats
+ << ") source:" << source << "(" << sourceForStats << ")";
#ifdef STATSD
if (mAudioAnalytics.mDeliverStatistics) {
(void)android::util::stats_write(
android::util::MEDIAMETRICS_AUDIORECORDDEVICEUSAGE_REPORTED
- /* timestamp, */
- /* mediaApexVersion, */
- , devices.c_str()
+ , ENUM_EXTRACT(inputDeviceBits)
, deviceNames.c_str()
, deviceTimeNs
- , encoding.c_str()
+ , ENUM_EXTRACT(encodingForStats)
, frameCount
, intervalCount
, sampleRate
- , flags.c_str()
+ , ENUM_EXTRACT(flagsForStats)
, packageName.c_str()
, selectedDeviceId
- , callerName.c_str()
- , source.c_str()
+ , ENUM_EXTRACT(callerNameForStats)
+ , ENUM_EXTRACT(sourceForStats)
);
}
#endif
@@ -347,31 +392,43 @@
int32_t underrun = 0; // zero for record types
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_UNDERRUN, &underrun);
- ALOGD("(key=%s) id:%s endAudioIntervalGroup devices:%s deviceNames:%s "
- "deviceTimeNs:%lld encoding:%s frameCount:%d intervalCount:%d "
- "sampleRate:%d underrun:%d "
- "flags:%s type:%s",
- key.c_str(), id.c_str(), devices.c_str(), deviceNames.c_str(),
- (long long)deviceTimeNs, encoding.c_str(), frameCount, intervalCount,
- sampleRate, underrun,
- flags.c_str(), type.c_str());
+
+ const bool isInput = types::isInputThreadType(type);
+ const auto encodingForStats = types::lookup<types::ENCODING, short_enum_type_t>(encoding);
+ const auto flagsForStats =
+ (isInput ? types::lookup<types::INPUT_FLAG, short_enum_type_t>(flags)
+ : types::lookup<types::OUTPUT_FLAG, short_enum_type_t>(flags));
+ const auto typeForStats = types::lookup<types::THREAD_TYPE, short_enum_type_t>(type);
+
+ LOG(DEBUG) << "key:" << key
+ << " id:" << id
+ << " inputDevices:" << inputDevices << "(" << inputDeviceBits
+ << ") outputDevices:" << outputDevices << "(" << outputDeviceBits
+ << ") deviceNames:" << deviceNames
+ << " deviceTimeNs:" << deviceTimeNs
+ << " encoding:" << encoding << "(" << encodingForStats
+ << ") frameCount:" << frameCount
+ << " intervalCount:" << intervalCount
+ << " sampleRate:" << sampleRate
+ << " underrun:" << underrun
+ << " flags:" << flags << "(" << flagsForStats
+ << ") type:" << type << "(" << typeForStats
+ << ")";
#ifdef STATSD
if (mAudioAnalytics.mDeliverStatistics) {
(void)android::util::stats_write(
android::util::MEDIAMETRICS_AUDIOTHREADDEVICEUSAGE_REPORTED
- /* timestamp, */
- /* mediaApexVersion, */
- , devices.c_str()
+ , ENUM_EXTRACT(inputDeviceBits)
+ , ENUM_EXTRACT(outputDeviceBits)
, deviceNames.c_str()
, deviceTimeNs
- , encoding.c_str()
+ , ENUM_EXTRACT(encodingForStats)
, frameCount
, intervalCount
, sampleRate
- , flags.c_str()
-
+ , ENUM_EXTRACT(flagsForStats)
, underrun
- , type.c_str()
+ , ENUM_EXTRACT(typeForStats)
);
}
#endif
@@ -420,34 +477,51 @@
mAudioAnalytics.mAnalyticsState->timeMachine().get(
key, AMEDIAMETRICS_PROP_USAGE, &usage);
- ALOGD("(key=%s) id:%s endAudioIntervalGroup devices:%s deviceNames:%s "
- "deviceTimeNs:%lld encoding:%s frameCount:%d intervalCount:%d "
- "sampleRate:%d underrun:%d "
- "callerName:%s contentType:%s "
- "deviceLatencyMs:%lf deviceStartupMs:%lf deviceVolume:%lf "
- "packageName:%s playbackPitch:%lf playbackSpeed:%lf "
- "selectedDeviceId:%d streamType:%s usage:%s",
- key.c_str(), id.c_str(), devices.c_str(), deviceNames.c_str(),
- (long long)deviceTimeNs, encoding.c_str(), frameCount, intervalCount,
- sampleRate, underrun,
- callerName.c_str(), contentType.c_str(),
- deviceLatencyMs, deviceStartupMs, deviceVolume,
- packageName.c_str(), playbackPitch, playbackSpeed,
- selectedDeviceId, streamType.c_str(), usage.c_str());
+ const auto callerNameForStats =
+ types::lookup<types::CALLER_NAME, short_enum_type_t>(callerName);
+ const auto contentTypeForStats =
+ types::lookup<types::CONTENT_TYPE, short_enum_type_t>(contentType);
+ const auto encodingForStats = types::lookup<types::ENCODING, short_enum_type_t>(encoding);
+ const auto flagsForStats = types::lookup<types::OUTPUT_FLAG, short_enum_type_t>(flags);
+ const auto streamTypeForStats =
+ types::lookup<types::STREAM_TYPE, short_enum_type_t>(streamType);
+ const auto usageForStats = types::lookup<types::USAGE, short_enum_type_t>(usage);
+
+ LOG(DEBUG) << "key:" << key
+ << " id:" << id
+ << " outputDevices:" << outputDevices << "(" << outputDeviceBits
+ << ") deviceNames:" << deviceNames
+ << " deviceTimeNs:" << deviceTimeNs
+ << " encoding:" << encoding << "(" << encodingForStats
+ << ") frameCount:" << frameCount
+ << " intervalCount:" << intervalCount
+ << " sampleRate:" << sampleRate
+ << " underrun:" << underrun
+ << " flags:" << flags << "(" << flagsForStats
+ << ") callerName:" << callerName << "(" << callerNameForStats
+ << ") contentType:" << contentType << "(" << contentTypeForStats
+ << ") deviceLatencyMs:" << deviceLatencyMs
+ << " deviceStartupMs:" << deviceStartupMs
+ << " deviceVolume:" << deviceVolume
+ << " packageName:" << packageName
+ << " playbackPitch:" << playbackPitch
+ << " playbackSpeed:" << playbackSpeed
+ << " selectedDeviceId:" << selectedDeviceId
+ << " streamType:" << streamType << "(" << streamTypeForStats
+ << ") usage:" << usage << "(" << usageForStats
+ << ")";
#ifdef STATSD
if (mAudioAnalytics.mDeliverStatistics) {
(void)android::util::stats_write(
android::util::MEDIAMETRICS_AUDIOTRACKDEVICEUSAGE_REPORTED
- /* timestamp, */
- /* mediaApexVersion, */
- , devices.c_str()
+ , ENUM_EXTRACT(outputDeviceBits)
, deviceNames.c_str()
, deviceTimeNs
- , encoding.c_str()
+ , ENUM_EXTRACT(encodingForStats)
, frameCount
, intervalCount
, sampleRate
- , flags.c_str()
+ , ENUM_EXTRACT(flagsForStats)
, underrun
, packageName.c_str()
@@ -455,10 +529,10 @@
, (float)deviceStartupMs
, (float)deviceVolume
, selectedDeviceId
- , streamType.c_str()
- , usage.c_str()
- , contentType.c_str()
- , callerName.c_str()
+ , ENUM_EXTRACT(streamTypeForStats)
+ , ENUM_EXTRACT(usageForStats)
+ , ENUM_EXTRACT(contentTypeForStats)
+ , ENUM_EXTRACT(callerNameForStats)
);
}
#endif
@@ -490,7 +564,6 @@
item->get(AMEDIAMETRICS_PROP_NAME, &name);
ALOGD("(key=%s) a2dp connected device:%s atNs:%lld",
key.c_str(), name.c_str(), (long long)atNs);
-
}
void AudioAnalytics::DeviceConnection::createPatch(
@@ -502,27 +575,36 @@
item->get(AMEDIAMETRICS_PROP_OUTPUTDEVICES, &outputDevices);
if (outputDevices.find("AUDIO_DEVICE_OUT_BLUETOOTH_A2DP") != std::string::npos) {
// TODO compare address
- int64_t timeDiff = item->getTimestamp();
+ int64_t timeDiffNs = item->getTimestamp();
if (mA2dpConnectionRequestNs == 0) {
ALOGD("%s: A2DP create patch didn't see a connection request", __func__);
- timeDiff -= mA2dpConnectionServiceNs;
+ timeDiffNs -= mA2dpConnectionServiceNs;
} else {
- timeDiff -= mA2dpConnectionRequestNs;
+ timeDiffNs -= mA2dpConnectionRequestNs;
}
- ALOGD("(key=%s) A2DP device connection time: %lld", key.c_str(), (long long)timeDiff);
+
mA2dpConnectionRequestNs = 0;
mA2dpConnectionServiceNs = 0;
++mA2dpConnectionSuccesses;
+ const auto connectionTimeMs = float(timeDiffNs * 1e-6);
+
+ const auto outputDeviceBits = types::lookup<types::OUTPUT_DEVICE, long_enum_type_t>(
+ "AUDIO_DEVICE_OUT_BLUETOOTH_A2DP");
+
+ LOG(DEBUG) << "key:" << key
+ << " A2DP SUCCESS"
+ << " outputDevices:" << outputDeviceBits
+ << " connectionTimeMs:" << connectionTimeMs;
#ifdef STATSD
if (mAudioAnalytics.mDeliverStatistics) {
+ const long_enum_type_t inputDeviceBits{};
(void)android::util::stats_write(
android::util::MEDIAMETRICS_AUDIODEVICECONNECTION_REPORTED
- /* timestamp, */
- /* mediaApexVersion, */
- , "AUDIO_DEVICE_OUT_BLUETOOTH_A2DP"
- , android::util::MEDIAMETRICS_AUDIO_DEVICE_CONNECTION_REPORTED__RESULT__SUCCESS
- , /* connection_time_ms */ timeDiff * 1e-6 /* NS to MS */
+ , ENUM_EXTRACT(inputDeviceBits)
+ , ENUM_EXTRACT(outputDeviceBits)
+ , types::DEVICE_CONNECTION_RESULT_SUCCESS
+ , connectionTimeMs
, /* connection_count */ 1
);
}
@@ -552,18 +634,25 @@
void AudioAnalytics::DeviceConnection::expire() {
std::lock_guard l(mLock);
if (mA2dpConnectionRequestNs == 0) return; // ignore (this was an internal connection).
+
+#ifdef STATSD
+ const long_enum_type_t inputDeviceBits{};
+#endif
+ const auto outputDeviceBits = types::lookup<types::OUTPUT_DEVICE, long_enum_type_t>(
+ "AUDIO_DEVICE_OUT_BLUETOOTH_A2DP");
+
if (mA2dpConnectionServiceNs == 0) {
- ALOGD("A2DP device connection service cancels");
++mA2dpConnectionJavaServiceCancels; // service did not connect to A2DP
+ LOG(DEBUG) << "A2DP CANCEL"
+ << " outputDevices:" << outputDeviceBits;
#ifdef STATSD
if (mAudioAnalytics.mDeliverStatistics) {
(void)android::util::stats_write(
android::util::MEDIAMETRICS_AUDIODEVICECONNECTION_REPORTED
- /* timestamp, */
- /* mediaApexVersion, */
- , "AUDIO_DEVICE_OUT_BLUETOOTH_A2DP"
- , android::util::MEDIAMETRICS_AUDIO_DEVICE_CONNECTION_REPORTED__RESULT__JAVA_SERVICE_CANCEL
+ , ENUM_EXTRACT(inputDeviceBits)
+ , ENUM_EXTRACT(outputDeviceBits)
+ , types::DEVICE_CONNECTION_RESULT_JAVA_SERVICE_CANCEL
, /* connection_time_ms */ 0.f
, /* connection_count */ 1
);
@@ -575,18 +664,19 @@
// AudioFlinger didn't play - an expiration may occur because there is no audio playing.
// Should we check elsewhere?
// TODO: disambiguate this case.
- ALOGD("A2DP device connection expired, state unknown");
mA2dpConnectionRequestNs = 0;
mA2dpConnectionServiceNs = 0;
++mA2dpConnectionUnknowns; // connection result unknown
+
+ LOG(DEBUG) << "A2DP UNKNOWN"
+ << " outputDevices:" << outputDeviceBits;
#ifdef STATSD
if (mAudioAnalytics.mDeliverStatistics) {
(void)android::util::stats_write(
android::util::MEDIAMETRICS_AUDIODEVICECONNECTION_REPORTED
- /* timestamp, */
- /* mediaApexVersion, */
- , "AUDIO_DEVICE_OUT_BLUETOOTH_A2DP"
- , android::util::MEDIAMETRICS_AUDIO_DEVICE_CONNECTION_REPORTED__RESULT__UNKNOWN
+ , ENUM_EXTRACT(inputDeviceBits)
+ , ENUM_EXTRACT(outputDeviceBits)
+ , types::DEVICE_CONNECTION_RESULT_UNKNOWN
, /* connection_time_ms */ 0.f
, /* connection_count */ 1
);
diff --git a/services/mediametrics/AudioAnalytics.h b/services/mediametrics/AudioAnalytics.h
index 9089d6f..138ddcc 100644
--- a/services/mediametrics/AudioAnalytics.h
+++ b/services/mediametrics/AudioAnalytics.h
@@ -109,7 +109,7 @@
*/
std::string getThreadFromTrack(const std::string& track) const;
- const bool mDeliverStatistics __unused = true;
+ const bool mDeliverStatistics;
// Actions is individually locked
AnalyticsActions mActions;
diff --git a/services/mediametrics/AudioPowerUsage.cpp b/services/mediametrics/AudioPowerUsage.cpp
index b1648d9..c441110 100644
--- a/services/mediametrics/AudioPowerUsage.cpp
+++ b/services/mediametrics/AudioPowerUsage.cpp
@@ -20,6 +20,7 @@
#include "AudioAnalytics.h"
#include "MediaMetricsService.h"
+#include "StringUtils.h"
#include <map>
#include <sstream>
#include <string>
@@ -117,7 +118,7 @@
int32_t AudioPowerUsage::deviceFromStringPairs(const std::string& device_strings) {
int32_t deviceMask = 0;
- const auto devaddrvec = MediaMetricsService::getDeviceAddressPairs(device_strings);
+ const auto devaddrvec = stringutils::getDeviceAddressPairs(device_strings);
for (const auto &[device, addr] : devaddrvec) {
int32_t combo_device = 0;
deviceFromString(device, combo_device);
diff --git a/services/mediametrics/AudioTypes.cpp b/services/mediametrics/AudioTypes.cpp
new file mode 100644
index 0000000..2a2dbaf
--- /dev/null
+++ b/services/mediametrics/AudioTypes.cpp
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "AudioTypes.h"
+#include "StringUtils.h"
+#include <media/TypeConverter.h> // requires libmedia_helper to get the Audio code.
+
+namespace android::mediametrics::types {
+
+std::unordered_map<std::string, int64_t>& getAudioDeviceInMap() {
+ // DO NOT MODIFY VALUES (OK to add new ones). This does NOT match audio_device_t.
+ static std::unordered_map<std::string, int64_t> map{
+ {"AUDIO_DEVICE_IN_COMMUNICATION", 1LL << 0},
+ {"AUDIO_DEVICE_IN_AMBIENT", 1LL << 1},
+ {"AUDIO_DEVICE_IN_BUILTIN_MIC", 1LL << 2},
+ {"AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET", 1LL << 3},
+ {"AUDIO_DEVICE_IN_WIRED_HEADSET", 1LL << 4},
+ {"AUDIO_DEVICE_IN_AUX_DIGITAL", 1LL << 5},
+ {"AUDIO_DEVICE_IN_HDMI", 1LL << 5}, // HDMI == AUX_DIGITAL (6 reserved)
+ {"AUDIO_DEVICE_IN_VOICE_CALL", 1LL << 7},
+ {"AUDIO_DEVICE_IN_TELEPHONY_RX", 1LL << 7}, // TELEPHONY_RX == VOICE_CALL (8 reserved)
+ {"AUDIO_DEVICE_IN_BACK_MIC", 1LL << 9},
+ {"AUDIO_DEVICE_IN_REMOTE_SUBMIX", 1LL << 10},
+ {"AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET", 1LL << 11},
+ {"AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET", 1LL << 12},
+ {"AUDIO_DEVICE_IN_USB_ACCESSORY", 1LL << 13},
+ {"AUDIO_DEVICE_IN_USB_DEVICE", 1LL << 14},
+ {"AUDIO_DEVICE_IN_FM_TUNER", 1LL << 15},
+ {"AUDIO_DEVICE_IN_TV_TUNER", 1LL << 16},
+ {"AUDIO_DEVICE_IN_LINE", 1LL << 17},
+ {"AUDIO_DEVICE_IN_SPDIF", 1LL << 18},
+ {"AUDIO_DEVICE_IN_BLUETOOTH_A2DP", 1LL << 19},
+ {"AUDIO_DEVICE_IN_LOOPBACK", 1LL << 20},
+ {"AUDIO_DEVICE_IN_IP", 1LL << 21},
+ {"AUDIO_DEVICE_IN_BUS", 1LL << 22},
+ {"AUDIO_DEVICE_IN_PROXY", 1LL << 23},
+ {"AUDIO_DEVICE_IN_USB_HEADSET", 1LL << 24},
+ {"AUDIO_DEVICE_IN_BLUETOOTH_BLE", 1LL << 25},
+ {"AUDIO_DEVICE_IN_HDMI_ARC", 1LL << 26},
+ {"AUDIO_DEVICE_IN_ECHO_REFERENCE", 1LL << 27},
+ {"AUDIO_DEVICE_IN_DEFAULT", 1LL << 28},
+ // R values above.
+ };
+ return map;
+}
+
+std::unordered_map<std::string, int64_t>& getAudioDeviceOutMap() {
+ // DO NOT MODIFY VALUES (OK to add new ones). This does NOT match audio_device_t.
+ static std::unordered_map<std::string, int64_t> map{
+ {"AUDIO_DEVICE_OUT_EARPIECE", 1LL << 0},
+ {"AUDIO_DEVICE_OUT_SPEAKER", 1LL << 1},
+ {"AUDIO_DEVICE_OUT_WIRED_HEADSET", 1LL << 2},
+ {"AUDIO_DEVICE_OUT_WIRED_HEADPHONE", 1LL << 3},
+ {"AUDIO_DEVICE_OUT_BLUETOOTH_SCO", 1LL << 4},
+ {"AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET", 1LL << 5},
+ {"AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT", 1LL << 6},
+ {"AUDIO_DEVICE_OUT_BLUETOOTH_A2DP", 1LL << 7},
+ {"AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES", 1LL << 8},
+ {"AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER", 1LL << 9},
+ {"AUDIO_DEVICE_OUT_AUX_DIGITAL", 1LL << 10},
+ {"AUDIO_DEVICE_OUT_HDMI", 1LL << 10}, // HDMI == AUX_DIGITAL (11 reserved)
+ {"AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET", 1LL << 12},
+ {"AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET", 1LL << 13},
+ {"AUDIO_DEVICE_OUT_USB_ACCESSORY", 1LL << 14},
+ {"AUDIO_DEVICE_OUT_USB_DEVICE", 1LL << 15},
+ {"AUDIO_DEVICE_OUT_REMOTE_SUBMIX", 1LL << 16},
+ {"AUDIO_DEVICE_OUT_TELEPHONY_TX", 1LL << 17},
+ {"AUDIO_DEVICE_OUT_LINE", 1LL << 18},
+ {"AUDIO_DEVICE_OUT_HDMI_ARC", 1LL << 19},
+ {"AUDIO_DEVICE_OUT_SPDIF", 1LL << 20},
+ {"AUDIO_DEVICE_OUT_FM", 1LL << 21},
+ {"AUDIO_DEVICE_OUT_AUX_LINE", 1LL << 22},
+ {"AUDIO_DEVICE_OUT_SPEAKER_SAFE", 1LL << 23},
+ {"AUDIO_DEVICE_OUT_IP", 1LL << 24},
+ {"AUDIO_DEVICE_OUT_BUS", 1LL << 25},
+ {"AUDIO_DEVICE_OUT_PROXY", 1LL << 26},
+ {"AUDIO_DEVICE_OUT_USB_HEADSET", 1LL << 27},
+ {"AUDIO_DEVICE_OUT_HEARING_AID", 1LL << 28},
+ {"AUDIO_DEVICE_OUT_ECHO_CANCELLER", 1LL << 29},
+ {"AUDIO_DEVICE_OUT_DEFAULT", 1LL << 30},
+ // R values above.
+ };
+ return map;
+}
+
+std::unordered_map<std::string, int32_t>& getCallerNameMap() {
+ // DO NOT MODIFY VALUES (OK to add new ones).
+ // This may be found in frameworks/av/media/libmediametrics/include/MediaMetricsConstants.h
+ static std::unordered_map<std::string, int32_t> map{
+ {"aaudio", 0}, // Native AAudio
+ {"java", 1}, // Java API layer
+ {"media", 2}, // libmedia (mediaplayer)
+ {"opensles", 3}, // Open SLES
+ {"rtp", 4}, // RTP communication
+ {"soundpool", 5}, // SoundPool
+ {"tonegenerator", 6}, // dial tones
+ {"unknown", 7}, // callerName not set
+ // R values above.
+ };
+ return map;
+}
+
+std::unordered_map<std::string, int32_t>& getThreadTypeMap() {
+ // DO NOT MODIFY VALUES (OK to add new ones).
+ // This may be found in frameworks/av/services/audioflinger/Threads.h
+ static std::unordered_map<std::string, int32_t> map{
+ // UNKNOWN is -1
+ {"MIXER", 0}, // Thread class is MixerThread
+ {"DIRECT", 1}, // Thread class is DirectOutputThread
+ {"DUPLICATING", 2}, // Thread class is DuplicatingThread
+ {"RECORD", 3}, // Thread class is RecordThread
+ {"OFFLOAD", 4}, // Thread class is OffloadThread
+ {"MMAP_PLAYBACK", 5}, // Thread class for MMAP playback stream
+ {"MMAP_CAPTURE", 6}, // Thread class for MMAP capture stream
+ // R values above.
+ };
+ return map;
+}
+
+// Helper: Create the corresponding int32 from string flags split with '|'.
+template <typename Traits>
+int32_t int32FromFlags(const std::string &flags)
+{
+ const auto result = stringutils::split(flags, "|");
+ int32_t intFlags = 0;
+ for (const auto& flag : result) {
+ typename Traits::Type value;
+ if (!TypeConverter<Traits>::fromString(flag, value)) {
+ break;
+ }
+ intFlags |= value;
+ }
+ return intFlags;
+}
+
+template <typename Traits>
+std::string stringFromFlags(const std::string &flags, size_t len)
+{
+ const auto result = stringutils::split(flags, "|");
+ std::string sFlags;
+ for (const auto& flag : result) {
+ typename Traits::Type value;
+ if (!TypeConverter<Traits>::fromString(flag, value)) {
+ break;
+ }
+ if (len >= flag.size()) continue;
+ if (!sFlags.empty()) sFlags += "|";
+ sFlags += flag.c_str() + len;
+ }
+ return sFlags;
+}
+
+template <>
+int32_t lookup<CONTENT_TYPE>(const std::string &contentType)
+{
+ AudioContentTraits::Type value;
+ if (!TypeConverter<AudioContentTraits>::fromString(contentType, value)) {
+ value = AUDIO_CONTENT_TYPE_UNKNOWN;
+ }
+ return (int32_t)value;
+}
+
+template <>
+std::string lookup<CONTENT_TYPE>(const std::string &contentType)
+{
+ AudioContentTraits::Type value;
+ if (!TypeConverter<AudioContentTraits>::fromString(contentType, value)) {
+ return "UNKNOWN";
+ }
+ return contentType.c_str() + sizeof("AUDIO_CONTENT_TYPE");
+}
+
+template <>
+int32_t lookup<ENCODING>(const std::string &encoding)
+{
+ FormatTraits::Type value;
+ if (!TypeConverter<FormatTraits>::fromString(encoding, value)) {
+ value = AUDIO_FORMAT_INVALID;
+ }
+ return (int32_t)value;
+}
+
+template <>
+std::string lookup<ENCODING>(const std::string &encoding)
+{
+ FormatTraits::Type value;
+ if (!TypeConverter<FormatTraits>::fromString(encoding, value)) {
+ return "INVALID";
+ }
+ return encoding.c_str() + sizeof("AUDIO_FORMAT");
+}
+
+template <>
+int32_t lookup<INPUT_FLAG>(const std::string &inputFlag)
+{
+ return int32FromFlags<InputFlagTraits>(inputFlag);
+}
+
+template <>
+std::string lookup<INPUT_FLAG>(const std::string &inputFlag)
+{
+ return stringFromFlags<InputFlagTraits>(inputFlag, sizeof("AUDIO_INPUT_FLAG"));
+}
+
+template <>
+int32_t lookup<OUTPUT_FLAG>(const std::string &outputFlag)
+{
+ return int32FromFlags<OutputFlagTraits>(outputFlag);
+}
+
+template <>
+std::string lookup<OUTPUT_FLAG>(const std::string &outputFlag)
+{
+ return stringFromFlags<OutputFlagTraits>(outputFlag, sizeof("AUDIO_OUTPUT_FLAG"));
+}
+
+template <>
+int32_t lookup<SOURCE_TYPE>(const std::string &sourceType)
+{
+ SourceTraits::Type value;
+ if (!TypeConverter<SourceTraits>::fromString(sourceType, value)) {
+ value = AUDIO_SOURCE_DEFAULT;
+ }
+ return (int32_t)value;
+}
+
+template <>
+std::string lookup<SOURCE_TYPE>(const std::string &sourceType)
+{
+ SourceTraits::Type value;
+ if (!TypeConverter<SourceTraits>::fromString(sourceType, value)) {
+ return "DEFAULT";
+ }
+ return sourceType.c_str() + sizeof("AUDIO_SOURCE");
+}
+
+template <>
+int32_t lookup<STREAM_TYPE>(const std::string &streamType)
+{
+ StreamTraits::Type value;
+ if (!TypeConverter<StreamTraits>::fromString(streamType, value)) {
+ value = AUDIO_STREAM_DEFAULT;
+ }
+ return (int32_t)value;
+}
+
+template <>
+std::string lookup<STREAM_TYPE>(const std::string &streamType)
+{
+ StreamTraits::Type value;
+ if (!TypeConverter<StreamTraits>::fromString(streamType, value)) {
+ return "DEFAULT";
+ }
+ return streamType.c_str() + sizeof("AUDIO_STREAM");
+}
+
+template <>
+int32_t lookup<USAGE>(const std::string &usage)
+{
+ UsageTraits::Type value;
+ if (!TypeConverter<UsageTraits>::fromString(usage, value)) {
+ value = AUDIO_USAGE_UNKNOWN;
+ }
+ return (int32_t)value;
+}
+
+template <>
+std::string lookup<USAGE>(const std::string &usage)
+{
+ UsageTraits::Type value;
+ if (!TypeConverter<UsageTraits>::fromString(usage, value)) {
+ return "UNKNOWN";
+ }
+ return usage.c_str() + sizeof("AUDIO_USAGE");
+}
+
+template <>
+int64_t lookup<INPUT_DEVICE>(const std::string &inputDevice)
+{
+ auto& map = getAudioDeviceInMap();
+ auto it = map.find(inputDevice);
+ if (it == map.end()) {
+ return 0;
+ }
+ return it->second;
+}
+
+template <>
+std::string lookup<INPUT_DEVICE>(const std::string &inputDevice)
+{
+ auto& map = getAudioDeviceInMap();
+ auto it = map.find(inputDevice);
+ if (it == map.end()) {
+ return "NONE";
+ }
+ return inputDevice.c_str() + sizeof("AUDIO_DEVICE_IN");
+}
+
+template <>
+int64_t lookup<OUTPUT_DEVICE>(const std::string &outputDevice)
+{
+ auto& map = getAudioDeviceOutMap();
+ auto it = map.find(outputDevice);
+ if (it == map.end()) {
+ return 0; // nothing
+ }
+ return it->second;
+}
+
+template <>
+std::string lookup<OUTPUT_DEVICE>(const std::string &outputDevice)
+{
+ auto& map = getAudioDeviceOutMap();
+ auto it = map.find(outputDevice);
+ if (it == map.end()) {
+ return "NONE";
+ }
+ return outputDevice.c_str() + sizeof("AUDIO_DEVICE_OUT");
+}
+
+template <>
+int32_t lookup<CALLER_NAME>(const std::string &callerName)
+{
+ auto& map = getCallerNameMap();
+ auto it = map.find(callerName);
+ if (it == map.end()) {
+ return 7; // return unknown
+ }
+ return it->second;
+}
+
+template <>
+std::string lookup<CALLER_NAME>(const std::string &callerName)
+{
+ auto& map = getCallerNameMap();
+ auto it = map.find(callerName);
+ if (it == map.end()) {
+ return "unknown";
+ }
+ return callerName;
+}
+
+template <>
+int32_t lookup<THREAD_TYPE>(const std::string &threadType)
+{
+ auto& map = getThreadTypeMap();
+ auto it = map.find(threadType);
+ if (it == map.end()) {
+ return -1; // note this as an illegal thread value as we don't have unknown here.
+ }
+ return it->second;
+}
+
+template <>
+std::string lookup<THREAD_TYPE>(const std::string &threadType)
+{
+ auto& map = getThreadTypeMap();
+ auto it = map.find(threadType);
+ if (it == map.end()) {
+ return "UNKNOWN";
+ }
+ return threadType;
+}
+
+bool isInputThreadType(const std::string &threadType)
+{
+ return threadType == "RECORD" || threadType == "MMAP_CAPTURE";
+}
+
+} // namespace android::mediametrics::types
diff --git a/services/mediametrics/AudioTypes.h b/services/mediametrics/AudioTypes.h
new file mode 100644
index 0000000..a094e6e
--- /dev/null
+++ b/services/mediametrics/AudioTypes.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <string>
+#include <unordered_map>
+
+namespace android::mediametrics::types {
+
+// Helper methods that map mediametrics logged strings to
+// integer codes.
+std::unordered_map<std::string, int64_t>& getAudioDeviceInMap();
+std::unordered_map<std::string, int64_t>& getAudioDeviceOutMap();
+std::unordered_map<std::string, int32_t>& getCallerNameMap();
+std::unordered_map<std::string, int32_t>& getThreadTypeMap();
+
+// Enumeration for the device connection results.
+enum DeviceConnectionResult : int32_t {
+ DEVICE_CONNECTION_RESULT_UNKNOWN = 0, // Success is unknown.
+ DEVICE_CONNECTION_RESULT_SUCCESS = 1, // Audio delivered
+ DEVICE_CONNECTION_RESULT_JAVA_SERVICE_CANCEL = 2, // Canceled in Java service
+ // Do not modify the constants above after R. Adding new constants is fine.
+};
+
+// Enumeration for all the string translations to integers (generally int32_t) unless noted.
+enum AudioEnumCategory {
+ CALLER_NAME,
+ CONTENT_TYPE,
+ ENCODING,
+ INPUT_DEVICE, // int64_t
+ INPUT_FLAG,
+ OUTPUT_DEVICE, // int64_t
+ OUTPUT_FLAG,
+ SOURCE_TYPE,
+ STREAM_TYPE,
+ THREAD_TYPE,
+ USAGE,
+};
+
+// Convert a string (or arbitrary S) from an AudioEnumCategory to a particular type.
+// This is used to convert log std::strings back to the original type (int32_t or int64_t).
+//
+// For a string, generally there is a prefix "AUDIO_INPUT_FLAG" or some such that could
+// actually indicate the category so the AudioEnumCategory could be superfluous, but
+// we use it to find the proper default value in case of an unknown string.
+//
+// lookup<ENCODING, int32_t>("AUDIO_FORMAT_PCM_16_BIT") -> 1
+//
+template <AudioEnumCategory C, typename T, typename S>
+T lookup(const S &str);
+
+// Helper: Allow using a const char * in lieu of std::string.
+template <AudioEnumCategory C, typename T>
+T lookup(const char *str) {
+ return lookup<C, T, std::string>(str);
+}
+
+bool isInputThreadType(const std::string &threadType);
+
+} // namespace android::mediametrics::types
diff --git a/services/mediametrics/MediaMetricsService.cpp b/services/mediametrics/MediaMetricsService.cpp
index 584bd13..d682fed 100644
--- a/services/mediametrics/MediaMetricsService.cpp
+++ b/services/mediametrics/MediaMetricsService.cpp
@@ -95,58 +95,6 @@
}
}
-/* static */
-std::string MediaMetricsService::tokenizer(std::string::const_iterator& it,
- const std::string::const_iterator& end, const char *reserved) {
- // consume leading white space
- for (; it != end && std::isspace(*it); ++it);
- if (it == end) return {};
-
- auto start = it;
- // parse until we hit a reserved keyword or space
- if (strchr(reserved, *it)) return {start, ++it};
- for (;;) {
- ++it;
- if (it == end || std::isspace(*it) || strchr(reserved, *it)) return {start, it};
- }
-}
-
-/* static */
-std::vector<std::pair<std::string, std::string>>
-MediaMetricsService::getDeviceAddressPairs(const std::string& devices) {
- std::vector<std::pair<std::string, std::string>> result;
-
- // Currently, the device format is EXACTLY
- // (device1, addr1)|(device2, addr2)|...
-
- static constexpr char delim[] = "()|,";
- for (auto it = devices.begin(); ; ) {
- auto token = tokenizer(it, devices.end(), delim);
- if (token != "(") return result;
-
- auto device = tokenizer(it, devices.end(), delim);
- if (device.empty() || !std::isalnum(device[0])) return result;
-
- token = tokenizer(it, devices.end(), delim);
- if (token != ",") return result;
-
- // special handling here for empty addresses
- auto address = tokenizer(it, devices.end(), delim);
- if (address.empty() || !std::isalnum(device[0])) return result;
- if (address == ")") { // no address, just the ")"
- address.clear();
- } else {
- token = tokenizer(it, devices.end(), delim);
- if (token != ")") return result;
- }
-
- result.emplace_back(std::move(device), std::move(address));
-
- token = tokenizer(it, devices.end(), delim);
- if (token != "|") return result; // this includes end of string detection
- }
-}
-
MediaMetricsService::MediaMetricsService()
: mMaxRecords(kMaxRecords),
mMaxRecordAgeNs(kMaxRecordAgeNs),
diff --git a/services/mediametrics/MediaMetricsService.h b/services/mediametrics/MediaMetricsService.h
index b8eb267..d152264 100644
--- a/services/mediametrics/MediaMetricsService.h
+++ b/services/mediametrics/MediaMetricsService.h
@@ -77,20 +77,6 @@
*/
static std::pair<std::string, int64_t> getSanitizedPackageNameAndVersionCode(uid_t uid);
- /**
- * Return string tokens from iterator, separated by spaces and reserved chars.
- */
- static std::string tokenizer(std::string::const_iterator& it,
- const std::string::const_iterator& end, const char *reserved);
-
- /**
- * Parse the devices string and return a vector of device address pairs.
- *
- * A failure to parse returns early with the contents that were able to be parsed.
- */
- static std::vector<std::pair<std::string, std::string>>
- getDeviceAddressPairs(const std::string &devices);
-
protected:
// Internal call where release is true if ownership of item is transferred
diff --git a/services/mediametrics/StringUtils.cpp b/services/mediametrics/StringUtils.cpp
new file mode 100644
index 0000000..50525bc
--- /dev/null
+++ b/services/mediametrics/StringUtils.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 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 "MediaMetricsService::stringutils"
+#include <utils/Log.h>
+
+#include "StringUtils.h"
+
+namespace android::mediametrics::stringutils {
+
+std::string tokenizer(std::string::const_iterator& it,
+ const std::string::const_iterator& end, const char *reserved)
+{
+ // consume leading white space
+ for (; it != end && std::isspace(*it); ++it);
+ if (it == end) return {};
+
+ auto start = it;
+ // parse until we hit a reserved keyword or space
+ if (strchr(reserved, *it)) return {start, ++it};
+ for (;;) {
+ ++it;
+ if (it == end || std::isspace(*it) || strchr(reserved, *it)) return {start, it};
+ }
+}
+
+std::vector<std::string> split(const std::string& flags, const char *delim)
+{
+ std::vector<std::string> result;
+ for (auto it = flags.begin(); ; ) {
+ auto flag = tokenizer(it, flags.end(), delim);
+ if (flag.empty() || !std::isalnum(flag[0])) return result;
+ result.emplace_back(std::move(flag));
+
+ // look for the delimeter and discard
+ auto token = tokenizer(it, flags.end(), delim);
+ if (token.size() != 1 || strchr(delim, token[0]) == nullptr) return result;
+ }
+}
+
+std::vector<std::pair<std::string, std::string>> getDeviceAddressPairs(const std::string& devices)
+{
+ std::vector<std::pair<std::string, std::string>> result;
+
+ // Currently, the device format is EXACTLY
+ // (device1, addr1)|(device2, addr2)|...
+
+ static constexpr char delim[] = "()|,";
+ for (auto it = devices.begin(); ; ) {
+ auto token = tokenizer(it, devices.end(), delim);
+ if (token != "(") return result;
+
+ auto device = tokenizer(it, devices.end(), delim);
+ if (device.empty() || !std::isalnum(device[0])) return result;
+
+ token = tokenizer(it, devices.end(), delim);
+ if (token != ",") return result;
+
+ // special handling here for empty addresses
+ auto address = tokenizer(it, devices.end(), delim);
+ if (address.empty() || !std::isalnum(device[0])) return result;
+ if (address == ")") { // no address, just the ")"
+ address.clear();
+ } else {
+ token = tokenizer(it, devices.end(), delim);
+ if (token != ")") return result;
+ }
+
+ result.emplace_back(std::move(device), std::move(address));
+
+ token = tokenizer(it, devices.end(), delim);
+ if (token != "|") return result; // this includes end of string detection
+ }
+}
+
+size_t replace(std::string &str, const char *targetChars, const char replaceChar)
+{
+ size_t replaced = 0;
+ for (char &c : str) {
+ if (strchr(targetChars, c) != nullptr) {
+ c = replaceChar;
+ ++replaced;
+ }
+ }
+ return replaced;
+}
+
+} // namespace android::mediametrics::stringutils
diff --git a/services/mediametrics/StringUtils.h b/services/mediametrics/StringUtils.h
new file mode 100644
index 0000000..d878720
--- /dev/null
+++ b/services/mediametrics/StringUtils.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+namespace android::mediametrics::stringutils {
+
+/**
+ * Return string tokens from iterator, separated by spaces and reserved chars.
+ */
+std::string tokenizer(std::string::const_iterator& it,
+ const std::string::const_iterator& end, const char *reserved);
+
+/**
+ * Splits flags string based on delimeters (or, whitespace which is removed).
+ */
+std::vector<std::string> split(const std::string& flags, const char *delim);
+
+/**
+ * Parse the devices string and return a vector of device address pairs.
+ *
+ * A failure to parse returns early with the contents that were able to be parsed.
+ */
+std::vector<std::pair<std::string, std::string>> getDeviceAddressPairs(const std::string &devices);
+
+/**
+ * Replaces targetChars with replaceChar in string, returns number of chars replaced.
+ */
+size_t replace(std::string &str, const char *targetChars, const char replaceChar);
+
+} // namespace android::mediametrics::stringutils
diff --git a/services/mediametrics/tests/mediametrics_tests.cpp b/services/mediametrics/tests/mediametrics_tests.cpp
index f7988f1..7da6306 100644
--- a/services/mediametrics/tests/mediametrics_tests.cpp
+++ b/services/mediametrics/tests/mediametrics_tests.cpp
@@ -18,6 +18,7 @@
#include <utils/Log.h>
#include "MediaMetricsService.h"
+#include "StringUtils.h"
#include <stdio.h>
@@ -884,12 +885,12 @@
}
TEST(mediametrics_tests, device_parsing) {
- auto devaddr = android::MediaMetricsService::getDeviceAddressPairs("(DEVICE, )");
+ auto devaddr = android::mediametrics::stringutils::getDeviceAddressPairs("(DEVICE, )");
ASSERT_EQ((size_t)1, devaddr.size());
ASSERT_EQ("DEVICE", devaddr[0].first);
ASSERT_EQ("", devaddr[0].second);
- devaddr = android::MediaMetricsService::getDeviceAddressPairs(
+ devaddr = android::mediametrics::stringutils::getDeviceAddressPairs(
"(DEVICE1, A)|(D, ADDRB)");
ASSERT_EQ((size_t)2, devaddr.size());
ASSERT_EQ("DEVICE1", devaddr[0].first);
@@ -897,7 +898,7 @@
ASSERT_EQ("D", devaddr[1].first);
ASSERT_EQ("ADDRB", devaddr[1].second);
- devaddr = android::MediaMetricsService::getDeviceAddressPairs(
+ devaddr = android::mediametrics::stringutils::getDeviceAddressPairs(
"(A,B)|(C,D)");
ASSERT_EQ((size_t)2, devaddr.size());
ASSERT_EQ("A", devaddr[0].first);
@@ -905,7 +906,7 @@
ASSERT_EQ("C", devaddr[1].first);
ASSERT_EQ("D", devaddr[1].second);
- devaddr = android::MediaMetricsService::getDeviceAddressPairs(
+ devaddr = android::mediametrics::stringutils::getDeviceAddressPairs(
" ( A1 , B ) | ( C , D2 ) ");
ASSERT_EQ((size_t)2, devaddr.size());
ASSERT_EQ("A1", devaddr[0].first);