RK3399 Android 10 Camera2保存录像时缩略图获取为空

news2025/1/11 16:49:39

RK3399 Android 10相机录像保存时无法获取缩略预览图

  • 先找到录像点击按钮
//点击快门按钮时可以通过log打印看到停止录像的流程

log1

  • onShutterButtonClick()
//这里主要看停止的流程即stop = true时会进入onStopVideoRecording方法
public void onShutterButtonClick() {
        Log.d(TAG, "onShutterButtonClick");
        if (mSwitchingCamera || !mAppController.getCameraAppUI().isModeCoverHide()) {
            Log.d(TAG," mSwitchingCamera or isModeCoverHide not record!!!!!");
            return;
        }
        boolean stop = mMediaRecorderRecording;

        if (stop) {
            long delta = SystemClock.uptimeMillis() - mRecordingStartTime;
            Log.i(TAG, "mRecordingStartTime = " + mRecordingStartTime + ", delta = " + delta);
            if (delta < 1500) {
                Log.i(TAG, "recorder duration too short");
                return;
            }
            // CameraAppUI mishandles mode option enable/disable
            // for video, override that
            mAppController.getCameraAppUI().enableModeOptions();
            onStopVideoRecording();
        } else {
            int countDownDuration = mActivity.getSettingsManager()
                .getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION);
            mTimerDuration = countDownDuration;
            if (countDownDuration > 0) {
                // Start count down.
                mAppController.getCameraAppUI().transitionToCancel();
                mAppController.getCameraAppUI().hideModeOptions();
                mUI.startCountdown(countDownDuration);
                return;
            } else {
                // CameraAppUI mishandles mode option enable/disable
                // for video, override that
                mAppController.getCameraAppUI().disableModeOptions();
                startVideoRecording();
            }
        }
        mAppController.setShutterEnabled(false);
        if (mCameraSettings != null) {
            mFocusManager.onShutterUp(mCameraSettings.getCurrentFocusMode());
        }

        // Keep the shutter button disabled when in video capture intent
        // mode and recording is stopped. It'll be re-enabled when
        // re-take button is clicked.
        if (!(mIsVideoCaptureIntent && stop)) {
            mHandler.sendEmptyMessageDelayed(MSG_ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
        }
    }
  • onStopVideoRecording()
//这里可以看到有调用stopVideoRecording方法
private void onStopVideoRecording() {
        AudioManager am = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);
        am.abandonAudioFocus(null);
        mAppController.getCameraAppUI().setSwipeEnabled(true);
        boolean recordFail = stopVideoRecording();
        if (mIsVideoCaptureIntent) {
            if (mQuickCapture) {
                doReturnToCaller(!recordFail);
            } else if (!recordFail) {
                showCaptureResult();
            }
            mAppController.getCameraAppUI().disableModeOptions();
        } else if (!recordFail){
            // Start capture animation.
            if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
                // The capture animation is disabled on ICS because we use SurfaceView
                // for preview during recording. When the recording is done, we switch
                // back to use SurfaceTexture for preview and we need to stop then start
                // the preview. This will cause the preview flicker since the preview
                // will not be continuous for a short period of time.
                //mAppController.startFlashAnimation(false);
            }
        }
    }
  • stopVideoRecording()
