1. 程式人生 > >ffmpeg解碼RTSP/TCP視訊流H.264(QT介面顯示視訊畫面)

ffmpeg解碼RTSP/TCP視訊流H.264(QT介面顯示視訊畫面)

我用的ffmpeg版本為 ffmpeg-2.1.8.tar.bz2
版本低了恐怕有些標頭檔案和API找不到。
在Linux下解壓後編譯,Linux下編譯很簡單,我這裡生成的動態庫:
./configure –enable-shared
make
就能找到各個so動態庫檔案。
移動位置後,記得手動連結 一下:

ln -s libavcodec.so.55 libavcodec.so
ln -s libavdevice.so.55 libavdevice.so
ln -s libavfilter.so.3 libavfilter.so
ln -s libavformat.so.55 libavformat.so
ln -s libavutil.so.52 libavutil.so ln -s libswscale.so.2 libswscale.so

QT pro檔案中記得加入:
INCLUDEPATH += ffmpeg/include
// windows下用這幾個
win32: LIBS += ffmpeg/lib/libavcodec.dll.a ffmpeg/lib/libavfilter.dll.a ffmpeg/lib/libavformat.dll.a ffmpeg/lib/libswscale.dll.a ffmpeg/lib/libavutil.dll.a
// Linux下用這幾個
LIBS += -lavcodec -lavdevice -lavfilter -lavformat -lavutil -lswscale -L./ffmpeg/so

我這裡對外提供三個介面:

void VideoStream::setUrl(QString url)
{
    m_str_url = url;
}

void VideoStream::startStream()
{
    videoStreamIndex=-1;
    av_register_all();//註冊庫中所有可用的檔案格式和解碼器
    avformat_network_init();//初始化網路流格式,使用RTSP網路流時必須先執行
    pAVFormatContext = avformat_alloc_context();//申請一個AVFormatContext結構的記憶體,並進行簡單初始化
pAVFrame=av_frame_alloc(); if (this->Init()) { m_timerPlay->start(); } } void VideoStream::stopStream() { m_timerPlay->stop(); avformat_free_context(pAVFormatContext); av_frame_free(&pAVFrame); sws_freeContext(pSwsContext); }

裡面與ffmpeg解碼相關的私有變數:

    QMutex mutex;
    AVPicture  pAVPicture;
    AVFormatContext *pAVFormatContext;
    AVCodecContext *pAVCodecContext;
    AVFrame *pAVFrame;
    SwsContext * pSwsContext;
    AVPacket pAVPacket;

在QT裡,用一個QLabel的物件來顯示解碼後的視訊畫面:

connect(this,SIGNAL(GetImage(QImage)),this,SLOT(SetImageSlots(QImage)));

...
void VideoStream::SetImageSlots(const QImage &image)
{
    if (image.height()>0){
        QPixmap pix = QPixmap::fromImage(image.scaled(m_i_w,m_i_h));
        m_label->setPixmap(pix);
    }
}

這裡用的QTimer來進行一幀幀資料的解碼,也可以用一個執行緒比如QThread來進行解碼:

    m_timerPlay = new QTimer;
    m_timerPlay->setInterval(10);
    connect(m_timerPlay,SIGNAL(timeout()),this,SLOT(playSlots()));

    m_i_frameFinished = 0;

... 
bool VideoStream::Init()
{
    if(m_str_url.isEmpty())
        return false;
    //開啟視訊流
    int result=avformat_open_input(&pAVFormatContext, m_str_url.toStdString().c_str(),NULL,NULL);
    if (result<0){
        qDebug()<<"開啟視訊流失敗";
        return false;
    }

    //獲取視訊流資訊
    result=avformat_find_stream_info(pAVFormatContext,NULL);
    if (result<0){
        qDebug()<<"獲取視訊流資訊失敗";
        return false;
    }

    //獲取視訊流索引
    videoStreamIndex = -1;
    for (uint i = 0; i < pAVFormatContext->nb_streams; i++) {
        if (pAVFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStreamIndex = i;
            break;
        }
    }

    if (videoStreamIndex==-1){
        qDebug()<<"獲取視訊流索引失敗";
        return false;
    }

    //獲取視訊流的解析度大小
    pAVCodecContext = pAVFormatContext->streams[videoStreamIndex]->codec;
    videoWidth=pAVCodecContext->width;
    videoHeight=pAVCodecContext->height;

    avpicture_alloc(&pAVPicture,PIX_FMT_RGB24,videoWidth,videoHeight);

    AVCodec *pAVCodec;

    //獲取視訊流解碼器
    pAVCodec = avcodec_find_decoder(pAVCodecContext->codec_id);
    pSwsContext = sws_getContext(videoWidth,videoHeight,PIX_FMT_YUV420P,videoWidth,videoHeight,PIX_FMT_RGB24,SWS_BICUBIC,0,0,0);

    //開啟對應解碼器
    result=avcodec_open2(pAVCodecContext,pAVCodec,NULL);
    if (result<0){
        qDebug()<<"開啟解碼器失敗";
        return false;
    }

    qDebug()<<"初始化視訊流成功";
    return true;
}

void VideoStream::playSlots()
{
    //一幀一幀讀取視訊
    if (av_read_frame(pAVFormatContext, &pAVPacket) >= 0){
        if(pAVPacket.stream_index==videoStreamIndex){
            qDebug()<<"開始解碼"<<QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
            avcodec_decode_video2(pAVCodecContext, pAVFrame, &m_i_frameFinished, &pAVPacket);
            if (m_i_frameFinished){
                mutex.lock();
                sws_scale(pSwsContext,(const uint8_t* const *)pAVFrame->data,pAVFrame->linesize,0,videoHeight,pAVPicture.data,pAVPicture.linesize);
                //傳送獲取一幀影象訊號
                QImage image(pAVPicture.data[0],videoWidth,videoHeight,QImage::Format_RGB888);
                emit GetImage(image);
                mutex.unlock();
            }
        }
    }
    av_free_packet(&pAVPacket);//釋放資源,否則記憶體會一直上升
}

備註:
標頭檔案包含及注意事項

//必須加以下內容,否則編譯不能通過,為了相容C和C99標準

#ifndef INT64_C
#define INT64_C
#define UINT64_C
#endif

//引入ffmpeg標頭檔案
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
#include <libswscale/swscale.h>
#include <libavutil/frame.h>
}

啟發:
setUrl(QString url);
這裡的url 一般情況下是一個RTSP流的播放地址,如rtsp://192.168.1.123:554/stream1
但也可以是一個TCP流。
我這邊測試的是一個本地的socket流,設url地址為 http://127.0.0.1:5858
可直接解碼播放。