ffmpeg读取packet数据

在ffmpeg中,使用AVPacket结构体表示视频文件中的压缩数据,也就是还未进行解压缩的原始视频帧与音频帧。

整体代码如下:

#include<iostream>
#define __STDC_CONSTANT_MACROS
extern "C"
{
#include "libavcodec/avcodec.h"
#include "include/libavformat/avformat.h"
#include "include/libswscale/swscale.h"
#include "include/libavdevice/avdevice.h"
}
using namespace std;

AVFormatContext* myFFMpegDemux(const char* path, AVFormatContext* ic);//打开视频文件
void myFFMpegShow(AVFormatContext* ic);//显示视频文件信息
const AVCodec* myFFMpegFindVideoCodec(AVFormatContext* ic, const AVCodec* vcodec, int* videoindex);//寻找视频流解码器
const AVCodec* myFFMpegFindAudioCodec(AVFormatContext* ic, const AVCodec* acodec, int* audioindex);//寻找音频流解码器
void myFFMpegOpenVideoCodec(AVFormatContext* ic, AVCodecContext *vc, int videoindex);//启动视频流解码器
void myFFMpegOpenAudioCodec(AVFormatContext* ic, AVCodecContext *ac, int audioindex);//启动音频流解码器

int main(int argc, char *argv[])
{
	const char* path = "ds.mov";//记录视频源文件的路径,这里视频文件ds.mov直接放在项目工程里面了,所以可以直接用视频名称
	//如果视频不在项目工程里面,路径的书写格式举例:D:\\code of visual studio\\ffmpegTest\\ffmpegTest\\ds.mov
	//路径要使用“\\”,不然会被视为转义字符
	cout << "TEST DEMUX" << endl;

	//初始化封装库
	//在新版本中av_register_all()被弃用了,可以根据代码里有无此函数判断ffmpeg版本

	//初始化网络库(可以打开rtsp,rtmp,http协议的流媒体视频)
	avformat_network_init();

	//解封装上下文
	AVFormatContext* ic = nullptr;//将其地址做为输入,会申请一块空间,将这块空间的地址赋给ic
	//解封装上下文AVFormatContext,是存储音视频封装格式中包含信息的结构体。

	//对视频进行解封装
	ic = myFFMpegDemux(path,ic);

	//获取并显示视频流与音频流的信息
	//myFFMpegShow(ic);

	//初始化解码器信息
	const AVCodec* vcodec = nullptr;
	const AVCodec* acodec = nullptr;

	//寻找合适的视频解码器与音频解码器
	int videoindex = -1;
	int audioindex = -1;
	vcodec = myFFMpegFindVideoCodec(ic, vcodec,&videoindex);
	acodec = myFFMpegFindAudioCodec(ic, acodec, &audioindex);
	
	
	//创建解码器上下文
	AVCodecContext *vc = avcodec_alloc_context3(vcodec);
	myFFMpegOpenVideoCodec(ic, vc, videoindex);
	AVCodecContext *ac = avcodec_alloc_context3(acodec);
	myFFMpegOpenAudioCodec(ic, ac, audioindex);

	AVPacket *packet;//创建packet指针
	packet = av_packet_alloc();//初始化

	cout << "av_read_frame start!" << endl;
	int ret = 0;
	int packetCount = 0;//当前的帧数
	int packetShowCount = 10;//设定的最多显示帧数
	while (true)//开始循环
	{
		ret = av_read_frame(ic, packet);//读取一个packet结构体
		if (ret < 0) {
			cout << "av_read_frame end!" << endl;
			break;
		}

		if (packetCount++ < packetShowCount) {
			if (packet->stream_index == audioindex) {//如果读到的是音频帧
				cout << "audio 显示时间戳" << packet->pts << endl;
				cout << "audio 解码时间戳" << packet->dts << endl;
				cout << "audio 压缩编码数据大小" << packet->size << endl;
				cout << "audio 数据的偏移地址" << packet->pos << endl;
				cout << "audio 时长:" << packet->duration * av_q2d(ic->streams[audioindex]->time_base) << endl;
			}
			else if (packet->stream_index == videoindex) {//如果读到的是视频帧
				cout << "video 显示时间戳" << packet->pts << endl;
				cout << "video 解码时间戳" << packet->dts << endl;
				cout << "video 压缩编码数据大小" << packet->size << endl;
				cout << "video 数据的偏移地址" << packet->pos << endl;
				cout << "video 时长:" << packet->duration * av_q2d(ic->streams[videoindex]->time_base) << endl;
			}
			else {
				cout << "unknown stream_index:" << packet->stream_index;//未能识别类型的帧
			}
		}
		av_packet_unref(packet);
	}

	//资源回收
	if (ic) {//如果封装上下文仍存在
		avformat_close_input(&ic);//释放资源,指针置零
		ic = nullptr;
		avcodec_close(vc);
		avcodec_close(ac);
		vcodec = nullptr;
		acodec = nullptr;
		vc = nullptr;
		ac = nullptr;
	}
	return 0;
}

