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都被释放。