cts_audio work: initial code
- volume calibration and THD test case work tuned.
- 59 unit tests pass
- local audio playback / recording works : uses tinyalsa
- host to device ptorocol in host side implemented / tested (with loopback)
- device side recording / playback works for test cases / test_io.xml
- python processing baseline added.
- spectrum algorithm implementated: calculate Transfer Function of device
over host and check if amplitudes are within margain. Needs parameter tuning
- spectrum test needs some improvements due to the non-flat response from
ref microphone.
- build is enabled only for linux host except the client code
Change-Id: I8453ac72b6fce7ddbfee7e2cc05207f09f2b3d88
diff --git a/suite/audio_quality/lib/src/Adb.cpp b/suite/audio_quality/lib/src/Adb.cpp
new file mode 100644
index 0000000..fbfc974
--- /dev/null
+++ b/suite/audio_quality/lib/src/Adb.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2012 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 <stdlib.h>
+
+#include <StringUtil.h>
+#include "Adb.h"
+
+Adb::Adb(const android::String8& device)
+ : mDevice(device)
+{
+
+}
+
+Adb::~Adb()
+{
+
+}
+
+bool Adb::setPortForwarding(int hostPort, int devicePort)
+{
+ android::String8 command;
+ if (command.appendFormat("forward tcp:%d tcp:%d", hostPort, devicePort) != 0) {
+ return false;
+ }
+ if (executeCommand(command) != 0) {
+ return false;
+ }
+ return true;
+}
+
+bool Adb::launchClient(const android::String8& clientBinary, const android::String8& component)
+{
+ android::String8 command;
+ if (command.appendFormat("install -r %s", clientBinary.string()) != 0) {
+ return false;
+ }
+ if (executeCommand(command) != 0) {
+ return false;
+ }
+ command.clear();
+ if (command.appendFormat("shell am start -W -n %s", component.string()) != 0) {
+ return false;
+ }
+ if (executeCommand(command) != 0) {
+ return false;
+ }
+ return true;
+}
+
+/** @param command ADB command except adb -s XYZW */
+int Adb::executeCommand(const android::String8& command)
+{
+ android::String8 adbCommand;
+ if (mDevice.empty()) {
+ if (adbCommand.appendFormat("adb %s", command.string()) != 0) {
+ return -1;
+ }
+ } else {
+ if (adbCommand.appendFormat("adb -s %s %s", mDevice.string(),
+ command.string()) != 0) {
+ return -1;
+ }
+ }
+ return system(adbCommand.string());
+}
+
diff --git a/suite/audio_quality/lib/src/Adb.h b/suite/audio_quality/lib/src/Adb.h
new file mode 100644
index 0000000..59d4b9b
--- /dev/null
+++ b/suite/audio_quality/lib/src/Adb.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#ifndef CTSAUDIO_ADB_H
+#define CTSAUDIO_ADB_H
+
+#include <utils/String8.h>
+
+/** ADB interface to set port forwarding and launch client app */
+class Adb {
+public:
+ /// device: device number typically passed in adb's -s argument.
+ /// if device string is empty, adb command will be called without -s option.
+ Adb(const android::String8& device);
+ ~Adb();
+ bool setPortForwarding(int hostPort, int devicePort);
+ /// install given clientBinary to DUT and launch given component.
+ bool launchClient(const android::String8& clientBinary, const android::String8& component);
+private:
+ int executeCommand(const android::String8& command);
+
+private:
+ android::String8 mDevice;
+};
+
+#endif // CTSAUDIO_ADB_H
+
+
+
+
diff --git a/suite/audio_quality/lib/src/BuiltinProcessing.cpp b/suite/audio_quality/lib/src/BuiltinProcessing.cpp
new file mode 100644
index 0000000..49815ce
--- /dev/null
+++ b/suite/audio_quality/lib/src/BuiltinProcessing.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2012 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 <stdint.h>
+#include <math.h>
+#include <utils/StrongPointer.h>
+#include "audio/Buffer.h"
+#include "BuiltinProcessing.h"
+#include "Log.h"
+#include "task/TaskCase.h"
+
+// Buffer, Value, Value
+static const bool RMS_MVA_INPUT_TYPE[] = {true, false, false};
+// Value
+static const bool RMS_MVA_OUTPUT_TYPE[] = {false};
+
+BuiltinProcessing::BuiltinInfo BuiltinProcessing::BUINTIN_FN_TABLE[N_BUILTIN_FNS] =
+{
+ {
+ "rms_mva", &BuiltinProcessing::rms_mva,
+ sizeof(RMS_MVA_INPUT_TYPE)/sizeof(bool), RMS_MVA_INPUT_TYPE,
+ sizeof(RMS_MVA_OUTPUT_TYPE)/sizeof(bool), RMS_MVA_OUTPUT_TYPE,
+ }
+};
+
+BuiltinProcessing::BuiltinProcessing()
+ : mRMSPasses(0)
+{
+
+}
+
+// pass for 5 consecutive passes
+TaskGeneric::ExecutionResult BuiltinProcessing::rms_mva(void** inputs, void** outputs)
+{
+ LOGD("BuiltinProcessing::rms_mva in %x %x %x out %x",
+ inputs[0], inputs[1], inputs[2], outputs[0]);
+ android::sp<Buffer>& data(*reinterpret_cast<android::sp<Buffer>*>(inputs[0]));
+
+ int64_t passMin = (reinterpret_cast<TaskCase::Value*>(inputs[1]))->getInt64();
+ int64_t passMax = (reinterpret_cast<TaskCase::Value*>(inputs[2]))->getInt64();
+
+ int64_t rms = 0;
+ size_t samples = data->getSize()/2;
+ int16_t* rawData = reinterpret_cast<int16_t*>(data->getData());
+ double energy = 0.0f;
+ for (size_t i = 0; i < samples; i++) {
+ energy += (rawData[i] * rawData[i]);
+ }
+ rms = (int64_t)sqrt(energy/samples);
+
+ TaskGeneric::ExecutionResult result = TaskGeneric::EResultOK;
+ if (rms < passMin) {
+ LOGW("Volume %lld low compared to min %lld max %lld", rms, passMin, passMax);
+ mRMSPasses = 0;
+ } else if (rms <= passMax) {
+ LOGW("Volume %lld OK compared to min %lld max %lld", rms, passMin, passMax);
+ mRMSPasses++;
+ if (mRMSPasses >= RMS_CONTINUOUS_PASSES) {
+ //mRMSPasses = 0;
+ result = TaskGeneric::EResultPass;
+ }
+ } else {
+ LOGW("Volume %lld high compared to min %lld max %lld", rms, passMin, passMax);
+ mRMSPasses = 0;
+ }
+ TaskCase::Value* rmsVal = reinterpret_cast<TaskCase::Value*>(outputs[0]);
+ rmsVal->setInt64(rms);
+
+ return result;
+}
+
+
diff --git a/suite/audio_quality/lib/src/ClientImpl.cpp b/suite/audio_quality/lib/src/ClientImpl.cpp
new file mode 100644
index 0000000..615cd2a
--- /dev/null
+++ b/suite/audio_quality/lib/src/ClientImpl.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2012 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 "Log.h"
+
+#include "Adb.h"
+#include "ClientImpl.h"
+
+
+ClientImpl::ClientImpl()
+ : mAudio(new RemoteAudio(mSocket))
+{
+
+}
+
+ClientImpl::~ClientImpl()
+{
+ mAudio->release();
+}
+
+bool ClientImpl::init(const android::String8& param)
+{
+ Adb adb(param);
+ if (!adb.setPortForwarding(HOST_TCP_PORT, CLIENT_TCP_PORT)) {
+ LOGE("adb port forwarding failed");
+ return false;
+ }
+ android::String8 clientBinary("client/CtsAudioClient.apk");
+ android::String8 componentName("com.android.cts.audiotest/.CtsAudioClientActivity");
+ if (!adb.launchClient(clientBinary, componentName)) {
+ LOGE("cannot install or launch client");
+ return false;
+ }
+ // now socket connection
+ return mAudio->init(HOST_TCP_PORT);
+}
+
+
diff --git a/suite/audio_quality/lib/src/ClientImpl.h b/suite/audio_quality/lib/src/ClientImpl.h
new file mode 100644
index 0000000..28a7bcc
--- /dev/null
+++ b/suite/audio_quality/lib/src/ClientImpl.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+
+#ifndef CTSAUDIO_CLIENTIMPL_H
+#define CTSAUDIO_CLIENTIMPL_H
+
+#include <utils/StrongPointer.h>
+#include "ClientInterface.h"
+#include "ClientSocket.h"
+#include "audio/RemoteAudio.h"
+
+class ClientImpl: public ClientInterface {
+public:
+ static const int HOST_TCP_PORT = 15001;
+ static const int CLIENT_TCP_PORT = 15001;
+ ClientImpl();
+ virtual ~ClientImpl();
+ virtual bool init(const android::String8& param);
+
+ virtual ClientSocket& getSocket() {
+ return mSocket;
+ };
+
+ virtual android::sp<RemoteAudio>& getAudio() {
+ return mAudio;
+ };
+
+private:
+ ClientSocket mSocket;
+ android::sp<RemoteAudio> mAudio;
+
+};
+
+
+#endif // CTSAUDIO_CLIENTIMPL_H
diff --git a/suite/audio_quality/lib/src/ClientSocket.cpp b/suite/audio_quality/lib/src/ClientSocket.cpp
new file mode 100644
index 0000000..1fa9090
--- /dev/null
+++ b/suite/audio_quality/lib/src/ClientSocket.cpp
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2012 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 <arpa/inet.h>
+#include <errno.h>
+#include <strings.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "Log.h"
+
+#include "ClientSocket.h"
+
+ClientSocket::ClientSocket()
+ : mSocket(-1),
+ mTimeoutEnabled(false)
+{
+
+}
+
+ClientSocket::~ClientSocket()
+{
+ release();
+}
+
+bool ClientSocket::init(const char* hostIp, int port, bool enableTimeout)
+{
+ LOGD("ClientSocket::init");
+ mSocket = socket(AF_INET, SOCK_STREAM, 0);
+ if (mSocket < 0) {
+ LOGE("cannot open socket %d", errno);
+ return false;
+ }
+ int reuse = 1;
+ if (setsockopt(mSocket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
+ LOGE("setsockopt error %d", errno);
+ release();
+ return false;
+ }
+
+ struct sockaddr_in serverAddr;
+ bzero((char*)&serverAddr, sizeof(serverAddr));
+ serverAddr.sin_family = AF_INET;
+ serverAddr.sin_port = htons(port);
+ if (inet_pton(AF_INET, hostIp, &serverAddr.sin_addr) != 1) {
+ release();
+ LOGE("inet_pton failed %d", errno);
+ return false;
+ }
+ if (connect(mSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
+ release();
+ LOGE("cannot connect socket %d", errno);
+ return false;
+ }
+ mTimeoutEnabled = enableTimeout;
+ return true;
+}
+
+const int ZERO_RW_SLEEP_TIME_US = 10;
+
+// make non-blocking mode only during read. This allows supporting time-out for read
+bool ClientSocket::readData(char* data, int len, int timeoutInMs)
+{
+ bool useTimeout = (mTimeoutEnabled && (timeoutInMs > 0));
+ int flOriginal = 0;
+ int timeInSec = 0;
+ int timeInUs = 0;
+ if (useTimeout) {
+ flOriginal = fcntl(mSocket, F_GETFL,0);
+ if (flOriginal == -1) {
+ LOGE("fcntl error %d", errno);
+ return false;
+ }
+ if (fcntl(mSocket, F_SETFL, flOriginal | O_NONBLOCK) == -1) {
+ LOGE("fcntl error %d", errno);
+ return false;
+ }
+ timeInSec = timeoutInMs / 1000;
+ timeInUs = (timeoutInMs % 1000) * 1000;
+ }
+ bool result = true;
+ int read;
+ int toRead = len;
+ while (toRead > 0) {
+ if (useTimeout) {
+ fd_set rfds;
+ struct timeval tv;
+ tv.tv_sec = timeInSec;
+ tv.tv_usec = timeInUs;
+ FD_ZERO(&rfds);
+ FD_SET(mSocket, &rfds);
+ if (select(mSocket + 1, &rfds, NULL, NULL, &tv) == -1) {
+ LOGE("select failed");
+ result = false;
+ break;
+ }
+ if (!FD_ISSET(mSocket, &rfds)) {
+ LOGE("socket read timeout");
+ result = false;
+ break;
+ }
+ }
+ read = recv(mSocket, (void*)data, toRead, 0);
+ if (read > 0) {
+ toRead -= read;
+ data += read;
+ } else if (read == 0) {
+ // in blocking mode, zero read mean's peer closed.
+ // in non-blocking mode, select said that there is data. so it should not happen
+ LOGE("zero read, peer closed or what?, nonblocking: %d", useTimeout);
+ result = false;
+ break;
+ } else {
+ LOGE("recv returned %d", read);
+ result = false;
+ break;
+ }
+ }
+ if (useTimeout) {
+ fcntl(mSocket, F_SETFL, flOriginal); // now blocking again
+ }
+ return result;
+}
+
+bool ClientSocket::sendData(const char* data, int len)
+{
+ int sent;
+ int toSend = len;
+ while (toSend > 0) {
+ sent = send(mSocket, (void*)data, (size_t)toSend, 0);
+ if (sent > 0) {
+ toSend -= sent;
+ data += sent;
+ } else if (sent == 0) { // no more buffer?
+ usleep(ZERO_RW_SLEEP_TIME_US); // just wait
+ } else {
+ LOGE("send returned %d, error %d", sent, errno);
+ return false;
+ }
+ }
+ return true;
+}
+
+void ClientSocket::release()
+{
+ if (mSocket != -1) {
+ close(mSocket);
+ mSocket = -1;
+ }
+}
diff --git a/suite/audio_quality/lib/src/FileUtil.cpp b/suite/audio_quality/lib/src/FileUtil.cpp
new file mode 100644
index 0000000..e40b7e3
--- /dev/null
+++ b/suite/audio_quality/lib/src/FileUtil.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2012 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 <time.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <errno.h>
+
+#include "Log.h"
+#include "StringUtil.h"
+#include "FileUtil.h"
+
+
+// This class is used by Log. So we cannot use LOG? macros here.
+#define _LOGD_(x...) do { fprintf(stderr, x); fprintf(stderr, "\n"); } while(0)
+
+// reported generated under reports/YYYY_MM_DD_HH_MM_SS dir
+const char reportTopDir[] = "reports";
+android::String8 FileUtil::mDirPath;
+
+bool FileUtil::prepare(android::String8& dirPath)
+{
+ if (mDirPath.length() != 0) {
+ dirPath = mDirPath;
+ _LOGD_("mDirPath %s", mDirPath.string());
+ return true;
+ }
+
+ time_t timeNow = time(NULL);
+ if (timeNow == ((time_t)-1)) {
+ _LOGD_("time error");
+ return false;
+ }
+ // tm is allocated in static buffer, and should not be freed.
+ struct tm* tm = localtime(&timeNow);
+ if (tm == NULL) {
+ _LOGD_("localtime error");
+ return false;
+ }
+ int result = mkdir(reportTopDir, S_IRWXU);
+ if ((result == -1) && (errno != EEXIST)) {
+ _LOGD_("mkdir of topdir failed, error %d", errno);
+ return false;
+ }
+ android::String8 path;
+ if (path.appendFormat("%s/%04d_%02d_%02d_%02d_%02d_%02d", reportTopDir,tm->tm_year + 1900,
+ tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec) != 0) {
+ return false;
+ }
+ result = mkdir(path.string(), S_IRWXU);
+ if ((result == -1) && (errno != EEXIST)) {
+ _LOGD_("mkdir of report dir failed, error %d", errno);
+ return false;
+ }
+ mDirPath = path;
+ dirPath = path;
+
+ return true;
+}
+
+FileUtil::FileUtil()
+{
+
+}
+
+FileUtil::~FileUtil()
+{
+ if (mFile.is_open()) {
+ mFile.close();
+ }
+}
+
+bool FileUtil::init(const char* fileName)
+{
+ if (fileName == NULL) {
+ return true;
+ }
+
+ mFile.open(fileName, std::ios::out | std::ios::trunc);
+ if (!mFile.is_open()) {
+ return false;
+ }
+ return true;
+}
+
+bool FileUtil::doVprintf(bool fileOnly, int loglevel, const char *fmt, va_list ap)
+{
+ // prevent messed up log in multi-thread env. Still multi-line logs can be messed up.
+ android::Mutex::Autolock lock(mWriteLock);
+ int start = 0;
+ if (loglevel != -1) {
+ mBuffer[0] = '0' + loglevel;
+ mBuffer[1] = '>';
+ start = 2;
+ }
+ int size;
+ size = vsnprintf(mBuffer + start, BUFFER_SIZE - start - 2, fmt, ap); // 2 for \n\0
+ if (size < 0) {
+ fprintf(stderr, "FileUtil::vprintf failed");
+ return false;
+ }
+ size += start;
+ mBuffer[size] = '\n';
+ size++;
+ mBuffer[size] = 0;
+
+ if (!fileOnly) {
+ fprintf(stdout, "%s", mBuffer);
+ }
+ if (mFile.is_open()) {
+ mFile<<mBuffer;
+ }
+ return true;
+}
+
+bool FileUtil::doPrintf(const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ bool result = doVprintf(false, -1, fmt, ap);
+ va_end(ap);
+ return result;
+}
diff --git a/suite/audio_quality/lib/src/GenericFactory.cpp b/suite/audio_quality/lib/src/GenericFactory.cpp
new file mode 100644
index 0000000..f615876
--- /dev/null
+++ b/suite/audio_quality/lib/src/GenericFactory.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2012 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 "Log.h"
+#include "GenericFactory.h"
+#include "ClientImpl.h"
+#include "task/TaskAll.h"
+
+ClientInterface* GenericFactory::createClientInterface()
+{
+ return new ClientImpl();
+}
+
+TaskGeneric* GenericFactory::createTask(TaskGeneric::TaskType type)
+{
+ TaskGeneric* task;
+ switch(type) {
+ case TaskGeneric::ETaskBatch:
+ task = new TaskBatch();
+ break;
+ case TaskGeneric::ETaskCase:
+ task = new TaskCase();
+ break;
+ case TaskGeneric::ETaskSequential:
+ task = new TaskSequential();
+ break;
+ case TaskGeneric::ETaskProcess:
+ task = new TaskProcess();
+ break;
+ case TaskGeneric::ETaskInput:
+ task = new TaskInput();
+ break;
+ case TaskGeneric::ETaskOutput:
+ task = new TaskOutput();
+ break;
+ case TaskGeneric::ETaskSound:
+ task = new TaskSound();
+ break;
+ case TaskGeneric::ETaskSave:
+ task = new TaskSave();
+ break;
+ // simple elements without its own class
+ case TaskGeneric::ETaskSetup:
+ case TaskGeneric::ETaskAction:
+ task = new TaskGeneric(type);
+ break;
+ case TaskGeneric::ETaskMessage:
+ task = new TaskMessage();
+ break;
+ default:
+ LOGE("GenericFactory::createTask unsupported type %d", type);
+ return NULL;
+ }
+ LOGD("GenericFactory::createTask 0x%x, type %d", task, type);
+ return task;
+}
diff --git a/suite/audio_quality/lib/src/Log.cpp b/suite/audio_quality/lib/src/Log.cpp
new file mode 100644
index 0000000..d4cf353
--- /dev/null
+++ b/suite/audio_quality/lib/src/Log.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2012 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 <stdio.h>
+#include <stdarg.h>
+
+#include "StringUtil.h"
+#include "Log.h"
+
+Log* Log::mInstance = NULL;
+
+#define ASSERT_PLAIN(cond) if(!(cond)) { fprintf(stderr, \
+ "assertion failed %s %d", __FILE__, __LINE__); \
+ *(char*)0 = 0; /* this will crash */};
+
+Log* Log::Instance(const char* dirName)
+{
+ if (!mInstance) {
+ mInstance = new Log();
+ ASSERT_PLAIN(mInstance->init(dirName));
+ }
+ return mInstance;
+}
+void Log::Finalize()
+{
+ delete mInstance;
+ mInstance = NULL;
+}
+void Log::printf(LogLevel level, const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ FileUtil::doVprintf(level < mLogLevel, level, fmt, ap);
+ va_end(ap);
+}
+
+void Log::setLogLevel(LogLevel level)
+{
+ mLogLevel = level;
+}
+
+Log::Log()
+ : mLogLevel(ELogV)
+{
+ ::fprintf(stderr, "Log level %d\n", mLogLevel);
+}
+
+Log::~Log()
+{
+
+}
+
+bool Log::init(const char* dirName)
+{
+ if (dirName == NULL) {
+ return true;
+ }
+ android::String8 logFile;
+ if (logFile.appendFormat("%s/log.txt", dirName) != 0) {
+ return false;
+ }
+ return FileUtil::init(logFile.string());
+}
+
+
+
diff --git a/suite/audio_quality/lib/src/RWBuffer.h b/suite/audio_quality/lib/src/RWBuffer.h
new file mode 100644
index 0000000..8b9d2a0
--- /dev/null
+++ b/suite/audio_quality/lib/src/RWBuffer.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+
+#ifndef CTSAUDIO_RWBUFFER_H
+#define CTSAUDIO_RWBUFFER_H
+
+#include <stdint.h>
+#include <utils/String8.h>
+#include "Log.h"
+
+/// utility for R/W buffer
+class RWBuffer {
+public:
+ RWBuffer(int capacity)
+ : mCapacity(capacity),
+ mWrPoint(0),
+ mRdPoint(0) {
+ mBuffer = new char[capacity];
+ }
+
+ ~RWBuffer() {
+ delete[] mBuffer;
+ }
+
+ void reset() {
+ mWrPoint = 0;
+ mRdPoint = 0;
+ }
+
+ void resetWr() {
+ mWrPoint = 0;
+ }
+
+ void resetRd() {
+ mRdPoint = 0;
+ }
+
+ const char* getBuffer() {
+ return mBuffer;
+ }
+ char* getUnwrittenBuffer() {
+ return mBuffer + mWrPoint;
+ }
+
+ inline void assertWriteCapacity(int sizeToWrite) {
+ ASSERT((mWrPoint + sizeToWrite) <= mCapacity);
+ }
+ void increaseWritten(int size) {
+ assertWriteCapacity(0); // damage already done, but detect and panic if happened
+ mWrPoint += size;
+ }
+
+ int getSizeWritten() {
+ return mWrPoint;
+ }
+
+ int getSizeRead() {
+ return mRdPoint;
+ }
+
+ template <typename T> void write(T v) {
+ char* src = (char*)&v;
+ assertWriteCapacity(sizeof(T));
+ memcpy(mBuffer + mWrPoint, src, sizeof(T));
+ mWrPoint += sizeof(T);
+ }
+ void writeStr(const android::String8& str) {
+ size_t len = str.length();
+ assertWriteCapacity(len);
+ memcpy(mBuffer + mWrPoint, str.string(), len);
+ mWrPoint += len;
+ }
+ template <typename T> T read() {
+ T v;
+ ASSERT((mRdPoint + sizeof(T)) <= mWrPoint);
+ memcpy(&v, mBuffer + mRdPoint, sizeof(T));
+ mRdPoint += sizeof(T);
+ }
+
+private:
+ int mCapacity;
+ int mWrPoint;
+ int mRdPoint;
+ char* mBuffer;
+};
+
+
+#endif // CTSAUDIO_RWBUFFER_H
diff --git a/suite/audio_quality/lib/src/Report.cpp b/suite/audio_quality/lib/src/Report.cpp
new file mode 100644
index 0000000..b487836
--- /dev/null
+++ b/suite/audio_quality/lib/src/Report.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2012 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 "Log.h"
+#include "StringUtil.h"
+#include "Report.h"
+
+
+Report* Report::mInstance = NULL;
+
+Report* Report::Instance(const char* dirName)
+{
+ if (mInstance == NULL) {
+ mInstance = new Report();
+ ASSERT(mInstance->init(dirName));
+ }
+ return mInstance;
+}
+void Report::Finalize()
+{
+ delete mInstance;
+ mInstance = NULL;
+}
+
+
+Report::Report()
+{
+
+}
+
+Report::~Report()
+{
+ writeSummary();
+}
+
+bool Report::init(const char* dirName)
+{
+ if (dirName == NULL) {
+ return true;
+ }
+ android::String8 report;
+ if (report.appendFormat("%s/report.txt", dirName) != 0) {
+ return false;
+ }
+ return FileUtil::init(report.string());
+}
+
+void Report::printf(const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ FileUtil::doVprintf(false, -1, fmt, ap);
+ va_end(ap);
+}
+
+void Report::addCasePassed(const android::String8& name)
+{
+ mPassedCases.push_back(name);
+}
+
+void Report::addCaseFailed(const android::String8& name)
+{
+ mFailedCases.push_back(name);
+}
+
+void Report::writeSummary()
+{
+ printf("= Test cases executed: %d, passed: %d, failed: %d =",
+ mPassedCases.size() + mFailedCases.size(), mPassedCases.size(), mFailedCases.size());
+ printf("= Failed cases =");
+ std::list<android::String8>::iterator it;
+ for (it = mFailedCases.begin(); it != mFailedCases.end(); it++) {
+ printf("* %s", it->string());
+ }
+ printf("= Passed cases =");
+ for (it = mPassedCases.begin(); it != mPassedCases.end(); it++) {
+ printf("* %s", it->string());
+ }
+}
diff --git a/suite/audio_quality/lib/src/Semaphore.cpp b/suite/audio_quality/lib/src/Semaphore.cpp
new file mode 100644
index 0000000..b430cad
--- /dev/null
+++ b/suite/audio_quality/lib/src/Semaphore.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2012 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 "Semaphore.h"
+
+
+Semaphore::Semaphore(int count)
+{
+ if (sem_init(&mSem, 0, count) != 0) {
+ ASSERT(false);
+ }
+}
+
+Semaphore::~Semaphore()
+{
+ sem_destroy(&mSem);
+}
+
+void Semaphore::tryWait()
+{
+ sem_trywait(&mSem);
+}
+
+bool Semaphore::wait()
+{
+ if (sem_wait(&mSem) == 0) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool Semaphore::timedWait(int timeInMSec)
+{
+ const int ONE_SEC_IN_NANOSEC = 1000000000;
+ const int ONE_MSEC_IN_NANOSEC = 1000000;
+ const int ONE_SEC_IN_MSEC = 1000;
+ struct timespec timeOld;
+ if (clock_gettime(CLOCK_REALTIME, &timeOld) != 0) {
+ return false;
+ }
+ int secToGo = timeInMSec / ONE_SEC_IN_MSEC;
+ int msecToGo = timeInMSec - (ONE_SEC_IN_MSEC * secToGo);
+ int nanoSecToGo = ONE_MSEC_IN_NANOSEC * msecToGo;
+ struct timespec timeNew = timeOld;
+ int nanoTotal = timeOld.tv_nsec + nanoSecToGo;
+ //LOGI("secToGo %d, msecToGo %d, nanoTotal %d", secToGo, msecToGo, nanoTotal);
+ if (nanoTotal > ONE_SEC_IN_NANOSEC) {
+ nanoTotal -= ONE_SEC_IN_NANOSEC;
+ secToGo += 1;
+ }
+ timeNew.tv_sec += secToGo;
+ timeNew.tv_nsec = nanoTotal;
+ //LOGV("Semaphore::timedWait now %d-%d until %d-%d for %d msecs",
+ // timeOld.tv_sec, timeOld.tv_nsec, timeNew.tv_sec, timeNew.tv_nsec, timeInMSec);
+ if (sem_timedwait(&mSem, &timeNew) == 0) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void Semaphore::post()
+{
+ sem_post(&mSem);
+}
diff --git a/suite/audio_quality/lib/src/Settings.cpp b/suite/audio_quality/lib/src/Settings.cpp
new file mode 100644
index 0000000..4f78fe4
--- /dev/null
+++ b/suite/audio_quality/lib/src/Settings.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2012 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 "Settings.h"
+#include "Log.h"
+
+Settings* Settings::mInstance = NULL;
+
+Settings* Settings::Instance()
+{
+ if (mInstance == NULL) {
+ mInstance = new Settings();
+ }
+ return mInstance;
+}
+
+void Settings::Finalize()
+{
+ delete mInstance;
+ mInstance = NULL;
+}
+
+
+void Settings::addSetting(SettingType type, const android::String8 setting)
+{
+ // TODO key, string can be better if there are large number of settings
+ switch(type) {
+ case EADB:
+ mAdbSetting = setting;
+ default:
+ ASSERT(false);
+ }
+}
+const android::String8& Settings::getSetting(SettingType type)
+{
+ switch(type) {
+ case EADB:
+ return mAdbSetting;
+ default:
+ ASSERT(false);
+ }
+ return mAdbSetting; // just for removing compiler warning, will not reach here
+}
+
+
+
diff --git a/suite/audio_quality/lib/src/SignalProcessingImpl.cpp b/suite/audio_quality/lib/src/SignalProcessingImpl.cpp
new file mode 100644
index 0000000..fed9866
--- /dev/null
+++ b/suite/audio_quality/lib/src/SignalProcessingImpl.cpp
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2012 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 <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <utils/StrongPointer.h>
+
+#include "Log.h"
+#include "audio/Buffer.h"
+#include "StringUtil.h"
+#include "SimpleScriptExec.h"
+#include "SignalProcessingImpl.h"
+#include "task/TaskCase.h"
+
+enum ToPythonCommandType {
+ EHeader = 0x0,
+ ETerminate = 0x1,
+ EFunctionName = 0x2,
+ EAudioMono = 0x4,
+ EAudioStereo = 0x5,
+ EValue64Int = 0x8,
+ EValueDouble = 0x9,
+ EExecutionResult = 0x10
+};
+
+const android::String8 \
+ SignalProcessingImpl::MAIN_PROCESSING_SCRIPT("test_description/processing_main.py");
+
+SignalProcessingImpl::SignalProcessingImpl()
+ : mChildRunning(false),
+ mBuffer(1024)
+{
+
+}
+
+SignalProcessingImpl::~SignalProcessingImpl()
+{
+ if (mSocket.get() != NULL) {
+ int terminationCommand [] = {ETerminate, 0};
+ send((char*)terminationCommand, sizeof(terminationCommand));
+ mSocket->release();
+ }
+ if (mChildRunning) {
+ waitpid(mChildPid, NULL, 0);
+ }
+}
+
+#define CHILD_LOGE(x...) do { fprintf(stderr, x); \
+ fprintf(stderr, " %s - %d\n", __FILE__, __LINE__); } while(0)
+
+const int CHILD_WAIT_TIME_US = 100000;
+
+bool SignalProcessingImpl::init(const android::String8& script)
+{
+ pid_t pid;
+ if ((pid = fork()) < 0) {
+ LOGE("SignalProcessingImpl::init fork failed %d", errno);
+ return false;
+ } else if (pid == 0) { // child
+ if (execl(SimpleScriptExec::PYTHON_PATH, SimpleScriptExec::PYTHON_PATH,
+ script.string(), NULL) < 0) {
+ CHILD_LOGE("execl %s %s failed %d", SimpleScriptExec::PYTHON_PATH,
+ script.string(), errno);
+ exit(EXIT_FAILURE);
+ }
+ } else { // parent
+ mChildPid = pid;
+ mChildRunning = true;
+ int result = false;
+ int retryCount = 0;
+ // not that clean, but it takes some time for python side to have socket ready
+ const int MAX_RETRY = 20;
+ while (retryCount < MAX_RETRY) {
+ usleep(CHILD_WAIT_TIME_US);
+ mSocket.reset(new ClientSocket());
+ if (mSocket.get() == NULL) {
+ result = false;
+ break;
+ }
+ if (mSocket->init("127.0.0.1", SCRIPT_PORT, true)) {
+ result = true;
+ break;
+ }
+ retryCount++;
+ }
+ if (!result) {
+ LOGE("cannot connect to child");
+ mSocket.reset(NULL);
+ return result;
+ }
+ }
+ return true;
+}
+
+
+TaskGeneric::ExecutionResult SignalProcessingImpl::run( const android::String8& functionScript,
+ int nInputs, bool* inputTypes, void** inputs,
+ int nOutputs, bool* outputTypes, void** outputs)
+{
+ mBuffer.reset();
+ mBuffer.write <int32_t>((int32_t)EHeader);
+ mBuffer.write<int32_t>(nInputs + 1);
+ mBuffer.write<int32_t>((int32_t)EFunctionName);
+ mBuffer.write<int32_t>((int32_t)functionScript.length());
+ mBuffer.writeStr(functionScript);
+ if (!send(mBuffer.getBuffer(), mBuffer.getSizeWritten())) {
+ LOGE("send failed");
+ return TaskGeneric::EResultError;
+ }
+ for (int i = 0; i < nInputs; i++) {
+ mBuffer.reset();
+ if (inputTypes[i]) { // android::sp<Buffer>*
+ android::sp<Buffer>* buffer = reinterpret_cast<android::sp<Buffer>*>(inputs[i]);
+ mBuffer.write<int32_t>((int32_t)((*buffer)->isStereo() ? EAudioStereo : EAudioMono));
+ int dataLen = (*buffer)->getSize();
+ mBuffer.write<int32_t>(dataLen);
+ if (!send(mBuffer.getBuffer(), mBuffer.getSizeWritten())) {
+ LOGE("send failed");
+ return TaskGeneric::EResultError;
+ }
+ if (!send((*buffer)->getData(), dataLen)) {
+ LOGE("send failed");
+ return TaskGeneric::EResultError;
+ }
+ LOGD("%d-th param buffer %d, stereo:%d", dataLen, (*buffer)->isStereo());
+ } else { //TaskCase::Value*
+ TaskCase::Value* val = reinterpret_cast<TaskCase::Value*>(inputs[i]);
+ bool isI64 = (val->getType() == TaskCase::Value::ETypeI64);
+ mBuffer.write<int32_t>((int32_t)(isI64 ? EValue64Int : EValueDouble));
+ if (isI64) {
+ mBuffer.write<int64_t>(val->getInt64());
+ } else {
+ mBuffer.write<double>(val->getDouble());
+ }
+ if (!send(mBuffer.getBuffer(), mBuffer.getSizeWritten())) {
+ LOGE("send failed");
+ return TaskGeneric::EResultError;
+ }
+ LOGD("%d-th param Value", i);
+ }
+ }
+ int32_t header[4]; // id 0 - no of types - id 0x10 - ExecutionResult
+ if (!read((char*)header, sizeof(header))) {
+ LOGE("read failed");
+ return TaskGeneric::EResultError;
+ }
+ if (header[0] != 0) {
+ LOGE("wrong data");
+ return TaskGeneric::EResultError;
+ }
+ if (header[2] != EExecutionResult) {
+ LOGE("wrong data");
+ return TaskGeneric::EResultError;
+ }
+ if (header[3] == TaskGeneric::EResultError) {
+ LOGE("script returned error %d", header[3]);
+ return (TaskGeneric::ExecutionResult)header[3];
+ }
+ if ((header[1] - 1) != nOutputs) {
+ LOGE("wrong data");
+ return TaskGeneric::EResultError;
+ }
+ for (int i = 0; i < nOutputs; i++) {
+ int32_t type;
+ if (!read((char*)&type, sizeof(type))) {
+ LOGE("read failed");
+ return TaskGeneric::EResultError;
+ }
+ if (outputTypes[i]) { // android::sp<Buffer>*
+ int32_t dataLen;
+ if (!read((char*)&dataLen, sizeof(dataLen))) {
+ LOGE("read failed");
+ return TaskGeneric::EResultError;
+ }
+ android::sp<Buffer>* buffer = reinterpret_cast<android::sp<Buffer>*>(outputs[i]);
+ if (buffer->get() == NULL) { // data not allocated, this can happen for unknown-length output
+ *buffer = new Buffer(dataLen, dataLen, (type == EAudioStereo) ? true: false);
+ if (buffer->get() == NULL) {
+ LOGE("alloc failed");
+ return TaskGeneric::EResultError;
+ }
+ }
+ bool isStereo = (*buffer)->isStereo();
+
+ if (((type == EAudioStereo) && isStereo) || ((type == EAudioMono) && !isStereo)) {
+ // valid
+ } else {
+ LOGE("%d-th output wrong type %d stereo: %d", i, type, isStereo);
+ return TaskGeneric::EResultError;
+ }
+
+ if (dataLen > (int)(*buffer)->getSize()) {
+ LOGE("%d-th output data too long %d while buffer size %d", i, dataLen,
+ (*buffer)->getSize());
+ return TaskGeneric::EResultError;
+ }
+ if (!read((*buffer)->getData(), dataLen)) {
+ LOGE("read failed");
+ return TaskGeneric::EResultError;
+ }
+ LOGD("received buffer %x %x", ((*buffer)->getData())[0], ((*buffer)->getData())[1]);
+ (*buffer)->setHandled(dataLen);
+ (*buffer)->setSize(dataLen);
+ } else { //TaskCase::Value*
+ TaskCase::Value* val = reinterpret_cast<TaskCase::Value*>(outputs[i]);
+ if ((type == EValue64Int) || (type == EValueDouble)) {
+ if (!read((char*)val->getPtr(), sizeof(int64_t))) {
+ LOGE("read failed");
+ return TaskGeneric::EResultError;
+ }
+ if (type == EValue64Int) {
+ val->setType(TaskCase::Value::ETypeI64);
+ } else {
+ val->setType(TaskCase::Value::ETypeDouble);
+ }
+ } else {
+ LOGE("wrong type %d", type);
+ return TaskGeneric::EResultError;
+ }
+ }
+ }
+ return (TaskGeneric::ExecutionResult)header[3];
+}
+
+bool SignalProcessingImpl::send(const char* data, int len)
+{
+ //LOGD("send %d", len);
+ return mSocket->sendData(data, len);
+}
+
+bool SignalProcessingImpl::read(char* data, int len)
+{
+ const int READ_TIMEOUT_MS = 60000 * 2; // as some calculation like calc_delay takes almost 20 secs
+ //LOGD("read %d", len);
+ return mSocket->readData(data, len, READ_TIMEOUT_MS);
+}
+
diff --git a/suite/audio_quality/lib/src/SignalProcessingImpl.h b/suite/audio_quality/lib/src/SignalProcessingImpl.h
new file mode 100644
index 0000000..29b410e
--- /dev/null
+++ b/suite/audio_quality/lib/src/SignalProcessingImpl.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#ifndef CTSAUDIO_SIGNALPROCESSINGIMPL_H
+#define CTSAUDIO_SIGNALPROCESSINGIMPL_H
+
+#include <UniquePtr.h>
+#include <utils/String8.h>
+
+#include "SignalProcessingInterface.h"
+#include "ClientSocket.h"
+#include "RWBuffer.h"
+
+/**
+ * Implements SignalProcessingInterface
+ */
+class SignalProcessingImpl: public SignalProcessingInterface {
+public:
+ static const android::String8 MAIN_PROCESSING_SCRIPT;
+ SignalProcessingImpl();
+ virtual ~SignalProcessingImpl();
+ /**
+ * @param script main script to call function script
+ */
+ virtual bool init(const android::String8& script);
+
+ virtual TaskGeneric::ExecutionResult run(const android::String8& functionScript,
+ int nInputs, bool* inputTypes, void** inputs,
+ int nOutputs, bool* outputTypes, void** outputs);
+private:
+ bool send(const char* data, int len);
+ bool read(char* data, int len);
+
+private:
+ static const int SCRIPT_PORT = 15010;
+ UniquePtr<ClientSocket> mSocket;
+ pid_t mChildPid;
+ bool mChildRunning;
+ RWBuffer mBuffer;
+};
+
+
+#endif //CTSAUDIO_SIGNALPROCESSINGIMPL_H
diff --git a/suite/audio_quality/lib/src/SimpleScriptExec.cpp b/suite/audio_quality/lib/src/SimpleScriptExec.cpp
new file mode 100644
index 0000000..0a2045b
--- /dev/null
+++ b/suite/audio_quality/lib/src/SimpleScriptExec.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2012 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 <stdlib.h>
+#include <stdio.h>
+
+#include "Log.h"
+#include "StringUtil.h"
+
+#include "SimpleScriptExec.h"
+
+const char* SimpleScriptExec::PYTHON_PATH = "/usr/bin/python";
+const char* PASS_MAGIC_WORD = "___CTS_AUDIO_PASS___";
+
+bool SimpleScriptExec::checkPythonEnv()
+{
+ android::String8 script("test_description/conf/check_conf.py");
+ android::String8 param;
+ android::String8 result;
+ if (!runScript(script, param, result)) {
+ return false;
+ }
+
+ android::String8 rePattern;
+ return checkIfPassed(result, rePattern);
+}
+
+bool SimpleScriptExec::checkIfPassed(const android::String8& str, const android::String8& reMatch,
+ int nmatch, regmatch_t pmatch[])
+{
+ android::String8 match;
+ match.append(PASS_MAGIC_WORD);
+ match.append(reMatch);
+ LOGV("re match %s", match.string());
+ regex_t re;
+ int cflags = REG_EXTENDED;
+ if (nmatch == 0) {
+ cflags |= REG_NOSUB;
+ }
+ if (regcomp(&re, match.string(), cflags) != 0) {
+ LOGE("regcomp failed");
+ return false;
+ }
+ bool result = false;
+ if (regexec(&re, str.string(), nmatch, pmatch, 0) == 0) {
+ // match found. passed
+ result = true;
+ }
+ regfree(&re);
+ return result;
+}
+
+bool SimpleScriptExec::runScript(const android::String8& script, const android::String8& param,
+ android::String8& result)
+{
+ FILE *fpipe;
+ android::String8 command;
+ command.appendFormat("%s %s %s", PYTHON_PATH, script.string(), param.string());
+ const int READ_SIZE = 1024;
+ char buffer[READ_SIZE];
+ size_t len = 0;
+
+ if ( !(fpipe = (FILE*)popen(command.string(),"r")) ) {
+ LOGE("cannot execute python");
+ return false;
+ }
+ result.clear();
+ while((len = fread(buffer, 1, READ_SIZE, fpipe)) > 0) {
+ result.append(buffer, len);
+ }
+ pclose(fpipe);
+
+ return true;
+}
+
+
+
diff --git a/suite/audio_quality/lib/src/SimpleScriptExec.h b/suite/audio_quality/lib/src/SimpleScriptExec.h
new file mode 100644
index 0000000..28117c4
--- /dev/null
+++ b/suite/audio_quality/lib/src/SimpleScriptExec.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#ifndef CTSAUDIO_SIMPLESCRIPTEXEC_H
+#define CTSAUDIO_SIMPLESCRIPTEXEC_H
+
+#include <sys/types.h>
+#include <regex.h>
+
+#include <utils/String8.h>
+
+/**
+ * Utility class for executing simple scripts
+ * which prints ___CTS_AUDIO_PASS___ string in output
+ */
+class SimpleScriptExec {
+public:
+ static const char* PYTHON_PATH;
+
+ static bool checkPythonEnv();
+ /**
+ * run given script
+ * @param script full path of the script
+ * @param param arguments to pass
+ * @param result
+ */
+ static bool runScript(const android::String8& script, const android::String8& param,
+ android::String8& result);
+
+ /**
+ * check if the given str include magic words for pass.
+ * @param str
+ * @param reMatch pattern to match in re besides the pass string
+ * @param nmatch number of substring pattern match elements. It should be in POSIX
+ * extended RE syntax, no \d nor [:digit:]
+ * @param pmatch pattern match elements
+ * @return true if passed
+ */
+ static bool checkIfPassed(const android::String8& str, const android::String8& reMatch,
+ int nmatch = 0, regmatch_t pmatch[] = NULL);
+};
+
+
+#endif // CTSAUDIO_SIMPLESCRIPTEXEC_H
diff --git a/suite/audio_quality/lib/src/StringUtil.cpp b/suite/audio_quality/lib/src/StringUtil.cpp
new file mode 100644
index 0000000..f23a29a
--- /dev/null
+++ b/suite/audio_quality/lib/src/StringUtil.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2012 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 <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include "Log.h"
+#include "StringUtil.h"
+
+std::vector<android::String8>* StringUtil::split(const android::String8& str, char delimiter)
+{
+ std::vector<android::String8>* tokens = new std::vector<android::String8>();
+ unsigned int lastTokenEnd = 0;
+ for (unsigned int i = 0; i < str.length(); i++) {
+ if (str[i] == delimiter) {
+ if ((i - lastTokenEnd) > 0) {
+ tokens->push_back(substr(str, lastTokenEnd, i - lastTokenEnd));
+ }
+ lastTokenEnd = i + 1; // 1 for skipping delimiter
+ }
+ }
+ if (lastTokenEnd < str.length()) {
+ tokens->push_back(substr(str, lastTokenEnd, str.length() - lastTokenEnd));
+ }
+ return tokens;
+}
+
+android::String8 StringUtil::substr(const android::String8& str, size_t pos, size_t n)
+{
+ size_t l = str.length();
+
+ if (pos >= l) {
+ android::String8 resultDummy;
+ return resultDummy;
+ }
+ if ((pos + n) > l) {
+ n = l - pos;
+ }
+ android::String8 result(str.string() + pos, n);
+ return result;
+}
+
+int StringUtil::compare(const android::String8& str, const char* other)
+{
+ return strcmp(str.string(), other);
+}
+
+bool StringUtil::endsWith(const android::String8& str, const char* other)
+{
+ size_t l1 = str.length();
+ size_t l2 = strlen(other);
+ const char* data = str.string();
+ if (l2 > l1) {
+ return false;
+ }
+ size_t iStr = l1 - l2;
+ size_t iOther = 0;
+ for(; iStr < l1; iStr++) {
+ if (data[iStr] != other[iOther]) {
+ return false;
+ }
+ iOther++;
+ }
+ return true;
+}
diff --git a/suite/audio_quality/lib/src/audio/AudioHardware.cpp b/suite/audio_quality/lib/src/audio/AudioHardware.cpp
new file mode 100644
index 0000000..eeb70f5
--- /dev/null
+++ b/suite/audio_quality/lib/src/audio/AudioHardware.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2012 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 <sys/types.h>
+#include <regex.h>
+#include <stdlib.h>
+
+#include <tinyalsa/asoundlib.h>
+
+#include "Log.h"
+#include "StringUtil.h"
+#include "SimpleScriptExec.h"
+#include "audio/AudioHardware.h"
+#include "audio/Buffer.h"
+#include "audio/AudioPlaybackLocal.h"
+#include "audio/AudioRecordingLocal.h"
+#include "audio/AudioRemote.h"
+#include "task/TaskCase.h"
+
+int AudioHardware::mHwId = -1;
+
+int AudioHardware::detectAudioHw()
+{
+ android::String8 script("test_description/conf/detect_usb_audio.py");
+ android::String8 param("MobilePre");
+ android::String8 resultStr;
+ if (!SimpleScriptExec::runScript(script, param, resultStr)) {
+ LOGE("cannot run script");
+ return -1;
+ }
+
+ android::String8 match("[ \t]+([A-Za-z0-9_]+)[ \t]+([0-9]+)");
+ const int nmatch = 3;
+ regmatch_t pmatch[nmatch];
+ if (!SimpleScriptExec::checkIfPassed(resultStr, match, nmatch, pmatch)) {
+ LOGE("result not correct %s", resultStr.string());
+ return -1;
+ }
+ LOGV("pmatch 0: %d, %d 1:%d, %d 2:%d, %d",
+ pmatch[0].rm_so, pmatch[0].rm_eo,
+ pmatch[1].rm_so, pmatch[1].rm_eo,
+ pmatch[2].rm_so, pmatch[2].rm_eo);
+
+ if (pmatch[1].rm_so == -1) {
+ return -1;
+ }
+ if (pmatch[2].rm_so == -1) {
+ return -1;
+ }
+ android::String8 product = StringUtil::substr(resultStr, pmatch[1].rm_so,
+ pmatch[1].rm_eo - pmatch[1].rm_so);
+ LOGI("Audio device %s found", product.string());
+ android::String8 cardNumber = StringUtil::substr(resultStr, pmatch[2].rm_so,
+ pmatch[2].rm_eo - pmatch[2].rm_so);
+ int cardN = atoi(cardNumber.string());
+ LOGI("Card number : %d", cardN);
+ return cardN;
+}
+
+android::sp<AudioHardware> AudioHardware::createAudioHw(bool local, bool playback,
+ TaskCase* testCase)
+{
+ android::sp<AudioHardware> hw;
+ if (local) {
+ if (mHwId < 0) {
+ mHwId = detectAudioHw();
+ }
+ if (mHwId < 0) {
+ return NULL;
+ }
+ if (playback) {
+ hw = new AudioPlaybackLocal(mHwId);
+ } else {
+ hw = new AudioRecordingLocal(mHwId);
+ }
+ } else {
+ if (testCase != NULL) {
+ if (playback) {
+ hw = new AudioRemotePlayback(testCase->getRemoteAudio());
+ } else {
+ hw = new AudioRemoteRecording(testCase->getRemoteAudio());
+ }
+ }
+ }
+ return hw;
+}
+
+AudioHardware::~AudioHardware()
+{
+
+}
+
+bool AudioHardware::startPlaybackOrRecordById(const android::String8& id, TaskCase* testCase)
+{
+ if (testCase == NULL) { // default implementation only handles local buffer.
+ return false;
+ }
+ android::sp<Buffer> buffer = testCase->findBuffer(id);
+ if (buffer.get() == NULL) {
+ return false;
+ }
+ return startPlaybackOrRecord(buffer);
+}
diff --git a/suite/audio_quality/lib/src/audio/AudioLocal.cpp b/suite/audio_quality/lib/src/audio/AudioLocal.cpp
new file mode 100644
index 0000000..71b7afb
--- /dev/null
+++ b/suite/audio_quality/lib/src/audio/AudioLocal.cpp
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2012 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 <Log.h>
+#include "audio/Buffer.h"
+#include "audio/AudioLocal.h"
+
+bool AudioLocal::prepare(AudioHardware::SamplingRate samplingRate, int gain, int /*mode*/)
+{
+ LOGV("prepare");
+ // gain control not necessary in MobilePre as there is no control.
+ // This means audio source itself should be adjusted to control volume
+ if (mState == EStNone) {
+ if (run() != android::NO_ERROR) {
+ LOGE("AudioLocal cannot run");
+ // cannot run thread
+ return false;
+ }
+ mState = EStCreated;
+ } else if (mState == EStRunning) {
+ // wrong usage. first stop!
+ return false;
+ }
+ mClientCompletionWait.tryWait(); // this will reset semaphore to 0 if it is 1.
+ mSamplingRate = samplingRate;
+ return issueCommandAndWaitForCompletion(ECmInitialize);
+}
+
+bool AudioLocal::startPlaybackOrRecord(android::sp<Buffer>& buffer, int numberRepetition)
+{
+ LOGV("startPlaybackOrRecord");
+ if (mState != EStInitialized) {
+ LOGE("startPlaybackOrRecord while not initialized");
+ // wrong state
+ return false;
+ }
+ mBuffer = buffer;
+ mNumberRepetition = numberRepetition;
+ mCurrentRepeat = 0;
+ return issueCommandAndWaitForCompletion(ECmRun);
+}
+
+bool AudioLocal::waitForCompletion()
+{
+ int waitTimeInMsec = mBuffer->getSamples() / (mSamplingRate/1000);
+ waitTimeInMsec += COMMAND_WAIT_TIME_MSEC;
+ LOGD("waitForCompletion will wait for %d", waitTimeInMsec);
+ if (!mClientCompletionWait.timedWait(waitTimeInMsec)) {
+ LOGE("waitForCompletion time-out");
+ return false;
+ }
+ return mCompletionResult;
+}
+
+void AudioLocal::stopPlaybackOrRecord()
+{
+ LOGV("stopPlaybackOrRecord");
+ if (mState == EStRunning) {
+ issueCommandAndWaitForCompletion(ECmStop);
+ }
+
+ if (mState != EStNone) { // thread alive
+ requestExit();
+ mCurrentCommand = ECmThreadStop;
+ mAudioThreadWait.post();
+ requestExitAndWait();
+ mState = EStNone;
+ }
+}
+
+bool AudioLocal::issueCommandAndWaitForCompletion(AudioCommand command)
+{
+ mCurrentCommand = command;
+ mAudioThreadWait.post();
+ if (!mClientCommandWait.timedWait(COMMAND_WAIT_TIME_MSEC)) {
+ LOGE("issueCommandAndWaitForCompletion timeout cmd %d", command);
+ return false;
+ }
+ return mCommandResult;
+}
+
+AudioLocal::~AudioLocal()
+{
+ LOGV("~AudioLocal");
+}
+
+AudioLocal::AudioLocal()
+ : mState(EStNone),
+ mCurrentCommand(ECmNone),
+ mClientCommandWait(0),
+ mClientCompletionWait(0),
+ mAudioThreadWait(0),
+ mCompletionResult(false)
+{
+ LOGV("AudioLocal");
+}
+
+
+bool AudioLocal::threadLoop()
+{
+ if (mCurrentCommand == ECmNone) {
+ if (mState == EStRunning) {
+ if (doPlaybackOrRecord(mBuffer)) {
+ // check exit condition
+ if (mBuffer->bufferHandled()) {
+ mCurrentRepeat++;
+ LOGV("repeat %d - %d", mCurrentRepeat, mNumberRepetition);
+ if (mCurrentRepeat == mNumberRepetition) {
+ LOGV("AudioLocal complete command");
+ mState = EStInitialized;
+ mCompletionResult = true;
+ mClientCompletionWait.post();
+ } else {
+ mBuffer->restart();
+ }
+ }
+ } else {
+ mState = EStInitialized;
+ //notify error
+ mCompletionResult = false;
+ mClientCompletionWait.post();
+ }
+ return true;
+ }
+ //LOGV("audio thread waiting");
+ mAudioThreadWait.wait();
+ //LOGV("audio thread waken up");
+ if (mCurrentCommand == ECmNone) {
+ return true; // continue to check exit condition
+ }
+ }
+
+ int pendingCommand = mCurrentCommand;
+ // now there is a command
+ switch (pendingCommand) {
+ case ECmInitialize:
+ mCommandResult = doPrepare(mSamplingRate, AudioHardware::SAMPLES_PER_ONE_GO);
+ if (mCommandResult) {
+ mState = EStInitialized;
+ }
+ break;
+ case ECmRun: {
+ mCommandResult = doPlaybackOrRecord(mBuffer);
+ if (mCommandResult) {
+ mState = EStRunning;
+ }
+ }
+ break;
+ case ECmStop:
+ doStop();
+ mState = EStCreated;
+ mCommandResult = true;
+ break;
+ case ECmThreadStop:
+ return false;
+ break;
+ default:
+ // this should not happen
+ ASSERT(false);
+ break;
+ }
+
+ mCurrentCommand = ECmNone;
+ mClientCommandWait.post();
+
+ return true;
+}
+
+
diff --git a/suite/audio_quality/lib/src/audio/AudioPlaybackLocal.cpp b/suite/audio_quality/lib/src/audio/AudioPlaybackLocal.cpp
new file mode 100644
index 0000000..fe91cb3
--- /dev/null
+++ b/suite/audio_quality/lib/src/audio/AudioPlaybackLocal.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+// TODO remove all the headers upto asound.h after removing pcm_drain hack
+#include <errno.h>
+#include <unistd.h>
+#include <poll.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <limits.h>
+#include <linux/ioctl.h>
+#define __force
+#define __bitwise
+#define __user
+#include <sound/asound.h>
+
+#include <string.h>
+#include <tinyalsa/asoundlib.h>
+
+#include "audio/AudioHardware.h"
+#include "audio/Buffer.h"
+#include "Log.h"
+
+#include "audio/AudioPlaybackLocal.h"
+
+
+AudioPlaybackLocal::AudioPlaybackLocal(int hwId)
+ : mHwId(hwId),
+ mPcmHandle(NULL)
+{
+ LOGV("AudioPlaybackLocal %x", (unsigned int)this);
+}
+
+AudioPlaybackLocal::~AudioPlaybackLocal()
+{
+ LOGV("~AudioPlaybackLocal %x", (unsigned int)this);
+ releaseHw();
+}
+
+bool AudioPlaybackLocal::doPrepare(AudioHardware::SamplingRate samplingRate, int samplesInOneGo)
+{
+ releaseHw();
+
+ struct pcm_config config;
+
+ memset(&config, 0, sizeof(config));
+ config.channels = 2;
+ config.rate = samplingRate;
+ config.period_size = 1024;
+ config.period_count = 64;
+ config.format = PCM_FORMAT_S16_LE;
+ config.start_threshold = 0;
+ config.stop_threshold = 0;
+ config.silence_threshold = 0;
+
+ mPcmHandle = pcm_open(mHwId, 0, PCM_OUT, &config);
+ if (!mPcmHandle || !pcm_is_ready(mPcmHandle)) {
+ LOGE("Unable to open PCM device(%d) (%s)\n", mHwId, pcm_get_error(mPcmHandle));
+ return false;
+ }
+
+ mSamples = samplesInOneGo;
+ mSizes = samplesInOneGo * 4; // stereo, 16bit
+
+ return true;
+}
+
+bool AudioPlaybackLocal::doPlaybackOrRecord(android::sp<Buffer>& buffer)
+{
+ if (buffer->amountToHandle() < (size_t)mSizes) {
+ mSizes = buffer->amountToHandle();
+ }
+ if (pcm_write(mPcmHandle, buffer->getUnhanledData(), mSizes)) {
+ LOGE("AudioPlaybackLocal error %s", pcm_get_error(mPcmHandle));
+ return false;
+ }
+ buffer->increaseHandled(mSizes);
+ LOGV("AudioPlaybackLocal::doPlaybackOrRecord %d", buffer->amountHandled());
+ return true;
+}
+
+void AudioPlaybackLocal::doStop()
+{
+ // TODO: remove when pcm_stop does pcm_drain
+ // hack to have snd_pcm_drain equivalent
+ struct pcm_ {
+ int fd;
+ };
+ pcm_* pcm = (pcm_*)mPcmHandle;
+ ioctl(pcm->fd, SNDRV_PCM_IOCTL_DRAIN);
+ pcm_stop(mPcmHandle);
+}
+
+void AudioPlaybackLocal::releaseHw()
+{
+ if (mPcmHandle != NULL) {
+ LOGV("releaseHw %x", (unsigned int)this);
+ doStop();
+ pcm_close(mPcmHandle);
+ mPcmHandle = NULL;
+ }
+}
diff --git a/suite/audio_quality/lib/src/audio/AudioProtocol.cpp b/suite/audio_quality/lib/src/audio/AudioProtocol.cpp
new file mode 100644
index 0000000..a046432
--- /dev/null
+++ b/suite/audio_quality/lib/src/audio/AudioProtocol.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2012 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 <stdint.h>
+#include <arpa/inet.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <utils/StrongPointer.h>
+
+#include "audio/Buffer.h"
+#include "Log.h"
+#include "audio/AudioProtocol.h"
+
+
+bool AudioProtocol::sendCommand(AudioParam& param)
+{
+ mBuffer[0] = htonl(mCommand);
+ mBuffer[1] = 0;
+ return sendData((char*)mBuffer, 8);
+}
+
+bool AudioProtocol::handleReply(const uint32_t* data, AudioParam* param)
+{
+ if (!checkHeaderId(data, mCommand)) {
+ return false;
+ }
+ if (data[1] != 0) { // no endian change for 0
+ LOGE("error in reply %d", ntohl(data[1]));
+ return false;
+ }
+ if (data[2] != 0) {
+ LOGE("payload length %d not zero", ntohl(data[2]));
+ return false;
+ }
+ return true;
+}
+
+bool AudioProtocol::handleReplyHeader(ClientSocket& socket, uint32_t* data, CommandId& id)
+{
+ if (!socket.readData((char*)data, REPLY_HEADER_SIZE)) {
+ LOGE("handleReplyHeader cannot read");
+ return false;
+ }
+ uint32_t command = ntohl(data[0]);
+ if ((command & 0xffff0000) != 0x43210000) {
+ LOGE("Wrong header %x %x", command, data[0]);
+ return false;
+ }
+ command = (command & 0xffff) | 0x12340000; // convert to id
+ if (command < ECmdStart) {
+ LOGE("Wrong header %x %x", command, data[0]);
+ return false;
+ }
+ if (command > (ECmdLast - 1)) {
+ LOGE("Wrong header %x %x", command, data[0]);
+ return false;
+ }
+ id = (CommandId)command;
+ LOGD("received reply with command %x", command);
+ return true;
+}
+
+bool AudioProtocol::checkHeaderId(const uint32_t* data, uint32_t command)
+{
+ if (ntohl(data[0]) != ((command & 0xffff) | 0x43210000)) {
+ LOGE("wrong reply ID 0x%x", ntohl(data[0]));
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * param0 u32 data id
+ * param1 sp<Buffer>
+ */
+bool CmdDownload::sendCommand(AudioParam& param)
+{
+ mBuffer[0] = htonl(ECmdDownload);
+ mBuffer[1] = htonl(4 + param.mBuffer->getSize());
+ mBuffer[2] = htonl(param.mId);
+ if(!sendData((char*)mBuffer, 12)) {
+ return false;
+ }
+ return sendData(param.mBuffer->getData(), param.mBuffer->getSize());
+}
+
+/**
+ * param0 u32 data id
+ * param1 u32 sampling rate
+ * param2 u32 mono / stereo(MSB) | mode
+ * param3 u32 volume
+ * param4 u32 repeat
+ */
+bool CmdStartPlayback::sendCommand(AudioParam& param)
+{
+ mBuffer[0] = htonl(ECmdStartPlayback);
+ mBuffer[1] = htonl(20);
+ mBuffer[2] = htonl(param.mId);
+ mBuffer[3] = htonl(param.mSamplingF);
+ uint32_t mode = param.mStereo ? 1<<31 : 0;
+ mode |= param.mMode;
+ mBuffer[4] = htonl(mode);
+ mBuffer[5] = htonl(param.mVolume);
+ mBuffer[6] = htonl(param.mNumberRepetition);
+
+ return sendData((char*)mBuffer, 28);
+}
+
+
+/**
+ * param0 u32 sampling rate
+ * param1 u32 mono / stereo(MSB) | mode
+ * param2 u32 volume
+ * param3 u32 samples
+ */
+bool CmdStartRecording::sendCommand(AudioParam& param)
+{
+ mBuffer[0] = htonl(ECmdStartRecording);
+ mBuffer[1] = htonl(16);
+ mBuffer[2] = htonl(param.mSamplingF);
+ uint32_t mode = param.mStereo ? 1<<31 : 0;
+ mode |= param.mMode;
+ mBuffer[3] = htonl(mode);
+ mBuffer[4] = htonl(param.mVolume);
+ uint32_t samples = param.mBuffer->getSize() / (param.mStereo ? 4 : 2);
+ mBuffer[5] = htonl(samples);
+
+ return sendData((char*)mBuffer, 24);
+}
+
+/**
+ * param0 sp<Buffer>
+ */
+bool CmdStartRecording::handleReply(const uint32_t* data, AudioParam* param)
+{
+ if (!checkHeaderId(data, ECmdStartRecording)) {
+ return false;
+ }
+ if (data[1] != 0) { // no endian change for 0
+ LOGE("error in reply %d", ntohl(data[1]));
+ return false;
+ }
+ int len = ntohl(data[2]);
+ if (len > (int)param->mBuffer->getCapacity()) {
+ LOGE("received data %d exceeding buffer capacity %d", len, param->mBuffer->getCapacity());
+ // read and throw away
+ //Buffer tempBuffer(len);
+ //readData(tempBuffer.getData(), len);
+ return false;
+ }
+ if (!readData(param->mBuffer->getData(), len)) {
+ return false;
+ }
+ LOGI("received data %d from device", len);
+ param->mBuffer->setHandled(len);
+ param->mBuffer->setSize(len);
+ return true;
+}
+
+
+
+
diff --git a/suite/audio_quality/lib/src/audio/AudioRecordingLocal.cpp b/suite/audio_quality/lib/src/audio/AudioRecordingLocal.cpp
new file mode 100644
index 0000000..1325949
--- /dev/null
+++ b/suite/audio_quality/lib/src/audio/AudioRecordingLocal.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2012 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 <stdint.h>
+#include <string.h>
+#include <tinyalsa/asoundlib.h>
+
+#include "audio/AudioHardware.h"
+#include "audio/Buffer.h"
+#include "Log.h"
+
+#include "audio/AudioRecordingLocal.h"
+
+
+AudioRecordingLocal::AudioRecordingLocal(int hwId)
+ : mHwId(hwId),
+ mPcmHandle(NULL)
+{
+ LOGV("AudioRecordingLocal %x", (unsigned int)this);
+}
+
+AudioRecordingLocal::~AudioRecordingLocal()
+{
+ LOGV("~AudioRecordingLocal %x", (unsigned int)this);
+ releaseHw();
+}
+
+bool AudioRecordingLocal::doPrepare(AudioHardware::SamplingRate samplingRate, int samplesInOneGo)
+{
+ releaseHw();
+
+ struct pcm_config config;
+
+ memset(&config, 0, sizeof(config));
+ config.channels = 2;
+ config.rate = samplingRate;
+ config.period_size = 1024;
+ config.period_count = 32;
+ config.format = PCM_FORMAT_S16_LE;
+ config.start_threshold = 0;
+ config.stop_threshold = 0;
+ config.silence_threshold = 0;
+
+ mPcmHandle = pcm_open(mHwId, 0, PCM_IN, &config);
+ if (!mPcmHandle || !pcm_is_ready(mPcmHandle)) {
+ LOGE("Unable to open PCM device(%d) (%s)\n", mHwId, pcm_get_error(mPcmHandle));
+ return false;
+ }
+
+ mSamples = samplesInOneGo;
+ mSizes = samplesInOneGo * 4; // stereo, 16bit
+
+ mBufferSize = pcm_get_buffer_size(mPcmHandle);
+ LOGD("buffer size %d, read size %d", mBufferSize, mSizes);
+ return true;
+}
+
+bool AudioRecordingLocal::doPlaybackOrRecord(android::sp<Buffer>& buffer)
+{
+ int toRead = mSizes;
+ if (buffer->amountToHandle() < (size_t)mSizes) {
+ toRead = buffer->amountToHandle();
+ }
+ LOGD("recording will read %d", toRead);
+
+ while (toRead > 0) {
+ int readSize = (toRead > mBufferSize) ? mBufferSize : toRead;
+ if (pcm_read(mPcmHandle, buffer->getUnhanledData(), readSize)) {
+ LOGE("AudioRecordingLocal error %s", pcm_get_error(mPcmHandle));
+ return false;
+ }
+ buffer->increaseHandled(readSize);
+ toRead -= readSize;
+ }
+ LOGV("AudioRecordingLocal::doPlaybackOrRecord %d", buffer->amountHandled());
+ return true;
+}
+
+void AudioRecordingLocal::doStop()
+{
+ pcm_stop(mPcmHandle);
+}
+
+void AudioRecordingLocal::releaseHw()
+{
+ if (mPcmHandle != NULL) {
+ LOGV("releaseHw %x", (unsigned int)this);
+ doStop();
+ pcm_close(mPcmHandle);
+ mPcmHandle = NULL;
+ }
+}
diff --git a/suite/audio_quality/lib/src/audio/AudioRemote.cpp b/suite/audio_quality/lib/src/audio/AudioRemote.cpp
new file mode 100644
index 0000000..444a33e
--- /dev/null
+++ b/suite/audio_quality/lib/src/audio/AudioRemote.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2012 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 "Log.h"
+#include "audio/AudioRemote.h"
+#include "audio/RemoteAudio.h"
+
+bool AudioRemote::prepare(AudioHardware::SamplingRate samplingRate, int volume, int mode)
+{
+ if (mRemote == NULL) {
+ LOGE("AudioRemote::prepare mRemote NULL");
+ return false;
+ }
+ mSamplingRate = samplingRate;
+ mVolume = volume;
+ mMode = mode;
+ return true;
+}
+
+AudioRemote::AudioRemote(android::sp<RemoteAudio>& remote)
+ : mRemote(remote)
+{
+
+}
+
+AudioRemotePlayback::AudioRemotePlayback(android::sp<RemoteAudio>& remote)
+ : AudioRemote(remote)
+{
+
+}
+
+bool AudioRemotePlayback::startPlaybackOrRecord(android::sp<Buffer>& buffer, int numberRepetition)
+{
+ //TODO not supported for the moment
+ return false;
+}
+
+bool AudioRemotePlayback::waitForCompletion()
+{
+ return mRemote->waitForPlaybackCompletion();
+}
+
+void AudioRemotePlayback::stopPlaybackOrRecord()
+{
+ mRemote->stopPlayback();
+}
+
+bool AudioRemotePlayback::startPlaybackForRemoteData(int id, bool stereo, int numberRepetition)
+{
+ return mRemote->startPlayback(stereo, mSamplingRate, mMode, mVolume, id, numberRepetition);
+}
+
+AudioRemoteRecording::AudioRemoteRecording(android::sp<RemoteAudio>& remote)
+ : AudioRemote(remote)
+{
+
+}
+
+bool AudioRemoteRecording::startPlaybackOrRecord(android::sp<Buffer>& buffer,
+ int /*numberRepetition*/)
+{
+ bool stereo = buffer->isStereo();
+ return mRemote->startRecording(stereo, mSamplingRate, mMode, mVolume, buffer);
+}
+
+bool AudioRemoteRecording::waitForCompletion()
+{
+ return mRemote->waitForRecordingCompletion();
+}
+
+void AudioRemoteRecording::stopPlaybackOrRecord()
+{
+ mRemote->stopRecording();
+}
+
+
+
diff --git a/suite/audio_quality/lib/src/audio/AudioSignalFactory.cpp b/suite/audio_quality/lib/src/audio/AudioSignalFactory.cpp
new file mode 100644
index 0000000..d2308a3
--- /dev/null
+++ b/suite/audio_quality/lib/src/audio/AudioSignalFactory.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2012 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 <math.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "Log.h"
+#include "audio/AudioSignalFactory.h"
+
+android::sp<Buffer> AudioSignalFactory::generateSineWave(AudioHardware::BytesPerSample BPS,
+ int maxPositive, AudioHardware::SamplingRate samplingRate, int signalFreq,
+ int samples, bool stereo)
+{
+ int bufferSize = samples * (stereo? 2 : 1) * BPS;
+ android::sp<Buffer> buffer(new Buffer(bufferSize));
+ // only 16bit signed
+ ASSERT(BPS == AudioHardware::E2BPS);
+ int16_t* data = reinterpret_cast<int16_t*>(buffer->getData());
+ double multiplier = 2.0 * M_PI * (double)signalFreq / samplingRate;
+ for (int i = 0; i < samples; i++) {
+ double val = sin(multiplier * i) * maxPositive;
+ *data = (int16_t)val;
+ data++;
+ if(stereo) {
+ *data = (int16_t)val;
+ data++;
+ }
+ }
+ buffer->setSize(buffer->getCapacity());
+ return buffer;
+}
+android::sp<Buffer> AudioSignalFactory::generateWhiteNoise(AudioHardware::BytesPerSample BPS,
+ int maxPositive, int samples, bool stereo)
+{
+ int bufferSize = samples * (stereo? 2 : 1) * BPS;
+ android::sp<Buffer> buffer(new Buffer(bufferSize, bufferSize));
+ // only 16bit signed
+ ASSERT(BPS == AudioHardware::E2BPS);
+ srand(123456);
+ int16_t* data = reinterpret_cast<int16_t*>(buffer->getData());
+ int middle = RAND_MAX / 2;
+ double multiplier = (double)maxPositive / middle;
+ for (int i = 0; i < samples; i++) {
+ int val = rand();
+ val = (int16_t)((val - middle) * maxPositive / middle);
+ *data = val;
+ data++;
+ if (stereo) {
+ *data = val;
+ data++;
+ }
+ }
+ buffer->setSize(buffer->getCapacity());
+ return buffer;
+}
+
+android::sp<Buffer> AudioSignalFactory::generateZeroSound(AudioHardware::BytesPerSample BPS,
+ int samples, bool stereo)
+{
+ int bufferSize = samples * (stereo? 2 : 1) * BPS;
+ android::sp<Buffer> buffer(new Buffer(bufferSize, bufferSize));
+ // only 16bit signed
+ ASSERT(BPS == AudioHardware::E2BPS);
+ int16_t* data = reinterpret_cast<int16_t*>(buffer->getData());
+ for (int i = 0; i < samples; i++) {
+ *data = 0;
+ data++;
+ if (stereo) {
+ *data = 0;
+ data++;
+ }
+ }
+ buffer->setSize(buffer->getCapacity());
+ return buffer;
+}
+
+
diff --git a/suite/audio_quality/lib/src/audio/Buffer.cpp b/suite/audio_quality/lib/src/audio/Buffer.cpp
new file mode 100644
index 0000000..316374a
--- /dev/null
+++ b/suite/audio_quality/lib/src/audio/Buffer.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2012 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 <stdint.h>
+#include <iostream>
+#include <fstream>
+#include <utils/String8.h>
+#include "Log.h"
+#include "StringUtil.h"
+#include "audio/Buffer.h"
+
+Buffer::Buffer(size_t capacity, size_t size, bool stereo)
+ : mCapacity(capacity),
+ mSize(size),
+ mHandled(0),
+ mStereo(stereo)
+{
+ mData = new char[capacity];
+ //LOGV("Buffer %d data %x", capacity, (unsigned int)mData);
+ // assume 4bytes alignment
+ ASSERT(((long)mData & 0x3) == 0);
+ // filling with zero just to make valgrind happy.
+ // Otherwise, valgrind will complain about uninitialized data for all captured data
+ memset(mData, capacity, 0);
+};
+
+Buffer::~Buffer()
+{
+ delete[] mData;
+ //LOGV("~Buffer %d", mCapacity);
+}
+
+void Buffer::changeToMono(ConvertOption option)
+{
+ size_t newSize = mSize/2;
+ int16_t* data = reinterpret_cast<int16_t*>(mData);
+ if (option == EKeepCh0) {
+ for (size_t i = 0; i < newSize/2; i++) { //16bpp only
+ int16_t l = data[i * 2];
+ data[i] = l;
+ }
+ } else if (option == EKeepCh1) {
+ for (size_t i = 0; i < newSize/2; i++) { //16bpp only
+ int16_t r = data[i * 2 + 1];
+ data[i] = r;
+ }
+ } else { // average
+ for (size_t i = 0; i < newSize/2; i++) { //16bpp only
+ int16_t l = data[i * 2];
+ int16_t r = data[i * 2 + 1];
+ int16_t avr = (int16_t)(((int32_t)l + (int32_t)r)/2);
+ data[i] = avr;
+ }
+ }
+ mSize = newSize;
+ mHandled /= 2;
+ mStereo = false;
+}
+
+bool Buffer::changeToStereo()
+{
+ //TODO ChangeToStereo
+ return false;
+}
+
+const char* EXTENSION_S16_STEREO = ".r2s";
+const char* EXTENSION_S16_MONO = ".r2m";
+Buffer* Buffer::loadFromFile(const android::String8& filename)
+{
+ bool stereo;
+ if (StringUtil::endsWith(filename, EXTENSION_S16_STEREO)) {
+ stereo = true;
+ } else if (StringUtil::endsWith(filename, EXTENSION_S16_MONO)) {
+ stereo = false;
+ } else {
+ LOGE("Buffer::loadFromFile specified file %s has unknown extension.", filename.string());
+ return NULL;
+ }
+ std::ifstream file(filename.string(), std::ios::in | std::ios::binary |
+ std::ios::ate);
+ if (!file.is_open()) {
+ LOGE("Buffer::loadFromFile cannot open file %s.", filename.string());
+ return NULL;
+ }
+ size_t size = file.tellg();
+ Buffer* buffer = new Buffer(size, size, stereo);
+ if (buffer == NULL) {
+ return NULL;
+ }
+ file.seekg(0, std::ios::beg);
+ file.read(buffer->mData, size); //TODO handle read error
+ file.close();
+ return buffer;
+}
+
+bool Buffer::saveToFile(const android::String8& filename)
+{
+ android::String8 filenameWithExtension(filename);
+ if (isStereo()) {
+ filenameWithExtension.append(EXTENSION_S16_STEREO);
+ } else {
+ filenameWithExtension.append(EXTENSION_S16_MONO);
+ }
+ std::ofstream file(filenameWithExtension.string(), std::ios::out | std::ios::binary |
+ std::ios::trunc);
+ if (!file.is_open()) {
+ LOGE("Buffer::saveToFile cannot create file %s.",
+ filenameWithExtension.string());
+ return false;
+ }
+ file.write(mData, mSize);
+ bool writeOK = true;
+ if (file.rdstate() != std::ios_base::goodbit) {
+ LOGE("Got error while writing file %s %x", filenameWithExtension.string(), file.rdstate());
+ writeOK = false;
+ }
+ file.close();
+ return writeOK;
+}
+
+bool Buffer::operator == (const Buffer& b) const
+{
+ if (mStereo != b.mStereo) {
+ LOGD("stereo mismatch %d %d", mStereo, b.mStereo);
+ return false;
+ }
+ if (mSize != b.mSize) {
+ LOGD("size mismatch %d %d", mSize, b.mSize);
+ return false;
+ }
+ for (size_t i = 0; i < mSize; i++) {
+ if (mData[i] != b.mData[i]) {
+ LOGD("%d %x vs %x", i, mData[i], b.mData[i]);
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/suite/audio_quality/lib/src/audio/RemoteAudio.cpp b/suite/audio_quality/lib/src/audio/RemoteAudio.cpp
new file mode 100644
index 0000000..10ae14c
--- /dev/null
+++ b/suite/audio_quality/lib/src/audio/RemoteAudio.cpp
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2012 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 <arpa/inet.h>
+#include <strings.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <utils/Looper.h>
+
+#include "Log.h"
+#include "audio/AudioProtocol.h"
+#include "audio/RemoteAudio.h"
+
+
+RemoteAudio::RemoteAudio(ClientSocket& socket)
+ : mExitRequested(false),
+ mSocket(socket),
+ mDownloadHandler(new CommandHandler(*this, (int)AudioProtocol::ECmdDownload)),
+ mPlaybackHandler(new CommandHandler(*this, (int)AudioProtocol::ECmdStartPlayback)),
+ mRecordingHandler(new CommandHandler(*this, (int)AudioProtocol::ECmdStartRecording)),
+ mDownloadId(0)
+{
+ mCmds[AudioProtocol::ECmdDownload - AudioProtocol::ECmdStart] = new CmdDownload(socket);
+ mCmds[AudioProtocol::ECmdStartPlayback - AudioProtocol::ECmdStart] =
+ new CmdStartPlayback(socket);
+ mCmds[AudioProtocol::ECmdStopPlayback - AudioProtocol::ECmdStart] =
+ new CmdStopPlayback(socket);
+ mCmds[AudioProtocol::ECmdStartRecording - AudioProtocol::ECmdStart] =
+ new CmdStartRecording(socket);
+ mCmds[AudioProtocol::ECmdStopRecording - AudioProtocol::ECmdStart] =
+ new CmdStopRecording(socket);
+}
+
+RemoteAudio::~RemoteAudio()
+{
+ for (int i = 0; i < (AudioProtocol::ECmdLast - AudioProtocol::ECmdStart); i++) {
+ delete mCmds[i];
+ }
+ //mBufferList.clear();
+}
+
+bool RemoteAudio::init(int port)
+{
+ mPort = port;
+ if (run() != android::NO_ERROR) {
+ LOGE("RemoteAudio cannot run");
+ // cannot run thread
+ return false;
+ }
+
+ if (!mInitWait.timedWait(CLIENT_WAIT_TIMEOUT_MSEC)) {
+ return false;
+ }
+ return mInitResult;
+}
+
+bool RemoteAudio::threadLoop()
+{
+ // initial action until socket connection done by init
+ mLooper = new android::Looper(false);
+ if (mLooper.get() == NULL) {
+ wakeClient(false);
+ return false;
+ }
+ android::Looper::setForThread(mLooper);
+
+ if (!mSocket.init("127.0.0.1", mPort)) {
+ wakeClient(false);
+ return false;
+ }
+ LOGD("adding fd %d to polling", mSocket.getFD());
+ mLooper->addFd(mSocket.getFD(), EIdSocket, ALOOPER_EVENT_INPUT, socketRxCallback, this);
+ wakeClient(true);
+ while(!mExitRequested) {
+ mLooper->pollOnce(10000);
+ }
+ return false; // exit without requestExit()
+}
+
+void RemoteAudio::wakeClient(bool result)
+{
+ mInitResult = result;
+ mInitWait.post();
+}
+
+bool RemoteAudio::handlePacket()
+{
+ uint32_t data[AudioProtocol::REPLY_HEADER_SIZE/sizeof(uint32_t)];
+ AudioProtocol::CommandId id;
+ if (!AudioProtocol::handleReplyHeader(mSocket, data, id)) {
+ return false;
+ }
+ AudioParam* param = NULL;
+ CommandHandler* handler = NULL;
+ if (id == AudioProtocol::ECmdDownload) {
+ handler = reinterpret_cast<CommandHandler*>(mDownloadHandler.get());
+ } else if (id == AudioProtocol::ECmdStartPlayback) {
+ handler = reinterpret_cast<CommandHandler*>(mPlaybackHandler.get());
+ } else if (id == AudioProtocol::ECmdStartRecording) {
+ handler = reinterpret_cast<CommandHandler*>(mRecordingHandler.get());
+ param = &(handler->getParam());
+ }
+ bool result = mCmds[id - AudioProtocol::ECmdStart]->handleReply(data, param);
+ if (handler != NULL) {
+ LOGD("handler present. Notify client");
+ android::Mutex::Autolock lock(handler->mStateLock);
+ if (handler->mNotifyOnReply) {
+ handler->mNotifyOnReply = false;
+ handler->mResult = result;
+ handler->mClientWait.post();
+ }
+ handler->mActive = false;
+ }
+ return result;
+}
+
+int RemoteAudio::socketRxCallback(int fd, int events, void* data)
+{
+ RemoteAudio* self = reinterpret_cast<RemoteAudio*>(data);
+ if (events & ALOOPER_EVENT_INPUT) {
+ //LOGD("socketRxCallback input");
+ if (!self->handlePacket()) { //error, stop polling
+ LOGE("socketRxCallback, error in packet, stopping polling");
+ return 0;
+ }
+ }
+ return 1;
+}
+
+void RemoteAudio::sendCommand(android::sp<android::MessageHandler>& command)
+{
+ mLooper->sendMessage(command, toCommandHandler(command)->getMessage());
+}
+
+bool RemoteAudio::waitForCompletion(android::sp<android::MessageHandler>& command, int timeInMSec)
+{
+ return toCommandHandler(command)->timedWait(timeInMSec);
+}
+
+bool RemoteAudio::waitForPlaybackOrRecordingCompletion(
+ android::sp<android::MessageHandler>& commandHandler)
+{
+ CommandHandler* handler = reinterpret_cast<CommandHandler*>(commandHandler.get());
+ handler->mStateLock.lock();
+ if(!handler->mActive) {
+ handler->mStateLock.unlock();
+ return true;
+ }
+ int runTime = handler->getParam().mBuffer->getSize() /
+ (handler->getParam().mStereo ? 4 : 2) * 1000 / handler->getParam().mSamplingF;
+ handler->mNotifyOnReply = true;
+ handler->mStateLock.unlock();
+ return waitForCompletion(commandHandler, runTime + CLIENT_WAIT_TIMEOUT_MSEC);
+}
+
+void RemoteAudio::doStop(android::sp<android::MessageHandler>& commandHandler,
+ AudioProtocol::CommandId id)
+{
+ CommandHandler* handler = reinterpret_cast<CommandHandler*>(commandHandler.get());
+ handler->mStateLock.lock();
+ if (!handler->mActive) {
+ handler->mStateLock.unlock();
+ return;
+ }
+ handler->mActive = false;
+ handler->mNotifyOnReply = false;
+ handler->mStateLock.unlock();
+ android::sp<android::MessageHandler> command(new CommandHandler(*this, (int)id));
+ sendCommand(command);
+ waitForCompletion(command, CLIENT_WAIT_TIMEOUT_MSEC);
+}
+
+
+bool RemoteAudio::downloadData(const android::String8 name, android::sp<Buffer>& buffer, int& id)
+{
+ CommandHandler* handler = reinterpret_cast<CommandHandler*>(mDownloadHandler.get());
+ id = mDownloadId;
+ mDownloadId++;
+ handler->getParam().mId = id;
+ handler->getParam().mBuffer = buffer;
+ sendCommand(mDownloadHandler);
+ handler->mStateLock.lock();
+ handler->mNotifyOnReply = true;
+ handler->mStateLock.unlock();
+ // assume 1Mbps ==> 1000 bits per msec ==> 125 bytes per msec
+ int maxWaitTime = CLIENT_WAIT_TIMEOUT_MSEC + buffer->getSize() / 125;
+ // client blocked until reply comes from DUT
+ if (!waitForCompletion(mDownloadHandler, maxWaitTime)) {
+ LOGE("timeout");
+ return false;
+ }
+ mBufferList[id] = buffer;
+ mIdMap[name] = id;
+ return handler->mResult;
+}
+
+int RemoteAudio::getDataId(const android::String8& name)
+{
+ std::map<android::String8, int>::iterator it;
+ it = mIdMap.find(name);
+ if (it == mIdMap.end()) {
+ LOGE("Buffer name %s not registered", name.string());
+ return -1;
+ }
+ return it->second;
+}
+
+bool RemoteAudio::startPlayback(bool stereo, int samplingF, int mode, int volume,
+ int id, int numberRepetition)
+{
+ CommandHandler* handler = reinterpret_cast<CommandHandler*>(mPlaybackHandler.get());
+ handler->mStateLock.lock();
+ if (handler->mActive) {
+ LOGE("busy");
+ handler->mStateLock.unlock();
+ return false;
+ }
+ std::map<int, android::sp<Buffer> >::iterator it;
+ it = mBufferList.find(id);
+ if (it == mBufferList.end()) {
+ LOGE("Buffer id %d not registered", id);
+ return false;
+ }
+ handler->mActive = true;
+ handler->getParam().mStereo = stereo;
+ handler->getParam().mSamplingF = samplingF;
+ handler->getParam().mMode = mode;
+ handler->getParam().mVolume = volume;
+ handler->getParam().mId = id;
+ // for internal tracking
+ handler->getParam().mBuffer = it->second;
+ handler->getParam().mNumberRepetition = numberRepetition;
+ handler->mStateLock.unlock();
+ sendCommand(mPlaybackHandler);
+ if (!waitForCompletion(mPlaybackHandler, CLIENT_WAIT_TIMEOUT_MSEC)) {
+ LOGE("timeout");
+ return false;
+ }
+ return handler->mResult;
+}
+
+void RemoteAudio::stopPlayback()
+{
+ doStop(mPlaybackHandler, AudioProtocol::ECmdStopPlayback);
+}
+
+bool RemoteAudio::waitForPlaybackCompletion()
+{
+ return waitForPlaybackOrRecordingCompletion(mPlaybackHandler);
+}
+
+bool RemoteAudio::startRecording(bool stereo, int samplingF, int mode, int volume,
+ android::sp<Buffer>& buffer)
+{
+ CommandHandler* handler = reinterpret_cast<CommandHandler*>(mRecordingHandler.get());
+ handler->mStateLock.lock();
+ if (handler->mActive) {
+ LOGE("busy");
+ handler->mStateLock.unlock();
+ return false;
+ }
+ handler->mActive = true;
+ handler->getParam().mStereo = stereo;
+ handler->getParam().mSamplingF = samplingF;
+ handler->getParam().mMode = mode;
+ handler->getParam().mVolume = volume;
+ handler->getParam().mBuffer = buffer;
+ handler->mStateLock.unlock();
+ sendCommand(mRecordingHandler);
+ if (!waitForCompletion(mRecordingHandler, CLIENT_WAIT_TIMEOUT_MSEC)) {
+ LOGE("timeout");
+ return false;
+ }
+ return handler->mResult;
+}
+
+bool RemoteAudio::waitForRecordingCompletion()
+{
+ return waitForPlaybackOrRecordingCompletion(mRecordingHandler);
+}
+
+void RemoteAudio::stopRecording()
+{
+ doStop(mRecordingHandler, AudioProtocol::ECmdStopRecording);
+}
+
+/** should be called before RemoteAudio is destroyed */
+void RemoteAudio::release()
+{
+ android::sp<android::MessageHandler> command(new CommandHandler(*this, CommandHandler::EExit));
+ sendCommand(command);
+ join(); // wait for exit
+ mSocket.release();
+}
+
+void RemoteAudio::CommandHandler::handleMessage(const android::Message& message)
+{
+ switch(message.what) {
+ case EExit:
+ LOGD("thread exit requested, will exit ");
+ mResult = true;
+ mThread.mExitRequested = true;
+ mClientWait.post(); // client will not wait, but just do it.
+ break;
+ case AudioProtocol::ECmdDownload:
+ case AudioProtocol::ECmdStartPlayback:
+ case AudioProtocol::ECmdStopPlayback:
+ case AudioProtocol::ECmdStartRecording:
+ case AudioProtocol::ECmdStopRecording:
+ {
+ mResult = (mThread.mCmds[message.what - AudioProtocol::ECmdStart]) \
+ ->sendCommand(mParam);
+ // no post for download. Client blocked until reply comes with time-out
+ if (message.what != AudioProtocol::ECmdDownload) {
+ mClientWait.post();
+ }
+
+ }
+ break;
+
+ }
+}
diff --git a/suite/audio_quality/lib/src/task/ModelBuilder.cpp b/suite/audio_quality/lib/src/task/ModelBuilder.cpp
new file mode 100644
index 0000000..87373ca
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/ModelBuilder.cpp
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2012 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 <tinyxml.h>
+
+#include <UniquePtr.h>
+
+#include "Log.h"
+#include "GenericFactory.h"
+#include "task/ModelBuilder.h"
+
+static const int MAX_NO_CHILDREN = 8;
+static const ModelBuilder::ChildInfo CASE_TABLE[] = {
+ { TaskGeneric::ETaskSetup, true },
+ { TaskGeneric::ETaskAction, true },
+ { TaskGeneric::ETaskSave, false }
+};
+static const ModelBuilder::ChildInfo SETUP_TABLE[] = {
+ { TaskGeneric::ETaskSound, false },
+ { TaskGeneric::ETaskProcess, false }
+};
+static const ModelBuilder::ChildInfo ACTION_TABLE[] = {
+ { TaskGeneric::ETaskSequential, true }
+};
+static const ModelBuilder::ChildInfo SEQUENTIAL_TABLE[] = {
+ { TaskGeneric::ETaskSequential, false },
+ { TaskGeneric::ETaskInput, false },
+ { TaskGeneric::ETaskOutput, false },
+ { TaskGeneric::ETaskProcess, false },
+ { TaskGeneric::ETaskMessage, false }
+};
+
+
+ModelBuilder::ParsingInfo ModelBuilder::mParsingTable[ModelBuilder::PARSING_TABLE_SIZE] = {
+ { "case", TaskGeneric::ETaskCase, CASE_TABLE,
+ sizeof(CASE_TABLE)/sizeof(ModelBuilder::ChildInfo) },
+ { "setup", TaskGeneric::ETaskSetup, SETUP_TABLE,
+ sizeof(SETUP_TABLE)/sizeof(ModelBuilder::ChildInfo) },
+ { "action", TaskGeneric::ETaskAction, ACTION_TABLE,
+ sizeof(ACTION_TABLE)/sizeof(ModelBuilder::ChildInfo) },
+ { "sequential", TaskGeneric::ETaskSequential, SEQUENTIAL_TABLE,
+ sizeof(SEQUENTIAL_TABLE)/sizeof(ModelBuilder::ChildInfo) },
+ { "process", TaskGeneric::ETaskProcess, NULL, 0 },
+ { "input", TaskGeneric::ETaskInput, NULL, 0 },
+ { "output", TaskGeneric::ETaskOutput, NULL, 0 },
+ { "sound", TaskGeneric::ETaskSound, NULL, 0 },
+ { "save", TaskGeneric::ETaskSave, NULL, 0 },
+ { "message", TaskGeneric::ETaskMessage, NULL, 0 }
+};
+
+
+ModelBuilder::ModelBuilder()
+ : mFactory(new GenericFactory())
+{
+
+}
+
+ModelBuilder::ModelBuilder(GenericFactory* factory)
+ : mFactory(factory)
+{
+
+}
+ModelBuilder::~ModelBuilder()
+{
+ delete mFactory;
+}
+
+TaskGeneric* ModelBuilder::parseTestDescriptionXml(const android::String8& xmlFileName,
+ bool caseOnly)
+{
+ TiXmlDocument doc(xmlFileName.string());
+ if (!doc.LoadFile()) {
+ LOGE("ModelBuilder::parseTestDescriptionXml cannot load file %s", xmlFileName.string());
+ return NULL;
+ }
+ const TiXmlElement* root;
+ if ((root = doc.FirstChildElement("case")) != NULL) {
+ return parseCase(*root);
+ } else if (!caseOnly && ((root = doc.FirstChildElement("batch")) != NULL)) {
+ return parseBatch(*root, xmlFileName);
+ } else {
+ LOGE("ModelBuilder::parseTestDescriptionXml wrong root element");
+ return NULL;
+ }
+}
+
+TaskGeneric* ModelBuilder::parseGeneric(const TiXmlElement& self, int tableIndex)
+{
+ TaskGeneric::TaskType typeSelf(mParsingTable[tableIndex].type);
+ int Nchildren = mParsingTable[tableIndex].Nchildren;
+ UniquePtr<TaskGeneric> taskSelf(mFactory->createTask(typeSelf));
+ if (taskSelf.get() == NULL) {
+ return NULL;
+ }
+ if (!parseAttributes(self, *taskSelf.get())) {
+ return NULL;
+ }
+ // copy mandatory flags, and will be cleared once the item is found
+ bool mandatoryAbsence[MAX_NO_CHILDREN];
+ const ModelBuilder::ChildInfo* childTable = mParsingTable[tableIndex].allowedChildren;
+ for (int i = 0; i < Nchildren; i++) {
+ mandatoryAbsence[i] = childTable[i].mandatory;
+ }
+
+ // handle children
+ const TiXmlElement* child = self.FirstChildElement();
+ while (child != NULL) {
+ TaskGeneric::TaskType childType(TaskGeneric::ETaskInvalid);
+ int i;
+ // check if type is valid
+ for (i = 0; i < PARSING_TABLE_SIZE; i++) {
+ if (strcmp(child->Value(), mParsingTable[i].name) == 0) {
+ break;
+ }
+ }
+ if (i == PARSING_TABLE_SIZE) {
+ LOGE("ModelBuilder::parseGeneric unknown element %s", child->Value());
+ return NULL;
+ }
+ childType = mParsingTable[i].type;
+ int j;
+ // check if the type is allowed as child
+ for (j = 0; j < Nchildren; j++) {
+ if (childTable[j].type == childType) {
+ if (childTable[j].mandatory) {
+ mandatoryAbsence[j] = false;
+ }
+ break;
+ }
+ }
+ if (j == Nchildren) {
+ LOGE("ModelBuilder::parseGeneric unsupported child type %d for type %d", childType,
+ typeSelf);
+ return NULL;
+ }
+ UniquePtr<TaskGeneric> taskChild(parseGeneric(*child, i));
+ if (taskChild.get() == NULL) {
+ LOGE("ModelBuilder::parseGeneric failed in parsing child type %d for type %d",
+ childType, typeSelf);
+ return NULL;
+ }
+ if (!taskSelf.get()->addChild(taskChild.get())) {
+ LOGE("ModelBuilder::parseGeneric cannot add child type %d to type %d", childType,
+ typeSelf);
+ return NULL;
+ }
+ TaskGeneric* donotuse = taskChild.release();
+
+ child = child->NextSiblingElement();
+ }
+ for (int i = 0; i < Nchildren; i++) {
+ if (mandatoryAbsence[i]) {
+ LOGE("ModelBuilder::parseGeneric mandatory child type %d not present in type %d",
+ childTable[i].type, typeSelf);
+ return NULL;
+ }
+ }
+
+ return taskSelf.release();
+}
+
+
+TaskCase* ModelBuilder::parseCase(const TiXmlElement& root)
+{
+ // position 0 of mParsingTable should be "case"
+ return reinterpret_cast<TaskCase*>(parseGeneric(root, 0));
+}
+
+
+TaskBatch* ModelBuilder::parseBatch(const TiXmlElement& root, const android::String8& xmlFileName)
+{
+ UniquePtr<TaskBatch> batch(
+ reinterpret_cast<TaskBatch*>(mFactory->createTask(TaskGeneric::ETaskBatch)));
+ if (batch.get() == NULL) {
+ LOGE("ModelBuilder::handleBatch cannot create TaskBatch");
+ return NULL;
+ }
+ if (!parseAttributes(root, *batch.get())) {
+ return NULL;
+ }
+
+ const TiXmlElement* inc = root.FirstChildElement("include");
+ if (inc == NULL) {
+ LOGE("ModelBuilder::handleBatch no include inside batch");
+ return NULL;
+ }
+ android::String8 path = xmlFileName.getPathDir();
+
+ UniquePtr<TaskCase> testCase;
+ int i = 0;
+ while (1) {
+ if (inc == NULL) {
+ break;
+ }
+ if (strcmp(inc->Value(),"include") != 0) {
+ LOGE("ModelBuilder::handleBatch invalid element %s", inc->Value());
+ }
+ testCase.reset(parseInclude(*inc, path));
+ if (testCase.get() == NULL) {
+ LOGE("ModelBuilder::handleBatch cannot create test case from include");
+ return NULL;
+ }
+ if (!batch.get()->addChild(testCase.get())) {
+ return NULL;
+ }
+ TaskGeneric* donotuse = testCase.release(); // parent will take care of destruction.
+ inc = inc->NextSiblingElement();
+ i++;
+ }
+ if (i == 0) {
+ // at least one include should exist.
+ LOGE("ModelBuilder::handleBatch no include elements");
+ return NULL;
+ }
+
+ return batch.release();
+}
+
+TaskCase* ModelBuilder::parseInclude(const TiXmlElement& elem, const android::String8& path)
+{
+ const char* fileName = elem.Attribute("file");
+ if (fileName == NULL) {
+ LOGE("ModelBuilder::handleBatch no include elements");
+ return NULL;
+ }
+ android::String8 incFile = path;
+ incFile.appendPath(fileName);
+
+ // again no dynamic_cast intentionally
+ return reinterpret_cast<TaskCase*>(parseTestDescriptionXml(incFile, true));
+}
+
+bool ModelBuilder::parseAttributes(const TiXmlElement& elem, TaskGeneric& task)
+{
+ const TiXmlAttribute* attr = elem.FirstAttribute();
+ while (1) {
+ if (attr == NULL) {
+ break;
+ }
+ android::String8 name(attr->Name());
+ android::String8 value(attr->Value());
+ if (!task.parseAttribute(name, value)) {
+ LOGE("ModelBuilder::parseAttributes cannot parse attribute %s:%s for task type %d",
+ attr->Name(), attr->Value(), task.getType());
+ return false;
+ }
+ attr = attr->Next();
+ }
+ return true;
+}
diff --git a/suite/audio_quality/lib/src/task/TaskAsync.cpp b/suite/audio_quality/lib/src/task/TaskAsync.cpp
new file mode 100644
index 0000000..4121a5e
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskAsync.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2012 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 <stdlib.h>
+#include "Log.h"
+#include "StringUtil.h"
+#include "task/TaskAll.h"
+
+TaskAsync::TaskAsync(TaskType type)
+ : TaskGeneric(type),
+ mVolume(-1),
+ mDeviceType(EDeviceHost),
+ mMode(AudioHardware::EModeVoice),
+ mAsynchronous(false)
+{
+ // nothing to do
+}
+
+TaskAsync::~TaskAsync()
+{
+
+}
+
+TaskGeneric::ExecutionResult TaskAsync::run()
+{
+ // id is mandatory
+ if (mId.length() == 0) {
+ LOGE(" TaskAsync::run no id attribute");
+ return TaskGeneric::EResultError;
+ }
+ TaskGeneric::ExecutionResult result = start();
+ if (result == TaskGeneric::EResultOK) {
+ if (!isAsynchronous()) {
+ return complete();
+ } else {
+ if (!getParentSequential()->queueAsyncTask(const_cast<TaskAsync*>(this))) {
+ LOGE("TaskAsync::run queueAsyncTask failed");
+ return TaskGeneric::EResultError;
+ }
+ }
+ }
+ return result;
+}
+
+bool TaskAsync::parseAttribute(const android::String8& name, const android::String8& value)
+{
+ bool result = true;
+ if (StringUtil::compare(name, "id") == 0) {
+ mId.append(value);
+ } else if (StringUtil::compare(name, "gain") == 0) {
+ mVolume = atoi(value.string());
+ if ((mVolume < 1) || (mVolume > 100)) {
+ LOGE("TaskGeneric::parseAttribute gain out of range %d", mVolume);
+ return false;
+ }
+ } else if (StringUtil::compare(name, "sync") == 0) {
+ if (StringUtil::compare(value, "start") == 0) { // async
+ makeAsynchronous();
+ }
+ } else if (StringUtil::compare(name, "device") == 0) {
+ if (StringUtil::compare(value, "host") == 0) {
+ mDeviceType = EDeviceHost;
+ } else if (StringUtil::compare(value, "DUT") == 0) {
+ mDeviceType = EDeviceDUT;
+ } else {
+ return false;
+ }
+ } else if (StringUtil::compare(name, "mode") == 0) {
+ if (StringUtil::compare(value, "voice") == 0) {
+ mMode = AudioHardware::EModeVoice;
+ } else if (StringUtil::compare(value, "music") == 0) {
+ mMode = AudioHardware::EModeMusic;
+ } else {
+ return false;
+ }
+ } else {
+ result = TaskGeneric::parseAttribute(name, value);
+ }
+ return result;
+}
+
+TaskSequential* TaskAsync::getParentSequential()
+{
+ ASSERT(getParent()->getType() == TaskGeneric::ETaskSequential);
+ return reinterpret_cast<TaskSequential*>(getParent());
+}
+
diff --git a/suite/audio_quality/lib/src/task/TaskBatch.cpp b/suite/audio_quality/lib/src/task/TaskBatch.cpp
new file mode 100644
index 0000000..f8c77fe
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskBatch.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2012 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 "Log.h"
+#include "Report.h"
+
+#include "task/TaskBatch.h"
+
+static const android::String8 STR_NAME("name");
+static const android::String8 STR_VERSION("version");
+static const android::String8 STR_DESCRIPTION("description");
+
+TaskBatch::TaskBatch()
+ :TaskGeneric(TaskGeneric::ETaskBatch)
+{
+ const android::String8* list[] = {&STR_NAME, &STR_VERSION, &STR_DESCRIPTION, NULL};
+ registerSupportedStringAttributes(list);
+}
+
+TaskBatch::~TaskBatch()
+{
+
+}
+
+bool TaskBatch::addChild(TaskGeneric* child)
+{
+ if (child->getType() != TaskGeneric::ETaskCase) {
+ LOGE("TaskBatch::addChild wrong child type %d", child->getType());
+ return false;
+ }
+ return TaskGeneric::addChild(child);
+}
+
+bool runAlways(TaskGeneric* child, void* data)
+{
+ child->run();
+ return true;
+}
+
+TaskGeneric::ExecutionResult TaskBatch::run()
+{
+ android::String8 name;
+ android::String8 version;
+
+ if (!findStringAttribute(STR_NAME, name) || !findStringAttribute(STR_VERSION, version)) {
+ LOGW("TaskBatch::run no name or version information");
+ }
+ Report::Instance()->printf("= Test batch %s version %s started. =", name.string(),
+ version.string());
+ bool result = TaskGeneric::forEachChild(runAlways, NULL);
+ Report::Instance()->printf("= Finished Test batch =");
+ return TaskGeneric::EResultOK;
+}
+
+
diff --git a/suite/audio_quality/lib/src/task/TaskCase.cpp b/suite/audio_quality/lib/src/task/TaskCase.cpp
new file mode 100644
index 0000000..9cbc6c8
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskCase.cpp
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2012 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 <sys/types.h>
+#include <regex.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "Log.h"
+#include "audio/RemoteAudio.h"
+#include "ClientImpl.h"
+#include "Report.h"
+#include "Settings.h"
+#include "StringUtil.h"
+#include "task/TaskCase.h"
+
+static const android::String8 STR_NAME("name");
+static const android::String8 STR_VERSION("version");
+static const android::String8 STR_DESCRIPTION("description");
+
+TaskCase::TaskCase()
+ : TaskGeneric(TaskGeneric::ETaskCase),
+ mClient(NULL)
+{
+ const android::String8* list[] = {&STR_NAME, &STR_VERSION, &STR_DESCRIPTION, NULL};
+ registerSupportedStringAttributes(list);
+}
+
+TaskCase::~TaskCase()
+{
+ delete mClient;
+}
+
+bool TaskCase::getCaseName(android::String8& name)
+{
+ if (!findStringAttribute(STR_NAME, name)) {
+ LOGW("TaskCase no name");
+ return false;
+ }
+ return true;
+}
+
+bool TaskCase::addChild(TaskGeneric* child)
+{
+ if ((child->getType() != TaskGeneric::ETaskSetup)
+ && (child->getType() != TaskGeneric::ETaskAction)
+ && (child->getType() != TaskGeneric::ETaskSave)) {
+ LOGE("TestCase::addChild wrong child type %d", child->getType());
+ return false;
+ }
+ return TaskGeneric::addChild(child);
+}
+
+template <typename T> bool registerGeneric(
+ typename std::map<android::String8, T>& map,
+ const android::String8& name, T& data)
+{
+ typename std::map<android::String8, T>::iterator it;
+ it = map.find(name);
+ if (it != map.end()) {
+ LOGV("registerGeneric key %s already registered", name.string());
+ return false;
+ }
+ LOGD("registerGeneric registered key %s", name.string());
+ map[name] = data;
+ return true;
+}
+
+template <typename T> bool findGeneric(typename std::map<android::String8, T>& map,
+ const android::String8& name, T& data)
+{
+ LOGD("findGeneric key %s", name.string());
+ typename std::map<android::String8, T>::iterator it;
+ it = map.find(name);
+ if (it == map.end()) {
+ return false;
+ }
+ data = it->second;
+ return true;
+}
+
+template <typename T> bool updateGeneric(typename std::map<android::String8, T>& map,
+ const android::String8& name, T& data)
+{
+ LOGD("updateGeneric key %s", name.string());
+ typename std::map<android::String8, T>::iterator it;
+ it = map.find(name);
+ if (it == map.end()) {
+ return false;
+ }
+ it->second = data;
+ return true;
+}
+
+// return all the matches for the given regular expression.
+// name string and the data itself is copied.
+template <typename T> typename std::list<std::pair<android::String8, T> >* findAllGeneric(
+ typename std::map<android::String8, T>& map, const char* re)
+{
+ regex_t regex;
+ if (regcomp(®ex, re, REG_EXTENDED | REG_NOSUB) != 0) {
+ LOGE("regcomp failed");
+ return NULL;
+ }
+ typename std::map<android::String8, T>::iterator it;
+ typename std::list<std::pair<android::String8, T> >* list = NULL;
+ for (it = map.begin(); it != map.end(); it++) {
+ if (regexec(®ex, it->first, 0, NULL, 0) == 0) {
+ if (list == NULL) { // create only when found
+ list = new std::list<std::pair<android::String8, T> >();
+ if (list == NULL) {
+ regfree(®ex);
+ return NULL;
+ }
+ }
+ typename std::pair<android::String8, T> match(it->first, it->second);
+ list->push_back(match);
+ }
+ }
+ regfree(®ex);
+ return list;
+}
+
+
+bool TaskCase::registerBuffer(const android::String8& orig, android::sp<Buffer>& buffer)
+{
+ android::String8 translated;
+ if (!translateVarName(orig, translated)) {
+ return false;
+ }
+ return registerGeneric<android::sp<Buffer> >(mBufferList, translated, buffer);
+}
+
+bool TaskCase::updateBuffer(const android::String8& orig, android::sp<Buffer>& buffer)
+{
+ android::String8 translated;
+ if (!translateVarName(orig, translated)) {
+ return false;
+ }
+ return updateGeneric<android::sp<Buffer> >(mBufferList, translated, buffer);
+}
+
+android::sp<Buffer> TaskCase::findBuffer(const android::String8& orig)
+{
+ android::String8 translated;
+ android::sp<Buffer> result;
+ if (!translateVarName(orig, translated)) {
+ return result;
+ }
+ findGeneric<android::sp<Buffer> >(mBufferList, translated, result);
+ return result;
+}
+
+std::list<TaskCase::BufferPair>* TaskCase::findAllBuffers(const android::String8& re)
+{
+ android::String8 translated;
+ if (!translateVarName(re, translated)) {
+ return NULL;
+ }
+ return findAllGeneric<android::sp<Buffer> >(mBufferList, translated.string());
+}
+
+
+bool TaskCase::registerValue(const android::String8& orig, Value& val)
+{
+ android::String8 translated;
+ if (!translateVarName(orig, translated)) {
+ return false;
+ }
+ LOGD("str %x", translated.string());
+ return registerGeneric<Value>(mValueList, translated, val);
+}
+
+bool TaskCase::updateValue(const android::String8& orig, Value& val)
+{
+ android::String8 translated;
+ if (!translateVarName(orig, translated)) {
+ return false;
+ }
+ return updateGeneric<Value>(mValueList, translated, val);
+}
+
+bool TaskCase::findValue(const android::String8& orig, Value& val)
+{
+ android::String8 translated;
+ if (!translateVarName(orig, translated)) {
+ return false;
+ }
+ return findGeneric<Value>(mValueList, translated, val);
+}
+
+std::list<TaskCase::ValuePair>* TaskCase::findAllValues(const android::String8& re)
+{
+ android::String8 translated;
+ if (!translateVarName(re, translated)) {
+ return NULL;
+ }
+ return findAllGeneric<Value>(mValueList, translated.string());
+}
+
+bool TaskCase::registerIndex(const android::String8& name, int value)
+{
+ return registerGeneric<int>(mIndexList, name, value);
+}
+
+bool TaskCase::updateIndex(const android::String8& name, int value)
+{
+ return updateGeneric<int>(mIndexList, name, value);
+}
+
+bool TaskCase::findIndex(const android::String8& name, int& val)
+{
+ return findGeneric<int>(mIndexList, name, val);
+}
+
+std::list<TaskCase::IndexPair>* TaskCase::findAllIndices(const android::String8& re)
+{
+ android::String8 translated;
+ if (!translateVarName(re, translated)) {
+ return NULL;
+ }
+ return findAllGeneric<int>(mIndexList, translated.string());
+}
+
+bool TaskCase::translateVarName(const android::String8& orig, android::String8& translated)
+{
+ const char* src = orig.string();
+ const int nmatch = 2;
+ regmatch_t pmatch[nmatch];
+ regex_t re;
+ size_t strStart = 0;
+
+ if (regcomp(&re, "[a-z0-9_]*[$]([a-z0-9]+)[_]*", REG_EXTENDED) != 0) {
+ LOGE("regcomp failed");
+ return false;
+ }
+ bool result = false;
+ size_t matchStart = 0;
+ size_t matchEnd = 0;
+ while (regexec(&re, src, nmatch, pmatch, 0) == 0) {
+ matchStart = strStart + pmatch[1].rm_so;
+ matchEnd = strStart + pmatch[1].rm_eo;
+ translated.append(StringUtil::substr(orig, strStart, pmatch[1].rm_so - 1)); //-1 for $
+ android::String8 indexName;
+ indexName.append(StringUtil::substr(orig, matchStart, matchEnd - matchStart));
+ int val;
+ if (!findIndex(indexName, val)) {
+ LOGE("TaskCase::translateVarName no index with name %s", indexName.string());
+ regfree(&re);
+ return false;
+ }
+ translated.appendFormat("%d", val);
+ LOGD("match found strStart %d, matchStart %d, matchEnd %d, converted str %s",
+ strStart, matchStart, matchEnd, translated.string());
+ src += pmatch[1].rm_eo;
+ strStart += pmatch[1].rm_eo;
+ }
+ if (matchEnd < orig.length()) {
+ //LOGD("%d %d", matchEnd, orig.length());
+ translated.append(StringUtil::substr(orig, matchEnd, orig.length() - matchEnd));
+ }
+ LOGD("translated str %s to %s", orig.string(), translated.string());
+ regfree(&re);
+ return true;
+}
+
+android::sp<RemoteAudio>& TaskCase::getRemoteAudio()
+{
+ if (mClient == NULL) {
+ mClient = new ClientImpl();
+ ASSERT(mClient->init(Settings::Instance()->getSetting(Settings::EADB)));
+ }
+ return mClient->getAudio();
+}
+
+void TaskCase::releaseRemoteAudio()
+{
+ delete mClient;
+ mClient = NULL;
+}
+
+TaskGeneric::ExecutionResult TaskCase::run()
+{
+ android::String8 name;
+ android::String8 version;
+ //LOGI("str %d, %d", strlen(STR_NAME), strlen(STR_VERSION));
+ if (!findStringAttribute(STR_NAME, name) || !findStringAttribute(STR_VERSION, version)) {
+ LOGW("TaskCase::run no name or version information");
+ }
+ Report::Instance()->printf("== Test case %s version %s started ==", name.string(),
+ version.string());
+ std::list<TaskGeneric*>::iterator i = getChildren().begin();
+ std::list<TaskGeneric*>::iterator end = getChildren().end();
+ TaskGeneric* setup = *i;
+ i++;
+ TaskGeneric* action = *i;
+ i++;
+ TaskGeneric* save = (i == end)? NULL : *i;
+ if (save == NULL) {
+ LOGW("No save stage in test case");
+ }
+ bool testPassed = true;
+ TaskGeneric::ExecutionResult result = setup->run();
+ TaskGeneric::ExecutionResult resultAction(TaskGeneric::EResultOK);
+ if (result != TaskGeneric::EResultOK) {
+ Report::Instance()->printf("== setup stage failed %d ==", result);
+ testPassed = false;
+ } else {
+ resultAction = action->run();
+ if (resultAction != TaskGeneric::EResultPass) {
+ Report::Instance()->printf("== action stage failed %d ==", resultAction);
+ testPassed = false;
+ }
+ // save done even for failure if possible
+ if (save != NULL) {
+ result = save->run();
+ }
+ if (result != TaskGeneric::EResultOK) {
+ Report::Instance()->printf("== save stage failed %d ==", result);
+ testPassed = false;
+ }
+ }
+ if (testPassed) {
+ result = TaskGeneric::EResultPass;
+ Report::Instance()->printf("== Case %s Passed ==", name.string());
+ Report::Instance()->addCasePassed(name);
+ } else {
+ if (resultAction != TaskGeneric::EResultOK) {
+ result = resultAction;
+ }
+ Report::Instance()->printf("== Case %s Failed ==", name.string());
+ Report::Instance()->addCaseFailed(name);
+ }
+ // release remote audio for other cases to use
+ releaseRemoteAudio();
+ return result;
+}
+
+
diff --git a/suite/audio_quality/lib/src/task/TaskGeneric.cpp b/suite/audio_quality/lib/src/task/TaskGeneric.cpp
new file mode 100644
index 0000000..7abd137
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskGeneric.cpp
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2012 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 "Log.h"
+
+#include "task/TaskAll.h"
+
+
+TaskGeneric::TaskGeneric(TaskType type):
+ mType(type),
+ mParent(NULL)
+{
+
+}
+
+bool deleteChildInstance(TaskGeneric* child, void* /*data*/)
+{
+ delete child;
+ return true;
+}
+
+TaskGeneric::~TaskGeneric()
+{
+ forEachChild(deleteChildInstance, NULL);
+ //mChildren.clear();
+}
+
+bool TaskGeneric::addChild(TaskGeneric* child)
+{
+ mChildren.push_back(child);
+ child->setParent(this);
+ return true;
+}
+
+bool TaskGeneric::forEachChild(bool (*runForEachChild)(TaskGeneric* child, void* data), void* data)
+{
+ std::list<TaskGeneric*>::iterator i = mChildren.begin();
+ std::list<TaskGeneric*>::iterator end = mChildren.end();
+ for (; i != end; i++) {
+ if (!(*runForEachChild)(*i, data)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+TaskGeneric* TaskGeneric::getParent()
+{
+ return mParent;
+}
+
+TaskCase* TaskGeneric::getTestCase()
+{
+ TaskGeneric* task = this;
+
+ while (task != NULL) {
+ if (task->getType() == ETaskCase) {
+ // do not use dynamic_cast intentionally
+ return reinterpret_cast<TaskCase*>(task);
+ }
+ task = task->getParent();
+ }
+ LOGE("TaskGeneric::getTestCase no TaskCase found!");
+ return NULL;
+}
+
+void TaskGeneric::setParent(TaskGeneric* parent)
+{
+ LOGD("TaskGeneric::setParent self %x, parent %x", this, parent);
+ mParent = parent;
+}
+
+bool runChild(TaskGeneric* child, void* data)
+{
+ TaskGeneric::ExecutionResult* result = reinterpret_cast<TaskGeneric::ExecutionResult*>(data);
+ *result = child->run();
+ if (*result != TaskGeneric::EResultOK) {
+ LOGE("child type %d returned %d", child->getType(), *result);
+ return false;
+ }
+ return true;
+}
+
+TaskGeneric::ExecutionResult TaskGeneric::run()
+{
+ ExecutionResult result = EResultOK;
+ forEachChild(runChild, &result);
+ return result;
+}
+
+bool TaskGeneric::parseAttribute(const android::String8& name, const android::String8& value)
+{
+ // default implementation only handles registered string attributes
+ if (!addStringAttribute(name, value)) {
+ LOGE("parseAttribute unknown attribute %s %s for type %d",
+ name.string(), value.string(), getType());
+ return false;
+ }
+ return true;
+}
+
+
+void TaskGeneric::registerSupportedStringAttributes(const android::String8* keys[])
+{
+ int i = 0;
+ while (keys[i] != NULL) {
+ mAllowedStringAttributes.insert(*keys[i]);
+ i++;
+ }
+}
+
+bool TaskGeneric::addStringAttribute(const android::String8& key, const android::String8& value)
+{
+ std::set<android::String8, android::String8>::iterator it = mAllowedStringAttributes.find(key);
+ if (it == mAllowedStringAttributes.end()) {
+ return false; // not allowed
+ }
+ mStringAttributes[key] = value;
+ return true;
+}
+
+bool TaskGeneric::findStringAttribute(const android::String8& key, android::String8& value)
+{
+ std::map<android::String8, android::String8>::iterator it = mStringAttributes.find(key);
+ if (it == mStringAttributes.end()) {
+ return false; // not found
+ }
+ value = it->second;
+ return true;
+}
+
diff --git a/suite/audio_quality/lib/src/task/TaskInput.cpp b/suite/audio_quality/lib/src/task/TaskInput.cpp
new file mode 100644
index 0000000..e5b4b06
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskInput.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2012 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 <stdlib.h>
+
+#include "Log.h"
+#include "audio/AudioHardware.h"
+#include "task/TaskCase.h"
+#include "task/TaskInput.h"
+
+TaskInput::TaskInput()
+ : TaskAsync(TaskGeneric::ETaskInput),
+ mRecordingTimeInMs(0)
+{
+
+}
+
+TaskInput::~TaskInput()
+{
+
+}
+
+bool TaskInput::parseAttribute(const android::String8& name, const android::String8& value)
+{
+ if (strcmp(name, "time") == 0) {
+ mRecordingTimeInMs = atoi(value);
+ if (mRecordingTimeInMs < 0) {
+ LOGE("TaskInput::parseAttribute invalid recording time %d", mRecordingTimeInMs);
+ return false;
+ }
+ return true;
+ }
+ return TaskAsync::parseAttribute(name, value);
+}
+
+TaskGeneric::ExecutionResult TaskInput::start()
+{
+ bool localDevice = (mDeviceType == TaskAsync::EDeviceHost);
+ android::sp<AudioHardware> hw = AudioHardware::createAudioHw(localDevice, false,
+ getTestCase());
+ if (hw.get() == NULL) {
+ LOGE("createAudioHw failed");
+ return TaskGeneric::EResultError;
+ }
+ // TODO support stereo mode in local later
+ // for now, local is captured in stereo, and it is stored to mono
+ // by keeping only channel 1.
+ // local : stereo only, remote : mono only
+ size_t bufferSize = mRecordingTimeInMs * AudioHardware::ESampleRate_44100 / 1000 *
+ (localDevice ? 4 : 2);
+ android::sp<Buffer> buffer(new Buffer(bufferSize, bufferSize, localDevice));
+ if (buffer.get() == NULL) {
+ LOGE("buffer alloc failed");
+ return TaskGeneric::EResultError;
+ }
+ if (!hw->prepare(AudioHardware::ESampleRate_44100, mVolume, mMode)) {
+ LOGE("prepare failed");
+ return TaskGeneric::EResultError;
+ }
+ if (!hw->startPlaybackOrRecord(buffer)) {
+ LOGE("record failed");
+ return TaskGeneric::EResultError;
+ }
+ // now store sp
+ mHw = hw;
+ mBuffer = buffer;
+ return TaskGeneric::EResultOK;
+}
+
+TaskGeneric::ExecutionResult TaskInput::complete()
+{
+ bool result = mHw->waitForCompletion();
+ mHw->stopPlaybackOrRecord();
+ mHw.clear();
+ if (!result) {
+ LOGE("waitForComletion failed");
+ return TaskGeneric::EResultError;
+ }
+ // TODO: need to keep stereo for local if in stereo mode
+ // For now, convert to mono if it is stereo
+ if (mBuffer->isStereo()) {
+ mBuffer->changeToMono(Buffer::EKeepCh0);
+ }
+ if (!getTestCase()->registerBuffer(mId, mBuffer)) {
+ if (!getTestCase()->updateBuffer(mId, mBuffer)) {
+ LOGE("cannot register/update buffer %s", mId.string());
+ return TaskGeneric::EResultError;
+ }
+ }
+ return TaskGeneric::EResultOK;
+}
+
+
diff --git a/suite/audio_quality/lib/src/task/TaskMessage.cpp b/suite/audio_quality/lib/src/task/TaskMessage.cpp
new file mode 100644
index 0000000..4241d2e
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskMessage.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2012 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 "task/TaskMessage.h"
+
+
+TaskMessage::TaskMessage()
+ : TaskGeneric(TaskGeneric::ETaskMessage)
+{}
+TaskMessage::~TaskMessage()
+{
+
+}
+TaskGeneric::ExecutionResult TaskMessage::run()
+{
+ //TODO
+ return TaskGeneric::EResultError;
+}
+bool TaskMessage::parseAttribute(const android::String8& name, const android::String8& value)
+{
+ //TODO
+ return false;
+}
diff --git a/suite/audio_quality/lib/src/task/TaskOutput.cpp b/suite/audio_quality/lib/src/task/TaskOutput.cpp
new file mode 100644
index 0000000..ce3a7d8
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskOutput.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2012 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 "task/TaskCase.h"
+#include "StringUtil.h"
+#include "task/TaskOutput.h"
+#include "audio/AudioRemote.h"
+#include "audio/RemoteAudio.h"
+
+
+TaskOutput::TaskOutput()
+ : TaskAsync(TaskGeneric::ETaskOutput),
+ mWaitForCompletion(false)
+{
+
+}
+
+TaskOutput::~TaskOutput()
+{
+
+}
+bool TaskOutput::parseAttribute(const android::String8& name, const android::String8& value)
+{
+ if (StringUtil::compare(name, "waitforcompletion") == 0) {
+ if (StringUtil::compare(value, "1") == 0) {
+ mWaitForCompletion = true;
+ }
+ return true;
+ }
+ return TaskAsync::parseAttribute(name, value);
+}
+TaskGeneric::ExecutionResult TaskOutput::start()
+{
+ bool localDevice = (mDeviceType == TaskAsync::EDeviceHost);
+ android::sp<AudioHardware> hw = AudioHardware::createAudioHw(localDevice, true, getTestCase());
+ if (hw.get() == NULL) {
+ LOGE("cannot create Audio HW");
+ return TaskGeneric::EResultError;
+ }
+ if (!hw->prepare(AudioHardware::ESampleRate_44100, mVolume, mMode)) {
+ LOGE("prepare failed");
+ return TaskGeneric::EResultError;
+ }
+ if (localDevice) {
+ android::sp<Buffer> buffer;
+ buffer = getTestCase()->findBuffer(mId);
+ if (buffer.get() == NULL) {
+ LOGE("cannot find buffer %s", mId.string());
+ return TaskGeneric::EResultError;
+ }
+ buffer->restart(); // reset to play from beginning
+
+ if (!hw->startPlaybackOrRecord(buffer)) {
+ LOGE("play failed");
+ return TaskGeneric::EResultError;
+ }
+ } else {
+ int id = getTestCase()->getRemoteAudio()->getDataId(mId);
+ if (id < 0) {
+ return TaskGeneric::EResultError;
+ }
+ AudioRemotePlayback* remote = reinterpret_cast<AudioRemotePlayback*>(hw.get());
+ if (!remote->startPlaybackForRemoteData(id, false)) { // mono always
+ return TaskGeneric::EResultError;
+ }
+ }
+ // now store sp
+ mHw = hw;
+
+ return TaskGeneric::EResultOK;
+}
+
+TaskGeneric::ExecutionResult TaskOutput::complete()
+{
+ bool result = true;
+ if (mWaitForCompletion) {
+ result = mHw->waitForCompletion();
+ }
+ mHw->stopPlaybackOrRecord();
+ mHw.clear();
+ if (!result) {
+ LOGE("waitForCompletion failed");
+ return TaskGeneric::EResultError;
+ }
+ return TaskGeneric::EResultOK;
+}
+
+
diff --git a/suite/audio_quality/lib/src/task/TaskProcess.cpp b/suite/audio_quality/lib/src/task/TaskProcess.cpp
new file mode 100644
index 0000000..f1e47af
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskProcess.cpp
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2012 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 <stdlib.h>
+#include <string.h>
+
+#include <vector>
+
+#include "Log.h"
+#include "StringUtil.h"
+#include "task/TaskProcess.h"
+#include "SignalProcessingImpl.h"
+
+TaskProcess::TaskProcess()
+ : TaskGeneric(TaskGeneric::ETaskProcess)
+{
+
+}
+
+TaskProcess::~TaskProcess()
+{
+}
+
+TaskGeneric::ExecutionResult TaskProcess::run()
+{
+ if (mType == EBuiltin) {
+ return doRun(true);
+ } else {
+ if (mSp.get() == NULL) {
+ mSp.reset(new SignalProcessingImpl());
+ if (!mSp->init(SignalProcessingImpl::MAIN_PROCESSING_SCRIPT)) {
+ mSp.reset(NULL);
+ return TaskGeneric::EResultError;
+ }
+ }
+ return doRun(false);
+ }
+}
+
+// Allocate Buffers and Values to pass to builtin functions
+bool TaskProcess::prepareParams(std::vector<TaskProcess::Param>& list,
+ const bool* paramTypes,
+ UniquePtr<void_ptr, DefaultDelete<void_ptr[]> > & ptrs,
+ UniquePtr<UniqueValue, DefaultDelete<UniqueValue[]> > & values,
+ UniquePtr<UniqueBuffer, DefaultDelete<UniqueBuffer[]> > & buffers,
+ bool isInput)
+{
+ size_t N = list.size();
+
+ LOGD("TaskProcess::prepareParams N = %d", N);
+ ptrs.reset(new void_ptr[N]);
+ if (ptrs.get() == NULL) {
+ LOGE("alloc failed");
+ return false;
+ }
+ // set to NULL to detect illegal access
+ bzero(ptrs.get(), N * sizeof(void_ptr));
+ values.reset(new UniqueValue[N]);
+ if (values.get() == NULL) {
+ LOGE("alloc failed");
+ return false;
+ }
+ buffers.reset(new UniqueBuffer[N]);
+ if (buffers.get() == NULL) {
+ LOGE("alloc failed");
+ return false;
+ }
+
+ void_ptr* voidPtrs = ptrs.get();
+ UniqueValue* valuesPtr = values.get();
+ UniqueBuffer* buffersPtr = buffers.get();
+ for (size_t i = 0; i < N; i++) {
+ if ((paramTypes != NULL) && paramTypes[i] && (list[i].getType() != EId)) {
+ LOGE("mismatching types %d %d", paramTypes[i], list[i].getType());
+ return false;
+ }
+ if ((paramTypes != NULL) && !paramTypes[i] && (list[i].getType() == EId)) {
+ LOGE("mismatching types %d %d", paramTypes[i], list[i].getType());
+ return false;
+ }
+ switch(list[i].getType()) {
+ case EId: {
+ UniquePtr<android::sp<Buffer> > buffer(new android::sp<Buffer>());
+ if (buffer.get() == NULL) {
+ LOGE("alloc failed");
+ return false;
+ }
+ if (isInput) {
+ *(buffer.get()) = getTestCase()->findBuffer(list[i].getParamString());
+ if (buffer.get()->get() == NULL) {
+ LOGE("find failed");
+ return false;
+ }
+ LOGD("input buffer len %d stereo %d", (*buffer.get())->getSize(),
+ (*buffer.get())->isStereo());
+ }
+ buffersPtr[i].reset(buffer.release());
+ voidPtrs[i] = buffersPtr[i].get();
+ }
+ break;
+ case EVal: {
+ valuesPtr[i].reset(new TaskCase::Value());
+ if (isInput) {
+ if (!getTestCase()->findValue(list[i].getParamString(), *(valuesPtr[i].get()))) {
+ LOGE("find %s failed", list[i].getParamString().string());
+ return false;
+ }
+ }
+ voidPtrs[i] = valuesPtr[i].get();
+ }
+ break;
+ case EConst: {
+ if (!isInput) {
+ LOGE("const for output");
+ return false;
+ }
+ voidPtrs[i] = list[i].getValuePtr();
+
+ if (list[i].getValue().getType() == TaskCase::Value::ETypeDouble) {
+ LOGD(" %f", list[i].getValue().getDouble());
+ } else {
+ LOGD(" %lld", list[i].getValue().getInt64());
+ }
+ }
+ break;
+ }
+ LOGD("TaskProcess::prepareParams %d-th, const 0x%x", i, voidPtrs[i]);
+ }
+ return true;
+}
+
+// run builtin function by searching BuiltinProcessing::BUINTIN_FN_TABLE
+TaskGeneric::ExecutionResult TaskProcess::doRun(bool builtIn)
+{
+ BuiltinProcessing::BuiltinInfo* info = NULL;
+ if (builtIn) {
+ for (int i = 0; i < BuiltinProcessing::N_BUILTIN_FNS; i++) {
+ if (StringUtil::compare(mName, BuiltinProcessing::BUINTIN_FN_TABLE[i].mName) == 0) {
+ info = &BuiltinProcessing::BUINTIN_FN_TABLE[i];
+ break;
+ }
+ }
+ if (info == NULL) {
+ LOGE("TaskProcess::runBuiltin no match for %s", mName.string());
+ return TaskGeneric::EResultError;
+ }
+ if (mInput.size() != info->mNInput) {
+ LOGE("TaskProcess::runBuiltin size mismatch %d vs %d", mInput.size(), info->mNInput);
+ return TaskGeneric::EResultError;
+ }
+ if (mOutput.size() != info->mNOutput) {
+ LOGE("TaskProcess::runBuiltin size mismatch %d vs %d", mOutput.size(), info->mNOutput);
+ return TaskGeneric::EResultError;
+ }
+ }
+ // This is for passing to builtin fns. Just void pts will be cleared in exit
+ UniquePtr<void_ptr, DefaultDelete<void_ptr[]> > inputs;
+ // This is for holding Value instances. Will be destroyed in exit
+ UniquePtr<UniqueValue, DefaultDelete<UniqueValue[]> > inputValues;
+ // This is for holding android::sp<Buffer>. Buffer itself is from the global map.
+ UniquePtr<UniqueBuffer, DefaultDelete<UniqueBuffer[]> > inputBuffers;
+
+ UniquePtr<void_ptr, DefaultDelete<void_ptr[]> > outputs;
+ // Value is created here. Builtin function just need to set it.
+ UniquePtr<UniqueValue, DefaultDelete<UniqueValue[]> > outputValues;
+ // Buffer itself should be allocated by the builtin function itself.
+ UniquePtr<UniqueBuffer, DefaultDelete<UniqueBuffer[]> > outputBuffers;
+
+ if (!prepareParams(mInput, builtIn ? info->mInputTypes : NULL, inputs, inputValues,
+ inputBuffers, true)) {
+ return TaskGeneric::EResultError;
+ }
+
+ if (!prepareParams(mOutput, builtIn ? info->mOutputTypes : NULL, outputs, outputValues,
+ outputBuffers, false)) {
+ return TaskGeneric::EResultError;
+ }
+
+ TaskGeneric::ExecutionResult result;
+ if (builtIn) {
+ result = (mBuiltin.*(info->mFunction))(inputs.get(), outputs.get());
+ } else {
+ UniquePtr<bool, DefaultDelete<bool[]> > inputTypes(new bool[mInput.size()]);
+ for (size_t i = 0; i < mInput.size(); i++) {
+ (inputTypes.get())[i] = mInput[i].isIdType();
+ }
+ UniquePtr<bool, DefaultDelete<bool[]> > outputTypes(new bool[mOutput.size()]);
+ for (size_t i = 0; i < mOutput.size(); i++) {
+ (outputTypes.get())[i] = mOutput[i].isIdType();
+ }
+ result = mSp->run( mName,
+ mInput.size(), inputTypes.get(), inputs.get(),
+ mOutput.size(), outputTypes.get(), outputs.get());
+ }
+ if ((result == TaskGeneric::EResultOK) || (result == TaskGeneric::EResultFail)
+ || (result == TaskGeneric::EResultPass)) {
+ // try to save result
+ bool saveResultFailed = false;
+ for (size_t i = 0; i < mOutput.size(); i++) {
+ if (mOutput[i].isIdType()) { // Buffer
+ android::sp<Buffer>* bufferp =
+ reinterpret_cast<android::sp<Buffer>*>((outputs.get())[i]);
+ if (!getTestCase()->registerBuffer(mOutput[i].getParamString(), *bufferp)) {
+ // maybe already there, try update
+ if (!getTestCase()->updateBuffer(mOutput[i].getParamString(), *bufferp)) {
+ LOGE("cannot register / update %d-th output Buffer for builtin fn %s",
+ i, mName.string());
+ saveResultFailed = true; // mark failure, but continue
+ }
+ }
+ } else { // Value
+ TaskCase::Value* valuep =
+ reinterpret_cast<TaskCase::Value*>((outputs.get())[i]);
+ if (!getTestCase()->registerValue(mOutput[i].getParamString(), *valuep)) {
+ if (!getTestCase()->updateValue(mOutput[i].getParamString(), *valuep)) {
+ LOGE("cannot register / update %d-th output Value for builtin fn %s",
+ i, mName.string());
+ saveResultFailed = true; // mark failure, but continue
+ }
+ }
+ }
+ }
+ if (saveResultFailed) {
+ LOGE("TaskProcess::runBuiltin cannot save result");
+ return TaskGeneric::EResultError;
+ }
+ }
+ LOGV("TaskProcess::runBuiltin return %d", result);
+ return result;
+}
+
+bool TaskProcess::parseParams(std::vector<TaskProcess::Param>& list, const char* str, bool isInput)
+{
+ LOGV("TaskProcess::parseParams will parse %s", str);
+ android::String8 paramStr(str);
+ UniquePtr<std::vector<android::String8> > paramTokens(StringUtil::split(paramStr, ','));
+ if (paramTokens.get() == NULL) {
+ LOGE("split failed");
+ return false;
+ }
+ std::vector<android::String8>& tokens = *(paramTokens.get());
+ for (size_t i = 0; i < tokens.size(); i++) {
+ UniquePtr<std::vector<android::String8> > itemTokens(StringUtil::split(tokens[i], ':'));
+ if (itemTokens.get() == NULL) {
+ LOGE("split failed");
+ return false;
+ }
+ if (itemTokens->size() != 2) {
+ LOGE("size mismatch %d", itemTokens->size());
+ return false;
+ }
+ std::vector<android::String8>& item = *(itemTokens.get());
+ if (StringUtil::compare(item[0], "id") == 0) {
+ Param param(EId, item[1]);
+ list.push_back(param);
+ LOGD(" id %s", param.getParamString().string());
+ } else if (StringUtil::compare(item[0], "val") == 0) {
+ Param param(EVal, item[1]);
+ list.push_back(param);
+ LOGD(" val %s", param.getParamString().string());
+ } else if (isInput && (StringUtil::compare(item[0], "consti") == 0)) {
+ long long value = atoll(item[1].string());
+ TaskCase::Value v(value);
+ Param param(v);
+ list.push_back(param);
+ LOGD("consti %lld", value);
+ } else if (isInput && (StringUtil::compare(item[0], "constf") == 0)) {
+ double value = atof(item[1].string());
+ TaskCase::Value v(value);
+ Param param(v);
+ list.push_back(param);
+ LOGD("constf %f", value);
+ } else {
+ LOGE("unrecognized word %s", item[0].string());
+ return false;
+ }
+ LOGV("TaskProcess::parseParams %d-th type %d", i, list[i].getType());
+ }
+ return true;
+}
+
+bool TaskProcess::parseAttribute(const android::String8& name, const android::String8& value)
+{
+ if (StringUtil::compare(name, "method") == 0) {
+ UniquePtr<std::vector<android::String8> > tokenPtr(StringUtil::split(value, ':'));
+ std::vector<android::String8>* tokens = tokenPtr.get();
+ if (tokens == NULL) {
+ LOGE("split failed");
+ return false;
+ }
+ if (tokens->size() != 2) {
+ LOGE("cannot parse attr %s %s", name.string(), value.string());
+ return false;
+ }
+ if (StringUtil::compare(tokens->at(0), "builtin") == 0) {
+ mType = EBuiltin;
+ } else if (StringUtil::compare(tokens->at(0), "script") == 0) {
+ mType = EScript;
+ } else {
+ LOGE("cannot parse attr %s %s", name.string(), value.string());
+ return false;
+ }
+ mName.append(tokens->at(1));
+ return true;
+ } else if (StringUtil::compare(name, "input") == 0) {
+ return parseParams(mInput, value, true);
+ } else if (StringUtil::compare(name, "output") == 0) {
+ return parseParams(mOutput, value, false);
+ } else {
+ LOGE("cannot parse attr %s %s", name.string(), value.string());
+ return false;
+ }
+}
+
+TaskProcess::Param::Param(TaskProcess::ParamType type, android::String8& string)
+ : mType(type),
+ mString(string)
+{
+ ASSERT((type == TaskProcess::EId) || (type == TaskProcess::EVal));
+
+}
+
+TaskProcess::Param::Param(TaskCase::Value& val)
+ : mType(TaskProcess::EConst),
+ mValue(val)
+{
+
+}
+
+TaskProcess::ParamType TaskProcess::Param::getType()
+{
+ return mType;
+}
+
+android::String8& TaskProcess::Param::getParamString()
+{
+ ASSERT((mType == TaskProcess::EId) || (mType == TaskProcess::EVal));
+ return mString;
+}
+
+TaskCase::Value& TaskProcess::Param::getValue()
+{
+ ASSERT(mType == TaskProcess::EConst);
+ return mValue;
+}
+
+TaskCase::Value* TaskProcess::Param::getValuePtr()
+{
+ ASSERT(mType == TaskProcess::EConst);
+ return &mValue;
+}
diff --git a/suite/audio_quality/lib/src/task/TaskSave.cpp b/suite/audio_quality/lib/src/task/TaskSave.cpp
new file mode 100644
index 0000000..d62b846
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskSave.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2012 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 <sys/stat.h>
+#include <sys/types.h>
+#include <errno.h>
+
+#include <UniquePtr.h>
+
+#include "Log.h"
+#include "FileUtil.h"
+#include "Report.h"
+#include "StringUtil.h"
+#include "task/TaskCase.h"
+#include "task/TaskGeneric.h"
+#include "task/TaskSave.h"
+
+static const android::String8 STR_FILE("file");
+static const android::String8 STR_REPORT("report");
+
+TaskSave::TaskSave()
+ : TaskGeneric(TaskGeneric::ETaskSave)
+{
+ const android::String8* list[] = {&STR_FILE, &STR_REPORT, NULL};
+ registerSupportedStringAttributes(list);
+}
+
+TaskSave::~TaskSave()
+{
+
+}
+
+bool TaskSave::handleFile()
+{
+ android::String8 fileValue;
+ if (!findStringAttribute(STR_FILE, fileValue)) {
+ LOGI("no saving to file");
+ return true; // true as there is no need to save
+ }
+
+ UniquePtr<std::vector<android::String8> > list(StringUtil::split(fileValue, ','));
+ std::vector<android::String8>* listp = list.get();
+ if (listp == NULL) {
+ LOGE("alloc failed");
+ return false;
+ }
+
+ android::String8 dirName;
+ if (!FileUtil::prepare(dirName)) {
+ LOGE("cannot prepare report dir");
+ return false;
+ }
+ android::String8 caseName;
+ if (!getTestCase()->getCaseName(caseName)) {
+ return false;
+ }
+ dirName.appendPath(caseName);
+ int result = mkdir(dirName.string(), S_IRWXU);
+ if ((result == -1) && (errno != EEXIST)) {
+ LOGE("mkdir of save dir %s failed, error %d", dirName.string(), errno);
+ return false;
+ }
+
+ for (size_t i = 0; i < listp->size(); i++) {
+ UniquePtr<std::list<TaskCase::BufferPair> > buffers(
+ getTestCase()->findAllBuffers((*listp)[i]));
+ std::list<TaskCase::BufferPair>* buffersp = buffers.get();
+ if (buffersp == NULL) {
+ LOGE("no buffer for given pattern %s", ((*listp)[i]).string());
+ return false;
+ }
+ std::list<TaskCase::BufferPair>::iterator it = buffersp->begin();
+ std::list<TaskCase::BufferPair>::iterator end = buffersp->end();
+ for (; it != end; it++) {
+ android::String8 fileName(dirName);
+ fileName.appendPath(it->first);
+ if (!it->second->saveToFile(fileName)) {
+ LOGE("save failed");
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool TaskSave::handleReport()
+{
+ android::String8 reportValue;
+ if (!findStringAttribute(STR_REPORT, reportValue)) {
+ LOGI("no saving to report");
+ return true; // true as there is no need to save
+ }
+
+ UniquePtr<std::vector<android::String8> > list(StringUtil::split(reportValue, ','));
+ std::vector<android::String8>* listp = list.get();
+ if (listp == NULL) {
+ LOGE("alloc failed");
+ return false;
+ }
+ Report::Instance()->printf("=== Values stored ===");
+ for (size_t i = 0; i < listp->size(); i++) {
+ UniquePtr<std::list<TaskCase::ValuePair> > values(
+ getTestCase()->findAllValues((*listp)[i]));
+ std::list<TaskCase::ValuePair>* valuesp = values.get();
+ if (valuesp == NULL) {
+ LOGE("no value for given pattern %s", ((*listp)[i]).string());
+ return false;
+ }
+ std::list<TaskCase::ValuePair>::iterator it = values->begin();
+ std::list<TaskCase::ValuePair>::iterator end = values->end();
+ for (; it != end; it++) {
+ if (it->second.getType() == TaskCase::Value::ETypeDouble) {
+ Report::Instance()->printf(" %s: %f", it->first.string(),
+ it->second.getDouble());
+ } else { //64bit int
+ Report::Instance()->printf(" %s: %lld", it->first.string(),
+ it->second.getInt64());
+ }
+ }
+ }
+ return true;
+}
+
+TaskGeneric::ExecutionResult TaskSave::run()
+{
+ bool failed = false;
+ if (!handleFile()) {
+ failed = true;
+ }
+ if (!handleReport()) {
+ failed = true;
+ }
+ if (failed) {
+ return TaskGeneric::EResultError;
+ } else {
+ return TaskGeneric::EResultOK;
+ }
+}
+
+
diff --git a/suite/audio_quality/lib/src/task/TaskSequential.cpp b/suite/audio_quality/lib/src/task/TaskSequential.cpp
new file mode 100644
index 0000000..8c60cb7f
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskSequential.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2012 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 <stdlib.h>
+#include <algorithm>
+#include "Log.h"
+#include "StringUtil.h"
+#include "task/TaskSequential.h"
+#include "task/TaskCase.h"
+#include "task/TaskAsync.h"
+
+TaskSequential::TaskSequential()
+ : TaskGeneric(TaskGeneric::ETaskSequential),
+ mRepeatCount(1),
+ mRepeatIndex(-1)
+{
+
+}
+
+TaskSequential::~TaskSequential()
+{
+
+}
+
+
+TaskGeneric::ExecutionResult TaskSequential::run()
+{
+ mRepeatIndex = -1;
+ bool storeIndex = (mIndexName.length() == 0 ? false: true);
+ if (storeIndex && !getTestCase()->registerIndex(mIndexName, mRepeatIndex)) {
+ if (!getTestCase()->updateIndex(mIndexName, mRepeatIndex)) {
+ LOGE("register/update of index %s failed", mIndexName.string());
+ return TaskGeneric::EResultError;
+ }
+ }
+
+ TaskGeneric::ExecutionResult firstError(TaskGeneric::EResultOK);
+
+ for (mRepeatIndex = 0; mRepeatIndex < mRepeatCount; mRepeatIndex++) {
+ LOGI(" TaskSequential index %s loop %d-th", mIndexName.string(), mRepeatIndex);
+ if (storeIndex && !getTestCase()->updateIndex(mIndexName, mRepeatIndex)) {
+ return TaskGeneric::EResultError;
+ }
+ std::list<TaskGeneric*>::iterator i = getChildren().begin();
+ std::list<TaskGeneric*>::iterator end = getChildren().end();
+ for (; i != end; i++) {
+ TaskGeneric* child = *i;
+ TaskGeneric::ExecutionResult result = child->run();
+ if ((result != TaskGeneric::EResultOK) && (firstError == TaskGeneric::EResultOK)) {
+ firstError = result;
+ break;
+ }
+ }
+ TaskGeneric::ExecutionResult result = runAsyncTasksQueued();
+ if ((result != TaskGeneric::EResultOK) && (firstError == TaskGeneric::EResultOK)) {
+ firstError = result;
+ }
+ switch (firstError) {
+ case TaskGeneric::EResultOK:
+ case TaskGeneric::EResultContinue:
+ // continue at the last index should be treated as OK
+ firstError = TaskGeneric::EResultOK;
+ break; // continue for loop
+ case TaskGeneric:: EResultBreakOneLoop:
+ return TaskGeneric::EResultOK;
+ case TaskGeneric::EResultError:
+ case TaskGeneric::EResultFail:
+ case TaskGeneric::EResultPass:
+ mRepeatIndex = mRepeatCount; //exit for loop
+ break;
+ }
+ }
+ // update to the loop exit value
+ if (storeIndex && !getTestCase()->updateIndex(mIndexName, mRepeatIndex)) {
+ return TaskGeneric::EResultError;
+ }
+ return firstError;
+}
+
+bool TaskSequential::queueAsyncTask(TaskAsync* task)
+{
+ std::list<TaskAsync*>::iterator it;
+ it = std::find(mAsyncTasks.begin(), mAsyncTasks.end(), task);
+ if (it != mAsyncTasks.end()) { // already queued
+ return true;
+ }
+ mAsyncTasks.push_back(task);
+ return true;
+}
+
+TaskGeneric::ExecutionResult TaskSequential::runAsyncTasksQueued()
+{
+ std::list<TaskAsync*>::iterator i = mAsyncTasks.begin();
+ std::list<TaskAsync*>::iterator end = mAsyncTasks.end();
+ TaskGeneric::ExecutionResult firstError(TaskGeneric::EResultOK);
+
+ for (; i != end; i++) {
+ TaskAsync* child = *i;
+ TaskGeneric::ExecutionResult result = child->complete();
+ if ((result != TaskGeneric::EResultOK) && (firstError == TaskGeneric::EResultOK)) {
+ firstError = result;
+ }
+ }
+ mAsyncTasks.clear();
+ return firstError;
+}
+
+
+bool TaskSequential::parseAttribute(const android::String8& name, const android::String8& value)
+{
+ if (StringUtil::compare(name, "repeat") == 0) {
+ mRepeatCount = atoi(value.string());
+ if (mRepeatCount <= 0) {
+ LOGE("TaskSequential::parseAttribute invalid value %s for key %s",
+ value.string(), name.string());
+ return false;
+ }
+ return true;
+ } else if (StringUtil::compare(name, "index") == 0) {
+ mIndexName.append(value);
+ LOGD("TaskSequential::parseAttribute index %s", mIndexName.string());
+ return true;
+ } else {
+ return false;
+ }
+}
diff --git a/suite/audio_quality/lib/src/task/TaskSound.cpp b/suite/audio_quality/lib/src/task/TaskSound.cpp
new file mode 100644
index 0000000..f530203
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskSound.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2012 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 <UniquePtr.h>
+#include "Log.h"
+#include "audio/AudioSignalFactory.h"
+#include "audio/RemoteAudio.h"
+#include "StringUtil.h"
+#include "task/TaskCase.h"
+#include "task/TaskSound.h"
+
+static const android::String8 STR_ID("id");
+static const android::String8 STR_TYPE("type");
+
+TaskSound::TaskSound()
+ : TaskGeneric(TaskGeneric::ETaskSound),
+ mPreload(false)
+{
+ const android::String8* list[] = {&STR_ID, &STR_TYPE, NULL};
+ registerSupportedStringAttributes(list);
+}
+
+TaskSound::~TaskSound()
+{
+
+}
+
+bool TaskSound::parseAttribute(const android::String8& name, const android::String8& value)
+{
+ if (StringUtil::compare(name, "preload") == 0) {
+ if (StringUtil::compare(value, "1") == 0) {
+ mPreload = true;
+ }
+ return true;
+ }
+ return TaskGeneric::parseAttribute(name, value);
+}
+
+TaskGeneric::ExecutionResult TaskSound::run()
+{
+ //TODO : needs to support data generated from process
+ android::String8 id;
+ if (!findStringAttribute(STR_ID, id)) {
+ LOGE("TaskSound::run %s string not found", STR_ID.string());
+ return TaskGeneric::EResultError;
+ }
+ android::String8 type;
+ if (!findStringAttribute(STR_TYPE, type)) {
+ LOGE("TaskSound::run %s string not found", STR_TYPE.string());
+ return TaskGeneric::EResultError;
+ }
+ UniquePtr<std::vector<android::String8> > tokens(StringUtil::split(type, ':'));
+ if (tokens.get() == NULL) {
+ LOGE("alloc failed");
+ return TaskGeneric::EResultError;
+ }
+ android::sp<Buffer> buffer;
+ if (StringUtil::compare(tokens->at(0), "file") == 0) {
+ if (tokens->size() != 2) {
+ LOGE("Wrong number of parameters %d", tokens->size());
+ }
+ buffer = Buffer::loadFromFile(tokens->at(1));
+ } else if (StringUtil::compare(tokens->at(0), "sin") == 0) {
+ if (tokens->size() != 4) {
+ LOGE("Wrong number of parameters %d", tokens->size());
+ }
+ int amplitude = atoi(tokens->at(1).string());
+ int freq = atoi(tokens->at(2).string());
+ int time = atoi(tokens->at(3).string());
+ int samples = time * AudioHardware::ESampleRate_44100 / 1000;
+ buffer = AudioSignalFactory::generateSineWave(AudioHardware::E2BPS, amplitude,
+ AudioHardware::ESampleRate_44100, freq, samples, true);
+ } else if (StringUtil::compare(tokens->at(0), "random") == 0) {
+ if (tokens->size() != 3) {
+ LOGE("Wrong number of parameters %d", tokens->size());
+ }
+ int amplitude = atoi(tokens->at(1).string());
+ int time = atoi(tokens->at(2).string());
+ int samples = time * AudioHardware::ESampleRate_44100 / 1000;
+ buffer = AudioSignalFactory::generateWhiteNoise(AudioHardware::E2BPS, amplitude,
+ samples, true);
+ } else { // unknown word
+ LOGE("TaskSound::run unknown word in type %s", type.string());
+ // next buffer check will return
+ }
+
+ if (buffer.get() == NULL) {
+ return TaskGeneric::EResultError;
+ }
+ if (!getTestCase()->registerBuffer(id, buffer)) {
+ LOGE("TaskSound::run registering buffer %s failed", id.string());
+ return TaskGeneric::EResultError;
+ }
+ if (mPreload) {
+ int downloadId;
+ if (!getTestCase()->getRemoteAudio()->downloadData(id, buffer, downloadId)) {
+ return TaskGeneric::EResultError;
+ }
+ LOGI("Downloaded buffer %s to DUT with id %d", id.string(), downloadId);
+ }
+ return TaskGeneric::EResultOK;
+}
+
+
+