简单的基于FFMpeg的视频文件截图程序

一、概述

          熟悉了FFMPEG常用数据结构之后,按照零基础学习FFMPEG的学习轨迹,就应当实现一个“最简单的音频播放器”来继续熟悉FFMPEG的工作流程和一些常用的函数。实现播放器的文章请参考最简单的音频播放器。最简单的音频播放器同最简单的视频播放器一样,代码里有许多跟SDL相关的东西,如果不了解SDL,那么这个工程对初学者来说还是有难度的。于是我转向了实现简单的基于FFMPEG的视频文件的截图程序,里面没有任何SDL相关的东西,我觉得这个作为接触第一个FFMPEG的工程才是最适合的。

这个工程的原版可以在FFMPEG tutorial中文版中找到,名字叫:制作屏幕录像。它对解码后的数据帧保存为ppm格式的文件,我运行后得到的ppm文件始终打不开。鉴于ppm格式的文件不常见,我对其进行了修改,将图像数据保存为了jpeg格式,这样方便打开查看结果。

二、主要内容

      先附上视频文件截图程序流程图:

      

流程还是很清晰的,左边是解码部分,如果你已经做过或看过最简单的音频播放器,那么左边的解码流程应该是很熟悉了。右边和中间是编码部分,大部分与解码过程类似,只是需要自己创建流,设置编码环境,写文件头,文件尾等操作。每个处理过程基本上都对应一个函数,我也写了注释。

关于原版的工程(制作屏幕录像)的文档,可以在这里下载:http://download.csdn.net/detail/gameloft9/8724473。可以和本工程结合着看。

三、源代码

开发环境:VS 2013

#include <stdio.h>
#include<stdlib.h>
#include<string.h>

//包含库
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "SDL/SDL.h"
#include "libswresample/swresample.h"  
};

