分类: IOS技术

IOS技术

URL编码 百分号编码 定义特点

1. URL 编码是:使用 百分号% + 十六进制数字 在URL中表示特殊字符的编码方式。
2. 为什么要使用 URL编码 百分号编码 ?
每一个 URL 都是单独的字符串。作为一个连续的字符串,URL 是不能存在空格的。所以在使用 URL 的时候需要使用其他的字符代替 空格。
URL 中经常使用到的 ? 和 # 等符号在 URL 中具有特殊的定义,在正常使用时,不能直接使用这些符号。所以我们需要使用其他字符代替这些符号。
根据 RFC 3986 协议定义,URL 对于在 URL 中用到的特殊字符和关键字,都需要使用特定的 URL 编码进行转化。
3. URL 如何转化特殊字符与关键字
主要分成两步:
step 1 :将保留字符转化为对应的 ASCⅡ 字节值 (byte value);
step 2 :将对应的对应的 ASCⅡ 字节值(十进制)转换成对应的十六进制值。
step 3 :在对应十六进制值前面加上百分号 % 。
4. 示例:空格的 URL 编码转化:
空格 -> ASCⅡ 字节值 : 32 -> 十六进制值 : 20 -> 前加 % :%20

【读书笔记】iOS-网络-HTTP-URL百分号编码

代码:

