上一篇我们追了音频的"顺流"------数据从 App 写入 Ring Buffer,由 AudioFlinger 消费输出。这一篇我们转换视角,看音频的"逆流":AudioRecord。
如果说 AudioTrack 是"喇叭",AudioRecord 就是"麦克风"。数据方向完全相反------硬件产生的 PCM 数据,经过 Audio HAL → RecordThread → Ring Buffer,最终由你的 read() 调用取走。但底层机制------共享内存、零拷贝、无锁同步------和 AudioTrack 出奇地相似,毕竟同一套基础设施,只是数据方向反了。
本文主要内容:
- AudioRecord API 核心参数、音频源类型解析
- 创建流程 :权限检查 → JNI → Native →
AudioFlinger::openRecord() - RecordThread 采集机制:如何从 HAL 周期性读取数据
- 共享内存 Ring Buffer:录音端的生产消费模型(与 AudioTrack 对比)
- read() 内部实现:阻塞与非阻塞、时间戳同步
- 实战案例:录音机 + 实时音频处理 + PCM Dump 调试
源码版本 :Android 15 AOSP 关键路径 :
frameworks/base/media/java/android/media/AudioRecord.java、frameworks/av/media/libaudioclient/AudioRecord.cpp、frameworks/av/services/audioflinger/RecordTracks.h
AudioRecord API 基础
音频源类型:选对 AudioSource 很关键
AudioRecord 最重要的参数是 AudioSource,它不只是标签,直接影响录音路径和信号处理:
java
// 常用音频源对比
AudioSource.MIC // 原始麦克风输入,无任何处理
AudioSource.VOICE_COMMUNICATION // 针对 VoIP 优化:噪声消除 + 回声消除
AudioSource.VOICE_RECOGNITION // 针对语音识别优化:低延迟 + 高清晰度
AudioSource.CAMCORDER // 相机录像:可能使用辅助麦克风降低风噪
AudioSource.VOICE_CALL // 录制通话音频(需特殊权限)
AudioSource.UNPROCESSED // 完全原始信号,无任何DSP处理(Android 7+)
AudioSource.VOICE_PERFORMANCE // 针对现场表演:最低延迟(Android 10+)
踩坑经验 :做语音识别 App 时用了 MIC,结果在嘈杂环境下识别率很低。换成 VOICE_RECOGNITION 后,系统会自动启用降噪算法,识别率明显提升。代价是多了一点处理延迟,但对识别场景来说完全值得。
基础用法:Builder 模式
java
// 第一步:计算最小缓冲区大小
int minBufferSize = AudioRecord.getMinBufferSize(
16000, // 采样率:16kHz(语音识别常用)
AudioFormat.CHANNEL_IN_MONO, // 单声道(录音通常用单声道)
AudioFormat.ENCODING_PCM_16BIT // 16-bit PCM
);
if (minBufferSize == AudioRecord.ERROR_BAD_VALUE) {
// 参数不支持,需要换参数
throw new IllegalArgumentException("不支持的录音参数");
}
// 第二步:创建 AudioRecord(推荐 Builder 方式,Android 6+)
AudioRecord recorder = new AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.VOICE_RECOGNITION)
.setAudioFormat(new AudioFormat.Builder()
.setSampleRate(16000)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_IN_MONO)
.build())
.setBufferSizeInBytes(minBufferSize * 2) // 实际缓冲区建议 2x 最小值
.build();
// 第三步:检查是否成功初始化
if (recorder.getState() != AudioRecord.STATE_INITIALIZED) {
throw new IllegalStateException("AudioRecord 初始化失败");
}
参数选择指南
| 场景 | 采样率 | 声道 | 格式 | AudioSource |
|---|---|---|---|---|
| 语音识别 | 16000 Hz | MONO | PCM_16BIT | VOICE_RECOGNITION |
| 电话录音 | 8000 Hz | MONO | PCM_16BIT | VOICE_COMMUNICATION |
| 高清录音 | 44100 Hz | STEREO | PCM_16BIT | MIC |
| 专业录音 | 48000 Hz | STEREO | PCM_FLOAT | UNPROCESSED |
| 游戏/现场 | 44100 Hz | MONO | PCM_16BIT | VOICE_PERFORMANCE |
注意 :
CHANNEL_IN_STEREO在部分低端设备上可能不支持,生产环境需做降级处理。
AudioRecord 创建流程深度解析
与 AudioTrack 高度对称,AudioRecord 的创建同样跨越三个进程边界。

