音视频处理FFMPeg开发实战(4) -- 抓取视频存为单张图片

本例演示如何打开一个媒体文件或网络媒体流文件 ,解码后存为jpeg图片。

定义全局变量

AVFormatContext *g_inputContext = NULL;
AVFormatContext * g_outputContext;
int64_t g_lastReadPacktTime = 0;

1、打开一个媒体输入文件

打开媒体文件,读取媒体文件的数据包,分析其中的流信息。分析其帧率。

int OpenInputMediaFile(string inputUrl)
{
	//创建一个AVFormatContext类型变量并初始化默认参数
	g_inputContext = avformat_alloc_context();

	// 取得时间(单位为microsecond,微秒)
	g_lastReadPacktTime = av_gettime();
	
	// 填写检测是否中止阻塞功能的回调函数。
	g_inputContext->interrupt_callback.callback = interrupt_cb;

	// 强制以指定格式打开,为NULL时,自动检测其格式。
	AVInputFormat *fmt = NULL;

	// 强制以指定格式打开,为NULL时,自动检测其格式。
	AVDictionary **options = NULL;

	// 打开一个输入流(本地文件名或者是网络流地址URL),并解析其头部格式,并把信息填写到g_inputContext中。
	int ret = avformat_open_input(&g_inputContext, inputUrl.c_str(), fmt, options);
	if (ret < 0)
	{
		return  ret;
	}

	// 读取媒体文件的数据包,分析其中的流信息。分析其帧率
	ret = avformat_find_stream_info(g_inputContext, nullptr);

	return ret;
}

2、打开媒体输出文件

int OpenOutputMediaFile(string outUrl)
{
	// 创建一个用于输出的格式信息对像(AVFormatContext)
	// 格式指定jpeg
	int ret = avformat_alloc_output_context2(&g_outputContext, nullptr, "singlejpeg", outUrl.c_str());
	if (ret < 0)
	{
		goto Error;
	}

	// 打开文件
	ret = avio_open2(&g_outputContext->pb, outUrl.c_str(), AVIO_FLAG_WRITE, nullptr, nullptr);
	if (ret < 0)
	{
		goto Error;
	}

	// 找出源媒体中流的信息
	for (int i = 0; i < g_inputContext->nb_streams; i++)
	{
	
		// 由于是输出图片,所以音频流需要。
		if(g_inputContext->streams[i]->codec->codec_type == 
			AVMediaType::AVMEDIA_TYPE_AUDIO)
		{
			continue;
		}	
		// 用输入流的编码信息来创建一个流对像,
		AVStream * stream = avformat_new_stream(g_outputContext, g_inputContext->streams[i]->codec->codec);

		// 把输入流的编码信息拷贝到个输出流对的编码信息中,
		ret = avcodec_copy_context(stream->codec, g_inputContext->streams[i]->codec);
		if (ret < 0)
		{
			goto Error;
		}
	}

	// 分配流的私有信息,并把流的头部信息写到输出文件
	ret = avformat_write_header(g_outputContext, nullptr);
	if (ret < 0)
	{
		goto Error;
	}

	return ret;
Error:

	// 出错后关闭输出环境变量
	if (g_outputContext)
	{
		for (int i = 0; i < g_outputContext->nb_streams; i++)
		{
			avcodec_close(g_outputContext->streams[i]->codec);
		}
		avformat_close_input(&g_outputContext);
	}
	return ret;
}

3、读入一帧数据

shared_ptr<AVPacket> ReadPacketFrame()
{
	shared_ptr<AVPacket> packet(static_cast<AVPacket*>(av_malloc(sizeof(AVPacket))), [&](AVPacket *p) 
			{ av_packet_free(&p); 
				av_freep(&p); }
			);

	// 初始化packet
	av_init_packet(packet.get());
	
	// 取时间
	g_lastReadPacktTime = av_gettime();

	// 读入一帧数据到packet
	int ret = av_read_frame(g_inputContext, packet.get());
	if (ret >= 0)
	{
		return packet;
	}
	else
	{
		return nullptr;
	}
}

4、把数据写到输出文件中

void av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb)
{
	if (pkt->pts != AV_NOPTS_VALUE)
		pkt->pts = av_rescale_q(pkt->pts, src_tb, dst_tb);
	if (pkt->dts != AV_NOPTS_VALUE)
		pkt->dts = av_rescale_q(pkt->dts, src_tb, dst_tb);
	if (pkt->duration > 0)
		pkt->duration = av_rescale_q(pkt->duration, src_tb, dst_tb);
}
int WriteFrame(shared_ptr<AVPacket> packet)
{
	auto inputStream = inputContext->streams[packet->stream_index];
	auto outputStream = outputContext->streams[packet->stream_index];				
	av_packet_rescale_ts(packet.get(),inputStream->time_base,outputStream->time_base);
	return av_interleaved_write_frame(outputContext, packet.get());
}

