文章目录
ijkplayer 中很多地方都用到了序列号 (serial) 相关的字段,为什么会需要这个字段呢?其实这是为 seek 这个功能作准备的。因为 seek 之后,之前缓冲好的数据就不能用了,seek 前后的数据是“不连续的”,我们用 serial 这个字段来区分是否是连续的数据。
serial 概念主要存在于以下几个结构体中:
- struct PacketQueue
typedef struct PacketQueue {
MyAVPacketList *first_pkt, *last_pkt;
...
int serial;
MyAVPacketList *recycle_pkt;
...
} PacketQueue;
- struct MyAVPacketList
typedef struct MyAVPacketList {
AVPacket pkt;
struct MyAVPacketList *next;
int serial;
} MyAVPacketList;
- struct Decoder 中的 pkt_serial
typedef struct Decoder {
AVPacket pkt;
PacketQueue *queue;
...
int pkt_serial;
int finished;
int packet_pending;
...
} Decoder;
- struct Frame
typedef struct Frame {
AVFrame *frame;
...
int serial;
double pts; /* presentation timestamp for the frame */
double duration; /* estimated duration of the frame */
...
} Frame;
- struct SDL_AMediaCodec 中的 object_serial
typedef struct SDL_AMediaCodec
{
...
SDL_AMediaCodec_Opaque *opaque;
int object_serial;
...
} SDL_AMediaCodec;
6.typedef struct Clock 中的 serial 和 queue_serial
typedef struct Clock {
double pts; /* clock base */
...
int serial; /* clock is based on a packet with this serial */
int paused;
int *queue_serial; /* pointer to the current packet queue serial, used for obsolete clock detection */
} Clock;
接下来我们来具体分析下 serial 的作用。
首先,可以先抛出一个结论:serial 的连环改变都是因为往 packetqueue 中放入一个 flush 包引起的。我们可以先来分析下 PacketQueue 和 MyAVPacketList 中的序列号。
1. PacketQueue 和 MyAVPacketList 中的序列号
PacketQueue 的 serial 是在 packet_queue_put_private 中发生改变的。
static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt)
{
MyAVPacketList *pkt1;
//......
pkt1->pkt = *pkt;
pkt1->next = NULL;
if (pkt == &flush_pkt) //如果put了一个flush_pkt,队列的序列号会增加
q->serial++;
pkt1->serial = q->serial;//MyAVPacketList的serial也会跟PacketQueue保持一样
//......
return 0;
}
那么什么时候会 put 一个 flush_pkt ?
Put flush_pkt 的时机
- stream_component_open -> packet_queue_start -> packet_queue_put_private(q, &flush_pkt)
在一开始打开一个流的时候,启动队列时会 put 进一个flush_pkt。
static void packet_queue_start(PacketQueue *q)
{
SDL_LockMutex(q->mutex);
q->abort_request = 0;
packet_queue_put_private(q, &flush_pkt);
SDL_UnlockMutex(q->mutex);
}
这时候 serial 发生初次更改,serial = 1。
- 在成功完成 seek 后,会往包队列中 put 进一个flush_pkt
每发生一次 seek ,serial 都会累加一次。
2. Decoder 中的 pkt_serial
经由 get_video_frame -> decoder_decode_frame -> packet_queue_get_or_buffering 中,会对 Decoder 的 pkt_serial 进行赋值。
do {
if (d->queue->nb_packets == 0)
SDL_CondSignal(d->empty_queue_cond);
if (d->packet_pending) {
av_packet_move_ref(&pkt, &d->pkt);
d->packet_pending = 0;
} else {
// &d->pkt_serial 传入 packet_queue_get_or_buffering,可以在函数中获得值
if (packet_queue_get_or_buffering(ffp, d->queue, &pkt, &d->pkt_serial, &d->finished) < 0)
return -1;
}
} while (d->queue->serial != d->pkt_serial);//比较是否是连续的流
static int packet_queue_get_or_buffering(FFPlayer *ffp, PacketQueue *q, AVPacket *pkt, int *serial, int *finished)
{
assert(finished);
if (!ffp->packet_buffering)
return packet_queue_get(q, pkt, 1, serial);
while (1) {
// 再传入 packet_queue_get 中
int new_packet = packet_queue_get(q, pkt, 0, serial);
//......
if (*finished == *serial) {
av_packet_unref(pkt);
continue;
}
else
break;
}
return 1;
}
/* 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;
//......
pkt1 = q->first_pkt;
if (pkt1) {
q->first_pkt = pkt1->next;
if (!q->first_pkt)
q->last_pkt = NULL;
q->nb_packets--;
q->size -= pkt1->pkt.size + sizeof(*pkt1);
q->duration -= FFMAX(pkt1->pkt.duration, MIN_PKT_DURATION);
*pkt = pkt1->pkt;
if (serial)
*serial = pkt1->serial;//在此处MyAVPacketList的serial赋予给了Deocder的pkt_serial
//......
return ret;
}
可以说,Decoder 的 serial 记录了上一次解码所用的 AVPacket 的序列号。
那么,Decoder 的 serial 主要在什么地方会用到?如下:
- 上面图中 packet_queue_get_or_buffering 所在的 do-while 循环判断条件,判断是否连续的流,这里获得的 packet 需要是最新的 serial 的
- 在解码函数中会先比对 PacketQueue 的 serial 和 Decoder 的 serial 是否相等,不相等的话就不需要再进行解码工作了,放弃解码器旧的无效缓存
- audio_thread 中将解码所得的 AVFrame 放入帧队列前,为 Frame 的 serial 赋值
af->serial = is->auddec.pkt_serial;
- decoder_decode_frame 中在解码完成时,给 Decoder.finished 赋值
Decoder.finished 用于判断是否播放完成if (ret == AVERROR_EOF) { d->finished = d->pkt_serial; avcodec_flush_buffers(d->avctx); return 0; }
3. Frame 中的 serial
Frame 的 serial 在帧入队列前被 Deocder.pkt_serial 赋值,如前面第 3 小点讲到。
Frame 的 serial 作用体现在:
- 视频显示 video_refresh 函数中
frame_timer 代表了视频帧当前真实的时间轴,当出现“不连续的情况” ,则重新更新 frame_timerif (lastvp->serial != vp->serial) is->frame_timer = av_gettime_relative() / 1000000.0;
- video_refresh 中
与 PacketQueue 的 serial 不相等,则重新去获取下一个视频 frameif (vp->serial != is->videoq.serial) { frame_queue_next(&is->pictq); goto retry; }
- video_refresh 中
vp_duration 用于计算相邻两帧之间的 pts 时间差,当相邻两帧同流的情况下才进行计算static double vp_duration(VideoState *is, Frame *vp, Frame *nextvp) { if (vp->serial == nextvp->serial) { double duration = nextvp->pts - vp->pts; if (isnan(duration) || duration <= 0 || duration > is->max_frame_duration) return vp->duration; else return duration; } else { return 0.0; } }
- audio_decode_frame 中获取音频 frame
do { if (!(af = frame_queue_peek_readable(&is->sampq))) return -1; frame_queue_next(&is->sampq); } while (af->serial != is->audioq.serial);
4. SDL_AMediaCodec 中的 object_serial
当调用 MediaCodec 硬解时才会用到
首次赋值是在创建 Codec 时发生的,在 SDL_AMediaCodecJava_createByCodecName -> SDL_AMediaCodec_create_object_serial 函数中:
int SDL_AMediaCodec_create_object_serial()
{
int object_serial = __sync_add_and_fetch(&g_amediacodec_object_serial, 1);
if (object_serial == 0)
object_serial = __sync_add_and_fetch(&g_amediacodec_object_serial, 1);
return object_serial;
}
g_amediacodec_object_serial 是一个静态的全局变量,初始值为 0,所以一般来说上面这个函数返回 1 给 SDL_AMediaCodec.object_serial,即 SDL_AMediaCodec.object_serial 变为 1
而当 Codec flush 刷新时,则会得到更新:
feed_input_buffer->SDL_AMediaCodec_flush->SDL_AMediaCodecJava_flush->SDL_AMediaCodec_create_object_serial,再一次调用 SDL_AMediaCodec_create_object_serial 此函数,使得 SDL_AMediaCodec.object_serial 加1
Codec flush 刷新,是在以下场景发生的,看下面这段代码:
注意 ffp_is_flush_packet(&pkt) 和 opaque->acodec_flush_request 这两个条件。一般来说,当进行 seek 后,将会清空队列并扔进一个 flush 包,也即 seek 后会进入 SDL_AMediaCodec_flush 的逻辑当中
然后,还需要结合另一个结构体字段来看,SDL_AMediaCodecBufferProxy 中的 acodec_serial
struct SDL_AMediaCodecBufferProxy
{
int buffer_id;
int buffer_index;
int acodec_serial;
SDL_AMediaCodecBufferInfo buffer_info;
};
在 drain_output_buffer_l -> amc_fill_frame -> SDL_VoutAndroid_obtainBufferProxy -> SDL_VoutAndroid_obtainBufferProxy_l 里 SDL_AMediaCodecBufferProxy.acodec_serial 会被 SDL_AMediaCodec.object_serial 赋值,相当于 SDL_AMediaCodecBufferProxy.acodec_serial 相当于保存了解码后 SDL_AMediaCodec.object_serial 的值
SDL_AMediaCodec_flush 触发 SDL_AMediaCodec.object_serial 改变后,在渲染时 SDL_VoutAndroid_releaseBufferProxy_l 函数会进行一次判断 SDL_AMediaCodec_isSameSerial,判断上面这两个字段的值是否相等
bool SDL_AMediaCodec_isSameSerial(SDL_AMediaCodec* acodec, int acodec_serial)
{
if (acodec == NULL)
return false;
return acodec->object_serial == acodec_serial;
}
不相等则函数返回,不进行后面的渲染流程
5. Clock 中的 serial
Clock serial 主要是在音视频帧在要被播放的时候更新的
-
Audio 部分: 在取音频帧的函数 audio_decode_frame 中,赋值 is->audio_clock_serial,而 is->audio_clock_serial 又会在 sdl_audio_callback 回调函数中的 set_clock_at 里被赋值给 Clock.serial
is->audio_clock_serial = af->serial;
if (!isnan(is->audio_clock)) { set_clock_at(&is->audclk, is->audio_clock - (double)(is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec - SDL_AoutGetLatencySeconds(ffp->aout), is->audio_clock_serial, ffp->audio_callback_time / 1000000.0); sync_clock_to_slave(&is->extclk, &is->audclk); }
-
Video 部分: 在视频显示函数 video_refresh 中,使用 Frame.serial 进行赋值
update_video_pts(is, vp->pts, vp->pos, vp->serial);
-
*Clock.queue_serial, 在初始化时钟时用 PacketQueue.serial 的地址进行赋值
init_clock(&is->vidclk, &is->videoq.serial); init_clock(&is->audclk, &is->audioq.serial);
用途:
- get_video_frame 解码函数中, 作丢帧处理时判断是否与 Decoder.pkt_serial 相等
if (ffp->framedrop>0 || (ffp->framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) { ffp->stat.decode_frame_count++; if (frame->pts != AV_NOPTS_VALUE) { double diff = dpts - get_master_clock(is); if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD && diff - is->frame_last_filter_delay < 0 && is->viddec.pkt_serial == is->vidclk.serial && is->videoq.nb_packets) { is->frame_drops_early++; //...
- 在 get_clock 时可以判断是否连续
static double get_clock(Clock *c) { if (*c->queue_serial != c->serial)//先判断与queue_serial是否相等,其实相当于比较是否与is->videoq.serial或is->audio.serial相等 return NAN; if (c->paused) { return c->pts; } else { double time = av_gettime_relative() / 1000000.0; return c->pts_drift + time - (time - c->last_updated) * (1.0 - c->speed); } }