先来看看MediaExtractor所处的位置:

(一)创建流程

在GenericSource.cpp的NuPlayer::GenericSource::initFromDataSource()函数中调用了:

extractor = MediaExtractor::Create(mDataSource,mimeType.isEmpty() ? NULL : mimeType.string());

NuPlayer会为每个播放的文件,创建一个MediaExtractor,这个类的作用就是解析,概念上等同于demuxer或者Parser。MediaExtractor负责从文件中分离音视频数据,并抽象成MediaSource。MediaSource生产数据,送往MediaCodec。MediaCodec又将数据通过ACodec和OpenMAX的接口送往组件。组件解码后的数据会返回给MediaCodec,之后再由MediaCodec送往Renderer模块。

函数如下所示(MediaExtractor.cpp):

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)) {//此时的source是FileSourceALOGV("FAILED to autodetect media content.");return NULL;}mime = tmp.string();ALOGV("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 typeif (!strncmp(mime, "drm+", 4)) {const char *originalMime = strchr(mime+4, '+');if (originalMime == NULL) {// second + not foundreturn NULL;}++originalMime;if (!strncmp(mime, "drm+es_based+", 13)) {// DRMExtractor sets container metadata kKeyIsDRM to 1return new DRMExtractor(source, originalMime);} else if (!strncmp(mime, "drm+container_based+", 20)) {mime = originalMime;isDrm = true;} else {return NULL;}}bool use_fsl = false;int value;MediaExtractor *ret = NULL;value = property_get_int32("media.fsl_codec.flag", 0);if(value & 0x01)use_fsl = true;if(isDrm)use_fsl = false;if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)|| !strcasecmp(mime, "audio/mp4")) {if(use_fsl)ret = new FslExtractor(source,mime);elseret = new MPEG4Extractor(source);} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) {if(use_fsl)ret = new FslExtractor(source,mime);elseret = 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)) {if(use_fsl)ret = new FslExtractor(source,mime);elseret = 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)) {if(use_fsl)ret = new FslExtractor(source,mime);elseret = new MatroskaExtractor(source);} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {if(use_fsl)ret = new FslExtractor(source,mime);elseret = new MPEG2TSExtractor(source);} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WVM)) {// Return now.  WVExtractor should not have the DrmFlag set in the block below.return new WVMExtractor(source);} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC_ADTS)) {if(use_fsl)ret = new FslExtractor(source,mime);elseret = new AACExtractor(source, meta);} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2PS)) {if(use_fsl)ret = new FslExtractor(source,mime);elseret = new MPEG2PSExtractor(source);} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MIDI)) {ret = new MidiExtractor(source);} else if(!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_AVI)){ret = new FslExtractor(source,mime);} else if(!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_FLV)){ret = new FslExtractor(source,mime);} else if(!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_ASF)){ret = new FslExtractor(source,mime);} else if(!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_RMVB)){ret = new FslExtractor(source,mime);} else if(!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_APE)) {ret = new FslExtractor(source,mime);}if (ret != NULL) {if (isDrm) {ret->setDrmFlag(true);} else {ret->setDrmFlag(false);}}return ret;
}

首先,这里的source是FileSource,FileSource的父类是DataSource,在setDataSource的过程中,NuPlayer::setDataSourceAsync函数会创建一个GenericSource,如下所示:

NuPlayer::setDataSourceAsync(int fd, int64_t offset, int64_t length) {sp<AMessage> msg = new AMessage(kWhatSetDataSource, this);sp<AMessage> notify = new AMessage(kWhatSourceNotify, this);sp<GenericSource> source =new GenericSource(notify, mUIDValid, mUID);status_t err = source->setDataSource(fd, offset, length);if (err != OK) {ALOGE("Failed to set data source!");source = NULL;}

而在这个类的构造函数中,有一个很重要的点:DataSource::RegisterDefaultSniffers();(GenericSource.cpp)

void DataSource::RegisterDefaultSniffers() {Mutex::Autolock autoLock(gSnifferMutex);if (gSniffersRegistered) {return;}RegisterSniffer_l(SniffMPEG4);RegisterSniffer_l(SniffMatroska);RegisterSniffer_l(SniffOgg);RegisterSniffer_l(SniffWAV);RegisterSniffer_l(SniffFLAC);RegisterSniffer_l(SniffAMR);RegisterSniffer_l(SniffMPEG2TS);RegisterSniffer_l(SniffMP3);RegisterSniffer_l(SniffAAC);RegisterSniffer_l(SniffMPEG2PS);RegisterSniffer_l(SniffWVM);RegisterSniffer_l(SniffMidi);RegisterSniffer_l(SniffFSL);char value[PROPERTY_VALUE_MAX];if (property_get("drm.service.enabled", value, NULL)&& (!strcmp(value, "1") || !strcasecmp(value, "true"))) {RegisterSniffer_l(SniffDRM);}gSniffersRegistered = true;
}

通过这个函数,注册了很多探测器函数,这些函数用来探测文件的类型 (探测器函数究竟是如何探测出文件类型的,一般有三种方法:1) 读取文件名的后缀;2) 解析文件头;3) 读取一小段数据,解析这段数据)。

继续回到MediaExtractor::Create函数中,它调用了source->sniff函数,这个函数如下所示:

bool DataSource::sniff(String8 *mimeType, float *confidence, sp<AMessage> *meta) {*mimeType = "";*confidence = 0.0f;meta->clear();{Mutex::Autolock autoLock(gSnifferMutex);if (!gSniffersRegistered) {return false;}}ALOGV("******* In DataSource::sniff function. ************");for (List<SnifferFunc>::iterator it = gSniffers.begin();it != gSniffers.end(); ++it) {String8 newMimeType;float newConfidence;sp<AMessage> newMeta;if ((*it)(this, &newMimeType, &newConfidence, &newMeta)) {if (newConfidence > *confidence) {*mimeType = newMimeType;*confidence = newConfidence;*meta = newMeta;}}}ALOGV("******* At end of DataSource::sniff function.  ***********");return *confidence > 0.0;
}

从DataSource::RegisterDefaultSniffers()中注册过的sniff中,遍历执行,理论上来说,在执行到SniffMPEG4函数时,应该能够检测出来文件类型,因为这个文件就是mp4格式的。(具体内部是如何检测的,以后添加进去,同时还可以把那个检测错误的例子添加进去。)

继续回到MediaExtractor::Create函数中,从sniff函数中出来,意味着检测出媒体类型的,然后打印出:

MediaExtractor: Autodetected media content as 'video/mp4' with confidence 0.40

然后,会根据媒体类型来选取对应的Extractor:

if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)|| !strcasecmp(mime, "audio/mp4")) {if(use_fsl)ret = new FslExtractor(source,mime);elseret = new MPEG4Extractor(source);}

对于普通的平台,mp4格式的文件,就会使用MPEG4Extractor,而对于FSL平台,会提供有FSLExtractor来使用,这些Extractor以lib库的形式存在于Android系统中,这些lib库对于解析数据有硬件加速作用,但是源码是不公开的。

所以对于普通的文件,都会有加速的lib库来使用,那么就创建了FslExtractor来使用,进入FslExtractor的构造函数中(FslExtractor.cpp):

FslExtractor::FslExtractor(const sp<DataSource> &source,const char *mime): mDataSource(source),mReader(new FslDataSourceReader(mDataSource)),mMime(strdup(mime)),bInit(false),mFileMetaData(new MetaData)
{memset(&mLibName,0,255);mLibHandle = NULL;IParser = NULL;parserHandle = NULL;mFileMetaData->setCString(kKeyMIMEType, mime);currentVideoTs = 0;currentAudioTs = 0;mVideoActived = false;ALOGV("FslExtractor::FslExtractor mime=%s",mMime);ALOGD("FslExtractor::FslExtractor mime=%s",mMime);
}

创建了一个FslDataSourceReader来读取文件。

