FFMpeg中的AVPacket结构使用

977 阅读6分钟

1、定义一个AVPacket包并且alloc,如下:

	AVPacket* pkt = av_packet_alloc();
	cout << "pkt->buf->data = " << (unsigned int)pkt->buf->data << endl;
	cout << "pkt->buf->buffer = " << (unsigned int)pkt->buf->buffer << endl;
	cout << "pkt->buf->size = " << pkt->buf->size << endl;
	cout << "pkt->data = "<< (unsigned int)pkt->data <<", pkt->size = " << pkt->size << endl;

 - 其中,测试发现pkt->buf是个NULL指针,pkt->buf是AVPacket包结构体中的“AVBufferRef *buf;”字段(它是对一个引用计数buffer的引用,而这个引用计数buffer中存放了Packet包的数据。可以为NULL,此时Packet包数据就不是引用计数的。),简单来说,就是如果设置了引用计数,则pkt->buf就不是NULL。 

 - 最后一行打印中,pkt->data和pkt->size也都是等于0. 说明:此时只创建了一个结构体,并没有给里面的相关指针申请空间。av_packet_alloc创建一个AVPacket,将其字段设为默认值(data为空,没有数据缓存空间)。

2、运行了下面的语句后,再打印上面的四条内容。

    int re = av_read_frame(ic, pkt);

 输出如下:

	pkt->buf->data = 74811264
	pkt->buf->buffer = 74831104
	pkt->buf->size = 19752
	pkt->data = 74811264, pkt->size = 19688

注意:上面的pkt->buf->data和pkt->data是相等的,他们就是数据缓存,pkt->buf->size是pkt->buf->data的大小,以字节为单位。

3、在上面av_read_frame的基础上如果再定义一个pkt2,同时引用到pkt上面,如下,此时再分别打印pkt和pkt2的内容:

	AVPacket* pkt2 = av_packet_alloc();
	av_packet_ref(pkt2, pkt); // pkt2共享同一个数据缓存空间...

	cout << "pkt->buf->data = " << (unsigned int)pkt->buf->data << endl;
	cout << "pkt->buf->buffer = " << (unsigned int)pkt->buf->buffer << endl;
	cout << "pkt->buf->size = " << pkt->buf->size << endl;
	cout << "pkt->data = " << (unsigned int)pkt->data << ", pkt->size = " << pkt->size << endl;
	cout << "--------------------------------" << endl;
	cout << "pkt2->buf->data = " << (unsigned int)pkt2->buf->data << endl;
	cout << "pkt2->buf->buffer = " << (unsigned int)pkt2->buf->buffer << endl;
	cout << "pkt2->buf->size = " << pkt2->buf->size << endl;
	cout << "pkt2->data = " << (unsigned int)pkt2->data << ", pkt2->size = " << pkt2->size << endl;
	cout << "--------------------------------" << endl;

输出如下:

	pkt->buf->data = 74811264
	pkt->buf->buffer = 74831104
	pkt->buf->size = 19752
	pkt->data = 74811264, pkt->size = 19688
	--------------------------------
	pkt2->buf->data = 74811264
	pkt2->buf->buffer = 74831104
	pkt2->buf->size = 19752
	pkt2->data = 74811264, pkt2->size = 19688
	--------------------------------

可见,两者的内容是一样的,所以说明pkt2中并没有新创建空间,而是引用了pkt中的空间。

4、在上面第三步的基础上,只对pkt进行unref,然后打印相关信息。

	av_packet_unref(pkt);
	cout << "pkt->data = " << (unsigned int)pkt->data << ", pkt->size = " << pkt->size << endl;
	cout << "--------------------------------" << endl;
	cout << "pkt2->buf->data = " << (unsigned int)pkt2->buf->data << endl;
	cout << "pkt2->buf->buffer = " << (unsigned int)pkt2->buf->buffer << endl;
	cout << "pkt2->buf->size = " << pkt2->buf->size << endl;
	cout << "pkt2->data = " << (unsigned int)pkt2->data << ", pkt2->size = " << pkt2->size << endl;

输出如下:

	pkt->data = 0, pkt->size = 0
	--------------------------------
	pkt2->buf->data = 74811264
	pkt2->buf->buffer = 74831104
	pkt2->buf->size = 19752
	pkt2->data = 74811264, pkt2->size = 19688

此时,pkt->buf变成了NULL指针,pkt->data和pkt->size也都重新等于0,但是pkt2依旧存在。