AVFormatContext* myFFMpegDemux(const char* path,AVFormatContext* ic) {
	//对视频文件进行解封装操作
	int re = avformat_open_input(&ic, path, 0, nullptr);//0表示自动选择解封装器,设置一个返回值知道有无错误
	if (re != 0)//如果返回值不是0,说明打开出现错误
	{
		char buf[1024] = { 0 };
		av_strerror(re, buf, sizeof(buf) - 1);//记录错误
		cout << "open" << path << "failed!:" << buf << endl;//提示错误
		return nullptr;
	}
	cout << "open " << path << " success!" << endl;//提示成功
	return ic;
}

void myFFMpegShow(AVFormatContext* ic) {
	avformat_find_stream_info(ic, 0);
	//自行计算视频的总时长,以毫秒为单位,AV_TIME_BASE为1秒时长
	int total = ic->duration / (AV_TIME_BASE / 1000);
	cout << "total ms = " << total << endl;
	//定义了秒,小时,分钟
	int total_seconds, hour, minute, second;
	total_seconds = (ic->duration) / AV_TIME_BASE;
	hour = total_seconds / 3600;
	minute = (total_seconds % 3600) / 60;
	second = (total_seconds % 60);
	cout << "total duration " <<"hours: "<< hour <<"minutes: "<< minute <<"seconds: "<< second << endl;

	//获取视频流的详细信息,包括视频流与音频流
	av_dump_format(ic, 1, "2", 0);

	for (int i = 0; i < ic->nb_streams; i++)//对视频中所有的流进行遍历
	{
		AVStream* as = ic->streams[i];
		//音频
		if (as->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			cout << i << "音频" << endl;
			cout << "simple_rate" << as->codecpar->sample_rate << endl;
			cout << "format" << as->codecpar->format << endl;
			cout << "channels" << as->codecpar->channels << endl;
			cout << "codec_id" << as->codecpar->codec_id << endl;
		}
		//视频
		else if (as->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			cout << i << "视频" << endl;
			cout << "simple_rate" << as->codecpar->sample_rate << endl;
			cout << "format" << as->codecpar->format << endl;
			cout << "channels" << as->codecpar->channels << endl;
			cout << "codec_id" << as->codecpar->codec_id << endl;
		}
	}
}

const AVCodec* myFFMpegFindVideoCodec(AVFormatContext* ic, const AVCodec* vcodec,int* videoindex) {
	//找到视频流在streams[i]中的编号i
	*videoindex = av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, -1, -1, &vcodec, 0);
	//找合适的视频解码器,得到流对应的解码器编号
	vcodec = avcodec_find_decoder(ic->streams[*videoindex]->codecpar->codec_id);
	//如果没有找到流对应的解码器,需要判断一下
	if (!vcodec)
	{
		cout << "can't find the codec id" << ic->streams[*videoindex]->codecpar->codec_id << endl;
		return nullptr;
	}
	cout << " find the video codec id" << ic->streams[*videoindex]->codecpar->codec_id << endl;
	return vcodec;
}

const AVCodec* myFFMpegFindAudioCodec(AVFormatContext* ic,const AVCodec* acodec,int* audioindex) {
	//找到音频流在streams[i]中的编号i
	*audioindex = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, &acodec, 0);
	//找合适的音频解码器,得到流对应的解码器编号
	acodec = avcodec_find_decoder(ic->streams[*audioindex]->codecpar->codec_id);
	//如果没有找到流对应的解码器,需要判断一下
	if (!acodec) {
		cout << "can't find the codec id" << ic->streams[*audioindex]->codecpar->codec_id << endl;
		return nullptr;
	}
	cout << " find the audio codec id" << ic->streams[*audioindex]->codecpar->codec_id << endl;
	return acodec;
}

void myFFMpegOpenVideoCodec(AVFormatContext* ic, AVCodecContext *vc, int videoindex) {
	//配置解码器上下文参数
	avcodec_parameters_to_context(vc, ic->streams[videoindex]->codecpar);
	//八线程解码
	vc->thread_count = 8;
	//打开解码器上下文
	int re = avcodec_open2(vc, 0, 0);
	if (re != 0)
	{
		char buf[1024] = { 0 };
		av_strerror(re, buf, sizeof(buf) - 1);
		cout << "open vedio codec failed!" << buf << endl;
		return ;
	}
	cout << "video codec success!" << endl;
}

void myFFMpegOpenAudioCodec(AVFormatContext* ic, AVCodecContext *ac, int audioindex) {
	//配置解码器上下文参数
	avcodec_parameters_to_context(ac, ic->streams[audioindex]->codecpar);
	//八线程解码
	ac->thread_count = 8;
	//打开解码器上下文
	int re = avcodec_open2(ac, 0, 0);
	if (re != 0)
	{
		char buf[1024] = { 0 };
		av_strerror(re, buf, sizeof(buf) - 1);
		cout << "open audio codec failed!" << buf << endl;
		return;
	}
	cout << "audio codec success!" << endl;
}

