Android WebRTC 对 AudioRecord 的使用技术分享

目录
  • 一、创建和初始化
  • 二、启动
  • 三、读数据
  • 四、停止和销毁

前言:

AudioRecord 是 Android 基于原始PCM音频数据录制的类,WebRCT 对其封装的代码位置位于org/webrtc/audio/WebRtcAudioRecord.java,接下来我们学习一下 AudioRecord 是如何创建启动,读取音频采集数据以及销毁等功能的。

一、创建和初始化

 private int initRecording(int sampleRate, int channels) {
    Logging.d(TAG, "initRecording(sampleRate=" + sampleRate + ", channels=" + channels + ")");
    if (audioRecord != null) {
      reportWebRtcAudioRecordInitError("InitRecording called twice without StopRecording.");
      return -1;
    }
    final int bytesPerFrame = channels * (BITS_PER_SAMPLE / 8);
    final int framesPerBuffer = sampleRate / BUFFERS_PER_SECOND;
    byteBuffer = ByteBuffer.allocateDirect(bytesPerFrame * framesPerBuffer);
    Logging.d(TAG, "byteBuffer.capacity: " + byteBuffer.capacity());
    emptyBytes = new byte[byteBuffer.capacity()];
    // Rather than passing the ByteBuffer with every callback (requiring
    // the potentially expensive GetDirectBufferAddress) we simply have the
    // the native class cache the address to the memory once.
    nativeCacheDirectBufferAddress(byteBuffer, nativeAudioRecord);

    // Get the minimum buffer size required for the successful creation of
    // an AudioRecord object, in byte units.
    // Note that this size doesn't guarantee a smooth recording under load.
    final int channelConfig = channelCountToConfiguration(channels);
    int minBufferSize =
        AudioRecord.getMinBufferSize(sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT);
    if (minBufferSize == AudioRecord.ERROR || minBufferSize == AudioRecord.ERROR_BAD_VALUE) {
      reportWebRtcAudioRecordInitError("AudioRecord.getMinBufferSize failed: " + minBufferSize);
      return -1;
    }
    Logging.d(TAG, "AudioRecord.getMinBufferSize: " + minBufferSize);

    // Use a larger buffer size than the minimum required when creating the
    // AudioRecord instance to ensure smooth recording under load. It has been
    // verified that it does not increase the actual recording latency.
    int bufferSizeInBytes = Math.max(BUFFER_SIZE_FACTOR * minBufferSize, byteBuffer.capacity());
    Logging.d(TAG, "bufferSizeInBytes: " + bufferSizeInBytes);
    try {
      audioRecord = new AudioRecord(audioSource, sampleRate, channelConfig,
          AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes);
    } catch (IllegalArgumentException e) {
      reportWebRtcAudioRecordInitError("AudioRecord ctor error: " + e.getMessage());
      releaseAudioResources();
      return -1;
    }
    if (audioRecord == null || audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
      reportWebRtcAudioRecordInitError("Failed to create a new AudioRecord instance");
      releaseAudioResources();
      return -1;
    }
    if (effects != null) {
      effects.enable(audioRecord.getAudioSessionId());
    }
    logMainParameters();
    logMainParametersExtended();
    return framesPerBuffer;
  }

在初始化的方法中,主要做了两件事。

创建缓冲区:

  • 由于实际使用数据的代码在native层,因此这里创建了一个Java的direct buffer,而且AudioRecord也有通过ByteBuffer读数据的接口,并且实际把数据复制到ByteBuffer的代码也在native层,所以这里使用direct buffer效率会更高。
  • ByteBuffer的容量为单次读取数据的大小。Android的数据格式是打包格式(packed),在多个声道时,同一个样点的不同声道连续存储在一起,接着存储下一个样点的不同声道;一帧就是一个样点的所有声道数据的合集,一次读取的帧数是10ms的样点数(采样率除以100,样点个数等于采样率时对应于1s的数据,所以除以100就是10ms的数据);ByteBuffer的容量为帧数乘声道数乘每个样点的字节数(PCM 16 bit表示每个样点为两个字节)。
  • 这里调用的nativeCacheDirectBufferAddress JNI函数会在native层把ByteBuffer的访问地址提前保存下来,避免每次读到音频数据后,还需要调用接口获取访问地址。

