Android Media Framework(3): Stagefright框架流程解读

1. StageFright介绍

Android froyo版本多媒体引擎做了变动,新添加了stagefright框架,并且默认情况android选择stagefright,并没有完全抛弃opencore,主要是做了一个OMX层,仅仅是对 opencore的omx-component部分做了引用。stagefright是在MediaPlayerService这一层加入的,和opencore是并列的。Stagefright在 Android中是以shared library的形式存在(libstagefright.so),其中的module -- AwesomePlayer可用来播放video/audio。 AwesomePlayer提供许多API,可以让上层的应用程序(Java/JNI)来调用。

2. StageFright数据流封装

2.1 由数据源DataSource生成MediaExtractor

通过MediaExtractor::Create(dataSource)来实现。Create方法通过两步来生成相应的 MediaExtractor(MediaExtractor.cpp):

通过dataSource->sniff来探测数据类型
生成相应的Extractor:

 if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4) || !strcasecmp(mime, "audio/mp4")){        
    return new MPEG4Extractor(source);    
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) {       
     return new MP3Extractor(source, meta);    
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB) || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {        
    return new AMRExtractor(source);    
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) {        
    return new WAVExtractor(source);    
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_OGG)) {        
    return new OggExtractor(source);    
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MATROSKA)) {        
    return new MatroskaExtractor(source);   
 } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {        
    return new MPEG2TSExtractor(source);    
}    

2.2 demux

把音视频轨道分离,生成mVideoTrack和mAudioTrack两个MediaSource。代码如下(AwesomePlayer.cpp):

if (!haveVideo && !strncasecmp(mime, "video/", 6)) {       
    setVideoSource(extractor->getTrack(i));        
    haveVideo = true;   
} else if (!haveAudio && !strncasecmp(mime, "audio/", 6)) {       
    setAudioSource(extractor->getTrack(i));        
    haveAudio = true;   
}    

2.3 MediaSource(Parse, Decode)

得到的这两个MediaSource,只具有parser功能,没有decode功能。还需要对这两个MediaSource做进一步的包装,获取了两个MediaSource(具有parse和decode功能):

mVideoSource = OMXCodec::Create(mClient.interface(), 
                                mVideoTrack->getFormat(), 
                                false, // createEncoder            
                                mVideoTrack,            
                                NULL, 
                                flags);
mAudioSource = OMXCodec::Create(mClient.interface(), 
                                mAudioTrack->getFormat(),               
                                false, // createEncoder                
                                mAudioTrack);    

当调用MediaSource.start()方法后,它的内部就会开始从数据源获取数据并解析,等到缓冲区满后便停止。在AwesomePlayer里就可以调用MediaSource的read方法读取解码后的数据。

对于mVideoSource来说,读取的数据:mVideoSource->read(&mVideoBuffer, &options)交给显示模块进行渲染,mVideoRenderer->render(mVideoBuffer);
对mAudioSource来说,用mAudioPlayer对mAudioSource进行封装,然后由mAudioPlayer负责读取数据和播放控制。

3. StageFright的Decode

经过“数据流的封装”得到的两个MediaSource,其实是两个OMXCodec。AwesomePlayer和mAudioPlayer都是从MediaSource中得到数据进行播放。AwesomePlayer得到的是最终需要渲染的原始视频数据,而mAudioPlayer读取的是最终需要播放的原始音频数据。也就是说,从OMXCodec中读到的数据已经是原始数据了。

OMXCodec是怎么把数据源经过parse、decode两步以后转化成原始数据的。从OMXCodec::Create这个构造方法开始,它的参数:

  • IOMX &omx指的是一个OMXNodeInstance对象的实例。
  • MetaData &meta这个参数由MediaSource.getFormat获取得到。这个对象的主要成员就是一个KeyedVector<uint32_t, typed_data> mItems,里面存放了一些代表MediaSource格式信息的名值对。
  • bool createEncoder指明这个OMXCodec是编码还是解码。
  • MediaSource &source是一个MediaExtractor。
  • char *matchComponentName指定一种Codec用于生成这个OMXCodec。

先使用findMatchingCodecs寻找对应的Codec,找到以后为当前IOMX分配节点并注册事件监听器:omx->allocateNode(componentName, observer, &node)。最后,把IOMX封装进一个OMXCodec:

sp<OMXCodec> codec = new OMXCodec(omx, node, quirks, createEncoder, mime, componentName, source);

这样就得到了OMXCodec。

