x264中的空闲帧,参考帧队列管理

本文深入探讨H.264编码器中参考帧的更新与管理机制,包括帧作为参考帧的条件判断、帧的插入与删除流程、线程间帧的共享及释放机制。详细解释了x264_frame_push、x264_frame_pop等关键函数的作用,以及如何通过MMCO命令更新参考帧POC映射。

先直接从更新参考帧的位置看起,这里的操作是最为复杂的地方

static inline int reference_update( x264_t *h )
{
    if( !h->fdec->b_kept_as_ref )//如果帧不被当做参考帧
    {
        if( h->i_thread_frames > 1 )
        {
            x264_frame_push_unused( h, h->fdec );
            h->fdec = x264_frame_pop_unused( h, 1 );
            if( !h->fdec )
                return -1;
        }
        return 0;
    }

    /* apply mmco from previous frame. 将参考帧poc映射,后续再细琢磨*/
    for( int i = 0; i < h->sh.i_mmco_command_count; i++ )
        for( int j = 0; h->frames.reference[j]; j++ )
        {
            if( h->frames.reference[j]->i_poc == h->sh.mmco[i].i_poc )
            {
                x264_frame_push_unused( h, x264_frame_shift( &h->frames.reference[j] ) );//
            }
        }
        
    /* move frame in the buffer */
    x264_frame_push( h->frames.reference, h->fdec );//插入重建帧,当成参考帧
    if( h->frames.reference[h->sps->i_num_ref_frames] )//如果参考序列满了,则要删掉最前的那个参考帧,然后把参考帧序列挪动一下位置
    {
        x264_frame_push_unused( h, x264_frame_shift( h->frames.reference ) );//把新的帧查到最前面
    }
    
    h->fdec = x264_frame_pop_unused( h, 1 );//取出一个空余的帧节点,下一次重建帧使用这个存储数据
    if( !h->fdec )
        return -1;
    return 0;
}
 

void x264_frame_push( x264_frame_t **list, x264_frame_t *frame )//上面调用这个函数向参考帧中插入一个frame,
{
    int i = 0;
    while( list[i] ) i++;//找到一个空闲的frame
    list[i] = frame;//把要插入的frame地址赋值过去
}

//如何理解这句话

x264_frame_push_unused( h, x264_frame_shift( h->frames.reference ) );//把新的帧查到最前面

先看x264_frame_shift

x264_frame_t *x264_frame_shift( x264_frame_t **list )
{
    x264_frame_t *frame = list[0];
    int i;
    for( i = 0; list[i]; i++ )
        list[i] = list[i+1];
    assert(frame);
    return frame;//顺序性的取帧,一个线程取一个
}

把位置0的frame取出来,后面的依次向前挪动一个位置。因为reference[0]是最老的一个参考帧,这里存满了,要删掉一个最老的

//如何删掉这个最老的参考帧,i_reference_count 被参考的次数

void x264_frame_push_unused( x264_t *h, x264_frame_t *frame )
{
    assert( frame->i_reference_count > 0 );
    frame->i_reference_count--;//有多少个线程把这个帧当成参考帧,清空一个,这个帧的i_reference_count 就 -- 
    if( frame->i_reference_count == 0 )//已经没有线程引用这个帧当做参考帧了
        x264_frame_push( h->frames.unused[frame->b_fdec], frame );//把空闲的frame 回收
}

//每个线程都有自己的x264_t 上下文,每调用依次这个函数,frame被引用的次数减1,直到为0,这个时候frame就可以被释放了,释放不是释放内存,而是吧frame重新回收到上下文的unused队列,供下次使用。

因此调用x264_frame_push(unused,frame);

void x264_frame_push( x264_frame_t **list, x264_frame_t *frame )//在unused数组中找到一个空闲的frame位置,把要释放的frame填进去就好。
{
    int i = 0;
    while( list[i] ) i++;
    list[i] = frame;
}

那么这里释放了,后面如何使用的呢?

x264_frame_t *x264_frame_pop( x264_frame_t **list )//弹出最近的一个参考帧
{
    x264_frame_t *frame;
    int i = 0;
    assert( list[0] );
    while( list[i+1] ) i++;
    frame = list[i];
    list[i] = NULL;
    return frame;
}

