ffmpeg TS DTS PTS AV_TIME_BASE

 

视频的显示和存放原理

对于一个电影,帧是这样来显示的:I B B P。现在我们需要在显示B帧之前知道P帧中的信息。因此,帧可能会按照这样的方式来存储:IPBB。这就是为什么我们会有一个解码时间戳和一个显示时间戳的原因。解码时间戳告诉我们什么时候需要解码,显示时间戳告诉我们什么时候需要显示。所以,在这种情况下,我们的流可以是这样的:

PTS: 1 4 2 3
DTS: 1 2 3 4
Stream: I P B B

通常PTS和DTS只有在流中有B帧的时候会不同。

DTS和PTS

音频和视频流都有一些关于以多快速度和什么时间来播放它们的信息在里面。音频流有采样,视频流有每秒的帧率。然而,如果我们只是简单的通过数帧和乘以帧率的方式来同步视频,那么就很有可能会失去同步。于是作为一种补充,在流中的包有种叫做DTS(解码时间戳)和PTS(显示时间戳)的机制。为了这两个参数,你需要了解电影存放的方式。像MPEG等格式,使用被叫做B帧(B表示双向bidrectional)的方式。另外两种帧被叫做I帧和P帧(I表示关键帧,P表示预测帧)。I帧包含了某个特定的完整图像。P帧依赖于前面的I帧和P帧并且使用比较或者差分的方式来编码。B帧与P帧有点类似,但是它是依赖于前面和后面的帧的信息的。这也就解释了为什么我们可能在调用avcodec_decode_video以后会得不到一帧图像。

ffmpeg中的时间单位

AV_TIME_BASE

ffmpeg中的内部计时单位(时间基),ffmepg中的所有时间都是于它为一个单位,比如AVStream中的duration即以为着这个流的长度为duration个AV_TIME_BASE。AV_TIME_BASE定义为:

#define         AV_TIME_BASE   1000000

 

AV_TIME_BASE_Q

ffmpeg内部时间基的分数表示,实际上它是AV_TIME_BASE的倒数。从它的定义能很清楚的看到这点:

#define         AV_TIME_BASE_Q   (AVRational){1, AV_TIME_BASE}

 

AVRatioal的定义如下:

typedef struct AVRational{
int num; //numerator
int den; //denominator
} AVRational;

ffmpeg提供了一个把AVRatioal结构转换成double的函数:

复制代码

static inline double av_q2d(AVRational a){
/**
* Convert rational to double.
* @param a rational to convert
**/
    return a.num / (double) a.den;
}

复制代码

现在可以根据pts来计算一桢在整个视频中的时间位置:

timestamp(秒) = pts * av_q2d(st->time_base)
 

计算视频长度的方法:

time(秒) = st->duration * av_q2d(st->time_base)
 

这里的st是一个AVStream对象指针。

时间基转换公式

  • timestamp(ffmpeg内部时间戳) = AV_TIME_BASE * time(秒)
  • time(秒) = AV_TIME_BASE_Q * timestamp(ffmpeg内部时间戳)

所以当需要把视频跳转到N秒的时候可以使用下面的方法:

int64_t timestamp = N * AV_TIME_BASE; 
2
av_seek_frame(fmtctx, index_of_video, timestamp, AVSEEK_FLAG_BACKWARD);

ffmpeg同样为我们提供了不同时间基之间的转换函数:

int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq)

这个函数的作用是计算a * bq / cq,来把时间戳从一个时基调整到另外一个时基。在进行时基转换的时候,我们应该首选这个函数,因为它可以避免溢出的情况发生。

 

-------------------------------------------------------------------------------------------------------------------------

 

介绍: 

MPEG的系统层编码为不同的应用场景设计了两种格式: 

TS(Transport Stream) 和PS(Program Stream),

它们两者之间不具有层级关系,

在逻辑上,它们两者都是由PES(Packetized Elementary Stream)包组成的,

所以可以很方便地实现相互转换.

 

TS(Transport Stream): 

  是将具有一个或多个独立时间基的一个或多个节目(包括音频和视频)组成一个流,

  组成同一个节目的基本流(如一个视频流,多个音频流)的PES包有一个共用的时间基。

  TS的包长标准为188bytes.

 

