将音频从手机麦克风同时传输到蓝牙耳机的扬声器,并从蓝牙耳机的麦克风传输到 Android 上的手机扬声器?

时间:2021-07-20 14:33:29

标签: android audio bluetooth stream

我尝试使用 2 个服务

  1. phoneMicToBle
  2. BleMicToPhone

当在主活动的 Oncreate 方法上同时调用这两个服务时,我写的第一个服务只会被执行。 例子: onCreate{ startService(this,phoneMicToBle::class.java) startService(this,BleMicToPhone::class.java) } 那么 phoneMicToBle 只会被执行。如果我编写了 BleMicToPhone 的服务,那么它将被执行。任何人都可以帮助我如何使其同时工作。

1.BleMicToPhone 服务代码

class BleMicToPhone : Service() {
/**
 * Signals whether a recording is in progress (true) or not (false).
 */
private val recordingInProgress = AtomicBoolean(false)
private var recorder: AudioRecord? = null
private var recordingThread: Thread? = null
private var audioManager: AudioManager? = null
private var streamOutput = 0
override fun onCreate() {
    Log.d("AudioRelayService", "Sampling rate: " + SAMPLING_RATE_IN_HZ + " Hz")
    instance = this
    audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
}

override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {

    // STREAM_ALARM also works, but STREAM_VOICE_CALL reduces the echo
    streamOutput = intent.getIntExtra(STREAM_KEY, AudioManager.STREAM_VOICE_CALL)
    displayNotification()
    startRecording()

    Log.d(TAG, "onStartCommand: AudiRelay service")

    return START_STICKY
}

override fun onBind(intent: Intent): IBinder? {
    return null
}

fun startRecording() {
    // Depending on the device one might has to change the AudioSource, e.g. to DEFAULT
    // or VOICE_COMMUNICATION
    recorder = AudioRecord(
        MediaRecorder.AudioSource.DEFAULT,
        SAMPLING_RATE_IN_HZ, CHANNEL_CONFIG, AUDIO_FORMAT, BUFFER_SIZE
    )
    setPreferredInputDevice(recorder!!)
    improveRecorder(recorder!!)
    recorder!!.startRecording()
    recordingInProgress.set(true)
    recordingThread = Thread(RecordingRunnable(), "Recording Thread Audio Relay")
    recordingThread!!.start()
}

private fun improveRecorder(recorder: AudioRecord) {
    val audioSessionId = recorder.audioSessionId

    // Turn on Android library filter for reducing background noise in recordings
    if (NoiseSuppressor.isAvailable()) {
        NoiseSuppressor.create(audioSessionId)
    }

    // Android library filter for automatic volume control in recordings
    if (AutomaticGainControl.isAvailable()) {
        AutomaticGainControl.create(audioSessionId)
    }

    // Android library filter for reducing echo in recordings
    if (AcousticEchoCanceler.isAvailable()) {
        AcousticEchoCanceler.create(audioSessionId)
    }
}

fun stopRecording() {
    if (null == recorder) {
        return
    }
    recordingInProgress.set(false)
    recorder!!.stop()
    recorder!!.release()
    recorder = null
    recordingThread = null
}

override fun onDestroy() {
    stopRecording()
}

fun shutDown() {
    stopRecording()
    instance = null
    stopSelf()
}

private fun displayNotification() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val chan = NotificationChannel(
            NOTIFICATION_CHANNEL_ID,
            "AudioRelayService", NotificationManager.IMPORTANCE_NONE
        )
        chan.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
        val manager = (getSystemService(NOTIFICATION_SERVICE) as NotificationManager)
        manager.createNotificationChannel(chan)
        val notificationBuilder = Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
        val notification = notificationBuilder.setOngoing(true)
            .setContentTitle(NOTIFICATION_MESSAGE)
            .setCategory(Notification.CATEGORY_SERVICE)
            .build()
        startForeground(2, notification)
    } else {
        val notification = Notification.Builder(this)
            .setContentTitle(NOTIFICATION_MESSAGE)
        startForeground(1, notification.build())
    }
}

private inner class RecordingRunnable : Runnable {
    override fun run() {
        val buffer = ByteBuffer.allocateDirect(BUFFER_SIZE)
        val audio = AudioTrack(
            streamOutput,
            SAMPLING_RATE_IN_HZ,
            AudioFormat.CHANNEL_OUT_MONO,
            AUDIO_FORMAT,
            BUFFER_SIZE,
            AudioTrack.MODE_STREAM
        )
        setPreferredOutputDevice(audio)
        audio.play()
        while (recordingInProgress.get()) {
            val result = recorder!!.read(buffer, BUFFER_SIZE)
            if (result < 0) {
                Log.w(TAG, "Reading of buffer failed.")
            } else {
                audio.write(buffer.array(), 0, BUFFER_SIZE)
                buffer.clear()
            }
        }
    }
}

fun recordingInProgress(): Boolean {
    return recordingInProgress.get()
}

/**
 * Function to set the preferred input device to Bluetooth SCO.
 * Function has no effect on Android version below Android 6.0 Marshmallow
 * @param recorder: AudioRecord instance
 */
