Android中的实时音频处理

36

我正在尝试编写一个应用程序,可以实时解码音频莫尔斯电码。我找到了这份文档,其中介绍了如何在Android上从麦克风录制音频。我想知道的是是否可以访问麦克风的原始输入数据,还是必须将其写入/读取到文件中。

谢谢。


保罗,你曾经做过这个项目吗?我正在研究类似的“家庭”项目,并且对实时处理传入音频很感兴趣...也许需要使用本地库来获得足够的性能?如果你想要的话,请给我发电子邮件,地址是andrew@mackenzie-serres.net。谢谢! - Andrew Mackenzie
5个回答

28
如果使用上面的示例中的MediaRecorder,它会将压缩的音频保存到文件中。如果使用AudioRecord,则可以直接获取音频样本。是的,你想要做的应该是可能的。

你能帮我解决 https://stackoverflow.com/questions/61184352/android-accessibility-service-real-time-audio-processing 这个问题吗? - Feroz Siddiqui
为了更好地理解音频处理,您可以访问此参考链接: Android-Audio-Processing-Using-WebRTC - Muhammad Usman Bashir

7

有一个来自MIT媒体实验室的感知框架叫做Funf:http://code.google.com/p/funf-open-sensing-framework/
他们已经创建了用于音频输入和一些分析(如FFT等)的类,文件保存或上传也已实现,而且他们处理了手机上可用的大多数传感器。
你还可以从他们编写的代码中获得灵感,我认为那非常好。


6
使用AudioRecord有些过度了。只需每1000毫秒检查MediaRecorder.getMaxAmplitude()以区分响声和静音。
如果你真的需要分析波形,那么是的,你需要使用AudioRecord。获取原始数据并计算你关心的原始字节部分的均方根以获得音量感觉。
但是,既然MediaRecorder.getMaxAmplitude()使用起来更加容易,为什么要这么做呢?
请参见我在这个问题中给出的代码。

3
1000毫秒等于1秒,这似乎对于解析莫尔斯电码来说并不够频繁。 - StockB
3
除非你开始录音,否则你不能使用getMaxAmplitude()(其实你可以,但你总是会得到0)。因此,你仍然需要录制一个文件,这可能会无限增长。绝对不是一个解决办法。 - matteo
你能帮我解决 https://stackoverflow.com/questions/61184352/android-accessibility-service-real-time-audio-processing 这个问题吗? - Feroz Siddiqui

1
我找到了一个方法来实现它。基本上,你需要在其中运行一个新线程,在该线程中不断调用myAndroidRecord.read()。在此调用之后,循环遍历缓冲区中的所有条目,然后可以逐个实时查看原始值。下面是主活动的代码示例。
package com.example.mainproject;

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


import android.content.pm.PackageManager;
import android.Manifest;

import android.content.Context;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.widget.TextView;
import android.media.AudioManager;
import android.media.AudioFormat;
import android.os.Bundle;



import java.util.Arrays;

public class MainActivity extends AppCompatActivity {

    private AudioManager myAudioManager;
    private static final int REQUEST_RECORD_AUDIO_PERMISSION = 200;
    // Requesting permission to RECORD_AUDIO
    private boolean permissionToRecordAccepted = false;
    private String [] permissions = {Manifest.permission.RECORD_AUDIO};

    private static final int PERMISSION_RECORD_AUDIO = 0;
    Thread mThread;

    @Override
    public void onRequestPermissionsResult(int requestCode,  String[] permissions,  int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode){
            case REQUEST_RECORD_AUDIO_PERMISSION:
                permissionToRecordAccepted  = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                break;
        }
        if (!permissionToRecordAccepted ) finish();

    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if(ContextCompat.checkSelfPermission(this,Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED){
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.RECORD_AUDIO)) {
                // Show an explanation to the user *asynchronously* -- don't block
                // this thread waiting for the user's response! After the user
                // sees the explanation, try again to request the permission.
                ActivityCompat.requestPermissions(this,
                        new String[] { Manifest.permission.RECORD_AUDIO },
                        PERMISSION_RECORD_AUDIO);
                return;
            } else {
                // No explanation needed; request the permission
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.RECORD_AUDIO},
                        1);
                ActivityCompat.requestPermissions(this,
                        new String[] { Manifest.permission.RECORD_AUDIO },
                        PERMISSION_RECORD_AUDIO);

                // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
                // app-defined int constant. The callback method gets the
                // result of the request.
            }
        }else{

            myAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
            String x = myAudioManager.getProperty(AudioManager.PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED);

            runOnUiThread(()->{
                TextView tvAccXValue = findViewById(R.id.raw_available);
                tvAccXValue.setText(x);
            });

            mThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    record();
                }
            });
            mThread.start();
        }
    }

    private void record(){
        int audioSource = MediaRecorder.AudioSource.MIC;
        int samplingRate = 11025;
        int channelConfig = AudioFormat.CHANNEL_IN_DEFAULT;
        int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
        int bufferSize = AudioRecord.getMinBufferSize(samplingRate,channelConfig,audioFormat);

        short[] buffer = new short[bufferSize/4];
        AudioRecord myRecord = new AudioRecord(audioSource,samplingRate,channelConfig,audioFormat,bufferSize);

        myRecord.startRecording();

        int noAllRead = 0;
        while(true){
            int bufferResults = myRecord.read(buffer,0,bufferSize/4);
            noAllRead += bufferResults;
            int ii = noAllRead;
            for (int i = 0;i<bufferResults;i++){
                int val = buffer[i];
                runOnUiThread(()->{
                    TextView raw_value = findViewById(R.id.sensor_value);
                    raw_value.setText(String.valueOf(val));
                    TextView no_read = findViewById(R.id.no_read_val);
                    no_read.setText(String.valueOf(ii));
                });
            }

        }
    }
}

这只是一个演示,在实际应用中,您需要更加深入地思考何时以及如何停止运行的线程。此示例会无限期地运行,直到您退出应用程序。

与UI更新相关的代码,例如TextView raw_value = findViewById(R.id.sensor_value);,是特定于此示例的,您应该定义自己的代码。

int ii = noAllRead;int val = buffer[i];是必要的,因为Java不允许您在lambda方法中放置非有效最终变量。


-2

看起来必须先将其转储到文件中。

如果你查看android.media.AudioRecord source, 原生音频数据字节缓冲区不会暴露给公共API。

根据我的经验,在为Android构建音频合成器时,很难实现实时性能并保持音频保真度。然而,摩尔斯电码的“翻译器”肯定是可行的,并且听起来是一个有趣的小项目。祝好运!


你认为为什么音频缓冲区没有传递到Java?read()方法呢? - dmazzoni
@Error454,您能否举个例子直接读取值而不是文件转储?我目前也在遇到同样的问题。谢谢! - Amuoeba
1
@Amuoeba,你可以使用AudioRecord并定期调用read(...)来获取原始音频数据。 - Error 454
@Error454 是的,我在看到你的回复之前就已经解决了。很遗憾我没有早点看到它,这会浪费我相当多的时间。我也发布了一个带有示例代码的答案供其他人参考。谢谢! - Amuoeba

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接