Я мультиплексирую видео и аудио потоки. Видеопоток исходит из сгенерированных данных изображения. Аудиопоток поступает из файла aac. Некоторые аудиофайлы длиннее, чем общее время видео, которое я установил, поэтому моя стратегия - останавливать мультиплексор аудиопотока, когда его время становится больше, чем общее время видео (последний из которых я контролирую по количеству закодированных видеокадров).
Я не буду помещать здесь весь код установки, он похож на muxing.c из последнего репозитория FFMPEG. Единственная разница в том, что я использую аудиопоток из файла, как я уже сказал, а не из синтетически сгенерированного закодированного кадра. Я почти уверен, что проблема в моей неправильной синхронизации во время цикла мультиплексора. Вот что я делаю:
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 16000, моно - тогда видеофайл добавляет почти целую секунду к своему размеру. Буду признателен, если кто-нибудь укажет, что я здесь делаю неправильно.
Важное примечание: я не устанавливаю продолжительность ни для одного из контекстов. Я контролирую завершение сеанса мультиплексирования, который основан на количестве кадров видео. Поток аудиовхода, конечно, имеет длительность, но мне это не помогает, поскольку продолжительность видео определяет длину фильма.
ОБНОВИТЬ:
Это вторая попытка награждения.
ОБНОВЛЕНИЕ 2:
На самом деле моя временная метка звука {den, num} была неправильной, тогда как {1,1} действительно подходит, как объясняется в ответе. Что мешало ему работать, так это ошибка в этой строке (моя ошибка):
mAudioOutStream.next_pts += a_pkt.pts;
Что должно быть:
mAudioOutStream.next_pts = a_pkt.pts;
Ошибка приводила к экспоненциальному увеличению количества точек, что приводило к очень раннему достижению конца потока (в терминах точек) и, следовательно, приводило к прерыванию аудиопотока намного раньше, чем предполагалось.