FFMPEG: flux de multiplexage de durée différente

je multiplie les flux vidéo et audio. Le flux vidéo provient des données d'image générées. Le flux audio provient du fichier aac. Certains fichiers audio sont plus longs que le temps total de vidéo que j'ai défini de sorte que ma stratégie pour arrêter audio stream muxer lorsque son temps devient plus grand que le temps total de vidéo(le dernier que je contrôle par nombre de cadres vidéo encodés).

Je ne mettrai pas ici tout le code de configuration, mais il est similaire à multiplexage.c exemple du dernier rapport FFMPEG. La seule la différence est que j'utilise le flux audio à partir du fichier,comme je l'ai dit, pas à partir d'un cadre encodé de façon synthétique. Je suis presque sûr que le problème est dans ma mauvaise synchronisation pendant la boucle muxer.Voici ce que je fais:

void AudioSetup(const char* audioInFileName)
{
    AVOutputFormat* outputF = mOutputFormatContext->oformat;
    auto audioCodecId = outputF->audio_codec;

    if (audioCodecId == AV_CODEC_ID_NONE) {
        return false;
    }

    audio_codec = avcodec_find_encoder(audioCodecId);

    avformat_open_input(&mInputAudioFormatContext,
    audioInFileName, 0, 0);
    avformat_find_stream_info(mInputAudioFormatContext, 0);

    av_dump_format(mInputAudioFormatContext, 0, audioInFileName, 0);


    for (size_t i = 0; i < mInputAudioFormatContext->nb_streams; i++) {
        if (mInputAudioFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            inAudioStream = mInputAudioFormatContext->streams[i];

            AVCodecParameters *in_codecpar = inAudioStream->codecpar;
            mAudioOutStream.st = avformat_new_stream(mOutputFormatContext, NULL);
            mAudioOutStream.st->id = mOutputFormatContext->nb_streams - 1;
            AVCodecContext* c = avcodec_alloc_context3(audio_codec);
            mAudioOutStream.enc = c;
            c->sample_fmt = audio_codec->sample_fmts[0];
            avcodec_parameters_to_context(c, inAudioStream->codecpar);
            //copyparams from input to autput audio stream:
            avcodec_parameters_copy(mAudioOutStream.st->codecpar, inAudioStream->codecpar);

            mAudioOutStream.st->time_base.num = 1;
            mAudioOutStream.st->time_base.den = c->sample_rate;

            c->time_base = mAudioOutStream.st->time_base;

            if (mOutputFormatContext->oformat->flags & AVFMT_GLOBALHEADER) {
                c->flags |= CODEC_FLAG_GLOBAL_HEADER;
            }
            break;
        }
    }
}

void Encode()
{
    int cc = av_compare_ts(mVideoOutStream.next_pts, mVideoOutStream.enc->time_base,
    mAudioOutStream.next_pts, mAudioOutStream.enc->time_base);

    if (mAudioOutStream.st == NULL || cc <= 0) {
        uint8_t* data = GetYUVFrame();//returns ready video YUV frame to work with
        int ret = 0;
        AVPacket pkt = { 0 };
        av_init_packet(&pkt);
        pkt.size = packet->dataSize;
        pkt.data = data;
        const int64_t duration = av_rescale_q(1, mVideoOutStream.enc->time_base, mVideoOutStream.st->time_base);

        pkt.duration = duration;
        pkt.pts = mVideoOutStream.next_pts;
        pkt.dts = mVideoOutStream.next_pts;
        mVideoOutStream.next_pts += duration;

        pkt.stream_index = mVideoOutStream.st->index;
        ret = av_interleaved_write_frame(mOutputFormatContext, &pkt);
    } else
    if(audio_time <  video_time) {
        //5 -  duration of video in seconds
        AVRational r = {  60, 1 };

        auto cmp= av_compare_ts(mAudioOutStream.next_pts, mAudioOutStream.enc->time_base, 5, r);
        if (cmp >= 0) {
            mAudioOutStream.next_pts = (int64_t)std::numeric_limits<int64_t>::max();
            return true; //don't mux audio anymore
        }

        AVPacket a_pkt = { 0 };
        av_init_packet(&a_pkt);

        int ret = 0;
        ret = av_read_frame(mInputAudioFormatContext, &a_pkt);
        //if audio file is shorter than stop muxing when at the end of the file
        if (ret == AVERROR_EOF) {
            mAudioOutStream.next_pts = (int64_t)std::numeric_limits<int64_t>::max(); 
            return true;
        }
        a_pkt.stream_index = mAudioOutStream.st->index;

        av_packet_rescale_ts(&a_pkt, inAudioStream->time_base, mAudioOutStream.st->time_base);
        mAudioOutStream.next_pts += a_pkt.pts;

        ret = av_interleaved_write_frame(mOutputFormatContext, &a_pkt);
    }
}

Maintenant, la partie vidéo est impeccable. Mais si la piste audio est plus longue que la durée de la vidéo, je vais obtenir la longueur totale de la vidéo plus d'environ 5% - 20%, et il est clair que l'audio contribue à cela que les cadres vidéo sont finis exactement où il est censé pour être.

Le plus proche de "hack" je suis venu avec cette partie:

AVRational r = {  60 ,1 };
auto cmp= av_compare_ts(mAudioOutStream.next_pts, mAudioOutStream.enc->time_base, 5, r);
if (cmp >= 0) {
    mAudioOutStream.next_pts = (int64_t)std::numeric_limits<int64_t>::max();
    return true;
} 

