libavcodec, как перекодировать видео с разной частотой кадров?

Я захватываю видеокадры с камеры через v4l, и мне нужно перекодировать их в формат mpeg4, чтобы последовательно передавать их через RTP.

На самом деле все "работает", но есть кое-что, чего я не делаю при перекодировании: входной поток производит 15 кадров в секунду, а выходной - 25 кадров в секунду, и каждый входной кадр преобразуется в одну последовательность видеообъектов (я проверил это с помощью простой проверки на выходном битовом потоке). Я предполагаю, что получатель правильно анализирует битовый поток mpeg4, но пакетирование RTP как-то неправильно. Как я должен разделить закодированный битовый поток на один или несколько AVPacket? Может быть, я упускаю очевидное, и мне просто нужно искать маркеры кадров B/P, но я думаю, что неправильно использую API кодирования.

Вот фрагмент моего кода, основанный на доступных образцах ffmpeg:

// input frame
AVFrame *picture;
// input frame color-space converted
AVFrame *planar;
// input format context, video4linux2
AVFormatContext *iFmtCtx;
// output codec context, mpeg4
AVCodecContext *oCtx;
// [ init everything ]
// ...
oCtx->time_base.num = 1;
oCtx->time_base.den = 25;
oCtx->gop_size = 10;
oCtx->max_b_frames = 1;
oCtx->bit_rate = 384000;
oCtx->pix_fmt = PIX_FMT_YUV420P;

for(;;)
{
  // read frame
  rdRes = av_read_frame( iFmtCtx, &pkt );
  if ( rdRes >= 0 && pkt.size > 0 )
  {
    // decode it
    iCdcCtx->reordered_opaque = pkt.pts;
    int decodeRes = avcodec_decode_video2( iCdcCtx, picture, &gotPicture, &pkt );
    if ( decodeRes >= 0 && gotPicture )
    {
      // scale / convert color space
      avpicture_fill((AVPicture *)planar, planarBuf.get(), oCtx->pix_fmt, oCtx->width, oCtx->height);
      sws_scale(sws, picture->data, picture->linesize, 0, iCdcCtx->height, planar->data, planar->linesize);
      // encode
      ByteArray encBuf( 65536 );
      int encSize = avcodec_encode_video( oCtx, encBuf.get(), encBuf.size(), planar );
      // this happens every GOP end
      while( encSize == 0 )
        encSize = avcodec_encode_video( oCtx, encBuf.get(), encBuf.size(), 0 );
      // send the transcoded bitstream with the result PTS
      if ( encSize > 0 )
        enqueueFrame( oCtx->coded_frame->pts, encBuf.get(), encSize );
    }
  }
}

person ubik    schedule 16.11.2010    source источник


Ответы (1)


Самым простым решением было бы использовать два потока. Первый поток будет делать все, что указано в вашем вопросе (декодирование, масштабирование/преобразование цветового пространства, кодирование). Частично перекодированные кадры будут записаны в промежуточную очередь, совместно используемую вторым потоком. Максимальная длина этой очереди в этом конкретном случае (при преобразовании из более низкого в более высокий битрейт) будет 1 кадр. Второй поток будет считывать кадры цикла из входной очереди следующим образом:

void FpsConverter::ThreadProc()
{

timeBeginPeriod(1);
DWORD start_time = timeGetTime();
int frame_counter = 0;
while(!shouldFinish()) {
    Frame *frame = NULL;
    DWORD time_begin = timeGetTime();
    ReadInputFrame(frame);
    WriteToOutputQueue(frame);
    DWORD time_end = timeGetTime();
    DWORD next_frame_time = start_time + ++frame_counter * frame_time;
    DWORD time_to_sleep = next_frame_time - time_end;
    if (time_to_sleep > 0) {
        Sleep(time_to_sleep);
    }
}
timeEndPeriod(1);
}

Когда мощности процессора достаточно и требуется более высокая точность и плавность, вы можете вычислить выходной кадр не только из одного кадра, но и из нескольких кадров с помощью некоторой интерполяции (аналогично методам, используемым в кодеках mpeg). Чем ближе отметка времени выходного кадра к отметке времени входного кадра, тем больший вес вы должны присвоить этому конкретному входному кадру.

person truthseeker    schedule 28.07.2011