• 多媒体开发(10):提取图片以及位图保存


    小白:提取视频中的图片吗?那很简单,播放视频再截图就行啦。

    播放视频再截图的做法,当然可以。但是,手动截图会太累而且无法保证准确度,特别是需要反复提取图片时,或者需要提取“105秒那一瞬间的美女图片”时,或者我需要每秒出一张图片时,那有别的办法吗?

    本文介绍,如何使用FFmpeg实现从视频中提取图片的功能。

    一般使用FFmpeg的方式,有两种,一种是使用FFmpeg的命令(也就是调用ffmpeg这个程序),另一种是调用FFmpeg的库文件。这里小程同样从命令行以及代码调用这两种方式,进行介绍。

    (一)使用FFmpeg命令来解决问题

    在安装FFmpeg后,打个命令就可以实现这个功能。对于FFmpeg的安装或调试,之前介绍过。

    提取图片可以这样,比如:

    ffmpeg -ss 00:00:5 -i moments.mp4 -vframes 1 -f image2 -y a.png

    参数的意思是这样的:

    ss表示开始提取图片的时间点,既可以用时分秒格式,也可以是多少秒。
    如果使用到这个参数,那应该把它作为第一个参数,因为可以让FFmpeg提速。
    
    i表示输入文件,就是视频文件。
    vframes表示拿多少帧,也就是多少张图片。注意,这个参数要放在-i参数之后。
    f表示提取出来的图片的格式。
    y表示覆盖已有同名的图片。
    

    再比如,可以这样:

    ffmpeg -i xxx.mp4 -r 1 -y -f image2 -t 5 -s 240*320 pc%3d.jpg

    参数的意思是这样的:

    r表示每秒提取图片的帧数,即帧率,默认是25fps,上面设置为一秒拿一张图。
    t表现持续提取多少秒,也可以用时分秒的格式来表示。
    s表出来的图片的尺寸。
    3%d表示以001、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库来解决问题

    通过写代码调用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
    ");
    		}
    	}
    	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
    ");
    				}
    				else {
    					av_log(NULL, AV_LOG_DEBUG, "av_read_frame error
    ");
    				}
    				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;
    }
    
    
    • 程序演示了把解码后的图片数据保存成位图的过程。如果有需要可以做更多的修改,比如av_seek_frame到适当的位置再开始解码与保存位图,也可以控制多少帧后保存一张位图,等等。
    • av_register_all注册“所有”,所有的编解码器、muxer与demuxer等等(前提是configure编译时有enable,才会真正使用到),这一步是关键的初始化工作,没有这一步,FFmpeg很可能不能如期工作。
    • avformat_open_input打开输入。“输入”是一个抽象,这里具体成文件。这一步之后,就获得了一些文件格式信息。
    • avformat_find_stream_info查找流的信息。多媒体数据由流组成,这一步就是获取媒体格式信息,有可能比较耗时。这一步后,流使用的编解码器被确定下来。
    • avcodec_find_decoder找到解码器。
    • avcodec_open2打开解码器。
    • av_read_frame读取一个packet,未解码。
    • avcodec_decode_video2解码一个视频帧。
    • sws_getContext获取并初始化一个SwsContext场景,swscontext不仅可以缩放图片,还可以转换颜色布局。
    • sws_scale缩放或转换。
    • 在调用sws_scale之前,对frame->data跟frame->linesize做的处理,是为了调整坐标系,让图片适合位图的坐标系(从下往上,从左往右),这样转换出来的位图才不会颠倒。
    • 这里选择的是24位的位图(没有调色板),在写入rgb数据前,先把文件头与位图信息写好。

    至此,在视频中提取图片的实现,就介绍完毕了。

    总结一下,本文从直接使用ffmpeg命令行,以及写代码调用FFmpeg库文件的两种方式入手,介绍了如何实现从视频中提取图片的功能。至此为止,有缘再见,see you.


    考考你

  • 相关阅读:
    java8 lambda表达式的使用
    关键字 static 静态
    关键字 enum 枚举
    关键字:final 最终的不可被改变的
    关键字 abstract 抽象
    Java中的关键字汇总(50个)
    关键字 assert 断言
    关键字 instanceof 实例
    java8新特性
    docker update restart=always container
  • 原文地址:https://www.cnblogs.com/freeself/p/14598017.html
Copyright © 2020-2023  润新知