音视频开发进阶指南(第二章)

音视频开发进阶指南(第二章)

书中示例源码地址:
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 
#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 
#include 

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

参考

使用libmp3lame库编码mp3
Android移植lame库(采用CMake)

你可能感兴趣的:(音视频开发进阶指南(第二章))