private fun setPreferredInputDevice(recorder: AudioRecord) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        val inputs = audioManager!!.getDevices(AudioManager.GET_DEVICES_INPUTS)
        for (input in inputs) {
            if (input.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
                recorder.preferredDevice = input

            }
        }
    }
}

/**
 * Function to set the preferred input device to a connected AUX line (preferably) or
 * the built-in speakers if the AUX line is not connected.
 * Function has no effect on Android version below Android 6.0 Marshmallow
 * @param audio: AudioTrack instance
 */

private fun setPreferredOutputDevice(audio: AudioTrack) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        val outputs = audioManager!!.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
        for (output in outputs) {
             if (output.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER ||
                output.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER_SAFE) {
                audio.preferredDevice = output
            }
        }
    }



}

companion object {
    //        private const val NOTIFICATION_CHANNEL_ID = "1"
    var instance: AudioRelayService? = null
    private val TAG = AudioRelayService::class.java.canonicalName
    const val STREAM_KEY = "STREAM"


    val NOTIFICATION_CHANNEL_ID = (BuildConfig::class.java.getPackage().toString()
            + "." + TAG)
    private const val NOTIFICATION_MESSAGE = "Mic Repeater is running."
    private val SAMPLING_RATE_IN_HZ = minSupportedSampleRate
    private const val CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO
    private const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT

    /**
     * Size of the buffer where the audio data is stored by Android
     */
    private val BUFFER_SIZE = AudioRecord.getMinBufferSize(
        SAMPLING_RATE_IN_HZ,
        CHANNEL_CONFIG, AUDIO_FORMAT
    )// return the minimum supported audio sample rate

    // If none of the sample rates are supported return -1 and handle it in calling method
    /*
     * Selecting default audio input source for recording since
     * AudioFormat.CHANNEL_CONFIGURATION_DEFAULT is deprecated and selecting
     * default encoding format.
     */
    private val minSupportedSampleRate: Int
        private get() {
            val validSampleRates = intArrayOf(
                8000, 11025, 16000, 22050,
                32000, 37800, 44056, 44100, 47250, 48000, 50000, 50400, 88200,
                96000, 176400, 192000, 352800, 2822400, 5644800
            )
            /*
    * Selecting default audio input source for recording since
    * AudioFormat.CHANNEL_CONFIGURATION_DEFAULT is deprecated and selecting
    * default encoding format.
    */
            for (validSampleRate in validSampleRates) {
                val result = AudioRecord.getMinBufferSize(
                    validSampleRate,
                    CHANNEL_CONFIG,
                    AUDIO_FORMAT
                )
                if (result > 0) {
                    // return the minimum supported audio sample rate
                    return validSampleRate
                }
            }
            // If none of the sample rates are supported return -1 and handle it in calling method
            return -1
        }
}

}

  1. PhoneMicToBle 服务代码:
<块引用>

class PhoneMicToBleService:Service() {

private val recordingInProgress = AtomicBoolean(false)
private var recorder: AudioRecord? = null
private var recordingThread: Thread? = null
private var audioManager: AudioManager? = null
private var streamOutput = 0
override fun onCreate() {
    Log.d("AudioRelayService", "Sampling rate: " + SAMPLING_RATE_IN_HZ + " Hz")
    instance = this
    audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
}

override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {

    // STREAM_ALARM also works, but STREAM_VOICE_CALL reduces the echo
    streamOutput = intent.getIntExtra(STREAM_KEY, AudioManager.STREAM_VOICE_CALL)
    displayNotification()
    startRecording()

    Log.d(TAG, "onStartCommand: Phonemic to Ble")

    return START_STICKY
}

override fun onBind(intent: Intent): IBinder? {
    return null
}

fun startRecording() {
    // Depending on the device one might has to change the AudioSource, e.g. to DEFAULT
    // or VOICE_COMMUNICATION
    recorder = AudioRecord(
        MediaRecorder.AudioSource.DEFAULT,
        SAMPLING_RATE_IN_HZ, CHANNEL_CONFIG, AUDIO_FORMAT, BUFFER_SIZE
    )
    setPreferredInputDevice(recorder!!)
    improveRecorder(recorder!!)
    recorder!!.startRecording()
    recordingInProgress.set(true)
    recordingThread = Thread(RecordingRunnable(), "Recording Thread")
    recordingThread!!.start()
}






private fun improveRecorder(recorder: AudioRecord) {
    val audioSessionId = recorder.audioSessionId

    // Turn on Android library filter for reducing background noise in recordings
    if (NoiseSuppressor.isAvailable()) {
        NoiseSuppressor.create(audioSessionId)
    }

    // Android library filter for automatic volume control in recordings
    if (AutomaticGainControl.isAvailable()) {
        AutomaticGainControl.create(audioSessionId)
    }

    // Android library filter for reducing echo in recordings
    if (AcousticEchoCanceler.isAvailable()) {
        AcousticEchoCanceler.create(audioSessionId)
    }
}

fun stopRecording() {
    if (null == recorder) {
        return
    }
    recordingInProgress.set(false)
    recorder!!.stop()
    recorder!!.release()
    recorder = null
    recordingThread = null
}

override fun onDestroy() {
    stopRecording()
}

fun shutDown() {
    stopRecording()
    instance = null
    stopSelf()
}

private fun displayNotification() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val chan = NotificationChannel(
            NOTIFICATION_CHANNEL_ID,
            "PhoneMicToSpeaker", NotificationManager.IMPORTANCE_NONE
        )
        chan.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
        val manager = (getSystemService(NOTIFICATION_SERVICE) as NotificationManager)
        manager.createNotificationChannel(chan)
        val notificationBuilder = Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
        val notification = notificationBuilder.setOngoing(true)
            .setContentTitle(NOTIFICATION_MESSAGE)
            .setCategory(Notification.CATEGORY_SERVICE)
            .build()
        startForeground(3, notification)
    } else {
        val notification = Notification.Builder(this)
            .setContentTitle(NOTIFICATION_MESSAGE)
        startForeground(4, notification.build())
    }
}

