1. Analysis of Mp3 encoding format
MP3, full name of MPEG Audio Layer3, is an efficient computer Audio encoding scheme, it with a large compression ratio (1:10 to 1:12) to convert Audio files into smaller files with the extension name. MP3, and can basically maintain the sound quality of the original file. If you have a 4-minute CD-quality WAV audio with audio parameters of 44.1khz sampling, stereo, and sampling accuracy of 16 bits (2 bytes), then the audio takes up 441000*2(channel)*2(bytes)*60(seconds)*4(minutes)=40.4MB. For the MP3 format, MP3 audio takes up only about 4MB, which is good for storage and network transmission.
1.1 MP3 File Structure
An MP3 file consists of a frame, which is the smallest unit of an MP3 file. The MP3 audio file itself has no header, and when you want to read information about the MP3 audio file, you can read the header of the first frame, so you can cut any part of the MP3 audio file for proper playback. The entire MP3 file structure consists of three parts: TAG_V2(ID3V2), Frame, and TAG_V1(ID3V1). The details are as follows:
1.2 MP3 frame format
Each frame is independent and consists of a frame header, additional information, and sound data. Its length varies with the bit rate, and the playback time of each frame is usually 0.026 seconds. The structure of the MP3 frame is as follows:
Each frame header is 4 bytes (32 bits). Two bytes may be followed by the CRC check, depending on the 16th bit of the frame header. If it is 0, there is no check, and if it is 1, there is check. The frame header structure is as follows:
typedefstruct-tagHeader{
unsigned intSync:11position// Synchronize information
unsigned int version: 2; / / version
unsigned int layer: 2; / / layer
unsigned int error2protection: 1; / / CRC correction
unsigned int bit2rate2index: 4; // Bit rate index
unsigned int sample2rate2index: 2; // Sampling rate index
unsigned int padding: 1; / / empty words
unsigned int extension: 1; // Private flag
unsigned int channel2mode: 2; // Stereo mode
unsigned int modeextension: 2 ;/ / to keep
unsigned int copyright: 1; // Copyright mark
unsigned int original: 1; // Raw media
unsigned int emphasis: 2 ;// Emphasize the way
} HEADER;
Copy the code
Sync indicates the synchronization information, which contains 11 bits and is set to 1. Channel2mode is the stereo channel mode, accounting for 2 is, 11 is Single stereo (Mono); See this article for other parameters.
2. Lame compilation and encapsulation
Lame is an open source project started in 1998 by Mike Cheng and is currently the best MP3 encoding engine available. Lame encodes MP3 with pure thick sound, wide space, clear bass and good detail. Its original acoustic modeling technology ensures the authenticity of CD audio reproduction. Combined with VBR and ABR parameters, the sound quality is almost as good as CD audio, but the file size is very small.
2.1 Lame library compilation and encapsulation
2.1.1 Porting Lame library to Android project
(1) unzip the lame
- Unzip lame-3.99.5 and copy the libmp3lame directory from the source code to the Android project CPP directory.
- Rename libmp3lame to lame and delete the i386 directory, vector directory, depcomp, lame.rc, logoe.ico, makefile. am, makefile. in files;
- Copy the lame.h file from your inlude directory to the Android project CPP directory. The lame.h header contains declarations of all the called functions.
- Configure the cmakelists.txt file
set(SRC_DIR src/main/cpp/lame)
include_directories(src/main/cpp/lame)
aux_source_directory(src/main/cpp/lame SRC_LIST)
add_library(...... ${SRC_LIST})
Copy the code
(2) lamemp3. Java, create a native method that calls lame library functions
/** Created by Jiangdg on 2017/6/9. */
public class LameMp3 {
// Statically load the shared library LameMp3
static {
System.loadLibrary("LameMp3");
}
/** Initialize the lame library and configure the relevant information **@paramInSampleRate Audio sampling rate in PCM format *@paramOutChannel Number of PCM audio channels *@paramOutSampleRate Audio sampling rate in MP3 format *@paramOutBitRate Mp3 format audio bit rate *@paramQuality Mp3 audio quality, 0 to 9, slowest, worst, fastest, best */
public native static void lameInit(int inSampleRate, int outChannel,int outSampleRate, int outBitRate, int quality);
/** Encode PCM to MP3 format **@paramLetftBuf left PCM data *@paramRightBuf right PCM data, consistent * if monophonic@paramSampleRate Size of the PCM byte read *@paramMp3Buf stores mp3 data cache *@returnThe length of encoded data is */
public native static int lameEncode(short[] letftBuf, short[] rightBuf,int sampleRate, byte[] mp3Buf);
/** Save mp3 audio stream to file **@paramMp3buf MP3 data stream *@returnData flow length rty */
public native static int lameFlush(byte[] mp3buf);
/** * Release the lame library resource */
public native static void lameClose(a);
}
Copy the code
As you can see from the API documentation for the Lame library (lame-3.99.5\API), encapsulating Mp3 using Lame requires four steps: initializing the Lame engine, encoding the PCM as an Mp3 data frame, writing the file, and releasing the Lame engine resources. Therefore, in Lamemp3. Java, we define the corresponding native method for the Java layer to call, and finally generate the required MP3 file.
(3) LameMp3.c
// Local implementation
// Created by jianddongguo on 2017/6/14.
#include <jni.h>
#include "LameMp3.h"
#include "lame/lame.h"
// Declare a pointer variable to lame_global_struct
// can be considered a global context
static lame_global_flags *gfp = NULL;
JNIEXPORT void JNICALL
Java_com_teligen_lametomp3_LameMp3_lameInit(JNIEnv *env, jclass type, jint inSampleRate, jint outChannelNum, jint outSampleRate, jint outBitRate, jint quality) {
if(gfp ! = NULL){ lame_close(gfp); gfp = NULL; }// Initialize the encoder engine, returning a pointer to the lame_global_flags structure type
// The memory required for encoding is allocated, otherwise NULL is returned
gfp = lame_init();
LOGI("Initialization of lame library completed");
// set the sampling rate of the input data stream. The default is 44100Hz
lame_set_in_samplerate(gfp,inSampleRate);
// Set the number of channels for the input data stream. The default is 2
lame_set_num_channels(gfp,outChannelNum);
// Set the output data stream sampling rate, default is 0, unit of KHz
lame_set_out_samplerate(gfp,outSampleRate);
lame_set_mode(gfp,MPEG_mode);
// Set the bit compression ratio. The default is 11
lame_set_brate(gfp,outBitRate);
// Code quality, recommended 2, 5, 7
lame_set_quality(gfp,quality);
// Configure parameters
lame_init_params(gfp);
LOGI("Configuring the lame parameter is complete");
}
JNIEXPORT jint JNICALL
Java_com_teligen_lametomp3_LameMp3_lameFlush(JNIEnv *env, jclass type, jbyteArray mp3buf_) {
jbyte *mp3buf = (*env)->GetByteArrayElements(env, mp3buf_, NULL);
jsize len = (*env)->GetArrayLength(env,mp3buf_);
// Flush the PCM cache with a "0" padding to keep the last few frames intact
// Refresh the MP3 cache to return the last few frames
int resut = lame_encode_flush(gfp, // Global context
mp3buf, // A pointer to the MP3 cache
len); // The length of valid MP3 data
(*env)->ReleaseByteArrayElements(env, mp3buf_, mp3buf, 0);
LOG_I("Write MP3 data to file, return frame number =%d",resut);
return resut;
}
JNIEXPORT void JNICALL
Java_com_teligen_lametomp3_LameMp3_lameClose(JNIEnv *env, jclass type) {
// Release the occupied memory resources
lame_close(gfp);
gfp = NULL;
LOGI("Release lame resource");
}
JNIEXPORT jint JNICALL
Java_com_teligen_lametomp3_LameMp3_lameEncode(JNIEnv *env, jclass type, jshortArray letftBuf_, jshortArray rightBuf_, jint sampleRate, jbyteArray mp3Buf_) {
if(letftBuf_ == NULL || mp3Buf_ == NULL){
LOGI("LetftBuf and rightBuf or mp3Buf_ cannot be empty");
return -1;
}
jshort *letftBuf = NULL;
jshort *rightBuf = NULL;
if(letftBuf_ ! = NULL){ letftBuf = (*env)->GetShortArrayElements(env, letftBuf_, NULL); }if(rightBuf_ ! = NULL){ rightBuf = (*env)->GetShortArrayElements(env, rightBuf_, NULL); } jbyte *mp3Buf = (*env)->GetByteArrayElements(env, mp3Buf_, NULL); jsize readSizes = (*env)->GetArrayLength(env,mp3Buf_);// Encode PCM data as mp3
int result = lame_encode_buffer(gfp, // Global context
letftBuf, // Left channel PCM data
rightBuf, // Right channel PCM data
sampleRate, // Channel data stream sampling rate
mp3Buf, // start address of mp3 data cache
readSizes); // Cache address valid mp3 data length
// Release resources
if(letftBuf_ ! = NULL){ (*env)->ReleaseShortArrayElements(env, letftBuf_, letftBuf,0);
}
if(rightBuf_ ! = NULL){ (*env)->ReleaseShortArrayElements(env, rightBuf_, rightBuf,0);
}
(*env)->ReleaseByteArrayElements(env, mp3Buf_, mp3Buf, 0);
LOG_I("Encoding PCM mp3, data length =%d",result);
return result;
}
Copy the code
GFP is a pointer variable to lame_global_struct, which is used to point to the lame_global_struct. The lame_global_struct structure declares the encoding parameters as follows:
lame_global_flags *gfp = NULL;
typedef struct lame_global_struct lame_global_flags;
struct lame_global_struct {
unsigned int class_id;
unsigned long num_samples;
int num_channels;
int samplerate_in;
int samplerate_out; brate;
floatcompression_ratio; . }Copy the code
In addition, when configuring the LAME encoding engine, there is a lame_set_quality function to set the quality of the encoding. Why do you need this setting, you may ask, since audio encoding quality is not generally determined by bit rate? Well, it is true that the bit rate determines the quality of the code, and the parameters here are mainly used to select the algorithm for encoding processing. Different algorithms have different effects and speeds. For example, when the quality is 0, the algorithm selected is the best, but the processing speed is the slowest; When quality is 9, the algorithm selected is the worst, but the speed is the fastest. In general, the following three Settings are officially recommended:
Quality = 2 Close to the best, speed is not very slow; Quality is good, speed is ok; Quality =7 Good quality, fast speed;
(4) CMakeList.txt
#Specify the minimum version of Cmake requiredCmake_minimum_required (VERSION 3.4.1 track)
#Specify the source path, assigning the SRC /main/ CPP /lame path to SRC_DIR
set(SRC_DIR src/main/cpp/lame)
#Specifies the header file path
include_directories(src/main/cpp/lame)
#Assign all file names in the SRC /main/ CPP /lame directories to SRC_LIST
aux_source_directory(src/main/cpp/lame SRC_LIST)
#Add_library: Specifies the build library file, including three arguments:
#LameMp3 is the name of the library file; SHARED represents a dynamically linked library;
#SRC/main/CPP/LameMp3 and c${SRC_LIST}Specifies the source files required to generate the library files
#Among them,The ${}The SRC /main/ CPP /lame command imports all source files under the SRC /main/ CPP /lame directory
add_library(
LameMp3
SHARED
src/main/cpp/LameMp3.c ${SRC_LIST})
#Searches for libraries in the specified directorylogAnd save its path to the variable log-lib
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
#The library${log-lib}Link to the LameMp3 dynamic library and include two parameters
#LameMp3 is the target library
# ${log-lib}Is the library to link to
target_link_libraries( # Specifies the target library.
LameMp3
# Links the target library to the log library
# included in the NDK.
${log-lib} )
Copy the code
Cmake is a cross-platform compilation tool that allows you to use simple statements to describe the compilation process for all platforms and output various types of Makefiles or Project files. Cmake all statement commands are written in cmakelists.txt. The main rules are as follows:
A. In Cmake, comments start with the # character and end with the line; B. Commands are case-insensitive and parameters are case-sensitive. C. A command consists of a command name and a parameter list. Parameters are separated by Spaces.
(5) Build. Gradle (Module app), select the compilation platform
android {
defaultConfig {
/ /... The code is omitted
externalNativeBuild {
cmake {
cppFlags ""}}// Select the compilation platform
ndk{
abiFilters 'x86'.'x86_64'.'armeabi'.'armeabi-v7a'.'arm64-v8a'}}/ /... The code is omitted
externalNativeBuild {
cmake {
path "CMakeLists.txt"}}Copy the code
2.2 Custom open source library: Lame4Mp3
Lame4Mp3 is an open source project based on Lame library. Combined with the MediaCodec API provided by Android, Lame4Mp3 can encode PCM data streams into AAC or MP3 format data, and support both AAC and MP3 encoding. It is suitable for local recording of MP3 / AAC files and recording while playing (MP3) in Android live.
2.2.1 Adding a Dependency
(1) Add in project build.gradle
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Copy the code
(2) Add it to Module gradle
Dependencies {the compile 'com. Making. Jiangdongguo: Lame4Mp3: v1.0.0'}Copy the code
2.2.2 Usage of Lame4Mp3
(1) Set parameters
Mp3Recorder mMp3Recorder = Mp3Recorder.getInstance();
// Set the AudioRecord parameter
mMp3Recorder.setAudioSource(Mp3Recorder.AUDIO_SOURCE_MIC);
mMp3Recorder.setAudioSampleRare(Mp3Recorder.SMAPLE_RATE_8000HZ);
mMp3Recorder.setAudioChannelConfig(Mp3Recorder.AUDIO_CHANNEL_MONO);
mMp3Recorder.setAduioFormat(Mp3Recorder.AUDIO_FORMAT_16Bit);
// Configure Lame
mMp3Recorder.setLameBitRate(Mp3Recorder.LAME_BITRATE_32);
mMp3Recorder.setLameOutChannel(Mp3Recorder.LAME_OUTCHANNEL_1);
// Configure the MediaCodec parameter
mMp3Recorder.setMediaCodecBitRate(Mp3Recorder.ENCODEC_BITRATE_1600HZ);
mMp3Recorder.setMediaCodecSampleRate(Mp3Recorder.SMAPLE_RATE_8000HZ);
// Set the mode
// Mp3Recorder.MODE_AAC only encode AAC data stream
// Mp3Recorder.MODE_MP3 only encode to get Mp3 files
// Mp3Recorder.MODE_BOTH simultaneous encoding
mMp3Recorder.setMode(Mp3Recorder.MODE_BOTH);
Copy the code
(2) Start coding
mMp3Recorder.start(filePath, fileName, new Mp3Recorder.OnAACStreamResultListener() {
@Override
public void onEncodeResult(byte[] data, int offset, int length, long timestamp) {
Log.i("MainActivity"."Acc Data Stream Length:"+data.length); }});Copy the code
(3) Stop coding
mMp3Recorder.stop();
Copy the code
Finally, it is important to note that when encoding both AAC and Mp3, the PCM data streams are input to MediaCodec and Lame engines differently, with the former accepting only data stored by byte[] and the latter accepting data stored by Short []. That is, if the collected PCM data is stored as byte[], we need to convert it to short[], and we need to pay attention to the size side. The specific code is as follows:
private short[] transferByte2Short(byte[] data,int readBytes){
// byte[] goes to short[], reducing the array length by half
int shortLen = readBytes / 2;
// Assemble byte[] numbers into a ByteBuffer buffer
ByteBuffer byteBuffer = ByteBuffer.wrap(data, 0, readBytes);
// Convert the ByteBuffer to a small end and get the shortBuffer
// Small endian: High bytes of data are stored in the high address of memory, low bytes of data are stored in the low address of memory
ShortBuffer shortBuffer = byteBuffer.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
short[] shortData = new short[shortLen];
shortBuffer.get(shortData, 0, shortLen);
return shortData;
}
Copy the code
GitHub address: github.com/jiangdonggu… Welcome everyone star~(attached LameToMp3 NDK project)