diff options
author | Andrew Lehmer <alehmer@google.com> | 2017-04-26 14:58:59 -0700 |
---|---|---|
committer | Andrew Lehmer <alehmer@google.com> | 2017-04-26 14:58:59 -0700 |
commit | e76dcf96b0c451e46cddfa695de8feeb92533937 (patch) | |
tree | ed9a45d409f988f517e6c3f3a685cbf81ac45a5a /android/WALT/app/src/main/jni | |
parent | bcf013dda8ffac9fd76937be6441b44bb9f3586f (diff) | |
download | walt-e76dcf96b0c451e46cddfa695de8feeb92533937.tar.gz |
Import google/walt
Cloned from https://github.com/google/walt.git without modification.
Bug: 36896528
Test: N/A
Diffstat (limited to 'android/WALT/app/src/main/jni')
-rw-r--r-- | android/WALT/app/src/main/jni/Android.mk | 30 | ||||
-rw-r--r-- | android/WALT/app/src/main/jni/Application.mk | 17 | ||||
-rw-r--r-- | android/WALT/app/src/main/jni/Makefile | 17 | ||||
-rw-r--r-- | android/WALT/app/src/main/jni/README.md | 112 | ||||
-rwxr-xr-x | android/WALT/app/src/main/jni/findteensy.py | 30 | ||||
-rw-r--r-- | android/WALT/app/src/main/jni/player.c | 520 | ||||
-rw-r--r-- | android/WALT/app/src/main/jni/sync_clock.c | 327 | ||||
-rw-r--r-- | android/WALT/app/src/main/jni/sync_clock.h | 50 | ||||
-rw-r--r-- | android/WALT/app/src/main/jni/sync_clock_jni.c | 62 | ||||
-rw-r--r-- | android/WALT/app/src/main/jni/sync_clock_linux.c | 80 |
10 files changed, 1245 insertions, 0 deletions
diff --git a/android/WALT/app/src/main/jni/Android.mk b/android/WALT/app/src/main/jni/Android.mk new file mode 100644 index 0000000..3307036 --- /dev/null +++ b/android/WALT/app/src/main/jni/Android.mk @@ -0,0 +1,30 @@ +# Copyright (C) 2015 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 := sync_clock_jni +LOCAL_SRC_FILES := sync_clock_jni.c sync_clock.c player.c + +LOCAL_CFLAGS := -g -DUSE_LIBLOG -Werror + +# needed for logcat +LOCAL_SHARED_LIBRARIES := libcutils + +LOCAL_LDLIBS := -lOpenSLES -llog + +include $(BUILD_SHARED_LIBRARY) diff --git a/android/WALT/app/src/main/jni/Application.mk b/android/WALT/app/src/main/jni/Application.mk new file mode 100644 index 0000000..7c01d06 --- /dev/null +++ b/android/WALT/app/src/main/jni/Application.mk @@ -0,0 +1,17 @@ +# Copyright (C) 2015 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. +# + +APP_ABI := all +APP_PLATFORM := android-9 diff --git a/android/WALT/app/src/main/jni/Makefile b/android/WALT/app/src/main/jni/Makefile new file mode 100644 index 0000000..4c54f56 --- /dev/null +++ b/android/WALT/app/src/main/jni/Makefile @@ -0,0 +1,17 @@ +# Copyright (C) 2015 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. +# + +all: + gcc -o ut sync_clock_linux.c sync_clock.c
\ No newline at end of file diff --git a/android/WALT/app/src/main/jni/README.md b/android/WALT/app/src/main/jni/README.md new file mode 100644 index 0000000..a1316db --- /dev/null +++ b/android/WALT/app/src/main/jni/README.md @@ -0,0 +1,112 @@ +# Clock Synchronization + +How it works + +## Step 1 - rough sync + + T0 = current_time() + Tell the remote to zero clock. + Wait for confirmation from remote + maxE = current_time() - T0 + All further local time is measured from T0 + + +After this step we are sure that the remote clock lags behind the local clock by +some value E. And we know that E >= 0 because remote was zeroed *after* we +zeroed the local time (recored T0). And also E<= maxE. So 0 = minE < E < maxE. + + +## Step 2 - find better lower bound - `minE` + +Send some messages from local to remote, note the time right before sending the +message (denote it as `t_local`) and have the remote reply with his timestamps +of when it received the messages according to his clock that lags by unknown +positive value `E` behind the local clock, denote it by `t_remote`. + + + t_remote = t_local - E + travel_time + E = t_local - t_remote + travel_time > t_local - t_remote + since travel_time > 0 + E > t_local - t_remote + + set minE to max(minE, t_local - t_remote) + Repeat + +We need to first send a bunch of messages with some random small delays, and +only after that get the remote timestamps for all of them. This helps deal with +unwanted buffering and delay added by the kernel of hardware in the outgoing +direction. + +## Step 3 - find better upper bound `maxE` + +Same idea, but in the opposite direction. Remote device sends us messages and +then the timestamps according to his clock of when they were sent. We record the +local timestamps when we receive them. + + t_local = t_remote + E + travel_time + E = t_local - t_remote - travel time < t_local - t_remote + set maxE = min(maxE, t_local - t_remote) + Repeat + +## Comparison with NTP + +NTP measures the mean travel_time (latency) and assumes it to be symmetric - the +same in both directions. If the symmetry assumption is broken, there is no way +to detect this. Both, systematic asymmetry in latency and clock difference would +result in exactly the same observations - +[explanation here](http://cs.stackexchange.com/questions/103/clock-synchronization-in-a-network-with-asymmetric-delays). + +In our case the latency can be relatively small compared to network, but is +likely to be asymmetric due to the asymmetric nature of USB. The algorithm +described above guarantees that the clock difference is within the measured +bounds `minE < E < maxE`, though the resulting interval `deltaE = maxE - minE` +can be fairly large compared to synchronization accuracy of NTP on a network +with good latency symmetry. + +Observed values for `deltaE` + - Linux desktop machine (HP Z420), USB2 port: ~100us + - Same Linux machine, USB3 port: ~800us + - Nexus 5 ~100us + - Nexus 7 (both the old and the newer model) ~300us + - Samsung Galaxy S3 ~150us + + + +## Implementation notes + +General + - All times in this C code are recored in microseconds, unless otherwise + specified. + - The timestamped messages are all single byte. + +USB communication + - USB hierarchy recap: USB device has several interfaces. Each interface has + several endpoints. Endpoints are directional IN = data going into the host, + OUT = data going out of the host. To get data from the device via an IN + endpoint, we must query it. + - There are two types of endpoints - BULK and INTERRUPT. For our case it's not + really important. Currently all the comms are done via a BULK interface + exposed when you compile Teensy code in "Serial". + - All the comms are done using the Linux API declared in linux/usbdevice_fs.h + - The C code can be compiled for both Android JNI and Linux. + - The C code is partially based on the code of libusbhost from the Android OS + core code, but does not use that library because it's an overkill for our + needs. + +## There are two ways of communicating via usbdevice_fs + + // Async way + ioctl(fd, USBDEVFS_SUBMITURB, urb); + // followed by + ioctl(fd, USBDEVFS_REAPURB, &urb); // Blocks until there is a URB to read. + + // Sync way + struct usbdevfs_bulktransfer ctrl; + ctrl.ep = endpoint; + ctrl.len = length; + ctrl.data = buffer; + ctrl.timeout = timeout; // [milliseconds] Will timeout if there is nothing to read + int ret = ioctl(fd, USBDEVFS_BULK, &ctrl); + + + diff --git a/android/WALT/app/src/main/jni/findteensy.py b/android/WALT/app/src/main/jni/findteensy.py new file mode 100755 index 0000000..820bc14 --- /dev/null +++ b/android/WALT/app/src/main/jni/findteensy.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +# Copyright (C) 2015 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. +# + +""" +Find and print the device path for TeensyUSB + +Usage: + sudo ./ut `./findteensy.py` +""" + +import subprocess +line = subprocess.check_output("lsusb | grep eensy", shell=True) +parts = line.split() +bus = parts[1] +dev = parts[3].strip(':') +print "/dev/bus/usb/%s/%s" % (bus, dev)
\ No newline at end of file diff --git a/android/WALT/app/src/main/jni/player.c b/android/WALT/app/src/main/jni/player.c new file mode 100644 index 0000000..361d0a8 --- /dev/null +++ b/android/WALT/app/src/main/jni/player.c @@ -0,0 +1,520 @@ +/* + * Copyright 2015 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 <android/log.h> +#include <assert.h> +#include <jni.h> +#include <malloc.h> +#include <math.h> +#include <sys/types.h> + +// for native audio +#include <SLES/OpenSLES.h> +#include <SLES/OpenSLES_Android.h> +#include <SLES/OpenSLES_AndroidConfiguration.h> + +#include "sync_clock.h" + +// logging +#define APPNAME "WALT" + +// engine interfaces +static SLObjectItf engineObject = NULL; +static SLEngineItf engineEngine = NULL; + +// output mix interfaces +static SLObjectItf outputMixObject = NULL; + +// buffer queue player interfaces +static SLObjectItf bqPlayerObject = NULL; +static SLPlayItf bqPlayerPlay = NULL; +static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue = NULL; + +// recorder interfaces +static SLObjectItf recorderObject = NULL; +static SLRecordItf recorderRecord; +static SLAndroidSimpleBufferQueueItf recorderBufferQueue; +static volatile int bqPlayerRecorderBusy = 0; + +static unsigned int recorder_frames; +static short* recorderBuffer; +static unsigned recorderSize = 0; + +static unsigned int framesPerBuffer; + +#define CHANNELS 1 // 1 for mono, 2 for stereo + +// Each short represents a 16-bit audio sample +static short* beepBuffer = NULL; +static short* silenceBuffer = NULL; +static unsigned int bufferSizeInBytes = 0; + +#define MAXIMUM_AMPLITUDE_VALUE 32767 + +// how many times to play the wave table (so we can actually hear it) +#define BUFFERS_TO_PLAY 10 + +static unsigned buffersRemaining = 0; +static short warmedUp = 0; + + +// Timestamps +// te - enqueue time +// tc - callback time +int64_t te_play = 0, te_rec = 0, tc_rec = 0; + +/** + * Create wave tables for audio out. + */ +void createWaveTables(){ + bufferSizeInBytes = framesPerBuffer * sizeof(*beepBuffer); + silenceBuffer = malloc(bufferSizeInBytes); + beepBuffer = malloc(bufferSizeInBytes); + + + __android_log_print(ANDROID_LOG_VERBOSE, + APPNAME, + "Creating wave tables, 1 channel. Frames: %i Buffer size (bytes): %i", + framesPerBuffer, + bufferSizeInBytes); + + unsigned int i; + for (i = 0; i < framesPerBuffer; i++) { + silenceBuffer[i] = 0; + beepBuffer[i] = (i & 2 - 1) * MAXIMUM_AMPLITUDE_VALUE; + // This fills a buffer that looks like [min, min, max, max, min, min...] + // which is a square wave at 1/4 frequency of the sampling rate + // for 48kHz sampling this is 12kHz pitch, still well audible. + } +} + +// this callback handler is called every time a buffer finishes playing +void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) +{ + if (bq == NULL) { + __android_log_print(ANDROID_LOG_ERROR, APPNAME, "buffer queue is null"); + } + assert(bq == bqPlayerBufferQueue); + assert(NULL == context); + + if (buffersRemaining > 0) { // continue playing tone + if(buffersRemaining == BUFFERS_TO_PLAY && warmedUp) { + // Enqueue the first non-silent buffer, save the timestamp + // For cold test Enqueue happens in playTone rather than here. + te_play = uptimeMicros(); + } + buffersRemaining--; + + SLresult result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, beepBuffer, + bufferSizeInBytes); + (void)result; + assert(SL_RESULT_SUCCESS == result); + } else if (warmedUp) { // stop tone but keep playing silence + SLresult result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, silenceBuffer, + bufferSizeInBytes); + assert(SL_RESULT_SUCCESS == result); + (void) result; + } else { // stop playing completely + SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Done playing tone"); + } +} + +jlong Java_org_chromium_latency_walt_AudioTest_playTone(JNIEnv* env, jclass clazz){ + + int64_t t_start = uptimeMicros(); + te_play = 0; + + SLresult result; + + if (!warmedUp) { + result = (*bqPlayerBufferQueue)->Clear(bqPlayerBufferQueue); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // Enqueue first buffer + te_play = uptimeMicros(); + result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, beepBuffer, + bufferSizeInBytes); + assert(SL_RESULT_SUCCESS == result); + (void) result; + + result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING); + assert(SL_RESULT_SUCCESS == result); + (void) result; + + int dt_state = uptimeMicros() - t_start; + __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "playTone() changed state to playing dt=%d us", dt_state); + // TODO: this block takes lots of time (~13ms on Nexus 7) research this and decide how to measure. + } + + __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Playing tone"); + buffersRemaining = BUFFERS_TO_PLAY; + + return (jlong) t_start; +} + + +// create the engine and output mix objects +void Java_org_chromium_latency_walt_AudioTest_createEngine(JNIEnv* env, jclass clazz) +{ + __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Creating audio engine"); + + SLresult result; + + // create engine + result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // realize the engine + result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // get the engine interface, which is needed in order to create other objects + result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // create output mix, + result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // realize the output mix + result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + (void)result; +} + +void Java_org_chromium_latency_walt_AudioTest_destroyEngine(JNIEnv *env, jclass clazz) +{ + if (bqPlayerObject != NULL) { + (*bqPlayerObject)->Destroy(bqPlayerObject); + bqPlayerObject = NULL; + } + + if (outputMixObject != NULL) { + (*outputMixObject)->Destroy(outputMixObject); + outputMixObject = NULL; + } + + if (engineObject != NULL) { + (*engineObject)->Destroy(engineObject); + engineObject = NULL; + } +} + +// create buffer queue audio player +void Java_org_chromium_latency_walt_AudioTest_createBufferQueueAudioPlayer(JNIEnv* env, + jclass clazz, jint optimalFrameRate, jint optimalFramesPerBuffer) +{ + __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Creating audio player with frame rate %d and frames per buffer %d", + optimalFrameRate, optimalFramesPerBuffer); + + framesPerBuffer = optimalFramesPerBuffer; + createWaveTables(); + + SLresult result; + + // configure the audio source (supply data through a buffer queue in PCM format) + SLDataLocator_AndroidSimpleBufferQueue locator_bufferqueue_source; + SLDataFormat_PCM format_pcm; + SLDataSource audio_source; + + // source location + locator_bufferqueue_source.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; + locator_bufferqueue_source.numBuffers = 1; + + // source format + format_pcm.formatType = SL_DATAFORMAT_PCM; + format_pcm.numChannels = 1; + + // Note: this shouldn't be called samplesPerSec it should be called *framesPerSec* + // because when channels = 2 then there are 2 samples per frame. + format_pcm.samplesPerSec = (SLuint32) optimalFrameRate * 1000; + format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + format_pcm.containerSize = 16; + format_pcm.channelMask = SL_SPEAKER_FRONT_CENTER; + format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN; + + audio_source.pLocator = &locator_bufferqueue_source; + audio_source.pFormat = &format_pcm; + + // configure the output: An output mix sink + SLDataLocator_OutputMix locator_output_mix; + SLDataSink audio_sink; + + locator_output_mix.locatorType = SL_DATALOCATOR_OUTPUTMIX; + locator_output_mix.outputMix = outputMixObject; + + audio_sink.pLocator = &locator_output_mix; + audio_sink.pFormat = NULL; + + // create audio player + // Note: Adding other output interfaces here will result in your audio being routed using the + // normal path NOT the fast path + const SLInterfaceID interface_ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME }; + const SLboolean interfaces_required[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE }; + + result = (*engineEngine)->CreateAudioPlayer( + engineEngine, + &bqPlayerObject, + &audio_source, + &audio_sink, + 2, // Number of interfaces + interface_ids, + interfaces_required + ); + + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // realize the player + result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // get the play interface + result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // get the buffer queue interface + result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, + &bqPlayerBufferQueue); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // register callback on the buffer queue + result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, NULL); + assert(SL_RESULT_SUCCESS == result); + (void)result; +} + +void Java_org_chromium_latency_walt_AudioTest_startWarmTest(JNIEnv* env, jclass clazz) { + SLresult result; + + result = (*bqPlayerBufferQueue)->Clear(bqPlayerBufferQueue); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // enqueue some silence + result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, silenceBuffer, bufferSizeInBytes); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // set the player's state to playing + result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + warmedUp = 1; +} + +void Java_org_chromium_latency_walt_AudioTest_stopTests(JNIEnv *env, jclass clazz) { + SLresult result; + + result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + warmedUp = 0; +} + +// this callback handler is called every time a buffer finishes recording +void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context) +{ + tc_rec = uptimeMicros(); + assert(bq == recorderBufferQueue); + assert(NULL == context); + + // for streaming recording, here we would call Enqueue to give recorder the next buffer to fill + // but instead, this is a one-time buffer so we stop recording + SLresult result; + result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED); + if (SL_RESULT_SUCCESS == result) { + recorderSize = recorder_frames * sizeof(short); + } + bqPlayerRecorderBusy = 0; + + //// TODO: Use small buffers and re-enqueue each time + // result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recorderBuffer, + // recorder_frames * sizeof(short)); + // assert(SL_RESULT_SUCCESS == result); +} + +// create audio recorder +jboolean Java_org_chromium_latency_walt_AudioTest_createAudioRecorder(JNIEnv* env, + jclass clazz, jint optimalFrameRate, jint framesToRecord) +{ + SLresult result; + + __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Creating audio recorder with frame rate %d and frames to record %d", + optimalFrameRate, framesToRecord); + // Allocate buffer + recorder_frames = framesToRecord; + recorderBuffer = malloc(sizeof(*recorderBuffer) * recorder_frames); + + // configure audio source + SLDataLocator_IODevice loc_dev = { + SL_DATALOCATOR_IODEVICE, + SL_IODEVICE_AUDIOINPUT, + SL_DEFAULTDEVICEID_AUDIOINPUT, + NULL + }; + SLDataSource audioSrc = {&loc_dev, NULL}; + + // configure audio sink + SLDataLocator_AndroidSimpleBufferQueue loc_bq; + loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; + loc_bq.numBuffers = 2; + + + // source format + SLDataFormat_PCM format_pcm; + format_pcm.formatType = SL_DATAFORMAT_PCM; + format_pcm.numChannels = CHANNELS; + // Note: this shouldn't be called samplesPerSec it should be called *framesPerSec* + // because when channels = 2 then there are 2 samples per frame. + format_pcm.samplesPerSec = (SLuint32) optimalFrameRate * 1000; + format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + format_pcm.containerSize = 16; + format_pcm.channelMask = SL_SPEAKER_FRONT_CENTER; + format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN; + + + SLDataSink audioSnk = {&loc_bq, &format_pcm}; + + // create audio recorder + // (requires the RECORD_AUDIO permission) + const SLInterfaceID id[2] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + SL_IID_ANDROIDCONFIGURATION }; + const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; + result = (*engineEngine)->CreateAudioRecorder(engineEngine, + &recorderObject, + &audioSrc, + &audioSnk, + sizeof(id)/sizeof(id[0]), + id, req); + + // Configure the voice recognition preset which has no + // signal processing for lower latency. + SLAndroidConfigurationItf inputConfig; + result = (*recorderObject)->GetInterface(recorderObject, + SL_IID_ANDROIDCONFIGURATION, + &inputConfig); + if (SL_RESULT_SUCCESS == result) { + SLuint32 presetValue = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION; + (*inputConfig)->SetConfiguration(inputConfig, + SL_ANDROID_KEY_RECORDING_PRESET, + &presetValue, + sizeof(SLuint32)); + } + + // realize the audio recorder + result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != result) { + return JNI_FALSE; + } + + // get the record interface + result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // get the buffer queue interface + result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &recorderBufferQueue); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // register callback on the buffer queue + result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, bqRecorderCallback, + NULL); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Audio recorder created, buffer size: %d frames", + recorder_frames); + + return JNI_TRUE; +} + + +// set the recording state for the audio recorder +void Java_org_chromium_latency_walt_AudioTest_startRecording(JNIEnv* env, jclass clazz) +{ + SLresult result; + + if( bqPlayerRecorderBusy) { + return; + } + // in case already recording, stop recording and clear buffer queue + result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED); + assert(SL_RESULT_SUCCESS == result); + (void)result; + result = (*recorderBufferQueue)->Clear(recorderBufferQueue); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // the buffer is not valid for playback yet + recorderSize = 0; + + // enqueue an empty buffer to be filled by the recorder + // (for streaming recording, we would enqueue at least 2 empty buffers to start things off) + te_rec = uptimeMicros(); // TODO: investigate if it's better to time after SetRecordState + tc_rec = 0; + result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recorderBuffer, + recorder_frames * sizeof(short)); + // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT, + // which for this code example would indicate a programming error + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // start recording + result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING); + assert(SL_RESULT_SUCCESS == result); + (void)result; + bqPlayerRecorderBusy = 1; +} + +jshortArray Java_org_chromium_latency_walt_AudioTest_getRecordedWave(JNIEnv *env, jclass cls) +{ + jshortArray result; + result = (*env)->NewShortArray(env, recorder_frames); + if (result == NULL) { + return NULL; /* out of memory error thrown */ + } + (*env)->SetShortArrayRegion(env, result, 0, recorder_frames, recorderBuffer); + return result; +} + +jlong Java_org_chromium_latency_walt_AudioTest_getTcRec(JNIEnv *env, jclass cls) { + return (jlong) tc_rec; +} + +jlong Java_org_chromium_latency_walt_AudioTest_getTeRec(JNIEnv *env, jclass cls) { + return (jlong) te_rec; +} + +jlong Java_org_chromium_latency_walt_AudioTest_getTePlay(JNIEnv *env, jclass cls) { + return (jlong) te_play; +} diff --git a/android/WALT/app/src/main/jni/sync_clock.c b/android/WALT/app/src/main/jni/sync_clock.c new file mode 100644 index 0000000..1591f88 --- /dev/null +++ b/android/WALT/app/src/main/jni/sync_clock.c @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2015 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 "sync_clock.h" + +#include <asm/byteorder.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <linux/usbdevice_fs.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/inotify.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <time.h> +#include <unistd.h> + + +#ifdef __ANDROID__ + #include <android/log.h> + #define LOGD(...) __android_log_print(ANDROID_LOG_VERBOSE, "ClockSyncNative", __VA_ARGS__) +#else + #define LOGD(...) printf(__VA_ARGS__) +#endif + + +// How many times to repeat the 1..9 digit sequence it's a tradeoff between +// precision and how long it takes. +// TODO: investigate better combination of constants for repeats and wait times +const int kSyncRepeats = 7; +const int kMillion = 1000000; + + +/** +uptimeMicros() - returns microseconds elapsed since boot. +Same time as Android's SystemClock.uptimeMillis() but in microseconds. + +Adapted from Android: +platform/system/core/libutils/Timers.cpp +platform/system/core/include/utils/Timers.h + +See: +http://developer.android.com/reference/android/os/SystemClock.html +https://android.googlesource.com/platform/system/core/+/master/libutils/Timers.cpp +*/ +int64_t uptimeMicros() { + struct timespec ts = {0}; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ((int64_t)ts.tv_sec) * kMillion + ts.tv_nsec / 1000; +} + + +// Sleeps us microseconds +int microsleep(int us) { + struct timespec ts = {0}; + ts.tv_sec = us / kMillion; + us %= kMillion; + ts.tv_nsec = us*1000; + nanosleep(&ts, NULL); +} + + +// *********************** Generic USB functions ******************************* + +static int send_char_async(int fd, int endpoint, char msg, char * label) { + // TODO: Do we really need a buffer longer than 1 char here? + char buffer[256] = {0}; + buffer[0] = msg; + int length = 1; + + // TODO: free() the memory used for URBs. + // Circular buffer of URBs? Cleanup at the end of clock sync? + // Several may be used simultaneously, no signal when done. + struct usbdevfs_urb *urb = calloc(1, sizeof(struct usbdevfs_urb)); + memset(urb, 0, sizeof(struct usbdevfs_urb)); + + int res; + urb->status = -1; + urb->buffer = buffer; + urb->buffer_length = length; + urb->endpoint = endpoint; + urb->type = USBDEVFS_URB_TYPE_BULK; + urb->usercontext = label; // This is hackish + do { + res = ioctl(fd, USBDEVFS_SUBMITURB, urb); + } while((res < 0) && (errno == EINTR)); + return res; +} + + +// Send or read using USBDEVFS_BULK. Allows to set a timeout. +static int bulk_talk(int fd, int endpoint, char * buffer, int length) { + // Set some reasonable timeout. 20ms is plenty time for most transfers but + // short enough to fail quickly if all transfers and retries fail with + // timeout. + const int kTimeoutMs = 20; + struct usbdevfs_bulktransfer ctrl = {0}; + // TODO: need to limit request size to avoid EINVAL + + ctrl.ep = endpoint; + ctrl.len = length; + ctrl.data = buffer; + ctrl.timeout = kTimeoutMs; + int ret = ioctl(fd, USBDEVFS_BULK, &ctrl); + return ret; +} + + +/******************************************************************************* +* Clock sync specific stuff below. +* Most data is stored in the clock_connection struct variable. +*/ + +// Send a single character to the remote in a blocking mode +int send_cmd(struct clock_connection *clk, char cmd) { + return bulk_talk(clk->fd, clk->endpoint_out, &cmd, 1); +} + +// Schedule a single character to be sent to the remote - async. +int send_async(struct clock_connection *clk, char cmd) { + return send_char_async(clk->fd, clk->endpoint_out, cmd, NULL); +} + + +int bulk_read(struct clock_connection *clk) { + memset(clk->buffer, 0, sizeof(clk->buffer)); + int ret = bulk_talk(clk->fd, clk->endpoint_in, clk->buffer, sizeof(clk->buffer)); + return ret; +} + +// microseconds elapsed since clk->t_base +int micros(struct clock_connection *clk) { + return uptimeMicros() - clk->t_base; +} + +// Clear all incoming data that's already waiting somewhere in kernel buffers +// and discard it. +void flush_incoming(struct clock_connection *clk) { + // When bulk_read times out errno = ETIMEDOUT=110, retval =-1 + // should we check for this? + + while(bulk_read(clk) >= 0) { + // TODO: fail nicely if waiting too long to avoid hangs + } +} + +// Ask the remote to send its timestamps +// for the digits previously sent to it. +void read_remote_timestamps(struct clock_connection *clk, int * times_remote) { + int i; + int t_remote; + // Go over the digits [1, 2 ... 9] + for (i = 0; i < 9; i++) { + char digit = i + '1'; + send_cmd(clk, CMD_SYNC_READOUT); + bulk_read(clk); + if (clk->buffer[0] != digit) { + LOGD("Error, bad reply for R%d: %s", i+1, clk->buffer); + } + // The reply string looks like digit + space + timestamp + // Offset by 2 to ignore the digit and the space + t_remote = atoi(clk->buffer + 2); + times_remote[i] = t_remote; + } +} + + +// Preliminary rough sync with a single message - CMD_SYNC_ZERO = 'Z'. +// This is not strictly necessary but greatly simplifies debugging +// by removing the need to look at very long numbers. +void zero_remote(struct clock_connection *clk) { + flush_incoming(clk); + clk->t_base = uptimeMicros(); + send_cmd(clk, CMD_SYNC_ZERO); + bulk_read(clk); // TODO, make sure we got 'z' + clk->maxE = micros(clk); + clk->minE = 0; + + LOGD("Sent a 'Z', reply '%c' in %d us\n", clk->buffer[0], clk->maxE); +} + + + +void improve_minE(struct clock_connection *clk) { + int times_local_sent[9] = {0}; + int times_remote_received[9] = {0}; + + // Set sleep time as 1/kSleepTimeDivider of the current bounds interval, + // but never less or more than k(Min/Max)SleepUs. All pretty random + // numbers that could use some tuning and may behave differently on + // different devices. + const int kMaxSleepUs = 700; + const int kMinSleepUs = 70; + const int kSleepTimeDivider = 10; + int minE = clk->minE; + int sleep_time = (clk->maxE - minE) / kSleepTimeDivider; + if(sleep_time > kMaxSleepUs) sleep_time = kMaxSleepUs; + if(sleep_time < kMinSleepUs) sleep_time = kMinSleepUs; + + flush_incoming(clk); + // Send digits to remote side + int i; + for (i = 0; i < 9; i++) { + char c = i + '1'; + times_local_sent[i] = micros(clk); + send_async(clk, c); + microsleep(sleep_time); + } + + // Read out receive times from the other side + read_remote_timestamps(clk, times_remote_received); + + // Do stats + for (i = 0; i < 9; i++) { + int tls = times_local_sent[i]; + int trr = times_remote_received[i]; + + int dt; + + // Look at outgoing digits + dt = tls - trr; + if (tls != 0 && trr != 0 && dt > minE) { + minE = dt; + } + + } + + clk->minE = minE; + + LOGD("E is between %d and %d us, sleep_time=%d\n", clk->minE, clk->maxE, sleep_time); +} + +void improve_maxE(struct clock_connection *clk) { + int times_remote_sent[9] = {0}; + int times_local_received[9] = {0}; + + // Tell the remote to send us digits with delays + // TODO: try tuning / configuring the delay time on remote side + send_async(clk, CMD_SYNC_SEND); + + // Read and timestamp the incoming digits, they may arrive out of order. + // TODO: Try he same with USBDEVFS_REAPURB, it might be faster + int i; + for (i = 0; i < 9; ++i) { + int retval = bulk_read(clk); + // TODO: deal with retval = (bytes returned) > 1. shouldn't happen. + // Can it happen on some devices? + int t_local = micros(clk); + int digit = atoi(clk->buffer); + if (digit <=0 || digit > 9) { + LOGD("Error, bad incoming digit: %s\n", clk->buffer); + } + times_local_received[digit-1] = t_local; + } + + // Flush whatever came after the digits. As of this writing, it's usually + // a single linefeed character. + flush_incoming(clk); + // Read out the remote timestamps of when the digits were sent + read_remote_timestamps(clk, times_remote_sent); + + // Do stats + int maxE = clk->maxE; + for (i = 0; i < 9; i++) { + int trs = times_remote_sent[i]; + int tlr = times_local_received[i]; + int dt = tlr - trs; + if (tlr != 0 && trs != 0 && dt < maxE) { + maxE = dt; + } + } + + clk->maxE = maxE; + + LOGD("E is between %d and %d us\n", clk->minE, clk->maxE); +} + + +void improve_bounds(struct clock_connection *clk) { + improve_minE(clk); + improve_maxE(clk); +} + +// get minE and maxE again after some time to check for clock drift +void update_bounds(struct clock_connection *clk) { + // Reset the bounds to some unrealistically large numbers + int i; + clk->minE = -1e7; + clk->maxE = 1e7; + // Talk to remote to get bounds on minE and maxE + for (i=0; i < kSyncRepeats; i++) { + improve_bounds(clk); + } +} + +void sync_clocks(struct clock_connection *clk) { + // Send CMD_SYNC_ZERO to remote for rough initial sync + zero_remote(clk); + + int rep; + for (rep=0; rep < kSyncRepeats; rep++) { + improve_bounds(clk); + } + + // Shift the base time to set minE = 0 + clk->t_base += clk->minE; + clk->maxE -= clk->minE; + clk->minE = 0; + LOGD("Base time shifted for zero minE\n"); +} + + diff --git a/android/WALT/app/src/main/jni/sync_clock.h b/android/WALT/app/src/main/jni/sync_clock.h new file mode 100644 index 0000000..862bb7d --- /dev/null +++ b/android/WALT/app/src/main/jni/sync_clock.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 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 <inttypes.h> + +#define CLOCK_BUFFER_LENGTH 512 + +// Commands, original definitions in TensyUSB side code. +#define CMD_RESET 'F' // Reset all vars +#define CMD_SYNC_SEND 'I' // Send some digits for clock sync +#define CMD_SYNC_READOUT 'R' // Read out sync times +#define CMD_SYNC_ZERO 'Z' // Initial zero + + +struct clock_connection { + int fd; + int endpoint_in; + int endpoint_out; + int64_t t_base; + char buffer[CLOCK_BUFFER_LENGTH]; + int minE; + int maxE; +}; + + +// Returns microseconds elapsed since boot +int64_t uptimeMicros(); + +// Returns microseconds elapsed since last clock sync +int micros(struct clock_connection *clk); + +// Runs clock synchronization logic +void sync_clocks(struct clock_connection *clk); + +// Run the sync logic without changing clocks, used for estimating clock drift +void update_bounds(struct clock_connection *clk); + diff --git a/android/WALT/app/src/main/jni/sync_clock_jni.c b/android/WALT/app/src/main/jni/sync_clock_jni.c new file mode 100644 index 0000000..15adfd5 --- /dev/null +++ b/android/WALT/app/src/main/jni/sync_clock_jni.c @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2015 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 "sync_clock.h" + +#include <android/log.h> +#include <jni.h> + + +#define APPNAME "ClockSyncJNI" + +// This is global so that we don't have to pass it aroundbetween Java and here. +// TODO: come up with some more elegant solution. +struct clock_connection clk; + +jlong +Java_org_chromium_latency_walt_WaltUsbConnection_syncClock__III( + JNIEnv* env, + jobject thiz, + jint fd, + jint endpoint_out, + jint endpoint_in +){ + clk.fd = (int)fd; + clk.endpoint_in = (int)endpoint_in; + clk.endpoint_out = (int)endpoint_out; + clk.t_base = 0; + sync_clocks(&clk); + // __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Returned from sync_clocks\n"); + + int64_t t_base = clk.t_base; + return (jlong) t_base; +} + +void +Java_org_chromium_latency_walt_WaltUsbConnection_updateBounds() { + update_bounds(&clk); +} + +jint +Java_org_chromium_latency_walt_WaltUsbConnection_getMinE() { + return clk.minE; +} + + +jint +Java_org_chromium_latency_walt_WaltUsbConnection_getMaxE() { + return clk.maxE; +} diff --git a/android/WALT/app/src/main/jni/sync_clock_linux.c b/android/WALT/app/src/main/jni/sync_clock_linux.c new file mode 100644 index 0000000..1287b76 --- /dev/null +++ b/android/WALT/app/src/main/jni/sync_clock_linux.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2015 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 "sync_clock.h" + +#include <errno.h> +#include <fcntl.h> +#include <linux/usbdevice_fs.h> +#include <stdio.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <unistd.h> + + +int main(int argc, char ** argv) { + if(argc < 2) { + printf("Usage %s <device_path>\n" + "Try `lsusb | grep eensy` and use /dev/bus/usb/<Bus>/<Device>\n", + argv[0]); + return 1; + } + + printf("Opening %s\n", argv[1]); + int fd = open(argv[1], O_RDWR); + printf("open() fd=%d, errno=%d, %s\n", fd, errno, strerror(errno)); + + // The interface and endpoint numbers are defined by the TeensyUSB. They may + // be different depending on the mode (Serial vs HID) the Teensy code is + // compiled in. A real app would employ some discovery logic here. To list + // the interfaces and endpoints use `lsusb --verbose` or an app like USB + // Host Viewer on Android. Look for a "CDC Data" interface (class 0x0a). + int interface = 1; + int ep_out = 0x03; + int ep_in = 0x84; + + int ret = ioctl(fd, USBDEVFS_CLAIMINTERFACE, &interface); + printf("Interface claimed retval=%d, errno=%d, %s\n", ret, errno, strerror(errno)); + if (errno == EBUSY) { + printf("You may need to run 'sudo rmmod cdc_acm' to release the " + "interface claimed by the kernel serial driver."); + return 1; + } + + struct clock_connection clk; + clk.fd = fd; + clk.endpoint_in = ep_in; + clk.endpoint_out = ep_out; + + sync_clocks(&clk); + + printf("===========================\n" + "sync_clocks base_t=%lld, minE=%d, maxE=%d\n", + (long long int)clk.t_base, clk.minE, clk.maxE); + + // Check for clock drift. Try sleeping here to let it actually drift away. + update_bounds(&clk); + + printf("*** UPDATE ****************\n" + "Update_bounds base_t=%lld, minE=%d, maxE=%d\n", + (long long int)(clk.t_base), clk.minE, clk.maxE + ); + + + close(fd); + return 0; +}
\ No newline at end of file |