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

简介: 笔记

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



使用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右键,如下图

40.png


如果你没有,请点击File->Settings->Tools->External Tools,点击+号,按照下图照抄即可

41.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源文件的情况进行区分,是进行单通道编码还是双通道编码。例子中也有体现。否则可以会出现声音变慢,变快,有杂音,有空白等情况。

目录
相关文章
|
Web App开发 编解码 Android开发
2023年音视频开发知识技术合集(基础入门到高级进阶)
2023年音视频开发知识技术合集(基础入门到高级进阶)
|
3月前
|
人工智能 数据挖掘 数据处理
揭秘Python编程之美:从基础到进阶的代码实践之旅
【9月更文挑战第14天】本文将带领读者深入探索Python编程语言的魅力所在。通过简明扼要的示例,我们将揭示Python如何简化复杂问题,提升编程效率。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开一扇通往高效编码世界的大门。让我们开始这段充满智慧和乐趣的Python编程之旅吧!
|
6月前
|
Linux 开发工具 C++
技术笔记:RustGUI编程
技术笔记:RustGUI编程
|
2月前
|
程序员 数据库 开发者
探索Python编程之旅:从基础到进阶
【9月更文挑战第34天】本文将引导你踏上Python编程的奇妙旅程,从最初的安装和运行第一个程序开始,逐步深入到面向对象编程、文件操作和网络编程等高级主题。我们将通过代码示例和清晰的步骤解释,帮助你构建起对Python语言的深刻理解,并鼓励你在遇到问题时主动寻找解决方案,培养解决问题的能力。无论你是初学者还是有一定经验的开发者,都能在这篇文章中找到有价值的内容和启发。
|
2月前
|
机器学习/深度学习 生物认证 语音技术
声纹识别入门:原理与基础知识
【10月更文挑战第16天】声纹识别(Voice Biometrics)是生物特征识别技术的一种,它通过分析个人的语音特征来验证身份。与指纹识别或面部识别相比,声纹识别具有非接触性、易于远程操作等特点,因此在电话银行、客户服务、智能家居等领域得到了广泛应用。
215 0
|
3月前
|
存储 XML 前端开发
探索Android应用开发:从基础到进阶
【8月更文挑战第57天】在这篇文章中,我们将深入探讨Android应用开发的奥秘。无论你是新手还是有经验的开发者,本文都将为你提供有价值的见解和技巧。我们将从基本的UI设计开始,逐步介绍数据存储、网络请求等高级主题,并展示一些实用的代码示例。让我们一起踏上这段激动人心的旅程吧!
|
3月前
|
数据采集 JavaScript 前端开发
打造你的Python爬虫:从基础到进阶
【9月更文挑战第5天】在数字信息泛滥的时代,掌握一项技能能让我们更好地筛选和利用这些资源。本文将带你了解如何用Python构建一个基本的网页爬虫,进而拓展到更复杂的数据抓取任务。无论你是编程新手还是有一定经验的开发者,跟随这篇文章的步伐,你将能够实现自动化获取网络数据的目标。准备好了吗?让我们一起潜入代码的世界,解锁新的可能!
|
4月前
|
数据处理 开发者 Python
Python技术深度探索与实战应用
Python技术深度探索与实战应用
74 3
|
7月前
|
自然语言处理 Java 编译器
【软件设计师—基础精讲笔记10】第十章 程序设计语言基础
【软件设计师—基础精讲笔记10】第十章 程序设计语言基础
113 1
|
7月前
|
算法
【软件设计师—基础精讲笔记9】第九章 算法设计与分析
【软件设计师—基础精讲笔记9】第九章 算法设计与分析
58 1