[go: nahoru, domu]

Skip to content

Commit

Permalink
fix(Android): Threading issues leading to multiple others. Performanc…
Browse files Browse the repository at this point in the history
…e, accuracy and compatibility with older devices.
  • Loading branch information
llfbandit committed May 30, 2024
1 parent 14b8b5f commit f9c55ec
Show file tree
Hide file tree
Showing 23 changed files with 417 additions and 371 deletions.
4 changes: 4 additions & 0 deletions record/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 5.1.1
* chore: Updated Android dependency.
* chore: Updated README.md.

## 5.1.0
* chore: Updated Android and web dependencies.
* chore: Updated README.md.
Expand Down
7 changes: 3 additions & 4 deletions record/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ but are not available in current (and tested) browsers (Chrome / Firefox).
| pcm16bits | ✔️ 2 | ✔️ | ✔️ | ✔️ | ✔️ |

\* AAC is streamed with raw AAC with ADTS headers, so it's directly readable through a file!
1. min SDK: 23. Bluetooth telephony device link (SCO) is automatically done but there's no phone call management.
1. Bluetooth telephony device link (SCO) is automatically done but there's no phone call management.
2. Unsupported on legacy Android recorder.
3. Sample rate output is determined by your settings in OS. Bit depth is likely 32 bits.

Expand Down Expand Up @@ -79,13 +79,12 @@ record.dispose(); // As always, don't forget this one.
### Android
```xml
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- Optional: Add this permission if you want to use bluetooth telephony device like headset/earbuds (min SDK: 23) -->
<!-- Optional: Add this permission if you want to use bluetooth telephony device like headset/earbuds -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!-- Optional: Add this permission if you want to save your recordings in public folders -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
```
- min SDK: 21 (amrNb/amrWb: 26, Opus: 29)
- min SDK: 19 with legacy recorder.
- min SDK: 23 (amrNb/amrWb: 26, Opus: 29)

