FreeRTOS深入教程(任务创建的深入和任务调度机制分析)
文章目录
- 前言
- 一、深入理解任务的创建
- 二、任务的调度机制
- 1.FreeRTOS中任务调度的策略
- 2.FreeRTOS任务调度策略实现的核心
- 3.FreeRTOS内部链表源码解析
- 4.如何通过就绪链表管理任务的执行顺序
- 三、一个任务能够运行多久
- 1.高优先级任务可抢占低优先级任务一直运行
- 2.相同优先级的任务遵循时间片轮转
- 四、FreeRTOS中任务如何释放CPU
- 总结
前言
本篇文章将带大家深入学习任务的创建和分析任务调度的机制。
一、深入理解任务的创建
创建任务函数原型:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */const configSTACK_DEPTH_TYPE usStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask )
这里只讲解几个比较重要的参数,其他参数不态清楚的同学可以去看之前的文章:
任务创建
usStackDepth:任务栈的大小
每一个任务都需要有自己的栈,用来保存寄存器的值和局部变量等。
在FreeRTOS中会使用pvPortMalloc来申请栈,大小为传入的usStackDepth * 4字节。
StackType_t * pxStack;/* Allocate space for the stack used by the task being created. */
pxStack = pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
栈分配的大小由局部变量和调用深度确定。
TaskHandle_t:TCB控制块
精简后的TCB任务控制块:
typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{volatile StackType_t * pxTopOfStack; /*< Points to the location of ListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */ListItem_t xEventListItem; /*< Used to reference a task from an event list. */UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */StackType_t * pxStack; /*< Points to the char pcTaskName[ configMAX_TASK_NAME_LEN ];
} tskTCB;
TCB控制块中保存着任务的重要信息:
pxTopOfStack:这个参数指向任务堆栈的最顶部,即最近放入任务堆栈的项目的位置。这必须是 TCB 结构的第一个成员。
ListItem_t xStateListItem:这是一个用于任务状态管理的链表项。它用于将任务插入到就绪、阻塞或挂起状态链表中,以便操作系统可以有效地管理任务状态。
ListItem_t xEventListItem:这是用于将任务插入到事件列表中的链表项。当任务等待某个事件发生时,它会被插入到事件列表中。这允许任务在事件发生时被及时唤醒。
UBaseType_t uxPriority:这是任务的优先级。任务的优先级用于决定它在多任务系统中的调度顺序。较低的数值表示更高的优先级,0通常是最低优先级。
StackType_t * pxStack:这个参数指向任务堆栈的起始位置。任务堆栈是用于保存任务上下文信息的内存区域,包括寄存器值、局部变量等。
char pcTaskName[configMAX_TASK_NAME_LEN]:这个数组用于保存任务的名称,以便在调试和诊断中使用。configMAX_TASK_NAME_LEN 是一个配置参数,定义了任务名称的最大长度。
那么这里就有一个疑问了:创建任务中的函数,和任务中的参数保存到哪里去了。
创建任务中的函数其实就是一个函数指针也就是一个地址,当创建任务时PC会保存函数的地址,当任务被调用时,立刻从PC中取出地址跳转到函数中执行。
参数会保存在R0寄存器中。
二、任务的调度机制
1.FreeRTOS中任务调度的策略
在FreeRTOS中任务的调度支持 可抢占
和时间片轮转
。
可抢占:
在可抢占式调度中,任务可以被更高优先级的任务抢占。当一个高优先级任务变得可用时,它可以打断当前正在执行的低优先级任务,从而使系统立即切换到高优先级任务执行。
在FreeRTOS中,任务调度是基于任务优先级的。当一个任务抢占另一个任务时,它会立即执行,无论被抢占的任务是否已经执行完其时间片。这种方式确保了高优先级任务能够及时响应,并在需要时立即执行,不受低优先级任务的阻碍。
在FreeRTOS中通过配置configUSE_PREEMPTION
来决定是否启动抢占。
时间片轮转:
时间片轮转是指操作系统为每个任务分配一个时间片,即预定义的时间量。在时间片轮转调度方式下,每个任务可以执行一个时间片,然后系统将控制权移交给下一个就绪的任务。如果一个任务在其时间片结束前没有完成,系统会暂停该任务,将控制权交给下一个就绪的任务。
FreeRTOS允许你在配置系统时启用或禁用时间片轮转。时间片的大小可以根据应用程序的需要进行调整。这种调度方式有助于确保任务之间的公平性,避免某些任务长时间占用处理器,同时允许多个任务分享处理时间。
在FreeRTOS中通过配置configUSE_TIME_SLICING
来决定是否启动时间片轮转。
组合应用
在FreeRTOS中,可抢占和时间片轮转调度方式可以结合使用。这样可以实现灵活的任务管理,确保高优先级任务能够抢占低优先级任务,并且为任务提供公平的处理器时间,从而有效地管理系统资源。
2.FreeRTOS任务调度策略实现的核心
在 FreeRTOS 中,任务管理使用就绪链表、阻塞链表和挂起链表来管理任务的状态和调度。这些链表用于维护不同状态的任务列表。让我们逐一了解它们:
1.就绪链表(Ready List)
就绪链表包含所有处于就绪状态的任务。就绪状态的任务是指已经准备好运行,但由于当前执行的任务正在占用 CPU 资源,它们暂时无法立即执行。这些任务按照优先级被组织在就绪链表中。当当前正在执行的任务释放 CPU(例如,由于时间片用完、任务阻塞或挂起等原因)时,调度器从就绪链表中选择优先级最高的任务来执行。
2.阻塞链表(Blocked List)
阻塞链表包含那些由于某种原因而无法立即执行的任务。这些原因可能包括等待某个事件、资源不可用、延时等情况。当任务处于阻塞状态时,它们不会被调度器所执行。这些任务会在特定条件满足之后重新放入就绪链表,等待调度器选择其执行。
3.挂起链表(Suspended List)
挂起链表包含已被显式挂起的任务。当任务被挂起时,它们暂时停止运行,不再参与调度。这些任务不会出现在就绪链表或阻塞链表中,因为它们被明确地挂起,不参与任务调度。
在 FreeRTOS 中,任务的状态转换是动态的。任务可以从就绪状态变为阻塞状态或挂起状态,然后再返回到就绪状态。这些状态的变化取决于任务的执行和系统中的事件。管理任务状态的链表是 FreeRTOS 在调度和管理任务时使用的数据结构。这些链表确保了任务的有效调度和管理,以满足实时系统的要求。
这些链表是 FreeRTOS 内部任务管理的一部分,并且开发者可以通过 FreeRTOS 提供的 API 函数来管理和操作任务的状态以及链表中的任务。
3.FreeRTOS内部链表源码解析
FreeRTOS中使用下面的链表来管理任务的调度:
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ]; /*< Prioritised ready tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList1; /*< Delayed tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList2; /*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList; /*< Points to the delayed task list currently being used. */
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList; /*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t xPendingReadyList; /*< Tasks that have been readied while the scheduler was suspended. They will be moved to the ready list when the scheduler is resumed. */
pxReadyTasksLists:
这是一个数组,包含了多个链表,其数量等于configMAX_PRIORITIES,它用于存储处于就绪状态的任务。每个链表对应一个优先级,因此,数组中的每个元素存储了同一优先级的就绪任务。当任务准备好运行时,它将被添加到适当优先级的链表中,以等待被调度器选中执行。
xDelayedTaskList1 和 xDelayedTaskList2:
这两个链表用于存储被延时挂起的任务。通常,xDelayedTaskList1 包含所有未溢出的延时任务,而 xDelayedTaskList2 用于存储延时已经溢出的任务。这种设计允许 FreeRTOS 处理不同时间范围内的延时任务。延时任务在指定的时间段内不会被执行,而是在延时到期后再被移到就绪链表。
pxDelayedTaskList 和 pxOverflowDelayedTaskList:
这两个指针变量用于指向当前使用的延时任务链表。通常,pxDelayedTaskList 指向 xDelayedTaskList1 或 xDelayedTaskList2 中的一个,具体取决于当前的延时情况。这些链表用于存储不同时间范围内的延时任务。
xPendingReadyList:
这个链表用于存储在调度器被挂起时已经准备好运行的任务。当调度器处于挂起状态时,如果有任务变为就绪状态,它们将被添加到这个链表中。当调度器被恢复时,这些任务将被移动到适当的 pxReadyTasksLists 中,以等待被调度执行。
4.如何通过就绪链表管理任务的执行顺序
在创建任务时会通过prvAddNewTaskToReadyList函数将任务添加进入就绪链表。
在创建任务时当新创建的任务优先级大于或者等于当前任务优先级时,pxCurrentTCB当前任务指针指向pxNewTCB新添加任务的指针。
在prvAddNewTaskToReadyList函数中通过prvAddTaskToReadyList函数将不同优先级的任务添加进入不同的就绪链表当中:
vListInsertEnd函数会将新创建的任务添加到当前就绪链表的最后一项。
下面我们举一个例子验证上述代码:
void vTask1( void *pvParameters )
{/* 任务函数的主体一般都是无限循环 */for( ;; ){flagIdleTaskrun = 0;flagTask1run = 1;flagTask2run = 0;flagTask3run = 0;/* 打印任务的信息 */printf("T1\r\n"); }
}void vTask2( void *pvParameters )
{ /* 任务函数的主体一般都是无限循环 */for( ;; ){flagIdleTaskrun = 0;flagTask1run = 0;flagTask2run = 1;flagTask3run = 0;/* 打印任务的信息 */printf("T2\r\n"); }
}void vTask3( void *pvParameters )
{ const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL ); /* 任务函数的主体一般都是无限循环 */for( ;; ){flagIdleTaskrun = 0;flagTask1run = 0;flagTask2run = 0;flagTask3run = 1;/* 打印任务的信息 */printf("T3\r\n"); // 如果不休眠的话, 其他任务无法得到执行//vTaskDelay( xDelay5ms );}
}xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);
xTaskCreate(vTask3, "Task 3", 1000, NULL, 1, NULL);
运行结果:
运行的结果是任务3先运行。
根据上述代码分析可以画出一个图来表示:
首先运行Task3:
第二运行Task1:
第三运行Task2:
三、一个任务能够运行多久
1.高优先级任务可抢占低优先级任务一直运行
void vTask1( void *pvParameters )
{/* 任务函数的主体一般都是无限循环 */for( ;; ){flagIdleTaskrun = 0;flagTask1run = 1;flagTask2run = 0;flagTask3run = 0;/* 打印任务的信息 */printf("T1\r\n"); }
}void vTask2( void *pvParameters )
{ /* 任务函数的主体一般都是无限循环 */for( ;; ){flagIdleTaskrun = 0;flagTask1run = 0;flagTask2run = 1;flagTask3run = 0;/* 打印任务的信息 */printf("T2\r\n"); }
}void vTask3( void *pvParameters )
{ const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL ); /* 任务函数的主体一般都是无限循环 */for( ;; ){flagIdleTaskrun = 0;flagTask1run = 0;flagTask2run = 0;flagTask3run = 1;/* 打印任务的信息 */printf("T3\r\n"); // 如果不休眠的话, 其他任务无法得到执行//vTaskDelay( xDelay5ms );}
}xTaskCreate(vTask1, "Task 1", 1000, NULL, 2, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);
xTaskCreate(vTask3, "Task 3", 1000, NULL, 1, NULL);
运行结果:
当把configUSE_PREEMPTION配置为了1时,如果高优先级任务不主动释放CPU,那么其他低优先级的任务将无法执行。
2.相同优先级的任务遵循时间片轮转
当配置了configUSE_TIME_SLICING为1时,相同优先级的任务将轮流执行一个Tick的时间。
xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);
xTaskCreate(vTask3, "Task 3", 1000, NULL, 1, NULL);
运行结果:
四、FreeRTOS中任务如何释放CPU
1.任务主动让出CPU:
任务可以调用vTaskDelay()函数或者vTaskDelayUntil()函数,将自己挂起一段时间,以便其他任务能够运行。这种方式是任务主动放弃CPU的一种方式。
2.阻塞等待事件:
任务可以调用FreeRTOS提供的阻塞函数,如xQueueReceive()、xSemaphoreTake()等,来等待特定事件的发生。当任务在等待某个事件时,它会被置于阻塞状态,从而释放CPU,直到事件发生后才会被唤醒。
3.时间片轮转:
如果使用了时间片轮转调度策略,任务会在其时间片用尽时自动释放CPU,允许其他任务运行。时间片轮转是一种公平分配CPU时间的策略,每个任务都有一个小的时间片来执行,然后被放回就绪队列,等待下一次执行。
4.任务进入阻塞状态:
任务在执行过程中,如果发生某些阻塞事件,如等待一个队列满足条件、等待互斥信号量等,会自动进入阻塞状态,这时会释放CPU。一旦阻塞条件得到满足,任务将被重新置于就绪状态。
总结
本篇文章深入的讲解了任务创建的内部实现和任务调度的源代码分析和实现,学习这篇文章有助于更深入的学习FreeRTOS的源码。
相关文章:

FreeRTOS深入教程(任务创建的深入和任务调度机制分析)
文章目录 前言一、深入理解任务的创建二、任务的调度机制1.FreeRTOS中任务调度的策略2.FreeRTOS任务调度策略实现的核心3.FreeRTOS内部链表源码解析4.如何通过就绪链表管理任务的执行顺序 三、一个任务能够运行多久1.高优先级任务可抢占低优先级任务一直运行2.相同优先级的任务…...

Megatron-LM GPT 源码分析(一) Tensor Parallel分析
引言 本文基于开源代码 GitHub - NVIDIA/Megatron-LM: Ongoing research training transformer models at scale ,通过GPT的模型运行示例,从三个维度 - 模型结构、代码运行、代码逻辑说明 对其源码做深入的分析。 Tensor Parallel源码分析...

分类预测 | MATLAB实现SSA-CNN-GRU麻雀算法优化卷积门控循环单元数据分类预测
分类预测 | MATLAB实现SSA-CNN-GRU麻雀算法优化卷积门控循环单元数据分类预测 目录 分类预测 | MATLAB实现SSA-CNN-GRU麻雀算法优化卷积门控循环单元数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.MATLAB实现SSA-CNN-GRU麻雀算法优化卷积门控循环单元数据…...

婚礼的魅力
昨日有幸被邀请去当伴郎,虽然是替补,即别人鸽了,过去救急,但总归是去起作用。 婚礼的魅力,感受到了,满满的仪式感,紧凑的流程,还有不断的拍照,做视频,留下美好…...