– (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

//URL百分号编码
//URL编码会编码&,问号和其他标点符号。
NSString *urlString=@”http://myhost.com?query=This is a question”;
NSString *encoded=[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(@”–urlString–%@”,urlString);
NSLog(@”–endcoded–%@”,encoded);

}

 

输出:

2015-08-18 21:41:58.501 百分号编码[730:74165] –urlString–http://myhost.com?query=This is a question
2015-08-18 21:41:58.501 百分号编码[730:74165] –endcoded–http://myhost.com?query=This%20is%20a%20question

巧用iOS宏定义

1 __attribute__((used,section…)) 把某个变量的放入特殊的section中
2
3 用法:
4 char *kChinaPYG __attribute((used, section(“__DATA, ChinaPYG”))) = “ChinaPYG.CoM”;
5 char *kDllHook __attribute((used, section(“__DATA, DllHook”))) = “DllHook.CoM”;
6 使用 used字段,即使没有任何引用,在Release下也不会被优化
7
8 进阶:宏定义来实现上述过程
9 #define XX_EXPORT_MODULE(module, impl) \
10 class NSObject; char * k##module __XX_MODULE_DATA(XXModule) = “{\””#module”\”:\””#impl”\”}”
11
12 #define __XX_MODULE_DATA(XXModule) __attribute((used, section(“__DATA, “#XXModule””)))
13
14 获取文件名
15 #define _XX_FILE_ (strrchr(__FILE__, ‘/’) ? strrchr(__FILE__, ‘/’) + 1 : __FILE__)
16
17 获取代码行
18 __LINE__
19
20 __func__,__FUNCTION__ ,__PRETTY_FUNCTION__
21 描述
22 GCC实现了如下的函数宏
23 __func__ C99的标准,但是GCC只输出函数名称。
24 __FUNCTION__ 同 __func__,
25 __PRETTY_FUNCTION__ 非标准宏。这个宏比 __FUNCTION__ 功能更强:
26 (1)若用 g++ 编译 C++ 程序, __FUNCTION__ 只能输出类的成员函数名,不会输出类名;而 __PRETTY_FUNCTION__ 则会以 <return-type> <class-name>::<member-function-name>(<parameters-list>)的格式输出成员函数的详悉信息(注: 只会输出 parameters-list 的形参类型,而不会输出形参名)。
27 (2) 若用 gcc 编译 C 程序,__PRETTY_FUNCTION__ 跟 __FUNCTION__ 的功能相同.
28 __VA_ARGS__
29 描述
30 C99 编译器标准允许定义可变参数宏(variadic macros),这样就使用拥有可以变化的参数表的宏。
31 #define FYFLog(format, …) NSLog(format, __VA_ARGS__)
32 缺省号代表一个可以变化的参数表。使用保留名 ‘__VA_ARGS__’ 把参数传递给宏。当宏的调用展开时,实际的参数就传递给 NSLog() 了。
33
34
35 ##
36 描述
37 (1)在标准 C 里,你不能省略可变参数,但是你却可以给它传递一个空的参数。GNU CPP 在这种情况下可以让你完全的忽略可变参数。在上面的例子中,编译器仍然会有问题( complain ),因为宏展开后,里面的字符串后面会有个多余的逗号。
38 为了解决这个问题, CPP 使用一个特殊的‘ ## ’操作。书写格式为:
39 #define XXLog(format, …) NSLog(format, ##__VA_ARGS__)
40 这里,如果可变参数被忽略或为空,‘ ## ’操作将使预处理器( preprocessor )去除掉它前面的那个逗号。如果你在宏调用时,确实提供了一些可变参数, GNU CPP 也会工作正常,它会把这些可变参数放到逗号的后面。
41
42 宏定义再进阶
43 #define __XX_CONCAT(A, B) A ## B
44 #define XX_DYNAMIC __attribute__((objc_dynamic))
45 #define XX_EXPORT_XXXX(SomeThing) \
46 XX_EXPORT_XXXX_REMAP(, SomeThing)
47
48 #define XX_EXPORT_XXXX_REMAP(name, SomeThing) \
49 _XX_EXPORT_XXXX_REMAP_INTERNAL(name, SomeThing, “__hello_key__”)
50
51 #define _XX_EXPORT_XXXX_REMAP_INTERNAL(name, SomeThing, key) \
52 + (NSArray *)__XX_CONCAT(key, __LINE__) XX_DYNAMIC { \
53     return @[NSStringFromSelector(SomeThing), name]; \
54 }

iOS判断字符串中是否含有非法字符

iOS判断字符串中是否含有非法字符 (非法字符是指 除数字 字母 文字以外的所有字符)    

1

2

3

4

5

6

7

8

9

10

//判断是否含有非法字符 yes 有  no没有

+ (BOOL)JudgeTheillegalCharacter:(NSString *)content{

    //提示 标签不能输入特殊字符

    NSString *str =@“^[A-Za-z0-9\\u4e00-\u9fa5]+$”;

    NSPredicate* emailTest = [NSPredicate predicateWithFormat:@“SELF MATCHES %@”, str];

    if (![emailTest evaluateWithObject:content]) {

        return YES;

    }

    return NO;

}

ios 音视频处理优化总结

做了那么多年C底层,现在做iOS上层,感觉还是有很多优势的。做底层的时候就做过音视频的东西,上层有很多类似,相通的东西。

公司做了个视频软件,实时监控。程序丢包严重,卡顿,还会黑屏。我接手之前就是这个样子。经过一天的摸索,结合之前看了一天的经验,把丢包问题,黑屏问题跟卡顿问题通通解决了。

由于代码不是我写的,我想了个很初级的办法,在所有音视频关键函数打上断点(对xcode来说很方便,体力活),在半个小时内,把视频播放的流程走了一遍。

把音视频路线纪录了下来,把重复进入的无关紧要的函数进行严查,严查程序里用到的所有定时器,特别是耗时的定时器,间隔时间又特别短,发现很多不合理的地方。这些定时器都跟UI没有直接关系,却都跑在主线程,非常占用主线程CPU资源。导致卡顿。分别做了线程runloop处理。

然后是发现一个奇葩的做法,就是用定时器刷新缓冲区,这个是数据接收的缓冲区。间隔时间还特别短。在网络传输高分辨率的图片时,必然造成来不及读取缓冲区,就发现图片已经丢失的情况,造成反复丢帧。我毫不犹豫就把定时器干掉了。至于缓冲区是否会越界,溢出的问题,后面再看。后面发现不会,每次接收都是覆盖缓冲区的。还需要时间检验跟调试工具进一步内存跟踪调试。

再一个发现在跳帧处理的时候,时间逻辑有问题,没有实时更新缓存时间。然后就是frame还在进行插入操作的时候就把frame提前释放的问题。

还有就是OPENGL 转换出RGB数据的时候,数据格式的处理,位数跟深度的处理有误,造成像素没那么高的问题。保存*帧图片的时候也有这个问题。

iOS高级编程 runtime

代码示例:

1 #import <objc/runtime.h>
2 #import <objc/message.h>
3 #import <stdio.h>
4
5 extern int MyUIApplicationMain(int argc, char *argv[], void *principalClassName, void *delegateClassName);
6
7 struct MyRect {
8     float x;
9     float y;
10     float width;
11     float height;
12 };
13 typedef struct MyRect MyRect;
14
15 void *navController;
16 static int numberOfRows = 100;
17
18 int tableView_numberOfRowInSection(void *receiver, struct objc_selector *selector, void *tblView, int section) {
19     return numberOfRows;
20 }
21
22 void *tableView_cellForRowAtIndexPath(void *receiver, struct objc_selector *selector, void *tblView, void *indexPath) {
23     Class TableViewCell = (Class)objc_getClass(“UITableViewCell”);
24     void *cell = class_createInstance(TableViewCell, 0);
25     objc_msgSend(cell, sel_registerName(“init”));
26     char buffer[7];
27
28     int row = (int)objc_msgSend(indexPath, sel_registerName(“row”));
29     sprintf(buffer, “Row %d”, row);
30     void *label = objc_msgSend(objc_getClass(“NSString”), sel_registerName(“stringWithUTF8String:”), buffer);
31     objc_msgSend(cell, sel_registerName(“setText:”), label);
32
33     return cell;
34 }
35
36 void tableView_didSelectRowAtIndexPath(void *receiver, struct objc_selector *seletor, void *tblView, void *indexPath) {
37     Class ViewController = (Class)objc_getClass(“UIViewController”);
38     void *vc = class_createInstance(ViewController, 0);
39     objc_msgSend(vc, sel_registerName(“init”));
40     char buffer[8];
41     int row = (int)objc_msgSend(indexPath, sel_registerName(“row”));
42     sprintf(buffer, “Item %d”, row);
43     void *label = objc_msgSend(objc_getClass(“NSString”), sel_registerName(“stringWithUTF8String”), buffer);
44     objc_msgSend(vc, sel_registerName(“setTitle”), label);
45
46     objc_msgSend(navController, sel_registerName(“pushViewController:animated:”), vc, 1);
47 }
48
49 void *createDataSource() {
50     Class superclass = (Class)objc_getClass(“NSObject”);
51     Class DataSource = objc_allocateClassPair(superclass, “DataSource”, 0);
52     class_addMethod(DataSource, sel_registerName(“tableView:numberOfRowsInSection:”), (void (*))tableView_numberOfRowInSection, nil);
53     class_addMethod(DataSource, sel_registerName(“tableView:cellForRowAtIndexPath:”), (void (*))tableView_cellForRowAtIndexPath, nil);
54
55     objc_registerClassPair(DataSource);
56     return class_createInstance(DataSource, 0);
57 }
58
59 void *createDelegate() {
60     Class superClass = (Class)object_getClass(@”NSObject”);
61     Class DataSource = objc_allocateClassPair(superClass, “Delegate”, 0);
62     class_addMethod(DataSource, sel_registerName(“tableView:didSelectRowAtIndexPath:”), (void (*))tableView_didSelectRowAtIndexPath, nil);
63
64     objc_registerClassPair(DataSource);
65     return class_createInstance(DataSource, 0);
66 }
67
68 //自定义主函数
69 void applicationdidFinishLaunching(void *receiver, struct objc_selector *selector, void *application) {
70     Class windowClass = (Class)object_getClass(@”UIWindow”);
71     void *windowInstance = class_createInstance(windowClass, 0);
72
73     objc_msgSend(windowClass, sel_registerName(“initWithFrame:”), (MyRect){0, 0, 320, 480});
74
75     //make key and visiable
76     objc_msgSend(windowInstance, sel_registerName(“makeKeyAndVisible”));
77
78     //创建表
79     Class TableViewController = (Class)object_getClass(@”UITableViewController”);
80     void *tableViewController = class_createInstance(TableViewController, 0);
81     objc_msgSend(TableViewController, sel_registerName(“init”));
82     void *tableView = objc_msgSend(TableViewController, sel_registerName(“tableView”));
83     objc_msgSend(tableView, sel_registerName(“setDataSource”), createDataSource());
84     objc_msgSend(tableView, sel_registerName(“setDelegate”), createDelegate());
85
86     Class NavController = (Class)object_getClass(@”UINavigationController”);
87     navController = class_createInstance(NavController, 0);
88     objc_msgSend(navController, sel_registerName(“initWithRootViewCOntroller:”), tableViewController);
89     void *view = objc_msgSend(navController, sel_registerName(“view”));
90
91     //add TableView to window
92     objc_msgSend(windowInstance, sel_registerName(“addSubview:”), view);
93
94 }
95
96 //create an class named “AppDelegate”, and return its name as an instance of class NSString
97 void *createAppDelegate() {
98     Class mySubclass = objc_allocateClassPair((Class)object_getClass(@”NSObject”), “AppDelegate”, 0);
99     struct objc_selector *selName = sel_registerName(“application:didFinishLaunchingWithOptions:”);
100     class_addMethod(mySubclass, selName, (void (*))applicationdidFinishLaunching, nil);
101     objc_registerClassPair(mySubclass);
102     return objc_msgSend(object_getClass(@”NNString”), sel_registerName(“stringWithUTF8String:”), “AppDelegate”);
103 }
104
105 int main(int argc, char *argv[]) {
106     return MyUIApplicationMain(argc, argv, 0, createAppDelegate());

iOS 实时录音和播放

需求:*近公司需要做一个楼宇对讲的功能:门口机(连接WIFI)拨号对室内机(对应的WIFI)的设备进行呼叫,室内机收到呼叫之后将对收到的数据进行UDP广播的转发,手机(连接对应的WIFI)收到视频流之后,实时的展示视频数据(手机可以接听,挂断,手机接听之后,室内机不展示视频,只是进行转发。)

简单点说就是手机客户端需要做一个类似于直播平台的软件,可以实时的展示视频,实时的播放接收到的声音数据,并且实时将手机麦克风收到的声音回传给室内机,室内机负责转发给门口机。

 

这篇文章介绍iOS怎么进行实时的录音和播放收到的声音数据

 

想要使用系统的框架实时播放声音和录音数据,就得知道音频队列服务,

在AudioToolbox框架中的音频队列服务,它完全可以做到音频播放和录制,

一个音频服务队列有三个部分组成:

1.三个缓冲器Buffers:每个缓冲器都是一个存储音频数据的临时仓库。
2.一个缓冲队列Buffer Queue:一个包含音频缓冲器的有序队列。
3.一个回调CallBack:一个自定义的队列回调函数。

具体怎么运转的还是百度吧!

我的简单理解:

对于播放:系统会自动从缓冲队列中循环取出每个缓冲器中的数据进行播放,我们需要做的就是将接收到的数据循环的放到缓冲器中,剩下的就交给系统去实现了。

对于录音:  系统会自动将录的声音放入队列中的每个缓冲器中,我们需要做的就是从回调函数中将数据转化我们自己的数据就OK了。

 

#pragma mark–实时播放

1. 导入系统框架AudioToolbox.framework  AVFoundation.framework

2. 获取麦克风权限,在工程的Info.plist文件中加入Privacy – Microphone Usage Description 这个key 描述:App想要访问您的麦克风

3. 创建播放声音的类 EYAudio

 

EYAudio.h

#import <Foundation/Foundation.h>

@interface EYAudio : NSObject

// 播放的数据流数据
– (void)playWithData:(NSData *)data;

// 声音播放出现问题的时候可以重置一下
– (void)resetPlay;

// 停止播放
– (void)stop;

@end

 

EYAudio.m

 

#import “EYAudio.h”
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>

#define MIN_SIZE_PER_FRAME 1920 //每个包的大小,室内机要求为960,具体看下面的配置信息
#define QUEUE_BUFFER_SIZE 3 //缓冲器个数
#define SAMPLE_RATE 16000 //采样频率

@interface EYAudio(){
AudioQueueRef audioQueue; //音频播放队列
AudioStreamBasicDescription _audioDescription;
AudioQueueBufferRef audioQueueBuffers[QUEUE_BUFFER_SIZE]; //音频缓存
BOOL audioQueueBufferUsed[QUEUE_BUFFER_SIZE]; //判断音频缓存是否在使用
NSLock *sysnLock;
NSMutableData *tempData;
OSStatus osState;
}
@end

@implementation EYAudio

#pragma mark – 提前设置AVAudioSessionCategoryMultiRoute 播放和录音
+ (void)initialize
{
NSError *error = nil;
//只想要播放:AVAudioSessionCategoryPlayback
//只想要录音:AVAudioSessionCategoryRecord
//想要”播放和录音”同时进行 必须设置为:AVAudioSessionCategoryMultiRoute 而不是AVAudioSessionCategoryPlayAndRecord(设置这个不好使)
BOOL ret = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryMultiRoute error:&error];
if (!ret) {
NSLog(@”设置声音环境失败”);
return;
}
//启用audio session
ret = [[AVAudioSession sharedInstance] setActive:YES error:&error];
if (!ret)
{
NSLog(@”启动失败”);
return;
}
}

– (void)resetPlay
{
if (audioQueue != nil) {
AudioQueueReset(audioQueue);
}
}

– (void)stop
{
if (audioQueue != nil) {
AudioQueueStop(audioQueue,true);
}

audioQueue = nil;
sysnLock = nil;
}

– (instancetype)init
{
self = [super init];
if (self) {
sysnLock = [[NSLock alloc]init];

//设置音频参数 具体的信息需要问后台
_audioDescription.mSampleRate = SAMPLE_RATE;
_audioDescription.mFormatID = kAudioFormatLinearPCM;
_audioDescription.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
//1单声道
_audioDescription.mChannelsPerFrame = 1;
//每一个packet一侦数据,每个数据包下的桢数,即每个数据包里面有多少桢
_audioDescription.mFramesPerPacket = 1;
//每个采样点16bit量化 语音每采样点占用位数
_audioDescription.mBitsPerChannel = 16;
_audioDescription.mBytesPerFrame = (_audioDescription.mBitsPerChannel / 8) * _audioDescription.mChannelsPerFrame;
//每个数据包的bytes总数,每桢的bytes数*每个数据包的桢数
_audioDescription.mBytesPerPacket = _audioDescription.mBytesPerFrame * _audioDescription.mFramesPerPacket;

// 使用player的内部线程播放 新建输出
AudioQueueNewOutput(&_audioDescription, AudioPlayerAQInputCallback, (__bridge void * _Nullable)(self), nil, 0, 0, &audioQueue);

// 设置音量
AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, 1.0);

// 初始化需要的缓冲区
for (int i = 0; i < QUEUE_BUFFER_SIZE; i++) {
audioQueueBufferUsed[i] = false;
osState = AudioQueueAllocateBuffer(audioQueue, MIN_SIZE_PER_FRAME, &audioQueueBuffers[i]);
}

osState = AudioQueueStart(audioQueue, NULL);
if (osState != noErr) {
NSLog(@”AudioQueueStart Error”);
}
}
return self;
}

