WEBRTC 接收H264 RTP数据流小结

WEBRTC 接收H264 RTP数据流小结

这篇文章是对webrtc 中,接收H264 RTP包的一个总结,主要分为两个部分:
第一部分,介绍H264打包成RTP包的规范,以及WEBRTC中目前正在使用的几种格式。
第二部分,介绍WEBRTC的数据流,从接收RTP包,到拼装成H264 Frame,最终送入Decoder,获取YUV数据。

第一部分:RTP Payload Format for H.264 Video阅读笔记

参考链接:rfc6184

RTP Payload Format

具体RTP 的协议格式,可以参考RFC 3550

1.RTP Header

    0                   1                   2                   3
   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |V=2|P|X|  CC   |M|     PT      |       sequence number         |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |                           timestamp                           |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |           synchronization source (SSRC) identifier            |
  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
  |            contributing source (CSRC) identifiers             |
  |                             ....                              |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  Figure 1.  RTP header according to RFC 3550

2.Payload Structures

定义了三种不同的Playload结构类型

  1. Single NAL Unit Packet

    在一个RTP Playload中,只包含一个Nal Unit 。

  2. Single NAL Unit Packet

    在一个RTP Playload中,聚合了多个Nal Unit。大致包含以下几种:

    • STAP-A:
    • STAP-B
    • MTAP-16
    • MTAP-24
  1. Fragmentation Unit

    把一个Nal Unit 进行拆分,打包到多个RTP 包中。

    • FU-A
    • FU-B
      Table 1.  Summary of NAL unit types and the corresponding packet
                types

      NAL Unit  Packet    Packet Type Name               Section
      Type      Type
      -------------------------------------------------------------
      0        reserved                                     -
      1-23     NAL unit  Single NAL unit packet             5.6
      24       STAP-A    Single-time aggregation packet     5.7.1
      25       STAP-B    Single-time aggregation packet     5.7.1
      26       MTAP16    Multi-time aggregation packet      5.7.2
      27       MTAP24    Multi-time aggregation packet      5.7.2
      28       FU-A      Fragmentation unit                 5.8
      29       FU-B      Fragmentation unit                 5.8
      30-31    reserved                                     -

2.1 NAL Unit Header

--
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+

  • F:0表示payload 内容没有错误,1表示payload中的内容可能有错误内容或语法错误。
  • NRI:00表示没有参考帧。
  • Type:1-23

2.1.1 Packetization Modes

  • Single Nal unit mode:
  • Non-interleaved mode:
  • Interleaved mode:

--

      Table 3.  Summary of allowed NAL unit types for each packetization
            mode (yes = allowed, no = disallowed, ig = ignore)

  Payload Packet    Single NAL    Non-Interleaved    Interleaved
  Type    Type      Unit Mode           Mode             Mode
  -------------------------------------------------------------
  0      reserved      ig               ig               ig
  1-23   NAL unit     yes              yes               no
  24     STAP-A        no              yes               no
  25     STAP-B        no               no              yes
  26     MTAP16        no               no              yes
  27     MTAP24        no               no              yes
  28     FU-A          no              yes              yes
  29     FU-B          no               no              yes
  30-31  reserved      ig               ig               ig

2.2 Single NAL Unit Packet

在一个rtp'包中,只包含有一个完整的Nal Unit(视频帧)。

例: 如有一个 H.264 的 NALU 是这样的:
[00 00 00 01 67 42 A0 1E 23 56 0E 2F ... ]
这是一个序列参数集 NAL 单元. [00 00 00 01] 是四个字节的开始码, 67 是 NALU 头, 42 开始的数据是 NALU 内容.

封装成 RTP 包将如下:

[ F|NRI| Type ] [ 67 42 A0 1E 23 56 0E 2F ]

a single NAL unit :[ 67 42 A0 1E 23 56 0E 2F ], 即只要去掉 4 个字节的开始码就可以了.

--
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|NRI| Type | |
+-+-+-+-+-+-+-+-+ |
| |
| Bytes 2..n of a single NAL unit |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Figure 2.  RTP payload format for single NAL unit packet

2.3 Aggregation Packets

在一个rtp包中,会有多个Nul Unit(一个rtp包带多个视频帧)。这种情况会在视频帧比较小的时候采用。

  1. Single-time aggregation packet (STAP):
    • STAP-A: without DON

例:

如有一个 H.264 的 NALU 是这样的:

[00 00 00 01 67 42 A0 1E 23 56 0E 2F ... ]
[00 00 00 01 68 42 B0 12 58 6A D4 FF ... ]

封装成 RTP 包将如下:

[ STAP-A NAL HDR ] [78 (STAP-A头,占用1个字节)] [第一个NALU长度 (占用两个字节)] [ 67 42 A0 1E 23 56 0E 2F ] [第二个NALU长度 (占用两个字节)] [68 42 B0 12 58 6A D4 FF ... ]

          0                   1                   2                   3
         0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                          RTP Header                           |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |STAP-A NAL HDR |         NALU 1 Size           | NALU 1 HDR    |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                         NALU 1 Data                           |
        :                                                               :
        +               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |               | NALU 2 Size                   | NALU 2 HDR    |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                         NALU 2 Data                           |
        :                                                               :
        |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                               :...OPTIONAL RTP padding        |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            
        Figure 7.  An example of an RTP packet including an STAP-A
                   containing two single-time aggregation units
               
               
        <!---->
       
* STAP-B: including DON

    <!---->


         0                   1                   2                   3
         0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                          RTP Header                           |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |STAP-B NAL HDR | DON                           | NALU 1 Size   |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        | NALU 1 Size   | NALU 1 HDR    | NALU 1 Data                   |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               +
        :                                                               :
        +               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |               | NALU 2 Size                   | NALU 2 HDR    |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                       NALU 2 Data                             |
        :                                                               :
        |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                               :...OPTIONAL RTP padding        |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            
        Figure 8.  An example of an RTP packet including an STAP-B
                   containing two single-time aggregation units


<!---->
  1. Multi-time aggregation packet (MTAP):

    这两种MAPS的区别在于 timestamp offset 的长度不同。

    • MTAP16:
        0                   1                   2                   3
        0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                          RTP Header                           |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |MTAP16 NAL HDR |  decoding order number base   | NALU 1 Size   |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |  NALU 1 Size  |  NALU 1 DOND  |       NALU 1 TS offset        |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |  NALU 1 HDR   |  NALU 1 DATA                                  |
        +-+-+-+-+-+-+-+-+                                               +
        :                                                               :
        +               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |               | NALU 2 SIZE                   |  NALU 2 DOND  |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |       NALU 2 TS offset        |  NALU 2 HDR   |  NALU 2 DATA  |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+               |
        :                                                               :
        |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                               :...OPTIONAL RTP padding        |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        
        Figure 12.  An RTP packet including a multi-time aggregation
                        packet of type MTAP16 containing two multi-time
                        aggregation units

* MTAP24:



        0                   1                   2                   3
        0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                          RTP Header                           |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |MTAP24 NAL HDR |  decoding order number base   | NALU 1 Size   |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |  NALU 1 Size  |  NALU 1 DOND  |       NALU 1 TS offs          |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |NALU 1 TS offs |  NALU 1 HDR   |  NALU 1 DATA                  |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               +
        :                                                               :
        +               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |               | NALU 2 SIZE                   |  NALU 2 DOND  |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |       NALU 2 TS offset                        |  NALU 2 HDR   |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |  NALU 2 DATA                                                  |
        :                                                               :
        |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                               :...OPTIONAL RTP padding        |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        
        Figure 13.  An RTP packet including a multi-time aggregation
                    packet of type MTAP24 containing two multi-time
                    aggregation units

--

2.4 Fragmentation Units

一个Nal Unit会被分割成,通过多个rtp包进行发送,这样便于传输和以后做fec处理。

  • FU-A:
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FU indicator  |   FU header   |                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
|                                                               |
|                         FU payload                            |
|                                                               |
|                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               :...OPTIONAL RTP padding        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Figure 14.  RTP payload format for FU-A
  • FU-B:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FU indicator  |   FU header   |               DON             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
|                                                               |
|                         FU payload                            |
|                                                               |
|                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               :...OPTIONAL RTP padding        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Figure 15.  RTP payload format for FU-B

The FU indicator octet has the following format:

   +---------------+
   |0|1|2|3|4|5|6|7|
   +-+-+-+-+-+-+-+-+
   |F|NRI|  Type   |
   +---------------+

The FU header has the following format:

  • S:1表示第一包
  • E:1表示是最后一个包
  • R:1表示中间
  • Type:类型


