aboutsummaryrefslogtreecommitdiff
path: root/android/WALT/app/src/main/jni
diff options
context:
space:
mode:
authorAndrew Lehmer <alehmer@google.com>2017-04-26 14:58:59 -0700
committerAndrew Lehmer <alehmer@google.com>2017-04-26 14:58:59 -0700
commite76dcf96b0c451e46cddfa695de8feeb92533937 (patch)
treeed9a45d409f988f517e6c3f3a685cbf81ac45a5a /android/WALT/app/src/main/jni
parentbcf013dda8ffac9fd76937be6441b44bb9f3586f (diff)
downloadwalt-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.mk30
-rw-r--r--android/WALT/app/src/main/jni/Application.mk17
-rw-r--r--android/WALT/app/src/main/jni/Makefile17
-rw-r--r--android/WALT/app/src/main/jni/README.md112
-rwxr-xr-xandroid/WALT/app/src/main/jni/findteensy.py30
-rw-r--r--android/WALT/app/src/main/jni/player.c520
-rw-r--r--android/WALT/app/src/main/jni/sync_clock.c327
-rw-r--r--android/WALT/app/src/main/jni/sync_clock.h50
-rw-r--r--android/WALT/app/src/main/jni/sync_clock_jni.c62
-rw-r--r--android/WALT/app/src/main/jni/sync_clock_linux.c80
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