MediaExtractor介绍

概述


本篇介绍播放器结构中的第一部分Stream+Demuxer.

Awesomeplayer中对应的数据结构主要有DataSourceMediaExtractorMediaSource

其中DataSource 主要负责提供原始数据,MediaSource负责提供demux后的数据(即实际的audio 或者 video 数据包)

MediaExtractor则负责中间的过程,即将从DataSource得到的原始数据解析成解码器需要的es数据,并通过MediaSource的接口输出。

ts为例,extractorawesomeplayer中的位置图如下:

我们按照一个demux的基本结构结合ts的实例来分析整个结构

其中demuxer的部分我们按照ffmpeg demuxer的结构中的主要成员来分析

1 stream -- 功能

2 extractor 创建流程

3 extractor 结构介绍(ts为例)

3.1 demuxer -- read_probe

3.2 demuxer -- read_header

3.3 demuxer -- read_packet

3.4 demuxer -- read_seek

下面通过实际的代码来分析

1 stream -- 功能


stream的主要功能是,从外部介质(本地磁盘或者网络等)获取待播放的原始数据。

对应的数据结构为:DataSource

看下awesomeplayer中对应的代码

a构造函数

 [html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  AwesomePlayer::AwesomePlayer(){  

2.      *************  

3.      DataSource::RegisterDefaultSniffers();  

4.      *************  

5.  }  

看下RegisterDefaultSniffers实现

 [html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  // static  

2.  void DataSource::RegisterSniffer(SnifferFunc func) {  

3.      Mutex::Autolock autoLock(gSnifferMutex);  

4.     

5.      for (List<SnifferFunc>::iterator it = gSniffers.begin();  

6.           it != gSniffers.end(); ++it) {  

7.          if (*it == func) {  

8.              return;  

9.          }  

10.     }  

11.    

12.     gSniffers.push_back(func);  

13. }  

void DataSource::RegisterDefaultSniffers() {

 [html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  RegisterSniffer(SniffMPEG4);  

2.  RegisterSniffer(SniffFragmentedMP4);  

3.  RegisterSniffer(SniffMatroska);  

4.  RegisterSniffer(SniffOgg);  

5.  RegisterSniffer(SniffWAV);  

6.  RegisterSniffer(SniffFLAC);  

7.  RegisterSniffer(SniffAMR);  

8.  RegisterSniffer(SniffMPEG2TS);  

9.  RegisterSniffer(SniffMP3);  

10. RegisterSniffer(SniffAAC);  

11. RegisterSniffer(SniffMPEG2PS);  

12. RegisterSniffer(SniffWVM);  

13.   

14. char value[PROPERTY_VALUE_MAX];  

15. if (property_get("drm.service.enabled", value, NULL)  

16.         && (!strcmp(value, "1") || !strcasecmp(value, "true"))) {  

17.     RegisterSniffer(SniffDRM);  

18. }  

从代码可以看出RegisterDefaultSniffers的主要作用既是注册Sniffer函数

将所有的sniffer函数都挂在全局链表gSniffers中。

sniffer函数的主要作用就是用于探测文件的类型,每种类型的媒体文件都对应一个sniffer函数。

这里从代码可以看出原生的android播放器支持的格式还比较少

这里主要作用就是注册完成后 demuxerread_probe阶段便可以通过调用sniffer函数来探测文件类型,具体等讲解probe的时候结合实际的例子(mpegts)分析

b setDataSource

 [html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  status_t AwesomePlayer::setDataSource(  

2.          int fd, int64_t offset, int64_t length) {  

3.      Mutex::Autolock autoLock(mLock);  

4.     

5.      reset_l();  

6.     

7.      sp<DataSource> dataSource = new FileSource(fd, offset, length);  

8.     

9.      status_t err = dataSource->initCheck();  

10.    

11.     if (err != OK) {  

12.         return err;  

13.     }  

14.    

15.     mFileSource = dataSource;  

16.    

17.     {  

18.         Mutex::Autolock autoLock(mStatsLock);  

19.         mStats.mFd = fd;  

20.         mStats.mURI = String8();  

21.     }  

22.    

23.     return setDataSource_l(dataSource);  

24. }  

代码中构造了FileSource对象赋值给mFileSource,这里FileSource继承自DataSource,提供stream功能。

下面列出filesource类的定义

 [html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  class FileSource : public DataSource {  

2.  public:  

3.      FileSource(const char *filename);  

4.      FileSource(int fd, int64_t offset, int64_t length);  

5.     

6.      virtual status_t initCheck() const;  

7.     

8.      virtual ssize_t readAt(off64_t offset, void *data, size_t size);  

9.     

10.     virtual status_t getSize(off64_t *size);  

11.    

12.     virtual sp<DecryptHandle> DrmInitialization(const char *mime);  

13.    

14.     virtual void getDrmInfo(sp<DecryptHandle> &handle, DrmManagerClient **client);  

15.    

16. protected:  

17.     virtual ~FileSource();  

18.    

19. private:  

20.     int mFd;  

21.     int64_t mOffset;  

22.     int64_t mLength;  

23.     Mutex mLock;  

24.    

25.     /*for DRM*/  

26.     sp<DecryptHandle> mDecryptHandle;  

27.     DrmManagerClient *mDrmManagerClient;  

28.     int64_t mDrmBufOffset;  

29.     int64_t mDrmBufSize;  

30.     unsigned char *mDrmBuf;  

31.    

32.     ssize_t readAtDRM(off64_t offset, void *data, size_t size);  

33.    

34.     FileSource(const FileSource &);  

35.     FileSource &operator=(const FileSource &);  

36. };  



这里filesource提供了readAt方法提供原始数据获取,由于其参数有offset,则支持随机存取,支持seek功能。其构造函数如下

 [html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  FileSource::FileSource(const char *filename)  

2.      : mFd(-1),  

3.        mOffset(0),  

4.        mLength(-1),  

5.        mDecryptHandle(NULL),  

6.        mDrmManagerClient(NULL),  

7.        mDrmBufOffset(0),  

8.        mDrmBufSize(0),  

9.        mDrmBuf(NULL){  

10.    

11.     mFd = open(filename, O_LARGEFILE | O_RDONLY);  

12.    

13.     if (mFd >= 0) {  

14.         mLength = lseek64(mFd, 0, SEEK_END);  

15.     } else {  

16.         ALOGE("Failed to open file '%s'. (%s)", filename, strerror(errno));  

17.     }  

18. }  



构造函数的主要功能就是open给定的文件名,并将句柄存储在mFd中,后面读取数据可以直接使用Linux标准方法读取。

2 awesomeplayerextractor 创建流程


setDataSource的最后,会调用setDataSource_l(dataSource);datasource和对应的extractor对应起来,这里看下流程

 [html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  status_t AwesomePlayer::setDataSource_l(  

2.          const sp<DataSource> &dataSource) {  

3.      sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);  

4.     

5.      if (extractor == NULL) {  

6.          return UNKNOWN_ERROR;  

7.      }  

8.     

9.      if (extractor->getDrmFlag()) {  

10.         checkDrmStatus(dataSource);  

11.     }  

12.    

13.     return setDataSource_l(extractor);  

14. }  


2.1 MediaExtractor::Create

这里通过MediaExtractor::Create创建extractor ,分段来看代码实现。这里还是忽略drm等实现

 [html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  sp<MediaExtractor> MediaExtractor::Create(  

2.          const sp<DataSource> &source, const char *mime) {  

3.      sp<AMessage> meta;  

4.     

5.      String8 tmp;  

6.      if (mime == NULL) {  

7.          float confidence;  

8.          if (!source->sniff(&tmp, &confidence, &meta)) {  

9.              ALOGV("FAILED to autodetect media content.");  

10.    

11.             return NULL;  

12.         }  

13.    

14.         mime = tmp.string();  

15.         ALOGV("Autodetected media content as '%s' with confidence %.2f",  

16.              mime, confidence);  

17.     }  

这里第一步是通过调用datasourcesiniff函数探测文件类型。

看下sniff实现

 [html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  bool DataSource::sniff(  

2.          String8 *mimeType, float *confidence, sp<AMessage> *meta) {  

3.      *mimeType = "";  

4.      *confidence = 0.0f;  

5.      meta->clear();  

6.     

7.      Mutex::Autolock autoLock(gSnifferMutex);  

8.      for (List<SnifferFunc>::iterator it = gSniffers.begin();  

9.           it != gSniffers.end(); ++it) {  

10.         String8 newMimeType;  

11.         float newConfidence;  

12.         sp<AMessage> newMeta;  

13.         if ((*it)(this, &newMimeType, &newConfidence, &newMeta)) {  

14.             if (newConfidence > *confidence) {  

15.                 *mimeType = newMimeType;  

16.                 *confidence = newConfidence;  

17.                 *meta = newMeta;  

18.             }  

19.         }  

20.     }  

21.    

22.     return *confidence > 0.0;  

23. }  

主要是将gSniffers链表中的每种格式的函数调用一遍,选取最高的confidence作为选中的文件格式。后面会以ts为例讲解具体细节。

下面继续

 [html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  MediaExtractor *ret = NULL;  

2.      if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)  

3.              || !strcasecmp(mime, "audio/mp4")) {  

4.          int fragmented = 0;  

5.          if (meta != NULL && meta->findInt32("fragmented", &fragmented) && fragmented) {  

6.              ret = new FragmentedMP4Extractor(source);  

7.          } else {  

8.              ret = new MPEG4Extractor(source);  

9.          }  

10.     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) {  

11.         ret = new MP3Extractor(source, meta);  

12.     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)  

13.             || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {  

14.         ret = new AMRExtractor(source);  

15.     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)) {  

16.         ret = new FLACExtractor(source);  

17.     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) {  

18.         ret = new WAVExtractor(source);  

19.     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_OGG)) {  

20.         ret = new OggExtractor(source);  

21.     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MATROSKA)) {  

22.         ret = new MatroskaExtractor(source);  

23.     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {  

24.         ret = new MPEG2TSExtractor(source);  

25.     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WVM)) {  

26.         // Return now.  WVExtractor should not have the DrmFlag set in the block below.  

27.         return new WVMExtractor(source);  

28.     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC_ADTS)) {  

29.         ret = new AACExtractor(source, meta);  

30.     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2PS)) {  

31.         ret = new MPEG2PSExtractor(source);  

32.     }  

33.    

34.     if (ret != NULL) {  

35.        if (isDrm) {  

36.            ret->setDrmFlag(true);  

37.        } else {  

38.            ret->setDrmFlag(false);  

39.        }  

40.     }  

41.    

42.     return ret;  

43. }  

成功的通过sniff函数确定了文件的格式之后,就可以构造extractor对象了。

例如:如果文件格式是ts格式,则会调用ret = new MPEG2TSExtractor(source);

具体的构造函数就完成了文件头的解析获取了流信息。

2.2 setDataSource_l(extractor)

创建了extractor对象之后,setDataSource_l(extractor) 的主要作用就是使用上面得到的信息来构造播放器框架了。

这里只将重要的语句列出

setVideoSource(extractor->getTrack(i));

setAudioSource(extractor->getTrack(i));

addTextSource_l(i, extractor->getTrack(i));

主要是将文件中的各个流通过上面三个方法,存放在mVideoTrackmAudioTrack,之后作为参数传递给解码器。便建立了解码器与extractor的关联

3 MediaExtractor的结构介绍(ts为例)


这里主要是以ts为例,按照一个demuxer的具体功能组件(仿照ffmpeg结构)来介绍,extractor是如何实现一个demuxer的功能。

ts相关的代码文件是:frameworks/media/libstagefrightplayer/mpeg2ts/MPEG2TSExtractor.cpp

下面是一张MPEG2TSExtractor 的总体结构图:

 

 

下面的很多介绍都会引用此图

解释下这张图:MPEG2TSExtractor是总入口,负责解析文件头信息,提供原始数据包。在功能上讲,MPEG2TSExtractor ATSParser的封装,而ATSParser负责实际的解析工作,

即:MPEG2TSExtractor FileSource中获取数据,提供给ATSParser 进行解析。而MPEG2TSExtractor 对外提供的各种接口及文件信息都是借由ATSParser 来完成的。

比如图中:getTrack接口提供的MediaSource也就是awesomeplayer中的mAudioTrackmVideoTrack,是MPEG2TSSource结构,而MPEG2TSSource的工作由AnotherPacketSourcemSourceImpls)完成

而实际上AnotherPacketSource 是在ATSParser中的Stream中生成的,每个Stream对应一个实际的流。后面分析代码时可以依据上面解释来理解。

3.1 demuxer -- read_probe

这里的probe主要是对应datasourcesniffer函数。具体实现如下:

 [html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  bool SniffMPEG2TS(  

2.          const sp<DataSource> &source, String8 *mimeType, float *confidence,  

3.          sp<AMessage> *) {  

4.      for (int i = 0; i < 5; ++i) {  

5.          char header;  

6.          if (source->readAt(kTSPacketSize * i, &header, 1) != 1  

7.                  || header != 0x47) {  

8.              return false;  

9.          }  

10.     }  

11.    

12.     *confidence = 0.1f;  

13.     mimeType->setTo(MEDIA_MIMETYPE_CONTAINER_MPEG2TS);  

14.    

15.     return true;  

16. }  

基本思路很简单,对于ts文件每个ts包为188字节,每个包的同步字为0x47

这里主要通过datasourcereadAt接口读取一个字节,判断是否是0x47,主要是间隔kTSPacketSize=188读取一个字节判断5

如果全部通过则确定此文件为mpegts文件。设置*confidence = 0.1f;返回。

3.2 demuxer -- read_header

ffmpeg中的read_header函数主要作用是解析文件头信息,获取文件中的具体流信息及参数

这里主要是通过构造函数来完成,具体看下

 [html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  MPEG2TSExtractor::MPEG2TSExtractor(const sp<DataSource> &source)  

2.      : mDataSource(source),  

3.        mParser(new ATSParser),  

4.        mOffset(0) {  

5.      init();  

6.  }  

这里将datasource参数存放在mDataSource中,并构造了ATSParser对象,这里可以这样认定:MPEG2TSExtractorATSParser的封装,具体的工作都是ATSParser完成的

看下其构造函数

 [html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  ATSParser::ATSParser(uint32_t flags)  

2.      : mFlags(flags),  

3.        mAbsoluteTimeAnchorUs(-1ll),  

4.        mNumTSPacketsParsed(0),  

5.        mNumPCRs(0) {  

6.      mPSISections.add(0 /* PID */, new PSISection);  

7.  }  

构造好ATSParser对象之后,调用了init()函数

 [html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  void MPEG2TSExtractor::init() {  

2.      bool haveAudio = false;  

3.      bool haveVideo = false;  

4.      int numPacketsParsed = 0;  

5.     

6.      while (feedMore() == OK) {  

7.          ATSParser::SourceType type;  

8.          if (haveAudio && haveVideo) {  

9.              break;  

10.         }  

11.         if (!haveVideo) {  

12.             sp<AnotherPacketSource> impl =  

13.                 (AnotherPacketSource *)mParser->getSource(  

14.                         ATSParser::VIDEO).get();  

15.    

16.             if (impl != NULL) {  

17.                 haveVideo = true;  

18.                 mSourceImpls.push(impl);  

19.             }  

20.         }  

21.    

22.         if (!haveAudio) {  

23.             sp<AnotherPacketSource> impl =  

24.                 (AnotherPacketSource *)mParser->getSource(  

25.                         ATSParser::AUDIO).get();  

26.    

27.             if (impl != NULL) {  

28.                 haveAudio = true;  

29.                 mSourceImpls.push(impl);  

30.             }  

31.         }  

32.    

33.         if (++numPacketsParsed > 10000) {  

34.             break;  

35.         }  

36.     }  

37.    

38.     ALOGI("haveAudio=%d, haveVideo=%d", haveAudio, haveVideo);  

39. }  

这里feedMore便是解析文件头的具体实现,代码的后半部分主要是当解析成功即feedMore() == OK

feedmore作用有两个:解析头信息+缓冲数据

audio videosource存放在栈mSourceImpls中。看下具体实现

 [html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  status_t MPEG2TSExtractor::feedMore() {  

2.      Mutex::Autolock autoLock(mLock);  

3.     

4.      uint8_t packet[kTSPacketSize];  

5.      ssize_t n = mDataSource->readAt(mOffset, packet, kTSPacketSize);  

6.     

7.      if (n < (ssize_t)kTSPacketSize) {  

8.          return (n < 0) ? (status_t)n : ERROR_END_OF_STREAM;  

9.      }  

10.    

11.     mOffset += n;  

12.     return mParser->feedTSPacket(packet, kTSPacketSize);  

13. }  

从代码中可以看出mParser并不与datasource直接联系,而是mExtractor读取一个ts包,传递给mParser来分析

具体分析过程通过调用mParser->feedTSPacket方法完成,进入ATParser类方法中

 [html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  status_t ATSParser::feedTSPacket(const void *data, size_t size) {  

2.      CHECK_EQ(size, kTSPacketSize);  

3.     

4.      ABitReader br((const uint8_t *)data, kTSPacketSize);  

5.      return parseTS(&br);  

6.  }  

 [html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  status_t ATSParser::parseTS(ABitReader *br) {  

2.     

3.      *****************  

4.     

5.      if (adaptation_field_control == 1 || adaptation_field_control == 3) {  

6.          err = parsePID(  

7.                  br, PID, continuity_counter, payload_unit_start_indicator);  

8.      }  

9.     

10.     ++mNumTSPacketsParsed;  

11.    

12.     return err;  

13. }  


省略部分无关代码

如果对ts文件格式不了解,网上有很多资料。这里解析ts头主要就是解析几个表,如PAT(pid==0) PMT(pid通过解析pat获取) 以及通过上面两步获取实际流pid号之后解析实际数据获取参数。

这里获取pid之后通过调用parsePID来处理。

 [html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  status_t ATSParser::parsePID(  

2.          ABitReader *br, unsigned PID,  

3.          unsigned continuity_counter,  

4.          unsigned payload_unit_start_indicator) {  

5.     

6.          if (PID == 0) {  

7.              parseProgramAssociationTable(§ionBits);  

8.          } else {  

9.     

10.              bool handled = false;  

11.             for (size_t i = 0; i < mPrograms.size(); ++i) {  

12.                 status_t err;  

13.                 if (!mPrograms.editItemAt(i)->parsePSISection(  

14.                             PID, §ionBits, &err)) {  

15.                     continue;  

16.                 }  

17.         section->clear();  

18.    

19.         return OK;  

20.     }  

21.    

22. bool handled = false;  

23.     for (size_t i = 0; i < mPrograms.size(); ++i) {  

24.         status_t err;  

25.         if (mPrograms.editItemAt(i)->parsePID(  

26.                     PID, continuity_counter, payload_unit_start_indicator,  

27.                     br, &err)) {  

28.             if (err != OK) {  

29.                 return err;  

30.             }  

31.    

32.             handled = true;  

33.             break;  

34.         }  

35.     }  

36.    

37.     if (!handled) {  

38.         ALOGV("PID 0x%04x not handled.", PID);  

39.     }  

40.    

41.     return OK;  

42. }  


 

这里主要是根据pid的不同来分别处理,如parseProgramAssociationTable用来处理pat表,此时pid==0

如果不是,则调用mPrograms.editItemAt(i)->parsePSISection来处理pmt

这里如果没有处理过pat表,则 mPrograms.size()==0 会进入下一循环直到找到pat为止

这里不再深入了,解析完pmt表,就知道了文件中有几个流,几路audio 几路video等信息都得到了

后面会调用每个流的parsePID -- >调用每个流的parse函数,后续调用顺序为

mStreams.editValueAt(index)->parse->flush();->parsePES->onPayloadData

这里简单总结下:在ATSParser中有几个内嵌类,

Program类负责解析pmt

Stream类负责解析每个流的具体信息

具体代码不再列举了,总归都是按照ts的标准来解析,若读者不熟悉ts强烈建议仔细阅读下ffmpeg或者ATSParser中的实现方式。

重点注释:

每个Stream代表一个流,可以是音频流或者是视频流,读包的操作主要就是通过Stream接口来完成的。当在extractor中的feedmore方法中读取一个ts包传递给mParser解析时,

如果是实际的数据包,则最终都会存储在Streambuffer中,而stream中的成员mSource(AnotherPacketSource)则是最终传递上去的保存在MPEG2TSExtractor中保存在全局变量中mSourceImpl

而实际的mAudioTrack mVideoTrack则是通过getTrack返回MPEG2TSSource,而MPEG2TSSource 封装了AnotherPacketSource ,也就和实际的stream关联起来,

最终可以通过MPEG2TSSource 读取保存在stream中的数据包

【说明】请仔细消化上面这段话,并结合代码及上面的图来理解

3.3 demuxer -- read_packet

之前分析过,在awesomeplayer中有如下语句

mAudioTrack = extractor->getTrack(*);

mVideoTrack = extractor->getTrack(*);

这里getTrack便是建立awesomeplayerextractor连接的地方(参考上面的黑体部分)

这里分析下ts的具体实现

[html] view plaincopy

1.  sp<MediaSource> MPEG2TSExtractor::getTrack(size_t index) {

2.   

3.   

4.  ************

5.   

6.  return new MPEG2TSSource(this, mSourceImpls.editItemAt(index), seekable);

7.  }

[html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  sp<MediaSource> MPEG2TSExtractor::getTrack(size_t index) {  

2.     

3.     

4.      ************  

5.     

6.      return new MPEG2TSSource(this, mSourceImpls.editItemAt(index), seekable);  

7.  }  

这里在MPEG2TSExtractor定义了一个新的继承自MediaSource的类MPEG2TSSource,返回给上层

其构造函数如下:

[html] view plaincopy

1.  MPEG2TSSource::MPEG2TSSource(

2.  const sp<MPEG2TSExtractor> &extractor,

3.  const sp<AnotherPacketSource> &impl,

4.  bool seekable)

5.  : mExtractor(extractor),

6.  mImpl(impl),

7.  mSeekable(seekable) {

8.  }

[html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  MPEG2TSSource::MPEG2TSSource(  

2.          const sp<MPEG2TSExtractor> &extractor,  

3.          const sp<AnotherPacketSource> &impl,  

4.          bool seekable)  

5.      : mExtractor(extractor),  

6.        mImpl(impl),  

7.        mSeekable(seekable) {  

8.  }  

这里传入的参数mSourceImpls.editItemAt(index)是一个AnotherPacketSource对象(实际生成是在ATSParser中的Stream类中),主要负责缓存数据包,等上层解码器需要数据的时候会从此处读取

看下实际的读包方法:

awesomeplayer中会调用mAudioTrack->read(*)方法,由于mAudioTrack == mVideoTrack == MPEG2TSSource ,因此调用的是MPEG2TSSource read方法

[html] view plaincopy

1.  status_t MPEG2TSSource::read(

2.  MediaBuffer **out, const ReadOptions *options) {

3.  *out = NULL;

4.   

5.  int64_t seekTimeUs;

6.  ReadOptions::SeekMode seekMode;

7.  if (mSeekable && options && options->getSeekTo(&seekTimeUs, &seekMode)) {

8.  mExtractor->seekTo(seekTimeUs);

9.  }

10.  

11. status_t finalResult;

12. while (!mImpl->hasBufferAvailable(&finalResult)) {

13. if (finalResult != OK) {

14. return ERROR_END_OF_STREAM;

15. }

16.  

17. status_t err = mExtractor->feedMore();

18. if (err != OK) {

19. mImpl->signalEOS(err);

20. }

21. }

22.  

23. return mImpl->read(out, options);

24. }

[html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  status_t MPEG2TSSource::read(  

2.          MediaBuffer **out, const ReadOptions *options) {  

3.      *out = NULL;  

4.     

5.      int64_t seekTimeUs;  

6.      ReadOptions::SeekMode seekMode;  

7.      if (mSeekable && options && options->getSeekTo(&seekTimeUs, &seekMode)) {  

8.          mExtractor->seekTo(seekTimeUs);  

9.      }  

10.    

11.     status_t finalResult;  

12.     while (!mImpl->hasBufferAvailable(&finalResult)) {  

13.         if (finalResult != OK) {  

14.             return ERROR_END_OF_STREAM;  

15.         }  

16.    

17.         status_t err = mExtractor->feedMore();  

18.         if (err != OK) {  

19.             mImpl->signalEOS(err);  

20.         }  

21.     }  

22.    

23.     return mImpl->read(out, options);  

24. }  


 

这里首先判断是否需要seek,若不需要。则通过mImpl->hasBufferAvailable看是否有缓存数据包,此处mImpl == mSourceImpls.editItemAt(index),(见上面构造函数参数列表)

如果没有则通过feedMore()读取包

最后通过 mImpl->read(out, options)返回给上层。

这里再解释下:首先每个流都对应一个MPEG2TSSource 对象,而每个MPEG2TSSource 对象都有一个AnotherPacketSource 对象,对应一个缓存列表。一开始extractor通过feedMore解析数据后将数据存储在各个AnotherPacketSource 的缓存列表中

举例来讲:如果某一时刻 audio 缓存类表为空,而video 缓存列表为 4 此时读取audio包会导致mImpl->hasBufferAvailable ==0,此时会通过mExtractor->feedMore 继续缓存数据,若下一包为video则挂在到video缓存(此处video+1=5

直到解析到一个audio包,则返回。此处的实现保证了不会因为包分布不合理导致解码阻塞等情况

看下feedMore实现

[html] view plaincopy

1.  status_t MPEG2TSExtractor::feedMore() {

2.  Mutex::Autolock autoLock(mLock);

3.   

4.  uint8_t packet[kTSPacketSize];

5.  ssize_t n = mDataSource->readAt(mOffset, packet, kTSPacketSize);

6.   

7.  if (n < (ssize_t)kTSPacketSize) {

8.  return (n < 0) ? (status_t)n : ERROR_END_OF_STREAM;

9.  }

10.  

11. mOffset += n;

12. return mParser->feedTSPacket(packet, kTSPacketSize);

13. }

[html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  status_t MPEG2TSExtractor::feedMore() {  

2.      Mutex::Autolock autoLock(mLock);  

3.     

4.      uint8_t packet[kTSPacketSize];  

5.      ssize_t n = mDataSource->readAt(mOffset, packet, kTSPacketSize);  

6.     

7.      if (n < (ssize_t)kTSPacketSize) {  

8.          return (n < 0) ? (status_t)n : ERROR_END_OF_STREAM;  

9.      }  

10.    

11.     mOffset += n;  

12.     return mParser->feedTSPacket(packet, kTSPacketSize);  

13. }  

主要是读取一包,然后解析,调用顺序如下

feedMore->mParser->feedTSPacket->parseTS->ATSParser::parsePID->ATSParser::Program::parsePID->ATSParser::Stream::parse

ATSParser::Stream::parse 最终会将数据包缓存起来(具体实现方式:将数据全部缓存起来,放在mBuffer中,当此包数据都完成时,即得到了一个完整的audio或者video包,调用flush解析好了存在AnotherPacketSource的缓存中。)

3.4 demuxer -- read_seek

当需要seek的时候,入口在awesomeplayerseekto,代码如下

[html] view plaincopy

1.  status_t AwesomePlayer::seekTo(int64_t timeUs) {

2.  ATRACE_CALL();

3.   

4.  if (mExtractorFlags & MediaExtractor::CAN_SEEK) {

5.  Mutex::Autolock autoLock(mLock);

6.  return seekTo_l(timeUs);

7.  }

8.   

9.  return OK;

10. }

[html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  status_t AwesomePlayer::seekTo(int64_t timeUs) {  

2.      ATRACE_CALL();  

3.     

4.      if (mExtractorFlags & MediaExtractor::CAN_SEEK) {  

5.          Mutex::Autolock autoLock(mLock);  

6.          return seekTo_l(timeUs);  

7.      }  

8.     

9.      return OK;  

10. }  

[html] view plaincopy

1.  status_t AwesomePlayer::seekTo_l(int64_t timeUs) {

2.  *********************

3.  mSeeking = SEEK;

4.  mSeekNotificationSent = false;

5.  mSeekTimeUs = timeUs;

6.  modifyFlags((AT_EOS | AUDIO_AT_EOS | VIDEO_AT_EOS), CLEAR);

7.  seekAudioIfNecessary_l();

8.  if (!(mFlags & PLAYING)) {

9.  ALOGV("seeking while paused, sending SEEK_COMPLETE notification"

10. " immediately.");

11. notifyListener_l(MEDIA_SEEK_COMPLETE);

12. mSeekNotificationSent = true;

13. if ((mFlags & PREPARED) && mVideoSource != NULL) {

14. modifyFlags(SEEK_PREVIEW, SET);

15. postVideoEvent_l();

16. }

17. }

18.  

19. return OK;

20. }

[html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  status_t AwesomePlayer::seekTo_l(int64_t timeUs) {  

2.      *********************  

3.      mSeeking = SEEK;  

4.      mSeekNotificationSent = false;  

5.      mSeekTimeUs = timeUs;  

6.      modifyFlags((AT_EOS | AUDIO_AT_EOS | VIDEO_AT_EOS), CLEAR);  

7.      seekAudioIfNecessary_l();  

8.      if (!(mFlags & PLAYING)) {  

9.          ALOGV("seeking while paused, sending SEEK_COMPLETE notification"  

10.              " immediately.");  

11.         notifyListener_l(MEDIA_SEEK_COMPLETE);  

12.         mSeekNotificationSent = true;  

13.         if ((mFlags & PREPARED) && mVideoSource != NULL) {  

14.             modifyFlags(SEEK_PREVIEW, SET);  

15.             postVideoEvent_l();  

16.         }  

17.     }  

18.    

19.     return OK;  

20. }  


首先设置seek标志,mSeeking = SEEK,然后设置seek到的位置 mSeekTimeUs = timeUs;清空标志位。

调用seekAudioIfNecessary_l,看下实现

[html] view plaincopy

1.  void AwesomePlayer::seekAudioIfNecessary_l() {

2.  if (mSeeking != NO_SEEK && mVideoSource == NULL && mAudioPlayer != NULL) {

3.  mAudioPlayer->seekTo(mSeekTimeUs);

4.   

5.  mWatchForAudioSeekComplete = true;

6.  mWatchForAudioEOS = true;

7.   

8.  if (mDecryptHandle != NULL) {

9.  mDrmManagerClient->setPlaybackStatus(mDecryptHandle,

10. Playback::PAUSE, 0);

11. mDrmManagerClient->setPlaybackStatus(mDecryptHandle,

12. Playback::START, mSeekTimeUs / 1000);

13. }

14. }

15. }

[html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  void AwesomePlayer::seekAudioIfNecessary_l() {  

2.      if (mSeeking != NO_SEEK && mVideoSource == NULL && mAudioPlayer != NULL) {  

3.          mAudioPlayer->seekTo(mSeekTimeUs);  

4.     

5.          mWatchForAudioSeekComplete = true;  

6.          mWatchForAudioEOS = true;  

7.     

8.          if (mDecryptHandle != NULL) {  

9.              mDrmManagerClient->setPlaybackStatus(mDecryptHandle,  

10.                     Playback::PAUSE, 0);  

11.             mDrmManagerClient->setPlaybackStatus(mDecryptHandle,  

12.                     Playback::START, mSeekTimeUs / 1000);  

13.         }  

14.     }  

15. }  


 

比较简单,调用mAudioPlayer->seekTo

[html] view plaincopy

1.  status_t AudioPlayer::seekTo(int64_t time_us) {

2.  Mutex::Autolock autoLock(mLock);

3.   

4.  mSeeking = true;

5.  mPositionTimeRealUs = mPositionTimeMediaUs = -1;

6.  mReachedEOS = false;

7.  mSeekTimeUs = time_us;

8.   

9.  // Flush resets the number of played frames

10. mNumFramesPlayed = 0;

11. mNumFramesPlayedSysTimeUs = ALooper::GetNowUs();

12.  

13. if (mAudioSink != NULL) {

14. mAudioSink->flush();

15. } else {

16. mAudioTrack->flush();

17. }

18.  

19. return OK;

20. }

[html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  status_t AudioPlayer::seekTo(int64_t time_us) {  

2.      Mutex::Autolock autoLock(mLock);  

3.     

4.      mSeeking = true;  

5.      mPositionTimeRealUs = mPositionTimeMediaUs = -1;  

6.      mReachedEOS = false;  

7.      mSeekTimeUs = time_us;  

8.     

9.      // Flush resets the number of played frames  

10.     mNumFramesPlayed = 0;  

11.     mNumFramesPlayedSysTimeUs = ALooper::GetNowUs();  

12.    

13.     if (mAudioSink != NULL) {  

14.         mAudioSink->flush();  

15.     } else {  

16.         mAudioTrack->flush();  

17.     }  

18.    

19.     return OK;  

20. }  


 

从代码中看主要是设置标志位

mSeeking = true;

mSeekTimeUs = time_us;

然后清空audioplayer中的pcm数据,而设置好seek标志之后,在audioplayerfill buffer中读取数据时便会设置要读取的时间的数据,具体代码如下

[html] view plaincopy

1.  size_t AudioPlayer::fillBuffer(void *data, size_t size) {

2.  *************

3.  MediaSource::ReadOptions options;

4.  options.setSeekTo(mSeekTimeUs);

5.  err = mSource->read(&mInputBuffer, &options);

6.  *************

7.  }

[html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  size_t AudioPlayer::fillBuffer(void *data, size_t size) {  

2.      *************  

3.      MediaSource::ReadOptions options;  

4.      options.setSeekTo(mSeekTimeUs);  

5.      err = mSource->read(&mInputBuffer, &options);       

6.      *************  

7.  }  

只列出了关键语句。

通过上面分析得出:当收到seek命令时,对于audio来讲就是清空audioplayerpcm数据,设置下次要读取的位置MediaSource::ReadOptions options,其他工作由上层完成(指的是decoder,具体在分析decoder的时候介绍)

audioseek完成之后,看下videoseek

具体代码在onVideoEvent

[html] view plaincopy

1.  void AwesomePlayer::onVideoEvent() {

2.   

3.  ********

4.   

5.  MediaSource::ReadOptions options;

6.  if (mSeeking != NO_SEEK) {

7.  options.setSeekTo(

8.  mSeekTimeUs,

9.  mSeeking == SEEK_VIDEO_ONLY

10. ? MediaSource::ReadOptions::SEEK_NEXT_SYNC

11. : MediaSource::ReadOptions::SEEK_CLOSEST_SYNC);

12. } ​​

13.  

14. status_t err = mVideoSource->read(&mVideoBuffer, &options);

15.  

16. ********

17.  

18. }

[html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  void AwesomePlayer::onVideoEvent() {  

2.     

3.      ********  

4.     

5.      MediaSource::ReadOptions options;  

6.      if (mSeeking != NO_SEEK) {  

7.              options.setSeekTo(  

8.               mSeekTimeUs,  

9.               mSeeking == SEEK_VIDEO_ONLY  

10.                 ? MediaSource::ReadOptions::SEEK_NEXT_SYNC  

11.                 : MediaSource::ReadOptions::SEEK_CLOSEST_SYNC);  

12.    }    ​​  

13.    

14.    status_t err = mVideoSource->read(&mVideoBuffer, &options);  

15.    

16.     ********  

17.    

18. }  


 

这里的处理方式与audio类似,都是设置好seek后的位置 MediaSource::ReadOptions options ,然后再从mVideoSource读取数据时作为参数传递进去(decoder后面介绍)。

但这里可以想到,最终decoder都会设置MediaExtractor 读取位置来达到seek的效果,这里看些tscase

这里在ts extractor中调用read方法读取数据代码如下:

[html] view plaincopy

1.  status_t MPEG2TSSource::read(

2.  MediaBuffer **out, const ReadOptions *options) {

3.  *out = NULL;

4.   

5.  int64_t seekTimeUs;

6.  ReadOptions::SeekMode seekMode;

7.  if (mSeekable && options && options->getSeekTo(&seekTimeUs, &seekMode)) {

8.  mExtractor->seekTo(seekTimeUs);

9.  }

10.  

11. status_t finalResult;

12. while (!mImpl->hasBufferAvailable(&finalResult)) {

13. if (finalResult != OK) {

14. return ERROR_END_OF_STREAM;

15. }

16.  

17. status_t err = mExtractor->feedMore();

18. if (err != OK) {

19. mImpl->signalEOS(err);

20. }

21. }

22.  

23. return mImpl->read(out, options);

24. }

[html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  status_t MPEG2TSSource::read(  

2.          MediaBuffer **out, const ReadOptions *options) {  

3.      *out = NULL;  

4.     

5.      int64_t seekTimeUs;  

6.      ReadOptions::SeekMode seekMode;  

7.      if (mSeekable && options && options->getSeekTo(&seekTimeUs, &seekMode)) {  

8.          mExtractor->seekTo(seekTimeUs);  

9.      }  

10.    

11.     status_t finalResult;  

12.     while (!mImpl->hasBufferAvailable(&finalResult)) {  

13.         if (finalResult != OK) {  

14.             return ERROR_END_OF_STREAM;  

15.         }  

16.    

17.         status_t err = mExtractor->feedMore();  

18.         if (err != OK) {  

19.             mImpl->signalEOS(err);  

20.         }  

21.     }  

22.    

23.     return mImpl->read(out, options);  

24. }  



 

可以看到option已经传递进来,继续跟进mImpl->read

[html] view plaincopy

1.  status_t AnotherPacketSource::read(

2.  MediaBuffer **out, const ReadOptions *) {

3.  *out = NULL;

4.   

5.  Mutex::Autolock autoLock(mLock);

6.  while (mEOSResult == OK && mBuffers.empty()) {

7.  mCondition.wait(mLock);

8.  }

9.   

10. if (!mBuffers.empty()) {

11. const sp<ABuffer> buffer = *mBuffers.begin();

12. mBuffers.erase(mBuffers.begin());

13.  

14. int32_t discontinuity;

15. if (buffer->meta()->findInt32("discontinuity", &discontinuity)) {

16. if (wasFormatChange(discontinuity)) {

17. mFormat.clear();

18. }

19.  

20. return INFO_DISCONTINUITY;

21. } else {

22. int64_t timeUs;

23. CHECK(buffer->meta()->findInt64("timeUs", &timeUs));

24.  

25. MediaBuffer *mediaBuffer = new MediaBuffer(buffer);

26.  

27. mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs);

28.  

29. *out = mediaBuffer;

30. return OK;

31. }

32. }

33.  

34. return mEOSResult;

35. }

[html] view plaincopy在CODE上查看代码片派生到我的代码片

1.  status_t AnotherPacketSource::read(  

2.          MediaBuffer **out, const ReadOptions *) {  

3.      *out = NULL;  

4.     

5.      Mutex::Autolock autoLock(mLock);  

6.      while (mEOSResult == OK && mBuffers.empty()) {  

7.          mCondition.wait(mLock);  

8.      }  

9.     

10.     if (!mBuffers.empty()) {  

11.         const sp<ABuffer> buffer = *mBuffers.begin();  

12.         mBuffers.erase(mBuffers.begin());  

13.    

14.         int32_t discontinuity;  

15.         if (buffer->meta()->findInt32("discontinuity", &discontinuity)) {  

16.             if (wasFormatChange(discontinuity)) {  

17.                 mFormat.clear();  

18.             }  

19.    

20.             return INFO_DISCONTINUITY;  

21.         } else {  

22.             int64_t timeUs;  

23.             CHECK(buffer->meta()->findInt64("timeUs", &timeUs));  

24.    

25.             MediaBuffer *mediaBuffer = new MediaBuffer(buffer);  

26.    

27.             mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs);  

28.    

29.             *out = mediaBuffer;  

30.             return OK;  

31.         }  

32.     }  

33.    

34.     return mEOSResult;  

35. }  



 

从代码里看并没有使用传递进来的 ReadOptions ,预计seek动作实在decoder中解决的。等分析decoder的时候再详细看。

到此,extractor就分析完了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值