實現屏幕錄制有兩種方式,壹種是通過Core Graphic,另壹種是通過AVFoundation。核心圖形,妳可以找到壹個蘋果的官方樣本代碼,如果使用。
CGImageRef截圖= CGWindowListCreateImage(CGRectMake(0.0f,0.0f,[self screenRect].size.width,[self screenRect].size.height),kCGWindowListOptionOnScreenOnly,kCGNullWindowID,kCGWindowImageDefault | kcgwindowwimagenominalresolution);
它的優點是可以根據WindowID獲取指定窗口的圖像,並且可以通過ListOption設置包括桌面圖標、桌面圖標移除、桌面移除在內的各種設置,所以微信Mac的截圖功能應該就是使用上面那行代碼。所以我們也可以設置壹個NSTimer,以六十分之壹秒的速率獲取截圖,形成壹個流。實踐表明性能還不錯。燒CPU錄屏是常有的事。畢竟需要按幀計算像素,CPU對於Mac來說不是特別大的問題。所以這個方法是可行的,但是我覺得不夠優雅。
同樣,在核心圖形中還有另壹種實現方法:CGDisplayCreateImage:
CGImageRef Ref = CGDisplayCreateImage(display);
//ns data * data =(ns data *)CFBridgingRelease(CGDataProviderCopyData(cgimagegetdata provider(Ref)));
screen img =[[NSImage alloc]initwithcigimage:Ref size:CGDisplayScreenSize(display)];
//screen img =[image mutable copy];
CGImageRelease(Ref);
CGDisplayRelease(顯示);
這個實現的機制和上面是壹致的,效果和性能也不錯,但是還是不夠優雅。
所以現在是時候求助AVFoundation了。在AVFoundation中,有壹個輸入類叫做AVCaptureScreenInput,可以直接將視頻輸入到當前屏幕。這時我想起兩年前我做了壹個視頻追蹤人臉的sdk。簡單來說,我通過AVDeviceCapture獲取攝像頭的輸入,然後打開壹個AVSession,然後讀出輸入中的緩沖區,對每壹幀進行人臉檢測。然後我根據Github上蘋果屏幕錄制的壹個官方例子和這方面的幾個倉庫實現了壹個簡單的屏幕錄制,用AVCaptureMovieFileOutput作為輸出。到了這裏,壹切順利,屏幕錄制輸出到mov文件正常。然後我開始從緩沖區讀取緩沖區。簡單地說,從緩沖區讀取幀是基於AVCaptureFileOutputDelegate中的回調。
-(void)capture output:(avcapturefileuput *)capture output didOutputSampleBuffer:(CMSampleBufferRef)sample buffer from connection:(AVCaptureConnection *)connection;
去實現它。CMSampleBuffers這裏是壹個核心基礎對象,包含零個或多個特定媒體類型的壓縮或未壓縮樣本,通常用於傳輸媒體數據。CMSampleBuffers可以包含:
CMBlockBuffer,可能包含壹個或多個樣本(樣本可以翻譯成幀嗎?或者取樣的意義...)
CVImageBuffer包括緩沖區級別的附件和樣本級別的附件,還包括所包含的所有樣本的格式、大小和時間信息。
根據Apple Doc,CMSampleBuffers是這兩個緩沖區之壹的包裝器,因此每個CMSampleBuffers將只包含其中之壹。妳需要用不同的方法提取裏面的數據。所以我通常用最普通的方法來取緩沖區:
CVImageBufferRef image buffer = CMSampleBufferGetImageBuffer(sample buffer);
CVPixelBufferLockBaseAddress(image buffer,0);//鎖定圖像緩沖區
uint 8 _ t * base address =(uint 8 _ t *)CVPixelBufferGetBaseAddressOfPlane(image buffer,0);//獲取圖像信息
size _ t bytes perrow = CVPixelBufferGetBytesPerRow(image buffer);
size _ t width = CVPixelBufferGetWidth(image buffer);
size _ t height = CVPixelBufferGetHeight(image buffer);
cgcolorspace ref colorSpace = cgcolorspace createdevicergb();
CGContextRef new context = CGBitmapContextCreate(base address,width,height,8,bytesPerRow,colorSpace,kcgbitmapbyteorder 32 little | kcgimagalphapremultipliedfirst);
CGImageRef new image = CGBitmapContextCreateImage(new context);
CGContextRelease(new context);
CGColorSpaceRelease(顏色空間);
CVPixelBufferUnlockBaseAddress(image buffer,0);
但是,這時候出現了壹個小錯誤。這裏獲得的CMSampleBuffers包含CMBlockBuffer!於是我開始查各種stackoverflow,沒有解決方案。壹開始我以為是視頻格式問題,需要按照H264的編碼來分析,但是怎麽可能呢...我百思不得其解,即使讀取CMBlockBuffer中的數據,也無法轉換成NSImage,說明這個數據不是正常數據。那麽,有沒有可能將壹幀分割成多個樣本進行傳輸...有可能,但是我嘗試了還是沒有結果。
這時我回頭看了壹下,發現不需要把視頻導出到壹個文件裏。有沒有其他輸出可以替代?碰巧在stackoverflow上看到這個問題,就用AVCaptureVideoDataOutput試了壹下。我在嘗試之前就有強烈的感覺——畢竟上次輸出是直接輸出到壹個文件,這次輸出顯然是直接輸出為數據。因此,您所要做的就是給出這樣的輸出,然後就可以恢復正常了:
self . output =[[avcapturevideodatoutput alloc]init];
[((avcapturevideodatoutput *)self . output)setvideo settings:[ns dictionaryWithObjectsAndKeys:@(kcvpixelformatype _ 32 bgra),kcvpixembufferpixelformatypekey,nil]];
dispatch _ queue _ t queue = dispatch _ queue _ create(" com . Sergio . chan ",0);
[(AVCaptureVideoDataOutput *)self . output setSampleBufferDelegate:self queue:queue];
此時,可以按幀正常解析sampleBuffer。這裏有兩個問題。壹個是上面的代碼獲得CGImageRef的壹個newImage對象後,每次都需要釋放newImage,否則內存溢出會爆發。壹個是線程安全。在上面的代碼中,我們可以看到這個新的AVCaptureVideoDataOutputSampleBufferDelegate實際上是在壹個獨立的線程上接收回調,所以如果妳想在這個委托中執行UI操作,記得返回主線程operation =。=
@try {
CVImageBufferRef image buffer = CMSampleBufferGetImageBuffer(sample buffer);
CVPixelBufferLockBaseAddress(image buffer,0);//鎖定圖像緩沖區
uint 8 _ t * base address =(uint 8 _ t *)CVPixelBufferGetBaseAddressOfPlane(image buffer,0);//獲取圖像信息
size _ t bytes perrow = CVPixelBufferGetBytesPerRow(image buffer);
size _ t width = CVPixelBufferGetWidth(image buffer);
size _ t height = CVPixelBufferGetHeight(image buffer);
cgcolorspace ref colorSpace = cgcolorspace createdevicergb();
CGContextRef new context = CGBitmapContextCreate(base address,width,height,8,bytesPerRow,colorSpace,kcgbitmapbyteorder 32 little | kcgimagalphapremultipliedfirst);
CGImageRef new image = CGBitmapContextCreateImage(new context);
CGContextRelease(new context);
CGColorSpaceRelease(顏色空間);
CVPixelBufferUnlockBaseAddress(image buffer,0);
n image * image =[[n image alloc]initwithgimage:new image size:[self screen rect]。size];
CGImageRelease(new image);
dispatch _ async(dispatch _ get _ main _ queue(),^{
if(self.imageView) {
self . imageview . image = image;
}
});
}
@ catch(n exception * exception){
NSLog(@“在%@處出錯”,exception . debug description);
}
@終於{
返回;
}
PS中獲取ScreenRect的方法。可可如下:
-(非矩形)屏幕矩形
{
NSRect screenRect
ns array * screen array =[ns screen screens];
ns screen * screen =[screen array objectAtIndex:0];
screen rect =[屏幕框架];//[屏幕可視框架];
返回screenRect
}
後來,這裏出現了壹個小坑。如果使用visibleframe,那麽如果妳的窗口是全屏模式,那麽妳在獲取visibleframe的時候實際上會省略掉上面狀態欄的那部分,因為估計在計算visibleFrame的時候沒有考慮狀態欄是否隱藏,所以這裏還是用Frame比較好。
這裏,從delegate獲得每壹幀的數據後,每壹幀都可以被壓縮並以數據的形式傳輸。最後差點忘了介紹AVCaptureScreenInput的壹些功能:
self . input . capturesmouseclicks = YES;
self . input . minframeduration = CMTimeMake(1,60);
self . input . scale factor = 0.5f;
self . input . crop rect =[self screen rect];
首先,AVCaptureScreenInput可以記錄鼠標移動的軌跡,也可以記錄鼠標的點擊事件(自我體驗)。第二個屬性設置為最大幀速率,即每秒60幀。顧名思義,第三個和第四個屬性分別是縮放比例和最終輸出裁剪區域。設置這兩個屬性可以減少每壹幀的大小,也就是說在輸入的時候已經限制了大小,然後可以做壹些壓縮什麽的。最後,AVCaptureScreenInput其實還有壹個關鍵屬性,不過現在已經被放棄了,因為蘋果已經把這個屬性內置到系統默認中了:joy:重復幀會自動取消,在之前的版本中可以通過壹個屬性來設置,現在已經默認采用了。
多余說幾點:
其實核心媒體層的知識點很多,但是文檔太少,研究的人太少,真的很難。感興趣的朋友可以參考蘋果的參考,看看這壹塊的內容。
其實可能有人知道,AVFoundation下面,Core Media上面有壹層叫視頻工具箱,是2012年只有越獄設備才能調用的私有API,但是WWDC蘋果在2014年開放了這壹層。所以妳可以在AVFoundation更深的層次上做視頻的編解碼和流處理。這次只看了這壹塊的知識,留下壹些資料來源:Github WWDC。
最後,也是最重要的!代碼已經編譯成開源庫放在Github上了!