Android录音mp3格式实例详解

Android录音支持的格式有amr、aac,但这两种音频格式在跨平台上表现并不好。
MP3显然才是跨平台的最佳选择。

项目地址

GavinCT/AndroidMP3Recorder

实现思路概述

在分析代码前,我们需要明确几个问题

1. 如何最终生成MP3

实现MP3格式最好是借助Lame这个成熟的解决方案。
对于Android来说,需要借助JNI来调用Lame的C语言代码,实现音频格式的转化。

2. 如何获取最初的音频数据

AudioRecord类可以直接帮助我们获取音频数据。

3. 如何进行转换

网上有代码是先录制后转为MP3,这种效率比较低。因为如果录音时间过长,转换时间就会相应变长,用户在存储录音时需要等待的时间就会变长。
Samsung Developers先录后转示例代码
显然,这种方案是不可取的。
我们需要的是边录边转的实现方式,这样在停止录音进行存储的时候,就不会花费太长时间。

实现代码介绍

既然是录音,我们上面也提到了需要使用AudioRecord类,我们就从这个类的构造器开始说起

构造器

public AudioRecord (int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)

构造器参数很多,我们一点一点来看:

  • audioSource : 声源,一般使用MediaRecorder.AudioSource.MIC表示来自于麦克风
  • sampleRateInHz :官方明确说到只有44100Hz是所有设备都支持的。其他22050、16000和11025只能在某些设备上使用。
  • channelConfig : 有立体声(CHANNEL_IN_STEREO)和单声道(CHANNEL_IN_MONO)两种。但只有单声道(CHANNEL_IN_MONO)是所有设备都支持的。
  • audioFormat : 有ENCODING_PCM_16BIT和ENCODING_PCM_8BIT两种音频编码格式。同样的,官方声明只有ENCODING_PCM_16BIT是所有设备都支持的。
  • bufferSizeInBytes : 录音期间声音数据的写入缓冲区大小(单位是字节)。

其实从上面的解释可以看到,类的参数很多,但为了保证在所有设备上可以使用,我们真正需要填写的只有一个参数:bufferSizeInBytes,其他都可以使用通用的参数而不用自己费心来选择。
在深究bufferSizeInBytes该传入什么之前,我们先略过这一段,先来说一下录音的读取与转换。

录音的读取与转换策略

录音的读取其实和UDP差不多,需要不断的读取数据。
既然是不断,那么我们当然需要循环读取,意味着我们需要一个线程来单独读取录音,避免阻塞主线程。
还和UDP差不多的是,如果不及时读取,数据超过缓冲区大小,会造成这段录音数据的丢失。
上面提到过,我们想要实现的是边录边转。那么问题来了,如果我们读取完数据后接着将数据传给Lame进行MP3编码,Lame的编码时间是不确定的,是不是有可能造成数据的丢失呢?
答案当然是有可能,所以我们不能巧合编程。
我们需要另外一个线程,即数据编码线程来专门进行MP3编码,而当前的录音读取线程只负责读取录音PCM数据。
有了两条线程,我们还需要确认一点,什么时候编码线程开始处理数据?

编码线程处理数据的时机

传统的方法是当线程中有数据的时候开始处理,这就需要在这个线程里面不断循环查看是否有数据需要处理,有数据就开始处理,没有数据我们可以暂时休息几毫秒(当然一直不sleep也可以,但造成的系统消耗太多)。
这种方式显然也是低效的,因为无论我们让线程休息多久都可以判定为不合理。因为我们并不知道准确的时间。
那么还有别的方法么?
显然录音这个类是知道什么时候该处理数据,什么时候可以休息。
Don't call me , I will call you.
是的,我们应该去看看有没有监听器,让录音来通知编码线程开始工作。
AudioRecord为我们提供了这样的方法:

public int setPositionNotificationPeriod (int periodInFrames)

Added in API level 3
Sets the period at which the listener is called, if set with setRecordPositionUpdateListener(OnRecordPositionUpdateListener) or setRecordPositionUpdateListener(OnRecordPositionUpdateListener, Handler). It is possible for notifications to be lost if the period is too small.

