Android播放器框架分析之AwesomePlayer


date: 2013.09.25; modification:2013.09.28

目录:

  • 1 简介
  • 2 AwesomePlayer概述
    • 2.1 关键成员分析
      • 2.1.1 Demux相关
      • 2.1.2 音频相关
      • 2.1.3 视频相关
      • 2.1.4 其他
    • 2.2 基本播放流程
      • 2.2.1 设置数据源URI
      • 2.2.2 开启定时器队列,并且 Post一个AsyncPrepareEvent 事件
      • 2.2.3 AsyncPrepare 事件被触发
      • 2.2.4 Post 第一个VideoEvent
      • 2.2.5 VideoEvent 被触发
    • 2.3 视频 / 音频 分离
      • 2.3.1 创建DataSource
      • 2.3.2 根据DataSource 创建MediaExtractor
      • 2.3.3 根据MediaExtractor 做A/V分离
    • 2.4 创建视频解码器
    • 2.5 OMXCodec::Create
      • 2.5.1 OMXCodec::findMatchingCodecs 找出可能匹配的Codec
      • 2.5.2 InstantiateSoftwareCodec 创建软编码器
      • 2.5.3 编码器名称的一点说明
      • 2.5.4 创建OMXCodec
    • 2.6 解封装, 解码
  • 3 参考资料

1 简介

Java层 要开启一个播放器进行播放, 需要以下几行代码:

1
2
3
4
5
MediaPlayer mp = new MediaPlayer(); 
mp.setDisplay (...);            /// 设置播放器Suface 
mp.setDataSource(PATH_TO_FILE); ///设置媒体URI 
mp.prepare();                   /// 初始化播放器 
mp.start();                     /// 开始播放

MediaPlayer 的 Native 层定义了各种负责实际播放的player, 分别对应不同的媒体类型. 其中比较重要的一个player就是 stagefright. StagefrightPlayer其实只是个壳, 里面具体调用的是AwesomePlayer.

2 AwesomePlayer概述

忽略掉 JNI 封装层, Stagefright 从 AwesomePlayer开始. AwesomePlayer 是Stagefright核心. AwesomePlayer有一些接口甚至与MediaPlayer 是一一对应的, 例如setDataSource, prepare.

AwesomePlayer的结构框图如下:

Android播放器框架分析之AwesomePlayer_第1张图片

AwesomePlayer结构框图

说明:

  • 本文中DataSource有两个概念:
    • 一个是上面框图中的DataSource Input(这是为了区分, 我给起的名字, 在代码中就叫DataSource)指的是单纯的数据输入(未demux的), 例如:
      • 对http来说, 指的是一个网络连接.
      • 对本地播放来说指的是一个open的流媒体文件.
    • 在后文的setDataSource中的datasource指的是从数据输入到demux输出的一个过程(即上面框图中最外层的DataSource).
    • 这两个概念在代码中都有出现, 但是代表数据输入的datasource只在setDataSource接口的内部实现中出现, 对于外部看来, datasource指的就是图中外层的DataSource.
  • VideoTrack与AudioTrack指的是Extractor(即demux)的两个通道, 从这里输出的分别就是单纯的解复用后的Video/Audio流了.
  • VideoRenderer + Surface即视频的输出.
  • AudioSink即音频的输出.

2.1 关键成员分析

2.1.1 Demux相关

sp<MediaSource> mVideoTrack; sp<MediaSource> mAudioTrack

分别代表一个视频轨道和音频轨道, 用于提取视频流和音频流(Demux后但未解码的数据). mVideoTrack和mAudioTrack 在 onPrepareAsyncEvent事件被触发时, 由MediaExtractor分离出来.

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
void AwesomePlayer::onPrepareAsyncEvent() {  
         status_t err = finishSetDataSource_l();  
 
status_t AwesomePlayer::finishSetDataSource_l() {  
  sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);  
     return setDataSource_l(extractor);  
}  
 
