Mediacodec 如何硬件解码到纹理的

2023-05-16

Mediacodec 如何硬件解码到纹理的

背景:

网上很多关于mediacodec,surface ,surfacetexture的源码分析,以及内部原理,但是都局限于各自的内容,今天我们就从mediacodec硬件解码到纹理,以及外部纹理到底是什么角度的来分析源码。

mediacodec

 

这是googl官网提供的一张图https://developer.android.com/reference/android/media/MediaCodec,

大体上是生产者消费者之间的关系。

解码过程如下:

Java
 MediaCodec codec = MediaCodec.createByCodecName(name);
 codec.configure(format, …);
 MediaFormat outputFormat = codec.getOutputFormat(); // option B
 codec.start();
 for (;;) {
  int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
  if (inputBufferId >= 0) {
    ByteBuffer inputBuffer = codec.getInputBuffer(…);
    // fill inputBuffer with valid data
    …
    codec.queueInputBuffer(inputBufferId, …);
  }
  int outputBufferId = codec.dequeueOutputBuffer(…);
  if (outputBufferId >= 0) {
    ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
    MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
    // bufferFormat is identical to outputFormat
    // outputBuffer is ready to be processed or rendered.
    …
    codec.releaseOutputBuffer(outputBufferId, …);
  } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    // Subsequent data will conform to new format.
    // Can ignore if using getOutputFormat(outputBufferId)
    outputFormat = codec.getOutputFormat(); // option B
  }
 }
 codec.stop();
 codec.release();

因为我们要分析渲染到纹理的过程,我就着重看下releaseOutputBuffer这个方法

releaseOutputBuffer

Java
public void releaseOutputBuffer (int index,
                boolean render)
/*
If you are done with a buffer, use this call to return the buffer to the codec or to render it on the output surface. If you configured the codec with an output surface, setting render to true will first send the buffer to that output surface. The surface will release the buffer back to the codec once it is no longer used/displayed. Once an output buffer is released to the codec, it MUST NOT be used until it is later retrieved by getOutputBuffer(int) in response to a dequeueOutputBuffer(MediaCodec.BufferInfo, long) return value or a Callback#onOutputBufferAvailable callback.

Parameters
indexint: The index of a client-owned output buffer previously returned from a call to dequeueOutputBuffer(MediaCodec.BufferInfo, long).

renderboolean: If a valid surface was specified when configuring the codec, passing true renders this output buffer to the surface.
*/

可以看的到当render=true的时候就会渲染到surface,那我结合android framework的源码来跟下这个代码到底做了些什么

https://android.googlesource.com/platform/frameworks/base/+/master/media/jni/android_media_MediaCodec.cpp

C++
status_t JMediaCodec::releaseOutputBuffer(
        size_t index, bool render, bool updatePTS, int64_t timestampNs) {
    if (updatePTS) {
        return mCodec->renderOutputBufferAndRelease(index, timestampNs);
    }
    return render
        ? mCodec->renderOutputBufferAndRelease(index)
        : mCodec->releaseOutputBuffer(index);
}

可以看的出来当render=true的时候,调用的是renderOutputBufferAndRelease,那接下来我们来看下renderOutputBufferAndRelease他的源码

renderOutputBufferAndRelease

C++
status_t MediaCodec::renderOutputBufferAndRelease(size_t index, int64_t timestampNs) {
    sp<AMessage> msg = new AMessage(kWhatReleaseOutputBuffer, this);
    msg->setSize("index", index);
    msg->setInt32("render", true);
    msg->setInt64("timestampNs", timestampNs);

    sp<AMessage> response;
    return PostAndAwaitResponse(msg, &response);
}

mediacodec在start的时候会起一个线程,这里会这个解码线程发送一个消息,并设置msg->setInt32("render", true);那我们就看下它的接收方是怎么做的

C++
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
    ....
    case kWhatReleaseOutputBuffer:
        {
            sp<AReplyToken> replyID;
            CHECK(msg->senderAwaitsResponse(&replyID));
            status_t err = onReleaseOutputBuffer(msg);

            PostReplyWithError(replyID, err);
            break;
        }
        .........
    }
}

接着看下onReleaseOutputBuffer这个

onReleaseOutputBuffer

C++
tatus_t MediaCodec::onReleaseOutputBuffer(const sp<AMessage> &msg) {
    size_t index;
    CHECK(msg->findSize("index", &index));

    int32_t render;
    if (!msg->findInt32("render", &render)) {
        render = 0;
    }

    if (index >= mPortBuffers[kPortIndexOutput].size()) {
        return -ERANGE;
    }

    BufferInfo *info = &mPortBuffers[kPortIndexOutput][index];

    if (info->mData == nullptr || !info->mOwnedByClient) {
        return -EACCES;
    }

    // synchronization boundary for getBufferAndFormat
    sp<MediaCodecBuffer> buffer;
  
    if (render && buffer->size() != 0) {
        ......
        status_t err = mBufferChannel->renderOutputBuffer(buffer, renderTimeNs);

        if (err == NO_INIT) {
            ALOGE("rendering to non-initilized(obsolete) surface");
            return err;
        }
        if (err != OK) {
            ALOGI("rendring output error %d", err);
        }
    }
    return OK;
}
 

最终调用这个mBufferChannel->renderOutputBuffer(buffer, renderTimeNs),也就是mBufferChannel就是ACodecBufferChannel 这里就不去跟代码分析了,也就是我们要去找它对应的

ACodecBufferChannel::renderOutputBuffer

C++
status_t ACodecBufferChannel::renderOutputBuffer(
        const sp<MediaCodecBuffer> &buffer, int64_t timestampNs) {
    std::shared_ptr<const std::vector<const BufferInfo>> array(
            std::atomic_load(&mOutputBuffers));
    BufferInfoIterator it = findClientBuffer(array, buffer);
    if (it == array->end()) {
        return -ENOENT;
    }

    ALOGV("renderOutputBuffer #%d", it->mBufferId);
    sp<AMessage> msg = mOutputBufferDrained->dup();
    msg->setObject("buffer", buffer);
    msg->setInt32("buffer-id", it->mBufferId);
    msg->setInt32("render", true);
    msg->setInt64("timestampNs", timestampNs);
    msg->post();
    return OK;
}

可以看下mOutputBufferDrained这个是来自哪里

C++
ACodecBufferChannel::ACodecBufferChannel(
        const sp<AMessage> &inputBufferFilled, const sp<AMessage> &outputBufferDrained)
    : mInputBufferFilled(inputBufferFilled),
      mOutputBufferDrained(outputBufferDrained),
      mHeapSeqNum(-1) {
}
std::shared_ptr<BufferChannelBase> ACodec::getBufferChannel() {
    if (!mBufferChannel) {
        mBufferChannel = std::make_shared<ACodecBufferChannel>(
                new AMessage(kWhatInputBufferFilled, this),
                new AMessage(kWhatOutputBufferDrained, this));
    }
    return mBufferChannel;
}
status_t MediaCodec::init(const AString &name) {

    ..............
    mBufferChannel = mCodec->getBufferChannel();
    mBufferChannel->setCallback(
            std::unique_ptr<CodecBase::BufferCallback>(
                    new BufferCallback(new AMessage(kWhatCodecNotify, this))));
}

也就是说mOutputBufferDrained = new AMessage(kWhatOutputBufferDrained, this)

因此sp<AMessage> msg = mOutputBufferDrained->dup(); 的消息发送到了ACodec里边,那我们就看下消息接收方

C++
bool ACodec::BaseState::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
        case kWhatOutputBufferDrained:
        {
            onOutputBufferDrained(msg);
            break;
        }
}

代码又来了到了onOutputBufferDrained

ACodec::onOutputBufferDrained

C++

void ACodec::BaseState::onOutputBufferDrained(const sp<AMessage> &msg) {
    IOMX::buffer_id bufferID;
    CHECK(msg->findInt32("buffer-id", (int32_t*)&bufferID));
    sp<RefBase> obj;
    CHECK(msg->findObject("buffer", &obj));
    sp<MediaCodecBuffer> buffer = static_cast<MediaCodecBuffer *>(obj.get());
    int32_t discarded = 0;
    msg->findInt32("discarded", &discarded);


    info->mData = buffer;
    int32_t render;
    .........
     err = mCodec->mNativeWindow->queueBuffer(
            mCodec->mNativeWindow.get(), info->mGraphicBuffer.get(), info->mFenceFd);
    .........
}
 

mNativeWindow来自于你创建解码器通过texture-》创建的surfacetexture,又通过surfacetexture创建surface的native层结构就是mNativeWindow,mNativeWindow即使surfacetexture的生产者

 

借用网络上的一张图,可以看的出来mNativeWindow对应的就是surface,mNativeWindow->queueBuffer 就是生产者入队一帧,这一帧的数据是由解码器产生的,接下来我们就看下怎么入队的

