FFmpeg從入門到精通:SEI那些事

2020-12-06 CIO時代網

  流媒體是採用流式傳輸方式在網絡上播放的媒體格式,視頻網站內容、短視頻、在線直播這些視頻形態,均屬於流媒體的不同分支。流媒體大致包含三個層級:碼流、封裝和協議。從音視頻編碼器輸出的碼流,經過某種封裝格式後,經過特定的協議傳輸、保存,構成了流媒體世界的基礎功能。

 

  在直播應用的開發過程中,如果把主播端消息事件傳遞到觀眾端,一般會以Instant Messaging(即時通訊)的方式傳遞過去,但因為消息分發通道和直播通道是分開的,因此消息與直播音視頻數據的同步性就會出現很多問題。那麼有沒有在音視頻內部傳遞消息的方法呢?答案是SEI(Supplemental Enhancement Information)。金山雲目前推出的直播問答解決方案中,就用到了SEI,作為一名視頻雲架構資深開發工程師,今天就來和大家分享一下SEI的技術細節。

 

  SEI介紹

 

  SEI即補充增強信息,屬於碼流範疇,它提供了向視頻碼流中加入額外信息的方法,是H.264/H.265這些視頻壓縮標準的特性之一。SEI的基本特徵如下:

 

  1.並非解碼過程的必須選項

 

  2.可能對解碼過程(容錯、糾錯)有幫助

 

  3.集成在視頻碼流中

 

  也就是說,視頻編碼器在輸出視頻碼流的時候,可以不提供SEI信息。雖然在視頻的傳輸過程、解封裝、解碼這些環節,都可能因為某種原因丟棄SEI內容,但在視頻內容的生成端和傳輸過程中,是可以插入SEI信息的。這些插入的信息,和其他視頻內容一同經過傳輸鏈路到達消費端。舉例來說,當前火爆的直播問答模式,就是通過SEI傳遞較多和答題業務相關的信息,通過SEI承載的信息,極大地優化了題目顯示和觀眾音視頻觀看的同步性。

 

  那麼在SEI中可以添加哪些信息呢?以下是一些用戶場景可任意擴展的例子:

 

  1.傳遞編碼器參數

 

  2.傳遞視頻版權資訊

 

  3.傳遞攝像頭參數

 

  4.傳遞內容生成過程中的剪輯事件(引發場景切換)

 

  對於SEI如何應用,我們先以H.264/AVC這一視頻編碼標準為例。在這一標準中,整個系統框架分為兩層:視頻編碼層面(Video Coding Layer,簡稱VCL)和網絡抽象層面(Network Abstraction Layer,簡稱NAL)。VCL負責表示有效視頻數據的內容,NAL負責格式化數據並提供頭信息,以保證數據適合各種信道和存儲介質上的傳輸。NAL unit是NAL的基本語法結構,它包含一個字節的頭信息(NAL header)和一系列來自VCL的原始數據字節流(RBSP)。

 

  H.264/AVC中的情況

 

  NAL unit type儲存在NAL header中,在H.264/AVC標準中,可用的NAL unit type一共有17種,作用是高速解碼器,承載的數據是視頻關鍵幀,還是視頻解碼器的配置參數信息。其中值為6時表徵SEI內容。比較常見的類型如下表所示:

  H.264/AVC中完整的NAL unit類型定義請參考《ISO/IEC 14496-10:2014》,這是MPEG專家組為AVC編解碼器制定的標準,H.264/AVC中NAL unit類型完整定義都在該標準的7-1表中,標準一共預留了32種類型,在NAL header裡面,用5 bits表徵NAL unit type。

  H.264/AVC中的NAL unit type

 

  如上圖所示,在8 bits的NAL header中:

 

  1.第0位是禁止位0,值為1時表示語法出錯

 

  2.第1~2位是參考級別(NRI,NAL ref idc)

 

  3.第3~7位是NAL unit type

 

  需要注意的是,當NRI取值為"00"(二進位)時,表徵NAL unit不參與重建參考圖像,這時的NAL unit是可以丟棄的。大於"00"(二進位)時,NAL unit不能被丟棄。

 

  H.265/HEVC中的情況

 

  《ISO/IEC 23008-2:2015》是MPEG專家組為HEVC編解碼器制定的標準,H.265/HEVC中NAL unit類型完整定義都在該標準的7-1表中,可用的NAL unit type一共有40種之多,其中39和40都表徵SEI內容。因為標準一共預留64種類型,所以在NAL header裡面,用6 bits表徵NAL unit type。

  H.265/HEVC中的NAL unit type

 

  如上圖所示,在16 bits的NAL header中:

 

  1.第0位是禁止位0,值為1時表示語法出錯

 

  2.第1~6位是NAL unit type

 

  3.第7~12位是NUH layer id

 

  4.第13~15位是temporal_id

 

  SEI類型

 

  在H.264/AVC視頻編碼標準中,並沒有規定SEI payload type的範圍,所以表徵payload type的字節數是浮動的。

 

  語法分析如下所示,當開始解析類型為SEI的NAL時,持續讀取8bit,直到非0xff為止,然後把讀取的數值累加,累加值即為SEI payload type。

 

  sei_message(){

 

  payloadType = 0

 

  while( next_bits(8) == 0xFF){

 

  ff_byte

 

  payloadType += 255

 

  }

 

  last_payload_type_byte

 

  payloadType += last_payload_type_byte

 

  }

 

  讀取SEI payload size和payload type邏輯類似,仍然是讀取到0xff為止,這樣可以支持任意長度的SEI payload添加。

 

  sei_message(){

 

  payloadSize = 0

 

  while( next_bits(8) == 0xFF){

 

  ff_byte

 

  payloadSize += 255

 

  }

 

  last_payload_size_byte

 

  payloadSize += last_payload_size_byte

 

  }

 

  當獲取了SEI payload類型和大小後,就進入了實際的SEI內容讀取。

 

  當前《ISO/IEC 14496-10:2014》Annex D.1.1提供了最大到181的payload類型處理規範,由於類型可以指定任意大小,給SEI的添加、處理創造了很大的自由空間。

 

  其中SEI payload類型值為5時,指定的處理方法叫user_data_unregistered(),字面含義為未註冊的用戶數據,常用於存儲編碼器的編碼參數信息,是比較常見的payload類型。

 

  讀取payload type為5時,具體的語法解析流程如下:

 

  user_data_unregistered(payloadSize){

 

  uuid_iso_iec_11578

 

  for( i=16; i< payloadSize; i++)

 

  user_data_payload_byte

 

  }

 

  其中uuid_iso_iec_11578的詳細定義在《ISO/IEC 11578:1996》 Annex A中,大致規定了使用128 bits(16個字節)來指定UUID。此處UUID可以表徵寫入SEI payload的角色ID,或者表徵其他業務用途。剩下的payloadSize -16位元組,即是業務層傳遞的具體內容了。

 

  通過user_data_unregistered()語法解析可以看出,當使用SEI payload type為5時,注意事項如下:

 

  1.payload size應該大於16;

 

  2.uuid可能出現0x000000/0x000001/0x000002,需要插入0x03做防競爭處理;

 

  構成RBSP時,都需要做RBSP拖尾處理。拖尾處理對所有SODB方式都一致。rbsp_trailing_bits()語法邏輯如下:

 

  rbsp_trailing_bits( ){

 

  rbsp_stop_one_bit

 

  while( !byte_aligned( ) )

 

  rbsp_alignment_zero_bit

 

  }

 

  SEI例子

 

  從video.js <https://github.com/videojs/video.js>的示例中下載oceans.mp4 <http://vjs.zencdn.net/v/oceans.mp4>並提取出H.264碼流如下:

  bitstream from oceans.mp4

 

  NAL header

 

  起始碼(暗紅底色)"0x00000001"分割出來的比特流即是NAL unit,起始碼緊跟的第一個字節(墨綠底色)是NAL header。上圖「NAL header」一共出現了四個數值:

 

  ·"0x06",此時NRI為"00B",NAL unit type為SEI類型。

 

  ·「0x67」,此時NRI為「11B」,NAL unit type為SPS類型。

 

  ·「0x68」,此時NRI為「11B」,NAL unit type為PPS類型。

 

  ·「0x65」,此時NRI為「11B」,NAL unit type為IDR圖像。

 

  SEI payload type

 

  "0x06"後一個字節為「0x05」(淡黃底色)是SEI payload type,即表徵SEI payload分析遵循user_data_unregistered()語法。

 

  SEI payload size

 

  「0x05」後一個字節為「0x2F」(淡藍底色)是SEI payload size,此時整個payload是47個字節。

 

  SEI payload uuid

 

  "0x2F"隨後的16個字節即為uuid,此時uuid為

 

  dc45e9bd-e6d9-48b7-962c-d820d923eeef

 

  SEI payload content

 

  由於payload size是47個字節,除去16位元組的uuid,剩下31個字節的content。由於content是字符串,所以有結束符"0x00",有效的30個字符內容是:

 

  Zencoder Video Encoding System

 

  rbsp trailing bits

 

  47個payload字節後的"0x80"(灰底色)即是rbsp trailing bits,在user_data_unregistered()裡面都是按字節寫入的,所以此時的NAL unit結尾寫入的字節一定是0x80。

 

  SEI的生成

 

  生成SEI的方式很多,大致可以有:

 

  1.對已有碼流做filter,插入SEI NAL

 

  2.視頻編碼時生成SEI

 

  3.容器層寫入時插入SEI

 

  以下代碼示例來自於FFmpeg origin/master 分支。

 

  bsf

 

  BitStream Filter(碼流過濾)的縮寫為bsf,它的作用是,在不做碼流解碼的前提下,對已經編碼後的比特流做特定的修改、調整。

 

  bsf h264_metadata的調用

 

  使用ffmpeg工具時,可以使用比特流過濾器。基本的filter調用格式如下:

 

  ffmpeg -i INPUT -c:v copy -bsf:v filter1[=opt1=str1:opt2=str2][,filter2] OUTPUT

 

  從上文提到的mp4文件中提取出h.264碼流oceans.h264,可以使用 h264_metadata比特流過濾器添加SEI。下面示例命令添加了類型為未註冊的用戶數據的SEI,其中uuid為"086f3693-b7b3-4f2c-9653-21492feee5b8",payload內容為"hello":

 

  ./ffmpeg  -I oceans.h264 -c:v copy -bsf:v h264_metadata=sei_user_data='086f3693-b7b3-4f2c-9653-21492feee5b8+hello' oceans.sei.h264

 

  其中oceans.h264已經有一個SEI和28個SPS。輸出的oceans.sei.h264碼流中,共有28個SEI,其中第一個與輸入保持一致,剩下27個為新插入的SEI。

 

  bsf h264_metadata的代碼分析

 

  具體代碼位於:libavcodec/h264_metadata_bsf.c中。

 

  // 函數int h264_metadata_filter(AVBSFContext *bsf, AVPacket *out)

 

  if (ctx->sei_user_data && (has_sps || !ctx->sei_first_au)) {

 

  H264RawSEI *sei;

 

  H264RawSEIPayload *payload;

 

  H264RawSEIUserDataUnregistered *udu;

 

  int sei_pos, sei_new;

 

  ctx->sei_first_au = 1;

 

  for (i = 0; i < au->nb_units; i++) {

 

  if (au->units[i].type == H264_NAL_SEI ||

 

  au->units[i].type == H264_NAL_SLICE ||

 

  au->units[i].type == H264_NAL_IDR_SLICE)

 

  break;

 

  }

 

  sei_pos = i;

 

  if (sei_pos < au->nb_units &&

 

  au->units[sei_pos].type == H264_NAL_SEI) {

 

  sei_new = 0;

 

  sei = au->units[sei_pos].content;

 

  } else {

 

  sei_new = 1;

 

  sei = &ctx->sei_nal;

 

  memset(sei, 0, sizeof(*sei));

 

  }

 

  }

 

  以上代碼是h264_metadata添加SEI的判斷邏輯,當指定了sei_user_data時,滿足以下條件之一即可以處理:

 

  ·讀取的access units是第一個au;

 

  ·當前au包含sps;

 

  滿足插入SEI邏輯後,具體處理過程中:

 

  ·如果發現第一個NAL已經是SEI,則該au不做插入SEI處理;

 

  ·如果au包含了IDR幀或者非IDR未分區的幀,則在其前面插入SEI信息。

 

  基於以上代碼,oceans.sei.h264碼流中新插入27個新的SEI 符合處理邏輯。

 

  具體構造SEI NAL Unit代碼如下:

 

  sei->nal_unit_header.nal_unit_type = H264_NAL_SEI;

 

  err = ff_cbs_insert_unit_content(ctx->cbc, au,

 

  sei_pos, H264_NAL_SEI, sei);

 

  if (err < 0) {

 

  av_log(bsf, AV_LOG_ERROR, "Failed to insert SEI.\n");

 

  goto fail;

 

  }

 

  payload = &sei->payload[sei->payload_count];

 

  payload->payload_type = H264_SEI_TYPE_USER_DATA_UNREGISTERED;

 

  udu = &payload->payload.user_data_unregistered;

 

  for (i = j = 0; j < 32 && ctx->sei_user_data[i]; i++) {

 

  int c, v;

 

  c = ctx->sei_user_data[i];

 

  if (c == '-') {

 

  continue;

 

  } else if (av_isxdigit(c)) {

 

  c = av_tolower(c);

 

  v = (c <= '9' ? c - '0' : c - 'a' + 10);

 

  } else {

 

  goto invalid_user_data;

 

  }

 

  if (i & 1)

 

  udu->uuid_iso_iec_11578[j / 2] |= v;

 

  else

 

  udu->uuid_iso_iec_11578[j / 2] = v << 4;

 

  ++j;

 

  }

 

  if (j == 32 && ctx->sei_user_data[i] == '+') {

 

  sei_udu_string = av_strdup(ctx->sei_user_data + i + 1);

 

  if (!sei_udu_string) {

 

  err = AVERROR(ENOMEM);

 

  goto sei_fail;

 

  }

 

  udu->data = sei_udu_string;

 

  udu->data_length = strlen(sei_udu_string);

 

  payload->payload_size = 16 + udu->data_length;

 

  }

 

  代碼完整解釋了上文提到的SEI規範,其中"H264_SEI_TYPE_USER_DATA_UNREGISTERED"值為5,對應的即是未註冊的用戶信息。在解析"ffmpeg"工具輸入過程中,將"+"號前面的字符串轉換成二進位寫入uuid,"+"後內容使用字符串寫入payload。

 

  x264

 

  libx264支持多種SEI類型數據寫入,常用的仍然是SEI_USER_DATA_UNREGISTERED,具體的寫入函數x264_sei_version_write()位於libx264/encoder/set.c中。

 

  int x264_sei_version_write( x264_t *h, bs_t *s )

 

  {

 

  static const uint8_t uuid[16] =

 

  {

 

  0xdc, 0x45, 0xe9, 0xbd, 0xe6, 0xd9, 0x48, 0xb7,

 

  0x96, 0x2c, 0xd8, 0x20, 0xd9, 0x23, 0xee, 0xef

 

  };

 

  char *opts = x264_param2string( &h->param, 0 );

 

  char *payload;

 

  int length;

 

  if( !opts )

 

  return -1;

 

  CHECKED_MALLOC( payload, 200 + strlen( opts ) );

 

  memcpy( payload, uuid, 16 );

 

  sprintf( payload+16, "x264 - core %d%s - H.264/MPEG-4 AVC codec - "

 

  "Copy%s 2003-2018 - http://www.videolan.org/x264.html - options: %s",

 

  X264_BUILD, X264_VERSION, HAVE_GPL?"left":"right", opts );

 

  length = strlen(payload)+1;

 

  x264_sei_write( s, (uint8_t *)payload, length, SEI_USER_DATA_UNREGISTERED );

 

  x264_free( opts );

 

  x264_free( payload );

 

  return 0;

 

  fail:

 

  x264_free( opts );

 

  return -1;

 

  }

 

  libx264提供的uuid和上文舉例的uuid一致,payload中主要記錄了相關參數和版權資訊。以上函數完成了SEI參數的構造,下面的函數x264_sei_write完成了具體語法的寫入:

 

  void x264_sei_write( bs_t *s, uint8_t *payload, int payload_size, int payload_type )

 

  {

 

  int i;

 

  bs_realign( s );

 

  for( i = 0; i <= payload_type-255; i += 255 )

 

  bs_write( s, 8, 255 );

 

  bs_write( s, 8, payload_type-i );

 

  for( i = 0; i <= payload_size-255; i += 255 )

 

  bs_write( s, 8, 255 );

 

  bs_write( s, 8, payload_size-i );

 

  for( i = 0; i < payload_size; i++ )

 

  bs_write( s, 8, payload[i] );

 

  bs_rbsp_trailing( s );

 

  bs_flush( s );

 

  }

 

  以上寫入的代碼邏輯和標準語法說明保持一致。

 

  SEI解析

 

  FFmpeg在讀取和解碼NAL unit,都有相同的邏輯處理SEI。

 

  讀取或者解碼數據時,會調用下面函數進行碼流的解碼,其中buf包含具體的二進位流,buf_size是當前碼流長度。函數內部會解析碼流並實例出具體的NAL對象:

 

  //Locate in libavcodec/h264dec.c

 

  int decode_nal_units(H264Context *h, const uint8_t *buf, int buf_size)

 

  如果NAL對象類型是SEI 時,將調用以下函數解碼:

 

  //Locate in libavcodec/h264_sei.c

 

  int ff_h264_sei_decode(H264SEIContext *h, GetBitContext *gb,

 

  const H264ParamSets *ps, void *logctx)

 

  函數內部會判斷SEI payload type進行不同的函數調用,如果是未註冊的用戶數據,則調用以下函數:

 

  int decode_unregistered_user_data(H264SEIUnregistered *h, GetBitContext *gb,void *logctx, int size)

 

  {

 

  uint8_t *user_data;

 

  int e, build, i;

 

  if (size < 16 || size >= INT_MAX - 16)

 

  return AVERROR_INVALIDDATA;

 

  user_data = av_malloc(16 + size + 1);

 

  if (!user_data)

 

  return AVERROR(ENOMEM);

 

  for (i = 0; i < size + 16; i++)

 

  user_data[i] = get_bits(gb, 8);

 

  user_data[i] = 0;

 

  e = sscanf(user_data + 16, "x264 - core %d", &build);

 

  if (e == 1 && build > 0)

 

  h->x264_build = build;

 

  if (e == 1 && build == 1 && !strncmp(user_data+16, "x264 - core 0000", 16))

 

  h->x264_build = 67;

 

  av_free(user_data);

 

  return 0;

 

  }

 

  可以看到,根據SEI語法標準,在解析了SEI payload type和length後,對未註冊用戶數據的提取,跳過了uuid的分析,只嘗試提取了x264的build信息。總體上,並未利用SEI_USER_DATA_UNREGISTERED傳遞過來的其他相關參數信息。

 

  從解碼器邏輯看,H264SEIUnregistered結構體只有一個x264_build屬性,並未返回實質有效數據。上層業務如果需要提取SEI_USER_DATA_UNREGISTERED,仍然需要自己提取。提取邏輯,請參考下一小節(ffplay)。

 

  ffplay

 

  ffplay是一個簡單、常用的FFmpeg接口示例工具,常用於測試解碼、播放效果。如果在ffplay中示例跑通SEI提取功能,可以很方便的移植到其他平臺。

 

  在ffplay中通過函數av_read_frame(ic, pkt)返回後,讀取pkt->data可以快速拿到當前讀到的NAL unit。從data數據中取出NAL unit type,如果是SEI且是用戶未註冊數據類型(payload type值為5),則可以參考SEI語法繼續讀取UUID和其後傳遞的字符串。

 

  總結

 

  本文主要對H.264碼流中涉及用戶未註冊數據的SEI進行了分析。總體而言,SEI只是視頻標準裡面很小的一部分,但在應用過程中,比如直播問答項目中SEI承載的信息,就極大提升了直播觀看和答題操作的整體用戶體驗。所以說,從SEI的例子中,我們就會發現,視頻標準裡面還有很多金礦等待著大家的挖掘,這就是多媒體技術的魅力,也是金山雲努力的方向。

第三十屆CIO班招生 法國布雷斯特商學院碩士班招生 北達軟EXIN網絡空間與IT安全基礎認證培訓 北達軟EXIN DevOps Professional認證培訓

責編:fanwei

相關焦點

  • 微積分從入門到精通的十道關卡之語言關(一)
    微積分從入門到精通的十道關卡之語言關(一)(一定先關注,再下單)第四關 懵逼的ε-δ,ε-X,M-δ,M-X系列語言(一)進入大學,很多學生不幸被極限中的ε-δ,ε-X,M-δ,M-X系列語言給難住了,當然更不幸的是很多高等數學教科書中根本就不學習「ε-δ」系列語言,尤其那些對概念理解要求比較高的經濟、管理類專業,往往因為對概念理解不到位濫用數學方法去解決經濟和管理中的問題,效果如何看看學界成果就知道了。
  • ffmpeg使用avfilter縮放圖像
    上篇文章ffmpeg解碼+OpenGL播放視頻,yuv420p格式寫了使用ffmpeg解碼,配合OpenGL播放視頻文件,接下來再加個avfilter縮放圖像,這樣OpenGL的顯示與視頻文件的格式就解耦合了。解碼後的視頻格式轉換,可以使用sws_scale()函數,這是ffmpeg很早以前就有的API,avfilter機制反而是後來添加的。
  • 質粒構建:從入門到精通之高手進階
    輕鬆科研 | 趣讀文獻 | 前沿資訊 | 實用技巧在前兩期《質粒構建:從入門到精通之初窺門徑
  • 電腦主板維修,從入門到精通,這樣的教程你是否喜歡呢
    大家好,我是你們的蚯蚓叔叔,蚯蚓電腦,有很多粉絲朋友在後臺跟我留言:能否出一套電腦主板維修的相關教程,通過幾個月的慎重考慮,我決定從零基礎到入門再到精通,而且免費的奉獻給大家;那麼就從今天這篇文章開始:
  • 無人機相關英文縮寫大合集:從入門到假裝精通
    下面筆者梳理了一些無人機的相關縮寫與通俗解釋,從入門到假裝精通,算是拋磚引玉。  一、入門:  ①UAV:無人駕駛飛機(Unmanned Aerial Vehicle),簡稱無人機。  ②FPV:第一人稱主視角(First Person View),把無人機機載攝像頭的畫面實時傳輸回來。
  • 微積分從入門到精通的第六關——橫空出世的「非恆等變形」
    01微積分從入門到精通的十大關卡(六)—— 橫空出世的「非恆等變形」
  • 微積分從入門到精通第七關——中間變量是什麼「鬼」?
    01微積分從入門到精通的十道關卡(七)
  • 弱電電工基礎技術手冊,新手從入門到精通利器!
    弱電電工基礎技術手冊,新手從入門到精通利器!大家熟悉的電工,一般都是從事高壓強電方面的工作,但是弱電電工的概念人們並不是很清楚,弱電電工是指從事建築物內部以及內部和外部間的信息交換與信息傳遞工程安裝維修工作的電工。如今,信息化發展越來越好,弱電電工在建築領域中起到的作用也越來越大。很多朋友都想要從事或者轉行做弱電,那麼如何進行更好地學習呢?
  • 早安我的少年sei清除自己的記憶了怎麼辦
    早安我的少年俘獲了不少少女的心,不管是媽媽粉還是女友粉都被他可愛的不要不要的,但是隨著和sei的互動變多,我們會發現sei的態度也有很大的變化,在一次功能擴展後甚至自行刪除記憶了,這樣應該怎麼辦呢?的互動變多,我們會發現sei的態度也有很大的變化,在一次功能擴展後甚至自行刪除記憶了,這樣應該怎麼辦呢?
  • 2019版CAD軟體基礎教程+7G視頻教程,從入門到精通,3天學會繪圖
    2019版CAD軟體基礎教程+7G視頻教程,從入門到精通,3天學會繪圖本套課程有完善的課程體系,科學的教學方式,本套課程適合從基礎開始系統學習CAD的同學學習,也適合有一定基礎的同學學習提高,通過本課程的學習,可以明顯提高作圖水平,對於準備從CAD製圖、建築設計、機械製造等行業同學有非常大的幫助
  • 《早安我的少年》怎麼讓sei醒過來 讓sei醒過來方法
    導 讀 在早安我的少年手遊中怎麼讓sei醒過來呢?
  • 151頁電氣識圖實操手冊,入門到精通快速提升
    151頁電氣識圖實操手冊,入門到精通快速提升作為電氣施工人員,日常工作中,離不開電氣識圖,電氣識圖是電氣施工人員必須具備的最基礎的技能之一。但是電氣圖紙也是比較複雜的,對新手而言確實不容易搞懂,還有一些常用的術語、電氣符號,都跟電氣施工息息相關。
  • 《早安我的少年》sei的等級怎麼看 sei的等級查看方式
    導 讀 在早安我的少年手遊中sei的等級究竟該怎麼查看呢?
  • 《早安我的少年》助手Sei怎麼叫醒 睡眠模式喚醒Sei攻略
    晚安有什麼用 通過在主頁面進行「晚安」,sei會進入睡眠模式。 關於睡眠模式 在睡眠模式中如果蓋住屏幕,屏幕就會自動關閉。當蓋住屏幕時,請注意以下幾點: 1.關閉終端的鎖屏功能;2.連接充電器;3.不按鎖屏按鈕; 就算沒有進行「晚安」,鬧鐘一樣會響; 如果在沒有設定鬧鐘時,進行了晚安,sei會一直睡到您叫醒他為止。請從「叫醒sei」把sei叫醒。
  • 電工技術:小白~入門~精通~前景,你可以這樣學習,老電工珍藏!
    小白一個,憑著對電工技術的熱愛和激情,怎麼樣才能更好的學習電工,需要看那些書籍?哪些書籍在學習前期能真正的幫助我們?下面我們就來重點說一下:什麼樣的書籍對電工的前期學習有幫助?你可以參考:《從零開始學電工》,《電工由入門到精通》,《維修電工基礎知識》,《家裝電工》等等。這些書籍雖然沒有《電工基礎》系統,全面,但是的確都是電工裡面的一個個小的分支,分開來重點介紹。
  • Python爬蟲從入門到精通只需要三個月
    如何入門Python,為了能夠幫助大家更輕鬆的學好Python開發,Python爬數據,Python數據分析等相關理論知識,給大家共同分享自己一套Python學習生活資料,文章最後面的有附屬的相關資料,無論你是大牛還是小白,是想轉行還是想入行都可以來了解,一起不斷進步以及一起努力學習
  • 從入門到精通:SEM競價推廣那些事兒
    這是在進行推廣前非常重要的一項基礎工作,如果計劃、單元組建部合理,不僅會影響到質量度,反而會讓你更不好管理帳戶,尤其會影響到寫廣告創意、出價、查看數據報告和後期優化等。推廣計劃的建立重在區分產品、詞類、周期等,推廣單元的建立則主要區分關鍵詞的類型。參考下圖示例:
  • 《第二銀河》圖文全攻略 從入門到精通教程技巧及資料總結
    《第二銀河》圖文全攻略,從入門到精通教程技巧及資料總結。(含「國家/職業選擇」「上手指南」「系統詳解」「玩法技巧」"資料解析「)《第二銀河》是一款開放性太空戰爭手遊,本作模擬構建了一個含有4961個恆星系的宇宙太空,是一款融合了RPG和SLG元素的開放世界太空星戰遊戲。玩家將建造並駕駛各式宇宙艦船,在多個人造蟲洞間躍遷穿梭,於廣袤而深邃的宇宙深處進行探索。
  • 內涵段子你是sei的文字版 太搞笑網友開始各種模仿
    內涵段子你是sei的文字版 太搞笑網友開始各種模仿時間:2018-02-09 23:27   來源:今日頭條   責任編輯:毛青青 川北在線核心提示:原標題:內涵段子你是sei的文字版 太搞笑網友開始各種模仿 最近在網絡上也是非常火的一個段子,叫做你是sei這個老哥也是非常的搞笑,在疑似喝醉的狀態下來了一段rap
  • 三分鐘從入門到精通——Python模塊
    #Python入門教程導入模塊時會發生什麼導入模塊名如果整個模塊(模塊名)尚不存在,則將其加載到內存中sys.modules使用module_name的鍵添加對它的引用在我們當前的名稱空間中添加一個引用了module_name對象的相同名稱的符號從module_name導入somename如果整個模塊(module_name)還不存在,則將其加載到內存中sys.modules使用module_name