我正在将我的应用内容流式传输到我的 RTMP 服务器并使用 RPBroadcastSampleHandler。
其中一种方法是
override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
switch sampleBufferType {
case .video:
streamer.appendSampleBuffer(sampleBuffer, withType: .video)
captureOutput(sampleBuffer)
case .audioApp:
streamer.appendSampleBuffer(sampleBuffer, withType: .audio)
captureAudioOutput(sampleBuffer)
case .audioMic:
()
}
}
captureOutput 方法是
self.lastSampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
// Append the sampleBuffer into videoWriterInput
if self.isRecordingVideo {
if self.videoWriterInput!.isReadyForMoreMediaData {
if self.videoWriter!.status == AVAssetWriterStatus.writing {
let whetherAppendSampleBuffer = self.videoWriterInput!.append(sampleBuffer)
print(">>>>>>>>>>>>>The time::: \(self.lastSampleTime.value)/\(self.lastSampleTime.timescale)")
if whetherAppendSampleBuffer {
print("DEBUG::: Append sample buffer successfully")
} else {
print("WARN::: Append sample buffer failed")
}
} else {
print("WARN:::The videoWriter status is not writing")
}
} else {
print("WARN:::Cannot append sample buffer into videoWriterInput")
}
}
由于这个示例缓冲区包含音频/视频数据,我想我可以在流式传输时使用 AVKit 将其保存在本地。所以我正在做的是在流的开头创建一个 Assets 编写器:
let fileManager = FileManager.default
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
self.videoOutputFullFileName = documentsPath.stringByAppendingPathComponent(str: "test_capture_video.mp4")
if self.videoOutputFullFileName == nil {
print("ERROR:The video output file name is nil")
return
}
self.isRecordingVideo = true
if fileManager.fileExists(atPath: self.videoOutputFullFileName!) {
print("WARN:::The file: \(self.videoOutputFullFileName!) exists, will delete the existing file")
do {
try fileManager.removeItem(atPath: self.videoOutputFullFileName!)
} catch let error as NSError {
print("WARN:::Cannot delete existing file: \(self.videoOutputFullFileName!), error: \(error.debugDescription)")
}
} else {
print("DEBUG:::The file \(self.videoOutputFullFileName!) doesn't exist")
}
let screen = UIScreen.main
let screenBounds = info.size
let videoCompressionPropertys = [
AVVideoAverageBitRateKey: screenBounds.width * screenBounds.height * 10.1
]
let videoSettings: [String: Any] = [
AVVideoCodecKey: AVVideoCodecH264,
AVVideoWidthKey: screenBounds.width,
AVVideoHeightKey: screenBounds.height,
AVVideoCompressionPropertiesKey: videoCompressionPropertys
]
self.videoWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoSettings)
guard let videoWriterInput = self.videoWriterInput else {
print("ERROR:::No video writer input")
return
}
videoWriterInput.expectsMediaDataInRealTime = true
// Add the audio input
var acl = AudioChannelLayout()
memset(&acl, 0, MemoryLayout<AudioChannelLayout>.size)
acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
let audioOutputSettings: [String: Any] =
[ AVFormatIDKey: kAudioFormatMPEG4AAC,
AVSampleRateKey : 44100,
AVNumberOfChannelsKey : 1,
AVEncoderBitRateKey : 64000,
AVChannelLayoutKey : Data(bytes: &acl, count: MemoryLayout<AudioChannelLayout>.size)]
audioWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeAudio, outputSettings: audioOutputSettings)
guard let audioWriterInput = self.audioWriterInput else {
print("ERROR:::No audio writer input")
return
}
audioWriterInput.expectsMediaDataInRealTime = true
do {
self.videoWriter = try AVAssetWriter(outputURL: URL(fileURLWithPath: self.videoOutputFullFileName!), fileType: AVFileTypeMPEG4)
} catch let error as NSError {
print("ERROR:::::>>>>>>>>>>>>>Cannot init videoWriter, error:\(error.localizedDescription)")
}
guard let videoWriter = self.videoWriter else {
print("ERROR:::No video writer")
return
}
if videoWriter.canAdd(videoWriterInput) {
videoWriter.add(videoWriterInput)
} else {
print("ERROR:::Cannot add videoWriterInput into videoWriter")
}
//Add audio input
if videoWriter.canAdd(audioWriterInput) {
videoWriter.add(audioWriterInput)
} else {
print("ERROR:::Cannot add audioWriterInput into videoWriter")
}
if videoWriter.status != AVAssetWriterStatus.writing {
print("DEBUG::::::::::::::::The videoWriter status is not writing, and will start writing the video.")
let hasStartedWriting = videoWriter.startWriting()
if hasStartedWriting {
videoWriter.startSession(atSourceTime: self.lastSampleTime)
print("DEBUG:::Have started writting on videoWriter, session at source time: \(self.lastSampleTime)")
LOG(videoWriter.status.rawValue)
} else {
print("WARN:::Fail to start writing on videoWriter")
}
} else {
print("WARN:::The videoWriter.status is writing now, so cannot start writing action on videoWriter")
}
然后在流的末尾保存并完成写入:
print("DEBUG::: Starting to process recorder final...")
print("DEBUG::: videoWriter status: \(self.videoWriter!.status.rawValue)")
self.isRecordingVideo = false
guard let videoWriterInput = self.videoWriterInput else {
print("ERROR:::No video writer input")
return
}
guard let videoWriter = self.videoWriter else {
print("ERROR:::No video writer")
return
}
guard let audioWriterInput = self.audioWriterInput else {
print("ERROR:::No audio writer input")
return
}
videoWriterInput.markAsFinished()
audioWriterInput.markAsFinished()
videoWriter.finishWriting {
if videoWriter.status == AVAssetWriterStatus.completed {
print("DEBUG:::The videoWriter status is completed")
let fileManager = FileManager.default
if fileManager.fileExists(atPath: self.videoOutputFullFileName!) {
print("DEBUG:::The file: \(self.videoOutputFullFileName ?? "") has been saved in documents folder, and is ready to be moved to camera roll")
let sharedFileURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.jp.awalker.co.Hotter")
guard let documentsPath = sharedFileURL?.path else {
LOG("ERROR:::No shared file URL path")
return
}
let finalFilename = documentsPath.stringByAppendingPathComponent(str: "test_capture_video.mp4")
//Check whether file exists
if fileManager.fileExists(atPath: finalFilename) {
print("WARN:::The file: \(finalFilename) exists, will delete the existing file")
do {
try fileManager.removeItem(atPath: finalFilename)
} catch let error as NSError {
print("WARN:::Cannot delete existing file: \(finalFilename), error: \(error.debugDescription)")
}
} else {
print("DEBUG:::The file \(self.videoOutputFullFileName!) doesn't exist")
}
do {
try fileManager.copyItem(at: URL(fileURLWithPath: self.videoOutputFullFileName!), to: URL(fileURLWithPath: finalFilename))
}
catch let error as NSError {
LOG("ERROR:::\(error.debugDescription)")
}
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: finalFilename))
}) { completed, error in
if completed {
print("Video \(self.videoOutputFullFileName ?? "") has been moved to camera roll")
}
if error != nil {
print ("ERROR:::Cannot move the video \(self.videoOutputFullFileName ?? "") to camera roll, error: \(error!.localizedDescription)")
}
}
} else {
print("ERROR:::The file: \(self.videoOutputFullFileName ?? "") doesn't exist, so can't move this file camera roll")
}
} else {
print("WARN:::The videoWriter status is not completed, stauts: \(videoWriter.status)")
}
}
我遇到的问题是从未达到 finishWriting 完成代码。编写器处于“写入”状态,因此视频文件未保存。
如果我删除“finishWriting”行并让完成代码继续运行,则会保存一个文件,但没有正确完成,当我试图查看它时它无法播放,因为它可能缺少元数据。
还有其他方法吗?我不想真正开始使用 AVKit 进行捕获以保存录制内容,因为它占用了过多的 CPU,并且 RPBroadcastSampleHandler 的 CMSampleBuffer 已经有了视频数据,但在这里使用 AVKit 可能是一个错误的举动?
我应该改变什么?如何从该 CMSampleBuffer 保存视频?
最佳答案
此方法立即返回并使其工作异步执行
当 broadcastFinished 返回时,您的扩展被终止。我能够让它工作的唯一方法是阻止该方法返回,直到视频处理完成。我不确定这是否是正确的方法(看起来很奇怪),但它确实有效。像这样:
var finishedWriting = false
videoWriter.finishWriting {
NSLog("DEBUG:::The videoWriter finished writing.")
if videoWriter.status == .completed {
NSLog("DEBUG:::The videoWriter status is completed")
let fileManager = FileManager.default
if fileManager.fileExists(atPath: self.videoOutputFullFileName!) {
NSLog("DEBUG:::The file: \(self.videoOutputFullFileName ?? "") has been saved in documents folder, and is ready to be moved to camera roll")
let sharedFileURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.xxx.com")
guard let documentsPath = sharedFileURL?.path else {
NSLog("ERROR:::No shared file URL path")
finishedWriting = true
return
}
let finalFilename = documentsPath + "/test_capture_video.mp4"
//Check whether file exists
if fileManager.fileExists(atPath: finalFilename) {
NSLog("WARN:::The file: \(finalFilename) exists, will delete the existing file")
do {
try fileManager.removeItem(atPath: finalFilename)
} catch let error as NSError {
NSLog("WARN:::Cannot delete existing file: \(finalFilename), error: \(error.debugDescription)")
}
} else {
NSLog("DEBUG:::The file \(self.videoOutputFullFileName!) doesn't exist")
}
do {
try fileManager.copyItem(at: URL(fileURLWithPath: self.videoOutputFullFileName!), to: URL(fileURLWithPath: finalFilename))
}
catch let error as NSError {
NSLog("ERROR:::\(error.debugDescription)")
}
PHPhotoLibrary.shared().performChanges({
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: "xxx")
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: finalFilename))
}) { completed, error in
if completed {
NSLog("Video \(self.videoOutputFullFileName ?? "") has been moved to camera roll")
}
if error != nil {
NSLog("ERROR:::Cannot move the video \(self.videoOutputFullFileName ?? "") to camera roll, error: \(error!.localizedDescription)")
}
finishedWriting = true
}
} else {
NSLog("ERROR:::The file: \(self.videoOutputFullFileName ?? "") doesn't exist, so can't move this file camera roll")
finishedWriting = true
}
} else {
NSLog("WARN:::The videoWriter status is not completed, status: \(videoWriter.status)")
finishedWriting = true
}
}
while finishedWriting == false {
// NSLog("DEBUG:::Waiting to finish writing...")
}
我认为您还必须在某些时候调用 extensionContext.completeRequest,但没有它我的工作正常耸耸肩。
关于ios - 使用 ReplayKit 流式传输时从 CMSampleBuffer 保存视频,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46903118/
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po