* [Audio formats sample rate hints](https://developer.android.com/guide/topics/media/media-formats#audio-formats)

Expand Down
11 changes: 8 additions & 3 deletions record/example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ if (flutterVersionName == null) {
}

android {
compileSdkVersion 33
compileSdk 34

lintOptions {
disable 'InvalidPackage'
}

defaultConfig {
applicationId "com.llfbandit.record_example"
minSdkVersion 21
targetSdkVersion 33
minSdkVersion 23
targetSdkVersion 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
Expand All @@ -45,6 +45,11 @@ android {
}
}
namespace 'com.llfbandit.record_example'

compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}

flutter {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Mon Apr 03 14:57:23 CEST 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
7 changes: 6 additions & 1 deletion record/example/windows/flutter/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake)
# https://github.com/flutter/flutter/issues/57146.
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")

# Set fallback configurations for older versions of the flutter tool.
if (NOT DEFINED FLUTTER_TARGET_PLATFORM)
set(FLUTTER_TARGET_PLATFORM "windows-x64")
endif()

# === Flutter Library ===
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")

Expand Down Expand Up @@ -91,7 +96,7 @@ add_custom_command(
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
windows-x64 $<CONFIG>
${FLUTTER_TARGET_PLATFORM} $<CONFIG>
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
Expand Down
4 changes: 2 additions & 2 deletions record/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: record
description: Audio recorder from microphone to file or stream with multiple codecs, bit rate and sampling rate options.
version: 5.1.0
version: 5.1.1
homepage: https://github.com/llfbandit/record/tree/master/record

environment:
Expand All @@ -18,7 +18,7 @@ dependencies:
record_web: ^1.0.4
record_windows: ^1.0.2
record_linux: '>=0.5.0 <1.0.0'
record_android: ^1.2.0
record_android: ^1.2.2
record_darwin: ^1.1.0

dev_dependencies:
Expand Down
8 changes: 8 additions & 0 deletions record_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 1.2.2
* fix: Threading issues. Model is now more robust and overall performance is much better.
* fix: Recording on older Android devices ..., 8, 9.
* fix: Recording on slow devices (e.g. writing on external SD cards, ...).
* fix: Audio duration detection should be less a problem now.
* chore: minSDK is now 23 (Android 6), targetSDK is now 34.
* chore: code cleanup and adjustment from new minSDK.

## 1.2.1
* fix: Stopping stream recording throws ExceptionInterruptedException.

Expand Down
24 changes: 6 additions & 18 deletions record_android/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ buildscript {

dependencies {
// https://developer.android.com/studio/releases/gradle-plugin
classpath 'com.android.tools.build:gradle:7.4.2'
classpath 'com.android.tools.build:gradle:8.4.1'
// https://plugins.gradle.org/plugin/org.jetbrains.kotlin.android
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
Expand All @@ -32,34 +32,22 @@ android {
namespace 'com.llfbandit.record'
}

compileSdkVersion 33
compileSdk 34

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '17'
}

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}

defaultConfig {
minSdkVersion 21
minSdk 23
}
}

/*allprojects {
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
}
}
}*/

dependencies {
runtimeOnly 'androidx.appcompat:appcompat:1.6.1'
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ public static <T> T checkNotNull(T reference) {
}

public static void deleteFile(String path) {
if (path != null) {
if (path == null) return;

try {
File file = new File(path);

if (file.exists()) {
//noinspection ResultOfMethodCallIgnored
file.delete();
}
} catch (SecurityException e) {
// Ignored
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.llfbandit.record.methodcall

import android.app.Activity
import android.content.Context
import android.os.Build
import com.llfbandit.record.Utils
import com.llfbandit.record.permission.PermissionManager
import com.llfbandit.record.record.RecordConfig
Expand Down Expand Up @@ -144,11 +143,7 @@ class MethodCallHandlerImpl(
Utils.firstNonNull(call.argument("bitRate"), 128000),
Utils.firstNonNull(call.argument("sampleRate"), 44100),
Utils.firstNonNull(call.argument("numChannels"), 2),
if (Build.VERSION.SDK_INT >= 23) {
DeviceUtils.deviceInfoFromMap(appContext, call.argument("device"))
} else {
null
},
DeviceUtils.deviceInfoFromMap(appContext, call.argument("device")),
Utils.firstNonNull(call.argument("autoGain"), false),
Utils.firstNonNull(call.argument("echoCancel"), false),
Utils.firstNonNull(call.argument("noiseSuppress"), false),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
package com.llfbandit.record.record

import android.annotation.SuppressLint
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaFormat
import android.media.MediaRecorder
import android.media.audiofx.AcousticEchoCanceler
import android.media.audiofx.AutomaticGainControl
import android.media.audiofx.NoiseSuppressor
import android.os.Build
import android.util.Log
import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.math.abs
import kotlin.math.log10


class PCMReader(
// Config to setup the recording
private val config: RecordConfig,
Expand All @@ -30,7 +31,7 @@ class PCMReader(
private var noiseSuppressor: NoiseSuppressor? = null

// Min size of the buffer for writings
var bufferSize = 0
private var bufferSize = 0

// Last acquired amplitude
private var amplitude: Double = -160.0
Expand All @@ -56,22 +57,22 @@ class PCMReader(
}

@Throws(Exception::class)
fun read(audioBuffer: ByteBuffer): Int {
val resultBytes = reader.read(audioBuffer, audioBuffer.remaining())
fun read(): ByteArray {
val buffer = ByteArray(bufferSize)
val resultBytes = reader.read(buffer, 0, bufferSize)
if (resultBytes < 0) {
throw Exception(getReadFailureReason(resultBytes))
}

audioBuffer.limit(resultBytes)
val audioBuffer = ByteArray(resultBytes)
System.arraycopy(buffer, 0, audioBuffer, 0, resultBytes)

if (resultBytes > 0) {
val buffer = ByteArray(resultBytes)
audioBuffer.duplicate()[buffer, 0, resultBytes]

// Update amplitude
amplitude = getAmplitude(buffer, resultBytes)
amplitude = getAmplitude(audioBuffer, resultBytes)
}
return resultBytes

return audioBuffer
}

fun getAmplitude(): Double {
Expand All @@ -85,6 +86,7 @@ class PCMReader(
noiseSuppressor?.release()
}

@SuppressLint("MissingPermission")
@Throws(Exception::class)
private fun createReader(): AudioRecord {
val sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE)
Expand All @@ -105,7 +107,7 @@ class PCMReader(
throw Exception("PCM reader failed to initialize.")
}

if (Build.VERSION.SDK_INT >= 23 && config.device != null) {
if (config.device != null) {
if (!reader.setPreferredDevice(config.device)) {
Log.w(TAG, "Unable to set device ${config.device.productName}")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import android.content.IntentFilter
import android.media.AudioDeviceCallback
import android.media.AudioDeviceInfo
import android.media.AudioManager
import android.os.Build
import com.llfbandit.record.record.device.DeviceUtils

interface BluetoothScoListener {
Expand Down Expand Up @@ -36,39 +35,37 @@ class BluetoothReceiver(
fun register() {
context.registerReceiver(this, filter)

if (Build.VERSION.SDK_INT >= 23) {
audioDeviceCallback = object : AudioDeviceCallback() {
override fun onAudioDevicesAdded(addedDevices: Array<AudioDeviceInfo>) {
devices.addAll(DeviceUtils.filterSources(addedDevices.asList()))

val hasBluetoothSco = devices.any {
it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO
}
if (hasBluetoothSco) {
startBluetooth()
}
audioDeviceCallback = object : AudioDeviceCallback() {
override fun onAudioDevicesAdded(addedDevices: Array<AudioDeviceInfo>) {
devices.addAll(DeviceUtils.filterSources(addedDevices.asList()))

val hasBluetoothSco = devices.any {
it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO
}
if (hasBluetoothSco) {
startBluetooth()
}
}

override fun onAudioDevicesRemoved(removedDevices: Array<AudioDeviceInfo>) {
devices.removeAll(DeviceUtils.filterSources(removedDevices.asList()).toSet())
override fun onAudioDevicesRemoved(removedDevices: Array<AudioDeviceInfo>) {
devices.removeAll(DeviceUtils.filterSources(removedDevices.asList()).toSet())

val hasBluetoothSco = devices.any {
it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO
}
if (!hasBluetoothSco) {
stopBluetooth()
}
val hasBluetoothSco = devices.any {
it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO
}
if (!hasBluetoothSco) {
stopBluetooth()
}
}

audioManager.registerAudioDeviceCallback(audioDeviceCallback, null)
}

audioManager.registerAudioDeviceCallback(audioDeviceCallback, null)
}

fun unregister() {
stopBluetooth()

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && audioDeviceCallback != null) {
if (audioDeviceCallback != null) {
audioManager.unregisterAudioDeviceCallback(audioDeviceCallback)
audioDeviceCallback = null
}
Expand Down
Loading

0 comments on commit f9c55ec

Please sign in to comment.