am 16e1678d: Merge "Make the SyncManager have wakelocks per account, not per account type, otherwise the parallel sync behavior of gmail can cause the first account to finish to release the wakelock that is used for the other account, which will result in the device g
* commit '16e1678df48f6de4eb602f9cd18baa1e40b1181f':
Make the SyncManager have wakelocks per account, not per account type, otherwise the parallel sync behavior of gmail can cause the first account to finish to release the wakelock that is used for the other account, which will result in the device going to sleep before the sync is complete.
diff --git a/Android.mk b/Android.mk
index d4dc088..4f62d78 100644
--- a/Android.mk
+++ b/Android.mk
@@ -109,6 +109,7 @@
core/java/android/content/pm/IPackageMoveObserver.aidl \
core/java/android/content/pm/IPackageStatsObserver.aidl \
core/java/android/database/IContentObserver.aidl \
+ core/java/android/hardware/ISerialManager.aidl \
core/java/android/hardware/usb/IUsbManager.aidl \
core/java/android/net/IConnectivityManager.aidl \
core/java/android/net/INetworkManagementEventObserver.aidl \
@@ -219,7 +220,11 @@
LOCAL_NO_EMMA_COMPILE := true
# List of classes and interfaces which should be loaded by the Zygote.
+ifneq ($(TARGET_PRELOADED_CLASSES),)
+LOCAL_JAVA_RESOURCE_FILES += $(TARGET_PRELOADED_CLASSES)
+else
LOCAL_JAVA_RESOURCE_FILES += $(LOCAL_PATH)/preloaded-classes
+endif
#LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
diff --git a/api/current.txt b/api/current.txt
index d113e90..ca4da60 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -94,6 +94,7 @@
field public static final java.lang.String REORDER_TASKS = "android.permission.REORDER_TASKS";
field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
field public static final java.lang.String SEND_SMS = "android.permission.SEND_SMS";
+ field public static final java.lang.String SERIAL_PORT = "android.permission.SERIAL_PORT";
field public static final java.lang.String SET_ACTIVITY_WATCHER = "android.permission.SET_ACTIVITY_WATCHER";
field public static final java.lang.String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
field public static final java.lang.String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH";
@@ -5120,6 +5121,7 @@
field public static final java.lang.String POWER_SERVICE = "power";
field public static final java.lang.String SEARCH_SERVICE = "search";
field public static final java.lang.String SENSOR_SERVICE = "sensor";
+ field public static final java.lang.String SERIAL_SERVICE = "serial";
field public static final java.lang.String STORAGE_SERVICE = "storage";
field public static final java.lang.String TELEPHONY_SERVICE = "phone";
field public static final java.lang.String TEXT_SERVICES_MANAGER_SERVICE = "textservices";
@@ -9603,6 +9605,18 @@
field public static final float STANDARD_GRAVITY = 9.80665f;
}
+ public class SerialManager {
+ method public java.lang.String[] getSerialPorts();
+ method public android.hardware.SerialPort openSerialPort(java.lang.String, int) throws java.io.IOException;
+ }
+
+ public class SerialPort {
+ method public void close() throws java.io.IOException;
+ method public java.lang.String getName();
+ method public int read(java.nio.ByteBuffer) throws java.io.IOException;
+ method public void write(java.nio.ByteBuffer, int) throws java.io.IOException;
+ }
+
}
package android.hardware.usb {
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 293116cb..9a75e44 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -195,6 +195,21 @@
}
intent.putExtra(key, list);
hasIntentInfo = true;
+ } else if (opt.equals("--ef")) {
+ String key = nextArgRequired();
+ String value = nextArgRequired();
+ intent.putExtra(key, Float.valueOf(value));
+ hasIntentInfo = true;
+ } else if (opt.equals("--efa")) {
+ String key = nextArgRequired();
+ String value = nextArgRequired();
+ String[] strings = value.split(",");
+ float[] list = new float[strings.length];
+ for (int i = 0; i < strings.length; i++) {
+ list[i] = Float.valueOf(strings[i]);
+ }
+ intent.putExtra(key, list);
+ hasIntentInfo = true;
} else if (opt.equals("--ez")) {
String key = nextArgRequired();
String value = nextArgRequired();
@@ -1221,9 +1236,11 @@
" [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]\n" +
" [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]\n" +
" [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]\n" +
+ " [--ef <EXTRA_KEY> <EXTRA_FLOAT_VALUE> ...]\n" +
" [--eu <EXTRA_KEY> <EXTRA_URI_VALUE> ...]\n" +
" [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]\n" +
" [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" +
+ " [--efa <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]\n" +
" [-n <COMPONENT>] [-f <FLAGS>]\n" +
" [--grant-read-uri-permission] [--grant-write-uri-permission]\n" +
" [--debug-log-resolution] [--exclude-stopped-packages]\n" +
diff --git a/cmds/servicemanager/service_manager.c b/cmds/servicemanager/service_manager.c
index 2df450f3..b45b257 100644
--- a/cmds/servicemanager/service_manager.c
+++ b/cmds/servicemanager/service_manager.c
@@ -46,6 +46,7 @@
{ AID_RADIO, "isms" },
{ AID_RADIO, "iphonesubinfo" },
{ AID_RADIO, "simphonebook" },
+ { AID_MEDIA, "aah.common_clock" },
};
void *svcmgr_handle;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 2139704..404ba87 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -42,7 +42,9 @@
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
+import android.hardware.ISerialManager;
import android.hardware.SensorManager;
+import android.hardware.SerialManager;
import android.hardware.usb.IUsbManager;
import android.hardware.usb.UsbManager;
import android.location.CountryDetector;
@@ -427,6 +429,12 @@
return new UsbManager(ctx, IUsbManager.Stub.asInterface(b));
}});
+ registerService(SERIAL_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(SERIAL_SERVICE);
+ return new SerialManager(ctx, ISerialManager.Stub.asInterface(b));
+ }});
+
registerService(VIBRATOR_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
return new Vibrator();
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 48f94d0..140b620 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1783,6 +1783,15 @@
public static final String USB_SERVICE = "usb";
/**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.hardware.SerialManager} for access to serial ports.
+ *
+ * @see #getSystemService
+ * @see android.harware.SerialManager
+ */
+ public static final String SERIAL_SERVICE = "serial";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/hardware/ISerialManager.aidl b/core/java/android/hardware/ISerialManager.aidl
new file mode 100644
index 0000000..74d30f7
--- /dev/null
+++ b/core/java/android/hardware/ISerialManager.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+package android.hardware;
+
+import android.os.ParcelFileDescriptor;
+
+/** @hide */
+interface ISerialManager
+{
+ /* Returns a list of all available serial ports */
+ String[] getSerialPorts();
+
+ /* Returns a file descriptor for the serial port. */
+ ParcelFileDescriptor openSerialPort(String name);
+}
diff --git a/core/java/android/hardware/SerialManager.java b/core/java/android/hardware/SerialManager.java
new file mode 100644
index 0000000..cc4555c
--- /dev/null
+++ b/core/java/android/hardware/SerialManager.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+
+package android.hardware;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * This class provides access to serial ports
+ */
+public class SerialManager {
+ private static final String TAG = "SerialManager";
+
+ private final Context mContext;
+ private final ISerialManager mService;
+
+ /**
+ * {@hide}
+ */
+ public SerialManager(Context context, ISerialManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Returns a string array containing the names of available serial ports
+ *
+ * @return names of available serial ports
+ */
+ public String[] getSerialPorts() {
+ try {
+ return mService.getSerialPorts();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in getSerialPorts", e);
+ return null;
+ }
+ }
+
+ /**
+ * Opens and returns the {@link android.hardware.SerialPort} with the given name.
+ * The speed of the serial port must be one of:
+ * 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600,
+ * 19200, 38400, 57600, 115200, 230400, 460800, 500000, 576000, 921600, 1000000, 1152000,
+ * 1500000, 2000000, 2500000, 3000000, 3500000 or 4000000
+ *
+ * @param name of the serial port
+ * @param speed at which to open the serial port
+ * @return the serial port
+ */
+ public SerialPort openSerialPort(String name, int speed) throws IOException {
+ try {
+ ParcelFileDescriptor pfd = mService.openSerialPort(name);
+ if (pfd != null) {
+ SerialPort port = new SerialPort(name);
+ port.open(pfd, speed);
+ return port;
+ } else {
+ throw new IOException("Could not open serial port " + name);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "exception in UsbManager.openDevice", e);
+ }
+ return null;
+ }
+}
diff --git a/core/java/android/hardware/SerialPort.java b/core/java/android/hardware/SerialPort.java
new file mode 100644
index 0000000..5aee0f6
--- /dev/null
+++ b/core/java/android/hardware/SerialPort.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+package android.hardware;
+
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.nio.ByteBuffer;
+
+/**
+ * This class provides support for reading and writing data to serial ports
+ */
+public class SerialPort {
+
+ private static final String TAG = "SerialPort";
+
+ // used by the JNI code
+ private int mNativeContext;
+ private final String mName;
+ private ParcelFileDescriptor mFileDescriptor;
+
+ /**
+ * SerialPort should only be instantiated by SerialManager
+ * @hide
+ */
+ public SerialPort(String name) {
+ mName = name;
+ }
+
+ /**
+ * SerialPort should only be instantiated by SerialManager
+ * Speed must be one of 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600,
+ * 19200, 38400, 57600, 115200, 230400, 460800, 500000, 576000, 921600, 1000000, 1152000,
+ * 1500000, 2000000, 2500000, 3000000, 3500000, 4000000
+ *
+ * @hide
+ */
+ public void open(ParcelFileDescriptor pfd, int speed) throws IOException {
+ native_open(pfd.getFileDescriptor(), speed);
+ mFileDescriptor = pfd;
+ }
+
+ /**
+ * Closes the serial port
+ */
+ public void close() throws IOException {
+ if (mFileDescriptor != null) {
+ mFileDescriptor.close();
+ mFileDescriptor = null;
+ }
+ native_close();
+ }
+
+ /**
+ * Returns the name of the serial port
+ *
+ * @return the serial port's name
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Reads data into the provided buffer
+ *
+ * @param buffer to read into
+ * @return number of bytes read
+ */
+ public int read(ByteBuffer buffer) throws IOException {
+ if (buffer.isDirect()) {
+ return native_read_direct(buffer, buffer.remaining());
+ } else if (buffer.hasArray()) {
+ return native_read_array(buffer.array(), buffer.remaining());
+ } else {
+ throw new IllegalArgumentException("buffer is not direct and has no array");
+ }
+ }
+
+ /**
+ * Writes data from provided buffer
+ *
+ * @param buffer to write
+ * @param length number of bytes to write
+ */
+ public void write(ByteBuffer buffer, int length) throws IOException {
+ if (buffer.isDirect()) {
+ native_write_direct(buffer, length);
+ } else if (buffer.hasArray()) {
+ native_write_array(buffer.array(), length);
+ } else {
+ throw new IllegalArgumentException("buffer is not direct and has no array");
+ }
+ }
+
+ private native void native_open(FileDescriptor pfd, int speed) throws IOException;
+ private native void native_close();
+ private native int native_read_array(byte[] buffer, int length) throws IOException;
+ private native int native_read_direct(ByteBuffer buffer, int length) throws IOException;
+ private native void native_write_array(byte[] buffer, int length) throws IOException;
+ private native void native_write_direct(ByteBuffer buffer, int length) throws IOException;
+}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index cdb622c..fbf512c 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -16,6 +16,7 @@
package android.os.storage;
+import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -534,6 +535,7 @@
* @hide
*/
public String getVolumeState(String mountPoint) {
+ if (mMountService == null) return Environment.MEDIA_REMOVED;
try {
return mMountService.getVolumeState(mountPoint);
} catch (RemoteException e) {
@@ -547,6 +549,7 @@
* @hide
*/
public StorageVolume[] getVolumeList() {
+ if (mMountService == null) return new StorageVolume[0];
try {
Parcelable[] list = mMountService.getVolumeList();
if (list == null) return new StorageVolume[0];
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index aece5f0..bc6489b 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -128,6 +128,7 @@
android_media_ToneGenerator.cpp \
android_hardware_Camera.cpp \
android_hardware_SensorManager.cpp \
+ android_hardware_SerialPort.cpp \
android_hardware_UsbDevice.cpp \
android_hardware_UsbDeviceConnection.cpp \
android_hardware_UsbRequest.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index dd7dd86..12ad5fc 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -76,6 +76,7 @@
extern int register_android_hardware_Camera(JNIEnv *env);
extern int register_android_hardware_SensorManager(JNIEnv *env);
+extern int register_android_hardware_SerialPort(JNIEnv *env);
extern int register_android_hardware_UsbDevice(JNIEnv *env);
extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env);
extern int register_android_hardware_UsbRequest(JNIEnv *env);
@@ -1162,6 +1163,7 @@
REG_JNI(register_com_android_internal_os_ZygoteInit),
REG_JNI(register_android_hardware_Camera),
REG_JNI(register_android_hardware_SensorManager),
+ REG_JNI(register_android_hardware_SerialPort),
REG_JNI(register_android_hardware_UsbDevice),
REG_JNI(register_android_hardware_UsbDeviceConnection),
REG_JNI(register_android_hardware_UsbRequest),
diff --git a/core/jni/android_hardware_SerialPort.cpp b/core/jni/android_hardware_SerialPort.cpp
new file mode 100644
index 0000000..7922115
--- /dev/null
+++ b/core/jni/android_hardware_SerialPort.cpp
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "SerialPortJNI"
+
+#include "utils/Log.h"
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <termios.h>
+
+using namespace android;
+
+static jfieldID field_context;
+
+static void
+android_hardware_SerialPort_open(JNIEnv *env, jobject thiz, jobject fileDescriptor, jint speed)
+{
+ switch (speed) {
+ case 50:
+ speed = B50;
+ break;
+ case 75:
+ speed = B75;
+ break;
+ case 110:
+ speed = B110;
+ break;
+ case 134:
+ speed = B134;
+ break;
+ case 150:
+ speed = B150;
+ break;
+ case 200:
+ speed = B200;
+ break;
+ case 300:
+ speed = B300;
+ break;
+ case 600:
+ speed = B600;
+ break;
+ case 1200:
+ speed = B1200;
+ break;
+ case 1800:
+ speed = B1800;
+ break;
+ case 2400:
+ speed = B2400;
+ break;
+ case 4800:
+ speed = B4800;
+ break;
+ case 9600:
+ speed = B9600;
+ break;
+ case 19200:
+ speed = B19200;
+ break;
+ case 38400:
+ speed = B38400;
+ break;
+ case 57600:
+ speed = B57600;
+ break;
+ case 115200:
+ speed = B115200;
+ break;
+ case 230400:
+ speed = B230400;
+ break;
+ case 460800:
+ speed = B460800;
+ break;
+ case 500000:
+ speed = B500000;
+ break;
+ case 576000:
+ speed = B576000;
+ break;
+ case 921600:
+ speed = B921600;
+ break;
+ case 1000000:
+ speed = B1000000;
+ break;
+ case 1152000:
+ speed = B1152000;
+ break;
+ case 1500000:
+ speed = B1500000;
+ break;
+ case 2000000:
+ speed = B2000000;
+ break;
+ case 2500000:
+ speed = B2500000;
+ break;
+ case 3000000:
+ speed = B3000000;
+ break;
+ case 3500000:
+ speed = B3500000;
+ break;
+ case 4000000:
+ speed = B4000000;
+ break;
+ default:
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "Unsupported serial port speed");
+ return;
+ }
+
+ int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+ // duplicate the file descriptor, since ParcelFileDescriptor will eventually close its copy
+ fd = dup(fd);
+ if (fd < 0) {
+ jniThrowException(env, "java/io/IOException", "Could not open serial port");
+ return;
+ }
+ env->SetIntField(thiz, field_context, fd);
+
+ struct termios tio;
+ if (tcgetattr(fd, &tio))
+ memset(&tio, 0, sizeof(tio));
+
+ tio.c_cflag = speed | CS8 | CLOCAL | CREAD;
+ tio.c_iflag = IGNPAR;
+ tio.c_lflag = 0; /* turn of CANON, ECHO*, etc */
+ /* no timeout but request at least one character per read */
+ tio.c_cc[VTIME] = 0;
+ tio.c_cc[VMIN] = 1;
+ tcsetattr(fd, TCSANOW, &tio);
+ tcflush(fd, TCIFLUSH);
+}
+
+static void
+android_hardware_SerialPort_close(JNIEnv *env, jobject thiz)
+{
+ int fd = env->GetIntField(thiz, field_context);
+ close(fd);
+ env->SetIntField(thiz, field_context, -1);
+}
+
+static jint
+android_hardware_SerialPort_read_array(JNIEnv *env, jobject thiz, jbyteArray buffer, jint length)
+{
+ int fd = env->GetIntField(thiz, field_context);
+ jbyte* buf = (jbyte *)malloc(length);
+ if (!buf) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+ return -1;
+ }
+
+ int ret = read(fd, buf, length);
+ if (ret > 0) {
+ // copy data from native buffer to Java buffer
+ env->SetByteArrayRegion(buffer, 0, ret, buf);
+ }
+
+ free(buf);
+ if (ret < 0)
+ jniThrowException(env, "java/io/IOException", NULL);
+ return ret;
+}
+
+static jint
+android_hardware_SerialPort_read_direct(JNIEnv *env, jobject thiz, jobject buffer, jint length)
+{
+ int fd = env->GetIntField(thiz, field_context);
+
+ jbyte* buf = (jbyte *)env->GetDirectBufferAddress(buffer);
+ if (!buf) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "ByteBuffer not direct");
+ return -1;
+ }
+
+ int ret = read(fd, buf, length);
+ if (ret < 0)
+ jniThrowException(env, "java/io/IOException", NULL);
+ return ret;
+}
+
+static void
+android_hardware_SerialPort_write_array(JNIEnv *env, jobject thiz, jbyteArray buffer, jint length)
+{
+ int fd = env->GetIntField(thiz, field_context);
+ jbyte* buf = (jbyte *)malloc(length);
+ if (!buf) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+ return;
+ }
+ env->GetByteArrayRegion(buffer, 0, length, buf);
+
+ jint ret = write(fd, buf, length);
+ free(buf);
+ if (ret < 0)
+ jniThrowException(env, "java/io/IOException", NULL);
+}
+
+static void
+android_hardware_SerialPort_write_direct(JNIEnv *env, jobject thiz, jobject buffer, jint length)
+{
+ int fd = env->GetIntField(thiz, field_context);
+
+ jbyte* buf = (jbyte *)env->GetDirectBufferAddress(buffer);
+ if (!buf) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "ByteBuffer not direct");
+ return;
+ }
+ int ret = write(fd, buf, length);
+ if (ret < 0)
+ jniThrowException(env, "java/io/IOException", NULL);
+}
+
+static JNINativeMethod method_table[] = {
+ {"native_open", "(Ljava/io/FileDescriptor;I)V",
+ (void *)android_hardware_SerialPort_open},
+ {"native_close", "()V", (void *)android_hardware_SerialPort_close},
+ {"native_read_array", "([BI)I",
+ (void *)android_hardware_SerialPort_read_array},
+ {"native_read_direct", "(Ljava/nio/ByteBuffer;I)I",
+ (void *)android_hardware_SerialPort_read_direct},
+ {"native_write_array", "([BI)V",
+ (void *)android_hardware_SerialPort_write_array},
+ {"native_write_direct", "(Ljava/nio/ByteBuffer;I)V",
+ (void *)android_hardware_SerialPort_write_direct},
+};
+
+int register_android_hardware_SerialPort(JNIEnv *env)
+{
+ jclass clazz = env->FindClass("android/hardware/SerialPort");
+ if (clazz == NULL) {
+ LOGE("Can't find android/hardware/SerialPort");
+ return -1;
+ }
+ field_context = env->GetFieldID(clazz, "mNativeContext", "I");
+ if (field_context == NULL) {
+ LOGE("Can't find SerialPort.mNativeContext");
+ return -1;
+ }
+
+ return AndroidRuntime::registerNativeMethods(env, "android/hardware/SerialPort",
+ method_table, NELEM(method_table));
+}
diff --git a/core/jni/android_view_Display.cpp b/core/jni/android_view_Display.cpp
index 5e668b9..0b7af85 100644
--- a/core/jni/android_view_Display.cpp
+++ b/core/jni/android_view_Display.cpp
@@ -28,6 +28,7 @@
#include <android_runtime/AndroidRuntime.h>
#include <utils/misc.h>
#include <utils/Log.h>
+#include <cutils/properties.h>
// ----------------------------------------------------------------------------
@@ -44,6 +45,7 @@
jfieldID ydpi;
};
static offsets_t offsets;
+static bool headless = false;
// ----------------------------------------------------------------------------
@@ -51,10 +53,19 @@
JNIEnv* env, jobject clazz, jint dpy)
{
DisplayInfo info;
- status_t err = SurfaceComposerClient::getDisplayInfo(DisplayID(dpy), &info);
- if (err < 0) {
- jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
- return;
+ if (headless) {
+ // initialize dummy display with reasonable values
+ info.pixelFormatInfo.format = 1; // RGB_8888
+ info.fps = 60;
+ info.density = 160;
+ info.xdpi = 160;
+ info.ydpi = 160;
+ } else {
+ status_t err = SurfaceComposerClient::getDisplayInfo(DisplayID(dpy), &info);
+ if (err < 0) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
}
env->SetIntField(clazz, offsets.pixelFormat,info.pixelFormatInfo.format);
env->SetFloatField(clazz, offsets.fps, info.fps);
@@ -66,6 +77,7 @@
static jint android_view_Display_getRawWidth(
JNIEnv* env, jobject clazz)
{
+ if (headless) return 640;
DisplayID dpy = env->GetIntField(clazz, offsets.display);
return SurfaceComposerClient::getDisplayWidth(dpy);
}
@@ -73,6 +85,7 @@
static jint android_view_Display_getRawHeight(
JNIEnv* env, jobject clazz)
{
+ if (headless) return 480;
DisplayID dpy = env->GetIntField(clazz, offsets.display);
return SurfaceComposerClient::getDisplayHeight(dpy);
}
@@ -80,6 +93,7 @@
static jint android_view_Display_getOrientation(
JNIEnv* env, jobject clazz)
{
+ if (headless) return 0; // Surface.ROTATION_0
DisplayID dpy = env->GetIntField(clazz, offsets.display);
return SurfaceComposerClient::getDisplayOrientation(dpy);
}
@@ -87,6 +101,7 @@
static jint android_view_Display_getDisplayCount(
JNIEnv* env, jclass clazz)
{
+ if (headless) return 1;
return SurfaceComposerClient::getNumberOfDisplays();
}
@@ -113,6 +128,12 @@
void nativeClassInit(JNIEnv* env, jclass clazz)
{
+ char value[PROPERTY_VALUE_MAX];
+
+ property_get("ro.config.headless", value, "0");
+ if (strcmp(value, "1") == 0)
+ headless = true;
+
offsets.display = env->GetFieldID(clazz, "mDisplay", "I");
offsets.pixelFormat = env->GetFieldID(clazz, "mPixelFormat", "I");
offsets.fps = env->GetFieldID(clazz, "mRefreshRate", "F");
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 72863a2..a186e6d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1473,6 +1473,12 @@
android:description="@string/permdesc_bindPackageVerifier"
android:protectionLevel="signature" />
+ <!-- Allows applications to access serial ports via the SerialManager. -->
+ <permission android:name="android.permission.SERIAL_PORT"
+ android:label="@string/permlab_serialPort"
+ android:description="@string/permdesc_serialPort"
+ android:protectionLevel="normal" />
+
<!-- The system process is explicitly the only one allowed to launch the
confirmation UI for full backup/restore -->
<uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 051ed14..9b21ca7 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -304,6 +304,12 @@
<string-array name="config_usbHostBlacklist">
</string-array>
+ <!-- List of paths to serial ports that are available to the serial manager.
+ for example, /dev/ttyUSB0
+ -->
+ <string-array translatable="false" name="config_serialPorts">
+ </string-array>
+
<!-- Vibrator pattern for feedback about a long screen/key press -->
<integer-array name="config_longPressVibePattern">
<item>0</item>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index eeff473..80a5eb6 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2228,6 +2228,11 @@
<string name="permdesc_bindPackageVerifier">Allows the holder to make requests of
package verifiers. Should never be needed for normal applications.</string>
+ <!-- Title of an application permission which allows the application to access serial ports via the SerialManager. [CHAR LIMIT=40] -->
+ <string name="permlab_serialPort">access serial ports</string>
+ <!-- Description of an application permission which allows the application access serial ports via the SerialManager. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_serialPort">Allows the holder to access serial ports using the SerialManager API.</string>
+
<!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Text in the save password dialog, asking if the browser should remember a password. -->
<string name="save_password_message">Do you want the browser to remember this password?</string>
<!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Button in the save password dialog, saying not to remember this password. -->
diff --git a/data/etc/android.hardware.bluetooth.xml b/data/etc/android.hardware.bluetooth.xml
new file mode 100644
index 0000000..4aa1744
--- /dev/null
+++ b/data/etc/android.hardware.bluetooth.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<!-- Adds the feature indicating support for the Bluetooth API -->
+<permissions>
+ <feature name="android.hardware.bluetooth" />
+</permissions>
diff --git a/include/aah_timesrv/ICommonClock.h b/include/aah_timesrv/ICommonClock.h
new file mode 100644
index 0000000..d723d1b
--- /dev/null
+++ b/include/aah_timesrv/ICommonClock.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef ANDROID_ICOMMONCLOCK_H
+#define ANDROID_ICOMMONCLOCK_H
+
+#include <stdint.h>
+
+#include <binder/IInterface.h>
+#include <binder/IServiceManager.h>
+
+namespace android {
+
+class ICommonClockListener : public IInterface {
+ public:
+ DECLARE_META_INTERFACE(CommonClockListener);
+
+ virtual void onClockSync(uint32_t timelineID) = 0;
+ virtual void onClockSyncLoss() = 0;
+};
+
+class BnCommonClockListener : public BnInterface<ICommonClockListener> {
+ public:
+ virtual status_t onTransact(uint32_t code, const Parcel& data,
+ Parcel* reply, uint32_t flags = 0);
+};
+
+class ICommonClock : public IInterface {
+ public:
+ DECLARE_META_INTERFACE(CommonClock);
+
+ // Name of the ICommonClock service registered with the service manager.
+ static const String16 kServiceName;
+
+ // a reserved invalid timeline ID
+ static const uint32_t kInvalidTimelineID;
+
+ virtual status_t isCommonTimeValid(bool* valid, uint32_t* timelineID) = 0;
+ virtual status_t commonTimeToLocalTime(int64_t commonTime,
+ int64_t* localTime) = 0;
+ virtual status_t localTimeToCommonTime(int64_t localTime,
+ int64_t* commonTime) = 0;
+ virtual status_t getCommonTime(int64_t* commonTime) = 0;
+ virtual status_t getCommonFreq(uint64_t* freq) = 0;
+ virtual status_t getLocalTime(int64_t* localTime) = 0;
+ virtual status_t getLocalFreq(uint64_t* freq) = 0;
+
+ virtual status_t registerListener(
+ const sp<ICommonClockListener>& listener) = 0;
+ virtual status_t unregisterListener(
+ const sp<ICommonClockListener>& listener) = 0;
+
+ // Simple helper to make it easier to connect to the CommonClock service.
+ static inline sp<ICommonClock> getInstance() {
+ sp<IBinder> binder = defaultServiceManager()->checkService(
+ ICommonClock::kServiceName);
+ sp<ICommonClock> clk = interface_cast<ICommonClock>(binder);
+ return clk;
+ }
+};
+
+class BnCommonClock : public BnInterface<ICommonClock> {
+ public:
+ virtual status_t onTransact(uint32_t code, const Parcel& data,
+ Parcel* reply, uint32_t flags = 0);
+};
+
+}; // namespace android
+
+#endif // ANDROID_ICOMMONCLOCK_H
diff --git a/include/aah_timesrv/cc_helper.h b/include/aah_timesrv/cc_helper.h
new file mode 100644
index 0000000..c86f503
--- /dev/null
+++ b/include/aah_timesrv/cc_helper.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef __CC_HELPER_H__
+#define __CC_HELPER_H__
+
+#include <stdint.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class ICommonClock;
+
+// CCHelper is a simple wrapper class to help with centralizing access to the
+// Common Clock service as well as to implement a simple policy of making a
+// basic attempt to reconnect to the common clock service when things go wrong.
+class CCHelper {
+ public:
+ static status_t isCommonTimeValid(bool* valid, uint32_t* timelineID);
+ static status_t commonTimeToLocalTime(int64_t commonTime, int64_t* localTime);
+ static status_t localTimeToCommonTime(int64_t localTime, int64_t* commonTime);
+ static status_t getCommonTime(int64_t* commonTime);
+ static status_t getCommonFreq(uint64_t* freq);
+ static status_t getLocalTime(int64_t* localTime);
+ static status_t getLocalFreq(uint64_t* freq);
+
+ private:
+ static bool verifyClock_l();
+
+ static Mutex lock_;
+ static sp<ICommonClock> common_clock_;
+};
+
+
+} // namespace android
+#endif // __CC_HELPER_H__
diff --git a/include/aah_timesrv/local_clock.h b/include/aah_timesrv/local_clock.h
new file mode 100644
index 0000000..845d1c21
--- /dev/null
+++ b/include/aah_timesrv/local_clock.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+
+#ifndef __LOCAL_CLOCK_H__
+#define __LOCAL_CLOCK_H__
+
+#include <stdint.h>
+
+#include <hardware/local_time_hal.h>
+#include <utils/Errors.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class LocalClock {
+ public:
+ LocalClock();
+
+ bool initCheck();
+
+ int64_t getLocalTime();
+ uint64_t getLocalFreq();
+ status_t setLocalSlew(int16_t rate);
+ int32_t getDebugLog(struct local_time_debug_event* records,
+ int max_records);
+
+ private:
+ static Mutex dev_lock_;
+ static local_time_hw_device_t* dev_;
+};
+
+} // namespace android
+#endif // __LOCAL_CLOCK_H__
diff --git a/include/media/AudioTrack.h b/include/media/AudioTrack.h
index 923518d..99262eb 100644
--- a/include/media/AudioTrack.h
+++ b/include/media/AudioTrack.h
@@ -416,7 +416,7 @@
*/
status_t dump(int fd, const Vector<String16>& args) const;
-private:
+protected:
/* copying audio tracks is not allowed */
AudioTrack(const AudioTrack& other);
AudioTrack& operator = (const AudioTrack& other);
@@ -486,8 +486,27 @@
int mSessionId;
int mAuxEffectId;
Mutex mLock;
+ bool mIsTimed;
};
+class TimedAudioTrack : public AudioTrack
+{
+public:
+ TimedAudioTrack();
+
+ /* allocate a shared memory buffer that can be passed to queueTimedBuffer */
+ status_t allocateTimedBuffer(size_t size, sp<IMemory>* buffer);
+
+ /* queue a buffer obtained via allocateTimedBuffer for playback at the
+ given timestamp */
+ status_t queueTimedBuffer(const sp<IMemory>& buffer, int64_t pts);
+
+ /* define a transform between media time and either common time or
+ local time */
+ enum TargetTimeline {LOCAL_TIME, COMMON_TIME};
+ status_t setMediaTimeTransform(const LinearTransform& xform,
+ TargetTimeline target);
+};
}; // namespace android
diff --git a/include/media/IAudioFlinger.h b/include/media/IAudioFlinger.h
index 9e3cb7f..f72452c 100644
--- a/include/media/IAudioFlinger.h
+++ b/include/media/IAudioFlinger.h
@@ -54,6 +54,7 @@
uint32_t flags,
const sp<IMemory>& sharedBuffer,
int output,
+ bool isTimed,
int *sessionId,
status_t *status) = 0;
diff --git a/include/media/IAudioTrack.h b/include/media/IAudioTrack.h
index 47d530b..7343a48 100644
--- a/include/media/IAudioTrack.h
+++ b/include/media/IAudioTrack.h
@@ -24,7 +24,7 @@
#include <utils/Errors.h>
#include <binder/IInterface.h>
#include <binder/IMemory.h>
-
+#include <utils/LinearTransform.h>
namespace android {
@@ -69,6 +69,23 @@
/* get this tracks control block */
virtual sp<IMemory> getCblk() const = 0;
+
+ /* Allocate a shared memory buffer suitable for holding timed audio
+ samples */
+ virtual status_t allocateTimedBuffer(size_t size,
+ sp<IMemory>* buffer) = 0;
+
+ /* Queue a buffer obtained via allocateTimedBuffer for playback at the given
+ timestamp */
+ virtual status_t queueTimedBuffer(const sp<IMemory>& buffer,
+ int64_t pts) = 0;
+
+ /* Define the linear transform that will be applied to the timestamps
+ given to queueTimedBuffer (which are expressed in media time).
+ Target specifies whether this transform converts media time to local time
+ or Tungsten time. The values for target are defined in AudioTrack.h */
+ virtual status_t setMediaTimeTransform(const LinearTransform& xform,
+ int target) = 0;
};
// ----------------------------------------------------------------------------
diff --git a/include/media/MediaPlayerInterface.h b/include/media/MediaPlayerInterface.h
index 4328d3c..2e60f5b 100644
--- a/include/media/MediaPlayerInterface.h
+++ b/include/media/MediaPlayerInterface.h
@@ -46,6 +46,9 @@
// The shared library with the test player is passed passed as an
// argument to the 'test:' url in the setDataSource call.
TEST_PLAYER = 5,
+
+ AAH_RX_PLAYER = 100,
+ AAH_TX_PLAYER = 101,
};
diff --git a/libs/aah_timesrv/Android.mk b/libs/aah_timesrv/Android.mk
new file mode 100644
index 0000000..d807b25
--- /dev/null
+++ b/libs/aah_timesrv/Android.mk
@@ -0,0 +1,19 @@
+LOCAL_PATH:= $(call my-dir)
+#
+# libaah_timesrv_client
+# (binder marshalers for ICommonClock as well as common clock and local clock
+# helper code)
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libaah_timesrv_client
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := cc_helper.cpp \
+ local_clock.cpp \
+ ICommonClock.cpp
+LOCAL_SHARED_LIBRARIES := libbinder \
+ libhardware \
+ libutils
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/libs/aah_timesrv/ICommonClock.cpp b/libs/aah_timesrv/ICommonClock.cpp
new file mode 100644
index 0000000..79f1a7e
--- /dev/null
+++ b/libs/aah_timesrv/ICommonClock.cpp
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2011 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 <aah_timesrv/ICommonClock.h>
+#include <binder/Parcel.h>
+
+namespace android {
+
+/***** ICommonClock *****/
+
+enum {
+ IS_COMMON_TIME_VALID = IBinder::FIRST_CALL_TRANSACTION,
+ COMMON_TIME_TO_LOCAL_TIME,
+ LOCAL_TIME_TO_COMMON_TIME,
+ GET_COMMON_TIME,
+ GET_COMMON_FREQ,
+ GET_LOCAL_TIME,
+ GET_LOCAL_FREQ,
+ REGISTER_LISTENER,
+ UNREGISTER_LISTENER,
+};
+
+const String16 ICommonClock::kServiceName("aah.common_clock");
+const uint32_t ICommonClock::kInvalidTimelineID = 0;
+
+class BpCommonClock : public BpInterface<ICommonClock>
+{
+ public:
+ BpCommonClock(const sp<IBinder>& impl)
+ : BpInterface<ICommonClock>(impl) {}
+
+ virtual status_t isCommonTimeValid(bool* valid, uint32_t* timelineID) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(IS_COMMON_TIME_VALID,
+ data,
+ &reply);
+ if (status == OK) {
+ *valid = reply.readInt32();
+ *timelineID = reply.readInt32();
+ }
+ return status;
+ }
+
+ virtual status_t commonTimeToLocalTime(int64_t commonTime,
+ int64_t* localTime) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ data.writeInt64(commonTime);
+ status_t status = remote()->transact(COMMON_TIME_TO_LOCAL_TIME,
+ data, &reply);
+ if (status == OK) {
+ *localTime = reply.readInt64();
+ }
+ return status;
+ }
+
+ virtual status_t localTimeToCommonTime(int64_t localTime,
+ int64_t* commonTime) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ data.writeInt64(localTime);
+ status_t status = remote()->transact(LOCAL_TIME_TO_COMMON_TIME,
+ data, &reply);
+ if (status == OK) {
+ *commonTime = reply.readInt64();
+ }
+ return status;
+ }
+
+ virtual status_t getCommonTime(int64_t* commonTime) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_COMMON_TIME, data, &reply);
+ if (status == OK) {
+ *commonTime = reply.readInt64();
+ }
+ return status;
+ }
+
+ virtual status_t getCommonFreq(uint64_t* freq) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_COMMON_FREQ, data, &reply);
+ if (status == OK) {
+ *freq = reply.readInt64();
+ }
+ return status;
+ }
+
+ virtual status_t getLocalTime(int64_t* localTime) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_LOCAL_TIME, data, &reply);
+ if (status == OK) {
+ *localTime = reply.readInt64();
+ }
+ return status;
+ }
+
+ virtual status_t getLocalFreq(uint64_t* freq) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ status_t status = remote()->transact(GET_LOCAL_FREQ, data, &reply);
+ if (status == OK) {
+ *freq = reply.readInt64();
+ }
+ return status;
+ }
+
+ virtual status_t registerListener(
+ const sp<ICommonClockListener>& listener) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ data.writeStrongBinder(listener->asBinder());
+ return remote()->transact(REGISTER_LISTENER, data, &reply);
+ }
+
+ virtual status_t unregisterListener(
+ const sp<ICommonClockListener>& listener) {
+ Parcel data, reply;
+ data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+ data.writeStrongBinder(listener->asBinder());
+ return remote()->transact(UNREGISTER_LISTENER, data, &reply);
+ }
+};
+
+IMPLEMENT_META_INTERFACE(CommonClock, "android.aah.CommonClock");
+
+status_t BnCommonClock::onTransact(uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags) {
+ switch(code) {
+ case IS_COMMON_TIME_VALID: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ bool valid;
+ uint32_t timelineID;
+ status_t status = isCommonTimeValid(&valid, &timelineID);
+ if (status == OK) {
+ reply->writeInt32(valid);
+ reply->writeInt32(timelineID);
+ }
+ return status;
+ } break;
+
+ case COMMON_TIME_TO_LOCAL_TIME: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ int64_t commonTime = data.readInt64();
+ int64_t localTime;
+ status_t status = commonTimeToLocalTime(commonTime, &localTime);
+ if (status == OK) {
+ reply->writeInt64(localTime);
+ }
+ return status;
+ } break;
+
+ case LOCAL_TIME_TO_COMMON_TIME: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ int64_t localTime = data.readInt64();
+ int64_t commonTime;
+ status_t status = localTimeToCommonTime(localTime, &commonTime);
+ if (status == OK) {
+ reply->writeInt64(commonTime);
+ }
+ return status;
+ } break;
+
+ case GET_COMMON_TIME: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ int64_t commonTime;
+ status_t status = getCommonTime(&commonTime);
+ if (status == OK) {
+ reply->writeInt64(commonTime);
+ }
+ return status;
+ } break;
+
+ case GET_COMMON_FREQ: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ uint64_t freq;
+ status_t status = getCommonFreq(&freq);
+ if (status == OK) {
+ reply->writeInt64(freq);
+ }
+ return status;
+ } break;
+
+ case GET_LOCAL_TIME: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ int64_t localTime;
+ status_t status = getLocalTime(&localTime);
+ if (status == OK) {
+ reply->writeInt64(localTime);
+ }
+ return status;
+ } break;
+
+ case GET_LOCAL_FREQ: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ uint64_t freq;
+ status_t status = getLocalFreq(&freq);
+ if (status == OK) {
+ reply->writeInt64(freq);
+ }
+ return status;
+ } break;
+
+ case REGISTER_LISTENER: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ sp<ICommonClockListener> listener =
+ interface_cast<ICommonClockListener>(data.readStrongBinder());
+ return registerListener(listener);
+ } break;
+
+ case UNREGISTER_LISTENER: {
+ CHECK_INTERFACE(ICommonClock, data, reply);
+ sp<ICommonClockListener> listener =
+ interface_cast<ICommonClockListener>(data.readStrongBinder());
+ return unregisterListener(listener);
+ } break;
+ }
+ return BBinder::onTransact(code, data, reply, flags);
+}
+
+/***** ICommonClockListener *****/
+
+enum {
+ ON_CLOCK_SYNC = IBinder::FIRST_CALL_TRANSACTION,
+ ON_CLOCK_SYNC_LOSS,
+};
+
+class BpCommonClockListener : public BpInterface<ICommonClockListener>
+{
+ public:
+ BpCommonClockListener(const sp<IBinder>& impl)
+ : BpInterface<ICommonClockListener>(impl) {}
+
+ virtual void onClockSync(uint32_t timelineID) {
+ Parcel data, reply;
+ data.writeInterfaceToken(
+ ICommonClockListener::getInterfaceDescriptor());
+ data.writeInt32(timelineID);
+ remote()->transact(ON_CLOCK_SYNC, data, &reply);
+ }
+
+ virtual void onClockSyncLoss() {
+ Parcel data, reply;
+ data.writeInterfaceToken(
+ ICommonClockListener::getInterfaceDescriptor());
+ remote()->transact(ON_CLOCK_SYNC_LOSS, data, &reply);
+ }
+};
+
+IMPLEMENT_META_INTERFACE(CommonClockListener,
+ "android.aah.CommonClockListener");
+
+status_t BnCommonClockListener::onTransact(
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
+ switch(code) {
+ case ON_CLOCK_SYNC: {
+ CHECK_INTERFACE(ICommonClockListener, data, reply);
+ uint32_t timelineID = data.readInt32();
+ onClockSync(timelineID);
+ return NO_ERROR;
+ } break;
+
+ case ON_CLOCK_SYNC_LOSS: {
+ CHECK_INTERFACE(ICommonClockListener, data, reply);
+ onClockSyncLoss();
+ return NO_ERROR;
+ } break;
+ }
+
+ return BBinder::onTransact(code, data, reply, flags);
+}
+
+}; // namespace android
diff --git a/libs/aah_timesrv/cc_helper.cpp b/libs/aah_timesrv/cc_helper.cpp
new file mode 100644
index 0000000..ed6ba5c
--- /dev/null
+++ b/libs/aah_timesrv/cc_helper.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2011 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>
+
+#include <aah_timesrv/cc_helper.h>
+#include <aah_timesrv/ICommonClock.h>
+#include <utils/threads.h>
+
+namespace android {
+
+Mutex CCHelper::lock_;
+sp<ICommonClock> CCHelper::common_clock_;
+
+bool CCHelper::verifyClock_l() {
+ if (common_clock_ == NULL) {
+ common_clock_ = ICommonClock::getInstance();
+ if (common_clock_ == NULL)
+ return false;
+ }
+
+ return true;
+}
+
+
+// Helper methods which attempts to make calls to the common time binder
+// service. If the first attempt fails with DEAD_OBJECT, the helpers will
+// attempt to make a connection to the service again (assuming that the process
+// hosting the service had crashed and the client proxy we are holding is dead)
+// If the second attempt fails, or no connection can be made, the we let the
+// error propagate up the stack and let the caller deal with the situation as
+// best they can.
+
+#define CCHELPER_METHOD(decl, call) \
+ status_t CCHelper::decl { \
+ Mutex::Autolock lock(&lock_); \
+ \
+ if (!verifyClock_l()) \
+ return DEAD_OBJECT; \
+ \
+ status_t status = common_clock_->call; \
+ if (DEAD_OBJECT == status) { \
+ common_clock_ = NULL; \
+ if (!verifyClock_l()) \
+ return DEAD_OBJECT; \
+ status_t status = common_clock_->call; \
+ } \
+ \
+ return status; \
+ }
+
+#define VERIFY_CLOCK()
+
+CCHELPER_METHOD(isCommonTimeValid(bool* valid, uint32_t* timelineID),
+ isCommonTimeValid(valid, timelineID))
+CCHELPER_METHOD(commonTimeToLocalTime(int64_t commonTime, int64_t* localTime),
+ commonTimeToLocalTime(commonTime, localTime))
+CCHELPER_METHOD(localTimeToCommonTime(int64_t localTime, int64_t* commonTime),
+ localTimeToCommonTime(localTime, commonTime))
+CCHELPER_METHOD(getCommonTime(int64_t* commonTime),
+ getCommonTime(commonTime))
+CCHELPER_METHOD(getCommonFreq(uint64_t* freq),
+ getCommonFreq(freq))
+CCHELPER_METHOD(getLocalTime(int64_t* localTime),
+ getLocalTime(localTime))
+CCHELPER_METHOD(getLocalFreq(uint64_t* freq),
+ getLocalFreq(freq))
+
+} // namespace android
diff --git a/libs/aah_timesrv/local_clock.cpp b/libs/aah_timesrv/local_clock.cpp
new file mode 100644
index 0000000..2d9a481
--- /dev/null
+++ b/libs/aah_timesrv/local_clock.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "aah_timesrv"
+#include <utils/Log.h>
+
+#include <assert.h>
+#include <stdint.h>
+
+#include <aah_timesrv/local_clock.h>
+#include <hardware/hardware.h>
+#include <hardware/local_time_hal.h>
+#include <utils/Errors.h>
+#include <utils/threads.h>
+
+namespace android {
+
+Mutex LocalClock::dev_lock_;
+local_time_hw_device_t* LocalClock::dev_ = NULL;
+
+LocalClock::LocalClock() {
+ int res;
+ const hw_module_t* mod;
+
+ AutoMutex lock(&dev_lock_);
+
+ if (dev_ != NULL)
+ return;
+
+ res = hw_get_module_by_class(LOCAL_TIME_HARDWARE_MODULE_ID, NULL, &mod);
+ if (res) {
+ LOGE("Failed to open local time HAL module (res = %d)", res);
+ } else {
+ res = local_time_hw_device_open(mod, &dev_);
+ if (res) {
+ LOGE("Failed to open local time HAL device (res = %d)", res);
+ dev_ = NULL;
+ }
+ }
+}
+
+bool LocalClock::initCheck() {
+ return (NULL != dev_);
+}
+
+int64_t LocalClock::getLocalTime() {
+ assert(NULL != dev_);
+ assert(NULL != dev_->get_local_time);
+
+ return dev_->get_local_time(dev_);
+}
+
+uint64_t LocalClock::getLocalFreq() {
+ assert(NULL != dev_);
+ assert(NULL != dev_->get_local_freq);
+
+ return dev_->get_local_freq(dev_);
+}
+
+status_t LocalClock::setLocalSlew(int16_t rate) {
+ assert(NULL != dev_);
+
+ if (!dev_->set_local_slew)
+ return INVALID_OPERATION;
+
+ return static_cast<status_t>(dev_->set_local_slew(dev_, rate));
+}
+
+int32_t LocalClock::getDebugLog(struct local_time_debug_event* records,
+ int max_records) {
+ assert(NULL != dev_);
+
+ if (!dev_->get_debug_log)
+ return INVALID_OPERATION;
+
+ return dev_->get_debug_log(dev_, records, max_records);
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/Android.mk b/media/libaah_rtp/Android.mk
new file mode 100644
index 0000000..1dceda995
--- /dev/null
+++ b/media/libaah_rtp/Android.mk
@@ -0,0 +1,40 @@
+LOCAL_PATH:= $(call my-dir)
+#
+# libaah_rtp
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libaah_rtp
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+ aah_decoder_pump.cpp \
+ aah_rx_player.cpp \
+ aah_rx_player_core.cpp \
+ aah_rx_player_ring_buffer.cpp \
+ aah_rx_player_substream.cpp \
+ aah_tx_packet.cpp \
+ aah_tx_player.cpp \
+ aah_tx_sender.cpp \
+ pipe_event.cpp
+
+LOCAL_C_INCLUDES := \
+ frameworks/base/include \
+ frameworks/base/include/media/stagefright/openmax \
+ frameworks/base/media \
+ frameworks/base/media/libstagefright
+
+LOCAL_SHARED_LIBRARIES := \
+ libaah_timesrv_client \
+ libbinder \
+ libmedia \
+ libstagefright \
+ libstagefright_foundation \
+ libutils
+
+LOCAL_LDLIBS := \
+ -lpthread
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/media/libaah_rtp/aah_decoder_pump.cpp b/media/libaah_rtp/aah_decoder_pump.cpp
new file mode 100644
index 0000000..cde9be4
--- /dev/null
+++ b/media/libaah_rtp/aah_decoder_pump.cpp
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <poll.h>
+#include <pthread.h>
+
+#include <aah_timesrv/cc_helper.h>
+#include <media/AudioSystem.h>
+#include <media/AudioTrack.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OMXClient.h>
+#include <media/stagefright/OMXCodec.h>
+#include <media/stagefright/Utils.h>
+#include <utils/threads.h>
+
+#include "aah_decoder_pump.h"
+
+namespace android {
+
+AAH_DecoderPump::AAH_DecoderPump(OMXClient& omx)
+ : omx_(omx)
+ , thread_(this)
+ , thread_status_(OK)
+ , renderer_(NULL)
+ , last_queued_pts_valid_(false)
+ , last_queued_pts_(0)
+ , last_ts_transform_valid_(false)
+ , last_volume_(0xFF) {
+}
+
+AAH_DecoderPump::~AAH_DecoderPump() {
+ shutdown();
+}
+
+status_t AAH_DecoderPump::queueForDecode(MediaBuffer* buf) {
+ if (NULL == buf) {
+ return BAD_VALUE;
+ }
+
+ if (OK != thread_status_) {
+ return thread_status_;
+ }
+
+ { // Explicit scope for AutoMutex pattern.
+ AutoMutex lock(&thread_lock_);
+ in_queue_.push_back(buf);
+ }
+
+ thread_cond_.signal();
+
+ return OK;
+}
+
+void AAH_DecoderPump::queueToRenderer(MediaBuffer* decoded_sample) {
+ Mutex::Autolock lock(&render_lock_);
+ sp<MetaData> meta;
+ int64_t ts;
+ status_t res;
+
+ // Fetch the metadata and make sure the sample has a timestamp. We
+ // cannot render samples which are missing PTSs.
+ meta = decoded_sample->meta_data();
+ if ((meta == NULL) || (!meta->findInt64(kKeyTime, &ts))) {
+ LOGV("Decoded sample missing timestamp, cannot render.");
+ } else {
+ // If we currently are not holding on to a renderer, go ahead and
+ // make one now.
+ if (NULL == renderer_) {
+ renderer_ = new TimedAudioTrack();
+ if (NULL != renderer_) {
+ int frameCount;
+ AudioTrack::getMinFrameCount(&frameCount,
+ AUDIO_STREAM_DEFAULT,
+ static_cast<int>(format_sample_rate_));
+ int ch_format = (format_channels_ == 1)
+ ? AUDIO_CHANNEL_OUT_MONO
+ : AUDIO_CHANNEL_OUT_STEREO;
+
+ res = renderer_->set(AUDIO_STREAM_DEFAULT,
+ format_sample_rate_,
+ AUDIO_FORMAT_PCM_16_BIT,
+ ch_format,
+ frameCount);
+ if (res != OK) {
+ LOGE("Failed to setup audio renderer. (res = %d)", res);
+ delete renderer_;
+ renderer_ = NULL;
+ } else {
+ CHECK(last_ts_transform_valid_);
+
+ res = renderer_->setMediaTimeTransform(
+ last_ts_transform_, TimedAudioTrack::COMMON_TIME);
+ if (res != NO_ERROR) {
+ LOGE("Failed to set media time transform on AudioTrack"
+ " (res = %d)", res);
+ delete renderer_;
+ renderer_ = NULL;
+ } else {
+ float volume = static_cast<float>(last_volume_)
+ / 255.0f;
+ if (renderer_->setVolume(volume, volume) != OK) {
+ LOGW("%s: setVolume failed", __FUNCTION__);
+ }
+
+ renderer_->start();
+ }
+ }
+ } else {
+ LOGE("Failed to allocate AudioTrack to use as a renderer.");
+ }
+ }
+
+ if (NULL != renderer_) {
+ uint8_t* decoded_data =
+ reinterpret_cast<uint8_t*>(decoded_sample->data());
+ uint32_t decoded_amt = decoded_sample->range_length();
+ decoded_data += decoded_sample->range_offset();
+
+ sp<IMemory> pcm_payload;
+ res = renderer_->allocateTimedBuffer(decoded_amt, &pcm_payload);
+ if (res != OK) {
+ LOGE("Failed to allocate %d byte audio track buffer."
+ " (res = %d)", decoded_amt, res);
+ } else {
+ memcpy(pcm_payload->pointer(), decoded_data, decoded_amt);
+
+ res = renderer_->queueTimedBuffer(pcm_payload, ts);
+ if (res != OK) {
+ LOGE("Failed to queue %d byte audio track buffer with media"
+ " PTS %lld. (res = %d)", decoded_amt, ts, res);
+ } else {
+ last_queued_pts_valid_ = true;
+ last_queued_pts_ = ts;
+ }
+ }
+
+ } else {
+ LOGE("No renderer, dropping audio payload.");
+ }
+ }
+}
+
+void AAH_DecoderPump::stopAndCleanupRenderer() {
+ if (NULL == renderer_) {
+ return;
+ }
+
+ renderer_->stop();
+ delete renderer_;
+ renderer_ = NULL;
+}
+
+void AAH_DecoderPump::setRenderTSTransform(const LinearTransform& trans) {
+ Mutex::Autolock lock(&render_lock_);
+
+ if (last_ts_transform_valid_ && !memcmp(&trans,
+ &last_ts_transform_,
+ sizeof(trans))) {
+ return;
+ }
+
+ last_ts_transform_ = trans;
+ last_ts_transform_valid_ = true;
+
+ if (NULL != renderer_) {
+ status_t res = renderer_->setMediaTimeTransform(
+ last_ts_transform_, TimedAudioTrack::COMMON_TIME);
+ if (res != NO_ERROR) {
+ LOGE("Failed to set media time transform on AudioTrack"
+ " (res = %d)", res);
+ }
+ }
+}
+
+void AAH_DecoderPump::setRenderVolume(uint8_t volume) {
+ Mutex::Autolock lock(&render_lock_);
+
+ if (volume == last_volume_) {
+ return;
+ }
+
+ last_volume_ = volume;
+ if (renderer_ != NULL) {
+ float volume = static_cast<float>(last_volume_) / 255.0f;
+ if (renderer_->setVolume(volume, volume) != OK) {
+ LOGW("%s: setVolume failed", __FUNCTION__);
+ }
+ }
+}
+
+// isAboutToUnderflow is something of a hack used to figure out when it might be
+// time to give up on trying to fill in a gap in the RTP sequence and simply
+// move on with a discontinuity. If we had perfect knowledge of when we were
+// going to underflow, it would not be a hack, but unfortunately we do not.
+// Right now, we just take the PTS of the last sample queued, and check to see
+// if its presentation time is within kAboutToUnderflowThreshold from now. If
+// it is, then we say that we are about to underflow. This decision is based on
+// two (possibly invalid) assumptions.
+//
+// 1) The transmitter is leading the clock by more than
+// kAboutToUnderflowThreshold.
+// 2) The delta between the PTS of the last sample queued and the next sample
+// is less than the transmitter's clock lead amount.
+//
+// Right now, the default transmitter lead time is 1 second, which is a pretty
+// large number and greater than the 50mSec that kAboutToUnderflowThreshold is
+// currently set to. This should satisfy assumption #1 for now, but changes to
+// the transmitter clock lead time could effect this.
+//
+// For non-sparse streams with a homogeneous sample rate (the vast majority of
+// streams in the world), the delta between any two adjacent PTSs will always be
+// the homogeneous sample period. It is very uncommon to see a sample period
+// greater than the 1 second clock lead we are currently using, and you
+// certainly will not see it in an MP3 file which should satisfy assumption #2.
+// Sparse audio streams (where no audio is transmitted for long periods of
+// silence) and extremely low framerate video stream (like an MPEG-2 slideshow
+// or the video stream for a pay TV audio channel) are examples of streams which
+// might violate assumption #2.
+bool AAH_DecoderPump::isAboutToUnderflow(int64_t threshold) {
+ Mutex::Autolock lock(&render_lock_);
+
+ // If we have never queued anything to the decoder, we really don't know if
+ // we are going to underflow or not.
+ if (!last_queued_pts_valid_ || !last_ts_transform_valid_) {
+ return false;
+ }
+
+ // Don't have access to Common Time? If so, then things are Very Bad
+ // elsewhere in the system; it pretty much does not matter what we do here.
+ // Since we cannot really tell if we are about to underflow or not, its
+ // probably best to assume that we are not and proceed accordingly.
+ int64_t tt_now;
+ if (OK != CCHelper::getCommonTime(&tt_now)) {
+ return false;
+ }
+
+ // Transform from media time to common time.
+ int64_t last_queued_pts_tt;
+ if (!last_ts_transform_.doForwardTransform(last_queued_pts_,
+ &last_queued_pts_tt)) {
+ return false;
+ }
+
+ // Check to see if we are underflowing.
+ return ((tt_now + threshold - last_queued_pts_tt) > 0);
+}
+
+void* AAH_DecoderPump::workThread() {
+ // No need to lock when accessing decoder_ from the thread. The
+ // implementation of init and shutdown ensure that other threads never touch
+ // decoder_ while the work thread is running.
+ CHECK(decoder_ != NULL);
+ CHECK(format_ != NULL);
+
+ // Start the decoder and note its result code. If something goes horribly
+ // wrong, callers of queueForDecode and getOutput will be able to detect
+ // that the thread encountered a fatal error and shut down by examining
+ // thread_status_.
+ thread_status_ = decoder_->start(format_.get());
+ if (OK != thread_status_) {
+ LOGE("AAH_DecoderPump's work thread failed to start decoder (res = %d)",
+ thread_status_);
+ return NULL;
+ }
+
+ while (!thread_.exitPending()) {
+ status_t res;
+ MediaBuffer* bufOut = NULL;
+
+ res = decoder_->read(&bufOut);
+
+ if (res == INFO_FORMAT_CHANGED) {
+ // Format has changed. Destroy our current renderer so that a new
+ // one can be created during queueToRenderer with the proper format.
+ //
+ // TODO : In order to transition seamlessly, we should change this
+ // to put the old renderer in a queue to play out completely before
+ // we destroy it. We can still create a new renderer, the timed
+ // nature of the renderer should ensure a seamless splice.
+ stopAndCleanupRenderer();
+ res = OK;
+ }
+
+ // Any error aside from INFO_FORMAT_CHANGED is considered to be fatal
+ // and will result in shutdown of the decoder pump thread.
+ if (res != OK) {
+ LOGE("%s: Failed to decode data (res = %d)",
+ __PRETTY_FUNCTION__, res);
+ CHECK(NULL == bufOut);
+ thread_status_ = res;
+ break;
+ }
+
+ if (NULL == bufOut) {
+ LOGW("%s: Successful decode, but no buffer produced",
+ __PRETTY_FUNCTION__);
+ continue;
+ }
+
+ queueToRenderer(bufOut);
+ bufOut->release();
+ }
+
+ decoder_->stop();
+ stopAndCleanupRenderer();
+
+ return NULL;
+}
+
+status_t AAH_DecoderPump::init(sp<MetaData> params) {
+ Mutex::Autolock lock(&init_lock_);
+
+ if (decoder_ != NULL) {
+ // already inited
+ return OK;
+ }
+
+ if (params == NULL) {
+ return BAD_VALUE;
+ }
+
+ if (!params->findInt32(kKeyChannelCount, &format_channels_)) {
+ return BAD_VALUE;
+ }
+
+ if (!params->findInt32(kKeySampleRate, &format_sample_rate_)) {
+ return BAD_VALUE;
+ }
+
+ CHECK(OK == thread_status_);
+ CHECK(decoder_ == NULL);
+
+ status_t ret_val = UNKNOWN_ERROR;
+
+ // Cache the format and attempt to create the decoder.
+ format_ = params;
+ decoder_ = OMXCodec::Create(
+ omx_.interface(), // IOMX Handle
+ format_, // Metadata for substream (indicates codec)
+ false, // Make a decoder, not an encoder
+ sp<MediaSource>(this)); // We will be the source for this codec.
+
+ if (decoder_ == NULL) {
+ LOGE("Failed to allocate decoder in %s", __PRETTY_FUNCTION__);
+ goto bailout;
+ }
+
+ // Fire up the pump thread. It will take care of starting and stopping the
+ // decoder.
+ ret_val = thread_.run("aah_decode_pump", ANDROID_PRIORITY_AUDIO);
+ if (OK != ret_val) {
+ LOGE("Failed to start work thread in %s (res = %d)",
+ __PRETTY_FUNCTION__, ret_val);
+ goto bailout;
+ }
+
+bailout:
+ if (OK != ret_val) {
+ decoder_ = NULL;
+ format_ = NULL;
+ }
+
+ return OK;
+}
+
+status_t AAH_DecoderPump::shutdown() {
+ Mutex::Autolock lock(&init_lock_);
+ return shutdown_l();
+}
+
+status_t AAH_DecoderPump::shutdown_l() {
+ thread_.requestExit();
+ thread_cond_.signal();
+ thread_.requestExitAndWait();
+
+ MBQueue::iterator I;
+ for (I = in_queue_.begin(); I != in_queue_.end(); ++I) {
+ (*I)->release();
+ }
+ in_queue_.clear();
+
+ last_queued_pts_valid_ = false;
+ last_ts_transform_valid_ = false;
+ last_volume_ = 0xFF;
+
+ decoder_ = NULL;
+ format_ = NULL;
+
+ return OK;
+}
+
+status_t AAH_DecoderPump::read(MediaBuffer **buffer,
+ const ReadOptions *options) {
+ if (!buffer) {
+ return BAD_VALUE;
+ }
+
+ *buffer = NULL;
+
+ // While its not time to shut down, and we have no data to process, wait.
+ AutoMutex lock(&thread_lock_);
+ while (!thread_.exitPending() && in_queue_.empty())
+ thread_cond_.wait(thread_lock_);
+
+ // At this point, if its not time to shutdown then we must have something to
+ // process. Go ahead and pop the front of the queue for processing.
+ if (!thread_.exitPending()) {
+ CHECK(!in_queue_.empty());
+
+ *buffer = *(in_queue_.begin());
+ in_queue_.erase(in_queue_.begin());
+ }
+
+ // If we managed to get a buffer, then everything must be OK. If not, then
+ // we must be shutting down.
+ return (NULL == *buffer) ? INVALID_OPERATION : OK;
+}
+
+AAH_DecoderPump::ThreadWrapper::ThreadWrapper(AAH_DecoderPump* owner)
+ : Thread(false /* canCallJava*/ )
+ , owner_(owner) {
+}
+
+bool AAH_DecoderPump::ThreadWrapper::threadLoop() {
+ CHECK(NULL != owner_);
+ owner_->workThread();
+ return false;
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_decoder_pump.h b/media/libaah_rtp/aah_decoder_pump.h
new file mode 100644
index 0000000..f5e7d03
--- /dev/null
+++ b/media/libaah_rtp/aah_decoder_pump.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef __DECODER_PUMP_H__
+#define __DECODER_PUMP_H__
+
+#include <pthread.h>
+
+#include <media/stagefright/MediaSource.h>
+#include <utils/LinearTransform.h>
+#include <utils/List.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class MetaData;
+class OMXClient;
+class TimedAudioTrack;
+
+class AAH_DecoderPump : public MediaSource {
+ public:
+ explicit AAH_DecoderPump(OMXClient& omx);
+
+ status_t queueForDecode(MediaBuffer* buf);
+
+ status_t init(sp<MetaData> params);
+ status_t shutdown();
+
+ void setRenderTSTransform(const LinearTransform& trans);
+ void setRenderVolume(uint8_t volume);
+ bool isAboutToUnderflow(int64_t threshold);
+
+ // MediaSource methods
+ virtual status_t start(MetaData *params) { return OK; }
+ virtual sp<MetaData> getFormat() { return format_; }
+ virtual status_t stop() { return OK; }
+ virtual status_t read(MediaBuffer **buffer,
+ const ReadOptions *options);
+
+ protected:
+ virtual ~AAH_DecoderPump();
+
+ private:
+ class ThreadWrapper : public Thread {
+ public:
+ friend class AAH_DecoderPump;
+ explicit ThreadWrapper(AAH_DecoderPump* owner);
+
+ private:
+ virtual bool threadLoop();
+ AAH_DecoderPump* owner_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ThreadWrapper);
+ };
+
+ void* workThread();
+ virtual status_t shutdown_l();
+ void queueToRenderer(MediaBuffer* decoded_sample);
+ void stopAndCleanupRenderer();
+
+ sp<MetaData> format_;
+ int32_t format_channels_;
+ int32_t format_sample_rate_;
+
+ sp<MediaSource> decoder_;
+ OMXClient& omx_;
+ Mutex init_lock_;
+
+ ThreadWrapper thread_;
+ Condition thread_cond_;
+ Mutex thread_lock_;
+ status_t thread_status_;
+
+ Mutex render_lock_;
+ TimedAudioTrack* renderer_;
+ bool last_queued_pts_valid_;
+ int64_t last_queued_pts_;
+ bool last_ts_transform_valid_;
+ LinearTransform last_ts_transform_;
+ uint8_t last_volume_;
+
+ // protected by the thread_lock_
+ typedef List<MediaBuffer*> MBQueue;
+ MBQueue in_queue_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(AAH_DecoderPump);
+};
+
+} // namespace android
+#endif // __DECODER_PUMP_H__
diff --git a/media/libaah_rtp/aah_rx_player.cpp b/media/libaah_rtp/aah_rx_player.cpp
new file mode 100644
index 0000000..1fe836c
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player.cpp
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+
+#include <aah_timesrv/cc_helper.h>
+#include <binder/IServiceManager.h>
+#include <media/MediaPlayerInterface.h>
+#include <utils/Log.h>
+
+#include "aah_rx_player.h"
+
+namespace android {
+
+const uint32_t AAH_RXPlayer::kRTPRingBufferSize = 1 << 10;
+
+sp<MediaPlayerBase> createAAH_RXPlayer() {
+ sp<MediaPlayerBase> ret = new AAH_RXPlayer();
+ return ret;
+}
+
+AAH_RXPlayer::AAH_RXPlayer()
+ : ring_buffer_(kRTPRingBufferSize)
+ , substreams_(NULL) {
+ thread_wrapper_ = new ThreadWrapper(*this);
+
+ is_playing_ = false;
+ multicast_joined_ = false;
+ transmitter_known_ = false;
+ current_epoch_known_ = false;
+ data_source_set_ = false;
+ sock_fd_ = -1;
+
+ substreams_.setCapacity(4);
+
+ memset(&listen_addr_, 0, sizeof(listen_addr_));
+ memset(&transmitter_addr_, 0, sizeof(transmitter_addr_));
+
+ fetchAudioFlinger();
+}
+
+AAH_RXPlayer::~AAH_RXPlayer() {
+ reset_l();
+ CHECK(substreams_.size() == 0);
+ omx_.disconnect();
+}
+
+status_t AAH_RXPlayer::initCheck() {
+ if (thread_wrapper_ == NULL) {
+ LOGE("Failed to allocate thread wrapper!");
+ return NO_MEMORY;
+ }
+
+ if (!ring_buffer_.initCheck()) {
+ LOGE("Failed to allocate reassembly ring buffer!");
+ return NO_MEMORY;
+ }
+
+ // Check for the presense of the A@H common time service by attempting to
+ // query for CommonTime's frequency. If we get an error back, we cannot
+ // talk to the service at all and should abort now.
+ status_t res;
+ uint64_t freq;
+ res = CCHelper::getCommonFreq(&freq);
+ if (OK != res) {
+ LOGE("Failed to connect to common time service!");
+ return res;
+ }
+
+ return omx_.connect();
+}
+
+status_t AAH_RXPlayer::setDataSource(
+ const char *url,
+ const KeyedVector<String8, String8> *headers) {
+ AutoMutex api_lock(&api_lock_);
+ uint32_t a, b, c, d;
+ uint16_t port;
+
+ if (data_source_set_) {
+ return INVALID_OPERATION;
+ }
+
+ if (NULL == url) {
+ return BAD_VALUE;
+ }
+
+ if (5 != sscanf(url, "%*[^:/]://%u.%u.%u.%u:%hu", &a, &b, &c, &d, &port)) {
+ LOGE("Failed to parse URL \"%s\"", url);
+ return BAD_VALUE;
+ }
+
+ if ((a > 255) || (b > 255) || (c > 255) || (d > 255) || (port == 0)) {
+ LOGE("Bad multicast address \"%s\"", url);
+ return BAD_VALUE;
+ }
+
+ LOGI("setDataSource :: %u.%u.%u.%u:%hu", a, b, c, d, port);
+
+ a = (a << 24) | (b << 16) | (c << 8) | d;
+
+ memset(&listen_addr_, 0, sizeof(listen_addr_));
+ listen_addr_.sin_family = AF_INET;
+ listen_addr_.sin_port = htons(port);
+ listen_addr_.sin_addr.s_addr = htonl(a);
+ data_source_set_ = true;
+
+ return OK;
+}
+
+status_t AAH_RXPlayer::setDataSource(int fd, int64_t offset, int64_t length) {
+ return INVALID_OPERATION;
+}
+
+status_t AAH_RXPlayer::setVideoSurface(const sp<Surface>& surface) {
+ return OK;
+}
+
+status_t AAH_RXPlayer::setVideoSurfaceTexture(
+ const sp<ISurfaceTexture>& surfaceTexture) {
+ return OK;
+}
+
+status_t AAH_RXPlayer::prepare() {
+ return OK;
+}
+
+status_t AAH_RXPlayer::prepareAsync() {
+ sendEvent(MEDIA_PREPARED);
+ return OK;
+}
+
+status_t AAH_RXPlayer::start() {
+ AutoMutex api_lock(&api_lock_);
+
+ if (is_playing_) {
+ return OK;
+ }
+
+ status_t res = startWorkThread();
+ is_playing_ = (res == OK);
+ return res;
+}
+
+status_t AAH_RXPlayer::stop() {
+ return pause();
+}
+
+status_t AAH_RXPlayer::pause() {
+ AutoMutex api_lock(&api_lock_);
+ stopWorkThread();
+ CHECK(sock_fd_ < 0);
+ is_playing_ = false;
+ return OK;
+}
+
+bool AAH_RXPlayer::isPlaying() {
+ AutoMutex api_lock(&api_lock_);
+ return is_playing_;
+}
+
+status_t AAH_RXPlayer::seekTo(int msec) {
+ sendEvent(MEDIA_SEEK_COMPLETE);
+ return OK;
+}
+
+status_t AAH_RXPlayer::getCurrentPosition(int *msec) {
+ if (NULL != msec) {
+ *msec = 0;
+ }
+ return OK;
+}
+
+status_t AAH_RXPlayer::getDuration(int *msec) {
+ if (NULL != msec) {
+ *msec = 1;
+ }
+ return OK;
+}
+
+status_t AAH_RXPlayer::reset() {
+ AutoMutex api_lock(&api_lock_);
+ reset_l();
+ return OK;
+}
+
+void AAH_RXPlayer::reset_l() {
+ stopWorkThread();
+ CHECK(sock_fd_ < 0);
+ CHECK(!multicast_joined_);
+ is_playing_ = false;
+ data_source_set_ = false;
+ transmitter_known_ = false;
+ memset(&listen_addr_, 0, sizeof(listen_addr_));
+}
+
+status_t AAH_RXPlayer::setLooping(int loop) {
+ return OK;
+}
+
+player_type AAH_RXPlayer::playerType() {
+ return AAH_RX_PLAYER;
+}
+
+status_t AAH_RXPlayer::setParameter(int key, const Parcel &request) {
+ return ERROR_UNSUPPORTED;
+}
+
+status_t AAH_RXPlayer::getParameter(int key, Parcel *reply) {
+ return ERROR_UNSUPPORTED;
+}
+
+status_t AAH_RXPlayer::invoke(const Parcel& request, Parcel *reply) {
+ if (!reply) {
+ return BAD_VALUE;
+ }
+
+ int32_t magic;
+ status_t err = request.readInt32(&magic);
+ if (err != OK) {
+ reply->writeInt32(err);
+ return OK;
+ }
+
+ if (magic != 0x12345) {
+ reply->writeInt32(BAD_VALUE);
+ return OK;
+ }
+
+ int32_t methodID;
+ err = request.readInt32(&methodID);
+ if (err != OK) {
+ reply->writeInt32(err);
+ return OK;
+ }
+
+ switch (methodID) {
+ // Get Volume
+ case INVOKE_GET_MASTER_VOLUME: {
+ if (audio_flinger_ != NULL) {
+ reply->writeInt32(OK);
+ reply->writeFloat(audio_flinger_->masterVolume());
+ } else {
+ reply->writeInt32(UNKNOWN_ERROR);
+ }
+ } break;
+
+ // Set Volume
+ case INVOKE_SET_MASTER_VOLUME: {
+ float targetVol = request.readFloat();
+ reply->writeInt32(audio_flinger_->setMasterVolume(targetVol));
+ } break;
+
+ default: return BAD_VALUE;
+ }
+
+ return OK;
+}
+
+void AAH_RXPlayer::fetchAudioFlinger() {
+ if (audio_flinger_ == NULL) {
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IBinder> binder;
+ binder = sm->getService(String16("media.audio_flinger"));
+
+ if (binder == NULL) {
+ LOGW("AAH_RXPlayer failed to fetch handle to audio flinger."
+ " Master volume control will not be possible.");
+ }
+
+ audio_flinger_ = interface_cast<IAudioFlinger>(binder);
+ }
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_rx_player.h b/media/libaah_rtp/aah_rx_player.h
new file mode 100644
index 0000000..44aedc3
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player.h
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef __AAH_RX_PLAYER_H__
+#define __AAH_RX_PLAYER_H__
+
+#include <media/MediaPlayerInterface.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OMXClient.h>
+#include <netinet/in.h>
+#include <utils/KeyedVector.h>
+#include <utils/LinearTransform.h>
+#include <utils/threads.h>
+
+#include "aah_decoder_pump.h"
+#include "pipe_event.h"
+
+namespace android {
+
+class AAH_RXPlayer : public MediaPlayerInterface {
+ public:
+ AAH_RXPlayer();
+
+ virtual status_t initCheck();
+ virtual status_t setDataSource(const char *url,
+ const KeyedVector<String8, String8>*
+ headers);
+ virtual status_t setDataSource(int fd, int64_t offset, int64_t length);
+ virtual status_t setVideoSurface(const sp<Surface>& surface);
+ virtual status_t setVideoSurfaceTexture(const sp<ISurfaceTexture>&
+ surfaceTexture);
+ virtual status_t prepare();
+ virtual status_t prepareAsync();
+ virtual status_t start();
+ virtual status_t stop();
+ virtual status_t pause();
+ virtual bool isPlaying();
+ virtual status_t seekTo(int msec);
+ virtual status_t getCurrentPosition(int *msec);
+ virtual status_t getDuration(int *msec);
+ virtual status_t reset();
+ virtual status_t setLooping(int loop);
+ virtual player_type playerType();
+ virtual status_t setParameter(int key, const Parcel &request);
+ virtual status_t getParameter(int key, Parcel *reply);
+ virtual status_t invoke(const Parcel& request, Parcel *reply);
+
+ protected:
+ virtual ~AAH_RXPlayer();
+
+ private:
+ class ThreadWrapper : public Thread {
+ public:
+ friend class AAH_RXPlayer;
+ explicit ThreadWrapper(AAH_RXPlayer& player)
+ : Thread(false /* canCallJava */ )
+ , player_(player) { }
+
+ virtual bool threadLoop() { return player_.threadLoop(); }
+
+ private:
+ AAH_RXPlayer& player_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ThreadWrapper);
+ };
+
+#pragma pack(push, 1)
+ struct PacketBuffer {
+ ssize_t length_;
+ uint8_t data_[0];
+
+ // TODO : consider changing this to be some form of ring buffer or free
+ // pool system instead of just using the heap in order to avoid heap
+ // fragmentation.
+ static PacketBuffer* allocate(ssize_t length);
+ static void destroy(PacketBuffer* pb);
+
+ private:
+ // Force people to use allocate/destroy instead of new/delete.
+ PacketBuffer() { }
+ ~PacketBuffer() { }
+ };
+
+ struct RetransRequest {
+ uint32_t magic_;
+ uint32_t mcast_ip_;
+ uint16_t mcast_port_;
+ uint16_t start_seq_;
+ uint16_t end_seq_;
+ };
+#pragma pack(pop)
+
+ enum GapStatus {
+ kGS_NoGap = 0,
+ kGS_NormalGap,
+ kGS_FastStartGap,
+ };
+
+ struct SeqNoGap {
+ uint16_t start_seq_;
+ uint16_t end_seq_;
+ };
+
+ class RXRingBuffer {
+ public:
+ explicit RXRingBuffer(uint32_t capacity);
+ ~RXRingBuffer();
+
+ bool initCheck() const { return (ring_ != NULL); }
+ void reset();
+
+ // Push a packet buffer with a given sequence number into the ring
+ // buffer. pushBuffer will always consume the buffer pushed to it,
+ // either destroying it because it was a duplicate or overflow, or
+ // holding on to it in the ring. Callers should not hold any references
+ // to PacketBuffers after they have been pushed to the ring. Returns
+ // false in the case of a serious error (such as ring overflow).
+ // Callers should consider resetting the pipeline entirely in the event
+ // of a serious error.
+ bool pushBuffer(PacketBuffer* buf, uint16_t seq);
+
+ // Fetch the next buffer in the RTP sequence. Returns NULL if there is
+ // no buffer to fetch. If a non-NULL PacketBuffer is returned,
+ // is_discon will be set to indicate whether or not this PacketBuffer is
+ // discontiuous with any previously returned packet buffers. Packet
+ // buffers returned by fetchBuffer are the caller's responsibility; they
+ // must be certain to destroy the buffers when they are done.
+ PacketBuffer* fetchBuffer(bool* is_discon);
+
+ // Returns true and fills out the gap structure if the read pointer of
+ // the ring buffer is currently pointing to a gap which would stall a
+ // fetchBuffer operation. Returns false if the read pointer is not
+ // pointing to a gap in the sequence currently.
+ GapStatus fetchCurrentGap(SeqNoGap* gap);
+
+ // Causes the read pointer to skip over any portion of a gap indicated
+ // by nak. If nak is NULL, any gap currently blocking the read pointer
+ // will be completely skipped. If any portion of a gap is skipped, the
+ // next successful read from fetch buffer will indicate a discontinuity.
+ void processNAK(SeqNoGap* nak = NULL);
+
+ // Compute the number of milliseconds until the inactivity timer for
+ // this RTP stream. Returns -1 if there is no active timeout, or 0 if
+ // the system has already timed out.
+ int computeInactivityTimeout();
+
+ private:
+ Mutex lock_;
+ PacketBuffer** ring_;
+ uint32_t capacity_;
+ uint32_t rd_;
+ uint32_t wr_;
+
+ uint16_t rd_seq_;
+ bool rd_seq_known_;
+ bool waiting_for_fast_start_;
+ bool fetched_first_packet_;
+
+ uint64_t rtp_activity_timeout_;
+ bool rtp_activity_timeout_valid_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(RXRingBuffer);
+ };
+
+ class Substream : public virtual RefBase {
+ public:
+ Substream(uint32_t ssrc, OMXClient& omx);
+
+ void cleanupBufferInProgress();
+ void shutdown();
+ void processPayloadStart(uint8_t* buf,
+ uint32_t amt,
+ int32_t ts_lower);
+ void processPayloadCont (uint8_t* buf,
+ uint32_t amt);
+ void processCompletedBuffer();
+ void processTSTransform(const LinearTransform& trans);
+
+ bool isAboutToUnderflow();
+ uint32_t getSSRC() const { return ssrc_; }
+ uint16_t getProgramID() const { return (ssrc_ >> 5) & 0x1F; }
+
+ protected:
+ virtual ~Substream() {
+ shutdown();
+ }
+
+ private:
+ void cleanupDecoder();
+ bool setupSubstreamType(uint8_t substream_type,
+ uint8_t codec_type);
+
+ uint32_t ssrc_;
+ bool waiting_for_rap_;
+
+ bool substream_details_known_;
+ uint8_t substream_type_;
+ uint8_t codec_type_;
+ sp<MetaData> substream_meta_;
+
+ MediaBuffer* buffer_in_progress_;
+ uint32_t expected_buffer_size_;
+ uint32_t buffer_filled_;
+
+ sp<AAH_DecoderPump> decoder_;
+
+ static int64_t kAboutToUnderflowThreshold;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Substream);
+ };
+
+ typedef DefaultKeyedVector< uint32_t, sp<Substream> > SubstreamVec;
+
+ status_t startWorkThread();
+ void stopWorkThread();
+ virtual bool threadLoop();
+ bool setupSocket();
+ void cleanupSocket();
+ void resetPipeline();
+ void reset_l();
+ bool processRX(PacketBuffer* pb);
+ void processRingBuffer();
+ void processCommandPacket(PacketBuffer* pb);
+ bool processGaps();
+ int computeNextGapRetransmitTimeout();
+ void fetchAudioFlinger();
+
+ PipeEvent wakeup_work_thread_evt_;
+ sp<ThreadWrapper> thread_wrapper_;
+ Mutex api_lock_;
+ bool is_playing_;
+ bool data_source_set_;
+
+ struct sockaddr_in listen_addr_;
+ int sock_fd_;
+ bool multicast_joined_;
+
+ struct sockaddr_in transmitter_addr_;
+ bool transmitter_known_;
+
+ uint32_t current_epoch_;
+ bool current_epoch_known_;
+
+ SeqNoGap current_gap_;
+ GapStatus current_gap_status_;
+ uint64_t next_retrans_req_time_;
+
+ RXRingBuffer ring_buffer_;
+ SubstreamVec substreams_;
+ OMXClient omx_;
+
+ // Connection to audio flinger used to hack a path to setMasterVolume.
+ sp<IAudioFlinger> audio_flinger_;
+
+ static const uint32_t kRTPRingBufferSize;
+ static const uint32_t kRetransRequestMagic;
+ static const uint32_t kFastStartRequestMagic;
+ static const uint32_t kRetransNAKMagic;
+ static const uint32_t kGapRerequestTimeoutUSec;
+ static const uint32_t kFastStartTimeoutUSec;
+ static const uint32_t kRTPActivityTimeoutUSec;
+
+ static const uint32_t INVOKE_GET_MASTER_VOLUME = 3;
+ static const uint32_t INVOKE_SET_MASTER_VOLUME = 4;
+
+ static uint64_t monotonicUSecNow();
+
+ DISALLOW_EVIL_CONSTRUCTORS(AAH_RXPlayer);
+};
+
+} // namespace android
+
+#endif // __AAH_RX_PLAYER_H__
diff --git a/media/libaah_rtp/aah_rx_player_core.cpp b/media/libaah_rtp/aah_rx_player_core.cpp
new file mode 100644
index 0000000..a86ea17
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player_core.cpp
@@ -0,0 +1,791 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <fcntl.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <utils/misc.h>
+
+#include <media/stagefright/Utils.h>
+
+#include "aah_rx_player.h"
+#include "aah_tx_packet.h"
+
+namespace android {
+
+const uint32_t AAH_RXPlayer::kRetransRequestMagic =
+ FOURCC('T','r','e','q');
+const uint32_t AAH_RXPlayer::kRetransNAKMagic =
+ FOURCC('T','n','a','k');
+const uint32_t AAH_RXPlayer::kFastStartRequestMagic =
+ FOURCC('T','f','s','t');
+const uint32_t AAH_RXPlayer::kGapRerequestTimeoutUSec = 75000;
+const uint32_t AAH_RXPlayer::kFastStartTimeoutUSec = 800000;
+const uint32_t AAH_RXPlayer::kRTPActivityTimeoutUSec = 10000000;
+
+static inline int16_t fetchInt16(uint8_t* data) {
+ return static_cast<int16_t>(U16_AT(data));
+}
+
+static inline int32_t fetchInt32(uint8_t* data) {
+ return static_cast<int32_t>(U32_AT(data));
+}
+
+static inline int64_t fetchInt64(uint8_t* data) {
+ return static_cast<int64_t>(U64_AT(data));
+}
+
+uint64_t AAH_RXPlayer::monotonicUSecNow() {
+ struct timespec now;
+ int res = clock_gettime(CLOCK_MONOTONIC, &now);
+ CHECK(res >= 0);
+
+ uint64_t ret = static_cast<uint64_t>(now.tv_sec) * 1000000;
+ ret += now.tv_nsec / 1000;
+
+ return ret;
+}
+
+status_t AAH_RXPlayer::startWorkThread() {
+ status_t res;
+ stopWorkThread();
+ res = thread_wrapper_->run("TRX_Player", PRIORITY_AUDIO);
+
+ if (res != OK) {
+ LOGE("Failed to start work thread (res = %d)", res);
+ }
+
+ return res;
+}
+
+void AAH_RXPlayer::stopWorkThread() {
+ thread_wrapper_->requestExit(); // set the exit pending flag
+ wakeup_work_thread_evt_.setEvent();
+
+ status_t res;
+ res = thread_wrapper_->requestExitAndWait(); // block until thread exit.
+ if (res != OK) {
+ LOGE("Failed to stop work thread (res = %d)", res);
+ }
+
+ wakeup_work_thread_evt_.clearPendingEvents();
+}
+
+void AAH_RXPlayer::cleanupSocket() {
+ if (sock_fd_ >= 0) {
+ if (multicast_joined_) {
+ int res;
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr = listen_addr_.sin_addr;
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ res = setsockopt(sock_fd_,
+ IPPROTO_IP,
+ IP_DROP_MEMBERSHIP,
+ &mreq, sizeof(mreq));
+ if (res < 0) {
+ LOGW("Failed to leave multicast group. (%d, %d)", res, errno);
+ }
+ multicast_joined_ = false;
+ }
+
+ close(sock_fd_);
+ sock_fd_ = -1;
+ }
+
+ resetPipeline();
+}
+
+void AAH_RXPlayer::resetPipeline() {
+ ring_buffer_.reset();
+
+ // Explicitly shudown all of the active substreams, then call clear out the
+ // collection. Failure to clear out a substream can result in its decoder
+ // holding a reference to itself and therefor not going away when the
+ // collection is cleared.
+ for (size_t i = 0; i < substreams_.size(); ++i)
+ substreams_.valueAt(i)->shutdown();
+
+ substreams_.clear();
+
+ current_gap_status_ = kGS_NoGap;
+}
+
+bool AAH_RXPlayer::setupSocket() {
+ long flags;
+ int res, buf_size;
+ socklen_t opt_size;
+
+ cleanupSocket();
+ CHECK(sock_fd_ < 0);
+
+ // Make the socket
+ sock_fd_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock_fd_ < 0) {
+ LOGE("Failed to create listen socket (errno %d)", errno);
+ goto bailout;
+ }
+
+ // Set non-blocking operation
+ flags = fcntl(sock_fd_, F_GETFL);
+ res = fcntl(sock_fd_, F_SETFL, flags | O_NONBLOCK);
+ if (res < 0) {
+ LOGE("Failed to set socket (%d) to non-blocking mode (errno %d)",
+ sock_fd_, errno);
+ goto bailout;
+ }
+
+ // Bind to our port
+ struct sockaddr_in bind_addr;
+ memset(&bind_addr, 0, sizeof(bind_addr));
+ bind_addr.sin_family = AF_INET;
+ bind_addr.sin_addr.s_addr = INADDR_ANY;
+ bind_addr.sin_port = listen_addr_.sin_port;
+ res = bind(sock_fd_,
+ reinterpret_cast<const sockaddr*>(&bind_addr),
+ sizeof(bind_addr));
+ if (res < 0) {
+ uint32_t a = ntohl(bind_addr.sin_addr.s_addr);
+ uint16_t p = ntohs(bind_addr.sin_port);
+ LOGE("Failed to bind socket (%d) to %d.%d.%d.%d:%hd. (errno %d)",
+ sock_fd_,
+ (a >> 24) & 0xFF,
+ (a >> 16) & 0xFF,
+ (a >> 8) & 0xFF,
+ (a ) & 0xFF,
+ p,
+ errno);
+
+ goto bailout;
+ }
+
+ buf_size = 1 << 16; // 64k
+ res = setsockopt(sock_fd_,
+ SOL_SOCKET, SO_RCVBUF,
+ &buf_size, sizeof(buf_size));
+ if (res < 0) {
+ LOGW("Failed to increase socket buffer size to %d. (errno %d)",
+ buf_size, errno);
+ }
+
+ buf_size = 0;
+ opt_size = sizeof(buf_size);
+ res = getsockopt(sock_fd_,
+ SOL_SOCKET, SO_RCVBUF,
+ &buf_size, &opt_size);
+ if (res < 0) {
+ LOGW("Failed to increase socket buffer size to %d. (errno %d)",
+ buf_size, errno);
+ } else {
+ LOGI("RX socket buffer size is now %d bytes", buf_size);
+ }
+
+ if (listen_addr_.sin_addr.s_addr) {
+ // Join the multicast group and we should be good to go.
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr = listen_addr_.sin_addr;
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ res = setsockopt(sock_fd_,
+ IPPROTO_IP,
+ IP_ADD_MEMBERSHIP,
+ &mreq, sizeof(mreq));
+ if (res < 0) {
+ LOGE("Failed to join multicast group. (errno %d)", errno);
+ goto bailout;
+ }
+ multicast_joined_ = true;
+ }
+
+ return true;
+
+bailout:
+ cleanupSocket();
+ return false;
+}
+
+bool AAH_RXPlayer::threadLoop() {
+ struct pollfd poll_fds[2];
+ bool process_more_right_now = false;
+
+ if (!setupSocket()) {
+ sendEvent(MEDIA_ERROR);
+ goto bailout;
+ }
+
+ while (!thread_wrapper_->exitPending()) {
+ // Step 1: Wait until there is something to do.
+ int gap_timeout = computeNextGapRetransmitTimeout();
+ int ring_timeout = ring_buffer_.computeInactivityTimeout();
+ int timeout = -1;
+
+ if (!ring_timeout) {
+ LOGW("RTP inactivity timeout reached, resetting pipeline.");
+ resetPipeline();
+ timeout = gap_timeout;
+ } else {
+ if (gap_timeout < 0) {
+ timeout = ring_timeout;
+ } else if (ring_timeout < 0) {
+ timeout = gap_timeout;
+ } else {
+ timeout = (gap_timeout < ring_timeout) ? gap_timeout
+ : ring_timeout;
+ }
+ }
+
+ if ((0 != timeout) && (!process_more_right_now)) {
+ // Set up the events to wait on. Start with the wakeup pipe.
+ memset(&poll_fds, 0, sizeof(poll_fds));
+ poll_fds[0].fd = wakeup_work_thread_evt_.getWakeupHandle();
+ poll_fds[0].events = POLLIN;
+
+ // Add the RX socket.
+ poll_fds[1].fd = sock_fd_;
+ poll_fds[1].events = POLLIN;
+
+ // Wait for something interesing to happen.
+ int poll_res = poll(poll_fds, NELEM(poll_fds), timeout);
+ if (poll_res < 0) {
+ LOGE("Fatal error (%d,%d) while waiting on events",
+ poll_res, errno);
+ sendEvent(MEDIA_ERROR);
+ goto bailout;
+ }
+ }
+
+ if (thread_wrapper_->exitPending()) {
+ break;
+ }
+
+ wakeup_work_thread_evt_.clearPendingEvents();
+ process_more_right_now = false;
+
+ // Step 2: Do we have data waiting in the socket? If so, drain the
+ // socket moving valid RTP information into the ring buffer to be
+ // processed.
+ if (poll_fds[1].revents) {
+ struct sockaddr_in from;
+ socklen_t from_len;
+
+ ssize_t res = 0;
+ while (!thread_wrapper_->exitPending()) {
+ // Check the size of any pending packet.
+ res = recv(sock_fd_, NULL, 0, MSG_PEEK | MSG_TRUNC);
+
+ // Error?
+ if (res < 0) {
+ // If the error is anything other than would block,
+ // something has gone very wrong.
+ if ((errno != EAGAIN) && (errno != EWOULDBLOCK)) {
+ LOGE("Fatal socket error during recvfrom (%d, %d)",
+ (int)res, errno);
+ goto bailout;
+ }
+
+ // Socket is out of data, just break out of processing and
+ // wait for more.
+ break;
+ }
+
+ // Allocate a payload.
+ PacketBuffer* pb = PacketBuffer::allocate(res);
+ if (NULL == pb) {
+ LOGE("Fatal error, failed to allocate packet buffer of"
+ " length %u", static_cast<uint32_t>(res));
+ goto bailout;
+ }
+
+ // Fetch the data.
+ from_len = sizeof(from);
+ res = recvfrom(sock_fd_, pb->data_, pb->length_, 0,
+ reinterpret_cast<struct sockaddr*>(&from),
+ &from_len);
+ if (res != pb->length_) {
+ LOGE("Fatal error, fetched packet length (%d) does not"
+ " match peeked packet length (%u). This should never"
+ " happen. (errno = %d)",
+ static_cast<int>(res),
+ static_cast<uint32_t>(pb->length_),
+ errno);
+ }
+
+ bool drop_packet = false;
+ if (transmitter_known_) {
+ if (from.sin_addr.s_addr !=
+ transmitter_addr_.sin_addr.s_addr) {
+ uint32_t a = ntohl(from.sin_addr.s_addr);
+ uint16_t p = ntohs(from.sin_port);
+ LOGV("Dropping packet from unknown transmitter"
+ " %u.%u.%u.%u:%hu",
+ ((a >> 24) & 0xFF),
+ ((a >> 16) & 0xFF),
+ ((a >> 8) & 0xFF),
+ ( a & 0xFF),
+ p);
+
+ drop_packet = true;
+ } else {
+ transmitter_addr_.sin_port = from.sin_port;
+ }
+ } else {
+ memcpy(&transmitter_addr_, &from, sizeof(from));
+ transmitter_known_ = true;
+ }
+
+ if (!drop_packet) {
+ bool serious_error = !processRX(pb);
+
+ if (serious_error) {
+ // Something went "seriously wrong". Currently, the
+ // only trigger for this should be a ring buffer
+ // overflow. The current failsafe behavior for when
+ // something goes seriously wrong is to just reset the
+ // pipeline. The system should behave as if this
+ // AAH_RXPlayer was just set up for the first time.
+ LOGE("Something just went seriously wrong with the"
+ " pipeline. Resetting.");
+ resetPipeline();
+ }
+ } else {
+ PacketBuffer::destroy(pb);
+ }
+ }
+ }
+
+ // Step 3: Process any data we mave have accumulated in the ring buffer
+ // so far.
+ if (!thread_wrapper_->exitPending()) {
+ processRingBuffer();
+ }
+
+ // Step 4: At this point in time, the ring buffer should either be
+ // empty, or stalled in front of a gap caused by some dropped packets.
+ // Check on the current gap situation and deal with it in an appropriate
+ // fashion. If processGaps returns true, it means that it has given up
+ // on a gap and that we should try to process some more data
+ // immediately.
+ if (!thread_wrapper_->exitPending()) {
+ process_more_right_now = processGaps();
+ }
+ }
+
+bailout:
+ cleanupSocket();
+ return false;
+}
+
+bool AAH_RXPlayer::processRX(PacketBuffer* pb) {
+ CHECK(NULL != pb);
+
+ uint8_t* data = pb->data_;
+ ssize_t amt = pb->length_;
+ uint32_t nak_magic;
+ uint16_t seq_no;
+ uint32_t epoch;
+
+ // Every packet either starts with an RTP header which is at least 12 bytes
+ // long or is a retry NAK which is 14 bytes long. If there are fewer than
+ // 12 bytes here, this cannot be a proper RTP packet.
+ if (amt < 12) {
+ LOGV("Dropping packet, too short to contain RTP header (%u bytes)",
+ static_cast<uint32_t>(amt));
+ goto drop_packet;
+ }
+
+ // Check to see if this is the special case of a NAK packet.
+ nak_magic = ntohl(*(reinterpret_cast<uint32_t*>(data)));
+ if (nak_magic == kRetransNAKMagic) {
+ // Looks like a NAK packet; make sure its long enough.
+
+ if (amt < static_cast<ssize_t>(sizeof(RetransRequest))) {
+ LOGV("Dropping packet, too short to contain NAK payload (%u bytes)",
+ static_cast<uint32_t>(amt));
+ goto drop_packet;
+ }
+
+ SeqNoGap gap;
+ RetransRequest* rtr = reinterpret_cast<RetransRequest*>(data);
+ gap.start_seq_ = ntohs(rtr->start_seq_);
+ gap.end_seq_ = ntohs(rtr->end_seq_);
+
+ LOGV("Process NAK for gap at [%hu, %hu]", gap.start_seq_, gap.end_seq_);
+ ring_buffer_.processNAK(&gap);
+
+ return true;
+ }
+
+ // According to the TRTP spec, version should be 2, padding should be 0,
+ // extension should be 0 and CSRCCnt should be 0. If any of these tests
+ // fail, we chuck the packet.
+ if (data[0] != 0x80) {
+ LOGV("Dropping packet, bad V/P/X/CSRCCnt field (0x%02x)",
+ data[0]);
+ goto drop_packet;
+ }
+
+ // Check the payload type. For TRTP, it should always be 100.
+ if ((data[1] & 0x7F) != 100) {
+ LOGV("Dropping packet, bad payload type. (%u)",
+ data[1] & 0x7F);
+ goto drop_packet;
+ }
+
+ // Check whether the transmitter has begun a new epoch.
+ epoch = (U32_AT(data + 8) >> 10) & 0x3FFFFF;
+ if (current_epoch_known_) {
+ if (epoch != current_epoch_) {
+ LOGV("%s: new epoch %u", __PRETTY_FUNCTION__, epoch);
+ current_epoch_ = epoch;
+ resetPipeline();
+ }
+ } else {
+ current_epoch_ = epoch;
+ current_epoch_known_ = true;
+ }
+
+ // Extract the sequence number and hand the packet off to the ring buffer
+ // for dropped packet detection and later processing.
+ seq_no = U16_AT(data + 2);
+ return ring_buffer_.pushBuffer(pb, seq_no);
+
+drop_packet:
+ PacketBuffer::destroy(pb);
+ return true;
+}
+
+void AAH_RXPlayer::processRingBuffer() {
+ PacketBuffer* pb;
+ bool is_discon;
+ sp<Substream> substream;
+ LinearTransform trans;
+ bool foundTrans = false;
+
+ while (NULL != (pb = ring_buffer_.fetchBuffer(&is_discon))) {
+ if (is_discon) {
+ // Abort all partially assembled payloads.
+ for (size_t i = 0; i < substreams_.size(); ++i) {
+ CHECK(substreams_.valueAt(i) != NULL);
+ substreams_.valueAt(i)->cleanupBufferInProgress();
+ }
+ }
+
+ uint8_t* data = pb->data_;
+ ssize_t amt = pb->length_;
+
+ // Should not have any non-RTP packets in the ring buffer. RTP packets
+ // must be at least 12 bytes long.
+ CHECK(amt >= 12);
+
+ // Extract the marker bit and the SSRC field.
+ bool marker = (data[1] & 0x80) != 0;
+ uint32_t ssrc = U32_AT(data + 8);
+
+ // Is this the start of a new TRTP payload? If so, the marker bit
+ // should be set and there are some things we should be checking for.
+ if (marker) {
+ // TRTP headers need to have at least a byte for version, a byte for
+ // payload type and flags, and 4 bytes for length.
+ if (amt < 18) {
+ LOGV("Dropping packet, too short to contain TRTP header"
+ " (%u bytes)", static_cast<uint32_t>(amt));
+ goto process_next_packet;
+ }
+
+ // Check the TRTP version and extract the payload type/flags.
+ uint8_t trtp_version = data[12];
+ uint8_t payload_type = (data[13] >> 4) & 0xF;
+ uint8_t trtp_flags = data[13] & 0xF;
+
+ if (1 != trtp_version) {
+ LOGV("Dropping packet, bad trtp version %hhu", trtp_version);
+ goto process_next_packet;
+ }
+
+ // Is there a timestamp transformation present on this packet? If
+ // so, extract it and pass it to the appropriate substreams.
+ if (trtp_flags & 0x02) {
+ ssize_t offset = 18 + ((trtp_flags & 0x01) ? 4 : 0);
+ if (amt < (offset + 24)) {
+ LOGV("Dropping packet, too short to contain TRTP Timestamp"
+ " Transformation (%u bytes)",
+ static_cast<uint32_t>(amt));
+ goto process_next_packet;
+ }
+
+ trans.a_zero = fetchInt64(data + offset);
+ trans.b_zero = fetchInt64(data + offset + 16);
+ trans.a_to_b_numer = static_cast<int32_t>(
+ fetchInt32 (data + offset + 8));
+ trans.a_to_b_denom = U32_AT(data + offset + 12);
+ foundTrans = true;
+
+ uint32_t program_id = (ssrc >> 5) & 0x1F;
+ for (size_t i = 0; i < substreams_.size(); ++i) {
+ sp<Substream> iter = substreams_.valueAt(i);
+ CHECK(iter != NULL);
+
+ if (iter->getProgramID() == program_id) {
+ iter->processTSTransform(trans);
+ }
+ }
+ }
+
+ // Is this a command packet? If so, its not necessarily associate
+ // with one particular substream. Just give it to the command
+ // packet handler and then move on.
+ if (4 == payload_type) {
+ processCommandPacket(pb);
+ goto process_next_packet;
+ }
+ }
+
+ // If we got to here, then we are a normal packet. Find (or allocate)
+ // the substream we belong to and send the packet off to be processed.
+ substream = substreams_.valueFor(ssrc);
+ if (substream == NULL) {
+ substream = new Substream(ssrc, omx_);
+ if (substream == NULL) {
+ LOGE("Failed to allocate substream for SSRC 0x%08x", ssrc);
+ goto process_next_packet;
+ }
+ substreams_.add(ssrc, substream);
+
+ if (foundTrans) {
+ substream->processTSTransform(trans);
+ }
+ }
+
+ CHECK(substream != NULL);
+
+ if (marker) {
+ // Start of a new TRTP payload for this substream. Extract the
+ // lower 32 bits of the timestamp and hand the buffer to the
+ // substream for processing.
+ uint32_t ts_lower = U32_AT(data + 4);
+ substream->processPayloadStart(data + 12, amt - 12, ts_lower);
+ } else {
+ // Continuation of an existing TRTP payload. Just hand it off to
+ // the substream for processing.
+ substream->processPayloadCont(data + 12, amt - 12);
+ }
+
+process_next_packet:
+ PacketBuffer::destroy(pb);
+ } // end of main processing while loop.
+}
+
+void AAH_RXPlayer::processCommandPacket(PacketBuffer* pb) {
+ CHECK(NULL != pb);
+
+ uint8_t* data = pb->data_;
+ ssize_t amt = pb->length_;
+
+ // verify that this packet meets the minimum length of a command packet
+ if (amt < 20) {
+ return;
+ }
+
+ uint8_t trtp_version = data[12];
+ uint8_t trtp_flags = data[13] & 0xF;
+
+ if (1 != trtp_version) {
+ LOGV("Dropping packet, bad trtp version %hhu", trtp_version);
+ return;
+ }
+
+ // calculate the start of the command payload
+ ssize_t offset = 18;
+ if (trtp_flags & 0x01) {
+ // timestamp is present (4 bytes)
+ offset += 4;
+ }
+ if (trtp_flags & 0x02) {
+ // transform is present (24 bytes)
+ offset += 24;
+ }
+
+ // the packet must contain 2 bytes of command payload beyond the TRTP header
+ if (amt < offset + 2) {
+ return;
+ }
+
+ uint16_t command_id = U16_AT(data + offset);
+
+ switch (command_id) {
+ case TRTPControlPacket::kCommandNop:
+ break;
+
+ case TRTPControlPacket::kCommandEOS:
+ case TRTPControlPacket::kCommandFlush: {
+ uint16_t program_id = (U32_AT(data + 8) >> 5) & 0x1F;
+ LOGI("*** %s flushing program_id=%d",
+ __PRETTY_FUNCTION__, program_id);
+
+ Vector<uint32_t> substreams_to_remove;
+ for (size_t i = 0; i < substreams_.size(); ++i) {
+ sp<Substream> iter = substreams_.valueAt(i);
+ if (iter->getProgramID() == program_id) {
+ iter->shutdown();
+ substreams_to_remove.add(iter->getSSRC());
+ }
+ }
+
+ for (size_t i = 0; i < substreams_to_remove.size(); ++i) {
+ substreams_.removeItem(substreams_to_remove[i]);
+ }
+ } break;
+ }
+}
+
+bool AAH_RXPlayer::processGaps() {
+ // Deal with the current gap situation. Specifically...
+ //
+ // 1) If a new gap has shown up, send a retransmit request to the
+ // transmitter.
+ // 2) If a gap we were working on has had a packet in the middle or at
+ // the end filled in, send another retransmit request for the begining
+ // portion of the gap. TRTP was designed for LANs where packet
+ // re-ordering is very unlikely; so see the middle or end of a gap
+ // filled in before the begining is an almost certain indication that
+ // a retransmission packet was also dropped.
+ // 3) If we have been working on a gap for a while and it still has not
+ // been filled in, send another retransmit request.
+ // 4) If the are no more gaps in the ring, clear the current_gap_status_
+ // flag to indicate that all is well again.
+
+ // Start by fetching the active gap status.
+ SeqNoGap gap;
+ bool send_retransmit_request = false;
+ bool ret_val = false;
+ GapStatus gap_status;
+ if (kGS_NoGap != (gap_status = ring_buffer_.fetchCurrentGap(&gap))) {
+ // Note: checking for a change in the end sequence number should cover
+ // moving on to an entirely new gap for case #1 as well as resending the
+ // begining of a gap range for case #2.
+ send_retransmit_request = (kGS_NoGap == current_gap_status_) ||
+ (current_gap_.end_seq_ != gap.end_seq_);
+
+ // If this is the same gap we have been working on, and it has timed
+ // out, then check to see if our substreams are about to underflow. If
+ // so, instead of sending another retransmit request, just give up on
+ // this gap and move on.
+ if (!send_retransmit_request &&
+ (kGS_NoGap != current_gap_status_) &&
+ (0 == computeNextGapRetransmitTimeout())) {
+
+ // If out current gap is the fast-start gap, don't bother to skip it
+ // because substreams look like the are about to underflow.
+ if ((kGS_FastStartGap != gap_status) ||
+ (current_gap_.end_seq_ != gap.end_seq_)) {
+ for (size_t i = 0; i < substreams_.size(); ++i) {
+ if (substreams_.valueAt(i)->isAboutToUnderflow()) {
+ LOGV("About to underflow, giving up on gap [%hu, %hu]",
+ gap.start_seq_, gap.end_seq_);
+ ring_buffer_.processNAK();
+ current_gap_status_ = kGS_NoGap;
+ return true;
+ }
+ }
+ }
+
+ // Looks like no one is about to underflow. Just go ahead and send
+ // the request.
+ send_retransmit_request = true;
+ }
+ } else {
+ current_gap_status_ = kGS_NoGap;
+ }
+
+ if (send_retransmit_request) {
+ // If we have been working on a fast start, and it is still not filled
+ // in, even after the extended retransmit time out, give up and skip it.
+ // The system should fall back into its normal slow-start behavior.
+ if ((kGS_FastStartGap == current_gap_status_) &&
+ (current_gap_.end_seq_ == gap.end_seq_)) {
+ LOGV("Fast start is taking forever; giving up.");
+ ring_buffer_.processNAK();
+ current_gap_status_ = kGS_NoGap;
+ return true;
+ }
+
+ // Send the request.
+ RetransRequest req;
+ uint32_t magic = (kGS_FastStartGap == gap_status)
+ ? kFastStartRequestMagic
+ : kRetransRequestMagic;
+ req.magic_ = htonl(magic);
+ req.mcast_ip_ = listen_addr_.sin_addr.s_addr;
+ req.mcast_port_ = listen_addr_.sin_port;
+ req.start_seq_ = htons(gap.start_seq_);
+ req.end_seq_ = htons(gap.end_seq_);
+
+ {
+ uint32_t a = ntohl(transmitter_addr_.sin_addr.s_addr);
+ uint16_t p = ntohs(transmitter_addr_.sin_port);
+ LOGV("Sending to transmitter %u.%u.%u.%u:%hu",
+ ((a >> 24) & 0xFF),
+ ((a >> 16) & 0xFF),
+ ((a >> 8) & 0xFF),
+ ( a & 0xFF),
+ p);
+ }
+
+ int res = sendto(sock_fd_, &req, sizeof(req), 0,
+ reinterpret_cast<struct sockaddr*>(&transmitter_addr_),
+ sizeof(transmitter_addr_));
+ if (res < 0) {
+ LOGE("Error when sending retransmit request (%d)", errno);
+ } else {
+ LOGV("%s request for range [%hu, %hu] sent",
+ (kGS_FastStartGap == gap_status) ? "Fast Start" : "Retransmit",
+ gap.start_seq_, gap.end_seq_);
+ }
+
+ // Update the current gap info.
+ current_gap_ = gap;
+ current_gap_status_ = gap_status;
+ next_retrans_req_time_ = monotonicUSecNow() +
+ ((kGS_FastStartGap == current_gap_status_)
+ ? kFastStartTimeoutUSec
+ : kGapRerequestTimeoutUSec);
+ }
+
+ return false;
+}
+
+// Compute when its time to send the next gap retransmission in milliseconds.
+// Returns < 0 for an infinite timeout (no gap) and 0 if its time to retransmit
+// right now.
+int AAH_RXPlayer::computeNextGapRetransmitTimeout() {
+ if (kGS_NoGap == current_gap_status_) {
+ return -1;
+ }
+
+ int64_t timeout_delta = next_retrans_req_time_ - monotonicUSecNow();
+
+ timeout_delta /= 1000;
+ if (timeout_delta <= 0) {
+ return 0;
+ }
+
+ return static_cast<uint32_t>(timeout_delta);
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_rx_player_ring_buffer.cpp b/media/libaah_rtp/aah_rx_player_ring_buffer.cpp
new file mode 100644
index 0000000..22467f7
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player_ring_buffer.cpp
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include "aah_rx_player.h"
+
+namespace android {
+
+AAH_RXPlayer::RXRingBuffer::RXRingBuffer(uint32_t capacity) {
+ capacity_ = capacity;
+ rd_ = wr_ = 0;
+ ring_ = new PacketBuffer*[capacity];
+ memset(ring_, 0, sizeof(PacketBuffer*) * capacity);
+ reset();
+}
+
+AAH_RXPlayer::RXRingBuffer::~RXRingBuffer() {
+ reset();
+ delete[] ring_;
+}
+
+void AAH_RXPlayer::RXRingBuffer::reset() {
+ AutoMutex lock(&lock_);
+
+ if (NULL != ring_) {
+ while (rd_ != wr_) {
+ CHECK(rd_ < capacity_);
+ if (NULL != ring_[rd_]) {
+ PacketBuffer::destroy(ring_[rd_]);
+ ring_[rd_] = NULL;
+ }
+ rd_ = (rd_ + 1) % capacity_;
+ }
+ }
+
+ rd_ = wr_ = 0;
+ rd_seq_known_ = false;
+ waiting_for_fast_start_ = true;
+ fetched_first_packet_ = false;
+ rtp_activity_timeout_valid_ = false;
+}
+
+bool AAH_RXPlayer::RXRingBuffer::pushBuffer(PacketBuffer* buf,
+ uint16_t seq) {
+ AutoMutex lock(&lock_);
+ CHECK(NULL != ring_);
+ CHECK(NULL != buf);
+
+ rtp_activity_timeout_valid_ = true;
+ rtp_activity_timeout_ = monotonicUSecNow() + kRTPActivityTimeoutUSec;
+
+ // If the ring buffer is totally reset (we have never received a single
+ // payload) then we don't know the rd sequence number and this should be
+ // simple. We just store the payload, advance the wr pointer and record the
+ // initial sequence number.
+ if (!rd_seq_known_) {
+ CHECK(rd_ == wr_);
+ CHECK(NULL == ring_[wr_]);
+ CHECK(wr_ < capacity_);
+
+ ring_[wr_] = buf;
+ wr_ = (wr_ + 1) % capacity_;
+ rd_seq_ = seq;
+ rd_seq_known_ = true;
+ return true;
+ }
+
+ // Compute the seqence number of this payload and of the write pointer,
+ // normalized around the read pointer. IOW - transform the payload seq no
+ // and the wr pointer seq no into a space where the rd pointer seq no is
+ // zero. This will define 4 cases we can consider...
+ //
+ // 1) norm_seq == norm_wr_seq
+ // This payload is contiguous with the last. All is good.
+ //
+ // 2) ((norm_seq < norm_wr_seq) && (norm_seq >= norm_rd_seq)
+ // aka ((norm_seq < norm_wr_seq) && (norm_seq >= 0)
+ // This payload is in the past, in the unprocessed region of the ring
+ // buffer. It is probably a retransmit intended to fill in a dropped
+ // payload; it may be a duplicate.
+ //
+ // 3) ((norm_seq - norm_wr_seq) & 0x8000) != 0
+ // This payload is in the past compared to the write pointer (or so very
+ // far in the future that it has wrapped the seq no space), but not in
+ // the unprocessed region of the ring buffer. This could be a duplicate
+ // retransmit; we just drop these payloads unless we are waiting for our
+ // first fast start packet. If we are waiting for fast start, than this
+ // packet is probably the first packet of the fast start retransmission.
+ // If it will fit in the buffer, back up the read pointer to its position
+ // and clear the fast start flag, otherwise just drop it.
+ //
+ // 4) ((norm_seq - norm_wr_seq) & 0x8000) == 0
+ // This payload which is ahead of the next write pointer. This indicates
+ // that we have missed some payloads and need to request a retransmit.
+ // If norm_seq >= (capacity - 1), then the gap is so large that it would
+ // overflow the ring buffer and we should probably start to panic.
+
+ uint16_t norm_wr_seq = ((wr_ + capacity_ - rd_) % capacity_);
+ uint16_t norm_seq = seq - rd_seq_;
+
+ // Check for overflow first.
+ if ((!(norm_seq & 0x8000)) && (norm_seq >= (capacity_ - 1))) {
+ LOGW("Ring buffer overflow; cap = %u, [rd, wr] = [%hu, %hu], seq = %hu",
+ capacity_, rd_seq_, norm_wr_seq + rd_seq_, seq);
+ PacketBuffer::destroy(buf);
+ return false;
+ }
+
+ // Check for case #1
+ if (norm_seq == norm_wr_seq) {
+ CHECK(wr_ < capacity_);
+ CHECK(NULL == ring_[wr_]);
+
+ ring_[wr_] = buf;
+ wr_ = (wr_ + 1) % capacity_;
+
+ CHECK(wr_ != rd_);
+ return true;
+ }
+
+ // Check case #2
+ uint32_t ring_pos = (rd_ + norm_seq) % capacity_;
+ if ((norm_seq < norm_wr_seq) && (!(norm_seq & 0x8000))) {
+ // Do we already have a payload for this slot? If so, then this looks
+ // like a duplicate retransmit. Just ignore it.
+ if (NULL != ring_[ring_pos]) {
+ LOGD("RXed duplicate retransmit, seq = %hu", seq);
+ PacketBuffer::destroy(buf);
+ } else {
+ // Looks like we were missing this payload. Go ahead and store it.
+ ring_[ring_pos] = buf;
+ }
+
+ return true;
+ }
+
+ // Check case #3
+ if ((norm_seq - norm_wr_seq) & 0x8000) {
+ if (!waiting_for_fast_start_) {
+ LOGD("RXed duplicate retransmit from before rd pointer, seq = %hu",
+ seq);
+ PacketBuffer::destroy(buf);
+ } else {
+ // Looks like a fast start fill-in. Go ahead and store it, assuming
+ // that we can fit it in the buffer.
+ uint32_t implied_ring_size = static_cast<uint32_t>(norm_wr_seq)
+ + (rd_seq_ - seq);
+
+ if (implied_ring_size >= (capacity_ - 1)) {
+ LOGD("RXed what looks like a fast start packet (seq = %hu),"
+ " but packet is too far in the past to fit into the ring"
+ " buffer. Dropping.", seq);
+ PacketBuffer::destroy(buf);
+ } else {
+ ring_pos = (rd_ + capacity_ + seq - rd_seq_) % capacity_;
+ rd_seq_ = seq;
+ rd_ = ring_pos;
+ waiting_for_fast_start_ = false;
+
+ CHECK(ring_pos < capacity_);
+ CHECK(NULL == ring_[ring_pos]);
+ ring_[ring_pos] = buf;
+ }
+
+ }
+ return true;
+ }
+
+ // Must be in case #4 with no overflow. This packet fits in the current
+ // ring buffer, but is discontiuguous. Advance the write pointer leaving a
+ // gap behind.
+ uint32_t gap_len = (ring_pos + capacity_ - wr_) % capacity_;
+ LOGD("Drop detected; %u packets, seq_range [%hu, %hu]",
+ gap_len,
+ rd_seq_ + norm_wr_seq,
+ rd_seq_ + norm_wr_seq + gap_len - 1);
+
+ CHECK(NULL == ring_[ring_pos]);
+ ring_[ring_pos] = buf;
+ wr_ = (ring_pos + 1) % capacity_;
+ CHECK(wr_ != rd_);
+
+ return true;
+}
+
+AAH_RXPlayer::PacketBuffer*
+AAH_RXPlayer::RXRingBuffer::fetchBuffer(bool* is_discon) {
+ AutoMutex lock(&lock_);
+ CHECK(NULL != ring_);
+ CHECK(NULL != is_discon);
+
+ // If the read seqence number is not known, then this ring buffer has not
+ // received a packet since being reset and there cannot be any packets to
+ // return. If we are still waiting for the first fast start packet to show
+ // up, we don't want to let any buffer be consumed yet because we expect to
+ // see a packet before the initial read sequence number show up shortly.
+ if (!rd_seq_known_ || waiting_for_fast_start_) {
+ *is_discon = false;
+ return NULL;
+ }
+
+ PacketBuffer* ret = NULL;
+ *is_discon = !fetched_first_packet_;
+
+ while ((rd_ != wr_) && (NULL == ret)) {
+ CHECK(rd_ < capacity_);
+
+ // If we hit a gap, stall and do not advance the read pointer. Let the
+ // higher level code deal with requesting retries and/or deciding to
+ // skip the current gap.
+ ret = ring_[rd_];
+ if (NULL == ret) {
+ break;
+ }
+
+ ring_[rd_] = NULL;
+ rd_ = (rd_ + 1) % capacity_;
+ ++rd_seq_;
+ }
+
+ if (NULL != ret) {
+ fetched_first_packet_ = true;
+ }
+
+ return ret;
+}
+
+AAH_RXPlayer::GapStatus
+AAH_RXPlayer::RXRingBuffer::fetchCurrentGap(SeqNoGap* gap) {
+ AutoMutex lock(&lock_);
+ CHECK(NULL != ring_);
+ CHECK(NULL != gap);
+
+ // If the read seqence number is not known, then this ring buffer has not
+ // received a packet since being reset and there cannot be any gaps.
+ if (!rd_seq_known_) {
+ return kGS_NoGap;
+ }
+
+ // If we are waiting for fast start, then the current gap is a fast start
+ // gap and it includes all packets before the read sequence number.
+ if (waiting_for_fast_start_) {
+ gap->start_seq_ =
+ gap->end_seq_ = rd_seq_ - 1;
+ return kGS_FastStartGap;
+ }
+
+ // If rd == wr, then the buffer is empty and there cannot be any gaps.
+ if (rd_ == wr_) {
+ return kGS_NoGap;
+ }
+
+ // If rd_ is currently pointing at an unprocessed packet, then there is no
+ // current gap.
+ CHECK(rd_ < capacity_);
+ if (NULL != ring_[rd_]) {
+ return kGS_NoGap;
+ }
+
+ // Looks like there must be a gap here. The start of the gap is the current
+ // rd sequence number, all we need to do now is determine its length in
+ // order to compute the end sequence number.
+ gap->start_seq_ = rd_seq_;
+ uint16_t end = rd_seq_;
+ uint32_t tmp = (rd_ + 1) % capacity_;
+ while ((tmp != wr_) && (NULL == ring_[tmp])) {
+ ++end;
+ tmp = (tmp + 1) % capacity_;
+ }
+ gap->end_seq_ = end;
+
+ return kGS_NormalGap;
+}
+
+void AAH_RXPlayer::RXRingBuffer::processNAK(SeqNoGap* nak) {
+ AutoMutex lock(&lock_);
+ CHECK(NULL != ring_);
+
+ // If we were waiting for our first fast start fill-in packet, and we
+ // received a NAK, then apparantly we are not getting our fast start. Just
+ // clear the waiting flag and go back to normal behavior.
+ if (waiting_for_fast_start_) {
+ waiting_for_fast_start_ = false;
+ }
+
+ // If we have not received a packet since last reset, or there is no data in
+ // the ring, then there is nothing to skip.
+ if ((!rd_seq_known_) || (rd_ == wr_)) {
+ return;
+ }
+
+ // If rd_ is currently pointing at an unprocessed packet, then there is no
+ // gap to skip.
+ CHECK(rd_ < capacity_);
+ if (NULL != ring_[rd_]) {
+ return;
+ }
+
+ // Looks like there must be a gap here. Advance rd until we have passed
+ // over the portion of it indicated by nak (or all of the gap if nak is
+ // NULL). Then reset fetched_first_packet_ so that the next read will show
+ // up as being discontiguous.
+ uint16_t seq_after_gap = (NULL == nak) ? 0 : nak->end_seq_ + 1;
+ while ((rd_ != wr_) &&
+ (NULL == ring_[rd_]) &&
+ ((NULL == nak) || (seq_after_gap != rd_seq_))) {
+ rd_ = (rd_ + 1) % capacity_;
+ ++rd_seq_;
+ }
+ fetched_first_packet_ = false;
+}
+
+int AAH_RXPlayer::RXRingBuffer::computeInactivityTimeout() {
+ AutoMutex lock(&lock_);
+
+ if (!rtp_activity_timeout_valid_) {
+ return -1;
+ }
+
+ uint64_t now = monotonicUSecNow();
+ if (rtp_activity_timeout_ <= now) {
+ return 0;
+ }
+
+ return (rtp_activity_timeout_ - now) / 1000;
+}
+
+AAH_RXPlayer::PacketBuffer*
+AAH_RXPlayer::PacketBuffer::allocate(ssize_t length) {
+ if (length <= 0) {
+ return NULL;
+ }
+
+ uint32_t alloc_len = sizeof(PacketBuffer) + length;
+ PacketBuffer* ret = reinterpret_cast<PacketBuffer*>(
+ new uint8_t[alloc_len]);
+
+ if (NULL != ret) {
+ ret->length_ = length;
+ }
+
+ return ret;
+}
+
+void AAH_RXPlayer::PacketBuffer::destroy(PacketBuffer* pb) {
+ uint8_t* kill_me = reinterpret_cast<uint8_t*>(pb);
+ delete[] kill_me;
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_rx_player_substream.cpp b/media/libaah_rtp/aah_rx_player_substream.cpp
new file mode 100644
index 0000000..b015fc9
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player_substream.cpp
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+
+#include <utils/Log.h>
+
+#include <include/avc_utils.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OMXCodec.h>
+#include <media/stagefright/Utils.h>
+
+#include "aah_rx_player.h"
+
+namespace android {
+
+int64_t AAH_RXPlayer::Substream::kAboutToUnderflowThreshold =
+ 50ull * 1000;
+
+AAH_RXPlayer::Substream::Substream(uint32_t ssrc, OMXClient& omx) {
+ ssrc_ = ssrc;
+ substream_details_known_ = false;
+ buffer_in_progress_ = NULL;
+
+ decoder_ = new AAH_DecoderPump(omx);
+ if (decoder_ == NULL) {
+ LOGE("%s failed to allocate decoder pump!", __PRETTY_FUNCTION__);
+ }
+
+ // cleanupBufferInProgress will reset most of the internal state variables.
+ // Just need to make sure that buffer_in_progress_ is NULL before calling.
+ cleanupBufferInProgress();
+}
+
+
+void AAH_RXPlayer::Substream::shutdown() {
+ substream_meta_ = NULL;
+ cleanupBufferInProgress();
+ cleanupDecoder();
+}
+
+void AAH_RXPlayer::Substream::cleanupBufferInProgress() {
+ if (NULL != buffer_in_progress_) {
+ buffer_in_progress_->release();
+ buffer_in_progress_ = NULL;
+ }
+
+ expected_buffer_size_ = 0;
+ buffer_filled_ = 0;
+ waiting_for_rap_ = true;
+}
+
+void AAH_RXPlayer::Substream::cleanupDecoder() {
+ if (decoder_ != NULL) {
+ decoder_->shutdown();
+ }
+}
+
+void AAH_RXPlayer::Substream::processPayloadStart(uint8_t* buf,
+ uint32_t amt,
+ int32_t ts_lower) {
+ uint32_t min_length = 6;
+ // Do we have a buffer in progress already? If so, abort the buffer. In
+ // theory, this should never happen. If there were a discontinutity in the
+ // stream, the discon in the seq_nos at the RTP level should have already
+ // triggered a cleanup of the buffer in progress. To see a problem at this
+ // level is an indication either of a bug in the transmitter, or some form
+ // of terrible corruption/tampering on the wire.
+ if (NULL != buffer_in_progress_) {
+ LOGE("processPayloadStart is aborting payload already in progress.");
+ cleanupBufferInProgress();
+ }
+
+ // Parse enough of the header to know where we stand. Since this is a
+ // payload start, it should begin with a TRTP header which has to be at
+ // least 6 bytes long.
+ if (amt < min_length) {
+ LOGV("Discarding payload too short to contain TRTP header (len = %u)",
+ amt);
+ return;
+ }
+
+ // Check the TRTP version number.
+ if (0x01 != buf[0]) {
+ LOGV("Unexpected TRTP version (%u) in header. Expected %u.",
+ buf[0], 1);
+ return;
+ }
+
+ // Extract the substream type field and make sure its one we understand (and
+ // one that does not conflict with any previously received substream type.
+ uint8_t header_type = (buf[1] >> 4) & 0xF;
+ switch (header_type) {
+ case 0x01:
+ // Audio, yay! Just break. We understand audio payloads.
+ break;
+ case 0x02:
+ LOGV("RXed packet with unhandled TRTP header type (Video).");
+ return;
+ case 0x03:
+ LOGV("RXed packet with unhandled TRTP header type (Subpicture).");
+ return;
+ case 0x04:
+ LOGV("RXed packet with unhandled TRTP header type (Control).");
+ return;
+ default:
+ LOGV("RXed packet with unhandled TRTP header type (%u).",
+ header_type);
+ return;
+ }
+
+ if (substream_details_known_ && (header_type != substream_type_)) {
+ LOGV("RXed TRTP Payload for SSRC=0x%08x where header type (%u) does not"
+ " match previously received header type (%u)",
+ ssrc_, header_type, substream_type_);
+ return;
+ }
+
+ // Check the flags to see if there is another 32 bits of timestamp present.
+ uint32_t trtp_header_len = 6;
+ bool ts_valid = buf[1] & 0x1;
+ if (ts_valid) {
+ min_length += 4;
+ trtp_header_len += 4;
+ if (amt < min_length) {
+ LOGV("Discarding payload too short to contain TRTP timestamp"
+ " (len = %u)", amt);
+ return;
+ }
+ }
+
+ // Extract the TRTP length field and sanity check it.
+ uint32_t trtp_len;
+ trtp_len = (static_cast<uint32_t>(buf[2]) << 24) |
+ (static_cast<uint32_t>(buf[3]) << 16) |
+ (static_cast<uint32_t>(buf[4]) << 8) |
+ static_cast<uint32_t>(buf[5]);
+ if (trtp_len < min_length) {
+ LOGV("TRTP length (%u) is too short to be valid. Must be at least %u"
+ " bytes.", trtp_len, min_length);
+ return;
+ }
+
+ // Extract the rest of the timestamp field if valid.
+ int64_t ts = 0;
+ uint32_t parse_offset = 6;
+ if (ts_valid) {
+ ts = (static_cast<int64_t>(buf[parse_offset ]) << 56) |
+ (static_cast<int64_t>(buf[parse_offset + 1]) << 48) |
+ (static_cast<int64_t>(buf[parse_offset + 2]) << 40) |
+ (static_cast<int64_t>(buf[parse_offset + 3]) << 32);
+ ts |= ts_lower;
+ parse_offset += 4;
+ }
+
+ // Check the flags to see if there is another 24 bytes of timestamp
+ // transformation present.
+ if (buf[1] & 0x2) {
+ min_length += 24;
+ parse_offset += 24;
+ trtp_header_len += 24;
+ if (amt < min_length) {
+ LOGV("Discarding payload too short to contain TRTP timestamp"
+ " transformation (len = %u)", amt);
+ return;
+ }
+ }
+
+ // TODO : break the parsing into individual parsers for the different
+ // payload types (audio, video, etc).
+ //
+ // At this point in time, we know that this is audio. Go ahead and parse
+ // the basic header, check the codec type, and find the payload portion of
+ // the packet.
+ min_length += 3;
+ if (trtp_len < min_length) {
+ LOGV("TRTP length (%u) is too short to be a valid audio payload. Must"
+ " be at least %u bytes.", trtp_len, min_length);
+ return;
+ }
+
+ if (amt < min_length) {
+ LOGV("TRTP porttion of RTP payload (%u bytes) too small to contain"
+ " entire TRTP header. TRTP does not currently support fragmenting"
+ " TRTP headers across RTP payloads", amt);
+ return;
+ }
+
+ uint8_t codec_type = buf[parse_offset ];
+ uint8_t flags = buf[parse_offset + 1];
+ uint8_t volume = buf[parse_offset + 2];
+ parse_offset += 3;
+ trtp_header_len += 3;
+
+ if (!setupSubstreamType(header_type, codec_type)) {
+ return;
+ }
+
+ if (decoder_ != NULL) {
+ decoder_->setRenderVolume(volume);
+ }
+
+ // TODO : move all of the constant flag and offset definitions for TRTP up
+ // into some sort of common header file.
+ if (waiting_for_rap_ && !(flags & 0x08)) {
+ LOGV("Dropping non-RAP TRTP Audio Payload while waiting for RAP.");
+ return;
+ }
+
+ if (flags & 0x10) {
+ LOGV("Dropping TRTP Audio Payload with aux codec data present (only"
+ " handle MP3 right now, and it has no aux data)");
+ return;
+ }
+
+ // OK - everything left is just payload. Compute the payload size, start
+ // the buffer in progress and pack as much payload as we can into it. If
+ // the payload is finished once we are done, go ahead and send the payload
+ // to the decoder.
+ expected_buffer_size_ = trtp_len - trtp_header_len;
+ if (!expected_buffer_size_) {
+ LOGV("Dropping TRTP Audio Payload with 0 Access Unit length");
+ return;
+ }
+
+ CHECK(amt >= trtp_header_len);
+ uint32_t todo = amt - trtp_header_len;
+ if (expected_buffer_size_ < todo) {
+ LOGV("Extra data (%u > %u) present in initial TRTP Audio Payload;"
+ " dropping payload.", todo, expected_buffer_size_);
+ return;
+ }
+
+ buffer_filled_ = 0;
+ buffer_in_progress_ = new MediaBuffer(expected_buffer_size_);
+ if ((NULL == buffer_in_progress_) ||
+ (NULL == buffer_in_progress_->data())) {
+ LOGV("Failed to allocate MediaBuffer of length %u",
+ expected_buffer_size_);
+ cleanupBufferInProgress();
+ return;
+ }
+
+ sp<MetaData> meta = buffer_in_progress_->meta_data();
+ if (meta == NULL) {
+ LOGV("Missing metadata structure in allocated MediaBuffer; dropping"
+ " payload");
+ cleanupBufferInProgress();
+ return;
+ }
+
+ // TODO : set this based on the codec type indicated in the TRTP stream.
+ // Right now, we only support MP3, so the choice is obvious.
+ meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG);
+ if (ts_valid) {
+ meta->setInt64(kKeyTime, ts);
+ }
+
+ if (amt > 0) {
+ uint8_t* tgt =
+ reinterpret_cast<uint8_t*>(buffer_in_progress_->data());
+ memcpy(tgt + buffer_filled_, buf + trtp_header_len, todo);
+ buffer_filled_ += amt;
+ }
+
+ if (buffer_filled_ >= expected_buffer_size_) {
+ processCompletedBuffer();
+ }
+}
+
+void AAH_RXPlayer::Substream::processPayloadCont(uint8_t* buf,
+ uint32_t amt) {
+ if (NULL == buffer_in_progress_) {
+ LOGV("TRTP Receiver skipping payload continuation; no buffer currently"
+ " in progress.");
+ return;
+ }
+
+ CHECK(buffer_filled_ < expected_buffer_size_);
+ uint32_t buffer_left = expected_buffer_size_ - buffer_filled_;
+ if (amt > buffer_left) {
+ LOGV("Extra data (%u > %u) present in continued TRTP Audio Payload;"
+ " dropping payload.", amt, buffer_left);
+ cleanupBufferInProgress();
+ return;
+ }
+
+ if (amt > 0) {
+ uint8_t* tgt =
+ reinterpret_cast<uint8_t*>(buffer_in_progress_->data());
+ memcpy(tgt + buffer_filled_, buf, amt);
+ buffer_filled_ += amt;
+ }
+
+ if (buffer_filled_ >= expected_buffer_size_) {
+ processCompletedBuffer();
+ }
+}
+
+void AAH_RXPlayer::Substream::processCompletedBuffer() {
+ const uint8_t* buffer_data = NULL;
+ int sample_rate;
+ int channel_count;
+ size_t frame_size;
+ status_t res;
+
+ CHECK(NULL != buffer_in_progress_);
+
+ if (decoder_ == NULL) {
+ LOGV("Dropping complete buffer, no decoder pump allocated");
+ goto bailout;
+ }
+
+ buffer_data = reinterpret_cast<const uint8_t*>(buffer_in_progress_->data());
+ if (buffer_in_progress_->size() < 4) {
+ LOGV("MP3 payload too short to contain header, dropping payload.");
+ goto bailout;
+ }
+
+ // Extract the channel count and the sample rate from the MP3 header. The
+ // stagefright MP3 requires that these be delivered before decoing can
+ // begin.
+ if (!GetMPEGAudioFrameSize(U32_AT(buffer_data),
+ &frame_size,
+ &sample_rate,
+ &channel_count,
+ NULL,
+ NULL)) {
+ LOGV("Failed to parse MP3 header in payload, droping payload.");
+ goto bailout;
+ }
+
+
+ // Make sure that our substream metadata is set up properly. If there has
+ // been a format change, be sure to reset the underlying decoder. In
+ // stagefright, it seems like the only way to do this is to destroy and
+ // recreate the decoder.
+ if (substream_meta_ == NULL) {
+ substream_meta_ = new MetaData();
+
+ if (substream_meta_ == NULL) {
+ LOGE("Failed to allocate MetaData structure for substream");
+ goto bailout;
+ }
+
+ substream_meta_->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG);
+ substream_meta_->setInt32 (kKeyChannelCount, channel_count);
+ substream_meta_->setInt32 (kKeySampleRate, sample_rate);
+ } else {
+ int32_t prev_sample_rate;
+ int32_t prev_channel_count;
+ substream_meta_->findInt32(kKeySampleRate, &prev_sample_rate);
+ substream_meta_->findInt32(kKeyChannelCount, &prev_channel_count);
+
+ if ((prev_channel_count != channel_count) ||
+ (prev_sample_rate != sample_rate)) {
+ LOGW("Format change detected, forcing decoder reset.");
+ cleanupDecoder();
+
+ substream_meta_->setInt32(kKeyChannelCount, channel_count);
+ substream_meta_->setInt32(kKeySampleRate, sample_rate);
+ }
+ }
+
+ // If our decoder has not be set up, do so now.
+ res = decoder_->init(substream_meta_);
+ if (OK != res) {
+ LOGE("Failed to init decoder (res = %d)", res);
+ cleanupDecoder();
+ substream_meta_ = NULL;
+ goto bailout;
+ }
+
+ // Queue the payload for decode.
+ res = decoder_->queueForDecode(buffer_in_progress_);
+
+ if (res != OK) {
+ LOGD("Failed to queue payload for decode, resetting decoder pump!"
+ " (res = %d)", res);
+ cleanupDecoder();
+ cleanupBufferInProgress();
+ }
+
+ // NULL out buffer_in_progress before calling the cleanup helper.
+ //
+ // MediaBuffers use something of a hybrid ref-counting pattern which prevent
+ // the AAH_DecoderPump's input queue from adding their own reference to the
+ // MediaBuffer. MediaBuffers start life with a reference count of 0, as
+ // well as an observer which starts as NULL. Before being given an
+ // observer, the ref count cannot be allowed to become non-zero as it will
+ // cause calls to release() to assert. Basically, before a MediaBuffer has
+ // an observer, they behave like non-ref counted obects where release()
+ // serves the roll of delete. After a MediaBuffer has an observer, they
+ // become more like ref counted objects where add ref and release can be
+ // used, and when the ref count hits zero, the MediaBuffer is handed off to
+ // the observer.
+ //
+ // Given all of this, when we give the buffer to the decoder pump to wait in
+ // the to-be-processed queue, the decoder cannot add a ref to the buffer as
+ // it would in a traditional ref counting system. Instead it needs to
+ // "steal" the non-existent ref. In the case of queue failure, we need to
+ // make certain to release this non-existent reference so that the buffer is
+ // cleaned up during the cleanupBufferInProgress helper. In the case of a
+ // successful queue operation, we need to make certain that the
+ // cleanupBufferInProgress helper does not release the buffer since it needs
+ // to remain alive in the queue. We acomplish this by NULLing out the
+ // buffer pointer before calling the cleanup helper.
+ buffer_in_progress_ = NULL;
+
+bailout:
+ cleanupBufferInProgress();
+}
+
+
+void AAH_RXPlayer::Substream::processTSTransform(const LinearTransform& trans) {
+ if (decoder_ != NULL) {
+ decoder_->setRenderTSTransform(trans);
+ }
+}
+
+bool AAH_RXPlayer::Substream::isAboutToUnderflow() {
+ if (decoder_ == NULL) {
+ return false;
+ }
+
+ return decoder_->isAboutToUnderflow(kAboutToUnderflowThreshold);
+}
+
+bool AAH_RXPlayer::Substream::setupSubstreamType(uint8_t substream_type,
+ uint8_t codec_type) {
+ // Sanity check the codec type. Right now we only support MP3. Also check
+ // for conflicts with previously delivered codec types.
+ if (substream_details_known_ && (codec_type != codec_type_)) {
+ LOGV("RXed TRTP Payload for SSRC=0x%08x where codec type (%u) does not"
+ " match previously received codec type (%u)",
+ ssrc_, codec_type, codec_type_);
+ return false;
+ }
+
+ if (codec_type != 0x03) {
+ LOGV("RXed TRTP Audio Payload for SSRC=0x%08x with unsupported codec"
+ " type (%u)", ssrc_, codec_type);
+ return false;
+ }
+
+ if (!substream_details_known_) {
+ substream_type_ = substream_type;
+ codec_type_ = codec_type;
+ substream_details_known_ = true;
+ }
+
+ return true;
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_tx_packet.cpp b/media/libaah_rtp/aah_tx_packet.cpp
new file mode 100644
index 0000000..9a3b40a
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_packet.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+#define LOG_TAG "LibAAH_RTP"
+#include <utils/Log.h>
+
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "aah_tx_packet.h"
+
+namespace android {
+
+const int TRTPPacket::kRTPHeaderLen;
+const uint32_t TRTPPacket::kTRTPEpochMask;
+
+TRTPPacket::~TRTPPacket() {
+ delete mPacket;
+}
+
+void TRTPPacket::writeU8(uint8_t*& buf, uint8_t val) {
+ *buf = val;
+ buf++;
+}
+
+void TRTPPacket::writeU16(uint8_t*& buf, uint16_t val) {
+ *reinterpret_cast<uint16_t*>(buf) = htons(val);
+ buf += 2;
+}
+
+void TRTPPacket::writeU32(uint8_t*& buf, uint32_t val) {
+ *reinterpret_cast<uint32_t*>(buf) = htonl(val);
+ buf += 4;
+}
+
+void TRTPPacket::writeU64(uint8_t*& buf, uint64_t val) {
+ buf[0] = static_cast<uint8_t>(val >> 56);
+ buf[1] = static_cast<uint8_t>(val >> 48);
+ buf[2] = static_cast<uint8_t>(val >> 40);
+ buf[3] = static_cast<uint8_t>(val >> 32);
+ buf[4] = static_cast<uint8_t>(val >> 24);
+ buf[5] = static_cast<uint8_t>(val >> 16);
+ buf[6] = static_cast<uint8_t>(val >> 8);
+ buf[7] = static_cast<uint8_t>(val);
+ buf += 8;
+}
+
+void TRTPPacket::writeTRTPHeader(uint8_t*& buf,
+ bool isFirstFragment,
+ int totalPacketLen) {
+ // RTP header
+ writeU8(buf,
+ ((mVersion & 0x03) << 6) |
+ (static_cast<int>(mPadding) << 5) |
+ (static_cast<int>(mExtension) << 4) |
+ (mCsrcCount & 0x0F));
+ writeU8(buf,
+ (static_cast<int>(isFirstFragment) << 7) |
+ (mPayloadType & 0x7F));
+ writeU16(buf, mSeqNumber);
+ if (isFirstFragment && mPTSValid) {
+ writeU32(buf, mPTS & 0xFFFFFFFF);
+ } else {
+ writeU32(buf, 0);
+ }
+ writeU32(buf,
+ ((mEpoch & kTRTPEpochMask) << 10) |
+ ((mProgramID & 0x1F) << 5) |
+ (mSubstreamID & 0x1F));
+
+ // TRTP header
+ writeU8(buf, mTRTPVersion);
+ writeU8(buf,
+ ((mTRTPHeaderType & 0x0F) << 4) |
+ (mClockTranformValid ? 0x02 : 0x00) |
+ (mPTSValid ? 0x01 : 0x00));
+ writeU32(buf, totalPacketLen - kRTPHeaderLen);
+ if (mPTSValid) {
+ writeU32(buf, mPTS >> 32);
+ }
+
+ if (mClockTranformValid) {
+ writeU64(buf, mClockTranform.a_zero);
+ writeU32(buf, mClockTranform.a_to_b_numer);
+ writeU32(buf, mClockTranform.a_to_b_denom);
+ writeU64(buf, mClockTranform.b_zero);
+ }
+}
+
+bool TRTPAudioPacket::pack() {
+ int packetLen = kRTPHeaderLen +
+ mAccessUnitLen +
+ TRTPHeaderLen();
+
+ // TODO : support multiple fragments
+ const int kMaxUDPPayloadLen = 65507;
+ if (packetLen > kMaxUDPPayloadLen) {
+ return false;
+ }
+
+ mPacket = new uint8_t[packetLen];
+ if (!mPacket) {
+ return false;
+ }
+
+ mPacketLen = packetLen;
+
+ uint8_t* cur = mPacket;
+
+ writeTRTPHeader(cur, true, packetLen);
+ writeU8(cur, mCodecType);
+ writeU8(cur,
+ (static_cast<int>(mRandomAccessPoint) << 3) |
+ (static_cast<int>(mDropable) << 2) |
+ (static_cast<int>(mDiscontinuity) << 1) |
+ (static_cast<int>(mEndOfStream)));
+ writeU8(cur, mVolume);
+
+ memcpy(cur, mAccessUnitData, mAccessUnitLen);
+
+ return true;
+}
+
+int TRTPPacket::TRTPHeaderLen() const {
+ // 6 bytes for version, payload type, flags and length. An additional 4 if
+ // there are upper timestamp bits present and another 24 if there is a clock
+ // transformation present.
+ return 6 +
+ (mClockTranformValid ? 24 : 0) +
+ (mPTSValid ? 4 : 0);
+}
+
+int TRTPAudioPacket::TRTPHeaderLen() const {
+ // TRTPPacket::TRTPHeaderLen() for the base TRTPHeader. 3 bytes for audio's
+ // codec type, flags and volume field. Another 5 bytes if the codec type is
+ // PCM and we are sending sample rate/channel count. as well as however long
+ // the aux data (if present) is.
+
+ int pcmParamLength;
+ switch(mCodecType) {
+ case kCodecPCMBigEndian:
+ case kCodecPCMLittleEndian:
+ pcmParamLength = 5;
+ break;
+
+ default:
+ pcmParamLength = 0;
+ break;
+ }
+
+
+ // TODO : properly compute aux data length. Currently, nothing
+ // uses aux data, so its length is always 0.
+ int auxDataLength = 0;
+ return TRTPPacket::TRTPHeaderLen() +
+ 3 +
+ auxDataLength +
+ pcmParamLength;
+}
+
+bool TRTPControlPacket::pack() {
+ // command packets contain a 2-byte command ID
+ int packetLen = kRTPHeaderLen +
+ TRTPHeaderLen() +
+ 2;
+
+ mPacket = new uint8_t[packetLen];
+ if (!mPacket) {
+ return false;
+ }
+
+ mPacketLen = packetLen;
+
+ uint8_t* cur = mPacket;
+
+ writeTRTPHeader(cur, true, packetLen);
+ writeU16(cur, mCommandID);
+
+ return true;
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_tx_packet.h b/media/libaah_rtp/aah_tx_packet.h
new file mode 100644
index 0000000..6e1b52a
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_packet.h
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef __AAH_TX_PACKET_H__
+#define __AAH_TX_PACKET_H__
+
+#include <utils/LinearTransform.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+class TRTPPacket : public RefBase {
+ protected:
+ enum TRTPHeaderType {
+ kHeaderTypeAudio = 1,
+ kHeaderTypeVideo = 2,
+ kHeaderTypeSubpicture = 3,
+ kHeaderTypeControl = 4,
+ };
+
+ TRTPPacket(TRTPHeaderType headerType)
+ : mVersion(2)
+ , mPadding(false)
+ , mExtension(false)
+ , mCsrcCount(0)
+ , mPayloadType(100)
+ , mSeqNumber(0)
+ , mPTSValid(false)
+ , mPTS(0)
+ , mEpoch(0)
+ , mProgramID(0)
+ , mSubstreamID(0)
+ , mClockTranformValid(false)
+ , mTRTPVersion(1)
+ , mTRTPLength(0)
+ , mTRTPHeaderType(headerType)
+ , mPacket(NULL)
+ , mPacketLen(0) { }
+
+ public:
+ virtual ~TRTPPacket();
+
+ void setSeqNumber(uint16_t val) { mSeqNumber = val; }
+ uint16_t getSeqNumber() const { return mSeqNumber; }
+ void setPTS(int64_t val) {
+ mPTS = val;
+ mPTSValid = true;
+ }
+ int64_t getPTS() const { return mPTS; }
+ void setEpoch(uint32_t val) { mEpoch = val; }
+ void setProgramID(uint16_t val) { mProgramID = val; }
+ void setSubstreamID(uint16_t val) { mSubstreamID = val; }
+ void setClockTransform(const LinearTransform& trans) {
+ mClockTranform = trans;
+ mClockTranformValid = true;
+ }
+
+ uint8_t* getPacket() const { return mPacket; }
+ int getPacketLen() const { return mPacketLen; }
+
+ void setExpireTime(nsecs_t val) { mExpireTime = val; }
+ nsecs_t getExpireTime() const { return mExpireTime; }
+
+ virtual bool pack() = 0;
+
+ // mask for the number of bits in a TRTP epoch
+ static const uint32_t kTRTPEpochMask = (1 << 22) - 1;
+
+ protected:
+ static const int kRTPHeaderLen = 12;
+ virtual int TRTPHeaderLen() const;
+
+ void writeTRTPHeader(uint8_t*& buf,
+ bool isFirstFragment,
+ int totalPacketLen);
+
+ void writeU8(uint8_t*& buf, uint8_t val);
+ void writeU16(uint8_t*& buf, uint16_t val);
+ void writeU32(uint8_t*& buf, uint32_t val);
+ void writeU64(uint8_t*& buf, uint64_t val);
+
+ uint8_t mVersion;
+ bool mPadding;
+ bool mExtension;
+ uint8_t mCsrcCount;
+ uint8_t mPayloadType;
+ uint16_t mSeqNumber;
+ bool mPTSValid;
+ int64_t mPTS;
+ uint32_t mEpoch;
+ uint16_t mProgramID;
+ uint16_t mSubstreamID;
+ LinearTransform mClockTranform;
+ bool mClockTranformValid;
+ uint8_t mTRTPVersion;
+ uint32_t mTRTPLength;
+ TRTPHeaderType mTRTPHeaderType;
+
+ uint8_t* mPacket;
+ int mPacketLen;
+
+ nsecs_t mExpireTime;
+};
+
+class TRTPAudioPacket : public TRTPPacket {
+ public:
+ TRTPAudioPacket()
+ : TRTPPacket(kHeaderTypeAudio)
+ , mCodecType(kCodecInvalid)
+ , mRandomAccessPoint(false)
+ , mDropable(false)
+ , mDiscontinuity(false)
+ , mEndOfStream(false)
+ , mVolume(0)
+ , mAccessUnitData(NULL) { }
+
+ enum TRTPAudioCodecType {
+ kCodecInvalid = 0,
+ kCodecPCMBigEndian = 1,
+ kCodecPCMLittleEndian = 2,
+ kCodecMPEG1Audio = 3,
+ };
+
+ void setCodecType(TRTPAudioCodecType val) { mCodecType = val; }
+ void setRandomAccessPoint(bool val) { mRandomAccessPoint = val; }
+ void setDropable(bool val) { mDropable = val; }
+ void setDiscontinuity(bool val) { mDiscontinuity = val; }
+ void setEndOfStream(bool val) { mEndOfStream = val; }
+ void setVolume(uint8_t val) { mVolume = val; }
+ void setAccessUnitData(void* data, int len) {
+ mAccessUnitData = data;
+ mAccessUnitLen = len;
+ }
+
+ virtual bool pack();
+
+ protected:
+ virtual int TRTPHeaderLen() const;
+
+ private:
+ TRTPAudioCodecType mCodecType;
+ bool mRandomAccessPoint;
+ bool mDropable;
+ bool mDiscontinuity;
+ bool mEndOfStream;
+ uint8_t mVolume;
+ void* mAccessUnitData;
+ int mAccessUnitLen;
+};
+
+class TRTPControlPacket : public TRTPPacket {
+ public:
+ TRTPControlPacket()
+ : TRTPPacket(kHeaderTypeControl)
+ , mCommandID(kCommandNop) {}
+
+ enum TRTPCommandID {
+ kCommandNop = 1,
+ kCommandFlush = 2,
+ kCommandEOS = 3,
+ };
+
+ void setCommandID(TRTPCommandID val) { mCommandID = val; }
+
+ virtual bool pack();
+
+ private:
+ TRTPCommandID mCommandID;
+};
+
+} // namespace android
+
+#endif // __AAH_TX_PLAYER_H__
diff --git a/media/libaah_rtp/aah_tx_player.cpp b/media/libaah_rtp/aah_tx_player.cpp
new file mode 100644
index 0000000..72d02a5
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_player.cpp
@@ -0,0 +1,1092 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "LibAAH_RTP"
+#include <utils/Log.h>
+
+#include <netdb.h>
+#include <netinet/ip.h>
+
+#include <aah_timesrv/cc_helper.h>
+#include <media/IMediaPlayer.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MetaData.h>
+#include <utils/Timers.h>
+
+#include "aah_tx_packet.h"
+#include "aah_tx_player.h"
+
+namespace android {
+
+static int64_t kLowWaterMarkUs = 2000000ll; // 2secs
+static int64_t kHighWaterMarkUs = 10000000ll; // 10secs
+static const size_t kLowWaterMarkBytes = 40000;
+static const size_t kHighWaterMarkBytes = 200000;
+
+// When we start up, how much lead time should we put on the first access unit?
+static const int64_t kAAHStartupLeadTimeUs = 300000LL;
+
+// How much time do we attempt to lead the clock by in steady state?
+static const int64_t kAAHBufferTimeUs = 1000000LL;
+
+// how long do we keep data in our retransmit buffer after sending it.
+const int64_t AAH_TXPlayer::kAAHRetryKeepAroundTimeNs =
+ kAAHBufferTimeUs * 1100;
+
+sp<MediaPlayerBase> createAAH_TXPlayer() {
+ sp<MediaPlayerBase> ret = new AAH_TXPlayer();
+ return ret;
+}
+
+template <typename T> static T clamp(T val, T min, T max) {
+ if (val < min) {
+ return min;
+ } else if (val > max) {
+ return max;
+ } else {
+ return val;
+ }
+}
+
+struct AAH_TXEvent : public TimedEventQueue::Event {
+ AAH_TXEvent(AAH_TXPlayer *player,
+ void (AAH_TXPlayer::*method)()) : mPlayer(player)
+ , mMethod(method) {}
+
+ protected:
+ virtual ~AAH_TXEvent() {}
+
+ virtual void fire(TimedEventQueue *queue, int64_t /* now_us */) {
+ (mPlayer->*mMethod)();
+ }
+
+ private:
+ AAH_TXPlayer *mPlayer;
+ void (AAH_TXPlayer::*mMethod)();
+
+ AAH_TXEvent(const AAH_TXEvent &);
+ AAH_TXEvent& operator=(const AAH_TXEvent &);
+};
+
+AAH_TXPlayer::AAH_TXPlayer()
+ : mQueueStarted(false)
+ , mFlags(0)
+ , mExtractorFlags(0) {
+ DataSource::RegisterDefaultSniffers();
+
+ mBufferingEvent = new AAH_TXEvent(this, &AAH_TXPlayer::onBufferingUpdate);
+ mBufferingEventPending = false;
+
+ mPumpAudioEvent = new AAH_TXEvent(this, &AAH_TXPlayer::onPumpAudio);
+ mPumpAudioEventPending = false;
+
+ reset();
+}
+
+AAH_TXPlayer::~AAH_TXPlayer() {
+ if (mQueueStarted) {
+ mQueue.stop();
+ }
+
+ reset();
+}
+
+void AAH_TXPlayer::cancelPlayerEvents(bool keepBufferingGoing) {
+ if (!keepBufferingGoing) {
+ mQueue.cancelEvent(mBufferingEvent->eventID());
+ mBufferingEventPending = false;
+
+ mQueue.cancelEvent(mPumpAudioEvent->eventID());
+ mPumpAudioEventPending = false;
+ }
+}
+
+status_t AAH_TXPlayer::initCheck() {
+ // Check for the presense of the A@H common time service by attempting to
+ // query for CommonTime's frequency. If we get an error back, we cannot
+ // talk to the service at all and should abort now.
+ status_t res;
+ uint64_t freq;
+ res = CCHelper::getCommonFreq(&freq);
+ if (OK != res) {
+ LOGE("Failed to connect to common time service!");
+ return res;
+ }
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::setDataSource(
+ const char *url,
+ const KeyedVector<String8, String8> *headers) {
+ Mutex::Autolock autoLock(mLock);
+ return setDataSource_l(url, headers);
+}
+
+status_t AAH_TXPlayer::setDataSource_l(
+ const char *url,
+ const KeyedVector<String8, String8> *headers) {
+ reset_l();
+
+ // the URL must consist of "aahTX://" followed by the real URL of
+ // the data source
+ const char *kAAHPrefix = "aahTX://";
+ if (strncasecmp(url, kAAHPrefix, strlen(kAAHPrefix))) {
+ return INVALID_OPERATION;
+ }
+
+ mUri.setTo(url + strlen(kAAHPrefix));
+
+ if (headers) {
+ mUriHeaders = *headers;
+
+ ssize_t index = mUriHeaders.indexOfKey(String8("x-hide-urls-from-log"));
+ if (index >= 0) {
+ // Browser is in "incognito" mode, suppress logging URLs.
+
+ // This isn't something that should be passed to the server.
+ mUriHeaders.removeItemsAt(index);
+
+ mFlags |= INCOGNITO;
+ }
+ }
+
+ // The URL may optionally contain a "#" character followed by a Skyjam
+ // cookie. Ideally the cookie header should just be passed in the headers
+ // argument, but the Java API for supplying headers is apparently not yet
+ // exposed in the SDK used by application developers.
+ const char kSkyjamCookieDelimiter = '#';
+ char* skyjamCookie = strrchr(mUri.string(), kSkyjamCookieDelimiter);
+ if (skyjamCookie) {
+ skyjamCookie++;
+ mUriHeaders.add(String8("Cookie"), String8(skyjamCookie));
+ mUri = String8(mUri.string(), skyjamCookie - mUri.string());
+ }
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::setDataSource(int fd, int64_t offset, int64_t length) {
+ return INVALID_OPERATION;
+}
+
+status_t AAH_TXPlayer::setVideoSurface(const sp<Surface>& surface) {
+ return OK;
+}
+
+status_t AAH_TXPlayer::setVideoSurfaceTexture(
+ const sp<ISurfaceTexture>& surfaceTexture) {
+ return OK;
+}
+
+status_t AAH_TXPlayer::prepare() {
+ return INVALID_OPERATION;
+}
+
+status_t AAH_TXPlayer::prepareAsync() {
+ Mutex::Autolock autoLock(mLock);
+
+ return prepareAsync_l();
+}
+
+status_t AAH_TXPlayer::prepareAsync_l() {
+ if (mFlags & PREPARING) {
+ return UNKNOWN_ERROR; // async prepare already pending
+ }
+
+ mAAH_Sender = AAH_TXSender::GetInstance();
+ if (mAAH_Sender == NULL) {
+ return NO_MEMORY;
+ }
+
+ if (!mQueueStarted) {
+ mQueue.start();
+ mQueueStarted = true;
+ }
+
+ mFlags |= PREPARING;
+ mAsyncPrepareEvent = new AAH_TXEvent(
+ this, &AAH_TXPlayer::onPrepareAsyncEvent);
+
+ mQueue.postEvent(mAsyncPrepareEvent);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::finishSetDataSource_l() {
+ sp<DataSource> dataSource;
+
+ if (!strncasecmp("http://", mUri.string(), 7) ||
+ !strncasecmp("https://", mUri.string(), 8)) {
+
+ mConnectingDataSource = HTTPBase::Create(
+ (mFlags & INCOGNITO)
+ ? HTTPBase::kFlagIncognito
+ : 0);
+
+ mLock.unlock();
+ status_t err = mConnectingDataSource->connect(mUri, &mUriHeaders);
+ mLock.lock();
+
+ if (err != OK) {
+ mConnectingDataSource.clear();
+
+ LOGI("mConnectingDataSource->connect() returned %d", err);
+ return err;
+ }
+
+ mCachedSource = new NuCachedSource2(mConnectingDataSource);
+ mConnectingDataSource.clear();
+
+ dataSource = mCachedSource;
+
+ // We're going to prefill the cache before trying to instantiate
+ // the extractor below, as the latter is an operation that otherwise
+ // could block on the datasource for a significant amount of time.
+ // During that time we'd be unable to abort the preparation phase
+ // without this prefill.
+
+ mLock.unlock();
+
+ for (;;) {
+ status_t finalStatus;
+ size_t cachedDataRemaining =
+ mCachedSource->approxDataRemaining(&finalStatus);
+
+ if (finalStatus != OK ||
+ cachedDataRemaining >= kHighWaterMarkBytes ||
+ (mFlags & PREPARE_CANCELLED)) {
+ break;
+ }
+
+ usleep(200000);
+ }
+
+ mLock.lock();
+
+ if (mFlags & PREPARE_CANCELLED) {
+ LOGI("Prepare cancelled while waiting for initial cache fill.");
+ return UNKNOWN_ERROR;
+ }
+ } else {
+ dataSource = DataSource::CreateFromURI(mUri.string(), &mUriHeaders);
+ }
+
+ if (dataSource == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);
+
+ if (extractor == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ return setDataSource_l(extractor);
+}
+
+status_t AAH_TXPlayer::setDataSource_l(const sp<MediaExtractor> &extractor) {
+ // Attempt to approximate overall stream bitrate by summing all
+ // tracks' individual bitrates, if not all of them advertise bitrate,
+ // we have to fail.
+
+ int64_t totalBitRate = 0;
+
+ for (size_t i = 0; i < extractor->countTracks(); ++i) {
+ sp<MetaData> meta = extractor->getTrackMetaData(i);
+
+ int32_t bitrate;
+ if (!meta->findInt32(kKeyBitRate, &bitrate)) {
+ totalBitRate = -1;
+ break;
+ }
+
+ totalBitRate += bitrate;
+ }
+
+ mBitrate = totalBitRate;
+
+ LOGV("mBitrate = %lld bits/sec", mBitrate);
+
+ bool haveAudio = false;
+ for (size_t i = 0; i < extractor->countTracks(); ++i) {
+ sp<MetaData> meta = extractor->getTrackMetaData(i);
+
+ const char *mime;
+ CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+ if (!strncasecmp(mime, "audio/", 6)) {
+ mAudioSource = extractor->getTrack(i);
+ CHECK(mAudioSource != NULL);
+ haveAudio = true;
+ break;
+ }
+ }
+
+ if (!haveAudio) {
+ return UNKNOWN_ERROR;
+ }
+
+ mExtractorFlags = extractor->flags();
+
+ return OK;
+}
+
+void AAH_TXPlayer::abortPrepare(status_t err) {
+ CHECK(err != OK);
+
+ notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err);
+
+ mPrepareResult = err;
+ mFlags &= ~(PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED);
+ mPreparedCondition.broadcast();
+}
+
+void AAH_TXPlayer::onPrepareAsyncEvent() {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mFlags & PREPARE_CANCELLED) {
+ LOGI("prepare was cancelled before doing anything");
+ abortPrepare(UNKNOWN_ERROR);
+ return;
+ }
+
+ if (mUri.size() > 0) {
+ status_t err = finishSetDataSource_l();
+
+ if (err != OK) {
+ abortPrepare(err);
+ return;
+ }
+ }
+
+ mAudioSource->getFormat()->findInt64(kKeyDuration, &mDurationUs);
+
+ mAudioSource->start();
+
+ mFlags |= PREPARING_CONNECTED;
+
+ if (mCachedSource != NULL) {
+ postBufferingEvent_l();
+ } else {
+ finishAsyncPrepare_l();
+ }
+}
+
+void AAH_TXPlayer::finishAsyncPrepare_l() {
+ notifyListener_l(MEDIA_PREPARED);
+
+ mPrepareResult = OK;
+ mFlags &= ~(PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED);
+ mFlags |= PREPARED;
+ mPreparedCondition.broadcast();
+}
+
+status_t AAH_TXPlayer::start() {
+ Mutex::Autolock autoLock(mLock);
+
+ mFlags &= ~CACHE_UNDERRUN;
+
+ return play_l();
+}
+
+status_t AAH_TXPlayer::play_l() {
+ if (mFlags & PLAYING) {
+ return OK;
+ }
+
+ if (!(mFlags & PREPARED)) {
+ return INVALID_OPERATION;
+ }
+
+ {
+ Mutex::Autolock lock(mEndpointLock);
+ if (!mEndpointValid) {
+ return INVALID_OPERATION;
+ }
+ if (!mEndpointRegistered) {
+ mProgramID = mAAH_Sender->registerEndpoint(mEndpoint);
+ mEndpointRegistered = true;
+ }
+ }
+
+ mFlags |= PLAYING;
+
+ updateClockTransform_l(false);
+
+ postPumpAudioEvent_l(-1);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::stop() {
+ status_t ret = pause();
+ sendEOS_l();
+ return ret;
+}
+
+status_t AAH_TXPlayer::pause() {
+ Mutex::Autolock autoLock(mLock);
+
+ mFlags &= ~CACHE_UNDERRUN;
+
+ return pause_l();
+}
+
+status_t AAH_TXPlayer::pause_l(bool doClockUpdate) {
+ if (!(mFlags & PLAYING)) {
+ return OK;
+ }
+
+ cancelPlayerEvents(true /* keepBufferingGoing */);
+
+ mFlags &= ~PLAYING;
+
+ if (doClockUpdate) {
+ updateClockTransform_l(true);
+ }
+
+ return OK;
+}
+
+void AAH_TXPlayer::updateClockTransform_l(bool pause) {
+ // record the new pause status so that onPumpAudio knows what rate to apply
+ // when it initializes the transform
+ mPlayRateIsPaused = pause;
+
+ // if we haven't yet established a valid clock transform, then we can't
+ // do anything here
+ if (!mCurrentClockTransformValid) {
+ return;
+ }
+
+ // sample the current common time
+ int64_t commonTimeNow;
+ if (OK != CCHelper::getCommonTime(&commonTimeNow)) {
+ LOGE("updateClockTransform_l get common time failed");
+ mCurrentClockTransformValid = false;
+ return;
+ }
+
+ // convert the current common time to media time using the old
+ // transform
+ int64_t mediaTimeNow;
+ if (!mCurrentClockTransform.doReverseTransform(
+ commonTimeNow, &mediaTimeNow)) {
+ LOGE("updateClockTransform_l reverse transform failed");
+ mCurrentClockTransformValid = false;
+ return;
+ }
+
+ // calculate a new transform that preserves the old transform's
+ // result for the current time
+ mCurrentClockTransform.a_zero = mediaTimeNow;
+ mCurrentClockTransform.b_zero = commonTimeNow;
+ mCurrentClockTransform.a_to_b_numer = 1;
+ mCurrentClockTransform.a_to_b_denom = pause ? 0 : 1;
+
+ // send a packet announcing the new transform
+ sp<TRTPControlPacket> packet = new TRTPControlPacket();
+ packet->setClockTransform(mCurrentClockTransform);
+ packet->setCommandID(TRTPControlPacket::kCommandNop);
+ queuePacketToSender_l(packet);
+}
+
+void AAH_TXPlayer::sendEOS_l() {
+ sp<TRTPControlPacket> packet = new TRTPControlPacket();
+ packet->setCommandID(TRTPControlPacket::kCommandEOS);
+ queuePacketToSender_l(packet);
+}
+
+bool AAH_TXPlayer::isPlaying() {
+ return (mFlags & PLAYING) || (mFlags & CACHE_UNDERRUN);
+}
+
+status_t AAH_TXPlayer::seekTo(int msec) {
+ if (mExtractorFlags & MediaExtractor::CAN_SEEK) {
+ Mutex::Autolock autoLock(mLock);
+ return seekTo_l(static_cast<int64_t>(msec) * 1000);
+ }
+
+ notifyListener_l(MEDIA_SEEK_COMPLETE);
+ return OK;
+}
+
+status_t AAH_TXPlayer::seekTo_l(int64_t timeUs) {
+ mIsSeeking = true;
+ mSeekTimeUs = timeUs;
+
+ mCurrentClockTransformValid = false;
+ mLastQueuedMediaTimePTSValid = false;
+
+ // send a flush command packet
+ sp<TRTPControlPacket> packet = new TRTPControlPacket();
+ packet->setCommandID(TRTPControlPacket::kCommandFlush);
+ queuePacketToSender_l(packet);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::getCurrentPosition(int *msec) {
+ if (!msec) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mLock);
+
+ int position;
+
+ if (mIsSeeking) {
+ position = mSeekTimeUs / 1000;
+ } else if (mCurrentClockTransformValid) {
+ // sample the current common time
+ int64_t commonTimeNow;
+ if (OK != CCHelper::getCommonTime(&commonTimeNow)) {
+ LOGE("getCurrentPosition get common time failed");
+ return INVALID_OPERATION;
+ }
+
+ int64_t mediaTimeNow;
+ if (!mCurrentClockTransform.doReverseTransform(commonTimeNow,
+ &mediaTimeNow)) {
+ LOGE("getCurrentPosition reverse transform failed");
+ return INVALID_OPERATION;
+ }
+
+ position = static_cast<int>(mediaTimeNow / 1000);
+ } else {
+ position = 0;
+ }
+
+ int duration;
+ if (getDuration_l(&duration) == OK) {
+ *msec = clamp(position, 0, duration);
+ } else {
+ *msec = (position >= 0) ? position : 0;
+ }
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::getDuration(int* msec) {
+ if (!msec) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mLock);
+
+ return getDuration_l(msec);
+}
+
+status_t AAH_TXPlayer::getDuration_l(int* msec) {
+ if (mDurationUs < 0) {
+ return UNKNOWN_ERROR;
+ }
+
+ *msec = (mDurationUs + 500) / 1000;
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::reset() {
+ Mutex::Autolock autoLock(mLock);
+ reset_l();
+ return OK;
+}
+
+void AAH_TXPlayer::reset_l() {
+ if (mFlags & PREPARING) {
+ mFlags |= PREPARE_CANCELLED;
+ if (mConnectingDataSource != NULL) {
+ LOGI("interrupting the connection process");
+ mConnectingDataSource->disconnect();
+ }
+
+ if (mFlags & PREPARING_CONNECTED) {
+ // We are basically done preparing, we're just buffering
+ // enough data to start playback, we can safely interrupt that.
+ finishAsyncPrepare_l();
+ }
+ }
+
+ while (mFlags & PREPARING) {
+ mPreparedCondition.wait(mLock);
+ }
+
+ cancelPlayerEvents();
+
+ sendEOS_l();
+
+ mCachedSource.clear();
+
+ if (mAudioSource != NULL) {
+ mAudioSource->stop();
+ }
+ mAudioSource.clear();
+
+ mFlags = 0;
+ mExtractorFlags = 0;
+
+ mDurationUs = -1;
+ mIsSeeking = false;
+ mSeekTimeUs = 0;
+
+ mUri.setTo("");
+ mUriHeaders.clear();
+
+ mBitrate = -1;
+
+ {
+ Mutex::Autolock lock(mEndpointLock);
+ if (mAAH_Sender != NULL && mEndpointRegistered) {
+ mAAH_Sender->unregisterEndpoint(mEndpoint);
+ }
+ mEndpointRegistered = false;
+ mEndpointValid = false;
+ }
+
+ mProgramID = 0;
+
+ mAAH_Sender.clear();
+ mLastQueuedMediaTimePTSValid = false;
+ mCurrentClockTransformValid = false;
+ mPlayRateIsPaused = false;
+
+ mTRTPVolume = 255;
+}
+
+status_t AAH_TXPlayer::setLooping(int loop) {
+ return OK;
+}
+
+player_type AAH_TXPlayer::playerType() {
+ return AAH_TX_PLAYER;
+}
+
+status_t AAH_TXPlayer::setParameter(int key, const Parcel &request) {
+ return ERROR_UNSUPPORTED;
+}
+
+status_t AAH_TXPlayer::getParameter(int key, Parcel *reply) {
+ return ERROR_UNSUPPORTED;
+}
+
+status_t AAH_TXPlayer::invoke(const Parcel& request, Parcel *reply) {
+ if (!reply) {
+ return BAD_VALUE;
+ }
+
+ int32_t methodID;
+ status_t err = request.readInt32(&methodID);
+ if (err != android::OK) {
+ return err;
+ }
+
+ switch (methodID) {
+ case kInvokeSetAAHDstIPPort: {
+ if (mEndpointValid) {
+ return INVALID_OPERATION;
+ }
+
+ String16 addr = request.readString16();
+
+ int32_t port32;
+ err = request.readInt32(&port32);
+ if (err != android::OK) {
+ return err;
+ }
+
+ struct hostent* ent = gethostbyname(String8(addr).string());
+ if (ent == NULL) {
+ return ERROR_UNKNOWN_HOST;
+ }
+ if (!(ent->h_addrtype == AF_INET && ent->h_length == 4)) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mEndpointLock);
+ mEndpoint = AAH_TXSender::Endpoint(
+ reinterpret_cast<struct in_addr*>(ent->h_addr)->s_addr,
+ static_cast<uint16_t>(port32));
+ mEndpointValid = true;
+ return OK;
+ };
+
+ default:
+ return INVALID_OPERATION;
+ }
+}
+
+status_t AAH_TXPlayer::getMetadata(const media::Metadata::Filter& ids,
+ Parcel* records) {
+ using media::Metadata;
+
+ Metadata metadata(records);
+
+ metadata.appendBool(Metadata::kPauseAvailable, true);
+ metadata.appendBool(Metadata::kSeekBackwardAvailable, false);
+ metadata.appendBool(Metadata::kSeekForwardAvailable, false);
+ metadata.appendBool(Metadata::kSeekAvailable, false);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::setVolume(float leftVolume, float rightVolume) {
+ if (leftVolume != rightVolume) {
+ LOGE("%s does not support per channel volume: %f, %f",
+ __PRETTY_FUNCTION__, leftVolume, rightVolume);
+ }
+
+ float volume = clamp(leftVolume, 0.0f, 1.0f);
+
+ Mutex::Autolock lock(mLock);
+ mTRTPVolume = static_cast<uint8_t>((leftVolume * 255.0) + 0.5);
+
+ return OK;
+}
+
+status_t AAH_TXPlayer::setAudioStreamType(int streamType) {
+ return OK;
+}
+
+void AAH_TXPlayer::notifyListener_l(int msg, int ext1, int ext2) {
+ sendEvent(msg, ext1, ext2);
+}
+
+bool AAH_TXPlayer::getBitrate_l(int64_t *bitrate) {
+ off64_t size;
+ if (mDurationUs >= 0 &&
+ mCachedSource != NULL &&
+ mCachedSource->getSize(&size) == OK) {
+ *bitrate = size * 8000000ll / mDurationUs; // in bits/sec
+ return true;
+ }
+
+ if (mBitrate >= 0) {
+ *bitrate = mBitrate;
+ return true;
+ }
+
+ *bitrate = 0;
+
+ return false;
+}
+
+// Returns true iff cached duration is available/applicable.
+bool AAH_TXPlayer::getCachedDuration_l(int64_t *durationUs, bool *eos) {
+ int64_t bitrate;
+
+ if (mCachedSource != NULL && getBitrate_l(&bitrate)) {
+ status_t finalStatus;
+ size_t cachedDataRemaining = mCachedSource->approxDataRemaining(
+ &finalStatus);
+ *durationUs = cachedDataRemaining * 8000000ll / bitrate;
+ *eos = (finalStatus != OK);
+ return true;
+ }
+
+ return false;
+}
+
+void AAH_TXPlayer::ensureCacheIsFetching_l() {
+ if (mCachedSource != NULL) {
+ mCachedSource->resumeFetchingIfNecessary();
+ }
+}
+
+void AAH_TXPlayer::postBufferingEvent_l() {
+ if (mBufferingEventPending) {
+ return;
+ }
+ mBufferingEventPending = true;
+ mQueue.postEventWithDelay(mBufferingEvent, 1000000ll);
+}
+
+void AAH_TXPlayer::postPumpAudioEvent_l(int64_t delayUs) {
+ if (mPumpAudioEventPending) {
+ return;
+ }
+ mPumpAudioEventPending = true;
+ mQueue.postEventWithDelay(mPumpAudioEvent, delayUs < 0 ? 10000 : delayUs);
+}
+
+void AAH_TXPlayer::onBufferingUpdate() {
+ Mutex::Autolock autoLock(mLock);
+ if (!mBufferingEventPending) {
+ return;
+ }
+ mBufferingEventPending = false;
+
+ if (mCachedSource != NULL) {
+ status_t finalStatus;
+ size_t cachedDataRemaining = mCachedSource->approxDataRemaining(
+ &finalStatus);
+ bool eos = (finalStatus != OK);
+
+ if (eos) {
+ if (finalStatus == ERROR_END_OF_STREAM) {
+ notifyListener_l(MEDIA_BUFFERING_UPDATE, 100);
+ }
+ if (mFlags & PREPARING) {
+ LOGV("cache has reached EOS, prepare is done.");
+ finishAsyncPrepare_l();
+ }
+ } else {
+ int64_t bitrate;
+ if (getBitrate_l(&bitrate)) {
+ size_t cachedSize = mCachedSource->cachedSize();
+ int64_t cachedDurationUs = cachedSize * 8000000ll / bitrate;
+
+ int percentage = (100.0 * (double) cachedDurationUs)
+ / mDurationUs;
+ if (percentage > 100) {
+ percentage = 100;
+ }
+
+ notifyListener_l(MEDIA_BUFFERING_UPDATE, percentage);
+ } else {
+ // We don't know the bitrate of the stream, use absolute size
+ // limits to maintain the cache.
+
+ if ((mFlags & PLAYING) &&
+ !eos &&
+ (cachedDataRemaining < kLowWaterMarkBytes)) {
+ LOGI("cache is running low (< %d) , pausing.",
+ kLowWaterMarkBytes);
+ mFlags |= CACHE_UNDERRUN;
+ pause_l();
+ ensureCacheIsFetching_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_START);
+ } else if (eos || cachedDataRemaining > kHighWaterMarkBytes) {
+ if (mFlags & CACHE_UNDERRUN) {
+ LOGI("cache has filled up (> %d), resuming.",
+ kHighWaterMarkBytes);
+ mFlags &= ~CACHE_UNDERRUN;
+ play_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_END);
+ } else if (mFlags & PREPARING) {
+ LOGV("cache has filled up (> %d), prepare is done",
+ kHighWaterMarkBytes);
+ finishAsyncPrepare_l();
+ }
+ }
+ }
+ }
+ }
+
+ int64_t cachedDurationUs;
+ bool eos;
+ if (getCachedDuration_l(&cachedDurationUs, &eos)) {
+ LOGV("cachedDurationUs = %.2f secs, eos=%d",
+ cachedDurationUs / 1E6, eos);
+
+ if ((mFlags & PLAYING) &&
+ !eos &&
+ (cachedDurationUs < kLowWaterMarkUs)) {
+ LOGI("cache is running low (%.2f secs) , pausing.",
+ cachedDurationUs / 1E6);
+ mFlags |= CACHE_UNDERRUN;
+ pause_l();
+ ensureCacheIsFetching_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_START);
+ } else if (eos || cachedDurationUs > kHighWaterMarkUs) {
+ if (mFlags & CACHE_UNDERRUN) {
+ LOGI("cache has filled up (%.2f secs), resuming.",
+ cachedDurationUs / 1E6);
+ mFlags &= ~CACHE_UNDERRUN;
+ play_l();
+ notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_END);
+ } else if (mFlags & PREPARING) {
+ LOGV("cache has filled up (%.2f secs), prepare is done",
+ cachedDurationUs / 1E6);
+ finishAsyncPrepare_l();
+ }
+ }
+ }
+
+ postBufferingEvent_l();
+}
+
+void AAH_TXPlayer::onPumpAudio() {
+ while (true) {
+ Mutex::Autolock autoLock(mLock);
+ // If this flag is clear, its because someone has externally canceled
+ // this pump operation (probably because we a resetting/shutting down).
+ // Get out immediately, do not reschedule ourselves.
+ if (!mPumpAudioEventPending) {
+ return;
+ }
+
+ // Start by checking if there is still work to be doing. If we have
+ // never queued a payload (so we don't know what the last queued PTS is)
+ // or we have never established a MediaTime->CommonTime transformation,
+ // then we have work to do (one time through this loop should establish
+ // both). Otherwise, we want to keep a fixed amt of presentation time
+ // worth of data buffered. If we cannot get common time (service is
+ // unavailable, or common time is undefined)) then we don't have a lot
+ // of good options here. For now, signal an error up to the app level
+ // and shut down the transmission pump.
+ int64_t commonTimeNow;
+ if (OK != CCHelper::getCommonTime(&commonTimeNow)) {
+ // Failed to get common time; either the service is down or common
+ // time is not synced. Raise an error and shutdown the player.
+ LOGE("*** Cannot pump audio, unable to fetch common time."
+ " Shutting down.");
+ notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, UNKNOWN_ERROR);
+ mPumpAudioEventPending = false;
+ break;
+ }
+
+ if (mCurrentClockTransformValid && mLastQueuedMediaTimePTSValid) {
+ int64_t mediaTimeNow;
+ bool conversionResult = mCurrentClockTransform.doReverseTransform(
+ commonTimeNow,
+ &mediaTimeNow);
+ CHECK(conversionResult);
+
+ if ((mediaTimeNow +
+ kAAHBufferTimeUs -
+ mLastQueuedMediaTimePTS) <= 0) {
+ break;
+ }
+ }
+
+ MediaSource::ReadOptions options;
+ if (mIsSeeking) {
+ options.setSeekTo(mSeekTimeUs);
+ }
+
+ MediaBuffer* mediaBuffer;
+ status_t err = mAudioSource->read(&mediaBuffer, &options);
+ if (err != NO_ERROR) {
+ if (err == ERROR_END_OF_STREAM) {
+ LOGI("*** %s reached end of stream", __PRETTY_FUNCTION__);
+ notifyListener_l(MEDIA_BUFFERING_UPDATE, 100);
+ notifyListener_l(MEDIA_PLAYBACK_COMPLETE);
+ pause_l(false);
+ sendEOS_l();
+ } else {
+ LOGE("*** %s read failed err=%d", __PRETTY_FUNCTION__, err);
+ }
+ return;
+ }
+
+ if (mIsSeeking) {
+ mIsSeeking = false;
+ notifyListener_l(MEDIA_SEEK_COMPLETE);
+ }
+
+ uint8_t* data = (static_cast<uint8_t*>(mediaBuffer->data()) +
+ mediaBuffer->range_offset());
+ LOGV("*** %s got media buffer data=[%02hhx %02hhx %02hhx %02hhx]"
+ " offset=%d length=%d", __PRETTY_FUNCTION__,
+ data[0], data[1], data[2], data[3],
+ mediaBuffer->range_offset(), mediaBuffer->range_length());
+
+ int64_t mediaTimeUs;
+ CHECK(mediaBuffer->meta_data()->findInt64(kKeyTime, &mediaTimeUs));
+ LOGV("*** timeUs=%lld", mediaTimeUs);
+
+ if (!mCurrentClockTransformValid) {
+ if (OK == CCHelper::getCommonTime(&commonTimeNow)) {
+ mCurrentClockTransform.a_zero = mediaTimeUs;
+ mCurrentClockTransform.b_zero = commonTimeNow +
+ kAAHStartupLeadTimeUs;
+ mCurrentClockTransform.a_to_b_numer = 1;
+ mCurrentClockTransform.a_to_b_denom = mPlayRateIsPaused ? 0 : 1;
+ mCurrentClockTransformValid = true;
+ } else {
+ // Failed to get common time; either the service is down or
+ // common time is not synced. Raise an error and shutdown the
+ // player.
+ LOGE("*** Cannot begin transmission, unable to fetch common"
+ " time. Dropping sample with pts=%lld", mediaTimeUs);
+ notifyListener_l(MEDIA_ERROR,
+ MEDIA_ERROR_UNKNOWN,
+ UNKNOWN_ERROR);
+ mPumpAudioEventPending = false;
+ break;
+ }
+ }
+
+ LOGV("*** transmitting packet with pts=%lld", mediaTimeUs);
+
+ sp<TRTPAudioPacket> packet = new TRTPAudioPacket();
+ packet->setPTS(mediaTimeUs);
+ packet->setSubstreamID(1);
+
+ packet->setCodecType(TRTPAudioPacket::kCodecMPEG1Audio);
+ packet->setVolume(mTRTPVolume);
+ // TODO : introduce a throttle for this so we can control the
+ // frequency with which transforms get sent.
+ packet->setClockTransform(mCurrentClockTransform);
+ packet->setAccessUnitData(data, mediaBuffer->range_length());
+ packet->setRandomAccessPoint(true);
+
+ queuePacketToSender_l(packet);
+ mediaBuffer->release();
+
+ mLastQueuedMediaTimePTSValid = true;
+ mLastQueuedMediaTimePTS = mediaTimeUs;
+ }
+
+ { // Explicit scope for the autolock pattern.
+ Mutex::Autolock autoLock(mLock);
+
+ // If someone externally has cleared this flag, its because we should be
+ // shutting down. Do not reschedule ourselves.
+ if (!mPumpAudioEventPending) {
+ return;
+ }
+
+ // Looks like no one canceled us explicitly. Clear our flag and post a
+ // new event to ourselves.
+ mPumpAudioEventPending = false;
+ postPumpAudioEvent_l(10000);
+ }
+}
+
+void AAH_TXPlayer::queuePacketToSender_l(const sp<TRTPPacket>& packet) {
+ if (mAAH_Sender == NULL) {
+ return;
+ }
+
+ sp<AMessage> message = new AMessage(AAH_TXSender::kWhatSendPacket,
+ mAAH_Sender->handlerID());
+
+ {
+ Mutex::Autolock lock(mEndpointLock);
+ if (!mEndpointValid) {
+ return;
+ }
+
+ mAAH_Sender->assignSeqNumber(mEndpoint, packet);
+ message->setInt32(AAH_TXSender::kSendPacketIPAddr, mEndpoint.addr);
+ message->setInt32(AAH_TXSender::kSendPacketPort, mEndpoint.port);
+ }
+
+ packet->setProgramID(mProgramID);
+ packet->setExpireTime(systemTime() + kAAHRetryKeepAroundTimeNs);
+ packet->pack();
+
+ message->setObject(AAH_TXSender::kSendPacketTRTPPacket, packet);
+
+ message->post();
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_tx_player.h b/media/libaah_rtp/aah_tx_player.h
new file mode 100644
index 0000000..190b432
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_player.h
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef __AAH_TX_PLAYER_H__
+#define __AAH_TX_PLAYER_H__
+
+#include <libstagefright/include/HTTPBase.h>
+#include <libstagefright/include/NuCachedSource2.h>
+#include <libstagefright/include/TimedEventQueue.h>
+#include <media/MediaPlayerInterface.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MediaSource.h>
+#include <utils/LinearTransform.h>
+#include <utils/String8.h>
+#include <utils/threads.h>
+
+#include "aah_tx_sender.h"
+
+namespace android {
+
+class AAH_TXPlayer : public MediaPlayerHWInterface {
+ public:
+ AAH_TXPlayer();
+
+ virtual status_t initCheck();
+ virtual status_t setDataSource(const char *url,
+ const KeyedVector<String8, String8>*
+ headers);
+ virtual status_t setDataSource(int fd, int64_t offset, int64_t length);
+ virtual status_t setVideoSurface(const sp<Surface>& surface);
+ virtual status_t setVideoSurfaceTexture(const sp<ISurfaceTexture>&
+ surfaceTexture);
+ virtual status_t prepare();
+ virtual status_t prepareAsync();
+ virtual status_t start();
+ virtual status_t stop();
+ virtual status_t pause();
+ virtual bool isPlaying();
+ virtual status_t seekTo(int msec);
+ virtual status_t getCurrentPosition(int *msec);
+ virtual status_t getDuration(int *msec);
+ virtual status_t reset();
+ virtual status_t setLooping(int loop);
+ virtual player_type playerType();
+ virtual status_t setParameter(int key, const Parcel &request);
+ virtual status_t getParameter(int key, Parcel *reply);
+ virtual status_t invoke(const Parcel& request, Parcel *reply);
+ virtual status_t getMetadata(const media::Metadata::Filter& ids,
+ Parcel* records);
+ virtual status_t setVolume(float leftVolume, float rightVolume);
+ virtual status_t setAudioStreamType(int streamType);
+
+ // invoke method IDs
+ enum {
+ // set the IP address and port of the A@H receiver
+ kInvokeSetAAHDstIPPort = 1,
+ };
+
+ static const int64_t kAAHRetryKeepAroundTimeNs;
+
+ protected:
+ virtual ~AAH_TXPlayer();
+
+ private:
+ friend struct AwesomeEvent;
+
+ enum {
+ PLAYING = 1,
+ PREPARING = 8,
+ PREPARED = 16,
+ PREPARE_CANCELLED = 64,
+ CACHE_UNDERRUN = 128,
+
+ // We are basically done preparing but are currently buffering
+ // sufficient data to begin playback and finish the preparation
+ // phase for good.
+ PREPARING_CONNECTED = 2048,
+
+ INCOGNITO = 32768,
+ };
+
+ status_t setDataSource_l(const char *url,
+ const KeyedVector<String8, String8> *headers);
+ status_t setDataSource_l(const sp<MediaExtractor>& extractor);
+ status_t finishSetDataSource_l();
+ status_t prepareAsync_l();
+ void onPrepareAsyncEvent();
+ void finishAsyncPrepare_l();
+ void abortPrepare(status_t err);
+ status_t play_l();
+ status_t pause_l(bool doClockUpdate = true);
+ status_t seekTo_l(int64_t timeUs);
+ void updateClockTransform_l(bool pause);
+ void sendEOS_l();
+ void cancelPlayerEvents(bool keepBufferingGoing = false);
+ void reset_l();
+ void notifyListener_l(int msg, int ext1 = 0, int ext2 = 0);
+ bool getBitrate_l(int64_t* bitrate);
+ status_t getDuration_l(int* msec);
+ bool getCachedDuration_l(int64_t* durationUs, bool* eos);
+ void ensureCacheIsFetching_l();
+ void postBufferingEvent_l();
+ void postPumpAudioEvent_l(int64_t delayUs);
+ void onBufferingUpdate();
+ void onPumpAudio();
+ void queuePacketToSender_l(const sp<TRTPPacket>& packet);
+
+ Mutex mLock;
+
+ TimedEventQueue mQueue;
+ bool mQueueStarted;
+
+ sp<TimedEventQueue::Event> mBufferingEvent;
+ bool mBufferingEventPending;
+
+ uint32_t mFlags;
+ uint32_t mExtractorFlags;
+
+ String8 mUri;
+ KeyedVector<String8, String8> mUriHeaders;
+
+ sp<TimedEventQueue::Event> mAsyncPrepareEvent;
+ Condition mPreparedCondition;
+ status_t mPrepareResult;
+
+ bool mIsSeeking;
+ int64_t mSeekTimeUs;
+
+ sp<TimedEventQueue::Event> mPumpAudioEvent;
+ bool mPumpAudioEventPending;
+
+ sp<HTTPBase> mConnectingDataSource;
+ sp<NuCachedSource2> mCachedSource;
+
+ sp<MediaSource> mAudioSource;
+ int64_t mDurationUs;
+ int64_t mBitrate;
+
+ sp<AAH_TXSender> mAAH_Sender;
+ LinearTransform mCurrentClockTransform;
+ bool mCurrentClockTransformValid;
+ int64_t mLastQueuedMediaTimePTS;
+ bool mLastQueuedMediaTimePTSValid;
+ bool mPlayRateIsPaused;
+
+ Mutex mEndpointLock;
+ AAH_TXSender::Endpoint mEndpoint;
+ bool mEndpointValid;
+ bool mEndpointRegistered;
+ uint16_t mProgramID;
+
+ uint8_t mTRTPVolume;
+
+ DISALLOW_EVIL_CONSTRUCTORS(AAH_TXPlayer);
+};
+
+} // namespace android
+
+#endif // __AAH_TX_PLAYER_H__
diff --git a/media/libaah_rtp/aah_tx_sender.cpp b/media/libaah_rtp/aah_tx_sender.cpp
new file mode 100644
index 0000000..779d809
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_sender.cpp
@@ -0,0 +1,626 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "LibAAH_RTP"
+#include <media/stagefright/foundation/ADebug.h>
+
+#include <netinet/in.h>
+#include <poll.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <media/stagefright/foundation/AMessage.h>
+#include <utils/misc.h>
+
+#include "aah_tx_player.h"
+#include "aah_tx_sender.h"
+
+namespace android {
+
+const char* AAH_TXSender::kSendPacketIPAddr = "ipaddr";
+const char* AAH_TXSender::kSendPacketPort = "port";
+const char* AAH_TXSender::kSendPacketTRTPPacket = "trtp";
+
+const int AAH_TXSender::kRetryTrimIntervalUs = 100000;
+const int AAH_TXSender::kHeartbeatIntervalUs = 1000000;
+const int AAH_TXSender::kRetryBufferCapacity = 100;
+
+Mutex AAH_TXSender::sLock;
+wp<AAH_TXSender> AAH_TXSender::sInstance;
+uint32_t AAH_TXSender::sNextEpoch;
+bool AAH_TXSender::sNextEpochValid = false;
+
+AAH_TXSender::AAH_TXSender() : mSocket(-1) { }
+
+sp<AAH_TXSender> AAH_TXSender::GetInstance() {
+ Mutex::Autolock autoLock(sLock);
+
+ sp<AAH_TXSender> sender = sInstance.promote();
+
+ if (sender == NULL) {
+ sender = new AAH_TXSender();
+ if (sender == NULL) {
+ return NULL;
+ }
+
+ sender->mLooper = new ALooper();
+ if (sender->mLooper == NULL) {
+ return NULL;
+ }
+
+ sender->mReflector = new AHandlerReflector<AAH_TXSender>(sender.get());
+ if (sender->mReflector == NULL) {
+ return NULL;
+ }
+
+ sender->mSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (sender->mSocket == -1) {
+ LOGW("%s unable to create socket", __PRETTY_FUNCTION__);
+ return NULL;
+ }
+
+ struct sockaddr_in bind_addr;
+ memset(&bind_addr, 0, sizeof(bind_addr));
+ bind_addr.sin_family = AF_INET;
+ if (bind(sender->mSocket,
+ reinterpret_cast<const sockaddr*>(&bind_addr),
+ sizeof(bind_addr)) < 0) {
+ LOGW("%s unable to bind socket (errno %d)",
+ __PRETTY_FUNCTION__, errno);
+ return NULL;
+ }
+
+ sender->mRetryReceiver = new RetryReceiver(sender.get());
+ if (sender->mRetryReceiver == NULL) {
+ return NULL;
+ }
+
+ sender->mLooper->setName("AAH_TXSender");
+ sender->mLooper->registerHandler(sender->mReflector);
+ sender->mLooper->start(false, false, PRIORITY_AUDIO);
+
+ if (sender->mRetryReceiver->run("AAH_TXSenderRetry", PRIORITY_AUDIO)
+ != OK) {
+ LOGW("%s unable to start retry thread", __PRETTY_FUNCTION__);
+ return NULL;
+ }
+
+ sInstance = sender;
+ }
+
+ return sender;
+}
+
+AAH_TXSender::~AAH_TXSender() {
+ mLooper->stop();
+ mLooper->unregisterHandler(mReflector->id());
+
+ if (mRetryReceiver != NULL) {
+ mRetryReceiver->requestExit();
+ mRetryReceiver->mWakeupEvent.setEvent();
+ if (mRetryReceiver->requestExitAndWait() != OK) {
+ LOGW("%s shutdown of retry receiver failed", __PRETTY_FUNCTION__);
+ }
+ mRetryReceiver->mSender = NULL;
+ mRetryReceiver.clear();
+ }
+
+ if (mSocket != -1) {
+ close(mSocket);
+ }
+}
+
+// Return the next epoch number usable for a newly instantiated endpoint.
+uint32_t AAH_TXSender::getNextEpoch() {
+ Mutex::Autolock autoLock(sLock);
+
+ if (sNextEpochValid) {
+ sNextEpoch = (sNextEpoch + 1) & TRTPPacket::kTRTPEpochMask;
+ } else {
+ sNextEpoch = ns2ms(systemTime()) & TRTPPacket::kTRTPEpochMask;
+ sNextEpochValid = true;
+ }
+
+ return sNextEpoch;
+}
+
+// Notify the sender that a player has started sending to this endpoint.
+// Returns a program ID for use by the calling player.
+uint16_t AAH_TXSender::registerEndpoint(const Endpoint& endpoint) {
+ Mutex::Autolock lock(mEndpointLock);
+
+ EndpointState* eps = mEndpointMap.valueFor(endpoint);
+ if (eps) {
+ eps->playerRefCount++;
+ } else {
+ eps = new EndpointState(getNextEpoch());
+ mEndpointMap.add(endpoint, eps);
+ }
+
+ // if this is the first registered endpoint, then send a message to start
+ // trimming retry buffers and a message to start sending heartbeats.
+ if (mEndpointMap.size() == 1) {
+ sp<AMessage> trimMessage = new AMessage(kWhatTrimRetryBuffers,
+ handlerID());
+ trimMessage->post(kRetryTrimIntervalUs);
+
+ sp<AMessage> heartbeatMessage = new AMessage(kWhatSendHeartbeats,
+ handlerID());
+ heartbeatMessage->post(kHeartbeatIntervalUs);
+ }
+
+ eps->nextProgramID++;
+ return eps->nextProgramID;
+}
+
+// Notify the sender that a player has ceased sending to this endpoint.
+// An endpoint's state can not be deleted until all of the endpoint's
+// registered players have called unregisterEndpoint.
+void AAH_TXSender::unregisterEndpoint(const Endpoint& endpoint) {
+ Mutex::Autolock lock(mEndpointLock);
+
+ EndpointState* eps = mEndpointMap.valueFor(endpoint);
+ if (eps) {
+ eps->playerRefCount--;
+ CHECK(eps->playerRefCount >= 0);
+ }
+}
+
+void AAH_TXSender::assignSeqNumber(const Endpoint& endpoint,
+ const sp<TRTPPacket>& packet) {
+ Mutex::Autolock lock(mEndpointLock);
+ assignSeqNumber_l(endpoint, packet);
+}
+
+void AAH_TXSender::assignSeqNumber_l(const Endpoint& endpoint,
+ const sp<TRTPPacket>& packet) {
+ EndpointState* eps = mEndpointMap.valueFor(endpoint);
+ if (!eps) {
+ // the endpoint state has disappeared, so the player that sent this
+ // packet must be dead.
+ return;
+ }
+ packet->setEpoch(eps->epoch);
+ packet->setSeqNumber(eps->trtpSeqNumber++);
+}
+
+void AAH_TXSender::onMessageReceived(const sp<AMessage>& msg) {
+ switch (msg->what()) {
+ case kWhatSendPacket:
+ onSendPacket(msg);
+ break;
+
+ case kWhatTrimRetryBuffers:
+ trimRetryBuffers();
+ break;
+
+ case kWhatSendHeartbeats:
+ sendHeartbeats();
+ break;
+
+ default:
+ TRESPASS();
+ break;
+ }
+}
+
+void AAH_TXSender::onSendPacket(const sp<AMessage>& msg) {
+ LOGV("*** %s", __PRETTY_FUNCTION__);
+
+ sp<RefBase> obj;
+ CHECK(msg->findObject(kSendPacketTRTPPacket, &obj));
+ sp<TRTPPacket> packet = static_cast<TRTPPacket*>(obj.get());
+
+ uint32_t ipAddr;
+ CHECK(msg->findInt32(kSendPacketIPAddr,
+ reinterpret_cast<int32_t*>(&ipAddr)));
+
+ int32_t port32;
+ CHECK(msg->findInt32(kSendPacketPort, &port32));
+ uint16_t port = port32;
+
+ doSendPacket(packet, ipAddr, port);
+
+ addToRetryBuffer(Endpoint(ipAddr, port), packet);
+}
+
+void AAH_TXSender::doSendPacket(sp<TRTPPacket> packet,
+ uint32_t ipAddr,
+ uint16_t port) {
+ struct sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = ipAddr;
+ addr.sin_port = htons(port);
+
+ ssize_t result = sendto(mSocket,
+ packet->getPacket(),
+ packet->getPacketLen(),
+ 0,
+ (const struct sockaddr *) &addr,
+ sizeof(addr));
+ if (result == -1) {
+ LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+}
+
+void AAH_TXSender::addToRetryBuffer(const Endpoint& endpoint,
+ const sp<TRTPPacket>& packet) {
+ Mutex::Autolock lock(mEndpointLock);
+
+ EndpointState* eps = mEndpointMap.valueFor(endpoint);
+ if (!eps) {
+ return;
+ }
+
+ addToRetryBuffer_l(eps, packet);
+}
+
+void AAH_TXSender::addToRetryBuffer_l(EndpointState* eps,
+ const sp<TRTPPacket>& packet) {
+ RetryBuffer& retry = eps->retry;
+ retry.push_back(packet);
+
+ LOGV("*** %s seq=%hu size=%d",
+ __PRETTY_FUNCTION__,
+ packet->getSeqNumber(),
+ retry.size());
+}
+
+void AAH_TXSender::trimRetryBuffers() {
+ LOGV("*** %s", __PRETTY_FUNCTION__);
+
+ Mutex::Autolock lock(mEndpointLock);
+
+ nsecs_t localTimeNow = systemTime();
+
+ Vector<Endpoint> endpointsToRemove;
+
+ for (size_t i = 0; i < mEndpointMap.size(); i++) {
+ EndpointState* eps = mEndpointMap.editValueAt(i);
+ RetryBuffer& retry = eps->retry;
+
+ while (!retry.isEmpty()) {
+ if (retry[0]->getExpireTime() < localTimeNow) {
+ retry.pop_front();
+ } else {
+ break;
+ }
+ }
+
+ LOGV("*** %s size=%d", __PRETTY_FUNCTION__, retry.size());
+
+ if (retry.isEmpty() && eps->playerRefCount == 0) {
+ endpointsToRemove.add(mEndpointMap.keyAt(i));
+ }
+ }
+
+ // remove the state for any endpoints that are no longer in use
+ for (size_t i = 0; i < endpointsToRemove.size(); i++) {
+ Endpoint& e = endpointsToRemove.editItemAt(i);
+ LOGD("*** %s removing endpoint addr=%08x", __PRETTY_FUNCTION__, e.addr);
+ size_t index = mEndpointMap.indexOfKey(e);
+ delete mEndpointMap.valueAt(index);
+ mEndpointMap.removeItemsAt(index);
+ }
+
+ // schedule the next trim
+ if (mEndpointMap.size()) {
+ sp<AMessage> trimMessage = new AMessage(kWhatTrimRetryBuffers,
+ handlerID());
+ trimMessage->post(kRetryTrimIntervalUs);
+ }
+}
+
+void AAH_TXSender::sendHeartbeats() {
+ Mutex::Autolock lock(mEndpointLock);
+
+ for (size_t i = 0; i < mEndpointMap.size(); i++) {
+ EndpointState* eps = mEndpointMap.editValueAt(i);
+ const Endpoint& ep = mEndpointMap.keyAt(i);
+
+ sp<TRTPControlPacket> packet = new TRTPControlPacket();
+ packet->setCommandID(TRTPControlPacket::kCommandNop);
+
+ assignSeqNumber_l(ep, packet);
+ packet->setExpireTime(systemTime() +
+ AAH_TXPlayer::kAAHRetryKeepAroundTimeNs);
+ packet->pack();
+
+ doSendPacket(packet, ep.addr, ep.port);
+ addToRetryBuffer_l(eps, packet);
+ }
+
+ // schedule the next heartbeat
+ if (mEndpointMap.size()) {
+ sp<AMessage> heartbeatMessage = new AMessage(kWhatSendHeartbeats,
+ handlerID());
+ heartbeatMessage->post(kHeartbeatIntervalUs);
+ }
+}
+
+// Receiver
+
+// initial 4-byte ID of a retry request packet
+const uint32_t AAH_TXSender::RetryReceiver::kRetryRequestID = 'Treq';
+
+// initial 4-byte ID of a retry NAK packet
+const uint32_t AAH_TXSender::RetryReceiver::kRetryNakID = 'Tnak';
+
+// initial 4-byte ID of a fast start request packet
+const uint32_t AAH_TXSender::RetryReceiver::kFastStartRequestID = 'Tfst';
+
+AAH_TXSender::RetryReceiver::RetryReceiver(AAH_TXSender* sender)
+ : Thread(false),
+ mSender(sender) {}
+
+ AAH_TXSender::RetryReceiver::~RetryReceiver() {
+ mWakeupEvent.clearPendingEvents();
+ }
+
+// Returns true if val is within the interval bounded inclusively by
+// start and end. Also handles the case where there is a rollover of the
+// range between start and end.
+template <typename T>
+static inline bool withinIntervalWithRollover(T val, T start, T end) {
+ return ((start <= end && val >= start && val <= end) ||
+ (start > end && (val >= start || val <= end)));
+}
+
+bool AAH_TXSender::RetryReceiver::threadLoop() {
+ struct pollfd pollFds[2];
+ pollFds[0].fd = mSender->mSocket;
+ pollFds[0].events = POLLIN;
+ pollFds[0].revents = 0;
+ pollFds[1].fd = mWakeupEvent.getWakeupHandle();
+ pollFds[1].events = POLLIN;
+ pollFds[1].revents = 0;
+
+ int pollResult = poll(pollFds, NELEM(pollFds), -1);
+ if (pollResult == -1) {
+ LOGE("%s poll failed", __PRETTY_FUNCTION__);
+ return false;
+ }
+
+ if (exitPending()) {
+ LOGI("*** %s exiting", __PRETTY_FUNCTION__);
+ return false;
+ }
+
+ if (pollFds[0].revents) {
+ handleRetryRequest();
+ }
+
+ return true;
+}
+
+void AAH_TXSender::RetryReceiver::handleRetryRequest() {
+ LOGV("*** RX %s start", __PRETTY_FUNCTION__);
+
+ RetryPacket request;
+ struct sockaddr requestSrcAddr;
+ socklen_t requestSrcAddrLen = sizeof(requestSrcAddr);
+
+ ssize_t result = recvfrom(mSender->mSocket, &request, sizeof(request), 0,
+ &requestSrcAddr, &requestSrcAddrLen);
+ if (result == -1) {
+ LOGE("%s recvfrom failed, errno=%d", __PRETTY_FUNCTION__, errno);
+ return;
+ }
+
+ if (static_cast<size_t>(result) < sizeof(RetryPacket)) {
+ LOGW("%s short packet received", __PRETTY_FUNCTION__);
+ return;
+ }
+
+ uint32_t host_request_id = ntohl(request.id);
+ if ((host_request_id != kRetryRequestID) &&
+ (host_request_id != kFastStartRequestID)) {
+ LOGW("%s received retry request with bogus ID (%08x)",
+ __PRETTY_FUNCTION__, host_request_id);
+ return;
+ }
+
+ Endpoint endpoint(request.endpointIP, ntohs(request.endpointPort));
+
+ Mutex::Autolock lock(mSender->mEndpointLock);
+
+ EndpointState* eps = mSender->mEndpointMap.valueFor(endpoint);
+
+ if (eps == NULL || eps->retry.isEmpty()) {
+ // we have no retry buffer or an empty retry buffer for this endpoint,
+ // so NAK the entire request
+ RetryPacket nak = request;
+ nak.id = htonl(kRetryNakID);
+ result = sendto(mSender->mSocket, &nak, sizeof(nak), 0,
+ &requestSrcAddr, requestSrcAddrLen);
+ if (result == -1) {
+ LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+ return;
+ }
+
+ RetryBuffer& retry = eps->retry;
+
+ uint16_t startSeq = ntohs(request.seqStart);
+ uint16_t endSeq = ntohs(request.seqEnd);
+
+ uint16_t retryFirstSeq = retry[0]->getSeqNumber();
+ uint16_t retryLastSeq = retry[retry.size() - 1]->getSeqNumber();
+
+ // If this is a fast start, then force the start of the retry to match the
+ // start of the retransmit ring buffer (unless the end of the retransmit
+ // ring buffer is already past the point of fast start)
+ if ((host_request_id == kFastStartRequestID) &&
+ !((startSeq - retryFirstSeq) & 0x8000)) {
+ startSeq = retryFirstSeq;
+ }
+
+ int startIndex;
+ if (withinIntervalWithRollover(startSeq, retryFirstSeq, retryLastSeq)) {
+ startIndex = static_cast<uint16_t>(startSeq - retryFirstSeq);
+ } else {
+ startIndex = -1;
+ }
+
+ int endIndex;
+ if (withinIntervalWithRollover(endSeq, retryFirstSeq, retryLastSeq)) {
+ endIndex = static_cast<uint16_t>(endSeq - retryFirstSeq);
+ } else {
+ endIndex = -1;
+ }
+
+ if (startIndex == -1 && endIndex == -1) {
+ // no part of the request range is found in the retry buffer
+ RetryPacket nak = request;
+ nak.id = htonl(kRetryNakID);
+ result = sendto(mSender->mSocket, &nak, sizeof(nak), 0,
+ &requestSrcAddr, requestSrcAddrLen);
+ if (result == -1) {
+ LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+ return;
+ }
+
+ if (startIndex == -1) {
+ // NAK a subrange at the front of the request range
+ RetryPacket nak = request;
+ nak.id = htonl(kRetryNakID);
+ nak.seqEnd = htons(retryFirstSeq - 1);
+ result = sendto(mSender->mSocket, &nak, sizeof(nak), 0,
+ &requestSrcAddr, requestSrcAddrLen);
+ if (result == -1) {
+ LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+
+ startIndex = 0;
+ } else if (endIndex == -1) {
+ // NAK a subrange at the back of the request range
+ RetryPacket nak = request;
+ nak.id = htonl(kRetryNakID);
+ nak.seqStart = htons(retryLastSeq + 1);
+ result = sendto(mSender->mSocket, &nak, sizeof(nak), 0,
+ &requestSrcAddr, requestSrcAddrLen);
+ if (result == -1) {
+ LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+
+ endIndex = retry.size() - 1;
+ }
+
+ // send the retry packets
+ for (int i = startIndex; i <= endIndex; i++) {
+ const sp<TRTPPacket>& replyPacket = retry[i];
+
+ result = sendto(mSender->mSocket,
+ replyPacket->getPacket(),
+ replyPacket->getPacketLen(),
+ 0,
+ &requestSrcAddr,
+ requestSrcAddrLen);
+
+ if (result == -1) {
+ LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+ }
+ }
+}
+
+// Endpoint
+
+AAH_TXSender::Endpoint::Endpoint()
+ : addr(0)
+ , port(0) { }
+
+AAH_TXSender::Endpoint::Endpoint(uint32_t a, uint16_t p)
+ : addr(a)
+ , port(p) {}
+
+bool AAH_TXSender::Endpoint::operator<(const Endpoint& other) const {
+ return ((addr < other.addr) ||
+ (addr == other.addr && port < other.port));
+}
+
+// EndpointState
+
+AAH_TXSender::EndpointState::EndpointState(uint32_t _epoch)
+ : retry(kRetryBufferCapacity)
+ , playerRefCount(1)
+ , trtpSeqNumber(0)
+ , nextProgramID(0)
+ , epoch(_epoch) { }
+
+// CircularBuffer
+
+template <typename T>
+CircularBuffer<T>::CircularBuffer(size_t capacity)
+ : mCapacity(capacity)
+ , mHead(0)
+ , mTail(0)
+ , mFillCount(0) {
+ mBuffer = new T[capacity];
+}
+
+template <typename T>
+CircularBuffer<T>::~CircularBuffer() {
+ delete [] mBuffer;
+}
+
+template <typename T>
+void CircularBuffer<T>::push_back(const T& item) {
+ if (this->isFull()) {
+ this->pop_front();
+ }
+ mBuffer[mHead] = item;
+ mHead = (mHead + 1) % mCapacity;
+ mFillCount++;
+}
+
+template <typename T>
+void CircularBuffer<T>::pop_front() {
+ CHECK(!isEmpty());
+ mBuffer[mTail] = T();
+ mTail = (mTail + 1) % mCapacity;
+ mFillCount--;
+}
+
+template <typename T>
+size_t CircularBuffer<T>::size() const {
+ return mFillCount;
+}
+
+template <typename T>
+bool CircularBuffer<T>::isFull() const {
+ return (mFillCount == mCapacity);
+}
+
+template <typename T>
+bool CircularBuffer<T>::isEmpty() const {
+ return (mFillCount == 0);
+}
+
+template <typename T>
+const T& CircularBuffer<T>::itemAt(size_t index) const {
+ CHECK(index < mFillCount);
+ return mBuffer[(mTail + index) % mCapacity];
+}
+
+template <typename T>
+const T& CircularBuffer<T>::operator[](size_t index) const {
+ return itemAt(index);
+}
+
+} // namespace android
diff --git a/media/libaah_rtp/aah_tx_sender.h b/media/libaah_rtp/aah_tx_sender.h
new file mode 100644
index 0000000..a4baf5f
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_sender.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef __AAH_TX_SENDER_H__
+#define __AAH_TX_SENDER_H__
+
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AHandlerReflector.h>
+#include <utils/RefBase.h>
+#include <utils/threads.h>
+
+#include "aah_tx_packet.h"
+#include "pipe_event.h"
+
+namespace android {
+
+template <typename T> class CircularBuffer {
+ public:
+ CircularBuffer(size_t capacity);
+ ~CircularBuffer();
+ void push_back(const T& item);;
+ void pop_front();
+ size_t size() const;
+ bool isFull() const;
+ bool isEmpty() const;
+ const T& itemAt(size_t index) const;
+ const T& operator[](size_t index) const;
+
+ private:
+ T* mBuffer;
+ size_t mCapacity;
+ size_t mHead;
+ size_t mTail;
+ size_t mFillCount;
+};
+
+class AAH_TXSender : public virtual RefBase {
+ public:
+ ~AAH_TXSender();
+
+ static sp<AAH_TXSender> GetInstance();
+
+ ALooper::handler_id handlerID() { return mReflector->id(); }
+
+ // an IP address and port
+ struct Endpoint {
+ Endpoint();
+ Endpoint(uint32_t a, uint16_t p);
+ bool operator<(const Endpoint& other) const;
+
+ uint32_t addr;
+ uint16_t port;
+ };
+
+ uint16_t registerEndpoint(const Endpoint& endpoint);
+ void unregisterEndpoint(const Endpoint& endpoint);
+ void assignSeqNumber(const Endpoint& endpoint,
+ const sp<TRTPPacket>& packet);
+
+ enum {
+ kWhatSendPacket,
+ kWhatTrimRetryBuffers,
+ kWhatSendHeartbeats,
+ };
+
+ // fields for SendPacket messages
+ static const char* kSendPacketIPAddr;
+ static const char* kSendPacketPort;
+ static const char* kSendPacketTRTPPacket;
+
+ private:
+ AAH_TXSender();
+
+ static Mutex sLock;
+ static wp<AAH_TXSender> sInstance;
+ static uint32_t sNextEpoch;
+ static bool sNextEpochValid;
+
+ static uint32_t getNextEpoch();
+
+ typedef CircularBuffer<sp<TRTPPacket> > RetryBuffer;
+
+ // state maintained on a per-endpoint basis
+ struct EndpointState {
+ EndpointState(uint32_t epoch);
+ RetryBuffer retry;
+ int playerRefCount;
+ uint16_t trtpSeqNumber;
+ uint16_t nextProgramID;
+ uint32_t epoch;
+ };
+
+ friend class AHandlerReflector<AAH_TXSender>;
+ void onMessageReceived(const sp<AMessage>& msg);
+ void onSendPacket(const sp<AMessage>& msg);
+ void doSendPacket(sp<TRTPPacket> packet, uint32_t ipAddr, uint16_t port);
+ void addToRetryBuffer(const Endpoint& endpoint,
+ const sp<TRTPPacket>& packet);
+ void addToRetryBuffer_l(EndpointState* eps,
+ const sp<TRTPPacket>& packet);
+ void trimRetryBuffers();
+ void sendHeartbeats();
+ void assignSeqNumber_l(const Endpoint& endpoint,
+ const sp<TRTPPacket>& packet);
+ sp<ALooper> mLooper;
+ sp<AHandlerReflector<AAH_TXSender> > mReflector;
+
+ int mSocket;
+
+ DefaultKeyedVector<Endpoint, EndpointState*> mEndpointMap;
+ Mutex mEndpointLock;
+
+ static const int kRetryTrimIntervalUs;
+ static const int kHeartbeatIntervalUs;
+ static const int kRetryBufferCapacity;
+
+ class RetryReceiver : public Thread {
+ private:
+ friend class AAH_TXSender;
+
+ RetryReceiver(AAH_TXSender* sender);
+ virtual ~RetryReceiver();
+ virtual bool threadLoop();
+ void handleRetryRequest();
+
+ static const int kMaxReceiverPacketLen;
+ static const uint32_t kRetryRequestID;
+ static const uint32_t kFastStartRequestID;
+ static const uint32_t kRetryNakID;
+
+ AAH_TXSender* mSender;
+ PipeEvent mWakeupEvent;
+ };
+
+ sp<RetryReceiver> mRetryReceiver;
+};
+
+struct RetryPacket {
+ uint32_t id;
+ uint32_t endpointIP;
+ uint16_t endpointPort;
+ uint16_t seqStart;
+ uint16_t seqEnd;
+} __attribute__((packed));
+
+} // namespace android
+
+#endif // __AAH_TX_SENDER_H__
diff --git a/media/libaah_rtp/pipe_event.cpp b/media/libaah_rtp/pipe_event.cpp
new file mode 100644
index 0000000..96c674b
--- /dev/null
+++ b/media/libaah_rtp/pipe_event.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "LibAAH_RTP"
+#include <utils/Log.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <unistd.h>
+
+#include "pipe_event.h"
+
+namespace android {
+
+PipeEvent::PipeEvent() {
+ pipe_[0] = -1;
+ pipe_[1] = -1;
+
+ // Create the pipe.
+ if (pipe(pipe_) >= 0) {
+ // Set non-blocking mode on the read side of the pipe so we can
+ // easily drain it whenever we wakeup.
+ fcntl(pipe_[0], F_SETFL, O_NONBLOCK);
+ } else {
+ LOGE("Failed to create pipe event %d %d %d",
+ pipe_[0], pipe_[1], errno);
+ pipe_[0] = -1;
+ pipe_[1] = -1;
+ }
+}
+
+PipeEvent::~PipeEvent() {
+ if (pipe_[0] >= 0) {
+ close(pipe_[0]);
+ }
+
+ if (pipe_[1] >= 0) {
+ close(pipe_[1]);
+ }
+}
+
+void PipeEvent::clearPendingEvents() {
+ char drain_buffer[16];
+ while (read(pipe_[0], drain_buffer, sizeof(drain_buffer)) > 0) {
+ // No body.
+ }
+}
+
+bool PipeEvent::wait(int timeout) {
+ struct pollfd wait_fd;
+
+ wait_fd.fd = getWakeupHandle();
+ wait_fd.events = POLLIN;
+ wait_fd.revents = 0;
+
+ int res = poll(&wait_fd, 1, timeout);
+
+ if (res < 0) {
+ LOGE("Wait error in PipeEvent; sleeping to prevent overload!");
+ usleep(1000);
+ }
+
+ return (res > 0);
+}
+
+void PipeEvent::setEvent() {
+ char foo = 'q';
+ write(pipe_[1], &foo, 1);
+}
+
+} // namespace android
+
diff --git a/media/libaah_rtp/pipe_event.h b/media/libaah_rtp/pipe_event.h
new file mode 100644
index 0000000..637bfef
--- /dev/null
+++ b/media/libaah_rtp/pipe_event.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef __PIPE_EVENT_H__
+#define __PIPE_EVENT_H__
+
+namespace android {
+
+class PipeEvent {
+ public:
+ PipeEvent();
+ ~PipeEvent();
+
+ bool initCheck() const {
+ return ((pipe_[0] >= 0) && (pipe_[1] >= 0));
+ }
+
+ int getWakeupHandle() const { return pipe_[0]; }
+
+ // block until the event fires; returns true if the event fired and false if
+ // the wait timed out. Timeout is expressed in milliseconds; negative
+ // values mean wait forever.
+ bool wait(int timeout = -1);
+
+ void clearPendingEvents();
+ void setEvent();
+
+ private:
+ int pipe_[2];
+};
+
+} // namespace android
+
+#endif // __PIPE_EVENT_H__
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index c2c6715..3b94410 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -79,7 +79,8 @@
// ---------------------------------------------------------------------------
AudioTrack::AudioTrack()
- : mStatus(NO_INIT)
+ : mStatus(NO_INIT),
+ mIsTimed(false)
{
}
@@ -94,7 +95,8 @@
void* user,
int notificationFrames,
int sessionId)
- : mStatus(NO_INIT)
+ : mStatus(NO_INIT),
+ mIsTimed(false)
{
mStatus = set(streamType, sampleRate, format, channelMask,
frameCount, flags, cbf, user, notificationFrames,
@@ -112,7 +114,8 @@
void* user,
int notificationFrames,
int sessionId)
- : mStatus(NO_INIT)
+ : mStatus(NO_INIT),
+ mIsTimed(false)
{
mStatus = set(streamType, sampleRate, format, channelMask,
0, flags, cbf, user, notificationFrames,
@@ -520,6 +523,10 @@
{
int afSamplingRate;
+ if (mIsTimed) {
+ return INVALID_OPERATION;
+ }
+
if (AudioSystem::getOutputSamplingRate(&afSamplingRate, mStreamType) != NO_ERROR) {
return NO_INIT;
}
@@ -533,6 +540,10 @@
uint32_t AudioTrack::getSampleRate()
{
+ if (mIsTimed) {
+ return INVALID_OPERATION;
+ }
+
AutoMutex lock(mLock);
return mCblk->sampleRate;
}
@@ -558,6 +569,10 @@
return NO_ERROR;
}
+ if (mIsTimed) {
+ return INVALID_OPERATION;
+ }
+
if (loopStart >= loopEnd ||
loopEnd - loopStart > cblk->frameCount ||
cblk->server > loopStart) {
@@ -641,6 +656,8 @@
status_t AudioTrack::setPosition(uint32_t position)
{
+ if (mIsTimed) return INVALID_OPERATION;
+
AutoMutex lock(mLock);
Mutex::Autolock _l(mCblk->lock);
@@ -790,6 +807,7 @@
((uint16_t)flags) << 16,
sharedBuffer,
output,
+ mIsTimed,
&mSessionId,
&status);
@@ -958,6 +976,7 @@
{
if (mSharedBuffer != 0) return INVALID_OPERATION;
+ if (mIsTimed) return INVALID_OPERATION;
if (ssize_t(userSize) < 0) {
// sanity-check. user is most-likely passing an error code.
@@ -1020,6 +1039,36 @@
// -------------------------------------------------------------------------
+TimedAudioTrack::TimedAudioTrack() {
+ mIsTimed = true;
+}
+
+status_t TimedAudioTrack::allocateTimedBuffer(size_t size, sp<IMemory>* buffer)
+{
+ return mAudioTrack->allocateTimedBuffer(size, buffer);
+}
+
+status_t TimedAudioTrack::queueTimedBuffer(const sp<IMemory>& buffer,
+ int64_t pts)
+{
+ // restart track if it was disabled by audioflinger due to previous underrun
+ if (mActive && (mCblk->flags & CBLK_DISABLED_MSK)) {
+ mCblk->flags &= ~CBLK_DISABLED_ON;
+ LOGW("queueTimedBuffer() track %p disabled, restarting", this);
+ mAudioTrack->start();
+ }
+
+ return mAudioTrack->queueTimedBuffer(buffer, pts);
+}
+
+status_t TimedAudioTrack::setMediaTimeTransform(const LinearTransform& xform,
+ TargetTimeline target)
+{
+ return mAudioTrack->setMediaTimeTransform(xform, target);
+}
+
+// -------------------------------------------------------------------------
+
bool AudioTrack::processAudioBuffer(const sp<AudioTrackThread>& thread)
{
Buffer audioBuffer;
@@ -1460,4 +1509,3 @@
// -------------------------------------------------------------------------
}; // namespace android
-
diff --git a/media/libmedia/IAudioFlinger.cpp b/media/libmedia/IAudioFlinger.cpp
index d58834b..c7b49cd 100644
--- a/media/libmedia/IAudioFlinger.cpp
+++ b/media/libmedia/IAudioFlinger.cpp
@@ -90,6 +90,7 @@
uint32_t flags,
const sp<IMemory>& sharedBuffer,
int output,
+ bool isTimed,
int *sessionId,
status_t *status)
{
@@ -105,6 +106,7 @@
data.writeInt32(flags);
data.writeStrongBinder(sharedBuffer->asBinder());
data.writeInt32(output);
+ data.writeInt32(isTimed);
int lSessionId = 0;
if (sessionId != NULL) {
lSessionId = *sessionId;
@@ -684,11 +686,12 @@
uint32_t flags = data.readInt32();
sp<IMemory> buffer = interface_cast<IMemory>(data.readStrongBinder());
int output = data.readInt32();
+ bool isTimed = data.readInt32();
int sessionId = data.readInt32();
status_t status;
sp<IAudioTrack> track = createTrack(pid,
streamType, sampleRate, format,
- channelCount, bufferCount, flags, buffer, output, &sessionId, &status);
+ channelCount, bufferCount, flags, buffer, output, isTimed, &sessionId, &status);
reply->writeInt32(sessionId);
reply->writeInt32(status);
reply->writeStrongBinder(track->asBinder());
diff --git a/media/libmedia/IAudioTrack.cpp b/media/libmedia/IAudioTrack.cpp
index bc8ff34..a496fb8 100644
--- a/media/libmedia/IAudioTrack.cpp
+++ b/media/libmedia/IAudioTrack.cpp
@@ -35,7 +35,10 @@
FLUSH,
MUTE,
PAUSE,
- ATTACH_AUX_EFFECT
+ ATTACH_AUX_EFFECT,
+ ALLOCATE_TIMED_BUFFER,
+ QUEUE_TIMED_BUFFER,
+ SET_MEDIA_TIME_TRANSFORM,
};
class BpAudioTrack : public BpInterface<IAudioTrack>
@@ -113,6 +116,52 @@
}
return status;
}
+
+ virtual status_t allocateTimedBuffer(size_t size, sp<IMemory>* buffer) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor());
+ data.writeInt32(size);
+ status_t status = remote()->transact(ALLOCATE_TIMED_BUFFER,
+ data, &reply);
+ if (status == NO_ERROR) {
+ status = reply.readInt32();
+ if (status == NO_ERROR) {
+ *buffer = interface_cast<IMemory>(reply.readStrongBinder());
+ }
+ }
+ return status;
+ }
+
+ virtual status_t queueTimedBuffer(const sp<IMemory>& buffer,
+ int64_t pts) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor());
+ data.writeStrongBinder(buffer->asBinder());
+ data.writeInt64(pts);
+ status_t status = remote()->transact(QUEUE_TIMED_BUFFER,
+ data, &reply);
+ if (status == NO_ERROR) {
+ status = reply.readInt32();
+ }
+ return status;
+ }
+
+ virtual status_t setMediaTimeTransform(const LinearTransform& xform,
+ int target) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor());
+ data.writeInt64(xform.a_zero);
+ data.writeInt64(xform.b_zero);
+ data.writeInt32(xform.a_to_b_numer);
+ data.writeInt32(xform.a_to_b_denom);
+ data.writeInt32(target);
+ status_t status = remote()->transact(SET_MEDIA_TIME_TRANSFORM,
+ data, &reply);
+ if (status == NO_ERROR) {
+ status = reply.readInt32();
+ }
+ return status;
+ }
};
IMPLEMENT_META_INTERFACE(AudioTrack, "android.media.IAudioTrack");
@@ -158,10 +207,38 @@
reply->writeInt32(attachAuxEffect(data.readInt32()));
return NO_ERROR;
} break;
+ case ALLOCATE_TIMED_BUFFER: {
+ CHECK_INTERFACE(IAudioTrack, data, reply);
+ sp<IMemory> buffer;
+ status_t status = allocateTimedBuffer(data.readInt32(), &buffer);
+ reply->writeInt32(status);
+ if (status == NO_ERROR) {
+ reply->writeStrongBinder(buffer->asBinder());
+ }
+ return NO_ERROR;
+ } break;
+ case QUEUE_TIMED_BUFFER: {
+ CHECK_INTERFACE(IAudioTrack, data, reply);
+ sp<IMemory> buffer = interface_cast<IMemory>(
+ data.readStrongBinder());
+ uint64_t pts = data.readInt64();
+ reply->writeInt32(queueTimedBuffer(buffer, pts));
+ return NO_ERROR;
+ } break;
+ case SET_MEDIA_TIME_TRANSFORM: {
+ CHECK_INTERFACE(IAudioTrack, data, reply);
+ LinearTransform xform;
+ xform.a_zero = data.readInt64();
+ xform.b_zero = data.readInt64();
+ xform.a_to_b_numer = data.readInt32();
+ xform.a_to_b_denom = data.readInt32();
+ int target = data.readInt32();
+ reply->writeInt32(setMediaTimeTransform(xform, target));
+ return NO_ERROR;
+ } break;
default:
return BBinder::onTransact(code, data, reply, flags);
}
}
}; // namespace android
-
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index ec7d8a0..b5a60d2 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -29,7 +29,8 @@
libstagefright_omx \
libstagefright_foundation \
libgui \
- libdl
+ libdl \
+ libaah_rtp
LOCAL_STATIC_LIBRARIES := \
libstagefright_rtsp \
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 0386d4b..a434614 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -68,6 +68,11 @@
#include <OMX.h>
+namespace android {
+sp<MediaPlayerBase> createAAH_TXPlayer();
+sp<MediaPlayerBase> createAAH_RXPlayer();
+}
+
namespace {
using android::media::Metadata;
using android::status_t;
@@ -583,6 +588,14 @@
}
}
+ if (!strncasecmp("aahRX://", url, 8)) {
+ return AAH_RX_PLAYER;
+ }
+
+ if (!strncasecmp("aahTX://", url, 8)) {
+ return AAH_TX_PLAYER;
+ }
+
// use MidiFile for MIDI extensions
int lenURL = strlen(url);
for (int i = 0; i < NELEM(FILE_EXTS); ++i) {
@@ -619,6 +632,14 @@
LOGV("Create Test Player stub");
p = new TestPlayerStub();
break;
+ case AAH_RX_PLAYER:
+ LOGV(" create A@H RX Player");
+ p = createAAH_RXPlayer();
+ break;
+ case AAH_TX_PLAYER:
+ LOGV(" create A@H TX Player");
+ p = createAAH_TXPlayer();
+ break;
default:
LOGE("Unknown player type: %d", playerType);
return NULL;
@@ -975,9 +996,21 @@
status_t MediaPlayerService::Client::setVolume(float leftVolume, float rightVolume)
{
LOGV("[%d] setVolume(%f, %f)", mConnId, leftVolume, rightVolume);
- // TODO: for hardware output, call player instead
- Mutex::Autolock l(mLock);
- if (mAudioOutput != 0) mAudioOutput->setVolume(leftVolume, rightVolume);
+
+ // for hardware output, call player instead
+ sp<MediaPlayerBase> p = getPlayer();
+ {
+ Mutex::Autolock l(mLock);
+ if (p != 0 && p->hardwareOutput()) {
+ MediaPlayerHWInterface* hwp =
+ reinterpret_cast<MediaPlayerHWInterface*>(p.get());
+ return hwp->setVolume(leftVolume, rightVolume);
+ } else {
+ if (mAudioOutput != 0) mAudioOutput->setVolume(leftVolume, rightVolume);
+ return NO_ERROR;
+ }
+ }
+
return NO_ERROR;
}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 304df72..c9b552b 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -615,7 +615,7 @@
if (mGlobalActions == null) {
mGlobalActions = new GlobalActions(mContext);
}
- final boolean keyguardShowing = mKeyguardMediator.isShowingAndNotHidden();
+ final boolean keyguardShowing = keyguardIsShowingTq();
mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());
if (keyguardShowing) {
// since it took two seconds of long press to bring this up,
@@ -698,7 +698,10 @@
mContext = context;
mWindowManager = windowManager;
mPowerManager = powerManager;
- mKeyguardMediator = new KeyguardViewMediator(context, this, powerManager);
+ if ("0".equals(SystemProperties.get("ro.config.headless", "0"))) {
+ // don't create KeyguardViewMediator if headless
+ mKeyguardMediator = new KeyguardViewMediator(context, this, powerManager);
+ }
mHandler = new Handler();
mOrientationListener = new MyOrientationListener(mContext);
SettingsObserver settingsObserver = new SettingsObserver(mHandler);
@@ -1655,7 +1658,7 @@
* given the situation with the keyguard.
*/
void launchHomeFromHotKey() {
- if (mKeyguardMediator.isShowingAndNotHidden()) {
+ if (mKeyguardMediator != null && mKeyguardMediator.isShowingAndNotHidden()) {
// don't launch home if keyguard showing
} else if (!mHideLockScreen && mKeyguardMediator.isInputRestricted()) {
// when in keyguard restricted mode, must first verify unlock
@@ -2296,6 +2299,9 @@
/** {@inheritDoc} */
public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
+ // do nothing if headless
+ if (mKeyguardMediator == null) return;
+
// lid changed state
mLidOpen = lidOpen ? LID_OPEN : LID_CLOSED;
boolean awakeNow = mKeyguardMediator.doLidChangeTq(lidOpen);
@@ -2496,9 +2502,10 @@
// the same as if it were open and in front.
// This will prevent any keys other than the power button from waking the screen
// when the keyguard is hidden by another activity.
- final boolean keyguardActive = (isScreenOn ?
- mKeyguardMediator.isShowingAndNotHidden() :
- mKeyguardMediator.isShowing());
+ final boolean keyguardActive = (mKeyguardMediator == null ? false :
+ (isScreenOn ?
+ mKeyguardMediator.isShowingAndNotHidden() :
+ mKeyguardMediator.isShowing()));
if (!mSystemBooted) {
// If we have not yet booted, don't let key events do anything.
@@ -2757,7 +2764,7 @@
final boolean isWakeMotion = (policyFlags
& (WindowManagerPolicy.FLAG_WAKE | WindowManagerPolicy.FLAG_WAKE_DROPPED)) != 0;
if (isWakeMotion) {
- if (mKeyguardMediator.isShowing()) {
+ if (mKeyguardMediator != null && mKeyguardMediator.isShowing()) {
// If the keyguard is showing, let it decide what to do with the wake motion.
mKeyguardMediator.onWakeMotionWhenKeyguardShowingTq();
} else {
@@ -2812,10 +2819,13 @@
/** {@inheritDoc} */
public void screenTurnedOff(int why) {
EventLog.writeEvent(70000, 0);
+
synchronized (mLock) {
mScreenOn = false;
}
- mKeyguardMediator.onScreenTurnedOff(why);
+ if (mKeyguardMediator != null) {
+ mKeyguardMediator.onScreenTurnedOff(why);
+ }
synchronized (mLock) {
updateOrientationListenerLp();
updateLockScreenTimeout();
@@ -2837,7 +2847,9 @@
/** {@inheritDoc} */
public void screenTurnedOn() {
EventLog.writeEvent(70000, 1);
- mKeyguardMediator.onScreenTurnedOn();
+ if (mKeyguardMediator != null) {
+ mKeyguardMediator.onScreenTurnedOn();
+ }
synchronized (mLock) {
mScreenOn = true;
updateOrientationListenerLp();
@@ -2853,15 +2865,20 @@
/** {@inheritDoc} */
public void enableKeyguard(boolean enabled) {
- mKeyguardMediator.setKeyguardEnabled(enabled);
+ if (mKeyguardMediator != null) {
+ mKeyguardMediator.setKeyguardEnabled(enabled);
+ }
}
/** {@inheritDoc} */
public void exitKeyguardSecurely(OnKeyguardExitResult callback) {
- mKeyguardMediator.verifyUnlock(callback);
+ if (mKeyguardMediator != null) {
+ mKeyguardMediator.verifyUnlock(callback);
+ }
}
private boolean keyguardIsShowingTq() {
+ if (mKeyguardMediator == null) return false;
return mKeyguardMediator.isShowingAndNotHidden();
}
@@ -2873,11 +2890,13 @@
/** {@inheritDoc} */
public boolean isKeyguardSecure() {
+ if (mKeyguardMediator == null) return false;
return mKeyguardMediator.isSecure();
}
/** {@inheritDoc} */
public boolean inKeyguardRestrictedKeyInputMode() {
+ if (mKeyguardMediator == null) return false;
return mKeyguardMediator.isInputRestricted();
}
@@ -3094,8 +3113,10 @@
/** {@inheritDoc} */
public void systemReady() {
- // tell the keyguard
- mKeyguardMediator.onSystemReady();
+ if (mKeyguardMediator != null) {
+ // tell the keyguard
+ mKeyguardMediator.onSystemReady();
+ }
android.os.SystemProperties.set("dev.bootcomplete", "1");
synchronized (mLock) {
updateOrientationListenerLp();
@@ -3256,7 +3277,9 @@
public void run() {
synchronized (this) {
if (localLOGV) Log.v(TAG, "mScreenLockTimeout activating keyguard");
- mKeyguardMediator.doKeyguardTimeout();
+ if (mKeyguardMediator != null) {
+ mKeyguardMediator.doKeyguardTimeout();
+ }
mLockScreenTimerActive = false;
}
}
@@ -3264,7 +3287,8 @@
private void updateLockScreenTimeout() {
synchronized (mScreenLockTimeout) {
- boolean enable = (mAllowLockscreenWhenOn && mScreenOn && mKeyguardMediator.isSecure());
+ boolean enable = (mAllowLockscreenWhenOn && mScreenOn &&
+ mKeyguardMediator != null && mKeyguardMediator.isSecure());
if (mLockScreenTimerActive != enable) {
if (enable) {
if (localLOGV) Log.v(TAG, "setting lockscreen timer");
@@ -3457,7 +3481,7 @@
public void screenOnStoppedLw() {
if (mPowerManager.isScreenOn()) {
- if (!mKeyguardMediator.isShowingAndNotHidden()) {
+ if (mKeyguardMediator != null && !mKeyguardMediator.isShowingAndNotHidden()) {
long curTime = SystemClock.uptimeMillis();
mPowerManager.userActivity(curTime, false, LocalPowerManager.OTHER_EVENT);
}
diff --git a/services/aah_timesrv/Android.mk b/services/aah_timesrv/Android.mk
new file mode 100644
index 0000000..db9bdc0
--- /dev/null
+++ b/services/aah_timesrv/Android.mk
@@ -0,0 +1,28 @@
+LOCAL_PATH:= $(call my-dir)
+
+#
+# aah_timesrv
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ aah_common_clock_service.cpp \
+ aah_timesrv.cpp \
+ clock_recovery.cpp \
+ common_clock.cpp
+
+ifeq ($(AAH_TSDEBUG), true)
+LOCAL_SRC_FILES += diag_thread.cpp
+LOCAL_CFLAGS += -DAAH_TSDEBUG
+endif
+
+LOCAL_SHARED_LIBRARIES := \
+ libaah_timesrv_client \
+ libbinder \
+ libutils
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := aah_timesrv
+
+include $(BUILD_EXECUTABLE)
diff --git a/services/aah_timesrv/aah_common_clock_service.cpp b/services/aah_timesrv/aah_common_clock_service.cpp
new file mode 100644
index 0000000..88a98de3
--- /dev/null
+++ b/services/aah_timesrv/aah_common_clock_service.cpp
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2011 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 <aah_timesrv/local_clock.h>
+#include <binder/IServiceManager.h>
+#include <binder/IPCThreadState.h>
+#include <utils/String8.h>
+
+#include "aah_common_clock_service.h"
+#include "common_clock.h"
+
+namespace android {
+
+bool AAHCommonClock::init(CommonClock* common_clock,
+ LocalClock* local_clock) {
+ mCommonClock = common_clock;
+ mLocalClock = local_clock;
+ mTimelineID = kInvalidTimelineID;
+
+ return ((NULL != mCommonClock) && (NULL != mLocalClock));
+}
+
+status_t AAHCommonClock::dump(int fd, const Vector<String16>& args) {
+ const size_t SIZE = 256;
+ char buffer[SIZE];
+
+ if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
+ snprintf(buffer, SIZE, "Permission Denial: "
+ "can't dump AAHCommonClock from pid=%d, uid=%d\n",
+ IPCThreadState::self()->getCallingPid(),
+ IPCThreadState::self()->getCallingUid());
+ } else {
+ int64_t localTime = mLocalClock->getLocalTime();
+ int64_t commonTime;
+ bool synced = (OK == mCommonClock->localToCommon(localTime,
+ &commonTime));
+ if (synced) {
+ snprintf(buffer, SIZE,
+ "Common time synced\nLocal time: %lld\nCommon time: %lld\n"
+ "Timeline ID: %u\n",
+ localTime, commonTime, mTimelineID);
+ } else {
+ snprintf(buffer, SIZE,
+ "Common time not synced\nLocal time: %lld\n",
+ localTime);
+ }
+ }
+
+ write(fd, buffer, strlen(buffer));
+ return NO_ERROR;
+}
+
+sp<AAHCommonClock> AAHCommonClock::instantiate(CommonClock* common_clock,
+ LocalClock* local_clock) {
+ sp<AAHCommonClock> tcc = new AAHCommonClock();
+ if (tcc == NULL || !tcc->init(common_clock, local_clock))
+ return NULL;
+
+ defaultServiceManager()->addService(ICommonClock::kServiceName, tcc);
+ return tcc;
+}
+
+status_t AAHCommonClock::isCommonTimeValid(bool* valid,
+ uint32_t* timelineID) {
+ Mutex::Autolock lock(mLock);
+
+ *valid = mCommonClock->isValid();
+ *timelineID = mTimelineID;
+ return OK;
+}
+
+status_t AAHCommonClock::commonTimeToLocalTime(int64_t commonTime,
+ int64_t* localTime) {
+ return mCommonClock->commonToLocal(commonTime, localTime);
+}
+
+status_t AAHCommonClock::localTimeToCommonTime(int64_t localTime,
+ int64_t* commonTime) {
+ return mCommonClock->localToCommon(localTime, commonTime);
+}
+
+status_t AAHCommonClock::getCommonTime(int64_t* commonTime) {
+ return localTimeToCommonTime(mLocalClock->getLocalTime(), commonTime);
+}
+
+status_t AAHCommonClock::getCommonFreq(uint64_t* freq) {
+ *freq = mCommonClock->getCommonFreq();
+ return OK;
+}
+
+status_t AAHCommonClock::getLocalTime(int64_t* localTime) {
+ *localTime = mLocalClock->getLocalTime();
+ return OK;
+}
+
+status_t AAHCommonClock::getLocalFreq(uint64_t* freq) {
+ *freq = mLocalClock->getLocalFreq();
+ return OK;
+}
+
+status_t AAHCommonClock::registerListener(
+ const sp<ICommonClockListener>& listener) {
+ Mutex::Autolock lock(mLock);
+
+ // check whether this is a duplicate
+ for (size_t i = 0; i < mListeners.size(); i++) {
+ if (mListeners[i]->asBinder() == listener->asBinder())
+ return ALREADY_EXISTS;
+ }
+
+ mListeners.add(listener);
+ return listener->asBinder()->linkToDeath(this);
+}
+
+status_t AAHCommonClock::unregisterListener(
+ const sp<ICommonClockListener>& listener) {
+ Mutex::Autolock lock(mLock);
+
+ for (size_t i = 0; i < mListeners.size(); i++) {
+ if (mListeners[i]->asBinder() == listener->asBinder()) {
+ mListeners[i]->asBinder()->unlinkToDeath(this);
+ mListeners.removeAt(i);
+ return OK;
+ }
+ }
+
+ return NAME_NOT_FOUND;
+}
+
+void AAHCommonClock::binderDied(const wp<IBinder>& who) {
+ Mutex::Autolock lock(mLock);
+
+ for (size_t i = 0; i < mListeners.size(); i++) {
+ if (mListeners[i]->asBinder() == who) {
+ mListeners.removeAt(i);
+ return;
+ }
+ }
+}
+
+void AAHCommonClock::notifyOnClockSync(uint32_t timelineID) {
+ Mutex::Autolock lock(mLock);
+
+ mTimelineID = timelineID;
+ for (size_t i = 0; i < mListeners.size(); i++) {
+ mListeners[i]->onClockSync(mTimelineID);
+ }
+}
+
+void AAHCommonClock::notifyOnClockSyncLoss() {
+ Mutex::Autolock lock(mLock);
+
+ mTimelineID = kInvalidTimelineID;
+ for (size_t i = 0; i < mListeners.size(); i++) {
+ mListeners[i]->onClockSyncLoss();
+ }
+}
+
+}; // namespace android
diff --git a/services/aah_timesrv/aah_common_clock_service.h b/services/aah_timesrv/aah_common_clock_service.h
new file mode 100644
index 0000000..1100fc1
--- /dev/null
+++ b/services/aah_timesrv/aah_common_clock_service.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2011 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 <aah_timesrv/ICommonClock.h>
+
+#ifndef ANDROID_AAH_COMMONCLOCK_SERVICE_H
+#define ANDROID_AAH_COMMONCLOCK_SERVICE_H
+
+namespace android {
+
+class CommonClock;
+class LocalClock;
+
+class AAHCommonClock : public BnCommonClock,
+ public android::IBinder::DeathRecipient {
+ public:
+ static sp<AAHCommonClock> instantiate(CommonClock* common_clock,
+ LocalClock* local_clock);
+
+ virtual status_t dump(int fd, const Vector<String16>& args);
+
+ virtual status_t isCommonTimeValid(bool* valid, uint32_t *timelineID);
+ virtual status_t commonTimeToLocalTime(int64_t common_time,
+ int64_t* local_time);
+ virtual status_t localTimeToCommonTime(int64_t local_time,
+ int64_t* common_time);
+ virtual status_t getCommonTime(int64_t* common_time);
+ virtual status_t getCommonFreq(uint64_t* freq);
+ virtual status_t getLocalTime(int64_t* local_time);
+ virtual status_t getLocalFreq(uint64_t* freq);
+
+ virtual status_t registerListener(
+ const sp<ICommonClockListener>& listener);
+ virtual status_t unregisterListener(
+ const sp<ICommonClockListener>& listener);
+
+ void notifyOnClockSync(uint32_t timelineID);
+ void notifyOnClockSyncLoss();
+
+ private:
+ AAHCommonClock() {}
+ bool init(CommonClock* common_clock, LocalClock* local_clock);
+
+ virtual void binderDied(const wp<IBinder>& who);
+
+ CommonClock* mCommonClock;
+ LocalClock* mLocalClock;
+
+ // this lock serializes access to mTimelineID and mListeners
+ Mutex mLock;
+
+ uint32_t mTimelineID;
+ Vector<sp<ICommonClockListener> > mListeners;
+};
+
+}; // namespace android
+
+#endif // ANDROID_AAH_COMMONCLOCK_SERVICE_H
diff --git a/services/aah_timesrv/aah_timesrv.cpp b/services/aah_timesrv/aah_timesrv.cpp
new file mode 100644
index 0000000..451a751
--- /dev/null
+++ b/services/aah_timesrv/aah_timesrv.cpp
@@ -0,0 +1,1102 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <linux/if_ether.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <netinet/ip.h>
+#include <poll.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#define LOG_TAG "aah_timesrv"
+
+#include <aah_timesrv/local_clock.h>
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+#include <utils/Log.h>
+#include <utils/Timers.h>
+
+#include "aah_common_clock_service.h"
+#include "clock_recovery.h"
+#include "common_clock.h"
+
+namespace android {
+
+/***** time sync protocol packets *****/
+
+enum TimeServicePacketType {
+ TIME_PACKET_WHO_IS_MASTER_REQUEST = 1,
+ TIME_PACKET_WHO_IS_MASTER_RESPONSE,
+ TIME_PACKET_SYNC_REQUEST,
+ TIME_PACKET_SYNC_RESPONSE,
+ TIME_PACKET_MASTER_ANNOUNCEMENT,
+};
+
+struct TimeServicePacketHeader {
+ TimeServicePacketHeader(TimeServicePacketType type)
+ : magic(htonl(kMagic)),
+ packetType(htonl(type)),
+ kernelTxLocalTime(0),
+ kernelTxCommonTime(0),
+ kernelRxLocalTime(0) { }
+
+ TimeServicePacketType type() const {
+ return static_cast<TimeServicePacketType>(ntohl(packetType));
+ }
+
+ bool checkMagic() const {
+ return (ntohl(magic) == kMagic);
+ }
+
+ static const uint32_t kMagic;
+
+ // magic number identifying the protocol
+ uint32_t magic;
+
+ // TimeServicePacketType value
+ uint32_t packetType;
+
+ // placeholders for transmit/receive timestamps that can be filled in
+ // by a kernel netfilter driver
+ //
+ // local time (in the transmitter's domain) when this packet was sent
+ int64_t kernelTxLocalTime;
+ // common time when this packet was sent
+ int64_t kernelTxCommonTime;
+ // local time (in the receiver's domain) when this packet was received
+ int64_t kernelRxLocalTime;
+} __attribute__((packed));
+
+const uint32_t TimeServicePacketHeader::kMagic = 0x54756e67;
+
+// packet querying for a suitable master
+struct WhoIsMasterRequestPacket {
+ WhoIsMasterRequestPacket() : header(TIME_PACKET_WHO_IS_MASTER_REQUEST) {}
+
+ TimeServicePacketHeader header;
+
+ // device ID of the sender
+ uint64_t senderDeviceID;
+
+ // If this is kInvalidTimelineID, then any master can response to this
+ // request. If this is not kInvalidTimelineID, the only a master publishing
+ // the given timeline ID will respond.
+ uint32_t timelineID;
+} __attribute__((packed));
+
+// response to a WhoIsMaster request
+struct WhoIsMasterResponsePacket {
+ WhoIsMasterResponsePacket() : header(TIME_PACKET_WHO_IS_MASTER_RESPONSE) {}
+
+ TimeServicePacketHeader header;
+
+ // the master's device ID
+ uint64_t deviceID;
+
+ // the timeline ID being published by this master
+ uint32_t timelineID;
+} __attribute__((packed));
+
+// packet sent by a client requesting correspondence between local
+// and common time
+struct SyncRequestPacket {
+ SyncRequestPacket() : header(TIME_PACKET_SYNC_REQUEST) {}
+
+ TimeServicePacketHeader header;
+
+ // timeline that the client is following
+ uint32_t timelineID;
+
+ // local time when this request was transmitted
+ int64_t clientTxLocalTime;
+} __attribute__((packed));
+
+// response to a sync request sent by the master
+struct SyncResponsePacket {
+ SyncResponsePacket() : header(TIME_PACKET_SYNC_RESPONSE) {}
+
+ TimeServicePacketHeader header;
+
+ // flag that is set if the recipient of the sync request is not acting
+ // as a master for the requested timeline
+ uint32_t nak;
+
+ // local time when this request was transmitted by the client
+ int64_t clientTxLocalTime;
+
+ // common time when the master received the request
+ int64_t masterRxCommonTime;
+
+ // common time when the master transmitted the response
+ int64_t masterTxCommonTime;
+} __attribute__((packed));
+
+// announcement of the master's presence
+struct MasterAnnouncementPacket {
+ MasterAnnouncementPacket() : header(TIME_PACKET_MASTER_ANNOUNCEMENT) {}
+
+ TimeServicePacketHeader header;
+
+ // the master's device ID
+ uint64_t deviceID;
+
+ // the timeline ID being published by this master
+ uint32_t timelineID;
+} __attribute__((packed));
+
+/***** time service implementation *****/
+
+class AAHTimeService : public Thread {
+ public:
+ AAHTimeService();
+ ~AAHTimeService();
+
+ private:
+ bool threadLoop();
+
+ bool runStateMachine();
+ bool setup();
+
+ void assignTimelineID();
+ bool assignDeviceID();
+
+ static bool arbitrateMaster(uint64_t deviceID1, uint64_t deviceID2);
+
+ bool handlePacket();
+ bool handleWhoIsMasterRequest (const WhoIsMasterRequestPacket* request,
+ const sockaddr_in& srcAddr);
+ bool handleWhoIsMasterResponse(const WhoIsMasterResponsePacket* response,
+ const sockaddr_in& srcAddr);
+ bool handleSyncRequest (const SyncRequestPacket* request,
+ const sockaddr_in& srcAddr);
+ bool handleSyncResponse (const SyncResponsePacket* response,
+ const sockaddr_in& srcAddr);
+ bool handleMasterAnnouncement (const MasterAnnouncementPacket* packet,
+ const sockaddr_in& srcAddr);
+
+ bool handleTimeout();
+ bool handleTimeoutInitial();
+ bool handleTimeoutClient();
+ bool handleTimeoutMaster();
+ bool handleTimeoutRonin();
+ bool handleTimeoutWaitForElection();
+
+ bool sendWhoIsMasterRequest();
+ bool sendSyncRequest();
+ bool sendMasterAnnouncement();
+
+ bool becomeClient(const sockaddr_in& masterAddr,
+ uint64_t masterDeviceID,
+ uint32_t timelineID);
+ bool becomeMaster();
+ bool becomeRonin();
+ bool becomeWaitForElection();
+ bool becomeInitial();
+
+ void notifyClockSync();
+ void notifyClockSyncLoss();
+
+ enum State {
+ // the device just came up and is trying to discover the master
+ STATE_INITIAL,
+
+ // the device is a client of a master
+ STATE_CLIENT,
+
+ // the device is acting as master
+ STATE_MASTER,
+
+ // the device has lost contact with its master and needs to participate
+ // in the election of a new master
+ STATE_RONIN,
+
+ // the device is waiting for announcement of the newly elected master
+ STATE_WAIT_FOR_ELECTION,
+ };
+
+ State mState;
+ static const char* stateToString(State s);
+ void setState(State s);
+
+ // interval in milliseconds of the state machine's timeout
+ int mTimeoutMs;
+
+ // common clock, local clock abstraction, and clock recovery loop
+ CommonClock mCommonClock;
+ LocalClock mLocalClock;
+ ClockRecoveryLoop mClockRecovery;
+
+ // implementation of ICommonClock
+ sp<AAHCommonClock> mICommonClock;
+
+ // UDP socket for the time sync protocol
+ int mSocket;
+
+ // unique ID of this device
+ uint64_t mDeviceID;
+
+ // timestamp captured when a packet is received
+ int64_t mLastPacketRxLocalTime;
+
+ // multicast address used for master queries and announcements
+ struct sockaddr_in mMulticastAddr;
+
+ // ID of the timeline that this device is following
+ uint32_t mTimelineID;
+
+ // flag for whether the clock has been synced to a timeline
+ bool mClockSynced;
+
+ /*** status while in the Initial state ***/
+ int mInitial_WhoIsMasterRequestTimeouts;
+ static const int kInitial_NumWhoIsMasterRetries;
+ static const int kInitial_WhoIsMasterTimeoutMs;
+
+ /*** status while in the Client state ***/
+ struct sockaddr_in mClient_MasterAddr;
+ uint64_t mClient_MasterDeviceID;
+ bool mClient_SeenFirstSyncResponse;
+ bool mClient_SyncRequestPending;
+ int mClient_SyncRequestTimeouts;
+ static const int kClient_SyncRequestIntervalMs;
+ static const int kClient_SyncRequestTimeoutMs;
+ static const int kClient_NumSyncRequestRetries;
+
+ /*** status while in the Master state ***/
+ static const int kMaster_AnnouncementIntervalMs;
+
+ /*** status while in the Ronin state ***/
+ int mRonin_WhoIsMasterRequestTimeouts;
+ static const int kRonin_NumWhoIsMasterRetries;
+ static const int kRonin_WhoIsMasterTimeoutMs;
+
+ /*** status while in the WaitForElection state ***/
+ static const int kWaitForElection_TimeoutMs;
+
+ static const char* kServiceAddr;
+ static const uint16_t kServicePort;
+
+ static const int kInfiniteTimeout;
+};
+
+// multicast IP address used by this protocol
+const char* AAHTimeService::kServiceAddr = "224.128.87.87";
+
+// UDP port used by this protocol
+const uint16_t AAHTimeService::kServicePort = 8787;
+
+// mTimeoutMs value representing an infinite timeout
+const int AAHTimeService::kInfiniteTimeout = -1;
+
+/*** Initial state constants ***/
+
+// number of WhoIsMaster attempts sent before giving up
+const int AAHTimeService::kInitial_NumWhoIsMasterRetries = 6;
+
+// timeout used when waiting for a response to a WhoIsMaster request
+const int AAHTimeService::kInitial_WhoIsMasterTimeoutMs = 500;
+
+/*** Client state constants ***/
+
+// interval between sync requests sent to the master
+const int AAHTimeService::kClient_SyncRequestIntervalMs = 1000;
+
+// timeout used when waiting for a response to a sync request
+const int AAHTimeService::kClient_SyncRequestTimeoutMs = 400;
+
+// number of sync requests that can fail before a client assumes its master
+// is dead
+const int AAHTimeService::kClient_NumSyncRequestRetries = 5;
+
+/*** Master state constants ***/
+
+// timeout between announcements by the master
+const int AAHTimeService::kMaster_AnnouncementIntervalMs = 10000;
+
+/*** Ronin state constants ***/
+
+// number of WhoIsMaster attempts sent before declaring ourselves master
+const int AAHTimeService::kRonin_NumWhoIsMasterRetries = 4;
+
+// timeout used when waiting for a response to a WhoIsMaster request
+const int AAHTimeService::kRonin_WhoIsMasterTimeoutMs = 500;
+
+/*** WaitForElection state constants ***/
+
+// how long do we wait for an announcement from a master before
+// trying another election?
+const int AAHTimeService::kWaitForElection_TimeoutMs = 5000;
+
+AAHTimeService::AAHTimeService()
+ : Thread(false)
+ , mState(STATE_INITIAL)
+ , mTimeoutMs(kInfiniteTimeout)
+ , mClockRecovery(&mLocalClock, &mCommonClock)
+ , mSocket(-1)
+ , mDeviceID(0)
+ , mLastPacketRxLocalTime(0)
+ , mTimelineID(ICommonClock::kInvalidTimelineID)
+ , mClockSynced(false)
+ , mInitial_WhoIsMasterRequestTimeouts(0)
+ , mClient_MasterDeviceID(0)
+ , mClient_SeenFirstSyncResponse(false)
+ , mClient_SyncRequestPending(false)
+ , mClient_SyncRequestTimeouts(0)
+ , mRonin_WhoIsMasterRequestTimeouts(0) {
+ memset(&mMulticastAddr, 0, sizeof(mMulticastAddr));
+ memset(&mClient_MasterAddr, 0, sizeof(mClient_MasterAddr));
+}
+
+AAHTimeService::~AAHTimeService() {
+ if (mSocket != -1) {
+ close(mSocket);
+ mSocket = -1;
+ }
+}
+
+bool AAHTimeService::threadLoop() {
+ runStateMachine();
+ IPCThreadState::self()->stopProcess();
+ return false;
+}
+
+bool AAHTimeService::runStateMachine() {
+ if (!mLocalClock.initCheck())
+ return false;
+
+ if (!mCommonClock.init(mLocalClock.getLocalFreq()))
+ return false;
+
+ if (!setup())
+ return false;
+
+ // Enter the initial state; this will also send the first request to
+ // discover the master
+ becomeInitial();
+
+ // run the state machine
+ while (true) {
+ struct pollfd pfd = {mSocket, POLLIN, 0};
+ nsecs_t startNs = systemTime();
+ int rc = poll(&pfd, 1, mTimeoutMs);
+ int elapsedMs = ns2ms(systemTime() - startNs);
+ mLastPacketRxLocalTime = mLocalClock.getLocalTime();
+
+ if (rc == -1) {
+ LOGE("%s:%d poll failed", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ if (rc == 0) {
+ mTimeoutMs = kInfiniteTimeout;
+ if (!handleTimeout()) {
+ LOGE("handleTimeout failed");
+ }
+ } else {
+ if (mTimeoutMs != kInfiniteTimeout) {
+ mTimeoutMs = (mTimeoutMs > elapsedMs)
+ ? mTimeoutMs - elapsedMs
+ : 0;
+ }
+
+ if (pfd.revents & POLLIN) {
+ if (!handlePacket()) {
+ LOGE("handlePacket failed");
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+bool AAHTimeService::setup() {
+ int rc;
+
+ // seed the random number generator (used to generated timeline IDs)
+ srand(static_cast<unsigned int>(systemTime()));
+
+ // open a UDP socket for the timeline serivce
+ mSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (mSocket == -1) {
+ LOGE("%s:%d socket failed", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ // initialize the multicast address
+ memset(&mMulticastAddr, 0, sizeof(mMulticastAddr));
+ mMulticastAddr.sin_family = AF_INET;
+ inet_aton(kServiceAddr, &mMulticastAddr.sin_addr);
+ mMulticastAddr.sin_port = htons(kServicePort);
+
+ // bind the socket to the time service port on all interfaces
+ struct sockaddr_in bindAddr;
+ memset(&bindAddr, 0, sizeof(bindAddr));
+ bindAddr.sin_family = AF_INET;
+ bindAddr.sin_addr.s_addr = htonl(INADDR_ANY);
+ bindAddr.sin_port = htons(kServicePort);
+ rc = bind(mSocket, reinterpret_cast<const sockaddr *>(&bindAddr),
+ sizeof(bindAddr));
+ if (rc) {
+ LOGE("%s:%d bind failed", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ // add the socket to the multicast group
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr = mMulticastAddr.sin_addr;
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ rc = setsockopt(mSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ &mreq, sizeof(mreq));
+ if (rc == -1) {
+ LOGE("%s:%d setsockopt failed (err = %d)",
+ __PRETTY_FUNCTION__, __LINE__, errno);
+ return false;
+ }
+
+ // disable loopback of multicast packets
+ const int zero = 0;
+ rc = setsockopt(mSocket, IPPROTO_IP, IP_MULTICAST_LOOP,
+ &zero, sizeof(zero));
+ if (rc == -1) {
+ LOGE("%s:%d setsockopt failed", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ // get the device's unique ID
+ if (!assignDeviceID())
+ return false;
+
+ // start the ICommonClock service
+ mICommonClock = AAHCommonClock::instantiate(&mCommonClock, &mLocalClock);
+ if (mICommonClock == NULL)
+ return false;
+
+ return true;
+}
+
+// generate a unique device ID that can be used for arbitration
+bool AAHTimeService::assignDeviceID() {
+ // on the PandaBoard, derive the device ID from the MAC address of
+ // the eth0 interface
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_addr.sa_family = AF_INET;
+ strlcpy(ifr.ifr_name, "eth0", IFNAMSIZ);
+
+ int rc = ioctl(mSocket, SIOCGIFHWADDR, &ifr);
+ if (rc) {
+ LOGE("%s:%d ioctl failed", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ if (ifr.ifr_addr.sa_family != ARPHRD_ETHER) {
+ LOGE("%s:%d got non-Ethernet address", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ mDeviceID = 0;
+ for (int i = 0; i < ETH_ALEN; i++) {
+ mDeviceID = (mDeviceID << 8) | ifr.ifr_hwaddr.sa_data[i];
+ }
+
+ return true;
+}
+
+// generate a new timeline ID
+void AAHTimeService::assignTimelineID() {
+ do {
+ mTimelineID = rand();
+ } while (mTimelineID == ICommonClock::kInvalidTimelineID);
+}
+
+// Select a preference between the device IDs of two potential masters.
+// Returns true if the first ID wins, or false if the second ID wins.
+bool AAHTimeService::arbitrateMaster(uint64_t deviceID1,
+ uint64_t deviceID2) {
+ return (deviceID1 > deviceID2);
+}
+
+bool AAHTimeService::handlePacket() {
+ const int kMaxPacketSize = 100;
+ uint8_t buf[kMaxPacketSize];
+ struct sockaddr_in srcAddr;
+ socklen_t srcAddrLen = sizeof(srcAddr);
+
+ ssize_t recvBytes = recvfrom(
+ mSocket, buf, sizeof(buf), 0,
+ reinterpret_cast<const sockaddr *>(&srcAddr), &srcAddrLen);
+
+ if (recvBytes == -1) {
+ LOGE("%s:%d recvfrom failed", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ if (recvBytes < static_cast<ssize_t>(sizeof(TimeServicePacketHeader)))
+ return false;
+
+ TimeServicePacketHeader* header =
+ reinterpret_cast<TimeServicePacketHeader*>(buf);
+
+ if (!header->checkMagic())
+ return false;
+
+ bool result;
+
+ switch (header->type()) {
+ case TIME_PACKET_WHO_IS_MASTER_REQUEST: {
+ if (recvBytes <
+ static_cast<ssize_t>(sizeof(WhoIsMasterRequestPacket))) {
+ result = false;
+ } else {
+ result = handleWhoIsMasterRequest(
+ reinterpret_cast<WhoIsMasterRequestPacket*>(buf),
+ srcAddr);
+ }
+ } break;
+
+ case TIME_PACKET_WHO_IS_MASTER_RESPONSE: {
+ if (recvBytes <
+ static_cast<ssize_t>(sizeof(WhoIsMasterResponsePacket))) {
+ result = false;
+ } else {
+ result = handleWhoIsMasterResponse(
+ reinterpret_cast<WhoIsMasterResponsePacket*>(buf),
+ srcAddr);
+ }
+ } break;
+
+ case TIME_PACKET_SYNC_REQUEST: {
+ if (recvBytes < static_cast<ssize_t>(sizeof(SyncRequestPacket))) {
+ result = false;
+ } else {
+ result = handleSyncRequest(
+ reinterpret_cast<SyncRequestPacket*>(buf),
+ srcAddr);
+ }
+ } break;
+
+ case TIME_PACKET_SYNC_RESPONSE: {
+ if (recvBytes < static_cast<ssize_t>(sizeof(SyncResponsePacket))) {
+ result = false;
+ } else {
+ result = handleSyncResponse(
+ reinterpret_cast<SyncResponsePacket*>(buf),
+ srcAddr);
+ }
+ } break;
+
+ case TIME_PACKET_MASTER_ANNOUNCEMENT: {
+ if (recvBytes <
+ static_cast<ssize_t>(sizeof(MasterAnnouncementPacket))) {
+ result = false;
+ } else {
+ result = handleMasterAnnouncement(
+ reinterpret_cast<MasterAnnouncementPacket*>(buf),
+ srcAddr);
+ }
+ } break;
+
+ default: {
+ LOGD("%s:%d unknown packet type", __PRETTY_FUNCTION__, __LINE__);
+ result = false;
+ } break;
+ }
+
+ return result;
+}
+
+bool AAHTimeService::handleTimeout() {
+ switch (mState) {
+ case STATE_INITIAL:
+ return handleTimeoutInitial();
+ case STATE_CLIENT:
+ return handleTimeoutClient();
+ case STATE_MASTER:
+ return handleTimeoutMaster();
+ case STATE_RONIN:
+ return handleTimeoutRonin();
+ case STATE_WAIT_FOR_ELECTION:
+ return handleTimeoutWaitForElection();
+ }
+
+ return false;
+}
+
+bool AAHTimeService::handleTimeoutInitial() {
+ if (++mInitial_WhoIsMasterRequestTimeouts ==
+ kInitial_NumWhoIsMasterRetries) {
+ // none of our attempts to discover a master succeeded, so make
+ // this device the master
+ return becomeMaster();
+ } else {
+ // retry the WhoIsMaster request
+ return sendWhoIsMasterRequest();
+ }
+}
+
+bool AAHTimeService::handleTimeoutClient() {
+ if (mClient_SyncRequestPending) {
+ mClient_SyncRequestPending = false;
+
+ if (++mClient_SyncRequestTimeouts < kClient_NumSyncRequestRetries) {
+ // a sync request has timed out, so retry
+ return sendSyncRequest();
+ } else {
+ // The master has failed to respond to a sync request for too many
+ // times in a row. Assume the master is dead and start electing
+ // a new master.
+ return becomeRonin();
+ }
+ } else {
+ // initiate the next sync request
+ return sendSyncRequest();
+ }
+}
+
+bool AAHTimeService::handleTimeoutMaster() {
+ // send another announcement from the master
+ return sendMasterAnnouncement();
+}
+
+bool AAHTimeService::handleTimeoutRonin() {
+ if (++mRonin_WhoIsMasterRequestTimeouts == kRonin_NumWhoIsMasterRetries) {
+ // no other master is out there, so we won the election
+ return becomeMaster();
+ } else {
+ return sendWhoIsMasterRequest();
+ }
+}
+
+bool AAHTimeService::handleTimeoutWaitForElection() {
+ return becomeRonin();
+}
+
+bool AAHTimeService::handleWhoIsMasterRequest(
+ const WhoIsMasterRequestPacket* request,
+ const sockaddr_in& srcAddr) {
+ if (mState == STATE_MASTER) {
+ // is this request related to this master's timeline?
+ if (ntohl(request->timelineID) != ICommonClock::kInvalidTimelineID &&
+ ntohl(request->timelineID) != mTimelineID)
+ return true;
+
+ WhoIsMasterResponsePacket response;
+ response.deviceID = htonq(mDeviceID);
+ response.timelineID = htonl(mTimelineID);
+
+ ssize_t sendBytes = sendto(
+ mSocket, &response, sizeof(response), 0,
+ reinterpret_cast<const sockaddr *>(&srcAddr),
+ sizeof(srcAddr));
+ if (sendBytes == -1) {
+ LOGE("%s:%d sendto failed", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+ } else if (mState == STATE_RONIN) {
+ // if we hear a WhoIsMaster request from another device following
+ // the same timeline and that device wins arbitration, then we will stop
+ // trying to elect ourselves master and will instead wait for an
+ // announcement from the election winner
+ if (ntohl(request->timelineID) != mTimelineID)
+ return true;
+
+ if (arbitrateMaster(ntohq(request->senderDeviceID), mDeviceID))
+ return becomeWaitForElection();
+
+ return true;
+ } else if (mState == STATE_INITIAL) {
+ // If a group of devices booted simultaneously (e.g. after a power
+ // outage) and all of them are in the initial state and there is no
+ // master, then each device may time out and declare itself master at
+ // the same time. To avoid this, listen for
+ // WhoIsMaster(InvalidTimeline) requests from peers. If we would lose
+ // arbitration against that peer, reset our timeout count so that the
+ // peer has a chance to become master before we time out.
+ if (ntohl(request->timelineID) == ICommonClock::kInvalidTimelineID &&
+ arbitrateMaster(ntohq(request->senderDeviceID), mDeviceID)) {
+ mInitial_WhoIsMasterRequestTimeouts = 0;
+ }
+ }
+
+ return true;
+}
+
+bool AAHTimeService::handleWhoIsMasterResponse(
+ const WhoIsMasterResponsePacket* response,
+ const sockaddr_in& srcAddr) {
+ if (mState == STATE_INITIAL || mState == STATE_RONIN) {
+ return becomeClient(srcAddr,
+ ntohq(response->deviceID),
+ ntohl(response->timelineID));
+ } else if (mState == STATE_CLIENT) {
+ // if we get multiple responses because there are multiple devices
+ // who believe that they are master, then follow the master that
+ // wins arbitration
+ if (arbitrateMaster(ntohq(response->deviceID),
+ mClient_MasterDeviceID)) {
+ return becomeClient(srcAddr,
+ ntohq(response->deviceID),
+ ntohl(response->timelineID));
+ }
+ }
+
+ return true;
+}
+
+bool AAHTimeService::handleSyncRequest(const SyncRequestPacket* request,
+ const sockaddr_in& srcAddr) {
+ SyncResponsePacket response;
+ if (mState == STATE_MASTER && ntohl(request->timelineID) == mTimelineID) {
+ int64_t rxLocalTime = (request->header.kernelRxLocalTime) ?
+ ntohq(request->header.kernelRxLocalTime) : mLastPacketRxLocalTime;
+
+ int64_t rxCommonTime;
+ if (OK != mCommonClock.localToCommon(rxLocalTime, &rxCommonTime)) {
+ return false;
+ }
+
+ // TODO(johngro) : now that common time has moved out of the kernel, in
+ // order to turn netfilter based timestamping of transmit and receive
+ // times, we will need to make some changes to the sync request/resposne
+ // packet structure. Currently masters send back to clients RX and TX
+ // times expressed in common time (since the master's local time is not
+ // useful to the client). Now that the netfilter driver has no access
+ // to common time, then netfilter driver should capture the master's rx
+ // local time as the packet comes in, and put the master's tx local time
+ // into the packet as the response goes out. The user mode code (this
+ // function) needs to add the master's local->common transformation to
+ // the packet so that the client can make use of the data.
+ int64_t txLocalTime = mLocalClock.getLocalTime();;
+ int64_t txCommonTime;
+ if (OK != mCommonClock.localToCommon(txLocalTime, &txCommonTime)) {
+ return false;
+ }
+
+ response.nak = htonl(0);
+ response.clientTxLocalTime = (request->header.kernelTxLocalTime) ?
+ request->header.kernelTxLocalTime : request->clientTxLocalTime;
+ response.masterRxCommonTime = htonq(rxCommonTime);
+ response.masterTxCommonTime = htonq(txCommonTime);
+ } else {
+ response.nak = htonl(1);
+ response.clientTxLocalTime = htonl(0);
+ response.masterRxCommonTime = htonl(0);
+ response.masterTxCommonTime = htonl(0);
+ }
+
+ ssize_t sendBytes = sendto(
+ mSocket, &response, sizeof(response), 0,
+ reinterpret_cast<const sockaddr *>(&srcAddr),
+ sizeof(srcAddr));
+ if (sendBytes == -1) {
+ LOGE("%s:%d sendto failed", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ return true;
+}
+
+bool AAHTimeService::handleSyncResponse(
+ const SyncResponsePacket* response,
+ const sockaddr_in& srcAddr) {
+ if (mState != STATE_CLIENT)
+ return true;
+
+ if (ntohl(response->nak)) {
+ // if our master is no longer accepting requests, then we need to find
+ // a new master
+ return becomeRonin();
+ }
+
+ mClient_SyncRequestPending = 0;
+ mClient_SyncRequestTimeouts = 0;
+
+ bool result;
+ if (!mClient_SeenFirstSyncResponse) {
+ // the first request/response exchange between a client and a master
+ // may take unusually long due to ARP, so discard it.
+ mClient_SeenFirstSyncResponse = true;
+ result = true;
+ } else {
+ int64_t clientTxLocalTime = ntohq(response->clientTxLocalTime);
+ int64_t clientRxLocalTime = (response->header.kernelRxLocalTime)
+ ? ntohq(response->header.kernelRxLocalTime)
+ : mLastPacketRxLocalTime;
+ int64_t masterTxCommonTime = (response->header.kernelTxCommonTime)
+ ? ntohq(response->header.kernelTxCommonTime)
+ : ntohq(response->masterTxCommonTime);
+ int64_t masterRxCommonTime = ntohq(response->masterRxCommonTime);
+
+ int64_t rtt = (clientRxLocalTime - clientTxLocalTime);
+ int64_t avgLocal = (clientTxLocalTime + clientRxLocalTime) >> 1;
+ int64_t avgCommon = (masterTxCommonTime + masterRxCommonTime) >> 1;
+ result = mClockRecovery.pushDisciplineEvent(avgLocal, avgCommon, rtt);
+
+ if (result) {
+ // indicate to listeners that we've synced to the common timeline
+ notifyClockSync();
+ } else {
+ LOGE("Panic! Observed clock sync error is too high to tolerate,"
+ " resetting state machine and starting over.");
+ notifyClockSyncLoss();
+ return becomeInitial();
+ }
+ }
+
+ mTimeoutMs = kClient_SyncRequestIntervalMs;
+ return result;
+}
+
+bool AAHTimeService::handleMasterAnnouncement(
+ const MasterAnnouncementPacket* packet,
+ const sockaddr_in& srcAddr) {
+ uint64_t newDeviceID = ntohq(packet->deviceID);
+ uint32_t newTimelineID = ntohl(packet->timelineID);
+
+ if (mState == STATE_INITIAL ||
+ mState == STATE_RONIN ||
+ mState == STATE_WAIT_FOR_ELECTION) {
+ // if we aren't currently following a master, then start following
+ // this new master
+ return becomeClient(srcAddr, newDeviceID, newTimelineID);
+ } else if (mState == STATE_CLIENT) {
+ // if the new master wins arbitration against our current master,
+ // then become a client of the new master
+ if (arbitrateMaster(newDeviceID, mClient_MasterDeviceID))
+ return becomeClient(srcAddr, newDeviceID, newTimelineID);
+ } else if (mState == STATE_MASTER) {
+ // two masters are competing - if the new one wins arbitration, then
+ // cease acting as master
+ if (arbitrateMaster(newDeviceID, mDeviceID))
+ return becomeClient(srcAddr, newDeviceID, newTimelineID);
+ }
+
+ return true;
+}
+
+bool AAHTimeService::sendWhoIsMasterRequest() {
+ assert(mState == STATE_INITIAL || mState == STATE_RONIN);
+
+ WhoIsMasterRequestPacket request;
+ request.senderDeviceID = htonq(mDeviceID);
+ request.timelineID = htonl(mTimelineID);
+
+ ssize_t sendBytes = sendto(
+ mSocket, &request, sizeof(request), 0,
+ reinterpret_cast<const sockaddr *>(&mMulticastAddr),
+ sizeof(mMulticastAddr));
+ if (sendBytes == -1) {
+ LOGE("%s:%d sendto failed", __PRETTY_FUNCTION__, __LINE__);
+ }
+
+ if (mState == STATE_INITIAL) {
+ mTimeoutMs = kInitial_WhoIsMasterTimeoutMs;
+ } else {
+ mTimeoutMs = kRonin_WhoIsMasterTimeoutMs;
+ }
+
+ return (sendBytes != -1);
+}
+
+bool AAHTimeService::sendSyncRequest() {
+ assert(mState == STATE_CLIENT);
+
+ SyncRequestPacket request;
+ request.timelineID = htonl(mTimelineID);
+ request.clientTxLocalTime = htonq(mLocalClock.getLocalTime());
+
+ ssize_t sendBytes = sendto(
+ mSocket, &request, sizeof(request), 0,
+ reinterpret_cast<const sockaddr *>(&mClient_MasterAddr),
+ sizeof(mClient_MasterAddr));
+ if (sendBytes == -1) {
+ LOGE("%s:%d sendto failed", __PRETTY_FUNCTION__, __LINE__);
+ }
+
+ mTimeoutMs = kClient_SyncRequestTimeoutMs;
+ mClient_SyncRequestPending = true;
+ return (sendBytes != -1);
+}
+
+bool AAHTimeService::sendMasterAnnouncement() {
+ assert(mState == STATE_MASTER);
+
+ MasterAnnouncementPacket announce;
+ announce.deviceID = htonq(mDeviceID);
+ announce.timelineID = htonl(mTimelineID);
+
+ ssize_t sendBytes = sendto(
+ mSocket, &announce, sizeof(announce), 0,
+ reinterpret_cast<const sockaddr *>(&mMulticastAddr),
+ sizeof(mMulticastAddr));
+ if (sendBytes == -1) {
+ LOGE("%s:%d sendto failed", __PRETTY_FUNCTION__, __LINE__);
+ }
+
+ mTimeoutMs = kMaster_AnnouncementIntervalMs;
+ return (sendBytes != -1);
+}
+
+bool AAHTimeService::becomeClient(const sockaddr_in& masterAddr,
+ uint64_t masterDeviceID,
+ uint32_t timelineID) {
+ mClient_MasterAddr = masterAddr;
+ mClient_MasterDeviceID = masterDeviceID;
+
+ if (mTimelineID != timelineID) {
+ // start following a new timeline
+ mTimelineID = timelineID;
+ mClockRecovery.reset(true, true);
+ notifyClockSyncLoss();
+ } else {
+ // start following a new master on the existing timeline
+ mClockRecovery.reset(false, true);
+ }
+
+ mClient_SyncRequestPending = 0;
+ mClient_SyncRequestTimeouts = 0;
+ mClient_SeenFirstSyncResponse = false;
+
+ setState(STATE_CLIENT);
+
+ // add some jitter to when the various clients send their requests
+ // in order to reduce the likelihood that a group of clients overload
+ // the master after receiving a master announcement
+ usleep((rand() % 100) * 1000);
+
+ return sendSyncRequest();
+}
+
+bool AAHTimeService::becomeMaster() {
+ if (mTimelineID == ICommonClock::kInvalidTimelineID) {
+ // this device has not been following any existing timeline,
+ // so it will create a new timeline and declare itself master
+ assert(!mCommonClock.isValid());
+
+ // set the common time basis
+ mCommonClock.setBasis(mLocalClock.getLocalTime(), 0);
+
+ // assign an arbitrary timeline iD
+ assignTimelineID();
+
+ // notify listeners that we've created a common timeline
+ notifyClockSync();
+ }
+
+ mClockRecovery.reset(false, true);
+
+ setState(STATE_MASTER);
+ return sendMasterAnnouncement();
+}
+
+bool AAHTimeService::becomeRonin() {
+ mRonin_WhoIsMasterRequestTimeouts = 0;
+ setState(STATE_RONIN);
+ return sendWhoIsMasterRequest();
+}
+
+bool AAHTimeService::becomeWaitForElection() {
+ setState(STATE_WAIT_FOR_ELECTION);
+ mTimeoutMs = kWaitForElection_TimeoutMs;
+ return true;
+}
+
+bool AAHTimeService::becomeInitial() {
+ setState(STATE_INITIAL);
+
+ // reset clock recovery
+ mClockRecovery.reset(true, true);
+
+ // reset internal state bookkeeping.
+ mTimeoutMs = kInfiniteTimeout;
+ mLastPacketRxLocalTime = 0;
+ mTimelineID = ICommonClock::kInvalidTimelineID;
+ mClockSynced = false;
+ mInitial_WhoIsMasterRequestTimeouts = 0;
+ mClient_MasterDeviceID = 0;
+ mClient_SeenFirstSyncResponse = false;
+ mClient_SyncRequestPending = false;
+ mClient_SyncRequestTimeouts = 0;
+ mRonin_WhoIsMasterRequestTimeouts = 0;
+
+ // send the first request to discover the master
+ return sendWhoIsMasterRequest();
+}
+
+void AAHTimeService::notifyClockSync() {
+ if (!mClockSynced) {
+ mICommonClock->notifyOnClockSync(mTimelineID);
+ mClockSynced = true;
+ }
+}
+
+void AAHTimeService::notifyClockSyncLoss() {
+ if (mClockSynced) {
+ mICommonClock->notifyOnClockSyncLoss();
+ mClockSynced = false;
+ }
+}
+
+void AAHTimeService::setState(State s) {
+ mState = s;
+ LOGI("State transition; state is now %s", stateToString(s));
+}
+
+const char* AAHTimeService::stateToString(State s) {
+ switch(s) {
+ case STATE_INITIAL:
+ return "INITIAL";
+ case STATE_CLIENT:
+ return "CLIENT";
+ case STATE_MASTER:
+ return "MASTER";
+ case STATE_RONIN:
+ return "RONIN";
+ case STATE_WAIT_FOR_ELECTION:
+ return "WAIT_FOR_ELECTION";
+ default:
+ return "unknown";
+ }
+}
+
+} // namespace android
+
+int main(int argc, char *argv[]) {
+ using namespace android;
+
+ sp<AAHTimeService> service = new AAHTimeService();
+ if (service == NULL)
+ return 1;
+
+ ProcessState::self()->startThreadPool();
+ service->run("AAHTimeService", ANDROID_PRIORITY_NORMAL);
+
+ IPCThreadState::self()->joinThreadPool();
+ return 0;
+}
diff --git a/services/aah_timesrv/clock_recovery.cpp b/services/aah_timesrv/clock_recovery.cpp
new file mode 100644
index 0000000..732392b
--- /dev/null
+++ b/services/aah_timesrv/clock_recovery.cpp
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#define __STDC_LIMIT_MACROS
+#define LOG_TAG "aah_timesrv"
+#include <utils/Log.h>
+#include <stdint.h>
+
+#include <aah_timesrv/local_clock.h>
+#include <assert.h>
+
+#include "clock_recovery.h"
+#include "common_clock.h"
+#ifdef AAH_TSDEBUG
+#include "diag_thread.h"
+#endif
+
+namespace android {
+
+ClockRecoveryLoop::ClockRecoveryLoop(LocalClock* local_clock,
+ CommonClock* common_clock) {
+ assert(NULL != local_clock);
+ assert(NULL != common_clock);
+
+ local_clock_ = local_clock;
+ common_clock_ = common_clock;
+
+ local_clock_can_slew_ = local_clock_->initCheck() &&
+ (local_clock_->setLocalSlew(0) == OK);
+
+ computePIDParams();
+ reset(true, true);
+
+#ifdef AAH_TSDEBUG
+ diag_thread_ = new DiagThread(common_clock_, local_clock_);
+ if (diag_thread_ != NULL) {
+ status_t res = diag_thread_->startWorkThread();
+ if (res != OK)
+ LOGW("Failed to start A@H clock recovery diagnostic thread.");
+ } else
+ LOGW("Failed to allocate diagnostic thread.");
+#endif
+}
+
+ClockRecoveryLoop::~ClockRecoveryLoop() {
+#ifdef AAH_TSDEBUG
+ diag_thread_->stopWorkThread();
+#endif
+}
+
+void ClockRecoveryLoop::reset(bool position, bool frequency) {
+ Mutex::Autolock lock(&lock_);
+ reset_l(position, frequency);
+}
+
+uint32_t ClockRecoveryLoop::findMinRTTNdx(DisciplineDataPoint* data,
+ uint32_t count) {
+ uint32_t min_rtt = 0;
+ for (uint32_t i = 1; i < count; ++i)
+ if (data[min_rtt].rtt > data[i].rtt)
+ min_rtt = i;
+
+ return min_rtt;
+}
+
+bool ClockRecoveryLoop::pushDisciplineEvent(int64_t local_time,
+ int64_t nominal_common_time,
+ int64_t rtt) {
+ Mutex::Autolock lock(&lock_);
+
+ // If we have not defined a basis for common time, then we need to use these
+ // initial points to do so. In order to avoid significant initial error
+ // from a particularly bad startup data point, we collect the first N data
+ // points and choose the best of them before moving on.
+ if (!common_clock_->isValid()) {
+ if (startup_filter_wr_ < kStartupFilterSize) {
+ DisciplineDataPoint& d = startup_filter_data_[startup_filter_wr_];
+ d.local_time = local_time;
+ d.nominal_common_time = nominal_common_time;
+ d.rtt = rtt;
+ startup_filter_wr_++;
+ }
+
+ if (startup_filter_wr_ == kStartupFilterSize) {
+ uint32_t min_rtt = findMinRTTNdx(startup_filter_data_,
+ kStartupFilterSize);
+
+ common_clock_->setBasis(
+ startup_filter_data_[min_rtt].local_time,
+ startup_filter_data_[min_rtt].nominal_common_time);
+ }
+
+ return true;
+ }
+
+ int64_t observed_common;
+ int64_t delta;
+ int32_t delta32;
+ int32_t correction_cur;
+ int32_t correction_cur_P = 0;
+ int32_t correction_cur_I = 0;
+ int32_t correction_cur_D = 0;
+
+ if (OK != common_clock_->localToCommon(local_time, &observed_common)) {
+ // Since we just checked to make certain that this conversion was valid,
+ // and no one else in the system should be messing with it, if this
+ // conversion is suddenly invalid, it is a good reason to panic.
+ LOGE("Failed to convert local time to common time in %s:%d",
+ __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ // Implement a filter which should match NTP filtering behavior when a
+ // client is associated with only one peer of lower stratum. Basically,
+ // always use the best of the N last data points, where best is defined as
+ // lowest round trip time. NTP uses an N of 8; we use a value of 6.
+ //
+ // TODO(johngro) : experiment with other filter strategies. The goal here
+ // is to mitigate the effects of high RTT data points which typically have
+ // large asymmetries in the TX/RX legs. Downside of the existing NTP
+ // approach (particularly because of the PID controller we are using to
+ // produce the control signal from the filtered data) are that the rate at
+ // which discipline events are actually acted upon becomes irregular and can
+ // become drawn out (the time between actionable event can go way up). If
+ // the system receives a strong high quality data point, the proportional
+ // component of the controller can produce a strong correction which is left
+ // in place for too long causing overshoot. In addition, the integral
+ // component of the system currently is an approximation based on the
+ // assumption of a more or less homogeneous sampling of the error. Its
+ // unclear what the effect of undermining this assumption would be right
+ // now.
+
+ // Two ideas which come to mind immediately would be to...
+ // 1) Keep a history of more data points (32 or so) and ignore data points
+ // whose RTT is more than a certain number of standard deviations outside
+ // of the norm.
+ // 2) Eliminate the PID controller portion of this system entirely.
+ // Instead, move to a system which uses a very wide filter (128 data
+ // points or more) with a sum-of-least-squares line fitting approach to
+ // tracking the long term drift. This would take the place of the I
+ // component in the current PID controller. Also use a much more narrow
+ // outlier-rejector filter (as described in #1) to drive a short term
+ // correction factor similar to the P component of the PID controller.
+ assert(filter_wr_ < kFilterSize);
+ filter_data_[filter_wr_].local_time = local_time;
+ filter_data_[filter_wr_].observed_common_time = observed_common;
+ filter_data_[filter_wr_].nominal_common_time = nominal_common_time;
+ filter_data_[filter_wr_].rtt = rtt;
+ filter_data_[filter_wr_].point_used = false;
+ filter_wr_ = (filter_wr_ + 1) % kFilterSize;
+ if (!filter_wr_)
+ filter_full_ = true;
+
+ // Scan the accumulated data for the point with the minimum RTT. If that
+ // point has never been used before, go ahead and use it now, otherwise just
+ // do nothing.
+ uint32_t scan_end = filter_full_ ? kFilterSize : filter_wr_;
+ uint32_t min_rtt = findMinRTTNdx(filter_data_, scan_end);
+ if (filter_data_[min_rtt].point_used)
+ return true;
+
+ local_time = filter_data_[min_rtt].local_time;
+ observed_common = filter_data_[min_rtt].observed_common_time;
+ nominal_common_time = filter_data_[min_rtt].nominal_common_time;
+ filter_data_[min_rtt].point_used = true;
+
+ // Compute the error then clamp to the panic threshold. If we ever exceed
+ // this amt of error, its time to panic and reset the system.
+ delta = nominal_common_time - observed_common;
+ if ((delta > panic_thresh_) || (delta < -panic_thresh_)) {
+ // PANIC!!!
+ //
+ // TODO(johngro) : need to report this to the upper levels of
+ // code.
+ reset_l(false, true);
+ return false;
+ } else
+ delta32 = delta;
+
+ // Accumulate error into the integrated error, then clamp.
+ integrated_error_ += delta32;
+ if (integrated_error_ > pid_params_.integrated_delta_max)
+ integrated_error_ = pid_params_.integrated_delta_max;
+ else if (integrated_error_ < pid_params_.integrated_delta_min)
+ integrated_error_ = pid_params_.integrated_delta_min;
+
+ // Compute the difference in error between last time and this time, then
+ // update last_delta_
+ int32_t input_D = last_delta_valid_ ? delta32 - last_delta_ : 0;
+ last_delta_valid_ = true;
+ last_delta_ = delta32;
+
+ // Compute the various components of the correction value.
+ correction_cur_P = doGainScale(pid_params_.gain_P, delta32);
+ correction_cur_I = doGainScale(pid_params_.gain_I, integrated_error_);
+
+ // TODO(johngro) : the differential portion of this code used to rely
+ // upon a completely homogeneous discipline frequency. Now that the
+ // discipline frequency may not be homogeneous, its probably important
+ // to divide by the amt of time between discipline events during the
+ // gain calculation.
+ correction_cur_D = doGainScale(pid_params_.gain_D, input_D);
+
+ // Compute the final correction value and clamp.
+ correction_cur = correction_cur_P + correction_cur_I + correction_cur_D;
+ if (correction_cur < pid_params_.correction_min)
+ correction_cur = pid_params_.correction_min;
+ else if (correction_cur > pid_params_.correction_max)
+ correction_cur = pid_params_.correction_max;
+
+ // If there was a change in the amt of correction to use, update the
+ // system.
+ if (correction_cur_ != correction_cur) {
+ correction_cur_ = correction_cur;
+ applySlew();
+ }
+
+ LOGV("observed %lld nominal %lld delta = %5lld "
+ "int = %7d correction %3d (P %3d, I %3d, D %3d)\n",
+ observed_common,
+ nominal_common_time,
+ nominal_common_time - observed_common,
+ integrated_error_,
+ correction_cur,
+ correction_cur_P,
+ correction_cur_I,
+ correction_cur_D);
+
+#ifdef AAH_TSDEBUG
+ diag_thread_->pushDisciplineEvent(
+ local_time,
+ observed_common,
+ nominal_common_time,
+ correction_cur,
+ correction_cur_P,
+ correction_cur_I,
+ correction_cur_D);
+#endif
+
+ return true;
+}
+
+void ClockRecoveryLoop::computePIDParams() {
+ // TODO(johngro) : add the ability to fetch parameters from the driver/board
+ // level in case they have a HW clock discipline solution with parameters
+ // tuned specifically for it.
+
+ // Correction factor is limited to +/-100 PPM.
+ pid_params_.correction_min = -100;
+ pid_params_.correction_max = 100;
+
+ // Default proportional gain to 1:10. (1 PPM of correction for
+ // every 10 uSec of instantaneous error)
+ memset(&pid_params_.gain_P, 0, sizeof(pid_params_.gain_P));
+ pid_params_.gain_P.a_to_b_numer = 1;
+ pid_params_.gain_P.a_to_b_denom = 10;
+
+ // Set the integral gain to 1:50
+ memset(&pid_params_.gain_I, 0, sizeof(pid_params_.gain_I));
+ pid_params_.gain_I.a_to_b_numer = 1;
+ pid_params_.gain_I.a_to_b_denom = 50;
+
+ // Default controller is just a PI controller. Right now, the network based
+ // measurements of the error are way to noisy to feed into the differential
+ // component of a PID controller. Someday we might come back and add some
+ // filtering of the error channel, but until then leave the controller as a
+ // simple PI controller.
+ memset(&pid_params_.gain_D, 0, sizeof(pid_params_.gain_D));
+
+ // Don't let the integral component of the controller wind up to
+ // the point where it would want to drive the correction factor
+ // past saturation.
+ int64_t tmp;
+ pid_params_.gain_I.doReverseTransform(pid_params_.correction_min, &tmp);
+ pid_params_.integrated_delta_min = static_cast<int32_t>(tmp);
+ pid_params_.gain_I.doReverseTransform(pid_params_.correction_max, &tmp);
+ pid_params_.integrated_delta_max = static_cast<int32_t>(tmp);
+
+ // By default, panic when the sync error is > 50mSec;
+ panic_thresh_ = 50000;
+}
+
+void ClockRecoveryLoop::reset_l(bool position, bool frequency) {
+ assert(NULL != common_clock_);
+
+ if (position) {
+ common_clock_->resetBasis();
+ startup_filter_wr_ = 0;
+ }
+
+ if (frequency) {
+ last_delta_valid_ = false;
+ last_delta_ = 0;
+ integrated_error_ = 0;
+ correction_cur_ = 0;
+ applySlew();
+ }
+
+ filter_wr_ = 0;
+ filter_full_ = false;
+}
+
+int32_t ClockRecoveryLoop::doGainScale(const LinearTransform& gain,
+ int32_t val) {
+ if (!gain.a_to_b_numer || !gain.a_to_b_denom || !val)
+ return 0;
+
+ int64_t tmp;
+ int64_t val64 = static_cast<int64_t>(val);
+ if (!gain.doForwardTransform(val64, &tmp)) {
+ LOGW("Overflow/Underflow while scaling %d in %s",
+ val, __PRETTY_FUNCTION__);
+ return (val < 0) ? INT32_MIN : INT32_MAX;
+ }
+
+ if (tmp > INT32_MAX) {
+ LOGW("Overflow while scaling %d in %s", val, __PRETTY_FUNCTION__);
+ return INT32_MAX;
+ }
+
+ if (tmp < INT32_MIN) {
+ LOGW("Underflow while scaling %d in %s", val, __PRETTY_FUNCTION__);
+ return INT32_MIN;
+ }
+
+ return static_cast<int32_t>(tmp);
+}
+
+void ClockRecoveryLoop::applySlew() {
+ if (local_clock_can_slew_)
+ local_clock_->setLocalSlew(correction_cur_);
+ else
+ common_clock_->setSlew(local_clock_->getLocalTime(), correction_cur_);
+}
+
+} // namespace android
diff --git a/services/aah_timesrv/clock_recovery.h b/services/aah_timesrv/clock_recovery.h
new file mode 100644
index 0000000..c468109
--- /dev/null
+++ b/services/aah_timesrv/clock_recovery.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef __CLOCK_RECOVERY_H__
+#define __CLOCK_RECOVERY_H__
+
+#include <stdint.h>
+#include <utils/LinearTransform.h>
+#include <utils/threads.h>
+
+#ifdef AAH_TSDEBUG
+#include "diag_thread.h"
+#endif
+
+namespace android {
+
+class CommonClock;
+class LocalClock;
+
+class ClockRecoveryLoop {
+ public:
+ ClockRecoveryLoop(LocalClock* local_clock, CommonClock* common_clock);
+ ~ClockRecoveryLoop();
+
+ void reset(bool position, bool frequency);
+ bool pushDisciplineEvent(int64_t local_time,
+ int64_t nominal_common_time,
+ int64_t data_point_rtt);
+
+ private:
+ typedef struct {
+ // Limits for the correction factor supplied to set_counter_slew_rate.
+ // The controller will always clamp its output to the range expressed by
+ // correction_(min|max)
+ int32_t correction_min;
+ int32_t correction_max;
+
+ // Limits for the internal integration accumulator in the PID
+ // controller. The value of the accumulator is scaled by gain_I to
+ // produce the integral component of the PID controller output.
+ // Platforms can use these limits to prevent windup in the system
+ // if/when the correction factor needs to be driven to saturation for
+ // extended periods of time.
+ int32_t integrated_delta_min;
+ int32_t integrated_delta_max;
+
+ // Gain for the P, I and D components of the controller.
+ LinearTransform gain_P;
+ LinearTransform gain_I;
+ LinearTransform gain_D;
+ } PIDParams;
+
+ typedef struct {
+ int64_t local_time;
+ int64_t observed_common_time;
+ int64_t nominal_common_time;
+ int64_t rtt;
+ bool point_used;
+ } DisciplineDataPoint;
+
+ static uint32_t findMinRTTNdx(DisciplineDataPoint* data, uint32_t count);
+
+ void computePIDParams();
+ void reset_l(bool position, bool frequency);
+ static int32_t doGainScale(const LinearTransform& gain, int32_t val);
+ void applySlew();
+
+ // The local clock HW abstraction we use as the basis for common time.
+ LocalClock* local_clock_;
+ bool local_clock_can_slew_;
+
+ // The common clock we end up controlling along with the lock used to
+ // serialize operations.
+ CommonClock* common_clock_;
+ Mutex lock_;
+
+ // The parameters computed to be used for the PID Controller.
+ PIDParams pid_params_;
+
+ // The maximum allowed error (as indicated by a pushDisciplineEvent) before
+ // we panic.
+ int32_t panic_thresh_;
+
+ // parameters maintained while running and reset during a reset
+ // of the frequency correction.
+ bool last_delta_valid_;
+ int32_t last_delta_;
+ int32_t integrated_error_;
+ int32_t correction_cur_;
+
+ // State kept for filtering the discipline data.
+ static const uint32_t kFilterSize = 6;
+ DisciplineDataPoint filter_data_[kFilterSize];
+ uint32_t filter_wr_;
+ bool filter_full_;
+
+ static const uint32_t kStartupFilterSize = 4;
+ DisciplineDataPoint startup_filter_data_[kStartupFilterSize];
+ uint32_t startup_filter_wr_;
+
+#ifdef AAH_TSDEBUG
+ sp<DiagThread> diag_thread_;
+#endif
+};
+
+} // namespace android
+
+#endif // __CLOCK_RECOVERY_H__
diff --git a/services/aah_timesrv/common_clock.cpp b/services/aah_timesrv/common_clock.cpp
new file mode 100644
index 0000000..bc40ecc
--- /dev/null
+++ b/services/aah_timesrv/common_clock.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define __STDC_LIMIT_MACROS
+
+#define LOG_TAG "aah_timesrv"
+#include <utils/Log.h>
+
+#include <stdint.h>
+
+#include <utils/Errors.h>
+#include <utils/LinearTransform.h>
+
+#include "common_clock.h"
+
+namespace android {
+
+CommonClock::CommonClock() {
+ cur_slew_ = 0;
+ cur_trans_valid_ = false;
+
+ cur_trans_.a_zero = 0;
+ cur_trans_.b_zero = 0;
+ cur_trans_.a_to_b_numer = local_to_common_freq_numer_ = 1;
+ cur_trans_.a_to_b_denom = local_to_common_freq_denom_ = 1;
+}
+
+bool CommonClock::init(uint64_t local_freq) {
+ Mutex::Autolock lock(&lock_);
+
+ if (!local_freq)
+ return false;
+
+ uint64_t numer = kCommonFreq;
+ uint64_t denom = local_freq;
+
+ LinearTransform::reduce(&numer, &denom);
+ if ((numer > UINT32_MAX) || (denom > UINT32_MAX)) {
+ LOGE("Overflow in CommonClock::init while trying to reduce %lld/%lld",
+ kCommonFreq, local_freq);
+ return false;
+ }
+
+ cur_trans_.a_to_b_numer = local_to_common_freq_numer_ =
+ static_cast<uint32_t>(numer);
+ cur_trans_.a_to_b_denom = local_to_common_freq_denom_ =
+ static_cast<uint32_t>(denom);
+
+ return true;
+}
+
+status_t CommonClock::localToCommon(int64_t local, int64_t *common_out) const {
+ Mutex::Autolock lock(&lock_);
+
+ if (!cur_trans_valid_)
+ return INVALID_OPERATION;
+
+ if (!cur_trans_.doForwardTransform(local, common_out))
+ return INVALID_OPERATION;
+
+ return OK;
+}
+
+status_t CommonClock::commonToLocal(int64_t common, int64_t *local_out) const {
+ Mutex::Autolock lock(&lock_);
+
+ if (!cur_trans_valid_)
+ return INVALID_OPERATION;
+
+ if (!cur_trans_.doReverseTransform(common, local_out))
+ return INVALID_OPERATION;
+
+ return OK;
+}
+
+void CommonClock::setBasis(int64_t local, int64_t common) {
+ Mutex::Autolock lock(&lock_);
+
+ cur_trans_.a_zero = local;
+ cur_trans_.b_zero = common;
+ cur_trans_valid_ = true;
+}
+
+void CommonClock::resetBasis() {
+ Mutex::Autolock lock(&lock_);
+
+ cur_trans_.a_zero = 0;
+ cur_trans_.b_zero = 0;
+ cur_trans_valid_ = false;
+}
+
+status_t CommonClock::setSlew(int64_t change_time, int32_t ppm) {
+ Mutex::Autolock lock(&lock_);
+
+ int64_t new_local_basis;
+ int64_t new_common_basis;
+
+ if (cur_trans_valid_) {
+ new_local_basis = change_time;
+ if (!cur_trans_.doForwardTransform(change_time, &new_common_basis)) {
+ LOGE("Overflow when attempting to set slew rate to %d", ppm);
+ return INVALID_OPERATION;
+ }
+ } else {
+ new_local_basis = 0;
+ new_common_basis = 0;
+ }
+
+ cur_slew_ = ppm;
+ uint32_t n1 = local_to_common_freq_numer_;
+ uint32_t n2 = 1000000 + cur_slew_;
+
+ uint32_t d1 = local_to_common_freq_denom_;
+ uint32_t d2 = 1000000;
+
+ // n1/d1 has alredy been reduced, no need to do so here.
+ LinearTransform::reduce(&n1, &d2);
+ LinearTransform::reduce(&n2, &d1);
+ LinearTransform::reduce(&n2, &d2);
+
+ cur_trans_.a_zero = new_local_basis;
+ cur_trans_.b_zero = new_common_basis;
+ cur_trans_.a_to_b_numer = n1 * n2;
+ cur_trans_.a_to_b_denom = d1 * d2;
+
+ return OK;
+}
+
+} // namespace android
diff --git a/services/aah_timesrv/common_clock.h b/services/aah_timesrv/common_clock.h
new file mode 100644
index 0000000..8c24507
--- /dev/null
+++ b/services/aah_timesrv/common_clock.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef __COMMON_CLOCK_H__
+#define __COMMON_CLOCK_H__
+
+#include <stdint.h>
+
+#include <utils/Errors.h>
+#include <utils/LinearTransform.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class CommonClock {
+ public:
+ CommonClock();
+
+ bool init(uint64_t local_freq);
+
+ status_t localToCommon(int64_t local, int64_t *common_out) const;
+ status_t commonToLocal(int64_t common, int64_t *local_out) const;
+ uint64_t getCommonFreq() const { return kCommonFreq; }
+ bool isValid() const { return cur_trans_valid_; }
+ status_t setSlew(int64_t change_time, int32_t ppm);
+ void setBasis(int64_t local, int64_t common);
+ void resetBasis();
+ private:
+ mutable Mutex lock_;
+
+ int32_t cur_slew_;
+ uint32_t local_to_common_freq_numer_;
+ uint32_t local_to_common_freq_denom_;
+
+ LinearTransform cur_trans_;
+ bool cur_trans_valid_;
+
+ static const uint64_t kCommonFreq = 1000000ull;
+};
+
+} // namespace android
+#endif // __COMMON_CLOCK_H__
diff --git a/services/aah_timesrv/diag_thread.cpp b/services/aah_timesrv/diag_thread.cpp
new file mode 100644
index 0000000..50be811
--- /dev/null
+++ b/services/aah_timesrv/diag_thread.cpp
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "aah_timesrv"
+#include <utils/Log.h>
+
+#include <fcntl.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <utils/Errors.h>
+#include <utils/misc.h>
+
+#include <aah_timesrv/local_clock.h>
+
+#include "common_clock.h"
+#include "diag_thread.h"
+
+#define kMaxEvents 16
+#define kListenPort 9876
+
+static bool setNonblocking(int fd) {
+ int flags = fcntl(fd, F_GETFL);
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
+ LOGE("Failed to set socket (%d) to non-blocking mode (errno %d)",
+ fd, errno);
+ return false;
+ }
+
+ return true;
+}
+
+static bool setNodelay(int fd) {
+ int tmp = 1;
+ if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &tmp, sizeof(tmp)) < 0) {
+ LOGE("Failed to set socket (%d) to no-delay mode (errno %d)",
+ fd, errno);
+ return false;
+ }
+
+ return true;
+}
+
+namespace android {
+
+DiagThread::DiagThread(CommonClock* common_clock, LocalClock* local_clock) {
+ common_clock_ = common_clock;
+ local_clock_ = local_clock;
+ listen_fd_ = -1;
+ data_fd_ = -1;
+ kernel_logID_basis_known_ = false;
+ discipline_log_ID_ = 0;
+}
+
+DiagThread::~DiagThread() {
+}
+
+status_t DiagThread::startWorkThread() {
+ status_t res;
+ stopWorkThread();
+ res = run("Diag");
+
+ if (res != OK)
+ LOGE("Failed to start work thread (res = %d)", res);
+
+ return res;
+}
+
+void DiagThread::stopWorkThread() {
+ status_t res;
+ res = requestExitAndWait(); // block until thread exit.
+ if (res != OK)
+ LOGE("Failed to stop work thread (res = %d)", res);
+}
+
+bool DiagThread::openListenSocket() {
+ bool ret = false;
+ int flags;
+ cleanupListenSocket();
+
+ if ((listen_fd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
+ LOGE("Socket failed.");
+ goto bailout;
+ }
+
+ // Set non-blocking operation
+ if (!setNonblocking(listen_fd_))
+ goto bailout;
+
+ struct sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = INADDR_ANY;
+ addr.sin_port = htons(kListenPort);
+
+ if (bind(listen_fd_, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
+ LOGE("Bind failed.");
+ goto bailout;
+ }
+
+ if (listen(listen_fd_, 1) < 0) {
+ LOGE("Listen failed.");
+ goto bailout;
+ }
+
+ ret = true;
+bailout:
+ if (!ret)
+ cleanupListenSocket();
+
+ return ret;
+}
+
+void DiagThread::cleanupListenSocket() {
+ if (listen_fd_ >= 0) {
+ int res;
+
+ struct linger l;
+ l.l_onoff = 1;
+ l.l_linger = 0;
+
+ setsockopt(listen_fd_, SOL_SOCKET, SO_LINGER, &l, sizeof(l));
+ shutdown(listen_fd_, SHUT_RDWR);
+ close(listen_fd_);
+ listen_fd_ = -1;
+ }
+}
+
+void DiagThread::cleanupDataSocket() {
+ if (data_fd_ >= 0) {
+ int res;
+
+ struct linger l;
+ l.l_onoff = 1;
+ l.l_linger = 0;
+
+ setsockopt(data_fd_, SOL_SOCKET, SO_LINGER, &l, sizeof(l));
+ shutdown(data_fd_, SHUT_RDWR);
+ close(data_fd_);
+ data_fd_ = -1;
+ }
+}
+
+void DiagThread::resetLogIDs() {
+ // Drain and discard all of the events from the kernel
+ struct local_time_debug_event events[kMaxEvents];
+ while(local_clock_->getDebugLog(events, kMaxEvents) > 0)
+ ;
+
+ {
+ Mutex::Autolock lock(&discipline_log_lock_);
+ discipline_log_.clear();
+ discipline_log_ID_ = 0;
+ }
+
+ kernel_logID_basis_known_ = false;
+}
+
+void DiagThread::pushDisciplineEvent(int64_t observed_local_time,
+ int64_t observed_common_time,
+ int64_t nominal_common_time,
+ int32_t total_correction,
+ int32_t P_correction,
+ int32_t I_correction,
+ int32_t D_correction) {
+ Mutex::Autolock lock(&discipline_log_lock_);
+
+ DisciplineEventRecord evt;
+
+ evt.event_id = discipline_log_ID_++;
+
+ evt.action_local_time = local_clock_->getLocalTime();
+ common_clock_->localToCommon(evt.action_local_time,
+ &evt.action_common_time);
+
+ evt.observed_local_time = observed_local_time;
+ evt.observed_common_time = observed_common_time;
+ evt.nominal_common_time = nominal_common_time;
+ evt.total_correction = total_correction;
+ evt.P_correction = P_correction;
+ evt.I_correction = I_correction;
+ evt.D_correction = D_correction;
+
+ discipline_log_.push_back(evt);
+ while (discipline_log_.size() > kMaxDisciplineLogSize)
+ discipline_log_.erase(discipline_log_.begin());
+}
+
+bool DiagThread::threadLoop() {
+ struct pollfd poll_fds[1];
+
+ if (!openListenSocket()) {
+ LOGE("Failed to open listen socket");
+ goto bailout;
+ }
+
+ while (!exitPending()) {
+ memset(&poll_fds, 0, sizeof(poll_fds));
+
+ if (data_fd_ < 0) {
+ poll_fds[0].fd = listen_fd_;
+ poll_fds[0].events = POLLIN;
+ } else {
+ poll_fds[0].fd = data_fd_;
+ poll_fds[0].events = POLLRDHUP | POLLIN;
+ }
+
+ int poll_res = poll(poll_fds, NELEM(poll_fds), 50);
+ if (poll_res < 0) {
+ LOGE("Fatal error (%d,%d) while waiting on events",
+ poll_res, errno);
+ goto bailout;
+ }
+
+ if (exitPending())
+ break;
+
+ if (poll_fds[0].revents) {
+ if (poll_fds[0].fd == listen_fd_) {
+ data_fd_ = accept(listen_fd_, NULL, NULL);
+
+ if (data_fd_ < 0) {
+ LOGW("Failed accept on socket %d with err %d",
+ listen_fd_, errno);
+ } else {
+ if (!setNonblocking(data_fd_))
+ cleanupDataSocket();
+ if (!setNodelay(data_fd_))
+ cleanupDataSocket();
+ }
+ } else
+ if (poll_fds[0].fd == data_fd_) {
+ if (poll_fds[0].revents & POLLRDHUP) {
+ // Connection hung up; time to clean up.
+ cleanupDataSocket();
+ } else
+ if (poll_fds[0].revents & POLLIN) {
+ uint8_t cmd;
+ if (read(data_fd_, &cmd, sizeof(cmd)) > 0) {
+ switch(cmd) {
+ case 'r':
+ case 'R':
+ resetLogIDs();
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ struct local_time_debug_event events[kMaxEvents];
+ int amt = local_clock_->getDebugLog(events, kMaxEvents);
+
+ if (amt > 0) {
+ for (int i = 0; i < amt; i++) {
+ struct local_time_debug_event& e = events[i];
+
+ if (!kernel_logID_basis_known_) {
+ kernel_logID_basis_ = e.local_timesync_event_id;
+ kernel_logID_basis_known_ = true;
+ }
+
+ char buf[1024];
+ int64_t common_time;
+ status_t res = common_clock_->localToCommon(e.local_time,
+ &common_time);
+ snprintf(buf, sizeof(buf), "E,%lld,%lld,%lld,%d\n",
+ e.local_timesync_event_id - kernel_logID_basis_,
+ e.local_time,
+ common_time,
+ (OK == res) ? 1 : 0);
+ buf[sizeof(buf) - 1] = 0;
+
+ if (data_fd_ >= 0)
+ write(data_fd_, buf, strlen(buf));
+ }
+ }
+
+ { // scope for autolock pattern
+ Mutex::Autolock lock(&discipline_log_lock_);
+
+ while (discipline_log_.size() > 0) {
+ char buf[1024];
+ DisciplineEventRecord& e = *discipline_log_.begin();
+ snprintf(buf, sizeof(buf),
+ "D,%lld,%lld,%lld,%lld,%lld,%lld,%d,%d,%d,%d\n",
+ e.event_id,
+ e.action_local_time,
+ e.action_common_time,
+ e.observed_local_time,
+ e.observed_common_time,
+ e.nominal_common_time,
+ e.total_correction,
+ e.P_correction,
+ e.I_correction,
+ e.D_correction);
+ buf[sizeof(buf) - 1] = 0;
+
+ if (data_fd_ >= 0)
+ write(data_fd_, buf, strlen(buf));
+
+ discipline_log_.erase(discipline_log_.begin());
+ }
+ }
+ }
+
+bailout:
+ cleanupDataSocket();
+ cleanupListenSocket();
+ return false;
+}
+
+} // namespace android
diff --git a/services/aah_timesrv/diag_thread.h b/services/aah_timesrv/diag_thread.h
new file mode 100644
index 0000000..6ebe829
--- /dev/null
+++ b/services/aah_timesrv/diag_thread.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef __DIAG_THREAD_H__
+#define __DIAG_THREAD_H__
+
+#include <utils/List.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class CommonClock;
+class LocalClock;
+
+class DiagThread : public Thread {
+ public:
+ DiagThread(CommonClock* common_clock, LocalClock* local_clock);
+ ~DiagThread();
+
+ status_t startWorkThread();
+ void stopWorkThread();
+ virtual bool threadLoop();
+
+ void pushDisciplineEvent(int64_t observed_local_time,
+ int64_t observed_common_time,
+ int64_t nominal_common_time,
+ int32_t total_correction,
+ int32_t P_correction,
+ int32_t I_correction,
+ int32_t D_correction);
+
+ private:
+ typedef struct {
+ int64_t event_id;
+ int64_t action_local_time;
+ int64_t action_common_time;
+ int64_t observed_local_time;
+ int64_t observed_common_time;
+ int64_t nominal_common_time;
+ int32_t total_correction;
+ int32_t P_correction;
+ int32_t I_correction;
+ int32_t D_correction;
+ } DisciplineEventRecord;
+
+ bool openListenSocket();
+ void cleanupListenSocket();
+ void cleanupDataSocket();
+ void resetLogIDs();
+
+ CommonClock* common_clock_;
+ LocalClock* local_clock_;
+ int listen_fd_;
+ int data_fd_;
+
+ int64_t kernel_logID_basis_;
+ bool kernel_logID_basis_known_;
+
+ static const size_t kMaxDisciplineLogSize = 16;
+ Mutex discipline_log_lock_;
+ List<DisciplineEventRecord> discipline_log_;
+ int64_t discipline_log_ID_;
+};
+
+} // namespace android
+
+#endif //__ DIAG_THREAD_H__
diff --git a/services/audioflinger/Android.mk b/services/audioflinger/Android.mk
index fa49592..d4eea3d 100644
--- a/services/audioflinger/Android.mk
+++ b/services/audioflinger/Android.mk
@@ -8,7 +8,8 @@
AudioResampler.cpp.arm \
AudioResamplerSinc.cpp.arm \
AudioResamplerCubic.cpp.arm \
- AudioPolicyService.cpp
+ AudioPolicyService.cpp \
+ AudioBufferProvider.cpp
LOCAL_C_INCLUDES := \
system/media/audio_effects/include
@@ -22,7 +23,8 @@
libhardware_legacy \
libeffects \
libdl \
- libpowermanager
+ libpowermanager \
+ libaah_timesrv_client
LOCAL_STATIC_LIBRARIES := \
libcpustats \
diff --git a/services/audioflinger/AudioBufferProvider.cpp b/services/audioflinger/AudioBufferProvider.cpp
new file mode 100644
index 0000000..678fd58
--- /dev/null
+++ b/services/audioflinger/AudioBufferProvider.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#undef __STRICT_ANSI__
+#define __STDINT_LIMITS
+#define __STDC_LIMIT_MACROS
+#include <stdint.h>
+
+#include "AudioBufferProvider.h"
+
+namespace android {
+
+const int64_t AudioBufferProvider::kInvalidPTS = INT64_MAX;
+
+}; // namespace android
diff --git a/services/audioflinger/AudioBufferProvider.h b/services/audioflinger/AudioBufferProvider.h
index 81c5c39..62ad6bd 100644
--- a/services/audioflinger/AudioBufferProvider.h
+++ b/services/audioflinger/AudioBufferProvider.h
@@ -38,8 +38,15 @@
};
virtual ~AudioBufferProvider() {}
-
- virtual status_t getNextBuffer(Buffer* buffer) = 0;
+
+ // value representing an invalid presentation timestamp
+ static const int64_t kInvalidPTS;
+
+ // pts is the local time when the next sample yielded by getNextBuffer
+ // will be rendered.
+ // Pass kInvalidPTS if the PTS is unknown or not applicable.
+ virtual status_t getNextBuffer(Buffer* buffer, int64_t pts) = 0;
+
virtual void releaseBuffer(Buffer* buffer) = 0;
};
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 94efa74..9d4d107 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -58,6 +58,9 @@
#include <powermanager/PowerManager.h>
// #define DEBUG_CPU_USAGE 10 // log statistics every n wall clock seconds
+#include <aah_timesrv/cc_helper.h>
+#include <aah_timesrv/local_clock.h>
+
// ----------------------------------------------------------------------------
@@ -150,8 +153,9 @@
AudioFlinger::AudioFlinger()
: BnAudioFlinger(),
- mPrimaryHardwareDev(0), mMasterVolume(1.0f), mMasterMute(false), mNextUniqueId(1),
- mBtNrecIsOff(false)
+ mPrimaryHardwareDev(0), mMasterVolume(1.0f), mMasterVolumeSW(1.0f),
+ mMasterVolumeSupportLvl(MVS_NONE), mMasterMute(false), mNextUniqueId(1),
+ mBtNrecIsOff(false)
{
}
@@ -190,6 +194,33 @@
return;
}
+ // Determine the level of master volume support the primary audio HAL has,
+ // and set the initial master volume at the same time.
+ float initialVolume = 1.0;
+ mMasterVolumeSupportLvl = MVS_NONE;
+ if (0 == mPrimaryHardwareDev->init_check(mPrimaryHardwareDev)) {
+ AutoMutex lock(mHardwareLock);
+ audio_hw_device_t *dev = mPrimaryHardwareDev;
+
+ mHardwareStatus = AUDIO_HW_GET_MASTER_VOLUME;
+ if ((NULL != dev->get_master_volume) &&
+ (NO_ERROR == dev->get_master_volume(dev, &initialVolume))) {
+ mMasterVolumeSupportLvl = MVS_FULL;
+ } else {
+ mMasterVolumeSupportLvl = MVS_SETONLY;
+ initialVolume = 1.0;
+ }
+
+ mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
+ if ((NULL == dev->set_master_volume) ||
+ (NO_ERROR != dev->set_master_volume(dev, initialVolume))) {
+ mMasterVolumeSupportLvl = MVS_NONE;
+ }
+ mHardwareStatus = AUDIO_HW_INIT;
+ }
+
+ // Set the mode for each audio HAL, and try to set the initial volume (if
+ // supported) for all of the non-primary audio HALs.
for (size_t i = 0; i < mAudioHwDevs.size(); i++) {
audio_hw_device_t *dev = mAudioHwDevs[i];
@@ -201,11 +232,22 @@
mMode = AUDIO_MODE_NORMAL;
mHardwareStatus = AUDIO_HW_SET_MODE;
dev->set_mode(dev, mMode);
- mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
- dev->set_master_volume(dev, 1.0f);
- mHardwareStatus = AUDIO_HW_IDLE;
+
+ if ((dev != mPrimaryHardwareDev) &&
+ (NULL != dev->set_master_volume)) {
+ mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
+ dev->set_master_volume(dev, initialVolume);
+ }
+
+ mHardwareStatus = AUDIO_HW_INIT;
}
}
+
+ mMasterVolumeSW = (MVS_NONE == mMasterVolumeSupportLvl)
+ ? initialVolume
+ : 1.0;
+ mMasterVolume = initialVolume;
+ mHardwareStatus = AUDIO_HW_IDLE;
}
status_t AudioFlinger::initCheck() const
@@ -376,6 +418,7 @@
uint32_t flags,
const sp<IMemory>& sharedBuffer,
int output,
+ bool isTimed,
int *sessionId,
status_t *status)
{
@@ -439,7 +482,7 @@
LOGV("createTrack() lSessionId: %d", lSessionId);
track = thread->createTrack_l(client, streamType, sampleRate, format,
- channelMask, frameCount, sharedBuffer, lSessionId, &lStatus);
+ channelMask, frameCount, sharedBuffer, lSessionId, isTimed, &lStatus);
// move effect chain to this output thread if an effect on same session was waiting
// for a track to be created
@@ -532,20 +575,29 @@
return PERMISSION_DENIED;
}
+ float swmv = value;
+
// when hw supports master volume, don't scale in sw mixer
- { // scope for the lock
- AutoMutex lock(mHardwareLock);
- mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
- if (mPrimaryHardwareDev->set_master_volume(mPrimaryHardwareDev, value) == NO_ERROR) {
- value = 1.0f;
+ if (MVS_NONE != mMasterVolumeSupportLvl) {
+ for (size_t i = 0; i < mAudioHwDevs.size(); i++) {
+ AutoMutex lock(mHardwareLock);
+ audio_hw_device_t *dev = mAudioHwDevs[i];
+
+ mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
+ if (NULL != dev->set_master_volume) {
+ dev->set_master_volume(dev, value);
+ }
+ mHardwareStatus = AUDIO_HW_IDLE;
}
- mHardwareStatus = AUDIO_HW_IDLE;
+
+ swmv = 1.0;
}
Mutex::Autolock _l(mLock);
- mMasterVolume = value;
+ mMasterVolume = value;
+ mMasterVolumeSW = swmv;
for (uint32_t i = 0; i < mPlaybackThreads.size(); i++)
- mPlaybackThreads.valueAt(i)->setMasterVolume(value);
+ mPlaybackThreads.valueAt(i)->setMasterVolume(swmv);
return NO_ERROR;
}
@@ -633,9 +685,27 @@
float AudioFlinger::masterVolume() const
{
+ if (MVS_FULL == mMasterVolumeSupportLvl) {
+ float ret_val;
+ AutoMutex lock(mHardwareLock);
+
+ mHardwareStatus = AUDIO_HW_GET_MASTER_VOLUME;
+ assert(NULL != mPrimaryHardwareDev);
+ assert(NULL != mPrimaryHardwareDev->get_master_volume);
+
+ mPrimaryHardwareDev->get_master_volume(mPrimaryHardwareDev, &ret_val);
+ mHardwareStatus = AUDIO_HW_IDLE;
+ return ret_val;
+ }
+
return mMasterVolume;
}
+float AudioFlinger::masterVolumeSW() const
+{
+ return mMasterVolumeSW;
+}
+
bool AudioFlinger::masterMute() const
{
return mMasterMute;
@@ -1356,7 +1426,8 @@
readOutputParameters();
- mMasterVolume = mAudioFlinger->masterVolume();
+ mMasterVolume = mAudioFlinger->masterVolumeSW();
+
mMasterMute = mAudioFlinger->masterMute();
for (int stream = 0; stream < AUDIO_STREAM_CNT; stream++) {
@@ -1467,6 +1538,7 @@
int frameCount,
const sp<IMemory>& sharedBuffer,
int sessionId,
+ bool isTimed,
status_t *status)
{
sp<Track> track;
@@ -1516,9 +1588,14 @@
}
}
- track = new Track(this, client, streamType, sampleRate, format,
- channelMask, frameCount, sharedBuffer, sessionId);
- if (track->getCblk() == NULL || track->name() < 0) {
+ if (!isTimed) {
+ track = new Track(this, client, streamType, sampleRate, format,
+ channelMask, frameCount, sharedBuffer, sessionId);
+ } else {
+ track = TimedTrack::create(this, client, streamType, sampleRate, format,
+ channelMask, frameCount, sharedBuffer, sessionId);
+ }
+ if (track == NULL || track->getCblk() == NULL || track->name() < 0) {
lStatus = NO_MEMORY;
goto Exit;
}
@@ -1840,6 +1917,10 @@
acquireWakeLock();
+ LocalClock lc;
+ uint64_t localTimeFreq;
+ localTimeFreq = lc.getLocalFreq();
+
while (!exitPending())
{
#ifdef DEBUG_CPU_USAGE
@@ -1934,8 +2015,21 @@
}
if (LIKELY(mixerStatus == MIXER_TRACKS_READY)) {
+ // obtain the presentation timestamp of the next output buffer
+ int64_t pts;
+ status_t status = INVALID_OPERATION;
+
+ if (NULL != mOutput->stream->get_next_write_timestamp) {
+ status = mOutput->stream->get_next_write_timestamp(
+ mOutput->stream, &pts);
+ }
+
+ if (status != NO_ERROR) {
+ pts = AudioBufferProvider::kInvalidPTS;
+ }
+
// mix buffers...
- mAudioMixer->process();
+ mAudioMixer->process(pts);
sleepTime = 0;
standbyTime = systemTime() + kStandbyTimeInNsecs;
//TODO: delay standby when effects have a tail
@@ -2050,7 +2144,7 @@
// The first time a track is added we wait
// for all its buffers to be filled before processing it
mAudioMixer->setActiveTrack(track->name());
- if (cblk->framesReady() && track->isReady() &&
+ if (track->framesReady() && track->isReady() &&
!track->isPaused() && !track->isTerminated())
{
//LOGV("track %d u=%08x, s=%08x [OK] on thread %p", track->name(), cblk->user, cblk->server, this);
@@ -2704,7 +2798,8 @@
// output audio to hardware
while (frameCount) {
buffer.frameCount = frameCount;
- activeTrack->getNextBuffer(&buffer);
+ activeTrack->getNextBuffer(&buffer,
+ AudioBufferProvider::kInvalidPTS);
if (UNLIKELY(buffer.raw == 0)) {
memset(curBuf, 0, frameCount * mFrameSize);
break;
@@ -2971,7 +3066,7 @@
if (LIKELY(mixerStatus == MIXER_TRACKS_READY)) {
// mix buffers...
if (outputsReady(outputTracks)) {
- mAudioMixer->process();
+ mAudioMixer->process(AudioBufferProvider::kInvalidPTS);
} else {
memset(mMixBuffer, 0, mixBufferSize);
}
@@ -3366,7 +3461,8 @@
(int)mAuxBuffer);
}
-status_t AudioFlinger::PlaybackThread::Track::getNextBuffer(AudioBufferProvider::Buffer* buffer)
+status_t AudioFlinger::PlaybackThread::Track::getNextBuffer(
+ AudioBufferProvider::Buffer* buffer, int64_t pts)
{
audio_track_cblk_t* cblk = this->cblk();
uint32_t framesReady;
@@ -3407,10 +3503,14 @@
return NOT_ENOUGH_DATA;
}
+uint32_t AudioFlinger::PlaybackThread::Track::framesReady() const{
+ return mCblk->framesReady();
+}
+
bool AudioFlinger::PlaybackThread::Track::isReady() const {
if (mFillingUpStatus != FS_FILLING || isStopped() || isPausing()) return true;
- if (mCblk->framesReady() >= mCblk->frameCount ||
+ if (framesReady() >= mCblk->frameCount ||
(mCblk->flags & CBLK_FORCEREADY_MSK)) {
mFillingUpStatus = FS_FILLED;
android_atomic_and(~CBLK_FORCEREADY_MSK, &mCblk->flags);
@@ -3579,6 +3679,393 @@
mAuxBuffer = buffer;
}
+// timed audio tracks
+
+sp<AudioFlinger::PlaybackThread::TimedTrack>
+AudioFlinger::PlaybackThread::TimedTrack::create(
+ const wp<ThreadBase>& thread,
+ const sp<Client>& client,
+ int streamType,
+ uint32_t sampleRate,
+ uint32_t format,
+ uint32_t channelMask,
+ int frameCount,
+ const sp<IMemory>& sharedBuffer,
+ int sessionId) {
+ if (!client->reserveTimedTrack())
+ return NULL;
+
+ sp<TimedTrack> track = new TimedTrack(
+ thread, client, streamType, sampleRate, format, channelMask, frameCount,
+ sharedBuffer, sessionId);
+
+ if (track == NULL) {
+ client->releaseTimedTrack();
+ return NULL;
+ }
+
+ return track;
+}
+
+AudioFlinger::PlaybackThread::TimedTrack::TimedTrack(
+ const wp<ThreadBase>& thread,
+ const sp<Client>& client,
+ int streamType,
+ uint32_t sampleRate,
+ uint32_t format,
+ uint32_t channelMask,
+ int frameCount,
+ const sp<IMemory>& sharedBuffer,
+ int sessionId)
+ : Track(thread, client, streamType, sampleRate, format, channelMask,
+ frameCount, sharedBuffer, sessionId),
+ mTimedSilenceBuffer(NULL),
+ mTimedSilenceBufferSize(0),
+ mTimedAudioOutputOnTime(false),
+ mMediaTimeTransformValid(false)
+{
+ LocalClock lc;
+ mLocalTimeFreq = lc.getLocalFreq();
+
+ mLocalTimeToSampleTransform.a_zero = 0;
+ mLocalTimeToSampleTransform.b_zero = 0;
+ mLocalTimeToSampleTransform.a_to_b_numer = sampleRate;
+ mLocalTimeToSampleTransform.a_to_b_denom = mLocalTimeFreq;
+ LinearTransform::reduce(&mLocalTimeToSampleTransform.a_to_b_numer,
+ &mLocalTimeToSampleTransform.a_to_b_denom);
+}
+
+AudioFlinger::PlaybackThread::TimedTrack::~TimedTrack() {
+ mClient->releaseTimedTrack();
+ delete [] mTimedSilenceBuffer;
+}
+
+status_t AudioFlinger::PlaybackThread::TimedTrack::allocateTimedBuffer(
+ size_t size, sp<IMemory>* buffer) {
+
+ Mutex::Autolock _l(mTimedBufferQueueLock);
+
+ trimTimedBufferQueue_l();
+
+ // lazily initialize the shared memory heap for timed buffers
+ if (mTimedMemoryDealer == NULL) {
+ const int kTimedBufferHeapSize = 512 << 10;
+
+ mTimedMemoryDealer = new MemoryDealer(kTimedBufferHeapSize,
+ "AudioFlingerTimed");
+ if (mTimedMemoryDealer == NULL)
+ return NO_MEMORY;
+ }
+
+ sp<IMemory> newBuffer = mTimedMemoryDealer->allocate(size);
+ if (newBuffer == NULL) {
+ newBuffer = mTimedMemoryDealer->allocate(size);
+ if (newBuffer == NULL)
+ return NO_MEMORY;
+ }
+
+ *buffer = newBuffer;
+ return NO_ERROR;
+}
+
+// caller must hold mTimedBufferQueueLock
+void AudioFlinger::PlaybackThread::TimedTrack::trimTimedBufferQueue_l() {
+ int64_t mediaTimeNow;
+ {
+ Mutex::Autolock mttLock(mMediaTimeTransformLock);
+ if (!mMediaTimeTransformValid)
+ return;
+
+ int64_t targetTimeNow;
+ status_t res = (mMediaTimeTransformTarget == TimedAudioTrack::COMMON_TIME)
+ ? CCHelper::getCommonTime(&targetTimeNow)
+ : CCHelper::getLocalTime(&targetTimeNow);
+
+ if (OK != res)
+ return;
+
+ if (!mMediaTimeTransform.doReverseTransform(targetTimeNow,
+ &mediaTimeNow)) {
+ return;
+ }
+ }
+
+ size_t trimIndex;
+ for (trimIndex = 0; trimIndex < mTimedBufferQueue.size(); trimIndex++) {
+ if (mTimedBufferQueue[trimIndex].pts() > mediaTimeNow)
+ break;
+ }
+
+ if (trimIndex) {
+ mTimedBufferQueue.removeItemsAt(0, trimIndex);
+ }
+}
+
+status_t AudioFlinger::PlaybackThread::TimedTrack::queueTimedBuffer(
+ const sp<IMemory>& buffer, int64_t pts) {
+
+ {
+ Mutex::Autolock mttLock(mMediaTimeTransformLock);
+ if (!mMediaTimeTransformValid)
+ return INVALID_OPERATION;
+ }
+
+ Mutex::Autolock _l(mTimedBufferQueueLock);
+
+ mTimedBufferQueue.add(TimedBuffer(buffer, pts));
+
+ return NO_ERROR;
+}
+
+status_t AudioFlinger::PlaybackThread::TimedTrack::setMediaTimeTransform(
+ const LinearTransform& xform, TimedAudioTrack::TargetTimeline target) {
+
+ LOGV("%s az=%lld bz=%lld n=%d d=%u tgt=%d", __PRETTY_FUNCTION__,
+ xform.a_zero, xform.b_zero, xform.a_to_b_numer, xform.a_to_b_denom,
+ target);
+
+ if (!(target == TimedAudioTrack::LOCAL_TIME ||
+ target == TimedAudioTrack::COMMON_TIME)) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mMediaTimeTransformLock);
+ mMediaTimeTransform = xform;
+ mMediaTimeTransformTarget = target;
+ mMediaTimeTransformValid = true;
+
+ return NO_ERROR;
+}
+
+#define min(a, b) ((a) < (b) ? (a) : (b))
+
+// implementation of getNextBuffer for tracks whose buffers have timestamps
+status_t AudioFlinger::PlaybackThread::TimedTrack::getNextBuffer(
+ AudioBufferProvider::Buffer* buffer, int64_t pts)
+{
+ if (pts == AudioBufferProvider::kInvalidPTS) {
+ buffer->raw = 0;
+ buffer->frameCount = 0;
+ return INVALID_OPERATION;
+ }
+
+ // get ahold of the output stream that these samples will be written to
+ sp<ThreadBase> thread = mThread.promote();
+ if (thread == NULL) {
+ buffer->raw = 0;
+ buffer->frameCount = 0;
+ return INVALID_OPERATION;
+ }
+ PlaybackThread* playbackThread = static_cast<PlaybackThread*>(thread.get());
+
+ Mutex::Autolock _l(mTimedBufferQueueLock);
+
+ while (true) {
+
+ // if we have no timed buffers, then fail
+ if (mTimedBufferQueue.isEmpty()) {
+ buffer->raw = 0;
+ buffer->frameCount = 0;
+ return NOT_ENOUGH_DATA;
+ }
+
+ TimedBuffer& head = mTimedBufferQueue.editItemAt(0);
+
+ // calculate the PTS of the head of the timed buffer queue expressed in
+ // local time
+ int64_t headLocalPTS;
+ {
+ Mutex::Autolock mttLock(mMediaTimeTransformLock);
+
+ assert(mMediaTimeTransformValid);
+
+ if (mMediaTimeTransform.a_to_b_denom == 0) {
+ // the transform represents a pause, so yield silence
+ timedYieldSilence(buffer->frameCount, buffer);
+ return NO_ERROR;
+ }
+
+ int64_t transformedPTS;
+ if (!mMediaTimeTransform.doForwardTransform(head.pts(),
+ &transformedPTS)) {
+ // the transform failed. this shouldn't happen, but if it does
+ // then just drop this buffer
+ LOGW("timedGetNextBuffer transform failed");
+ buffer->raw = 0;
+ buffer->frameCount = 0;
+ mTimedBufferQueue.removeAt(0);
+ return NO_ERROR;
+ }
+
+ if (mMediaTimeTransformTarget == TimedAudioTrack::COMMON_TIME) {
+ if (OK != CCHelper::commonTimeToLocalTime(transformedPTS,
+ &headLocalPTS)) {
+ buffer->raw = 0;
+ buffer->frameCount = 0;
+ return INVALID_OPERATION;
+ }
+ } else {
+ headLocalPTS = transformedPTS;
+ }
+ }
+
+ // adjust the head buffer's PTS to reflect the portion of the head buffer
+ // that has already been consumed
+ int64_t effectivePTS = headLocalPTS +
+ ((head.position() / mCblk->frameSize) * mLocalTimeFreq / sampleRate());
+
+ // Calculate the delta in samples between the head of the input buffer
+ // queue and the start of the next output buffer that will be written.
+ // If the transformation fails because of over or underflow, it means
+ // that the sample's position in the output stream is so far out of
+ // whack that it should just be dropped.
+ int64_t sampleDelta;
+ if (llabs(effectivePTS - pts) >= (static_cast<int64_t>(1) << 31)) {
+ LOGV("*** head buffer is too far from PTS: dropped buffer");
+ mTimedBufferQueue.removeAt(0);
+ continue;
+ }
+ if (!mLocalTimeToSampleTransform.doForwardTransform(
+ (effectivePTS - pts) << 32, &sampleDelta)) {
+ LOGV("*** too late during sample rate transform: dropped buffer");
+ mTimedBufferQueue.removeAt(0);
+ continue;
+ }
+
+ LOGV("*** %s head.pts=%lld head.pos=%d pts=%lld sampleDelta=[%d.%08x]",
+ __PRETTY_FUNCTION__, head.pts(), head.position(), pts,
+ static_cast<int32_t>((sampleDelta >= 0 ? 0 : 1) + (sampleDelta >> 32)),
+ static_cast<uint32_t>(sampleDelta & 0xFFFFFFFF));
+
+ // if the delta between the ideal placement for the next input sample and
+ // the current output position is within this threshold, then we will
+ // concatenate the next input samples to the previous output
+ const int64_t kSampleContinuityThreshold =
+ (static_cast<int64_t>(sampleRate()) << 32) / 10;
+
+ // if this is the first buffer of audio that we're emitting from this track
+ // then it should be almost exactly on time.
+ const int64_t kSampleStartupThreshold = 1LL << 32;
+
+ if ((mTimedAudioOutputOnTime && llabs(sampleDelta) <= kSampleContinuityThreshold) ||
+ (!mTimedAudioOutputOnTime && llabs(sampleDelta) <= kSampleStartupThreshold)) {
+ // the next input is close enough to being on time, so concatenate it
+ // with the last output
+ timedYieldSamples(buffer);
+
+ LOGV("*** on time: head.pos=%d frameCount=%u", head.position(), buffer->frameCount);
+ return NO_ERROR;
+ } else if (sampleDelta > 0) {
+ // the gap between the current output position and the proper start of
+ // the next input sample is too big, so fill it with silence
+ uint32_t framesUntilNextInput = (sampleDelta + 0x80000000) >> 32;
+
+ timedYieldSilence(framesUntilNextInput, buffer);
+ LOGV("*** silence: frameCount=%u", buffer->frameCount);
+ return NO_ERROR;
+ } else {
+ // the next input sample is late
+ uint32_t lateFrames = static_cast<uint32_t>(-((sampleDelta + 0x80000000) >> 32));
+ size_t onTimeSamplePosition =
+ head.position() + lateFrames * mCblk->frameSize;
+
+ if (onTimeSamplePosition > head.buffer()->size()) {
+ // all the remaining samples in the head are too late, so
+ // drop it and move on
+ LOGV("*** too late: dropped buffer");
+ mTimedBufferQueue.removeAt(0);
+ continue;
+ } else {
+ // skip over the late samples
+ head.setPosition(onTimeSamplePosition);
+
+ // yield the available samples
+ timedYieldSamples(buffer);
+
+ LOGV("*** late: head.pos=%d frameCount=%u", head.position(), buffer->frameCount);
+ return NO_ERROR;
+ }
+ }
+ }
+}
+
+// Yield samples from the timed buffer queue head up to the given output
+// buffer's capacity.
+//
+// Caller must hold mTimedBufferQueueLock
+void AudioFlinger::PlaybackThread::TimedTrack::timedYieldSamples(
+ AudioBufferProvider::Buffer* buffer) {
+
+ const TimedBuffer& head = mTimedBufferQueue[0];
+
+ buffer->raw = (static_cast<uint8_t*>(head.buffer()->pointer()) +
+ head.position());
+
+ uint32_t framesLeftInHead = ((head.buffer()->size() - head.position()) /
+ mCblk->frameSize);
+ size_t framesRequested = buffer->frameCount;
+ buffer->frameCount = min(framesLeftInHead, framesRequested);
+
+ mTimedAudioOutputOnTime = true;
+}
+
+// Yield samples of silence up to the given output buffer's capacity
+//
+// Caller must hold mTimedBufferQueueLock
+void AudioFlinger::PlaybackThread::TimedTrack::timedYieldSilence(
+ uint32_t numFrames, AudioBufferProvider::Buffer* buffer) {
+
+ // lazily allocate a buffer filled with silence
+ if (mTimedSilenceBufferSize < numFrames * mCblk->frameSize) {
+ delete [] mTimedSilenceBuffer;
+ mTimedSilenceBufferSize = numFrames * mCblk->frameSize;
+ mTimedSilenceBuffer = new uint8_t[mTimedSilenceBufferSize];
+ memset(mTimedSilenceBuffer, 0, mTimedSilenceBufferSize);
+ }
+
+ buffer->raw = mTimedSilenceBuffer;
+ size_t framesRequested = buffer->frameCount;
+ buffer->frameCount = min(numFrames, framesRequested);
+
+ mTimedAudioOutputOnTime = false;
+}
+
+void AudioFlinger::PlaybackThread::TimedTrack::releaseBuffer(
+ AudioBufferProvider::Buffer* buffer) {
+
+ Mutex::Autolock _l(mTimedBufferQueueLock);
+
+ if (buffer->raw != mTimedSilenceBuffer) {
+ TimedBuffer& head = mTimedBufferQueue.editItemAt(0);
+ head.setPosition(head.position() + buffer->frameCount * mCblk->frameSize);
+ if (static_cast<size_t>(head.position()) >= head.buffer()->size()) {
+ mTimedBufferQueue.removeAt(0);
+ }
+ }
+
+ buffer->raw = 0;
+ buffer->frameCount = 0;
+}
+
+uint32_t AudioFlinger::PlaybackThread::TimedTrack::framesReady() const {
+ Mutex::Autolock _l(mTimedBufferQueueLock);
+
+ uint32_t frames = 0;
+ for (size_t i = 0; i < mTimedBufferQueue.size(); i++) {
+ const TimedBuffer& tb = mTimedBufferQueue[i];
+ frames += (tb.buffer()->size() - tb.position()) / mCblk->frameSize;
+ }
+
+ return frames;
+}
+
+AudioFlinger::PlaybackThread::TimedTrack::TimedBuffer::TimedBuffer()
+ : mPTS(0), mPosition(0) {}
+
+AudioFlinger::PlaybackThread::TimedTrack::TimedBuffer::TimedBuffer(
+ const sp<IMemory>& buffer, int64_t pts)
+ : mBuffer(buffer), mPTS(pts), mPosition(0) {}
+
// ----------------------------------------------------------------------------
// RecordTrack constructor must be called with AudioFlinger::mLock held
@@ -3615,7 +4102,7 @@
}
}
-status_t AudioFlinger::RecordThread::RecordTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer)
+status_t AudioFlinger::RecordThread::RecordTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer, int64_t pts)
{
audio_track_cblk_t* cblk = this->cblk();
uint32_t framesAvail;
@@ -3937,7 +4424,8 @@
: RefBase(),
mAudioFlinger(audioFlinger),
mMemoryDealer(new MemoryDealer(1024*1024, "AudioFlinger::Client")),
- mPid(pid)
+ mPid(pid),
+ mTimedTrackCount(0)
{
// 1 MB of address space is good for 32 tracks, 8 buffers each, 4 KB/buffer
}
@@ -3953,6 +4441,31 @@
return mMemoryDealer;
}
+// Reserve one of the limited slots for a timed audio track associated
+// with this client
+bool AudioFlinger::Client::reserveTimedTrack()
+{
+ const int kMaxTimedTracksPerClient = 4;
+
+ Mutex::Autolock _l(mTimedTrackLock);
+
+ if (mTimedTrackCount >= kMaxTimedTracksPerClient) {
+ LOGW("can not create timed track - pid %d has exceeded the limit",
+ mPid);
+ return false;
+ }
+
+ mTimedTrackCount++;
+ return true;
+}
+
+// Release a slot for a timed audio track
+void AudioFlinger::Client::releaseTimedTrack()
+{
+ Mutex::Autolock _l(mTimedTrackLock);
+ mTimedTrackCount--;
+}
+
// ----------------------------------------------------------------------------
AudioFlinger::NotificationClient::NotificationClient(const sp<AudioFlinger>& audioFlinger,
@@ -4024,6 +4537,38 @@
return mTrack->attachAuxEffect(EffectId);
}
+status_t AudioFlinger::TrackHandle::allocateTimedBuffer(size_t size,
+ sp<IMemory>* buffer) {
+ if (!mTrack->isTimedTrack())
+ return INVALID_OPERATION;
+
+ PlaybackThread::TimedTrack* tt =
+ reinterpret_cast<PlaybackThread::TimedTrack*>(mTrack.get());
+ return tt->allocateTimedBuffer(size, buffer);
+}
+
+status_t AudioFlinger::TrackHandle::queueTimedBuffer(const sp<IMemory>& buffer,
+ int64_t pts) {
+ if (!mTrack->isTimedTrack())
+ return INVALID_OPERATION;
+
+ PlaybackThread::TimedTrack* tt =
+ reinterpret_cast<PlaybackThread::TimedTrack*>(mTrack.get());
+ return tt->queueTimedBuffer(buffer, pts);
+}
+
+status_t AudioFlinger::TrackHandle::setMediaTimeTransform(
+ const LinearTransform& xform, int target) {
+
+ if (!mTrack->isTimedTrack())
+ return INVALID_OPERATION;
+
+ PlaybackThread::TimedTrack* tt =
+ reinterpret_cast<PlaybackThread::TimedTrack*>(mTrack.get());
+ return tt->setMediaTimeTransform(
+ xform, static_cast<TimedAudioTrack::TargetTimeline>(target));
+}
+
status_t AudioFlinger::TrackHandle::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
@@ -4261,7 +4806,8 @@
}
buffer.frameCount = mFrameCount;
- if (LIKELY(mActiveTrack->getNextBuffer(&buffer) == NO_ERROR)) {
+ if (LIKELY(mActiveTrack->getNextBuffer(
+ &buffer, AudioBufferProvider::kInvalidPTS) == NO_ERROR)) {
size_t framesOut = buffer.frameCount;
if (mResampler == 0) {
// no resampling
@@ -4540,7 +5086,7 @@
return NO_ERROR;
}
-status_t AudioFlinger::RecordThread::getNextBuffer(AudioBufferProvider::Buffer* buffer)
+status_t AudioFlinger::RecordThread::getNextBuffer(AudioBufferProvider::Buffer* buffer, int64_t pts)
{
size_t framesReq = buffer->frameCount;
size_t framesReady = mFrameCount - mRsmpInIndex;
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 2e05593..17975e3 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -84,6 +84,7 @@
uint32_t flags,
const sp<IMemory>& sharedBuffer,
int output,
+ bool isTimed,
int *sessionId,
status_t *status);
@@ -97,6 +98,7 @@
virtual status_t setMasterMute(bool muted);
virtual float masterVolume() const;
+ virtual float masterVolumeSW() const;
virtual bool masterMute() const;
virtual status_t setStreamVolume(int stream, float value, int output);
@@ -188,6 +190,7 @@
AUDIO_HW_SET_MIC_MUTE,
AUDIO_SET_VOICE_VOLUME,
AUDIO_SET_PARAMETER,
+ AUDIO_HW_GET_MASTER_VOLUME,
};
// record interface
@@ -235,12 +238,18 @@
pid_t pid() const { return mPid; }
sp<AudioFlinger> audioFlinger() { return mAudioFlinger; }
+ bool reserveTimedTrack();
+ void releaseTimedTrack();
+
private:
Client(const Client&);
Client& operator = (const Client&);
sp<AudioFlinger> mAudioFlinger;
sp<MemoryDealer> mMemoryDealer;
pid_t mPid;
+
+ Mutex mTimedTrackLock;
+ int mTimedTrackCount;
};
// --- Notification Client ---
@@ -346,7 +355,9 @@
TrackBase(const TrackBase&);
TrackBase& operator = (const TrackBase&);
- virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer) = 0;
+ virtual status_t getNextBuffer(
+ AudioBufferProvider::Buffer* buffer,
+ int64_t pts) = 0;
virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer);
uint32_t format() const {
@@ -609,7 +620,6 @@
int16_t *mainBuffer() { return mMainBuffer; }
int auxEffectId() { return mAuxEffectId; }
-
protected:
friend class ThreadBase;
friend class TrackHandle;
@@ -620,7 +630,11 @@
Track(const Track&);
Track& operator = (const Track&);
- virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer);
+ virtual status_t getNextBuffer(
+ AudioBufferProvider::Buffer* buffer,
+ int64_t pts);
+ virtual uint32_t framesReady() const;
+
bool isMuted() { return mMute; }
bool isPausing() const {
return mState == PAUSING;
@@ -636,6 +650,8 @@
return (mStreamType == AUDIO_STREAM_CNT);
}
+ virtual bool isTimedTrack() const { return false; }
+
// we don't really need a lock for these
float mVolume[2];
volatile bool mMute;
@@ -653,6 +669,78 @@
bool mHasVolumeController;
}; // end of Track
+ class TimedTrack : public Track {
+ public:
+ static sp<TimedTrack> create(const wp<ThreadBase>& thread,
+ const sp<Client>& client,
+ int streamType,
+ uint32_t sampleRate,
+ uint32_t format,
+ uint32_t channelMask,
+ int frameCount,
+ const sp<IMemory>& sharedBuffer,
+ int sessionId);
+ ~TimedTrack();
+
+ class TimedBuffer {
+ public:
+ TimedBuffer();
+ TimedBuffer(const sp<IMemory>& buffer, int64_t pts);
+ const sp<IMemory>& buffer() const { return mBuffer; }
+ int64_t pts() const { return mPTS; }
+ int position() const { return mPosition; }
+ void setPosition(int pos) { mPosition = pos; }
+ private:
+ sp<IMemory> mBuffer;
+ int64_t mPTS;
+ int mPosition;
+ };
+
+ virtual bool isTimedTrack() const { return true; }
+
+ virtual uint32_t framesReady() const;
+
+ virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer,
+ int64_t pts);
+ virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer);
+ void timedYieldSamples(AudioBufferProvider::Buffer* buffer);
+ void timedYieldSilence(uint32_t numFrames,
+ AudioBufferProvider::Buffer* buffer);
+
+ status_t allocateTimedBuffer(size_t size,
+ sp<IMemory>* buffer);
+ status_t queueTimedBuffer(const sp<IMemory>& buffer,
+ int64_t pts);
+ status_t setMediaTimeTransform(const LinearTransform& xform,
+ TimedAudioTrack::TargetTimeline target);
+ void trimTimedBufferQueue_l();
+
+ private:
+ TimedTrack(const wp<ThreadBase>& thread,
+ const sp<Client>& client,
+ int streamType,
+ uint32_t sampleRate,
+ uint32_t format,
+ uint32_t channelMask,
+ int frameCount,
+ const sp<IMemory>& sharedBuffer,
+ int sessionId);
+
+ uint64_t mLocalTimeFreq;
+ LinearTransform mLocalTimeToSampleTransform;
+ sp<MemoryDealer> mTimedMemoryDealer;
+ Vector<TimedBuffer> mTimedBufferQueue;
+ uint8_t* mTimedSilenceBuffer;
+ uint32_t mTimedSilenceBufferSize;
+ mutable Mutex mTimedBufferQueueLock;
+ bool mTimedAudioOutputOnTime;
+
+ Mutex mMediaTimeTransformLock;
+ LinearTransform mMediaTimeTransform;
+ bool mMediaTimeTransformValid;
+ TimedAudioTrack::TargetTimeline mMediaTimeTransformTarget;
+ };
+
// playback track
class OutputTrack : public Track {
@@ -726,6 +814,7 @@
int frameCount,
const sp<IMemory>& sharedBuffer,
int sessionId,
+ bool isTimed,
status_t *status);
AudioStreamOut* getOutput();
@@ -914,6 +1003,12 @@
virtual void setVolume(float left, float right);
virtual sp<IMemory> getCblk() const;
virtual status_t attachAuxEffect(int effectId);
+ virtual status_t allocateTimedBuffer(size_t size,
+ sp<IMemory>* buffer);
+ virtual status_t queueTimedBuffer(const sp<IMemory>& buffer,
+ int64_t pts);
+ virtual status_t setMediaTimeTransform(const LinearTransform& xform,
+ int target);
virtual status_t onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags);
private:
@@ -961,7 +1056,9 @@
RecordTrack(const RecordTrack&);
RecordTrack& operator = (const RecordTrack&);
- virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer);
+ virtual status_t getNextBuffer(
+ AudioBufferProvider::Buffer* buffer,
+ int64_t pts);
bool mOverflow;
};
@@ -997,7 +1094,8 @@
AudioStreamIn* clearInput();
virtual audio_stream_t* stream();
- virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer);
+ virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer,
+ int64_t pts);
virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer);
virtual bool checkForNewParameters_l();
virtual String8 getParameters(const String8& keys);
@@ -1373,6 +1471,12 @@
friend class RecordThread;
friend class PlaybackThread;
+ enum master_volume_support {
+ MVS_NONE,
+ MVS_SETONLY,
+ MVS_FULL,
+ };
+
mutable Mutex mLock;
DefaultKeyedVector< pid_t, wp<Client> > mClients;
@@ -1386,6 +1490,8 @@
DefaultKeyedVector< int, sp<PlaybackThread> > mPlaybackThreads;
PlaybackThread::stream_type_t mStreamTypes[AUDIO_STREAM_CNT];
float mMasterVolume;
+ float mMasterVolumeSW;
+ master_volume_support mMasterVolumeSupportLvl;
bool mMasterMute;
DefaultKeyedVector< int, sp<RecordThread> > mRecordThreads;
diff --git a/services/audioflinger/AudioMixer.cpp b/services/audioflinger/AudioMixer.cpp
index 6e9319d..760981b 100644
--- a/services/audioflinger/AudioMixer.cpp
+++ b/services/audioflinger/AudioMixer.cpp
@@ -18,6 +18,7 @@
#define LOG_TAG "AudioMixer"
//#define LOG_NDEBUG 0
+#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
@@ -30,6 +31,9 @@
#include <system/audio.h>
+#include <aah_timesrv/local_clock.h>
+#include <aah_timesrv/cc_helper.h>
+
#include "AudioMixer.h"
namespace android {
@@ -47,6 +51,9 @@
AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate)
: mActiveTrack(0), mTrackNames(0), mSampleRate(sampleRate)
{
+ LocalClock lc;
+ mLocalTimeFreq = lc.getLocalFreq();
+
mState.enabledTracks= 0;
mState.needsChanged = 0;
mState.frameCount = frameCount;
@@ -74,6 +81,7 @@
t->in = 0;
t->mainBuffer = NULL;
t->auxBuffer = NULL;
+ t->localTimeFreq = mLocalTimeFreq;
t++;
}
}
@@ -293,6 +301,7 @@
if (resampler == 0) {
resampler = AudioResampler::create(
format, channelCount, devSampleRate);
+ resampler->setLocalTimeFreq(localTimeFreq);
}
return true;
}
@@ -340,13 +349,13 @@
-void AudioMixer::process()
+void AudioMixer::process(int64_t pts)
{
- mState.hook(&mState);
+ mState.hook(&mState, pts);
}
-void AudioMixer::process__validate(state_t* state)
+void AudioMixer::process__validate(state_t* state, int64_t pts)
{
LOGW_IF(!state->needsChanged,
"in process__validate() but nothing's invalid");
@@ -450,7 +459,7 @@
countActiveTracks, state->enabledTracks,
all16BitsStereoNoResample, resampling, volumeRamp);
- state->hook(state);
+ state->hook(state, pts);
// Now that the volume ramp has been done, set optimal state and
// track hooks for subsequent mixer process
@@ -859,7 +868,7 @@
}
// no-op case
-void AudioMixer::process__nop(state_t* state)
+void AudioMixer::process__nop(state_t* state, int64_t pts)
{
uint32_t e0 = state->enabledTracks;
size_t bufSize = state->frameCount * sizeof(int16_t) * MAX_NUM_CHANNELS;
@@ -889,7 +898,9 @@
size_t outFrames = state->frameCount;
while (outFrames) {
t1.buffer.frameCount = outFrames;
- t1.bufferProvider->getNextBuffer(&t1.buffer);
+ int64_t outputPTS = calculateOutputPTS(
+ t1, pts, state->frameCount - outFrames);
+ t1.bufferProvider->getNextBuffer(&t1.buffer, outputPTS);
if (!t1.buffer.raw) break;
outFrames -= t1.buffer.frameCount;
t1.bufferProvider->releaseBuffer(&t1.buffer);
@@ -899,7 +910,7 @@
}
// generic code without resampling
-void AudioMixer::process__genericNoResampling(state_t* state)
+void AudioMixer::process__genericNoResampling(state_t* state, int64_t pts)
{
int32_t outTemp[BLOCKSIZE * MAX_NUM_CHANNELS] __attribute__((aligned(32)));
@@ -911,7 +922,7 @@
e0 &= ~(1<<i);
track_t& t = state->tracks[i];
t.buffer.frameCount = state->frameCount;
- t.bufferProvider->getNextBuffer(&t.buffer);
+ t.bufferProvider->getNextBuffer(&t.buffer, pts);
t.frameCount = t.buffer.frameCount;
t.in = t.buffer.raw;
// t.in == NULL can happen if the track was flushed just after having
@@ -965,7 +976,9 @@
if (t.frameCount == 0 && outFrames) {
t.bufferProvider->releaseBuffer(&t.buffer);
t.buffer.frameCount = (state->frameCount - numFrames) - (BLOCKSIZE - outFrames);
- t.bufferProvider->getNextBuffer(&t.buffer);
+ int64_t outputPTS = calculateOutputPTS(
+ t, pts, numFrames + (BLOCKSIZE - outFrames));
+ t.bufferProvider->getNextBuffer(&t.buffer, outputPTS);
t.in = t.buffer.raw;
if (t.in == NULL) {
enabledTracks &= ~(1<<i);
@@ -994,7 +1007,7 @@
// generic code with resampling
-void AudioMixer::process__genericResampling(state_t* state)
+void AudioMixer::process__genericResampling(state_t* state, int64_t pts)
{
int32_t* const outTemp = state->outputTemp;
const size_t size = sizeof(int32_t) * MAX_NUM_CHANNELS * state->frameCount;
@@ -1033,6 +1046,7 @@
// acquire/release the buffers because it's done by
// the resampler.
if ((t.needs & NEEDS_RESAMPLE__MASK) == NEEDS_RESAMPLE_ENABLED) {
+ t.resampler->setPTS(pts);
(t.hook)(&t, outTemp, numFrames, state->resampleTemp, aux);
} else {
@@ -1040,7 +1054,8 @@
while (outFrames < numFrames) {
t.buffer.frameCount = numFrames - outFrames;
- t.bufferProvider->getNextBuffer(&t.buffer);
+ int64_t outputPTS = calculateOutputPTS(t, pts, outFrames);
+ t.bufferProvider->getNextBuffer(&t.buffer, outputPTS);
t.in = t.buffer.raw;
// t.in == NULL can happen if the track was flushed just after having
// been enabled for mixing.
@@ -1060,7 +1075,8 @@
}
// one track, 16 bits stereo without resampling is the most common case
-void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state)
+void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state,
+ int64_t pts)
{
const int i = 31 - __builtin_clz(state->enabledTracks);
const track_t& t = state->tracks[i];
@@ -1075,7 +1091,8 @@
const uint32_t vrl = t.volumeRL;
while (numFrames) {
b.frameCount = numFrames;
- t.bufferProvider->getNextBuffer(&b);
+ int64_t outputPTS = calculateOutputPTS(t, pts, out - t.mainBuffer);
+ t.bufferProvider->getNextBuffer(&b, outputPTS);
int16_t const *in = b.i16;
// in == NULL can happen if the track was flushed just after having
@@ -1118,7 +1135,8 @@
// 2 tracks is also a common case
// NEVER used in current implementation of process__validate()
// only use if the 2 tracks have the same output buffer
-void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state)
+void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state,
+ int64_t pts)
{
int i;
uint32_t en = state->enabledTracks;
@@ -1152,7 +1170,9 @@
if (frameCount0 == 0) {
b0.frameCount = numFrames;
- t0.bufferProvider->getNextBuffer(&b0);
+ int64_t outputPTS = calculateOutputPTS(t0, pts,
+ out - t0.mainBuffer);
+ t0.bufferProvider->getNextBuffer(&b0, outputPTS);
if (b0.i16 == NULL) {
if (buff == NULL) {
buff = new int16_t[MAX_NUM_CHANNELS * state->frameCount];
@@ -1166,7 +1186,9 @@
}
if (frameCount1 == 0) {
b1.frameCount = numFrames;
- t1.bufferProvider->getNextBuffer(&b1);
+ int64_t outputPTS = calculateOutputPTS(t1, pts,
+ out - t0.mainBuffer);
+ t1.bufferProvider->getNextBuffer(&b1, outputPTS);
if (b1.i16 == NULL) {
if (buff == NULL) {
buff = new int16_t[MAX_NUM_CHANNELS * state->frameCount];
@@ -1213,6 +1235,11 @@
}
}
+int64_t AudioMixer::calculateOutputPTS(const track_t& t, int64_t basePTS,
+ int outputFrameIndex)
+{
+ return basePTS + ((outputFrameIndex * t.localTimeFreq) / t.sampleRate);
+}
+
// ----------------------------------------------------------------------------
}; // namespace android
-
diff --git a/services/audioflinger/AudioMixer.h b/services/audioflinger/AudioMixer.h
index 75c9170..d32a366 100644
--- a/services/audioflinger/AudioMixer.h
+++ b/services/audioflinger/AudioMixer.h
@@ -85,7 +85,7 @@
status_t setParameter(int target, int name, void *value);
status_t setBufferProvider(AudioBufferProvider* bufferProvider);
- void process();
+ void process(int64_t pts);
uint32_t trackNames() const { return mTrackNames; }
@@ -125,7 +125,7 @@
struct state_t;
struct track_t;
- typedef void (*mix_t)(state_t* state);
+ typedef void (*mix_t)(state_t* state, int64_t pts);
typedef void (*hook_t)(track_t* t, int32_t* output, size_t numOutFrames, int32_t* temp, int32_t* aux);
static const int BLOCKSIZE = 16; // 4 cache lines
@@ -163,6 +163,8 @@
int32_t* mainBuffer;
int32_t* auxBuffer;
+ uint64_t localTimeFreq;
+
bool setResampler(uint32_t sampleRate, uint32_t devSampleRate);
bool doesResample() const;
void resetResampler();
@@ -185,6 +187,8 @@
uint32_t mTrackNames;
const uint32_t mSampleRate;
+ int64_t mLocalTimeFreq;
+
state_t mState __attribute__((aligned(32)));
void invalidateState(uint32_t mask);
@@ -196,12 +200,17 @@
static void volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux);
static void volumeStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux);
- static void process__validate(state_t* state);
- static void process__nop(state_t* state);
- static void process__genericNoResampling(state_t* state);
- static void process__genericResampling(state_t* state);
- static void process__OneTrack16BitsStereoNoResampling(state_t* state);
- static void process__TwoTracks16BitsStereoNoResampling(state_t* state);
+ static void process__validate(state_t* state, int64_t pts);
+ static void process__nop(state_t* state, int64_t pts);
+ static void process__genericNoResampling(state_t* state, int64_t pts);
+ static void process__genericResampling(state_t* state, int64_t pts);
+ static void process__OneTrack16BitsStereoNoResampling(state_t* state,
+ int64_t pts);
+ static void process__TwoTracks16BitsStereoNoResampling(state_t* state,
+ int64_t pts);
+
+ static int64_t calculateOutputPTS(const track_t& t, int64_t basePTS,
+ int outputFrameIndex);
};
// ----------------------------------------------------------------------------
diff --git a/services/audioflinger/AudioResampler.cpp b/services/audioflinger/AudioResampler.cpp
index dca795c..2dc49df 100644
--- a/services/audioflinger/AudioResampler.cpp
+++ b/services/audioflinger/AudioResampler.cpp
@@ -118,7 +118,8 @@
int32_t sampleRate) :
mBitDepth(bitDepth), mChannelCount(inChannelCount),
mSampleRate(sampleRate), mInSampleRate(sampleRate), mInputIndex(0),
- mPhaseFraction(0) {
+ mPhaseFraction(0), mLocalTimeFreq(0),
+ mPTS(AudioBufferProvider::kInvalidPTS) {
// sanity check on format
if ((bitDepth != 16) ||(inChannelCount < 1) || (inChannelCount > 2)) {
LOGE("Unsupported sample format, %d bits, %d channels", bitDepth,
@@ -152,6 +153,23 @@
mVolume[1] = right;
}
+void AudioResampler::setLocalTimeFreq(uint64_t freq) {
+ mLocalTimeFreq = freq;
+}
+
+void AudioResampler::setPTS(int64_t pts) {
+ mPTS = pts;
+}
+
+int64_t AudioResampler::calculateOutputPTS(int outputFrameIndex) {
+
+ if (mPTS == AudioBufferProvider::kInvalidPTS) {
+ return AudioBufferProvider::kInvalidPTS;
+ } else {
+ return mPTS + ((outputFrameIndex * mLocalTimeFreq) / mSampleRate);
+ }
+}
+
void AudioResampler::reset() {
mInputIndex = 0;
mPhaseFraction = 0;
@@ -198,7 +216,8 @@
// buffer is empty, fetch a new one
while (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer,
+ calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL) {
goto resampleStereo16_exit;
}
@@ -292,7 +311,8 @@
// buffer is empty, fetch a new one
while (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer,
+ calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL) {
mInputIndex = inputIndex;
mPhaseFraction = phaseFraction;
@@ -602,4 +622,3 @@
// ----------------------------------------------------------------------------
}
; // namespace android
-
diff --git a/services/audioflinger/AudioResampler.h b/services/audioflinger/AudioResampler.h
index 9f06c1c..da736ab 100644
--- a/services/audioflinger/AudioResampler.h
+++ b/services/audioflinger/AudioResampler.h
@@ -49,6 +49,10 @@
virtual void init() = 0;
virtual void setSampleRate(int32_t inSampleRate);
virtual void setVolume(int16_t left, int16_t right);
+ virtual void setLocalTimeFreq(uint64_t freq);
+
+ // set the PTS of the next buffer output by the resampler
+ virtual void setPTS(int64_t pts);
virtual void resample(int32_t* out, size_t outFrameCount,
AudioBufferProvider* provider) = 0;
@@ -72,6 +76,8 @@
AudioResampler(const AudioResampler&);
AudioResampler& operator=(const AudioResampler&);
+ int64_t calculateOutputPTS(int outputFrameIndex);
+
int32_t mBitDepth;
int32_t mChannelCount;
int32_t mSampleRate;
@@ -86,6 +92,8 @@
size_t mInputIndex;
int32_t mPhaseIncrement;
uint32_t mPhaseFraction;
+ uint64_t mLocalTimeFreq;
+ int64_t mPTS;
};
// ----------------------------------------------------------------------------
diff --git a/services/audioflinger/AudioResamplerCubic.cpp b/services/audioflinger/AudioResamplerCubic.cpp
index 4d721f6..e864b30 100644
--- a/services/audioflinger/AudioResamplerCubic.cpp
+++ b/services/audioflinger/AudioResamplerCubic.cpp
@@ -65,7 +65,7 @@
// fetch first buffer
if (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer, mPTS);
if (mBuffer.raw == NULL)
return;
// LOGW("New buffer: offset=%p, frames=%dn", mBuffer.raw, mBuffer.frameCount);
@@ -95,7 +95,8 @@
inputIndex = 0;
provider->releaseBuffer(&mBuffer);
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer,
+ calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL)
goto save_state; // ugly, but efficient
in = mBuffer.i16;
@@ -130,7 +131,7 @@
// fetch first buffer
if (mBuffer.frameCount == 0) {
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer, mPTS);
if (mBuffer.raw == NULL)
return;
// LOGW("New buffer: offset=%p, frames=%d\n", mBuffer.raw, mBuffer.frameCount);
@@ -160,7 +161,8 @@
inputIndex = 0;
provider->releaseBuffer(&mBuffer);
mBuffer.frameCount = inFrameCount;
- provider->getNextBuffer(&mBuffer);
+ provider->getNextBuffer(&mBuffer,
+ calculateOutputPTS(outputIndex / 2));
if (mBuffer.raw == NULL)
goto save_state; // ugly, but efficient
// LOGW("New buffer: offset=%p, frames=%dn", mBuffer.raw, mBuffer.frameCount);
@@ -181,4 +183,3 @@
// ----------------------------------------------------------------------------
}
; // namespace android
-
diff --git a/services/audioflinger/AudioResamplerSinc.cpp b/services/audioflinger/AudioResamplerSinc.cpp
index 9e5e254..6184795 100644
--- a/services/audioflinger/AudioResamplerSinc.cpp
+++ b/services/audioflinger/AudioResamplerSinc.cpp
@@ -204,7 +204,8 @@
// buffer is empty, fetch a new one
while (buffer.frameCount == 0) {
buffer.frameCount = inFrameCount;
- provider->getNextBuffer(&buffer);
+ provider->getNextBuffer(&buffer,
+ calculateOutputPTS(outputIndex / 2));
if (buffer.raw == NULL) {
goto resample_exit;
}
@@ -355,4 +356,3 @@
// ----------------------------------------------------------------------------
}; // namespace android
-
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index bbc26d6..69cac48 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -50,6 +50,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.WorkSource;
import android.provider.Settings.SettingNotFoundException;
import android.provider.Settings;
@@ -158,6 +159,7 @@
private boolean mDoneBooting = false;
private boolean mBootCompleted = false;
+ private boolean mHeadless = false;
private int mStayOnConditions = 0;
private final int[] mBroadcastQueue = new int[] { -1, -1, -1 };
private final int[] mBroadcastWhy = new int[3];
@@ -504,6 +506,7 @@
mButtonLight = lights.getLight(LightsService.LIGHT_ID_BUTTONS);
mKeyboardLight = lights.getLight(LightsService.LIGHT_ID_KEYBOARD);
mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION);
+ mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0"));
nativeInit();
synchronized (mLocks) {
@@ -1806,9 +1809,11 @@
}
}
}
- nativeSetPowerState(
- (mPowerState & SCREEN_ON_BIT) != 0,
- (mPowerState & SCREEN_BRIGHT) == SCREEN_BRIGHT);
+ if (!mHeadless) {
+ nativeSetPowerState(
+ (mPowerState & SCREEN_ON_BIT) != 0,
+ (mPowerState & SCREEN_BRIGHT) == SCREEN_BRIGHT);
+ }
}
private int screenOffFinishedAnimatingLocked(int reason) {
@@ -2097,7 +2102,9 @@
} else {
synchronized (mLocks) {
// we're turning off
- final boolean animate = animating && targetValue == Power.BRIGHTNESS_OFF;
+ final boolean animate = animating
+ && targetValue == Power.BRIGHTNESS_OFF
+ && !mHeadless;
if (animate) {
// It's pretty scary to hold mLocks for this long, and we should
// redesign this, but it works for now.
diff --git a/services/java/com/android/server/SerialService.java b/services/java/com/android/server/SerialService.java
new file mode 100644
index 0000000..5d2b2a0
--- /dev/null
+++ b/services/java/com/android/server/SerialService.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2011 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 an
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.Context;
+import android.hardware.ISerialManager;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.util.ArrayList;
+
+public class SerialService extends ISerialManager.Stub {
+
+ private final Context mContext;
+ private final String[] mSerialPorts;
+
+ public SerialService(Context context) {
+ mContext = context;
+ mSerialPorts = context.getResources().getStringArray(
+ com.android.internal.R.array.config_serialPorts);
+ }
+
+ public String[] getSerialPorts() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null);
+
+ ArrayList<String> ports = new ArrayList<String>();
+ for (int i = 0; i < mSerialPorts.length; i++) {
+ String path = mSerialPorts[i];
+ if (new File(path).exists()) {
+ ports.add(path);
+ }
+ }
+ String[] result = new String[ports.size()];
+ ports.toArray(result);
+ return result;
+ }
+
+ public ParcelFileDescriptor openSerialPort(String path) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null);
+ return native_open(path);
+ }
+
+ private native ParcelFileDescriptor native_open(String path);
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2714fc5..c6941dd 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -106,6 +106,7 @@
String factoryTestStr = SystemProperties.get("ro.factorytest");
int factoryTest = "".equals(factoryTestStr) ? SystemServer.FACTORY_TEST_OFF
: Integer.parseInt(factoryTestStr);
+ final boolean headless = "1".equals(SystemProperties.get("ro.config.headless", "0"));
LightsService lights = null;
PowerManagerService power = null;
@@ -124,6 +125,7 @@
BluetoothA2dpService bluetoothA2dp = null;
DockObserver dock = null;
UsbService usb = null;
+ SerialService serial = null;
UiModeManagerService uiMode = null;
RecognitionManagerService recognition = null;
ThrottleService throttle = null;
@@ -375,15 +377,17 @@
reportWtf("starting ThrottleService", e);
}
- try {
- /*
- * NotificationManagerService is dependant on MountService,
- * (for media / usb notifications) so we must start MountService first.
- */
- Slog.i(TAG, "Mount Service");
- ServiceManager.addService("mount", new MountService(context));
- } catch (Throwable e) {
- reportWtf("starting Mount Service", e);
+ if (!"0".equals(SystemProperties.get("system_init.startmountservice"))) {
+ try {
+ /*
+ * NotificationManagerService is dependant on MountService,
+ * (for media / usb notifications) so we must start MountService first.
+ */
+ Slog.i(TAG, "Mount Service");
+ ServiceManager.addService("mount", new MountService(context));
+ } catch (Throwable e) {
+ reportWtf("starting Mount Service", e);
+ }
}
try {
@@ -437,17 +441,21 @@
try {
Slog.i(TAG, "Wallpaper Service");
- wallpaper = new WallpaperManagerService(context);
- ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper);
+ if (!headless) {
+ wallpaper = new WallpaperManagerService(context);
+ ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper);
+ }
} catch (Throwable e) {
reportWtf("starting Wallpaper Service", e);
}
- try {
- Slog.i(TAG, "Audio Service");
- ServiceManager.addService(Context.AUDIO_SERVICE, new AudioService(context));
- } catch (Throwable e) {
- reportWtf("starting Audio Service", e);
+ if (!"0".equals(SystemProperties.get("system_init.startaudioservice"))) {
+ try {
+ Slog.i(TAG, "Audio Service");
+ ServiceManager.addService(Context.AUDIO_SERVICE, new AudioService(context));
+ } catch (Throwable e) {
+ reportWtf("starting Audio Service", e);
+ }
}
try {
@@ -476,6 +484,15 @@
}
try {
+ Slog.i(TAG, "Serial Service");
+ // Serial port support
+ serial = new SerialService(context);
+ ServiceManager.addService(Context.SERIAL_SERVICE, serial);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failure starting SerialService", e);
+ }
+
+ try {
Slog.i(TAG, "UI Mode Manager Service");
// Listen for UI mode changes
uiMode = new UiModeManagerService(context);
@@ -621,7 +638,7 @@
public void run() {
Slog.i(TAG, "Making services ready");
- startSystemUi(contextF);
+ if (!headless) startSystemUi(contextF);
try {
if (batteryF != null) batteryF.systemReady();
} catch (Throwable e) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index d6d3b9d..93b9662 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -257,7 +257,9 @@
static final String[] EMPTY_STRING_ARRAY = new String[0];
public ActivityStack mMainStack;
-
+
+ private final boolean mHeadless;
+
/**
* Description of a request to start a new activity, which has been held
* due to app switches being disabled.
@@ -1366,6 +1368,7 @@
mUsageStatsService = new UsageStatsService(new File(
systemDir, "usagestats").toString());
+ mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0"));
GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version",
ConfigurationInfo.GL_ES_VERSION_UNDEFINED);
@@ -1901,6 +1904,13 @@
}
boolean startHomeActivityLocked() {
+ if (mHeadless) {
+ // Added because none of the other calls to ensureBootCompleted seem to fire
+ // when running headless.
+ ensureBootCompleted();
+ return false;
+ }
+
if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL
&& mTopAction == null) {
// We are running in factory test mode, but unable to find
@@ -3631,7 +3641,9 @@
if (hr.app == null && app.info.uid == hr.info.applicationInfo.uid
&& processName.equals(hr.processName)) {
try {
- if (mMainStack.realStartActivityLocked(hr, app, true, true)) {
+ if (mHeadless) {
+ Slog.e(TAG, "Starting activities not supported on headless device: " + hr);
+ } else if (mMainStack.realStartActivityLocked(hr, app, true, true)) {
didSomething = true;
}
} catch (Exception e) {
@@ -3809,7 +3821,7 @@
synchronized (this) {
booting = mBooting;
mBooting = false;
- enableScreen = !mBooted;
+ enableScreen = !mBooted && !mHeadless;
mBooted = true;
}
@@ -12737,6 +12749,9 @@
*/
public boolean updateConfigurationLocked(Configuration values,
ActivityRecord starting, boolean persistent) {
+ // do nothing if we are headless
+ if (mHeadless) return true;
+
int changes = 0;
boolean kept = true;
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 211c4da..03a79ec 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -245,6 +245,7 @@
private static final String SYSTEM_SECURE = "ro.secure";
private static final String SYSTEM_DEBUGGABLE = "ro.debuggable";
+ private static final String SYSTEM_HEADLESS = "ro.config.headless";
/**
* Condition waited on by {@link #reenableKeyguard} to know the call to
@@ -254,6 +255,8 @@
*/
private boolean mKeyguardDisabled = false;
+ private final boolean mHeadless;
+
private static final int ALLOW_DISABLE_YES = 1;
private static final int ALLOW_DISABLE_NO = 0;
private static final int ALLOW_DISABLE_UNKNOWN = -1; // check with DevicePolicyManager
@@ -719,6 +722,7 @@
mHaveInputMethods = haveInputMethods;
mLimitedAlphaCompositing = context.getResources().getBoolean(
com.android.internal.R.bool.config_sf_limitedAlpha);
+ mHeadless = "1".equals(SystemProperties.get(SYSTEM_HEADLESS, "0"));
mPowerManager = pm;
mPowerManager.setPolicy(mPolicy);
@@ -4854,6 +4858,8 @@
// TODO: more accounting of which pid(s) turned it on, keep count,
// only allow disables from pids which have count on, etc.
public void showStrictModeViolation(boolean on) {
+ if (mHeadless) return;
+
int pid = Binder.getCallingPid();
synchronized(mWindowMap) {
// Ignoring requests to enable the red border from clients
diff --git a/services/jni/Android.mk b/services/jni/Android.mk
index 6fa5dfa..c63b84d 100644
--- a/services/jni/Android.mk
+++ b/services/jni/Android.mk
@@ -9,6 +9,7 @@
com_android_server_InputWindowHandle.cpp \
com_android_server_LightsService.cpp \
com_android_server_PowerManagerService.cpp \
+ com_android_server_SerialService.cpp \
com_android_server_SystemServer.cpp \
com_android_server_UsbDeviceManager.cpp \
com_android_server_UsbHostManager.cpp \
diff --git a/services/jni/com_android_server_BatteryService.cpp b/services/jni/com_android_server_BatteryService.cpp
index b9f2c1f..87ca3bf 100644
--- a/services/jni/com_android_server_BatteryService.cpp
+++ b/services/jni/com_android_server_BatteryService.cpp
@@ -233,75 +233,75 @@
DIR* dir = opendir(POWER_SUPPLY_PATH);
if (dir == NULL) {
LOGE("Could not open %s\n", POWER_SUPPLY_PATH);
- return -1;
- }
- while ((entry = readdir(dir))) {
- const char* name = entry->d_name;
+ } else {
+ while ((entry = readdir(dir))) {
+ const char* name = entry->d_name;
- // ignore "." and ".."
- if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {
- continue;
- }
-
- char buf[20];
- // Look for "type" file in each subdirectory
- snprintf(path, sizeof(path), "%s/%s/type", POWER_SUPPLY_PATH, name);
- int length = readFromFile(path, buf, sizeof(buf));
- if (length > 0) {
- if (buf[length - 1] == '\n')
- buf[length - 1] = 0;
-
- if (strcmp(buf, "Mains") == 0) {
- snprintf(path, sizeof(path), "%s/%s/online", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.acOnlinePath = strdup(path);
+ // ignore "." and ".."
+ if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {
+ continue;
}
- else if (strcmp(buf, "USB") == 0) {
- snprintf(path, sizeof(path), "%s/%s/online", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.usbOnlinePath = strdup(path);
- }
- else if (strcmp(buf, "Battery") == 0) {
- snprintf(path, sizeof(path), "%s/%s/status", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.batteryStatusPath = strdup(path);
- snprintf(path, sizeof(path), "%s/%s/health", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.batteryHealthPath = strdup(path);
- snprintf(path, sizeof(path), "%s/%s/present", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.batteryPresentPath = strdup(path);
- snprintf(path, sizeof(path), "%s/%s/capacity", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.batteryCapacityPath = strdup(path);
- snprintf(path, sizeof(path), "%s/%s/voltage_now", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0) {
- gPaths.batteryVoltagePath = strdup(path);
- // voltage_now is in microvolts, not millivolts
- gVoltageDivisor = 1000;
- } else {
- snprintf(path, sizeof(path), "%s/%s/batt_vol", POWER_SUPPLY_PATH, name);
+ char buf[20];
+ // Look for "type" file in each subdirectory
+ snprintf(path, sizeof(path), "%s/%s/type", POWER_SUPPLY_PATH, name);
+ int length = readFromFile(path, buf, sizeof(buf));
+ if (length > 0) {
+ if (buf[length - 1] == '\n')
+ buf[length - 1] = 0;
+
+ if (strcmp(buf, "Mains") == 0) {
+ snprintf(path, sizeof(path), "%s/%s/online", POWER_SUPPLY_PATH, name);
if (access(path, R_OK) == 0)
+ gPaths.acOnlinePath = strdup(path);
+ }
+ else if (strcmp(buf, "USB") == 0) {
+ snprintf(path, sizeof(path), "%s/%s/online", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.usbOnlinePath = strdup(path);
+ }
+ else if (strcmp(buf, "Battery") == 0) {
+ snprintf(path, sizeof(path), "%s/%s/status", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryStatusPath = strdup(path);
+ snprintf(path, sizeof(path), "%s/%s/health", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryHealthPath = strdup(path);
+ snprintf(path, sizeof(path), "%s/%s/present", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryPresentPath = strdup(path);
+ snprintf(path, sizeof(path), "%s/%s/capacity", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryCapacityPath = strdup(path);
+
+ snprintf(path, sizeof(path), "%s/%s/voltage_now", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0) {
gPaths.batteryVoltagePath = strdup(path);
- }
+ // voltage_now is in microvolts, not millivolts
+ gVoltageDivisor = 1000;
+ } else {
+ snprintf(path, sizeof(path), "%s/%s/batt_vol", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryVoltagePath = strdup(path);
+ }
- snprintf(path, sizeof(path), "%s/%s/temp", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0) {
- gPaths.batteryTemperaturePath = strdup(path);
- } else {
- snprintf(path, sizeof(path), "%s/%s/batt_temp", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
+ snprintf(path, sizeof(path), "%s/%s/temp", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0) {
gPaths.batteryTemperaturePath = strdup(path);
- }
+ } else {
+ snprintf(path, sizeof(path), "%s/%s/batt_temp", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryTemperaturePath = strdup(path);
+ }
- snprintf(path, sizeof(path), "%s/%s/technology", POWER_SUPPLY_PATH, name);
- if (access(path, R_OK) == 0)
- gPaths.batteryTechnologyPath = strdup(path);
+ snprintf(path, sizeof(path), "%s/%s/technology", POWER_SUPPLY_PATH, name);
+ if (access(path, R_OK) == 0)
+ gPaths.batteryTechnologyPath = strdup(path);
+ }
}
}
+ closedir(dir);
}
- closedir(dir);
if (!gPaths.acOnlinePath)
LOGE("acOnlinePath not found");
diff --git a/services/jni/com_android_server_SerialService.cpp b/services/jni/com_android_server_SerialService.cpp
new file mode 100644
index 0000000..4bb7e36
--- /dev/null
+++ b/services/jni/com_android_server_SerialService.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "SerialServiceJNI"
+#include "utils/Log.h"
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+namespace android
+{
+
+static struct parcel_file_descriptor_offsets_t
+{
+ jclass mClass;
+ jmethodID mConstructor;
+} gParcelFileDescriptorOffsets;
+
+static jobject android_server_SerialService_open(JNIEnv *env, jobject thiz, jstring path)
+{
+ const char *pathStr = env->GetStringUTFChars(path, NULL);
+
+ int fd = open(pathStr, O_RDWR | O_NOCTTY);
+ if (fd < 0) {
+ LOGE("could not open %s", pathStr);
+ env->ReleaseStringUTFChars(path, pathStr);
+ return NULL;
+ }
+ env->ReleaseStringUTFChars(path, pathStr);
+
+ jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
+ if (fileDescriptor == NULL) {
+ return NULL;
+ }
+ return env->NewObject(gParcelFileDescriptorOffsets.mClass,
+ gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
+}
+
+
+static JNINativeMethod method_table[] = {
+ { "native_open", "(Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;",
+ (void*)android_server_SerialService_open },
+};
+
+int register_android_server_SerialService(JNIEnv *env)
+{
+ jclass clazz = env->FindClass("com/android/server/SerialService");
+ if (clazz == NULL) {
+ LOGE("Can't find com/android/server/SerialService");
+ return -1;
+ }
+
+ clazz = env->FindClass("android/os/ParcelFileDescriptor");
+ LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor");
+ gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
+ gParcelFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V");
+ LOG_FATAL_IF(gParcelFileDescriptorOffsets.mConstructor == NULL,
+ "Unable to find constructor for android.os.ParcelFileDescriptor");
+
+ return jniRegisterNativeMethods(env, "com/android/server/SerialService",
+ method_table, NELEM(method_table));
+}
+
+};
diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp
index 4178039..0a93525 100644
--- a/services/jni/onload.cpp
+++ b/services/jni/onload.cpp
@@ -27,6 +27,7 @@
int register_android_server_InputManager(JNIEnv* env);
int register_android_server_LightsService(JNIEnv* env);
int register_android_server_PowerManagerService(JNIEnv* env);
+int register_android_server_SerialService(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
@@ -49,6 +50,7 @@
LOG_ASSERT(env, "Could not retrieve the env!");
register_android_server_PowerManagerService(env);
+ register_android_server_SerialService(env);
register_android_server_InputApplicationHandle(env);
register_android_server_InputWindowHandle(env);
register_android_server_InputManager(env);
diff --git a/tests/SerialChat/Android.mk b/tests/SerialChat/Android.mk
new file mode 100644
index 0000000..a534e1a
--- /dev/null
+++ b/tests/SerialChat/Android.mk
@@ -0,0 +1,26 @@
+#
+# Copyright (C) 2011 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_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := SerialChat
+
+include $(BUILD_PACKAGE)
diff --git a/tests/SerialChat/AndroidManifest.xml b/tests/SerialChat/AndroidManifest.xml
new file mode 100644
index 0000000..0efdb58
--- /dev/null
+++ b/tests/SerialChat/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.serialchat">
+
+ <uses-permission android:name="android.permission.SERIAL_PORT"/>
+
+ <application android:label="Serial Chat">
+ <activity android:name="SerialChat" android:label="Serial Chat">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/SerialChat/res/layout/serial_chat.xml b/tests/SerialChat/res/layout/serial_chat.xml
new file mode 100644
index 0000000..596ecbf
--- /dev/null
+++ b/tests/SerialChat/res/layout/serial_chat.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ >
+
+ <ScrollView android:id="@+id/scroll"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1"
+ >
+ <TextView android:id="@+id/log"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="25dp"
+ android:textSize="12sp"
+ android:textColor="#ffffffff"
+ />
+ </ScrollView>
+
+ <EditText android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:capitalize="sentences"
+ android:autoText="true"
+ android:singleLine="true"
+ />
+
+</LinearLayout>
+
+
diff --git a/tests/SerialChat/src/com/android/serialchat/SerialChat.java b/tests/SerialChat/src/com/android/serialchat/SerialChat.java
new file mode 100644
index 0000000..faec312
--- /dev/null
+++ b/tests/SerialChat/src/com/android/serialchat/SerialChat.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+package com.android.serialchat;
+
+import android.app.Activity;
+import android.content.Context;
+import android.hardware.SerialManager;
+import android.hardware.SerialPort;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.util.Log;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import java.nio.ByteBuffer;
+import java.io.IOException;
+
+public class SerialChat extends Activity implements Runnable, TextView.OnEditorActionListener {
+
+ private static final String TAG = "SerialChat";
+
+ private TextView mLog;
+ private EditText mEditText;
+ private ByteBuffer mInputBuffer;
+ private ByteBuffer mOutputBuffer;
+ private SerialManager mSerialManager;
+ private SerialPort mSerialPort;
+ private boolean mPermissionRequestPending;
+
+ private static final int MESSAGE_LOG = 1;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mSerialManager = (SerialManager)getSystemService(Context.SERIAL_SERVICE);
+ setContentView(R.layout.serial_chat);
+ mLog = (TextView)findViewById(R.id.log);
+ mEditText = (EditText)findViewById(R.id.message);
+ mEditText.setOnEditorActionListener(this);
+
+ if (false) {
+ mInputBuffer = ByteBuffer.allocateDirect(1024);
+ mOutputBuffer = ByteBuffer.allocateDirect(1024);
+ } else {
+ mInputBuffer = ByteBuffer.allocate(1024);
+ mOutputBuffer = ByteBuffer.allocate(1024);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ String[] ports = mSerialManager.getSerialPorts();
+ if (ports != null && ports.length > 0) {
+ try {
+ mSerialPort = mSerialManager.openSerialPort(ports[0], 115200);
+ if (mSerialPort != null) {
+ new Thread(this).start();
+ }
+ } catch (IOException e) {
+ }
+ }
+
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mSerialPort != null) {
+ try {
+ mSerialPort.close();
+ } catch (IOException e) {
+ }
+ mSerialPort = null;
+ }
+ super.onDestroy();
+ }
+
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (/* actionId == EditorInfo.IME_ACTION_DONE && */ mSerialPort != null) {
+ try {
+ String text = v.getText().toString();
+ Log.d(TAG, "write: " + text);
+ byte[] bytes = text.getBytes();
+ mOutputBuffer.clear();
+ mOutputBuffer.put(bytes);
+ mSerialPort.write(mOutputBuffer, bytes.length);
+ } catch (IOException e) {
+ Log.e(TAG, "write failed", e);
+ }
+ v.setText("");
+ return true;
+ }
+ Log.d(TAG, "onEditorAction " + actionId + " event: " + event);
+ return false;
+ }
+
+ public void run() {
+ Log.d(TAG, "run");
+ int ret = 0;
+ byte[] buffer = new byte[1024];
+ while (ret >= 0) {
+ try {
+ Log.d(TAG, "calling read");
+ mInputBuffer.clear();
+ ret = mSerialPort.read(mInputBuffer);
+ Log.d(TAG, "read returned " + ret);
+ mInputBuffer.get(buffer, 0, ret);
+ } catch (IOException e) {
+ Log.e(TAG, "read failed", e);
+ break;
+ }
+
+ if (ret > 0) {
+ Message m = Message.obtain(mHandler, MESSAGE_LOG);
+ String text = new String(buffer, 0, ret);
+ Log.d(TAG, "chat: " + text);
+ m.obj = text;
+ mHandler.sendMessage(m);
+ }
+ }
+ Log.d(TAG, "thread out");
+ }
+
+ Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_LOG:
+ mLog.setText(mLog.getText() + (String)msg.obj);
+ break;
+ }
+ }
+ };
+}
+
+
diff --git a/tools/aidl/AST.cpp b/tools/aidl/AST.cpp
index 752ef7c..bfa6765 100755
--- a/tools/aidl/AST.cpp
+++ b/tools/aidl/AST.cpp
@@ -111,6 +111,21 @@
fprintf(to, "%s", this->value.c_str());
}
+StringLiteralExpression::StringLiteralExpression(const string& v)
+ :value(v)
+{
+}
+
+StringLiteralExpression::~StringLiteralExpression()
+{
+}
+
+void
+StringLiteralExpression::Write(FILE* to)
+{
+ fprintf(to, "\"%s\"", this->value.c_str());
+}
+
Variable::Variable()
:type(NULL),
name(),
@@ -277,6 +292,17 @@
{
}
+MethodCall::MethodCall(const string& n, int argc = 0, ...)
+ :obj(NULL),
+ clazz(NULL),
+ name(n)
+{
+ va_list args;
+ va_start(args, argc);
+ init(argc, args);
+ va_end(args);
+}
+
MethodCall::MethodCall(Expression* o, const string& n)
:obj(o),
clazz(NULL),
@@ -367,11 +393,29 @@
{
}
+NewExpression::NewExpression(Type* t, int argc = 0, ...)
+ :type(t)
+{
+ va_list args;
+ va_start(args, argc);
+ init(argc, args);
+ va_end(args);
+}
+
NewExpression::~NewExpression()
{
}
void
+NewExpression::init(int n, va_list args)
+{
+ for (int i=0; i<n; i++) {
+ Expression* expression = (Expression*)va_arg(args, void*);
+ this->arguments.push_back(expression);
+ }
+}
+
+void
NewExpression::Write(FILE* to)
{
fprintf(to, "new %s(", this->type->InstantiableName().c_str());
@@ -636,6 +680,20 @@
fprintf(to, "}\n");
}
+Break::Break()
+{
+}
+
+Break::~Break()
+{
+}
+
+void
+Break::Write(FILE* to)
+{
+ fprintf(to, "break;\n");
+}
+
Method::Method()
:ClassElement(),
modifiers(0),
@@ -678,7 +736,7 @@
fprintf(to, "%s\n", this->comment.c_str());
}
- WriteModifiers(to, this->modifiers, SCOPE_MASK | STATIC | FINAL | OVERRIDE);
+ WriteModifiers(to, this->modifiers, SCOPE_MASK | STATIC | ABSTRACT | FINAL | OVERRIDE);
if (this->returnType != NULL) {
string dim;
diff --git a/tools/aidl/AST.h b/tools/aidl/AST.h
index 3156356..bb090e0 100755
--- a/tools/aidl/AST.h
+++ b/tools/aidl/AST.h
@@ -54,6 +54,16 @@
virtual void Write(FILE* to);
};
+// TODO: also escape the contents. not needed for now
+struct StringLiteralExpression : public Expression
+{
+ string value;
+
+ StringLiteralExpression(const string& value);
+ virtual ~StringLiteralExpression();
+ virtual void Write(FILE* to);
+};
+
struct Variable : public Expression
{
Type* type;
@@ -146,6 +156,7 @@
vector<string> exceptions;
MethodCall(const string& name);
+ MethodCall(const string& name, int argc, ...);
MethodCall(Expression* obj, const string& name);
MethodCall(Type* clazz, const string& name);
MethodCall(Expression* obj, const string& name, int argc, ...);
@@ -174,8 +185,12 @@
vector<Expression*> arguments;
NewExpression(Type* type);
+ NewExpression(Type* type, int argc, ...);
virtual ~NewExpression();
virtual void Write(FILE* to);
+
+private:
+ void init(int n, va_list args);
};
struct NewArrayExpression : public Expression
@@ -292,6 +307,13 @@
virtual void Write(FILE* to);
};
+struct Break : public Statement
+{
+ Break();
+ virtual ~Break();
+ virtual void Write(FILE* to);
+};
+
struct Method : public ClassElement
{
string comment;
diff --git a/tools/aidl/Android.mk b/tools/aidl/Android.mk
index 2ad0728..77d46ab 100644
--- a/tools/aidl/Android.mk
+++ b/tools/aidl/Android.mk
@@ -17,7 +17,9 @@
search_path.cpp \
AST.cpp \
Type.cpp \
- generate_java.cpp
+ generate_java.cpp \
+ generate_java_binder.cpp \
+ generate_java_rpc.cpp
LOCAL_CFLAGS := -g
LOCAL_MODULE := aidl
diff --git a/tools/aidl/Type.cpp b/tools/aidl/Type.cpp
index 6b69864..9a58af0 100755
--- a/tools/aidl/Type.cpp
+++ b/tools/aidl/Type.cpp
@@ -11,6 +11,7 @@
Type* FLOAT_TYPE;
Type* DOUBLE_TYPE;
Type* STRING_TYPE;
+Type* OBJECT_TYPE;
Type* CHAR_SEQUENCE_TYPE;
Type* TEXT_UTILS_TYPE;
Type* REMOTE_EXCEPTION_TYPE;
@@ -21,9 +22,17 @@
Type* BINDER_PROXY_TYPE;
Type* PARCEL_TYPE;
Type* PARCELABLE_INTERFACE_TYPE;
+Type* CONTEXT_TYPE;
Type* MAP_TYPE;
Type* LIST_TYPE;
Type* CLASSLOADER_TYPE;
+Type* RPC_SERVICE_BASE_TYPE;
+Type* RPC_DATA_TYPE;
+Type* RPC_BROKER_TYPE;
+Type* RPC_ENDPOINT_INFO_TYPE;
+Type* RPC_RESULT_HANDLER_TYPE;
+Type* RPC_ERROR_TYPE;
+Type* RPC_ERROR_LISTENER_TYPE;
Expression* NULL_VALUE;
Expression* THIS_VALUE;
@@ -34,38 +43,49 @@
void
register_base_types()
{
- VOID_TYPE = new BasicType("void", "XXX", "XXX", "XXX", "XXX", "XXX");
+ VOID_TYPE = new BasicType("void",
+ "XXX", "XXX", "XXX", "XXX", "XXX",
+ "XXX", "XXX", "XXX", "XXX", "XXX");
NAMES.Add(VOID_TYPE);
BOOLEAN_TYPE = new BooleanType();
NAMES.Add(BOOLEAN_TYPE);
- BYTE_TYPE = new BasicType("byte", "writeByte", "readByte",
- "writeByteArray", "createByteArray", "readByteArray");
+ BYTE_TYPE = new BasicType("byte",
+ "writeByte", "readByte", "writeByteArray", "createByteArray", "readByteArray",
+ "putByte", "getByte", "putByteArray", "createByteArray", "getByteArray");
NAMES.Add(BYTE_TYPE);
CHAR_TYPE = new CharType();
NAMES.Add(CHAR_TYPE);
- INT_TYPE = new BasicType("int", "writeInt", "readInt",
- "writeIntArray", "createIntArray", "readIntArray");
+ INT_TYPE = new BasicType("int",
+ "writeInt", "readInt", "writeIntArray", "createIntArray", "readIntArray",
+ "putInteger", "getInteger", "putIntegerArray", "createIntegerArray", "getIntegerArray");
NAMES.Add(INT_TYPE);
- LONG_TYPE = new BasicType("long", "writeLong", "readLong",
- "writeLongArray", "createLongArray", "readLongArray");
+ LONG_TYPE = new BasicType("long",
+ "writeLong", "readLong", "writeLongArray", "createLongArray", "readLongArray",
+ "putLong", "getLong", "putLongArray", "createLongArray", "getLongArray");
NAMES.Add(LONG_TYPE);
- FLOAT_TYPE = new BasicType("float", "writeFloat", "readFloat",
- "writeFloatArray", "createFloatArray", "readFloatArray");
+ FLOAT_TYPE = new BasicType("float",
+ "writeFloat", "readFloat", "writeFloatArray", "createFloatArray", "readFloatArray",
+ "putFloat", "getFloat", "putFloatArray", "createFloatArray", "getFloatArray");
NAMES.Add(FLOAT_TYPE);
- DOUBLE_TYPE = new BasicType("double", "writeDouble", "readDouble",
- "writeDoubleArray", "createDoubleArray", "readDoubleArray");
+ DOUBLE_TYPE = new BasicType("double",
+ "writeDouble", "readDouble", "writeDoubleArray", "createDoubleArray", "readDoubleArray",
+ "putDouble", "getDouble", "putDoubleArray", "createDoubleArray", "getDoubleArray");
NAMES.Add(DOUBLE_TYPE);
STRING_TYPE = new StringType();
NAMES.Add(STRING_TYPE);
+ OBJECT_TYPE = new Type("java.lang", "Object",
+ Type::BUILT_IN, false, false);
+ NAMES.Add(OBJECT_TYPE);
+
CHAR_SEQUENCE_TYPE = new CharSequenceType();
NAMES.Add(CHAR_SEQUENCE_TYPE);
@@ -75,8 +95,7 @@
LIST_TYPE = new ListType();
NAMES.Add(LIST_TYPE);
- TEXT_UTILS_TYPE = new Type("android.text", "TextUtils",
- Type::BUILT_IN, false, false);
+ TEXT_UTILS_TYPE = new Type("android.text", "TextUtils", Type::BUILT_IN, false, false);
NAMES.Add(TEXT_UTILS_TYPE);
REMOTE_EXCEPTION_TYPE = new RemoteExceptionType();
@@ -103,6 +122,37 @@
PARCELABLE_INTERFACE_TYPE = new ParcelableInterfaceType();
NAMES.Add(PARCELABLE_INTERFACE_TYPE);
+ CONTEXT_TYPE = new Type("android.content", "Context",
+ Type::BUILT_IN, false, false);
+ NAMES.Add(CONTEXT_TYPE);
+
+ RPC_SERVICE_BASE_TYPE = new Type("com.android.athome.service", "AndroidAtHomeService",
+ Type::BUILT_IN, false, false);
+ NAMES.Add(RPC_SERVICE_BASE_TYPE);
+
+ RPC_DATA_TYPE = new RpcDataType();
+ NAMES.Add(RPC_DATA_TYPE);
+
+ RPC_BROKER_TYPE = new Type("com.android.athome.utils", "AndroidAtHomeBroker",
+ Type::BUILT_IN, false, false);
+ NAMES.Add(RPC_BROKER_TYPE);
+
+ RPC_ENDPOINT_INFO_TYPE = new ParcelableType("com.android.athome.rpc", "EndpointInfo",
+ true, __FILE__, __LINE__);
+ NAMES.Add(RPC_ENDPOINT_INFO_TYPE);
+
+ RPC_RESULT_HANDLER_TYPE = new ParcelableType("com.android.athome.rpc", "RpcResultHandler",
+ true, __FILE__, __LINE__);
+ NAMES.Add(RPC_RESULT_HANDLER_TYPE);
+
+ RPC_ERROR_TYPE = new ParcelableType("com.android.athome.rpc", "RpcError",
+ true, __FILE__, __LINE__);
+ NAMES.Add(RPC_ERROR_TYPE);
+
+ RPC_ERROR_LISTENER_TYPE = new Type("com.android.athome.rpc", "RpcErrorHandler",
+ Type::BUILT_IN, false, false);
+ NAMES.Add(RPC_ERROR_LISTENER_TYPE);
+
CLASSLOADER_TYPE = new ClassLoaderType();
NAMES.Add(CLASSLOADER_TYPE);
@@ -244,6 +294,26 @@
}
void
+Type::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
+ __FILE__, __LINE__, m_qualifiedName.c_str());
+ addTo->Add(new LiteralExpression("/* WriteToRpcData error "
+ + m_qualifiedName + " */"));
+}
+
+void
+Type::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+ Variable** cl)
+{
+ fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
+ __FILE__, __LINE__, m_qualifiedName.c_str());
+ addTo->Add(new LiteralExpression("/* ReadFromRpcData error "
+ + m_qualifiedName + " */"));
+}
+
+void
Type::SetQualifiedName(const string& qualified)
{
m_qualifiedName = qualified;
@@ -264,29 +334,35 @@
// ================================================================
-BasicType::BasicType(const string& name, const string& marshallMethod,
- const string& unmarshallMethod,
- const string& writeArray, const string& createArray,
- const string& readArray)
+BasicType::BasicType(const string& name, const string& marshallParcel,
+ const string& unmarshallParcel, const string& writeArrayParcel,
+ const string& createArrayParcel, const string& readArrayParcel,
+ const string& marshallRpc, const string& unmarshallRpc,
+ const string& writeArrayRpc, const string& createArrayRpc, const string& readArrayRpc)
:Type(name, BUILT_IN, true, false),
- m_marshallMethod(marshallMethod),
- m_unmarshallMethod(unmarshallMethod),
- m_writeArrayMethod(writeArray),
- m_createArrayMethod(createArray),
- m_readArrayMethod(readArray)
+ m_marshallParcel(marshallParcel),
+ m_unmarshallParcel(unmarshallParcel),
+ m_writeArrayParcel(writeArrayParcel),
+ m_createArrayParcel(createArrayParcel),
+ m_readArrayParcel(readArrayParcel),
+ m_marshallRpc(marshallRpc),
+ m_unmarshallRpc(unmarshallRpc),
+ m_writeArrayRpc(writeArrayRpc),
+ m_createArrayRpc(createArrayRpc),
+ m_readArrayRpc(readArrayRpc)
{
}
void
BasicType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
{
- addTo->Add(new MethodCall(parcel, m_marshallMethod, 1, v));
+ addTo->Add(new MethodCall(parcel, m_marshallParcel, 1, v));
}
void
BasicType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
{
- addTo->Add(new Assignment(v, new MethodCall(parcel, m_unmarshallMethod)));
+ addTo->Add(new Assignment(v, new MethodCall(parcel, m_unmarshallParcel)));
}
bool
@@ -298,22 +374,35 @@
void
BasicType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
{
- addTo->Add(new MethodCall(parcel, m_writeArrayMethod, 1, v));
+ addTo->Add(new MethodCall(parcel, m_writeArrayParcel, 1, v));
}
void
BasicType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable**)
{
- addTo->Add(new Assignment(v, new MethodCall(parcel, m_createArrayMethod)));
+ addTo->Add(new Assignment(v, new MethodCall(parcel, m_createArrayParcel)));
}
void
BasicType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
{
- addTo->Add(new MethodCall(parcel, m_readArrayMethod, 1, v));
+ addTo->Add(new MethodCall(parcel, m_readArrayParcel, 1, v));
}
+void
+BasicType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, m_marshallRpc, 2, k, v));
+}
+
+void
+BasicType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+ Variable** cl)
+{
+ addTo->Add(new Assignment(v, new MethodCall(data, m_unmarshallRpc, 1, k)));
+}
// ================================================================
@@ -362,6 +451,19 @@
addTo->Add(new MethodCall(parcel, "readBooleanArray", 1, v));
}
+void
+BooleanType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, "putBoolean", 2, k, v));
+}
+
+void
+BooleanType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+ Variable** cl)
+{
+ addTo->Add(new Assignment(v, new MethodCall(data, "getBoolean", 1, k)));
+}
// ================================================================
@@ -408,6 +510,20 @@
addTo->Add(new MethodCall(parcel, "readCharArray", 1, v));
}
+void
+CharType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, "putChar", 2, k, v));
+}
+
+void
+CharType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+ Variable** cl)
+{
+ addTo->Add(new Assignment(v, new MethodCall(data, "getChar", 1, k)));
+}
+
// ================================================================
StringType::StringType()
@@ -458,6 +574,20 @@
addTo->Add(new MethodCall(parcel, "readStringArray", 1, v));
}
+void
+StringType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, "putString", 2, k, v));
+}
+
+void
+StringType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable**)
+{
+ addTo->Add(new Assignment(v, new MethodCall(data, "getString", 1, k)));
+}
+
// ================================================================
CharSequenceType::CharSequenceType()
@@ -729,8 +859,7 @@
}
void
-MapType::ReadFromParcel(StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl)
+MapType::ReadFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable** cl)
{
EnsureClassLoader(addTo, cl);
addTo->Add(new MethodCall(parcel, "readMap", 2, v, *cl));
@@ -771,6 +900,19 @@
addTo->Add(new MethodCall(parcel, "readList", 2, v, *cl));
}
+void
+ListType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, "putList", 2, k, v));
+}
+
+void
+ListType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+ Variable** cl)
+{
+ addTo->Add(new Assignment(v, new MethodCall(data, "getList", 1, k)));
+}
// ================================================================
@@ -942,6 +1084,12 @@
SetQualifiedName(m_importName + gen);
}
+const vector<Type*>&
+GenericType::GenericArgumentTypes() const
+{
+ return m_args;
+}
+
string
GenericType::GenericArguments() const
{
@@ -1041,6 +1189,46 @@
}
}
+void
+GenericListType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, "putList", 2, k, v));
+}
+
+void
+GenericListType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl)
+{
+ string classArg = GenericArgumentTypes()[0]->QualifiedName();
+ classArg += ".class";
+ addTo->Add(new Assignment(v, new MethodCall(data, "getList", 2, k,
+ new LiteralExpression(classArg))));
+}
+
+
+// ================================================================
+
+RpcDataType::RpcDataType()
+ :Type("com.android.athome.rpc", "RpcData", Type::BUILT_IN, false, false)
+{
+}
+
+void
+RpcDataType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags)
+{
+ addTo->Add(new MethodCall(data, "putRpcData", 2, k, v));
+}
+
+void
+RpcDataType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+ Variable** cl)
+{
+ addTo->Add(new Assignment(v, new MethodCall(data, "getRpcData", 1, k)));
+}
+
+
// ================================================================
ClassLoaderType::ClassLoaderType()
diff --git a/tools/aidl/Type.h b/tools/aidl/Type.h
index 662e3a2..97b3a12 100755
--- a/tools/aidl/Type.h
+++ b/tools/aidl/Type.h
@@ -59,6 +59,11 @@
virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable** cl);
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
+
protected:
void SetQualifiedName(const string& qualified);
Expression* BuildWriteToParcelFlags(int flags);
@@ -80,11 +85,17 @@
class BasicType : public Type
{
public:
- BasicType(const string& name, const string& marshallMethod,
- const string& unmarshallMethod,
- const string& writeArray,
- const string& createArray,
- const string& readArray);
+ BasicType(const string& name,
+ const string& marshallParcel,
+ const string& unmarshallParcel,
+ const string& writeArrayParcel,
+ const string& createArrayParcel,
+ const string& readArrayParcel,
+ const string& marshallRpc,
+ const string& unmarshallRpc,
+ const string& writeArrayRpc,
+ const string& createArrayRpc,
+ const string& readArrayRpc);
virtual void WriteToParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, int flags);
@@ -100,12 +111,22 @@
virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable** cl);
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
+
private:
- string m_marshallMethod;
- string m_unmarshallMethod;
- string m_writeArrayMethod;
- string m_createArrayMethod;
- string m_readArrayMethod;
+ string m_marshallParcel;
+ string m_unmarshallParcel;
+ string m_writeArrayParcel;
+ string m_createArrayParcel;
+ string m_readArrayParcel;
+ string m_marshallRpc;
+ string m_unmarshallRpc;
+ string m_writeArrayRpc;
+ string m_createArrayRpc;
+ string m_readArrayRpc;
};
class BooleanType : public Type
@@ -126,6 +147,11 @@
Variable* parcel, Variable** cl);
virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable** cl);
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
};
class CharType : public Type
@@ -146,6 +172,11 @@
Variable* parcel, Variable** cl);
virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable** cl);
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
};
@@ -169,6 +200,11 @@
Variable* parcel, Variable** cl);
virtual void ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable** cl);
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
};
class CharSequenceType : public Type
@@ -305,6 +341,11 @@
Variable* parcel, Variable** cl);
virtual void ReadFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable** cl);
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
};
class ParcelableType : public Type
@@ -357,6 +398,7 @@
GenericType(const string& package, const string& name,
const vector<Type*>& args);
+ const vector<Type*>& GenericArgumentTypes() const;
string GenericArguments() const;
virtual string ImportType() const;
@@ -391,10 +433,26 @@
virtual void ReadFromParcel(StatementBlock* addTo, Variable* v,
Variable* parcel, Variable** cl);
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
+
private:
string m_creator;
};
+class RpcDataType : public Type
+{
+public:
+ RpcDataType();
+
+ virtual void WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, int flags);
+ virtual void CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+ Variable* data, Variable** cl);
+};
+
class ClassLoaderType : public Type
{
public:
@@ -438,11 +496,13 @@
extern Type* VOID_TYPE;
extern Type* BOOLEAN_TYPE;
+extern Type* BYTE_TYPE;
extern Type* CHAR_TYPE;
extern Type* INT_TYPE;
extern Type* LONG_TYPE;
extern Type* FLOAT_TYPE;
extern Type* DOUBLE_TYPE;
+extern Type* OBJECT_TYPE;
extern Type* STRING_TYPE;
extern Type* CHAR_SEQUENCE_TYPE;
extern Type* TEXT_UTILS_TYPE;
@@ -455,6 +515,16 @@
extern Type* PARCEL_TYPE;
extern Type* PARCELABLE_INTERFACE_TYPE;
+extern Type* CONTEXT_TYPE;
+
+extern Type* RPC_SERVICE_BASE_TYPE;
+extern Type* RPC_DATA_TYPE;
+extern Type* RPC_BROKER_TYPE;
+extern Type* RPC_ENDPOINT_INFO_TYPE;
+extern Type* RPC_RESULT_HANDLER_TYPE;
+extern Type* RPC_ERROR_TYPE;
+extern Type* RPC_ERROR_LISTENER_TYPE;
+
extern Expression* NULL_VALUE;
extern Expression* THIS_VALUE;
extern Expression* SUPER_VALUE;
diff --git a/tools/aidl/aidl.cpp b/tools/aidl/aidl.cpp
index fb4067a..78404cb 100644
--- a/tools/aidl/aidl.cpp
+++ b/tools/aidl/aidl.cpp
@@ -29,7 +29,7 @@
test_document(document_item_type* d)
{
while (d) {
- if (d->item_type == INTERFACE_TYPE) {
+ if (d->item_type == INTERFACE_TYPE_BINDER) {
interface_type* c = (interface_type*)d;
printf("interface %s %s {\n", c->package, c->name.data);
interface_item_type *q = (interface_item_type*)c->interface_items;
@@ -242,7 +242,8 @@
parcelable_type* p = (parcelable_type*)items;
err |= check_filename(filename, p->package, &p->name);
}
- else if (items->item_type == INTERFACE_TYPE) {
+ else if (items->item_type == INTERFACE_TYPE_BINDER
+ || items->item_type == INTERFACE_TYPE_RPC) {
interface_type* c = (interface_type*)items;
err |= check_filename(filename, c->package, &c->name);
}
@@ -295,7 +296,8 @@
type = new ParcelableType(p->package ? p->package : "",
p->name.data, false, filename, p->name.lineno);
}
- else if (items->item_type == INTERFACE_TYPE) {
+ else if (items->item_type == INTERFACE_TYPE_BINDER
+ || items->item_type == INTERFACE_TYPE_RPC) {
interface_type* c = (interface_type*)items;
type = new InterfaceType(c->package ? c->package : "",
c->name.data, false, c->oneway,
@@ -310,7 +312,7 @@
if (old == NULL) {
NAMES.Add(type);
- if (items->item_type == INTERFACE_TYPE) {
+ if (items->item_type == INTERFACE_TYPE_BINDER) {
// for interfaces, also add the stub and proxy types, we don't
// bother checking these for duplicates, because the parser
// won't let us do it.
@@ -330,6 +332,19 @@
filename, c->name.lineno);
NAMES.Add(proxy);
}
+ else if (items->item_type == INTERFACE_TYPE_RPC) {
+ // for interfaces, also add the service base type, we don't
+ // bother checking these for duplicates, because the parser
+ // won't let us do it.
+ interface_type* c = (interface_type*)items;
+
+ string name = c->name.data;
+ name += ".ServiceBase";
+ Type* base = new Type(c->package ? c->package : "",
+ name, Type::GENERATED, false, false,
+ filename, c->name.lineno);
+ NAMES.Add(base);
+ }
} else {
if (old->Kind() == Type::BUILT_IN) {
fprintf(stderr, "%s:%d attempt to redefine built in class %s\n",
@@ -498,7 +513,7 @@
int err = 0;
while (items) {
// (nothing to check for PARCELABLE_TYPE)
- if (items->item_type == INTERFACE_TYPE) {
+ if (items->item_type == INTERFACE_TYPE_BINDER) {
map<string,method_type*> methodNames;
interface_type* c = (interface_type*)items;
@@ -544,7 +559,10 @@
const document_item_type* next = items->next;
if (items->next != NULL) {
int lineno = -1;
- if (next->item_type == INTERFACE_TYPE) {
+ if (next->item_type == INTERFACE_TYPE_BINDER) {
+ lineno = ((interface_type*)next)->interface_token.lineno;
+ }
+ else if (next->item_type == INTERFACE_TYPE_RPC) {
lineno = ((interface_type*)next)->interface_token.lineno;
}
else if (next->item_type == PARCELABLE_TYPE) {
@@ -598,7 +616,7 @@
slash = "";
}
- if (items->item_type == INTERFACE_TYPE) {
+ if (items->item_type == INTERFACE_TYPE_BINDER || items->item_type == INTERFACE_TYPE_RPC) {
fprintf(to, "%s: \\\n", options.outputFileName.c_str());
} else {
// parcelable: there's no output file.
@@ -658,7 +676,7 @@
generate_outputFileName(const Options& options, const document_item_type* items)
{
// items has already been checked to have only one interface.
- if (items->item_type == INTERFACE_TYPE) {
+ if (items->item_type == INTERFACE_TYPE_BINDER || items->item_type == INTERFACE_TYPE_RPC) {
interface_type* type = (interface_type*)items;
return generate_outputFileName2(options, type->name, type->package);
@@ -751,7 +769,7 @@
interface_type* iface = (interface_type*)malloc(
sizeof(interface_type));
memset(iface, 0, sizeof(interface_type));
- iface->document_item.item_type = INTERFACE_TYPE;
+ iface->document_item.item_type = INTERFACE_TYPE_BINDER;
iface->interface_token.lineno = lineno;
iface->interface_token.data = strdup(type);
iface->package = packagename ? strdup(packagename) : NULL;
@@ -995,5 +1013,3 @@
fprintf(stderr, "aidl: internal error\n");
return 1;
}
-
-
diff --git a/tools/aidl/aidl_language.h b/tools/aidl/aidl_language.h
index 9ca5deb..985f646 100644
--- a/tools/aidl/aidl_language.h
+++ b/tools/aidl/aidl_language.h
@@ -64,7 +64,8 @@
enum {
PARCELABLE_TYPE = 12,
- INTERFACE_TYPE
+ INTERFACE_TYPE_BINDER,
+ INTERFACE_TYPE_RPC
};
typedef struct document_item_type {
diff --git a/tools/aidl/aidl_language_l.l b/tools/aidl/aidl_language_l.l
index 567b1cf..8092366 100644
--- a/tools/aidl/aidl_language_l.l
+++ b/tools/aidl/aidl_language_l.l
@@ -81,6 +81,7 @@
/* keywords */
parcelable { SET_BUFFER(PARCELABLE); return PARCELABLE; }
interface { SET_BUFFER(INTERFACE); return INTERFACE; }
+rpc { SET_BUFFER(INTERFACE); return RPC; }
in { SET_BUFFER(IN); return IN; }
out { SET_BUFFER(OUT); return OUT; }
inout { SET_BUFFER(INOUT); return INOUT; }
diff --git a/tools/aidl/aidl_language_y.y b/tools/aidl/aidl_language_y.y
index 3d65f17..965d936 100644
--- a/tools/aidl/aidl_language_y.y
+++ b/tools/aidl/aidl_language_y.y
@@ -19,6 +19,7 @@
%token ARRAY
%token PARCELABLE
%token INTERFACE
+%token RPC
%token IN
%token OUT
%token INOUT
@@ -102,6 +103,8 @@
interface_header:
INTERFACE {
interface_type* c = (interface_type*)malloc(sizeof(interface_type));
+ c->document_item.item_type = INTERFACE_TYPE_BINDER;
+ c->document_item.next = NULL;
c->interface_token = $1.buffer;
c->oneway = false;
memset(&c->oneway_token, 0, sizeof(buffer_type));
@@ -110,19 +113,34 @@
}
| ONEWAY INTERFACE {
interface_type* c = (interface_type*)malloc(sizeof(interface_type));
+ c->document_item.item_type = INTERFACE_TYPE_BINDER;
+ c->document_item.next = NULL;
c->interface_token = $2.buffer;
c->oneway = true;
c->oneway_token = $1.buffer;
c->comments_token = &c->oneway_token;
$$.interface_obj = c;
}
+ | RPC {
+ interface_type* c = (interface_type*)malloc(sizeof(interface_type));
+ c->document_item.item_type = INTERFACE_TYPE_RPC;
+ c->document_item.next = NULL;
+ c->interface_token = $1.buffer;
+ c->oneway = false;
+ memset(&c->oneway_token, 0, sizeof(buffer_type));
+ c->comments_token = &c->interface_token;
+ $$.interface_obj = c;
+ }
+ ;
+
+interface_keywords:
+ INTERFACE
+ | RPC
;
interface_decl:
interface_header IDENTIFIER '{' interface_items '}' {
interface_type* c = $1.interface_obj;
- c->document_item.item_type = INTERFACE_TYPE;
- c->document_item.next = NULL;
c->name = $2.buffer;
c->package = g_currentPackage ? strdup(g_currentPackage) : NULL;
c->open_brace_token = $3.buffer;
@@ -130,12 +148,12 @@
c->close_brace_token = $5.buffer;
$$.interface_obj = c;
}
- | INTERFACE error '{' interface_items '}' {
+ | interface_keywords error '{' interface_items '}' {
fprintf(stderr, "%s:%d: syntax error in interface declaration. Expected type name, saw \"%s\"\n",
g_currentFilename, $2.buffer.lineno, $2.buffer.data);
$$.document_item = NULL;
}
- | INTERFACE error '}' {
+ | interface_keywords error '}' {
fprintf(stderr, "%s:%d: syntax error in interface declaration. Expected type name, saw \"%s\"\n",
g_currentFilename, $2.buffer.lineno, $2.buffer.data);
$$.document_item = NULL;
diff --git a/tools/aidl/generate_java.cpp b/tools/aidl/generate_java.cpp
index 83e3bbc..9e57407 100644
--- a/tools/aidl/generate_java.cpp
+++ b/tools/aidl/generate_java.cpp
@@ -1,5 +1,4 @@
#include "generate_java.h"
-#include "AST.h"
#include "Type.h"
#include <string.h>
#include <stdio.h>
@@ -7,18 +6,6 @@
#include <string.h>
// =================================================
-class VariableFactory
-{
-public:
- VariableFactory(const string& base); // base must be short
- Variable* Get(Type* type);
- Variable* Get(int index);
-private:
- vector<Variable*> m_vars;
- string m_base;
- int m_index;
-};
-
VariableFactory::VariableFactory(const string& base)
:m_base(base),
m_index(0)
@@ -43,195 +30,7 @@
}
// =================================================
-class StubClass : public Class
-{
-public:
- StubClass(Type* type, Type* interfaceType);
- virtual ~StubClass();
-
- Variable* transact_code;
- Variable* transact_data;
- Variable* transact_reply;
- Variable* transact_flags;
- SwitchStatement* transact_switch;
-private:
- void make_as_interface(Type* interfaceType);
-};
-
-StubClass::StubClass(Type* type, Type* interfaceType)
- :Class()
-{
- this->comment = "/** Local-side IPC implementation stub class. */";
- this->modifiers = PUBLIC | ABSTRACT | STATIC;
- this->what = Class::CLASS;
- this->type = type;
- this->extends = BINDER_NATIVE_TYPE;
- this->interfaces.push_back(interfaceType);
-
- // descriptor
- Field* descriptor = new Field(STATIC | FINAL | PRIVATE,
- new Variable(STRING_TYPE, "DESCRIPTOR"));
- descriptor->value = "\"" + interfaceType->QualifiedName() + "\"";
- this->elements.push_back(descriptor);
-
- // ctor
- Method* ctor = new Method;
- ctor->modifiers = PUBLIC;
- ctor->comment = "/** Construct the stub at attach it to the "
- "interface. */";
- ctor->name = "Stub";
- ctor->statements = new StatementBlock;
- MethodCall* attach = new MethodCall(THIS_VALUE, "attachInterface",
- 2, THIS_VALUE, new LiteralExpression("DESCRIPTOR"));
- ctor->statements->Add(attach);
- this->elements.push_back(ctor);
-
- // asInterface
- make_as_interface(interfaceType);
-
- // asBinder
- Method* asBinder = new Method;
- asBinder->modifiers = PUBLIC;
- asBinder->returnType = IBINDER_TYPE;
- asBinder->name = "asBinder";
- asBinder->statements = new StatementBlock;
- asBinder->statements->Add(new ReturnStatement(THIS_VALUE));
- this->elements.push_back(asBinder);
-
- // onTransact
- this->transact_code = new Variable(INT_TYPE, "code");
- this->transact_data = new Variable(PARCEL_TYPE, "data");
- this->transact_reply = new Variable(PARCEL_TYPE, "reply");
- this->transact_flags = new Variable(INT_TYPE, "flags");
- Method* onTransact = new Method;
- onTransact->modifiers = PUBLIC | OVERRIDE;
- onTransact->returnType = BOOLEAN_TYPE;
- onTransact->name = "onTransact";
- onTransact->parameters.push_back(this->transact_code);
- onTransact->parameters.push_back(this->transact_data);
- onTransact->parameters.push_back(this->transact_reply);
- onTransact->parameters.push_back(this->transact_flags);
- onTransact->statements = new StatementBlock;
- onTransact->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
- this->elements.push_back(onTransact);
- this->transact_switch = new SwitchStatement(this->transact_code);
-
- onTransact->statements->Add(this->transact_switch);
- MethodCall* superCall = new MethodCall(SUPER_VALUE, "onTransact", 4,
- this->transact_code, this->transact_data,
- this->transact_reply, this->transact_flags);
- onTransact->statements->Add(new ReturnStatement(superCall));
-}
-
-StubClass::~StubClass()
-{
-}
-
-void
-StubClass::make_as_interface(Type *interfaceType)
-{
- Variable* obj = new Variable(IBINDER_TYPE, "obj");
-
- Method* m = new Method;
- m->comment = "/**\n * Cast an IBinder object into an ";
- m->comment += interfaceType->QualifiedName();
- m->comment += " interface,\n";
- m->comment += " * generating a proxy if needed.\n */";
- m->modifiers = PUBLIC | STATIC;
- m->returnType = interfaceType;
- m->name = "asInterface";
- m->parameters.push_back(obj);
- m->statements = new StatementBlock;
-
- IfStatement* ifstatement = new IfStatement();
- ifstatement->expression = new Comparison(obj, "==", NULL_VALUE);
- ifstatement->statements = new StatementBlock;
- ifstatement->statements->Add(new ReturnStatement(NULL_VALUE));
- m->statements->Add(ifstatement);
-
- // IInterface iin = obj.queryLocalInterface(DESCRIPTOR)
- MethodCall* queryLocalInterface = new MethodCall(obj, "queryLocalInterface");
- queryLocalInterface->arguments.push_back(new LiteralExpression("DESCRIPTOR"));
- IInterfaceType* iinType = new IInterfaceType();
- Variable *iin = new Variable(iinType, "iin");
- VariableDeclaration* iinVd = new VariableDeclaration(iin, queryLocalInterface, iinType);
- m->statements->Add(iinVd);
-
- // Ensure the instance type of the local object is as expected.
- // One scenario where this is needed is if another package (with a
- // different class loader) runs in the same process as the service.
-
- // if (iin != null && iin instanceof <interfaceType>) return (<interfaceType>) iin;
- Comparison* iinNotNull = new Comparison(iin, "!=", NULL_VALUE);
- Comparison* instOfCheck = new Comparison(iin, " instanceof ",
- new LiteralExpression(interfaceType->QualifiedName()));
- IfStatement* instOfStatement = new IfStatement();
- instOfStatement->expression = new Comparison(iinNotNull, "&&", instOfCheck);
- instOfStatement->statements = new StatementBlock;
- instOfStatement->statements->Add(new ReturnStatement(new Cast(interfaceType, iin)));
- m->statements->Add(instOfStatement);
-
- string proxyType = interfaceType->QualifiedName();
- proxyType += ".Stub.Proxy";
- NewExpression* ne = new NewExpression(NAMES.Find(proxyType));
- ne->arguments.push_back(obj);
- m->statements->Add(new ReturnStatement(ne));
-
- this->elements.push_back(m);
-}
-
-
-
-// =================================================
-class ProxyClass : public Class
-{
-public:
- ProxyClass(Type* type, InterfaceType* interfaceType);
- virtual ~ProxyClass();
-
- Variable* mRemote;
- bool mOneWay;
-};
-
-ProxyClass::ProxyClass(Type* type, InterfaceType* interfaceType)
- :Class()
-{
- this->modifiers = PRIVATE | STATIC;
- this->what = Class::CLASS;
- this->type = type;
- this->interfaces.push_back(interfaceType);
-
- mOneWay = interfaceType->OneWay();
-
- // IBinder mRemote
- mRemote = new Variable(IBINDER_TYPE, "mRemote");
- this->elements.push_back(new Field(PRIVATE, mRemote));
-
- // Proxy()
- Variable* remote = new Variable(IBINDER_TYPE, "remote");
- Method* ctor = new Method;
- ctor->name = "Proxy";
- ctor->statements = new StatementBlock;
- ctor->parameters.push_back(remote);
- ctor->statements->Add(new Assignment(mRemote, remote));
- this->elements.push_back(ctor);
-
- // IBinder asBinder()
- Method* asBinder = new Method;
- asBinder->modifiers = PUBLIC;
- asBinder->returnType = IBINDER_TYPE;
- asBinder->name = "asBinder";
- asBinder->statements = new StatementBlock;
- asBinder->statements->Add(new ReturnStatement(mRemote));
- this->elements.push_back(asBinder);
-}
-
-ProxyClass::~ProxyClass()
-{
-}
-
-// =================================================
-static string
+string
gather_comments(extra_text_type* extra)
{
string s;
@@ -249,7 +48,7 @@
return s;
}
-static string
+string
append(const char* a, const char* b)
{
string s = a;
@@ -257,379 +56,25 @@
return s;
}
-static void
-generate_new_array(Type* t, StatementBlock* addTo, Variable* v,
- Variable* parcel)
-{
- Variable* len = new Variable(INT_TYPE, v->name + "_length");
- addTo->Add(new VariableDeclaration(len, new MethodCall(parcel, "readInt")));
- IfStatement* lencheck = new IfStatement();
- lencheck->expression = new Comparison(len, "<", new LiteralExpression("0"));
- lencheck->statements->Add(new Assignment(v, NULL_VALUE));
- lencheck->elseif = new IfStatement();
- lencheck->elseif->statements->Add(new Assignment(v,
- new NewArrayExpression(t, len)));
- addTo->Add(lencheck);
-}
-
-static void
-generate_write_to_parcel(Type* t, StatementBlock* addTo, Variable* v,
- Variable* parcel, int flags)
-{
- if (v->dimension == 0) {
- t->WriteToParcel(addTo, v, parcel, flags);
- }
- if (v->dimension == 1) {
- t->WriteArrayToParcel(addTo, v, parcel, flags);
- }
-}
-
-static void
-generate_create_from_parcel(Type* t, StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl)
-{
- if (v->dimension == 0) {
- t->CreateFromParcel(addTo, v, parcel, cl);
- }
- if (v->dimension == 1) {
- t->CreateArrayFromParcel(addTo, v, parcel, cl);
- }
-}
-
-static void
-generate_read_from_parcel(Type* t, StatementBlock* addTo, Variable* v,
- Variable* parcel, Variable** cl)
-{
- if (v->dimension == 0) {
- t->ReadFromParcel(addTo, v, parcel, cl);
- }
- if (v->dimension == 1) {
- t->ReadArrayFromParcel(addTo, v, parcel, cl);
- }
-}
-
-
-static void
-generate_method(const method_type* method, Class* interface,
- StubClass* stubClass, ProxyClass* proxyClass, int index)
-{
- arg_type* arg;
- int i;
- bool hasOutParams = false;
-
- const bool oneway = proxyClass->mOneWay || method->oneway;
-
- // == the TRANSACT_ constant =============================================
- string transactCodeName = "TRANSACTION_";
- transactCodeName += method->name.data;
-
- char transactCodeValue[50];
- sprintf(transactCodeValue, "(android.os.IBinder.FIRST_CALL_TRANSACTION + %d)", index);
-
- Field* transactCode = new Field(STATIC | FINAL,
- new Variable(INT_TYPE, transactCodeName));
- transactCode->value = transactCodeValue;
- stubClass->elements.push_back(transactCode);
-
- // == the declaration in the interface ===================================
- Method* decl = new Method;
- decl->comment = gather_comments(method->comments_token->extra);
- decl->modifiers = PUBLIC;
- decl->returnType = NAMES.Search(method->type.type.data);
- decl->returnTypeDimension = method->type.dimension;
- decl->name = method->name.data;
-
- arg = method->args;
- while (arg != NULL) {
- decl->parameters.push_back(new Variable(
- NAMES.Search(arg->type.type.data), arg->name.data,
- arg->type.dimension));
- arg = arg->next;
- }
-
- decl->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
-
- interface->elements.push_back(decl);
-
- // == the stub method ====================================================
-
- Case* c = new Case(transactCodeName);
-
- MethodCall* realCall = new MethodCall(THIS_VALUE, method->name.data);
-
- // interface token validation is the very first thing we do
- c->statements->Add(new MethodCall(stubClass->transact_data,
- "enforceInterface", 1, new LiteralExpression("DESCRIPTOR")));
-
- // args
- Variable* cl = NULL;
- VariableFactory stubArgs("_arg");
- arg = method->args;
- while (arg != NULL) {
- Type* t = NAMES.Search(arg->type.type.data);
- Variable* v = stubArgs.Get(t);
- v->dimension = arg->type.dimension;
-
- c->statements->Add(new VariableDeclaration(v));
-
- if (convert_direction(arg->direction.data) & IN_PARAMETER) {
- generate_create_from_parcel(t, c->statements, v,
- stubClass->transact_data, &cl);
- } else {
- if (arg->type.dimension == 0) {
- c->statements->Add(new Assignment(
- v, new NewExpression(v->type)));
- }
- else if (arg->type.dimension == 1) {
- generate_new_array(v->type, c->statements, v,
- stubClass->transact_data);
- }
- else {
- fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__,
- __LINE__);
- }
- }
-
- realCall->arguments.push_back(v);
-
- arg = arg->next;
- }
-
- // the real call
- Variable* _result = NULL;
- if (0 == strcmp(method->type.type.data, "void")) {
- c->statements->Add(realCall);
-
- if (!oneway) {
- // report that there were no exceptions
- MethodCall* ex = new MethodCall(stubClass->transact_reply,
- "writeNoException", 0);
- c->statements->Add(ex);
- }
- } else {
- _result = new Variable(decl->returnType, "_result",
- decl->returnTypeDimension);
- c->statements->Add(new VariableDeclaration(_result, realCall));
-
- if (!oneway) {
- // report that there were no exceptions
- MethodCall* ex = new MethodCall(stubClass->transact_reply,
- "writeNoException", 0);
- c->statements->Add(ex);
- }
-
- // marshall the return value
- generate_write_to_parcel(decl->returnType, c->statements, _result,
- stubClass->transact_reply,
- Type::PARCELABLE_WRITE_RETURN_VALUE);
- }
-
- // out parameters
- i = 0;
- arg = method->args;
- while (arg != NULL) {
- Type* t = NAMES.Search(arg->type.type.data);
- Variable* v = stubArgs.Get(i++);
-
- if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
- generate_write_to_parcel(t, c->statements, v,
- stubClass->transact_reply,
- Type::PARCELABLE_WRITE_RETURN_VALUE);
- hasOutParams = true;
- }
-
- arg = arg->next;
- }
-
- // return true
- c->statements->Add(new ReturnStatement(TRUE_VALUE));
- stubClass->transact_switch->cases.push_back(c);
-
- // == the proxy method ===================================================
- Method* proxy = new Method;
- proxy->comment = gather_comments(method->comments_token->extra);
- proxy->modifiers = PUBLIC;
- proxy->returnType = NAMES.Search(method->type.type.data);
- proxy->returnTypeDimension = method->type.dimension;
- proxy->name = method->name.data;
- proxy->statements = new StatementBlock;
- arg = method->args;
- while (arg != NULL) {
- proxy->parameters.push_back(new Variable(
- NAMES.Search(arg->type.type.data), arg->name.data,
- arg->type.dimension));
- arg = arg->next;
- }
- proxy->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
- proxyClass->elements.push_back(proxy);
-
- // the parcels
- Variable* _data = new Variable(PARCEL_TYPE, "_data");
- proxy->statements->Add(new VariableDeclaration(_data,
- new MethodCall(PARCEL_TYPE, "obtain")));
- Variable* _reply = NULL;
- if (!oneway) {
- _reply = new Variable(PARCEL_TYPE, "_reply");
- proxy->statements->Add(new VariableDeclaration(_reply,
- new MethodCall(PARCEL_TYPE, "obtain")));
- }
-
- // the return value
- _result = NULL;
- if (0 != strcmp(method->type.type.data, "void")) {
- _result = new Variable(proxy->returnType, "_result",
- method->type.dimension);
- proxy->statements->Add(new VariableDeclaration(_result));
- }
-
- // try and finally
- TryStatement* tryStatement = new TryStatement();
- proxy->statements->Add(tryStatement);
- FinallyStatement* finallyStatement = new FinallyStatement();
- proxy->statements->Add(finallyStatement);
-
- // the interface identifier token: the DESCRIPTOR constant, marshalled as a string
- tryStatement->statements->Add(new MethodCall(_data, "writeInterfaceToken",
- 1, new LiteralExpression("DESCRIPTOR")));
-
- // the parameters
- arg = method->args;
- while (arg != NULL) {
- Type* t = NAMES.Search(arg->type.type.data);
- Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
- int dir = convert_direction(arg->direction.data);
- if (dir == OUT_PARAMETER && arg->type.dimension != 0) {
- IfStatement* checklen = new IfStatement();
- checklen->expression = new Comparison(v, "==", NULL_VALUE);
- checklen->statements->Add(new MethodCall(_data, "writeInt", 1,
- new LiteralExpression("-1")));
- checklen->elseif = new IfStatement();
- checklen->elseif->statements->Add(new MethodCall(_data, "writeInt",
- 1, new FieldVariable(v, "length")));
- tryStatement->statements->Add(checklen);
- }
- else if (dir & IN_PARAMETER) {
- generate_write_to_parcel(t, tryStatement->statements, v, _data, 0);
- }
- arg = arg->next;
- }
-
- // the transact call
- MethodCall* call = new MethodCall(proxyClass->mRemote, "transact", 4,
- new LiteralExpression("Stub." + transactCodeName),
- _data, _reply ? _reply : NULL_VALUE,
- new LiteralExpression(
- oneway ? "android.os.IBinder.FLAG_ONEWAY" : "0"));
- tryStatement->statements->Add(call);
-
- // throw back exceptions.
- if (_reply) {
- MethodCall* ex = new MethodCall(_reply, "readException", 0);
- tryStatement->statements->Add(ex);
- }
-
- // returning and cleanup
- if (_reply != NULL) {
- if (_result != NULL) {
- generate_create_from_parcel(proxy->returnType,
- tryStatement->statements, _result, _reply, &cl);
- }
-
- // the out/inout parameters
- arg = method->args;
- while (arg != NULL) {
- Type* t = NAMES.Search(arg->type.type.data);
- Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
- if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
- generate_read_from_parcel(t, tryStatement->statements,
- v, _reply, &cl);
- }
- arg = arg->next;
- }
-
- finallyStatement->statements->Add(new MethodCall(_reply, "recycle"));
- }
- finallyStatement->statements->Add(new MethodCall(_data, "recycle"));
-
- if (_result != NULL) {
- proxy->statements->Add(new ReturnStatement(_result));
- }
-}
-
-static void
-generate_interface_descriptors(StubClass* stub, ProxyClass* proxy)
-{
- // the interface descriptor transaction handler
- Case* c = new Case("INTERFACE_TRANSACTION");
- c->statements->Add(new MethodCall(stub->transact_reply, "writeString",
- 1, new LiteralExpression("DESCRIPTOR")));
- c->statements->Add(new ReturnStatement(TRUE_VALUE));
- stub->transact_switch->cases.push_back(c);
-
- // and the proxy-side method returning the descriptor directly
- Method* getDesc = new Method;
- getDesc->modifiers = PUBLIC;
- getDesc->returnType = STRING_TYPE;
- getDesc->returnTypeDimension = 0;
- getDesc->name = "getInterfaceDescriptor";
- getDesc->statements = new StatementBlock;
- getDesc->statements->Add(new ReturnStatement(new LiteralExpression("DESCRIPTOR")));
- proxy->elements.push_back(getDesc);
-}
-
-static Class*
-generate_interface_class(const interface_type* iface)
-{
- InterfaceType* interfaceType = static_cast<InterfaceType*>(
- NAMES.Find(iface->package, iface->name.data));
-
- // the interface class
- Class* interface = new Class;
- interface->comment = gather_comments(iface->comments_token->extra);
- interface->modifiers = PUBLIC;
- interface->what = Class::INTERFACE;
- interface->type = interfaceType;
- interface->interfaces.push_back(IINTERFACE_TYPE);
-
- // the stub inner class
- StubClass* stub = new StubClass(
- NAMES.Find(iface->package, append(iface->name.data, ".Stub").c_str()),
- interfaceType);
- interface->elements.push_back(stub);
-
- // the proxy inner class
- ProxyClass* proxy = new ProxyClass(
- NAMES.Find(iface->package,
- append(iface->name.data, ".Stub.Proxy").c_str()),
- interfaceType);
- stub->elements.push_back(proxy);
-
- // stub and proxy support for getInterfaceDescriptor()
- generate_interface_descriptors(stub, proxy);
-
- // all the declared methods of the interface
- int index = 0;
- interface_item_type* item = iface->interface_items;
- while (item != NULL) {
- if (item->item_type == METHOD_TYPE) {
- generate_method((method_type*)item, interface, stub, proxy, index);
- }
- item = item->next;
- index++;
- }
-
- return interface;
-}
-
+// =================================================
int
generate_java(const string& filename, const string& originalSrc,
interface_type* iface)
{
+ Class* cl;
+
+ if (iface->document_item.item_type == INTERFACE_TYPE_BINDER) {
+ cl = generate_binder_interface_class(iface);
+ }
+ else if (iface->document_item.item_type == INTERFACE_TYPE_RPC) {
+ cl = generate_rpc_interface_class(iface);
+ }
+
Document* document = new Document;
document->comment = "";
if (iface->package) document->package = iface->package;
document->originalSrc = originalSrc;
- document->classes.push_back(generate_interface_class(iface));
+ document->classes.push_back(cl);
// printf("outputting... filename=%s\n", filename.c_str());
FILE* to;
diff --git a/tools/aidl/generate_java.h b/tools/aidl/generate_java.h
index 203fe23..4bfcfeb 100644
--- a/tools/aidl/generate_java.h
+++ b/tools/aidl/generate_java.h
@@ -2,6 +2,7 @@
#define GENERATE_JAVA_H
#include "aidl_language.h"
+#include "AST.h"
#include <string>
@@ -10,5 +11,23 @@
int generate_java(const string& filename, const string& originalSrc,
interface_type* iface);
+Class* generate_binder_interface_class(const interface_type* iface);
+Class* generate_rpc_interface_class(const interface_type* iface);
+
+string gather_comments(extra_text_type* extra);
+string append(const char* a, const char* b);
+
+class VariableFactory
+{
+public:
+ VariableFactory(const string& base); // base must be short
+ Variable* Get(Type* type);
+ Variable* Get(int index);
+private:
+ vector<Variable*> m_vars;
+ string m_base;
+ int m_index;
+};
+
#endif // GENERATE_JAVA_H
diff --git a/tools/aidl/generate_java_binder.cpp b/tools/aidl/generate_java_binder.cpp
new file mode 100644
index 0000000..2e459a8
--- /dev/null
+++ b/tools/aidl/generate_java_binder.cpp
@@ -0,0 +1,559 @@
+#include "generate_java.h"
+#include "Type.h"
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// =================================================
+class StubClass : public Class
+{
+public:
+ StubClass(Type* type, Type* interfaceType);
+ virtual ~StubClass();
+
+ Variable* transact_code;
+ Variable* transact_data;
+ Variable* transact_reply;
+ Variable* transact_flags;
+ SwitchStatement* transact_switch;
+private:
+ void make_as_interface(Type* interfaceType);
+};
+
+StubClass::StubClass(Type* type, Type* interfaceType)
+ :Class()
+{
+ this->comment = "/** Local-side IPC implementation stub class. */";
+ this->modifiers = PUBLIC | ABSTRACT | STATIC;
+ this->what = Class::CLASS;
+ this->type = type;
+ this->extends = BINDER_NATIVE_TYPE;
+ this->interfaces.push_back(interfaceType);
+
+ // descriptor
+ Field* descriptor = new Field(STATIC | FINAL | PRIVATE,
+ new Variable(STRING_TYPE, "DESCRIPTOR"));
+ descriptor->value = "\"" + interfaceType->QualifiedName() + "\"";
+ this->elements.push_back(descriptor);
+
+ // ctor
+ Method* ctor = new Method;
+ ctor->modifiers = PUBLIC;
+ ctor->comment = "/** Construct the stub at attach it to the "
+ "interface. */";
+ ctor->name = "Stub";
+ ctor->statements = new StatementBlock;
+ MethodCall* attach = new MethodCall(THIS_VALUE, "attachInterface",
+ 2, THIS_VALUE, new LiteralExpression("DESCRIPTOR"));
+ ctor->statements->Add(attach);
+ this->elements.push_back(ctor);
+
+ // asInterface
+ make_as_interface(interfaceType);
+
+ // asBinder
+ Method* asBinder = new Method;
+ asBinder->modifiers = PUBLIC;
+ asBinder->returnType = IBINDER_TYPE;
+ asBinder->name = "asBinder";
+ asBinder->statements = new StatementBlock;
+ asBinder->statements->Add(new ReturnStatement(THIS_VALUE));
+ this->elements.push_back(asBinder);
+
+ // onTransact
+ this->transact_code = new Variable(INT_TYPE, "code");
+ this->transact_data = new Variable(PARCEL_TYPE, "data");
+ this->transact_reply = new Variable(PARCEL_TYPE, "reply");
+ this->transact_flags = new Variable(INT_TYPE, "flags");
+ Method* onTransact = new Method;
+ onTransact->modifiers = PUBLIC | OVERRIDE;
+ onTransact->returnType = BOOLEAN_TYPE;
+ onTransact->name = "onTransact";
+ onTransact->parameters.push_back(this->transact_code);
+ onTransact->parameters.push_back(this->transact_data);
+ onTransact->parameters.push_back(this->transact_reply);
+ onTransact->parameters.push_back(this->transact_flags);
+ onTransact->statements = new StatementBlock;
+ onTransact->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
+ this->elements.push_back(onTransact);
+ this->transact_switch = new SwitchStatement(this->transact_code);
+
+ onTransact->statements->Add(this->transact_switch);
+ MethodCall* superCall = new MethodCall(SUPER_VALUE, "onTransact", 4,
+ this->transact_code, this->transact_data,
+ this->transact_reply, this->transact_flags);
+ onTransact->statements->Add(new ReturnStatement(superCall));
+}
+
+StubClass::~StubClass()
+{
+}
+
+void
+StubClass::make_as_interface(Type *interfaceType)
+{
+ Variable* obj = new Variable(IBINDER_TYPE, "obj");
+
+ Method* m = new Method;
+ m->comment = "/**\n * Cast an IBinder object into an ";
+ m->comment += interfaceType->QualifiedName();
+ m->comment += " interface,\n";
+ m->comment += " * generating a proxy if needed.\n */";
+ m->modifiers = PUBLIC | STATIC;
+ m->returnType = interfaceType;
+ m->name = "asInterface";
+ m->parameters.push_back(obj);
+ m->statements = new StatementBlock;
+
+ IfStatement* ifstatement = new IfStatement();
+ ifstatement->expression = new Comparison(obj, "==", NULL_VALUE);
+ ifstatement->statements = new StatementBlock;
+ ifstatement->statements->Add(new ReturnStatement(NULL_VALUE));
+ m->statements->Add(ifstatement);
+
+ // IInterface iin = obj.queryLocalInterface(DESCRIPTOR)
+ MethodCall* queryLocalInterface = new MethodCall(obj, "queryLocalInterface");
+ queryLocalInterface->arguments.push_back(new LiteralExpression("DESCRIPTOR"));
+ IInterfaceType* iinType = new IInterfaceType();
+ Variable *iin = new Variable(iinType, "iin");
+ VariableDeclaration* iinVd = new VariableDeclaration(iin, queryLocalInterface, iinType);
+ m->statements->Add(iinVd);
+
+ // Ensure the instance type of the local object is as expected.
+ // One scenario where this is needed is if another package (with a
+ // different class loader) runs in the same process as the service.
+
+ // if (iin != null && iin instanceof <interfaceType>) return (<interfaceType>) iin;
+ Comparison* iinNotNull = new Comparison(iin, "!=", NULL_VALUE);
+ Comparison* instOfCheck = new Comparison(iin, " instanceof ",
+ new LiteralExpression(interfaceType->QualifiedName()));
+ IfStatement* instOfStatement = new IfStatement();
+ instOfStatement->expression = new Comparison(iinNotNull, "&&", instOfCheck);
+ instOfStatement->statements = new StatementBlock;
+ instOfStatement->statements->Add(new ReturnStatement(new Cast(interfaceType, iin)));
+ m->statements->Add(instOfStatement);
+
+ string proxyType = interfaceType->QualifiedName();
+ proxyType += ".Stub.Proxy";
+ NewExpression* ne = new NewExpression(NAMES.Find(proxyType));
+ ne->arguments.push_back(obj);
+ m->statements->Add(new ReturnStatement(ne));
+
+ this->elements.push_back(m);
+}
+
+
+
+// =================================================
+class ProxyClass : public Class
+{
+public:
+ ProxyClass(Type* type, InterfaceType* interfaceType);
+ virtual ~ProxyClass();
+
+ Variable* mRemote;
+ bool mOneWay;
+};
+
+ProxyClass::ProxyClass(Type* type, InterfaceType* interfaceType)
+ :Class()
+{
+ this->modifiers = PRIVATE | STATIC;
+ this->what = Class::CLASS;
+ this->type = type;
+ this->interfaces.push_back(interfaceType);
+
+ mOneWay = interfaceType->OneWay();
+
+ // IBinder mRemote
+ mRemote = new Variable(IBINDER_TYPE, "mRemote");
+ this->elements.push_back(new Field(PRIVATE, mRemote));
+
+ // Proxy()
+ Variable* remote = new Variable(IBINDER_TYPE, "remote");
+ Method* ctor = new Method;
+ ctor->name = "Proxy";
+ ctor->statements = new StatementBlock;
+ ctor->parameters.push_back(remote);
+ ctor->statements->Add(new Assignment(mRemote, remote));
+ this->elements.push_back(ctor);
+
+ // IBinder asBinder()
+ Method* asBinder = new Method;
+ asBinder->modifiers = PUBLIC;
+ asBinder->returnType = IBINDER_TYPE;
+ asBinder->name = "asBinder";
+ asBinder->statements = new StatementBlock;
+ asBinder->statements->Add(new ReturnStatement(mRemote));
+ this->elements.push_back(asBinder);
+}
+
+ProxyClass::~ProxyClass()
+{
+}
+
+// =================================================
+static void
+generate_new_array(Type* t, StatementBlock* addTo, Variable* v,
+ Variable* parcel)
+{
+ Variable* len = new Variable(INT_TYPE, v->name + "_length");
+ addTo->Add(new VariableDeclaration(len, new MethodCall(parcel, "readInt")));
+ IfStatement* lencheck = new IfStatement();
+ lencheck->expression = new Comparison(len, "<", new LiteralExpression("0"));
+ lencheck->statements->Add(new Assignment(v, NULL_VALUE));
+ lencheck->elseif = new IfStatement();
+ lencheck->elseif->statements->Add(new Assignment(v,
+ new NewArrayExpression(t, len)));
+ addTo->Add(lencheck);
+}
+
+static void
+generate_write_to_parcel(Type* t, StatementBlock* addTo, Variable* v,
+ Variable* parcel, int flags)
+{
+ if (v->dimension == 0) {
+ t->WriteToParcel(addTo, v, parcel, flags);
+ }
+ if (v->dimension == 1) {
+ t->WriteArrayToParcel(addTo, v, parcel, flags);
+ }
+}
+
+static void
+generate_create_from_parcel(Type* t, StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl)
+{
+ if (v->dimension == 0) {
+ t->CreateFromParcel(addTo, v, parcel, cl);
+ }
+ if (v->dimension == 1) {
+ t->CreateArrayFromParcel(addTo, v, parcel, cl);
+ }
+}
+
+static void
+generate_read_from_parcel(Type* t, StatementBlock* addTo, Variable* v,
+ Variable* parcel, Variable** cl)
+{
+ if (v->dimension == 0) {
+ t->ReadFromParcel(addTo, v, parcel, cl);
+ }
+ if (v->dimension == 1) {
+ t->ReadArrayFromParcel(addTo, v, parcel, cl);
+ }
+}
+
+
+static void
+generate_method(const method_type* method, Class* interface,
+ StubClass* stubClass, ProxyClass* proxyClass, int index)
+{
+ arg_type* arg;
+ int i;
+ bool hasOutParams = false;
+
+ const bool oneway = proxyClass->mOneWay || method->oneway;
+
+ // == the TRANSACT_ constant =============================================
+ string transactCodeName = "TRANSACTION_";
+ transactCodeName += method->name.data;
+
+ char transactCodeValue[50];
+ sprintf(transactCodeValue, "(android.os.IBinder.FIRST_CALL_TRANSACTION + %d)", index);
+
+ Field* transactCode = new Field(STATIC | FINAL,
+ new Variable(INT_TYPE, transactCodeName));
+ transactCode->value = transactCodeValue;
+ stubClass->elements.push_back(transactCode);
+
+ // == the declaration in the interface ===================================
+ Method* decl = new Method;
+ decl->comment = gather_comments(method->comments_token->extra);
+ decl->modifiers = PUBLIC;
+ decl->returnType = NAMES.Search(method->type.type.data);
+ decl->returnTypeDimension = method->type.dimension;
+ decl->name = method->name.data;
+
+ arg = method->args;
+ while (arg != NULL) {
+ decl->parameters.push_back(new Variable(
+ NAMES.Search(arg->type.type.data), arg->name.data,
+ arg->type.dimension));
+ arg = arg->next;
+ }
+
+ decl->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
+
+ interface->elements.push_back(decl);
+
+ // == the stub method ====================================================
+
+ Case* c = new Case(transactCodeName);
+
+ MethodCall* realCall = new MethodCall(THIS_VALUE, method->name.data);
+
+ // interface token validation is the very first thing we do
+ c->statements->Add(new MethodCall(stubClass->transact_data,
+ "enforceInterface", 1, new LiteralExpression("DESCRIPTOR")));
+
+ // args
+ Variable* cl = NULL;
+ VariableFactory stubArgs("_arg");
+ arg = method->args;
+ while (arg != NULL) {
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = stubArgs.Get(t);
+ v->dimension = arg->type.dimension;
+
+ c->statements->Add(new VariableDeclaration(v));
+
+ if (convert_direction(arg->direction.data) & IN_PARAMETER) {
+ generate_create_from_parcel(t, c->statements, v,
+ stubClass->transact_data, &cl);
+ } else {
+ if (arg->type.dimension == 0) {
+ c->statements->Add(new Assignment(v, new NewExpression(v->type)));
+ }
+ else if (arg->type.dimension == 1) {
+ generate_new_array(v->type, c->statements, v,
+ stubClass->transact_data);
+ }
+ else {
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__,
+ __LINE__);
+ }
+ }
+
+ realCall->arguments.push_back(v);
+
+ arg = arg->next;
+ }
+
+ // the real call
+ Variable* _result = NULL;
+ if (0 == strcmp(method->type.type.data, "void")) {
+ c->statements->Add(realCall);
+
+ if (!oneway) {
+ // report that there were no exceptions
+ MethodCall* ex = new MethodCall(stubClass->transact_reply,
+ "writeNoException", 0);
+ c->statements->Add(ex);
+ }
+ } else {
+ _result = new Variable(decl->returnType, "_result",
+ decl->returnTypeDimension);
+ c->statements->Add(new VariableDeclaration(_result, realCall));
+
+ if (!oneway) {
+ // report that there were no exceptions
+ MethodCall* ex = new MethodCall(stubClass->transact_reply,
+ "writeNoException", 0);
+ c->statements->Add(ex);
+ }
+
+ // marshall the return value
+ generate_write_to_parcel(decl->returnType, c->statements, _result,
+ stubClass->transact_reply,
+ Type::PARCELABLE_WRITE_RETURN_VALUE);
+ }
+
+ // out parameters
+ i = 0;
+ arg = method->args;
+ while (arg != NULL) {
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = stubArgs.Get(i++);
+
+ if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+ generate_write_to_parcel(t, c->statements, v,
+ stubClass->transact_reply,
+ Type::PARCELABLE_WRITE_RETURN_VALUE);
+ hasOutParams = true;
+ }
+
+ arg = arg->next;
+ }
+
+ // return true
+ c->statements->Add(new ReturnStatement(TRUE_VALUE));
+ stubClass->transact_switch->cases.push_back(c);
+
+ // == the proxy method ===================================================
+ Method* proxy = new Method;
+ proxy->comment = gather_comments(method->comments_token->extra);
+ proxy->modifiers = PUBLIC;
+ proxy->returnType = NAMES.Search(method->type.type.data);
+ proxy->returnTypeDimension = method->type.dimension;
+ proxy->name = method->name.data;
+ proxy->statements = new StatementBlock;
+ arg = method->args;
+ while (arg != NULL) {
+ proxy->parameters.push_back(new Variable(
+ NAMES.Search(arg->type.type.data), arg->name.data,
+ arg->type.dimension));
+ arg = arg->next;
+ }
+ proxy->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
+ proxyClass->elements.push_back(proxy);
+
+ // the parcels
+ Variable* _data = new Variable(PARCEL_TYPE, "_data");
+ proxy->statements->Add(new VariableDeclaration(_data,
+ new MethodCall(PARCEL_TYPE, "obtain")));
+ Variable* _reply = NULL;
+ if (!oneway) {
+ _reply = new Variable(PARCEL_TYPE, "_reply");
+ proxy->statements->Add(new VariableDeclaration(_reply,
+ new MethodCall(PARCEL_TYPE, "obtain")));
+ }
+
+ // the return value
+ _result = NULL;
+ if (0 != strcmp(method->type.type.data, "void")) {
+ _result = new Variable(proxy->returnType, "_result",
+ method->type.dimension);
+ proxy->statements->Add(new VariableDeclaration(_result));
+ }
+
+ // try and finally
+ TryStatement* tryStatement = new TryStatement();
+ proxy->statements->Add(tryStatement);
+ FinallyStatement* finallyStatement = new FinallyStatement();
+ proxy->statements->Add(finallyStatement);
+
+ // the interface identifier token: the DESCRIPTOR constant, marshalled as a string
+ tryStatement->statements->Add(new MethodCall(_data, "writeInterfaceToken",
+ 1, new LiteralExpression("DESCRIPTOR")));
+
+ // the parameters
+ arg = method->args;
+ while (arg != NULL) {
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
+ int dir = convert_direction(arg->direction.data);
+ if (dir == OUT_PARAMETER && arg->type.dimension != 0) {
+ IfStatement* checklen = new IfStatement();
+ checklen->expression = new Comparison(v, "==", NULL_VALUE);
+ checklen->statements->Add(new MethodCall(_data, "writeInt", 1,
+ new LiteralExpression("-1")));
+ checklen->elseif = new IfStatement();
+ checklen->elseif->statements->Add(new MethodCall(_data, "writeInt",
+ 1, new FieldVariable(v, "length")));
+ tryStatement->statements->Add(checklen);
+ }
+ else if (dir & IN_PARAMETER) {
+ generate_write_to_parcel(t, tryStatement->statements, v, _data, 0);
+ }
+ arg = arg->next;
+ }
+
+ // the transact call
+ MethodCall* call = new MethodCall(proxyClass->mRemote, "transact", 4,
+ new LiteralExpression("Stub." + transactCodeName),
+ _data, _reply ? _reply : NULL_VALUE,
+ new LiteralExpression(
+ oneway ? "android.os.IBinder.FLAG_ONEWAY" : "0"));
+ tryStatement->statements->Add(call);
+
+ // throw back exceptions.
+ if (_reply) {
+ MethodCall* ex = new MethodCall(_reply, "readException", 0);
+ tryStatement->statements->Add(ex);
+ }
+
+ // returning and cleanup
+ if (_reply != NULL) {
+ if (_result != NULL) {
+ generate_create_from_parcel(proxy->returnType,
+ tryStatement->statements, _result, _reply, &cl);
+ }
+
+ // the out/inout parameters
+ arg = method->args;
+ while (arg != NULL) {
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
+ if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+ generate_read_from_parcel(t, tryStatement->statements,
+ v, _reply, &cl);
+ }
+ arg = arg->next;
+ }
+
+ finallyStatement->statements->Add(new MethodCall(_reply, "recycle"));
+ }
+ finallyStatement->statements->Add(new MethodCall(_data, "recycle"));
+
+ if (_result != NULL) {
+ proxy->statements->Add(new ReturnStatement(_result));
+ }
+}
+
+static void
+generate_interface_descriptors(StubClass* stub, ProxyClass* proxy)
+{
+ // the interface descriptor transaction handler
+ Case* c = new Case("INTERFACE_TRANSACTION");
+ c->statements->Add(new MethodCall(stub->transact_reply, "writeString",
+ 1, new LiteralExpression("DESCRIPTOR")));
+ c->statements->Add(new ReturnStatement(TRUE_VALUE));
+ stub->transact_switch->cases.push_back(c);
+
+ // and the proxy-side method returning the descriptor directly
+ Method* getDesc = new Method;
+ getDesc->modifiers = PUBLIC;
+ getDesc->returnType = STRING_TYPE;
+ getDesc->returnTypeDimension = 0;
+ getDesc->name = "getInterfaceDescriptor";
+ getDesc->statements = new StatementBlock;
+ getDesc->statements->Add(new ReturnStatement(new LiteralExpression("DESCRIPTOR")));
+ proxy->elements.push_back(getDesc);
+}
+
+Class*
+generate_binder_interface_class(const interface_type* iface)
+{
+ InterfaceType* interfaceType = static_cast<InterfaceType*>(
+ NAMES.Find(iface->package, iface->name.data));
+
+ // the interface class
+ Class* interface = new Class;
+ interface->comment = gather_comments(iface->comments_token->extra);
+ interface->modifiers = PUBLIC;
+ interface->what = Class::INTERFACE;
+ interface->type = interfaceType;
+ interface->interfaces.push_back(IINTERFACE_TYPE);
+
+ // the stub inner class
+ StubClass* stub = new StubClass(
+ NAMES.Find(iface->package, append(iface->name.data, ".Stub").c_str()),
+ interfaceType);
+ interface->elements.push_back(stub);
+
+ // the proxy inner class
+ ProxyClass* proxy = new ProxyClass(
+ NAMES.Find(iface->package,
+ append(iface->name.data, ".Stub.Proxy").c_str()),
+ interfaceType);
+ stub->elements.push_back(proxy);
+
+ // stub and proxy support for getInterfaceDescriptor()
+ generate_interface_descriptors(stub, proxy);
+
+ // all the declared methods of the interface
+ int index = 0;
+ interface_item_type* item = iface->interface_items;
+ while (item != NULL) {
+ if (item->item_type == METHOD_TYPE) {
+ generate_method((method_type*)item, interface, stub, proxy, index);
+ }
+ item = item->next;
+ index++;
+ }
+
+ return interface;
+}
+
diff --git a/tools/aidl/generate_java_rpc.cpp b/tools/aidl/generate_java_rpc.cpp
new file mode 100644
index 0000000..456a950
--- /dev/null
+++ b/tools/aidl/generate_java_rpc.cpp
@@ -0,0 +1,684 @@
+#include "generate_java.h"
+#include "Type.h"
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+Type* SERVICE_CONTAINER_TYPE = new Type("com.android.athome.service",
+ "AndroidAtHomeServiceContainer", Type::BUILT_IN, false, false);
+
+static string
+format_int(int n)
+{
+ char str[20];
+ sprintf(str, "%d", n);
+ return string(str);
+}
+
+static string
+class_name_leaf(const string& str)
+{
+ string::size_type pos = str.rfind('.');
+ if (pos == string::npos) {
+ return str;
+ } else {
+ return string(str, pos+1);
+ }
+}
+
+// =================================================
+class RpcProxyClass : public Class
+{
+public:
+ RpcProxyClass(const interface_type* iface, InterfaceType* interfaceType);
+ virtual ~RpcProxyClass();
+
+ Variable* endpoint;
+ Variable* context;
+
+private:
+ void generate_ctor();
+};
+
+RpcProxyClass::RpcProxyClass(const interface_type* iface, InterfaceType* interfaceType)
+ :Class()
+{
+ this->comment = gather_comments(iface->comments_token->extra);
+ this->modifiers = PUBLIC;
+ this->what = Class::CLASS;
+ this->type = interfaceType;
+
+ // context
+ this->context = new Variable(CONTEXT_TYPE, "_context");
+ this->elements.push_back(new Field(PRIVATE, this->context));
+ // endpoint
+ this->endpoint = new Variable(RPC_ENDPOINT_INFO_TYPE, "_endpoint");
+ this->elements.push_back(new Field(PRIVATE, this->endpoint));
+
+ // methods
+ generate_ctor();
+}
+
+RpcProxyClass::~RpcProxyClass()
+{
+}
+
+void
+RpcProxyClass::generate_ctor()
+{
+ Variable* context = new Variable(CONTEXT_TYPE, "context");
+ Variable* endpoint = new Variable(RPC_ENDPOINT_INFO_TYPE, "endpoint");
+ Method* ctor = new Method;
+ ctor->modifiers = PUBLIC;
+ ctor->name = this->type->Name();
+ ctor->statements = new StatementBlock;
+ ctor->parameters.push_back(context);
+ ctor->parameters.push_back(endpoint);
+ this->elements.push_back(ctor);
+
+ ctor->statements->Add(new Assignment(this->context, context));
+ ctor->statements->Add(new Assignment(this->endpoint, endpoint));
+}
+
+// =================================================
+class ServiceBaseClass : public Class
+{
+public:
+ ServiceBaseClass(const interface_type* iface);
+ virtual ~ServiceBaseClass();
+
+ void AddMethod(const string& methodName, StatementBlock** statements);
+ void DoneWithMethods();
+
+ bool needed;
+ Method* processMethod;
+ Variable* actionParam;
+ Variable* errorParam;
+ Variable* requestData;
+ Variable* resultData;
+ IfStatement* dispatchIfStatement;
+
+private:
+ void generate_ctor();
+ void generate_process();
+};
+
+ServiceBaseClass::ServiceBaseClass(const interface_type* iface)
+ :Class(),
+ needed(false),
+ dispatchIfStatement(NULL)
+{
+ this->comment = "/** Extend this to implement a link service. */";
+ this->modifiers = STATIC | PUBLIC | ABSTRACT;
+ this->what = Class::CLASS;
+ this->type = NAMES.Find(iface->package, append(iface->name.data, ".ServiceBase").c_str());
+ this->extends = RPC_SERVICE_BASE_TYPE;
+
+ // methods
+ generate_ctor();
+ generate_process();
+}
+
+ServiceBaseClass::~ServiceBaseClass()
+{
+}
+
+void
+ServiceBaseClass::generate_ctor()
+{
+ Variable* container = new Variable(SERVICE_CONTAINER_TYPE, "container");
+ Variable* name = new Variable(STRING_TYPE, "name");
+ Variable* type = new Variable(STRING_TYPE, "type");
+ Variable* version = new Variable(INT_TYPE, "version");
+ Method* ctor = new Method;
+ ctor->modifiers = PUBLIC;
+ ctor->name = class_name_leaf(this->type->Name());
+ ctor->statements = new StatementBlock;
+ ctor->parameters.push_back(container);
+ ctor->parameters.push_back(name);
+ ctor->parameters.push_back(type);
+ ctor->parameters.push_back(version);
+ this->elements.push_back(ctor);
+
+ ctor->statements->Add(new MethodCall("super", 4, container, name, type, version));
+}
+
+void
+ServiceBaseClass::generate_process()
+{
+ // byte[] process(String action, byte[] params, RpcError status)
+ this->processMethod = new Method;
+ this->processMethod->modifiers = PUBLIC;
+ this->processMethod->returnType = BYTE_TYPE;
+ this->processMethod->returnTypeDimension = 1;
+ this->processMethod->name = "process";
+ this->processMethod->statements = new StatementBlock;
+ this->elements.push_back(this->processMethod);
+
+ this->actionParam = new Variable(STRING_TYPE, "action");
+ this->processMethod->parameters.push_back(this->actionParam);
+
+ Variable* requestParam = new Variable(BYTE_TYPE, "requestParam", 1);
+ this->processMethod->parameters.push_back(requestParam);
+
+ this->errorParam = new Variable(RPC_ERROR_TYPE, "errorParam", 0);
+ this->processMethod->parameters.push_back(this->errorParam);
+
+ this->requestData = new Variable(RPC_DATA_TYPE, "request");
+ this->processMethod->statements->Add(new VariableDeclaration(requestData,
+ new NewExpression(RPC_DATA_TYPE, 1, requestParam)));
+
+ this->resultData = new Variable(RPC_DATA_TYPE, "resultData");
+ this->processMethod->statements->Add(new VariableDeclaration(this->resultData,
+ NULL_VALUE));
+}
+
+void
+ServiceBaseClass::AddMethod(const string& methodName, StatementBlock** statements)
+{
+ IfStatement* ifs = new IfStatement();
+ ifs->expression = new MethodCall(new StringLiteralExpression(methodName), "equals", 1,
+ this->actionParam);
+ ifs->statements = *statements = new StatementBlock;
+ if (this->dispatchIfStatement == NULL) {
+ this->dispatchIfStatement = ifs;
+ this->processMethod->statements->Add(dispatchIfStatement);
+ } else {
+ this->dispatchIfStatement->elseif = ifs;
+ this->dispatchIfStatement = ifs;
+ }
+}
+
+void
+ServiceBaseClass::DoneWithMethods()
+{
+ IfStatement* s = new IfStatement;
+ s->statements = new StatementBlock;
+ this->processMethod->statements->Add(s);
+ s->expression = new Comparison(this->resultData, "!=", NULL_VALUE);
+ s->statements->Add(new ReturnStatement(new MethodCall(this->resultData, "serialize")));
+ s->elseif = new IfStatement;
+ s = s->elseif;
+ s->statements->Add(new ReturnStatement(NULL_VALUE));
+}
+
+// =================================================
+class ResultDispatcherClass : public Class
+{
+public:
+ ResultDispatcherClass();
+ virtual ~ResultDispatcherClass();
+
+ void AddMethod(int index, const string& name, Method** method, Variable** param);
+
+ bool needed;
+ Variable* methodId;
+ Variable* callback;
+ Method* onResultMethod;
+ Variable* resultParam;
+ SwitchStatement* methodSwitch;
+
+private:
+ void generate_ctor();
+ void generate_onResult();
+};
+
+ResultDispatcherClass::ResultDispatcherClass()
+ :Class(),
+ needed(false)
+{
+ this->modifiers = PRIVATE | FINAL;
+ this->what = Class::CLASS;
+ this->type = new Type("_ResultDispatcher", Type::GENERATED, false, false);
+ this->interfaces.push_back(RPC_RESULT_HANDLER_TYPE);
+
+ // methodId
+ this->methodId = new Variable(INT_TYPE, "methodId");
+ this->elements.push_back(new Field(PRIVATE, this->methodId));
+ this->callback = new Variable(OBJECT_TYPE, "callback");
+ this->elements.push_back(new Field(PRIVATE, this->callback));
+
+ // methods
+ generate_ctor();
+ generate_onResult();
+}
+
+ResultDispatcherClass::~ResultDispatcherClass()
+{
+}
+
+void
+ResultDispatcherClass::generate_ctor()
+{
+ Variable* methodIdParam = new Variable(INT_TYPE, "methId");
+ Variable* callbackParam = new Variable(OBJECT_TYPE, "cbObj");
+ Method* ctor = new Method;
+ ctor->modifiers = PUBLIC;
+ ctor->name = this->type->Name();
+ ctor->statements = new StatementBlock;
+ ctor->parameters.push_back(methodIdParam);
+ ctor->parameters.push_back(callbackParam);
+ this->elements.push_back(ctor);
+
+ ctor->statements->Add(new Assignment(this->methodId, methodIdParam));
+ ctor->statements->Add(new Assignment(this->callback, callbackParam));
+}
+
+void
+ResultDispatcherClass::generate_onResult()
+{
+ this->onResultMethod = new Method;
+ this->onResultMethod->modifiers = PUBLIC;
+ this->onResultMethod->returnType = VOID_TYPE;
+ this->onResultMethod->returnTypeDimension = 0;
+ this->onResultMethod->name = "onResult";
+ this->onResultMethod->statements = new StatementBlock;
+ this->elements.push_back(this->onResultMethod);
+
+ this->resultParam = new Variable(BYTE_TYPE, "result", 1);
+ this->onResultMethod->parameters.push_back(this->resultParam);
+
+ this->methodSwitch = new SwitchStatement(this->methodId);
+ this->onResultMethod->statements->Add(this->methodSwitch);
+}
+
+void
+ResultDispatcherClass::AddMethod(int index, const string& name, Method** method, Variable** param)
+{
+ Method* m = new Method;
+ m->modifiers = PUBLIC;
+ m->returnType = VOID_TYPE;
+ m->returnTypeDimension = 0;
+ m->name = name;
+ m->statements = new StatementBlock;
+ *param = new Variable(BYTE_TYPE, "result", 1);
+ m->parameters.push_back(*param);
+ this->elements.push_back(m);
+ *method = m;
+
+ Case* c = new Case(format_int(index));
+ c->statements->Add(new MethodCall(new LiteralExpression("this"), name, 1, this->resultParam));
+ c->statements->Add(new Break());
+
+ this->methodSwitch->cases.push_back(c);
+}
+
+// =================================================
+static void
+generate_new_array(Type* t, StatementBlock* addTo, Variable* v, Variable* from)
+{
+ fprintf(stderr, "aidl: implement generate_new_array %s:%d\n", __FILE__, __LINE__);
+ exit(1);
+}
+
+static void
+generate_create_from_data(Type* t, StatementBlock* addTo, const string& key, Variable* v,
+ Variable* data, Variable** cl)
+{
+ Expression* k = new StringLiteralExpression(key);
+ if (v->dimension == 0) {
+ t->CreateFromRpcData(addTo, k, v, data, cl);
+ }
+ if (v->dimension == 1) {
+ //t->ReadArrayFromRpcData(addTo, v, data, cl);
+ fprintf(stderr, "aidl: implement generate_create_from_data for arrays%s:%d\n",
+ __FILE__, __LINE__);
+ }
+}
+
+static void
+generate_write_to_data(Type* t, StatementBlock* addTo, Expression* k, Variable* v, Variable* data)
+{
+ if (v->dimension == 0) {
+ t->WriteToRpcData(addTo, k, v, data, 0);
+ }
+ if (v->dimension == 1) {
+ //t->WriteArrayToParcel(addTo, v, data);
+ fprintf(stderr, "aidl: implement generate_write_to_data for arrays%s:%d\n",
+ __FILE__, __LINE__);
+ }
+}
+
+// =================================================
+static string
+results_class_name(const string& n)
+{
+ string str = n;
+ str[0] = toupper(str[0]);
+ str.insert(0, "On");
+ return str;
+}
+
+static string
+results_method_name(const string& n)
+{
+ string str = n;
+ str[0] = toupper(str[0]);
+ str.insert(0, "on");
+ return str;
+}
+
+static Type*
+generate_results_method(const method_type* method, RpcProxyClass* proxyClass)
+{
+ arg_type* arg;
+
+ string resultsMethodName = results_method_name(method->name.data);
+ Type* resultsInterfaceType = new Type(results_class_name(method->name.data),
+ Type::GENERATED, false, false);
+
+ if (!method->oneway) {
+ Class* resultsClass = new Class;
+ resultsClass->modifiers = STATIC | PUBLIC;
+ resultsClass->what = Class::INTERFACE;
+ resultsClass->type = resultsInterfaceType;
+
+ Method* resultMethod = new Method;
+ resultMethod->comment = gather_comments(method->comments_token->extra);
+ resultMethod->modifiers = PUBLIC;
+ resultMethod->returnType = VOID_TYPE;
+ resultMethod->returnTypeDimension = 0;
+ resultMethod->name = resultsMethodName;
+ if (0 != strcmp("void", method->type.type.data)) {
+ resultMethod->parameters.push_back(new Variable(NAMES.Search(method->type.type.data),
+ "_result", method->type.dimension));
+ }
+ arg = method->args;
+ while (arg != NULL) {
+ if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+ resultMethod->parameters.push_back(new Variable(
+ NAMES.Search(arg->type.type.data), arg->name.data,
+ arg->type.dimension));
+ }
+ arg = arg->next;
+ }
+ resultsClass->elements.push_back(resultMethod);
+
+ if (resultMethod->parameters.size() > 0) {
+ proxyClass->elements.push_back(resultsClass);
+ return resultsInterfaceType;
+ }
+ }
+ //delete resultsInterfaceType;
+ return NULL;
+}
+
+static void
+generate_proxy_method(const method_type* method, RpcProxyClass* proxyClass,
+ ResultDispatcherClass* resultsDispatcherClass, Type* resultsInterfaceType, int index)
+{
+ arg_type* arg;
+ Method* proxyMethod = new Method;
+ proxyMethod->comment = gather_comments(method->comments_token->extra);
+ proxyMethod->modifiers = PUBLIC;
+ proxyMethod->returnType = VOID_TYPE;
+ proxyMethod->returnTypeDimension = 0;
+ proxyMethod->name = method->name.data;
+ proxyMethod->statements = new StatementBlock;
+ proxyClass->elements.push_back(proxyMethod);
+
+ // The local variables
+ Variable* _data = new Variable(RPC_DATA_TYPE, "_data");
+ proxyMethod->statements->Add(new VariableDeclaration(_data, new NewExpression(RPC_DATA_TYPE)));
+
+ // Add the arguments
+ arg = method->args;
+ while (arg != NULL) {
+ if (convert_direction(arg->direction.data) & IN_PARAMETER) {
+ // Function signature
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
+ proxyMethod->parameters.push_back(v);
+
+ // Input parameter marshalling
+ generate_write_to_data(t, proxyMethod->statements,
+ new StringLiteralExpression(arg->name.data), v, _data);
+ }
+ arg = arg->next;
+ }
+
+ // If there is a results interface for this class
+ Expression* resultParameter;
+ if (resultsInterfaceType != NULL) {
+ // Result interface parameter
+ Variable* resultListener = new Variable(resultsInterfaceType, "_result");
+ proxyMethod->parameters.push_back(resultListener);
+
+ // Add the results dispatcher callback
+ resultsDispatcherClass->needed = true;
+ resultParameter = new NewExpression(resultsDispatcherClass->type, 2,
+ new LiteralExpression(format_int(index)), resultListener);
+ } else {
+ resultParameter = NULL_VALUE;
+ }
+
+ // All proxy methods take an error parameter
+ Variable* errorListener = new Variable(RPC_ERROR_LISTENER_TYPE, "_errors");
+ proxyMethod->parameters.push_back(errorListener);
+
+ // Call the broker
+ proxyMethod->statements->Add(new MethodCall(RPC_BROKER_TYPE, "sendRequest", 6,
+ new FieldVariable(THIS_VALUE, "_context"),
+ new StringLiteralExpression(method->name.data),
+ proxyClass->endpoint,
+ new MethodCall(_data, "serialize"),
+ resultParameter,
+ errorListener));
+}
+
+static void
+generate_result_dispatcher_method(const method_type* method,
+ ResultDispatcherClass* resultsDispatcherClass, Type* resultsInterfaceType, int index)
+{
+ arg_type* arg;
+ Method* dispatchMethod;
+ Variable* dispatchParam;
+ resultsDispatcherClass->AddMethod(index, method->name.data, &dispatchMethod, &dispatchParam);
+
+ Variable* classLoader = NULL;
+ Variable* resultData = new Variable(RPC_DATA_TYPE, "resultData");
+ dispatchMethod->statements->Add(new VariableDeclaration(resultData,
+ new NewExpression(RPC_DATA_TYPE, 1, dispatchParam)));
+
+ // The callback method itself
+ MethodCall* realCall = new MethodCall(
+ new Cast(resultsInterfaceType, new FieldVariable(THIS_VALUE, "callback")),
+ results_method_name(method->name.data));
+
+ // The return value
+ {
+ Type* t = NAMES.Search(method->type.type.data);
+ Variable* rv = new Variable(t, "rv");
+ dispatchMethod->statements->Add(new VariableDeclaration(rv));
+ generate_create_from_data(t, dispatchMethod->statements, "_result", rv,
+ resultData, &classLoader);
+ realCall->arguments.push_back(rv);
+ }
+
+ VariableFactory stubArgs("arg");
+ arg = method->args;
+ while (arg != NULL) {
+ if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+ // Unmarshall the results
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = stubArgs.Get(t);
+ dispatchMethod->statements->Add(new VariableDeclaration(v));
+
+ generate_create_from_data(t, dispatchMethod->statements, arg->name.data, v,
+ resultData, &classLoader);
+
+ // Add the argument to the callback
+ realCall->arguments.push_back(v);
+ }
+ arg = arg->next;
+ }
+
+ // Call the callback method
+ dispatchMethod->statements->Add(realCall);
+}
+
+static void
+generate_service_base_methods(const method_type* method, ServiceBaseClass* serviceBaseClass)
+{
+ arg_type* arg;
+ StatementBlock* block;
+ serviceBaseClass->AddMethod(method->name.data, &block);
+
+ // The abstract method that the service developers implement
+ Method* decl = new Method;
+ decl->comment = gather_comments(method->comments_token->extra);
+ decl->modifiers = PUBLIC | ABSTRACT;
+ decl->returnType = NAMES.Search(method->type.type.data);
+ decl->returnTypeDimension = method->type.dimension;
+ decl->name = method->name.data;
+
+ arg = method->args;
+ while (arg != NULL) {
+ decl->parameters.push_back(new Variable(
+ NAMES.Search(arg->type.type.data), arg->name.data,
+ arg->type.dimension));
+ arg = arg->next;
+ }
+
+ serviceBaseClass->elements.push_back(decl);
+
+ // The call to decl (from above)
+ MethodCall* realCall = new MethodCall(THIS_VALUE, method->name.data);
+
+ // args
+ Variable* classLoader = NULL;
+ VariableFactory stubArgs("_arg");
+ arg = method->args;
+ while (arg != NULL) {
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = stubArgs.Get(t);
+ v->dimension = arg->type.dimension;
+
+ // Unmarshall the parameter
+ block->Add(new VariableDeclaration(v));
+ if (convert_direction(arg->direction.data) & IN_PARAMETER) {
+ generate_create_from_data(t, block, arg->name.data, v,
+ serviceBaseClass->requestData, &classLoader);
+ } else {
+ if (arg->type.dimension == 0) {
+ block->Add(new Assignment(v, new NewExpression(v->type)));
+ }
+ else if (arg->type.dimension == 1) {
+ generate_new_array(v->type, block, v, serviceBaseClass->requestData);
+ }
+ else {
+ fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__,
+ __LINE__);
+ }
+ }
+
+ // Add that parameter to the method call
+ realCall->arguments.push_back(v);
+
+ arg = arg->next;
+ }
+
+ // the real call
+ bool first = true;
+ Variable* _result = NULL;
+ if (0 == strcmp(method->type.type.data, "void")) {
+ block->Add(realCall);
+ } else {
+ _result = new Variable(decl->returnType, "_result",
+ decl->returnTypeDimension);
+ block->Add(new VariableDeclaration(_result, realCall));
+
+ // need the result RpcData
+ if (first) {
+ block->Add(new Assignment(serviceBaseClass->resultData,
+ new NewExpression(RPC_DATA_TYPE)));
+ first = false;
+ }
+
+ // marshall the return value
+ generate_write_to_data(decl->returnType, block,
+ new StringLiteralExpression("_result"), _result, serviceBaseClass->resultData);
+ }
+
+ // out parameters
+ int i = 0;
+ arg = method->args;
+ while (arg != NULL) {
+ Type* t = NAMES.Search(arg->type.type.data);
+ Variable* v = stubArgs.Get(i++);
+
+ if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+ // need the result RpcData
+ if (first) {
+ block->Add(new Assignment(serviceBaseClass->resultData,
+ new NewExpression(RPC_DATA_TYPE)));
+ first = false;
+ }
+
+
+ generate_write_to_data(t, block, new StringLiteralExpression(arg->name.data),
+ v, serviceBaseClass->resultData);
+ }
+
+ arg = arg->next;
+ }
+}
+
+static void
+generate_method(const method_type* method, RpcProxyClass* proxyClass,
+ ServiceBaseClass* serviceBaseClass, ResultDispatcherClass* resultsDispatcherClass,
+ int index)
+{
+ // == the callback interface for results =================================
+ // the service base class
+ Type* resultsInterfaceType = generate_results_method(method, proxyClass);
+
+ // == the method in the proxy class =====================================
+ generate_proxy_method(method, proxyClass, resultsDispatcherClass, resultsInterfaceType, index);
+
+ // == the method in the result dispatcher class =========================
+ if (resultsInterfaceType != NULL) {
+ generate_result_dispatcher_method(method, resultsDispatcherClass, resultsInterfaceType,
+ index);
+ }
+
+ // == the dispatch method in the service base class ======================
+ generate_service_base_methods(method, serviceBaseClass);
+}
+
+Class*
+generate_rpc_interface_class(const interface_type* iface)
+{
+ // the proxy class
+ InterfaceType* interfaceType = static_cast<InterfaceType*>(
+ NAMES.Find(iface->package, iface->name.data));
+ RpcProxyClass* proxy = new RpcProxyClass(iface, interfaceType);
+
+ // the service base class
+ ServiceBaseClass* base = new ServiceBaseClass(iface);
+ proxy->elements.push_back(base);
+
+ // the result dispatcher
+ ResultDispatcherClass* results = new ResultDispatcherClass();
+
+ // all the declared methods of the proxy
+ int index = 0;
+ interface_item_type* item = iface->interface_items;
+ while (item != NULL) {
+ if (item->item_type == METHOD_TYPE) {
+ generate_method((method_type*)item, proxy, base, results, index);
+ }
+ item = item->next;
+ index++;
+ }
+ base->DoneWithMethods();
+
+ // only add this if there are methods with results / out parameters
+ if (results->needed) {
+ proxy->elements.push_back(results);
+ }
+
+ return proxy;
+}
+