AudioTrack
AudioTrack是Android Audio系统提供给应用开发者(java/C++)的API,用于操作音频播放的数据通路。MeidaPlayer在播放音乐时用到的是它,我们可以也可以直接使用AudioTrack进行音频播放。它是最基本的音频数据输出类。
AudioTrack.java的构造函数
AudioTrack.java
public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,int bufferSizeInBytes, int mode)throws IllegalArgumentException {this(streamType, sampleRateInHz, channelConfig, audioFormat,bufferSizeInBytes, mode, AudioManager.AUDIO_SESSION_ID_GENERATE);}
看一下形参的含义;
①streamType:音频流类型,用于描述AudioTrack的播放场景。最常见的是AudioManager.STREAM_MUSIC,也就是我们平时用手机进行多媒体音乐播放的情形;
②sampleRateInHz:采样率,如48000,就表示音频数据每秒包含48000个音频采样点,采样率会影响音频数据流的音质效果;
③channelConfig:声道数,如AudioFormat. CHANNEL_ CONFIGURATION_ STEREO,表示双声道;
④audioFormat:音频编码格式;
⑤bufferSizeInBytes:由音频数据特性来确定的缓冲区的最小size,这个缓冲区是指用于向audioflinger进行跨进程数据传输的FIFO;
⑥mode:音频数据的传输模式,如AudioTrack.MODE_STREAM;
AudioTrack使用方式
//获取bufferSizeInBytes缓冲区的最小sizeint bufsize= AudioTrack. getMinBufferSize( 8000, AudioFormat.CHANNEL_CONFIGURATION_STEREO,AudioFormat. ENCODING_PCM_16BIT);//创建AudioTrackAudioTrack track= new AudioTrack( AudioManager. STREAM_ MUSIC, AudioFormat. CHANNEL_CONFIGURATION_STEREO,AudioFormat.ENCODING_PCM_16BIT,bufsize,AudioTrack.MODE_STREAM);//开始播放track. play();......//调用write往track中写数据track.write(buffer,0,length);......//停止播放track. stop();//释放底层资源track.release();
在创建AudioTrack的时候,我们需要指明音频流类型和数据播放模式:
数据播放模式
AudioTrack有两种数据播放模式,MODE_STREAM和MODE_STATIC;
MODE_STREAM:常见的一种模式,这种模式下,通过write一次次把音频数据写到AudioTrack中,也就是写进去多少,AudioTrack就播放多少;
MODE_STATIC:这种数据加载模式在play之前就把所有的数据通过一次write调用传递到AudioTrack的内部缓冲区中,这种模式呢,也有缺点,就是如果一次性写进去的数据太多,会导致系统无法分配足够的内存来存储数据;这种模式适用于像铃声这种内存占用量较小,延时要求比较高的文件;
音频流类型
android将系统的声音分为好几种流类型,常见的有:
(1)STREAM_ALARM:警告;
(2)STREAM_MUSIC:音乐;
(3)STREAM_RING:铃声;
(4)STREAM_SYSTEM:系统声音,如低电量提示音,锁屏声音等;
(5)STREAM_VOICE_CALL:通话声;
注意:这些类型的划分与音频数据本身并没有关系,如MUSIC和RING类型都可以是某首MP3歌曲,把音频流进行分类,是Audio系统对音频的管理策略有关,以便于管理;
.在创建AudioTrack的时候,我们需要指明bufferSizeInBytes缓冲区的大小,也就是调用这个函数:
AudioTrack. getMinBufferSize( 8000, AudioFormat.CHANNEL_CONFIGURATION_STEREO,AudioFormat. ENCODING_PCM_16BIT);
详细的代码分析可以参考我的博客:https://blog.csdn.net/weixin_52370850/article/details/143651127
继续回到AudioTrack的构造函数,代码如下:
public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,int bufferSizeInBytes, int mode, int sessionId)throws IllegalArgumentException {this((new AudioAttributes.Builder()).setLegacyStreamType(streamType).build(),(new AudioFormat.Builder()).setChannelMask(channelConfig).setEncoding(audioFormat).setSampleRate(sampleRateInHz).build(),bufferSizeInBytes,mode, sessionId);deprecateStreamTypeForPlayback(streamType, "AudioTrack", "AudioTrack()");}public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,int mode, int sessionId)throws IllegalArgumentException {super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK);audioParamCheck(rate, channelMask, channelIndexMask, encoding, mode);mStreamType = AudioSystem.STREAM_DEFAULT;audioBuffSizeCheck(bufferSizeInBytes);//调用native层的native_setup,进行初始化int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,mNativeBufferSizeInBytes, mDataLoadMode, session, 0 /*nativeTrackInJavaObj*/);if (mDataLoadMode == MODE_STATIC) {mState = STATE_NO_STATIC_DATA;} else {mState = STATE_INITIALIZED;}}
很容易发现AudioTrack.java的构造函数并没有做什么实质性的工作,只是做了些参数的转化和成员变量的初始化。
往下看native_setup()函数。
路径:android_media_AudioTrack.cpp
static jintandroid_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, jobject jaa,jintArray jSampleRate, jint channelPositionMask, jint channelIndexMask,jint audioFormat, jint buffSizeInBytes, jint memoryMode, jintArray jSession,jlong nativeAudioTrack) {//声明Native层的AudioTrack对象,这个是audiotrack的proxy。用于跨进程通信!!!!sp<AudioTrack> lpTrack = 0;AudioTrackJniStorage* lpJniStorage = NULL;if (nativeAudioTrack == 0) {int* sampleRates = env->GetIntArrayElements(jSampleRate, NULL);int sampleRateInHertz = sampleRates[0];env->ReleaseIntArrayElements(jSampleRate, sampleRates, JNI_ABORT);audio_channel_mask_t nativeChannelMask = nativeChannelMaskFromJavaChannelMasks(channelPositionMask, channelIndexMask);uint32_t channelCount = audio_channel_count_from_out_mask(nativeChannelMask);audio_format_t format = audioFormatToNative(audioFormat);size_t frameCount;if (audio_is_linear_pcm(format)) {const size_t bytesPerSample = audio_bytes_per_sample(format);frameCount = buffSizeInBytes / (channelCount * bytesPerSample);} else {frameCount = buffSizeInBytes;}lpTrack = new AudioTrack();lpJniStorage = new AudioTrackJniStorage();lpJniStorage->mCallbackData.audioTrack_class = (jclass)env->NewGlobalRef(clazz);// we use a weak reference so the AudioTrack object can be garbage collected.lpJniStorage->mCallbackData.audioTrack_ref = env->NewGlobalRef(weak_this);lpJniStorage->mCallbackData.busy = false;status_t status = NO_ERROR;switch (memoryMode) {case MODE_STREAM:status = lpTrack->set(AUDIO_STREAM_DEFAULT,//指定流类型sampleRateInHertz,format,//采样点精度,即编码格式,一般为PCM16和PCM8nativeChannelMask,frameCount,AUDIO_OUTPUT_FLAG_NONE,audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user)0,0,// 共享类型,STREAM模式下为空,实际使用的共享内存有AudioFlinger创建true,sessionId,AudioTrack::TRANSFER_SYNC,NULL,-1, -1,paa);break;case MODE_STATIC://数据加载模式为STATIC模式,需要先创建共享内存if (!lpJniStorage->allocSharedMem(buffSizeInBytes)) {ALOGE("Error creating AudioTrack in static mode: error creating mem heap base");goto native_init_failure;}status = lpTrack->set(AUDIO_STREAM_DEFAULT,sampleRateInHertz,format,nativeChannelMask,frameCount,AUDIO_OUTPUT_FLAG_NONE,audioCallback, &(lpJniStorage->mCallbackData),0,lpJniStorage->mMemBase,//STATIC模式,需要传递共享内存true,sessionId,AudioTrack::TRANSFER_SHARED,NULL,-1, -1,paa);break;default:goto native_init_failure;}}//把JNI层中new出来的AudioTrack对象指针保存到Java对象的一个变量中,这样就把JNI层的AudioTrack对象和Java层的AudioTrack对象关联起来了,setAudioTrack(env, thiz, lpTrack);//lpJniStorage对象指针也保存到Java对象中。env->SetLongField(thiz, javaAudioTrackFields.jniData, (jlong)lpJniStorage);return (jint) AUDIO_JAVA_SUCCESS;}
android_media_AudioTrack_setup的主要工作就是创建在native层的AudioTrack对象,对它进行初始化,并完成JNI层的AudioTrack对象和Java层的AudioTrack对象的关联绑定,这个过程和函数的名字还是很匹配的,setup建立起audiotrack使用需要的一切;
在不同的数据加载模式下,AudioTrack对象的创建也会不同,在MODE_STATIC模式下进行数据传输,需要在audiotrack侧创建共享内存;在MODE_STREAM模式下进行数据传输,需要在audioflinger侧创建共享内存。
play():AudioTrack.java
public void play() throws IllegalStateException {if (mState != STATE_INITIALIZED) {throw new IllegalStateException("play() called on uninitialized AudioTrack.");}final int delay = getStartDelayMs();if (delay == 0) {startImpl();} else {new Thread() {public void run() {try {Thread.sleep(delay);} catch (InterruptedException e) {e.printStackTrace();}baseSetStartDelayMs(0);try {startImpl();} catch (IllegalStateException e) {}}}.start();}}private void startImpl() {synchronized(mPlayStateLock) {baseStart();native_start();mPlayState = PLAYSTATE_PLAYING;}}native_start():android_media_AudioTrack.cppstatic void android_media_AudioTrack_start(JNIEnv *env, jobject thiz){//从java的AudioTrack对象中获取对应的native层的AudioTrack对象指针sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);lpTrack->start();}
write():AudioTrack.java
public int write(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes) {return write(audioData, offsetInBytes, sizeInBytes, WRITE_BLOCKING);}public int write(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes,@WriteMode int writeMode) {//调用native的native_write_byteint ret = native_write_byte(audioData, offsetInBytes, sizeInBytes, mAudioFormat,writeMode == WRITE_BLOCKING);//如果当前的数据加载类型是MODE_STATIC,在write后才会将状态设置为初始化,这跟MODE_STREAM类型是不一样的if ((mDataLoadMode == MODE_STATIC)&& (mState == STATE_NO_STATIC_DATA)&& (ret > 0)) {// benign race with respect to other APIs that read mStatemState = STATE_INITIALIZED;}return ret;}
native_write_byte:android_media_AudioTrack.cpp
static jint android_media_AudioTrack_writeArray(JNIEnv *env, jobject thiz,T javaAudioData,jint offsetInSamples, jint sizeInSamples,jint javaAudioFormat,jboolean isWriteBlocking) {sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);//调用writeToTrack()方法jint samplesWritten = writeToTrack(lpTrack, javaAudioFormat, cAudioData,offsetInSamples, sizeInSamples, isWriteBlocking == JNI_TRUE /* blocking */);envReleaseArrayElements(env, javaAudioData, cAudioData, 0);return samplesWritten;}static jint writeToTrack(const sp<AudioTrack>& track, jint audioFormat, const T *data,jint offsetInSamples, jint sizeInSamples, bool blocking) {ssize_t written = 0;size_t sizeInBytes = sizeInSamples * sizeof(T);//track->sharedBuffer() == 0,表示此时的数据加载是STREAM模式,如果不等于0,那就是STATIC模式if (track->sharedBuffer() == 0) {//STREAM模式,调用write写数据written = track->write(data + offsetInSamples, sizeInBytes, blocking);if (written == (ssize_t) WOULD_BLOCK) {written = 0;}} else {if ((size_t)sizeInBytes > track->sharedBuffer()->size()) {sizeInBytes = track->sharedBuffer()->size();}//在STATIC模式下,直接把数据memcpy到共享内存,这种模式下,先调用write,后调用playmemcpy(track->sharedBuffer()->pointer(), data + offsetInSamples, sizeInBytes);written = sizeInBytes;}if (written >= 0) {return written / sizeof(T);}return interpretWriteSizeError(written);}
stop():AudioTrack.java
public void stop()throws IllegalStateException {if (mState != STATE_INITIALIZED) {throw new IllegalStateException("stop() called on uninitialized AudioTrack.");}// stop playingsynchronized(mPlayStateLock) {//调用native方法native_stop();baseStop();mPlayState = PLAYSTATE_STOPPED;mAvSyncHeader = null;mAvSyncBytesRemaining = 0;}}android_media_AudioTrack.cppandroid_media_AudioTrack_stop(JNIEnv *env, jobject thiz){sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);//调用Native层的stop方法lpTrack->stop();}
release():AudioTrack.java
public void release() {try {//调用stop()stop();} catch(IllegalStateException ise) {}baseRelease();//调用native方法native_release();//将状态置为未初始化,下次用到AudioTrack时,需要重新创建AudioTrack,才会将状态置为初始化状态,mState = STATE_UNINITIALIZED;}android_media_AudioTrack.cppstatic void android_media_AudioTrack_release(JNIEnv *env, jobject thiz) {//释放资源sp<AudioTrack> lpTrack = setAudioTrack(env, thiz, 0);// delete the JNI dataAudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetLongField(thiz, javaAudioTrackFields.jniData);//之前保存在java对象中的指针变量此时要设置为0env->SetLongField(thiz, javaAudioTrackFields.jniData, 0);}
**java层的AudioTrack只是将工作通过二道贩子android_media_AudioTrack(JNI)交给了Native层的AudioTrack。**注意java层AudioTrack的状态,其中mState状态只在AudioTrack被new出来的时候才会被置为STATE_INITIALIZED,而且还要保证此时的数据传输模式为MODE_STREAM,mState状态只有被置为STATE_INITIALIZED后,write,play,stop方法才可以调用,并且在release方法后,mState状态会被置为STATE_UNINITIALIZED,下次要想继续用AudioTrack的话,必须要重新创建它;如果数据传输模式为MODE_STATIC,AudioTrack在调用了write()方法后才会将mState状态置为STATE_INITIALIZED,所以,MODE_STATIC模式下,只能先write,后play。
AudioTrack.cpp
在java层new AudioTrack的时候,会创建出在native层的AudioTrack对象,并且调用该对象的set方法进行初始化,native层的AudioTrack在frameworks\av\media\libaudioclient目录下,我们来分析Native层的AudioTrack;
AudioTrack的无参构造函数:
AudioTrack.cppAudioTrack::AudioTrack(): mStatus(NO_INIT),//把初始状态设置为NO_INITmState(STATE_STOPPED),mPreviousPriority(ANDROID_PRIORITY_NORMAL),mPreviousSchedulingGroup(SP_DEFAULT),mPausedPosition(0),mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE),mRoutedDeviceId(AUDIO_PORT_HANDLE_NONE),mPortId(AUDIO_PORT_HANDLE_NONE){mAttributes.content_type = AUDIO_CONTENT_TYPE_UNKNOWN;mAttributes.usage = AUDIO_USAGE_UNKNOWN;mAttributes.flags = 0x0;strcpy(mAttributes.tags, "");}
AudioTrack初始化方法set():
status_t AudioTrack::set(audio_stream_type_t streamType,uint32_t sampleRate,audio_format_t format,audio_channel_mask_t channelMask,size_t frameCount,audio_output_flags_t flags,callback_t cbf,void* user,int32_t notificationFrames,const sp<IMemory>& sharedBuffer,bool threadCanCallJava,audio_session_t sessionId,transfer_type transferType,const audio_offload_info_t *offloadInfo,uid_t uid,pid_t pid,const audio_attributes_t* pAttributes,bool doNotReconnect,float maxRequiredSpeed){..................................................//cbf是JNI层传入的回调函数audioCallback,如果设置了回调函数,则启动一个线程if (cbf != NULL) {mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava);mAudioTrackThread->run("AudioTrack", ANDROID_PRIORITY_AUDIO, 0 /*stack*/);}....................................//关键调用status_t status = createTrack_l();....................................return NO_ERROR;}核心调用status_t status = createTrack_l();status_t AudioTrack::createTrack_l(){//得到AudioFlinger的Binder代理端BpAudioFlinger;const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();//向audioFlinger发送createTrack请求,返回IAudioTrack对象,后续AudioFlinger和AudioTrack的交互就是围绕着IAudioTrack进行sp<IAudioTrack> track = audioFlinger->createTrack(streamType,mSampleRate,mFormat,mChannelMask,&temp,&flags,mSharedBuffer,output,mClientPid,tid,&mSessionId,mClientUid,&status,mPortId);//在STREAM模式下,没有在AudioTrack端创建共享内存,共享内存最终由AudioFlinger创建;//取出AudioFlinger创建的共享内存sp<IMemory> iMem = track->getCblk();//IMemory的Pointer在此处将返回共享内存的首地址,类型为void *void *iMemPointer = iMem->pointer();//static_cast直接把这个void *类型转换成audio_track_cblk_t,表明这块内存的首部中存在audio_track_cblk_t这个对象audio_track_cblk_t* cblk = static_cast<audio_track_cblk_t*>(iMemPointer);mCblk = cblk;void* buffers;if (mSharedBuffer == 0) {//buffers指向共享buffer,它的起始位置是共享内存的首部偏移audio_track_cblk_t的大小buffers = cblk + 1;} else {//STATIC模式buffers = mSharedBuffer->pointer();}return NO_ERROR;}
createTrack_l()方法的核心调用是audioFlinger->createTrack(),返回一个IAudioTrack对象;IAudioTrack中有一块共享内存,其头部是一个audio_track_cblk_t对象,在该对象之后是跨进程共享buffer(FIFO);关于这个audio_track_cblk_t对象,在AudioTrackShared.h中声明,在AudioTrack.cpp中定义,它是用来协调和管理AudioTrack和AudioFlinger二者数据传输节奏的。audiotrack向audio_track_cblk_t查询可用的空余空间进行写数据。AudioFlinger向audio_track_cblk_tc查询可用的数据量进行读取。audioflinger和audiotrack构成了一个消费者生产者模型。
mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava)用于pull的数据传输方式
这个线程与音频数据的传输方式有关系,AudioTrack支持两种数据传输方式:
Push方式:用户主动调用write写数据,这相当于数据被push到AudioTrack;
Pull方式:JNI层的代码中在构造AudioTrack时,传入了一个回调函数audioCallback,pull方式就是AudioTrackThread调用这个回调函数主动从用户那里pull数据。
write()ssize_t AudioTrack::write(const void* buffer, size_t userSize, bool blocking){//Buffer抽象共享FIFO中一块指定大小可以读写的缓冲区Buffer audioBuffer;while (userSize >= mFrameSize) {//以帧为单位audioBuffer.frameCount = userSize / mFrameSize;//从FIFO中得到一块空闲的数据缓冲区status_t err = obtainBuffer(&audioBuffer,blocking ? &ClientProxy::kForever : &ClientProxy::kNonBlocking);//空闲数据缓冲区的大小是audioBuffer.size,地址在audioBuffer.i8中,数据传递通过memcpy来完成size_t toWrite = audioBuffer.size;memcpy(audioBuffer.i8, buffer, toWrite);buffer = ((const char *) buffer) + toWrite;userSize -= toWrite;written += toWrite;//释放从FIFO申请的buffer。更新FIFO的读写指针releaseBuffer(&audioBuffer);}if (written > 0) {//定位音视频不同步问题可以用到这个属性,表示audiotrack一共往audioflinger写过多少数据。mFramesWritten += written / mFrameSize;}return written;}
write函数,就是简单的memcpy,但读进程(audioflinger)和写进程(audiotrack)之间的同步工作则是通过obtainBuffer和releaseBuffer来完成的;obtainBuffer的功能,就是从cblk管理的数据缓冲中得到一块可写的空闲空间,如果没有的话,那就阻塞,而releaseBuffer,则是在使用完这块空间后更新写指针的位置;
stop()
void AudioTrack::stop(){//mAudioTrack是IAudioTrack类型,通知AudioFlinger端的track进行stop。mAudioTrack->stop();sp<AudioTrackThread> t = mAudioTrackThread;if (t != 0) {if (!isOffloaded_l()) {//停止AudioTrackThreadt->pause();}} else {setpriority(PRIO_PROCESS, 0, mPreviousPriority);set_sched_policy(0, mPreviousSchedulingGroup);}}
调用IAudioTrack的stop,并且并退出回调线程。
start()
status_t AudioTrack::start(){status = mAudioTrack->start();}
播放操作还是通过IAudioTrack通知AudioFlinger端;
总结:
①java层的AudioTrack只是java语言的api(包皮公司),我们对java层AudioTrack的api调用,实际都会传递到native层的AudioTrack去执行;
②native层的AudioTrack在初始化的时候,会跨进程的调用AudioFlinger的createTrack方法,并返回一个IAudioTrack的对象,native层的AudioTrack以及AudioFlinger通过这个对象进行交互;
③得到IAudioTrack对象以后,native层AudioTrack的start(),stop等方法的最终操作会传递给AudioFlinger去执行;
④在native层的AudioTrack中将数据write进内存,AudioFlinger通过内存共享与AudioTrack共享同一块资源,从而完成数据的传递;
重点记忆:cblk封装FIFO相关的操作!!!!!!!!!!!!!!数据通路。
控制通路直接通过IAudioTrack实现。
相关文章:
AudioTrack
AudioTrack是Android Audio系统提供给应用开发者(java/C)的API,用于操作音频播放的数据通路。MeidaPlayer在播放音乐时用到的是它,我们可以也可以直接使用AudioTrack进行音频播放。它是最基本的音频数据输出类。 AudioTrack.java…...
多条件排序(C# and Lua)
C# 升序排序 OrderBy 按升序对序列的元素进行排序 ThenBy 按升序对序列中的元素执行后续排序 降序排序 OrderByDescending 按降序对序列的元素排序 ThenByDescending 按降序对序列中的元素执行后续排序 public class Fruit {public int id;public string name;publi…...
人工智能之数学基础:线性方程组求解的得力助手——增广矩阵
本文重点 增广矩阵是一个极具实用价值的工具,尤其在处理线性方程组时,它展现了卓越的功效。通过整合系数和常数项,增广矩阵简化了计算过程并提供了判断方程组解集的有效方法。 增广矩阵的起源与定义 增广矩阵的概念源于线性方程组求解的需求。在解决线性方程组时,我们常…...
Vue3 + ECharts 数据可视化实战指南
一、为什么选择ECharts? 百度开源的成熟可视化库 支持30种图表类型 完善的文档和社区支持 与Vue3完美兼容 二、环境搭建 1. 创建Vue3项目 npm create vuelatest # 选择TypeScript、Pinia等按需配置 2. 安装核心依赖 npm install echarts vue-echarts vueus…...
关于Flask框架30道面试题及解析
文章目录 基础概念1. 什么是Flask?其核心特性是什么?2. Flask和Django的主要区别?3. 解释Flask中的“路由”概念。如何定义动态路由?核心组件4. Flask的请求上下文(Request Context)和应用上下文(Application Context)有什么区别?5. 如何访问请求参数?POST和GET方法的…...
服务安全认证概述与基础认证方式
文章目录 1. 引言1.1 认证与授权的区别1.2 认证方式的演进 2. 基础认证方式2.1 HTTP Basic Authentication2.2 API Key 认证2.3 HMAC-SHA256 签名认证2.4 JWT(JSON Web Token) 3. 认证方式对比与总结3.1 认证方式对比3.2 如何选择合适的认证方式…...
【Android Studio开发】生命周期、Activity和组件通信(上)
零、前期配置 1.【Android】模式 2.点击【运行】,弹出模拟器 右侧是模拟机,显示Hello World 3. 打开【activity_main.xml】文件,点击【Design】,然后点击【Component Tree】 在弹出的Component Tree中右键【main】,选择【Conver…...
【ES】Elasticsearch学习
文章目录 简单的安装 简单的安装 参考:https://blog.csdn.net/smilehappiness/article/details/118466378 官网:https://www.elastic.co/guide/en/elasticsearch/reference/current/targz.html 下载:https://www.elastic.co/cn/downloads/e…...
实验三 Python 数据可视化 Python 聚类-K-means(CQUPT)
一、实验目的 Python 数据可视化: 1、学习使用 jieba、wordcloud 等类库生成词云图。 2、学习使用 Matplotlib 库进行数据可视化。 Python 聚类-K-means: 1、理解聚类非监督学习方法的基本原理。 2、掌握 Python、numpy、pandas、sklearn 实现聚类…...
【STM32】SPI通信协议W25Q64Flash存储器芯片(学习笔记)
通信接口部分有介绍SPI:【STM32】USART串口协议&串口外设-学习笔记-CSDN博客 SPI通信协议 SPI通信 SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线四根通信线:SCK(Serial Clock&…...
JavaScript实现一个函数,将数组扁平化(flatten),即把多维数组转为一维数组。
大白话实现一个函数,将数组扁平化(flatten),即把多维数组转为一维数组。 思路 实现数组扁平化的基本思路是遍历数组中的每个元素,如果元素是数组,就递归地将其扁平化并添加到结果数组中;如果元…...
SpringBoot最佳实践之 - 使用AOP记录操作日志
1. 前言 本篇博客是个人在工作中遇到的需求。针对此需求,开发了具体的实现代码。并不是普适的记录操作日志的方式。以阅读本篇博客的朋友,可以参考此篇博客中记录日志的方式,可能会对你有些许帮助和启发。 2. 需求描述 有一个后台管理系统…...
第六届机电一体化技术与智能制造国际学术会议(ICMTIM 2025)
重要信息 4月11-13日 南京江北新区工业大学亚朵酒店 www.icmtim.org(点击了解参会投稿等) 简介 由南京工业大学主办,南京工业大学电气工程与控制科学学院、中国矿业大学、黑龙江大学、江苏省自动化学会承办的第六届机电一体化技术…...
numpy学习笔记4:np.arange(0, 10, 2) 的详细解释
numpy学习笔记4:np.arange(0, 10, 2) 的详细解释 以下是 np.arange(0, 10, 2) 的详细解释: 1. 函数作用 np.arange() 是 NumPy 中用于生成均匀间隔数值序列的函数,类似于 Python 内置的 range(),但返回的是 NumPy 数组而非列表&…...
期刊分区表2025年名单下载(经济学、管理学)
2025年期刊分区表包括SCIE、SSCI、A&HCI、ESCI和OAJ,共设置了包括自然科学、社会科学和人文科学在内的21个大类 本次分享的是期刊分区表2025年名单经济学类、管理学类,一共7631025条 一、数据介绍 数据名称:期刊分区表2025年名单 数据…...
八股学习-JUC java并发编程
本文仅供个人学习使用,参考资料:JMM(Java 内存模型)详解 | JavaGuide 线程基础概念 用户线程:由用户空间程序管理和调度的线程,运行在用户空间。 内核线程:由操作系统内核管理和调度的线程&…...
嵌入式笔记 | 正点原子STM32F103ZET6 4 | 中断补充
1. 外设引脚重映射 1.1 定义 在STM32中,每个外设的引脚都有默认的GPIO端口,但有些引脚可以通过重映射寄存器将功能映射到其他端口。这种机制称为引脚重映射,主要用于解决引脚复用冲突或优化PCB布线。 1.2 重映射的类型 部分重映射&#x…...
PostgreSQL_数据下载并保存(psycopg2)
目录 前置: 1 数据下载 1.1 多个股票多个交易日 1.2 一个交易日所有股票 2 数据保存,使用python中的psycopg2包 2.1 在PyCharm中创建新项目,并安装包 2.2 代码-多个股票多个交易日 2.3 代码-一个交易日所有股票 2.4 在 pgAdmin4 中…...
启明星辰春招面试题
《网安面试指南》https://mp.weixin.qq.com/s/RIVYDmxI9g_TgGrpbdDKtA?token1860256701&langzh_CN 5000篇网安资料库https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247486065&idx2&snb30ade8200e842743339d428f414475e&chksmc0e4732df793fa3bf39…...
边缘计算革命:重构软件架构的范式与未来
摘要 边缘计算通过将算力下沉至网络边缘,正在颠覆传统中心化软件架构的设计逻辑。本文系统分析了边缘计算对软件架构的范式革新,包括分布式分层架构、实时资源调度、安全防护体系等技术变革,并结合工业物联网、智慧医疗等场景案例,…...
【读点论文】Chain Replication for Supporting High Throughput and Availability
在分布式系统中,强一致性往往和高可用、高吞吐是矛盾的。比如传统的关系型数据库,其保证了强一致性,但往往牺牲了可用性和吞吐量。而像 NoSQL 数据库,虽然其吞吐量、和扩展性很高,但往往只支持最终一致性,无…...
Servlet、Servlet的5个接口方法、生命周期、以及模拟实现 HttpServlet 来写接口的基本原理
DAY15.1 Java核心基础 Servlet Servlet是一个接口,是java的基础,java之所以编写web的程序,接收请求并响应,就是因为Sevlet接口 Java 类实现了Servlet接口的时候就可以接收并响应请求,成为web服务器 Web服务器就是接…...
深入了解 C# 中的 LINQ:功能、语法与应用解析
1. 什么是 LINQ? LINQ(Language Integrated Query,语言集成查询)是 C# 和其他 .NET 语言中的一种强大的查询功能,它允许开发者在语言中直接执行查询操作。LINQ 使得开发者可以使用 C# 语法(或 VB.NET&…...
贝叶斯公式的一个直观解释
E E E:抓到娃娃 H H H:坐地铁 H ˉ \bar H Hˉ:坐公交 P ( E ) P ( H ) P ( E ∣ H ) P ( H ‾ ) P ( E ∣ H ‾ ) P({E}) P({H}) P({E} \mid {H}) {P}(\overline{{H}}) {P}({E} \mid \overline{{H}}) P(E)P(H)P(E∣H)P(H)P(E∣H) P (…...
Java 大视界 -- Java 大数据分布式计算中的通信优化与网络拓扑设计(145)
💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也…...
reconstruct_3d_object_model_for_matching例子
文章目录 1.获取om3文件2.准备可视化3.准备3D可视化4.读取3D模型5.显示成对注册结果16.显示成对注册结果27.联合注册模型8.处理图像8.1子采样8.2 图像计算与平滑8.3 三角测量 9.基于表面做3D匹配10.评估模型准确度10.1 在场景中找到模型10.2 计算模型和场景之间的距离 11.立体系…...
【JavaWeb学习Day27】
Tlias前端 员工管理 条件分页查询: 页面布局 搜索栏: <!-- 搜索栏 --><div class"container"><el-form :inline"true" :model"searchEmp" class"demo-form-inline"><el-form-item label…...
Webrtc编译官方示例实现视频通话
Webrtc编译官方示例实现视频通话 前言 webrtc官网demo中给了一个供我们学习和应用webrtc的一个很好的例子:peerconnection,这期我们就来编译和运行下这个程序看看视频通话的效果以。 1、打开源码工程 继上期源码编译完成后,我们使用vs打开…...
编程语言选择分析:C#、Rust、Go 与 TypeScript 编译器优化
编程语言选择分析:C#、Rust、Go 与 TypeScript 编译器优化 在讨论编程语言的选择时,特别是针对微软的 C# 和 Rust,以及谷歌的 Go 语言,以及微软试图通过 Go 来拯救 TypeScript 编译器的问题,我们可以从多个角度来分析和…...
信息学奥赛一本通 1610:玩具装箱 | 洛谷 P3195 [HNOI2008] 玩具装箱
【题目链接】 ybt 1610:玩具装箱 洛谷 P3195 [HNOI2008] 玩具装箱 【题目考点】 1. 动态规划:斜率优化动规 斜率优化动规模板题:信息学奥赛一本通 1607:【 例 2】任务安排 2 | 洛谷 P10979 任务安排 2 【解题思路】 玩具长度…...
