当前位置: 首页 > news >正文

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内核中关于视频设备的内核驱动框架,为上层的访问底层的视频设备提供了统一的接口.凡是内核中的子系统都有抽象底层硬件的差异,为上层提供统一的接口和提取出公共代码避免代码冗余等好处.

相关文章:

MediaCodec创建对应解码器

媒体编解码API使用示例 //获取相关格式文件的内容信息&#xff0c;如轨道数量、获取MIME信息、视频的高度与宽度、语言格式、播放总时长等 MediaExtractor mediaExtractor new MediaExtractor(); try {mediaExtractor.setDataSource(path); // 设置数据源 } catch (IOExcept…...

使用eXosip+ffmpeg、ffplay命令行实现sip客户端

文章目录 前言一、关键实现1、主要流程2、解决端口冲突&#xff08;1&#xff09;、出现原因&#xff08;2&#xff09;、解决方法 3、解析sdp&#xff08;1&#xff09;、定义实体&#xff08;2&#xff09;、解析视频&#xff08;3&#xff09;、解析音频 4、命令行推拉流&am…...

dotNet 之网络TCP

**硬件支持型号 点击 查看 硬件支持 详情** DTU701 产品详情 DTU702 产品详情 DTU801 产品详情 DTU802 产品详情 DTU902 产品详情 G5501 产品详情 ARM dotnet 编程 dotNet使用TCP&#xff0c;可以使用Socket和TcpClient 、TcpListener类 2种&#xff0c;对于高级用户&…...

python基础面试题汇总(持续更新),冲击offer

目录 1.概念理解题python内置数据结构&#xff0c;哪些是不可变的python新式类和经典类的区别is和有什么区别Python中变量查找顺序python函数的参数是值传递还是引用传递python垃圾回收机制什么是闭包什么是装饰器&#xff0c;开发中用到举例如何实现只读属性Python中类方法、实…...

Java课题笔记~ AOP编程术语(掌握)

&#xff08;1&#xff09; 切面&#xff08;Aspect&#xff09; 切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面是通知&#xff08;Advice&#xff09;。实际就是对主业务逻辑的一种增强。 &#xff08;2&#xff09; 连接点&#xff08;Jo…...

暑假刷题第23天--8/6

3748. 递增子串 - AcWing题库 #include<iostream> #include<string> const int N200005; int a[N]; using namespace std; int main(){int t;cin>>t;for(int q1;q<t;q){int n;cin>>n;string s;cin>>s;int cnt1;a[1]1;for(int i2;i<n;i){i…...

ArcGIS API for JavaScript 4.x 教程(一) 显示一张地图

了解如何创建和显示带有基本地图图层的地图。 地图包含地理数据层。地图包含一个基本地图层&#xff0c;以及一个或多个数据层&#xff08;可选&#xff09;。可以使用地图视图显示地图的特定区域&#xff0c;并设置位置和缩放级别。 本教程将向您展示如何使用地形底图层创建和…...

Python-OpenCV中的图像处理

Python-OpenCV中的图像处理 颜色空间转换物体跟踪获取HSV的值几何变换图像缩放图像平移图像旋转仿射变换透视变换 图像阈值单阈值自适应阈值Otsus二值化 颜色空间转换 在 OpenCV 中有超过 150 中进行颜色空间转换的方法。但是你以后就会 发现我们经常用到的也就两种&#xff1…...

分清性能测试,负载测试,压力测试这三个的区别

做测试一年多来&#xff0c;虽然平时的工作都能很好的完成&#xff0c;但最近突然发现自己在关于测试的整体知识体系上面的了解很是欠缺&#xff0c;所以&#xff0c;在工作之余也做了一些测试方面的知识的补充。不足之处&#xff0c;还请大家多多交流&#xff0c;互相学习。 …...

前端架构师岗位的工作职责(合集)

前端架构师岗位的工作职责1 职责&#xff1a; 1.制定前端的标准和规范&#xff0c;并推广和应用&#xff0c;提高团队的开发效率; 2.前端架构的框架或核心模块的设计与实现; 3.在前端架构、设计与开发上对团队进行足够的指导; 4.在日常的系统设计与优化上与服务端团队紧密合…...

使用 Amazon ECS Anywhere 在边缘部署 Amazon IoT Greengrass

1.概述 亚马逊云科技提供了完备的IoT服务能力&#xff0c;涵盖设备服务、连接和控制服务以及云端分析服务&#xff0c;是快速构建安全可靠、可扩展的 IoT 平台的常见选择。Amazon IoT Greengrass 边缘运行时和云服务&#xff0c;可帮助您在设备上构建、部署和管理 IoT 应用。A…...

pytorch Stream 多流处理

