H.265 Decoding with VideoToolBox

I am creating an app that decodes H.265 elementary streams on iOS.

I use VideoToolBox to decode from H.265 to NV12.

The decoded data is enqueued in the CMSampleBufferDisplayLayer as a CMSampleBuffer.

However, nothing is displayed in the VideoPlayerView. It remains black.

The decoding in VideoToolBox is successful. I confirmed this by saving the NV12 data in the CMSampleBuffer to a file and displaying it using a tool.

Why is nothing displayed in the VideoPlayerView?

I can provide other source code as well.

//
//  ContentView.swift
//  H265Decoder
//
//  Created by Kohshin Tokunaga on 2025/02/15.
//

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("H.265 Player (temp.h265)")
                .font(.headline)
            VideoPlayerView()
                .frame(width: 360, height: 640) // Adjust or make it responsive for iOS
        }
        .padding()
    }
}

#Preview {
    ContentView()
}
//
//  VideoPlayerView.swift
//  H265Decoder
//
//  Created by Kohshin Tokunaga on 2025/02/15.
//

import SwiftUI
import AVFoundation

struct VideoPlayerView: UIViewRepresentable {
    
    // Return an H265Player as the coordinator, and start playback there.
    func makeCoordinator() -> H265Player {
        H265Player()
    }
    
    func makeUIView(context: Context) -> UIView {
        let uiView = UIView(frame: .zero)
        
        // Base layer for attaching sublayers
        uiView.backgroundColor = .black // Screen background color (for iOS)
        
        // Create the display layer and add it to uiView.layer
        let displayLayer = context.coordinator.displayLayer
        displayLayer.frame = uiView.bounds
        displayLayer.backgroundColor = UIColor.clear.cgColor
        
        uiView.layer.addSublayer(displayLayer)
        
        // Start playback
        context.coordinator.startPlayback()
        
        return uiView
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        // Reset the frame of the AVSampleBufferDisplayLayer when the view's size changes.
        let displayLayer = context.coordinator.displayLayer
        displayLayer.frame = uiView.layer.bounds
        
        // Optionally update the layer's background color, etc.
        uiView.backgroundColor = .black
        displayLayer.backgroundColor = UIColor.clear.cgColor
        
        // Flush transactions if necessary
        CATransaction.flush()
    }
}
//
//  H265Player.swift
//  H265Decoder
//
//  Created by Kohshin Tokunaga on 2025/02/15.
//

import Foundation
import AVFoundation
import CoreMedia

class H265Player: NSObject, VideoDecoderDelegate {
    
    let displayLayer = AVSampleBufferDisplayLayer()
    private var decoder: H265Decoder?
    
    override init() {
        super.init()
        
        // Initial configuration for the display layer
        displayLayer.videoGravity = .resizeAspect
        
        // Initialize the decoder (delegate = self)
        decoder = H265Decoder(delegate: self)
        
        // For simple playback, set isBaseline to true
        decoder?.isBaseline = true
    }
    
    func startPlayback() {
        // Load the file "cars_320x240.h265"
        guard let url = Bundle.main.url(forResource: "temp2", withExtension: "h265") else {
            print("File not found")
            return
        }
        do {
            let data = try Data(contentsOf: url)
            // Set FPS and video size as needed
            let packet = VideoPacket(data: data,
                                     type: .h265,
                                     fps: 30,
                                     videoSize: CGSize(width: 1080, height: 1920))
            
            // Decode as a single packet
            decoder?.decodeOnePacket(packet)
            
        } catch {
            print("Failed to load file: \(error)")
        }
    }
    
    // MARK: - VideoDecoderDelegate
    func decodeOutput(video: CMSampleBuffer) {
        // When decoding is complete, send the output to AVSampleBufferDisplayLayer
        displayLayer.enqueue(video)
    }
    
    func decodeOutput(error: DecodeError) {
        print("Decoding error: \(error)")
    }
}
Answered by KohshinTokunaga in 826263022

I uploaded the project to github.

https://github.com/kohshin1977/H265Decoder

Accepted Answer

I uploaded the project to github.

https://github.com/kohshin1977/H265Decoder

H.265 Decoding with VideoToolBox
 
 
Q