利他才能利己


  • 首页

  • 标签

  • 归档

  • 搜索

MacOS 文件大小的单位

发表于 2018-03-24 | 分类于 MacOS |

缘由

我一直以为文件大小都是按照 1024 为单位来计算的, 但是在 MacOS 上面是按照 1000 为单位计算的, windows 上面是按照 1024 为单位来计算的.

偶然的发现

之前写代码, 需要用到获取文件大小的功能, 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 读取文件大小
long getFileSize(char *filename) {

FILE *fp = fopen(filename, "r");
if (!fp) {
return -1;
}

fseek(fp, 0, SEEK_END);

long size = ftell(fp);
fclose(fp);

return size;
}

文件名是 mm.aac, 一个音频文件, 获取到的文件大小是 76395 字节.

我无聊至极, 使用 du 命令来获取该文件的大小:

1
du -sh mm.aac 

显示文件大小是 76KB.

MacOS 上面查看文件属性, 获取到的大小也是 76KB

1

二者获取的文件大小一致, 可以看出文件的大小是正确的.

于是我查了一下在 windows 上面的该文件的大小, 如下截图:

1

可以看出, MacOS 上面文件大小是按照 1000 单位来计算的, 而 windows 上面是按照 1024 单位来计算的.

1
2
76395 / 1000 = 76.395
76395 / 1024 = 74.604

按照 GNU 的习惯,单纯的 K M G T 分别指代 KiB MiB GiB TiB 以 1024 进制数据量单位,用 KB MB GB TB 以 1000 进制数据量单位.

所以, 在 windows 上面准确表示 76395bytes 应该是 74.6KiB.

推荐

在线计算 单位换算.

GO: 写第一个 GOLang 程序

发表于 2018-03-11 | 分类于 Server |

最近在看人工智能相关的知识,无意中发现了一个巨牛的 人工智能教程,分享一下给大家。

教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点 这里 可以直接看教程。

简介

本文分享在 MacOS 上面通过 pkg 的方式安装 GO 编程环境, 以及编译运行第一个使用 GOLang 编写的程序.

其他关于 GO 的安装方式, 如使用 Homebrew 安装或者使用源码安装的方式, 请自行尝试.

下载安装

在 这里 下载 GO 的 pkg 安装包.

但是你需要科(fan)学(qiang)才能下载.

1

你也可以下载 Stable versions.

下载完成后, 直接双击安装即可.

配置环境变量

安装完成后, GO 安装目录在 /usr/local 下面.

安装目录如下图所示:

1

需要将其设置到环境变量中, 才能在终端使用其相关的命令.

编辑 ~/.bash_profile, 增加下面几行即可.

1
2
3
4
5
# Golang

export GOROOT=/usr/local/go
export GOPATH=~/gowork
export PATH=$GOROOT/bin:$GOPATH:$PATH

记住, GOROOT 和 GOPATH 必须设置.
gowork 是我自己新建的目录. GOPATH 可以理解为工作目录.

关于 GOPATH 可以参考这个文档 SettingGOPATH.

我刚开始没有配置 GOROOT 变量, 执行 go 命令会提示警告, 警告内容如下:

1
warning: GOPATH set to GOROOT (/usr/local/go) has no effect

编辑保存, 然后使其立即生效.

1
source ~/.bash_profile

检查环境变量是否生效.

1
go version

对应输出

1
go version go1.9.4 darwin/amd64

也可以使用 go env 来查看更加详细的内容.

编译运行 GOLang 程序

1.编写代码

编辑一个 Hello GOLang 的程序.

在任意一个文件夹位置, 新建一个 go_hello.go 的文件.

1
touch go_hello.go

写入如下程序:

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
fmt.Println("Hello, GOLang!")
}

2.编译运行

使用 go build 命令编译.

1
go build go_hello.go

编译成功后, 生成可执行文件 go_hello. 执行即可.

1
./go_hello

或者直接运行, 执行下面的命令.

1
go run go_hello.go 

这个不会生成可执行文件.

至此, GO 的编程环境已经搭建完成, 比较简单.

卸载 GO

如果你想卸载 GO 环境, 只需要三个步骤.

1.删除安装目录

1
sudo rm -fr /usr/local/go

2.删除 go 文件

1
sudo rm -f /etc/paths.d/go

3.删除环境变量

编辑 etc, 删除对应添加的 go 环境变量即可.

参考

GO: Getting Started

GO: DOC

SettingGOPATH

C 指针与 OC 对象之间的转换

发表于 2018-03-07 | 分类于 iOS |

Core Foundation 框架

Core Foundation 框架 (CoreFoundation.framework) 是一组 C 语言接口, 简称 CF.

它们为 iOS 应用程序提供基本数据管理和服务功能.

如 Core Graphics、Core Text,并且我们可能需要将 CF 对象和OC 对象进行相互转化,ARC 下,编译器不会自动管理 CF 对象的内存,我们需要手动管理.

创建一个 CF 对象使用后, 需要使用 CFRelease 将其手动释放, 换句话说, Core Foundation 对象类型不在 ARC 管理范畴内.

如何将 CF 和 OC 对象有效的结合起来, 在 ARC 环境下, 提供了 桥接 的技术, 即 ARC 下 OC 对象和 Core Foundation 对象之间的桥梁.

ARC 桥接

ARC 下 C 指针与 OC 指针(对象)之间转换, 一般会用到下面的方法.

1
2
3
4
5
__bridge_retained <#CF type#>)<#expression#>

__bridge_transfer <#Objective-C type#>)<#expression#>

__bridge <#type#>)<#expression#>

也就是所谓的 桥接, 它是 Object-C 在 ARC 环境下开发出来的一种用作转换 C 指针跟 OC (类)指针的一种转换技术, 所以是 ARC 下的称谓, 在 MRC 下没有 桥接.

针对内存管理问题,ARC 可以管理 Objective-C 对象, 但不支持 Core Foundation 对象的管理,所以转换后要注意一个问题:谁来释放使用后的对象.

结合 ARC 和 内存管理, 下面分别介绍一下.

Core Foundation 对象必须使用 CFRetain 和 CFRelease 来进行内存管理.
当使用 Objective-C 和 Core Foundation 对象相互转换的时候,必须让编译器知道,到底由谁来负责释放对象,是否交给 ARC 处理, 只有正确的处理,才能避免内存泄漏和 double free 导致程序崩溃.

__bridge_retained <#CF type#>)<#expression#>

__bridge_retained 等同于 CFBridgingRetain().

将 Objective-C 对象转换为 Core Foundation 对象,把对象所有权桥接给 Core Foundation 对象,同时剥夺 ARC 的管理权,后续需要开发者使用 CFRelease 或者相关方法手动来释放 CF 对象.

示例:

