-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
1431 lines (1147 loc) · 108 KB
/
index.html
File metadata and controls
1431 lines (1147 loc) · 108 KB
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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html" charset="UTF-8" >
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="generator" content="Simplicity + Power = Elegance">
<title>Simplicity + Power = Elegance</title>
<meta name="author" content="max Lin">
<link rel="alternate" type="application/atom+xml" title="RSS" href="/atom.xml">
<meta name="description">
<meta property="og:type" content="blog">
<meta property="og:title" content="Simplicity + Power = Elegance">
<meta property="og:url" content="http://fancymax.github.io/index.html">
<meta property="og:site_name" content="Simplicity + Power = Elegance">
<meta property="og:description">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Simplicity + Power = Elegance">
<meta name="twitter:description">
<meta property="og:image" content="http://7xpbra.com1.z0.glb.clouddn.com/me.jpeg"/>
<!--STYLES-->
<link rel="stylesheet" href="/assets/css/style.min.css" type="text/css">
<!--STYLES END-->
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-71671188-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</head>
<body>
<div id="blog">
<!-- Define author's picture -->
<header id="header" data-behavior="1">
<i id="btn-open-sidebar" class="fa fa-lg fa-bars"></i>
<h1 class="header-title">
<a class="header-title-link" href="/ ">Simplicity + Power = Elegance</a>
</h1>
<a class="header-right-picture "
href="/#about">
<img class="header-picture" src="http://7xpbra.com1.z0.glb.clouddn.com/me.jpeg"/>
</a>
</header>
<!-- Define author's picture -->
<nav id="sidebar" data-behavior="1">
<div class="sidebar-profile">
<a href="/#about">
<img class="sidebar-profile-picture" src="http://7xpbra.com1.z0.glb.clouddn.com/me.jpeg"/>
</a>
<span class="sidebar-profile-name">max Lin</span>
</div>
<ul class="sidebar-buttons">
<li class="sidebar-button">
<a class="sidebar-button-link "
href="/ "
>
<i class="sidebar-button-icon fa fa-lg fa-home"></i>
<span class="sidebar-button-desc">首页</span>
</a>
</li>
<li class="sidebar-button">
<a class="sidebar-button-link "
href="/all-tags"
>
<i class="sidebar-button-icon fa fa-lg fa-tags"></i>
<span class="sidebar-button-desc">标签</span>
</a>
</li>
<li class="sidebar-button">
<a class="sidebar-button-link "
href="/all-archives"
>
<i class="sidebar-button-icon fa fa-lg fa-archive"></i>
<span class="sidebar-button-desc">归档</span>
</a>
</li>
<li class="sidebar-button">
<a class="sidebar-button-link "
href="/#about"
>
<i class="sidebar-button-icon fa fa-lg fa-question"></i>
<span class="sidebar-button-desc">关于</span>
</a>
</li>
</ul>
<ul class="sidebar-buttons">
<li class="sidebar-button">
<a class="sidebar-button-link " href="https://github.com/fancymax" target="_blank">
<i class="sidebar-button-icon fa fa-lg fa-github"></i>
<span class="sidebar-button-desc">GitHub</span>
</a>
</li>
<li class="sidebar-button">
<a class="sidebar-button-link " href="mailto:lindahai0512@gmail.com" target="_blank">
<i class="sidebar-button-icon fa fa-lg fa-envelope-o"></i>
<span class="sidebar-button-desc">邮箱</span>
</a>
</li>
</ul>
<ul class="sidebar-buttons">
<li class="sidebar-button">
<a class="sidebar-button-link "
href="/atom.xml"
>
<i class="sidebar-button-icon fa fa-lg fa-rss"></i>
<span class="sidebar-button-desc">RSS</span>
</a>
</li>
</ul>
</nav>
<div id="main" data-behavior="1"
class="
hasCoverMetaIn
">
<section class="postShorten-group main-content-wrap">
<article class="postShorten postShorten--thumbnailimg-bottom" itemscope itemType="http://schema.org/BlogPosting">
<div class="postShorten-wrap">
<div class="postShorten-header">
<h1 class="postShorten-title" itemprop="headline">
<a class="link-unstyled" href="/2018/07/08/sellbook2018/">
搬家卖书
</a>
</h1>
<div class="postShorten-meta">
<time itemprop="datePublished" content="Sun Jul 08 2018 20:38:10 GMT+0800">
7月 08, 2018
</time>
</div>
</div>
<div class="postShorten-content" itemprop="articleBody">
<p>这两年买纸质书买的越来越少了。但是有些书没有电子版还是要买纸质的。</p>
<p>很多书买的时候都很兴奋,但是经常买回来看两眼就放弃了。趁着这次要搬家的机会,打算把珍藏多年的一些纸质书卖了,一方面让这些书继续发光发热,另一方面通过这个也能交到一些志同道合的朋友,也算是一举两得啦。</p>
<p>新书5折,旧书3折。相关书目如下,深圳面交,先到先得。</p>
<ul>
<li><p>mac相关</p>
<ul>
<li>Cocoa Programming for OS X (英文原版书,原价49.9 dolar)</li>
<li>AVFoundation开发秘籍(主要讲Mac/iOS平台音视频相关技术)</li>
<li>硅谷革命 (讲述Mac机的研发历史,之前在极客时间买的)</li>
</ul>
</li>
</ul>
<p><img src="http://7xpbra.com1.z0.glb.clouddn.com/2018070815310289329749.jpg" alt="test"></p>
<ul>
<li>网络相关</li>
</ul>
<p><img src="http://7xpbra.com1.z0.glb.clouddn.com/20180708153103095346687.jpg" alt=""></p>
<ul>
<li>测试相关</li>
</ul>
<p><img src="http://7xpbra.com1.z0.glb.clouddn.com/20180708153103126235703.jpg" alt=""></p>
<ul>
<li>深度学习&算法</li>
</ul>
<p><img src="http://7xpbra.com1.z0.glb.clouddn.com/20180708153103146911476.jpg" alt=""></p>
<ul>
<li>人文类</li>
</ul>
<p><img src="http://7xpbra.com1.z0.glb.clouddn.com/20180708153103165789492.jpg" alt=""></p>
<ul>
<li><p>UI设计</p>
<ul>
<li><p>about Face 最新英文原版 (原价50 dolar)<br><img src="http://7xpbra.com1.z0.glb.clouddn.com/20180708153103174856562.jpg" alt=""></p>
<p><img src="http://7xpbra.com1.z0.glb.clouddn.com/20180708153103180192471.jpg" alt=""></p>
</li>
</ul>
</li>
<li><p>其他杂类<br><img src="http://7xpbra.com1.z0.glb.clouddn.com/20180708153105335931689.jpg" alt=""></p>
</li>
</ul>
<p>
<a href="/2018/07/08/sellbook2018/#post-footer" class="postShorten-excerpt_link link">
注释和共享
</a>
</p>
</div>
</div>
</article>
<article class="postShorten postShorten--thumbnailimg-bottom" itemscope itemType="http://schema.org/BlogPosting">
<div class="postShorten-wrap">
<div class="postShorten-header">
<h1 class="postShorten-title" itemprop="headline">
<a class="link-unstyled" href="/2018/06/13/xpc/">
macOS 进程间通信方案探究
</a>
</h1>
<div class="postShorten-meta">
<time itemprop="datePublished" content="Wed Jun 13 2018 20:00:23 GMT+0800">
6月 13, 2018
</time>
</div>
</div>
<div class="postShorten-content" itemprop="articleBody">
<p>macOS上的进程间通信的方案很多,总的来说分为以下三种,如下图所示</p>
<p><img src="http://7xpbra.com1.z0.glb.clouddn.com/2017120715126509008638.jpg" alt="IPC"></p>
<ul>
<li>第一种也是历史最悠久的是基于POSIX的实现,其中常见的是基于Socket (TCP/IP)的实现。同时这种方案和平台无关。</li>
<li>第二种基于 底层信号机制,mac系统的内核组件就是使用mach_mag来传递消息。参考</li>
<li>第三种就是Foundation库里提供的一些类库。github上有个不错的范例实现基于CFMachPort—<a href="https://github.com/Syphon/Simple" target="_blank" rel="external">https://github.com/Syphon/Simple</a></li>
</ul>
<h2 id="XPC方式">XPC方式</h2><p>估计是苹果觉得这些方案,都不够好,要么不够易用,要么不够安全。所以在2011年的时候推出的全新的XPC进程间通信的方案。</p>
<blockquote>
<p>The XPC Services API provides a low-level (libSystem) interprocess communication mechanism based on serialized property lists.</p>
</blockquote>
<p>这是一套C的API,范例代码可以参考<a href="https://github.com/objcio/issue-14-xpc" target="_blank" rel="external">https://github.com/objcio/issue-14-xpc</a>。</p>
<p>但是这个技术有一个挺大的局限性,XPC进程只能是一个不带界面的控制台进程。关于 XPC 比较特别的尝试可以看看这个代码: <a href="https://github.com/OpenEmu/OpenEmuXPCCommunicator" target="_blank" rel="external">https://github.com/OpenEmu/OpenEmuXPCCommunicator</a>。</p>
<p>这可能一般的简单的程序架构是没问题的,但是有些特殊的要求,比如我就要求主程序和子程序都是界面程序 并且可以互相调用,这样子就完全搞不定了,由于XPC这套东西并不开源,所以要实现这种更灵活的互相调用,只能另辟蹊径。</p>
<p>为了实现这种两个界面程序,主程序和子程序可以互相调用的需求推荐就采取 Socket的方式实现,因为这种最标准的方式和操作系统无关,相比其他方案 不会因为 系统API被废弃而 变得不可用。</p>
<h2 id="Socket方式">Socket方式</h2><p>一个理想的调用关系大概是这样的:<br><img src="http://7xpbra.com1.z0.glb.clouddn.com/20180613152888929566604.png" alt=""></p>
<p>子程序放在主程序的程序包内。</p>
<p>当主程序需要调用子程序时 主程序先创建一个 TCP Server,并且把Server的地址和端口 作为参数 调起子程序(NSTask)。</p>
<p>子程序启动后,获取参数中的TCP地址和端口, 然后创建TCP Client进行连接。然后通过TCP的通信来互相调用。</p>
<p>至此 通信层的功能和大体框架基本确定了。</p>
<p>那应用层应该以何种方式调用呢?是否要定义一个特别的通信规约?</p>
<p>如果定义了通信规约,那每次别人要使用这套框架的时候还要先熟悉一下规约,这就显得不灵活 和 不易用了。有没有可能借用XPC的思路,直接以Objc消息传递的方式来互相调用呢?借助于Objective-C的runtime,这是可以实现的。我个人XPC的内部实现思路估计也是一样的。</p>
<p>先复习一下Objc的消息转发流程:</p>
<p><img src="http://7xpbra.com1.z0.glb.clouddn.com/20180613152888996391921.png" alt=""></p>
<p>注意最后一步 forwardInvocation 可以获取函数的所有调用参数。</p>
<blockquote>
<p>-(void)forwardInvocation:(NSInvocation *)anInvocation;</p>
</blockquote>
<p>假如 主程序利用 forwardInvocation 把需要调用的子程序的 函数信息 序列化 通过 Tcp 传递给子程序,子程序 反序列化后再根据函数信息调用对应功能。这就完成了一个互相调用的过程。</p>
<blockquote>
<p>实现对应功能的时候注意一下 NSInvocation 在Swift已经被废弃了,所以相关功能只能使用OC编写。</p>
<p>另外 NSInvocation 在ARC环境下跑的时候有个坑,可以参考一下 <a href="https://stackoverflow.com/questions/8811498/nsinvocation-and-arc-automatic-reference-counting" target="_blank" rel="external">nsinvocation-and-arc-automatic-reference-counting</a>。</p>
</blockquote>
<p>具体的代码就不放上来,大家有兴趣可以按照上述思路 实现一下看看,一方面可以熟悉一下TCP流程,另一方面也可以熟悉 OC的 转发流程,另外实现完这一套之后 也会 对苹果XPC内部实现有一定的了解。</p>
<p>参考链接:</p>
<ul>
<li><a href="https://www.objc.io/issues/14-mac/xpc/" target="_blank" rel="external">https://www.objc.io/issues/14-mac/xpc/</a></li>
<li><a href="https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/Introduction.html#//apple_ref/doc/uid/10000172i" target="_blank" rel="external">https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/Introduction.html#//apple_ref/doc/uid/10000172i</a></li>
<li><a href="https://www.mikeash.com/pyblog/friday-qa-2009-01-16.html" target="_blank" rel="external">https://www.mikeash.com/pyblog/friday-qa-2009-01-16.html</a></li>
<li><a href="https://www.mikeash.com/pyblog/friday-qa-2009-02-20-the-good-and-bad-of-distributed-objects.html" target="_blank" rel="external">https://www.mikeash.com/pyblog/friday-qa-2009-02-20-the-good-and-bad-of-distributed-objects.html</a></li>
</ul>
<p>
<a href="/2018/06/13/xpc/#post-footer" class="postShorten-excerpt_link link">
注释和共享
</a>
</p>
</div>
</div>
</article>
<article class="postShorten postShorten--thumbnailimg-bottom" itemscope itemType="http://schema.org/BlogPosting">
<div class="postShorten-wrap">
<div class="postShorten-header">
<h1 class="postShorten-title" itemprop="headline">
<a class="link-unstyled" href="/2018/05/20/mpv-1/">
mpv源码学习-1编译调试
</a>
</h1>
<div class="postShorten-meta">
<time itemprop="datePublished" content="Sun May 20 2018 17:36:05 GMT+0800">
5月 20, 2018
</time>
</div>
</div>
<div class="postShorten-content" itemprop="articleBody">
<p>最近对mpv的源码产生了兴趣,打算研究研究。</p>
<p><a href="https://github.com/mpv-player/mpv" target="_blank" rel="external">mpv</a>是属于一个大型的C语言的跨平台的播放器项目,其代码源自MPlayer和mplayer2,是一个历史非常悠久的项目了。</p>
<p>先来分析一下此项目大致存在的难点:</p>
<ul>
<li>针对跨平台的编译处理。</li>
<li>运用了C语言的奇淫巧技。</li>
<li>图形学的一些专业知识,主要是编解码、图形渲染等。</li>
</ul>
<p>先从github上下载了mpv的源码,然后进行编译,整个过程比较人性化。</p>
<h2 id="waf编译系统">waf编译系统</h2><p>mpv项目自己定制了一个叫<a href="https://github.com/waf-project/waf" target="_blank" rel="external">waf</a>的编译系统,至于为什么使用waf编译系统在他们的项目文档里有做介绍<a href="https://github.com/mpv-player/mpv/blob/master/DOCS/waf-buildsystem.rst" target="_blank" rel="external">why waf</a>,总结下来大致以下几点:</p>
<ul>
<li>编译脚本 在跨平台时比较保持一致的代码,并且尽量减少重复和胶水代码。</li>
<li>配置(configuration)步骤 和 编译(buid)步骤必须是分离的。</li>
<li>脚本比较好理解、好修改。(waf使用python编写)</li>
</ul>
<h2 id="源码编译">源码编译</h2><p>(本文的编译过程基于Mac)先从github上下载了mpv的源码。</p>
<p>第一步先跑个脚本下载最新的waf执行文件。</p>
<figure class="highlight stata"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./<span class="keyword">bootstrap</span>.py</span><br></pre></td></tr></table></figure>
<p>第二步跑一下waf的configuration步骤,这个过程会告诉你缺少哪些组件。</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./waf configure</span><br></pre></td></tr></table></figure>
<p>根据第二步的结果,对一些缺少的组件进行安装。</p>
<figure class="highlight armasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">brew </span>install libass</span><br><span class="line"><span class="keyword">brew </span>install ffmpeg</span><br></pre></td></tr></table></figure>
<p>然后 再跑一遍 ./waf configure 就成功生成了config.h,用于最后的编译过程。</p>
<p>在configure阶段可以使用下列选项,不进行代码优化,便于后续调试。</p>
<p><code>--disable-optimize</code></p>
<p>第三部 跑一下build步骤,这样就生成了最终的执行文件了。</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./waf</span><br></pre></td></tr></table></figure>
<p>最后一步 运行一下 安装步骤,就把mpv 放到了 /usr/local/bin/mpv 系统目录。</p>
<figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./waf <span class="keyword">install</span></span><br></pre></td></tr></table></figure>
<h2 id="代码调试">代码调试</h2><p>安装好mpv之后,就可以先尝试运行一下程序,可以正常的播放视频。</p>
<figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">mpv</span> xx/xx.mp4</span><br></pre></td></tr></table></figure>
<p>接下来尝试用lldb来调试一下代码。由于mpv在mac系统上运行,所以代码的入口在 xx/mpv/osdep/macosx_application.m 这个文件。所以先用lldb在这里设置一个断点。</p>
<figure class="highlight nimrod"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#Launch a process for with arguments a.out 1 2 3 without having to supply the args every time</span></span><br><span class="line">lldb -- mpv xx/xx.mp4</span><br><span class="line"></span><br><span class="line"><span class="comment">#enter lldb env then set breakpoint</span></span><br><span class="line">breakpoint <span class="type">set</span> --name cocoa_main</span><br><span class="line"></span><br><span class="line"><span class="comment">#star debug</span></span><br><span class="line">run</span><br><span class="line"></span><br><span class="line"><span class="comment">#运行结果</span></span><br><span class="line">* thread <span class="comment">#1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1</span></span><br><span class="line"> frame <span class="comment">#0: 0x00000001000d711b mpv`cocoa_main(argc=2, argv=0x00007ffeefbff940) at macosx_application.m:338 [opt]</span></span><br><span class="line"> <span class="number">335</span> </span><br><span class="line"> <span class="number">336</span> <span class="type">int</span> cocoa_main(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span><br><span class="line"> <span class="number">337</span> {</span><br><span class="line">-> <span class="number">338</span> @autoreleasepool {</span><br><span class="line"> <span class="number">339</span> application_instantiated = <span class="literal">true</span>;</span><br><span class="line"> <span class="number">340</span> [[<span class="type">EventsResponder</span> sharedInstance] setIsApplication:<span class="type">YES</span>];</span><br><span class="line"> <span class="number">341</span> </span><br><span class="line"><span class="type">Target</span> <span class="number">0</span>: (mpv) stopped.</span><br></pre></td></tr></table></figure>
<p>这之后 就可以一边看代码一边通过调试来理解代码行为了。</p>
<p>最后再补充几条常用lldb的命令,更多内容可以查看lldb的官方文档。</p>
<figure class="highlight livecodeserver"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Show the stack backtrace for the current thread</span></span><br><span class="line">thread backtrace</span><br><span class="line"></span><br><span class="line"><span class="comment"># List all breakpoints</span></span><br><span class="line"><span class="built_in">breakpoint</span> list</span><br><span class="line"></span><br><span class="line"><span class="comment"># Delete a breakpoint</span></span><br><span class="line"><span class="built_in">breakpoint</span> <span class="built_in">delete</span> <span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">breakpoint</span> disable/enable</span><br></pre></td></tr></table></figure>
<p>
<a href="/2018/05/20/mpv-1/#post-footer" class="postShorten-excerpt_link link">
注释和共享
</a>
</p>
</div>
</div>
</article>
<article class="postShorten postShorten--thumbnailimg-bottom" itemscope itemType="http://schema.org/BlogPosting">
<div class="postShorten-wrap">
<div class="postShorten-header">
<h1 class="postShorten-title" itemprop="headline">
<a class="link-unstyled" href="/2018/02/10/livePhoto/">
Live photo导入到Final Cut Pro
</a>
</h1>
<div class="postShorten-meta">
<time itemprop="datePublished" content="Sat Feb 10 2018 21:43:08 GMT+0800">
2月 10, 2018
</time>
</div>
</div>
<div class="postShorten-content" itemprop="articleBody">
<p>Live photo是近两年来我最喜欢的新特性之一(只能在iPhone 6S/SE上使用)。</p>
<p>它的功能简单来说就是拍一张照片时候 顺便 记录拍摄瞬间 1~2秒的视频。这就非常有用了,原因有这么几个:</p>
<ol>
<li>有人被拍的时候就喜欢眨眼,有了这个功能就可以在在视频的关键帧中挑一张不眨眼的靓照做为主图像。</li>
<li>一般拍照的时候 都是 精彩瞬间,所以5、6张live photo完全可以直接组合成一个有趣的视频,而静态图片是做不到的。</li>
</ol>
<h2 id="如何生成Live_Photo">如何生成Live Photo</h2><p>其实live photo内容构成很简单,就是 JPG + MOV,即一张高清大图,加上一段720P的视频。</p>
<p>所以如果你要自己制作一个live photo也很简单,<a href="https://github.com/mzp/LoveLiver" target="_blank" rel="external">LoveLiver</a> 这个开源软件可以选择一个视频,然后把其中一段转换成live photo,并导入到系统的Photos中。</p>
<p><img src="http://7xpbra.com1.z0.glb.clouddn.com/20180208151808958441671.jpg" alt="2"></p>
<h2 id="Live_Photo导入iMovie/Final_Cut_Pro">Live Photo导入iMovie/Final Cut Pro</h2><p>虽然Live Photo这么好,但是苹果原生的iMovie/Final Cut Pro却不支持直接导入,真是坑爹。</p>
<p>只能曲线救国,通过系统 原生应用 Image Capture.app(在 spotlight搜索 image即可找到) ,插入手机 连接线,然后选择对应的Live Photo,然后点击 Import,它会下载到指定的文件夹里。</p>
<p><img src="http://7xpbra.com1.z0.glb.clouddn.com/2018020815180905305502.jpg" alt="1"></p>
<p>最终Live Photo会导入成 JPG + MOV,还有可能有AAE文件(这是个XML文件,记录着这张照片的修改信息),然后你把对应MOV文件导入到iMovie/Final Cut Pro就完成任务啦。</p>
<p>
<a href="/2018/02/10/livePhoto/#post-footer" class="postShorten-excerpt_link link">
注释和共享
</a>
</p>
</div>
</div>
</article>
<article class="postShorten postShorten--thumbnailimg-bottom" itemscope itemType="http://schema.org/BlogPosting">
<div class="postShorten-wrap">
<div class="postShorten-header">
<h1 class="postShorten-title" itemprop="headline">
<a class="link-unstyled" href="/2018/01/10/imageCaptureCore/">
我是如何管理照片的
</a>
</h1>
<div class="postShorten-meta">
<time itemprop="datePublished" content="Wed Jan 10 2018 21:50:11 GMT+0800">
1月 10, 2018
</time>
</div>
</div>
<div class="postShorten-content" itemprop="articleBody">
<h2 id="I_Hired_Myself">I Hired Myself</h2><p><a href="http://atp.fm/episodes/259" target="_blank" rel="external">ATP 259期 I Hired Myself</a> 中Casey 提到他写了一个“Mac App”(其实是个命令行工具)用于管理他的照片流,让他有种I Hired Myself的感觉。我听了之后也很有感触,这也是我选择 当程序员的初衷。</p>
<h2 id="我的照片流">我的照片流</h2><p>今天谈一谈Mac上导入照片工作流,其中利用到了ImageCaptureCore框架,功能原理如下:</p>
<p><img src="http://7xpbra.com1.z0.glb.clouddn.com/20180203151764086753471.png" alt="1"></p>
<p>这个框架支持手机 以及 相机的SD卡,一般的应用场景是 </p>
<ul>
<li><p>插上相机的SD卡</p>
</li>
<li><p>电脑自动检测到且自动打开photos这个Mac上的原生图片工具</p>
</li>
<li><p>然后列出SD卡里的所有照片</p>
<p><br>之后,你可以选择 照片 导入到Mac里。photo里还提供了导入后删除的选项。</p>
</li>
</ul>
<p>不知道各位第一次使用的时候什么感想,反正我当时是被惊艳到了。</p>
<p>至此之后 我就喜欢上了这样的照片管理流程。</p>
<ul>
<li>我会定期把 相机 和 手机导入到 Mac里,然后把源文件删除。</li>
<li>然后定期 对 Mac 进行TimeMachine 备份,这样就同时把照片给备份了。</li>
<li>然后每次浏览 Mac的照片库的时候 都会标注 为 最喜爱 的照片。</li>
<li>同时把所有最喜欢的照片 添加到 iCloud photo Sharing里。这样我和女朋友都可以在photo的共享里找到这些照片。</li>
</ul>
<p>这样做有很多好处:</p>
<ul>
<li>还是使用 5G 的iCloud空间就够了。</li>
<li>可以定期清理 手机 中大量照片占用的空间。</li>
<li>照片进行了定期备份。</li>
<li>相机 和 手机的照片可以统一存放处理。</li>
<li>在任意设备 都可以获取 自己挑选的最喜欢的照片。</li>
</ul>
<h2 id="ImageCaptureCore">ImageCaptureCore</h2><p>下面再来说说ImageCaptureCore, 其实Photos里的那一系列功能都是 通过它实现的。<br>它的主要结构如下,基本上是提供了一个树形结构 方便 你来处理导入 照片。<br><img src="http://7xpbra.com1.z0.glb.clouddn.com/20180203151764241678581.png" alt="2"><br>大家有兴趣可以下载 苹果的范例程序<a href="https://developer.apple.com/library/content/samplecode/CameraBrowser/Introduction/Intro.html" target="_blank" rel="external">CameraBrowser</a>来研究一下。</p>
<p>
<a href="/2018/01/10/imageCaptureCore/#post-footer" class="postShorten-excerpt_link link">
注释和共享
</a>
</p>
</div>
</div>
</article>
<article class="postShorten postShorten--thumbnailimg-bottom" itemscope itemType="http://schema.org/BlogPosting">
<div class="postShorten-wrap">
<div class="postShorten-header">
<h1 class="postShorten-title" itemprop="headline">
<a class="link-unstyled" href="/2017/10/28/obs/">
开源直播工具OBS研究
</a>
</h1>
<div class="postShorten-meta">
<time itemprop="datePublished" content="Sat Oct 28 2017 19:03:17 GMT+0800">
10月 28, 2017
</time>
</div>
</div>
<div class="postShorten-content" itemprop="articleBody">
<h1 id="项目简介">项目简介</h1><p>OBS - Free and open source software for live streaming and screen recording(OBS是一款开源的用于录屏直播的工具软件)。</p>
<p>旧版的OBS只能支持Windows,目前已经停止开发。作者为了支持Windows/Mac/Linux重写了整个软件,项目地址为<a href="https://github.com/jp9000/obs-studio" target="_blank" rel="external">obs-studio in Github</a>。</p>
<p>新版的OBS的目标有以下几点:</p>
<ol>
<li>Make it multiplatform.(<strong>跨平台支持</strong>)</li>
<li>Separate the application from the core, allowing custom application of the core if desired, and easier extending of the user interface.(<strong>模块化、易扩展</strong>)</li>
<li>Simplify complex systems to not only make it easier to use, but easier to maintain.(<strong>简化系统,使其易用易维护</strong>)</li>
<li>Make a better/cleaner code base, use better coding standards, use standard libraries where possible (not just STL and C standard library, but also things like ffmpeg as well), and improve maintainability of the project as a whole.(<strong>尽量利用其他开源软件成果</strong>)</li>
<li>Implement a new API-independent shader/effect system allowing better and easier shaders usage and customization without having to duplicate shader code.(<strong>实现独立于API的shader/effect系统</strong>)</li>
<li>Better device support. (<strong>更好的支持有录屏需求的设备</strong>)</li>
</ol>
<p>OBS项目的语言分布:</p>
<ol>
<li>C: 57.6%</li>
<li>C++: 36.3%</li>
<li>Objective-C/Objective-C++: 4%</li>
<li>others: 3%</li>
</ol>
<p>OBS代码主要包含这些部分:</p>
<ol>
<li>libobs: 核心代码,定义项目框架以及核心API,主要用C语言编写。</li>
<li>UI: 界面代码,采用C++的QT框架,开发出适用三大平台的界面。</li>
<li>plugins: 插件代码,可独立编译成dll(windows平台)或so(*nix平台),包含Source(录屏输入源)、Output、Service(各种流播服务)等全部被定义为插件。</li>
<li>libobs-d3d11: 基于D3D的图形子系统,主要用在Windows系统。</li>
<li>libobs-opengl: 基于opengl的图形子系统,主要用在*uix系统。</li>
</ol>
<h2 id="OBS软件功能概述">OBS软件功能概述</h2><p>OBS项目工程中以场景组的方式呈现给用户,可以自由设置场景、输入源、效果处理,配置直播服务。<br><img src="http://7xpbra.com1.z0.glb.clouddn.com/2016121483032obs_ui.png" alt="2016121483032obs_ui.png"></p>
<h3 id="OBS项目工程结构">OBS项目工程结构</h3><p>OBS项目中一个工程结构如下<img src="http://7xpbra.com1.z0.glb.clouddn.com/201612147022obs_project_stucture.png" alt="201612147022obs_project_stucture.png"></p>
<p>一个场景组包含多个场景,OBS直播的时候是把整个场景流播给用户,那为什么需要多个场景?因为播主在直播时有快速切换场景的需要,所以播主需要在直播前编辑好多个场景(比如纯游戏场景;游戏+头像;解说;休息场景等),然后直播的过程中可以根据不同的需要快速切换。</p>
<h3 id="OBS场景的转场">OBS场景的转场</h3><p>OBS中的转场,是场景切换时的动画效果,目前支持 Fade和Switch等多种效果。</p>
<h3 id="OBS输入源的种类">OBS输入源的种类</h3><p>一个场景可以包含多个输入源,一个直播工具可以支持的输入源种类反应了其强大性。OBS支持 输入源种类如下<br><img src="http://7xpbra.com1.z0.glb.clouddn.com/2016121460496obs_source_type.png" alt="2016121460496obs_source_type.png"></p>
<h3 id="OBS输入源的效果设置">OBS输入源的效果设置</h3><p>针对每个输入源可以增加各种滤镜效果,以下列出我觉得最实用的几种:<br><img src="http://7xpbra.com1.z0.glb.clouddn.com/2016122283761OBS_Effect.png" alt="2016122283761OBS_Effect.png"></p>
<ol>
<li>音频效果:<ol>
<li>Video Delay: 设置延迟时间,用来处理音视频不同步的的场景。</li>
<li>Noise Suppression: 噪音抑制</li>
<li>Gain: 音频增益</li>
<li>Noise Gate: 噪声门,把小噪音去掉</li>
</ol>
</li>
<li>视频效果:<ol>
<li>Crop: 就是最实用的Crop,不过OBS里不能用鼠标拖拽来控制Crop区域,略显不便</li>
<li>Chroma Key: 如果有绿幕背景,可以用来去背景,在摄像头的输入源中最常用。</li>
<li>Image Mask: 打水印</li>
<li>Scroll: 滚动效果,在一些浏览器的输入源上最实用。</li>
</ol>
</li>
</ol>
<h3 id="OBS工作室模式">OBS工作室模式</h3><p><img src="http://7xpbra.com1.z0.glb.clouddn.com/2016122215478OBS_StdioMode.png" alt="2016122215478OBS_StdioMode.png"><br>左边是预览界面,可以进行编辑。右边是正在直播的界面。中间是把预览界面切到直播界面的各种转场效果。</p>
<p>一般比较专业的直播都是使用这个模式,可以在预览界面编辑好画面之后再推送到直播画面。</p>
<h2 id="OBS插件系统">OBS插件系统</h2><p>OBS项目中把除了核心框架以及渲染系统之外的 部件全部抽象成了Module,一个或多个Module最后封装到插件中(以dll或so的形式),只要把插件放入特定的目录即可被主程序使用。</p>
<p>Mac版OBS的插件目录在/Applications/OBS.app/Contents/Resources/obs-plugins,其中</p>
<ol>
<li>mac-avcapture.so 对应Mac的视频捕获设备</li>
<li>mac-capture.so 对应屏幕捕获 和 窗口捕获</li>
<li>mac-syphon.so 对应注入捕获游戏画面</li>
</ol>
<h3 id="OBS插件定义">OBS插件定义</h3><p>一个典型的OBS插件代码包含三个部分:</p>
<ol>
<li>插件定义 -> plugin-main.c</li>
<li>编译打包 -> CMakeList.txt</li>
<li>内部实现代码 -> XX.c/YY.c …</li>
</ol>
<p>下面以mac-capture.so插件为例来看看它的插件定义代码</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor">#<span class="keyword">include</span> <obs-module.h></span></span><br><span class="line"></span><br><span class="line">OBS_DECLARE_MODULE()</span><br><span class="line">OBS_MODULE_USE_DEFAULT_LOCALE(<span class="string">"mac-capture"</span>, <span class="string">"en-US"</span>) <span class="comment">//多语言支持</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">extern</span> <span class="keyword">struct</span> obs_source_info coreaudio_input_capture_info; <span class="comment">//输入源1</span></span><br><span class="line"><span class="keyword">extern</span> <span class="keyword">struct</span> obs_source_info coreaudio_output_capture_info;<span class="comment">//输入源2</span></span><br><span class="line"><span class="keyword">extern</span> <span class="keyword">struct</span> obs_source_info display_capture_info;<span class="comment">//输入源3</span></span><br><span class="line"><span class="keyword">extern</span> <span class="keyword">struct</span> obs_source_info window_capture_info;<span class="comment">//输入源4</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">obs_module_load</span><span class="params">(<span class="keyword">void</span>)</span> <span class="comment">//注册支持的输入源</span></span><br><span class="line"></span>{</span><br><span class="line"> obs_register_source(&coreaudio_input_capture_info);</span><br><span class="line"> obs_register_source(&coreaudio_output_capture_info);</span><br><span class="line"> obs_register_source(&display_capture_info);</span><br><span class="line"> obs_register_source(&window_capture_info);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>可以看出这个插件定义了四个输入源,这里你可能有个疑问,为什么不是一个插件 对应 一个输入源,因为功能相近的输入源集成到一个插件里可以减少冗余代码。</p>
<p>所以OBS中插件可以定义为 包含 一个 或 多个 输入(或 输出/编码/服务)模块的动态库代码。<br><img src="http://7xpbra.com1.z0.glb.clouddn.com/2016121549583obs_module_define.png" alt="2016121549583obs_module_define.png"><br>任意一个开源库比如FFmpeg,经过OBS统一的接口定义封装即可编译成OBS的一个插件为OBS系统所用。</p>
<h3 id="OBS插件加载流程">OBS插件加载流程</h3><p>插件系统大体都有一个类似的套路,OBS的也不例外。简单来说就是定义插件存放在特定目录,在程序启动时,动态加载所有的插件(存储为对象或一系列函数指针),存储在字典 或者 链表这样的数据结构里。</p>
<p>下面来详细分析一下OBS中插件加载流程:<br><img src="http://7xpbra.com1.z0.glb.clouddn.com/2016121573630OBS_import_module-2.png" alt="2016121573630OBS_import_module-2.png"></p>
<p>然后以mac-capture.so中的display_capture_info,来看看它的结构定义,可以看出它主要定义了id、type、name 以及一些接口API。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> obs_source_info display_capture_info = {</span><br><span class="line"> .id = <span class="string">"display_capture"</span>,</span><br><span class="line"> .type = OBS_SOURCE_TYPE_INPUT,</span><br><span class="line"> .get_name = display_capture_getname,</span><br><span class="line"></span><br><span class="line"> .create = display_capture_create,</span><br><span class="line"> .destroy = display_capture_destroy,</span><br><span class="line"></span><br><span class="line"> .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW |</span><br><span class="line"> OBS_SOURCE_DO_NOT_DUPLICATE,</span><br><span class="line"> .<span class="keyword">video_t</span>ick = <span class="keyword">display_capture_video_t</span>ick,</span><br><span class="line"> .video_render = display_capture_video_render,</span><br><span class="line"></span><br><span class="line"> .get_width = display_capture_getwidth,</span><br><span class="line"> .get_height = display_capture_getheight,</span><br><span class="line"></span><br><span class="line"> .get_defaults = display_capture_defaults,</span><br><span class="line"> .get_properties = display_capture_properties,</span><br><span class="line"> .update = display_capture_update,</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>其中需要注意一下get_properties这个接口,这个接口是干啥用的?顾名思义是获取模块的属性数据,按我的理解 UI层可以利用这个属性数据来构建这个模块对应的界面,并设置这个模块的属性参数。</p>
<h3 id="OBS视频处理流程">OBS视频处理流程</h3><h4 id="视频渲染输出流程">视频渲染输出流程</h4><p>OBS视频渲染和输出是系统的核心流程,我们以Mac桌面录制输入,以及ffmpeg输出为例来分析一下整个流程(多路输入 和 多路输出道理也是类似的), 图中为了简单起见忽略了输出编码流程仅包含非编码流程。<br><img src="http://7xpbra.com1.z0.glb.clouddn.com/2016122060378OBS_Video_Render_2.png" alt="2016122060378OBS_Video_Render_2.png"><br>可以看出OBS创建了两个线程,一个用于显示渲染,另一个用于编码输出。<br>渲染部分最终调用的是 输入模块里的渲染代码,而编码输出部分最终也是调用 输出模块的代码。</p>
<p><strong>另外在渲染线程中 也负责把图形系统的数据 拷贝到 输出数据的缓存中,以便于输出线程进行处理</strong>。</p>
<h4 id="视频输出数据结构分析">视频输出数据结构分析</h4><p>OBS的核心数据结构定义在libobs/obs-internal.h中 主体结构为obs_core如下图所示(仅保留的主要的数据结构)<br><img src="http://7xpbra.com1.z0.glb.clouddn.com/2016121631085OBS_CoreVideo-2.jpg" alt="2016121631085OBS_CoreVideo-2.jpg"></p>
<p>右下方的video(结构为video_output)用在输出模块的raw_video接口进行处理,把 输出数据中的cache转成实际的输出,以下是video_output详细数据结构:<br><img src="http://7xpbra.com1.z0.glb.clouddn.com/2016121665597OBS_Video_detail_2.jpg" alt="2016121665597OBS_Video_detail_2.jpg"></p>
<h3 id="OBS音频处理流程">OBS音频处理流程</h3><p>OBS音频处理是在一个线程中完成了先渲染后输出的过程。而视频处理则是 分别开了渲染线程 和 输出线程。</p>
<p>具体流程如下, 在输出函数中在判断是否需要编码,再调用对应的非编码流程 或 编码流程:<br><img src="http://7xpbra.com1.z0.glb.clouddn.com/2016122068454OBS_Audio_Render.png" alt="2016122068454OBS_Audio_Render.png"></p>
<h1 id="OBS图形系统架构">OBS图形系统架构</h1><p>OBS的图形系统主要负责 场景的渲染、场景的切换、以及各种输入源的音视频效果的处理,属于OBS的核心之一。<br>通过使用软件以及视频渲染流程的分析 得到OBS图形系统的大体的逻辑关系。</p>
<p><img src="http://7xpbra.com1.z0.glb.clouddn.com/2016122324058OBS_Graphic_Structure_1.png" alt="2016122324058OBS_Graphic_Structure_1.png"></p>
<p>针对图形系统主要分析以下三个问题:</p>
<ol>
<li>多滤镜叠加的渲染处理。</li>
<li>滤镜和转场效果的实现与集成。</li>
<li>图形API的封装。</li>
</ol>
<h2 id="单个场景的渲染流程">单个场景的渲染流程</h2><p>场景(Scene)也被封装成输入源(Source)的一种,所以UI层只要把当前的场景取出来,调用它的obs_source_video_render即可。</p>
<p><img src="http://7xqmjb.com1.z0.glb.clouddn.com/2017011791095OBS_sceneRender.png" alt="2017011791095OBS_sceneRender.png"></p>
<p>在场景内部会渲染其包含的renderitem(也就是实际的输入源),比如前一个图所展示的游戏录制、桌面录制输入源等。</p>
<h2 id="多个滤镜叠加的输入源渲染流程">多个滤镜叠加的输入源渲染流程</h2><p>这部分分析了很长时间一直没看懂,主要有两个原因:</p>
<ol>
<li>之前不熟悉OpenGl的渲染流程,所以搞不懂滤镜的渲染流程。</li>
<li>这部分的逻辑比较绕,没分析出多个滤镜是怎么叠加渲染的。</li>
</ol>
<p>前段时间花了点时间好好学习了一下OpenGl(仅仅学习和音视频处理相关的章节),写了一些<a href="https://github.com/fancymax/LearnOpenGL_Mac" target="_blank" rel="external">demo</a>。</p>
<p>现在再来分析这部分相对轻松一些,简述一下:当渲染带滤镜的输入源时,会先渲染它最后一个滤镜,然后在这个滤镜的渲染代码又会调用渲染前一个滤镜,最后调用第一个滤镜的渲染代码。</p>
<p>在第一个滤镜的渲染代码里 直接渲染 调用输入源的渲染流程,然后生成texture。</p>
<p>每个滤镜都在前一个滤镜渲染生成的texture的基础渲染生成新的texture。</p>
<p>流程图如下:<br><img src="http://7xqmjb.com1.z0.glb.clouddn.com/2017011799373OBS_filterRender.png" alt="2017011799373OBS_filterRender.png"></p>
<h2 id="滤镜和转场效果的实现与集成">滤镜和转场效果的实现与集成</h2><p>OBS项目中,滤镜和转场效果都被抽象成插件。</p>
<p>以Mac版OBS为例:</p>
<ol>
<li>所有的滤镜都在obs-filters.so这个插件里;</li>
<li>所有的转场效果都在obs-transitions.so里;</li>
</ol>
<p>滤镜和转场其实分析起来是类似的,所以后续的暂时以滤镜为例来加以说明。</p>
<p>首先如果滤镜个数太多,拆分到两个插件里是没问题的。不过OBS项目中全部集中在一个插件里。</p>
<p>每个滤镜其实都被定义成了输入模块,以obs_source_info定义暴露API,以crop_filter为例见如下定义,唯一和普通输入模块不同的是类型定义(.type)。 </p>
<figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">struct obs_source_info <span class="variable">crop_filter =</span> {</span><br><span class="line"> .<span class="variable">id =</span> <span class="string">"crop_filter"</span>,</span><br><span class="line"> .<span class="variable">type =</span> OBS_SOURCE_TYPE_FILTER,</span><br><span class="line"> .<span class="variable">output_flags =</span> OBS_SOURCE_VIDEO,</span><br><span class="line"> .<span class="variable">get_name =</span> crop_filter_get_name,</span><br><span class="line"> .<span class="variable">create =</span> crop_filter_create,</span><br><span class="line"> .<span class="variable">destroy =</span> crop_filter_destroy,</span><br><span class="line"> .<span class="variable">update =</span> crop_filter_update,</span><br><span class="line"> .<span class="variable">get_properties =</span> crop_filter_properties,</span><br><span class="line"> .<span class="variable">get_defaults =</span> crop_filter_defaults,</span><br><span class="line"> .<span class="variable">video_tick =</span> crop_filter_tick,</span><br><span class="line"> .<span class="variable">video_render =</span> crop_filter_render,</span><br><span class="line"> .<span class="variable">get_width =</span> crop_filter_width,</span><br><span class="line"> .<span class="variable">get_height =</span> crop_filter_height</span><br><span class="line">};</span><br></pre></td></tr></table></figure>
<p>细心观察每个滤镜的代码组成发现都是一个套路,主要由两部分组成:</p>
<ol>
<li>XXX.c (C代码,用于暴露API以及例行处理)</li>
<li>XXX.effect (自定义文件,实际效果处理逻辑)</li>
</ol>
<p>关于这个effect留到后续讲解。</p>
<p>看到这里得出一个结论,OBS项目要增加新的滤镜效果只要编写对应的XXX.c 和 XXX.effect,放到对应的插件以输入模块的API暴露出来,并注册就可以了。</p>
<h2 id="图形API的封装处理">图形API的封装处理</h2><p>目前OBS系统的图形API包括OpenGl以及d3d11:</p>
<ol>
<li>在图形库加载层利用了<a href="https://github.com/Dav1dde/glad" target="_blank" rel="external">Multi-Language GL/GLES/EGL/GLX/WGL Loader-Generator</a>对不同平台加载图形库代码进行了封装。</li>
<li>在API调用层面也进行了抽象统一,具体可以查看 libobs/graphics/graphics-imports.c的定义。</li>
<li>自定义了效果描述文件 XXX.effect,这样就不用针对OpenGl和d3d11写两遍Shader。</li>
</ol>
<p>我们以chroma-key-filter为例分析一下它的创建流程:<br><img src="http://7xpbra.com1.z0.glb.clouddn.com/2016122616450OBS_filter_create.png" alt="2016122616450OBS_filter_create.png"></p>
<ol>
<li>其中AddNewFilter是在界面中触发的添加效果的功能。</li>
<li>ep_parse把xxx.effect配置文件解析成对应的配置结构。</li>
<li>ep_compile把对应的配置结构解析 效果数据结构。</li>
</ol>
<p>effect文件的作用可以参考程序中的注释</p>
<figure class="highlight applescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line"> * Effects introduce a means <span class="keyword">of</span> bundling together shader <span class="type">text</span> <span class="keyword">into</span> one</span><br><span class="line"> * <span class="type">file</span> <span class="keyword">with</span> shared functions <span class="keyword">and</span> parameters. This <span class="keyword">is</span> done because often</span><br><span class="line"> * shaders must be duplicated when you need <span class="keyword">to</span> alter minor aspects <span class="keyword">of</span> <span class="keyword">the</span> code</span><br><span class="line"> * <span class="keyword">that</span> cannot be done via constants. Effects allow developers <span class="keyword">to</span> easily</span><br><span class="line"> * switch shaders <span class="keyword">and</span> <span class="keyword">set</span> constants <span class="keyword">that</span> can be used <span class="keyword">between</span> shaders.</span><br><span class="line"> *</span><br><span class="line"> * Effects are built via <span class="keyword">the</span> effect parser, <span class="keyword">and</span> shaders are automatically</span><br><span class="line"> * generated <span class="keyword">for</span> each technique's pass.</span><br><span class="line"> */</span><br></pre></td></tr></table></figure>
<p>effect文件包括这几个部分:<br><img src="http://7xpbra.com1.z0.glb.clouddn.com/2016122616242effect_file_structure.png" alt="2016122616242effect_file_structure.png"></p>
<p>pass对应 vertex_shader 和 pixel_shader</p>
<p>technique 对应一个具体效果的渲染设置,包含多个pass</p>
<p>effect文件包含多个technique渲染设置、可以共享文件中的参数和函数。</p>
<figure class="highlight ceylon"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">SolidVertInOut VSSolid(SolidVertInOut vert<span class="number">_</span><span class="keyword">in</span>)</span><br><span class="line">{</span><br><span class="line"> SolidVertInOut vert<span class="number">_</span><span class="keyword">out</span>;</span><br><span class="line"> vert<span class="number">_</span><span class="keyword">out</span>.pos = mul(float<span class="number">4</span>(vert<span class="number">_</span><span class="keyword">in</span>.pos.xyz, <span class="number">1.0</span>), ViewProj);</span><br><span class="line"> <span class="keyword">return</span> vert<span class="number">_</span><span class="keyword">out</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">float<span class="number">4</span> PSSolid(SolidVertInOut vert<span class="number">_</span><span class="keyword">in</span>) : TARGET</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">return</span> color;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">technique Solid</span><br><span class="line">{</span><br><span class="line"> pass</span><br><span class="line"> {</span><br><span class="line"> vertex<span class="number">_</span>shader = VSSolid(vert<span class="number">_</span><span class="keyword">in</span>);</span><br><span class="line"> pixel<span class="number">_</span>shader = PSSolid(vert<span class="number">_</span><span class="keyword">in</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>effect文件支持基本的C语法,支持宏定义和include包含其他文件,由libobs/util中的cf-lexer.c和cf-parser.c提供解析支持。</p>
<h1 id="支持OBS插件">支持OBS插件</h1><p>OBS的插件是在OBS项目定义的比较宽泛,插件的范畴包括 整个录屏、处理、推流 中的各个功能模块.</p>
<p>如果我们的软件中可以直接支持OBS插件,就可以节省大量的开发、测试的时间。但由于我们的程序框架和OBS的完全不同,要如何支持OBS项目的插件呢?</p>
<p>想了两种方法,并尝试分析一下优缺点。</p>
<h2 id="支持OBS的方法分析">支持OBS的方法分析</h2><h3 id="二进制级别的支持">二进制级别的支持</h3><p>顾名思义,就是把OBS的插件直接放到我们程序的相应目录就可以用。这种方式下维护、更新、新增 OBS插件 代价是最小的。也是我心中理想的支持方式。</p>
<p>但是以这种方式支持的遇到较大困难。先看OBS项目中的代码的各个模块:</p>
<ol>
<li>libobs: 核心代码,定义项目框架以及核心API,主要用C语言编写。</li>
<li>UI: 界面代码,采用C++的QT框架,开发出适用三大平台的界面。</li>
<li>plugins: 插件代码,可独立编译成dll(windows平台)或so(*nix平台),包含Source(录屏输入源)、Output、Service(各种流播服务)等全部被定义为插件。</li>
<li>libobs-d3d11: 基于D3D的图形子系统,主要用在Windows系统。</li>
<li>libobs-opengl: 基于opengl的图形子系统,主要用在*uix系统。</li>
</ol>
<p>我们想要支持plugins中的插件,</p>
<p>但是plugins中的插件要依赖libobs,</p>
<p>而libobs又要依赖libobs-opengl 和 QT界面库。</p>
<p>也就是除非 我们的项目支持基于OBS项目改写,否则这种支持方式的不太现实。</p>
<h3 id="代码级别的支持">代码级别的支持</h3><p>这是退而求其次的方式,简单的说就是把OBS的插件代码扣出来,确保其不依赖于libobs,然后集成我们的项目中。这种方式每支持一个插件都存在集成的工作量,也可能会引入Bug,不过不失为一个较为可行的方案。</p>
<h3 id="OBS项目编译">OBS项目编译</h3><p>尝试了在Mac平台上编译OBS项目,还比较顺利。具体可以参考<a href="https://github.com/jp9000/obs-studio/wiki/Install-Instructions#mac-osx" target="_blank" rel="external">install help</a></p>
<p>有个小问题,在cmake后报错提示无法找到QT5的cmake模块。需要给cmake指定一下QT5的安装目录,以我的安装目录为例,命令如下:<br><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cmake .. -DCMAKE_PREFIX_PATH=<span class="regexp">/usr/</span>local<span class="regexp">/Cellar/</span>qt5<span class="regexp">/5.3.1/</span></span><br></pre></td></tr></table></figure></p>
<p>cmake命令我是不熟悉的,不过看了<a href="http://hahack.com/codes/cmake/" target="_blank" rel="external">这篇文章</a>也基本懂了。</p>
<p>OBS项目最后的编译结果如下:</p>
<p><img src="http://7xqmjb.com1.z0.glb.clouddn.com/201702058452OBS_Compile.png" alt="201702058452OBS_Compile.png"></p>
<p>主要有三个目录:</p>
<ol>
<li>bin: 主程序</li>
<li>data: 国际化资源 以及 视频effect效果资源</li>
<li>obs-plugin: 插件编译结果</li>
</ol>
<p>使用otool -L分析中其中主要动态库和插件(仅以mac-capture为例)的依赖关系如下:</p>
<p><img src="http://7xqmjb.com1.z0.glb.clouddn.com/2017020524659OBS_dependency.png" alt="2017020524659OBS_dependency.png"></p>
<p>前面在代码层面分析直接二进制支持OBS插件感觉很困难。但这里基于编译后的动态库依赖关系分析好像又有一定可行性。我们把mac-capture.so、libobs.0.dylib、ffmpeg独立出来,去掉OBS主程序和QT等库,自己写代码来调用libobs.0.dylib提供的功能,以此直接支持OBS的插件。</p>
<p>后续进行完相关的实验,看看到底是否可行,再来补充。</p>
<h3 id="模块列表">模块列表</h3><p>OBS项目中类型为OBS_SOURCE_TYPE_INPUT是我们可以考虑优先支持的模块。以下是功能说明。</p>
<table>
<thead>
<tr>
<th style="text-align:center">插件</th>
<th style="text-align:center">子模块</th>
<th style="text-align:center">功能描述</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">mac-capture</td>
<td style="text-align:center">coreaudio_input_capture</td>
<td style="text-align:center">音频输入获取</td>
</tr>
<tr>
<td style="text-align:center"></td>
<td style="text-align:center">coreaudio_output_capture</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center"></td>
<td style="text-align:center">display_capture</td>
<td style="text-align:center">桌面获取</td>
</tr>
<tr>
<td style="text-align:center"></td>
<td style="text-align:center">window_capture</td>
<td style="text-align:center">窗体获取</td>
</tr>
<tr>
<td style="text-align:center">mac-avcapture</td>
<td style="text-align:center">av_capture</td>
<td style="text-align:center">摄像头获取</td>
</tr>
<tr>
<td style="text-align:center">mac-syphon</td>
<td style="text-align:center">syphon</td>
<td style="text-align:center">程序注入获取界面</td>
</tr>
<tr>
<td style="text-align:center">obs-ffmpeg</td>
<td style="text-align:center">ffmpeg_source</td>
<td style="text-align:center">ffmpeg输入源</td>
</tr>
<tr>
<td style="text-align:center">obs-browser</td>
<td style="text-align:center">browser_source</td>
<td style="text-align:center">浏览器输入</td>
</tr>
<tr>
<td style="text-align:center">text-freetype2</td>
<td style="text-align:center">freetype2_source</td>
<td style="text-align:center">text输入</td>
</tr>
<tr>
<td style="text-align:center">decklink</td>
<td style="text-align:center">decklink-input</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">image-source</td>
<td style="text-align:center">image_source</td>
<td style="text-align:center">图片输入</td>
</tr>
<tr>
<td style="text-align:center"></td>
<td style="text-align:center">slideshow</td>
<td style="text-align:center"></td>
</tr>
<tr>
<td style="text-align:center">vlc-video</td>
<td style="text-align:center">vlc_source</td>
<td style="text-align:center">vlc输入</td>
</tr>
</tbody>
</table>
<h1 id="mac-syphon插件分析">mac-syphon插件分析</h1><p>mac-syphon是OBS项目中用于获取游戏画面(仅用于mac平台)的插件,十分重要。下面来分析一下它的实现原理。</p>
<p>首先mac-syphon是OBS的输入源插件,所以遵循OBS的插件API设定,具体可以查看OBS插件分析章节的介绍。<br>mac-syphon内部其实组合了多个开源项目功能来完成:获取游戏画面,并展示到OBS界面上的功能。</p>
<p>我们以OBS获取MineCraft这个游戏的画面为例,来看一张总的实现原理图:<br><img src="http://7xpbra.com1.z0.glb.clouddn.com/2017010524004OBS_SyphonInject.png" alt="2017010524004OBS_SyphonInject.png"></p>
<p>简单解释一下这个过程:</p>
<p><img src="http://7xpbra.com1.z0.glb.clouddn.com/2017010568862OBS_SyphonInject_flow.png" alt="2017010568862OBS_SyphonInject_flow.png"></p>
<p>注入游戏进程的方法这里用的是Scripting Additions的方式,这是macOS独有的技术,windows上肯定要用其他方式,到时候再单独研究。除了注入方式的区别,其他流程Win和Mac平台应该类似的。</p>
<p>另外由于注入的函数中替换的是OpenGL的渲染API,所以这个插件支持的游戏必然是使用OpenGL渲染的。假如某个程序或游戏不使用OpenGL则无法注入。</p>
<h2 id="ScriptingAdditions">ScriptingAdditions</h2><p>ScriptingAdditions就是macOS中Applescript中一个技术,不太好解释,反正它的作用就是帮助注入到游戏进程里,直接上两个文档:</p>
<ol>
<li><a href="https://developer.apple.com/library/content/technotes/tn1164/_index.html" target="_blank" rel="external">Scripting Additions for Mac OS X</a></li>
<li><a href="https://developer.apple.com/library/content/documentation/AppleScript/Conceptual/AppleScriptX/Concepts/osa.html" target="_blank" rel="external">Open Scripting Architecture</a></li>
</ol>
<h2 id="mach_override">mach_override</h2><p>这里其实利用了两个项目<a href="https://github.com/rentzsch/jrswizzle" target="_blank" rel="external">jrswizzle</a>和<a href="https://github.com/rentzsch/mach_override" target="_blank" rel="external">mach_override</a>,功能就是把游戏进程中的flushBuffer替换为自己写的flushBufferSyphon,把orig_CGLFlushDrawable替换为CGLFlushDrawableOverride,从而实现把自己写的功能注入到游戏的渲染API中。</p>
<h2 id="Syphon">Syphon</h2><p><a href="https://github.com/Syphon" target="_blank" rel="external">Syphon</a>项目是一套传输图形画面的Client/Server框架。</p>
<p>项目还提供了<a href="https://github.com/Syphon/Simple" target="_blank" rel="external">Client/Server Demo</a>可以很方便的测试画面传输的功能。</p>
<h2 id="Syphon_Inject">Syphon Inject</h2><p><a href="https://github.com/zakk4223/SyphonInject" target="_blank" rel="external">SyphonInject</a>项目组合Syphon的功能以及注入游戏的功能,提供了一个Demo。</p>
<p>下图中我用Syphon Inject注入到Dota2游戏,然后把界面传送给<a href="https://github.com/Syphon/Simple" target="_blank" rel="external">Client Demo</a>。</p>
<p><img src="http://7xpbra.com1.z0.glb.clouddn.com/2017010588377SyphonInject_1.png" alt="2017010588377SyphonInject_1.png"></p>
<p><img src="http://7xpbra.com1.z0.glb.clouddn.com/2017010548405SyphonInject_2.png" alt="2017010548405SyphonInject_2.png"></p>
<p><strong>SyphonInject编译注意事项</strong>: SyphonInject项目早期利用mach_inject项目进行注入,后期已经修改为ScriptingAdditions方式注入。最新的代码没有依赖mach_inject,所以可以把mach_inject项目依赖去掉再编译。</p>
<p>最后OBS项目其实上述项目组合,用插件的API包装成了mac-Syphon插件。</p>
<h1 id="mac-capture插件分析">mac-capture插件分析</h1><p>mac-capture插件是OBS项目中对应mac平台的 屏幕界面获取、窗口界面获取、输入音频获取、输出音频获取 四大模块的具体实现。</p>
<p>obs模块的具体结构和API不再列出,详细情况可查看之前的文档,这里着重分析模块内部的功能实现。</p>
<h2 id="mac-display模块">mac-display模块</h2><p>主要利用<a href="https://developer.apple.com/reference/coregraphics/1655858-quartz_display_services" target="_blank" rel="external">Quartz Display Services</a>获取界面图像数据转换成texuture提供给主程序。</p>
<p>其中调用CGDisplayStreamCreateWithDispatchQueue接口获取的显示界面图像数据,数据结构是IOSurfaceRef。具体用法可以查看上述Quartz Display Services链接文档。</p>
<p>主要的实现流程如下图:<br><img src="http://7xqmjb.com1.z0.glb.clouddn.com/2017011827400OBS_macDisplay.png" alt="2017011827400OBS_macDisplay.png"></p>
<h2 id="mac-window-capture模块">mac-window-capture模块</h2><p>获取其他程序窗体界面的模块,也是利用了Quartz Display Services。</p>
<p>使用CGWindowListCreateImage接口,通过windowID把对应程序的界面以 CGImage 的形式返回,直接放到输出的Cache里。</p>
<p>这里有个疑问,模块没有实现.video_render这个渲染API。可能和模块的output_flags设置的是异步有关。<br>OBS_SOURCE_ASYNC_VIDEO异步的渲染流程可能未放到模块内部实现。</p>
<h2 id="mac-audio模块">mac-audio模块</h2><p>音频模块包括两个,音频输入模块 和 音频输出模块。</p>
<p>音频输入设备比如 iMac上自带的外置麦克风;音频输出模块 比如 插入的耳机等。</p>
<p>由于mac平台限制,无法直接录制 音频输出设备的声音。比如我要录制浏览器上youtube视频的声音,在不借助第三方程序的情况下是做不到。使用OBS也做不到。</p>
<p>不过利用第三方开源程序soundflower可以解决这个问题。在安装soundflower之后,OBS可以设置 音频输出捕获模块,设备选择soundflower。同时系统声音输出设备 也选择为soundflower。<br><img src="http://7xqmjb.com1.z0.glb.clouddn.com/2017011845343OBS_audioOutput.png" alt="2017011845343OBS_audioOutput.png"><br><img src="http://7xqmjb.com1.z0.glb.clouddn.com/2017011866291OBS_audioOutput1.png" alt="2017011866291OBS_audioOutput1.png"></p>
<p>这样系统的任何程序发出的声音就会先经过soundflower,然后被OBS捕获。</p>
<p>mac-audio中的音频输入模块 和 输出模块 实现流程基本相同,如下所示:<br><img src="http://7xqmjb.com1.z0.glb.clouddn.com/2017011826851obs_macAudio.png" alt="2017011826851obs_macAudio.png"></p>
<p>基本原理是 在初始化模块的时候 使用系统接口AudioObjectAddPropertyListener注册音频的回调函数。然后在回调函数中完成音频渲染以及输出音频数据缓存。</p>
<p>
<a href="/2017/10/28/obs/#post-footer" class="postShorten-excerpt_link link">
注释和共享
</a>
</p>
</div>
</div>
</article>
<article class="postShorten postShorten--thumbnailimg-bottom" itemscope itemType="http://schema.org/BlogPosting">
<div class="postShorten-wrap">
<div class="postShorten-header">
<h1 class="postShorten-title" itemprop="headline">
<a class="link-unstyled" href="/2017/07/10/relation/">
亲密关系
</a>
</h1>
<div class="postShorten-meta">
<time itemprop="datePublished" content="Mon Jul 10 2017 22:01:31 GMT+0800">
7月 10, 2017
</time>
</div>
</div>
<div class="postShorten-content" itemprop="articleBody">
<h2 id="吸引力">吸引力</h2><ul>
<li><p>要进一步探讨的微妙之处是,某些相像或许十分重要,而其他的相像 (或者相异) 可能无关痛痒。尤其是,如果伴侣在一些重要的议题上能和我们看法一致,这将特别具有奖赏价值。宗教就是这样一个议题:如果伴侣双方在宗教上都非常虔诚,共同的宗教信仰就非常有满足意义;而如果双方都不是积极的信徒,宗教上的相像就没什么影响,甚至有分歧也不打紧 (Lutz-Zois et al., 2006)。</p>
<ul>
<li>两个人三观要相同</li>
</ul>
</li>
<li><p>“相异”还能相吸的一种方式:互朴性</p>
<ul>
<li>一个愿打一个愿挨</li>
</ul>
</li>
</ul>
<h2 id="社会认知">社会认知</h2><ul>
<li><p>另外来看,如果人们了解伴侣各方面的情况,但却能以一种善意、大度的方式来进行诠释,这样的“错觉”就对亲密关系十分有益。</p>
</li>
<li><p>此外,保护自己免遭幻想破灭的聪明方法是:随着对伴侣了解程度的增加,不断调整自己对理想伴侣的期望,这样,对伴侣的期望标准就能切合伴侣的现状 (Fletcher et al., 2000a)。</p>
</li>
<li><p>人们常常能深切地感受到影响自己行为的外部压力,因而对自己行为的解释容易作出外部归因。但他们注意不到同样的环境也会影响他人,从而在解释他人的行为时,常常归因于他们的内部原因,如意图或性格。</p>
</li>
<li><p>即使苦闷的夫妻彼此示好,但双方都会认为对方的体贴只不过是消极常态中短暂的、不具代表性的片刻安宁。当善意被看做是偶然的,伤害被看做蓄意的,亲密关系就很难得到满足。</p>
</li>
<li><p>不良的归因方式会引起更多的纠纷和降低解决问题的效率,从而导致了那些本可避免的失落和不满。</p>
</li>
<li><p>不过,在相互依赖更多、更投入的人际关系 (如婚姻) 中,自我证实的动机居于主导 (这种现象叫婚姻转变),人们需要支持他们自我概念的反馈 (Swann et al.,1994) (见图4.5)。</p>
</li>
<li><p>设想有位男士把从妻子那里得到的表扬都解读为过誉之词。虽然这些表扬起初让他觉得乐观而幸福,但如果他最后判断妻子只不过是言不由衷…或者干脆认为妻子是个傻子,正面光辉就会慢慢消退。无论哪种情况,从自己非常熟悉的人那里得到了过分称赞的评价,都会使自己不安、并认为对方很虚伪 (Swann, 1996,p_118) 。</p>
</li>
<li><p>显然自恋者结交的都是相当糟糕的伴侣,但要一开始就看穿他们却非常困难(Hotchkiss,2003)。他们的自信早先是能打动人的,常常要花很多时间才能认识到他们是多么自私、小气和暴躁。所以自恋者常以“致命的吸引”(fatalattraction)形式出现,一开始或许具有吸引力,但长期来看却是要人命的(Brunelletal.,2004)。这就给人们带来了挑战,在判断未来的亲密伴侣时要尽可能地运用辨别力和洞察力。</p>
</li>
<li><p>举一点来说,我们都知道自己的好友和爱人已经喜欢上我们,所以营造迷人形象以贏得他们赞许的动机不足。也可能是因为他们十分了解我们,要改变他们的看法比较困难。不过,还有可能仅仅是因为我们变懒了。要表现出最好的言谈举止需要专心和努力。有礼貌的举止通常意味着某种形式的自我约束。在已经了解并喜爱我们的人身边,我们可以放松,无拘无束。但这也意味着人们对亲密伴侣常比对认识的其他人更为坦荡不羁 (Miller, 1997b)。</p>
</li>
<li><p>一般而言,关系越亲近,人们越把好友的形象当成自己的,只要有可能就尽量美化好友的形象。</p>
</li>
<li><p>有些人或许很难被人了解,但也有些人却善于观察别人。具备优秀社交技能的人往往擅长于评价和判断他人 (Letzring, 2008),这可能是因为他们的情绪智力 (e-motional intelligence) 很高。情绪智力指人们觉知、利用、理解和管理情绪的能力 (Salovey & Grewal, 2005)。如果人们有较高的情绪智力,就能驾轻就熟地调控自己的情感,从而很少在遭受打击和挫折时反应过度。他们还能敏感地体验到别人的感受,所以他们的人际交往更加满意和亲密 (Mirgain & Cordova, 2007)</p>
</li>
</ul>
<h2 id="友谊">友谊</h2><ul>
<li><p>当同居的情侣有一方正在准备紧张的律师资格考试时,双方把各自给予和得到的支持都记录下来,结果发现最能减少应试者焦虑的,是情侣提供而应试者并未觉察到的帮助 (Bolger et al., 2000)。</p>
</li>
<li><p>当受援者觉察到有形的支持时,如果这种支持能切合受援者当前的需要和目标就会更加有效。</p>
</li>
<li><p>当受援者觉察到有形的支持时,如果这种支持能切合受援者当前的需要和目标就会更加有效。另一项研究考察了备考律师资格的学生,结果发现物质支持——比如情侣为之下厨烹调——有帮助作用,而感情支持只会使应试者更加焦虑 (Shrout et al., 2006)。</p>
</li>
<li><p>事实上,如果夫妻双方没有共同的朋友,这样的婚姻通常很艰难。</p>
</li>
<li><p>只要我们活着,友谊就是无价之宝。</p>
</li>
</ul>
<h2 id="爱情">爱情</h2><ul>
<li><p>年轻人应该离开父母,自由地恋爱,自主地决定婚姻,并把恋人带回家与父母碰面,这样一种恋爱婚姻观,许多地方的人现在仍认为这是一种荒谬之极的婚恋观 (Macdonald & Jessica, 2006)。</p>
</li>
<li><p>这些研究表明肾上腺素增强了人们的爱情体验。</p>
</li>
<li><p>对于成人,爱人的触碰会刺激催产素的释放,尤其在性高潮时会释放大量的催产素;催产素可能正是夫妻在做爱之后会感到放松和嗜睡的原因之一 (Floyd, 2006)。</p>
</li>
<li><p>浪漫的夫妻一起参加新奇、兴奋的活动会让他们彼此更加相爱</p>
</li>
<li><p>浪漫因新奇、神秘和危险而繁盛;却因了解熟识而消亡。</p>
</li>
<li><p>就浪漫的爱情而言,当伴侣变得熟悉时大脑可能根本无法产生足够多的多巴胺,所以即使你的伴侣能一如既往地完美,你也不能同样地被唤醒。</p>
</li>
<li><p>当爱情关系变得重复、单调和沉闷时就会止步不前,并非一出现坏事情就会发生厌倦,而是在婚姻生活变得没有情趣、难以让人兴奋或者没有挑战性时才会滋生厌倦 (Harasymchuk & Fehr, 2007)。</p>
</li>
<li><p>这就是你的爱情策略。享受激情,但不要把它作为维持爱情关系的基础。培养与爱人之间的友谊。努力保持新鲜感;把握住每一个与配偶共同进行新奇探索的机会</p>
</li>
</ul>
<h2 id="压力与紧张">压力与紧张</h2><ul>
<li><p>的确,那些坚持认为“相异相吸”的人可能会得到一些重大教训,只要他们与差异显著的人生活在一起,相异只会增加摩擦,而不会让关系一帆风顺。</p>
</li>
<li><p>批评者的评论或行为所要表达的内容并不重要,要紧的是被批评者把这种行动诠释为不公正和吹毛求疵。伴侣一方提出如何使用洗碗机效率更高,这本来是一个温和的建议,但如果伴侣另一方认为这一建议是不必要的批评,就会受到伤害并引起冲突。</p>
</li>
<li><p>我们不要低估公平争斗和进行“优质”争斗的难度。这需要自律和对自己伴侣真正的关爱。但正面结果通常值得努力。因此,冲突并不是可怕的问题,而是具有挑战性的机遇——理解自己和伴侣的机会,个体亲密关系变得更满意更亲密的契机。</p>
</li>
</ul>
<p>
<a href="/2017/07/10/relation/#post-footer" class="postShorten-excerpt_link link">
注释和共享
</a>
</p>
</div>
</div>
</article>
<article class="postShorten postShorten--thumbnailimg-bottom" itemscope itemType="http://schema.org/BlogPosting">
<div class="postShorten-wrap">
<div class="postShorten-header">
<h1 class="postShorten-title" itemprop="headline">