小白:提取視頻中的圖片嗎?那很簡單,播放視頻再截圖就行啦。ide
播放視頻再截圖的作法,固然也能夠。可是,手動地截圖會太累並且沒法保證準確度,特別是須要反覆提取圖片時,或者須要提取「105秒那一瞬間的美女圖片」時,或者我須要每秒出一張圖片時,那有別的辦法嗎?佈局
本文介紹,如何使用FFmpeg實現從視頻中提取圖片的功能。編碼
通常使用FFmpeg的方式,有兩種,一種是使用FFmpeg的命令(也就是調用ffmpeg這個程序),另外一種是調用FFmpeg的庫文件。這裏小程一樣從命令行以及代碼調用這兩種方式,進行介紹。命令行
在安裝FFmpeg後,打個命令就能夠實現這個功能。對於FFmpeg的安裝或調試,以前介紹過。3d
提取圖片能夠這樣,好比:調試
ffmpeg -ss 00:00:5 -i moments.mp4 -vframes 1 -f image2 -y a.pngcode
參數的意思是這樣的:orm
ss表示開始提取圖片的時間點,既能夠用時分秒格式,也能夠是多少秒。 若是使用到這個參數,那應該把它做爲第一個參數,由於可讓FFmpeg提速。 i表示輸入文件,就是視頻文件。 vframes表示拿多少幀,也就是多少張圖片。注意,這個參數要放在-i參數以後。 f表示提取出來的圖片的格式。 y表示覆蓋已有同名的圖片。
再好比,能夠這樣:視頻
ffmpeg -i xxx.mp4 -r 1 -y -f image2 -t 5 -s 240*320 pc%3d.jpgblog
參數的意思是這樣的:
r表示每秒提取圖片的幀數,即幀率,默認是25fps,上面設置爲一秒拿一張圖。 t表現持續提取多少秒,也能夠用時分秒的格式來表示。 s表出來的圖片的尺寸。 3%d表示以00一、002這樣的格式來命名輸出的圖片。
因而,
小白:那麼說,若是我發現視頻某個時間點有美女的話,那我就能夠用ss從這個時間點再前一點,而後用t來持續提取5秒,或者用vframes來提取幾十張,那就準沒漏了!也就是這樣:
ffmpeg -ss 10 -t 5 -r 1 -i Movie-1.mp4 -f image2 -y pc-temp/image%3d.jpg
小白:看,這是提取到的美女圖:
另外一方面,你在提取到若干成圖片後,有可能想把這些圖片編碼成視頻,這時一樣能夠藉助FFmpeg命令來完成。須要注意,圖片變成視頻,是須要視頻編碼器的,因此在安裝FFmpeg時須要把視頻編碼器也帶上(好比x264),這個小程在以前有所介紹。
把圖片編碼成視頻的命令是這樣的:
ffmpeg -f image2 -i img%3d.jpg test.mp4
img%d表示以"img001", "img002"這種命名的文件(也就是以前提取出來的圖片),按順序使用。注意f參數要在i參數以前。
你可能以爲mp4格式沒有gif格式通用,因而又有了把mp4轉成gif動態圖的需求,這時仍是能夠敲打ffmpeg命令:
ffmpeg -i hello.mp4 hello.gif
固然這只是簡單地把mp4轉成gif,你也能夠加上分辨率、碼率之類的參數來控制,這裏不細說。
經過寫代碼調用FFmpeg庫的方式來提取圖片,而且保存成24bit的位圖。
小程先貼上演示代碼,再在後面作一些解釋:
#include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" #include <stdio.h> #include <stdlib.h> typedef struct { unsigned int filesize; unsigned short reserved1; unsigned short reserved2; unsigned int dataoffset; }BITMAP_FILE_HEADER; typedef struct { unsigned int infosize; int width; int height; unsigned short planecount; unsigned short bitcount; unsigned int compressiontype; unsigned int imagedatasize; int xpixpermeter; int ypixpermeter; unsigned int colorusedcount; unsigned int colorimportantcount; }BITMAP_INFO; void extractpicture(const char* filepath) { av_register_all(); av_log_set_level(AV_LOG_DEBUG); AVFormatContext* formatContext = avformat_alloc_context(); int status = 0; int success = 0; int videostreamidx = -1; AVCodecContext* codecContext = NULL; status = avformat_open_input(&formatContext, filepath, NULL, NULL); if (status == 0) { status = avformat_find_stream_info(formatContext, NULL); if (status >= 0) { for (int i = 0; i < formatContext->nb_streams; i ++) { if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { videostreamidx = i; break; } } if (videostreamidx > -1) { codecContext = formatContext->streams[videostreamidx]->codec; AVCodec* codec = avcodec_find_decoder(codecContext->codec_id); if (codec) { status = avcodec_open2(codecContext, codec, NULL); if (status == 0) { success = 1; } } } } else { av_log(NULL, AV_LOG_DEBUG, "avformat_find_stream_info error\n"); } } if (success) { av_dump_format(formatContext, 0, filepath, 0); int gotframe = 0; AVFrame* frame = av_frame_alloc(); int decodelen = 0; int limitcount = 10; int pcindex = 0; unsigned char* rgbdata = (unsigned char*)malloc(avpicture_get_size(AV_PIX_FMT_RGB24, codecContext->width, codecContext->height)); AVFrame* rgbframe = av_frame_alloc(); avpicture_fill((AVPicture*)rgbframe, rgbdata, AV_PIX_FMT_RGB24, codecContext->width, codecContext->height); struct SwsContext* swscontext = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt, codecContext->width, codecContext->height, AV_PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL); while (pcindex < limitcount) { AVPacket packet; av_init_packet( &packet ); status = av_read_frame(formatContext, &packet); if (status < 0) { if (status == AVERROR_EOF) { av_log(NULL, AV_LOG_DEBUG, "read end for file\n"); } else { av_log(NULL, AV_LOG_DEBUG, "av_read_frame error\n"); } av_packet_unref(&packet); break; } else { if (packet.stream_index == videostreamidx) { decodelen = avcodec_decode_video2(codecContext, frame, &gotframe, &packet); if (decodelen > 0 && gotframe) { frame->data[0] = frame->data[0] + frame->linesize[0] * (codecContext->height - 1); frame->data[1] = frame->data[1] + frame->linesize[1] * (codecContext->height / 2 - 1); frame->data[2] = frame->data[2] + frame->linesize[2] * (codecContext->height / 2 - 1); frame->linesize[0] *= -1; frame->linesize[1] *= -1; frame->linesize[2] *= -1; sws_scale(swscontext, frame->data, frame->linesize, 0, codecContext->height, rgbframe->data, rgbframe->linesize); char filename[12] = {0}; sprintf(filename, "pc%03d.bmp", ++ pcindex); FILE* file = fopen(filename, "wb"); if (file) { int pixcount = codecContext->width * codecContext->height; BITMAP_FILE_HEADER fileheader = {0}; fileheader.filesize = 2+sizeof(BITMAP_FILE_HEADER)+sizeof(BITMAP_INFO)+pixcount * 3; fileheader.dataoffset = 0x36; BITMAP_INFO bmpinfo = {0}; bmpinfo.infosize = sizeof(BITMAP_INFO); bmpinfo.width = codecContext->width; bmpinfo.height = codecContext->height; bmpinfo.planecount = 1; bmpinfo.bitcount = 24; bmpinfo.xpixpermeter = 5000; bmpinfo.ypixpermeter = 5000; unsigned short ftype = 0x4d42; fwrite(&ftype, sizeof ftype, 1, file); fwrite(&fileheader, sizeof fileheader, 1, file); fwrite(&bmpinfo, sizeof bmpinfo, 1, file); fwrite(rgbframe->data[0], pixcount*3, 1, file); fclose(file); } } } } av_packet_unref(&packet); } av_frame_free(&rgbframe); free(rgbdata); av_frame_free(&frame); sws_freeContext(swscontext); } avformat_free_context(formatContext); } int main(int argc, char *argv[]) { extractpicture("moments.mp4"); return 0; }
至此,在視頻中提取圖片的實現,就介紹完畢了。
總結一下,本文從直接使用ffmpeg命令行,以及寫代碼調用FFmpeg庫文件的兩種方式入手,介紹瞭如何實現從視頻中提取圖片的功能。