10 changed files with 448 additions and 732 deletions
@ -1,572 +0,0 @@ |
|||
package com.example.deep_voice |
|||
|
|||
import android.content.Context |
|||
import android.media.audiofx.AcousticEchoCanceler |
|||
import android.media.audiofx.NoiseSuppressor |
|||
import android.media.audiofx.AutomaticGainControl |
|||
import android.media.AudioManager |
|||
import android.os.Build |
|||
import android.util.Log |
|||
import io.flutter.embedding.engine.plugins.FlutterPlugin |
|||
import io.flutter.plugin.common.MethodCall |
|||
import io.flutter.plugin.common.MethodChannel |
|||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler |
|||
import io.flutter.plugin.common.MethodChannel.Result |
|||
import io.flutter.embedding.engine.plugins.activity.ActivityAware |
|||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding |
|||
import android.app.Application |
|||
import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding |
|||
import io.flutter.embedding.engine.FlutterEngine |
|||
import io.flutter.FlutterInjector |
|||
|
|||
/** |
|||
* 音频处理插件 |
|||
* |
|||
* 提供回音消除、降噪和自动增益控制等功能 |
|||
*/ |
|||
class AudioProcessingPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { |
|||
private lateinit var channel: MethodChannel |
|||
private lateinit var context: Context |
|||
private lateinit var audioManager: AudioManager |
|||
|
|||
// 音频处理组件 |
|||
private var echoCanceler: AcousticEchoCanceler? = null |
|||
private var noiseSuppressor: NoiseSuppressor? = null |
|||
private var automaticGainControl: AutomaticGainControl? = null |
|||
|
|||
// 当前音频会话ID |
|||
private var currentAudioSessionId: Int = -1 |
|||
|
|||
companion object { |
|||
private const val TAG = "AudioProcessingPlugin" |
|||
private const val CHANNEL_NAME = "com.deep_voice.audio_processing" |
|||
|
|||
// 保存静态实例,确保在任何地方都能访问 |
|||
@JvmStatic |
|||
private var instance: AudioProcessingPlugin? = null |
|||
|
|||
// 保存应用Context |
|||
@JvmStatic |
|||
private var applicationContext: Context? = null |
|||
|
|||
// 静态初始化块,尝试在类加载时就获取applicationContext |
|||
init { |
|||
try { |
|||
Log.i(TAG, "AudioProcessingPlugin静态初始化块被执行") |
|||
// 尝试通过FlutterInjector获取applicationContext |
|||
val flutterLoader = FlutterInjector.instance().flutterLoader() |
|||
if (flutterLoader.initialized()) { |
|||
Log.i(TAG, "FlutterLoader已初始化") |
|||
} else { |
|||
Log.i(TAG, "FlutterLoader尚未初始化") |
|||
} |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "静态初始化块执行失败: ${e.message}") |
|||
} |
|||
} |
|||
|
|||
// 获取实例的方法 |
|||
@JvmStatic |
|||
fun getInstance(): AudioProcessingPlugin? { |
|||
return instance |
|||
} |
|||
|
|||
// 获取应用Context的方法 |
|||
@JvmStatic |
|||
fun getApplicationContext(): Context? { |
|||
return applicationContext |
|||
} |
|||
|
|||
// 设置应用Context的方法,可以从外部调用 |
|||
@JvmStatic |
|||
fun setApplicationContext(ctx: Context) { |
|||
if (applicationContext == null) { |
|||
applicationContext = ctx.applicationContext |
|||
Log.i(TAG, "通过setApplicationContext方法设置了applicationContext") |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 构造函数,尝试获取applicationContext |
|||
init { |
|||
Log.i(TAG, "AudioProcessingPlugin实例初始化块被执行") |
|||
} |
|||
|
|||
override fun onAttachedToEngine(binding: FlutterPluginBinding) { |
|||
Log.i(TAG, "onAttachedToEngine被调用") |
|||
context = binding.applicationContext |
|||
applicationContext = context |
|||
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager |
|||
channel = MethodChannel(binding.binaryMessenger, CHANNEL_NAME) |
|||
channel.setMethodCallHandler(this) |
|||
instance = this |
|||
Log.i(TAG, "音频处理插件已初始化,context: $context, applicationContext: $applicationContext") |
|||
} |
|||
|
|||
override fun onDetachedFromEngine(binding: FlutterPluginBinding) { |
|||
channel.setMethodCallHandler(null) |
|||
releaseAudioEffects() |
|||
if (instance == this) { |
|||
instance = null |
|||
} |
|||
Log.i(TAG, "音频处理插件已释放") |
|||
} |
|||
|
|||
// 实现ActivityAware接口,获取Activity上下文 |
|||
override fun onAttachedToActivity(binding: ActivityPluginBinding) { |
|||
Log.i(TAG, "onAttachedToActivity被调用") |
|||
// 如果context尚未初始化,使用Activity上下文 |
|||
if (!::context.isInitialized) { |
|||
context = binding.activity |
|||
Log.i(TAG, "使用Activity上下文初始化context") |
|||
|
|||
// 如果audioManager尚未初始化,初始化它 |
|||
if (!::audioManager.isInitialized) { |
|||
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager |
|||
Log.i(TAG, "使用Activity上下文初始化audioManager") |
|||
} |
|||
} |
|||
} |
|||
|
|||
override fun onDetachedFromActivity() { |
|||
Log.i(TAG, "onDetachedFromActivity被调用") |
|||
} |
|||
|
|||
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { |
|||
onAttachedToActivity(binding) |
|||
} |
|||
|
|||
override fun onDetachedFromActivityForConfigChanges() { |
|||
onDetachedFromActivity() |
|||
} |
|||
|
|||
override fun onMethodCall(call: MethodCall, result: Result) { |
|||
when (call.method) { |
|||
"isEchoCancellationAvailable" -> { |
|||
result.success(AcousticEchoCanceler.isAvailable()) |
|||
} |
|||
"isNoiseSuppressionAvailable" -> { |
|||
result.success(NoiseSuppressor.isAvailable()) |
|||
} |
|||
"enableEchoCancellation" -> { |
|||
val audioSessionId = call.argument<Int>("audioSessionId") |
|||
if (audioSessionId == null) { |
|||
result.error("INVALID_ARGUMENT", "音频会话ID不能为空", null) |
|||
return |
|||
} |
|||
result.success(enableEchoCancellation(audioSessionId)) |
|||
} |
|||
"disableEchoCancellation" -> { |
|||
result.success(disableEchoCancellation()) |
|||
} |
|||
"enableNoiseSuppression" -> { |
|||
val audioSessionId = call.argument<Int>("audioSessionId") |
|||
if (audioSessionId == null) { |
|||
result.error("INVALID_ARGUMENT", "音频会话ID不能为空", null) |
|||
return |
|||
} |
|||
result.success(enableNoiseSuppression(audioSessionId)) |
|||
} |
|||
"disableNoiseSuppression" -> { |
|||
result.success(disableNoiseSuppression()) |
|||
} |
|||
"getAudioSessionId" -> { |
|||
// 尝试获取当前活动的音频会话ID |
|||
try { |
|||
Log.i(TAG, "getAudioSessionId方法被调用") |
|||
|
|||
// 检查applicationContext是否已初始化 |
|||
if (applicationContext != null) { |
|||
Log.i(TAG, "静态applicationContext已初始化") |
|||
|
|||
// 如果context尚未初始化,使用applicationContext |
|||
if (!::context.isInitialized) { |
|||
context = applicationContext!! |
|||
Log.i(TAG, "使用静态applicationContext初始化context") |
|||
} |
|||
} else { |
|||
Log.w(TAG, "静态applicationContext尚未初始化") |
|||
} |
|||
|
|||
// 检查context是否初始化 |
|||
if (!::context.isInitialized) { |
|||
Log.e(TAG, "context尚未初始化,尝试从静态实例获取") |
|||
|
|||
// 尝试从静态实例获取context |
|||
val staticInstance = getInstance() |
|||
if (staticInstance != null) { |
|||
try { |
|||
// 尝试访问context,如果未初始化会抛出异常 |
|||
val testContext = staticInstance.context |
|||
context = testContext |
|||
Log.i(TAG, "从静态实例获取context成功") |
|||
} catch (e: UninitializedPropertyAccessException) { |
|||
Log.e(TAG, "静态实例的context也未初始化: ${e.message}") |
|||
|
|||
// 尝试使用应用级别的Context |
|||
val appContext = getApplicationContext() |
|||
if (appContext != null) { |
|||
context = appContext |
|||
Log.i(TAG, "使用应用级别的Context初始化成功") |
|||
} else { |
|||
Log.e(TAG, "无法获取任何有效的Context,无法获取音频会话ID") |
|||
result.success(-1) |
|||
return |
|||
} |
|||
} |
|||
} else { |
|||
// 尝试使用应用级别的Context |
|||
val appContext = getApplicationContext() |
|||
if (appContext != null) { |
|||
context = appContext |
|||
Log.i(TAG, "使用应用级别的Context初始化成功") |
|||
} else { |
|||
Log.e(TAG, "无法获取context,无法获取音频会话ID") |
|||
result.success(-1) |
|||
return |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 检查audioManager是否初始化,如果没有则初始化 |
|||
if (!::audioManager.isInitialized) { |
|||
Log.w(TAG, "audioManager尚未初始化,正在初始化") |
|||
try { |
|||
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager |
|||
Log.i(TAG, "audioManager初始化成功") |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "初始化audioManager失败: ${e.message}") |
|||
result.success(-1) |
|||
return |
|||
} |
|||
} |
|||
|
|||
// 使用ensureValidAudioSessionId方法获取有效的音频会话ID |
|||
val sessionId = ensureValidAudioSessionId() |
|||
Log.i(TAG, "返回音频会话ID: $sessionId") |
|||
result.success(sessionId) |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "获取音频会话ID失败: ${e.message}") |
|||
result.success(-1) |
|||
} |
|||
} |
|||
else -> { |
|||
result.notImplemented() |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 启用回音消除 |
|||
* |
|||
* @param audioSessionId 音频会话ID |
|||
* @return 是否成功启用 |
|||
*/ |
|||
private fun enableEchoCancellation(audioSessionId: Int): Boolean { |
|||
if (!AcousticEchoCanceler.isAvailable()) { |
|||
Log.w(TAG, "设备不支持回音消除") |
|||
return false |
|||
} |
|||
|
|||
try { |
|||
Log.i(TAG, "尝试启用回音消除,音频会话ID: $audioSessionId") |
|||
|
|||
// 确保context已初始化 |
|||
if (!::context.isInitialized) { |
|||
Log.w(TAG, "context尚未初始化,尝试获取") |
|||
|
|||
// 尝试从静态实例或应用级别获取context |
|||
val staticInstance = getInstance() |
|||
if (staticInstance != null && try { staticInstance.context; true } catch (e: Exception) { false }) { |
|||
context = staticInstance.context |
|||
} else { |
|||
val appContext = getApplicationContext() |
|||
if (appContext != null) { |
|||
context = appContext |
|||
} else { |
|||
Log.e(TAG, "无法获取context,无法启用回音消除") |
|||
return false |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 确保audioManager已初始化 |
|||
if (!::audioManager.isInitialized) { |
|||
try { |
|||
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager |
|||
Log.i(TAG, "audioManager初始化成功") |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "初始化audioManager失败: ${e.message}") |
|||
return false |
|||
} |
|||
} |
|||
|
|||
// 如果已经创建了回音消除器,先释放 |
|||
if (echoCanceler != null) { |
|||
echoCanceler?.release() |
|||
echoCanceler = null |
|||
} |
|||
|
|||
// 创建新的回音消除器 |
|||
echoCanceler = AcousticEchoCanceler.create(audioSessionId) |
|||
if (echoCanceler == null) { |
|||
Log.e(TAG, "创建回音消除器失败") |
|||
return false |
|||
} |
|||
|
|||
// 启用回音消除 |
|||
echoCanceler?.enabled = true |
|||
currentAudioSessionId = audioSessionId |
|||
|
|||
Log.i(TAG, "回音消除已启用,音频会话ID: $audioSessionId") |
|||
return echoCanceler?.enabled == true |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "启用回音消除失败: ${e.message}") |
|||
return false |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 禁用回音消除 |
|||
* |
|||
* @return 是否成功禁用 |
|||
*/ |
|||
private fun disableEchoCancellation(): Boolean { |
|||
if (echoCanceler == null) { |
|||
return true |
|||
} |
|||
|
|||
try { |
|||
echoCanceler?.enabled = false |
|||
echoCanceler?.release() |
|||
echoCanceler = null |
|||
Log.i(TAG, "回音消除已禁用") |
|||
return true |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "禁用回音消除失败: ${e.message}") |
|||
return false |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 启用噪声抑制 |
|||
* |
|||
* @param audioSessionId 音频会话ID |
|||
* @return 是否成功启用 |
|||
*/ |
|||
private fun enableNoiseSuppression(audioSessionId: Int): Boolean { |
|||
if (!NoiseSuppressor.isAvailable()) { |
|||
Log.w(TAG, "设备不支持噪声抑制") |
|||
return false |
|||
} |
|||
|
|||
try { |
|||
Log.i(TAG, "尝试启用噪声抑制,音频会话ID: $audioSessionId") |
|||
|
|||
// 确保context已初始化 |
|||
if (!::context.isInitialized) { |
|||
Log.w(TAG, "context尚未初始化,尝试获取") |
|||
|
|||
// 尝试从静态实例或应用级别获取context |
|||
val staticInstance = getInstance() |
|||
if (staticInstance != null && try { staticInstance.context; true } catch (e: Exception) { false }) { |
|||
context = staticInstance.context |
|||
} else { |
|||
val appContext = getApplicationContext() |
|||
if (appContext != null) { |
|||
context = appContext |
|||
} else { |
|||
Log.e(TAG, "无法获取context,无法启用噪声抑制") |
|||
return false |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 确保audioManager已初始化 |
|||
if (!::audioManager.isInitialized) { |
|||
try { |
|||
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager |
|||
Log.i(TAG, "audioManager初始化成功") |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "初始化audioManager失败: ${e.message}") |
|||
return false |
|||
} |
|||
} |
|||
|
|||
// 如果已经创建了噪声抑制器,先释放 |
|||
if (noiseSuppressor != null) { |
|||
noiseSuppressor?.release() |
|||
noiseSuppressor = null |
|||
} |
|||
|
|||
// 创建新的噪声抑制器 |
|||
noiseSuppressor = NoiseSuppressor.create(audioSessionId) |
|||
if (noiseSuppressor == null) { |
|||
Log.e(TAG, "创建噪声抑制器失败") |
|||
return false |
|||
} |
|||
|
|||
// 启用噪声抑制 |
|||
noiseSuppressor?.enabled = true |
|||
currentAudioSessionId = audioSessionId |
|||
|
|||
Log.i(TAG, "噪声抑制已启用,音频会话ID: $audioSessionId") |
|||
return noiseSuppressor?.enabled == true |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "启用噪声抑制失败: ${e.message}") |
|||
return false |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 禁用噪声抑制 |
|||
* |
|||
* @return 是否成功禁用 |
|||
*/ |
|||
private fun disableNoiseSuppression(): Boolean { |
|||
if (noiseSuppressor == null) { |
|||
return true |
|||
} |
|||
|
|||
try { |
|||
noiseSuppressor?.enabled = false |
|||
noiseSuppressor?.release() |
|||
noiseSuppressor = null |
|||
Log.i(TAG, "噪声抑制已禁用") |
|||
return true |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "禁用噪声抑制失败: ${e.message}") |
|||
return false |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 释放所有音频效果 |
|||
*/ |
|||
private fun releaseAudioEffects() { |
|||
try { |
|||
echoCanceler?.release() |
|||
echoCanceler = null |
|||
|
|||
noiseSuppressor?.release() |
|||
noiseSuppressor = null |
|||
|
|||
automaticGainControl?.release() |
|||
automaticGainControl = null |
|||
|
|||
currentAudioSessionId = -1 |
|||
Log.i(TAG, "所有音频效果已释放") |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "释放音频效果失败: ${e.message}") |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 检查音频会话ID是否有效,如果无效则创建一个新的 |
|||
* |
|||
* @return 有效的音频会话ID |
|||
*/ |
|||
private fun ensureValidAudioSessionId(): Int { |
|||
// 如果已经有有效的音频会话ID,直接返回 |
|||
if (currentAudioSessionId > 0) { |
|||
Log.i(TAG, "使用已存在的音频会话ID: $currentAudioSessionId") |
|||
return currentAudioSessionId |
|||
} |
|||
|
|||
Log.i(TAG, "尝试创建有效的音频会话ID") |
|||
|
|||
// 尝试从AudioManager获取音频会话ID |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && ::audioManager.isInitialized) { |
|||
try { |
|||
val audioAttributes = audioManager.getActivePlaybackConfigurations() |
|||
if (audioAttributes.isNotEmpty()) { |
|||
val sessionId = audioAttributes[0].audioAttributes.usage |
|||
if (sessionId > 0) { |
|||
currentAudioSessionId = sessionId |
|||
Log.i(TAG, "从AudioManager获取到音频会话ID: $sessionId") |
|||
return sessionId |
|||
} |
|||
} |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "从AudioManager获取音频会话ID失败: ${e.message}") |
|||
} |
|||
} |
|||
|
|||
// 尝试创建一个临时的AudioTrack来获取会话ID |
|||
try { |
|||
val audioTrack = android.media.AudioTrack( |
|||
android.media.AudioAttributes.Builder() |
|||
.setUsage(android.media.AudioAttributes.USAGE_MEDIA) |
|||
.setContentType(android.media.AudioAttributes.CONTENT_TYPE_SPEECH) |
|||
.build(), |
|||
android.media.AudioFormat.Builder() |
|||
.setSampleRate(16000) |
|||
.setChannelMask(android.media.AudioFormat.CHANNEL_OUT_MONO) |
|||
.setEncoding(android.media.AudioFormat.ENCODING_PCM_16BIT) |
|||
.build(), |
|||
1024, // 最小缓冲区大小 |
|||
android.media.AudioTrack.MODE_STREAM, |
|||
android.media.AudioManager.AUDIO_SESSION_ID_GENERATE |
|||
) |
|||
|
|||
val generatedSessionId = audioTrack.audioSessionId |
|||
if (generatedSessionId > 0) { |
|||
currentAudioSessionId = generatedSessionId |
|||
Log.i(TAG, "成功创建临时AudioTrack并获取音频会话ID: $generatedSessionId") |
|||
|
|||
// 释放AudioTrack资源 |
|||
audioTrack.release() |
|||
|
|||
return generatedSessionId |
|||
} else { |
|||
Log.w(TAG, "创建的AudioTrack未返回有效的音频会话ID") |
|||
audioTrack.release() |
|||
} |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "创建临时AudioTrack失败: ${e.message}") |
|||
} |
|||
|
|||
// 尝试创建一个临时的AudioRecord来获取会话ID |
|||
try { |
|||
val minBufferSize = android.media.AudioRecord.getMinBufferSize( |
|||
16000, |
|||
android.media.AudioFormat.CHANNEL_IN_MONO, |
|||
android.media.AudioFormat.ENCODING_PCM_16BIT |
|||
) |
|||
|
|||
if (minBufferSize > 0) { |
|||
val audioRecord = android.media.AudioRecord( |
|||
android.media.MediaRecorder.AudioSource.MIC, |
|||
16000, |
|||
android.media.AudioFormat.CHANNEL_IN_MONO, |
|||
android.media.AudioFormat.ENCODING_PCM_16BIT, |
|||
minBufferSize |
|||
) |
|||
|
|||
val recordSessionId = audioRecord.audioSessionId |
|||
if (recordSessionId > 0) { |
|||
currentAudioSessionId = recordSessionId |
|||
Log.i(TAG, "成功创建临时AudioRecord并获取音频会话ID: $recordSessionId") |
|||
|
|||
// 释放AudioRecord资源 |
|||
audioRecord.release() |
|||
|
|||
return recordSessionId |
|||
} else { |
|||
Log.w(TAG, "创建的AudioRecord未返回有效的音频会话ID") |
|||
audioRecord.release() |
|||
} |
|||
} else { |
|||
Log.w(TAG, "无法获取有效的AudioRecord最小缓冲区大小") |
|||
} |
|||
} catch (e: Exception) { |
|||
Log.e(TAG, "创建临时AudioRecord失败: ${e.message}") |
|||
} |
|||
|
|||
// 如果以上方法都失败,使用一个固定的默认值 |
|||
val defaultSessionId = 1 |
|||
Log.w(TAG, "使用默认音频会话ID: $defaultSessionId") |
|||
currentAudioSessionId = defaultSessionId |
|||
return defaultSessionId |
|||
} |
|||
} |
|||
Loading…
Reference in new issue