int main(int argc, char* argv[])
{
	//解码相关
	char* filepath = "1.mp4";//待解码文件

	AVFormatContext* pFormatCtx;//解封装格式上下文
	AVCodecContext* pCodecCtx;//解码上下文
	AVCodec			*pCodec;//解码器
	AVPacket packet; //解码前的数据包
	AVFrame *pFrame = av_frame_alloc();//解码后的数据帧
	int videoindex; //视频流编号
	int gotPicture_in = 0;
	unsigned i = 0;
	//编码相关
	AVStream* video_st;
	AVFormatContext* oFormatCtx;//封装格式上下文
	AVCodecContext* oCodecCtx;//编码码上下文
	AVCodec			*oCodec;//编码器
	AVOutputFormat* fmt;//输出封装格式上下文
	AVPacket pkt; //编码后的数据包
	uint8_t* picture_buf;//存放图片数据的缓存
	AVFrame*  picture = av_frame_alloc();
	int got_picture_out = 0;

	const char* out_file = "encode.jpg";    //输出文件

	int size; //指定色彩格式(例如YUV420P)的一帧图像大小
	int y_size;//输出帧的分辨率,即width*height


	//----------------------------------------------------------------
	av_register_all();//注册格式库和编解码库
	pFormatCtx = avformat_alloc_context();
	if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0)//打开输入文件并读取文件头
		return -1;
	if (avformat_find_stream_info(pFormatCtx, NULL) < 0)//读取流信息
		return -1;
	//找到视频流
	videoindex = -1;
	for (i = 0; i < (pFormatCtx->nb_streams); i++)
	{
		if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
			videoindex = i;
	}
	if (videoindex == -1)
	{
		return -1;
	}

	//获取编解码上下文
	pCodecCtx = pFormatCtx->streams[videoindex]->codec;
	//获取解码器
	pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
	if (pCodec == NULL)
		return -1;
	//打开解码器
	if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
		return -1;

	//编码组件设置部分
	//--------------------------------------------------------
	//根据文件名配分封装格式上下文
	avformat_alloc_output_context2(&oFormatCtx, NULL, NULL, out_file);
	fmt = oFormatCtx->oformat;
	//为输出文件创建输出数据缓存 
	if (avio_open(&oFormatCtx->pb, out_file, AVIO_FLAG_READ_WRITE) < 0){
		printf("Couldn't open output file.");
		return -1;
	}
	//添加新的流到封装格式上下文中
	//mux的时候,必须在写文件头的前调用,而且必须手动释放分配的内存
	video_st = avformat_new_stream(oFormatCtx, 0);
	if (video_st == NULL){
		return -1;
	}
	//设置编码上下文属性
	oCodecCtx = video_st->codec;
	oCodecCtx->codec_id = fmt->video_codec;
	oCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
	oCodecCtx->pix_fmt = PIX_FMT_YUVJ420P;//编码为YUVJ420P格式
	oCodecCtx->width =  pCodecCtx->width;
	oCodecCtx->height = pCodecCtx->height;
	//编码的时基必须由用户指定
	oCodecCtx->time_base.num = 1;
	oCodecCtx->time_base.den = 25;
	//匹配合适编码器
	oCodec = avcodec_find_encoder(oCodecCtx->codec_id);
	if (!oCodec){
		printf("Codec not found.");
		return -1;
	}
	//打开编码器
	if (avcodec_open2(oCodecCtx, oCodec, NULL) < 0){
		printf("Could not open codec.");
		return -1;
	}
	//计算图片大小
	size = avpicture_get_size(oCodecCtx->pix_fmt, oCodecCtx->width, oCodecCtx->height);
	picture_buf = (uint8_t *)av_malloc(size);
	if (!picture_buf)
	{
		return -1;
	}
	//avpicture_fill是让picture的data[0]、data[1]、data[2]等正确的指向av_frame_alloc()分配空间地址,
	//因为av_frame_alloc()分配的空间是一个线性地址(一个连续的缓冲区),而picture的data[]是分别指向
	//不同的平面的,如YUV420P中的Y平面、U平面、V平面,通过avpicture_fill之后,picture的data[]就分别指向
	//这个线性地址的不同位置了。完成avpicture_fill后,你对picture中的data[]进行操作时,实际是操作avcodec_alloc_frame()
	//分配的空间。
	avpicture_fill((AVPicture *)picture, picture_buf, oCodecCtx->pix_fmt, oCodecCtx->width, oCodecCtx->height);

	// 写流的头部到输出文件中去
	avformat_write_header(oFormatCtx, NULL);
	y_size = oCodecCtx->width * oCodecCtx->height;//图形大小
	av_new_packet(&pkt, y_size * 3);//为编码后的数据包分配内存大小
	//---------------------------------------------------------------------------------------------------------------

	//解码
	//这里是先解码然后再编码,然后封装为jpeg格式。
	//读取视频流	
	while (av_read_frame(pFormatCtx, &packet) >= 0) {
		// Is this a packet from the video stream?
		if (packet.stream_index == videoindex) {
			// Decode video frame
			int ret = avcodec_decode_video2(pCodecCtx, pFrame, &gotPicture_in, &packet);
			if (ret < 0)
				return -1;
			// Did we get a  frame?
			if (gotPicture_in) {
				//这里选取了第五十个数据帧进行截图
				if (++i > 50){
					//编码  
					ret = avcodec_encode_video2(oCodecCtx, &pkt, pFrame, &got_picture_out);
					if (ret < 0){
						printf("Encode Error.\n");
						return -1;
					}
					if (got_picture_out == 1){
						pkt.stream_index = video_st->index;
						ret = av_write_frame(oFormatCtx, &pkt);
					}
					//写文件尾
					av_write_trailer(oFormatCtx);
					printf("Encode Successful.\n");
					break;
				}
			}
		}
	}
	//释放内存
	av_free(picture_buf);

	av_free_packet(&packet);
	av_free_packet(&pkt);

	av_frame_free(&pFrame);
	av_frame_free(&picture);

	avcodec_close(pCodecCtx);
	avcodec_close(video_st->codec);

	//avformat_close_input(&pFormatCtx);
	avformat_free_context(pFormatCtx);
	avformat_free_context(oFormatCtx);
	return 0;
}

四、运行结果


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值