Skip to content

The display is stopped and joined twice when decoder update fails

I still need to write a proper test for that, but from input/decoder.c

    vout_thread_t *p_vout =
        input_resource_RequestVout(p_owner->p_resource, vctx, &cfg, NULL,
                                   &has_started);

The issue can arise when decoders are updating their output format, leading to requesting a new vout from the input resources:

vout_thread_t *input_resource_RequestVout(input_resource_t *p_resource,
                                          vlc_video_context *vctx,
                                          const vout_configuration_t *cfg,
                                          enum vlc_vout_order *order,
                                          bool *has_started)

Since decoder already had a vout, it is reused and this branch of input/resource.c is chosen.

    else
    {
        vout_rsc = resource_GetVoutRsc(p_resource, dcfg.vout);
        assert(vout_rsc != NULL);

        /* the caller is going to reuse the free vout, it's not free anymore */
        if (p_resource->vout_rsc_free == vout_rsc)
            p_resource->vout_rsc_free = NULL;
    }

In that case, the vout_rsc is still ->started and it can lead to this branch, which can no-op if the format is already matching.


    if (vout_rsc->started)
    {
        assert(cfg->vout != NULL);
        int ret = vout_ChangeSource(dcfg.vout, dcfg.fmt);
        if (ret == 0)
        {
            vlc_mutex_unlock(&p_resource->lock);
            return dcfg.vout;
        }
    }

But when the format is not matching, it continues to calling into vlc_vout functions, still with the vout_rsc->started flag:

    if (vout_Request(&dcfg, vctx, p_resource->p_input)) {
        input_resource_PutVoutLocked(p_resource, dcfg.vout, NULL);
        vlc_mutex_unlock(&p_resource->lock);
        return NULL;
    }
int vout_Request(const vout_configuration_t *cfg, vlc_video_context *vctx, input_thread_t *input)

In this function, the vout code will end up killing the display:

    if (sys->display != NULL)
        vout_StopDisplay(cfg->vout);

And then request a new vout display / thread. If the start fails, an error is returned to the input resource.

    if (vout_Start(vout, vctx, cfg))
    {
        msg_Err(cfg->vout, "video output display creation failed");
        video_format_Clean(&sys->original);
        vout_DisableWindow(vout);
        return -1;
    }

The input resource will then call input_resource_PutVoutLocked, which calls vout_StopDisplay since vout_rsc->started.

static void input_resource_PutVoutLocked(input_resource_t *p_resource,
                                         vout_thread_t *vout, bool *has_stopped)
{
    assert(vout != NULL);
    struct vout_resource *vout_rsc = resource_GetVoutRsc(p_resource, vout);
    assert(vout_rsc != NULL);

    if (has_stopped != NULL)
        *has_stopped = vout_rsc->started;

    if (vout_rsc->started)
    {
        vout_StopDisplay(vout_rsc->vout);
        vout_rsc->started = false;
    }

And ends up with a double vlc_join:

void vout_StopDisplay(vout_thread_t *vout)
{
    vout_thread_sys_t *sys = VOUT_THREAD_TO_SYS(vout);

    atomic_store(&sys->control_is_terminated, true);
    // wake up so it goes back to the loop that will detect the terminated state
    vout_control_Wake(&sys->control);
    vlc_join(sys->thread, NULL);

    vout_ReleaseDisplay(sys);
}

The issue also seems to happen when decoder is being deleted but I couldn't pin-point a specific interleaving for now.

To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information