5、初始化库

// 初始化FFMpeg库
void InitFFMpeg()
{
	av_register_all();    // 注册所有的编解码器
	avfilter_register_all();// 注册所有的滤镜
					
	// 初网络组件。如果输入输出文件,不涉及网络,可以不调用。
	avformat_network_init();
}

5、关闭资源

void CloseInput()
{
	if (g_inputContext != nullptr)
	{
		// 关闭输入环境,释放结构中的资源
		avformat_close_input(&g_inputContext); 
	}
}

void CloseOutput()
{
	if (g_outputContext != nullptr)
	{
		for (int i = 0; i < g_outputContext->nb_streams; i++)
		{
			AVCodecContext *codecContext = g_outputContext->streams[i]->codec;
			// 关闭输出编码器,释放相关的数据。
			avcodec_close(codecContext);
		}
		// 关闭输出环境,释放结构中的资源
		avformat_close_input(&g_outputContext);
	}
}

6、初始化编码器和解码器Context


int InitDecodeContext(AVStream *inputStream)
{	
	auto codecId = inputStream->codec->codec_id;
	auto codec = avcodec_find_decoder(codecId);
	if (!codec)
	{
		return -1;
	}
	int ret = avcodec_open2(inputStream->codec, codec, NULL);
	return ret;
}

int initEncoderCodec(AVStream* inputStream,AVCodecContext **encodeContext)
	{
		AVCodec *  picCodec;
		
		picCodec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);		
		(*encodeContext) = avcodec_alloc_context3(picCodec);
	
		(*encodeContext)->codec_id = picCodec->id;
		(*encodeContext)->time_base.num = inputStream->codec->time_base.num;
		(*encodeContext)->time_base.den = inputStream->codec->time_base.den;
		(*encodeContext)->pix_fmt =  *picCodec->pix_fmts;
		(*encodeContext)->width = inputStream->codec->width;
		(*encodeContext)->height =inputStream->codec->height;
		int ret = avcodec_open2((*encodeContext), picCodec, nullptr);
		if (ret < 0)
		{
			std::cout<<"open video codec failed"<<endl;
			return  ret;
		}
			return 1;
	}

7、解码视频帧

bool Decode(AVStream* inputStream,AVPacket* packet, AVFrame *frame)
{
	int gotFrame = 0;
	auto hr = avcodec_decode_video2(inputStream->codec, frame, &gotFrame, packet);
	if (hr >= 0 && gotFrame != 0)
	{
		return true;
	}
	return false;
}

8、编码图片数据

std::shared_ptr<AVPacket> 
Encode(AVCodecContext *encodeContext,AVFrame * frame)
{
	int gotOutput = 0;
	std::shared_ptr<AVPacket> pkt(static_cast<AVPacket*>(av_malloc(sizeof(AVPacket))), [&](AVPacket *p) { av_packet_free(&p); av_freep(&p); });
	av_init_packet(pkt.get());
	pkt->data = NULL;
	pkt->size = 0;
	int ret = avcodec_encode_video2(encodeContext, pkt.get(), frame, &gotOutput);
	if (ret >= 0 && gotOutput)
	{
		return pkt;
	}
	else
	{
		return nullptr;
	}
}

9、主控流程

int CaptureJpeg(string mediaURL, string jpegFile)
{
	InitFFMpeg();
	int ret = OpenInputMediaFile(mediaURL);
	if (ret >= 0)
	{
		ret = OpenOutputMediaFile(jpegFile);
	}

	if (ret <0) 
		goto Error;
	
	AVCodecContext *encodeContext = nullptr;
	InitDecodeContext(g_inputContext->streams[0]);
	AVFrame *videoFrame = av_frame_alloc();
	initEncoderCodec(g_inputContext->streams[0],&encodeContext);

	while (true)
	{
		// 从输入流中读取一个数据包
		auto packet = ReadPacketFromSource();
		// 如果视频帧,一般视频流的序号(stream_index)为0
		if(packet && packet->stream_index == 0)
		{
			if(Decode(g_inputContext->streams[0],packet.get(),videoFrame))
			{
				auto packetEncode = Encode(encodeContext,videoFrame);
				if(packetEncode)
				{
					ret = WritePacket(packetEncode);
					if(ret >= 0)// 抓一张就退出来。
					{
						break;
					}
				}

			}
		}
	}

	 av_frame_free(&videoFrame);
	 avcodec_close(encodeContext);	
Error:
	CloseInput(); // 关闭输入
	CloseOutput();// 关闭输出
	return 0;
}

10、下载工程文件

在Debug – x86下编译通过

下载源文件

发布了43 篇原创文章 · 获赞 9 · 访问量 2648

猜你喜欢

转载自blog.csdn.net/x879014419/article/details/105275815