从上面的定义可以分成三层来看TS/PS。

ES层   : 由单独的音频(如mp3),视频流(如h.264)组成基本的ES(Elementary Stream)。

PES层  : 将基本的ES按一定的规则(如H.264以AU)进行封装,并打上时间戳,组成PES。

TS/PS层: 将PES包进行切分后再封装成188bytes大小的TS包,

         同时还将一些节目信息也封装成TS包(称为section), 两者共同组成TS层。

 

从上面的总结,TS/PS总体上来说,是一种封装格式,用来承载数据。

所以FFmpeg

将TS/PS的解析文件定义在libavformat/mpegts.c文件中

将音频,视频的解码定义在libavcodec/mpeg12.c文件中

 

下面来看FFmpeg是如何进行TS的demuxer的。

1. MPEG2-TS的demuxer函数

  1. AVInputFormat ff_mpegts_demuxer = { 
  2.     "mpegts", 
  3.     NULL_IF_CONFIG_SMALL("MPEG-2 transport stream format"),
  4.     sizeof(MpegTSContext),
  5.     mpegts_probe,
  6.     mpegts_read_header,
  7.     mpegts_read_packet, 
  8.     mpegts_read_close, 
  9.     read_seek,
  10.     mpegts_get_pcr,
  11.     .flags = AVFMT_SHOW_IDS|AVFMT_TS_DISCONT, 
  12. #ifdef USE_SYNCPOINT_SEARCH 
  13.     .read_seek2 = read_seek2, 
  14. #endif 
  15. };

 

2. 解析流中的TS格式

 

  1. /*
  2.  * 出现3种格式,主要原因是:
  3.  * TS标准是 188Bytes;
  4.  * 日本标准是192Bytes的DVH-S格式;
  5.  * 第三种的 204Bytes则是在188Bytes的基础上,加上16Bytes的FEC(前向纠错).
  6.  */
  7. #define TS_PACKET_SIZE 188
  8. #define TS_DVHS_PACKET_SIZE 192
  9. #define TS_FEC_PACKET_SIZE 204
  10.  
  11. #define TS_MAX_PACKET_SIZE 204
  12.  
  13. //< maximum score, half of that is used for file-extension-based detection
  14. #define AVPROBE_SCORE_MAX 100

 

 

  1. /*
  2.  * 函数功能:
  3.  * 分析流中是三种TS格式的哪一种
  4.  */
  5. static int mpegts_probe(AVProbeData *p)
  6. {
  7. #define CHECK_COUNT 10
  8.  
  9.   const int size= p->buf_size;
  10.   int score, fec_score, dvhs_score;
  11.   int check_count= size / TS_FEC_PACKET_SIZE;
  12.  
  13.   if (check_count < CHECK_COUNT)
  14.       return -1;
  15.  
  16.   score     = analyze(p->buf, TS_PACKET_SIZE *check_count, TS_PACKET_SIZE , NULL) 
  17.               * CHECK_COUNT / check_count;
  18.   dvhs_score= analyze(p->buf, TS_DVHS_PACKET_SIZE*check_count, TS_DVHS_PACKET_SIZE, NULL)
  19.               * CHECK_COUNT / check_count;
  20.   fec_score = analyze(p->buf, TS_FEC_PACKET_SIZE *check_count, TS_FEC_PACKET_SIZE , NULL)
  21.               * CHECK_COUNT / check_count;
  22.  
  23.   /* 
  24.    * we need a clear definition for the returned score ,
  25.    * otherwise things will become messy sooner or later
  26.    */
  27.   if (score > fec_score && score > dvhs_score && score > 6) 
  28.     return AVPROBE_SCORE_MAX + score - CHECK_COUNT;
  29.   else if(dvhs_score > score && dvhs_score > fec_score && dvhs_score > 6) 
  30.     return AVPROBE_SCORE_MAX + dvhs_score - CHECK_COUNT;
  31.   else if(fec_score > 6) 
  32.     return AVPROBE_SCORE_MAX + fec_score - CHECK_COUNT;
  33.   else 
  34.     return -1;
  35. }

 

  1. /*
  2.  * 函数功能:
  3.  * 在size大小的buf中,寻找满足特定格式,长度为packet_size的
  4.  * packet的个数;
  5.  * 显然,返回的值越大越可能是相应的格式(188/192/204)
  6.  */
  7. static int analyze(const uint8_t *buf, int size, int packet_size, int *index){
  8.   int stat[TS_MAX_PACKET_SIZE];
  9.   int i;
  10.   int x=0;
  11.   int best_score=0;
  12.  
  13.   memset(stat, 0, packet_size*sizeof(int));
  14.     
  15.   for (x=i=0; i < size-3; i++)
  16.   {
  17.     if ((buf[i] == 0x47) && !(buf[i+1] & 0x80) && (buf[i+3] & 0x30))
  18.     {
  19.       stat[x]++;
  20.             
  21.       if (stat[x] > best_score)
  22.       {
  23.         best_score= stat[x];
  24.         if (index) 
  25.           *index= x;
  26.       }
  27.     }
  28.  
  29.     x++;
  30.     if (x == packet_size) 
  31.       x= 0; 
  32.   }
  33.     
  34.   return best_score;
  35. }

 

