天天看點

[轉]iOS微信小視訊優化心得

轉自:http://www.cnblogs.com/itlover2013/p/5469588.html

小視訊是微信6.0版本重大功能之一,在開發過程中遇到不少問題。本文先叙述小視訊的産品需求,介紹了幾個實作方案,分析每個方案的優缺點,最後總結出最優的解決方案。

小視訊播放需求

  1. 可以同時播放多個視訊
  2. 使用者操作界面時視訊可以繼續播放
  3. 播放時不能卡住界面,視訊滑進界面内後要立即播放
  4. 視訊在清單内播放是靜音播放,點選放大是有聲播放

小視訊播放方案

1. MPMoviePlayerController

MPMoviePlayerController是一個簡單易用的視訊播放控件,可以播放本地檔案和網絡流媒體,支援mov、mp4、mpv、3gp等H.264和MPEG-4視訊編碼格式,支援拖動進度條、快進、後退、暫停、全屏等操作,并為開發者提供了一系列播放狀态事件通知。使用時先設定URL,然後把它的view add到某個parent view裡,再調用play即可。

但這方案的缺點是,同一時間隻能有一個MPMoviePlayerController對象播放,不滿足同時多個播放的需求;而且也不支援靜音播放。MPMoviePlayerController适合于全屏播放視訊的場景。

2. AVPlayer

AVPlayer是AVFoundation.Framework提供的偏向于底層的視訊播放控件,用起來複雜,但功能強大。單獨使用AVPlayer是無法顯示視訊的,要把它添加到AVPlayerLayer裡才行。另外它需要配合AVPlayerItem使用,AVPlayerItem類似于MVC裡的Model層,負責資源加載、視訊播放設定及播放狀态管理(通過KVO方式來觀察狀态)。它們關系如下:

首先建立一個AVPlayerItem對象:

NSURL* videoUrl = [NSURL fileURLWithPath:m_path isDirectory:NO];
m_playItem = [AVPlayerItem playerItemWithURL:videoUrl];
// 監聽playItem的status屬性
[m_playItem addObserver:self forKeyPath:@"status" 
    options:NSKeyValueObservingOptionNew context:nil];      

接下來是建立AVPlayer和AVPlayerLayerView對象。AVPlayerLayerView是自定義的UIView,用于AVPlayer播放,其layerClass是AVPlayerLayer:

// AVPlayer
m_player = [AVPlayer playerWithPlayerItem:m_playItem];
m_player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
// AVPlayerLayerView
m_playerView = [[AVPlayerLayerView alloc] initWithFrame:self.bounds];
[self addSubview:m_playerView];
// 把AVPlayer添加到AVPlayerLayer
[(AVPlayerLayer*)[m_playerView layer] setPlayer:m_player];
// 觀察AVPlayerItem播放結束的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(itemPlayEnded:) 
    name:AVPlayerItemDidPlayToEndTimeNotification object:m_playItem];      

AVPlayerItem的status屬性有三種狀态:AVPlayerStatusUnknown、AVPlayerStatusReadyToPlay及AVPlayerStatusFailed。當status=AVPlayerStatusReadyToPlay時,就代表視訊能播放了,此時調用AVPlayer的play方法就能播放視訊了。

相比MPMoviePlayerController,AVPlayer有最多可以同時播放16個視訊。另外AVPlayer在使用時會占用AudioSession,這個會影響用到AudioSession的地方,如聊天視窗開啟小視訊功能。還有AVPlayer釋放時最好先把AVPlayerItem置空,否則會有解碼線程殘留着。最後是性能問題,如果聊天視窗連續播放幾個小視訊,清單滑動時會非常卡。通過Instrument測試性能,看不出哪裡耗時,懷疑是視訊播放互相搶鎖引起的。

3. AVAssetReader+AVAssetReaderTrackOutput

既然AVPlayer在播放視訊時會有性能問題,我們不如做自己的播放器。AVAssetReader可以從原始資料裡擷取解碼後的音視訊資料。結合AVAssetReaderTrackOutput,能讀取一幀幀的CMSampleBufferRef。CMSampleBufferRef可以轉化成CGImageRef。為此,我們可以寫個MMovieDecoder的類,負責視訊解碼,每讀出一個SampleBuffer就往上層回調:

AVAssetReader* reader = [[AVAssetReader alloc] initWithAsset:m_asset error:&error];
NSArray* videoTracks = [m_asset tracksWithMediaType:AVMediaTypeVideo];
AVAssetTrack* videoTrack = [videoTracks objectAtIndex:0];
// 視訊播放時,m_pixelFormatType=kCVPixelFormatType_32BGRA
// 其他用途,如視訊壓縮,m_pixelFormatType=kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
NSDictionary* options = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:
        (int)m_pixelFormatType] forKey:(id)kCVPixelBufferPixelFormatTypeKey];
AVAssetReaderTrackOutput* videoReaderOutput = [[AVAssetReaderTrackOutput alloc] 
        initWithTrack:videoTrack outputSettings:options];
[reader addOutput:videoReaderOutput];
[reader startReading];
// 要確定nominalFrameRate>0,之前出現過android拍的0幀視訊
while ([reader status] == AVAssetReaderStatusReading && videoTrack.nominalFrameRate > 0) {
    // 讀取video sample
    CMSampleBufferRef videoBuffer = [videoReaderOutput copyNextSampleBuffer];
    [m_delegate mMovieDecoder:self onNewVideoFrameReady:videoBuffer);
    CFRelease(videoBuffer);    
    // 根據需要休眠一段時間;比如上層播放視訊時每幀之間是有間隔的
    [NSThread sleepForTimeInterval:sampleInternal];
}
    
