You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Qt 6.x多媒体:麦克风音频同时输出至扬声器与UDP网络

Qt 6.x 音频捕获、本地播放与RTP/UDP传输实现问题

我在Qt桌面支持论坛提问未获有效解答,现求助:需在Qt 6.x环境下完成以下三个核心功能:

  • 通过QAudioSource捕获麦克风音频
  • 将捕获的音频实时输出至QAudioSink(扬声器)
  • 对音频数据封装RTP头后通过UDP网络传输

当前已实现内容

  • 基于Qt 6.x音频输出示例,完成了从自定义只读QIODevice(mpIODeviceSink)向扬声器播放合成音频的功能
  • 在同一工作线程中,通过QAudioSource将麦克风音频写入只写QIODevice(mpIODeviceSource),并通过自定义audioCaptured信号捕获数据(目前仅做日志记录)

待确认的问题

  1. 我考虑使用读写模式的中间QIODevice(例如QBuffer)实现一对多输出,同时给扬声器和UDP传输提供数据,这个方案是否可行?
  2. UDP传输需要发送固定大小的定时数据报来保障音频质量,是否需要子类化QAudioSink来关联UDPSocket?

相关代码片段

信号连接代码

connect(mpIODeviceSource.get(), &IODeviceSource::audioCaptured,
        this, &RtpWorker::handleAudioCaptured, Qt::UniqueConnection);

RtpWorker类头文件代码

#pragma once

// SYSTEM INCLUDES
#include <QTimer>
#include <QThread>
#include <QUdpSocket>
#include <QAudioSink>
#include <QAudioSource>
#include <spdlog/common.h>

// APPLICATION INCLUDES
#include "rtp/rtp.h"
#include "IODeviceSink.h"
#include "IODeviceSource.h"

// DEFINES
// EXTERNAL FUNCTIONS
// EXTERNAL VARIABLES
// CONSTANTS
// STRUCTS
// TYPEDEFS
// FORWARD DECLARATIONS
class QTimer;
class QTimerEvent;
class QUdpSocket;
class QAudioSource;
class QAudioBuffer;
class QAudioSink;

const auto gHeaderLenLambda = [](const QByteArray& rRawHeader)->qsizetype {
    const auto header = reinterpret_cast<const rtp_hdr_t*>(rRawHeader.constData());
    qsizetype headerLen = sizeof(rtp_hdr_t) + header->cc * sizeof(uint32_t);
    // header extension
    if (header->x != 0u) {
        const auto ext = reinterpret_cast<const rtp_hdr_ext_t*>(
            rRawHeader.constData() + headerLen);
        const auto ext_len = ntohs(ext->len);
        headerLen += static_cast<qsizetype>(
            sizeof(uint32_t) + (ext_len * sizeof(uint32_t)));
    }
    // must have sufficient bytes in raw header
    if (headerLen > rRawHeader.size()) {
        return -1;
    }
    return headerLen;
};


/**
 * Active Object QT pattern - see tutorial https://www.youtube.com/watch?v=SncJ3D-fO7g
 * See also: https://forum.qt.io/topic/140465/qt-6-x-qaudiosink-potential-bug
 */
class RtpWorker final : public QObject {
    Q_OBJECT
public:
    //! mpThread cannot have parent, as we need to move this object
    //! Also, note most QT class members need 'this' as parent for
    //! thread affinity to work correctly.
    RtpWorker();

    //! these  deleted constructors and assignment operators are not
    //! strictly required as the QObject superclass prevents copy
    //! but left in for clarity
    RtpWorker(const RtpWorker&) = delete;
    RtpWorker(RtpWorker&&) noexcept = delete;
    RtpWorker& operator=(const RtpWorker&) = delete;
    RtpWorker& operator=(RtpWorker&&) noexcept = delete;

    //! virtual destructor overload
    ~RtpWorker() override {
        QMetaObject::invokeMethod(this, "cleanup");
        mpThread->wait();
    }
Q_SIGNALS:
    void stop();
    void start(const qint64, const QString&, const quint16,
        const qint64, const QByteArray&, const QByteArrayList&);
    void datagramsReady(const QByteArrayList&);
    void bytesWritten(qint64, bool);
    void watchdogStatusChanged(bool, const QString&);

    /**
     * Signal to asynchronously notify application thread .
     *
     * @param rStatusText [in] Status area notification text.
     * @param rStyleSheet [in] Text stylesheet.
     * @param timeout     [in] Timeout after which the text
     *                    disappears.
     */
    void workerStatusChange(const QString& rStatusText,
        const QString& rStyleSheet, int timeout = 0) const;