buf[i] == 0x47  

   其中的sync_byte固定为0x47,即上面的. 

!(buf[i+1] & 0x80)   

   由于transport_error_indicator为1的TS Packet实际有错误,

   表示携带的数据无意义, 这样的Packet显然没什么意义.

buf[i+3] & 0x30 

   对于adaptation_field_control, 如果取值为0x00,则表示为未来保留,现在不用.

 

这就是MPEG TS的侦测过程.

 

 

3. MPEG2-TS头解析

  1. #define NB_PID_MAX 8192
  2. #define MAX_SECTION_SIZE 4096
  3.         
  4. /* pids */
  5. #define PAT_PID 0x0000
  6. #define SDT_PID 0x0011
  7.         
  8. /* table ids */
  9. #define PAT_TID 0x00
  10. #define PMT_TID 0x02
  11. #define SDT_TID 0x42

 

  1. /*
  2.  * 函数功能:
  3.  * 
  4.  */
  5. int mpegts_read_header(AVFormatContext *s, AVFormatParameters *ap)
  6. {
  7.   /*
  8.    * MpegTSContext , 是为了解码不同容器格式所使用的私有数据,
  9.    * 只有在相应的诸如mpegts.c文件才可以使用的.
  10.    * 这样,增加了这个库的模块化.
  11.    */
  12.   MpegTSContext *ts = s->priv_data;
  13.   AVIOContext *pb = s->pb;
  14.   uint8_t buf[8*1024];
  15.   int len;
  16.   int64_t pos;
  17.  
  18.   /* read the first 8*1024 bytes to get packet size */
  19.   pos = avio_tell(pb);                   // 获取buf的当前位置
  20.   len = avio_read(pb, buf, sizeof(buf)); // 从pb->opaque中读取sizeof(buf)个字节到buf
  21.   if (len != sizeof(buf))
  22.     goto fail;
  23.  
  24.   /* 
  25.    * 获得TS包的实际长度
  26.    */
  27.   ts->raw_packet_size = get_packet_size(buf, sizeof(buf));
  28.   if (ts->raw_packet_size <= 0) 
  29.   {
  30.     av_log(s, AV_LOG_WARNING, "Could not detect TS packet size, defaulting to non-FEC/DVHS\n");
  31.     ts->raw_packet_size = TS_PACKET_SIZE;
  32.   }
  33.  
  34.   ts->stream = s; 
  35.   ts->auto_guess = 0;
  36.   
  37.   if (s->iformat == &ff_mpegts_demuxer) 
  38.   {
  39.     /* normal demux */
  40.     /* first do a scaning to get all the services */
  41.     if (avio_seek(pb, pos, SEEK_SET) < 0)
  42.     {
  43.       av_log(s, AV_LOG_ERROR, "Unable to seek back to the start\n");
  44.     }
  45.  
  46.     /*
  47.      * 挂载了两个Section类型的过滤器,
  48.      * 其实在TS的两种负载中,section是PES的元数据,
  49.      * 只有先解析了section,才能进一步解析PES数据,因此先挂上section的过滤器。
  50.      */
  51.     mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1);
  52.     mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1);
  53.  
  54.     /*
  55.      */ 
  56.     handle_packets(ts, s->probesize / ts->raw_packet_size);
  57.  
  58.     /* if could not find service, enable auto_guess */
  59.     ts->auto_guess = 1;
  60.     av_dlog(ts->stream, "tuning done\n");
  61.     s->ctx_flags |= AVFMTCTX_NOHEADER;
  62.   } 
  63.   else 
  64.   {
  65.     ...
  66.   }
  67.  
  68.   avio_seek(pb, pos, SEEK_SET); 
  69.   return 0;
  70.  
  71. fail:
  72.   return -1;
  73. }

 

  1. MpegTSFilter *mpegts_open_section_filter(MpegTSContext* ts, 
  2.                                          unsigned int pid,
  3.                                          SectionCallback* section_cb, 
  4.                                          void* opaque,
  5.                                          int check_crc)
  6.   MpegTSFilter *filter;
  7.   MpegTSSectionFilter *sec; 
  8.  
  9.   av_dlog(ts->stream, "Filter: pid=0x%x\n", pid);
  10.  
  11.   if (pid >= NB_PID_MAX || ts->pids[pid])
  12.     return NULL;
  13.  
  14.   filter = av_mallocz(sizeof(MpegTSFilter));
  15.   if (!filter)
  16.     return NULL;
  17.  
  18.   ts->pids[pid] = filter;
  19.   filter->type = MPEGTS_SECTION;
  20.   filter->pid = pid; 
  21.   filter->last_cc = -1;
  22.   sec = &filter->u.section_filter;
  23.   sec->section_cb = section_cb;
  24.   sec->opaque = opaque;
  25.   sec->section_buf= av_malloc(MAX_SECTION_SIZE);
  26.   sec->check_crc = check_crc;
  27.  
  28.   if (!sec->section_buf) 
  29.   {
  30.     av_free(filter);
  31.     return NULL;
  32.   }
  33.  
  34.   return filter;
  35. }

