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:…...
网路传输层UDP/TCP
一、端口号 1.端口号 1.1 五元组 端口号(port)标识了一个主机上进行通信的不同的应用程序. 如图所示, 在一个机器上运行着许多进程, 每个进程使用的应用层协议都不一样, 比如FTP, SSH, SMTP, HTTP等. 当主机接收到一个报文中, 网络层一定封装了一个目的ip标识我这台主机, …...
Python大数据处理 基本的编程方法
目录 一、实验目的 二、实验要求 三、实验代码 四、实验结果 五、实验体会 一、实验目的 体会基本的python编程方法;学习python中的各类函数;了解python读取与写入文件的方法。 二、实验要求 输入2000年后的某年某月某日,判断这一天是…...
STM32F103_LL库+寄存器学习笔记06 - 梳理串口与串行发送“Hello,World“
导言 USART是嵌入式非常重要的通讯方式,它的功能强大、灵活性高且用途广泛。只停留在HAL库层面上用USART只能算是入门,要加深对USART的理解,必须从寄存器层面入手。接下来,先从最简单的USART串行发送开始。 另外,在接…...
硬件基础--14_电功率
电功率 电功率:指电流在单位时间内做的功(表示用电器消耗电能快慢的一个物理量)。 单位:瓦特(W),简称瓦。 公式:PUI(U为电压,单位为V,i为电流,单位为A,P为电功率,单位为W)。 单位换算:进位为1000ÿ…...
【C#语言】C#文件操作实战:动态路径处理与安全写入
文章目录 ⭐前言⭐一、场景痛点⭐二、完整实现代码⭐三、关键技术解析🌟1、动态路径处理🌟2、智能目录创建🌟3、安全的文件写入 ⭐四、进阶扩展方案🌟1、用户自定义路径选择🌟2、异常处理增强🌟3、异步写入…...
Vue.js 完全指南:从入门到精通
1. Vue.js 简介 1.1 什么是 Vue.js? Vue.js(通常简称为 Vue)是一个用于构建用户界面的渐进式 JavaScript 框架。所谓"渐进式",意味着 Vue 的设计是由浅入深的,你可以根据自己的需求选择使用它的一部分或全部功能。 Vue 最初由尤雨溪(Evan You)在 2014 年创…...
在Git仓库的Readme上增加目录页
一般在编写Readme时想要增加像文章那样的目录,方便快速跳转,但是Markdown语法并没有提供这样的方法,但是可以通过超链接结合锚点的方式来实现,如下图是我之前一个项目里写的Readme: 例如有下面几个Readme内容ÿ…...
C# SolidWorks 二次开发 -各种菜单命令增加方式
今天给大家讲一讲solidworks中各种菜单界面,如下图,大概有13处,也许还不完整哈。 1.CommandManager选项卡2.下拉选项卡3.菜单栏4.下级菜单5.浮动工具栏6.快捷方式工具栏7.FeatureManager工具栏区域8.MontionManager区域 ModelView?9.任务窗…...
分布式架构-Spring技术如何能实现分布式事务
在Spring技术栈中实现分布式事务,可通过多种成熟方案实现跨服务或跨数据库的事务一致性管理。以下是主要实现方式及技术要点: 一、基于Seata框架的AT模式 核心组件 TC (Transaction Coordinator):全局事务协调器(独立部署…...
【RocketMQRocketMQ Dashbord】Springboot整合RocketMQ
【RocketMQ&&RocketMQ Dashbord】Springboot整合RocketMQ 【一】Mac安装RocketMQ和RocketMQ Dashbord【1】安装RocketMQ(1)下载(2)修改 JVM 参数(3)启动测试(4)关闭测试&…...
vue 3 深度指南:从基础到全栈开发实践
目录 一、环境搭建与项目初始化 1. 前置依赖安装 2. 项目初始化与结构解析 二、核心概念与语法深度解析 1. MVVM 模式与响应式原理 2. 模板语法与指令进阶 3. 组件化开发 三、进阶开发与全栈集成 1. 路由管理(Vue Router) 2. 状态管理…...
《白帽子讲 Web 安全》之跨站请求伪造
引言 在数字化时代,网络已深度融入人们生活的方方面面,Web 应用如雨后春笋般蓬勃发展,为人们提供着便捷高效的服务。然而,繁荣的背后却潜藏着诸多安全隐患,跨站请求伪造(CSRF)便是其中极为隐蔽…...
K8S学习之基础五十:k8s中pod时区问题并通过kibana查看日志
k8s中pod默认时区不是中国的,挂载一个时区可以解决 vi pod.yaml apiVersion: v1 kind: Pod metadata:name: counter spec:containers:- name: countimage: 172.16.80.140/busybox/busybox:latestimagePullPolicy: IfNotPresentargs: [/bin/sh,-c,i0;while true;do …...
nginx代理前端请求
一,项目配置 我在 ip 为 192.168.31.177 的机器上使用 vue3 开发前端项目,项目中使用 axios 调用后端接口。 这是 axios 的配置: import axios from axios;const request axios.create({baseURL: http://192.168.31.177:8001,// 设置请求…...
LibVLC —— 《基于Qt的LibVLC专业开发技术》视频教程
🔔 LibVLC/VLC 相关技术、疑难杂症文章合集(掌握后可自封大侠 ⓿_⓿)(记得收藏,持续更新中…) 《基于Qt的LibVLC专业开发技术》课程视频,(CSDN课程主页、51CTO课程主页) 适合具有一些C++/Qt编程基础,想要进一步提高或涉足音视频行业的。本课程分7章节,共计35小节。…...
Android生态大变革,谷歌调整开源政策,核心开发不再公开
“开源”这个词曾经是Android的护城河,如今却成了谷歌的烫手山芋。最近谷歌宣布调整Android的开源政策,核心开发将全面转向私有分支。翻译成人话就是:以后Android的核心更新,不再公开共享了。 这操作不就是开源变节吗,…...
Android Gradle 插件问题:The option ‘android.useDeprecatedNdk‘ is deprecated.
问题与处理策略 问题描述 在 Android 项目中,报如下警告 The option android.useDeprecatedNdk is deprecated. The current default is false. It has been removed from the current version of the Android Gradle plugin. NdkCompile is no longer supported…...
【web应用安全】关于web应用安全的几个主要问题的思考
文章目录 防重放攻击1. **Token机制(一次性令牌)**2. **时间戳 超时验证**3. **Nonce(一次性随机数)**4. **请求签名(如HMAC)**5. **HTTPS 安全Cookie**6. **幂等性设计****综合防御策略建议****注意事项…...
Git 基础入门:从概念到实践的版本控制指南
一、Git 核心概念解析 1. 仓库(Repository) Git 的核心存储单元,包含项目所有文件及其完整历史记录。分为本地仓库(开发者本地副本)和远程仓库(如 GitHub、GitLab 等云端存储),支持…...
银行分布式新核心的部署架构(两地三中心)
银行的核心系统对可用性和性能要求均非常严苛,所以一般都采用两地三中心部署模式。 其中: 同城两个主数据中心各自部署一套热备,平时两个中心同时在线提供服务,进行负载均衡假如其中一个数据中心出现异常,则由另外一个…...
Spring 及 Spring Boot 条件化注解(15个)完整列表及示例
Spring 及 Spring Boot 条件化注解完整列表及示例 1. 所有条件化注解列表 Spring 和 Spring Boot 提供了以下条件化注解(共 15 个),用于在配置类或方法上实现条件化注册 Bean 或配置: 注解名称作用来源框架Conditional自定义条件…...
MantisBT在Windows10上安装部署详细步骤
MantisBT 是一款基于 Web 的开源缺陷跟踪系统,以下是在 Windows 10 上安装部署 MantisBT 的详细步骤: 1. 安装必要的环境 MantisBT 是一个基于 PHP 的 Web 应用程序,因此需要安装 Web 服务器(如 Apache)、PHP 和数据…...
9.4分漏洞!Next.js Middleware鉴权绕过漏洞安全风险通告
今日,亚信安全CERT监控到安全社区研究人员发布安全通告,Next.js 存在一个授权绕过漏洞,编号为 CVE-2025-29927。攻击者可能通过发送精心构造的 x-middleware-subrequest 请求头绕过中间件安全控制,从而在未授权的情况下访问受保护…...
处理json,将接口返回的数据转成list<T>,和几个时间处理方法的工具类
接口或者其他方式返回json格式,也可以直接处理里边只有list的json数据 //第一种json格式,包含分页信息 {"code": 200,"msg": null,"data": {"records": [{"风速": "0.0","电流"…...
OpenCV图像拼接(5)图像拼接模块的用于创建权重图函数createWeightMap()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 cv::detail::createWeightMap 是 OpenCV 库中用于图像拼接模块的一个函数,主要用于创建权重图。这个权重图在图像拼接过程中扮演着重…...
linux 运行脚本命令区别
文章目录 chmod 赋予权限运行sh script.sh适用场景 bash script.shsource 或 . 脚本 chmod 赋予权限运行 chmod x script.sh # 赋予执行权限 ./script.sh # 直接执行创建新的子进程,不会影响当前 shell 的环境变量。#!(Shebang) 指…...
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置
噩梦终结:Flutter 配安卓、鸿蒙、iOS 真机调试环境 问题背景 很多开发者在配置 Flutter 项目环境时遇到困难,尤其是在处理 Android、鸿蒙和 iOS 真机调试环境时。卓伊凡最近接手了一个项目,发现很多“专业程序员”在环境搭建上花费了大量时…...
C++11QT复习 (六)
类型转换函数和类域 **Day6-3 类型转换函数和类域****1. 类型转换函数(Type Conversion Functions)****1.1 概述****1.2 代码示例****1.3 关键优化** **2. 类域(Class Scope)****2.1 作用域 vs 可见域****2.2 代码示例****2.3 关键…...
区块链技术在投票系统中的应用:安全、透明与去中心化
区块链技术在投票系统中的应用:安全、透明与去中心化 【引言】 近年来,电子投票系统因其便捷性受到广泛关注,但随之而来的安全问题也屡见不鲜,如选票篡改、重复投票、数据泄露等。如何确保投票的公平性、透明度和安全性? 区块链技术或许是解决方案之一! 区块链的 去中…...