// 播放数据
-(void)playWithData:(NSData *)data
{
[sysnLock lock];

tempData = [NSMutableData new];
[tempData appendData: data];
NSUInteger len = tempData.length;
Byte *bytes = (Byte*)malloc(len);
[tempData getBytes:bytes length: len];

int i = 0;
while (true) {
if (!audioQueueBufferUsed[i]) {
audioQueueBufferUsed[i] = true;
break;
}else {
i++;
if (i >= QUEUE_BUFFER_SIZE) {
i = 0;
}
}
}

audioQueueBuffers[i] -> mAudioDataByteSize = (unsigned int)len;
// 把bytes的头地址开始的len字节给mAudioData,向第i个缓冲器
memcpy(audioQueueBuffers[i] -> mAudioData, bytes, len);

// 释放对象
free(bytes);

//将第i个缓冲器放到队列中,剩下的都交给系统了
AudioQueueEnqueueBuffer(audioQueue, audioQueueBuffers[i], 0, NULL);

[sysnLock unlock];
}

// ************************** 回调 **********************************
// 回调回来把buffer状态设为未使用
static void AudioPlayerAQInputCallback(void* inUserData,AudioQueueRef audioQueueRef, AudioQueueBufferRef audioQueueBufferRef) {

EYAudio* audio = (__bridge EYAudio*)inUserData;

;
}

– (void)resetBufferState:(AudioQueueRef)audioQueueRef and:(AudioQueueBufferRef)audioQueueBufferRef {
// 防止空数据让audioqueue后续都不播放,为了安全防护一下
if (tempData.length == 0) {
audioQueueBufferRef->mAudioDataByteSize = 1;
Byte* byte = audioQueueBufferRef->mAudioData;
byte = 0;
AudioQueueEnqueueBuffer(audioQueueRef, audioQueueBufferRef, 0, NULL);
}

for (int i = 0; i < QUEUE_BUFFER_SIZE; i++) {
// 将这个buffer设为未使用
if (audioQueueBufferRef == audioQueueBuffers[i]) {
audioQueueBufferUsed[i] = false;
}
}
}

@end

 

 

外界使用: 不断调用下面的方法将NSData传递进来

– (void)playWithData:(NSData *)data;

 

#pragma mark–实时录音

 

1. 导入系统框架AudioToolbox.framework  AVFoundation.framework

2. 创建录音的类 EYRecord

 

EYRecord.h

 

#import <Foundation/Foundation.h>

@interface ESARecord : NSObject

//开始录音
– (void)startRecording;

//停止录音
– (void)stopRecording;

@end

 

EYRecord.m

 

#import “ESARecord.h”
#import <AudioToolbox/AudioToolbox.h>

