美文网首页
音视频开发进阶指南(第二章)

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

作者: DD_Dog | 来源:发表于2019-10-11 16:38 被阅读0次

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

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

    参考

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

    相关文章

      网友评论

          本文标题:音视频开发进阶指南(第二章)

          本文链接:https://www.haomeiwen.com/subject/gqgtmctx.html