0%

写在最前面

通过Google搜索了很多资料,可是都并不完善,我还是遇到好多问题,特此写这篇文章总结!

正文

一、通过brew安装 Nginx

终端执行:
brew search nginxbrew install nginx
当前版本 1.6.2
安装完以后,可以在终端输出的信息里看到一些配置路径:
/usr/local/etc/nginx/nginx.conf (配置文件路径)
/usr/local/var/www (服务器默认路径)
/usr/local/Cellar/nginx/1.6.2 (貌似是安装路径)

二、访问localhost:8080

Nginx 默认8080端口,这时已经可以访问了:
localhost:8080
会有一个默认欢迎界面。

三、修改 php-fpm 配置文件

1.执行命令:
sudo cp /private/etc/php-fpm.conf.default /private/etc/php-fpm.conf
2.找到目录下的 php-fpm 文件
/private/etc/php-fpm.conf
3.找到24行的 error_log ,改为(整行替换,注意 ‘;’ 和空格,就是要把‘;’也删除掉,这里我掉坑了)
error_log = /usr/local/var/log/php-fpm.log
否则 php-fpm 时会报错:
ERROR: failed to open error_log (/usr/var/log/php-fpm.log): No such file or directory

四、修改 Nginx 配置

1.打开 nginx.config 文件
/usr/local/etc/nginx/nginx.conf
2.找到 server 的 location 配置,给 index 加一个 index.php

1
2
3
4
location / {
root html;
index index.html index.htm index.php;
}

3.并打开 server 下被注释的 location ~.php$(即删除代码前面的 ‘#’),如下:

1
2
3
4
5
6
location ~ .php$ {
root html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
include fastcgi_params;}

4.并修改 fastcgi_param 参数
fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
改为
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

五、创建 index.php

在 /usr/local/var/www 目录下,删除 index.html,创建 index.php,输入
<?php phpinfo(); ?>

六、启动相关服务

sudo php-fpm
然后访问localhost:8080,看到 php 配置信息,就说明 ok 了

七、nginx常用命令

sudo nginx #启动nginx
sudo nginx -s quit #快速停止nginx
sudo nginx -V #查看版本,以及配置文件地址
sudo nginx -v #查看版本
sudo nginx -s reload|reopen|stop|quit #重新加载配置|重启|快速停止|安全关闭nginx
sudo nginx -h #帮助

修改 nginx.conf 后,重载配置文件
sudo nginx -s reload
停止 nginx 服务器
sudo nginx -s stop
停止 php-fpm可以直接在 Activity Monitor 中停止。也可以使用脚本来停。

八、可能出现的问题

1.访问 index.php 报 403 Forbidden.查看(四.2)步骤中,是否在 index 后添加 index.php。
2.访问 index.php 报 File not found.查看(四.4)中,fastcgi_param 参数是否修改。

!!!以上是我搜索资料大部分的内容(也纠正了一些地方),接下来我记录下我遇到的问题

1、首先,每个人的mac目录地址都不一样,需要看清楚,还有版本问题,nginx版本不同路径都会有相应的变化,配置文件的代码位置也是,上面我已修改。我用的nginx版本是nginx version: nginx/1.13.12
2、问题:2018/05/18 18:21:43 [error] 85581#0: *4 kevent() reported about an closed connection (54: Connection reset by peer) while reading response header from upstream, client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "localhost:8088"
出错的原因是php-fpm未启动。
解决:sudo php-fpm
3、可能使用sudo php-fpm 会报路径出错问题
那就是代表你配置路径那里没有修改,具体可以看上面的 三、修改 php-fpm 配置文件
4、sudo nginx -s reload 报错 nginx: [alert] could not open error log file: open() "/usr/local/nginx/logs/error.log" failed
这个问题我纠结了好久,路径没有错误为啥就找不到,后来才知道是权限问题:
原因:当前用户对该位置没有写入权限
解决办法:
1.使用命令:sudo /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf 以root权限启动
2.使用命令:sudo chmod -R a+rw /usr/local/nginx 给所有用户赋权限(个人学习,不考虑安全问题)
/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf 启动Nginx
5、sudo nginx 启动错误/usr/local/var/run/nginx.pid failed
解决方案:
sudo nginx -c /usr/local/etc/nginx/nginx.conf
sudo nginx -s reload

6、 unable to bind listening socket for address ‘127.0.0.1:9000’: Address already in use
启动php-fpm报错:

1
2
3
zhangweitingdeMacBook-Pro:PHP Tate$ sudo php-fpm
[23-May-2018 10:24:02] ERROR: unable to bind listening socket for address '127.0.0.1:9000': Address already in use (48)
[23-May-2018 10:24:02] ERROR: FPM initialization failed

原因是9000端口已经被占用,停止原先的php-fpm:
zhangweitingdeMacBook-Pro:PHP Tate$ sudo killall php-fpm
zhangweitingdeMacBook-Pro:PHP Tate$ sudo php-fpm
这样就能重启了~

7、访问报403权限问题
ls -l 查看nginx的配置文件发现用户名是root 用户组是wheel , 修改配置文件第一行加上 user root wheel;(这里具体要看你的权限,我是这个) 重启nginx就好了~
sudo nginx -s reload
sudo killall php-fpm
sudo php-fpm

8、访问路径目录权限问题
chmod -R 777 ./PHP2/ (PS 这里为目录结构) 给目录所有权限

补上一张成功的图片,看到之后好欣慰,这是打开了浏览器看到的PHP配置信息
image.png

9、让Nginx访问URL,兼容//模式

在Nginx低版本中,是不支持PATHINFO的,但是可以通过在Nginx.conf中配置转发规则实现:

/ {…..省略部分代码
1
2
3
4
5
  if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?s=/$1 last;
break;
}
}

