sunlubo / SwiftFFmpeg

A Swift wrapper for the FFmpeg API
Apache License 2.0
521 stars 83 forks source link

Foundation.Data from AVFrame’s `data` property #53

Closed Sajjon closed 3 years ago

Sajjon commented 3 years ago

Im not great at pointer syntax, how can I convert an AVFrame instance data into Swift Foundation Data

(End goal is to convert a Bink Video to an AVPlayer playable item, Im trying each AVFrame => Data => CIImage => CVPixelBuffer => append to AVAssetWriteeInput (AVAssetWriterInputPixelBufferAdaptor))

sunlubo commented 3 years ago

video data is stored in AVFrame.data

Sajjon commented 3 years ago

Yes, that's what I said, and its type is UnsafeMutableBufferPointer<UnsafeMutablePointer<UInt8>?>

And Im asking how I can convert that type into Data?

Sajjon commented 3 years ago

@sunlubo Or rather, how can I create an CIImage or CGImage from an AVFrame? :)

Sajjon commented 3 years ago

Ah I found this: https://github.com/sunlubo/SwiftFFmpeg/issues/2

Sajjon commented 3 years ago

But it is outdated and still references the (now updated) and hard to use type data, hmm...

gregcotten commented 3 years ago

To initialize without copying (you must hold onto the AVFrame and make sure it's not unref'd until you're done with Data!

let data = Data(bytesNoCopy: .init(frame.buffer[0]!.data), count: Int(frame.buffer[0]!.size), deallocator: .none)

An untested method to have the Data safely deallocate the frame when no longer needed:

let data = Data(bytesNoCopy: .init(frame.buffer[0]!.data), count: Int(frame.buffer[0]!.size), deallocator: .custom({ _, bytes in
                        assert(bytes == Int(frame.buffer[0]!.size))
                        frame.unref()
                    }))

To initialize with Data by copying:

Data(buffer: .init(start: frame.buffer[0]!.data, count: Int(frame.buffer[0]!.size)))

Remember if you are sampling planar frame data (planar audio channels or YCbCr data), you'll need to get each valid plane (I was just using an index of 0)

Sajjon commented 3 years ago

@gregcotten thx a lot!

Edit; I got it working! Thx for the help. Managed convert a BINK Video to a Mov file.

Hmm im a little bit over my head here, I wanna play a BINK (bik files) video on macOS and Im able to read frame data using (Swift)FFmpeg. And I thought I would be able to save each individual frame as an CIImage and write usinf AVAsserWriter and that way somehow save the whole series of frames as a .MOV or similar movie that I can play with AVPlayer.

I've tried this, but CIImage initializer fails.

var dataSingleFrame = Data()
for (frameDataIndex, maybeFrameData) in frame.data.enumerated() {
    guard let frameData = maybeFrameData else {
        break
    }
    let byteCount = Int(frame.linesize[frameDataIndex])
    let singleFramePartData = Data(bytesNoCopy: .init(frameData), count: byteCount, deallocator: .none)
    dataSingleFrame.append(contentsOf: singleFramePartData)
}

guard let ciImage = CIImage(data: dataSingleFrame) else {
    fatalError("Failed to create CI image from data")
}

Any idea?

gregcotten commented 3 years ago

@Sajjon Apologies, I've amended my response to use AVFrame's buffer array instead.

Sajjon commented 3 years ago

Thx for the help @gregcotten ! maybe we ought to Swiftify those properties and change to [Data] instead of UnsafeMutableBufferPointer<UnsafeMutablePointer<UInt8>?> ?

chris-mds commented 2 years ago

Hi @Sajjon ,

did you have a working code snippet of your function? I modified it like the following code, but still no successful conversion to the image property. Even better would be a conversion function to CMSampleBuffer Maybe some ideas?

var dataSingleFrame = Data()
for (_, maybeFrameData) in frame.buf.enumerated() {
  guard let frameData = maybeFrameData else {
      break
  }
  let singleFramePartData = Data(bytesNoCopy: .init(frameData.data), count: frameData.size, deallocator: .none)
  dataSingleFrame.append(contentsOf: singleFramePartData)
}

guard let ciImage = CIImage(data: dataSingleFrame) else {
  print("Failed to create CI image from data")
  continue
}