c++ - por - FFMPEG: multiplexación de flujos con diferente duración
multiplexacion por division de frecuencia ejemplos (1)
Estoy multiplexando secuencias de video y audio. El flujo de video proviene de los datos de imagen generados. El flujo de audio proviene de un archivo aac. Algunos archivos de audio son más largos que el tiempo total de video que configuré, por lo que mi estrategia para detener la transmisión de audio cuando su tiempo sea mayor que el tiempo total de video (el último que controlo por número de cuadros de video codificados).
No pondré aquí todo el código de configuración, pero es similar al ejemplo muxing.c del último repositorio de FFMPEG. La única diferencia es que uso el flujo de audio del archivo, como dije, no del cuadro codificado generado sintéticamente. Estoy bastante seguro de que el problema está en mi sincronización incorrecta durante el bucle de muxer. Esto es lo que hago:
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);
}
}
Ahora, la parte de video es impecable. Pero si la pista de audio es más larga que la duración del video, estoy obteniendo una duración total del video de aproximadamente 5% a 20%, y está claro que el audio está contribuyendo a que los cuadros de video se terminen exactamente donde se supone que deben estar.
El ''truco'' más cercano con el que vine es esta parte:
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;
}
Aquí estaba tratando de comparar los siguientes next_pts
del flujo de audio con el tiempo total establecido para el archivo de video, que es de 5 segundos. Al establecer r = {60,1}
estoy convirtiendo esos segundos en la base de tiempo de la transmisión de audio. Al menos eso es lo que creo que estoy haciendo. Con este truco, obtengo una desviación muy pequeña de la duración correcta de la película al usar archivos AAC estándar, que es una frecuencia de muestreo de 44100, estéreo. Pero si pruebo con muestras más problemáticas, como AAC 16000, mono, entonces el archivo de video agrega casi un segundo a su tamaño. Apreciaré si alguien puede señalar lo que estoy haciendo mal aquí.
Nota importante: no configuro la duración en ninguno de los contextos. Controlo la finalización de la sesión muxing, que se basa en el recuento de fotogramas de video. El flujo de entrada de audio tiene una duración, por supuesto, pero no me ayuda ya que la duración del video es lo que define la duración de la película.
ACTUALIZAR:
Este es el segundo intento de recompensa.
ACTUALIZACIÓN 2:
En realidad, mi marca de tiempo de audio de {den, num} estaba equivocada, mientras que {1,1} es el camino a seguir, como lo explica la respuesta. Lo que impedía que funcionara era un error en esta línea (mi error):
mAudioOutStream.next_pts += a_pkt.pts;
Que debe ser:
mAudioOutStream.next_pts = a_pkt.pts;
El error provocó un incremento exponencial de pts, lo que provocó un alcance muy temprano hasta el final de la secuencia (en términos de pts) y, por lo tanto, provocó que la transmisión de audio terminara mucho antes de lo que se suponía.
El problema es que le dices que compare el tiempo de audio dado con 5
tics a 60 seconds per tick
. Estoy realmente sorprendido de que funcione en algunos casos, pero creo que realmente depende de la time_base
de time_base
específica de la time_base
de audio dada.
Supongamos que el audio tiene una time_base
de time_base
de 1/25
y la transmisión es de 6
segundos, que es más de lo que desea, por lo que desea que av_compare_ts
devuelva 0
o 1
. Dadas estas condiciones, tendrás los siguientes valores:
mAudioOutStream.next_pts = 150
mAudioOutStream.enc->time_base = 1/25
Así llamas a av_compare_ts
con los siguientes parámetros:
ts_a = 150
tb_a = 1/25
ts_b = 5
tb_b = 60/1
Ahora veamos la implementación 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;
}
Teniendo en cuenta los valores anteriores, obtienes:
a = 1 * 1 = 1
b = 60 * 25 = 1500
Entonces av_rescale_rnd
se llama con estos parámetros:
a = 150
b = 1
c = 1500
rnd = AV_ROUND_DOWN
Teniendo en cuenta nuestros parámetros, podemos en realidad av_rescale_rnd
toda la función av_rescale_rnd
en la siguiente línea. (No av_rescale_rnd
todo el cuerpo de la función para av_rescale_rnd
ya que es bastante largo, pero puedes verlo here ).
return (a * b) / c;
Esto devolverá (150 * 1) / 1500
, que es 0
.
Por av_rescale_rnd(ts_a, a, b, AV_ROUND_DOWN) < ts_b
tanto, av_rescale_rnd(ts_a, a, b, AV_ROUND_DOWN) < ts_b
se resolverá como true
, porque 0
es más pequeño que ts_b
( 5
), y así av_compare_ts
devolverá -1
, que no es exactamente lo que desea.
Si cambias tu r
a 1/1
debería funcionar, porque ahora tus 5
serán tratados como 5 seconds
:
ts_a = 150
tb_a = 1/25
ts_b = 5
tb_b = 1/1
En av_compare_ts
ahora obtenemos:
a = 1 * 1 = 1
b = 1 * 25 = 25
Entonces av_rescale_rnd
se llama con estos parámetros:
a = 150
b = 1
c = 25
rnd = AV_ROUND_DOWN
Esto devolverá (150 * 1) / 25
, que es 6
.
6
es mayor que 5
, la condición falla y av_rescale_rnd
se llama nuevamente, esta vez con:
a = 5
b = 25
c = 1
rnd = AV_ROUND_DOWN
que devolverá (5 * 25) / 1
, que es 125
. Eso es más pequeño que 150
, por lo tanto, se devuelve 1
y se soluciona su problema.
En caso de que step_size sea mayor que 1
Si el step_size
de su flujo de audio no es 1
, necesita modificar su r
para step_size = 1024
en cuenta, por ejemplo, step_size = 1024
:
r = { 1, 1024 };
Repasemos rápidamente lo que sucede ahora:
En ~ 6 segundos:
mAudioOutStream.next_pts = 282
mAudioOutStream.enc->time_base = 1/48000
av_compare_ts
obtiene los siguientes parámetros:
ts_a = 282
tb_a = 1/48000
ts_b = 5
tb_b = 1/1024
Así:
a = 1 * 1024 = 1024
b = 1 * 48000 = 48000
Y en av_rescale_rnd
:
a = 282
b = 1024
c = 48000
rnd = AV_ROUND_DOWN
(a * b) / c
dará (282 * 1024) / 48000
= 288768 / 48000
que es 6
.
Con r={1,1}
habrías obtenido 0
nuevo, porque habría calculado (281 * 1) / 48000
.