MediaCodec 编解码器的加载流程

简介: MediaCodec 编解码器的加载流程

MediaCode 的CreateByComponentName和CreateByType会调用mediacodec的构造方法

// static
sp<MediaCodec> MediaCodec::CreateByType(
        const sp<ALooper> &looper, const AString &mime, bool encoder, status_t *err, pid_t pid,
        uid_t uid) {
    sp<AMessage> format;
    return CreateByType(looper, mime, encoder, err, pid, uid, format);
}
sp<MediaCodec> MediaCodec::CreateByType(
        const sp<ALooper> &looper, const AString &mime, bool encoder, status_t *err, pid_t pid,
        uid_t uid, sp<AMessage> format) {
    Vector<AString> matchingCodecs;
    MediaCodecList::findMatchingCodecs(
            mime.c_str(),
            encoder,
            0,
            format,
            &matchingCodecs);
    if (err != NULL) {
        *err = NAME_NOT_FOUND;
    }
    for (size_t i = 0; i < matchingCodecs.size(); ++i) {
        sp<MediaCodec> codec = new MediaCodec(looper, pid, uid);
        AString componentName = matchingCodecs[i];
        status_t ret = codec->init(componentName);
        if (err != NULL) {
            *err = ret;
        }
        if (ret == OK) {
            return codec;
        }
        ALOGD("Allocating component '%s' failed (%d), try next one.",
                componentName.c_str(), ret);
    }
    return NULL;
}
// static
sp<MediaCodec> MediaCodec::CreateByComponentName(
        const sp<ALooper> &looper, const AString &name, status_t *err, pid_t pid, uid_t uid) {
    sp<MediaCodec> codec = new MediaCodec(looper, pid, uid);
    const status_t ret = codec->init(name);
    if (err != NULL) {
        *err = ret;
    }
    return ret == OK ? codec : NULL; // NULL deallocates codec.
}

 

MediaCodec的构造方法调用了MediaCodecList的getInstance方法

Z:\rb-a3399pb1\frameworks\av\media\libstagefright\MediaCodec.cpp

MediaCodec::MediaCodec(
        const sp<ALooper> &looper, pid_t pid, uid_t uid,
        std::function<sp<CodecBase>(const AString &, const char *)> getCodecBase,
        std::function<status_t(const AString &, sp<MediaCodecInfo> *)> getCodecInfo)
    : mState(UNINITIALIZED),
      mReleasedByResourceManager(false),
      mLooper(looper),
      mCodec(NULL),
      mReplyID(0),
      mFlags(0),
      mStickyError(OK),
      mSoftRenderer(NULL),
      mIsVideo(false),
      mVideoWidth(0),
      mVideoHeight(0),
      mRotationDegrees(0),
      mDequeueInputTimeoutGeneration(0),
      mDequeueInputReplyID(0),
      mDequeueOutputTimeoutGeneration(0),
      mDequeueOutputReplyID(0),
      mTunneledInputWidth(0),
      mTunneledInputHeight(0),
      mTunneled(false),
      mTunnelPeekState(TunnelPeekState::kEnabledNoBuffer),
      mHaveInputSurface(false),
      mHavePendingInputBuffers(false),
      mCpuBoostRequested(false),
      mPlaybackDurationAccumulator(new PlaybackDurationAccumulator()),
      mIsSurfaceToScreen(false),
      mLatencyUnknown(0),
      mBytesEncoded(0),
      mEarliestEncodedPtsUs(INT64_MAX),
      mLatestEncodedPtsUs(INT64_MIN),
      mFramesEncoded(0),
      mNumLowLatencyEnables(0),
      mNumLowLatencyDisables(0),
      mIsLowLatencyModeOn(false),
      mIndexOfFirstFrameWhenLowLatencyOn(-1),
      mInputBufferCounter(0),
      mGetCodecBase(getCodecBase),
      mGetCodecInfo(getCodecInfo) {
    if (uid == kNoUid) {
        mUid = AIBinder_getCallingUid();
    } else {
        mUid = uid;
    }
    mResourceManagerProxy = new ResourceManagerServiceProxy(pid, mUid,
            ::ndk::SharedRefBase::make<ResourceManagerClient>(this));
    if (!mGetCodecBase) {
        mGetCodecBase = [](const AString &name, const char *owner) {
            return GetCodecBase(name, owner);
        };
    }
    if (!mGetCodecInfo) {
        mGetCodecInfo = [](const AString &name, sp<MediaCodecInfo> *info) -> status_t {
            *info = nullptr;
            const sp<IMediaCodecList> mcl = MediaCodecList::getInstance();


1.MediaCodecList.cpp开始讲,前面的流程我也不太懂。

MediaCodecList位于 frameworks\av\media\libstagefright\MediaCodecList.cpp

// static
sp<IMediaCodecList> MediaCodecList::getInstance() {
    Mutex::Autolock _l(sRemoteInitMutex);
    if (sRemoteList == nullptr) {
        sMediaPlayer = defaultServiceManager()->getService(String16("media.player"));获取mediaplayerservice
        sp<IMediaPlayerService> service =
            interface_cast<IMediaPlayerService>(sMediaPlayer);
        if (service.get() != nullptr) {
            sRemoteList = service->getCodecList(); 获取codecList
            if (sRemoteList != nullptr) {
                sBinderDeathObserver = new BinderDeathObserver();
                sMediaPlayer->linkToDeath(sBinderDeathObserver.get()); 保活
            }
        }
        if (sRemoteList == nullptr) {
            // if failed to get remote list, create local list
            sRemoteList = getLocalInstance(); 获取codecList
        }
    }
    return sRemoteList;
}

frameworks\av\media\libmediaplayerservice\MediaPlayerService.cpp 的 getCodecList方法实际上调用的还是MediaCodecList的getLocalInstance方法

sp<IMediaCodecList> MediaPlayerService::getCodecList() const {

   return MediaCodecList::getLocalInstance();

}

// static
sp<IMediaCodecList> MediaCodecList::getLocalInstance() {
    Mutex::Autolock autoLock(sInitMutex);
    if (sCodecList == nullptr) {
        MediaCodecList *codecList = new MediaCodecList(GetBuilders());
        if (codecList->initCheck() == OK) {
            sCodecList = codecList;
            if (isProfilingNeeded()) {
                ALOGV("Codec profiling needed, will be run in separated thread.");
                pthread_t profiler;
                if (pthread_create(&profiler, nullptr, profilerThreadWrapper, nullptr) != 0) {
                    ALOGW("Failed to create thread for codec profiling.");
                }
            }
        } else {
            // failure to initialize may be temporary. retry on next call.
            delete codecList;
        }
    }
    return sCodecList;
}

获取builders

std::unique_ptr<MediaCodecListBuilderBase> sCodec2InfoBuilder;
MediaCodecListBuilderBase *GetCodec2InfoBuilder() {
    Mutex::Autolock _l(sCodec2InfoBuilderMutex);
    if (!sCodec2InfoBuilder) {
        sCodec2InfoBuilder.reset(new Codec2InfoBuilder);
    }
    return sCodec2InfoBuilder.get();
}
std::vector<MediaCodecListBuilderBase *> GetBuilders() {
    std::vector<MediaCodecListBuilderBase *> builders;
    // if plugin provides the input surface, we cannot use OMX video encoders.
    // In this case, rely on plugin to provide list of OMX codecs that are usable.
    sp<PersistentSurface> surfaceTest = CCodec::CreateInputSurface();
    if (surfaceTest == nullptr) {
        ALOGD("Allowing all OMX codecs");
        builders.push_back(&sOmxInfoBuilder);
    } else {
        ALOGD("Allowing only non-surface-encoder OMX codecs");
        builders.push_back(&sOmxNoSurfaceEncoderInfoBuilder);
    }
    builders.push_back(GetCodec2InfoBuilder());
    return builders;
}

构造方法如下:

MediaCodecList::MediaCodecList(std::vector<MediaCodecListBuilderBase*> builders) {
    mGlobalSettings = new AMessage();
    mCodecInfos.clear();
    MediaCodecListWriter writer;
    for (MediaCodecListBuilderBase *builder : builders) {
        if (builder == nullptr) {
            ALOGD("ignored a null builder");
            continue;
        }
        auto currentCheck = builder->buildMediaCodecList(&writer); 创建mediacodeclist
        if (currentCheck != OK) {
            ALOGD("ignored failed builder");
            continue;
        } else {
            mInitCheck = currentCheck;
        }
    }
    writer.writeGlobalSettings(mGlobalSettings);
    writer.writeCodecInfos(&mCodecInfos);
    std::stable_sort(
            mCodecInfos.begin(),
            mCodecInfos.end(),
            [](const sp<MediaCodecInfo> &info1, const sp<MediaCodecInfo> &info2) {
                // null is lowest
                return info1 == nullptr
                        || (info2 != nullptr && info1->getRank() < info2->getRank());
            });
    // remove duplicate entries
    bool dedupe = property_get_bool("debug.stagefright.dedupe-codecs", true);
    if (dedupe) {
        std::set<std::string> codecsSeen;
        for (auto it = mCodecInfos.begin(); it != mCodecInfos.end(); ) {
            std::string codecName = (*it)->getCodecName();
            if (codecsSeen.count(codecName) == 0) {
                codecsSeen.emplace(codecName);
                it++;
            } else {
                it = mCodecInfos.erase(it);
            }
        }
    }
}

std::unique_ptr<MediaCodecListBuilderBase> sCodec2InfoBuilder;

MediaCodecListBuilderBase 有两种 Codec2InfoBuilder 和 OmxInfoBuilder

Z:\rb-a3399pb1\frameworks\av\media\codec2\sfplugin\Codec2InfoBuilder.cpp 进行xml的解析

status_t Codec2InfoBuilder::buildMediaCodecList(MediaCodecListWriter* writer) {
    // TODO: Remove run-time configurations once all codecs are working
    // properly. (Assume "full" behavior eventually.)
    //
    // debug.stagefright.ccodec supports 5 values.
    //   0 - No Codec 2.0 components are available.
    //   1 - Audio decoders and encoders with prefix "c2.android." are available
    //       and ranked first.
    //       All other components with prefix "c2.android." are available with
    //       their normal ranks.
    //       Components with prefix "c2.vda." are available with their normal
    //       ranks.
    //       All other components with suffix ".avc.decoder" or ".avc.encoder"
    //       are available but ranked last.
    //   2 - Components with prefix "c2.android." are available and ranked
    //       first.
    //       Components with prefix "c2.vda." are available with their normal
    //       ranks.
    //       All other components with suffix ".avc.decoder" or ".avc.encoder"
    //       are available but ranked last.
    //   3 - Components with prefix "c2.android." are available and ranked
    //       first.
    //       All other components are available with their normal ranks.
    //   4 - All components are available with their normal ranks.
    //
    // The default value (boot time) is 1.
    //
    // Note: Currently, OMX components have default rank 0x100, while all
    // Codec2.0 software components have default rank 0x200.
    int option = ::android::base::GetIntProperty("debug.stagefright.ccodec", 4);
    // Obtain Codec2Client
    std::vector<Traits> traits = Codec2Client::ListComponents();
    // parse APEX XML first, followed by vendor XML.
    // Note: APEX XML names do not depend on ro.media.xml_variant.* properties.
    MediaCodecsXmlParser parser;
    parser.parseXmlFilesInSearchDirs(
            { "media_codecs.xml", "media_codecs_performance.xml" },
            { "/apex/com.android.media.swcodec/etc" });
    // TODO: remove these c2-specific files once product moved to default file names
    parser.parseXmlFilesInSearchDirs(
            { "media_codecs_c2.xml", "media_codecs_performance_c2.xml" });
    // parse default XML files
    parser.parseXmlFilesInSearchDirs();
    // The mainline modules for media may optionally include some codec shaping information.
    // Based on vendor partition SDK, and the brand/product/device information
    // (expect to be empty in almost always)
    //
    {
        // get build info so we know what file to search
        // ro.vendor.build.fingerprint
        std::string fingerprint = base::GetProperty("ro.vendor.build.fingerprint",
                                               "brand/product/device:");
        ALOGV("property_get for ro.vendor.build.fingerprint == '%s'", fingerprint.c_str());
        // ro.vendor.build.version.sdk
        std::string sdk = base::GetProperty("ro.vendor.build.version.sdk", "0");
        ALOGV("property_get for ro.vendor.build.version.sdk == '%s'", sdk.c_str());
        std::string brand;
        std::string product;
        std::string device;
        size_t pos1;
        pos1 = fingerprint.find('/');
        if (pos1 != std::string::npos) {
            brand = fingerprint.substr(0, pos1);
            size_t pos2 = fingerprint.find('/', pos1+1);
            if (pos2 != std::string::npos) {
                product = fingerprint.substr(pos1+1, pos2 - pos1 - 1);
                size_t pos3 = fingerprint.find('/', pos2+1);
                if (pos3 != std::string::npos) {
                    device = fingerprint.substr(pos2+1, pos3 - pos2 - 1);
                    size_t pos4 = device.find(':');
                    if (pos4 != std::string::npos) {
                        device.resize(pos4);
                    }
                }
            }
        }
        ALOGV("parsed: sdk '%s' brand '%s' product '%s' device '%s'",
            sdk.c_str(), brand.c_str(), product.c_str(), device.c_str());
        std::string base = "/apex/com.android.media/etc/formatshaper";
        // looking in these directories within the apex
        const std::vector<std::string> modulePathnames = {
            base + "/" + sdk + "/" + brand + "/" + product + "/" + device,
            base + "/" + sdk + "/" + brand + "/" + product,
            base + "/" + sdk + "/" + brand,
            base + "/" + sdk,
            base
        };
        parser.parseXmlFilesInSearchDirs( { "media_codecs_shaping.xml" }, modulePathnames);
    }
    if (parser.getParsingStatus() != OK) {
        ALOGD("XML parser no good");
        return OK;
    }
    MediaCodecsXmlParser::AttributeMap settings = parser.getServiceAttributeMap();
    for (const auto &v : settings) {
        if (!hasPrefix(v.first, "media-type-")
                && !hasPrefix(v.first, "domain-")
                && !hasPrefix(v.first, "variant-")) {
            writer->addGlobalSetting(v.first.c_str(), v.second.c_str());
        }
    }
    for (const Traits& trait : traits) {
        C2Component::rank_t rank = trait.rank;
        // Interface must be accessible for us to list the component, and there also
        // must be an XML entry for the codec. Codec aliases listed in the traits
        // allow additional XML entries to be specified for each alias. These will
        // be listed as separate codecs. If no XML entry is specified for an alias,
        // those will be treated as an additional alias specified in the XML entry
        // for the interface name.
        std::vector<std::string> nameAndAliases = trait.aliases;
        nameAndAliases.insert(nameAndAliases.begin(), trait.name);
        for (const std::string &nameOrAlias : nameAndAliases) {
            bool isAlias = trait.name != nameOrAlias;
            std::shared_ptr<Codec2Client::Interface> intf =
                Codec2Client::CreateInterfaceByName(nameOrAlias.c_str());
            if (!intf) {
                ALOGD("could not create interface for %s'%s'",
                        isAlias ? "alias " : "",
                        nameOrAlias.c_str());
                continue;
            }
            if (parser.getCodecMap().count(nameOrAlias) == 0) {
                if (isAlias) {
                    std::unique_ptr<MediaCodecInfoWriter> baseCodecInfo =
                        writer->findMediaCodecInfo(trait.name.c_str());
                    if (!baseCodecInfo) {
                        ALOGD("alias '%s' not found in xml but canonical codec info '%s' missing",
                                nameOrAlias.c_str(),
                                trait.name.c_str());
                    } else {
                        ALOGD("alias '%s' not found in xml; use an XML <Alias> tag for this",
                                nameOrAlias.c_str());
                        // merge alias into existing codec
                        baseCodecInfo->addAlias(nameOrAlias.c_str());
                    }
                } else {
                    ALOGD("component '%s' not found in xml", trait.name.c_str());
                }
                continue;
            }
            std::string canonName = trait.name;
            // TODO: Remove this block once all codecs are enabled by default.
            switch (option) {
            case 0:
                continue;
            case 1:
                if (hasPrefix(canonName, "c2.vda.")) {
                    break;
                }
                if (hasPrefix(canonName, "c2.android.")) {
                    if (trait.domain == C2Component::DOMAIN_AUDIO) {
                        rank = 1;
                        break;
                    }
                    break;
                }
                if (hasSuffix(canonName, ".avc.decoder") ||
                        hasSuffix(canonName, ".avc.encoder")) {
                    rank = std::numeric_limits<decltype(rank)>::max();
                    break;
                }
                continue;
            case 2:
                if (hasPrefix(canonName, "c2.vda.")) {
                    break;
                }
                if (hasPrefix(canonName, "c2.android.")) {
                    rank = 1;
                    break;
                }
                if (hasSuffix(canonName, ".avc.decoder") ||
                        hasSuffix(canonName, ".avc.encoder")) {
                    rank = std::numeric_limits<decltype(rank)>::max();
                    break;
                }
                continue;
            case 3:
                if (hasPrefix(canonName, "c2.android.")) {
                    rank = 1;
                }
                break;
            }
            const MediaCodecsXmlParser::CodecProperties &codec =
                parser.getCodecMap().at(nameOrAlias);
            // verify that either the codec is explicitly enabled, or one of its domains is
            bool codecEnabled = codec.quirkSet.find("attribute::disabled") == codec.quirkSet.end();
            if (!codecEnabled) {
                for (const std::string &domain : codec.domainSet) {
                    const Switch enabled = isDomainEnabled(domain, settings);
                    ALOGV("codec entry '%s' is in domain '%s' that is '%s'",
                            nameOrAlias.c_str(), domain.c_str(), asString(enabled));
                    if (enabled) {
                        codecEnabled = true;
                        break;
                    }
                }
            }
            // if codec has variants, also check that at least one of them is enabled
            bool variantEnabled = codec.variantSet.empty();
            for (const std::string &variant : codec.variantSet) {
                const Switch enabled = isVariantExpressionEnabled(variant, settings);
                ALOGV("codec entry '%s' has a variant '%s' that is '%s'",
                        nameOrAlias.c_str(), variant.c_str(), asString(enabled));
                if (enabled) {
                    variantEnabled = true;
                    break;
                }
            }
            if (!codecEnabled || !variantEnabled) {
                ALOGD("codec entry for '%s' is disabled", nameOrAlias.c_str());
                continue;
            }
            ALOGV("adding codec entry for '%s'", nameOrAlias.c_str());
            std::unique_ptr<MediaCodecInfoWriter> codecInfo = writer->addMediaCodecInfo();
            codecInfo->setName(nameOrAlias.c_str());
            codecInfo->setOwner(("codec2::" + trait.owner).c_str());
            bool encoder = trait.kind == C2Component::KIND_ENCODER;
            typename std::underlying_type<MediaCodecInfo::Attributes>::type attrs = 0;
            if (encoder) {
                attrs |= MediaCodecInfo::kFlagIsEncoder;
            }
            if (trait.owner == "software") {
                attrs |= MediaCodecInfo::kFlagIsSoftwareOnly;
            } else {
                attrs |= MediaCodecInfo::kFlagIsVendor;
                if (trait.owner == "vendor-software") {
                    attrs |= MediaCodecInfo::kFlagIsSoftwareOnly;
                } else if (codec.quirkSet.find("attribute::software-codec")
                        == codec.quirkSet.end()) {
                    attrs |= MediaCodecInfo::kFlagIsHardwareAccelerated;
                }
            }
            codecInfo->setAttributes(attrs);
            if (!codec.rank.empty()) {
                uint32_t xmlRank;
                char dummy;
                if (sscanf(codec.rank.c_str(), "%u%c", &xmlRank, &dummy) == 1) {
                    rank = xmlRank;
                }
            }
            ALOGV("rank: %u", (unsigned)rank);
            codecInfo->setRank(rank);
            for (const std::string &alias : codec.aliases) {
                ALOGV("adding alias '%s'", alias.c_str());
                codecInfo->addAlias(alias.c_str());
            }
            for (auto typeIt = codec.typeMap.begin(); typeIt != codec.typeMap.end(); ++typeIt) {
                const std::string &mediaType = typeIt->first;
                const Switch typeEnabled = isSettingEnabled(
                        "media-type-" + mediaType, settings, Switch::ENABLED_BY_DEFAULT());
                const Switch domainTypeEnabled = isSettingEnabled(
                        "media-type-" + mediaType + (encoder ? "-encoder" : "-decoder"),
                        settings, Switch::ENABLED_BY_DEFAULT());
                ALOGV("type '%s-%s' is '%s/%s'",
                        mediaType.c_str(), (encoder ? "encoder" : "decoder"),
                        asString(typeEnabled), asString(domainTypeEnabled));
                if (!typeEnabled || !domainTypeEnabled) {
                    ALOGD("media type '%s' for codec entry '%s' is disabled", mediaType.c_str(),
                            nameOrAlias.c_str());
                    continue;
                }
                ALOGI("adding type '%s'", typeIt->first.c_str());
                const MediaCodecsXmlParser::AttributeMap &attrMap = typeIt->second;
                std::unique_ptr<MediaCodecInfo::CapabilitiesWriter> caps =
                    codecInfo->addMediaType(mediaType.c_str());
                for (const auto &v : attrMap) {
                    std::string key = v.first;
                    std::string value = v.second;
                    size_t variantSep = key.find(":::");
                    if (variantSep != std::string::npos) {
                        std::string variant = key.substr(0, variantSep);
                        const Switch enabled = isVariantExpressionEnabled(variant, settings);
                        ALOGV("variant '%s' is '%s'", variant.c_str(), asString(enabled));
                        if (!enabled) {
                            continue;
                        }
                        key = key.substr(variantSep + 3);
                    }
                    if (key.find("feature-") == 0 && key.find("feature-bitrate-modes") != 0) {
                        int32_t intValue = 0;
                        // Ignore trailing bad characters and default to 0.
                        (void)sscanf(value.c_str(), "%d", &intValue);
                        caps->addDetail(key.c_str(), intValue);
                    } else {
                        caps->addDetail(key.c_str(), value.c_str());
                    }
                }
                if (!addSupportedProfileLevels(intf, caps.get(), trait, mediaType)) {
                    // TODO(b/193279646) This will get fixed in C2InterfaceHelper
                    // Some components may not advertise supported values if they use a const
                    // param for profile/level (they support only one profile). For now cover
                    // only VP8 here until it is fixed.
                    if (mediaType == MIMETYPE_VIDEO_VP8) {
                        caps->addProfileLevel(VP8ProfileMain, VP8Level_Version0);
                    }
                }
                addSupportedColorFormats(intf, caps.get(), trait, mediaType);
            }
        }
    }
    return OK;
}

MediaCodecsXmlParser 位于 frameworks\av\media\libstagefright\xmlparser\MediaCodecsXmlParser.cpp  frameworks\av\media\libstagefright\xmlparser\include\media\stagefright\xmlparser\MediaCodecsXmlParser.h

用于解析xml

status_t parseXmlFilesInSearchDirs(

           const std::vector<std::string> &xmlFiles = getDefaultXmlNames(),

           const std::vector<std::string> &searchDirs = getDefaultSearchDirs());

static std::vector<std::string> getDefaultSearchDirs() {
        return { "/product/etc",
                 "/odm/etc",
                 "/vendor/etc",
                 "/system/etc" };
    }
std::vector<std::string> MediaCodecsXmlParser::getDefaultXmlNames() {
    static constexpr char const* prefixes[] = {
            "media_codecs",
            "media_codecs_performance"
        };

会优先读取/apex/com.android.media.swcodec/etc下的media_codecs.xml


目录
相关文章
|
6月前
|
算法 数据处理 开发者
FFmpeg库的使用与深度解析:解码音频流流程
FFmpeg库的使用与深度解析:解码音频流流程
104 0
|
6月前
|
存储 编解码 缓存
【ffmpeg 移动视频流位置】深入理解FFmpeg:精细探讨seek操作和编解码上下文
【ffmpeg 移动视频流位置】深入理解FFmpeg:精细探讨seek操作和编解码上下文
323 0
|
编解码 Android开发 C语言
【Android 高性能音频】AAudio 音频库 简介 ( AAudio 音频库简介 | 音频流 | 音频设备 | 共享模式 | 数据模式 )
【Android 高性能音频】AAudio 音频库 简介 ( AAudio 音频库简介 | 音频流 | 音频设备 | 共享模式 | 数据模式 )
757 0
|
编解码 内存技术 容器
【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )(二)
【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )(二)
260 0
【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )(二)
【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )(一)
【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )(一)
275 0
【FFmpeg】使用 FFmpeg 处理音视频格式转换流程 ( 解复用 | 解码 | 帧处理 | 编码 | 复用 )(一)
|
测试技术 API Android开发
【Android 高性能音频】OboeTester 音频性能测试应用 ( Oboe 输出测试参数 | API 选择 | 音频输出设备选择 | 采样率 | 通道 | 采样格式 | 播放偏好 )(二)
【Android 高性能音频】OboeTester 音频性能测试应用 ( Oboe 输出测试参数 | API 选择 | 音频输出设备选择 | 采样率 | 通道 | 采样格式 | 播放偏好 )(二)
294 0
【Android 高性能音频】OboeTester 音频性能测试应用 ( Oboe 输出测试参数 | API 选择 | 音频输出设备选择 | 采样率 | 通道 | 采样格式 | 播放偏好 )(二)
|
测试技术 API Android开发
【Android 高性能音频】OboeTester 音频性能测试应用 ( Oboe 输出测试参数 | API 选择 | 音频输出设备选择 | 采样率 | 通道 | 采样格式 | 播放偏好 )(一)
【Android 高性能音频】OboeTester 音频性能测试应用 ( Oboe 输出测试参数 | API 选择 | 音频输出设备选择 | 采样率 | 通道 | 采样格式 | 播放偏好 )(一)
334 0
【Android 高性能音频】OboeTester 音频性能测试应用 ( Oboe 输出测试参数 | API 选择 | 音频输出设备选择 | 采样率 | 通道 | 采样格式 | 播放偏好 )(一)
|
Java API Android开发
【Android 高性能音频】Oboe 播放器开发 ( 为 OpenSL ES 配置参数以获得最佳延迟 | Oboe 音频流 | Oboe 音频设备 )
【Android 高性能音频】Oboe 播放器开发 ( 为 OpenSL ES 配置参数以获得最佳延迟 | Oboe 音频流 | Oboe 音频设备 )
598 0
|
API Android开发 数据格式
【Android 高性能音频】Oboe 开发流程 ( 包含头 Oboe 头文件 | 创建音频流 | 设置音频流 | 音频流回调类 AudioStreamCallback )
【Android 高性能音频】Oboe 开发流程 ( 包含头 Oboe 头文件 | 创建音频流 | 设置音频流 | 音频流回调类 AudioStreamCallback )
215 0