1
2
3
4
5
6
7
8
9
10
void *cPointer;
NSObject *objc = [[NSObject alloc] init];

//将 OC 对象转换为 C 指针
cPointer = (__bridge_retained void*)objc;

//use cPointer ...

//需要释放资源
CFRelease(cPointer);

在 ARC 下, CFBridgingRetain 实现如下:

1
2
3
NS_INLINE CF_RETURNS_RETAINED CFTypeRef _Nullable CFBridgingRetain(id _Nullable X) {
return (__bridge_retained CFTypeRef)X;
}

关于 CFTypeRef, 如下:

1
typedef const CF_BRIDGED_TYPE(id) void * CFTypeRef;

所以 CFBridgingRetain 返回值是 const void * 类型的.

上面的示例可以改写为:

1
2
3
4
5
6
7
const void *cPointer;
const NSObject *objc = [[NSObject alloc] init];
cPointer = CFBridgingRetain(objc);

//use cPointer ...

CFRelease(cPointer);

__bridge_transfer <#Objective-C type#>)<#expression#>

__bridge_transfer 等同于 CFBridgingRelease().

将非 OC 对象转换为 OC 对象,同时将对象的管理权交给 ARC,开发者无需手动管理内存.

示例:

1
2
3
4
5
6
7
8
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
CFStringRef strUUID = CFUUIDCreateString(kCFAllocatorDefault, uuid);
NSString *str = (__bridge_transfer NSString *)strUUID;

//无需释放 strUUID
//CFRelease(strUUID);

CFRelease(uuid);

CFBridgingRelease 实现如下:

1
2
3
NS_INLINE id _Nullable CFBridgingRelease(CFTypeRef CF_CONSUMED _Nullable X) {
return (__bridge_transfer id)X;
}

上面的示例可以改写为:

1
2
3
4
5
6
7
8
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
CFStringRef strUUID = CFUUIDCreateString(kCFAllocatorDefault, uuid);
NSString *str = CFBridgingRelease(strUUID);

//无需释放 strUUID
//CFRelease(strUUID);

CFRelease(uuid);

__bridge

__bridge 不改变对象所有权, 需要我们自己来管理内存, 它也是我们经常使用的方法, 从某种程度上来说, 它是上面两个方法的简化版本.

__bridge 可以将 OC 对象 与 C 指针相互转换, 示例:

1
2
3
4
5
//CFString -> OC 对象
CFStringRef cfString = CFStringCreateWithCString(kCFAllocatorDefault, "very", kCFStringEncodingUTF8);
NSString *nsString = (__bridge NSString *)cfString;
NSLog(@"CFString -> NSString: %@", nsString);
CFRelease(cfString);

如果将 CFRelease(cfString) 注释掉, Xcode 的静态检测器会告诉你有内存泄露的情况, 如图:
1

再来另外一个例子, 如下:

1
2
3
4
5
//OC 对象 -> CFString
NSString *nstr = @"itman";
CFStringRef cfStringRef = (__bridge CFStringRef)nstr;
NSLog(@"NSString -> CFString: %@", cfStringRef);
CFRelease(cfStringRef);

无论是使用 CFRelease(cfStringRef), 还是注释掉 CFRelease(cfStringRef), 静态检测器都不会报错. 说明这种情况下, 当前的内存管理已经被 OC 对象管理.

经典例子

NSString 与 CFString 转换

1、NSString → CFString

1
2
3
4
5
6
7
8
9
{
NSString *string = @"veryitman.com";
// 方法1: 不需要释放
CFStringRef cfStr = (__bridge CFStringRef)string;

// 方法2: 需要释放, 这里 retain 了
CFStringRef cfStr2 = (__bridge_retained CFStringRef)string;
CFRelease(cfStr2);
}

2、CFString → NSString

1
2
3
4
5
6
7
8
9
CFStringRef cfStr = CFStringCreateWithCString(kCFAllocatorDefault, "veryitman", kCFStringEncodingUnicode);

// 方法1: 需要释放
NSString *string = (__bridge NSString *)cfStr;
NSLog(@"OC string: %@", string);
CFRelease(cfStr);

// 方法2: 不需要释放, __bridge_transfer 自带 release
string = (__bridge_transfer NSString *)cfStr;

野指针

运行下面的示例:

1
2
3
4
5
6
7
void *p;
{
NSObject *objc = [[NSObject alloc] init];
p = (__bridge void*)objc;
}

NSLog(@"mark: %@", (__bridge NSObject*)p);

会直接 crash, 如图:

1

当 objc 这个对象超出作用域范围,其内存就会被回收,接着在作用域范围外用 void *p 去访问 objc 的内存,就造成了野指针.

结合上面所说的, 我们可以让指针 p 对 objc 进行引用即 retain 操作, 修改如下:

1
2
3
4
5
6
7
8
9
10
11
void *p;
{
NSObject *objc = [[NSObject alloc] init];
//p = (__bridge void*)objc;
p = (__bridge_retained void*)objc;
}

NSLog(@"mark: %@", (__bridge NSObject*)p);

// 一定要释放
CFRelease(p);

可以正常的运行. 还可以修改为另一种方式:

1
2
3
4
5
6
7
8
9
10
11
12
void *p;
{
NSObject *objc = [[NSObject alloc] init];
//p = (__bridge void*)objc;
//p = (__bridge_retained void*)objc;
p = (void *)CFBridgingRetain(objc);
}

NSLog(@"mark: %@", (__bridge NSObject*)p);

// 一定要释放
CFRelease(p);

扫码关注,你我就各多一个朋友~

音视频编程: iOS 推流

发表于 2018-02-26 | 分类于 iOS |

本篇分享是基于 音视频编程: 构建 nginx 推流服务器 这篇文章的, 之前是使用 ffmpeg 命令进行推流并且都是在 macos 上面进行的操作.

现在在 iOS 平台上使用 LFLiveKit 进行推流, 然后使用 VLC 播放器播放流视频.

该系列博文:

  • 音视频编程: 编译 faac/faad2

  • 音视频编程: iOS 使用 faac 编码

  • 音视频编程: 简单分析 WAV 文件

  • 音视频编程: iOS 使用 faad2

  • 音视频编程: 构建 nginx 推流服务器

  • 音视频编程: iOS 推流 (本篇)

基本概念

一般直播过程包括对音视频的采集、处理、编码、封包、推流、传输、转码、分发、解码、播放等.

推流, 指使用推流工具把直播内容传输到服务器的过程. 本篇分享使用 LFLiveKit 工具将 iPhone 摄像头和麦克风采集的数据推流到本地流服务器.

拉流, 指对服务器直播的内容,用指定地址进行拉取(播放)的过程, 本篇分享使用 VLC 充当拉流的角色.

集成 LFLiveKit

