kingslay / KSPlayer

A video player for iOS、macOS、tvOS、visionOS , based on AVPlayer and FFmpeg, support the horizontal, vertical screen. support adjust volume, brightness and seek by slide, SwiftUI, support subtitles.
https://apps.apple.com/app/tracyplayer/id6450770064
GNU General Public License v3.0
984 stars 197 forks source link

Annex-B videotoolbox硬解码失败 #680

Closed blackox626 closed 3 months ago

blackox626 commented 10 months ago

codecpar.extradata 如果是 Annex-B 格式,直接创建 formatDescription ,会导致decompressionSession 创建失败, videotoolbox硬解码走不到

kingslay commented 10 months ago

能够提供视频片段让我进行调试吗?你可以上传一分钟以内的小视频到github就可以了。

blackox626 commented 10 months ago

能够提供视频片段让我进行调试吗?你可以上传一分钟以内的小视频到github就可以了。

我这边业务链接不方便 给到, 不过是不是可以通过 加一个 mp4toannexb filter 来模拟这个case

kingslay commented 10 months ago

你可以在网上随便找个视频,然后用ffmpeg转一个Annex-B 格式。这样就可以了

blackox626 commented 10 months ago

另外我这边 尝试 改了下,发现能播放 渲染出来了。但是视频会卡住。。 还没定位到原因 只考虑了 hevc 的情况 我贴一下代码, swift 不太熟练

class DecoderInfo {
    var vps: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: 1)
    var sps: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: 1)
    var f_pps: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: 1)
    var r_pps: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: 1)
    var vps_size: Int32 = 0
    var sps_size: Int32 = 0
    var f_pps_size: Int32 = 0
    var r_pps_size: Int32 = 0
}

func getNALUInfo(extraData: UnsafeMutablePointer<UInt8>,extraDataSize: Int32, decoderInfo: DecoderInfo) {

    var decoderInfo = decoderInfo

    var data = extraData
    var size = extraDataSize;

    var startCodeVPSIndex : Int32 = 0
    var startCodeSPSIndex : Int32 = 0
    var startCodeFPPSIndex : Int32  = 0
    var startCodeRPPSIndex : Int32  = 0
    var nalu_type : Int  = 0

    for i in 0..<size {
        if i >= 3 {
            if data[Int(i)] == 0x01 && data[Int(i) - 1] == 0x00 && data[Int(i) - 2] == 0x00 && data[Int(i) - 3] == 0x00 {
                if startCodeVPSIndex == 0 {
                    startCodeVPSIndex = i
                    continue
                }
                if i > startCodeVPSIndex && startCodeSPSIndex == 0 {
                    startCodeSPSIndex = i
                    continue
                }
                if i > startCodeSPSIndex && startCodeFPPSIndex == 0 {
                    startCodeFPPSIndex = i
                    continue
                }
                if i > startCodeFPPSIndex && startCodeRPPSIndex == 0 {
                    startCodeRPPSIndex = i
                }
            }
        }
    }

    let spsSize = startCodeFPPSIndex - startCodeSPSIndex - 4
    decoderInfo.sps_size = spsSize

    let vpsSize = startCodeSPSIndex - startCodeVPSIndex - 4
    decoderInfo.vps_size = vpsSize

    let f_ppsSize = (startCodeRPPSIndex != 0) ? (startCodeRPPSIndex - startCodeFPPSIndex - 4) : (size - (startCodeFPPSIndex + 1))
    decoderInfo.f_pps_size = f_ppsSize

    nalu_type = Int(data[Int(startCodeVPSIndex) + 1]) & 0x4F
    if nalu_type == 0x40 {
        let vps =
        withUnsafeMutablePointer(to: &data[Int(startCodeVPSIndex) + 1]) {$0}

        decoderInfo.vps = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(vpsSize))
        memcpy(decoderInfo.vps, vps, Int(vpsSize))
    }

    nalu_type = Int(data[Int(startCodeSPSIndex) + 1]) & 0x4F
    if nalu_type == 0x42 {
        let sps =
        withUnsafeMutablePointer(to: &data[Int(startCodeSPSIndex) + 1]) {$0}

        decoderInfo.sps = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(spsSize))
        memcpy(decoderInfo.sps, sps, Int(spsSize))
    }

    nalu_type = Int(data[Int(startCodeFPPSIndex) + 1]) & 0x4F
    if nalu_type == 0x44 {

        let fpps =
        withUnsafeMutablePointer(to: &data[Int(startCodeFPPSIndex) + 1]) {$0}

        decoderInfo.f_pps = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(f_ppsSize))
        memcpy(decoderInfo.f_pps, fpps, Int(f_ppsSize))
    }

    if startCodeRPPSIndex == 0 {
        return
    }

    let r_ppsSize = size - (startCodeRPPSIndex + 1)
    decoderInfo.r_pps_size = r_ppsSize

    nalu_type = Int(data[Int(startCodeRPPSIndex) + 1]) & 0x4F
    if nalu_type == 0x44 {
        let rpps =
        withUnsafeMutablePointer(to: &data[Int(startCodeRPPSIndex) + 1]) {$0}

        decoderInfo.r_pps = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(r_ppsSize))
        memcpy(decoderInfo.r_pps, rpps, Int(r_ppsSize))
    }
}

if let extradata {
                extradataSize = codecpar.extradata_size
                /// avcc
//                if extradataSize >= 5, extradata[4] == 0xFE {
//                    extradata[4] = 0xFF
//                    isConvertNALSize = true
//                } else {
//                    isConvertNALSize = false
//                }
//                atomsData = Data(bytes: extradata, count: Int(extradataSize))
                atomsData = nil
                isConvertNALSize = true
                getNALUInfo(extraData: extradata, extraDataSize: extradataSize, decoderInfo: decoderInfo)

            }

