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.
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_base
1/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) / 1500
0
.
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 r
1/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) / 25
6
.
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) / 1
125
. 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
.