《音视频开发进阶指南》作者人很好,又是个大牛,仰慕男神。
在学第二章时,编译过程遇到问题,调了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
。
关于OC 和 C++混编,推荐看这篇文章聊聊你不知道的 Objective-C++。
下面分别是Mp3Encoder.hpp
、Mp3Encoder.cpp
、ViewController.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中的,只是效率没那么高。
- LAME 的交叉编译
若要在移动端平台上编码 MP3 文件,使用 LAME 成为唯一的选择。
先(科学上网)下载最新的 LAME 版本。https://sourceforge.net/projects/lame/files/lame/3.99/
编译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
其实,这就是个静态库。实在不想编译,可以下载别人编译好的静态库直接拉进项目用。
- FDK_AAC 的交叉编译
FDK_AAC 是用来编码和解码 AAC 格式音频文件的开源库, Android 系统编码和解码 AAC 所用的就是这个库。
先(科学上网)下载最新的 LAME 版本。
https://sourceforge.net/p/opencore-amr/fdk-aac/ci/v0.1.4/tree/
还要下载gas-preprocessor.pl
,然后复制到/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
。
- X264 的交叉编译
X264 是一个开源的 H.264/MPEG-4 AVC 视频编码函数库,是最好的有损视频编码器之一。
书上的方法和前面大同小异,但还是用书本作者的脚本吧。弃疗了。
2.3.4 使用 LAME 编码 MP3 文件
文章开头的项目,只简单把OC 和 C++ 混编。下面加入编码 MP3 文件的功能。当点击按钮的时候,输入的是一个 PCM 文件的路径和一个 MP3 文件的路径,等运行完毕,电脑上的播放器直接就可以播放该 MP3 文件。
-
首先把编译出的
lame.h
和lame.a
文件拖进项目中。 -
完善头文件
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();
};
- 完善实现文件
#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() {
}
- 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.
网友评论