设置通知周期。 以帧为单位。

到这里,我们可以回来来解释bufferSizeInBytes大小的传入了。

缓冲区的大小

其实AudioRecord类提供了一个方便的方法getMinBufferSize来获取缓冲区的大小。

public static int getMinBufferSize (int sampleRateInHz, int channelConfig, int audioFormat)

这里的3个参数,其实我们都可以从构造器的参数里看到,因此传入并没有什么问题。
但关键在如上面我们设置了周期单位,如果获得的缓冲区大小不是周期单位的整数倍呢?
不是整数倍当然会如我们猜想的一样造成数据丢失,因此我们还需要一些数据的纠正来保证缓冲区大小是整数倍。

mBufferSize = AudioRecord.getMinBufferSize(DEFAULT_SAMPLING_RATE,
    DEFAULT_CHANNEL_CONFIG, DEFAULT_AUDIO_FORMAT.getAudioFormat());

int bytesPerFrame = DEFAULT_AUDIO_FORMAT.getBytesPerFrame();
/* Get number of samples. Calculate the buffer size
 * (round up to the factor of given frame size)
 * 使能被整除,方便下面的周期性通知
 * */
int frameSize = mBufferSize / bytesPerFrame;
if (frameSize % FRAME_COUNT != 0) {
  frameSize += (FRAME_COUNT - frameSize % FRAME_COUNT);
  mBufferSize = frameSize * bytesPerFrame;
}

讲完了数据的获取线程和编码线程,我们来仔细看看帮助我们实现MP3编码的功臣:Lame

Lame的获取与编译

Lame在线下载地址

步骤

解压libmp3lame 到jni目录.
拷贝 lame.h (include目录下)
创建Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE  := mp3lame
LOCAL_SRC_FILES := bitstream.c fft.c id3tag.c mpglib_interface.c presets.c quantize.c  reservoir.c tables.c util.c VbrTag.c encoder.c gain_analysis.c lame.c newmdct.c  psymodel.c quantize_pvt.c set_get.c takehiro.c vbrquantize.c version.c
include $(BUILD_SHARED_LIBRARY)

删除非.c/.h文件:GNU autotools, Makefile.am Makefile.in libmp3lame_vc8.vcproj logoe.ico depcomp, folders i386 等无用文件。
编辑 jni/utils.h。把extern ieee754_float32_t fast_log2(ieee754_float32_t x);替换为extern float fast_log2(float x);。如果忘了替换,编译时会报出以下错误:

[armeabi] Compile thumb : mp3lame <= bitstream.c
In file included from jni/bitstream.c:36:0:
jni/util.h:574:5: error: unknown type name 'ieee754_float32_t'
jni/util.h:574:40: error: unknown type name 'ieee754_float32_t'
make.exe: *** [obj/local/armeabi/objs/mp3lame/bitstream.o] Error 1

编译库文件。可能会报出警告,忽略即可。

Lame需要对外提供的方法

  • init 初始化
  • inSamplerate : 输入采样频率 Hz
  • inChannel : 输入声道数
  • outSamplerate : 输出采样频率 Hz
  • outBitrate : Encoded bit rate. KHz
  • quality : MP3音频质量。0~9。 其中0是最好,非常慢,9是最差。

推荐:

2 :near-best quality, not too slow
5 :good quality, fast
7 :ok quality, really fast

private static final int DEFAULT_LAME_MP3_QUALITY = 7;
/**
 * 与DEFAULT_CHANNEL_CONFIG相关,因为是mono单声,所以是1
 */
private static final int DEFAULT_LAME_IN_CHANNEL = 1;
/**
 * Encoded bit rate. MP3 file will be encoded with bit rate 32kbps
 */
private static final int DEFAULT_LAME_MP3_BIT_RATE = 32;

/*
* Initialize lame buffer
* mp3 sampling rate is the same as the recorded pcm sampling rate
* The bit rate is 32kbps
*
*/
LameUtil.init(DEFAULT_SAMPLING_RATE, DEFAULT_LAME_IN_CHANNEL, DEFAULT_SAMPLING_RATE, DEFAULT_LAME_MP3_BIT_RATE, DEFAULT_LAME_MP3_QUALITY);