LFLiveKit 是一个开源的推流 SDK, 支持 H264 and AAC 的硬编码, 支持美颜(集成了 GPUImage).

使用 LFLiveKit 比较简单, 按照其 demo 集成即可.

1.工程配置info.plist

需要访问相机和麦克风权限.

1
2
3
Privacy - Camera Usage Description

Privacy - Microphone Usage Description

2.请求相机和麦克风权限.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
- (void)requestAccessForVideo {

__weak typeof(self) _self = self;
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];

switch (status) {

case AVAuthorizationStatusNotDetermined: {

[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {

if (granted) {

dispatch_async(dispatch_get_main_queue(), ^{
[_self.session setRunning:YES];
});
}
}];

break;
}

case AVAuthorizationStatusAuthorized: {

dispatch_async(dispatch_get_main_queue(), ^{
[_self.session setRunning:YES];
});

break;
}

case AVAuthorizationStatusDenied:
case AVAuthorizationStatusRestricted:
// 用户明确地拒绝授权,或者相机设备无法访问
break;

default:
break;
}
}

- (void)requestAccessForAudio {

AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];

switch (status) {

case AVAuthorizationStatusNotDetermined: {
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
}];
break;
}

case AVAuthorizationStatusAuthorized: {
break;
}

case AVAuthorizationStatusDenied:
case AVAuthorizationStatusRestricted:
break;
default:
break;
}
}

务必要调用 LFLiveSession 的 setRunning 方法.

完整的工程代码, 后续我会放到 Github 上面. 方便有需要的下载.

推流播放

上面的基本配置完成后, 可以开始推流了.

1.启动 nginx

1
sudo nginx

2.启动推流

1
2
3
4
5
6
7
- (void)startLive {

LFLiveStreamInfo *streamInfo = [LFLiveStreamInfo new];
streamInfo.url = sRTMPUrl;
[self.session startLive:streamInfo];
self.session.beautyFace = YES;
}

这里的 sRTMPUrl 是一个常量定义:

1
static NSString * const sRTMPUrl = @"rtmp://192.168.1.102:1935/rtmplive/channel";

192.168.1.102 是我的本机(mac) ip 地址.

如果一切正常的话, 你在手机上面可以看到自己的预览画面.

3.VLC 播放

使用 VLC 播放 rtmp 的流, 打开 VLC 播放器, 选择 File/Open Network, 输入 rtmp://192.168.1.102:1935/rtmplive/channel 这个地址即可实时播放了.

这里注意一定要使用本机的 ip 地址, 不要使用 localhost:1935 类似的地址, 否则无法播放和推流.

后续分享如何在 iOS 上面使用播放器来播放直播流.

音视频编程: 构建 nginx 推流服务器

发表于 2018-02-25 | 分类于 iOS |

本篇分享的主要内容

如何在 macos 中构建本地的推流服务器(nginx + rtmp-nginx-module), 并使用 ffmpeg 命令进行视频推流, 然后使用 VLC 播放器播放视频.

该系列博文:

  • 音视频编程: 编译 faac/faad2

  • 音视频编程: iOS 使用 faac 编码

  • 音视频编程: 简单分析 WAV 文件

  • 音视频编程: iOS 使用 faad2

  • 音视频编程: 构建 nginx 推流服务器 (本篇)

安装 rtmp-nginx-module

之前在测试 php 程序的时候, 我已经安装过 nginx, 但是发现这样在配置 rtmp-nginx-module 的时候, 无法成功.

配置文件修改完成之后, nginx 总是无法识别 rtmp, 报错如下:

1
[emerg] 30766#0: unknown directive "rtmp" in /usr/local/etc/nginx/nginx.conf:40

现在需要卸载原来已经安装的 nginx, 命令如下:

1
brew uninstall nginx

再次安装:

1
brew install nginx-full --with-rtmp-module 

耐心等待一会 ~~

nginx 的版本(nginx -v):

1
nginx/1.12.2

rtmp-nginx-module 版本:

1
1.1.7.11-dev_2

配置 rtmp

编辑 nginx 的配置文件, 文件位置位于:

1
/usr/local/etc/nginx/nginx.conf

在 http {} 后面写入如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rtmp {
server {
#rtmp协议的默认端口号是 1935
listen 1935;
#直播流配置, 访问 path 是 rtmplive
application rtmplive {
#开启实时
live on;
#设置 rtmp 引擎的最大连接数. 默认为off
max_connections 1024;
#不记录数据
record off;
}
}
}

注意: rtmplive 是固定的.

具体的 nginx 配置详见后面的附录内容.

推流

**1.安装 ffmpeg 即可. **

1
brew install ffmpeg

这个过程有点久, 如果你没有梯子基本安装不了 [大哭]~

2.安装 VLC

直接去官网下载 dmg 包, 安装即可.

打开 VLC 然后选择从 File/Open Network 打开文件, 如下图所示:

1

在弹出的框中, 写入推流地址 rtmp://localhost:1935/rtmplive/channel, 如图所示:

1

直接点击 Open.

注意: 如果 localhost 无法播放, 请更换为你的本机 ip, 如我的本机 ip 是 192.168.1.122, 对应的播放地址为 rtmp://192.168.1.122:1935/rtmplive/channel.
如果还是无法成功, 请关闭 macos 的防火墙.

3.启动 nginx

1
sudo nginx

可以在浏览器访问如下地址, 看看 nginx 是否启动成功.

1
http://localhost:8080/index.html

4.ffmpeg 推流

推流的命令如下:

1
ffmpeg -re -i ~/Desktop/launcher.mp4 -vcodec libx264 -acodec aac -strict -2 -f flv rtmp://localhost:1935/rtmplive/channel

其中 launcher.mp4 是我自己从网络上面下载的一个视频文件, 时长大约是 5 分钟.

在 VLC 中, 就可以看到推流播放的视频了. [开心]~~

附录

nginx 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
worker_processes  1;

error_log /usr/local/etc/nginx/logs/error.log debug;

pid /usr/local/var/run/nginx.pid;

events {
worker_connections 256;
}

