前言
decoder_decode_frame函数主要功能是用来解码,在video_thread线程和audio_thread线程中被调用。这个函数有很多判断序列号的地方,序列号主要是跳转时用到的,跳转时PacketQueue的序列号会变,所以先说下序列号。
一、PacketQueue的serial,MyAVPacketList的serial,Decoder的pkt_serial
1、PacketQueue的serial
typedef struct PacketQueue {
AVFifoBuffer *pkt_list; //ffmpeg实现的先进先出的队列
int nb_packets; // 队列中AVPacket数量
int size; //队列占用内存大小
int64_t duration; // 队列所有数据包的持续时间
int abort_request; //是否结束
int serial; //队列序列号
SDL_mutex *mutex; //队列锁
SDL_cond *cond;//队列条件变量
} PacketQueue;
PacketQueue是所有序列号的源头,其他序列号是直接或间接被PacketQueue影响的。跳转时,PacketQueue的序列号会增加,代表一个新的流。
在stream_open()中调用packet_queue_init(),PacketQueue序列号初始化为0。
接下来在read_thread线程-->stream_component_open()-->decoder_start()-->packet_queue_start()中,运行q->serial++,此时PacketQueue的serial为1。
2、MyAVPacketList的serial
typedef struct MyAVPacketList {
AVPacket *pkt;
int serial;
} MyAVPacketList;
由于AVPacket信息中不带序列号,所以用MyAVPacketList结构体封装了一下序列号信息。
MyAVPacketList序列号第一次初始化是在read_thread线程,从文件读取Avpacket数据向PacketQueue队列插入数据时赋值的。调用顺序是:read_thread线程-->packet_queue_put()-->packet_queue_put_private()。
在函数packet_queue_put_private中,MyAVPacketList的序列号第一次赋值为PacketQueue的序列号。(可以把PacketQueue看成容器,每一个MyAVPacketList都是容器的一个元素,容器的序列号决定了每一个元素的序列号)。
3、Decoder的pkt_serial
Decoder的pkt_serial在调用decoder_init时初始化的,初始为-1。调用顺序:read_thread线程-->stream_component_open()-->decoder_init()。
二、decoder_decode_frame
for (;;) {
//解码成功return返回1
if (d->queue->serial == d->pkt_serial) {...}
//从队列中取出Avpacket数据,并判断Avpacket的序列号和Decoder的序列号是否一致,
//不一致继续取,直到序列号一致为止
do {...} while (1);
//不断的向解码器输送数据,用于解码
if (d->avctx->codec_type == AVMEDIA_TYPE_SUBTITLE) {...}
else {
if (avcodec_send_packet(d->avctx, d->pkt) == AVERROR(EAGAIN)) {
...
} else {
av_packet_unref(d->pkt);
}
}
}
decoder_decode_frame内部主要是for循环,不断从PacketQueue队列中get数据,用avcodec_send_packet函数向解码器发送数据,并用avcodec_receive_frame获取解码后的数据,解码成功直接return返回。可以分为三部分。
1、从解码器获取数据
if (d->queue->serial == d->pkt_serial) {
do {
if (d->queue->abort_request)
return -1;
switch (d->avctx->codec_type) {
case AVMEDIA_TYPE_VIDEO:
ret = avcodec_receive_frame(d->avctx, frame);
if (ret >= 0) {
//static int decoder_reorder_pts = -1;
if (decoder_reorder_pts == -1) {
frame->pts = frame->best_effort_timestamp;
} else if (!decoder_reorder_pts) {
frame->pts = frame->pkt_dts;
}
}
break;
case AVMEDIA_TYPE_AUDIO:
ret = avcodec_receive_frame(d->avctx, frame);
if (ret >= 0) {
AVRational tb = (AVRational){1, frame->sample_rate};
if (frame->pts != AV_NOPTS_VALUE)
frame->pts = av_rescale_q(frame->pts, d->avctx->pkt_timebase, tb);
else if (d->next_pts != AV_NOPTS_VALUE)
frame->pts = av_rescale_q(d->next_pts, d->next_pts_tb, tb);
if (frame->pts != AV_NOPTS_VALUE) {
d->next_pts = frame->pts + frame->nb_samples;
d->next_pts_tb = tb;
}
}
break;
}
if (ret == AVERROR_EOF) {
d->finished = d->pkt_serial;
avcodec_flush_buffers(d->avctx);
return 0;
}
if (ret >= 0)
return 1;
} while (ret != AVERROR(EAGAIN));
}
由于PacketQueue的serial的初始值(为1)和Decoder的pkt_serial初始值(-1)不一致,第一次运行时,if语句内部的代码没有运行。
if语句内的代码主要功能是:不论是视频还是音频,都用avcodec_receive_frame函数获取解码后的Avframe数据,如果获取成功直接跳出for循环并返回1。
获取到解码数据后,对于视频做了pts矫正。frame->pts=frame->best_effort_timestamp;
//static int decoder_reorder_pts = -1;
if (decoder_reorder_pts == -1) { //初始值为-1
frame->pts = frame->best_effort_timestamp;
}
/**
*使用各种启发式估计的帧时间戳,以流时基为单位
*-编码:未使用
*-解码:由libavcodec设置,由用户读取。
*/
int64_t best_effort_timestamp;
对frame的pts进行矫正,具体矫正算法由libavcodec设置,帧时间戳,基本与 pts 相同,如果当前 pts 存在不合理值,会尝试进行一系列校准来得到这个更合理的值。
2、从PacketQueue队列中get数据
第二部分代码如下:
do {
if (d->queue->nb_packets == 0)
SDL_CondSignal(d->empty_queue_cond);
if (d->packet_pending) { //初始化为0
d->packet_pending = 0;
} else {
int old_serial = d->pkt_serial;
if (packet_queue_get(d->queue, d->pkt, 1, &d->pkt_serial) < 0)
return -1;
if (old_serial != d->pkt_serial) {
avcodec_flush_buffers(d->avctx);
d->finished = 0;
d->next_pts = d->start_pts;
d->next_pts_tb = d->start_pts_tb;
}
}
if (d->queue->serial == d->pkt_serial)
break;
av_packet_unref(d->pkt);
} while (1);
第二部分是在一个do{}while(1)循环中。d->empty_queue_cond是在decoder_init函数中赋值的,d->empty_queue_cond是is->continue_read_thread,
if (d->queue->nb_packets == 0)
SDL_CondSignal(d->empty_queue_cond);
先判断队列中是否有数据,如果没有用SDL_CondSignal函数向read_thread线程发信号,防止read_thread线程在SDL_CondWaitTimeout处等待,就是唤醒read_thread线程,立即读取数据。
接下来用packet_queue_get函数从队列取数据。
/* return < 0 if aborted, 0 if no packet and > 0 if packet. */
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial)
{
MyAVPacketList pkt1;
int ret;
SDL_LockMutex(q->mutex);
for (;;) {
if (q->abort_request) {
ret = -1;
break;
}
if (av_fifo_size(q->pkt_list) >= sizeof(pkt1)) {
av_fifo_generic_read(q->pkt_list, &pkt1, sizeof(pkt1), NULL);
q->nb_packets--;
q->size -= pkt1.pkt->size + sizeof(pkt1);
q->duration -= pkt1.pkt->duration;
av_packet_move_ref(pkt, pkt1.pkt);
if (serial)
*serial = pkt1.serial;
av_packet_free(&pkt1.pkt);
ret = 1;
break;
} else if (!block) {
ret = 0;
break;
} else {
SDL_CondWait(q->cond, q->mutex);
}
}
SDL_UnlockMutex(q->mutex);
return ret;
}
在packet_queue_get函数内部,首先判断队列中是否有数据,有则用av_fifo_generic_read函数把读取的数据保存到pkt1中,再用av_packet_move_ref()把pkt1的数据拷贝到pkt中,并用 av_packet_free函数释放ptk1的内存。
如果队列中没有数据则一直在SDL_CondWait(q->cond, q->mutex)处等待,直到read_thread线程中,packet_queue_put_private函数调用SDL_CondSignal()发送信号为止。即read_thread线程读取到数据后,唤醒解码线程立即解码。packet_queue_put_private()调用SDL_CondSignal()的代码如下:
Decoder的pkt_serial通过指针传参,赋值为pkt1的序列号,ptk1的序列号等于PacketQueue的序列号,所以此时Decoder的pkt_serial也等于PacketQueue的序列号。
if (serial)
*serial = pkt1.serial;
接下来有句代码如下:
if (old_serial != d->pkt_serial) {
avcodec_flush_buffers(d->avctx);
d->finished = 0;
d->next_pts = d->start_pts;
d->next_pts_tb = d->start_pts_tb;
}
old_serial是上一次取的MyAVPacketList的序列号,对于第一次运行来说,old_serial是d->pkt_serial的初始值-1,所以这里会有一次刷新解码器缓存的操作,当读取到第一个MyAVPacketList后,Decoder的pkt_serial等于PacketQueue的序列号,即d->queue->serial 和 d->pkt_serial相等,所以下面的代码会跳出do{}while(1)循环。
if (d->queue->serial == d->pkt_serial)
break;
接下来运行第三部分。
3、向解码器输送数据
if (d->avctx->codec_type == AVMEDIA_TYPE_SUBTITLE) {
省略...
} else {
if (avcodec_send_packet(d->avctx, d->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;
} else {
av_packet_unref(d->pkt);
}
}
略去字幕部分,代码功能主要是用avcodec_send_packet函数向解码器发送数据。由于在for循环中,此时PacketQueue的serial(为1)和Decoder的serial(为1)已经一致了,所以第一部分的功能开始运行,用avcodec_receive_frame函数获取解码后的数据。
如果获取到解码数据,跳出for循环直接返回。如果没有获取到解码数据,就一直在for循环中,不断从队列中get数据向解码器发送,直到获取到解码数据为止。
有可能向解码器输送多个Avpacket后,才会获取到解码数据。
4、函数返回值
当函数avcodec_receive_frame获取到解码数据后,程序跳出for循环并返回1
当解码器没有数据可读时返回0
出现解码错误时返回负数
三、总结:
decoder_decode_frame函数的主要功能是:判断从PacketQueue取出的MyAVpacketList序列号是否和Decoder的序列号一致,如果两者不一致,就一直从队列中取,直到取到的序列号一致为止。如果两者序列号一致,就用avcodec_send_packet函数向解码器输送数据,然后通过avcodec_receive_frame从解码器获取解码后的数据返回。
正确取到解码数据后返回1,读取到解码器末尾时返回0,其余返回负数。