Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Produire ses vidéos (audio + vidéo synchronisés ) sous Linux
Pour le prix de la webcam

Le , par ericb2

0PARTAGES

Pour ceux que cela intéresse, j'ai repris un projet (on dit "forké" et j'ai ajouté ce dont j'avais besoin pour créer ffmpeg-cpp2 sous Linux.

Il s'agit d'une petite API en C++ qui encapsule les bibliothèques qui constituent ffmpeg (libavformat, libavutil, libavdevice, libavcodec, ...)

L'utilisation basique de la webcam est donnée dans la petite démo qui s'appelle remux_webcam (voir ci-dessous). Mais les filtres, dont je ne parle pas ici, c'est encore plus génial !

L'API est basée sur la notion de flux entrée/sortie :
  • source audio : raw (par exemple venant de la webcam), ou frame (bande son venant d'une vidéo donnée ;
  • source vidéo (un fichier, un flux réseau, un flux raw (venant d'une webcam)
  • l'extraction du son ou de l'image ;
  • le filtrage (plus de 100 filtres, issus de ffmpeg : rotation, zoom, crop etc) ;
  • le démultiplexage : on extrait le son ou l'image d'un flux (le décodage est automatique) ;
  • on peut ensuite mélanger les flux que l'on a extrait avec un multiplexeur.


En gros, on peut faire ce qu'on veut, quelle que soit la source, et SANS être obligé d'utiliser l'interface graphique du développeur. Comme ça, vous faîtes ce que vous voulez.

Exemple : j'ai associé la bande son de Hells Bells (AC-DC) au début de la vidéo BigBuckBunny. ça rend pas mal du tout :-)

Le dépôt (framagit) est là : ffmpeg-cpp2 sur framagit

Afin de protéger mon travail, j'ai mis la licence GPL V3, mais je pense repasser un jour sous LGPL.

Dans les démos, on a plein d'exemples qui devraient fonctionner tout droit.

Voir : démos

Application à la webcam :

  • j'utilise alsa + v4l2 sous Linux (sous Windows, ça pourrait être dshow à la place de v4l2, et DirectSound à la place d'alsa)
  • le démultiplexeur pour la webcam (partie vidéo) est le même que pour les sources type fichier, mais j'ai surchargé le constructeur, pour des raisons d'utilisabilité
  • pour le son, j'ai implémenté une nouvelle classe, dérivant d'une source classique


Les problèmes rencontrés : la synchronisation, et la bande son qui ne durait quasiment pas. Le problème venait du fait qu'il fallait deux fils d'exécution séparés pour le son et la vidéo sur la même webcam. Sinon alsa (avec pulse ou hw:1,0 etc) fonctionne super bien (j'utilise Linuxmint)

Les dépendances :

  • ffmpeg (toutes les bibliothèques associées en fait. J'utilise la version 4.2.2
  • la libpthread (pour l'enregistrement séparé son / image synchrone)
  • certaines bibliothèques (mais on peut s'en passer) de codecs (h264, liblamemp3, libvpx-vp9, etc
  • v4l2 , qui est l'API standard pour Linux
  • la libasound (bibliothèque alsasound)
  • la bibliothèque c++ standard : std::chrono (pour l'instant, on enregistre une durée donnée, mais quand il y aura un bouton start/stop, la durée ne sera plus un problème)
  • std::fstream pour la suppression des fichiers audio et vidéo intermédiaires


TODO : implémenter VAAPI pour le décodage et l'encodage

Le contenu du programme est trivial, mais pour une vraie application, j'aurais créé des nouvelles classes (TODO). Les commentaires sont dans le code.

Initialisation :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
 * File remux_webcam.cpp
 * Copyright Eric Bachard / 2020 05 08
 * This document is under GPL v3 license
 * see : http://www.gnu.org/licenses/gpl-3.0.html
 */

#include <iostream>
#include <chrono>
#include <ffmpegcpp.h>
#include <thread>         // std::thread
#include <fstream>        // std::remove

static bool bRecording = false;

using namespace ffmpegcpp;
using std::string;
using std::cerr;

//#ifdef ALSA_BUFFER_SIZE_MAX
#undef ALSA_BUFFER_SIZE_MAX
#define ALSA_BUFFER_SIZE_MAX  524288

// les fichiers temporaires contenant le son et les images
const char * audio_file = "../videos/audio.mp4";  // aac (s32le ?)
const char * video_file = "../videos/video_H264.mp4";  // h264

Enregistrement Audio :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void record_Audio()
{
    const char * audioDevice = "pulse";  // fonctionne avec pulseaudio ici
    //const char * audioDevice = "hw:1,0";
    const char * audioDeviceFormat = "alsa";

    // on crée une instance du container audio
 
   Muxer* Amuxer  = new Muxer(audio_file);   

    // paramètres usuels
    int audioSampleRate = 44100;
    int audioChannels   = 2;


    // on choisit d'encoder le son avec le codec aac, avec une instance de l'encodeur qui suit 
    AudioCodec         *   audioCodec = new AudioCodec(AV_CODEC_ID_AAC);
    AudioEncoder       *   audioEncoder = new AudioEncoder(audioCodec, Amuxer);


    // définition de la source audio (voir le constructeur pour retrouver le mécanisme
    // habituel de ffmpeg
    RawAudioFileSource *   audioFile = new RawAudioFileSource( audioDevice,
                                                            audioDeviceFormat,
                                                            audioSampleRate,
                                                            audioChannels,
                                                            audioEncoder);

    // préparation
    audioFile->PreparePipeline();

    // bRecording est une variable globale, car on doit pouvoir terminer le fil d'exécution
    while (!audioFile->IsDone())
    {
        audioFile->Step();

        if (bRecording == false)
            audioFile->Stop();
    }

    Amuxer->Close();

    if (audioEncoder != nullptr)
        delete audioEncoder;

    delete Amuxer;
}

Même chose pour la vidéo :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void record_Video()
{
    int width  = 1280; // 1920;
    int height = 720;  // 1080;
    int fps = 24;  // Logitech prefered fps value

    AVRational frameRate = { 24, 1 };

    // These are example video and audio sources used below.
    const char * videoDevice = "/dev/video0";
    AVPixelFormat outputPixFormat= AV_PIX_FMT_NV12;

    Muxer* Vmuxer = new Muxer(video_file);
    H264Codec  * vcodec = new H264Codec();

    VideoEncoder * videoEncoder = new VideoEncoder(vcodec, Vmuxer, frameRate, outputPixFormat);

    Demuxer * demuxer  = new Demuxer(videoDevice, width, height, fps);
    demuxer->DecodeBestVideoStream(videoEncoder);
    demuxer->PreparePipeline();

    while (!demuxer->IsDone())
    {
        demuxer->Step();

        if (bRecording == false)
        {
            demuxer->Stop();
        }
    }

    // close the first muxers and save separately audio and video files to disk
    Vmuxer->Close();

    if (videoEncoder != nullptr)
        delete videoEncoder;

    delete Vmuxer;
}


Création et assemblage de la vidéo finale :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
void create_final_Video()
{
    const char * final_file = "../videos/final_video.mp4";  // h264 + aac (or vp9 + aac)

    Muxer* AVmuxer = new Muxer(final_file);

    AudioCodec * faudioCodec = new AudioCodec(AV_CODEC_ID_AAC);
    H264Codec  * fvcodec = new H264Codec();

    try
    {
        // Create encoders for both
        VideoEncoder* fvideoEncoder = new VideoEncoder(fvcodec, AVmuxer);
        AudioEncoder* faudioEncoder = new AudioEncoder(faudioCodec, AVmuxer);

        // Load both audio and video from a container
        Demuxer* videoContainer = new Demuxer(video_file);
        Demuxer* audioContainer = new Demuxer(audio_file);

        // Tie the best stream from each container to the output
        videoContainer->DecodeBestVideoStream(fvideoEncoder);
        audioContainer->DecodeBestAudioStream(faudioEncoder);

        // Prepare the pipeline. We want to call this before the rest of the loop
        // to ensure that the muxer will be fully ready to receive data from
        // multiple sources.
        videoContainer->PreparePipeline();
        audioContainer->PreparePipeline();

        // Pump the audio and video fully through.
        // To avoid big buffers, we interleave these calls so that the container
        // can be written to disk efficiently.
        while ( (!videoContainer->IsDone()) || (!audioContainer->IsDone()))
        {
            if (!videoContainer->IsDone())
                videoContainer->Step();

            if (!audioContainer->IsDone())
                audioContainer->Step();
        }

        // Save everything to disk by closing the muxer.
        AVmuxer->Close();
    }
    catch (FFmpegException e)
    {
        cerr << e.what() << "\n";
        throw e;
    }

    delete AVmuxer;
}


Et enfin, le programme principal :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
int main(void)
{
    // obligatoire sinon la webcam n'est pas intialisée correctement
    avdevice_register_all();
    //avformat_network_init(); // future use

   // globale, permet de terminer proprement les 2 fils audio et vidéo
    bRecording = true;

    // on démarre l'enregistrement, en appelant séparément les deux 
   // process qui seront de fait quasi-synchronisés 
    std::thread first (record_Audio);
    std::thread second (record_Video);

    // on va enregistrer 1 min = 60 secondes
    auto start = std::chrono::steady_clock::now();
    auto current_time = std::chrono::steady_clock::now();
         std::chrono::duration<double> elapsed_seconds = current_time - start;
    do
    {
        current_time = std::chrono::steady_clock::now();
        elapsed_seconds = current_time - start;

    } while ((elapsed_seconds.count()) < (60));


    // on annonce aux fils d'exécution qu'il faut terminer
    bRecording = false;

    // on recolle les morceaux, ce qui donne le temps de finaliser les
   // 2 fichiers temporaires, et de ne pas utiliser des fichiers en cours d'écriture

    first.join();
    second.join();


    // assemblage des 2 fichiers temporaires (audio / vidéo)
    create_final_Video();


    // c'est fini !!
    std::cout << "Encoding complete!" << "\n";


   // il peut être intéressant de conserver les fichiers temporaires
#define TEST
#ifdef TEST
    std::remove(audio_file);
    std::remove(video_file);

     bool failed = (std::ifstream(audio_file) || std::ifstream(video_file));

    if(failed)
    {
        std::perror("Error opening deleted file");
        return 1;
    }
#endif

    return 0;
}

Toute retour d'expérience et/ou aide est bienvenu, évidemment !!

Une erreur dans cette actualité ? Signalez-le nous !