AVAssetWriterInput and readyForMoreMediaData
Asked Answered
P

4

10

Is AVAssetWriterInput's readyForMoreMediaData updated in a background thread? If readyForMoreMediaData is NO, can I block in the main thread and wait until the value changes to YES?

I'm using an AVAssetWriterInput by pushing data to it (i.e. without using requestMediaDataWhenReadyOnQueue) and I've set expectsMediaDataInRealTime, and 99.9% of the time I can just call appendSampleBuffer (or appendPixelBuffer) on it as fast as my app can generate frames.

This works fine unless you put the device (iPhone 3GS) to sleep for 15 minutes or so in the middle of an AVAssetWriter session. After waking up the device, appendPixelBuffer sometimes gets an error saying, "A pixel buffer cannot be appended when readyForMoreMediaData is NO". Hence my question - how best to respond to readyForMoreMediaData=NO and if I can just wait a bit in the main thread like so:

while ( ![assetWriterInput readyForMoreMediaData] )
{
    Sleep for a few milliseconds
}
Papule answered 4/5, 2011 at 0:11 Comment(0)
P
5

Be careful not to just block the thread, here is what I was doing before that was not working:

while (adaptor.assetWriterInput.readyForMoreMediaData == FALSE) {
  [NSThread sleepForTimeInterval:0.1];
}

The above approach would fail sometimes on my iPad2. Doing this instead fixed the problem:

while (adaptor.assetWriterInput.readyForMoreMediaData == FALSE) {
  NSDate *maxDate = [NSDate dateWithTimeIntervalSinceNow:0.1];
  [[NSRunLoop currentRunLoop] runUntilDate:maxDate];
}
Pattipattie answered 22/7, 2012 at 0:11 Comment(5)
Hi @Pattipattie - I might be missing something, but it looks like you're trying to "sleep" 0.1 second in both approaches - but in the second one, you are not sleeping. The code doesn't mean it, and I can see from logs too...Plunkett
Invoking runUntilDate on the run loop is not the same as sleeping. The run loop can do other things, like respond to async IO becoming ready or NSNotification delivery. The point here is that the readyForMoreMediaData flag could be getting set by one of the run loop operations, which seems to be what was going on with the iPad2.Pattipattie
So it's runUntilDate "not including self"? Guess that's what I missedPlunkett
Apple provides some detailed run loop docs: developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/…Pattipattie
I can't believe this is the best practice. And why doesn't KVO work? I tried that as well. Grrr.Versicular
D
3

Don't find something similar, so I left it here. Swift 4 solution. Would better using precise technique to solve that problem. F.e. using NSCondition:

func startRecording() {
        // start recording code goes here 

    readyForMediaCondition = NSCondition()
    readyForMediaObservation = pixelBufferInput?.assetWriterInput.observe(\.isReadyForMoreMediaData, options: .new, changeHandler: { [weak self](_, change) in
        guard let isReady = change.newValue else {
            return
        }

        if isReady {
            self?.readyForMediaCondition?.lock()
            self?.readyForMediaCondition?.signal()
            self?.readyForMediaCondition?.unlock()
        }
    })
}

Next:

func grabFrame(time: CMTime? = nil) {
    readyForMediaCondition?.lock()
    while !pixelBufferInput!.assetWriterInput.isReadyForMoreMediaData {
        readyForMediaCondition?.wait()
    }
    readyForMediaCondition?.unlock()

    // append your framebuffer here
}

Don't forget to invalidate observer in the end

readyForMediaObservation?.invalidate()
Distraught answered 27/3, 2018 at 9:12 Comment(0)
G
1
        int waitTime = 300;
        while (weakSelf.input.readyForMoreMediaData == NO) {
            NSLog(@"readyForMoreMediaData is NO");
            NSTimeInterval waitIntervale = 0.001 * waitTime;
            NSDate *maxDate = [NSDate dateWithTimeIntervalSinceNow:waitIntervale];
            [[NSRunLoop currentRunLoop] runUntilDate:maxDate];
            waitTime += 200; // add 200ms every time
        }
Gui answered 29/9, 2018 at 8:44 Comment(1)
hi jeff, welcome to the community. 1. You should point out where the asker has gone wrong, 2. You should explain your solution.Thisbe
C
1

Polling is not the advisable solution.

You should be using requestMediaDataWhenReadyOnQueue

assetWriterInput.requestMediaDataWhenReady(on: serialQueue) { [weak self] in
    guard let self = self else { return }
    while self.assetWriterInput.isReadyForMoreMediaData {
        // Copy the next sample buffer from source media.
        guard let nextSampleBuffer = copyNextSampleBufferToWrite() else {
            // Mark the input as finished.
            self.assetWriterInput.markAsFinished()
            break
        }
        // Append the sample buffer to the input.
        self.assetWriterInput.append(nextSampleBuffer)
    }
}
Chiropody answered 9/2, 2022 at 7:34 Comment(5)
If by polling you mean while loop then that is incorrect as it's exactly what the doc you referenced does: while self.assetWriterInput.isReadyForMoreMediaDataLibava
You can do the while loop after the callback is called, just like the doc I referenced shows. But you should not be looping on isReadyForMoreMediaData until after that callback, because it’s a huge waste of cpu cycles.Chiropody
The doc shows the while loop being executed within the call back passed in, not afterwards.Libava
yes, that’s exactly what I mean. Sorry I that was obvious ¯_(ツ)_/¯Chiropody
I suggest you edit your answer as currently what you're proposing still goes against the documentation.Libava

© 2022 - 2024 — McMap. All rights reserved.