参考资料

Mac下Nginx环境配置
Mac 平台搭建PHP开发环境:Nginx、PHP、MySQL.md
mac下nginx的安装和配置

Xcode12模板位置:
1
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/File Templates/iOS/Source/Cocoa Touch Class.xctemplate

1、设置导航栏为透明

1
2
3
self.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationBar.shadowImage = UIImage()
self.navigationBar.isTranslucent = true

2、bridging-header是swift刚出的时候,官方提供一种混编的方式。现在cocoapods 的版本已经支持swift 了,当你在安装pods的时候,添加下面这句话.

1
2
# Uncomment this line if you're using Swift
use_frameworks!

它在cocoa pods中已经对Swift进行了配置,所以可以直接使用,不需要创建bridging-header。

推荐swift项目学习:
U17
LBXMLYFM-Swift

3、必要构造器

我们可以通过required关键字来实现必要构造器,子类必须实现父类的必要构造器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Animal {
var name: String
required init(name: String) {
self.name = name
}
}

class Dog: Animal {
var foot: Int
//在重写父类必要构造器的时候不需要加override
required init(name: String) {
foot = 4
super.init(name: name)
}
}

Dog(name: "dog")

有一点需要注意的就是:如果子类继承的构造器能满足必要构造器的要求,则无须在子类中显式提供必要构造器的实现。

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
var name: String
required init(name: String) {
self.name = name
}
}

class Dog: Animal {
var foot = 2
}

Dog(name: "dog")

在我们日常开发中,我们会经常自定义UITableViewCell的子类来实现我们定制化的需求,如果我们没有实现required init?(coder aDecoder: NSCoder)方法的话,我们的代码是编译报错的。查看文档我们发现该方法为NSCoding的方法,且该方法为UIView 必要构造器,所以它的子类必须实现该方法。

1
2
3
4
5
6
7
8
9
10
class CustomTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)

}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

4、访问级别

Swift 为代码中的实体提供了五种不同的访问级别。这些访问级别不仅与源文件中定义的实体相关,同时也与源文件所属的模块相关。

  • openpublic 级别可以让实体被同一模块源文件中的所有实体访问,在模块外也可以通过导入该模块来访问源文件里的所有实体。通常情况下,你会使用 open 或 public 级别来指定框架的外部接口。open 和 public 的区别在后面会提到。

  • internal 级别让实体被同一模块源文件中的任何实体访问,但是不能被模块外的实体访问。通常情况下,如果某个接口只在应用程序或框架内部使用,就可以将其设置为 internal 级别。

  • fileprivate 限制实体只能在其定义的文件内部访问。如果功能的部分实现细节只需要在文件内使用时,可以使用 fileprivate 来将其隐藏。

  • private 限制实体只能在其定义的作用域,以及同一文件内的 extension 访问。如果功能的部分细节只需要在当前作用域内使用时,可以使用 private 来将其隐藏。

