Skip to content

Commit

Permalink
release 1.7.0
Browse files Browse the repository at this point in the history
  • Loading branch information
teamclouday committed May 9, 2022
1 parent 3e9210d commit dd96bfc
Show file tree
Hide file tree
Showing 18 changed files with 115 additions and 62 deletions.
22 changes: 11 additions & 11 deletions Android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ plugins {
}

android {
compileSdkVersion 30
compileSdkVersion 31
buildToolsVersion "30.0.3"

defaultConfig {
applicationId "com.example.microphone"
minSdkVersion 23
targetSdkVersion 30
targetSdkVersion 31
versionCode 6
versionName "1.6"
versionName "1.7"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand All @@ -35,13 +35,13 @@ android {
dependencies {

implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.6.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
4 changes: 3 additions & 1 deletion Android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package="com.example.microphone">
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Expand All @@ -16,7 +17,8 @@
android:theme="@style/Theme.Microphone"
android:name=".DefaultApp">
<activity
android:name=".MainActivity">
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ class MainActivity : AppCompatActivity()
// check for audio permission
if(ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED)
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO), 0)
if(ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH) != PackageManager.PERMISSION_GRANTED)
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.BLUETOOTH), 0)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if(ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED)
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.BLUETOOTH_CONNECT), 0)
}
// start handler thread
handlerThread = HandlerThread("MicActivityStart", Process.THREAD_PRIORITY_BACKGROUND)
handlerThread.start()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import kotlinx.coroutines.sync.withLock
class AudioBuffer
{
// set buffer size for latency
private val BUFFER_SIZE = 2
private val BUFFER_SIZE = 3
// actual buffer of byte arrays (FIFO with queue)
private val buffer = ArrayDeque<ByteArray>()
// mutex for coroutines
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioRecord
import android.media.MediaRecorder
import android.util.Log
Expand Down Expand Up @@ -42,8 +41,6 @@ class MicAudioManager(ctx : Context) {
require(ContextCompat.checkSelfPermission(ctx, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED){
"Microphone recording is not permitted"
}
// setup noise suppression
(ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager).setParameters("noise_suppression=auto")
// init recorder
recorder = AudioRecord(AUDIO_SOURCE, SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, BUFFER_SIZE)
require(recorder?.state == AudioRecord.STATE_INITIALIZED){"Microphone not init properly"}
Expand All @@ -53,7 +50,7 @@ class MicAudioManager(ctx : Context) {
suspend fun record(audioBuffer : AudioBuffer)
{
// read number of shorts
val size = recorder?.read(buffer, 0, BUFFER_SIZE, AudioRecord.READ_BLOCKING) ?: return
val size = recorder?.read(buffer, 0, BUFFER_SIZE) ?: return
if(size <= 0)
{
delay(RECORD_DELAY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ class ForegroundService : Service() {
val onTap = Intent(ctx, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
}
val pendingIntent = PendingIntent.getActivity(ctx, 0, onTap, 0)
val pendingIntent = PendingIntent.getActivity(ctx, 0, onTap, PendingIntent.FLAG_IMMUTABLE)
val builder = NotificationCompat.Builder(ctx, "AndroidMic")
.setSmallIcon(R.drawable.icon)
.setContentTitle(getString(R.string.activity_name))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package com.example.microphone.streaming

import android.Manifest
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothClass
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothSocket
import android.bluetooth.*
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.core.content.ContextCompat
import com.example.microphone.audio.AudioBuffer
Expand All @@ -26,7 +24,7 @@ class BluetoothStreamer(private val ctx : Context) : Streamer {
private val myUUID : UUID = UUID.fromString("34335e34-bccf-11eb-8529-0242ac130003")
private val MAX_WAIT_TIME = 1500L // timeout

private val adapter : BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
private val adapter : BluetoothAdapter
private var target : BluetoothDevice? = null
private var socket : BluetoothSocket? = null

Expand All @@ -51,12 +49,20 @@ class BluetoothStreamer(private val ctx : Context) : Streamer {
// init everything
init
{
// get bluetooth adapter
val bm = ctx.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
adapter = bm.adapter
// check bluetooth adapter
require(adapter != null) {"Bluetooth adapter is not found"}
// check permission
require(ContextCompat.checkSelfPermission(ctx, Manifest.permission.BLUETOOTH) == PackageManager.PERMISSION_GRANTED){
"Bluetooth is not permitted"
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
require(ContextCompat.checkSelfPermission(ctx, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED){
"Bluetooth is not permitted"
}
}
require(adapter.isEnabled){"Bluetooth adapter is not enabled"}
// set target device
selectTargetDevice()
Expand All @@ -77,13 +83,21 @@ class BluetoothStreamer(private val ctx : Context) : Streamer {
} catch (e : IOException) {
Log.d(TAG, "connect [createInsecureRfcommSocketToServiceRecord]: ${e.message}")
null
} catch (e : SecurityException)
{
Log.d(TAG, "connect [createInsecureRfcommSocketToServiceRecord]: ${e.message}")
null
} ?: return false
// connect to server
try {
socket?.connect()
} catch (e : IOException){
Log.d(TAG, "connect [connect]: ${e.message}")
return false
} catch (e : SecurityException)
{
Log.d(TAG, "connect [connect]: ${e.message}")
return false
}
Log.d(TAG, "connect: connected")
return true
Expand Down Expand Up @@ -136,22 +150,28 @@ class BluetoothStreamer(private val ctx : Context) : Streamer {
private fun selectTargetDevice()
{
target = null
val pairedDevices = adapter?.bondedDevices ?: return
for(device in pairedDevices)
{
if(device.bluetoothClass.majorDeviceClass == BluetoothClass.Device.Major.COMPUTER)
try {
val pairedDevices = adapter.bondedDevices ?: return
for(device in pairedDevices)
{
Log.d(TAG, "selectTargetDevice: testing ${device.name}")
if(testConnection(device))
if(device.bluetoothClass.majorDeviceClass == BluetoothClass.Device.Major.COMPUTER)
{
target = device
Log.d(TAG, "selectTargetDevice: ${device.name} is valid")
break
Log.d(TAG, "selectTargetDevice: testing ${device.name}")
if(testConnection(device))
{
target = device
Log.d(TAG, "selectTargetDevice: ${device.name} is valid")
break
}
else
Log.d(TAG, "selectTargetDevice: ${device.name} is invalid")
}
else
Log.d(TAG, "selectTargetDevice: ${device.name} is invalid")
}
} catch(e : SecurityException)
{
Log.d(TAG, "selectTargetDevice: ${e.message}")
}

}

// test connection with a device
Expand All @@ -165,13 +185,21 @@ class BluetoothStreamer(private val ctx : Context) : Streamer {
} catch (e : IOException) {
Log.d(TAG, "testConnection [createInsecureRfcommSocketToServiceRecord]: ${e.message}")
null
} catch (e : SecurityException)
{
Log.d(TAG, "testConnection [createInsecureRfcommSocketToServiceRecord]: ${e.message}")
null
} ?: return false
// try to connect
try {
socket.connect()
} catch (e : IOException){
Log.d(TAG, "testConnection [connect]: ${e.message}")
return false
} catch (e : SecurityException)
{
Log.d(TAG, "testConnection [connect]: ${e.message}")
return false
}
var isValid = false
runBlocking(Dispatchers.IO) {
Expand Down Expand Up @@ -215,8 +243,15 @@ class BluetoothStreamer(private val ctx : Context) : Streamer {
// get connected device information
override fun getInfo() : String
{
if(adapter == null || target == null || socket == null) return ""
return "[Device Name] ${target?.name}\n[Device Address] ${target?.address}"
if(target == null || socket == null) return ""
val deviceName = try {
target?.name
} catch (e : SecurityException)
{
Log.d(TAG, "getInfo: ${e.message}")
"null"
}
return "[Device Name] ${deviceName}\n[Device Address] ${target?.address}"
}

// return true if is connected for streaming
Expand Down
2 changes: 1 addition & 1 deletion Android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.0'
classpath 'com.android.tools.build:gradle:7.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

// NOTE: Do not place your application dependencies here; they belong
Expand Down
2 changes: 1 addition & 1 deletion Android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Mon May 24 15:46:59 EDT 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
Binary file modified Assets/p1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Assets/p4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Assets/sound_config6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ Use your Android phone as a microphone to Windows PC
6. For microphone, click `Properties` and set following:
<img src="Assets/sound_config5.png" width="300" alt="sound config5">

On my machine, this setup has the lowest delay and best sound quality. VB is not optimized as hardware devices, so these configurations are important for audio.
On my machine, this setup has the lowest delay and best sound quality.
Can further improve audio latency by opening `VBCABLE_ControlPanel.exe` (from downloaded folder of VB) and set `Max Latency` in Options to 2048 smp:
<img src="Assets/sound_config6.png" width="600" alt="sound config6">

Do not set to 512 smp since that will cause most buffers lost. If 2048 has no sound or bad quality, consider a higher smp.

</details>

Expand Down Expand Up @@ -106,7 +110,7 @@ __SpeexDSP Filters__:
- [ ] Audio effect filters (Not yet released)
- [x] Pitch Shifter
- [x] Add White Noise
- [x] Repeated Background Track
- [x] Repeated Background Audio
- [x] SpeexDSP Noise Cancellation
- [x] SpeexDSP Automatic Gain Control
- [x] SpeexDSP Voice Activity Detection
Expand All @@ -121,18 +125,18 @@ Check out [Avalonia](https://github.com/AvaloniaUI/Avalonia)! With this it may b

Pre-built installers can be found [here](https://github.com/teamclouday/AndroidMic/releases)


------

## Windows Side

<img src="Assets/p1.png" width="500" alt="Windows Side">

## Android Side (Portrait)
<p float="left">
<img src="Assets/p1.png" width="510" alt="Windows Side">
<img src="Assets/p4.png" width="250" alt="Windows Side">
</p>

<img src="Assets/p2.jpg" width="250" alt="Android Side">
## Android Side

## Android Side (Landscape)
<img src="Assets/p2.jpg" width="250" alt="Android Side">

<img src="Assets/p3.jpg" width="500" alt="Android Side">

2 changes: 1 addition & 1 deletion Windows/AndroidMic/AdvancedWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
<CheckBox Content="Noise Cancellation" VerticalContentAlignment="Center" x:Name="NoiseCancelEnableCheckbox"
FontSize="20" FontFamily="Comic Sans MS" VerticalAlignment="Bottom" Width="300"
Checked="NoiseCancelEnableCheckbox_StateChanged" Unchecked="NoiseCancelEnableCheckbox_StateChanged" Margin="0,0,0,170"/>
<CheckBox Content="Automic Gain Control" VerticalContentAlignment="Center" x:Name="AutomicGainEnableCheckbox"
<CheckBox Content="Automatic Gain Control" VerticalContentAlignment="Center" x:Name="AutomicGainEnableCheckbox"
FontSize="20" FontFamily="Comic Sans MS" VerticalAlignment="Bottom" Width="300"
Checked="AutomicGainEnableCheckbox_StateChanged" Unchecked="AutomicGainEnableCheckbox_StateChanged" Margin="0,0,0,130"/>
<CheckBox Content="Voice Activity Detector" VerticalContentAlignment="Center" x:Name="VADEnableCheckbox"
Expand Down
2 changes: 1 addition & 1 deletion Windows/AndroidMic/AndroidMic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<ProductName>AndroidMic</ProductName>
<PublisherName>Teamclouday</PublisherName>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.6.0.0</ApplicationVersion>
<ApplicationVersion>1.7.0.0</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<PublishWizardCompleted>true</PublishWizardCompleted>
<BootstrapperEnabled>true</BootstrapperEnabled>
Expand Down
2 changes: 1 addition & 1 deletion Windows/AndroidMic/Library/Audio/AudioBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace AndroidMic.Audio
{
public class AudioBuffer
{
public const int MAX_BUFFER_SIZE = 2;
public const int MAX_BUFFER_SIZE = 3;
private readonly Queue<byte[]> buffer = new Queue<byte[]>();
private readonly object toLock = new object();

Expand Down
Loading

0 comments on commit dd96bfc

Please sign in to comment.