Я пытаюсь получить поток 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?
Спасибо за предложение, но я хотел бы придерживаться библиотеки FFmpeg. Можно ли получить поток udp с libvlc? – franz
Я не уверен. Я думаю, вы можете, основываясь на комментариях в ссылке i, вставленной. Вы можете проверить себя, если вы откроете клиент VLC и перейдете в Media-> Open network stream и вставьте туда свою ссылку. Если поток начинается, вы можете, вероятно, сделать это и с libvlc. –
Да, это имеет смысл, поскольку VLC основан на libVLC. Хорошо, спасибо за ответы, это будет мой план резервного копирования, если мне не удастся получить поток с FFmpeg, все еще ожидая ответа. – franz