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

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系统提供给应用开发者&#xff08;java/C&#xff09;的API&#xff0c;用于操作音频播放的数据通路。MeidaPlayer在播放音乐时用到的是它&#xff0c;我们可以也可以直接使用AudioTrack进行音频播放。它是最基本的音频数据输出类。 AudioTrack.java…...

Windows安装Rust环境(详细教程)

一、 安装mingw64(C语言环境) Rust默认使用的C语言依赖Visual Studio&#xff0c;但该工具占用空间大安装也较为麻烦&#xff0c;可以选用轻便的mingw64包。 1.1 安装地址 (1) 下载地址1-GitHub&#xff1a;Releases niXman/mingw-builds-binaries GitHub (2) 下载地址2-W…...

监控IP,网站将异常情况通过飞书机器人发至指定群内

界面如下&#xff0c;丑是丑了点&#xff0c;但主打一个实用。 主要就是通过ping&#xff0c;就是一直在ping&#xff0c;当不通的时候&#xff0c;就根据你设置的报警时间&#xff0c;主要是利用飞书机器人来给飞书指定群里发异常信息报警。 直接上代码 import subprocess i…...

JVM 02

今天是2025/03/23 19:07 day 10 总路线请移步主页Java大纲相关文章 今天进行JVM 3,4 个模块的归纳 首先是JVM的相关内容概括的思维导图 3. 类加载机制 加载过程 加载&#xff08;Loading&#xff09; 通过类全限定名获取类的二进制字节流&#xff08;如从JAR包、网络、动态…...

局域网设备访问虚拟机 挂载NFS

目录 引言&#xff1a;网络IP问题配置虚拟机网络有线网络&#xff1a;无线网络&#xff1a; NFS文件挂载服务端配置客户端连接 引言&#xff1a; 需求&#xff1a;局域网下树莓派设备想要访问电脑主机上的虚拟机。这样可以通过nfs挂载网络设备&#xff0c;有利于交叉编译环境调…...

零、ubuntu20.04 安装 anaconda

1.anaconda下载 地址&#xff1a;Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 选择&#xff1a;Anaconda3-2023.07-2-Linux-x86_64.sh 2.anaconda安装 选择下载目录&#xff0c;选在在终端中打开&#xff0c;然后在终端输入安装命…...

IntelliJ IDEA 将 Spring Boot 项目远程部署到服务器

使用 IntelliJ IDEA 将 Spring Boot 项目远程部署到服务器的详细步骤&#xff0c;涵盖多种常见方法&#xff1a; 方法一&#xff1a;通过 SSH Maven 插件直接部署 1. 服务器环境准备 确保服务器已安装&#xff1a; Java 运行环境&#xff08;与项目 JDK 版本一致&#xff0…...

利用dify打造命令行助手

利用dify打造命令行助手 前言 我是mac os 的使用者。 如果说macos哪个工具我使用最频繁&#xff0c;最能提高效率的工作工具非zsh莫属(当然&#xff0c;我安装了iterm)。前不久&#xff0c;我要扩容线上的k8s集群。便想统计一下每个node上运行的pod数量。我不知道合适的命令&…...

前端面试整理

一、csshtml 二、js 三、vue 四、react 1.React 类组件与函数式组件核心区别 定义方式的不同&#xff0c;类组件用ES6的class&#xff0c;继承React.Component&#xff0c;而函数式组件用函数声明‌。 状态管理方面&#xff0c;类组件用this.state和setState&#xff0c;函…...

Perl语言的计算机网络

Perl语言在计算机网络中的应用 引言 在计算机科学的众多领域中&#xff0c;网络编程是一个极具挑战性和广泛应用的领域。在这其中&#xff0c;Perl语言以其强大的文本处理能力和简洁的语法&#xff0c;成为了网络编程的重要工具之一。自从1987年Larry Wall创造Perl以来&#…...

数据结构初阶-二叉树的应用

