IOS 设备信息

From: http://www.jianshu.com/p/c3499d8e9a72

最近工作上需要获取设备的一些信息,整理了一下,方便大家。

1.获取电池电量(一般用百分数表示,大家自行处理就好)

-(CGFloat)getBatteryQuantity
{
    return [[UIDevice currentDevice] batteryLevel];
}

2.获取电池状态(UIDeviceBatteryState为枚举类型)

-(UIDeviceBatteryState)getBatteryStauts
{
    return [UIDevice currentDevice].batteryState;
}

3.获取总内存大小

-(long long)getTotalMemorySize
{
    return [NSProcessInfo processInfo].physicalMemory;
}

4.获取当前可用内存

-(long long)getAvailableMemorySize
{
    vm_statistics_data_t vmStats;
    mach_msg_type_number_t infoCount = HOST_VM_INFO_COUNT;
    kern_return_t kernReturn = host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&vmStats, &infoCount);
    if (kernReturn != KERN_SUCCESS)
    {
        return NSNotFound;
    }
    return ((vm_page_size * vmStats.free_count + vm_page_size * vmStats.inactive_count));
}

5.获取已使用内存

- (double)getUsedMemory
{
  task_basic_info_data_t taskInfo;
  mach_msg_type_number_t infoCount = TASK_BASIC_INFO_COUNT;
  kern_return_t kernReturn = task_info(mach_task_self(), 
                                       TASK_BASIC_INFO, 
                                       (task_info_t)&taskInfo, 
                                       &infoCount);

  if (kernReturn != KERN_SUCCESS
      ) {
    return NSNotFound;
  }

  return taskInfo.resident_size;
}

6.获取总磁盘容量

include <sys/mount.h>
-(long long)getTotalDiskSize
{
    struct statfs buf;
    unsigned long long freeSpace = -1;
    if (statfs("/var", &buf) >= 0)
    {
        freeSpace = (unsigned long long)(buf.f_bsize * buf.f_blocks);
    }
    return freeSpace;
}

7.获取可用磁盘容量

-(long long)getAvailableDiskSize
{
    struct statfs buf;
    unsigned long long freeSpace = -1;
    if (statfs("/var", &buf) >= 0)
    {
        freeSpace = (unsigned long long)(buf.f_bsize * buf.f_bavail);
    }
    return freeSpace;
}

8.容量转换

-(NSString *)fileSizeToString:(unsigned long long)fileSize
{
    NSInteger KB = 1024;
    NSInteger MB = KB*KB;
    NSInteger GB = MB*KB;

    if (fileSize < 10)  {
        return @"0 B";
    }else if (fileSize < KB)    {
        return @"< 1 KB";
    }else if (fileSize < MB)    {
        return [NSString stringWithFormat:@"%.1f KB",((CGFloat)fileSize)/KB];
    }else if (fileSize < GB)    {
        return [NSString stringWithFormat:@"%.1f MB",((CGFloat)fileSize)/MB];
    }else   {
         return [NSString stringWithFormat:@"%.1f GB",((CGFloat)fileSize)/GB];
    }
}

8.型号

#import <sys/sysctl.h>

+ (NSString *)getCurrentDeviceModel:(UIViewController *)controller
{
    int mib[2];
    size_t len;
    char *machine;

    mib[0] = CTL_HW;
    mib[1] = HW_MACHINE;
    sysctl(mib, 2, NULL, &len, NULL, 0);
    machine = malloc(len);
    sysctl(mib, 2, machine, &len, NULL, 0);

    NSString *platform = [NSString stringWithCString:machine encoding:NSASCIIStringEncoding];
    free(machine);

    if ([platform isEqualToString:@"iPhone3,1"]) return @"iPhone 4 (A1332)";
    if ([platform isEqualToString:@"iPhone3,2"]) return @"iPhone 4 (A1332)";
    if ([platform isEqualToString:@"iPhone3,3"]) return @"iPhone 4 (A1349)";
    if ([platform isEqualToString:@"iPhone4,1"]) return @"iPhone 4s (A1387/A1431)";
    if ([platform isEqualToString:@"iPhone5,1"]) return @"iPhone 5 (A1428)";
    if ([platform isEqualToString:@"iPhone5,2"]) return @"iPhone 5 (A1429/A1442)";
    if ([platform isEqualToString:@"iPhone5,3"]) return @"iPhone 5c (A1456/A1532)";
    if ([platform isEqualToString:@"iPhone5,4"]) return @"iPhone 5c (A1507/A1516/A1526/A1529)";
    if ([platform isEqualToString:@"iPhone6,1"]) return @"iPhone 5s (A1453/A1533)";
    if ([platform isEqualToString:@"iPhone6,2"]) return @"iPhone 5s (A1457/A1518/A1528/A1530)";
    if ([platform isEqualToString:@"iPhone7,1"]) return @"iPhone 6 Plus (A1522/A1524)";
    if ([platform isEqualToString:@"iPhone7,2"]) return @"iPhone 6 (A1549/A1586)";
    if ([platform isEqualToString:@"iPhone8,1"]) return @"iPhone 6s";
    if ([platform isEqualToString:@"iPhone8,2"]) return @"iPhone 6s Plus";
    if ([platform isEqualToString:@"iPod1,1"])   return @"iPod Touch 1G (A1213)";
    if ([platform isEqualToString:@"iPod2,1"])   return @"iPod Touch 2G (A1288)";
    if ([platform isEqualToString:@"iPod3,1"])   return @"iPod Touch 3G (A1318)";
    if ([platform isEqualToString:@"iPod4,1"])   return @"iPod Touch 4G (A1367)";
    if ([platform isEqualToString:@"iPod5,1"])   return @"iPod Touch 5G (A1421/A1509)";

    if ([platform isEqualToString:@"iPad1,1"])   return @"iPad 1G (A1219/A1337)";
    if ([platform isEqualToString:@"iPad2,1"])   return @"iPad 2 (A1395)";
    if ([platform isEqualToString:@"iPad2,2"])   return @"iPad 2 (A1396)";
    if ([platform isEqualToString:@"iPad2,3"])   return @"iPad 2 (A1397)";
    if ([platform isEqualToString:@"iPad2,4"])   return @"iPad 2 (A1395+New Chip)";
    if ([platform isEqualToString:@"iPad2,5"])   return @"iPad Mini 1G (A1432)";
    if ([platform isEqualToString:@"iPad2,6"])   return @"iPad Mini 1G (A1454)";
    if ([platform isEqualToString:@"iPad2,7"])   return @"iPad Mini 1G (A1455)";

    if ([platform isEqualToString:@"iPad3,1"])   return @"iPad 3 (A1416)";
    if ([platform isEqualToString:@"iPad3,2"])   return @"iPad 3 (A1403)";
    if ([platform isEqualToString:@"iPad3,3"])   return @"iPad 3 (A1430)";
    if ([platform isEqualToString:@"iPad3,4"])   return @"iPad 4 (A1458)";
    if ([platform isEqualToString:@"iPad3,5"])   return @"iPad 4 (A1459)";
    if ([platform isEqualToString:@"iPad3,6"])   return @"iPad 4 (A1460)";

    if ([platform isEqualToString:@"iPad4,1"])   return @"iPad Air (A1474)";
    if ([platform isEqualToString:@"iPad4,2"])   return @"iPad Air (A1475)";
    if ([platform isEqualToString:@"iPad4,3"])   return @"iPad Air (A1476)";
    if ([platform isEqualToString:@"iPad4,4"])   return @"iPad Mini 2G (A1489)";
    if ([platform isEqualToString:@"iPad4,5"])   return @"iPad Mini 2G (A1490)";
    if ([platform isEqualToString:@"iPad4,6"])   return @"iPad Mini 2G (A1491)";

    if ([platform isEqualToString:@"i386"])      return @"iPhone Simulator";
    if ([platform isEqualToString:@"x86_64"])    return @"iPhone Simulator";
    return platform;
}

可以根据自己的需求增改。有人说也可以按照手机屏幕来判断,但5和5s/5c等手机屏幕相同尺寸则无法判断。。。我只做了iPhone的机型,所以iPad和iPod Touch的信息并没有更新到最新,也请朋友们补充。

9.IP地址

#import <ifaddrs.h>#import <arpa/inet.h>

- (NSString *)deviceIPAdress {
    NSString *address = @"an error occurred when obtaining ip address";
    struct ifaddrs *interfaces = NULL;
    struct ifaddrs *temp_addr = NULL;
    int success = 0;

    success = getifaddrs(&interfaces);

    if (success == 0) { // 0 表示获取成功

        temp_addr = interfaces;
        while (temp_addr != NULL) {
            if( temp_addr->ifa_addr->sa_family == AF_INET) {
                // Check if interface is en0 which is the wifi connection on the iPhone
                if ([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) {
                    // Get NSString from C String
                    address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
                }
            }

            temp_addr = temp_addr->ifa_next;
        }
    }

    freeifaddrs(interfaces);
    return address;
}

10.当前手机连接的WIFI名称(SSID)

需要#import <SystemConfiguration/CaptiveNetwork.h>

- (NSString *)getWifiName
{
    NSString *wifiName = nil;

    CFArrayRef wifiInterfaces = CNCopySupportedInterfaces();
    if (!wifiInterfaces) {
        return nil;
    }

    NSArray *interfaces = (__bridge NSArray *)wifiInterfaces;

    for (NSString *interfaceName in interfaces) {
        CFDictionaryRef dictRef = CNCopyCurrentNetworkInfo((__bridge CFStringRef)(interfaceName));

        if (dictRef) {
            NSDictionary *networkInfo = (__bridge NSDictionary *)dictRef;

            wifiName = [networkInfo objectForKey:(__bridge NSString *)kCNNetworkInfoKeySSID];

            CFRelease(dictRef);
        }
    }

    CFRelease(wifiInterfaces);
    return wifiName;
}

11.当前手机系統版本

[[[UIDevice currentDevice] systemVersion] floatValue] ;

版权声明:本文为 Crazy Steven 原创出品,欢迎转载,转载时请注明出处!

srs配置

From: https://awen.me/post/191485023.html

SRS 是一个开源的,国产的直播服务。可以去官网下载:
官网

环境

操作系统:centos 7
推流软件:obs 或 ffmpeg
拉流软件:ffplay 或 vlc

安装

如果是 centos 6 可以直接下载这个进行安装

# wget -c http://ossrs.net/srs.release/releases/files/SRS-CentOS6-x86_64-2.0.239.zip

# unzip SRS-CentOS6-x86_64-2.0.239.zip
# cd SRS-CentOS6-x86_64-2.0.239
# chmod +x INSTALL
# ./INSTALL

安装在/usr/local/srs目录中,可以直接使用

/etc/init.d/srs start

启动

centos 7 需要编译

[root@adsl-172-10-100-120 opt]# wget -c https://github.com/ossrs/srs/archive/v2.0-r1.tar.gz
[root@adsl-172-10-100-120 opt]# tar zxvf v2.0-r1.tar.gz
[root@adsl-172-10-100-120 opt]# cd srs-2.0-r1/

然后进入到 trunk 目录

[root@adsl-172-10-100-120 srs-2.0-r1]# cd trunk/
[root@adsl-172-10-100-120 trunk]# ls
3rdparty  auto  conf  configure  doc  etc  ide  modules  research  scripts  src

使用 help 可以查看编译帮助文档

[root@adsl-172-10-100-120 trunk]# ./configure --help

比如我这里编译全部的模块安装到/usr/local/srs目录

[root@adsl-172-10-100-120 trunk]# ./configure --full --prefix=/usr/local/srs

然后

make && make install

进入 /usr/local/srs

[root@adsl-172-10-100-120 srs]# pwd
/usr/local/srs
[root@adsl-172-10-100-120 srs]# ./objs/
nginx/ srs
[root@adsl-172-10-100-120 srs]# ./objs/srs -c ./conf/srs.conf
[2017-05-02 17:22:56.900][trace][1949][0] XCORE-SRS/2.0.239(ZhouGuowen)
[2017-05-02 17:22:56.900][trace][1949][0] config parse complete
[2017-05-02 17:22:56.900][trace][1949][0] write log to file ./objs/srs.log
[2017-05-02 17:22:56.900][trace][1949][0] you can: tailf ./objs/srs.log
[2017-05-02 17:22:56.900][trace][1949][0] @see: https://github.com/ossrs/srs/wiki/v1_CN_SrsLog

##推流

➜  Downloads ffmpeg -i gopro.mp4 -vcodec libx264 -acodec aac -f flv rtmp://172.10.100.120/live/lol

拉流

然后 ffplay 播放

➜  ~ ffplay rtmp://172.10.100.120/live/lol
ffplay version 3.3 Copyright (c) 2003-2017 the FFmpeg developers
  built with Apple LLVM version 8.1.0 (clang-802.0.42)
  configuration: --prefix=/usr/local/Cellar/ffmpeg/3.3 --enable-shared --enable-pthreads --enable-gpl --enable-version3 --enable-hardcoded-tables --enable-avresample --cc=clang --host-cflags= --host-ldflags= --enable-libmp3lame --enable-libx264 --enable-libx265 --enable-libxvid --enable-opencl --disable-lzma --enable-vda
  libavutil      55. 58.100 / 55. 58.100
  libavcodec     57. 89.100 / 57. 89.100
  libavformat    57. 71.100 / 57. 71.100
  libavdevice    57.  6.100 / 57.  6.100
  libavfilter     6. 82.100 /  6. 82.100
  libavresample   3.  5.  0 /  3.  5.  0
  libswscale      4.  6.100 /  4.  6.100
  libswresample   2.  7.100 /  2.  7.100
  libpostproc    54.  5.100 / 54.  5.100
Input #0, flv, from 'rtmp://172.10.100.120/live/lol':    0B f=0/0
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf57.71.100
    server          : SRS/2.0.239(ZhouGuowen)
    srs_primary     : SRS/1.0release
    srs_authors     : winlin,wenjie.zhao
    server_version  : 2.0.239
  Duration: N/A, start: 0.023000, bitrate: N/A
    Stream #0:0: Audio: aac (LC), 44100 Hz, stereo, fltp, 128 kb/s
    Stream #0:1: Video: h264 (High), yuv420p(progressive), 1280x720 [SAR 1:1 DAR 16:9], 29.97 fps, 29.97 tbr, 1k tbn, 59.94 tbc
   3.96 A-V:  0.082 fd=  22 aq=    0KB vq=    0KB sq=    0B f=0/0

srs 配置

[root@adsl-172-10-100-120 srs]# ls
conf  etc  objs
[root@adsl-172-10-100-120 srs]# cd conf/
[root@adsl-172-10-100-120 conf]# ls
bandwidth.conf    edge.conf                 hls.conf                  http.hooks.callback.conf  push.flv.conf               transcode2hls.audio.only.conf
console.conf      edge.token.traverse.conf  http.aac.live.conf        http.mp3.live.conf        push.mpegts.over.udp.conf   transform.edge.conf
demo.19350.conf   ffmpeg.transcode.conf     http.flv.live.conf        http.server.conf          push.rtsp.conf
demo.conf         forward.master.conf       http.flv.live.edge1.conf  http.ts.live.conf         realtime.conf
dvr.path.conf     forward.slave.conf        http.flv.live.edge2.conf  ingest.conf               rtmp.conf
dvr.segment.conf  full.conf                 http.heartbeat.conf       mac.dev.conf              security.deny.publish.conf
dvr.session.conf  hds.conf                  http.hls.conf             origin.conf               srs.conf

默认的配置文件是 srs.conf

[root@adsl-172-10-100-120 conf]# cat srs.conf
# main config for srs.
# @see full.conf for detail config.

listen              1935;
max_connections     1000;
srs_log_tank        file;
srs_log_file        ./objs/srs.log;
http_api {
    enabled         on;
    listen          1985; #默认端口1985
}
http_server {
    enabled         on;
    listen          8080;
    dir             ./objs/nginx/html;
}
stats {
    network         0;
    disk            sda sdb xvda xvdb;
}
vhost __defaultVhost__ { #虚拟主机
}

如果你希望将推流的内容转换成 hls,可以参考

[root@adsl-172-10-100-120 conf]# cat hls.conf
# the config for srs to delivery hls
# @see https://github.com/ossrs/srs/wiki/v1_CN_SampleHLS
# @see full.conf for detail config.

listen              1935;
max_connections     1000;
daemon              off;
srs_log_tank        console;
vhost __defaultVhost__ {
    hls {
        enabled         on;
        hls_fragment    10;
        hls_window      60;
        hls_path        ./objs/nginx/html;
        hls_m3u8_file   [app]/[stream].m3u8;
        hls_ts_file     [app]/[stream]-[seq].ts;
    }
}

更多使用方法,参考[官方维基](https://github.com/ossrs/srs/wiki/v3_CN_Home)

xcode 项目更名

From: http://www.jianshu.com/p/2887d6fb5769

前言:

在iOS开发中,有时候想改一下项目的名字,这会遇到很多麻烦。

  • 直接改项目名的话,Xcode不会帮你改所有的名字
  • 项目中的很多文件、文件夹或者是项目设置的项,都是不能随便改的,有时候改着改着,就会编译不了。

所以各位重命名项目时,记得先备份好一份噢。本文我会介绍一种“完美”的修改方法。

注意:重命名项目时,记得先备份好一份
注意:重命名项目时,记得先备份好一份
注意:重命名项目时,记得先备份好一份

重要的事情说三遍

本文会把一个项目名叫 OldDemo123 改成 NewDemo

正文:

修改前的项目结构:

修改前的项目结构

1、打开项目,对项目名进行 Rename

1.1、选中项目名并按下回车,进入可编辑状态:

选中项目名字,进行编辑

1.2、输入新的项目名字,然后按回车,弹出改名前和改名后的文件对名,这时点击 Rename

点击 Rename

2、修改文件夹名字和显示包内容

2.1、打开应用所在文件夹,修改文件夹名字

注意:

  • 文件夹NewDemoTestsNewDemoUITests里面也要修改
  • 这里的NewDemoTests,原先为OldDemo123Tests
    我们改名字时需要注意,只需要把旧名字(OldDemo123)替换成新名字(NewDemo)即可,不要把其它字符(Tests)删除!

修成后的文件夹名字

2.2、选中 NewDemo.xcodeproj 右键打开 –> 显示包内容 –> 双击打开 project.pbxproj

显示包内容,双击打开 project.pbxproj

2.3、打开 project.pbxproj 文件之后,用搜索快捷键 command + f 全局搜索旧的项目名 OldDemo123 ,并用新的项目名 NewDemo 进行替换。替换完成后进行保存 command + s,然后关闭。

注意:要把所有的 OldDemo123 更换成 NewDemo

搜索 OldDemo123 ,并替换成 NewDemo

3、打开 NewDemo.xcodeproj 文件

注意:打开的是 NewDemo.xcodeproj 文件,而不是 NewDemo.xcworkspace文件。

3.1、此时会弹出提示框,点击 OK 就行。

弹出提示框

3.2、显示此时项目结构和修改更新Podfile文件

修改好项目结构

如果你的项目里面没有使用CocoaPods的话,项目应该可以运行成功了。

使用CocoaPods的话,项目虽然表面看起来已经修改成功了,但是运行之后发现提示错误:

使用CocoaPods的话,会提示的错误

此时打开项目文件夹,找到 Podfile 文件,双击打开,修改 target 后的项目名为最新的项目名 NewDemo

target 'NewDemo' do
pod 'AFNetworking', '~> 3.0'
end

然后在终端,用 cd 到项目目录下,运行 $ pod install,进行更新。

3.3、打开 NewDemo.xcworkspace 文件
此时文件显示错误:因为文件路径的原因

错误显示

选中显示红色的 OldDemo123 文件,点击右侧文件夹小图标,更改路径。

修改文件路径

路径更改成功之后,项目基本就可以运行成功了。

4、修改 Scheme

选中 OldDemo123 –> 下拉中选中 Manage Schemes –> 弹出一个显示框。

修改Scheme名

选中要修改的 OldDemo123 那一行,并按下回车,进行修改新的名称 NewDemo,然后点击 Close

修改新的 Scheme 名

5、项目内全局修改

其实到上面,项目已经基本修改完成了,但是对于一些处女座、强迫症患者来说,还有一些问题,如下:

生成类时的顶部介绍

全局搜索旧的项目名,并进行修改。

修改类的顶部介绍

最后:

到此,项目名已经完全修改完成了,小伙伴们可以尝试修改了。

下面是修改后的项目结构:

修改后的项目结构

//补充如果有oc的桥街头,手动更改下,项目文件地址可能需要修改下

注意:重命名项目时,记得先备份好一份

OC runtime UIButton扩展Block点击事件

From: http://www.jianshu.com/p/d4eab6815ac2

主要实现通过runtime动态关联属性。
上码!!!!

#import <UIKit/UIKit.h>

typedef void(^ButtonBlock)(UIButton* btn);

@interface UIButton (Block)

/**
 *  button 添加点击事件
 *
 *  @param block
 */
- (void)addAction:(ButtonBlock)block;

/**
 *  button 添加事件
 *
 *  @param block
 *  @param controlEvents 点击的方式
 */
- (void)addAction:(ButtonBlock)block forControlEvents:(UIControlEvents)controlEvents;

@end

////////////////////////////////////////////////////


#import "UIButton+Block.h"

#import <objc/runtime.h>

@implementation UIButton (Block)
static char ActionTag;

/**
 *  button 添加点击事件 默认点击方式UIControlEventTouchUpInside
 *
 *  @param block
 */
- (void)addAction:(ButtonBlock)block {
    objc_setAssociatedObject(self, &ActionTag, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
    [self addTarget:self action:@selector(action:) forControlEvents:UIControlEventTouchUpInside];
}

/**
 *  button 添加事件
 *
 *  @param block
 *  @param controlEvents 点击的方式
 */
- (void)addAction:(ButtonBlock)block forControlEvents:(UIControlEvents)controlEvents {
    objc_setAssociatedObject(self, &ActionTag, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
    [self addTarget:self action:@selector(action:) forControlEvents:controlEvents];
}

/**
 *  button 事件的响应方法
 *
 *  @param sender 
 */
- (void)action:(id)sender {
    ButtonBlock blockAction = (ButtonBlock)objc_getAssociatedObject(self, &ActionTag);
    if (blockAction) {
        blockAction(self);
    }
}
@end

使用方法如下

UIButton *testButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
testButton.backgroundColor = [UIColor redColor];
[self.view addSubview:testButton];

[testButton addAction:^(UIButton *btn) {
    NSLog(@"我被点名了");
}];

[testButton addAction:^(UIButton *btn) {
     NSLog(@"我被点名了");
} forControlEvents:UIControlEventTouchUpInside];

各种进度提示layer 动画

From: http://www.code4app.com/blog-822721-738.html

初步学习了CoreAnimation框架,总结了几个动画效果,主要是通过CAShapeLayer与贝塞尔曲线实现。先看下效果

1.gif

扇形下载进度

要实现扇形的下载进度,有两种方法, 这里先使用第一种:

1.使用设置UIBezierPath的角度

2.使用 CAShapeLayer的stokeEnd属性

CGPoint point = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);


CGFloat startAngle = - M_PI /2;


CGFloat endAngle = self.progress *M_PI *2 + startAngle;


UIBezierPath *path =[UIBezierPath bezierPathWithArcCenter:point radius:self.bounds.size.width/2 startAngle:startAngle endAngle:endAngle clockwise:1];


[path addLineToPoint:point];



CAShapeLayer *layer =[CAShapeLayer layer];


layer.path = path.CGPath;

layer.fillColor =[UIColor colorWithRed:0.47 green:0.83 blue:0.98 alpha:1].CGColor;

[self.layer addSublayer:layer];

圆形进度

首先 我们需要一个背景层 一个前景层,一个路径供给两个layer使用。这里我们使用改变stokeEnd 来改变圆弧的进度,代码里增加了一点渐变
   self.backLayer =[CAShapeLayer layer];
    self.backLayer.fillColor  =[UIColor clearColor].CGColor;
    self.backLayer.frame = self.bounds;
    self.backLayer.lineWidth = 4;
    self.backLayer.strokeColor =[UIColor lightGrayColor].CGColor;
    [self.layer addSublayer:self.backLayer];

    self.foreLayer =[CAShapeLayer layer];
    self.foreLayer.fillColor  =[UIColor clearColor].CGColor;
    self.foreLayer.frame = self.bounds;
    self.foreLayer.strokeColor =[UIColor redColor].CGColor;
    self.foreLayer.lineWidth = 4;
    [self.layer addSublayer:self.foreLayer];


    UIBezierPath *path=  [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2) radius:self.bounds.size.width/2-2 startAngle:-M_PI_2 endAngle:M_PI *1.5 clockwise:YES];
    self.backPath = path;

    self.backLayer.path = self.backPath.CGPath;
    self.foreLayer.path = self.backPath.CGPath;
    self.foreLayer.strokeEnd = 0;


    self.gradientLayerLeft =[CAGradientLayer layer];
    self.gradientLayerLeft.frame = self.bounds;

    self.gradientLayerLeft.colors =@[(__bridge id)[UIColor redColor].CGColor,(__bridge id)[UIColor yellowColor].CGColor,(__bridge id)[UIColor blueColor].CGColor];
    self.gradientLayerLeft.locations = @[@0,@0.5,@1];
    self.gradientLayerLeft.startPoint = CGPointMake(0, 0);
    self.gradientLayerLeft.endPoint = CGPointMake(0, 1);
    [self.layer addSublayer:self.gradientLayerLeft];



    [self.gradientLayerLeft setMask:self.foreLayer];
-(void)setProgressValue:(CGFloat)progressValue
{
    _progressValue = progressValue;
    self.foreLayer.strokeEnd = progressValue;
    self.label.text = [NSString stringWithFormat:@"%.f%%",progressValue *100];
}

一个加载动画

引自:旋转加载动画

可以重点学习下做动画的思路,

- (void)animationDidStart:(CAAnimation *)anim{
    [UIView animateWithDuration:0.3 delay:0.1 options:UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionBeginFromCurrentState animations:^{

        self.ball_1.transform = CGAffineTransformMakeTranslation(-BALL_RADIUS, 0);
        self.ball_1.transform = CGAffineTransformScale(self.ball_1.transform, 0.7, 0.7);

        self.ball_3.transform = CGAffineTransformMakeTranslation(BALL_RADIUS, 0);
        self.ball_3.transform = CGAffineTransformScale(self.ball_3.transform, 0.7, 0.7);


        self.ball_2.transform = CGAffineTransformScale(self.ball_2.transform, 0.7, 0.7);
    } completion:^(BOOL finished) {
        [UIView animateWithDuration:0.3 delay:0.1 options:UIViewAnimationOptionCurveEaseIn  | UIViewAnimationOptionBeginFromCurrentState animations:^{
            self.ball_1.transform = CGAffineTransformIdentity;
            self.ball_3.transform = CGAffineTransformIdentity;
            self.ball_2.transform = CGAffineTransformIdentity;
        } completion:NULL];

    }];
}

使用正余弦做的注水动画

在使用正余弦做注水动画时,先了解下正余弦

我们要做的 就是使用两条正余弦,但是这两条正余弦, 波峰需要对应波谷,有两种方法:

  1. 使用for循环分别拼接正余弦的路径

    -(void)updateWave
    {

    CGFloat waterWaveWidth = self.bounds.size.width;

    CGMutablePathRef path = CGPathCreateMutable();

    CGMutablePathRef maskPath = CGPathCreateMutable();

    CGPathMoveToPoint(path, nil, 0, _waveY);

    CGPathMoveToPoint(maskPath, nil, 0, _waveY);

    CGFloat y = _waveY;


    for (float x = 0.0f; x <= waterWaveWidth ; x++) {
        y = _waveAmplitude * sin(_wavePalstance * x + _waveX) + _waveY;

        CGPathAddLineToPoint(path, nil, x, y);

    }
    for (float x = 0.0f; x <= waterWaveWidth ; x++) {
        y = _waveAmplitude * cos(_wavePalstance * x + _waveX) + _waveY;

        CGPathAddLineToPoint(maskPath, nil, x, y);
    }
    [self updateLayer:_waveLayer1 path:path];
    [self updateLayer:_waveLayer2 path:maskPath];

}
-(void)updateLayer:(CAShapeLayer *)layer path:(CGMutablePathRef )path
{

    CGFloat waterWaveWidth = self.bounds.size.width;
    CGPathAddLineToPoint(path, nil, waterWaveWidth, self.bounds.size.height);
    CGPathAddLineToPoint(path, nil, 0, self.bounds.size.height);
    CGPathCloseSubpath(path);
    layer.path = path;
     CGPathRelease(path);
}

2.使用单个for循环只是 设置 另一条曲线的y值相反即可实现两条正余弦的效果 ,最后一个动画中会有说明

for (int x = 0; x<WIDTH; x++) {
        y = waveHeight*sinf(0.01*waveCurvature*x+offSetValue*0.045);
        CGPathAddLineToPoint(path, nil, x, y);

        masky = -y;
        CGPathAddLineToPoint(maskPath, nil, x, masky);
    }

但是我们有个需求就是改变 波浪的高度, 实现注水的百分比,就需要设置波浪的偏距

-(void)updateWaveY
{
    CGFloat targetY = self.bounds.size.height - _progress * self.bounds.size.height;
    if (_waveY < targetY) {
        _waveY += 2;
    }
    if (_waveY > targetY ) {
        _waveY -= 2;
    }
}

正余弦动画2

如果有个需求 ,比如一个小船 随着波浪的波动而起伏

那我们就需要计算 波浪的位置,然后设置小船的frame

    for (int x = 0; x<WIDTH; x++) {
        y = waveHeight*sinf(0.01*waveCurvature*x+offSetValue*0.045);
        CGPathAddLineToPoint(path, nil, x, y);

        masky = -y;
        CGPathAddLineToPoint(maskPath, nil, x, masky);
    }
计算出实浪波动时,最中间的位置,设置小船的frame
  CGFloat CentY = waveHeight*sinf(0.01*waveCurvature*WIDTH/2+offSetValue*0.045);
    CGRect boardViewFrame = [boardView frame];
    boardViewFrame.origin.y = 100-waveHeight+CentY;
    boardView.frame = boardViewFrame;

来自:https://github.com/liuxinixn/ProgressViewDemo

iOS音频开发(录音+播放+剪辑+合成+压缩转码)

iOS音频开发(录音+播放+剪辑+合成+压缩转码)

From: http://blog.csdn.net/feng2qing/article/details/67655175

录音:

AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *sessionError;

[session setCategory:AVAudioSessionCategoryPlayAndRecord error:&sessionError];
[session setActive:YES error:nil];

NSDictionary *setting = [NSDictionary dictionaryWithObjectsAndKeys:
                               [NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey,
                               [NSNumber numberWithFloat:8000], AVSampleRateKey, 
                               [NSNumber numberWithInt:2], AVNumberOfChannelsKey, 
                               [NSNumber numberWithInt:16], AVLinearPCMBitDepthKey,  
                               [NSNumber numberWithBool:NO], AVLinearPCMIsNonInterleaved,  
                               [NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,  
                               [NSNumber numberWithBool:NO], AVLinearPCMIsBigEndianKey,  
                               [NSNumber numberWithInt:AVAudioQualityMax], AVEncoderAudioQualityKey,  
                               nil];
self.audioRecorder.delegate = self;

self.audioRecorder.meteringEnabled = YES;

self.audioRecorder = [[AVAudioRecorder alloc] initWithURL:[NSURL URLWithString:filePath] settings:setting error:nil];

[self.audioRecorder prepareToRecord];
[self.audioRecorder record];


[self.audioRecorder pause];

[self.audioRecorder stop];




- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag;

- (void)audioRecorderEncodeErrorDidOccur:(AVAudioRecorder *)recorder error:(NSError * __nullable)error;

- (void)audioRecorderBeginInterruption:(AVAudioRecorder *)recorder NS_DEPRECATED_IOS(2_2, 8_0);

- (void)audioRecorderEndInterruption:(AVAudioRecorder *)recorder withOptions:(NSUInteger)flags NS_DEPRECATED_IOS(6_0, 8_0);

播放

AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *sessionError;
[session setCategory:AVAudioSessionCategoryPlayback error:&sessionError];
[session setActive:YES error:nil];


[[UIDevice currentDevice] setProximityMonitoringEnabled:YES];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sensorStateChange:)name:UIDeviceProximityStateDidChangeNotification object:nil];

self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL URLWithString:filePath] error:nil];