http {
include mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /usr/local/etc/nginx/logs/access.log main;

sendfile on;

keepalive_timeout 65;

index index.html index.php;

include /usr/local/etc/nginx/sites-enabled/*;
include /usr/local/etc/nginx/conf.d/*;

server {
listen 8080;
server_name localhost;

#charset koi8-r;

#access_log logs/host.access.log main;

location / {
root /Users/me/workspace/projs/phpwork/; #web的根目录
index index.php index.html index.htm;
}
}
}

rtmp {
server {
#rtmp协议的默认端口号是1935
listen 1935;
#直播流配置,访问路径是rtmplive
application rtmplive {
#开启实时
live on;
#为rtmp引擎设置最大连接数.默认为off
max_connections 1024;
#不记录数据
record off;
}
}
}

视频文件

使用下载的 mp4 文件, 有些无法播放, 暂时估计应该是码率的问题, 大家在测试过程中, 发现视频无法播放, 最后试试其他视频文件.

另外, 视频文件不要太小, 尽量能让其播放时长在 3-5 分钟.

brew install/uninstall 报错

报错信息如下:

1
2
3
4
5
6
Error: undefined method `patch' for #<Resource:0x000001040877a8>
/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/gcc@4.9.rb:55:in `block in <class:GccAT49>'
/usr/local/Homebrew/Library/Homebrew/resource.rb:49:in `instance_eval'
/usr/local/Homebrew/Library/Homebrew/resource.rb:49:in `initialize'
/usr/local/Homebrew/Library/Homebrew/software_spec.rb:111:in `new'
/usr/local/Homebrew/Library/Homebrew/software_spec.rb:111:in `resource'

最终的解决方案是重新安装 Homebrew, 命令如下:

1
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

检查本机的 ip

1
ifconfig | grep "inet " | grep -v 127.0.0.1

NSLog 格式化输出 NSInteger/NSUInteger

发表于 2018-02-22 | 分类于 iOS |

最近在看人工智能相关的知识,无意中发现了一个巨牛的 人工智能教程,分享一下给大家。

教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点 这里 可以直接看教程。

简介

在 Xcode 中 使用 NSLog 打印 NSInteger/NSUInteger 数据, 经常会遇到类似的警告.

如下图所示:
1

但是在 iphone 4s(32位)机器上就没有任何警告, 只是在64位的机型上面有这样的警告.

在 NSObjCRuntime.h 里面定义 NSInteger 和 NSUInteger 如下:

1
2
3
4
5
6
7
#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif

也就是说 64 位下是 long 类型,32 位下是 int 类型.

所以, 上面的代码在 32 位机型上面不会有警告.

按照 Xcode 的提示, Fix it:

1
2
3
4
5
NSUInteger iun = 3;
NSLog(@"iun: %lu", (unsigned long)iun);

NSInteger nsi = 6;
NSLog(@"nsi: %ld", (long)nsi);

这样无论在 32 位还是在 64 位机型上面, 都没有警告了.

Twitter 帖子

在 twitter 有个帖子 说了另一种解决办法, 原文如下:

1
%zd, %tu, %tx (signed, unsigned, hex) currently format NSInteger and NSUInteger with no warnings.

意思是使用 zd 可以格式化有符号的如 NSInteger, 使用 tu 格式化无符号的如 NSUInteger. 而 tx 可以用来格式化输出 16 进制数据.

按照这个说法, 修改如下:

1
2
3
4
5
NSUInteger iun = 3;
NSLog(@"iun: %zd", iun);

NSInteger nsi = 6;
NSLog(@"nsi: %td", nsi);

或者这样修改:

1
2
3
4
5
NSUInteger iun = 3;
NSLog(@"iun: %tu", iun);

NSInteger nsi = 6;
NSLog(@"nsi: %td", nsi);

确实没有了警告的问题.

我在实践过程中发现一个问题: 使用 zu 来格式化输出 NSUInteger, 在 32 位机器上面, 仍旧会报警告.

1
2
NSUInteger iun = 3;
NSLog(@"iun: %zu", iun);

这就让我开始怀疑 z 和 t 的可靠性了.

下面接着了解一下它们.

关于 z t

关于 z t 的说明可以参考 String Format Specifiers

1

可以说明:

格式化 z 可以用来代表 size_t.
格式化 t 可以用来代表 ptrdiff_t.

ptrdiff_t 是C/C++标准库中定义的一个与机器相关的数据类型.
ptrdiff_t 类型变量通常用来保存两个指针减法操作的结果, 其定义在 C99 标准中.

size_t 类型用于指明数组长度, 它必须是一个正数, 而 ptrdiff_t 类型则应保证足以存放同一数组中两个指针之间的差距, 它有可能是负数.

即: size_t 是 unsigned 类型, 而 ptrdiff_t 则是 signed 整型.

1
2
printf("size_t bytes = %d\n" ,sizeof(size_t));
printf("ptrdiff_t bytes = %d\n" ,sizeof(ptrdiff_t));

在 32 位机型中输出结果:

1
2
size_t bytes = 4
ptrdiff_t bytes = 4

在 64 位机型中输出结果:

1
2
size_t bytes = 8
ptrdiff_t bytes = 8

这说明, sizet_t 和 ptrdiff_t 是和机器类型相关的跨平台的.

系统定义:

1
2
typedef __SIZE_TYPE__ size_t;
typedef __PTRDIFF_TYPE__ ptrdiff_t;

32 位机型定义:

1
2
#define __SIZE_TYPE__ long unsigned int
#define __PTRDIFF_TYPE__ int

64 位机型定义:

1
2
#define __SIZE_TYPE__ long unsigned int
#define __PTRDIFF_TYPE__ long int

终极解决方案

既然在 Xcode 里面提示 Fix it 可以使用, 那么为了提高代码的健壮性和可维护性, 可以定义宏定义来解决.

宏定义如下:

1
2
3
4
5
6
7
#if __LP64__
#define MZNSI @"ld"
#define MZNSU @"lu"
#else
#define MZNSI @"d"
#define MZNSU @"u"
#endif //__LP64__

使用方法, 如下:

1
2
3
4
NSInteger nsi = 6;
NSUInteger iun = 3;
NSLog(@"NSInteger nsi = %"MZNSI, nsi);
NSLog(@"NSUInteger iun = %"MZNSU, iun);

附录

给出常用的格式化打印输出字符串中的格式符, 如下所示:

1)%c:对应参数是一个 int 类型,但实际运行时会将该 int 类型对象转换为 unsigned char 类型.

2)%d 或者 %i:对应参数是一个 int 类型. 只不过, %d 一般用于十进制, %i 可以对应各种进制的数据.

二者在打印输出上面没有什么太大的差别. 但是在接收输入(scanf)上, %d 用于十进制, %i 对应各种进制的数据. 可以参考 Difference between %d and %i format specifier in C language 这篇文章.

3)%f:对应参数是一个 double 类型.

4)%ld:对应参数是一个 long int 类型.

5)%s:对应参数是一个 const char* 类型,表示输出一个字符串.

6)%u:对应参数是一个 unsigned int 类型.

7)%zu:对应参数是一个 size_t 类型.

8)%td:对应参数是一个 ptrdiff_t 类型.

9)%x(或 %X):对应参数是一个 int 类型,不过会以十六进制形式输出,其中大于 9 的数字根据字母 x 大小写进行转换,如果是 %x,则大于 9 的数用 a~f 表示;如果是 %X,则用 A~F 表示.

10)%%:输出一个 % 符号.

推荐

Apple:String Format Specifiers


扫码关注,期待与你的交流~

音视频编程: iOS 使用 faad2

发表于 2018-02-20 | 分类于 iOS |

最近在看人工智能相关的知识,无意中发现了一个巨牛的 人工智能教程,分享一下给大家。

教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点 这里 可以直接看教程。

简介

本次分享使用 faad2 解码 AAC 音频文件, 将 AAC 文件转换为 WAV 文件并使用 AVAudioPlayer 进行播放。

在博文 音视频编程: 简单分析 WAV 文件 给大家简单的分析了一下 WAV 的数据头协议, 其实也是为了这篇博文来服务的, 所以阅读本文之前, 建议先看上文。

该系列博文:

  • 音视频编程: 编译 faac/faad2

  • 音视频编程: iOS 使用 faac 编码

  • 音视频编程: 简单分析 WAV 文件

  • 音视频编程: iOS 使用 faad2 (本篇)

  • 音视频编程: 构建 nginx 推流服务器

例子介绍

本文以一个实际的例子, 使用 faad2的各个函数来解码 AAC 数据.

主要有以下几个步骤:

  • 获取输入文件
  • 获取 faad 解码器句柄
  • 初始化 faad 解码器
  • 根据文件解析文件帧, 并写入输出文件中
  • 写入文件头将其封装为 WAV 格式的音频文件
  • 关闭 faad 解码器句柄

工程实战

引入 faad2

将编译好的 faad2 导入工程即可.

1
其中关键的 API 在 neaacdec.h 中有描述.

工程效果图:
1

编码实现

在 音视频编程: 简单分析 WAV 文件 中已经定义了 WAV 的数据头.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct MZWavAudioFileHeader
{
char riff[4]; // 字符串 "RIFF"
uint32_t totalLength; // 文件总大小, 包括PCM 数据大小和该文件头大小
char wave[4]; // 字符串 "WAVE"
char fmt[4]; // 字符串 "fmt "
uint32_t format; // WAV 头大小, 固定为值 16
uint16_t pcm; // PCM 编码方式, 固定值为 1
uint16_t channels; // 声道数量, 为 2
uint32_t frequency; // 采样频率
uint32_t bytes_per_second; // 每秒字节数(码率), 其值=采样率x通道数x位深度/8
uint16_t bytes_by_capture; // 采样块大小
uint16_t bits_per_sample; // 采样点大小, 这里是 16 位
char data[4]; // 字符串 "data"
uint32_t bytes_in_pcmdata; // pcm 数据长度
};

现在实现写入数据头的方法 mz_write_wav_header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* 写入 wav 头数据.
*
* @param file wav 文件指针.
* @param total_samples_per_channel 每个声道的采样数.
* @param samplerate 采样率.
* @param channels 声道数.
*/
void mz_write_wav_header(FILE *file, int total_samples_per_channel, int samplerate, int channels) {

if (NULL == file) {
return;
}

if (total_samples_per_channel <= 0) {
return;
}

printf("FAAD. total_samples_per_channel: %i, samplerate: %i, channels: %i\n",
total_samples_per_channel, samplerate, channels);

struct MZWavAudioFileHeader wavHeader;

// 写入 RIFF
strcpy(wavHeader.riff, "RIFF");

wavHeader.bits_per_sample = 16;

wavHeader.totalLength = (total_samples_per_channel * channels * wavHeader.bits_per_sample/8) + sizeof(wavHeader) - 8;

// 写入 WAVE 和 fmt
strcpy(wavHeader.wave, "WAVE");
strcpy(wavHeader.fmt, "fmt ");

wavHeader.format = 16;
wavHeader.pcm = 1;
wavHeader.channels = channels;
wavHeader.frequency = samplerate;

// 每秒的字节数(码率)=采样率x通道数x位深度/8
wavHeader.bytes_per_second = wavHeader.channels * wavHeader.frequency * wavHeader.bits_per_sample/8;

wavHeader.bytes_by_capture = wavHeader.channels*wavHeader.bits_per_sample/8;

wavHeader.bytes_in_pcmdata = total_samples_per_channel * wavHeader.channels * wavHeader.bits_per_sample/8;

// 写入 data
strcpy(wavHeader.data, "data");

fwrite(&wavHeader, 1, sizeof(wavHeader), file);
}

解码主要用到了 FAAD2 中的 NeAACDecDecode 函数. 函数原型如下:

1
2
3
4
void* NEAACDECAPI NeAACDecDecode(NeAACDecHandle hDecoder,
NeAACDecFrameInfo *hInfo,
unsigned char *buffer,
unsigned long buffer_size);

对应帧定义的结构体: NeAACDecFrameInfo, 定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
typedef struct NeAACDecFrameInfo
{
unsigned long bytesconsumed;
unsigned long samples;
unsigned char channels;
unsigned char error;
unsigned long samplerate;

/* SBR: 0: off, 1: on; upsample, 2: on; downsampled, 3: off; upsampled */
unsigned char sbr;

/* MPEG-4 ObjectType */
unsigned char object_type;

/* AAC header type; MP4 will be signalled as RAW also */
unsigned char header_type;

/* multichannel configuration */
unsigned char num_front_channels;
unsigned char num_side_channels;
unsigned char num_back_channels;
unsigned char num_lfe_channels;
unsigned char channel_position[64];

/* PS: 0: off, 1: on */
unsigned char ps;
} NeAACDecFrameInfo;

具体的解码实现, 我放到了 Github 上面了, 大家可以去 这里 查看.

麻烦

解码 aac, 解决采样频率和通道数不对的问题

1
2
3
4
//防止采样频率加倍
NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration(decoder);
conf->dontUpSampleImplicitSBR = 1;
NeAACDecSetConfiguration(decoder, conf);
1
2
3
4
5
6
//从双声道的数据中提取单通道  
for(i=0,j=0; i<4096 && j<2048; i+=4, j+=2) {

frame_mono[j]=pcm_data[i];
frame_mono[j+1]=pcm_data[i+1];
}

具体可以查阅 FAAD2 的源码. 感谢 使用FAAD库解码AAC实例及 及 faad解码后的通道数不正确的问题 提供.

音视频编程: 简单分析 WAV 文件

发表于 2018-02-20 | 分类于 iOS |

最近在看人工智能相关的知识,无意中发现了一个巨牛的 人工智能教程,分享一下给大家。

教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点 这里 可以直接看教程。


这篇分享是下篇 音视频编程: iOS 使用 faad2 的预备知识, 如果要解码 WAV 文件, 首先需要了解一下 WAV 音频文件的格式。

今天说的是 线性 PCM 对应的 WAV 格式的数据。

PCM 的种类

  • 线性化 PCM
  • A律量化的 PCM
  • U律量化的 PCM
  • AD PCM
  • GSM

该系列博文:

  • 音视频编程: 编译 faac/faad2

  • 音视频编程: iOS 使用 faac 编码

  • 音视频编程: 简单分析 WAV 文件(本篇)

  • 音视频编程: iOS 使用 faad2

  • 音视频编程: 构建 nginx 推流服务器

WAV 简介

WAV 只是该音频文件的后缀名, 其完整名称缩写是 WAVE,WAVE(Waveform Audio File Format), 采用RIFF(Resource Interchange File Format)文件格式结构。

WAV 格式的音频文件通常用来保存 PCM 格式的原始音频数据,通常被称之为无损音频。

WAV 音频文件, 粗略来说是 WAV 数据头 + PCM 数据组成的. 裸数据 PCM 外面包了一层文件头,WAV 实质为一个 RIFF 文件.

WAV 数据头

关于 WAV 音频文件的数据头定义如下图所示:

1

最前面的4个字节用来标示是 RIFF 字符串。

可以看出, 一般的 WAV 文件的数据头为 44 个字节, 其后面跟的是 PCM 数据。

分析 WAV 数据头

使用 hexdump 来看一下 WAV 文件的数据头。

在当前路径下, 有个 wav 格式的音频文件 m.wav, 使用 hexdump 分析一下.

1
hexdump -n 44 m.wav

2

其中, -n 44 表示查看前 44 个字节.

按字节分组的图, 如下所示:
2

1
52 49 46 46

分别是 RIFF 的 ASCII 码.

跟在 RIFF 后面的四个字节是文件的大小信息, 我们先使用 ls 命令看一下该文件的大小。

1
ls -al

输出文件大小为(字节数): 1080808

1
staff  1080808 Jan 25 15:44 m.wav

RIFF 后面的四个字节分别是: e0 7d 10 00, 由于该存储使用了小端序(Little-Endian 存储,也就是说对其中的数据,低位字节在前,高位字节在后), 所以16进制表示为: 0x00107de0, 对应的字节大小是 1080808.

上面说到, 线性 PCM, 其实在该文件头中, 第17到第第22个字节(上图红色的5和6组合)标示了 PCM 的类型, 即:

1
10 00 00 00 01 00 

其他类型的 PCM 类型定义为:

A律量化的PCM: 12 00 00 0006 00
U律量化的PCM: 12 00 00 00 07 00
AD PCM: 32 00 00 00 02 00
GSM: 14 00 00 00 31 00

最后4个字节表示真正 PCM 数据的文件大小, 即: 0x00107dbc, 其10进制大小为: 1080764, 用总文件大小减去 1080764, 就是文件头的大小, 如下:

1
1080808 - 1080764 = 44

其他对应的数据, 大家可以对照表自行分析。

定义数据头

数据类型

  • char 占用 1 个字节
  • uint32_t 占用 4 个字节
  • uint16_t 占用 2 个字节

这里使用结构体定义 WAV 文件头, 其定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct MZWavAudioFileHeader
{
char riff[4]; // 字符串 "RIFF"
uint32_t totalLength; // 文件总大小, 包括PCM 数据大小和该文件头大小
char wave[4]; // 字符串 "WAVE"
char fmt[4]; // 字符串 "fmt "
uint32_t format; // WAV 头大小, 固定为值 16
uint16_t pcm; // PCM 编码方式, 固定值为 1
uint16_t channels; // 声道数量, 为 2
uint32_t frequency; // 采样频率
uint32_t bytes_per_second; // 每秒字节数(码率), 其值=采样率x通道数x位深度/8
uint16_t bytes_by_capture; // 采样块大小
uint16_t bits_per_sample; // 采样点大小, 这里是 16 位
char data[4]; // 字符串 "data"
uint32_t bytes_in_pcmdata; // pcm 数据长度
};

可以使用下面代码来计算该结构体所占的字节数(结果是44):

1
int wav_header_size = sizeof(struct MZWavAudioFileHeader);

音视频编程: iOS 使用 faac 编码

发表于 2018-02-16 | 分类于 iOS |

简介

本文分享如何将 WAV 格式的音频文件转换(编码)为 AAC 格式的音频文件并使用 AVAudioPlayer 播放编码后的文件.

这里的编码功能使用 faac 这个库来实现.

该系列博文:

  • 音视频编程: 编译 faac/faad2

  • 音视频编程: iOS 使用 faac 编码(本篇)

  • 音视频编程: 简单分析 WAV 文件

  • 音视频编程: iOS 使用 faad2

  • 音视频编程: 构建 nginx 推流服务器

函数介绍

获取 faac 的版本

1
int FAACAPI faacEncGetVersion(char **faac_id_string, char **faac_copyright_string);

示例:

1
2
3
4
char *version;
char *copyright;
faacEncGetVersion(&version, &copyright);
printf("FAAC version: %s, copyright: %s", version, copyright);

打印结果:

1
2
3
4
FAAC version: 1.28, copyright: FAAC - Freeware Advanced Audio Coder (http://www.audiocoding.com/)
Copyright (C) 1999,2000,2001 Menno Bakker
Copyright (C) 2002,2003 Krzysztof Nikiel
This software is based on the ISO MPEG-4 reference source code.

打开并初始化 faac 编码器

1
2
3
4
5
6
7
8
// sampleRate: 采样率
// numChannels: 通道数量,1-单声道 2-立体声
// inputSamples: 编码后的数据长度
// maxOutputBytes: 编码后的信息最大长度
faacEncHandle FAACAPI faacEncOpen(unsigned long sampleRate,
unsigned int numChannels,
unsigned long *inputSamples,
unsigned long *maxOutputBytes);

该函数返回一个 faac 编码器句柄. 其很多函数都需要这个句柄.

关闭 faac 编码器

1
int FAACAPI faacEncClose(faacEncHandle hEncoder);

将开启 faac 编码器返回的句柄传入即可.

获取配置和设置配置

1
2
3
4
5
6
faacEncConfigurationPtr FAACAPI
faacEncGetCurrentConfiguration(faacEncHandle hEncoder);


int FAACAPI faacEncSetConfiguration(faacEncHandle hEncoder,
faacEncConfigurationPtr config);

faac 的配置被定义为了一个结构体 faacEncConfiguration, 大家可以看源码.

编码(编码一帧音频数据)

1
2
3
4
5
6
7
8
//	hEncoder: faacEncOpen 返回的编码器句柄
// inputBuffer: 输入信息缓冲区
// samplesInput: faacEncOpen编码后的数据长度,即缓冲区长度
// outputBuffer: 编码后输出信息缓冲区
// bufferSize: 输出信息长度
int FAACAPI faacEncEncode(faacEncHandle hEncoder, int32_t * inputBuffer, unsigned int samplesInput,
unsigned char *outputBuffer,
unsigned int bufferSize);

实例

可以自己新建一个 iOS 工程.

准备好一个 wav 格式的音频文件.

效果如下图所示:
1

新建一个 C++ 文件, MZCodec.

MZCodec.hpp

1
2
3
4
5
6
7
8
#ifndef MZCodec_hpp
#define MZCodec_hpp

#include <stdio.h>

int codeWAV(const char *srcFilePath, const char *destPath);

#endif /* MZCodec_hpp */

MZCodec.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#include "MZCodec.hpp"

#include "faac.h"
#include <stdio.h>

int codeWAV(const char *srcFilePath, const char *destPath) {

unsigned long nSampleRate = 44100;//采样率
unsigned int nChannels = 2;//声道数
unsigned int nPCMBitSize = 16;//单样本位数

unsigned long nInputSamples = 0;
unsigned long nMaxOutputBytes = 0;

int nRet;
faacEncHandle hEncoder;
faacEncConfigurationPtr pConfiguration;

size_t nBytesRead;
unsigned long nPCMBufferSize;
unsigned char *pbPCMBuffer;
unsigned char *pbAACBuffer;

FILE *fpIn; // WAV file for input
FILE *fpOut; // AAC file for output

/// 获取 faac 版本信息
{
char *version;
char *copyright;
faacEncGetVersion(&version, &copyright);
printf("FAAC version: %s, copyright: %s", version, copyright);
}

fpIn = fopen(srcFilePath, "rb");

if (NULL == fpIn) {
return -2;
}

fpOut = fopen(destPath, "wb");

/// 1. 打开 FAAC
hEncoder = faacEncOpen(nSampleRate, nChannels, &nInputSamples, &nMaxOutputBytes);

if (NULL == hEncoder) {

printf("[ERROR] Failed to call faacEncOpen()\n");
return -1;
}

nPCMBufferSize = nInputSamples * nPCMBitSize / 8;
pbPCMBuffer = new unsigned char[nPCMBufferSize];
pbAACBuffer = new unsigned char[nMaxOutputBytes];

/// 2.1. 获取当前的编码器配置
pConfiguration = faacEncGetCurrentConfiguration(hEncoder);

pConfiguration->inputFormat = FAAC_INPUT_16BIT;
// 对象类型只有为 LOW, iOS 的 AVAudioPlayer 才能播放
pConfiguration->aacObjectType = LOW;
// 0 = Raw; 1 = ADTS
pConfiguration->outputFormat = 1;
pConfiguration->mpegVersion = MPEG4;
pConfiguration->useTns = 1;
pConfiguration->bitRate = 30;

/// 2.2. 配置编码器
nRet = faacEncSetConfiguration(hEncoder, pConfiguration);

//是wav格式, 先读取前面的
fseek(fpIn, 58, SEEK_SET);

do {

//读入的实际字节数,最大不会超过 nPCMBufferSize
nBytesRead = fread(pbPCMBuffer, 1, nPCMBufferSize, fpIn);

//输入样本数,用实际读入字节数计算
//一般只有读到文件尾时才不是 nPCMBufferSize/(nPCMBitSize/8)
nInputSamples = nBytesRead / (nPCMBitSize / 8);

/// 3. 编码
nRet = faacEncEncode(hEncoder,
(int *)pbPCMBuffer,
(unsigned int)nInputSamples,
pbAACBuffer,
(unsigned int)nMaxOutputBytes);

fwrite(pbAACBuffer, 1, nRet, fpOut);

printf("FaacEncEncode returns %d\n", nRet);
} while (nBytesRead > 0);

/// 4. 关闭 FAAC
nRet = faacEncClose(hEncoder);

delete[] pbPCMBuffer;
delete[] pbAACBuffer;

fclose(fpIn);
fclose(fpOut);

return 0;
}

使用 MZCodec 的 Controller 需要命名为 .mm 文件.

ViewController.mm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
NSBundle *bundle = [NSBundle mainBundle];
NSString *resPath = [bundle pathForResource:@"m" ofType:@"wav"];
NSLog(@"The path of wav file: %@", resPath);

NSArray<NSString *> *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *destPath = [[docPath lastObject] stringByAppendingString:@"/out.aac"];
NSLog(@"The path of aac file: %@", destPath);

dispatch_async(dispatch_get_global_queue(0, 0), ^{

codeWAV([resPath UTF8String], [destPath UTF8String]);

dispatch_async(dispatch_get_main_queue(), ^{
//转换完成.
});
});

编码过程需要点时间, 如果文件很大, 时间越久. 所以, 新开线程来进行编码.

编码完成后, 可在对应的沙盒目录找到 out.aac 文件.

具体代码在 Github 上面, 感兴趣的可以点击 前往.

注意事项

这个 aacObjectType 需要注意, 之前我在这里折腾了很久.

1
pConfiguration->aacObjectType = LOW;

如果设置为其他三种, 编码后的 aac 文件, AVAudioPlayer 播放不了, 初始化 AVAudioPlayer 就会报错, 报错信息如下:

1

音视频编程: 编译 faac/faad2

发表于 2018-02-14 | 分类于 iOS |

本文分享如何在 MacOS 上面编译 faac 和 faad2, 编译后的库文件可以直接在 iOS 上面使用.

后续会分享如何编译给 Android 使用的库文件的方法以及实战.

该系列博文:

  • 音视频编程: 编译 faac/faad2(本篇)

  • 音视频编程: iOS 使用 faac 编码

  • 音视频编程: 简单分析 WAV 文件

  • 音视频编程: iOS 使用 faad2

  • 音视频编程: 构建 nginx 推流服务器

简介

faad 是 AAC 格式文件的音频解码库. 相对于 FFmpeg 来说比较轻量和简单. 编译出来全平台静态库文件较小, API 也比较简单, 缺点是功能单一, 只能处理 AAC 格式的音频文件。

faac 是与之对应的 AAC 音频编码库. 支持 MPEG-4 和 MPEG-2 的AAC 编码,其特性包括:可移植性好,快速,支持LC/Main/LTP,通过 Dream 支持 DRM.

这两个库虽然很久没有更新了, 但是其功能已经满足很多业务场景了, 并且很好用。

下载源码

点击 源码链接 即可下载源码

1

自从 2020.08 后该网站已经无法访问了,可以从 What Happened to Audiocoding.com (FAAC & FAAD2) 了解一下。

可以从 Sourceforge.net 下载。

编译 faac

下载完成后, 解压, 将 faac-1.28 放到新建目录 src 下面即可, 目录结构如下:
1

修改 build_faac.sh 权限:

1
chmod +x build_faac.sh

编辑 build_faac.sh 内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#!/bin/sh  

# http://www.linuxfromscratch.org/blfs/view/svn/multimedia/faac.html
# ftp://mirror.ovh.net/gentoo-distfiles/distfiles/

major=1
minor=28
micro=

XCD_ROOT="/Applications/Xcode.app/Contents/Developer"
TOL_ROOT="/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain"
#修改为最新的 sdk 版本
SDK_ROOT="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.2.sdk"
SDK_SML_ROOT="/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator11.2.sdk"

export PATH=$TOL_ROOT/usr/bin:$PATH

work=`pwd`
srcs=$work/src
buid=$work/build
insl=$buid/install
name=faac-${major}.${minor}
pakt=${name}.tar.gz
dest=$work/faac-iOS-${major}.${minor}.${micro}.tgz

rm -rf $srcs $buid $dest && mkdir -p $srcs $buid


archs="i386 x86_64 armv7 armv7s arm64"

for a in $archs; do
case $a in
arm*)
sys_root=${SDK_ROOT}
host=arm-apple-darwin
;;
i386|x86_64)
sys_root=${SDK_SML_ROOT}
host=$a-apple-darwin
;;
esac
prefix=$insl/$a && rm -rf $prefix && mkdir -p $prefix
rm -rf $srcs && mkdir -p $srcs && cd $work && tar xvzf $pakt -C $srcs && cd $srcs/$name


