首页 > 其他分享 >Android 音频采集 - AudioRecord

Android 音频采集 - AudioRecord

时间:2024-11-15 22:45:48浏览次数:3  
标签:int 音频 AudioRecord 采集 AudioFormat Android public

一、概述

AudioRecord 是 Android 平台比较重要的类,也是 Java 接口中比较偏底层(平台)的接口,可以通过它从平台的音频输入硬件来获取原始音频 PCM 数据。它的工作原理是要需要通过应用侧轮询调用 read 接口来驱动,每调用一次,系统就会从硬件采集到的数据填充一次,至于传递数据的载体可以是 byte[] 数组 或者 ByteBuffer 。

在这里插入图片描述

二、所需权限

应用程序创建 AudioRecord 实例需要在AndroidManifest文件赋予 Manifest.permission.RECORD_AUDIO 权限。没有赋予这个权限,如果使用 AudioRecord.Builder 来构建的话,执行build()函数会抛出 UnsupportedOperationException 异常,即使您捕获了此异常,在获取 AudioRecord 状态 的时候,也是未初始化状态(STATE_UNINITIALIZED)。

<uses-permission android:name="android.permission.RECORD_AUDIO"/>

在 API 23 (Android 6.0) 之后,为了保护用户隐私,对于一些敏感权限(比如录音权限),应用需要在运行时动态申请。示例代码如下:

private static final int PERMISSION_REQUEST_CODE = 1;

// step1: 检查是否有录音权限
private boolean checkPermission() {
    int result = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO);
    return result == PackageManager.PERMISSION_GRANTED;
}

// step2: 请求录音权限
private void requestPermission() {
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, PERMISSION_REQUEST_CODE);
}

// step3: 处理权限请求结果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode == PERMISSION_REQUEST_CODE) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 权限被授予,可以进行录音
            startRecording(); 
        } else {
            // 权限被拒绝,无法进行录音
            Toast.makeText(this, "录音权限被拒绝", Toast.LENGTH_SHORT).show();
        }
    }
}

三、初始化工作

3.1 确定硬件 buffer size

上文提及到,在创建 AudioRecord 对象的时候会初始化与其关联的 buffer , buffer size 是在构造它的时候传递过去的。所以第一步要做的事情就是确定 buffer size ,一般来说,设置硬件 buffer size 最大等于每一帧音频帧的 size ,这样处理起来比较方便。

音频帧大小 = 通道数 ∗ 每个采样点所占字节数 ∗ 采样率 / 帧长 音频帧大小 = 通道数 * 每个采样点所占字节数 * 采样率 / 帧长 音频帧大小=通道数∗每个采样点所占字节数∗采样率/帧长

举例说明:使用场景为双通道,每个采样点需要16bit,采样率48000,帧长(采样时间): 100ms,那么最终一帧数据的大小为:

//帧大小
final int bytesPerFrame = 2 * (16/8) * 48000 / 100; // 1920

//创建buffer
ByteBuffer buffer = ByteBuffer.allocateDirect(bytesPerFrame);

还有一个重要的步骤,就是获取硬件所支持的最小缓冲区大小 minHwBufferSize。因为我们想要设置缓冲区大小 ≥ 实际场景需要的音频帧大小,那如果音频帧大小要小于 minHwBufferSize 呢,所以我们需要做一些处理。如以下代码:

//获取硬件所支持的最小缓冲区大小
int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);

if(minBufferSize < buffer.capacity()){
	minBufferSize = buffer.capacity();
}

//这样设置采集和处理音频数据比较平滑
int bufferSizeInBytes = Math.max(BUFFER_SIZE_FACTOR * minBufferSize, byteBuffer.capacity()); // BUFFER_SIZE_FACTOR 一般为2

3.2 构建 AudioRecord 对象

一共有两种方式可以构建出 AudioRecord 对象,分别是通过构造方法和构造者模式。

构造方法

// 1. 通过构造方法创建
AudioRecord audioRecord = null;
try {
	audioRecord = new AudioRecord(AudioSource.VOICE_COMMUNICATION, 48000, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes);
} catch (IllegalArgumentException e) {
	e.printStackTrace();
}

if(audioRecord == null || audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
	//构造失败	
}

构建者模式

//2. 通过构造者模式创建
AudioRecord audioRecord = null;
try {
	recorder = new AudioRecord.Builder()
	  .setAudioSource(AudioSource.VOICE_COMMUNICATION)
	  .setAudioFormat(new AudioFormat.Builder()
	    .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
	    .setSampleRate(48000)
	    .setChannelMask(AudioFormat.CHANNEL_IN_STEREO)
	    .build())
	  .setBufferSizeInBytes(bufferSizeInBytes)
	  .build();
} catch (UnsupportedOperationException e) {
	e.printStackTrace();	
}