Ici j'ai essayé de comparer next_pts du flux audio avec le temps total défini pour le fichier vidéo,qui est de 5 secondes. Par le paramètre r = {60,1} je convertis ces secondes par la time_base du flux audio. Du moins, c'est ce que je crois que je fais. Avec ce hack, Je m'écarte très légèrement de la bonne longueur de film en utilisant des fichiers AAC standard,c'est un taux d'échantillon de 44100,stéréo. Mais si je tester avec des échantillons plus problématiques,comme L'AAC taux d'échantillonnage 16000, mono - puis le fichier vidéo ajoute presque une seconde entière à sa taille. J'apprécierai si quelqu'un peut me faire remarquer ce que je fais de mal ici.

Note importante: Je ne fixe pas la durée pour aucun des contextes. Je contrôle la fin de la session de mixage, qui est basée sur le comptage des images vidéo.Le flux d'entrée audio a la durée, bien sûr, mais cela ne m'aide pas car la durée de la vidéo est ce qui définit la longueur du film.

mise à jour:

c'est la deuxième tentative de prime.

UPDATE 2:

en fait, mon horodatage audio de {den, num} était erroné, alors que {1,1} est en effet le chemin à suivre, comme expliqué par la réponse. Ce qui l'empêchait de fonctionner était un bug dans cette ligne (ma mauvaise):

     mAudioOutStream.next_pts += a_pkt.pts;

Qui doit être:

     mAudioOutStream.next_pts = a_pkt.pts;

le bogue a entraîné une augmentation exponentielle de pts, ce qui a causé une atteinte très précoce à l'extrémité du cours d'eau (en termes de pts) et par conséquent, le flux audio a été terminé beaucoup plus tôt que prévu.

22
demandé sur Michael IV 2018-03-18 01:10:23

1 réponses

le problème est que vous lui dites de comparer le temps audio donné avec 5 tiques 60 seconds per tick. Je suis en fait surpris que cela fonctionne dans certains cas, mais je suppose que cela dépend vraiment de la spécificité time_base le flux audio.

supposons que l'audio ait un time_base1/25 et le flux est à 6 secondes, ce qui est plus que ce que vous voulez, si vous voulez av_compare_ts retour 0 ou 1. Compte tenu de ces conditions, vous aurez les suivants valeurs:

mAudioOutStream.next_pts = 150
mAudioOutStream.enc->time_base = 1/25

ainsi vous appelez av_compare_ts avec les paramètres suivants:

ts_a = 150
tb_a = 1/25
ts_b = 5
tb_b = 60/1

voyons maintenant la mise en oeuvre de av_compare_ts:

int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b)
{
    int64_t a = tb_a.num * (int64_t)tb_b.den;
    int64_t b = tb_b.num * (int64_t)tb_a.den;
    if ((FFABS(ts_a)|a|FFABS(ts_b)|b) <= INT_MAX)
        return (ts_a*a > ts_b*b) - (ts_a*a < ts_b*b);
    if (av_rescale_rnd(ts_a, a, b, AV_ROUND_DOWN) < ts_b)
        return -1;
    if (av_rescale_rnd(ts_b, b, a, AV_ROUND_DOWN) < ts_a)
        return 1;
    return 0;
}

etant Donné les valeurs ci-dessus, vous obtenez:

a = 1 * 1 = 1
b = 60 * 25 = 1500

av_rescale_rnd est appelé avec ces paramètres:

a = 150
b = 1
c = 1500
rnd = AV_ROUND_DOWN

étant donné nos paramètres, nous pouvons en fait démonter toute la fonction av_rescale_rnd à la ligne suivante. (Je ne vais pas copier l'ensemble du corps de la fonction av_rescale_rnd comme il est assez long, mais vous pouvez regarder ce ici.)

return (a * b) / c;

retour (150 * 1) / 15000.

ainsi av_rescale_rnd(ts_a, a, b, AV_ROUND_DOWN) < ts_b résoudra true, parce que 0 est plus petit que ts_b (5), et donc av_compare_ts retour -1, ce qui n'est pas exactement ce que vous voulez.

si vous changez votre r1/1 il devrait fonctionner, parce que maintenant votre 5 seront en fait traités comme 5 seconds:

ts_a = 150
tb_a = 1/25
ts_b = 5
tb_b = 1/1

av_compare_ts nous dès maintenant:

a = 1 * 1 = 1
b = 1 * 25 = 25

av_rescale_rnd est appelé avec ces paramètres:

a = 150
b = 1
c = 25
rnd = AV_ROUND_DOWN

retour (150 * 1) / 256.

6 est supérieur à 5, la condition échoue, et av_rescale_rnd est appelé à nouveau, cette fois avec:

a = 5
b = 25
c = 1
rnd = AV_ROUND_DOWN

qui sera de retour (5 * 25) / 1125. Qui est plus petit que 150, donc 1 est retourné et voilà votre le problème est résolu.

si step_size est supérieur à 1

Si le step_size de votre flux audio n'est pas 1, vous devez modifier votre r pour compte pour que, par exemple,step_size = 1024:

r = { 1, 1024 };

récapitulons rapidement ce qui se passe maintenant:

À ~6 secondes:

mAudioOutStream.next_pts = 282
mAudioOutStream.enc->time_base = 1/48000

av_compare_ts reçoit les paramètres suivants:

ts_a = 282
tb_a = 1/48000
ts_b = 5
tb_b = 1/1024

ainsi:

a = 1 * 1024 = 1024
b = 1 * 48000 = 48000

et en av_rescale_rnd:

a = 282
b = 1024
c = 48000
rnd = AV_ROUND_DOWN

(a * b) / c donner (282 * 1024) / 48000= 288768 / 48000 qui est 6.

r={1,1} vous avez obtenu 0 encore une fois, parce qu'il serait calculé (281 * 1) / 48000.

2
répondu Max Vollmer 2018-04-16 08:42:44