AwesomePlayer中得到这个OMXCodec后,首先调用mVideoSource->start()进行初始化。 OMXCodec初始化主要是做两件事:
(1) 向OpenMAX发送开始命令。mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle)
(2) 调用allocateBuffers()分配两个缓冲区,存放在Vector<BufferInfo> mPortBuffers中,分别用于输入和输出。
AwesomePlayer开始播放后,通过mVideoSource->read(&mVideoBuffer, &options)读取数据。
mVideoSource->read(&mVideoBuffer, &options)具体是调用OMXCodec.read来读取数据。而OMXCodec.read主要分两步来实现数据的读取:
(1) 通过调用drainInputBuffers()对mPortBuffers[kPortIndexInput]进行填充,这一步完成 parse。由OpenMAX从数据源把demux后的数据读取到输入缓冲区,作为OpenMAX的输入。
(2) 通过fillOutputBuffers()对mPortBuffers[kPortIndexOutput]进行填充,这一步完成 decode。由OpenMAX对输入缓冲区中的数据进行解码,然后把解码后可以显示的视频数据输出到输出缓冲区。
AwesomePlayer通过mVideoRenderer->render(mVideoBuffer)对经过parse和decode 处理的数据进行渲染。一个mVideoRenderer其实就是一个包装了IOMXRenderer的AwesomeRemoteRenderer:

mVideoRenderer = new AwesomeRemoteRenderer(
                                        mClient.interface()->createRenderer(mISurface, component, 
                                        (OMX_COLOR_FORMATTYPE)format, decodedWidth, decodedHeight, 
                                        mVideoWidth, mVideoHeight, rotationDegrees));

4. StageFright处理流程

Audioplayer 为AwesomePlayer的成员,audioplayer通过callback来驱动数据的获取,awesomeplayer则是通过 videoevent来驱动。二者有个共性,就是数据的获取都抽象成mSource->Read()来完成,且read内部把parser和dec 绑在一起。Stagefright AV同步部分,audio完全是callback驱动数据流,video部分在onVideoEvent里会获取audio的时间戳,是传统的AV时间戳做同步。

4.1 AwesomePlayer的Video主要有以下几个成员:

(1) mVideoSource(解码视频)
(2) mVideoTrack(从多媒体文件中读取视频数据)
(3) mVideoRenderer(对解码好的视频进行格式转换,android使用的格式为RGB565)
(4) mISurface(重绘图层)
(5) mQueue(event事件队列)

4.2 stagefright运行时的Audio部分抽象流程如下:

(1) 设置mUri的路径
(2) 启动mQueue,创建一个线程来运行 threadEntry(命名为TimedEventQueue,这个线程就是event调度器)
(3) 打开mUri所指定的文件的头部,则会根据类型选择不同的分离器(如MPEG4Extractor)
(4) 使用 MPEG4Extractor对MP4进行音视频轨道的分离,并返回MPEG4Source类型的视频轨道给mVideoTrack
(5) 根据 mVideoTrack中的编码类型来选择解码器,avc的编码类型会选择AVCDecoder,并返回给mVideoSource,并设置mVideoSource中的mSource为mVideoTrack
(6) 插入onVideoEvent到Queue中,开始解码播放
(7) 通过mVideoSource对象来读取解析好的视频buffer

如果解析好的buffer还没到AV时间戳同步的时刻,则推迟到下一轮操作
(1) mVideoRenderer为空,则进行初始化(如果不使用 OMX会将mVideoRenderer设置为AwesomeLocalRenderer)
(2) 通过mVideoRenderer对象将解析好的视频buffer转换成RGB565格式,并发给display模块进行图像绘制
(3) 将onVideoEvent重新插入event调度器来循环

4.3 数据由源到最终解码后的流程如下:

  • 设置DataSource,数据源可以两种URI和FD。URI可以http: //,rtsp://等。FD是一个本地文件描述符,能过FD,可以找到对应的文件。
  • 由DataSource生成MediaExtractor。通过sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);来实现。 MediaExtractor::Create(dataSource)会根据不同的数据内容创建不同的数据读取对象。
  • 通过调用setVideoSource由MediaExtractor分解生成音频数据流(mAudioTrack)和视频数据流(mVideoTrack)。
  • onPrepareAsyncEvent()如果DataSource是URL的话,根据地址获取数据,并开始缓冲,直到获取到mVideoTrack和mAudioTrack。mVideoTrack和mAudioTrack通过调用initVideoDecoder()和initAudioDecoder()来生成 mVideoSource和mAudioSource这两个音视频解码器。然后调用postBufferingEvent_l()提交事件开启缓冲。
  • 数据缓冲的执行函数是onBufferingUpdate()。缓冲区有足够的数据可以播放时,调用play_l()开始播放。play_l()中关键是调用了postVideoEvent_l(),提交了 mVideoEvent。这个事件执行时会调用函数onVideoEvent()。这个函数通过调用 mVideoSource->read(&mVideoBuffer, &options)进行视频解码。音频解码通过mAudioPlayer实现。
  • 视频解码器解码后通过mVideoSource->read读取一帧帧的数据,放到mVideoBuffer中,最后通过 mVideoRenderer->render(mVideoBuffer)把视频数据发送到显示模块。当需要暂停或停止时,调用cancelPlayerEvents来提交事件用来停止解码,还可以选择是否继续缓冲数据。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容