<menu id="w8yyk"><menu id="w8yyk"></menu></menu>
  • <dd id="w8yyk"><nav id="w8yyk"></nav></dd>
    <menu id="w8yyk"></menu>
    <menu id="w8yyk"><code id="w8yyk"></code></menu>
    <menu id="w8yyk"></menu>
    <xmp id="w8yyk">
    <xmp id="w8yyk"><nav id="w8yyk"></nav>
  • 網站首頁 > 物聯資訊 > 技術分享

    ffmpeg解碼流程

    2016-09-28 00:00:00 廣州睿豐德信息科技有限公司 閱讀
    睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接

    轉帖360doc http://www.360doc.com/content/11/1117/09/8050095_165108638.shtml

    FFMPEG解碼流程

    RFID設備管理軟件

    1. 注冊所有容器格式和CODEC:av_register_all()

    2. 打開文件:av_open_input_file()

    3. 從文件中提取流信息:av_find_stream_info()

    4. 窮舉所有的流,查找其中種類為CODEC_TYPE_VIDEO

    5. 查找對應的解碼器:avcodec_find_decoder()

    6. 打開編解碼器:avcodec_open()

    7. 為解碼幀分配內存:avcodec_alloc_frame()

    8. 不停地從碼流中提取出幀數據:av_read_frame()

    9. 判斷幀的類型,對于視頻幀調用:avcodec_decode_video()

    10. 解碼完后,釋放解碼器:avcodec_close()

    11. 關閉輸入文件:av_close_input_file()

     

    首先第一件事情就是開一個視頻文件并從中得到流。我們要做的第一件事情就是使用av_register_all();來初始化libavformat/libavcodec: 

    這一步注冊庫中含有的所有可用的文件格式和編碼器,這樣當打開一個文件時,它們才能夠自動選擇相應的文件格式和編碼器。av_register_all()只需調用一次,所以,要放在初始化代碼中。也可以僅僅注冊個人的文件格式和編碼。

    下一步,打開文件:

    AVFormatContext *pFormatCtx;
    const char      *filename="myvideo.mpg";
    av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL);   // 打開視頻文件
    最后三個參數描述了文件格式,緩沖區大小(size)和格式參數;我們通過簡單地指明NULL或0告訴 libavformat 去自動探測文件格式并且使用默認的緩沖區大小。這里的格式參數指的是視頻輸出參數,比如寬高的坐標。

    下一步,我們需要取出包含在文件中的流信息:
    av_find_stream_info(pFormatCtx);                                // 取出流信息

    AVFormatContext 結構體

    dump_format(pFormatCtx, 0, filename, false);//我們可以使用這個函數把獲取到得參數全部輸出。

    for(i=0; i<pFormatCtx->nb_streams; i++)        //區分視頻流和音頻流
     if(pFormatCtx->streams->codec.codec_type==CODEC_TYPE_VIDEO) //找到視頻流,這里也可以換成音頻
        {
            videoStream=i;
            break;
        }

    接下來就需要尋找解碼器

    AVCodec *pCodec;
    pCodec=avcodec_find_decoder(pCodecCtx->codec_id);

    avcodec_open(pCodecCtx, pCodec);    // 打開解碼器
    給視頻幀分配空間以便存儲解碼后的圖片:

    AVFrame *pFrame;
    pFrame=avcodec_alloc_frame();

    /////////////////////////////////////////開始解碼///////////////////////////////////////////

    第一步當然是讀數據:

    我們將要做的是通過讀取包來讀取整個視頻流,然后把它解碼成幀,最后轉換格式并且保存。

    while(av_read_frame(pFormatCtx, &packet)>=0) {     //讀數據

         if(packet.stream_index==videoStream){         //判斷是否視頻流

             avcodec_decode_video(pCodecCtx,pFrame, &frameFinished,

    packet.data, packet.size);                         //解碼

         if(frameFinished) {

    img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24,(AVPicture*)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width,pCodecCtx->height);//轉換   }   

    SaveFrame(pFrameRGB, pCodecCtx->width,pCodecCtx->height, i); //保存數據

    av_free_packet(&packet);                       //釋放

    av_read_frame()讀取一個包并且把它保存到AVPacket結構體中。這些數據可以在后面通過av_free_packet()來釋 放。函數avcodec_decode_video()把包轉換為幀。然而當解碼一個包的時候,我們可能沒有得到我們需要的關于幀的信息。因此,當我們得 到下一幀的時候,avcodec_decode_video()為我們設置了幀結束標志frameFinished。最后,我們使用 img_convert()函數來把幀從原始格式(pCodecCtx->pix_fmt)轉換成為RGB格式。要記住,你可以把一個 AVFrame結構體的指針轉換為AVPicture結構體的指針。最后,我們把幀和高度寬度信息傳遞給我們的SaveFrame函數。

    到此解碼完畢,顯示過程使用SDL完成考慮到我們以后會使用firmware進行顯示操作,SDL忽略不講。

    音視頻同步

    DTS(解碼時間戳)和PTS(顯示時間戳)

    當我們調用av_read_frame()得到一個包的時候,PTS和DTS的信息也會保存在包中。但是我們真正想要的PTS是我們剛剛解碼出來的 原始幀 的PTS,這樣我們才能知道什么時候來顯示它。然而,我們從avcodec_decode_video()函數中得到的幀只是一個AVFrame,其中并 沒有包含有用的PTS值(注意:AVFrame并沒有包含時間戳信息,但當我們等到幀的時候并不是我們想要的樣子)。。我們保存一幀的第一個包的PTS: 這將作為整個這一幀的PTS。我們 可以通過函數avcodec_decode_video()來計算出哪個包是一幀的第一個包。怎樣實現呢?任何時候當一個包開始一幀的時 候,avcodec_decode_video()將調用一個函數來為一幀申請一個緩沖。當然,ffmpeg允許我們重新定義那個分配內存的函數。計算前 一幀和現在這一幀的時間戳來預測出下一個時間戳的時間。同時,我們需要同步視頻到音頻。我們將設置一個音頻時間audioclock;一個內部值記錄了我 們正在播放的音頻的位置。就像從任意的mp3播放器中讀出來的數字一樣。既然我們把視頻同步到音頻,視頻線程使用這個值來算出是否太快還是太慢。

     

     

     

     

    用FFMPEG SDK進行視頻轉碼壓縮時解決音視頻不同步問題的方法(轉)

    ffmpeg 2010-07-21 19:54:16 閱讀163 評論0 

    用FFMPEG SDK進行視頻轉碼壓縮的時候,轉碼成功后去看視頻的內容,發現音視頻是不同步的。這個的確是一個惱火的事情。我在用FFMPEG SDK做h264格式的FLV文件編碼Filter的時候就碰到了這個問題。

            經過研究發現,FFMPEG SDK寫入視頻的時候有兩個地方用來控制寫入的時間戳,一個是AvPacket, 一個是AvFrame。 在調用avcodec_encode_video的時候需要傳入AvFrame的對象指針,也就是傳入一幀未壓縮的視頻進行壓縮處理,AvFrame包含 一個pts的參數,這個參數就是當前幀將來在還原播放的時候的時間戳。而AvPacket里面也有pts,還有dts。說起這個就必須要說明一下 I,P,B三種視頻壓縮幀。I幀就是關鍵幀,不依賴于其他視頻幀,P幀是向前預測的幀,只依賴于前面的視頻幀,而B幀是雙向預測視頻幀,依賴于前后視頻 幀。由于B幀的存在,因為它是雙向的,必須知道前面的視頻幀和后面的視頻幀的詳細內容后,才能知道本B幀最終該呈現什么圖像。而pts和dts兩個參數就 是用來控制視頻幀的顯示和解碼的順序。

          pts就是幀顯示的順序。

          dts就是幀被讀取進行解碼的順序。

         如果沒有B幀存在,dts和pts是相同的。反之,則是不相同的。關于這個的詳細介紹可以參考一下mpeg的原理。

    再說說AvPacket中包含的pts和dts兩個到底該設置什么值?

    pts和dts需要設置的就是視頻幀解碼和顯示的順序。每增加一幀就加一,并不是播放視頻的時間戳。

    但是實踐證明經過rmvb解碼的視頻有時候并不是固定幀率的,而是變幀率的,這樣,如果每壓縮一幀,pts和dts加一的方案為導致音視頻不同步。

    那怎么來解決音視頻同步的問題呢?

    請看如下代碼段。

    lTimeStamp 是通過directshow 獲取的當前的視頻幀的時間戳。

    m_llframe_index為當前已經經過壓縮處理的幀的數量。

    首先av_rescale計算得到當前壓縮處理已經需要處理什么時間戳的視頻幀,如果該時間戳尚未到達directshow當前提供的視頻幀的時間戳,則將該幀丟棄掉。

    否則進行壓縮操作。并設置AVPacket的pts和dts。這里假設B幀不存在。

    因為在將來播放的時候視頻以我們設定的固定播放幀率進行播放,所以需要根據設定的播放幀率計算得到的視頻幀時間戳和directshow提供的當前視頻幀 的時間戳進行比較,設定是否需要進行實施延緩播放的策略。如果需要延緩播放,則將pts增加步長2,否則以普通速度播放,則設置為1.dts與之相同。
    __int64 x =av_rescale(m_llframe_index,AV_TIME_BASE*(int64_t)c->time_base.num,c->time_base.den);

    if( x > lTimeStamp )
    {
    return TRUE;
    }
    m_pVideoFrame2->pts = lTimeStamp;
    m_pVideoFrame2->pict_type = 0;

    int out_size = avcodec_encode_video( c, m_pvideo_outbuf, video_outbuf_size,m_pVideoFrame2 );
    /* if zero size, it means the image was buffered */
    if (out_size > 0)
    {
    AVPacket pkt;
    av_init_packet(&pkt);

    if( x > lTimeStamp )
    {
       pkt.pts = pkt.dts = m_llframe_index;
       pkt.duration = 0;
    }
    else
    {
       pkt.duration = (lTimeStamp - x)*c->time_base.den/1000000 + 1;
       pkt.pts = m_llframe_index;
       pkt.dts = pkt.pts;
       m_llframe_index += pkt.duration;
    }

    //pkt.pts = lTimeStamp * (__int64)frame_rate.den / 1000;
    if( c->coded_frame && c->coded_frame->key_frame )
    {
        pkt.flags |= PKT_FLAG_KEY;
    }

    pkt.stream_index= m_pVideoStream->index;
    pkt.data= m_pvideo_outbuf;
    pkt.size= out_size;

    /* write the compressed frame in the media file */
    ret = av_interleaved_write_frame( m_pAvFormatContext, &pkt );
    }
    else
    {
    ret = 0;
    }

     

    請問avcodec_decode_video解碼的幀為什么后面的比前面的pts小呢?

    請問如下代碼:
    while( av_read_frame(pFormatCtxSource,&packet)>=0 )
    {
        if( packet.stream_index==videoStream )
        {
            int out_size = avcodec_decode_video(pCodecCtxSource,pFrameSource, &bFrameFinished, packet.data, packet.size); // Decode fromsource frame

            if( bFrameFinished )
            {
                pFrameSource->pts =av_rescale_q(packet.pts, pCodecCtxSource->time_base,pStCodec->time_base);
                int out_size =avcodec_encode_video(pStCodec, video_buffer, 200000, pFrameSource); // Encodeto output
                if( out_size>0 )
                {
                    // ...
                }
            }
        }

        av_free_packet(&packet);

    }

    在我Decode的時候,第一幀得到的 pFrameSource->pts 是96,再解第二幀的時候,pFrameSource->pts 計算完后就成了80幾,后幾幀也是比96小,過一會又會解出來一個100多的,接下來又是比100多小的,這是為什么?在Encode的時候,先 Encode一個pts=96的,再去Encode比96小的幀就返回-1了,直到找到一個比96大的。

    另外,我計算pts的方法正確嗎?

     

    答復:

    Because you have B - Frame

    for example:

    the Inputsequence for video encoder
    1  2  3   4   5    6   7
    I   B   B   P  B   B   I

    Let's take1,2,3.. as PTS for simplification

    the out sequencefor video encoder ( this equals the decoder sequence)
    1  4  2    3   7   5   6
    I  P    B   B   I    B   B

    you will get aPTS sequence as following:

    1  4  2  3  7  5  6 

    7  5 6sequence will be same as your question

     

    問:

    哦,那是不是我的pts不能這么算呢?而是要每次+1,對嗎?那么,packet中的pts和dts要用在什么地方呢?我這樣按存儲順序進行解碼的話,顯示之前是不是要自己進行緩存呢?謝謝!

     

    另外,還有個問題,既然解碼的時候,不一定是按照pts遞增的順序得到的解碼后的畫面,那我在編碼圖像的時候,是應該按照解碼出來的幀順序進行編碼嗎?還是把幀先緩存起來,最后嚴格接照圖像的顯示順序來編碼呢?用代碼來表示,就是:
    方法一:
    while(av_read_frame )
    {    
        解碼;   
       pts+1;    
        編碼;   
        輸出;
    }

    方法二:
    while(av_read_frame )
    {
        解碼;
       if( pts<previous )
        {
           緩存;
        }
       else
        {
           編碼緩存的幀并寫入文件;
        }
    }

    這兩個方法,哪個是正確的呢?因為我看到網上的代碼都用的是方法一,但是我覺得方法二是對的呀?

     

    答:

    the output of decoderis the right order for display because I/P frames will be cacheduntil next I/P

     

     

    理解:

     

    Decoder 后output的pts 是按正常的順序,即顯示的順序輸出的,如果有B幀,decoder會緩存。

    但encoder后,輸出的是按dts輸出的。

     

     

    Pts,dts并不是時間戳,而更應該理解為frame的順序序列號。由于每幀frame的幀率并不一定是一致的,可能會變化的。轉換為時間戳的話,應該是(pts*幀率)。為加深理解

    可以將pts比做是第pts幀frame,假設每幀的幀率不變的話,則顯示的時間戳為(pts*幀率),如果考慮幀率變化的,則要想辦法將(pts*當前的幀率)累加到后面。

     

    在tutorial5中在decode 下增加trace后打印情況:

    len1 = avcodec_decode_video(is->video_st->codec,pFrame, &frameFinished,

                          packet->data,packet->size);

          printf("-----------------------------------------------------------------------------\n");

          printf("avcodec_decode_videopacket->pts:%x,packet->dts:%x\n",packet->pts,packet->dts);

          printf("avcodec_decode_videopFrame->pkt_pts:%x,pFrame->pkt_dts:%x,pFrame->pts:%x\n",pFrame->pkt_pts,pFrame->pkt_dts,pFrame->pts);

          if(pFrame->opaque)

               printf("avcodec_decode_video*(uint64_t *)pFrame->opaque:%x\n",*(uint64_t *)pFrame->opaque);

     

    其中播一個mp4文件的打印情況:

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

    avcodec_decode_video packet->pts:1ae,packet->dts:0

    avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

    avcodec_decode_video *(uint64_t *)pFrame->opaque:1ae

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

    avcodec_decode_video packet->pts:1af,packet->dts:0

    avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

    avcodec_decode_video *(uint64_t *)pFrame->opaque:1af

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

    avcodec_decode_video packet->pts:24c,packet->dts:0

    avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

    avcodec_decode_video *(uint64_t *)pFrame->opaque:1ac

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

    avcodec_decode_video packet->pts:24d,packet->dts:0

    avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

    avcodec_decode_video *(uint64_t *)pFrame->opaque:24d

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

    avcodec_decode_video packet->pts:24e,packet->dts:0

    avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

    avcodec_decode_video*(uint64_t *)pFrame->opaque:24e

     

    以下為播放rm文件的情況:

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

    avcodec_decode_videopacket->pts:1831b,packet->dts:0

    avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

    avcodec_decode_video *(uint64_t *)pFrame->opaque:1831b

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

    avcodec_decode_videopacket->pts:18704,packet->dts:0

    avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

    avcodec_decode_video *(uint64_t *)pFrame->opaque:18704

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

    avcodec_decode_videopacket->pts:18aed,packet->dts:0

    avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

    avcodec_decode_video *(uint64_t *)pFrame->opaque:18aed

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

    avcodec_decode_videopacket->pts:18ed6,packet->dts:0

    avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

    avcodec_decode_video *(uint64_t *)pFrame->opaque:18ed6

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

    avcodec_decode_videopacket->pts:192bf,packet->dts:0

    avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

    avcodec_decode_video *(uint64_t *)pFrame->opaque:192bf

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

    avcodec_decode_videopacket->pts:196a8,packet->dts:0

    avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

    avcodec_decode_video *(uint64_t *)pFrame->opaque:196a8

     

     

    可以看出有的pts是+1 累加,有的是加了很多,但都是按順序累加的。當傳人decoder前的packet有pts時,則decoder后獲取的frame將會賦值packet 的pts;當傳人的packet 只是一幀的部分數據或是B幀,由于decoder出來的frame要按正常的pts順序輸出,有可能decoder不會獲取到frame ,或decoder內部會緩存也不會輸出frame,即frame的pts會為空。Frame pts(即opaque) 為空的話則會看frame->dts,dts都沒有的話才認為frame->pts為0.

     

    對于:

    pts *= av_q2d(is->video_st->time_base);////即pts*幀率

     

        // Did we get avideo frame?

       if(frameFinished) {

          pts =synchronize_video(is, pFrame, pts);

    ///// synchronize_video考慮了3中情況:

    1.    pts拿到的話就用該pts

    2.    pts沒有拿到的話就用前一幀的pts時間

    3.    如果該幀要重復顯示,則將顯示的數量*幀率,再加到前面的pts中。

         if(queue_picture(is, pFrame, pts) < 0) {/////傳人decoder后的幀隊列中,以便后續去獲取show。

     

     

    static double synchronize_video(VideoState *is, AVFrame*src_frame, double pts) {

     

      doubleframe_delay;

     

      if(pts != 0) {

        /* if we havepts, set video clock to it */

        is->video_clock = pts;

      } else {

        /* if we aren'tgiven a pts, set it to the clock */

        pts =is->video_clock;

      }

      /* update thevideo clock */

    /////很關鍵:前面傳進來的pts已經是時間戳了,是當前frame開始播放的時間戳,

    /////下面frame_delay是該幀顯示完將要花費的時間,(pts+frame_delay)也即是/////預測的下一幀將要播放的時間戳。

      frame_delay =av_q2d(is->video_st->codec->time_base);

      /* if we arerepeating a frame, adjust clock accordingly */

     

    //////重復多幀的話要累加上

      frame_delay +=src_frame->repeat_pict * (frame_delay * 0.5);

     is->video_clock += frame_delay;

      return pts;/////此時返回的值即為下一幀將要開始顯示的時間戳。

    }

     

     

     

    ///////開定時器去顯示幀隊列中的已經decode過的數據,按前面的分析我們已經知道幀隊列中的數據已經是按pts順序插入到隊列中的。 Timer的作用就是有幀率不一致及重復幀的情況造成時間戳不是線性的,有快有慢,從而tutorial5才有timer的方式來播放:追趕

    以下是一個網友很直觀淺顯的例子解釋:

    ccq(183892517) 17:05:21 if(packet->dts ==AV_NOPTS_VALUE 是不是就是沒有獲取到dts的情況?

    David Cen(3727567) 17:06:44 就是有一把尺子 一只螞蟻跟著一個標桿走  David Cen(3727567) 17:06:58 標桿是勻速的 螞蟻或快或慢 DavidCen(3727567) 17:07:18 慢了你就抽它 讓他跑起來 快了就拽它  David Cen(3727567) 17:07:38 這樣音(標桿)視頻(螞蟻)就能同步了 DavidCen(3727567) 17:08:00 這里最大的問題就是音頻是勻速的 視頻是非線性的

     

    另外:此時vp–>pts獲取到的pts已經轉化為時間戳了,這個時間戳為就是當前幀顯示結束的時間戳,也即是下一幀將顯示的預測時間戳。

    static void video_refresh_timer(void *userdata) {

     

      VideoState *is = (VideoState*)userdata;

      VideoPicture *vp;

      double actual_delay, delay,sync_threshold, ref_clock, diff;

     

      if(is->video_st) {

        if(is->pictq_size == 0) {

          schedule_refresh(is, 1);

        } else {

          vp =&is->pictq[is->pictq_rindex];

     

          delay = vp->pts -is->frame_last_pts; /* the pts from last time */  ////這是當前要顯示的frame和下一 副                                                        //////將要顯示的 frame的間隔時間

          if(delay <= 0 || delay>= 1.0) {

             /* if incorrect delay, useprevious one */

             delay =is->frame_last_delay;

          }

          /* save for next time */

          is->frame_last_delay =delay;

          is->frame_last_pts =vp->pts;

     

          /* update delay to sync toaudio */

          ref_clock = get_audio_clock(is);/////獲取到聲音當前播放的時間戳。

          diff = vp->pts -ref_clock;////// vp->pts實際上是預測的下一幀將要播放的開始時間,

     

    //////////也就是說在diff這段時間中聲音是勻速發生的,但是在delay這段時間frame的顯示可能就會有快//////////慢的區別。

            

     

              

     

          /* Skip or repeat the frame.Take delay into account

              FFPlay still doesn't "know if this is thebest guess." */

          sync_threshold = (delay >AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD;

            

          if(fabs(diff) < AV_NOSYNC_THRESHOLD) {

             if(diff <=-sync_threshold) {

               delay = 0;//////下一幀畫面顯示的時間和當前的聲音很近的話加快顯示下一幀(即后面video_display顯示完當前幀后開啟定時器很快去顯示下一幀)

             } else if(diff >=sync_threshold) {

               delay = 2 * delay;//////下一幀開始顯示的時間和當前聲音的時間隔的比較長則延緩,即兩幀畫面間話的顯示的時間長度大于兩幀畫面間的聲音播放的時間,則我 們將兩幀畫顯示的時候加倍拖長點,比如幀1和幀2的時間顯示間隔為40ms,但幀1和幀2的聲音播放時間為55ms,怎么辦呢?我們不可能去打亂聲音的質 量的,則我們采用的方法是:將兩幀畫面的播放間隔加大,本來是過30ms就要開始播下一幀的,我們改成60ms后才播下一幀。

             }

          }/////

    ////當然如果diff大于AV_NOSYNC_THRESHOLD,即快進的模式了,畫面跳動太大,不存在音視頻同步的問題了。

     

          is->frame_timer += delay;

          /* computer the REAL delay*/

          actual_delay =is->frame_timer - (av_gettime() / 1000000.0);

          if(actual_delay < 0.010){

             /* Really it should skipthe picture instead */

             actual_delay = 0.010;

          }

          schedule_refresh(is,(int)(actual_delay * 1000 + 0.5));////開定時器去顯示下一幀

          /* show the picture! */

          video_display(is);////立馬顯示當前幀

         

          /* update queue for nextpicture! */

          if(++is->pictq_rindex ==VIDEO_PICTURE_QUEUE_SIZE) {

             is->pictq_rindex = 0;

          }

         SDL_LockMutex(is->pictq_mutex);

          is->pictq_size--;

         SDL_CondSignal(is->pictq_cond);

         SDL_UnlockMutex(is->pictq_mutex);

        }

      } else {

        schedule_refresh(is, 100);

      }

    RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
    最近免费观看高清韩国日本大全