FFmpeg Visual Studio开发(二):视频解封装

上一篇我们在Visual Studio上搭建好了FFmpeg4的环境,本篇文章我们来学习FFmpeg的视频解封装。文章会把程序分成几段来讲解,最后会贴出完整代码。

准备工作

首先创建一个新的控制台工程,把FFmpeg的库配置好,不熟悉的朋友可以看看上一篇文章。接着跑一下测试程序看看配置是否成功。

#include "stdafx.h"
#include <iostream>

extern "C"
{
#include "libavformat/avformat.h"
};

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	cout << "hello FFmpeg" << endl;
	cout << avcodec_configuration() << endl;
	return 0;
}

打印了配置信息,说明目前是没有问题的了。(只能说是目前…)
在这里插入图片描述
解封装的过程很简单:打开文件 -> 读取流信息 -> 获取视频流 -> 循环解析帧数据。

打开文件

首先,我们需要打开一个视频文件。方式很简单,输入文件绝对路径(比如:C://WorkZone//Res//video.mp4),把它打开。如果打开失败,则退出程序。

AVFormatContext 结构体包含了流、格式、文件等相关的信息。

avformat_open_input 函数会创建一个 AVFormatContext 实例,并把文件信息写进去。这个函数要和avformat_close_input 函数成对使用,当不再使用 AVFormatContext 实例时,就要调用 avformat_close_input 释放内存。

int _tmain(int argc, _TCHAR* argv[])
{

	char fileName[100];
	cout << "file name: ";
	//输入文件名,如C://WorkZone//Res//video.mp4
	cin >> fileName;

	AVFormatContext *avFormatContext = NULL;

	//打开文件流,读取头信息
	int ret = avformat_open_input(&avFormatContext, fileName, NULL, NULL);

	if (ret < 0){//文件打开失败
		char buff[1024];
		//把具体错误信息写入buff
		av_strerror(ret, buff, sizeof(buff)-1);
		cout << "can't open file" << endl;
		cout << buff << endl;
		//释放AVFormatContext的内存
		avformat_close_input(&avFormatContext);
		return -1;
	}
	...

读取流信息

成功读取文件信息后,再把流信息读入 AVFormatContext 实例。同样的,读取失败时需要释放AVFormatContext 实例的内存。

	...
	//读取流信息
	ret = avformat_find_stream_info(avFormatContext, NULL);

	if (ret < 0){//读取流信息失败
		char buff[1024];
		//把具体错误信息写入buff
		av_strerror(ret, buff, sizeof(buff)-1);
		cout << "can't open stream" << endl;
		cout << buff << endl;
		//释放AVFormatContext的内存
		avformat_close_input(&avFormatContext);
		return -1;
	}
	...

获取视频流

此时 AVFormatContext 中已经有流的相关信息了,它可能有视频流信息、可能有音频流信息、也可能都有。FFmpeg是通过下标来判断是否有视频流或音频流的。

	...
	//获取视频流
	int videoIndex = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
	if (videoIndex < 0){
		cout << av_get_media_type_string(AVMEDIA_TYPE_VIDEO) << endl;
		//释放AVFormatContext的内存
		avformat_close_input(&avFormatContext);
		return -1;
	}
	//根据videoIndex获取视频流
	AVStream *videoStream = avFormatContext->streams[videoIndex];
	...

获取封装帧

AVPacket 结构体用于装载音频帧或视频帧。上面提到FFmpeg是通过下标来判断音频和视频的,因此AVPacket装载的帧类型就用下标来判断(videoIndex)。

注意这个while循环,程序就是在这里读取每一帧的数据的。得到了帧数据后还可以进行解码、转码等操作。本程序仅仅是打印了前10个视频帧的信息。

	...
	AVPacket *packet = av_packet_alloc();
	cout << "start read frames" << endl;

	int frameCount = 0;
	
	while (1){
		//读取帧
		ret = av_read_frame(avFormatContext, packet);
		
		//文件读完或已经读了10个帧,则结束
		if (ret < 0 || frameCount == 10){
			cout << "read finished" << endl;
			break;
		}

		if (packet->stream_index == videoIndex){
			cout << "---------frame-----------" << endl;
			cout << "pts:" << packet->pts << endl;
			cout << "dts:" << packet->dts << endl;
			cout << "pos:" << packet->pos << endl;
			frameCount++;
		}
	
		//把AVPacket里的数据清空,以便装载下一帧的信息
		av_packet_unref(packet);
	}
	...

完整程序

#include "stdafx.h"
#include <iostream>

extern "C"
{
#include "libavformat/avformat.h"
};

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{

	char fileName[100];
	cout << "file name: ";
	//输入文件名,如C://WorkZone//Res//video.mp4
	cin >> fileName;

	AVFormatContext *avFormatContext = NULL;

	//打开文件流,读取头信息
	int ret = avformat_open_input(&avFormatContext, fileName, NULL, NULL);

	if (ret < 0){//文件打开失败
		char buff[1024];
		//把具体错误信息写入buff
		av_strerror(ret, buff, sizeof(buff)-1);
		cout << "can't open file" << endl;
		cout << buff << endl;
		//释放AVFormatContext的内存
		avformat_close_input(&avFormatContext);
		return -1;
	}

	//读取流信息
	ret = avformat_find_stream_info(avFormatContext, NULL);

	if (ret < 0){//读取流信息失败
		char buff[1024];
		//把具体错误信息写入buff
		av_strerror(ret, buff, sizeof(buff)-1);
		cout << "can't open stream" << endl;
		cout << buff << endl;
		//释放AVFormatContext的内存
		avformat_close_input(&avFormatContext);
		return -1;
	}

	//打印格式信息
	av_dump_format(avFormatContext, 0, fileName, 0);
	cout << "stream num:" << avFormatContext->nb_streams << endl;
	cout << "duration:" << avFormatContext->duration << endl << endl;

	//获取视频流
	int videoIndex = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
	if (videoIndex < 0){
		cout << av_get_media_type_string(AVMEDIA_TYPE_VIDEO) << endl;
		//释放AVFormatContext的内存
		avformat_close_input(&avFormatContext);
		return -1;
	}

	//根据videoIndex获取视频流
	AVStream *videoStream = avFormatContext->streams[videoIndex];
	cout << "rate:" << av_q2d(videoStream->avg_frame_rate) << endl;
	cout << "size:" << videoStream->codecpar->width << "x" <<
		videoStream->codecpar->height << endl;
	cout << "codec:" << videoStream->codecpar->codec_id << endl;
	cout << "duration:" << videoStream->duration << endl;

	//提取码流
	AVPacket *packet = av_packet_alloc();
	cout << "start read frames" << endl;

	int frameCount = 0;

	while (1){
		ret = av_read_frame(avFormatContext, packet);
		
		//文件读完或已经读了10个帧,则结束
		if (ret < 0 || frameCount == 10){
			cout << "read finished" << endl;
			break;
		}

		if (packet->stream_index == videoIndex){
			cout << "---------frame-----------" << endl;
			cout << "pts:" << packet->pts << endl;
			cout << "dts:" << packet->dts << endl;
			cout << "pos:" << packet->pos << endl;
			frameCount++;
		}
	
		//把AVPacket里的数据清空,以便装载下一帧的信息
		av_packet_unref(packet);
	}

	//释放AVFormatContext的内存
	avformat_close_input(&avFormatContext);
	return 0;
}

最后

本篇文章讲述如何通过FFmpeg解封装视频。下一篇我们来学习FFmpeg视频解码。

项目工程在我的Gitee仓库里,感兴趣的朋友可以看看。

参考文章

《FFmpeg简单使用:解封装 ---- 基本流程》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值