status_t AwesomePlayer::setDataSource_l( const sp<MediaExtractor> &extractor) {  
     for ( size_t i = 0; i < extractor->countTracks(); ++i) {  
         sp<MetaData> meta = extractor->getTrackMetaData(i);  
 
         const char *mime;  
         CHECK(meta->findCString(kKeyMIMEType, &mime));  
 
         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 ;  
         if (haveAudio && haveVideo) {  
             break ;  
         }  
     }  

从上面这段代码可以看到AwesomePlayer默认采用第一个VideoTrack和第一个AudioTrack, 那如何切换VideoTrack和AudioTrack?

2.1.2 音频相关

sp<MediaSource> mAudioSource;

mAudioSource 可以认为是一个音频解码器的封装

sp<MediaPlayerBase::AudioSink> mAudioSink;

mAudioSink 代表一个音频输出设备. 用于播放解码后的音频数据. (AudioSink is used for in-memory decode and potentially other applications where output doesn't go straight to hardware)

AudioPlayer *mAudioPlayer;

mAudioPlayer 把mAudioSource和mAudioSink 包起来,完成一个音频播放器的功能. 如start, stop, pause, seek 等.

AudioPlayer和 AudioSink通过Callback建立关联. 当AudioSink可以输出音频时,会通过回调通知AudioPlayer填充音频数据. 而此时AudioPlayer 会尝试从AudioSource 读取音频数据.

2.1.3 视频相关

sp<MediaSource> mVideoSource

mVideoSource 可以认为是一个视频解码器的封装, 用于产生视频图像供AwesomeRender渲染, mVideoSource的数据源则由mVideoTrack提供.

mVideoSource 由OMXCodec创建.

1
2
3
4
5
6
7
status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {  
     mVideoSource = OMXCodec::Create(  
             mClient.interface(), mVideoTrack->getFormat(),  
             false , // createEncoder  
             mVideoTrack,  
             NULL, flags);  
}  

sp<AwesomeRenderer> mVideoRenderer

负责将解码后的图片渲染输出

sp<ISurface> mISurface

供播放器渲染的画布

2.1.4 其他

OMXClient mClient

OMX可以理解为一个编解码器的库, AwesomePlayer利用OMXClient 跟OMX IL进行通信. 这里OMX IL类似于一个服务端. AwesomePlayer 作为一个客户端, 请求OMX IL进行解码的工作.

TimedEventQueue mQueue

AwesomePlayer采用定时器队列的方式进行运作. mQueue 在MediaPlayer调用 prepare的时候就开始运作, (而不是MediaPlayer.start).

1
2
3
4
5
6
7
status_t AwesomePlayer::prepareAsync_l() { 
     if (!mQueueStarted) { 
         mQueue.start(); 
         mQueueStarted = true
    
     return OK; 

AwesomePlayer处理了几个定时器事件, 包括:

  • onVideoEvent();
  • onStreamDone();
  • onBufferingUpdate();
  • onCheckAudioStatus();
  • onPrepareAsyncEvent();

总结: 从关键的成员可以看出, AwesomePlayer 拥有视频源和音频源 (VideoTrack, AudioTrack), 有音视频解码器(VideoSoure, AudioSource), 可以渲染图像 (AwesomeRenderer) , 可以输出声音 (AudioSink), 具备一个播放器完整的材料了.

2.2 基本播放流程

2.2.1 设置数据源URI

1
2
3
4
5
6
status_t AwesomePlayer::setDataSource_l(  
         const char *uri, const KeyedVector<String8, String8> *headers) {  
     /// 这里只是把URL保存起来而已, 真正的工作在Prepare之后进行  
     mUri = uri;  
     return OK;  

2.2.2 开启定时器队列,并且 Post一个AsyncPrepareEvent 事件

1
2
3
4
5
6
7
8
9
10
11
12
status_t AwesomePlayer::prepareAsync_l() {  
 
     /// 开启定时器队列  
     mQueue.start();  
 
     /// Post AsyncPrepare 事件  
     mAsyncPrepareEvent = new AwesomeEvent(  
             this , &AwesomePlayer::onPrepareAsyncEvent);  
 
     mQueue.postEvent(mAsyncPrepareEvent);  
     return OK;  
}  

Prepare 之后, AwesomePlayer 开始运作.

2.2.3 AsyncPrepare 事件被触发

当这个事件被触发时, AwesomePlayer 开始创建 VideoTrack和AudioTrack , 然后创建 VideoDecoder和AudioDecoder

1
2
3
4
5
6
7
8
9
10
void AwesomePlayer::onPrepareAsyncEvent() {  
     /// a. 创建视频源和音频源  
     finishSetDataSource_l();  
 
     /// b. 创建视频解码器  
     initVideoDecoder();  
 
     /// c. 创建音频解码器  
     initAudioDecoder();  
}  

至此,播放器准备工作完成, 可以开始播放了

2.2.4 Post 第一个VideoEvent

AwesomePlayer::play() 调用 -> AwesomePlayer::play_l() 调用 -> AwesomePlayer::postVideoEvent_l(int64_t delayUs)

1
2
3
void AwesomePlayer::postVideoEvent_l(int64_t delayUs) {  
     mQueue.postEventWithDelay(mVideoEvent, delayUs < 0 ? 10000 : delayUs);  
}  

2.2.5 VideoEvent 被触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void AwesomePlayer::onVideoEvent() {  
 
     /// 从视频解码器中读出视频图像  
     mVideoSource->read(&mVideoBuffer, &options);  
 
     /// 创建AwesomeRenderer (如果没有的话)  
     if (mVideoRendererIsPreview || mVideoRenderer == NULL) {  
         initRenderer_l();  
     }  
 
     /// 渲染视频图像  
      mVideoRenderer->render(mVideoBuffer);  
 
     /// 再次发送一个VideoEvent, 这样播放器就不停的播放了  
      postVideoEvent_l();  
}  

总结: SetDataSource -> Prepare -> Play -> postVieoEvent -> OnVideoEvent -> postVideoEvent-> .... onVideoEvent-> postStreamDoneEvent -> 播放结束

2.3 视频 / 音频 分离

2.3.1 创建DataSource

如前面提到的, 当AsyncPrepare 事件被触发时, AwesomePlayer会调用 finishSetDataSource_l 创建 VideoTrack 和 AudioTrack.

finishSetDataSource_l 通过URI前缀判断 媒体类型, 比如 http, rtsp,或者本地文件等 这里的uri就是一开始 通过setDataSource设置的 根据uri 创建相应的DataSource, 再进一步的利用 DataSource 创建MediaExtractor做A/V分离

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
     status_t AwesomePlayer::finishSetDataSource_l() {  
         sp<datasource> dataSource;  
         /// 通过URI前缀判断媒体类型, 比如 http, rtsp,或者本地文件等  
         /// 这里的uri就是一开始 通过setDataSource设置的  
         /// 根据uri 创建相应的MediaExtractor  
 
         if (!strncasecmp( "http://" , mUri.string(), 7)) {  
             mConnectingDataSource = new NuHTTPDataSource;  
             mConnectingDataSource->connect(mUri, &mUriHeaders);  
             mCachedSource = new NuCachedSource2(mConnectingDataSource);  
             dataSource = mCachedSource;  
         } else if (!strncasecmp( "rtsp://" , mUri.string(), 7)) {  
             mRTSPController->connect(mUri.string());  
             sp<mediaextractor> extractor = mRTSPController.get();  
 
             /// rtsp 比较特殊, MediaExtractor对象的创建不需要DataSource  
             return setDataSource_l(extractor);  
         } else {  
             /// 本地文件  
             dataSource = DataSource::CreateFromURI(mUri.string(), &mUriHeaders);  
         }  
 
         /// 用dataSource创建一个MediaExtractor用于A/V分离       
         sp<mediaextractor> extractor = MediaExtractor::Create(dataSource);  
 
         return setDataSource_l(extractor);  
    
</mediaextractor></mediaextractor></datasource>

2.3.2 根据DataSource 创建MediaExtractor

先看看 AwesomePlayer 的构造函数,里面有一行代码.

1
2
3
4
5
AwesomePlayer::AwesomePlayer(){  
     //...  
     DataSource::RegisterDefaultSniffers();  
     //...  
}  

RegisterDefaultSniffers 注册了一些了媒体的MIME类型的探测函数.

1
2
3
4
5
6
7
8
9
void DataSource::RegisterDefaultSniffers() {  
     RegisterSniffer(SniffMPEG4);  
     RegisterSniffer(SniffMatroska);  
     RegisterSniffer(SniffOgg);  
     RegisterSniffer(SniffWAV);  
     RegisterSniffer(SniffAMR);  
     RegisterSniffer(SniffMPEG2TS);  
     RegisterSniffer(SniffMP3);  

这些探测用于判断媒体的MIME类型, 进而决定要创建什么样的MediaExtractor.

再回到 MediaExtractor::Create, MediaExtractor对象在这里创建. 下面代码有点长, 其实这段代码只是根据MIME类型创建Extractor的各个分支而已.

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
     sp<mediaextractor> MediaExtractor::Create(
             const sp<datasource> &source, const char *mime) {
         sp<amessage> meta;
 
         String8 tmp;
         if (mime == NULL) {
             float confidence;
             if (!source->sniff(&tmp, &confidence, &meta)) {
                 LOGV( "FAILED to autodetect media content." );
 
                 return NULL;
             }
 
             mime = tmp.string();
             LOGV( "Autodetected media content as '%s' with confidence %.2f" ,
                  mime, confidence);
         }
 
         bool isDrm = false ;
         // DRM MIME type syntax is "drm+type+original" where
         // type is "es_based" or "container_based" and
         // original is the content's cleartext MIME type
         if (! strncmp (mime, "drm+" , 4)) {
             const char *originalMime = strchr (mime+4, '+' );
             if (originalMime == NULL) {
                 // second + not found
                 return NULL;
             }
             ++originalMime;
             if (! strncmp (mime, "drm+es_based+" , 13)) {
                 // DRMExtractor sets container metadata kKeyIsDRM to 1
                 return new DRMExtractor(source, originalMime);
             } else if (! strncmp (mime, "drm+container_based+" , 20)) {
                 mime = originalMime;
                 isDrm = true ;
             } else {
                 return NULL;
             }
         }
 
         MediaExtractor *ret = NULL;
         if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)
                 || !strcasecmp(mime, "audio/mp4" )) {
             ret = new MPEG4Extractor(source);
         } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) {
             ret = new MP3Extractor(source, meta);
         } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)
                 || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
             ret = new AMRExtractor(source);
         } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)) {
             ret = new FLACExtractor(source);
         } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) {
             ret = new WAVExtractor(source);
         } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_OGG)) {
             ret = new OggExtractor(source);
         } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MATROSKA)) {
             ret = new MatroskaExtractor(source);
         } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {
             ret = new MPEG2TSExtractor(source);
         } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_AVI)) {
             ret = new AVIExtractor(source);
         } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WVM)) {
             ret = new WVMExtractor(source);
         } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC_ADTS)) {
             ret = new AACExtractor(source);
         }
 
         if (ret != NULL) {
            if (isDrm) {
                ret->setDrmFlag( true );
            } else {
                ret->setDrmFlag( false );
            }
         }
 
         return ret;
     }