private boolean stopVideoRecording() {
        // Do nothing if camera device is still capturing photo. Monkey test can trigger app crashes
        // (b/17313985) without this check. Crash could also be reproduced by continuously tapping
        // on shutter button and preview with two fingers.
        if (mSnapshotInProgress) {
            Log.v(TAG, "Skip stopVideoRecording since snapshot in progress");
            return true;
        }
        Log.v(TAG, "stopVideoRecording");

        // Re-enable sound as early as possible to avoid interfering with stop
        // recording sound.
        restoreRingerMode();

        mUI.setSwipingEnabled(true);
        mUI.showPassiveFocusIndicator();
        mAppController.getCameraAppUI().setShouldSuppressCaptureIndicator(false);

        boolean fail = false;
    	//因为mMediaRecorderRecording等于true,所以会走入下面的判断
        if (mMediaRecorderRecording) {
            boolean shouldAddToMediaStoreNow = false;

            try {
                mMediaRecorder.setOnErrorListener(null);
                mMediaRecorder.setOnInfoListener(null);
                mMediaRecorder.stop();
                //这里将shouldAddToMediaStoreNow设置为true
                shouldAddToMediaStoreNow = true;
                mCurrentVideoFilename = mVideoFilename;
                Log.v(TAG, "stopVideoRecording: current video filename: " + mCurrentVideoFilename);
            } catch (RuntimeException e) {
                Log.e(TAG, "stop fail",  e);
                if (mVideoFilename != null) {
                    deleteVideoFile(mVideoFilename);
                }
                fail = true;
            }
            mMediaRecorderRecording = false;
            mActivity.unlockOrientation();

            // If the activity is paused, this means activity is interrupted
            // during recording. Release the camera as soon as possible because
            // face unlock or other applications may need to use the camera.
            if (mPaused) {
                // b/16300704: Monkey is fast so it could pause the module while recording.
                // stopPreview should definitely be called before switching off.
                stopPreview();
                closeCamera();
            }

            mUI.showRecordingUI(false);
            // The orientation was fixed during video recording. Now make it
            // reflect the device orientation as video recording is stopped.
            mUI.setOrientationIndicator(0, true);
            mActivity.enableKeepScreenOn(false);
            //上面有将shouldAddToMediaStoreNow设置为true 同事fail默认为false,因此会走入下面的if语句
            if (shouldAddToMediaStoreNow && !fail) {
                //然后这里进入saveVideo方法
                if (mVideoFileDescriptor == null) {
                    saveVideo();
                } else if (mIsVideoCaptureIntent) {
                    // if no file save is needed, we can show the post capture UI now
                    showCaptureResult();
                }
            }
        }
        // release media recorder
        releaseMediaRecorder();

        mAppController.getCameraAppUI().showModeOptions();
        mAppController.getCameraAppUI().animateBottomBarToFullSize(mShutterIconId);
        if (!mPaused && mCameraDevice != null) {
            setFocusParameters();
            mCameraDevice.lock();
            if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
                stopPreview();
                // Switch back to use SurfaceTexture for preview.
                startPreview();
            }
            // Update the parameters here because the parameters might have been altered
            // by MediaRecorder.
            mCameraSettings = mCameraDevice.getSettings();
        }

        // Check this in advance of each shot so we don't add to shutter
        // latency. It's true that someone else could write to the SD card
        // in the mean time and fill it, but that could have happened
        // between the shutter press and saving the file too.
        mActivity.updateStorageSpaceAndHint(null);

        return fail;
    }
  • saveVideo()里面是通过MediaSaver接口的实现类MediaSaverImpI.addVideo方法
private void saveVideo() {
        if (mVideoFileDescriptor == null) {
            long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
            if (duration > 0) {
                //
            } else {
                Log.w(TAG, "Video duration <= 0 : " + duration);
            }
            mCurrentVideoValues.put(Video.Media.SIZE, new File(mCurrentVideoFilename).length());
            mCurrentVideoValues.put(Video.Media.DURATION, duration);
            getServices().getMediaSaver().addVideo(mCurrentVideoFilename,
                    mCurrentVideoValues, mOnVideoSavedListener);
            logVideoCapture(duration);
        }
        mCurrentVideoValues = null;
    }
  • 然后addVideo方法中new 了一个 AsyncTask
@Override
public void addVideo(String path, ContentValues values, OnMediaSavedListener l) {
    // We don't set a queue limit for video saving because the file
    // is already in the storage. Only updating the database.
    new VideoSaveTask(path, values, l, mContentResolver).execute();
}
//VideoSaveTask在onPostExecute方法将uri通过构造函数中的OnMediaSavedListener即mOnVideoSavedListener将其传递出去
private final MediaSaver.OnMediaSavedListener mOnVideoSavedListener =
            new MediaSaver.OnMediaSavedListener() {
                @Override
                public void onMediaSaved(Uri uri) {
                    if (uri != null) {
                        mCurrentVideoUri = uri;
                        mCurrentVideoUriFromMediaSaved = true;
                        onVideoSaved();
                        //然后通过notifyNewMedia又走到CameraActivity中的notifyNewMedia方法,从上面的log打印也可以看到
                        mActivity.notifyNewMedia(uri);
                    }
                }
            };
private class VideoSaveTask extends AsyncTask <Void, Void, Uri> {
        private String path;
        private final ContentValues values;
        private final OnMediaSavedListener listener;
        private final ContentResolver resolver;

