《音视频开发进阶指南》读书笔记(二) —— 移动端环境搭建

1,283 阅读6分钟

第一章 音视频基础概念

《音视频开发进阶指南》作者人很好,又是个大牛,仰慕男神。

在学第二章时,编译过程遇到问题,调了5个小时都没弄好。然后加了他微信,感谢百忙之中抽时间给我讲解低级问题。


2019.5.17 更新

大佬说本文有点范,复述书上的内容比较多,让我加点自己的思考。这章就是通过交叉编译编译出一些常用第三方库,是章傻瓜式教程。所以考虑第三章如果也没什么说的话,就不复述了。

当然,这些库在网上通常很好找。有人就问了,直接拉进项目用不就好了,为什么还要花成本去学习交叉编译(笔者零基础学起来很吃力,书上还有由于版本更新或者运行环境问题而废弃的命令)?

当然常用的库网上都有资源,应付日常的开发没问题。所以直接跳过这章也不是不行。但第三章,FFmpeg 会集成编译出的第三方库。所以,如果你要自定义FFmpeg,网上如果找不到相应的资源,就需要自己动手。从这个角度看,这章可以先跳过,以后有需要再学。


第二章 移动端环境搭建

本章会讲解交叉编译,最后会使用 LAME 这个开源的 MP3 编码库在 iOS 平台和 Android 平台上将一个 PCM 文件编码为 MP3 文件,最终将编码后的 MP3 文件发送到电脑上即可进行播放。

2.1 在 iOS 上如何搭建一个基础项目

首先创建一个名为 ktv 的项目,并且pod进两个库。

target 'KTV' do
	pod 'Mantle', '1.5'
	pod 'AFNetworking', '2.6.0'
end

然后增加 C++ 支持,编写一个类Mp3Encoder,负责将 PCM 数据编码为 MP3 文件。然后把控制器文件后缀改为.mm

image.png


关于OC 和 C++混编,推荐看这篇文章聊聊你不知道的 Objective-C++


下面分别是Mp3Encoder.hppMp3Encoder.cppViewController.mm目前的代码。

#ifndef Mp3Encoder_hpp
#define Mp3Encoder_hpp

#include <stdio.h>

class Mp3Encoder
{
public:
    void func();

};

#endif /* Mp3Encoder_hpp */
#include "Mp3Encoder.hpp"

void Mp3Encoder::func() {
    printf("C++");
}

#import "ViewController.h"
#import <iostream>
#include "Mp3Encoder.hpp"
#import <AFNetworking.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Mp3Encoder *encoder = new Mp3Encoder();
    encoder->func();
    
}
@end

2.3 交叉编译的原理与实践

在音视频的开发中,第三方库都是需要进行交叉编译的。

2.3.1 交叉编译的原理

  • 编译的过程:使用本机器的编译器,将源代码编译链接成为一个可以在本机器上运行的程序。

  • 交叉编译:在一个平台上生成另外一个平台的可执行代码。两个平台有不同的机器指令。交叉编译详解

书本理论这里看不太懂,笔者也是这学期刚学的编译原理。编译可以分为前端后端两部分。

image.png

在后端可以将中间代码生成对应设备的机器码。

image.png

所以目的就是在电脑上生成ARM CPU的可执行代码。

2.3.2 iOS 平台交叉编译的实践

随着时间的推移,iPhone平台对应的指令集也在变。

  • armv6:iPhone、iPhone2、iPhone 3G
  • armv7:iPhone 4、iPhone 4S
  • armv7s:iPhone 5、iPhone 5S
  • arm64:iPhone 5S、iPhone 6(P)、iPhone6S(P)、iPhone7(P)

机器对指令集的支持是向下兼容的,因此armv7的指令集是可以运行在 iPhone 5S中的,只是效率没那么高。


  1. LAME 的交叉编译

若要在移动端平台上编码 MP3 文件,使用 LAME 成为唯一的选择。

先(科学上网)下载最新的 LAME 版本。sourceforge.net/projects/la…

编译LAME的步骤,照书上给出的步骤,运行脚本报错。

./configure \
--disable-shared \
--disable-frontend \
--host=arm-apple-darwin \
--prefix="/Users/hsusue/Downloads/音视频/cross_compile_project-master/ffmpeg_2.8.5_android/external_libs/lame/thin-lame/arm64" \
CC="xcrun -sdk iphoneos clang -arch arm64" \
CFLAGS="-arch arm64 -fembed-bitcode -miphoneos-version-min=7.0" \
LDFLAGS="-arch arm64 -fembed-bitcode -miphoneos-version-min=7.0"

make clean
make -j8
make install

image.png

跟作者沟通后(作者人很好,很热情),得知作者github有这脚本(LAME脚本居然只在安卓文件夹中)。下载来对比脚本一样,但却能运行。。。搞不懂

