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

ExoPlayer架构详解与源码分析(14)——ProgressiveMediaPeriod

系列文章目录

ExoPlayer架构详解与源码分析(1)——前言
ExoPlayer架构详解与源码分析(2)——Player
ExoPlayer架构详解与源码分析(3)——Timeline
ExoPlayer架构详解与源码分析(4)——整体架构
ExoPlayer架构详解与源码分析(5)——MediaSource
ExoPlayer架构详解与源码分析(6)——MediaPeriod
ExoPlayer架构详解与源码分析(7)——SampleQueue
ExoPlayer架构详解与源码分析(8)——Loader
ExoPlayer架构详解与源码分析(9)——TsExtractor
ExoPlayer架构详解与源码分析(10)——H264Reader
ExoPlayer架构详解与源码分析(11)——DataSource
ExoPlayer架构详解与源码分析(12)——Cache
ExoPlayer架构详解与源码分析(13)——TeeDataSource和CacheDataSource
ExoPlayer架构详解与源码分析(14)——ProgressiveMediaPeriod


文章目录

  • 系列文章目录
  • 前言
  • ProgressiveMediaPeriod
  • 总结


前言

中途间隔了一段时间,之前写了那么多铺垫,终于看到ProgressiveMediaPeriod实现部分了

ProgressiveMediaPeriod

有了之前的那些铺垫,这里直接看源码了

  @Overridepublic void prepare(Callback callback, long positionUs) {this.callback = callback;loadCondition.open();//保证继续加载的开关打开,Loader不阻塞startLoading();}private void startLoading() {ExtractingLoadable loadable =//创建loadable 共Loader加载new ExtractingLoadable(//extractorOutput监听,也就是Loader加载过程中会回调ProgressiveMediaPeriod的track,endTracksuri, dataSource, progressiveMediaExtractor, /* extractorOutput= */ this, loadCondition);if (prepared) {//如果已经准备完成Assertions.checkState(isPendingReset());if (durationUs != C.TIME_UNSET && pendingResetPositionUs > durationUs) {//当前定位位置已经超过总时长,直接加载结束loadingFinished = true;pendingResetPositionUs = C.TIME_UNSET;return;}//通过seekMap查找出当前时间对于的数据位置loadable.setLoadPosition(checkNotNull(seekMap).getSeekPoints(pendingResetPositionUs).first.position,pendingResetPositionUs);for (SampleQueue sampleQueue : sampleQueues) {//将所有的轨道开始时间同步sampleQueue.setStartTimeUs(pendingResetPositionUs);}pendingResetPositionUs = C.TIME_UNSET;}//获取开始加载时所有轨道已经提前的数据块总数,后面通过当前的数据块总数和开始的数量对比,可以判断出是否加载了新的数据extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount();long elapsedRealtimeMs =loader.startLoading(//Loader开始加载,具体过程参照Loader部分的文章,此时加载状态的回调this,也就是加载完成后会调用ProgressiveMediaPeriod  onLoadCompleted,seekMaploadable, this, loadErrorHandlingPolicy.getMinimumLoadableRetryCount(dataType));DataSpec dataSpec = loadable.dataSpec;//触发监听mediaSourceEventDispatcher.loadStarted(new LoadEventInfo(loadable.loadTaskId, dataSpec, elapsedRealtimeMs),C.DATA_TYPE_MEDIA,C.TRACK_TYPE_UNKNOWN,/* trackFormat= */ null,C.SELECTION_REASON_UNKNOWN,/* trackSelectionData= */ null,/* mediaStartTimeUs= */ loadable.seekTimeUs,durationUs);}@Override//Loader加载时如果解析器需要输出Sample数据会先回调track,获取TrackOutputpublic TrackOutput track(int id, int type) {return prepareTrackOutput(new TrackId(id, /* isIcyTrack= */ false));}//构建TrackOutputprivate TrackOutput prepareTrackOutput(TrackId id) {int trackCount = sampleQueues.length;for (int i = 0; i < trackCount; i++) {//查询当前的sampleQueue是否已创建直接返回if (id.equals(sampleQueueTrackIds[i])) {return sampleQueues[i];}}SampleQueue trackOutput =//创建新的SampleQueue对应一个新的轨道,这里传入了缓存分配器allocatorSampleQueue.createWithDrm(allocator, drmSessionManager, drmEventDispatcher);trackOutput.setUpstreamFormatChangeListener(this);//设置Format改变的监听@NullableTypeTrackId[] sampleQueueTrackIds = Arrays.copyOf(this.sampleQueueTrackIds, trackCount + 1);sampleQueueTrackIds[trackCount] = id;this.sampleQueueTrackIds = Util.castNonNullTypeArray(sampleQueueTrackIds);//更新全局的SampleQueue数组@NullableType SampleQueue[] sampleQueues = Arrays.copyOf(this.sampleQueues, trackCount + 1);sampleQueues[trackCount] = trackOutput;this.sampleQueues = Util.castNonNullTypeArray(sampleQueues);return trackOutput;}@Override//当解析器已经将所有轨道解析出来时会调用此方法,参照H264Reader部分public void endTracks() {sampleQueuesBuilt = true;//当前方法在解析的子线程中回调,需要将方法方法当前ProgressiveMediaPeriod的线程中执行maybeFinishPreparehandler.post(maybeFinishPrepareRunnable);}private void maybeFinishPrepare() {if (released || prepared || !sampleQueuesBuilt || seekMap == null) {return;}//确保所有轨道均已解析for (SampleQueue sampleQueue : sampleQueues) {if (sampleQueue.getUpstreamFormat() == null) {return;}}//阻塞住loader的解析,防止继续更新sampleQueuesloadCondition.close();//创建TrackGroup,trackState 供后续的selectTracks使用int trackCount = sampleQueues.length;TrackGroup[] trackArray = new TrackGroup[trackCount];boolean[] trackIsAudioVideoFlags = new boolean[trackCount];for (int i = 0; i < trackCount; i++) {Format trackFormat = checkNotNull(sampleQueues[i].getUpstreamFormat());@Nullable String mimeType = trackFormat.sampleMimeType;boolean isAudio = MimeTypes.isAudio(mimeType);boolean isAudioVideo = isAudio || MimeTypes.isVideo(mimeType);trackIsAudioVideoFlags[i] = isAudioVideo;haveAudioVideoTracks |= isAudioVideo;...trackFormat = trackFormat.copyWithCryptoType(drmSessionManager.getCryptoType(trackFormat));trackArray[i] = new TrackGroup(/* id= */ Integer.toString(i), trackFormat);}trackState = new TrackState(new TrackGroupArray(trackArray), trackIsAudioVideoFlags);prepared = true;//标记准备完成checkNotNull(callback).onPrepared(this);//通知上层prepared ,上层接下就会去调用ProgressiveMediaPeriod.selectTracks获取轨道信息}@Override//获取轨道public long selectTracks(@NullableType ExoTrackSelection[] selections,//TrackSelector 部分会说到boolean[] mayRetainStreamFlags,@NullableType SampleStream[] streams,//Renderer部分会说的boolean[] streamResetFlags,long positionUs) {assertPrepared();//确保已经准备完成TrackGroupArray tracks = trackState.tracks;boolean[] trackEnabledStates = trackState.trackEnabledStates;int oldEnabledTrackCount = enabledTrackCount;// 去除原来mayRetainStreamFlags标记的无需保留的轨道for (int i = 0; i < selections.length; i++) {if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) {int track = ((SampleStreamImpl) streams[i]).track;Assertions.checkState(trackEnabledStates[track]);enabledTrackCount--;trackEnabledStates[track] = false;streams[i] = null;}}//如果是第一次selectTracks,而且positionUs 位置又不为0,或者之前一次selectTracks禁用了所有的轨道,这2种情况就需要Seekboolean seekRequired = seenFirstTrackSelection ? oldEnabledTrackCount == 0 : positionUs != 0;// 开始通过selections选择新的轨道for (int i = 0; i < selections.length; i++) {if (streams[i] == null && selections[i] != null) {ExoTrackSelection selection = selections[i];Assertions.checkState(selection.length() == 1);Assertions.checkState(selection.getIndexInTrackGroup(0) == 0);int track = tracks.indexOf(selection.getTrackGroup());Assertions.checkState(!trackEnabledStates[track]);enabledTrackCount++;trackEnabledStates[track] = true;streams[i] = new SampleStreamImpl(track);//向入参中赋值streamResetFlags[i] = true;if (!seekRequired) {SampleQueue sampleQueue = sampleQueues[track];seekRequired =!sampleQueue.seekTo(positionUs, /* allowTimeBeyondBuffer= */ true)&& sampleQueue.getReadIndex() != 0;}}}if (enabledTrackCount == 0) {pendingDeferredRetry = false;notifyDiscontinuity = false;if (loader.isLoading()) {// Discard as much as we can synchronously.for (SampleQueue sampleQueue : sampleQueues) {sampleQueue.discardToEnd();}loader.cancelLoading();} else {for (SampleQueue sampleQueue : sampleQueues) {sampleQueue.reset();}}} else if (seekRequired) {positionUs = seekToUs(positionUs);// We'll need to reset renderers consuming from all streams due to the seek.for (int i = 0; i < streams.length; i++) {if (streams[i] != null) {streamResetFlags[i] = true;}}}seenFirstTrackSelection = true;return positionUs;}@Override//解析器解析出SeekMap时回调public void seekMap(SeekMap seekMap) {handler.post(() -> setSeekMap(seekMap));}@Overridepublic boolean continueLoading(long playbackPositionUs) {if (loadingFinished//加载完成,出错等情况返回false|| loader.hasFatalError()|| pendingDeferredRetry|| (prepared && enabledTrackCount == 0)) {return false;}boolean continuedLoading = loadCondition.open();if (!loader.isLoading()) {//当前没有正在加载startLoading();//再次startLoadingcontinuedLoading = true;}return continuedLoading;}@Override//Loader加载完成后回调public void onLoadCompleted(ExtractingLoadable loadable, long elapsedRealtimeMs, long loadDurationMs) {if (durationUs == C.TIME_UNSET && seekMap != null) {//此时durationUs 未知,seekMap 已经有了boolean isSeekable = seekMap.isSeekable();long largestQueuedTimestampUs =//查询所有轨道中时间戳最大值getLargestQueuedTimestampUs(/* includeDisabledTracks= */ true);durationUs =//将最大值+10毫秒作为当前媒体的时长largestQueuedTimestampUs == Long.MIN_VALUE? 0: largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US;//此时durationUs 等信息已知可以通过MediaSource更新一把TimeLinelistener.onSourceInfoRefreshed(durationUs, isSeekable, isLive);}StatsDataSource dataSource = loadable.dataSource;//StatsDataSource可以缓存Uri和ResponseHeader,获取这些值构建LoadEventInfo LoadEventInfo loadEventInfo =new LoadEventInfo(loadable.loadTaskId,loadable.dataSpec,dataSource.getLastOpenedUri(),dataSource.getLastResponseHeaders(),elapsedRealtimeMs,loadDurationMs,dataSource.getBytesRead());loadErrorHandlingPolicy.onLoadTaskConcluded(loadable.loadTaskId);//触发监听mediaSourceEventDispatcher.loadCompleted(loadEventInfo,C.DATA_TYPE_MEDIA,C.TRACK_TYPE_UNKNOWN,/* trackFormat= */ null,C.SELECTION_REASON_UNKNOWN,/* trackSelectionData= */ null,/* mediaStartTimeUs= */ loadable.seekTimeUs,durationUs);loadingFinished = true;//此时的loadingFinished 已为true,没设置为false前是无法continueLoading的//请求上层继续加载,是否继续加载由上层决定,如果上层同意继续加载会调用ProgressiveMediaPeriod的continueLoadingcheckNotNull(callback).onContinueLoadingRequested(this);}

这里再总结下ProgressiveMediaPeriod执行过程:

  1. 首先调用prepare方法启动Loader去获取资源的轨道信息,此处参考ExoPlayer架构详解与源码分析(8)——Loader
  2. Loader中的解析器获取到信息后回回调endTrack通知ProgressiveMediaPeriod prepare完成
  3. ProgressiveMediaPeriod 会进一步通知上层此时MeidaSource已经准备完成,上层再Renderer(四大组件之一)绘制前会通过ExoPlayer的另一大组件TrackSelector(后面会说到),调用ProgressiveMediaPeriod 的selectTracks选择轨道数据,也就将SampleQueue中的数据提供出去,此处参考ExoPlayer架构详解与源码分析(7)——SampleQueue
  4. 同时Loader从打开到当前加载数据量超过1M(默认值)时就会阻塞当前的Loader,然后询问上层是否继续加载
  5. 上层主要是通过后面要将LoadControl判断,如果上层决定继续加载更多数据,就会调用ProgressiveMediaPeriod continueLoading解开阻塞的锁继续加载。因为我们知道数据是加载到内存中的,如果无限制的加载肯定是不行的,需要有节制的加载和释放数据。而LoadControl就负责这块工作后面会讲到。
  6. 而上面过程当需要释放已经播放的内存时就会调用discardBuffer方法释放Sample中的内存
  7. 当所有的数据加载完成的时候会调用onLoadCompleted将loadingFinished 标记为true

总结

到这里Exoplayer中最复杂的组件MediaSource就全部分析完毕了,之前说了MediaSource在整个运载火箭中的角色就类似于燃料系统,确保火箭顺利升空,在运行过程中持续稳定的为火箭提供燃料。这些燃料提供给谁了呢,或者说是被谁消耗了呢。这就是下面要讲的火箭的核心发动机Renderers,也是Exoplayer四大组件中另一个重要的角色。


版权声明 ©
本文为CSDN作者山雨楼原创文章
转载请注明出处
原创不易,觉得有用的话,收藏转发点赞支持

相关文章:

ExoPlayer架构详解与源码分析(14)——ProgressiveMediaPeriod

系列文章目录 ExoPlayer架构详解与源码分析&#xff08;1&#xff09;——前言 ExoPlayer架构详解与源码分析&#xff08;2&#xff09;——Player ExoPlayer架构详解与源码分析&#xff08;3&#xff09;——Timeline ExoPlayer架构详解与源码分析&#xff08;4&#xff09;—…...

docker部署kafka(单节点) + Springboot集成kafka

环境&#xff1a; 操作系统&#xff1a;win10 Docker&#xff1a;Docker Desktop 4.21.1 (114176)、Docker Engine v24.0.2 SpringBoot&#xff1a;2.7.15 步骤1&#xff1a;创建网络&#xff1a; docker network create --subnet172.18.0.0/16 net-kafka 步骤2&#xff1a;安…...

一.1.(3)半导体二极管基本电路的分析方法及常见应用电路

1.二极管基本电路的分析方法 先标正负极&#xff0c;再看是否理想二极管 将二极管视为断路&#xff0c;求两端电压 两端电压均大于导通电压&#xff0c;压差大的先导通&#xff08;由于电源不是完全的阶跃&#xff0c;而是有一个电压爬升的过程&#xff09; 2.常见应用电路 1.求…...

银河麒麟V10 SP1 审计工具 auditd更新

前言 银河麒麟V10 SP1 审计工具 auditd 引发的内存占用过高&#xff0c; 内存使用率一直在 60% 以上&#xff0c; 内存一直不释放 排查 可以使用ps或者top查看系统进程使用情况 ps -aux|sort -k4nr|head -n 5 发现银河麒麟审计工具 auditd 一直占用内存不释放 解决 办法一…...

JWT(Json Web Token)在.NET Core中的使用

登录成功时生成JWT字符串目录 JWT是什么&#xff1f; JWT的优点&#xff1a; JWT在.NET Core 中的使用 JWT是什么&#xff1f; JWT把登录信息&#xff08;也称作令牌&#xff09;保存在客户端为了防止客户端的数据造假&#xff0c;保存在客户端的令牌经过了签名处理&#xf…...

《QT从基础到进阶·四十三》QPlugin插件多线程问题和只有插件dll没有头文件和lib文件时调用插件中的方法

1、插件和多线程问题&#xff1a; 创建插件对象不能放到多线程执行&#xff0c;不然报错&#xff1a;ASSERT failure in QWidget: "Widgets must be created in the GUlthread. //不能放在多线程执行 QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName))…...

Android SurfaceFlinger——屏幕状态初始化(二十二)

对于开机启动动画前期准备的相关步骤,我们已经分析了前 5 个,对于第 6 步调用 eglGetDisplay() 函数对 OpenGL ES 初始化并获取默认屏幕,我们在介绍 OpenGL ES 的时候也进行了详细的分析,下一步我们我们来分析对屏幕的状态进行初始化。 1)getInternalDisplayToken:获取显…...

