如何在Qt中通过以太网播放流式音频?

9

我的目标是在局域网网络上传输*.wav文件,要求没有延迟或最小化延迟。

此外,我们通过部分读取服务器上的文件,以320字节为一部分。之后,我们通过UDP发送数据包,并将接收数据写入抖动缓冲区。抖动缓冲区的大小为10。 为保证声音清晰,应该设置哪些定时器延迟?

以下是发送方:

void MainWindow::on_start_tx_triggered()
{
    timer1 = new QTimer (this);
    udpSocketout = new QUdpSocket(this);
    qDebug()<<"Start";
    for (int i = 0; i < playlist.size(); ++i)
    {
        inputFile.setFileName(playlist.at(i));
        qDebug()<<inputFile.fileName();
        if (!inputFile.open(QIODevice::ReadOnly))
        {
            qDebug()<< "file not found;";
        }
    }
    connect(timer1, SIGNAL(timeout()), this, SLOT(writeDatagrams()));
    timer1->start(5);
}

void MainWindow::writeDatagrams()
{
    if(!inputFile.atEnd()){
        payloadout = inputFile.read(320);
    }
    qDebug()<<payloadout;
    QDataStream out(&datagramout, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_4_7);
    out << qint64(0);
    out << payloadout;
    out.device()->seek(qint64(0));
    out << qint64(datagramout.size() - sizeof(qint64));
    qint64 writtenBytes = udpSocketout->writeDatagram(datagramout, remoteHOST, remotePORT);
    qDebug() << "Sent " << writtenBytes << " bytes.";
}

这里是接收器和播放器:
void MainWindow::on_start_rx_triggered()
{
    udpSocketin = new QUdpSocket(this);
    udpSocketin->bind(localHOST, localPORT);
    connect(udpSocketin, SIGNAL(readyRead()),
            this, SLOT(readDatagrams()));
    QDataStream out(&datagramout, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_4_7);
    timer2 =  new QTimer (this);
    connect(timer2, SIGNAL(timeout()), this, SLOT(playbuff()));
    timer2->start(50);
    audioout = new QAudioOutput(format, this);
}

void MainWindow::readDatagrams()
{
    datagramin.resize(udpSocketin->pendingDatagramSize());
    qint64 receiveBytes = udpSocketin->readDatagram(datagramin.data(), datagramin.size());
    qDebug() << "Receive " << receiveBytes << " bytes.";
    QDataStream in(&datagramin, QIODevice::ReadOnly);
    in.setVersion(QDataStream::Qt_4_7);
    quint64 size = 0;
    if(in.device()->size() > sizeof(quint64))
    {
        in >> size;
    }
    else
        return;
    if(in.device()->size() < size)
        return;
    in >> payloadin;
    qDebug() << payloadin.size();
    emit jitterbuff();
}

void MainWindow::jitterbuff()
{
    if (buff_pos < SIZE_OF_BUF)
    {
        QDataStream out(&buffered, QIODevice::WriteOnly);
        out.setVersion(QDataStream::Qt_4_7);
        out << payloadin;
        buff_pos++;
    }
    else
        buff_pos = 0;
}

void MainWindow::playbuff()
{
    qDebug() << "YES!!!";
    buffer = new QBuffer(&buffered);
    buffer->open(QIODevice::ReadOnly);
    audioout->start(buffer);
    QEventLoop loop;
    QTimer::singleShot(50, &loop, SLOT(quit()));
    loop.exec();
    buffer->close();
}

