AVAssetWriter影片數據編碼
- 2021 年 6 月 21 日
- 筆記
- AVAssetWriter, AVAssetWriterInput, 影片編碼
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;
}
}


