将原始音频字节记录到 Android 中的局部变量
Posted
技术标签:
【中文标题】将原始音频字节记录到 Android 中的局部变量【英文标题】:Record Raw Audio Bytes to Local Variable in 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 !
【问题讨论】:
【参考方案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 好的,我明白了...我撤消了编辑,但仍需要进行某种修改。以上是关于将原始音频字节记录到 Android 中的局部变量的主要内容,如果未能解决你的问题,请参考以下文章
使用 java [重复] 确定给定一个字节 [] 记录的音频数据的频率