open 为最高访问级别(限制最少),private 为最低访问级别(限制最多)。

open 只能作用于类和类的成员,它和 public 的区别主要在于 open 限定的类和成员能够在模块外能被继承和重写,在下面的 子类 这一节中有详解。将类的访问级别显示指定为 open 表明你已经设计好了类的代码,并且充分考虑过这个类在其他模块中用作父类时的影响。

5、pod不重新安装已有的库

pod install --no-repo-update

6、swift 闭包 循环引用

参考链接:
Swift闭包循环引用
Swift与OC真正去理解Block解决循环引用的技巧

7、Swift 5.1 - 字符串

Swift 5.1 (3) - 字符串
iOS Swift中String的常用操作以及数据转化

8、tableView.deselectRow(at: indexPath, animated: true) 注意事项

这里不单单取消选中,还会把cell所有的子控件设置为选中或者高亮

9、VC的便利构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
convenience init(type: ContentType) {
// 便利构造函数中 一定不会有super 对于属性的赋值 一般在self.init()之后 只有self被初始化后,才能对其进行赋值, 不能使用let修饰属性 var 并且告诉编译器其强制解包 一定有值
self.init()
self.type = type
}

override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

10、实现TableView下拉关闭

1
2
3
4
5
6
7
8
let cv:CGFloat = -150 //下拉关闭数值
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = tableView.contentOffset.y;
print(message: offset)
if (offset < cv) {
close()
}
}

11、关于UILabel设置AttributedString以后末尾…不出现的问题

需要重新设置一次label的lineBreakMode属性

1
2
editLabel.attributedText = PublicTools.load_attributedString(model.editor_note.trimmingCharacters(in: .newlines), font: FMFont13, color: CFontColor8, alignment: .left, lineSpacing: kLabelSpace)
editLabel.lineBreakMode = .byTruncatingTail

12、当两个UILabel并排显示时,如何设置约束,让A或者B能显示你想要的需求,就需要用到下面两个约束

ContentHuggingPriority ==> 表示当前的Label的内容不想被拉伸

1
playCountLabel.setContentHuggingPriority(UILayoutPriority(rawValue: 1000), for: .vertical)

ContentCompressionResistancePriority ==> 表示当前的Label的内容不想被收缩

1
playCountLabel.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 1000), for: .horizontal)

默认情况下: HuggingPriority == 250, CompressionResistancePriority == 750

12、@discardableResult的作用

在Swift中,当有返回值的方法未得到接收和使用时通常会出现警告
在正式编译中不会影响编译结果,但是也妨碍代码的美观整洁,在方法上加上“@discardableResult”就可以取消这个警告

