【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
ffmpeg-截图功能实现
1. ffmpeg 命令行方式
ffmpeg.exe 10 -i possible.mkv test.jpg
或
ffmpeg.exe -ss 10 -i possible.mkv -y -f image2 -t 0.01 0.jpg
或
ffmpeg.exe -ss 00:00:10 -i possible.mkv -y -f image2 -frames:v 1 0.jpg
命令参数说明:
-i: 选择输入文件, 如"-i possible.mkv"是指定ffmpeg.exe输入的媒体文件为possible.mkv
-ss: 选择开始时间, 如"-ss 10"是将视频指向10秒, 也就是从10秒开始
-y: 强制覆盖文件(防止因为重名出错)
-f: 指定输出的文件格式, 如"-f image2"
-t: 指定操作的持续时间("-t 0.01"相当于取原视频中的第10s~10.01秒), 一般用于截取视频使用, 而不是用在截图上.
"-frames:v 1" for a single image
用于替换-t选项, 上面的-t选项在截图中使用是不合理的. 该操作可以指定1张图片
0.jpg // 为输出文件
你可能会遇到的error:
[image2 @ 000002159e25b4c0] Could not get frame filename number 2 from pattern '0.jpg'.
Use '-frames:v 1' for a single image,
or '-update' option,
or use a pattern such as %03d within the filename.
av_interleaved_write_frame(): Invalid argument
原因:
你截图没有选定操作的个数, 加上-frames:v 1选项或者-t 0.01即可.
2. ffmpeg 代码方式
配套博文:
ffmpeg-struct SwsContext使用心得
PPM / PGM / PBM 图像格式
源码:
#include "mainwindow.h"
#include <QApplication>
#include <QDebug>
#include <stdio.h>
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/pixfmt.h"
#include "libavutil/log.h"
}
/**
* 错误提示字符串
*/
#define ERR_STR_LEN 1024
static char err_buf[ERR_STR_LEN];
/**
* 用户自定义区
*/
#define START_FRAME 200 // 避免视频前面是黑的, 这有点坑人~
const char *file_path = "possible.mkv"; // 视频文件的路径, 支持linux/windows格式.
#define MYDEBUG qDebug() <<"[FILE:" <<__FILE__ <<",FUNC:" <<__FUNCTION__ <<",LINE:" <<__LINE__ <<"] "
void SaveFrame(AVFrame *pFrame, int width, int height,int index)
{
FILE *pFile;
char szFilename[32];
int x, y;
uint8_t *pTemp;
sprintf(szFilename, "debug/frame%d.ppm", index);
pFile=fopen(szFilename, "wb");
if(pFile == NULL)
return;
//写文件头, ppm格式: https://blog.csdn.net/MACMACip/article/details/105378600
fprintf(pFile, "P6 %d %d 255", width, height);
/**
* 写像素数据
* 使用for(y=0; y<height; y++){
* fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);
* } 也可, 如果你的RGB排布刚好是正确的像素的话~
*/
for(y=0; y<height; y++) {
pTemp = pFrame->data[0] + y*pFrame->linesize[0];
for(x=0; x<width; x++) {
fwrite(pTemp+2, 1, 1, pFile);
fwrite(pTemp+0, 1, 1, pFile);
fwrite(pTemp+1, 1, 1, pFile);
pTemp += 3;
}
}
fclose(pFile);
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
int ret = -1, index = 0, image_size = -1;
int videoStream = -1, got_picture = -1, numBytes= -1;
uint8_t *out_buffer = NULL;
AVFormatContext *pFormatCtx = NULL;
AVCodecContext *pCodecCtx = NULL;
AVCodec *pCodec = NULL;
AVPacket *packet = NULL;
AVFrame *pFrame = NULL, *pFrameRGB = NULL;
struct SwsContext *img_convert_ctx = NULL;
av_register_all();
pFormatCtx = avformat_alloc_context();
if(NULL == pFormatCtx){
MYDEBUG <<"avformat_alloc_context() failed.";
return -1;
}
// 1. 打开视频文件;
ret = avformat_open_input(&pFormatCtx, file_path, NULL, NULL);
if(ret < 0){
av_strerror(ret, err_buf, ERR_STR_LEN);
MYDEBUG <<err_buf;
avformat_free_context(pFormatCtx);
return -2;
}
// 2. 读取视频文件信息;
ret = avformat_find_stream_info(pFormatCtx, NULL);
if (ret < 0) {
av_strerror(ret, err_buf, ERR_STR_LEN);
MYDEBUG <<err_buf;
goto __EXIT_ERROR;
}
// 3. 获取视频流
for (int i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStream = i;
}
}
if (videoStream == -1) {
MYDEBUG <<"coun't find a video stream.";
goto __EXIT_ERROR;
}
// 4. 根据上面得到的视频流类型打开对应的解码器
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
MYDEBUG <<"Decoder not found.";
goto __EXIT_ERROR;
}
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
MYDEBUG <<"Ddecoder couldn't open.";
goto __EXIT_ERROR;
}
// 5. 分配并初始化一个视频packet
image_size = pCodecCtx->width * pCodecCtx->height;
packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一个packet
if(!packet){
MYDEBUG <<"malloc() failed.";
goto __EXIT_QUIT;
}
ret = av_new_packet(packet, image_size);
if(ret < 0){
av_strerror(ret, err_buf, ERR_STR_LEN);
MYDEBUG <<err_buf;
goto __EXIT_QUIT;
}
// 6. 分配两个frame分别存放yuv和rgb数据
pFrame = av_frame_alloc();
pFrameRGB = av_frame_alloc();
if(!pFrame || !pFrameRGB){
MYDEBUG <<"pFrame:" <<pFrame
<<", pFrameRGB:" <<pFrameRGB
<<" av_frame_alloc() failed";
goto __EXIT_QUIT;
}
// 7. 分配一个struct SwsContext结构体, 填充源图像和目标图像信息(为接下来的转换做准备)
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
AV_PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);
// 8. pFrameRGB和out_buffer都是已经申请到的一段内存, 会将pFrameRGB的数据按RGB24格式自动"关联(转换并放置)"到out_buffer。
numBytes = avpicture_get_size(AV_PIX_FMT_RGB24, pCodecCtx->width,pCodecCtx->height);
out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
ret = avpicture_fill((AVPicture *) pFrameRGB, out_buffer, AV_PIX_FMT_RGB24,
pCodecCtx->width, pCodecCtx->height);
if (!img_convert_ctx || numBytes<0 || !out_buffer || ret<0) {
MYDEBUG <<"img_convert_ctx:" <<img_convert_ctx
<<", numBytes:" <<numBytes
<<", out_buffer:" <<out_buffer
<<", ret:" <<ret;
goto __EXIT_QUIT;
}
while(1){
if (av_read_frame(pFormatCtx, packet) < 0) {
break;
}
if (packet->stream_index == videoStream) {
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);
if (ret < 0) {
printf("decode error.");
return -1;
}
}
if (got_picture) {
sws_scale(img_convert_ctx,
(uint8_t const * const *) pFrame->data,
pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
pFrameRGB->linesize);
if(index++ > START_FRAME){
SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, index-START_FRAME-1);
}
}
av_free_packet(packet);
if(index >START_FRAME+10){
break;
}
}
__EXIT_QUIT:
avcodec_close(pCodecCtx);
__EXIT_ERROR:
avformat_close_input(&pFormatCtx);
sws_freeContext(img_convert_ctx);
if(packet){
free(packet);
}
if(pFrame){
av_frame_free(&pFrame);
}
if(pFrameRGB){
av_frame_free(&pFrameRGB);
}
if(out_buffer){
av_free(out_buffer);
}
// MainWindow w;
// w.show();
//return a.exec();
return 0;
}