C++

int Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd) {
    ATRACE_CALL();
    ALOGV("Surface::queueBuffer");
    Mutex::Autolock lock(mMutex);
    int64_t timestamp;
    bool isAutoTimestamp = false;
    .........
    int i = getSlotFromBufferLocked(buffer);
    nsecs_t now = systemTime();
    status_t err = mGraphicBufferProducer->queueBuffer(i, input, &output);
    mLastQueueDuration = systemTime() - now;
    if (err != OK)  {
        ALOGE("queueBuffer: error queuing buffer to SurfaceTexture, %d", err);
    }
    ............

    return err;
}
 

也就是最终mGraphicBufferProducer->queueBuffer把这个解码器输出数据给入队, 解码器输出的数据在一开始创建解码器的时候,从nativewidow->dequeue出来的数据

BufferQueueProducer::queueBuffer

C++

status_t BufferQueueProducer::queueBuffer(int slot,
        const QueueBufferInput &input, QueueBufferOutput *output) {
    ATRACE_CALL();
    ATRACE_BUFFER_INDEX(slot);
    ..........................
       
        item.mAcquireCalled = mSlots[slot].mAcquireCalled;
        item.mGraphicBuffer = mSlots[slot].mGraphicBuffer;
        item.mCrop = crop;
        item.mTransform = transform &
                ~static_cast<uint32_t>(NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY);
        item.mTransformToDisplayInverse =
                (transform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY) != 0;
        item.mScalingMode = static_cast<uint32_t>(scalingMode);
        item.mTimestamp = requestedPresentTimestamp;
        item.mIsAutoTimestamp = isAutoTimestamp;
        item.mDataSpace = dataSpace;
        item.mHdrMetadata = hdrMetadata;
        item.mFrameNumber = currentFrameNumber;
        item.mSlot = slot;
        item.mFence = acquireFence;
        item.mFenceTime = acquireFenceTime;
        item.mIsDroppable = mCore->mAsyncMode ||
                (mConsumerIsSurfaceFlinger && mCore->mQueueBufferCanDrop) ||
                (mCore->mLegacyBufferDrop && mCore->mQueueBufferCanDrop) ||
                (mCore->mSharedBufferMode && mCore->mSharedBufferSlot == slot);
        item.mSurfaceDamage = surfaceDamage;
        item.mQueuedBuffer = true;
        item.mAutoRefresh = mCore->mSharedBufferMode && mCore->mAutoRefresh;
        item.mApi = mCore->mConnectedApi;

        mStickyTransform = stickyTransform;

        output->bufferReplaced = false;
        if (mCore->mQueue.empty()) {
            // When the queue is empty, we can ignore mDequeueBufferCannotBlock
            // and simply queue this buffer
            mCore->mQueue.push_back(item);
            frameAvailableListener = mCore->mConsumerListener;
        } else {
                .......
                mCore->mQueue.push_back(item);
                frameAvailableListener = mCore->mConsumerListener;
                .......
        }

          ........
        if (frameAvailableListener != nullptr) {
            frameAvailableListener->onFrameAvailable(item);
        }
    }

    return NO_ERROR;
}
 

会把这块显存最终 mCore->mQueue.push_back(item) 放入到对队列中,最终通过 frameAvailableListener->onFrameAvailable(item) ,去一层一层的通知到Java层的QSurfaceTexture创建的设置的onFrameAvailable回调,最终通知调用我有一帧数据准备好了,你可以用QSurfaceTexture.updateTexImage()来更新数据到纹理上了,接下来我们就看下是怎么更新纹理的

如何更新到纹理的

QSurfaceTexture.updateTexImage()

C++
status_t SurfaceTexture::updateTexImage() {
    ATRACE_CALL();
    SFT_LOGV("updateTexImage");
    Mutex::Autolock lock(mMutex);

    if (mAbandoned) {
        SFT_LOGE("updateTexImage: SurfaceTexture is abandoned!");
        return NO_INIT;
    }

    return mEGLConsumer.updateTexImage(*this);
}

接下来看下mEGLConsumer.updateTexImage

EGLConsumer::updateTexImage