至此,MediaExtractor::Create函数分析完毕。

(二)FslExtractor类的解读:

class FslExtractor : public MediaExtractor
class MediaExtractor : public RefBase
class MediaExtractor : public RefBase {
public:static sp<MediaExtractor> Create(const sp<DataSource> &source, const char *mime = NULL);virtual size_t countTracks() = 0;virtual sp<MediaSource> getTrack(size_t index) = 0;enum GetTrackMetaDataFlags {kIncludeExtensiveMetaData = 1};virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags = 0) = 0;// Return container specific meta-data. The default implementation// returns an empty metadata object.virtual sp<MetaData> getMetaData();enum Flags {CAN_SEEK_BACKWARD  = 1,  // the "seek 10secs back button"CAN_SEEK_FORWARD   = 2,  // the "seek 10secs forward button"CAN_PAUSE          = 4,CAN_SEEK           = 8,  // the "seek bar"};// If subclasses do _not_ override this, the default is// CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD | CAN_SEEK | CAN_PAUSEvirtual uint32_t flags() const;// for DRMvoid setDrmFlag(bool flag) {mIsDrm = flag;};bool getDrmFlag() {return mIsDrm;}virtual char* getDrmTrackInfo(size_t trackID, int *len) {return NULL;}virtual void setUID(uid_t uid) {}protected:MediaExtractor() : mIsDrm(false) {}virtual ~MediaExtractor() {}private:bool mIsDrm;MediaExtractor(const MediaExtractor &);MediaExtractor &operator=(const MediaExtractor &);
};

这个类是个基类,定义了一些简单的函数。

下面是FslExtractor类的实现:

class FslExtractor : public MediaExtractor {
public:// Extractor assumes ownership of "source".FslExtractor(const sp<DataSource> &source,const char *mime);//构造函数virtual size_t countTracks();  //统计媒体文件中的Track数目virtual sp<MediaSource> getTrack(size_t index);  //获取Trackvirtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);//获取track元数据virtual sp<MetaData> getMetaData();virtual uint32_t flags() const;status_t Init();  //初始化函数status_t ActiveTrack(uint32 index);status_t DisableTrack(uint32 index);status_t HandleSeekOperation(uint32_t index,int64_t * ts, uint32_t flag);status_t GetNextSample(uint32_t index,bool is_sync);status_t CheckInterleaveEos(uint32_t index);status_t ClearTrackSource(uint32_t index);protected:virtual ~FslExtractor();private:sp<DataSource> mDataSource;FslDataSourceReader *mReader;char *mMime;bool bInit;char mLibName[255];void *mLibHandle;FslParserInterface * IParser;  //FslParserInterface类中包含了一个lib库向外提供的函数接口FslFileStream fileOps;//对于文件的操作函数集合ParserMemoryOps memOps;//对于内存的操作函数集合ParserOutputBufferOps outputBufferOps;//对于outputbuffer的操作函数集合uint32_t mReadMode;uint32_t mNumTracks;bool bSeekable;uint64_t mMovieDuration;struct TrackInfo {uint32_t mTrackNum;sp<FslMediaSource> mSource;sp<MetaData> mMeta;const FslExtractor *mExtractor;bool bCodecInfoSent;bool bPartial;sp<ABuffer> buffer;int64_t outTs = 0;int32_t syncFrame = 0;uint32_t max_input_size;uint32_t type;bool bIsNeedConvert;};Vector<TrackInfo> mTracks;sp<MetaData> mFileMetaData;FslParserHandle  parserHandle;Mutex mLock;int64_t currentVideoTs;int64_t currentAudioTs;bool mVideoActived;bool isLiveStreaming() const;status_t GetLibraryName();status_t CreateParserInterface();status_t ParseFromParser();status_t ParseMetaData();status_t ParseMediaFormat();status_t ParseVideo(uint32 index, uint32 type,uint32 subtype);status_t ParseAudio(uint32 index, uint32 type,uint32 subtype);status_t ParseText(uint32 index, uint32 type,uint32 subtype);int bytesForSize(size_t size);void storeSize(uint8_t *data, size_t &idx, size_t size);void addESDSFromCodecPrivate(const sp<MetaData> &meta,bool isAudio, const void *priv, size_t privSize);status_t addVorbisCodecInfo(const sp<MetaData> &meta,const void *_codecPrivate, size_t codecPrivateSize);bool isTrackModeParser();status_t convertPCMData(sp<ABuffer> inBuffer, sp<ABuffer> outBuffer, int32_t bitPerSample);FslExtractor(const FslExtractor &);FslExtractor &operator=(const FslExtractor &);
};

