2016-07-28 37 views
0

Qt WidgetアプリケーションでIPカメラストリームを取得しようとしています。まず、IPカメラのUDPポートに接続します。 IPカメラはH.264エンコードされたビデオをストリーミングしています。ソケットがバインドされた後、各readyRead()シグナル上で、フルフレームを取得するために受信したデータグラムでバッファを満たしています。FFmpegライブラリを使用したQt - H.264ビデオストリーミング

変数の初期化:次のように

AVCodec *codec; 
AVCodecContext *codecCtx; 
AVFrame *frame; 
AVPacket packet; 
this->buffer.clear(); 
this->socket = new QUdpSocket(this); 

QObject::connect(this->socket, &QUdpSocket::connected, this, &H264VideoStreamer::connected); 
QObject::connect(this->socket, &QUdpSocket::disconnected, this, &H264VideoStreamer::disconnected); 
QObject::connect(this->socket, &QUdpSocket::readyRead, this, &H264VideoStreamer::readyRead); 
QObject::connect(this->socket, &QUdpSocket::hostFound, this, &H264VideoStreamer::hostFound); 
QObject::connect(this->socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error(QAbstractSocket::SocketError))); 
QObject::connect(this->socket, &QUdpSocket::stateChanged, this, &H264VideoStreamer::stateChanged); 

avcodec_register_all(); 

codec = avcodec_find_decoder(AV_CODEC_ID_H264); 
if (!codec){ 
    qDebug() << "Codec not found"; 
    return; 
} 

codecCtx = avcodec_alloc_context3(codec); 
if (!codecCtx){ 
    qDebug() << "Could not allocate video codec context"; 
    return; 
} 

if (codec->capabilities & CODEC_CAP_TRUNCATED) 
     codecCtx->flags |= CODEC_FLAG_TRUNCATED; 

codecCtx->flags2 |= CODEC_FLAG2_CHUNKS; 

AVDictionary *dictionary = nullptr; 

if (avcodec_open2(codecCtx, codec, &dictionary) < 0) { 
    qDebug() << "Could not open codec"; 
    return; 
} 

アルゴリズムである:

void H264VideoImageProvider::readyRead() { 
QByteArray datagram; 
datagram.resize(this->socket->pendingDatagramSize()); 
QHostAddress sender; 
quint16 senderPort; 

this->socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); 

QByteArray rtpHeader = datagram.left(12); 
datagram.remove(0, 12); 

int nal_unit_type = datagram[0] & 0x1F; 
bool start = (datagram[1] & 0x80) != 0; 

int seqNo = rtpHeader[3] & 0xFF; 

qDebug() << "H264 video decoder::readyRead()" 
     << "from: " << sender.toString() << ":" << QString::number(senderPort) 
     << "\n\tDatagram size: " << QString::number(datagram.size()) 
     << "\n\tH264 RTP header (hex): " << rtpHeader.toHex() 
     << "\n\tH264 VIDEO data (hex): " << datagram.toHex(); 

qDebug() << "nal_unit_type = " << nal_unit_type << " - " << getNalUnitTypeStr(nal_unit_type); 
if (start) 
    qDebug() << "START"; 

if (nal_unit_type == 7){ 
    this->sps = datagram; 
    qDebug() << "Sequence parameter found = " << this->sps.toHex(); 
    return; 
} else if (nal_unit_type == 8){ 
    this->pps = datagram; 
    qDebug() << "Picture parameter found = " << this->pps.toHex(); 
    return; 
} 

//VIDEO_FRAME 
if (start){ 
    if (!this->buffer.isEmpty()) 
     decodeBuf(); 

    this->buffer.clear(); 
    qDebug() << "Initializing new buffer..."; 

    this->buffer.append(char(0x00)); 
    this->buffer.append(char(0x00)); 
    this->buffer.append(char(0x00)); 
    this->buffer.append(char(0x01)); 

    this->buffer.append(this->sps); 

    this->buffer.append(char(0x00)); 
    this->buffer.append(char(0x00)); 
    this->buffer.append(char(0x00)); 
    this->buffer.append(char(0x01)); 

    this->buffer.append(this->pps); 

    this->buffer.append(char(0x00)); 
    this->buffer.append(char(0x00)); 
    this->buffer.append(char(0x00)); 
    this->buffer.append(char(0x01)); 
} 