1.单值二叉树 题目链接&#xff1a;https://leetcode.cn/problems/univalued-binary-tree/description/ 题目思路&#xff1a;我们把根结点与左孩子和右孩子进行比较&#xff0c;只有左右子树都是单值二叉树的时候才为单值二叉树。但是我们需要先返回的是false&#xff0c;最…...

【赵渝强老师】在Docker中运行达梦数据库

Docker是一个客户端服务器&#xff08;Client-Server&#xff09;架构。Docker客户端和Docker守护进程交流&#xff0c;而Docker的守护进程是运作Docker的核心&#xff0c;起着非常重要的作用&#xff08;如构建、运行和分发Docker容器等&#xff09;。达梦官方提供了DM 8在Doc…...

掌握C#循环:for、while、break与continue详解及游戏案例

Langchain系列文章目录 01-玩转LangChain&#xff1a;从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块&#xff1a;四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain&#xff1a;从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…...

备份比赛数据【算法赛】

0备份比赛数据【算法赛】 - 蓝桥云课 问题描述 蓝桥杯大赛的组委会最近遇到了一个棘手的问题。他们有 N 台电脑需要备份比赛数据&#xff0c;每台电脑所需的备份时间分别为 A1​,A2​,…,AN​ 分钟。 备份必须按编号顺序依次进行&#xff0c;即先第 1 台&#xff0c;再第 2 …...

【算法笔记】图论基础(二):最短路、判环、二分图

目录 最短路松弛操作Dijkstra朴素Dijkstra时间复杂度算法过程例题 堆优化Dijkstra时间按复杂度算法过程例题 bellman-ford时间复杂度为什么dijkstra不能处理负权边&#xff1f;dijkstra的三个步骤&#xff1a;反例失效的原因 算法过程例题 spfa时间复杂度算法过程例题spfa求最短…...

【性能优化点滴】odygrd/quill 中一个简单的标记位作用--降低 IO 次数

在 StreamSink 类中&#xff0c;成员变量 _write_occurred 的作用是 跟踪自上次刷新&#xff08;Flush&#xff09;以来是否有写入操作发生&#xff0c;其核心目的是 优化 I/O 性能。以下是详细解析&#xff1a; _write_occurred 的作用 1. 避免不必要的刷新&#xff08;Flush…...

Unity2022发布Webgl2微信小游戏部分真机黑屏

复现规律&#xff1a; Unity PlayerSetting中取消勾选ShowSplashScreen 分析&#xff1a; 在Unity中&#xff0c;Splash Screen&#xff08;启动画面&#xff09; 不仅是视觉上的加载动画&#xff0c;还承担了关键的引擎初始化、资源预加载和渲染环境准备等底层逻辑。禁用后导…...

TDengine 3.3.2.0 集群报错 Post “http://buildkitsandbox:6041/rest/sql“

原因&#xff1a; 初始化时处于内网环境下&#xff0c;Post “http://buildkitsandbox:6041/rest/sql“ 无法访问 修复&#xff1a; vi /etc/hosts将buildkitsandbox映射为本机节点 外网环境下初始化时没有该问题...

基于yolov11的中空圆柱形缺陷检测系统python源码+pytorch模型+评估指标曲线+精美GUI界面

【背景介绍】 中空圆柱形缺陷检测在多个领域具有深远意义。在石油、天然气及化工行业&#xff0c;缺陷检测可预防泄漏事故&#xff0c;避免火灾、爆炸及环境污染&#xff0c;保障人员与财产安全。建筑、桥梁及航空航天领域则依赖此技术确保中空圆柱形结构的稳定性&#xff0c;…...

Python爬虫-爬取AliExpress商品搜索词排名数据

前言 本文是该专栏的第49篇,后面会持续分享python爬虫干货知识,记得关注。 本文,笔者以AliExpress平台为例。基于Python爬虫,通过某个指定的“搜索关键词”,批量获取该“搜索关键词”的商品排名数据。 具体实现思路和详细逻辑,笔者将在正文结合完整代码进行详细介绍。废…...

20250321在荣品的PRO-RK3566开发板的buildroot系统下使用ll命令【直接编译进IMG】