x264_frame_t *x264_frame_pop_unused( x264_t *h, int b_fdec )
{
    x264_frame_t *frame;
    if( h->frames.unused[b_fdec][0] )
        frame = x264_frame_pop( h->frames.unused[b_fdec] );//从unused中pop一个地址不为空的frame出来。前面释放的时候,frame地址呗这里回收了,现在pop再次利用
    else
        frame = frame_new( h, b_fdec );
    if( !frame )
        return NULL;
    frame->b_last_minigop_bframe = 0;
    frame->i_reference_count = 1;
    frame->b_intra_calculated = 0;
    frame->b_scenecut = 1;
    frame->b_keyframe = 0;
    frame->b_corrupt = 0;
    frame->i_slice_count = h->param.b_sliced_threads ? h->param.i_threads : 1;

    memset( frame->weight, 0, sizeof(frame->weight) );
    memset( frame->f_weighted_cost_delta, 0, sizeof(frame->f_weighted_cost_delta) );

    return frame;
}

 

 

 

<think>我们正在讨论Tricore架构下以太网DMA的多帧接收配置。用户的问题基于之前的对话,特别是关于以太网描述符的实现细节。用户现在想了解如何配置Tricore以太网DMA来实现多帧接收。根据之前的回答,我们知道Tricore的以太网DMA使用描述符环(DescriptorRing)来管理数据传输。每个描述符包含状态字、数据缓冲区指针、下一个描述符指针等字段。多帧接收意味着DMA能够连续接收多个以太网帧而不需要CPU频繁干预。实现多帧接收的关键点包括:1.初始化一个描述符环形缓冲区(RingBuffer),每个描述符指向一个独立的数据缓冲区。2.设置DMA的接收描述符基地址寄存器(RX_DESC_BASE)指向这个环形缓冲区。3.通过描述符的状态位(特别是OWN位)来控制DMA和CPU之间的所有权切换。4.在中断服务程序(ISR)中处理接收到的帧,并回收描述符(将OWN位重新置1,交还给DMA)。用户提供的引用信息:引用[1]:提到了发送描述符中TDES3[31]位(即OWN位)的作用。在发送时,应用程序将OWN位置1,DMA开始传输,传输完成后DMA将OWN位清零。接收方向类似,但方向相反:DMA将数据写入缓冲区后,将OWN位清零,表示CPU可以处理数据。引用[2]:提到了在接收过程中,如果DMA空闲,需要检查状态寄存器中的RBU(ReceiveBufferUnavailable)位。如果RBU被置位,表示DMA没有下一个描述符的控制权(即没有可用的描述符),此时需要清除中断标志并重新获得控制权。因此,配置多帧接收的步骤可以归纳如下:###配置Tricore以太网DMA实现多帧接收的步骤####1.初始化描述符环-创建多个接收描述符(例如8个、16个等),形成环形链表。-每个描述符的数据指针(data_ptr)指向一个足够容纳以太网帧的缓冲区(通常为1524字节或更大)。-初始化每个描述符的状态字,将OWN位置1(表示所有权归DMA,即DMA可以写入数据)。-设置每个描述符的next_desc指向下一个描述符,最后一个描述符指向第一个,形成环。示例代码(基于iLLD库):```c//定义描述符结构(根据具体硬件手册,可能与之前定义略有不同)typedefvolatilestruct{uint32status;//状态字,包含OWN位等uint32data_ptr;//数据缓冲区物理地址uint32next_desc;//下一个描述符的物理地址uint32reserved;}EthRxDescriptor;//创建描述符数组和数据缓冲区#defineNUM_RX_DESC8EthRxDescriptorrx_desc_ring[NUM_RX_DESC]__attribute__((aligned(16)));//对齐要求uint8rx_buffers[NUM_RX_DESC][1524]__attribute__((aligned(8)));//缓冲区对齐//初始化描述符环voidinit_rx_descriptors(){for(inti=0;i<NUM_RX_DESC;i++){rx_desc_ring[i].status=ETH_DESC_OWN;//OWN=1,其他标志位根据需求设置rx_desc_ring[i].data_ptr=(uint32)rx_buffers[i];//缓冲区的物理地址rx_desc_ring[i].next_desc=(uint32)&rx_desc_ring[(i+1)%NUM_RX_DESC];//下一个描述符的物理地址}//设置DMA接收描述符基地址寄存器ETH->RX_DESC_BASE=(uint32)rx_desc_ring;//注意:需要物理地址,如果使用MMU则需转换}```####2.配置DMA寄存器-设置接收描述符基地址寄存器(RX_DESC_BASE)为描述符环的起始物理地址。-配置DMA操作模式寄存器(ETH_DMA_OPMODE):-使能接收状态轮询(RSP)或中断模式(推荐中断)。-设置接收阈值(StoreandForward模式或Cut-through模式,通常用StoreandForward)。-使能DMA接收通道。####3.中断配置-使能接收中断(例如:正常接收完成中断、接收缓冲区不可用中断等)。-在中断服务程序(ISR)中处理接收到的帧。####4.中断服务程序(ISR)处理多帧接收在接收中断中,需要遍历描述符环,处理所有OWN位为0(即DMA已经完成数据写入)的描述符。处理完成后,将描述符的OWN位置1,交还给DMA,以便接收下一帧。示例代码:```cvoidETH_Receive_IRQHandler(void){//检查接收中断标志if(ETH->STATUS&RX_INT){//遍历描述符环,处理所有接收到的帧EthRxDescriptor*curr_desc=&rx_desc_ring[0];intprocessed=0;while(processed<NUM_RX_DESC){//检查当前描述符的OWN位:0表示CPU可以处理if((curr_desc->status&ETH_DESC_OWN)==0){//处理接收到的帧(位于curr_desc->data_ptr指向的缓冲区)uint32frame_length=(curr_desc->status>>16)&0x3FFF;//根据手册获取帧长度process_frame((uint8*)curr_desc->data_ptr,frame_length);//处理完成后,将OWN位置1,交还给DMAcurr_desc->status=ETH_DESC_OWN;//注意:可能需要设置其他状态位,具体参考手册//移动至下一个描述符curr_desc=(EthRxDescriptor*)curr_desc->next_desc;processed++;}else{//遇到OWN=1的描述符,表示后续描述符尚未就绪,退出循环break;}}//清除中断标志ETH->STATUS=RX_INT;}}```####5.处理接收缓冲区不可用(RBU)情况在接收过程中,如果DMA用完了所有描述符(即所有描述符的OWN位都为0,没有可用缓冲区),则DMA会停止接收并设置RBU标志。此时,在中断服务程序中需要检测并处理这种情况,即回收一些描述符(将OWN位置1)后,清除RBU标志。根据引用[2]中的描述,当检测到RBU中断时,需要调用类似`IfxGeth_dma_clearInterruptFlag()`的函数清除中断标志,并重新启动DMA。在中断服务程序中可以这样处理:```cif(ETH->STATUS&RBU_INT){//检查是否有描述符可以回收(例如,应用程序已经处理了一些帧,但尚未将OWN位置1)//通常,在正常接收中断处理中已经回收了描述符,所以这里只需清除标志//但是,如果确实没有可用描述符,则需要紧急分配新的缓冲区并更新描述符//清除RBU中断标志(通过写1清除)ETH->STATUS=RBU_INT;//注意:清除RBU中断后,DMA会重新检查描述符环,如果有描述符的OWN=1,则会继续接收。}```###注意事项1.**内存屏障**:在更新描述符状态或读取描述符状态前,需要使用`__dsync()`指令确保缓存一致性[^1]。2.**描述符对齐**:描述符环和数据缓冲区需要按照手册要求对齐(通常描述符16字节对齐,缓冲区8字节对齐)。3.**物理地址**:DMA使用物理地址,如果使用MMU,需要将虚拟地址转换为物理地址。4.**中断处理效率**:为了高效处理多帧,中断服务程序应尽可能快地处理完所有就绪的描述符,避免长时间关中断。###引用说明以上实现参考了英飞凌官方手册和iLLD库:-描述符的OWN位操作[^1]-RBU中断的处理[^2]###相关问题基于这个配置过程,我们可以生成以下相关问题:1.在Tricore以太网DMA多帧接收中,如何避免接收缓冲区溢出?2.如何优化Tricore以太网接收中断处理程序的性能?3.Tricore以太网DMA描述符环的大小如何选择?有什么影响因素?4.在Tricore上,如何处理以太网接收帧的分片(chaineddescriptors)?</think>### Tricore 以太网 DMA 多帧接收配置实现 在 Tricore 架构中,配置 DMA 实现多帧接收的核心是通过**描述符环(Descriptor Ring)** 结构管理多个接收缓冲区。以下是详细实现方法: #### 1. **描述符环初始化** 创建环形描述符队列,每个描述符关联独立的数据缓冲区: ```c #define RX_DESC_COUNT 8 // 描述符数量(决定可缓冲的帧数) // 描述符结构(参考TC3xx手册Section 38.5.2) typedef struct { volatile uint32_t status; // 状态寄存器 uint32_t data_buffer_ptr; // 数据缓冲区物理地址 uint32_t next_desc_ptr; // 下一个描述符地址 uint32_t reserved; } RxDescriptor; // 初始化描述符环 RxDescriptor rx_ring[RX_DESC_COUNT] __attribute__((aligned(16))); // 16字节对齐 uint8_t rx_buffers[RX_DESC_COUNT][ETH_MAX_FRAME_SIZE]; // 接收缓冲区 void init_rx_descriptor_ring() { for (int i = 0; i < RX_DESC_COUNT; i++) { rx_ring[i].status = DESC_OWN; // 初始所有权归DMA[^1] rx_ring[i].data_buffer_ptr = (uint32_t)&rx_buffers[i]; rx_ring[i].next_desc_ptr = (uint32_t)&rx_ring[(i+1) % RX_DESC_COUNT]; } // 注册到ETH模块 ETH->RX_DESC_BASE = (uint32_t)rx_ring; // 设置DMA基地址 ETH->DMA_MODE |= RX_START; // 启动DMA接收 } ``` #### 2. **多帧接收工作流程** 1. **DMA自动填充**: - DMA 按顺序使用描述符(`OWN=1`) - 数据写入完成后,DMA 自动清除 `OWN` 位[^1] ```c // DMA完成后的描述符状态变化: status = (OWN << 31) | (FRAME_LEN << 16) | STATUS_FLAGS; ``` 2. **中断处理**: ```c void ETH_IRQHandler() { // 检查接收中断标志 if (ETH->STATUS & RX_INT) { RxDescriptor *curr = &rx_ring[rx_index]; // 处理所有已填充的描述符 while (!(curr->status & DESC_OWN)) { uint16_t frame_len = (curr->status >> 16) & 0x3FFF; process_frame(curr->data_buffer_ptr, frame_len); // 关键:回收描述符 __dsync(); // 内存屏障确保缓存一致性[^1] curr->status = DESC_OWN; // 返还所有权给DMA curr = (RxDescriptor*)curr->next_desc_ptr; rx_index = (rx_index + 1) % RX_DESC_COUNT; } // 处理RBU(接收缓冲区不可用)状态 if (ETH->DMA_STATUS & RBU_FLAG) { ETH->DMA_STATUS = RBU_FLAG; // 写1清除标志[^2] } } } ``` #### 3. **关键配置寄存器** | 寄存器 | 功能说明 | 配置值示例 | |-----------------|-----------------------------------|------------------| | `ETH_DMA_MODE` | DMA工作模式控制 | `RX_EN | STORE_FORWARD` | | `ETH_DMA_RX_CTRL`| 接收控制寄存器 | `OSF_EN | RTC_64` | | `ETH_INT_ENABLE`| 中断使能寄存器 | `RXC_INT_EN` | #### 4. **优化多帧接收性能** 1. **双缓冲策略**: - 使用两个描述符环交替工作 - CPU 处理一个环时,DMA 填充另一个环 2. **动态描述符扩展**: ```c // 当空闲描述符不足时动态添加 if (free_desc_count < MIN_FREE_DESC) { expand_descriptor_ring(); } ``` 3. **零拷贝优化**: - 直接使用 DMA 填充的缓冲区处理数据 - 避免 `memcpy` 操作 > **注意**:当出现发送挂起时,检查 `TDES3[31]`(OWN位)是否被 DMA 置0,需应用程序重置为1才能继续传输[^1]。 ### 常见问题解决 **接收停滞问题**: 1. 检查 `RBU`(Receive Buffer Unavailable) 标志: ```c if (ETH->DMA_CH_STATUS & RBU_FLAG) { ETH->DMA_CH_STATUS = RBU_FLAG; // 清除标志[^2] } ``` 2. 确认描述符回收机制是否正确重置 `OWN` 位 **多帧丢失问题**: - 增加描述符数量(`RX_DESC_COUNT`) - 优化中断处理延迟 - 启用 DMA 接收阈值控制(`ETH_DMA_RX_CTRL.RTC`) > 完整实现参考:*TC3xx User Manual* Section 38.6 "Ethernet DMA Operation" 和 iLLD 库中的 `IfxEth_Geth.c` 模块[^1][^2]。 --- ### 相关问题 1. Tricore 以太网 DMA 接收中断延迟如何优化? 2. 如何实现以太网接收缓冲区的动态内存管理? 3. Tricore 以太网 DMA 多帧接收中的错误处理机制? 4. ETH 模块的接收描述符环溢出如何检测和预防? [^1]: TC3xx User Manual v2.0, ETH DMA Operation 章节 [^2]: iLLD 库文档 IfxGeth_Dma.c 实现参考
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值