开发者社区> 技术小阿哥> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

Android音频开发(2):如何采集一帧音频

简介:
+关注继续查看

Android SDK 提供了两套音频采集的API,分别是:MediaRecorder 和 AudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风录入的音频数据进行编码压缩(如AMR、MP3等)并存成文件,而后者则更接近底层,能够更加自由灵活地控制,可以得到原始的一帧帧PCM音频数据。


如果想简单地做一个录音机,录制成音频文件,则推荐使用 MediaRecorder,而如果需要对音频做进一步的算法处理、或者采用第三方的编码库进行压缩、以及网络传输等应用,则建议使用 AudioRecord,其实 MediaRecorder 底层也是调用了 AudioRecord 与 Android Framework 层的 AudioFlinger 进行交互的。


音频的开发,更广泛地应用不仅仅局限于本地录音,因此,我们需要重点掌握如何利用更加底层的 AudioRecord API 来采集音频数据(注意,使用它采集到的音频数据是原始的PCM格式,想压缩为mp3,aac等格式的话,还需要专门调用编码器进行编码)。


1. AudioRecord 的工作流程


首先,我们了解一下 AudioRecord 的工作流程:


(1) 配置参数,初始化内部的音频缓冲区

(2) 开始采集

(3) 需要一个线程,不断地从 AudioRecord 的缓冲区将音频数据“读”出来,注意,这个过程一定要及时,否则就会出现“overrun”的错误,该错误在音频开发中比较常见,意味着应用层没有及时地“取走”音频数据,导致内部的音频缓冲区溢出。

(4) 停止采集,释放资源


2. AudioRecord 的参数配置


wKioL1bhXMew-y-lAAFNssMMHH8488.png


上面是 AudioRecord 的构造函数,我们可以发现,它主要是靠构造函数来配置采集参数的,下面我们来一一解释这些参数的含义(建议对照着我的上一篇文章来理解):


(1) audioSource


该参数指的是音频采集的输入源,可选的值以常量的形式定义在 MediaRecorder.AudioSource 类中,常用的值包括:DEFAULT(默认),VOICE_RECOGNITION(用于语音识别,等同于DEFAULT),MIC(由手机麦克风输入),VOICE_COMMUNICATION(用于VoIP应用)等等。


(2) sampleRateInHz


采样率,注意,目前44100Hz是唯一可以保证兼容所有Android手机的采样率。


(3) channelConfig


通道数的配置,可选的值以常量的形式定义在 AudioFormat 类中,常用的是 CHANNEL_IN_MONO(单通道),CHANNEL_IN_STEREO(双通道)


(4) audioFormat


这个参数是用来配置“数据位宽”的,可选的值也是以常量的形式定义在 AudioFormat 类中,常用的是 ENCODING_PCM_16BIT(16bit),ENCODING_PCM_8BIT(8bit),注意,前者是可以保证兼容所有Android手机的。


(5) bufferSizeInBytes


这个是最难理解又最重要的一个参数,它配置的是 AudioRecord 内部的音频缓冲区的大小,该缓冲区的值不能低于一帧“音频帧”(Frame)的大小,而前一篇文章介绍过,一帧音频帧的大小计算如下:


int size = 采样率 x 位宽 x 采样时间 x 通道数


采样时间一般取 2.5ms~120ms 之间,由厂商或者具体的应用决定,我们其实可以推断,每一帧的采样时间取得越短,产生的延时就应该会越小,当然,碎片化的数据也就会越多。


在Android开发中,AudioRecord 类提供了一个帮助你确定这个 bufferSizeInBytes 的函数,原型如下:


int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat);


不同的厂商的底层实现是不一样的,但无外乎就是根据上面的计算公式得到一帧的大小,音频缓冲区的大小则必须是一帧大小的2~N倍,有兴趣的朋友可以继续深入源码探究探究。


实际开发中,强烈建议由该函数计算出需要传入的 bufferSizeInBytes,而不是自己手动计算。


3. 音频的采集线程


当创建好了 AudioRecord 对象之后,就可以开始进行音频数据的采集了,通过下面两个函数控制采集的开始/停止:


AudioRecord.startRecording();

AudioRecord.stop();


一旦开始采集,必须通过线程循环尽快取走音频,否则系统会出现 overrun,调用的读取数据的接口是:


AudioRecord.read(byte[] audioData, int offsetInBytes, int sizeInBytes);


4. 示例代码


我将 AudioRecord 类的接口简单封装了一下,提供了一个 AudioCapturer 类,可以到我的Github下载:https://github.com/Jhuster/Android/blob/master/Audio/AudioCapturer.java


这里也贴出来一份:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*
 *  COPYRIGHT NOTICE  
 *  Copyright (C) 2016, Jhuster <lujun.hust@gmail.com>
 *  https://github.com/Jhuster/Android
 *   
 *  @license under the Apache License, Version 2.0 
 *
 *  @file    AudioCapturer.java
 *  
 *  @version 1.0     
 *  @author  Jhuster
 *  @date    2016/03/10    
 */
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;
 
