Android音频开发——对讲机实时语音对话

前言

由于公司需求, 安排我研究对讲机的实时语音对话. 对讲机点击按钮发起对话, Android 端接听, 然后进行语音对话.研究了几天第三方对讲机Demo,发现这个demo只是简单播放音频, 而且还没有提供Android客户端相关代码,Java版也要自己看底层实现,没办法只有自己动手造, 我只想说 *** !!!,

准备工作

一开始本来打算用Web端来做客户端, 但是由于技术有限, 中途换成Android(Kotlin) 端, 后台是 SpringBoot. 前后端交互是通过WebSocket进行实时数据交互. 确定技术方案后, 然后了解相关音频格式

PCM

PCM(Pulse Code Modulation)也被称为脉码编码调制。PCM文件是模拟音频信号经模数转换(A/D变换)直接形成的二进制序列,该文件没有附加的文件头文件结束标志。PCM中的声音数据没有被压缩,如果是单声道的文件,采样数据按时间的先后顺序依次存入。但是只有这些数字化的音频二进制序列并不能够播放,因为任何的播放器都不知道应该以什么样的声道数、采样频率和采样位数播放,这个二进制序列没有任何自描述性。

WAV

WAVE(Waveform Audio File Format),又或者是因为扩展名而被大众所知的WAV,也是一种无损音频编码。WAV文件可以当成是PCM文件的wrapper,实际上查看pcm和对应wav文件的hex文件,可以发现,wav文件只是在pcm文件的开头多了44bytes,来表征其声道数、采样频率和采样位数等信息。由于其具有自描述性,WAV文件可以被基本所有的音频播放器播放.自然而言的认为,若我们需要在web端播放纯纯的PCM码流,是否只需要在其头部加上44bytes转成对应的WAV文件,就可以播放了。
 

  • GSM
    GSM 06.10有损声音压缩。用于压缩语音的有损格式,用于全球移动电信标准(GSM)。它的目的是有益于缩小音频数据大小,但是当给定的音频信号被多次编码和解码时,它会引入大量的噪声。这种格式被一些语音邮件应用程序使用。这是CPU密集型

基本流程

对讲机发起呼叫(或者Android直接发起对话)

Android端接听, 发去网络请求.
后端接受请求, 调用对讲机接听方法.
后台相当于一个中转站, 转发Android的音频数据、对讲机回调音频数据.
Android、后端 通过WebScoket 进行实时数据传输(byte)

注意

  • 对讲机回调回来的是 GSM音频数据
  • Android端录音是数据是 PCM音频数据

多说无益上码

Android 端

在app gradle下添加相关依赖

// WebSocket
api 'org.java-websocket:Java-WebSocket:1.3.6'

api 'com.github.tbruyelle:rxpermissions:0.10.2'

// retrofit
String retrofit_version = '2.4.0'
api "com.squareup.retrofit2:retrofit:$retrofit_version"
api "com.squareup.retrofit2:converter-gson:${retrofit_version}"
api "com.squareup.retrofit2:adapter-rxjava2:${retrofit_version}"

// okhttp
String okhttp_version = '3.4.1'
api "com.squareup.okhttp3:okhttp:${okhttp_version}"
api "com.squareup.okhttp3:logging-interceptor:${okhttp_version}"

// RxKotlin and RxAndroid 2.x
api 'io.reactivex.rxjava2:rxkotlin:2.3.0'
api 'io.reactivex.rxjava2:rxandroid:2.1.0'

新建JWebSocketClient 继承WebSocketClient

class JWebSocketClient(serverUri: URI,private val callback: ((data: ByteBuffer?) -> Unit)) : WebSocketClient(serverUri) {

    override fun onOpen(handshakedata: ServerHandshake?) {
        Log.d("LLLLLLLLLLLL", "onOpen")
    }

    override fun onClose(code: Int, reason: String?, remote: Boolean) {
        Log.d("LLLLLLLLLLLL", "code = $code, onClose = $reason")
    }

    override fun onMessage(message: String?) {
        //Log.d("LLLLLLLLLLLL", "onMessage = $message")
    }


    override fun onMessage(bytes: ByteBuffer?) {
        super.onMessage(bytes)

        //Log.d("LLLLLLLLLLLL", "onMessage2 = $bytes")

        callback.invoke(bytes)
    }

    override fun onError(ex: Exception?) {
        Log.d("LLLLLLLLLLLL", "onError = $ex")
    }
}

onMessage方法, 接受到后台传过来的数据, 调用callback回调到Activity中处理。 MainActivitiy 相关代码

class MainActivity : AppCompatActivity() {

    private lateinit var client: WebSocketClient

    private var isGranted = false
    private var isRecording = true

    private var disposable: Disposable? = null

    private val service by lazy {
        RetrofitFactory.newInstance.create(ApiService::class.java)
    }

    private val sampleRate = 8000
    private val channelIn = AudioFormat.CHANNEL_IN_MONO
    private val channelOut = AudioFormat.CHANNEL_OUT_MONO
    private val audioFormat = AudioFormat.ENCODING_PCM_16BIT

    private val trackBufferSize by lazy { AudioTrack.getMinBufferSize(sampleRate, channelOut, audioFormat) }

    private val recordBufferSize by lazy { AudioTrack.getMinBufferSize(sampleRate, channelOut, audioFormat) }

    private val audioTrack by lazy {
        AudioTrack(AudioManager.STREAM_MUSIC,
                sampleRate,
                channelOut,
                aud
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值