携程网网站是哪家公司做的,企业加强网站建设的必要性,八里河网站建设项目建设可行性,网页制作三剑客是哪三个文章目录一 PacketQueue 的线程安全设计线程同步手段二 serial 字段的作用详解为什么需要 serial#xff1f;serial 的工作机制三 简化版示例代码使用场景#xff08;解码线程伪代码#xff09;Seek 发生时四 总结一 PacketQueue 的线程安全设计
在 ffplay.c 中#xff0c…文章目录一 PacketQueue 的线程安全设计线程同步手段二 serial 字段的作用详解为什么需要 serialserial 的工作机制三 简化版示例代码使用场景解码线程伪代码Seek 发生时四 总结一 PacketQueue 的线程安全设计在 ffplay.c 中PacketQueue 是一个典型的生产者-消费者队列生产者read_thread从文件/网络读取 AVPacket 并放入队列消费者解码线程如 audio_thread / video_thread从队列中取出 AVPacket 解码线程同步手段typedefstructPacketQueue{AVPacketList*first_pkt,*last_pkt;intnb_packets;// 当前包数量intsize;// 总字节数用于限流int64_tduration;// 总时长毫秒intabort_request;// 是否请求终止intserial;// 关键字段播放序列号SDL_mutex*mutex;// 互斥锁SDL_cond*cond;// 条件变量}PacketQueue;SDL_mutex保护对队列结构体如 first_pkt, last_pkt, nb_packets 等的并发访问。SDL_cond用于阻塞/唤醒消费者调用 packet_queue_get(…, block1) 时若队列为空则 SDL_CondWait(cond, mutex) 阻塞生产者调用 packet_queue_put() 后调用 SDL_CondSignal(cond) 唤醒等待的消费者。二 serial 字段的作用详解为什么需要 serial当用户执行 seek快进/快退操作时旧的 AVPacket 已经无效必须被丢弃。此时可能出现以下情况read_thread 可能还在往队列里塞旧数据解码线程可能还在处理旧数据如果不清除这些“过期”数据就会出现音频“回放杂音”视频跳帧混乱音画不同步serial 用于标识“当前播放上下文”的版本号。serial 的工作机制初始化时q-serial 0执行 seek 时调用packet_queue_flush(is-audioq)清空队列插入一个特殊的flush_pkt其data NULL执行q-serial例如从 0 → 1此后所有新入队的 packet 都会设置pkt-serial q-serial解码线程在取到 packet 后会检查if(pkt-serial!decoder-pkt_serial){av_packet_unref(pkt);continue;// 丢弃旧序列数据}其中decoder-pkt_serial会在 flush 后被更新为新的 serial。三 简化版示例代码以下是一个高度简化但功能完整的PacketQueue实现突出serial和线程安全逻辑#includeSDL2/SDL.h#includelibavcodec/avcodec.h#defineMAX_QUEUE_SIZE(15*1024*1024)typedefstructMyAVPacketList{AVPacket pkt;intserial;structMyAVPacketList*next;}MyAVPacketList;typedefstructPacketQueue{MyAVPacketList*first,*last;intnb_packets;intsize;int64_tduration;intabort_request;intserial;// 序列号SDL_mutex*mutex;SDL_cond*cond;}PacketQueue;voidpacket_queue_init(PacketQueue*q){memset(q,0,sizeof(PacketQueue));q-mutexSDL_CreateMutex();q-condSDL_CreateCond();q-serial0;}intpacket_queue_put(PacketQueue*q,AVPacket*pkt){MyAVPacketList*pkt1;SDL_LockMutex(q-mutex);if(q-abort_request){SDL_UnlockMutex(q-mutex);return-1;}pkt1av_malloc(sizeof(MyAVPacketList));if(!pkt1)gotofail;pkt1-pkt*pkt;pkt1-serialq-serial;// 绑定当前 serialpkt1-nextNULL;if(!q-last)q-firstpkt1;elseq-last-nextpkt1;q-lastpkt1;q-nb_packets;q-sizepkt1-pkt.sizesizeof(*pkt1);q-durationpkt1-pkt.duration;SDL_CondSignal(q-cond);// 唤醒消费者SDL_UnlockMutex(q-mutex);return0;fail:SDL_UnlockMutex(q-mutex);return-1;}// 获取 packetblock1 表示阻塞等待intpacket_queue_get(PacketQueue*q,AVPacket*pkt,intblock,int*serial){MyAVPacketList*pkt1;intret;SDL_LockMutex(q-mutex);for(;;){if(q-abort_request){ret-1;break;}pkt1q-first;if(pkt1){q-firstpkt1-next;if(!q-first)q-lastNULL;q-nb_packets--;q-size-pkt1-pkt.sizesizeof(*pkt1);q-duration-pkt1-pkt.duration;*pktpkt1-pkt;if(serial)*serialpkt1-serial;// 返回 packet 的 serialav_free(pkt1);ret1;break;}elseif(!block){ret0;break;}else{SDL_CondWait(q-cond,q-mutex);// 阻塞等待}}SDL_UnlockMutex(q-mutex);returnret;}// seek 时调用清空队列 serialvoidpacket_queue_flush(PacketQueue*q){MyAVPacketList*pkt,*pkt1;SDL_LockMutex(q-mutex);for(pktq-first;pkt;pktpkt1){pkt1pkt-next;av_packet_unref(pkt-pkt);av_free(pkt);}q-firstq-lastNULL;q-nb_packets0;q-size0;q-duration0;q-serial;// 关键序列号递增SDL_UnlockMutex(q-mutex);}使用场景解码线程伪代码intwanted_serialis-audioq.serial;// 期望的 serialwhile(1){intserial;AVPacket pkt;if(packet_queue_get(is-audioq,pkt,1,serial)0)break;if(serial!wanted_serial){av_packet_unref(pkt);continue;// 丢弃旧序列数据}// 正常解码...decode_audio(pkt);av_packet_unref(pkt);}Seek 发生时// 用户 seek 到新位置packet_queue_flush(is-audioq);// serial 自增packet_queue_flush(is-videoq);// read_thread 会重新开始读取并给新 packet 打上新 serial四 总结机制作用SDL_mutex SDL_cond实现线程安全的生产者-消费者队列serial 字段标识“播放上下文”避免 seek 后旧数据污染