1
2
@discardableResult
public func handleUrl(url: String, completion:

还有一种取消警告的方法,不加@discardableResult直接加通配符接收方法返回值

1
_ = XX

13、dynamic关键字

如果您有过OC的开发经验,那一定会对OC中@dynamic关键字比较熟悉,它告诉编译器不要为属性合成getter和setter方法。
Swift中也有dynamic关键字,它可以用于修饰变量或函数,它的意思也与OC完全不同。它告诉编译器使用动态分发而不是静态分发。OC区别于其他语言的一个特点在于它的动态性,任何方法调用实际上都是消息分发,而Swift则尽可能做到静态分发。
因此,标记为dynamic的变量/函数会隐式的加上@objc关键字,它会使用OC的runtime机制。
虽然静态分发在效率上可能更好,不过一些app分析统计的库需要依赖动态分发的特性,动态的添加一些统计代码,这一点在Swift的静态分发机制下很难完成。这种情况下,虽然使用dynamic关键字会牺牲因为使用静态分发而获得的一些性能优化,但也依然是值得的。
使用动态分发,您可以更好的与OC中runtime的一些特性(如CoreData,KVC/KVO)进行交互,不过如果您不能确定变量或函数会被动态的修改、添加或使用了Method-Swizzle,那么就不应该使用dynamic关键字,否则有可能程序崩溃。

Restful是什么

  • 本质:一种软件架构风格
  • 核心:面向资源
  • 资源:网络上的一个实体,网络上的一个具体信息
  • 解决的问题:1、降低开发的复杂性;2、提高系统的可伸缩性

也可以总结为一句话:Restful是所有Web应用都应该遵守的架构设计指导原则。

Restful中HTTP协议介绍

http协议-url

http是一个属于应用层的协议,特点是简捷、快速。

schema://host[:port]/path[?query-string][#anchor]

  • ==scheme== 指定低层使用的的协议(例如:http,https,ftp)
  • ==host== 服务器的IP地址或域名
  • ==port== 服务器端口,默认为80
  • ==path== 访问资源的路径
  • ==query-string== 发送给http服务器的数据
  • ==anchor== 锚

http协议-请求

组成格式:请求行、消息报头、请求报文

请求行

  • 格式如下:Method Request-URL HTTP-Version CRLF

举例

  • GET / HTTP/1.1 CRLF

请求方法

  • ==GET== 请求获取Requesr-URI所标识的资源
  • ==POST== 在Request-URI所标识的资源后附加薪的数据
  • ==HEAD== 请求获取由Request-URI所标识的资源的响应消息报头
  • ==PUT== 请求服务器存储一个资源,并用Request-URI作为其标识
  • ==DELETE== 请求服务器删除Request-URI所标识的资源
  • ==OPTIONS== 请求查询服务器的性能,或者查询与资源相关的选项和需求

http协议-响应

组成格式:状态行、消息报头、响应正文

状态行

  • HTTP-Version Status-Code Reason-Phrase CRLF
  • 例如:HTTP /1.1 200 OK

常用状态码

  • 200 OK //客户端请求成功
  • 400 Bad Request //客户端请求有语法错误,不能被服务器所理解
  • 401 Unauthorized //服务器收到请求,但是拒绝提供服务
  • 404 Not Found //请求资源不存在
  • 500 Internal Server Error //服务器发送不可预期的错误
  • 503 Server Unavailable //服务器当前不能处理客户端的请求

Restful架构与其他架构的区别

SOAP WebService
  • WebService是一种跨编程语言和跨操作系统平台的远程调用技术。
  • WebService通过HTTP协议发送请求和接收结果时采用XML格式封装,并增加了一些特定的HTTP消息头,这些特定的HTTP消息头和XML内容格式就是SOAP协议。
效率和易用性
  • SOAP由于各种需求不断扩充其本身协议的内容,导致在SOAP处理方面的性能有所下降。同时在易用性方面以及学习成本上也有所
    增加。

  • Restful由于其面向资源接口设计以及操作抽象简化了开发者的不良设计,同时也最大限度
    的利用了Http最初的应用协议设计理念。

    安全性
  • Restful对于资源型服务接口来说很合适,同时特别适合对于效率要求很高,但是对于安全要求不高的场景。

  • SOAP的成熟性可以给需要提供给多开发语言的,对于安全性要求较高的接口设计带来便利。所以我觉得纯粹说什么设计模式将会占据主导地位没有什么意义,关键还是看应用场景。

如何设计Restful API

协议

API与用户的通信协议,总是使用HTTPS协议。

域名

应该尽量将API部署在专用域名之下。

1
https://api.example.com

如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。

1
https://example.org/api/
版本(Versioning)

应该将API的版本号放入URL。

1
https://api.example.com/v1/

另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github采用这种做法。

路径(Endpoint)

路径又称”终点”(endpoint),表示API的具体网址。
在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的”集合”(collection),所以API中的名词也应该使用复数。
举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

1
2
3
https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees
HTTP动词

对于资源的具体操作类型,由HTTP动词表示。
常用的HTTP动词有下面五个(括号里是对应的SQL命令)。

1
2
3
4
5
GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
DELETE(DELETE):从服务器删除资源。

还有两个不常用的HTTP动词。

1
2
HEAD:获取资源的元数据。
OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。

下面是一些例子。

1
2
3
4
5
6
7
8
GET /zoos:列出所有动物园
POST /zoos:新建一个动物园
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
过滤信息(Filtering)

如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。
下面是一些常见的参数。

1
2
3
4
5
?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件

参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。

状态码(Status Codes)

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

1
2
3
4
5
6
7
8
9
10
11
12
200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

状态码的完全列表参见这里

错误处理(Error handling)

如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。

1
2
3
{
error: "Invalid API key"
}
返回结果

针对不同操作,服务器向用户返回的结果应该符合以下规范。

1
2
3
4
5
6
GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档
Hypermedia API

RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。

1
2
3
4
5
6
{"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos",
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}}