        public VideoSaveTask(String path, ContentValues values, OnMediaSavedListener l,
                             ContentResolver r) {
            this.path = path;
            this.values = new ContentValues(values);
            this.listener = l;
            this.resolver = r;
        }

        @Override
        protected Uri doInBackground(Void... v) {
            Uri uri = null;
            try {
                Uri videoTable = Uri.parse(VIDEO_BASE_URI);
                uri = resolver.insert(videoTable, values);

                // Rename the video file to the final name. This avoids other
                // apps reading incomplete data.  We need to do it after we are
                // certain that the previous insert to MediaProvider is completed.
                String finalName = values.getAsString(Video.Media.DATA);
                finalName = finalName.substring(0, finalName.length() - 4);
                if (path.startsWith("/storage") && !path.startsWith(Storage.FLASH_DIR) && !(new File(Storage.EXTENAL_SD).canWrite())) {
                    path = path.replaceFirst("/storage/" , "/mnt/media_rw/");
                    if (finalName.startsWith("/storage") && !finalName.startsWith(Storage.FLASH_DIR))
                        finalName = finalName.replaceFirst("/storage/" , "/mnt/media_rw/");
                }
                File finalFile = new File(finalName);
                if (new File(path).renameTo(finalFile)) {
                    path = finalName;
                }
                values.put(Video.Media.DATA, finalName);
                resolver.update(uri, values, null, null);
            } catch (Exception e) {
                // We failed to insert into the database. This can happen if
                // the SD card is unmounted.
                Log.e(TAG, "failed to add video to media store", e);
                uri = null;
            } finally {
                Log.v(TAG, "Current video URI: " + uri);
            }
            return uri;
        }

        @Override
        protected void onPostExecute(Uri uri) {
            if (listener != null) {
                listener.onMediaSaved(uri);
            }
        }
    }

log2

  • notifyNewMedia方法这里可以看到里面又new了一个AsyncTask,这里直接看onPostExecute方法,上图中log的打印也可以证实这一点
@Override
    public void notifyNewMedia(Uri uri) {
        if (mPaused) {
            return;
        }
        // TODO: This method is running on the main thread. Also we should get
        // rid of that AsyncTask.

        updateStorageSpaceAndHint(null);
        ContentResolver cr = getContentResolver();
        String mimeType = cr.getType(uri);
        Log.v(TAG,"===============notifyNewMedia===================");
        if(mimeType == null) {
            Log.e(TAG, "Can't find video data in content resolver:" + uri);
            return;
        }
        FilmstripItem newData = null;
        if (FilmstripItemUtils.isMimeTypeVideo(mimeType)) {
            sendBroadcast(new Intent(CameraUtil.ACTION_NEW_VIDEO, uri).setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION));
            newData = mVideoItemFactory.queryContentUri(uri);
            if (newData == null) {
                Log.e(TAG, "Can't find video data in content resolver:" + uri);
                return;
            }
        } else if (FilmstripItemUtils.isMimeTypeImage(mimeType)) {
            CameraUtil.broadcastNewPicture(mAppContext, uri);
            newData = mPhotoItemFactory.queryContentUri(uri);
            if (newData == null) {
                Log.e(TAG, "Can't find photo data in content resolver:" + uri);
                return;
            }
        } else {
            Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri);
            return;
        }

        // We are preloading the metadata for new video since we need the
        // rotation info for the thumbnail.
        new AsyncTask<FilmstripItem, Void, FilmstripItem>() {
            @Override
            protected FilmstripItem doInBackground(FilmstripItem... params) {
                FilmstripItem data = params[0];
                MetadataLoader.loadMetadata(getAndroidContext(), data);
                return data;
            }

            @Override
            protected void onPostExecute(final FilmstripItem data) {
                // TODO: Figure out why sometimes the data is aleady there.
                mDataAdapter.addOrUpdate(data);

                // Legacy modules don't use CaptureSession, so we show the capture indicator when
                // the item was safed.
                //不管是拍照还是录像模块都会进入
                if (mCurrentModule instanceof PhotoModule ||
                        mCurrentModule instanceof VideoModule) {
                    AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
                        @Override
                        public void run() {
                            Log.d(TAG, "generateThumbnail onPostExecute");
                            //然后在这里获取缩略图,这里主要是通过FilmstripItem的实现类VideoItem
                            final Optional<Bitmap> bitmap = data.generateThumbnail(
                                    mAboveFilmstripControlLayout.getWidth(),
                                    mAboveFilmstripControlLayout.getMeasuredHeight());
                            //有缩略图就显示出来,但是log显示了一个空指针,因此可以判断肯定是获取缩略图有问题,导致无法显示
                            if (bitmap.isPresent()) {
                                indicateCapture(bitmap.get(), 0);
                            }
                            Log.d(TAG, "generateThumbnail onPostExecute end");
                        }
                    });
                }
            }
        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, newData);
    }

