FFMPEG Qt視頻播放器


  • 本文是根據PaintEvent事件處理函數不停在組件中繪制視頻幀數據

做過圖像界面開發的都知道,任何耗時的操作都不能放在主線程進行,一旦主線程阻塞了,那么體現出來的就是界面卡了。 而我們讀取視頻和解碼視頻是一個非常耗時的操作,因此需要另外開辟一個線程來專門做這件事

項目准備工作:

  1. 安裝和配置Qt;
  2. ffmpeg配置
    具體步驟讀者可以直接百度搜索;

項目具體步驟如下:

  1. ffmpeg解碼視頻文件得到的yuv數據
  2. 將yuv數據轉為RGB數據
  3. 用QImage加載RGB數據
  4. 傳輸至組件
  5. PaintEvent函數繪制圖像

項目代碼實現

  • 新建一個線程類,繼承自QThread
class VideoPlayer : public QThread
  • 在新建的線程類中重載其run函數,把解碼耗時操作全部都在run函數里面執行。
    解碼具體流程如下(引用雷博士博客中的圖片,致敬,連接:https://blog.csdn.net/leixiaohua1020/article/details/8652605)
    視頻解碼就是要到視頻文件中尋找視頻流,找到后對流逐幀解碼

代碼如下所示

    //1  初始化FFMPEG 
    av_register_all(); //調用了這個才能正常適用編碼器和解碼器
    //2  分配AVFormatContext
    AVFormatContext *pFormatCtx = avformat_alloc_context();//FFMPEG所有的操作都要通過這個AVFormatContext來進行
    char *file_path = "join.avi";
    //3. 打開視頻文件  
    if (avformat_open_input(&pFormatCtx, file_path, NULL, NULL) != 0) {//打開視頻文件
        qDebug()<<"can't open the file.             ";
        return ;    }
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {//因為視頻文件比較復雜所以打開后還需要讀取詳細的流信息
        qDebug()<<"Could't find stream infomation.";
        return ;
    }
    //    4.  找文件中的視頻流
    ///循環查找視頻中包含的流信息,直到找到視頻類型的流   
    ///便將其記錄下來保存到videoStream變量中
    ///這里我們現在只處理視頻流 音頻流先不管他
    int videoStream=-1;
    qDebug()<<QObject::tr("視頻流個數:")<<pFormatCtx->nb_streams;
    for (int i = 0; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStream = i;
        }
    }
    ///如果videoStream為說明沒有找到視頻流
    if (videoStream == -1) {
        qDebug()<<"Didn't find a video stream.";
        return  ;
    }
    //5   根據視頻流 打開一個解碼器來解碼:
    ///查找解碼器
    AVCodecContext *pCodecCtx;                              
    AVCodec *pCodec;
    pCodecCtx = pFormatCtx->streams[videoStream]->codec;
    //5.1 找到視頻流相對應的解碼器
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if (pCodec == NULL) {
        qDebug()<<"Codec not found.        ";
        return ;
    }
    ///5.2  打開解碼器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        qDebug()<<"Could not open codec.";
        return  ;}
    //6  讀取視頻
    AVFrame *pFrame, *pFrameRGB;
    int numBytes; uint8_t *out_buffer;
    pFrame = av_frame_alloc();
    pFrameRGB = av_frame_alloc();
    static struct SwsContext *img_convert_ctx;
    //將解碼后的YUV數據轉換成RGB32    
    img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
        pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
        AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);
    numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);
    out_buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
    avpicture_fill((AVPicture *) pFrameRGB, out_buffer, AV_PIX_FMT_RGB32,
        pCodecCtx->width, pCodecCtx->height);
    int y_size = pCodecCtx->width * pCodecCtx->height;
    AVPacket *packet = (AVPacket *) malloc(sizeof(AVPacket)); //6.1 分配一個packet  AVPacket是存儲壓縮編碼數據相關信息的一個結構體。
    av_new_packet(packet, y_size); //分配packet的數據
    av_dump_format(pFormatCtx, 0, file_path, 0); //輸出視頻信息
    int index = 0;
    int ret, got_picture;

while (1)
    {
        if (av_read_frame(pFormatCtx, packet) < 0)
        {
            break; //這里認為視頻讀取完了
        }
        if (packet->stream_index == videoStream) {
            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);//7.解碼
            if (ret < 0) {
                qDebug()<<"decode error.";
                return ;
            }
            if (got_picture) {//8.  YUV420數據>RGB
                sws_scale(img_convert_ctx,
                    (uint8_t const * const *) pFrame->data,
                    pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
                    pFrameRGB->linesize);
             
            }
        }
        av_free_packet(packet);
         msleep(40); //延時ms使其幀率=1000/40
    }
    
  • 發送QImage數據到界面
    由於我們不能夠在子線程中操作界面,(操作界面只能在主線程中進行,幾乎所有的圖形界面開發都是這樣設定),因此我們只能給主線程發送信號,信號帶上這個QIMage,讓主線程幫忙把這個圖像顯示出來。所以需要在 YUV420數據>RGB 后加入以下代碼
 //把這個RGB數據用QImage加載               
                QImage tmpImg((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);
                QImage image = tmpImg.copy(); //把圖像復制一份傳遞給界面顯示
                emit sig_GetOneFrame(image);  //發送信號
  • 槽函數調用update函數,自動調事件處理函數
void CameraClient::slotGetOneFrame(QImage img){
    mImage = img;
    update(); //調用update將執行paintEvent函數
}
  • 事件處理函數繪制
void CameraClient::paintEvent(QPaintEvent *event){
    QPainter painter(this);
    painter.setBrush(Qt::black);
    painter.drawRect(0, 0, this->width(), this->height()); //先畫成黑色
    if (mImage.size().width() <= 0) return;
    ///將圖像按比例縮放成和窗口一樣大小
    QImage img = mImage.scaled(this->size(),Qt::KeepAspectRatio);
    int x = this->width() - img.width();
    int y = this->height() - img.height();
    x /= 2;
    y /= 2;
    painter.drawImage(QPoint(x,y),img); //畫出圖像
}

這樣就完成了簡單的視頻顯示

完整代碼:
https://download.csdn.net/download/a18796007675/10454097
參考:http://blog.yundiantech.com/?log=blog&id=9


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM