【问题标题】:Record Raw Audio Bytes to Local Variable in Android将原始音频字节记录到 Android 中的局部变量
【发布时间】:2021-12-27 23:02:40
【问题描述】:

我在 Android 上将音频作为原始字节录制到一个变量中,该变量可由 AudioTrack 的实例播放。

也就是说,不是作为文件。

我选择了采样率为 44100 的 8 位单声道 PCM,因为该音频数据应该是未压缩的……并且是可预测的、与平台无关的格式。

别担心...我们在这里只处理小型音频 sn-ps...此外,现在人们拥有如此多的 RAM,以至于它是超高频的。

我已经尝试过下面的方法......虽然它显然有效,但相关部分 writeAudioToLocalVariable() 是来自互联网的修改后的剪切粘贴而且我对内容还不是很了解。

考虑到我们无法预测最终用户设备的硬件能力,是否有更博学的人告诉我,这是否是 writeAudioToLocalVariable() 函数中最稳定可靠的处理方式?

这是我得到的:

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

import android.Manifest;
import android.content.pm.PackageManager;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import java.io.ByteArrayOutputStream;

public class MainActivity extends AppCompatActivity {
    private static final int NOT_USED = 999999999;
    private static final int SAMPLERATE = 44100;
    private static final int REC_CHANNELS = AudioFormat.CHANNEL_IN_MONO;
    private static final int PLAYBACK_CHANNELS = AudioFormat.CHANNEL_OUT_MONO;
    private static final int ENCODING = AudioFormat.ENCODING_PCM_8BIT;
    // !!! needs to be large enough for all devices
    private static final int MY_CHOSEN_BUFFER_SIZE = 8192;
    private AudioRecord recorder = null;
    private Thread recordingThread = null;
    boolean isRecording = false;

    byte[] recordedAudioAsBytes; // <------ this is where recorded audio ends up

    AudioTrack player;

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

        int minBufferSize = AudioRecord.getMinBufferSize(SAMPLERATE,
                REC_CHANNELS, ENCODING);
        Log.i("ABC", "FYI, min buffer size for this device is : " + minBufferSize);
    }

    private void startRecording() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, NOT_USED);
            Log.i("ABC", "permission fail, returning.");
            return;
        }
        recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
                SAMPLERATE, REC_CHANNELS,
                ENCODING, MY_CHOSEN_BUFFER_SIZE);
        recorder.startRecording();
        isRecording = true;
        recordingThread = new Thread(new Runnable() {
            public void run() {
                Log.i("ABC", "the thread is running");
                writeAudioToLocalVariable();
            }
        }, "AudioRecorder Thread");
        recordingThread.start();
    }

    private void writeAudioToLocalVariable() {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] temporaryChunkOfBytes = new byte[MY_CHOSEN_BUFFER_SIZE];
        while (isRecording) {
            recorder.read(temporaryChunkOfBytes, 0, MY_CHOSEN_BUFFER_SIZE);
            try {
                System.out.println("Appending to baos : " + temporaryChunkOfBytes);
                //printBytes(temporaryChunkOfBytes);
                baos.write(temporaryChunkOfBytes, 0, MY_CHOSEN_BUFFER_SIZE);
            } catch (Exception e) {
                Log.i("ABC", "Exception while appending bytes : " + e); // <----- this is not called and that is good.
            }
        }
        recordedAudioAsBytes = baos.toByteArray();
    }

    private void stopRecording() {
        // stops the recording activity
        if (null != recorder) {
            isRecording = false;
            recorder.stop();
            recorder.release();
            recorder = null;
            recordingThread = null;
        }
    }

    public void recordButtonClicked(View v) {
        isRecording = true;
        startRecording();
    }

    public void stopButtonClicked(View v) {
        isRecording = false;
        stopRecording();
    }

    public void playButtonPressed(View v) {

        // this verifies that audio data exists as expected
        for (int i=0; i<recordedAudioAsBytes.length; i++) {
            Log.i("ABC", "byte[" + i + "] = " + recordedAudioAsBytes[i]);
        }

        // STREAM MODE ACTUALLY WORKS!! (STATIC MODE DOES NOT WORK)
        AudioTrack player = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLERATE, PLAYBACK_CHANNELS,
                ENCODING, MY_CHOSEN_BUFFER_SIZE, AudioTrack.MODE_STREAM);
        player.play();
        player.write(recordedAudioAsBytes, 0, recordedAudioAsBytes.length);

    }

}

谢谢你...来自我和许多其他人在未来丰富的 RAM !

【问题讨论】:

    标签: java android audio encoding audiorecord


    【解决方案1】:

    实现的想法是正确的,但它有一些错误。 ByteArrayOutputStream 是一个输出流,它将内存存储为以后可以抓取的字节,并根据需要处理不断增长的数组。您可以假设在最坏的情况下,这样做需要 2*sound_data_size 的存储空间(它可能需要在某个时候进行复制)。

    所有循环所做的只是从音频数据中一次读取 8K 并将其写入循环中的 baos 直到完成。我确实看到了一些错误——它可以在最后一次写入时写入额外的数据(读取可以返回少于完整缓冲区)。如果流在 isRecording 完成之前关闭(读取将立即返回 -1),它将快速循环,并且在这种情况下可能会向您发送 OOM。它可能会在接近尾声时丢失数据(如果循环变量在读取数据之前变为假)。试试这个:

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] temporaryChunkOfBytes = new byte[MY_CHOSEN_BUFFER_SIZE];
        int read = 0;
        while((read = recorder.read(temporaryChunkOfBytes, 0, MY_CHOSEN_BUFFER_SIZE) >= 0){
            try {
                System.out.println("Appending to baos : " + temporaryChunkOfBytes);
                //printBytes(temporaryChunkOfBytes);
                baos.write(temporaryChunkOfBytes, 0, read);
            } catch (Exception e) {
                Log.i("ABC", "Exception while appending bytes : " + e); // <----- this is not called and that is good.
            }
        }
        recordedAudioAsBytes = baos.toByteArray();
    

    使用此版本,您不应该只让线程始终运行。开始录制时启动线程。这修复了上述所有 3 个错误 - 如果读取返回 -1,它会中断循环。如果读取量小于缓冲区,则仅写入读取量。它会一直读取直到文件关闭,直到变量设置为 false 才会处理缓冲区中未读取的数据。

    您最终会遇到一些内存问题。基本上你每秒会占用 44K 的内存。如果你录制几秒钟,没问题。如果您要录制几分钟,则需要将其写入文件,然后在处理文件时流式传输。

    【讨论】:

    • 抱歉,如果你看到了这个的初始版本,我写完后发现了错误
    • 不用担心,感谢您的帮助!欣赏你的经历。 IDE告诉我“read = recorder.read(temporaryChunkOfBytes, 0, MY_CHOSEN_BUFFER_SIZE) >= 0”是一个整数,但应该是一个布尔值......所以我编辑了你的答案,而不是试图粘贴在这里...... lmk 如果有意义的话。
    • 我的和你的之间的一个细微差别是你的不能完全防止-1。如果 numBytesRead == -1 你不想 baos.write
    • 好的,我明白了...我撤消了编辑,但仍需要进行某种修改。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-08
    • 1970-01-01
    • 1970-01-01
    • 2016-12-14
    相关资源
    最近更新 更多