目录
这个函数顾名思义:解码
一、大致逻辑如下:
1、从video_queue里获取packet,:packet_queue_get();
2、发送packet给decoder :decoder_send_packer();
3、从decoder去获取,:decoder_receive_frame();
这个看代码时候可能顺序不一样,这个是因为一些工程的原因:
比如:
//流连续的情况下,不断调用avcodec_receive_frame获取解码后的frame;
循环调用avcodec_receive_frame,有取到帧,就返回。
(即使还没送入新的Packet,这是为了兼容一个Packet可以解出多个Frame的情况)
// 3*. 从解码器接收frame
// 3.1 一个视频packet含一个视频frame
// 解码器缓存一定数量的packet后,才有解码后的frame输出
// frame输出顺序是按pts的顺序,如IBBPBBP
// frame->pkt_pos变量是此frame对应的packet在视频文件中的偏移地址,值同pkt.pos
还比如:
// 2*. 将packet发送给解码器
// 发送packet的顺序是按dts递增的顺序,如IPBBPBB
// pkt.pos变量可以标识当前packet在视频文件中的地址偏移
//[4]解码器中会缓存一定数量的帧,一个新的解码动作启动后,向解码器送入好几个packet解码器才会输出
第一个packet,这比较容易理解,因为解码时帧之 间有信赖关系,例如IPB三个帧被送入解码器后,
B帧解码需要依赖I帧和P帧,所在在B帧输出前,I帧和P帧必须存在于解码器中而不能删除。理解了这一点,
后面视频frame队列中对视频帧的显示和删除机制才容易理解。
//[5]. 解码器中缓存的帧可以通过冲洗(flush)解码器取出。冲洗(flush)解码器的方法
就是调用avcodec_send_packet(..., NULL),然后多次调用avcodec_receive_frame()将缓存帧取尽。
缓存帧取完后,avcodec_receive_frame()返回AVERROR_EOF。ffplay中,是通过向解码器发送flush_pkt
(实际为NULL),每次seek操作都会向解码器发送flush_pkt。
下面是一个比较形象的流程图,
二、发送失败重新发送packet
肯定会遇到发送失败packet的情况,怎么处理呢?
答案:决不能丢掉,保存下来等下次再发,
用到一个函数:av_packet_move_ref(&d->pkt, &pkt);
这个是复制操作,将&pkt指针给&d->pkt,内存里面保存AVbuffer的内容没有改变,
//av_packet_move_ref这个函数就是完全的只复制,source的值完全的搬到destination,并且把source重置掉。其实就是搬了个位置,buf的引用数不改变。
为什么做这个操作,
看ffplayer的发送失败保存的代码:
if (avcodec_send_packet(d->avctx, &pkt) == AVERROR(EAGAIN)) {
av_log(d->avctx, AV_LOG_ERROR, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
d->packet_pending = 1;//用于在send失败时重新发送
//这个是复制操作,将&pkt指针给&d->pkt,内存里面保存AVbuffer的内容没有改变,这个AVbuffer会再下次继续发送,和上面if (d->packet_pending)对应的。
av_packet_move_ref(&d->pkt, &pkt);
}
看再发送的时候,如果发送失败,会重新保存这次的packet到一个全局变量&d->pkt内,然后在下次循环中不再取数据,直接send_packet,
//从packet_queue取packet
do {
if (d->queue->nb_packets == 0)// 如果packet_queue为空则等待
SDL_CondSignal(d->empty_queue_cond);
//如果有待重发的pkt,则先取待重发的pkt,否则从队列中取一个pkt
if (d->packet_pending) {// 有未处理的packet则先处理
// 把上次发送失败的,且存放在&d->pkt的packet复制给&pkt, 不再从video_queue中get数据,
av_packet_move_ref(&pkt, &d->pkt);
d->packet_pending = 0;
} else {
// 1*. 取出一个packet。使用pkt对应的serial赋值给d->pkt_serial
if (packet_queue_get(d->queue, &pkt, 1, &d->pkt_serial) < 0)// 取pkt
return -1;
}
} while (d->queue->serial != d->pkt_serial);
实现了