创建 AudioRecord对象,构造函数有很多参数,分析如下:

  • audioSource:指的是音频采集模式,默认是 VOICE_COMMUNICATION,该模式会使用硬件AEC(回声抑制)
  • sampleRate:采样率
  • channelConfig:声道数
  • audioFormat:音频数据格式,这里实际使用的是 AudioFormat.ENCODING_PCM_16BIT,即PCM 16 bit的数据格式。
  • bufferSize:系统创建AudioRecord时使用的缓冲区大小,这里使用了两个数值的较大者:通过AudioRecord.getMinBufferSize接口获取的最小缓冲区大小的两倍,读取数据的ByteBuffer的容量。通过注释我们可以了解到,考虑最小缓冲区的两倍是为了确保系统负载较高的情况下音频采集仍能平稳运行,而且这里设置更大的缓冲区并不会增加音频采集的延迟。

二、启动

private boolean startRecording() {
    Logging.d(TAG, "startRecording");
    assertTrue(audioRecord != null);
    assertTrue(audioThread == null);
    try {
      audioRecord.startRecording();
    } catch (IllegalStateException e) {
      reportWebRtcAudioRecordStartError(AudioRecordStartErrorCode.AUDIO_RECORD_START_EXCEPTION,
          "AudioRecord.startRecording failed: " + e.getMessage());
      return false;
    }
    if (audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
      reportWebRtcAudioRecordStartError(
          AudioRecordStartErrorCode.AUDIO_RECORD_START_STATE_MISMATCH,
          "AudioRecord.startRecording failed - incorrect state :"
          + audioRecord.getRecordingState());
      return false;
    }
    audioThread = new AudioRecordThread("AudioRecordJavaThread");
    audioThread.start();
    return true;
  }

​ 在该方法中,首先启动了 audioRecord,接着判断了读取线程事都正在录制中。

三、读数据

 private class AudioRecordThread extends Thread {
    private volatile boolean keepAlive = true;

    public AudioRecordThread(String name) {
      super(name);
    }

    // TODO(titovartem) make correct fix during webrtc:9175
    @SuppressWarnings("ByteBufferBackingArray")
    @Override
    public void run() {
      Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
      Logging.d(TAG, "AudioRecordThread" + WebRtcAudioUtils.getThreadInfo());
      assertTrue(audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING);

      long lastTime = System.nanoTime();
      while (keepAlive) {
        int bytesRead = audioRecord.read(byteBuffer, byteBuffer.capacity());
        if (bytesRead == byteBuffer.capacity()) {
          if (microphoneMute) {
            byteBuffer.clear();
            byteBuffer.put(emptyBytes);
          }
          // It's possible we've been shut down during the read, and stopRecording() tried and
          // failed to join this thread. To be a bit safer, try to avoid calling any native methods
          // in case they've been unregistered after stopRecording() returned.
          if (keepAlive) {
            nativeDataIsRecorded(bytesRead, nativeAudioRecord);
          }
          if (audioSamplesReadyCallback != null) {
            // Copy the entire byte buffer array.  Assume that the start of the byteBuffer is
            // at index 0.
            byte[] data = Arrays.copyOf(byteBuffer.array(), byteBuffer.capacity());
            audioSamplesReadyCallback.onWebRtcAudioRecordSamplesReady(
                new AudioSamples(audioRecord, data));
          }
        } else {
          String errorMessage = "AudioRecord.read failed: " + bytesRead;
          Logging.e(TAG, errorMessage);
          if (bytesRead == AudioRecord.ERROR_INVALID_OPERATION) {
            keepAlive = false;
            reportWebRtcAudioRecordError(errorMessage);
          }
        }
        if (DEBUG) {
          long nowTime = System.nanoTime();
          long durationInMs = TimeUnit.NANOSECONDS.toMillis((nowTime - lastTime));
          lastTime = nowTime;
          Logging.d(TAG, "bytesRead[" + durationInMs + "] " + bytesRead);
        }
      }

      try {
        if (audioRecord != null) {
          audioRecord.stop();
        }
      } catch (IllegalStateException e) {
        Logging.e(TAG, "AudioRecord.stop failed: " + e.getMessage());
      }
    }

    // Stops the inner thread loop and also calls AudioRecord.stop().
    // Does not block the calling thread.
    public void stopThread() {
      Logging.d(TAG, "stopThread");
      keepAlive = false;
    }
  }

