音視頻編解碼——LAME


一、LAME簡介

LAME是目前非常優秀的一種MP3編碼引擎,在業界,轉碼成Mp3格式的音頻文件時,最常用的就是LAME庫。當達到320Kbit/s時,LAME編碼出來的音頻質量幾乎可以和CD的音質相媲美,並且還能保證整個音頻文件的體積非常小,因此若要在移動端平台上編碼MP3文件,使用LAME便成為唯一的選擇。

二、使用場景

操作系統:Android。

場景:

1.錄音時保存Mp3格式的文件     

2. 將wav無損音頻文件轉碼成mp3這種體積相對較小的音頻文件。   

3.可以將獲取到的音頻流進行錄制保存為mp3格式。 

附:如何錄制wav文件,在之前的博客里面我們講過:Android 音視頻開發(二):使用 AudioRecord 采集音頻PCM並保存到文件

三、開發准備

LAME的源碼是托管到sourceforge.net上的,我們開發一個基於LAME的項目,就不得不下載其源碼用於編譯。

LAME主頁:http://lame.sourceforge.net/

LAME下載:http://sourceforge.net/projects/lame/files/lame/3.99/

如果需要集成到Android系統上,就需要開發者具備一些NDK開發的能力。

四、開發過程

下面針對Android使用Lame做了基本的封裝,供實際開發過程中進行參考:

首先,在java類中定義native方法。

    private static native long nInit(int inSampleRate, int inChannels, int outSampleRate, int outBitrate, int model, int quality);

    private static native int nGetVersion(long lamePtr);

    private static native int mGetMp3bufferSize(long lamePtr);

    private static native int mGetMp3bufferSizeWithSamples(long lamePtr, int samples);

    private static native int nEncodeShortInterleaved(long lamePtr, short[] bufLR, int samples, byte[] outMp3buf);

    private static native int nEncodeShort(long lamePtr, short[] bufL, short[] bufR, int samples, byte[] outMp3buf);

    private static native int nFlush(long lamePtr, byte[] outBuf);

    private static native void nClose(long lamePtr);

生成相應的.h的頭文件,並實現該頭文件,完成整體邏輯的編寫。

#include <jni.h>
#include <cwchar>
#include <math.h>
#include "com_renhui_lame_Lame.h"
#include "libmp3lame/lame.h"

extern "C"