VideoItem中generateThumbnail方法
@Override
    public Optional<Bitmap> generateThumbnail(int boundingWidthPx, int boundingHeightPx) {
        return Optional.fromNullable(FilmstripItemUtils.loadVideoThumbnail(
                getData().getFilePath()));
    }
  • 然后可以看到真正获取缩略图是通过MediaMetadataRetriever的两个方法获取的,loadVideoThumbnail 和loadVideoThumbnail end也和上面的log相对应
/**
     * Loads the thumbnail of a video.
     *
     * @param path The path to the video file.
     * @return {@code null} if the loading failed.
     */
    public static Bitmap loadVideoThumbnail(String path) {
        Log.d(TAG, "MediaMetadataRetriever loadVideoThumbnail");
        Bitmap bitmap = null;
        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
        try {
            retriever.setDataSource(path);
            //从log可以看出这两个函数都是native函数,并且这两个函数目前获取都为空
            byte[] data = retriever.getEmbeddedPicture();
            if (data != null) {
                bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
            }
            if (bitmap == null) {
                bitmap = retriever.getFrameAtTime();
            }
        } catch (Exception e) {
            Log.e(TAG, "MediaMetadataRetriever.setDataSource() fail:" + e.getMessage());
        }
        retriever.release();
        Log.d(TAG, "MediaMetadataRetriever loadVideoThumbnail end");
        return bitmap;
    }
  • 然后我们找到了3399_Android10\frameworks\base\media\jni\android_media_MediaMetadataRetriever.cpp中的getFrameAtTime方法
static jobject android_media_MediaMetadataRetriever_getFrameAtTime(
        JNIEnv *env, jobject thiz, jlong timeUs, jint option, jint dst_width, jint dst_height)
{
    ALOGV("getFrameAtTime: %lld us option: %d dst width: %d heigh: %d",
            (long long)timeUs, option, dst_width, dst_height);
    sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz);
    if (retriever == 0) {
        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
        return NULL;
    }

    // Call native method to retrieve a video frame
    VideoFrame *videoFrame = NULL;
    //可以看到这里调用了retriever->getFrameAtTime方法
    //然后我们需要找到retriever
    sp<IMemory> frameMemory = retriever->getFrameAtTime方法(timeUs, option);
    if (frameMemory != 0) {  // cast the shared structure to a VideoFrame object
        videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
    }
    if (videoFrame == NULL) {
        //这里的打印也和log一致
        ALOGE("getFrameAtTime: videoFrame is a NULL pointer");
        return NULL;
    }

    return getBitmapFromVideoFrame(env, videoFrame, dst_width, dst_height, kRGB_565_SkColorType);
}
  • 最后我们找到了3399_Android10\frameworks\av\media\libmediaplayerservice\MetadataRetrieverClient.cpp文件
static sp<MediaMetadataRetrieverBase> createRetriever(player_type playerType)
{
    sp<MediaMetadataRetrieverBase> p;
    char value[PROPERTY_VALUE_MAX];

    switch (playerType) {
        case STAGEFRIGHT_PLAYER:
        case NU_PLAYER:
        {
            p = new StagefrightMetadataRetriever;
            break;
        }
        //原生的Android10是没有这个case的,这里是RK添加的一个case,我们可以通过设置属性cts_gts.status 为true来进行验证
        //最后发现就是这里有问题,我们可以修改为使用原生的StagefrightMetadataRetriever即修复这个bug
        case ROCKIT_PLAYER:
        {
            if (property_get("cts_gts.status", value, NULL)
                    && !strcasecmp("true", value)) {
                p = new StagefrightMetadataRetriever;
            } else {
				p = new RockitMetadataRetriever;
            }
            break;
        }
        default:
            // TODO:
            // support for TEST_PLAYER
            ALOGE("player type %d is not supported",  playerType);
            break;
    }
    if (p == NULL) {
        ALOGE("failed to create a retriever object");
    }
    return p;
}

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

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

