基于RTMP的音视频拉流实现1

RTMP拉流的基本框架

整体结构

  1. PullWork类: 总控制
  • Init:
    初始化音频包队列(audioq),采样帧队列(videoq)。
    初始化视频包队列(sampq),图像帧队列(pictq)。
    初始化同步模块(AVSync),采用audio master。
    初始化音频输出模块(AudioOutSDL),并添加回调函数。
    初始化音频解码模块(AudioDecodeLoop),并启动线程。
    初始化视频输出模块(VideoOutputLoop),并添加回调函数。
    初始化视频解码模块(VideoDecodeLoop),并启动线程。
    初始化rtmp播放模块(RTMPPlayer),启动播放线程。
  • 添加回调函数,用于RTMPPlayer向音频包队列(audioPacketCallback),或者视频包队列添加packet(videoPacketCallback);视频显示回调函数等(outVideoPicture)。
  1. PacketQueue类:借鉴ffplay实现
  • 设计原则:
    1)有边界:可以设置最大缓存数量
    2)时长计算:可以结算队列里的数据维持播放的时间长度
    3)可阻塞:push和pop的时候可以使用阻塞的方式
    4)可非阻塞: push和pop的时候可以使用非阻塞的方式
    5)可清空队列
    6)可以唤醒队列进行退出

  • PacketQueue由链表方式实现:

    typedef struct MyAVPacketList {
        AVPacket	pkt;				// 解封装后的包
        struct MyAVPacketList	*next;  // 指向下一个元素
        int	serial;						// 播放序列
    } MyAVPacketList;
    
  • PacketQueue类操作提供以下⽅法:

    • packet_queue_init:初始化各个字段的值,并创建mutex和cond。
    • packet_queue_destroy:清空节点,清理mutex和cond。
    • packet_queue_start:启动,插⼊flush_pkt。
    • packet_queue_abort:中止,abort_request_ = 1
    • packet_queue_get:获取⼀个包,更新队列属性。
    • packet_queue_put:存⼊⼀个包,调用packet_queue_put_private,计算serial,包入队列,队列属性更新(包数量,字节数,时长)。
    • packet_queue_put_nullpacket:放⼊“空包”(nullpacket)。放⼊空包意味着流的结束,为了冲刷解码器,将编码器中所有frame都读取出来。
    • packet_queue_flush:遍历释放AVPacket(AVpacket对应的数据也被释放掉)。最后将PacketQueue的属性恢复为空队列状态。
  • PacketQueue解释:

    • flush_pkt定义为:static AVPacket flush_pkt,是⼀个特殊的packet,可以作为两段不同播放序列的标记。插⼊ flush_pkt 触发PacketQueue其对应的serial进行加1操作,并清空自身缓存avcodec_flush_buffers(),以备新序列的数据进⾏新解码。
    • MyAVPacketList的内存是完全由PacketQueue维护的,在put的时候malloc,在get的时候free。AVPacket分两块:一部分是AVPacket结构体的内存,这部分从MyAVPacketList的定义可以看出是和MyAVPacketList共存亡的。另一部分是AVPacket字段指向的内存,这部分一般通过av_packet_unref 函数释放。
  1. FrameQueue类:参考ffplay,FrameQueue是⼀个环形缓冲区(ring buffer),是用数组实现的⼀个FIFO。适合于明确了缓冲区的最大容量的情形。每一个frame_queue有⼀个写端⼀个读端,写端位于解码线程,读端位于播放线程。
  • Frame封装:
// 用于缓存解码后的数据
typedef struct Frame {
    AVFrame	*frame;         // 指向数据帧
    AVSubtitle sub;        	// 用于字幕
    Int	serial;             // 帧序列,在seek的操作时serial会变化
    double	pts;            // 时间戳,单位为秒
    double	duration;       // 该帧持续时间,单位为秒
    int  int_duration;      // 单位为ms,方便统计避免浮点数的问题
    int64_t	pos;            // 该帧在输入文件中的字节位置
    int	width;              // 图像宽度
    int	height;          	// 图像高读
    int	format;          	//图像(enum AVPixelFormat),声音(enum AVSampleFormat)
    AVRational sar;       	// 图像的宽高比(16:9,4:3),如果未知或未指定则为0/1
    int	uploaded;           // 用来记录该帧是否已经显示过?
    int	flip_v;             // =1则旋转180, = 0则正常播放
} Frame;
  • FrameQueue的设计⽐PacketQueue复杂,引⼊了读取节点但节点不出队列的操作、读取下⼀节点也不出队列等等的操作,FrameQueue操作提供以下⽅法:
    • frame_queue_unref_item:释放Frame⾥⾯的AVFrame和 AVSubtitle。
    • frame_queue_init:初始化队列,确定队列大小,分配AVFrame内存等。
    • frame_queue_destory:销毁队列,释放队列中对AVBuffer的引用和AVFrame对象。
    • frame_queue_signal:发送唤醒信号
    • frame_queue_peek:获取当前Frame对象,获取队列当前Frame, 在调用该函数前先调用frame_queue_nb_remaining确保有frame可读。
    • frame_queue_peek_next:获取当前Frame的下⼀Frame,调用之前先调用frame_queue_nb_remaining确保至少有2 Frame在队列。
    • frame_queue_peek_last:获取上⼀Frame。
    • frame_queue_peek_writable:获取⼀个可写Frame,有最大容量限制,可以以阻塞或⾮阻塞方式进⾏。
    • frame_queue_peek_readable:获取⼀个可读Frame,防止队列为空,可以以阻塞或⾮阻塞⽅式进⾏。
    • frame_queue_push:更新写索引,此时Frame才真正⼊队列,更新队列属性
    • frame_queue_next:更新读索引,此时Frame才真正出队列,更新队列属性。
    • frame_queue_unref_item:释放对应的AVFrame和AVSubtitle
    • frame_queue_nb_remaining:获取队列Frame节点个数
    • frame_queue_last_pos:获取最近播放Frame对应数据在媒体⽂件的位置,主要在seek时使用。
  • FrameQueue的解释
    • rindex_shown取0/1,表示该帧是否显示过,适用于用于保留上一帧的情况(keep_last为True)。
    • 写队列:
      frame_queue_peek_writable(&is->pictq) 向队列尾部申请⼀个可写的帧空间,若队列已满,则等待(由SDL_cond * cond控制,由frame_queue_next或frame_queue_signal触发唤醒)。
      av_frame_move_ref(vp->frame, src_frame) 将src_frame中所有数据拷贝到vp->frame并复位src_frame,vp->frame中AVBuffer使用引用计数机制,不会执⾏AVBuffer的拷贝动作,仅是修改指针指向值。
      frame_queue_push(&is->pictq) 此步仅将frame_queue中的写索引加1,实际的数据写⼊在此步之前已经完成。
    • 读队列:读队列和写队列步骤是类似的,基本步骤如下:
      从frame_queue_peek_readable获取可读Frame;如果需要更新读索引(出队列该节点)则调用frame_queue_peek_next;并释放帧的内存。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值