FFMPEG: мультиплексирование потоков различной длительности
Я мультиплексирование видео и аудио потоков. Видеопоток поступает из сгенерированных данных изображения. Аудиопоток поступает из файла aac. Некоторые аудиофайлы длиннее, чем общее время видео, которое я установил, поэтому моя стратегия остановки аудиопотока muxer, когда его время становится больше, чем общее время видео(последнее я контролирую по количеству кодированных видеокадров).
Я не буду ставить здесь весь код установки, но он похож на muxing.c пример из последнего РЕПО FFMPEG. Единственный разница в том,что я использую аудиопоток из файла, как я уже сказал, а не из синтетически сгенерированного кодированного кадра. Я уверен, что проблема в моей неправильной синхронизации во время цикла muxer.Вот что я делаю:--7-->
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);
}
}
теперь, видео часть безупречна. Но если звуковая дорожка длиннее продолжительности видео, я получаю общую длину видео больше примерно на 5% - 20%, и ясно, что аудио способствует этому, поскольку видеокадры заканчиваются именно там, где должны быть.
ближайший "Хак" я пришел с этой частью:
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;
}
здесь я пытался сравнить next_pts
аудиопотока с общим временем, установленным для видеофайла, которое составляет 5 секунд. Установив r = {60,1}
Я конвертирую эти секунды по time_base аудиопотока. По крайней мере, мне так кажется. С помощью этого хака я получаю очень небольшое отклонение от правильной длины фильма при использовании стандартных файлов AAC, это частота дискретизации 44100, стерео. Но если я тест с более проблемными образцами, такими как AAC sample rate 16000,mono - тогда видеофайл добавляет почти целую секунду к своему размеру.
Я буду признателен, если кто-то сможет указать, что я делаю неправильно здесь.
важное примечание: Я не устанавливаю продолжительность для любого из контекстов. Я контролирую завершение сеанса muxing, который основан на подсчете видеокадров.Входной поток аудио имеет продолжительность, конечно, но это не помогает мне, поскольку продолжительность видео определяет длину фильма.
обновление:
Это вторая попытка баунти.
обновление 2:
на самом деле,моя звуковая метка времени {den,num} была неправильной,в то время как {1,1} действительно путь, как объясняется ответом. То, что мешало ему работать, было ошибкой в этой строке (мой плохой):
mAudioOutStream.next_pts += a_pkt.pts;
что необходимо:
mAudioOutStream.next_pts = a_pkt.pts;
ошибка привела к экспоненциальному приращению pts, что вызвало очень ранний доступ к концу потока (с точки зрения pts) и поэтому аудиопоток был прерван намного раньше, чем предполагалось.
1 ответов
проблема в том, что вы говорите ему сравнить данное время звука с 5
галочки на 60 seconds per tick
. Я действительно удивлен, что он работает в некоторых случаях, но я думаю, что это действительно зависит от конкретного time_base
данного аудиопотока.
предположим, что звук имеет time_base
of 1/25
и поток на 6
секунд, что больше, чем вы хотите, поэтому вы хотите av_compare_ts
вернуться 0
или 1
. Учитывая эти условия, у вас будет следующее значения:
mAudioOutStream.next_pts = 150
mAudioOutStream.enc->time_base = 1/25
так ты называешь av_compare_ts
со следующими параметрами:
ts_a = 150
tb_a = 1/25
ts_b = 5
tb_b = 60/1
теперь давайте посмотрим на реализацию 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;
}
учитывая приведенные выше значения, вы получаете:
a = 1 * 1 = 1
b = 60 * 25 = 1500
затем av_rescale_rnd
вызывается с этими параметрами:
a = 150
b = 1
c = 1500
rnd = AV_ROUND_DOWN
учитывая наши параметры, мы можем фактически снять всю функцию av_rescale_rnd
к следующей строке. (Я не буду копировать все тело функции av_rescale_rnd
как это довольно долго, но вы можете посмотреть на него здесь.)
return (a * b) / c;
возвращает (150 * 1) / 1500
, которая составляет 0
.
av_rescale_rnd(ts_a, a, b, AV_ROUND_DOWN) < ts_b
разрешает true
, потому что 0
меньше, чем ts_b
(5
), а так av_compare_ts
вернутся -1
, что точно не то, что вы хотите.
если вы измените свой r
to 1/1
он должен работать, потому что теперь ваш 5
будет рассматриваться как 5 seconds
:
ts_a = 150
tb_a = 1/25
ts_b = 5
tb_b = 1/1
на av_compare_ts
теперь мы получаем:
a = 1 * 1 = 1
b = 1 * 25 = 25
затем av_rescale_rnd
вызывается с этими параметрами:
a = 150
b = 1
c = 25
rnd = AV_ROUND_DOWN
возвращает (150 * 1) / 25
, которая составляет 6
.
6
больше 5
условие не выполняется и av_rescale_rnd
вызывается снова, на этот раз с:
a = 5
b = 25
c = 1
rnd = AV_ROUND_DOWN
что вернет (5 * 25) / 1
, которая составляет 125
. Это меньше, чем 150
, таким образом 1
возвращается и вуаля ваш проблема решена.
в случае, если step_size больше 1
если step_size
вашего аудиопотока нет 1
, вам нужно изменить свой r
чтобы объяснить это, например step_size = 1024
:
r = { 1, 1024 };
давайте быстро напомним, что происходит сейчас:
на ~6 секунд:
mAudioOutStream.next_pts = 282
mAudioOutStream.enc->time_base = 1/48000
av_compare_ts
получает следующие параметры:
ts_a = 282
tb_a = 1/48000
ts_b = 5
tb_b = 1/1024
таким образом:
a = 1 * 1024 = 1024
b = 1 * 48000 = 48000
и в av_rescale_rnd
:
a = 282
b = 1024
c = 48000
rnd = AV_ROUND_DOWN
(a * b) / c
даст (282 * 1024) / 48000
= 288768 / 48000
что это 6
.
с r={1,1}
вы получили бы 0
опять же, потому что он рассчитал бы (281 * 1) / 48000
.