C++
status_t EGLConsumer::updateTexImage(SurfaceTexture& st){   
     BufferItem item;

    // Acquire the next buffer.
    // In asynchronous mode the list is guaranteed to be one buffer
    // deep, while in synchronous mode we use the oldest buffer.
    err = st.acquireBufferLocked(&item, 0);
   .......
    // Release the previous buffer.
    err = updateAndReleaseLocked(item, nullptr, st);
   ........
    // Bind the new buffer to the GL texture, and wait until it's ready.
    return bindTextureImageLocked(st);
 }

  接下来重点分析下这3个方法到底是干什么的吧

st.acquireBufferLocked

C++
status_t ConsumerBase::acquireBufferLocked(BufferItem *item,
        nsecs_t presentWhen, uint64_t maxFrameNumber) {
    if (mAbandoned) {
        CB_LOGE("acquireBufferLocked: ConsumerBase is abandoned!");
        return NO_INIT;
    }

    status_t err = mConsumer->acquireBuffer(item, presentWhen, maxFrameNumber);
    if (err != NO_ERROR) {
        return err;
    }

    if (item->mGraphicBuffer != nullptr) {
        if (mSlots[item->mSlot].mGraphicBuffer != nullptr) {
            freeBufferLocked(item->mSlot);
        }
        mSlots[item->mSlot].mGraphicBuffer = item->mGraphicBuffer;
    }

    mSlots[item->mSlot].mFrameNumber = item->mFrameNumber;
    mSlots[item->mSlot].mFence = item->mFence;

    CB_LOGV("acquireBufferLocked: -> slot=%d/%" PRIu64,
            item->mSlot, item->mFrameNumber);

    return OK;
}

这个正好跟上边入队对上了, 这里从消费者这里取出这个item,item包含了上述中这个mGraphicBuffer,接下来我们看下另外一个接口updateAndReleaseLocked

updateAndReleaseLocked

C++

status_t EGLConsumer::updateAndReleaseLocked(const BufferItem& item, PendingRelease* pendingRelease,
                                             SurfaceTexture& st) {
    status_t err = NO_ERROR;

    int slot = item.mSlot;
    ........
    err = mEglSlots[slot].mEglImage->createIfNeeded(mEglDisplay);
    if (err != NO_ERROR) {
        EGC_LOGW("updateAndRelease: unable to createImage on display=%p slot=%d", mEglDisplay,
                 slot);
        st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR);
        return UNKNOWN_ERROR;
    }
    ...................
     sp<EglImage> nextTextureImage = mEglSlots[slot].mEglImage;
    mCurrentTextureImage = nextTextureImage;
    return err;
}
status_t EGLConsumer::EglImage::createIfNeeded(EGLDisplay eglDisplay, bool forceCreation) {
    // If there's an image and it's no longer valid, destroy it.
 
   .......
   mEglImage = createImage(mEglDisplay, mGraphicBuffer);
   ........

    return OK;
}
EGLImageKHR EGLConsumer::EglImage::createImage(EGLDisplay dpy,
                                               const sp<GraphicBuffer>& graphicBuffer) {
    EGLClientBuffer cbuf = static_cast<EGLClientBuffer>(graphicBuffer->getNativeBuffer());
    const bool createProtectedImage =
            (graphicBuffer->getUsage() & GRALLOC_USAGE_PROTECTED) && hasEglProtectedContent();
    EGLint attrs[] = {
            EGL_IMAGE_PRESERVED_KHR,
            EGL_TRUE,
            createProtectedImage ? EGL_PROTECTED_CONTENT_EXT : EGL_NONE,
            createProtectedImage ? EGL_TRUE : EGL_NONE,
            EGL_NONE,
    };
    eglInitialize(dpy, nullptr, nullptr);
    EGLImageKHR image =
            eglCreateImageKHR(dpy, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, cbuf, attrs);
    if (image == EGL_NO_IMAGE_KHR) {
        EGLint error = eglGetError();
        ALOGE("error creating EGLImage: %#x", error);
        eglTerminate(dpy);
    }
    return image;
}
 

接下来重点看下eglCreateImageKHR

eglCreateImageKHR

C++

EGLAPI EGLImageKHR EGLAPIENTRY eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list)
{
        return egl::CreateImageKHR(dpy, ctx, target, buffer, attrib_list);
}

C++

