Solved Streaming audio file to a network camera
-
Hi, I have a Doorbird doorbell and I am trying to use its API to play a sound. The request should look like this:
POST /bha-api/audio-transmit.cgi HTTP/1.0\r\n Content-Type: audio/basic\r\n Content-Length: 9999999\r\n Connection: Keep-Alive\r\n Cache-Control: no-cache\r\n \r\n <AUDIO DATA> <AUDIO DATA> <AUDIO DATA> ...
I am sending the sound file using QNetworkAccessManager like this:
QNetworkRequest request(m_url); QFile *sound = new QFile(":/resources/test.wav"); if(!sound->open(QIODevice::ReadOnly)) { } QByteArray bytes = sound->readAll(); request.setHeader(QNetworkRequest::ContentTypeHeader, "audio/basic"); request.setHeader(QNetworkRequest::ContentLengthHeader, "9999999"); request.setRawHeader("Connection", "Keep-Alive"); request.setRawHeader("Cache-Control", "no-cache"); QNetworkReply *reply = m_networkManager->post(request, bytes); sound->setParent(reply); connect(reply, &QNetworkReply::finished, this, [=]() { if (reply->error() != QNetworkReply::NoError) { qDebug() << "Error: " + QString(reply->errorString()); } QVariant statusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ); qDebug() << "Status code: " + statusCode.toString(); });
There is no error and the returned status code is 200. The problem is I can hear only a very quick sound lasting less than a second.
I think the problem lies in the fact that I am uploading the whole file at once while it is expected to be streamed in real-time (it should be a voice transmission).
How do I stream the file as if it was live voice recording instead of uploading it all at once?
-
Hi @Jansi said in Streaming audio file to a network camera:
I think the problem lies in the fact that I am uploading the whole file at once while it is expected to be streamed in real-time (it should be a voice transmission).
Might be, but the API clearly states:
Codec: When using this API audio MUST be G.711 μ-law (sampling rate 8000Hz)
So if you have a file with the correct codec and sample rate (and small enough to be transmitted at once), it will be played in real time, as there is no other way as to play 8000 samples per second.
The document also gave the hint to use Wireshark for debugging, maybe use some official software first and see how data is transmitted?
Regards
How do I stream the file as if it was live voice recording instead of uploading it all at once?
-
Hi @Jansi said in Streaming audio file to a network camera:
I think the problem lies in the fact that I am uploading the whole file at once while it is expected to be streamed in real-time (it should be a voice transmission).
Might be, but the API clearly states:
Codec: When using this API audio MUST be G.711 μ-law (sampling rate 8000Hz)
So if you have a file with the correct codec and sample rate (and small enough to be transmitted at once), it will be played in real time, as there is no other way as to play 8000 samples per second.
The document also gave the hint to use Wireshark for debugging, maybe use some official software first and see how data is transmitted?
Regards
How do I stream the file as if it was live voice recording instead of uploading it all at once?
-
@Jansi said in Streaming audio file to a network camera:
... QFile *sound = new QFile(":/resources/test.wav"); if(!sound->open(QIODevice::ReadOnly)) { } QByteArray bytes = sound->readAll(); ...
In addition to the good suggestions from @aha_1980 your code is actually going on even if the sound file cannot be opened, your check is flawed since you're doing nothing in case of open error...
-
@Pablo-J-Rogina said in Streaming audio file to a network camera:
QFile *sound
No need to create it on the heap (and then forgot to delete it) :)
-
@Christian-Ehrlicher yeah, I've just copied/pasted OP code snippet just with focus on the open() check but missed that one, good catch.
-
@aha_1980 said in Streaming audio file to a network camera:
Codec: When using this API audio MUST be G.711 μ-law (sampling rate 8000Hz)
I recorded a message using Audacity, saved it to a WAV and then recoded the file using ffmpeg like this:
ffmpeg -i record.wav -codec:a pcm_mulaw -ac 1 -ar 8000 test.wav
, so it should be the correct format and it's only 7,3 kilobytes:$ file test.wav test.wav: RIFF (little-endian) data, WAVE audio, ITU G.711 mu-law, mono 8000 Hz
One thing I noticed is that Qt overwrites the Content-Lenght to the "correct" size of the file instead of 99999999 that I set manually. It also sets other headers, so maybe that's the issue.
@Pablo-J-Rogina said in Streaming audio file to a network camera:
your check is flawed since you're doing nothing in case of open error...
I just omitted that part to make the code shorter.
-
OK, so I used QTcpSocket and it sort of works now. After a lot of trial and error, I found that if I write the headers, then flush, then write the sound bytes and then sleep for one second, the sound will be played. If I remove either the flush() or the sleep() call then the sound is not played.
void DoorbirdClient::doConnect() { socket = new QTcpSocket(this); connect(socket, SIGNAL(connected()), this, SLOT(connected())); connect(socket, SIGNAL(disconnected()), this, SLOT(disconnected())); connect(socket, SIGNAL(bytesWritten(qint64)), this, SLOT(bytesWritten(qint64))); connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead())); socket->connectToHost(m_url.host(), 80); if(!socket->waitForConnected(5000)) { qDebug() << "Error: " << socket->errorString(); } } void DoorbirdClient::connected() { const char* postHeader = QString("POST /bha-api/audio-transmit.cgi?sessionid=" + m_securityToken + " HTTP/1.0\r\n").toUtf8().constData(); socket->write(postHeader); socket->write("Content-Type: audio/basic\r\n"); socket->write("Content-Length: 9999999\r\n"); socket->write("Connection: Keep-Alive\r\n"); socket->write("Cache-Control: no-cache\r\n"); socket->write("\r\n"); QFile sound(":/resources/test.wav"); if(!sound.open(QIODevice::ReadOnly)) { qDebug() << "Sound file could not be opened"; socket->disconnectFromHost(); return; } QByteArray bytes = sound.readAll(); qDebug() << "Sending sound bytes"; socket->flush(); socket->write(bytes); sleep(1); }
I highly doubt that using sleep() while writing to a socket is the proper solution though...