./buildroot/system/skeleton/etc/profile # some more ls aliases alias llls -alF alias lals -A alias lls -CF 20250321在荣品的PRO-RK3566开发板的buildroot系统下使用ll命令【直接编译进IMG】 2025/3/21 16:53 cd /etc/ echo "" >> # some more ls ali…...

Flink 自定义数据源:从理论到实践的全方位指南

目录 第一章:自定义数据源的基础概念 数据源是什么?它在 Flink 中扮演什么角色? Flink 的内置数据源:开箱即用的 “标配” 为什么需要自定义数据源?它的杀手锏在哪? 第二章:自定义数据源的实现之道 接口选择:从简单到高级,选对工具事半功倍 SourceFunction:入门…...

如何在 Java 中查找 PDF 页面大小(教程)

PDF 文件并未被 Java 直接支持。本教程将向您展示如何使用 JPedal Java PDF 库 以简单的步骤提取 PDF 文件的页面大小&#xff08;高度和宽度&#xff09;。页面大小可以以 厘米、英寸或像素 为单位获取。 为什么要使用第三方库处理 PDF 文件&#xff1f; PDF 文件是一种复杂…...

java版嘎嘎快充玉阳软件互联互通中电联云快充协议充电桩铁塔协议汽车单车一体充电系统源码uniapp

演示&#xff1a; 微信小程序&#xff1a;嘎嘎快充 http://server.s34.cn:1888/ 系统管理员 admin/123456 运营管理员 yyadmin/Yyadmin2024 运营商 operator/operator2024 系统特色&#xff1a; 多商户、汽车单车一体、互联互通、移动管理端&#xff08;开发中&#xff09; 另…...

使用Mastra.ai构建AI智能体:一次动手实践

Mastra框架提供了一种简洁高效的AI智能体构建方式。 本文将分享我使用Mastra.ai的实践经历。 我们将逐步完成环境搭建、探索框架核心功能,并构建一个能与工具交互的基础智能体。 过程中我会总结成功经验、遇到的问题以及收获的启示。 如果你对AI开发感兴趣,或正在寻找一个…...

Redis之大key问题

BigKey 常见面试题目 你会么&#xff1f; MoreKey 案例 大批量往redis里面插入2000W测试数据key Linux Bash下面执行&#xff0c;批量插入100W for((i1;i<100*10000;i)); do echo "set k$i v$i" >> /tmp/redisTest.txt ;done;生成100W条redis批量设置kv的…...

Excel第41套全国人口普查

2. 导入网页中的表格&#xff1a;数据-现有链接-考生文件夹&#xff1a;网页-找到表格-点击→变为√-导入删除外部链接关系&#xff1a;数据-点击链接-选中连接-删除-确定&#xff08;套用表格格式-也会是删除外部链接&#xff09;数值缩小10000倍&#xff08;除以10000即可&am…...

深度学习驱动的车牌识别:技术演进与未来挑战

一、引言 1.1 研究背景 在当今社会&#xff0c;智能交通系统的发展日益重要&#xff0c;而车牌识别作为其关键组成部分&#xff0c;发挥着至关重要的作用。车牌识别技术广泛应用于交通管理、停车场管理、安防监控等领域。在交通管理中&#xff0c;它可以用于车辆识别、交通违…...

PageHiOffice网页组件(WebOffice文档控件)开发集成技巧专题一

PageHiOffice网页组件作为最新一代的WebOffice文档控件&#xff0c;这是目前市场上唯一能做到在Chrome等最新版浏览器中实现内嵌网页运行的商用文档控件&#xff0c;是OA及ERP等系统处理各种文档的福音。从发布到完善已经超过3年&#xff0c;不管是功能性还是稳定性都已经有了长…...

A2 最佳学习方法

记录自己想法的最好理由是发现自己的想法&#xff0c;并将其组织成可传播的形式 (The best reason for recording what one thinks is to discover what one thinks and to organize it in transmittable form.) Prof Ackoff 经验之谈&#xff1a; 做培训或者写文章&#xff…...