#define QUEUE_BUFFER_SIZE 3 // 输出音频队列缓冲个数
#define kDefaultBufferDurationSeconds 0.03//调整这个值使得录音的缓冲区大小为960,实际会小于或等于960,需要处理小于960的情况
#define kDefaultSampleRate 16000 //定义采样率为16000

extern NSString * const ESAIntercomNotifationRecordString;

static BOOL isRecording = NO;

@interface ESARecord(){
AudioQueueRef _audioQueue; //输出音频播放队列
AudioStreamBasicDescription _recordFormat;
AudioQueueBufferRef _audioBuffers[QUEUE_BUFFER_SIZE]; //输出音频缓存
}
@property (nonatomic, assign) BOOL isRecording;

@end

@implementation ESARecord

– (instancetype)init
{
self = [super init];
if (self) {
//重置下
memset(&_recordFormat, 0, sizeof(_recordFormat));
_recordFormat.mSampleRate = kDefaultSampleRate;
_recordFormat.mChannelsPerFrame = 1;
_recordFormat.mFormatID = kAudioFormatLinearPCM;

_recordFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
_recordFormat.mBitsPerChannel = 16;
_recordFormat.mBytesPerPacket = _recordFormat.mBytesPerFrame = (_recordFormat.mBitsPerChannel / 8) * _recordFormat.mChannelsPerFrame;
_recordFormat.mFramesPerPacket = 1;

//初始化音频输入队列
AudioQueueNewInput(&_recordFormat, inputBufferHandler, (__bridge void *)(self), NULL, NULL, 0, &_audioQueue);

//计算估算的缓存区大小
int frames = (int)ceil(kDefaultBufferDurationSeconds * _recordFormat.mSampleRate);
int bufferByteSize = frames * _recordFormat.mBytesPerFrame;

NSLog(@”缓存区大小%d”,bufferByteSize);

//创建缓冲器
for (int i = 0; i < QUEUE_BUFFER_SIZE; i++){
AudioQueueAllocateBuffer(_audioQueue, bufferByteSize, &_audioBuffers[i]);
AudioQueueEnqueueBuffer(_audioQueue, _audioBuffers[i], 0, NULL);
}
}
return self;
}