上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。

Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。

1
2
3
4
5
{
"current_user_url": "https://api.github.com/user",
"authorizations_url": "https://api.github.com/authorizations",
// ...
}

从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。

1
2
3
4
{
"message": "Requires authentication",
"documentation_url": "https://developer.github.com/v3"
}

上面代码表示,服务器给出了提示信息,以及文档的网址。

HTTP其他
  • API的身份认证应该使用OAuth 2.0框架。
  • 服务器返回的数据格式,应该尽量使用JSON,避免使用XML。

参考资料

Restful API实战
RESTful API 设计指南
理解RESTful架构
RESTful API 设计参考文献列表

推流演示

搭建本地服务器

安装Nginx

  • Nginx介绍
    • Nginx是什么?

Nginx.png
* 简言之,Nginx本身是一个非常出色的HTTP服务器,具有占用内存少,高并发的特点。

  • Nginx安装
1
2
3
4
5
6
7
// 1> 将Nginx Clone到本地
$ brew tap homebrew/nginx
// 2> 链接要执行的命令
$ brew link pcre rtmp-nginx-module
// 3> 安装Nginx
$ brew install nginx-full --with-rtmp-module

  • 记住安装配置文件的路径(/usr/local/etc/nginx/nginx.conf)

  • 启动即可:

  • 配置Nginx,支持http协议拉流

1
2
3
4
5
6
7
8
9
10
location /hls {
#Serve HLS config
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /usr/local/var/www;
add_header Cache-Control no-cache;
}

  • 配置Nginx,支持rtmp协议推流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
rtmp {
server {
listen 1935;
application rtmplive {
live on;
max_connections 1024;
}
application hls{
live on;
hls on;
hls_path /usr/local/var/www/hls;
hls_fragment 1s;
}
}
}

  • 重启Nginx: nginx -s reload
Read more »

常见的流媒体协议

  • 常见的流媒体协议有很多比如:
    • RTP(Real-time Transport Protocol), 常用语电话会议, 网络电话等场景, 但是缺点是不提供网络保障
    • RTCP(Real-time Transport Control Protocol), 是实时传输协议(RTP)的一个姐妹协议, 也常用于语电话会议, 网络电话等场景.
    • RTMP(Real Time Streaming Protocol), RTMP是Adobe开发的协议
    • HLS(HTTP Live Streaming)是苹果公司(Apple Inc.)实现的基于HTTP的流媒体传输协议,可实现流媒体的直播和点播
Read more »

为什么学习YUV颜色空间

  • 使用系统提供的接口获取到的音视频数据都保存在CMSampleBufferRef中, 使用GPUImamge获取到的音频数据为CMSampleBufferRef
  • CMSampleBufferRef
    • 这个结构在iOS中表示一帧音频/视频数据
    • 它里面包含了这一帧数据的内容和格式, 我们可以把它的内容取出来,提取出/转换成 我们想要的数据
    • 代表视频的CMSampleBufferRef中保存的数据是yuv420格式的视频帧(我们在视频输出设置中将输出格式设为:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)
    • 代表音频的CMSampleBufferRef中保存的数据是PCM格式的音频帧
Read more »

iOS硬编码实现

前言

  • 在上一篇中,我们已经知道iOS编码的一些概念知识,从现在开始,我们可以正式对采集到的视频进行编码
  • 这里我们重点介绍硬编码的使用方式,也就是VideoToolBox框架的使用
  • 编码的流程:采集–> 获取到视频帧–> 对视频帧进行编码 –> 获取到视频帧信息 –> 将编码后的数据以NALU方式写入到文件

视频采集

  • 视频采集我们已经在前面进行了介绍和学习,所有这里就直接贴代码,只是我对采集过程进行了一些简单的封装

