-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
1174 lines (925 loc) · 60.7 KB
/
atom.xml
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title><![CDATA[Chih Sheng Huang's Blog]]></title>
<link href="http://pedoe.github.io/atom.xml" rel="self"/>
<link href="http://pedoe.github.io/"/>
<updated>2016-05-27T15:02:54+08:00</updated>
<id>http://pedoe.github.io/</id>
<author>
<name><![CDATA[Chih Sheng Huang]]></name>
</author>
<generator uri="http://octopress.org/">Octopress</generator>
<entry>
<title type="html"><![CDATA[How to run java on Mac]]></title>
<link href="http://pedoe.github.io/blog/2016/05/27/how-to-run-java-on-mac/"/>
<updated>2016-05-27T14:07:18+08:00</updated>
<id>http://pedoe.github.io/blog/2016/05/27/how-to-run-java-on-mac</id>
<content type="html"><![CDATA[<h2>How to run the java code on the Mac</h2>
<h2>前言</h2>
<p>最近要開始學java, 本來想說要找個IDE來做開發練習, 但是又有點懶, 也想繼續多多練習使用vim。 就google了一下怎麼在mac上build java code, 畢竟跟C或C++不太一樣不能用gcc。接著以最常見的hello world當作範例。</p>
<h2>Step1</h2>
<p>寫一個印出hello world的程式</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>class HelloWorld{
</span><span class='line'> public static void main(String[] args)
</span><span class='line'> {
</span><span class='line'> System.out.println("hello world");
</span><span class='line'> }
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<h2>Step2</h2>
<p>使用javac指令做編譯</p>
<p> <img src="http://pedoe.github.io/images/HowToBuildJavaOnMac/compile.png" alt="compile" /></p>
<h2>Step3</h2>
<p>輸入java HelloWorld, 就可以看到結果了!</p>
<p> <img src="http://pedoe.github.io/images/HowToBuildJavaOnMac/execute.png" alt="execute" /></p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[iOS H264 Hardware Encode]]></title>
<link href="http://pedoe.github.io/blog/2016/04/13/ios-h264-hardware-encode/"/>
<updated>2016-04-13T16:33:42+08:00</updated>
<id>http://pedoe.github.io/blog/2016/04/13/ios-h264-hardware-encode</id>
<content type="html"><![CDATA[<h2>How to encode h264 file on iPhone by hardware encoding</h2>
<h2>前言</h2>
<p>最近工作上需要在iPhone上處理影音方面的codec, 花了不少時間去study跟找資料, 所以想把過程記錄下來, 怕以後自己忘掉可以很快回想起來。也希望可以幫到有需要的人,如果有許多錯誤還請多包涵, 畢竟小弟我只是個寫程式的新手。</p>
<p>要做到視訊影音的傳輸, 大概可以分成四個部分</p>
<ol>
<li>影像encode</li>
<li>影像decode</li>
<li>聲音播放</li>
<li>聲音錄製</li>
</ol>
<p>這篇就先記錄影像encode的部分, 為了達到encode出 h264 file的目的, 這邊的例子為用iPhone錄製影像, 並將影像encode成h264 file再輸出, 大概可以分成錄製影像流程跟硬體encode流程這兩個部分做說明。</p>
<h2>錄製影像</h2>
<p>這部分網路上資料很多, google就有很多答案, 主要的流程可以分為</p>
<ol>
<li>選擇輸入來源(相機, 麥克風, 耳機等等)</li>
<li>選擇前鏡頭後鏡頭</li>
<li>建立data output buffer</li>
<li>建立AVCaptrueSession</li>
<li>設定session相關參數</li>
</ol>
<p>這裡主要提一下AVCaptureSession, 根據Apple官方文件的說明, AVCaptureSession用來控制聲音或影像的輸入與輸出。就是先建立AVCaptureSession後, 加入輸入源例如AVCaptureDevieInput以及輸出源例如AVCaptureMovieFileOutput, 參考Apple官方的sample code如下:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>AVCaptureSession *captureSession = [[AVCaptureSession alloc] init];
</span><span class='line'>AVCaptureDevice *audioCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
</span><span class='line'>NSError *error = nil;
</span><span class='line'>AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioCaptureDevice error:&error];
</span><span class='line'>if (audioInput) {
</span><span class='line'> [captureSession addInput:audioInput];
</span><span class='line'>}
</span><span class='line'>else {
</span><span class='line'> // Handle the failure.
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>其他設定相機錄製等等就到後面直接看程式碼說明會比較清楚。</p>
<h2>Hardware Encode 流程</h2>
<p>在以前蘋果是沒有開放hardware codec給開發者使用的, 到了iOS8.0後才推出了VideoToolbox這個framework來讓開發者使用, 藉由硬體將需要大量運算的影像編解碼處理掉, 可以省下不少運算資源留給其他功能。為了使用硬體進行encode, 我們可以先參考蘋果在WWDC14中session513(Direct Access to Video Encoding and Decoding)中對encode流程的示意圖如下:</p>
<p> <img src="http://pedoe.github.io/images/iOS_H264_HardwareEncode/CompressFlow.png" alt="compress flow" /></p>
<p>這張圖要描述一開始iPhone鏡頭(最左邊)開啟後開始拍攝, 資料流開始讀入, 並假設現在在拍那隻貓, 貓的影像就會一張一張存到Buffer中(此時是為壓縮的原始檔)。</p>
<p>再來中間是AVAssetWriter, 這部分將未壓縮的frame丟到Video Encoder中轉成H.246的格式再輸出成file。這個部分可以再拆更細來看如下圖:</p>
<p> <img src="http://pedoe.github.io/images/iOS_H264_HardwareEncode/CompressionSession.png" alt="compression session" /></p>
<p>為了使用video encoder, 我們必須創建VTCompressionSession這個物件, 其中VTCompressionSession這個物件必須給定部分條件或值才能成功建立, 條件如下</p>
<ol>
<li>Dimenssions for compressed output</li>
<li>Format for compression</li>
<li>PixelBufferAttributes describing source buffer requirements (optional)</li>
<li>A callback for VTCompresssionSession output</li>
</ol>
<p>第一個就是你必須告知encoder你想要壓縮的frame大小, 大小單位為pixel; 第二個是則是你想壓縮成的格式, 在Apple官方提供的framework中有幾種格式可以選, 不過這裡格式就用H.264;
第三個為看你要不要建立buffer pool來存放你的source frame, 這部分我沒有用到就不是非常清楚; 第四個為必須給一個callback讓encoder處理完後呼叫, callback的機制可以參考下圖</p>
<p> <img src="http://pedoe.github.io/images/iOS_H264_HardwareEncode/CompressionCallback.png" alt="compression callback" /></p>
<p>可以看出來我們必須要將壓縮後存有h.264 frame的CMSampleBuffer傳入callback, 並在callback中進行h.264 frame的解析處理(關於sps, pps ,i-frame etc.), 若在中間錯誤callback則會有
error code等相關處理。</p>
<p>創建完VTCompressionSession這個物件後, 可以對該encoder給定一些描述, 例如壓縮的bitrate, 畫素等等如下圖:</p>
<p> <img src="http://pedoe.github.io/images/iOS_H264_HardwareEncode/CompressionConfiguring.png" alt="compression configuring" /></p>
<p>整個流程可以總結如下</p>
<ol>
<li>準備一個裝有未壓縮frame的Buffer</li>
<li>建立encoder(前面提到的VTCompressionSession物件)</li>
<li>設定encoder相關參數, 特性</li>
<li>開始錄影後把得到的CVPixelBuffer送進encoder內, 再把encode出來的frame放入CMSampleBuffer做後續的處理(看要存成檔案還是透過網路傳出去等等)</li>
</ol>
<h2>實作錄製影像並encode成h264檔案</h2>
<h3>開啟錄影等功能</h3>
<p>首先開啟一個新的專案</p>
<p> <img src="http://pedoe.github.io/images/iOS_H264_HardwareEncode/CreateProject.png" alt="create project" /></p>
<p>建立一個Button跟UIView, Button用來開始錄製和結束錄製, UIView則用來呈現出鏡頭拍攝到的即時影像。</p>
<p> <img src="http://pedoe.github.io/images/iOS_H264_HardwareEncode/UIView.png" alt="set view and button on storyboard" /></p>
<p>接下來加入VideoToolBox的framework和把UIView跟ViewController做連結</p>
<p> <img src="http://pedoe.github.io/images/iOS_H264_HardwareEncode/myView_VideoToolBox.png" alt="videoToolBox" /></p>
<p>接著先處理相機相關的部分, 根據上面介紹的流程, 我們先建立物件如下, 並且使class符合<AVCaptureVideoDataOutputSampleBufferDelegate> 這個協定的規範</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>@interface ViewController ()<AVCaptureVideoDataOutputSampleBufferDelegate>
</span><span class='line'>{
</span><span class='line'> AVCaptureSession *captureSession; //根據前面說明captureSession負責處理資料的輸入輸出
</span><span class='line'> AVCaptureVideoPreviewLayer *previewLayer; //到時候將鏡頭拍攝到的影像在此view上作呈現
</span><span class='line'> AVCaptureConnection *connection; //負責資料流的開關與建立captureSession後和capture到的output, input資料做連結
</span><span class='line'> bool isStart; //開啟和關閉影像錄製, 初始值給定true
</span><span class='line'>}
</span><span class='line'>@end</span></code></pre></td></tr></table></div></figure>
<p>再來建立Button觸發後的行為</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>- (IBAction)actionStartStop:(id)sender {
</span><span class='line'> if (isStart) {
</span><span class='line'> [self startCamera];
</span><span class='line'> isStart = false;
</span><span class='line'> [_startButton setTitle:@"Stop" forState:UIControlStateNormal];
</span><span class='line'> }
</span><span class='line'> else{
</span><span class='line'> isStart = true;
</span><span class='line'> [self stopCamera];
</span><span class='line'> [_startButton setTitle:@"Start" forState:UIControlStateNormal];
</span><span class='line'> }
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>- (void)startCamera
</span><span class='line'>{
</span><span class='line'>
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>- (void)stopCamera
</span><span class='line'>{
</span><span class='line'>
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>接下來處理button觸發後startCamera的行為, 首先建立輸入來源, 在這邊我們選擇前鏡頭, 方便自拍</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>NSError *deviceError;
</span><span class='line'>AVCaptureDeviceInput *inputDevice;
</span><span class='line'>for (AVCaptureDevice *device in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
</span><span class='line'> if ([device position] == AVCaptureDevicePositionFront) {
</span><span class='line'> inputDevice = [AVCaptureDeviceInput deviceInputWithDevice:device error:&deviceError];
</span><span class='line'> }
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>處理輸出源, 這邊使用了AVCaptureVideoDataOutput這個類別, 因為它提供我們去access得到的未壓縮frame, 可以呼叫 captureOutput:didOutputSampleBuffer:fromConnection:
這個delegate method來對frame做後續處理製作成h264的frame。除了選定輸出源, 我們還可以對輸出源做參數上的設定, iOS對kCVPixelBufferPixelFormatTypeKey支援三種format分別是</p>
<pre><code>kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
kCVPixelFormatType_32BGRA.
</code></pre>
<p>詳細差異我就不是很清楚了</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>AVCaptureVideoDataOutput *outputDevice = [[AVCaptureVideoDataOutput alloc]init];
</span><span class='line'>NSString *key = (NSString *)kCVPixelBufferPixelFormatTypeKey;
</span><span class='line'>NSNumber *val = [NSNumber numberWithUnsignedInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange];
</span><span class='line'>NSDictionary *videoSettings = [NSDictionary dictionaryWithObject:val forKey:key];
</span><span class='line'>outputDevice.videoSettings = videoSettings;
</span><span class='line'>[outputDevice setSampleBufferDelegate:self queue:dispatch_get_main_queue()];</span></code></pre></td></tr></table></div></figure>
<p></p>
<p>allocate AVCaptureSession, 接著加入output與input, 再呼叫begingConfiguring為後續調整output做準備。
這邊我們設定畫素為640x480, 並將connection設為連接output與video, 最後commit我們的配置。</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>captureSession = [[AVCaptureSession alloc]init];
</span><span class='line'>[captureSession addInput:inputDevice];
</span><span class='line'>[captureSession addOutput:outputDevice];
</span><span class='line'>[captureSession beginConfiguration];
</span><span class='line'>captureSession.sessionPrest = AVCaptureSessionPreset640x480;
</span><span class='line'>connection = [outputDevice connectionWithMediaType:AVMediaTypeVideo];
</span><span class='line'>[connection commitConfiguration];</span></code></pre></td></tr></table></div></figure>
<p>最後把擷取到的影像放到previewLayer上做呈現, 並startRunning!</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>previewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:captureSession];
</span><span class='line'>previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
</span><span class='line'>[self.myView.layer addSublayer:previewLayer];
</span><span class='line'>previewLayer.frame = self.myView.bounds;
</span><span class='line'>[captureSession startRunning];</span></code></pre></td></tr></table></div></figure>
<p>停止的部分比較簡單, 呼叫stopRunning即可</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>- (void)stopCamera
</span><span class='line'>{
</span><span class='line'> [captureSession stopRunning];
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>到這邊應該就可以試看看簡單的攝影功能了, 按下button相機鏡頭拍攝到的影像就會呈現在preView上。</p>
<h3>Encoder建立</h3>
<p>先新增一個名為VideoEncode的class, 再VideoEncode.h檔中, 我們建立兩個method與新增一個delegate 的portocol協定
initEncode:width:height是用來初始化一個encoder, 並給定要encode的影像畫素大小; encode:則是把未壓縮的frame做encode, 需要傳入一裝有未壓縮frame的sampleBuffer
這邊則創建一個新的portocol協定取名為H264HwEncoderDelegate, 裡面含有兩個實作方法, 分別是取得sps, pps frame還有keyframe的方法。接下來只要在ViewController中實作該方法就可以得到
影像再存入檔案中了。</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>#import <Foundation/Foundation.h>
</span><span class='line'>#import <AVFoundation/AVFoundation.h>
</span><span class='line'>#import <VideoToolbox/VideoToolbox.h>
</span><span class='line'>
</span><span class='line'>@protocol H264HwEncoderDelegate <NSObject>
</span><span class='line'>
</span><span class='line'>- (void)gotSpsPps:(NSData*)sps pps:(NSData*)pps;
</span><span class='line'>- (void)gotEncodedData:(NSData*)data isKeyFrame:(BOOL)isKeyFrame;
</span><span class='line'>
</span><span class='line'>@end
</span><span class='line'>
</span><span class='line'>@interface VideoEncode : NSObject
</span><span class='line'>- (void) initEncode:(int)width height:(int)height;
</span><span class='line'>- (void) encode:(CMSampleBufferRef )sampleBuffer;
</span><span class='line'>
</span><span class='line'>@property (weak, nonatomic) id<H264HwEncoderDelegate> delegate;
</span><span class='line'>
</span><span class='line'>@end</span></code></pre></td></tr></table></div></figure>
<p>接下來宣告相關變數, VTCompressionSessionRef就是前面提到的VideoToolbox提供的壓縮session物件, encodeQueue則是要將encode frame丟到queue裡面照順序處理, 得到的file時間順序才會對。
CMFormatDescriptionRef是用來設定壓縮的format;</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>@implementation VideoEncode
</span><span class='line'>{
</span><span class='line'> NSString *error;
</span><span class='line'> VTCompressionSessionRef *encodeSession;
</span><span class='line'> dispatch_queue_t encodeQueue;
</span><span class='line'> CMFormatDescriptionRef format;
</span><span class='line'> BOOL initialized;
</span><span class='line'> int frameCount;
</span><span class='line'> NSData *sps;
</span><span class='line'> NSData *pps;
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>進行初始化</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>- (id)init
</span><span class='line'>{
</span><span class='line'> self = [super init];
</span><span class='line'> if(self) {
</span><span class='line'> [self initVariables];
</span><span class='line'> }
</span><span class='line'> return self;
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>- (void)initVariables
</span><span class='line'>{
</span><span class='line'> encodeSession = nil;
</span><span class='line'> initialized = true;
</span><span class='line'> encodeQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
</span><span class='line'> frameCount = 0;
</span><span class='line'> sps = NULL;
</span><span class='line'> pps = NULL;
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>初始化encoder內容, 先將初始化的過程丟到sync Queue內保護起來照順序做。接著建立Compression Session, VTCompressionSessionCreate這個API有許多東西要給入, 例如畫素大小,
callbalck(didCompressH264), 還有最一開始宣告的encodeSession位置等等。而VTCompressionSessionCreate這個API會回傳一個error code, 正常的話會傳回0, 方便我們檢驗。
VTSessionSetProperty有很多參數可以設定, 這裏設定了realtime的encode, I frame到下一張I frame的間隔, 以及選定compression bitstream的profile level。
最後告知encoder準備好了。</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>- (void)initEncode:(int)width height:(int)height
</span><span class='line'>{
</span><span class='line'> dispatch_sync(encodeQueue, ^{
</span><span class='line'> // Create the compression session
</span><span class='line'> OSStatus status = VTCompressionSessionCreate(NULL, width, height, kCMVideoCodecType_H264, NULL, NULL, NULL, didCompressH264, (__bridge void *)(self), &encodeSession);
</span><span class='line'> NSLog(@"H264: VTCompressionSessionCreate %d", (int)status);
</span><span class='line'>
</span><span class='line'> if (status != 0) {
</span><span class='line'> NSLog(@"H264: Unable to create H264 session");
</span><span class='line'> error = @"H264: Unable to create H264 session";
</span><span class='line'>
</span><span class='line'> return;
</span><span class='line'> }
</span><span class='line'>
</span><span class='line'> // Set the properties
</span><span class='line'> VTSessionSetProperty(encodeSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
</span><span class='line'> VTSessionSetProperty(encodeSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, (__bridge CFNumberRef)@(10.0)); // change the frame number between 2 I frame
</span><span class='line'> VTSessionSetProperty(encodeSession, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Main_AutoLevel);
</span><span class='line'>
</span><span class='line'> // Tell the encoder to start encoding
</span><span class='line'> vVTCompressionSessionPrepareToEncodeFrames(encodeSession);
</span><span class='line'> });
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>處理encode frame部分, 前面init時不丟到queue裡面還行, 這裏注意一定要丟到sync queue裡, 確保frame的順序正確。
使用CMSampleBufferGetImageBuffer來取得buffer內的影像資料, 存到imageBuffer內; VTCompressionSessionEncodeFrame, 這個function就是負責壓縮frame的function, 所以有一些參數必須設置
以及傳入。一些重要一定得傳入的有先前創建的encodeSession, 還有存有未壓縮frame的imageBuffer, 最後則是當error發生時要進行的處理。</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>- (void)encode:(CMSampleBufferRef)sampleBuffer
</span><span class='line'>{
</span><span class='line'> dispatch_sync(encodeQueue, ^{
</span><span class='line'> frameCount++;
</span><span class='line'> // Get the CV Image buffer
</span><span class='line'> CVImageBufferRef imageBuffer = (CVImageBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
</span><span class='line'> // Create properties
</span><span class='line'> CMTime presentationTimeStamp = CMTimeMake(frameCount, 1000);
</span><span class='line'> //CMTime duration = CMTimeMake(1, DURATION);
</span><span class='line'> VTEncodeInfoFlags flags;
</span><span class='line'> // Pass it to the encoder
</span><span class='line'> OSStatus statusCode = VTCompressionSessionEncodeFrame(encodeSession,
</span><span class='line'> imageBuffer,
</span><span class='line'> presentationTimeStamp,
</span><span class='line'> kCMTimeInvalid,
</span><span class='line'> NULL, NULL, &flags);
</span><span class='line'> // Check for error
</span><span class='line'> if (statusCode != noErr) {
</span><span class='line'> NSLog(@"H264: VTCompressionSessionEncodeFrame failed with %d", (int)statusCode);
</span><span class='line'> error = @"H264: VTCompressionSessionEncodeFrame failed ";
</span><span class='line'>
</span><span class='line'> // End the session
</span><span class='line'> VTCompressionSessionInvalidate(encodeSession);
</span><span class='line'> CFRelease(encodeSession);
</span><span class='line'> encodeSession = NULL;
</span><span class='line'> error = NULL;
</span><span class='line'>
</span><span class='line'> return;
</span><span class='line'> }
</span><span class='line'>
</span><span class='line'> NSLog(@"H264: VTCompressionSessionEncodeFrame Success");
</span><span class='line'> });
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>關於callback比較特別, 進去看VTCompressionCreate這個API裡面的Callback是一個C語言的function pointer type, 所以要做一些轉換。要宣告該callback函數讓其他method能夠呼叫,
再實作該callbackvoid。callback要處理的事情有點多就分段記錄下來, 首先是檢查呼叫callback時傳入的狀態與資料是否正確以及是否有key frame等等, 再來是因為在C語言的寫法下我們沒辦法像[self function]這樣呼叫, C語言看不懂self, 所以在呼叫callback時要把整個class傳入再做casting的轉換。</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>void didCompressH264(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags,CMSampleBufferRef sampleBuffer );
</span><span class='line'>
</span><span class='line'>void didCompressH264(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags,CMSampleBufferRef sampleBuffer )
</span><span class='line'>{
</span><span class='line'> if (status != 0) {
</span><span class='line'> assert(status == 0);
</span><span class='line'> return;
</span><span class='line'> }
</span><span class='line'>
</span><span class='line'> if (!CMSampleBufferDataIsReady(sampleBuffer)) {
</span><span class='line'> NSLog(@"didCompressH264 data is not ready");
</span><span class='line'> }
</span><span class='line'>
</span><span class='line'> VideoEncode *THIS = (__bridge VideoEncode*)outputCallbackRefCon;
</span><span class='line'>
</span><span class='line'> //Check if we have got a key frame first
</span><span class='line'> bool keyframe = !CFDictionaryContainsKey((CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true), 0)), kCMSampleAttachmentKey_NotSync);
</span></code></pre></td></tr></table></div></figure>
<p>若成功得到key frame後, 依照h264的格式先後得到sps, pps, i-frame。根據官方提供的資料, 在錄製影像時未壓縮的檔案是以MP4的格式header用AVCC呈現。可以看下圖會比比較清楚的了解,
可以看到下圖中MP4的sps, pps可以直接利用CMVideoFormatDescriptionGetH264ParameterSetAtIndex這個method來取得。</p>
<p> <img src="http://pedoe.github.io/images/iOS_H264_HardwareEncode/H264_sps_pps.png" alt="H264 sps pps format" /></p>
<p>知道如何取得sps, pps後先宣告frame的資料大小以及存放frame raw data的變數, 接下來呼叫CMVideoFormatDescriptionGetH264ParameterSetAtIndex時
將宣告的變數位址傳入。得到sps, pps frame後就可以將資料丟到有實作前面定義portocol協定的class中, 在這邊是ViewController, 因此ViewController那邊就可以得到sps, pps frame的資料。</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>if (keyframe) {
</span><span class='line'> CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);
</span><span class='line'> size_t sparameterSetSize, sparameterSetCount;
</span><span class='line'> const uint8_t *sparameterSet;
</span><span class='line'> OSStatus statusCode = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format,0,&sparameterSet,&sparameterSetSize,&sparameterSetCount, 0);
</span><span class='line'>
</span><span class='line'> if (statusCode == noErr) {
</span><span class='line'> assert(status == 0);
</span><span class='line'> // Found sps and now check the pps
</span><span class='line'> size_t pparameterSetSize, pparameterSetCount;
</span><span class='line'> const uint8_t *pparameterSet;
</span><span class='line'> OSStatus statusCode = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format,1,&pparameterSet,&pparameterSetSize,&pparameterSetCount, 0);
</span><span class='line'>
</span><span class='line'> if (statusCode == noErr) {
</span><span class='line'> assert(status == 0);
</span><span class='line'> // Found pps
</span><span class='line'> THIS->sps = [NSData dataWithBytes:sparameterSet length:sparameterSetSize];
</span><span class='line'> THIS->pps = [NSData dataWithBytes:pparameterSet length:pparameterSetSize];
</span><span class='line'>
</span><span class='line'> if (THIS->_delegate) {
</span><span class='line'> [THIS->_delegate gotSpsPps:THIS->sps pps:THIS->pps];
</span><span class='line'> }
</span><span class='line'> }
</span><span class='line'> }
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>處理完sps, pps frame後進行i-frame的處理。i-frame跟sps, pps frame比起來就需要做一些轉換, MP4的前四個byte代表該筆資料的長度, 第五個byte開始才是影像資料。
所以我們需要另外處理一下, 而且前四個byte是以big-endian呈現資料長度, 需要轉成low-endian。剩下的就是不斷的把位置移到下一個NALU開頭, 找到之後再把資料依序處理。
這部分可以參考下圖</p>
<p> <img src="http://pedoe.github.io/images/iOS_H264_HardwareEncode/H264_iFrame.png" alt="h264 i frame" /></p>
<p>因為i-frame不確定長度會有多長, 有可能會比較大的長度才得到完整的i-frame, 這裏用CMBlockBufferGetDataPointer去取得資料。一樣把要存放資料與資料的大小長度等變數位址傳入。</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
</span><span class='line'>size_t length, totalLength;
</span><span class='line'>char *dataPointer;
</span><span class='line'>OSStatus statusCodeRet = CMBlockBufferGetDataPointer(dataBuffer, 0, &length, &totalLength, &dataPointer);
</span><span class='line'>if (statusCodeRet == noErr) {
</span><span class='line'> assert(statusCodeRet == 0);
</span><span class='line'> size_t bufferOffset = 0;
</span><span class='line'> static const int AVCCHeaderLength = 4;
</span><span class='line'> while (bufferOffset < totalLength - AVCCHeaderLength) {
</span><span class='line'> // Read the NAL unit length
</span><span class='line'> uint32_t NALUnitLength = 0;
</span><span class='line'> memcpy(&NALUnitLength, dataPointer + bufferOffset, AVCCHeaderLength);
</span><span class='line'>
</span><span class='line'> // Convert the length value from Big-endian to Little-endian
</span><span class='line'> // After converting the length value, the start 4 byte will present the NALUnitLength
</span><span class='line'>
</span><span class='line'> NALUnitLength = CFSwapInt32BigToHost(NALUnitLength);
</span><span class='line'> NSData *data = [[NSData alloc]initWithBytes:(dataPointer + bufferOffset + AVCCHeaderLength) length:NALUnitLength];
</span><span class='line'>
</span><span class='line'> if (THIS->_delegate) {
</span><span class='line'> [THIS->_delegate gotEncodedData:data isKeyFrame:keyframe];
</span><span class='line'> }
</span><span class='line'>
</span><span class='line'> // Move to the next NAL unit in the block buffer
</span><span class='line'> bufferOffset += AVCCHeaderLength + NALUnitLength;
</span><span class='line'> }
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>回到ViewController來看, 將宣告的變數補齊以及實作delegate</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>@interface ViewController ()<AVCaptureVideoDataOutputSampleBufferDelegate, H264HwEncoderDelegate>
</span><span class='line'>{
</span><span class='line'> AVCaptureSession *captureSession;
</span><span class='line'> AVCaptureVideoPreviewLayer *previewLayer;
</span><span class='line'> AVCaptureConnection *connection;
</span><span class='line'> bool isStart;
</span><span class='line'> VideoEncode *videoEncode;
</span><span class='line'> NSFileHandle *fileHandle;
</span><span class='line'> NSString *h264File;
</span><span class='line'>}
</span><span class='line'>@property (weak, nonatomic) IBOutlet UIButton *startButton;
</span><span class='line'>@end</span></code></pre></td></tr></table></div></figure>
<p>接著透過delegate取得資料後寫入檔案, 在前面有用到AVCaptureVideoDataOutput並實作該協定, 所以加上該協定下的delegate method。
這個method會在有資料進到buffer內時被呼叫, 我們再將buffer傳給繼承VideoEncode的物件videoEncode去做影像的encode。</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>-(void) captureOutput:(AVCaptureOutput*)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection*)connection
</span><span class='line'>{
</span><span class='line'> [videoEncode encode:sampleBuffer];
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>接著在ViewController內實作H264HwEncoderDelegate的方法來取得sps, pps, i-frame, 記得要在寫入file前將frame的資料加上h264的header byte(0x00, 0x00, 0x00, 0x01)
因為MP4 AVCC下沒有h264的header, 要自己加上去。</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>- (void)gotSpsPps:(NSData*)sps pps:(NSData*)pps
</span><span class='line'>{
</span><span class='line'> NSLog(@"gotSpsPps %d %d", (int)[sps length], (int)[pps length]);
</span><span class='line'> const char bytes[] = "\x00\x00\x00\x01";
</span><span class='line'> size_t length = (sizeof bytes) - 1; //string literals have implicit trailing '\0'
</span><span class='line'> NSData *ByteHeader = [NSData dataWithBytes:bytes length:length];
</span><span class='line'> [fileHandle writeData:ByteHeader];
</span><span class='line'> [fileHandle writeData:sps];
</span><span class='line'> [fileHandle writeData:ByteHeader];
</span><span class='line'> [fileHandle writeData:pps];
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>- (void)gotEncodedData:(NSData*)data isKeyFrame:(BOOL)isKeyFrame
</span><span class='line'>{
</span><span class='line'> NSLog(@"gotEncodedData %d", (int)[data length]);
</span><span class='line'> if (fileHandle != NULL)
</span><span class='line'> {
</span><span class='line'> const char bytes[] = "\x00\x00\x00\x01";
</span><span class='line'> size_t length = (sizeof bytes) - 1; //string literals have implicit trailing '\0'
</span><span class='line'> NSData *ByteHeader = [NSData dataWithBytes:bytes length:length];
</span><span class='line'> [fileHandle writeData:ByteHeader];
</span><span class='line'> [fileHandle writeData:data];
</span><span class='line'> }
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>到這邊應該就差不多完成了, 可以簡單的錄製影像成h264 file, 再找個可以播放h264的播放器驗證看看。我是用VLC或是用自己寫的另一個專案做h264 decoder,
這部分找機會會在記錄一下, 畢竟打一篇網誌真的要好久好久…</p>
<p>可能有部分小細節沒有提到, 所以我將這部分的代碼放在我的GitHub上: <a href="https://github.com/pedoe/iOS_h264_hardware_encode">https://github.com/pedoe/iOS_h264_hardware_encode</a></p>
<p>有問題的地方還請告知~</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[How to separate storyboard for iPhone and iPad]]></title>
<link href="http://pedoe.github.io/blog/2016/03/17/how-to-separate-storyboard-for-iphone-and-ipad/"/>
<updated>2016-03-17T19:29:51+08:00</updated>
<id>http://pedoe.github.io/blog/2016/03/17/how-to-separate-storyboard-for-iphone-and-ipad</id>
<content type="html"><![CDATA[<h2>Storyboard for iPhone and iPad</h2>
<p>在Xocde6時Apple提供了Universal Storyboard的選項讓一個storyboard可以適應任何size的裝置。但有時候還是會因為iPhone與iPad的大小不同來呈現不同的效果, 在Standford CS193 Class11中的例子就使用iPad額外添加splitView的效果。</p>
<p>1.增加iPad的Storyboard</p>
<p> <img src="http://pedoe.github.io/images/CreateStoryboard.png" alt="Create Storyboard" /></p>
<p>2.取名(這裏我用<code>Main_iPad</code>)</p>
<p> <img src="http://pedoe.github.io/images/GiveName.png" alt="Give Name" /></p>
<p>3.在Project setting中選擇Info</p>
<p> <img src="http://pedoe.github.io/images/Info.png" alt="Info" /></p>
<p>4.右鍵新增並取名為的Main storyboard file base name (iPad)</p>
<p> <img src="http://pedoe.github.io/images/AddNewStoryboardfile.png" alt="Add New Storyboard File" /></p>
<p>5.Value中給定你在步驟3中設定iPad storyboard的名字(這裏我的是<code>Main_iPad</code>)</p>
<p>6.別忘了開啟iPad Storyboard設定Initial view, 才找得到你的Viewcontroller</p>
<p> <img src="http://pedoe.github.io/images/AddInitialView.png" alt="AddInitialView" /></p>
<h2>Reference</h2>
<p>1.<a href="https://irawd.wordpress.com/2014/10/21/xcode-6-separate-storyboard-for-ipad-and-iphone/">https://irawd.wordpress.com/2014/10/21/xcode-6-separate-storyboard-for-ipad-and-iphone/</a></p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[How to Update Locate Database on OS X]]></title>
<link href="http://pedoe.github.io/blog/2016/03/17/update-locate-database/"/>
<updated>2016-03-17T09:51:29+08:00</updated>
<id>http://pedoe.github.io/blog/2016/03/17/update-locate-database</id>
<content type="html"><![CDATA[<h2>在Mac上使用locate指令尋找檔案</h2>
<p>最近開始學在Mac上用Terminal做事情, ㄧ直不知道怎樣找檔案最快, 直到讀到了locate這個指令。</p>
<p>但是第一次使用locate前需要update database, 若是沒有更新會遇到以下問題:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>WARNING: The locate database (/var/db/locate.database) does not exist.
</span><span class='line'>To create the database, run the following command:
</span><span class='line'>
</span><span class='line'> sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.locate.plist
</span><span class='line'>
</span><span class='line'> Please be aware that the database can take some time to generate; once
</span><span class='line'> the database has been created, this message will no longer appear.</span></code></pre></td></tr></table></div></figure>
<p>而怎麼更新database在"Learning Unix for OS X" 這本書上並沒有講得很清楚, 因此上網google了一下。</p>
<p>需要執行<code>$sudo /usr/libexec/locate.updatedb</code></p>
<p>讓你的Mac做第一次更新, 第一次會花些時間來建立你的database。</p>
<p>成功後就可以使用locate查詢, ex: <code>$locate test</code></p>
<p>但是接下來每次更新都需要下相同指令<code>$sudo /usr/libexec/locate.updatedb</code>,</p>
<p>變得稍嫌麻煩, 可以做一些更改將指令簡化成updatedb(跟linux上相同的指令)。</p>
<p><code>cd /usr/local/bin</code></p>
<p><code>ln -s /usr/libexec/locate.updatedb updatedb</code></p>
<p>接下來每次更新database只需要下<code>updatedb</code>就可以了。</p>
<h2>Referrence</h2>
<p>1.<a href="http://aross.se/2015/07/19/how-to-update-locate-database-on-osx.html">http://aross.se/2015/07/19/how-to-update-locate-database-on-osx.html</a></p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Post Article]]></title>
<link href="http://pedoe.github.io/blog/2016/03/03/post-article/"/>
<updated>2016-03-03T21:28:18+08:00</updated>
<id>http://pedoe.github.io/blog/2016/03/03/post-article</id>
<content type="html"><![CDATA[<h2>前言</h2>
<p> 成功架好blog後要做的第一件事, 就是來發表一篇文章充充版面。這篇文章中會教大家如何發表文章以及基礎的排版編輯。首先要來談談使用Octopress+Github Pages架設網頁的主要優點。</p>
<ol>
<li><p>GitHub是一使用Git版本控制並且提供存放軟體代碼與內容服務的平台。而Git是一免費的分散式版本控制系統, 由Linus Torvalds(Linux Kernel的創作者)在2005年左右所創造出來的, 目前是被廣泛應用的版本控制系統之一。Git的好處是可以在本機端就有一儲存庫(Repository)可進行修改編輯並運作版本控制, 不必連線到主機端取得資料, 使得開發非常快速且方便。因此使用Github來存放blog好處是可以用強大的git進行blog內容的控制, 也容易讓多個作者維護同一個blog。最棒的是可以學習熟悉目前世界上最潮的版本控制系統。</p></li>
<li><p>Octopress支援markdown語法來進行編輯, 大大簡化編寫網頁的過程。Markdown是一種輕量級的標記式語言, 目的為好讀好寫, 讓使用者免去學習HTML複雜繁長的語法由靠編寫markdown文件再轉換成HTML文件來產生網頁。這篇文章將會介紹一些常用的語法並舉簡單的例子實作markdown語法。而使用Octopress還有一個好處是內部一些方便的指令, 例如幫助你把網頁部署到github pages與local端預覽blog呈現效果等等。</p></li>
</ol>
<h2>發表你第一篇文章</h2>
<ol>
<li><p>進到你的Octopress資料夾內</p>
<p><code>$cd octopress</code></p></li>
<li><p>產生一篇名為'title'的文章</p>
<p><code>$rake new_post['title']</code></p></li>
<li><p>接下來你就會看到產生一個名字為時間與title的markdown文件, 類似下方的訊息</p>
<p><code>Creating new post: source/_posts/2016-03-04-title.markdown</code></p></li>
<li><p>產生文章後我們來預覽看看</p>
<p><code>$rake preview</code></p>
<p><img src="http://pedoe.github.io/images/new_post_article.png" alt="Alt Text" /></p>
<p>有沒有看到上面一篇名為'Title'的文章?恭喜你!在blog上發表了第一篇文章。</p></li>
</ol>
<h2>編輯文章內容</h2>
<p>要開始編輯文章, 我們必須進到文章所在的資料夾去開始編輯。</p>
<ol>
<li><p>預設中是放在/octopress/source/_posts</p>
<p><code>$cd octopress/source/_posts</code></p></li>
<li><p>檢查文章是否存在, 輸入ls來觀看資料夾內所有內容</p>
<p><code>$ls</code></p>
<p>此時你應該會看到剛剛所建立的文章, 在本文的例子中是'2016-03-04-title.markdown'這篇文章</p>
<p><img src="http://pedoe.github.io/images/ls.png" alt="Alt Text" /></p></li>
<li><p>由於我是用vim進行文章編輯, 網路上也有許多方便好用的markdwon編輯軟體, 就依照個人喜好囉。首先開啟文章進行編輯</p>
<p><code>$vim 2016-03-04-title.markdown</code></p>
<p>開啟後你應該會看到如下的畫面</p>
<p><img src="http://pedoe.github.io/images/content.png" alt="Alt Text" /></p>
<p>title這行就是顯示你這篇文章的title</p>
<p>data就是你產生文章的時間</p>
<p>comment則是你可以選擇這篇文章是否允許評論(要能成功使別人留言還需做點額外努力, 可以自行google或是以後有機會談到blog設定時再分享給大家)</p>
<p>categories則是可以添加你希望支援的語法, 例如CSS3, Sass等等</p></li>
<li><p>接下來隨意輸入內容進行測試</p>