self.audioPlayer.delegate = self;

[self.audioPlayer prepareToPlay];
[self.audioPlayer play];


[self.audioPlayer stop];

[self.audioPlayer pause];


if ([[UIDevice currentDevice] proximityState] == YES) {

    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
} else {

    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
}



- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag;

- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError * __nullable)error;

- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player NS_DEPRECATED_IOS(2_2, 8_0);

- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player withOptions:(NSUInteger)flags NS_DEPRECATED_IOS(6_0, 8_0);

剪辑

//AVURLAsset是AVAsset的子类,AVAsset类专门用于获取多媒体的相关信息,包括获取多媒体的画面、声音等信息.而AVURLAsset子类的作用则是根据NSURL来初始化AVAsset对象.
AVURLAsset *videoAsset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:filePath]];
//音频输出会话
//AVAssetExportPresetAppleM4A: This export option will produce an audio-only .m4a file with appropriate iTunes gapless playback data(输出音频,并且是.m4a格式)
AVAssetExportSession *exportSession = [AVAssetExportSession exportSessionWithAsset:videoAsset presetName:AVAssetExportPresetAppleM4A];
//设置输出路径 / 文件类型 / 截取时间段
exportSession.outputURL = [NSURL fileURLWithPath:resultPath];
exportSession.outputFileType = AVFileTypeAppleM4A;
exportSession.timeRange = CMTimeRangeFromTimeToTime(CMTimeMake(time1, 1), CMTimeMake(time2, 1));
[exportSession exportAsynchronouslyWithCompletionHandler:^{
    //exporeSession.status
}];

