Android uses AudioRecord to record the microphone to get the original audio data in PCM format. PCM files cannot be used for playback and need to be encoded and compressed.
LAME is an excellent MP3 encoding engine. In the industry, the most commonly used encoder for transcoding MP3 audio files is the LAME library. At 320Kbit/s or more, LAME can encode audio that is almost as good as CD and is very small, so using LAME is the only option for encoding MP3 files on mobile platforms.
Compile the LAME library
Android Studio3.0 + uses CMake as the default source for compiling native files. There are many articles on the web that are not suitable for this purpose.
Port lame library to Android (using CMake)
Android Studio3.0 + Lame library (CMake)
Audio coding
Here’s a guide to audio and Video Development: Practice for Android and Ios.
WAV coding
PCM stands for Pulse Code Modulation. The general workflow of PCM has been described before, and one implementation of WAV coding (there are many implementations, but none of them compress) is to add 44 bytes in front of the PCM data format, which respectively describe the PCM sampling rate, number of channels, data format and other information.
Features: Very good sound quality, a large number of software support.
Application: multimedia development of intermediate files, save music and sound materials.
MP3 encoding
MP3 has a good compression ratio. An MP3 file encoded using LAME (an implementation of the MP3 encoding format) at a medium to high bit rate sounds very similar to the source WAV file. Of course, the appropriate parameters should be adjusted for different application scenarios to achieve the best results.
Features: sound quality in 128Kbit/s above performance is good, high compression ratio, a large number of software and hardware support, good compatibility.
Application: music appreciation with high bit rate and requirement for compatibility.
AAC encoding
AAC is a new generation of audio lossy compression technology. Through some additional coding technologies (such as PS, SBR, etc.), it derived three main coding formats lC-AAC, HE-AAC, He-AAC V2. Lc-aac is a traditional AAC. Relatively speaking, it is mainly applied to the encoding of medium-high bit rate scenarios (≥80Kbit/s). He-aac (equivalent to AAC+SBR) is mainly used for coding in medium and low bit rate scenarios (≤80Kbit/s). The newly introduced HE-AAC V2 (equivalent to AAC+SBR+PS) is mainly used for coding in low bit rate scenarios (≤48Kbit/s). In fact, most encoders are set to ≤48Kbit/s automatically enable PS technology, and >48Kbit/s does not add PS, equivalent to ordinary he-aac.
Features: excellent performance at bit rate less than 128Kbit/s, and mostly used for audio encoding in video.
Application: 128Kbit/s below the audio encoding, mostly used for video audio track encoding.
Ogg coding
Ogg is a very promising encoding with excellent performance at various bit rates, especially in low and medium bit rate scenarios. In addition to the sound quality, Ogg is completely free, which sets the stage for more support for Ogg. Ogg has a very good algorithm that can achieve better sound quality at a smaller bit rate. A 128Kbit/s Ogg is better than an MP3 at 192Kbit/s or higher bit rate. But at present because there is no media service software support, so digital broadcast based on Ogg can not be realized. Ogg’s current support is not good enough, either in software or hardware support, is not comparable to MP3.
Features: can use smaller bit rate than MP3 to achieve better sound quality than MP3, high, medium and low bit rate have good performance, compatibility is not good, streaming features are not supported.
Application scenario: Audio message scenario in voice chat.
Convert PCM files to MP3 using LAME
Using the lame library blog, the project can now use the lame method via JNI.
- Create a new Mp3Encoder. Java file and add the relevant native methods.
public class Mp3Encoder {
public native int init(String pcmPath,
int audioChannels,
int bitRate,
int sampleRate,
String mp3Path);
public native void encode(a);
public native void destroy(a);
}
Copy the code
- Generate the corresponding Mp3Encoder. Java header (.h file, automatically generated using the javah command) com_wyt_myapplication_Mp3Encoder.
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_wyt_myapplication_Mp3Encoder */
#ifndef _Included_com_wyt_myapplication_Mp3Encoder
#define _Included_com_wyt_myapplication_Mp3Encoder
#ifdef __cplusplus
extern "C" {
#endif
/* * Class: com_wyt_myapplication_Mp3Encoder * Method: init * Signature: (Ljava/lang/String; IIILjava/lang/String;) I */
JNIEXPORT jint JNICALL Java_com_wyt_myapplication_Mp3Encoder_init
(JNIEnv *, jobject, jstring, jint, jint, jint, jstring);
/* * Class: com_wyt_myapplication_Mp3Encoder * Method: encode * Signature: ()V */
JNIEXPORT void JNICALL Java_com_wyt_myapplication_Mp3Encoder_encode
(JNIEnv *, jobject);
/* * Class: com_wyt_myapplication_Mp3Encoder * Method: destroy * Signature: ()V */
JNIEXPORT void JNICALL Java_com_wyt_myapplication_Mp3Encoder_destroy
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
Copy the code
- Create a Mp3Encoder. CPP file in SRC /main/ CPP and implement the methods in the com_wyt_myapplication_Mp3Encoder. H header.
However, the implementation of this method requires the support of the LAME library method, which would be complicated if the PCM to MP3 logic were done in this file. We first encapsulated the relevant logic of converting LAME from PCM to MP3 into a new C/C ++ file, and just called it in the Mp3Encoder. CPP file. We separated the implementation of Java’s call to native method from the implementation of the specific logic of native method.
That is, the logic is divided into four layers: Java code — implementation of Java calling native methods — encapsulation of lame methods — lame methods. The four representative files are: Mp3Encoder. Java — Mp3Encoder. CPP — mp3_encode
Here first to divide out the Mp3Encoder. CPP code (I wrote the code before writing the article), in actual work, this step to mp3_encode. CPP after implementation.
There are three main methods: initialize LAME, encode and release resources after encoding.
#include "com_wyt_myapplication_Mp3Encoder.h"
#include "mp3_encoder.h"
Mp3Encoder *encoder = NULL;
JNIEXPORT jint JNICALL Java_com_wyt_myapplication_Mp3Encoder_init
(JNIEnv *env, jobject jobj, jstring pcmPathParam, jint audioChannelsParam, jint bitRateParam, jint sampleRateParam, jstring mp3PahtParam){
const char* pcmPath = env->GetStringUTFChars(pcmPathParam,NULL);
const char* mp3Path = env->GetStringUTFChars(mp3PahtParam,NULL);
encoder = new Mp3Encoder();
int ret = encoder->lint(pcmPath,
mp3Path,
sampleRateParam,
audioChannelsParam,
bitRateParam);
env->ReleaseStringUTFChars(mp3PahtParam, mp3Path);
env->ReleaseStringUTFChars(pcmPathParam, pcmPath);
return ret;
}
JNIEXPORT void JNICALL Java_com_wyt_myapplication_Mp3Encoder_encode
(JNIEnv *, jobject){
encoder->Encode();
}
JNIEXPORT void JNICALL Java_com_wyt_myapplication_Mp3Encoder_destroy
(JNIEnv *, jobject){
encoder->Destory();
}
Copy the code
- Mp3_encode. CPP to create
In mp3_encode. CPP, simple encapsulation is carried out on the basis of LAME library method to complete the conversion from PCM to MP3.
CPP is a native C++ class, which has nothing to do with the Mp3Encoder. Java class.
Class exposes three methods to be called by the three methods in the Mp3Encoder. CPP file.
#include <stdio.h>
#include "lame.h"
#ifndef MYAPPLICATION_MP3_ENCODER_H
#define MYAPPLICATION_MP3_ENCODER_H
#ifdef __cplusplus
extern "C" {
#endif
class Mp3Encoder {
private:
FILE *pcmFIle;
FILE *mp3File;
lame_t lameClient;
public:
Mp3Encoder();
~Mp3Encoder();
int lint(const char *pcmFilePath,
const char *mp3FilePath,
int sampleRate,
int channels,
int bitRate);
void Encode(a);
void Destory(a);
};
#ifdef __cplusplus
}
#endif
#endif
Copy the code
The implementation of the mp3_encode. CPP file, which looks a bit long, is actually easy to understand, mainly initialize lame parameters; The buffer read by PCM file is converted into MP3Buffer by lame. Write mp3buffer to a file.
#include "mp3_encoder.h"
extern "C"
Mp3Encoder::Mp3Encoder(){
}
Mp3Encoder::~Mp3Encoder(){
}
int Mp3Encoder::lint(const char *pcmFilePath,
const char *mp3FilePath,
int sampleRate,
int channels,
int bitRate) {
int ret = 1;
pcmFIle = fopen(pcmFilePath, "rb");
if (pcmFIle) {
mp3File = fopen(mp3FilePath, "wb");
if (mp3File) {
// Initialize lame parameters, input/output sample rate, number of audio channels, bit rate
lameClient = lame_init();
lame_set_in_samplerate(lameClient, sampleRate);
lame_set_out_samplerate(lameClient, sampleRate);
lame_set_num_channels(lameClient, channels);
lame_set_brate(lameClient, 128);
lame_init_params(lameClient);
ret = 0; }}return ret;
}
void Mp3Encoder::Encode() {
int bufferSize = 1024 * 256;
short *buffer = new short[bufferSize / 2];
short *leftChannelBuffer = new short[bufferSize / 4];/ / the left channel
short *rightChannelBuffer = new short[bufferSize / 4];/ / right channel
unsigned char *mp3_buffer = new unsigned char[bufferSize];
size_t readBufferSize = 0;
while ((readBufferSize = fread(buffer, 2, bufferSize / 2, pcmFIle)) > 0) {
for (int i = 0; i < readBufferSize; i++) {
if (i % 2= =0) {
leftChannelBuffer[i / 2] = buffer[i];
} else {
rightChannelBuffer[i / 2] = buffer[i]; }}size_t writeSize = lame_encode_buffer(
lameClient,
(short int *) leftChannelBuffer,
(short int *) rightChannelBuffer,
(int) (readBufferSize / 2),
mp3_buffer,
bufferSize);
fwrite(mp3_buffer, 1, writeSize, mp3File);
}
delete [] buffer;
delete [] leftChannelBuffer;
delete [] rightChannelBuffer;
delete [] mp3_buffer;
}
void Mp3Encoder::Destory() {
if (pcmFIle){
fclose(pcmFIle);
}
if(mp3File){ fclose(mp3File); lame_close(lameClient); }}Copy the code
-
The SRC/main/CPP/mp3_encoder CPP, SRC/main/CPP/Mp3Encoder CPP added to CMakeLists. TXT add_libraty method. If not, read the first two blogs.
-
Android file read and write permissions do not forget to set manifest.xml, 6.0 or more adaptive dynamic permission access mechanism, here will not say.
-
That’s pretty much it. Now you can use it in projects, such as onCreate() in MainActivity.
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private String[] permissions = new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
private List<String> mPermissionList = new ArrayList<>();
private static final int MY_PERMISSIONS_REQUEST = 1001;
// the sampling rate is now guaranteed to be 44100Hz for all devices, but other sampling rates (22050, 16000, 11025) are also available for some devices.
public static final int SAMPLE_RATE_INHZ = 44100;
// Number of channels. CHANNEL_IN_MONO and CHANNEL_IN_STEREO. CHANNEL_IN_MONO can be used on all devices.
public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
// The format of the returned audio data. ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT.
public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
checkPermissions();
String pcmPath, mp3Path;
pcmPath = "/storage/emulated/0/0001.pcm";// PCM file path, file must exist!
mp3Path = "/storage/emulated/0/0001.mp3";// Save path of converted MP3 files
Mp3Encoder encoder = new Mp3Encoder();
if(encoder.init(pcmPath,CHANNEL_CONFIG,128,SAMPLE_RATE_INHZ,mp3Path) == 0){
Log.d(TAG, "onCreate: encoder-init:success");
encoder.encode();
encoder.destroy();
Log.d(TAG, "onCreate:encode finish");
}else {
Log.d(TAG, "onCreate: encoder-init:failed"); }}/** * A native method that is implemented by the 'native-lib' native library, * which is packaged with this application. */
public native String stringFromJNI(a);
private void checkPermissions(a) {
// Marshmallow is used to apply for runtime permissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
for (int i = 0; i < permissions.length; i++) {
if (ContextCompat.checkSelfPermission(this, permissions[i]) !=
PackageManager.PERMISSION_GRANTED) {
mPermissionList.add(permissions[i]);
}
}
if(! mPermissionList.isEmpty()) { String[] permissions = mPermissionList.toArray(new String[mPermissionList.size()]);
ActivityCompat.requestPermissions(this, permissions, MY_PERMISSIONS_REQUEST); }}}@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == MY_PERMISSIONS_REQUEST) {
for (int i = 0; i < grantResults.length; i++) {
if(grantResults[i] ! = PackageManager.PERMISSION_GRANTED) { Log.i(TAG, permissions[i] +"Permission prohibited by user!"); }}// Permission application at runtime is not the focus of this demo, so no more processing will be done, please agree with permission application.}}}Copy the code
- No PCM file? Their own hands, their own AudioRecorder to write an APP, record a PCM! (The code for recording PCM is ready, the article is not written yet, don’t wait here, I don’t know when I will write the article…)
conclusion
This is a blog that integrates lame library using CMake on Android Studio 3.0 or above. Then, through JNI development, Java code is used to call the native layer code that encapsulates the audio encoding logic to complete the conversion from PCM file to MP3 file, which completely describes the basic process of JNI development.