bs_t结构及其相关函数的定义

本文详细介绍了H.264标准中比特流的处理方法,包括比特流结构定义、初始化、读写操作及特殊编码方式等。特别关注了UE、SE和TE编码的实现细节。

此部分内容对应H.264标准9.1节及[2]中6.4.13小节

bs_t结构描述了比特流的概念,从比特流中进行顺序读写操作(类似于前向迭代器)

typedef struct bs_s

{

    uint8_t *p_start;                // 缓冲区首地址

    uint8_t *p;                         // 缓冲区当前的读写指针

    uint8_t *p_end;                 // 缓冲区尾地址

    int     i_left;                // p所指字节当前还有多少比特可读写

    int     i_bits_encoded;   /* RD only */

} bs_t;

void bs_init( bs_t *s, void *p_data, int i_data )

使用p_data指向的i_data个字节作为缓冲区初始化比特流s

int bs_pos( bs_t *s )

返回比特流s当前读写的位置(以比特为单位),类似于ftell的功能,

int bs_eof( bs_t *s )

当前是否已经位于比特流s的尾部,如果是,返回1;否则返回0

static uint32_t bs_read( bs_t *s, int i_count )

从比特流s中读出i_count个比特的值并返回

static uint32_t bs_read1( bs_t *s )

从比特流s中读出1个比特并返回之

static uint32_t bs_show( bs_t *s, int i_count )

从比特流中读出i_count个比特的值并返回,但是不移动读写指针

此处实现似乎存在潜在的错误,即如果p<p_end但是p向后移动i_count个比特后越过了p_end,程序依然能够返回,不报错!!

static void bs_skip( bs_t *s, int i_count )

将比特读写位置向后移动i_count个比特

static int bs_read_ue( bs_t *s )

static int bs_read_se( bs_t *s )

static int bs_read_te( bs_t *s, int x )

static void bs_write( bs_t *s, int i_count, uint32_t i_bits )

向指定比特流s写入i_count个比特的值i_bits;如果当前位置距离比特流尾部相差少于四个字节??,则不做任何工作,直接返回;如果要写入的值所需比特数大于i_count,则截掉高位后写入。

static void bs_write1( bs_t *s, uint32_t i_bit )

向指定比特流s写入一个比特值i_bit;如果当前已处于比特流尾部,则不做任何工作,直接返回。

static void bs_align_0( bs_t *s )

向后调整比特流的读写位置,使其处于字节对齐位置;中间这些跳过的比特位全部置0

static void bs_align_1( bs_t *s )

向后调整比特流的读写位置,使其处于字节对齐位置;中间这些跳过的比特位全部置1

static void bs_align( bs_t *s )

与bs_align_0函数完全相同

void bs_write_ue( bs_t *s, unsigned int val )

以ue映射方式向比特流中写入无符号值,具体过程为:

(1)    首先向比特流中写入连续M个比特的0,其中M=floor(log2(val+1));

(2)    向比特流中写入1个比特的1

(3)    向比特流中写入M个比特,这M个比特的值应等于val+1-2M

static void bs_write_se( bs_t *s, int val )

以H.264标准Table 9.3的映射方式向比特流中写入。具体为:

当val<=0时,则以ue方式向比特流中写入-2*val

当val>0时,则以ue方式向比特流中写入2*val-1

static void bs_write_te( bs_t *s, int x, int val )

当x=1时,向比特流中写入val的最低比特的反码;当x>1时,按照ue映射方式写入;其他情况下不执行任何操作。

static void bs_rbsp_trailing( bs_t *s )

无论比特流当前位置是否字节对齐,都向其中写入一个比特1及若干个(0~7个)比特0,使其字节对齐

static int bs_size_ue( unsigned int val )

返回以ue方式对val进行Exp-goloma编码所需要的比特数。

static int bs_size_se( int val )

返回以se方式对val进行Exp-goloma编码所需要的比特数。

static int bs_size_te( int x, int val )

返回以te方式对val进行Exp-goloma编码所需要的比特数。当x=1时,返回1;当x>1时,按照ue映射方式计算并返回;其他情况下返回0。


