精华内容
下载资源
问答
  • 媒体提取器 注入时将从进程的内存中提取大多数*媒体(PNG,JPG,ZIP等)的DLL。 这是我在2012年3月进行的一个项目。某些事情可能无法完全发挥作用,部分代码可能很难看,并且您可能不应该将代码用作编程的参考。
  • Extractor

    Extractor

    Extractor在multimedia框架扮演着解析器的角色,用于解析文件的封装。extractor会把视频文件解析成音频流和视频流,把音频文件解析成音频流。在这里,我只针播放音频文件这一场景进行总结。

    类说明:

    1.FileSource:根据构造时传递的文件描述符或者文件路径对多媒体文件进行读取等操作。
    2.RemoteDataSource:binder server。在加载解析器的过程中,FileSource被保存位RemoteDataSource的本地对象,供代理进行文件读取的操作。所以,通过RemoteDataSource这个binder服务,extractor可以跨进程访问文件数据。
    3.CallbackDataSource:顾名思义就是用于回调DataSource,在加载extractor过程中,它保存了RemoteDataSource的远程对象在自己的私有变量sp mIDataSource中,通过mIDataSource,CallbackDataSource跨进程操作FileSource,从而访问多媒体文件。
    4.TinyCacheSource:从字面上理解就是:小型缓冲数据源。它保存了CallbackDataSource的对象,通过CallbackDataSource访问多媒体文件。
    5.MediaExtractorFactory:工具类,里面都是static函数。用于加载extractor组件,创建idatasource等
    6.MediaExtractor:extractor组件需继承它。在8.0版本中,它是一个binder server;而在9.0的版本中,它只是一个类而已,通过其他binder服务来调用它,间接binder化。通过它可以获得解析器的音频轨或者视频轨的信息。
    7.RemoteMediaExtractor:binder server。RemoteMediaExtractor跟RemoteDataSource的作用是相似的,它也是将MediaExtractor保存为私有变量中,它的binder client通过它可以访问到MediaExtractor,间接解析多媒体文件。
    8.NuMediaExtractor:在NuMediaExtractor中,会去保存RemoteMediaExtractor的远程代理对象,也就是binder client。它其实就是针对MediaExtractor的再一次封装,比如它在调用RemoteMediaExtractor获取文件格式的时候,获取得到的是MetaData,然后它会对MetaData进行处理,转化成AMessage,然后再送给调用者。这个类一般是调用SoundPool解析音频文件的时候才会用到的。使用MediaPlayer播放音频文件是不会走这个类的。
    9.ExtractorPlugin:解析器组件。每一个extractor so都对应一个组件,这个组件会保存so库的句柄,路径及库中定义的解析器信息。

    总bouml时序图

    下面这张时序图展现了开机时,SoundPool解析ogg等多媒体文件时加载解析器的过程。SoundPool跟mediaplayer一样都是用来播放音频的接口,它一般用于解析和播放短小的音频,例如按键音,警告音等。
    在这里插入图片描述

    上面这个时序图包含以下几个过程:
    1.创建FileSource,然后为它创建binder server:RemoteDataSource,目的是为了提供IDataSource接口,跨进程访问FileSource。
    2.创建TinyCacheSource,封装IDataSource接口,供media.extractor进程(pe -e | grep media可以看到这个进程)使用。
    3.更新Extractors,打开system/lib/extractor下的所有extractor,并保存到MediaExtractorFactory的gPlugins成员中。
    4.将之前创建的TinyCacheSource依次传递给每一个extractor,调用每一个extractor的sniff函数,如果extractor可以解析此文件,则会设置标志,之后就是使用这个extractor来解析这个文件。
    5.创建RemoteMediaExtractor,封装IMediaExtractor接口,以便解码时使用。

    分析DataSource

    之所以分析DataSource,是因为有的开源解码器需要根据文件路径名来进行初始化操作,这就需要分析DataSource的相关代码,了解到如何获取文件路径。在soundpool和mediaplayer设置数据源setDataSorce的时候会创建FileSource,它的构造函数如下,可见,在构造的时候,FileSource就已经根据传递进来的文件描述符fd获取到文件的路径名。并且,它提供了toString接口去获得文件路径名。

    FileSource::FileSource(const char *filename){
        mFd = open(filename, O_LARGEFILE | O_RDONLY);
        mName = String8::format( "FileSource(fd(%s), %lld, %lld)", nameForFd(fd).c_str(),(long long) mOffset,(long long) mLength);
    }
    class FileSource : public DataSource {
        virtual String8 toString() { return mName; }
     }
    

    既然通过FileSource就可以获取到文件路径名,那么我们只需要分析FileSource的相关框架就行了。下图是FileSOurce的相关类图:
    在这里插入图片描述
    在上面的分析和bouml中,我们知道了RemoteDataSource这一侧属于binder server。它的代码片如下:

    class RemoteDataSource : public BnDataSource {
        virtual String8 toString()  {
            return mName;
        }
        explicit RemoteDataSource(const sp<DataSource> &source) {
            mSource = source;
            mName = String8::format("RemoteDataSource(%s)", mSource->toString().string());
        }
    }
    

    可以看出它提供了toString接口,返回了一个String8类型的mName,这个mName在构造的时候已经赋值了。因为这个参数source是FileSource,所以调用的是FileSource的toString。可见binder server这一侧已经保存了文件路径名,并且提供了binder接口toString。这样只要使用binder 接口IDataSource的toString()就可以获取到文件的路径名。
    从DataSource的类图关系中可以知道:CallbackDataSource拥有IDataSource binder接口;TinyCacheSource的mSource就是CallbackDataSource,并且TinyCacheSource的toString()就是调用mSource的toString()。所以,只要调用TinyCacheSource的toString就可以获得FileSource保存的文件路径名。
    现在来看下上面的时序图。new TinyCacheSource 创建出来的对象作为localSource参数传递给了MediaExtractorFactory::CreateFromService,然后经过一系列内部调用在调用(*it)->def.sniff的时候把刚才传递进来的localSource作为参数传递进去。这里的sniff就是每一个extractor的静态函数:

    static MediaExtractor::CreatorFunc Sniff( DataSourceBase *source, float *confidence, void **meta,MediaExtractor::FreeMetaFunc *freeMeta)
    

    根据上面的分析及类图关系,要想获得文件路径名,可以这样做:

    static MediaExtractor::CreatorFunc Sniff( DataSourceBase *source,...){
    	 sp<DataSource> dataSource = static_cast<DataSource*>(source);
    	str = dataSource->toString();
    	ALOGD("file name: %s",str.c_str());
    }
    

    获取的路径名是这样的:

    TinyCacheSource(CallbackDataSource(493->496, RemoteDataSource(FileSource(fd(/storage/emulated/0/Music/music/lunisolar.
    ape), 0, 26460428))))
    

    然后自己用字符串截取函数截取下就ok了。

    更新extractor

    在这里插入图片描述
    在调用MediaExtractorFactory::UpdateExtractors时会去遍历system/lib/extractors/下的所有so库,调用dlopen,对每一个存在GETEXTRACTORDEF符号的库的信息保存到ExtractorPlugin中(当然这里会判断是否已经注册了),最后把这个信息列表保存到gPlugins。

    class MediaExtractorFactory {
    	static std::shared_ptr<List<sp<ExtractorPlugin>>> gPlugins;
    }
    

    创建IMediaExtractor

    在这里插入图片描述
    获取到IMediaExtractor就可以访问binder server RemoteMediaExtractor。开头说过RemoteMediaExtractor里面保存有MediaExtractor,一个MediaExtractor对应一个RemoteMediaExtractor。这里的MediaExtractor是个功能基类,谷歌自带的或者自己实现的解析器都需要继承它。比如系统中的解析器有:MP3Extractor、MPEG2TSExtractor.cpp、AACExtractor.cpp、FLACExtractor.cpp等等。
    从时序图中可以看出,在MediaExtractorFactory::sniff中,会取出gPlugins保存的每一个extractor库,然后调用它们的sniff函数。例如MP3Extractor中的sniff函数会去判断这个数据源是不是属于MP3格式的,如果是则会设置confidence为正数。MediaExtractorFactory::sniff会返回confidence值最大的那个extractor的CreatorFunc。CreatorFunc是每一个extractor 库需要创建的一个static函数,用于构造extractor。

    //MediaExtractorFactory.cpp
    sp<IMediaExtractor> MediaExtractorFactory::CreateFromService(...){
    	MediaExtractor::CreatorFunc creator = NULL;	//sniff返回后的CreatorFunc会保存到这里
    	creator = sniff(source.get(), &confidence, &meta, &freeMeta, plugin);
    	MediaExtractor *ret = creator(source.get(), meta);		//相当于调用具体extractor的CreateExtractor函数,如果是mp3的source,则调用的是下面这个函数
    	return CreateIMediaExtractorFromMediaExtractor(ret, source, plugin);		//创建IMediaExtractor
    }
    //MP3Extractor.cpp
    static MediaExtractor* CreateExtractor(DataSourceBase *source,void *meta) {	
        Mp3Meta *metaData = static_cast<Mp3Meta *>(meta);
        return new MP3Extractor(source, metaData);
    }
    

    CreateIMediaExtractorFromMediaExtractor是InterfaceUtils.cpp里面的函数,我在画时序图的时候为了画面简洁,把它画到MediaExtractorFactory里面去了。这个函数把刚才创建的MediaExtractor,还有属于它的DataSource,ExtractorPlugin都传递给RemoteMediaExtractor,并构造RemoteMediaExtractor。
    因为MediaExtractor仅仅是一个本地类,无法提供跨进程访问接口,所以需要一个binder server为它提供接口,这个server就是RemoteMediaExtractor。来看看RemoteMediaExtractor的相关代码

    //RemoteMediaExtractor.cpp
    RemoteMediaExtractor::RemoteMediaExtractor(MediaExtractor *extractor, const sp<DataSource> &source,const sp<RefBase> &plugin)
        :mExtractor(extractor),mSource(source),mExtractorPlugin(plugin) {
        ......
     }
     size_t RemoteMediaExtractor::countTracks() {
        return mExtractor->countTracks();
    }
    //RemoteMediaExtractor.h
     class RemoteMediaExtractor : public BnMediaExtractor { // IMediaExtractor wrapper to the MediaExtractor.
     }
    

    可以看出RemoteMediaExtractor就是MediaExtractor的wrapper(封装),只要获得IMediaExtractor,就可以跨进程访问MediaExtractor,比如MP3Extractor。最后就是调用registerMediaExtractor把IMediaExtractor保存到ExtractorInstance里面,方便后面使用。

    展开全文
  • Android-MediaExtractor详解

    千次阅读 2019-07-31 20:47:01
    // 通过binder的方式获取"media.extractor"服务,然后创建extractor } return NULL; } sp<IMediaExtractor> MediaExtractorFactory::CreateFromService( const sp<DataSource> &source, const char *mime) { ...

    前言:

    视频播放过程分为三个部分:

    1. MediaExtractor,即视频解析,主要作用是音视频分离,解析头信息,分别获取音视频流。

    2. MediaCodec,即对音视频流进行解码,获取pcm和yuv数据。详见: https://blog.csdn.net/cheriyou_/article/details/92787998

    3. Render,即分别对音视频进行渲染,此处涉及其他模块,我们重点需要了解的是音视频播放的时间戳对齐过程。详见: https://blog.csdn.net/cheriyou_/article/details/101207443

    本文的主要内容就是介绍nuplayer中MediaExtractor的应用。

     

    MediaExtractor的重点:

    1. mediaextractorservice

    2. libmediaextractor

    3. libstagefright/ MediaExtractorFactory.cpp NuMediaExtractor.cpp

    4.frameworks/av/media/extractors

     

    分析在nuplayer下extractor的应用:

    // 在nuplayer::GenericSource的onPrepareAsyncz中首先会从文件或者链接等方式读取
    // dataSource,然后调用initFromDataSource对dataSource进行音视频分离。
    
    NuPlayer::GenericSource::initFromDataSource(){
        sp<IMediaExtractor> extractor;
        extractor = MediaExtractorFactory::Create(dataSource, NULL)  //在此函数中会判断
    //"media.stagefright.extractremote"是否为true,若是,就会用binder获取"media.extractor"服务,
    // 然后创建extractor,否则用本地的extractor。
    
        sp<MetaData> fileMeta = extractor->getMetaData();// 获取metadata
        size_t numtracks = extractor->countTracks(); // 获取track数
        mFileMeta = fileMeta;
        for (size_t i = 0; i < numtracks; ++i) { // for循环获取每个truck的信息
            sp<IMediaSource> track = extractor->getTrack(i);
            sp<MetaData> meta = extractor->getTrackMetaData(i); // 获取当前trunck的metadata
    
            if (!strncasecmp(mime, "video/", 6)) { // 如果当前track是视频trunck
                if (mVideoTrack.mSource == NULL) {
                    mVideoTrack.mIndex = i;
                    mVideoTrack.mSource = track;
                    mVideoTrack.mPackets =
                    new AnotherPacketSource(mVideoTrack.mSource->getFormat());
    
                    // video always at the beginning
                    mMimes.insertAt(String8(mime), 0);
    
                }
            }
            else // 如果是audio也做类似的处理
        }
        mSources.push(track); // 把解析出来的track流push到mSources中。后续要对哪个流进行操作就会从mSources中读取这个流。
        }
    }
    
    sp<IMediaExtractor> MediaExtractorFactory::Create(
            const sp<DataSource> &source, const char *mime) {
    
        if (!property_get_bool("media.stagefright.extractremote", true)) {
            // local extractor
            return CreateFromService(source, mime);
        } else {
            // 通过binder的方式获取"media.extractor"服务,然后创建extractor
        }
        return NULL;
    }
    
    
    sp<IMediaExtractor> MediaExtractorFactory::CreateFromService(
            const sp<DataSource> &source, const char *mime) {
    
        ......
        void *meta = nullptr;
        MediaExtractor::CreatorFunc creator = NULL;
        MediaExtractor::FreeMetaFunc freeMeta = nullptr;
        float confidence;
        sp<ExtractorPlugin> plugin;
        creator = sniff(source.get(), &confidence, &meta, &freeMeta, plugin);
    
        MediaExtractor *ret = creator(source.get(), meta);
    
        return CreateIMediaExtractorFromMediaExtractor(ret, source, plugin);
    }
    
    MediaExtractor::CreatorFunc MediaExtractorFactory::sniff(
            DataSourceBase *source, float *confidence, void **meta,
            MediaExtractor::FreeMetaFunc *freeMeta, sp<ExtractorPlugin> &plugin) { // 此函数的大概作用就是从所有的extractor里面选出最合适的一个.
        *confidence = 0.0f;
        *meta = nullptr;
    
        std::shared_ptr<List<sp<ExtractorPlugin>>> plugins;
        {
            Mutex::Autolock autoLock(gPluginMutex);
            if (!gPluginsRegistered) {
                return NULL;
            }
            plugins = gPlugins;
        }
    
        MediaExtractor::CreatorFunc curCreator = NULL;
        MediaExtractor::CreatorFunc bestCreator = NULL;
        for (auto it = plugins->begin(); it != plugins->end(); ++it) {
            float newConfidence;
            void *newMeta = nullptr;
            MediaExtractor::FreeMetaFunc newFreeMeta = nullptr;
            if ((curCreator = (*it)->def.sniff(source, &newConfidence, &newMeta, &newFreeMeta))) {
                if (newConfidence > *confidence) {
                    *confidence = newConfidence;
                    if (*meta != nullptr && *freeMeta != nullptr) {
                        (*freeMeta)(*meta);
                    }
                    *meta = newMeta;
                    *freeMeta = newFreeMeta;
                    plugin = *it;
                    bestCreator = curCreator;
                } else {
                    if (newMeta != nullptr && newFreeMeta != nullptr) {
                        newFreeMeta(newMeta);
                    }
                }
            }
        }
    
        return bestCreator;
    }
    
    // 例如对于mp4文件,此时就会创建一个MPEG4Extractor。
    
    status_t MPEG4Extractor::getMetaData(MetaDataBase &meta) {
        status_t err;
        if ((err = readMetaData()) != OK) {
            return UNKNOWN_ERROR;
        }
        meta = mFileMetaData;
        return OK;
    }
    
    status_t MPEG4Extractor::readMetaData() {
    
        off64_t offset = 0;
        status_t err;
        bool sawMoovOrSidx = false;
    
        while (!((mHasMoovBox && sawMoovOrSidx && (mMdatFound || mMoofFound)) ||
                 (mIsHeif && (mPreferHeif || !mHasMoovBox) &&
                         (mItemTable != NULL) && mItemTable->isValid()))) {
            off64_t orig_offset = offset;
            err = parseChunk(&offset, 0); // while循环处理文件,解析各种box。
    // parseChunk是一个递归函数。
    
            if (err != OK && err != UNKNOWN_ERROR) {
                break;
            } else if (offset <= orig_offset) {
                err = ERROR_MALFORMED;
                break;
            } else if (err == UNKNOWN_ERROR) {
                sawMoovOrSidx = true;
            }
        }
    
        if (mIsHeif && (mItemTable != NULL) && (mItemTable->countImages() > 0)) {
            // 处理heif文件,省略。
        }
    
        if (mInitCheck == OK) {
            if (findTrackByMimePrefix("video/") != NULL) {
                mFileMetaData.setCString(  // 给mFileMetaData设置类型
                        kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_MPEG4);
            } else if (findTrackByMimePrefix("audio/") != NULL) {
                mFileMetaData.setCString(kKeyMIMEType, "audio/mp4");
            } else if (findTrackByMimePrefix(
                    MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC) != NULL) {
                mFileMetaData.setCString(
                        kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_HEIF);
            } else {
                mFileMetaData.setCString(kKeyMIMEType, "application/octet-stream");
            }
        } else {
            mInitCheck = err;
        }
    
        CHECK_NE(err, (status_t)NO_INIT);
    
        // copy pssh data into file metadata
        uint64_t psshsize = 0;
        for (size_t i = 0; i < mPssh.size(); i++) { // 此处for循环计算mpssh的长度。
            psshsize += 20 + mPssh[i].datalen;
        }
        if (psshsize > 0 && psshsize <= UINT32_MAX) { // 此处把mpssh里面的数据copy给
    // mFileMetaData。其中mpssh是在parseChunk中从文件中解析出来的。
            char *buf = (char*)malloc(psshsize);
            if (!buf) {
                ALOGE("b/28471206");
                return NO_MEMORY;
            }
            char *ptr = buf;
            for (size_t i = 0; i < mPssh.size(); i++) {
                memcpy(ptr, mPssh[i].uuid, 20); // uuid + length
                memcpy(ptr + 20, mPssh[i].data, mPssh[i].datalen);
                ptr += (20 + mPssh[i].datalen);
            }
            mFileMetaData.setData(kKeyPssh, 'pssh', buf, psshsize); // 给mFileMetaData设置data
            free(buf);
        }
    
        return mInitCheck;
    }
    
    size_t MPEG4Extractor::countTracks() { 
        status_t err;
        if ((err = readMetaData()) != OK) {
            ALOGV("MPEG4Extractor::countTracks: no tracks");
            return 0;
        }
    
        size_t n = 0;
        Track *track = mFirstTrack; 
        while (track) {
            ++n;
            track = track->next;
        }
    
        ALOGV("MPEG4Extractor::countTracks: %zu tracks", n);
        return n;
    }
    
    MediaTrack *MPEG4Extractor::getTrack(size_t index) {
        status_t err;
        if ((err = readMetaData()) != OK) {
            return NULL;
        }
    
        Track *track = mFirstTrack; // 初始化track为第一个track,然后循环去找需要的track
        while (index > 0) {
            if (track == NULL) {
                return NULL;
            }
    
            track = track->next;
            --index;
        }
    
        // 然后处理一系列异常情况
    
        MPEG4Source *source =  new MPEG4Source(
                track->meta, mDataSource, track->timescale, track->sampleTable,
                mSidxEntries, trex, mMoofOffset, itemTable);
        if (source->init() != OK) {
            delete source;
            return NULL;
        }
        return source;
    }
    
    status_t MPEG4Extractor::getTrackMetaData(
            MetaDataBase &meta,
            size_t index, uint32_t flags) {
    
        Track *track = mFirstTrack;
        while (index > 0) { //根据index找到对应的track
            if (track == NULL) {
                return UNKNOWN_ERROR;
            }
    
            track = track->next;
            --index;
        }
    
        [=] {
            int64_t duration;
            int32_t samplerate;
            if (track->has_elst && mHeaderTimescale != 0 &&
                    track->meta.findInt64(kKeyDuration, &duration) &&
                    track->meta.findInt32(kKeySampleRate, &samplerate)) {
    
                track->has_elst = false;
    
                if (track->elst_segment_duration > INT64_MAX) {
                    return;
                }
                int64_t segment_duration = track->elst_segment_duration;
                int64_t media_time = track->elst_media_time;
                int64_t halfscale = mHeaderTimescale / 2;
    
                int64_t delay;
                // delay = ((media_time * samplerate) + halfscale) / mHeaderTimescale;
                if (__builtin_mul_overflow(media_time, samplerate, &delay) ||
                        __builtin_add_overflow(delay, halfscale, &delay) ||
                        (delay /= mHeaderTimescale, false) ||
                        delay > INT32_MAX ||
                        delay < INT32_MIN) {
                    return;
                }
                ALOGV("delay = %" PRId64, delay);
                track->meta.setInt32(kKeyEncoderDelay, delay);
            // 此处的delay用于nuplayer的sendMetaDataToHal中,
    // nuplayer发现有delay时会设置audio codec delay的samples。
                int64_t scaled_duration;
                // scaled_duration = duration * mHeaderTimescale;
                if (__builtin_mul_overflow(duration, mHeaderTimescale, &scaled_duration)) {
                    return;
                }
                ALOGV("scaled_duration = %" PRId64, scaled_duration);
    
                int64_t segment_end;
                int64_t padding;
                // padding = scaled_duration - ((segment_duration + media_time) * 1000000);
                if (__builtin_add_overflow(segment_duration, media_time, &segment_end) ||
                        __builtin_mul_overflow(segment_end, 1000000, &segment_end) ||
                        __builtin_sub_overflow(scaled_duration, segment_end, &padding)) {
                    return;
                }
                ALOGV("segment_end = %" PRId64 ", padding = %" PRId64, segment_end, padding);
    
                if (padding < 0) {
                    // track duration from media header (which is what kKeyDuration is) might
                    // be slightly shorter than the segment duration, which would make the
                    // padding negative. Clamp to zero.
                    padding = 0;
                }
    
                int64_t paddingsamples;
                int64_t halfscale_e6;
                int64_t timescale_e6;
                // paddingsamples = ((padding * samplerate) + (halfscale * 1000000))
                //                / (mHeaderTimescale * 1000000);
                if (__builtin_mul_overflow(padding, samplerate, &paddingsamples) ||
                        __builtin_mul_overflow(halfscale, 1000000, &halfscale_e6) ||
                        __builtin_mul_overflow(mHeaderTimescale, 1000000, &timescale_e6) ||
                        __builtin_add_overflow(paddingsamples, halfscale_e6, &paddingsamples) ||
                        (paddingsamples /= timescale_e6, false) ||
                        paddingsamples > INT32_MAX) {
                    return;
                }
                ALOGV("paddingsamples = %" PRId64, paddingsamples);
                track->meta.setInt32(kKeyEncoderPadding, paddingsamples);
            }
        }();
    
        if ((flags & kIncludeExtensiveMetaData)
                && !track->includes_expensive_metadata) {
            track->includes_expensive_metadata = true;
    
            const char *mime;
            CHECK(track->meta.findCString(kKeyMIMEType, &mime));
            if (!strncasecmp("video/", mime, 6)) {
                // MPEG2 tracks do not provide CSD, so read the stream header
                if (!strcmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG2)) {
                    off64_t offset;
                    size_t size;
                    if (track->sampleTable->getMetaDataForSample(
                                0 /* sampleIndex */, &offset, &size, NULL /* sampleTime */) == OK) {
                        if (size > kMaxTrackHeaderSize) {
                            size = kMaxTrackHeaderSize;
                        }
                        uint8_t header[kMaxTrackHeaderSize];
                        if (mDataSource->readAt(offset, &header, size) == (ssize_t)size) {
                            track->meta.setData(kKeyStreamHeader, 'mdat', header, size);
                            // 设置kKeyStreamHeader
                        }
                    }
                }
    
           // 设置kKeyThumbnailTime.
                if (mMoofOffset > 0) {
                    int64_t duration;
                    if (track->meta.findInt64(kKeyDuration, &duration)) {
                        // nothing fancy, just pick a frame near 1/4th of the duration
                        track->meta.setInt64(
                                kKeyThumbnailTime, duration / 4);
                // 此处是设置缩略图的时间为duration的1/4
                    }
                } else {
                    uint32_t sampleIndex;
                    uint32_t sampleTime;
                    if (track->timescale != 0 &&
                            track->sampleTable->findThumbnailSample(&sampleIndex) == OK
                            && track->sampleTable->getMetaDataForSample(
                                sampleIndex, NULL /* offset */, NULL /* size */,
                                &sampleTime) == OK) {
                        track->meta.setInt64(
                                kKeyThumbnailTime,
                                ((int64_t)sampleTime * 1000000) / track->timescale);
                    }
                }
            }
        }
    
        meta = track->meta;
        return OK;
    }
    

     

    展开全文
  • 488 read(14, "media.extractor\0aextractor\0", 127) = 27 488 close(14) = 0 488 --- SIGSYS {si_signo=SIGSYS, si_code=SYS_SECCOMP, si_call_addr=0xb28004fc, si_syscall=__NR_gettimeofday, si_arch=AUDIT_...

    Monkey 测试时出了 ANR , 然后 mediaextractor 进程被 kill 了, 然后大家搞不清是因为 mediaextractor 被杀所以导致 ANR 了,还是 ANR 之后 mediaextractor 被杀 , 于是这个问题终于辗转到征求我的意见了...  // MAGIC1. DO NOT TOUCH.  BY 冗戈微言 http://blog.csdn.net/leonxu_sjtu/


    因为 LMK 是不至于杀 native 进程的, 所以确实有些奇怪; 对于这种莫名被杀的状况, 开了 strace 然后再 monkey 复现, 抓到如下的 syscall 队列: // MAGIC2. DO NOT TOUCH.  BY 冗戈微言 
    展开全文
  • if (mediaFormat.getString(MediaFormat.KEY_MIME).contains("video")) { // Log.d(TAG, "track " + i + " is video track. track info is: " + mediaFormat.toString()); int width = mediaFormat.getInteger...

    Android使用MediaExtractor读取媒体信息


    Refrence

    1. Android 判断字符串是否为url
    2. 用来测试的在线小视频url地址

    Source Code

    public class BottomActivity extends AppCompatActivity {
        
        private static final String TAG = "BGBottomActivity";
        private TextView tvInfo;
        private Button btnOpenFile;
        private BGMediaInfo mbgMediaInfo;
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_bottom);
            
            if (mbgMediaInfo == null) {
                mbgMediaInfo = new BGMediaInfo();
            }
            initComponentView();
        }
        
        private void initComponentView() {
            tvInfo = (TextView) findViewById(R.id.tv_Info);
            EditText etUrl = (EditText) findViewById(R.id.et_Url);
            btnOpenFile = (Button) findViewById(R.id.btn_OpenFile);
            
            etUrl.setOnEditorActionListener((v, actionId, event) -> dealUrl(v, actionId));
            btnOpenFile.setOnClickListener(v -> onBtnClick());
            
        }
        
        private boolean dealUrl(TextView view, int actionId) {
            CharSequence str = view.getText();
            Log.d(TAG, "actionId is: " + actionId + ", content is: " + str);
            
            if (TextUtils.isEmpty(str) || !isHttpUrl(str.toString())) {
                btnOpenFile.setClickable(true);
                Toast.makeText(this, "请正确输入网址", Toast.LENGTH_SHORT).show();
                return false;
            }
            btnOpenFile.setClickable(false);
            try {
                tvInfo.setText(mbgMediaInfo.deal("https://v-cdn.zjol.com.cn/278587.mp4"));
            } catch (IOException e) {
                e.printStackTrace();
            }
            
            return false;
        }
        
        
        private void onBtnClick() {
            Log.d(TAG, "onBtnClick: ");
            
            // TODO 打开本地媒体文件
        }
        
        
        /**
         * 判断字符串是否为URL
         *
         * @param urls 需要判断的String类型url
         * @return true:是URL;false:不是URL
         */
        private boolean isHttpUrl(String urls) {
            boolean isurl = false;
            
            if (urls.isEmpty() || urls.contains(" ")) {
                return isurl;
            }
            //设置正则表达式
            String regex = "(((https|http)?://)?([a-z0-9\\-]+[.])|(www.))"
                    + "\\w+[.|\\/]([a-z0-9]{0,})?[[.]([a-z0-9]{0,})]+((/[\\S&&[^,;\u4E00-\u9FA5]]+)+)?([.][a-z0-9]{0,}+|/?)";
            // 对比
            Pattern pat = Pattern.compile(regex.trim());
            Matcher mat = pat.matcher(urls.trim());
            // 判断是否匹配
            isurl = mat.matches();
            
            return isurl;
        }
    }
    
    public class BGMediaInfo {
        
        private static final String TAG = "BGMediaInfo";
        private StringBuilder strBuilder;
        private int trackCount = 0;
        private MediaExtractor mediaExtractor;
        
        public BGMediaInfo() {
            if (strBuilder == null) {
                strBuilder = new StringBuilder();
            }
        }
        
        public String deal(String url) throws IOException {
            if (mediaExtractor == null) {
                mediaExtractor = new MediaExtractor();
            }
            strBuilder.delete(0, strBuilder.length());
            mediaExtractor.setDataSource(url);
            trackCount = mediaExtractor.getTrackCount();
            if (trackCount < 1) {
                Log.e(TAG, url + " has no track!");
            }
            for (int i = 0; i < trackCount; i++) {
                MediaFormat mediaFormat = mediaExtractor.getTrackFormat(i);
                if (mediaFormat.getString(MediaFormat.KEY_MIME).contains("video")) {
                    // Log.d(TAG, "track " + i + " is video track. track info is: " + mediaFormat.toString());
                    int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH, -1);
                    int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT, -1);
                    long duration = mediaFormat.getLong(MediaFormat.KEY_DURATION, -1);
                    int frameRate = mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE, -1);
                    strBuilder.append("视频尺寸:" + width + "*" + height + "\n帧率:" + frameRate + "fps\n时长:" + duration / 1000 / 1000 + "秒\n");
                    Log.i(TAG, "视频尺寸:" + width + "*" + height + ",帧率:" + frameRate + "fps,时长:" + duration / 1000 / 1000 + "秒");
                    String mime = mediaFormat.getString(MediaFormat.KEY_MIME, "err");
                    int profile = mediaFormat.getInteger(MediaFormat.KEY_PROFILE, -1);
                    int level = mediaFormat.getInteger(MediaFormat.KEY_LEVEL, -1);
                    int maxInputSize = mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE, -1);
                    strBuilder.append("视频格式为:" + mime + "\n配置:" + profile + "\n配置等级:" + level + "\n最大单帧缓冲区:" + maxInputSize);
                    Log.i(TAG, "视频格式为:" + mime + ",配置:" + profile + ",配置等级:" + level + ",最大单帧缓冲区:" + maxInputSize);
                    
                    
                } else if (mediaFormat.getString(MediaFormat.KEY_MIME).contains("audio")) {
                    // Log.d(TAG, "track " + i + " is audio track. track info is: " + mediaFormat.toString());
                    int samplerate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE, -1);
                    int bitrate = mediaFormat.getInteger(MediaFormat.KEY_BIT_RATE, -1);
                    long duration = mediaFormat.getLong(MediaFormat.KEY_DURATION, -1);
                    strBuilder.append("音频采样率:" + samplerate + "\n比特率:" + bitrate + "\n时长:" + duration / 1000 / 1000 + "秒\n");
                    Log.i(TAG, "音频采样率:" + samplerate + ",比特率:" + bitrate + ",时长:" + duration / 1000 / 1000 + "秒");
                    String mime = mediaFormat.getString(MediaFormat.KEY_MIME, "err");
                    int profile = mediaFormat.getInteger(MediaFormat.KEY_PROFILE, -1);
                    int aacprofile = mediaFormat.getInteger(MediaFormat.KEY_AAC_PROFILE, -1);
                    int level = mediaFormat.getInteger(MediaFormat.KEY_LEVEL, -1);
                    int maxInputSize = mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE, -1);
                    strBuilder.append("音频格式:" + mime + "\n配置:" + profile + "\n配置等级:" + level + "\n最大单帧缓冲区:" + maxInputSize);
                    Log.i(TAG, "音频格式:" + mime + ",配置:" + profile + ",配置等级:" + level + ",最大单帧缓冲区:" + maxInputSize);
                }
                strBuilder.append("\n--------\n");
            }
            strBuilder.append("配置详情请查询 MediaCodecInfo.CodecProfileLevel \n");
            
            mediaExtractor.release();
            mediaExtractor = null;
            
            return strBuilder.toString();
        }
    }
    

    媒体信息

    展开全文
  • ExoPlayer之Extractor

    2020-07-31 10:16:56
    代码中关于Extractor的注释说明 - Extracts media data from a container format. Extractor对应于媒体文件解封装的处理: 解封装的 作用,就是将输入的封装格式的数据,分离成为音频流压缩编码数据和视频流压缩编码...
  • 语言:English 从嵌入式播放器复制媒体源,然后将链接下载到剪贴板。 从嵌入式播放器复制媒体源,并将下载链接下载到剪贴板,以粘贴到您喜欢的独立视频播放器等中。重量轻,可通过域白名单和文件类型进行配置。...
  • Easy CD-DA Extractor是一款优秀的音乐CD抓取、格式转换、光盘刻录软件,满足你对音乐的最常用编辑需求。它提供高效音乐CD抓取功能,能直接抓取防拷贝CD,并将音乐输出为MP3, Windows Media Audio 8 and 9, Ogg ...
  • 通过support@leads-extractor.com与我们联系======= Leads Extractor是一个功能强大的工具,可以以每月公平的价格和无限的访问量来促进您的公司或产品的发展,而无需永久隐藏费用。 提取数据,并使用Anyleads,...
  • Android8.0 Media系统(二)

    千次阅读 2018-08-21 11:46:22
    上一篇从MediaPlayer的创建,设置数据源,播放准备三个流程来看Media系统,今天我们继续来看initFromDataSource()函数,将创建数据提取器,数据提取器将读取数据源文件的元数据信息,将每一路数据流的比特率进行累加...
  • 分离出source中的音频和视频extractor 对音视频数据进行解码decoder 解码后的数据进行渲染render(其中包含音视频同步) 渲染后的数据交给device播放 大致流程就这,建议先熟悉这个流程图再结合代码看,代码是实现...
  • Android 在扫描U盘解析图片缩略图时,拔出U盘导致崩溃 在出现该问题时,发现日志中显示processkiller:....相关信息,然后出现了进程被杀死的日志。 ...xxxxxxxx W/ProcessKiller( 2176): Sending SIG
  • 语言:English 选择一个DOM元素并获取应用于其及其所有子元素的样式。
  • multimedia框架之加载media extractor组件 Extractor概念关键类说明流程图DataSourceUpdateExtractorsIMediaExtractor Extractor概念 Extractor在multimedia框架扮演着解析器的角色,用于解析文件的封装。extractor...
  • Android8.0 Media系统(一)

    千次阅读 2018-08-20 21:18:48
    以上四篇对Audio系统的简要分析,由于Audio涉及...我们继续Media系统的征程,Media系统任然是一个庞大的系统,以MediaPlayer为例,贯穿了Java,JNI,C++库,硬件抽象层,OpenMax驱动,涉及到音视频,编解码等内容。...
  • 安全删除U盘时,大家常常会遇到提示“无法停止‘通用卷’设备,请稍候再停止该设备。” 这种情况下可以强行拔下U盘吗?当然不可以!这时候如果强行拔除的话,很容易损坏计算机U口或者你的U盘。如果你的U盘上有重要的...
  • 3. nuplayer怎么从media.extractor获取对应的extractor 第一步: frameworks/av/media/extractors下每个目录都会生成对应的so,eg: libmp4extractor 、 libaacextractor 、 libmpeg2extractor 等等 第二步: ...
  • #media-query-extractor 通过媒体查询对 css 规则进行分组,并将它们提取到单独的文件中。 警告 使用此工具将更改文件中 css 规则的位置。 因此,如果您依赖属性覆盖,它会弄乱您的样式。 这个工具只能在你掌握你...
  • ++i) { MediaFormat format = extractor.getTrackFormat(i); String mime = format.getString(MediaFormat.KEY_MIME); if (weAreInterestedInThisTrack) { extractor.selectTrack(i); } } ByteBuffer inputBuffer =...
  • 我们在Android N Audio播放三:prepare大揭秘介绍了在prepare的过程中会创建Extractor, Extractor的主要作用是从容器格式中把音频和视频剥离出来,为之后的解码提供音频流和视频流,要知道。音频和视频的解码是分离...
  • 选择一个DOM元素,并将样式应用于它及其所有的子元素。 支持语言:English
  • Easy CD-DA Extractor是一款优秀的音乐CD抓取、格式转换、光盘刻录软件,满足你对音乐的最常用编辑需求。它提供高效音乐CD抓取功能,能直接抓取防拷贝CD,并将音乐输出为MP3, Windows Media Audio 8 and 9, Ogg ...
  • MPEG4Extractor分析

    2019-04-29 17:41:09
    Android Stagefright MPEG4Extractor分析 视频播放的基本流程 播放器从DataSource获取媒体数据,通过Demuxer分离音视频轨道,分别送到相应的音视频解码器,最后将解码后的数据输出到音视频设备。 在Stage...
  • 如果您看到部分媒体意外丢失或重复,请尝试设置Mp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS或 FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS,这将使提取器完全忽略编辑列表。这些文件可以使用...
  • media.player的加载 int main(int argc __unused, char **argv __unused) { signal(SIGPIPE, SIG_IGN); sp<ProcessState> proc(ProcessState::self()); sp<IServiceManager> sm...
  • Easy CD-DA Extractor Free

    2019-03-08 10:40:54
    Easy CD-DA Extractor是一款优秀的音乐CD抓取、格式转换、光盘刻录软件,满足你对音乐的最常用编辑需求。它提供高效音乐CD抓取功能,能直接抓取防拷贝CD,并将音乐输出为MP3, Windows Media Audio 8 and 9, Ogg ...
  • ExoPlayer 的小解析

    千次阅读 2017-06-01 18:05:45
    import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory; import ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,803
精华内容 721
关键字:

media.extractor