参考链接
ffmpeg + opencv 把摄像头画面保存为mp4文件_ffmpeg转化摄像头mp4-CSDN博客
调试环境
Ubuntu24.04
ffmpeg 6.1.1
opencv 4.6
g++ 13.2.0
C++源码
cpp
#include <iostream>
#include <sys/time.h>
#include <string>
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavdevice/avdevice.h>
#include <libswscale/swscale.h>
#include <libavcodec/avcodec.h>
#ifdef __cplusplus
} // endof extern "C"
#endif
#include <opencv2/opencv.hpp>
using namespace cv;
AVFrame *videoFrame = nullptr;
AVCodecContext *cctx = nullptr;
SwsContext *swsCtx = nullptr;
int frameCounter = 0;
AVFormatContext *ofctx = nullptr;
int fps = 30;
int width = 640;
int height = 480;
int bitrate = 2000;
long start_time = 0;
const std::string output_filename = "out.mp4";
// const std::string output_filename = "out.ts";
long getCurrentTime()
{
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}
static void pushFrame(uint8_t *data, long currentTime)
{
int err;
AVPacket pkt = {0};
int inLinesize[1] = {3 * cctx->width};
// From RGB to YUV
sws_scale(swsCtx, (const uint8_t *const *)&data, inLinesize, 0, cctx->height, videoFrame->data, videoFrame->linesize);
videoFrame->pts = ((currentTime - start_time) / 1000.0) * 90000;
std::cout << videoFrame->pts << " " << cctx->time_base.num << " " << cctx->time_base.den << " " << frameCounter << std::endl;
if ((err = avcodec_send_frame(cctx, videoFrame)) < 0)
{
std::cout << "Failed to send frame" << err << std::endl;
return;
}
pkt.buf = NULL;
pkt.side_data = NULL;
pkt.data = NULL;
pkt.size = 0;
pkt.flags |= AV_PKT_FLAG_KEY;
if (avcodec_receive_packet(cctx, &pkt) == 0)
{
static int counter = 0;
if (counter == 0)
{
FILE *fp = fopen("dump_first_frame1.dat", "wb");
fwrite(pkt.data, pkt.size, 1, fp);
fclose(fp);
}
std::cout << "pkt key: " << (pkt.flags & AV_PKT_FLAG_KEY) << " " << pkt.size << " " << (counter++) << std::endl;
uint8_t *size = ((uint8_t *)pkt.data);
std::cout << "first: " << (int)size[0] << " " << (int)size[1] << " " << (int)size[2] << " " << (int)size[3] << " " << (int)size[4] << " " << (int)size[5] << " " << (int)size[6] << " " << (int)size[7] << std::endl;
av_interleaved_write_frame(ofctx, &pkt);
av_packet_unref(&pkt);
}
}
static void finish()
{
// DELAYED FRAMES
AVPacket pkt = {0};
pkt.data = NULL;
pkt.size = 0;
while (true)
{
avcodec_send_frame(cctx, NULL);
if (avcodec_receive_packet(cctx, &pkt) == 0)
{
av_interleaved_write_frame(ofctx, &pkt);
av_packet_unref(&pkt);
}
else
{
break;
}
}
av_write_trailer(ofctx);
avformat_close_input(&ofctx);
}
static void free()
{
if (videoFrame)
{
av_frame_free(&videoFrame);
}
if (cctx)
{
avcodec_free_context(&cctx);
}
if (ofctx)
{
avformat_free_context(ofctx);
}
if (swsCtx)
{
sws_freeContext(swsCtx);
}
}
int main()
{
// VideoCapture capture("road_test.mp4");
VideoCapture capture(0);
Mat frame;
capture >> frame;
width = frame.cols;
height = frame.rows;
avdevice_register_all();
int err = avformat_alloc_output_context2(&ofctx, nullptr, nullptr, output_filename.c_str());
if (err)
{
std::cout << "can't create output context" << std::endl;
return -1;
}
const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec)
{
std::cout << "can't create codec" << std::endl;
return -1;
}
AVStream *stream = avformat_new_stream(ofctx, codec);
if (!stream)
{
std::cout << "can't find format" << std::endl;
return -1;
}
cctx = avcodec_alloc_context3(codec);
if (!cctx)
{
std::cout << "can't create codec context" << std::endl;
return -1;
}
stream->codecpar->codec_id = AV_CODEC_ID_H264;
stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
stream->codecpar->width = width;
stream->codecpar->height = height;
stream->codecpar->format = AV_PIX_FMT_YUV420P;
stream->codecpar->bit_rate = bitrate * 1000;
avcodec_parameters_to_context(cctx, stream->codecpar);
cctx->time_base = (AVRational){1, 1};
cctx->max_b_frames = 2;
cctx->gop_size = 12;
cctx->framerate = (AVRational){fps, 1};
if (stream->codecpar->codec_id == AV_CODEC_ID_H265)
{
av_opt_set(cctx, "preset", "ultrafast", 0);
}
avcodec_parameters_from_context(stream->codecpar, cctx);
if ((err = avcodec_open2(cctx, codec, NULL)) < 0)
{
std::cout << "Failed to open codec" << err << std::endl;
return -1;
}
if ((err = avio_open(&ofctx->pb, output_filename.c_str(), AVIO_FLAG_WRITE)) < 0)
{
std::cout << "Failed to open file" << err << std::endl;
return -1;
}
if ((err = avformat_write_header(ofctx, NULL)) < 0)
{
std::cout << "Failed to write header" << err << std::endl;
return -1;
}
av_dump_format(ofctx, 0, output_filename.c_str(), 1);
videoFrame = av_frame_alloc();
videoFrame->format = AV_PIX_FMT_YUV420P;
videoFrame->width = cctx->width;
videoFrame->height = cctx->height;
if ((err = av_frame_get_buffer(videoFrame, 32)) < 0)
{
std::cout << "Failed to allocate picture" << err << std::endl;
return -1;
}
swsCtx = sws_getContext(cctx->width, cctx->height, AV_PIX_FMT_BGR24, cctx->width, cctx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, 0, 0, 0);
start_time = getCurrentTime();
for (int i = 0; i < 100; ++i)
{
capture >> frame;
pushFrame(frame.data, getCurrentTime());
}
finish();
free();
return 0;
}
CMakeLists.txt
cpp
cmake_minimum_required(VERSION 3.16)
project(recordStudy)
find_package(OpenCV REQUIRED)
include_directories(. ${OpenCV_INCLUDE_DIRS})
link_libraries(avformat)
link_libraries(avcodec)
link_libraries(avutil)
link_libraries(swscale)
link_libraries(avdevice)
add_executable(recordStudy main.cpp)
target_link_libraries(recordStudy ${OpenCV_LIBS})
牢骚
不知道Ubuntu24.04更新了啥,感觉启动都很卡,干啥都卡