使用 ReplayKit 广播时将 CMSampleBuffer 转换为 .mov
Posted
技术标签:
【中文标题】使用 ReplayKit 广播时将 CMSampleBuffer 转换为 .mov【英文标题】:Convert CMSampleBuffer to .mov while broadcasting with ReplayKit 【发布时间】:2020-08-19 20:04:50 【问题描述】:请告诉我我的代码有什么问题。我尝试将我的屏幕广播到文件(newFile.mov),然后将它与一些数据一起发送到服务器,但它没有保存,停止捕获后,文件钢为空。
这是我来自 BExtensionUpload 的代码,它捕获广播并将其保存到我获取它并发送到服务器的组中的文件中
import ReplayKit
class SampleHandler: RPBroadcastSampleHandler
let appIdentifier = "com.group.CY"
var videoWriterInput: AVAssetWriterInput!
var audioWriterInput: AVAssetWriterInput!
var videoWriter: AVAssetWriter!
var sessionAtSourceTime: CMTime!
var outputFileLocation: URL?
override func broadcastStarted(withSetupInfo setupInfo: [String : NSObject]?)
do
outputFileLocation = videoFileLocation()
videoWriter = try AVAssetWriter(outputURL: outputFileLocation!, fileType: AVFileType.mov)
videoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: [
AVVideoCodecKey : AVVideoCodecType.h264,
AVVideoWidthKey : 720,
AVVideoHeightKey : 1280,
AVVideoCompressionPropertiesKey : [
AVVideoAverageBitRateKey : 2300000,
],
])
videoWriterInput.expectsMediaDataInRealTime = true
if videoWriter.canAdd(videoWriterInput)
videoWriter.add(videoWriterInput)
else
print("no input added")
audioWriterInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: nil)
audioWriterInput.expectsMediaDataInRealTime = true
if videoWriter.canAdd(audioWriterInput!)
videoWriter.add(audioWriterInput!)
videoWriter.startWriting()
catch let error
debugPrint(error.localizedDescription)
override func broadcastFinished()
super.broadcastFinished()
videoWriterInput.markAsFinished()
videoWriter.finishWriting
()
override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType)
super.processSampleBuffer(sampleBuffer, with: sampleBufferType)
if writable,
sessionAtSourceTime == nil
sessionAtSourceTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
videoWriter.startSession(atSourceTime: sessionAtSourceTime!)
switch sampleBufferType
case .video:
if videoWriterInput.isReadyForMoreMediaData
videoWriterInput.append(sampleBuffer)
case .audioApp:
if audioWriterInput.isReadyForMoreMediaData
audioWriterInput?.append(sampleBuffer)
case .audioMic:
print("mic")
@unknown default:
print("unknown")
func videoFileLocation() -> URL?
let fileManager = FileManager.default
do
if let container = fileManager.containerURL(forSecurityApplicationGroupIdentifier: appIdentifier)
let videoContainer = container.appendingPathComponent("Video")
try? fileManager.createDirectory(at: videoContainer, withIntermediateDirectories: false, attributes: nil)
let videoOutputUrl = videoContainer.appendingPathComponent("newFile").appendingPathExtension("mov")
if fileManager.fileExists(atPath: videoOutputUrl.path)
try fileManager.removeItem(at: videoOutputUrl)
fileManager.createFile(atPath: videoOutputUrl.path, contents: nil, attributes: nil)
return videoOutputUrl
catch
print(error)
return nil
【问题讨论】:
【参考方案1】:此代码适用于我,但除了我在应用程序组中的目录之外,我还必须使用 phphotoLibrary。应用程序组目录不是创建文件的唯一方法,您可以使用任何您想要的目录。我希望它可以帮助某人。
import ReplayKit
import Photos
class SampleHandler: RPBroadcastSampleHandler
let appIdentifier = "group.CY"
let fileManager = FileManager.default
var videoWriterInput: AVAssetWriterInput!
var microphoneWriterInput: AVAssetWriterInput!
var videoWriter: AVAssetWriter!
var sessionBeginAtSourceTime: CMTime!
var isRecording = false
var outputFileLocation: URL!
override func broadcastStarted(withSetupInfo setupInfo: [String : NSObject]?)
guard !isRecording else return
isRecording = true
sessionBeginAtSourceTime = nil
setUpWriter()
func setUpWriter()
let width = UIScreen.main.bounds.width * 2
let height = UIScreen.main.bounds.height * 2
self.outputFileLocation = videoFileLocation()
// Add the video input
videoWriter = try? AVAssetWriter.init(outputURL: self.outputFileLocation, fileType: AVFileType.mp4)
let videoCompressionPropertys = [
AVVideoAverageBitRateKey: width * height * 10.1
]
let videoSettings: [String: Any] = [
AVVideoCodecKey: AVVideoCodecType.h264,
AVVideoWidthKey: width,
AVVideoHeightKey: height,
AVVideoCompressionPropertiesKey: videoCompressionPropertys
]
videoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoSettings)
videoWriterInput.expectsMediaDataInRealTime = true
// Add the microphone 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)]
microphoneWriterInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: audioOutputSettings)
microphoneWriterInput.expectsMediaDataInRealTime = true
if videoWriter.canAdd(videoWriterInput)
videoWriter.add(videoWriterInput)
if videoWriter.canAdd(microphoneWriterInput)
videoWriter.add(microphoneWriterInput)
videoWriter.startWriting()
override func broadcastFinished()
guard isRecording else return
isRecording = false
sessionBeginAtSourceTime = nil
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
videoWriterInput.markAsFinished()
microphoneWriterInput.markAsFinished()
var finishedWriting = false
videoWriter.finishWriting
PHPhotoLibrary.shared().performChanges(
PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: "xxx")
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: self.outputFileLocation)
) completed, error in
if completed
NSLog("Video \(self.outputFileLocation.path ?? "") has been moved to camera roll")
if error != nil
NSLog("ERROR:::Cannot move the video \(self.outputFileLocation.path ?? "") to camera roll, error: \(error!.localizedDescription)")
finishedWriting = true
while finishedWriting == false
// NSLog("DEBUG:::Waiting to finish writing...")
dispatchGroup.leave()
dispatchGroup.wait() // <= blocks the thread here
override func finishBroadcastWithError(_ error: Error)
let e = error
print(e)
override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType)
let writable = canWrite()
if writable, sessionBeginAtSourceTime == nil
sessionBeginAtSourceTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
videoWriter.startSession(atSourceTime: sessionBeginAtSourceTime!)
if writable
switch sampleBufferType
case .video:
if videoWriterInput.isReadyForMoreMediaData
videoWriterInput.append(sampleBuffer)
case .audioApp:
print("audio")
case .audioMic:
if microphoneWriterInput.isReadyForMoreMediaData
microphoneWriterInput.append(sampleBuffer)
@unknown default:
print("unknown")
func videoFileLocation() -> URL
let documentsPath = fileManager.containerURL(forSecurityApplicationGroupIdentifier: appIdentifier)!
let videoOutputUrl = documentsPath
.appendingPathComponent("Library")
.appendingPathComponent("Caches")
.appendingPathComponent("mobile")
.appendingPathExtension("mp4")
do
if fileManager.fileExists(atPath: videoOutputUrl.path)
try fileManager.removeItem(at: videoOutputUrl)
catch print(error)
return videoOutputUrl
func canWrite() -> Bool
return videoWriter != nil && isRecording && videoWriter?.status == .writing
【讨论】:
以上是关于使用 ReplayKit 广播时将 CMSampleBuffer 转换为 .mov的主要内容,如果未能解决你的问题,请参考以下文章