从JVM底层揭开Java方法重载与重写的面纱:原理、区别与高频面试题突破

&#x1f31f;引言&#xff1a;一场由方法调用引发的"血案" 2018年&#xff0c;某电商平台在"双十一"大促期间遭遇严重系统故障。 技术团队排查发现&#xff0c;问题根源竟是一个继承体系中的方法重写未被正确处理&#xff0c;导致订单金额计算出现指数级…...

线程控制与线程操作

目录 线程的创建 tid pthread_self() 线程的退出 pthread_join 传参问题和返回值问题 pthread_exit 线程取消 线程分离 我们来学习线程的控制与线程操作 线程的创建 我们之前在线程的概念中就讲过了&#xff0c;我们可以通过pthread_create来创建一个或者多个子线程…...

Spring相关API

1是相对路径 2 是绝对路径 3 在注解时使用...

【GoLang】调用llm时提示词prompt的介绍以及使用方式

介绍 提示词是一种与大模型交互的对话格式&#xff0c;它以 JSON 格式定义了一个消息列表&#xff08;messages&#xff09;&#xff0c;包含了系统消息和用户消息。 我们向AI提问时&#xff0c;其实发给AI的都是提示词&#xff0c;别看我们只是简单输入了一句话&#xff0c;…...

[杂学笔记]锁为什么影响效率、I/O多路复用、三种I/O多路复用模型的区别、atomic原子操作类、MySQL的持久性是如何实现的

目录 1.锁为什么影响效率 2.I./O多路复用 3.三种I/O多路复用模型的区别 4.atomic原子操作类 介绍 常用函数 内存顺序含义 5.MySQL持久性的实现 1.锁为什么影响效率 线程阻塞与上下文切换&#xff1a;在多线程并发访问的场景下&#xff0c;只有一个线程能够进入临界区…...

AI Agent开发大全第八课-Stable Diffusion 3的本地安装全步骤

前言 就像我们前面几课所述,本系列是一门体系化的教学,它不像网上很多个别存在的单篇博客走“吃快餐”模式,而是从扎实的基础来带领大家一步步迈向AI开发高手。所以我们的AI课程设置是相当全面的,除了有牢固的基础知识外还有外面互联网上也搜不到的生产级实战。 前面讲过…...

Leetcode 刷题笔记 图论part05

卡码网 107 寻找存在的路径 初识并查集 并查集功能&#xff1a; 寻找根节点&#xff0c;函数: find(int u)&#xff0c;也就是判断这个节点的祖先节点是哪个将两个节点接入到同一个集合&#xff0c;函数: join(int u, int v)&#xff0c;将两个节点连在同一个根节点上判断两…...

NSSRound(持续更新)

