简体   繁体   English

我们如何从 iPhone Camera 中获取 H.264 编码的视频 stream?

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

I am using following to get video sample buffer:我正在使用以下方法获取视频样本缓冲区:

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

Now my question is that how can I get h.264 encoded NSData from above sampleBuffer .现在我的问题是如何从sampleBuffer上方获取 h.264 编码的 NSData 。 Please suggest.请建议。

You can only get raw video images in either BGRA or YUV color formats from AVFoundation. 您只能从AVFoundation获取BGRA或YUV颜色格式的原始视频图像。 However, when you write those frames to an mp4 via AVAssetWriter, they will be encoded using H264 encoding. 但是,当您通过AVAssetWriter将这些帧写入mp4时,它们将使用H264编码进行编码。

A good example with code on how to do that is RosyWriter 关于如何做到这一点的代码的一个很好的例子是RosyWriter

Note that after each AVAssetWriter write, you will know that one complete H264 NAL was written to a mp4. 请注意,在每次AVAssetWriter写入之后,您将知道已将一个完整的H264 NAL写入mp4。 You could write code that reads a complete H264 NAL after each write by AVAssetWriter, which is going to give you access to an H264 encoded frame. 您可以编写在AVAssetWriter每次写入后读取完整H264 NAL的代码,这将使您可以访问H264编码的帧。 It might take a bit to get it right with decent speed, but it is doable( I did it successfully). 可能需要一点时间才能以合适的速度完成它,但它是可行的(我做得很成功)。

By the way, in order to successfully decode these encoded video frames, you will need H264 SPS and PPS information which is located in a different place in the mp4 file. 顺便说一句,为了成功解码这些编码的视频帧,您将需要位于mp4文件中不同位置的H264 SPS和PPS信息。 In my case, I actually create couple of test mp4 files, and then manually extracted those out. 在我的情况下,我实际上创建了几个测试mp4文件,然后手动提取出来。 Since those don't change, unless you change the H264 encoded specs, you can use them in your code. 由于这些不会更改,除非您更改H264编码的规范,否则您可以在代码中使用它们。

Check my post to SPS values for H 264 stream in iPhone to see some of the SPS/PPS I used in my code. 在iPhone中检查我的帖子到H 264流的SPS值,看看我在代码中使用的一些SPS / PPS。

Just a final note, in my case I had to stream h264 encoded frames to another endpoint for decoding/viewing; 最后一点,在我的情况下,我不得不将h264编码的帧流式传输到另一个端点进行解码/查看; so my code had to do this fast. 所以我的代码必须快速完成。 In my case, it was relatively fast; 就我而言,它相对较快; but eventually I switched to VP8 for encoding/decoding just because it was way faster because everything was done in memory without file reading/writing. 但最终我切换到VP8进行编码/解码只是因为速度更快,因为一切都是在没有文件读/写的情况下在内存中完成的。

Good luck, and hopefully this info helps. 祝你好运,希望这些信息有所帮助。

Update for 2017: 2017年更新:

You can do streaming Video and Audio now by using the VideoToolbox API. 您现在可以使用VideoToolbox API进行视频和音频流式传输。 Read the documentation here: VTCompressionSession 阅读此处的文档: VTCompressionSession

Original answer (from 2013): 原始答案(自2013年起):

Short: You can't, the sample buffer you receive is uncompressed. 简短:您不能,您收到的样本缓冲区是未压缩的。

Methods to get hardware accelerated h264 compression: 获得硬件加速h264压缩的方法:

As you can see both write to a file, writing to a pipe does not work as the encoder updates header information after a frame or GOP has been fully written. 正如您可以看到写入文件一样,写入管道不起作用,因为编码器在完全写入帧或GOP后更新标头信息。 So you better don't touch the file while the encoder writes to it as it does randomly rewrite header information. 因此,当编码器写入文件时,最好不要触摸文件,因为它会随机重写标题信息。 Without this header information the video file will not be playable (it updates the size field, so the first header written says the file is 0 bytes). 如果没有此标头信息,视频文件将无法播放(它会更新大小字段,因此写入的第一个标头表示文件为0字节)。 Directly writing to a memory area is not supported currently. 目前不支持直接写入存储区。 But you can open the encoded video-file and demux the stream to get to the h264 data ( after the encoder has closed the file of course) 但是你可以打开编码的视频文件与多路分流去的H264数据(编码器已经关闭过程的文件

Use VideoToolbox API. refer: https://developer.apple.com/videos/play/wwdc2014/513/使用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