3101. 交替子数组计数 Medium

给你一个 二进制数组 nums 。 如果一个 子数组 中 不存在 两个 相邻 元素的值 相同 的情况&#xff0c;我们称这样的子数组为 交替子数组 。 返回数组 nums 中交替子数组的数量。 示例 1&#xff1a; 输入&#xff1a; nums [0,1,1,1] 输出&#xff1a; 5 解释&…...

Linux系统基础命令行指令——Ubuntu

基础指令 更新指令 sudo apt update sudo apt upgrade 切换超级管理员 su root 切换路径 //相对、绝对 cd 路径回上一级路径 cd ..cd ../.. 退两级路径 查看当前目录 pwd查看指定路径内容 ls //常见搭配 ls -al 创建目录 mkdir 路径 创建文件 touc…...

qt 读取配置文件

在Qt中读取配置文件&#xff0c;主要有以下几种方法&#xff1a; 使用QFile和QTextStream类&#xff1a; 这种方法适用于读取任意文本文件&#xff0c;包括配置文件。使用QFile的open()方法打开配置文件。使用QTextStream的readLine()方法逐行读取配置数据。使用QXmlStreamRea…...

拉格朗日插值法【python,算法】

拉格朗日插值是一种在数值分析中用来构建通过一系列已知数据点的多项式插值的方法。这种方法以 18 世纪的法国数学家约瑟夫拉格朗日命名。当给定一组离散的数据点(&#x1d465;_0,&#x1d466;_0),(&#x1d465;_1,&#x1d466;_1),...,(&#x1d465;_&#x1d45b;,&…...

定个小目标之刷LeetCode热题(41)

338. 比特位计数 给你一个整数 n &#xff0c;对于 0 < i < n 中的每个 i &#xff0c;计算其二进制表示中 1 的个数 &#xff0c;返回一个长度为 n 1 的数组 ans 作为答案。 今天看一下这道简单题&#xff0c;主要考查位运算&#xff0c;代码如下 class Solution {pu…...

Kotlin中的关键字

Kotlin 中的关键字可分为几个大类&#xff1a; 声明/定义关键字&#xff1a; class&#xff1a;用于定义类interface&#xff1a;用于定义接口object&#xff1a;用于声明对象&#xff0c;Kotlin中实现单例模式的关键字fun&#xff1a;用于声明函数var&#xff1a;用于声明可变…...

LabVIEW新能源汽车电池性能测试系统

新能源汽车的核心部件之一是电池&#xff0c;其性能直接关系到整车的续航里程、安全性和寿命。为了确保电池的性能和可靠性&#xff0c;测试是必不可少的环节。本文介绍了一种基于LabVIEW的新能源汽车电池性能测试系统&#xff0c;通过LabVIEW与数据采集设备的无缝集成&#xf…...

Elasticsearch 实现 Word、PDF,TXT 文件的全文内容提取与检索

文章目录 一、安装软件:1.通过docker安装好Es、kibana安装kibana:2.安装原文检索与分词插件:之后我们可以通过doc命令查看下载的镜像以及运行的状态:二、创建管道pipeline名称为attachment二、创建索引映射:用于存放上传文件的信息三、SpringBoot整合对于原文检索1、导入依赖…...

深度学习赋能数据分析,联蔚盘云引领业务革新

一、引言 随着大数据时代的到来&#xff0c;深度学习技术正逐渐成为企业数据分析的新引擎。联蔚盘云凭借其在深度学习领域的深厚积累&#xff0c;为企业提供高效、精准的数据分析解决方案&#xff0c;助力企业实现业务革新与增长。 二、深度学习与数据分析的完美结合 联蔚盘…...

Arthas实战(5)- 项目性能调优

1、接口耗时查询&#xff1a;trace命令 trace 命令能主动搜索 class-pattern&#xff0f;method-pattern 对应的方法调用路径&#xff0c;渲染和统计整个调用链路上的所有性能开销和追踪调用链路。 1.1 准备测试应用 新建一个 SpringBoot 应用&#xff0c;写一耗时久的代码&…...

昇思25天学习打卡营第7天|Pix2Pix实现图像转换

文章目录 昇思MindSpore应用实践基于MindSpore的Pix2Pix图像转换1、Pix2Pix 概述2、U-Net架构定义UNet Skip Connection Block 2、生成器部分3、基于PatchGAN的判别器4、Pix2Pix的生成器和判别器初始化5、模型训练6、模型推理 Reference 昇思MindSpore应用实践 本系列文章主要…...

单选多选提交问卷,代码示例

&#xff45;&#xff4c;&#xff45;&#xff4d;&#xff45;&#xff4e;&#xff54;中 需要对接口返回的数据进行分析。多选问题使用checkbox&#xff0c;单选题使用radio。 多选时可以绑定&#xff4d;&#xff49;&#xff4e;&#xff0f;&#xff4d;&#xff41;&am…...

mars3d加载wms服务或者wmts服务注意事项

1.wms只支持4326、3857、4490的标准切片&#xff0c;其他坐标系不支持 Mars3D三维可视化平台 | 火星科技 2.wmts同理&#xff0c;Mars3D三维可视化平台 | 火星科技 3.对应级别tilematrix找到的瓦片tilerow&tilecol这两个参数使用常见报错无效参考&#xff1a; 【Mars3d】…...

Kimi-VL-A3B-Thinking作品分享:OCR识别模糊手写体+公式识别+LaTeX自动转换

Kimi-VL-A3B-Thinking作品分享&#xff1a;OCR识别模糊手写体公式识别LaTeX自动转换 1. 引言&#xff1a;当AI能看懂你的草稿纸 想象一下&#xff0c;你有一张拍得有点模糊的会议白板照片&#xff0c;上面潦草地写满了讨论要点和几个复杂的数学公式。或者&#xff0c;你翻出一…...

Kaggle Notebook中文乱码终结者:3分钟搞定Matplotlib字体配置(附Noto Sans CJK全流程)

Kaggle Notebook中文乱码终结者&#xff1a;3分钟搞定Matplotlib字体配置&#xff08;附Noto Sans CJK全流程&#xff09; 在数据可视化过程中&#xff0c;中文显示问题一直是困扰许多Kaggle用户的痛点。当你在Notebook中满怀期待地运行代码&#xff0c;却发现图表中的中文变成…...

【Python内存管理2026权威白皮书】:GIL演进、引用计数重构与GC智能调度三大突破性策略首次公开

第一章&#xff1a;Python智能体内存管理策略2026最新趋势全景概览随着大语言模型驱动的Python智能体&#xff08;Agent&#xff09;在生产环境中的深度部署&#xff0c;传统CPython内存管理机制正面临前所未有的挑战&#xff1a;动态工具调用、多轮推理缓存、跨Agent状态共享及…...

避开Unity动态合批的坑:为什么你的Dynamic Batching不生效?

深度剖析Unity动态合批失效的六大技术陷阱与实战解决方案 当你在Unity项目中精心设计了数百个低多边形道具&#xff0c;却发现性能面板中的Draw Calls居高不下时&#xff0c;动态合批&#xff08;Dynamic Batching&#xff09;很可能正在暗中失效。本文将揭示那些官方文档未曾详…...

搞懂 SAP Fiori 前端服务器授权模型:从看得见应用,到真正拿到数据

在很多 SAP 项目里,权限问题最容易制造一种很迷惑的现象:用户明明已经拿到了角色,却还是打不开应用;或者磁贴已经能看见了,点进去却报错;再或者应用能启动,却一条业务数据都读不出来。要把这类问题讲清楚,关键不在于死记事务码,而在于真正理解 SAP Fiori 的授权是如何…...

Qwen3.5-27B-GPTQ-Int4:超高效多模态AI新体验

Qwen3.5-27B-GPTQ-Int4&#xff1a;超高效多模态AI新体验 【免费下载链接】Qwen3.5-27B-GPTQ-Int4 项目地址: https://ai.gitcode.com/hf_mirrors/Qwen/Qwen3.5-27B-GPTQ-Int4 导语 阿里云推出Qwen3.5-27B-GPTQ-Int4模型&#xff0c;通过4位量化技术实现性能与效率的双…...

AutoDL云服务器避坑指南:从PyTorch到Jupyter,手把手搞定GPU环境配置

AutoDL云服务器GPU环境配置实战&#xff1a;从镜像选择到Jupyter避坑全攻略 第一次在AutoDL这类云GPU平台上配置深度学习环境时&#xff0c;那种既兴奋又忐忑的心情我至今记忆犹新。看着琳琅满目的镜像选项和复杂的版本匹配要求&#xff0c;稍有不慎就会陷入"版本地狱&qu…...

OpenClaw轻量化方案实测:nanobot镜像性能与成本对比

OpenClaw轻量化方案实测&#xff1a;nanobot镜像性能与成本对比 1. 为什么选择nanobot镜像 上个月我在尝试用OpenClaw搭建个人自动化助手时&#xff0c;遇到了一个典型的技术选择困境&#xff1a;是直接调用云端大模型API&#xff0c;还是部署本地模型&#xff1f;经过反复权…...

Linux核心转储文件生成与调试全指南

1. Linux核心转储文件调试方法详解1.1 核心转储文件概述在Linux系统下&#xff0c;当程序发生崩溃时&#xff0c;系统会生成一个包含程序崩溃时内存映像的文件&#xff0c;称为core文件。这个文件记录了程序崩溃时的内存状态和调试信息&#xff0c;是定位程序崩溃原因的重要工具…...

大模型进阶:掌握Function Calling和MCP,解锁AI生产力(收藏版)

本文深入探讨了Function Calling技术如何帮助大模型获取实时信息、执行任务&#xff0c;以及MCP协议在大模型与外部交互中的关键作用。文章阐述了从提示工程到RAG&#xff0c;再到Function Calling和MCP的技术演进路径&#xff0c;强调了这些技术如何使大模型从信息工具转变为生…...