从视频中获取图片是一个比较直观的例子,这里从一个基础的例子来查看FFmpeg相关api的使用,从mp4文件中获取一帧图像,保存为jpeg格式图片,mp4文件比较好准备,一般手机录屏文件就是mp4格式。
原理还是比较清楚,得到一个AVFrame后,再使用jpeg的编码器来转换
int getpic() {
std::string filename = "test.mp4"; // 输入MP4文件名
std::string outputFilename = "output.jpg"; // 输出图片文件名
int targetSecond = 1; // 目标秒数
AVFormatContext* formatContext = nullptr;
if (avformat_open_input(&formatContext, filename.c_str(), nullptr, nullptr) != 0) {
std::cerr << "Error opening input file" << std::endl;
return -1;
}
if (avformat_find_stream_info(formatContext, nullptr) < 0) {
std::cerr << "Error finding stream information" << std::endl;
avformat_close_input(&formatContext);
return -1;
}
const AVCodec* codec = nullptr;
int videoStreamIndex = -1;
for (unsigned int i = 0; i < formatContext->nb_streams; i++) {
if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStreamIndex = i;
codec = avcodec_find_decoder(formatContext->streams[i]->codecpar->codec_id);
break;
}
}
if (videoStreamIndex == -1 || codec == nullptr) {
std::cerr << "Error finding video stream or decoder" << std::endl;
avformat_close_input(&formatContext);
return -1;
}
AVCodecContext* codecContext = avcodec_alloc_context3(codec);
if (codecContext == nullptr) {
std::cerr << "Error allocating codec context" << std::endl;
avformat_close_input(&formatContext);
return -1;
}
if (avcodec_parameters_to_context(codecContext, formatContext->streams[videoStreamIndex]->codecpar) < 0) {
std::cerr << "Error setting codec parameters" << std::endl;
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
return -1;
}
if (avcodec_open2(codecContext, codec, nullptr) < 0) {
std::cerr << "Error opening codec" << std::endl;
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
return -1;
}
AVPacket packet;
av_init_packet(&packet);
// 计算目标时间戳
int64_t targetTimestamp = targetSecond * AV_TIME_BASE;
// 查找目标时间戳所对应的帧
AVFrame* frame = av_frame_alloc();
bool foundTargetFrame = false;
int count = 0;
while (av_read_frame(formatContext, &packet) >= 0) {
if (packet.stream_index == videoStreamIndex) {
int response = avcodec_send_packet(codecContext, &packet);
if (response < 0) {
std::cerr << "Error sending packet to decoder" << std::endl;
break;
}
count++;
response = avcodec_receive_frame(codecContext, frame);
if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) {
continue;
}
else if (response < 0) {
std::cerr << "Error receiving frame from decoder" << std::endl;
break;
}
// 检查帧的时间戳是否接近目标时间戳
/*
if (frame->pts >= targetTimestamp - (AV_TIME_BASE / 2) && frame->pts <= targetTimestamp + (AV_TIME_BASE / 2)) {
foundTargetFrame = true;
break;
}*/
if (count == 20) {
foundTargetFrame = true;
char outname[] = "out.jpg";
// savePicture(frame, outname);
break;
}
}
av_packet_unref(&packet);
}
if (!foundTargetFrame) {
std::cerr << "Target frame not found" << std::endl;
av_frame_free(&frame);
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
return -1;
}
// 将帧的数据保存为JPEG图片
AVFrame* rgbFrame = av_frame_alloc();
if (rgbFrame == nullptr) {
std::cerr << "Error allocating RGB frame" << std::endl;
av_frame_free(&frame);
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
return -1;
}
/*
struct SwsContext* swsContext
= sws_getContext(
codecContext->width, codecContext->height, codecContext->pix_fmt,
codecContext->width, codecContext->height, AV_PIX_FMT_RGB24,
SWS_BILINEAR, nullptr, nullptr, nullptr
);
if (swsContext == nullptr) {
std::cerr << "Error creating SwsContext" << std::endl;
av_frame_free(&frame);
av_frame_free(&rgbFrame);
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
return -1;
}
// 分配RGB帧的缓冲区
int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, codecContext->width, codecContext->height, 1);
uint8_t* buffer = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t));
av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, AV_PIX_FMT_RGB24, codecContext->width, codecContext->height, 1);
// 将解码后的帧转换为RGB格式
sws_scale(swsContext, frame->data, frame->linesize, 0, codecContext->height, rgbFrame->data, rgbFrame->linesize);
*/
// 保存RGB帧为JPEG图片
const AVCodec* jpegCodec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
if (jpegCodec == nullptr) {
std::cerr << "Error finding JPEG encoder" << std::endl;
av_frame_free(&frame);
av_frame_free(&rgbFrame);
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
return -1;
}
AVCodecContext* jpegCodecContext = avcodec_alloc_context3(jpegCodec);
if (jpegCodecContext == nullptr) {
std::cerr << "Error allocating JPEG codec context" << std::endl;
av_frame_free(&frame);
av_frame_free(&rgbFrame);
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
return -1;
}
jpegCodecContext->pix_fmt = AV_PIX_FMT_YUVJ420P;
jpegCodecContext->width = codecContext->width;
jpegCodecContext->height = codecContext->height;
// 设置编码器时间基
jpegCodecContext->time_base = { 1, 25 };//formatContext->streams[videoStreamIndex]->time_base;
if (avcodec_open2(jpegCodecContext, jpegCodec, nullptr) < 0) {
std::cerr << "Error opening JPEG codec" << std::endl;
av_frame_free(&frame);
av_frame_free(&rgbFrame);
avcodec_free_context(&codecContext);
avcodec_free_context(&jpegCodecContext);
avformat_close_input(&formatContext);
return -1;
}
AVPacket jpegPacket;
av_init_packet(&jpegPacket);
jpegPacket.data = nullptr;
jpegPacket.size = 0;
if (avcodec_send_frame(jpegCodecContext, frame) < 0) {//rgbFrame
std::cerr << "Error sending frame to JPEG encoder" << std::endl;
av_frame_free(&frame);
av_frame_free(&rgbFrame);
avcodec_free_context(&codecContext);
avcodec_free_context(&jpegCodecContext);
avformat_close_input(&formatContext);
return -1;
}
if (avcodec_receive_packet(jpegCodecContext, &jpegPacket) < 0) {
std::cerr << "Error receiving packet from JPEG encoder" << std::endl;
av_frame_free(&frame);
av_frame_free(&rgbFrame);
avcodec_free_context(&codecContext);
avcodec_free_context(&jpegCodecContext);
avformat_close_input(&formatContext);
return -1;
}
// 保存JPEG图像到文件
FILE* outputFile = fopen(outputFilename.c_str(), "wb");
if (outputFile == nullptr) {
std::cerr << "Error opening output file" << std::endl;
av_frame_free(&frame);
av_frame_free(&rgbFrame);
avcodec_free_context(&codecContext);
avcodec_free_context(&jpegCodecContext);
avformat_close_input(&formatContext);
return -1;
}
fwrite(jpegPacket.data, 1, jpegPacket.size, outputFile);
fclose(outputFile);
// 清理资源
av_frame_free(&frame);
av_frame_free(&rgbFrame);
av_packet_unref(&packet);
av_packet_unref(&jpegPacket);
avcodec_free_context(&codecContext);
return 1;
}
获取的图片看上去不是太清晰,字有些糊掉了
从AVFrame保存为jpg图片的处理可以有另外的一个方式,有些差异,
int savePicture(AVFrame* pFrame, char* out_name) {//编码保存图片
int width = pFrame->width;
int height = pFrame->height;
AVCodecContext* pCodeCtx = NULL;
AVFormatContext* pFormatCtx = avformat_alloc_context();
// 设置输出文件格式
pFormatCtx->oformat = av_guess_format("mjpeg", NULL, NULL);
// 创建并初始化输出AVIOContext
if (avio_open(&pFormatCtx->pb, out_name, AVIO_FLAG_READ_WRITE) < 0) {
printf("Couldn't open output file.");
return -1;
}
// 构建一个新stream
AVStream* pAVStream = avformat_new_stream(pFormatCtx, 0);
if (pAVStream == NULL) {
return -1;
}
AVCodecParameters* parameters = pAVStream->codecpar;
parameters->codec_id = pFormatCtx->oformat->video_codec;
parameters->codec_type = AVMEDIA_TYPE_VIDEO;
parameters->format = AV_PIX_FMT_YUVJ420P;
parameters->width = pFrame->width;
parameters->height = pFrame->height;
const AVCodec* pCodec = avcodec_find_encoder(pAVStream->codecpar->codec_id); //查找编码器
if (!pCodec) {
printf("Could not find encoder\n");
return -1;
}
pCodeCtx = avcodec_alloc_context3(pCodec); //为AVCodecContext分配内存
if (!pCodeCtx) {
fprintf(stderr, "Could not allocate video codec context\n");
exit(1);
}
if ((avcodec_parameters_to_context(pCodeCtx, pAVStream->codecpar)) < 0) {
fprintf(stderr, "Failed to copy %s codec parameters to decoder context\n",
av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
return -1;
}
// AVRational tmp = { 1, 25 };
pCodeCtx->time_base = { 1, 25 };
if (avcodec_open2(pCodeCtx, pCodec, NULL) < 0) { //打开编码器
printf("Could not open codec.");
return -1;
}
int ret = avformat_write_header(pFormatCtx, NULL);
if (ret < 0) {
printf("write_header fail\n");
return -1;
}
int y_size = width * height;
//Encode
// 给AVPacket分配足够大的空间
AVPacket pkt;
av_new_packet(&pkt, y_size * 3);
// 编码数据
ret = avcodec_send_frame(pCodeCtx, pFrame);
if (ret < 0) {
printf("Could not avcodec_send_frame.");
return -1;
}
// 得到编码后数据
ret = avcodec_receive_packet(pCodeCtx, &pkt);
if (ret < 0) {
printf("Could not avcodec_receive_packet");
return -1;
}
ret = av_write_frame(pFormatCtx, &pkt);
if (ret < 0) {
printf("Could not av_write_frame");
return -1;
}
av_packet_unref(&pkt);
//Write Trailer
av_write_trailer(pFormatCtx);
avcodec_close(pCodeCtx);
avio_close(pFormatCtx->pb);
avformat_free_context(pFormatCtx);
return 0;
}
参考资料
FFmpeg将视频转换成一帧一帧的jpeg图片(代码实现)_ffmpeg把视频转为一帧帧图片-CSDN博客