Compare commits
15 commits
Author | SHA1 | Date | |
---|---|---|---|
4c0adf1ebd | |||
113b4641d4 | |||
9a0919771d | |||
f63cb4614e | |||
0e33f8604e | |||
8d5966e22b | |||
a37889886c | |||
c3018a9251 | |||
40ac06b59d | |||
cd37a9a95a | |||
a7cb68003a | |||
7a39eb0457 | |||
39f4ef617d | |||
2cdc2676ea | |||
6d24ec3c76 |
25 changed files with 346 additions and 4609 deletions
|
@ -1,10 +1,7 @@
|
||||||
# Android App Written in C (build via docker/podman)
|
# Native (non java written) Android App Written in C (build via docker/podman)
|
||||||
|
|
||||||
This repo provides its user with the ability to setup and compile a Android-App that is
|
branch `webview` to only setup a webview
|
||||||
based on `android.app.NativeActiviy` and thus can be completely compiled C source-code.
|
|
||||||
|
|
||||||
It is an adoptation (and hopefully somehow simplification of this repo [`rawdrawandroid`](https://github.com/cnlohr/rawdrawandroid)
|
|
||||||
and this many thanks and credits to most of the c-code goes to CNLohr.
|
|
||||||
|
|
||||||
## usage
|
## usage
|
||||||
|
|
||||||
|
@ -23,6 +20,7 @@ adb install -r app/result/app.apk
|
||||||
|
|
||||||
## basic ideas
|
## basic ideas
|
||||||
|
|
||||||
|
* "native app" to remove need to work with JAVA/KOTLIN madness
|
||||||
* work within container (debian based image)
|
* work within container (debian based image)
|
||||||
* use Makefile as a build tool
|
* use Makefile as a build tool
|
||||||
* avoid the "unhappiness" of having to deal with neither KOTLIN nor JAVA
|
* avoid the "unhappiness" of having to deal with neither KOTLIN nor JAVA
|
||||||
|
|
|
@ -25,13 +25,13 @@ CFLAGS+= -I./src/nativecode/rawdraw -I./src/nativecode -I$(NDK)/sysroot/usr/incl
|
||||||
CFLAGS+= -I$(NDK)/sysroot/usr/include/android -I$(NDK_TOOLCHAIN_INCLUDE) -I$(NDK_TOOLCHAIN_INCLUDE)/android
|
CFLAGS+= -I$(NDK)/sysroot/usr/include/android -I$(NDK_TOOLCHAIN_INCLUDE) -I$(NDK_TOOLCHAIN_INCLUDE)/android
|
||||||
CFLAGS+= -fPIC -DANDROIDVERSION=$(ANDROIDVERSION)
|
CFLAGS+= -fPIC -DANDROIDVERSION=$(ANDROIDVERSION)
|
||||||
CFLAGS_ARM64:=-m64
|
CFLAGS_ARM64:=-m64
|
||||||
LDFLAGS?=-Wl,--gc-sections -Wl,-Map=output.map -s
|
LDFLAGS?=-Wl,--gc-sections -s
|
||||||
LDFLAGS += -s
|
LDFLAGS += -s
|
||||||
# LDFLAGS += -static-libstdc++
|
# LDFLAGS += -static-libstdc++
|
||||||
LDFLAGS += -lm -lGLESv3 -lEGL -landroid -llog -lOpenSLES
|
LDFLAGS += -lm -lGLESv3 -lEGL -landroid -llog -lOpenSLES
|
||||||
LDFLAGS += -shared -uANativeActivity_onCreate
|
LDFLAGS += -shared -uANativeActivity_onCreate
|
||||||
# locatoins of "mostly c" source files
|
# locatoins of "mostly c" source files
|
||||||
SRC?=./src/nativecode/test.c
|
SRC?=./src/nativecode/main.c
|
||||||
RAWDRAWANDROIDSRCS=./src/nativecode/android_native_app_glue.c
|
RAWDRAWANDROIDSRCS=./src/nativecode/android_native_app_glue.c
|
||||||
ANDROIDSRCS:= $(SRC) $(RAWDRAWANDROIDSRCS)
|
ANDROIDSRCS:= $(SRC) $(RAWDRAWANDROIDSRCS)
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,8 @@
|
||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
#include <android/native_activity.h>
|
#include <android/native_activity.h>
|
||||||
|
|
||||||
#include "webview_native_activity.h"
|
//webview
|
||||||
|
//webview #include "webview_native_activity.h"
|
||||||
struct android_app * gapp;
|
struct android_app * gapp;
|
||||||
|
|
||||||
#define LOGI(...) ((void)printf(__VA_ARGS__))
|
#define LOGI(...) ((void)printf(__VA_ARGS__))
|
||||||
|
|
|
@ -1,210 +0,0 @@
|
||||||
//Copyright 2020 <>< Charles Lohr, You may use this file and library freely under the MIT/x11, NewBSD or ColorChord Licenses.
|
|
||||||
|
|
||||||
#include "android_usb_devices.h"
|
|
||||||
#include "CNFG.h"
|
|
||||||
#include "os_generic.h"
|
|
||||||
|
|
||||||
double dTimeOfUSBFail;
|
|
||||||
double dTimeOfLastAsk;
|
|
||||||
jobject deviceConnection = 0;
|
|
||||||
int deviceConnectionFD = 0;
|
|
||||||
extern struct android_app * gapp;
|
|
||||||
|
|
||||||
void DisconnectUSB()
|
|
||||||
{
|
|
||||||
deviceConnectionFD = 0;
|
|
||||||
dTimeOfUSBFail = OGGetAbsoluteTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
int RequestPermissionOrGetConnectionFD( char * ats, uint16_t vid, uint16_t pid )
|
|
||||||
{
|
|
||||||
//Don't permit
|
|
||||||
if( OGGetAbsoluteTime() - dTimeOfUSBFail < 1 )
|
|
||||||
{
|
|
||||||
ats+=sprintf(ats, "Comms failed. Waiting to reconnect." );
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct android_app* app = gapp;
|
|
||||||
const struct JNINativeInterface * env = 0;
|
|
||||||
const struct JNINativeInterface ** envptr = &env;
|
|
||||||
const struct JNIInvokeInterface ** jniiptr = app->activity->vm;
|
|
||||||
const struct JNIInvokeInterface * jnii = *jniiptr;
|
|
||||||
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
|
|
||||||
env = (*envptr);
|
|
||||||
|
|
||||||
// Retrieves NativeActivity.
|
|
||||||
jobject lNativeActivity = gapp->activity->clazz;
|
|
||||||
|
|
||||||
//https://stackoverflow.com/questions/13280581/using-android-to-communicate-with-a-usb-hid-device
|
|
||||||
|
|
||||||
//UsbManager manager = (UsbManager)getSystemService(Context.USB_SERVICE);
|
|
||||||
jclass ClassContext = env->FindClass( envptr, "android/content/Context" );
|
|
||||||
jfieldID lid_USB_SERVICE = env->GetStaticFieldID( envptr, ClassContext, "USB_SERVICE", "Ljava/lang/String;" );
|
|
||||||
jobject USB_SERVICE = env->GetStaticObjectField( envptr, ClassContext, lid_USB_SERVICE );
|
|
||||||
|
|
||||||
jmethodID MethodgetSystemService = env->GetMethodID( envptr, ClassContext, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;" );
|
|
||||||
jobject manager = env->CallObjectMethod( envptr, lNativeActivity, MethodgetSystemService, USB_SERVICE);
|
|
||||||
//Actually returns an android/hardware/usb/UsbManager
|
|
||||||
jclass ClassUsbManager = env->FindClass( envptr, "android/hardware/usb/UsbManager" );
|
|
||||||
|
|
||||||
//HashMap<String, UsbDevice> deviceList = mManager.getDeviceList();
|
|
||||||
jmethodID MethodgetDeviceList = env->GetMethodID( envptr, ClassUsbManager, "getDeviceList", "()Ljava/util/HashMap;" );
|
|
||||||
jobject deviceList = env->CallObjectMethod( envptr, manager, MethodgetDeviceList );
|
|
||||||
|
|
||||||
//Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
|
|
||||||
jclass ClassHashMap = env->FindClass( envptr, "java/util/HashMap" );
|
|
||||||
jmethodID Methodvalues = env->GetMethodID( envptr, ClassHashMap, "values", "()Ljava/util/Collection;" );
|
|
||||||
jobject deviceListCollection = env->CallObjectMethod( envptr, deviceList, Methodvalues );
|
|
||||||
jclass ClassCollection = env->FindClass( envptr, "java/util/Collection" );
|
|
||||||
jmethodID Methoditerator = env->GetMethodID( envptr, ClassCollection, "iterator", "()Ljava/util/Iterator;" );
|
|
||||||
jobject deviceListIterator = env->CallObjectMethod( envptr, deviceListCollection, Methoditerator );
|
|
||||||
jclass ClassIterator = env->FindClass( envptr, "java/util/Iterator" );
|
|
||||||
|
|
||||||
//while (deviceIterator.hasNext())
|
|
||||||
jmethodID MethodhasNext = env->GetMethodID( envptr, ClassIterator, "hasNext", "()Z" );
|
|
||||||
jboolean bHasNext = env->CallBooleanMethod( envptr, deviceListIterator, MethodhasNext );
|
|
||||||
|
|
||||||
ats+=sprintf(ats, "Has Devices: %d\n", bHasNext );
|
|
||||||
|
|
||||||
jmethodID Methodnext = env->GetMethodID( envptr, ClassIterator, "next", "()Ljava/lang/Object;" );
|
|
||||||
|
|
||||||
jclass ClassUsbDevice = env->FindClass( envptr, "android/hardware/usb/UsbDevice" );
|
|
||||||
jclass ClassUsbInterface = env->FindClass( envptr, "android/hardware/usb/UsbInterface" );
|
|
||||||
jclass ClassUsbEndpoint = env->FindClass( envptr, "android/hardware/usb/UsbEndpoint" );
|
|
||||||
jclass ClassUsbDeviceConnection = env->FindClass( envptr, "android/hardware/usb/UsbDeviceConnection" );
|
|
||||||
jmethodID MethodgetDeviceName = env->GetMethodID( envptr, ClassUsbDevice, "getDeviceName", "()Ljava/lang/String;" );
|
|
||||||
jmethodID MethodgetVendorId = env->GetMethodID( envptr, ClassUsbDevice, "getVendorId", "()I" );
|
|
||||||
jmethodID MethodgetProductId = env->GetMethodID( envptr, ClassUsbDevice, "getProductId", "()I" );
|
|
||||||
jmethodID MethodgetInterfaceCount = env->GetMethodID( envptr, ClassUsbDevice, "getInterfaceCount", "()I" );
|
|
||||||
jmethodID MethodgetInterface = env->GetMethodID( envptr, ClassUsbDevice, "getInterface", "(I)Landroid/hardware/usb/UsbInterface;" );
|
|
||||||
|
|
||||||
jmethodID MethodgetEndpointCount = env->GetMethodID( envptr, ClassUsbInterface, "getEndpointCount", "()I" );
|
|
||||||
jmethodID MethodgetEndpoint = env->GetMethodID( envptr, ClassUsbInterface, "getEndpoint", "(I)Landroid/hardware/usb/UsbEndpoint;" );
|
|
||||||
|
|
||||||
jmethodID MethodgetAddress = env->GetMethodID( envptr, ClassUsbEndpoint, "getAddress", "()I" );
|
|
||||||
jmethodID MethodgetMaxPacketSize = env->GetMethodID( envptr, ClassUsbEndpoint, "getMaxPacketSize", "()I" );
|
|
||||||
|
|
||||||
jobject matchingDevice = 0;
|
|
||||||
jobject matchingInterface = 0;
|
|
||||||
|
|
||||||
while( bHasNext )
|
|
||||||
{
|
|
||||||
// UsbDevice device = deviceIterator.next();
|
|
||||||
// Log.i(TAG,"Model: " + device.getDeviceName());
|
|
||||||
jobject device = env->CallObjectMethod( envptr, deviceListIterator, Methodnext );
|
|
||||||
uint16_t vendorId = env->CallIntMethod( envptr, device, MethodgetVendorId );
|
|
||||||
uint16_t productId = env->CallIntMethod( envptr, device, MethodgetProductId );
|
|
||||||
int ifaceCount = env->CallIntMethod( envptr, device, MethodgetInterfaceCount );
|
|
||||||
const char *strdevname = env->GetStringUTFChars(envptr, env->CallObjectMethod( envptr, device, MethodgetDeviceName ), 0);
|
|
||||||
ats+=sprintf(ats, "%s,%04x:%04x(%d)\n", strdevname,
|
|
||||||
vendorId,
|
|
||||||
productId, ifaceCount );
|
|
||||||
|
|
||||||
if( vendorId == vid && productId == pid )
|
|
||||||
{
|
|
||||||
if( ifaceCount )
|
|
||||||
{
|
|
||||||
matchingDevice = device;
|
|
||||||
matchingInterface = env->CallObjectMethod( envptr, device, MethodgetInterface, 0 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bHasNext = env->CallBooleanMethod( envptr, deviceListIterator, MethodhasNext );
|
|
||||||
}
|
|
||||||
|
|
||||||
jobject matchingEp = 0;
|
|
||||||
|
|
||||||
if( matchingInterface )
|
|
||||||
{
|
|
||||||
//matchingInterface is of type android/hardware/usb/UsbInterface
|
|
||||||
int epCount = env->CallIntMethod( envptr, matchingInterface, MethodgetEndpointCount );
|
|
||||||
ats+=sprintf(ats, "Found device %d eps\n", epCount );
|
|
||||||
int i;
|
|
||||||
for( i = 0; i < epCount; i++ )
|
|
||||||
{
|
|
||||||
jobject endpoint = env->CallObjectMethod( envptr, matchingInterface, MethodgetEndpoint, i );
|
|
||||||
jint epnum = env->CallIntMethod( envptr, endpoint, MethodgetAddress );
|
|
||||||
jint mps = env->CallIntMethod( envptr, endpoint, MethodgetMaxPacketSize );
|
|
||||||
if( epnum == 0x02 ) matchingEp = endpoint;
|
|
||||||
ats+=sprintf(ats, "%p: %02x: MPS: %d (%c)\n", endpoint, epnum, mps, (matchingEp == endpoint)?'*':' ' );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jmethodID MethodopenDevice = env->GetMethodID( envptr, ClassUsbManager, "openDevice", "(Landroid/hardware/usb/UsbDevice;)Landroid/hardware/usb/UsbDeviceConnection;" );
|
|
||||||
jmethodID MethodrequestPermission = env->GetMethodID( envptr, ClassUsbManager, "requestPermission", "(Landroid/hardware/usb/UsbDevice;Landroid/app/PendingIntent;)V" );
|
|
||||||
jmethodID MethodhasPermission = env->GetMethodID( envptr, ClassUsbManager, "hasPermission", "(Landroid/hardware/usb/UsbDevice;)Z" );
|
|
||||||
jmethodID MethodclaimInterface = env->GetMethodID( envptr, ClassUsbDeviceConnection, "claimInterface", "(Landroid/hardware/usb/UsbInterface;Z)Z" );
|
|
||||||
jmethodID MethodsetInterface = env->GetMethodID( envptr, ClassUsbDeviceConnection, "setInterface", "(Landroid/hardware/usb/UsbInterface;)Z" );
|
|
||||||
jmethodID MethodgetFileDescriptor = env->GetMethodID( envptr, ClassUsbDeviceConnection, "getFileDescriptor", "()I" );
|
|
||||||
//jmethodID MethodbulkTransfer = env->GetMethodID( envptr, ClassUsbDeviceConnection, "bulkTransfer", "(Landroid/hardware/usb/UsbEndpoint;[BII)I" );
|
|
||||||
|
|
||||||
//see https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/hardware/usb/UsbDeviceConnection.java
|
|
||||||
//Calls: native_bulk_request -> android_hardware_UsbDeviceConnection_bulk_request -> usb_device_bulk_transfer
|
|
||||||
// UsbEndpoint endpoint, byte[] buffer, int length, int timeout
|
|
||||||
//bulkTransfer(UsbEndpoint endpoint, byte[] buffer, int length, int timeout)
|
|
||||||
|
|
||||||
//UsbDeviceConnection bulkTransfer
|
|
||||||
|
|
||||||
if( matchingEp && matchingDevice )
|
|
||||||
{
|
|
||||||
//UsbDeviceConnection deviceConnection = manager.openDevice( device )
|
|
||||||
deviceConnection = env->CallObjectMethod( envptr, manager, MethodopenDevice, matchingDevice );
|
|
||||||
jint epnum = env->CallIntMethod( envptr, matchingEp, MethodgetAddress );
|
|
||||||
|
|
||||||
if( !deviceConnection )
|
|
||||||
{
|
|
||||||
// hasPermission(UsbDevice device)
|
|
||||||
|
|
||||||
if( OGGetAbsoluteTime() - dTimeOfLastAsk < 5 )
|
|
||||||
{
|
|
||||||
ats+=sprintf(ats, "Asked for permission. Waiting to ask again." );
|
|
||||||
}
|
|
||||||
else if( env->CallBooleanMethod( envptr, manager, MethodhasPermission, matchingDevice ) )
|
|
||||||
{
|
|
||||||
ats+=sprintf(ats, "Has permission - disconnected?" );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//android.app.PendingIntent currently setting to 0 (null) seems not to cause crashes, but does force lock screen to happen.
|
|
||||||
//Because the screen locks we need to do a much more complicated operation, generating a PendingIntent. See Below.
|
|
||||||
// env->CallVoidMethod( envptr, manager, MethodrequestPermission, matchingDevice, 0 );
|
|
||||||
|
|
||||||
//This part mimiced off of:
|
|
||||||
//https://www.programcreek.com/java-api-examples/?class=android.hardware.usb.UsbManager&method=requestPermission
|
|
||||||
// manager.requestPermission(device, PendingIntent.getBroadcast(context, 0, new Intent(MainActivity.ACTION_USB_PERMISSION), 0));
|
|
||||||
jclass ClassPendingIntent = env->FindClass( envptr, "android/app/PendingIntent" );
|
|
||||||
jclass ClassIntent = env->FindClass(envptr, "android/content/Intent");
|
|
||||||
jmethodID newIntent = env->GetMethodID(envptr, ClassIntent, "<init>", "(Ljava/lang/String;)V");
|
|
||||||
jstring ACTION_USB_PERMISSION = env->NewStringUTF( envptr, "com.android.recipes.USB_PERMISSION" );
|
|
||||||
jobject intentObject = env->NewObject(envptr, ClassIntent, newIntent, ACTION_USB_PERMISSION);
|
|
||||||
|
|
||||||
jmethodID MethodgetBroadcast = env->GetStaticMethodID( envptr, ClassPendingIntent, "getBroadcast",
|
|
||||||
"(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;" );
|
|
||||||
jobject pi = env->CallStaticObjectMethod( envptr, ClassPendingIntent, MethodgetBroadcast, lNativeActivity, 0, intentObject, 0 );
|
|
||||||
|
|
||||||
//This actually requests permission.
|
|
||||||
env->CallVoidMethod( envptr, manager, MethodrequestPermission, matchingDevice, pi );
|
|
||||||
dTimeOfLastAsk = OGGetAbsoluteTime();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//Because we want to read and write to an interrupt endpoint, we need to claim the interface - it seems setting interfaces is insufficient here.
|
|
||||||
jboolean claimOk = env->CallBooleanMethod( envptr, deviceConnection, MethodclaimInterface, matchingInterface, 1 );
|
|
||||||
//jboolean claimOk = env->CallBooleanMethod( envptr, deviceConnection, MethodsetInterface, matchingInterface );
|
|
||||||
//jboolean claimOk = 1;
|
|
||||||
if( claimOk )
|
|
||||||
{
|
|
||||||
deviceConnectionFD = env->CallIntMethod( envptr, deviceConnection, MethodgetFileDescriptor );
|
|
||||||
}
|
|
||||||
|
|
||||||
ats+=sprintf(ats, "DC: %p; Claim: %d; FD: %d\n", deviceConnection, claimOk, deviceConnectionFD );
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
jnii->DetachCurrentThread( jniiptr );
|
|
||||||
return (!deviceConnectionFD)?-5:0;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
//Copyright 2020 <>< Charles Lohr, You may use this file and library freely under the MIT/x11, NewBSD or ColorChord Licenses.
|
|
||||||
|
|
||||||
#ifndef _ANDROID_USB_DEVICES_H
|
|
||||||
#define _ANDROID_USB_DEVICES_H
|
|
||||||
|
|
||||||
#include <asset_manager.h>
|
|
||||||
#include <asset_manager_jni.h>
|
|
||||||
#include <android_native_app_glue.h>
|
|
||||||
|
|
||||||
int RequestPermissionOrGetConnectionFD( char * debug_status, uint16_t vid, uint16_t pid );
|
|
||||||
void DisconnectUSB(); //Disconnect from USB
|
|
||||||
|
|
||||||
extern jobject deviceConnection;
|
|
||||||
extern int deviceConnectionFD;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
3
app/src/nativecode/cnfa/.gitignore
vendored
3
app/src/nativecode/cnfa/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
||||||
os_generic.h
|
|
||||||
example
|
|
||||||
|
|
|
@ -1,128 +0,0 @@
|
||||||
//Copyright <>< 2010-2020 Charles Lohr (And other authors as cited)
|
|
||||||
//CNFA is licensed under the MIT/x11, ColorChord or NewBSD Licenses. You choose.
|
|
||||||
|
|
||||||
#ifndef _CNFA_C
|
|
||||||
#define _CNFA_C
|
|
||||||
|
|
||||||
#include "CNFA.h"
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#if defined(WINDOWS) || defined(WIN32) || defined(WIN64) \
|
|
||||||
|| defined(_WIN32) || defined(_WIN64)
|
|
||||||
#ifndef strdup
|
|
||||||
#define strdup _strdup
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static CNFAInitFn * CNFADrivers[MAX_CNFA_DRIVERS];
|
|
||||||
static char * CNFADriverNames[MAX_CNFA_DRIVERS];
|
|
||||||
static int CNFADriverPriorities[MAX_CNFA_DRIVERS];
|
|
||||||
|
|
||||||
void RegCNFADriver( int priority, const char * name, CNFAInitFn * fn )
|
|
||||||
{
|
|
||||||
int j;
|
|
||||||
|
|
||||||
if( priority <= 0 )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("[CNFA] Registering Driver: %s\n", name);
|
|
||||||
|
|
||||||
for( j = MAX_CNFA_DRIVERS-1; j >= 0; j-- )
|
|
||||||
{
|
|
||||||
//Cruise along, find location to insert
|
|
||||||
if( j > 0 && ( !CNFADrivers[j-1] || CNFADriverPriorities[j-1] < priority ) )
|
|
||||||
{
|
|
||||||
CNFADrivers[j] = CNFADrivers[j-1];
|
|
||||||
CNFADriverNames[j] = CNFADriverNames[j-1];
|
|
||||||
CNFADriverPriorities[j] = CNFADriverPriorities[j-1];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CNFADrivers[j] = fn;
|
|
||||||
CNFADriverNames[j] = strdup( name );
|
|
||||||
CNFADriverPriorities[j] = priority;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CNFADriver * CNFAInit( const char * driver_name, const char * your_name, CNFACBType cb, int reqSPSPlay, int reqSPSRec,
|
|
||||||
int reqChannelsPlay, int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque)
|
|
||||||
{
|
|
||||||
|
|
||||||
#if defined( ANDROID ) || defined( __android__ )
|
|
||||||
//Android can't run static-time code.
|
|
||||||
void REGISTERAndroidCNFA();
|
|
||||||
REGISTERAndroidCNFA();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int i;
|
|
||||||
struct CNFADriver * ret = 0;
|
|
||||||
int minprio = 0;
|
|
||||||
CNFAInitFn * bestinit = 0;
|
|
||||||
if( driver_name == 0 || strlen( driver_name ) == 0 )
|
|
||||||
{
|
|
||||||
//Search for a driver.
|
|
||||||
for( i = 0; i < MAX_CNFA_DRIVERS; i++ )
|
|
||||||
{
|
|
||||||
if( CNFADrivers[i] == 0 )
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if( CNFADriverPriorities[i] > minprio )
|
|
||||||
{
|
|
||||||
minprio = CNFADriverPriorities[i];
|
|
||||||
bestinit = CNFADrivers[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if( bestinit )
|
|
||||||
{
|
|
||||||
ret = (struct CNFADriver *)bestinit( cb, your_name, reqSPSPlay, reqSPSRec, reqChannelsPlay, reqChannelsRec, sugBufferSize, outputSelect, inputSelect, opaque );
|
|
||||||
}
|
|
||||||
if( ret )
|
|
||||||
{
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for( i = 0; i < MAX_CNFA_DRIVERS; i++ )
|
|
||||||
{
|
|
||||||
if( CNFADrivers[i] == 0 )
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if( strcmp( CNFADriverNames[i], driver_name ) == 0 )
|
|
||||||
{
|
|
||||||
return (struct CNFADriver *)CNFADrivers[i]( cb, your_name, reqSPSPlay, reqSPSRec, reqChannelsPlay, reqChannelsRec, sugBufferSize, outputSelect, inputSelect, opaque );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
printf( "CNFA Driver not found.\n" );
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CNFAState( struct CNFADriver * cnfaobject )
|
|
||||||
{
|
|
||||||
if( cnfaobject )
|
|
||||||
{
|
|
||||||
return cnfaobject->StateFn( cnfaobject );
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CNFAClose( struct CNFADriver * cnfaobject )
|
|
||||||
{
|
|
||||||
if( cnfaobject )
|
|
||||||
{
|
|
||||||
cnfaobject->CloseFn( cnfaobject );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
|
@ -1,122 +0,0 @@
|
||||||
//Copyright <>< 2010-2020 Charles Lohr (And other authors as cited)
|
|
||||||
//CNFA is licensed under the MIT/x11, ColorChord or NewBSD Licenses. You choose.
|
|
||||||
//
|
|
||||||
// CN's Platform-agnostic, foundational sound driver subsystem.
|
|
||||||
// Easily output and input sound on a variety of platforms.
|
|
||||||
//
|
|
||||||
// Options:
|
|
||||||
// * #define CNFA_IMPLEMENTATION before this header and it will build all
|
|
||||||
// definitions in.
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef _CNFA_H
|
|
||||||
#define _CNFA_H
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//this #define is per-platform. For instance on Linux, you have ALSA, Pulse and null
|
|
||||||
#define MAX_CNFA_DRIVERS 4
|
|
||||||
|
|
||||||
struct CNFADriver;
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef BUILD_DLL
|
|
||||||
#ifdef WINDOWS
|
|
||||||
#define DllExport __declspec( dllexport )
|
|
||||||
#else
|
|
||||||
#define DllExport extern
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
#define DllExport
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//NOTE: Some drivers have synchronous duplex mode, other drivers will use two different callbacks. If ether is unavailable, it will be NULL.
|
|
||||||
//I.e. if `out` is null, only use in to read. If in is null, only place samples in out.
|
|
||||||
typedef void(*CNFACBType)( struct CNFADriver * sd, short * out, short * in, int framesp, int framesr );
|
|
||||||
|
|
||||||
typedef void*(CNFAInitFn)( CNFACBType cb, const char * your_name, int reqSPSPlay, int reqSPSRec, int reqChannelsPlay, int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque );
|
|
||||||
|
|
||||||
struct CNFADriver
|
|
||||||
{
|
|
||||||
void (*CloseFn)( void * object );
|
|
||||||
int (*StateFn)( void * object );
|
|
||||||
CNFACBType callback;
|
|
||||||
short channelsPlay;
|
|
||||||
short channelsRec;
|
|
||||||
int spsPlay;
|
|
||||||
int spsRec;
|
|
||||||
void * opaque;
|
|
||||||
|
|
||||||
//More fields may exist on a per-sound-driver basis
|
|
||||||
};
|
|
||||||
|
|
||||||
//Accepts:
|
|
||||||
//If DriverName = 0 or empty, will try to find best driver.
|
|
||||||
//
|
|
||||||
// our_source_name is an optional argument, but on some platforms controls the name of your endpoint.
|
|
||||||
// reqSPSPlay = 44100 is guaranteed on many platforms.
|
|
||||||
// reqSPSRec = 44100 is guaranteed on many platforms.
|
|
||||||
// NOTE: Some platforms do not allow SPS play and REC to deviate from each other.
|
|
||||||
// reqChannelsRec = 1 or 2 guaranteed on many platforms.
|
|
||||||
// reqChannelsPlay = 1 or 2 guaranteedon many platforms. NOTE: Some systems require ChannelsPlay == ChannelsRec!
|
|
||||||
// sugBufferSize = No promises.
|
|
||||||
// outputSelect = No standardization, NULL is OK for default.
|
|
||||||
// inputSelect = No standardization, NULL is OK for default.
|
|
||||||
|
|
||||||
DllExport struct CNFADriver * CNFAInit( const char * driver_name, const char * your_name, CNFACBType cb, int reqSPSPlay, int reqSPSRec, int reqChannelsPlay,
|
|
||||||
int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque );
|
|
||||||
|
|
||||||
DllExport int CNFAState( struct CNFADriver * cnfaobject ); //returns bitmask. 1 if mic recording, 2 if play back running, 3 if both running.
|
|
||||||
DllExport void CNFAClose( struct CNFADriver * cnfaobject );
|
|
||||||
|
|
||||||
|
|
||||||
//Called by various sound drivers. Notice priority must be greater than 0. Priority of 0 or less will not register.
|
|
||||||
//This is an internal function. Applications shouldnot call it.
|
|
||||||
void RegCNFADriver( int priority, const char * name, CNFAInitFn * fn );
|
|
||||||
|
|
||||||
#if defined(_MSC_VER) && !defined(__clang__)
|
|
||||||
#define REGISTER_CNFA( cnfadriver, priority, name, function ) \
|
|
||||||
void REGISTER##cnfadriver() { RegCNFADriver( priority, name, function ); }
|
|
||||||
#else
|
|
||||||
#define REGISTER_CNFA( cnfadriver, priority, name, function ) \
|
|
||||||
void __attribute__((constructor)) REGISTER##cnfadriver() { RegCNFADriver( priority, name, function ); }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef __TINYC__
|
|
||||||
#ifndef TCC
|
|
||||||
#define TCC
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef CNFA_IMPLEMENTATION
|
|
||||||
#include "CNFA.c"
|
|
||||||
#include "CNFA_null.c"
|
|
||||||
#if defined(WINDOWS) || defined(WIN32) || defined(WIN64)
|
|
||||||
#include "CNFA_winmm.c"
|
|
||||||
#include "CNFA_wasapi.c"
|
|
||||||
#elif defined( ANDROID ) || defined( __android__ )
|
|
||||||
#include "CNFA_android.c"
|
|
||||||
#elif defined(__NetBSD__) || defined(__sun)
|
|
||||||
#include "CNFA_sun.c"
|
|
||||||
#elif defined(__linux__)
|
|
||||||
#include "CNFA_alsa.c"
|
|
||||||
#if defined(PULSEAUDIO)
|
|
||||||
#include "CNFA_pulse.c"
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -1,374 +0,0 @@
|
||||||
//Copyright 2015-2020 <>< Charles Lohr under the MIT/x11, NewBSD or ColorChord License. You choose.
|
|
||||||
|
|
||||||
#include "CNFA.h"
|
|
||||||
#include "os_generic.h"
|
|
||||||
#include <alsa/asoundlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
struct CNFADriverAlsa
|
|
||||||
{
|
|
||||||
void (*CloseFn)( void * object );
|
|
||||||
int (*StateFn)( void * object );
|
|
||||||
CNFACBType callback;
|
|
||||||
short channelsPlay;
|
|
||||||
short channelsRec;
|
|
||||||
int spsPlay;
|
|
||||||
int spsRec;
|
|
||||||
void * opaque;
|
|
||||||
|
|
||||||
char * devRec;
|
|
||||||
char * devPlay;
|
|
||||||
|
|
||||||
snd_pcm_uframes_t bufsize;
|
|
||||||
og_thread_t threadPlay;
|
|
||||||
og_thread_t threadRec;
|
|
||||||
snd_pcm_t *playback_handle;
|
|
||||||
snd_pcm_t *record_handle;
|
|
||||||
|
|
||||||
char playing;
|
|
||||||
char recording;
|
|
||||||
};
|
|
||||||
|
|
||||||
int CNFAStateAlsa( void * v )
|
|
||||||
{
|
|
||||||
struct CNFADriverAlsa * r = (struct CNFADriverAlsa *)v;
|
|
||||||
return ((r->playing)?2:0) | ((r->recording)?1:0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CloseCNFAAlsa( void * v )
|
|
||||||
{
|
|
||||||
struct CNFADriverAlsa * r = (struct CNFADriverAlsa *)v;
|
|
||||||
if( r )
|
|
||||||
{
|
|
||||||
if( r->playback_handle ) snd_pcm_close (r->playback_handle);
|
|
||||||
if( r->record_handle ) snd_pcm_close (r->record_handle);
|
|
||||||
|
|
||||||
if( r->threadPlay ) OGJoinThread( r->threadPlay );
|
|
||||||
if( r->threadRec ) OGJoinThread( r->threadRec );
|
|
||||||
|
|
||||||
OGUSleep(2000);
|
|
||||||
|
|
||||||
if( r->devRec ) free( r->devRec );
|
|
||||||
if( r->devPlay ) free( r->devPlay );
|
|
||||||
free( r );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int SetHWParams( snd_pcm_t * handle, int * samplerate, short * channels, snd_pcm_uframes_t * bufsize, struct CNFADriverAlsa * a )
|
|
||||||
{
|
|
||||||
int err;
|
|
||||||
int bufs;
|
|
||||||
int dir;
|
|
||||||
snd_pcm_hw_params_t *hw_params;
|
|
||||||
if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
|
|
||||||
fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",
|
|
||||||
snd_strerror (err));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((err = snd_pcm_hw_params_any (handle, hw_params)) < 0) {
|
|
||||||
fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",
|
|
||||||
snd_strerror (err));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((err = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
|
|
||||||
fprintf (stderr, "cannot set access type (%s)\n",
|
|
||||||
snd_strerror (err));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((err = snd_pcm_hw_params_set_format (handle, hw_params, SND_PCM_FORMAT_S16_LE )) < 0) {
|
|
||||||
fprintf (stderr, "cannot set sample format (%s)\n",
|
|
||||||
snd_strerror (err));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((err = snd_pcm_hw_params_set_rate_near (handle, hw_params, (unsigned int*)samplerate, 0)) < 0) {
|
|
||||||
fprintf (stderr, "cannot set sample rate (%s)\n",
|
|
||||||
snd_strerror (err));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((err = snd_pcm_hw_params_set_channels (handle, hw_params, *channels)) < 0) {
|
|
||||||
fprintf (stderr, "cannot set channel count (%s)\n",
|
|
||||||
snd_strerror (err));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
dir = 0;
|
|
||||||
if( (err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, bufsize, &dir)) < 0 )
|
|
||||||
{
|
|
||||||
fprintf( stderr, "cannot set period size. (%s)\n",
|
|
||||||
snd_strerror(err) );
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
//NOTE: This step is critical for low-latency sound.
|
|
||||||
bufs = *bufsize*3;
|
|
||||||
if( (err = snd_pcm_hw_params_set_buffer_size(handle, hw_params, bufs)) < 0 )
|
|
||||||
{
|
|
||||||
fprintf( stderr, "cannot set snd_pcm_hw_params_set_buffer_size size. (%s)\n",
|
|
||||||
snd_strerror(err) );
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if ((err = snd_pcm_hw_params (handle, hw_params)) < 0) {
|
|
||||||
fprintf (stderr, "cannot set parameters (%s)\n",
|
|
||||||
snd_strerror (err));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
snd_pcm_hw_params_free (hw_params);
|
|
||||||
return 0;
|
|
||||||
fail:
|
|
||||||
snd_pcm_hw_params_free (hw_params);
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int SetSWParams( struct CNFADriverAlsa * d, snd_pcm_t * handle, int isrec )
|
|
||||||
{
|
|
||||||
snd_pcm_sw_params_t *sw_params;
|
|
||||||
int err;
|
|
||||||
//Time for software parameters:
|
|
||||||
|
|
||||||
if( !isrec )
|
|
||||||
{
|
|
||||||
if ((err = snd_pcm_sw_params_malloc (&sw_params)) < 0) {
|
|
||||||
fprintf (stderr, "cannot allocate software parameters structure (%s)\n",
|
|
||||||
snd_strerror (err));
|
|
||||||
goto failhard;
|
|
||||||
}
|
|
||||||
if ((err = snd_pcm_sw_params_current (handle, sw_params)) < 0) {
|
|
||||||
fprintf (stderr, "cannot initialize software parameters structure (%s) (%p)\n",
|
|
||||||
snd_strerror (err), handle);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
int buffer_size = d->bufsize*3;
|
|
||||||
int period_size = d->bufsize;
|
|
||||||
printf( "PERIOD: %d BUFFER: %d\n", period_size, buffer_size );
|
|
||||||
|
|
||||||
if ((err = snd_pcm_sw_params_set_avail_min (handle, sw_params, period_size )) < 0) {
|
|
||||||
fprintf (stderr, "cannot set minimum available count (%s)\n",
|
|
||||||
snd_strerror (err));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
//if ((err = snd_pcm_sw_params_set_stop_threshold(handle, sw_params, 512 )) < 0) {
|
|
||||||
// fprintf (stderr, "cannot set minimum available count (%s)\n",
|
|
||||||
// snd_strerror (err));
|
|
||||||
// goto fail;
|
|
||||||
//}
|
|
||||||
if ((err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, buffer_size - period_size )) < 0) {
|
|
||||||
fprintf (stderr, "cannot set minimum available count (%s)\n",
|
|
||||||
snd_strerror (err));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
if ((err = snd_pcm_sw_params (handle, sw_params)) < 0) {
|
|
||||||
fprintf (stderr, "cannot set software parameters (%s)\n",
|
|
||||||
snd_strerror (err));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((err = snd_pcm_prepare (handle)) < 0) {
|
|
||||||
fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
|
|
||||||
snd_strerror (err));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
fail:
|
|
||||||
if( !isrec )
|
|
||||||
{
|
|
||||||
snd_pcm_sw_params_free (sw_params);
|
|
||||||
}
|
|
||||||
failhard:
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void * RecThread( void * v )
|
|
||||||
{
|
|
||||||
struct CNFADriverAlsa * r = (struct CNFADriverAlsa *)v;
|
|
||||||
short samples[r->bufsize * r->channelsRec];
|
|
||||||
snd_pcm_start(r->record_handle);
|
|
||||||
do
|
|
||||||
{
|
|
||||||
int err = snd_pcm_readi( r->record_handle, samples, r->bufsize );
|
|
||||||
if( err < 0 )
|
|
||||||
{
|
|
||||||
fprintf( stderr, "Warning: ALSA Recording Failed\n" );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if( err != r->bufsize )
|
|
||||||
{
|
|
||||||
fprintf( stderr, "Warning: ALSA Recording Underflow\n" );
|
|
||||||
}
|
|
||||||
r->recording = 1;
|
|
||||||
r->callback( (struct CNFADriver *)r, 0, samples, 0, err );
|
|
||||||
} while( 1 );
|
|
||||||
r->recording = 0;
|
|
||||||
fprintf( stderr, "ALSA Recording Stopped\n" );
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void * PlayThread( void * v )
|
|
||||||
{
|
|
||||||
struct CNFADriverAlsa * r = (struct CNFADriverAlsa *)v;
|
|
||||||
short samples[r->bufsize * r->channelsPlay];
|
|
||||||
int err;
|
|
||||||
//int total_avail = snd_pcm_avail(r->playback_handle);
|
|
||||||
|
|
||||||
snd_pcm_start(r->playback_handle);
|
|
||||||
r->callback( (struct CNFADriver *)r, samples, 0, r->bufsize, 0 );
|
|
||||||
err = snd_pcm_writei(r->playback_handle, samples, r->bufsize);
|
|
||||||
|
|
||||||
while( err >= 0 )
|
|
||||||
{
|
|
||||||
// int avail = snd_pcm_avail(r->playback_handle);
|
|
||||||
// printf( "avail: %d\n", avail );
|
|
||||||
r->callback( (struct CNFADriver *)r, samples, 0, r->bufsize, 0 );
|
|
||||||
err = snd_pcm_writei(r->playback_handle, samples, r->bufsize);
|
|
||||||
if( err != r->bufsize )
|
|
||||||
{
|
|
||||||
fprintf( stderr, "Warning: ALSA Playback Overflow\n" );
|
|
||||||
}
|
|
||||||
r->playing = 1;
|
|
||||||
}
|
|
||||||
r->playing = 0;
|
|
||||||
fprintf( stderr, "ALSA Playback Stopped\n" );
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct CNFADriverAlsa * InitALSA( struct CNFADriverAlsa * r )
|
|
||||||
{
|
|
||||||
printf( "CNFA Alsa Init %p %p (%d %d) %d %d\n", r->playback_handle, r->record_handle, r->spsPlay, r->spsRec, r->channelsPlay, r->channelsRec );
|
|
||||||
|
|
||||||
int err;
|
|
||||||
if( r->channelsPlay )
|
|
||||||
{
|
|
||||||
if ((err = snd_pcm_open (&r->playback_handle, r->devPlay?r->devPlay:"default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
|
|
||||||
fprintf (stderr, "cannot open output audio device (%s)\n",
|
|
||||||
snd_strerror (err));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( r->channelsRec )
|
|
||||||
{
|
|
||||||
if ((err = snd_pcm_open (&r->record_handle, r->devRec?r->devRec:"default", SND_PCM_STREAM_CAPTURE, 0)) < 0) {
|
|
||||||
fprintf (stderr, "cannot open input audio device (%s)\n",
|
|
||||||
snd_strerror (err));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printf( "%p %p\n", r->playback_handle, r->record_handle );
|
|
||||||
|
|
||||||
if( r->playback_handle )
|
|
||||||
{
|
|
||||||
if( SetHWParams( r->playback_handle, &r->spsPlay, &r->channelsPlay, &r->bufsize, r ) < 0 )
|
|
||||||
goto fail;
|
|
||||||
if( SetSWParams( r, r->playback_handle, 0 ) < 0 )
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( r->record_handle )
|
|
||||||
{
|
|
||||||
if( SetHWParams( r->record_handle, &r->spsRec, &r->channelsRec, &r->bufsize, r ) < 0 )
|
|
||||||
goto fail;
|
|
||||||
if( SetSWParams( r, r->record_handle, 1 ) < 0 )
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
if( r->playback_handle )
|
|
||||||
{
|
|
||||||
snd_async_handler_t *pcm_callback;
|
|
||||||
//Handle automatically cleaned up when stream closed.
|
|
||||||
err = snd_async_add_pcm_handler(&pcm_callback, r->playback_handle, playback_callback, r);
|
|
||||||
if(err < 0)
|
|
||||||
{
|
|
||||||
printf("Playback callback handler error: %s\n", snd_strerror(err));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( r->record_handle )
|
|
||||||
{
|
|
||||||
snd_async_handler_t *pcm_callback;
|
|
||||||
//Handle automatically cleaned up when stream closed.
|
|
||||||
err = snd_async_add_pcm_handler(&pcm_callback, r->record_handle, record_callback, r);
|
|
||||||
if(err < 0)
|
|
||||||
{
|
|
||||||
printf("Record callback handler error: %s\n", snd_strerror(err));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if( r->playback_handle && r->record_handle )
|
|
||||||
{
|
|
||||||
err = snd_pcm_link ( r->playback_handle, r->record_handle );
|
|
||||||
if(err < 0)
|
|
||||||
{
|
|
||||||
printf("snd_pcm_link error: %s\n", snd_strerror(err));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( r->playback_handle )
|
|
||||||
{
|
|
||||||
r->threadPlay = OGCreateThread( PlayThread, r );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( r->record_handle )
|
|
||||||
{
|
|
||||||
r->threadRec = OGCreateThread( RecThread, r );
|
|
||||||
}
|
|
||||||
|
|
||||||
printf( "CNFA Alsa Init Out -> %p %p (%d %d) %d %d\n", r->playback_handle, r->record_handle, r->spsPlay, r->spsRec, r->channelsPlay, r->channelsRec );
|
|
||||||
|
|
||||||
return r;
|
|
||||||
|
|
||||||
fail:
|
|
||||||
if( r )
|
|
||||||
{
|
|
||||||
if( r->playback_handle ) snd_pcm_close (r->playback_handle);
|
|
||||||
if( r->record_handle ) snd_pcm_close (r->record_handle);
|
|
||||||
free( r );
|
|
||||||
}
|
|
||||||
fprintf( stderr, "Error: ALSA failed to start.\n" );
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void * InitALSADriver( CNFACBType cb, const char * your_name, int reqSPSPlay, int reqSPSRec, int reqChannelsPlay, int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque )
|
|
||||||
{
|
|
||||||
struct CNFADriverAlsa * r = (struct CNFADriverAlsa *)malloc( sizeof( struct CNFADriverAlsa ) );
|
|
||||||
|
|
||||||
r->CloseFn = CloseCNFAAlsa;
|
|
||||||
r->StateFn = CNFAStateAlsa;
|
|
||||||
r->callback = cb;
|
|
||||||
r->opaque = opaque;
|
|
||||||
r->spsPlay = reqSPSPlay;
|
|
||||||
r->spsRec = reqSPSRec;
|
|
||||||
r->channelsPlay = reqChannelsPlay;
|
|
||||||
r->channelsRec = reqChannelsRec;
|
|
||||||
|
|
||||||
r->devRec = (inputSelect)?strdup(inputSelect):0;
|
|
||||||
r->devPlay = (outputSelect)?strdup(outputSelect):0;
|
|
||||||
|
|
||||||
r->playback_handle = 0;
|
|
||||||
r->record_handle = 0;
|
|
||||||
r->bufsize = sugBufferSize;
|
|
||||||
|
|
||||||
return InitALSA(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
REGISTER_CNFA( ALSA, 10, "ALSA", InitALSADriver );
|
|
||||||
|
|
|
@ -1,319 +0,0 @@
|
||||||
//Copyright 2019-2020 <>< Charles Lohr under the ColorChord License, MIT/x11 license or NewBSD Licenses.
|
|
||||||
// This was originally to be used with rawdrawandroid
|
|
||||||
|
|
||||||
#include "CNFA.h"
|
|
||||||
#include "os_generic.h"
|
|
||||||
#include <pthread.h> //Using android threads not os_generic threads.
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
//based on https://github.com/android/ndk-samples/blob/master/native-audio/app/src/main/cpp/native-audio-jni.c
|
|
||||||
|
|
||||||
// for native audio
|
|
||||||
#include <SLES/OpenSLES.h>
|
|
||||||
#include <SLES/OpenSLES_Android.h>
|
|
||||||
|
|
||||||
#include <android_native_app_glue.h>
|
|
||||||
#include <jni.h>
|
|
||||||
#include <native_activity.h>
|
|
||||||
|
|
||||||
struct CNFADriverAndroid
|
|
||||||
{
|
|
||||||
//Standard header - must remain.
|
|
||||||
void (*CloseFn)( void * object );
|
|
||||||
int (*StateFn)( void * object );
|
|
||||||
CNFACBType callback;
|
|
||||||
short channelsPlay;
|
|
||||||
short channelsRec;
|
|
||||||
int spsPlay;
|
|
||||||
int spsRec;
|
|
||||||
void * opaque;
|
|
||||||
|
|
||||||
int buffsz;
|
|
||||||
|
|
||||||
SLObjectItf engineObject;
|
|
||||||
SLEngineItf engineEngine;
|
|
||||||
SLRecordItf recorderRecord;
|
|
||||||
SLObjectItf recorderObject;
|
|
||||||
|
|
||||||
SLPlayItf playerPlay;
|
|
||||||
SLObjectItf playerObject;
|
|
||||||
SLObjectItf outputMixObject;
|
|
||||||
|
|
||||||
SLAndroidSimpleBufferQueueItf recorderBufferQueue;
|
|
||||||
SLAndroidSimpleBufferQueueItf playerBufferQueue;
|
|
||||||
//unsigned recorderSize;
|
|
||||||
|
|
||||||
int recorderBufferSizeBytes;
|
|
||||||
int playerBufferSizeBytes;
|
|
||||||
short * recorderBuffer;
|
|
||||||
short * playerBuffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
|
|
||||||
{
|
|
||||||
struct CNFADriverAndroid * r = (struct CNFADriverAndroid*)context;
|
|
||||||
r->callback( (struct CNFADriver*)r, 0, r->recorderBuffer, 0, r->buffsz/(sizeof(short)*r->channelsRec) );
|
|
||||||
(*r->recorderBufferQueue)->Enqueue( r->recorderBufferQueue, r->recorderBuffer, r->recorderBufferSizeBytes/(r->channelsRec*sizeof(short)) );
|
|
||||||
}
|
|
||||||
|
|
||||||
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
|
|
||||||
{
|
|
||||||
struct CNFADriverAndroid * r = (struct CNFADriverAndroid*)context;
|
|
||||||
r->callback( (struct CNFADriver*)r, r->playerBuffer, 0, r->buffsz/(sizeof(short)*r->channelsPlay), 0 );
|
|
||||||
(*r->playerBufferQueue)->Enqueue( r->playerBufferQueue, r->playerBuffer, r->playerBufferSizeBytes/(r->channelsPlay*sizeof(short)));
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct CNFADriverAndroid* InitAndroidDriver( struct CNFADriverAndroid * r )
|
|
||||||
{
|
|
||||||
SLresult result;
|
|
||||||
printf( "Starting InitAndroidDriver\n" );
|
|
||||||
|
|
||||||
// create engine
|
|
||||||
result = slCreateEngine(&r->engineObject, 0, NULL, 0, NULL, NULL);
|
|
||||||
assert(SL_RESULT_SUCCESS == result);
|
|
||||||
(void)result;
|
|
||||||
|
|
||||||
// realize the engine
|
|
||||||
result = (*r->engineObject)->Realize(r->engineObject, SL_BOOLEAN_FALSE);
|
|
||||||
assert(SL_RESULT_SUCCESS == result);
|
|
||||||
(void)result;
|
|
||||||
|
|
||||||
// get the engine interface, which is needed in order to create other objects
|
|
||||||
result = (*r->engineObject)->GetInterface(r->engineObject, SL_IID_ENGINE, &r->engineEngine);
|
|
||||||
assert(SL_RESULT_SUCCESS == result);
|
|
||||||
(void)result;
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
if( r->channelsPlay )
|
|
||||||
{
|
|
||||||
printf("create output mix");
|
|
||||||
|
|
||||||
SLDataFormat_PCM format_pcm ={
|
|
||||||
SL_DATAFORMAT_PCM,
|
|
||||||
r->channelsPlay,
|
|
||||||
r->spsPlay*1000,
|
|
||||||
SL_PCMSAMPLEFORMAT_FIXED_16,
|
|
||||||
SL_PCMSAMPLEFORMAT_FIXED_16,
|
|
||||||
(r->channelsPlay==1)?SL_SPEAKER_FRONT_CENTER:3,
|
|
||||||
SL_BYTEORDER_LITTLEENDIAN,
|
|
||||||
};
|
|
||||||
SLDataLocator_AndroidSimpleBufferQueue loc_bq_play = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
|
|
||||||
SLDataSource source = {&loc_bq_play, &format_pcm};
|
|
||||||
const SLInterfaceID ids[1] = {SL_IID_VOLUME};
|
|
||||||
const SLboolean req[1] = {SL_BOOLEAN_TRUE};
|
|
||||||
const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
|
|
||||||
|
|
||||||
result = (*r->engineEngine)->CreateOutputMix(r->engineEngine, &r->outputMixObject, 0, ids, req);
|
|
||||||
result = (*r->outputMixObject)->Realize(r->outputMixObject, SL_BOOLEAN_FALSE);
|
|
||||||
|
|
||||||
SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, r->outputMixObject };
|
|
||||||
SLDataSink sink;
|
|
||||||
sink.pFormat = &format_pcm;
|
|
||||||
sink.pLocator = &loc_outmix;
|
|
||||||
|
|
||||||
// create audio player
|
|
||||||
result = (*r->engineEngine)->CreateAudioPlayer(r->engineEngine, &r->playerObject, &source, &sink, 1, id, req);
|
|
||||||
if (SL_RESULT_SUCCESS != result) {
|
|
||||||
printf( "CreateAudioPlayer failed\n" );
|
|
||||||
return JNI_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// realize the audio player
|
|
||||||
result = (*r->playerObject)->Realize(r->playerObject, SL_BOOLEAN_FALSE);
|
|
||||||
if (SL_RESULT_SUCCESS != result) {
|
|
||||||
printf( "AudioPlayer Realize failed: %d\n", result );
|
|
||||||
return JNI_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the player interface
|
|
||||||
result = (*r->playerObject)->GetInterface(r->playerObject, SL_IID_PLAY, &r->playerPlay);
|
|
||||||
assert(SL_RESULT_SUCCESS == result);
|
|
||||||
(void)result;
|
|
||||||
|
|
||||||
// get the buffer queue interface
|
|
||||||
result = (*r->playerObject)->GetInterface(r->playerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &r->playerBufferQueue);
|
|
||||||
assert(SL_RESULT_SUCCESS == result);
|
|
||||||
(void)result;
|
|
||||||
|
|
||||||
// register callback on the buffer queue
|
|
||||||
result = (*r->playerBufferQueue)->RegisterCallback(r->playerBufferQueue, bqPlayerCallback, r);
|
|
||||||
assert(SL_RESULT_SUCCESS == result);
|
|
||||||
(void)result;
|
|
||||||
|
|
||||||
printf( "===================== Player init ok.\n" );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( r->channelsRec )
|
|
||||||
{
|
|
||||||
// configure audio source
|
|
||||||
SLDataLocator_IODevice loc_devI = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};
|
|
||||||
SLDataSource audioSrc = {&loc_devI, NULL};
|
|
||||||
|
|
||||||
// configure audio sink
|
|
||||||
SLDataFormat_PCM format_pcm ={
|
|
||||||
SL_DATAFORMAT_PCM,
|
|
||||||
r->channelsRec,
|
|
||||||
r->spsRec*1000,
|
|
||||||
SL_PCMSAMPLEFORMAT_FIXED_16,
|
|
||||||
SL_PCMSAMPLEFORMAT_FIXED_16,
|
|
||||||
(r->channelsRec==1)?SL_SPEAKER_FRONT_CENTER:3,
|
|
||||||
SL_BYTEORDER_LITTLEENDIAN,
|
|
||||||
};
|
|
||||||
SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
|
|
||||||
SLDataSink audioSnk = {&loc_bq, &format_pcm};
|
|
||||||
|
|
||||||
|
|
||||||
const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
|
|
||||||
const SLboolean req[1] = {SL_BOOLEAN_TRUE};
|
|
||||||
|
|
||||||
result = (*r->engineEngine)->CreateAudioRecorder(r->engineEngine, &r->recorderObject, &audioSrc, &audioSnk, 1, id, req);
|
|
||||||
if (SL_RESULT_SUCCESS != result) {
|
|
||||||
printf( "CreateAudioRecorder failed\n" );
|
|
||||||
return JNI_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// realize the audio recorder
|
|
||||||
result = (*r->recorderObject)->Realize(r->recorderObject, SL_BOOLEAN_FALSE);
|
|
||||||
if (SL_RESULT_SUCCESS != result) {
|
|
||||||
printf( "AudioRecorder Realize failed: %d\n", result );
|
|
||||||
return JNI_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the record interface
|
|
||||||
result = (*r->recorderObject)->GetInterface(r->recorderObject, SL_IID_RECORD, &r->recorderRecord);
|
|
||||||
assert(SL_RESULT_SUCCESS == result);
|
|
||||||
(void)result;
|
|
||||||
|
|
||||||
// get the buffer queue interface
|
|
||||||
result = (*r->recorderObject)->GetInterface(r->recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &r->recorderBufferQueue);
|
|
||||||
assert(SL_RESULT_SUCCESS == result);
|
|
||||||
(void)result;
|
|
||||||
|
|
||||||
// register callback on the buffer queue
|
|
||||||
result = (*r->recorderBufferQueue)->RegisterCallback(r->recorderBufferQueue, bqRecorderCallback, r);
|
|
||||||
assert(SL_RESULT_SUCCESS == result);
|
|
||||||
(void)result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if( r->playerPlay )
|
|
||||||
{
|
|
||||||
result = (*r->playerPlay)->SetPlayState(r->playerPlay, SL_PLAYSTATE_STOPPED);
|
|
||||||
assert(SL_RESULT_SUCCESS == result); (void)result;
|
|
||||||
result = (*r->playerBufferQueue)->Clear(r->playerBufferQueue);
|
|
||||||
assert(SL_RESULT_SUCCESS == result); (void)result;
|
|
||||||
r->playerBuffer = malloc( r->playerBufferSizeBytes );
|
|
||||||
memset( r->playerBuffer, 0, r->playerBufferSizeBytes );
|
|
||||||
result = (*r->playerBufferQueue)->Enqueue(r->playerBufferQueue, r->playerBuffer, r->playerBufferSizeBytes );
|
|
||||||
assert(SL_RESULT_SUCCESS == result); (void)result;
|
|
||||||
result = (*r->playerPlay)->SetPlayState(r->playerPlay, SL_PLAYSTATE_PLAYING);
|
|
||||||
assert(SL_RESULT_SUCCESS == result); (void)result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if( r->recorderRecord )
|
|
||||||
{
|
|
||||||
result = (*r->recorderRecord)->SetRecordState(r->recorderRecord, SL_RECORDSTATE_STOPPED);
|
|
||||||
assert(SL_RESULT_SUCCESS == result); (void)result;
|
|
||||||
result = (*r->recorderBufferQueue)->Clear(r->recorderBufferQueue);
|
|
||||||
assert(SL_RESULT_SUCCESS == result); (void)result;
|
|
||||||
// the buffer is not valid for playback yet
|
|
||||||
|
|
||||||
r->recorderBuffer = malloc( r->recorderBufferSizeBytes );
|
|
||||||
|
|
||||||
// enqueue an empty buffer to be filled by the recorder
|
|
||||||
// (for streaming recording, we would enqueue at least 2 empty buffers to start things off)
|
|
||||||
result = (*r->recorderBufferQueue)->Enqueue(r->recorderBufferQueue, r->recorderBuffer, r->recorderBufferSizeBytes );
|
|
||||||
// the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT,
|
|
||||||
// which for this code example would indicate a programming error
|
|
||||||
assert(SL_RESULT_SUCCESS == result); (void)result;
|
|
||||||
|
|
||||||
// start recording
|
|
||||||
result = (*r->recorderRecord)->SetRecordState(r->recorderRecord, SL_RECORDSTATE_RECORDING);
|
|
||||||
assert(SL_RESULT_SUCCESS == result); (void)result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
printf( "Complete Init Sound Android\n" );
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CNFAStateAndroid( void * v )
|
|
||||||
{
|
|
||||||
struct CNFADriverAndroid * soundobject = (struct CNFADriverAndroid *)v;
|
|
||||||
return ((soundobject->recorderObject)?1:0) | ((soundobject->playerObject)?2:0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CloseCNFAAndroid( void * v )
|
|
||||||
{
|
|
||||||
struct CNFADriverAndroid * r = (struct CNFADriverAndroid *)v;
|
|
||||||
// destroy audio recorder object, and invalidate all associated interfaces
|
|
||||||
if (r->recorderObject != NULL) {
|
|
||||||
(*r->recorderObject)->Destroy(r->recorderObject);
|
|
||||||
r->recorderObject = NULL;
|
|
||||||
r->recorderRecord = NULL;
|
|
||||||
r->recorderBufferQueue = NULL;
|
|
||||||
if( r->recorderBuffer ) free( r->recorderBuffer );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (r->playerObject != NULL) {
|
|
||||||
(*r->playerObject)->Destroy(r->playerObject);
|
|
||||||
r->playerObject = NULL;
|
|
||||||
r->playerPlay = NULL;
|
|
||||||
r->playerBufferQueue = NULL;
|
|
||||||
if( r->playerBuffer ) free( r->playerBuffer );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// destroy engine object, and invalidate all associated interfaces
|
|
||||||
if (r->engineObject != NULL) {
|
|
||||||
(*r->engineObject)->Destroy(r->engineObject);
|
|
||||||
r->engineObject = NULL;
|
|
||||||
r->engineEngine = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int AndroidHasPermissions(const char* perm_name);
|
|
||||||
void AndroidRequestAppPermissions(const char * perm);
|
|
||||||
|
|
||||||
|
|
||||||
void * InitCNFAAndroid( CNFACBType cb, const char * your_name, int reqSPSPlay, int reqSPSRec, int reqChannelsPlay, int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque )
|
|
||||||
{
|
|
||||||
struct CNFADriverAndroid * r = (struct CNFADriverAndroid *)malloc( sizeof( struct CNFADriverAndroid ) );
|
|
||||||
memset( r, 0, sizeof( *r) );
|
|
||||||
r->CloseFn = CloseCNFAAndroid;
|
|
||||||
r->StateFn = CNFAStateAndroid;
|
|
||||||
r->callback = cb;
|
|
||||||
r->opaque = opaque;
|
|
||||||
r->channelsPlay = reqChannelsPlay;
|
|
||||||
r->channelsRec = reqChannelsRec;
|
|
||||||
r->spsRec = reqSPSRec;
|
|
||||||
r->spsPlay = reqSPSPlay;
|
|
||||||
|
|
||||||
r->recorderBufferSizeBytes = sugBufferSize * 2 * r->channelsRec;
|
|
||||||
r->playerBufferSizeBytes = sugBufferSize * 2 * r->channelsPlay;
|
|
||||||
|
|
||||||
int hasperm = AndroidHasPermissions( "RECORD_AUDIO" );
|
|
||||||
if( !hasperm )
|
|
||||||
{
|
|
||||||
AndroidRequestAppPermissions( "RECORD_AUDIO" );
|
|
||||||
}
|
|
||||||
|
|
||||||
r->buffsz = sugBufferSize;
|
|
||||||
|
|
||||||
return InitAndroidDriver(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Tricky: On Android, this can't actually run before main. Have to manually execute it.
|
|
||||||
|
|
||||||
REGISTER_CNFA( AndroidCNFA, 10, "ANDROID", InitCNFAAndroid );
|
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
//Copyright 2015-2020 <>< Charles Lohr under the ColorChord License.
|
|
||||||
|
|
||||||
#include "CNFA.h"
|
|
||||||
#include "os_generic.h"
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
struct CNFADriverNull
|
|
||||||
{
|
|
||||||
void (*CloseFn)( void * object );
|
|
||||||
int (*StateFn)( void * object );
|
|
||||||
CNFACBType callback;
|
|
||||||
short channelsPlay;
|
|
||||||
short channelsRec;
|
|
||||||
int spsPlay;
|
|
||||||
int spsRec;
|
|
||||||
void * opaque;
|
|
||||||
};
|
|
||||||
|
|
||||||
void CloseCNFANull( void * object )
|
|
||||||
{
|
|
||||||
free( object );
|
|
||||||
}
|
|
||||||
|
|
||||||
int CNFAStateNull( void * object )
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void * InitCNFANull( CNFACBType cb, const char * your_name, int reqSPSPlay, int reqSPSRec, int reqChannelsPlay, int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque )
|
|
||||||
{
|
|
||||||
struct CNFADriverNull * r = (struct CNFADriverNull *)malloc( sizeof( struct CNFADriverNull ) );
|
|
||||||
r->CloseFn = CloseCNFANull;
|
|
||||||
r->StateFn = CNFAStateNull;
|
|
||||||
r->callback = cb;
|
|
||||||
r->spsPlay = reqSPSPlay;
|
|
||||||
r->spsRec = reqSPSRec;
|
|
||||||
r->opaque = opaque;
|
|
||||||
r->channelsPlay = reqChannelsPlay;
|
|
||||||
r->channelsRec = reqChannelsRec;
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
REGISTER_CNFA( NullCNFA, 1, "NULL", InitCNFANull );
|
|
||||||
|
|
|
@ -1,308 +0,0 @@
|
||||||
//Copyright 2015-2020 <>< Charles Lohr under the MIT/x11, NewBSD or ColorChord License. You choose.
|
|
||||||
|
|
||||||
//This file is really rough. Full duplex doesn't seem to work hardly at all.
|
|
||||||
|
|
||||||
|
|
||||||
#include "CNFA.h"
|
|
||||||
#include "os_generic.h"
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#include <pulse/simple.h>
|
|
||||||
#include <pulse/pulseaudio.h>
|
|
||||||
#include <pulse/error.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#define BUFFERSETS 3
|
|
||||||
|
|
||||||
|
|
||||||
//from http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/Samples/AsyncPlayback/
|
|
||||||
//also http://maemo.org/api_refs/5.0/5.0-final/pulseaudio/pacat_8c-example.html
|
|
||||||
|
|
||||||
|
|
||||||
struct CNFADriverPulse
|
|
||||||
{
|
|
||||||
void (*CloseFn)( void * object );
|
|
||||||
int (*StateFn)( void * object );
|
|
||||||
CNFACBType callback;
|
|
||||||
short channelsPlay;
|
|
||||||
short channelsRec;
|
|
||||||
int spsPlay;
|
|
||||||
int spsRec;
|
|
||||||
void * opaque;
|
|
||||||
|
|
||||||
char * sourceNamePlay;
|
|
||||||
char * sourceNameRec;
|
|
||||||
|
|
||||||
og_thread_t thread;
|
|
||||||
pa_stream * play;
|
|
||||||
pa_stream * rec;
|
|
||||||
pa_context * pa_ctx;
|
|
||||||
pa_mainloop *pa_ml;
|
|
||||||
int pa_ready;
|
|
||||||
int buffer;
|
|
||||||
//More fields may exist on a per-sound-driver basis
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int CNFAStatePulse( void * v )
|
|
||||||
{
|
|
||||||
struct CNFADriverPulse * soundobject = (struct CNFADriverPulse *)v;
|
|
||||||
return ((soundobject->play)?2:0) | ((soundobject->rec)?1:0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CloseCNFAPulse( void * v )
|
|
||||||
{
|
|
||||||
struct CNFADriverPulse * r = (struct CNFADriverPulse *)v;
|
|
||||||
if( r )
|
|
||||||
{
|
|
||||||
if( r->play )
|
|
||||||
{
|
|
||||||
pa_stream_unref (r->play);
|
|
||||||
r->play = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( r->rec )
|
|
||||||
{
|
|
||||||
pa_stream_unref (r->rec);
|
|
||||||
r->rec = 0;
|
|
||||||
}
|
|
||||||
OGUSleep(2000);
|
|
||||||
OGCancelThread( r->thread );
|
|
||||||
|
|
||||||
|
|
||||||
if( r->sourceNamePlay ) free( r->sourceNamePlay );
|
|
||||||
if( r->sourceNameRec ) free( r->sourceNameRec );
|
|
||||||
free( r );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void * CNFAThread( void * v )
|
|
||||||
{
|
|
||||||
struct CNFADriverPulse * r = (struct CNFADriverPulse*)v;
|
|
||||||
while(1)
|
|
||||||
{
|
|
||||||
pa_mainloop_iterate( r->pa_ml, 1, NULL );
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void stream_request_cb(pa_stream *s, size_t length, void *userdata)
|
|
||||||
{
|
|
||||||
struct CNFADriverPulse * r = (struct CNFADriverPulse*)userdata;
|
|
||||||
if( !r->play )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
short bufp[length*r->channelsPlay/sizeof(short)];
|
|
||||||
r->callback( (struct CNFADriver*)r, bufp, 0, length/(sizeof(short)*r->channelsPlay), 0 );
|
|
||||||
pa_stream_write(r->play, &bufp[0], length, NULL, 0LL, PA_SEEK_RELATIVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void stream_record_cb(pa_stream *s, size_t length, void *userdata)
|
|
||||||
{
|
|
||||||
struct CNFADriverPulse * r = (struct CNFADriverPulse*)userdata;
|
|
||||||
|
|
||||||
uint16_t * bufr;
|
|
||||||
|
|
||||||
if (pa_stream_peek(r->rec, (const void**)&bufr, &length) < 0) {
|
|
||||||
fprintf(stderr, ("pa_stream_peek() failed: %s\n"), pa_strerror(pa_context_errno(r->pa_ctx)));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
short * buffer;
|
|
||||||
buffer = (short*)pa_xmalloc(length);
|
|
||||||
memcpy(buffer, bufr, length);
|
|
||||||
pa_stream_drop(r->rec);
|
|
||||||
r->callback( (struct CNFADriver*)r, 0, buffer, 0, length/(sizeof(short)*r->channelsRec) );
|
|
||||||
pa_xfree( buffer );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static void stream_underflow_cb(pa_stream *s, void *userdata) {
|
|
||||||
printf("underflow\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void pa_state_cb(pa_context *c, void *userdata) {
|
|
||||||
pa_context_state_t state;
|
|
||||||
int *pa_ready = (int*)userdata;
|
|
||||||
state = pa_context_get_state(c);
|
|
||||||
switch (state) {
|
|
||||||
// These are just here for reference
|
|
||||||
case PA_CONTEXT_UNCONNECTED:
|
|
||||||
case PA_CONTEXT_CONNECTING:
|
|
||||||
case PA_CONTEXT_AUTHORIZING:
|
|
||||||
case PA_CONTEXT_SETTING_NAME:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
case PA_CONTEXT_FAILED:
|
|
||||||
case PA_CONTEXT_TERMINATED:
|
|
||||||
*pa_ready = 2;
|
|
||||||
break;
|
|
||||||
case PA_CONTEXT_READY:
|
|
||||||
*pa_ready = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void * InitCNFAPulse( CNFACBType cb, const char * your_name, int reqSPSPlay, int reqSPSRec, int reqChannelsPlay, int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque )
|
|
||||||
{
|
|
||||||
static pa_buffer_attr bufattr;
|
|
||||||
static pa_sample_spec ss;
|
|
||||||
int error;
|
|
||||||
pa_mainloop_api *pa_mlapi;
|
|
||||||
const char * title = your_name;
|
|
||||||
|
|
||||||
struct CNFADriverPulse * r = (struct CNFADriverPulse *)malloc( sizeof( struct CNFADriverPulse ) );
|
|
||||||
|
|
||||||
r->pa_ml = pa_mainloop_new();
|
|
||||||
if( !r->pa_ml )
|
|
||||||
{
|
|
||||||
fprintf( stderr, "Failed to initialize pa_mainloop_new()\n" );
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
pa_mlapi = pa_mainloop_get_api(r->pa_ml);
|
|
||||||
if( !pa_mlapi )
|
|
||||||
{
|
|
||||||
fprintf( stderr, "Failed to initialize pa_mainloop_get_api()\n" );
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
r->pa_ctx = pa_context_new(pa_mlapi, title );
|
|
||||||
pa_context_connect(r->pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);
|
|
||||||
|
|
||||||
//TODO: pa_context_set_state_callback
|
|
||||||
|
|
||||||
r->CloseFn = CloseCNFAPulse;
|
|
||||||
r->StateFn = CNFAStatePulse;
|
|
||||||
r->callback = cb;
|
|
||||||
r->opaque = opaque;
|
|
||||||
r->spsPlay = reqSPSPlay;
|
|
||||||
r->spsRec = reqSPSRec;
|
|
||||||
r->channelsPlay = reqChannelsPlay;
|
|
||||||
r->channelsRec = reqChannelsRec;
|
|
||||||
r->sourceNamePlay = outputSelect?strdup(outputSelect):0;
|
|
||||||
r->sourceNameRec = inputSelect?strdup(inputSelect):0;
|
|
||||||
|
|
||||||
r->play = 0;
|
|
||||||
r->rec = 0;
|
|
||||||
r->buffer = sugBufferSize;
|
|
||||||
|
|
||||||
printf ("Pulse: from: [O/I] %s/%s (%s) / (%d,%d)x(%d,%d) (%d)\n", r->sourceNamePlay, r->sourceNameRec, title, r->spsPlay, r->spsRec, r->channelsPlay, r->channelsRec, r->buffer );
|
|
||||||
|
|
||||||
memset( &ss, 0, sizeof( ss ) );
|
|
||||||
|
|
||||||
ss.format = PA_SAMPLE_S16NE;
|
|
||||||
|
|
||||||
r->pa_ready = 0;
|
|
||||||
pa_context_set_state_callback(r->pa_ctx, pa_state_cb, &r->pa_ready);
|
|
||||||
|
|
||||||
while (r->pa_ready == 0)
|
|
||||||
{
|
|
||||||
pa_mainloop_iterate(r->pa_ml, 1, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
int bufbytes = r->buffer * sizeof(short) * r->channelsRec;
|
|
||||||
|
|
||||||
if( r->channelsPlay )
|
|
||||||
{
|
|
||||||
ss.channels = r->channelsPlay;
|
|
||||||
ss.rate = r->spsPlay;
|
|
||||||
|
|
||||||
if (!(r->play = pa_stream_new(r->pa_ctx, "Play", &ss, NULL))) {
|
|
||||||
error = -3; //XXX ??? TODO
|
|
||||||
fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
pa_stream_set_underflow_callback(r->play, stream_underflow_cb, NULL);
|
|
||||||
pa_stream_set_write_callback(r->play, stream_request_cb, r );
|
|
||||||
|
|
||||||
bufattr.fragsize = (uint32_t)-1;
|
|
||||||
bufattr.maxlength = bufbytes*3; //XXX TODO Consider making this -1
|
|
||||||
bufattr.minreq = 0;
|
|
||||||
bufattr.prebuf = (uint32_t)-1;
|
|
||||||
bufattr.tlength = bufbytes*3;
|
|
||||||
int ret = pa_stream_connect_playback(r->play, r->sourceNamePlay, &bufattr,
|
|
||||||
// PA_STREAM_INTERPOLATE_TIMING
|
|
||||||
// |PA_STREAM_ADJUST_LATENCY //Some servers don't like the adjust_latency flag.
|
|
||||||
// |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL);
|
|
||||||
PA_STREAM_NOFLAGS, NULL, NULL );
|
|
||||||
if( ret < 0 )
|
|
||||||
{
|
|
||||||
fprintf(stderr, __FILE__": (PLAY) pa_stream_connect_playback() failed: %s\n", pa_strerror(ret));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if( r->channelsRec )
|
|
||||||
{
|
|
||||||
ss.channels = r->channelsRec;
|
|
||||||
ss.rate = r->spsRec;
|
|
||||||
|
|
||||||
if (!(r->rec = pa_stream_new(r->pa_ctx, "Record", &ss, NULL))) {
|
|
||||||
error = -3; //XXX ??? TODO
|
|
||||||
fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
pa_stream_set_read_callback(r->rec, stream_record_cb, r );
|
|
||||||
|
|
||||||
bufattr.fragsize = bufbytes;
|
|
||||||
bufattr.maxlength = (uint32_t)-1;//(uint32_t)-1; //XXX: Todo, should this be low?
|
|
||||||
bufattr.minreq = bufbytes;
|
|
||||||
bufattr.prebuf = (uint32_t)-1;
|
|
||||||
bufattr.tlength = bufbytes*3;
|
|
||||||
int ret = pa_stream_connect_record(r->rec, r->sourceNameRec, &bufattr,
|
|
||||||
// PA_STREAM_INTERPOLATE_TIMING
|
|
||||||
PA_STREAM_ADJUST_LATENCY //Some servers don't like the adjust_latency flag.
|
|
||||||
// PA_STREAM_AUTO_TIMING_UPDATE
|
|
||||||
// PA_STREAM_NOFLAGS
|
|
||||||
);
|
|
||||||
|
|
||||||
printf( "PA REC RES: %d\n", ret );
|
|
||||||
|
|
||||||
if( ret < 0 )
|
|
||||||
{
|
|
||||||
fprintf(stderr, __FILE__": (REC) pa_stream_connect_playback() failed: %s\n", pa_strerror(ret));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printf( "Pulse initialized.\n" );
|
|
||||||
|
|
||||||
|
|
||||||
r->thread = OGCreateThread( CNFAThread, r );
|
|
||||||
|
|
||||||
|
|
||||||
if( r->play )
|
|
||||||
{
|
|
||||||
stream_request_cb( r->play, bufbytes, r );
|
|
||||||
stream_request_cb( r->play, bufbytes, r );
|
|
||||||
}
|
|
||||||
|
|
||||||
return r;
|
|
||||||
|
|
||||||
fail:
|
|
||||||
if( r )
|
|
||||||
{
|
|
||||||
if( r->play ) pa_xfree (r->play);
|
|
||||||
if( r->rec ) pa_xfree (r->rec);
|
|
||||||
free( r );
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
REGISTER_CNFA( PulseCNFA, 11, "PULSE", InitCNFAPulse );
|
|
||||||
|
|
||||||
|
|
|
@ -1,275 +0,0 @@
|
||||||
//Copyright 2015-2020 <>< Charles Lohr under the MIT/x11, NewBSD or ColorChord License. You choose.
|
|
||||||
|
|
||||||
#include "CNFA.h"
|
|
||||||
#include "os_generic.h"
|
|
||||||
#include <sys/audioio.h>
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
|
|
||||||
struct CNFADriverSun
|
|
||||||
{
|
|
||||||
void (*CloseFn)( void * object );
|
|
||||||
int (*StateFn)( void * object );
|
|
||||||
CNFACBType callback;
|
|
||||||
short channelsPlay;
|
|
||||||
short channelsRec;
|
|
||||||
int spsPlay;
|
|
||||||
int spsRec;
|
|
||||||
void * opaque;
|
|
||||||
|
|
||||||
char * devRec;
|
|
||||||
char * devPlay;
|
|
||||||
|
|
||||||
short * samplesRec;
|
|
||||||
short * samplesPlay;
|
|
||||||
|
|
||||||
og_thread_t threadPlay;
|
|
||||||
og_thread_t threadRec;
|
|
||||||
int bufsize;
|
|
||||||
int playback_handle;
|
|
||||||
int record_handle;
|
|
||||||
|
|
||||||
char playing;
|
|
||||||
char recording;
|
|
||||||
};
|
|
||||||
|
|
||||||
int CNFAStateSun( void * v )
|
|
||||||
{
|
|
||||||
struct CNFADriverSun * r = (struct CNFADriverSun *)v;
|
|
||||||
return ((r->playing)?2:0) | ((r->recording)?1:0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CloseCNFASun( void * v )
|
|
||||||
{
|
|
||||||
struct CNFADriverSun * r = (struct CNFADriverSun *)v;
|
|
||||||
if( r )
|
|
||||||
{
|
|
||||||
if( r->playback_handle != -1 ) close (r->playback_handle);
|
|
||||||
if( r->record_handle != -1 ) close (r->record_handle);
|
|
||||||
|
|
||||||
if( r->threadPlay ) OGJoinThread( r->threadPlay );
|
|
||||||
if( r->threadRec ) OGJoinThread( r->threadRec );
|
|
||||||
|
|
||||||
OGUSleep(2000);
|
|
||||||
|
|
||||||
free( r->devRec );
|
|
||||||
free( r->devPlay );
|
|
||||||
free( r->samplesRec );
|
|
||||||
free( r->samplesPlay );
|
|
||||||
free( r );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void * RecThread( void * v )
|
|
||||||
{
|
|
||||||
struct CNFADriverSun * r = (struct CNFADriverSun *)v;
|
|
||||||
size_t nbytes = r->bufsize * (2 * r->channelsRec);
|
|
||||||
do
|
|
||||||
{
|
|
||||||
int nread = read( r->record_handle, r->samplesRec, nbytes );
|
|
||||||
if( nread < 0 )
|
|
||||||
{
|
|
||||||
fprintf( stderr, "Warning: Sun Recording Failed\n" );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
r->recording = 1;
|
|
||||||
r->callback( (struct CNFADriver *)r, NULL, r->samplesRec, 0, (nread / 2) / r->channelsRec);
|
|
||||||
} while( 1 );
|
|
||||||
r->recording = 0;
|
|
||||||
fprintf( stderr, "Sun Recording Stopped\n" );
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void * PlayThread( void * v )
|
|
||||||
{
|
|
||||||
struct CNFADriverSun * r = (struct CNFADriverSun *)v;
|
|
||||||
size_t nbytes = r->bufsize * (2 * r->channelsPlay);
|
|
||||||
int err;
|
|
||||||
|
|
||||||
r->callback( (struct CNFADriver *)r, r->samplesPlay, NULL, r->bufsize, 0 );
|
|
||||||
err = write( r->playback_handle, r->samplesPlay, nbytes );
|
|
||||||
|
|
||||||
while( err >= 0 )
|
|
||||||
{
|
|
||||||
r->callback( (struct CNFADriver *)r, r->samplesPlay, NULL, r->bufsize, 0 );
|
|
||||||
err = write( r->playback_handle, r->samplesPlay, nbytes );
|
|
||||||
r->playing = 1;
|
|
||||||
}
|
|
||||||
r->playing = 0;
|
|
||||||
fprintf( stderr, "Sun Playback Stopped\n" );
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct CNFADriverSun * InitSun( struct CNFADriverSun * r )
|
|
||||||
{
|
|
||||||
const char * devPlay = r->devPlay;
|
|
||||||
const char * devRec = r->devRec;
|
|
||||||
struct audio_info rinfo, pinfo;
|
|
||||||
|
|
||||||
if( devRec == NULL || strcmp ( devRec, "default" ) == 0 )
|
|
||||||
{
|
|
||||||
devRec = "/dev/audio";
|
|
||||||
}
|
|
||||||
|
|
||||||
if( devPlay == NULL || strcmp ( devPlay , "default" ) == 0 )
|
|
||||||
{
|
|
||||||
devPlay = "/dev/audio";
|
|
||||||
}
|
|
||||||
|
|
||||||
printf( "CNFA Sun Init -> devPlay: %s, channelsPlay: %d, spsPlay: %d, devRec: %s, channelsRec: %d, spsRec: %d\n", devPlay, r->channelsPlay, r->spsPlay, devRec, r->channelsRec, r->spsRec);
|
|
||||||
|
|
||||||
if( r->channelsPlay && r->channelsRec && strcmp (devPlay, devRec) == 0 )
|
|
||||||
{
|
|
||||||
if ( (r->playback_handle = r->record_handle = open (devPlay, O_RDWR)) < 0 )
|
|
||||||
{
|
|
||||||
fprintf (stderr, "cannot open audio device (%s)\n",
|
|
||||||
strerror (errno));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if( r->channelsPlay )
|
|
||||||
{
|
|
||||||
if ( (r->playback_handle = open (devPlay, O_WRONLY)) < 0 )
|
|
||||||
{
|
|
||||||
fprintf (stderr, "cannot open output audio device %s (%s)\n",
|
|
||||||
r->devPlay, strerror (errno));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( r->channelsRec )
|
|
||||||
{
|
|
||||||
if ( (r->record_handle = open (devRec, O_RDONLY)) < 0 )
|
|
||||||
{
|
|
||||||
fprintf (stderr, "cannot open input audio device %s (%s)\n",
|
|
||||||
r->devRec, strerror (errno));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( r->playback_handle )
|
|
||||||
{
|
|
||||||
AUDIO_INITINFO(&pinfo);
|
|
||||||
|
|
||||||
pinfo.play.precision = 16;
|
|
||||||
pinfo.play.encoding = AUDIO_ENCODING_LINEAR;
|
|
||||||
pinfo.play.sample_rate = r->spsPlay;
|
|
||||||
pinfo.play.channels = r->channelsPlay;
|
|
||||||
|
|
||||||
if ( ioctl(r->playback_handle, AUDIO_SETINFO, &pinfo) < 0 )
|
|
||||||
{
|
|
||||||
fprintf (stderr, "cannot set audio playback format (%s)\n",
|
|
||||||
strerror (errno));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ioctl(r->playback_handle, AUDIO_GETINFO, &pinfo) < 0 )
|
|
||||||
{
|
|
||||||
fprintf (stderr, "cannot get audio record format (%s)\n",
|
|
||||||
strerror (errno));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
r->spsPlay = pinfo.play.sample_rate;
|
|
||||||
r->channelsPlay = pinfo.play.channels;
|
|
||||||
|
|
||||||
if ( (r->samplesPlay = calloc(2 * r->channelsPlay, r->bufsize)) == NULL )
|
|
||||||
{
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( r->record_handle )
|
|
||||||
{
|
|
||||||
AUDIO_INITINFO(&rinfo);
|
|
||||||
|
|
||||||
rinfo.record.precision = 16;
|
|
||||||
rinfo.record.encoding = AUDIO_ENCODING_LINEAR;
|
|
||||||
rinfo.record.sample_rate = r->spsRec;
|
|
||||||
rinfo.record.channels = r->channelsRec;
|
|
||||||
|
|
||||||
if ( ioctl(r->record_handle, AUDIO_SETINFO, &rinfo) < 0 )
|
|
||||||
{
|
|
||||||
fprintf (stderr, "cannot set audio record format (%s)\n",
|
|
||||||
strerror (errno));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ioctl(r->record_handle, AUDIO_GETINFO, &rinfo) < 0 )
|
|
||||||
{
|
|
||||||
fprintf (stderr, "cannot get audio record format (%s)\n",
|
|
||||||
strerror (errno));
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
r->spsRec = rinfo.record.sample_rate;
|
|
||||||
r->channelsRec = rinfo.record.channels;
|
|
||||||
|
|
||||||
if ( (r->samplesRec = calloc(2 * r->channelsRec, r->bufsize)) == NULL )
|
|
||||||
{
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( r->playback_handle )
|
|
||||||
{
|
|
||||||
r->threadPlay = OGCreateThread( PlayThread, r );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( r->record_handle )
|
|
||||||
{
|
|
||||||
r->threadRec = OGCreateThread( RecThread, r );
|
|
||||||
}
|
|
||||||
|
|
||||||
printf( "CNFA Sun Init Out -> channelsPlay: %d, spsPlay: %d, channelsRec: %d, spsRec: %d\n", r->channelsPlay, r->spsPlay, r->channelsRec, r->spsRec);
|
|
||||||
|
|
||||||
return r;
|
|
||||||
|
|
||||||
fail:
|
|
||||||
if( r )
|
|
||||||
{
|
|
||||||
if( r->playback_handle != -1 ) close (r->playback_handle);
|
|
||||||
if( r->record_handle != -1 ) close (r->record_handle);
|
|
||||||
free( r->samplesPlay );
|
|
||||||
free( r->samplesRec );
|
|
||||||
free( r );
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void * InitSunDriver( CNFACBType cb, const char * your_name, int reqSPSPlay, int reqSPSRec, int reqChannelsPlay, int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque )
|
|
||||||
{
|
|
||||||
struct CNFADriverSun * r = (struct CNFADriverSun *)malloc( sizeof( struct CNFADriverSun ) );
|
|
||||||
|
|
||||||
r->CloseFn = CloseCNFASun;
|
|
||||||
r->StateFn = CNFAStateSun;
|
|
||||||
r->callback = cb;
|
|
||||||
r->opaque = opaque;
|
|
||||||
r->spsPlay = reqSPSPlay;
|
|
||||||
r->spsRec = reqSPSRec;
|
|
||||||
r->channelsPlay = reqChannelsPlay;
|
|
||||||
r->channelsRec = reqChannelsRec;
|
|
||||||
|
|
||||||
r->devRec = (inputSelect)?strdup(inputSelect):0;
|
|
||||||
r->devPlay = (outputSelect)?strdup(outputSelect):0;
|
|
||||||
|
|
||||||
r->samplesPlay = NULL;
|
|
||||||
r->samplesRec = NULL;
|
|
||||||
|
|
||||||
r->playback_handle = -1;
|
|
||||||
r->record_handle = -1;
|
|
||||||
r->bufsize = sugBufferSize;
|
|
||||||
|
|
||||||
return InitSun(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
REGISTER_CNFA( SUN, 10, "Sun", InitSunDriver );
|
|
||||||
|
|
|
@ -1,514 +0,0 @@
|
||||||
#include "CNFA.h"
|
|
||||||
|
|
||||||
//Needed libraries: -lmmdevapi -lavrt -lole32
|
|
||||||
//Or DLLs: C:/windows/system32/avrt.dll C:/windows/system32/ole32.dll
|
|
||||||
|
|
||||||
#ifdef TCC
|
|
||||||
#define NO_WIN_HEADERS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef NO_WIN_HEADERS
|
|
||||||
#include "CNFA_wasapi_utils.h"
|
|
||||||
#else
|
|
||||||
#include <InitGuid.h>
|
|
||||||
#include <audioclient.h> // Render and capturing audio
|
|
||||||
#include <mmdeviceapi.h> // Audio device handling
|
|
||||||
#include <Functiondiscoverykeys_devpkey.h> // Property keys for audio devices
|
|
||||||
#include <avrt.h> // Thread management
|
|
||||||
#include "windows.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "os_generic.h"
|
|
||||||
|
|
||||||
#if defined(WIN32) && !defined( TCC )
|
|
||||||
#pragma comment(lib,"avrt.lib")
|
|
||||||
#pragma comment(lib,"ole32.lib")
|
|
||||||
//And maybe mmdevapi.lib
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define WASAPIPRINT(message) (printf("[WASAPI] %s\n", message))
|
|
||||||
#define WASAPIERROR(error, message) (printf("[WASAPI][ERR] %s HRESULT: 0x%lX\n", message, error))
|
|
||||||
#define PRINTGUID(guid) (printf("{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]))
|
|
||||||
|
|
||||||
#define WASAPI_EXTRA_DEBUG FALSE
|
|
||||||
|
|
||||||
// Forward declarations
|
|
||||||
void CloseCNFAWASAPI(void* stateObj);
|
|
||||||
int CNFAStateWASAPI(void* object);
|
|
||||||
static struct CNFADriverWASAPI* StartWASAPIDriver(struct CNFADriverWASAPI* initState);
|
|
||||||
static IMMDevice* WASAPIGetDefaultDevice(BOOL isCapture, BOOL isMultimedia);
|
|
||||||
static void WASAPIPrintAllDeviceLists();
|
|
||||||
static void WASAPIPrintDeviceList(EDataFlow dataFlow);
|
|
||||||
void* ProcessEventAudioIn(void* stateObj);
|
|
||||||
void* InitCNFAWASAPIDriver(
|
|
||||||
CNFACBType callback, const char *session_name,
|
|
||||||
int reqSampleRateOut, int reqSampleRateIn,
|
|
||||||
int reqChannelsOut, int reqChannelsIn, int sugBufferSize,
|
|
||||||
const char * inputDevice, const char * outputDevice,
|
|
||||||
void * opaque
|
|
||||||
);
|
|
||||||
|
|
||||||
DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395L, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E);
|
|
||||||
DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2L, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6);
|
|
||||||
DEFINE_GUID(IID_IMMEndpoint, 0x1BE09788L, 0x6894, 0x4089, 0x85, 0x86, 0x9A, 0x2A, 0x6C, 0x26, 0x5A, 0xC5);
|
|
||||||
DEFINE_GUID(IID_IAudioClient, 0x1CB9AD4CL, 0xDBFA, 0x4c32, 0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2);
|
|
||||||
DEFINE_GUID(IID_IAudioCaptureClient, 0xC8ADBD64L, 0xE71E, 0x48a0, 0xA4, 0xDE, 0x18, 0x5C, 0x39, 0x5C, 0xD3, 0x17);
|
|
||||||
|
|
||||||
// This is a fallback if the client application does not provide a GUID.
|
|
||||||
DEFINE_GUID(CNFA_GUID, 0x899081C7L, 0x9428, 0x4103, 0x87, 0x93, 0x26, 0x47, 0xE5, 0xEA, 0xA2, 0xB4);
|
|
||||||
|
|
||||||
struct CNFADriverWASAPI
|
|
||||||
{
|
|
||||||
// Common CNFA items
|
|
||||||
void (*CloseFn)(void* object);
|
|
||||||
int (*StateFn)(void* object);
|
|
||||||
CNFACBType Callback;
|
|
||||||
short ChannelCountOut; // Not yet used.
|
|
||||||
short ChannelCountIn; // How many cahnnels the input stream has per frame. E.g. stereo = 2.
|
|
||||||
int SampleRateOut;
|
|
||||||
int SampleRateIn;
|
|
||||||
void* Opaque; // Not relevant to us
|
|
||||||
|
|
||||||
// Adjustable WASAPI-specific items
|
|
||||||
const char* SessionName; // The name to give our audio sessions. Otherwise, defaults to using embedded EXE name, Window title, or EXE file name directly.
|
|
||||||
const GUID* SessionID; // In order to have different CNFA-based applications individually controllable from the volume mixer, this should be set differently for every client program, but constant across all runs/builds of that application.
|
|
||||||
|
|
||||||
// Everything below here is for internal use only. Do not attempt to interact with these items.
|
|
||||||
const char* InputDeviceID; // The device to use for getting input from. Can be a render device (operating in loopback), or a capture device.
|
|
||||||
const char* OutputDeviceID; // Not yet used.
|
|
||||||
IMMDeviceEnumerator* DeviceEnumerator; // The base object that allows us to look through the system's devices, and from there get everything else.
|
|
||||||
IMMDevice* Device; // The device we are taking input from.
|
|
||||||
IAudioClient* Client; // The base client we use for getting input.
|
|
||||||
IAudioCaptureClient* CaptureClient; // The specific client we use for getting input.
|
|
||||||
WAVEFORMATEX* MixFormat; // The format of the input stream.
|
|
||||||
INT32 BytesPerFrame; // The number of bytes of one full frame of audio. AKA (channel count) * (sample bit depth), in Bytes.
|
|
||||||
BOOL StreamReady; // Whether the input stream is ready for data retrieval.
|
|
||||||
BOOL KeepGoing; // Whether to continue interacting with the streams, or shutdown the driver.
|
|
||||||
og_thread_t ThreadOut; // Not yet used.
|
|
||||||
og_thread_t ThreadIn; // The thread used to grab input data.
|
|
||||||
HANDLE EventHandleOut; // Not yet used.
|
|
||||||
HANDLE EventHandleIn; // The handle used to wait for more input data to be ready in the input thread.
|
|
||||||
HANDLE TaskHandleOut; // The task used to request output thread priority changes.
|
|
||||||
HANDLE TaskHandleIn; // The task used to request input thread priority changes.
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is where the driver's current state is stored.
|
|
||||||
static struct CNFADriverWASAPI* WASAPIState;
|
|
||||||
|
|
||||||
// Stops streams, ends threads, and cleans up all resources used by the driver.
|
|
||||||
void CloseCNFAWASAPI(void* stateObj)
|
|
||||||
{
|
|
||||||
struct CNFADriverWASAPI* state = (struct CNFADriverWASAPI*)stateObj;
|
|
||||||
if(state != NULL)
|
|
||||||
{
|
|
||||||
// TODO: See if there are any other items that need cleanup.
|
|
||||||
state->KeepGoing = FALSE;
|
|
||||||
if (state->ThreadOut != NULL) { OGJoinThread(state->ThreadOut); }
|
|
||||||
if (state->ThreadIn != NULL) { OGJoinThread(state->ThreadIn); }
|
|
||||||
if (state->EventHandleOut != NULL) { CloseHandle(state->EventHandleOut); }
|
|
||||||
if (state->EventHandleIn != NULL) { CloseHandle(state->EventHandleIn); }
|
|
||||||
CoTaskMemFree(state->MixFormat);
|
|
||||||
if (state->CaptureClient != NULL) { state->CaptureClient->lpVtbl->Release(state->CaptureClient); }
|
|
||||||
if (state->Client != NULL) { state->Client->lpVtbl->Release(state->Client); }
|
|
||||||
if (state->Device != NULL) { state->Device->lpVtbl->Release(state->Device); }
|
|
||||||
if (state->DeviceEnumerator != NULL) { state->DeviceEnumerator->lpVtbl->Release(state->DeviceEnumerator); }
|
|
||||||
free(stateObj);
|
|
||||||
CoUninitialize();
|
|
||||||
printf("[WASAPI] Cleanup completed. Goodbye.\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets the current state of the driver.
|
|
||||||
// 0 = No streams active
|
|
||||||
// 1 = Input stream active
|
|
||||||
// 2 = Output stream active
|
|
||||||
// 3 = Both streams active
|
|
||||||
int CNFAStateWASAPI(void* stateObj)
|
|
||||||
{
|
|
||||||
struct CNFADriverWASAPI* state = (struct CNFADriverWASAPI*)stateObj;
|
|
||||||
if(state != NULL)
|
|
||||||
{
|
|
||||||
if (state->StreamReady) { return 1; } // TODO: Output the correct status when output is implemented.
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reads the desired configuration, interfaces with WASAPI to get the current system information, and starts the input stream.
|
|
||||||
static struct CNFADriverWASAPI* StartWASAPIDriver(struct CNFADriverWASAPI* initState)
|
|
||||||
{
|
|
||||||
WASAPIState = initState;
|
|
||||||
WASAPIState->StreamReady = FALSE;
|
|
||||||
WASAPIState->SessionID = &CNFA_GUID;
|
|
||||||
|
|
||||||
HRESULT ErrorCode;
|
|
||||||
#ifndef BUILD_DLL
|
|
||||||
// A library should never call CoInitialize, as it needs to be done from the host program according to its threading model needs.
|
|
||||||
// NOTE: If you are getting errors, and you are using CNFA as a DLL, you need to call CoInitialize yourself with an appropriate threading model for your needs!
|
|
||||||
// When the host program is something like ColorChord on the other hand, it cannot be expected to call CoInitialize itself, so we do it on its behalf.
|
|
||||||
// This restricts the threading model of direct consumers of CNFA, but we can address that if it does ever become an issue.
|
|
||||||
ErrorCode = CoInitialize(NULL);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "COM INIT FAILED!"); return WASAPIState; }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if(WASAPI_EXTRA_DEBUG)
|
|
||||||
{
|
|
||||||
printf("[WASAPI] CLSID for MMDeviceEnumerator: ");
|
|
||||||
PRINTGUID(CLSID_MMDeviceEnumerator);
|
|
||||||
printf("\n[WASAPI] IID for IMMDeviceEnumerator: ");
|
|
||||||
PRINTGUID(IID_IMMDeviceEnumerator);
|
|
||||||
printf("\n[WASAPI] IID for IAudioClient: ");
|
|
||||||
PRINTGUID(IID_IAudioClient);
|
|
||||||
printf("\n[WASAPI] IID for IAudioCaptureClient: ");
|
|
||||||
PRINTGUID(IID_IAudioCaptureClient);
|
|
||||||
printf("\n[WASAPI] IID for IMMEndpoint: ");
|
|
||||||
PRINTGUID(IID_IMMEndpoint);
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorCode = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (void**)&(WASAPIState->DeviceEnumerator));
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get device enumerator. "); return WASAPIState; }
|
|
||||||
|
|
||||||
WASAPIPrintAllDeviceLists();
|
|
||||||
|
|
||||||
// We need to find the appropriate device to use.
|
|
||||||
BYTE DeviceDirection = 2; // 0 = Render, 1 = Capture, 2 = Unknown
|
|
||||||
|
|
||||||
if (WASAPIState->InputDeviceID == NULL)
|
|
||||||
{
|
|
||||||
WASAPIPRINT("No device specified, attempting to use system default multimedia capture device as input.");
|
|
||||||
WASAPIState->Device = WASAPIGetDefaultDevice(TRUE, TRUE);
|
|
||||||
DeviceDirection = 1;
|
|
||||||
}
|
|
||||||
else if (strcmp(WASAPIState->InputDeviceID, "defaultRender") == 0)
|
|
||||||
{
|
|
||||||
WASAPIPRINT("Attempting to use system default render device as input.");
|
|
||||||
WASAPIState->Device = WASAPIGetDefaultDevice(FALSE, TRUE);
|
|
||||||
DeviceDirection = 0;
|
|
||||||
}
|
|
||||||
else if (strncmp("defaultCapture", WASAPIState->InputDeviceID, strlen("defaultCapture")) == 0)
|
|
||||||
{
|
|
||||||
BOOL IsMultimedia = TRUE;
|
|
||||||
if (strstr(WASAPIState->InputDeviceID, "Comm") != NULL) { IsMultimedia = FALSE; }
|
|
||||||
printf("[WASAPI] Attempting to use system default %s capture device as input.\n", (IsMultimedia ? "multimedia" : "communications"));
|
|
||||||
WASAPIState->Device = WASAPIGetDefaultDevice(TRUE, IsMultimedia);
|
|
||||||
DeviceDirection = 1;
|
|
||||||
}
|
|
||||||
else // A specific device was selected by ID.
|
|
||||||
{
|
|
||||||
LPWSTR DeviceIDasLPWSTR;
|
|
||||||
DeviceIDasLPWSTR = malloc((strlen(WASAPIState->InputDeviceID) + 1) * sizeof(WCHAR));
|
|
||||||
mbstowcs(DeviceIDasLPWSTR, WASAPIState->InputDeviceID, strlen(WASAPIState->InputDeviceID) + 1);
|
|
||||||
printf("[WASAPI] Attempting to find specified device \"%ls\".\n", DeviceIDasLPWSTR);
|
|
||||||
|
|
||||||
ErrorCode = WASAPIState->DeviceEnumerator->lpVtbl->GetDevice(WASAPIState->DeviceEnumerator, DeviceIDasLPWSTR, &(WASAPIState->Device));
|
|
||||||
if (FAILED(ErrorCode))
|
|
||||||
{
|
|
||||||
WASAPIERROR(ErrorCode, "Failed to get audio device from the given ID. Using default multimedia capture device instead.");
|
|
||||||
WASAPIState->Device = WASAPIGetDefaultDevice(TRUE, TRUE);
|
|
||||||
DeviceDirection = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
printf("[WASAPI] Found specified device.\n");
|
|
||||||
DWORD DeviceState;
|
|
||||||
ErrorCode = WASAPIState->Device->lpVtbl->GetState(WASAPIState->Device, &DeviceState);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get device state."); }
|
|
||||||
|
|
||||||
if ((DeviceState & DEVICE_STATE_DISABLED) == DEVICE_STATE_DISABLED) { WASAPIERROR(E_FAIL, "The specified device is currently disabled."); }
|
|
||||||
if ((DeviceState & DEVICE_STATE_NOTPRESENT) == DEVICE_STATE_NOTPRESENT) { WASAPIERROR(E_FAIL, "The specified device is not currently present."); }
|
|
||||||
if ((DeviceState & DEVICE_STATE_UNPLUGGED) == DEVICE_STATE_UNPLUGGED) { WASAPIERROR(E_FAIL, "The specified device is currently unplugged."); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DeviceDirection == 2) // We still don't know what type of device we are trying to use. Query the endpoint to find out.
|
|
||||||
{
|
|
||||||
IMMEndpoint* Endpoint;
|
|
||||||
ErrorCode = WASAPIState->Device->lpVtbl->QueryInterface(WASAPIState->Device, &IID_IMMEndpoint, (void**)&Endpoint);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get endpoint of device."); }
|
|
||||||
|
|
||||||
EDataFlow DataFlow;
|
|
||||||
ErrorCode = Endpoint->lpVtbl->GetDataFlow(Endpoint, &DataFlow);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Could not determine endpoint type."); }
|
|
||||||
|
|
||||||
DeviceDirection = (DataFlow == eRender) ? 0 : 1;
|
|
||||||
|
|
||||||
if (Endpoint != NULL) { Endpoint->lpVtbl->Release(Endpoint); }
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should have a device now.
|
|
||||||
char* DeviceDirectionDesc = (DeviceDirection == 0) ? "render" : ((DeviceDirection == 1) ? "capture" : "UNKNOWN");
|
|
||||||
|
|
||||||
LPWSTR DeviceID;
|
|
||||||
ErrorCode = WASAPIState->Device->lpVtbl->GetId(WASAPIState->Device, &DeviceID);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio device ID."); return WASAPIState; }
|
|
||||||
else { printf("[WASAPI] Using device ID \"%ls\", which is a %s device.\n", DeviceID, DeviceDirectionDesc); }
|
|
||||||
|
|
||||||
// Start an audio client and get info about the stream format.
|
|
||||||
ErrorCode = WASAPIState->Device->lpVtbl->Activate(WASAPIState->Device, &IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&(WASAPIState->Client));
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio client. "); return WASAPIState; }
|
|
||||||
|
|
||||||
ErrorCode = WASAPIState->Client->lpVtbl->GetMixFormat(WASAPIState->Client, &(WASAPIState->MixFormat));
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get mix format. "); return WASAPIState; }
|
|
||||||
printf("[WASAPI] Mix format is %d channel, %luHz sample rate, %db per sample.\n", WASAPIState->MixFormat->nChannels, WASAPIState->MixFormat->nSamplesPerSec, WASAPIState->MixFormat->wBitsPerSample);
|
|
||||||
printf("[WASAPI] Mix format is format %d, %dB block-aligned, with %dB of extra data in this definition.\n", WASAPIState->MixFormat->wFormatTag, WASAPIState->MixFormat->nBlockAlign, WASAPIState->MixFormat->cbSize);
|
|
||||||
|
|
||||||
// We'll request PCM, 16bpS data from the system. It should be able to do this conversion for us, as long as we are not in exclusive mode.
|
|
||||||
// TODO: This isn't working, no matter what combination I try to ask it for. Figure this out, so we don't have to do the conversion ourselves.
|
|
||||||
// Also, we probably don't handle channel counts > 2 with this current setup.
|
|
||||||
//WASAPIState->MixFormat->wFormatTag = WAVE_FORMAT_PCM;
|
|
||||||
//WASAPIState->MixFormat->wBitsPerSample = 16 * WASAPIState->MixFormat->nChannels;
|
|
||||||
//WASAPIState->MixFormat->nBlockAlign = 2 * WASAPIState->MixFormat->nChannels;
|
|
||||||
//WASAPIState->MixFormat->nAvgBytesPerSec = WASAPIState->MixFormat->nSamplesPerSec * WASAPIState->MixFormat->nBlockAlign;
|
|
||||||
|
|
||||||
WASAPIState->ChannelCountIn = WASAPIState->MixFormat->nChannels;
|
|
||||||
WASAPIState->SampleRateIn = WASAPIState->MixFormat->nSamplesPerSec;
|
|
||||||
WASAPIState->BytesPerFrame = WASAPIState->MixFormat->nChannels * (WASAPIState->MixFormat->wBitsPerSample / 8);
|
|
||||||
|
|
||||||
REFERENCE_TIME DefaultInterval, MinimumInterval;
|
|
||||||
ErrorCode = WASAPIState->Client->lpVtbl->GetDevicePeriod(WASAPIState->Client, &DefaultInterval, &MinimumInterval);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get device timing info. "); return WASAPIState; }
|
|
||||||
printf("[WASAPI] Default transaction period is %lld ticks, minimum is %lld ticks.\n", DefaultInterval, MinimumInterval);
|
|
||||||
|
|
||||||
// Configure a capture client.
|
|
||||||
UINT32 StreamFlags;
|
|
||||||
if (DeviceDirection == 1) { StreamFlags = AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_EVENTCALLBACK; }
|
|
||||||
else if (DeviceDirection == 0) { StreamFlags = (AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK); }
|
|
||||||
else { WASAPIPRINT("[ERR] Device type was not determined!"); return WASAPIState; }
|
|
||||||
|
|
||||||
// TODO: Allow the target application to influence the interval we choose. Super realtime apps may require MinimumInterval.
|
|
||||||
ErrorCode = WASAPIState->Client->lpVtbl->Initialize(WASAPIState->Client, AUDCLNT_SHAREMODE_SHARED, StreamFlags, DefaultInterval, DefaultInterval, WASAPIState->MixFormat, WASAPIState->SessionID);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Could not init audio client."); return WASAPIState; }
|
|
||||||
|
|
||||||
WASAPIState->EventHandleIn = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
||||||
if (WASAPIState->EventHandleIn == NULL) { WASAPIERROR(E_FAIL, "Failed to make event handle."); return WASAPIState; }
|
|
||||||
|
|
||||||
ErrorCode = WASAPIState->Client->lpVtbl->SetEventHandle(WASAPIState->Client, WASAPIState->EventHandleIn);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to set event handler."); return WASAPIState; }
|
|
||||||
|
|
||||||
UINT32 BufferFrameCount;
|
|
||||||
ErrorCode = WASAPIState->Client->lpVtbl->GetBufferSize(WASAPIState->Client, &BufferFrameCount);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Could not get audio client buffer size."); return WASAPIState; }
|
|
||||||
|
|
||||||
ErrorCode = WASAPIState->Client->lpVtbl->GetService(WASAPIState->Client, &IID_IAudioCaptureClient, (void**)&(WASAPIState->CaptureClient));
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Could not get audio capture client."); return WASAPIState; }
|
|
||||||
|
|
||||||
// Begin capturing audio. It will be received on a separate thread.
|
|
||||||
ErrorCode = WASAPIState->Client->lpVtbl->Start(WASAPIState->Client);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Could not start audio client."); return WASAPIState; }
|
|
||||||
WASAPIState->StreamReady = TRUE;
|
|
||||||
|
|
||||||
WASAPIState->KeepGoing = TRUE;
|
|
||||||
WASAPIState->ThreadIn = OGCreateThread(ProcessEventAudioIn, WASAPIState);
|
|
||||||
|
|
||||||
return WASAPIState;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets the default render or capture device.
|
|
||||||
// isCapture: If true, gets the default capture device, otherwise gets the default render device.
|
|
||||||
// isMultimedia: If true, gets the system default devide for "multimedia" use, otheriwse for "communication" use.
|
|
||||||
static IMMDevice* WASAPIGetDefaultDevice(BOOL isCapture, BOOL isMultimedia)
|
|
||||||
{
|
|
||||||
HRESULT ErrorCode;
|
|
||||||
IMMDevice* Device;
|
|
||||||
ErrorCode = WASAPIState->DeviceEnumerator->lpVtbl->GetDefaultAudioEndpoint(WASAPIState->DeviceEnumerator, isCapture ? eCapture : eRender, isMultimedia ? eMultimedia : eCommunications, &Device);
|
|
||||||
if (FAILED(ErrorCode))
|
|
||||||
{
|
|
||||||
WASAPIERROR(ErrorCode, "Failed to get default device.");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return Device;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prints all available devices to the console.
|
|
||||||
static void WASAPIPrintAllDeviceLists()
|
|
||||||
{
|
|
||||||
WASAPIPrintDeviceList(eRender);
|
|
||||||
WASAPIPrintDeviceList(eCapture);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prints a list of all available devices of a specified data flow direction to the console.
|
|
||||||
static void WASAPIPrintDeviceList(EDataFlow dataFlow)
|
|
||||||
{
|
|
||||||
printf("[WASAPI] %s Devices:\n", (dataFlow == eCapture ? "Capture" : "Render"));
|
|
||||||
IMMDeviceCollection* Devices;
|
|
||||||
HRESULT ErrorCode = WASAPIState->DeviceEnumerator->lpVtbl->EnumAudioEndpoints(WASAPIState->DeviceEnumerator, dataFlow, (WASAPI_EXTRA_DEBUG ? DEVICE_STATEMASK_ALL : DEVICE_STATE_ACTIVE), &Devices);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio endpoints."); return; }
|
|
||||||
|
|
||||||
UINT32 DeviceCount;
|
|
||||||
ErrorCode = Devices->lpVtbl->GetCount(Devices, &DeviceCount);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio endpoint count."); return; }
|
|
||||||
|
|
||||||
for (UINT32 DeviceIndex = 0; DeviceIndex < DeviceCount; DeviceIndex++)
|
|
||||||
{
|
|
||||||
IMMDevice* Device;
|
|
||||||
ErrorCode = Devices->lpVtbl->Item(Devices, DeviceIndex, &Device);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio device."); continue; }
|
|
||||||
|
|
||||||
LPWSTR DeviceID;
|
|
||||||
ErrorCode = Device->lpVtbl->GetId(Device, &DeviceID);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio device ID."); continue; }
|
|
||||||
|
|
||||||
IPropertyStore* Properties;
|
|
||||||
ErrorCode = Device->lpVtbl->OpenPropertyStore(Device, STGM_READ, &Properties);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get device properties."); continue; }
|
|
||||||
|
|
||||||
PROPVARIANT Variant;
|
|
||||||
PropVariantInit(&Variant);
|
|
||||||
|
|
||||||
ErrorCode = Properties->lpVtbl->GetValue(Properties, &PKEY_Device_FriendlyName, &Variant);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get device friendly name."); }
|
|
||||||
|
|
||||||
LPWSTR DeviceFriendlyName = L"[Name Retrieval Failed]";
|
|
||||||
if (Variant.pwszVal != NULL) { DeviceFriendlyName = Variant.pwszVal; }
|
|
||||||
|
|
||||||
printf("[WASAPI] [%d]: \"%ls\" = \"%ls\"\n", DeviceIndex, DeviceFriendlyName, DeviceID);
|
|
||||||
|
|
||||||
CoTaskMemFree(DeviceID);
|
|
||||||
DeviceID = NULL;
|
|
||||||
PropVariantClear(&Variant);
|
|
||||||
if (Properties != NULL) { Properties->lpVtbl->Release(Properties); }
|
|
||||||
if (Device != NULL) { Device->lpVtbl->Release(Device); }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Devices != NULL) { Devices->lpVtbl->Release(Devices); }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Runs on a thread. Waits for audio data to be ready from the system, then forwards it to the registered callback.
|
|
||||||
void* ProcessEventAudioIn(void* stateObj)
|
|
||||||
{
|
|
||||||
struct CNFADriverWASAPI* state = (struct CNFADriverWASAPI*)stateObj;
|
|
||||||
HRESULT ErrorCode;
|
|
||||||
UINT32 PacketLength;
|
|
||||||
|
|
||||||
// TODO: Set this based on our device period requested. If we are using 10ms or higher, just request "Audio", not "Pro Audio".
|
|
||||||
DWORD TaskIndex = 0;
|
|
||||||
state->TaskHandleIn = AvSetMmThreadCharacteristicsW(L"Pro Audio", &TaskIndex);
|
|
||||||
if (state->TaskHandleIn == NULL) { WASAPIERROR(E_FAIL, "Failed to request thread priority elevation on input task."); }
|
|
||||||
|
|
||||||
while (state->KeepGoing)
|
|
||||||
{
|
|
||||||
// Waits up to 500ms to get the next audio buffer from the system.
|
|
||||||
// The timeout is used because if no audio sessions are active, WASAPI stops sending buffers after a few that indicate silence.
|
|
||||||
// This means that if the client tries to exit, this loop would not complete, and therefore the thread would not exit, until the next buffer is received.
|
|
||||||
// This is mostly an issue in loopback mode, where true silence is common, not so much on microphones.
|
|
||||||
DWORD WaitResult = WaitForSingleObject(state->EventHandleIn, 500);
|
|
||||||
if (WaitResult == WAIT_TIMEOUT) { continue; } // We are in a period of silence. Keep waiting for audio.
|
|
||||||
else if (WaitResult != WAIT_OBJECT_0) { WASAPIERROR(E_FAIL, "Something went wrong while waiting for an audio event."); continue; }
|
|
||||||
|
|
||||||
ErrorCode = state->CaptureClient->lpVtbl->GetNextPacketSize(state->CaptureClient, &PacketLength);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio packet size."); continue; }
|
|
||||||
|
|
||||||
BYTE* DataBuffer;
|
|
||||||
UINT32 FramesAvailable;
|
|
||||||
DWORD BufferStatus;
|
|
||||||
BOOL Released = FALSE;
|
|
||||||
ErrorCode = state->CaptureClient->lpVtbl->GetBuffer(state->CaptureClient, &DataBuffer, &FramesAvailable, &BufferStatus, NULL, NULL);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to get audio buffer."); continue; }
|
|
||||||
|
|
||||||
// "The data in the packet is not correlated with the previous packet's device position; this is possibly due to a stream state transition or timing glitch."
|
|
||||||
// There's no real way for us to notify the client about this...
|
|
||||||
if ((BufferStatus & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) == AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY)
|
|
||||||
{
|
|
||||||
WASAPIPRINT("A data discontinuity was detected.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((BufferStatus & AUDCLNT_BUFFERFLAGS_SILENT) == AUDCLNT_BUFFERFLAGS_SILENT)
|
|
||||||
{
|
|
||||||
UINT32 Length = FramesAvailable * state->MixFormat->nChannels;
|
|
||||||
if (Length == 0) { Length = state->MixFormat->nChannels; }
|
|
||||||
INT16* AudioData = malloc(Length * 2);
|
|
||||||
for (int i = 0; i < Length; i++) { AudioData[i] = 0; }
|
|
||||||
|
|
||||||
ErrorCode = state->CaptureClient->lpVtbl->ReleaseBuffer(state->CaptureClient, FramesAvailable);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to release audio buffer."); }
|
|
||||||
else { Released = TRUE; }
|
|
||||||
|
|
||||||
if (WASAPI_EXTRA_DEBUG) { printf("[WASAPI] SILENCE buffer received. Passing on %d samples.\n", Length); }
|
|
||||||
|
|
||||||
WASAPIState->Callback((struct CNFADriver*)WASAPIState, 0, AudioData, 0, Length / state->MixFormat->nChannels );
|
|
||||||
free(AudioData);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// TODO: This assumes that data is coming in at 32b float format. While this appears to be the format that WASAPI uses internally in all cases I've seen, I don't think it's guaranteed.
|
|
||||||
// We should instead read the MixFormat information and properly handle the data in other cases.
|
|
||||||
// Ideally, we could request 16b signed PCM data from WASAPI, so we don't even have to do any conversion. But I couldn't get this working yet.
|
|
||||||
UINT32 Size = FramesAvailable * state->BytesPerFrame; // Size in bytes
|
|
||||||
FLOAT* DataAsFloat = (FLOAT*)DataBuffer; // The raw input data, reinterpreted as floats.
|
|
||||||
INT16* AudioData = malloc((FramesAvailable * state->MixFormat->nChannels) * 2); // The data we are passing to the consumer.
|
|
||||||
for (INT32 i = 0; i < Size / 4; i++) { AudioData[i] = (INT16)(DataAsFloat[i] * 32767.5F); }
|
|
||||||
|
|
||||||
ErrorCode = state->CaptureClient->lpVtbl->ReleaseBuffer(state->CaptureClient, FramesAvailable);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to release audio buffer."); }
|
|
||||||
else { Released = TRUE; }
|
|
||||||
|
|
||||||
if (WASAPI_EXTRA_DEBUG) { printf("[WASAPI] Got %d bytes of audio data in %d frames. Fowarding to %p.\n", Size, FramesAvailable, (void*) WASAPIState->Callback); }
|
|
||||||
|
|
||||||
WASAPIState->Callback((struct CNFADriver*)WASAPIState, 0, AudioData, 0, FramesAvailable );
|
|
||||||
free(AudioData);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Released)
|
|
||||||
{
|
|
||||||
ErrorCode = state->CaptureClient->lpVtbl->ReleaseBuffer(state->CaptureClient, FramesAvailable);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to release audio buffer."); }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorCode = state->Client->lpVtbl->Stop(state->Client);
|
|
||||||
if (FAILED(ErrorCode)) { WASAPIERROR(ErrorCode, "Failed to stop audio client."); }
|
|
||||||
|
|
||||||
if(state->TaskHandleIn != NULL) { AvRevertMmThreadCharacteristics(state->TaskHandleIn); }
|
|
||||||
|
|
||||||
state->StreamReady = FALSE;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Begins preparation of the WASAPI driver.
|
|
||||||
// callback: The user application's function where audio data is placed when received from the system and/or audio data is retrieved from to give to the system.
|
|
||||||
// sessionName: How your session will appear to the end user if you play audio.
|
|
||||||
// reqSampleRateIn/Out: Sample rate you'd like to request. Ignored, as this is determined by the system. See note below.
|
|
||||||
// reqChannelsIn: Input channel count you'd like to request. Ignored, as this is determined by the system. See note below.
|
|
||||||
// reqChannelsOut: Output channel count you'd like to request. Ignored, as this is determined by the system. See note below.
|
|
||||||
// sugBufferSize: Buffer size you'd like to request. Ignored, as this is determined by the system. See note below.
|
|
||||||
// inputDevice: The device you want to receive audio from. Loopback is supported, so this can be either a capture or render device.
|
|
||||||
// To get the default render device, specify "defaultRender"
|
|
||||||
// To get the default multimedia capture device, specify "defaultCapture"
|
|
||||||
// To get the default communications capture device, specify "defaultCaptureComm"
|
|
||||||
// A device ID as presented by WASAPI can be specified, regardless of what type it is. If it is invalid, the default capture device is used as fallback.
|
|
||||||
// If you do not wish to receive audio, specify null. NOT YET IMPLEMENTED
|
|
||||||
// outputDevice: The device you want to output audio to. OUTPUT IS NOT IMPLEMENTED.
|
|
||||||
// NOTES:
|
|
||||||
// Regarding format requests: Sample rate and channel count is determined by the system settings, and cannot be changed. Resampling/mixing will be required in your application if you cannot accept the current system mode. Make sure to check `WASAPIState` for the current system mode.
|
|
||||||
// Note also that both sample rate and channel count can vary between input and output!
|
|
||||||
// Currently audio output (playing) is not yet implemented.
|
|
||||||
void* InitCNFAWASAPIDriver(
|
|
||||||
CNFACBType callback, const char *sessionName,
|
|
||||||
int reqSampleRateOut, int reqSampleRateIn,
|
|
||||||
int reqChannelsOut, int reqChannelsIn, int sugBufferSize,
|
|
||||||
const char * outputDevice, const char * inputDevice,
|
|
||||||
void * opaque)
|
|
||||||
{
|
|
||||||
struct CNFADriverWASAPI * InitState = malloc(sizeof(struct CNFADriverWASAPI));
|
|
||||||
memset(InitState, 0, sizeof(*InitState));
|
|
||||||
InitState->CloseFn = CloseCNFAWASAPI;
|
|
||||||
InitState->StateFn = CNFAStateWASAPI;
|
|
||||||
InitState->Callback = callback;
|
|
||||||
InitState->Opaque = opaque;
|
|
||||||
// TODO: Waiting for CNFA to support directional sample rates.
|
|
||||||
InitState->SampleRateIn = reqSampleRateIn; // Will be overridden by the actual system setting.
|
|
||||||
InitState->SampleRateOut = reqSampleRateOut; // Will be overridden by the actual system setting.
|
|
||||||
InitState->ChannelCountIn = reqChannelsIn; // Will be overridden by the actual system setting.
|
|
||||||
InitState->ChannelCountOut = reqChannelsOut; // Will be overridden by the actual system setting.
|
|
||||||
InitState->InputDeviceID = inputDevice;
|
|
||||||
InitState->OutputDeviceID = outputDevice;
|
|
||||||
|
|
||||||
InitState->SessionName = sessionName;
|
|
||||||
|
|
||||||
WASAPIPRINT("WASAPI Init");
|
|
||||||
|
|
||||||
return StartWASAPIDriver(InitState);
|
|
||||||
}
|
|
||||||
|
|
||||||
REGISTER_CNFA(cnfa_wasapi, 20, "WASAPI", InitCNFAWASAPIDriver);
|
|
|
@ -1,636 +0,0 @@
|
||||||
#ifndef _CNFA_WASAPI_UTILS_H
|
|
||||||
#define _CNFA_WASAPI_UTILS_H
|
|
||||||
|
|
||||||
//#include "ole2.h"
|
|
||||||
|
|
||||||
#ifndef REFPROPERTYKEY
|
|
||||||
#define REFPROPERTYKEY const PROPERTYKEY * __MIDL_CONST
|
|
||||||
#endif //REFPROPERTYKEY
|
|
||||||
|
|
||||||
// Necessary definitions
|
|
||||||
#define _ANONYMOUS_STRUCT
|
|
||||||
#define BEGIN_INTERFACE
|
|
||||||
#define END_INTERFACE
|
|
||||||
#define DEVICE_STATE_ACTIVE 0x00000001
|
|
||||||
#define AUDCLNT_STREAMFLAGS_CROSSPROCESS 0x00010000
|
|
||||||
#define AUDCLNT_STREAMFLAGS_LOOPBACK 0x00020000
|
|
||||||
#define AUDCLNT_STREAMFLAGS_EVENTCALLBACK 0x00040000
|
|
||||||
#define AUDCLNT_STREAMFLAGS_NOPERSIST 0x00080000
|
|
||||||
#define AUDCLNT_STREAMFLAGS_RATEADJUST 0x00100000
|
|
||||||
#define AUDCLNT_STREAMFLAGS_PREVENT_LOOPBACK_CAPTURE 0x01000000
|
|
||||||
#define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
|
|
||||||
#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
|
|
||||||
#define AUDCLNT_SESSIONFLAGS_EXPIREWHENUNOWNED 0x10000000
|
|
||||||
#define AUDCLNT_SESSIONFLAGS_DISPLAY_HIDE 0x20000000
|
|
||||||
#define AUDCLNT_SESSIONFLAGS_DISPLAY_HIDEWHENEXPIRED 0x40000000
|
|
||||||
enum _AUDCLNT_BUFFERFLAGS
|
|
||||||
{
|
|
||||||
AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY = 0x1,
|
|
||||||
AUDCLNT_BUFFERFLAGS_SILENT = 0x2,
|
|
||||||
AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR = 0x4
|
|
||||||
} ;
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef REFIID
|
|
||||||
#define REFIID const IID * __MIDL_CONST
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef PropVariantInit
|
|
||||||
#define PropVariantInit(pvar) memset ( (pvar), 0, sizeof(PROPVARIANT) )
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined (__TINYC__)
|
|
||||||
#define _COM_Outptr_
|
|
||||||
#define _In_
|
|
||||||
#define _Out_
|
|
||||||
#define _Outptr_
|
|
||||||
#define _In_opt_
|
|
||||||
#define _Out_opt_
|
|
||||||
#define __RPC__in
|
|
||||||
#define __RPC__out
|
|
||||||
#define interface struct
|
|
||||||
#define CONST_VTBL
|
|
||||||
#define _Outptr_result_buffer_(X)
|
|
||||||
#define _Inexpressible_(X)
|
|
||||||
#define REFPROPVARIANT const PROPVARIANT * __MIDL_CONST
|
|
||||||
typedef struct tagPROPVARIANT PROPVARIANT;
|
|
||||||
typedef struct tWAVEFORMATEX WAVEFORMATEX;
|
|
||||||
typedef IID GUID;
|
|
||||||
typedef void* HANDLE;
|
|
||||||
|
|
||||||
#define CLSCTX_INPROC_SERVER 0x1
|
|
||||||
#define CLSCTX_INPROC_HANDLER 0x2
|
|
||||||
#define CLSCTX_LOCAL_SERVER 0x4
|
|
||||||
#define CLSCTX_REMOTE_SERVER 0x10
|
|
||||||
#define STGM_READ 0x00000000L
|
|
||||||
#define CLSCTX_ALL (CLSCTX_INPROC_SERVER| \
|
|
||||||
CLSCTX_INPROC_HANDLER| \
|
|
||||||
CLSCTX_LOCAL_SERVER| \
|
|
||||||
CLSCTX_REMOTE_SERVER)
|
|
||||||
typedef unsigned short VARTYPE;
|
|
||||||
|
|
||||||
typedef struct _tagpropertykey {
|
|
||||||
GUID fmtid;
|
|
||||||
DWORD pid;
|
|
||||||
} PROPERTYKEY;
|
|
||||||
|
|
||||||
#ifndef __wtypes_h__
|
|
||||||
typedef struct tagDEC {
|
|
||||||
USHORT wReserved;
|
|
||||||
BYTE scale;
|
|
||||||
BYTE sign;
|
|
||||||
ULONG Hi32;
|
|
||||||
ULONGLONG Lo64;
|
|
||||||
} DECIMAL;
|
|
||||||
|
|
||||||
// Property varient struct, used for getting the device name info
|
|
||||||
typedef BYTE PROPVAR_PAD1;
|
|
||||||
typedef BYTE PROPVAR_PAD2;
|
|
||||||
typedef ULONG PROPVAR_PAD3;
|
|
||||||
|
|
||||||
struct tagPROPVARIANT {
|
|
||||||
union {
|
|
||||||
struct tag_inner_PROPVARIANT
|
|
||||||
{
|
|
||||||
VARTYPE vt;
|
|
||||||
PROPVAR_PAD1 wReserved1;
|
|
||||||
PROPVAR_PAD2 wReserved2;
|
|
||||||
PROPVAR_PAD3 wReserved3;
|
|
||||||
union
|
|
||||||
{
|
|
||||||
double dblVal; // Filler for the largest object we need to store
|
|
||||||
LPWSTR pwszVal; // This is the only parameter we actually use
|
|
||||||
};
|
|
||||||
} ;
|
|
||||||
DECIMAL decVal;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define _Inout_updates_(dwCount)
|
|
||||||
#define FAR
|
|
||||||
|
|
||||||
|
|
||||||
typedef interface IUnknown IUnknown;
|
|
||||||
typedef IUnknown *LPUNKNOWN;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef NO_WIN_HEADERS
|
|
||||||
#undef DEFINE_GUID
|
|
||||||
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
|
||||||
EXTERN_C const GUID DECLSPEC_SELECTANY name \
|
|
||||||
= { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
|
|
||||||
#undef DEFINE_PROPERTYKEY
|
|
||||||
#define DEFINE_PROPERTYKEY(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8,pid) \
|
|
||||||
EXTERN_C const PROPERTYKEY DECLSPEC_SELECTANY name \
|
|
||||||
= { { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }, pid }
|
|
||||||
|
|
||||||
// stuff to be able to read device names
|
|
||||||
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
|
|
||||||
|
|
||||||
#ifndef WINOLEAPI
|
|
||||||
#define WINOLEAPI EXTERN_C DECLSPEC_IMPORT HRESULT STDAPICALLTYPE
|
|
||||||
#define WINOLEAPI_(type) EXTERN_C DECLSPEC_IMPORT type STDAPICALLTYPE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Define necessary functions
|
|
||||||
WINOLEAPI_(HANDLE)
|
|
||||||
AvSetMmThreadCharacteristicsW(LPCWSTR TaskName, LPDWORD TaskIndex);
|
|
||||||
|
|
||||||
WINOLEAPI_(BOOL)
|
|
||||||
AvRevertMmThreadCharacteristics(HANDLE AvrtHandle);
|
|
||||||
|
|
||||||
WINOLEAPI CoInitialize(LPVOID pvReserved);
|
|
||||||
WINOLEAPI_(void) CoUninitialize();
|
|
||||||
WINOLEAPI_(void) CoTaskMemFree(LPVOID pv);
|
|
||||||
|
|
||||||
WINOLEAPI CoCreateInstance(
|
|
||||||
REFCLSID rclsid,
|
|
||||||
LPUNKNOWN pUnkOuter,
|
|
||||||
DWORD dwClsContext,
|
|
||||||
REFIID riid,
|
|
||||||
LPVOID FAR* ppv);
|
|
||||||
|
|
||||||
// WINOLEAPI CoCreateInstanceEx(
|
|
||||||
// REFCLSID Clsid,
|
|
||||||
// IUnknown *punkOuter,
|
|
||||||
// DWORD dwClsCtx,
|
|
||||||
// COSERVERINFO *pServerInfo,
|
|
||||||
// DWORD dwCount,
|
|
||||||
// MULTI_QI *pResults );
|
|
||||||
|
|
||||||
#endif //NO_WIN_HEADERS
|
|
||||||
|
|
||||||
// forward declarations
|
|
||||||
typedef struct IMMDevice IMMDevice;
|
|
||||||
typedef struct IMMDeviceCollection IMMDeviceCollection;
|
|
||||||
typedef struct IMMDeviceEnumerator IMMDeviceEnumerator;
|
|
||||||
typedef struct IMMNotificationClient IMMNotificationClient;
|
|
||||||
typedef struct IPropertyStore IPropertyStore;
|
|
||||||
typedef struct IAudioClient IAudioClient;
|
|
||||||
typedef struct IAudioCaptureClient IAudioCaptureClient;
|
|
||||||
|
|
||||||
// So the linker doesn't complain
|
|
||||||
extern const IID CLSID_MMDeviceEnumerator;
|
|
||||||
extern const IID IID_IMMDeviceEnumerator;
|
|
||||||
extern const IID IID_IAudioClient;
|
|
||||||
extern const IID CNFA_GUID;
|
|
||||||
extern const IID IID_IAudioCaptureClient;
|
|
||||||
|
|
||||||
typedef enum __MIDL___MIDL_itf_mmdeviceapi_0000_0000_0001
|
|
||||||
{
|
|
||||||
eRender = 0,
|
|
||||||
eCapture = ( eRender + 1 ) ,
|
|
||||||
eAll = ( eCapture + 1 ) ,
|
|
||||||
EDataFlow_enum_count = ( eAll + 1 )
|
|
||||||
} EDataFlow;
|
|
||||||
|
|
||||||
typedef enum __MIDL___MIDL_itf_mmdeviceapi_0000_0000_0002
|
|
||||||
{
|
|
||||||
eConsole = 0,
|
|
||||||
eMultimedia = ( eConsole + 1 ) ,
|
|
||||||
eCommunications = ( eMultimedia + 1 ) ,
|
|
||||||
ERole_enum_count = ( eCommunications + 1 )
|
|
||||||
} ERole;
|
|
||||||
|
|
||||||
typedef struct IMMDeviceEnumeratorVtbl
|
|
||||||
{
|
|
||||||
BEGIN_INTERFACE
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
|
|
||||||
IMMDeviceEnumerator * This,
|
|
||||||
/* [in] */ REFIID riid,
|
|
||||||
/* [annotation][iid_is][out] */
|
|
||||||
_COM_Outptr_ void **ppvObject);
|
|
||||||
|
|
||||||
ULONG ( STDMETHODCALLTYPE *AddRef )(
|
|
||||||
IMMDeviceEnumerator * This);
|
|
||||||
|
|
||||||
ULONG ( STDMETHODCALLTYPE *Release )(
|
|
||||||
IMMDeviceEnumerator * This);
|
|
||||||
|
|
||||||
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *EnumAudioEndpoints )(
|
|
||||||
IMMDeviceEnumerator * This,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ EDataFlow dataFlow,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ DWORD dwStateMask,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Out_ IMMDeviceCollection **ppDevices);
|
|
||||||
|
|
||||||
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetDefaultAudioEndpoint )(
|
|
||||||
IMMDeviceEnumerator * This,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ EDataFlow dataFlow,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ ERole role,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Out_ IMMDevice **ppEndpoint);
|
|
||||||
|
|
||||||
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetDevice )(
|
|
||||||
IMMDeviceEnumerator * This,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ LPCWSTR pwstrId,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Out_ IMMDevice **ppDevice);
|
|
||||||
|
|
||||||
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *RegisterEndpointNotificationCallback )(
|
|
||||||
IMMDeviceEnumerator * This,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ IMMNotificationClient *pClient);
|
|
||||||
|
|
||||||
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *UnregisterEndpointNotificationCallback )(
|
|
||||||
IMMDeviceEnumerator * This,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ IMMNotificationClient *pClient);
|
|
||||||
|
|
||||||
END_INTERFACE
|
|
||||||
} IMMDeviceEnumeratorVtbl;
|
|
||||||
|
|
||||||
interface IMMDeviceEnumerator
|
|
||||||
{
|
|
||||||
CONST_VTBL struct IMMDeviceEnumeratorVtbl *lpVtbl;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct IMMDeviceCollectionVtbl
|
|
||||||
{
|
|
||||||
BEGIN_INTERFACE
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
|
|
||||||
IMMDeviceCollection * This,
|
|
||||||
/* [in] */ REFIID riid,
|
|
||||||
/* [annotation][iid_is][out] */
|
|
||||||
_COM_Outptr_ void **ppvObject);
|
|
||||||
|
|
||||||
ULONG ( STDMETHODCALLTYPE *AddRef )(
|
|
||||||
IMMDeviceCollection * This);
|
|
||||||
|
|
||||||
ULONG ( STDMETHODCALLTYPE *Release )(
|
|
||||||
IMMDeviceCollection * This);
|
|
||||||
|
|
||||||
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetCount )(
|
|
||||||
IMMDeviceCollection * This,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Out_ UINT *pcDevices);
|
|
||||||
|
|
||||||
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *Item )(
|
|
||||||
IMMDeviceCollection * This,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ UINT nDevice,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Out_ IMMDevice **ppDevice);
|
|
||||||
|
|
||||||
END_INTERFACE
|
|
||||||
} IMMDeviceCollectionVtbl;
|
|
||||||
|
|
||||||
interface IMMDeviceCollection
|
|
||||||
{
|
|
||||||
CONST_VTBL struct IMMDeviceCollectionVtbl *lpVtbl;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct IMMDeviceVtbl
|
|
||||||
{
|
|
||||||
BEGIN_INTERFACE
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
|
|
||||||
IMMDevice * This,
|
|
||||||
/* [in] */ REFIID riid,
|
|
||||||
/* [annotation][iid_is][out] */
|
|
||||||
_COM_Outptr_ void **ppvObject);
|
|
||||||
|
|
||||||
ULONG ( STDMETHODCALLTYPE *AddRef )(
|
|
||||||
IMMDevice * This);
|
|
||||||
|
|
||||||
ULONG ( STDMETHODCALLTYPE *Release )(
|
|
||||||
IMMDevice * This);
|
|
||||||
|
|
||||||
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *Activate )(
|
|
||||||
IMMDevice * This,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ REFIID iid,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ DWORD dwClsCtx,
|
|
||||||
/* [annotation][unique][in] */
|
|
||||||
_In_opt_ PROPVARIANT *pActivationParams,
|
|
||||||
/* [annotation][iid_is][out] */
|
|
||||||
_Out_ void **ppInterface);
|
|
||||||
|
|
||||||
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OpenPropertyStore )(
|
|
||||||
IMMDevice * This,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ DWORD stgmAccess,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Out_ IPropertyStore **ppProperties);
|
|
||||||
|
|
||||||
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetId )(
|
|
||||||
IMMDevice * This,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Outptr_ LPWSTR *ppstrId);
|
|
||||||
|
|
||||||
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetState )(
|
|
||||||
IMMDevice * This,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Out_ DWORD *pdwState);
|
|
||||||
|
|
||||||
END_INTERFACE
|
|
||||||
} IMMDeviceVtbl;
|
|
||||||
|
|
||||||
interface IMMDevice
|
|
||||||
{
|
|
||||||
CONST_VTBL struct IMMDeviceVtbl *lpVtbl;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct IMMNotificationClientVtbl
|
|
||||||
{
|
|
||||||
BEGIN_INTERFACE
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
|
|
||||||
IMMNotificationClient * This,
|
|
||||||
/* [in] */ REFIID riid,
|
|
||||||
/* [annotation][iid_is][out] */
|
|
||||||
_COM_Outptr_ void **ppvObject);
|
|
||||||
|
|
||||||
ULONG ( STDMETHODCALLTYPE *AddRef )(
|
|
||||||
IMMNotificationClient * This);
|
|
||||||
|
|
||||||
ULONG ( STDMETHODCALLTYPE *Release )(
|
|
||||||
IMMNotificationClient * This);
|
|
||||||
|
|
||||||
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OnDeviceStateChanged )(
|
|
||||||
IMMNotificationClient * This,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ LPCWSTR pwstrDeviceId,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ DWORD dwNewState);
|
|
||||||
|
|
||||||
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OnDeviceAdded )(
|
|
||||||
IMMNotificationClient * This,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ LPCWSTR pwstrDeviceId);
|
|
||||||
|
|
||||||
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OnDeviceRemoved )(
|
|
||||||
IMMNotificationClient * This,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ LPCWSTR pwstrDeviceId);
|
|
||||||
|
|
||||||
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OnDefaultDeviceChanged )(
|
|
||||||
IMMNotificationClient * This,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ EDataFlow flow,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ ERole role,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ LPCWSTR pwstrDefaultDeviceId);
|
|
||||||
|
|
||||||
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *OnPropertyValueChanged )(
|
|
||||||
IMMNotificationClient * This,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ LPCWSTR pwstrDeviceId,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ const PROPERTYKEY key);
|
|
||||||
|
|
||||||
END_INTERFACE
|
|
||||||
} IMMNotificationClientVtbl;
|
|
||||||
|
|
||||||
interface IMMNotificationClient
|
|
||||||
{
|
|
||||||
CONST_VTBL struct IMMNotificationClientVtbl *lpVtbl;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct IPropertyStoreVtbl
|
|
||||||
{
|
|
||||||
BEGIN_INTERFACE
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
|
|
||||||
__RPC__in IPropertyStore * This,
|
|
||||||
/* [in] */ __RPC__in REFIID riid,
|
|
||||||
/* [annotation][iid_is][out] */
|
|
||||||
_COM_Outptr_ void **ppvObject);
|
|
||||||
|
|
||||||
ULONG ( STDMETHODCALLTYPE *AddRef )(
|
|
||||||
__RPC__in IPropertyStore * This);
|
|
||||||
|
|
||||||
ULONG ( STDMETHODCALLTYPE *Release )(
|
|
||||||
__RPC__in IPropertyStore * This);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *GetCount )(
|
|
||||||
__RPC__in IPropertyStore * This,
|
|
||||||
/* [out] */ __RPC__out DWORD *cProps);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *GetAt )(
|
|
||||||
__RPC__in IPropertyStore * This,
|
|
||||||
/* [in] */ DWORD iProp,
|
|
||||||
/* [out] */ __RPC__out PROPERTYKEY *pkey);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *GetValue )(
|
|
||||||
__RPC__in IPropertyStore * This,
|
|
||||||
/* [in] */ __RPC__in REFPROPERTYKEY key,
|
|
||||||
/* [out] */ __RPC__out PROPVARIANT *pv);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *SetValue )(
|
|
||||||
__RPC__in IPropertyStore * This,
|
|
||||||
/* [in] */ __RPC__in REFPROPERTYKEY key,
|
|
||||||
/* [in] */ __RPC__in REFPROPVARIANT propvar);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *Commit )(
|
|
||||||
__RPC__in IPropertyStore * This);
|
|
||||||
|
|
||||||
END_INTERFACE
|
|
||||||
} IPropertyStoreVtbl;
|
|
||||||
|
|
||||||
interface IPropertyStore
|
|
||||||
{
|
|
||||||
CONST_VTBL struct IPropertyStoreVtbl *lpVtbl;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ----- audioclient.h -----
|
|
||||||
|
|
||||||
typedef enum _AUDCLNT_SHAREMODE
|
|
||||||
{
|
|
||||||
AUDCLNT_SHAREMODE_SHARED,
|
|
||||||
AUDCLNT_SHAREMODE_EXCLUSIVE
|
|
||||||
} AUDCLNT_SHAREMODE;
|
|
||||||
|
|
||||||
typedef LONGLONG REFERENCE_TIME;
|
|
||||||
|
|
||||||
typedef struct IAudioClientVtbl
|
|
||||||
{
|
|
||||||
BEGIN_INTERFACE
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
|
|
||||||
IAudioClient * This,
|
|
||||||
/* [in] */ REFIID riid,
|
|
||||||
/* [annotation][iid_is][out] */
|
|
||||||
_COM_Outptr_ void **ppvObject);
|
|
||||||
|
|
||||||
ULONG ( STDMETHODCALLTYPE *AddRef )(
|
|
||||||
IAudioClient * This);
|
|
||||||
|
|
||||||
ULONG ( STDMETHODCALLTYPE *Release )(
|
|
||||||
IAudioClient * This);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *Initialize )(
|
|
||||||
IAudioClient * This,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ AUDCLNT_SHAREMODE ShareMode,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ DWORD StreamFlags,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ REFERENCE_TIME hnsBufferDuration,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ REFERENCE_TIME hnsPeriodicity,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ const WAVEFORMATEX *pFormat,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_opt_ LPCGUID AudioSessionGuid);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *GetBufferSize )(
|
|
||||||
IAudioClient * This,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Out_ UINT32 *pNumBufferFrames);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *GetStreamLatency )(
|
|
||||||
IAudioClient * This,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Out_ REFERENCE_TIME *phnsLatency);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *GetCurrentPadding )(
|
|
||||||
IAudioClient * This,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Out_ UINT32 *pNumPaddingFrames);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *IsFormatSupported )(
|
|
||||||
IAudioClient * This,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ AUDCLNT_SHAREMODE ShareMode,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ const WAVEFORMATEX *pFormat,
|
|
||||||
/* [unique][annotation][out] */
|
|
||||||
_Out_opt_ WAVEFORMATEX **ppClosestMatch);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *GetMixFormat )(
|
|
||||||
IAudioClient * This,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Out_ WAVEFORMATEX **ppDeviceFormat);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *GetDevicePeriod )(
|
|
||||||
IAudioClient * This,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Out_opt_ REFERENCE_TIME *phnsDefaultDevicePeriod,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Out_opt_ REFERENCE_TIME *phnsMinimumDevicePeriod);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *Start )(
|
|
||||||
IAudioClient * This);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *Stop )(
|
|
||||||
IAudioClient * This);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *Reset )(
|
|
||||||
IAudioClient * This);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *SetEventHandle )(
|
|
||||||
IAudioClient * This,
|
|
||||||
/* [in] */ HANDLE eventHandle);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *GetService )(
|
|
||||||
IAudioClient * This,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ REFIID riid,
|
|
||||||
/* [annotation][iid_is][out] */
|
|
||||||
_Out_ void **ppv);
|
|
||||||
|
|
||||||
END_INTERFACE
|
|
||||||
} IAudioClientVtbl;
|
|
||||||
|
|
||||||
interface IAudioClient
|
|
||||||
{
|
|
||||||
CONST_VTBL struct IAudioClientVtbl *lpVtbl;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct IAudioCaptureClientVtbl
|
|
||||||
{
|
|
||||||
BEGIN_INTERFACE
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
|
|
||||||
IAudioCaptureClient * This,
|
|
||||||
/* [in] */ REFIID riid,
|
|
||||||
/* [annotation][iid_is][out] */
|
|
||||||
_COM_Outptr_ void **ppvObject);
|
|
||||||
|
|
||||||
ULONG ( STDMETHODCALLTYPE *AddRef )(
|
|
||||||
IAudioCaptureClient * This);
|
|
||||||
|
|
||||||
ULONG ( STDMETHODCALLTYPE *Release )(
|
|
||||||
IAudioCaptureClient * This);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *GetBuffer )(
|
|
||||||
IAudioCaptureClient * This,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Outptr_result_buffer_(_Inexpressible_("*pNumFramesToRead * pFormat->nBlockAlign")) BYTE **ppData,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Out_ UINT32 *pNumFramesToRead,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Out_ DWORD *pdwFlags,
|
|
||||||
/* [annotation][unique][out] */
|
|
||||||
_Out_opt_ UINT64 *pu64DevicePosition,
|
|
||||||
/* [annotation][unique][out] */
|
|
||||||
_Out_opt_ UINT64 *pu64QPCPosition);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *ReleaseBuffer )(
|
|
||||||
IAudioCaptureClient * This,
|
|
||||||
/* [annotation][in] */
|
|
||||||
_In_ UINT32 NumFramesRead);
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *GetNextPacketSize )(
|
|
||||||
IAudioCaptureClient * This,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Out_ UINT32 *pNumFramesInNextPacket);
|
|
||||||
|
|
||||||
END_INTERFACE
|
|
||||||
} IAudioCaptureClientVtbl;
|
|
||||||
|
|
||||||
interface IAudioCaptureClient
|
|
||||||
{
|
|
||||||
CONST_VTBL struct IAudioCaptureClientVtbl *lpVtbl;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef interface IMMEndpoint IMMEndpoint;
|
|
||||||
|
|
||||||
typedef struct IMMEndpointVtbl
|
|
||||||
{
|
|
||||||
BEGIN_INTERFACE
|
|
||||||
|
|
||||||
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
|
|
||||||
IMMEndpoint * This,
|
|
||||||
/* [in] */ REFIID riid,
|
|
||||||
/* [annotation][iid_is][out] */
|
|
||||||
_COM_Outptr_ void **ppvObject);
|
|
||||||
|
|
||||||
ULONG ( STDMETHODCALLTYPE *AddRef )(
|
|
||||||
IMMEndpoint * This);
|
|
||||||
|
|
||||||
ULONG ( STDMETHODCALLTYPE *Release )(
|
|
||||||
IMMEndpoint * This);
|
|
||||||
|
|
||||||
/* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *GetDataFlow )(
|
|
||||||
IMMEndpoint * This,
|
|
||||||
/* [annotation][out] */
|
|
||||||
_Out_ EDataFlow *pDataFlow);
|
|
||||||
|
|
||||||
END_INTERFACE
|
|
||||||
} IMMEndpointVtbl;
|
|
||||||
|
|
||||||
interface IMMEndpoint
|
|
||||||
{
|
|
||||||
CONST_VTBL struct IMMEndpointVtbl *lpVtbl;
|
|
||||||
};
|
|
||||||
|
|
||||||
#define DEVICE_STATE_ACTIVE 0x00000001
|
|
||||||
#define DEVICE_STATE_DISABLED 0x00000002
|
|
||||||
#define DEVICE_STATE_NOTPRESENT 0x00000004
|
|
||||||
#define DEVICE_STATE_UNPLUGGED 0x00000008
|
|
||||||
#define DEVICE_STATEMASK_ALL 0x0000000f
|
|
||||||
|
|
||||||
#endif // _CNFA_WASAPI_UTILS_H
|
|
|
@ -1,258 +0,0 @@
|
||||||
//Copyright 2015-2020 <>< Charles Lohr under the ColorChord License, MIT/x11 license or NewBSD Licenses.
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
#include "CNFA.h"
|
|
||||||
#include "os_generic.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <mmsystem.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
//Include -lwinmm, or, C:/windows/system32/winmm.dll
|
|
||||||
|
|
||||||
#if defined(WINDOWS) || defined(WIN32) || defined(WIN64) \
|
|
||||||
|| defined(_WIN32) || defined(_WIN64)
|
|
||||||
#ifndef strdup
|
|
||||||
#define strdup _strdup
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(WIN32) && !defined( TCC )
|
|
||||||
#pragma comment(lib,"winmm.lib")
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define BUFFS 3
|
|
||||||
|
|
||||||
struct CNFADriverWin
|
|
||||||
{
|
|
||||||
//Standard header - must remain.
|
|
||||||
void (*CloseFn)( void * object );
|
|
||||||
int (*StateFn)( void * object );
|
|
||||||
CNFACBType callback;
|
|
||||||
short channelsPlay;
|
|
||||||
short channelsRec;
|
|
||||||
int spsPlay;
|
|
||||||
int spsRec;
|
|
||||||
void * opaque;
|
|
||||||
|
|
||||||
char * sInputDev;
|
|
||||||
char * sOutputDev;
|
|
||||||
|
|
||||||
int buffer;
|
|
||||||
int isEnding;
|
|
||||||
int GOBUFFRec;
|
|
||||||
int GOBUFFPlay;
|
|
||||||
|
|
||||||
int recording;
|
|
||||||
int playing;
|
|
||||||
|
|
||||||
HWAVEIN hMyWaveIn;
|
|
||||||
HWAVEOUT hMyWaveOut;
|
|
||||||
WAVEHDR WavBuffIn[BUFFS];
|
|
||||||
WAVEHDR WavBuffOut[BUFFS];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
static struct CNFADriverWin * w;
|
|
||||||
|
|
||||||
void CloseCNFAWin( void * v )
|
|
||||||
{
|
|
||||||
struct CNFADriverWin * r = (struct CNFADriverWin *)v;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if( r )
|
|
||||||
{
|
|
||||||
if( r->hMyWaveIn )
|
|
||||||
{
|
|
||||||
waveInStop(r->hMyWaveIn);
|
|
||||||
waveInReset(r->hMyWaveIn);
|
|
||||||
for ( i=0;i<BUFFS;i++)
|
|
||||||
{
|
|
||||||
waveInUnprepareHeader(r->hMyWaveIn,&(r->WavBuffIn[i]),sizeof(WAVEHDR));
|
|
||||||
free ((r->WavBuffIn[i]).lpData);
|
|
||||||
}
|
|
||||||
waveInClose(r->hMyWaveIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
if( r->hMyWaveOut )
|
|
||||||
{
|
|
||||||
waveOutPause(r->hMyWaveOut);
|
|
||||||
waveOutReset(r->hMyWaveOut);
|
|
||||||
|
|
||||||
for ( i=0;i<BUFFS;i++)
|
|
||||||
{
|
|
||||||
waveInUnprepareHeader(r->hMyWaveIn,&(r->WavBuffOut[i]),sizeof(WAVEHDR));
|
|
||||||
free ((r->WavBuffOut[i]).lpData);
|
|
||||||
}
|
|
||||||
waveInClose(r->hMyWaveIn);
|
|
||||||
waveOutClose(r->hMyWaveOut);
|
|
||||||
}
|
|
||||||
free( r );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int CNFAStateWin( void * v )
|
|
||||||
{
|
|
||||||
struct CNFADriverWin * soundobject = (struct CNFADriverWin *)v;
|
|
||||||
|
|
||||||
return soundobject->recording | (soundobject->playing?2:0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CALLBACK HANDLEMIC(HWAVEIN hwi, UINT umsg, DWORD dwi, DWORD hdr, DWORD dwparm)
|
|
||||||
{
|
|
||||||
int ctr;
|
|
||||||
int ob;
|
|
||||||
long cValue;
|
|
||||||
unsigned int maxWave=0;
|
|
||||||
|
|
||||||
if (w->isEnding) return;
|
|
||||||
|
|
||||||
switch (umsg)
|
|
||||||
{
|
|
||||||
case MM_WIM_OPEN:
|
|
||||||
printf( "Mic Open.\n" );
|
|
||||||
w->recording = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MM_WIM_DATA:
|
|
||||||
ob = (w->GOBUFFRec+(BUFFS))%BUFFS;
|
|
||||||
w->callback( (struct CNFADriver*)w, 0, (short*)(w->WavBuffIn[w->GOBUFFRec]).lpData, 0, w->buffer );
|
|
||||||
waveInAddBuffer(w->hMyWaveIn,&(w->WavBuffIn[w->GOBUFFRec]),sizeof(WAVEHDR));
|
|
||||||
w->GOBUFFRec = ( w->GOBUFFRec + 1 ) % BUFFS;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void CALLBACK HANDLESINK(HWAVEIN hwi, UINT umsg, DWORD dwi, DWORD hdr, DWORD dwparm)
|
|
||||||
{
|
|
||||||
int ctr;
|
|
||||||
long cValue;
|
|
||||||
unsigned int maxWave=0;
|
|
||||||
|
|
||||||
if (w->isEnding) return;
|
|
||||||
|
|
||||||
switch (umsg)
|
|
||||||
{
|
|
||||||
case MM_WOM_OPEN:
|
|
||||||
printf( "Sink Open.\n" );
|
|
||||||
w->playing = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MM_WOM_DONE:
|
|
||||||
w->callback( (struct CNFADriver*)w, (short*)(w->WavBuffOut[w->GOBUFFPlay]).lpData, 0, w->buffer, 0 );
|
|
||||||
waveOutWrite( w->hMyWaveOut, &(w->WavBuffOut[w->GOBUFFPlay]),sizeof(WAVEHDR) );
|
|
||||||
w->GOBUFFPlay = ( w->GOBUFFPlay + 1 ) % BUFFS;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static struct CNFADriverWin * InitWinCNFA( struct CNFADriverWin * r )
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
WAVEFORMATEX wfmt;
|
|
||||||
memset( &wfmt, 0, sizeof(wfmt) );
|
|
||||||
printf ("WFMT Size (debugging temp for TCC): %llu\n", sizeof(wfmt) );
|
|
||||||
printf( "WFMT: %d %d %d\n", r->channelsRec, r->spsRec, r->spsRec * r->channelsRec );
|
|
||||||
w = r;
|
|
||||||
|
|
||||||
wfmt.wFormatTag = WAVE_FORMAT_PCM;
|
|
||||||
wfmt.nChannels = r->channelsRec;
|
|
||||||
wfmt.nAvgBytesPerSec = r->spsRec * r->channelsRec;
|
|
||||||
wfmt.nBlockAlign = r->channelsRec * 2;
|
|
||||||
wfmt.nSamplesPerSec = r->spsRec;
|
|
||||||
wfmt.wBitsPerSample = 16;
|
|
||||||
wfmt.cbSize = 0;
|
|
||||||
|
|
||||||
long dwdeviceR, dwdeviceP;
|
|
||||||
dwdeviceR = r->sInputDev?atoi(r->sInputDev):WAVE_MAPPER;
|
|
||||||
dwdeviceP = r->sOutputDev?atoi(r->sOutputDev):WAVE_MAPPER;
|
|
||||||
|
|
||||||
if( r->channelsRec )
|
|
||||||
{
|
|
||||||
printf( "In Wave Devs: %d; WAVE_MAPPER: %d; Selected Input: %ld\n", waveInGetNumDevs(), WAVE_MAPPER, dwdeviceR );
|
|
||||||
int p = waveInOpen(&r->hMyWaveIn, dwdeviceR, &wfmt, (intptr_t)(&HANDLEMIC), 0, CALLBACK_FUNCTION);
|
|
||||||
if( p )
|
|
||||||
{
|
|
||||||
fprintf( stderr, "Error performing waveInOpen. Received code: %d\n", p );
|
|
||||||
}
|
|
||||||
printf( "waveInOpen: %d\n", p );
|
|
||||||
for ( i=0;i<BUFFS;i++)
|
|
||||||
{
|
|
||||||
memset( &(r->WavBuffIn[i]), 0, sizeof(r->WavBuffIn[i]) );
|
|
||||||
(r->WavBuffIn[i]).dwBufferLength = r->buffer*2*r->channelsRec;
|
|
||||||
(r->WavBuffIn[i]).dwLoops = 1;
|
|
||||||
(r->WavBuffIn[i]).lpData=(char*) malloc(r->buffer*r->channelsRec*2);
|
|
||||||
printf( "buffer gen size: %d: %p\n", r->buffer*r->channelsRec*2, (r->WavBuffIn[i]).lpData );
|
|
||||||
p = waveInPrepareHeader(r->hMyWaveIn,&(r->WavBuffIn[i]),sizeof(WAVEHDR));
|
|
||||||
printf( "WIPr: %d\n", p );
|
|
||||||
waveInAddBuffer(r->hMyWaveIn,&(r->WavBuffIn[i]),sizeof(WAVEHDR));
|
|
||||||
printf( "WIAr: %d\n", p );
|
|
||||||
}
|
|
||||||
p = waveInStart(r->hMyWaveIn);
|
|
||||||
if( p )
|
|
||||||
{
|
|
||||||
fprintf( stderr, "Error performing waveInStart. Received code %d\n", p );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wfmt.nChannels = r->channelsPlay;
|
|
||||||
wfmt.nAvgBytesPerSec = r->spsPlay * r->channelsPlay;
|
|
||||||
wfmt.nBlockAlign = r->channelsPlay * 2;
|
|
||||||
wfmt.nSamplesPerSec = r->spsPlay;
|
|
||||||
|
|
||||||
if( r->channelsPlay )
|
|
||||||
{
|
|
||||||
printf( "Out Wave Devs: %d; WAVE_MAPPER: %d; Selected Input: %ld\n", waveOutGetNumDevs(), WAVE_MAPPER, dwdeviceP );
|
|
||||||
int p = waveOutOpen( &r->hMyWaveOut, dwdeviceP, &wfmt, (intptr_t)(void*)(&HANDLESINK), (intptr_t)r, CALLBACK_FUNCTION);
|
|
||||||
if( p )
|
|
||||||
{
|
|
||||||
fprintf( stderr, "Error performing waveOutOpen. Received code: %d\n", p );
|
|
||||||
}
|
|
||||||
printf( "waveOutOpen: %d\n", p );
|
|
||||||
for ( i=0;i<BUFFS;i++)
|
|
||||||
{
|
|
||||||
memset( &(r->WavBuffOut[i]), 0, sizeof(r->WavBuffOut[i]) );
|
|
||||||
(r->WavBuffOut[i]).dwBufferLength = r->buffer*2*r->channelsPlay;
|
|
||||||
(r->WavBuffOut[i]).dwLoops = 1;
|
|
||||||
int size = r->buffer*r->channelsPlay*2;
|
|
||||||
char * buf = (r->WavBuffOut[i]).lpData=(char*) malloc(size);
|
|
||||||
memset( buf, 0, size );
|
|
||||||
p = waveOutPrepareHeader(r->hMyWaveOut,&(r->WavBuffOut[i]),sizeof(WAVEHDR));
|
|
||||||
waveOutWrite( r->hMyWaveOut, &(r->WavBuffOut[i]),sizeof(WAVEHDR));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void * InitCNFAWin( CNFACBType cb, const char * your_name, int reqSPSPlay, int reqSPSRec, int reqChannelsPlay, int reqChannelsRec, int sugBufferSize, const char * outputSelect, const char * inputSelect, void * opaque )
|
|
||||||
{
|
|
||||||
struct CNFADriverWin * r = (struct CNFADriverWin *)malloc( sizeof( struct CNFADriverWin ) );
|
|
||||||
memset( r, 0, sizeof(*r) );
|
|
||||||
r->CloseFn = CloseCNFAWin;
|
|
||||||
r->StateFn = CNFAStateWin;
|
|
||||||
r->callback = cb;
|
|
||||||
r->opaque = opaque;
|
|
||||||
r->spsPlay = reqSPSPlay;
|
|
||||||
r->spsRec = reqSPSRec;
|
|
||||||
r->channelsPlay = reqChannelsPlay;
|
|
||||||
r->channelsRec = reqChannelsRec;
|
|
||||||
r->buffer = sugBufferSize;
|
|
||||||
r->sInputDev = inputSelect?strdup(inputSelect):0;
|
|
||||||
r->sOutputDev = outputSelect?strdup(outputSelect):0;
|
|
||||||
|
|
||||||
r->recording = 0;
|
|
||||||
r->playing = 0;
|
|
||||||
r->isEnding = 0;
|
|
||||||
r->GOBUFFPlay = 0;
|
|
||||||
r->GOBUFFRec = 0;
|
|
||||||
|
|
||||||
return InitWinCNFA(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
REGISTER_CNFA( WinCNFA, 10, "WIN", InitCNFAWin );
|
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2020 <>< Charles Lohr
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,34 +0,0 @@
|
||||||
.PHONY: all shared shared-example wave_player clean
|
|
||||||
|
|
||||||
all: example wav_player
|
|
||||||
|
|
||||||
os_generic.h:
|
|
||||||
wget https://raw.githubusercontent.com/cntools/rawdraw/master/os_generic.h
|
|
||||||
|
|
||||||
LDFLAGS = -lasound -lpthread
|
|
||||||
ifeq "$(shell pkgconf --exists pulse && echo 'found' )" "found"
|
|
||||||
PULSE ?= "YES"
|
|
||||||
else
|
|
||||||
PULSE ?= "NO"
|
|
||||||
endif
|
|
||||||
ifeq "$(PULSE)" "YES"
|
|
||||||
LDFLAGS += -lpulse -DPULSEAUDIO
|
|
||||||
endif
|
|
||||||
|
|
||||||
example : example.c os_generic.h
|
|
||||||
$(CC) -o $@ $^ $(LDFLAGS) -lm
|
|
||||||
|
|
||||||
wav_player: os_generic.h
|
|
||||||
make -C wave_player PULSE=$(PULSE)
|
|
||||||
|
|
||||||
shared : os_generic.h
|
|
||||||
$(CC) CNFA.c -shared -fpic -o libCNFA.so -DCNFA_IMPLEMENTATION -DBUILD_DLL \
|
|
||||||
$(LDFLAGS)
|
|
||||||
|
|
||||||
shared-example : example.c shared
|
|
||||||
$(CC) -L. -Wl,-rpath=. -o example example.c -DUSE_SHARED -lm -lCNFA
|
|
||||||
|
|
||||||
clean :
|
|
||||||
rm -rf *.o *~ example libCNFA.so
|
|
||||||
make -C wave_player clean
|
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
# cnfa
|
|
||||||
CN's foundational audio drivers
|
|
||||||
|
|
||||||
See CNFA.h for more help and use info.
|
|
||||||
|
|
||||||
This toolset is 100% based around sound callbacks.
|
|
||||||
|
|
||||||
You get three functions:
|
|
||||||
|
|
||||||
```C
|
|
||||||
struct CNFADriver* CNFAInit(const char* driver_name,
|
|
||||||
const char* your_name,
|
|
||||||
CNFACBType cb,
|
|
||||||
int reqSPSPlay,
|
|
||||||
int reqSPSRec,
|
|
||||||
int reqChannelsPlay,
|
|
||||||
int reqChannelsRec,
|
|
||||||
int sugBufferSize,
|
|
||||||
const char* outputSelect,
|
|
||||||
const char* inputSelect,
|
|
||||||
void* opaque)
|
|
||||||
|
|
||||||
// Returns bitmask: 1 if mic recording, 2 if playback running, 3 if both running.
|
|
||||||
int CNFAState(struct CNFADriver* cnfaobject)
|
|
||||||
|
|
||||||
void CNFAClose(struct CNFADriver* cnfaobject)
|
|
||||||
```
|
|
||||||
|
|
||||||
Then it goes and calls a callback function, the `CNFACBType cb` parameter. This can feed you new frames, or you can pass frames back in.
|
|
||||||
`framesp` is the size of one channel of the output buffer in samples. If there are multiple channels they should be interleaved i.e. L:R:L:R...
|
|
||||||
`framerp` is the size of one channel of the input buffer in samples. Works the same way as the output buffer.
|
|
||||||
You can obtain the number of input/output channels from `sd->channelsRec` and `sd->channelsPlay` respectively.
|
|
||||||
|
|
||||||
```C
|
|
||||||
void Callback(struct CNFADriver* sd, short* out, short* in, int framesp, int framesr)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
for( i = 0; i < framesr * sd->channelsRec; i++ )
|
|
||||||
short value = in[i];
|
|
||||||
for( i = 0; i < framesp * sd->channelsPlay; i++ )
|
|
||||||
out[i] = 0; //Send output frames.
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
There are two examples in this repository, [example.c](example.c) and [wave_player.c](wave_player/wav_player.c). Both of these show examples of using
|
|
||||||
CNFA to output sound. For use of CNFA for input see [colorchord](https://github.com/cnlohr/colorchord)
|
|
||||||
|
|
||||||
### Building .DLL and .SO files
|
|
||||||
If you would like to use CNFA in a project where using a DLL or SO file is more practical, you can easily build those files. The below steps are for the Clang & GGC compilers, but others like TCC should work fine as well, just have not been tested.
|
|
||||||
|
|
||||||
NOTE: In order for functions to be exported, you'll need to make sure `-DBUILD_DLL` is specified!
|
|
||||||
|
|
||||||
Parts of CNFA rely on [rawdraw](https://github.com/cntools/rawdraw), so make sure to clone this repo somewhere and insert the path to it in the `[RAWDRAW PATH]` space below:
|
|
||||||
|
|
||||||
**Don't forget to install all libraries' headers!** For Linux, at least these packages: `libasound2-dev`, `libpulse-dev`
|
|
||||||
|
|
||||||
Windows build:
|
|
||||||
```PS
|
|
||||||
& "C:\Program Files\LLVM\bin\clang.exe" CNFA.c -shared -o CNFA.dll -DWINDOWS -DCNFA_IMPLEMENTATION -DBUILD_DLL -D_CRT_SECURE_NO_WARNINGS -I"[RAWDRAW PATH]" -lmmdevapi -lavrt -lole32
|
|
||||||
```
|
|
||||||
|
|
||||||
Linux build:
|
|
||||||
```Bash
|
|
||||||
make shared-example
|
|
||||||
```
|
|
||||||
This will build the shared library as `libCNFA.so` and will build the example
|
|
||||||
project to link against it. You can run the example project with `./example`.
|
|
||||||
The makefile will try and copy the `os_generic.h` header from
|
|
||||||
[rawdraw](https://github.com/cntools/rawdraw)
|
|
||||||
automatically using `wget`.
|
|
||||||
|
|
||||||
The command to build simply build the shared library is:
|
|
||||||
```Bash
|
|
||||||
gcc CNFA.c -shared -fpic -o CNFA.so -DCNFA_IMPLEMENTATION -DBUILD_DLL -I"[RAWDRAW PATH]" -lasound -lpulse -lpthread
|
|
||||||
```
|
|
|
@ -1,65 +0,0 @@
|
||||||
#include <stdio.h>
|
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
// If using the shared library, don't define CNFA_IMPLEMENTATION
|
|
||||||
// (it's already in the library).
|
|
||||||
#ifndef USE_SHARED
|
|
||||||
#define CNFA_IMPLEMENTATION
|
|
||||||
#endif
|
|
||||||
#include "CNFA.h"
|
|
||||||
|
|
||||||
#define RUNTIME 500000
|
|
||||||
|
|
||||||
double omega = 0;
|
|
||||||
int totalframesr = 0;
|
|
||||||
int totalframesp = 0;
|
|
||||||
|
|
||||||
void Callback( struct CNFADriver * sd, short * out, short * in, int framesp, int framesr )
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
totalframesr += framesr;
|
|
||||||
totalframesp += framesp;
|
|
||||||
|
|
||||||
int channels = sd->channelsPlay;
|
|
||||||
for( i = 0; i < framesp; i++ )
|
|
||||||
{
|
|
||||||
// Shift phase, so we run at 440 Hz (A4)
|
|
||||||
omega += ( 3.14159 * 2 * 440. ) / sd->spsPlay;
|
|
||||||
|
|
||||||
// Make the 440 Hz tone at 10% volume and convert to short.
|
|
||||||
short value = sin( omega ) * 0.1 * 32767;
|
|
||||||
|
|
||||||
int c;
|
|
||||||
for( c = 0; c < channels; c++ )
|
|
||||||
{
|
|
||||||
*(out++) = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct CNFADriver * cnfa;
|
|
||||||
|
|
||||||
int main( int argc, char ** argv )
|
|
||||||
{
|
|
||||||
cnfa = CNFAInit(
|
|
||||||
|
|
||||||
//"PULSE",
|
|
||||||
"ALSA", //You can select a plaback driver, or use 0 for default.
|
|
||||||
//0, //default
|
|
||||||
"cnfa_example", Callback,
|
|
||||||
48000, //Requested samplerate for playback
|
|
||||||
48000, //Requested samplerate for recording
|
|
||||||
2, //Number of playback channels.
|
|
||||||
2, //Number of record channels.
|
|
||||||
1024, //Buffer size in frames.
|
|
||||||
0, //Could be a string, for the selected input device - but 0 means default.
|
|
||||||
0, //Could be a string, for the selected output device - but 0 means default.
|
|
||||||
0 // 'opaque' value if the driver wanted it.
|
|
||||||
);
|
|
||||||
|
|
||||||
sleep( RUNTIME );
|
|
||||||
printf( "Received %d (%d per sec) frames\nSent %d (%d per sec) frames\n", totalframesr, totalframesr/RUNTIME, totalframesp, totalframesp/RUNTIME );
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
.PHONY: clean
|
|
||||||
|
|
||||||
C_SRCS = wav_player.c
|
|
||||||
OUT := wav_player
|
|
||||||
|
|
||||||
CFLAGS = -O2 -g
|
|
||||||
LDFLAGS = -lasound -lpthread -lm
|
|
||||||
CC ?= gcc -std=c99
|
|
||||||
ifeq "$(shell pkgconf --exists pulse && echo 'found' )" "found"
|
|
||||||
PULSE ?= "YES"
|
|
||||||
else
|
|
||||||
PULSE ?= "NO"
|
|
||||||
endif
|
|
||||||
ifeq "$(PULSE)" "YES"
|
|
||||||
LDFLAGS += -lpulse
|
|
||||||
endif
|
|
||||||
|
|
||||||
OBJS := $(C_SRCS:.c=.o)
|
|
||||||
|
|
||||||
$(OUT): $(OBJS)
|
|
||||||
$(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) -o $(OUT)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm $(OBJS) $(OUT)
|
|
||||||
|
|
||||||
.c.o:
|
|
||||||
$(CC) $(CFLAGS) -c $< -o $@
|
|
|
@ -1,117 +0,0 @@
|
||||||
/*
|
|
||||||
* wavDefs.h Samuel Ellicott - 9-3-13
|
|
||||||
* defines basic features of PCM encoded wav files
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _WAV_DEFS_H_
|
|
||||||
#define _WAV_DEFS_H_
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
//#define ABSOLUTE 1
|
|
||||||
#ifdef ABSOLUTE
|
|
||||||
|
|
||||||
//absolute offsets PCM audacity files only
|
|
||||||
// name offset description
|
|
||||||
//RIFF chunk descriptor
|
|
||||||
#define CHUNK_ID 0 //start of the riff chunk
|
|
||||||
#define CHUNK_SIZE 4 //size of the file minus the first 2 entries
|
|
||||||
#define FORMAT 8 //describes the format aka. WAVE
|
|
||||||
|
|
||||||
//fmt sub-chunk descriptor
|
|
||||||
#define FMT_CHUNK_ID 12 //start of the fmt sub chunk
|
|
||||||
#define FMT_CHUNK_SIZE 16 //length of the rest of the sub-chunk. for PCM should be 16
|
|
||||||
#define AUDIO_FORMAT 20 //audio format - for PCM should be 1 (unsigned char)
|
|
||||||
#define NUM_CHANNELS 22 //the number of channels 1 or 2 (unsigned char)
|
|
||||||
#define SAMPLE_RATE 24 //sample rate 8000, 44100, or 48000 typical in hz
|
|
||||||
#define BIT_RATE 28 //bit rate - (SampleRate * NumChannels * BitsPerSample)/8
|
|
||||||
#define BLOCK_ALIGN 32 //NumChannels * BitsPerSample/8 (unsigned char)
|
|
||||||
//1 - 8 bit mono, 2 - 8 bit stereo/16 bit mono, 4 - 16 bit stereo
|
|
||||||
#define BITS_PER_SAMPLE 34 //bits per channel 8 or 16 (unsigned char)
|
|
||||||
|
|
||||||
|
|
||||||
//fmt sub-chunk descriptor
|
|
||||||
#define CHUNK_ID_1 12 //start of the fmt sub chunk
|
|
||||||
#define CHUNK_SIZE_1 16 //length of the rest of the sub-chunk. for PCM should be 1
|
|
||||||
|
|
||||||
//info data sub-chunk descriptor
|
|
||||||
//if Wav_Data.is_info == 1 than this is info chunk
|
|
||||||
//if Wav_Data.is_info == 0 than this is data chunk
|
|
||||||
#define _CHUNK_ID 36 //start of data sub chunk
|
|
||||||
#define _CHUNK_SIZE 40 //chunk size of sub chunk
|
|
||||||
#define _CHUNK_DATA 44 //data in chunk
|
|
||||||
|
|
||||||
#else
|
|
||||||
//general chunk descriptor
|
|
||||||
#define CHUNK_ID 0 //start of the riff chunk
|
|
||||||
#define CHUNK_SIZE 4 //size of the file minus the first 2 entries
|
|
||||||
#define FORMAT 8 //describes the format for RIFF and LIST chunks
|
|
||||||
#define CHUNK_DATA 8 //start of data for other chunks
|
|
||||||
#define CHUNK_ID_LEN 5 //length of chunk ID string (including null char)
|
|
||||||
|
|
||||||
//fmt sub-chunk descriptor
|
|
||||||
#define FMT_CHUNK_ID 0 //start of the fmt sub chunk
|
|
||||||
#define FMT_CHUNK_SIZE 4 //length of the rest of the sub-chunk. for PCM should be 16
|
|
||||||
#define AUDIO_FORMAT 8 //audio format - for PCM should be 1 (unsigned char)
|
|
||||||
#define NUM_CHANNELS 10 //the number of channels 1 or 2 (unsigned char)
|
|
||||||
#define SAMPLE_RATE 12 //sample rate 8000, 44100, or 48000 typical in hz
|
|
||||||
#define BIT_RATE 16 //bit rate - (SampleRate * NumChannels * BitsPerSample)/8
|
|
||||||
#define BLOCK_ALIGN 20 //NumChannels * BitsPerSample/8 (unsigned char)
|
|
||||||
//1 - 8 bit mono, 2 - 8 bit stereo/16 bit mono, 4 - 16 bit stereo
|
|
||||||
#define BITS_PER_SAMPLE 22 //bits per channel 8 or 16 (unsigned char)
|
|
||||||
|
|
||||||
#endif //ABSOLUTE
|
|
||||||
|
|
||||||
#define MAX_TAG_SIZE 100 //defines the maximum number of characters to be allocated for any info string
|
|
||||||
|
|
||||||
/*
|
|
||||||
* data on the optional INFO data chunk contains pointers to the following info:
|
|
||||||
* title
|
|
||||||
* author
|
|
||||||
* genre
|
|
||||||
*/
|
|
||||||
typedef struct WaveInfoChunk{
|
|
||||||
uint8_t is_info; //1 for info data, 0 for no info data
|
|
||||||
uint32_t info_offset; //the file offset for the info chunk
|
|
||||||
uint32_t info_len; //length of the info chunk
|
|
||||||
|
|
||||||
//begin heap pointers
|
|
||||||
char *title;
|
|
||||||
char *artist;
|
|
||||||
char *genre;
|
|
||||||
char *creation_date;
|
|
||||||
} WaveInfoChunk;
|
|
||||||
/*
|
|
||||||
* data structure containing the data neccesarry for PCM audio playback
|
|
||||||
*/
|
|
||||||
typedef struct WaveFmtChunk{
|
|
||||||
uint32_t fmt_len; //fmt size: 16 for pcm
|
|
||||||
uint16_t audio_format; //1 = PCM
|
|
||||||
uint16_t num_channels; //1 for mono, 2 for stereo
|
|
||||||
uint32_t sample_rate; //44100 (CD), 48000 (DAT)
|
|
||||||
uint32_t byte_rate; //SampleRate * NumChannels * BitsPerSample/8
|
|
||||||
uint16_t block_align; //1 - 8 bit mono
|
|
||||||
//2 - 8 bit stereo/16 bit mono
|
|
||||||
//4 - 16 bit stereo
|
|
||||||
uint16_t bits_per_sample; //8 or 16
|
|
||||||
uint8_t bytes_per_sample;
|
|
||||||
} WaveFmtChunk;
|
|
||||||
|
|
||||||
typedef struct WaveDataChunk{
|
|
||||||
uint32_t data_offset; //the file offset for data chunk
|
|
||||||
uint32_t data_size; //data size in bytes
|
|
||||||
uint32_t current_offset; //current offset in file in bytes
|
|
||||||
uint16_t num_samples; //number of samples
|
|
||||||
uint16_t samples_left; //number of samples left to read
|
|
||||||
} WaveDataChunk;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* struct all the pertinent information for the wav file
|
|
||||||
*/
|
|
||||||
typedef struct WaveHeaderChunk{
|
|
||||||
struct WaveFmtChunk fmt; //the wave format chunk
|
|
||||||
struct WaveInfoChunk info; //the info metadata chunk
|
|
||||||
struct WaveDataChunk data;
|
|
||||||
} WaveHeaderChunk;
|
|
||||||
|
|
||||||
#endif //_WAV_DEFS_H_
|
|
|
@ -1,389 +0,0 @@
|
||||||
/*
|
|
||||||
* Sam Ellicott - 09-06-22
|
|
||||||
* CNFA demo wave file player
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
#include "wavDefs.h"
|
|
||||||
|
|
||||||
// wave file player function prototypes
|
|
||||||
int loadHeader(FILE *file, WaveHeaderChunk *hdr);
|
|
||||||
int loadInfo(FILE *file, WaveHeaderChunk *hdr);
|
|
||||||
int printInfo(FILE *file);
|
|
||||||
int readData(FILE *file, WaveHeaderChunk *hdr, void* buff, int buff_len);
|
|
||||||
void freeData(WaveHeaderChunk *hdr);
|
|
||||||
|
|
||||||
/* ------------------------------------------ Main Application Code ------------------------------*/
|
|
||||||
#define BUFF_SIZE 2048
|
|
||||||
// If using the shared library, don't define CNFA_IMPLEMENTATION
|
|
||||||
// (it's already in the library).
|
|
||||||
#ifndef USE_SHARED
|
|
||||||
#define CNFA_IMPLEMENTATION
|
|
||||||
#endif
|
|
||||||
#include "../CNFA.h"
|
|
||||||
|
|
||||||
int totalframesr = 0;
|
|
||||||
int totalframesp = 0;
|
|
||||||
|
|
||||||
FILE* wav_file;
|
|
||||||
WaveHeaderChunk hdr;
|
|
||||||
struct CNFADriver * cnfa;
|
|
||||||
short buff[BUFF_SIZE];
|
|
||||||
int is_done;
|
|
||||||
|
|
||||||
void Callback( struct CNFADriver * sd, short * out, short * in, int framesp, int framesr )
|
|
||||||
{
|
|
||||||
int* is_done_ptr = (int*) sd->opaque;
|
|
||||||
const int output_channels = sd->channelsPlay;
|
|
||||||
const int file_channels = hdr.fmt.num_channels;
|
|
||||||
const int output_buff_sz = framesp * output_channels;
|
|
||||||
int br = 0;
|
|
||||||
|
|
||||||
// if we have already ended the file, then clear the buffer and exit function
|
|
||||||
if(*is_done_ptr) {
|
|
||||||
memset(out, 0, sizeof(short) * output_buff_sz);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
totalframesr += framesr;
|
|
||||||
totalframesp += framesp;
|
|
||||||
|
|
||||||
if (output_channels == file_channels) {
|
|
||||||
int read_buff_sz = framesp * sd->channelsPlay;
|
|
||||||
br = readData(wav_file, &hdr, out, read_buff_sz);
|
|
||||||
}
|
|
||||||
else if (output_channels > file_channels) {
|
|
||||||
int read_buff_sz = framesp;
|
|
||||||
|
|
||||||
// exit loop if we are done filling the output buffer or we are at the end of file
|
|
||||||
int samples_remaining = read_buff_sz;
|
|
||||||
while (samples_remaining > 0 && br >= 0) {
|
|
||||||
int read_sz = (samples_remaining > BUFF_SIZE) ? BUFF_SIZE : samples_remaining;
|
|
||||||
br = readData(wav_file, &hdr, buff, read_sz);
|
|
||||||
// duplicate data on left and right channels
|
|
||||||
for (int i = 0; i < read_buff_sz; ++i){
|
|
||||||
out[2*i] = buff[i];
|
|
||||||
out[2*i+1] = buff[i];
|
|
||||||
}
|
|
||||||
samples_remaining -= br;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
printf("what are you doing? mono sound output?\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// end of file
|
|
||||||
if (br < 0) {
|
|
||||||
printf("End of wave file: setting flag\n");
|
|
||||||
*is_done_ptr = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main (int nargs, char** args) {
|
|
||||||
const char* filename;
|
|
||||||
// if there is a file given on the command line play it.
|
|
||||||
if(nargs >= 2) {
|
|
||||||
filename = args[1];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
printf("\nError, no input file\nUseage %s <wave file>\n", args[0]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
wav_file = fopen(filename, "r");
|
|
||||||
|
|
||||||
printInfo(wav_file);
|
|
||||||
printf("\n\n");
|
|
||||||
|
|
||||||
printf("loading file\n");
|
|
||||||
loadHeader(wav_file, &hdr);
|
|
||||||
|
|
||||||
printf("playing file\n");
|
|
||||||
|
|
||||||
is_done = 0;
|
|
||||||
cnfa = CNFAInit(
|
|
||||||
NULL, // String, for the driver "PULSE", "WASAPI" (output only) - NULL means default.
|
|
||||||
"cnfa_example", // Name of program to audio driver
|
|
||||||
Callback, // CNFA callback function handle
|
|
||||||
hdr.fmt.sample_rate, // Requested samplerate for playback
|
|
||||||
hdr.fmt.sample_rate, // Requested samplerate for record
|
|
||||||
2, // Number of playback channels.
|
|
||||||
2, // Number of record channels.
|
|
||||||
1024, // Buffer size in frames.
|
|
||||||
NULL, // String, for the selected input device - NULL means default.
|
|
||||||
NULL, // String, for the selected output device - NULL means default.
|
|
||||||
&is_done // pass an integer as an "opaque" object so that CNFA can close
|
|
||||||
);
|
|
||||||
|
|
||||||
int runtime = 0;
|
|
||||||
const char* spin_glyph = "-\\|/";
|
|
||||||
const char* glyph = spin_glyph;
|
|
||||||
int i = 0;
|
|
||||||
while (!is_done){
|
|
||||||
sleep(1);
|
|
||||||
++runtime;
|
|
||||||
printf("\r %c ", *glyph++);
|
|
||||||
fflush(stdout);
|
|
||||||
if (!*glyph) {
|
|
||||||
glyph = spin_glyph;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CNFAClose(cnfa);
|
|
||||||
fclose(wav_file);
|
|
||||||
|
|
||||||
printf( "Received %d (%d per sec) frames\nSent %d (%d per sec) frames\n",
|
|
||||||
totalframesr, totalframesr/runtime, // recorded samples, recorded samples/sec
|
|
||||||
totalframesp, totalframesp/runtime ); // outputted samples, outputted samples/sec
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------ Wave File Player Code ------------------------------*/
|
|
||||||
int printInfo(FILE *file){
|
|
||||||
WaveHeaderChunk wav_data;
|
|
||||||
|
|
||||||
if(loadHeader(file, &wav_data)!=0){
|
|
||||||
printf("file invalid\n\r");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
//print file data
|
|
||||||
printf("\n");
|
|
||||||
printf("Audio Format: %i \n", wav_data.fmt.audio_format);
|
|
||||||
printf("Channels: %u \n", wav_data.fmt.num_channels);
|
|
||||||
printf("Sample Rate: %lu \n", wav_data.fmt.sample_rate);
|
|
||||||
printf("Block Alignment (bytes): %u \n", wav_data.fmt.block_align);
|
|
||||||
printf("Bits-per-Sample: %u \n", wav_data.fmt.bits_per_sample);
|
|
||||||
|
|
||||||
if(loadInfo(file, &wav_data)==0){//if there is data show it
|
|
||||||
printf("\n");
|
|
||||||
printf("Track name: %s \n\r", wav_data.info.title);
|
|
||||||
printf("Artist: %s \n\r", wav_data.info.artist);
|
|
||||||
printf("Genre: %s \n\r", wav_data.info.genre);
|
|
||||||
printf("Creation date: %s \n\r", wav_data.info.creation_date);
|
|
||||||
}
|
|
||||||
|
|
||||||
freeData(&wav_data);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int loadHeader(FILE *file, WaveHeaderChunk *hdr){
|
|
||||||
char chunk_id[CHUNK_ID_LEN];
|
|
||||||
uint32_t chunk_offset=0;//keeps track of position in file - referenced to the chunk id
|
|
||||||
uint32_t chunk_len=0;//the length of the current chunk
|
|
||||||
uint32_t br;
|
|
||||||
|
|
||||||
|
|
||||||
//check if the file is valid first
|
|
||||||
if(file==NULL){
|
|
||||||
printf("Could not open file \n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
// check that the pointer is valid
|
|
||||||
if (!hdr) {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
// clear pointers
|
|
||||||
hdr->info.creation_date = NULL;
|
|
||||||
hdr->info.genre = NULL;
|
|
||||||
hdr->info.artist = NULL;
|
|
||||||
hdr->info.title = NULL;
|
|
||||||
|
|
||||||
//look for RIFF/WAVE file header
|
|
||||||
fseek(file, CHUNK_ID, SEEK_SET);
|
|
||||||
fgets(chunk_id, CHUNK_ID_LEN, file);
|
|
||||||
if(strncmp(chunk_id, "RIFF", 4)!=0){
|
|
||||||
printf("File is not a wav file\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
fseek(file, FORMAT, SEEK_SET);
|
|
||||||
fgets(chunk_id, CHUNK_ID_LEN, file);
|
|
||||||
if(strncmp(chunk_id, "WAVE", 4)!=0){
|
|
||||||
printf("File is not a wav file \n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
chunk_offset+=CHUNK_DATA+4;//add the file offset of the begining of the first chunk
|
|
||||||
|
|
||||||
//must be WAVE look for chunks
|
|
||||||
do{
|
|
||||||
//get the chunk id
|
|
||||||
fseek(file, chunk_offset+CHUNK_ID, SEEK_SET);
|
|
||||||
fgets(chunk_id, CHUNK_ID_LEN, file);
|
|
||||||
|
|
||||||
//get the chunk length
|
|
||||||
fseek(file, chunk_offset+CHUNK_SIZE, SEEK_SET);
|
|
||||||
br = fread(&chunk_len, sizeof(uint32_t), 1, file);//get length
|
|
||||||
|
|
||||||
//check for fmt chunk
|
|
||||||
if(strncmp(chunk_id, "fmt ", 4)==0){//if format section
|
|
||||||
//get the format section of the file
|
|
||||||
fseek(file, chunk_offset+AUDIO_FORMAT, SEEK_SET);
|
|
||||||
br = fread(&hdr->fmt.audio_format, sizeof(uint16_t), 1, file);
|
|
||||||
|
|
||||||
//number of channels
|
|
||||||
fseek(file, chunk_offset+NUM_CHANNELS, SEEK_SET);
|
|
||||||
br = fread(&hdr->fmt.num_channels, sizeof(uint16_t), 1, file);
|
|
||||||
|
|
||||||
//sample rate
|
|
||||||
fseek(file, chunk_offset+SAMPLE_RATE, SEEK_SET);
|
|
||||||
br = fread(&hdr->fmt.sample_rate, sizeof(uint32_t), 1, file);
|
|
||||||
|
|
||||||
//bits per channel tells if mono, stereo, or hifi stereo
|
|
||||||
fseek(file, chunk_offset+BLOCK_ALIGN, SEEK_SET);
|
|
||||||
br = fread(&hdr->fmt.block_align, sizeof(uint16_t), 1, file);
|
|
||||||
|
|
||||||
//bits per channel 8 or 16
|
|
||||||
fseek(file, chunk_offset+BITS_PER_SAMPLE, SEEK_SET);
|
|
||||||
br = fread(&hdr->fmt.bits_per_sample, sizeof(uint16_t), 1, file);
|
|
||||||
|
|
||||||
hdr->fmt.bytes_per_sample=hdr->fmt.bits_per_sample/8;
|
|
||||||
}
|
|
||||||
else if(strncmp(chunk_id, "data", 4)==0){//if chunk is of data type
|
|
||||||
// grab data chunk location and size
|
|
||||||
hdr->data.data_offset = chunk_offset;
|
|
||||||
hdr->data.data_size = chunk_len;
|
|
||||||
hdr->data.num_samples = (uint16_t) chunk_len/hdr->fmt.bytes_per_sample;
|
|
||||||
hdr->data.samples_left = hdr->data.num_samples;
|
|
||||||
hdr->data.current_offset = chunk_offset+CHUNK_DATA;
|
|
||||||
}
|
|
||||||
else if(strncmp(chunk_id, "LIST", 4)==0){//if chunk is of LIST type
|
|
||||||
//check if it is of info type
|
|
||||||
fseek(file, chunk_offset+CHUNK_DATA, SEEK_SET);
|
|
||||||
fgets(chunk_id, 5, file);
|
|
||||||
if(strncmp(chunk_id, "INFO", 4)==0){
|
|
||||||
// grab info location and size
|
|
||||||
hdr->info.info_offset = chunk_offset+4;
|
|
||||||
hdr->info.info_len = chunk_len;
|
|
||||||
hdr->info.is_info = 1;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
// no file info (artist, genre, etc)
|
|
||||||
hdr->info.is_info = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(strncmp(chunk_id, "data", 4)!=0){
|
|
||||||
//get length of chunk
|
|
||||||
fseek(file, chunk_offset+CHUNK_SIZE, SEEK_SET);
|
|
||||||
br = fread(&chunk_len, 2, 1, file);//get length
|
|
||||||
}
|
|
||||||
|
|
||||||
//done reading skip chunk
|
|
||||||
chunk_offset=chunk_offset+chunk_len+CHUNK_DATA;
|
|
||||||
fseek(file, chunk_offset, SEEK_SET);
|
|
||||||
|
|
||||||
} while(fgets(chunk_id, 5, file)!=NULL);//loop until the end of the file is reached
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* reads the info chunk if available and puts data into the WaveHeader structure provided
|
|
||||||
*/
|
|
||||||
int loadInfo(FILE *file, WaveHeaderChunk *hdr){
|
|
||||||
char chunk_id[CHUNK_ID_LEN];
|
|
||||||
uint32_t chunk_offset=hdr->info.info_offset+CHUNK_DATA;//current file position
|
|
||||||
uint32_t chunk_len=0;//the length of the current chunk
|
|
||||||
uint32_t tag_len=0;
|
|
||||||
uint32_t br;
|
|
||||||
|
|
||||||
if(hdr->info.is_info==0){//if no data
|
|
||||||
printf("No artist information is available for this wav file \n");
|
|
||||||
return 1;//no data
|
|
||||||
}
|
|
||||||
|
|
||||||
fseek(file, chunk_offset, SEEK_SET);//go to chunk data
|
|
||||||
while(chunk_offset-4 - hdr->info.info_offset < hdr->info.info_len){//while in info chunk
|
|
||||||
//get the chunk id
|
|
||||||
fseek(file, chunk_offset+CHUNK_ID, SEEK_SET);
|
|
||||||
fgets(chunk_id, 5, file);
|
|
||||||
//get the chunk length
|
|
||||||
fseek(file, chunk_offset+CHUNK_SIZE, SEEK_SET);
|
|
||||||
br = fread(&chunk_len, sizeof(uint32_t), 1, file);//get length
|
|
||||||
//make sure the length of the tag isn't to big
|
|
||||||
if(chunk_len > MAX_TAG_SIZE){//if too big
|
|
||||||
tag_len=MAX_TAG_SIZE;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
tag_len=chunk_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
//go to tag information
|
|
||||||
fseek(file, chunk_offset+CHUNK_DATA, SEEK_SET);
|
|
||||||
if(strncmp(chunk_id, "INAM", 4)==0){ // if is name chunk
|
|
||||||
hdr->info.title=malloc(tag_len+1); // allocate memory for tag
|
|
||||||
fgets(hdr->info.title, tag_len, file); // read tag
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(strncmp(chunk_id, "IART", 4)==0){ // if is artist chunk
|
|
||||||
hdr->info.artist=malloc(tag_len+1); // allocate memory for tag
|
|
||||||
fgets(hdr->info.artist, tag_len, file); // read tag
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(strncmp(chunk_id, "IGNR", 4)==0){ // if is genre chunk
|
|
||||||
hdr->info.genre=malloc(tag_len+1); // allocate memory for tag
|
|
||||||
fgets(hdr->info.genre, tag_len, file); // read tag
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(strncmp(chunk_id, "ICRD", 4)==0){ // if is creation date chunk
|
|
||||||
hdr->info.creation_date=malloc(tag_len+1); // allocate memory for tag
|
|
||||||
fgets(hdr->info.creation_date, tag_len, file); // read tag
|
|
||||||
|
|
||||||
}
|
|
||||||
//done reading skip chunk
|
|
||||||
chunk_offset=chunk_offset+chunk_len+CHUNK_DATA;
|
|
||||||
fseek(file, chunk_offset, SEEK_SET);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int readData(FILE *file, WaveHeaderChunk *hdr, void* buff, int buff_len){
|
|
||||||
uint32_t br;
|
|
||||||
int bytes_to_read = hdr->fmt.bytes_per_sample*buff_len;
|
|
||||||
|
|
||||||
if(!hdr) {
|
|
||||||
printf("Error: No valid header\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fseek(file, hdr->data.current_offset, SEEK_SET);
|
|
||||||
br = fread(buff, hdr->fmt.bytes_per_sample, buff_len, file);
|
|
||||||
|
|
||||||
|
|
||||||
int bytes_read = hdr->fmt.bytes_per_sample*br;
|
|
||||||
|
|
||||||
//printf("samples read: %u\n", br);
|
|
||||||
|
|
||||||
hdr->data.samples_left -= br;
|
|
||||||
hdr->data.current_offset += bytes_read;
|
|
||||||
|
|
||||||
if(feof(file)){
|
|
||||||
br = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return br;
|
|
||||||
}
|
|
||||||
|
|
||||||
void freeData(WaveHeaderChunk *hdr) {
|
|
||||||
//free the info tag heap data
|
|
||||||
if(hdr->info.title){
|
|
||||||
free(hdr->info.title);
|
|
||||||
hdr->info.title=NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(hdr->info.artist){
|
|
||||||
free(hdr->info.artist);
|
|
||||||
hdr->info.artist=NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(hdr->info.genre){
|
|
||||||
free(hdr->info.genre);
|
|
||||||
hdr->info.genre=NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(hdr->info.creation_date){
|
|
||||||
free(hdr->info.creation_date);
|
|
||||||
hdr->info.creation_date=NULL;
|
|
||||||
}
|
|
||||||
}
|
|
339
app/src/nativecode/main.c
Normal file
339
app/src/nativecode/main.c
Normal file
|
@ -0,0 +1,339 @@
|
||||||
|
//Copyright (c) 2011-2020 <>< Charles Lohr - Under the MIT/x11 or NewBSD License you choose.
|
||||||
|
//Copyright modificatoins Alexander Mahr
|
||||||
|
// NO WARRANTY! NO GUARANTEE OF SUPPORT! USE AT YOUR OWN RISK
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <android_native_app_glue.h>
|
||||||
|
#include <android/log.h>
|
||||||
|
#define APPNAME "MyApp"
|
||||||
|
|
||||||
|
//#include <android/sensor.h>
|
||||||
|
#include <byteswap.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include "CNFGAndroid.h"
|
||||||
|
#define true 1
|
||||||
|
//#define CNFA_IMPLEMENTATION
|
||||||
|
#define CNFG_IMPLEMENTATION
|
||||||
|
#define CNFG3D
|
||||||
|
|
||||||
|
//AUDIO //#include "cnfa/CNFA.h"
|
||||||
|
#include "CNFG.h"
|
||||||
|
|
||||||
|
#define WEBVIEW_NATIVE_ACTIVITY_IMPLEMENTATION
|
||||||
|
#include "webview_native_activity.h"
|
||||||
|
|
||||||
|
WebViewNativeActivityObject MyWebView;
|
||||||
|
unsigned long iframeno = 0;
|
||||||
|
void AndroidDisplayKeyboard(int pShow);
|
||||||
|
int lastbuttonx = 0;
|
||||||
|
int lastbuttony = 0;
|
||||||
|
int lastmotionx = 0;
|
||||||
|
int lastmotiony = 0;
|
||||||
|
int lastbid = 0;
|
||||||
|
int lastmask = 0;
|
||||||
|
int lastkey, lastkeydown;
|
||||||
|
static int keyboard_up;
|
||||||
|
uint8_t buttonstate[8];
|
||||||
|
short screenx, screeny;
|
||||||
|
extern struct android_app * gapp;
|
||||||
|
// used for webview
|
||||||
|
uint32_t * webviewdata;
|
||||||
|
char fromJSBuffer[128];
|
||||||
|
jobject g_attachLooper;
|
||||||
|
pthread_t jsthread;
|
||||||
|
|
||||||
|
void HandleKey( int keycode, int bDown )
|
||||||
|
{
|
||||||
|
lastkey = keycode;
|
||||||
|
lastkeydown = bDown;
|
||||||
|
if( keycode == 10 && !bDown ) { keyboard_up = 0; AndroidDisplayKeyboard( keyboard_up ); }
|
||||||
|
|
||||||
|
if( keycode == 4 ) { AndroidSendToBack( 1 ); } //Handle Physical Back Button.
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleButton( int x, int y, int button, int bDown )
|
||||||
|
{
|
||||||
|
buttonstate[button] = bDown;
|
||||||
|
lastbid = button;
|
||||||
|
lastbuttonx = x;
|
||||||
|
lastbuttony = y;
|
||||||
|
|
||||||
|
if( bDown ) { keyboard_up = !keyboard_up; AndroidDisplayKeyboard( keyboard_up ); }
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleMotion( int x, int y, int mask )
|
||||||
|
{
|
||||||
|
lastmask = mask;
|
||||||
|
lastmotionx = x;
|
||||||
|
lastmotiony = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
//writes the text to a file to path (example): /storage/emulated/0/Android/data/org.yourorg.cnfgtest/files
|
||||||
|
// You would not normally want to do this, but it's an example of how to do local storage.
|
||||||
|
void Log(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list arg;
|
||||||
|
va_start(arg, fmt);
|
||||||
|
__android_log_print(ANDROID_LOG_VERBOSE, APPNAME,fmt,arg);
|
||||||
|
va_end(arg);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int HandleDestroy()
|
||||||
|
{
|
||||||
|
printf( "Destroying\n" );
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
volatile int suspended;
|
||||||
|
|
||||||
|
void HandleSuspend()
|
||||||
|
{
|
||||||
|
suspended = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleResume()
|
||||||
|
{
|
||||||
|
suspended = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HandleThisWindowTermination()
|
||||||
|
{
|
||||||
|
suspended = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckWebView( void * v )
|
||||||
|
{
|
||||||
|
static int runno = 0;
|
||||||
|
WebViewNativeActivityObject * wvn = (WebViewNativeActivityObject*)v;
|
||||||
|
if( WebViewGetProgress( wvn ) != 100 ) return;
|
||||||
|
|
||||||
|
runno++;
|
||||||
|
if( runno == 1 )
|
||||||
|
{
|
||||||
|
// The attach (initial) message payload has no meaning.
|
||||||
|
WebViewPostMessage( wvn, "", 1 );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Invoke JavaScript, which calls a function to send a webmessage
|
||||||
|
// back into C land.
|
||||||
|
WebViewExecuteJavascript( wvn, "SendMessageToC();" );
|
||||||
|
|
||||||
|
// Send a WebMessage into the JavaScript code.
|
||||||
|
char st[128];
|
||||||
|
sprintf( st, "Into JavaScript %d\n", runno );
|
||||||
|
WebViewPostMessage( wvn, st, 0 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SetupWebView( void * v )
|
||||||
|
{
|
||||||
|
WebViewNativeActivityObject * wvn = (WebViewNativeActivityObject*)v;
|
||||||
|
|
||||||
|
|
||||||
|
const struct JNINativeInterface * env = 0;
|
||||||
|
const struct JNINativeInterface ** envptr = &env;
|
||||||
|
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
|
||||||
|
const struct JNIInvokeInterface * jnii = *jniiptr;
|
||||||
|
|
||||||
|
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
|
||||||
|
env = (*envptr);
|
||||||
|
|
||||||
|
while( g_attachLooper == 0 ) usleep(1);
|
||||||
|
if(screenx<1000){
|
||||||
|
screenx=1000;
|
||||||
|
}
|
||||||
|
if(screeny<1000){
|
||||||
|
screeny=1000;
|
||||||
|
}
|
||||||
|
//WebViewCreate( wvn, "file:///android_asset/index.html", g_attachLooper, screenx, screeny);
|
||||||
|
WebViewCreate( wvn, "https://server.alexmahr.de/browser.html", g_attachLooper, screenx, screeny);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void * JavscriptThread( void * v )
|
||||||
|
{
|
||||||
|
const struct JNINativeInterface * env = 0;
|
||||||
|
const struct JNINativeInterface ** envptr = &env;
|
||||||
|
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
|
||||||
|
const struct JNIInvokeInterface * jnii = *jniiptr;
|
||||||
|
|
||||||
|
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
|
||||||
|
env = (*envptr);
|
||||||
|
|
||||||
|
// Create a looper on this thread...
|
||||||
|
jclass LooperClass = env->FindClass(envptr, "android/os/Looper");
|
||||||
|
jmethodID myLooperMethod = env->GetStaticMethodID(envptr, LooperClass, "myLooper", "()Landroid/os/Looper;");
|
||||||
|
jobject thisLooper = env->CallStaticObjectMethod( envptr, LooperClass, myLooperMethod );
|
||||||
|
if( !thisLooper )
|
||||||
|
{
|
||||||
|
jmethodID prepareMethod = env->GetStaticMethodID(envptr, LooperClass, "prepare", "()V");
|
||||||
|
env->CallStaticVoidMethod( envptr, LooperClass, prepareMethod );
|
||||||
|
thisLooper = env->CallStaticObjectMethod( envptr, LooperClass, myLooperMethod );
|
||||||
|
g_attachLooper = env->NewGlobalRef(envptr, thisLooper);
|
||||||
|
}
|
||||||
|
|
||||||
|
jmethodID getQueueMethod = env->GetMethodID( envptr, LooperClass, "getQueue", "()Landroid/os/MessageQueue;" );
|
||||||
|
jobject lque = env->CallObjectMethod( envptr, g_attachLooper, getQueueMethod );
|
||||||
|
|
||||||
|
jclass MessageQueueClass = env->FindClass(envptr, "android/os/MessageQueue");
|
||||||
|
jmethodID nextMethod = env->GetMethodID( envptr, MessageQueueClass, "next", "()Landroid/os/Message;" );
|
||||||
|
|
||||||
|
jclass MessageClass = env->FindClass(envptr, "android/os/Message");
|
||||||
|
jfieldID objid = env->GetFieldID( envptr, MessageClass, "obj", "Ljava/lang/Object;" );
|
||||||
|
jclass PairClass = env->FindClass(envptr, "android/util/Pair");
|
||||||
|
jfieldID pairfirst = env->GetFieldID( envptr, PairClass, "first", "Ljava/lang/Object;" );
|
||||||
|
|
||||||
|
while(1)
|
||||||
|
{
|
||||||
|
// Instead of using Looper::loop(), we just call next on the looper object.
|
||||||
|
jobject msg = env->CallObjectMethod( envptr, lque, nextMethod );
|
||||||
|
jobject innerObj = env->GetObjectField( envptr, msg, objid );
|
||||||
|
const char * name;
|
||||||
|
jstring strObj;
|
||||||
|
jclass innerClass;
|
||||||
|
|
||||||
|
// Check Object Type
|
||||||
|
{
|
||||||
|
innerClass = env->GetObjectClass( envptr, innerObj );
|
||||||
|
jmethodID mid = env->GetMethodID( envptr, innerClass, "getClass", "()Ljava/lang/Class;");
|
||||||
|
jobject clsObj = env->CallObjectMethod( envptr, innerObj, mid );
|
||||||
|
jclass clazzz = env->GetObjectClass( envptr, clsObj );
|
||||||
|
mid = env->GetMethodID(envptr, clazzz, "getName", "()Ljava/lang/String;");
|
||||||
|
strObj = (jstring)env->CallObjectMethod( envptr, clsObj, mid);
|
||||||
|
name = env->GetStringUTFChars( envptr, strObj, 0);
|
||||||
|
env->DeleteLocalRef( envptr, clsObj );
|
||||||
|
env->DeleteLocalRef( envptr, clazzz );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( strcmp( name, "z5" ) == 0 )
|
||||||
|
{
|
||||||
|
// Special, Some Androids (notably Meta Quest) use a different private message type.
|
||||||
|
jfieldID mstrf = env->GetFieldID( envptr, innerClass, "a", "[B" );
|
||||||
|
jbyteArray jba = (jstring)env->GetObjectField(envptr, innerObj, mstrf );
|
||||||
|
int len = env->GetArrayLength( envptr, jba );
|
||||||
|
jboolean isCopy = 0;
|
||||||
|
jbyte * bufferPtr = env->GetByteArrayElements(envptr, jba, &isCopy);
|
||||||
|
|
||||||
|
if( len >= 6 )
|
||||||
|
{
|
||||||
|
const char *descr = (const char*)bufferPtr + 6;
|
||||||
|
char tcpy[len-5];
|
||||||
|
memcpy( tcpy, descr, len-6 );
|
||||||
|
tcpy[len-6] = 0;
|
||||||
|
snprintf( fromJSBuffer, sizeof( fromJSBuffer)-1, "WebMessage: %s\n", tcpy );
|
||||||
|
|
||||||
|
env->DeleteLocalRef( envptr, jba );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
jobject MessagePayload = env->GetObjectField( envptr, innerObj, pairfirst );
|
||||||
|
// MessagePayload is a org.chromium.content_public.browser.MessagePayload
|
||||||
|
|
||||||
|
jclass mpclass = env->GetObjectClass( envptr, MessagePayload );
|
||||||
|
|
||||||
|
// Get field "b" which is the web message payload.
|
||||||
|
// If you are using binary sockets, it will be in `c` and be a byte array.
|
||||||
|
jfieldID mstrf = env->GetFieldID( envptr, mpclass, "b", "Ljava/lang/String;" );
|
||||||
|
jstring strObjDescr = (jstring)env->GetObjectField(envptr, MessagePayload, mstrf );
|
||||||
|
|
||||||
|
const char *descr = env->GetStringUTFChars( envptr, strObjDescr, 0);
|
||||||
|
snprintf( fromJSBuffer, sizeof( fromJSBuffer)-1, "WebMessage: %s\n", descr );
|
||||||
|
|
||||||
|
env->ReleaseStringUTFChars(envptr, strObjDescr, descr);
|
||||||
|
env->DeleteLocalRef( envptr, strObjDescr );
|
||||||
|
env->DeleteLocalRef( envptr, MessagePayload );
|
||||||
|
env->DeleteLocalRef( envptr, mpclass );
|
||||||
|
}
|
||||||
|
env->ReleaseStringUTFChars(envptr, strObj, name);
|
||||||
|
env->DeleteLocalRef( envptr, strObj );
|
||||||
|
env->DeleteLocalRef( envptr, msg );
|
||||||
|
env->DeleteLocalRef( envptr, innerObj );
|
||||||
|
env->DeleteLocalRef( envptr, innerClass );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetupJSThread()
|
||||||
|
{
|
||||||
|
pthread_create( &jsthread, 0, JavscriptThread, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
int main( int argc, char ** argv )
|
||||||
|
{
|
||||||
|
Log( "Starting Up" );
|
||||||
|
|
||||||
|
CNFGBGColor = 0x000040ff;
|
||||||
|
CNFGSetupFullscreen( "Test Bench", 0 );
|
||||||
|
CNFGGetDimensions( &screenx, &screeny );
|
||||||
|
|
||||||
|
size_t webviewdatasize = sizeof(uint32_t) * screeny * screenx;
|
||||||
|
Log( "MYSTRING %dx%d,%ld",screenx, screeny,webviewdatasize);
|
||||||
|
|
||||||
|
webviewdata = malloc(webviewdatasize);
|
||||||
|
|
||||||
|
|
||||||
|
HandleWindowTermination = HandleThisWindowTermination;
|
||||||
|
|
||||||
|
|
||||||
|
SetupJSThread();
|
||||||
|
|
||||||
|
// Create webview and wait for its completion
|
||||||
|
RunCallbackOnUIThread( SetupWebView, &MyWebView );
|
||||||
|
while( !MyWebView.WebViewObject ) usleep(1);
|
||||||
|
|
||||||
|
Log( "Startup Complete" );
|
||||||
|
|
||||||
|
while(1)
|
||||||
|
{
|
||||||
|
iframeno++;
|
||||||
|
CNFGHandleInput();
|
||||||
|
|
||||||
|
if( suspended ) { usleep(1000000); continue; }
|
||||||
|
|
||||||
|
RunCallbackOnUIThread( (void(*)(void*))WebViewRequestRenderToCanvas, &MyWebView );
|
||||||
|
RunCallbackOnUIThread( CheckWebView, &MyWebView );
|
||||||
|
|
||||||
|
CNFGClearFrame();
|
||||||
|
CNFGColor( 0xFFFFFFFF );
|
||||||
|
CNFGGetDimensions( &screenx, &screeny );
|
||||||
|
|
||||||
|
// Mesh in background
|
||||||
|
CNFGSetLineWidth( 9 );
|
||||||
|
CNFGPenX = 0; CNFGPenY = 400;
|
||||||
|
CNFGColor( 0xffffffff );
|
||||||
|
CNFGFlushRender();
|
||||||
|
|
||||||
|
CNFGPenX = 0; CNFGPenY = 480;
|
||||||
|
char st[50];
|
||||||
|
sprintf( st, "%ld\n%dx%d %d %d %d %d %d %d\n%d %d",iframeno, screenx, screeny, lastbuttonx, lastbuttony, lastmotionx, lastmotiony, lastkey, lastkeydown, lastbid, lastmask);
|
||||||
|
CNFGDrawText( st, 10 );
|
||||||
|
CNFGSetLineWidth( 2 );
|
||||||
|
|
||||||
|
|
||||||
|
// Last WebMessage
|
||||||
|
CNFGColor( 0xFFFFFFFF );
|
||||||
|
CNFGPenX = 0; CNFGPenY = 100;
|
||||||
|
CNFGDrawText( fromJSBuffer, 6 );
|
||||||
|
|
||||||
|
WebViewNativeGetPixels( &MyWebView, webviewdata,screenx,screeny);
|
||||||
|
CNFGBlitImage( webviewdata, 0, 0, screenx,screeny );
|
||||||
|
|
||||||
|
//On Android, CNFGSwapBuffers must be called, and CNFGUpdateScreenWithBitmap does not have an implied framebuffer swap.
|
||||||
|
CNFGSwapBuffers();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
|
|
@ -1,663 +0,0 @@
|
||||||
//Copyright (c) 2011-2020 <>< Charles Lohr - Under the MIT/x11 or NewBSD License you choose.
|
|
||||||
// NO WARRANTY! NO GUARANTEE OF SUPPORT! USE AT YOUR OWN RISK
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <math.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "os_generic.h"
|
|
||||||
#include <GLES3/gl3.h>
|
|
||||||
#include <android/asset_manager.h>
|
|
||||||
#include <android/asset_manager_jni.h>
|
|
||||||
#include <android_native_app_glue.h>
|
|
||||||
#include <android/sensor.h>
|
|
||||||
#include <byteswap.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include "CNFGAndroid.h"
|
|
||||||
|
|
||||||
//#define CNFA_IMPLEMENTATION
|
|
||||||
#define CNFG_IMPLEMENTATION
|
|
||||||
#define CNFG3D
|
|
||||||
|
|
||||||
//#include "cnfa/CNFA.h"
|
|
||||||
#include "CNFG.h"
|
|
||||||
|
|
||||||
#define WEBVIEW_NATIVE_ACTIVITY_IMPLEMENTATION
|
|
||||||
#include "webview_native_activity.h"
|
|
||||||
|
|
||||||
float mountainangle;
|
|
||||||
float mountainoffsetx;
|
|
||||||
float mountainoffsety;
|
|
||||||
|
|
||||||
ASensorManager * sm;
|
|
||||||
const ASensor * as;
|
|
||||||
bool no_sensor_for_gyro = false;
|
|
||||||
ASensorEventQueue* aeq;
|
|
||||||
ALooper * l;
|
|
||||||
|
|
||||||
WebViewNativeActivityObject MyWebView;
|
|
||||||
|
|
||||||
const uint32_t SAMPLE_RATE = 44100;
|
|
||||||
const uint16_t SAMPLE_COUNT = 512;
|
|
||||||
uint32_t stream_offset = 0;
|
|
||||||
uint16_t audio_frequency;
|
|
||||||
|
|
||||||
void SetupIMU()
|
|
||||||
{
|
|
||||||
sm = ASensorManager_getInstanceForPackage("gyroscope");
|
|
||||||
as = ASensorManager_getDefaultSensor( sm, ASENSOR_TYPE_GYROSCOPE );
|
|
||||||
no_sensor_for_gyro = as == NULL;
|
|
||||||
l = ALooper_prepare( ALOOPER_PREPARE_ALLOW_NON_CALLBACKS );
|
|
||||||
aeq = ASensorManager_createEventQueue( sm, (ALooper*)&l, 0, 0, 0 ); //XXX??!?! This looks wrong.
|
|
||||||
if(!no_sensor_for_gyro) {
|
|
||||||
ASensorEventQueue_enableSensor( aeq, as);
|
|
||||||
printf( "setEvent Rate: %d\n", ASensorEventQueue_setEventRate( aeq, as, 10000 ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
float accx, accy, accz;
|
|
||||||
int accs;
|
|
||||||
|
|
||||||
void AccCheck()
|
|
||||||
{
|
|
||||||
if(no_sensor_for_gyro) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ASensorEvent evt;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
ssize_t s = ASensorEventQueue_getEvents( aeq, &evt, 1 );
|
|
||||||
if( s <= 0 ) break;
|
|
||||||
accx = evt.vector.v[0];
|
|
||||||
accy = evt.vector.v[1];
|
|
||||||
accz = evt.vector.v[2];
|
|
||||||
mountainangle /*degrees*/ -= accz;// * 3.1415 / 360.0;// / 100.0;
|
|
||||||
mountainoffsety += accy;
|
|
||||||
mountainoffsetx += accx;
|
|
||||||
accs++;
|
|
||||||
} while( 1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned frames = 0;
|
|
||||||
unsigned long iframeno = 0;
|
|
||||||
|
|
||||||
void AndroidDisplayKeyboard(int pShow);
|
|
||||||
|
|
||||||
int lastbuttonx = 0;
|
|
||||||
int lastbuttony = 0;
|
|
||||||
int lastmotionx = 0;
|
|
||||||
int lastmotiony = 0;
|
|
||||||
int lastbid = 0;
|
|
||||||
int lastmask = 0;
|
|
||||||
int lastkey, lastkeydown;
|
|
||||||
|
|
||||||
static int keyboard_up;
|
|
||||||
uint8_t buttonstate[8];
|
|
||||||
|
|
||||||
void HandleKey( int keycode, int bDown )
|
|
||||||
{
|
|
||||||
lastkey = keycode;
|
|
||||||
lastkeydown = bDown;
|
|
||||||
if( keycode == 10 && !bDown ) { keyboard_up = 0; AndroidDisplayKeyboard( keyboard_up ); }
|
|
||||||
|
|
||||||
if( keycode == 4 ) { AndroidSendToBack( 1 ); } //Handle Physical Back Button.
|
|
||||||
}
|
|
||||||
|
|
||||||
void HandleButton( int x, int y, int button, int bDown )
|
|
||||||
{
|
|
||||||
buttonstate[button] = bDown;
|
|
||||||
lastbid = button;
|
|
||||||
lastbuttonx = x;
|
|
||||||
lastbuttony = y;
|
|
||||||
|
|
||||||
if( bDown ) { keyboard_up = !keyboard_up; AndroidDisplayKeyboard( keyboard_up ); }
|
|
||||||
}
|
|
||||||
|
|
||||||
void HandleMotion( int x, int y, int mask )
|
|
||||||
{
|
|
||||||
lastmask = mask;
|
|
||||||
lastmotionx = x;
|
|
||||||
lastmotiony = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
//writes the text to a file to path (example): /storage/emulated/0/Android/data/org.yourorg.cnfgtest/files
|
|
||||||
// You would not normally want to do this, but it's an example of how to do local storage.
|
|
||||||
void Log(const char *fmt, ...)
|
|
||||||
{
|
|
||||||
const char* getpath = AndroidGetExternalFilesDir();
|
|
||||||
char buffer[2048];
|
|
||||||
snprintf(buffer, sizeof(buffer), "%s/log.txt", getpath);
|
|
||||||
FILE *f = fopen(buffer, "w");
|
|
||||||
if (f == NULL)
|
|
||||||
{
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
va_list arg;
|
|
||||||
va_start(arg, fmt);
|
|
||||||
vsnprintf(buffer, sizeof(buffer), fmt, arg);
|
|
||||||
va_end(arg);
|
|
||||||
|
|
||||||
fprintf(f, "%s\n", buffer);
|
|
||||||
|
|
||||||
fclose(f);
|
|
||||||
}
|
|
||||||
#define HMX 162
|
|
||||||
#define HMY 162
|
|
||||||
short screenx, screeny;
|
|
||||||
float Heightmap[HMX*HMY];
|
|
||||||
|
|
||||||
extern struct android_app * gapp;
|
|
||||||
|
|
||||||
void DrawHeightmap()
|
|
||||||
{
|
|
||||||
int x, y;
|
|
||||||
//float fdt = ((iframeno++)%(360*10))/10.0;
|
|
||||||
|
|
||||||
mountainangle += .2;
|
|
||||||
if( mountainangle < 0 ) mountainangle += 360;
|
|
||||||
if( mountainangle > 360 ) mountainangle -= 360;
|
|
||||||
|
|
||||||
mountainoffsety = mountainoffsety - ((mountainoffsety-100) * .1);
|
|
||||||
|
|
||||||
float eye[3] = { (float)(sin(mountainangle*(3.14159/180.0))*30*sin(mountainoffsety/100.)), (float)(cos(mountainangle*(3.14159/180.0))*30*sin(mountainoffsety/100.)), (float)(30*cos(mountainoffsety/100.)) };
|
|
||||||
float at[3] = { 0,0, 0 };
|
|
||||||
float up[3] = { 0,0, 1 };
|
|
||||||
|
|
||||||
tdSetViewport( -1, -1, 1, 1, screenx, screeny );
|
|
||||||
|
|
||||||
tdMode( tdPROJECTION );
|
|
||||||
tdIdentity( gSMatrix );
|
|
||||||
tdPerspective( 30, ((float)screenx)/((float)screeny), .1, 200., gSMatrix );
|
|
||||||
|
|
||||||
tdMode( tdMODELVIEW );
|
|
||||||
tdIdentity( gSMatrix );
|
|
||||||
tdTranslate( gSMatrix, 0, 0, -40 );
|
|
||||||
tdLookAt( gSMatrix, eye, at, up );
|
|
||||||
|
|
||||||
float scale = 60./HMX;
|
|
||||||
|
|
||||||
for( x = 0; x < HMX-1; x++ )
|
|
||||||
for( y = 0; y < HMY-1; y++ )
|
|
||||||
{
|
|
||||||
float tx = x-HMX/2;
|
|
||||||
float ty = y-HMY/2;
|
|
||||||
float pta[3];
|
|
||||||
float ptb[3];
|
|
||||||
float ptc[3];
|
|
||||||
float ptd[3];
|
|
||||||
|
|
||||||
float normal[3];
|
|
||||||
float lightdir[3] = { .6, -.6, 1 };
|
|
||||||
float tmp1[3];
|
|
||||||
float tmp2[3];
|
|
||||||
|
|
||||||
RDPoint pto[6];
|
|
||||||
|
|
||||||
pta[0] = (tx+0)*scale; pta[1] = (ty+0)*scale; pta[2] = Heightmap[(x+0)+(y+0)*HMX]*scale;
|
|
||||||
ptb[0] = (tx+1)*scale; ptb[1] = (ty+0)*scale; ptb[2] = Heightmap[(x+1)+(y+0)*HMX]*scale;
|
|
||||||
ptc[0] = (tx+0)*scale; ptc[1] = (ty+1)*scale; ptc[2] = Heightmap[(x+0)+(y+1)*HMX]*scale;
|
|
||||||
ptd[0] = (tx+1)*scale; ptd[1] = (ty+1)*scale; ptd[2] = Heightmap[(x+1)+(y+1)*HMX]*scale;
|
|
||||||
|
|
||||||
tdPSub( pta, ptb, tmp2 );
|
|
||||||
tdPSub( ptc, ptb, tmp1 );
|
|
||||||
tdCross( tmp1, tmp2, normal );
|
|
||||||
tdNormalizeSelf( normal );
|
|
||||||
|
|
||||||
tdFinalPoint( pta, pta );
|
|
||||||
tdFinalPoint( ptb, ptb );
|
|
||||||
tdFinalPoint( ptc, ptc );
|
|
||||||
tdFinalPoint( ptd, ptd );
|
|
||||||
|
|
||||||
if( pta[2] >= 1.0 ) continue;
|
|
||||||
if( ptb[2] >= 1.0 ) continue;
|
|
||||||
if( ptc[2] >= 1.0 ) continue;
|
|
||||||
if( ptd[2] >= 1.0 ) continue;
|
|
||||||
|
|
||||||
if( pta[2] < 0 ) continue;
|
|
||||||
if( ptb[2] < 0 ) continue;
|
|
||||||
if( ptc[2] < 0 ) continue;
|
|
||||||
if( ptd[2] < 0 ) continue;
|
|
||||||
|
|
||||||
pto[0].x = pta[0]; pto[0].y = pta[1];
|
|
||||||
pto[1].x = ptb[0]; pto[1].y = ptb[1];
|
|
||||||
pto[2].x = ptd[0]; pto[2].y = ptd[1];
|
|
||||||
|
|
||||||
pto[3].x = ptc[0]; pto[3].y = ptc[1];
|
|
||||||
pto[4].x = ptd[0]; pto[4].y = ptd[1];
|
|
||||||
pto[5].x = pta[0]; pto[5].y = pta[1];
|
|
||||||
|
|
||||||
// CNFGColor(((x+y)&1)?0xFFFFFF:0x000000);
|
|
||||||
|
|
||||||
float bright = tdDot( normal, lightdir );
|
|
||||||
if( bright < 0 ) bright = 0;
|
|
||||||
CNFGColor( 0xff | ( ( (int)( bright * 90 ) ) << 24 ) );
|
|
||||||
|
|
||||||
// CNFGTackPoly( &pto[0], 3 ); CNFGTackPoly( &pto[3], 3 );
|
|
||||||
CNFGTackSegment( pta[0], pta[1], ptb[0], ptb[1] );
|
|
||||||
CNFGTackSegment( pta[0], pta[1], ptc[0], ptc[1] );
|
|
||||||
CNFGTackSegment( ptb[0], ptb[1], ptc[0], ptc[1] );
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int HandleDestroy()
|
|
||||||
{
|
|
||||||
printf( "Destroying\n" );
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
volatile int suspended;
|
|
||||||
|
|
||||||
void HandleSuspend()
|
|
||||||
{
|
|
||||||
suspended = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HandleResume()
|
|
||||||
{
|
|
||||||
suspended = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
void AudioCallback( struct CNFADriver * sd, short * out, short * in, int framesp, int framesr )
|
|
||||||
{
|
|
||||||
memset(out, 0, framesp*sizeof(uint16_t));
|
|
||||||
if(suspended) return;
|
|
||||||
if(!buttonstate[1]) return; // play audio only if ~touching with two fingers
|
|
||||||
audio_frequency = 440;
|
|
||||||
for(uint32_t i = 0; i < framesp; i++) {
|
|
||||||
int16_t sample = INT16_MAX * sin(audio_frequency*(2*M_PI)*(stream_offset+i)/SAMPLE_RATE);
|
|
||||||
out[i] = sample;
|
|
||||||
}
|
|
||||||
stream_offset += framesp;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
void MakeNotification( const char * channelID, const char * channelName, const char * title, const char * message )
|
|
||||||
{
|
|
||||||
static int id;
|
|
||||||
id++;
|
|
||||||
|
|
||||||
const struct JNINativeInterface * env = 0;
|
|
||||||
const struct JNINativeInterface ** envptr = &env;
|
|
||||||
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
|
|
||||||
const struct JNIInvokeInterface * jnii = *jniiptr;
|
|
||||||
|
|
||||||
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
|
|
||||||
env = (*envptr);
|
|
||||||
|
|
||||||
jstring channelIDStr = env->NewStringUTF( ENVCALL channelID );
|
|
||||||
jstring channelNameStr = env->NewStringUTF( ENVCALL channelName );
|
|
||||||
|
|
||||||
// Runs getSystemService(Context.NOTIFICATION_SERVICE).
|
|
||||||
jclass NotificationManagerClass = env->FindClass( ENVCALL "android/app/NotificationManager" );
|
|
||||||
jclass activityClass = env->GetObjectClass( ENVCALL gapp->activity->clazz );
|
|
||||||
jmethodID MethodGetSystemService = env->GetMethodID( ENVCALL activityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
|
|
||||||
jstring notificationServiceName = env->NewStringUTF( ENVCALL "notification" );
|
|
||||||
jobject notificationServiceObj = env->CallObjectMethod( ENVCALL gapp->activity->clazz, MethodGetSystemService, notificationServiceName);
|
|
||||||
|
|
||||||
// create the Notification channel.
|
|
||||||
jclass notificationChannelClass = env->FindClass( ENVCALL "android/app/NotificationChannel" );
|
|
||||||
jmethodID notificationChannelConstructorID = env->GetMethodID( ENVCALL notificationChannelClass, "<init>", "(Ljava/lang/String;Ljava/lang/CharSequence;I)V" );
|
|
||||||
jobject notificationChannelObj = env->NewObject( ENVCALL notificationChannelClass, notificationChannelConstructorID, channelIDStr, channelNameStr, 3 ); // IMPORTANCE_DEFAULT
|
|
||||||
jmethodID createNotificationChannelID = env->GetMethodID( ENVCALL NotificationManagerClass, "createNotificationChannel", "(Landroid/app/NotificationChannel;)V" );
|
|
||||||
env->CallVoidMethod( ENVCALL notificationServiceObj, createNotificationChannelID, notificationChannelObj );
|
|
||||||
|
|
||||||
env->DeleteLocalRef( ENVCALL channelNameStr );
|
|
||||||
env->DeleteLocalRef( ENVCALL notificationChannelObj );
|
|
||||||
|
|
||||||
// Create the Notification builder.
|
|
||||||
jclass classBuilder = env->FindClass( ENVCALL "android/app/Notification$Builder" );
|
|
||||||
jstring titleStr = env->NewStringUTF( ENVCALL title );
|
|
||||||
jstring messageStr = env->NewStringUTF( ENVCALL message );
|
|
||||||
jmethodID eventConstructor = env->GetMethodID( ENVCALL classBuilder, "<init>", "(Landroid/content/Context;Ljava/lang/String;)V" );
|
|
||||||
jobject eventObj = env->NewObject( ENVCALL classBuilder, eventConstructor, gapp->activity->clazz, channelIDStr );
|
|
||||||
jmethodID setContentTitleID = env->GetMethodID( ENVCALL classBuilder, "setContentTitle", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;" );
|
|
||||||
jmethodID setContentTextID = env->GetMethodID( ENVCALL classBuilder, "setContentText", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;" );
|
|
||||||
jmethodID setSmallIconID = env->GetMethodID( ENVCALL classBuilder, "setSmallIcon", "(I)Landroid/app/Notification$Builder;" );
|
|
||||||
|
|
||||||
// You could do things like setPriority, or setContentIntent if you want it to do something when you click it.
|
|
||||||
|
|
||||||
env->CallObjectMethod( ENVCALL eventObj, setContentTitleID, titleStr );
|
|
||||||
env->CallObjectMethod( ENVCALL eventObj, setContentTextID, messageStr );
|
|
||||||
env->CallObjectMethod( ENVCALL eventObj, setSmallIconID, 17301504 ); // R.drawable.alert_dark_frame
|
|
||||||
|
|
||||||
// eventObj.build()
|
|
||||||
jmethodID buildID = env->GetMethodID( ENVCALL classBuilder, "build", "()Landroid/app/Notification;" );
|
|
||||||
jobject notification = env->CallObjectMethod( ENVCALL eventObj, buildID );
|
|
||||||
|
|
||||||
// NotificationManager.notify(...)
|
|
||||||
jmethodID notifyID = env->GetMethodID( ENVCALL NotificationManagerClass, "notify", "(ILandroid/app/Notification;)V" );
|
|
||||||
env->CallVoidMethod( ENVCALL notificationServiceObj, notifyID, id, notification );
|
|
||||||
|
|
||||||
env->DeleteLocalRef( ENVCALL notification );
|
|
||||||
env->DeleteLocalRef( ENVCALL titleStr );
|
|
||||||
env->DeleteLocalRef( ENVCALL activityClass );
|
|
||||||
env->DeleteLocalRef( ENVCALL messageStr );
|
|
||||||
env->DeleteLocalRef( ENVCALL channelIDStr );
|
|
||||||
env->DeleteLocalRef( ENVCALL NotificationManagerClass );
|
|
||||||
env->DeleteLocalRef( ENVCALL notificationServiceObj );
|
|
||||||
env->DeleteLocalRef( ENVCALL notificationServiceName );
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void HandleThisWindowTermination()
|
|
||||||
{
|
|
||||||
suspended = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
uint32_t randomtexturedata[256*256];
|
|
||||||
uint32_t webviewdata[500*500];
|
|
||||||
char fromJSBuffer[128];
|
|
||||||
|
|
||||||
void CheckWebView( void * v )
|
|
||||||
{
|
|
||||||
static int runno = 0;
|
|
||||||
WebViewNativeActivityObject * wvn = (WebViewNativeActivityObject*)v;
|
|
||||||
if( WebViewGetProgress( wvn ) != 100 ) return;
|
|
||||||
|
|
||||||
runno++;
|
|
||||||
if( runno == 1 )
|
|
||||||
{
|
|
||||||
// The attach (initial) message payload has no meaning.
|
|
||||||
WebViewPostMessage( wvn, "", 1 );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Invoke JavaScript, which calls a function to send a webmessage
|
|
||||||
// back into C land.
|
|
||||||
WebViewExecuteJavascript( wvn, "SendMessageToC();" );
|
|
||||||
|
|
||||||
// Send a WebMessage into the JavaScript code.
|
|
||||||
char st[128];
|
|
||||||
sprintf( st, "Into JavaScript %d\n", runno );
|
|
||||||
WebViewPostMessage( wvn, st, 0 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jobject g_attachLooper;
|
|
||||||
|
|
||||||
void SetupWebView( void * v )
|
|
||||||
{
|
|
||||||
WebViewNativeActivityObject * wvn = (WebViewNativeActivityObject*)v;
|
|
||||||
|
|
||||||
|
|
||||||
const struct JNINativeInterface * env = 0;
|
|
||||||
const struct JNINativeInterface ** envptr = &env;
|
|
||||||
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
|
|
||||||
const struct JNIInvokeInterface * jnii = *jniiptr;
|
|
||||||
|
|
||||||
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
|
|
||||||
env = (*envptr);
|
|
||||||
|
|
||||||
while( g_attachLooper == 0 ) usleep(1);
|
|
||||||
WebViewCreate( wvn, "file:///android_asset/index.html", g_attachLooper, 500, 500 );
|
|
||||||
//WebViewCreate( wvn, "about:blank", g_attachLooper, 500, 500 );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pthread_t jsthread;
|
|
||||||
|
|
||||||
void * JavscriptThread( void * v )
|
|
||||||
{
|
|
||||||
const struct JNINativeInterface * env = 0;
|
|
||||||
const struct JNINativeInterface ** envptr = &env;
|
|
||||||
const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm;
|
|
||||||
const struct JNIInvokeInterface * jnii = *jniiptr;
|
|
||||||
|
|
||||||
jnii->AttachCurrentThread( jniiptr, &envptr, NULL);
|
|
||||||
env = (*envptr);
|
|
||||||
|
|
||||||
// Create a looper on this thread...
|
|
||||||
jclass LooperClass = env->FindClass(envptr, "android/os/Looper");
|
|
||||||
jmethodID myLooperMethod = env->GetStaticMethodID(envptr, LooperClass, "myLooper", "()Landroid/os/Looper;");
|
|
||||||
jobject thisLooper = env->CallStaticObjectMethod( envptr, LooperClass, myLooperMethod );
|
|
||||||
if( !thisLooper )
|
|
||||||
{
|
|
||||||
jmethodID prepareMethod = env->GetStaticMethodID(envptr, LooperClass, "prepare", "()V");
|
|
||||||
env->CallStaticVoidMethod( envptr, LooperClass, prepareMethod );
|
|
||||||
thisLooper = env->CallStaticObjectMethod( envptr, LooperClass, myLooperMethod );
|
|
||||||
g_attachLooper = env->NewGlobalRef(envptr, thisLooper);
|
|
||||||
}
|
|
||||||
|
|
||||||
jmethodID getQueueMethod = env->GetMethodID( envptr, LooperClass, "getQueue", "()Landroid/os/MessageQueue;" );
|
|
||||||
jobject lque = env->CallObjectMethod( envptr, g_attachLooper, getQueueMethod );
|
|
||||||
|
|
||||||
jclass MessageQueueClass = env->FindClass(envptr, "android/os/MessageQueue");
|
|
||||||
jmethodID nextMethod = env->GetMethodID( envptr, MessageQueueClass, "next", "()Landroid/os/Message;" );
|
|
||||||
|
|
||||||
jclass MessageClass = env->FindClass(envptr, "android/os/Message");
|
|
||||||
jfieldID objid = env->GetFieldID( envptr, MessageClass, "obj", "Ljava/lang/Object;" );
|
|
||||||
jclass PairClass = env->FindClass(envptr, "android/util/Pair");
|
|
||||||
jfieldID pairfirst = env->GetFieldID( envptr, PairClass, "first", "Ljava/lang/Object;" );
|
|
||||||
|
|
||||||
while(1)
|
|
||||||
{
|
|
||||||
// Instead of using Looper::loop(), we just call next on the looper object.
|
|
||||||
jobject msg = env->CallObjectMethod( envptr, lque, nextMethod );
|
|
||||||
jobject innerObj = env->GetObjectField( envptr, msg, objid );
|
|
||||||
const char * name;
|
|
||||||
jstring strObj;
|
|
||||||
jclass innerClass;
|
|
||||||
|
|
||||||
// Check Object Type
|
|
||||||
{
|
|
||||||
innerClass = env->GetObjectClass( envptr, innerObj );
|
|
||||||
jmethodID mid = env->GetMethodID( envptr, innerClass, "getClass", "()Ljava/lang/Class;");
|
|
||||||
jobject clsObj = env->CallObjectMethod( envptr, innerObj, mid );
|
|
||||||
jclass clazzz = env->GetObjectClass( envptr, clsObj );
|
|
||||||
mid = env->GetMethodID(envptr, clazzz, "getName", "()Ljava/lang/String;");
|
|
||||||
strObj = (jstring)env->CallObjectMethod( envptr, clsObj, mid);
|
|
||||||
name = env->GetStringUTFChars( envptr, strObj, 0);
|
|
||||||
env->DeleteLocalRef( envptr, clsObj );
|
|
||||||
env->DeleteLocalRef( envptr, clazzz );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( strcmp( name, "z5" ) == 0 )
|
|
||||||
{
|
|
||||||
// Special, Some Androids (notably Meta Quest) use a different private message type.
|
|
||||||
jfieldID mstrf = env->GetFieldID( envptr, innerClass, "a", "[B" );
|
|
||||||
jbyteArray jba = (jstring)env->GetObjectField(envptr, innerObj, mstrf );
|
|
||||||
int len = env->GetArrayLength( envptr, jba );
|
|
||||||
jboolean isCopy = 0;
|
|
||||||
jbyte * bufferPtr = env->GetByteArrayElements(envptr, jba, &isCopy);
|
|
||||||
|
|
||||||
if( len >= 6 )
|
|
||||||
{
|
|
||||||
const char *descr = (const char*)bufferPtr + 6;
|
|
||||||
char tcpy[len-5];
|
|
||||||
memcpy( tcpy, descr, len-6 );
|
|
||||||
tcpy[len-6] = 0;
|
|
||||||
snprintf( fromJSBuffer, sizeof( fromJSBuffer)-1, "WebMessage: %s\n", tcpy );
|
|
||||||
|
|
||||||
env->DeleteLocalRef( envptr, jba );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
jobject MessagePayload = env->GetObjectField( envptr, innerObj, pairfirst );
|
|
||||||
// MessagePayload is a org.chromium.content_public.browser.MessagePayload
|
|
||||||
|
|
||||||
jclass mpclass = env->GetObjectClass( envptr, MessagePayload );
|
|
||||||
|
|
||||||
// Get field "b" which is the web message payload.
|
|
||||||
// If you are using binary sockets, it will be in `c` and be a byte array.
|
|
||||||
jfieldID mstrf = env->GetFieldID( envptr, mpclass, "b", "Ljava/lang/String;" );
|
|
||||||
jstring strObjDescr = (jstring)env->GetObjectField(envptr, MessagePayload, mstrf );
|
|
||||||
|
|
||||||
const char *descr = env->GetStringUTFChars( envptr, strObjDescr, 0);
|
|
||||||
snprintf( fromJSBuffer, sizeof( fromJSBuffer)-1, "WebMessage: %s\n", descr );
|
|
||||||
|
|
||||||
env->ReleaseStringUTFChars(envptr, strObjDescr, descr);
|
|
||||||
env->DeleteLocalRef( envptr, strObjDescr );
|
|
||||||
env->DeleteLocalRef( envptr, MessagePayload );
|
|
||||||
env->DeleteLocalRef( envptr, mpclass );
|
|
||||||
}
|
|
||||||
env->ReleaseStringUTFChars(envptr, strObj, name);
|
|
||||||
env->DeleteLocalRef( envptr, strObj );
|
|
||||||
env->DeleteLocalRef( envptr, msg );
|
|
||||||
env->DeleteLocalRef( envptr, innerObj );
|
|
||||||
env->DeleteLocalRef( envptr, innerClass );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetupJSThread()
|
|
||||||
{
|
|
||||||
pthread_create( &jsthread, 0, JavscriptThread, 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
int main( int argc, char ** argv )
|
|
||||||
{
|
|
||||||
int x, y;
|
|
||||||
double ThisTime;
|
|
||||||
double LastFPSTime = OGGetAbsoluteTime();
|
|
||||||
|
|
||||||
Log( "Starting Up" );
|
|
||||||
|
|
||||||
CNFGBGColor = 0x000040ff;
|
|
||||||
CNFGSetupFullscreen( "Test Bench", 0 );
|
|
||||||
|
|
||||||
HandleWindowTermination = HandleThisWindowTermination;
|
|
||||||
|
|
||||||
for( x = 0; x < HMX; x++ )
|
|
||||||
for( y = 0; y < HMY; y++ )
|
|
||||||
{
|
|
||||||
Heightmap[x+y*HMX] = tdPerlin2D( x, y )*8.;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char * assettext = "Not Found";
|
|
||||||
AAsset * file = AAssetManager_open( gapp->activity->assetManager, "asset.txt", AASSET_MODE_BUFFER );
|
|
||||||
if( file )
|
|
||||||
{
|
|
||||||
size_t fileLength = AAsset_getLength(file);
|
|
||||||
char * temp = (char*)malloc( fileLength + 1);
|
|
||||||
memcpy( temp, AAsset_getBuffer( file ), fileLength );
|
|
||||||
temp[fileLength] = 0;
|
|
||||||
assettext = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetupIMU();
|
|
||||||
|
|
||||||
// Disabled, for now.
|
|
||||||
//InitCNFAAndroid( AudioCallback, "A Name", SAMPLE_RATE, 0, 1, 0, SAMPLE_COUNT, 0, 0, 0 );
|
|
||||||
|
|
||||||
SetupJSThread();
|
|
||||||
|
|
||||||
// Create webview and wait for its completion
|
|
||||||
RunCallbackOnUIThread( SetupWebView, &MyWebView );
|
|
||||||
while( !MyWebView.WebViewObject ) usleep(1);
|
|
||||||
|
|
||||||
Log( "Startup Complete" );
|
|
||||||
|
|
||||||
while(1)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
iframeno++;
|
|
||||||
|
|
||||||
if( iframeno == 200 )
|
|
||||||
{
|
|
||||||
MakeNotification( "default", "rawdraw alerts", "rawdraw", "Hit frame two hundred\nNew Line" );
|
|
||||||
}
|
|
||||||
|
|
||||||
CNFGHandleInput();
|
|
||||||
AccCheck();
|
|
||||||
|
|
||||||
if( suspended ) { usleep(50000); continue; }
|
|
||||||
|
|
||||||
RunCallbackOnUIThread( (void(*)(void*))WebViewRequestRenderToCanvas, &MyWebView );
|
|
||||||
RunCallbackOnUIThread( CheckWebView, &MyWebView );
|
|
||||||
|
|
||||||
CNFGClearFrame();
|
|
||||||
CNFGColor( 0xFFFFFFFF );
|
|
||||||
CNFGGetDimensions( &screenx, &screeny );
|
|
||||||
|
|
||||||
// Mesh in background
|
|
||||||
CNFGSetLineWidth( 9 );
|
|
||||||
DrawHeightmap();
|
|
||||||
CNFGPenX = 0; CNFGPenY = 400;
|
|
||||||
CNFGColor( 0xffffffff );
|
|
||||||
CNFGDrawText( assettext, 15 );
|
|
||||||
CNFGFlushRender();
|
|
||||||
|
|
||||||
CNFGPenX = 0; CNFGPenY = 480;
|
|
||||||
char st[50];
|
|
||||||
sprintf( st, "%dx%d %d %d %d %d %d %d\n%d %d\n%5.2f %5.2f %5.2f %d", screenx, screeny, lastbuttonx, lastbuttony, lastmotionx, lastmotiony, lastkey, lastkeydown, lastbid, lastmask, accx, accy, accz, accs );
|
|
||||||
CNFGDrawText( st, 10 );
|
|
||||||
CNFGSetLineWidth( 2 );
|
|
||||||
|
|
||||||
// Square behind text
|
|
||||||
CNFGColor( 0x303030ff );
|
|
||||||
CNFGTackRectangle( 600, 0, 950, 350);
|
|
||||||
|
|
||||||
CNFGPenX = 10; CNFGPenY = 10;
|
|
||||||
|
|
||||||
// Text
|
|
||||||
CNFGColor( 0xffffffff );
|
|
||||||
for( i = 0; i < 1; i++ )
|
|
||||||
{
|
|
||||||
int c;
|
|
||||||
char tw[2] = { 0, 0 };
|
|
||||||
for( c = 0; c < 256; c++ )
|
|
||||||
{
|
|
||||||
tw[0] = c;
|
|
||||||
|
|
||||||
CNFGPenX = ( c % 16 ) * 20+606;
|
|
||||||
CNFGPenY = ( c / 16 ) * 20+5;
|
|
||||||
CNFGDrawText( tw, 4 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Green triangles
|
|
||||||
CNFGPenX = 0;
|
|
||||||
CNFGPenY = 0;
|
|
||||||
CNFGColor( 0x00FF00FF );
|
|
||||||
|
|
||||||
for( i = 0; i < 400; i++ )
|
|
||||||
{
|
|
||||||
RDPoint pp[3];
|
|
||||||
pp[0].x = (short)(50*sin((float)(i+iframeno)*.01) + (i%20)*30);
|
|
||||||
pp[0].y = (short)(50*cos((float)(i+iframeno)*.01) + (i/20)*20)+700;
|
|
||||||
pp[1].x = (short)(20*sin((float)(i+iframeno)*.01) + (i%20)*30);
|
|
||||||
pp[1].y = (short)(50*cos((float)(i+iframeno)*.01) + (i/20)*20)+700;
|
|
||||||
pp[2].x = (short)(10*sin((float)(i+iframeno)*.01) + (i%20)*30);
|
|
||||||
pp[2].y = (short)(30*cos((float)(i+iframeno)*.01) + (i/20)*20)+700;
|
|
||||||
CNFGTackPoly( pp, 3 );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Last WebMessage
|
|
||||||
CNFGColor( 0xFFFFFFFF );
|
|
||||||
CNFGPenX = 0; CNFGPenY = 100;
|
|
||||||
CNFGDrawText( fromJSBuffer, 6 );
|
|
||||||
|
|
||||||
int x, y;
|
|
||||||
for( y = 0; y < 256; y++ )
|
|
||||||
for( x = 0; x < 256; x++ )
|
|
||||||
randomtexturedata[x+y*256] = x | ((x*394543L+y*355+iframeno*3)<<8);
|
|
||||||
CNFGBlitImage( randomtexturedata, 100, 600, 256, 256 );
|
|
||||||
|
|
||||||
WebViewNativeGetPixels( &MyWebView, webviewdata, 500, 500 );
|
|
||||||
CNFGBlitImage( webviewdata, 500, 640, 500, 500 );
|
|
||||||
|
|
||||||
frames++;
|
|
||||||
//On Android, CNFGSwapBuffers must be called, and CNFGUpdateScreenWithBitmap does not have an implied framebuffer swap.
|
|
||||||
CNFGSwapBuffers();
|
|
||||||
|
|
||||||
ThisTime = OGGetAbsoluteTime();
|
|
||||||
if( ThisTime > LastFPSTime + 1 )
|
|
||||||
{
|
|
||||||
printf( "FPS: %d\n", frames );
|
|
||||||
frames = 0;
|
|
||||||
LastFPSTime+=1;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return(0);
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue