AudioFlinger与AudioPoliceManager初始化流程
AF/APF启动流程
在启动AudioSeriver服务的过程中会对启动AF/APF。main_audioserver.cpp有如下代码:
AudioFlinger::instantiate();AudioPolicyService::instantiate();
AF初始化流程
1.AudioFlinger::instantiate()
1.1 AudioFlinger构造函数
void AudioFlinger::instantiate() {sp<IServiceManager> sm(defaultServiceManager());sm->addService(String16(IAudioFlinger::DEFAULT_SERVICE_NAME),new AudioFlingerServerAdapter(new AudioFlinger()), false,IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT);
}
创建AudioFlingerServerAdapter对象,然后将AudioFlinger对象传入在AudioFlingerr中会调用onFirstRef
()函数。
AudioFlinger::AudioFlinger() {mDevicesFactoryHal = DevicesFactoryHalInterface::create();mEffectsFactoryHal = EffectsFactoryHalInterface::create();
}
在AudioFlinger的构造函数中会创建DevicesFactoryHalInterface和EffectsFactoryHalInterface对象,DevicesFactoryHalInterface对象会调用HIDL的getDevicesFactory()函数,EffectsFactoryHalInterface对象会调用HIDL的getEffectsFactory()函数。
DevicesFactoryHalInterface.h
:
sp<DevicesFactoryHalInterface> DevicesFactoryHalInterface::create() {return createPreferredImpl<DevicesFactoryHalInterface>("android.hardware.audio", "IDevicesFactory");
}
FactoryHalHidl.h
template <class Interface>
static sp<Interface> createPreferredImpl(const std::string& package, const std::string& interface) {return sp<Interface>{static_cast<Interface*>(detail::createPreferredImpl(package, interface))};
}
void* createPreferredImpl(const std::string& package, const std::string& interface) {for (auto version = detail::sAudioHALVersions; version != nullptr; ++version) {void* rawInterface = nullptr;if (hasHalService(package, *version, interface)&& createHalService(*version, interface, &rawInterface)) {return rawInterface;}}return nullptr;
}
const char* sAudioHALVersions[] = {"7.0","6.0","5.0","4.0",nullptr
};
函数hasHalService
功能为将"android.hardware.audio"与sAudioHALVersions拼接从7.0开始循环,从serviceManager中查找是否存在当前的服务比如android.hardware.audio@7.0::IDevicesFactory
当查到有相关的hidl接口那么就调用函数createHalService
createHalService
功能如下:
1.拼接libName =“libaudiohal@”+7.0+“.so” factoryFunctionName = “create”+IDevicesFactory
2.dlopen打开库libaudiohal@7.0.so"
3.dlsym获取函数createIDevicesFactory
4.调用函数createIDevicesFactory
5.返回rawInterface
6.将rawInterface强转成IDevicesFactory类型
DevicesFactoryHalHybrid.cpp
extern "C" __attribute__((visibility("default"))) void* createIDevicesFactory() {auto service = hardware::audio::CPP_VERSION::IDevicesFactory::getService();return service ? new CPP_VERSION::DevicesFactoryHalHybrid(service) : nullptr;
}
DevicesFactoryHalHybrid::DevicesFactoryHalHybrid(sp<IDevicesFactory> hidlFactory): mLocalFactory(new DevicesFactoryHalLocal()),mHidlFactory(new DevicesFactoryHalHidl(hidlFactory)) {
}
函数DevicesFactoryHalHybrid
功能如下:
1.创建DevicesFactoryHalLocal对象
2.创建DevicesFactoryHalHidl对象
3.将DevicesFactoryHalHidl对象传入DevicesFactoryHalLocal对象
1.2 AudioFlinger::onFirstRef解析
void AudioFlinger::onFirstRef()
{mMode = AUDIO_MODE_NORMAL;gAudioFlinger = this; // we are already refcounted, store into atomic pointer.mDevicesFactoryHalCallback = new DevicesFactoryHalCallbackImpl;mDevicesFactoryHal->setCallbackOnce(mDevicesFactoryHalCallback);
}
1.3总结
-
启动入口:
在AudioServer启动时,通过AudioFlinger::instantiate()和AudioPolicyService::instantiate()初始化AF和APF
AudioFlinger初始化流程: -
创建AudioFlingerServerAdapter,并传入AudioFlinger对象
在AudioFlinger构造函数中创建两个关键对象:
DevicesFactoryHalInterface:用于设备工厂
EffectsFactoryHalInterface:用于音效工厂 -
HAL层交互:
通过createPreferredImpl函数查找合适的HAL版本(从7.0到4.0)libaudiohal@7.0.so
使用hasHalService检查服务是否存在
通过createHalService加载对应的.so库并获取工厂函数 -
设备工厂创建:
最终创建DevicesFactoryHalHybrid对象
该对象包含两个子工厂:
DevicesFactoryHalLocal:本地工厂
DevicesFactoryHalHidl:HIDL接口工厂
APM初始化流程
1.AudioPolicyService::instantiate()
AudioPolicyService::AudioPolicyService(): BnAudioPolicyService(),mAudioPolicyManager(NULL),mAudioPolicyClient(NULL),mPhoneState(AUDIO_MODE_INVALID),mCaptureStateNotifier(false),mCreateAudioPolicyManager(createAudioPolicyManager),mDestroyAudioPolicyManager(destroyAudioPolicyManager) {
}
void AudioPolicyService::onFirstRef()
{{Mutex::Autolock _l(mLock);// start audio commands threadmAudioCommandThread = new AudioCommandThread(String8("ApmAudio"), this);// start output activity command threadmOutputCommandThread = new AudioCommandThread(String8("ApmOutput"), this);mAudioPolicyClient = new AudioPolicyClient(this);loadAudioPolicyManager();mAudioPolicyManager = mCreateAudioPolicyManager(mAudioPolicyClient);}// load audio processing modulessp<AudioPolicyEffects> audioPolicyEffects = new AudioPolicyEffects();sp<UidPolicy> uidPolicy = new UidPolicy(this);sp<SensorPrivacyPolicy> sensorPrivacyPolicy = new SensorPrivacyPolicy(this);{Mutex::Autolock _l(mLock);mAudioPolicyEffects = audioPolicyEffects;mUidPolicy = uidPolicy;mSensorPrivacyPolicy = sensorPrivacyPolicy;}
#ifdef ESWIN_AOSP_ENHANCEMENTstd::thread notifier([=]() {ALOGI("onFirstRef registerSelf start.");uidPolicy->registerSelf();sensorPrivacyPolicy->registerSelf();ALOGI("onFirstRef registerSelf end.");});notifier.detach();
#endif /* ESWIN_AOSP_ENHANCEMENT */
}
1.1 createAudioPolicyManager
static AudioPolicyInterface* createAudioPolicyManager(AudioPolicyClientInterface *clientInterface)
{AudioPolicyManager *apm = new AudioPolicyManager(clientInterface);status_t status = apm->initialize();if (status != NO_ERROR) {delete apm;apm = nullptr;}return apm;
}
在函数createAudioPolicyManager
中首先会调用AudioPolicyManager的构造函数,在构造函数中调用loadConfig这个函数的作用是解析audio_policy_configuration.xml。我们之后在分析,只需要知道之后时候xml解析结束。 然后调用initialize
1.1.1 AudioPolicyManager::initialize()
status_t AudioPolicyManager::initialize() {···onNewAudioModulesAvailableInt(nullptr /*newDevices*/);updateDevicesAndOutputs();···return status;
}
1.1.2 AudioPolicyManager::onNewAudioModulesAvailableInt()
这个函数很重要 做了很多工作
void AudioPolicyManager::onNewAudioModulesAvailableInt(DeviceVector *newDevices)
{//mHwModulesAll是xml文件中所有的mudule,mHwModules是已经加载的modulefor (const auto& hwModule : mHwModulesAll) {if (std::find(mHwModules.begin(), mHwModules.end(), hwModule) != mHwModules.end()) {continue;}//加载modulehwModule->setHandle(mpClientInterface->loadHwModule(hwModule->getName()));if (hwModule->getHandle() == AUDIO_MODULE_HANDLE_NONE) {ALOGW("could not open HW module %s", hwModule->getName());continue;}mHwModules.push_back(hwModule);// open all output streams needed to access attached devices// except for direct output streams that are only opened when they are actually// required by an app.// This also validates mAvailableOutputDevices listfor (const auto& outProfile : hwModule->getOutputProfiles()) {if (!outProfile->canOpenNewIo()) {ALOGE("Invalid Output profile max open count %u for profile %s",outProfile->maxOpenCount, outProfile->getTagName().c_str());continue;}else {ALOGE("valid Output profile max open count %u for profile %s",outProfile->maxOpenCount, outProfile->getTagName().c_str());}//hasSupportedDevices 都是 xml文件中可以根据route将device跟mixport配对上的if (!outProfile->hasSupportedDevices()) {ALOGW("Output profile contains no device on module %s", hwModule->getName());continue;}if ((outProfile->getFlags() & AUDIO_OUTPUT_FLAG_TTS) != 0) {mTtsOutputAvailable = true;}const DeviceVector &supportedDevices = outProfile->getSupportedDevices();DeviceVector availProfileDevices = supportedDevices.filter(mOutputDevicesAll);sp<DeviceDescriptor> supportedDevice = 0;// 保存defaultOutputDevice标签内名字和devicePort标签的tagName相同,如Speakerif (supportedDevices.contains(mDefaultOutputDevice)) {supportedDevice = mDefaultOutputDevice;} else {// choose first device present in profile's SupportedDevices also part of// mAvailableOutputDevices.if (availProfileDevices.isEmpty()) {continue;}supportedDevice = availProfileDevices.itemAt(0);}//mOutputDevicesAll对应xml中的attachedDevicesif (!mOutputDevicesAll.contains(supportedDevice)) {continue;}//经过重重过滤其实只剩下speakersp<SwAudioOutputDescriptor> outputDesc = new SwAudioOutputDescriptor(outProfile,mpClientInterface);audio_io_handle_t output = AUDIO_IO_HANDLE_NONE;//打开通路status_t status = outputDesc->open(nullptr, DeviceVector(supportedDevice),AUDIO_STREAM_DEFAULT,AUDIO_OUTPUT_FLAG_NONE, &output);if (status != NO_ERROR) {ALOGW("Cannot open output stream for devices %s on hw module %s",supportedDevice->toString().c_str(), hwModule->getName());continue;} else{ALOGE(" open output stream for devices %s on hw module %s",supportedDevice->toString().c_str(), hwModule->getName());}for (const auto &device : availProfileDevices) {// give a valid ID to an attached device once confirmed it is reachableif (!device->isAttached()) {device->attach(hwModule);mAvailableOutputDevices.add(device);device->setEncapsulationInfoFromHal(mpClientInterface);if (newDevices) newDevices->add(device);setEngineDeviceConnectionState(device, AUDIO_POLICY_DEVICE_STATE_AVAILABLE);}}if (mPrimaryOutput == nullptr &&outProfile->getFlags() & AUDIO_OUTPUT_FLAG_PRIMARY) {mPrimaryOutput = outputDesc;}if ((outProfile->getFlags() & AUDIO_OUTPUT_FLAG_DIRECT) != 0) {outputDesc->close();} else {addOutput(output, outputDesc);setOutputDevices(outputDesc,DeviceVector(supportedDevice),true,0,NULL);}}// open input streams needed to access attached devices to validate// mAvailableInputDevices list···}
}
我们来总结一下onNewAudioModulesAvailableInt
的功能:
- 遍历所有硬件模块(mHwModulesAll),检查是否已加载。
- 从hwmodel中找到符合要求的outProfile,这一过程涉及四个变量
- outProfile 这个变量是mixport
- supportedDevice xml文件中可以根据route将device跟mixport配对上的
- defaultOutputDevice xml中defaultOutputDevice对应的device
- mOutputDevicesAll 对应xml中的attachedDevices
- 遍历所有module下面的所有的outProfile,根据OutProfile找到支持的device,返回一个supportDevices ;
- 通过mOutputDevicesAll(xml文件中的attrachDevices)过滤出有效的availProfileDevices;
- 判断包不包含默认设备mDefaultOutputDevice(xml文件中的defaultOutputDevice),如果包含,设置为默认设备;如果不包含,就设置为第一个设备。
- 只设置支持当前输出设备的stream
接下来我们分别loadHwModule
、open
1.1.3 AudioPolicyManager::loadHwModule
loadHwModule
会调用到AudioFlinger中的loadHwModule
audio_module_handle_t AudioFlinger::loadHwModule(const char *name)
{Mutex::Autolock _l(mLock);AutoMutex lock(mHardwareLock);return loadHwModule_l(name);
}
audio_module_handle_t AudioFlinger::loadHwModule_l(const char *name)
{for (size_t i = 0; i < mAudioHwDevs.size(); i++) {if (strncmp(mAudioHwDevs.valueAt(i)->moduleName(), name, strlen(name)) == 0) {ALOGW("loadHwModule() module %s already loaded", name);return mAudioHwDevs.keyAt(i);}}sp<DeviceHalInterface> dev;int rc = mDevicesFactoryHal->openDevice(name, &dev);····AudioHwDevice *audioDevice = new AudioHwDevice(handle, name, dev, flags);if (strcmp(name, AUDIO_HARDWARE_MODULE_ID_PRIMARY) == 0) {mPrimaryHardwareDev = audioDevice;mHardwareStatus = AUDIO_HW_SET_MODE;mPrimaryHardwareDev->hwDevice()->setMode(mMode);mHardwareStatus = AUDIO_HW_IDLE;}mAudioHwDevs.add(handle, audioDevice);}
DevicesFactoryHalHybrid::openDevice
status_t DevicesFactoryHalHybrid::openDevice(const char *name, sp<DeviceHalInterface> *device) {if (mHidlFactory != 0 && strcmp(AUDIO_HARDWARE_MODULE_ID_A2DP, name) != 0 &&strcmp(AUDIO_HARDWARE_MODULE_ID_HEARING_AID, name) != 0) {return mHidlFactory->openDevice(name, device);}return mLocalFactory->openDevice(name, device);
}
这里要注意如果是A2DP就走本地的,如果不是走HIDL的。这里我们是speaker所以走mHidlFactory
status_t DevicesFactoryHalHidl::openDevice(const char *name, sp<DeviceHalInterface> *device) {auto factories = copyDeviceFactories();if (factories.empty()) return NO_INIT;status_t status;auto hidlId = idFromHal(name, &status);if (status != OK) return status;Result retval = Result::NOT_INITIALIZED;for (const auto& factory : factories) {Return<void> ret = factory->openDevice(hidlId,[&](Result r, const sp<IDevice>& result) {retval = r;if (retval == Result::OK) {*device = new DeviceHalHidl(result);}});}return BAD_VALUE;
}
DevicesFactory::openDevice
Return<void> DevicesFactory::openDevice(const hidl_string& moduleName, openDevice_cb _hidl_cb) {if (moduleName == AUDIO_HARDWARE_MODULE_ID_PRIMARY) {return openDevice<PrimaryDevice>(moduleName.c_str(), _hidl_cb);}return openDevice(moduleName.c_str(), _hidl_cb);
}
解析来的流程如图所示
1.1.4 AudioFlinger::loadHwModule总结
通过传参hwModule->getName(),如primary,通过拼接查询找到合适的audio.primary.xxx.so。,通过dlopen加载so,dlsym获取对应的对象,通过回调将对象返回。最后为AudioFlinger::loadHwModule_l中 sp dev赋值
1.2.1 SwAudioOutputDescriptor::open
status_t SwAudioOutputDescriptor::open(const audio_config_t *config,const DeviceVector &devices,audio_stream_type_t stream,audio_output_flags_t flags,audio_io_handle_t *output)
{···status_t status = mClientInterface->openOutput(mProfile->getModuleHandle(),output,&lConfig,device,&mLatency,mFlags);····
}status_t AudioFlinger::openOutput(const media::OpenOutputRequest& request,media::OpenOutputResponse* response)
{audio_io_handle_t output;···sp<ThreadBase> thread = openOutput_l(module, &output, &config, deviceType, address, flags);if (thread != 0) {if ((flags & AUDIO_OUTPUT_FLAG_MMAP_NOIRQ) == 0) {PlaybackThread *playbackThread = (PlaybackThread *)thread.get();latencyMs = playbackThread->latency();// notify client processes of the new output creationplaybackThread->ioConfigChanged(AUDIO_OUTPUT_OPENED);// the first primary output opened designates the primary hw device if no HW module// named "primary" was already loaded.AutoMutex lock(mHardwareLock);if ((mPrimaryHardwareDev == nullptr) && (flags & AUDIO_OUTPUT_FLAG_PRIMARY)) {ALOGI("Using module %d as the primary audio interface", module);.mPrimaryHardwareDev = playbackThread->getOutput()->audioHwDev;mHardwareStatus = AUDIO_HW_SET_MODE;mPrimaryHardwareDev->hwDevice()->setMode(mMode);mHardwareStatus = AUDIO_HW_IDLE;}} else {MmapThread *mmapThread = (MmapThread *)thread.get();mmapThread->ioConfigChanged(AUDIO_OUTPUT_OPENED);}response->output = VALUE_OR_RETURN_STATUS(legacy2aidl_audio_io_handle_t_int32_t(output));response->config = VALUE_OR_RETURN_STATUS(legacy2aidl_audio_config_t_AudioConfig(config));response->latencyMs = VALUE_OR_RETURN_STATUS(convertIntegral<int32_t>(latencyMs));response->flags = VALUE_OR_RETURN_STATUS(legacy2aidl_audio_output_flags_t_int32_t_mask(flags));return NO_ERROR;}}sp<AudioFlinger::ThreadBase> AudioFlinger::openOutput_l(audio_module_handle_t module,audio_io_handle_t *output,audio_config_t *config,audio_devices_t deviceType,const String8& address,audio_output_flags_t flags)
{//获取合适的AudioHwDevice,这个AudioHwDevice是在loadHwModule_l中创建的AudioHwDevice *outHwDev = findSuitableHwDev_l(module, deviceType);if (outHwDev == NULL) {return 0;}if (*output == AUDIO_IO_HANDLE_NONE) {*output = nextUniqueId(AUDIO_UNIQUE_ID_USE_OUTPUT);} else {// Audio Policy does not currently request a specific output handle.// If this is ever needed, see openInput_l() for example code.ALOGE("openOutput_l requested output handle %d is not AUDIO_IO_HANDLE_NONE", *output);return 0;}mHardwareStatus = AUDIO_HW_OUTPUT_OPEN;···AudioStreamOut *outputStream = NULL;status_t status = outHwDev->openOutputStream(&outputStream,*output,deviceType,flags,config,address.string());mHardwareStatus = AUDIO_HW_IDLE;if (status == NO_ERROR) {if (flags & AUDIO_OUTPUT_FLAG_MMAP_NOIRQ) {sp<MmapPlaybackThread> thread =new MmapPlaybackThread(this, *output, outHwDev, outputStream, mSystemReady);mMmapThreads.add(*output, thread);ALOGD("openOutput_l() created mmap playback thread: ID %d thread %p",*output, thread.get());return thread;} else {sp<PlaybackThread> thread;if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {thread = new OffloadThread(this, outputStream, *output, mSystemReady);ALOGD("openOutput_l() created offload output: ID %d thread %p",*output, thread.get());} else if ((flags & AUDIO_OUTPUT_FLAG_DIRECT)|| !isValidPcmSinkFormat(config->format)|| !isValidPcmSinkChannelMask(config->channel_mask)) {thread = new DirectOutputThread(this, outputStream, *output, mSystemReady);ALOGD("openOutput_l() created direct output: ID %d thread %p",*output, thread.get());} else {//创建线程并且key是outputthread = new MixerThread(this, outputStream, *output, mSystemReady);ALOGD("openOutput_l() created mixer output: ID %d thread %p",*output, thread.get());}mPlaybackThreads.add(*output, thread);struct audio_patch patch;mPatchPanel.notifyStreamOpened(outHwDev, *output, &patch);if (thread->isMsdDevice()) {thread->setDownStreamPatch(&patch);}return thread;}}return 0;
}
1.2.2 openOutputStream解析
status_t AudioHwDevice::openOutputStream(AudioStreamOut **ppStreamOut,audio_io_handle_t handle,audio_devices_t deviceType,audio_output_flags_t flags,struct audio_config *config,const char *address)
{struct audio_config originalConfig = *config;AudioStreamOut *outputStream = new AudioStreamOut(this, flags);status_t status = outputStream->open(handle, deviceType, config, address);···*ppStreamOut = outputStream;return status;
}status_t AudioStreamOut::open(audio_io_handle_t handle,audio_devices_t deviceType,struct audio_config *config,const char *address)
{sp<StreamOutHalInterface> outStream;audio_output_flags_t customFlags = (config->format == AUDIO_FORMAT_IEC61937)? (audio_output_flags_t)(flags | AUDIO_OUTPUT_FLAG_IEC958_NONAUDIO): flags;int status = hwDev()->openOutputStream(handle,deviceType,customFlags,config,address,&outStream);if (status == NO_ERROR) {stream = outStream;mHalFormatHasProportionalFrames = audio_has_proportional_frames(config->format);status = stream->getFrameSize(&mHalFrameSize);LOG_ALWAYS_FATAL_IF(status != OK, "Error retrieving frame size from HAL: %d", status);LOG_ALWAYS_FATAL_IF(mHalFrameSize <= 0, "Error frame size was %zu but must be greater than"" zero", mHalFrameSize);}return status;
}status_t DeviceHalHidl::openOutputStream(audio_io_handle_t handle,audio_devices_t deviceType,audio_output_flags_t flags,struct audio_config *config,const char *address,sp<StreamOutHalInterface> *outStream) {Return<void> ret = mDevice->openOutputStream(handle, hidlDevice, hidlConfig, hidlFlags,
#if MAJOR_VERSION >= 4{} /* metadata */,
#endif[&](Result r, const sp<IStreamOut>& result, const AudioConfig& suggestedConfig) {retval = r;if (retval == Result::OK) {*outStream = new StreamOutHalHidl(result);}HidlUtils::audioConfigToHal(suggestedConfig, config);});return processReturn("openOutputStream", ret, retval);
}
最终会调用到audio_hw.c中的函数adev_open_output_stream
static int adev_open_output_stream(struct audio_hw_device *dev,audio_io_handle_t handle,audio_devices_t devices,audio_output_flags_t flags,struct audio_config *config,struct audio_stream_out **stream_out,const char *address __unused) {*stream_out = NULL;struct esw_stream_out *out= (struct esw_stream_out *)calloc(1, sizeof(struct esw_stream_out));if (!out) {return -ENOMEM;}out->stream.common.get_sample_rate = out_get_sample_rate;out->stream.common.set_sample_rate = out_set_sample_rate;out->stream.common.get_buffer_size = out_get_buffer_size;out->stream.common.get_channels = out_get_channels;out->stream.common.get_format = out_get_format;out->stream.common.set_format = out_set_format;out->stream.common.standby = out_standby;out->stream.common.dump = out_dump;out->stream.common.set_parameters = out_set_parameters;out->stream.common.get_parameters = out_get_parameters;out->stream.common.add_audio_effect = out_add_audio_effect;out->stream.common.remove_audio_effect = out_remove_audio_effect;out->stream.get_latency = out_get_latency;out->stream.set_volume = out_set_volume;out->stream.write = out_write;out->stream.get_render_position = out_get_render_position;out->stream.pause = out_pause;out->stream.resume = out_resume;out->stream.drain = out_drain;out->stream.flush = out_flush;out->stream.get_next_write_timestamp = out_get_next_write_timestamp;out->sample_rate = config->sample_rate;out->paused = false;
···
}
到此我们已经已经打开了一个输出通道,音频数据流从这个通道流向某个设备,这个通道支持的sample_rate、format、channel是从xml解析出来的。
在AudioOutputDescriptor
构造函数中调用PolicyAudioPort::pickChannelMask
解析上述参数规则如下:
对于采样格式,精度越高优先级越高;对于通道掩码(通道数),如果是直接输出 (Direct Output),则通道数越小优先级越高,否则,通道数越大优先级越高;对于采样率,如果是直接输出 (Direct Output),则采样率越小优先级越高,否则,采样率越大优先级越高。
1.2.3 open流程总结
- 设备打开流程:
从SwAudioOutputDescriptor
发起请求,最终由AudioFlinger
处理。
通过AudioHwDevice
打开硬件输出流。
根据需求创建不同类型的音频线程。 - 线程类型:
MixerThread:默认混音线程。
DirectOutputThread:直接输出线程。
OffloadThread:压缩音频输出线程。
MmapPlaybackThread:低延迟 MMAP 线程。 - 主设备设置:
如果打开的是主输出设备,会将其设置为主硬件设备,并更新硬件状态。 - 延迟与通知:
计算并返回输出设备的延迟时间。
通知客户端输出设备已打开。
参考文章
https://blog.csdn.net/qq_42364999/article/details/146414847?spm=1001.2014.3001.5501
相关文章:
AudioFlinger与AudioPoliceManager初始化流程
AF/APF启动流程 在启动AudioSeriver服务的过程中会对启动AF/APF。main_audioserver.cpp有如下代码: AudioFlinger::instantiate();AudioPolicyService::instantiate();AF初始化流程 1.AudioFlinger::instantiate() 1.1 AudioFlinger构造函数 void AudioFlinger:…...
Keepalive+LVS+Nginx+NFS高可用架构
KeepaliveLVSNginxNFS高可用架构 1. NFS 业务服务器(192.168.98.138)2. Web服务集群(搭建RS服务器)开机自启动自动挂载配置nginx(为了区分Web1与Web2访问的文件内容) 3. LVS主机(Keepalivedlvs&…...
SpringBoot分布式项目订单管理实战:Mybatis最佳实践全解
一、架构设计与技术选型 典型分布式订单系统架构: [网关层] → [订单服务] ←→ [分布式缓存]↑ ↓ [用户服务] [支付服务]↓ ↓ [MySQL集群] ← [分库分表中间件]技术栈组合: Spring Boot 3.xMybatis-Plus 3.5.xShardingSpher…...
ctfshow WEB web8
首先确定注入点,输入以下payload使SQL恒成立 ?id-1/**/or/**/true 再输入一下payload 使SQL恒不成立 ?id-1/**/or/**/false 由于SQL恒不成立, 数据库查询不到任何数据, 从而导致页面空显示 由以上返回结果可知,该页面存在SQL注入,注入点…...
当AI代写作业成为常态:重构智能时代的教育范式
2023年春季,某重点高中教师发现全班35%的作文呈现相似AI生成特征;同年国际数学竞赛中,参赛选手使用AI解题引发伦理争议。当GPT-4在SAT考试中取得1520分,当Claude3能撰写专业学术论文,教育领域正面临百年未有之大变革。这场技术革命倒逼我们重新思考:在AI能完成知识性任务…...
基于杜鹃鸟鲶鱼优化(Cuckoo Catfish Optimizer,CCO)算法的多个无人机协同路径规划(可以自定义无人机数量及起始点),MATLAB代码
一、杜鹃鸟鲶鱼优化算法 杜鹃鸟鲶鱼优化(Cuckoo Catfish Optimizer,CCO)算法模拟了杜鹃鸟鲶鱼的搜索、捕食和寄生慈鲷行为。该算法的早期迭代侧重于执行多维包络搜索策略和压缩空间策略,并结合辅助搜索策略来有效限制慈鳔的逃逸空…...
Ubuntu 22.04.5 LTS 设置时间同步 ntp
提示:文章为操作记录,以备下次使用 文章目录 前言一、设置ntp1.1替换国内源1.2 更新源&安装1.3 验证 前言 设置时间同步,环境版本 # cat /etc/os-release PRETTY_NAME"Ubuntu 22.04.5 LTS" NAME"Ubuntu" VERSION_…...
搭建前端环境和后端环境
搭建前端环境 ①、安装vscode,并安装相应的插件工具 ②、安装node.js,可以选择当前版本,或者其他版本 ③、创建工作区 创建一个空文件夹,然后通过vscode工具打开,保存为后缀名为.code-workspace ④、从gitee…...
【VirtualBox 安装 Ubuntu 22.04】
网上教程良莠不齐,有一个CSDN的教程虽然很全面,但是截图冗余,看蒙了给我,这里记录一个整洁的教程链接。以备后患。 下载安装全流程 UP还在记录生活,看的我好羡慕,呜呜。 [VirtualBox网络配置超全详解]&am…...
好用的Markdown阅读编辑器Typora破解记录
Typora破解 一、下载Typora二、安装Typora三、破解Typora 😀 记录一下Typora破解记录,怕不常用忘记咯,感觉自己现在的脑子就像我的肠子一样,刚装进去就么得了。。。😔 Typroa算是用起来很舒服的Markdown阅读器了吧&am…...
系统与网络安全------Windows系统安全(1)
资料整理于网络资料、书本资料、AI,仅供个人学习参考。 用户账号基础 本地用户账号基础 用户账号概述 用户账号用来记录用户的用户名和口令、隶属的组等信息 每个用户账号包含唯一的登录名和对应的密码 不同的用户身份拥有不同的权限 操作系统根据SID识别不同…...
使用 Docker Compose 在单节点部署多容器
Docker Compose 是什么 Docker Compose 是一个用于运行多容器应用的工具, 通过一个docker-compose.yml文件, 配置应用的服务、网络和卷,然后使用简单的命令启动或停止所有服务 为什么需要 Docker Compose 当你有一个包含多个相互依赖的容器应用时,手动…...
CSS中的em,rem,vm,vh详解
一:em 和 rem 是两种相对单位,它们常用于 CSS 中来设置尺寸、字体大小、间距等,主要用于更灵活和响应式的布局设计。它们与像素(px)不同,不是固定的,而是相对于其他元素的尺寸来计算的。 1. em …...
MQTT之重复消息(5、TCP重连和MQTT重连)
目录 1. TCP 协议层的重传(原生机制) 2. 触发 TCP 重传的具体场景 3、TCP 重传的关键参数(了解) 第一、重传超时(RTO - Retransmission Timeout) 第二、重传次数 第三、累计时间 vs 本次 RTO 的区别 第四.常见问题解答 第…...
Ground Truth(真实标注数据):机器学习中的“真相”基准
Ground Truth:机器学习中的“真相”基准 文章目录 Ground Truth:机器学习中的“真相”基准引言什么是Ground Truth?Ground Truth的重要性1. 模型训练的基础2. 模型评估的标准3. 模型改进的指导 获取Ground Truth的方法1. 人工标注2. 众包标注…...
Android学习总结之通信篇
一、Binder跨进程通信的底层实现细节(挂科率35%) 高频问题:“Binder如何实现一次跨进程方法调用?” 候选人常见错误: 仅回答“通过Binder驱动传输数据”,缺乏对内存映射和线程调度的描述混淆Binde…...
自动化发布工具CI/CD实践Jenkins部署与配置教程
1. 前言背景 其实一直想把jenkins 的笔记整理下,介于公司这次升级jenkins2.0 ,根据自己部署的一些经验,我把它整理成笔记。 之前我们的jenkins1.0 时代 还一直停留在 free style 或者 maven 风格的项目,随着项目的日益增多&#x…...
kubernet在prometheus+alertmanager+grafana框架下新增部署loki模块
整个框架拓扑图 #mermaid-svg-OK7jgNZ2I7II8nJx {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-OK7jgNZ2I7II8nJx .error-icon{fill:#552222;}#mermaid-svg-OK7jgNZ2I7II8nJx .error-text{fill:#552222;stroke:#552…...
Ubuntu下UEFI安全启动安装Nvdia驱动
简介 众所周知,Ubuntu默认使用Nouveau开源驱动,其性能受限,因此我们需要安装Nvidia专用驱动。 安装专用驱动的一般方法非常简单,只需要sudo ubuntu-drivers devices && sudo ubuntu-drivers autoinstall即可,…...
Java多线程与高并发专题—— CyclicBarrier 和 CountDownLatch 有什么异同?
引入 上一篇我们了解CountDownLatch的原理和常见用法,在CountDownLatch的源码注释中,有提到: 另一种典型用法是将一个问题分解为 N 个部分,用一个Runnable描述每个部分,该Runnable执行相应部分的任务并对闭锁进行倒计…...
【深度学习】不管理论,入门从手写数字识别开始
1. 环境安装 学习深度学习,开发语言是Python。Python开发工具有很多。其中 anaconda vscode的Python开发环境很好用,建议使用这个组合。 编写手写数字识别测试代码,需要在使用Anaconda安装以下4个库: NumpyScipymatplotlibsci…...
Docker使用ubuntu
1. 更换源 sudo nano /etc/docker/daemon.json //daemon.conf查找最新可用的源1、2,手动查找会不断更新! 1.1 添加DNS sudo nano /etc/resolv.confnameserver 8.8.8.8 nameserver 8.8.4.4 2. 修改配置文件后重新加载 sudo systemctl daemon-relo…...
Windows 系统下多功能免费 PDF 编辑工具详解
IceCream PDF Editor是一款极为实用且操作简便的PDF文件编辑工具,它完美适配Windows操作系统。其用户界面设计得十分直观,哪怕是初次接触的用户也能快速上手。更为重要的是,该软件具备丰富多样的强大功能,能全方位满足各类PDF编辑…...
影响HTTP网络请求的因素
影响 HTTP 网络请求的因素 1. 带宽 2. 延迟 浏览器阻塞:浏览器会因为一些原因阻塞请求,浏览器对于同一个域名,同时只能有4个连接(这个根据浏览器内核不同可能会有所差异),超过浏览器最大连接数限制&…...
OpenGL —— 流媒体播放器 - ffmpeg解码rtsp流,opengl渲染yuv视频(附源码,glfw+glad)
🔔 OpenGL 相关技术、疑难杂症文章合集(掌握后可自封大侠 ⓿_⓿)(记得收藏,持续更新中…) 效果 说明 FFMpeg和OpenGL作为两大技术巨头,分别在视频解码和图形渲染领域发挥着举足轻重的作用。本文将综合两者实战视频播放器,大概技术流程为:ffmpeg拉取rtsp协议视频流,并…...
Java + LangChain 实战入门,开发大语言模型应用!
在 Baeldung 上看到了一篇介绍基于 Java LangChain 开发大语言模型应用的基础入门文章,写的非常不错,非常适合初学者。于是,我抽空翻译了一下。 原文地址:https://www.baeldung.com/java-langchain-basics翻译: Java…...
【目标检测】【深度学习】【Pytorch版本】YOLOV1模型算法详解
【目标检测】【深度学习】【Pytorch版本】YOLOV1模型算法详解 文章目录 【目标检测】【深度学习】【Pytorch版本】YOLOV1模型算法详解前言YOLOV1的模型结构YOLOV1模型的基本执行流程YOLOV1模型的网络参数YOLOV1模型的训练方式 YOLOV1的核心思想前向传播阶段网格单元(grid cell)…...
软考《信息系统运行管理员》- 6.2 信息系统硬件的安全运维
硬件安全运行的概念 硬件安全运行的含义是保护支撑信息系统业务活动的信息系统硬件资产免遭自然灾害、人 为因素及各种计算机犯罪行为导致的破坏。硬件安全通常包括环境安全、设备安全和介质安全。 硬件安全运行的影响因素 硬件安全运行的影响因素主要有: (1)自然…...
SQL 视图
SQL 视图 引言 SQL(结构化查询语言)视图是数据库管理系统中的一种重要概念。它提供了一个虚拟的表,该表由一个或多个基本表的数据组成,用户可以通过视图查询数据,而不需要直接操作基本表。本文将详细介绍SQL视图的定…...
Chrome 开发环境快速屏蔽 CORS 跨域限制!
Chrome 开发环境快速屏蔽 CORS 跨域限制【详细教程】 ❓ 为什么需要临时屏蔽 CORS? 在前后端开发过程中,我们经常会遇到 跨域请求被浏览器拦截 的问题。例如,你在 http://localhost:3000 调用 https://api.example.com 时,可能会…...
数值稳定性 + 模型初始化和激活函数
数值稳定性 神经网络的梯度 考虑如下有 d 层的神经网络 h t f t ( h t − 1 ) and y ℓ ∘ f d ∘ … ∘ f 1 ( x ) \mathbf{h}^t f_t(\mathbf{h}^{t-1}) \quad \text{and} \quad y \ell \circ f_d \circ \ldots \circ f_1(\mathbf{x}) htft(ht−1)andyℓ∘fd∘…∘…...
【消息队列】几个mq组件的对比: redis stream/rabbitmq/rocketmq/kafka
1. 消息队列 几个组件: Redis Stream:适用于对性能要求高、可靠性要求不高的场景Rocket MQ:可靠性高,性能优秀,但官方对 go 不太友好,sdk 缺少很多功能支持Rabbit MQ:性能适中,使用…...
TCP协议与wireshark抓包分析
一、tcp协议格式 1. 源端口号 : 发送方使用的端口号 2. 目的端口号 : 接收方使用的端口号 3. 序号: 数据包编号 , tcp 协议为每个数据都设置编号,用于确认是否接收到相应的包 4. 确认序列号 : 使用 tcp 协议接收到数据包,…...
深度学习四大核心架构:神经网络(NN)、卷积神经网络(CNN)、循环神经网络(RNN)与Transformer全概述
目录 📂 深度学习四大核心架构 🌰 知识点概述 🧠 核心区别对比表 ⚡ 生活化案例理解 🔑 选型指南 📂 深度学习四大核心架构 第一篇: 神经网络基础(NN) 🌰 知识点概述…...
springcloud 整合 Redis_Redisson
springcloud 整合 Redis 、Redisson Redis-x64-5.0.14.1 版本 https://blog.csdn.net/wojiubugaosuni12/article/details/134452665 https://www.123pan.com/s/8EpMjv-MTjBv.html spring cloud 整合 redis Redis 5.0.14 Springcloud 2021.0.5 Redis启动 redis-server.exe 点击…...
Windows模仿Mac大小写切换, 中英文切换
CapsLock 功能优化脚本部署指南 部署步骤 第一步:安装 AutoHotkey v2 访问 AutoHotkey v2 官网下载并安装最新版本安装时勾选 "Add Compile Script to context menus" 第二步:部署脚本 直接运行 (调试推荐) 新建文本文件,粘贴…...
基于Spring Boot的木里风景文化管理平台的设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
【银河麒麟系统常识】命令:uname -m(查看系统架构)
命令: uname -m 功能 常用的 Linux/Unix 终端命令,用于显示当前系统的硬件架构; 返回 返回系统的CPU架构类型,用于判断软件兼容性; 输出结果架构说明常见设备x86_64Intel/AMD 64位 CPU主流 PC、服务器aarch64ARM 64位 …...
网络原理-TCP/IP
网络原理学习笔记:TCP/IP 核心概念 本文是我在学习网络原理时整理的笔记,主要涵盖传输层、网络层和数据链路层的核心协议和概念,特别是 TCP, UDP, IP, 和以太网。 一、传输层 (Transport Layer) 传输层负责提供端到端(进程到进…...
【银河麒麟高级服务器操作系统 】虚拟机运行数据库存储异常现象分析及处理全流程
更多银河麒麟操作系统产品及技术讨论,欢迎加入银河麒麟操作系统官方论坛 https://forum.kylinos.cn 了解更多银河麒麟操作系统全新产品,请点击访问 麒麟软件产品专区:https://product.kylinos.cn 开发者专区:https://developer…...
AI-Sphere-Butler之如何使用腾讯云ASR语音识别服务
环境: AI-Sphere-Butler WSL2 英伟达4070ti 12G Win10 Ubuntu22.04 腾讯云ASR 问题描述: AI-Sphere-Butler之如何使用腾讯云ASR语音识别服务,本地硬件配置不高的情况,建议使用云服务商的ASR 解决方案: 1.登…...
Dify 0.15.3版本 本地部署指南
目录 背景 一、单机部署机器配置最低要求 二、系统Python环境安装 安装需要的python依赖 使用pyenv官方安装脚本 安装poetry 三、中间件部署 PostgreSQL本地部署 添加PG官方仓库 安装pg 16 检查pg版本 修改密码为dify默认 创建数据库dify 安装pg vector插件 修改…...
全书测试:《C++性能优化指南》
以下20道多选题和10道设计题, 用于本书的测试。 以下哪些是C性能优化的核心策略?(多选) A) 优先优化所有代码段 B) 使用更高效的算法 C) 减少内存分配次数 D) 将所有循环展开 关于字符串优化,正确的措施包括ÿ…...
Oracle数据库数据编程SQL<递归函数详解>
递归函数是一种在函数体内直接或间接调用自身的函数。这种函数通过将复杂问题分解为更小的相同问题来解决特定类型的编程任务。 目录 一、递归函数基本概念 1. 递归定义 2. 递归工作原理 二、递归函数示例 1. 经典阶乘函数 2. 斐波那契数列 3. 计算数字位数 三、递归查…...
Burp Suite从入门到实战之配置启动
目录 1.Burp Suite配置启动 1.1安装Burp Suite jar包 1.2JDK,JDK包含JRE(Java运行时环境) 1.2.1配置JDK11环境变量配置 1.2.2系统变量里添加JAVA_HOME编辑 1.2.3找到Path变量进行编辑添加bin 1.2.4命令行查看是否配置成功 1.3激活j…...
【力扣hot100题】(016)缺失的第一个正数
题目里这么多条条框框……先不按条条框框做了两下。 第一个思路:你不仁我不义,先排序后遍历(时间不符题意) class Solution { public:int firstMissingPositive(vector<int>& nums) {sort(nums.begin(),nums.end());i…...
(undone) MIT6.824 Lecture 02 - RPC and Threads
知乎专栏:https://zhuanlan.zhihu.com/p/641105196 原视频:https://www.bilibili.com/video/BV16f4y1z7kn?spm_id_from333.788.videopod.episodes&vd_source7a1a0bc74158c6993c7355c5490fc600&p2 看知乎专栏 一、Why we choose go?…...
红宝书第二十一讲:详解JavaScript的模块化(CommonJS与ES Modules)
红宝书第二十一讲:详解JavaScript的模块化(CommonJS与ES Modules) 资料取自《JavaScript高级程序设计(第5版)》。 查看总目录:红宝书学习大纲 一、模块化的意义:分而治之 模块化解决代码依赖混…...
输入百分比校验(数字非负数保留2位不四舍五入)
场景用于输入百分比,限制只能输入非负数,保留2位小数,且不四舍五入 以下举例环境 vue2 element-ui 请自行根据实际场景使用 html部分 <el-inputv-model"item.percentage"placeholder"请输入"maxlength"5"…...
Python----机器学习(KNN:决策边界,决策边界计算,交叉验证步骤)
一、KNN算法简介 1.1、定义 KNN(K-Nearest Neighbor)算法是一种基于实例的学习方法,通过测量数据点之间的距离进行分类或回归分析。它是一种简单易懂的多分类技术,依赖于距离最近的邻居来推断数据点的类别或数值,为许…...