// 告訴上層視訊解碼結束
[m_delegate mMovieDecoderOnDecodeFinished:self];      

另一個是MVideoPlayerView,負責視訊的顯示,它接收MMovieDecoder回調的CMSampleBufferRef後,把它轉為CGImageRef,然後設定layer.contents為這個CGImageRef對象。建立CGImageRef不會做圖檔資料的記憶體拷貝,它隻會當Core Animation執行Transaction::commit()觸發layer -display時,才把圖檔資料拷貝到layer buffer裡。

AVAssetReader也能decode音頻的SampleBuffer,不過本人還沒想到如何播放CMSampleBufferRef的音頻,目前隻能靜音播放。

4. 方案對比

對方案二、三做了滑動性能對比和耗電對比,測試條件分别是

滑動:在iPhone4的聊天視窗,有30個小視訊,來回做4次清單滑動

耗電:在iPhone5s,螢幕亮度調到最大,禁止自動鎖屏,開啟飛行模式,聊天視窗同時播放着3個小視訊,10分鐘

方案三無論滑動性能和耗電均優于方案二,由于方案三隻能靜音播放,是以方案三用于聊天視窗和朋友圈清單播放,方案二用于點選放大時的有聲播放。

小視訊錄制需求

  1. 支援白平衡、對焦、縮放
  2. 錄制視訊長度6秒,30幀/秒,盡量不丢幀
  3. 能錄制不同尺寸和碼率的視訊

小視訊錄制方案

對于需求1,AVFoundation有API可以支援,這裡不多說。這裡重點說說需求2、3的實作方案。

前期錄制方案如下:

  1. 建立AVCaptureSession,設定拍攝分辨率
  2. 添加AVCaptureInput,如攝像頭和麥克風
  3. 添加AVCaptureOutput,如AVCaptureVideoDataOutput、AVCaptureAudioDataOutput。這裡AVCaptureAudioDataOutput建議在Session -startRunning後才添加,避免影響攝像頭啟動時間
  4. 添加AVCaptureVideoPreviewLayer,為使用者提供拍攝預覽界面
  5. 建立MMovieWriter,裡面包含AVAssetWriter對象,用于寫視訊
  6. 開始捕捉-startRunning
  7. AVCaptureVideoDataOutput和AVCaptureAudioDataOutput不停地往MMovieWriter傳遞VideoSampleBuffer和AudioSampleBuffer,MMovieWriter對VideoSampleBuffer做分辨率壓縮,以及對AudioSampleBuffer做碼率壓縮
  8. 結束捕捉-stopRunning,MMovieWriter停止寫視訊,把生成的視訊檔案抛給上層

在4s以上的裝置拍攝小視訊挺流暢,幀率能達到要求。但是在iPhone4,錄制的時候特别卡,錄到的視訊隻有6~8幀/秒。嘗試把錄制視訊時的界面動畫去掉,稍微流暢些,幀率多了3~4幀/秒,還是不滿足需求。通過Instrument檢測,發現跟寫音頻時的壓縮有關,寫音頻時阻塞了AVFoundation的線程,引起後續的丢幀。網上也有人回報類似問題 http://stackoverflow.com/questions/16686076/performance-issues-with-avassetwriterinput-audio-and-single-core-devices。把寫音頻去掉後,幀率果然上去了。但是系統相機的拍攝視訊是非常流暢的。于是用AVCaptureMovieFileOutput(640*480)直接生成視訊檔案,拍視訊很流暢。然而錄制的6s視訊大小有2M+,再用MMovieDecoder+MMovieWriter壓縮至少要7~8s,影響聊天視窗發小視訊的速度。

綜上所述,要想拍視訊不卡,就要在錄制過程中盡量不做CPU耗時操作,而且AVCaptureOutput傳遞資料給上層時不能卡住AV線程。最終想到個方案,加個Cache層,先把AVCaptureOutput傳遞的SampleBuffer緩存下來,不在AV的線程寫視訊;等CPU空閑時,再喚起movieWriter線程寫視訊。流程如下圖所示:

通過這樣處理,拍視訊流暢度跟系統相機接近了,隻是剛拍的前1s幀數隻有18幀,後面穩定到30幀/秒左右了。而且使用者松手拍完後,最多等1s就能把視訊寫完檔案了;也優化了之前的視訊截圖生成接口,減少200ms。不過拍攝穩定性不夠好,經常出現下面的寫失敗錯誤,頻率大概是6次/100次:

[GL] <mmoviewriter.mm:476::-[mmoviewriter> INFO: audio writer status 3, desc Error Domain=AVFoundationErrorDomain Code=-11800 "這項操作無法完成" UserInfo=0x11495910 {NSLocalizedDescription=這項操作無法完成, NSUnderlyingError=0x1146e8d0 "The operation couldn’t be completed. (OSStatus error -12633.)", NSLocalizedFailureReason=發生未知錯誤(-12633)}
           

通過google搜尋,網上說這錯誤原因是同一個FrameTime寫入了兩幀。但是FrameTime是從SampleBuffer裡取的,理論上不會時間重合(我沒打log驗證);而且老方案沒出現這種錯誤,新方案延後處理才會出現的。經過多次試驗,把Buffer Cache設定上限,當Buffer數達到一定數量後強制讓MovieWriter寫入檔案,同時把下面這行代碼注釋,錯誤不再出現了:

方案對比:

在iPhone4聊天視窗拍攝若幹個6s視訊10次,算平均值

轉載于:https://www.cnblogs.com/linganxiong/p/7580528.html