合成

将路径filePath1和路径filePath2下的音频合成

AVURLAsset *videoAsset1 = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:filePath1] options:nil];
AVURLAsset *videoAsset2 = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:filePath2] options:nil];

AVAssetTrack *assetTrack1 = [[videoAsset1 tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
AVAssetTrack *assetTrack2 = [[videoAsset2 tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];

AVMutableComposition *composition = [AVMutableComposition composition];
AVMutableCompositionTrack *compositionTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];

[compositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset1.duration) ofTrack:assetTrack1 atTime:kCMTimeZero error:nil];
[compositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset2.duration) ofTrack:assetTrack2 atTime:videoAsset1.duration error:nil];

AVAssetExportSession *exporeSession = [AVAssetExportSession exportSessionWithAsset:composition presetName:AVAssetExportPresetAppleM4A];
exporeSession.outputFileType = AVFileTypeAppleM4A;
exporeSession.outputURL = [NSURL fileURLWithPath:resultPath];
[exporeSession exportAsynchronouslyWithCompletionHandler:^{

}];

压缩转码

下载LAME (Lame Aint an MP3 Encoder)

双击解压后放到一个文件夹下,文件夹需要命名为lame,否则无法生成.h.a文件

使用Terminal进入该文件夹,编译生成静态库,[脚本代码][3]

[3]: https://github.com/kewlbear/lame-ios-build/blob/master/build-lame.sh
1
2
3
4
5
6
7
$:cd cd /Users/mac/Desktop/lame
//创建build_lame.sh
$:touch build_lame.sh
//打开build_lame.sh,粘贴脚本代码
$:open build_lame.sh
//编译执行脚本,生成静态库,需要输入本机密码
$:sudo sh build_lame.sh

这里写图片描述

fat-lame文件夹下的include文件夹和lib文件夹放入工程,再写一个OC的类调用lame.h

@try {
    int read, write;
    FILE *pcm = fopen([filePath cStringUsingEncoding:1], "rb");
    fseek(pcm, 4*1024, SEEK_CUR);
    FILE *mp3 = fopen([resultPath cStringUsingEncoding:1], "wb");

    const int PCM_SIZE = 8192;
    const int MP3_SIZE = 8192;
    short int pcm_buffer[PCM_SIZE*2];
    unsigned char mp3_buffer[MP3_SIZE];


    lame_t lame = lame_init();

    lame_set_in_samplerate(lame, 8000);
    lame_set_num_channels(lame,2);
    lame_set_out_samplerate(lame, 8000);
    lame_set_brate(lame, 8);

    lame_set_quality(lame, 7);


    lame_set_VBR(lame, vbr_default);
    lame_init_params(lame);

    do {
        size_t size = (size_t)(2 * sizeof(short int));
        read = fread(pcm_buffer, size, PCM_SIZE, pcm);
        if (read == 0) {
            write = lame_encode_flush(lame, mp3_buffer, MP3_SIZE);
        } else {
            write = lame_encode_buffer_interleaved(lame, pcm_buffer, read, mp3_buffer, MP3_SIZE);
        }
        fwrite(mp3_buffer, write, 1, mp3);

    } while (read != 0);

    lame_close(lame);
    fclose(mp3);
    fclose(pcm);
}
@catch (NSException *exception) {
    NSLog(@"%@",[exception description]);
}
@finally {

    return resultPath;
}

基本上可以将100K左右的录音文件压缩到10K以下

参考1 参考2 参考3 参考4

swift 指针问题

虽然是老版本的但是参考意义也很不错
From: https://onevcat.com/2015/01/swift-pointer/

2015-01-19 • 能工巧匠集

Apple 期望在 Swift 中指针能够尽量减少登场几率,因此在 Swift 中指针被映射为了一个泛型类型,并且还比较抽象。这在一定程度上造成了在 Swift 中指针使用的困难,特别是对那些并不熟悉指针,也没有多少指针操作经验的开发者 (包括我自己也是) 来说,在 Swift 中使用指针确实是一个挑战。在这篇文章里,我希望能从最基本的使用开始,总结一下在 Swift 中使用指针的一些常见方式和场景。这篇文章假定你至少知道指针是什么,如果对指针本身的概念不太清楚的话,可以先看看这篇五分钟 C 指针教程 (或者它的中文版本),应该会很有帮助。

初步

在 Swift 中,指针都使用一个特殊的类型来表示,那就是 UnsafePointer<T>。遵循了 Cocoa 的一贯不可变原则,UnsafePointer<T> 也是不可变的。当然对应地,它还有一个可变变体,UnsafeMutablePointer<T>。绝大部分时间里,C 中的指针都会被以这两种类型引入到 Swift 中:C 中 const 修饰的指针对应 UnsafePointer (最常见的应该就是 C 字符串的 const char * 了),而其他可变的指针则对应 UnsafeMutablePointer。除此之外,Swift 中存在表示一组连续数据指针的 UnsafeBufferPointer<T>,表示非完整结构的不透明指针 COpaquePointer 等等。另外你可能已经注意到了,能够确定指向内容的指针类型都是泛型的 struct,我们可以通过这个泛型来对指针指向的类型进行约束以提供一定安全性。

