网站首页 > 技术文章 正文
/* return the number of undisplayed frames in the queue */
static int frame_queue_nb_remaining(FrameQueue *f)
{
return f->size - f->rindex_shown;
}
函数整体功能概述
frame_queue_nb_remaining 函数的主要作用是计算并返回 FrameQueue 结构体中队列里还未显示(或未处理)的帧数。通过简单的数值计算,依据队列中当前的总帧数以及已经展示(处理)过的帧数相关的偏移量,得出尚未展示的帧数,该函数为程序中其他部分判断队列中剩余可处理数据量提供了便利。
函数具体实现逻辑分析
return f->size - f->rindex_shown;
在这行代码中,利用了 FrameQueue 结构体中的两个成员变量 f->size 和 f->rindex_shown 来进行计算。
- f->size 表示当前帧队列中已有的帧数,它反映了队列整体的占用情况,也就是队列里总共包含了多少帧数据(无论是已经展示过的还是尚未展示的)。
- f->rindex_shown 通常用于表示已经展示或者处理过的帧数相关的偏移量,即记录了从队列头部开始,已经被处理或者展示给用户(具体取决于程序对这个变量的使用场景,比如视频播放场景下就是已经播放过的帧数等)的帧数数量。
通过用 f->size 减去 f->rindex_shown,就能得到两者的差值,这个差值恰好就是队列中还没有被展示(处理)的帧数,然后将这个计算结果作为函数的返回值返回给调用者,以便调用者根据这个返回值来了解当前帧队列中还有多少帧数据等待后续的处理操作,例如决定是否需要等待更多帧入队、是否可以进行一些批量处理等操作,取决于程序围绕帧队列所构建的具体逻辑。
注意事项
- 对于 f->size 和 f->rindex_shown 这两个成员变量,它们的含义和使用方式是由整个程序围绕 FrameQueue 结构体所设定的特定逻辑来决定的。所以在程序的其他地方,需要对这两个变量进行正确的维护和更新,确保它们的值始终能够准确反映出队列的实际状态。如果这两个变量的值出现错误,比如没有及时更新或者被错误修改了,那么通过本函数计算出来的未显示帧数就会不准确,进而可能影响到程序中依赖这个返回值所做的后续决策和操作,导致程序出现逻辑错误或者运行异常等情况。
- 在多线程环境下,由于多个线程可能会同时涉及对帧队列进行操作(如往队列中添加帧、从队列中读取并处理帧等操作),而这些操作往往都会影响到 f->size 和 f->rindex_shown 的值,所以需要确保在这些操作过程中对这两个变量的访问和修改是线程安全的。例如,在修改它们的值时,可能需要通过互斥锁等同步机制来避免并发访问导致的数据不一致问题,否则本函数获取到的这两个变量的值可能是处于一种不稳定、不一致的状态,从而导致返回的未显示帧数结果不可靠。
/* return last shown position */
static int64_t frame_queue_last_pos(FrameQueue *f)
{
Frame *fp = &f->queue[f->rindex];
if (f->rindex_shown && fp->serial == f->pktq->serial)
return fp->pos;
else
return -1;
}
函数整体功能概述
frame_queue_last_pos 函数旨在获取并返回 FrameQueue 结构体中队列里最后显示(或处理)位置的相关信息,具体是返回最后显示帧对应的位置值(如果满足一定条件的话),若不满足相应条件,则返回 -1,以此来告知调用者当前不存在有效的最后显示位置信息。这个位置信息通常可用于诸如音视频播放中的定位、同步等相关操作场景中,帮助程序了解播放的进度或者进行一些基于位置的判断等。
函数具体实现逻辑分析
- 获取当前读索引对应的帧元素指针
Frame *fp = &f->queue[f->rindex];
首先,通过 FrameQueue 结构体中的 rindex(读索引)成员变量来确定当前逻辑上读取队列元素的起始位置,以该位置作为索引从 queue 数组中获取对应的帧元素,并将该帧元素的指针赋值给 Frame 类型的指针变量 fp。这里的 rindex 通常标记着当前读取队列的起始位置(基于循环队列的概念),通过它可以定位到相关的帧元素,方便后续对该元素中包含的各种信息(如这里要关注的 pos、serial 等信息)进行访问和判断。
- 判断是否满足返回位置信息的条件并返回相应结果
if (f->rindex_shown && fp->serial == f->pktq->serial)
return fp->pos;
else
return -1;
这里会检查两个条件:
- f->rindex_shown:这个变量通常表示已经展示或者处理过的帧数相关的偏移量,只有当它的值为 true(或者说非 0,具体取决于其数据类型定义,这里假设非 0 表示已展示)时,才意味着存在已经被处理过的帧,有获取最后显示位置信息的可能性;若它为 0,说明还没有帧被展示过,那就不存在有效的最后显示位置,此时应返回 -1。
- fp->serial == f->pktq->serial:这里涉及到 serial(序列号)的比较,fp->serial 是当前获取到的帧元素(通过前面步骤定位到的 fp 所指向的帧)的序列号,f->pktq->serial 则是与之关联的数据包队列的序列号。只有当这两个序列号相等时,才表示当前帧与对应的数据包是匹配的、处于正确的逻辑序列中,此时该帧的位置信息才是有效的,可以返回其对应的 pos(位置)值;若序列号不相等,说明可能存在流切换、数据不一致等情况,此时该帧的位置信息不符合预期,不能作为有效的最后显示位置信息,同样需要返回 -1。
如果上述两个条件同时满足,就返回 fp->pos,即当前帧元素中记录的位置值,这个值就是最后显示位置对应的位置信息;若不满足这两个条件中的任何一个,函数则返回 -1,表示不存在有效的最后显示位置信息可供返回。
注意事项
- 对于 f->rindex_shown、fp->serial 和 f->pktq->serial 这些变量,它们的含义和使用方式是由整个程序围绕 FrameQueue 结构体以及相关联的数据包队列等所设定的特定逻辑决定的。在程序的其他地方,需要对这些变量进行正确的维护和更新,确保它们的值始终能准确反映出队列的真实状态以及数据的逻辑关联性。例如,如果 serial 的更新机制出现错误,导致帧的序列号与数据包队列的序列号不匹配情况频发,那么本函数可能经常无法正确返回有效的位置信息,影响到程序中基于此位置信息所进行的后续操作,比如播放进度的准确显示、音视频同步的正确判断等。
- 在多线程环境下,由于多个线程可能同时对帧队列以及相关的数据包队列进行操作,这可能会导致 f->rindex_shown、fp->serial 和 f->pktq->serial 等变量的值在不同时刻发生变化。所以需要通过合适的线程同步机制(如互斥锁、条件变量等)来保证在访问和判断这些变量时,它们的值是稳定且一致的,避免出现因并发访问导致的错误判断,进而影响函数返回结果的准确性以及程序的正常运行。
static void decoder_abort(Decoder *d, FrameQueue *fq)
{
packet_queue_abort(d->queue);
frame_queue_signal(fq);
SDL_WaitThread(d->decoder_tid, NULL);
d->decoder_tid = NULL;
packet_queue_flush(d->queue);
}
函数整体功能概述
decoder_abort 函数主要用于在需要终止解码器相关操作时,执行一系列的清理和终止流程。它会先通知数据包队列停止接收新数据,接着向帧队列发送信号以唤醒可能正在等待的线程,然后等待解码器线程结束,最后清理相关资源并刷新数据包队列,确保整个解码器相关的资源能被妥善处理,避免出现数据不一致、内存泄漏或线程异常等问题。
函数具体流程分析
- 通知数据包队列停止接收新数据
调用 packet_queue_abort 函数,传入解码器 d 所关联的数据包队列指针 d->queue。这个函数的作用通常是设置数据包队列中的一个标志位(比如 abort_request 标志),用于告知队列相关的操作(如往队列中放入数据包的线程等)停止继续接收新的数据,避免后续再有新数据进入队列而导致一些混乱,使得整个流程能逐步有序地停止下来。
- 向帧队列发送信号唤醒等待线程
调用 frame_queue_signal 函数,传入帧队列指针 fq。该函数内部会先对帧队列的互斥锁进行加锁,然后通过 SDL_CondSignal 函数向帧队列对应的条件变量发送一个信号,目的是唤醒至少一个正在等待该条件变量的线程(如果有线程正在等待的话)。这些等待线程可能是在等待帧队列中有可读帧或者可写帧等情况,发送信号后它们可以被唤醒并去检查相应的条件是否满足,进而执行后续的操作,比如结束等待并进行资源清理等,以此来推动整个程序在终止流程中的线程同步。
3. 等待解码器线程结束
调用 SDL_WaitThread 函数,传入解码器 d 的解码器线程标识符 d->decoder_tid 以及一个 NULL 参数(表示不需要获取线程的返回值)。这个函数会阻塞当前线程,直到指定的解码器线程(由 d->decoder_tid 标识)执行完毕并退出。这样做是为了确保解码器线程能完整地结束其正在执行的任务,释放它所占用的资源,避免线程被强制终止而可能遗留一些未处理好的资源或者导致程序出现不稳定的情况,保证整个解码器相关操作能平稳终止。
4. 清理解码器线程标识符
在解码器线程结束后,将解码器 d 的 decoder_tid 成员变量设置为 NULL,表示当前解码器已经没有对应的活动线程了,这是一种对解码器状态的正确标记,方便后续在程序中判断解码器的线程状态,避免误操作或者重复使用已经结束的线程相关资源等情况。
5. 刷新数据包队列
packet_queue_flush(d->queue);
调用 packet_queue_flush 函数,传入 d->queue,这个函数的作用通常是清空数据包队列中已有的数据包,释放这些数据包所占用的各种资源(比如内存等),将队列恢复到初始的空状态,为下次可能的使用或者整个程序的重新初始化等做好准备,同时也避免了之前遗留数据包对后续操作造成影响,确保资源被彻底清理干净。
注意事项
- 在多线程环境下,各个操作之间的顺序很重要。例如,先通知数据包队列停止接收新数据,然后唤醒帧队列等待的线程以及等待解码器线程结束,这样的顺序是为了保证整个终止流程能有条不紊地进行,避免出现数据不一致或者线程死锁等问题。如果顺序不当,比如在解码器线程还未结束时就过早地清理了一些关键资源,可能会导致解码器线程出现异常或者无法正常结束,进而影响整个程序的稳定性。
- 对于涉及的各个函数(如 packet_queue_abort、frame_queue_signal、SDL_WaitThread、packet_queue_flush 等),它们都依赖于相关结构体(如 PacketQueue、FrameQueue 等)中的一些成员变量(如标志位、条件变量、线程标识符等)的正确设置和维护。在程序的其他部分,需要确保这些变量能按照预期进行更新和管理,否则可能导致这些函数无法正常发挥作用,例如信号无法正确唤醒线程、线程无法被正确等待结束等情况,从而影响 decoder_abort 函数整体的终止流程效果。
- 整个 decoder_abort 函数执行过程中,要确保没有其他线程在同一时间对正在操作的相关资源(数据包队列、帧队列、解码器线程等)进行不恰当的访问或修改,不然可能会引发并发访问冲突,出现数据丢失、程序崩溃等严重问题。所以在必要时,可能需要额外的互斥锁等同步机制来保障资源访问的独占性,虽然这里从代码表面看没有显式体现,但在更复杂的多线程环境下需要综合考虑这些因素。
static inline void fill_rectangle(int x, int y, int w, int h)
{
SDL_Rect rect;
rect.x = x;
rect.y = y;
rect.w = w;
rect.h = h;
if (w && h)
SDL_RenderFillRect(renderer, &rect);
}
函数整体功能概述
fill_rectangle 函数的主要作用是在图形渲染的相关场景中,使用 SDL(Simple DirectMedia Layer,一个跨平台的多媒体库)库来填充一个指定位置和大小的矩形区域。它接收矩形的左上角坐标(x 和 y)以及宽度(w)和高度(h)作为参数,先构建一个 SDL_Rect 结构体来表示这个矩形,然后在宽度和高度都不为 0 的条件下,调用 SDL_RenderFillRect 函数进行实际的矩形填充操作。
函数具体流程分析
- 构建 SDL_Rect 结构体
SDL_Rect rect;
rect.x = x;
rect.y = y;
rect.w = w;
rect.h = h;
首先定义了一个 SDL_Rect 类型的变量 rect,这个结构体在 SDL 库中常用于表示二维平面上的矩形区域,它包含了四个成员变量,分别对应矩形左上角的 x 坐标(rect.x)、y 坐标(rect.y)以及矩形的宽度(rect.w)和高度(rect.h)。在这里,将传入函数的参数 x、y、w、h 分别赋值给 rect 结构体对应的成员变量,从而构建出了一个能准确描述要填充矩形区域位置和大小的 SDL_Rect 结构体实例。
- 调用 SDL_RenderFillRect 进行矩形填充(条件判断)
if (w && h)
SDL_RenderFillRect(renderer, &rect);
这里进行了一个条件判断,通过逻辑与操作符(&&)检查矩形的宽度 w 和高度 h 是否都不为 0。只有当宽度和高度都大于 0 时,才调用 SDL_RenderFillRect 函数进行矩形填充操作。这是因为在图形渲染中,宽度或高度为 0 的矩形实际上是没有可填充区域的,调用 SDL_RenderFillRect 函数去处理这样的情况没有实际意义,甚至可能导致错误(具体取决于 SDL_RenderFillRect 函数内部的实现和对参数的要求)。
SDL_RenderFillRect 函数用于在当前渲染目标(由 renderer 变量指定,这里 renderer 应该是在函数外部定义好的有效的 SDL 渲染器对象)上填充给定的矩形区域(通过传入 &rect,即前面构建好的 SDL_Rect 结构体的指针来指定要填充的矩形)。它会根据渲染器的设置以及当前的渲染状态(比如颜色设置等,通常需要在其他地方提前配置好),将指定的矩形区域用相应的颜色填充,从而在屏幕或者其他渲染目标上显示出一个填充后的矩形效果。
注意事项
- renderer 变量:在代码中直接使用了 renderer 变量来调用 SDL_RenderFillRect 函数,但从这个函数的代码片段来看,并没有对 renderer 进行定义或者初始化相关的操作展示。在实际的完整程序中,需要确保 renderer 是一个有效的、已经正确初始化的 SDL 渲染器对象,否则调用 SDL_RenderFillRect 函数时会出现错误,比如导致程序崩溃或者无法正确渲染出矩形等情况。
- 参数合法性:虽然函数内部对宽度 w 和高度 h 进行了是否为 0 的判断,但传入的参数 x、y、w、h 的合法性还需要在调用该函数之前进一步确保。例如,坐标值不能超出渲染目标的有效范围(如果有范围限制的话),不然可能会导致渲染异常或者出现不可预期的图形显示效果。而且,参数的类型(这里应该是整数类型)也需要符合 SDL_Rect 结构体以及 SDL_RenderFillRect 函数对参数类型的要求,否则同样可能引发错误。
- 多线程环境(如果涉及):如果该函数在多线程环境下被调用,需要考虑与渲染相关操作的线程安全性。因为对渲染器(renderer)的操作可能不是线程安全的,多个线程同时调用 SDL_RenderFillRect 函数或者对渲染器进行其他相关操作(比如设置渲染颜色、目标等)可能会导致数据不一致、图形显示混乱等问题。在这种情况下,可能需要通过合适的线程同步机制(如互斥锁等)来保证每次只有一个线程能够访问和操作渲染器对象,确保渲染操作的正确性和稳定性。
static int realloc_texture(SDL_Texture **texture, Uint32 new_format, int new_width, int new_height, SDL_BlendMode blendmode, int init_texture)
{
Uint32 format;
int access, w, h;
if (!*texture || SDL_QueryTexture(*texture, &format, &access, &w, &h) < 0 || new_width != w || new_height != h || new_format != format) {
void *pixels;
int pitch;
if (*texture)
SDL_DestroyTexture(*texture);
if (!(*texture = SDL_CreateTexture(renderer, new_format, SDL_TEXTUREACCESS_STREAMING, new_width, new_height)))
return -1;
if (SDL_SetTextureBlendMode(*texture, blendmode) < 0)
return -1;
if (init_texture) {
if (SDL_LockTexture(*texture, NULL, &pixels, &pitch) < 0)
return -1;
memset(pixels, 0, pitch * new_height);
SDL_UnlockTexture(*texture);
}
av_log(NULL, AV_LOG_VERBOSE, "Created %dx%d texture with %s.\n", new_width, new_height, SDL_GetPixelFormatName(new_format));
}
return 0;
}
函数整体功能概述
realloc_texture 函数主要用于重新分配或初始化一个 SDL_Texture 纹理对象。它会根据传入的新格式(new_format)、新宽度(new_width)、新高度(new_height)以及混合模式(blendmode)等参数,判断是否需要销毁已有的纹理并创建新的纹理,同时还可以选择是否对新创建的纹理进行初始化操作(将纹理像素数据清 0),常用于图形渲染相关程序中对纹理资源的动态管理场景,比如在视频播放时,根据视频画面尺寸变化来更新对应的纹理。
函数具体流程分析
- 检查是否需要重新分配纹理
if (!*texture || SDL_QueryTexture(*texture, &format, &access, &w, &h) < 0 || new_width!= w || new_height!= h || new_format!= format) {
这里进行了一个条件判断,通过逻辑或(||)连接了多个子条件来确定是否需要重新分配纹理:
- !*texture:首先检查传入的 SDL_Texture 指针 *texture 是否为 NULL,如果是 NULL,表示当前没有已存在的纹理,那么显然需要创建一个新的纹理,满足重新分配的条件。
- SDL_QueryTexture(*texture, &format, &access, &w, &h) < 0:调用 SDL_QueryTexture 函数来获取当前已有纹理(如果存在的话)的相关信息,包括纹理的像素格式(format)、访问模式(access)以及宽度(w)和高度(h)。如果这个查询操作失败(返回值小于 0),说明无法获取到已有纹理的正确信息,这种情况下也需要重新分配纹理来确保后续操作能正常进行。
- new_width!= w || new_height!= h || new_format!= format:分别比较传入的新宽度(new_width)、新高度(new_height)和新格式(new_format)与已查询到的现有纹理的宽度(w)、高度(h)和格式(format)是否一致。只要其中有一个参数不一致,就意味着现有纹理的规格与期望的不一致,需要重新创建纹理来满足新的要求,同样满足重新分配的条件。
- 销毁已有纹理(如果存在)并创建新纹理
if (*texture)
SDL_DestroyTexture(*texture);
if (!(*texture = SDL_CreateTexture(renderer, new_format, SDL_TEXTUREACCESS_STREAMING, new_width, new_height)))
return -1;
- 如果前面的条件判断确定需要重新分配纹理,并且当前存在已有的纹理(*texture 不为 NULL),那么首先调用 SDL_DestroyTexture 函数销毁已有的纹理,释放其占用的资源,避免内存泄漏等问题。
- 接着调用 SDL_CreateTexture 函数创建一个新的纹理,传入渲染器对象(renderer,这里同样需要确保 renderer 是一个有效的、已正确初始化的对象)、新的像素格式(new_format)、纹理访问模式(SDL_TEXTUREACCESS_STREAMING,表示可以逐行读写纹理像素数据的一种常用访问模式)以及新的宽度(new_width)和高度(new_height)参数。如果创建纹理失败(函数返回 NULL),则直接返回 -1,表示纹理重新分配过程出现错误,无法继续进行。
- 设置纹理的混合模式
if (SDL_SetTextureBlendMode(*texture, blendmode) < 0)
return -1;
在成功创建新纹理后,调用 SDL_SetTextureBlendMode 函数来设置新纹理的混合模式,传入刚创建的纹理指针(*texture)和指定的混合模式参数(blendmode)。如果设置操作失败(返回值小于 0),函数直接返回 -1,因为混合模式设置不成功可能会影响后续纹理在渲染时与其他元素的融合效果,导致渲染结果不符合预期,所以无法继续进行后续操作。
- 初始化新纹理(可选操作,根据 init_texture 参数决定)
if (init_texture) {
if (SDL_LockTexture(*texture, NULL, &pixels, &pitch) < 0)
return -1;
memset(pixels, 0, pitch * new_height);
SDL_UnlockTexture(*texture);
}
根据传入的 init_texture 参数来决定是否对新创建的纹理进行初始化操作:
- 如果 init_texture 的值为 true(这里假设非 0 表示 true,具体取决于其数据类型定义),首先调用 SDL_LockTexture 函数锁定新创建的纹理,这样做是为了获取纹理的像素数据指针(pixels)以及每行像素数据的字节长度(pitch),以便后续对纹理像素进行操作。如果锁定纹理操作失败(返回值小于 0),则返回 -1,因为无法获取到可操作的像素数据指针,就不能进行初始化操作了。
- 在成功锁定纹理并获取到相关指针和字节长度后,使用 memset 函数将纹理的所有像素数据清 0,通过将 pixels 指针所指向的内存区域(总大小为 pitch * new_height)的每个字节都设置为 0 来实现初始化效果,即将整个纹理填充为默认的空白状态(通常用于一些需要先清空纹理内容的场景,比如重新绘制新内容之前等情况)。
- 最后调用 SDL_UnlockTexture 函数解锁纹理,释放对纹理像素数据的锁定状态,使得纹理可以正常用于后续的渲染等操作。
- 记录日志信息(可选)
av_log(NULL, AV_LOG_VERBOSE, "Created %dx%d texture with %s.\n", new_width, new_height, SDL_GetPixelFormatName(new_format));
调用 av_log 函数记录一条详细程度为 VERBOSE(详细)的日志信息,日志内容包含了新创建纹理的宽度(new_width)、高度(new_height)以及像素格式名称(通过 SDL_GetPixelFormatName(new_format) 获取),用于在程序运行过程中方便开发者查看纹理创建的相关情况,比如在调试或者查看资源分配情况时很有用。
- 返回操作结果
return 0;
如果前面的所有操作(重新分配纹理、设置混合模式、可选的纹理初始化等)都顺利完成,没有出现返回 -1 的错误情况,那么函数最终返回 0,表示纹理重新分配或初始化操作成功。
注意事项
- renderer 变量:和前面介绍的函数类似,在代码中直接使用了 renderer 变量来创建纹理以及可能涉及的其他与渲染相关操作(虽然这里未详细体现其在其他操作中的使用,但基于 SDL 库的相关函数调用逻辑可知其重要性),需要确保 renderer 是一个有效的、已经正确初始化的 SDL 渲染器对象,否则调用 SDL_CreateTexture 等函数时会出现错误,影响整个纹理重新分配过程以及后续的渲染操作。
- 参数合法性:传入的 new_format、new_width、new_height、blendmode 和 init_texture 等参数的合法性需要在调用函数之前进行检查和确保。例如,new_format 需要是 SDL 库支持的有效的像素格式,new_width 和 new_height 应该是合理的正整数(通常情况下,不能为负数或过大超出系统限制等),blendmode 也需要是 SDL 库中定义的合法的混合模式类型,否则可能导致函数内部的相关操作(如创建纹理、设置混合模式等)出现错误,无法正确执行。
- 多线程环境(如果涉及):如果该函数在多线程环境下被调用,要考虑与纹理操作相关的线程安全性。特别是对纹理的创建、销毁以及锁定和解锁等操作,多个线程同时进行这些操作可能会导致资源冲突、数据不一致等问题,比如可能出现纹理被重复销毁或创建、同时锁定导致死锁等情况。所以在多线程场景下,可能需要通过合适的线程同步机制(如互斥锁等)来保证每次只有一个线程能够对纹理进行相关操作,确保纹理管理和使用的正确性与稳定性。
static void calculate_display_rect(SDL_Rect *rect,
int scr_xleft, int scr_ytop, int scr_width, int scr_height,
int pic_width, int pic_height, AVRational pic_sar)
{
AVRational aspect_ratio = pic_sar;
int64_t width, height, x, y;
if (av_cmp_q(aspect_ratio, av_make_q(0, 1)) <= 0)
aspect_ratio = av_make_q(1, 1);
aspect_ratio = av_mul_q(aspect_ratio, av_make_q(pic_width, pic_height));
/* XXX: we suppose the screen has a 1.0 pixel ratio */
height = scr_height;
width = av_rescale(height, aspect_ratio.num, aspect_ratio.den) & ~1;
if (width > scr_width) {
width = scr_width;
height = av_rescale(width, aspect_ratio.den, aspect_ratio.num) & ~1;
}
x = (scr_width - width) / 2;
y = (scr_height - height) / 2;
rect->x = scr_xleft + x;
rect->y = scr_ytop + y;
rect->w = FFMAX((int)width, 1);
rect->h = FFMAX((int)height, 1);
}
函数整体功能概述
calculate_display_rect 函数主要用于根据给定的屏幕参数(如屏幕坐标、宽度、高度)以及图片相关参数(如图片宽度、高度、像素宽高比)来计算出一个合适的矩形区域,该矩形区域定义了图片在屏幕上的显示位置和大小,以保证图片能按照正确的比例进行显示,常用于视频播放、图像展示等涉及图像适配屏幕显示的场景中。
函数具体流程分析
- 初始化变量与处理特殊宽高比情况
AVRational aspect_ratio = pic_sar;
int64_t width, height, x, y;
if (av_cmp_q(aspect_ratio, av_make_q(0, 1)) <= 0)
aspect_ratio = av_make_q(1, 1);
- 首先,定义了一个 AVRational 类型的变量 aspect_ratio,并将传入的图片像素宽高比参数 pic_sar 赋值给它,用于后续基于图片自身宽高比相关的计算。同时定义了几个 int64_t 类型的变量 width、height、x、y,它们将分别用于存储计算过程中涉及的宽度、高度、水平坐标和垂直坐标等数值。
- 接着,通过 av_cmp_q 函数比较 aspect_ratio 和 av_make_q(0, 1) 的大小关系(av_cmp_q 函数用于比较两个 AVRational 类型的有理数的大小),如果 aspect_ratio 小于等于 av_make_q(0, 1),说明宽高比可能是无效的(比如宽高比的分子为 0 等不符合常理的情况),此时将 aspect_ratio 设置为 av_make_q(1, 1),也就是默认采用 1:1 的宽高比,确保后续计算能正常进行。
- 计算基于图片宽高比调整后的宽高值(初步)
aspect_ratio = av_mul_q(aspect_ratio, av_make_q(pic_width, pic_height));
height = scr_height;
width = av_rescale(height, aspect_ratio.num, aspect_ratio.den) & ~1;
- 先调用 av_mul_q 函数将之前处理后的 aspect_ratio 与由图片宽度 pic_width 和图片高度 pic_height 组成的有理数(通过 av_make_q 函数创建)相乘,这样得到的新 aspect_ratio 综合考虑了图片原始的宽高比以及像素宽高比,更能准确反映图片实际的显示比例关系,用于后续计算图片在屏幕上适配后的宽度和高度。
- 然后,先将屏幕高度 scr_height 赋值给 height,表示初步以屏幕高度作为参考来计算适配后的宽度和高度。接着调用 av_rescale 函数,它根据给定的比例(这里就是新的 aspect_ratio)对 height(屏幕高度)进行缩放计算,得到适配后的宽度值 width,并通过 & ~1 操作将计算结果的最低位清 0(通常用于确保宽度值是偶数,可能与某些显示设备或者图形库的要求相关,比如在一些对像素对齐有要求的场景下)。
- 调整宽高值以适应屏幕宽度(如果超出)
if (width > scr_width) {
width = scr_width;
height = av_rescale(width, aspect_ratio.den, aspect_ratio.num) & ~1;
}
- 检查计算得到的 width 是否大于屏幕宽度 scr_width,如果大于,说明按照前面基于屏幕高度计算出的宽度超出了屏幕范围,此时需要进行调整。
- 将 width 的值设置为屏幕宽度 scr_width,然后再次调用 av_rescale 函数,这次是根据调整后的宽度 width(即屏幕宽度)以及 aspect_ratio 的分母和分子,反算得到适配后的高度值 height,同样通过 & ~1 操作保证高度值为偶数,确保图片能在屏幕宽度范围内按照正确比例显示。
- 计算图片在屏幕上的显示坐标
x = (scr_width - width) / 2;
y = (scr_height - height) / 2;
在确定了图片适配后的宽度 width 和高度 height 后,计算图片在屏幕上的水平坐标 x 和垂直坐标 y。通过用屏幕宽度 scr_width 减去图片宽度 width 后除以 2,得到图片在水平方向上相对于屏幕左边的偏移量,也就是图片水平居中的坐标;同理,用屏幕高度 scr_height 减去图片高度 height 后除以 2,得到图片在垂直方向上相对于屏幕上边的偏移量,使图片能在屏幕上垂直居中显示,从而实现图片在屏幕上的居中对齐效果。
- 设置输出矩形结构体的相关属性
rect->x = scr_xleft + x;
rect->y = scr_ytop + y;
rect->w = FFMAX((int)width, 1);
rect->h = FFMAX((int)height, 1);
- 最后,将计算得到的图片显示坐标和大小信息填充到传入的 SDL_Rect 类型的指针 rect 所指向的结构体中。
- rect->x 和 rect->y 分别通过将屏幕的起始坐标(scr_xleft 和 scr_ytop)与前面计算出的图片相对屏幕的偏移量(x 和 y)相加得到,确定了图片在整个屏幕坐标系中的具体左上角坐标位置。
- 对于 rect->w 和 rect->h,使用 FFMAX 宏(通常用于取两个值中的较大值)来确保宽度和高度至少为 1(防止出现宽度或高度为 0 等不符合显示要求的情况),将经过类型转换后的 width 和 height 值分别赋给 rect->w 和 rect->h,这样就完整地定义了图片在屏幕上显示的矩形区域,后续可以基于这个矩形区域进行图像的渲染等操作。
注意事项
- AVRational 类型相关操作:函数中频繁使用了 AVRational 类型以及相关的操作函数(如 av_cmp_q、av_mul_q、av_make_q、av_rescale 等),这些操作依赖于 FFmpeg 库对有理数的处理机制。需要确保正确引入了 FFmpeg 相关的头文件以及链接了对应的库文件,并且传入的 pic_sar(图片像素宽高比)等参数构造的 AVRational 类型值是符合预期和合法的,否则可能导致这些有理数运算出现错误,进而影响图片显示位置和大小的计算结果。
- 整数类型转换与取值范围:在计算过程中涉及到 int64_t 类型与 int 类型之间的转换(如将 width 和 height 转换为 int 类型赋值给 rect->w 和 rect->h),要注意数值范围的问题,避免出现数据溢出等情况。特别是在进行缩放计算等可能导致数值变化较大的操作时,需要确保转换后的 int 类型能够正确表示计算结果,不然可能会导致显示矩形的宽度和高度出现错误值,影响图片的正常显示效果。
- 屏幕参数与图片参数的合法性:传入的屏幕相关参数(scr_xleft、scr_ytop、scr_width、scr_height)以及图片相关参数(pic_width、pic_height、pic_sar)的合法性和合理性需要提前确认。例如,屏幕宽度和高度不能为负数或不合理的极小值,图片的宽度、高度以及宽高比也需要是符合实际情况的值,否则在函数内部进行各种计算时可能会出现除以 0、计算结果异常等问题,无法正确计算出图片的显示矩形区域。
- 多线程环境(如果涉及):如果该函数在多线程环境下被调用,要考虑与显示相关计算的线程安全性。虽然这个函数主要是进行数值计算,但如果多个线程同时传入不同的参数来调用该函数,并且后续基于计算结果对共享的显示资源(比如同一个屏幕缓冲区等)进行操作,可能会导致显示混乱、数据不一致等问题。所以在多线程场景下,可能需要通过合适的线程同步机制(如互斥锁等)来保证每次只有一个线程能够调用该函数并基于其结果进行后续显示相关操作,确保显示效果的正确性和稳定性。
- 上一篇: 3款思维导图软件,让你的导图锦上添花
- 下一篇: OpenGL是一种跨平台的图形库--简介
猜你喜欢
- 2024-12-11 解读常见图像格式
- 2024-12-11 PPT中,这5个常见的标题栏设计方法,能提高标题的设计感
- 2024-12-11 工科生太浪漫,亲手做的小玩具,跨越时空,满满都是90后的回忆!
- 2024-12-11 巧用SmartArt样式 做好媒体展示
- 2024-12-11 这门课盛产心灵手巧的工科生!把童年回忆变成现实,可以把“三潭印月”放在手心……
- 2024-12-11 U8g2图形库与STM32移植(I2C,软件与硬件)
- 2024-12-11 9个超方便的LOGO制作神器!分享给大家
- 2024-12-11 结构图和关系图的福音来了,快收藏
- 2024-12-11 PPT仪表盘图表,感受数据的速度与激情!
- 2024-12-11 制作PPT 活用SmartArt样式
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)