EGLImageKHR CreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list)
{
        TRACE("(EGLDisplay dpy = %p, EGLContext ctx = %p, EGLenum target = 0x%X, buffer = %p, const EGLint attrib_list = %p)", dpy, ctx, target, buffer, attrib_list);

        egl::Display *display = egl::Display::get(dpy);
        egl::Context *context = static_cast<egl::Context*>(ctx);

        .................
        GLuint name = static_cast<GLuint>(reinterpret_cast<uintptr_t>(buffer));

        if(name == 0)
        {
                return error(EGL_BAD_PARAMETER, EGL_NO_IMAGE_KHR);
        }
        if(target == EGL_NATIVE_BUFFER_ANDROID)
        {
                ANativeWindowBuffer *nativeBuffer = reinterpret_cast<ANativeWindowBuffer*>(buffer);

                if(!nativeBuffer || GLPixelFormatFromAndroid(nativeBuffer->format) == GL_NONE)
                {
                        ALOGW("%s badness unsupported HAL format=%x", __FUNCTION__, nativeBuffer ? nativeBuffer->format : 0);
                        return error(EGL_BAD_ATTRIBUTE, EGL_NO_IMAGE_KHR);
                }

                Image *image = new AndroidNativeImage(nativeBuffer);
                EGLImageKHR eglImage = display->createSharedImage(image);

                return success(eglImage);
        }

        return success(eglImage);
}
EGLImageKHR Display::createSharedImage(Image *image)
{
        return reinterpret_cast<EGLImageKHR>((intptr_t)mSharedImageNameSpace.allocate(image));
}

经过上边的源码可以看的出来updateAndReleaseLocked 最终把一个grapicbuffer转换成了         EGLImageKHR 记录在了mEglSlots[slot].mEglImage,也就是mCurrentTextureImage,这个mCurrentTextureImage 在接下来纹理绑定也是直管重要的,那我们就在回到bindTextureImageLocked

bindTextureImageLocked

C++

status_t EGLConsumer::bindTextureImageLocked(SurfaceTexture& st) {
    if (mEglDisplay == EGL_NO_DISPLAY) {
        ALOGE("bindTextureImage: invalid display");
        return INVALID_OPERATION;
    }

  
    glBindTexture(st.mTexTarget, st.mTexName);
    if (st.mCurrentTexture == BufferQueue::INVALID_BUFFER_SLOT && mCurrentTextureImage == nullptr) {
        EGC_LOGE("bindTextureImage: no currently-bound texture");
        return NO_INIT;
    }
    ........................

    mCurrentTextureImage->bindToTextureTarget(st.mTexTarget);

    ........................
    // Wait for the new buffer to be ready.
    return doGLFenceWaitLocked(st);
}
 

glBindTexture

C++

GL_APICALL void GL_APIENTRY glBindTexture(GLenum target, GLuint texture)
{
        return es2::BindTexture(target, texture);
}

void BindTexture(GLenum target, GLuint texture)
{
        TRACE("(GLenum target = 0x%X, GLuint texture = %d)", target, texture);

        es2::Context *context = es2::getContext();

        if(context)
        {
                switch(target)
                {
         
                case GL_TEXTURE_EXTERNAL_OES:
                        context->bindTextureExternal(texture);
                        break;
 
                }
        }
}

void Context::bindTextureExternal(GLuint texture)
{
        mResourceManager->checkTextureAllocation(texture, TEXTURE_EXTERNAL);

        mState.samplerTexture[TEXTURE_EXTERNAL][mState.activeSampler] = getTexture(texture);
}

void ResourceManager::checkTextureAllocation(GLuint texture, TextureType type)
{
        if(!getTexture(texture) && texture != 0)
        {
                Texture *textureObject;

                if(type == TEXTURE_2D)
                {
                        textureObject = new Texture2D(texture);
                }
                else if(type == TEXTURE_CUBE)
                {
                        textureObject = new TextureCubeMap(texture);
                }
                else
                {
                        UNREACHABLE(type);
                        return;
                }

                mTextureMap[texture] = textureObject;
                textureObject->addRef();
        }
}
 

接下来看下bindToTextureTarget的实现

bindToTextureTarget

C++
void EGLConsumer::EglImage::bindToTextureTarget(uint32_t texTarget) {
    glEGLImageTargetTexture2DOES(texTarget, static_cast<GLeglImageOES>(mEglImage));
}
GL_APICALL void GL_APIENTRY glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image)
{
        return es2::EGLImageTargetTexture2DOES(target, image);
}

void EGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image)
{
     
        es2::Context *context = es2::getContext();

        if(context)
        {
                es2::Texture2D *texture = nullptr;

                switch(target)
                {
                case GL_TEXTURE_2D:           texture = context->getTexture2D();       break;
                case GL_TEXTURE_EXTERNAL_OES: texture = context->getTextureExternal(); break;
                default:                      UNREACHABLE(target);
                }

             

                egl::Image *eglImage = context->getSharedImage(image);

                if(!eglImage)
                {
                        return error(GL_INVALID_OPERATION);
                }

                texture->setSharedImage(eglImage);
        }
}
 

texTarget在surfacetexturenatice层创建的时候就已经确定好了GL_TEXTURE_EXTERNAL_OES

context->getSharedImage(image); 把上边创建的好的image给取出来,然后把这块数据通过 texture->setSharedImage(eglImage),替换texture内部的数据。

总结:

至此分析到这,大家可以清晰看出来的,外部纹理到底是怎么回事了,本质就是把外部一块显存转化为纹理的内部的显存使用,对于大家以后使用外部纹理可以更放使用了

为什么当使用外部纹理的时候需要用GL_TEXTURE_EXTERNAL_OES进行绑定

通过上边的分析我们知道在surfacetexture内部绑定纹理的是GL_TEXTURE_EXTERNAL_OES,在context专门为外部纹理建立了缓存,当我们使用这个纹理作为输入纹理的,如果不是在OES则会直接找不到这个纹理,导致渲染效果异常

C++
  mResourceManager->checkTextureAllocation(texture, TEXTURE_EXTERNAL);

        mState.samplerTexture[TEXTURE_EXTERNAL][mState.activeSampler] = getTexture(texture);
 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Mediacodec 如何硬件解码到纹理的 的相关文章

  • Sublime Text 4 解决 python 代码自动补全问题

    目录 一 下载安装 Anaconda 插件二 修改配置 xff0c 链接 python 库三 补充 xff1a 对不同的 python虚拟环境 分别配置不同的运行环境 Sublime Text 默认是对 python 代码不进行自动补全的
  • CentOS 7 升级安装 Python 3.9 版本

    由于 yum install python3 默认安装的 Python 版本较低 xff0c 现如今有更高版本的 Python 需求 xff0c 就想用编译安装的方法安装一个较高版本的 Python xff0c 顺道记录一下安装过程 注意
  • CentOS 7 升级 cmke 版本

    操作步骤 删除老版本的 cmake yum remove span class token parameter variable y span cmake 安装所需模块 yum span class token function insta
  • CentOS 7 下载安装 Java JDK 17

    操作步骤 利用 wget 下载 Java JDK 17 到 usr local java 安装目录中 xff0c 并解压缩 span class token comment 进入安装目录 span span class token buil
  • 每日一题(力扣)计划

    983 最低票价 问题描述 在一个火车旅行很受欢迎的国度 xff0c 你提前一年计划了一些火车旅行 在接下来的一年里 xff0c 你要旅行的日子将以一个名为 days 的数组给出 每一项是一个从 1 到 365 的整数 火车票有三种不同的销
  • 算数左移、逻辑右移、算数右移、逻辑右移

    算术左移和逻辑左移一样都是右边补0 算术左移 xff08 适用于有符号数 xff09 高位移出 xff0c 低位补0 xff08 要注意数据位最高位为1时极有可能溢出 xff09 比如 0010 1011B 算术左移一位 01010110B
  • java使用ssl连接mysql

    Connecting Securely Using SSL 官方文档 xff0c 演示使用 上面文章里 Setting up Server Authentication 就是在客户端设置认证mysql服务器 xff0c Setting up
  • amd和arm安装ffmpeg踩坑报错处理

    一 amd安装ffmpeg yum安装 系统版本 xff1a CentOS Linux release 7 2 1511 Core 内核版本 xff1a 3 10 0 862 el7 x86 64 1 SMP Fri Apr 20 16 4
  • Powershell常用命令(一)

    Invoke Command xff08 icm xff09 功能 xff1a 在本地或者远程运行命令 xff0c 命令执行的结果 span class token punctuation span 含错误 span class token
  • Ubuntu 突然无法联网,ifconfig 查看仅回环IP时,ping 其他IP显示connect: Network is unreachable的解决方法

    场景 xff1a Ubuntu先前使用时可以联网 xff0c 本地私有ip也存在 xff0c 某次打开Ubuntu后 xff0c 用ifconfig查看IP仅本地的回环ip xff0c ping 别的ip显示connect Network
  • linux命令_复制文件/目录到指定目录下

    复制文件 xff1a 把1 txt 复制到根目录下的sbin目录 cp 43 文件名 xff08 可带路径 xff09 43 目标路径 xff08 带路径 xff09 如 xff1a cp 1 txt sbin 复制目录 xff1a 把re
  • 栈(一本通)

    第一类型 xff1a 车厢调度 进栈顺序是固定的1 2 3 4 xff0c 判断能不能按照给定的顺序出来 include lt iostream gt include lt cstring gt include lt cmath gt in
  • 一本通:1191流感传染

    一本通 xff1a 流感传染 题目描述 有一批易感人群住在网格状的宿舍区内 xff0c 宿舍区为n n的矩阵 xff0c 每个格点为一个房间 xff0c 房间里可能住人 xff0c 也可能空着 在第一天 xff0c 有些房间里的人得了流感
  • CentOS 7 使用iptables 开放端口

    转自 https www cnblogs com kyuang p 6307535 html CentOS 7 0默认使用的是firewall作为防火墙 xff0c 这里改为iptables防火墙 1 关闭firewall xff1a sy
  • 模拟幅度调制系统抗干扰性能仿真分析

    目录 1 引言2 系统模型和仿真1 DSB SC调制和解调1 调制过程2 解调过程3 信噪比分析4 matlab代码 2 AM调制和解调1 调制过程2 解调过程 xff1a 3 信噪比分析4 matlab代码 3 SSB调制和解调1 调制过
  • 解决重启后 /mnt/hgfs下共享文件夹挂载失效问题

    问题描述 xff1a 如题 xff0c 在 mn hgfs 下使用ls命令不显示共享文件夹 xff0c 在root权限下运行命令 xff1a vmhgfs fuse host mnt hgfs 后重新进入目录显示共享文件夹 xff0c 重启
  • Matlab安装硬件支持包补充@水月流荧

    您好 xff0c 请问我按照文章内容下载了支持包 xff0c 并且将archive拷贝到E matlab bin win64 xff08 本机位置 xff09 中 xff0c 后在win64中找到SupportSoftwareInstall
  • spring-boot-maven-plugin未指定版本导致的编译错误

    spring boot maven plugin未指定版本导致的编译错误 报错 springboot应用在使用maven编译时会报如下错误 xff1a Java Runtime class file version 61 0 this ve
  • ssh连接Centos7时vim无法使用粘贴板原因及解决方法

    原因 xff1a 1 命令行的vim没有 43 和 两个寄存器 xff08 原因之一 xff09 xff0c 退出vim后粘贴板内容不会保留到vim所在系统的粘贴板 2 在有 43 和 寄存器的前提下 xff0c 可以从vim复制到vim所
  • >Error:com.android.tools.aapt2.Aapt2Exception: AAPT2 error: check logs for details

    gt Error com android tools aapt2 Aapt2Exception AAPT2 error check logs for details 这个错误会在3 0里面出现 解决办法是在gradle properties