对于一个 UnsafePointer<T> 类型,我们可以通过 memory 属性对其进行取值,如果这个指针是可变的 UnsafeMutablePointer<T> 类型,我们还可以通过 memory 对它进行赋值。比如我们想要写一个利用指针直接操作内存的计数器的话,可以这么做:

func incrementor(ptr: UnsafeMutablePointer<Int>) {
    ptr.memory += 1
}

var a = 10
incrementor(&a)

a  // 11

这里和 C 的指针使用类似,我们通过在变量名前面加上 & 符号就可以将指向这个变量的指针传递到接受指针作为参数的方法中去。在上面的 incrementor 中我们通过直接操作 memory 属性改变了指针指向的内容。

与这种做法类似的是使用 Swift 的 inout 关键字。我们在将变量传入 inout 参数的函数时,同样也使用 & 符号表示地址。不过区别是在函数体内部我们不需要处理指针类型,而是可以对参数直接进行操作。

func incrementor1(inout num: Int) {
    num += 1
}

var b = 10
incrementor1(&b)

b  // 11

虽然 & 在参数传递时表示的意义和 C 中一样,是某个“变量的地址”,但是在 Swift 中我们没有办法直接通过这个符号获取一个 UnsafePointer 的实例。需要注意这一点和 C 有所不同:

// 无法编译
let a = 100
let b = &a

指针初始化和内存管理

在 Swift 中不能直接取到现有对象的地址,我们还是可以创建新的 UnsafeMutablePointer 对象。与 Swift 中其他对象的自动内存管理不同,对于指针的管理,是需要我们手动进行内存的申请和释放的。一个 UnsafeMutablePointer 的内存有三种可能状态:

  • 内存没有被分配,这意味着这是一个 null 指针,或者是之前已经释放过
  • 内存进行了分配,但是值还没有被初始化
  • 内存进行了分配,并且值已经被初始化

其中只有第三种状态下的指针是可以保证正常使用的。UnsafeMutablePointer 的初始化方法 (init) 完成的都是从其他类型转换到 UnsafeMutablePointer 的工作。我们如果想要新建一个指针,需要做的是使用 alloc: 这个类方法。该方法接受一个 num: Int 作为参数,将向系统申请 num 个数的对应泛型类型的内存。下面的代码申请了一个 Int 大小的内存,并返回指向这块内存的指针:

var intPtr = UnsafeMutablePointer<Int>.alloc(1)
// "UnsafeMutablePointer(0x7FD3A8E00060)"

接下来应该做的是对这个指针的内容进行初始化,我们可以使用 initialize: 方法来完成初始化:

intPtr.initialize(10)
// intPtr.memory 为 10

在完成初始化后,我们就可以通过 memory 来操作指针指向的内存值了。

在使用之后,我们最好尽快释放指针指向的内容和指针本身。与 initialize: 配对使用的 destroy 用来销毁指针指向的对象,而与 alloc: 对应的 dealloc: 用来释放之前申请的内存。它们都应该被配对使用:

intPtr.destroy()
intPtr.dealloc(1)
intPtr = nil

注意其实在这里对于 Int 这样的在 C 中映射为 int 的 “平凡值” 来说,destroy 并不是必要的,因为这些值被分配在常量段上。但是对于像类的对象或者结构体实例来说,如果不保证初始化和摧毁配对的话,是会出现内存泄露的。所以没有特殊考虑的话,不论内存中到底是什么,保证 initialize:destroy 配对会是一个好习惯。

指向数组的指针

在 Swift 中将一个数组作为参数传递到 C API 时,Swift 已经帮助我们完成了转换,这在 Apple 的[官方博客][3]中有个很好的例子:

[3]: https://developer.apple.com/swift/blog/?id=6

import Accelerate

let a: [Float] = [1, 2, 3, 4]
let b: [Float] = [0.5, 0.25, 0.125, 0.0625]
var result: [Float] = [0, 0, 0, 0]

vDSP_vadd(a, 1, b, 1, &result, 1, 4)

// result now contains [1.5, 2.25, 3.125, 4.0625]

对于一般的接受 const 数组的 C API,其要求的类型为 UnsafePointer,而非 const 的数组则对应 UnsafeMutablePointer。使用时,对于 const 的参数,我们直接将 Swift 数组传入 (上例中的 ab);而对于可变的数组,在前面加上 & 后传入即可 (上例中的 result)。

对于传参,Swift 进行了简化,使用起来非常方便。但是如果我们想要使用指针来像之前用 memory 的方式直接操作数组的话,就需要借助一个特殊的类型:UnsafeMutableBufferPointer。Buffer Pointer 是一段连续的内存的指针,通常用来表达像是数组或者字典这样的集合类型。

var array = [1, 2, 3, 4, 5]
var arrayPtr = UnsafeMutableBufferPointer<Int>(start: &array, count: array.count)
// baseAddress 是第一个元素的指针
var basePtr = arrayPtr.baseAddress as UnsafeMutablePointer<Int>

basePtr.memory // 1
basePtr.memory = 10
basePtr.memory // 10

//下一个元素
var nextPtr = basePtr.successor()
nextPtr.memory // 2

指针操作和转换

withUnsafePointer

上面我们说过,在 Swift 中不能像 C 里那样使用 & 符号直接获取地址来进行操作。如果我们想对某个变量进行指针操作,我们可以借助 withUnsafePointer 这个辅助方法。这个方法接受两个参数,第一个是 inout 的任意类型,第二个是一个闭包。Swift 会将第一个输入转换为指针,然后将这个转换后的 Unsafe 的指针作为参数,去调用闭包。使用起来大概是这个样子:

var test = 10
test = withUnsafeMutablePointer(&test, { (ptr: UnsafeMutablePointer<Int>) -> Int in
    ptr.memory += 1
    return ptr.memory
})

test // 11

这里其实我们做了和文章一开始的 incrementor 相同的事情,区别在于不需要通过方法的调用来将值转换为指针。这么做的好处对于那些只会执行一次的指针操作来说是显而易见的,可以将“我们就是想对这个指针做点事儿”这个意图表达得更加清晰明确。

unsafeBitCast

unsafeBitCast 是非常危险的操作,它会将一个指针指向的内存强制按位转换为目标的类型。因为这种转换是在 Swift 的类型管理之外进行的,因此编译器无法确保得到的类型是否确实正确,你必须明确地知道你在做什么。比如:

let arr = NSArray(object: "meow")
let str = unsafeBitCast(CFArrayGetValueAtIndex(arr, 0), CFString.self)
str // “meow”

因为 NSArray 是可以存放任意 NSObject 对象的,当我们在使用 CFArrayGetValueAtIndex 从中取值的时候,得到的结果将是一个 UnsafePointer<Void>。由于我们很明白其中存放的是 String 对象,因此可以直接将其强制转换为 CFString

关于 unsafeBitCast 一种更常见的使用场景是不同类型的指针之间进行转换。因为指针本身所占用的的大小是一定的,所以指针的类型进行转换是不会出什么致命问题的。这在与一些 C API 协作时会很常见。比如有很多 C API 要求的输入是 void *,对应到 Swift 中为 UnsafePointer<Void>。我们可以通过下面这样的方式将任意指针转换为 UnsafePointer。

var count = 100
var voidPtr = withUnsafePointer(&count, { (a: UnsafePointer<Int>) -> UnsafePointer<Void> in
    return unsafeBitCast(a, UnsafePointer<Void>.self)
})
// voidPtr 是 UnsafePointer<Void>。相当于 C 中的 void *

// 转换回 UnsafePointer<Int>
var intPtr = unsafeBitCast(voidPtr, UnsafePointer<Int>.self)
intPtr.memory //100

总结

Swift 从设计上来说就是以安全作为重要原则的,虽然可能有些啰嗦,但是还是要重申在 Swift 中直接使用和操作指针应该作为最后的手段,它们始终是无法确保安全的。从传统的 C 代码和与之无缝配合的 Objective-C 代码迁移到 Swift 并不是一件小工程,我们的代码库肯定会时不时出现一些和 C 协作的地方。我们当然可以选择使用 Swift 重写部分陈旧代码,但是对于像是安全或者性能至关重要的部分,我们可能除了继续使用 C API 以外别无选择。如果我们想要继续使用那些 API 的话,了解一些基本的 Swift 指针操作和使用的知识会很有帮助。

对于新的代码,尽量避免使用 Unsafe 开头的类型,意味着可以避免很多不必要的麻烦。Swift 给开发者带来的最大好处是可以让我们用更加先进的编程思想,进行更快和更专注的开发。只有在尊重这种思想的前提下,我们才能更好地享受这门新语言带来的种种优势。显然,这种思想是不包括到处使用 UnsafePointer 的 :)

最近的文章

跨平台开发时代的 (再次) 到来?

这篇文章主要想谈谈最近又刮起的移动开发跨平台之风,并着重介绍和对比一下像是 Xamarin,NativeScript 和 React Native 之类的东西。不会有特别深入的技术讨论,大家可以当作一篇科普类的文章来看。故事的开始“一次编码,处处运行” 永远是程序员们的理想乡。二十年前 Java 正是举着这面大旗登场,击败了众多竞争对手。但是时至今日,事实已经证明了 Java 笨重的体型和缓慢的发展显然已经很难再抓住这个时代快速跃动的脚步。在新时代的移动大潮下,一个应用想要取胜,完美的使用……

2015-03-27 • 能工巧匠集继续阅读

更早的文章

Apple WatchKit 初探

随着今天凌晨 Apple 发布了第一版的 Watch Kit 的 API,对于开发者来说,这款新设备的一些更详细的信息也算是逐渐浮出水面。可以说第一版的 WatchKit 开放的功能总体还是令人满意的。Apple 在承诺逐渐开放的方向上继续前进。本来在 WWDC 之后预期 Today Widget 会是各类新颖 app 的舞台以及对 iOS 功能的极大扩展,但是随着像 Launcher 和 PCalc 这些创意型的 Today Widget 接连被下架事件,让开发者也不得不下调对 Watc……

