Fix GStreamer rtmpsrc plugin's bug

问题描述:

当使用GStreamer的rtmpsrc plugin时,在停止pipeline时会偶现一个crash bug。

具体描述:

当从外部调用gst_set_state(GST_STATE_NULL)之后, rtmpsrc会进一步调用gst_rtmp_src_unlock()和gst_rtmp_src_stop()两个接口,然后当此时pipeline的数据线程阻塞在gst_rtmp_src_create()的librtmp的RTMP_Read()接口时,调用RTMP_Free()会导致crash;究其原因,librtmp不是一个多线程的库。

解决方案:

1. 在gstrtmpsrc中加锁保护rtmp对象。
2. 在librtmp中添加回调方法,使得RTMP_Read()在无音视频数据的情况下不会长时间阻塞。

备注:

GStreamer 1.4.5
librtmp 2.4_p20131018


gstrtmpsrc.c

gst_rtmp_src_init()
{
...
g_mutex_init(&rtmpsrc->lock);
}

static void
gst_rtmp_src_finalize (GObject * object)
{
    /* Add by sparktend. */
    rtmpsrc->stop_flag = FALSE;
    g_mutex_clear (&rtmpsrc->lock);
}

static int stop_interrupt_cb(void *ctx)
{
    GstRTMPSrc *rtmpsrc = (GstRTMPSrc *)ctx;
    if (rtmpsrc) {
          GST_DEBUG_OBJECT (rtmpsrc, "interrupt_cb flag=%d", rtmpsrc->stop_flag);
         return rtmpsrc->stop_flag;
    } else {
        return 0;
    }
}

gst_rtmp_src_start() 
{
....
  RTMP_Init (src->rtmp);
  /* Add by sparktend, init rtmp object callback. */
  src->rtmp->interrupt_callback.callback = stop_interrupt_cb;
  src->rtmp->interrupt_callback.opaque = src;
} 

在_unlock()、_create()和_stop()中使用rtmp对象时,用锁保护。

g_mutex_lock(&rtmpsrc->lock);
..
g_mutex_unlock(&rtmpsrc->lock);

librtmp
rtmp.h

  /* Add by sparktend, for no-block callback. */
  typedef struct AVIOInterruptCB {
       int (*callback)(void*);
       void *opaque;
  } AVIOInterruptCB;

  typedef struct RTMP
  {
    ....
     /* Add by sparktend, for no-block callback. */
     AVIOInterruptCB interrupt_callback;
   }RTMP;


rtmp.c
RTMP_GetNextMediaPacket()
{
      if (!bHasMediaPacket)
     {
       RTMPPacket_Free(packet);
       /* Add  by sparktend, do callback. */
       RTMP_Log(RTMP_LOGDEBUG,"bHasMediaPacket = 0");
       if (r->interrupt_callback.callback) {
            RTMP_Log(RTMP_LOGDEBUG,"will do callback");
            if (r->interrupt_callback.callback(r->interrupt_callback.opaque)) {
               break;
            }
       }
     }
}

VLC优化(2)修改VLC读缓冲机制

0x00 前置信息

为进一步降低延迟,采用极端方法修改VLC读缓冲机制。

0x01 VLC读缓冲机制

对于一个rtmp流的读取,发起端在Demux module中,具体在该模块的Demux方法中调用ffmepg的接口av_read_frame读取每一帧数据。但是这个read的接口实在不清晰,经过了多个抽象层的封装,最后真正指向了rtmp_read接口。还是通过一个图来看会比较清晰:

vlc_demux_read_analysis.png

上图描述了read指针的指向,由read的指向可以看出VLC中抽象层次的关系。
Demux layer 开始调用av_read_frame接口,在ffmpeg中经过层层调用,s->read_packet实际指向了Demux layer中IORead接口,然后再进一步指向Stream layer;Stream layer的AReadStream指向了Access layer的Read接口,最终Read接口调用了ffmpeg的接口avio_read,至进一步指向了rtmp_read接口。

- 阅读剩余部分 -

VLC优化(1) avformat_find_stream_info接口延迟降低

0x00 前置信息

版本:ffmpeg2.2.0
文件:vlc src/module/demux/avformat/demux.c
函数:OpenDemux

0x01 研究背景

ffmpeg的两个接口avformat_open_input和avformat_find_stream_info分别用于打开一个流和分析流信息。在初始信息不足的情况下,avformat_find_stream_info接口需要在内部调用read_frame_internal接口读取流数据,然后再分析后,设置核心数据结构AVFormatContext。由于需要读取数据包,avformat_find_stream_info接口会带来很大的延迟,那么有几种方案可以降低该接口的延迟,具体如下:

- 阅读剩余部分 -

VLC学习(2) VLC架构及流程分析

0x00 前置信息

VLC是一个非常庞大的工程,我从它的架构及流程入手进行分析,涉及到一些很细的概念先搁置一边,日后详细分析。

0x01 源码结构(Android Java相关的暂未分析)

# build-android-arm-linux-androideabi/:第三方库。
# modules/:模块代码。
# modules/demux:      解复用模块代码。
# modules/codec:       解码模块相关代码。
# modules/access:    访问模块相关代码。
# 其他:未详细分析。
# src/: VLC架构核心代码。
# src/config/:           从命令行和配置文件加载配置,提供功能模块的读取和写入配置。
# src/control/:            提供动作控制功能,如播放/暂停,音量管理,全屏,日志等。
# src/extras/:             平台特殊性相关代码。
# src/modules/:          模块管理。
# src/network/:           提供网络接口。
# src/posix/:               多线程相关。
# src/osd/:                 显示屏幕上的操作。
# src/interface/ :         提供代码中可以调用的接口中,如按键后硬件作出反应。
# src/playlist/:            管理播放功能,如停止,播放,下一首,随机播放等。
# src/text/:                 字符集。
# src/input/:               输入流相关代码。
# src/video_output/ :   初始化视频播放器,把从解码器获得的数据处理后播放。
# src/audio_output/ :   初始化音频混合器,把从解码器获得的数据处理后播放。
# src/stream_output/:  输出音频流和视频流到网络。
# src/test/:                  libvlc测试模块。
# src/misc/:                libvlc使用的其他部分功能,如线程系统,消息队列,CPU的检测,对象查找系统,或平台的特定代码。
# 其他:未详细分析。

0x02 基础概念

对于一个视频的播放,播放器的执行步骤大致如下:

  1. 读取原始数据
  2. 解复用
  3. 解码
  4. 显示

- 阅读剩余部分 -

avformat_open_input详细分析

0x00 前置信息

版本:ffmpeg2.2.0。

0x01 代码

int avformat_open_input(AVFormatContext **ps, const char *filename,
                                        AVInputFormat *fmt, AVDictionary **options)
{
        AVFormatContext *s = *ps;
        int ret = 0;
        AVDictionary *tmp = NULL;
        ID3v2ExtraMeta *id3v2_extra_meta = NULL;

        if (!s && !(s = avformat_alloc_context()))
                return AVERROR(ENOMEM);
        if (fmt)
                s->iformat = fmt;

        if (options)
                av_dict_copy(&tmp, *options, 0);

        if ((ret = av_opt_set_dict(s, &tmp)) < 0)
                goto fail;

        if ((ret = init_input(s, filename, &tmp)) < 0)
                goto fail;

        /* Check filename in case an image number is expected. */
        if (s->iformat->flags & AVFMT_NEEDNUMBER) {
                if (!av_filename_number_test(filename)) {
                        ret = AVERROR(EINVAL);
                        goto fail;
                }
        }

        s->duration = s->start_time = AV_NOPTS_VALUE;
        av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));

        /* Allocate private data. */
        if (s->iformat->priv_data_size > 0) {
                if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {
                        ret = AVERROR(ENOMEM);
                        goto fail;
                }
                if (s->iformat->priv_class) {
                        *(const AVClass **) s->priv_data = s->iformat->priv_class;
                        av_opt_set_defaults(s->priv_data);
                        if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)
                                goto fail;
                }
        }

        /* e.g. AVFMT_NOFILE formats will not have a AVIOContext */
        if (s->pb)
                ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);

        if (s->iformat->read_header)
                if ((ret = s->iformat->read_header(s)) < 0)
                        goto fail;

        if (id3v2_extra_meta &&
                (ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0)
                goto fail;
        ff_id3v2_free_extra_meta(&id3v2_extra_meta);

        if ((ret = queue_attached_pictures(s)) < 0)
                goto fail;

        if (s->pb && !s->data_offset)
                s->data_offset = avio_tell(s->pb);

        s->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;

        if (options) {
                av_dict_free(options);
                *options = tmp;
        }
        *ps = s;
        return 0;

fail:
        ff_id3v2_free_extra_meta(&id3v2_extra_meta);
        av_dict_free(&tmp);
        if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO))
                avio_close(s->pb);
        avformat_free_context(s);
        *ps = NULL;
        return ret;
}

0x02 接口功能

  1. 创建AVFormatContext结构并填充其中的关键字段
  2. 打开一个指定URI,初始化输入模块。
  3. 解析多媒体文件或流的头信息,为每个流创建AVStream结构。

- 阅读剩余部分 -