    /**
     * IPC Signal from worker thread used to safely update
     * mTXBufferList.
     *
     * @param aTSIncrement [in] Timestamp increment per datagram.
     * @param rAudioData   [in] raw multichannel audio.
     */
    void updateAudio(const qint64 aTSIncrement, const QByteArrayList& rAudioData);

    //! Add log entry
    void addLogEntry(const spdlog::level::level_enum, const QString&);

    // slots are all called while in the worker thread context
private Q_SLOTS:
    /**
     * Start signal handler.
     *
     * <p>This slot runs in the worker thread context.  Creates a
     * buffered list of transmit UDP datagrams.  Each datagram is prefixed
     * with an RTP header (including a sequence# & timestamp).
     *
     * <p>The first datagram will always have the marker bit set.
     *
     * @param aTSIncrement   [in] Timestamp increment per datagram.
     * @param rTargetIP      [in] Target IP address.
     * @param aTargetPort    [in] Target UDP port.
     * @param aDurationUsecs
     *                       [in] Duration (specified in microseconds) to
     *                       sleep between recurring timer callbacks.
     *                       This time represents the duration for all
     *                       <code>mTXBufferList</code> datagrams.
     * @param rHeader        [in] RTP header consisting of a QByteArray
     *                       containing an rtp_hdr_t with optional contributing
     *                       sources (up to 16) & rtp_hdr_ext_t fields & data if
     *                       the x bit of the rtp_hdr_t is set.
     * @param rAudioData     [in] raw multichannel audio.
     */
    void handleStart(
        const qint64 aTSIncrement,
        const QString& rTargetIP,
        const quint16 aTargetPort,
        const qint64 aDurationUsecs,
        const QByteArray& rHeader,
        const QByteArrayList& rAudioData);

    /**
     * Slot to handle captured audio data.
     *
     * <p>Saves the audio data to a QIODevice.
     *
     * @param rAudioBuffer [in] audio data with (containing valid
     *                     QAudioFormat).
     */
    void handleAudioCaptured(const QAudioBuffer& rAudioBuffer);

    /**
     * Audio updated slot handler.
     *
     * @param aTSIncrement [in] Timestamp increment per datagram.
     * @param rAudioData   [in] raw multichannel audio.
     */
    void handleAudioUpdated(
        const qint64 aTSIncrement,
        const QByteArrayList& rAudioData);

    /**
     * QUdpSocket slot readyRead handler.
     *
     * <p>Slot called when UDP data returned via echo to this
     * thread.  When called drain any pending datagrams and pet the
     * inactivity watchdog timer.
     */
    void readyRead();

    /**
     * Slot to handle bytes written.
     *
     * <p>Called when bytes successfully sent to target where we
     * emit the bytesWritten signal to update the stats area in the
     * GUI.
     *
     * @param aBytesWritten [in] number of bytes written to socket.
     */
    void handleBytesWritten(qint64 aBytesWritten) {
        // forward to GUI
        Q_EMIT bytesWritten(aBytesWritten, false);
    }

    /**
     * Stop slot handler.
     *
     * <p>This is called in the worker thread context.  Close open
     * timers, sockets, QAudioSources and QAudioSinks.
     */
    void handleStop();


    /**
     * Cleanup method - runs in worker thread context.
     */
    void cleanup();

    /**
     * Timer callback event handler.
     *
     * @param event [in] timer event.
     */
    void timerEvent(QTimerEvent* event) override;
private:
    std::unique_ptr<QThread> mpThread;
    std::unique_ptr<QBasicTimer> mpTimer;
    std::unique_ptr<QTimer> mpWatchdog;
    std::unique_ptr<QUdpSocket> mpSocket;
    //! Audio Source (microphone).
    std::unique_ptr<QAudioSource> mpAudioSource;
    //! IODevice connected to mpAudioSource
    std::unique_ptr<IODeviceSource> mpIODeviceSource;
    //! Audio Sink (speaker).
    std::unique_ptr<QAudioSink> mpAudioSink;
    //! IODevice connected to mpAudioSink
    std::unique_ptr<IODeviceSink> mpIODeviceSink;

    QByteArrayList mTXBufferList;
    QHostAddress mTargetIP;
    quint16 mTargetPort;
    qint64 mTSIncrement;
    qint64 mTXCounter;
};

内容的提问来源于stack exchange,提问作者johnco3

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 01:30:50