音视频开发进阶指南(第二章)
书中示例源码地址:
ffmpeg编译参考链接
使用libmp3lame开源库,编码PCM数据为MP3,结尾有源码,分支为chapter_2.x
一、新建NDK工程
网上一大堆。
二、下载LAME源码
lame源码地址lame官网
下载后解压,本demo中中使用的是3.100版本
三、拷贝libmp3lame源码到工程
在项目cpp目录下新建libmp3lame目录,用来存放下载的源码。
找到解压 的libmp3lame文件夹,将里面的.c和.h文件全部复制到项目的cpp/libmp3lame目录中。 libmp3lame文件夹内还包含其他文件夹,例如vector和i386是不需要的,可以忽略,然后,再找到解压的include文件夹,将lame.h文件拷贝到cpp/libmp3lame目录中,一共拷贝43个文件。
移植过来的代码要做一些修改才能编译通过:
1)删除fft.c文件的47行的”include “vector/lame_intrin.h”“
2)修改set_get.h文件的24行的#include“lame.h”
3)将util.h文件的574行的”extern ieee754_float32_tfast_log2(ieee754_float32_t x);” 替换为 “extern float fast_log2(float x);”
四、编写编码工具类
mp3_encoder.h
#include <stdio.h>
#include "jni.h"
#include "../libmp3lame/lame.h"
class Mp3Encoder {
private:
FILE *pcmFile;
FILE *mp3File;
lame_t lameClient;
public:
Mp3Encoder();
~Mp3Encoder();
int Init(const char *pcmFilePath, const char *mp3FilePath,
int sampleRate, int channels, int bitRate);
void Encode();
void Destory();
};
mp3_encoder.cpp
//
// Created by bian on 2019/10/10.
//
#include "stdio.h"
#include <android/log.h>
#include <mp3_encoder.h>
#define LOG_TAG "Mp3Encorder"
#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,FORMAT,##__VA_ARGS__);
int Mp3Encoder::Init(const char *pcmFilePath, const char *mp3FilePath,
int sampleRate, int channels, int bitRate) {
int ret = -1;
pcmFile = fopen(pcmFilePath, "rb");//rb-以二进制读取模式打开文件
if (pcmFile) {
mp3File = fopen(mp3FilePath, "wb");//wb-以二进制写入模式打开文件
if (mp3File) {
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, bitRate / 1000);//比特率
lame_init_params(lameClient);
ret = 0;
LOGI("set lameClient params done!")
} else {
LOGE("open mp3File failed! path=%s", mp3FilePath);
}
} else {
LOGE("open pcmFile failed! path=%s", pcmFilePath);
}
return ret;
}
void Mp3Encoder::Encode() {
int channels = lame_get_num_channels(lameClient);
LOGI("通道个数为:%d", channels);
int bufferSize = 1024 * 256;//缓冲区大小
short *buffer = new short[bufferSize / channels];//读取pcmFile的缓冲区
//双声道情况时,为左右声道分配缓冲区
short *leftBuffer = new short[bufferSize / 4]; //左声道缓冲区
short *rightBuffer = new short[bufferSize / 4];//右声道缓冲区
unsigned char *mp3_buffer = new unsigned char[bufferSize];
size_t readBufferSize = 0;
int frameSize = sizeof(short int) * channels;//一个帧的字节数
LOGI("frameSize=%d", frameSize);
while ((readBufferSize = fread(buffer, frameSize, bufferSize / channels,
pcmFile)) > 0) {
if (channels == 1) {
LOGI("readBufferSize=%d", readBufferSize);
//对pcm数据进行编码,单声道情况,右声道为空
size_t wroteSize = lame_encode_buffer(lameClient,
(short int *) buffer,//左声道
NULL,//右声道
(int) (readBufferSize),
mp3_buffer,//编译后的数据
bufferSize);
LOGI("wroteSize=%d", wroteSize);
fwrite(mp3_buffer, 1, wroteSize, mp3File);//写入编码后的数据到mp3File
} else if (channels == 2) {
for (int i = 0; i < readBufferSize; i++) {
if (i % 2 == 0) {
leftBuffer[i / 2] = buffer[i];
} else {
rightBuffer[i / 2] = buffer[i];
}
}
//对pcm数据进行编码
size_t wroteSize = lame_encode_buffer(lameClient,
(short int *) leftBuffer,//左声道
(short int *) rightBuffer,//右声道
(int) (readBufferSize / 2),
mp3_buffer,//编译后的数据
bufferSize);
fwrite(mp3_buffer, 1, wroteSize, mp3File);//写入编码后的数据到mp3File
}
}
delete[] buffer;
delete[] leftBuffer;
delete[] rightBuffer;
delete[] mp3_buffer;
}
void Mp3Encoder::Destory() {
if (pcmFile) {
fclose(pcmFile);
}
if (mp3File) {
fclose(mp3File);
lame_close(lameClient);
}
}
Mp3Encoder::Mp3Encoder() {
}
Mp3Encoder::~Mp3Encoder() {
}
五、在java中调用
新建类Mp3Encoder.java
public class Mp3Encoder {
public native int init(String pcmPath, int audioChannels,
int bitRate, int sampleRate, String mp3Path);
public native void encode();
public native void destroy();
}
生成头文件
在Mp3Encoder右键,如下图
image.png
如果你没有,请点击File->Settings->Tools->External Tools,点击+号,按照下图照抄即可
image.png
最后是JNI开发
Mp3Encoder.cpp
Mp3Encoder *encoder;
JNIEXPORT jint JNICALL Java_com_flyscale_mp3encoder_Mp3Encoder_init
(JNIEnv *env, jobject jclazz, jstring pcmFileParam, jint channels,
jint bitRate, jint sampleRate, jstring mp3FileParam) {
int ret = -1;
const char *pcmPath = env->GetStringUTFChars(pcmFileParam, NULL);
const char *mp3Path = env->GetStringUTFChars(mp3FileParam, NULL);
encoder = new Mp3Encoder();
ret = encoder->Init(pcmPath, mp3Path, sampleRate, channels, bitRate);
env->ReleaseStringUTFChars(mp3FileParam, mp3Path);
env->ReleaseStringUTFChars(pcmFileParam, pcmPath);
return ret;
}
JNIEXPORT void JNICALL Java_com_flyscale_mp3encoder_Mp3Encoder_encode(JNIEnv *env, jobject jclazz) {
LOGI("encoder encode");
encoder->Encode();
}
JNIEXPORT void JNICALL Java_com_flyscale_mp3encoder_Mp3Encoder_destroy
(JNIEnv *env, jobject jclazz) {
encoder->Destory();
}
六、编写CMakeLists.txt
依赖lame库的方式有几种,我直接把lame的源码与我自己的源码放在一起进行编译。
#指定使用的cmake最低版本号
cmake_minimum_required(VERSION 3.4.1)
#指定工程名,非必须
project(mp3encoder)
#指定头文件路径
include_directories(src/main/cpp/include/)
set(SRC_FILES
src/main/cpp/Mp3Encoder.cpp
src/main/cpp/mp3_encoder.cpp
src/main/cpp/libmp3lame/bitstream.c
src/main/cpp/libmp3lame/encoder.c
src/main/cpp/libmp3lame/fft.c
src/main/cpp/libmp3lame/gain_analysis.c
src/main/cpp/libmp3lame/id3tag.c
src/main/cpp/libmp3lame/lame.c
src/main/cpp/libmp3lame/mpglib_interface.c
src/main/cpp/libmp3lame/newmdct.c
src/main/cpp/libmp3lame/presets.c
src/main/cpp/libmp3lame/psymodel.c
src/main/cpp/libmp3lame/quantize_pvt.c
src/main/cpp/libmp3lame/quantize.c
src/main/cpp/libmp3lame/reservoir.c
src/main/cpp/libmp3lame/set_get.c
src/main/cpp/libmp3lame/tables.c
src/main/cpp/libmp3lame/takehiro.c
src/main/cpp/libmp3lame/util.c
src/main/cpp/libmp3lame/vbrquantize.c
src/main/cpp/libmp3lame/VbrTag.c
src/main/cpp/libmp3lame/version.c
)
#编译为共享库,名称audioencoder
add_library(
audioencoder
# Sets the library as a shared library.
SHARED
#源文件路径
${SRC_FILES})
#在默认路径下查找log库,并保存在变量log-lib中
find_library(log-lib log)
# 链接库
target_link_libraries( # Specifies the target library.
audioencoder
# 将Log-lib变量代表的库,链接到audioencoder库
${log-lib})
七、PCM源与参数问题
pcm音频源文件分单通道和双通道,采样率,比特率,所以要转码时要注意根据PCM源文件的情况进行区分,是进行单通道编码还是双通道编码。例子中也有体现。否则可以会出现声音变慢,变快,有杂音,有空白等情况。
github源码
[PCM音频数据](https://pan.baidu.com/s/136sjYRJlQY5uYy7F1hzPKg
提取码:h7wv)
网友评论