视频采集.png

Read more »

为什么进行压缩编码?

  • 视频是由一帧帧的图像组成(见实例)
    • 比如一张Gif图片其实就可以被分解成若干张单独的图片
      img
    • 分别出的图片
      2.png
  • 未经压缩的视频的数据量巨大
    • 比如:录音一分钟视频, 需要多大的空间来保存了?
    • 1> 为了不让用户感受到卡顿效果, 1秒钟之内至少需要16帧画面(正常开发通常会采集30帧)
    • 2> 假如该视频是一个1280*720分辨率的视频(正常情况下会比这个大很多)
    • 结果:1280_720_16*60≈843.75M
    • 如果帧率更高、分辨率更高、加上音频,那么一分钟的视频是多大呢?
  • 结论:
    • 不经过压缩编码的视频,根本没办法保存,更何况网络中的传输
    • 视频录制完成后,要先编码,再传输,在解码,再播放(重现)
Read more »

GPUImage的介绍

  • GPUImage 是一个开源的基于GPU的图片或视频的处理框架,其本身内置了多达120多种常见的滤镜效果
  • GPUImage是利用GPU,使在图片和视频上应用不同的效果和滤镜变得非常的容易,同时它还拥有出色的性能,并且它的性能要比苹果内置的相关APIs出色

高斯模糊(毛玻璃)效果

  • 在iOS总实现毛玻璃效果方式有很多
    • UIToolBar本身有毛玻璃效果
    • iOS8之后UIVisualEffectView直接创建毛玻璃View
    • 系统CoreImage框架中直接修改图片
    • GPUImage框架给图片添加一层滤镜
  • 实现思路
    • 获取要修改成毛玻璃的图片
    • 给图片添加滤镜
    • 生成新的图片
  • 实现代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fileprivate func generateBlurImage(_ sourceImage : UIImage) -> UIImage {
// 1.创建用于处理单张图片(类似于美图秀秀中打开相册中的图片进行处理)
let processView = GPUImagePicture(image: sourceImage)

// 2.创建滤镜
let blurFilter = GPUImageGaussianBlurFilter()
// 纹理大小
blurFilter.texelSpacingMultiplier = 2.0
blurFilter.blurRadiusInPixels = 5.0
processView?.addTarget(blurFilter)

// 3.处理图像
blurFilter.useNextFrameForImageCapture()
processView?.processImage()

// 4.获取最新的图像
return blurFilter.imageFromCurrentFramebuffer()
}

Read more »

概述

  • 音视频采集是直播架构的第一环,是视频的来源

    • 其实视频的采集有多个应用场景:比如二维码开发
  • 音视频采集包括两部分:

    • 视频采集
    • 音频采集
  • 在iOS开发中,是可以同步采集视频&音频的,使用方式也非常简单

  • 相关的采集API都封装在AVFoundation框架中,导入对应框架,实现功能即可

采集步骤

采集步骤文字描述
  • PS:如果做过二维码开发,应该对相关步骤非常熟悉(非常类似)

  • 导入框架

    • 相关API主要在AVFoundation框架中,因此需要先导入框架
  • 创建捕捉会话(AVCaptureSession)

    • 该会话用于连接之后的输入源&输出源
    • 输入源:摄像头&话筒
    • 输出源:拿到对应的音频&视频数据的出口
    • 会话:用于将输入源&输出源连接起来
  • 设置视频输入源&输出源

    • 输入源(AVCaptureDeviceInput):从摄像头输入
    • 输出源(AVCaptureVideoDataOutput):可以设置代理,在代理方法中拿到数据
    • 将输入&输出添加到会话中
  • 设置音频输入源&输出源

    • 输入源(AVCaptureDeviceInput):从话筒输入
    • 输出源(AVCaptureAudioDataOutput):可以设置代理,在代理方法中拿到数据
    • 将输入&输出添加到会话中
  • 添加预览图层(可选)

    • 如果希望用户看到采集的画面,可以添加预览图层
    • 该预览图层不是必须的,及时没有添加也可以正常采集数据
  • 开始采集即可

    • 调用会话(AVCaptureSession)的startRunning方法即可开始采集
Read more »