2016-07-28 11 views
0

Я пытаюсь получить поток IP-камеры в приложении Qt Widget. Во-первых, я подключаюсь к UDP-порту IP-камеры. IP-камера передает потоковое видео H.264. После того, как сокет связывается, на каждом сигнале readyRead() я заполняю буфер с полученными дейтаграммами, чтобы получить полный кадр.Qt - потоковая передача видео H.264 с использованием библиотек FFmpeg

инициализации переменной:

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 байт дейтаграммы Header RTP
  • все другое VIDEO DATA
  • последние 5 бит первого байта VIDEO DATA, говорит, какой тип блока NAL он есть. Я всегда получаю один из следующих 4 значений (1 - кодированный фрагмент без IDR, 5 фрагментов кода IDR, 7 SPS, 8 PPS)
  • 5-й бит во втором байте данных VIDEO DATA говорит, что если эта датаграмма является данными START в кадре
  • все видеоданная сохраняются в буфере, начиная с с
  • раз новый кадр поступает - с установлены, он декодируется и новый буфер генерируются
  • кадров для декодирования генерируются следующим образом:

    SPS

    ПФС

    каскадного ВИДЕО ДАННЫХ

  • декодирование производится с использованием avcodec_decode_video2() из библиотеки FFmpeg

    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, конвертирует decodedFrame непосредственно в QImage и обновляет его, как только новый кадр декодируется и отправляется в пользовательский интерфейс.

Это хороший подход для декодирования потока H.264? У меня возникают следующие проблемы:

  • avcodec_decode_video2() возвращает значение, подобное размеру закодированного буфера. Возможно ли, что кодированная и декодированная дата всегда одинакового размера?
  • got_frame всегда 0, поэтому это означает, что я никогда не получал полный кадр в результате. В чем причина? Неправильно создана видеокадра? Или видеокадр неправильно преобразован из QByteArray в AVframe?
  • Как преобразовать декодированный AVframe обратно в QByteArray и может ли он просто быть преобразован в QImage?

ответ

1

Весь процесс ручного рендеринга кадров можно оставить в другой библиотеке. Если единственная цель - графический интерфейс Qt с живой подписью с IP-камеры, вы можете использовать библиотеку libvlc.Вы можете найти здесь пример: https://wiki.videolan.org/LibVLC_SampleCode_Qt

+0

Спасибо за предложение, но я хотел бы придерживаться библиотеки FFmpeg. Можно ли получить поток udp с libvlc? – franz

+1

Я не уверен. Я думаю, вы можете, основываясь на комментариях в ссылке i, вставленной. Вы можете проверить себя, если вы откроете клиент VLC и перейдете в Media-> Open network stream и вставьте туда свою ссылку. Если поток начинается, вы можете, вероятно, сделать это и с libvlc. –

+0

Да, это имеет смысл, поскольку VLC основан на libVLC. Хорошо, спасибо за ответы, это будет мой план резервного копирования, если мне не удастся получить поток с FFmpeg, все еще ожидая ответа. – franz