我没有检查你的代码,因为我不熟悉Qt,但是关于通过局域网发送音频的一些一般性评论:1.尽管数据包丢失很少,但仍应准备好偶尔发生的数据包丢失。2.我对局域网没有太多经验,但我猜测延迟可能会在100毫秒或更低。 - Bjorn Roche
100毫秒对于流媒体音频来说延迟太高了... 是的,我准备好了应对数据包丢失,因为我使用UDP。但我想要获得清晰的输出声音。我使用缓冲数据(例如在last.fm上这样做)。 - HoBBiT
另外,我如何理解如果我使用: 采样率= 8000 kHz 采样大小= 8位 并通过320字节从文件中读取,则我在数据中有5毫秒的声音。 例如,抖动缓冲区的大小为10个单元,则我需要5 * 10毫秒的延迟用于抖动缓冲区+ 5毫秒的分组化。结果,我将总延迟为55毫秒。这不是吗? 如果有错误,请告诉我。 - HoBBiT
你说“100毫秒对于流媒体音频来说延迟太高了...”,这取决于你的应用程序:对于语音/电话,不是。对于音乐,可能是。对于视频,也许是(我会尽量保持音频的总延迟在一个视频帧以下,通常约为1/30秒=33毫秒)。如果你需要所有客户端以相位精确同步播放音频,你将需要比你现在做的更复杂的东西。 - Bjorn Roche
非常确定 - 我已经做了很长时间 - 但你应该始终检查其他人的计算 :) - Bjorn Roche
显示剩余3条评论
1个回答

8

这个问题已经解决。QAdioOutput有两种模式,分别是“推送”和“拉取”。我向QIODevice给出一个指针,并直接写入数据。解决方案:

读取UDP套接字:

void MainWindow::on_start_rx_triggered()
{
    udpSocketin = new QUdpSocket(this);
    udpSocketin->bind(localPORT);
    connect(udpSocketin, SIGNAL(readyRead()), this, SLOT(readDatagrams()));
    QDataStream out(&datagramout, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_4_7);
    timer2 = new QTimer (this);
    connect(timer2, SIGNAL(timeout()), this, SLOT(playbuff()));
    timer2->setInterval(15*9);
    audioout = new QAudioOutput(format, this);
    input = audioout->start();
}

void MainWindow::readDatagrams()
{
    if (udpSocketin->hasPendingDatagrams()){
    datagramin.resize(udpSocketin->pendingDatagramSize());
    qint64 receiveBytes = udpSocketin->readDatagram(datagramin.data(), datagramin.size());
    if (receiveBytes <= 0)
    {
        msg.warning(this, "File ERROR", "The end!", QMessageBox::Ok);
        emit on_stop_rx_triggered();
    }
    QDataStream in(&datagramin, QIODevice::ReadOnly);
    in.setVersion(QDataStream::Qt_4_7);
    quint64 size = 0;
    if(in.device()->size() > sizeof(quint64))
    {
        in >> size;
    }
    else return;
    in >> rxfilename;
    in >> name;
    in >> payloadin;
    emit jitterbuff();
}

void MainWindow::jitterbuff()
{
    if (buff_pos < SIZE_OF_BUF)
    {
        buffered.append(payloadin);
        buff_pos++;
    }
    else
    {
        timer2->start();
        buffered.clear();
        buff_pos = 0;
    }
}

void MainWindow::playbuff()
{
    if (!buffered.isEmpty())
    {
        buffer = new QBuffer(&buffered);
        buffer->open(QIODevice::ReadOnly);
        input->write(buffered);
        buffer->close();
    }
}

向UDP套接字写入:

void MainWindow::on_start_tx_triggered()
{
    timer1 = new QTimer (this);
    udpSocketout = new QUdpSocket(this);
    inputFile.setFileName(playlist.at(playIDfile));
    if (!inputFile.open(QIODevice::ReadOnly))
    {
        msg.warning(this, "File ERROR", "File not found!", QMessageBox::Ok);
        return;
    }
    fileinfo = new QFileInfo (inputFile);
    txfilename = fileinfo->fileName();
    ui->playedFile->setText("Now played: " + txfilename);
    connect(timer1, SIGNAL(timeout()), this, SLOT(writeDatagrams()));
    timer1->start(15);
}
void  MainWindow::writeDatagrams()
{

    if(!inputFile.atEnd()){
        payloadout = inputFile.read(SIZE_OF_SOUND);
        QDataStream out(&datagramout, QIODevice::WriteOnly);
        out.setVersion(QDataStream::Qt_4_7);
        out << qint64(0);
        out << txfilename;
        out << name;
        out << payloadout;
        out.device()->seek(qint64(0));
        out << qint64(datagramout.size() - sizeof(qint64));
        qint64 writtenBytes = udpSocketout->writeDatagram(datagramout, remoteHOST, remotePORT);
    }
}

如果有人出现了问题,我会尽力帮助他/她。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接