if(audioRecord == null || audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
	//构造失败	
}

我们需要关注的是构建 AudioRecord 所需要的必要参数。

audioSource

音频源,定义了音频信号的默认输入设备和采集配置。具体常量见 AudioSource 这个类。

sampleRateInHz

采样率(单位:赫兹Hz),通常设置 44100Hz 可确保能够在几乎所有设备上都能正常工作,像其他的 22050Hz, 16000Hz, 11025Hz 也许只能在部分的设备上工作。

channelConfig

描述音频通道的配置,一般配置单通道(AudioFormat.CHANNEL_IN_MONO) 和双声道 (AudioFormat.CHANNEL_IN_STEROR)。

audioFormat

音频格式,这里表示采集音频数据的精度,参数可选:AudioFormat.ENCODING_PCM_8BIT,AudioFormat.ENCODING_PCM_16BIT,AudioFormat.ENCODING_PCM_FLOAT。

bufferSizeInBytes

缓存 buffer 的大小(单位: 字节),上文 3.1 章节已提及过。

四、采集音频数据

  • 开启音频采集

通过调用 startRecording 接口来控制硬件开启采集状态,可以通过通过 AudioRecord 对象内部的 recordingState 状态来判断是否开启成功。

var result = ErrorCode.SV_NO_ERROR
try {
  audioRecord?.startRecording()
} catch (err: IllegalStateException) {
  err.printStackTrace()
  result = ErrorCode.SV_START_ERROR
}

if(audioRecord!!.recordingState != AudioRecord.RECORDSTATE_RECORDING) {
  //通过 recordingState 状态来判断是否开启成功
}
  • 创建采集线程
captureThread = AudioCaptureThread()
captureThread?.start() ?: { result = ErrorCode.SV_START_ERROR }
  • 读取音频数据

其实 read 函数这里有个细节的地方,来看看 read 函数的定义:

public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes);

public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes) {
    return read(audioData, offsetInBytes, sizeInBytes, READ_BLOCKING);
}

@IntDef({
    READ_BLOCKING,
    READ_NON_BLOCKING
})
@Retention(RetentionPolicy.SOURCE)
public @interface ReadMode {}
public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes, @ReadMode int readMode)

如果不带参数 readMode 的情况,默认走的是阻塞模式,调用 read 会一直等待系统那边填充结束后才返回给你。所以走阻塞的情况一般需要创建一条采集线程,以防止阻塞住调用线程。具体怎么选择?一般来说,如果采集的 buffer size 比较小,则选择阻塞模式。buffer size 比较大,可以选择非阻塞,因为非阻塞模式,系统并没有提供填充结束的回调通知,还需要开发者自行的计算采集间隔时间。

while (keepAlive) {
    val readSize = audioRecord!!.read(byteBuffer, byteBuffer.capacity())
    if(readSize == byteBuffer.capacity()) {
        val data = Arrays.copyOf(byteBuffer.array(), byteBuffer.capacity())
        fos?.write(data)
    } else {
        // read failed.
    }
}

五、停止采集

  • 停止采集,停止采集线程,将 keepAlive 变量置为 false 即可。

  • 调用 stop 接口,关闭硬件采集。

    runCatching {
        audioRecord?.stop()
    }
    

六、其他接口

上文主要讲述了使用 AudioRecord 接口的整体调用姿势,下面来讲讲 AudioRecord 的几个平时开发很少用,却很实用的接口。

6.1 分段录制

相关配套的接口如下,其中 markerPosition 和 period 区别在于是否需要周期性回调。这类接口的使用场景一般:分段录制,采集进度回调显示等。

// 设置标记帧,采集了 markerInFrames 帧数就回调 OnRecordPositionUpdateListener#onMarkerReached
public int setNotificationMarkerPosition(int markerInFrames)

// 设置一个采集周期,采集到的音频帧等于 periodInFrames 就触发回调 OnRecordPositionUpdateListener#onPeriodicNotification
public int setPositionNotificationPeriod(int periodInFrames);

public interface OnRecordPositionUpdateListener  {
    /**
     * Called on the listener to notify it that the previously set marker has been reached
     * by the recording head.
     */
    void onMarkerReached(AudioRecord recorder);

    /**
     * Called on the listener to periodically notify it that the record head has reached
     * a multiple of the notification period.
     */
    void onPeriodicNotification(AudioRecord recorder);
}

6.2 音频路由相关

设置音频路由偏好

// 指定采集的音频输入设备偏好
public boolean setPreferredDevice(AudioDeviceInfo deviceInfo);

监听音频路由

// 添加音频路由监听
public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener, android.os.Handler handler);
// 取消音频路由监听
public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener);

