AVAssetWriter介紹
可以通過AVAssetWriter來對媒體樣本重新做編碼。
針對一個視訊檔案,只可以使用一個AVAssetWriter來寫入,所以每一個檔案都需要對應一個新的AVAssetWriter例項。
AVAssetWriter初始化
使用一個視訊檔案路徑對AVAssetReader進行初始化,並指定檔案型別。
NSError * error;
_mAssetWriter = [[AVAssetWriter alloc] initWithURL:videoUrl fileType:AVFileTypeAppleM4V error:&error];
AVAssetWriter設定Input
在寫入之前,需要設定Input,與AVAssetReader的Output一樣,也可以設定AVAssetWriterInput輸入的型別為AVMediaTypeAudio或者AVMediaTypeVideo,以下設定以AVMediaTypeVideo為例。
在設定Input時可以指定output設定,這個設定裡主要包含視訊引數。
AVVideoCompressionPropertiesKey對應的屬性值是編碼相關的,比如一下引數:
- AVVideoAverageBitRateKey:視訊尺寸*比率,10.1相當於AVCaptureSessionPresetHigh,數值越大,顯示越精細(只支援H.264)。
- AVVideoMaxKeyFrameIntervalKey:關鍵幀最大間隔,若設定1每幀都是關鍵幀,數值越大壓縮率越高(只支援H.264)。
- AVVideoProfileLevelKey:畫質級別,與裝置相關。
- P-Baseline Profile:基本畫質。支援I/P 幀,只支援無交錯(Progressive)和CAVLC;
- EP-Extended profile:進階畫質。支援I/P/B/SP/SI 幀,只支援無交錯(Progressive)和CAVLC;
- MP-Main profile:主流畫質。提供I/P/B 幀,支援無交錯(Progressive)和交(Interlaced),也支援CAVLC 和CABAC 的支援;
- HP-High profile:高階畫質。在main Profile 的基礎上增加了8×8內部預測、自定義量化、 無損視訊編碼和更多的YUV 格式;
AVVideoCodecKey:視訊的編碼方式,這裡設定為H.264.
AVVideoWidthKey, AVVideoHeightKey:視訊的寬高。
更多的設定可以參考文件:Video Settings | Apple Developer Documentation
NSDictionary *codec_settings = @{AVVideoAverageBitRateKey: @(_bitRate)};
NSDictionary *video_settings = @{AVVideoCodecKey: AVVideoCodecH264,
AVVideoCompressionPropertiesKey: codec_settings,
AVVideoWidthKey: @(1920),
AVVideoHeightKey: @(1080)};
_mAssetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:video_settings];
針對AVAssetWriterInput還可以設定相應的AVAssetWriterInputPixelBufferAdaptor來接收CVPixelBuffer。
AVAssetWriterInputPixelBufferAdaptor提供了一個CVPixelBufferPoolRef,您可以使用它來分配用於寫入輸出檔案的畫素緩衝區。文件中寫到使用提供的畫素緩衝池進行緩衝區分配通常比附加使用單獨池分配的畫素緩衝區更有效。
初始化的時候可以設定相關的引數,比如CVPixelBuffer的顏色格式,CPU和GPU的記憶體共享方式等。
CVPixelBuffer可以由AVAssetWriterInputPixelBufferAdaptor提供的緩衝池建立。
CVOpenGLESTextureCacheRef建立一塊專門用於存放紋理的緩衝區,這樣每次傳遞紋理畫素資料給GPU時,直接使用這個緩衝區中的記憶體,避免了重複建立,提高了效率。
NSMutableDictionary * attributes = [NSMutableDictionary dictionary];
attributes[(NSString *) kCVPixelBufferPixelFormatTypeKey] = @(kCVPixelFormatType_32BGRA);
NSDictionary *IOSurface_properties = @{@"IOSurfaceOpenGLESFBOCompatibility": @YES, @"IOSurfaceOpenGLESTextureCompatibility": @YES};
attributes[(NSString *) kCVPixelBufferIOSurfacePropertiesKey] = IOSurface_properties;
_mAssetWriterPixelBufferInput = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:_mAssetWriterInput
sourcePixelBufferAttributes:attributes];
CVPixelBufferRef renderTarget;
CVOpenGLESTextureCacheRef videoTextureCache;
CVReturn err;
if (videoTextureCache == NULL) {
err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, [EAGLContext currentContext], NULL, & videoTextureCache);
if (err) {
//錯誤處理
}
}
err = CVPixelBufferPoolCreatePixelBuffer (NULL, [_mAssetWriterPixelBufferInput pixelBufferPool], &renderTarget);
if (err) {
//錯誤處理
}
//對CVPixelBuffer新增附加資訊,做顏色格式的轉化
CVBufferSetAttachment(renderTarget,
kCVImageBufferColorPrimariesKey,
kCVImageBufferColorPrimaries_ITU_R_709_2,
kCVAttachmentMode_ShouldPropagate);
CVBufferSetAttachment(renderTarget,
kCVImageBufferYCbCrMatrixKey,
kCVImageBufferYCbCrMatrix_ITU_R_601_4,
kCVAttachmentMode_ShouldPropagate);
CVBufferSetAttachment(renderTarget,
kCVImageBufferTransferFunctionKey,
kCVImageBufferTransferFunction_ITU_R_709_2,
kCVAttachmentMode_ShouldPropagate);
從CVPixelBuffer建立OpenGL的texture,會將renderTarget中的畫素資料傳輸給OpenGL,可以在該texture上的繪製再編碼進檔案中。
CVOpenGLESTextureRef renderTexture;
err = CVOpenGLESTextureCacheCreateTextureFromImage (kCFAllocatorDefault,
videoTextureCache,
renderTarget,
NULL,
GL_TEXTURE_2D,
GL_RGBA,
[1920],
[1080],
GL_BGRA,
GL_UNSIGNED_BYTE,
0,
& renderTexture);
在寫入之前設定好Input,之後呼叫startWriting方法。
if ([_mAssetWriter canAddInput:_mAssetWriterInput]){
[_mAssetWriter addInput:_mAssetWriterInput];
}
[_mAssetWriter startWriting];
[_mAssetWriter startSessionAtSourceTime:kCMTimeZero];
資料寫入
以AVAssetReader讀取的sampleBuffer作為輸入源來做資料寫入,需要處理的異常情況也比較多,注意writer的狀態處理。
程式碼示例
//判斷input是否準備好接受新的資料
while (_mAssetWriterInput.isReadyForMoreMediaData)
{
CMSampleBufferRef sampleBuffer = [output copyNextSampleBuffer];
if (sampleBuffer)
{
BOOL error = NO;
if (_reader.status != AVAssetReaderStatusReading || _writer.status != AVAssetWriterStatusWriting)
{
error = YES;
}
if (_videoOutput == output)
{
// update the video progress
_lastSamplePresentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
if (![_mAssetWriterPixelBufferInput appendPixelBuffer:pixelBuffer withPresentationTime:_lastSamplePresentationTime])
{
error = YES;
}
dispatch_async(dispatch_get_main_queue(), ^{
_progress(CMTimeGetSeconds(_lastSamplePresentationTime) / _duration * 0.8);
});
}
if (error){
return NO;
}
}
else
{
//資料寫入完成,標記Input結束
[_mAssetWriterInput markAsFinished];
return NO;
}
}