5、在上面第三步的基础上,只对pkt2进行free,即av_packet_free(&pkt2),然后打印相关信息。可以发现,此时pkt2本身就是NULL了,更别提pkt2里面的字段内容了。而pkt及pkt中的内容依旧存在,并没有发生变化,因为毕竟free只是针对pkt2. 注意下面两者的区别:

	void av_packet_free(AVPacket **pkt);
	void av_free_packet(AVPacket *pkt);

 av_free_packet的参数是一个指针,而av_packet_free的参数则是一个指针的指针。av_free_packet是一个被废弃的函数,新版本中不用了。 对于av_packet_free,类似于“free(p); p = Null;”,不仅清空了内存,还清空了指针变量本身,即让指针变量等于NULL。 一般,如果用了av_packet_alloc后就要调用av_packet_free来释放。但如果有引用计数,在调用av_packet_free前一般先调用av_packet_unref。

下面来说说在循环av_read_frame过程中pkt的变化情况。 

1、在第一次av_read_frame(ic, pkt)后,打印pkt的相关信息,如下:

	cout << "pkt->buf->data = " << (unsigned int)pkt->buf->data << endl;
	cout << "pkt->buf->buffer = " << (unsigned int)pkt->buf->buffer << endl;
	cout << "pkt->buf->size = " << pkt->buf->size << endl;
	cout << "pkt->data = " << (unsigned int)pkt->data << ", pkt->size = " << pkt->size << endl;	

输出如下:

	pkt->buf->data = 74811264
	pkt->buf->buffer = 74831104
	pkt->buf->size = 19752
	pkt->data = 74811264, pkt->size = 19688

2、然后对pkt进行unref,再利用pkt循环进行av_read_frame(ic, pkt),同样打印pkt的相关信息,此时输出如下:

	pkt->buf->data = 12831936
	pkt->buf->buffer = 12375808
	pkt->buf->size = 20830
	pkt->data = 12831936, pkt->size = 20766

可见,即便是同一个pkt(pkt变量本身的值(即(unsigned int)pkt)不会变化),前后两次存放数据的空间也是不一样的。及时unref可以防止内存泄露!!!

上面第二步是先对pkt进行了unref,然后read frame,如果不进行unref就read frame,则前后pkt中的空间地址也是不一样的,而且前面用过的空间没有被释放掉,内存 消耗会越来越大,所以要及时进行unref。当然,pkt变量本身的值(即(unsigned int)pkt)不会变化,即pkt指向的空间位置没有变化,空间中的内容发生了变化。

下面讨论下AVPacket队列问题,未实测。 

 下面代码从流中读取AVPacket插入队列:

	AVPacket packet;
	while(av_read_frame(pFormatCtx, &packet)>=0){
		if(packet.stream_index == audioStream){
			packet_queue_put(&audioq, &packet);
		}else{
			//av_free_packet(&packet);
			av_packet_unref(&packet);
		}
	}

如果是音频流则将读到Packet调用packet_queue_put插入到队列,如果不是音频流则调用av_packet_unref释放已读取到的AVPacket数据。 

下面代码是packet_queue_put函数中将Packet放入到一个新建的队列节点的代码片段:

	AVPacketList *pktl;
	//if(av_dup_packet(pkt)<0)
		//return -1;
	pktl = (AVPacketList *)av_malloc(sizeof(AVPacketList));
	if(!pktl)
		return -1;
	if(av_packet_ref(&pktl->pkt, pkt)<0)
		return -1;
	//pktl->pkt = *pkt;
	pktl->next = nullptr;

注意,在调用packet_queue_put时传递的是指针,也就是形参pkt和实参packet中的data引用的是同一个数据缓存。但是在循环调用av_read_frame的时候,会将packet中的data释放掉,以便于读取下一个帧数据。所以就需要对data引用的数据缓存进行处理,保证在读取下一个帧数据的时候,其data引用的数据空间没有被释放。有两种方法,复制一份data引用的数据缓存或者给data引用的缓存空间加一个引用计数。

 注释掉的部分是使用已废弃的APIav_dup_packet,该函数将pkt中data引用的数据缓存复制一份给队列节点中的AVPacket。添加引用计数的方法则是调用av_apcket_ref将data引用的数据缓存的引用计数+1,这样其就不会被释放掉,是更好的管理其data引用的缓存空间。  

从队列中取出AVPacket:

	//*pkt = pktl->pkt;
	if(av_packet_ref(pkt, &pktl->pkt)<0){
		ret = 0;
		break;
	}

注释掉的代码仍然是两个packet引用了同一个缓存空间,这样在一个使用完成释放掉缓存的时候,会造成另一个访问错误。所以调用av_packet_ref将其引用计数+1,这样在释放其中一个packet的时候其引用的数据缓存就不会被释放掉,直到两个packet都被释放。