对于这部分代码,需要分析数据结构的定义:

依次为:

 

    struct MpegTSContext;

               |

               V

    struct MpegTSFilter;

               |

               V

+--------------+---------------+

|                              |

V                              V

MpegTSPESFilter        MpegTSSectionFilter

 

就是struct MpegTSContext;中有NB_PID_MAX(8192)个TS的Filter,

而每个struct MpegTSFilter

  可能是 PES    的Filter

  或者是 Section的Filter。

 

为什么NB_PID_MAX 是 8192,

需要看TS的语法结构(ISO/IEC 138138-1 page 19):

 

  1. Syntax                          No. of bits         Mnemonic
  2. transport_packet(){ 
  3.   sync_byte                        8                 bslbf
  4.   transport_error_indicator        1                 bslbf
  5.   payload_unit_start_indicator     1                 bslbf
  6.   transport_priority               1                 bslbf
  7.   PID                              13                uimsbf
  8.   transport_scrambling_control     2                 bslbf
  9.   adaptation_field_control         2                 bslbf
  10.   continuity_counter               4                 uimsbf
  11.   if (adaptation_field_control=='10' || 
  12.       adaptation_field_control=='11' )
  13.   { 
  14.         adaptation_field() 
  15.   } 
  16.        
  17.   if (adaptation_field_control=='01' || 
  18.       adaptation_field_control=='11' ) 
  19.   { 
  20.     for (i=0;i<N;i++)
  21.     { 
  22.       data_byte                     8                bslbf
  23.     } 
  24.   } 
  25. }

而8192,是2^13=8192(PID)的最大数目,

为什么会有PES和Section的区分,更详细的可以参考ISO/IEC-13818-1.

 

 

挂载上了两种section过滤器,如下:

=========================================================================

PID                |Section Name           |Callback

=========================================================================

SDT_PID(0x0011)    |ServiceDescriptionTable|sdt_cb

                   |                       |

PAT_PID(0x0000)    |ProgramAssociationTable|pat_cb

=========================================================================

设计成回调函数,是为了在后面使用。

 