JNIEXPORT jlong JNICALL Java_com_renhui_lame_Lame_nInit(JNIEnv *env, jclass type, jint inSampleRate, jint inChannels, 
    jint outSampleRate, jint outBitrate, jint model, jint quality) { lame_global_flags
*lameFlags; lameFlags = lame_init(); lame_set_in_samplerate(lameFlags, inSampleRate); lame_set_num_channels(lameFlags, inChannels); lame_set_out_samplerate(lameFlags, outSampleRate); lame_set_brate(lameFlags, outBitrate); lame_set_mode(lameFlags, (MPEG_mode) model); lame_set_quality(lameFlags, quality); int code = lame_init_params(lameFlags); if (code != 0) { lame_close(lameFlags); return code; } return (long) lameFlags; } JNIEXPORT jint JNICALL Java_com_renhui_lame_Lame_nGetVersion(JNIEnv *env, jclass type, jlong lamePtr) { lame_global_flags *lameFlags; lameFlags = (lame_global_flags *) lamePtr; return lame_get_version(lameFlags); } JNIEXPORT jint JNICALL Java_com_renhui_lame_Lame_mGetMp3bufferSize(JNIEnv *env, jclass type, jlong lamePtr) { lame_global_flags *lameFlags; lameFlags = (lame_global_flags *) lamePtr; return lame_get_size_mp3buffer(lameFlags); } JNIEXPORT jint JNICALL Java_com_renhui_lame_Lame_mGetMp3bufferSizeWithSamples(JNIEnv *env, jclass type, jlong lamePtr, jint samples) { lame_global_flags *lameFlags; lameFlags = (lame_global_flags *) lamePtr; int version = lame_get_version(lameFlags); int bitrate = lame_get_brate(lameFlags); int sampleRate = lame_get_out_samplerate(lameFlags); float p = (bitrate / 8.0f) / sampleRate; if (version == 0) { // MPEG2: num_samples*(bitrate/8)/samplerate + 4*576*(bitrate/8)/samplerate + 256 return (jint) ceil(samples * p + 4 * 576 * p + 256); } else if (version == 1) { // MPEG1: num_samples*(bitrate/8)/samplerate + 4*1152*(bitrate/8)/samplerate + 512 return (jint) ceil(samples * p + 4 * 1152 * p + 512); } else { return (jint) ceil((1.25 * samples + 7200)); } } JNIEXPORT jint JNICALL Java_com_renhui_lame_Lame_nEncodeShortInterleaved(JNIEnv *env, jclass type, jlong lamePtr, jshortArray bufLR_, jint samples, jbyteArray outMp3buf_) { lame_global_flags *lameFlags; lameFlags = (lame_global_flags *) lamePtr; jshort *bufLR = env->GetShortArrayElements(bufLR_, NULL); jbyte *outMp3buf = env->GetByteArrayElements(outMp3buf_, NULL); const jsize outMp3bufSize = env->GetArrayLength(outMp3buf_); int result = lame_encode_buffer_interleaved(lameFlags, bufLR, samples, (u_char *) outMp3buf, outMp3bufSize); env->ReleaseShortArrayElements(bufLR_, bufLR, 0); env->ReleaseByteArrayElements(outMp3buf_, outMp3buf, 0); return result; } JNIEXPORT jint JNICALL Java_com_renhui_lame_Lame_nEncodeShort(JNIEnv *env, jclass type, jlong lamePtr, jshortArray bufL_, jshortArray bufR_, jint samples, jbyteArray outMp3buf_) { lame_global_flags *lameFlags; lameFlags = (lame_global_flags *) lamePtr; jshort *bufL = env->GetShortArrayElements(bufL_, NULL); jshort *bufR = env->GetShortArrayElements(bufR_, NULL); jbyte *outMp3buf = env->GetByteArrayElements(outMp3buf_, NULL); const jsize outMp3bufSize = env->GetArrayLength(outMp3buf_); int result = lame_encode_buffer(lameFlags, bufL, bufR, samples, (u_char *) outMp3buf, outMp3bufSize); env->ReleaseShortArrayElements(bufL_, bufL, 0); env->ReleaseShortArrayElements(bufR_, bufR, 0); env->ReleaseByteArrayElements(outMp3buf_, outMp3buf, 0); return result; } JNIEXPORT jint JNICALL Java_com_renhui_lame_Lame_nFlush(JNIEnv *env, jclass type, jlong lamePtr, jbyteArray outBuf_) { lame_global_flags *lameFlags; lameFlags = (lame_global_flags *) lamePtr; jbyte *outBuf = env->GetByteArrayElements(outBuf_, NULL); const jsize outBufSize = env->GetArrayLength(outBuf_); int result = lame_encode_flush(lameFlags, (u_char *) outBuf, outBufSize); env->ReleaseByteArrayElements(outBuf_, outBuf, 0); return result; } JNIEXPORT void JNICALL Java_com_renhui_lame_Lame_nClose(JNIEnv *env, jclass type, jlong lamePtr) { lame_global_flags *lameFlags; lameFlags = (lame_global_flags *) lamePtr; lame_close(lameFlags); }

編寫Android.mk和Application.mk,為ndk-build打包做准備。

Android.mk:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE :=mp3lame
LAME_LIBMP3_DIR :=libmp3lame
LOCAL_SRC_FILES :=\
$(LAME_LIBMP3_DIR)/bitstream.c \
$(LAME_LIBMP3_DIR)/fft.c \
$(LAME_LIBMP3_DIR)/id3tag.c \
$(LAME_LIBMP3_DIR)/mpglib_interface.c \
$(LAME_LIBMP3_DIR)/presets.c \
$(LAME_LIBMP3_DIR)/quantize.c \
$(LAME_LIBMP3_DIR)/reservoir.c \
$(LAME_LIBMP3_DIR)/tables.c  \
$(LAME_LIBMP3_DIR)/util.c \
$(LAME_LIBMP3_DIR)/VbrTag.c \
$(LAME_LIBMP3_DIR)/encoder.c \
$(LAME_LIBMP3_DIR)/gain_analysis.c \
$(LAME_LIBMP3_DIR)/lame.c \
$(LAME_LIBMP3_DIR)/newmdct.c \
$(LAME_LIBMP3_DIR)/psymodel.c \
$(LAME_LIBMP3_DIR)/quantize_pvt.c \
$(LAME_LIBMP3_DIR)/set_get.c \
$(LAME_LIBMP3_DIR)/takehiro.c \
$(LAME_LIBMP3_DIR)/vbrquantize.c \
$(LAME_LIBMP3_DIR)/version.c \
com_renhui_lame_Lame.cpp
LOCAL_C_INCLUDES += $(LOCAL_PATH)/mp3lame
LOCAL_LDLIBS := -llog -lz
include $(BUILD_SHARED_LIBRARY)

Application.mk:

APP_ABI := all
#APP_ABI := armeabi armeabi-v7a x86

# APP_ABI :=armeabi
APP_PLATFORM := android-14

附:有關編譯語法,整理了一篇文章,供大家參考:Android NDK學習(二):編譯腳本語法Android.mk和Application.mk

五、Lame重點API說明

1. lame_init()

lame_init() 用於初始化lame引擎,初始化完成后可以設置輸入的相關參數:比特率、通道數。

注意:這些參數需要了解清楚需求后進行設置,否則轉碼出來的音頻可能出現時長或者播放的問題。

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);

 2. lame_encode_buffer()和 lame_encode_buffer_interleaved()

if (channels == 2) {
  write = lame_encode_buffer_interleaved(gfp, input_buffer, read, mp3_buffer,MP3BUFSIZE);//立體聲用此方法編碼
} else if (channels == 1) {
  write= lame_encode_buffer(gfp, input_buffer, input_buffer, read,mp3_buffer, MP3BUFSIZE);//單聲道
}

3. lame_mp3_tags_fid(gfp,outfp)

在lame_encode_flush(gfp,mp3_buffer, sizeof(mp3_buffer))方法之后,lame_close(gfp)之前調用lame_mp3_tags_fid(gfp,outfp)方法為MP3文件添加vbr頭,播放器才能正讀取時間。

六、思維拓展

1. 錄音為Mp3格式:(代碼已轉private)

https://github.com/renhui/LameAndroid-master/tree/master/lameRecordMp3

2. 將wav格式的音頻文件轉碼為Mp3格式:(代碼已轉private)

https://github.com/renhui/LameAndroid-master/tree/master/lameWav2Mp3

3. 結合NBPlayer,將Player的PCM數據保存到Mp3文件里

Doing...

 

推薦資料:

Android錄制音頻並使用Lame轉成mp3

LameMp3開發問題解決方案錦集(安卓ndk)

Lame實時mp3編碼錄音過程中的pcm數據

iOS 使用 Lame 轉碼 MP3 的最正確姿勢

iOS-使用Lame轉碼:PCM->MP3

Android 使用 lame wav 轉 mp3 、pcm 轉 mp3 (邊錄邊轉);使用 mad mp3 轉 wav、mp3 轉 pcm (邊播邊轉)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM