Qtベースのアプリケーションでビデオからフレームを抽出する必要があります。ffmpegライブラリを使用すると、フレームをAVFrameとしてフェッチできます。これは、アプリケーションの他の部分で使用するためにQImageに変換する必要があります。この変換は効率的である必要があります。これまでのところsws_scale()
、使用するのは正しい関数のようですが、どのソースと宛先のピクセル形式を指定するかはわかりません。
6 に答える
最初にデコードされたものをRGB色空間でAVFame
別のものに変換し、次にに変換する次の2ステップのプロセスを思い付きました。それは動作し、適度に高速です。AVFrame
QImage
src_frame = get_decoded_frame();
AVFrame *pFrameRGB = avcodec_alloc_frame(); // intermediate pframe
if(pFrameRGB==NULL) {
;// Handle error
}
int numBytes= avpicture_get_size(PIX_FMT_RGB24,
is->video_st->codec->width, is->video_st->codec->height);
uint8_t *buffer = (uint8_t*)malloc(numBytes);
avpicture_fill((AVPicture*)pFrameRGB, buffer, PIX_FMT_RGB24,
is->video_st->codec->width, is->video_st->codec->height);
int dst_fmt = PIX_FMT_RGB24;
int dst_w = is->video_st->codec->width;
int dst_h = is->video_st->codec->height;
// TODO: cache following conversion context for speedup,
// and recalculate only on dimension changes
SwsContext *img_convert_ctx_temp;
img_convert_ctx_temp = sws_getContext(
is->video_st->codec->width, is->video_st->codec->height,
is->video_st->codec->pix_fmt,
dst_w, dst_h, (PixelFormat)dst_fmt,
SWS_BICUBIC, NULL, NULL, NULL);
QImage *myImage = new QImage(dst_w, dst_h, QImage::Format_RGB32);
sws_scale(img_convert_ctx_temp,
src_frame->data, src_frame->linesize, 0, is->video_st->codec->height,
pFrameRGB->data,
pFrameRGB->linesize);
uint8_t *src = (uint8_t *)(pFrameRGB->data[0]);
for (int y = 0; y < dst_h; y++)
{
QRgb *scanLine = (QRgb *) myImage->scanLine(y);
for (int x = 0; x < dst_w; x=x+1)
{
scanLine[x] = qRgb(src[3*x], src[3*x+1], src[3*x+2]);
}
src += pFrameRGB->linesize[0];
}
より効率的なアプローチを見つけたら、コメントで知らせてください
手遅れですが、誰かが役に立つと思うかもしれません。ここから、少し短く見える同じ変換を行う手がかりを得ました。
そこで、デコードされたすべてのフレームで再利用されるQImageを作成しました。
QImage img( width, height, QImage::Format_RGB888 );
作成されたframeRGB:
frameRGB = av_frame_alloc();
//Allocate memory for the pixels of a picture and setup the AVPicture fields for it.
avpicture_alloc( ( AVPicture *) frameRGB, AV_PIX_FMT_RGB24, width, height);
最初のフレームがデコードされた後、この方法で変換コンテキストSwsContextを作成します(次のすべてのフレームで使用されます)。
mImgConvertCtx = sws_getContext( codecContext->width, codecContext->height, codecContext->pix_fmt, width, height, AV_PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);
そして最後に、デコードされたフレームごとに変換が実行されます。
if( 1 == framesFinished && nullptr != imgConvertCtx )
{
//conversion frame to frameRGB
sws_scale(imgConvertCtx, frame->data, frame->linesize, 0, codecContext->height, frameRGB->data, frameRGB->linesize);
//setting QImage from frameRGB
for( int y = 0; y < height; ++y )
memcpy( img.scanLine(y), frameRGB->data[0]+y * frameRGB->linesize[0], mWidth * 3 );
}
詳細については、リンクを参照してください。
より単純なアプローチだと思います。
void takeSnapshot(AVCodecContext* dec_ctx, AVFrame* frame)
{
SwsContext* img_convert_ctx;
img_convert_ctx = sws_getContext(dec_ctx->width,
dec_ctx->height,
dec_ctx->pix_fmt,
dec_ctx->width,
dec_ctx->height,
AV_PIX_FMT_RGB24,
SWS_BICUBIC, NULL, NULL, NULL);
AVFrame* frameRGB = av_frame_alloc();
avpicture_alloc((AVPicture*)frameRGB,
AV_PIX_FMT_RGB24,
dec_ctx->width,
dec_ctx->height);
sws_scale(img_convert_ctx,
frame->data,
frame->linesize, 0,
dec_ctx->height,
frameRGB->data,
frameRGB->linesize);
QImage image(frameRGB->data[0],
dec_ctx->width,
dec_ctx->height,
frameRGB->linesize[0],
QImage::Format_RGB888);
image.save("capture.png");
}
今日、私はに直接パスをテストしましたimage->bit()
がswscale
、最終的には機能するので、メモリにコピーする必要はありません。例えば:
/* 1. Get frame and QImage to show */
struct my_frame *frame = get_frame(source);
QImage *myImage = new QImage(dst_w, dst_h, QImage::Format_RGBA8888);
/* 2. Convert and write into image buffer */
uint8_t *dst[] = {myImage->bits()};
int linesizes[4];
av_image_fill_linesizes(linesizes, AV_PIX_FMT_RGBA, frame->width);
sws_scale(myswscontext, frame->data, (const int*)frame->linesize,
0, frame->height, dst, linesizes);
scanLineがバッファを介してシークしていることを発見しました。必要なのは、AVFrameにAV_PIX_FMT_RGB32を使用し、QImageにQImage::FORMAT_RGB32を使用することだけです。
次に、デコード後、memcpyを実行します
memcpy(img.scanLine(0), pFrameRGB->data[0], pFrameRGB->linesize[0] * pFrameRGB->height());
私は他の提案された解決策に問題がありました:
- 彼らは、AVFrame、SwsContext、または割り当てられたバッファのいずれかを解放することについて言及していませんでした。これにより、大量のメモリリークが発生しました(処理するフレームが数千ありました)。QImageは基になるデータに依存しており、データをコピーしないため、これらの問題をすべて簡単に解決することはできませんでした。バッファを直接解放する場合、QImageは解放されたデータを指し、中断します。これは、QImageのcleanupFunctionを使用して、画像が不要になったらバッファを解放することで解決できますが、他の問題があると、とにかくうまくいきませんでした。
- 場合によっては、QImage.bitsをsws_scaleに直接渡すという提案の1つは、QImageが最小32ビットで整列されているため機能しないことがあります。したがって、特定の寸法では、sws_scaleによって予想される幅と一致せず、各行が少しシフトして出力されます。
- 3番目の問題は、非推奨のAVPicture要素を使用していたことです。
私は別の質問に問題をリストしました。ピクセル形式の変換でAVFrameをQImageに変換し、最終的にQImageにコピーして安全に解放できる一時バッファーを使用した解決策を見つけました。
したがって、完全に機能し、効率的で、非推奨の関数呼び出しがない実装については、私の回答を参照してください:https ://stackoverflow.com/a/68212609/7360943