4. MPEG2-TS的包处理

 

 

  1. int handle_packets(MpegTSContext *ts, int nb_packets)
  2. {
  3.   AVFormatContext *s = ts->stream;
  4.   uint8_t packet[TS_PACKET_SIZE];
  5.   int packet_num, ret;
  6.      
  7.   ts->stop_parse = 0;
  8.   packet_num = 0;
  9.  
  10.   for ( ; ; ) 
  11.   {
  12.     packet_num++;
  13.     
  14.     if (nb_packets != 0 && packet_num >= nb_packets ||
  15.         ts->stop_parse > 1) 
  16.     {
  17.       ret = AVERROR(EAGAIN);
  18.       break;
  19.     }
  20.  
  21.     if (ts->stop_parse > 0)
  22.       break;
  23.         
  24.     ret = read_packet(s, packet, ts->raw_packet_size);
  25.     if (ret != 0)
  26.       return ret;
  27.  
  28.     ret = handle_packet(ts, packet);
  29.     if (ret != 0)
  30.       return ret;
  31.   } 
  32.   
  33.   return 0; 
  34. }

 

它的代码结构很简单:

handle_packets()

    |

    +->read_packet()

    |

    +->handle_packet()

        |

        +->write_section_data()

    

read_packet(),  很简单, 就是去找sync_byte(0x47),

handle_packet(),是真正处理数据的地方.它的代码如下:

  1. /* 
  2.  * 功能: handle one TS packet 
  3.  */
  4. int handle_packet(MpegTSContext *ts, const uint8_t *packet)
  5. {
  6.   AVFormatContext *s = ts->stream;
  7.   MpegTSFilter *tss;
  8.   int len, pid, cc, expected_cc, cc_ok, afc, is_start;
  9.   const uint8_t *p, *p_end;
  10.   int64_t pos;
  11.  
  12.   /* 获取该包的PID */
  13.   pid = AV_RB16(packet + 1) & 0x1fff;
  14.   if (pid && discard_pid(ts, pid))
  15.      return 0;
  16.  
  17.   /* 
  18.    * 是否是PES或者Section的开头
  19.    * 即syntax element: payload_unit_start_indicator 
  20.    */
  21.   is_start = packet[1] & 0x40;
  22.   tss = ts->pids[pid];
  23.  
  24.   /* 
  25.    * ts->auto_guess此时为0,因此不考虑下面的代码
  26.    */
  27.   if (ts->auto_guess && tss == NULL && is_start) 
  28.   {
  29.     add_pes_stream(ts, pid, -1);
  30.     tss = ts->pids[pid];
  31.   }
  32.   if (!tss)
  33.     return 0;
  34.  
  35.   /* 
  36.    * continuity check (currently not used) 
  37.    * 虽然检查,但不利用检查的结果
  38.    */
  39.   cc = (packet[3] & 0xf);
  40.   expected_cc = (packet[3] & 0x10) ? (tss->last_cc + 1) & 0x0f : tss->last_cc;
  41.   cc_ok = (tss->last_cc < 0) || (expected_cc == cc);
  42.   tss->last_cc = cc;
  43.  
  44.   /* 
  45.    * 解析 adaptation_field_control 语法元素
  46.    * =======================================================
  47.    * 00 | Reserved for future use by ISO/IEC
  48.    * 01 | No adaptation_field, payload only
  49.    * 10 | Adaptation_field only, no payload
  50.    * 11 | Adaptation_field follwed by payload
  51.    * =======================================================
  52.    */ 
  53.   afc = (packet[3] >> 4) & 3;
  54.   p = packet + 4;
  55.   if (afc == 0) /* reserved value */
  56.     return 0; 
  57.   if (afc == 2) /* adaptation field only */ 
  58.     return 0;
  59.   if (afc == 3) 
  60.   {
  61.     /* 
  62.      * 跳过 adapation field 
  63.      * p[0]对应的语法元素为: adaptation_field_length
  64.      */
  65.     p += p[0] + 1;
  66.   }
  67.  
  68.   /* 
  69.    * if past the end of packet, ignore 
  70.    * p已近到达TS包中的有效负载的地方
  71.    */
  72.   p_end = packet + TS_PACKET_SIZE;
  73.   if (p >= p_end)
  74.     return 0;
  75.  
  76.   pos = avio_tell(ts->stream->pb);
  77.   ts->pos47= pos % ts->raw_packet_size;
  78.  
  79.   if (tss->type == MPEGTS_SECTION) 
  80.   {
  81.     /*
  82.      * 针对Section, 第一个字节对应的语法元素为:pointer_field(见2.4.4.1),
  83.      * 它表示在当前TS包中,从pointer_field开始到第一个section的第一个字节间的字节数。
  84.      * 当TS包中有至少一个section的起始时,
  85.      *    payload_unit_start_indicator = 1 且 TS负载的第一个字节为pointer_field;
  86.      *    pointer_field = 0x00时,表示section的起始就在这个字节之后;
  87.      * 当TS包中没有section的起始时, 
  88.      *    payload_unit_start_indicator = 0 且 TS负载中没有pointer_field;
  89.      */
  90.     if (is_start) 
  91.     {
  92.       /* pointer field present */
  93.       len = *p++;
  94.       if (p + len > p_end)
  95.         return 0;
  96.  
  97.       if (len && cc_ok) 
  98.       {
  99.         /* 
  100.          * write remaining section bytes 
  101.          * TS包的负载部分由Section A的End部分和Section B的Start组成,
  102.          * 先把Section A的End部分写入
  103.          */
  104.         write_section_data(s, tss, p, len, 0);
  105.  
  106.         /* check whether filter has been closed */
  107.         if (!ts->pids[pid])
  108.           return 0;
  109.       }
  110.       p += len;
  111.  
  112.       if (p < p_end) 
  113.       { 
  114.         /*
  115.          * 再将Section B的Start部分写入
  116.          */
  117.         write_section_data(s, tss, p, p_end - p, 1);
  118.       }
  119.     } 
  120.     else 
  121.     {
  122.       /* TS包负载仅是一个Section的中间部分部分,将其写入*/
  123.       if (cc_ok) 
  124.       {
  125.         write_section_data(s, tss, p, p_end - p, 0);
  126.       }
  127.     }
  128.   } 
  129.   else 
  130.   {
  131.     int ret;
  132.  
  133.     /* 
  134.      * 如果是PES类型,直接调用其Callback,
  135.      * 但显然,只有Section部分解析完成后才可能解析PES
  136.      */
  137.     // Note: The position here points actually behind the current packet.
  138.     if ((ret = tss->u.pes_filter.pes_cb(tss, p, p_end - p, is_start,
  139.         pos - ts->raw_packet_size)) < 0)
  140.       return ret;
  141.   }
  142.  
  143.   return 0;
  144. }

