Libavcodec、異なるフレームレートでビデオをトランスコードする方法は?
-
10-10-2019 - |
質問
V4Lを介してカメラからビデオフレームをつかんでいます。MPEG4形式でそれらをトランスコードして、RTPを介して連続的にストリーミングする必要があります。
すべてが実際に「動作」しますが、再エンコード中はそうではありません:入力ストリームは15fpsを生成しますが、出力は25fpsで、すべての入力フレームは1つのビデオオブジェクトシーケンスで変換されます(簡単なチェックでこれを確認しました出力ビットストリーム)。受信機はMPEG4ビットストリームを正しく解析しているが、RTPパケット化は何らかの形で間違っていると思います。エンコードされたビットストリームを1つ以上の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 );
}
}
}
解決
最も簡単な解決策は、2つのスレッドを使用することです。最初のスレッドは、質問で概説されているすべてのことを実行します(デコード、スケーリング /カラースペース変換、コーディング)。部分的にトランスコードされたフレームは、2番目のスレッドと共有された中間キューに書き込まれます。このキューの最大長は、この特定のケース(より低いビットレートからより高いビットレートに変換)1フレームにあります。 2番目のスレッドは、このような入力キューからループフレームで読み取りされます。
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);
}
CPUの電力が十分であり、より高い忠実度と滑らかさが必要な場合、1つのフレームからだけでなく、何らかの補間によってより多くのフレーム(MPEGコーデックで使用される技術と同様)を計算できます。入力フレームタイムスタンプへのより近い出力フレームタイムスタンプは、この特定の入力フレームに割り当てる必要がある重みを増やす必要があります。