You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android调用MicrophoneInfo空对象报错及实时音频频率检测问题

问题描述

在Android Studio中基于MicrophoneInfo相关API获取手机麦克风参数时,调用getId()、getLocation()等返回整型值的基础方法触发空对象报错,报错参考截图:
空指针报错截图
原有实现代码如下:

package com.example.mi_microfono;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.pm.PackageManager;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.media.AudioDeviceInfo;
import android.media.MicrophoneInfo.Coordinate3F;
import android.media.MicrophoneInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.TextView;

import java.io.IOException;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private MicrophoneInfo miMicrofono;
    private TextView infoMicrofono;
    private MediaRecorder grabacion;
    private String archivoSalida = null;
    private AudioDeviceInfo micMic;


    @RequiresApi(api = Build.VERSION_CODES.Q)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}, 1000);
        }

        infoMicrofono = (TextView) findViewById(R.id.infoMicro);


        configMicro();


    }

    public void configMicro(){

        if (grabacion==null){

            archivoSalida = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC) + "/grabacion5.mp3";
            grabacion = new MediaRecorder();
            grabacion.setAudioSource(MediaRecorder.AudioSource.MIC);
            grabacion.setAudioSamplingRate(44100);
            grabacion.setAudioEncodingBitRate(96000);
            grabacion.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
            grabacion.setOutputFile(archivoSalida);
            grabacion.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB );
        }

        try{
            grabacion.prepare();
            grabacion.start();


        }catch (IOException e){

            String mensajeError = e.getMessage();
            //mensaje.setText(mensajeError);

        }
    }

    @RequiresApi(api = Build.VERSION_CODES.P)
    public void dataMicro(View view){

        int data;
        try {

            data = miMicrofono.getId();

            infoMicrofono.setText("Locación: " + data);

        }catch (NullPointerException e){

            infoMicrofono.setText("error: " + e);
        }

        grabacion.stop();

    }

}

诉求:修复上述空对象报错,同时实现无需执行音频录制、播放操作,直接实时检测手机麦克风拾取到的音频频率,正确调用getFrequencyResponse()方法。

报错根因

代码中仅声明了MicrophoneInfo miMicrofono变量,从未对其进行赋值,调用其方法时必然触发空指针。MicrophoneInfo为系统返回的硬件信息对象,不支持手动实例化,必须通过系统服务获取。除此之外原有代码还存在两个逻辑问题:

  • 权限判断逻辑错误:使用&&判断权限状态,会出现某一个权限未授权时不发起申请的问题
  • 错误依赖MediaRecorder:获取麦克风静态参数不需要启动录制,getFrequencyResponse()返回的是麦克风硬件出厂标定的静态频率响应曲线,不是实时采集的音频频率
空指针修复方案
  1. 修正权限判断逻辑,将权限判断条件的&&改为||,确保录音、存储权限任意一个未授权时都发起申请,等用户授权完成后再执行麦克风相关初始化。
  2. 通过AudioManager获取麦克风信息列表,对miMicrofono变量赋值,核心代码如下:
// 在onCreate中初始化AudioManager,等权限授予后执行以下逻辑
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    List<MicrophoneInfo> micList = audioManager.getMicrophones();
    if (micList != null && !micList.isEmpty()) {
        // 索引0一般为设备内置主麦克风,可根据getLocation()返回值筛选需要的麦克风
        miMicrofono = micList.get(0);
        // 此处即可正常调用getId()、getLocation()、getFrequencyResponse()等方法
        // 例:获取静态频率响应参数
        List<MicrophoneInfo.FrequencyResponse> staticFreqResponse = miMicrofono.getFrequencyResponse();
    }
}
实时检测麦克风拾取音频频率实现方案

不需要启动MediaRecorder录制文件,也不需要播放音频,通过AudioRecord读取内存中的裸音频流,配合FFT(快速傅里叶变换)计算即可得到实时频率,实现步骤如下:

  • 仅需申请RECORD_AUDIO权限,无需存储权限(全程不写入本地文件)
  • 初始化AudioRecord实例,配置为44100Hz采样率、单声道、16位PCM格式,分配最小缓冲区大小,启动采集后仅在内存中读取音频帧,不做存储
  • 在子线程中循环读取音频缓冲区数据,对数据做加窗处理后执行FFT变换,计算各频点的幅值,幅值最高的频点即为当前麦克风拾取声音的主频率
  • 将计算结果回调到主线程更新UI,不需要检测时停止采集、释放AudioRecord资源即可

核心实现参考代码:

// 子线程内执行实时采集逻辑
private void startFrequencyDetect() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
        return;
    }
    int sampleRate = 44100;
    int channelConfig = AudioFormat.CHANNEL_IN_MONO;
    int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
    // FFT需要的样本点数取2的幂次,此处取1024/2048均可
    int fftSize = 2048;
    AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, channelConfig, audioFormat, Math.max(minBufferSize, fftSize*2));
    short[] audioBuffer = new short[fftSize];
    audioRecord.startRecording();

    // 引入JTransforms库做FFT计算,也可替换为自行实现的轻量FFT算法
    FloatFFT_1D fft = new FloatFFT_1D(fftSize);
    float[] fftData = new float[fftSize];
    boolean isRunning = true;

    new Thread(() -> {
        while (isRunning) {
            int readSize = audioRecord.read(audioBuffer, 0, fftSize);
            if (readSize <=0) continue;
            // 填充FFT数据,加汉宁窗减少频谱泄漏
            for (int i = 0; i < fftSize; i++) {
                double window = 0.5 * (1 - Math.cos(2 * Math.PI * i / (fftSize - 1)));
                fftData[i] = (float) (audioBuffer[i] / 32768.0 * window);
            }
            fft.realForward(fftData);
            // 计算各频点幅值,找到最大幅值对应的频率
            float maxMagnitude = 0;
            int maxIndex = 0;
            for (int i = 2; i < fftSize/2; i++) {
                float real = fftData[2*i];
                float imag = fftData[2*i+1];
                float magnitude = (float) Math.sqrt(real*real + imag*imag);
                if (magnitude > maxMagnitude) {
                    maxMagnitude = magnitude;
                    maxIndex = i;
                }
            }
            // 计算得到当前主频率
            float currentFreq = maxIndex * sampleRate * 1.0f / fftSize;
            // 切到主线程更新UI
            runOnUiThread(() -> infoMicrofono.setText("当前实时频率:" + currentFreq + "Hz"));
        }
        audioRecord.stop();
        audioRecord.release();
    }).start();
}

注:getFrequencyResponse()返回的是硬件静态参数,和实时采集的音频频率无关,不需要启动录制就可以直接调用,实时频率必须通过音频流+FFT计算得到。

内容的提问来源于stack exchange,提问作者Diego C

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 02:30:53