write_section_data()函数:

  反复收集buffer中的数据,指导完成相关Section的重组过程,

  然后调用之前注册的两个section_cb.

 

 

5. 节目指定信息的解析

 

  1. /*
  2.  * PAT(Program Association Table) 节目相关表
  3.  * 提供了节目号与PID值的对应关系
  4.  * 见ISO/IEC 13818-1 2.4.4.3 Table 2-30
  5.  */
  6. void pat_cb(MpegTSFilter *filter, const uint8_t *section, int section_len);
  7.  
  8. /*
  9.  * PMT(Program Map Table) 节目映射表
  10.  * 提供了节目号与组成节目的元素之间的映射关系--或者称为"节目定义"
  11.  * 见ISO/IEC 13818-1 2.4.4.8 Table 2-33
  12.  */
  13. void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len);
  14.  
  15.  
  16. /*
  17.  * SDT(Transport Stream Description Table) TS描述表
  18.  * 用于定义TS描述子的表
  19.  * 见ISO/IEC 13818-1 2.4.4.12 Table 2-36
  20.  */
  21. void sdt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len)

 

6. 解析PES包

  1. /* 
  2.  * 见ISO/IEC 13818-1 2.4.3.6 Table 2-21
  3.  */
  4. int mpegts_push_data(MpegTSFilter* filter,
  5.                      const uint8_t* buf, 
  6.                      int buf_size, 
  7.                      int is_start,
  8.                      int64_t pos);

 

至此,整个TS层的解析基本完成。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值