</amessage></datasource></mediaextractor>

2.3.3 根据MediaExtractor 做A/V分离

再看看 setDataSource_l(const sp &extractor) , 这是A/V分离的最后步骤.

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
     status_t AwesomePlayer::setDataSource_l( const sp<mediaextractor> &extractor) {  
 
         /// 从全部的Track中选取一个Video Track和一个AudioTrack  
         for ( size_t i = 0; i < extractor->countTracks(); ++i) {  
             sp<metadata> meta = extractor->getTrackMetaData(i);  
 
             const char *mime;  
             CHECK(meta->findCString(kKeyMIMEType, &mime));  
 
             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 ;  
             }  
 
             if (haveAudio && haveVideo) {  
                 break ;  
             }  
         }  
 
         /// Flags 标志这个媒体的一些属性:  
          /// CAN_SEEK_BACKWARD 是否能后退10秒  
         /// CAN_SEEK_FORWARD 是否能前进10秒  
         /// CAN_SEEK 能否Seek?  
         /// CAN_PAUSE 能否暂停  
         mExtractorFlags = extractor->flags();  
         return OK;  
     }  
</metadata></mediaextractor>

从这个函数可以看到MediaExtractor 需要实现的基本比较重要的接口 (这个几个接口都是纯虚函数, 可见Extractor的子类是一定要搞定它们的)