随机推荐

  • 一键部署k8s脚本

    一键部署k8s脚本 目录 一键部署k8s脚本前情提示环境准备硬件系统要求角色说明 xff1a 其余准备工作 centos准备centos网络准备centos软件准备 部署k8s前配置下载部署脚本安装 Ansible 运行环境修改安装的配置文
  • Docker in docker 实现

    Docker in docker 文章目录 Docker in docker原理实现 xff08 centos7 xff09 常见问题参考 在docker容器内运行docker一般是不被倡导的 但有些场景和业务上 xff0c 需要在容器内使
  • 一篇文章入门spring

    Spring入门1 在之前我们对象的创建都是我们自己new出来的 xff0c 比如Student stu 61 new Student xff08 xff09 但是我们现在有了spring 我们将对象的创建的工作交给spring来处理 那么
  • JAVA数据结构之数组详细教程

    本文整理了java数据结构的数组操作 希望对刚入门数据结构的同志们有帮助 java数组非常简单 只要有JAVA语言基础就可以看这篇博文 大家不要害怕 非常简单 整理博客真的很花费时间 xff0c 如果对大家有帮助 xff0c 麻烦点赞评论
  • JAVA程序(MongoTemplate)操作mongodb数据库常用方法(超级详细)

    这里使用的是Spring 43 MongoTemplate来操作mongodb数据库 如果有不了解spring的同志先去了解一下spring为好 xff0c 这里给出实现的一些方法 主要有 xff1a 查询 增加 修改 删除 多字段增加 模
  • template操作mongodb数据库(更新方法大全)

    本文是使用JAVA程序操作MongoDB数据库 里面提供了各种更新数据的方法 xff0c 查询的各种方法会在后面进行更新 本文只是提供了数据库更新操作的一些方法 数据库数据和字段如下 xff1a 对于更新数据 xff0c 我将更新数据的方法
  • mongodb template 计算mongodb日期的解决方案

    mongodb由于特殊的日期格式 存在 8时区的问题 所以在使用java程序解决日期计算问题就会有点麻烦 其实也很简单 就是先将日期改变时区 然后转成带有格式String类型的日期 然后在java里面的将String转化成date类型即可
  • 一篇文章彻底理解二分搜索树(附代码实现)

    本文使用JAVA语言进行描述 其实本质上什么语言描述数据结构都是可以的 二叉树基础 二叉树的根节点 二叉树递归结构 xff1a 上面是一个满二叉树 但是实际中也有二叉树不是满的 二分搜索树 二分搜索树也不一定是满的 所以使用二分搜索树需要具
  • opengl 源码分析常见问题

    Opengl 一些问题解答 为什么opengl 不能跨线程 大家有没有想过这个问题 xff0c 网上给出的答案其实看得不太明白 xff0c 接下来我们看源码让你知道 C EGLContext Display createContext EG
  • mongodbtamplate使用程序创建mongdb索引的解决方案

    话不多说 xff0c 直接上代码 xff1a span class token keyword public span span class token keyword boolean span span class token funct
  • el表达式取不到值

    在jsp页面中有可能出现el表达式取不到值的问题 xff0c 但是反复检查代码 xff0c 跑断点都没有问题 xff0c 这是因为jsp忽略了el表达式 所以只要加上下面一行代码就可以了 span class token operator
  • Kaggle心脏病数据集为例学习机器学习的可解释性分析

    需要安装的工具包 pip install numpy pandas matplotlib seaborn wheel pandas profiling jupyter notebook span class token operator s
  • readlink /var/lib/docker/overlay2: invalid argument的解决方案

    发生这种情况是因为在运行Docker映像之间重新启动了docker xff0c 该映像已损坏 我重新启动系统 xff0c 然后运行以下命令 docker compose build no cache docker compose up 您还
  • python调用IP摄像头

    利用RTSP 43 opencv就可以实现网络摄像头的调用 rtsp是实时流传输协议 xff0c 是基于TCP IP协议体系中的一个应用层协议 xff0c 可以控制声音或者影像的多媒体串流协议 但是不同品牌的摄像头有不同的RTSP地址 下面
  • 22岁-时光如河,浮生为鱼

    时光如河 xff0c 浮生为 x1f41f 还没有学会告别 xff0c 四年就这样悄悄过去了 如往年今日一样 xff0c 依旧写些懒懒散散的文字致敬这一年的时光 x1f495 22岁生日快乐 x1f495 全文约4200字 xff0c 阅读
  • 电子书下载网站汇总

    网站名称地址简介语言推荐指数备注Book4Uhttp www book4you sk 外文下载网站斯洛伐克语 BookYardshttps www bookyards com en welcome主要面向教师的门户网站 xff0c 其中的书
  • Docker版 E5续订的E5调用API续订服务:Microsoft 365 E5 Renew X

    本文是基于作者SundayRX提出的E5 调用API续订服务 xff1a Microsoft 365 E5 Renew X的基础上提出的Docker版本的E5调用API续订服务 基础的账号注册等过程见SundayRX的博客 xff1a 账号
  • Docker版 Linux百度网盘备份工具

    一些必须要知道的事 xff1a 这个镜像的主要目的是为了将服务器或者群晖等linux场景中的资料备份到百度网盘中容器的基础镜像为ubuntu镜像 容器的备份周期为每天的凌晨两点 具体步骤如下 xff1a 下载镜像 docker pull h
  • 操作系统(五)中断和异常

    1 5 中断和异常 在上节内核态与用户态的转换过程中曾经提到过 xff0c 操作系统会响应中断信号强制夺回CPU使用权 xff0c 使用户态转换为内核态 中断 是操作系统夺回CPU使用权的唯一方式 xff0c 如果没有 中断 机制 xff0
  • Mediacodec 如何硬件解码到纹理的

    Mediacodec 如何硬件解码到纹理的 背景 xff1a 网上很多关于mediacodec xff0c surface xff0c surfacetexture的源码分析 xff0c 以及内部原理 xff0c 但是都局限于各自的内容 x