相关文章

【HAL库】BMP180气压传感器+STM32,hal库移植

BMP180气压传感器STM32 1 导入.c.h文件&#xff08;不再赘述&#xff0c;详细见LED部分&#xff09;2 Cubemx配置3 修改 .h 文件4 测试 将BMP180从标准库移植到HAL库。模拟IIC。 极简工程代码如下&#xff1a; https://github.com/wyfroom/HAL_BMP180 该份代码硬件配置&#x…

C++——深入探究函数重载

文章目录 概述函数重载函数重载的概念函数重载的细节 C支持函数重载的原理——名字修饰(name Mangling) 概述 本篇博客讲诉的是c函数重载是什么&#xff0c;以及了解其种的一些特征以及重载函数的意义&#xff0c;并且运用linux中的g编译器简单探究一下函数重载底层是如何实现的…

线性表之顺序表(增删查改)详解

&#x1f355;博客主页&#xff1a;️自信不孤单 &#x1f36c;文章专栏&#xff1a;数据结构与算法 &#x1f35a;代码仓库&#xff1a;破浪晓梦 &#x1f36d;欢迎关注&#xff1a;欢迎大家点赞收藏关注 文章目录 &#x1f349;线性表&#x1f352;顺序表1. 概念及结构2. 接口…

数据库JDBC

数据库厂商提供一个程序来完成 API 的转换&#xff0c;对原生 API 封装再提供成JDBC 的形状。 这个程序叫数据库驱动包。 JAVA程序员要想对数据库开发&#xff0c; 就要导入对应的数据库驱动包&#xff0c;才能编写代码。 数据库驱动是让JDBC认识数据库API URL 计算机里的一…

MySQL安装与新用户的创建相关

一、MySQL安装 1. 官网下载mysql的ims包 MySQL :: Download MySQL Installer (Archived Versions) 下载好&#xff0c;双击运行。 2. 根据提示进行安装 这里选择手动安装的选项&#xff1a; 然后选择你安装的MySQL版本&#xff0c;这里是5.7 勾选自定义MySQL安装位置 下一…

教育大数据总体解决方案(7)

考勤查询 创客教室 为体现学校创新教育的成果&#xff0c;丰富学校创新实践活动&#xff0c;加强创新课程普及教育&#xff0c;把机器人创新教育作为学校的教育特色来体现&#xff0c;使学生通过理论与实践相结合的方法&#xff0c;进一步学习掌握机械、电子结构、信息技术、人…

让我们谈谈你对 ThreadLocal 的理解

介绍 ThreadLocal 从 JDK1.2 开始&#xff0c;ThreadLocal 是一个被用来存储线程本地变量的类。在 ThreadLocal 中的变量在线程之间是独立的。当多个线程访问 ThreadLocal 中的变量&#xff0c;它们事实上访问的是自己当前线程在内存中的变量&#xff0c;这能确保这些变量是线…

基于鲸鱼算法的极限学习机(ELM)回归预测-附代码

基于鲸鱼算法的极限学习机(ELM)回归预测 文章目录 基于鲸鱼算法的极限学习机(ELM)回归预测1.极限学习机原理概述2.ELM学习算法3.回归问题数据处理4.基于鲸鱼算法优化的ELM5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;本文利用鲸鱼算法对极限学习机进行优化&#xff0c;并…

kettle——数据清洗(数据表——>转换——>数据表)

目录 1、表输入 ①点击“新建”选项&#xff0c;新建数据库 ②选择“获取SQL” ③选择表a ④注意&#xff1a;字段只显示了5个&#xff0c;而一共有6个字段&#xff0c;money字段需要手动添加 2、转换 ①打开java 控件&#xff0c;设置变量 3、表输出 ①连接表b ②映…

【Redis7】Redis7 复制(重点:复制原理)

【大家好&#xff0c;我是爱干饭的猿&#xff0c;本文重点介绍Redis7 复制。 后续会继续分享Redis7和其他重要知识点总结&#xff0c;如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下吧】 上一篇文章&#xff1a;《【Redis7】Redis7 事务&管道&…

