/*
* 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;
}
}