(三)解析文件流程

在NuPlayer::GenericSource::initFromDataSource()函数中,创建好Extractor后,随后就调用mFileMeta = extractor->getMetaData();来解析文件,来看看其内部实现(FslExtractor.cpp):

sp<MetaData> FslExtractor::getMetaData()
{if(!bInit){status_t ret = OK;ret = Init();if(ret != OK)return NULL;}return mFileMetaData;
}

跳转到Init函数中去执行了:

status_t FslExtractor::Init()
{status_t ret = OK;if(mReader == NULL)return UNKNOWN_ERROR;ALOGD("FslExtractor::Init BEGIN");memset (&fileOps, 0, sizeof(FslFileStream));fileOps.Open = appFileOpen;fileOps.Read= appReadFile;fileOps.Seek = appSeekFile;fileOps.Tell = appGetCurrentFilePos;fileOps.Size= appFileSize;fileOps.Close = appFileClose;fileOps.CheckAvailableBytes = appCheckAvailableBytes;fileOps.GetFlag = appGetFlag;memset (&memOps, 0, sizeof(ParserMemoryOps));memOps.Calloc = appCalloc;memOps.Malloc = appMalloc;memOps.Free= appFree;memOps.ReAlloc= appReAlloc;outputBufferOps.RequestBuffer = appRequestBuffer;outputBufferOps.ReleaseBuffer = appReleaseBuffer;ret = CreateParserInterface();if(ret != OK){ALOGE("FslExtractor create parser failed");return ret;}ret = ParseFromParser();ALOGD("FslExtractor::Init ret=%d",ret);if(ret == OK)bInit = true;return ret;
}

这个Init函数中,初始化了fileOps,memOps和outputBufferOps,分别为它们初始化了对应的函数指针,然后就进入CreateParserInterface函数了,这个函数如下所示:

status_t FslExtractor::CreateParserInterface()
{status_t ret = OK;int32 err = PARSER_SUCCESS;tFslParserQueryInterface  myQueryInterface;ret = GetLibraryName();if(ret != OK)return ret;do{mLibHandle = dlopen(mLibName, RTLD_NOW);if (mLibHandle == NULL){ret = UNKNOWN_ERROR;break;}ALOGD("load parser name %s",mLibName);myQueryInterface = (tFslParserQueryInterface)dlsym(mLibHandle, "FslParserQueryInterface");if(myQueryInterface == NULL){ret = UNKNOWN_ERROR;break;}IParser = new FslParserInterface;if(IParser == NULL){ret = UNKNOWN_ERROR;break;}err = myQueryInterface(PARSER_API_GET_VERSION_INFO, (void **)&IParser->getVersionInfo);if(err)break;if(!IParser->getVersionInfo){err = PARSER_ERR_INVALID_API;break;}// create & deleteerr = myQueryInterface(PARSER_API_CREATE_PARSER, (void **)&IParser->createParser);if(err)break;if(!IParser->createParser){err = PARSER_ERR_INVALID_API;break;}}

这个函数中重要的部分我都用红色标出了,首先通过GetLibraryName函数获取到所需的lib库的名字,对于MP4格式的文件,所需的lib库的名字就为lib_mp4_parser_arm11_elinux.3.0.so,

status_t FslExtractor::GetLibraryName()
{const char * name = NULL;for (size_t i = 0; i < sizeof(mime_table) / sizeof(mime_table[0]); i++) {if (!strcmp((const char *)mMime, mime_table[i].mime)) {name = mime_table[i].name;break;}}if(name == NULL)return NAME_NOT_FOUND;strcpy(mLibName, "lib_");strcat(mLibName,name);strcat(mLibName,"_parser_arm11_elinux.3.0.so");ALOGD("GetLibraryName %s",mLibName);return OK;
}

然后使用dlopen函数来打开动态库,把Handler保存在mLibHandle中,这个mLibHandle就交给dlsym系统调用来使用了。有关dlopen和dlsym调用的知识,可以查看:《ShareLibarayMgr.cpp的解析》

然后tFslParserQueryInterface是一个函数指针:

typedef int32 (*tFslParserQueryInterface)(uint32 id, void ** func);

首先声明一个tFslParserQueryInterface类型的变量myQueryInterface,这样的话,myQueryInterface也就是一个函数指针了,然后通过强制类型转换,把dlsym(mLibHandle, "FslParserQueryInterface")也转换成一个tFslParserQueryInterface类型的函数指针,最终通过myQueryInterface(PARSER_API_GET_VERSION_INFO, (void **)&IParser->getVersionInfo);就把一个enum值与lib提供出来的接口函数统一起来。从而每个lib提供出来的函数接口就保存在FslParserInterface * IParser中了,以后如果想要使用lib库提供的函数,就只需要使用IParser中的函数接口就行,如IParser->getVersionInfo(), IParser->createParser2()等等。

下面继续回到FslExtractor::Init()函数中去,下一个调用的函数就是ParseFromParser();

status_t FslExtractor::ParseFromParser()
{int32 err = (int32)PARSER_SUCCESS;uint32 flag = FLAG_H264_NO_CONVERT | FLAG_OUTPUT_PTS | FLAG_ID3_FORMAT_NON_UTF8;uint32 trackCnt = 0;bool bLive = mReader->isLiveStreaming();ALOGI("Core parser %s \n", IParser->getVersionInfo());if(IParser->createParser2){if(bLive){flag |= FILE_FLAG_NON_SEEKABLE;flag |= FILE_FLAG_READ_IN_SEQUENCE;}err = IParser->createParser2(flag,&fileOps,&memOps,&outputBufferOps,(void *)mReader,&parserHandle);ALOGD("createParser2 flag=%x,err=%d\n",flag,err);}else{err = IParser->createParser(bLive,&fileOps,&memOps,&outputBufferOps,(void *)mReader,&parserHandle);ALOGD("createParser flag=%x,err=%d\n",flag,err);}if(PARSER_SUCCESS !=  err){ALOGE("fail to create the parser: %d\n", err);return UNKNOWN_ERROR;}if(mReader->isStreaming() || !strcasecmp(mMime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)|| !strcasecmp(mMime, MEDIA_MIMETYPE_CONTAINER_MPEG2PS))mReadMode = PARSER_READ_MODE_FILE_BASED;elsemReadMode = PARSER_READ_MODE_TRACK_BASED;err = IParser->setReadMode(parserHandle, mReadMode);if(PARSER_SUCCESS != err){ALOGW("fail to set read mode to track mode\n");mReadMode = PARSER_READ_MODE_FILE_BASED;err = IParser->setReadMode(parserHandle, mReadMode);if(PARSER_SUCCESS != err){ALOGE("fail to set read mode to file mode\n");return UNKNOWN_ERROR;}}if ((NULL == IParser->getNextSample && PARSER_READ_MODE_TRACK_BASED == mReadMode)|| (NULL == IParser->getFileNextSample && PARSER_READ_MODE_FILE_BASED == mReadMode)){ALOGE("get next sample did not exist");return UNKNOWN_ERROR;}err = IParser->getNumTracks(parserHandle, &trackCnt);if(err)return UNKNOWN_ERROR;mNumTracks = trackCnt;if(IParser->initializeIndex){err = IParser->initializeIndex(parserHandle);}ALOGI("mReadMode=%d,mNumTracks=%u",mReadMode,mNumTracks);err = IParser->isSeekable(parserHandle,(bool *)&bSeekable);if(err)return UNKNOWN_ERROR;ALOGI("bSeekable %d", bSeekable);err = IParser->getMovieDuration(parserHandle, (uint64 *)&mMovieDuration);if(err)return UNKNOWN_ERROR;err = ParseMetaData();if(err)return UNKNOWN_ERROR;err = ParseMediaFormat();if(err)return UNKNOWN_ERROR;return OK;
}

首先通过IParser->getVersionInfo()获取当前使用的lib库的版本号并打印出来,然后通过IParser->createParser2来创建Parser,然后设置ReadMode,对于实时直播视频等等,它的ReadMode是PARSER_READ_MODE_FILE_BASED,对于普通文件,ReadMode是PARSER_READ_MODE_TRACK_BASED,然后把ReadMode通过IParser->setReadMode函数设置到lib库里面,这些都是lib库需要的一些参数,具体的实现在其内部。

之后判断IParser->getNextSample是否存在,IParser->getNextSample对应PARSER_READ_MODE_TRACK_BASED, 而IParser->getFileNextSample 对应PARSER_READ_MODE_FILE_BASED。这两个函数都是必须提供好的,如果没有这两个函数的话,就无法继续运行了。

然后就是通过IParser->getNumTracks来获取这个媒体文件的track数目,并通过IParser->isSeekable来查询文件是否可以执行seek操作。

然后通过ParseMetaData函数,来获取一些medatada数据,metadata数据包括title,artist,album等等,在系统中显示的这些信息就是从这里获取到的。

之后就是ParseMediaFormat函数了,在这个函数内部会去使用IParser->getNumPrograms, IParser->getProgramTracks,IParser->getTrackType等等操作,获取文件中的track内容和信息。然后根据解析出来的TrackTypes执行ParseVideo,ParseAudio和ParseText函数。

在FslExtractor::ParseVideo函数内部,分别依次调用IParser->getTrackDuration,IParser->getDecoderSpecificInfo,IParser->getBitRate,IParser->getVideoFrameWidth,IParser->getVideoFrameHeight,IParser->getVideoFrameRate等等lib库里面的函数来获取这些信息,然后打印出:

FslExtractor: ParseVideo width=1024,height=768,fps=20,rotate=0

同时呢,解析出来这些数据后,当然是把他们放在metadata数据里面比较合适啊,于是通过meta->setData把这些数据都放到metadata中。

同时,在FslExtractor中有个Vector<TrackInfo> mTracks,这里面保存着这些Track的信息,先声明一个TrackInfo结构体,然后将里面的成员变量一一赋值,最后添加到Vector中,最后打印出这句话:

FslExtractor: add video track index=0,source index=0,mime=video/avc

至此,ParseFromParser()函数算是分析完了,我们继续回到FslExtractor::Init()函数中,打印出这句话:FslExtractor: FslExtractor::Init ret=0

10. Android MultiMedia框架完全解析 - MediaExtractor::Create函数的解析和FslExtractor分析相关推荐

  1. 8. Android MultiMedia框架完全解析 - prepareAsync的过程分析

    还是从mediaplayer.cpp文件开始分析: status_t MediaPlayer::prepareAsync() {ALOGV("prepareAsync");Mute ...

  2. Android MultiMedia框架完全解析 - 概览

    之前的工作中,一直在看Android MultiMedia的一些东西,关注我博客的同学也许知道我换工作了,以后将要从事Camera相关的工作,于是乎,将之前整理存放在有道云笔记里面的一些东西发出来,整 ...

  3. Android开源框架PowerfulViewLibrary——PowerfulEditText的介绍和源码解析

    本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 转载请注明出处:http://blog.csdn.net/chay_chan/article/details/63685905 An ...

  4. android 日志框架封装,FLog: 一个基于函数组合的Android日志框架,拥有极简的结构和极高的灵活性、扩展性...

    FLog 一个基于函数组合的Android日志框架,拥有极简的结构和极高的灵活性.扩展性 下载 在根目录下的build.gradle中添加jitpack.io的maven地址 allprojects ...

  5. CImageList类Create函数参数解析

    前面提到了CImageList类的Create(...)函数,虽然MSDN上已经有所解释,但仍有网友问到参数的具体含义,下面就我的理解,对参数进行一次轻量级的剖析 函数原型(其他重载函数请参看msdn ...

  6. Android Multimedia框架总结(二十四)MediaMuxer实现手机屏幕录制成gif图

    原址:http://blog.csdn.net/hejjunlin/article/details/53866405 前言:上篇中,介绍是用MediaMuxer与MediaExtractor进入音视频 ...

  7. Android Multimedia框架总结(十七)音频开发基础知识

    原文链接:http://blog.csdn.net/hejjunlin/article/details/53078828 近年来,唱吧,全民K歌,QQ音乐,等成为音频软件的主流力量,音频开发一直是多媒 ...

  8. Android Multimedia框架总结(二十八)NuPlayer到OMX过程

    原址 NuPlayer是谷歌新研发的.  AwesomePlayer存在BUG,谷歌早已在android m 版本中弃用. sp<MediaPlayerBase> MediaPlayerS ...

  9. Android Binder框架实现之bindService详解

        Android Binder框架实现之bindService详解 Android Binder框架实现目录: Android Binder框架实现之Binder的设计思想 Android Bi ...

最新文章

  1. 计算机与维修专业学校,计算机应用与维修学校录取分专业可靠
  2. 4次优化,我把 Redis 性能 “压榨” 到极致!
  3. 科研人专属微信红包封面免费送!速领
  4. AgileEAS.NET平台开发指南-实现插件
  5. linux shell脚本字符串连接符,学习Linux shell脚本中连接字符串的方法
  6. 开发日记-20190609 关键词 记录一次失败,感悟,畅想未来
  7. c语言去尾法和进一法的例子,《去尾法与进一法》教学案例与反思
  8. 机器学习--机器学习的分类
  9. PHP好玩的代码一(笛卡尔的情书)
  10. python mongodb 设置密码前一篇ok,csv文件存入mongodb
  11. 48. action 与 filter 的执行流程
  12. WinForm:API
  13. 大众点评运维架构详大揭秘!
  14. mysql 5.7 存储引擎_mysql5.7——innodb存储引擎总结-阿里云开发者社区
  15. c语言 最大子段和,C语言程序设计100例之(13):最大子段和
  16. 吊炸天!74款APP完整源码!
  17. 【一】机器学习在网络空间安全研究中的应用
  18. 两台android相互ADB实现一台安卓手机给另一台安卓手机ADB
  19. 工业互联网与物联网的区别
  20. (离散)令R={m|m=a+b√2,a,b∈Q,+为普通加法},定义映射g:R→  R 为g(a+b√2)= a-b√2,试证:g是/R,+/到/R,+/的自同构映射

热门文章

  1. 双系统ubuntu无法访问windows磁盘|| Error mounting /dev/sda2 at /media/...:Command-line 'mount -t ntfs -o
  2. HttpPrinter 易桥打印中间件 web打印插件使用说明
  3. 计算机 无法开机 拔电源 过一会,电脑有时候开机不到一秒就自动关机,拔掉电源再重插就没问题,怎么回事...
  4. 材料力学求解器-刚架与桁架杆系的计算机求解(附matlab代码)
  5. selenium实例
  6. 华为无线路由器信道怎么测试软件,家里人太多网速抢不赢?华为AX3Pro路由上手评测...
  7. 电脑装了python后变卡_老电脑windows系统越用越卡的6种解决方法
  8. 适用于大中型银行的云原生网络体系建设方案攻略
  9. 电脑声道,如何查看电脑声卡是几声道的?
  10. Windows Server 活动目录功能