Existem duas implementações de gravação no Android, usando MediaRecorder e AudioRecord respectivamente. Infelizmente, nenhuma das duas implementações possui uma API de pausa.
1. A ideia do MediaRecorder para realizar a pausa é gerar um arquivo toda vez que você clicar em pausar, usar um array para salvar o caminho do arquivo, e ao final unir todos os arquivos correspondentes ao caminho no array para gerar um arquivo de gravação concluído, mas tente encontrar MediaRecorder Cada arquivo gravado tem um arquivo de cabeçalho e a voz mesclada não pode ser reproduzida normalmente sem saber o formato do arquivo de cabeçalho.
Finalmente, AudioRecord foi escolhido para implementação. AudioRecord suporta processamento de fluxo de arquivo em tempo real, e o arquivo de formato PCM original gravado é um arquivo vazio sem arquivos de cabeçalho de tarefa. Depois de mesclar os dados, é simples.
2. A ideia principal é ler continuamente o fluxo do arquivo de áudio enquanto a gravação está sendo gravada e adicionar conteúdo ao arquivo continuamente, parar de adicionar conteúdo ao arquivo após pressionar a pausa e continuar adicionando ao caminho getRecordSdcardPCMFile ao clicar em iniciar novamente o conteúdo até final da gravação
while (status == recodificação) {
audioRecord.read(b, 0, b.length);
//向文件中追加内容
mRandomAccessFile.seek(mRandomAccessFile.length());
mRandomAccessFile.write(b, 0, b.length);
//LogUtils.e("buff大小:" + b.length);
}
A seguir está o código de implementação completo
public class AudioRecoderTool {
public static int recodEnd =0;//Termina ou não inicia
public static int recoding =1;//正在录音
public static int recodPasue =2;//暂停中
//Entrada de áudio - microfone
private final static int AUDIO_INPUT = MediaRecorder.AudioSource.MIC;
//采用频率
//44100 é o padrão atual, mas alguns dispositivos ainda suportam 22050, 16000, 11025
//A frequência de amostragem é geralmente dividida em três níveis: 22,05KHz, 44,1KHz e 48KHz
private final static int AUDIO_SAMPLE_RATE =16000;
//声道 单声道 // 设置音频的录制的声道CHANNEL_IN_STEREO为双声道,CHANNEL_CONFIGURATION_MONO为单声道
private final static int AUDIO_CHANNEL = AudioFormat.CHANNEL_IN_STEREO;
//编码
private final static int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
// 缓冲区字节大小
private int bufferSizeInBytes =0;
//录音对象
private AudioRecord audioRecord;
//文件名
private String fileName;
//录音状态
private int status = recodEnd;
//线程池
private ExecutorService mExecutorService;
private OnAudioStatusUpdateListener audioStatusUpdateListener;
private long startTime;
public AudioRecoderTool() {
mExecutorService = Executors.newCachedThreadPool();
}
private void initAudio()
{
// obtém o tamanho do buffer em bytes
bufferSizeInBytes = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE,
AUDIO_CHANNEL, AUDIO_ENCODING);
audioRecord =new AudioRecord(AUDIO_INPUT, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING, bufferSizeInBytes);
}
private final Manipulador mHandler =novo Manipulador();
private Runnable mUpdateMicStatusTimer =new Runnable() {
public void run() {
if(status == recodificação)
atualizarMicStatus();
}
};
private int SPACE =100;// 间隔取样时间
/**
- Atualizar duração da gravação
*/
private void updateMicStatus() {
if (audioRecord !=null) {
if (null != audioStatusUpdateListener) {
audioStatusUpdateListener.onUpdate(0, System.currentTimeMillis() - startTime);
}
mHandler.postDelayed(mUpdateMicStatusTimer, ESPAÇO);
}
}
public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) {
this.audioStatusUpdateListener = audioStatusUpdateListener;
}
/**
-
comece a gravar
*/
public void startRecord(final String fileName) {
this.fileName = fileName;
if (audioRecord==null) {
initAudio();
}
if(status == recodificação)
{
LogUtils.e("Gravando~");
}
estado = recodificação;
Log.d("AudioRecorder", "===startRecord===" + audioRecord.getState());
/* 获取开始时间* */
startTime = System.currentTimeMillis();
updateMicStatus();
//使用线程池管理线程
mExecutorService.execute(new Runnable() {
@Sobrepor
public void run() {
writeDataTOFile(arquivo);
}
});
}
/**
- pausar gravação
*/
public void pauseRecord() {
status = recodPasue;
if(audioStatusUpdateListener !=null)
{
audioStatusUpdateListener.onUpdate(0,System.currentTimeMillis() - startTime);
audioStatusUpdateListener.onStop(fileName);
}
}
/**
- pare de gravar
*/
public void stopRecord() {
Log.d(“Gravador de Áudio”, “=stopRecord=”);
if (status == recodEnd) {
LogUtils.e("gravação não iniciada");
}else if(status == recodPasue){//暂停状态下直接释放资源
audioRecord.stop();
status = recodEnd;
release();
}else
{
//No estado de gravação, será liberado no método writeDataTOFile
status = recodEnd;
}
}
/**
- liberar recursos
*/
public void release() {
Log.d(“Gravador de Áudio”, “=liberar=”);
if (audioRecord !=null) {
audioRecord.release();
audioRecord =null;
}
if(audioStatusUpdateListener !=null)
{
audioStatusUpdateListener.onUpdate(0,System.currentTimeMillis() - startTime);
audioStatusUpdateListener.onStop(fileName);
}
status = recodEnd;
}
/**
- cancelar gravação
*/
public void canel() {
nomeArquivo =nulo;
if (audioRecord !=null) {
audioRecord.release();
audioRecord =null;
}
status = recodEnd;
}
/**
-
gravar informações de áudio no arquivo
*/
private void writeDataTOFile(String currentFileName) {
RandomAccessFile mRandomAccessFile =null;
try {
mRandomAccessFile =novo RandomAccessFile(novo Arquivo(
FileUtils.getRecordSdcardPCMFile(currentFileName)), “rw”);
byte[] b =new byte[bufferSizeInBytes /4];
//开始录制音频
audioRecord.startRecording();
//判断是否正在录制
status = recoding;
while (status == recoding) {
audioRecord.read(b, 0, b.length);
//向文件中追加内容
mRandomAccessFile.seek(mRandomAccessFile.length());
mRandomAccessFile.write(b, 0, b.length);
//LogUtils.e("buff大小:" + b.length);
}
// pare de gravar
audioRecord.stop();
mRandomAccessFile.close();
//释放资源
if(status == recodEnd)
liberar();
LogUtils.e("保存成功");
}catch (FileNotFoundException e) {
// TODO Bloco catch gerado automaticamente
e.printStackTrace();
}catch (IOException e) {
// TODO Bloco catch gerado automaticamente
LogUtils.e("异常关闭文件流");
e.printStackTrace();
}
}
interface pública OnAudioStatusUpdateListener {
/**
-
gravação...
-
@param db som atual decibel
-
@param tempo de gravação de tempo
*/
public void onUpdate(double db, long time);
/**
-
pare de gravar
-
@param filePath salvar caminho
*/
public void onStop(String filePath);
}
}
Como o arquivo gravado é um arquivo PCM, não pode ser reproduzido diretamente. Para ser compatível com IOS e considerar o tamanho do arquivo, o formato .aac com conteúdo menor ao mesmo tempo é selecionado.
A biblioteca android-aac-enc é usada aqui, você pode pesquisar e baixá-la no git.
Por ser uma biblioteca C, ela precisa do NDK para compilar a pasta jni antes de poder ser usada normalmente. O comando para gerar o arquivo de cabeçalho em jni está anexado aqui. Parece que o comando javah foi cancelado após a versão JDK10. Aqui , o arquivo de cabeçalho é gerado usando java xxxx.java -h . O comando gera o arquivo de cabeçalho .h e deve ser executado no diretório raiz do arquivo xxxx.java.
Arraste o arquivo .h gerado acima (o arquivo apontado pela seta vermelha abaixo) para o diretório raiz jni
O conteúdo do arquivo gerado se parece com isso
Em seguida, crie o arquivo c no diretório raiz jnl
Aqui eu criei aac-enc.c com o mesmo nome de arquivo da biblioteca original de jnl. Se você quiser criar o nome do arquivo você mesmo, modifique o nome do arquivo da seta vermelha na imagem abaixo no Android.mk.
Neste ponto, você pode usar o comando ndk-build para compilar e gerar o arquivo .os.
Em seguida, use o seguinte método para converter o arquivo de gravação PCM em um arquivo .aac
encoder.init(32000, 2, 16000, 16, FileUtils.getRecordSdcardAACFile(currentRptBean.getVoiceFile()));
//对二进制代码进行编码
encoder.encode(b);
//编码完成
encoder.uninit();