AudioTrack

news2025/3/22 14:50:56

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缓冲区的最小size

int bufsize= AudioTrack. getMinBufferSize( 8000, AudioFormat.CHANNEL_CONFIGURATION_STEREO,AudioFormat. ENCODING_PCM_16BIT);

//创建AudioTrack

AudioTrack 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 jint

android_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和PCM8

                    nativeChannelMask,

                    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.cpp



static 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_byte

    int 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 mState

        mState = 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,后调用play

        memcpy(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 playing

    synchronized(mPlayStateLock) {

        //调用native方法

        native_stop();

        baseStop();

        mPlayState = PLAYSTATE_STOPPED;

        mAvSyncHeader = null;

        mAvSyncBytesRemaining = 0;

    }

}

android_media_AudioTrack.cpp

android_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.cpp

static void android_media_AudioTrack_release(JNIEnv *env,  jobject thiz) {

    //释放资源

    sp<AudioTrack> lpTrack = setAudioTrack(env, thiz, 0);

    // delete the JNI data

    AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetLongField(

        thiz, javaAudioTrackFields.jniData);

    //之前保存在java对象中的指针变量此时要设置为0

    env->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.cpp

AudioTrack::AudioTrack()

    : mStatus(NO_INIT),//把初始状态设置为NO_INIT

      mState(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()) {

            //停止AudioTrackThread

            t->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实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2319624.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

多条件排序(C# and Lua)

C# 升序排序 OrderBy 按升序对序列的元素进行排序 ThenBy 按升序对序列中的元素执行后续排序 降序排序 OrderByDescending 按降序对序列的元素排序 ThenByDescending 按降序对序列中的元素执行后续排序 public class Fruit {public int id;public string name;publi…

人工智能之数学基础:线性方程组求解的得力助手——增广矩阵

本文重点 增广矩阵是一个极具实用价值的工具,尤其在处理线性方程组时,它展现了卓越的功效。通过整合系数和常数项,增广矩阵简化了计算过程并提供了判断方程组解集的有效方法。 增广矩阵的起源与定义 增广矩阵的概念源于线性方程组求解的需求。在解决线性方程组时,我们常…

关于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&#xff08;JSON Web Token&#xff09; 3. 认证方式对比与总结3.1 认证方式对比3.2 如何选择合适的认证方式&#xf…

【Android Studio开发】生命周期、Activity和组件通信(上)

零、前期配置 1.【Android】模式 2.点击【运行】&#xff0c;弹出模拟器 右侧是模拟机&#xff0c;显示Hello World 3. 打开【activity_main.xml】文件&#xff0c;点击【Design】&#xff0c;然后点击【Component Tree】 在弹出的Component Tree中右键【main】,选择【Conver…

【ES】Elasticsearch学习

文章目录 简单的安装 简单的安装 参考&#xff1a;https://blog.csdn.net/smilehappiness/article/details/118466378 官网&#xff1a;https://www.elastic.co/guide/en/elasticsearch/reference/current/targz.html 下载&#xff1a;https://www.elastic.co/cn/downloads/e…

实验三 Python 数据可视化 Python 聚类-K-means(CQUPT)

一、实验目的 Python 数据可视化&#xff1a; 1、学习使用 jieba、wordcloud 等类库生成词云图。 2、学习使用 Matplotlib 库进行数据可视化。 Python 聚类-K-means&#xff1a; 1、理解聚类非监督学习方法的基本原理。 2、掌握 Python、numpy、pandas、sklearn 实现聚类…

【STM32】SPI通信协议W25Q64Flash存储器芯片(学习笔记)

通信接口部分有介绍SPI&#xff1a;【STM32】USART串口协议&串口外设-学习笔记-CSDN博客 SPI通信协议 SPI通信 SPI&#xff08;Serial Peripheral Interface&#xff09;是由Motorola公司开发的一种通用数据总线四根通信线&#xff1a;SCK&#xff08;Serial Clock&…

SpringBoot最佳实践之 - 使用AOP记录操作日志

1. 前言 本篇博客是个人在工作中遇到的需求。针对此需求&#xff0c;开发了具体的实现代码。并不是普适的记录操作日志的方式。以阅读本篇博客的朋友&#xff0c;可以参考此篇博客中记录日志的方式&#xff0c;可能会对你有些许帮助和启发。 2. 需求描述 有一个后台管理系统…

第六届机电一体化技术与智能制造国际学术会议(ICMTIM 2025)

重要信息 4月11-13日 南京江北新区工业大学亚朵酒店 www.icmtim.org&#xff08;点击了解参会投稿等&#xff09; 简介 由南京工业大学主办&#xff0c;南京工业大学电气工程与控制科学学院、中国矿业大学、黑龙江大学、江苏省自动化学会承办的第六届机电一体化技术…

期刊分区表2025年名单下载(经济学、管理学)

2025年期刊分区表包括SCIE、SSCI、A&HCI、ESCI和OAJ&#xff0c;共设置了包括自然科学、社会科学和人文科学在内的21个大类 本次分享的是期刊分区表2025年名单经济学类、管理学类&#xff0c;一共7631025条 一、数据介绍 数据名称&#xff1a;期刊分区表2025年名单 数据…

八股学习-JUC java并发编程

本文仅供个人学习使用&#xff0c;参考资料&#xff1a;JMM&#xff08;Java 内存模型&#xff09;详解 | JavaGuide 线程基础概念 用户线程&#xff1a;由用户空间程序管理和调度的线程&#xff0c;运行在用户空间。 内核线程&#xff1a;由操作系统内核管理和调度的线程&…

PostgreSQL_数据下载并保存(psycopg2)

目录 前置&#xff1a; 1 数据下载 1.1 多个股票多个交易日 1.2 一个交易日所有股票 2 数据保存&#xff0c;使用python中的psycopg2包 2.1 在PyCharm中创建新项目&#xff0c;并安装包 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…

边缘计算革命:重构软件架构的范式与未来

摘要 边缘计算通过将算力下沉至网络边缘&#xff0c;正在颠覆传统中心化软件架构的设计逻辑。本文系统分析了边缘计算对软件架构的范式革新&#xff0c;包括分布式分层架构、实时资源调度、安全防护体系等技术变革&#xff0c;并结合工业物联网、智慧医疗等场景案例&#xff0c…

【读点论文】Chain Replication for Supporting High Throughput and Availability

在分布式系统中&#xff0c;强一致性往往和高可用、高吞吐是矛盾的。比如传统的关系型数据库&#xff0c;其保证了强一致性&#xff0c;但往往牺牲了可用性和吞吐量。而像 NoSQL 数据库&#xff0c;虽然其吞吐量、和扩展性很高&#xff0c;但往往只支持最终一致性&#xff0c;无…

Servlet、Servlet的5个接口方法、生命周期、以及模拟实现 HttpServlet 来写接口的基本原理

DAY15.1 Java核心基础 Servlet Servlet是一个接口&#xff0c;是java的基础&#xff0c;java之所以编写web的程序&#xff0c;接收请求并响应&#xff0c;就是因为Sevlet接口 Java 类实现了Servlet接口的时候就可以接收并响应请求&#xff0c;成为web服务器 Web服务器就是接…

贝叶斯公式的一个直观解释

E E E&#xff1a;抓到娃娃 H H H&#xff1a;坐地铁 H ˉ \bar H Hˉ&#xff1a;坐公交 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)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

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.立体系…