ffplay的整体结构是由5个线程和4个队列组成、运转的(不分析字幕)。
整体结构
5个线程
- 解复用线程 : read_thread,由主线程创建,负责媒体文件的解复用和读取,读取的数据根据流类型放入到对应的编码数据队列中。
- 音频解码线程 : audio_thread,由read_thread创建,负责将编码数据队列中的数据解码,放入到原始数据队列中。
- 视频解码线程 : video_thread,由read_thread创建,负责将编码数据队列中的数据解码,放入到原始数据队列中。
- 音频渲染线程 : 由SDL创建,负责将音频原始数据队列中的数据发送给音频播放设备。
- 视频渲染线程 : main线程,负责用视频原始数据队列中的数据不断的更新纹理内容,并刷新显示器进行显示。
4个队列
- FrameQueue pictq : 视频原始数据队列
- FrameQueue sampq : 音频原始数据队列
- PacketQueue audioq : 音频编码数据队列
- PacketQueue videoq : 视频编码数据队列
PacketQueue
Packet队列是基于链表实现的普通队列,由于编码数据帧比较小,所以这是个无限队列。
1 2 3 4 5 6 7 8 9 10 11
| typedef struct PacketQueue { MyAVPacketList *first_pkt, *last_pkt; int nb_packets; int size; int64_t duration; int abort_request; int serial; SDL_mutex *mutex; SDL_cond *cond; } PacketQueue;
|
packet_queue_init
初始化队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| static int packet_queue_init(PacketQueue *q) { memset(q, 0, sizeof(PacketQueue)); q->mutex = SDL_CreateMutex(); if (!q->mutex) { av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError()); return AVERROR(ENOMEM); } q->cond = SDL_CreateCond(); if (!q->cond) { av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError()); return AVERROR(ENOMEM); } q->abort_request = 1; return 0; }
|
packet_queue_start
启动队列,每次启动都会在队列中插入一个分割标记,并将队列序号+1
1 2 3 4 5 6 7 8 9 10
| 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); }
|
packet_queue_destroy
销毁队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| static void packet_queue_flush(PacketQueue *q) { MyAVPacketList *pkt, *pkt1; SDL_LockMutex(q->mutex); for (pkt = q->first_pkt; pkt; pkt = pkt1) { pkt1 = pkt->next; av_packet_unref(&pkt->pkt); av_freep(&pkt); } q->last_pkt = NULL; q->first_pkt = NULL; q->nb_packets = 0; q->size = 0; q->duration = 0; SDL_UnlockMutex(q->mutex); }
static void packet_queue_destroy(PacketQueue *q) { packet_queue_flush(q); SDL_DestroyMutex(q->mutex); SDL_DestroyCond(q->cond); }
|
packet_queue_get
从队列中取出头节点,block参数表明当队列为空时是否需要阻塞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| 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; } 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 -= pkt1->pkt.duration; *pkt = pkt1->pkt; if (serial) *serial = pkt1->serial; av_free(pkt1); ret = 1; break; } else if (!block) { ret = 0; break; } else { SDL_CondWait(q->cond, q->mutex); } } SDL_UnlockMutex(q->mutex); return ret; }
|
packet_queue_put
在队列尾部插入新节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt) { MyAVPacketList *pkt1; if (q->abort_request) return -1;
pkt1 = av_malloc(sizeof(MyAVPacketList)); if (!pkt1) return -1; pkt1->pkt = *pkt; pkt1->next = NULL; if (pkt == &flush_pkt) q->serial++; pkt1->serial = q->serial; if (!q->last_pkt) q->first_pkt = pkt1; else q->last_pkt->next = pkt1; q->last_pkt = pkt1; q->nb_packets++; q->size += pkt1->pkt.size + sizeof(*pkt1); q->duration += pkt1->pkt.duration; SDL_CondSignal(q->cond); return 0; }
static int packet_queue_put(PacketQueue *q, AVPacket *pkt) { int ret; SDL_LockMutex(q->mutex); ret = packet_queue_put_private(q, pkt); SDL_UnlockMutex(q->mutex); if (pkt != &flush_pkt && ret < 0) av_packet_unref(pkt);
return ret; }
|
FrameQueue
Frame队列与Packet队列不同,因为Frame中存储的是原始数据,是非常庞大的,所以需要设置成有限队列,这里采用的是滚动数组的方式来实现,分别用读写两个索引来记录位置。
由于将数组进行了读写分离,所以在线程加锁时有了优化,不再需要锁住整个函数,可以局部加锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| typedef struct FrameQueue { Frame queue [FRAME_QUEUE_SIZE]; int rindex; int windex; int size; int max_size; int keep_last; int rindex_shown; SDL_mutex *mutex; SDL_cond *cond; PacketQueue *pktq; } FrameQueue;
|
frame_queue_init
初始化队列,并设置队列的最大大小,根据最大大小提前创建好每一个Frame对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| static int frame_queue_init(FrameQueue *f, PacketQueue *pktq, int max_size, int keep_last) { int i; memset(f, 0, sizeof(FrameQueue)); if (!(f->mutex = SDL_CreateMutex())) { av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError()); return AVERROR(ENOMEM); } if (!(f->cond = SDL_CreateCond())) { av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError()); return AVERROR(ENOMEM); } f->pktq = pktq; f->max_size = FFMIN(max_size, FRAME_QUEUE_SIZE); f->keep_last = !!keep_last; for (i = 0; i < f->max_size; i++) if (!(f->queue[i].frame = av_frame_alloc())) return AVERROR(ENOMEM); return 0; }
|
frame_queue_destory
销毁队列
1 2 3 4 5 6 7 8 9 10 11 12 13
| static void frame_queue_destory(FrameQueue *f) { int i; for (i = 0; i < f->max_size; i++) { Frame *vp = &f->queue[i]; frame_queue_unref_item(vp); av_frame_free(&vp->frame); } SDL_DestroyMutex(f->mutex); SDL_DestroyCond(f->cond); }
|
frame_queue_peek_readable
读取这里也和Packet队列不同,首先通过frame_queue_peek_readable获取到读索引位置的节点,想要获取下一个节点,需要调用frame_queue_next将读索引移动到下一个位置。
读取时如果当前队列为空,则进行阻塞等待。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
static Frame *frame_queue_peek_readable(FrameQueue *f) { SDL_LockMutex(f->mutex); while (f->size - f->rindex_shown <= 0 && !f->pktq->abort_request) { SDL_CondWait(f->cond, f->mutex); } SDL_UnlockMutex(f->mutex); if (f->pktq->abort_request) return NULL; return &f->queue[(f->rindex + f->rindex_shown) % f->max_size]; }
|
frame_queue_next中并没有对队列进行判空,所以调用该方法的时候需要先用frame_queue_nb_remaining判断一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
static void frame_queue_next(FrameQueue *f) { if (f->keep_last && !f->rindex_shown) { f->rindex_shown = 1; return; } frame_queue_unref_item(&f->queue[f->rindex]); if (++f->rindex == f->max_size) f->rindex = 0; SDL_LockMutex(f->mutex); f->size--; SDL_CondSignal(f->cond); SDL_UnlockMutex(f->mutex); }
|
获取当前队列中可读的节点数量
1 2 3 4
| static int frame_queue_nb_remaining(FrameQueue *f) { return f->size - f->rindex_shown; }
|
frame_queue_peek_writable
写入的话和读取一样,先通过frame_queue_peek_writable获取当前写索引位置的节点,写入完毕后,调用frame_queue_push通知队列写入完毕,写索引向后移动一位。
写入时如果当前队列已满,那么会进行阻塞等待。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
static Frame *frame_queue_peek_writable(FrameQueue *f) { SDL_LockMutex(f->mutex); while (f->size >= f->max_size && !f->pktq->abort_request) { SDL_CondWait(f->cond, f->mutex); } SDL_UnlockMutex(f->mutex); if (f->pktq->abort_request) return NULL; return &f->queue[f->windex]; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
static void frame_queue_push(FrameQueue *f) { if (++f->windex == f->max_size) f->windex = 0; SDL_LockMutex(f->mutex); f->size++; SDL_CondSignal(f->cond); SDL_UnlockMutex(f->mutex); }
|
结构体
MyAVPacketList
Packet队列中的节点
1 2 3 4 5
| typedef struct MyAVPacketList { AVPacket pkt; struct MyAVPacketList *next; int serial; } MyAVPacketList;
|
Frame
Frame队列中的节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| typedef struct Frame { AVFrame *frame; AVSubtitle sub; int serial; double pts; double duration; int64_t pos; int width; int height; int format; AVRational sar; int uploaded; int flip_v; } Frame;
|