export CC="$TOL_ROOT/usr/bin/clang -arch $a -isysroot $sys_root"
export CXX="$TOL_ROOT/usr/bin/clang++ -arch $a -isysroot $sys_root"
export CXXFLAGS="-arch $a -isysroot $sys_root"
export CFLAGS="-arch $a -isysroot $sys_root"
export LDFLAGS="-isysroot $sys_root"
export LIBS="-L${sys_root}/usr/lib"

chmod +x bootstrap
./bootstrap \
&&./configure \
--host=$host \
--with-sysroot=$sys_root \
--prefix=$prefix \
--disable-shared \
--enable-static \
--disable-faac \
--with-mp4v2 \
&&make && make install
lipo_archs="$lipo_archs $prefix/lib/libfaac.a"

echo 'continue any key pressed..'
read -n 1
done

univ=$insl/universal && mkdir -p $univ/lib
cp -r $prefix/include $univ/
lipo $lipo_archs -create -output $univ/lib/libfaac.a
ranlib $univ/lib/libfaac.a
strip -S $univ/lib/libfaac.a

cd $univ && tar cvzf $dest *

执行 ./build_faac.sh, 开始编译。

编译成功后, 生成 build 目录。
1

其中, universal 生成的库是通用库。

编译 faad2

解压后, 将 faad2-2.7 改名为 faad.