/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "can.h" #include "tim.h" #include "gpio.h" #include "stm32f1xx_hal_iwdg.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ typedef enum { RECOVERY_IDLE, // 空闲状态,无恢复进行 RECOVERY_STOPPING,// 正在停止CAN RECOVERY_DELAYING,// 停止后的延时 RECOVERY_INITIALIZING,//重新初始化CAN外设 RECOVERY_CONFIGURING,//重新配置CAN过滤器 RECOVERY_STARTING, //重新启动CAN RECOVERY_ACTIVATING_NOTIFICATIONS,//重新激活CAN中断 RECOVERY_COMPLETE //恢复完成,清理状态 } RecoveryState_t; RecoveryState_t recoveryState = RECOVERY_IDLE;//恢复状态机当前状态 uint32_t recoveryStartTime = 0; //用于恢复过程中的延时计时 volatile uint8_t busOffFlag = 0; //总线关闭标志,在错误回调中置位 #define NUM_TX_MSGS 2 // 发送报文的数量 typedef struct { uint32_t id; // CAN ID uint8_t data[8]; // 数据 uint8_t dlc; // 数据长度 uint32_t interval; // 发送间隔(ms) uint32_t lastSendTime; // 上次发送的时间戳 } CanTxMsgConfig_t; CanTxMsgConfig_t txMsgConfigs[NUM_TX_MSGS] = { {0x123, {0}, 8, 20, 0}, // 0x123, 8字节数据, 20ms间隔 {0x124, {0}, 8, 20, 0} // ID 0x124, 8????, 20ms?? }; uint32_t TxMailbox; // 发送邮箱号,由HAL_CAN_AddTxMessage函数填充 //发送相关状态和统计变量 volatile uint32_t txBusyCount = 0; // 邮箱繁忙计数 volatile uint8_t consecutiveErrors = 0; //// 连续发送错误次数 #define MAX_CONSECUTIVE_ERRORS 10 // 最大连续错误次数,触发恢复 volatile uint8_t canSendFlag = 0; //定时器中断置位,主循环检查此标志决定是否处理发送 volatile uint8_t txInProgress = 0; // 发送正在进行标志,用于超时判断 volatile uint32_t txSuccessCount = 0; // 发送成功计数器 volatile uint32_t txErrorCount = 0; //发送失败计数器 volatile uint32_t rxErrorCount = 0; //接收错误计数器 volatile uint32_t txStartTime = 0; //单次发送开始的超时计时起点 // CAN错误统计 volatile uint32_t warningErrorCount = 0; //警告错误计数 volatile uint32_t passiveErrorCount = 0; //被动错误计数 volatile uint32_t busOffCount = 0; //总线关闭事件计数 volatile uint32_t rxOverflowCount = 0; //接收队列溢出计数 #define TX_TIMEOUT_MS 50 // 发送超时时间 #define MIN_TX_INTERVAL_MS 10 // 最小发送间隔(由定时器控制) //接收相关 CAN_RxHeaderTypeDef RxHeader; //接收报文头 uint8_t RxData[8]; //接收数据 volatile uint32_t rxCount = 0; volatile uint32_t rxtest = 0; //用于测试 IWDG_HandleTypeDef hiwdg; //独立看门狗句柄 /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* USER CODE BEGIN PFP */ void HandleCanTransmission();//// 主循环中处理CAN发送的核心函数 void PrepareCanMessage(uint8_t msgIndex);//// 准备特定索引的报文数据 void ProcessCanError(uint32_t error);//(函数已实现,但声明似乎未被使用) void CAN_Recovery_Procedure(void);//总线恢复过程状态机 void ProcessReceivedData(); //处理解析后的接收数据 void ProcessRxQueueInMainLoop(void); //在主循环中处理接收队列中的报文 static void MX_IWDG_Init(void); //初始化看门狗 // 接收队列定义 #define RX_QUEUE_SIZE 20 // typedef struct { CAN_RxHeaderTypeDef header; uint8_t data[8]; } CanRxMsg_t; CanRxMsg_t rxQueue[RX_QUEUE_SIZE]; //// 接收队列缓冲区 volatile uint16_t rxQueueHead = 0; //// 队列头指针(生产者索引,由中断修改) volatile uint16_t rxQueueTail = 0; //队列尾指针(消费者索引,由主循环修改) /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_CAN_Init();// CubeMX生成的CAN初始化(参数配置) MX_TIM2_Init();//定时器2初始化,用于产生发送周期中断 MX_IWDG_Init();//初始化独立看门狗 /* USER CODE BEGIN 2 */ //配置CAN过滤器:只接收ID为0x1003扩展帧 CAN_FilterTypeDef sFilterConfig; sFilterConfig.FilterBank = 0; sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; sFilterConfig.FilterIdHigh = (0x1003 << 3)>>16; sFilterConfig.FilterIdLow = (0x1003 << 3)|(CAN_RI0R_IDE<<2)|(CAN_RI0R_RTR<<1); sFilterConfig.FilterMaskIdHigh = (0x1004 << 3)>>16;; sFilterConfig.FilterMaskIdLow = (0x1004 << 3)|(CAN_RI0R_IDE<<2)|(CAN_RI0R_RTR<<1); sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; sFilterConfig.FilterActivation = ENABLE; sFilterConfig.SlaveStartFilterBank = 14; if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK) { Error_Handler(); } // // 启动CAN外设 if (HAL_CAN_Start(&hcan) != HAL_OK) { Error_Handler(); } // 激活CAN中断:发送邮箱空、错误、FIFO0收到报文 if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_TX_MAILBOX_EMPTY | CAN_IT_ERROR | CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK) { Error_Handler(); } //// 启动定时器2中断 HAL_TIM_Base_Start_IT(&htim2); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { HandleCanTransmission(); // // 处理CAN发送 ProcessRxQueueInMainLoop();// 处理接收队列 // 检查总线关闭标志并启动恢复流程 if (busOffFlag && recoveryState == RECOVERY_IDLE) { recoveryState = RECOVERY_STOPPING; } // 执行恢复状态机 if (recoveryState != RECOVERY_IDLE) { CAN_Recovery_Procedure(); // ?????? } // 刷新独立看门狗,防止MCU复位 HAL_IWDG_Refresh(&hiwdg); } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } /* USER CODE BEGIN 4 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { static uint32_t lastSendTime = 0; uint32_t currentTime = HAL_GetTick(); // 检查是否达到最小发送间隔,防止主循环处理不过来时频繁置位 if (currentTime - lastSendTime >= MIN_TX_INTERVAL_MS) { canSendFlag = 1; lastSendTime = currentTime; } } } void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan) { __disable_irq(); txInProgress = 0; // 清除发送中标志 txSuccessCount++; // // 成功计数增加 consecutiveErrors = 0; __enable_irq(); } void HAL_CAN_TxMailbox1CompleteCallback(CAN_HandleTypeDef *hcan) { __disable_irq(); txInProgress = 0; txSuccessCount++; consecutiveErrors = 0; __enable_irq(); } void HAL_CAN_TxMailbox2CompleteCallback(CAN_HandleTypeDef *hcan) { __disable_irq(); txInProgress = 0; txSuccessCount++; consecutiveErrors = 0; __enable_irq(); } void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan) { uint32_t errorcode = HAL_CAN_GetError(hcan);//// 获取错误代码 __disable_irq(); txInProgress = 0;//发生错误,认为当前发送失败 txErrorCount++; if (errorcode & HAL_CAN_ERROR_EWG) { warningErrorCount++; } if (errorcode & HAL_CAN_ERROR_EPV) { passiveErrorCount++; } if (errorcode & HAL_CAN_ERROR_BOF) { busOffCount++; busOffFlag = 1;// 最重要的标志,触发总线恢复 } __enable_irq(); } void PrepareCanMessage(uint8_t msgIndex) { static uint8_t counter = 0; // ????????????? switch(msgIndex) { case 0: // ID 0x123 for (int i = 0; i < 8; i++) { txMsgConfigs[0].data[i] = counter + i; } break; case 1: // ID 0x124 for (int i = 0; i < 8; i++) { txMsgConfigs[1].data[i] = counter + 0x10 + i; // ??????? } break; // ??????????... } counter++; } void HandleCanTransmission(void) { uint32_t currentTime = HAL_GetTick(); uint8_t in_progress; __disable_irq(); in_progress = txInProgress;//// 获取发送状态 __enable_irq(); // 1. 检查超时:如果正在发送且超时,则判定为发送失败 if (in_progress && (currentTime - txStartTime > TX_TIMEOUT_MS)) { __disable_irq(); txInProgress = 0; txErrorCount++; consecutiveErrors++; uint8_t errs = consecutiveErrors; __enable_irq(); if (errs > MAX_CONSECUTIVE_ERRORS) { CAN_Recovery_Procedure(); __disable_irq(); consecutiveErrors = 0; __enable_irq(); } return; } // 2. 如果没有正在进行的发送,则检查各个报文是否到发送时间 if (!in_progress) { for (int i = 0; i < NUM_TX_MSGS; i++) { if (currentTime - txMsgConfigs[i].lastSendTime >= txMsgConfigs[i].interval) { // 检查是否有空闲邮箱 PrepareCanMessage(i); if (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) > 0) { // ??TxHeader CAN_TxHeaderTypeDef TxHeader; TxHeader.StdId = txMsgConfigs[i].id; TxHeader.ExtId = 0x00; TxHeader.RTR = CAN_RTR_DATA; TxHeader.IDE = CAN_ID_STD; TxHeader.DLC = txMsgConfigs[i].dlc; TxHeader.TransmitGlobalTime = DISABLE; __disable_irq(); txInProgress = 1;//// 设置发送标志 __enable_irq(); txStartTime = currentTime; // 调用HAL库函数发送 HAL_StatusTypeDef status = HAL_CAN_AddTxMessage( &hcan, &TxHeader, txMsgConfigs[i].data, &TxMailbox ); if (status != HAL_OK) { __disable_irq(); txInProgress = 0; if (status == HAL_BUSY) { txBusyCount++; } else { txErrorCount++; consecutiveErrors++; } __enable_irq(); } else { // ????,???????? txMsgConfigs[i].lastSendTime = currentTime;//// 更新本报文发送时间 } } } } } } void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CanRxMsg_t *nextMsg; uint16_t next_head; __disable_irq(); // 计算下一个队列头位置,并检查队列是否已满 next_head = (rxQueueHead + 1) % RX_QUEUE_SIZE; if (next_head == rxQueueTail) { rxErrorCount++; rxOverflowCount++; // ?????? __enable_irq(); // 队列已满,不得不读取并丢弃该报文,否则FIFO会阻塞 HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData); return; } __enable_irq(); // 队列未满,获取报文并存入队列 nextMsg = &rxQueue[rxQueueHead]; if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &(nextMsg->header), nextMsg->data) == HAL_OK) { __disable_irq(); rxQueueHead = next_head; // 移动头指针,完成入队 __enable_irq(); } } // ???????????? void ProcessRxQueueInMainLoop(void) { while (rxQueueTail != rxQueueHead) {//// 队列不为空 ProcessReceivedData(&rxQueue[rxQueueTail].header, rxQueue[rxQueueTail].data); rxQueueTail = (rxQueueTail + 1) % RX_QUEUE_SIZE;// 移动尾指针,出队 } } void ProcessReceivedData(CAN_RxHeaderTypeDef *header, uint8_t *data) { // // 检查是否为扩展帧且ID是0x1003 if (header->IDE == CAN_ID_EXT && header->ExtId == 0x1003) { if (data[0] == 1) { rxtest++; } // 根据数据字节0的命令类型处理 switch(data[0]) { case 0x01: // ????1 break; case 0x02: // ????2 break; // ????... default: // ???? break; } // ????????????????? // DEBUG_PRINTF("Received ID 0x1003 message, data[0]: %d\n", data[0]); } // ??????,?????????ID?? // ??????,???????? else { // ????????(????????) rxErrorCount++; } } void CAN_Recovery_Procedure(void) { uint32_t currentTime = HAL_GetTick(); switch (recoveryState) { case RECOVERY_IDLE: recoveryState = RECOVERY_STOPPING; case RECOVERY_DELAYING: if (currentTime - recoveryStartTime >= 100) { recoveryState = RECOVERY_INITIALIZING; } break; case RECOVERY_INITIALIZING: MX_CAN_Init(); // ?????CAN recoveryState = RECOVERY_CONFIGURING; break; case RECOVERY_CONFIGURING: { CAN_FilterTypeDef sFilterConfig = {0}; sFilterConfig.FilterBank = 0; sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; sFilterConfig.FilterIdHigh = 0x0000; sFilterConfig.FilterIdLow = 0x0000; sFilterConfig.FilterMaskIdHigh = 0x0000; sFilterConfig.FilterMaskIdLow = 0x0000; sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; sFilterConfig.FilterActivation = ENABLE; sFilterConfig.SlaveStartFilterBank = 14; if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) == HAL_OK) { recoveryState = RECOVERY_STARTING; } break; } case RECOVERY_STARTING: if (HAL_CAN_Start(&hcan) == HAL_OK) { recoveryState = RECOVERY_ACTIVATING_NOTIFICATIONS; } break; case RECOVERY_ACTIVATING_NOTIFICATIONS: if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_TX_MAILBOX_EMPTY | CAN_IT_ERROR | CAN_IT_RX_FIFO0_MSG_PENDING) == HAL_OK) { recoveryState = RECOVERY_COMPLETE; } break; case RECOVERY_COMPLETE: // 清理状态,重置计数器,恢复完成 __disable_irq(); txInProgress = 0; consecutiveErrors = 0; txErrorCount = 0; txSuccessCount = 0; busOffFlag = 0; __enable_irq(); recoveryState = RECOVERY_IDLE; break; case RECOVERY_STOPPING: // ??????????? for (int i = 0; i < 3; i++) { HAL_CAN_AbortTxRequest(&hcan, (1 << i)); } // ??CAN??? if (HAL_CAN_Stop(&hcan) == HAL_OK) { recoveryState = RECOVERY_DELAYING; recoveryStartTime = currentTime; } else { // ??????,??????? recoveryState = RECOVERY_IDLE; // ????,???? } break; } } static void MX_IWDG_Init(void) { hiwdg.Instance = IWDG; // ???????????????? hiwdg.Init.Prescaler = IWDG_PRESCALER_32; // 32?? hiwdg.Init.Reload = 0x0FFF; // ???4095 // ???? = (32 * 4095) / 40000 ؠ3.276? if (HAL_IWDG_Init(&hiwdg) != HAL_OK) { Error_Handler(); } } /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { __disable_irq(); while (1) { // ??????????,?????? HAL_IWDG_Refresh(&hiwdg); HAL_Delay(100); // ?????? } } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ 请详细分析上述代码,逐行详细分析
09-16
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值