读取压缩帧的核心代码是:

ret = av_read_frame(ic, packet);//读取一个packet结构体

上句代码实际上实现了对AVPacket *类型变量packet的更新,变量packet始终为当前读取出的最新的一个帧,当然,该帧可能是视频帧也可能是音频帧。

            if (packet->stream_index == audioindex) {//如果读到的是音频帧
				cout << "audio 显示时间戳" << packet->pts << endl;
				cout << "audio 解码时间戳" << packet->dts << endl;
				cout << "audio 压缩编码数据大小" << packet->size << endl;
				cout << "audio 数据的偏移地址" << packet->pos << endl;
				cout << "audio 时长:" << packet->duration * av_q2d(ic->streams[audioindex]->time_base) << endl;
			}

通过packet->stream_index变量可以判断该帧是视频帧还是音频帧,随后显示该帧的各种信息。

百度百科里这么说的:

数字电视系统码流分析中,对PES进行分析,打包的基本码流(PES)是非定长的,一般是一个存取单元的长度,一个存取单元为一个视频帧,也可以是一个音频帧。为实现解码的同步,每段之前还要插入相应的时间标记和相关的标志符,解码时间标签(DTS,decoding time stamp)显示时间标签(PTS,presentation time stamp)、以及段内信息类型和用户类型等标志信息。

DTS:decoding time stamp 解码时间戳

PTS:presentation time stamp 显示时间戳

掏出《音视频开发进阶指南》,里面这样说的:PTS决定这帧数据什么时候显示给用户,DTS决定该帧数据什么被解码,如果视频里各个帧的编码是按照显示顺序依次进行的,那么解码和显示的时间应该是一致的。但是实际上大多数编码解码标准中,编码顺序和输入顺序并不一致,所以需要两种时间戳。

pos表示的是流中字节的位置,这个数据并不是很重要。

运行结果如下图所示:

 从运行结果中也可以看到一些需要说明一下的内容。

可以看到在输出内容中,通常是一个视频帧两个音频帧为周期进行循环,这是由视频的编码格式决定的,说明该种编码方式为一个视频帧后方跟随两个音频帧。

此外,对于音频流,一个AVPacket可能包含多个AVFrame,而视频流中一个只AVPacket包含AVFrame。

每一个视频帧的时长和音频帧的时长都是有固定长度的,这也体现了编码的规范性。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要使用FFmpeg获取最新的一帧数据,需要使用FFmpeg提供的API。以下是一个简单的示例代码,可以获取视频文件的最新一帧数据: ```c #include <stdio.h> #include <stdlib.h> extern "C" { #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libavutil/imgutils.h> } int main(int argc, char *argv[]) { AVFormatContext *formatContext = NULL; AVCodecContext *codecContext = NULL; AVCodec *codec = NULL; AVFrame *frame = NULL; AVPacket packet; int videoStreamIndex = -1; int ret; if (argc < 2) { printf("Usage: %s <input_file>\n", argv[0]); return -1; } av_register_all(); if (avformat_open_input(&formatContext, argv[1], NULL, NULL) < 0) { printf("Could not open input file."); return -1; } if (avformat_find_stream_info(formatContext, NULL) < 0) { printf("Could not find stream information."); return -1; } for (int i = 0; i < formatContext->nb_streams; i++) { if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoStreamIndex = i; break; } } if (videoStreamIndex == -1) { printf("Could not find video stream."); return -1; } codecContext = avcodec_alloc_context3(NULL); if (!codecContext) { printf("Could not allocate codec context."); return -1; } avcodec_parameters_to_context(codecContext, formatContext->streams[videoStreamIndex]->codecpar); codec = avcodec_find_decoder(codecContext->codec_id); if (!codec) { printf("Could not find codec."); return -1; } if (avcodec_open2(codecContext, codec, NULL) < 0) { printf("Could not open codec."); return -1; } frame = av_frame_alloc(); if (!frame) { printf("Could not allocate frame."); return -1; } while (av_read_frame(formatContext, &packet) >= 0) { if (packet.stream_index == videoStreamIndex) { ret = avcodec_send_packet(codecContext, &packet); if (ret < 0) { printf("Error sending packet to decoder."); break; } while (ret >= 0) { ret = avcodec_receive_frame(codecContext, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { printf("Error receiving frame from decoder."); goto end; } // 此时frame里面就是最新的一帧数据,可以进行处理 } } av_packet_unref(&packet); } end: avformat_close_input(&formatContext); avcodec_free_context(&codecContext); av_frame_free(&frame); return 0; } ``` 需要注意的是,这只是一个简单的示例代码,实际应用需要根据具体情况进行修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值