Android 录音详解(二)—— 录制 mp3 格式音频( lame 库的编译及使用)

2018-11-27 liuyingcong 安卓开发

        众多周知,mp3 是跨平台性最好的音频格式,由于采用了压缩率更高的有损压缩算法,文件大小是大约每分钟1M,使其在网络中传输更快,占用存储空间也更少;与此同时,它的声音质量也不错,尤其是人声(相声、评书、脱口秀),当然追求无损音乐的除外。

        Android 中没有提供录制 mp3 的 API,需要使用开源库 lame,lame 是专门用于编码 mp3 的轻量高效的 c 代码库。由于采用 c 语言编写,故需要用到 jni。且听我慢慢道来~

一、lame 库下载

    https://sourceforge.net/projects/lame/files/lame/

    我们使用最新的版本:3.100

二、lame 引入

    1、创建 jni 文件夹:

            首先把 Android Studio 中的项目切换到 Project 视图,在 main 目录下新建 jni 文件夹,在jni文件夹里面新建 lame 文件夹;

    2、复制要编译的文件:

            解压下载的 lame 库,打开 libmp3lame 文件夹,把里面所有后缀名为 .c .h 的文件(不包括两个文件夹下的)复制到上述新建的 lame 文件夹内;返回上一级,打开 include 文件夹,把 lame.h 文件同样复制到上述新建的 lame 文件夹内,此时 lame 文件夹内应包含 42 个文件。

    3、修改库文件:

            打开项目中刚刚拷贝过来的 util.h 文件,把 570 行的两处 ieee754_float32_t 改为 float

            打开 set_get.h 文件,把头部的 #include <lame.h> 改为 #include "lame.h"

            打开 fft.c 文件,删除第47行  #include "vector/lame_intrin.h"

    4、新建 Android.mk 文件
            在 jni 目录下新建文件 Android.mk,将下面代码拷贝到文件中

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

    5、新建 Application.mk 文件:
            在 jni 目录下新建文件 Application.mk,将下面代码拷贝到文件中:

APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
APP_CFLAGS += -DSTDC_HEADERS
APP_PLATFORM := android-19

三、编写 java 类和 c 文件

    1、新建 MP3Recorder 类:

            在项目包名(如 com.demo.lame)下新建 MP3Recorder 类,存放本地方法:

package com.demo.lame;

/**
 * 18.11.30 8:51 Yingcong Liu
 */
public class MP3Recorder {
    static {
     System.loadLibrary("mp3lame");   
  }
    /**
     * 初始化 lame编码器
     *
     * @param inSampleRate
     *              输入采样率
     * @param outChannel
     *              声道数
     * @param outSampleRate
     *              输出采样率
     * @param outBitrate
     *              比特率(kbps)
     * @param quality
     *              0~9,0最好
     */
    public static native void init(int inSampleRate, int outChannel, int outSampleRate, int outBitrate, int quality);
    
    /**
     *  编码,把 AudioRecord 录制的 PCM 数据转换成 mp3 格式
     *
     * @param buffer_l
     *          左声道输入数据
     * @param buffer_r
     *          右声道输入数据
     * @param samples
     *          输入数据的size
     * @param mp3buf
     *          输出数据
     * @return
     *          输出到mp3buf的byte数量
     */
    public static native int encode(short[] buffer_l, short[] buffer_r, int samples, byte[] mp3buf);
    
    /**
     *  刷写
     *
     * @param mp3buf
     *          mp3数据缓存区
     * @return
     *          返回刷写的数量
     */
    public static native int flush(byte[] mp3buf);
    