encode

  • bufferLeft : 左声道数据
  • bufferRight:右声道数据
  • samples :每个声道输入数据大小
  • mp3buf :用于接收转换后的数据。7200 + (1.25 * buffer_l.length)

这里需要解释一下:

Task task = mTasks.remove(0);
short[] buffer = task.getData();
int readSize = task.getReadSize();
int encodedSize = LameUtil.encode(buffer, buffer, readSize, mMp3Buffer);
  • 左右声道 :当前声道选的是单声道,因此两边传入一样的buffer。
  • 输入数据大小 :录音线程读取到buffer中的数据不一定是占满的,所以read方法会返回当前大小size,即前size个数据是有效的音频数据,后面的数据是以前留下的废数据。 这个size同样需要传入到Lame编码器中用于编码。
  • mp3的buffer:官方规定了计算公式:7200 + (1.25 * buffer_l.length)。(可以在lame.h文件中看到)

flush

将MP3结尾信息写入buffer中。
传入参数:mp3buf至少7200字节。这里还是用以前定义的mp3buf来传入,避免创建过多的数组。

close

关闭释放Lame

OK,到这里,核心的转换代码就完成了,我们再来点锦上添花的东西。

音量

一般我们在做录音的时候,都会有一个需求,根据音量的大小显示一个动画,让录音显得更生动一些。
当然,我在这个库里也提供了。
那么怎么来计算音量呢?
我参考了三星的音量计算。
总结如下:

/**
* 此计算方法来自samsung开发范例
*
* @param buffer
* @param readSize
*/
private void calculateRealVolume(short[] buffer, int readSize) {
  int sum = 0;
  for (int i = 0; i < readSize; i++) {
    sum += buffer[i] * buffer[i];
  }
  if (readSize > 0) {
    double amplitude = sum / readSize;
    mVolume = (int) Math.sqrt(amplitude);
  }
};

关于最大音量

其实对于音量,我不是特别明白。
最大音量在三星的代码中给出的是4000,但是我在实际的测试中发现,这个计算公式得出的音量大小一般都在1500以内。
因此在我提供的录音库里面,我把最大音量规定为了2000。
这块儿欢迎大家来提宝贵意见。

MP3录音实现参考

yhirano/Mp3VoiceRecorderSampleForAndroid
日本人写的,感觉他的判断不完善,有点巧合编程的意思,也或许是我没看懂。

talzeus/AndroidMp3Recorder
比较严谨的代码。主要依据这个库进行的修改。

存在的问题:

AudioRecord传入参数很多没有按Android规定传入。如采样频率使用了22050Hz。

使用了自己构造的RingBuffer,看这有点头晕。 我在库里使用List来存储未编码的音频数据,更容易理解。

没有提供音量大小。

您可能感兴趣的文章:

  • Android编程录音工具类RecorderUtil定义与用法示例
  • Android编程实现录音及保存播放功能的方法【附demo源码下载】
  • Android编程检测手机录音权限是否打开的方法
  • Android实现录音功能实现实例(MediaRecorder)
  • android语音即时通讯之录音、播放功能实现代码
  • Android6.0编程实现双向通话自动录音功能的方法详解
  • Android使用AudioRecord实现暂停录音功能实例代码
  • Android 录音与播放功能的简单实例
  • Android编程实现通话录音功能的方法
  • 利用libmp3lame实现在Android上录音MP3文件示例
  • Android录音--AudioRecord、MediaRecorder的使用
  • android 通过MediaRecorder实现简单的录音示例
  • Android使用MediaRecorder实现录音及播放
  • Android录音播放管理工具
  • Android实现拍照、录像、录音代码范例
  • Android实现自制和播放录音程序
  • Android中简单调用图片、视频、音频、录音和拍照的方法
  • Android编程开发录音和播放录音简单示例
(0)