public interface OnRoutingChangedListener {
    public void onRoutingChanged(AudioRouting router);
}

最后

本文比较详细的讲解了 AudioRecord 的运行机制以及使用方面的介绍,下面是 github 上的示例代码链接。

https://github.com/Sound-Vision/audio_record

如果您觉得以上内容对您有所帮助的话,可以关注下我们运营的公众号 声知视界,会定期的推送 音视频技术、移动端技术 为主轴的 科普类、基础知识类、行业资讯类等相关文章。

标签:int,音频,AudioRecord,采集,AudioFormat,Android,public
From: https://blog.csdn.net/AVExplorer/article/details/143653098

相关文章

  • Android Framework AMS(15)ContentProvider分析-2(getContentResolver及ContentResolver
    该系列文章总纲链接:专题总纲目录AndroidFramework总纲本章关键点总结&说明:说明:本章节主要解读ContentProvider组件的基本知识。关注思维导图中左上侧部分即可。有了前面activity组件分析、service组件分析、广播组件分析、ContentProvider组件的基本流程分析、基于此......
  • Android15音频进阶之input调节CarAudioService音量过程(九十四)
    简介:CSDN博客专家、《Android系统多媒体进阶实战》一书作者新书发布:《Android系统多媒体进阶实战》......
  • 迅为RK3588开发板Android12动态替换开机logo
    性能强iTOP-3588开发板采用瑞芯微RK3588处理器,是全新一代AloT高端应用芯片,采用8nmLP制程,搭载八核64位CPU,四核Cortex-A76和四核Cortex-A55架构,主频高达2.4GHz。 四核心架构GPU集成MaliG610MP4四核GPU、支持OpenGLES1.1、2.0、3.2,OpenCL2.2和Vulkan1.2。带有MMU的特殊2D硬......
  • Apple Logic Pro 11.1 - 专业音乐制作 (音频编辑)
    AppleLogicPro11.1-专业音乐制作(音频编辑)LogicPro配备全新AI功能,引领音乐创作再上新阶请访问原文链接:https://sysin.org/blog/apple-logic-pro/查看最新版。原创作品,转载请保留出处。作者主页:sysin.orgLogicPro配备全新AI功能,引领音乐创作再上新阶伴奏乐手......
  • OpenAI模型whisper 音频转文本
    最近有一个音频转文本的需求,了解到了OpenAI的whisper模型可以实现。Whisper是OpenAI提供的一个通用语音识别模型,支持多语言的音频转文本功能,并具有较高的准确性。它的主要用途包括自动语音识别(ASR)、语言翻译(将音频直接翻译成英文文本)等。Whisper支持将长时间音频文件(如对......
  • 想定制RK3562主板Android系统的开机动画和桌面壁纸吗?看这篇文章就够了
    本文介绍瑞芯微RK3562开发板在安卓Android13系统替换开机动画和桌面壁纸的方法,使用触觉智能EVB3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1T算力NPU,可用于轻量级人工智能应用。开机动画替换将做好的开机动画文件bootanimation.zip包拷贝至vendor/rockchip/common/......
  • 一文简单了解Android中的input流程
    在Android中,输入事件(例如触摸、按键)从硬件传递到应用程序并最终由应用层消费。整个过程涉及多个系统层次,包括硬件层、Linux内核、Native层、Framework层和应用层。我们将深入解析这一流程,并结合代码逐步了解输入事件的传递。1.输入事件的产生与传递输入事件的产生......
  • 基于springboot+vue的Android的党员之家服务APP小程序(源码+文档+部署讲解等)
    课题简介本党员之家服务APP基于springboot+vue技术开发,专为Android平台设计,涵盖源码、文档和部署讲解,为党员们提供便捷、高效的服务。在资讯功能方面,APP会及时推送党的最新理论成果、政策解读、重要会议精神等内容,让党员能够第一时间了解党和国家的政治动态。同......
  • Android运行时请求权限封装
    @目录1介绍2测试用例设计3实现4用例测试5总结本文目的:“借助透明Activity封装一个易于调用的权限请求模块”1介绍Android权限的校验和申请比较简单,但在实际项目中使用时还要进行系统版本的适配,最不友好的是权限的申请结果需要在onRequestPermissionsResult中进行判断,如......
  • Rust 在 Android 的编程实践——技术驱动的车云一体化解决方案探索
    Greptime车云一体化解决方案颠覆了从前传统的车云协同模式,采用更加低成本、高效率的方案来满足当前的市场需求。其中GreptimeDBEdge作为核心组件,专为车机环境量身打造。本文旨在详尽探讨在Android平台利用Rust语言进行开发过程中所积累的经验和教训。交叉编译在车机场景......