Skip to content

6.6.3 - A software session that decodes into nothing says so

Choose a tag to compare

@superuser404notfound superuser404notfound released this 04 Aug 07:39

One diagnostic addition and one hardening on the software playback path, from a field report on 6.6.0. No API change.

Added

  • A software session says when its frames are decoding into nothing. The software path renders into an AVSampleBufferDisplayLayer the engine owns, and that layer reaches the screen only once the host binds a surface, either bind(view:) with an AetherPlayerView or AetherPlayerSurface in SwiftUI. A host that presents an AVPlayerViewController for a software-routed source instead gets audio, a completely healthy engine log, and no picture, because this path has no AVPlayerItem for AVKit to show; a layer bound to a view that never got a layout looks identical from the outside. Neither left a trace. Both are now named once per session, about two seconds after frames start flowing, with the count of frames that went nowhere:

    [SWHost] 50 frames decoded into a display layer that is in no view hierarchy: the host never
    bound a render surface (AetherEngine.bind(view:) / AetherPlayerSurface). The software path
    renders into that layer, not through AVPlayerViewController, so audio plays and no picture
    can appear
    
    [SWHost] display layer is bound but sized 0x0 after 50 frames: the bound view has no layout,
    so no frame can be visible
    

    Reported by @akacores as a renderer that stops accepting frames (#298). Worth stating for anyone reading a similar log, because the quoted signature is what a healthy session prints:

    [Renderer] enqueue #1:   status=rendering ready=true  error=nil
    [Renderer] enqueue #30:  status=rendering ready=false error=nil   (t+1.37s)
    [Renderer] enqueue #100: status=rendering ready=false error=nil   (t+4.74s)
    

    isReadyForMoreMediaData is the demux loop's back-pressure gate, which parks before feeding a video packet, so a full queue immediately after an enqueue is the gate working. #100 at t+4.74s on a 23.976 fps source is real-time pacing, and audio and video are fed by the same loop: a queue that had stopped draining would have parked that loop within one queue depth and stopped the audio with it.

Changed

  • A frame whose presentation timestamp is not numeric no longer reaches the display queue. AV_NOPTS_VALUE arrives at the renderer as CMTime.invalid, and CoreMedia builds a sample buffer from it without complaint (CMSampleBufferCreateReadyWithImageBuffer returns noErr, the sample's PTS reads back as NaN seconds), so the render synchronizer was the first thing in the chain that could not schedule it. The deinterlace path has dropped its own untimestamped output for that reason since it was added; this is the same rule one layer lower, before the B-frame reorder buffer, where such a frame additionally reordered its neighbours because every comparison against NaN is false. Drops are counted and named:

    [Renderer] dropped N frame(s) with no usable timestamp (unschedulable)
    

    Hardening rather than a fix for the report above: the demuxer opens with fflags +genpts, so a container that carries no presentation timestamps of its own (AVI with B-frames, the reported case) has them regenerated before the decoder sees them (#298).

Verifying it

aetherctl play --sw <url> on any software-routed source prints the new line at about t+2s, because the CLI binds no render surface at all. aetherctl pktdump <url> answers the timestamp half: NOPTS_pts counts what actually reaches the decoder, which is not what ffprobe reports on the same file unless it is given -fflags +genpts too.