private inner class RecordingRunnable : Runnable {
    override fun run() {
        val buffer = ByteBuffer.allocateDirect(BUFFER_SIZE)
        val audio = AudioTrack(
            streamOutput,
            SAMPLING_RATE_IN_HZ,
            AudioFormat.CHANNEL_OUT_MONO,
            AUDIO_FORMAT,
            BUFFER_SIZE,
            AudioTrack.MODE_STREAM
        )
        setPreferredOutputDevice(audio)
        audio.play()


        while (recordingInProgress.get()) {
            val result = recorder!!.read(buffer, BUFFER_SIZE)
            if (result < 0) {
                Log.w(TAG, "Reading of buffer failed.")
            } else {
                audio.write(buffer.array(), 0, BUFFER_SIZE)
                buffer.clear()
            }
        }
    }
}


fun recordingInProgress(): Boolean {
    return recordingInProgress.get()
}

/**
 * Function to set the preferred input device to Bluetooth SCO.
 * Function has no effect on Android version below Android 6.0 Marshmallow
 * @param recorder: AudioRecord instance
 */
private fun setPreferredInputDevice(recorder: AudioRecord) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        val inputs = audioManager!!.getDevices(AudioManager.GET_DEVICES_INPUTS)
        for (input in inputs) {
            if (input.type == AudioDeviceInfo.TYPE_BUILTIN_MIC ) {
                recorder.preferredDevice = input
            }
        }
    }
}

/**
 * Function to set the preferred input device to a connected AUX line (preferably) or
 * the built-in speakers if the AUX line is not connected.
 * Function has no effect on Android version below Android 6.0 Marshmallow
 * @param audio: AudioTrack instance
 */
private fun setPreferredOutputDevice(audio: AudioTrack) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        val outputs = audioManager!!.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
        for (output in outputs) {
            if (output.type == AudioDeviceInfo.TYPE_AUX_LINE || output.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES || output.type == AudioDeviceInfo.TYPE_WIRED_HEADSET) {
                audio.preferredDevice = output
                break
            } else if (output.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
                audio.preferredDevice = output
            }
        }
    }

}

companion object {
    //        private const val NOTIFICATION_CHANNEL_ID = "1"
    var instance: PhoneMicToBleService? = null
    private val TAG = PhoneMicToBleService::class.java.canonicalName
    const val STREAM_KEY = "STREAM PHONE MIC TO BLE SERVICE"


    val NOTIFICATION_CHANNEL_ID = (BuildConfig::class.java.getPackage().toString()
            + "." + TAG)
    private const val NOTIFICATION_MESSAGE = "Mic Repeater is running."
    private val SAMPLING_RATE_IN_HZ = minSupportedSampleRate
    private const val CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO
    private const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT

    /**
     * Size of the buffer where the audio data is stored by Android
     */
    private val BUFFER_SIZE = AudioRecord.getMinBufferSize(
        SAMPLING_RATE_IN_HZ,
        CHANNEL_CONFIG, AUDIO_FORMAT
    )// return the minimum supported audio sample rate

    // If none of the sample rates are supported return -1 and handle it in calling method
    /*
     * Selecting default audio input source for recording since
     * AudioFormat.CHANNEL_CONFIGURATION_DEFAULT is deprecated and selecting
     * default encoding format.
     */
    private val minSupportedSampleRate: Int
        private get() {
            val validSampleRates = intArrayOf(
                8000, 11025, 16000, 22050,
                32000, 37800, 44056, 44100, 47250, 48000, 50000, 50400, 88200,
                96000, 176400, 192000, 352800, 2822400, 5644800
            )
            /*
    * Selecting default audio input source for recording since
    * AudioFormat.CHANNEL_CONFIGURATION_DEFAULT is deprecated and selecting
    * default encoding format.
    */
            for (validSampleRate in validSampleRates) {
                val result = AudioRecord.getMinBufferSize(
                    validSampleRate,
                    CHANNEL_CONFIG,
                    AUDIO_FORMAT
                )
                if (result > 0) {
                    // return the minimum supported audio sample rate
                    return validSampleRate
                }
            }
            // If none of the sample rates are supported return -1 and handle it in calling method
            return -1
        }
}

}

0 个答案:

没有答案