2014-11-19 • 能工巧匠集继续阅读

本站点采用知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议Jekyll 于 2017-05-05 生成,感谢 Digital Ocean 为本站提供稳定的 VPS 服务 本站由 @onevcat 创建,采用 Vno - Jekyll 作为主题,您可以在 GitHub 找到本站源码 - © 2017

图片合成视频

https://github.com/czboosj/HJImagesToVideo 自己修复了这个库的内存泄漏

https://github.com/Willib/ImagesToVideo
swift 版本的图片处理

https://stackoverflow.com/questions/3741323/how-do-i-export-uiimage-array-as-a-movie?noredirect=1&lq=1
图片合成详解

From: http://www.itqls.com/index.php?m=Home&c=Article&a=index&id=63

最近做一个无线设备相关的项目,

需要把扫描得到的图像,合成视频.

由于扫描得到的图像 是通过 drawRect 绘图画出来的,不是一个 Image

而图片转视频的方法是需要用 CGImage去实现的.

方法如下:

由于我需要得到 Image 数组, 就需要把我绘制出来的图片转换一下,

截屏: (正确姿势)

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
- (UIImage*)screenCap
{
    CGSize size=  CGSizeMake((int)self.bounds.size.width, (int)self.bounds.size.height);
    
//  size 为尺寸, YES 为不透明,第三个参数是缩放比例,直接根据屏幕设置保持清晰度
//  关于这个不透明好多博客都说错了,opaque 的意思是不透明...
    UIGraphicsBeginImageContextWithOptions(size, YES, [UIScreen mainScreen].scale);
// 遇到这个没拆分出来直接写,后发现内存泄漏,所以提出来 然后Relese
    CGContextRef context = UIGraphicsGetCurrentContext();
    [self.layer renderInContext:context];
    UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
CGContextRelease(context);
    UIGraphicsEndImageContext();
    return viewImage;
}

通过这个方法可以拿到当前屏幕的图像转换成的 Image

但是,如果需要拿到大量的图,比如100张,他就相当于去截图100张,

内存消耗会非常恐怖.

所以 调用 以上方法 screenCap 的时候, 在外面包一层 autoRelease 去解决.

图片转视频:

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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
- (void)saveVideo:(NSMutableArray *)imageArr withPaths:(NSString *)paths andCallBack:(void(^)(void))callBack
{
    if (!imageArr.count) {
        return;
    }
    
    UIImage *image = self.screenCap;
    
    CGSize sizeImage = image.size;
    int width = ((int) (sizeImage.width / 16) * 16);
    int height = ((int) (sizeImage.height / 16) * 16);
    NSLog(@"%d,%d",width,height);
    
    CGSize size = CGSizeMake(width, height);
    
    NSError *error = nil;
    unlink([paths UTF8String]);
    
    AVAssetWriter *videoWriter = [[AVAssetWriter alloc] initWithURL:
                                  [NSURL fileURLWithPath:paths]
                                fileType:AVFileTypeQuickTimeMovie
                                error:&error];
    
    NSParameterAssert(videoWriter);
    
    if(error)
        
        NSLog(@"error = %@", [error localizedDescription]);
    
    NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey,
                                   [NSNumber numberWithInt:size.width], AVVideoWidthKey,
                                   [NSNumber numberWithInt:size.height], AVVideoHeightKey, nil];
    
    AVAssetWriterInput *writerInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
    
    
    NSDictionary *sourcePixelBufferAttributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
                                                           
                                                           [NSNumber numberWithInt:kCVPixelFormatType_32ARGB], kCVPixelBufferPixelFormatTypeKey, nil];
    
    
    
    AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerInput
                                                            sourcePixelBufferAttributes:sourcePixelBufferAttributesDictionary];
    
    NSParameterAssert(writerInput);
    
    NSParameterAssert([videoWriter canAddInput:writerInput]);
    
    if ([videoWriter canAddInput:writerInput])
        NSLog(@"I can add this input");
    else
        NSLog(@"i can't add this input");
    
    [videoWriter addInput:writerInput];
    
    [videoWriter startWriting];
    
    [videoWriter startSessionAtSourceTime:kCMTimeZero];
    dispatch_queue_t    dispatchQueue = dispatch_queue_create("mediaInputQueue", NULL);
    
    [writerInput requestMediaDataWhenReadyOnQueue:dispatchQueue usingBlock:^{
            
        CVPixelBufferRef buffer = NULL;
        NSUInteger fps = 10;
        int frameCount = 0;
        double numberOfSecondsPerFrame = 0.1;
        double frameDuration = fps * numberOfSecondsPerFrame;
        
        for (UIImage *img in imageArr)
        {
            //convert uiimage to CGImage.
            buffer = [self pixelBufferFromCGImage:[img CGImage] size:size];
            
            BOOL append_ok = NO;
            int j = 0;
            while (!append_ok && j < 30)
            {
                if (adaptor.assetWriterInput.readyForMoreMediaData)
                {
                    CMTime frameTime = CMTimeMake(frameCount * frameDuration, (int32_t) fps);
                    append_ok = [adaptor appendPixelBuffer:buffer withPresentationTime:frameTime];
                    if (!append_ok)
                    {
                        NSError *error = videoWriter.error;
                        if (error != nil)
                        {
                            NSLog(@"Unresolved error %@,%@.", error, [error userInfo]);
                        }
                    }
                }
                else
                {
                    printf("adaptor not ready %d, %d\n", frameCount, j);
                    [NSThread sleepForTimeInterval:0.1];
                }
                j++;
            }
            if (!append_ok)
            {
                printf("error appending image %d times %d\n, with error.", frameCount, j);
            }
            frameCount++;
            
            CVPixelBufferRelease(buffer);
        }
        //Finish the session:
        [writerInput markAsFinished];
        [videoWriter finishWriting];
        
        callBack();
    }];
    
    NSLog(@"outside for loop");
    
}

下面这个方法是拿到 一个 buffer 的 , 通过它去拼接视频.

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
- (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image size:(CGSize)size
{
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,
                             [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey, nil];
    CVPixelBufferRef pxbuffer = NULL;
    CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, size.width, size.height, kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef) options, &pxbuffer);
    
    NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL);
    
    CVPixelBufferLockBaseAddress(pxbuffer, 0);
    void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);
    NSParameterAssert(pxdata != NULL);
    
    CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(pxdata, size.width, size.height, 8, 4 * size.width, rgbColorSpace, kCGImageAlphaPremultipliedFirst);
    NSParameterAssert(context);
    
    CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image);
    
    CGColorSpaceRelease(rgbColorSpace);
    CGContextRelease(context);
    CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
    return pxbuffer;
}

这里会有一个内存飙升的问题,所以要注意 release.

GPUImage录像的一些备忘

From: http://www.hudongdong.com/ios/530.html

GPUImage在加滤镜、图像处理上面集成的很完善,现在在使用的时候碰到的一些问题做下总结备忘,这些都是弄了很长时间才找到原因,如果碰到相似的可以节省下时间。当然简单的使用也可以看这个文章《IOS使用GPUImage滤镜初级试水

1、摄像头左右相反

使用GPU录像的时候,如果前置摄像头在左右是相反的,那么需要设置下镜像设置了

//如果是调用前置摄像头就发现成的像是左右相反了,就看下是不是开启了镜像
self.videoCamera.horizontallyMirrorFrontFacingCamera = YES;
self.videoCamera.horizontallyMirrorRearFacingCamera = NO;

2、开始录像的时候会图像会高亮下

如果录像的额时候,图像高亮下,可以在初始化GPUImageVideoCamera的时候,加上

[m_videoCamera addAudioInputsAndOutputs];

这个是为camera的增加的声音输入,但是这个如果是在一个界面的时候,频繁的增加有可能会造成录制的画面卡顿,所以可以在界面初始化Viewdidload的时候,初始化camera的时候加上这个代码。

3、第一帧偶尔黑屏

如果第一帧黑屏,应该是在开始录制等时间的问题,可以把要使用的视频第一帧裁减掉,裁剪视频的方案晚点会写成一个总结。

4、暂停/重录视频

如果仅仅是暂停录制,不暂停摄像头画面

//录制暂停
[m_movieWriter setPaused:true];
//录制继续
[m_movieWriter setPaused:false];

如果是暂停录制,并且也要暂停摄像头画面

//录制暂停
[m_movieWriter setPaused:true];
//摄像头暂停
[m_videoCamera pauseCameraCapture];
//录制继续
[m_movieWriter setPaused:false];
//摄像头继续
[m_videoCamera resumeCameraCapture];

5、暂停又重录会黑屏或者最后几秒有画面没声音

可以进入GPUImageMovieWriter.m中,在

- (void)processAudioBuffer:(CMSampleBufferRef)audioBuffer;

把里面的一段函数先注销掉

if (offsetTime.value > 0) {
     CFRelease(audioBuffer);
     audioBuffer = [self adjustTime:audioBuffer by:offsetTime];
     CFRetain(audioBuffer);
}

6、视频录制的代码

6.1、设置变量

//摄像头
    GPUImageVideoCamera *m_videoCamera;
    GPUImageFilter *m_customFilter;
    GPUImageView *m_filteredVideoView;
    //录制
    GPUImageMovieWriter *m_movieWriter;
    NSMutableDictionary * videoSettings; //字面意思.视频设置
    NSDictionary * audioSettings; //声音设置

6.2、初始化摄像头

m_videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset1280x720 cameraPosition:AVCaptureDevicePositionBack];
//    [m_videoCamera setFrameRate:60];
    //前置摄像头
    m_videoCamera.horizontallyMirrorFrontFacingCamera = YES;
    m_videoCamera.horizontallyMirrorRearFacingCamera = NO;
    m_videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
    [m_videoCamera addAudioInputsAndOutputs];
