struct AudioBuffer{ char* data; ...

Tạo vào: 25 tháng 11, 2025

Trả lời bằng GPT-5.1 Thinking bởi Chat01

Câu hỏi

text
struct AudioBuffer{ char* data; int size; }; std::queue<AudioBuffer> audio_queue_hdmi, audio_queue_rgb;void std::condition_variable m_cond_hdmi, m_cond_rgb;RkMediaScreenSource2::RkMediaScreenSourcePriv2::OnDecAudioPacket(char* data,int size){ //qDebug() << QString("OnDecAudioPacket --- %1 - %2").arg(GetId()).arg(GetChnId()); if(GetId() == "1"){ PushAudioToHdmi(data, size); }else if(GetId() == "2"){ PushAudioToRgb(data, size); }else{ qDebug() << QString("--- no need to push ao"); }

}
void RkMediaScreenSource2::RkMediaScreenSourcePriv2::sendAoStreamThread(int id, int chn_id) {
qDebug() << QString("111111111111 sendAoStreamThread");
do {
auto self = shared_from_this();
std::thread th([this, self, id, chn_id] {
sendAoStream(id, chn_id);
});
th.detach();

} while (0);

}
void RkMediaScreenSource2::RkMediaScreenSourcePriv2::sendAo2StreamThread(int id, int chn_id) {
qDebug() << QString("111111111111 sendAo2StreamThread");
do {
auto self = shared_from_this();
std::thread th([this, self, id, chn_id] {
sendAo2Stream(id, chn_id);
});
th.detach();

} while (0);

}
int RkMediaScreenSource2::RkMediaScreenSourcePriv2::sendAoStream(int id, int chn_id){
RK_S32 result = RK_SUCCESS;
AUDIO_FRAME_S frame;
RK_U64 timeStamp = 0;
RK_S32 s32MilliSec = -1;

text
int devId = id, chnIndex = chn_id; RK_U8 *srcData = RK_NULL; AudioBuffer buffer{0}; AO_CHN_STATE_S pstStatus; while (!m_close) { auto t1 = std::chrono::steady_clock::now(); { std::unique_lock<std::mutex> lk(m_mtx); m_cond_hdmi.wait(lk, [this] { return !audio_queue_hdmi.empty(); }); if (!audio_queue_hdmi.empty()) { buffer = audio_queue_hdmi.front(); audio_queue_hdmi.pop(); m_cond_hdmi.notify_one(); } } srcData = reinterpret_cast<RK_U8*>(calloc(buffer.size, sizeof(RK_U8))); std::memcpy(srcData, buffer.data, buffer.size); frame.u32Len = buffer.size; frame.u64TimeStamp = timeStamp++; frame.enBitWidth = AUDIO_BIT_WIDTH_16; frame.enSoundMode = AUDIO_SOUND_MODE_STEREO; frame.bBypassMbBlk = RK_FALSE; MB_EXT_CONFIG_S extConfig; memset(&extConfig, 0, sizeof(extConfig)); //extConfig.pFreeCB = freeBuffFunc; extConfig.pOpaque = srcData; extConfig.pu8VirAddr = srcData; extConfig.u64Size = buffer.size; RK_MPI_SYS_CreateMB(&(frame.pMbBlk), &extConfig); __RETRY: result = RK_MPI_AO_SendFrame(devId, chnIndex, &frame, s32MilliSec); if (result != RK_SUCCESS) { qWarning() << QString("send frame fail, result = %1, TimeStamp = %2, s32MilliSec = %3").arg(result, 0, 16).arg(frame.u64TimeStamp).arg(s32MilliSec); goto __RETRY; } RK_MPI_MB_ReleaseMB(frame.pMbBlk); // 释放内存 freeBuffFunc(srcData); auto t2 = std::chrono::steady_clock::now(); qDebug() << QString("PushAudioToRgb elapsed time:%1 ms.").arg(Util::DurationMs(t1, t2)); } RK_MPI_AO_WaitEos(devId, chnIndex, s32MilliSec); return result;

}

int RkMediaScreenSource2::RkMediaScreenSourcePriv2::sendAo2Stream(int id, int chn_id){
RK_S32 result = RK_SUCCESS;
AUDIO_FRAME_S frame;
RK_U64 timeStamp = 0;
RK_S32 s32MilliSec = -1;

text
int devId = id, chnIndex = chn_id; RK_U8 *srcData = RK_NULL; AudioBuffer buffer{0}; AO_CHN_STATE_S pstStatus; while (!m_close) { auto t1 = std::chrono::steady_clock::now(); { std::unique_lock<std::mutex> lk(m_mtx); m_cond_rgb.wait(lk, [this] { return !audio_queue_rgb.empty(); }); if (!audio_queue_rgb.empty()) { buffer = audio_queue_rgb.front(); audio_queue_rgb.pop(); m_cond_rgb.notify_one(); } } srcData = reinterpret_cast<RK_U8*>(calloc(buffer.size, sizeof(RK_U8))); std::memcpy(srcData, buffer.data, buffer.size); frame.u32Len = buffer.size; frame.u64TimeStamp = timeStamp++; frame.enBitWidth = AUDIO_BIT_WIDTH_16; frame.enSoundMode = AUDIO_SOUND_MODE_STEREO; frame.bBypassMbBlk = RK_FALSE; MB_EXT_CONFIG_S extConfig; memset(&extConfig, 0, sizeof(extConfig));

extConfig.pOpaque = srcData;
extConfig.pu8VirAddr = srcData;
extConfig.u64Size = buffer.size;
RK_MPI_SYS_CreateMB(&(frame.pMbBlk), &extConfig);
__RETRY:
result = RK_MPI_AO_SendFrame(devId, chnIndex, &frame, s32MilliSec);
if (result != RK_SUCCESS) {
qWarning() << QString("send frame fail, result = %1, TimeStamp = %2, s32MilliSec = %3").arg(result, 0, 16).arg(frame.u64TimeStamp).arg(s32MilliSec);
goto __RETRY;
}
RK_MPI_MB_ReleaseMB(frame.pMbBlk);
// 释放内存
freeBuffFunc(srcData);
auto t2 = std::chrono::steady_clock::now();
qDebug() << QString("PushAudioToRgb elapsed time:%1 ms.").arg(Util::DurationMs(t1, t2));
}
RK_MPI_AO_WaitEos(devId, chnIndex, s32MilliSec);

return result;

}
OnDecAudioPacket这个函数是我的音频回调数据,data已经是处理完成的音频数据,sendAoStreamThread和sendAo2StreamThread是我的两路消费者线程,任务时将回调的音频数据写入,改如何修改设计这部分完整代码,给出你设计和修改的全部代码

Suy nghĩ

Proposing a solution for better design

It looks like there are issues with queue logic, condition variables, and buffer handling that could lead to race conditions and capacity problems. I'd suggest redesigning the data flow, possibly adopting a std::vector<uint8_t> or a unique_ptr<char[]> for AudioBuffer. We could improve the producer-consumer pattern while addressing memory management and synchronization.

Proposing improvements in design for audio queue

It seems like we can improve the design using std::deque<AudioBuffer> with a mutex and condition variable for synchronization. I suggest creating a unified AudioQueue class with push() and pop() methods. Regarding the user’s code, we may need to handle buffer copying properly to avoid issues with buffer reuse. I'll draft the updated code considering external APIs and necessary thread synchronization.

Improving design for thread safety and handling reuse

We need to ensure thread safety with a predicate (!queue.empty() || m_close) and account for spurious wake-ups. We also want to avoid unnecessary memory allocation and reuse buffers. I'll redesign the code to make audio_queue_hdmi and audio_queue_rgb class members, as the original snippet mistakenly defines them as global variables. It’s important to respect the user’s request to include all necessary code modifications while keeping the design more streamlined.

Designing the AudioQueue structure and class implementation

I’m proposing a structure where AudioBuffer uses a std::vector<uint8_t>. In the RkMediaScreenSourcePriv2 class, we have dedicated methods for handling HDMI and RGB audio streams, alongside an AudioQueue for each type with corresponding thread safety. User didn't provide headers, but I’ve adapted it based on available info while maintaining the GetId() method to differentiate channels.

Reviewing design considerations for AudioBuffer and thread safety

It seems we don't require external browsing, as the task is focused on optimizing code. For the AudioBuffer, we should allocate and copy data within PushAudioToHdmi to avoid potential issues with the decoder’s pointer being invalid later. Using std::vector would simplify memory management. For thread safety, we can ensure that m_close is properly managed using std::atomic<bool>. I'll propose maintaining simplicity over optimization, as the user likely prefers that.

Improving memory management and avoiding contention

The current design with the same mutex for both hdmi and rgb creates contention, which is manageable but could be better. We’ll change the synchronization pattern so that the consumer waits for the push thread, and the producer sends the notify_one. Also, for memory management, using std::vector inside AudioQueue should handle automatic memory cleanup, avoiding manual freeing and potential double-free issues. This will simplify things.

Proposing the updated audio buffer management

The changes to the code involve unifying the logic for HDMI and RGB audio processing, using a templated worker function or maintaining separate functions. The use of std::vector<uint8_t> within the AudioBuffer struct allows smoother memory management without worrying about manual allocation. Also, proper synchronization using mutexes ensures thread safety, and the use of std::atomic<bool> for m_close helps avoid race conditions.

Providing modified code with RAII design

I'll propose a more robust design using std::vector for the AudioBuffer struct to manage memory more reliably. The PushAudioToHdmi function will incorporate this change by reallocating and copying data using std::vector. While this will require updates elsewhere in the code to account for the new structure, it ensures safer memory management without manual malloc/free.

Planning design and providing code with necessary explanations

I’ll give a brief explanation in Chinese about the issues, followed by the new design: a single AudioQueue class for each output, using improved thread management. I’ll show the full code for relevant sections and leave out over-explanation, ensuring that it's clear without being overly verbose. I'll also clarify function signatures, including the needed corrections for the condition variables and mutex handling.

Choosing data structure and managing memory

To minimize rework, I’ll retain m_mtx for both audio queues, keeping the mutex as is. I’ll update the AudioBuffer to use std::vector<uint8_t>, which is simpler than std::unique_ptr, and is both flexible and memory-managed. Using std::vector avoids manual size handling while maintaining move-only semantics for queue operations. This simplifies memory management while ensuring correctness.

Designing and structuring the code for audio queue handling

To improve the design, I’m using std::vector<uint8_t> for the audio buffer instead of manually tracking the size. This allows for simpler memory management and integrates smoothly with the queue (std::queue<AudioBuffer>). I will also introduce a helper function for both HDMI and RGB streams to avoid duplicated code and to keep the mutex handling simplified. I'll ensure the code is clear and concise, and remember they'd need to merge this into their existing project for compatibility.

Thought for 3m 11s

Trả lời

先给你说结论:你现在这段代码里有几个比较危险/不太合理的点:

  • AudioBuffer 只是一个裸指针 + size,很容易出现悬空指针/重复释放
  • audio_queue_hdmi / rgb 是全局队列,和类设计不匹配,也不好管理生命周期;
  • consumer 线程用 m_cond_xxx.wait() 没有考虑 m_close,退出条件不优雅;
  • consumer 从队列 pop() 后再 notify_one() 没意义(目前也没有“队满”的逻辑);
  • goto __RETRY 可能死循环,完全不退让 CPU。

下面我给你一份可以直接套用的完整设计,把这块做成标准的生产者/消费者模型,并且用 RAII 管好内存。你只需要把原来相关的实现换成下面的代码,并在头文件里加上对应成员即可。


1. 头文件部分(类成员设计)

假设你有一个内部类:

cpp
// RkMediaScreenSource2.h 里,简化示意 class RkMediaScreenSource2 { public: class RkMediaScreenSourcePriv2 : public std::enable_shared_from_this<RkMediaScreenSourcePriv2> { public: // 回调入口 void OnDecAudioPacket(char* data, int size); // 启动两路 AO 发送线程(HDMI / RGB) void sendAoStreamThread(int id, int chn_id); // HDMI void sendAo2StreamThread(int id, int chn_id); // RGB // 关闭 void Stop(); // 析构里也可以调用 private: // 回调里调用的生产者接口 void PushAudioToHdmi(const char* data, int size); void PushAudioToRgb(const char* data, int size); // 具体消费者 int sendAoStream(int id, int chn_id); // HDMI int sendAo2Stream(int id, int chn_id); // RGB // 通用发送实现(内部复用) int sendAoStreamImpl(int devId, int chnIndex, std::queue<std::vector<uint8_t>>& q, std::condition_variable& cv, const char* logTag); private: // ===== 音频队列相关成员 ===== // 使用 vector<uint8_t> 管理 PCM 数据,防止内存泄漏/悬空指针 std::mutex m_mtx; // 一个锁同时保护两路队列(简单直接) std::condition_variable m_cond_hdmi; std::condition_variable m_cond_rgb; std::queue<std::vector<uint8_t>> m_queue_hdmi; std::queue<std::vector<uint8_t>> m_queue_rgb; std::atomic<bool> m_close{false}; // 队列长度上限,防止堆积撑爆内存 static constexpr size_t kMaxAudioQueueSize = 128; // 你原来就有的成员(示意) // std::string GetId() const; }; };

关键点:

  • 不再用 struct AudioBuffer{ char* data; int size; },而是直接用 std::vector<uint8_t>
  • 队列和条件变量全部放进类里,去掉全局变量。

2. 源文件实现部分(完整可替换)

cpp
#include <queue> #include <vector> #include <mutex> #include <condition_variable> #include <thread> #include <atomic> #include <chrono> #include <cstring> // Rockchip / Qt 相关头自己包含 // #include "rkmedia_screen_source2.h" // #include "rk_mpi_ao.h" // #include "rk_mpi_mb.h" // #include <QDebug> // 假定你已有:void freeBuffFunc(void* p); // 假定你已有:double Util::DurationMs(const std::chrono::steady_clock::time_point&, // const std::chrono::steady_clock::time_point&); // ====================== 回调:生产者 ====================== void RkMediaScreenSource2::RkMediaScreenSourcePriv2::OnDecAudioPacket(char* data, int size) { // data 是解码好的 PCM,这里立即拷贝一份进队列,避免回调返回后指针失效 if (!data || size <= 0) { return; } // qDebug() << QString("OnDecAudioPacket --- id=%1 ch=%2").arg(GetId()).arg(GetChnId()); if (GetId() == "1") { // HDMI 路 PushAudioToHdmi(data, size); } else if (GetId() == "2") { // RGB 路 PushAudioToRgb(data, size); } else { qDebug() << QString("--- no need to push ao, id=%1").arg(GetId()); } } void RkMediaScreenSource2::RkMediaScreenSourcePriv2::PushAudioToHdmi(const char* data, int size) { if (m_close.load(std::memory_order_acquire)) { return; } std::vector<uint8_t> buf(size); std::memcpy(buf.data(), data, static_cast<size_t>(size)); { std::lock_guard<std::mutex> lk(m_mtx); // 简单限长策略:队列满了丢掉最老一帧,防止堆积 if (m_queue_hdmi.size() >= kMaxAudioQueueSize) { m_queue_hdmi.pop(); } m_queue_hdmi.push(std::move(buf)); } m_cond_hdmi.notify_one(); } void RkMediaScreenSource2::RkMediaScreenSourcePriv2::PushAudioToRgb(const char* data, int size) { if (m_close.load(std::memory_order_acquire)) { return; } std::vector<uint8_t> buf(size); std::memcpy(buf.data(), data, static_cast<size_t>(size)); { std::lock_guard<std::mutex> lk(m_mtx); if (m_queue_rgb.size() >= kMaxAudioQueueSize) { m_queue_rgb.pop(); } m_queue_rgb.push(std::move(buf)); } m_cond_rgb.notify_one(); } // ====================== 启动消费者线程 ====================== void RkMediaScreenSource2::RkMediaScreenSourcePriv2::sendAoStreamThread(int id, int chn_id) { qDebug() << QString("sendAoStreamThread (HDMI) start. dev=%1 chn=%2") .arg(id).arg(chn_id); auto self = shared_from_this(); std::thread th([this, self, id, chn_id]() { sendAoStream(id, chn_id); }); th.detach(); } void RkMediaScreenSource2::RkMediaScreenSourcePriv2::sendAo2StreamThread(int id, int chn_id) { qDebug() << QString("sendAo2StreamThread (RGB) start. dev=%1 chn=%2") .arg(id).arg(chn_id); auto self = shared_from_this(); std::thread th([this, self, id, chn_id]() { sendAo2Stream(id, chn_id); }); th.detach(); } // ====================== 通用发送实现 ====================== int RkMediaScreenSource2::RkMediaScreenSourcePriv2::sendAoStreamImpl( int devId, int chnIndex, std::queue<std::vector<uint8_t>>& q, std::condition_variable& cv, const char* logTag) { RK_S32 result = RK_SUCCESS; AUDIO_FRAME_S frame; std::memset(&frame, 0, sizeof(frame)); RK_U64 timeStamp = 0; RK_S32 s32MilliSec = -1; RK_U8* srcData = RK_NULL; while (!m_close.load(std::memory_order_acquire)) { // 1. 从队列取一帧 std::vector<uint8_t> buffer; { std::unique_lock<std::mutex> lk(m_mtx); cv.wait(lk, [this, &q]() { return m_close.load(std::memory_order_acquire) || !q.empty(); }); if (m_close.load(std::memory_order_acquire) && q.empty()) { break; // 收到关闭且没有数据,安全退出 } buffer = std::move(q.front()); q.pop(); } if (buffer.empty()) { continue; } auto t1 = std::chrono::steady_clock::now(); // 2. 为 RK_MPI_AO 准备 MB 数据 srcData = reinterpret_cast<RK_U8*>(calloc(buffer.size(), sizeof(RK_U8))); if (!srcData) { qWarning() << QString("[%1] calloc audio buffer failed, size=%2") .arg(logTag) .arg(buffer.size()); continue; } std::memcpy(srcData, buffer.data(), buffer.size()); std::memset(&frame, 0, sizeof(frame)); frame.u32Len = static_cast<RK_U32>(buffer.size()); frame.u64TimeStamp = timeStamp++; frame.enBitWidth = AUDIO_BIT_WIDTH_16; frame.enSoundMode = AUDIO_SOUND_MODE_STEREO; frame.bBypassMbBlk = RK_FALSE; MB_EXT_CONFIG_S extConfig; std::memset(&extConfig, 0, sizeof(extConfig)); // extConfig.pFreeCB = freeBuffFunc; // 如果你希望 RK 在 MB 释放时帮你 free,可以配置 extConfig.pOpaque = srcData; extConfig.pu8VirAddr = srcData; extConfig.u64Size = buffer.size(); RK_MPI_SYS_CreateMB(&(frame.pMbBlk), &extConfig); // 3. 发送,失败时稍微重试一下,避免死循环 const int kMaxRetry = 5; int retry = 0; do { result = RK_MPI_AO_SendFrame(devId, chnIndex, &frame, s32MilliSec); if (result == RK_SUCCESS) { break; } qWarning() << QString("[%1] send frame fail, result = 0x%2, TimeStamp = %3, s32MilliSec = %4, retry=%5") .arg(logTag) .arg(result, 0, 16) .arg(frame.u64TimeStamp) .arg(s32MilliSec) .arg(retry); if (m_close.load(std::memory_order_acquire)) { break; } std::this_thread::sleep_for(std::chrono::milliseconds(2)); } while (++retry < kMaxRetry && !m_close.load(std::memory_order_acquire)); RK_MPI_MB_ReleaseMB(frame.pMbBlk); freeBuffFunc(srcData); // 这里只释放我们自己 calloc 的缓冲 auto t2 = std::chrono::steady_clock::now(); qDebug() << QString("[%1] send audio elapsed time: %2 ms.") .arg(logTag) .arg(Util::DurationMs(t1, t2)); } RK_MPI_AO_WaitEos(devId, chnIndex, s32MilliSec); return result; } // ====================== HDMI / RGB 两个具体实现 ====================== int RkMediaScreenSource2::RkMediaScreenSourcePriv2::sendAoStream(int id, int chn_id) { return sendAoStreamImpl(id, chn_id, m_queue_hdmi, m_cond_hdmi, "HDMI"); } int RkMediaScreenSource2::RkMediaScreenSourcePriv2::sendAo2Stream(int id, int chn_id) { return sendAoStreamImpl(id, chn_id, m_queue_rgb, m_cond_rgb, "RGB"); } // ====================== 停止/析构 ====================== void RkMediaScreenSource2::RkMediaScreenSourcePriv2::Stop() { bool expected = false; if (m_close.compare_exchange_strong(expected, true, std::memory_order_acq_rel)) { // 只在第一次调用 Stop 时唤醒 m_cond_hdmi.notify_all(); m_cond_rgb.notify_all(); } } // 析构里记得调用 // RkMediaScreenSource2::RkMediaScreenSourcePriv2::~RkMediaScreenSourcePriv2() // { // Stop(); // }

3. 设计要点总结(为什么这么改)

  1. 数据所有权明确

    • 原来的 AudioBuffer{ char* data; int size; } 很容易搞不清 data 是谁 malloc、谁 free。
    • 现在队列里直接存 std::vector<uint8_t>,由 C++ 自动管理内存,不会悬空/重复释放。
  2. 回调里只做“拷贝 + 入队 + notify”

    • OnDecAudioPacket 很有可能在解码线程里调用,不能做耗时操作。
    • 现在只拷贝一份 PCM 到队列,然后唤醒消费者线程,足够轻量。
  3. 消费者线程统一走 sendAoStreamImpl

    • HDMI 和 RGB 完全同一套逻辑,只是队列和 cond 不同;用一个实现减少错误。
  4. 条件变量退出逻辑正确

    • wait() 的谓词检查:m_close || !q.empty(),并且在 m_close && q.empty 时 break,避免线程无法退出。
  5. 避免无限死循环重试

    • 给发送加了 kMaxRetry 和一点 sleep,防止 RK_MPI_AO_SendFrame 一直失败时 CPU 空转。

如果你愿意,我还可以再帮你把这套设计抽成一个通用的 AudioQueue 小类,这样 HDMI / RGB / 以后新增的路数都可以直接复用。

Chia sẻ Q&A này