Avassetreader Startreading Cannot Be Called Again After Reading Has Already Started
[Swift] Using AVAssetWriter to shrink video files for network transfer
To quote someone from stack overflow, y'all pretty much need a PHD to use AVAssetWriter. This is unfortunate because you can do some pretty useful things with it similar compressing your videos to be super quick for network transfer. In this post, I will walk you lot through a compression office and effort to create a picture about how everything works together. Information technology's not that scary if you take it a piece at a time.
We are going to create a function called func compressVideo(videoURL:URL, outputUrl:URL callback)
which volition simply compress a video file already located at a file system URL. This video file should be recorded with AVFoundation simply for this post I will assume you accept already washed that.
To shrink this file, we are going to use a class chosen AVAssetWriter
which basically creates an sound or video file at a specified destination on the devices filesystem while giving us complete control over the asset's settings such as size, file type, color grading, frame rate, compression, and more than.
Before we get into information technology, permit me give you an outline of the classes we will be using in this mail service:
AVAssetWriter — Writes an sound or video file at a specified destination on the devices filesystem while giving us complete control over the asset's settings such equally size, file type, colour grading, frame rate, compression, and more.
AVAssetWriterInput — A writer class takes multiple inputs that can be of different AVMediaTypes
. In this postal service, we will have one for the video and one for the audio.
AVAssetReader — This is how nosotros go the video data from the bodily file and feed it to the AVAssetReader.
AVAssetReaderTrackOutput — We create asset reader outputs for both audio and video and then the asset reader knows what to read from the files.
AVAsset — This is how AV Foundation handles files. We will read the file from the url into an AVAsset and then AV Foundation knows how to utilize it.
AVAssetTrack — An asset is broken down into tracks. We will just have 2 for our purposes: one for video and one for sound.
Allow'southward get started. Outset, we will create the role and some class properties exterior the office to agree our AVAssetReader
and AVAssetWriter
. We volition besides create a property to hold the bitrate of the video which will determine the amount of pinch the video has.
var assetWriter:AVAssetWriter?
var assetReader:AVAssetReader?
let bitrate:NSNumber = NSNumber(value:250000) func compressFile(urlToCompress: URL, outputURL: URL, completion:@escaping (URL)->Void){ //... lawmaking .... }
This role takes the url of the file to shrink, a link to an empty URL to write the newly compressed video to, and a completion block that returns goose egg simply contains the finished video URL.
//...code...
var audioFinished = faux
var videoFinished = false let asset = AVAsset(url: urlToCompress); //create asset reader
practice{
assetReader = try AVAssetReader(nugget: asset)
} grab{
assetReader = nil
} guard permit reader = assetReader else{
fatalError("Could non initalize asset reader probably failed its try take hold of")
} //...code...
Here we are creating ii bool variables to tell usa if the audio and video processing is finished and so we know when to finish writing to the file.
On the side by side line nosotros use the url to our video file to create an AVAsset. This is only putting the video in a format that AVFoundation can work with.
After that, we are creating our AVAssetReader with some fault checking.
//...lawmaking...
let videoTrack = nugget.tracks(withMediaType: AVMediaTypeVideo).kickoff!
permit audioTrack = asset.tracks(withMediaType: AVMediaTypeAudio).offset!
//...lawmaking...
Now we need to access the tracks from the AVAsset we accept just created.Since this is just simple video recording, we tin but assume there is but one audio and video track.
//...code...
let videoReaderSettings: [String:Any] = [kCVPixelBufferPixelFormatTypeKey as Cord!:kCVPixelFormatType_32ARGB ] // ADJUST Flake Charge per unit OF VIDEO Here let videoSettings:[String:Any] = [
AVVideoCompressionPropertiesKey: [AVVideoAverageBitRateKey:self.bitrate],
AVVideoCodecKey: AVVideoCodecH264,
AVVideoHeightKey: videoTrack.naturalSize.height,
AVVideoWidthKey: videoTrack.naturalSize.width
]
//...code...
This part is very of import. Here is where nosotros tell the AVAssetWriter
and AVAssetReader
what settings nosotros want for our output video. The pinch of the video is determined by the bitrate and then nosotros volition use the course holding we created before to add the amount of chip charge per unit nosotros want on the video. You should change this property for yourself based on how much compression vs quality you desire. Hither is the link to the settings you lot can apply to your nugget writer: Video Settings.
//...code...
let assetReaderVideoOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: videoReaderSettings)
let assetReaderAudioOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: nil) if reader.canAdd(assetReaderVideoOutput){
reader.add(assetReaderVideoOutput)
}else{
fatalError("Couldn't add video output reader")
} if reader.canAdd(assetReaderAudioOutput){
reader.add together(assetReaderAudioOutput)
}else{
fatalError("Couldn't add together audio output reader")
}
//...code...
Now we volition create our AVAssetReaderTrackOutput
for both audio and video. We requite them each their respective tracks and apply the video settings we ready here.
So with some simple error handling nosotros brand sure we are able to add the tracks.
//...code...
allow audioInput = AVAssetWriterInput(mediaType: AVMediaTypeAudio, outputSettings: nil)
let videoInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoSettings)
videoInput.transform = videoTrack.preferredTransform
//...lawmaking...
Here we are creating the AVAssetWriterInput
classes that will be passed the buffer information from the asset reader (this is like the data from the video and sound) and setting the media types and video settings.
The last line is important here. We demand to set the transform of the video input as the aforementioned orientation as the video rail otherwise it will default to being horizontal and we desire the footage to be upright like it was recorded on the device so we will give it the same information that the track has.
//...code...
let videoInputQueue = DispatchQueue(characterization: "videoQueue")
let audioInputQueue = DispatchQueue(label: "audioQueue") practice{
assetWriter = endeavor AVAssetWriter(outputURL: outputURL, fileType: AVFileTypeQuickTimeMovie)
}grab{
assetWriter = nothing
}
guard let writer = assetWriter else{
fatalError("assetWriter was nil")
}
//...lawmaking...
We demand to do two writes of information; one for the audio and one for the video. These need to happen at the same time so we demand to create two dispatch queues for each outcome to happen on. Next we are creating the asset writer.
Almost there…
//...code...
writer.shouldOptimizeForNetworkUse = truthful
writer.add(videoInput)
writer.add(audioInput) writer.startWriting()
reader.startReading()
writer.startSession(atSourceTime: kCMTimeZero)
//...code...
We are now fix to start writing. Get-go we add together the video inputs to the writer and set shouldOptimizeForNetworkUse
to true. (Pretty self explanatory). Next we start writing on both the reader and writer then set the time that the writer should showtime at. This volition just be the beginning and then we use the built in abiding kCMTimeZero
.
//...code...
let closeWriter:()->Void = {
if (audioFinished && videoFinished){
self.assetWriter?.finishWriting(completionHandler: { completion((self.assetWriter?.outputURL)!) }) self.assetReader?.cancelReading() }
}
//...lawmaking...
This is a picayune out of order now but nosotros demand to create a closure which will terminate off the function and telephone call the completion cake and stop writing to the file and save everything. This will be called later later on all the audio and video writing has been completed.
Very helpful website for swift closures
//...code...
audioInput.requestMediaDataWhenReady(on: audioInputQueue) {
while(audioInput.isReadyForMoreMediaData){
let sample = assetReaderAudioOutput.copyNextSampleBuffer()
if (sample != null){
audioInput.append(sample!)
}else{
audioInput.markAsFinished()
DispatchQueue.main.async {
audioFinished = true
closeWriter()
}
suspension;
}
}
} videoInput.requestMediaDataWhenReady(on: videoInputQueue) {
//asking data hither while(videoInput.isReadyForMoreMediaData){
permit sample = assetReaderVideoOutput.copyNextSampleBuffer()
if (sample != aught){
videoInput.suspend(sample!)
}else{
videoInput.markAsFinished()
DispatchQueue.primary.async {
videoFinished = true
closeWriter()
}
intermission;
}
} }
//...just add i more curly caryatid to finish off the function...
We need to pass buffers to the videoInput from the videoReader. This method will be called over and over when the videoInput is ready for some other buffer (segment of video), We phone call markAsFinished at when at that place are no buffers left and call our closeWriter()
from the previous step.
Basically to compress these files we are adjusting the bitrate and we created a helpful property at the superlative with the bitrate of the video. Just make it lower for more compression and higher for less pinch but improve quality.
Here is the link to the full code. Please feel gratis to tweet or email me of y'all take any questions. Bask!
EDIT: A user institute that they needed to create the output url like this:
I haven't tested this myself by I will include it in case this solves anyone else's issue
let formatter = DateFormatter()
formatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
let date = Appointment()
allow documentsPath = NSTemporaryDirectory()
let outputPath = "\(documentsPath)/\(formatter.string(from: date)).mov"
let newOutputUrl = URL(fileURLWithPath: outputPath)
Source: https://medium.com/samkirkiles/swift-using-avassetwriter-to-compress-video-files-for-network-transfer-4dcc7b4288c5
0 Response to "Avassetreader Startreading Cannot Be Called Again After Reading Has Already Started"
Mag-post ng isang Komento