//    m_customFilter = [[AmomoFilter alloc] init];
    m_customFilter = [[GPUImageFilter alloc] init];

    m_filteredVideoView = [[GPUImageView alloc] initWithFrame:CGRectMake(0.0,0.0,JLXScreenSize.width,JLXScreenSize.height)];
    // Add the view somewhere so it's visible
    [self.view addSubview:m_filteredVideoView];

    //添加滤镜
    [m_videoCamera addTarget:m_customFilter];
    [m_customFilter addTarget:m_filteredVideoView];
    [m_videoCamera startCameraCapture];

6.3、初始化movieWriter

-(void)initcustomWriter
{
    oldVideoPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/Movie4.mov"];
    unlink([oldVideoPath UTF8String]); // 如果已经存在文件,AVAssetWriter会有异常,删除旧文件
    NSURL *movieURL = [JLXNetTools conVertToURL:oldVideoPath];

    //视频设置
    videoSettings = [[NSMutableDictionary alloc] init];
    [videoSettings setObject:AVVideoCodecH264 forKey:AVVideoCodecKey];
    [videoSettings setObject:[NSNumber numberWithInteger:720] forKey:AVVideoWidthKey]; //视频的宽度,这里最好是定义imageCamera时候的宽度
    [videoSettings setObject:[NSNumber numberWithInteger:1280] forKey:AVVideoHeightKey]; //视频的高度.同上

    //音频设置
    AudioChannelLayout channelLayout;
    memset(&channelLayout, 0, sizeof(AudioChannelLayout));
    channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
    audioSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                     [ NSNumber numberWithInt: kAudioFormatMPEG4AAC], AVFormatIDKey,
                     [ NSNumber numberWithInt: 2 ], AVNumberOfChannelsKey,
                     [ NSNumber numberWithFloat: 16000.0 ], AVSampleRateKey,
                     [ NSData dataWithBytes:&channelLayout length: sizeof( AudioChannelLayout ) ], AVChannelLayoutKey,
                     [ NSNumber numberWithInt: 32000 ], AVEncoderBitRateKey,
                     nil];

    m_movieWriter = [[GPUImageMovieWriter alloc] initWithMovieURL:movieURL size:CGSizeMake(720, 1280) fileType:AVFileTypeQuickTimeMovie outputSettings:videoSettings];
//    [m_movieWriter setHasAudioTrack:YES audioSettings:audioSettings];
    [m_movieWriter setHasAudioTrack:true];
    m_videoCamera.audioEncodingTarget = m_movieWriter; //设置声音
//    m_movieWriter.assetWriter.movieFragmentInterval = kCMTimeInvalid; //Couldn't write a frame
    m_movieWriter.encodingLiveVideo = YES;
    [m_customFilter addTarget:m_movieWriter];
}

6.4、开始录制

[m_movieWriter startRecording];

6.5、结束录制

[m_movieWriter finishRecordingWithCompletionHandler:^{
   }];
[m_customFilter removeTarget:m_movieWriter];
m_videoCamera.audioEncodingTarget = nil;

6.6、暂停/重录视频

如果仅仅是暂停录制,不暂停摄像头画面

//录制暂停
[m_movieWriter setPaused:true];
//录制继续
[m_movieWriter setPaused:false];

如果是暂停录制,并且也要暂停摄像头画面

//录制暂停
[m_movieWriter setPaused:true];
//摄像头暂停
[m_videoCamera pauseCameraCapture];
//录制继续
[m_movieWriter setPaused:false];
//摄像头继续
[m_videoCamera resumeCameraCapture];

6.7、取消录制

[m_movieWriter cancelRecording];

七、视频滤镜

7.1、一个滤镜

    //添加滤镜
m_customFilter = [[GPUImageFilter alloc] init];
[m_videoCamera addTarget:m_customFilter];
[m_customFilter addTarget:m_filteredVideoView];
[m_customFilter addTarget:m_movieWriter];

7.2、多个滤镜

多个滤镜需要使用滤镜组

//漫画
    GPUImageSketchFilter *disFilter = [[GPUImageSketchFilter alloc] init];
    //褐色
    GPUImageSepiaFilter* sepiaFilter = [[GPUImageSepiaFilter alloc] init];

    filterGroup = [[GPUImageFilterGroup alloc] init];
    [filterGroup addFilter:disFilter];
    [filterGroup addFilter:sepiaFilter];

    //先后顺序
    [disFilter addTarget:sepiaFilter];

    //开始的滤镜
    [filterGroup setInitialFilters:[NSArray arrayWithObject:disFilter]];
    [filterGroup setTerminalFilter:sepiaFilter];

    [filterGroup addTarget:m_filteredVideoView];
    [m_videoCamera addTarget:filterGroup];

    [filterGroup addTarget:m_movieWriter];
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
GPUImage是Brad Larson在github托管的一个开源项目,项目实现了图片滤镜、摄像头实时滤镜,该项目的优点不但在于滤镜很多,而且处理效果是基于GPU的,比使用CPU性能更高。
下载地址是:https://github.com/BradLarson/GPUImage
已有的一些filter介绍:
#import "GPUImageBrightnessFilter.h" //亮度
#import "GPUImageExposureFilter.h" //曝光
#import "GPUImageContrastFilter.h" //对比度
#import "GPUImageSaturationFilter.h" //饱和度
#import "GPUImageGammaFilter.h" //伽马线
#import "GPUImageColorInvertFilter.h" //反色
#import "GPUImageSepiaFilter.h" //褐色(怀旧)
#import "GPUImageLevelsFilter.h" //色阶
#import "GPUImageGrayscaleFilter.h" //灰度
#import "GPUImageHistogramFilter.h" //色彩直方图,显示在图片上
#import "GPUImageHistogramGenerator.h" //色彩直方图
#import "GPUImageRGBFilter.h" //RGB
#import "GPUImageToneCurveFilter.h" //色调曲线
#import "GPUImageMonochromeFilter.h" //单色
#import "GPUImageOpacityFilter.h" //不透明度
#import "GPUImageHighlightShadowFilter.h" //提亮阴影
#import "GPUImageFalseColorFilter.h" //色彩替换(替换亮部和暗部色彩)
#import "GPUImageHueFilter.h" //色度
#import "GPUImageChromaKeyFilter.h" //色度键
#import "GPUImageWhiteBalanceFilter.h" //白平横
#import "GPUImageAverageColor.h" //像素平均色值
#import "GPUImageSolidColorGenerator.h" //纯色
#import "GPUImageLuminosity.h" //亮度平均
#import "GPUImageAverageLuminanceThresholdFilter.h" //像素色值亮度平均,图像黑白(有类似漫画效果)
#import "GPUImageLookupFilter.h" //lookup 色彩调整
#import "GPUImageAmatorkaFilter.h" //Amatorka lookup
#import "GPUImageMissEtikateFilter.h" //MissEtikate lookup
#import "GPUImageSoftEleganceFilter.h" //SoftElegance lookup
#pragma mark - 图像处理 Handle Image
#import "GPUImageCrosshairGenerator.h" //十字
#import "GPUImageLineGenerator.h" //线条
#import "GPUImageTransformFilter.h" //形状变化
#import "GPUImageCropFilter.h" //剪裁
#import "GPUImageSharpenFilter.h" //锐化
#import "GPUImageUnsharpMaskFilter.h" //反遮罩锐化
#import "GPUImageFastBlurFilter.h" //模糊
#import "GPUImageGaussianBlurFilter.h" //高斯模糊
#import "GPUImageGaussianSelectiveBlurFilter.h" //高斯模糊,选择部分清晰
#import "GPUImageBoxBlurFilter.h" //盒状模糊
#import "GPUImageTiltShiftFilter.h" //条纹模糊,中间清晰,上下两端模糊
#import "GPUImageMedianFilter.h" //中间值,有种稍微模糊边缘的效果
#import "GPUImageBilateralFilter.h" //双边模糊
#import "GPUImageErosionFilter.h" //侵蚀边缘模糊,变黑白
#import "GPUImageRGBErosionFilter.h" //RGB侵蚀边缘模糊,有色彩
#import "GPUImageDilationFilter.h" //扩展边缘模糊,变黑白
#import "GPUImageRGBDilationFilter.h" //RGB扩展边缘模糊,有色彩
#import "GPUImageOpeningFilter.h" //黑白色调模糊
#import "GPUImageRGBOpeningFilter.h" //彩色模糊
#import "GPUImageClosingFilter.h" //黑白色调模糊,暗色会被提亮
#import "GPUImageRGBClosingFilter.h" //彩色模糊,暗色会被提亮
#import "GPUImageLanczosResamplingFilter.h" //Lanczos重取样,模糊效果
#import "GPUImageNonMaximumSuppressionFilter.h" //非最大抑制,只显示亮度最高的像素,其他为黑
#import "GPUImageThresholdedNonMaximumSuppressionFilter.h" //与上相比,像素丢失更多
#import "GPUImageSobelEdgeDetectionFilter.h" //Sobel边缘检测算法(白边,黑内容,有点漫画的反色效果)
#import "GPUImageCannyEdgeDetectionFilter.h" //Canny边缘检测算法(比上更强烈的黑白对比度)
#import "GPUImageThresholdEdgeDetectionFilter.h" //阈值边缘检测(效果与上差别不大)
#import "GPUImagePrewittEdgeDetectionFilter.h" //普瑞维特(Prewitt)边缘检测(效果与Sobel差不多,貌似更平滑)
#import "GPUImageXYDerivativeFilter.h" //XYDerivative边缘检测,画面以蓝色为主,绿色为边缘,带彩色
#import "GPUImageHarrisCornerDetectionFilter.h" //Harris角点检测,会有绿色小十字显示在图片角点处
#import "GPUImageNobleCornerDetectionFilter.h" //Noble角点检测,检测点更多
#import "GPUImageShiTomasiFeatureDetectionFilter.h" //ShiTomasi角点检测,与上差别不大
#import "GPUImageMotionDetector.h" //动作检测
#import "GPUImageHoughTransformLineDetector.h" //线条检测
#import "GPUImageParallelCoordinateLineTransformFilter.h" //平行线检测
#import "GPUImageLocalBinaryPatternFilter.h" //图像黑白化,并有大量噪点
#import "GPUImageLowPassFilter.h" //用于图像加亮
#import "GPUImageHighPassFilter.h" //图像低于某值时显示为黑
#pragma mark - 视觉效果 Visual Effect
#import "GPUImageSketchFilter.h" //素描
#import "GPUImageThresholdSketchFilter.h" //阀值素描,形成有噪点的素描
#import "GPUImageToonFilter.h" //卡通效果(黑色粗线描边)
#import "GPUImageSmoothToonFilter.h" //相比上面的效果更细腻,上面是粗旷的画风
#import "GPUImageKuwaharaFilter.h" //桑原(Kuwahara)滤波,水粉画的模糊效果;处理时间比较长,慎用
#import "GPUImageMosaicFilter.h" //黑白马赛克
#import "GPUImagePixellateFilter.h" //像素化
#import "GPUImagePolarPixellateFilter.h" //同心圆像素化
#import "GPUImageCrosshatchFilter.h" //交叉线阴影,形成黑白网状画面
#import "GPUImageColorPackingFilter.h" //色彩丢失,模糊(类似监控摄像效果)
#import "GPUImageVignetteFilter.h" //晕影,形成黑色圆形边缘,突出中间图像的效果
#import "GPUImageSwirlFilter.h" //漩涡,中间形成卷曲的画面
#import "GPUImageBulgeDistortionFilter.h" //凸起失真,鱼眼效果
#import "GPUImagePinchDistortionFilter.h" //收缩失真,凹面镜
#import "GPUImageStretchDistortionFilter.h" //伸展失真,哈哈镜
#import "GPUImageGlassSphereFilter.h" //水晶球效果
#import "GPUImageSphereRefractionFilter.h" //球形折射,图形倒立
#import "GPUImagePosterizeFilter.h" //色调分离,形成噪点效果
#import "GPUImageCGAColorspaceFilter.h" //CGA色彩滤镜,形成黑、浅蓝、紫色块的画面
#import "GPUImagePerlinNoiseFilter.h" //柏林噪点,花边噪点
#import "GPUImage3x3ConvolutionFilter.h" //3x3卷积,高亮大色块变黑,加亮边缘、线条等
#import "GPUImageEmbossFilter.h" //浮雕效果,带有点3d的感觉
#import "GPUImagePolkaDotFilter.h" //像素圆点花样
#import "GPUImageHalftoneFilter.h" //点染,图像黑白化,由黑点构成原图的大致图形
#pragma mark - 混合模式 Blend
#import "GPUImageMultiplyBlendFilter.h" //通常用于创建阴影和深度效果
#import "GPUImageNormalBlendFilter.h" //正常
#import "GPUImageAlphaBlendFilter.h" //透明混合,通常用于在背景上应用前景的透明度
#import "GPUImageDissolveBlendFilter.h" //溶解
#import "GPUImageOverlayBlendFilter.h" //叠加,通常用于创建阴影效果
#import "GPUImageDarkenBlendFilter.h" //加深混合,通常用于重叠类型
#import "GPUImageLightenBlendFilter.h" //减淡混合,通常用于重叠类型
#import "GPUImageSourceOverBlendFilter.h" //源混合
#import "GPUImageColorBurnBlendFilter.h" //色彩加深混合
#import "GPUImageColorDodgeBlendFilter.h" //色彩减淡混合
#import "GPUImageScreenBlendFilter.h" //屏幕包裹,通常用于创建亮点和镜头眩光
#import "GPUImageExclusionBlendFilter.h" //排除混合
#import "GPUImageDifferenceBlendFilter.h" //差异混合,通常用于创建更多变动的颜色
#import "GPUImageSubtractBlendFilter.h" //差值混合,通常用于创建两个图像之间的动画变暗模糊效果
#import "GPUImageHardLightBlendFilter.h" //强光混合,通常用于创建阴影效果
#import "GPUImageSoftLightBlendFilter.h" //柔光混合
#import "GPUImageChromaKeyBlendFilter.h" //色度键混合
#import "GPUImageMaskFilter.h" //遮罩混合
#import "GPUImageHazeFilter.h" //朦胧加暗
#import "GPUImageLuminanceThresholdFilter.h" //亮度阈
#import "GPUImageAdaptiveThresholdFilter.h" //自适应阈值
#import "GPUImageAddBlendFilter.h" //通常用于创建两个图像之间的动画变亮模糊效果
#import "GPUImageDivideBlendFilter.h" //通常用于创建两个图像之间的动画变暗模糊效果