第一层:权限检查------比 AudioTrack 多了一道门
这是 AudioRecord 与 AudioTrack 最大的不同之处。录音涉及隐私,Android 在 Java 层就做了强制权限检查:
java
// frameworks/base/media/java/android/media/AudioRecord.java
public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig,
int audioFormat, int bufferSizeInBytes) {
// ...
// 1. 权限检查(API 23+ 需要运行时权限)
// 注意:这里只是参数校验,实际权限在 AudioPolicyService 里检查
if (audioSource == MediaRecorder.AudioSource.DEFAULT) {
mRecordSource = MediaRecorder.AudioSource.MIC;
} else {
mRecordSource = audioSource;
}
// 2. 触发 Native 初始化
int initResult = native_setup(
new WeakReference<AudioRecord>(this),
mRecordSource,
sampleRateInHz, channelMask, audioFormat,
buffSizeInBytes, sessionId,
getCurrentOpPackageName(), 0);
}
实际权限检查在哪里? 在 AudioPolicyService::getInputForAttr() 里,它会调用 PermissionController 验证调用方是否有 android.permission.RECORD_AUDIO。如果没有权限,openRecord() 会返回 PERMISSION_DENIED,整个 native_setup() 失败。
第二层:Native AudioRecord::set() 的关键步骤
cpp
// frameworks/av/media/libaudioclient/AudioRecord.cpp
status_t AudioRecord::set(
audio_source_t inputSource,
uint32_t sampleRate,
audio_format_t format,
audio_channel_mask_t channelMask,
size_t frameCount,
...) {
// 1. 参数校验
if (!audio_is_valid_format(format) || !audio_is_linear_pcm(format)) {
ALOGE("Invalid format %#x", format);
return BAD_VALUE;
}
// 2. 通过 AudioSystem 查询 AudioPolicyService,获取合适的输入设备
// 这里会触发路由决策:MIC → 哪个物理麦克风?
audio_io_handle_t input = AUDIO_IO_HANDLE_NONE;
status_t status = AudioSystem::getInputForAttr(
&mAttributes, &input, ..., &selectedDeviceId, ...);
// 3. 建立与 AudioFlinger 的连接
status = openRecord_l(0 /*epoch*/, mOpPackageName);
return NO_ERROR;
}
第三层:openRecord_l() ------ Binder 调用 AudioFlinger
cpp
// frameworks/av/media/libaudioclient/AudioRecord.cpp
status_t AudioRecord::openRecord_l(const Modulo<uint32_t> &epoch, ...) {
// 1. 获取 AudioFlinger Binder 代理
const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();
// 2. 构造请求(与 AudioTrack 非常相似)
IAudioFlinger::CreateRecordInput input;
input.attr = mAttributes;
input.config.sample_rate = mSampleRate;
input.config.channel_mask = mChannelMask;
input.config.format = mFormat;
input.frameCount = mReqFrameCount;
input.sessionId = mSessionId;
// 3. Binder IPC → AudioFlinger.createRecord()
IAudioFlinger::CreateRecordOutput output;
sp<media::IAudioRecord> record = audioFlinger->createRecord(input, output, &status);
// 4. 拿到共享内存,进行 mmap
sp<IMemory> iMem = record->getCblk();
void* iMemPointer = iMem->unsecurePointer();
// 5. mCblk:录音控制块
mCblk = static_cast<audio_track_cblk_t*>(iMemPointer);
mBuffer = (char*)iMemPointer + sizeof(audio_track_cblk_t);
return NO_ERROR;
}
AudioFlinger 端:createRecord() 的工作
cpp
// frameworks/av/services/audioflinger/AudioFlinger.cpp
sp<media::IAudioRecord> AudioFlinger::createRecord(
const CreateRecordInput& input,
CreateRecordOutput& output,
status_t *status) {
// 1. 权限二次验证(audioserver 进程再次检查)
if (!recordingAllowed(input.attr, input.clientPid, input.clientUid)) {
*status = PERMISSION_DENIED;
return nullptr;
}
// 2. 找到对应的 RecordThread
// RecordThread 是在 Audio HAL openInputStream 时创建的
sp<RecordThread> thread = checkRecordThread_l(input.inputId);
// 3. 在 RecordThread 上创建 RecordTrack
sp<RecordThread::RecordTrack> recordTrack = thread->createRecordTrack_l(
client, sampleRate, format, channelMask,
&frameCount, sessionId, ...);
// 4. 分配共享内存(同 AudioTrack)
// audio_track_cblk_t + PCM 数据缓冲区
sp<MemoryDealer> heap = new MemoryDealer(
sizeof(audio_track_cblk_t) + frameCount * mFrameSize);
sp<IMemory> cblkMemory = heap->allocate(...);
// 5. 返回 RecordHandle + 共享内存 fd
sp<RecordHandle> handle = new RecordHandle(recordTrack);
output.cblk = cblkMemory;
return handle;
}
RecordThread 采集机制
这是录音的核心"引擎",理解它能帮你解释很多录音延迟和质量问题。
RecordThread 的线程循环
cpp
// frameworks/av/services/audioflinger/Threads.cpp
bool AudioFlinger::RecordThread::threadLoop() {
while (!exitPending()) {
// 1. 等待有 Active RecordTrack
{
Mutex::Autolock _l(mLock);
while (mActiveTracks.isEmpty()) {
mWaitWorkCV.wait(mLock);
}
}
// 2. 从 Audio HAL 读取一个周期的数据
// HAL 的周期大小(period_size)决定了录音的最小延迟
// 典型值:44100Hz × 480帧 ≈ 10.9ms
ssize_t bytesRead = mInput->stream->read(
mRsmpInBuffer, // 读入缓冲区
mBufferSize // HAL 期望的每次读取大小
);
if (bytesRead < 0) {
// HAL 读取失败,可能是设备断开
ALOGE("RecordThread: read returned error %zd", bytesRead);
usleep(50000); // 50ms 后重试
continue;
}
// 3. 采样率转换(如果需要)
// App 请求 16kHz,硬件输出 48kHz → 需要 3:1 降采样
if (mResampler != nullptr) {
mResampler->resample(
mRecordBufferConverter, &provider,
mRsmpOutBuffer, mRsmpOutFrameCount);
}
// 4. 将数据分发给所有 Active RecordTrack
for (auto& track : mActiveTracks) {
if (track->isActive()) {
copyRecordToClients(track, ...);
}
}
}
}
copyRecordToClients() ------ 写入共享内存
cpp
// frameworks/av/services/audioflinger/RecordTracks.cpp
void AudioFlinger::RecordThread::copyRecordToClients(
const sp<RecordTrack>& activeTrack, ...) {
// 获取 Ring Buffer 的可写位置
AudioBufferProvider::Buffer buffer;
status_t status = activeTrack->getNextBuffer(&buffer);
if (status == OK && buffer.frameCount > 0) {
// 直接写入共享内存(这就是"零拷贝"的体现)
// mRsmpOutBuffer 是从 HAL 读来的数据(或重采样后的数据)
// buffer.raw 直接指向 App 进程 mmap 的共享内存
memcpy(buffer.raw, mRsmpOutBuffer, buffer.frameCount * mFrameSize);
// 更新写指针(原子操作)
activeTrack->releaseBuffer(&buffer);
// 唤醒正在 read() 阻塞的 App
activeTrack->mCblk->cv.signal();
}
}
关键洞察 :RecordThread 直接把 HAL 数据 memcpy 到共享内存,然后 App 的 read() 再从共享内存拷贝到 App 的 Buffer。整个过程实际上有一次 memcpy (从共享内存到 App buffer),而 AudioTrack 的 write() 也是一次 memcpy(App buffer 到共享内存)。这与 AudioTrack 是完全对称的。
共享内存 Ring Buffer ------ 录音版