​ 从 AudioRecord去数据的逻辑在 AudioRecordThread 线程的 Run函数中。

  • 在线程启动的地方,先设置线程的优先级为URGENT_AUDIO,这里调用的是Process.setThreadPriority。
  • 在一个循环中不停地调用audioRecord.read读取数据,把采集到的数据读到ByteBuffer中,然后调用nativeDataIsRecorded JNI函数通知native层数据已经读到,进行下一步处理。

四、停止和销毁

  private boolean stopRecording() {
    Logging.d(TAG, "stopRecording");
    assertTrue(audioThread != null);
    audioThread.stopThread();
    if (!ThreadUtils.joinUninterruptibly(audioThread, AUDIO_RECORD_THREAD_JOIN_TIMEOUT_MS)) {
      Logging.e(TAG, "Join of AudioRecordJavaThread timed out");
      WebRtcAudioUtils.logAudioState(TAG);
    }
    audioThread = null;
    if (effects != null) {
      effects.release();
    }
    releaseAudioResources();
    return true;
  }

​ 可以看到,这里首先把AudioRecordThread读数据循环的keepAlive条件置为false,接着调用ThreadUtils.joinUninterruptibly等待AudioRecordThread线程退出。

这里有一点值得一提,keepAlive变量加了volatile关键字进行修饰,这是因为修改和读取这个变量的操作可能发生在不同的线程,使用volatile关键字进行修饰,可以保证修改之后能被立即读取到。

AudioRecordThread线程退出循环后,会调用audioRecord.stop()停止采集;线程退出之后,会调用audioRecord.release()释放AudioRecord对象。

​ 以上,就是 Android WebRTC 音频采集 Java 层的大致流程。

