/* * 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)), mDeviceInfoHandler(new CommandHandler(*this, (int)AudioProtocol::ECmdGetDeviceInfo)), 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); mCmds[AudioProtocol::ECmdGetDeviceInfo - AudioProtocol::ECmdStart] = new CmdGetDeviceInfo(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, android::Looper::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; } 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()); } else if (id == AudioProtocol::ECmdGetDeviceInfo) { handler = reinterpret_cast<CommandHandler*>(mDeviceInfoHandler.get()); } AudioParam* param = NULL; if (handler != NULL) { 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 & android::Looper::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) { LOGV("waitForCompletion %d", 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->mStateLock.lock(); handler->getParam().mId = id; handler->getParam().mBuffer = buffer; handler->mNotifyOnReply = true; handler->mStateLock.unlock(); sendCommand(mDownloadHandler); // 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; } LOGD("RemoteAudio::startPlayback stereo %d mode %d", stereo, mode); 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); } bool RemoteAudio::getDeviceInfo(android::String8& data) { CommandHandler* handler = reinterpret_cast<CommandHandler*>(mDeviceInfoHandler.get()); handler->mStateLock.lock(); handler->mNotifyOnReply = true; handler->getParam().mExtra = &data; handler->mStateLock.unlock(); sendCommand(mDeviceInfoHandler); // client blocked until reply comes from DUT if (!waitForCompletion(mDeviceInfoHandler, CLIENT_WAIT_TIMEOUT_MSEC)) { LOGE("timeout"); return false; } return handler->mResult; } /** 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: case AudioProtocol::ECmdGetDeviceInfo: { mResult = (mThread.mCmds[message.what - AudioProtocol::ECmdStart]) \ ->sendCommand(mParam); // no post for download and getdeviceinfo. Client blocked until reply comes with time-out if ((message.what != AudioProtocol::ECmdDownload) && (message.what != AudioProtocol::ECmdGetDeviceInfo) ) { mClientWait.post(); } } break; } }