Git入门指南(手把手教学)

Git入门指南 一、什么是Git二、Git的安装下载三、git的简单实践1.创建git仓库2.Windows上生成公钥以绑定GitHub仓库3.写一个Helloworld 四、帮助学习的网站 一、什么是Git Git是一种分布式版本控制系统&#xff0c;它是由Linus Torvalds为了管理Linux内核开发而开发的。与中心化…

项目第四天

解决了路变墙 墙变路的问题 void onechange(ExMessage* msg) {if (msg->message WM_LBUTTONDOWN && msg->x > 50 && msg->x < 410 && msg->y > 50 && msg->y < 410){//printf("鼠标位置&#xff1a;x&#…

Vue.js中class与style的增强绑定

目录 一、v-bind绑定class属性 &#xff08;1&#xff09;绑定class样式&#xff0c;字符串写法 &#xff08;2&#xff09;绑定class样式&#xff0c;数组写法 &#xff08;3&#xff09;绑定class样式&#xff0c;对象写法 二、v-bind绑定内联样式style &#xff08;1&…

【CSS3】CSS3 属性选择器 ( CSS3 简介 | 属性选择器 | 属性选择器权重 )

文章目录 一、CSS3 简介二、CSS3 属性选择器权重三、CSS3 属性选择器 一、CSS3 简介 CSS3 是在 CSS2 基础上进行扩展后的样式 ; 在 移动端 对 CSS3 的支持 要比 PC 端支持的更好 , 建议在移动端开发时 , 多使用 CSS3 ; PC 端老版本浏览器不支持 CSS3 , 尤其是 IE 9 及以下的版…

【Linux 裸机篇(五)】I.MX6ULL BSP工程管理下的 Makefile编写、链接脚本

目录 一、BSP 工程二、Makefile三、链接脚本 一、BSP 工程 文件夹描述bsp存放驱动文件imx6ul存放跟芯片有关的文件&#xff0c;比如 NXP 官方的 SDK库文件obj存放编译生成的.o 文件project存放 start.S 和 main.c 文件&#xff0c;也就是应用文件 二、Makefile 1 CROSS_COMPI…

ESP32设备驱动-BMA400加速度传感器驱动

BMA400加速度传感器驱动 文章目录 BMA400加速度传感器驱动1、BMA400介绍2、硬件准备3、软件准备4、驱动实现1、BMA400介绍 BMA400 是第一款真正的超低功耗加速度传感器,不会影响性能。 BMA400 具有 12 位数字分辨率、连续测量和定义的可选带宽以及超低功耗,允许对三个垂直轴…

带有时钟使能和同步清零的D触发器

每个 Slice 有 8 个 FF 。四个可以配置为 D 型触发器或电平敏感锁存器&#xff0c;另外四个只能配置为 D 型触发器&#xff0c;但是需要记得是&#xff1a;当原来的四个 FF 配置为锁存器时&#xff0c;不能使用这四个 FF 。 &#xff08;1&#xff09;FDCE 简介 带有时钟使能…

【grpc01】入门

目录 背景 单体架构 微服务架构 代码冗余问题 服务之间调用 grpc protobuf protoc protoc-gen-go 背景 单体架构 有一些致命缺点&#xff1a; 一旦某个服务宕机&#xff0c;会引起整个应用不可用&#xff0c;隔离性差只能整体应用进行伸缩&#xff0c;浪费资源&#…

【Java基础】迷宫问题的Java代码实现

迷宫问题通常是指在给定的迷宫中&#xff0c;找到从起点到终点的路径的问题。迷宫通常由障碍物和自由空间组成&#xff0c;其中障碍物是不可穿越的区域&#xff0c;自由空间可以穿越。解决迷宫问题的方法有很多种&#xff0c;本文使用递归算法来解决迷宫问题。 一、使用递归算法…

MATLAB简单图形绘制(五)

目录 实验目的 实验内容 实验目的 1&#xff09;掌握MATLAB图形绘制的基本原理和方法&#xff1b; 2&#xff09;熟悉和了解MATLAB图形绘制程序编辑的基本指令&#xff1b; 3&#xff09;掌握利用MATLAB图形编辑窗口编辑和修改图形界面&#xff0c;并添加图形的各种标注&…