到此这篇关于Android WebRTC 对 AudioRecord 的使用技术分享的文章就介绍到这了,更多相关Android WebRTC 对 AudioRecord 的使用内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android使用AudioRecord判断是否有音频输入

    Android党都应该玩过一个叫吹裙子的游戏,这个游戏就是原理就是通过监听用户吹出的气的力度来决定如何把MM的裙子弄飞起来的,所以关键在于如何判断用户吹气的力度问题.现在公司刚好有这个需求要评估就是需要一直监听用户的语音输入,当在两秒内没有语音输入时候就暂停某项事情,有语音输入的时候就要继续做某件事.其实这两件事情的本质原理是一样的,就是通过这AudioRecord来处理用户输入的原始音频数据,从而计算出当前用户输入的音量大小来判断用户是否有语音输入.下面贴上一段代码用户可以自行研究. publ

  • Android提高之AudioRecord实现助听器的方法

    通常来说,在进行Android项目开发的时候可以通过MediaRecorder和AudioRecord这两个工具来实现录音的功能,MediaRecorder直接把麦克风的数据存到文件,并且能够直接进行编码(如AMR,MP3等),而AudioRecord则是读取麦克风的音频流.本文使用AudioRecord读取音频流,使用AudioTrack播放音频流,通过"边读边播放"以及增大音量的方式来实现一个简单的助听器程序. 此处需要注意:由于目前的Android模拟器还不支持AudioReco

  • Android利用AudioRecord类实现音频录制程序

    AudioRecord类相对于MediaRecorder来说,更加接近底层,为我们封装的方法也更少.然而实现一个AudioRecord的音频录制程序也很简单.本实例代码如下: package demo.camera; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; imp

  • Android音频处理之通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能

    音频这方面很博大精深,我这里肯定讲不了什么高级的东西,最多也只是一些基础类知识,首先,我们要介绍一下Android他提供的录音类,实际上他有两个,一个是MediaRecorder,还有一个就是我们今天要用到的AudioRecord,那他们有什么区别呢? 一.区别 MediaRecorder和AudioRecord都可以录制音频,区别是MediaRecorder录制的音频文件是经过压缩后的,需要设置编码器.并且录制的音频文件可以用系统自带的Music播放器播放. 而AudioRecord录制的是P

  • Android用AudioRecord进行录音

    在音视频开发中,录音当然是必不可少的.首先我们要学会单独的录音功能,当然这里说的录音是指用AudioRecord来录音,读取录音原始数据,读到的就是所谓的PCM数据.对于录音来说,最重要的几个参数要搞明白: 1.simpleRate采样率,采样率就是采样频率,每秒钟记录多少个样本. 2.channelConfig通道配置,其实就是所谓的单通道,双通道之类的,AudioFormat.CHANNEL_IN_MONO单通道,AudioFormat.CHANNEL_IN_STEREO双通道,这里只列了这

  • Android录音--AudioRecord、MediaRecorder的使用

    Android提供了两个API用于实现录音功能:android.media.AudioRecord.android.media.MediaRecorder. 网上有很多谈论这两个类的资料.现在大致总结下: 1.AudioRecord 主要是实现边录边播(AudioRecord+AudioTrack)以及对音频的实时处理(如会说话的汤姆猫.语音) 优点:语音的实时处理,可以用代码实现各种音频的封装 缺点:输出是PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码

  • android AudioRecorder简单心得分享

    1.如何创建一个有效的AudioRecorder实例 Android各种设备的采样频率不同,输入的声道数也不同,如果采用固定的采样频率和声道数,那么得到的AudioRecorder不一定能够正常初始化.为了正常使用,需要尝试各种不同的参数,得到在此设备上可以用的AudioRecorder实例.代码如下: 复制代码 代码如下: private void createAudioRecord() {              for (int sampleRate : new int[]{44100,

  • Android使用AudioRecord实现暂停录音功能实例代码

    题外话:发现好久都没有上来写博文了,毕业设计加上公司暂时没有Android的项目做,只能去自学web上的知识,摸爬打滚到现在,花了一个多月时间根据公司的现有模板做了公司内部一个任务管理系统,感觉都是比较浅的知识,没什么可以写的.想到之前做的语音识别的项目,虽然现在没什么下文了,但是谁懂~~~将来呢? 言归正传,项目长这样子: 设计的思路: 由于自带的AudioRecord没有pauseRecord()方法,我把开始录音-->(暂停/继续录音)...-->停止录音叫做一次录音,点击一次暂停就会产

  • Android WebRTC 对 AudioRecord 的使用技术分享

    目录 一.创建和初始化 二.启动 三.读数据 四.停止和销毁 前言: AudioRecord 是 Android 基于原始PCM音频数据录制的类,WebRCT 对其封装的代码位置位于org/webrtc/audio/WebRtcAudioRecord.java,接下来我们学习一下 AudioRecord 是如何创建启动,读取音频采集数据以及销毁等功能的. 一.创建和初始化 private int initRecording(int sampleRate, int channels) {    

  • Android 以任意比例裁剪图片代码分享

    公司的一个小伙伴写的,可以按照任意比例裁剪图片.我觉得挺好用的.简单在这里记录一下,以后肯定还会用到. public class SeniorCropImageView extends ImageView implements ScaleGestureDetector.OnScaleGestureListener, View.OnLayoutChangeListener { /* For drawing color field start */ private static final int

  • Android高仿微信聊天界面代码分享

    微信聊天现在非常火,是因其界面漂亮吗,哈哈,也许吧.微信每条消息都带有一个气泡,非常迷人,看起来感觉实现起来非常难,其实并不难.下面小编给大家分享实现代码. 先给大家展示下实现效果图: OK,下面我们来看一下整个小项目的主体结构: 下面是Activity的代码: package com.way.demo; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import jav

  • 关于Android实现简单的微信朋友圈分享功能

    1.先下载微信分享的jar包放在lib目录下,并且添加依赖, 清单文件添加 <activity android:name=".wxapi.WXEntryActivity" android:exported="true">//产生回调 </activity> 2.在工程下新建如图所示的包和Activity Activity里面的内容主要是创建微信api并且注册返回回调值 private final String APP_ID= "wx

  • vue技术分享之你可能不知道的7个秘密

    前言 本文是vue源码贡献值Chris Fritz在公共场合的一场分享,觉得分享里面有不少东西值得借鉴,虽然有些内容我在工作中也是这么做的,还是把大神的ppt在这里翻译一下,希望给朋友带来一些帮助. 一.善用watch的immediate属性 这一点我在项目中也是这么写的.例如有请求需要再也没初始化的时候就执行一次,然后监听他的变化,很多人这么写: created(){ this.fetchPostList() }, watch: { searchInputValue(){ this.fetch

  • Android 谷歌推荐的VR实现方式(分享)

    谷歌有专门的SDK来完成VR,我这次以一个全景图片的例子来说一下这个SDK实现VR的基本过程,首先全景图片就是百度地图里的那样,能够看到周围环境360的图片. 添加依赖 compile 'com.google.vr:sdk-panowidget:1.80.0' 添加权限 <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="an

  • springcloud组件技术分享(推荐)

    Springcloud技术分享 Spring Cloud 是一套完整的微服务解决方案,基于 Spring Boot 框架,准确的说,它不是一个框架,而是一个大的容器,它将市面上较好的微服务框架集成进来,从而简化了开发者的代码量. Spring Cloud 是什么? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的开发便利性简化了分布式系统的开发,比如服务发现.服务网关.服务路由.链路追踪等.Spring Cloud 并不重复造轮子,而是将市面上开发得比较好的模

  • 常见的反爬虫urllib技术分享

    目录 通过robots.txt来限制爬虫: 通过User-Agent来控制访问: 验证码: IP限制: cookie: JS渲染: 爬虫和反爬的对抗一直在进行着…为了帮助更好的进行爬虫行为以及反爬,今天就来介绍一下网页开发者常用的反爬手段. 通过robots.txt来限制爬虫: 爬虫都遵守着一个协议:robots.txt robots.txt(统一小写)是一种存放于网站根目录下的ASCII编码的文本文件,它通常告诉网络搜索引擎的漫游器(又称网络蜘蛛),此网站中的哪些内容是不应被搜索引擎的漫游器获

  • Android应用中使用及实现系统“分享”接口实例

    为了应用的推广.传播,很多的应用中都有"分享"功能,一个按钮,点击后会出现短信.微博等等一切实现了分享功能的应用列表.这一篇文章主要介绍怎么调用分享功能和怎么实现分享接口让自己应用出现分享列表中.Android应用中能很方便的完成这些功能,这也正是Android的伟大之处,他能很简单的完成应用之间的沟通以相互整合. 调用分享功能 1.分享文本 分享功能使用的隐式启动Activity的方法,这里的Action使用的是 ACTION_SEND. Intent sendIntent = ne

  • Android超实用的Toast提示框优化分享

    前言 相信每位Android开发者都用过Toast,都知道是弹出消息的.类似于js里面的alert,C#里面的MesageBox.当然android里面也有dialog,dialog是有焦点的,可与用户交互.而toast是没有焦点的,时间到了自动消失,不能回应用户的交互,下面就跟大家分享下Android中Toast提示框的优化方法. 先看下源码: public class Toast { public static final int LENGTH_SHORT = 0; public stati

随机推荐