6

So I have some code to create H264ParameterSets like:

var formatDesc: CMVideoFormatDescription?

func createH264FormatDescription(SPS: Array<UInt8>, PPS: Array<UInt8>) -> OSStatus {
    if formatDesc != nil { formatDesc = nil }

    let paramSet = [UnsafePointer<UInt8>(SPS), UnsafePointer<UInt8>(PPS)]
    let paramPointers = UnsafePointer<UnsafePointer<UInt8>>(paramSet)
    let paramSizes = UnsafePointer<Int>([SPS.count, PPS.count])

    let status = CMVideoFormatDescriptionCreateFromH264ParameterSets(allocator: kCFAllocatorDefault, parameterSetCount: 2, parameterSetPointers: paramPointers, parameterSetSizes: paramSizes, nalUnitHeaderLength: 4, formatDescriptionOut: &formatDesc)

    return status
}

Starting on Xcode 11.4 I got warnings for those UnsafePointer(), which seems not happen before:

Initialization of UnsafePointer<UInt8> results in a dangling pointer

Initialization of UnsafePointer<UnsafePointer<UInt8>> results in a dangling pointer

Initialization of UnsafePointer<Int> results in a dangling pointer

I'm not sure why we see this? and how can I remove the warning? Thank in advance.

Wingzero
  • 9,644
  • 10
  • 39
  • 80
  • @matt I don't think the questions you posted solved my question, I have checked it before, but it didn't give a clear shot. So downvote is actually abusing the mechanism how it works – Wingzero Apr 17 '20 at 04:13
  • Really? The links seem to me spot on. Passing unsafe pointers around was always wrong. It’s a warning now and a compile error in future. The links also give the right answer. – matt Apr 17 '20 at 04:27
  • @matt the problem is using .withUnsafeBufferPointer {} will trigger new errors, see answers below and my comment. I'd hope you can reopen this, since this is a slightly different issue, or the threads you posted cannot directly lead to a solution. – Wingzero Apr 17 '20 at 07:35
  • @matt besides, it's Apple's API asking me to pass UnsafePointer types. – Wingzero Apr 17 '20 at 07:37
  • even I can get UnsafePointer from UnsafeBufferPointer, it also breaks my code that I need a return. Using blocks will be more complex to have a return value? anyway, the point is my question cannot be simply solved by looking at above posts. I think people like me who are new to Video decoding+Swift will have same confusion. – Wingzero Apr 17 '20 at 07:51

1 Answers1

9

The easiest way to explain this warning is to look at one of the cases causing it. So lets start with your use of SPS.

It is an Array<UInt8> so it is backed by a buffer of UInt8 just like in C. When you pass SPS with UnsafePointer<UInt8>(SPS) it creates a valid pointer to the buffer for that moment. The issue is that you could then mutate SPS say by appending another value to it. This would mean that the buffer backing the Array is potentially moved to another place in memory. This would mean that your pointer that is now part of paramSet is invalid.

The other issue is that if you pass this pointer to something, like you do in this case, the other function could try to hold onto it and then it has an invalid pointer. So if you expect the other function to hold onto the pointer you need to manually manage memory with UnsafePointers and Unmanaged yourself. If CMVideoFormatDescriptionCreateFromH264ParameterSets() doesn't hold onto the pointers then the code I'll share is correct, if it does you will need to adjust it to create/destory the memory as needed.

Also it is worth noting that in this case, you can't mutate any of the Arrays you have because they are constants but in general the principle is still the same. This means that in theory it could never be mutated but the Swift compiler prefers to help us write code that is always safe and correct whenever possible, even with UnsafePointer types.

So how can you fix this? You will need to be able to call withUnsafeBufferPointer and then access the pointer through the UnsafeBufferPointer like this:

var formatDesc: CMVideoFormatDescription?

func createH264FormatDescription(SPS: Array<UInt8>, PPS: Array<UInt8>) -> OSStatus {
    if formatDesc != nil { formatDesc = nil }

    let status = SPS.withUnsafeBufferPointer { SPS in
        PPS.withUnsafeBufferPointer { PPS in
            let paramSet = [SPS.baseAddress!, PPS.baseAddress!]
            let paramSizes = [SPS.count, PPS.count]
            return paramSet.withUnsafeBufferPointer { paramSet in
                paramSizes.withUnsafeBufferPointer { paramSizes in
                    CMVideoFormatDescriptionCreateFromH264ParameterSets(allocator: kCFAllocatorDefault, parameterSetCount: 2, parameterSetPointers: paramSet.baseAddress!, parameterSetSizes: paramSizes.baseAddress!, nalUnitHeaderLength: 4, formatDescriptionOut: &formatDesc)
                }
            }
        }
    }
    return status
}

The reason this approach works is that for the scope of withUnsafeBufferPointer the Law of Exclusivity is protecting the arrays so they can't be mutated.

If you are worried about the baseAddress! usage you can check that it isn't nil but it is guaranteed to not be nil when count > 0 according the the compiler engineers (they have stated this on either Twitter or the Swift forums I forget...).

bscothern
  • 1,894
  • 11
  • 15
  • is there other ways to do this, not using nested blocks? really not a fan of 'with... {}' style, the code you send is nice, but seems a little verbose? – Wingzero Apr 17 '20 at 04:16
  • There aren’t really other options other than having your function take pointers instead of converting to them. Pointers in Swift are in general extremely verbose to use correctly. If you are doing similar things in other places you could write helper functions that takes N arrays and provides those N pointers to a closure. Then you could do this in 2 withUnsafe blocks at this level instead of 4. – bscothern Apr 17 '20 at 05:45
  • sorry your code won't compile. there is a typo first: need to change paramPointers to paramSet. However after this, it throws warning: Constant 'status' inferred to have type '()', which may be unexpected – Wingzero Apr 17 '20 at 06:37
  • error: Cannot convert value of type 'UnsafeBufferPointer' to expected argument type 'UnsafePointer' – Wingzero Apr 17 '20 at 06:37
  • error: Cannot convert value of type 'UnsafeBufferPointer>' to expected argument type 'UnsafePointer>' – Wingzero Apr 17 '20 at 06:37
  • error: Cannot convert return expression of type '()' to return type 'OSStatus' (aka 'Int32') – Wingzero Apr 17 '20 at 06:37
  • I suggest you try it on your side and post a good one, otherwise I cannot accept this as an answer – Wingzero Apr 17 '20 at 06:38
  • I have edited my post to fix the typo, access `baseAddress` and return the third call to `withUnsafeBufferPointer`. I can't compile the code because I don't have all the information needed to do so. Next time I would recommend editing the solution post after fixing the couple of issues that came from the question missing information that would have allowed me to build it. It was spot on and easy to fix once I had compilation errors. – bscothern Apr 17 '20 at 18:51
  • Hi there, could you explain why previous 'return' throw errors? – Wingzero Apr 20 '20 at 01:42
  • Besides, you should be able having all code for this func. I got new errors: `Generic parameter 'R' could not be inferred` on SPS in `SPS.withUnsafeBufferPointer { SPS in` – Wingzero Apr 20 '20 at 01:45
  • I'm going to open a new case as this has gone far beyond as a 'duplicate question'. Please answer in the new post https://stackoverflow.com/questions/61313993/cmvideoformatdescriptioncreatefromh264parametersets-throw-initialization-of-uns – Wingzero Apr 20 '20 at 01:54