//            _ = CMVideoFormatDescriptionCreate(allocator: kCFAllocatorDefault, codecType: codecType.rawValue, width: codecpar.width, height: codecpar.height, extensions: dic, formatDescriptionOut: &formatDescriptionOut)

            let parameterSetPointers: [UnsafePointer<UInt8>] = [UnsafePointer<UInt8>(decoderInfo.vps), UnsafePointer<UInt8>(decoderInfo.sps), UnsafePointer<UInt8>(decoderInfo.f_pps)]
            let parameterSetSizes: [Int] = [Int(decoderInfo.vps_size), Int(decoderInfo.sps_size), Int(decoderInfo.f_pps_size)]
            if #available(iOS 11.0, *) {
                let status = CMVideoFormatDescriptionCreateFromHEVCParameterSets(allocator: kCFAllocatorDefault,
                                                                                 parameterSetCount: 3,
                                                                                 parameterSetPointers: parameterSetPointers,
                                                                                 parameterSetSizes: parameterSetSizes,
                                                                                 nalUnitHeaderLength: 4,
                                                                                 extensions: nil,
                                                                                 formatDescriptionOut: &formatDescriptionOut)
            } else {
                let status = -1
            }

let attributes: NSMutableDictionary = [
            kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,//pixelFormatType,
            kCVPixelBufferMetalCompatibilityKey: true,
            kCVPixelBufferWidthKey: assetTrack.codecpar.width,
            kCVPixelBufferHeightKey: assetTrack.codecpar.height,
            kCVPixelBufferIOSurfacePropertiesKey: NSDictionary(),
        ]

if isConvertNALSize {
//            var ioContext: UnsafeMutablePointer<AVIOContext>?
//            let status = avio_open_dyn_buf(&ioContext)
//            if status == 0 {
//                var nalSize: UInt32 = 0
//                let end = data + size
//                var nalStart = data
//                while nalStart < end {
//                    nalSize = UInt32(nalStart[0]) << 16 | UInt32(nalStart[1]) << 8 | UInt32(nalStart[2])
//                    avio_wb32(ioContext, nalSize)
//                    nalStart += 3
//                    avio_write(ioContext, nalStart, Int32(nalSize))
//                    nalStart += Int(nalSize)
//                }
//                var demuxBuffer: UnsafeMutablePointer<UInt8>?
//                let demuxSze = avio_close_dyn_buf(ioContext, &demuxBuffer)
//                return try createSampleBuffer(data: demuxBuffer, size: Int(demuxSze))
//            } else {
//                throw NSError(errorCode: .codecVideoReceiveFrame, avErrorCode: status)
//            }

            var ioContext: UnsafeMutablePointer<AVIOContext>?
            let status = avio_open_dyn_buf(&ioContext)
            if status == 0 {

                var nalStart = data
                var i = 0
                var start = 0
                while i < size {

                    if i+2 < size {
                        if data[i] == 0x00 && data[i+1] == 0x00 && data[i+2] == 0x01 {
                            if start == 0 {
                                start = 3
                                nalStart += 3
                            } else  {
                                let len = i - start
                                avio_wb32(ioContext, UInt32(len))

                                avio_write(ioContext, nalStart, Int32(len))

                                start = i + 3

                                nalStart += len + 3
                            }
                            //
                            i+=3
                            continue
                        }
                    }

                    if i+3 < size {
                        if data[i] == 0x00 && data[i+1] == 0x00 && data[i+2] == 0x00 && data[i+3] == 0x01 {

                            if start == 0 {
                                start = 4
                                nalStart += 4

                            } else  {
                                let len = i - start
                                avio_wb32(ioContext, UInt32(len))

                                avio_write(ioContext, nalStart, Int32(len))

                                start = i + 4

                                nalStart += len + 4
                            }
                            //
                            i+=4
                            continue
                        }
                    }

                    i += 1
                }

                let len = size - start
                avio_wb32(ioContext, UInt32(len))

                avio_write(ioContext, nalStart, Int32(len))

                var demuxBuffer: UnsafeMutablePointer<UInt8>?
                let demuxSze = avio_close_dyn_buf(ioContext, &demuxBuffer)
                return try createSampleBuffer(data: demuxBuffer, size: Int(demuxSze))
            } else {
                throw NSError(errorCode: .codecVideoReceiveFrame, avErrorCode: status)
            }
blackox626 commented 10 months ago

你可以在网上随便找个视频,然后用ffmpeg转一个Annex-B 格式。这样就可以了

https://assets.weidianfans.com/res/1dc70c36.h264

annexb 格式的 h264 文件

kingslay commented 10 months ago

这个视频无法走我自己写的硬接,但是是可以走ffmpeg的硬接的。我自己实现的硬解比较简单,没有适配那么多的情况。所以这个还是会走videotoolbox的

blackox626 commented 10 months ago

这个视频无法走我自己写的硬接,但是是可以走ffmpeg的硬接的。我自己实现的硬解比较简单,没有适配那么多的情况。所以这个还是会走videotoolbox的

是的 。

//int ff_isom_write_hvcc(AVIOContext pb, const uint8_t data, int size, int ps_array_completeness);
这个方法 也可以处理 annexb extradata 转 hvcc ,自己硬解 也方便很多

kingslay commented 10 months ago

你用我的demo会发现它没有走硬解,是因为它是 bottom first。需要走filter

blackox626 commented 10 months ago

你用我的demo会发现它没有走硬解,是因为它是 bottom first。需要走filter

是的 ,我注释掉了 然后强制走videotoolbox 硬解,复现问题 哈哈

kingslay commented 3 months ago

我在lgpl分支支持Annex-B videotoolbox硬解码了