CUD Stream https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#c-language-extensions 中指出在kenel的调用函数中最后一个可选参数表示该核函数处在哪个流之中。 - 参数Dg用于定义整个grid的维度和尺寸&#xff0c;即一个grid有多少个block。为dim3类型。…...

微信小程序选项卡切换(滑动切换,点击切换)

效果如下&#xff1a;可点击切换&#xff0c;滑动切换 代码如下 这个可以在项目用 index.wxml <view classtopTabSwiper><view classtab {{currentData 0 ? "tabBorer" : ""}} data-current "0" bindtapcheckCurrent>选项一&…...

安路FPGA的赋值报错——移位处理,加括号

authordaisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 在使用移位符号用来当作除以号使用时&#xff0c;发现如下问题 其中 cnt_8K 为偶数和奇数时输出的数据不一样 reg [10:0] cnt_8K; reg [10:0] ram1_addra; always(posedge clk_16M) begin if(ram_out_flag )begin if(…...

GO学习之 接口(Interface)

GO系列 1、GO学习之Hello World 2、GO学习之入门语法 3、GO学习之切片操作 4、GO学习之 Map 操作 5、GO学习之 结构体 操作 6、GO学习之 通道(Channel) 7、GO学习之 多线程(goroutine) 8、GO学习之 函数(Function) 9、GO学习之 接口(Interface) 文章目录 GO系列前言一、什么是…...

ansible常见模块的运用

ansible常见模块的运用 一&#xff1a;Ansible简介二&#xff1a;ansible 环境安装部署管理端安装 ansibleansible 目录结构配置主机清单配置密钥对验证 三&#xff1a;ansible 命令行模块1&#xff0e;command 模块在远程主机执行命令&#xff0c;不支持管道&#xff0c;重定向…...

合宙Air724UG LuatOS-Air script lib API--patch

patch Table of Contents patch patch.safeJsonDecode(s) (local函数 无法被外部调用) patch 模块功能&#xff1a;Lua补丁 patch.safeJsonDecode(s) (local函数 无法被外部调用) 封装自定义的json.decode接口 参数 名称 传入值类型 释义 s string json格式的字符串 返回值 t…...

pytorch求导

pytorch求导的初步认识 requires_grad tensor(data, dtypeNone, deviceNone, requires_gradFalse)requires_grad是torch.tensor类的一个属性。如果设置为True&#xff0c;它会告诉PyTorch跟踪对该张量的操作&#xff0c;允许在反向传播期间计算梯度。 x.requires_grad 判…...

Java基础异常详解

Java基础异常详解 文章目录 Java基础异常详解编译时异常&#xff08;Checked Exception&#xff09;&#xff1a;运行时异常&#xff08;Unchecked Exception&#xff09;: Java中的异常是用于处理程序运行时出现的错误或异常情况的一种机制。 异常本身也是一个类。 异常分为…...

vue3+vue-i18n 监听语言的切换

最近在用 vue3 做一个后台管理系统&#xff0c;之前是只考虑中文&#xff0c;现在加了个需求是多语言。 本来也不是太难的需求&#xff0c;但是我用的并不熟悉&#xff0c;并且除了页面展示不同的语言&#xff0c;需求是在切换语言的时候在几个页面中需要做出一些自定义的行为&…...

visual studio 2022更改主题为深色

visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中&#xff0c;选择 环境 -> 常规 &#xff0c;将其中的颜色主题改成深色 点击确定&#xff0c;更改完成...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

srs linux

下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935&#xff0c;SRS管理页面端口是8080&#xff0c;可…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云

目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

day36-多路IO复用

一、基本概念 &#xff08;服务器多客户端模型&#xff09; 定义&#xff1a;单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用&#xff1a;应用程序通常需要处理来自多条事件流中的事件&#xff0c;比如我现在用的电脑&#xff0c;需要同时处理键盘鼠标…...

在树莓派上添加音频输入设备的几种方法

在树莓派上添加音频输入设备可以通过以下步骤完成&#xff0c;具体方法取决于设备类型&#xff08;如USB麦克风、3.5mm接口麦克风或HDMI音频输入&#xff09;。以下是详细指南&#xff1a; 1. 连接音频输入设备 USB麦克风/声卡&#xff1a;直接插入树莓派的USB接口。3.5mm麦克…...

Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解

文章目录 1. 题目描述1.1 链表节点定义 2. 理解题目2.1 问题可视化2.2 核心挑战 3. 解法一&#xff1a;HashSet 标记访问法3.1 算法思路3.2 Java代码实现3.3 详细执行过程演示3.4 执行结果示例3.5 复杂度分析3.6 优缺点分析 4. 解法二&#xff1a;Floyd 快慢指针法&#xff08;…...