diff options
Diffstat (limited to 'voip/jni/rtp')
-rw-r--r-- | voip/jni/rtp/Android.mk | 44 | ||||
-rw-r--r-- | voip/jni/rtp/AudioCodec.cpp | 161 | ||||
-rw-r--r-- | voip/jni/rtp/AudioCodec.h | 36 | ||||
-rw-r--r-- | voip/jni/rtp/AudioGroup.cpp | 1004 | ||||
-rw-r--r-- | voip/jni/rtp/RtpStream.cpp | 126 | ||||
-rw-r--r-- | voip/jni/rtp/rtp_jni.cpp | 32 | ||||
-rw-r--r-- | voip/jni/rtp/util.cpp | 61 |
7 files changed, 1464 insertions, 0 deletions
diff --git a/voip/jni/rtp/Android.mk b/voip/jni/rtp/Android.mk new file mode 100644 index 0000000..a364355 --- /dev/null +++ b/voip/jni/rtp/Android.mk @@ -0,0 +1,44 @@ +# +# Copyright (C) 2010 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. +# + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := librtp_jni + +LOCAL_SRC_FILES := \ + AudioCodec.cpp \ + AudioGroup.cpp \ + RtpStream.cpp \ + util.cpp \ + rtp_jni.cpp + +LOCAL_SHARED_LIBRARIES := \ + libnativehelper \ + libcutils \ + libutils \ + libmedia + +LOCAL_STATIC_LIBRARIES := + +LOCAL_C_INCLUDES += \ + $(JNI_H_INCLUDE) + +LOCAL_CFLAGS += -fvisibility=hidden + +LOCAL_PRELINK_MODULE := false + +include $(BUILD_SHARED_LIBRARY) diff --git a/voip/jni/rtp/AudioCodec.cpp b/voip/jni/rtp/AudioCodec.cpp new file mode 100644 index 0000000..ddd07fc --- /dev/null +++ b/voip/jni/rtp/AudioCodec.cpp @@ -0,0 +1,161 @@ +/* + * Copyrightm (C) 2010 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 <string.h> + +#include "AudioCodec.h" + +namespace { + +int8_t gExponents[128] = { + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +}; + +//------------------------------------------------------------------------------ + +class UlawCodec : public AudioCodec +{ +public: + bool set(int sampleRate, int sampleCount) { + mSampleCount = sampleCount; + return sampleCount > 0; + } + int encode(void *payload, int16_t *samples); + int decode(int16_t *samples, void *payload, int length); +private: + int mSampleCount; +}; + +int UlawCodec::encode(void *payload, int16_t *samples) +{ + int8_t *ulaws = (int8_t *)payload; + for (int i = 0; i < mSampleCount; ++i) { + int sample = samples[i]; + int sign = (sample >> 8) & 0x80; + if (sample < 0) { + sample = -sample; + } + sample += 132; + if (sample > 32767) { + sample = 32767; + } + int exponent = gExponents[sample >> 8]; + int mantissa = (sample >> (exponent + 3)) & 0x0F; + ulaws[i] = ~(sign | (exponent << 4) | mantissa); + } + return mSampleCount; +} + +int UlawCodec::decode(int16_t *samples, void *payload, int length) +{ + int8_t *ulaws = (int8_t *)payload; + for (int i = 0; i < length; ++i) { + int ulaw = ~ulaws[i]; + int exponent = (ulaw >> 4) & 0x07; + int mantissa = ulaw & 0x0F; + int sample = (((mantissa << 3) + 132) << exponent) - 132; + samples[i] = (ulaw < 0 ? -sample : sample); + } + return length; +} + +AudioCodec *newUlawCodec() +{ + return new UlawCodec; +} + +//------------------------------------------------------------------------------ + +class AlawCodec : public AudioCodec +{ +public: + bool set(int sampleRate, int sampleCount) { + mSampleCount = sampleCount; + return sampleCount > 0; + } + int encode(void *payload, int16_t *samples); + int decode(int16_t *samples, void *payload, int length); +private: + int mSampleCount; +}; + +int AlawCodec::encode(void *payload, int16_t *samples) +{ + int8_t *alaws = (int8_t *)payload; + for (int i = 0; i < mSampleCount; ++i) { + int sample = samples[i]; + int sign = (sample >> 8) & 0x80; + if (sample < 0) { + sample = -sample; + } + if (sample > 32767) { + sample = 32767; + } + int exponent = gExponents[sample >> 8]; + int mantissa = (sample >> (exponent == 0 ? 4 : exponent + 3)) & 0x0F; + alaws[i] = (sign | (exponent << 4) | mantissa) ^ 0xD5; + } + return mSampleCount; +} + +int AlawCodec::decode(int16_t *samples, void *payload, int length) +{ + int8_t *alaws = (int8_t *)payload; + for (int i = 0; i < length; ++i) { + int alaw = alaws[i] ^ 0x55; + int exponent = (alaw >> 4) & 0x07; + int mantissa = alaw & 0x0F; + int sample = (exponent == 0 ? (mantissa << 4) + 8 : + ((mantissa << 3) + 132) << exponent); + samples[i] = (alaw < 0 ? sample : -sample); + } + return length; +} + +AudioCodec *newAlawCodec() +{ + return new AlawCodec; +} + +struct AudioCodecType { + const char *name; + AudioCodec *(*create)(); +} gAudioCodecTypes[] = { + {"PCMA", newAlawCodec}, + {"PCMU", newUlawCodec}, + {NULL, NULL}, +}; + +} // namespace + +AudioCodec *newAudioCodec(const char *codecName) +{ + AudioCodecType *type = gAudioCodecTypes; + while (type->name != NULL) { + if (strcmp(codecName, type->name) == 0) { + return type->create(); + } + ++type; + } + return NULL; +} diff --git a/voip/jni/rtp/AudioCodec.h b/voip/jni/rtp/AudioCodec.h new file mode 100644 index 0000000..797494c --- /dev/null +++ b/voip/jni/rtp/AudioCodec.h @@ -0,0 +1,36 @@ +/* + * Copyrightm (C) 2010 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> + +#ifndef __AUDIO_CODEC_H__ +#define __AUDIO_CODEC_H__ + +class AudioCodec +{ +public: + virtual ~AudioCodec() {} + // Returns true if initialization succeeds. + virtual bool set(int sampleRate, int sampleCount) = 0; + // Returns the length of payload in bytes. + virtual int encode(void *payload, int16_t *samples) = 0; + // Returns the number of decoded samples. + virtual int decode(int16_t *samples, void *payload, int length) = 0; +}; + +AudioCodec *newAudioCodec(const char *codecName); + +#endif diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp new file mode 100644 index 0000000..fc1ed9b --- /dev/null +++ b/voip/jni/rtp/AudioGroup.cpp @@ -0,0 +1,1004 @@ +/* + * Copyright (C) 2010 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 <stdint.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/epoll.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <time.h> +#include <arpa/inet.h> +#include <netinet/in.h> + +#define LOG_TAG "AudioGroup" +#include <cutils/atomic.h> +#include <utils/Log.h> +#include <utils/Errors.h> +#include <utils/RefBase.h> +#include <utils/threads.h> +#include <utils/SystemClock.h> +#include <media/AudioSystem.h> +#include <media/AudioRecord.h> +#include <media/AudioTrack.h> +#include <media/mediarecorder.h> + +#include "jni.h" +#include "JNIHelp.h" + +#include "AudioCodec.h" + +extern int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss); + +namespace { + +using namespace android; + +int gRandom = -1; + +// We use a circular array to implement jitter buffer. The simplest way is doing +// a modulo operation on the index while accessing the array. However modulo can +// be expensive on some platforms, such as ARM. Thus we round up the size of the +// array to the nearest power of 2 and then use bitwise-and instead of modulo. +// Currently we make it 256ms long and assume packet interval is 32ms or less. +// The first 64ms is the place where samples get mixed. The rest 192ms is the +// real jitter buffer. For a stream at 8000Hz it takes 4096 bytes. These numbers +// are chosen by experiments and each of them can be adjusted as needed. + +// Other notes: +// + We use elapsedRealtime() to get the time. Since we use 32bit variables +// instead of 64bit ones, comparison must be done by subtraction. +// + Sampling rate must be multiple of 1000Hz, and packet length must be in +// milliseconds. No floating points. +// + If we cannot get enough CPU, we drop samples and simulate packet loss. +// + Resampling is not done yet, so streams in one group must use the same rate. +// For the first release we might only support 8kHz and 16kHz. + +class AudioStream +{ +public: + AudioStream(); + ~AudioStream(); + bool set(int mode, int socket, sockaddr_storage *remote, + const char *codecName, int sampleRate, int sampleCount, + int codecType, int dtmfType); + + void sendDtmf(int event); + bool mix(int32_t *output, int head, int tail, int sampleRate); + void encode(int tick, AudioStream *chain); + void decode(int tick); + +private: + enum { + NORMAL = 0, + SEND_ONLY = 1, + RECEIVE_ONLY = 2, + LAST_MODE = 2, + }; + + int mMode; + int mSocket; + sockaddr_storage mRemote; + AudioCodec *mCodec; + uint32_t mCodecMagic; + uint32_t mDtmfMagic; + + int mTick; + int mSampleRate; + int mSampleCount; + int mInterval; + + int16_t *mBuffer; + int mBufferMask; + int mBufferHead; + int mBufferTail; + int mLatencyScore; + + uint16_t mSequence; + uint32_t mTimestamp; + uint32_t mSsrc; + + int mDtmfEvent; + int mDtmfStart; + + AudioStream *mNext; + + friend class AudioGroup; +}; + +AudioStream::AudioStream() +{ + mSocket = -1; + mCodec = NULL; + mBuffer = NULL; + mNext = NULL; +} + +AudioStream::~AudioStream() +{ + close(mSocket); + delete mCodec; + delete [] mBuffer; + LOGD("stream[%d] is dead", mSocket); +} + +bool AudioStream::set(int mode, int socket, sockaddr_storage *remote, + const char *codecName, int sampleRate, int sampleCount, + int codecType, int dtmfType) +{ + if (mode < 0 || mode > LAST_MODE) { + return false; + } + mMode = mode; + + if (codecName) { + mRemote = *remote; + mCodec = newAudioCodec(codecName); + if (!mCodec || !mCodec->set(sampleRate, sampleCount)) { + return false; + } + } + + mCodecMagic = (0x8000 | codecType) << 16; + mDtmfMagic = (dtmfType == -1) ? 0 : (0x8000 | dtmfType) << 16; + + mTick = elapsedRealtime(); + mSampleRate = sampleRate / 1000; + mSampleCount = sampleCount; + mInterval = mSampleCount / mSampleRate; + + // Allocate jitter buffer. + for (mBufferMask = 8192; mBufferMask < sampleRate; mBufferMask <<= 1); + mBufferMask >>= 2; + mBuffer = new int16_t[mBufferMask]; + --mBufferMask; + mBufferHead = 0; + mBufferTail = 0; + mLatencyScore = 0; + + // Initialize random bits. + read(gRandom, &mSequence, sizeof(mSequence)); + read(gRandom, &mTimestamp, sizeof(mTimestamp)); + read(gRandom, &mSsrc, sizeof(mSsrc)); + + mDtmfEvent = -1; + mDtmfStart = 0; + + // Only take over the socket when succeeded. + mSocket = socket; + + LOGD("stream[%d] is configured as %s %dkHz %dms", mSocket, + (codecName ? codecName : "RAW"), mSampleRate, mInterval); + return true; +} + +void AudioStream::sendDtmf(int event) +{ + if (mDtmfMagic != 0) { + mDtmfEvent = event << 24; + mDtmfStart = mTimestamp + mSampleCount; + } +} + +bool AudioStream::mix(int32_t *output, int head, int tail, int sampleRate) +{ + if (mMode == SEND_ONLY) { + return false; + } + + if (head - mBufferHead < 0) { + head = mBufferHead; + } + if (tail - mBufferTail > 0) { + tail = mBufferTail; + } + if (tail - head <= 0) { + return false; + } + + head *= mSampleRate; + tail *= mSampleRate; + + if (sampleRate == mSampleRate) { + for (int i = head; i - tail < 0; ++i) { + output[i - head] += mBuffer[i & mBufferMask]; + } + } else { + // TODO: implement resampling. + return false; + } + return true; +} + +void AudioStream::encode(int tick, AudioStream *chain) +{ + if (tick - mTick >= mInterval) { + // We just missed the train. Pretend that packets in between are lost. + int skipped = (tick - mTick) / mInterval; + mTick += skipped * mInterval; + mSequence += skipped; + mTimestamp += skipped * mSampleCount; + LOGD("stream[%d] skips %d packets", mSocket, skipped); + } + + tick = mTick; + mTick += mInterval; + ++mSequence; + mTimestamp += mSampleCount; + + if (mMode == RECEIVE_ONLY) { + return; + } + + // If there is an ongoing DTMF event, send it now. + if (mDtmfEvent != -1) { + int duration = mTimestamp - mDtmfStart; + // Make sure duration is reasonable. + if (duration >= 0 && duration < mSampleRate * 100) { + duration += mSampleCount; + int32_t buffer[4] = { + htonl(mDtmfMagic | mSequence), + htonl(mDtmfStart), + mSsrc, + htonl(mDtmfEvent | duration), + }; + if (duration >= mSampleRate * 100) { + buffer[3] |= htonl(1 << 23); + mDtmfEvent = -1; + } + sendto(mSocket, buffer, sizeof(buffer), MSG_DONTWAIT, + (sockaddr *)&mRemote, sizeof(mRemote)); + return; + } + mDtmfEvent = -1; + } + + // It is time to mix streams. + bool mixed = false; + int32_t buffer[mSampleCount + 3]; + memset(buffer, 0, sizeof(buffer)); + while (chain) { + if (chain != this && + chain->mix(buffer, tick - mInterval, tick, mSampleRate)) { + mixed = true; + } + chain = chain->mNext; + } + if (!mixed) { + LOGD("stream[%d] no data", mSocket); + return; + } + + // Cook the packet and send it out. + int16_t samples[mSampleCount]; + for (int i = 0; i < mSampleCount; ++i) { + int32_t sample = buffer[i]; + if (sample < -32768) { + sample = -32768; + } + if (sample > 32767) { + sample = 32767; + } + samples[i] = sample; + } + if (!mCodec) { + // Special case for device stream. + send(mSocket, samples, sizeof(samples), MSG_DONTWAIT); + return; + } + + buffer[0] = htonl(mCodecMagic | mSequence); + buffer[1] = htonl(mTimestamp); + buffer[2] = mSsrc; + int length = mCodec->encode(&buffer[3], samples); + if (length <= 0) { + LOGD("stream[%d] encoder error", mSocket); + return; + } + sendto(mSocket, buffer, length + 12, MSG_DONTWAIT, (sockaddr *)&mRemote, + sizeof(mRemote)); +} + +void AudioStream::decode(int tick) +{ + char c; + if (mMode == SEND_ONLY) { + recv(mSocket, &c, 1, MSG_DONTWAIT); + return; + } + + // Make sure mBufferHead and mBufferTail are reasonable. + if ((unsigned int)(tick + 256 - mBufferHead) > 1024) { + mBufferHead = tick - 64; + mBufferTail = mBufferHead; + } + + if (tick - mBufferHead > 64) { + // Throw away outdated samples. + mBufferHead = tick - 64; + if (mBufferTail - mBufferHead < 0) { + mBufferTail = mBufferHead; + } + } + + if (mBufferTail - tick <= 80) { + mLatencyScore = tick; + } else if (tick - mLatencyScore >= 5000) { + // Reset the jitter buffer to 40ms if the latency keeps larger than 80ms + // in the past 5s. This rarely happens, so let us just keep it simple. + LOGD("stream[%d] latency control", mSocket); + mBufferTail = tick + 40; + } + + if (mBufferTail - mBufferHead > 256 - mInterval) { + // Buffer overflow. Drop the packet. + LOGD("stream[%d] buffer overflow", mSocket); + recv(mSocket, &c, 1, MSG_DONTWAIT); + return; + } + + // Receive the packet and decode it. + int16_t samples[mSampleCount]; + int length = 0; + if (!mCodec) { + // Special case for device stream. + length = recv(mSocket, samples, sizeof(samples), + MSG_TRUNC | MSG_DONTWAIT) >> 1; + } else { + __attribute__((aligned(4))) uint8_t buffer[2048]; + length = recv(mSocket, buffer, sizeof(buffer), + MSG_TRUNC | MSG_DONTWAIT); + + // Do we need to check SSRC, sequence, and timestamp? They are not + // reliable but at least they can be used to identity duplicates? + if (length < 12 || length > (int)sizeof(buffer) || + (ntohl(*(uint32_t *)buffer) & 0xC07F0000) != mCodecMagic) { + LOGD("stream[%d] malformed packet", mSocket); + return; + } + int offset = 12 + ((buffer[0] & 0x0F) << 2); + if ((buffer[0] & 0x10) != 0) { + offset += 4 + (ntohs(*(uint16_t *)&buffer[offset + 2]) << 2); + } + if ((buffer[0] & 0x20) != 0) { + length -= buffer[length - 1]; + } + length -= offset; + if (length >= 0) { + length = mCodec->decode(samples, &buffer[offset], length); + } + } + if (length != mSampleCount) { + LOGD("stream[%d] decoder error", mSocket); + return; + } + + if (tick - mBufferTail > 0) { + // Buffer underrun. Reset the jitter buffer to 40ms. + LOGD("stream[%d] buffer underrun", mSocket); + if (mBufferTail - mBufferHead <= 0) { + mBufferHead = tick + 40; + mBufferTail = mBufferHead; + } else { + int tail = (tick + 40) * mSampleRate; + for (int i = mBufferTail * mSampleRate; i - tail < 0; ++i) { + mBuffer[i & mBufferMask] = 0; + } + mBufferTail = tick + 40; + } + } + + // Append to the jitter buffer. + int tail = mBufferTail * mSampleRate; + for (int i = 0; i < mSampleCount; ++i) { + mBuffer[tail & mBufferMask] = samples[i]; + ++tail; + } + mBufferTail += mInterval; +} + +//------------------------------------------------------------------------------ + +class AudioGroup +{ +public: + AudioGroup(); + ~AudioGroup(); + bool set(int sampleRate, int sampleCount); + + bool setMode(int mode); + bool sendDtmf(int event); + bool add(AudioStream *stream); + bool remove(int socket); + +private: + enum { + ON_HOLD = 0, + MUTED = 1, + NORMAL = 2, + EC_ENABLED = 3, + LAST_MODE = 3, + }; + int mMode; + AudioStream *mChain; + int mEventQueue; + volatile int mDtmfEvent; + + int mSampleCount; + int mDeviceSocket; + AudioTrack mTrack; + AudioRecord mRecord; + + bool networkLoop(); + bool deviceLoop(); + + class NetworkThread : public Thread + { + public: + NetworkThread(AudioGroup *group) : Thread(false), mGroup(group) {} + + bool start() + { + if (run("Network", ANDROID_PRIORITY_AUDIO) != NO_ERROR) { + LOGE("cannot start network thread"); + return false; + } + return true; + } + + private: + AudioGroup *mGroup; + bool threadLoop() + { + return mGroup->networkLoop(); + } + }; + sp<NetworkThread> mNetworkThread; + + class DeviceThread : public Thread + { + public: + DeviceThread(AudioGroup *group) : Thread(false), mGroup(group) {} + + bool start() + { + char c; + while (recv(mGroup->mDeviceSocket, &c, 1, MSG_DONTWAIT) == 1); + + if (run("Device", ANDROID_PRIORITY_AUDIO) != NO_ERROR) { + LOGE("cannot start device thread"); + return false; + } + return true; + } + + private: + AudioGroup *mGroup; + bool threadLoop() + { + return mGroup->deviceLoop(); + } + }; + sp<DeviceThread> mDeviceThread; +}; + +AudioGroup::AudioGroup() +{ + mMode = ON_HOLD; + mChain = NULL; + mEventQueue = -1; + mDtmfEvent = -1; + mDeviceSocket = -1; + mNetworkThread = new NetworkThread(this); + mDeviceThread = new DeviceThread(this); +} + +AudioGroup::~AudioGroup() +{ + mNetworkThread->requestExitAndWait(); + mDeviceThread->requestExitAndWait(); + mTrack.stop(); + mRecord.stop(); + close(mEventQueue); + close(mDeviceSocket); + while (mChain) { + AudioStream *next = mChain->mNext; + delete mChain; + mChain = next; + } + LOGD("group[%d] is dead", mDeviceSocket); +} + +#define FROYO_COMPATIBLE +#ifdef FROYO_COMPATIBLE + +// Copied from AudioRecord.cpp. +status_t AudioRecord_getMinFrameCount( + int* frameCount, + uint32_t sampleRate, + int format, + int channelCount) +{ + size_t size = 0; + if (AudioSystem::getInputBufferSize(sampleRate, format, channelCount, &size) + != NO_ERROR) { + LOGE("AudioSystem could not query the input buffer size."); + return NO_INIT; + } + + if (size == 0) { + LOGE("Unsupported configuration: sampleRate %d, format %d, channelCount %d", + sampleRate, format, channelCount); + return BAD_VALUE; + } + + // We double the size of input buffer for ping pong use of record buffer. + size <<= 1; + + if (AudioSystem::isLinearPCM(format)) { + size /= channelCount * (format == AudioSystem::PCM_16_BIT ? 2 : 1); + } + + *frameCount = size; + return NO_ERROR; +} + +// Copied from AudioTrack.cpp. +status_t AudioTrack_getMinFrameCount( + int* frameCount, + int streamType, + uint32_t sampleRate) +{ + int afSampleRate; + if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) { + return NO_INIT; + } + int afFrameCount; + if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) { + return NO_INIT; + } + uint32_t afLatency; + if (AudioSystem::getOutputLatency(&afLatency, streamType) != NO_ERROR) { + return NO_INIT; + } + + // Ensure that buffer depth covers at least audio hardware latency + uint32_t minBufCount = afLatency / ((1000 * afFrameCount) / afSampleRate); + if (minBufCount < 2) minBufCount = 2; + + *frameCount = (sampleRate == 0) ? afFrameCount * minBufCount : + afFrameCount * minBufCount * sampleRate / afSampleRate; + return NO_ERROR; +} + +#endif + +bool AudioGroup::set(int sampleRate, int sampleCount) +{ + mEventQueue = epoll_create(2); + if (mEventQueue == -1) { + LOGE("epoll_create: %s", strerror(errno)); + return false; + } + + mSampleCount = sampleCount; + + // Find out the frame count for AudioTrack and AudioRecord. + int output = 0; + int input = 0; +#ifdef FROYO_COMPATIBLE + if (AudioTrack_getMinFrameCount(&output, AudioSystem::VOICE_CALL, + sampleRate) != NO_ERROR || output <= 0 || + AudioRecord_getMinFrameCount(&input, sampleRate, + AudioSystem::PCM_16_BIT, 1) != NO_ERROR || input <= 0) { + LOGE("cannot compute frame count"); + return false; + } +#else + if (AudioTrack::getMinFrameCount(&output, AudioSystem::VOICE_CALL, + sampleRate) != NO_ERROR || output <= 0 || + AudioRecord::getMinFrameCount(&input, sampleRate, + AudioSystem::PCM_16_BIT, 1) != NO_ERROR || input <= 0) { + LOGE("cannot compute frame count"); + return false; + } +#endif + LOGD("reported frame count: output %d, input %d", output, input); + + output = (output + sampleCount - 1) / sampleCount * sampleCount; + input = (input + sampleCount - 1) / sampleCount * sampleCount; + if (input < output * 2) { + input = output * 2; + } + LOGD("adjusted frame count: output %d, input %d", output, input); + + // Initialize AudioTrack and AudioRecord. + if (mTrack.set(AudioSystem::VOICE_CALL, sampleRate, AudioSystem::PCM_16_BIT, + AudioSystem::CHANNEL_OUT_MONO, output) != NO_ERROR || + mRecord.set(AUDIO_SOURCE_MIC, sampleRate, AudioSystem::PCM_16_BIT, + AudioSystem::CHANNEL_IN_MONO, input) != NO_ERROR) { + LOGE("cannot initialize audio device"); + return false; + } + LOGD("latency: output %d, input %d", mTrack.latency(), mRecord.latency()); + + // TODO: initialize echo canceler here. + + // Create device socket. + int pair[2]; + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair)) { + LOGE("socketpair: %s", strerror(errno)); + return false; + } + mDeviceSocket = pair[0]; + + // Create device stream. + mChain = new AudioStream; + if (!mChain->set(AudioStream::NORMAL, pair[1], NULL, NULL, + sampleRate, sampleCount, -1, -1)) { + close(pair[1]); + LOGE("cannot initialize device stream"); + return false; + } + + // Give device socket a reasonable timeout and buffer size. + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 1000 * sampleCount / sampleRate * 500; + if (setsockopt(pair[0], SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) || + setsockopt(pair[0], SOL_SOCKET, SO_RCVBUF, &output, sizeof(output)) || + setsockopt(pair[1], SOL_SOCKET, SO_SNDBUF, &output, sizeof(output))) { + LOGE("setsockopt: %s", strerror(errno)); + return false; + } + + // Add device stream into event queue. + epoll_event event; + event.events = EPOLLIN; + event.data.ptr = mChain; + if (epoll_ctl(mEventQueue, EPOLL_CTL_ADD, pair[1], &event)) { + LOGE("epoll_ctl: %s", strerror(errno)); + return false; + } + + // Anything else? + LOGD("stream[%d] joins group[%d]", pair[1], pair[0]); + return true; +} + +bool AudioGroup::setMode(int mode) +{ + if (mode < 0 || mode > LAST_MODE) { + return false; + } + if (mMode == mode) { + return true; + } + + LOGD("group[%d] switches from mode %d to %d", mDeviceSocket, mMode, mode); + mMode = mode; + + mDeviceThread->requestExitAndWait(); + if (mode == ON_HOLD) { + mTrack.stop(); + mRecord.stop(); + return true; + } + + mTrack.start(); + if (mode == MUTED) { + mRecord.stop(); + } else { + mRecord.start(); + } + + if (!mDeviceThread->start()) { + mTrack.stop(); + mRecord.stop(); + return false; + } + return true; +} + +bool AudioGroup::sendDtmf(int event) +{ + if (event < 0 || event > 15) { + return false; + } + + // DTMF is rarely used, so we try to make it as lightweight as possible. + // Using volatile might be dodgy, but using a pipe or pthread primitives + // or stop-set-restart threads seems too heavy. Will investigate later. + timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 100000000; + for (int i = 0; mDtmfEvent != -1 && i < 20; ++i) { + nanosleep(&ts, NULL); + } + if (mDtmfEvent != -1) { + return false; + } + mDtmfEvent = event; + nanosleep(&ts, NULL); + return true; +} + +bool AudioGroup::add(AudioStream *stream) +{ + mNetworkThread->requestExitAndWait(); + + epoll_event event; + event.events = EPOLLIN; + event.data.ptr = stream; + if (epoll_ctl(mEventQueue, EPOLL_CTL_ADD, stream->mSocket, &event)) { + LOGE("epoll_ctl: %s", strerror(errno)); + return false; + } + + stream->mNext = mChain->mNext; + mChain->mNext = stream; + if (!mNetworkThread->start()) { + // Only take over the stream when succeeded. + mChain->mNext = stream->mNext; + return false; + } + + LOGD("stream[%d] joins group[%d]", stream->mSocket, mDeviceSocket); + return true; +} + +bool AudioGroup::remove(int socket) +{ + mNetworkThread->requestExitAndWait(); + + for (AudioStream *stream = mChain; stream->mNext; stream = stream->mNext) { + AudioStream *target = stream->mNext; + if (target->mSocket == socket) { + stream->mNext = target->mNext; + LOGD("stream[%d] leaves group[%d]", socket, mDeviceSocket); + delete target; + break; + } + } + + // Do not start network thread if there is only one stream. + if (!mChain->mNext || !mNetworkThread->start()) { + return false; + } + return true; +} + +bool AudioGroup::networkLoop() +{ + int tick = elapsedRealtime(); + int deadline = tick + 10; + int count = 0; + + for (AudioStream *stream = mChain; stream; stream = stream->mNext) { + if (!stream->mTick || tick - stream->mTick >= 0) { + stream->encode(tick, mChain); + } + if (deadline - stream->mTick > 0) { + deadline = stream->mTick; + } + ++count; + } + + if (mDtmfEvent != -1) { + int event = mDtmfEvent; + for (AudioStream *stream = mChain; stream; stream = stream->mNext) { + stream->sendDtmf(event); + } + mDtmfEvent = -1; + } + + deadline -= tick; + if (deadline < 1) { + deadline = 1; + } + + epoll_event events[count]; + count = epoll_wait(mEventQueue, events, count, deadline); + if (count == -1) { + LOGE("epoll_wait: %s", strerror(errno)); + return false; + } + for (int i = 0; i < count; ++i) { + ((AudioStream *)events[i].data.ptr)->decode(tick); + } + + return true; +} + +bool AudioGroup::deviceLoop() +{ + int16_t output[mSampleCount]; + + if (recv(mDeviceSocket, output, sizeof(output), 0) <= 0) { + memset(output, 0, sizeof(output)); + } + if (mTrack.write(output, sizeof(output)) != (int)sizeof(output)) { + LOGE("cannot write to AudioTrack"); + return false; + } + + if (mMode != MUTED) { + uint32_t frameCount = mRecord.frameCount(); + AudioRecord::Buffer input; + input.frameCount = frameCount; + + if (mRecord.obtainBuffer(&input, -1) != NO_ERROR) { + LOGE("cannot read from AudioRecord"); + return false; + } + + if (input.frameCount < (uint32_t)mSampleCount) { + input.frameCount = 0; + } else { + if (mMode == NORMAL) { + send(mDeviceSocket, input.i8, sizeof(output), MSG_DONTWAIT); + } else { + // TODO: Echo canceller runs here. + send(mDeviceSocket, input.i8, sizeof(output), MSG_DONTWAIT); + } + if (input.frameCount < frameCount) { + input.frameCount = mSampleCount; + } + } + + mRecord.releaseBuffer(&input); + } + + return true; +} + +//------------------------------------------------------------------------------ + +static jfieldID gNative; +static jfieldID gMode; + +jint add(JNIEnv *env, jobject thiz, jint mode, + jint socket, jstring jRemoteAddress, jint remotePort, + jstring jCodecName, jint sampleRate, jint sampleCount, + jint codecType, jint dtmfType) +{ + const char *codecName = NULL; + AudioStream *stream = NULL; + AudioGroup *group = NULL; + + // Sanity check. + sockaddr_storage remote; + if (parse(env, jRemoteAddress, remotePort, &remote) < 0) { + // Exception already thrown. + return -1; + } + if (sampleRate < 0 || sampleCount < 0 || codecType < 0 || codecType > 127) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + goto error; + } + if (!jCodecName) { + jniThrowNullPointerException(env, "codecName"); + return -1; + } + codecName = env->GetStringUTFChars(jCodecName, NULL); + if (!codecName) { + // Exception already thrown. + return -1; + } + + // Create audio stream. + stream = new AudioStream; + if (!stream->set(mode, socket, &remote, codecName, sampleRate, sampleCount, + codecType, dtmfType)) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot initialize audio stream"); + goto error; + } + socket = -1; + + // Create audio group. + group = (AudioGroup *)env->GetIntField(thiz, gNative); + if (!group) { + int mode = env->GetIntField(thiz, gMode); + group = new AudioGroup; + if (!group->set(8000, 256) || !group->setMode(mode)) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot initialize audio group"); + goto error; + } + } + + // Add audio stream into audio group. + if (!group->add(stream)) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot add audio stream"); + goto error; + } + + // Succeed. + env->SetIntField(thiz, gNative, (int)group); + env->ReleaseStringUTFChars(jCodecName, codecName); + return socket; + +error: + delete group; + delete stream; + close(socket); + env->SetIntField(thiz, gNative, NULL); + env->ReleaseStringUTFChars(jCodecName, codecName); + return -1; +} + +void remove(JNIEnv *env, jobject thiz, jint socket) +{ + AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative); + if (group) { + if (socket == -1 || !group->remove(socket)) { + delete group; + env->SetIntField(thiz, gNative, NULL); + } + } +} + +void setMode(JNIEnv *env, jobject thiz, jint mode) +{ + AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative); + if (group && !group->setMode(mode)) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + env->SetIntField(thiz, gMode, mode); +} + +void sendDtmf(JNIEnv *env, jobject thiz, jint event) +{ + AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative); + if (group && !group->sendDtmf(event)) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + } +} + +JNINativeMethod gMethods[] = { + {"add", "(IILjava/lang/String;ILjava/lang/String;IIII)I", (void *)add}, + {"remove", "(I)V", (void *)remove}, + {"setMode", "(I)V", (void *)setMode}, + {"sendDtmf", "(I)V", (void *)sendDtmf}, +}; + +} // namespace + +int registerAudioGroup(JNIEnv *env) +{ + gRandom = open("/dev/urandom", O_RDONLY); + if (gRandom == -1) { + LOGE("urandom: %s", strerror(errno)); + return -1; + } + + jclass clazz; + if ((clazz = env->FindClass("android/net/rtp/AudioGroup")) == NULL || + (gNative = env->GetFieldID(clazz, "mNative", "I")) == NULL || + (gMode = env->GetFieldID(clazz, "mMode", "I")) == NULL || + env->RegisterNatives(clazz, gMethods, NELEM(gMethods)) < 0) { + LOGE("JNI registration failed"); + return -1; + } + return 0; +} diff --git a/voip/jni/rtp/RtpStream.cpp b/voip/jni/rtp/RtpStream.cpp new file mode 100644 index 0000000..33b88e4 --- /dev/null +++ b/voip/jni/rtp/RtpStream.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2010 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 <stdint.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netinet/in.h> + +#define LOG_TAG "RtpStream" +#include <utils/Log.h> + +#include "jni.h" +#include "JNIHelp.h" + +extern int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss); + +namespace { + +jfieldID gNative; + +jint create(JNIEnv *env, jobject thiz, jstring jAddress) +{ + env->SetIntField(thiz, gNative, -1); + + sockaddr_storage ss; + if (parse(env, jAddress, 0, &ss) < 0) { + // Exception already thrown. + return -1; + } + + int socket = ::socket(ss.ss_family, SOCK_DGRAM, 0); + socklen_t len = sizeof(ss); + if (socket == -1 || bind(socket, (sockaddr *)&ss, sizeof(ss)) != 0 || + getsockname(socket, (sockaddr *)&ss, &len) != 0) { + jniThrowException(env, "java/net/SocketException", strerror(errno)); + ::close(socket); + return -1; + } + + uint16_t *p = (ss.ss_family == AF_INET) ? + &((sockaddr_in *)&ss)->sin_port : &((sockaddr_in6 *)&ss)->sin6_port; + uint16_t port = ntohs(*p); + if ((port & 1) == 0) { + env->SetIntField(thiz, gNative, socket); + return port; + } + ::close(socket); + + socket = ::socket(ss.ss_family, SOCK_DGRAM, 0); + if (socket != -1) { + uint16_t delta = port << 1; + ++port; + + for (int i = 0; i < 1000; ++i) { + do { + port += delta; + } while (port < 1024); + *p = htons(port); + + if (bind(socket, (sockaddr *)&ss, sizeof(ss)) == 0) { + env->SetIntField(thiz, gNative, socket); + return port; + } + } + } + + jniThrowException(env, "java/net/SocketException", strerror(errno)); + ::close(socket); + return -1; +} + +jint dup(JNIEnv *env, jobject thiz) +{ + int socket1 = env->GetIntField(thiz, gNative); + int socket2 = ::dup(socket1); + if (socket2 == -1) { + jniThrowException(env, "java/lang/IllegalStateException", strerror(errno)); + } + LOGD("dup %d to %d", socket1, socket2); + return socket2; +} + +void close(JNIEnv *env, jobject thiz) +{ + int socket = env->GetIntField(thiz, gNative); + ::close(socket); + env->SetIntField(thiz, gNative, -1); + LOGD("close %d", socket); +} + +JNINativeMethod gMethods[] = { + {"create", "(Ljava/lang/String;)I", (void *)create}, + {"dup", "()I", (void *)dup}, + {"close", "()V", (void *)close}, +}; + +} // namespace + +int registerRtpStream(JNIEnv *env) +{ + jclass clazz; + if ((clazz = env->FindClass("android/net/rtp/RtpStream")) == NULL || + (gNative = env->GetFieldID(clazz, "mNative", "I")) == NULL || + env->RegisterNatives(clazz, gMethods, NELEM(gMethods)) < 0) { + LOGE("JNI registration failed"); + return -1; + } + return 0; +} diff --git a/voip/jni/rtp/rtp_jni.cpp b/voip/jni/rtp/rtp_jni.cpp new file mode 100644 index 0000000..9f4bff9 --- /dev/null +++ b/voip/jni/rtp/rtp_jni.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2010 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 "jni.h" + +extern int registerRtpStream(JNIEnv *env); +extern int registerAudioGroup(JNIEnv *env); + +__attribute__((visibility("default"))) jint JNI_OnLoad(JavaVM *vm, void *unused) +{ + JNIEnv *env = NULL; + if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK || + registerRtpStream(env) < 0 || registerAudioGroup(env) < 0) { + return -1; + } + return JNI_VERSION_1_4; +} diff --git a/voip/jni/rtp/util.cpp b/voip/jni/rtp/util.cpp new file mode 100644 index 0000000..1d702fc --- /dev/null +++ b/voip/jni/rtp/util.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2010 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 <string.h> +#include <arpa/inet.h> +#include <netinet/in.h> + +#include "jni.h" +#include "JNIHelp.h" + +int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss) +{ + if (!jAddress) { + jniThrowNullPointerException(env, "address"); + return -1; + } + if (port < 0 || port > 65535) { + jniThrowException(env, "java/lang/IllegalArgumentException", "port"); + return -1; + } + const char *address = env->GetStringUTFChars(jAddress, NULL); + if (!address) { + // Exception already thrown. + return -1; + } + memset(ss, 0, sizeof(*ss)); + + sockaddr_in *sin = (sockaddr_in *)ss; + if (inet_pton(AF_INET, address, &(sin->sin_addr)) > 0) { + sin->sin_family = AF_INET; + sin->sin_port = htons(port); + env->ReleaseStringUTFChars(jAddress, address); + return 0; + } + + sockaddr_in6 *sin6 = (sockaddr_in6 *)ss; + if (inet_pton(AF_INET6, address, &(sin6->sin6_addr)) > 0) { + sin6->sin6_family = AF_INET6; + sin6->sin6_port = htons(port); + env->ReleaseStringUTFChars(jAddress, address); + return 0; + } + + env->ReleaseStringUTFChars(jAddress, address); + jniThrowException(env, "java/lang/IllegalArgumentException", "address"); + return -1; +} |