virtual size_t countTracks() = 0; /// 该媒体包含了几个Track?

virtual sp getTrack(size_t index) = 0; /// 获取指定的Video/Audio Track, 可以看到一个Track本质上就是一个MediaSource.

virtual sp getTrackMetaData ( size_t index, uint32_t flags = 0) = 0; ///获取指定的Track的MetaData. 在AwesomePlayer 中, MetaData 实际上就是一块可以任意信息字段的叉烧, 字段类型可以是字符串或者是整形等等.这里Track的MetaData包含了Track的MIME类型. 这样AwesomePlayer就可以知道这个Track是Video 还是Audio的了.

总结: 至此, AwesomePlayer 就拥有VideoTrack 和AudioTrack了 (可能只有VideoTrack或者只有AudioTrack, 例如MP3). 接下来 音视频解码器 VideoSource/AudioSource 将从Video/Audio Track 中读取数据进行解码.

2.4 创建视频解码器

VideoTrack/AudioTrack 创建完毕之后, 紧接着就是创建 VideoSource了 (见 1.2.3). 看看initVideoDecoder

1
2
3
4
5
6
7
8
9
status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {  
     mVideoSource = OMXCodec::Create(  
             mClient.interface(), mVideoTrack->getFormat(),  
             false , // createEncoder  
             mVideoTrack,  
             NULL, flags);  
     /// ...  
     return mVideoSource != NULL ? OK : UNKNOWN_ERROR;  
}  

