[英]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: ¶meterSetPointer, parameterSetSizeOut: ¶meterSetLength, 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.