-(void)startRecording
{
// 开始录音
AudioQueueStart(_audioQueue, NULL);
isRecording = YES;
}

void inputBufferHandler(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime,UInt32 inNumPackets, const AudioStreamPacketDescription *inPacketDesc)
{
if (inNumPackets > 0) {
ESARecord *recorder = (__bridge ESARecord*)inUserData;
[recorder processAudioBuffer:inBuffer withQueue:inAQ];
}

if (isRecording) {
AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
}
}

– (void)processAudioBuffer:(AudioQueueBufferRef )audioQueueBufferRef withQueue:(AudioQueueRef )audioQueueRef
{
NSMutableData * dataM = [NSMutableData dataWithBytes:audioQueueBufferRef->mAudioData length:audioQueueBufferRef->mAudioDataByteSize];

if (dataM.length < 960) { //处理长度小于960的情况,此处是补00
Byte byte[] = {0x00};
NSData * zeroData = [[NSData alloc] initWithBytes:byte length:1];
for (NSUInteger i = dataM.length; i < 960; i++) {
[dataM appendData:zeroData];
}
}

// NSLog(@”实时录音的数据–%@”, dataM);
//此处是发通知将dataM 传递出去
[[NSNotificationCenter defaultCenter] postNotificationName:@”EYRecordNotifacation” object:@{@”data” : dataM}];
}

-(void)stopRecording
{
if (isRecording)
{
isRecording = NO;

//停止录音队列和移除缓冲区,以及关闭session,这里无需考虑成功与否
AudioQueueStop(_audioQueue, true);
//移除缓冲区,true代表立即结束录制,false代表将缓冲区处理完再结束
AudioQueueDispose(_audioQueue, true);
}
NSLog(@”停止录音”);
}

@end

基于iOS的网络音视频实时传输系统(一)- 前言

演示图

(由于GIF大小限制,图做得比较模糊和不太流畅,实际的效果是不错的,可以自己下下来实际跑一下)

client端:

%title插图%num

%title插图%num
server端:

%title插图%num

%title插图%num

下载

GitHub:

client 端:https://github.com/AmoAmoAmo/Smart_Device_Client

server端:https://github.com/AmoAmoAmo/Smart_Device_Server

另还写了一份macOS版的server,但是目前还有一些问题,有兴趣的去看看吧:https://github.com/AmoAmoAmo/Server_Mac

简介

之前在做类似的网络协议的时候,突发奇想,想写一个网络视频监控,基于局域网的情况下,将MacBook摄像头捕获到的视频,在手机端显示,但是由于对macOS不是很熟悉,*终导致该计划流产。所以后来干脆使用手机捕获视频数据。

为了简化项目工作量,socket协议也只用到了一些必要的功能,其他细节如client端退出监控视频时,server端会crash,各位有需要可以自行去添加一些如设置select()函数,或者设置signal()函数忽略这个断开的信号。等等

项目中没有写录制设备视频的功能,所以没有用到MP4封装
更多其他的细节已经搭建过程,有兴趣的可以去我的GitHub上回退到各个版本看循序渐进的过程。关于音视频我也是初学者,欢迎各位斧正。

主要功能

client端:

1. udp局域网搜索设备(模拟设备的代码在这里),或者手动添加其他设备(并没有功能) 到plist
2. 点击已添加的监控设备,开始TCP音视频数据传输
3. 接收到音视频数据,进行解码,并用OpenGL es渲染显示到界面上 或openAL播放音频
4. 横竖屏功能

server端(摄像头):

1. 点击“reset”,进入“配对模式”,即开始UDP监听AP
2. 连接成功后,将自己的设备信息发送给client
3. 开始捕获音视频,并进行硬编码,发送给client