VideoSource 是由 OMXCodec::Create 创建的. 从OMXCodec::Create的参数可以看出创建一个视频解码器需要什么材料:

  1. OMXClient. 用于跟OMX IL 通讯. 假如最后用的是OMXCodec 也不是SoftCodec的话, 需要用到它.

  2. mVideoTrack->getFormat (). getFormat返回包含该video track格式信息的MetaData.

  3. mVideoTrack. 如前面1.3.3 说的. 解码器会从 Video Track 中读取数据进行解码.

2.5 OMXCodec::Create

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
49
50
51
     sp<mediasource> OMXCodec::Create(  
             const sp<iomx> &omx,  
             const sp<metadata> &meta, bool createEncoder,  
             const sp<mediasource> &source,  
             const char *matchComponentName,  
             uint32_t flags) {  
 
         /// 获取MIME类型  
         const char *mime;  
         bool success = meta->findCString(kKeyMIMEType, &mime);  
 
         /// 根据MIME找出可能匹配的Codec  
         Vector<string8> matchingCodecs;  
         findMatchingCodecs(  
                 mime, createEncoder, matchComponentName, flags, &matchingCodecs);  
 
         IOMX::node_id node = 0;  
 
         /// 对每一种可能匹配的Codec, 尝试申请Codec  
         const char *componentName;  
         for ( size_t i = 0; i < matchingCodecs.size(); ++i) {  
             componentName = matchingCodecs[i].string();  
 
             /// 尝试申请软Codec  
             sp<mediasource> softwareCodec = createEncoder?  
                 InstantiateSoftwareEncoder(componentName, source, meta):  
                 InstantiateSoftwareCodec(componentName, source);  
 
             if (softwareCodec != NULL) {  
                 return softwareCodec;  
             }  
 
             /// 尝试申请OMXCodec  
             status_t err = omx->allocateNode(componentName, observer, &node);  
             if (err == OK) {  
                 sp<omxcodec> codec = new OMXCodec(  
                         omx, node, quirks,  
                         createEncoder, mime, componentName,  
                         source);  
 
                 /// 配置申请出来的OMXCodec  
                 err = codec->configureCodec(meta, flags);  
                 if (err == OK) {  
                     return codec;  
                 }  
             }  
         }  
 
         return NULL;  
    
</omxcodec></mediasource></string8></mediasource></metadata></iomx></mediasource>

2.5.1 OMXCodec::findMatchingCodecs 找出可能匹配的Codec

findMatchingCodecs 根据传入的MIME 从kDecoderInfo 中找出MIME对于的Codec名 (一种MIME可能对应多种Codec)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
     void   
     OMXCodec::findMatchingCodecs(  
             const char *mime,  
             bool createEncoder, const char *matchComponentName,  
             uint32_t flags,  
             Vector<string8> *matchingCodecs) {  
 
         for ( int index = 0;; ++index) {  
             const char *componentName;  
 
         componentName = GetCodec(  
                         kDecoderInfo,  
                         sizeof (kDecoderInfo) / sizeof (kDecoderInfo[0]),  
                         mime, index);  
 
 
             matchingCodecs->push(String8(componentName));  
         }  
    
</string8>

看看 kDecoderInfo 里面包含了什么Codec吧, 有点长.

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
49
50
51
52
53
54
55
56

你可能感兴趣的:(Android播放器框架分析之AwesomePlayer)