public class AudioCapturer {
 
    private static final String TAG = "AudioCapturer";
     
    private static final int DEFAULT_SOURCE = MediaRecorder.AudioSource.MIC;
    private static final int DEFAULT_SAMPLE_RATE = 44100;
    private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
    private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
 
    private AudioRecord mAudioRecord;
    private int mMinBufferSize = 0;
     
    private Thread mCaptureThread;  
    private boolean mIsCaptureStarted = false;
    private volatile boolean mIsLoopExit = false;
 
    private OnAudioFrameCapturedListener mAudioFrameCapturedListener;
 
    public interface OnAudioFrameCapturedListener {
        public void onAudioFrameCaptured(byte[] audioData);
    }  
 
    public boolean isCaptureStarted() {     
        return mIsCaptureStarted;
    }
 
    public void setOnAudioFrameCapturedListener(OnAudioFrameCapturedListener listener) {
        mAudioFrameCapturedListener = listener;
    }
 
    public boolean startCapture() {
        return startCapture(DEFAULT_SOURCE, DEFAULT_SAMPLE_RATE, DEFAULT_CHANNEL_CONFIG,
            DEFAULT_AUDIO_FORMAT);
    }
 
    public boolean startCapture(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) {
 
        if (mIsCaptureStarted) {
            Log.e(TAG, "Capture already started !");
            return false;
        }
     
        mMinBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz,channelConfig,audioFormat);
        if (mMinBufferSize == AudioRecord.ERROR_BAD_VALUE) {
            Log.e(TAG, "Invalid parameter !");
            return false;
        }
        Log.d(TAG , "getMinBufferSize = "+mMinBufferSize+" bytes !");
         
        mAudioRecord = new AudioRecord(audioSource,sampleRateInHz,channelConfig,audioFormat,mMinBufferSize);            
        if (mAudioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
            Log.e(TAG, "AudioRecord initialize fail !");
        return false;
        }      
 
        mAudioRecord.startRecording();
 
        mIsLoopExit = false;
        mCaptureThread = new Thread(new AudioCaptureRunnable());
        mCaptureThread.start();
 
        mIsCaptureStarted = true;
 
        Log.d(TAG, "Start audio capture success !");
 
        return true;
    }
 
    public void stopCapture() {
 
        if (!mIsCaptureStarted) {
            return;
        }
 
        mIsLoopExit = true;      
        try {
            mCaptureThread.interrupt();
            mCaptureThread.join(1000);
        
        catch (InterruptedException e) {    
            e.printStackTrace();
        }
 
        if (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
            mAudioRecord.stop();                       
        }
 
        mAudioRecord.release();    
     
        mIsCaptureStarted = false;
        mAudioFrameCapturedListener = null;
 
        Log.d(TAG, "Stop audio capture success !");
    }
 
    private class AudioCaptureRunnable implements Runnable {      
     
        @Override
        public void run() {
 
            while (!mIsLoopExit) {
 
                byte[] buffer = new byte[mMinBufferSize];
 
                int ret = mAudioRecord.read(buffer, 0, mMinBufferSize);               
                if (ret == AudioRecord.ERROR_INVALID_OPERATION) {
                    Log.e(TAG , "Error ERROR_INVALID_OPERATION");
                
                else if (ret == AudioRecord.ERROR_BAD_VALUE) {
                    Log.e(TAG , "Error ERROR_BAD_VALUE");
                
                else 
                    if (mAudioFrameCapturedListener != null) {
                        mAudioFrameCapturedListener.onAudioFrameCaptured(buffer);
                    }   
                    Log.d(TAG , "OK, Captured "+ret+" bytes !");
                }                                                      
            }      
        }    
    }
}


使用前要注意,添加如下权限:


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




本文转自 Jhuster 51CTO博客,原文链接:http://blog.51cto.com/ticktick/1749719,如需转载请自行联系原作者


版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
【Android开发】 ListView使用实战详解,你get到了嘛?
【Android开发】 ListView使用实战详解,你get到了嘛?
53 0
【Android 应用开发】Android开发技巧--Application, ListView排列,格式化浮点数,string.xml占位符,动态引用图片
【Android 应用开发】Android开发技巧--Application, ListView排列,格式化浮点数,string.xml占位符,动态引用图片
87 0
Android开发之ListView使用经验分享
在Android开发中,ListView是使用最广泛的组件之一,虽然谷歌推出了RecycleView,但是很多项目中依旧在使用ListView,本文将总结一下使用过程中遇到的一些问题,与大家共勉~~~ 一、ListView 与 Adapter List...
760 0
Android开发重要参考资料
=======================博客============================= 秋百万 有心课堂 郭霖 源码 安装ffmpeg 胡凯 官方培训课程 litesuitsway 爱哥 trinea robinRobin Hu...
824 0
13688
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载