使用方法

*次使用时,先点击“server”端的“reset”按钮,表示开始处于配对模式,

“client”端点击“+”按钮,开始去局域网里搜索(UDP)设备,就会得到设备的IP地址等信息,后面音视频传输就是用这个IP地址来建立TCP通信的。这个过程模拟实际应用中

这里音视频传输用TCP还是UDP没有什么讲究,主要是根据嵌入式工程师给的音视频协议文档里用到的是哪个。

这里就是用“server”端来模拟一种型号的监控摄像头。

找到设备后,信息会自动被填写到textField上,点击“添加”,设备就被写入plist文件里。

点击已添加的监控视频,即可开始音视频传输

iOS开发——APP回退到历史版本

1.软件准备

  • 【必备】Charles

2.正式开始

2.1打开Charles青花瓷
%title插图%num

2.2安装证书以便能够拦截解析HTTPS请求数据
%title插图%num

2.2.1如果是选择本地安装证书的话,记住保存格式选择.cer
%title插图%num

2.2.2双击下载好的证书
%title插图%num

2.2.3右键点击证书
%title插图%num

2.2.4信任证书
%title插图%num

2.2.5 ssl代理证书配置完成
%title插图%num

2.3打开iTunes
%title插图%num

2.4单击编辑左上角以便能够出现应用这个选项
%title插图%num

2.5在下拉选项中选择应用
%title插图%num

2.6单击正上方的App Store
%title插图%num

2.7搜索需要下载历史版本的App,这里我以IT之家为例
%title插图%num

2.8点击获取按钮,就会变成正在下载
%title插图%num

2.9这时候查看Charles能够看到一大堆请求,看关键字p28-buy,这个是Itunes的下载请求,而且是HTTPS加密的,链接旁边有一把小锁,不能查看里面的内容
%title插图%num

2.10打开ssl代理并打断点拦截该请求
%title插图%num

2.11进入iTunes删除刚才下载的App,因为刚才只为为了获取下载请求
%title插图%num

2.12删除好App后刷新一下,重启Charles,再次点击iTunes的获取按钮
%title插图%num

2.13可以看到这时候已经能够查看HTTPS请求里面的内容了,点击图中相应位置
%title插图%num

2.14往下翻,可以查看到一大串数字,这些全都是该应用的历史版本编号,我们只需要把想下载的历史版本的编号复制下来就好,后面会用到,这里我复制*个11577427,为该应用程序的*个版本
%title插图%num

2.15再次点击获取按钮,下载请求会被拦截,跳转到Charles
%title插图%num

2.16这时候把刚才我们复制好的历史版本号
%title插图%num

2.17点击Excute执行按钮,可能会来断点2次,除了*次,后面的都直接点击Excute执行按钮
%title插图%num

2.18大功告成!!!
%title插图%num

2.19查看下载好的App的版本号
%title插图%num

%title插图%num

%title插图%num

3.安装旧版本应用程序

旧版本的应用程序下载好了,接下来我们要安装到手机上才是王道,或者进行其他用途,这里介绍下2种安装到iPhone上的方法。

  • 1.利用iTunes的安装
    手机链接电脑,iTunes会自动弹出,然后会进行同步,刚才电脑上下载好的应用会自动安装到iPhone上。
  • 2.利用iTools的安装
%title插图%num

iOS自定义Log(OC方法与Swift方法)

新项目开始之时,自定义Log
在debug环境下打印内容,但是在release环境下不显示打印

动态获取打印所在文件

OC方法:

在pch文件里面添加:

#ifdef DEBUG
#define DLog(fmt, …) NSLog((@”%s [Line %d] ” fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define DLog(…)
#endif

printLog(@”test”);

打印输出:

Swift方法:

func printLog<T>(message:T,
file: String = #file,
method: String = #function,
line: Int = #line) {

#if DEBUG
guard System.isEnablePrint == true else {
return
}
print(“路径:\((file as NSString).lastPathComponent)[\(line)],方法:\(method),信息:\(message)”)
#endif

}

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速