【助けて!】FFmpeg APIで可変フレームレート動画の保存をするには?【知恵をください!】
解決したいこと
この質問の一環で、
長時間録画及び画像の表示を同時に行える、OpenCV&ffmpeg製システムを開発しようとしています。
しかし、録画した結果のfpsが狂っている様子です。
これを可変フレームレート化により解決しようと思いますが、どうすればよいでしょうか。
自前で多くのサイトを巡ったり、ヘッダー内部の解説をGPTに和訳させるなどしてそれっぽい機能の関数を探しましたが、見当たりませんでした。
迷える子羊にどうかお導きをいただけませんでしょうか。
処理の高速化、最適化などについても助言いただけると嬉しいです。
環境
- Win10 x64 22h2
- VC++ MFC VS2015
- (試験環境)UVC規格準拠のノートPC内臓Webカメラ
- (本番環境)UVC規格準拠の2592x1944 12fps 500万画素 スクエアのUSB3.0カメラ
- OpenCV 3.4.7
- ffmpeg 23.09.13releaseのshared
コード
動画の録画および録画中のプレビュ表示の為に録画のコードを下記のように書きました。
こちらをベース(というかほぼまんまパクリである)としています。
- GUIとは別のスレッドで
Acquire
が周期的に実行されます。プレビュー表示も兼ねています。これは100msごとに実行されます。つまり仮に厳密な間隔で実行されると10fpsとなります。 - 録画開始押下により
StartRecord
で準備がされて、Acquire
内のif (m_isRecord)
が満たされ録画処理が行われます。 - 最後に
EndRecord
で後片付けしています。
int hoge::Acquire(BYTE *pAcqImage){
int iRet = 0;
int cols,rows;
HRESULT hr;
IplImage *frame = 0;
frame = cvCreateImage(cvSize(BitmapInfo.bmiHeader.biWidth, BitmapInfo.bmiHeader.biHeight), IPL_DEPTH_8U, 3);
while(1){
hr = pGrab->GetCurrentBuffer((long *)&(BitmapInfo.bmiHeader.biSizeImage), (long *)(frame->imageData));
if(FAILED(hr)){Sleep(50); continue;}
else{
if(frame->origin == 0){cvFlip(frame, frame, 0);}
cv::Mat cvColor = cv::cvarrToMat(frame);
if (m_isRecord) {
const int stride[4] = { static_cast<int>(cvColor.step[0]) };
sws_scale(swsctx, &cvColor.data, stride, 0, cvColor.rows, frameCV->data, frameCV->linesize);
frameCV->pts = frame_pts++;
avcodec_send_frame(cctx, frameCV);
while ((ret = avcodec_receive_packet(cctx, pkt)) >= 0) {
pkt->duration = 1;
av_packet_rescale_ts(pkt, cctx->time_base, vstrm->time_base);
av_write_frame(outctx, pkt);
av_packet_unref(pkt);
++nb_frames;
}
}
cv::Mat cvGray;
cv::cvtColor(cvColor, cvGray, CV_RGB2GRAY);
cols = BitmapInfo.bmiHeader.biWidth;
rows = BitmapInfo.bmiHeader.biHeight;
memcpy((uchar*)pAcqImage, cvGray.data, sizeof(uchar) * cols * rows);
break;// 正常終了
}
}
cvReleaseImage(&frame);
return iRet;
}
bool hoge::StartRecord(CString filename) {
CStringA csaBuf(filename);
const AVOutputFormat* outfmt = av_guess_format("mp4", nullptr, nullptr);
int ret = avformat_alloc_output_context2(&outctx, outfmt, nullptr, nullptr);
const AVCodec* vcodec = avcodec_find_encoder_by_name("libx265");
vstrm = avformat_new_stream(outctx, vcodec);
cctx = avcodec_alloc_context3(vcodec);
const AVRational dst_fps = { iCameraFramerate[m_iCH], 1 };
cctx->width = iCameraWidth[m_iCH];
cctx->height = iCameraHeight[m_iCH];
cctx->pix_fmt = AV_PIX_FMT_YUV420P;
cctx->time_base = av_inv_q(dst_fps);
cctx->framerate = dst_fps;
if (outctx->oformat->flags & AVFMT_GLOBALHEADER) {
cctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
ret = avcodec_open2(cctx, vcodec, nullptr);
avcodec_parameters_from_context(vstrm->codecpar, cctx);
swsctx = sws_getContext(
iCameraWidth[m_iCH], iCameraHeight[m_iCH],
AV_PIX_FMT_BGR24,
iCameraWidth[m_iCH], iCameraHeight[m_iCH],
cctx->pix_fmt, SWS_BILINEAR, nullptr, nullptr, nullptr);
frameCV = av_frame_alloc();
frameCV->width = iCameraWidth[m_iCH];
frameCV->height = iCameraHeight[m_iCH];
frameCV->format = static_cast<int>(cctx->pix_fmt);
ret = av_frame_get_buffer(frameCV, 32);
pkt = av_packet_alloc();
ret = avio_open2(
&outctx->pb, csaBuf.GetBuffer(),
AVIO_FLAG_WRITE, nullptr, nullptr);
csaBuf.ReleaseBuffer();
ret = avformat_write_header(outctx, nullptr);
frame_pts = 0;
nb_frames = 0;
m_isRecord = true;
return true;
}
void hoge::EndRecord() {
if (m_isRecord) {
av_write_trailer(outctx);
avio_close(outctx->pb);
av_packet_free(&pkt);
av_frame_free(&frameCV);
sws_freeContext(swsctx);
avcodec_free_context(&cctx);
avformat_free_context(outctx);
m_isRecord = false;
}
}