簡體   English   中英

我們如何從 iPhone Camera 中獲取 H.264 編碼的視頻 stream?

[英]How can we get H.264 encoded video stream from iPhone Camera?

我正在使用以下方法獲取視頻樣本緩沖區:

- (void) writeSampleBufferStream:(CMSampleBufferRef)sampleBuffer ofType:(NSString *)mediaType

現在我的問題是如何從sampleBuffer上方獲取 h.264 編碼的 NSData 。 請建議。

您只能從AVFoundation獲取BGRA或YUV顏色格式的原始視頻圖像。 但是,當您通過AVAssetWriter將這些幀寫入mp4時,它們將使用H264編碼進行編碼。

關於如何做到這一點的代碼的一個很好的例子是RosyWriter

請注意,在每次AVAssetWriter寫入之后,您將知道已將一個完整的H264 NAL寫入mp4。 您可以編寫在AVAssetWriter每次寫入后讀取完整H264 NAL的代碼,這將使您可以訪問H264編碼的幀。 可能需要一點時間才能以合適的速度完成它,但它是可行的(我做得很成功)。

順便說一句,為了成功解碼這些編碼的視頻幀,您將需要位於mp4文件中不同位置的H264 SPS和PPS信息。 在我的情況下,我實際上創建了幾個測試mp4文件,然后手動提取出來。 由於這些不會更改,除非您更改H264編碼的規范,否則您可以在代碼中使用它們。

在iPhone中檢查我的帖子到H 264流的SPS值,看看我在代碼中使用的一些SPS / PPS。

最后一點,在我的情況下,我不得不將h264編碼的幀流式傳輸到另一個端點進行解碼/查看; 所以我的代碼必須快速完成。 就我而言,它相對較快; 但最終我切換到VP8進行編碼/解碼只是因為速度更快,因為一切都是在沒有文件讀/寫的情況下在內存中完成的。

祝你好運,希望這些信息有所幫助。

2017年更新:

您現在可以使用VideoToolbox API進行視頻和音頻流式傳輸。 閱讀此處的文檔: VTCompressionSession

原始答案(自2013年起):

簡短:您不能,您收到的樣本緩沖區是未壓縮的。

獲得硬件加速h264壓縮的方法:

正如您可以看到寫入文件一樣,寫入管道不起作用,因為編碼器在完全寫入幀或GOP后更新標頭信息。 因此,當編碼器寫入文件時,最好不要觸摸文件,因為它會隨機重寫標題信息。 如果沒有此標頭信息,視頻文件將無法播放(它會更新大小字段,因此寫入的第一個標頭表示文件為0字節)。 目前不支持直接寫入存儲區。 但是你可以打開編碼的視頻文件與多路分流去的H264數據(編碼器已經關閉過程的文件

使用VideoToolbox API。參考: https://developer.apple.com/videos/play/wwdc2014/513/

import Foundation
import AVFoundation
import VideoToolbox

public class LiveStreamSession {
    
    let compressionSession: VTCompressionSession
        
    var index = -1
    
    var lastInputPTS = CMTime.zero
    
    public init?(width: Int32, height: Int32){
        var compressionSessionOrNil: VTCompressionSession? = nil
        let status = VTCompressionSessionCreate(allocator: kCFAllocatorDefault,
                                                width: width,
                                                height: height,
                                                codecType: kCMVideoCodecType_H264,
                                                encoderSpecification: nil, // let the video toolbox choose a encoder
                                                imageBufferAttributes: nil,
                                                compressedDataAllocator: kCFAllocatorDefault,
                                                outputCallback: nil,
                                                refcon: nil,
                                                compressionSessionOut: &compressionSessionOrNil)
        guard status == noErr,
            let compressionSession = compressionSessionOrNil else {
            return nil
        }
        VTSessionSetProperty(compressionSession, key: kVTCompressionPropertyKey_RealTime, value: kCFBooleanTrue);
        VTCompressionSessionPrepareToEncodeFrames(compressionSession)
        
        self.compressionSession = compressionSession
        
    }
    
    public func pushVideoBuffer(buffer: CMSampleBuffer) {
        // image buffer
        guard let imageBuffer = CMSampleBufferGetImageBuffer(buffer) else {
            assertionFailure()
            return
        }
        
        // pts
        let pts = CMSampleBufferGetPresentationTimeStamp(buffer)
        guard CMTIME_IS_VALID(pts) else {
            assertionFailure()
            return
        }
        
        // duration
        var duration = CMSampleBufferGetDuration(buffer);
        if CMTIME_IS_INVALID(duration) && CMTIME_IS_VALID(self.lastInputPTS) {
            duration = CMTimeSubtract(pts, self.lastInputPTS)
        }
                
        index += 1
        self.lastInputPTS = pts
        print("[\(Date())]: pushVideoBuffer \(index)")
        
        let currentIndex = index
        VTCompressionSessionEncodeFrame(compressionSession, imageBuffer: imageBuffer, presentationTimeStamp: pts, duration: duration, frameProperties: nil, infoFlagsOut: nil) {[weak self] status, encodeInfoFlags, sampleBuffer in
            print("[\(Date())]: compressed \(currentIndex)")
            if let sampleBuffer = sampleBuffer {
                self?.didEncodeFrameBuffer(buffer: sampleBuffer, id: currentIndex)
            }
        }
    }
    
    deinit {
        VTCompressionSessionInvalidate(compressionSession)
    }
    
    private func didEncodeFrameBuffer(buffer: CMSampleBuffer, id: Int) {
        guard let attachments = CMSampleBufferGetSampleAttachmentsArray(buffer, createIfNecessary: true)
               else {
            return
        }
        let dic = Unmanaged<CFDictionary>.fromOpaque(CFArrayGetValueAtIndex(attachments, 0)).takeUnretainedValue()
        let keyframe = !CFDictionaryContainsKey(dic, Unmanaged.passRetained(kCMSampleAttachmentKey_NotSync).toOpaque())
//        print("[\(Date())]: didEncodeFrameBuffer \(id) is I frame: \(keyframe)")
        if keyframe,
           let formatDescription = CMSampleBufferGetFormatDescription(buffer) {
            // https://www.slideshare.net/instinctools_EE_Labs/videostream-compression-in-ios
            var number = 0
            CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDescription, parameterSetIndex: 0, parameterSetPointerOut: nil, parameterSetSizeOut: nil, parameterSetCountOut: &number, nalUnitHeaderLengthOut: nil)
            // SPS and PPS and so on...
            let parameterSets = NSMutableData()
            for index in 0 ... number - 1 {
                var parameterSetPointer: UnsafePointer<UInt8>?
                var parameterSetLength = 0
                CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDescription, parameterSetIndex: index, parameterSetPointerOut: &parameterSetPointer, parameterSetSizeOut: &parameterSetLength, parameterSetCountOut: nil, nalUnitHeaderLengthOut: nil)
//                parameterSets.append(startCode, length: startCodeLength)
                if let parameterSetPointer = parameterSetPointer {
                    parameterSets.append(parameterSetPointer, length: parameterSetLength)
                }
                
                //
                if index == 0 {
                    print("SPS is \(parameterSetPointer) with length \(parameterSetLength)")
                } else if index == 1 {
                    print("PPS is \(parameterSetPointer) with length \(parameterSetLength)")
                }
            }
            print("[\(Date())]: parameterSets \(parameterSets.length)")
        }
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM