MediaCodec创建对应解码器

媒体编解码API使用示例

//获取相关格式文件的内容信息,如轨道数量、获取MIME信息、视频的高度与宽度、语言格式、播放总时长等
MediaExtractor mediaExtractor = new MediaExtractor(); 
try {mediaExtractor.setDataSource(path); // 设置数据源
} catch (IOException e1) {e1.printStackTrace();
}String mimeType = null; // "video/mp4v-es" - MPEG4 video, "audio/3gpp" - AMR narrowband audio
for (int i = 0; i < mediaExtractor.getTrackCount(); i++) { // 信道总数MediaFormat format = mediaExtractor.getTrackFormat(i); // 音频文件信息mimeType = format.getString(MediaFormat.KEY_MIME);if (mimeType.startsWith("video/")) { // 视频信道mediaExtractor.selectTrack(i); // 切换到视频信道try {mediaCodec = MediaCodec.createDecoderByType(mimeType); // 创建解码器,提供数据输出} catch (IOException e) {e.printStackTrace();}mediaCodec.configure(format, surface, null, 0);break;}
}
mediaCodec.start(); // 启动MediaCodec ,等待传入数据

1.createDecoderByType根据MimeType信息,创建相匹配的解码器。

public static MediaCodec createDecoderByType(@NonNull String type)throws IOException {return new MediaCodec(type, true /* nameIsType */, false /* encoder */);
}private MediaCodec(@NonNull String name, boolean nameIsType, boolean encoder) {Looper looper;if ((looper = Looper.myLooper()) != null) {mEventHandler = new EventHandler(this, looper);} else if ((looper = Looper.getMainLooper()) != null) {mEventHandler = new EventHandler(this, looper);} else {mEventHandler = null;}mCallbackHandler = mEventHandler;mOnFrameRenderedHandler = mEventHandler;mBufferLock = new Object();native_setup(name, nameIsType, encoder);
}

通过JNI调用到MediaCodec.cpp

static void android_media_MediaCodec_native_setup(sp<JMediaCodec> codec = new JMediaCodec(env, thiz, tmp, nameIsType, encoder);
}JMediaCodec::JMediaCodec(JNIEnv *env, jobject thiz,const char *name, bool nameIsType, bool encoder): mClass(NULL),mObject(NULL) {
...if (nameIsType) {mCodec = MediaCodec::CreateByType(mLooper, name, encoder, &mInitStatus);} else {mCodec = MediaCodec::CreateByComponentName(mLooper, name, &mInitStatus);}CHECK((mCodec != NULL) != (mInitStatus != OK));
}

2.CreateByType创建对应的MediaCodec并初始化

// frameworks\av\media\libstagefright\MediaCodec.cpp
sp<MediaCodec> MediaCodec::CreateByType(const sp<ALooper> &looper, const AString &mime, bool encoder, status_t *err, pid_t pid,uid_t uid) {Vector<AString> matchingCodecs;// 1.根据mime获取对应的codecMediaCodecList::findMatchingCodecs(mime.c_str(),encoder,0,&matchingCodecs);if (err != NULL) {*err = NAME_NOT_FOUND;}//2.创建对应的MediaCodec并初始化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;
}

3.findMatchingCodecs获取对应的codec

先获取支持的编解码器列表,再匹配最佳的编解码器

//frameworks\av\media\libstagefright\MediaCodecList.cpp
void MediaCodecList::findMatchingCodecs(const char *mime, bool encoder, uint32_t flags,Vector<AString> *matches) {matches->clear();// 获取系统支持的编解码器列表const sp<IMediaCodecList> list = getInstance();if (list == nullptr) {return;}//匹配最佳的编解码器size_t index = 0;for (;;) {ssize_t matchIndex =list->findCodecByType(mime, encoder, index);if (matchIndex < 0) {break;}index = matchIndex + 1;const sp<MediaCodecInfo> info = list->getCodecInfo(matchIndex);CHECK(info != nullptr);AString componentName = info->getCodecName();if ((flags & kHardwareCodecsOnly) && isSoftwareCodec(componentName)) {ALOGV("skipping SW codec '%s'", componentName.c_str());} else {matches->push(componentName);ALOGV("matching '%s'", componentName.c_str());}}if (flags & kPreferSoftwareCodecs ||property_get_bool("debug.stagefright.swcodec", false)) {matches->sort(compareSoftwareCodecsFirst);}
}

4.获取系统支持的Codec列表

先通过binder调到MediaPlayerService的getCodecList函数

//frameworks\av\media\libstagefright\MediaCodecList.cpp
sp<IMediaCodecList> MediaCodecList::getInstance() {Mutex::Autolock _l(sRemoteInitMutex);if (sRemoteList == nullptr) {sp<IBinder> binder =defaultServiceManager()->getService(String16("media.player"));sp<IMediaPlayerService> service =interface_cast<IMediaPlayerService>(binder);if (service.get() != nullptr) {// 获取服务端的MediaCodecListsRemoteList = service->getCodecList();if (sRemoteList != nullptr) {sBinderDeathObserver = new BinderDeathObserver();binder->linkToDeath(sBinderDeathObserver.get());}}if (sRemoteList == nullptr) {// if failed to get remote list, create local listsRemoteList = getLocalInstance();}}return sRemoteList;
}

可以看到又回到了MediaCodecList,没错就是media/stagefright下的MediaCodecList
说明MediaCodecList的创建是在mediaplayerservice进程

//frameworks\av\media\libmediaplayerservice\MediaPlayerService.cpp
sp<IMediaCodecList> MediaPlayerService::getCodecList() const {return MediaCodecList::getLocalInstance();
}


GetBuilders()作为参数创建MediaCodecList单例并返回,关键在于GetBuilders()函数

//frameworks\av\media\libstagefright\MediaCodecList.cpp
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;
}GetBuilders
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 =StagefrightPluginLoader::GetCCodecInstance()->createInputSurface();if (surfaceTest == nullptr) {builders.push_back(&sOmxInfoBuilder);}builders.push_back(GetCodec2InfoBuilder());return builders;
}

5.构造MediaCodecList

frameworks\av\media\libstagefright\MediaCodecList.cpp
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;}// 进入build的buildMediaCodecList函数mInitCheck = builder->buildMediaCodecList(&writer);if (mInitCheck != OK) {break;}}writer.writeGlobalSettings(mGlobalSettings);writer.writeCodecInfos(&mCodecInfos);std::stable_sort(mCodecInfos.begin(),mCodecInfos.end(),[](const sp<MediaCodecInfo> &info1, const sp<MediaCodecInfo> &info2) {if (info2 == nullptr) {return false;} else if (info1 == nullptr) {return true;} else {return info1->rank() < info2->rank();}});
}

通过OmxInfoBuilder创建MediaCodec列表(途径一)
1、通过HIDL调用到OmxStore.cpp, 通过OmxStore从xml解析支持的CodecList及Attributes
2、将支持的codec存储进swCodecName2Info, hwCodecName2Info

//frameworks\av\media\libstagefright\OmxInfoBuilder.cpp
status_t OmxInfoBuilder::buildMediaCodecList(MediaCodecListWriter* writer) {// Obtain IOmxStoresp<IOmxStore> omxStore = IOmxStore::getService();if (omxStore == nullptr) {ALOGE("Cannot find an IOmxStore service.");return NO_INIT;}// List service attributes (global settings)Status status;hidl_vec<IOmxStore::RoleInfo> roles;// 通过HIDL,获取inRoleListauto transStatus = omxStore->listRoles([&roles] (const hidl_vec<IOmxStore::RoleInfo>& inRoleList) {roles = inRoleList;});if (!transStatus.isOk()) {ALOGE("Fail to obtain codec roles from IOmxStore.");return NO_INIT;}hidl_vec<IOmxStore::ServiceAttribute> serviceAttributes;transStatus = omxStore->listServiceAttributes([&status, &serviceAttributes] (Status inStatus,const hidl_vec<IOmxStore::ServiceAttribute>& inAttributes) {status = inStatus;serviceAttributes = inAttributes;});if (!transStatus.isOk()) {ALOGE("Fail to obtain global settings from IOmxStore.");return NO_INIT;}if (status != Status::OK) {ALOGE("IOmxStore reports parsing error.");return NO_INIT;}for (const auto& p : serviceAttributes) {writer->addGlobalSetting(p.key.c_str(), p.value.c_str());}// Convert roles to lists of codecs// codec name -> index into swCodecs/hwCodecsstd::map<hidl_string, std::unique_ptr<MediaCodecInfoWriter>>swCodecName2Info, hwCodecName2Info;char rank[PROPERTY_VALUE_MAX];uint32_t defaultRank = 0x100;if (property_get("debug.stagefright.omx_default_rank", rank, nullptr)) {defaultRank = std::strtoul(rank, nullptr, 10);}//将支持的codec存储进swCodecName2Info, hwCodecName2Infofor (const IOmxStore::RoleInfo& role : roles) {const hidl_string& typeName = role.type;bool isEncoder = role.isEncoder;bool preferPlatformNodes = role.preferPlatformNodes;// If preferPlatformNodes is true, hardware nodes must be added after// platform (software) nodes. hwCodecs is used to hold hardware nodes// that need to be added after software nodes for the same role.std::vector<const IOmxStore::NodeInfo*> hwCodecs;for (const IOmxStore::NodeInfo& node : role.nodes) {const hidl_string& nodeName = node.name;// OMX.google开头的都是软解码bool isSoftware = hasPrefix(nodeName, "OMX.google");MediaCodecInfoWriter* info;if (isSoftware) {auto c2i = swCodecName2Info.find(nodeName);if (c2i == swCodecName2Info.end()) {// Create a new MediaCodecInfo for a new node.c2i = swCodecName2Info.insert(std::make_pair(nodeName, writer->addMediaCodecInfo())).first;info = c2i->second.get();info->setName(nodeName.c_str());info->setOwner(node.owner.c_str());info->setEncoder(isEncoder);info->setRank(defaultRank);} else {// The node has been seen before. Simply retrieve the// existing MediaCodecInfoWriter.info = c2i->second.get();}} else {auto c2i = hwCodecName2Info.find(nodeName);if (c2i == hwCodecName2Info.end()) {// Create a new MediaCodecInfo for a new node.if (!preferPlatformNodes) {c2i = hwCodecName2Info.insert(std::make_pair(nodeName, writer->addMediaCodecInfo())).first;info = c2i->second.get();info->setName(nodeName.c_str());info->setOwner(node.owner.c_str());info->setEncoder(isEncoder);info->setRank(defaultRank);} else {// If preferPlatformNodes is true, this node must be// added after all software nodes.hwCodecs.push_back(&node);continue;}} else {// The node has been seen before. Simply retrieve the// existing MediaCodecInfoWriter.info = c2i->second.get();}}std::unique_ptr<MediaCodecInfo::CapabilitiesWriter> caps =info->addMime(typeName.c_str());if (queryCapabilities(node, typeName.c_str(), isEncoder, caps.get()) != OK) {ALOGW("Fail to add mime %s to codec %s",typeName.c_str(), nodeName.c_str());info->removeMime(typeName.c_str());}}。。。}return OK;
}

通过OmxStore.cpp解析xml中的编解码器及属性
OmxStore.cpp读取的配置文件有:
实际项目,在 /vendor/etc 中

//frameworks\av\media\libstagefright\xmlparser\include\media\stagefright\xmlparser\MediaCodecsXmlParser.h
static constexpr char const* defaultSearchDirs[] ={"/odm/etc", "/vendor/etc", "/etc", nullptr};
static constexpr char const* defaultMainXmlName ="media_codecs.xml";
static constexpr char const* defaultPerformanceXmlName ="media_codecs_performance.xml";
static constexpr char const* defaultProfilingResultsXmlPath ="/data/misc/media/media_codecs_profiling_results.xml";

获取对应解码器的查询能力,实际为创建对应得解码器

//frameworks\av\media\libstagefright\OmxInfoBuilder.cpp
status_t queryCapabilities(const IOmxStore::NodeInfo& node, const char* mime, bool isEncoder,MediaCodecInfo::CapabilitiesWriter* caps) {sp<ACodec> codec = new ACodec();status_t err = codec->queryCapabilities(node.owner.c_str(), node.name.c_str(), mime, isEncoder, caps);        // ...
}

依靠binder机制调用OMX服务中的allocateNode()

//frameworks\av\media\libstagefright\ACodec.cpp
status_t ACodec::queryCapabilities(const char* owner, const char* name, const char* mime, bool isEncoder,MediaCodecInfo::CapabilitiesWriter* caps) {const char *role = AVUtils::get()->getComponentRole(isEncoder, mime);if (role == NULL) {return BAD_VALUE;}OMXClient client;// 获取名为“” Omx实现status_t err = client.connect(owner);if (err != OK) {return err;}// 获取omx bp代理对象sp<IOMX> omx = client.interface();sp<CodecObserver> observer = new CodecObserver;sp<IOMXNode> omxNode;// hidl远程调用allocateNodeerr = omx->allocateNode(name, observer, &omxNoe);// ...    
}

获取服务名为XXXX的OMX,并以LWOmx封装返回

frameworks\av\media\libstagefright\OMXClient.cpp
status_t OMXClient::connect(const char* name) {using namespace ::android::hardware::media::omx::V1_0;if (name == nullptr) {name = "default";}sp<IOmx> tOmx = IOmx::getService(name);if (tOmx.get() == nullptr) {ALOGE("Cannot obtain IOmx service.");return NO_INIT;}if (!tOmx->isRemote()) {ALOGE("IOmx service running in passthrough mode.");return NO_INIT;}mOMX = new utils::LWOmx(tOmx);ALOGI("IOmx service obtained");return OK;
}

6.allocateNode创建对应的解码器

LWOmx是代理,具体的实现在libstagefright_omx.so

//frameworks\av\media\libmedia\omx\1.0\WOmx.cpp
status_t LWOmx::allocateNode(char const* name,sp<IOMXObserver> const& observer,sp<IOMXNode>* omxNode) {status_t fnStatus;// allocateNodestatus_t transStatus = toStatusT(mBase->allocateNode(name, new TWOmxObserver(observer),[&fnStatus, omxNode](Status status, sp<IOmxNode> const& node) {fnStatus = toStatusT(status);*omxNode = new LWOmxNode(node);}));return transStatus == NO_ERROR ? fnStatus : transStatus;
}

6.1 创建对应的OMXNodeInstance对象

OMXNodeInstance对象持有一些关键信息,比如生成的节点id(mNodeID)、ACodec传递下来的observer、plugin里创建的解码组件handle和构造OMXNodeInstance时传入的omx对象等。还有关键的解码事件返回的kCallbacks。

//frameworks\av\media\libstagefright\omx\1.0\Omx.cpp 
//libstagefright_omx.so
Return<void> Omx::allocateNode(const hidl_string& name,const sp<IOmxObserver>& observer,allocateNode_cb _hidl_cb) {using ::android::IOMXNode;using ::android::IOMXObserver;sp<OMXNodeInstance> instance;{Mutex::Autolock autoLock(mLock);if (mLiveNodes.size() == kMaxNodeInstances) {_hidl_cb(toStatus(NO_MEMORY), nullptr);return Void();}// 1. 实例化OMXNodeInstance对象,存放node id、解码组件handle、ACodec传递下来的observer等instance = new OMXNodeInstance(this, new LWOmxObserver(observer), name.c_str());OMX_COMPONENTTYPE *handle;// 2. master通知plugin创建对应的解码组件、并返回其操作句柄 OMX_ERRORTYPE err = mMaster->makeComponentInstance(name.c_str(), &OMXNodeInstance::kCallbacks,instance.get(), &handle);if (err != OMX_ErrorNone) {LOG(ERROR) << "Failed to allocate omx component ""'" << name.c_str() << "' "" err=" << asString(err) <<"(0x" << std::hex << unsigned(err) << ")";_hidl_cb(toStatus(StatusFromOMXError(err)), nullptr);return Void();}instance->setHandle(handle);// Find quirks from mParserconst auto& codec = mParser.getCodecMap().find(name.c_str());if (codec == mParser.getCodecMap().cend()) {LOG(WARNING) << "Failed to obtain quirks for omx component ""'" << name.c_str() << "' ""from XML files";} else {uint32_t quirks = 0;for (const auto& quirk : codec->second.quirkSet) {if (quirk == "requires-allocate-on-input-ports") {quirks |= OMXNodeInstance::kRequiresAllocateBufferOnInputPorts;}if (quirk == "requires-allocate-on-output-ports") {quirks |= OMXNodeInstance::kRequiresAllocateBufferOnOutputPorts;}}instance->setQuirks(quirks);}mLiveNodes.add(observer.get(), instance);mNode2Observer.add(instance.get(), observer.get());}observer->linkToDeath(this, 0);_hidl_cb(toStatus(OK), new TWOmxNode(instance));return Void();
}// frameworks/av/media/libstagefright/omx/OMXNodeInstance.cpp
// static
OMX_CALLBACKTYPE OMXNodeInstance::kCallbacks = {&OnEvent, &OnEmptyBufferDone, &OnFillBufferDone
};

6.2 makeComponentInstance

OMXMaster是解码库加载的核心
先找到指定的plugin(解码器组件,有厂商定制的及AOSP的)、再通知plugin去创建对应的解码组件。

frameworks\av\media\libstagefright\omx\OMXMaster.cpp
OMX_ERRORTYPE OMXMaster::makeComponentInstance(const char *name,const OMX_CALLBACKTYPE *callbacks,OMX_PTR appData,OMX_COMPONENTTYPE **component) {ALOGI("makeComponentInstance(%s) in %s process", name, mProcessName);Mutex::Autolock autoLock(mLock);*component = NULL;// 找到对应的Pluginssize_t index = mPluginByComponentName.indexOfKey(String8(name));if (index < 0) {return OMX_ErrorInvalidComponentName;}OMXPluginBase *plugin = mPluginByComponentName.valueAt(index);// 创建对应的解码组件OMX_ERRORTYPE err =plugin->makeComponentInstance(name, callbacks, appData, component);if (err != OMX_ErrorNone) {return err;}mPluginByInstance.add(*component, plugin);return err;
}

这些解码器是在哪加载的呢,回到OMXMaster构造函数

加载软硬编解码管理器中的所有解码器,并存在mPluginByComponentName。
1.加载厂商编解码管理器,libstagefrighthw.so
2.加载软编解码管理器,SoftOMXPlugin

OMXMaster::OMXMaster(): mVendorLibHandle(NULL) {pid_t pid = getpid();char filename[20];snprintf(filename, sizeof(filename), "/proc/%d/comm", pid);int fd = open(filename, O_RDONLY);if (fd < 0) {ALOGW("couldn't determine process name");strlcpy(mProcessName, "<unknown>", sizeof(mProcessName));} else {ssize_t len = read(fd, mProcessName, sizeof(mProcessName));if (len < 2) {ALOGW("couldn't determine process name");strlcpy(mProcessName, "<unknown>", sizeof(mProcessName));} else {// the name is newline terminated, so erase the newlinemProcessName[len - 1] = 0;}close(fd);}//加载厂商解码器SOaddVendorPlugin();//加载软解码器SOaddPlugin(new SoftOMXPlugin);
}void OMXMaster::addVendorPlugin() {addPlugin("libstagefrighthw.so");
}

7 实际创建对应解码器makeComponentInstance

如果是软件编解码器,调用SoftOMXPlugin的makeComponentInstance
如果是硬件就调用QComOMXPlugin的makeComponentInstance
Component可以理解为一个解码器实例。

7.1 软解码器

1.根据mime name匹配kComponents数据,如: { "OMX.google.aac.decoder", "aacdec", "audio_decoder.aac" },
2. 拿到的mLibNameSuffix值为aacdec,最后拼接的libName是libstagefright_soft_aacdec.so
3. 从system/lib目录下加载libstagefright_soft_aacdec.so库
4. 调用其对应的createSoftOMXComponent函数创建SoftOMXComponent

// frameworks\av\media\libstagefright\omx\SoftOMXComponent.cpp
OMX_ERRORTYPE SoftOMXPlugin::makeComponentInstance(const char *name,const OMX_CALLBACKTYPE *callbacks,OMX_PTR appData,OMX_COMPONENTTYPE **component) {ALOGV("makeComponentInstance '%s'", name);for (size_t i = 0; i < kNumComponents; ++i) {// 1.根据name匹配kComponents数据,// 如: { "OMX.google.aac.decoder", "aacdec", "audio_decoder.aac" },if (strcmp(name, kComponents[i].mName)) {continue;}// 2. 拿到的mLibNameSuffix值为aacdec,最后拼接的libName是libstagefright_soft_aacdec.soAString libName = "libstagefright_soft_";libName.append(kComponents[i].mLibNameSuffix);libName.append(".so");// RTLD_NODELETE means we keep the shared library around forever.// this eliminates thrashing during sequences like loading soundpools.// It also leaves the rest of the logic around the dlopen()/dlclose()// calls in this file unchanged.//// Implications of the change:// -- the codec process (where this happens) will have a slightly larger//    long-term memory footprint as it accumulates the loaded shared libraries.//    This is expected to be a small amount of memory.// -- plugin codecs can no longer (and never should have) depend on a//    free reset of any static data as the library would have crossed//    a dlclose/dlopen cycle.//// 3. 从system/lib目录下加载libstagefright_soft_aacdec.so库void *libHandle = dlopen(libName.c_str(), RTLD_NOW|RTLD_NODELETE);if (libHandle == NULL) {ALOGE("unable to dlopen %s: %s", libName.c_str(), dlerror());return OMX_ErrorComponentNotFound;}typedef SoftOMXComponent *(*CreateSoftOMXComponentFunc)(const char *, const OMX_CALLBACKTYPE *,OMX_PTR, OMX_COMPONENTTYPE **);CreateSoftOMXComponentFunc createSoftOMXComponent =(CreateSoftOMXComponentFunc)dlsym(libHandle,"_Z22createSoftOMXComponentPKcPK16OMX_CALLBACKTYPE""PvPP17OMX_COMPONENTTYPE");if (createSoftOMXComponent == NULL) {dlclose(libHandle);libHandle = NULL;return OMX_ErrorComponentNotFound;}// 4. 创建对应的SoftOMXComponentsp<SoftOMXComponent> codec =(*createSoftOMXComponent)(name, callbacks, appData, component);if (codec == NULL) {dlclose(libHandle);libHandle = NULL;return OMX_ErrorInsufficientResources;}OMX_ERRORTYPE err = codec->initCheck();if (err != OMX_ErrorNone) {dlclose(libHandle);libHandle = NULL;return err;}codec->incStrong(this);codec->setLibHandle(libHandle);return OMX_ErrorNone;}return OMX_ErrorInvalidComponentName;
}

SoftOmxPlugin是google提供的原生的一套编解码器插件
即通常说的软解硬解中的软解。它支持市面上常用的音视频格式,软解码支持的格式:

// frameworks\av\media\libstagefright\omx\SoftOMXPlugin.cpp
static const struct {const char *mName;const char *mLibNameSuffix;const char *mRole;} kComponents[] = {// two choices for aac decoding.// configurable in media/libstagefright/data/media_codecs_google_audio.xml// default implementation{ "OMX.google.aac.decoder", "aacdec", "audio_decoder.aac" },// alternate implementation{ "OMX.google.xaac.decoder", "xaacdec", "audio_decoder.aac" },{ "OMX.google.aac.encoder", "aacenc", "audio_encoder.aac" },{ "OMX.google.amrnb.decoder", "amrdec", "audio_decoder.amrnb" },{ "OMX.google.amrnb.encoder", "amrnbenc", "audio_encoder.amrnb" },{ "OMX.google.amrwb.decoder", "amrdec", "audio_decoder.amrwb" },{ "OMX.google.amrwb.encoder", "amrwbenc", "audio_encoder.amrwb" },{ "OMX.google.h264.decoder", "avcdec", "video_decoder.avc" },{ "OMX.google.h264.encoder", "avcenc", "video_encoder.avc" },{ "OMX.google.hevc.decoder", "hevcdec", "video_decoder.hevc" },{ "OMX.google.g711.alaw.decoder", "g711dec", "audio_decoder.g711alaw" },{ "OMX.google.g711.mlaw.decoder", "g711dec", "audio_decoder.g711mlaw" },{ "OMX.google.mpeg2.decoder", "mpeg2dec", "video_decoder.mpeg2" },{ "OMX.google.h263.decoder", "mpeg4dec", "video_decoder.h263" },{ "OMX.google.h263.encoder", "mpeg4enc", "video_encoder.h263" },{ "OMX.google.mpeg4.decoder", "mpeg4dec", "video_decoder.mpeg4" },{ "OMX.google.mpeg4.encoder", "mpeg4enc", "video_encoder.mpeg4" },{ "OMX.google.mp3.decoder", "mp3dec", "audio_decoder.mp3" },{ "OMX.google.vorbis.decoder", "vorbisdec", "audio_decoder.vorbis" },{ "OMX.google.opus.decoder", "opusdec", "audio_decoder.opus" },{ "OMX.google.vp8.decoder", "vpxdec", "video_decoder.vp8" },{ "OMX.google.vp9.decoder", "vpxdec", "video_decoder.vp9" },{ "OMX.google.vp8.encoder", "vpxenc", "video_encoder.vp8" },{ "OMX.google.vp9.encoder", "vpxenc", "video_encoder.vp9" },{ "OMX.google.raw.decoder", "rawdec", "audio_decoder.raw" },{ "OMX.google.flac.decoder", "flacdec", "audio_decoder.flac" },{ "OMX.google.flac.encoder", "flacenc", "audio_encoder.flac" },{ "OMX.google.gsm.decoder", "gsmdec", "audio_decoder.gsm" },
#ifdef QTI_FLAC_DECODER{ "OMX.qti.audio.decoder.flac", "qtiflacdec", "audio_decoder.flac" },
#endif
};

软解码器代码目录
frameworks\av\media\libstagefright\codecs,编译会生成对应的so库
车机内软解码so所在的目录

软解码器的实现

是基于“OpenMax IL的标准接口”进行,实际硬解码器也是如此。
以libstagefright_soft_aacdec为例子
libstagefright_soft_aacdec.so主体是SoftAAC2.cpp和SoftAAC2.h,所以dlsym返回的createSoftOMXComponent值如下:

//frameworks\av\media\libstagefright\codecs\aacdec\SoftAAC2.cpp
android::SoftOMXComponent *createSoftOMXComponent(const char *name, const OMX_CALLBACKTYPE *callbacks,OMX_PTR appData, OMX_COMPONENTTYPE **component) {return new android::SoftAAC2(name, callbacks, appData, component);
}

SoftAAC2继承自SimpleSoftOMXComponent

//frameworks\av\media\libstagefright\codecs\aacdec\SoftAAC2.cpp
SoftAAC2::SoftAAC2(const char *name,const OMX_CALLBACKTYPE *callbacks,OMX_PTR appData,OMX_COMPONENTTYPE **component): SimpleSoftOMXComponent(name, callbacks, appData, component),mAACDecoder(NULL),mStreamInfo(NULL),mIsADTS(false),mInputBufferCount(0),mOutputBufferCount(0),mSignalledError(false),mLastInHeader(NULL),mLastHeaderTimeUs(-1),mNextOutBufferTimeUs(0),mOutputPortSettingsChange(NONE) {initPorts();CHECK_EQ(initDecoder(), (status_t)OK);
}

SimpleSoftOMXComponent实现OpenMax IL的标准接口,例如sendCommand、setParameter

//frameworks\av\media\stagefright\omx\SimpleSoftOMXComponent.cpp
SimpleSoftOMXComponent::SimpleSoftOMXComponent(const char *name,const OMX_CALLBACKTYPE *callbacks,OMX_PTR appData,OMX_COMPONENTTYPE **component): SoftOMXComponent(name, callbacks, appData, component),mLooper(new ALooper),mHandler(new AHandlerReflector<SimpleSoftOMXComponent>(this)),mState(OMX_StateLoaded),mTargetState(OMX_StateLoaded) {mLooper->setName(name);mLooper->registerHandler(mHandler);mLooper->start(false, // runOnCallingThreadfalse, // canCallJavaANDROID_PRIORITY_VIDEO);
}

SoftOMXComponent里有对OpenMax接口的

// frameworks\av\media\stagefright\omx\SoftOMXComponent.cpp
// OMX_Component.h为 OpenMax IL version 1.1.2的接口头文件
#include <OMX_Component.h>
SoftOMXComponent::SoftOMXComponent(const char *name,const OMX_CALLBACKTYPE *callbacks,OMX_PTR appData,OMX_COMPONENTTYPE **component): mName(name),mCallbacks(callbacks),mComponent(new OMX_COMPONENTTYPE),mLibHandle(NULL) {mComponent->nSize = sizeof(*mComponent);mComponent->nVersion.s.nVersionMajor = 1;mComponent->nVersion.s.nVersionMinor = 0;mComponent->nVersion.s.nRevision = 0;mComponent->nVersion.s.nStep = 0;mComponent->pComponentPrivate = this;mComponent->pApplicationPrivate = appData;  。。。*component = mComponent;
}

7.2 硬解码器

调用mGetHandle函数指针,而该函数指针是在该类构造时初始化。

//hardware\qcom\media\libstagefrighthw\QComOMXPlugin.cpp
OMX_ERRORTYPE QComOMXPlugin::makeComponentInstance(const char *name,const OMX_CALLBACKTYPE *callbacks,OMX_PTR appData,OMX_COMPONENTTYPE **component) {if (mLibHandle == NULL) {return OMX_ErrorUndefined;}return (*mGetHandle)(reinterpret_cast<OMX_HANDLETYPE *>(component),const_cast<char *>(name),appData, const_cast<OMX_CALLBACKTYPE *>(callbacks));
}

可以看到原来mGetHandle就是libOmxCore.so库中的OMX_GetHandle函数

//hardware\qcom\media\libstagefrighthw\QComOMXPlugin.cpp
QComOMXPlugin::QComOMXPlugin(): mLibHandle(dlopen("libOmxCore.so", RTLD_NOW)),mInit(NULL),mDeinit(NULL),mComponentNameEnum(NULL),mGetHandle(NULL),mFreeHandle(NULL),mGetRolesOfComponentHandle(NULL) {if (mLibHandle != NULL) {mInit = (InitFunc)dlsym(mLibHandle, "OMX_Init");mDeinit = (DeinitFunc)dlsym(mLibHandle, "OMX_Deinit");mComponentNameEnum =(ComponentNameEnumFunc)dlsym(mLibHandle, "OMX_ComponentNameEnum");mGetHandle = (GetHandleFunc)dlsym(mLibHandle, "OMX_GetHandle");mFreeHandle = (FreeHandleFunc)dlsym(mLibHandle, "OMX_FreeHandle");mGetRolesOfComponentHandle =(GetRolesOfComponentFunc)dlsym(mLibHandle, "OMX_GetRolesOfComponent");if (!mInit || !mDeinit || !mComponentNameEnum || !mGetHandle ||!mFreeHandle || !mGetRolesOfComponentHandle) {dlclose(mLibHandle);mLibHandle = NULL;} else(*mInit)();}
}

OMX_GetHandle函数
1、如果是avc解码器,加载libOmxVideoDSMode.so库,判断是否有dsmode
2、视频预处理开启情况需要加载vpp库
3、动态加载对应的编解码so

//hardware\qcom\media\mm-core\src\common\qc_omx_core.c
// libOmxCore.so
OMX_API OMX_ERRORTYPE OMX_APIENTRY
OMX_GetHandle(OMX_OUT OMX_HANDLETYPE*     handle,OMX_IN OMX_STRING    componentName,OMX_IN OMX_PTR             appData,OMX_IN OMX_CALLBACKTYPE* callBacks)
{OMX_ERRORTYPE  eRet = OMX_ErrorNone;int cmp_index = -1;int hnd_index = -1;int vpp_cmp_index = -1;DEBUG_PRINT("OMXCORE API :  GetHandle %p %s %p\n", handle,componentName,appData);pthread_mutex_lock(&lock_core);if(handle){*handle = NULL;char optComponentName[OMX_MAX_STRINGNAME_SIZE];strlcpy(optComponentName, componentName, OMX_MAX_STRINGNAME_SIZE);// 如果是avc解码器,加载libOmxVideoDSMode.so库,判断是否有dsmodeif(strstr(componentName, "avc") && strstr(componentName, "decoder")){void *libhandle = dlopen("libOmxVideoDSMode.so", RTLD_NOW);if(libhandle){int (*fn_ptr)()  = dlsym(libhandle, "isDSModeActive");dlclose(libhandle);}else{DEBUG_PRINT_ERROR("Failed to load dsmode library");}}if(cmp_index < 0){cmp_index = get_cmp_index(componentName);strlcpy(optComponentName, componentName, OMX_MAX_STRINGNAME_SIZE);}if(cmp_index >= 0){char value[PROPERTY_VALUE_MAX];DEBUG_PRINT("getting fn pointer\n");// Load VPP omx component for decoder if vpp// property is enabled// 视频预处理开启情况需要加载vppif ((property_get("vendor.media.vpp.enable", value, NULL))&& (!strcmp("1", value) || !strcmp("true", value))) {DEBUG_PRINT("VPP property is enabled");if (!strcmp(core[cmp_index].so_lib_name, "libOmxVdec.so")|| !strcmp(core[cmp_index].so_lib_name, "libOmxSwVdec.so")) {vpp_cmp_index = get_cmp_index("OMX.qti.vdec.vpp");if (vpp_cmp_index < 0) {DEBUG_PRINT_ERROR("Unable to find VPP OMX lib in registry ");} else {DEBUG_PRINT("Loading vpp for vdec");cmp_index = vpp_cmp_index;}}}// dynamically load the socore[cmp_index].fn_ptr =omx_core_load_cmp_library(core[cmp_index].so_lib_name,&core[cmp_index].so_lib_handle);。。。。
}

加载对应的SO库

// /hardware\qcom\media\mm-core\src\common\qc_omx_core.c
static create_qc_omx_component
omx_core_load_cmp_library(char *libname, void **handle_ptr)
{create_qc_omx_component fn_ptr = NULL;if(handle_ptr){DEBUG_PRINT("Dynamically Loading the library : %s\n",libname);if (!strcmp(libname, "libOmxVpp.so"))*handle_ptr = dlopen(libname, RTLD_NOW|RTLD_GLOBAL);else*handle_ptr = dlopen(libname, RTLD_NOW);if(*handle_ptr){fn_ptr = dlsym(*handle_ptr, "get_omx_component_factory_fn");if(fn_ptr == NULL){DEBUG_PRINT("Error: Library %s incompatible as QCOM OMX component loader - %s\n",libname, dlerror());*handle_ptr = NULL;}}else{DEBUG_PRINT("Error: Couldn't load %s: %s\n",libname,dlerror());}}return fn_ptr;
}

高通平台支持的硬解码器
注意里边有一些是软解码(包含Sw字符的)

omx_core_cb_type core[] =
{//Common entriesOMX_REGISTRY_ENTRY("OMX.qcom.video.decoder.avc", "libOmxVdec.so", "video_decoder.avc"),OMX_REGISTRY_ENTRY("OMX.qcom.video.decoder.avc.secure", "libOmxVdec.so", "video_decoder.avc"),OMX_REGISTRY_ENTRY("OMX.qcom.video.decoder.mpeg2", "libOmxVdec.so", "video_decoder.mpeg2"),OMX_REGISTRY_ENTRY("OMX.qcom.video.decoder.mpeg2.secure", "libOmxVdec.so", "video_decoder.mpeg2"),OMX_REGISTRY_ENTRY("OMX.qcom.video.decoder.hevc", "libOmxVdec.so", "video_decoder.hevc"),OMX_REGISTRY_ENTRY("OMX.qcom.video.decoder.hevc.secure", "libOmxVdec.so", "video_decoder.hevc"),OMX_REGISTRY_ENTRY("OMX.qcom.video.decoder.vp8", "libOmxVdec.so", "video_decoder.vp8"),OMX_REGISTRY_ENTRY("OMX.qcom.video.decoder.vp9", "libOmxVdec.so", "video_decoder.vp9"),OMX_REGISTRY_ENTRY("OMX.qcom.video.decoder.vp9.secure", "libOmxVdec.so", "video_decoder.vp9"),OMX_REGISTRY_ENTRY("OMX.qti.video.decoder.mpeg4sw", "libOmxSwVdec.so", "video_decoder.mpeg4"),OMX_REGISTRY_ENTRY("OMX.qti.video.decoder.divxsw", "libOmxSwVdec.so", "video_decoder.divx"),OMX_REGISTRY_ENTRY("OMX.qti.video.decoder.divx4sw", "libOmxSwVdec.so", DIVX4_MIME),OMX_REGISTRY_ENTRY("OMX.qti.video.decoder.h263sw", "libOmxSwVdec.so", "video_decoder.h263"),OMX_REGISTRY_ENTRY("OMX.qcom.video.encoder.mpeg4sw", "libOmxSwVencMpeg4.so", "video_encoder.mpeg4"),OMX_REGISTRY_ENTRY("OMX.qcom.video.encoder.h263sw", "libOmxSwVencMpeg4.so", "video_encoder.h263"),OMX_REGISTRY_ENTRY("OMX.qcom.video.encoder.avc", "libOmxVenc.so", "video_encoder.avc"),OMX_REGISTRY_ENTRY("OMX.qcom.video.encoder.avc.secure", "libOmxVenc.so", "video_encoder.avc"),OMX_REGISTRY_ENTRY("OMX.qcom.video.encoder.vp8", "libOmxVenc.so", "video_encoder.vp8"),OMX_REGISTRY_ENTRY("OMX.qcom.video.encoder.hevc", "libOmxVenc.so", "video_encoder.hevc"),OMX_REGISTRY_ENTRY("OMX.qcom.video.encoder.hevc.secure", "libOmxVenc.so", "video_encoder.hevc"),OMX_REGISTRY_ENTRY("OMX.qcom.video.encoder.heic", "libOmxVenc.so", "image_encoder.heic"),OMX_REGISTRY_ENTRY("OMX.qcom.audio.decoder.Qcelp13", "libOmxQcelp13Dec.so", "audio_decoder.Qcelp13"),OMX_REGISTRY_ENTRY("OMX.qcom.audio.decoder.evrc", "libOmxEvrcDec.so", "audio_decoder.evrc"),OMX_REGISTRY_ENTRY("OMX.qcom.audio.decoder.wma", "libOmxWmaDec.so", "audio_decoder.wma"),OMX_REGISTRY_ENTRY("OMX.qcom.audio.decoder.wma10Pro", "libOmxWmaDec.so", "audio_decoder.wma"),OMX_REGISTRY_ENTRY("OMX.qcom.audio.decoder.wmaLossLess", "libOmxWmaDec.so", "audio_decoder.wma"),OMX_REGISTRY_ENTRY("OMX.qcom.audio.decoder.amrwbplus", "libOmxAmrwbplusDec.so", "audio_decoder.awbplus"),OMX_REGISTRY_ENTRY("OMX.qcom.audio.decoder.alac", "libOmxAlacDec.so", "audio_decoder.alac"),OMX_REGISTRY_ENTRY("OMX.qti.audio.decoder.alac.sw", "libOmxAlacDecSw.so", "audio_decoder.alac"),。。。。。
};

硬解码器的实现位置
Audio encode:hardware\qcom\audio\mm-audio
Audio decode:\vendor\qcom\proprietary\mm-audio\omx
Video encode/decode: hardware\qcom\media\mm-video-v4l2

对比Audio每个解码器均有一个so,Video采用v4l2框架实现,只有libOmxVenc.so,libOmxSwVencMpeg4
libOmxSwVdec,libOmxVdec.so 这4个库。

部分缩写含义

1.VPP 视频预处理
2.avc 就是H264
3.v4l2 是:video for linux version2,是Linux内核中关于视频设备的内核驱动框架,为上层的访问底层的视频设备提供了统一的接口.凡是内核中的子系统都有抽象底层硬件的差异,为上层提供统一的接口和提取出公共代码避免代码冗余等好处.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/27976.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

apt、aptitude、apt-get/apt-cache语法的区别

apt句法 aptitude句法 apt-get/apt-cache语法 描述 apt update aptitude update apt-get update 更新包存档元数据 apt install foo ap

【Spring】Spring AOP 初识及实现原理解析

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE进阶 目录 文章目录 一、初识AOP 1.1 什么是AOP&#xff1f; 1.2 AOP的组成 1.2.1 切面&#xff08;Aspect&#xff09; 1.2.2 切点&#xff08;Pointcut&#xff09; 1.2.3 连接点&…

Open3D(C++) 角度制与弧度制的相互转换

目录 一、弧度转角度1、计算公式2、主要函数3、示例代码4、结果展示二、角度转弧度1、计算公式2、主要函数3、示例代码4、结果展示三、归一化到(-PI,PI)1、主要函数<

blender基础认识(选项开关、工具栏、视图等)

文章目录 引言一、大纲选项开关和保存启动文件1. 大纲选项1. 禁用选中2. 视图影藏3. 视图禁用4. 渲染禁用 2. 保存启动文件 二、工具栏和侧边栏1. 左侧工具栏2. 右侧工具栏 三、视图1. 视角2. 缩放3. 拖拽4. 摄像机视角5. 切换正交视图6. 局部视图7. 显示隐藏 四、添加删除物体…

【DMA】如何保证 DMA 和 cache 的一致性

一方面&#xff0c;当 CPU 要从cache 读取数据时&#xff0c;会先检查cache是否命中&#xff0c;如果命中就直接返回&#xff0c;此时便不再访问内存&#xff1b;另一方面&#xff0c;DMA 在 向内存写入数据。这样一来就造成了DMA 传输的内容和cache中缓存的内容不一致。 DMA 向…

“深入剖析JVM内部机制:探秘Java虚拟机的运行原理“

标题&#xff1a;深入剖析JVM内部机制&#xff1a;探秘Java虚拟机的运行原理 摘要&#xff1a;本文将深入剖析Java虚拟机&#xff08;JVM&#xff09;的内部机制&#xff0c;探秘其运行原理。我们将从JVM的结构、内存管理、垃圾回收、即时编译等方面展开讨论&#xff0c;并通过…

关于echarts遇到的一些问题

1.echarts监听legend&#xff0c;动态设置legend属性无效 动态更改legend中的icon&#xff0c; myChart.setOption(option&#xff09;失效&#xff0c;但是设置局部就生效 myChart.on(legendselectchanged, function (params) {if (params.selected[params.name]) {data1[dat…

C++ 多态性——纯虚函数与抽象类

抽象类是一种特殊的类&#xff0c;它为一个类族提供统一的操作界面。抽象类是为了抽象和设计的目的而建立的。可以说&#xff0c;建立抽象类&#xff0c;就是为了通过它多态地使用其中的成员函数。抽象类处于类层次的上层&#xff0c;一个抽象类自身无法实例化&#xff0c;也就…

VUE+view table.exportCsv()导出.csv文档时如何防止数据格式为科学计数

当使用table.exportCsv()方法导出数据时&#xff0c;出现科学计数法问题&#xff0c;像电话号码&#xff0c;身份证号码等&#xff0c;当数据大于15位后面的会用0替代。 针对这一问题&#xff0c;解决方法如下&#xff1a;就是再数字前加上制表符“\t”注意双引号&#xff0c;…

前端技术基础-css

前端技术基础-css【了解】 一、css理解 概念&#xff1a;CSS&#xff1a;C(cascade) SS(StyleSheet) &#xff0c;级联样式表。作用&#xff1a;对网页提供丰富的视觉效果&#xff0c;进行美化页面(需要在html页面基础上)样式规则&#xff1a;样式1&#xff1a;值1;样式2&…

c++如何实现类名创建类对象(反射)

1. 适用场景 c开发过程中使用工厂模式创建各种类对象的时候可能需要写一堆的’if-else’函数来根据传入的参数类型创建不同的类对象; 尤其是多态的情况下返回基类指针的情况. 例如: class B -> class A // B继承A class C -> class A // C继承AA *Create(type) {if (ty…

0基础学习VR全景平台篇 第79篇:全景相机-泰科易如何直播推流

泰科易科技是中国的一家研发全景相机的高科技公司&#xff0c;前不久&#xff0c;在2020世界VR产业大会上发布了新一代5G VR直播影像采集终端--360starlight。以其出色的夜景成像效果和一“部”到位的直播方案重新定义了VR慢直播相机&#xff0c;对行业具有高度借鉴意义。 本文…

【工具插件类教学】电脑端移动端缩放大图自适应Simple Zoom

目录 简介 1.创建Canvas并设置 2.使用预制体Zoom 3.商店地址 简介 特点: •易于使用和高度可定制。 •支持鼠标(桌面)和触摸(移动)。 •指定最小和最大缩放的限制。 •缩放指针(鼠标/手指)或屏幕上预定义的自定义位置。 •变焦时使用夹紧/弹性变焦类型。 •定义缩…

MySQL插入数据的方法

插入数据方法&#xff1a; 1.insert into 表 values(value1, value2, value3....) 2.insert into 表 (字段1&#xff0c; 字段3&#xff0c; 字段5) values(value1, value2, value3) 3.insert into 表 [(字段1&#xff0c; 字段2&#xff0c; 字段3....)] values(value1, val…

【CSS】网格布局(简单布局、网格合并、网格嵌套)

文章目录 CSS网格布局&#xff08;Grid Layout&#xff09;1. 简单布局2. 网格合并3. 网格嵌套4. 总结 CSS网格布局&#xff08;Grid Layout&#xff09; CSS网格布局&#xff08;Grid Layout&#xff09;是一种强大且灵活的CSS布局系统&#xff0c;允许开发者以网格形式组织和…

Spring源码解析(八):bean后置处理器CommonAnnotationBeanPostProcessor

Spring源码系列文章 Spring源码解析(一)&#xff1a;环境搭建 Spring源码解析(二)&#xff1a;bean容器的创建、默认后置处理器、扫描包路径bean Spring源码解析(三)&#xff1a;bean容器的刷新 Spring源码解析(四)&#xff1a;单例bean的创建流程 Spring源码解析(五)&…

opencv基础-34 图像平滑处理-双边滤波cv2.bilateralFilter()

双边滤波&#xff08;BilateralFiltering&#xff09;是一种图像处理滤波技术&#xff0c;用于平滑图像并同时保留边缘信息。与其他传统的线性滤波方法不同&#xff0c;双边滤波在考虑像素之间的空间距离之外&#xff0c;还考虑了像素之间的灰度值相似性。这使得双边滤波能够有…

图像分段线性变换

图像分段线性变换&#xff08;Piecewise Linear Transformation&#xff09;是一种图像处理技术&#xff0c;它通过对不同区域的像素值应用不同的线性变换来调整图像的对比度和亮度。这通常用于增强图像中特定区域的细节或调整图像的整体外观。数学上&#xff0c;分段线性变换可…

数据结构初阶--二叉树的顺序结构之堆

目录 一.堆的概念及结构 1.1.堆的概念 1.2.堆的存储结构 二.堆的功能实现 2.1.堆的定义 2.2.堆的初始化 2.3.堆的销毁 2.4.堆的打印 2.5.堆的插入 向上调整算法 堆的插入 2.6.堆的删除 向下调整算法 堆的删除 2.7.堆的取堆顶元素 2.8.堆的判空 2.9.堆的求堆的…

[Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动程序

1.今天开发了一套服务程序&#xff0c;使用的是Odbc连接MySql数据库&#xff0c; 在我本机用VS打开程序时&#xff0c;访问一切正常&#xff0c;当发布出来装在电脑上&#xff0c;连接数据库时提示&#xff1a; [Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定…