先cd进文件夹中,然后运行test.sh

cd /所在目录/cross_compile_project-master/ffmpeg_2.8.5_android/external_libs/lame

其实,这就是个静态库。实在不想编译,可以下载别人编译好的静态库直接拉进项目用。


  1. FDK_AAC 的交叉编译

FDK_AAC 是用来编码和解码 AAC 格式音频文件的开源库, Android 系统编码和解码 AAC 所用的就是这个库。

先(科学上网)下载最新的 LAME 版本。 sourceforge.net/p/opencore-…

还要下载gas-preprocessor.pl,然后复制到/usr/bin目录下。

Mac中修改 usr/bin 目录权限

然后在根目录下建立build_armv7.sh脚本。

./configure \
--enable-static \
--disable-shared \
--host=arm-apple-darwin \
--prefix="/Users/hsusue/Downloads/音视频/FDK_AAC/opencore-amr-fdk-aac-83ac4a9860cd81f793cb4620952fbf795a281b49/thin/armv7" \
CC="xcrun -sdk iphoneos clang" \
AS="gas-preprocessor.pl $ CC" \
CFLAGS="-arch armv7 -mios-simulator-version-min=7.0" \
LDFLAGS="-arch armv7 -mios-simulator-version-min=7.0"

make clean
make -j8
make install

历史还是惊人的相似

image.png

还是去用作者的脚本吧,路径是/所在目录/cross_compile_project-master/FFmpeg-Compile-Source/build-fdk-aac/fdk-aac-0.1.4


  1. X264 的交叉编译

X264 是一个开源的 H.264/MPEG-4 AVC 视频编码函数库,是最好的有损视频编码器之一。

书上的方法和前面大同小异,但还是用书本作者的脚本吧。弃疗了。


2.3.4 使用 LAME 编码 MP3 文件

文章开头的项目,只简单把OC 和 C++ 混编。下面加入编码 MP3 文件的功能。当点击按钮的时候,输入的是一个 PCM 文件的路径和一个 MP3 文件的路径,等运行完毕,电脑上的播放器直接就可以播放该 MP3 文件。

  1. 首先把编译出的lame.hlame.a文件拖进项目中。

  2. 完善头文件Mp3Encoder.hpp

#ifndef Mp3Encoder_hpp
#define Mp3Encoder_hpp

#include <stdio.h>
#include "pc_lame/include/lame/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();

};

  1. 完善实现文件
#include "Mp3Encoder.hpp"

int Mp3Encoder::Init(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) {
            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;
        }
    }
    return ret;
}

void Mp3Encoder::Encode() {
    int bufferSize = 1024 * 256;
    short* buffer = new short[bufferSize / 2];
    short* leftBuffer = new short[bufferSize / 4];
    short* rightBuffer = new short[bufferSize / 4];
    unsigned char* mp3_buffer = new unsigned char[bufferSize];
    size_t readBufferSize = 0;
    while ((readBufferSize = fread(buffer, 2, bufferSize / 2, pcmFile))) {
        for (int i = 0; i < readBufferSize; i++) {
            if (i % 2 == 0) {
                leftBuffer[i / 2] = buffer[i];
            } else {
                rightBuffer[i / 2] = buffer[i];
            }
        }
        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);
    }
    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() {
    
}
  1. iOS 集成

4.1 拉一个pcm文件进项目沙盒 , 下载路径

4.2 创建一个按钮,点击时构建编码器,执行编码方法,最后释放。

- (IBAction)btnClick:(UIButton *)sender {

    Mp3Encoder *encoder = new Mp3Encoder();

    // 源文件的的路径
    const char* pcmFilePath = [[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"16k.pcm"] cStringUsingEncoding:NSUTF8StringEncoding];
    NSLog(@"%@", [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"16k.pcm"]);

    // 要生成的mp3文件的路径
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *docStr = [documentsDirectory stringByAppendingPathComponent:@"16k.mp3"];
    NSLog(@"%@", docStr);
    const char *mp3FilePath = [docStr cStringUsingEncoding:NSUTF8StringEncoding];

    int sampleRate = 44100;
    int channels = 2;
    int bitRate = 128 * 1024;
    // 初始化解码器,传入源文件路径,生成的文件路径,采样频率,声道数,码率
    encoder->Init(pcmFilePath, mp3FilePath, sampleRate, channels, bitRate);

    // 编码
    encoder->Encode();

    //关闭文件
    encoder->Destory();
    delete encoder;

}

这一章到这就结束了。主要介绍了如何创建一个项目,增加C++支持,学习了交叉编译,最终完成了编码 MP3 音频文件。


参考

  • [1] 展晓凯,魏晓红.音视频开发进阶指南(基于Android与iOS平台的实践)[M].北京:机械工业出版社,2018:14-42.