webrtc怎么播放本地音频文件

本文主要介绍怎么在webrtc中把一个端的音频文件像麦克风录制的音频一样在另外一端中播放,以Android开发为例,pc和ios也可参照这个方法。

Android webrtc中与音频相关的类

JavaAudioDeviceModule:音频设备管理器。在创建该类对象时会生成一个WebRtcAudioRecord对象作为音频输入,一个WebRtcAudioTrack对象作为音频输出。里面还包含一些关于音频的基本设置。

WebRtcAudioManager:提供查询采样率和缓冲区大小的方法。

WebRtcAudioRecord:录制(采集)声音。里面封装了一个AudioRecord对象。是本次讲解的重点。

WebRtcAudioTrack:播放声音。里面封装了一个AudioTrack对象。

WebRtcAudioEffects:包含音频的回声消除和降噪处理。

WebRtcAudioUtils:工具类。打印日志及获取相关音频及设备信息。

各个类之间的关系如下:

修改源码传输本地音频文件流

WebRtcAudioRecord中有一个专门的线程(AudioRecordThread)来采集设备声音,其中关键代码如下:

    public void run() {
      //设置线程优先级
      Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
      Logging.d(TAG, "AudioRecordThread" + WebRtcAudioUtils.getThreadInfo());
      //如果没有开始录制则报错,在audioRecord调用startRecording方法后状态会变为RECORDSTATE_RECORDING
      assertTrue(audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING);

      // 通知上层RecordState发生了变化
      doAudioRecordStateCallback(AUDIO_RECORD_START);

      long lastTime = System.nanoTime();
      AudioTimestamp audioTimestamp = null;
      if (Build.VERSION.SDK_INT >= 24) {
        audioTimestamp = new AudioTimestamp();
      }
      while (keepAlive) {
        //读取数据放到byteBuffer中
        int bytesRead = audioRecord.read(byteBuffer, byteBuffer.capacity());
        if (bytesRead == byteBuffer.capacity()) {
          //静音的时候清空byteBuffer,可以看出在静音的时候音频还是在录制,只不过没有把数据传出去
          if (microphoneMute) {
            byteBuffer.clear();
            byteBuffer.put(emptyBytes);
          }
          //有可能在此过程中调用了stopRecording方法,为了更安全一点这里又判断了keepAlive的值
          if (keepAlive) {
            long captureTimeNs = 0;
            if (Build.VERSION.SDK_INT >= 24) {
              if (audioRecord.getTimestamp(audioTimestamp, AudioTimestamp.TIMEBASE_MONOTONIC)
                  == AudioRecord.SUCCESS) {
                captureTimeNs = audioTimestamp.nanoTime;
              }
            }
            //用此方法把数据传到底层,这是关键
            nativeDataIsRecorded(nativeAudioRecord, bytesRead, captureTimeNs);
          }
          if (audioSamplesReadyCallback != null) {
            // Copy the entire byte buffer array. The start of the byteBuffer is not necessarily
            // at index 0.
            byte[] data = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.arrayOffset(),
                byteBuffer.capacity() + byteBuffer.arrayOffset());
            //给上层的回调,不太重要
            audioSamplesReadyCallback.onWebRtcAudioRecordSamplesReady(
                new JavaAudioDeviceModule.AudioSamples(audioRecord.getAudioFormat(),
                    audioRecord.getChannelCount(), audioRecord.getSampleRate(), data));
          }
        } else {
          String errorMessage = "AudioRecord.read failed: " + bytesRead;
          Logging.e(TAG, errorMessage);
          if (bytesRead == AudioRecord.ERROR_INVALID_OPERATION) {
            keepAlive = false;
            reportWebRtcAudioRecordError(errorMessage);
          }
        }
      }

      try {
        //释放资源
        if (audioRecord != null) {
          audioRecord.stop();
          doAudioRecordStateCallback(AUDIO_RECORD_STOP);
        }
      } catch (IllegalStateException e) {
        Logging.e(TAG, "AudioRecord.stop failed: " + e.getMessage());
      }
    }

为了能够更好的理解,这里有两个关键点需要说一下:

  1. byteBuffer的大小是在initRecording的时候计算出来的,其大小为:声道数*位深*(采样率/每秒发送的buffer个数),不可以在java层单独更改。

  1. 在initRecording时调用nativeCacheDirectBufferAddress把byteBuffer的地址传到了底层保存,所以后面在调用nativeDataIsRecorded传送数据的时候并没有把整个buffer传过去,大大提高了效率。

为了读取本地文件内容,我们需要先建立一个File和FileInputStream对象,还需要新建一个byte数组,其大小为byteBuffer的大小。然后把关键代码做如下修改:

while(keepAlive) {
                try {
                    long lastTime = System.currentTimeMillis();
                    //不断地读取文件内容到bytes数组
                    while (fileInputStream.read(bytes) != -1) {
                        //把数组里的内容放到byteBuffer
                        byteBuffer.put(Arrays.copyOfRange(bytes, 0, byteBuffer.capacity()));
                        //如果静音,则在byteBuffer里放入空内容
                        if (microphoneMute) {
                            byteBuffer.clear();
                            byteBuffer.put(emptyBytes);
                        }
                        // 为了安全性再次判断keepAlive值
                        if (keepAlive) {
                            //每十毫秒发送一次数据,十毫秒为类成员变量CALLBACK_BUFFER_SIZE_MS的值
                            while(System.currentTimeMillis() - lastTime < 10) {
                            }
                            lastTime = System.currentTimeMillis();
                            nativeDataIsRecorded(nativeAudioRecord, byteBuffer.capacity());
                        }
                        if (audioSamplesReadyCallback != null) {
                            byte[] data = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.arrayOffset(),
                                    byteBuffer.capacity() + byteBuffer.arrayOffset());
                            audioSamplesReadyCallback.onWebRtcAudioRecordSamplesReady(
                                    new JavaAudioDeviceModule.AudioSamples(audioRecord.getAudioFormat(),
                                            audioRecord.getChannelCount(), audioRecord.getSampleRate(), data));
                        }
                        if (!microphoneMute) {
                            //避免发生BufferOverflowException错误
                            byteBuffer.position(0);
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

需要注意的是音频文件的编码方式必须是pcm_s16le的,声道数必须是单声道的。当然编码方式和声道数也是可以自行设置的以匹配音频文件的,这里就不展开讲解了。

以上就是全部内容了,谢谢观看。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值