范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

WebRTC系列之JitterBuffer(1)

  在音视频网络传输过程中,由于存在网路抖动情况,接收端视频接受不及时导致播放卡顿,为了消除帧间抖动情况,一个解决手段是JitterBuffer。JitterBuffer包括RTP包的排序,GOP内帧排序以及GOP间排序。(文末注解名词解释)。 1、RTP包排序:PacketBuffer1.1 插入RTP数据包(PacketBuffer::InsertPacket)
  这个函数首先判断是不是首包,是的话就记录一下,接下来的包开始往后排序,不是的话就调用包序列号比较函数AheadOf。在利用索引计算的包在缓存中的位置如果被占用并且序列号一样,就是重复包,丢掉。如果被占用但是序列号不相同,就说明缓存满了,需要扩容,重新计算包的索引值,扩容后还是满的就要情况缓存了 PacketBuffer::InsertResult PacketBuffer::InsertPacket(     std::unique_ptr packet) {   PacketBuffer::InsertResult result;   //当前包序号   uint16_t seq_num = packet->seq_num;   //当前包在缓存中的索引   size_t index = seq_num % buffer_.size();    if (!first_packet_received_) {     //保存第一个包     first_seq_num_ = seq_num;     //第一个包的序号     first_packet_received_ = true;     //收到了第一个包   } else if (AheadOf(first_seq_num_, seq_num)) {     // If we have explicitly cleared past this packet then it"s old,     // don"t insert it, just silently ignore it.     // 如果当前包比之前记录的第一个包first_seq_num_还老     // 并且之前已经清理过第一个包序列号,说明已经至少成功解码过一帧,RtpVideoStreamReceiver::FrameDecoded     // 会调用PacketBuffer::ClearTo(seq_num),清理first_seq_num_之前的所有缓存,这个时候还来一个比first_seq_num_还     // 老的包,就没有必要再留着了。     if (is_cleared_to_first_seq_num_) {       return result;     }     // 相反如果没有被清理过,则是有必要保留成第一个包的,比如发生了乱序。     first_seq_num_ = seq_num;   }   //如果缓存的槽被占了,而且序号一样,说明是重复包,丢掉   if (buffer_[index] != nullptr) {     // Duplicate packet, just delete the payload.     if (buffer_[index]->seq_num == packet->seq_num) {       return result;     }      // The packet buffer is full, try to expand the buffer.     // 如果槽被占,但是输入包和对应槽的包序列号不等,说明缓存满了,需要扩容。     // ExpandBufferSize() 会更新缓存在新的队列的位置,并不会引起位置错误     while (ExpandBufferSize() && buffer_[seq_num % buffer_.size()] != nullptr) {     }     // 重新计算输入包索引.     index = seq_num % buffer_.size();      // Packet buffer is still full since we were unable to expand the buffer.     // 如果对应的槽还是被占用了,还是满,已经不行了,致命错误.     if (buffer_[index] != nullptr) {       // Clear the buffer, delete payload, and return false to signal that a       // new keyframe is needed.       RTC_LOG(LS_WARNING) << "Clear PacketBuffer and request key frame.";       ClearInternal();       result.buffer_cleared = true;       return result;     }   }   //之前的包是否连续,这里初始为false,在FindFrames中置位   packet->continuous = false;   //此处的move移动语义提升了效率   buffer_[index] = std::move(packet);   // 更新丢包信息,检查收到当前包后是否有丢包导致的空洞,也就是不连续.   UpdateMissingPackets(seq_num);    result.packets = FindFrames(seq_num);   return result; }1.2 插入填充包(PacketBuffer::InsertPadding)
  这里的填充包类似于滥竽充数,主要是由于发送端为了满足输出码率的情况下进行的Padding, PacketBuffer::InsertResult PacketBuffer::InsertPadding(uint16_t seq_num) {   PacketBuffer::InsertResult result;   // 更新丢包信息,检查收到当前包后是否有丢包导致的空洞,也就是不连续.   UpdateMissingPackets(seq_num);   // 分析排序缓存,检查是否能够组装出完整的帧并返回.   result.packets = FindFrames(static_cast(seq_num + 1));   return result; }1.3 丢包检测(PacketBuffer::UpdateMissingPackets)
  这个函数主要完成的是包是否是连续的,主要靠丢包缓存missing_packets_维护包序列号。 void PacketBuffer::UpdateMissingPackets(uint16_t seq_num) {   // 如果最新插入的包序列号还未设置过,这里直接设置一次.   if (!newest_inserted_seq_num_)     newest_inserted_seq_num_ = seq_num;    const int kMaxPaddingAge = 1000;   // 如果当前包的序列号新于之前的最新包序列号,没有发生乱序   if (AheadOf(seq_num, *newest_inserted_seq_num_)) {     // 丢包缓存missing_packets_最大保存1000个包,这里得到当前包1000个包以前的序列号,     // 也就差不多是丢包缓存里应该保存的最老的包.     uint16_t old_seq_num = seq_num - kMaxPaddingAge;     // 第一个>= old_seq_num的包的位置     auto erase_to = missing_packets_.lower_bound(old_seq_num);     // 删除丢包缓存里所有1000个包之前的所有包(如果有的话)     missing_packets_.erase(missing_packets_.begin(), erase_to);      // Guard against inserting a large amount of missing packets if there is a     // jump in the sequence number.     // 如果最老的包的序列号都比当前最新包序列号新,那么更新一下当前最新包序列号     if (AheadOf(old_seq_num, *newest_inserted_seq_num_))       *newest_inserted_seq_num_ = old_seq_num;     // 因为seq_num >newest_inserted_seq_num_,这里开始统计(newest_inserted_seq_num_, sum)之间的空洞.     ++*newest_inserted_seq_num_;     // 从newest_inserted_seq_num_开始,每个小于当前seq_num的包都进入丢包缓存,直到newest_inserted_seq_num_ ==     // seq_num,也就是最新包的序列号变成了当前seq_num.     while (AheadOf(seq_num, *newest_inserted_seq_num_)) {       missing_packets_.insert(*newest_inserted_seq_num_);       ++*newest_inserted_seq_num_;     }   } else {     // 如果当前收到的包的序列号小于当前收到的最新包序列号,则从丢包缓存中删除(之前应该已经进入丢包缓存)     missing_packets_.erase(seq_num);   } }1.4 连续包检测(PacketBuffer::PotentialNewFrame)
  主要作用就是检测当前包的前面是否连续,连续的话才会进行完整帧的检测。 bool PacketBuffer::PotentialNewFrame(uint16_t seq_num) const {   // 通过序列号获取缓存索引   size_t index = seq_num % buffer_.size();   // 上个包的索引   int prev_index = index > 0 ? index - 1 : buffer_.size() - 1;   const auto& entry = buffer_[index];   const auto& prev_entry = buffer_[prev_index];   // 如果当前包的槽位没有被占用,那么该包之前没有处理过,不连续   if (entry == nullptr)     return false;   // 如果当前包的槽位的序列号和当前包序列号不一致,不连续.   if (entry->seq_num != seq_num)     return false;   // 如果当前包的帧开始标识frame_begin为true,那么该包是帧第一个包,连续.   if (entry->is_first_packet_in_frame())     return true;   // 如果上个包的槽位没有被占用,那么上个包之前没有处理过,不连续.     if (prev_entry == nullptr)     return false;   // 如果上个包和当前包的序列号不连续,不连续.   if (prev_entry->seq_num != static_cast(entry->seq_num - 1))     return false;   // 如果上个包的时间戳和当前包的时间戳不相等,不连续.   if (prev_entry->timestamp != entry->timestamp)     return false;   // 排除掉以上所有错误后,如果上个包连续,则可以认为当前包连续.   if (prev_entry->continuous)     return true;   // 如果上个包不连续或者有其他错误,就返回不连续.   return false; }1.5 帧的完整性检测(PacketBuffer::FindFrames)
  PacketBuffer::FindFrames函数会遍历排序缓存中连续的包,检查一帧的边界,但是这里对VPX和H264的处理做了区分:
  对VPX,这个函数认为包的frame_begin可信,这样VPX的完整一帧就完全依赖于检测到frame_begin和frame_end这两个包;
  对H264,这个函数认为包的frame_begin不可信,并不依赖frame_begin来判断帧的开始,但是frame_end仍然是可信的,具体说H264的开始标识是通过从frame_end标识的一帧最后一个包向前追溯,直到找到一个时间戳不一样的断层,认为找到了完整的一个H264的帧。
  另外这里对H264的P帧做了一些特殊处理,虽然P帧可能已经完整,但是如果该P帧前面仍然有丢包空洞,不会立刻向后传递,会等待直到所有空洞被填满,因为P帧必须有参考帧才能正确解码。
  C++音视频学习资料免费获取方法:关注音视频开发T哥  ,点击「链接」即可免费获取2023年最新 C++音视频开发进阶独家免费学习大礼包!
  std::vector> PacketBuffer::FindFrames(     uint16_t seq_num) {   std::vector> found_frames;   // 基本算法:遍历所有连续包,先找到带有frame_end标识的帧最后一个包,然后向前回溯,   // 找到帧的第一个包(VPX是frame_begin, H264是时间戳不连续),组成完整一帧,   // PotentialNewFrame(seq_num)检测seq_num之前的所有包是否连续.   for (size_t i = 0; i < buffer_.size() && PotentialNewFrame(seq_num); ++i) {     // 当前包的缓存索引     size_t index = seq_num % buffer_.size();     // 如果seq_num之前所有包连续,那么seq_num自己也连续.     buffer_[index]->continuous = true;      // If all packets of the frame is continuous, find the first packet of the     // frame and add all packets of the frame to the returned packets.     // 找到了帧的最后一个包.     if (buffer_[index]->is_last_packet_in_frame()) {       // 帧开始序列号,从帧尾部开始.       uint16_t start_seq_num = seq_num;        // Find the start index by searching backward until the packet with       // the |frame_begin| flag is set.       // 开始向前回溯,找帧的第一个包.       // 帧开始的索引,从帧尾部开始.       int start_index = index;       // 已经测试的包数       size_t tested_packets = 0;       // 当前包的时间戳. 也就是帧的时间戳       int64_t frame_timestamp = buffer_[start_index]->timestamp;        // Identify H.264 keyframes by means of SPS, PPS, and IDR.       bool is_h264 = buffer_[start_index]->codec() == kVideoCodecH264;       bool has_h264_sps = false;       bool has_h264_pps = false;       bool has_h264_idr = false;       bool is_h264_keyframe = false;       int idr_width = -1;       int idr_height = -1;       // 从帧尾部的包开始回溯.       while (true) {         // 测试包数++         ++tested_packets;         // 如果是VPX,并且找到了frame_begin标识的第一个包,一帧完整,回溯结束.         if (!is_h264 && buffer_[start_index]->is_first_packet_in_frame())           break;         // h264 判断方式         if (is_h264) {           //获取h264 相关信息,           const auto* h264_header = absl::get_if(               &buffer_[start_index]->video_header.video_type_header);           if (!h264_header || h264_header->nalus_length >= kMaxNalusPerPacket)             return found_frames;           // 遍历所有NALU,注意WebRTC所有IDR帧前面都会带SPS、PPS.           for (size_t j = 0; j < h264_header->nalus_length; ++j) {             if (h264_header->nalus[j].type == H264::NaluType::kSps) {               has_h264_sps = true;             } else if (h264_header->nalus[j].type == H264::NaluType::kPps) {               has_h264_pps = true;             } else if (h264_header->nalus[j].type == H264::NaluType::kIdr) {               has_h264_idr = true;             }           }           // 默认sps_pps_idr_is_h264_keyframe_为false,也就是说只需要有IDR帧就认为是关键帧,           // 而不需要等待SPS、PPS完整.           if ((sps_pps_idr_is_h264_keyframe_ && has_h264_idr && has_h264_sps &&                has_h264_pps) ||               (!sps_pps_idr_is_h264_keyframe_ && has_h264_idr)) {             is_h264_keyframe = true;             // Store the resolution of key frame which is the packet with             // smallest index and valid resolution; typically its IDR or SPS             // packet; there may be packet preceeding this packet, IDR"s             // resolution will be applied to them.             if (buffer_[start_index]->width() > 0 &&                 buffer_[start_index]->height() > 0) {               idr_width = buffer_[start_index]->width();               idr_height = buffer_[start_index]->height();             }           }         }         // 如果检测包数已经达到缓存容量,中止.         if (tested_packets == buffer_.size())           break;          start_index = start_index > 0 ? start_index - 1 : buffer_.size() - 1;          // In the case of H264 we don"t have a frame_begin bit (yes,         // |frame_begin| might be set to true but that is a lie). So instead         // we traverese backwards as long as we have a previous packet and         // the timestamp of that packet is the same as this one. This may cause         // the PacketBuffer to hand out incomplete frames.         // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=7106         // 这里保留了注释,可以看看H264不使用frame_begin的原因,         //已timestamp发生变化,认为是一帧结束         if (is_h264 && (buffer_[start_index] == nullptr ||                         buffer_[start_index]->timestamp != frame_timestamp)) {           break;         }         // 如果仍然在一帧内,开始包序列号--.         --start_seq_num;       }       //如果没有sps或者pps 异常警告       if (is_h264) {         // Warn if this is an unsafe frame.         if (has_h264_idr && (!has_h264_sps || !has_h264_pps)) {           RTC_LOG(LS_WARNING)               << "Received H.264-IDR frame "                  "(SPS: "               << has_h264_sps << ", PPS: " << has_h264_pps << "). Treating as "               << (sps_pps_idr_is_h264_keyframe_ ? "delta" : "key")               << " frame since WebRTC-SpsPpsIdrIsH264Keyframe is "               << (sps_pps_idr_is_h264_keyframe_ ? "enabled." : "disabled");         }          // Now that we have decided whether to treat this frame as a key frame         // or delta frame in the frame buffer, we update the field that         // determines if the RtpFrameObject is a key frame or delta frame.         //帧的起始位置         const size_t first_packet_index = start_seq_num % buffer_.size();         // 设置数据缓存中的关键帧标识.         if (is_h264_keyframe) {           buffer_[first_packet_index]->video_header.frame_type =               VideoFrameType::kVideoFrameKey;           if (idr_width > 0 && idr_height > 0) {             // IDR frame was finalized and we have the correct resolution for             // IDR; update first packet to have same resolution as IDR.             buffer_[first_packet_index]->video_header.width = idr_width;             buffer_[first_packet_index]->video_header.height = idr_height;           }         } else {           buffer_[first_packet_index]->video_header.frame_type =               VideoFrameType::kVideoFrameDelta;         }          // If this is not a keyframe, make sure there are no gaps in the packet         // sequence numbers up until this point.         // 这个条件是说在丢包的列表里搜索>start_seq_num(帧开始序列号)的第一个位置,         // 发现其不等于丢包列表的开头, 有些丢的包序列号小于start_seq_num,         // 也就是说P帧前面有丢包空洞, 举例1: missing_packets_ = { 3, 4, 6},         // start_seq_num = 5, missing_packets_.upper_bound(start_seq_num)==6         // 作为一帧开始位置的序列号5,前面还有3、4这两个包还未收到,那么对P帧来说,虽然完整,但是向后传递也可能是没有意义的,         // 所以这里又清除了frame_created状态,先继续缓存,等待丢包的空洞填满.         // 举例2:         // missing_packets_ = { 10, 16, 17}, start_seq_num = 3,         // missing_packets_.upper_bound(start_seq_num)==10         // 作为一帧开始位置的序列号3,前面并没有丢包,并且帧完整,那么可以向后传递.         if (!is_h264_keyframe && missing_packets_.upper_bound(start_seq_num) !=                                      missing_packets_.begin()) {           return found_frames;         }       }        const uint16_t end_seq_num = seq_num + 1;       // Use uint16_t type to handle sequence number wrap around case.       uint16_t num_packets = end_seq_num - start_seq_num;       found_frames.reserve(found_frames.size() + num_packets);       for (uint16_t i = start_seq_num; i != end_seq_num; ++i) {         std::unique_ptr& packet = buffer_[i % buffer_.size()];         RTC_DCHECK(packet);         RTC_DCHECK_EQ(i, packet->seq_num);         // Ensure frame boundary flags are properly set.         packet->video_header.is_first_packet_in_frame = (i == start_seq_num);         packet->video_header.is_last_packet_in_frame = (i == seq_num);         found_frames.push_back(std::move(packet));       }       // 马上要组帧了,清除丢包列表中到帧开始位置之前的丢包.       // 对H264 P帧来说,如果P帧前面有空洞不会运行到这里,在上面已经解释.       // 对I帧来说,可以丢弃前面的丢包信息(?).       missing_packets_.erase(missing_packets_.begin(),                              missing_packets_.upper_bound(seq_num));     }     // 向后扩大搜索的范围,假设丢包、乱序,当前包的seq_num刚好填补了之前的一个空洞,     // 该包并不能检测出一个完整帧,需要这里向后移动指针到frame_end再进行回溯,直到检测出完整帧,     // 这里会继续检测之前缓存的因为前面有空洞而没有向后传递的P帧。     ++seq_num;   }   return found_frames; }2、帧的排序
  一个GOP内P帧依赖前面的P帧和I关键帧。,RtpFrameReferenceFinder就是要找到每个帧的参考帧。I帧是GOP起始帧自参考,后续GOP内每个帧都要参考上一帧。RtpFrameReferenceFinder维护最近的GOP表,收到P帧后,RtpFrameReferenceFinder找到P帧所属的GOP,将P帧的参考帧设置为GOP内该帧的上一帧,之后传递给FrameBuffer。 2.1 设置参考帧(RtpSeqNumOnlyRefFinder::ManageFrameInternal)
  这个函数主要是处理GOP内帧的连续性以及设置参考帧。 RtpSeqNumOnlyRefFinder::FrameDecision RtpSeqNumOnlyRefFinder::ManageFrameInternal(RtpFrameObject* frame) {   // 如果是关键帧,插入GOP表,key是last_seq_num,初始value是{last_seq_num,last_seq_num}   if (frame->frame_type() == VideoFrameType::kVideoFrameKey) {     last_seq_num_gop_.insert(std::make_pair(         frame->last_seq_num(),         std::make_pair(frame->last_seq_num(), frame->last_seq_num())));   }    // We have received a frame but not yet a keyframe, stash this frame.   // 如果GOP表空,那么就不可能找到参考帧,先缓存.   if (last_seq_num_gop_.empty())     return kStash;    // Clean up info for old keyframes but make sure to keep info   // for the last keyframe.   // 删除较老的关键帧(PID小于last_seq_num - 100), 但是至少保留一个。   auto clean_to = last_seq_num_gop_.lower_bound(frame->last_seq_num() - 100);   for (auto it = last_seq_num_gop_.begin();        it != clean_to && last_seq_num_gop_.size() > 1;) {     it = last_seq_num_gop_.erase(it);   }    // Find the last sequence number of the last frame for the keyframe   // that this frame indirectly references.   // 在GOP表中搜索第一个比当前帧新的关键帧。   auto seq_num_it = last_seq_num_gop_.upper_bound(frame->last_seq_num());   // 如果搜索到的关键帧是最老的,说明当前帧比最老的关键帧还老,无法设置参考帧,丢弃.   if (seq_num_it == last_seq_num_gop_.begin()) {     RTC_LOG(LS_WARNING) << "Generic frame with packet range ["                         << frame->first_seq_num() << ", "                         << frame->last_seq_num()                         << "] has no GoP, dropping frame.";     return kDrop;   }   // 如果搜索到的关键帧不是最老的,那么搜索到的关键帧的上一个关键帧所在的GOP里应该可以找到参考帧,   // 如果找不到关键帧,seq_num_it为end(), seq_num_it--则为最后一个关键帧.   seq_num_it--;    // Make sure the packet sequence numbers are continuous, otherwise stash   // this frame.   // 保证帧的连续,不连续则先缓存.   // 当前GOP的最新一个帧的最后一个包的序列号.   uint16_t last_picture_id_gop = seq_num_it->second.first;   // 当前GOP的最新包的序列号,可能是last_picture_id_gop, 也可能是填充包.   uint16_t last_picture_id_with_padding_gop = seq_num_it->second.second;   // P帧的连续性检查.   if (frame->frame_type() == VideoFrameType::kVideoFrameDelta) {     // 获得P帧第一个包的上个包的序列号.     uint16_t prev_seq_num = frame->first_seq_num() - 1;     // 如果P帧第一个包的上个包的序列号与当前GOP的最新包的序列号不等,说明不连续,先缓存.     if (prev_seq_num != last_picture_id_with_padding_gop)       return kStash;   }   // 现在这个帧是连续的了   RTC_DCHECK(AheadOrAt(frame->last_seq_num(), seq_num_it->first));    // Since keyframes can cause reordering we can"t simply assign the   // picture id according to some incrementing counter.   // 获得当前帧的最后一个包的序列号,设置为初始PID,后面还会设置一次Unwrap.   frame->SetId(frame->last_seq_num());   // 设置帧的参考帧数,P帧才需要1个参考帧.   frame->num_references =       frame->frame_type() == VideoFrameType::kVideoFrameDelta;   // 设置参考帧为当前GOP的最新一个帧的最后一个包的序列号,   // 既然该帧是连续的,那么其参考帧自然也就是上个帧.   frame->references[0] = rtp_seq_num_unwrapper_.Unwrap(last_picture_id_gop);   // 如果当前帧比当前GOP的最新一个帧的最后一个包还新,则更新GOP的最新一个帧的最后一个包(first)   // 以及GOP的最新包(second).   if (AheadOf(frame->Id(), last_picture_id_gop)) {     seq_num_it->second.first = frame->Id();// 更新GOP的最新一个帧的最后一个包     seq_num_it->second.second = frame->Id();// 更新GOP的最新包,可能被填充包更新.   }   // 更新填充包状态.   UpdateLastPictureIdWithPadding(frame->Id());   frame->SetSpatialIndex(0);   // 设置当前帧的PID为Unwrap形式.   frame->SetId(rtp_seq_num_unwrapper_.Unwrap(frame->Id()));   return kHandOff;2.2 处理Padding(RtpSeqNumOnlyRefFinder::PaddingReceived)
  该函数更新填充包,如果填充包填补了GOP内的序列号空洞,那么P就可以是连续的,尝试处理P帧。 RtpFrameReferenceFinder::ReturnVector RtpSeqNumOnlyRefFinder::PaddingReceived(     uint16_t seq_num) {   // 只保留最近100个填充包.   auto clean_padding_to =       stashed_padding_.lower_bound(seq_num - kMaxPaddingAge);   stashed_padding_.erase(stashed_padding_.begin(), clean_padding_to);   // 缓存填充包.   stashed_padding_.insert(seq_num);   // 更新填充包状态.   UpdateLastPictureIdWithPadding(seq_num);   RtpFrameReferenceFinder::ReturnVector res;   // 尝试处理一次缓存的P帧,有可能序列号连续了.   RetryStashedFrames(res);   return res; }
  3处理缓存的包(RtpSeqNumOnlyRefFinder::RetryStashedFrames)
  最常见的是找到带有参考帧的连续帧,如果遇到上述说的Padding包序列号刚好满足的情况时,也会尝试处理。 void RtpSeqNumOnlyRefFinder::RetryStashedFrames(     RtpFrameReferenceFinder::ReturnVector& res) {   bool complete_frame = false;   // 遍历缓存的帧   do {     complete_frame = false;     for (auto frame_it = stashed_frames_.begin();          frame_it != stashed_frames_.end();) {       // 调用ManageFramePidOrSeqNum来处理一个缓存帧,检查是否可以输出带参考帧的连续的帧.       FrameDecision decision = ManageFrameInternal(frame_it->get());        switch (decision) {         case kStash:// 仍然不连续,或者没有参考帧.           ++frame_it;// 检查下一个缓存帧.           break;         case kHandOff:// 找到了一个带参考帧的连续的帧.           complete_frame = true;           res.push_back(std::move(*frame_it));           ABSL_FALLTHROUGH_INTENDED;         case kDrop:// 无论kHandOff、kDrop都可以从缓存中删除了.           frame_it = stashed_frames_.erase(frame_it);// 删除并检查下一个缓存帧.       }     }   } while (complete_frame);// 如果能持续找到带参考帧的连续的帧则继续. }
  今天先写这么多,具体参考链接是一位大神写的博客,具体链接附上:
  http://t.csdn.cn/Sf0Dl
  后续内容有时间补上。
  原文链接:WebRTC系列之JitterBuffer(1)

三消息姚明现身北控主场,新疆队彻底被除名,曾繁日并未离队不知不觉间,联赛第三十轮比赛将正式拉开帷幕。在此前北控对阵江苏的比赛中,篮协主席姚明现身北控主场,观看了北控对阵江苏的比赛。继新疆退赛风波后,姚明备受球迷质疑。可事实上,作为篮协以提供6种配色供选择,定位纯电小钢炮,ZEEKRX新信息曝光各位老司机们,大家好!请点击关注汽车行业情报站,第一时间了解最新的汽车资讯,在这里轻松读懂汽车!近日,情报君从相关渠道获悉,ZEEKRX新信息曝光。从目前官方发布的信息来看,该车未iQOO11官宣降价!有大内存需求的伙伴们福音来啦你心动了吗?3月1日iQOO官宣iQOO11降价,除8G运存版以外都进行了调整,12GB256GB和16GB256GB版本皆下调300元,分别为4099元4399元,16GB512GB版则是下202324秋冬米兰时装周,释放出的信号,传统日常又被重新诠释正在如火如荼举办的,202324秋冬米兰时尚周的秀场上,送上了好多大牌的设计,如大家熟知的PradaMaxMaraFendi等意大利大牌发布了新作。之前,由于某品牌的模特走秀时的化20222023赛季全国高山滑雪锦标赛在通化市万峰滑雪度假区开赛今天上午,风和日丽白雪皑皑的通化市万峰滑雪度假区迎来了为期四天的20222023赛季全国高山滑雪锦标赛事。来自全国各省地市(州)的16支代表队和个人,共110名运动员报名参赛。据介速滑世锦赛中国队女子短距离团体追逐摘铜新华社荷兰海伦芬3月2日电(记者刘旸王湘江)2023国际滑联速度滑冰世锦赛2日在荷兰海伦芬进行首个比赛日竞逐,张丽娜金京珠李奇时组成的中国队在女子短距离团体追逐比赛中以1分27秒8厦门传统商超面临转型阵痛来源台海网台海网3月3日讯(导报记者孙春燕文陆军航图)新的一年,传统商超的关店潮仍在继续!继华润万家五缘湾店上月底宣布正式退出厦门市场之后,沃尔玛加州店也传出即将闭店的消息,时间就孙铭徽最想和胡金秋ampampamp赵睿搭档赵睿不选我就拉黑他漂流瓶见直播吧3月3日讯近日,广厦男篮球员孙铭徽参加了BLINK北京节目。Q是否期待即将到来的主客场?孙铭徽其实我期待了很久,我的家人真的好久好久没来杭州主场看我比赛了。我特别想现在立刻马生态大县江西崇义下竹功夫推动三产融合发展图为江西乐汇林新材料科技有限公司生产车间内,作业人员在生产线旁关注设备运转情况。(资料图)刘力鑫摄中新网赣州3月3日电(刘力鑫)88。3的森林覆盖率,70多万亩的竹林早春时节,走进下载看书剪辑这些APP为啥只能包月不能按次收费?市民刘先生发现,自己在使用百度网盘下载文件的时候,速度很慢,如果想加速,便要购买超级会员服务。但商家给出的付费方式只有按月按季度或者按年缴费,并没有单次付费的选项。记者注意到,像听后达利欧时代桥水CEO祭出40年来最大变革,押注AI和亚洲,开启裁员自从达利欧离开桥水之后,桥水已经和以往不同。桥水CEOBarDea采取雄心勃勃的策略,以提高回报率盈利能力开发新的收入来源。甚至在达利欧五个月前交出桥水控制权之前,改革就开始了,对
松下小型照明浴霸全新上市,身材虽小,却不将就照亮浴室空间,尽享沐浴之美松下小型照明浴霸自带10W冷色照明,5000K色温精致显色,能够让肌肤在灯光下呈现美丽质感。同时浴霸LED灯盖采用超声波焊接技术,能够有效防止浴室水蒸气进切记出国旅行千万不要帮别人代购,不然哑巴吃黄连,很容易得罪人天府晨报记者发现,出国旅行时,周围总是有一群亲戚朋友,要求帮忙购买各种商品。当海外旅游大灾难买房旅游时,心情是否依然美好?听听他们的故事。旁白丽丽,一所大学的博士丽丽在英国呆了10双胞胎一个奶奶带一个妈妈带,半年身高相差半个头,原因亏在补钙家有双胞胎,两个孩子长得简直就是一个模子里面刻出来的,无论长相身高体重等,都几乎一模一样,而且拥有一对双胞胎孩子,也让我倍感自豪。自从两个孩子出生之后,孩子的奶奶就一直帮我带,后来安防监控龙头新技术业绩数倍增长为了可以正常的长期分享,我还是保持只总结一些比较简洁的内容,分享实属不易希望大家点点关注点点赞。内容有好有普通主要就是要持续观看,我会长期分享,给大家带来一定的帮助。补充一句,分析随身wifi怎么选?看完这篇就够了上网流量是人们生活中不可或缺的一部分,随着人们工作生活环境的多样化,随身WiFi这个产品也渐渐的步入了人们的视线。但是随着随身WiFi越来越受欢迎,很多人面对五花八门的各类随身Wi活在中国真好看过中国,才知道中国人有多么自豪游过中国,才知道中国治安有多好看过北京,才知道中国文明有多高游过上海,才知道中国秩序多有条看过深圳,才知道中国速度难赶超去过广州,才知道非洲朋友真不商场线下体验新玩法洛天依线下展太空潮流展奥特曼主题展现在的商场功能变得越来越复合,人们除了购物,参观各种各样的展览体验各种各样的新概念也成为逛商场的一大乐趣。有别于展览馆,商场的展览往往更有互动感和潮流感,譬如最近火爆的洛天依十周年90的人喝到都是假的,揭秘蓝山咖啡的真相阳光与沙滩环绕的牙买加,这个加勒比海的热带岛国,特产一种稀有而名贵的咖啡BlueMountainCoffee,它被誉为咖啡中的爱马仕全世界最好的咖啡。现在,小惟带您起底蓝山的前世今沿着丝路去旅行伊朗。设拉子设拉子的历史文化已有4,000多年,是古波斯最古老的城市之一,公元2世纪时是萨珊王朝的领土,公元6世纪阿拉伯人入侵波斯,萨珊王朝被推翻后,伊斯兰教传入波斯,设拉子也迅速发展成伊斯兰在越南旅行,5000元人民币能啥?看当地人怎么说的?可能有众多的小伙伴有这样的体会世界那么大,我想去看看。奈何钱包里面的钱那么少,哪也去不了。(此处已添加小程序,请到今日头条客户端查看)不得不说,时代在高速发展的今天,小伙伴们所面临国家文物局社会力量可利用文物建筑开办民宿国家文物局日前印发关于鼓励和支持社会力量参与文物建筑保护利用的意见。意见明确,社会力量可利用文物建筑开设博物馆陈列馆等公共文化场所,也可利用文物建筑开办民宿客栈茶社等旅游休闲服务场