录音的 Ring Buffer 与播放的完全对称,只是生产者和消费者角色互换:
| 角色 | 播放(AudioTrack) | 录音(AudioRecord) |
|---|---|---|
| 生产者 | App(write()) |
audioserver(RecordThread) |
| 消费者 | audioserver(PlaybackThread) | App(read()) |
| 写指针操作者 | App | RecordThread |
| 读指针操作者 | AudioFlinger | App |
| 数据流向 | App → Buffer → 扬声器 | 麦克风 → Buffer → App |
read() 的内部实现
cpp
// frameworks/av/media/libaudioclient/AudioRecord.cpp
ssize_t AudioRecord::read(void* buffer, size_t userSize, bool blocking) {
// 1. 获取可读帧(如果没有数据,在此等待)
Buffer audioBuffer;
status_t err = obtainBuffer(&audioBuffer,
blocking ? &ClientProxy::kForever : &ClientProxy::kNonBlocking);
if (err == TIMED_OUT || err == WOULD_BLOCK) {
// 非阻塞模式:暂时没有数据
return 0;
}
if (err != NO_ERROR) {
return ssize_t(err);
}
// 2. 从共享内存拷贝到 App 缓冲区
size_t toRead = min(audioBuffer.size, userSize);
memcpy(buffer, audioBuffer.raw, toRead);
// 3. 更新读指针(原子操作,不需要持锁)
releaseBuffer(&audioBuffer);
return toRead;
}
阻塞 vs 非阻塞 read()
java
// 阻塞模式(默认):没有数据时 read() 会等待
// 适合:录音保存文件、语音识别(每帧数据都必须处理)
short[] buffer = new short[bufferSize / 2];
int readCount = audioRecord.read(buffer, 0, buffer.length);
// readCount > 0:成功读取了 readCount 个 short
// readCount == AudioRecord.ERROR_INVALID_OPERATION:状态错误
// readCount == AudioRecord.ERROR_BAD_VALUE:参数错误
// 非阻塞模式(Android 6+):没有数据立即返回 0
// 适合:需要精确控制时序的场景(如实时DSP处理)
int readCount = audioRecord.read(buffer, 0, buffer.length,
AudioRecord.READ_NON_BLOCKING);
if (readCount == 0) {
// 暂时没有数据,可以做其他事情
}
精确时间戳获取
java
// Android 7.0+ 支持获取录音时间戳,用于音视频同步
AudioTimestamp timestamp = new AudioTimestamp();
if (audioRecord.getTimestamp(timestamp, AudioTimestamp.TIMEBASE_MONOTONIC)
== AudioRecord.SUCCESS) {
// framePosition:已采集并交给 App 的帧数
// nanoTime:对应的 CLOCK_MONOTONIC 时间戳
long capturedUs = timestamp.framePosition * 1_000_000L / sampleRate;
Log.d(TAG, "已采集: " + capturedUs / 1000 + " ms");
}
录音控制流程
状态机
scss
STATE_UNINITIALIZED
↓ new AudioRecord().build() 成功
STATE_INITIALIZED
↓ startRecording()
RECORDSTATE_RECORDING
↓ stop()
RECORDSTATE_STOPPED
(release() 可随时调用)
AudioRecord 的状态比 AudioTrack 简单很多------没有 pause(),只有 start/stop。
startRecording() 的内部流程
cpp
// Java → Native → Binder
// Native 层:
status_t AudioRecord::start(AudioSystem::sync_event_t event, audio_session_t triggerSession) {
// 通知 AudioFlinger:把这个 RecordTrack 加入活跃列表
status_t status = mAudioRecord->start(event, triggerSession);
if (status == NO_ERROR) {
mActive = true;
// 启动回调线程(如果有回调)
if (mAudioRecordThread != nullptr) {
mAudioRecordThread->wakeup();
}
}
return status;
}
在 AudioFlinger 端,RecordThread 的 threadLoop() 会检测到有 Active Track,立即开始从 HAL 读取数据。
正确的 stop() 和 release()
java
// 错误做法:直接 release() 不 stop()
audioRecord.release(); // ❌ 可能导致 RecordThread 空指针
// 正确做法
if (audioRecord != null) {
if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
audioRecord.stop();
}
audioRecord.release();
audioRecord = null;
}
为什么要先 stop() 再 release()? stop() 会通知 RecordThread 将这个 Track 从活跃列表中移除,然后等待当前正在进行的 HAL read 完成。直接 release() 会释放共享内存,但 RecordThread 可能还在往里写,导致写越界。
音频格式处理
采样率转换的开销
录音时的采样率转换比播放时更敏感,因为它在 RecordThread 的关键路径上:
cpp
// frameworks/av/services/audioflinger/Threads.cpp
// RecordThread 创建时检查是否需要重采样
if (mSampleRate != mInput->stream->getSampleRate()) {
// 需要创建重采样器
// AudioResampler::kDefaultQuality:用于一般场景
// AudioResampler::kHighQuality:用于专业录音场景
mResampler = AudioResampler::create(
AUDIO_FORMAT_PCM_16_BIT,
mChannelCount,
mSampleRate,
AudioResampler::kDefaultQuality);
}
实际影响:在 Snapdragon 8 Gen 3 上测试:
- 无重采样(App 请求 = 硬件输出采样率):RecordThread 每周期 CPU 占用约 0.3%
- 需要重采样(16kHz → 48kHz):约 1.2%
- 同时需要声道转换(mono ← stereo):约 1.5%
因此,尽量让 App 请求的采样率与硬件一致,避免重采样开销。
声道转换
java
// 如果硬件只支持 STEREO 输入,但你请求 MONO
// AudioFlinger 的 RecordThread 会自动做声道混合(downmix)
// L+R → (L+R)/2
// 查询硬件支持的声道配置
AudioRecord.getMinBufferSize(
48000,
AudioFormat.CHANNEL_IN_STEREO, // 先试 STEREO
AudioFormat.ENCODING_PCM_16BIT);
// 如果返回 ERROR_BAD_VALUE,说明不支持,改用 MONO
实战案例
案例一:完整录音机实现
java
public class VoiceRecorder {
private AudioRecord mAudioRecord;
private Thread mRecordThread;
private volatile boolean mIsRecording = false;
private FileOutputStream mFileOutputStream;
private static final int SAMPLE_RATE = 16000;
private static final int CHANNEL = AudioFormat.CHANNEL_IN_MONO;
private static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT;
public void startRecording(String outputPath) throws IOException {
int bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL, FORMAT);
if (bufferSize <= 0) {
throw new IllegalStateException("设备不支持录音参数");
}
mAudioRecord = new AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.VOICE_RECOGNITION)
.setAudioFormat(new AudioFormat.Builder()
.setSampleRate(SAMPLE_RATE)
.setEncoding(FORMAT)
.setChannelMask(CHANNEL)
.build())
.setBufferSizeInBytes(bufferSize * 2)
.build();
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
throw new IllegalStateException("AudioRecord 初始化失败,检查 RECORD_AUDIO 权限");
}
mFileOutputStream = new FileOutputStream(outputPath);
mIsRecording = true;
mAudioRecord.startRecording();
mRecordThread = new Thread(() -> {
byte[] buffer = new byte[bufferSize];
android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); // 提高录音线程优先级
while (mIsRecording) {
int bytesRead = mAudioRecord.read(buffer, 0, buffer.length);
if (bytesRead > 0) {
try {
mFileOutputStream.write(buffer, 0, bytesRead);
} catch (IOException e) {
Log.e(TAG, "写入文件失败", e);
mIsRecording = false;
}
} else if (bytesRead == AudioRecord.ERROR_INVALID_OPERATION) {
Log.e(TAG, "AudioRecord 状态错误");
break;
}
}
}, "VoiceRecordThread");
mRecordThread.start();
}
public void stopRecording() {
mIsRecording = false;
if (mAudioRecord != null) {
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
}
try {
if (mFileOutputStream != null) {
mFileOutputStream.close();
mFileOutputStream = null;
}
} catch (IOException e) {
Log.e(TAG, "关闭文件失败", e);
}
}
}
案例二:实时音频处理(FFT 频谱分析)
java
public class RealtimeAudioProcessor {
private AudioRecord mAudioRecord;
private volatile boolean mProcessing = false;
// 处理回调(在独立线程中调用)
public interface AudioProcessCallback {
void onAudioData(short[] pcmData, int sampleCount);
}
public void startProcessing(AudioProcessCallback callback) {
int sampleRate = 44100;
int frameSize = 1024; // FFT 的帧大小必须是 2 的幂
int bufferSize = Math.max(
AudioRecord.getMinBufferSize(sampleRate,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT),
frameSize * 2 // 至少能放下 2 帧
);
mAudioRecord = new AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.MIC)
.setAudioFormat(new AudioFormat.Builder()
.setSampleRate(sampleRate)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_IN_MONO)
.build())
.setBufferSizeInBytes(bufferSize)
.build();
mProcessing = true;
mAudioRecord.startRecording();
new Thread(() -> {
short[] pcmBuffer = new short[frameSize];
android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
while (mProcessing) {
// 精确读取 frameSize 个 short(阻塞直到收集满一帧)
int totalRead = 0;
while (totalRead < frameSize && mProcessing) {
int read = mAudioRecord.read(
pcmBuffer, totalRead, frameSize - totalRead);
if (read > 0) {
totalRead += read;
} else {
break;
}
}
if (totalRead == frameSize) {
callback.onAudioData(pcmBuffer, frameSize);
}
}
}, "AudioProcessThread").start();
}
public void stopProcessing() {
mProcessing = false;
if (mAudioRecord != null) {
mAudioRecord.stop();
mAudioRecord.release();
mAudioRecord = null;
}
}
}
// 使用示例
processor.startProcessing((pcmData, count) -> {
// 在这里做 FFT、音量计算、语音活跃检测等
double rms = calculateRMS(pcmData, count);
float dB = (float) (20 * Math.log10(rms / 32768.0));
Log.d(TAG, "当前音量: " + dB + " dBFS");
});
案例三:使用 AudioRecord.OnRecordPositionUpdateListener 精确同步
java
// 通知位置更新回调,适合需要精确时序的场景
audioRecord.setPositionNotificationPeriod(441); // 每 441 帧通知一次(10ms at 44100Hz)
audioRecord.setRecordPositionUpdateListener(new AudioRecord.OnRecordPositionUpdateListener() {
@Override
public void onMarkerReached(AudioRecord recorder) {
// 到达 setNotificationMarkerPosition 设定的位置时回调
}
@Override
public void onPeriodicNotification(AudioRecord recorder) {
// 每 441 帧回调一次
// 注意:这个回调是在 AudioTrackThread(Native线程)中调用,不是主线程
int headPosition = recorder.getRecordPosition();
Log.d(TAG, "录音位置: " + headPosition + " 帧");
}
}, handler);
调试技巧
检查 RecordThread 状态
bash
# 查看所有录音线程信息
adb shell dumpsys media.audio_flinger | grep -A 30 "Input thread"
# 典型输出:
# Input thread 0x7f3d400000 type 0:
# Input: ...
# 1 RecordTracks, 1 active:
# active [ 0] state:0x01 id:3 sessionId:xx 16000Hz PCM_16_BIT MONO
# client: com.example.app (pid=12345)
# state:0x01 表示 ACTIVE(正在录音)
# 关注采样率和格式,确认是否需要重采样
录音 underrun 检测(overrun in recording = 溢出)
bash
# 对于录音,风险是 Ring Buffer 被 RecordThread 写满(App 读太慢)
# 这叫 overrun(溢出),与播放的 underrun 相反
adb shell dumpsys media.audio_flinger | grep -i "overrun\|overflow"
PCM 数据 Dump 调试
java
// 方法1:在 App 层 dump
private void dumpPcm(byte[] data, int size, FileOutputStream dump) {
if (dump != null) {
try { dump.write(data, 0, size); } catch (IOException ignored) {}
}
}
// 方法2:通过 adb shell 使用系统录音工具(无需权限)
// 注意:需要 adb root
adb shell tinycap /data/local/tmp/test.pcm -D 0 -d 0 -c 2 -r 44100 -b 16 -p 1024 &
# -D: card, -d: device, -c: channels, -r: rate, -b: bits, -p: period_size
# 录几秒后 Ctrl+C 停止
adb pull /data/local/tmp/test.pcm ./
# 用 Audacity(选 Raw Data,格式 Signed 16-bit PCM,Stereo,44100Hz)打开
常见问题排查
问题:startRecording() 后 read() 一直返回 0
java
// 原因1:没有声明权限(AndroidManifest.xml 缺 RECORD_AUDIO)
// 原因2:运行时权限没有申请(Android 6+)
// 原因3:应用在后台被系统静音(Android 9+ 前台服务限制)
// 诊断:检查 AudioRecord 状态
Log.d(TAG, "State: " + audioRecord.getState()); // 应为 STATE_INITIALIZED
Log.d(TAG, "RecordState: " + audioRecord.getRecordingState()); // 应为 RECORDSTATE_RECORDING
// 检查权限
boolean hasPermission = ContextCompat.checkSelfPermission(this,
Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
Log.d(TAG, "Has RECORD_AUDIO: " + hasPermission);
问题:录音有噪音/杂音(不是硬件问题)
java
// 原因:录音线程优先级太低,导致 read() 调用不及时,Ring Buffer 发生 overrun
// 解决:提高线程优先级
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
// 验证:查看 overrun 计数
adb shell dumpsys media.audio_flinger | grep "overrun"
问题:getMinBufferSize() 返回 ERROR_BAD_VALUE (-2)
java
// 尝试备用参数
int[] sampleRates = {44100, 22050, 16000, 11025, 8000};
int[] channelConfigs = {AudioFormat.CHANNEL_IN_STEREO, AudioFormat.CHANNEL_IN_MONO};
for (int rate : sampleRates) {
for (int channel : channelConfigs) {
int size = AudioRecord.getMinBufferSize(rate, channel,
AudioFormat.ENCODING_PCM_16BIT);
if (size > 0) {
Log.d(TAG, "支持: " + rate + "Hz " +
(channel == AudioFormat.CHANNEL_IN_MONO ? "MONO" : "STEREO"));
}
}
}
总结
从这两篇文章(AudioTrack + AudioRecord)的对比中,我们可以看到 Android 音频子系统设计的精妙之处:完全对称的双向管道。
| 对比项 | 播放(AudioTrack) | 录音(AudioRecord) |
|---|---|---|
| 创建入口 | AudioFlinger::createTrack() |
AudioFlinger::createRecord() |
| 工作线程 | PlaybackThread |
RecordThread |
| HAL操作 | output->write() |
input->read() |
| 共享内存生产者 | App write() |
RecordThread |
| 共享内存消费者 | AudioFlinger | App read() |
| 低延迟路径 | FastMixer / FAST Track | FastCapture(Android 7+) |
| 权限要求 | 无需特殊权限 | RECORD_AUDIO 必须 |
三个最重要的录音原则:
- 录音线程要提优先级 :
THREAD_PRIORITY_URGENT_AUDIO,否则容易 overrun 产生杂音 - 采样率匹配硬件:避免 RecordThread 做重采样,降低 CPU 开销和延迟
- stop() 后再 release():让 RecordThread 安全地从活跃列表移除,避免共享内存冲突
下一篇,我们深入 AudioFlinger 最复杂的部分------混音机制:当多个 App 同时播放音乐、游戏音效和提示音时,AudioFlinger 是如何把它们混合成一路信号的?