qDebug() << "Appending buffer data..."; 
this->buffer.append(datagram); 
} 
  • データグラムの最初の12のバイトは、RTPヘッダである他の
  • すべてがVIDEO DATAは
  • 最後の5ビットであります最初のVIDEO DATAバイトのうち、どのNALユニットタイプであるかを示します。私は常に次の4つの値の1つを取得します(IDRスライス、5コードIDRスライス、7 SPS、8 PPS)。
  • 2番目のVIDEO DATAバイトの5番目のビットは、このデータグラムがフレーム内のSTARTデータの場合に表示されます。
  • 新しいフレームが到着した後、すべてのビデオ・データは、START
  • 始まるバッファに格納されている - STARTが設定され、それはデコードされ、新しいバッファは、復号のため
  • フレームは、このように生成されて生成される:

    SPS

    PPS

    連結VIDEO DATA

  • 復号はFFmpegのライブラリーからavcodec_decode_video2()関数を用いて行われる

    void H264VideoStreamer::decode() { 
    
    av_init_packet(&packet); 
    
    av_new_packet(&packet, this->buffer.size()); 
    memcpy(packet.data, this->buffer.data_ptr(), this->buffer.size()); 
    packet.size = this->buffer.size();  
    
    frame = av_frame_alloc(); 
    if(!frame){ 
        qDebug() << "Could not allocate video frame"; 
        return; 
    } 
    
    int got_frame = 1; 
    
    int len = avcodec_decode_video2(codecCtx, frame, &got_frame, &packet); 
    
    if (len < 0){ 
        qDebug() << "Error while encoding frame."; 
        return; 
    } 
    
    //if(got_frame > 0){ // got_frame is always 0 
    // qDebug() << "Data decoded: " << frame->data[0]; 
    //} 
    
    char * frameData = (char *) frame->data[0]; 
    QByteArray decodedFrame; 
    decodedFrame.setRawData(frameData, len); 
    
    qDebug() << "Data decoded: " << decodedFrame; 
    
    av_frame_unref(frame); 
    av_free_packet(&packet); 
    
    emit imageReceived(decodedFrame); 
    } 
    

私のアイデアは、imageReceivedシグナルを受け取って、QImageで直接decodedFrameを変換し、新しいフレームがデコードされてUIに送られた後にリフレッシュするUIスレッドです。

H.264ストリームをデコードするためのこの良いアプローチはありますか?私は次の問題に直面しています:

  • avcodec_decode_video2()は、符号化されたバッファサイズと同じ値を返します。エンコードされデコードされた日付は常に同じサイズである可能性はありますか?
  • got_frameは常に0なので、実際に結果にフルフレームを受け取ったことはありません。理由は何でしょうか?ビデオフレームが正しく作成されていませんか?またはビデオフレームがQByteArrayからAVframeに間違って変換されましたか?
  • デコードされたAVフレームをQByteArrayに戻すにはどうすればできますか?単にQImageに変換するだけですか?

答えて

1

フレームを手動でレンダリングするプロセス全体を別のライブラリに任せることができます。唯一の目的がIPカメラからのライブフィードを使用するQt GUIであれば、libvlcライブラリを使用することができます。ここでは例を見つけることができます:https://wiki.videolan.org/LibVLC_SampleCode_Qt

+0

私はFFmpegライブラリに固執したいと思います。 libvlcでudpストリームを取得することは可能ですか? – franz

+1

わかりません。私は貼り付けられたリンクのコメントに基づいてあなたができると思います。あなたはVLCクライアントを開いて、メディア - >ネットワークストリームを開いてそこにあなたのリンクを貼り付けるならあなた自身をチェックすることができます。ストリームが開始された場合は、おそらくlibvlcでもそれを行うことができます。 –

+0

VLCはlibVLCをベースにしているので意味があります。さて、答えのためにこれまでのおかげで、私はFFmpegでストリームを取得しないで、まだ答えを待っている場合、これは私のバックアップ計画になります。 – franz

関連する問題