web-dev-qa-db-fra.com

Comment utiliser l'accélération matérielle avec ffmpeg

J'ai besoin que ffmpeg décode ma vidéo (par exemple, h264) en utilisant l'accélération matérielle. J'utilise la méthode habituelle de décodage des trames: lire le paquet -> décoder la trame. Et j'aimerais que ffmpeg accélère le décodage. Donc, je l'ai construit avec --enable-vaapi Et --enable-hwaccel=h264. Mais je ne sais pas vraiment ce que je devrais faire ensuite. J'ai essayé d'utiliser avcodec_find_decoder_by_name("h264_vaapi") mais il renvoie nullptr. Quoi qu'il en soit, je pourrais utiliser d'autres API et pas seulement VA. Comment est-il censé accélérer le décodage de ffmpeg?

P.S. Je n'ai trouvé aucun exemple sur Internet qui utilise ffmpeg avec hwaccel.

42
ixSci

Après quelques recherches, j'ai pu implémenter le décodage accéléré matériel nécessaire sous OS X (VDA) et Linux (VDPAU). Je mettrai à jour la réponse lorsque je maîtriserai également la mise en œuvre de Windows. Commençons donc par le plus simple:

Mac OS X

Pour que l'accélération matérielle fonctionne sous Mac OS, vous devez simplement utiliser les éléments suivants: avcodec_find_decoder_by_name("h264_vda"); Notez que vous ne pouvez accélérer les vidéos h264 que sur Mac OS avec FFmpeg.

Linux VDPAU

Sous Linux, les choses sont beaucoup plus compliquées (qui est surpris?). FFmpeg a 2 accélérateurs matériels sur Linux: VDPAU (Nvidia) et VAAPI (Intel) et un seul décodeur matériel: pour VDPAU. Et il peut sembler parfaitement raisonnable d’utiliser le décodeur vdpau comme dans l’exemple de Mac OS ci-dessus: avcodec_find_decoder_by_name("h264_vdpau");

Vous serez peut-être surpris de constater que cela ne change rien et que vous n’avez aucune accélération. C'est parce que ce n'est que le début, vous devez écrire beaucoup plus de code pour que l'accélération fonctionne. Heureusement, vous n'avez pas besoin de trouver vous-même une solution: il existe au moins deux bons exemples pour y parvenir: libavg et FFmpeg lui-même. libavg a la classe VDPAUDecoder qui est parfaitement claire et sur laquelle j'ai basé mon implémentation. Vous pouvez également consulter ffmpeg_vdpau.c pour obtenir une autre implémentation à comparer. À mon avis, la mise en œuvre de libavg est cependant plus facile à comprendre.

La seule chose qui manque aux deux exemples susmentionnés est une copie correcte de la trame décodée dans la mémoire principale. Les deux exemples utilisent VdpVideoSurfaceGetBitsYCbCr, ce qui tue toutes les performances que j'ai obtenues sur ma machine. C'est pourquoi vous souhaiterez peut-être utiliser la procédure suivante pour extraire les données d'un GPU:

bool VdpauDecoder::fillFrameWithData(AVCodecContext* context,
    AVFrame* frame)
{
    VdpauDecoder* vdpauDecoder = static_cast<VdpauDecoder*>(context->opaque);
    VdpOutputSurface surface;
    vdp_output_surface_create(m_VdpDevice, VDP_RGBA_FORMAT_B8G8R8A8, frame->width, frame->height, &surface);
    auto renderState = reinterpret_cast<vdpau_render_state*>(frame->data[0]);
    VdpVideoSurface videoSurface = renderState->surface;

    auto status = vdp_video_mixer_render(vdpauDecoder->m_VdpMixer,
        VDP_INVALID_HANDLE,
        nullptr,
        VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME,
        0, nullptr,
        videoSurface,
        0, nullptr,
        nullptr,
        surface,
        nullptr, nullptr, 0, nullptr);
    if(status == VDP_STATUS_OK)
    {
        auto tmframe = av_frame_alloc();
        tmframe->format = AV_PIX_FMT_BGRA;
        tmframe->width = frame->width;
        tmframe->height = frame->height;
        if(av_frame_get_buffer(tmframe, 32) >= 0)
        {
            VdpStatus status = vdp_output_surface_get_bits_native(surface, nullptr,
                reinterpret_cast<void * const *>(tmframe->data),
                reinterpret_cast<const uint32_t *>(tmframe->linesize));
            if(status == VDP_STATUS_OK && av_frame_copy_props(tmframe, frame) == 0)
            {
                av_frame_unref(frame);
                av_frame_move_ref(frame, tmframe);
                return;
            }
        }
        av_frame_unref(tmframe);
    }
    vdp_output_surface_destroy(surface);
    return 0;
}

Bien que certains objets "externes" soient utilisés à l'intérieur, vous devriez pouvoir le comprendre une fois que vous avez implémenté la partie "get buffer" (pour laquelle les exemples susmentionnés sont d'une grande aide). J'ai aussi utilisé le format BGRA qui convenait mieux à mes besoins. Peut-être en choisirez-vous un autre.

Le problème avec tout cela est que vous ne pouvez pas simplement le faire fonctionner à partir de FFmpeg, vous devez au moins comprendre les bases de l'API VDPAU. Et j'espère que ma réponse aidera quelqu'un à mettre en œuvre l'accélération matérielle sous Linux. J'y ai passé beaucoup de temps moi-même avant de réaliser qu'il n'existait pas de méthode simple et unilatérale pour implémenter le décodage accéléré par matériel sous Linux.

VA-API Linux

Comme ma question initiale concernait VA-API, je ne peux pas ne pas la laisser sans réponse. Tout d'abord, il n'y a pas de décodeur pour VA-API dans FFmpeg, donc avcodec_find_decoder_by_name("h264_vaapi") n'a pas de sens: c'est nullptr. Je ne sais pas à quel point il est plus difficile (ou peut-être plus simple?) De mettre en œuvre le décodage via VA-API, car tous les exemples que j'ai vus étaient plutôt intimidants. J'ai donc choisi de ne pas utiliser VA-API et j'ai dû implémenter l'accélération pour une carte Intel. Heureusement pour moi, il existe une bibliothèque VDPAU (pilote?) Qui fonctionne sur VA-API. Vous pouvez donc utiliser VDPAU sur des cartes Intel!

J'ai utilisé le suivant link pour le configurer sur mon Ubuntu.

Vous pouvez également vérifier les commentaires sur la question initiale, où @Timothy_G a également mentionné des liens concernant VA-API.

40
ixSci