【计算机网络笔记】DNS报文格式
DNS 提供域名到主机IP地址的映射 域名服务的三大要素: 域(Domain)和域名(Domain name): 域指由地 理位置或业务类型而联系在一起的一组计算机构 成。 主机:由域名来标识。域名是由字符和(或&a…...

10月28日
...

【性能测试】初识 Jmeter 中的 BeanShell
初识 Jmeter 中的 BeanShell 1.简介1.1 应用场景1.2 BeanShell 类型 2.常用内置变量2.1 log 日志模块2.2 vars 模块2.3 props 模块2.4 prev 模块 3.常见应用场景3.1 Java 文件处理3.2 导入外部 jar 包 BeanShell 是一个小型嵌入式 Java 源代码解释器,完全兼容 Java …...

Rust实现基于Tokio的限制内存占用的channel
Rust实现基于Tokio的限制内存占用的channel 简介 本文介绍如何基于tokio的channel实现一个限制内存占用的channel。 Tokio提供了多种协程间同步的接口,用于在不同的协程中同步数据。 常用的channel有两种:bounded和unbounded,其中ubbounded的channel可…...

【C++】C++入门(上)--命名空间 输入输出 缺省参数 函数重载
目录 一 命名空间 1 命名空间的定义 2 命名空间的使用 二 C输入和输出 1 输出 2 输入 三 缺省参数 1 缺省参数概念 2 缺省参数分类 (1) 全缺省参数 (2)半缺省参数 四 函数重载 1 函数重载概念 2 分类 1 参数类型不同 2 参数个数不同 3 参数类型顺序不同 3 C为什…...

设计模式:原型模式(C#、JAVA、JavaScript、C++、Python、Go、PHP)
上一篇《访问者模式》 下一篇《享元模式》 简介: 原型模式,它是一种创建型设计模式,它允许通过复制原型对象来创建新的对象,而无需知道创建的细节。其工作原…...

SpringMVC 资源状态转移RESTful
文章目录 1、RESTful简介a>资源b>资源的表述c>状态转移 2、RESTful的实现HiddenHttpMethodFilterRESTful案例 1、RESTful简介 REST:Representational State Transfer,表现层资源状态转移。 a>资源 资源是一种看待服务器的方式,…...

verilog vscode linux
安装 vscode 插件 插件:Verilog-HDL/SystemVerilog/Bluespec SystemVerilog 功能:.xdc .ucf .v 等代码高亮、代码格式化、语法检查(Linting)、光标放到变量上提示变量的信息等 关于其他语言的依赖工具等信息查看插件说明 代码对齐…...

Postman日常操作
一.Postman介绍 1.1第一个简单的demo 路特斯(英国汽车品牌)_百度百科 (baidu.com) 1.2 cookie 用postman测试需要登录权限的接口时,会被拦截,解决办法就是每次请求接口前,先执行登录,然后记住cookie或者to…...

10月份程序员书单推荐
新书书单 1、C程序设计教程(第9版) 1.广受认可的《C程序设计教程》系列的第9版(个别版本也译作《C语言大学教程》),秉承了该系列一贯的丰富而详细的风格。该系列一些版本因封面画有蚂蚁形象而被称为“C语言蚂蚁书”。…...

【ChatGPT系列】ChatGPT:创新工具还是失业威胁?
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…...

C++ 实现定时器的两种方法(线程定时和时间轮算法修改版)
定时器要求在固定的时间异步执行一个操作,比如boost库中的boost::asio::deadline_timer,以及MFC中的定时器。也可以利用c11的thread, mutex, condition_variable 来实现一个定时器。 1、使用C11中的thread, mutex, condition_variable来实现一个定时器。…...

2023mathorcup大数据竞赛选题建议及思路
大家好呀,昨天6点2023年第四届MathorCup高校数学建模挑战赛——大数据竞赛开赛,在这里给大家带来初步的选题建议及思路。 注意,本文章只是比较简略的图文讲解,更加详细完整的视频讲解请移步: 2023mathorcup大数据数学…...

部署vuepress项目到githubPage
部署vuepress项目到githubPage 1. 项目文件夹下有两个分支(main和gh-page) 1.1 main分支存放项目代码 1.2 gh-page分支存放 npm run docs:build之后的dist里面的所有文件 2. 分别提交到github上 3. 你的项目/docs/.vuepress/config.js module.export…...

ORACLE表空间说明及操作
ORACLE 表空间作用 数据存储:表空间是数据库中存储数据的逻辑结构。它提供了用于存储表、索引、视图、存储过程等数据库对象的空间。通过划分数据和索引等对象的存储,可以更好地管理和组织数据库的物理存储结构。性能管理和优化:通过将不同类…...

vue使用Element-plus的Image预览时样式崩乱
🔥博客主页: 破浪前进 🔖系列专栏: Vue、React、PHP ❤️感谢大家点赞👍收藏⭐评论✍️ 问题: 在使用组件库的image时出现了点小问题,预览的图片层级反而没有表格的层级高 效果图:…...

安装使用vcpkg的简易教程
目录 1. 首先安装vcpkg2. 在vcpkg目录下运行bootstrap-vcpkg.bat 命令3. 接着vs进行集成4. 使用vcpkg搜索可用的包5.下载安装所需包6.下载安装完成 1. 首先安装vcpkg 使用git命令下载 git clone https://github.com/Microsoft/vcpkg.git如果下载失败可直接下载文件 (vcpkg-ma…...

制作一个简单的C语言词法分析程序
1.分析组成 C语言的程序中,有很单词多符号和保留字。一些单词符号还有对应的左线性文法。所以我们需要先做出一个单词字符表,给出对应的识别码,然后跟据对应的表格来写出程序 2.程序设计 程序主要有循环判断构成。不需推理即可产生的符号我…...

Java项目中将MySQL改为8.0以上
博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容:毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 maven依…...

软考高项-计算题(2)
题4 项目的总预算是包含管理储备的,所以总预算应该是:13238102*360 ETC(BAC-EV)/CPI BAC60 EV60*0.318 CPI18/200.9 ETC42/0.9 答案选择C A 题5 因为题目中提到了“按目前的状况继续发展”,那么是:ETC(BAC-EV)/CPI EV1230*0…...

Centos使用war文件部署jenkins
部署jenkins所需要的jdk环境如下: 这里下载官网最新的版本: 选择jenkins2.414.3版本,所以jdk环境最低得是java11 安装java11环境 这里直接安装open-jdk yum -y install java-11-openjdk.x86_64 java-11-openjdk-devel.x86_64下载jenkins最新…...

数据结构和算法——用C语言实现所有排序算法
文章目录 前言排序算法的基本概念内部排序插入排序直接插入排序折半插入排序希尔排序 交换排序冒泡排序快速排序 选择排序简单选择排序堆排序 归并排序基数排序 外部排序多路归并败者树置换——选择排序最佳归并树 前言 本文所有代码均在仓库中,这是一个完整的由纯…...

吃豆人C语言开发—Day2 需求分析 流程图 原型图
目录 需求分析 流程图 原型图 主菜单: 设置界面: 地图选择: 游戏界面: 收集完成提示: 游戏胜利界面: 游戏失败界面 死亡提示: 这个项目是我和朋友们一起开发的,在此声明一下…...

Nautilus Chain 联合香港数码港举办 BIG DEMO DAY活动,释放何信号?
在今年的 10 月 26 日 9:30-18:30 GMT8 期间,Nautilus Chain 联合香港数码港共同举办了 “BIG DEMO DAY” Web3 项目路演活动,包括Xwinner、Sleek、Tx、All weather、Coral Finance、DBOE、PARSIQ、Hookfi、Parallels、Fintestra 以及 dot.GAMING 等在内…...

手写RPC框架
文章目录 什么是RPC框架RPC框架中的关键点通信协议序列化协议动态代理和反射 目前已有的RPC框架手写RPC框架介绍项目框架项目执行流程项目启动 什么是RPC框架 RPC(Remote Procedure Call,远程过程调用), 简单来说遵循RPC协议的就是RPC框架. …...

音视频常见问题(六):视频黑边或放大
摘要 本文介绍了视频黑边或放大的原因和解决方案。主要原因包括视频分辨率与显示视图尺寸不一致、摄像头采集、美颜滤镜格式兼容和分辨率。为了解决这些问题,开发者可以选择合适的渲染模式、动态调整分辨率、处理视频旋转和使用自定义视频渲染。 即构音视频SDK提供…...