相关推荐

  • Android编程检测手机录音权限是否打开的方法

    本文实例讲述了Android编程检测手机录音权限是否打开的方法.分享给大家供大家参考,具体如下: 6.0之前的权限检测只是检测到是否在清单文件中注册 Boolean flag = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.RECORD_AUDIO", "包名")); Boolean flag = PermissionChecker.checkSelfPer

  • Android 录音与播放功能的简单实例

     Android 录音与播放功能的简单实例 最近在研究Android中一些常用的功能,像地图.拍照.录音和播放的实现等等,还有一些侧滑.动画等是如何实现的. 今天就把录音和播放的实现分享一下,录音和播放比较简单,利用android内部的类即可实现. 1.先看下运行后的界面: 以下三张图分别是进入.录音.播放时的. 2.Layout布局文件 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout

  • 利用libmp3lame实现在Android上录音MP3文件示例

    之前项目需要实现MP3的录音,于是使用上了Lame这个库.这次做一个demo,使用AndroidStudio+Cmake+NDK进行开发.利用Android SDK提供的AndroidRecorder进行录音,得到PCM数据,并使用jni调用Lame这个C库将PCM数据转换为MP3文件.并使用MediaPlayer对录音的MP3文件进行播放.另外此次的按键是仿微信的语音按键,按下录音,松开结束,若中途上滑松开即取消 效果如下: 项目地址: LameMp3ForAndroid_jb51.rar 一

  • Android实现拍照、录像、录音代码范例

    本文主要介绍Android实现拍照.录像.录音代码的资料,这里整理了详细的代码,有需要的小伙伴可以参考下. RecordActivity.java package com.cons.dcg.collect; import java.io.File; import java.text.SimpleDateFormat; import java.util.*; import android.app.*; import android.content.Intent; import android.da

  • Android使用MediaRecorder实现录音及播放

    现在项目中有使用到音视频相关技术,在参考了网上各种大牛的资料及根据自己项目实际情况(兼容安卓6.0以上版本动态权限管理等),把声音录制及播放相关代码做个记录. public class MediaRecorderActivity extends BaseActivity { private Button start_tv; private ListView listView; //线程操作 private ExecutorService mExecutorService; //录音API pri

  • android 通过MediaRecorder实现简单的录音示例

    整理文档,搜刮出一个android 通过MediaRecorder实现简单的录音示例,稍微整理精简一下做下分享. MainActivity package com.centaur.collectvoice; import android.media.MediaRecorder; import android.os.Environment; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; impo

  • Android录音播放管理工具

    1.语音播放直接用系统工具就好了,这个就不多说了,根据传入的路径(网络路径或本地路径均可)播放音频文件 /** * Created by zhb on 2017/1/16. * 音乐在线播放 */ public class PlayManager { private Context mcontext; public PlayManager(Context context){ this.mcontext = context; } public void play(String song){ Med

  • Android实现自制和播放录音程序

    首先,让我们先看下实现的截图: 当有录音文件存在时,会显示在下面的ListView当中. 下面给出实现的完整代码: 1.主程序代码 package irdc.ex07_11; import java.io.File; import java.io.IOException; import java.util.ArrayList; import android.app.Activity; import android.content.Intent; import android.media.Medi

  • android语音即时通讯之录音、播放功能实现代码

    在android中,实现录音与语音播放的功能算是比较简单的,但是作为参考,还是很有必要将语音相关的知识做一个简要的记录. 首先,在android中,支持录音支持两种方式.主要包括:字节流模式和文件流模式.用文件流模式进行录音操作比较简单,而且相对来说,因为其封装性比较好,录制下的文件也会比较小.但是相对于文件流模式,就没有字节流模式那么灵活,但是想要用好字节流模式还是需要下一点功夫的. 下面开始介绍文件流模式的语音操作: 文件流模式 我们来看录音部分的实现,首先我们实现开始录音的部分: 在正式编

  • Android实现录音功能实现实例(MediaRecorder)

    本文介绍了Android实现录音的实例代码(MediaRecorder),分享给大家,具体如下: Android提供了两个API用于录音的实现:MediaRecorder 和 AudioRecord,各有优劣. 1.MediaRecorder 已经集成了录音.编码.压缩等,支持少量的录音音频格式,大概有.aac(API = 16) .amr .3gp 优点:大部分已经集成,直接调用相关接口即可,代码量小 缺点:无法实时处理音频:输出的音频格式不是很多,例如没有输出mp3格式文件 2.AudioR

  • Android编程录音工具类RecorderUtil定义与用法示例

    本文实例讲述了Android编程录音工具类RecorderUtil定义与用法.分享给大家供大家参考,具体如下: 以下工具类都是经过实战开发验证都是可以直接复制使用的. 录音工具类介绍: 录音工具类主要平时用来开发语音聊天的,在微信和QQ上该工具类都是常用的,因为语音聊天. 使用硬件一般都要开权限,别忘了.这里还需要搭配 Android FileUtil 类使用,为了方便才这么封装的 import android.media.MediaRecorder; import android.util.L

  • Android编程开发录音和播放录音简单示例

    本文实例讲述了Android编程开发录音和播放录音的方法.分享给大家供大家参考,具体如下: /* * The application needs to have the permission to write to external storage * if the output file is written to the external storage, and also the * permission to record audio. These permissions must be

  • Android6.0编程实现双向通话自动录音功能的方法详解

    本文实例讲述了Android6.0编程实现双向通话自动录音功能的方法.分享给大家供大家参考,具体如下: 项目中需要实现基于Android 6.0 的双向通话自动录音功能,在查阅相关android电话状态监听文章以及Git上的开源录音项目后,整理出此文 首先,介绍一下android 电话状态的监听(来电和去电): http://www.jb51.net/article/32433.htm 实现手机电话状态的监听,主要依靠两个类: TelephoneManger和PhoneStateListener

  • Android编程实现录音及保存播放功能的方法【附demo源码下载】

    本文实例讲述了Android编程实现录音及保存播放功能的方法.分享给大家供大家参考,具体如下: 在android中进行录音相对来说是比较简单的,使用系统提供的MediaRecorder类进行录音并保存,然后调用MediaPlayer进行播放.以下为xml配置文件代码: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas

  • Android使用AudioRecord实现暂停录音功能实例代码

    题外话:发现好久都没有上来写博文了,毕业设计加上公司暂时没有Android的项目做,只能去自学web上的知识,摸爬打滚到现在,花了一个多月时间根据公司的现有模板做了公司内部一个任务管理系统,感觉都是比较浅的知识,没什么可以写的.想到之前做的语音识别的项目,虽然现在没什么下文了,但是谁懂~~~将来呢? 言归正传,项目长这样子: 设计的思路: 由于自带的AudioRecord没有pauseRecord()方法,我把开始录音-->(暂停/继续录音)...-->停止录音叫做一次录音,点击一次暂停就会产

  • Android编程实现通话录音功能的方法

    本文实例讲述了Android编程实现通话录音功能的方法.分享给大家供大家参考,具体如下: 因受系统限制,只能录自已麦的声音,录不到对方的声音,可能需要改内核才能实现双向录音: 接通电话和挂断电话时,震动一下: 使用广播接收者实现自启动: 服务代码: package com.eboy.phoneListener; import java.io.File; import android.app.Service; import android.content.Context; import andro

  • Android录音--AudioRecord、MediaRecorder的使用

    Android提供了两个API用于实现录音功能:android.media.AudioRecord.android.media.MediaRecorder. 网上有很多谈论这两个类的资料.现在大致总结下: 1.AudioRecord 主要是实现边录边播(AudioRecord+AudioTrack)以及对音频的实时处理(如会说话的汤姆猫.语音) 优点:语音的实时处理,可以用代码实现各种音频的封装 缺点:输出是PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码

  • Android中简单调用图片、视频、音频、录音和拍照的方法

    本文实例讲述了Android中简单调用图片.视频.音频.录音和拍照的方法.分享给大家供大家参考,具体如下: //选择图片 requestCode 返回的标识 Intent innerIntent = new Intent(Intent.ACTION_GET_CONTENT); //"android.intent.action.GET_CONTENT" innerIntent.setType(contentType); //查看类型 String IMAGE_UNSPECIFIED =

随机推荐