记录Setting的跳转URL Scheme

// 更新ios8 后可以直接跳转到应用隐私设置

1
UIApplication.shared.openURL(URL(string:UIApplicationOpenSettingsURLString)!)

跳转方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
打开系统设置 等open
@param scheme <#scheme description#>
*/
-(void)openSysScheme:(NSString*)scheme{
UIApplication *application = [UIApplication sharedApplication];
NSURL *URL = [NSURL URLWithString:scheme];
if ([application respondsToSelector:@selector(openURL:options:completionHandler:)]) {
[application openURL:URL options:@{}
completionHandler:^(BOOL success) {
NSLog(@"Open %@: %d",scheme,success);
}];
} else {
BOOL success = [application openURL:URL];
NSLog(@"Open %@: %d",scheme,success);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func openSysSeting(type:openSysSetingType){
switch type {
case .photoPrivacy:// 相册的隐私设置
if #available(iOS 10.0, *) {
// UIApplication.shared.open(URL(string:"App-prefs:root=Photos")!)
UIApplication.shared.open(URL(string:"App-prefs:root=Privacy&path=PHOTOS")!, options: [:], completionHandler: { (bool) in
})
} else {
UIApplication.shared.openURL(URL(string:"prefs:root=Privacy&path=PHOTOS")!)
// Fallback on earlier versions
}
default:
break
}
}

From: http://noahzy.com/ji-lu-settingde-tiao-zhuan-url/

记录下各个页面的跳转URL Scheme,方便以后查询

IOS 10 后
"App-prefs:root=Privacy&path=CAMERA"

WIFI prefs:root=WIFI 个人热点 prefs:root=INTERNET_TETHERING 蜂窝设置 prefs:root=MOBILE_DATA_SETTINGS_ID 隐私 prefs:root=Privacy 隐私-相机 prefs:root=Privacy&path=CAMERA 隐私-相册 prefs:root=Privacy&path=PHOTOS 隐私-定位服务 prefs:root=LOCATION_SERVICES 通用 prefs:root=General&path VPN prefs:root=VPN 关于 prefs:root=General&path=About

辅助功能 prefs:root=General&path=ACCESSIBILITY 飞行模式 prefs:root=AIRPLANE_MODE 自动锁屏时间 prefs:root=General&path=AUTOLOCK 用量 prefs:root=General&path=USAGE 亮度 prefs:root=Brightness 蓝牙 prefs:root=General&path=Bluetooth 日期和时间 prefs:root=General&path=DATE_AND_TIME Facetime设置 prefs:root=FACETIME 键盘设置 prefs:root=General&path=Keyboard Icloud prefs:root=CASTLE 备份 prefs:root=CASTLE&path=STORAGE_AND_BACKUP 语言与地区设置 prefs:root=General&path=INTERNATIONAL 账户设置 prefs:root=ACCOUNT_SETTINGS 音乐 prefs:root=MUSIC EQ均衡器 prefs:root=MUSIC&path=EQ 设置 prefs:root= 备忘录 prefs:root=NOTES 通知 prefs:root=NOTIFICATIONS_ID 电话设置 prefs:root=Phone 照片与相机设置 prefs:root=Photos 还原 prefs:root=General&path=Reset 铃声设置 prefs:root=Sounds&path=Ringtone Safari prefs:root=Safari 声音 prefs:root=Sounds 系统更新 prefs:root=General&path=SOFTWARE_UPDATE_LINK STORE 设置 prefs:root=STORE 视频设置 prefs:root=VIDEO 壁纸设置 prefs:root=Wallpaper


2018-6-20 更新
苹果不让用这个私有方式了

From: https://www.jianshu.com/p/a94cd8103bba

例如跳转Wi-Fi,之前是使用prefs:root=WIFI来进行跳转

eg:

//iOS10
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"prefs:root=WIFI"] options:@{} completionHandler:nil];
//
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"prefs:root=WIFI"]];

这样在上架的时候会悲剧

中间进行一个转码,绕过苹果的代码扫描,亲测能过审核.

//将上面的跳转字符串转成字符,在进行拼接就好了
NSData *encryptString = [[NSData alloc] initWithBytes:(unsigned char []){0x70,0x72,0x65,0x66,0x73,0x3a,0x72,0x6f,0x6f,0x74,0x3d,0x4e,0x4f,0x54,0x49,0x46,0x49,0x43,0x41,0x54,0x49,0x4f,0x4e,0x53,0x5f,0x49,0x44} length:27];

NSString *string = [[NSString alloc] initWithData:encryptString encoding:NSUTF8StringEncoding];

[[UIApplication sharedApplication] openURL:[NSURL URLWithString:string] options:@{} completionHandler:nil];

swift

1
2
let dataString = Data.init(bytes: [0x70,0x72,0x65,0x66,0x73,0x3a,0x72,0x6f,0x6f,0x74,0x3d,0x50,0x72,0x69,0x76,0x61,0x63,0x79,0x26,0x70,0x61,0x74,0x68,0x3d,0x50,0x48,0x4f,0x54,0x4f,0x53])
let string = String.init(data: dataString, encoding: String.Encoding.utf8)

给一个转换的网站

http://www.ab126.com/goju/1711.html

一般ios 8 都用新的方式了, ios7 就这个转码吧