编写编译的脚本文件, 这里取名为 build_fadd2.sh

1
touch build_fadd2.sh

编辑 build_fadd2.sh 文件。

写入如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#!/bin/sh

CONFIGURE_FLAGS="--enable-static --with-pic"

ARCHS="arm64 armv7s armv7 x86_64 i386"

# directories
SOURCE="faad"
FAT="fat-faad"

SCRATCH="scratch-faad"
# must be an absolute path
THIN=`pwd`/"thin-faad"

COMPILE="y"
LIPO="y"

if [ "$*" ]
then
if [ "$*" = "lipo" ]
then
# skip compile
COMPILE=
else
ARCHS="$*"
if [ $# -eq 1 ]
then
# skip lipo
LIPO=
fi
fi
fi

if [ "$COMPILE" ]
then
CWD=`pwd`
for ARCH in $ARCHS
do
echo "building $ARCH..."
mkdir -p "$SCRATCH/$ARCH"
cd "$SCRATCH/$ARCH"

if [ "$ARCH" = "i386" -o "$ARCH" = "x86_64" ]
then
PLATFORM="iPhoneSimulator"
CPU=
if [ "$ARCH" = "x86_64" ]
then
SIMULATOR="-mios-simulator-version-min=7.0"
HOST="--host=x86_64-apple-darwin"
else
SIMULATOR="-mios-simulator-version-min=5.0"
HOST="--host=i386-apple-darwin"
fi
else
PLATFORM="iPhoneOS"
if [ $ARCH = "armv7s" ]
then
CPU="--cpu=swift"
else
CPU=
fi
SIMULATOR=
HOST="--host=arm-apple-darwin"
fi

XCRUN_SDK=`echo $PLATFORM | tr '[:upper:]' '[:lower:]'`
CC="xcrun -sdk $XCRUN_SDK clang -Wno-error=unused-command-line-argument-hard-error-in-future"
AS="$CWD/$SOURCE/extras/gas-preprocessor.pl $CC"
CFLAGS="-arch $ARCH $SIMULATOR"
CXXFLAGS="$CFLAGS"
LDFLAGS="$CFLAGS"

CC=$CC CFLAGS=$CXXFLAGS LDFLAGS=$LDFLAGS CPPFLAGS=$CXXFLAGS CXX=$CC CXXFLAGS=$CXXFLAGS $CWD/$SOURCE/configure \
$CONFIGURE_FLAGS \
$HOST \
--prefix="$THIN/$ARCH" \
--disable-shared \
--without-mp4v2

make clean && make && make install-strip
cd $CWD
done
fi

if [ "$LIPO" ]
then
echo "building fat binaries..."
mkdir -p $FAT/lib
set - $ARCHS
CWD=`pwd`
cd $THIN/$1/lib
for LIB in *.a
do
cd $CWD
lipo -create `find $THIN -name $LIB` -output $FAT/lib/$LIB
done

cd $CWD
cp -rf $THIN/$1/include $FAT
fi

给予执行权限:

1
chmod +x build_fadd2.sh

此时目录结构如下:
1

执行编译脚本, 在当前目录生成 scratch-faad 和 fat-faad 以及 thin-faad 目录, 如下:
1

使用 fat-faad 里面的库和文件即可。

可以使用下面的命令查看对应的 .a 文件所支持的架构:

1
lipo -info libfaad.a

会得到对应的输出:

1
Architectures in the fat file: libfaad.a are: i386 armv7 armv7s arm64 

同样可以查看 libfaac.a.

后记

编译好的文件, 已经放到 github 上面了, 有需要的可以下载使用, 点击 下载链接 进入。

感谢 fflydev/faac-ios-build 提供编译 faac 的 shell, 只需要将里面对应的 SDK 改为本机最新的 SDK 即可。

参考文章

  • faac 1.28的交叉编译与问题解决

  • 使用FAAD库解码AAC实例

  • 在Linux上使用FDK AAC將音樂轉成M4A(AAC、HE-AAC、HE-AACv2)格式

  • aac文件转码为wav文件

<1…131415…20>

193 日志
16 分类
163 标签
© 2024 veryitman