了解过PHP特性吗 这个题相当于是php特性大杂烩 先看源代码 <?php error_reporting(0); highlight_file(__FILE__); include("rce.php"); $checker_1 FALSE; $checker_2 FALSE; $checker_3 FALSE; $checker_4 FALSE; $num $_GET[num]; if (preg_match(&qu…...

Python虚拟环境:从入门到实战指南

目录 一、为什么需要Python虚拟环境&#xff1f; 二、如何创建Python虚拟环境&#xff1f; 1. 使用venv&#xff08;Python 3.3内置&#xff09; 2. 使用virtualenv&#xff08;第三方工具&#xff09; 3. 使用conda&#xff08;适合数据科学项目&#xff09; 三、虚拟环…...

Python实现小红书app版爬虫

简介&#xff1a;由于数据需求的日益增大&#xff0c;小红书网页版已经不能满足我们日常工作的需求&#xff0c;为此&#xff0c;小编特地开发了小红书手机版算法&#xff0c;方便大家获取更多的数据&#xff0c;提升工作效率。 手机版接口主要包括&#xff1a;搜素&#xff0…...

注册中心之Nacos相较Eureka的提升分析

1. 传统拉取模式的缺陷&#xff08;如Eureka&#xff09; 在类似Eureka的注册中心中&#xff0c;消费者需要定时&#xff08;如每30秒&#xff09;主动拉取服务列表&#xff08;Pull模式&#xff09;。如果此时某个服务突然宕机&#xff0c;消费者可能无法立即感知&#xff0c…...

高数下---8.1平面与直线

目录 平面的确定 直线的确定 若要求某一直线或平面就根据要素来求。 例题 平面中的特殊情况 平面中的解题思路 直线的解题思路 平面的确定 两要素 一 一点 二 倾斜角 即法向量 点法式 可化为一般式 Ax By Cz D 0; (A,B,C) 即法向量&#xff1b; 改变D 即…...

【AI速读】30分钟搭建持续集成:用Jenkins拯救你的项目

每个开发者都踩过的坑 你有没有这样的经历?花了一周时间改代码,自信满满准备提交,结果合并同事的更新后,项目突然编译失败,测试跑不通。你焦头烂额地排查问题,老板还在催进度……但明明不是你的错! 这种“集成地狱”几乎每个团队都遇到过。传统的手动集成方式(比如每周…...

centos 9 编译安装 rtpengine (快方式)-使用 debian12 系统自带

1&#xff1a;更新系统包 dnf update 2&#xff1a;启用EPEL仓库&#xff08;提供额外软件包) # 安装EPEL仓库 sudo dnf install epel-release -y# 检查EPEL仓库是否启用&#xff08;输出应包含epel&#xff09; dnf repolist# 启用CRB仓库 sudo dnf config-manager --set-e…...

Android第六次面试总结(okhttp篇)

OkHttp 是一个高效的 HTTP 客户端&#xff0c;它的工作流程包含多个步骤&#xff0c;从请求的创建、发送&#xff0c;到服务器响应的接收和处理&#xff0c;每个环节都有特定的逻辑和组件参与。以下是对 OkHttp 工作流程的详细说明&#xff1a; 1. 请求构建 在使用 OkHttp 发…...

ngx_http_escape_location_name

定义在 src\http\ngx_http.c static ngx_int_t ngx_http_escape_location_name(ngx_conf_t *cf, ngx_http_core_loc_conf_t *clcf) {u_char *p;size_t len;uintptr_t escape;escape 2 * ngx_escape_uri(NULL, clcf->name.data, clcf->name.len,NGX_ESCAPE_U…...

QT网络通信的接口与使用

文章目录 前言1.服务端实现流程1.1步骤 1&#xff1a;创建 QTcpServer 并监听端口1.2步骤 2&#xff1a;处理新连接请求1.3步骤 3&#xff1a;接收客户端数据1.4步骤 4&#xff1a;处理客户端断开 2.客户端实现流程2.1步骤 1&#xff1a;创建 QTcpSocket 并连接服务器2.2步骤 2…...

基于生成对抗网络(GAN)的图像超分辨率重建:技术与应用

图像超分辨率重建(Super-Resolution, SR)是计算机视觉领域的重要任务,旨在从低分辨率图像中恢复出高分辨率图像。这一技术在医学影像、卫星图像、视频增强等领域具有广泛的应用价值。传统的超分辨率方法依赖于插值或基于模型的重建,效果有限。近年来,生成对抗网络(GAN)通…...

【spring对bean Request和Session的管理流程】

在 Spring 框架中&#xff0c;除了常见的 单例&#xff08;Singleton&#xff09; 和 原型&#xff08;Prototype&#xff09; 作用域外&#xff0c;还支持 Request 和 Session 作用域。这两种作用域主要用于 Web 应用程序中&#xff0c;分别表示 Bean 的生命周期与 HTTP 请求或…...

FastGPT原理分析-数据集创建第二步:处理任务的执行

概述 文章《FastGPT原理分析-数据集创建第一步》已经分析了数据集创建的第一步&#xff1a;文件上传和预处理的实现逻辑。本文介绍文件上传后&#xff0c;数据处理任务的具体实现逻辑。 数据集创建总体实现步骤 从上文可知数据集创建总体上来说分为两大步骤&#xff1a; &a…...