+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+

目前webrtc中使用的打包代码如下:

    void RtpPacketizerH264::GeneratePackets() {
      LOG(LS_VERBOSE) << "RtpPacketizerH264::GeneratePackets packetization_mode "
                      << (int)packetization_mode_
                      << " max_payload_len "
                      << max_payload_len_
                      ;
    
      for (size_t i = 0; i < input_fragments_.size();) {
        switch (packetization_mode_) {
          case H264PacketizationMode::SingleNalUnit:
            PacketizeSingleNalu(i);
            ++i;
            break;
          case H264PacketizationMode::NonInterleaved:
            size_t fragment_len = input_fragments_[i].length;
            if (i + 1 == input_fragments_.size()) {
              // Pretend that last fragment is larger instead of making last packet
              // smaller.
              fragment_len += last_packet_reduction_len_;
            }
            if (fragment_len > max_payload_len_) {
              PacketizeFuA(i);
              ++i;
            } else {
              i = PacketizeStapA(i);
            }
            break;
        }
      }
    }

第二部分:接收端的数据流处理

先看一下整理的类图

video_receive_stream.jpg

处理流程如下

  1. 首先在cricket::WebRtcVideoChannel中的OnPacketReceived函数中,我们会收到RTP包。这个是通过ICE 建立的UDP链接传来的数据。
  2. RTP数据包一直走到webrtc::RtpVideoStreamReceiver的OnPacketReceived函数中,调用webrtc::RtpReceiverImpl的IncomingRtpPacket进行rtp 包解析。
  3. 解析完之后的数据,会通过OnReceivedPayloadData回调上来,webrtc::RtpVideoStreamReceiver会将接收到的rtp包数据,打包成VCMPacket的形式,插入到PacketBuffer中。
  4. PacketBuffer的主要工作就是收集rtp包,并且判断这些rtp包能否组装成一个完整的H264的Frame。主要的实现逻辑就是每次在InsertPacket的最后,都调用FindFrames函数去查是否有合适的帧组成。
  5. 如果发现一个完整的帧,PacketBuffer会通过OnReceivedFrame把frame数据回调给webrtc::RtpVideoStreamReceiver。然后再通过video_coding::RtpFrameReferenceFinder的ManageFrame来查找,是否有合适的帧可以送给Decoder解码。这里的合适主要分一下几点:
    • 判断帧是否连续
    • 判断参考帧是有没有丢失
    • 是否是IFRAME
  6. 找到decodeble的帧后,video_coding::RtpFrameReferenceFinder通过OnCompleteFrame把frame交给internal::VideoReceiveStream,它会把frame插入到FrameBuffer中。
  7. 这里的FrameBuffer,就是以前版本的jitter buffer。在新的webrtc中已经更名。
  8. internal::VideoReceiveStream内部有一个decode 线程,这个线程会定期问是否有合适的Frame可以送给decode解码。如果有,则把它送到vcm::VideoReceiver去解码。
  9. vcm::VideoReceiver中持有decode的外部类,VCMGenericDecoder。我们把数据送给他,如果有decode解码完的YUV数据,他会把数据通过FrameToRender 回调webrtc::VideoStreamDecoder。
  10. 最终,webrtc::VideoStreamDecoder把YUV数据通过OnFrame回调给internal::VideoReceiveStream。在这里,webrtc就会把YUV数据传给之前注册的Render。

总结

整体的数据流程在上面的已经做了一个简单的描述,其中比较主要的是还标黄色的几个类。由于时间有限,还是会有很多具体的细节没有扩展,比如packet buffer的拼frame的逻辑,FrameBuffer找decodable frame的逻辑。这些可以在下次的文章中再和大家分享。


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • 前言: 工作中常常需要使用NSTimer定时器.写下这篇日志是为了记下学到的东西. 常用方法讲解: + (NST...
    HEVI1991阅读 668评论 0 0
  • 简要说明: 白天逛景点,晚上购物 酒店[03.23-03.25] Chisun Inn Osaka Hommac...
    我擦擦日子阅读 247评论 0 0
  • 爷爷的手臂很长,这衬得他的身体很小。兴许,当年他个强壮的汉子,但,他成了老人,爷爷。他不再像年轻时闯荡了。他...
    广电1702b23余蕊阅读 195评论 6 1