    /**
     * 关闭 lame 编码器,释放资源
     */
    public static native void close();
}

    2、生成 MP3Recorder.h

        打开Android studio的terminal 命令行,输入cd app/src/main/java 命令切换到java目录下,输入javah -o MP3Recorder.h com.demo.lame.MP3Recorder (注意,后面是MP3Recorder的全类名,修改成你的生成MP3Recorder.h文件,生成过程中有可能出现:

    错误: 编码GBK的不可映射字符

 不要在意(原因讲一下,JDK默认是用Unicode编码的,由于文件中出现了中文注释,Unicode不支持,故出现这种错误,强迫症者可以用javah -encoding UTF-8 -o MP3Recorder),同步一下文件:File - Sync with File System,就会在java文件夹下发现 MP3Recorder.h文件,把它移动到jni文件夹下;

    3、新建 MP3Recorder.c

        在jni目录下新建 MP3Recorder.c 文件,把下面代码拷贝到里面,注意改方法名,参照MP3Recorder中的方法名:


#include "lame/lame.h"
#include "MP3Recorder.h"

static lame_global_flags *glf = NULL;

JNIEXPORT void JNICALL Java_com_demo_myrecord2_MP3Recorder_init(JNIEnv *env, jobject instance, jint inSamplerate, jint outChannel, jint outSamplerate, jint outBitrate, jint quality) {
    if (glf != NULL) {
        lame_close(glf);
        glf = NULL;
    }
    glf = lame_init();
    lame_set_in_samplerate(glf, inSamplerate);
    lame_set_num_channels(glf, outChannel);
    lame_set_out_samplerate(glf, outSamplerate);
    lame_set_brate(glf, outBitrate);
    lame_set_quality(glf, quality);
    lame_init_params(glf);
}

JNIEXPORT jint JNICALL Java_com_demo_myrecord2_MP3Recorder_encode(JNIEnv *env, jobject instance, jshortArray buffer_l, jshortArray buffer_r, jint samples, jbyteArray mp3buf) {
    jshort* j_buffer_l = (*env)->GetShortArrayElements(env, buffer_l, NULL);

    jshort* j_buffer_r = (*env)->GetShortArrayElements(env, buffer_r, NULL);

    const jsize mp3buf_size = (*env)->GetArrayLength(env, mp3buf);
    jbyte* j_mp3buf = (*env)->GetByteArrayElements(env, mp3buf, NULL);

    int result = lame_encode_buffer(glf, j_buffer_l, j_buffer_r,
                                    samples, j_mp3buf, mp3buf_size);

    (*env)->ReleaseShortArrayElements(env, buffer_l, j_buffer_l, 0);
    (*env)->ReleaseShortArrayElements(env, buffer_r, j_buffer_r, 0);
    (*env)->ReleaseByteArrayElements(env, mp3buf, j_mp3buf, 0);

    return result;
}

JNIEXPORT jint JNICALL Java_com_demo_myrecord2_MP3Recorder_flush(JNIEnv *env, jobject instance, jbyteArray mp3buf) {
    const jsize mp3buf_size = (*env)->GetArrayLength(env, mp3buf);
    jbyte* j_mp3buf = (*env)->GetByteArrayElements(env, mp3buf, NULL);

    int result = lame_encode_flush(glf, j_mp3buf, mp3buf_size);

    (*env)->ReleaseByteArrayElements(env, mp3buf, j_mp3buf, 0);

    return result;
}

JNIEXPORT void JNICALL Java_com_demo_myrecord2_MP3Recorder_close(JNIEnv *env, jobject instance) {
    lame_close(glf);
    glf = NULL;
}

四、编译    

    1、确认是否有 NDK:
            打开 Android Studio 的 File - Settings,打开 Appearance & Behavior - System Settings - Android SDK,切换到 SDK Tools 便签,勾选 NDK 并下载;
    2、确认项目是否已关联 NDK:
            打开 File - Project Structure,在 Android NDK location 中填入 NDK 路径,如:E:\sdk\ndk-bundle;同时,要把该路径配置成环境变量,后面会用到。

    3、编译生成 so库

        打开Android studio的terminal命令行,切换到jni目录下,输入命令:ndk-build,执行完毕后同步一下文件,就可以看到libs文件夹了(细心的同学会发现还多了obj文件夹,里面的内容几乎和libs文件夹夹是相同的,只是它里面带有调试信息,没用,可以删掉),里面就是各平台的so库。

        如果编译失败,请下载最新版本的NDK试一下。

五、使用

    1、在 app 下的 build-gradle 文件 android 项目里添加:

    externalNativeBuild {
        ndkBuild {
            path file('src/main/jni/Android.mk')
        }
    }

    2、录音的代码:

    // 录音状态
    private boolean isRecording;
    
    //开始录音
    private void record() {
        new Thread() {
            @Override
            public void run() {
                // 音源
                int audioSource = MediaRecorder.AudioSource.MIC;
                // 采样率
                int sampleRate = 44100;
                // 声道
                int channelConfig = AudioFormat.CHANNEL_IN_MONO;//单声道
                // 采样位数
                int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
                // 录音缓存区大小
                int bufferSizeInBytes;
                // 文件输出流
                FileOutputStream fos;
                // 录音最小缓存大小
                bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
                AudioRecord audioRecord = new AudioRecord(audioSource, sampleRate, channelConfig, audioFormat, bufferSizeInBytes);
                try {
                    fos = new FileOutputStream(getExternalCacheDir() + "/demo.mp3");
                    MP3Recorder.init(sampleRate, 2, sampleRate, 128, 5);
                    short[] buffer = new short[bufferSizeInBytes];
                    byte[] mp3buffer = new byte[(int) (7200 + buffer.length * 1.25)];
                    audioRecord.startRecording();
                    isRecording = true;
                    while (isRecording && audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
                        int readSize = audioRecord.read(buffer, 0, bufferSizeInBytes);
                        if (readSize > 0) {
                            int encodeSize = MP3Recorder.encode(buffer, buffer, readSize, mp3buffer);
                            if (encodeSize > 0) {
                                try {
                                    fos.write(mp3buffer, 0, encodeSize);
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                    
                    int flushSize = MP3Recorder.flush(mp3buffer);
                    if (flushSize > 0) {
                        try {
                            fos.write(mp3buffer, 0, flushSize);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    audioRecord.stop();
                    audioRecord.release();
                    MP3Recorder.close();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
    // 停止录音
    private void stop() {
        isRecording = false;
    }

标签: 录音 Android lame mp3

网站备案号:京ICP备11043289号-1 北京市公安局网络备案 海1101084571
版权所有 北京育灵童科技发展有限公司 Copyright © 2002-2024 www.elight.cn, All Rights Reserved